From 4de8357a189f70815f27151506e46022bd06400e Mon Sep 17 00:00:00 2001 From: Max Philippov Date: Tue, 19 Nov 2024 22:38:03 +0500 Subject: [PATCH] Stack Settings dialogs - return explicit dialog result, ignore for now "cancel" result - Settings passes down it's onClose to children dialogs so when they close via "close" - fix #3868 --- .../src/components/Dialog/BackButton.tsx | 2 + .../frontend/src/components/Dialog/Dialog.tsx | 41 ++- .../src/components/Dialog/DialogHeader.tsx | 6 +- .../src/components/Dialog/styles.module.scss | 5 + .../src/components/Settings/Settings.tsx | 277 +++++++++--------- .../components/dialogs/ConfirmationDialog.tsx | 15 +- .../dialogs/EditProfileDialog/index.tsx | 15 +- .../components/dialogs/ViewProfile/index.tsx | 12 +- .../AccountDeletionScreen.tsx | 1 - .../WelcomeScreen/InstantOnboardingScreen.tsx | 25 +- .../WelcomeScreen/OnboardingScreen.tsx | 18 +- .../screens/WelcomeScreen/index.tsx | 36 +-- .../frontend/src/contexts/DialogContext.tsx | 10 +- 13 files changed, 243 insertions(+), 220 deletions(-) diff --git a/packages/frontend/src/components/Dialog/BackButton.tsx b/packages/frontend/src/components/Dialog/BackButton.tsx index b3968c668a..9dbadde31f 100644 --- a/packages/frontend/src/components/Dialog/BackButton.tsx +++ b/packages/frontend/src/components/Dialog/BackButton.tsx @@ -14,6 +14,8 @@ export default function BackButton( return ( ( rect.left <= ev.clientX && ev.clientX <= rect.left + rect.width if (!isInDialog) { - const cancelEvent = new Event('cancel') - dialog.current.dispatchEvent(cancelEvent) - // cancel event doesn't trigger dialog close - dialog.current.close() + dialog.current.close('cancel') } } : () => {} - const onClose = (value: any) => { - props.onClose && props.onClose(value) - dialog.current!.style.display = 'none' + const onClose = (_: any) => { + props.onClose && props.onClose(dialog.current?.returnValue) } - const onCancel = (ev: React.BaseSyntheticEvent) => { + const onEscapePress = (ev: React.BaseSyntheticEvent) => { + // by default this fires a 'close' event with undefined as value + // to unify everything we explicitly call close with 'cancel' value + ev.preventDefault() if (!canEscapeKeyClose) { - ev.preventDefault() + return } + + dialog.current?.close('cancel') } useEffect(() => { - // calling showModal is "only" the way to have ::backdrop + // calling showModal is "only" the way to have ::backdrop and "cancel" event dialog.current?.showModal() - dialog.current!.style.display = 'flex' }) - let style - - if (!unstyled) { - style = { - width: width && `${width}px`, - height: height && `${height}px`, - } - } + const style = unstyled + ? undefined + : { + width: width && `${width}px`, + height: height && `${height}px`, + } + // NOTE: do not set method="dialog" on form, it will cause every button to close the dialog return ( ( style={style} data-testid={props['dataTestid']} > - {children} +
{children}
) } diff --git a/packages/frontend/src/components/Dialog/DialogHeader.tsx b/packages/frontend/src/components/Dialog/DialogHeader.tsx index 9dd45f2bb4..9c0e1b7a95 100644 --- a/packages/frontend/src/components/Dialog/DialogHeader.tsx +++ b/packages/frontend/src/components/Dialog/DialogHeader.tsx @@ -28,7 +28,11 @@ export default function DialogHeader(props: Props) { {children} {onClickEdit && } {onClose && ( - + )} ) diff --git a/packages/frontend/src/components/Dialog/styles.module.scss b/packages/frontend/src/components/Dialog/styles.module.scss index 1debebf310..c3c231311a 100644 --- a/packages/frontend/src/components/Dialog/styles.module.scss +++ b/packages/frontend/src/components/Dialog/styles.module.scss @@ -3,8 +3,13 @@ $paddingHorizontal: 30px; $paddingVertical: 20px; .dialog { + opacity: 0; padding: 0; + &:last-of-type { + opacity: 1; + } + &.unstyled { background: none; width: 100vw; diff --git a/packages/frontend/src/components/Settings/Settings.tsx b/packages/frontend/src/components/Settings/Settings.tsx index 2a6991feb9..4595c981db 100644 --- a/packages/frontend/src/components/Settings/Settings.tsx +++ b/packages/frontend/src/components/Settings/Settings.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from 'react' +import React, { useEffect } from 'react' import { useSettingsStore } from '../../stores/settings' import { SendBackupDialog } from '../dialogs/SetupMultiDevice' @@ -20,19 +20,11 @@ import useTranslationFunction from '../../hooks/useTranslationFunction' import type { DialogProps } from '../../contexts/DialogContext' -type SettingsView = - | 'main' - | 'chats_and_media' - | 'notifications' - | 'appearance' - | 'advanced' - export default function Settings({ onClose }: DialogProps) { const { openDialog } = useDialog() // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const settingsStore = useSettingsStore()[0]! const tx = useTranslationFunction() - const [settingsMode, setSettingsMode] = useState('main') useEffect(() => { if (window.__settingsOpened) { @@ -48,137 +40,142 @@ export default function Settings({ onClose }: DialogProps) { return ( - {settingsMode === 'main' && ( - <> - - - - { - openDialog(EditProfileDialog, { - settingsStore, - }) - }} - > - {tx('pref_edit_profile')} - - - setSettingsMode('chats_and_media')} - > - {tx('pref_chats_and_media')} - - setSettingsMode('notifications')} - > - {tx('pref_notifications')} - - setSettingsMode('appearance')} - > - {tx('pref_appearance')} - - { - openDialog(SendBackupDialog) - onClose() - }} - > - {tx('multidevice_title')} - - - setSettingsMode('advanced')} - > - {tx('menu_advanced')} - - - {!runtime.getRuntimeInfo().isMac && ( - runtime.openLink(donationUrl)} - isLink - > - {tx('donate')} - - )} - runtime.openHelpWindow()} - > - {tx('menu_help')} - - openDialog(About)}> - {tx('global_menu_help_about_desktop')} - - - - )} - {settingsMode === 'chats_and_media' && ( - <> - setSettingsMode('main')} - onClose={onClose} - /> - - - - - )} - {settingsMode === 'notifications' && ( - <> - setSettingsMode('main')} - onClose={onClose} - /> - - - - - )} - {settingsMode === 'appearance' && ( - <> - setSettingsMode('main')} - onClose={onClose} - /> - - - - - )} - {settingsMode === 'advanced' && ( - <> - setSettingsMode('main')} - onClose={onClose} - /> - - - - - )} + + + + openDialog(EditProfileDialog, { onClose })} + > + {tx('pref_edit_profile')} + + + openDialog(SettingsDialogChatsAndMedia, { onClose })} + > + {tx('pref_chats_and_media')} + + openDialog(SettingsDialogNotifications, { onClose })} + > + {tx('pref_notifications')} + + openDialog(SettingsDialogAppearance, { onClose })} + > + {tx('pref_appearance')} + + openDialog(SendBackupDialog, { onClose })} + > + {tx('multidevice_title')} + + + openDialog(SettingsDialogAdvanced, { onClose })} + > + {tx('menu_advanced')} + + + {!runtime.getRuntimeInfo().isMac && ( + runtime.openLink(donationUrl)} + isLink + > + {tx('donate')} + + )} + runtime.openHelpWindow()} + > + {tx('menu_help')} + + openDialog(About, { onClose })} + > + {tx('global_menu_help_about_desktop')} + + + + ) +} + +function SettingsDialogChatsAndMedia({ onClose }: DialogProps) { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const settingsStore = useSettingsStore()[0]! + const tx = useTranslationFunction() + return ( + + + + + + + ) +} + +function SettingsDialogNotifications({ onClose }: DialogProps) { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const settingsStore = useSettingsStore()[0]! + const tx = useTranslationFunction() + return ( + + + + + + + ) +} + +function SettingsDialogAppearance({ onClose }: DialogProps) { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const settingsStore = useSettingsStore()[0]! + const tx = useTranslationFunction() + + return ( + + {}} + title={tx('pref_appearance')} + onClose={onClose} + /> + + + + + ) +} + +function SettingsDialogAdvanced({ onClose }: DialogProps) { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const settingsStore = useSettingsStore()[0]! + const tx = useTranslationFunction() + return ( + + + + + ) } diff --git a/packages/frontend/src/components/dialogs/ConfirmationDialog.tsx b/packages/frontend/src/components/dialogs/ConfirmationDialog.tsx index 934bdc4f33..d6c9e89b55 100644 --- a/packages/frontend/src/components/dialogs/ConfirmationDialog.tsx +++ b/packages/frontend/src/components/dialogs/ConfirmationDialog.tsx @@ -34,14 +34,9 @@ export default function ConfirmationDialog({ }: Props) { const tx = useTranslationFunction() - const handleClick = (yes: boolean) => { + const handleClose = (result: string) => { onClose() - cb(yes) - } - - const handleClose = () => { - onClose() - cb(false) + cb(result === 'confirm') } return ( @@ -55,14 +50,16 @@ export default function ConfirmationDialog({ handleClick(false)} + formMethod='dialog' + value='cancel' data-testid='cancel' > {cancelLabel || tx('cancel')} handleClick(true)} + formMethod='dialog' + value='confirm' data-testid='confirm' > {confirmLabel || tx('yes')} diff --git a/packages/frontend/src/components/dialogs/EditProfileDialog/index.tsx b/packages/frontend/src/components/dialogs/EditProfileDialog/index.tsx index b8646aacde..6c2d099464 100644 --- a/packages/frontend/src/components/dialogs/EditProfileDialog/index.tsx +++ b/packages/frontend/src/components/dialogs/EditProfileDialog/index.tsx @@ -1,8 +1,7 @@ import React, { useState } from 'react' -import SettingsStoreInstance, { - SettingsStoreState, -} from '../../../stores/settings' +import SettingsStoreInstance from '../../../stores/settings' +import { useSettingsStore } from '../../../stores/settings' import { BackendRemote } from '../../../backend-com' import { selectedAccountId } from '../../../ScreenController' import { DeltaInput, DeltaTextarea } from '../../Login-Styles' @@ -24,7 +23,6 @@ import styles from './styles.module.scss' import type { DialogProps } from '../../../contexts/DialogContext' type Props = { - settingsStore: SettingsStoreState firstSetup?: boolean } & DialogProps @@ -39,14 +37,13 @@ export default function EditProfileDialog({ onClose, ...props }: Props) { ) } -function EditProfileDialogInner({ - onClose, - settingsStore, - firstSetup = false, -}: Props) { +function EditProfileDialogInner({ onClose, firstSetup = false }: Props) { const tx = useTranslationFunction() const openAlertDialog = useAlertDialog() + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const settingsStore = useSettingsStore()[0]! + const [displayname, setDisplayname] = useState( settingsStore.settings.displayname || '' ) diff --git a/packages/frontend/src/components/dialogs/ViewProfile/index.tsx b/packages/frontend/src/components/dialogs/ViewProfile/index.tsx index 6fb589c300..0fa017ad12 100644 --- a/packages/frontend/src/components/dialogs/ViewProfile/index.tsx +++ b/packages/frontend/src/components/dialogs/ViewProfile/index.tsx @@ -81,16 +81,24 @@ export default function ViewProfile( const onClickViewProfileMenu = useViewProfileMenu(contact) + const handleClose = (result: string) => { + if (result === 'close') { + onClose() + } else if (result === 'cancel') { + onBack && onBack() + } + } + return ( {showMenu && ( diff --git a/packages/frontend/src/components/screens/AccountDeletionScreen/AccountDeletionScreen.tsx b/packages/frontend/src/components/screens/AccountDeletionScreen/AccountDeletionScreen.tsx index be394c8744..55383ebb0e 100644 --- a/packages/frontend/src/components/screens/AccountDeletionScreen/AccountDeletionScreen.tsx +++ b/packages/frontend/src/components/screens/AccountDeletionScreen/AccountDeletionScreen.tsx @@ -54,7 +54,6 @@ export default function AccountDeletionScreen({ {}} width={400} dataTestid='account-deletion-dialog' > diff --git a/packages/frontend/src/components/screens/WelcomeScreen/InstantOnboardingScreen.tsx b/packages/frontend/src/components/screens/WelcomeScreen/InstantOnboardingScreen.tsx index b761aa9b85..0d3afcdbbd 100644 --- a/packages/frontend/src/components/screens/WelcomeScreen/InstantOnboardingScreen.tsx +++ b/packages/frontend/src/components/screens/WelcomeScreen/InstantOnboardingScreen.tsx @@ -9,7 +9,7 @@ import useChat from '../../../hooks/chat/useChat' import useInstantOnboarding from '../../../hooks/useInstantOnboarding' import useTranslationFunction from '../../../hooks/useTranslationFunction' import { DeltaInput } from '../../Login-Styles' -import { DialogBody, DialogContent, DialogHeader } from '../../Dialog' +import Dialog, { DialogBody, DialogContent, DialogHeader } from '../../Dialog' import { ScreenContext } from '../../../contexts/ScreenContext' import { Screens } from '../../../ScreenController' @@ -122,17 +122,22 @@ export default function InstantOnboardingScreen({ openDialog(UseOtherServerDialog) } - const onClickBack = () => { - saveDisplayName() - onCancel() + const onClose = (result: string) => { + if (result === 'cancel') { + saveDisplayName() + onCancel() + } } return ( - <> - + + - + ) } diff --git a/packages/frontend/src/components/screens/WelcomeScreen/OnboardingScreen.tsx b/packages/frontend/src/components/screens/WelcomeScreen/OnboardingScreen.tsx index 3e1ef8eb2f..969e0e69a1 100644 --- a/packages/frontend/src/components/screens/WelcomeScreen/OnboardingScreen.tsx +++ b/packages/frontend/src/components/screens/WelcomeScreen/OnboardingScreen.tsx @@ -6,7 +6,7 @@ import Button from '../../Button' import useDialog from '../../../hooks/dialog/useDialog' import useTranslationFunction from '../../../hooks/useTranslationFunction' import { BackendRemote, EffectfulBackendActions } from '../../../backend-com' -import { DialogBody, DialogContent, DialogHeader } from '../../Dialog' +import Dialog, { DialogBody, DialogContent, DialogHeader } from '../../Dialog' import { getLogger } from '../../../../../shared/logger' import styles from './styles.module.scss' @@ -56,8 +56,20 @@ export default function OnboardingScreen(props: Props) { } } + const onClose = (result: string) => { + if (result === 'cancel') { + onClickBackButton() + } + } + return ( - <> + - + ) } diff --git a/packages/frontend/src/components/screens/WelcomeScreen/index.tsx b/packages/frontend/src/components/screens/WelcomeScreen/index.tsx index 0712dea78a..0db484c167 100644 --- a/packages/frontend/src/components/screens/WelcomeScreen/index.tsx +++ b/packages/frontend/src/components/screens/WelcomeScreen/index.tsx @@ -1,6 +1,5 @@ import React, { useLayoutEffect, useState } from 'react' -import Dialog from '../../Dialog' import ImageBackdrop from '../../ImageBackdrop' import InstantOnboardingScreen from './InstantOnboardingScreen' import OnboardingScreen from './OnboardingScreen' @@ -37,27 +36,20 @@ export default function WelcomeScreen({ selectedAccountId, ...props }: Props) { return ( - - {!showInstantOnboarding ? ( - startInstantOnboardingFlow()} - selectedAccountId={selectedAccountId} - showBackButton={showBackButton} - hasConfiguredAccounts={hasConfiguredAccounts} - {...props} - /> - ) : ( - resetInstantOnboarding()} - /> - )} - + {!showInstantOnboarding ? ( + startInstantOnboardingFlow()} + selectedAccountId={selectedAccountId} + showBackButton={showBackButton} + hasConfiguredAccounts={hasConfiguredAccounts} + {...props} + /> + ) : ( + resetInstantOnboarding()} + /> + )} ) } diff --git a/packages/frontend/src/contexts/DialogContext.tsx b/packages/frontend/src/contexts/DialogContext.tsx index ba2058b5f8..a7111e1609 100644 --- a/packages/frontend/src/contexts/DialogContext.tsx +++ b/packages/frontend/src/contexts/DialogContext.tsx @@ -58,11 +58,17 @@ export const DialogContextProvider = ({ children }: PropsWithChildren<{}>) => { // From this point on we are only interested in the `DialogProps` dialogElement as DialogElementConstructor, { + // set additionalProps first so we don't override onClose, + // cause we need to combine additionalProps.onClose with our own dialog management + ...additionalProps, key: `dialog-${newDialogId}`, - onClose: () => { + onClose: (value: string) => { closeDialog(newDialogId) + + if (value !== 'cancel') { + additionalProps && additionalProps.onClose() + } }, - ...additionalProps, } )