Updated export flow visuals, new upgrade banner, light export (#2067)
* Backup: Exclude all disappearing messages from export * Reset overall statistics on start of backup * Updated visual design for the export process * On any export after first, don't include encryption info * Wire up the upgrade banner with dismiss button Unlike the previous implementation of this banner, it does not go away when the user clicks on a conversation. * Small tweak to the export error page: text width, error->isError * Responding to PR feedback: some refactors, typo fix, dev server - backupToDirectory -> exportToDirectory - backup.js attachments -> numAttachments - migration_view.js comment typo fix - background.js revert back to the staging server url - database.js remove dev-only commented-out migration * Incremental rollout for upgrade banner * Fix timing, address other PR feedback * exportingToDirectory -> exportToDirectory * inboxView: call proper method to show export process * Remove unnecessary cancel() if first export was never finished Since we do want to keep track of whether previous exports were even attempted, this works against our 'light export/import' strategy. It's also unnecessary, because the user will start at the first step of the process. * Log if we get the same value back from the s3 trigger file * Migration.cancel() - don't reset 'everAttempted' flag This would prevent us from doing the light export that second time through.
@@ -1,15 +1,7 @@
|
||||
{
|
||||
"cancelMigration": {
|
||||
"message": "Cancel migration",
|
||||
"description": "Shown on 'export complete' screen, allowing user to use Chrome App again"
|
||||
},
|
||||
"continueMigration": {
|
||||
"message": "Continue migration",
|
||||
"description": "Shown on 'cancel migration' confirmation dialog, dismissing the dialog without restoring Chrome App use',"
|
||||
},
|
||||
"cancelWarning": {
|
||||
"message": "Are you sure? If you import the data you've already exported, it will conflict with this installation. Please consider deleting all exported data if you continue with cancellation.",
|
||||
"description": "Shown on a confirmation dialog presented before the user cancels out of the migration process"
|
||||
"close": {
|
||||
"message": "Close",
|
||||
"description": "Shown on a button to dismiss a dialog box"
|
||||
},
|
||||
"loading": {
|
||||
"message": "Loading...",
|
||||
@@ -19,63 +11,101 @@
|
||||
"message": "Install is complete",
|
||||
"description": "Button to click when user has installed the new Signal Desktop"
|
||||
},
|
||||
"migrationWarning": {
|
||||
"message": "The Signal Desktop Chrome app has been deprecated. Would you like to migrate to the new Signal Desktop now?",
|
||||
"description": "Warning notification that this version of the app has been deprecated and the user must migrate"
|
||||
"upgradeBanner": {
|
||||
"message": "The next generation of Signal Desktop is here.",
|
||||
"description": "Shown at the top of the application prompting the user to upgrade to the new, standalone version of the app."
|
||||
},
|
||||
"upgradeNow": {
|
||||
"message": "Upgrade now",
|
||||
"description": "The button shown in the upgrade banner, starting the upgrade process."
|
||||
},
|
||||
"migrateToStandalone": {
|
||||
"message": "Migrate to standalone",
|
||||
"description": "Menu option to begin migrating this client to Electron"
|
||||
},
|
||||
"migrateInstallStep": {
|
||||
"message": "The first step is to install the new Signal Desktop.",
|
||||
"description": "The first step in the export process; installing the standalone desktop app, to ensure it is available for that platform."
|
||||
},
|
||||
"exportInstructions": {
|
||||
"message": "Now, choose a directory to store this application's exported data. It will contain your message history and sensitive cryptographic data, so be sure to save it somewhere private.",
|
||||
"description": "Description of the export process"
|
||||
"saveDataPrompt": {
|
||||
"message": "Your messages, contacts, groups, and other information can be seamlessly transferred to the new Signal Desktop. Select the folder where your exported Signal data should be saved.",
|
||||
"description": "Explanation of what will happen when the user presses the 'choose folder' button during the upgrade process"
|
||||
},
|
||||
"migrate": {
|
||||
"message": "Migrate",
|
||||
"description": "Button label to begin migrating this client to Electron"
|
||||
"chooseFolder": {
|
||||
"message": "Choose folder",
|
||||
"description": "Button which pops up a directory selection dialog, and starts saving data to the selected folder"
|
||||
},
|
||||
"migrateToStandalone": {
|
||||
"message": "Migrate to standalone",
|
||||
"description": "Menu option to begin migrating this client to Electron"
|
||||
"startExportHeader": {
|
||||
"message": "Upgrade to the new Signal Desktop",
|
||||
"description": "Header shown when starting the export and upgrade process"
|
||||
},
|
||||
"export": {
|
||||
"message": "Choose directory",
|
||||
"description": "Button to allow the user to export all data from app as part of migration process"
|
||||
"startExportIntro": {
|
||||
"message": "Signal Desktop is now available as a standalone application that features faster performance, expanded emoji support (with a brand-new emoji selector), enhanced notifications, and many other improvements. This process will prepare your Signal data for a smooth transition to upgrade.",
|
||||
"description": "Description of the why and the what of the export and upgrade process"
|
||||
},
|
||||
"exportAgain": {
|
||||
"message": "Export again",
|
||||
"description": "If user has already exported once, this button allows user to do it again if needed"
|
||||
"imReady": {
|
||||
"message": "I'm ready",
|
||||
"description": "Button to kick off the export and upgrade process"
|
||||
},
|
||||
"installHeader": {
|
||||
"message": "Select your Operating System",
|
||||
"description": "Header text for the install step of the export and upgrade flow"
|
||||
},
|
||||
"installIntro": {
|
||||
"message": "Download and install the new Signal Desktop for your chosen OS.",
|
||||
"description": "Introduction text for the install step of the export and upgrade flow"
|
||||
},
|
||||
"macOS": {
|
||||
"message": "macOS",
|
||||
"description": "The name of Apple's desktop operating system"
|
||||
},
|
||||
"windows": {
|
||||
"message": "Windows",
|
||||
"description": "The name of Microsoft's desktop operating system"
|
||||
},
|
||||
"debianLinux": {
|
||||
"message": "Debian-based Linux",
|
||||
"description": "A succinct name for the flavors of linux based on Debian"
|
||||
},
|
||||
"installed": {
|
||||
"message": "It's installed",
|
||||
"description": "The button indicating that the user is done with the installation step"
|
||||
},
|
||||
"saveHeader": {
|
||||
"message": "Save your data",
|
||||
"description": "The header for the choose folder step of the export and upgrade flow"
|
||||
},
|
||||
"completeHeader": {
|
||||
"message": "Success!",
|
||||
"description": "The header for the finish step of the export and upgrade flow"
|
||||
},
|
||||
"completeIntro": {
|
||||
"message": "Your Signal data was successfully saved here:",
|
||||
"description": "A label for the location of the files we put on disk during the export"
|
||||
},
|
||||
"completeNextSteps": {
|
||||
"message": "You are ready to make your move. Launch the new Signal Desktop and follow the instructions to complete the migration process.",
|
||||
"description": "A summary of the next steps to finish the export and upgrade process"
|
||||
},
|
||||
"completeSignoff": {
|
||||
"message": "We hope that you enjoy the new features. We're just getting started. Thanks for using Signal Desktop.",
|
||||
"description": "A warm signoff now that the export and upgrade process is complete"
|
||||
},
|
||||
"exportErrorHeader": {
|
||||
"message": "Something went wrong!",
|
||||
"description": "Header shown at the top of the error screen if something goes wrong during data export"
|
||||
},
|
||||
"exportError": {
|
||||
"message": "Unfortunately, something went wrong during the export. First, double-check your target empty directory for write access and enough space. Then, please submit a debug log so we can help you get migrated!",
|
||||
"message": "Unfortunately, something went wrong during the export. First, check that you have enough space where you saved the file. Then, please submit a debug log so we can help you get migrated!",
|
||||
"description": "Helper text if the user went forward on migrating the app, but ran into an error"
|
||||
},
|
||||
"confirmMigration": {
|
||||
"message": "Start migration process? You will not be able to send or receive Signal messages from this application while the migration is in progress.",
|
||||
"description": "Confirmation dialogue when beginning migration"
|
||||
"chooseFolderAndTryAgain": {
|
||||
"message": "Choose folder and try again",
|
||||
"description": "Button shown on the export error screen allowing user to try again"
|
||||
},
|
||||
"migrationDisconnecting": {
|
||||
"message": "Disconnecting...",
|
||||
"description": "Displayed while we wait for pending incoming messages to process"
|
||||
},
|
||||
"exporting": {
|
||||
"message": "Please wait while we export your data. It may take several minutes. You can still use Signal on your phone and other devices during this time.",
|
||||
"description": "Message shown on the migration screen while we export data"
|
||||
},
|
||||
"exportComplete": {
|
||||
"message": "Your data has been exported to: <p><b>$location$</b></p> You'll be able to import this data as you set up <a target='_blank' href='https://support.whispersystems.org/hc/en-us/articles/214507138'>the new Signal Desktop</a>.",
|
||||
"description": "Message shown on the migration screen when we are done exporting data",
|
||||
"placeholders": {
|
||||
"location": {
|
||||
"content": "$1",
|
||||
"example": "/Users/someone/somewhere"
|
||||
}
|
||||
}
|
||||
},
|
||||
"installNewSignal": {
|
||||
"message": "Install new Signal Desktop",
|
||||
"description": "When export is complete, a button shows which sends user to Signal Desktop install instructions"
|
||||
"savingData": {
|
||||
"message": "Saving your data",
|
||||
"description": "Message shown during the upgrade process while we export data"
|
||||
},
|
||||
"selectedLocation": {
|
||||
"message": "your selected location",
|
||||
|
||||
196
background.html
@@ -2,36 +2,6 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta charset='utf-8'>
|
||||
<script type='text/x-tmpl-mustache' id='app-migration-screen'>
|
||||
<div class='content'>
|
||||
<img src='/images/icon_128.png'>
|
||||
{{ ^hideProgress }}
|
||||
<div class='container'>
|
||||
<span class='dot'></span>
|
||||
<span class='dot'></span>
|
||||
<span class='dot'></span>
|
||||
</div>
|
||||
{{ /hideProgress }}
|
||||
<div class='message'>{{& message }}</div>
|
||||
<div>
|
||||
{{ #installButton }}
|
||||
<button class='install grey'>{{ installButton }}</button>
|
||||
{{ /installButton }}
|
||||
{{ #nextButton }}
|
||||
<button class='next grey'>{{ nextButton }}</button>
|
||||
{{ /nextButton }}
|
||||
{{ #exportButton }}
|
||||
<button class='export grey'>{{ exportButton }}</button>
|
||||
{{ /exportButton }}
|
||||
{{ #debugLogButton }}
|
||||
<button class='debug-log grey'>{{ debugLogButton }}</button>
|
||||
{{ /debugLogButton }}
|
||||
{{ #cancelButton }}
|
||||
<button class='cancel grey'>{{ cancelButton }}</button>
|
||||
{{ /cancelButton }}
|
||||
</div>
|
||||
</div>
|
||||
</script>
|
||||
<script type='text/x-tmpl-mustache' id='app-loading-screen'>
|
||||
<div class='content'>
|
||||
<img src='/images/icon_128.png'>
|
||||
@@ -110,12 +80,15 @@
|
||||
<a target='_blank' href='https://chrome.google.com/webstore/detail/bikioccmkafdpakkkcpdbppfkghcmihk'>
|
||||
<button class='upgrade'>{{ upgrade }}</button>
|
||||
</a>
|
||||
{{ expiredWarning }}
|
||||
</script>
|
||||
<script type='text/x-tmpl-mustache' id='migration_alert'>
|
||||
<button class='migrate'>{{ migrate }}</button>
|
||||
<div class='message'>
|
||||
{{ migrationWarning }}
|
||||
{{ expiredWarning }}
|
||||
</div>
|
||||
</script>
|
||||
<script type='text/x-tmpl-mustache' id='upgrade_banner'>
|
||||
<span class='x banner-close'></span>
|
||||
<button class='migrate'>{{ upgradeNow }}</button>
|
||||
<div class='message'>
|
||||
{{ upgradeMessage }}
|
||||
</div>
|
||||
</script>
|
||||
<script type='text/x-tmpl-mustache' id='banner'>
|
||||
@@ -619,6 +592,159 @@
|
||||
{{/action }}
|
||||
</script>
|
||||
|
||||
<script type='text/x-tmpl-mustache' id='linux-install-instructions'>
|
||||
<div class='confirmation-dialog modal'>
|
||||
<div class="content" tabindex="2">
|
||||
<div class='header'>{{ header }}</div>
|
||||
<pre class='instructions'>curl -s https://updates.signal.org/desktop/apt/keys.asc | sudo apt-key add -
|
||||
echo "deb [arch=amd64] https://updates.signal.org/desktop/apt xenial main" | sudo tee -a /etc/apt/sources.list.d/signal-xenial.list
|
||||
sudo apt update && sudo apt install signal-desktop
|
||||
/opt/Signal/signal-desktop --import</pre>
|
||||
<div class='buttons'>
|
||||
<button class='ok' tabindex='1'>{{ ok }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type='text/x-tmpl-mustache' id='migration-flow-template'>
|
||||
{{#isStep1}}
|
||||
<div id='step1' class='step'>
|
||||
<div class='inner'>
|
||||
<div class='step-body'>
|
||||
<img class='banner-image' src='/images/icon_128.png'>
|
||||
<div class='header'>{{ startHeader }}</div>
|
||||
<div class='body-text-wide'>{{ start }}</div>
|
||||
</div>
|
||||
<div class='nav'>
|
||||
<div>
|
||||
<a class='button start'>{{ startButton }}</a>
|
||||
</div>
|
||||
<div>
|
||||
<a class='link cancel'>{{ cancelButton }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{/isStep1}}
|
||||
{{#isStep2}}
|
||||
<div id='step2' class='step'>
|
||||
<div class='inner'>
|
||||
<div class='step-body'>
|
||||
<div class='header'>{{ installHeader }}</div>
|
||||
<p>{{ install }}</p>
|
||||
</div>
|
||||
<div class='install-container'>
|
||||
<div class='install'>
|
||||
<a class='install-mac' target='_blank' href='{{ macLink }}'>
|
||||
<span class='install-icon apple'></span>
|
||||
<div>Mac</div>
|
||||
</a>
|
||||
</div>
|
||||
<div class='install'>
|
||||
<a class='install-windows' target='_blank' href='{{ windowsLink }}'>
|
||||
<span class='install-icon windows'></span>
|
||||
<div>Windows</div>
|
||||
</a>
|
||||
</div>
|
||||
<div class='install'>
|
||||
<a class='install-linux' href='#'>
|
||||
<span class='install-icon linux'></span>
|
||||
<div>Debian-based Linux</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class='nav'>
|
||||
<div>
|
||||
<a class='button installed'>{{ installCompleteButton }}</a>
|
||||
</div>
|
||||
<div>
|
||||
<a class='link cancel'>{{ cancelButton }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{/isStep2}}
|
||||
{{#isStep3}}
|
||||
<div id='step3' class='step'>
|
||||
<div class='inner'>
|
||||
<div class='step-body'>
|
||||
<span class='banner-icon folder-outline'></span>
|
||||
<div class='header'>{{ chooseHeader }}</div>
|
||||
<div class='body-text-wide'>{{ choose }}</div>
|
||||
</div>
|
||||
<div class='nav'>
|
||||
<div>
|
||||
<a class='button choose'>{{ chooseButton }}</a>
|
||||
</div>
|
||||
<div>
|
||||
<a class='link cancel'>{{ cancelButton }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{/isStep3}}
|
||||
{{#isStep4}}
|
||||
<div id='step4' class='step'>
|
||||
<div class='inner'>
|
||||
<div class='step-body'>
|
||||
<span class='banner-icon export'></span>
|
||||
<div class='header'>{{ exportHeader }}</div>
|
||||
</div>
|
||||
<div class='progress'>
|
||||
<div class='bar-container'>
|
||||
<div class='bar progress-bar progress-bar-striped active'></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class='nav'>
|
||||
<div>
|
||||
<a class='link debug-log'>{{ debugLogButton }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{/isStep4}}
|
||||
{{#isStep5}}
|
||||
<div id='step5' class='step'>
|
||||
<div class='inner'>
|
||||
<div class='step-body'>
|
||||
<span class='banner-icon check-circle-outline'></span>
|
||||
<div class='header'>{{ completeHeader }}</div>
|
||||
<div class='body-text-wide center'>{{ completeIntro }}</div>
|
||||
<div class='export-location'>{{ completeLocation }}</div>
|
||||
<div class='body-text-wide'>{{ completeNextSteps }}</div>
|
||||
<div class='body-text-wide'>{{ completeSignoff }}</div>
|
||||
</div>
|
||||
<div class='nav'>
|
||||
<div>
|
||||
<a class='link debug-log'>{{ debugLogButton }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{/isStep5}}
|
||||
{{#isError}}
|
||||
<div id='error' class='step'>
|
||||
<div class='inner error-dialog clearfix'>
|
||||
<div class='step-body'>
|
||||
<span class='banner-icon alert-outline'></span>
|
||||
<div class='header'>{{ errorHeader }}</div>
|
||||
<div class='body-text-wide'>{{ error }}</div>
|
||||
</div>
|
||||
<div class='nav'>
|
||||
<div>
|
||||
<a class='button export'>{{ tryAgain }}</a>
|
||||
</div>
|
||||
<div>
|
||||
<a class='link debug-log'>{{ debugLogButton }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{/isError}}
|
||||
</script>
|
||||
|
||||
|
||||
<script type='text/x-tmpl-mustache' id='install_flow_template'>
|
||||
<div id='step1' class='step'>
|
||||
<div class='inner'>
|
||||
|
||||
1
images/alert-outline.svg
Normal file
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24" height="24" viewBox="0 0 24 24"><path d="M12,2L1,21H23M12,6L19.53,19H4.47M11,10V14H13V10M11,16V18H13V16" /></svg>
|
||||
|
After Width: | Height: | Size: 357 B |
1
images/apple.svg
Normal file
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24" height="24" viewBox="0 0 24 24"><path d="M18.71,19.5C17.88,20.74 17,21.95 15.66,21.97C14.32,22 13.89,21.18 12.37,21.18C10.84,21.18 10.37,21.95 9.1,22C7.79,22.05 6.8,20.68 5.96,19.47C4.25,17 2.94,12.45 4.7,9.39C5.57,7.87 7.13,6.91 8.82,6.88C10.1,6.86 11.32,7.75 12.11,7.75C12.89,7.75 14.37,6.68 15.92,6.84C16.57,6.87 18.39,7.1 19.56,8.82C19.47,8.88 17.39,10.1 17.41,12.63C17.44,15.65 20.06,16.66 20.09,16.67C20.06,16.74 19.67,18.11 18.71,19.5M13,3.5C13.73,2.67 14.94,2.04 15.94,2C16.07,3.17 15.6,4.35 14.9,5.19C14.21,6.04 13.07,6.7 11.95,6.61C11.8,5.46 12.36,4.26 13,3.5Z" /></svg>
|
||||
|
After Width: | Height: | Size: 824 B |
1
images/check-circle-outline.svg
Normal file
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24" height="24" viewBox="0 0 24 24"><path d="M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2M12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20A8,8 0 0,0 20,12A8,8 0 0,0 12,4M11,16.5L6.5,12L7.91,10.59L11,13.67L16.59,8.09L18,9.5L11,16.5Z" /></svg>
|
||||
|
After Width: | Height: | Size: 499 B |
1
images/export.svg
Normal file
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24" height="24" viewBox="0 0 24 24"><path d="M23,12L19,8V11H10V13H19V16M1,18V6C1,4.89 1.9,4 3,4H15A2,2 0 0,1 17,6V9H15V6H3V18H15V15H17V18A2,2 0 0,1 15,20H3A2,2 0 0,1 1,18Z" /></svg>
|
||||
|
After Width: | Height: | Size: 421 B |
1
images/folder-outline.svg
Normal file
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24" height="24" viewBox="0 0 24 24"><path d="M20,18H4V8H20M20,6H12L10,4H4C2.89,4 2,4.89 2,6V18A2,2 0 0,0 4,20H20A2,2 0 0,0 22,18V8C22,6.89 21.1,6 20,6Z" /></svg>
|
||||
|
After Width: | Height: | Size: 401 B |
2
images/linux.svg
Normal file
@@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg width="1792" height="1792" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg"><path d="M791 411q-11 1-15.5 10.5t-8.5 9.5q-5 1-5-5 0-12 19-15h10zm87 14q-4 1-11.5-6.5t-17.5-4.5q24-11 32 2 3 6-3 9zm-351 427q-4-1-6 3t-4.5 12.5-5.5 13.5-10 13q-10 11-1 12 4 1 12.5-7t12.5-18q1-3 2-7t2-6 1.5-4.5.5-4v-3l-1-2.5-3-2zm855 359q0-18-55-42 4-15 7.5-27.5t5-26 3-21.5.5-22.5-1-19.5-3.5-22-4-20.5-5-25-5.5-26.5q-10-48-47-103t-72-75q24 20 57 83 87 162 54 278-11 40-50 42-31 4-38.5-18.5t-8-83.5-11.5-107q-9-39-19.5-69t-19.5-45.5-15.5-24.5-13-15-7.5-7q-14-62-31-103t-29.5-56-23.5-33-15-40q-4-21 6-53.5t4.5-49.5-44.5-25q-15-3-44.5-18t-35.5-16q-8-1-11-26t8-51 36-27q37-3 51 30t4 58q-11 19-2 26.5t30 .5q13-4 13-36v-37q-5-30-13.5-50t-21-30.5-23.5-15-27-7.5q-107 8-89 134 0 15-1 15-9-9-29.5-10.5t-33 .5-15.5-5q1-57-16-90t-45-34q-27-1-41.5 27.5t-16.5 59.5q-1 15 3.5 37t13 37.5 15.5 13.5q10-3 16-14 4-9-7-8-7 0-15.5-14.5t-9.5-33.5q-1-22 9-37t34-14q17 0 27 21t9.5 39-1.5 22q-22 15-31 29-8 12-27.5 23.5t-20.5 12.5q-13 14-15.5 27t7.5 18q14 8 25 19.5t16 19 18.5 13 35.5 6.5q47 2 102-15 2-1 23-7t34.5-10.5 29.5-13 21-17.5q9-14 20-8 5 3 6.5 8.5t-3 12-16.5 9.5q-20 6-56.5 21.5t-45.5 19.5q-44 19-70 23-25 5-79-2-10-2-9 2t17 19q25 23 67 22 17-1 36-7t36-14 33.5-17.5 30-17 24.5-12 17.5-2.5 8.5 11q0 2-1 4.5t-4 5-6 4.5-8.5 5-9 4.5-10 5-9.5 4.5q-28 14-67.5 44t-66.5 43-49 1q-21-11-63-73-22-31-25-22-1 3-1 10 0 25-15 56.5t-29.5 55.5-21 58 11.5 63q-23 6-62.5 90t-47.5 141q-2 18-1.5 69t-5.5 59q-8 24-29 3-32-31-36-94-2-28 4-56 4-19-1-18-2 1-4 5-36 65 10 166 5 12 25 28t24 20q20 23 104 90.5t93 76.5q16 15 17.5 38t-14 43-45.5 23q8 15 29 44.5t28 54 7 70.5q46-24 7-92-4-8-10.5-16t-9.5-12-2-6q3-5 13-9.5t20 2.5q46 52 166 36 133-15 177-87 23-38 34-30 12 6 10 52-1 25-23 92-9 23-6 37.5t24 15.5q3-19 14.5-77t13.5-90q2-21-6.5-73.5t-7.5-97 23-70.5q15-18 51-18 1-37 34.5-53t72.5-10.5 60 22.5zm-628-827q3-17-2.5-30t-11.5-15q-9-2-9 7 2 5 5 6 10 0 7 15-3 20 8 20 3 0 3-3zm419 197q-2-8-6.5-11.5t-13-5-14.5-5.5q-5-3-9.5-8t-7-8-5.5-6.5-4-4-4 1.5q-14 16 7 43.5t39 31.5q9 1 14.5-8t3.5-20zm-178-213q0-11-5-19.5t-11-12.5-9-3q-6 0-8 2t0 4 5 3q14 4 18 31 0 3 8-2 2-2 2-3zm54-233q0-2-2.5-5t-9-7-9.5-6q-15-15-24-15-9 1-11.5 7.5t-1 13-.5 12.5q-1 4-6 10.5t-6 9 3 8.5q4 3 8 0t11-9 15-9q1-1 9-1t15-2 9-7zm565 1341q20 12 31 24.5t12 24-2.5 22.5-15.5 22-23.5 19.5-30 18.5-31.5 16.5-32 15.5-27 13q-38 19-85.5 56t-75.5 64q-17 16-68 19.5t-89-14.5q-18-9-29.5-23.5t-16.5-25.5-22-19.5-47-9.5q-44-1-130-1-19 0-57 1.5t-58 2.5q-44 1-79.5 15t-53.5 30-43.5 28.5-53.5 11.5q-29-1-111-31t-146-43q-19-4-51-9.5t-50-9-39.5-9.5-33.5-14.5-17-19.5q-10-23 7-66.5t18-54.5q1-16-4-40t-10-42.5-4.5-36.5 10.5-27q14-12 57-14t60-12q30-18 42-35t12-51q21 73-32 106-32 20-83 15-34-3-43 10-13 15 5 57 2 6 8 18t8.5 18 4.5 17 1 22q0 15-17 49t-14 48q3 17 37 26 20 6 84.5 18.5t99.5 20.5q24 6 74 22t82.5 23 55.5 4q43-6 64.5-28t23-48-7.5-58.5-19-52-20-36.5q-121-190-169-242-68-74-113-40-11 9-15-15-3-16-2-38 1-29 10-52t24-47 22-42q8-21 26.5-72t29.5-78 30-61 39-54q110-143 124-195-12-112-16-310-2-90 24-151.5t106-104.5q39-21 104-21 53-1 106 13.5t89 41.5q57 42 91.5 121.5t29.5 147.5q-5 95 30 214 34 113 133 218 55 59 99.5 163t59.5 191q8 49 5 84.5t-12 55.5-20 22q-10 2-23.5 19t-27 35.5-40.5 33.5-61 14q-18-1-31.5-5t-22.5-13.5-13.5-15.5-11.5-20.5-9-19.5q-22-37-41-30t-28 49 7 97q20 70 1 195-10 65 18 100.5t73 33 85-35.5q59-49 89.5-66.5t103.5-42.5q53-18 77-36.5t18.5-34.5-25-28.5-51.5-23.5q-33-11-49.5-48t-15-72.5 15.5-47.5q1 31 8 56.5t14.5 40.5 20.5 28.5 21 19 21.5 13 16.5 9.5z"/></svg>
|
||||
|
After Width: | Height: | Size: 3.4 KiB |
2
images/windows.svg
Normal file
@@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg width="1792" height="1792" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg"><path d="M746 1006v651l-682-94v-557h682zm0-743v659h-682v-565zm982 743v786l-907-125v-661h907zm0-878v794h-907v-669z"/></svg>
|
||||
|
After Width: | Height: | Size: 252 B |
@@ -69,10 +69,67 @@
|
||||
return accountManager;
|
||||
};
|
||||
|
||||
function isTimeToUpgrade(text) {
|
||||
var percentage = parseInt(text, 10);
|
||||
|
||||
if (isNaN(percentage)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var roll = _.random(1, 100);
|
||||
return roll <= percentage;
|
||||
}
|
||||
|
||||
var UPGRADE_VALUE = 'feb-2018-upgrade-dice';
|
||||
var UPGRADE_FLAG = window.UPGRADE_FLAG = 'feb-2018-upgrade-alert';
|
||||
var UPGRADE_URL = 'https://updates.signal.org/desktop/feb-2018-upgrade.txt';
|
||||
var HALF_HOUR = 30 * 60 * 1000;
|
||||
|
||||
function checkForUpgrade() {
|
||||
$.ajax(UPGRADE_URL).done(function(data) {
|
||||
var previous = storage.get(UPGRADE_VALUE);
|
||||
if (data === previous) {
|
||||
console.log('Upgrade check: Got same value as last time. Another check in 30 minutes');
|
||||
return;
|
||||
}
|
||||
|
||||
storage.put(UPGRADE_VALUE, data);
|
||||
if (isTimeToUpgrade(data)) {
|
||||
console.log('Upgrade check: time to upgrade!');
|
||||
storage.put(UPGRADE_FLAG, true);
|
||||
addUpgradeAlert();
|
||||
} else {
|
||||
console.log('Upgrade check: Not yet time to upgrade. Another check in 30 minutes');
|
||||
setTimeout(checkForUpgrade, HALF_HOUR);
|
||||
}
|
||||
}).fail(function() {
|
||||
console.log('Upgrade check: Request failed; another check in 30 minutes');
|
||||
setTimeout(checkForUpgrade, HALF_HOUR);
|
||||
});
|
||||
}
|
||||
|
||||
function addUpgradeAlert() {
|
||||
var app = window.owsDesktopApp;
|
||||
if (!app) {
|
||||
return;
|
||||
}
|
||||
|
||||
var view = app.inboxView;
|
||||
if (!view) {
|
||||
return;
|
||||
}
|
||||
|
||||
view.showUpgradeBanner();
|
||||
}
|
||||
|
||||
storage.fetch();
|
||||
storage.onready(function() {
|
||||
ConversationController.load();
|
||||
|
||||
if (!storage.get(UPGRADE_FLAG)) {
|
||||
checkForUpgrade();
|
||||
}
|
||||
|
||||
window.dispatchEvent(new Event('storage_ready'));
|
||||
setUnreadCount(storage.get("unreadCount", 0));
|
||||
|
||||
|
||||
82
js/backup.js
@@ -74,11 +74,11 @@
|
||||
};
|
||||
}
|
||||
|
||||
function exportNonMessages(idb_db, parent) {
|
||||
function exportNonMessages(idb_db, parent, options) {
|
||||
// We wouldn't want to overwrite another db file.
|
||||
var exclusive = true;
|
||||
return createFileAndWriter(parent, 'db.json', exclusive).then(function(writer) {
|
||||
return exportToJsonFile(idb_db, writer);
|
||||
return exportToJsonFile(idb_db, writer, options);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -86,10 +86,27 @@
|
||||
* Export all data from an IndexedDB database
|
||||
* @param {IDBDatabase} idb_db
|
||||
*/
|
||||
function exportToJsonFile(idb_db, fileWriter) {
|
||||
function exportToJsonFile(idb_db, fileWriter, options) {
|
||||
options = options || {};
|
||||
_.defaults(options, {excludeClientConfig: false});
|
||||
|
||||
return new Promise(function(resolve, reject) {
|
||||
var storeNames = idb_db.objectStoreNames;
|
||||
storeNames = _.without(storeNames, 'messages');
|
||||
storeNames = _.without(storeNames, 'messages', 'debug');
|
||||
|
||||
if (options.excludeClientConfig) {
|
||||
console.log('exportToJsonFile: excluding client config from export');
|
||||
storeNames = _.without(
|
||||
storeNames,
|
||||
'items',
|
||||
'signedPreKeys',
|
||||
'preKeys',
|
||||
'identityKeys',
|
||||
'sessions',
|
||||
'unprocessed' // since we won't be able to decrypt them anyway
|
||||
);
|
||||
}
|
||||
|
||||
var exportedStoreNames = [];
|
||||
if (storeNames.length === 0) {
|
||||
throw new Error('No stores to export');
|
||||
@@ -336,11 +353,11 @@
|
||||
});
|
||||
}
|
||||
|
||||
var attachments = 0;
|
||||
var failedAttachments = 0;
|
||||
var numAttachments = 0;
|
||||
var numFailedAttachments = 0;
|
||||
|
||||
function writeAttachment(dir, attachment) {
|
||||
attachments += 1;
|
||||
numAttachments += 1;
|
||||
|
||||
var filename = getAttachmentFileName(attachment);
|
||||
// If attachments are in messages with the same received_at and the same name,
|
||||
@@ -350,7 +367,7 @@
|
||||
var stream = createOutputStream(writer);
|
||||
return stream.write(attachment.data);
|
||||
}).catch(function(error) {
|
||||
failedAttachments += 1;
|
||||
numFailedAttachments += 1;
|
||||
console.log('writeAttachment error:', error && error.stack ? error.stack : error);
|
||||
});
|
||||
}
|
||||
@@ -379,8 +396,8 @@
|
||||
return filename.toString().replace(/[^a-z0-9.,+()'#\- ]/gi, '_');
|
||||
}
|
||||
|
||||
var conversations = 0;
|
||||
var failedConversations = 0;
|
||||
var numConversations = 0;
|
||||
var numFailedConversations = 0;
|
||||
|
||||
function delay(ms) {
|
||||
console.log('Waiting', ms, 'milliseconds');
|
||||
@@ -395,7 +412,7 @@
|
||||
// file for well-formed JSON, trying for up to five minutes.
|
||||
var CHECK_MAX = 60;
|
||||
function checkConversation(conversationId, dir, count) {
|
||||
return delay(5000).then(function() {
|
||||
return delay(2000).then(function() {
|
||||
console.log(
|
||||
'Verifying messages.json produced for conversation',
|
||||
conversationId,
|
||||
@@ -414,7 +431,7 @@
|
||||
);
|
||||
|
||||
if (count >= CHECK_MAX) {
|
||||
failedConversations += 1;
|
||||
numFailedConversations += 1;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -465,14 +482,20 @@
|
||||
request.onsuccess = function(event) {
|
||||
var cursor = event.target.result;
|
||||
if (cursor) {
|
||||
if (count !== 0) {
|
||||
stream.write(',');
|
||||
}
|
||||
|
||||
var message = cursor.value;
|
||||
var messageId = message.received_at;
|
||||
var attachments = message.attachments;
|
||||
|
||||
// skip message if it is disappearing, no matter the amount of time left
|
||||
if (message.expireTimer) {
|
||||
cursor.continue();
|
||||
return;
|
||||
}
|
||||
|
||||
if (count !== 0) {
|
||||
stream.write(',');
|
||||
}
|
||||
|
||||
// eliminate attachment data from the JSON, since it will go to disk
|
||||
message.attachments = _.map(attachments, function(attachment) {
|
||||
return _.omit(attachment, ['data']);
|
||||
@@ -504,7 +527,7 @@
|
||||
} else {
|
||||
var promise = stream.write(']}');
|
||||
|
||||
conversations += 1;
|
||||
numConversations += 1;
|
||||
|
||||
promiseChain = promiseChain
|
||||
.then(promise)
|
||||
@@ -755,24 +778,25 @@
|
||||
}
|
||||
|
||||
function printAttachmentStats() {
|
||||
console.log(
|
||||
'Total attachments:', attachments,
|
||||
'Failed attachments:', failedAttachments
|
||||
);
|
||||
console.log('Total attachments:', numAttachments);
|
||||
console.log('Failed attachments:', numFailedAttachments);
|
||||
}
|
||||
|
||||
function printConversationStats() {
|
||||
console.log(
|
||||
'Total conversations:', conversations,
|
||||
'Possibly malformed conversations:', failedConversations
|
||||
);
|
||||
console.log('Total conversations:', numConversations);
|
||||
console.log('Possibly malformed conversations:', numFailedConversations);
|
||||
}
|
||||
|
||||
Whisper.Backup = {
|
||||
backupToDirectory: function() {
|
||||
exportToDirectory: function(options) {
|
||||
return getDirectory().then(function(directoryEntry) {
|
||||
var idb;
|
||||
var dir;
|
||||
numConversations = 0;
|
||||
numFailedConversations = 0;
|
||||
numAttachments = 0;
|
||||
numFailedAttachments = 0;
|
||||
|
||||
return openDatabase().then(function(idb_db) {
|
||||
idb = idb_db;
|
||||
var name = 'Signal Export ' + getTimestamp();
|
||||
@@ -781,7 +805,7 @@
|
||||
return createDirectory(directoryEntry, name, exclusive);
|
||||
}).then(function(directory) {
|
||||
dir = directory;
|
||||
return exportNonMessages(idb, dir);
|
||||
return exportNonMessages(idb, dir, options);
|
||||
}).then(function() {
|
||||
return exportConversations(idb, dir);
|
||||
}).then(function() {
|
||||
@@ -791,10 +815,10 @@
|
||||
printAttachmentStats();
|
||||
printConversationStats();
|
||||
console.log('done backing up!');
|
||||
if (failedAttachments) {
|
||||
if (numFailedAttachments) {
|
||||
throw new Error('Export failed, one or more attachments failed');
|
||||
}
|
||||
if (failedConversations) {
|
||||
if (numFailedConversations) {
|
||||
throw new Error('Export failed, one or more conversations failed');
|
||||
}
|
||||
return path;
|
||||
|
||||
@@ -424,6 +424,6 @@
|
||||
console.log(event);
|
||||
};
|
||||
}
|
||||
}
|
||||
},
|
||||
];
|
||||
}());
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/*
|
||||
/*
|
||||
* vim: ts=4:sw=4:expandtab
|
||||
*/
|
||||
(function () {
|
||||
@@ -50,6 +50,9 @@
|
||||
this.cancel();
|
||||
}
|
||||
},
|
||||
focusOk: function() {
|
||||
this.$('.ok').focus();
|
||||
},
|
||||
focusCancel: function() {
|
||||
this.$('.cancel').focus();
|
||||
}
|
||||
|
||||
@@ -145,23 +145,16 @@
|
||||
this.inboxListView.stopListening();
|
||||
}.bind(this));
|
||||
|
||||
if (extension.expired()) {
|
||||
var banner = new Whisper.ExpiredAlertBanner().render();
|
||||
banner.$el.prependTo(this.$el);
|
||||
this.$el.addClass('expired');
|
||||
} else if (Whisper.Migration.inProgress()) {
|
||||
if (Whisper.Migration.inProgress()) {
|
||||
if (this.appLoadingScreen) {
|
||||
this.appLoadingScreen.remove();
|
||||
this.appLoadingScreen = null;
|
||||
}
|
||||
this.showMigrationScreen();
|
||||
this.showUpgradeScreen();
|
||||
}
|
||||
else if (storage.get(window.UPGRADE_FLAG)) {
|
||||
this.showUpgradeBanner();
|
||||
}
|
||||
// We'll re-enable this banner when we've made some usability improvements to
|
||||
// the migration process:
|
||||
// else {
|
||||
// var migrationBanner = new Whisper.MigrationAlertBanner().render();
|
||||
// migrationBanner.$el.prependTo(this.$el);
|
||||
// }
|
||||
},
|
||||
render_attributes: {
|
||||
welcomeToSignal : i18n('welcomeToSignal'),
|
||||
@@ -183,12 +176,32 @@
|
||||
'input input.search': 'filterContacts',
|
||||
'click .restart-signal': 'reloadBackgroundPage',
|
||||
'show .lightbox': 'showLightbox',
|
||||
'click .migrate': 'confirmMigration'
|
||||
'click .migrate': 'showUpgradeScreen',
|
||||
'click .banner-close': 'removeUpgradeBanner'
|
||||
},
|
||||
confirmMigration: function() {
|
||||
this.confirm(i18n('confirmMigration'), i18n('migrate')).then(this.showMigrationScreen.bind(this));
|
||||
showUpgradeBanner: function() {
|
||||
this.removeUpgradeBanner();
|
||||
|
||||
this.upgradeBanner = new Whisper.UpgradeBanner().render();
|
||||
this.upgradeBanner.$el.prependTo(this.$el);
|
||||
|
||||
// This class makes AppView have a height of 100% - 62px, so it doesn't push
|
||||
// our banner off-screen.
|
||||
this.$el.addClass('expired');
|
||||
},
|
||||
showMigrationScreen: function() {
|
||||
removeUpgradeBanner: function() {
|
||||
if (this.upgradeBanner) {
|
||||
this.upgradeBanner.remove();
|
||||
this.upgradeBanner = null;
|
||||
this.$el.removeClass('expired');
|
||||
}
|
||||
},
|
||||
showUpgradeScreen: function() {
|
||||
if (this.migrationScreen) {
|
||||
this.migrationScreen.remove();
|
||||
this.migrationScreen = null;
|
||||
}
|
||||
|
||||
this.migrationScreen = new Whisper.MigrationView();
|
||||
this.migrationScreen.render();
|
||||
this.migrationScreen.$el.prependTo(this.el);
|
||||
@@ -311,13 +324,13 @@
|
||||
}
|
||||
});
|
||||
|
||||
Whisper.MigrationAlertBanner = Whisper.View.extend({
|
||||
templateName: 'migration_alert',
|
||||
className: 'expiredAlert clearfix',
|
||||
Whisper.UpgradeBanner = Whisper.View.extend({
|
||||
templateName: 'upgrade_banner',
|
||||
className: 'expiredAlert upgrade-banner clearfix',
|
||||
render_attributes: function() {
|
||||
return {
|
||||
migrationWarning: i18n('migrationWarning'),
|
||||
migrate: i18n('migrate'),
|
||||
upgradeMessage: i18n('upgradeBanner'),
|
||||
upgradeNow: i18n('upgradeNow'),
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
@@ -2,13 +2,87 @@
|
||||
'use strict';
|
||||
window.Whisper = window.Whisper || {};
|
||||
|
||||
var LinuxInstructionsView = Whisper.ConfirmationDialogView.extend({
|
||||
className: 'linux-install-instructions',
|
||||
templateName: 'linux-install-instructions',
|
||||
_super: Whisper.ConfirmationDialogView.prototype,
|
||||
initialize: function() {
|
||||
this._super.initialize.call(this, {
|
||||
okText: i18n('close'),
|
||||
});
|
||||
},
|
||||
events: {
|
||||
'keyup': 'onKeyup',
|
||||
'click .ok': 'ok',
|
||||
'click .modal': 'ok',
|
||||
},
|
||||
render_attributes: function() {
|
||||
var attributes = this._super.render_attributes.call(this);
|
||||
// TODO: i18n
|
||||
attributes.header = 'Debian-based Linux install instructions';
|
||||
return attributes;
|
||||
},
|
||||
ok: function(event) {
|
||||
// We have an event on .modal, which is the background div, darkening the screen.
|
||||
// This ensures that a click on the dialog will not fire that event, by checking
|
||||
// the actual thing clicked against the target.
|
||||
if (event.target !== event.currentTarget) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._super.ok.call(this);
|
||||
}
|
||||
});
|
||||
|
||||
var State = {
|
||||
DISCONNECTING: 1,
|
||||
EXPORTING: 2,
|
||||
COMPLETE: 3,
|
||||
CHOOSE_DIR: 4,
|
||||
};
|
||||
|
||||
var STEPS = {
|
||||
INTRODUCTION: 1,
|
||||
INSTALL: 2,
|
||||
CHOOSE: 3,
|
||||
EXPORTING: 4,
|
||||
COMPLETE: 5,
|
||||
};
|
||||
|
||||
var GET_YAML_PATH = /^path: (.+)$/m;
|
||||
var BASE_PATH = 'https://updates.signal.org/desktop/';
|
||||
|
||||
function getCacheBuster() {
|
||||
return Math.random().toString(36).substring(7);
|
||||
}
|
||||
|
||||
function getLink(file) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
$.get(BASE_PATH + file + '?b=' + getCacheBuster())
|
||||
.done(function(data) {
|
||||
var match = GET_YAML_PATH.exec(data);
|
||||
if (match && match[1]) {
|
||||
return resolve(BASE_PATH + match[1]);
|
||||
}
|
||||
|
||||
return reject(new Error('Link not found in YAML from ' + file + ': ' + data));
|
||||
})
|
||||
.fail(function(xhr) {
|
||||
return reject(new Error(
|
||||
'Problem pulling ' + file + '; Request Status: ' + xhr.status +
|
||||
' Status Text: ' + xhr.statusText + ' ' + xhr.responseText)
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function getMacLink() {
|
||||
return getLink('latest-mac-import.yml');
|
||||
}
|
||||
|
||||
function getWindowsLink() {
|
||||
return getLink('latest-import.yml');
|
||||
}
|
||||
|
||||
Whisper.Migration = {
|
||||
isComplete: function() {
|
||||
return storage.get('migrationState') === State.COMPLETE;
|
||||
@@ -32,12 +106,27 @@
|
||||
},
|
||||
beginExport: function() {
|
||||
storage.put('migrationState', State.EXPORTING);
|
||||
return Whisper.Backup.backupToDirectory();
|
||||
|
||||
var everAttempted = this.everAttempted();
|
||||
storage.put('migrationEverAttempted', true);
|
||||
|
||||
// If this is the second time the user is attempting to export, we'll exclude
|
||||
// client-specific encryption configuration. Yes, this will fire if the user
|
||||
// initially attempts to save to a read-only directory or something like that, but
|
||||
// it will prevent the horrible encryption errors which result from import to the
|
||||
// same client config more than once. They can import the same message history
|
||||
// more than once, so we preserve that.
|
||||
return Whisper.Backup.exportToDirectory({
|
||||
excludeClientConfig: everAttempted,
|
||||
});
|
||||
},
|
||||
init: function() {
|
||||
storage.put('migrationState', State.DISCONNECTING);
|
||||
Whisper.events.trigger('start-shutdown');
|
||||
},
|
||||
everAttempted: function() {
|
||||
return Boolean(storage.get('migrationEverAttempted'));
|
||||
},
|
||||
everComplete: function() {
|
||||
return Boolean(storage.get('migrationEverCompleted'));
|
||||
},
|
||||
@@ -47,17 +136,42 @@
|
||||
};
|
||||
|
||||
Whisper.MigrationView = Whisper.View.extend({
|
||||
templateName: 'app-migration-screen',
|
||||
className: 'app-loading-screen',
|
||||
templateName: 'migration-flow-template',
|
||||
className: 'migration-flow',
|
||||
events: {
|
||||
'click .install': 'onClickInstall',
|
||||
'click .export': 'onClickExport',
|
||||
'click .install-mac': 'onClickMac',
|
||||
'click .install-windows': 'onClickWindows',
|
||||
'click .install-linux': 'onClickLinux',
|
||||
'click .start': 'onClickStart',
|
||||
'click .installed': 'onClickInstalled',
|
||||
'click .choose': 'onClickChoose',
|
||||
'click .debug-log': 'onClickDebugLog',
|
||||
'click .cancel': 'onClickCancel',
|
||||
'click .next': 'onClickNext',
|
||||
},
|
||||
initialize: function() {
|
||||
this.step = STEPS.INTRODUCTION;
|
||||
|
||||
// init() tells MessageReceiver to disconnect and drain its queue, will fire
|
||||
// 'shutdown-complete' event when that is done. Might result in a synchronous
|
||||
// event, so call it after we register our callback.
|
||||
Whisper.events.once('shutdown-complete', function() {
|
||||
this.shutdownComplete = true;
|
||||
}.bind(this));
|
||||
Whisper.Migration.init();
|
||||
|
||||
Promise.all([getMacLink(), getWindowsLink()]).then(function(results) {
|
||||
this.macLink = results[0];
|
||||
this.windowsLink = results[1];
|
||||
this.render();
|
||||
}.bind(this), function(error) {
|
||||
console.log(
|
||||
'MigrationView: Ran into problem pulling Mac/Windows install links:',
|
||||
error.stack
|
||||
);
|
||||
});
|
||||
|
||||
if (!Whisper.Migration.inProgress()) {
|
||||
this.render();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -66,107 +180,124 @@
|
||||
if (Whisper.Migration.everComplete()) {
|
||||
// If the user has ever successfully exported before, we'll show the 'finished'
|
||||
// screen with the 'Export again' button.
|
||||
this.step = STEPS.COMPLETE;
|
||||
Whisper.Migration.markComplete();
|
||||
} else if (!Whisper.Migration.isComplete()) {
|
||||
// This takes the user back to the very beginning of the process.
|
||||
Whisper.Migration.cancel();
|
||||
}
|
||||
|
||||
this.render();
|
||||
},
|
||||
render_attributes: function() {
|
||||
var message;
|
||||
var exportButton;
|
||||
var hideProgress = Whisper.Migration.isComplete();
|
||||
var debugLogButton = i18n('submitDebugLog');
|
||||
var installButton = i18n('installNewSignal');
|
||||
var cancelButton;
|
||||
var nextButton;
|
||||
|
||||
if (this.error) {
|
||||
// If we've never successfully exported, then we allow user to cancel out
|
||||
if (!Whisper.Migration.everComplete()) {
|
||||
cancelButton = i18n('cancel');
|
||||
}
|
||||
|
||||
return {
|
||||
message: i18n('exportError'),
|
||||
hideProgress: true,
|
||||
exportButton: i18n('exportAgain'),
|
||||
isError: true,
|
||||
errorHeader: i18n('exportErrorHeader'),
|
||||
error: i18n('exportError'),
|
||||
tryAgain: i18n('chooseFolderAndTryAgain'),
|
||||
debugLogButton: i18n('submitDebugLog'),
|
||||
cancelButton: cancelButton,
|
||||
};
|
||||
}
|
||||
|
||||
switch (storage.get('migrationState')) {
|
||||
case State.COMPLETE:
|
||||
var location = Whisper.Migration.getExportLocation() || i18n('selectedLocation');
|
||||
message = i18n('exportComplete', location);
|
||||
exportButton = i18n('exportAgain');
|
||||
installButton = null;
|
||||
debugLogButton = null;
|
||||
break;
|
||||
case State.EXPORTING:
|
||||
message = i18n('exporting');
|
||||
installButton = null;
|
||||
break;
|
||||
case State.DISCONNECTING:
|
||||
message = i18n('migrationDisconnecting');
|
||||
installButton = null;
|
||||
break;
|
||||
case State.CHOOSE_DIR:
|
||||
hideProgress = true;
|
||||
message = i18n('exportInstructions');
|
||||
exportButton = i18n('export');
|
||||
debugLogButton = null;
|
||||
installButton = null;
|
||||
break;
|
||||
default:
|
||||
message = i18n('migrateInstallStep');
|
||||
hideProgress = true;
|
||||
debugLogButton = null;
|
||||
nextButton = i18n('installComplete');
|
||||
cancelButton = i18n('cancel');
|
||||
break;
|
||||
}
|
||||
var location = Whisper.Migration.getExportLocation() || i18n('selectedLocation');
|
||||
|
||||
return {
|
||||
hideProgress: hideProgress,
|
||||
message: message,
|
||||
exportButton: exportButton,
|
||||
debugLogButton: debugLogButton,
|
||||
installButton: installButton,
|
||||
cancelButton: cancelButton,
|
||||
nextButton: nextButton,
|
||||
cancelButton: i18n('cancel'),
|
||||
debugLogButton: i18n('submitDebugLog'),
|
||||
|
||||
isStep1: this.step === 1,
|
||||
startHeader: i18n('startExportHeader'),
|
||||
start: i18n('startExportIntro'),
|
||||
startButton: i18n('imReady'),
|
||||
|
||||
isStep2: this.step === 2,
|
||||
installHeader: i18n('installHeader'),
|
||||
install: i18n('installIntro'),
|
||||
macOS: i18n('macOS'),
|
||||
macLink: this.macLink,
|
||||
windows: i18n('windows'),
|
||||
windowsLink: this.windowsLink,
|
||||
linux: i18n('debianLinux'),
|
||||
installCompleteButton: i18n('installed'),
|
||||
|
||||
isStep3: this.step === 3,
|
||||
chooseHeader: i18n('saveHeader'),
|
||||
choose: i18n('saveDataPrompt'),
|
||||
chooseButton: i18n('chooseFolder'),
|
||||
|
||||
isStep4: this.step === 4,
|
||||
exportHeader: i18n('savingData'),
|
||||
|
||||
isStep5: this.step === 5,
|
||||
completeHeader: i18n('completeHeader'),
|
||||
completeIntro: i18n('completeIntro'),
|
||||
completeLocation: location,
|
||||
completeNextSteps: i18n('completeNextSteps'),
|
||||
completeSignoff: i18n('completeSignoff'),
|
||||
};
|
||||
},
|
||||
onClickInstall: function() {
|
||||
var url = 'https://support.whispersystems.org/hc/en-us/articles/214507138';
|
||||
window.open(url, '_blank');
|
||||
onClickStart: function() {
|
||||
this.selectStep(STEPS.INSTALL);
|
||||
},
|
||||
onClickNext: function() {
|
||||
storage.put('migrationState', State.CHOOSE_DIR);
|
||||
onClickMac: function() {
|
||||
console.log('Mac install link clicked');
|
||||
},
|
||||
onClickWindows: function() {
|
||||
console.log('Windows install link clicked');
|
||||
},
|
||||
onClickLinux: function() {
|
||||
var dialog = this.linuxInstructionsView = new LinuxInstructionsView({});
|
||||
this.$el.prepend(dialog.el);
|
||||
dialog.focusOk();
|
||||
},
|
||||
onClickInstalled: function() {
|
||||
this.selectStep(STEPS.CHOOSE);
|
||||
},
|
||||
onClickChoose: function() {
|
||||
this.error = null;
|
||||
|
||||
if (!this.shutdownComplete) {
|
||||
console.log("Preventing export start; we haven't disconnected from the server");
|
||||
this.error = true;
|
||||
this.render();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Whisper.Migration.everComplete()) {
|
||||
return this.beginMigration();
|
||||
}
|
||||
|
||||
// Different behavior for the user's second time through
|
||||
this.selectStep(STEPS.EXPORTING);
|
||||
Whisper.Migration.beginExport()
|
||||
.then(this.completeMigration.bind(this))
|
||||
.catch(function(error) {
|
||||
if (!error || error.name !== 'ChooseError') {
|
||||
this.error = error || new Error('in case we reject() null!');
|
||||
}
|
||||
// Even if we run into an error, we call this complete because the user has
|
||||
// completed the process once before.
|
||||
Whisper.Migration.markComplete();
|
||||
this.selectStep(STEPS.COMPLETE);
|
||||
}.bind(this));
|
||||
this.render();
|
||||
},
|
||||
onClickCancel: function() {
|
||||
this.cancel();
|
||||
},
|
||||
onClickDebugLog: function() {
|
||||
this.openDebugLog();
|
||||
},
|
||||
|
||||
cancel: function() {
|
||||
console.log('Cancelling out of migration workflow after error');
|
||||
Whisper.Migration.cancel().then(function() {
|
||||
console.log('Restarting now');
|
||||
window.location.reload();
|
||||
});
|
||||
},
|
||||
onClickCancel: function() {
|
||||
var dialog = new Whisper.ConfirmationDialogView({
|
||||
message: i18n('cancelWarning'),
|
||||
okText: i18n('cancelMigration'),
|
||||
cancelText: i18n('continueMigration'),
|
||||
resolve: this.cancel.bind(this),
|
||||
});
|
||||
selectStep: function(step) {
|
||||
this.step = step;
|
||||
this.render();
|
||||
},
|
||||
|
||||
this.$el.prepend(dialog.el);
|
||||
dialog.focusCancel();
|
||||
},
|
||||
onClickDebugLog: function() {
|
||||
this.openDebugLog();
|
||||
},
|
||||
openDebugLog: function() {
|
||||
this.closeDebugLog();
|
||||
this.debugLogView = new Whisper.DebugLogView();
|
||||
@@ -178,49 +309,18 @@
|
||||
this.debugLogView = null;
|
||||
}
|
||||
},
|
||||
onClickExport: function() {
|
||||
this.error = null;
|
||||
|
||||
if (!Whisper.Migration.everComplete()) {
|
||||
return this.beginMigration();
|
||||
}
|
||||
beginMigration: function() {
|
||||
this.selectStep(STEPS.EXPORTING);
|
||||
|
||||
// Different behavior for the user's second time through
|
||||
Whisper.Migration.beginExport()
|
||||
.then(this.completeMigration.bind(this))
|
||||
.catch(function(error) {
|
||||
if (!error || error.name !== 'ChooseError') {
|
||||
this.error = error || new Error('in case we reject() null!');
|
||||
}
|
||||
// Even if we run into an error, we call this complete because the user has
|
||||
// completed the process once before.
|
||||
Whisper.Migration.markComplete();
|
||||
this.render();
|
||||
}.bind(this));
|
||||
this.render();
|
||||
},
|
||||
beginMigration: function() {
|
||||
Whisper.events.once('shutdown-complete', function() {
|
||||
Whisper.Migration.beginExport()
|
||||
.then(this.completeMigration.bind(this))
|
||||
.catch(this.onError.bind(this));
|
||||
|
||||
// Rendering because we're now in the 'exporting' state
|
||||
this.render();
|
||||
}.bind(this));
|
||||
|
||||
// tells MessageReceiver to disconnect and drain its queue, will fire
|
||||
// 'shutdown-complete' event when that is done. Might result in a synchronous
|
||||
// event, so call it after we register our callback.
|
||||
Whisper.Migration.init();
|
||||
|
||||
// Rendering because we're now in the 'disconnected' state
|
||||
this.render();
|
||||
.catch(this.onError.bind(this));
|
||||
},
|
||||
completeMigration: function(target) {
|
||||
// This will prevent connection to the server on future app launches
|
||||
Whisper.Migration.markComplete(target);
|
||||
this.render();
|
||||
this.selectStep(STEPS.COMPLETE);
|
||||
},
|
||||
onError: function(error) {
|
||||
if (error && error.name === 'ChooseError') {
|
||||
|
||||
@@ -218,7 +218,7 @@ button.hamburger {
|
||||
}
|
||||
|
||||
.dropoff {
|
||||
outline: solid 1px #2090ea;
|
||||
outline: solid 1px $blue;
|
||||
}
|
||||
|
||||
$avatar-size: 44px;
|
||||
@@ -542,6 +542,47 @@ input[type=text], input[type=search], textarea {
|
||||
}
|
||||
}
|
||||
|
||||
.upgrade-banner {
|
||||
background: linear-gradient(
|
||||
to bottom,
|
||||
rgb(238,238,238) 0%, // (1 - 0.41) * 255 + 0.41 * 213
|
||||
rgb(243,243,243) 20%, // (1 - 0.19) * 255 + 0.19 * 191
|
||||
rgb(255,255,255) 35%,
|
||||
rgb(255,255,255) 65%,
|
||||
rgb(249,249,249) 80%, // (1 - 0.19) * 255 + 0.19 * 222
|
||||
rgb(213,213,213) 100% // (1 - 0.27) * 255 + 0.27 * 98
|
||||
);
|
||||
padding: 10px;
|
||||
font-family: roboto-light;
|
||||
font-size: 14pt;
|
||||
|
||||
button {
|
||||
float: right;
|
||||
border: none;
|
||||
color: white;
|
||||
font-family: roboto-light;
|
||||
font-size: 14pt;
|
||||
border-radius: 0;
|
||||
box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
|
||||
line-height: 36px;
|
||||
padding: 0 2em;
|
||||
background: $blue;
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.message {
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.x {
|
||||
float: right;
|
||||
margin-left: 0.5em;
|
||||
margin-top: 0.5em;
|
||||
background-color: $grey_l2;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.inbox {
|
||||
position: relative;
|
||||
}
|
||||
@@ -602,6 +643,266 @@ input[type=text], input[type=search], textarea {
|
||||
}
|
||||
}
|
||||
|
||||
.migration-flow {
|
||||
z-index: 1000;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
font-family: roboto-light;
|
||||
|
||||
color: black;
|
||||
background: linear-gradient(
|
||||
to bottom,
|
||||
rgb(238,238,238) 0%, // (1 - 0.41) * 255 + 0.41 * 213
|
||||
rgb(243,243,243) 12%, // (1 - 0.19) * 255 + 0.19 * 191
|
||||
rgb(255,255,255) 27%,
|
||||
rgb(255,255,255) 60%,
|
||||
rgb(249,249,249) 85%, // (1 - 0.19) * 255 + 0.19 * 222
|
||||
rgb(213,213,213) 100% // (1 - 0.27) * 255 + 0.27 * 98
|
||||
);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
|
||||
font-size: 10pt;
|
||||
@media (min-height: 750px) and (min-width: 700px) {
|
||||
font-size: 14pt;
|
||||
}
|
||||
|
||||
.banner-image {
|
||||
margin: 1em;
|
||||
display: none;
|
||||
|
||||
@media (min-height: 550px) {
|
||||
display: inline-block;
|
||||
height: 10em;
|
||||
width: 10em;
|
||||
}
|
||||
}
|
||||
|
||||
.banner-icon {
|
||||
margin: 1em;
|
||||
display: none;
|
||||
|
||||
// 640px by 338px is the smallest the window can go
|
||||
@media (min-height: 550px) {
|
||||
display: inline-block;
|
||||
height: 10em;
|
||||
width: 10em;
|
||||
}
|
||||
|
||||
&.folder-outline {
|
||||
@include color-svg('../images/folder-outline.svg', #DEDEDE);
|
||||
}
|
||||
&.export {
|
||||
@include color-svg('../images/export.svg', #DEDEDE);
|
||||
}
|
||||
&.check-circle-outline {
|
||||
@include color-svg('../images/check-circle-outline.svg', #DEDEDE);
|
||||
}
|
||||
&.alert-outline {
|
||||
@include color-svg('../images/alert-outline.svg', #DEDEDE);
|
||||
}
|
||||
}
|
||||
|
||||
.os-header: {
|
||||
margin-top: 2em;
|
||||
}
|
||||
.header {
|
||||
font-weight: normal;
|
||||
margin-bottom: 1.5em;
|
||||
|
||||
font-size: 20pt;
|
||||
|
||||
@media (min-height: 750px) and (min-width: 700px) {
|
||||
font-size: 28pt;
|
||||
}
|
||||
}
|
||||
|
||||
.export-location {
|
||||
text-align: center;
|
||||
font-family: roboto;
|
||||
border: solid 1px $blue;
|
||||
color: $blue;
|
||||
margin-bottom: 1em;
|
||||
padding: 0.5em;
|
||||
-webkit-user-select: text;
|
||||
cursor: text;
|
||||
}
|
||||
.center {
|
||||
text-align: center;
|
||||
}
|
||||
.body-text {
|
||||
margin-bottom: 1em;
|
||||
max-width: 22em;
|
||||
text-align: left;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
.body-text-wide {
|
||||
margin-bottom: 1em;
|
||||
max-width: 30em;
|
||||
text-align: left;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.step {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
padding: 70px 0 50px;
|
||||
}
|
||||
.step-body {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
max-width: 35em;
|
||||
}
|
||||
|
||||
.linux-install-instructions .content {
|
||||
max-width: 1300px;
|
||||
.header {
|
||||
color: black;
|
||||
font-size: 120%;
|
||||
}
|
||||
|
||||
pre {
|
||||
&::-webkit-scrollbar {
|
||||
width: 5px;
|
||||
height: 5px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: rgba(255,255,255,0.40);
|
||||
border-radius: 2;
|
||||
}
|
||||
|
||||
cursor: text;
|
||||
-webkit-user-select: text;
|
||||
background-color: black;
|
||||
color: white;
|
||||
text-align: left;
|
||||
padding: 0.5em;
|
||||
overflow-x: scroll;
|
||||
}
|
||||
}
|
||||
|
||||
.inner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.button {
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
border: none;
|
||||
min-width: 300px;
|
||||
padding: 0.75em;
|
||||
margin-top: 1em;
|
||||
margin-bottom: 1em;
|
||||
color: white;
|
||||
background: $blue;
|
||||
box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
|
||||
|
||||
font-size: 12pt;
|
||||
|
||||
@media (min-height: 750px) and (min-width: 700px) {
|
||||
font-size: 20pt;
|
||||
}
|
||||
}
|
||||
a.link {
|
||||
display: block;
|
||||
cursor: pointer;
|
||||
text-decoration: underline;
|
||||
margin: 0.5em;
|
||||
color: $blue;
|
||||
}
|
||||
|
||||
.progress {
|
||||
text-align: center;
|
||||
padding: 1em;
|
||||
width: 80%;
|
||||
margin: auto;
|
||||
|
||||
.bar-container {
|
||||
height: 1em;
|
||||
margin: 1em;
|
||||
background-color: $grey_l;
|
||||
}
|
||||
.bar {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: $blue_l;
|
||||
transition: width 0.25s;
|
||||
box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
.install-container {
|
||||
@media (min-height: 550px) {
|
||||
margin-top: 4em;
|
||||
}
|
||||
text-align: center;
|
||||
}
|
||||
.install {
|
||||
cursor: pointer;
|
||||
background-color: white;
|
||||
padding: 0.5em;
|
||||
margin: 0.5em;
|
||||
display: inline-block;
|
||||
box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
|
||||
margin-left: 1em;
|
||||
margin-right: 1em;
|
||||
|
||||
a {
|
||||
color: black;
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
.install-icon {
|
||||
height: 7em;
|
||||
width: 7em;
|
||||
vertical-align: text-bottom;
|
||||
display: inline-block;
|
||||
margin: 1em;
|
||||
|
||||
@media (min-width: 800px) {
|
||||
margin-left: 2em;
|
||||
margin-right: 2em;
|
||||
}
|
||||
|
||||
|
||||
&.apple {
|
||||
@include color-svg('/images/apple.svg', $blue);
|
||||
}
|
||||
&.windows {
|
||||
@include color-svg('/images/windows.svg', $blue);
|
||||
}
|
||||
&.linux {
|
||||
@include color-svg('/images/linux.svg', $blue);
|
||||
}
|
||||
}
|
||||
|
||||
.nav {
|
||||
width: 100%;
|
||||
bottom: 50px;
|
||||
margin-top: auto;
|
||||
padding-bottom: 1em;
|
||||
padding-left: 20px;
|
||||
padding-right: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//yellow border fix
|
||||
.inbox:focus {
|
||||
outline: none;
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
.expired {
|
||||
.conversation-stack, .gutter {
|
||||
height: calc(100% - 56px);
|
||||
height: calc(100% - 62px);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -40,6 +40,23 @@ $text-dark_l2: darken($text-dark, 30%);
|
||||
background-color: darken($button-dark, 8%);
|
||||
}
|
||||
}
|
||||
|
||||
.upgrade-banner {
|
||||
button {
|
||||
float: right;
|
||||
border: none;
|
||||
color: white;
|
||||
font-family: roboto-light;
|
||||
font-size: 14pt;
|
||||
border-radius: 0;
|
||||
box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
|
||||
line-height: 36px;
|
||||
padding: 0 2em;
|
||||
background: $blue;
|
||||
margin-left: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.message-detail, .message-container, .conversation,
|
||||
.discussion-container {
|
||||
background-color: $grey-dark_l3;
|
||||
|
||||
@@ -363,7 +363,7 @@ button.hamburger {
|
||||
vertical-align: middle;
|
||||
display: inline-block;
|
||||
margin: 0 0 0 8px;
|
||||
width: calc(100% - 44px - 8px - 0.28571em);
|
||||
width: calc(100% - 44px - 8px - 0.2857142857em);
|
||||
text-align: left;
|
||||
cursor: pointer; }
|
||||
.contact-details p {
|
||||
@@ -378,7 +378,7 @@ button.hamburger {
|
||||
text-align: left; }
|
||||
.contact-details .number {
|
||||
color: #616161;
|
||||
font-size: 0.92857em; }
|
||||
font-size: 0.9285714286em; }
|
||||
.contact-details.not-clickable {
|
||||
cursor: default; }
|
||||
.contact-details .verified-icon {
|
||||
@@ -485,6 +485,31 @@ input[type=text]:active, input[type=text]:focus, input[type=search]:active, inpu
|
||||
.expiredAlert .message {
|
||||
padding: 10px 0; }
|
||||
|
||||
.upgrade-banner {
|
||||
background: linear-gradient(to bottom, #eeeeee 0%, #f3f3f3 20%, white 35%, white 65%, #f9f9f9 80%, #d5d5d5 100%);
|
||||
padding: 10px;
|
||||
font-family: roboto-light;
|
||||
font-size: 14pt; }
|
||||
.upgrade-banner button {
|
||||
float: right;
|
||||
border: none;
|
||||
color: white;
|
||||
font-family: roboto-light;
|
||||
font-size: 14pt;
|
||||
border-radius: 0;
|
||||
box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
|
||||
line-height: 36px;
|
||||
padding: 0 2em;
|
||||
background: #2090ea;
|
||||
margin-left: 20px; }
|
||||
.upgrade-banner .message {
|
||||
padding: 10px 0; }
|
||||
.upgrade-banner .x {
|
||||
float: right;
|
||||
margin-left: 0.5em;
|
||||
margin-top: 0.5em;
|
||||
background-color: #d9d9d9; }
|
||||
|
||||
.inbox {
|
||||
position: relative; }
|
||||
|
||||
@@ -530,6 +555,204 @@ input[type=text]:active, input[type=text]:focus, input[type=search]:active, inpu
|
||||
.app-loading-screen .dot:nth-child(3) {
|
||||
animation: loading 1500ms ease infinite 666ms; }
|
||||
|
||||
.migration-flow {
|
||||
z-index: 1000;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
font-family: roboto-light;
|
||||
color: black;
|
||||
background: linear-gradient(to bottom, #eeeeee 0%, #f3f3f3 12%, white 27%, white 60%, #f9f9f9 85%, #d5d5d5 100%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
font-size: 10pt;
|
||||
.os-header-margin-top: 2em; }
|
||||
@media (min-height: 750px) and (min-width: 700px) {
|
||||
.migration-flow {
|
||||
font-size: 14pt; } }
|
||||
.migration-flow .banner-image {
|
||||
margin: 1em;
|
||||
display: none; }
|
||||
@media (min-height: 550px) {
|
||||
.migration-flow .banner-image {
|
||||
display: inline-block;
|
||||
height: 10em;
|
||||
width: 10em; } }
|
||||
.migration-flow .banner-icon {
|
||||
margin: 1em;
|
||||
display: none; }
|
||||
@media (min-height: 550px) {
|
||||
.migration-flow .banner-icon {
|
||||
display: inline-block;
|
||||
height: 10em;
|
||||
width: 10em; } }
|
||||
.migration-flow .banner-icon.folder-outline {
|
||||
-webkit-mask: url("../images/folder-outline.svg") no-repeat center;
|
||||
-webkit-mask-size: 100%;
|
||||
background-color: #DEDEDE; }
|
||||
.migration-flow .banner-icon.export {
|
||||
-webkit-mask: url("../images/export.svg") no-repeat center;
|
||||
-webkit-mask-size: 100%;
|
||||
background-color: #DEDEDE; }
|
||||
.migration-flow .banner-icon.check-circle-outline {
|
||||
-webkit-mask: url("../images/check-circle-outline.svg") no-repeat center;
|
||||
-webkit-mask-size: 100%;
|
||||
background-color: #DEDEDE; }
|
||||
.migration-flow .banner-icon.alert-outline {
|
||||
-webkit-mask: url("../images/alert-outline.svg") no-repeat center;
|
||||
-webkit-mask-size: 100%;
|
||||
background-color: #DEDEDE; }
|
||||
.migration-flow .header {
|
||||
font-weight: normal;
|
||||
margin-bottom: 1.5em;
|
||||
font-size: 20pt; }
|
||||
@media (min-height: 750px) and (min-width: 700px) {
|
||||
.migration-flow .header {
|
||||
font-size: 28pt; } }
|
||||
.migration-flow .export-location {
|
||||
text-align: center;
|
||||
font-family: roboto;
|
||||
border: solid 1px #2090ea;
|
||||
color: #2090ea;
|
||||
margin-bottom: 1em;
|
||||
padding: 0.5em;
|
||||
-webkit-user-select: text;
|
||||
cursor: text; }
|
||||
.migration-flow .center {
|
||||
text-align: center; }
|
||||
.migration-flow .body-text {
|
||||
margin-bottom: 1em;
|
||||
max-width: 22em;
|
||||
text-align: left;
|
||||
margin-left: auto;
|
||||
margin-right: auto; }
|
||||
.migration-flow .body-text-wide {
|
||||
margin-bottom: 1em;
|
||||
max-width: 30em;
|
||||
text-align: left;
|
||||
margin-left: auto;
|
||||
margin-right: auto; }
|
||||
.migration-flow .step {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
padding: 70px 0 50px; }
|
||||
.migration-flow .step-body {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
max-width: 35em; }
|
||||
.migration-flow .linux-install-instructions .content {
|
||||
max-width: 1300px; }
|
||||
.migration-flow .linux-install-instructions .content .header {
|
||||
color: black;
|
||||
font-size: 120%; }
|
||||
.migration-flow .linux-install-instructions .content pre {
|
||||
cursor: text;
|
||||
-webkit-user-select: text;
|
||||
background-color: black;
|
||||
color: white;
|
||||
text-align: left;
|
||||
padding: 0.5em;
|
||||
overflow-x: scroll; }
|
||||
.migration-flow .linux-install-instructions .content pre::-webkit-scrollbar {
|
||||
width: 5px;
|
||||
height: 5px; }
|
||||
.migration-flow .linux-install-instructions .content pre::-webkit-scrollbar-track {
|
||||
background: transparent; }
|
||||
.migration-flow .linux-install-instructions .content pre::-webkit-scrollbar-thumb {
|
||||
background: rgba(255, 255, 255, 0.4);
|
||||
border-radius: 2; }
|
||||
.migration-flow .inner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
height: 100%; }
|
||||
.migration-flow .button {
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
border: none;
|
||||
min-width: 300px;
|
||||
padding: 0.75em;
|
||||
margin-top: 1em;
|
||||
margin-bottom: 1em;
|
||||
color: white;
|
||||
background: #2090ea;
|
||||
box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
|
||||
font-size: 12pt; }
|
||||
@media (min-height: 750px) and (min-width: 700px) {
|
||||
.migration-flow .button {
|
||||
font-size: 20pt; } }
|
||||
.migration-flow a.link {
|
||||
display: block;
|
||||
cursor: pointer;
|
||||
text-decoration: underline;
|
||||
margin: 0.5em;
|
||||
color: #2090ea; }
|
||||
.migration-flow .progress {
|
||||
text-align: center;
|
||||
padding: 1em;
|
||||
width: 80%;
|
||||
margin: auto; }
|
||||
.migration-flow .progress .bar-container {
|
||||
height: 1em;
|
||||
margin: 1em;
|
||||
background-color: #f3f3f3; }
|
||||
.migration-flow .progress .bar {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: #a2d2f4;
|
||||
transition: width 0.25s;
|
||||
box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5); }
|
||||
.migration-flow .install-container {
|
||||
text-align: center; }
|
||||
@media (min-height: 550px) {
|
||||
.migration-flow .install-container {
|
||||
margin-top: 4em; } }
|
||||
.migration-flow .install {
|
||||
cursor: pointer;
|
||||
background-color: white;
|
||||
padding: 0.5em;
|
||||
margin: 0.5em;
|
||||
display: inline-block;
|
||||
box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
|
||||
margin-left: 1em;
|
||||
margin-right: 1em; }
|
||||
.migration-flow .install a {
|
||||
color: black;
|
||||
text-decoration: none; }
|
||||
.migration-flow .install-icon {
|
||||
height: 7em;
|
||||
width: 7em;
|
||||
vertical-align: text-bottom;
|
||||
display: inline-block;
|
||||
margin: 1em; }
|
||||
@media (min-width: 800px) {
|
||||
.migration-flow .install-icon {
|
||||
margin-left: 2em;
|
||||
margin-right: 2em; } }
|
||||
.migration-flow .install-icon.apple {
|
||||
-webkit-mask: url("/images/apple.svg") no-repeat center;
|
||||
-webkit-mask-size: 100%;
|
||||
background-color: #2090ea; }
|
||||
.migration-flow .install-icon.windows {
|
||||
-webkit-mask: url("/images/windows.svg") no-repeat center;
|
||||
-webkit-mask-size: 100%;
|
||||
background-color: #2090ea; }
|
||||
.migration-flow .install-icon.linux {
|
||||
-webkit-mask: url("/images/linux.svg") no-repeat center;
|
||||
-webkit-mask-size: 100%;
|
||||
background-color: #2090ea; }
|
||||
.migration-flow .nav {
|
||||
width: 100%;
|
||||
bottom: 50px;
|
||||
margin-top: auto;
|
||||
padding-bottom: 1em;
|
||||
padding-left: 20px;
|
||||
padding-right: 20px; }
|
||||
|
||||
.inbox:focus {
|
||||
outline: none; }
|
||||
|
||||
@@ -841,11 +1064,11 @@ img.emoji.jumbo {
|
||||
padding: 0 20px;
|
||||
margin: 0 0 20px 20px; }
|
||||
.settings .syncSettings .synced_at {
|
||||
font-size: 0.92857em;
|
||||
font-size: 0.9285714286em;
|
||||
color: #616161; }
|
||||
.settings .syncSettings .sync_failed {
|
||||
display: none;
|
||||
font-size: 0.92857em;
|
||||
font-size: 0.9285714286em;
|
||||
color: red; }
|
||||
|
||||
.conversation-stack,
|
||||
@@ -853,7 +1076,7 @@ img.emoji.jumbo {
|
||||
height: 100%; }
|
||||
|
||||
.expired .conversation-stack, .expired .gutter {
|
||||
height: calc(100% - 56px); }
|
||||
height: calc(100% - 62px); }
|
||||
|
||||
.scrollable {
|
||||
height: 100%;
|
||||
@@ -994,7 +1217,7 @@ input.search {
|
||||
padding: 0.5em; }
|
||||
.index .last-message {
|
||||
margin: 6px 0 0;
|
||||
font-size: 0.92857em;
|
||||
font-size: 0.9285714286em;
|
||||
font-weight: 300; }
|
||||
.index .gutter .timestamp {
|
||||
position: absolute;
|
||||
@@ -1166,7 +1389,7 @@ input.search {
|
||||
.key-verification label {
|
||||
display: block;
|
||||
margin: 10px 0;
|
||||
font-size: 0.92857em; }
|
||||
font-size: 0.9285714286em; }
|
||||
.key-verification .icon {
|
||||
height: 1.25em;
|
||||
width: 1.25em;
|
||||
@@ -1272,7 +1495,7 @@ input.search {
|
||||
background-color: white; }
|
||||
.message-detail .contacts .contact-detail .error-message {
|
||||
margin: 6px 0 0;
|
||||
font-size: 0.92857em;
|
||||
font-size: 0.9285714286em;
|
||||
font-weight: bold;
|
||||
color: red; }
|
||||
.message-detail h3 {
|
||||
@@ -1654,7 +1877,7 @@ li.entry .error-icon-container {
|
||||
color: white;
|
||||
box-shadow: 0 0 5px 0 black;
|
||||
border-radius: 5px;
|
||||
font-size: 0.92857em;
|
||||
font-size: 0.9285714286em;
|
||||
z-index: 100; }
|
||||
|
||||
.confirmation-dialog .content {
|
||||
@@ -2137,13 +2360,25 @@ li.entry .error-icon-container {
|
||||
border: 1px solid #292929; }
|
||||
.android-dark button:hover, .android-dark .confirmation-dialog .content .buttons button:hover {
|
||||
background-color: #b8b8b8; }
|
||||
.android-dark .upgrade-banner button {
|
||||
float: right;
|
||||
border: none;
|
||||
color: white;
|
||||
font-family: roboto-light;
|
||||
font-size: 14pt;
|
||||
border-radius: 0;
|
||||
box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
|
||||
line-height: 36px;
|
||||
padding: 0 2em;
|
||||
background: #2090ea;
|
||||
margin-left: 20px; }
|
||||
.android-dark .message-detail, .android-dark .message-container, .android-dark .conversation,
|
||||
.android-dark .discussion-container {
|
||||
background-color: #171717; }
|
||||
.android-dark .modal .content {
|
||||
background-color: #333333; }
|
||||
.android-dark .lightbox .content {
|
||||
background-color: transparent; }
|
||||
background-color: rgba(0, 0, 0, 0); }
|
||||
.android-dark .key-verification .key {
|
||||
background-color: #030303;
|
||||
border-color: #292929; }
|
||||
|
||||