diff --git a/background.html b/background.html index a1e945eeb..22c713562 100644 --- a/background.html +++ b/background.html @@ -212,8 +212,6 @@ - - diff --git a/background_test.html b/background_test.html index 1580054fa..55402adf0 100644 --- a/background_test.html +++ b/background_test.html @@ -214,8 +214,6 @@ - - diff --git a/js/background.js b/js/background.js index 618e23905..dcf9ccff3 100644 --- a/js/background.js +++ b/js/background.js @@ -128,52 +128,6 @@ // of preload.js processing window.setImmediate = window.nodeSetImmediate; - window.toasts = new Map(); - window.pushToast = options => { - // Setting toasts with the same ID can be used to prevent identical - // toasts from appearing at once (stacking). - // If toast already exists, it will be reloaded (updated) - - const params = { - title: options.title, - id: options.id || window.generateID(), - description: options.description || '', - type: options.type || '', - icon: options.icon || '', - shouldFade: options.shouldFade, - }; - - // Give all toasts an ID. User may define. - let currentToast; - const toastID = params.id; - const toast = !!toastID && window.toasts.get(toastID); - if (toast) { - currentToast = window.toasts.get(toastID); - currentToast.update(params); - } else { - // Make new Toast - window.toasts.set( - toastID, - new Whisper.SessionToastView({ - el: $('body'), - }) - ); - - currentToast = window.toasts.get(toastID); - currentToast.render(); - currentToast.update(params); - } - - // Remove some toasts if too many exist - const maxToasts = 6; - while (window.toasts.size > maxToasts) { - const finalToastID = window.toasts.keys().next().value; - window.toasts.get(finalToastID).fadeToast(); - } - - return toastID; - }; - const { IdleDetector, MessageDataMigrator } = Signal.Workflow; const { mandatoryMessageUpgrade, @@ -813,12 +767,6 @@ window.setSettingValue('link-preview-setting', false); } - // Generates useful random ID for various purposes - window.generateID = () => - Math.random() - .toString(36) - .substring(3); - // Get memberlist. This function is not accurate >> // window.getMemberList = window.lokiPublicChatAPI.getListOfMembers(); window.setTheme = newTheme => { @@ -1113,26 +1061,9 @@ ourPubKey ); - const title = authorisations.length - ? window.i18n('devicePairingRequestReceivedLimitTitle') - : window.i18n('devicePairingRequestReceivedNoListenerTitle'); - - const description = authorisations.length - ? window.i18n( - 'devicePairingRequestReceivedLimitDescription', - window.CONSTANTS.MAX_LINKED_DEVICES - ) - : window.i18n('devicePairingRequestReceivedNoListenerDescription'); - - const type = authorisations.length ? 'info' : 'warning'; - - window.pushToast({ - title, - description, - type, - id: 'pairingRequestReceived', - shouldFade: false, - }); + window.libsession.Utils.ToastUtils.pushPairingRequestReceived( + authorisations.length + ); }); Whisper.events.on('devicePairingRequestAccepted', async (pubKey, cb) => { diff --git a/js/modules/signal.js b/js/modules/signal.js index fb78119f3..705bc015d 100644 --- a/js/modules/signal.js +++ b/js/modules/signal.js @@ -49,7 +49,6 @@ const { const { SettingsView, } = require('../../ts/components/session/settings/SessionSettings'); -const { SessionToast } = require('../../ts/components/session/SessionToast'); const { SessionModal } = require('../../ts/components/session/SessionModal'); const { SessionSeedModal, @@ -270,7 +269,6 @@ exports.setup = (options = {}) => { RemoveModeratorsDialog, GroupInvitation, SessionConversation, - SessionToast, SessionConfirm, SessionModal, SessionSeedModal, diff --git a/js/views/conversation_view.js b/js/views/conversation_view.js index 13ccccc35..da8f829c8 100644 --- a/js/views/conversation_view.js +++ b/js/views/conversation_view.js @@ -450,6 +450,7 @@ e.preventDefault(); if (this.fileInput.hasFiles()) { + // pushToast() this toast too const toast = new Whisper.VoiceNoteMustBeOnlyAttachmentToast(); toast.$el.appendTo(this.$el); toast.render(); diff --git a/js/views/invite_contacts_dialog_view.js b/js/views/invite_contacts_dialog_view.js index 0a0b42d63..e35306587 100644 --- a/js/views/invite_contacts_dialog_view.js +++ b/js/views/invite_contacts_dialog_view.js @@ -89,13 +89,7 @@ newMembers.length + existingMembers.length > window.CONSTANTS.MEDIUM_GROUP_SIZE_LIMIT ) { - const msg = window.i18n('closedGroupMaxSize'); - - window.pushToast({ - title: msg, - type: 'error', - id: 'tooManyMembers', - }); + window.libsession.Utils.ToastUtils.pushTooManyMembers(); return; } diff --git a/js/views/session_toast_view.js b/js/views/session_toast_view.js deleted file mode 100644 index 9fcc77a79..000000000 --- a/js/views/session_toast_view.js +++ /dev/null @@ -1,75 +0,0 @@ -/* global Whisper */ - -// eslint-disable-next-line func-names -(function() { - 'use strict'; - - window.Whisper = window.Whisper || {}; - - Whisper.SessionToastView = Whisper.View.extend({ - initialize(options) { - this.props = { - title: options.title, - id: options.id, - description: options.description, - icon: options.icon, - fadeToast: this.fadeToast.bind(this), - closeToast: this.closeToast.bind(this), - }; - }, - - render() { - this.toastView = new Whisper.ReactWrapperView({ - className: 'session-toast-wrapper', - Component: window.Signal.Components.SessionToast, - props: this.props, - }); - - this.$el.prepend(this.toastView.el); - }, - - update(options) { - this.props.title = options.title; - this.props.id = options.id; - this.props.description = options.description || ''; - this.props.type = options.type || ''; - this.props.icon = options.icon || ''; - this.props.shouldFade = options.shouldFade !== false; - - this.toastView.update(this.props); - - this.showToast(); - - if (this.timer) { - clearTimeout(this.timer); - } - if (this.props.shouldFade) { - this.timer = setTimeout(this.fadeToast.bind(this), 4000); - } - }, - - showToast() { - this.toastView.$el.show(); - }, - - fadeToast() { - this.removeToast(); - this.toastView.$el.fadeOut(500, () => { - this.toastView.remove(); - }); - }, - - closeToast() { - this.removeToast(); - this.toastView.$el.fadeOut(125, () => { - this.toastView.remove(); - }); - }, - - removeToast() { - if (this.props.id) { - window.toasts.delete(this.props.id); - } - }, - }); -})(); diff --git a/js/views/toast_view.js b/js/views/toast_view.js deleted file mode 100644 index ae90feb25..000000000 --- a/js/views/toast_view.js +++ /dev/null @@ -1,31 +0,0 @@ -/* global Whisper, Mustache, _ */ - -// eslint-disable-next-line func-names -(function() { - 'use strict'; - - window.Whisper = window.Whisper || {}; - - Whisper.ToastView = Whisper.View.extend({ - className: 'toast', - templateName: 'toast', - initialize() { - this.$el.hide(); - }, - - close() { - this.$el.fadeOut(this.remove.bind(this)); - }, - - render() { - this.$el.html( - Mustache.render( - _.result(this, 'template', ''), - _.result(this, 'render_attributes', '') - ) - ); - this.$el.show(); - setTimeout(this.close.bind(this), 2000); - }, - }); -})(); diff --git a/package.json b/package.json index a483676d5..bf228e07f 100644 --- a/package.json +++ b/package.json @@ -113,6 +113,7 @@ "react-portal": "^4.2.0", "react-qr-svg": "^2.2.1", "react-redux": "7.2.1", + "react-toastify": "^6.0.9", "react-virtualized": "9.21.0", "read-last-lines": "1.3.0", "redux": "4.0.1", diff --git a/stylesheets/_conversation.scss b/stylesheets/_conversation.scss index e17a01797..698e14f95 100644 --- a/stylesheets/_conversation.scss +++ b/stylesheets/_conversation.scss @@ -291,23 +291,3 @@ max-width: 150px; } } - -.toast { - position: absolute; - left: 50%; - transform: translate(-50%, 0); - bottom: 62px; - - text-align: center; - padding: 8px 16px; - border-radius: 4px; - z-index: 100; - - font-size: 13px; - line-height: 18px; - letter-spacing: 0; - - background-color: $color-gray-75; - color: $color-white; - box-shadow: 0 4px 16px 0 rgba(0, 0, 0, 0.12), 0 0 0 0.5px rgba(0, 0, 0, 0.08); -} diff --git a/stylesheets/_index.scss b/stylesheets/_index.scss index afd2705db..14b33f319 100644 --- a/stylesheets/_index.scss +++ b/stylesheets/_index.scss @@ -106,10 +106,6 @@ } } - .toast { - bottom: 78px; - } - .content { overflow-y: auto; max-height: calc(100% - 88px); diff --git a/stylesheets/_session.scss b/stylesheets/_session.scss index 9740dfedb..3e57d9c49 100644 --- a/stylesheets/_session.scss +++ b/stylesheets/_session.scss @@ -469,97 +469,37 @@ label { cursor: pointer; } -.session-toast-wrapper { - position: fixed; - right: $session-margin-lg; - bottom: $session-margin-lg; - - z-index: 10000; -} - -.session-toast { - position: relative; - padding: $session-margin-md $session-margin-md; +.Toastify__toast { @include themify($themes) { - background: rgba(themed('cellBackground'), 0.8); - } - margin-bottom: $session-margin-md; - display: flex; - flex-direction: row; - justify-content: flex-start; - - .toast-icon, - .toast-info { - display: flex; - flex-direction: column; - justify-content: center; - } - - .toast-icon { - padding-inline-end: $session-icon-size-md; - } - .toast-info { - margin-inline-end: $session-icon-size-sm + $session-icon-size-sm; - width: 350px; - - &-container { - display: block; - } - } - - .title, - .description { - text-overflow: ellipsis; - } - - .title { - font-size: $session-font-md; - line-height: $session-font-sm; - margin-bottom: $session-margin-sm; - padding-top: 0px; - @include themify($themes) { - color: themed('textColor'); - } - padding-top: 0px; - } - - .description { - font-size: $session-font-xs; - @include themify($themes) { - @include session-color-subtle(themed('textColor')); - } + background: rgba(themed('cellBackground'), 0.99); + color: themed('textColor'); } - - .toast-close { + .Toastify__close-button { @include themify($themes) { - @include session-color-subtle(themed('textColor')); - } - position: absolute; - top: $session-margin-md; - right: $session-margin-md; - - &:hover { - @include themify($themes) { - color: themed('textColor'); - } + color: subtle(themed('textColor')); } } - @mixin set-toast-theme($color) { border-left: 4px solid $color; } - &.info { - @include set-toast-theme($session-color-info); - } - &.success { + &--success { @include set-toast-theme($session-color-success); } - &.warning { + &--info { + @include set-toast-theme($session-color-info); + } + &--warning { @include set-toast-theme($session-color-warning-alt); } - &.error { + &--error { @include set-toast-theme($session-color-error); } + + .Toastify__progress-bar { + @include themify($themes) { + background-color: rgba(themed('textColor'), 0.1); + } + } } .session-modal { diff --git a/stylesheets/manifest.scss b/stylesheets/manifest.scss index 8faf2ace5..e9d03b0a7 100644 --- a/stylesheets/manifest.scss +++ b/stylesheets/manifest.scss @@ -2,6 +2,7 @@ @import 'node_modules/emoji-mart/css/emoji-mart.css'; @import 'node_modules/react-h5-audio-player/lib/styles.css'; @import 'node_modules/react-contexify/dist/ReactContexify.min.css'; +@import 'node_modules/react-toastify/dist/ReactToastify.css'; // Global Settings, Variables, and Mixins @import 'themes.scss'; diff --git a/test/index.html b/test/index.html index 634941b8b..d94ddf314 100644 --- a/test/index.html +++ b/test/index.html @@ -50,11 +50,6 @@ - - - - - diff --git a/ts/components/DevicePairingDialog.tsx b/ts/components/DevicePairingDialog.tsx index 7ef3eccf9..f4757b090 100644 --- a/ts/components/DevicePairingDialog.tsx +++ b/ts/components/DevicePairingDialog.tsx @@ -4,7 +4,8 @@ import { QRCode } from 'react-qr-svg'; import { SessionModal } from './session/SessionModal'; import { SessionButton, SessionButtonColor } from './session/SessionButton'; import { SessionSpinner } from './session/SessionSpinner'; -import classNames from 'classnames'; +import { toast } from 'react-toastify'; +import { SessionToast, SessionToastType } from './session/SessionToast'; import { ToastUtils } from '../session/utils'; interface Props { @@ -285,10 +286,10 @@ export class DevicePairingDialog extends React.Component { errors: null, }); this.closeDialog(); - ToastUtils.push({ - title: window.i18n('devicePairedSuccessfully'), - type: 'success', - }); + ToastUtils.pushToastSuccess( + 'devicePairedSuccessfully', + window.i18n('devicePairedSuccessfully') + ); const { currentPubKey } = this.state; if (currentPubKey) { const conv = window.ConversationController.get(currentPubKey); @@ -373,9 +374,10 @@ export class DevicePairingDialog extends React.Component { private triggerUnpairDevice() { const deviceUnpaired = () => { - ToastUtils.push({ - title: window.i18n('deviceUnpaired'), - }); + ToastUtils.pushToastSuccess( + 'deviceUnpaired', + window.i18n('deviceUnpaired') + ); this.closeDialog(); this.setState({ loading: false }); }; diff --git a/ts/components/LeftPane.tsx b/ts/components/LeftPane.tsx index 55ce80a02..566acb201 100644 --- a/ts/components/LeftPane.tsx +++ b/ts/components/LeftPane.tsx @@ -12,7 +12,8 @@ import { ConversationType } from '../state/ducks/conversations'; import { LeftPaneContactSection } from './session/LeftPaneContactSection'; import { LeftPaneSettingSection } from './session/LeftPaneSettingSection'; import { SessionIconType } from './session/icon'; -import { FOCUS_SECTION } from '../state/ducks/section'; +import { SessionTheme } from '../state/ducks/SessionTheme'; +import { DefaultTheme } from 'styled-components'; // from https://github.com/bvaughn/react-virtualized/blob/fb3484ed5dcc41bffae8eab029126c0fb8f7abc0/source/List/types.js#L5 export type RowRendererParamsType = { @@ -39,6 +40,8 @@ interface Props { updateSearchTerm: (searchTerm: string) => void; search: (query: string, options: SearchOptions) => void; clearSearch: () => void; + + theme: DefaultTheme; } export class LeftPane extends React.Component { @@ -76,15 +79,17 @@ export class LeftPane extends React.Component { public render(): JSX.Element { return ( -
- -
{this.renderSection()}
-
+ +
+ +
{this.renderSection()}
+
+
); } diff --git a/ts/components/MainViewController.tsx b/ts/components/MainViewController.tsx index 20dd05901..6b07835c6 100644 --- a/ts/components/MainViewController.tsx +++ b/ts/components/MainViewController.tsx @@ -16,6 +16,8 @@ export const MainViewController = { import { ContactType } from './session/SessionMemberListItem'; import { ToastUtils } from '../session/utils'; +import { toast } from 'react-toastify'; +import { SessionToast, SessionToastType } from './session/SessionToast'; export class MessageView extends React.Component { public render() { @@ -54,20 +56,17 @@ async function createClosedGroup( ) { // Validate groupName and groupMembers length if (groupName.length === 0) { - ToastUtils.push({ - title: window.i18n('invalidGroupNameTooShort'), - type: 'error', - id: 'invalidGroupName', - }); + ToastUtils.pushToastError( + 'invalidGroupName', + window.i18n('invalidGroupNameTooShort') + ); return; } else if (groupName.length > window.CONSTANTS.MAX_GROUP_NAME_LENGTH) { - ToastUtils.push({ - title: window.i18n('invalidGroupNameTooLong'), - type: 'error', - id: 'invalidGroupName', - }); - + ToastUtils.pushToastError( + 'invalidGroupName', + window.i18n('invalidGroupNameTooLong') + ); return; } @@ -75,20 +74,16 @@ async function createClosedGroup( // the same is valid with groups count < 1 if (groupMembers.length < 1) { - ToastUtils.push({ - title: window.i18n('pickClosedGroupMember'), - type: 'error', - id: 'pickClosedGroupMember', - }); - + ToastUtils.pushToastError( + 'pickClosedGroupMember', + window.i18n('pickClosedGroupMember') + ); return; } else if (groupMembers.length >= window.CONSTANTS.MEDIUM_GROUP_SIZE_LIMIT) { - ToastUtils.push({ - title: window.i18n('closedGroupMaxSize'), - type: 'error', - id: 'closedGroupMaxSize', - }); - + ToastUtils.pushToastError( + 'closedGroupMaxSize', + window.i18n('closedGroupMaxSize') + ); return; } diff --git a/ts/components/session/ActionsPanel.tsx b/ts/components/session/ActionsPanel.tsx index a70abbc48..ec6c1e275 100644 --- a/ts/components/session/ActionsPanel.tsx +++ b/ts/components/session/ActionsPanel.tsx @@ -6,8 +6,7 @@ import { PropsData as ConversationListItemPropsType } from '../ConversationListI import { createOrUpdateItem, getItemById } from '../../../js/modules/data'; import { APPLY_THEME } from '../../state/ducks/theme'; import { darkTheme, lightTheme } from '../../state/ducks/SessionTheme'; -import { MultiDeviceProtocol } from '../../session/protocols'; -import { UserUtil } from '../../util'; +import { SessionToastContainer } from './SessionToastContainer'; // tslint:disable-next-line: no-import-side-effect no-submodule-imports export enum SectionType { @@ -245,6 +244,8 @@ class ActionsPanelPrivate extends React.Component { isSelected={isSettingsPageSelected} onSelect={this.handleSectionSelect} /> + + ; @@ -132,11 +134,7 @@ export class LeftPaneContactSection extends React.Component { const error = validateNumber(sessionID, window.i18n); if (error) { - ToastUtils.push({ - title: error, - type: 'error', - id: 'addContact', - }); + ToastUtils.pushToastError('addContact', error); } else { window.Whisper.events.trigger('showConversation', sessionID); } diff --git a/ts/components/session/LeftPaneMessageSection.tsx b/ts/components/session/LeftPaneMessageSection.tsx index acca9b0dc..ff53a4047 100644 --- a/ts/components/session/LeftPaneMessageSection.tsx +++ b/ts/components/session/LeftPaneMessageSection.tsx @@ -31,6 +31,8 @@ import { } from './SessionButton'; import { OpenGroup } from '../../session/types'; import { ToastUtils } from '../../session/utils'; +import { toast } from 'react-toastify'; +import { SessionToast, SessionToastType } from './SessionToast'; export interface Props { searchTerm: string; @@ -385,12 +387,10 @@ export class LeftPaneMessageSection extends React.Component { const { openConversationInternal } = this.props; if (!this.state.valuePasted && !this.props.searchTerm) { - ToastUtils.push({ - title: window.i18n('invalidNumberError'), - type: 'error', - id: 'invalidPubKey', - }); - + ToastUtils.pushToastError( + 'invalidPubKey', + window.i18n('invalidNumberError') + ); return; } let pubkey: string; @@ -401,11 +401,7 @@ export class LeftPaneMessageSection extends React.Component { if (!error) { openConversationInternal(pubkey); } else { - ToastUtils.push({ - title: error, - type: 'error', - id: 'invalidPubKey', - }); + ToastUtils.pushToastError('invalidPubKey', error); } } @@ -418,41 +414,36 @@ export class LeftPaneMessageSection extends React.Component { // Server URL valid? if (serverUrl.length === 0 || !OpenGroup.validate(serverUrl)) { - ToastUtils.push({ - title: window.i18n('invalidOpenGroupUrl'), - id: 'connectToServer', - type: 'error', - }); - + ToastUtils.pushToastError( + 'connectToServer', + window.i18n('invalidOpenGroupUrl') + ); return; } // Already connected? if (Boolean(await OpenGroup.getConversation(serverUrl))) { - ToastUtils.push({ - title: window.i18n('publicChatExists'), - id: 'publicChatExists', - type: 'error', - }); - + ToastUtils.pushToastError( + 'publicChatExists', + window.i18n('publicChatExists') + ); return; } // Connect to server try { - ToastUtils.push({ - title: window.i18n('connectingToServer'), - id: 'connectToServer', - type: 'success', - }); + ToastUtils.pushToastSuccess( + 'connectingToServer', + window.i18n('connectingToServer') + ); + this.setState({ loading: true }); await OpenGroup.join(serverUrl, async () => { if (await OpenGroup.serverExists(serverUrl)) { - ToastUtils.push({ - title: window.i18n('connectToServerSuccess'), - id: 'connectToServer', - type: 'success', - }); + ToastUtils.pushToastSuccess( + 'connectToServerSuccess', + window.i18n('connectToServerSuccess') + ); } this.setState({ loading: false }); }); @@ -471,11 +462,10 @@ export class LeftPaneMessageSection extends React.Component { } } catch (e) { window.console.error('Failed to connect to server:', e); - ToastUtils.push({ - title: window.i18n('connectToServerFail'), - id: 'connectToServer', - type: 'error', - }); + ToastUtils.pushToastError( + 'connectToServerFail', + window.i18n('connectToServerFail') + ); this.setState({ loading: false }); } finally { this.setState({ diff --git a/ts/components/session/RegistrationTabs.tsx b/ts/components/session/RegistrationTabs.tsx index 6f9472cda..8cbef0a83 100644 --- a/ts/components/session/RegistrationTabs.tsx +++ b/ts/components/session/RegistrationTabs.tsx @@ -13,6 +13,8 @@ import { SessionIdEditable } from './SessionIdEditable'; import { SessionSpinner } from './SessionSpinner'; import { StringUtils, ToastUtils } from '../../session/utils'; import { createOrUpdateItem } from '../../../js/modules/data'; +import { toast } from 'react-toastify'; +import { SessionToast } from './SessionToast'; enum SignInMode { Default, @@ -807,35 +809,28 @@ export class RegistrationTabs extends React.Component<{}, State> { if (!trimName) { window.log.warn('invalid trimmed name for registration'); - ToastUtils.push({ - title: window.i18n('displayNameEmpty'), - type: 'error', - id: 'invalidDisplayName', - }); - + ToastUtils.pushToastError( + 'invalidDisplayName', + window.i18n('displayNameEmpty') + ); return; } if (passwordErrorString) { window.log.warn('invalid password for registration'); - ToastUtils.push({ - title: window.i18n('invalidPassword'), - type: 'error', - id: 'invalidPassword', - }); - + ToastUtils.pushToastError( + 'invalidPassword', + window.i18n('invalidPassword') + ); return; } if (!!password && !passwordFieldsMatch) { window.log.warn('passwords does not match for registration'); - - ToastUtils.push({ - title: window.i18n('passwordsDoNotMatch'), - type: 'error', - id: 'invalidPassword', - }); - + ToastUtils.pushToastError( + 'invalidPassword', + window.i18n('passwordsDoNotMatch') + ); return; } @@ -874,11 +869,10 @@ export class RegistrationTabs extends React.Component<{}, State> { await createOrUpdateItem(data); trigger('openInbox'); } catch (e) { - ToastUtils.push({ - title: `Error: ${e.message || 'Something went wrong'}`, - type: 'error', - id: 'registrationError', - }); + ToastUtils.pushToastError( + 'registrationError', + `Error: ${e.message || 'Something went wrong'}` + ); let exmsg = ''; if (e.message) { exmsg += e.message; @@ -905,12 +899,10 @@ export class RegistrationTabs extends React.Component<{}, State> { // tslint:disable-next-line: no-backbone-get-set-outside-model if (window.textsecure.storage.get('secondaryDeviceStatus') === 'ongoing') { window.log.warn('registering secondary device already ongoing'); - ToastUtils.push({ - title: window.i18n('pairingOngoing'), - type: 'error', - id: 'pairingOngoing', - }); - + ToastUtils.pushToastError( + 'pairingOngoing', + window.i18n('pairingOngoing') + ); return; } this.setState({ @@ -948,12 +940,10 @@ export class RegistrationTabs extends React.Component<{}, State> { const validationError = c.validateNumber(); if (validationError) { onError('Invalid public key').ignore(); - ToastUtils.push({ - title: window.i18n('invalidNumberError'), - type: 'error', - id: 'invalidNumberError', - }); - + ToastUtils.pushToastError( + 'invalidNumberError', + window.i18n('invalidNumberError') + ); return; } try { diff --git a/ts/components/session/SessionPasswordModal.tsx b/ts/components/session/SessionPasswordModal.tsx index 99a5b485f..8b989c593 100644 --- a/ts/components/session/SessionPasswordModal.tsx +++ b/ts/components/session/SessionPasswordModal.tsx @@ -4,6 +4,9 @@ import { SessionModal } from './SessionModal'; import { SessionButton, SessionButtonColor } from './SessionButton'; import { PasswordUtil } from '../../util/'; import { ToastUtils } from '../../session/utils'; +import { toast } from 'react-toastify'; +import { SessionToast, SessionToastType } from './SessionToast'; +import { SessionIconType } from './icon'; export enum PasswordAction { Set = 'set', Change = 'change', @@ -233,13 +236,20 @@ export class SessionPasswordModal extends React.Component { throw new Error(`Invalid action ${action}`); } - ToastUtils.push({ - id: 'set-password-success-toast', - type: action !== Remove ? 'success' : 'warning', - title: title, - description: description, - icon: action !== Remove ? 'lock' : undefined, - }); + if (action !== Remove) { + ToastUtils.pushToastSuccess( + 'setPasswordSuccessToast', + title, + description, + SessionIconType.Lock + ); + } else { + ToastUtils.pushToastWarning( + 'setPasswordSuccessToast', + title, + description + ); + } onSuccess(this.props.action); this.closeDialog(); @@ -261,11 +271,7 @@ export class SessionPasswordModal extends React.Component { window.CONSTANTS.MAX_PASSWORD_LENGTH ) ); - - ToastUtils.push({ - title, - type: 'warning', - }); + ToastUtils.pushToastWarning('passwordModal', title); } // Prevent pating into input diff --git a/ts/components/session/SessionRegistrationView.tsx b/ts/components/session/SessionRegistrationView.tsx index 6f1ef293a..32347195c 100644 --- a/ts/components/session/SessionRegistrationView.tsx +++ b/ts/components/session/SessionRegistrationView.tsx @@ -3,10 +3,11 @@ import { AccentText } from './AccentText'; import { RegistrationTabs } from './RegistrationTabs'; import { SessionIconButton, SessionIconSize, SessionIconType } from './icon'; +import { SessionToastContainer } from './SessionToastContainer'; export const SessionRegistrationView: React.FC = () => (
-
+
diff --git a/ts/components/session/SessionToast.tsx b/ts/components/session/SessionToast.tsx index ad7c6a162..241398d64 100644 --- a/ts/components/session/SessionToast.tsx +++ b/ts/components/session/SessionToast.tsx @@ -1,12 +1,8 @@ import React from 'react'; -import classNames from 'classnames'; -import { - SessionIcon, - SessionIconButton, - SessionIconSize, - SessionIconType, -} from './icon/'; +import { SessionIcon, SessionIconSize, SessionIconType } from './icon/'; +import { Flex } from './Flex'; +import styled from 'styled-components'; export enum SessionToastType { Info = 'info', @@ -15,70 +11,80 @@ export enum SessionToastType { Error = 'error', } -interface Props { +type Props = { title: string; id?: string; type?: SessionToastType; icon?: SessionIconType; description?: string; - closeToast: any; -} + closeToast?: any; +}; -export class SessionToast extends React.PureComponent { - constructor(props: any) { - super(props); - } +const TitleDiv = styled.div` + font-size: ${props => props.theme.common.fonts.md}; + line-height: ${props => props.theme.common.fonts.md}; + font-family: ${props => props.theme.common.fonts.sessionFontDefault}; + color: ${props => props.theme.colors.textColor}; + text-overflow: ellipsis; +`; - public render() { - const { title, description, type, icon } = this.props; +const DescriptionDiv = styled.div` + font-size: ${props => props.theme.common.fonts.sm}; + color: ${props => props.theme.colors.textColorSubtle}; + text-overflow: ellipsis; + font-family: ${props => props.theme.common.fonts.sessionFontDefault}; + padding-bottom: ${props => props.theme.common.fonts.xs}; + padding-top: ${props => props.theme.common.fonts.xs}; +`; - const toastType = type ? type : SessionToastType.Info; - const toastDesc = description ? description : ''; - const toastIconSize = toastDesc - ? SessionIconSize.Huge - : SessionIconSize.Medium; +const IconDiv = styled.div` + flex-shrink: 0; + padding-inline-end: ${props => props.theme.common.margins.xs}; +`; - // Set a custom icon or allow the theme to define the icon - let toastIcon = icon || undefined; - if (!toastIcon) { - switch (type) { - case SessionToastType.Info: - toastIcon = SessionIconType.Info; - break; - case SessionToastType.Success: - toastIcon = SessionIconType.Check; - break; - case SessionToastType.Error: - toastIcon = SessionIconType.Error; - break; - case SessionToastType.Warning: - toastIcon = SessionIconType.Warning; - break; - default: - toastIcon = SessionIconType.Info; - } - } +export const SessionToast = (props: Props) => { + const { title, description, type, icon } = props; - return ( -
-
- -
-
-
-

{title}

-

{toastDesc}

-
-
+ const toastDesc = description ? description : ''; + const toastIconSize = toastDesc + ? SessionIconSize.Huge + : SessionIconSize.Medium; -
- -
-
- ); + // Set a custom icon or allow the theme to define the icon + let toastIcon = icon || undefined; + if (!toastIcon) { + switch (type) { + case SessionToastType.Info: + toastIcon = SessionIconType.Info; + break; + case SessionToastType.Success: + toastIcon = SessionIconType.Check; + break; + case SessionToastType.Error: + toastIcon = SessionIconType.Error; + break; + case SessionToastType.Warning: + toastIcon = SessionIconType.Warning; + break; + default: + toastIcon = SessionIconType.Info; + } } -} + + return ( + + + + + + {title} + {toastDesc && {toastDesc}} + + + ); +}; diff --git a/ts/components/session/SessionToastContainer.tsx b/ts/components/session/SessionToastContainer.tsx new file mode 100644 index 000000000..d41976fb0 --- /dev/null +++ b/ts/components/session/SessionToastContainer.tsx @@ -0,0 +1,51 @@ +import React from 'react'; +import { Slide, ToastContainer, ToastContainerProps } from 'react-toastify'; +import styled from 'styled-components'; + +const SessionToastContainerPrivate = () => { + return ( + + ); +}; + +const WrappedToastContainer = ({ + className, + ...rest +}: ToastContainerProps & { className?: string }) => ( +
+ +
+); + +// tslint:disable-next-line: no-default-export +export const SessionToastContainer = styled(SessionToastContainerPrivate).attrs( + { + // custom props + } +)` + .Toastify__toast-container { + } + .Toastify__toast { + } + .Toastify__toast--error { + } + .Toastify__toast--warning { + } + .Toastify__toast--success { + } + .Toastify__toast-body { + } + .Toastify__progress-bar { + } +`; diff --git a/ts/components/session/conversation/SessionConversation.tsx b/ts/components/session/conversation/SessionConversation.tsx index 5abed9c8c..5857978ae 100644 --- a/ts/components/session/conversation/SessionConversation.tsx +++ b/ts/components/session/conversation/SessionConversation.tsx @@ -223,7 +223,8 @@ export class SessionConversation extends React.Component { const conversationModel = window.ConversationController.getOrThrow( conversationKey ); - const isRss = conversation.isRss; + + const { isRss } = conversation; // TODO VINCE: OPTIMISE FOR NEW SENDING??? const sendMessageFn = conversationModel.sendMessage.bind(conversationModel); diff --git a/ts/components/session/settings/SessionSettings.tsx b/ts/components/session/settings/SessionSettings.tsx index 418496997..896cdd2cb 100644 --- a/ts/components/session/settings/SessionSettings.tsx +++ b/ts/components/session/settings/SessionSettings.tsx @@ -10,6 +10,8 @@ import { import { BlockedNumberController, UserUtil } from '../../../util'; import { MultiDeviceProtocol } from '../../../session/protocols'; import { PubKey } from '../../../session/types'; +import { SessionToast, SessionToastType } from '../SessionToast'; +import { toast } from 'react-toastify'; import { ToastUtils } from '../../../session/utils'; export enum SessionSettingCategory { @@ -609,10 +611,7 @@ export class SettingsView extends React.Component { await BlockedNumberController.unblock(blockedNumber); this.forceUpdate(); } - ToastUtils.push({ - title: window.i18n('unblocked'), - id: 'unblocked', - }); + ToastUtils.pushToastSuccess('unblocked', window.i18n('unblocked')); }, hidden: false, onClick: undefined, diff --git a/ts/receiver/dataMessage.ts b/ts/receiver/dataMessage.ts index 37ade63e9..a0b07fa12 100644 --- a/ts/receiver/dataMessage.ts +++ b/ts/receiver/dataMessage.ts @@ -608,7 +608,7 @@ export async function handleMessageEvent(event: MessageEvent): Promise { SESSION_RESTORE, } = SignalService.DataMessage.Flags; - // eslint-disable-next-line no-bitwise + // tslint:disable-next-line: no-bitwise const isProfileUpdate = Boolean(message.flags & PROFILE_KEY_UPDATE); if (isProfileUpdate) { @@ -682,7 +682,7 @@ export async function handleMessageEvent(event: MessageEvent): Promise { // =========== Process flags ============= - // eslint-disable-next-line no-bitwise + // tslint:disable-next-line: no-bitwise if (message.flags & SESSION_RESTORE) { // Show that the session reset is "in progress" even though we had a valid session msg.set({ endSessionType: 'ongoing' }); diff --git a/ts/session/utils/Toast.ts b/ts/session/utils/Toast.ts deleted file mode 100644 index 5f03f1e3c..000000000 --- a/ts/session/utils/Toast.ts +++ /dev/null @@ -1,164 +0,0 @@ -export function push(options: { - title: string; - id?: string; - description?: string; - type?: 'success' | 'info' | 'warning' | 'error'; - icon?: string; - shouldFade?: boolean; -}) { - window.pushToast(options); -} - -export function pushLoadAttachmentFailure() { - window.pushToast({ - title: window.i18n('unableToLoadAttachment'), - type: 'error', - id: 'unableToLoadAttachment', - }); -} - -export function pushDangerousFileError() { - window.pushToast({ - title: window.i18n('dangerousFileType'), - type: 'error', - id: 'dangerousFileType', - }); -} - -export function pushFileSizeError(limit: number, units: string) { - window.pushToast({ - title: window.i18n('fileSizeWarning'), - description: `Max size: ${limit} ${units}`, - type: 'error', - id: 'fileSizeWarning', - }); -} - -export function pushMultipleNonImageError() { - window.pushToast({ - title: window.i18n('cannotMixImageAndNonImageAttachments'), - type: 'error', - id: 'cannotMixImageAndNonImageAttachments', - }); -} - -export function pushCannotMixError() { - window.pushToast({ - title: window.i18n('oneNonImageAtATimeToast'), - type: 'error', - id: 'oneNonImageAtATimeToast', - }); -} - -export function pushMaximumAttachmentsError() { - window.pushToast({ - title: window.i18n('maximumAttachments'), - type: 'error', - id: 'maximumAttachments', - }); -} - -export function pushMessageBodyTooLong() { - window.pushToast({ - title: window.i18n('messageBodyTooLong'), - type: 'error', - id: 'messageBodyTooLong', - }); -} - -export function pushMessageBodyMissing() { - window.pushToast({ - title: window.i18n('messageBodyMissing'), - type: 'error', - id: 'messageBodyMissing', - }); -} - -export function pushCopiedToClipBoard() { - window.pushToast({ - title: window.i18n('copiedToClipboard'), - type: 'success', - id: 'copiedToClipboard', - }); -} - -export function pushForceUnlinked() { - window.pushToast({ - title: window.i18n('successUnlinked'), - type: 'info', - id: 'successUnlinked', - }); -} - -export function pushSpellCheckDirty() { - window.pushToast({ - title: window.i18n('spellCheckDirty'), - type: 'info', - id: 'spellCheckDirty', - }); -} - -export function pushAlreadyMemberOpenGroup() { - window.pushToast({ - title: window.i18n('publicChatExists'), - type: 'info', - id: 'alreadyMemberPublicChat', - }); -} - -export function pushUserBanSuccess() { - window.pushToast({ - title: window.i18n('userBanned'), - type: 'success', - id: 'userBanned', - }); -} - -export function pushUserBanFailure() { - window.pushToast({ - title: window.i18n('userBanFailed'), - type: 'error', - id: 'userBanFailed', - }); -} - -export function pushMessageDeleteForbidden() { - window.pushToast({ - title: window.i18n('messageDeletionForbidden'), - type: 'error', - id: 'messageDeletionForbidden', - }); -} - -export function pushAudioPermissionNeeded() { - window.pushToast({ - id: 'audioPermissionNeeded', - title: window.i18n('audioPermissionNeededTitle'), - description: window.i18n('audioPermissionNeeded'), - type: 'info', - }); -} - -export function pushOriginalNotFound() { - window.pushToast({ - id: 'originalMessageNotFound', - title: window.i18n('originalMessageNotFound'), - type: 'error', - }); -} - -export function pushOriginalNoLongerAvailable() { - window.pushToast({ - id: 'originalMessageNotAvailable', - title: window.i18n('originalMessageNotAvailable'), - type: 'error', - }); -} - -export function pushFoundButNotLoaded() { - window.pushToast({ - id: 'messageFoundButNotLoaded', - title: window.i18n('messageFoundButNotLoaded'), - type: 'error', - }); -} diff --git a/ts/session/utils/Toast.tsx b/ts/session/utils/Toast.tsx new file mode 100644 index 000000000..2ed0c8ff3 --- /dev/null +++ b/ts/session/utils/Toast.tsx @@ -0,0 +1,214 @@ +import React from 'react'; +import { toast } from 'react-toastify'; +import { SessionIconType } from '../../components/session/icon'; +import { + SessionToast, + SessionToastType, +} from '../../components/session/SessionToast'; + +// if you push a toast manually with toast...() be sure to set the type attribute of the SessionToast component +export function pushToastError( + id: string, + title: string, + description?: string +) { + toast.error( + , + { toastId: id } + ); +} + +export function pushToastWarning( + id: string, + title: string, + description?: string +) { + toast.warning( + , + { toastId: id } + ); +} + +export function pushToastInfo(id: string, title: string, description?: string) { + toast.info( + , + { toastId: id } + ); +} + +export function pushToastSuccess( + id: string, + title: string, + description?: string, + icon?: SessionIconType +) { + toast.success( + , + { toastId: id } + ); +} + +export function pushLoadAttachmentFailure() { + pushToastError( + 'unableToLoadAttachment', + window.i18n('unableToLoadAttachment') + ); +} + +export function pushDangerousFileError() { + pushToastError('dangerousFileType', window.i18n('dangerousFileType')); +} + +export function pushFileSizeError(limit: number, units: string) { + pushToastError( + 'fileSizeWarning', + window.i18n('fileSizeWarning'), + `Max size: ${limit} ${units}` + ); +} + +export function pushMultipleNonImageError() { + pushToastError( + 'cannotMixImageAndNonImageAttachments', + window.i18n('cannotMixImageAndNonImageAttachments') + ); +} + +export function pushCannotMixError() { + pushToastError( + 'oneNonImageAtATimeToast', + window.i18n('oneNonImageAtATimeToast') + ); +} + +export function pushMaximumAttachmentsError() { + pushToastError('maximumAttachments', window.i18n('maximumAttachments')); +} + +export function pushMessageBodyTooLong() { + pushToastError('messageBodyTooLong', window.i18n('messageBodyTooLong')); +} + +export function pushMessageBodyMissing() { + pushToastError('messageBodyMissing', window.i18n('messageBodyMissing')); +} + +export function pushCopiedToClipBoard() { + pushToastInfo('copiedToClipboard', window.i18n('copiedToClipboard')); +} + +export function pushForceUnlinked() { + pushToastInfo('successUnlinked', window.i18n('successUnlinked')); +} + +export function pushSpellCheckDirty() { + pushToastInfo('spellCheckDirty', window.i18n('spellCheckDirty')); +} + +export function pushAlreadyMemberOpenGroup() { + pushToastInfo('publicChatExists', window.i18n('publicChatExists')); +} + +export function pushUserBanSuccess() { + pushToastSuccess('userBanned', window.i18n('userBanned')); +} + +export function pushUserBanFailure() { + pushToastError('userBanFailed', window.i18n('userBanFailed')); +} + +export function pushMessageDeleteForbidden() { + pushToastError( + 'messageDeletionForbidden', + window.i18n('messageDeletionForbidden') + ); +} + +export function pushAudioPermissionNeeded() { + pushToastInfo( + 'audioPermissionNeeded', + window.i18n('audioPermissionNeededTitle'), + window.i18n('audioPermissionNeeded') + ); +} + +export function pushOriginalNotFound() { + pushToastError( + 'originalMessageNotFound', + window.i18n('originalMessageNotFound') + ); +} + +export function pushOriginalNoLongerAvailable() { + pushToastError( + 'originalMessageNotAvailable', + window.i18n('originalMessageNotAvailable') + ); +} + +export function pushFoundButNotLoaded() { + pushToastError( + 'messageFoundButNotLoaded', + window.i18n('messageFoundButNotLoaded') + ); +} + +export function pushTooManyMembers() { + pushToastError('tooManyMembers', window.i18n('closedGroupMaxSize')); +} + +export function pushPairingRequestReceived(alreadyLinked: boolean) { + const title = alreadyLinked + ? window.i18n('devicePairingRequestReceivedLimitTitle') + : window.i18n('devicePairingRequestReceivedNoListenerTitle'); + + const description = alreadyLinked + ? window.i18n( + 'devicePairingRequestReceivedLimitDescription', + window.CONSTANTS.MAX_LINKED_DEVICES + ) + : window.i18n('devicePairingRequestReceivedNoListenerDescription'); + + if (alreadyLinked) { + toast.info( + , + { + toastId: 'pairingRequestReceived', + autoClose: false, + } + ); + } else { + toast.warning( + , + { + toastId: 'pairingRequestReceived', + autoClose: false, + } + ); + } +} diff --git a/ts/state/ducks/SessionTheme.tsx b/ts/state/ducks/SessionTheme.tsx index 38a6816be..4313ae643 100644 --- a/ts/state/ducks/SessionTheme.tsx +++ b/ts/state/ducks/SessionTheme.tsx @@ -18,6 +18,11 @@ const common = { sessionFontDefault: 'Public Sans', sessionFontAccent: 'Loor', sessionFontMono: 'SpaceMono', + xs: '11px', + sm: '13px', + md: '15px', + lg: '18px', + xl: '24px', }, margins: { xs: '5px', diff --git a/ts/styled.d.ts b/ts/styled.d.ts index 25d59d72c..b8e5b60c1 100644 --- a/ts/styled.d.ts +++ b/ts/styled.d.ts @@ -7,6 +7,11 @@ declare module 'styled-components' { sessionFontDefault: string; sessionFontAccent: string; sessionFontMono: string; + xs: string; + sm: string; + md: string; + lg: string; + xl: string; }; margins: { xs: string; diff --git a/ts/window.d.ts b/ts/window.d.ts index 45c0fcc1c..a5ee275ec 100644 --- a/ts/window.d.ts +++ b/ts/window.d.ts @@ -47,7 +47,6 @@ declare global { deleteAccount: any; displayNameRegex: any; friends: any; - generateID: any; getAccountManager: any; getConversations: any; getFriendsFromContacts: any; @@ -75,7 +74,6 @@ declare global { mnemonic: RecoveryPhraseUtil; onLogin: any; passwordUtil: any; - pushToast: any; resetDatabase: any; restart: any; seedNodeList: any; diff --git a/yarn.lock b/yarn.lock index b98cbf2f0..219b0520b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -126,6 +126,13 @@ dependencies: regenerator-runtime "^0.13.4" +"@babel/runtime@^7.8.7": + version "7.12.5" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.12.5.tgz#410e7e487441e1b360c29be715d870d9b985882e" + integrity sha512-plcc+hbExy3McchJCEQG3knOsuh3HH+Prx1P6cLIkET/0dLuQDEnrT+s27Axgc9bqfsmNUNHfscgMUdBpC9xfg== + dependencies: + regenerator-runtime "^0.13.4" + "@babel/template@^7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.10.4.tgz#3251996c4200ebc71d1a8fc405fba940f36ba278" @@ -3217,6 +3224,14 @@ dom-align@^1.7.0: dependencies: "@babel/runtime" "^7.1.2" +dom-helpers@^5.0.1: + version "5.2.0" + resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.2.0.tgz#57fd054c5f8f34c52a3eeffdb7e7e93cd357d95b" + integrity sha512-Ru5o9+V8CpunKnz5LGgWXkmrH/20cGKwcHwS4m73zIvs54CN9epEmT/HLqFJW3kXpakAFkEdzgy1hzlJe3E4OQ== + dependencies: + "@babel/runtime" "^7.8.7" + csstype "^3.0.2" + dom-walk@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/dom-walk/-/dom-walk-0.1.1.tgz#672226dc74c8f799ad35307df936aba11acd6018" @@ -8864,6 +8879,25 @@ react-styleguidist@7.0.1: webpack-dev-server "^2.11.2" webpack-merge "^4.1.2" +react-toastify@^6.0.9: + version "6.0.9" + resolved "https://registry.yarnpkg.com/react-toastify/-/react-toastify-6.0.9.tgz#2a65e5ad9e05f3ee47664cbc69441498b9d694ce" + integrity sha512-StHXv+4kezHUnPyoILlvygSptQ79bxVuvKcC05yXP0FlqQgPA1ue+80BqxZZiCw2jWDGiY2MHyqBfFKf5YzZbA== + dependencies: + classnames "^2.2.6" + prop-types "^15.7.2" + react-transition-group "^4.4.1" + +react-transition-group@^4.4.1: + version "4.4.1" + resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.1.tgz#63868f9325a38ea5ee9535d828327f85773345c9" + integrity sha512-Djqr7OQ2aPUiYurhPalTrVy9ddmFCCzwhqQmtN+J3+3DzLO209Fdr70QrN8Z3DsglWql6iY1lDWAfpFiBtuKGw== + dependencies: + "@babel/runtime" "^7.5.5" + dom-helpers "^5.0.1" + loose-envify "^1.4.0" + prop-types "^15.6.2" + react-virtualized@9.21.0: version "9.21.0" resolved "https://registry.yarnpkg.com/react-virtualized/-/react-virtualized-9.21.0.tgz#8267c40ffb48db35b242a36dea85edcf280a6506"