diff --git a/invokeai/frontend/web/src/common/components/InvModal/theme.ts b/invokeai/frontend/web/src/common/components/InvModal/theme.ts index 3596feb7a2..af587d53b7 100644 --- a/invokeai/frontend/web/src/common/components/InvModal/theme.ts +++ b/invokeai/frontend/web/src/common/components/InvModal/theme.ts @@ -16,6 +16,7 @@ export const baseStyle = definePartsStyle(() => ({ header: { fontWeight: 'semibold', fontSize: 'lg', + color: 'base.300' }, closeButton: { opacity: 0.5, diff --git a/invokeai/frontend/web/src/features/system/components/HotkeysModal/HotkeysModal.tsx b/invokeai/frontend/web/src/features/system/components/HotkeysModal/HotkeysModal.tsx index 285e8a76b1..b0ca291cbb 100644 --- a/invokeai/frontend/web/src/features/system/components/HotkeysModal/HotkeysModal.tsx +++ b/invokeai/frontend/web/src/features/system/components/HotkeysModal/HotkeysModal.tsx @@ -7,7 +7,6 @@ import { useDisclosure, } from '@chakra-ui/react'; import { IAINoContentFallback } from 'common/components/IAIImageFallback'; -import { InvHeading } from 'common/components/InvHeading/wrapper'; import { InvIconButton } from 'common/components/InvIconButton/InvIconButton'; import { InvInput } from 'common/components/InvInput/InvInput'; import { @@ -22,6 +21,7 @@ import { import ScrollableContent from 'common/components/OverlayScrollbars/ScrollableContent'; import type { HotkeyGroup } from 'features/system/components/HotkeysModal/useHotkeyData'; import { useHotkeyData } from 'features/system/components/HotkeysModal/useHotkeyData'; +import { StickyScrollable } from 'features/system/components/StickyScrollable'; import type { ChangeEventHandler, ReactElement } from 'react'; import { cloneElement, @@ -121,26 +121,9 @@ const HotkeysModal = ({ children }: HotkeysModalProps) => { - {filteredHotkeyGroups.map((group) => ( - - - {group.title} - - + + {filteredHotkeyGroups.map((group) => ( + {group.hotkeyListItems.map((hotkey, i) => ( { {i < group.hotkeyListItems.length - 1 && } ))} - - - ))} - {!filteredHotkeyGroups.length && ( - - )} + + ))} + {!filteredHotkeyGroups.length && ( + + )} + diff --git a/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsClearIntermediates.tsx b/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsClearIntermediates.tsx deleted file mode 100644 index b67ccc9099..0000000000 --- a/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsClearIntermediates.tsx +++ /dev/null @@ -1,93 +0,0 @@ -import { Heading } from '@chakra-ui/react'; -import { useAppDispatch } from 'app/store/storeHooks'; -import { InvButton } from 'common/components/InvButton/InvButton'; -import { InvText } from 'common/components/InvText/wrapper'; -import { resetCanvas } from 'features/canvas/store/canvasSlice'; -import { controlAdaptersReset } from 'features/controlAdapters/store/controlAdaptersSlice'; -import { addToast } from 'features/system/store/systemSlice'; -import { memo, useCallback } from 'react'; -import { useTranslation } from 'react-i18next'; -import { - useClearIntermediatesMutation, - useGetIntermediatesCountQuery, -} from 'services/api/endpoints/images'; -import { useGetQueueStatusQuery } from 'services/api/endpoints/queue'; - -import StyledFlex from './StyledFlex'; - -const SettingsClearIntermediates = () => { - const { t } = useTranslation(); - const dispatch = useAppDispatch(); - - const { data: intermediatesCount } = useGetIntermediatesCountQuery( - undefined, - { - refetchOnMountOrArgChange: true, - } - ); - - const [clearIntermediates, { isLoading: isLoadingClearIntermediates }] = - useClearIntermediatesMutation(); - - const { data: queueStatus } = useGetQueueStatusQuery(); - const hasPendingItems = - queueStatus && - (queueStatus.queue.in_progress > 0 || queueStatus.queue.pending > 0); - - const handleClickClearIntermediates = useCallback(() => { - if (hasPendingItems) { - return; - } - - clearIntermediates() - .unwrap() - .then((clearedCount) => { - dispatch(controlAdaptersReset()); - dispatch(resetCanvas()); - dispatch( - addToast({ - title: t('settings.intermediatesCleared', { count: clearedCount }), - status: 'info', - }) - ); - }) - .catch(() => { - dispatch( - addToast({ - title: t('settings.intermediatesClearedFailed'), - status: 'error', - }) - ); - }); - }, [t, clearIntermediates, dispatch, hasPendingItems]); - - return ( - - {t('settings.clearIntermediates')} - - {t('settings.clearIntermediatesWithCount', { - count: intermediatesCount ?? 0, - })} - - - {t('settings.clearIntermediatesDesc1')} - - - {t('settings.clearIntermediatesDesc2')} - - - {t('settings.clearIntermediatesDesc3')} - - - ); -}; - -export default memo(SettingsClearIntermediates); 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 4e9a0f2b61..f524450819 100644 --- a/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsModal.tsx +++ b/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsModal.tsx @@ -4,7 +4,6 @@ import { stateSelector } from 'app/store/store'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { InvButton } from 'common/components/InvButton/InvButton'; import { InvControl } from 'common/components/InvControl/InvControl'; -import { InvHeading } from 'common/components/InvHeading/wrapper'; import { InvModal, InvModalBody, @@ -16,8 +15,11 @@ import { } from 'common/components/InvModal/wrapper'; import { InvSwitch } from 'common/components/InvSwitch/wrapper'; import { InvText } from 'common/components/InvText/wrapper'; +import ScrollableContent from 'common/components/OverlayScrollbars/ScrollableContent'; import { useClearStorage } from 'common/hooks/useClearStorage'; import { shouldUseCpuNoiseChanged } from 'features/parameters/store/generationSlice'; +import { useClearIntermediates } from 'features/system/components/SettingsModal/useClearIntermediates'; +import { StickyScrollable } from 'features/system/components/StickyScrollable'; import { setEnableImageDebugging, setShouldConfirmOnDelete, @@ -33,10 +35,8 @@ import { cloneElement, memo, useCallback, useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useGetAppConfigQuery } from 'services/api/endpoints/appInfo'; -import SettingsClearIntermediates from './SettingsClearIntermediates'; import { SettingsLanguageSelect } from './SettingsLanguageSelect'; import { SettingsLogLevelSelect } from './SettingsLogLevelSelect'; -import StyledFlex from './StyledFlex'; const selector = createMemoizedSelector( [stateSelector], @@ -110,6 +110,13 @@ const SettingsModal = ({ children, config }: SettingsModalProps) => { }), }); + const { + clearIntermediates, + hasPendingItems, + intermediatesCount, + isLoading: isLoadingClearIntermediates, + } = useClearIntermediates(); + const { isOpen: isSettingsModalOpen, onOpen: onSettingsModalOpen, @@ -218,120 +225,143 @@ const SettingsModal = ({ children, config }: SettingsModalProps) => { isCentered > - + {t('common.settingsLabel')} - - - - {t('settings.general')} - - - - - - - {t('settings.generation')} - - - - - - - - - - {t('settings.ui')} - - - - - - - - - - {shouldShowLocalizationToggle && } - - - - - - {shouldShowDeveloperSettings && ( - - {t('settings.developer')} - + + + + + - - + + + + - - )} + + + + - {shouldShowClearIntermediates && } + + + + + + + + + + + {shouldShowLocalizationToggle && } + + + + - - {t('settings.resetWebUI')} - - {t('settings.resetWebUI')} - - {shouldShowResetWebUiText && ( - <> - - {t('settings.resetWebUIDesc1')} - - - {t('settings.resetWebUIDesc2')} - - + {shouldShowDeveloperSettings && ( + + + + + + + + + )} - - + + {shouldShowClearIntermediates && ( + + + {t('settings.clearIntermediatesWithCount', { + count: intermediatesCount ?? 0, + })} + + + {t('settings.clearIntermediatesDesc1')} + + + {t('settings.clearIntermediatesDesc2')} + + + {t('settings.clearIntermediatesDesc3')} + + + )} + + + + {t('settings.resetWebUI')} + + {shouldShowResetWebUiText && ( + <> + + {t('settings.resetWebUIDesc1')} + + + {t('settings.resetWebUIDesc2')} + + + )} + + + - - - {t('common.close')} - - + diff --git a/invokeai/frontend/web/src/features/system/components/SettingsModal/useClearIntermediates.ts b/invokeai/frontend/web/src/features/system/components/SettingsModal/useClearIntermediates.ts new file mode 100644 index 0000000000..90f5dee8b0 --- /dev/null +++ b/invokeai/frontend/web/src/features/system/components/SettingsModal/useClearIntermediates.ts @@ -0,0 +1,71 @@ +import { useAppDispatch } from 'app/store/storeHooks'; +import { resetCanvas } from 'features/canvas/store/canvasSlice'; +import { controlAdaptersReset } from 'features/controlAdapters/store/controlAdaptersSlice'; +import { addToast } from 'features/system/store/systemSlice'; +import { useCallback, useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; +import { + useClearIntermediatesMutation, + useGetIntermediatesCountQuery, +} from 'services/api/endpoints/images'; +import { useGetQueueStatusQuery } from 'services/api/endpoints/queue'; + +export type UseClearIntermediatesReturn = { + intermediatesCount: number | undefined; + clearIntermediates: () => void; + isLoading: boolean; + hasPendingItems: boolean; +}; + +export const useClearIntermediates = (): UseClearIntermediatesReturn => { + const { t } = useTranslation(); + const dispatch = useAppDispatch(); + + const { data: intermediatesCount } = useGetIntermediatesCountQuery( + undefined, + { + refetchOnMountOrArgChange: true, + } + ); + + const [_clearIntermediates, { isLoading }] = useClearIntermediatesMutation(); + + const { data: queueStatus } = useGetQueueStatusQuery(); + const hasPendingItems = useMemo( + () => + Boolean( + queueStatus && + (queueStatus.queue.in_progress > 0 || queueStatus.queue.pending > 0) + ), + [queueStatus] + ); + + const clearIntermediates = useCallback(() => { + if (hasPendingItems) { + return; + } + + _clearIntermediates() + .unwrap() + .then((clearedCount) => { + dispatch(controlAdaptersReset()); + dispatch(resetCanvas()); + dispatch( + addToast({ + title: t('settings.intermediatesCleared', { count: clearedCount }), + status: 'info', + }) + ); + }) + .catch(() => { + dispatch( + addToast({ + title: t('settings.intermediatesClearedFailed'), + status: 'error', + }) + ); + }); + }, [t, _clearIntermediates, dispatch, hasPendingItems]); + + return { intermediatesCount, clearIntermediates, isLoading, hasPendingItems }; +}; diff --git a/invokeai/frontend/web/src/features/system/components/StickyScrollable.tsx b/invokeai/frontend/web/src/features/system/components/StickyScrollable.tsx new file mode 100644 index 0000000000..87c81f7e8a --- /dev/null +++ b/invokeai/frontend/web/src/features/system/components/StickyScrollable.tsx @@ -0,0 +1,49 @@ +import { Flex } from '@chakra-ui/layout'; +import { InvHeading } from 'common/components/InvHeading/wrapper'; +import type { PropsWithChildren } from 'react'; +import { memo } from 'react'; + +export type StickyScrollableHeadingProps = { + title: string; +}; + +export const StickyScrollableHeading = memo( + (props: StickyScrollableHeadingProps) => { + return ( + + {props.title} + + ); + } +); + +StickyScrollableHeading.displayName = 'StickyScrollableHeading'; + +export type StickyScrollableContentProps = PropsWithChildren; + +export const StickyScrollableContent = memo( + (props: StickyScrollableContentProps) => { + return ( + + {props.children} + + ); + } +); + +StickyScrollableContent.displayName = 'StickyScrollableContent'; + +export type StickyScrollableProps = PropsWithChildren<{ + title: string; +}>; + +export const StickyScrollable = memo((props: StickyScrollableProps) => { + return ( + + + {props.children} + + ); +}); + +StickyScrollable.displayName = 'StickyScrollable';