mirror of
https://github.com/invoke-ai/InvokeAI.git
synced 2026-04-23 03:00:31 -04:00
document when to use redux vs nanostore, update change board flow to use nanostore
This commit is contained in:
@@ -1,13 +1,8 @@
|
||||
import type { ComboboxOnChange, ComboboxOption } from '@invoke-ai/ui-library';
|
||||
import { Combobox, ConfirmationAlertDialog, Flex, FormControl, Text } from '@invoke-ai/ui-library';
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { useAssertSingleton } from 'common/hooks/useAssertSingleton';
|
||||
import {
|
||||
changeBoardReset,
|
||||
isModalOpenChanged,
|
||||
selectChangeBoardModalSlice,
|
||||
} from 'features/changeBoardModal/store/slice';
|
||||
import { useChangeBoardModalApi, useChangeBoardModalState } from 'features/changeBoardModal/store/state';
|
||||
import { selectSelectedBoardId } from 'features/gallery/store/gallerySelectors';
|
||||
import { memo, useCallback, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@@ -15,30 +10,16 @@ import { useListAllBoardsQuery } from 'services/api/endpoints/boards';
|
||||
import { useAddImagesToBoardMutation, useRemoveImagesFromBoardMutation } from 'services/api/endpoints/images';
|
||||
import { useAddVideosToBoardMutation, useRemoveVideosFromBoardMutation } from 'services/api/endpoints/videos';
|
||||
|
||||
const selectImagesToChange = createSelector(
|
||||
selectChangeBoardModalSlice,
|
||||
(changeBoardModal) => changeBoardModal.image_names
|
||||
);
|
||||
|
||||
const selectVideosToChange = createSelector(
|
||||
selectChangeBoardModalSlice,
|
||||
(changeBoardModal) => changeBoardModal.video_ids
|
||||
);
|
||||
|
||||
const selectIsModalOpen = createSelector(
|
||||
selectChangeBoardModalSlice,
|
||||
(changeBoardModal) => changeBoardModal.isModalOpen
|
||||
);
|
||||
|
||||
const ChangeBoardModal = () => {
|
||||
useAssertSingleton('ChangeBoardModal');
|
||||
const dispatch = useAppDispatch();
|
||||
const currentBoardId = useAppSelector(selectSelectedBoardId);
|
||||
const [selectedBoardId, setSelectedBoardId] = useState<string | null>();
|
||||
const { data: boards, isFetching } = useListAllBoardsQuery({ include_archived: true });
|
||||
const isModalOpen = useAppSelector(selectIsModalOpen);
|
||||
const imagesToChange = useAppSelector(selectImagesToChange);
|
||||
const videosToChange = useAppSelector(selectVideosToChange);
|
||||
const changeBoardModalState = useChangeBoardModalState();
|
||||
const changeBoardModal = useChangeBoardModalApi();
|
||||
const imagesToChange = changeBoardModalState.imageNames;
|
||||
const videosToChange = changeBoardModalState.videoIds;
|
||||
const isModalOpen = changeBoardModalState.isOpen;
|
||||
const [addImagesToBoard] = useAddImagesToBoardMutation();
|
||||
const [removeImagesFromBoard] = useRemoveImagesFromBoardMutation();
|
||||
const [addVideosToBoard] = useAddVideosToBoardMutation();
|
||||
@@ -61,9 +42,8 @@ const ChangeBoardModal = () => {
|
||||
const value = useMemo(() => options.find((o) => o.value === selectedBoardId), [options, selectedBoardId]);
|
||||
|
||||
const handleClose = useCallback(() => {
|
||||
dispatch(changeBoardReset());
|
||||
dispatch(isModalOpenChanged(false));
|
||||
}, [dispatch]);
|
||||
changeBoardModal.close();
|
||||
}, [changeBoardModal]);
|
||||
|
||||
const handleChangeBoard = useCallback(() => {
|
||||
if (!selectedBoardId || (imagesToChange.length === 0 && videosToChange.length === 0)) {
|
||||
@@ -90,10 +70,10 @@ const ChangeBoardModal = () => {
|
||||
});
|
||||
}
|
||||
}
|
||||
dispatch(changeBoardReset());
|
||||
changeBoardModal.close();
|
||||
}, [
|
||||
addImagesToBoard,
|
||||
dispatch,
|
||||
changeBoardModal,
|
||||
imagesToChange,
|
||||
videosToChange,
|
||||
removeImagesFromBoard,
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
import type { PayloadAction } from '@reduxjs/toolkit';
|
||||
import { createSlice } from '@reduxjs/toolkit';
|
||||
import type { RootState } from 'app/store/store';
|
||||
import type { SliceConfig } from 'app/store/types';
|
||||
import z from 'zod';
|
||||
|
||||
const zChangeBoardModalState = z.object({
|
||||
isModalOpen: z.boolean().default(false),
|
||||
image_names: z.array(z.string()).default(() => []),
|
||||
video_ids: z.array(z.string()).default(() => []),
|
||||
});
|
||||
type ChangeBoardModalState = z.infer<typeof zChangeBoardModalState>;
|
||||
|
||||
const getInitialState = (): ChangeBoardModalState => zChangeBoardModalState.parse({});
|
||||
|
||||
const slice = createSlice({
|
||||
name: 'changeBoardModal',
|
||||
initialState: getInitialState(),
|
||||
reducers: {
|
||||
isModalOpenChanged: (state, action: PayloadAction<boolean>) => {
|
||||
state.isModalOpen = action.payload;
|
||||
},
|
||||
imagesToChangeSelected: (state, action: PayloadAction<string[]>) => {
|
||||
state.image_names = action.payload;
|
||||
},
|
||||
videosToChangeSelected: (state, action: PayloadAction<string[]>) => {
|
||||
state.video_ids = action.payload;
|
||||
},
|
||||
changeBoardReset: (state) => {
|
||||
state.image_names = [];
|
||||
state.isModalOpen = false;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const { isModalOpenChanged, imagesToChangeSelected, videosToChangeSelected, changeBoardReset } = slice.actions;
|
||||
|
||||
export const selectChangeBoardModalSlice = (state: RootState) => state.changeBoardModal;
|
||||
|
||||
export const changeBoardModalSliceConfig: SliceConfig<typeof slice> = {
|
||||
slice,
|
||||
schema: zChangeBoardModalState,
|
||||
getInitialState,
|
||||
};
|
||||
@@ -0,0 +1,70 @@
|
||||
import { useStore } from '@nanostores/react';
|
||||
import { atom } from 'nanostores';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
type ChangeBoardModalOpenArgs = {
|
||||
imageNames?: string[];
|
||||
videoIds?: string[];
|
||||
};
|
||||
|
||||
type ChangeBoardModalState = {
|
||||
isOpen: boolean;
|
||||
imageNames: string[];
|
||||
videoIds: string[];
|
||||
};
|
||||
|
||||
const initialState: ChangeBoardModalState = {
|
||||
isOpen: false,
|
||||
imageNames: [],
|
||||
videoIds: [],
|
||||
};
|
||||
|
||||
const $changeBoardModalState = atom<ChangeBoardModalState>(initialState);
|
||||
|
||||
const openModal = ({ imageNames = [], videoIds = [] }: ChangeBoardModalOpenArgs = {}) => {
|
||||
$changeBoardModalState.set({
|
||||
isOpen: true,
|
||||
imageNames: [...imageNames],
|
||||
videoIds: [...videoIds],
|
||||
});
|
||||
};
|
||||
|
||||
const closeModal = () => {
|
||||
$changeBoardModalState.set(initialState);
|
||||
};
|
||||
|
||||
const setImageNames = (imageNames: string[]) => {
|
||||
const current = $changeBoardModalState.get();
|
||||
$changeBoardModalState.set({ ...current, imageNames: [...imageNames] });
|
||||
};
|
||||
|
||||
const setVideoIds = (videoIds: string[]) => {
|
||||
const current = $changeBoardModalState.get();
|
||||
$changeBoardModalState.set({ ...current, videoIds: [...videoIds] });
|
||||
};
|
||||
|
||||
const resetSelections = () => {
|
||||
const current = $changeBoardModalState.get();
|
||||
$changeBoardModalState.set({ ...current, imageNames: [], videoIds: [] });
|
||||
};
|
||||
|
||||
export const useChangeBoardModalState = () => {
|
||||
return useStore($changeBoardModalState);
|
||||
};
|
||||
|
||||
export const useChangeBoardModalApi = () => {
|
||||
return useMemo(
|
||||
() => ({
|
||||
open: openModal,
|
||||
openWithImages: (imageNames: string[]) => openModal({ imageNames }),
|
||||
openWithVideos: (videoIds: string[]) => openModal({ videoIds }),
|
||||
setImageNames,
|
||||
setVideoIds,
|
||||
resetSelections,
|
||||
close: closeModal,
|
||||
}),
|
||||
[]
|
||||
);
|
||||
};
|
||||
|
||||
export type { ChangeBoardModalState };
|
||||
@@ -1,29 +1,23 @@
|
||||
import { MenuItem } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import {
|
||||
imagesToChangeSelected,
|
||||
isModalOpenChanged,
|
||||
videosToChangeSelected,
|
||||
} from 'features/changeBoardModal/store/slice';
|
||||
import { useChangeBoardModalApi } from 'features/changeBoardModal/store/state';
|
||||
import { useItemDTOContext } from 'features/gallery/contexts/ItemDTOContext';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PiFoldersBold } from 'react-icons/pi';
|
||||
import { isImageDTO } from 'services/api/types';
|
||||
import { isImageDTO, isVideoDTO } from 'services/api/types';
|
||||
|
||||
export const ContextMenuItemChangeBoard = memo(() => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
const itemDTO = useItemDTOContext();
|
||||
const changeBoardModal = useChangeBoardModalApi();
|
||||
|
||||
const onClick = useCallback(() => {
|
||||
if (isImageDTO(itemDTO)) {
|
||||
dispatch(imagesToChangeSelected([itemDTO.image_name]));
|
||||
} else {
|
||||
dispatch(videosToChangeSelected([itemDTO.video_id]));
|
||||
changeBoardModal.openWithImages([itemDTO.image_name]);
|
||||
} else if (isVideoDTO(itemDTO)) {
|
||||
changeBoardModal.openWithVideos([itemDTO.video_id]);
|
||||
}
|
||||
dispatch(isModalOpenChanged(true));
|
||||
}, [dispatch, itemDTO]);
|
||||
}, [changeBoardModal, itemDTO]);
|
||||
|
||||
return (
|
||||
<MenuItem icon={<PiFoldersBold />} onClickCapture={onClick}>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { MenuDivider, MenuItem } from '@invoke-ai/ui-library';
|
||||
import { useStore } from '@nanostores/react';
|
||||
import { $customStarUI } from 'app/store/nanostores/customStarUI';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { imagesToChangeSelected, isModalOpenChanged } from 'features/changeBoardModal/store/slice';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { useChangeBoardModalApi } from 'features/changeBoardModal/store/state';
|
||||
import { useDeleteImageModalApi } from 'features/deleteImageModal/store/state';
|
||||
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
|
||||
import { memo, useCallback } from 'react';
|
||||
@@ -16,10 +16,10 @@ import {
|
||||
|
||||
const MultipleSelectionMenuItems = () => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
const selection = useAppSelector((s) => s.gallery.selection);
|
||||
const customStarUi = useStore($customStarUI);
|
||||
const deleteImageModal = useDeleteImageModalApi();
|
||||
const changeBoardModal = useChangeBoardModalApi();
|
||||
|
||||
const isBulkDownloadEnabled = useFeatureStatus('bulkDownload');
|
||||
|
||||
@@ -28,9 +28,8 @@ const MultipleSelectionMenuItems = () => {
|
||||
const [bulkDownload] = useBulkDownloadImagesMutation();
|
||||
|
||||
const handleChangeBoard = useCallback(() => {
|
||||
dispatch(imagesToChangeSelected(selection.map((s) => s.id)));
|
||||
dispatch(isModalOpenChanged(true));
|
||||
}, [dispatch, selection]);
|
||||
changeBoardModal.openWithImages(selection.map((s) => s.id));
|
||||
}, [changeBoardModal, selection]);
|
||||
|
||||
const handleDeleteSelection = useCallback(() => {
|
||||
deleteImageModal.delete(selection.map((s) => s.id));
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { MenuDivider, MenuItem } from '@invoke-ai/ui-library';
|
||||
import { useStore } from '@nanostores/react';
|
||||
import { $customStarUI } from 'app/store/nanostores/customStarUI';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { isModalOpenChanged, videosToChangeSelected } from 'features/changeBoardModal/store/slice';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { useChangeBoardModalApi } from 'features/changeBoardModal/store/state';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PiFoldersBold, PiStarBold, PiStarFill, PiTrashSimpleBold } from 'react-icons/pi';
|
||||
@@ -10,18 +10,17 @@ import { useDeleteVideosMutation, useStarVideosMutation, useUnstarVideosMutation
|
||||
|
||||
const MultipleSelectionMenuItems = () => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
const selection = useAppSelector((s) => s.gallery.selection);
|
||||
const customStarUi = useStore($customStarUI);
|
||||
const changeBoardModal = useChangeBoardModalApi();
|
||||
|
||||
const [starVideos] = useStarVideosMutation();
|
||||
const [unstarVideos] = useUnstarVideosMutation();
|
||||
const [deleteVideos] = useDeleteVideosMutation();
|
||||
|
||||
const handleChangeBoard = useCallback(() => {
|
||||
dispatch(videosToChangeSelected(selection.map((s) => s.id)));
|
||||
dispatch(isModalOpenChanged(true));
|
||||
}, [dispatch, selection]);
|
||||
changeBoardModal.openWithVideos(selection.map((s) => s.id));
|
||||
}, [changeBoardModal, selection]);
|
||||
|
||||
const handleDeleteSelection = useCallback(() => {
|
||||
// TODO: Add confirm on delete and video usage functionality
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { EMPTY_ARRAY } from 'app/store/constants';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { selectGetVideoIdsQueryArgs } from 'features/gallery/store/gallerySelectors';
|
||||
import { selectAllowVideo } from 'features/system/store/configSlice';
|
||||
import { useGetVideoIdsQuery } from 'services/api/endpoints/videos';
|
||||
import { useDebounce } from 'use-debounce';
|
||||
|
||||
@@ -14,8 +15,12 @@ const getVideoIdsQueryOptions = {
|
||||
} satisfies Parameters<typeof useGetVideoIdsQuery>[1];
|
||||
|
||||
export const useGalleryVideoIds = () => {
|
||||
const isVideoEnabled = useAppSelector(selectAllowVideo);
|
||||
const _queryArgs = useAppSelector(selectGetVideoIdsQueryArgs);
|
||||
const [queryArgs] = useDebounce(_queryArgs, 300);
|
||||
const { videoIds, isLoading, isFetching } = useGetVideoIdsQuery(queryArgs, getVideoIdsQueryOptions);
|
||||
const { videoIds, isLoading, isFetching } = useGetVideoIdsQuery(queryArgs, {
|
||||
...getVideoIdsQueryOptions,
|
||||
skip: !isVideoEnabled,
|
||||
});
|
||||
return { videoIds, isLoading, isFetching, queryArgs };
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user