From 9f742a669e3d90c1ab005c3fbcffdd9fe9866d8e Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Wed, 28 Aug 2024 16:51:38 +1000 Subject: [PATCH] feat(ui): split settings modal --- .../frontend/web/src/app/components/App.tsx | 4 + .../SettingsModal/RefreshAfterResetModal.tsx | 72 +++++ .../components/SettingsModal/SettingsMenu.tsx | 11 +- .../SettingsModal/SettingsModal.tsx | 282 ++++++++---------- 4 files changed, 204 insertions(+), 165 deletions(-) create mode 100644 invokeai/frontend/web/src/features/system/components/SettingsModal/RefreshAfterResetModal.tsx diff --git a/invokeai/frontend/web/src/app/components/App.tsx b/invokeai/frontend/web/src/app/components/App.tsx index 07f484d200..68acdb4aec 100644 --- a/invokeai/frontend/web/src/app/components/App.tsx +++ b/invokeai/frontend/web/src/app/components/App.tsx @@ -17,6 +17,8 @@ import { useStarterModelsToast } from 'features/modelManagerV2/hooks/useStarterM import { ClearQueueConfirmationsAlertDialog } from 'features/queue/components/ClearQueueConfirmationAlertDialog'; import { StylePresetModal } from 'features/stylePresets/components/StylePresetForm/StylePresetModal'; import { activeStylePresetIdChanged } from 'features/stylePresets/store/stylePresetSlice'; +import RefreshAfterResetModal from 'features/system/components/SettingsModal/RefreshAfterResetModal'; +import SettingsModal from 'features/system/components/SettingsModal/SettingsModal'; import { configChanged } from 'features/system/store/configSlice'; import { selectLanguage } from 'features/system/store/systemSelectors'; import { AppContent } from 'features/ui/components/AppContent'; @@ -135,6 +137,8 @@ const App = ({ + + ); }; diff --git a/invokeai/frontend/web/src/features/system/components/SettingsModal/RefreshAfterResetModal.tsx b/invokeai/frontend/web/src/features/system/components/SettingsModal/RefreshAfterResetModal.tsx new file mode 100644 index 0000000000..2530b705d1 --- /dev/null +++ b/invokeai/frontend/web/src/features/system/components/SettingsModal/RefreshAfterResetModal.tsx @@ -0,0 +1,72 @@ +import { + Flex, + Modal, + ModalBody, + ModalContent, + ModalFooter, + ModalHeader, + ModalOverlay, + Text, +} from '@invoke-ai/ui-library'; +import { useStore } from '@nanostores/react'; +import { buildUseBoolean } from 'common/hooks/useBoolean'; +import { atom } from 'nanostores'; +import { memo, useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; + +const $refreshAfterResetModalState = atom(false); +export const useRefreshAfterResetModal = buildUseBoolean($refreshAfterResetModalState); + +const RefreshAfterResetModal = () => { + const { t } = useTranslation(); + const [countdown, setCountdown] = useState(3); + + const refreshModal = useRefreshAfterResetModal(); + const isOpen = useStore(refreshModal.$boolean); + + useEffect(() => { + if (!isOpen) { + return; + } + const i = window.setInterval(() => setCountdown((prev) => prev - 1), 1000); + return () => { + window.clearInterval(i); + }; + }, [isOpen]); + + useEffect(() => { + if (countdown <= 0) { + window.location.reload(); + } + }, [countdown]); + + return ( + <> + + + + + + + + + {t('settings.resetComplete')} {t('settings.reloadingIn')} {countdown}... + + + + + + + + + ); +}; + +export default memo(RefreshAfterResetModal); diff --git a/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsMenu.tsx b/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsMenu.tsx index 33455e50fa..82c8c264af 100644 --- a/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsMenu.tsx +++ b/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsMenu.tsx @@ -25,12 +25,13 @@ import { } from 'react-icons/pi'; import { RiDiscordFill, RiGithubFill, RiSettings4Line } from 'react-icons/ri'; -import SettingsModal from './SettingsModal'; +import { useSettingsModal } from './SettingsModal'; import { SettingsUpsellMenuItem } from './SettingsUpsellMenuItem'; const SettingsMenu = () => { const { t } = useTranslation(); const { isOpen, onOpen, onClose } = useDisclosure(); useGlobalMenuClose(onClose); + const settingsModal = useSettingsModal(); const isBugLinkEnabled = useFeatureStatus('bugLink'); const isDiscordLinkEnabled = useFeatureStatus('discordLink'); @@ -75,11 +76,9 @@ const SettingsMenu = () => { {t('common.hotkeysLabel')} - - }> - {t('common.settingsLabel')} - - + }> + {t('common.settingsLabel')} + diff --git a/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsModal.tsx b/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsModal.tsx index b9569a1a5c..4ee5850654 100644 --- a/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsModal.tsx +++ b/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsModal.tsx @@ -13,13 +13,15 @@ import { ModalOverlay, Switch, Text, - useDisclosure, } from '@invoke-ai/ui-library'; +import { useStore } from '@nanostores/react'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover'; import ScrollableContent from 'common/components/OverlayScrollbars/ScrollableContent'; +import { buildUseBoolean } from 'common/hooks/useBoolean'; import { useClearStorage } from 'common/hooks/useClearStorage'; import { selectShouldUseCPUNoise, shouldUseCpuNoiseChanged } from 'features/controlLayers/store/paramsSlice'; +import { useRefreshAfterResetModal } from 'features/system/components/SettingsModal/RefreshAfterResetModal'; import { SettingsDeveloperLogIsEnabled } from 'features/system/components/SettingsModal/SettingsDeveloperLogIsEnabled'; import { SettingsDeveloperLogLevel } from 'features/system/components/SettingsModal/SettingsDeveloperLogLevel'; import { SettingsDeveloperLogNamespaces } from 'features/system/components/SettingsModal/SettingsDeveloperLogNamespaces'; @@ -40,8 +42,9 @@ import { } from 'features/system/store/systemSlice'; import { selectShouldShowProgressInViewer } from 'features/ui/store/uiSelectors'; import { setShouldShowProgressInViewer } from 'features/ui/store/uiSlice'; -import type { ChangeEvent, ReactElement } from 'react'; -import { cloneElement, memo, useCallback, useEffect, useState } from 'react'; +import { atom } from 'nanostores'; +import type { ChangeEvent } from 'react'; +import { memo, useCallback, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; import { useGetAppConfigQuery } from 'services/api/endpoints/appInfo'; @@ -54,27 +57,29 @@ type ConfigOptions = { shouldShowLocalizationToggle?: boolean; }; +const defaultConfig: ConfigOptions = { + shouldShowDeveloperSettings: true, + shouldShowResetWebUiText: true, + shouldShowClearIntermediates: true, + shouldShowLocalizationToggle: true, +}; + type SettingsModalProps = { - /* The button to open the Settings Modal */ - children: ReactElement; config?: ConfigOptions; }; -const SettingsModal = ({ children, config }: SettingsModalProps) => { +const $settingsModal = atom(false); +export const useSettingsModal = buildUseBoolean($settingsModal); + +const SettingsModal = ({ config = defaultConfig }: SettingsModalProps) => { const dispatch = useAppDispatch(); const { t } = useTranslation(); - const [countdown, setCountdown] = useState(3); - - const shouldShowDeveloperSettings = config?.shouldShowDeveloperSettings ?? true; - const shouldShowResetWebUiText = config?.shouldShowResetWebUiText ?? true; - const shouldShowClearIntermediates = config?.shouldShowClearIntermediates ?? true; - const shouldShowLocalizationToggle = config?.shouldShowLocalizationToggle ?? true; useEffect(() => { - if (!shouldShowDeveloperSettings) { + if (!config?.shouldShowDeveloperSettings) { dispatch(logIsEnabledChanged(false)); } - }, [shouldShowDeveloperSettings, dispatch]); + }, [dispatch, config?.shouldShowDeveloperSettings]); const { isNSFWCheckerAvailable, isWatermarkerAvailable } = useGetAppConfigQuery(undefined, { selectFromResult: ({ data }) => ({ @@ -89,11 +94,10 @@ const SettingsModal = ({ children, config }: SettingsModalProps) => { intermediatesCount, isLoading: isLoadingClearIntermediates, refetchIntermediatesCount, - } = useClearIntermediates(shouldShowClearIntermediates); - - const { isOpen: isSettingsModalOpen, onOpen: _onSettingsModalOpen, onClose: onSettingsModalClose } = useDisclosure(); - - const { isOpen: isRefreshModalOpen, onOpen: onRefreshModalOpen, onClose: onRefreshModalClose } = useDisclosure(); + } = useClearIntermediates(Boolean(config?.shouldShowClearIntermediates)); + const settingsModal = useSettingsModal(); + const settingsModalIsOpen = useStore(settingsModal.$boolean); + const refreshModal = useRefreshAfterResetModal(); const shouldUseCpuNoise = useAppSelector(selectShouldUseCPUNoise); const shouldConfirmOnDelete = useAppSelector(selectSystemShouldConfirmOnDelete); @@ -105,25 +109,17 @@ const SettingsModal = ({ children, config }: SettingsModalProps) => { const clearStorage = useClearStorage(); - const handleOpenSettingsModel = useCallback(() => { - if (shouldShowClearIntermediates) { + useEffect(() => { + if (settingsModalIsOpen && Boolean(config?.shouldShowClearIntermediates)) { refetchIntermediatesCount(); } - _onSettingsModalOpen(); - }, [_onSettingsModalOpen, refetchIntermediatesCount, shouldShowClearIntermediates]); + }, [config?.shouldShowClearIntermediates, refetchIntermediatesCount, settingsModalIsOpen]); const handleClickResetWebUI = useCallback(() => { clearStorage(); - onSettingsModalClose(); - onRefreshModalOpen(); - setInterval(() => setCountdown((prev) => prev - 1), 1000); - }, [clearStorage, onSettingsModalClose, onRefreshModalOpen]); - - useEffect(() => { - if (countdown <= 0) { - window.location.reload(); - } - }, [countdown]); + settingsModal.setFalse(); + refreshModal.setTrue(); + }, [clearStorage, settingsModal, refreshModal]); const handleChangeShouldConfirmOnDelete = useCallback( (e: ChangeEvent) => { @@ -169,139 +165,107 @@ const SettingsModal = ({ children, config }: SettingsModalProps) => { ); return ( - <> - {cloneElement(children, { - onClick: handleOpenSettingsModel, - })} + + + + {t('common.settingsLabel')} + + + + + + + + {t('settings.confirmOnDelete')} + + + - - - - {t('common.settingsLabel')} - - - - - - - - {t('settings.confirmOnDelete')} - - + + + {t('settings.enableNSFWChecker')} + + + + {t('settings.enableInvisibleWatermark')} + + + + + + + {t('settings.showProgressInViewer')} + + + + {t('settings.antialiasProgressImages')} + + + + + {t('parameters.useCpuNoise')} + + + + {Boolean(config?.shouldShowLocalizationToggle) && } + + {t('settings.enableInformationalPopovers')} + + + + + {Boolean(config?.shouldShowDeveloperSettings) && ( + + + + + )} - - - {t('settings.enableNSFWChecker')} - - - - {t('settings.enableInvisibleWatermark')} - - - - - - - {t('settings.showProgressInViewer')} - - - - {t('settings.antialiasProgressImages')} - - - - - {t('parameters.useCpuNoise')} - - - - {shouldShowLocalizationToggle && } - - {t('settings.enableInformationalPopovers')} - - - - - {shouldShowDeveloperSettings && ( - - - - - - )} - - {shouldShowClearIntermediates && ( - - - {t('settings.clearIntermediatesDesc1')} - {t('settings.clearIntermediatesDesc2')} - {t('settings.clearIntermediatesDesc3')} - - )} - - - - {shouldShowResetWebUiText && ( - <> - {t('settings.resetWebUIDesc1')} - {t('settings.resetWebUIDesc2')} - - )} + {t('settings.clearIntermediatesDesc1')} + {t('settings.clearIntermediatesDesc2')} + {t('settings.clearIntermediatesDesc3')} - - - - + )} - - - - - - - - - - - - - {t('settings.resetComplete')} {t('settings.reloadingIn')} {countdown}... - - + + + {Boolean(config?.shouldShowResetWebUiText) && ( + <> + {t('settings.resetWebUIDesc1')} + {t('settings.resetWebUIDesc2')} + + )} + + - - - - - + + + + + + ); };