From 899b2b277b164e6b67f46f37420fe890e6296205 Mon Sep 17 00:00:00 2001 From: Vincent Date: Fri, 3 Jan 2020 19:56:29 +1100 Subject: [PATCH 01/12] Finished QR modal --- ts/components/AddServerDialog.tsx | 8 ++--- ts/components/DevicePairingDialog.tsx | 45 ++++++++++++++++----------- 2 files changed, 30 insertions(+), 23 deletions(-) diff --git a/ts/components/AddServerDialog.tsx b/ts/components/AddServerDialog.tsx index b6373455f..57fa9c19e 100644 --- a/ts/components/AddServerDialog.tsx +++ b/ts/components/AddServerDialog.tsx @@ -55,7 +55,7 @@ export class AddServerDialog extends React.Component {
@@ -217,7 +217,7 @@ export class AddServerDialog extends React.Component { const rawserverURL = serverURL .replace(/^https?:\/\//i, '') .replace(/[/\\]+$/i, ''); - const sslserverURL = `https://${rawserverURL}`; + const sslServerURL = `https://${rawserverURL}`; const conversationId = `publicChat:${channelId}@${rawserverURL}`; const conversationExists = window.ConversationController.get( @@ -231,7 +231,7 @@ export class AddServerDialog extends React.Component { } const serverAPI = await window.lokiPublicChatAPI.findOrCreateServer( - sslserverURL + sslServerURL ); if (!serverAPI) { // Url incorrect or server not compatible @@ -246,7 +246,7 @@ export class AddServerDialog extends React.Component { ); await serverAPI.findOrCreateChannel(channelId, conversationId); - await conversation.setPublicSource(sslserverURL, channelId); + await conversation.setPublicSource(sslServerURL, channelId); await conversation.setFriendRequestStatus( window.friends.friendRequestStatusEnum.friends ); diff --git a/ts/components/DevicePairingDialog.tsx b/ts/components/DevicePairingDialog.tsx index e855c7374..5b72f9d55 100644 --- a/ts/components/DevicePairingDialog.tsx +++ b/ts/components/DevicePairingDialog.tsx @@ -45,7 +45,7 @@ export class DevicePairingDialog extends React.Component { loading: true, view: 'default', pubKeyRequests: [], - data: [], + data: ['22452653255643252533'], }; } @@ -59,23 +59,30 @@ export class DevicePairingDialog extends React.Component { const waitingForRequest = this.state.view === 'waitingForRequest'; const nothingPaired = this.state.data.length === 0; - const renderPairedDevices = this.state.data.map((pubKey: any) => { - const pubKeyInfo = this.getPubkeyName(pubKey); - const isFinalItem = - this.state.data[this.state.data.length - 1] === pubKey; - - return ( -
-

- {pubKeyInfo.deviceAlias} -
- Pairing Secret:{' '} - {pubKeyInfo.secretWords} -

- {!isFinalItem ?
: null} -
- ); - }); + console.log(this.state); + console.log(this.state); + console.log(this.state); + console.log(this.state); + console.log(this.state); + + console.log('dAAVIHN'); + // const renderPairedDevices = this.state.data.map((pubKey: any) => { + // const pubKeyInfo = this.getPubkeyName(pubKey); + // const isFinalItem = + // this.state.data[this.state.data.length - 1] === pubKey; + + // return ( + //
+ //

+ // {pubKeyInfo.deviceAlias} + //
+ // Pairing Secret:{' '} + // {pubKeyInfo.secretWords} + //

