mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
can delete and rename boards
This commit is contained in:
parent
d306a84447
commit
8aac683319
@ -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 (
|
||||
<Flex
|
||||
onClick={handleCreateBoard}
|
||||
sx={{
|
||||
flexDir: 'column',
|
||||
justifyContent: 'space-between',
|
||||
|
@ -0,0 +1,45 @@
|
||||
import { Flex, Icon, Text } from '@chakra-ui/react';
|
||||
import { FaImages } from 'react-icons/fa';
|
||||
import { boardIdSelected } from '../../store/boardSlice';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
const AllImagesBoard = ({ isSelected }: { isSelected: boolean }) => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const handleAllImagesBoardClick = () => {
|
||||
dispatch(boardIdSelected(null));
|
||||
};
|
||||
|
||||
return (
|
||||
<Flex
|
||||
sx={{
|
||||
flexDir: 'column',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
cursor: 'pointer',
|
||||
w: 'full',
|
||||
h: 'full',
|
||||
gap: 1,
|
||||
}}
|
||||
onClick={handleAllImagesBoardClick}
|
||||
>
|
||||
<Flex
|
||||
sx={{
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
borderWidth: '1px',
|
||||
borderRadius: 'base',
|
||||
borderColor: isSelected ? 'base.500' : 'base.800',
|
||||
w: 'full',
|
||||
h: 'full',
|
||||
aspectRatio: '1/1',
|
||||
}}
|
||||
>
|
||||
<Icon boxSize={8} color="base.700" as={FaImages} />
|
||||
</Flex>
|
||||
<Text sx={{ color: 'base.200', fontSize: 'xs' }}>All Images</Text>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default AllImagesBoard;
|
@ -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 (
|
||||
<OverlayScrollbarsComponent
|
||||
@ -42,8 +46,13 @@ const BoardsList = () => {
|
||||
}}
|
||||
>
|
||||
<AddBoardButton />
|
||||
<AllImagesBoard isSelected={selectedBoardId === null} />
|
||||
{boards.map((board) => (
|
||||
<HoverableBoard key={board.board_id} board={board} />
|
||||
<HoverableBoard
|
||||
key={board.board_id}
|
||||
board={board}
|
||||
isSelected={selectedBoardId === board.board_id}
|
||||
/>
|
||||
))}
|
||||
</Grid>
|
||||
</OverlayScrollbarsComponent>
|
||||
|
@ -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 (
|
||||
<Box sx={{ touchAction: 'none' }}>
|
||||
<ContextMenu<HTMLDivElement>
|
||||
@ -51,10 +53,11 @@ const HoverableBoard = memo(({ board }: HoverableBoardProps) => {
|
||||
renderMenu={() => (
|
||||
<MenuList sx={{ visibility: 'visible !important' }}>
|
||||
<MenuItem
|
||||
icon={<ExternalLinkIcon />}
|
||||
// onClickCapture={handleOpenInNewTab}
|
||||
sx={{ color: 'error.300' }}
|
||||
icon={<FaTrash />}
|
||||
onClickCapture={handleDeleteBoard}
|
||||
>
|
||||
Sample Menu Item
|
||||
Delete Board
|
||||
</MenuItem>
|
||||
</MenuList>
|
||||
)}
|
||||
@ -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) => {
|
||||
}}
|
||||
>
|
||||
<Flex
|
||||
onClick={handleSelectBoard}
|
||||
sx={{
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
borderWidth: '1px',
|
||||
borderRadius: 'base',
|
||||
borderColor: 'base.800',
|
||||
borderColor: isSelected ? 'base.500' : 'base.800',
|
||||
w: 'full',
|
||||
h: 'full',
|
||||
aspectRatio: '1/1',
|
||||
@ -102,7 +105,26 @@ const HoverableBoard = memo(({ board }: HoverableBoardProps) => {
|
||||
<Icon boxSize={8} color="base.700" as={FaFolder} />
|
||||
)}
|
||||
</Flex>
|
||||
<Text sx={{ color: 'base.200', fontSize: 'xs' }}>{board_name}</Text>
|
||||
|
||||
<Editable
|
||||
defaultValue={board_name}
|
||||
submitOnBlur={false}
|
||||
onSubmit={(nextValue) => {
|
||||
handleUpdateBoardName(nextValue);
|
||||
}}
|
||||
>
|
||||
<EditablePreview
|
||||
sx={{ color: 'base.200', fontSize: 'xs', textAlign: 'left' }}
|
||||
/>
|
||||
<EditableInput
|
||||
sx={{
|
||||
color: 'base.200',
|
||||
fontSize: 'xs',
|
||||
textAlign: 'left',
|
||||
borderColor: 'base.500',
|
||||
}}
|
||||
/>
|
||||
</Editable>
|
||||
</Flex>
|
||||
)}
|
||||
</ContextMenu>
|
||||
|
@ -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<ImageDTO | BoardDTO> = [];
|
||||
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,9 +347,7 @@ const ImageGalleryContent = () => {
|
||||
data={items}
|
||||
endReached={handleEndReached}
|
||||
scrollerRef={(ref) => setScrollerRef(ref)}
|
||||
itemContent={(index, item) => {
|
||||
if (isImageDTO(item)) {
|
||||
return (
|
||||
itemContent={(index, item) => (
|
||||
<Flex sx={{ pb: 2 }}>
|
||||
<HoverableImage
|
||||
key={`${item.image_name}-${item.thumbnail_url}`}
|
||||
@ -365,15 +357,7 @@ const ImageGalleryContent = () => {
|
||||
}
|
||||
/>
|
||||
</Flex>
|
||||
);
|
||||
} else if (isBoardDTO(item)) {
|
||||
return (
|
||||
<Flex sx={{ pb: 2 }}>
|
||||
<HoverableBoard key={item.board_id} board={item} />
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
}}
|
||||
)}
|
||||
/>
|
||||
) : (
|
||||
<VirtuosoGrid
|
||||
@ -385,9 +369,7 @@ const ImageGalleryContent = () => {
|
||||
List: ListContainer,
|
||||
}}
|
||||
scrollerRef={setScroller}
|
||||
itemContent={(index, item) => {
|
||||
if (isImageDTO(item)) {
|
||||
return (
|
||||
itemContent={(index, item) => (
|
||||
<HoverableImage
|
||||
key={`${item.image_name}-${item.thumbnail_url}`}
|
||||
image={item}
|
||||
@ -395,13 +377,7 @@ const ImageGalleryContent = () => {
|
||||
selectedImage?.image_name === item?.image_name
|
||||
}
|
||||
/>
|
||||
);
|
||||
} else if (isBoardDTO(item)) {
|
||||
return (
|
||||
<HoverableBoard key={item.board_id} board={item} />
|
||||
);
|
||||
}
|
||||
}}
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
|
@ -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<BoardDTO>({
|
||||
selectId: (board) => board.board_id,
|
||||
@ -26,7 +31,7 @@ type AdditionalBoardsState = {
|
||||
export const initialBoardsState =
|
||||
boardsAdapter.getInitialState<AdditionalBoardsState>({
|
||||
offset: 0,
|
||||
limit: 0,
|
||||
limit: 50,
|
||||
total: 0,
|
||||
isLoading: false,
|
||||
selectedBoardId: null,
|
||||
@ -47,7 +52,7 @@ const boardsSlice = createSlice({
|
||||
boardRemoved: (state, action: PayloadAction<string>) => {
|
||||
boardsAdapter.removeOne(state, action.payload);
|
||||
},
|
||||
boardIdSelected: (state, action: PayloadAction<string>) => {
|
||||
boardIdSelected: (state, action: PayloadAction<string | null>) => {
|
||||
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);
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
);
|
||||
|
Loading…
Reference in New Issue
Block a user