diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/AddBoardButton.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/AddBoardButton.tsx index 2f60ea2dac..d8828fe736 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/AddBoardButton.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/AddBoardButton.tsx @@ -1,9 +1,19 @@ import { Flex, Icon, Text } from '@chakra-ui/react'; +import { useCallback } from 'react'; import { FaPlus } from 'react-icons/fa'; +import { useAppDispatch } from '../../../../app/store/storeHooks'; +import { boardCreated } from '../../../../services/thunks/board'; const AddBoardButton = () => { + const dispatch = useAppDispatch(); + + const handleCreateBoard = useCallback(() => { + dispatch(boardCreated({ requestBody: 'My Board' })); + }, [dispatch]); + return ( { + const dispatch = useDispatch(); + + const handleAllImagesBoardClick = () => { + dispatch(boardIdSelected(null)); + }; + + return ( + + + + + All Images + + ); +}; + +export default AllImagesBoard; diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList.tsx index 064d40b2d3..8603f28c9c 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList.tsx @@ -2,22 +2,26 @@ import { Grid } from '@chakra-ui/react'; import { createSelector } from '@reduxjs/toolkit'; import { useAppSelector } from 'app/store/storeHooks'; import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; -import { selectBoardsAll } from 'features/gallery/store/boardSlice'; -import { memo } from 'react'; +import { + boardsSelector, + selectBoardsAll, +} from 'features/gallery/store/boardSlice'; +import { memo, useState } from 'react'; import HoverableBoard from './HoverableBoard'; import { OverlayScrollbarsComponent } from 'overlayscrollbars-react'; import AddBoardButton from './AddBoardButton'; +import AllImagesBoard from './AllImagesBoard'; const selector = createSelector( - selectBoardsAll, - (boards) => { - return { boards }; + [selectBoardsAll, boardsSelector], + (boards, boardsState) => { + return { boards, selectedBoardId: boardsState.selectedBoardId }; }, defaultSelectorOptions ); const BoardsList = () => { - const { boards } = useAppSelector(selector); + const { boards, selectedBoardId } = useAppSelector(selector); return ( { }} > + {boards.map((board) => ( - + ))} diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/HoverableBoard.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/HoverableBoard.tsx index fa1caeac7a..4bb63dbb5e 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/HoverableBoard.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/HoverableBoard.tsx @@ -1,49 +1,51 @@ import { Box, + Editable, + EditableInput, + EditablePreview, Flex, Icon, Image, MenuItem, MenuList, - Text, } from '@chakra-ui/react'; -import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import { PropsWithChildren, memo, useCallback, useState } from 'react'; -import { FaFolder, FaImage } from 'react-icons/fa'; +import { useAppDispatch } from 'app/store/storeHooks'; +import { memo, useCallback } from 'react'; +import { FaFolder, FaTrash } from 'react-icons/fa'; import { ContextMenu } from 'chakra-ui-contextmenu'; import { useTranslation } from 'react-i18next'; -import { ExternalLinkIcon } from '@chakra-ui/icons'; -import { useAppToaster } from 'app/components/Toaster'; import { BoardDTO } from 'services/api'; -import { EntityId, createSelector } from '@reduxjs/toolkit'; -import { - selectFilteredImagesIds, - selectImagesById, -} from '../../store/imagesSlice'; -import { RootState } from '../../../../app/store/store'; -import { defaultSelectorOptions } from '../../../../app/store/util/defaultMemoizeOptions'; -import { useSelector } from 'react-redux'; import { IAIImageFallback } from 'common/components/IAIImageFallback'; import { boardIdSelected } from 'features/gallery/store/boardSlice'; +import { boardDeleted, boardUpdated } from '../../../../services/thunks/board'; interface HoverableBoardProps { board: BoardDTO; + isSelected: boolean; } -/** - * Gallery image component with delete/use all/use seed buttons on hover. - */ -const HoverableBoard = memo(({ board }: HoverableBoardProps) => { +const HoverableBoard = memo(({ board, isSelected }: HoverableBoardProps) => { const dispatch = useAppDispatch(); const { board_name, board_id, cover_image_url } = board; - const { t } = useTranslation(); - const handleSelectBoard = useCallback(() => { dispatch(boardIdSelected(board_id)); }, [board_id, dispatch]); + const handleDeleteBoard = useCallback(() => { + dispatch(boardDeleted(board_id)); + }, [board_id, dispatch]); + + const handleUpdateBoardName = (newBoardName: string) => { + dispatch( + boardUpdated({ + boardId: board_id, + requestBody: { board_name: newBoardName }, + }) + ); + }; + return ( @@ -51,10 +53,11 @@ const HoverableBoard = memo(({ board }: HoverableBoardProps) => { renderMenu={() => ( } - // onClickCapture={handleOpenInNewTab} + sx={{ color: 'error.300' }} + icon={} + onClickCapture={handleDeleteBoard} > - Sample Menu Item + Delete Board )} @@ -64,7 +67,6 @@ const HoverableBoard = memo(({ board }: HoverableBoardProps) => { position="relative" key={board_id} userSelect="none" - onClick={handleSelectBoard} ref={ref} sx={{ flexDir: 'column', @@ -77,12 +79,13 @@ const HoverableBoard = memo(({ board }: HoverableBoardProps) => { }} > { )} - {board_name} + + { + handleUpdateBoardName(nextValue); + }} + > + + + )} diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx index d97d814adf..95c37cba61 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx @@ -37,7 +37,7 @@ import { } from 'react'; import { useTranslation } from 'react-i18next'; import { BsPinAngle, BsPinAngleFill } from 'react-icons/bs'; -import { FaFolder, FaImage, FaPlus, FaServer, FaWrench } from 'react-icons/fa'; +import { FaImage, FaServer, FaWrench } from 'react-icons/fa'; import { MdPhotoLibrary } from 'react-icons/md'; import HoverableImage from './HoverableImage'; @@ -55,10 +55,6 @@ import { } from '../store/imagesSlice'; import { receivedPageOfImages } from 'services/thunks/image'; import { boardSelector } from '../store/boardSelectors'; -import { BoardDTO, ImageDTO } from '../../../services/api'; -import { isBoardDTO, isImageDTO } from '../../../services/types/guards'; -import HoverableBoard from './Boards/HoverableBoard'; -import IAIInput from '../../../common/components/IAIInput'; import { boardCreated } from '../../../services/thunks/board'; import BoardsList from './Boards/BoardsList'; import { selectBoardsById } from '../store/boardSlice'; @@ -66,18 +62,16 @@ import { selectBoardsById } from '../store/boardSlice'; const itemSelector = createSelector( [(state: RootState) => state], (state) => { - const { images, boards, gallery } = state; - - let items: Array = []; - let areMoreAvailable = false; - let isLoading = true; + const { images, boards } = state; const { categories } = images; const allImages = selectImagesAll(state); - items = allImages.filter((i) => categories.includes(i.image_category)); - areMoreAvailable = items.length < images.total; - isLoading = images.isLoading; + const items = allImages.filter((i) => + categories.includes(i.image_category) + ); + const areMoreAvailable = items.length < images.total; + const isLoading = images.isLoading; const selectedBoard = boards.selectedBoardId ? selectBoardsById(state, boards.selectedBoardId) @@ -353,27 +347,17 @@ const ImageGalleryContent = () => { data={items} endReached={handleEndReached} scrollerRef={(ref) => setScrollerRef(ref)} - itemContent={(index, item) => { - if (isImageDTO(item)) { - return ( - - - - ); - } else if (isBoardDTO(item)) { - return ( - - - - ); - } - }} + itemContent={(index, item) => ( + + + + )} /> ) : ( { List: ListContainer, }} scrollerRef={setScroller} - itemContent={(index, item) => { - if (isImageDTO(item)) { - return ( - - ); - } else if (isBoardDTO(item)) { - return ( - - ); - } - }} + itemContent={(index, item) => ( + + )} /> )} diff --git a/invokeai/frontend/web/src/features/gallery/store/boardSlice.ts b/invokeai/frontend/web/src/features/gallery/store/boardSlice.ts index de7ea1e828..d2e9a451d3 100644 --- a/invokeai/frontend/web/src/features/gallery/store/boardSlice.ts +++ b/invokeai/frontend/web/src/features/gallery/store/boardSlice.ts @@ -8,7 +8,12 @@ import { import { RootState } from 'app/store/store'; import { BoardDTO } from 'services/api'; import { dateComparator } from 'common/util/dateComparator'; -import { receivedBoards } from '../../../services/thunks/board'; +import { + boardCreated, + boardDeleted, + boardUpdated, + receivedBoards, +} from '../../../services/thunks/board'; export const boardsAdapter = createEntityAdapter({ selectId: (board) => board.board_id, @@ -26,7 +31,7 @@ type AdditionalBoardsState = { export const initialBoardsState = boardsAdapter.getInitialState({ offset: 0, - limit: 0, + limit: 50, total: 0, isLoading: false, selectedBoardId: null, @@ -47,7 +52,7 @@ const boardsSlice = createSlice({ boardRemoved: (state, action: PayloadAction) => { boardsAdapter.removeOne(state, action.payload); }, - boardIdSelected: (state, action: PayloadAction) => { + boardIdSelected: (state, action: PayloadAction) => { state.selectedBoardId = action.payload; }, }, @@ -66,6 +71,19 @@ const boardsSlice = createSlice({ state.total = total; boardsAdapter.upsertMany(state, items); }); + builder.addCase(boardCreated.fulfilled, (state, action) => { + const board = action.payload; + boardsAdapter.upsertOne(state, board); + }); + builder.addCase(boardUpdated.fulfilled, (state, action) => { + const board = action.payload; + boardsAdapter.upsertOne(state, board); + }); + builder.addCase(boardDeleted.pending, (state, action) => { + const boardId = action.meta.arg; + console.log({ boardId }); + boardsAdapter.removeOne(state, boardId); + }); }, }); diff --git a/invokeai/frontend/web/src/services/thunks/board.ts b/invokeai/frontend/web/src/services/thunks/board.ts index 4ead87c4d4..a536a3fdb0 100644 --- a/invokeai/frontend/web/src/services/thunks/board.ts +++ b/invokeai/frontend/web/src/services/thunks/board.ts @@ -21,3 +21,21 @@ export const boardCreated = createAppAsyncThunk( return response; } ); + +export const boardDeleted = createAppAsyncThunk( + 'api/boardDeleted', + async (boardId: string) => { + await BoardsService.deleteBoard({ boardId }); + return boardId; + } +); + +type BoardUpdatedArg = Parameters<(typeof BoardsService)['updateBoard']>[0]; + +export const boardUpdated = createAppAsyncThunk( + 'api/boardUpdated', + async (arg: BoardUpdatedArg) => { + const response = await BoardsService.updateBoard(arg); + return response; + } +);