feat(ui): style settings modal

This commit is contained in:
psychedelicious 2024-01-04 21:04:29 +11:00 committed by Kent Keirsey
parent fcf2006502
commit bbca053b48
6 changed files with 268 additions and 227 deletions

View File

@ -16,6 +16,7 @@ export const baseStyle = definePartsStyle(() => ({
header: {
fontWeight: 'semibold',
fontSize: 'lg',
color: 'base.300'
},
closeButton: {
opacity: 0.5,

View File

@ -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) => {
</InputGroup>
<ScrollableContent>
{filteredHotkeyGroups.map((group) => (
<Flex key={group.title} pb={4} flexDir="column">
<Flex
ps={2}
pb={4}
position="sticky"
zIndex={1}
top={0}
bg="base.800"
>
<InvHeading size="sm">{group.title}</InvHeading>
</Flex>
<Flex
key={group.title}
p={4}
borderRadius="base"
bg="base.750"
flexDir="column"
gap={4}
>
<Flex flexDir="column" gap={4}>
{filteredHotkeyGroups.map((group) => (
<StickyScrollable key={group.title} title={group.title}>
{group.hotkeyListItems.map((hotkey, i) => (
<Fragment key={i}>
<HotkeyListItem
@ -151,15 +134,15 @@ const HotkeysModal = ({ children }: HotkeysModalProps) => {
{i < group.hotkeyListItems.length - 1 && <Divider />}
</Fragment>
))}
</Flex>
</Flex>
))}
{!filteredHotkeyGroups.length && (
<IAINoContentFallback
label={t('hotkeys.noHotkeysFound')}
icon={null}
/>
)}
</StickyScrollable>
))}
{!filteredHotkeyGroups.length && (
<IAINoContentFallback
label={t('hotkeys.noHotkeysFound')}
icon={null}
/>
)}
</Flex>
</ScrollableContent>
</InvModalBody>
<InvModalFooter />

View File

@ -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 (
<StyledFlex>
<Heading size="sm">{t('settings.clearIntermediates')}</Heading>
<InvButton
tooltip={
hasPendingItems ? t('settings.clearIntermediatesDisabled') : undefined
}
colorScheme="warning"
onClick={handleClickClearIntermediates}
isLoading={isLoadingClearIntermediates}
isDisabled={!intermediatesCount || hasPendingItems}
>
{t('settings.clearIntermediatesWithCount', {
count: intermediatesCount ?? 0,
})}
</InvButton>
<InvText fontWeight="bold">
{t('settings.clearIntermediatesDesc1')}
</InvText>
<InvText variant="subtext">
{t('settings.clearIntermediatesDesc2')}
</InvText>
<InvText variant="subtext">
{t('settings.clearIntermediatesDesc3')}
</InvText>
</StyledFlex>
);
};
export default memo(SettingsClearIntermediates);

View File

@ -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
>
<InvModalOverlay />
<InvModalContent>
<InvModalContent maxH="80vh" h="80vh">
<InvModalHeader bg="none">{t('common.settingsLabel')}</InvModalHeader>
<InvModalCloseButton />
<InvModalBody>
<Flex gap={4} flexDir="column">
<StyledFlex>
<InvHeading size="sm">{t('settings.general')}</InvHeading>
<InvControl label={t('settings.confirmOnDelete')}>
<InvSwitch
isChecked={shouldConfirmOnDelete}
onChange={handleChangeShouldConfirmOnDelete}
/>
</InvControl>
</StyledFlex>
<StyledFlex>
<InvHeading size="sm">{t('settings.generation')}</InvHeading>
<InvControl
label={t('settings.enableNSFWChecker')}
isDisabled={!isNSFWCheckerAvailable}
>
<InvSwitch
isChecked={shouldUseNSFWChecker}
onChange={handleChangeShouldUseNSFWChecker}
/>
</InvControl>
<InvControl
label={t('settings.enableInvisibleWatermark')}
isDisabled={!isWatermarkerAvailable}
>
<InvSwitch
isChecked={shouldUseWatermarker}
onChange={handleChangeShouldUseWatermarker}
/>
</InvControl>
</StyledFlex>
<StyledFlex>
<InvHeading size="sm">{t('settings.ui')}</InvHeading>
<InvControl label={t('settings.showProgressInViewer')}>
<InvSwitch
isChecked={shouldShowProgressInViewer}
onChange={handleChangeShouldShowProgressInViewer}
/>
</InvControl>
<InvControl label={t('settings.antialiasProgressImages')}>
<InvSwitch
isChecked={shouldAntialiasProgressImage}
onChange={handleChangeShouldAntialiasProgressImage}
/>
</InvControl>
<InvControl
label={t('parameters.useCpuNoise')}
feature="noiseUseCPU"
>
<InvSwitch
isChecked={shouldUseCpuNoise}
onChange={handleChangeShouldUseCpuNoise}
/>
</InvControl>
{shouldShowLocalizationToggle && <SettingsLanguageSelect />}
<InvControl label={t('settings.enableInformationalPopovers')}>
<InvSwitch
isChecked={shouldEnableInformationalPopovers}
onChange={handleChangeShouldEnableInformationalPopovers}
/>
</InvControl>
</StyledFlex>
{shouldShowDeveloperSettings && (
<StyledFlex>
<InvHeading size="sm">{t('settings.developer')}</InvHeading>
<InvControl label={t('settings.shouldLogToConsole')}>
<InvModalBody display="flex" flexDir="column" gap={4}>
<ScrollableContent>
<Flex flexDir="column" gap={4}>
<StickyScrollable title={t('settings.general')}>
<InvControl label={t('settings.confirmOnDelete')}>
<InvSwitch
isChecked={shouldLogToConsole}
onChange={handleLogToConsoleChanged}
isChecked={shouldConfirmOnDelete}
onChange={handleChangeShouldConfirmOnDelete}
/>
</InvControl>
<SettingsLogLevelSelect />
<InvControl label={t('settings.enableImageDebugging')}>
</StickyScrollable>
<StickyScrollable title={t('settings.generation')}>
<InvControl
label={t('settings.enableNSFWChecker')}
isDisabled={!isNSFWCheckerAvailable}
>
<InvSwitch
isChecked={enableImageDebugging}
onChange={handleChangeEnableImageDebugging}
isChecked={shouldUseNSFWChecker}
onChange={handleChangeShouldUseNSFWChecker}
/>
</InvControl>
</StyledFlex>
)}
<InvControl
label={t('settings.enableInvisibleWatermark')}
isDisabled={!isWatermarkerAvailable}
>
<InvSwitch
isChecked={shouldUseWatermarker}
onChange={handleChangeShouldUseWatermarker}
/>
</InvControl>
</StickyScrollable>
{shouldShowClearIntermediates && <SettingsClearIntermediates />}
<StickyScrollable title={t('settings.ui')}>
<InvControl label={t('settings.showProgressInViewer')}>
<InvSwitch
isChecked={shouldShowProgressInViewer}
onChange={handleChangeShouldShowProgressInViewer}
/>
</InvControl>
<InvControl label={t('settings.antialiasProgressImages')}>
<InvSwitch
isChecked={shouldAntialiasProgressImage}
onChange={handleChangeShouldAntialiasProgressImage}
/>
</InvControl>
<InvControl
label={t('parameters.useCpuNoise')}
feature="noiseUseCPU"
>
<InvSwitch
isChecked={shouldUseCpuNoise}
onChange={handleChangeShouldUseCpuNoise}
/>
</InvControl>
{shouldShowLocalizationToggle && <SettingsLanguageSelect />}
<InvControl label={t('settings.enableInformationalPopovers')}>
<InvSwitch
isChecked={shouldEnableInformationalPopovers}
onChange={handleChangeShouldEnableInformationalPopovers}
/>
</InvControl>
</StickyScrollable>
<StyledFlex>
<InvHeading size="sm">{t('settings.resetWebUI')}</InvHeading>
<InvButton colorScheme="error" onClick={handleClickResetWebUI}>
{t('settings.resetWebUI')}
</InvButton>
{shouldShowResetWebUiText && (
<>
<InvText variant="subtext">
{t('settings.resetWebUIDesc1')}
</InvText>
<InvText variant="subtext">
{t('settings.resetWebUIDesc2')}
</InvText>
</>
{shouldShowDeveloperSettings && (
<StickyScrollable title={t('settings.developer')}>
<InvControl label={t('settings.shouldLogToConsole')}>
<InvSwitch
isChecked={shouldLogToConsole}
onChange={handleLogToConsoleChanged}
/>
</InvControl>
<SettingsLogLevelSelect />
<InvControl label={t('settings.enableImageDebugging')}>
<InvSwitch
isChecked={enableImageDebugging}
onChange={handleChangeEnableImageDebugging}
/>
</InvControl>
</StickyScrollable>
)}
</StyledFlex>
</Flex>
{shouldShowClearIntermediates && (
<StickyScrollable title={t('settings.clearIntermediates')}>
<InvButton
tooltip={
hasPendingItems
? t('settings.clearIntermediatesDisabled')
: undefined
}
colorScheme="warning"
onClick={clearIntermediates}
isLoading={isLoadingClearIntermediates}
isDisabled={!intermediatesCount || hasPendingItems}
>
{t('settings.clearIntermediatesWithCount', {
count: intermediatesCount ?? 0,
})}
</InvButton>
<InvText fontWeight="bold">
{t('settings.clearIntermediatesDesc1')}
</InvText>
<InvText variant="subtext">
{t('settings.clearIntermediatesDesc2')}
</InvText>
<InvText variant="subtext">
{t('settings.clearIntermediatesDesc3')}
</InvText>
</StickyScrollable>
)}
<StickyScrollable title={t('settings.resetWebUI')}>
<InvButton
colorScheme="error"
onClick={handleClickResetWebUI}
>
{t('settings.resetWebUI')}
</InvButton>
{shouldShowResetWebUiText && (
<>
<InvText variant="subtext">
{t('settings.resetWebUIDesc1')}
</InvText>
<InvText variant="subtext">
{t('settings.resetWebUIDesc2')}
</InvText>
</>
)}
</StickyScrollable>
</Flex>
</ScrollableContent>
</InvModalBody>
<InvModalFooter>
<InvButton onClick={onSettingsModalClose}>
{t('common.close')}
</InvButton>
</InvModalFooter>
<InvModalFooter />
</InvModalContent>
</InvModal>

View File

@ -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 };
};

View File

@ -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 (
<Flex ps={2} pb={4} position="sticky" zIndex={1} top={0} bg="base.800">
<InvHeading size="sm">{props.title}</InvHeading>
</Flex>
);
}
);
StickyScrollableHeading.displayName = 'StickyScrollableHeading';
export type StickyScrollableContentProps = PropsWithChildren;
export const StickyScrollableContent = memo(
(props: StickyScrollableContentProps) => {
return (
<Flex p={4} borderRadius="base" bg="base.750" flexDir="column" gap={4}>
{props.children}
</Flex>
);
}
);
StickyScrollableContent.displayName = 'StickyScrollableContent';
export type StickyScrollableProps = PropsWithChildren<{
title: string;
}>;
export const StickyScrollable = memo((props: StickyScrollableProps) => {
return (
<Flex key={props.title} flexDir="column">
<StickyScrollableHeading title={props.title} />
<StickyScrollableContent>{props.children}</StickyScrollableContent>
</Flex>
);
});
StickyScrollable.displayName = 'StickyScrollable';