feat(ui): improve accordion ux (#3647)

- Accordions now may be opened or closed regardless of whether or not
their contents are enabled or active
- Accordions have a short text indicator alerting the user if their
contents are enabled, either a simple `Enabled` or, for accordions like
LoRA or ControlNet, `X Active` if any are active



https://github.com/invoke-ai/InvokeAI/assets/4822129/43db63bd-7ef3-43f2-8dad-59fc7200af2e
This commit is contained in:
blessedcoolant 2023-07-05 20:57:23 +12:00 committed by GitHub
commit 07d7959dc0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 457 additions and 242 deletions

View File

@ -4,22 +4,25 @@ import {
Collapse,
Flex,
Spacer,
Switch,
Text,
useColorMode,
useDisclosure,
} from '@chakra-ui/react';
import { AnimatePresence, motion } from 'framer-motion';
import { PropsWithChildren, memo } from 'react';
import { mode } from 'theme/util/mode';
export type IAIToggleCollapseProps = PropsWithChildren & {
label: string;
isOpen: boolean;
onToggle: () => void;
withSwitch?: boolean;
activeLabel?: string;
defaultIsOpen?: boolean;
};
const IAICollapse = (props: IAIToggleCollapseProps) => {
const { label, isOpen, onToggle, children, withSwitch = false } = props;
const { label, activeLabel, children, defaultIsOpen = false } = props;
const { isOpen, onToggle } = useDisclosure({ defaultIsOpen });
const { colorMode } = useColorMode();
return (
<Box>
<Flex
@ -28,6 +31,7 @@ const IAICollapse = (props: IAIToggleCollapseProps) => {
alignItems: 'center',
p: 2,
px: 4,
gap: 2,
borderTopRadius: 'base',
borderBottomRadius: isOpen ? 0 : 'base',
bg: isOpen
@ -48,9 +52,31 @@ const IAICollapse = (props: IAIToggleCollapseProps) => {
}}
>
{label}
<AnimatePresence>
{activeLabel && (
<motion.div
key="statusText"
initial={{
opacity: 0,
}}
animate={{
opacity: 1,
transition: { duration: 0.1 },
}}
exit={{
opacity: 0,
transition: { duration: 0.1 },
}}
>
<Text
sx={{ color: 'accent.500', _dark: { color: 'accent.300' } }}
>
{activeLabel}
</Text>
</motion.div>
)}
</AnimatePresence>
<Spacer />
{withSwitch && <Switch isChecked={isOpen} pointerEvents="none" />}
{!withSwitch && (
<ChevronUpIcon
sx={{
w: '1rem',
@ -60,7 +86,6 @@ const IAICollapse = (props: IAIToggleCollapseProps) => {
transitionDuration: 'normal',
}}
/>
)}
</Flex>
<Collapse in={isOpen} animateOpacity style={{ overflow: 'unset' }}>
<Box

View File

@ -36,7 +36,6 @@ const IAISwitch = (props: Props) => {
isDisabled={isDisabled}
width={width}
display="flex"
gap={4}
alignItems="center"
{...formControlProps}
>
@ -47,6 +46,7 @@ const IAISwitch = (props: Props) => {
sx={{
cursor: isDisabled ? 'not-allowed' : 'pointer',
...formLabelProps?.sx,
pe: 4,
}}
{...formLabelProps}
>

View File

@ -0,0 +1,36 @@
import { createSelector } from '@reduxjs/toolkit';
import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import IAISwitch from 'common/components/IAISwitch';
import { isControlNetEnabledToggled } from 'features/controlNet/store/controlNetSlice';
import { useCallback } from 'react';
const selector = createSelector(
stateSelector,
(state) => {
const { isEnabled } = state.controlNet;
return { isEnabled };
},
defaultSelectorOptions
);
const ParamControlNetFeatureToggle = () => {
const { isEnabled } = useAppSelector(selector);
const dispatch = useAppDispatch();
const handleChange = useCallback(() => {
dispatch(isControlNetEnabledToggled());
}, [dispatch]);
return (
<IAISwitch
label="Enable ControlNet"
isChecked={isEnabled}
onChange={handleChange}
/>
);
};
export default ParamControlNetFeatureToggle;

View File

@ -0,0 +1,15 @@
import { filter } from 'lodash-es';
import { ControlNetConfig } from '../store/controlNetSlice';
export const getValidControlNets = (
controlNets: Record<string, ControlNetConfig>
) => {
const validControlNets = filter(
controlNets,
(c) =>
c.isEnabled &&
(Boolean(c.processedControlImage) ||
(c.processorType === 'none' && Boolean(c.controlImage)))
);
return validControlNets;
};

View File

@ -1,40 +1,30 @@
import { Flex } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit';
import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import IAICollapse from 'common/components/IAICollapse';
import { useCallback } from 'react';
import { isEnabledToggled } from '../store/slice';
import ParamDynamicPromptsMaxPrompts from './ParamDynamicPromptsMaxPrompts';
import ParamDynamicPromptsCombinatorial from './ParamDynamicPromptsCombinatorial';
import { Flex } from '@chakra-ui/react';
import ParamDynamicPromptsToggle from './ParamDynamicPromptsEnabled';
import ParamDynamicPromptsMaxPrompts from './ParamDynamicPromptsMaxPrompts';
const selector = createSelector(
stateSelector,
(state) => {
const { isEnabled } = state.dynamicPrompts;
return { isEnabled };
return { activeLabel: isEnabled ? 'Enabled' : undefined };
},
defaultSelectorOptions
);
const ParamDynamicPromptsCollapse = () => {
const dispatch = useAppDispatch();
const { isEnabled } = useAppSelector(selector);
const handleToggleIsEnabled = useCallback(() => {
dispatch(isEnabledToggled());
}, [dispatch]);
const { activeLabel } = useAppSelector(selector);
return (
<IAICollapse
isOpen={isEnabled}
onToggle={handleToggleIsEnabled}
label="Dynamic Prompts"
withSwitch
>
<IAICollapse label="Dynamic Prompts" activeLabel={activeLabel}>
<Flex sx={{ gap: 2, flexDir: 'column' }}>
<ParamDynamicPromptsToggle />
<ParamDynamicPromptsCombinatorial />
<ParamDynamicPromptsMaxPrompts />
</Flex>

View File

@ -1,23 +1,23 @@
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { combinatorialToggled } from '../store/slice';
import { createSelector } from '@reduxjs/toolkit';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import { useCallback } from 'react';
import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import IAISwitch from 'common/components/IAISwitch';
import { useCallback } from 'react';
import { combinatorialToggled } from '../store/slice';
const selector = createSelector(
stateSelector,
(state) => {
const { combinatorial } = state.dynamicPrompts;
const { combinatorial, isEnabled } = state.dynamicPrompts;
return { combinatorial };
return { combinatorial, isDisabled: !isEnabled };
},
defaultSelectorOptions
);
const ParamDynamicPromptsCombinatorial = () => {
const { combinatorial } = useAppSelector(selector);
const { combinatorial, isDisabled } = useAppSelector(selector);
const dispatch = useAppDispatch();
const handleChange = useCallback(() => {
@ -26,6 +26,7 @@ const ParamDynamicPromptsCombinatorial = () => {
return (
<IAISwitch
isDisabled={isDisabled}
label="Combinatorial Generation"
isChecked={combinatorial}
onChange={handleChange}

View File

@ -0,0 +1,36 @@
import { createSelector } from '@reduxjs/toolkit';
import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import IAISwitch from 'common/components/IAISwitch';
import { useCallback } from 'react';
import { isEnabledToggled } from '../store/slice';
const selector = createSelector(
stateSelector,
(state) => {
const { isEnabled } = state.dynamicPrompts;
return { isEnabled };
},
defaultSelectorOptions
);
const ParamDynamicPromptsToggle = () => {
const dispatch = useAppDispatch();
const { isEnabled } = useAppSelector(selector);
const handleToggleIsEnabled = useCallback(() => {
dispatch(isEnabledToggled());
}, [dispatch]);
return (
<IAISwitch
label="Enable Dynamic Prompts"
isChecked={isEnabled}
onChange={handleToggleIsEnabled}
/>
);
};
export default ParamDynamicPromptsToggle;

View File

@ -1,25 +1,31 @@
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAISlider from 'common/components/IAISlider';
import { maxPromptsChanged, maxPromptsReset } from '../store/slice';
import { createSelector } from '@reduxjs/toolkit';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import { useCallback } from 'react';
import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import IAISlider from 'common/components/IAISlider';
import { useCallback } from 'react';
import { maxPromptsChanged, maxPromptsReset } from '../store/slice';
const selector = createSelector(
stateSelector,
(state) => {
const { maxPrompts, combinatorial } = state.dynamicPrompts;
const { maxPrompts, combinatorial, isEnabled } = state.dynamicPrompts;
const { min, sliderMax, inputMax } =
state.config.sd.dynamicPrompts.maxPrompts;
return { maxPrompts, min, sliderMax, inputMax, combinatorial };
return {
maxPrompts,
min,
sliderMax,
inputMax,
isDisabled: !isEnabled || !combinatorial,
};
},
defaultSelectorOptions
);
const ParamDynamicPromptsMaxPrompts = () => {
const { maxPrompts, min, sliderMax, inputMax, combinatorial } =
const { maxPrompts, min, sliderMax, inputMax, isDisabled } =
useAppSelector(selector);
const dispatch = useAppDispatch();
@ -37,7 +43,7 @@ const ParamDynamicPromptsMaxPrompts = () => {
return (
<IAISlider
label="Max Prompts"
isDisabled={!combinatorial}
isDisabled={isDisabled}
min={min}
max={sliderMax}
value={maxPrompts}

View File

@ -1,14 +1,30 @@
import { Flex, useDisclosure } from '@chakra-ui/react';
import { Flex } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit';
import { stateSelector } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import IAICollapse from 'common/components/IAICollapse';
import { size } from 'lodash-es';
import { memo } from 'react';
import ParamLoraList from './ParamLoraList';
import ParamLoraSelect from './ParamLoraSelect';
const selector = createSelector(
stateSelector,
(state) => {
const loraCount = size(state.lora.loras);
return {
activeLabel: loraCount > 0 ? `${loraCount} Active` : undefined,
};
},
defaultSelectorOptions
);
const ParamLoraCollapse = () => {
const { isOpen, onToggle } = useDisclosure();
const { activeLabel } = useAppSelector(selector);
return (
<IAICollapse label="LoRAs" isOpen={isOpen} onToggle={onToggle}>
<IAICollapse label={'LoRA'} activeLabel={activeLabel}>
<Flex sx={{ flexDir: 'column', gap: 2 }}>
<ParamLoraSelect />
<ParamLoraList />

View File

@ -1,5 +1,5 @@
import { RootState } from 'app/store/store';
import { filter } from 'lodash-es';
import { getValidControlNets } from 'features/controlNet/util/getValidControlNets';
import { CollectInvocation, ControlNetInvocation } from 'services/api/types';
import { NonNullableGraph } from '../types/types';
import { CONTROL_NET_COLLECT } from './graphBuilders/constants';
@ -11,13 +11,7 @@ export const addControlNetToLinearGraph = (
): void => {
const { isEnabled: isControlNetEnabled, controlNets } = state.controlNet;
const validControlNets = filter(
controlNets,
(c) =>
c.isEnabled &&
(Boolean(c.processedControlImage) ||
(c.processorType === 'none' && Boolean(c.controlImage)))
);
const validControlNets = getValidControlNets(controlNets);
if (isControlNetEnabled && Boolean(validControlNets.length)) {
if (validControlNets.length > 1) {

View File

@ -1,20 +1,15 @@
import { Flex, useDisclosure } from '@chakra-ui/react';
import { useTranslation } from 'react-i18next';
import { Flex } from '@chakra-ui/react';
import IAICollapse from 'common/components/IAICollapse';
import { memo } from 'react';
import ParamBoundingBoxWidth from './ParamBoundingBoxWidth';
import { useTranslation } from 'react-i18next';
import ParamBoundingBoxHeight from './ParamBoundingBoxHeight';
import ParamBoundingBoxWidth from './ParamBoundingBoxWidth';
const ParamBoundingBoxCollapse = () => {
const { t } = useTranslation();
const { isOpen, onToggle } = useDisclosure();
return (
<IAICollapse
label={t('parameters.boundingBoxHeader')}
isOpen={isOpen}
onToggle={onToggle}
>
<IAICollapse label={t('parameters.boundingBoxHeader')}>
<Flex sx={{ gap: 2, flexDirection: 'column' }}>
<ParamBoundingBoxWidth />
<ParamBoundingBoxHeight />

View File

@ -1,4 +1,4 @@
import { Flex, useDisclosure } from '@chakra-ui/react';
import { Flex } from '@chakra-ui/react';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
@ -6,19 +6,14 @@ import IAICollapse from 'common/components/IAICollapse';
import ParamInfillMethod from './ParamInfillMethod';
import ParamInfillTilesize from './ParamInfillTilesize';
import ParamScaleBeforeProcessing from './ParamScaleBeforeProcessing';
import ParamScaledWidth from './ParamScaledWidth';
import ParamScaledHeight from './ParamScaledHeight';
import ParamScaledWidth from './ParamScaledWidth';
const ParamInfillCollapse = () => {
const { t } = useTranslation();
const { isOpen, onToggle } = useDisclosure();
return (
<IAICollapse
label={t('parameters.infillScalingHeader')}
isOpen={isOpen}
onToggle={onToggle}
>
<IAICollapse label={t('parameters.infillScalingHeader')}>
<Flex sx={{ gap: 2, flexDirection: 'column' }}>
<ParamInfillMethod />
<ParamInfillTilesize />

View File

@ -1,22 +1,16 @@
import IAICollapse from 'common/components/IAICollapse';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
import ParamSeamBlur from './ParamSeamBlur';
import ParamSeamSize from './ParamSeamSize';
import ParamSeamSteps from './ParamSeamSteps';
import ParamSeamStrength from './ParamSeamStrength';
import { useDisclosure } from '@chakra-ui/react';
import { useTranslation } from 'react-i18next';
import IAICollapse from 'common/components/IAICollapse';
import { memo } from 'react';
const ParamSeamCorrectionCollapse = () => {
const { t } = useTranslation();
const { isOpen, onToggle } = useDisclosure();
return (
<IAICollapse
label={t('parameters.seamCorrectionHeader')}
isOpen={isOpen}
onToggle={onToggle}
>
<IAICollapse label={t('parameters.seamCorrectionHeader')}>
<ParamSeamSize />
<ParamSeamBlur />
<ParamSeamStrength />

View File

@ -1,41 +1,45 @@
import { Divider, Flex } from '@chakra-ui/react';
import { useTranslation } from 'react-i18next';
import IAICollapse from 'common/components/IAICollapse';
import { Fragment, memo, useCallback } from 'react';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { createSelector } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import IAIButton from 'common/components/IAIButton';
import IAICollapse from 'common/components/IAICollapse';
import ControlNet from 'features/controlNet/components/ControlNet';
import ParamControlNetFeatureToggle from 'features/controlNet/components/parameters/ParamControlNetFeatureToggle';
import {
controlNetAdded,
controlNetSelector,
isControlNetEnabledToggled,
} from 'features/controlNet/store/controlNetSlice';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import { map } from 'lodash-es';
import { v4 as uuidv4 } from 'uuid';
import { getValidControlNets } from 'features/controlNet/util/getValidControlNets';
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
import IAIButton from 'common/components/IAIButton';
import ControlNet from 'features/controlNet/components/ControlNet';
import { map } from 'lodash-es';
import { Fragment, memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { v4 as uuidv4 } from 'uuid';
const selector = createSelector(
controlNetSelector,
(controlNet) => {
const { controlNets, isEnabled } = controlNet;
return { controlNetsArray: map(controlNets), isEnabled };
const validControlNets = getValidControlNets(controlNets);
const activeLabel =
isEnabled && validControlNets.length > 0
? `${validControlNets.length} Active`
: undefined;
return { controlNetsArray: map(controlNets), activeLabel };
},
defaultSelectorOptions
);
const ParamControlNetCollapse = () => {
const { t } = useTranslation();
const { controlNetsArray, isEnabled } = useAppSelector(selector);
const { controlNetsArray, activeLabel } = useAppSelector(selector);
const isControlNetDisabled = useFeatureStatus('controlNet').isFeatureDisabled;
const dispatch = useAppDispatch();
const handleClickControlNetToggle = useCallback(() => {
dispatch(isControlNetEnabledToggled());
}, [dispatch]);
const handleClickedAddControlNet = useCallback(() => {
dispatch(controlNetAdded({ controlNetId: uuidv4() }));
}, [dispatch]);
@ -45,13 +49,9 @@ const ParamControlNetCollapse = () => {
}
return (
<IAICollapse
label={'ControlNet'}
isOpen={isEnabled}
onToggle={handleClickControlNetToggle}
withSwitch
>
<IAICollapse label="ControlNet" activeLabel={activeLabel}>
<Flex sx={{ flexDir: 'column', gap: 3 }}>
<ParamControlNetFeatureToggle />
{controlNetsArray.map((c, i) => (
<Fragment key={c.controlNetId}>
{i > 0 && <Divider />}

View File

@ -1,37 +1,39 @@
import { Flex } from '@chakra-ui/react';
import { useTranslation } from 'react-i18next';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { RootState } from 'app/store/store';
import { createSelector } from '@reduxjs/toolkit';
import { stateSelector } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import IAICollapse from 'common/components/IAICollapse';
import { memo } from 'react';
import { ParamHiresStrength } from './ParamHiresStrength';
import { setHiresFix } from 'features/parameters/store/postprocessingSlice';
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
import { ParamHiresStrength } from './ParamHiresStrength';
import { ParamHiresToggle } from './ParamHiresToggle';
const selector = createSelector(
stateSelector,
(state) => {
const activeLabel = state.postprocessing.hiresFix ? 'Enabled' : undefined;
return { activeLabel };
},
defaultSelectorOptions
);
const ParamHiresCollapse = () => {
const { t } = useTranslation();
const hiresFix = useAppSelector(
(state: RootState) => state.postprocessing.hiresFix
);
const { activeLabel } = useAppSelector(selector);
const isHiresEnabled = useFeatureStatus('hires').isFeatureEnabled;
const dispatch = useAppDispatch();
const handleToggle = () => dispatch(setHiresFix(!hiresFix));
if (!isHiresEnabled) {
return null;
}
return (
<IAICollapse
label={t('parameters.hiresOptim')}
isOpen={hiresFix}
onToggle={handleToggle}
withSwitch
>
<IAICollapse label={t('parameters.hiresOptim')} activeLabel={activeLabel}>
<Flex sx={{ gap: 2, flexDirection: 'column' }}>
<ParamHiresToggle />
<ParamHiresStrength />
</Flex>
</IAICollapse>

View File

@ -23,7 +23,6 @@ export const ParamHiresToggle = () => {
return (
<IAISwitch
label={t('parameters.hiresOptim')}
fontSize="md"
isChecked={hiresFix}
onChange={handleChangeHiresFix}
/>

View File

@ -1,27 +1,33 @@
import { useTranslation } from 'react-i18next';
import { Flex } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit';
import { stateSelector } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import IAICollapse from 'common/components/IAICollapse';
import ParamPerlinNoise from './ParamPerlinNoise';
import ParamNoiseThreshold from './ParamNoiseThreshold';
import { RootState } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { setShouldUseNoiseSettings } from 'features/parameters/store/generationSlice';
import { memo } from 'react';
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
import ParamNoiseThreshold from './ParamNoiseThreshold';
import { ParamNoiseToggle } from './ParamNoiseToggle';
import ParamPerlinNoise from './ParamPerlinNoise';
const selector = createSelector(
stateSelector,
(state) => {
const { shouldUseNoiseSettings } = state.generation;
return {
activeLabel: shouldUseNoiseSettings ? 'Enabled' : undefined,
};
},
defaultSelectorOptions
);
const ParamNoiseCollapse = () => {
const { t } = useTranslation();
const isNoiseEnabled = useFeatureStatus('noise').isFeatureEnabled;
const shouldUseNoiseSettings = useAppSelector(
(state: RootState) => state.generation.shouldUseNoiseSettings
);
const dispatch = useAppDispatch();
const handleToggle = () =>
dispatch(setShouldUseNoiseSettings(!shouldUseNoiseSettings));
const { activeLabel } = useAppSelector(selector);
if (!isNoiseEnabled) {
return null;
@ -30,11 +36,10 @@ const ParamNoiseCollapse = () => {
return (
<IAICollapse
label={t('parameters.noiseSettings')}
isOpen={shouldUseNoiseSettings}
onToggle={handleToggle}
withSwitch
activeLabel={activeLabel}
>
<Flex sx={{ gap: 2, flexDirection: 'column' }}>
<ParamNoiseToggle />
<ParamPerlinNoise />
<ParamNoiseThreshold />
</Flex>

View File

@ -1,18 +1,31 @@
import { RootState } from 'app/store/store';
import { createSelector } from '@reduxjs/toolkit';
import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import IAISlider from 'common/components/IAISlider';
import { setThreshold } from 'features/parameters/store/generationSlice';
import { useTranslation } from 'react-i18next';
const selector = createSelector(
stateSelector,
(state) => {
const { shouldUseNoiseSettings, threshold } = state.generation;
return {
isDisabled: !shouldUseNoiseSettings,
threshold,
};
},
defaultSelectorOptions
);
export default function ParamNoiseThreshold() {
const dispatch = useAppDispatch();
const threshold = useAppSelector(
(state: RootState) => state.generation.threshold
);
const { threshold, isDisabled } = useAppSelector(selector);
const { t } = useTranslation();
return (
<IAISlider
isDisabled={isDisabled}
label={t('parameters.noiseThreshold')}
min={0}
max={20}

View File

@ -0,0 +1,27 @@
import type { RootState } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAISwitch from 'common/components/IAISwitch';
import { setShouldUseNoiseSettings } from 'features/parameters/store/generationSlice';
import { ChangeEvent } from 'react';
import { useTranslation } from 'react-i18next';
export const ParamNoiseToggle = () => {
const dispatch = useAppDispatch();
const shouldUseNoiseSettings = useAppSelector(
(state: RootState) => state.generation.shouldUseNoiseSettings
);
const { t } = useTranslation();
const handleChange = (e: ChangeEvent<HTMLInputElement>) =>
dispatch(setShouldUseNoiseSettings(e.target.checked));
return (
<IAISwitch
label="Enable Noise Settings"
isChecked={shouldUseNoiseSettings}
onChange={handleChange}
/>
);
};

View File

@ -1,16 +1,31 @@
import { RootState } from 'app/store/store';
import { createSelector } from '@reduxjs/toolkit';
import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import IAISlider from 'common/components/IAISlider';
import { setPerlin } from 'features/parameters/store/generationSlice';
import { useTranslation } from 'react-i18next';
const selector = createSelector(
stateSelector,
(state) => {
const { shouldUseNoiseSettings, perlin } = state.generation;
return {
isDisabled: !shouldUseNoiseSettings,
perlin,
};
},
defaultSelectorOptions
);
export default function ParamPerlinNoise() {
const dispatch = useAppDispatch();
const perlin = useAppSelector((state: RootState) => state.generation.perlin);
const { perlin, isDisabled } = useAppSelector(selector);
const { t } = useTranslation();
return (
<IAISlider
isDisabled={isDisabled}
label={t('parameters.perlinNoise')}
min={0}
max={1}

View File

@ -1,36 +1,46 @@
import { useTranslation } from 'react-i18next';
import { Box, Flex } from '@chakra-ui/react';
import IAICollapse from 'common/components/IAICollapse';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { setSeamless } from 'features/parameters/store/generationSlice';
import { memo } from 'react';
import { createSelector } from '@reduxjs/toolkit';
import { generationSelector } from 'features/parameters/store/generationSelectors';
import { useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import IAICollapse from 'common/components/IAICollapse';
import { generationSelector } from 'features/parameters/store/generationSelectors';
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
import ParamSeamlessXAxis from './ParamSeamlessXAxis';
import ParamSeamlessYAxis from './ParamSeamlessYAxis';
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
const getActiveLabel = (seamlessXAxis: boolean, seamlessYAxis: boolean) => {
if (seamlessXAxis && seamlessYAxis) {
return 'X & Y';
}
if (seamlessXAxis) {
return 'X';
}
if (seamlessYAxis) {
return 'Y';
}
};
const selector = createSelector(
generationSelector,
(generation) => {
const { shouldUseSeamless, seamlessXAxis, seamlessYAxis } = generation;
const { seamlessXAxis, seamlessYAxis } = generation;
return { shouldUseSeamless, seamlessXAxis, seamlessYAxis };
const activeLabel = getActiveLabel(seamlessXAxis, seamlessYAxis);
return { activeLabel };
},
defaultSelectorOptions
);
const ParamSeamlessCollapse = () => {
const { t } = useTranslation();
const { shouldUseSeamless } = useAppSelector(selector);
const { activeLabel } = useAppSelector(selector);
const isSeamlessEnabled = useFeatureStatus('seamless').isFeatureEnabled;
const dispatch = useAppDispatch();
const handleToggle = () => dispatch(setSeamless(!shouldUseSeamless));
if (!isSeamlessEnabled) {
return null;
}
@ -38,9 +48,7 @@ const ParamSeamlessCollapse = () => {
return (
<IAICollapse
label={t('parameters.seamlessTiling')}
isOpen={shouldUseSeamless}
onToggle={handleToggle}
withSwitch
activeLabel={activeLabel}
>
<Flex sx={{ gap: 5 }}>
<Box flexGrow={1}>

View File

@ -1,39 +1,39 @@
import { memo } from 'react';
import { Flex } from '@chakra-ui/react';
import { memo } from 'react';
import ParamSymmetryHorizontal from './ParamSymmetryHorizontal';
import ParamSymmetryVertical from './ParamSymmetryVertical';
import { useTranslation } from 'react-i18next';
import { createSelector } from '@reduxjs/toolkit';
import { stateSelector } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import IAICollapse from 'common/components/IAICollapse';
import { RootState } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { setShouldUseSymmetry } from 'features/parameters/store/generationSlice';
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
import { useTranslation } from 'react-i18next';
import ParamSymmetryToggle from './ParamSymmetryToggle';
const selector = createSelector(
stateSelector,
(state) => ({
activeLabel: state.generation.shouldUseSymmetry ? 'Enabled' : undefined,
}),
defaultSelectorOptions
);
const ParamSymmetryCollapse = () => {
const { t } = useTranslation();
const shouldUseSymmetry = useAppSelector(
(state: RootState) => state.generation.shouldUseSymmetry
);
const { activeLabel } = useAppSelector(selector);
const isSymmetryEnabled = useFeatureStatus('symmetry').isFeatureEnabled;
const dispatch = useAppDispatch();
const handleToggle = () => dispatch(setShouldUseSymmetry(!shouldUseSymmetry));
if (!isSymmetryEnabled) {
return null;
}
return (
<IAICollapse
label={t('parameters.symmetry')}
isOpen={shouldUseSymmetry}
onToggle={handleToggle}
withSwitch
>
<IAICollapse label={t('parameters.symmetry')} activeLabel={activeLabel}>
<Flex sx={{ gap: 2, flexDirection: 'column' }}>
<ParamSymmetryToggle />
<ParamSymmetryHorizontal />
<ParamSymmetryVertical />
</Flex>

View File

@ -12,6 +12,7 @@ export default function ParamSymmetryToggle() {
return (
<IAISwitch
label="Enable Symmetry"
isChecked={shouldUseSymmetry}
onChange={(e) => dispatch(setShouldUseSymmetry(e.target.checked))}
/>

View File

@ -1,39 +1,42 @@
import ParamVariationWeights from './ParamVariationWeights';
import ParamVariationAmount from './ParamVariationAmount';
import { useTranslation } from 'react-i18next';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { RootState } from 'app/store/store';
import { setShouldGenerateVariations } from 'features/parameters/store/generationSlice';
import { Flex } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit';
import { stateSelector } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import IAICollapse from 'common/components/IAICollapse';
import { memo } from 'react';
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
import ParamVariationAmount from './ParamVariationAmount';
import { ParamVariationToggle } from './ParamVariationToggle';
import ParamVariationWeights from './ParamVariationWeights';
const selector = createSelector(
stateSelector,
(state) => {
const activeLabel = state.generation.shouldGenerateVariations
? 'Enabled'
: undefined;
return { activeLabel };
},
defaultSelectorOptions
);
const ParamVariationCollapse = () => {
const { t } = useTranslation();
const shouldGenerateVariations = useAppSelector(
(state: RootState) => state.generation.shouldGenerateVariations
);
const { activeLabel } = useAppSelector(selector);
const isVariationEnabled = useFeatureStatus('variation').isFeatureEnabled;
const dispatch = useAppDispatch();
const handleToggle = () =>
dispatch(setShouldGenerateVariations(!shouldGenerateVariations));
if (!isVariationEnabled) {
return null;
}
return (
<IAICollapse
label={t('parameters.variations')}
isOpen={shouldGenerateVariations}
onToggle={handleToggle}
withSwitch
>
<IAICollapse label={t('parameters.variations')} activeLabel={activeLabel}>
<Flex sx={{ gap: 2, flexDirection: 'column' }}>
<ParamVariationToggle />
<ParamVariationAmount />
<ParamVariationWeights />
</Flex>

View File

@ -0,0 +1,27 @@
import type { RootState } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAISwitch from 'common/components/IAISwitch';
import { setShouldGenerateVariations } from 'features/parameters/store/generationSlice';
import { ChangeEvent } from 'react';
import { useTranslation } from 'react-i18next';
export const ParamVariationToggle = () => {
const dispatch = useAppDispatch();
const shouldGenerateVariations = useAppSelector(
(state: RootState) => state.generation.shouldGenerateVariations
);
const { t } = useTranslation();
const handleChange = (e: ChangeEvent<HTMLInputElement>) =>
dispatch(setShouldGenerateVariations(e.target.checked));
return (
<IAISwitch
label="Enable Variations"
isChecked={shouldGenerateVariations}
onChange={handleChange}
/>
);
};

View File

@ -49,7 +49,6 @@ export interface GenerationState {
verticalSymmetrySteps: number;
model: ModelParam;
vae: VAEParam;
shouldUseSeamless: boolean;
seamlessXAxis: boolean;
seamlessYAxis: boolean;
}
@ -84,9 +83,8 @@ export const initialGenerationState: GenerationState = {
verticalSymmetrySteps: 0,
model: '',
vae: '',
shouldUseSeamless: false,
seamlessXAxis: true,
seamlessYAxis: true,
seamlessXAxis: false,
seamlessYAxis: false,
};
const initialState: GenerationState = initialGenerationState;
@ -144,9 +142,6 @@ export const generationSlice = createSlice({
setImg2imgStrength: (state, action: PayloadAction<number>) => {
state.img2imgStrength = action.payload;
},
setSeamless: (state, action: PayloadAction<boolean>) => {
state.shouldUseSeamless = action.payload;
},
setSeamlessXAxis: (state, action: PayloadAction<boolean>) => {
state.seamlessXAxis = action.payload;
},
@ -268,7 +263,6 @@ export const {
modelSelected,
vaeSelected,
setShouldUseNoiseSettings,
setSeamless,
setSeamlessXAxis,
setSeamlessYAxis,
} = generationSlice.actions;

View File

@ -1,4 +1,4 @@
import { Box, Flex, useDisclosure } from '@chakra-ui/react';
import { Box, Flex } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
@ -21,19 +21,25 @@ const selector = createSelector(
[uiSelector, generationSelector],
(ui, generation) => {
const { shouldUseSliders } = ui;
const { shouldFitToWidthHeight } = generation;
const { shouldFitToWidthHeight, shouldRandomizeSeed } = generation;
return { shouldUseSliders, shouldFitToWidthHeight };
const activeLabel = !shouldRandomizeSeed ? 'Manual Seed' : undefined;
return { shouldUseSliders, shouldFitToWidthHeight, activeLabel };
},
defaultSelectorOptions
);
const ImageToImageTabCoreParameters = () => {
const { shouldUseSliders, shouldFitToWidthHeight } = useAppSelector(selector);
const { isOpen, onToggle } = useDisclosure({ defaultIsOpen: true });
const { shouldUseSliders, shouldFitToWidthHeight, activeLabel } =
useAppSelector(selector);
return (
<IAICollapse label={'General'} isOpen={isOpen} onToggle={onToggle}>
<IAICollapse
label={'General'}
activeLabel={activeLabel}
defaultIsOpen={true}
>
<Flex
sx={{
flexDirection: 'column',

View File

@ -1,5 +1,6 @@
import { Box, Flex, useDisclosure } from '@chakra-ui/react';
import { Box, Flex } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit';
import { stateSelector } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import IAICollapse from 'common/components/IAICollapse';
@ -11,25 +12,30 @@ import ParamScheduler from 'features/parameters/components/Parameters/Core/Param
import ParamSteps from 'features/parameters/components/Parameters/Core/ParamSteps';
import ParamWidth from 'features/parameters/components/Parameters/Core/ParamWidth';
import ParamSeedFull from 'features/parameters/components/Parameters/Seed/ParamSeedFull';
import { uiSelector } from 'features/ui/store/uiSelectors';
import { memo } from 'react';
const selector = createSelector(
uiSelector,
(ui) => {
stateSelector,
({ ui, generation }) => {
const { shouldUseSliders } = ui;
const { shouldRandomizeSeed } = generation;
return { shouldUseSliders };
const activeLabel = !shouldRandomizeSeed ? 'Manual Seed' : undefined;
return { shouldUseSliders, activeLabel };
},
defaultSelectorOptions
);
const TextToImageTabCoreParameters = () => {
const { shouldUseSliders } = useAppSelector(selector);
const { isOpen, onToggle } = useDisclosure({ defaultIsOpen: true });
const { shouldUseSliders, activeLabel } = useAppSelector(selector);
return (
<IAICollapse label={'General'} isOpen={isOpen} onToggle={onToggle}>
<IAICollapse
label={'General'}
activeLabel={activeLabel}
defaultIsOpen={true}
>
<Flex
sx={{
flexDirection: 'column',

View File

@ -1,5 +1,6 @@
import { Box, Flex, useDisclosure } from '@chakra-ui/react';
import { Box, Flex } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit';
import { stateSelector } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import IAICollapse from 'common/components/IAICollapse';
@ -12,25 +13,30 @@ import ParamScheduler from 'features/parameters/components/Parameters/Core/Param
import ParamSteps from 'features/parameters/components/Parameters/Core/ParamSteps';
import ImageToImageStrength from 'features/parameters/components/Parameters/ImageToImage/ImageToImageStrength';
import ParamSeedFull from 'features/parameters/components/Parameters/Seed/ParamSeedFull';
import { uiSelector } from 'features/ui/store/uiSelectors';
import { memo } from 'react';
const selector = createSelector(
uiSelector,
(ui) => {
stateSelector,
({ ui, generation }) => {
const { shouldUseSliders } = ui;
const { shouldRandomizeSeed } = generation;
return { shouldUseSliders };
const activeLabel = !shouldRandomizeSeed ? 'Manual Seed' : undefined;
return { shouldUseSliders, activeLabel };
},
defaultSelectorOptions
);
const UnifiedCanvasCoreParameters = () => {
const { shouldUseSliders } = useAppSelector(selector);
const { isOpen, onToggle } = useDisclosure({ defaultIsOpen: true });
const { shouldUseSliders, activeLabel } = useAppSelector(selector);
return (
<IAICollapse label={'General'} isOpen={isOpen} onToggle={onToggle}>
<IAICollapse
label={'General'}
activeLabel={activeLabel}
defaultIsOpen={true}
>
<Flex
sx={{
flexDirection: 'column',