mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
fix(ui): store customStarUI outside redux
JSX is not serializable, so it cannot be in redux. Non-serializable global state may be put into `nanostores`. - Use `nanostores` for `customStarUI` - Use `nanostores` for `headerComponent` - Re-enable the serializable & immutable check redux middlewares
This commit is contained in:
parent
2f5e923008
commit
f6738d647e
@ -12,33 +12,26 @@ import { languageSelector } from 'features/system/store/systemSelectors';
|
|||||||
import InvokeTabs from 'features/ui/components/InvokeTabs';
|
import InvokeTabs from 'features/ui/components/InvokeTabs';
|
||||||
import i18n from 'i18n';
|
import i18n from 'i18n';
|
||||||
import { size } from 'lodash-es';
|
import { size } from 'lodash-es';
|
||||||
import { ReactNode, memo, useCallback, useEffect } from 'react';
|
import { memo, useCallback, useEffect } from 'react';
|
||||||
import { ErrorBoundary } from 'react-error-boundary';
|
import { ErrorBoundary } from 'react-error-boundary';
|
||||||
import { usePreselectedImage } from '../../features/parameters/hooks/usePreselectedImage';
|
import { usePreselectedImage } from '../../features/parameters/hooks/usePreselectedImage';
|
||||||
import AppErrorBoundaryFallback from './AppErrorBoundaryFallback';
|
import AppErrorBoundaryFallback from './AppErrorBoundaryFallback';
|
||||||
import GlobalHotkeys from './GlobalHotkeys';
|
import GlobalHotkeys from './GlobalHotkeys';
|
||||||
import Toaster from './Toaster';
|
import Toaster from './Toaster';
|
||||||
import { CustomStarUi } from '../../features/ui/store/uiTypes';
|
import { useStore } from '@nanostores/react';
|
||||||
import { setCustomStarUi } from '../../features/ui/store/uiSlice';
|
import { $headerComponent } from 'app/store/nanostores/headerComponent';
|
||||||
|
|
||||||
const DEFAULT_CONFIG = {};
|
const DEFAULT_CONFIG = {};
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
config?: PartialAppConfig;
|
config?: PartialAppConfig;
|
||||||
headerComponent?: ReactNode;
|
|
||||||
selectedImage?: {
|
selectedImage?: {
|
||||||
imageName: string;
|
imageName: string;
|
||||||
action: 'sendToImg2Img' | 'sendToCanvas' | 'useAllParameters';
|
action: 'sendToImg2Img' | 'sendToCanvas' | 'useAllParameters';
|
||||||
};
|
};
|
||||||
customStarUi?: CustomStarUi;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const App = ({
|
const App = ({ config = DEFAULT_CONFIG, selectedImage }: Props) => {
|
||||||
config = DEFAULT_CONFIG,
|
|
||||||
headerComponent,
|
|
||||||
selectedImage,
|
|
||||||
customStarUi,
|
|
||||||
}: Props) => {
|
|
||||||
const language = useAppSelector(languageSelector);
|
const language = useAppSelector(languageSelector);
|
||||||
|
|
||||||
const logger = useLogger('system');
|
const logger = useLogger('system');
|
||||||
@ -61,12 +54,6 @@ const App = ({
|
|||||||
}
|
}
|
||||||
}, [dispatch, config, logger]);
|
}, [dispatch, config, logger]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (customStarUi) {
|
|
||||||
dispatch(setCustomStarUi(customStarUi));
|
|
||||||
}
|
|
||||||
}, [customStarUi, dispatch]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
dispatch(appStarted());
|
dispatch(appStarted());
|
||||||
}, [dispatch]);
|
}, [dispatch]);
|
||||||
@ -75,6 +62,8 @@ const App = ({
|
|||||||
handlePreselectedImage(selectedImage);
|
handlePreselectedImage(selectedImage);
|
||||||
}, [handlePreselectedImage, selectedImage]);
|
}, [handlePreselectedImage, selectedImage]);
|
||||||
|
|
||||||
|
const headerComponent = useStore($headerComponent);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ErrorBoundary
|
<ErrorBoundary
|
||||||
onReset={handleReset}
|
onReset={handleReset}
|
||||||
|
@ -15,7 +15,8 @@ import { socketMiddleware } from 'services/events/middleware';
|
|||||||
import Loading from '../../common/components/Loading/Loading';
|
import Loading from '../../common/components/Loading/Loading';
|
||||||
import '../../i18n';
|
import '../../i18n';
|
||||||
import AppDndContext from '../../features/dnd/components/AppDndContext';
|
import AppDndContext from '../../features/dnd/components/AppDndContext';
|
||||||
import { CustomStarUi } from '../../features/ui/store/uiTypes';
|
import { $customStarUI, CustomStarUi } from 'app/store/nanostores/customStarUI';
|
||||||
|
import { $headerComponent } from 'app/store/nanostores/headerComponent';
|
||||||
|
|
||||||
const App = lazy(() => import('./App'));
|
const App = lazy(() => import('./App'));
|
||||||
const ThemeLocaleProvider = lazy(() => import('./ThemeLocaleProvider'));
|
const ThemeLocaleProvider = lazy(() => import('./ThemeLocaleProvider'));
|
||||||
@ -83,18 +84,33 @@ const InvokeAIUI = ({
|
|||||||
};
|
};
|
||||||
}, [apiUrl, token, middleware, projectId]);
|
}, [apiUrl, token, middleware, projectId]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (customStarUi) {
|
||||||
|
$customStarUI.set(customStarUi);
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
$customStarUI.set(undefined);
|
||||||
|
};
|
||||||
|
}, [customStarUi]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (headerComponent) {
|
||||||
|
$headerComponent.set(headerComponent);
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
$headerComponent.set(undefined);
|
||||||
|
};
|
||||||
|
}, [headerComponent]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
<React.Suspense fallback={<Loading />}>
|
<React.Suspense fallback={<Loading />}>
|
||||||
<ThemeLocaleProvider>
|
<ThemeLocaleProvider>
|
||||||
<AppDndContext>
|
<AppDndContext>
|
||||||
<App
|
<App config={config} selectedImage={selectedImage} />
|
||||||
config={config}
|
|
||||||
headerComponent={headerComponent}
|
|
||||||
selectedImage={selectedImage}
|
|
||||||
customStarUi={customStarUi}
|
|
||||||
/>
|
|
||||||
</AppDndContext>
|
</AppDndContext>
|
||||||
</ThemeLocaleProvider>
|
</ThemeLocaleProvider>
|
||||||
</React.Suspense>
|
</React.Suspense>
|
||||||
|
@ -0,0 +1,14 @@
|
|||||||
|
import { MenuItemProps } from '@chakra-ui/react';
|
||||||
|
import { atom } from 'nanostores';
|
||||||
|
|
||||||
|
export type CustomStarUi = {
|
||||||
|
on: {
|
||||||
|
icon: MenuItemProps['icon'];
|
||||||
|
text: string;
|
||||||
|
};
|
||||||
|
off: {
|
||||||
|
icon: MenuItemProps['icon'];
|
||||||
|
text: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
export const $customStarUI = atom<CustomStarUi | undefined>(undefined);
|
@ -0,0 +1,4 @@
|
|||||||
|
import { atom } from 'nanostores';
|
||||||
|
import { ReactNode } from 'react';
|
||||||
|
|
||||||
|
export const $headerComponent = atom<ReactNode | undefined>(undefined);
|
3
invokeai/frontend/web/src/app/store/nanostores/index.ts
Normal file
3
invokeai/frontend/web/src/app/store/nanostores/index.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
/**
|
||||||
|
* For non-serializable data that needs to be available throughout the app, or when redux is not appropriate, use nanostores.
|
||||||
|
*/
|
@ -86,10 +86,7 @@ export const store = configureStore({
|
|||||||
.concat(autoBatchEnhancer());
|
.concat(autoBatchEnhancer());
|
||||||
},
|
},
|
||||||
middleware: (getDefaultMiddleware) =>
|
middleware: (getDefaultMiddleware) =>
|
||||||
getDefaultMiddleware({
|
getDefaultMiddleware()
|
||||||
immutableCheck: false,
|
|
||||||
serializableCheck: false,
|
|
||||||
})
|
|
||||||
.concat(api.middleware)
|
.concat(api.middleware)
|
||||||
.concat(dynamicMiddlewares)
|
.concat(dynamicMiddlewares)
|
||||||
.prepend(listenerMiddleware.middleware),
|
.prepend(listenerMiddleware.middleware),
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
import { MenuItem } from '@chakra-ui/react';
|
import { MenuItem } from '@chakra-ui/react';
|
||||||
|
import { useStore } from '@nanostores/react';
|
||||||
|
import { $customStarUI } from 'app/store/nanostores/customStarUI';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import {
|
import {
|
||||||
imagesToChangeSelected,
|
imagesToChangeSelected,
|
||||||
@ -12,12 +14,11 @@ import {
|
|||||||
useStarImagesMutation,
|
useStarImagesMutation,
|
||||||
useUnstarImagesMutation,
|
useUnstarImagesMutation,
|
||||||
} from '../../../../services/api/endpoints/images';
|
} from '../../../../services/api/endpoints/images';
|
||||||
import { uiSelector } from '../../../ui/store/uiSelectors';
|
|
||||||
|
|
||||||
const MultipleSelectionMenuItems = () => {
|
const MultipleSelectionMenuItems = () => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const selection = useAppSelector((state) => state.gallery.selection);
|
const selection = useAppSelector((state) => state.gallery.selection);
|
||||||
const { customStarUi } = useAppSelector(uiSelector);
|
const customStarUi = useStore($customStarUI);
|
||||||
|
|
||||||
const [starImages] = useStarImagesMutation();
|
const [starImages] = useStarImagesMutation();
|
||||||
const [unstarImages] = useUnstarImagesMutation();
|
const [unstarImages] = useUnstarImagesMutation();
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import { Flex, MenuItem, Spinner } from '@chakra-ui/react';
|
import { Flex, MenuItem, Spinner } from '@chakra-ui/react';
|
||||||
|
import { useStore } from '@nanostores/react';
|
||||||
import { useAppToaster } from 'app/components/Toaster';
|
import { useAppToaster } from 'app/components/Toaster';
|
||||||
|
import { $customStarUI } from 'app/store/nanostores/customStarUI';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { setInitialCanvasImage } from 'features/canvas/store/canvasSlice';
|
import { setInitialCanvasImage } from 'features/canvas/store/canvasSlice';
|
||||||
import {
|
import {
|
||||||
@ -7,6 +9,7 @@ import {
|
|||||||
isModalOpenChanged,
|
isModalOpenChanged,
|
||||||
} from 'features/changeBoardModal/store/slice';
|
} from 'features/changeBoardModal/store/slice';
|
||||||
import { imagesToDeleteSelected } from 'features/deleteImageModal/store/slice';
|
import { imagesToDeleteSelected } from 'features/deleteImageModal/store/slice';
|
||||||
|
import { workflowLoadRequested } from 'features/nodes/store/actions';
|
||||||
import { useRecallParameters } from 'features/parameters/hooks/useRecallParameters';
|
import { useRecallParameters } from 'features/parameters/hooks/useRecallParameters';
|
||||||
import { initialImageSelected } from 'features/parameters/store/actions';
|
import { initialImageSelected } from 'features/parameters/store/actions';
|
||||||
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
|
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
|
||||||
@ -32,10 +35,8 @@ import {
|
|||||||
useUnstarImagesMutation,
|
useUnstarImagesMutation,
|
||||||
} from 'services/api/endpoints/images';
|
} from 'services/api/endpoints/images';
|
||||||
import { ImageDTO } from 'services/api/types';
|
import { ImageDTO } from 'services/api/types';
|
||||||
import { sentImageToCanvas, sentImageToImg2Img } from '../../store/actions';
|
|
||||||
import { workflowLoadRequested } from 'features/nodes/store/actions';
|
|
||||||
import { configSelector } from '../../../system/store/configSelectors';
|
import { configSelector } from '../../../system/store/configSelectors';
|
||||||
import { uiSelector } from '../../../ui/store/uiSelectors';
|
import { sentImageToCanvas, sentImageToImg2Img } from '../../store/actions';
|
||||||
|
|
||||||
type SingleSelectionMenuItemsProps = {
|
type SingleSelectionMenuItemsProps = {
|
||||||
imageDTO: ImageDTO;
|
imageDTO: ImageDTO;
|
||||||
@ -51,7 +52,7 @@ const SingleSelectionMenuItems = (props: SingleSelectionMenuItemsProps) => {
|
|||||||
|
|
||||||
const isCanvasEnabled = useFeatureStatus('unifiedCanvas').isFeatureEnabled;
|
const isCanvasEnabled = useFeatureStatus('unifiedCanvas').isFeatureEnabled;
|
||||||
const { shouldFetchMetadataFromApi } = useAppSelector(configSelector);
|
const { shouldFetchMetadataFromApi } = useAppSelector(configSelector);
|
||||||
const { customStarUi } = useAppSelector(uiSelector);
|
const customStarUi = useStore($customStarUI);
|
||||||
|
|
||||||
const { metadata, workflow, isLoading } = useGetImageMetadataFromFileQuery(
|
const { metadata, workflow, isLoading } = useGetImageMetadataFromFileQuery(
|
||||||
{ image: imageDTO, shouldFetchMetadataFromApi },
|
{ image: imageDTO, shouldFetchMetadataFromApi },
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
import { Box, Flex } from '@chakra-ui/react';
|
import { Box, Flex } from '@chakra-ui/react';
|
||||||
|
import { useStore } from '@nanostores/react';
|
||||||
|
import { $customStarUI } from 'app/store/nanostores/customStarUI';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import IAIDndImage from 'common/components/IAIDndImage';
|
import IAIDndImage from 'common/components/IAIDndImage';
|
||||||
import IAIFillSkeleton from 'common/components/IAIFillSkeleton';
|
import IAIFillSkeleton from 'common/components/IAIFillSkeleton';
|
||||||
@ -10,6 +12,7 @@ import {
|
|||||||
} from 'features/dnd/types';
|
} from 'features/dnd/types';
|
||||||
import { useMultiselect } from 'features/gallery/hooks/useMultiselect';
|
import { useMultiselect } from 'features/gallery/hooks/useMultiselect';
|
||||||
import { MouseEvent, memo, useCallback, useMemo, useState } from 'react';
|
import { MouseEvent, memo, useCallback, useMemo, useState } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { FaTrash } from 'react-icons/fa';
|
import { FaTrash } from 'react-icons/fa';
|
||||||
import { MdStar, MdStarBorder } from 'react-icons/md';
|
import { MdStar, MdStarBorder } from 'react-icons/md';
|
||||||
import {
|
import {
|
||||||
@ -18,8 +21,6 @@ import {
|
|||||||
useUnstarImagesMutation,
|
useUnstarImagesMutation,
|
||||||
} from 'services/api/endpoints/images';
|
} from 'services/api/endpoints/images';
|
||||||
import IAIDndImageIcon from '../../../../common/components/IAIDndImageIcon';
|
import IAIDndImageIcon from '../../../../common/components/IAIDndImageIcon';
|
||||||
import { uiSelector } from '../../../ui/store/uiSelectors';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
|
|
||||||
interface HoverableImageProps {
|
interface HoverableImageProps {
|
||||||
imageName: string;
|
imageName: string;
|
||||||
@ -35,7 +36,7 @@ const GalleryImage = (props: HoverableImageProps) => {
|
|||||||
const { handleClick, isSelected, selection, selectionCount } =
|
const { handleClick, isSelected, selection, selectionCount } =
|
||||||
useMultiselect(imageDTO);
|
useMultiselect(imageDTO);
|
||||||
|
|
||||||
const { customStarUi } = useAppSelector(uiSelector);
|
const customStarUi = useStore($customStarUI);
|
||||||
|
|
||||||
const handleDelete = useCallback(
|
const handleDelete = useCallback(
|
||||||
(e: MouseEvent<HTMLButtonElement>) => {
|
(e: MouseEvent<HTMLButtonElement>) => {
|
||||||
|
@ -4,7 +4,7 @@ import { initialImageChanged } from 'features/parameters/store/generationSlice';
|
|||||||
import { SchedulerParam } from 'features/parameters/types/parameterSchemas';
|
import { SchedulerParam } from 'features/parameters/types/parameterSchemas';
|
||||||
import { setActiveTabReducer } from './extraReducers';
|
import { setActiveTabReducer } from './extraReducers';
|
||||||
import { InvokeTabName } from './tabMap';
|
import { InvokeTabName } from './tabMap';
|
||||||
import { CustomStarUi, UIState } from './uiTypes';
|
import { UIState } from './uiTypes';
|
||||||
|
|
||||||
export const initialUIState: UIState = {
|
export const initialUIState: UIState = {
|
||||||
activeTab: 0,
|
activeTab: 0,
|
||||||
@ -19,7 +19,6 @@ export const initialUIState: UIState = {
|
|||||||
favoriteSchedulers: [],
|
favoriteSchedulers: [],
|
||||||
globalContextMenuCloseTrigger: 0,
|
globalContextMenuCloseTrigger: 0,
|
||||||
panels: {},
|
panels: {},
|
||||||
customStarUi: undefined,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const uiSlice = createSlice({
|
export const uiSlice = createSlice({
|
||||||
@ -71,9 +70,6 @@ export const uiSlice = createSlice({
|
|||||||
) => {
|
) => {
|
||||||
state.panels[action.payload.name] = action.payload.value;
|
state.panels[action.payload.name] = action.payload.value;
|
||||||
},
|
},
|
||||||
setCustomStarUi: (state, action: PayloadAction<CustomStarUi>) => {
|
|
||||||
state.customStarUi = action.payload;
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
extraReducers(builder) {
|
extraReducers(builder) {
|
||||||
builder.addCase(initialImageChanged, (state) => {
|
builder.addCase(initialImageChanged, (state) => {
|
||||||
@ -95,7 +91,6 @@ export const {
|
|||||||
setShouldAutoChangeDimensions,
|
setShouldAutoChangeDimensions,
|
||||||
contextMenusClosed,
|
contextMenusClosed,
|
||||||
panelsChanged,
|
panelsChanged,
|
||||||
setCustomStarUi,
|
|
||||||
} = uiSlice.actions;
|
} = uiSlice.actions;
|
||||||
|
|
||||||
export default uiSlice.reducer;
|
export default uiSlice.reducer;
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import { MenuItemProps } from '@chakra-ui/react';
|
|
||||||
import { SchedulerParam } from 'features/parameters/types/parameterSchemas';
|
import { SchedulerParam } from 'features/parameters/types/parameterSchemas';
|
||||||
|
|
||||||
export type Coordinates = {
|
export type Coordinates = {
|
||||||
@ -13,17 +12,6 @@ export type Dimensions = {
|
|||||||
|
|
||||||
export type Rect = Coordinates & Dimensions;
|
export type Rect = Coordinates & Dimensions;
|
||||||
|
|
||||||
export type CustomStarUi = {
|
|
||||||
on: {
|
|
||||||
icon: MenuItemProps['icon'];
|
|
||||||
text: string;
|
|
||||||
};
|
|
||||||
off: {
|
|
||||||
icon: MenuItemProps['icon'];
|
|
||||||
text: string;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export interface UIState {
|
export interface UIState {
|
||||||
activeTab: number;
|
activeTab: number;
|
||||||
shouldShowImageDetails: boolean;
|
shouldShowImageDetails: boolean;
|
||||||
@ -37,5 +25,4 @@ export interface UIState {
|
|||||||
favoriteSchedulers: SchedulerParam[];
|
favoriteSchedulers: SchedulerParam[];
|
||||||
globalContextMenuCloseTrigger: number;
|
globalContextMenuCloseTrigger: number;
|
||||||
panels: Record<string, string>;
|
panels: Record<string, string>;
|
||||||
customStarUi?: CustomStarUi;
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user