+ // {!isFinalItem ?
: null} + //
+ // ); + // }); return ( <> @@ -113,7 +120,7 @@ export class DevicePairingDialog extends React.Component {
) : (
- {renderPairedDevices} + {'renderPairedDevices'}
)} From 2caffa9289aeb4fadcab18cd047f20785def3084 Mon Sep 17 00:00:00 2001 From: Vincent Date: Mon, 6 Jan 2020 09:30:29 +1100 Subject: [PATCH 02/12] Dependencies resorted and modal adjustments --- _locales/en/messages.json | 8 +++ js/modules/signal.js | 3 ++ js/views/app_view.js | 4 +- js/views/qr_dialog_view.js | 37 +++++++------- js/views/qr_dialog_view_old.js | 47 ++++++++++++++++++ package.json | 2 +- stylesheets/_session.scss | 21 ++++++++ ts/components/DevicePairingDialog.tsx | 20 ++++---- .../session/SessionDropdownTrigger.tsx | 37 -------------- ts/components/session/SessionQRModal.tsx | 49 +++++++++++++++++++ yarn.lock | 16 ++++-- 11 files changed, 171 insertions(+), 73 deletions(-) create mode 100644 js/views/qr_dialog_view_old.js delete mode 100644 ts/components/session/SessionDropdownTrigger.tsx create mode 100644 ts/components/session/SessionQRModal.tsx diff --git a/_locales/en/messages.json b/_locales/en/messages.json index afefeff60..e16cef15d 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -2106,6 +2106,14 @@ "description": "Button action that the user can click to view their unique seed" }, + "QRCodeTitle": { + "message": "Your Public Key QRCode", + "description": "Title given to QRCode modal" + }, + "QRCodeDescription": { + "message": "Your friends can scan this QR code to start a conversation with you.", + "description": "Description given to QRCode modal" + }, "showQRCode": { "message": "Show QR code", "description": "Button action that the user can click to view their QR code" diff --git a/js/modules/signal.js b/js/modules/signal.js index 47b6c5543..e8b9006ce 100644 --- a/js/modules/signal.js +++ b/js/modules/signal.js @@ -61,9 +61,11 @@ const { const { SessionToast } = require('../../ts/components/session/SessionToast'); const { SessionToggle } = require('../../ts/components/session/SessionToggle'); const { SessionModal } = require('../../ts/components/session/SessionModal'); +const { SessionQRModal } = require('../../ts/components/session/SessionQRModal'); const { SessionConfirm, } = require('../../ts/components/session/SessionConfirm'); + const { SessionDropdown, } = require('../../ts/components/session/SessionDropdown'); @@ -272,6 +274,7 @@ exports.setup = (options = {}) => { SessionToggle, SessionConfirm, SessionModal, + SessionQRModal, SessionDropdown, MediaGallery, Message, diff --git a/js/views/app_view.js b/js/views/app_view.js index ab8fbc193..f0e16347b 100644 --- a/js/views/app_view.js +++ b/js/views/app_view.js @@ -205,7 +205,9 @@ this.el.append(dialog.el); }, showQRDialog(string) { - const dialog = new Whisper.QRDialogView({ string }); + const dialog = new Whisper.QRDialogView({ + value: string, + }); this.el.append(dialog.el); }, showDevicePairingDialog() { diff --git a/js/views/qr_dialog_view.js b/js/views/qr_dialog_view.js index c365166c7..440a6694c 100644 --- a/js/views/qr_dialog_view.js +++ b/js/views/qr_dialog_view.js @@ -1,4 +1,4 @@ -/* global Whisper, i18n, QRCode */ +/* global Whisper */ // eslint-disable-next-line func-names (function() { @@ -7,28 +7,27 @@ window.Whisper = window.Whisper || {}; Whisper.QRDialogView = Whisper.View.extend({ - templateName: 'qr-code-template', className: 'loki-dialog qr-dialog modal', - initialize(options = {}) { - this.okText = options.okText || i18n('ok'); + initialize(options) { + this.value = options.value || ''; + this.close = this.close.bind(this); + this.onKeyup = this.onKeyup.bind(this); this.render(); - this.$('.qr-dialog').bind('keyup', event => this.onKeyup(event)); - - if (options.string) { - this.qr = new QRCode(this.$('#qr')[0], { - correctLevel: QRCode.CorrectLevel.L, - }).makeCode(options.string); - this.$('#qr').addClass('ready'); - } - }, - events: { - 'click .ok': 'close', }, - render_attributes() { - return { - ok: this.okText, - }; + + render() { + this.dialogView = new Whisper.ReactWrapperView({ + className: 'qr-dialog', + Component: window.Signal.Components.SessionQRModal, + props: { + value: this.value, + }, + }); + + this.$el.append(this.dialogView.el); + return this; }, + close() { this.remove(); }, diff --git a/js/views/qr_dialog_view_old.js b/js/views/qr_dialog_view_old.js new file mode 100644 index 000000000..c365166c7 --- /dev/null +++ b/js/views/qr_dialog_view_old.js @@ -0,0 +1,47 @@ +/* global Whisper, i18n, QRCode */ + +// eslint-disable-next-line func-names +(function() { + 'use strict'; + + window.Whisper = window.Whisper || {}; + + Whisper.QRDialogView = Whisper.View.extend({ + templateName: 'qr-code-template', + className: 'loki-dialog qr-dialog modal', + initialize(options = {}) { + this.okText = options.okText || i18n('ok'); + this.render(); + this.$('.qr-dialog').bind('keyup', event => this.onKeyup(event)); + + if (options.string) { + this.qr = new QRCode(this.$('#qr')[0], { + correctLevel: QRCode.CorrectLevel.L, + }).makeCode(options.string); + this.$('#qr').addClass('ready'); + } + }, + events: { + 'click .ok': 'close', + }, + render_attributes() { + return { + ok: this.okText, + }; + }, + close() { + this.remove(); + }, + onKeyup(event) { + switch (event.key) { + case 'Enter': + case 'Escape': + case 'Esc': + this.close(); + break; + default: + break; + } + }, + }); +})(); diff --git a/package.json b/package.json index a2305a59a..2ee4e6a2a 100644 --- a/package.json +++ b/package.json @@ -106,7 +106,7 @@ "react-contextmenu": "2.11.0", "react-dom": "16.8.3", "react-portal": "^4.2.0", - "react-qrcode": "^0.2.0", + "react-qr-svg": "^2.2.1", "react-redux": "6.0.1", "react-virtualized": "9.21.0", "read-last-lines": "1.3.0", diff --git a/stylesheets/_session.scss b/stylesheets/_session.scss index 228286ead..d1efe5f37 100644 --- a/stylesheets/_session.scss +++ b/stylesheets/_session.scss @@ -101,6 +101,10 @@ div.spacer-lg { opacity: 0.4; } +.text-center { + text-align: center; +} + .fullwidth { width: 100%; } @@ -111,6 +115,8 @@ $session-icon-size-sm: 15px; $session-icon-size-md: 20px; $session-icon-size-lg: 30px; +$session-modal-size: 220px; + $session-conversation-header-height: 60px; @mixin fontWasaBold { @@ -795,3 +801,18 @@ label { } } } + +#qr svg { + width: $session-modal-size; + height: $session-modal-size; + border: 6px solid white; + border-radius: 3px; +} + +.qr-dialog { + &__description { + max-width: $session-modal-size; + text-align: center; + margin: 0 auto; + } +} \ No newline at end of file diff --git a/ts/components/DevicePairingDialog.tsx b/ts/components/DevicePairingDialog.tsx index 5b72f9d55..6c5f3b44b 100644 --- a/ts/components/DevicePairingDialog.tsx +++ b/ts/components/DevicePairingDialog.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { QRCode } from 'react-qrcode'; +import { QRCode } from 'react-qr-svg'; import { SessionModal } from './session/SessionModal'; import { SessionButton } from './session/SessionButton'; @@ -45,11 +45,11 @@ export class DevicePairingDialog extends React.Component { loading: true, view: 'default', pubKeyRequests: [], - data: ['22452653255643252533'], + data: [], }; } - public componentDidMount() { + componentDidMount() { this.getSecondaryDevices(); } @@ -59,13 +59,6 @@ export class DevicePairingDialog extends React.Component { const waitingForRequest = this.state.view === 'waitingForRequest'; const nothingPaired = this.state.data.length === 0; - console.log(this.state); - console.log(this.state); - console.log(this.state); - console.log(this.state); - console.log(this.state); - - console.log('dAAVIHN'); // const renderPairedDevices = this.state.data.map((pubKey: any) => { // const pubKeyInfo = this.getPubkeyName(pubKey); // const isFinalItem = @@ -101,7 +94,12 @@ export class DevicePairingDialog extends React.Component {
- +
diff --git a/ts/components/session/SessionDropdownTrigger.tsx b/ts/components/session/SessionDropdownTrigger.tsx deleted file mode 100644 index 0a366388f..000000000 --- a/ts/components/session/SessionDropdownTrigger.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import React from 'react'; - -interface Props { - //mouseButton: Number; - posX: number; - posY: number; -} - -export class SessionDropdownTrigger extends React.Component { - public static defaultProps = { - //mouseButton: 2, // 0 is left click, 2 is right click - posX: 0, - posY: 0, - }; - constructor(props: any) { - super(props); - } - - public handleDropdownClick = (event: any) => { - event.preventDefault(); - event.stopPropagation(); - - let x = event.clientX || (event.touches && event.touches[0].pageX); - let y = event.clientY || (event.touches && event.touches[0].pageY); - - if (this.props.posX) { - x -= this.props.posX; - } - if (this.props.posY) { - y -= this.props.posY; - } - }; - - public render() { - return
; - } -} diff --git a/ts/components/session/SessionQRModal.tsx b/ts/components/session/SessionQRModal.tsx new file mode 100644 index 000000000..0c9ed3411 --- /dev/null +++ b/ts/components/session/SessionQRModal.tsx @@ -0,0 +1,49 @@ +import React from 'react'; +import { QRCode } from 'react-qr-svg'; + +import { SessionModal } from './SessionModal'; + +interface Props { + value: string; +} + +export class SessionQRModal extends React.Component { + constructor(props: any) { + super(props); + } + + public render() { + const { value } = this.props; + + console.log('skbsvbsgb'); + console.log('skbsvbsgb'); + console.log('skbsvbsgb'); + + return ( + null} + onClose={() => null} + > +
+ +
+

+ {window.i18n('QRCodeDescription')} +

+
+
+ +
+ +
+ +
+ ); + } + } \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 9a1347020..20e87d833 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7520,6 +7520,11 @@ q@^1.1.2, q@~1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/q/-/q-1.5.0.tgz#dd01bac9d06d30e6f219aecb8253ee9ebdc308f1" +qr.js@0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/qr.js/-/qr.js-0.0.0.tgz#cace86386f59a0db8050fa90d9b6b0e88a1e364f" + integrity sha1-ys6GOG9ZoNuAUPqQ2baw6IoeNk8= + qrcode@^1.4.4: version "1.4.4" resolved "https://registry.yarnpkg.com/qrcode/-/qrcode-1.4.4.tgz#f0c43568a7e7510a55efc3b88d9602f71963ea83" @@ -7758,10 +7763,13 @@ react-portal@^4.2.0: dependencies: prop-types "^15.5.8" -react-qrcode@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/react-qrcode/-/react-qrcode-0.2.0.tgz#a05cf2ae5ac57c3a9751e512132a821ed60533f9" - integrity sha512-3JzSzkTUUMb26sbq5/u75zw9l3gQ1BLvdCAYgRnAZ1wGJj1Su94pzv4g/XfzaJEj6h8Y0H9mYX4djmKBGZQHSQ== +react-qr-svg@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/react-qr-svg/-/react-qr-svg-2.2.1.tgz#f6f25820fdb2c39ad0d66a14a758e2f575717c68" + integrity sha512-rLDCZI9pIqD5lbBIatrqUMhP1gqQ7glqubXO/m/X87ikEPhXuY0hMLhYMuKoH4834G36ap8Az0HI4bXEJUN//w== + dependencies: + prop-types "^15.5.8" + qr.js "0.0.0" react-redux@6.0.1: version "6.0.1" From 2ede557a744c669b909afb50ac2ace83a062d10a Mon Sep 17 00:00:00 2001 From: Vincent Date: Mon, 6 Jan 2020 15:46:49 +1100 Subject: [PATCH 03/12] Modal additions incl. QR fixup and Seed View --- _locales/en/messages.json | 22 +- js/modules/signal.js | 8 +- js/views/qr_dialog_view.js | 16 +- js/views/seed_dialog_view.js | 94 ++------- js/views/session_dropdown_view.js | 32 --- package.json | 1 - stylesheets/_session.scss | 32 ++- ts/components/DevicePairingDialog.tsx | 108 +++++----- .../conversation/ConversationHeader.tsx | 15 +- ts/components/session/SessionModal.tsx | 5 +- ts/components/session/SessionQRModal.tsx | 41 ++-- ts/components/session/SessionSeedModal.tsx | 198 ++++++++++++++++++ ts/global.d.ts | 1 + yarn.lock | 67 +----- 14 files changed, 353 insertions(+), 287 deletions(-) create mode 100644 ts/components/session/SessionSeedModal.tsx diff --git a/_locales/en/messages.json b/_locales/en/messages.json index e16cef15d..07d5362f0 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -2102,16 +2102,26 @@ }, "showSeed": { - "message": "Show seed", + "message": "Show Seed", "description": "Button action that the user can click to view their unique seed" }, + "showSeedPasswordRequest": { + "message": "Please enter your password", + "description": "Request for user to enter password to show seed." + }, + "seedSavePrompt": { + "message": + "Please save the seed below in a safe location. They can be used to restore your account if you lose access or migrate to a new device.", + "description": "Prompt on seed modal requesting user to save their seed" + }, "QRCodeTitle": { "message": "Your Public Key QRCode", "description": "Title given to QRCode modal" }, "QRCodeDescription": { - "message": "Your friends can scan this QR code to start a conversation with you.", + "message": + "Your friends can scan this QR code to start a conversation with you.", "description": "Description given to QRCode modal" }, "showQRCode": { @@ -2168,7 +2178,10 @@ "description": "A button action that the user can click to reset the database" }, - + "password": { + "message": "Password", + "description": "Placeholder for password input" + }, "setPassword": { "message": "Set Password", "description": "Button action that the user can click to set a password" @@ -2190,6 +2203,9 @@ "invalidPassword": { "message": "Invalid password" }, + "noGivenPassword": { + "message": "Please enter your password" + }, "passwordsDoNotMatch": { "message": "Passwords do not match" }, diff --git a/js/modules/signal.js b/js/modules/signal.js index e8b9006ce..c34d37c8c 100644 --- a/js/modules/signal.js +++ b/js/modules/signal.js @@ -61,7 +61,12 @@ const { const { SessionToast } = require('../../ts/components/session/SessionToast'); const { SessionToggle } = require('../../ts/components/session/SessionToggle'); const { SessionModal } = require('../../ts/components/session/SessionModal'); -const { SessionQRModal } = require('../../ts/components/session/SessionQRModal'); +const { + SessionQRModal, +} = require('../../ts/components/session/SessionQRModal'); +const { + SessionSeedModal, +} = require('../../ts/components/session/SessionSeedModal'); const { SessionConfirm, } = require('../../ts/components/session/SessionConfirm'); @@ -275,6 +280,7 @@ exports.setup = (options = {}) => { SessionConfirm, SessionModal, SessionQRModal, + SessionSeedModal, SessionDropdown, MediaGallery, Message, diff --git a/js/views/qr_dialog_view.js b/js/views/qr_dialog_view.js index 440a6694c..6019f87c9 100644 --- a/js/views/qr_dialog_view.js +++ b/js/views/qr_dialog_view.js @@ -11,16 +11,17 @@ initialize(options) { this.value = options.value || ''; this.close = this.close.bind(this); - this.onKeyup = this.onKeyup.bind(this); + this.render(); }, render() { this.dialogView = new Whisper.ReactWrapperView({ - className: 'qr-dialog', + className: 'qr-dialog-wrapper', Component: window.Signal.Components.SessionQRModal, props: { value: this.value, + onClose: this.close, }, }); @@ -31,16 +32,5 @@ close() { this.remove(); }, - onKeyup(event) { - switch (event.key) { - case 'Enter': - case 'Escape': - case 'Esc': - this.close(); - break; - default: - break; - } - }, }); })(); diff --git a/js/views/seed_dialog_view.js b/js/views/seed_dialog_view.js index 88b282008..0ddeea06c 100644 --- a/js/views/seed_dialog_view.js +++ b/js/views/seed_dialog_view.js @@ -1,4 +1,4 @@ -/* global Whisper, i18n, Signal, passwordUtil */ +/* global Whisper */ // eslint-disable-next-line func-names (function() { @@ -8,90 +8,26 @@ Whisper.SeedDialogView = Whisper.View.extend({ className: 'loki-dialog seed-dialog modal', - templateName: 'seed-dialog', - initialize(options = {}) { - this.okText = options.okText || i18n('ok'); - this.cancelText = options.cancelText || i18n('cancel'); - this.confirmText = options.confirmText || i18n('confirm'); - this.copyText = options.copyText || i18n('copySeed'); - this.seed = options.seed || '-'; - + initialize() { + this.close = this.close.bind(this); this.render(); - this.showSeedView(false); - this.initPasswordHash(); - - this.$('#password').bind('keyup', event => this.onKeyup(event)); - }, - events: { - 'click .ok': 'close', - 'click .confirm': 'confirmPassword', - 'click .copy-seed': 'copySeed', - }, - render_attributes() { - return { - passwordViewTitle: i18n('passwordViewTitle'), - seedViewTitle: i18n('seedViewTitle'), - ok: this.okText, - copyText: this.copyText, - confirm: this.confirmText, - cancel: this.cancelText, - }; - }, - async initPasswordHash() { - const hash = await Signal.Data.getPasswordHash(); - this.passwordHash = hash; - this.showSeedView(!hash); - }, - showSeedView(show) { - const seedView = this.$('.seedView'); - const passwordView = this.$('.passwordView'); - if (show) { - this.$('.seed').html(this.seed); - seedView.show(); - passwordView.hide(); - } else { - this.$('.seed').html(''); - passwordView.show(); - this.$('#password').focus(); - seedView.hide(); - } }, - confirmPassword() { - this.$('.error').html(); - const password = this.$('#password').val(); - if ( - this.passwordHash && - !passwordUtil.matchesHash(password, this.passwordHash) - ) { - this.$('.error').html(`Error: ${i18n('invalidPassword')}`); - return; - } - this.showSeedView(true); + render() { + this.dialogView = new Whisper.ReactWrapperView({ + className: 'seed-dialog-wrapper', + Component: window.Signal.Components.SessionSeedModal, + props: { + onClose: this.close, + }, + }); + + this.$el.append(this.dialogView.el); + return this; }, + close() { this.remove(); }, - copySeed() { - window.clipboard.writeText(this.seed); - window.pushToast({ - title: i18n('copiedMnemonic'), - type: 'success', - id: 'copySeedToast', - }); - }, - onKeyup(event) { - switch (event.key) { - case 'Enter': - this.confirmPassword(); - break; - case 'Escape': - case 'Esc': - this.close(); - break; - default: - break; - } - }, }); })(); diff --git a/js/views/session_dropdown_view.js b/js/views/session_dropdown_view.js index a655bad34..e69de29bb 100644 --- a/js/views/session_dropdown_view.js +++ b/js/views/session_dropdown_view.js @@ -1,32 +0,0 @@ -/* global Whisper */ - -// eslint-disable-next-line func-names -(function() { - 'use strict'; - - window.Whisper = window.Whisper || {}; - - Whisper.SessionDropdownView = Whisper.View.extend({ - initialize(options) { - this.props = { - items: options.items, - }; - - this.render(); - }, - - render() { - this.dropdownView = new Whisper.ReactWrapperView({ - className: 'session-dropdown-wrapper', - Component: window.Signal.Components.SessionDropdown, - props: this.props, - }); - - this.$el.append(this.dropdownView.el); - }, - - openDropdown() {}, - - closeDropdown() {}, - }); -})(); diff --git a/package.json b/package.json index 2ee4e6a2a..dc3135079 100644 --- a/package.json +++ b/package.json @@ -101,7 +101,6 @@ "pify": "3.0.0", "protobufjs": "6.8.6", "proxy-agent": "3.0.3", - "qrcode": "^1.4.4", "react": "16.8.3", "react-contextmenu": "2.11.0", "react-dom": "16.8.3", diff --git a/stylesheets/_session.scss b/stylesheets/_session.scss index d1efe5f37..b01fbadeb 100644 --- a/stylesheets/_session.scss +++ b/stylesheets/_session.scss @@ -105,6 +105,13 @@ div.spacer-lg { text-align: center; } +@mixin text-highlight($color) { + background-color: rgba($color, 0.8); + padding: $session-margin-xs; + border-radius: 3px; + display: inline-block; +} + .fullwidth { width: 100%; } @@ -115,7 +122,9 @@ $session-icon-size-sm: 15px; $session-icon-size-md: 20px; $session-icon-size-lg: 30px; -$session-modal-size: 220px; +$session-modal-size-sm: 220px; +$session-modal-size-md: 400px; +$session-modal-size-lg: 650px; $session-conversation-header-height: 60px; @@ -470,7 +479,7 @@ label { right: $session-margin-lg; bottom: $session-margin-lg; - z-index: 100; + z-index: 10000; } .session-toast { @@ -552,6 +561,7 @@ label { left: 50%; top: 50%; transform: translate(-50%, -50%); + min-width: 300px; box-sizing: border-box; max-height: 70vh; @@ -622,6 +632,10 @@ label { margin: 0 $session-margin-xs; } } + + &__text-highlight { + @include text-highlight($session-shade-15); + } } .session-toggle { @@ -803,16 +817,22 @@ label { } #qr svg { - width: $session-modal-size; - height: $session-modal-size; + width: $session-modal-size-sm; + height: $session-modal-size-sm; border: 6px solid white; border-radius: 3px; } .qr-dialog { &__description { - max-width: $session-modal-size; + max-width: $session-modal-size-sm; text-align: center; margin: 0 auto; } -} \ No newline at end of file +} + +.seed-dialog { + &__description { + max-width: $session-modal-size-lg; + } +} diff --git a/ts/components/DevicePairingDialog.tsx b/ts/components/DevicePairingDialog.tsx index 6c5f3b44b..216ee0b30 100644 --- a/ts/components/DevicePairingDialog.tsx +++ b/ts/components/DevicePairingDialog.tsx @@ -49,7 +49,7 @@ export class DevicePairingDialog extends React.Component { }; } - componentDidMount() { + public componentDidMount() { this.getSecondaryDevices(); } @@ -98,7 +98,7 @@ export class DevicePairingDialog extends React.Component { value={window.textsecure.storage.user.getNumber()} bgColor="#FFFFFF" fgColor="#000000" - level="L" + level="L" />
@@ -207,58 +207,58 @@ export class DevicePairingDialog extends React.Component { this.showView(); } - private requestReceived(secondaryDevicePubKey: string | EventHandlerNonNull) { - // FIFO: push at the front of the array with unshift() - this.state.pubKeyRequests.unshift(secondaryDevicePubKey); - if (!this.state.currentPubKey) { - this.nextPubKey(); - - this.showView('requestReceived'); - } - } - - private allowDevice() { - this.setState({ - accepted: true, - }); - window.Whisper.trigger( - 'devicePairingRequestAccepted', - this.state.currentPubKey, - (errors: any) => { - this.transmisssionCB(errors); - - return true; - } - ); - this.showView(); - } - - private transmisssionCB(errors: any) { - if (!errors) { - this.setState({ - success: true, - }); - } else { - return; - } - } - - private skipDevice() { - window.Whisper.trigger( - 'devicePairingRequestRejected', - this.state.currentPubKey - ); - this.nextPubKey(); - this.showView(); - } - - private nextPubKey() { - // FIFO: pop at the back of the array using pop() - const pubKeyRequests = this.state.pubKeyRequests; - this.setState({ - currentPubKey: pubKeyRequests.pop(), - }); - } + // private requestReceived(secondaryDevicePubKey: string | EventHandlerNonNull) { + // // FIFO: push at the front of the array with unshift() + // this.state.pubKeyRequests.unshift(secondaryDevicePubKey); + // if (!this.state.currentPubKey) { + // this.nextPubKey(); + + // this.showView('requestReceived'); + // } + // } + + // private allowDevice() { + // this.setState({ + // accepted: true, + // }); + // window.Whisper.trigger( + // 'devicePairingRequestAccepted', + // this.state.currentPubKey, + // (errors: any) => { + // this.transmisssionCB(errors); + + // return true; + // } + // ); + // this.showView(); + // } + + // private transmisssionCB(errors: any) { + // if (!errors) { + // this.setState({ + // success: true, + // }); + // } else { + // return; + // } + // } + + // private skipDevice() { + // window.Whisper.trigger( + // 'devicePairingRequestRejected', + // this.state.currentPubKey + // ); + // this.nextPubKey(); + // this.showView(); + // } + + // private nextPubKey() { + // // FIFO: pop at the back of the array using pop() + // const pubKeyRequests = this.state.pubKeyRequests; + // this.setState({ + // currentPubKey: pubKeyRequests.pop(), + // }); + // } private onKeyUp(event: any) { switch (event.key) { diff --git a/ts/components/conversation/ConversationHeader.tsx b/ts/components/conversation/ConversationHeader.tsx index fe065e6bc..06e3433c0 100644 --- a/ts/components/conversation/ConversationHeader.tsx +++ b/ts/components/conversation/ConversationHeader.tsx @@ -3,12 +3,7 @@ import React from 'react'; import { ContactName } from './ContactName'; import { Avatar } from '../Avatar'; import { Colors, LocalizerType } from '../../types/Util'; -import { - ContextMenu, - ContextMenuTrigger, - MenuItem, - SubMenu, -} from 'react-contextmenu'; +import { ContextMenu, MenuItem, SubMenu } from 'react-contextmenu'; import { SessionIconButton, @@ -22,8 +17,6 @@ import { SessionButtonType, } from '../session/SessionButton'; -import { SessionDropdownTrigger } from '../session/SessionDropdownTrigger'; - interface TimerOption { name: string; value: number; @@ -239,7 +232,7 @@ export class ConversationHeader extends React.Component { ); } - public renderOptions(triggerId: string) { + public renderOptions() { const { showBackButton } = this.props; if (showBackButton) { @@ -247,12 +240,12 @@ export class ConversationHeader extends React.Component { } return ( - + <> - + ); } diff --git a/ts/components/session/SessionModal.tsx b/ts/components/session/SessionModal.tsx index b77d0aba3..3a14421e6 100644 --- a/ts/components/session/SessionModal.tsx +++ b/ts/components/session/SessionModal.tsx @@ -84,7 +84,10 @@ export class SessionModal extends React.PureComponent { }); window.removeEventListener('keyup', this.onKeyUp); - this.props.onClose(); + + if (this.props.onClose) { + this.props.onClose(); + } } public onKeyUp(event: any) { diff --git a/ts/components/session/SessionQRModal.tsx b/ts/components/session/SessionQRModal.tsx index 0c9ed3411..0f4e2ba70 100644 --- a/ts/components/session/SessionQRModal.tsx +++ b/ts/components/session/SessionQRModal.tsx @@ -2,9 +2,11 @@ import React from 'react'; import { QRCode } from 'react-qr-svg'; import { SessionModal } from './SessionModal'; +import { SessionButton } from './SessionButton'; interface Props { value: string; + onClose: any; } export class SessionQRModal extends React.Component { @@ -13,37 +15,30 @@ export class SessionQRModal extends React.Component { } public render() { - const { value } = this.props; - - console.log('skbsvbsgb'); - console.log('skbsvbsgb'); - console.log('skbsvbsgb'); + const { value, onClose } = this.props; return ( null} - onClose={() => null} + onClose={onClose} > -
- -
-

- {window.i18n('QRCodeDescription')} -

+
+ +
+

{window.i18n('QRCodeDescription')}

-
+
- + +
+ +
+
+
- - ); - } - } \ No newline at end of file + ); + } +} diff --git a/ts/components/session/SessionSeedModal.tsx b/ts/components/session/SessionSeedModal.tsx new file mode 100644 index 000000000..621434254 --- /dev/null +++ b/ts/components/session/SessionSeedModal.tsx @@ -0,0 +1,198 @@ +import React from 'react'; + +import { SessionModal } from './SessionModal'; +import { SessionButton } from './SessionButton'; + +interface Props { + onClose: any; +} + +interface State { + error: string; + loadingPassword: boolean; + loadingSeed: boolean; + seed: string; + hasPassword: boolean | null; + passwordHash: string; + passwordValid: boolean; +} + +export class SessionSeedModal extends React.Component { + constructor(props: any) { + super(props); + + this.state = { + error: '', + loadingPassword: true, + loadingSeed: true, + seed: '', + hasPassword: null, + passwordHash: '', + passwordValid: false, + }; + + this.copySeed = this.copySeed.bind(this); + this.getSeed = this.getSeed.bind(this); + this.confirmPassword = this.confirmPassword.bind(this); + this.checkHasPassword = this.checkHasPassword.bind(this); + } + + public render() { + const i18n = window.i18n; + const { onClose } = this.props; + + const maxPasswordLen = 64; + + this.checkHasPassword(); + this.getSeed(); + + const error = this.state.error; + const hasPassword = this.state.hasPassword; + const passwordValid = this.state.passwordValid; + + const loading = this.state.loadingPassword || this.state.loadingSeed; + + return ( + null} + onClose={onClose} + > + {!loading && ( + <> +
+ + {hasPassword && !passwordValid ? ( +
+

{i18n('showSeedPasswordRequest')}

+ + + {error && ( + <> +
+
{error}
+ + )} + +
+ +
+ + + +
+
+ ) : ( + <> +
+

+ {i18n('seedSavePrompt')} +

+
+ + + {this.state.seed} + +
+
+ +
+ + + { + this.copySeed(this.state.seed); + }} + /> +
+ + )} + + )} + + ); + } + + private confirmPassword() { + const passwordHash = this.state.passwordHash; + const passwordValue = $('#seed-input-password').val(); + const isPasswordValid = window.passwordUtil.matchesHash( + passwordValue, + passwordHash + ); + + if (!passwordValue) { + this.setState({ + error: window.i18n('noGivenPassword'), + }); + + return false; + } + + if (passwordHash && !isPasswordValid) { + this.setState({ + error: window.i18n('invalidPassword'), + }); + + return false; + } + + this.setState({ + passwordValid: true, + error: '', + }); + + return true; + } + + private checkHasPassword() { + if (!this.state.loadingPassword) { + return; + } + + const hashPromise = window.Signal.Data.getPasswordHash(); + + hashPromise.then((hash: any) => { + this.setState({ + hasPassword: !!hash, + passwordHash: hash, + loadingPassword: false, + }); + }); + } + + private async getSeed() { + if (this.state.seed) { + return this.state.seed; + } + + const manager = await window.getAccountManager(); + const seed = manager.getCurrentMnemonic(); + + this.setState({ + seed, + loadingSeed: false, + }); + + return seed; + } + + private copySeed(seed: string) { + window.clipboard.writeText(seed); + + window.pushToast({ + title: window.i18n('copiedMnemonic'), + type: 'success', + id: 'copySeedToast', + }); + } +} diff --git a/ts/global.d.ts b/ts/global.d.ts index 9e1d3dbc4..f5a1845cc 100644 --- a/ts/global.d.ts +++ b/ts/global.d.ts @@ -1,6 +1,7 @@ interface Window { getAccountManager: any; mnemonic: any; + clipboard: any; passwordUtil: any; dcodeIO: any; libsignal: any; diff --git a/yarn.lock b/yarn.lock index 20e87d833..66dc3a06e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -139,9 +139,9 @@ integrity sha512-Q1y515GcOdTHgagaVFhHnIFQ38ygs/kmxdNpvpou+raI9UO3YZcHDngBSYKQklcKlvA7iuQlmIKbzvmxcOE9CQ== "@types/dompurify@^2.0.0": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@types/dompurify/-/dompurify-2.0.1.tgz#0bf3a9f8ee21d81adb20b8c374ab034d6a74dbf7" - integrity sha512-OQ16dECrRv/I//woKkVUxyVGYR94W3qp3Wy//B63awHVe3h/1/URFqP5a/V2m4k01DEvWs1+z7FWW3xfM1lH3Q== + version "2.0.0" + resolved "https://registry.yarnpkg.com/@types/dompurify/-/dompurify-2.0.0.tgz#9616caa5bf2569aea2e4889d4f929d968c081b40" + integrity sha512-g/ilp+Bo6Ljy60i5LnjkGw00X7EIoFjoPGlxqZhV8TJ9fWEzXheioU1O+U/UzCzUA7pUDy/JNMytTQDJctpUHg== dependencies: "@types/trusted-types" "*" @@ -1243,19 +1243,6 @@ buble@^0.19.3: os-homedir "^1.0.1" vlq "^1.0.0" -buffer-alloc-unsafe@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz#bd7dc26ae2972d0eda253be061dba992349c19f0" - integrity sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg== - -buffer-alloc@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/buffer-alloc/-/buffer-alloc-1.2.0.tgz#890dd90d923a873e08e10e5fd51a57e5b7cce0ec" - integrity sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow== - dependencies: - buffer-alloc-unsafe "^1.1.0" - buffer-fill "^1.0.0" - buffer-crc32@0.2.13, buffer-crc32@^0.2.1: version "0.2.13" resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" @@ -1265,20 +1252,10 @@ buffer-equal@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/buffer-equal/-/buffer-equal-0.0.1.tgz#91bc74b11ea405bc916bc6aa908faafa5b4aac4b" -buffer-fill@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/buffer-fill/-/buffer-fill-1.0.0.tgz#f8f78b76789888ef39f205cd637f68e702122b2c" - integrity sha1-+PeLdniYiO858gXNY39o5wISKyw= - buffer-from@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.0.0.tgz#4cb8832d23612589b0406e9e2956c17f06fdf531" -buffer-from@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" - integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== - buffer-indexof@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/buffer-indexof/-/buffer-indexof-1.1.1.tgz#52fabcc6a606d1a00302802648ef68f639da268c" @@ -1295,14 +1272,6 @@ buffer@^4.3.0: ieee754 "^1.1.4" isarray "^1.0.0" -buffer@^5.4.3: - version "5.4.3" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.4.3.tgz#3fbc9c69eb713d323e3fc1a895eee0710c072115" - integrity sha512-zvj65TkFeIt3i6aj5bIvJDzjjQQGs4o/sNoezg1F1kYap9Nu2jcUdpwzRSJTHMMzG0H7bZkn4rNQpImhuxWX2A== - dependencies: - base64-js "^1.0.2" - ieee754 "^1.1.4" - buffers@~0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/buffers/-/buffers-0.1.1.tgz#b24579c3bed4d6d396aeee6d9a8ae7f5482ab7bb" @@ -2553,11 +2522,6 @@ diffie-hellman@^5.0.0: miller-rabin "^4.0.0" randombytes "^2.0.0" -dijkstrajs@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/dijkstrajs/-/dijkstrajs-1.0.1.tgz#d3cd81221e3ea40742cfcde556d4e99e98ddc71b" - integrity sha1-082BIh4+pAdCz83lVtTpnpjdxxs= - dir-glob@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-2.0.0.tgz#0b205d2b6aef98238ca286598a8204d29d0a0034" @@ -5018,11 +4982,6 @@ isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" -isarray@^2.0.1: - version "2.0.5" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723" - integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw== - isbinaryfile@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/isbinaryfile/-/isbinaryfile-4.0.2.tgz#bfc45642da645681c610cca831022e30af426488" @@ -7022,11 +6981,6 @@ pngjs@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-3.0.1.tgz#b15086ac1ac47298c8fd3f9cdf364fa9879c4db6" -pngjs@^3.3.0: - version "3.4.0" - resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-3.4.0.tgz#99ca7d725965fb655814eaf65f38f12bbdbf555f" - integrity sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w== - portfinder@^1.0.9: version "1.0.13" resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.13.tgz#bb32ecd87c27104ae6ee44b5a3ccbf0ebb1aede9" @@ -7525,19 +7479,6 @@ qr.js@0.0.0: resolved "https://registry.yarnpkg.com/qr.js/-/qr.js-0.0.0.tgz#cace86386f59a0db8050fa90d9b6b0e88a1e364f" integrity sha1-ys6GOG9ZoNuAUPqQ2baw6IoeNk8= -qrcode@^1.4.4: - version "1.4.4" - resolved "https://registry.yarnpkg.com/qrcode/-/qrcode-1.4.4.tgz#f0c43568a7e7510a55efc3b88d9602f71963ea83" - integrity sha512-oLzEC5+NKFou9P0bMj5+v6Z40evexeE29Z9cummZXZ9QXyMr3lphkURzxjXgPJC5azpxcshoDWV1xE46z+/c3Q== - dependencies: - buffer "^5.4.3" - buffer-alloc "^1.2.0" - buffer-from "^1.1.1" - dijkstrajs "^1.0.1" - isarray "^2.0.1" - pngjs "^3.3.0" - yargs "^13.2.4" - qs@5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/qs/-/qs-5.2.0.tgz#a9f31142af468cb72b25b30136ba2456834916be" @@ -10446,7 +10387,7 @@ yargs@^10.0.3: y18n "^3.2.1" yargs-parser "^8.0.0" -yargs@^13.2.4, yargs@^13.3.0: +yargs@^13.3.0: version "13.3.0" resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.0.tgz#4c657a55e07e5f2cf947f8a366567c04a0dedc83" integrity sha512-2eehun/8ALW8TLoIl7MVaRUrg+yCnenu8B4kBlRxj3GJGDKU1Og7sMXPNm1BYyM1DOJmTZ4YeN/Nwxv+8XJsUA== From 7b6e28eaaba58a1b494607cac997ca4fa3137f79 Mon Sep 17 00:00:00 2001 From: Vincent Date: Mon, 6 Jan 2020 16:08:22 +1100 Subject: [PATCH 04/12] Keyboard navigation wrt Modals --- _locales/en/messages.json | 12 ++++-- .../conversation/ConversationHeader.tsx | 2 +- ts/components/session/SessionModal.tsx | 3 -- ts/components/session/SessionSeedModal.tsx | 43 +++++++++++++------ ts/components/session/SessionSettings.tsx | 17 -------- 5 files changed, 40 insertions(+), 37 deletions(-) delete mode 100644 ts/components/session/SessionSettings.tsx diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 07d5362f0..cf624c633 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -2110,10 +2110,16 @@ "message": "Please enter your password", "description": "Request for user to enter password to show seed." }, - "seedSavePrompt": { + "seedSavePromptMain": { + "message": "Please save the seed below in a safe location.", + "description": + "Prompt on seed modal requesting user to save their seed. Line one" + }, + "seedSavePromptAlt": { "message": - "Please save the seed below in a safe location. They can be used to restore your account if you lose access or migrate to a new device.", - "description": "Prompt on seed modal requesting user to save their seed" + "They can be used to restore your account if you lose access or migrate to a new device.", + "description": + "Prompt on seed modal requesting user to save their seed. Line two" }, "QRCodeTitle": { "message": "Your Public Key QRCode", diff --git a/ts/components/conversation/ConversationHeader.tsx b/ts/components/conversation/ConversationHeader.tsx index 06e3433c0..8174ef6c8 100644 --- a/ts/components/conversation/ConversationHeader.tsx +++ b/ts/components/conversation/ConversationHeader.tsx @@ -336,7 +336,7 @@ export class ConversationHeader extends React.Component { {this.renderBackButton()}
- {this.renderOptions(triggerId)} + {this.renderOptions()} {this.renderTitle()} {isPrivateGroup ? this.renderMemberCount() : null}
diff --git a/ts/components/session/SessionModal.tsx b/ts/components/session/SessionModal.tsx index 3a14421e6..d6502e163 100644 --- a/ts/components/session/SessionModal.tsx +++ b/ts/components/session/SessionModal.tsx @@ -92,9 +92,6 @@ export class SessionModal extends React.PureComponent { public onKeyUp(event: any) { switch (event.key) { - case 'Enter': - this.props.onOk(); - break; case 'Esc': case 'Escape': this.close(); diff --git a/ts/components/session/SessionSeedModal.tsx b/ts/components/session/SessionSeedModal.tsx index 621434254..7ba3350a9 100644 --- a/ts/components/session/SessionSeedModal.tsx +++ b/ts/components/session/SessionSeedModal.tsx @@ -35,6 +35,9 @@ export class SessionSeedModal extends React.Component { this.getSeed = this.getSeed.bind(this); this.confirmPassword = this.confirmPassword.bind(this); this.checkHasPassword = this.checkHasPassword.bind(this); + this.onEnter = this.onEnter.bind(this); + + window.addEventListener('keyup', this.onEnter); } public render() { @@ -44,7 +47,7 @@ export class SessionSeedModal extends React.Component { const maxPasswordLen = 64; this.checkHasPassword(); - this.getSeed(); + void this.getSeed(); const error = this.state.error; const hasPassword = this.state.hasPassword; @@ -53,13 +56,13 @@ export class SessionSeedModal extends React.Component { const loading = this.state.loadingPassword || this.state.loadingSeed; return ( - null} - onClose={onClose} - > + <> {!loading && ( - <> + null} + onClose={onClose} + >
{hasPassword && !passwordValid ? ( @@ -94,9 +97,13 @@ export class SessionSeedModal extends React.Component { <>

- {i18n('seedSavePrompt')} + {i18n('seedSavePromptMain')} +
+ + {i18n('seedSavePromptAlt')} +

-
+
{this.state.seed} @@ -116,9 +123,9 @@ export class SessionSeedModal extends React.Component {
)} - + )} - + ); } @@ -151,6 +158,8 @@ export class SessionSeedModal extends React.Component { error: '', }); + window.removeEventListener('keyup', this.onEnter); + return true; } @@ -172,7 +181,7 @@ export class SessionSeedModal extends React.Component { private async getSeed() { if (this.state.seed) { - return this.state.seed; + return false; } const manager = await window.getAccountManager(); @@ -183,7 +192,7 @@ export class SessionSeedModal extends React.Component { loadingSeed: false, }); - return seed; + return true; } private copySeed(seed: string) { @@ -195,4 +204,12 @@ export class SessionSeedModal extends React.Component { id: 'copySeedToast', }); } + + private onEnter(event: any) { + if (event.key === 'Enter') { + if ($('#seed-input-password').is(':focus')) { + this.confirmPassword(); + } + } + } } diff --git a/ts/components/session/SessionSettings.tsx b/ts/components/session/SessionSettings.tsx deleted file mode 100644 index eacd69b4c..000000000 --- a/ts/components/session/SessionSettings.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import React from 'react'; - -interface Props { - i18n: any; -} - -export class SessionSettings extends React.Component { - constructor(props: any) { - super(props); - } - - public render() { - const i18n = this.props.i18n; - - return
; - } -} From 2d01275edea3b8d0cdc5e2fe4096a4cd7c2bb669 Mon Sep 17 00:00:00 2001 From: Vincent Date: Tue, 7 Jan 2020 10:19:44 +1100 Subject: [PATCH 05/12] QR Code responds to theme changes --- _locales/en/messages.json | 8 +- js/modules/signal.js | 11 ++ js/views/app_view.js | 4 +- js/views/password_dialog_view.js | 227 ++-------------------- js/views/password_dialog_view_old.js | 228 +++++++++++++++++++++++ stylesheets/_session.scss | 9 +- stylesheets/_session_theme_dark.scss | 1 + ts/components/AddServerDialog.tsx | 23 +-- ts/components/DevicePairingDialog.tsx | 12 +- ts/components/session/SessionQRModal.tsx | 13 +- ts/global.d.ts | 1 + 11 files changed, 299 insertions(+), 238 deletions(-) create mode 100644 js/views/password_dialog_view_old.js diff --git a/_locales/en/messages.json b/_locales/en/messages.json index cf624c633..17405e403 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -2021,7 +2021,7 @@ }, "copyPublicKey": { - "message": "Copy public key", + "message": "Copy Public Key", "description": "Button action that the user can click to copy their public keys" }, @@ -2086,7 +2086,7 @@ "A toast message telling the user that the message text was copied" }, "editProfile": { - "message": "Edit profile", + "message": "Edit Profile", "description": "Button action that the user can click to edit their profile" }, @@ -2131,11 +2131,11 @@ "description": "Description given to QRCode modal" }, "showQRCode": { - "message": "Show QR code", + "message": "Show QR Code", "description": "Button action that the user can click to view their QR code" }, "showAddServer": { - "message": "Add public server", + "message": "Add Public Server", "description": "Button action that the user can click to connect to a new public server" }, diff --git a/js/modules/signal.js b/js/modules/signal.js index c34d37c8c..1f63deb40 100644 --- a/js/modules/signal.js +++ b/js/modules/signal.js @@ -67,6 +67,15 @@ const { const { SessionSeedModal, } = require('../../ts/components/session/SessionSeedModal'); + +const { + SessionPasswordChangeModal, +} = require('../../ts/components/session/SessionPasswordChangeModal'); + +const { + SessionPasswordRemoveModal, +} = require('../../ts/components/session/SessionPasswordRemoveModal'); + const { SessionConfirm, } = require('../../ts/components/session/SessionConfirm'); @@ -281,6 +290,8 @@ exports.setup = (options = {}) => { SessionModal, SessionQRModal, SessionSeedModal, + SessionPasswordChangeModal, + SessionPasswordRemoveModal, SessionDropdown, MediaGallery, Message, diff --git a/js/views/app_view.js b/js/views/app_view.js index f0e16347b..2b2fb0fb6 100644 --- a/js/views/app_view.js +++ b/js/views/app_view.js @@ -196,8 +196,8 @@ this.el.append(dialog.el); dialog.focusInput(); }, - showPasswordDialog({ type, resolve, reject }) { - const dialog = Whisper.getPasswordDialogView(type, resolve, reject); + showPasswordDialog() { + const dialog = Whisper.PasswordDialogView(); this.el.append(dialog.el); }, showSeedDialog(seed) { diff --git a/js/views/password_dialog_view.js b/js/views/password_dialog_view.js index 57d2afa00..f35a85360 100644 --- a/js/views/password_dialog_view.js +++ b/js/views/password_dialog_view.js @@ -1,4 +1,4 @@ -/* global Whisper, i18n, _, Signal, passwordUtil */ +/* global Whisper */ // eslint-disable-next-line func-names (function() { @@ -6,223 +6,28 @@ window.Whisper = window.Whisper || {}; - const PasswordDialogView = Whisper.View.extend({ + Whisper.PasswordDialogView = Whisper.View.extend({ className: 'loki-dialog password-dialog modal', - templateName: 'password-dialog', - initialize(options) { - this.type = options.type; - this.resolve = options.resolve; - this.okText = options.okText || i18n('ok'); - - this.reject = options.reject; - this.cancelText = options.cancelText || i18n('cancel'); - - this.title = options.title; - + initialize() { + this.close = this.close.bind(this); this.render(); - this.updateUI(); - }, - events: { - keyup: 'onKeyup', - 'click .ok': 'ok', - 'click .cancel': 'cancel', }, - render_attributes() { - return { - showCancel: !this.hideCancel, - cancel: this.cancelText, - ok: this.okText, - title: this.title, - }; - }, - async updateUI() { - if (this.disableOkButton()) { - this.$('.ok').prop('disabled', true); - } else { - this.$('.ok').prop('disabled', false); - } - }, - disableOkButton() { - const password = this.$('#password').val(); - return _.isEmpty(password); - }, - async validate() { - const password = this.$('#password').val(); - const passwordConfirmation = this.$('#password-confirmation').val(); - - const pairValidation = this.validatePasswordPair( - password, - passwordConfirmation - ); - const hashValidation = await this.validatePasswordHash(password); - return pairValidation || hashValidation; - }, - async validatePasswordHash(password) { - // Check if the password matches the hash we have stored - const hash = await Signal.Data.getPasswordHash(); - if (hash && !passwordUtil.matchesHash(password, hash)) { - return i18n('invalidPassword'); - } - return null; - }, - validatePasswordPair(password, passwordConfirmation) { - if (!_.isEmpty(password)) { - // Check if the password is first valid - const passwordValidation = passwordUtil.validatePassword( - password, - i18n - ); - if (passwordValidation) { - return passwordValidation; - } + render() { + this.dialogView = new Whisper.ReactWrapperView({ + className: 'password-dialog-wrapper', + Component: window.Signal.Components.SessionPasswordChangeModal, + props: { + onClose: this.close, + }, + }); - // Check if the confirmation password is the same - if ( - !passwordConfirmation || - password.trim() !== passwordConfirmation.trim() - ) { - return i18n('passwordsDoNotMatch'); - } - } - return null; - }, - okPressed() { - const password = this.$('#password').val(); - if (this.type === 'set') { - window.setPassword(password.trim()); - } else if (this.type === 'remove') { - window.setPassword(null, password.trim()); - } + this.$el.append(this.dialogView.el); + return this; }, - okErrored() { - if (this.type === 'set') { - this.showError(i18n('setPasswordFail')); - } else if (this.type === 'remove') { - this.showError(i18n('removePasswordFail')); - } - }, - async ok() { - const error = await this.validate(); - if (error) { - this.showError(error); - return; - } - - // Clear any errors - this.showError(null); - try { - this.okPressed(); - - this.remove(); - if (this.resolve) { - this.resolve(); - } - } catch (e) { - this.okErrored(); - } - }, - cancel() { + close() { this.remove(); - if (this.reject) { - this.reject(); - } - }, - onKeyup(event) { - this.updateUI(); - switch (event.key) { - case 'Enter': - this.ok(); - break; - case 'Escape': - case 'Esc': - this.cancel(); - break; - default: - return; - } - event.preventDefault(); - }, - focusCancel() { - this.$('.cancel').focus(); - }, - showError(message) { - if (_.isEmpty(message)) { - this.$('.error').text(''); - this.$('.error').hide(); - } else { - this.$('.error').text(`Error: ${message}`); - this.$('.error').show(); - } - }, - }); - - const ChangePasswordDialogView = PasswordDialogView.extend({ - templateName: 'password-change-dialog', - disableOkButton() { - const oldPassword = this.$('#old-password').val(); - const newPassword = this.$('#new-password').val(); - return _.isEmpty(oldPassword) || _.isEmpty(newPassword); - }, - async validate() { - const oldPassword = this.$('#old-password').val(); - - // Validate the old password - if (!_.isEmpty(oldPassword)) { - const oldPasswordValidation = passwordUtil.validatePassword( - oldPassword, - i18n - ); - if (oldPasswordValidation) { - return oldPasswordValidation; - } - } else { - return i18n('typeInOldPassword'); - } - - const password = this.$('#new-password').val(); - const passwordConfirmation = this.$('#new-password-confirmation').val(); - - const pairValidation = this.validatePasswordPair( - password, - passwordConfirmation - ); - const hashValidation = await this.validatePasswordHash(oldPassword); - - return pairValidation || hashValidation; - }, - okPressed() { - const oldPassword = this.$('#old-password').val(); - const newPassword = this.$('#new-password').val(); - window.setPassword(newPassword.trim(), oldPassword.trim()); - }, - okErrored() { - this.showError(i18n('changePasswordFail')); }, }); - - Whisper.getPasswordDialogView = (type, resolve, reject) => { - // This is a differently styled dialog - if (type === 'change') { - return new ChangePasswordDialogView({ - title: i18n('changePassword'), - okTitle: i18n('change'), - resolve, - reject, - }); - } - - // Set and Remove is basically the same UI - const title = - type === 'remove' ? i18n('removePassword') : i18n('setPassword'); - const okTitle = type === 'remove' ? i18n('remove') : i18n('set'); - return new PasswordDialogView({ - title, - okTitle, - type, - resolve, - reject, - }); - }; -})(); +})(); \ No newline at end of file diff --git a/js/views/password_dialog_view_old.js b/js/views/password_dialog_view_old.js new file mode 100644 index 000000000..57d2afa00 --- /dev/null +++ b/js/views/password_dialog_view_old.js @@ -0,0 +1,228 @@ +/* global Whisper, i18n, _, Signal, passwordUtil */ + +// eslint-disable-next-line func-names +(function() { + 'use strict'; + + window.Whisper = window.Whisper || {}; + + const PasswordDialogView = Whisper.View.extend({ + className: 'loki-dialog password-dialog modal', + templateName: 'password-dialog', + initialize(options) { + this.type = options.type; + this.resolve = options.resolve; + this.okText = options.okText || i18n('ok'); + + this.reject = options.reject; + this.cancelText = options.cancelText || i18n('cancel'); + + this.title = options.title; + + this.render(); + this.updateUI(); + }, + events: { + keyup: 'onKeyup', + 'click .ok': 'ok', + 'click .cancel': 'cancel', + }, + render_attributes() { + return { + showCancel: !this.hideCancel, + cancel: this.cancelText, + ok: this.okText, + title: this.title, + }; + }, + async updateUI() { + if (this.disableOkButton()) { + this.$('.ok').prop('disabled', true); + } else { + this.$('.ok').prop('disabled', false); + } + }, + disableOkButton() { + const password = this.$('#password').val(); + return _.isEmpty(password); + }, + async validate() { + const password = this.$('#password').val(); + const passwordConfirmation = this.$('#password-confirmation').val(); + + const pairValidation = this.validatePasswordPair( + password, + passwordConfirmation + ); + const hashValidation = await this.validatePasswordHash(password); + + return pairValidation || hashValidation; + }, + async validatePasswordHash(password) { + // Check if the password matches the hash we have stored + const hash = await Signal.Data.getPasswordHash(); + if (hash && !passwordUtil.matchesHash(password, hash)) { + return i18n('invalidPassword'); + } + return null; + }, + validatePasswordPair(password, passwordConfirmation) { + if (!_.isEmpty(password)) { + // Check if the password is first valid + const passwordValidation = passwordUtil.validatePassword( + password, + i18n + ); + if (passwordValidation) { + return passwordValidation; + } + + // Check if the confirmation password is the same + if ( + !passwordConfirmation || + password.trim() !== passwordConfirmation.trim() + ) { + return i18n('passwordsDoNotMatch'); + } + } + return null; + }, + okPressed() { + const password = this.$('#password').val(); + if (this.type === 'set') { + window.setPassword(password.trim()); + } else if (this.type === 'remove') { + window.setPassword(null, password.trim()); + } + }, + okErrored() { + if (this.type === 'set') { + this.showError(i18n('setPasswordFail')); + } else if (this.type === 'remove') { + this.showError(i18n('removePasswordFail')); + } + }, + async ok() { + const error = await this.validate(); + if (error) { + this.showError(error); + return; + } + + // Clear any errors + this.showError(null); + + try { + this.okPressed(); + + this.remove(); + if (this.resolve) { + this.resolve(); + } + } catch (e) { + this.okErrored(); + } + }, + cancel() { + this.remove(); + if (this.reject) { + this.reject(); + } + }, + onKeyup(event) { + this.updateUI(); + switch (event.key) { + case 'Enter': + this.ok(); + break; + case 'Escape': + case 'Esc': + this.cancel(); + break; + default: + return; + } + event.preventDefault(); + }, + focusCancel() { + this.$('.cancel').focus(); + }, + showError(message) { + if (_.isEmpty(message)) { + this.$('.error').text(''); + this.$('.error').hide(); + } else { + this.$('.error').text(`Error: ${message}`); + this.$('.error').show(); + } + }, + }); + + const ChangePasswordDialogView = PasswordDialogView.extend({ + templateName: 'password-change-dialog', + disableOkButton() { + const oldPassword = this.$('#old-password').val(); + const newPassword = this.$('#new-password').val(); + return _.isEmpty(oldPassword) || _.isEmpty(newPassword); + }, + async validate() { + const oldPassword = this.$('#old-password').val(); + + // Validate the old password + if (!_.isEmpty(oldPassword)) { + const oldPasswordValidation = passwordUtil.validatePassword( + oldPassword, + i18n + ); + if (oldPasswordValidation) { + return oldPasswordValidation; + } + } else { + return i18n('typeInOldPassword'); + } + + const password = this.$('#new-password').val(); + const passwordConfirmation = this.$('#new-password-confirmation').val(); + + const pairValidation = this.validatePasswordPair( + password, + passwordConfirmation + ); + const hashValidation = await this.validatePasswordHash(oldPassword); + + return pairValidation || hashValidation; + }, + okPressed() { + const oldPassword = this.$('#old-password').val(); + const newPassword = this.$('#new-password').val(); + window.setPassword(newPassword.trim(), oldPassword.trim()); + }, + okErrored() { + this.showError(i18n('changePasswordFail')); + }, + }); + + Whisper.getPasswordDialogView = (type, resolve, reject) => { + // This is a differently styled dialog + if (type === 'change') { + return new ChangePasswordDialogView({ + title: i18n('changePassword'), + okTitle: i18n('change'), + resolve, + reject, + }); + } + + // Set and Remove is basically the same UI + const title = + type === 'remove' ? i18n('removePassword') : i18n('setPassword'); + const okTitle = type === 'remove' ? i18n('remove') : i18n('set'); + return new PasswordDialogView({ + title, + okTitle, + type, + resolve, + reject, + }); + }; +})(); diff --git a/stylesheets/_session.scss b/stylesheets/_session.scss index b01fbadeb..c96e0f726 100644 --- a/stylesheets/_session.scss +++ b/stylesheets/_session.scss @@ -106,7 +106,7 @@ div.spacer-lg { } @mixin text-highlight($color) { - background-color: rgba($color, 0.8); + background-color: rgba($color, 0.4); padding: $session-margin-xs; border-radius: 3px; display: inline-block; @@ -474,6 +474,12 @@ label { } } +.user-details-dialog { + .message { + word-break: break-all; + } +} + #session-toast-container { position: fixed; right: $session-margin-lg; @@ -819,7 +825,6 @@ label { #qr svg { width: $session-modal-size-sm; height: $session-modal-size-sm; - border: 6px solid white; border-radius: 3px; } diff --git a/stylesheets/_session_theme_dark.scss b/stylesheets/_session_theme_dark.scss index 291853042..ec0787eb5 100644 --- a/stylesheets/_session_theme_dark.scss +++ b/stylesheets/_session_theme_dark.scss @@ -54,3 +54,4 @@ } } } + diff --git a/ts/components/AddServerDialog.tsx b/ts/components/AddServerDialog.tsx index 57fa9c19e..c3087b592 100644 --- a/ts/components/AddServerDialog.tsx +++ b/ts/components/AddServerDialog.tsx @@ -36,7 +36,9 @@ export class AddServerDialog extends React.Component { this.attemptConnection = this.attemptConnection.bind(this); this.closeDialog = this.closeDialog.bind(this); - this.onKeyUp = this.onKeyUp.bind(this); + this.onEnter = this.onEnter.bind(this); + + window.addEventListener('keyup', this.onEnter); } public render() { @@ -191,23 +193,16 @@ export class AddServerDialog extends React.Component { ); } - private onKeyUp(event: any) { - switch (event.key) { - case 'Enter': - if (this.state.view === 'default') { - this.showView('connecting'); - } - break; - case 'Esc': - case 'Escape': - this.closeDialog(); - break; - default: + private onEnter(event: any) { + if (event.key === 'Enter') { + if ($('#server-url').is(':focus')) { + this.showView('connecting'); + } } } private closeDialog() { - window.removeEventListener('keyup', this.onKeyUp); + window.removeEventListener('keyup', this.onEnter); this.props.onClose(); } diff --git a/ts/components/DevicePairingDialog.tsx b/ts/components/DevicePairingDialog.tsx index 216ee0b30..3615ec696 100644 --- a/ts/components/DevicePairingDialog.tsx +++ b/ts/components/DevicePairingDialog.tsx @@ -59,6 +59,14 @@ export class DevicePairingDialog extends React.Component { const waitingForRequest = this.state.view === 'waitingForRequest'; const nothingPaired = this.state.data.length === 0; + const theme = window.Events.getThemeSetting(); + + // Foreground equivalent to .session-modal background color + const bgColor = 'rgba(0, 0, 0, 0)'; + const fgColor = theme === 'dark' + ? '#FFFFFF' + : '#1B1B1B'; + // const renderPairedDevices = this.state.data.map((pubKey: any) => { // const pubKeyInfo = this.getPubkeyName(pubKey); // const isFinalItem = @@ -96,8 +104,8 @@ export class DevicePairingDialog extends React.Component {
diff --git a/ts/components/session/SessionQRModal.tsx b/ts/components/session/SessionQRModal.tsx index 0f4e2ba70..9820377eb 100644 --- a/ts/components/session/SessionQRModal.tsx +++ b/ts/components/session/SessionQRModal.tsx @@ -13,10 +13,17 @@ export class SessionQRModal extends React.Component { constructor(props: any) { super(props); } - public render() { const { value, onClose } = this.props; - + + const theme = window.Events.getThemeSetting(); + + // Foreground equivalent to .session-modal background color + const bgColor = 'rgba(0, 0, 0, 0)'; + const fgColor = theme === 'dark' + ? '#FFFFFF' + : '#1B1B1B'; + return ( {
- +
diff --git a/ts/global.d.ts b/ts/global.d.ts index f5a1845cc..4f30c6a15 100644 --- a/ts/global.d.ts +++ b/ts/global.d.ts @@ -1,4 +1,5 @@ interface Window { + Events: any; getAccountManager: any; mnemonic: any; clipboard: any; From dff4523efa4b5a7213e643237699dad117d2d9df Mon Sep 17 00:00:00 2001 From: Vincent Date: Tue, 7 Jan 2020 10:29:47 +1100 Subject: [PATCH 06/12] Cleanups --- _locales/en/messages.json | 6 +++--- js/views/password_dialog_view.js | 2 +- stylesheets/_session_theme_dark.scss | 1 - ts/components/DevicePairingDialog.tsx | 4 +--- ts/components/session/SessionQRModal.tsx | 13 +++++++------ 5 files changed, 12 insertions(+), 14 deletions(-) diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 17405e403..8ec93809f 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -2122,12 +2122,12 @@ "Prompt on seed modal requesting user to save their seed. Line two" }, "QRCodeTitle": { - "message": "Your Public Key QRCode", - "description": "Title given to QRCode modal" + "message": "View My QR Code", + "description": "Title given to QR Code modal" }, "QRCodeDescription": { "message": - "Your friends can scan this QR code to start a conversation with you.", + "This is your unique public QR Code.
Other users may scan this in order to begin a conversation with you.", "description": "Description given to QRCode modal" }, "showQRCode": { diff --git a/js/views/password_dialog_view.js b/js/views/password_dialog_view.js index f35a85360..d3221b566 100644 --- a/js/views/password_dialog_view.js +++ b/js/views/password_dialog_view.js @@ -30,4 +30,4 @@ this.remove(); }, }); -})(); \ No newline at end of file +})(); diff --git a/stylesheets/_session_theme_dark.scss b/stylesheets/_session_theme_dark.scss index ec0787eb5..291853042 100644 --- a/stylesheets/_session_theme_dark.scss +++ b/stylesheets/_session_theme_dark.scss @@ -54,4 +54,3 @@ } } } - diff --git a/ts/components/DevicePairingDialog.tsx b/ts/components/DevicePairingDialog.tsx index 3615ec696..1a30e4153 100644 --- a/ts/components/DevicePairingDialog.tsx +++ b/ts/components/DevicePairingDialog.tsx @@ -63,9 +63,7 @@ export class DevicePairingDialog extends React.Component { // Foreground equivalent to .session-modal background color const bgColor = 'rgba(0, 0, 0, 0)'; - const fgColor = theme === 'dark' - ? '#FFFFFF' - : '#1B1B1B'; + const fgColor = theme === 'dark' ? '#FFFFFF' : '#1B1B1B'; // const renderPairedDevices = this.state.data.map((pubKey: any) => { // const pubKeyInfo = this.getPubkeyName(pubKey); diff --git a/ts/components/session/SessionQRModal.tsx b/ts/components/session/SessionQRModal.tsx index 9820377eb..cd8ee506e 100644 --- a/ts/components/session/SessionQRModal.tsx +++ b/ts/components/session/SessionQRModal.tsx @@ -3,6 +3,7 @@ import { QRCode } from 'react-qr-svg'; import { SessionModal } from './SessionModal'; import { SessionButton } from './SessionButton'; +import { SessionHtmlRenderer } from './SessionHTMLRenderer'; interface Props { value: string; @@ -15,15 +16,13 @@ export class SessionQRModal extends React.Component { } public render() { const { value, onClose } = this.props; - + const theme = window.Events.getThemeSetting(); // Foreground equivalent to .session-modal background color const bgColor = 'rgba(0, 0, 0, 0)'; - const fgColor = theme === 'dark' - ? '#FFFFFF' - : '#1B1B1B'; - + const fgColor = theme === 'dark' ? '#FFFFFF' : '#1B1B1B'; + return ( {
-

{window.i18n('QRCodeDescription')}

+

+ +

From a2f08c6e1407f4de6cf6656eef8b03d7dc4b008e Mon Sep 17 00:00:00 2001 From: Vincent Date: Tue, 7 Jan 2020 10:58:46 +1100 Subject: [PATCH 07/12] Stylistic icing --- js/views/qr_dialog_view_old.js | 47 -------- ts/components/session/SessionSeedModal.tsx | 133 +++++++++++---------- 2 files changed, 72 insertions(+), 108 deletions(-) delete mode 100644 js/views/qr_dialog_view_old.js diff --git a/js/views/qr_dialog_view_old.js b/js/views/qr_dialog_view_old.js deleted file mode 100644 index c365166c7..000000000 --- a/js/views/qr_dialog_view_old.js +++ /dev/null @@ -1,47 +0,0 @@ -/* global Whisper, i18n, QRCode */ - -// eslint-disable-next-line func-names -(function() { - 'use strict'; - - window.Whisper = window.Whisper || {}; - - Whisper.QRDialogView = Whisper.View.extend({ - templateName: 'qr-code-template', - className: 'loki-dialog qr-dialog modal', - initialize(options = {}) { - this.okText = options.okText || i18n('ok'); - this.render(); - this.$('.qr-dialog').bind('keyup', event => this.onKeyup(event)); - - if (options.string) { - this.qr = new QRCode(this.$('#qr')[0], { - correctLevel: QRCode.CorrectLevel.L, - }).makeCode(options.string); - this.$('#qr').addClass('ready'); - } - }, - events: { - 'click .ok': 'close', - }, - render_attributes() { - return { - ok: this.okText, - }; - }, - close() { - this.remove(); - }, - onKeyup(event) { - switch (event.key) { - case 'Enter': - case 'Escape': - case 'Esc': - this.close(); - break; - default: - break; - } - }, - }); -})(); diff --git a/ts/components/session/SessionSeedModal.tsx b/ts/components/session/SessionSeedModal.tsx index 7ba3350a9..05b4592f6 100644 --- a/ts/components/session/SessionSeedModal.tsx +++ b/ts/components/session/SessionSeedModal.tsx @@ -40,19 +40,83 @@ export class SessionSeedModal extends React.Component { window.addEventListener('keyup', this.onEnter); } - public render() { + private renderPasswordView() { + const maxPasswordLen = 64; + const error = this.state.error; const i18n = window.i18n; const { onClose } = this.props; - const maxPasswordLen = 64; + return ( + <> +

{i18n('showSeedPasswordRequest')}

+ + + {error && ( + <> +
+
{error}
+ + )} + +
+ +
+ + + +
+ + ); + } + + private renderSeedView() { + const i18n = window.i18n; + const { onClose } = this.props; + + return ( + <> +
+

+ {i18n('seedSavePromptMain')} +
+ {i18n('seedSavePromptAlt')} +

+
+ + {this.state.seed} +
+
+ +
+ + + { + this.copySeed(this.state.seed); + }} + /> +
+ + ); + } + + public render() { + const i18n = window.i18n; this.checkHasPassword(); void this.getSeed(); - const error = this.state.error; - const hasPassword = this.state.hasPassword; - const passwordValid = this.state.passwordValid; - + const { onClose } = this.props; + const { hasPassword, passwordValid } = this.state; const loading = this.state.loadingPassword || this.state.loadingSeed; return ( @@ -66,62 +130,9 @@ export class SessionSeedModal extends React.Component {
{hasPassword && !passwordValid ? ( -
-

{i18n('showSeedPasswordRequest')}

- - - {error && ( - <> -
-
{error}
- - )} - -
- -
- - - -
-
+ <>{this.renderPasswordView()} ) : ( - <> -
-

- {i18n('seedSavePromptMain')} -
- - {i18n('seedSavePromptAlt')} - -

-
- - - {this.state.seed} - -
-
- -
- - - { - this.copySeed(this.state.seed); - }} - /> -
- + <>{this.renderSeedView()} )} )} From 3df5ff20606bfa3ca75eae889903a462dd6d7c47 Mon Sep 17 00:00:00 2001 From: Vince <58160433+vincentbavitz@users.noreply.github.com> Date: Tue, 7 Jan 2020 11:06:28 +1100 Subject: [PATCH 08/12] Delete password_dialog_view_old.js --- js/views/password_dialog_view_old.js | 228 --------------------------- 1 file changed, 228 deletions(-) delete mode 100644 js/views/password_dialog_view_old.js diff --git a/js/views/password_dialog_view_old.js b/js/views/password_dialog_view_old.js deleted file mode 100644 index 57d2afa00..000000000 --- a/js/views/password_dialog_view_old.js +++ /dev/null @@ -1,228 +0,0 @@ -/* global Whisper, i18n, _, Signal, passwordUtil */ - -// eslint-disable-next-line func-names -(function() { - 'use strict'; - - window.Whisper = window.Whisper || {}; - - const PasswordDialogView = Whisper.View.extend({ - className: 'loki-dialog password-dialog modal', - templateName: 'password-dialog', - initialize(options) { - this.type = options.type; - this.resolve = options.resolve; - this.okText = options.okText || i18n('ok'); - - this.reject = options.reject; - this.cancelText = options.cancelText || i18n('cancel'); - - this.title = options.title; - - this.render(); - this.updateUI(); - }, - events: { - keyup: 'onKeyup', - 'click .ok': 'ok', - 'click .cancel': 'cancel', - }, - render_attributes() { - return { - showCancel: !this.hideCancel, - cancel: this.cancelText, - ok: this.okText, - title: this.title, - }; - }, - async updateUI() { - if (this.disableOkButton()) { - this.$('.ok').prop('disabled', true); - } else { - this.$('.ok').prop('disabled', false); - } - }, - disableOkButton() { - const password = this.$('#password').val(); - return _.isEmpty(password); - }, - async validate() { - const password = this.$('#password').val(); - const passwordConfirmation = this.$('#password-confirmation').val(); - - const pairValidation = this.validatePasswordPair( - password, - passwordConfirmation - ); - const hashValidation = await this.validatePasswordHash(password); - - return pairValidation || hashValidation; - }, - async validatePasswordHash(password) { - // Check if the password matches the hash we have stored - const hash = await Signal.Data.getPasswordHash(); - if (hash && !passwordUtil.matchesHash(password, hash)) { - return i18n('invalidPassword'); - } - return null; - }, - validatePasswordPair(password, passwordConfirmation) { - if (!_.isEmpty(password)) { - // Check if the password is first valid - const passwordValidation = passwordUtil.validatePassword( - password, - i18n - ); - if (passwordValidation) { - return passwordValidation; - } - - // Check if the confirmation password is the same - if ( - !passwordConfirmation || - password.trim() !== passwordConfirmation.trim() - ) { - return i18n('passwordsDoNotMatch'); - } - } - return null; - }, - okPressed() { - const password = this.$('#password').val(); - if (this.type === 'set') { - window.setPassword(password.trim()); - } else if (this.type === 'remove') { - window.setPassword(null, password.trim()); - } - }, - okErrored() { - if (this.type === 'set') { - this.showError(i18n('setPasswordFail')); - } else if (this.type === 'remove') { - this.showError(i18n('removePasswordFail')); - } - }, - async ok() { - const error = await this.validate(); - if (error) { - this.showError(error); - return; - } - - // Clear any errors - this.showError(null); - - try { - this.okPressed(); - - this.remove(); - if (this.resolve) { - this.resolve(); - } - } catch (e) { - this.okErrored(); - } - }, - cancel() { - this.remove(); - if (this.reject) { - this.reject(); - } - }, - onKeyup(event) { - this.updateUI(); - switch (event.key) { - case 'Enter': - this.ok(); - break; - case 'Escape': - case 'Esc': - this.cancel(); - break; - default: - return; - } - event.preventDefault(); - }, - focusCancel() { - this.$('.cancel').focus(); - }, - showError(message) { - if (_.isEmpty(message)) { - this.$('.error').text(''); - this.$('.error').hide(); - } else { - this.$('.error').text(`Error: ${message}`); - this.$('.error').show(); - } - }, - }); - - const ChangePasswordDialogView = PasswordDialogView.extend({ - templateName: 'password-change-dialog', - disableOkButton() { - const oldPassword = this.$('#old-password').val(); - const newPassword = this.$('#new-password').val(); - return _.isEmpty(oldPassword) || _.isEmpty(newPassword); - }, - async validate() { - const oldPassword = this.$('#old-password').val(); - - // Validate the old password - if (!_.isEmpty(oldPassword)) { - const oldPasswordValidation = passwordUtil.validatePassword( - oldPassword, - i18n - ); - if (oldPasswordValidation) { - return oldPasswordValidation; - } - } else { - return i18n('typeInOldPassword'); - } - - const password = this.$('#new-password').val(); - const passwordConfirmation = this.$('#new-password-confirmation').val(); - - const pairValidation = this.validatePasswordPair( - password, - passwordConfirmation - ); - const hashValidation = await this.validatePasswordHash(oldPassword); - - return pairValidation || hashValidation; - }, - okPressed() { - const oldPassword = this.$('#old-password').val(); - const newPassword = this.$('#new-password').val(); - window.setPassword(newPassword.trim(), oldPassword.trim()); - }, - okErrored() { - this.showError(i18n('changePasswordFail')); - }, - }); - - Whisper.getPasswordDialogView = (type, resolve, reject) => { - // This is a differently styled dialog - if (type === 'change') { - return new ChangePasswordDialogView({ - title: i18n('changePassword'), - okTitle: i18n('change'), - resolve, - reject, - }); - } - - // Set and Remove is basically the same UI - const title = - type === 'remove' ? i18n('removePassword') : i18n('setPassword'); - const okTitle = type === 'remove' ? i18n('remove') : i18n('set'); - return new PasswordDialogView({ - title, - okTitle, - type, - resolve, - reject, - }); - }; -})(); From a6d04c3b2e11438240e2bc46a80137116200a77f Mon Sep 17 00:00:00 2001 From: Vincent Date: Tue, 7 Jan 2020 12:00:58 +1100 Subject: [PATCH 09/12] Simplifications --- ts/components/session/SessionQRModal.tsx | 2 -- ts/components/session/SessionSeedModal.tsx | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/ts/components/session/SessionQRModal.tsx b/ts/components/session/SessionQRModal.tsx index cd8ee506e..e0f124b93 100644 --- a/ts/components/session/SessionQRModal.tsx +++ b/ts/components/session/SessionQRModal.tsx @@ -32,9 +32,7 @@ export class SessionQRModal extends React.Component {
-

-

diff --git a/ts/components/session/SessionSeedModal.tsx b/ts/components/session/SessionSeedModal.tsx index 05b4592f6..38208a0ce 100644 --- a/ts/components/session/SessionSeedModal.tsx +++ b/ts/components/session/SessionSeedModal.tsx @@ -113,7 +113,7 @@ export class SessionSeedModal extends React.Component { const i18n = window.i18n; this.checkHasPassword(); - void this.getSeed(); + this.getSeed().ignore(); const { onClose } = this.props; const { hasPassword, passwordValid } = this.state; From 2cf152b8825fe4ecdb1fb0ea71709ce23fefa1e4 Mon Sep 17 00:00:00 2001 From: Vince <58160433+vincentbavitz@users.noreply.github.com> Date: Tue, 7 Jan 2020 12:44:26 +1100 Subject: [PATCH 10/12] Update signal.js Rmv old deps --- js/modules/signal.js | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/js/modules/signal.js b/js/modules/signal.js index d1917d615..b9656f62a 100644 --- a/js/modules/signal.js +++ b/js/modules/signal.js @@ -66,15 +66,6 @@ const { const { SessionSeedModal, } = require('../../ts/components/session/SessionSeedModal'); - -const { - SessionPasswordChangeModal, -} = require('../../ts/components/session/SessionPasswordChangeModal'); - -const { - SessionPasswordRemoveModal, -} = require('../../ts/components/session/SessionPasswordRemoveModal'); - const { SessionConfirm, } = require('../../ts/components/session/SessionConfirm'); @@ -288,8 +279,6 @@ exports.setup = (options = {}) => { SessionModal, SessionQRModal, SessionSeedModal, - SessionPasswordChangeModal, - SessionPasswordRemoveModal, SessionDropdown, MediaGallery, Message, From d8cd0cfe92bcfd6797446a9e75bf6c4aeb30aed5 Mon Sep 17 00:00:00 2001 From: Vince <58160433+vincentbavitz@users.noreply.github.com> Date: Tue, 7 Jan 2020 12:45:57 +1100 Subject: [PATCH 11/12] Update password_dialog_view.js Revert --- js/views/password_dialog_view.js | 225 ++++++++++++++++++++++++++++--- 1 file changed, 210 insertions(+), 15 deletions(-) diff --git a/js/views/password_dialog_view.js b/js/views/password_dialog_view.js index d3221b566..57d2afa00 100644 --- a/js/views/password_dialog_view.js +++ b/js/views/password_dialog_view.js @@ -1,4 +1,4 @@ -/* global Whisper */ +/* global Whisper, i18n, _, Signal, passwordUtil */ // eslint-disable-next-line func-names (function() { @@ -6,28 +6,223 @@ window.Whisper = window.Whisper || {}; - Whisper.PasswordDialogView = Whisper.View.extend({ + const PasswordDialogView = Whisper.View.extend({ className: 'loki-dialog password-dialog modal', - initialize() { - this.close = this.close.bind(this); + templateName: 'password-dialog', + initialize(options) { + this.type = options.type; + this.resolve = options.resolve; + this.okText = options.okText || i18n('ok'); + + this.reject = options.reject; + this.cancelText = options.cancelText || i18n('cancel'); + + this.title = options.title; + this.render(); + this.updateUI(); + }, + events: { + keyup: 'onKeyup', + 'click .ok': 'ok', + 'click .cancel': 'cancel', }, + render_attributes() { + return { + showCancel: !this.hideCancel, + cancel: this.cancelText, + ok: this.okText, + title: this.title, + }; + }, + async updateUI() { + if (this.disableOkButton()) { + this.$('.ok').prop('disabled', true); + } else { + this.$('.ok').prop('disabled', false); + } + }, + disableOkButton() { + const password = this.$('#password').val(); + return _.isEmpty(password); + }, + async validate() { + const password = this.$('#password').val(); + const passwordConfirmation = this.$('#password-confirmation').val(); - render() { - this.dialogView = new Whisper.ReactWrapperView({ - className: 'password-dialog-wrapper', - Component: window.Signal.Components.SessionPasswordChangeModal, - props: { - onClose: this.close, - }, - }); + const pairValidation = this.validatePasswordPair( + password, + passwordConfirmation + ); + const hashValidation = await this.validatePasswordHash(password); - this.$el.append(this.dialogView.el); - return this; + return pairValidation || hashValidation; }, + async validatePasswordHash(password) { + // Check if the password matches the hash we have stored + const hash = await Signal.Data.getPasswordHash(); + if (hash && !passwordUtil.matchesHash(password, hash)) { + return i18n('invalidPassword'); + } + return null; + }, + validatePasswordPair(password, passwordConfirmation) { + if (!_.isEmpty(password)) { + // Check if the password is first valid + const passwordValidation = passwordUtil.validatePassword( + password, + i18n + ); + if (passwordValidation) { + return passwordValidation; + } + + // Check if the confirmation password is the same + if ( + !passwordConfirmation || + password.trim() !== passwordConfirmation.trim() + ) { + return i18n('passwordsDoNotMatch'); + } + } + return null; + }, + okPressed() { + const password = this.$('#password').val(); + if (this.type === 'set') { + window.setPassword(password.trim()); + } else if (this.type === 'remove') { + window.setPassword(null, password.trim()); + } + }, + okErrored() { + if (this.type === 'set') { + this.showError(i18n('setPasswordFail')); + } else if (this.type === 'remove') { + this.showError(i18n('removePasswordFail')); + } + }, + async ok() { + const error = await this.validate(); + if (error) { + this.showError(error); + return; + } + + // Clear any errors + this.showError(null); - close() { + try { + this.okPressed(); + + this.remove(); + if (this.resolve) { + this.resolve(); + } + } catch (e) { + this.okErrored(); + } + }, + cancel() { this.remove(); + if (this.reject) { + this.reject(); + } + }, + onKeyup(event) { + this.updateUI(); + switch (event.key) { + case 'Enter': + this.ok(); + break; + case 'Escape': + case 'Esc': + this.cancel(); + break; + default: + return; + } + event.preventDefault(); + }, + focusCancel() { + this.$('.cancel').focus(); + }, + showError(message) { + if (_.isEmpty(message)) { + this.$('.error').text(''); + this.$('.error').hide(); + } else { + this.$('.error').text(`Error: ${message}`); + this.$('.error').show(); + } + }, + }); + + const ChangePasswordDialogView = PasswordDialogView.extend({ + templateName: 'password-change-dialog', + disableOkButton() { + const oldPassword = this.$('#old-password').val(); + const newPassword = this.$('#new-password').val(); + return _.isEmpty(oldPassword) || _.isEmpty(newPassword); + }, + async validate() { + const oldPassword = this.$('#old-password').val(); + + // Validate the old password + if (!_.isEmpty(oldPassword)) { + const oldPasswordValidation = passwordUtil.validatePassword( + oldPassword, + i18n + ); + if (oldPasswordValidation) { + return oldPasswordValidation; + } + } else { + return i18n('typeInOldPassword'); + } + + const password = this.$('#new-password').val(); + const passwordConfirmation = this.$('#new-password-confirmation').val(); + + const pairValidation = this.validatePasswordPair( + password, + passwordConfirmation + ); + const hashValidation = await this.validatePasswordHash(oldPassword); + + return pairValidation || hashValidation; + }, + okPressed() { + const oldPassword = this.$('#old-password').val(); + const newPassword = this.$('#new-password').val(); + window.setPassword(newPassword.trim(), oldPassword.trim()); + }, + okErrored() { + this.showError(i18n('changePasswordFail')); }, }); + + Whisper.getPasswordDialogView = (type, resolve, reject) => { + // This is a differently styled dialog + if (type === 'change') { + return new ChangePasswordDialogView({ + title: i18n('changePassword'), + okTitle: i18n('change'), + resolve, + reject, + }); + } + + // Set and Remove is basically the same UI + const title = + type === 'remove' ? i18n('removePassword') : i18n('setPassword'); + const okTitle = type === 'remove' ? i18n('remove') : i18n('set'); + return new PasswordDialogView({ + title, + okTitle, + type, + resolve, + reject, + }); + }; })();