From 2d01275edea3b8d0cdc5e2fe4096a4cd7c2bb669 Mon Sep 17 00:00:00 2001 From: Vincent Date: Tue, 7 Jan 2020 10:19:44 +1100 Subject: [PATCH] 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;