From 822017b6786abebd203432277a2f70b81cd82487 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Fri, 28 Jun 2024 09:53:11 +1000 Subject: [PATCH] feat(ui): handle edge cases when archiving/deleting boards If the currently selected or auto-add board is archived or deleted, we should reset them. There are some edge cases taht weren't handled in the previous implementation. All handling of this logic is moved to the (renamed) listener. --- .../middleware/listenerMiddleware/index.ts | 4 +- .../addArchivedOrDeletedBoardListener.ts | 48 +++++++++++++++++++ .../listeners/checkAutoAddBoardVisible.ts | 25 ---------- .../components/Boards/BoardContextMenu.tsx | 4 +- .../components/GallerySettingsPopover.tsx | 2 - .../web/src/features/gallery/store/actions.ts | 2 - .../features/gallery/store/gallerySlice.ts | 32 +------------ 7 files changed, 52 insertions(+), 65 deletions(-) create mode 100644 invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/addArchivedOrDeletedBoardListener.ts delete mode 100644 invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/checkAutoAddBoardVisible.ts diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts index ee66c239274..48e7fa800ce 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts @@ -51,7 +51,7 @@ import { addUpscaleRequestedListener } from 'app/store/middleware/listenerMiddle import { addWorkflowLoadRequestedListener } from 'app/store/middleware/listenerMiddleware/listeners/workflowLoadRequested'; import type { AppDispatch, RootState } from 'app/store/store'; -import { addCheckAutoAddBoardVisibleListener } from './listeners/checkAutoAddBoardVisible'; +import { addArchivedOrDeletedBoardListener } from './listeners/addArchivedOrDeletedBoardListener'; export const listenerMiddleware = createListenerMiddleware(); @@ -118,7 +118,7 @@ addControlNetAutoProcessListener(startAppListening); addImageAddedToBoardFulfilledListener(startAppListening); addImageRemovedFromBoardFulfilledListener(startAppListening); addBoardIdSelectedListener(startAppListening); -addCheckAutoAddBoardVisibleListener(startAppListening); +addArchivedOrDeletedBoardListener(startAppListening); // Node schemas addGetOpenAPISchemaListener(startAppListening); diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/addArchivedOrDeletedBoardListener.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/addArchivedOrDeletedBoardListener.ts new file mode 100644 index 00000000000..0915929245d --- /dev/null +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/addArchivedOrDeletedBoardListener.ts @@ -0,0 +1,48 @@ +import { isAnyOf } from '@reduxjs/toolkit'; +import type { AppStartListening } from 'app/store/middleware/listenerMiddleware'; +import { selectListBoardsQueryArgs } from 'features/gallery/store/gallerySelectors'; +import { + autoAddBoardIdChanged, + boardIdSelected, + galleryViewChanged, + shouldShowArchivedBoardsChanged, +} from 'features/gallery/store/gallerySlice'; +import { boardsApi } from 'services/api/endpoints/boards'; +import { imagesApi } from 'services/api/endpoints/images'; + +export const addArchivedOrDeletedBoardListener = (startAppListening: AppStartListening) => { + startAppListening({ + matcher: isAnyOf( + // Updating a board may change its archived status + boardsApi.endpoints.updateBoard.matchFulfilled, + // If the selected/auto-add board was deleted from a different session, we'll only know during the list request, + boardsApi.endpoints.listAllBoards.matchFulfilled, + // If a board is deleted, we'll need to reset the auto-add board + imagesApi.endpoints.deleteBoard.matchFulfilled, + imagesApi.endpoints.deleteBoardAndImages.matchFulfilled, + // When we change the visibility of archived boards, we may need to reset the auto-add board + shouldShowArchivedBoardsChanged + ), + effect: async (action, { dispatch, getState }) => { + /** + * The auto-add board shouldn't be set to an archived board or deleted board. When we archive a board, delete + * a board, or change a the archived board visibility flag, we may need to reset the auto-add board. + */ + + const state = getState(); + const queryArgs = selectListBoardsQueryArgs(state); + const queryResult = boardsApi.endpoints.listAllBoards.select(queryArgs)(state); + const autoAddBoardId = state.gallery.autoAddBoardId; + + if (!queryResult.data) { + return; + } + + if (!queryResult.data.find((board) => board.board_id === autoAddBoardId)) { + dispatch(autoAddBoardIdChanged('none')); + dispatch(boardIdSelected({ boardId: 'none' })); + dispatch(galleryViewChanged('images')); + } + }, + }); +}; diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/checkAutoAddBoardVisible.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/checkAutoAddBoardVisible.ts deleted file mode 100644 index 05d4cd7eb6a..00000000000 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/checkAutoAddBoardVisible.ts +++ /dev/null @@ -1,25 +0,0 @@ -import type { AppStartListening } from 'app/store/middleware/listenerMiddleware'; -import { checkAutoAddBoardVisible } from 'features/gallery/store/actions'; -import { selectListBoardsQueryArgs } from 'features/gallery/store/gallerySelectors'; -import { autoAddBoardIdChanged } from 'features/gallery/store/gallerySlice'; -import { boardsApi } from 'services/api/endpoints/boards'; - -export const addCheckAutoAddBoardVisibleListener = (startAppListening: AppStartListening) => { - startAppListening({ - actionCreator: checkAutoAddBoardVisible, - effect: async (action, { dispatch, getState }) => { - const state = getState(); - const queryArgs = selectListBoardsQueryArgs(state); - const queryResult = boardsApi.endpoints.listAllBoards.select(queryArgs)(state); - const autoAddBoardId = state.gallery.autoAddBoardId; - - if (!queryResult.data) { - return; - } - - if (!queryResult.data.find((board) => board.board_id === autoAddBoardId)) { - dispatch(autoAddBoardIdChanged('none')); - } - }, - }); -}; diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardContextMenu.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardContextMenu.tsx index 0867de7c281..677919bd300 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardContextMenu.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardContextMenu.tsx @@ -2,7 +2,6 @@ import type { ContextMenuProps } from '@invoke-ai/ui-library'; import { ContextMenu, MenuGroup, MenuItem, MenuList } from '@invoke-ai/ui-library'; import { createSelector } from '@reduxjs/toolkit'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import { checkAutoAddBoardVisible } from 'features/gallery/store/actions'; import { autoAddBoardIdChanged, selectGallerySlice } from 'features/gallery/store/gallerySlice'; import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus'; import { toast } from 'features/toast/toast'; @@ -53,14 +52,13 @@ const BoardContextMenu = ({ board, setBoardToDelete, children }: Props) => { board_id: board.board_id, changes: { archived: true }, }).unwrap(); - dispatch(checkAutoAddBoardVisible()); } catch (error) { toast({ status: 'error', title: 'Unable to archive board', }); } - }, [board.board_id, updateBoard, dispatch]); + }, [board.board_id, updateBoard]); const handleUnarchive = useCallback(() => { updateBoard({ diff --git a/invokeai/frontend/web/src/features/gallery/components/GallerySettingsPopover.tsx b/invokeai/frontend/web/src/features/gallery/components/GallerySettingsPopover.tsx index 322873ceb89..3cc01f0b28b 100644 --- a/invokeai/frontend/web/src/features/gallery/components/GallerySettingsPopover.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/GallerySettingsPopover.tsx @@ -14,7 +14,6 @@ import { Switch, } from '@invoke-ai/ui-library'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import { checkAutoAddBoardVisible } from 'features/gallery/store/actions'; import { alwaysShowImageSizeBadgeChanged, autoAssignBoardOnClickChanged, @@ -69,7 +68,6 @@ const GallerySettingsPopover = () => { const handleChangeShouldShowArchivedBoardsChanged = useCallback( (e: ChangeEvent) => { dispatch(shouldShowArchivedBoardsChanged(e.target.checked)); - dispatch(checkAutoAddBoardVisible()); }, [dispatch] ); diff --git a/invokeai/frontend/web/src/features/gallery/store/actions.ts b/invokeai/frontend/web/src/features/gallery/store/actions.ts index 66e7ab7f23d..0b42890a261 100644 --- a/invokeai/frontend/web/src/features/gallery/store/actions.ts +++ b/invokeai/frontend/web/src/features/gallery/store/actions.ts @@ -5,5 +5,3 @@ export const sentImageToCanvas = createAction('gallery/sentImageToCanvas'); export const sentImageToImg2Img = createAction('gallery/sentImageToImg2Img'); export const imageDownloaded = createAction('gallery/imageDownloaded'); - -export const checkAutoAddBoardVisible = createAction('gallery/checkAutoAddBoardVisible'); diff --git a/invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts b/invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts index 8f298a4e9d4..ba96df64f39 100644 --- a/invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts +++ b/invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts @@ -1,9 +1,7 @@ import type { PayloadAction } from '@reduxjs/toolkit'; -import { createSlice, isAnyOf } from '@reduxjs/toolkit'; +import { createSlice } from '@reduxjs/toolkit'; import type { PersistConfig, RootState } from 'app/store/store'; import { uniqBy } from 'lodash-es'; -import { boardsApi } from 'services/api/endpoints/boards'; -import { imagesApi } from 'services/api/endpoints/images'; import type { ImageDTO } from 'services/api/types'; import type { BoardId, ComparisonMode, GalleryState, GalleryView } from './types'; @@ -115,29 +113,6 @@ export const gallerySlice = createSlice({ state.shouldShowArchivedBoards = action.payload; }, }, - extraReducers: (builder) => { - builder.addMatcher(isAnyBoardDeleted, (state, action) => { - const deletedBoardId = action.meta.arg.originalArgs; - if (deletedBoardId === state.selectedBoardId) { - state.selectedBoardId = 'none'; - state.galleryView = 'images'; - } - if (deletedBoardId === state.autoAddBoardId) { - state.autoAddBoardId = 'none'; - } - }); - builder.addMatcher(boardsApi.endpoints.listAllBoards.matchFulfilled, (state, action) => { - const boards = action.payload; - - if (!state.autoAddBoardId) { - return; - } - - if (!boards.map((b) => b.board_id).includes(state.autoAddBoardId)) { - state.autoAddBoardId = 'none'; - } - }); - }, }); export const { @@ -162,11 +137,6 @@ export const { shouldShowArchivedBoardsChanged, } = gallerySlice.actions; -const isAnyBoardDeleted = isAnyOf( - imagesApi.endpoints.deleteBoard.matchFulfilled, - imagesApi.endpoints.deleteBoardAndImages.matchFulfilled -); - export const selectGallerySlice = (state: RootState) => state.gallery; /* eslint-disable-next-line @typescript-eslint/no-explicit-any */