diff --git a/invokeai/frontend/web/src/features/settingsAccordions/hooks/useExpanderToggle.ts b/invokeai/frontend/web/src/features/settingsAccordions/hooks/useExpanderToggle.ts index 19aad55220..d065d7d8f9 100644 --- a/invokeai/frontend/web/src/features/settingsAccordions/hooks/useExpanderToggle.ts +++ b/invokeai/frontend/web/src/features/settingsAccordions/hooks/useExpanderToggle.ts @@ -1,23 +1,25 @@ -import { useDisclosure } from '@invoke-ai/ui'; -import { useAppDispatch } from 'app/store/storeHooks'; -import { expanderToggled } from 'features/settingsAccordions/store/actions'; -import { useCallback } from 'react'; +import { createSelector } from '@reduxjs/toolkit'; +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { + expanderStateChanged, + selectUiSlice, +} from 'features/ui/store/uiSlice'; +import { useCallback, useMemo } from 'react'; type UseExpanderToggleArg = { defaultIsOpen: boolean; - id?: string; + id: string; }; export const useExpanderToggle = (arg: UseExpanderToggleArg) => { const dispatch = useAppDispatch(); - const { isOpen, onToggle: _onToggle } = useDisclosure({ - defaultIsOpen: arg.defaultIsOpen, - }); + const selectIsOpen = useMemo( + () => createSelector(selectUiSlice, (ui) => ui.expanders[arg.id] ?? arg.defaultIsOpen), + [arg] + ); + const isOpen = useAppSelector(selectIsOpen); const onToggle = useCallback(() => { - if (arg.id) { - dispatch(expanderToggled({ id: arg.id, isOpen })); - } - _onToggle(); - }, [_onToggle, dispatch, arg.id, isOpen]); + dispatch(expanderStateChanged({ id: arg.id, isOpen: !isOpen })); + }, [dispatch, arg.id, isOpen]); return { isOpen, onToggle }; }; diff --git a/invokeai/frontend/web/src/features/settingsAccordions/hooks/useStandaloneAccordionToggle.ts b/invokeai/frontend/web/src/features/settingsAccordions/hooks/useStandaloneAccordionToggle.ts index 94a4aaefe3..d7a7ea0dc8 100644 --- a/invokeai/frontend/web/src/features/settingsAccordions/hooks/useStandaloneAccordionToggle.ts +++ b/invokeai/frontend/web/src/features/settingsAccordions/hooks/useStandaloneAccordionToggle.ts @@ -1,25 +1,31 @@ -import { useDisclosure } from '@invoke-ai/ui'; -import { useAppDispatch } from 'app/store/storeHooks'; -import { standaloneAccordionToggled } from 'features/settingsAccordions/store/actions'; -import { useCallback } from 'react'; +import { createSelector } from '@reduxjs/toolkit'; +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { + accordionStateChanged, + selectUiSlice, +} from 'features/ui/store/uiSlice'; +import { useCallback, useMemo } from 'react'; type UseStandaloneAccordionToggleArg = { defaultIsOpen: boolean; - id?: string; + id: string; }; export const useStandaloneAccordionToggle = ( arg: UseStandaloneAccordionToggleArg ) => { const dispatch = useAppDispatch(); - const { isOpen, onToggle: _onToggle } = useDisclosure({ - defaultIsOpen: arg.defaultIsOpen, - }); + const selectIsOpen = useMemo( + () => + createSelector( + selectUiSlice, + (ui) => ui.accordions[arg.id] ?? arg.defaultIsOpen + ), + [arg] + ); + const isOpen = useAppSelector(selectIsOpen); const onToggle = useCallback(() => { - if (arg.id) { - dispatch(standaloneAccordionToggled({ id: arg.id, isOpen })); - } - _onToggle(); - }, [_onToggle, arg.id, dispatch, isOpen]); + dispatch(accordionStateChanged({ id: arg.id, isOpen: !isOpen })); + }, [arg.id, dispatch, isOpen]); return { isOpen, onToggle }; }; diff --git a/invokeai/frontend/web/src/features/settingsAccordions/store/actions.ts b/invokeai/frontend/web/src/features/settingsAccordions/store/actions.ts deleted file mode 100644 index 8ce987168d..0000000000 --- a/invokeai/frontend/web/src/features/settingsAccordions/store/actions.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { createAction } from '@reduxjs/toolkit'; - -export const expanderToggled = createAction<{ id: string; isOpen: boolean }>( - 'parameters/expanderToggled' -); - -export const standaloneAccordionToggled = createAction<{ - id: string; - isOpen: boolean; -}>('parameters/standaloneAccordionToggled'); diff --git a/invokeai/frontend/web/src/features/ui/store/uiSlice.ts b/invokeai/frontend/web/src/features/ui/store/uiSlice.ts index 57b725ed06..71a26cb1f0 100644 --- a/invokeai/frontend/web/src/features/ui/store/uiSlice.ts +++ b/invokeai/frontend/web/src/features/ui/store/uiSlice.ts @@ -13,6 +13,8 @@ export const initialUIState: UIState = { shouldHidePreview: false, shouldShowProgressInViewer: true, panels: {}, + accordions: {}, + expanders: {}, }; export const uiSlice = createSlice({ @@ -37,6 +39,20 @@ export const uiSlice = createSlice({ ) => { state.panels[action.payload.name] = action.payload.value; }, + accordionStateChanged: ( + state, + action: PayloadAction<{ id: string; isOpen: boolean }> + ) => { + const { id, isOpen } = action.payload; + state.accordions[id] = isOpen; + }, + expanderStateChanged: ( + state, + action: PayloadAction<{ id: string; isOpen: boolean }> + ) => { + const { id, isOpen } = action.payload; + state.expanders[id] = isOpen; + }, }, extraReducers(builder) { builder.addCase(initialImageChanged, (state) => { @@ -51,6 +67,8 @@ export const { setShouldHidePreview, setShouldShowProgressInViewer, panelsChanged, + accordionStateChanged, + expanderStateChanged, } = uiSlice.actions; export default uiSlice.reducer; diff --git a/invokeai/frontend/web/src/features/ui/store/uiTypes.ts b/invokeai/frontend/web/src/features/ui/store/uiTypes.ts index 60a96142e4..4e371090cf 100644 --- a/invokeai/frontend/web/src/features/ui/store/uiTypes.ts +++ b/invokeai/frontend/web/src/features/ui/store/uiTypes.ts @@ -1,10 +1,36 @@ import type { InvokeTabName } from './tabMap'; export interface UIState { + /** + * Slice schema version. + */ _version: 1; + /** + * The currently active tab. + */ activeTab: InvokeTabName; + /** + * Whether or not to show image details, e.g. metadata, workflow, etc. + */ shouldShowImageDetails: boolean; + /** + * Whether or not to hide the preview. + */ shouldHidePreview: boolean; + /** + * Whether or not to show progress in the viewer. + */ shouldShowProgressInViewer: boolean; + /** + * The react-resizable-panels state. The shape is managed by react-resizable-panels. + */ panels: Record; + /** + * The state of accordions. The key is the id of the accordion, and the value is a boolean representing the open state. + */ + accordions: Record; + /** + * The state of expanders. The key is the id of the expander, and the value is a boolean representing the open state. + */ + expanders: Record; }