feat(ui): remember open/closed state of accordions/expanders

This commit is contained in:
psychedelicious 2024-01-23 19:31:20 +11:00 committed by Kent Keirsey
parent 504bdac14a
commit e7e7793896
5 changed files with 78 additions and 36 deletions

View File

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

View File

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

View File

@ -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');

View File

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

View File

@ -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<string, string>;
/**
* The state of accordions. The key is the id of the accordion, and the value is a boolean representing the open state.
*/
accordions: Record<string, boolean>;
/**
* The state of expanders. The key is the id of the expander, and the value is a boolean representing the open state.
*/
expanders: Record<string, boolean>;
}