From e41e8606b579b61c1ce2d34c102e6b5da728b79e Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Wed, 5 Jul 2023 17:33:03 +1000 Subject: [PATCH] feat(ui): improve accordion ux - 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 --- .../web/src/common/components/IAICollapse.tsx | 59 +++++++++++++------ .../web/src/common/components/IAISwitch.tsx | 2 +- .../ParamControlNetFeatureToggle.tsx | 36 +++++++++++ .../controlNet/util/getValidControlNets.ts | 15 +++++ .../ParamDynamicPromptsCollapse.tsx | 26 +++----- .../ParamDynamicPromptsCombinatorial.tsx | 15 ++--- .../components/ParamDynamicPromptsEnabled.tsx | 36 +++++++++++ .../ParamDynamicPromptsMaxPrompts.tsx | 24 +++++--- .../lora/components/ParamLoraCollapse.tsx | 22 ++++++- .../nodes/util/addControlNetToLinearGraph.ts | 10 +--- .../BoundingBox/ParamBoundingBoxCollapse.tsx | 13 ++-- .../ParamInfillAndScalingCollapse.tsx | 11 +--- .../ParamSeamCorrectionCollapse.tsx | 14 ++--- .../ControlNet/ParamControlNetCollapse.tsx | 44 +++++++------- .../Parameters/Hires/ParamHiresCollapse.tsx | 40 +++++++------ .../Parameters/Hires/ParamHiresToggle.tsx | 1 - .../Parameters/Noise/ParamNoiseCollapse.tsx | 41 +++++++------ .../Parameters/Noise/ParamNoiseThreshold.tsx | 21 +++++-- .../Parameters/Noise/ParamNoiseToggle.tsx | 27 +++++++++ .../Parameters/Noise/ParamPerlinNoise.tsx | 19 +++++- .../Seamless/ParamSeamlessCollapse.tsx | 42 +++++++------ .../Symmetry/ParamSymmetryCollapse.tsx | 36 +++++------ .../Symmetry/ParamSymmetryToggle.tsx | 1 + .../Variations/ParamVariationCollapse.tsx | 45 +++++++------- .../Variations/ParamVariationToggle.tsx | 27 +++++++++ .../parameters/store/generationSlice.ts | 10 +--- .../ImageToImageTabCoreParameters.tsx | 18 ++++-- .../TextToImageTabCoreParameters.tsx | 22 ++++--- .../UnifiedCanvasCoreParameters.tsx | 22 ++++--- 29 files changed, 457 insertions(+), 242 deletions(-) create mode 100644 invokeai/frontend/web/src/features/controlNet/components/parameters/ParamControlNetFeatureToggle.tsx create mode 100644 invokeai/frontend/web/src/features/controlNet/util/getValidControlNets.ts create mode 100644 invokeai/frontend/web/src/features/dynamicPrompts/components/ParamDynamicPromptsEnabled.tsx create mode 100644 invokeai/frontend/web/src/features/parameters/components/Parameters/Noise/ParamNoiseToggle.tsx create mode 100644 invokeai/frontend/web/src/features/parameters/components/Parameters/Variations/ParamVariationToggle.tsx diff --git a/invokeai/frontend/web/src/common/components/IAICollapse.tsx b/invokeai/frontend/web/src/common/components/IAICollapse.tsx index 5db26f3841..09dc1392e2 100644 --- a/invokeai/frontend/web/src/common/components/IAICollapse.tsx +++ b/invokeai/frontend/web/src/common/components/IAICollapse.tsx @@ -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 ( { alignItems: 'center', p: 2, px: 4, + gap: 2, borderTopRadius: 'base', borderBottomRadius: isOpen ? 0 : 'base', bg: isOpen @@ -48,19 +52,40 @@ const IAICollapse = (props: IAIToggleCollapseProps) => { }} > {label} + + {activeLabel && ( + + + {activeLabel} + + + )} + - {withSwitch && } - {!withSwitch && ( - - )} + { 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} > diff --git a/invokeai/frontend/web/src/features/controlNet/components/parameters/ParamControlNetFeatureToggle.tsx b/invokeai/frontend/web/src/features/controlNet/components/parameters/ParamControlNetFeatureToggle.tsx new file mode 100644 index 0000000000..3a7eea2fbf --- /dev/null +++ b/invokeai/frontend/web/src/features/controlNet/components/parameters/ParamControlNetFeatureToggle.tsx @@ -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 ( + + ); +}; + +export default ParamControlNetFeatureToggle; diff --git a/invokeai/frontend/web/src/features/controlNet/util/getValidControlNets.ts b/invokeai/frontend/web/src/features/controlNet/util/getValidControlNets.ts new file mode 100644 index 0000000000..4bff39db63 --- /dev/null +++ b/invokeai/frontend/web/src/features/controlNet/util/getValidControlNets.ts @@ -0,0 +1,15 @@ +import { filter } from 'lodash-es'; +import { ControlNetConfig } from '../store/controlNetSlice'; + +export const getValidControlNets = ( + controlNets: Record +) => { + const validControlNets = filter( + controlNets, + (c) => + c.isEnabled && + (Boolean(c.processedControlImage) || + (c.processorType === 'none' && Boolean(c.controlImage))) + ); + return validControlNets; +}; diff --git a/invokeai/frontend/web/src/features/dynamicPrompts/components/ParamDynamicPromptsCollapse.tsx b/invokeai/frontend/web/src/features/dynamicPrompts/components/ParamDynamicPromptsCollapse.tsx index 1aefecf3e6..0e41fad994 100644 --- a/invokeai/frontend/web/src/features/dynamicPrompts/components/ParamDynamicPromptsCollapse.tsx +++ b/invokeai/frontend/web/src/features/dynamicPrompts/components/ParamDynamicPromptsCollapse.tsx @@ -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 ( - + + diff --git a/invokeai/frontend/web/src/features/dynamicPrompts/components/ParamDynamicPromptsCombinatorial.tsx b/invokeai/frontend/web/src/features/dynamicPrompts/components/ParamDynamicPromptsCombinatorial.tsx index 30c2240c37..cb930acd3b 100644 --- a/invokeai/frontend/web/src/features/dynamicPrompts/components/ParamDynamicPromptsCombinatorial.tsx +++ b/invokeai/frontend/web/src/features/dynamicPrompts/components/ParamDynamicPromptsCombinatorial.tsx @@ -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 ( { + const { isEnabled } = state.dynamicPrompts; + + return { isEnabled }; + }, + defaultSelectorOptions +); + +const ParamDynamicPromptsToggle = () => { + const dispatch = useAppDispatch(); + const { isEnabled } = useAppSelector(selector); + + const handleToggleIsEnabled = useCallback(() => { + dispatch(isEnabledToggled()); + }, [dispatch]); + + return ( + + ); +}; + +export default ParamDynamicPromptsToggle; diff --git a/invokeai/frontend/web/src/features/dynamicPrompts/components/ParamDynamicPromptsMaxPrompts.tsx b/invokeai/frontend/web/src/features/dynamicPrompts/components/ParamDynamicPromptsMaxPrompts.tsx index 19f02ae3e5..172120fd1e 100644 --- a/invokeai/frontend/web/src/features/dynamicPrompts/components/ParamDynamicPromptsMaxPrompts.tsx +++ b/invokeai/frontend/web/src/features/dynamicPrompts/components/ParamDynamicPromptsMaxPrompts.tsx @@ -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 ( { + 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 ( - + diff --git a/invokeai/frontend/web/src/features/nodes/util/addControlNetToLinearGraph.ts b/invokeai/frontend/web/src/features/nodes/util/addControlNetToLinearGraph.ts index 11ceb23763..5c4d67ebd3 100644 --- a/invokeai/frontend/web/src/features/nodes/util/addControlNetToLinearGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/addControlNetToLinearGraph.ts @@ -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) { diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/BoundingBox/ParamBoundingBoxCollapse.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/BoundingBox/ParamBoundingBoxCollapse.tsx index fea0d8330a..b9cc8511aa 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/BoundingBox/ParamBoundingBoxCollapse.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/BoundingBox/ParamBoundingBoxCollapse.tsx @@ -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 ( - + diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/InfillAndScaling/ParamInfillAndScalingCollapse.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/InfillAndScaling/ParamInfillAndScalingCollapse.tsx index ed01da9876..a531eba57f 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/InfillAndScaling/ParamInfillAndScalingCollapse.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/InfillAndScaling/ParamInfillAndScalingCollapse.tsx @@ -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 ( - + diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/SeamCorrection/ParamSeamCorrectionCollapse.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/SeamCorrection/ParamSeamCorrectionCollapse.tsx index 992e8b6d02..88d839fa15 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/SeamCorrection/ParamSeamCorrectionCollapse.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/SeamCorrection/ParamSeamCorrectionCollapse.tsx @@ -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 ( - + diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/ControlNet/ParamControlNetCollapse.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/ControlNet/ParamControlNetCollapse.tsx index 06c6108dcb..59bf7542eb 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/ControlNet/ParamControlNetCollapse.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/ControlNet/ParamControlNetCollapse.tsx @@ -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 ( - + + {controlNetsArray.map((c, i) => ( {i > 0 && } diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Hires/ParamHiresCollapse.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Hires/ParamHiresCollapse.tsx index b4b077ad6c..fa8606d610 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/Hires/ParamHiresCollapse.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Hires/ParamHiresCollapse.tsx @@ -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 ( - + + diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Hires/ParamHiresToggle.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Hires/ParamHiresToggle.tsx index 0fc600e9e8..f8e6f22aa4 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/Hires/ParamHiresToggle.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Hires/ParamHiresToggle.tsx @@ -23,7 +23,6 @@ export const ParamHiresToggle = () => { return ( diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Noise/ParamNoiseCollapse.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Noise/ParamNoiseCollapse.tsx index adb76d8da0..4dea1dad4f 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/Noise/ParamNoiseCollapse.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Noise/ParamNoiseCollapse.tsx @@ -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 ( + diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Noise/ParamNoiseThreshold.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Noise/ParamNoiseThreshold.tsx index e339734992..3abb7532b4 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/Noise/ParamNoiseThreshold.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Noise/ParamNoiseThreshold.tsx @@ -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 ( { + const dispatch = useAppDispatch(); + + const shouldUseNoiseSettings = useAppSelector( + (state: RootState) => state.generation.shouldUseNoiseSettings + ); + + const { t } = useTranslation(); + + const handleChange = (e: ChangeEvent) => + dispatch(setShouldUseNoiseSettings(e.target.checked)); + + return ( + + ); +}; diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Noise/ParamPerlinNoise.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Noise/ParamPerlinNoise.tsx index ad710eae54..afd676223c 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/Noise/ParamPerlinNoise.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Noise/ParamPerlinNoise.tsx @@ -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 ( { + 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 ( diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Symmetry/ParamSymmetryCollapse.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Symmetry/ParamSymmetryCollapse.tsx index 59bdb39be1..f2ddd19768 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/Symmetry/ParamSymmetryCollapse.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Symmetry/ParamSymmetryCollapse.tsx @@ -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 ( - + + diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Symmetry/ParamSymmetryToggle.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Symmetry/ParamSymmetryToggle.tsx index 7cc17c045e..59386ff526 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/Symmetry/ParamSymmetryToggle.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Symmetry/ParamSymmetryToggle.tsx @@ -12,6 +12,7 @@ export default function ParamSymmetryToggle() { return ( dispatch(setShouldUseSymmetry(e.target.checked))} /> diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Variations/ParamVariationCollapse.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Variations/ParamVariationCollapse.tsx index 1564bd64e5..3cdfc3a06b 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/Variations/ParamVariationCollapse.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Variations/ParamVariationCollapse.tsx @@ -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 ( - + + diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Variations/ParamVariationToggle.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Variations/ParamVariationToggle.tsx new file mode 100644 index 0000000000..1c05468de0 --- /dev/null +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Variations/ParamVariationToggle.tsx @@ -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) => + dispatch(setShouldGenerateVariations(e.target.checked)); + + return ( + + ); +}; diff --git a/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts b/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts index 209cf4b639..960a41bb45 100644 --- a/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts +++ b/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts @@ -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) => { state.img2imgStrength = action.payload; }, - setSeamless: (state, action: PayloadAction) => { - state.shouldUseSeamless = action.payload; - }, setSeamlessXAxis: (state, action: PayloadAction) => { state.seamlessXAxis = action.payload; }, @@ -268,7 +263,6 @@ export const { modelSelected, vaeSelected, setShouldUseNoiseSettings, - setSeamless, setSeamlessXAxis, setSeamlessYAxis, } = generationSlice.actions; diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ImageToImage/ImageToImageTabCoreParameters.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ImageToImage/ImageToImageTabCoreParameters.tsx index 89286232c6..5f5c7ad46b 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/ImageToImage/ImageToImageTabCoreParameters.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/ImageToImage/ImageToImageTabCoreParameters.tsx @@ -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 ( - + { + 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 ( - + { + 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 ( - +