mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
cleanup and bug fixes
This commit is contained in:
parent
6437ef3f82
commit
9ca6980c7a
@ -12,6 +12,8 @@ class BoardDTO(BoardRecord):
|
||||
"""The URL of the thumbnail of the most recent image in the board."""
|
||||
image_count: int = Field(description="The number of images in the board.")
|
||||
"""The number of images in the board."""
|
||||
is_private: Optional[bool] = Field(description="Whether the board is private.")
|
||||
"""Whether the board is private."""
|
||||
|
||||
|
||||
def board_record_to_dto(board_record: BoardRecord, cover_image_name: Optional[str], image_count: int) -> BoardDTO:
|
||||
|
@ -20,6 +20,7 @@
|
||||
"archiveBoard": "Archive Board",
|
||||
"archived": "Archived",
|
||||
"autoAddBoard": "Auto-Add Board",
|
||||
"boards": "Boards",
|
||||
"bottomMessage": "Deleting this board and its images will reset any features currently using them.",
|
||||
"cancel": "Cancel",
|
||||
"changeBoard": "Change Board",
|
||||
@ -35,8 +36,10 @@
|
||||
"movingImagesToBoard_other": "Moving {{count}} images to board:",
|
||||
"myBoard": "My Board",
|
||||
"noMatching": "No matching Boards",
|
||||
"private": "Private",
|
||||
"searchBoard": "Search Boards...",
|
||||
"selectBoard": "Select a Board",
|
||||
"shared": "Shared",
|
||||
"topMessage": "This board contains images used in the following features:",
|
||||
"unarchiveBoard": "Unarchive Board",
|
||||
"uncategorized": "Uncategorized",
|
||||
|
@ -4,13 +4,17 @@ import { useTranslation } from 'react-i18next';
|
||||
import { PiPlusBold } from 'react-icons/pi';
|
||||
import { useCreateBoardMutation } from 'services/api/endpoints/boards';
|
||||
|
||||
const AddBoardButton = () => {
|
||||
type Props = {
|
||||
privateBoard: boolean;
|
||||
};
|
||||
|
||||
const AddBoardButton = ({ privateBoard }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const [createBoard, { isLoading }] = useCreateBoardMutation();
|
||||
const DEFAULT_BOARD_NAME = t('boards.myBoard');
|
||||
const handleCreateBoard = useCallback(() => {
|
||||
createBoard(DEFAULT_BOARD_NAME);
|
||||
}, [createBoard, DEFAULT_BOARD_NAME]);
|
||||
createBoard({ board_name: DEFAULT_BOARD_NAME, private_board: privateBoard });
|
||||
}, [createBoard, DEFAULT_BOARD_NAME, privateBoard]);
|
||||
|
||||
return (
|
||||
<IconButton
|
||||
@ -19,8 +23,9 @@ const AddBoardButton = () => {
|
||||
tooltip={t('boards.addBoard')}
|
||||
aria-label={t('boards.addBoard')}
|
||||
onClick={handleCreateBoard}
|
||||
size="sm"
|
||||
size="md"
|
||||
data-testid="add-board-button"
|
||||
variant="ghost"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
@ -1,11 +1,14 @@
|
||||
import { Collapse, Flex, Grid, GridItem } from '@invoke-ai/ui-library';
|
||||
import { Box, Collapse, Flex, Icon, Text } from '@invoke-ai/ui-library';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { overlayScrollbarsParams } from 'common/components/OverlayScrollbars/constants';
|
||||
import DeleteBoardModal from 'features/gallery/components/Boards/DeleteBoardModal';
|
||||
import GallerySettingsPopover from 'features/gallery/components/GallerySettingsPopover/GallerySettingsPopover';
|
||||
import { selectListBoardsQueryArgs } from 'features/gallery/store/gallerySelectors';
|
||||
import { OverlayScrollbarsComponent } from 'overlayscrollbars-react';
|
||||
import type { CSSProperties } from 'react';
|
||||
import { memo, useState } from 'react';
|
||||
import { memo, useCallback, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PiCaretUpBold } from 'react-icons/pi';
|
||||
import { useListAllBoardsQuery } from 'services/api/endpoints/boards';
|
||||
import type { BoardDTO } from 'services/api/types';
|
||||
|
||||
@ -19,56 +22,109 @@ const overlayScrollbarsStyles: CSSProperties = {
|
||||
width: '100%',
|
||||
};
|
||||
|
||||
type Props = {
|
||||
isOpen: boolean;
|
||||
};
|
||||
|
||||
const BoardsList = (props: Props) => {
|
||||
const { isOpen } = props;
|
||||
const BoardsListWithPrivate = () => {
|
||||
const selectedBoardId = useAppSelector((s) => s.gallery.selectedBoardId);
|
||||
const boardSearchText = useAppSelector((s) => s.gallery.boardSearchText);
|
||||
const allowPrivateBoards = useAppSelector((s) => s.config.allowPrivateBoards);
|
||||
const queryArgs = useAppSelector(selectListBoardsQueryArgs);
|
||||
const { data: boards } = useListAllBoardsQuery(queryArgs);
|
||||
const filteredBoards = boardSearchText
|
||||
? boards?.filter((board) => board.board_name.toLowerCase().includes(boardSearchText.toLowerCase()))
|
||||
: boards;
|
||||
const filteredPrivateBoards = filteredBoards?.filter((board) => board.is_private);
|
||||
const filteredSharedBoards = boards?.filter((board) => !board.is_private);
|
||||
const [boardToDelete, setBoardToDelete] = useState<BoardDTO>();
|
||||
const [isPrivateBoardsOpen, setIsPrivateBoardsOpen] = useState(true);
|
||||
const [isSharedBoardsOpen, setIsSharedBoardsOpen] = useState(true);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handlePrivateBoardsToggle = useCallback(
|
||||
() => setIsPrivateBoardsOpen(!isPrivateBoardsOpen),
|
||||
[isPrivateBoardsOpen, setIsPrivateBoardsOpen]
|
||||
);
|
||||
const handleSharedBoardsToggle = useCallback(
|
||||
() => setIsSharedBoardsOpen(!isSharedBoardsOpen),
|
||||
[isSharedBoardsOpen, setIsSharedBoardsOpen]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Collapse in={isOpen} animateOpacity>
|
||||
<Flex layerStyle="first" flexDir="column" gap={2} p={2} mt={2} borderRadius="base">
|
||||
<Flex gap={2} alignItems="center">
|
||||
<BoardsSearch />
|
||||
<AddBoardButton />
|
||||
</Flex>
|
||||
<OverlayScrollbarsComponent defer style={overlayScrollbarsStyles} options={overlayScrollbarsParams.options}>
|
||||
<Grid
|
||||
className="list-container"
|
||||
data-testid="boards-list"
|
||||
gridTemplateColumns="repeat(auto-fill, minmax(90px, 1fr))"
|
||||
maxH={346}
|
||||
>
|
||||
<GridItem p={1.5} data-testid="no-board">
|
||||
<NoBoardBoard isSelected={selectedBoardId === 'none'} />
|
||||
</GridItem>
|
||||
{filteredBoards &&
|
||||
filteredBoards.map((board, index) => (
|
||||
<GridItem key={board.board_id} p={1.5} data-testid={`board-${index}`}>
|
||||
<Flex layerStyle="first" flexDir="column" gap={2} p={2} mt={2} borderRadius="base">
|
||||
<Flex gap={2} alignItems="center">
|
||||
<BoardsSearch />
|
||||
<GallerySettingsPopover />
|
||||
</Flex>
|
||||
<OverlayScrollbarsComponent defer style={overlayScrollbarsStyles} options={overlayScrollbarsParams.options}>
|
||||
<Box maxH={346}>
|
||||
{allowPrivateBoards && (
|
||||
<>
|
||||
<Flex borderBottom="1px" borderColor="base.400" my="2" justifyContent="space-between">
|
||||
<Flex onClick={handlePrivateBoardsToggle} gap={2} alignItems="center" cursor="pointer">
|
||||
<Icon
|
||||
as={PiCaretUpBold}
|
||||
boxSize={6}
|
||||
transform={isPrivateBoardsOpen ? 'rotate(0deg)' : 'rotate(180deg)'}
|
||||
transitionProperty="common"
|
||||
transitionDuration="normal"
|
||||
color="base.400"
|
||||
/>
|
||||
<Text fontSize="md" fontWeight="medium">
|
||||
{t('boards.private')}
|
||||
</Text>
|
||||
</Flex>
|
||||
<AddBoardButton privateBoard={true} />
|
||||
</Flex>
|
||||
<Collapse in={isPrivateBoardsOpen} animateOpacity>
|
||||
<Flex direction="column">
|
||||
<NoBoardBoard isSelected={selectedBoardId === 'none'} />
|
||||
{filteredPrivateBoards &&
|
||||
filteredPrivateBoards.map((board) => (
|
||||
<GalleryBoard
|
||||
board={board}
|
||||
isSelected={selectedBoardId === board.board_id}
|
||||
setBoardToDelete={setBoardToDelete}
|
||||
key={board.board_id}
|
||||
/>
|
||||
))}
|
||||
</Flex>
|
||||
</Collapse>
|
||||
</>
|
||||
)}
|
||||
<Flex borderBottom="1px" borderColor="base.400" my="2" justifyContent="space-between">
|
||||
<Flex onClick={handleSharedBoardsToggle} gap={2} alignItems="center" cursor="pointer">
|
||||
<Icon
|
||||
as={PiCaretUpBold}
|
||||
boxSize={6}
|
||||
transform={isSharedBoardsOpen ? 'rotate(0deg)' : 'rotate(180deg)'}
|
||||
transitionProperty="common"
|
||||
transitionDuration="normal"
|
||||
color="base.400"
|
||||
/>
|
||||
<Text fontSize="md" fontWeight="medium">
|
||||
{allowPrivateBoards ? t('boards.shared') : t('boards.boards')}
|
||||
</Text>
|
||||
</Flex>
|
||||
<AddBoardButton privateBoard={false} />
|
||||
</Flex>
|
||||
<Collapse in={isSharedBoardsOpen} animateOpacity>
|
||||
<Flex direction="column">
|
||||
{filteredSharedBoards &&
|
||||
filteredSharedBoards.map((board) => (
|
||||
<GalleryBoard
|
||||
board={board}
|
||||
isSelected={selectedBoardId === board.board_id}
|
||||
setBoardToDelete={setBoardToDelete}
|
||||
key={board.board_id}
|
||||
/>
|
||||
</GridItem>
|
||||
))}
|
||||
</Grid>
|
||||
</OverlayScrollbarsComponent>
|
||||
</Flex>
|
||||
</Collapse>
|
||||
))}
|
||||
</Flex>
|
||||
</Collapse>
|
||||
</Box>
|
||||
</OverlayScrollbarsComponent>
|
||||
</Flex>
|
||||
<DeleteBoardModal boardToDelete={boardToDelete} setBoardToDelete={setBoardToDelete} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(BoardsList);
|
||||
export default memo(BoardsListWithPrivate);
|
||||
|
@ -1,15 +1,13 @@
|
||||
import type { SystemStyleObject } from '@invoke-ai/ui-library';
|
||||
import { Box, Editable, EditableInput, EditablePreview, Flex, Icon, Image, Text, Tooltip } from '@invoke-ai/ui-library';
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { skipToken } from '@reduxjs/toolkit/query';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import IAIDroppable from 'common/components/IAIDroppable';
|
||||
import SelectionOverlay from 'common/components/SelectionOverlay';
|
||||
import type { AddToBoardDropData } from 'features/dnd/types';
|
||||
import AutoAddIcon from 'features/gallery/components/Boards/AutoAddIcon';
|
||||
import BoardContextMenu from 'features/gallery/components/Boards/BoardContextMenu';
|
||||
import { BoardTotalsTooltip } from 'features/gallery/components/Boards/BoardsList/BoardTotalsTooltip';
|
||||
import { autoAddBoardIdChanged, boardIdSelected, selectGallerySlice } from 'features/gallery/store/gallerySlice';
|
||||
import { autoAddBoardIdChanged, boardIdSelected } from 'features/gallery/store/gallerySlice';
|
||||
import { memo, useCallback, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PiArchiveBold, PiImagesSquare } from 'react-icons/pi';
|
||||
@ -19,10 +17,8 @@ import type { BoardDTO } from 'services/api/types';
|
||||
|
||||
const editableInputStyles: SystemStyleObject = {
|
||||
p: 0,
|
||||
_focusVisible: {
|
||||
p: 0,
|
||||
textAlign: 'center',
|
||||
},
|
||||
fontSize: 'md',
|
||||
w: '100%',
|
||||
};
|
||||
|
||||
const ArchivedIcon = () => {
|
||||
@ -43,12 +39,7 @@ const GalleryBoard = ({ board, isSelected, setBoardToDelete }: GalleryBoardProps
|
||||
const dispatch = useAppDispatch();
|
||||
const { t } = useTranslation();
|
||||
const autoAssignBoardOnClick = useAppSelector((s) => s.gallery.autoAssignBoardOnClick);
|
||||
const selectIsSelectedForAutoAdd = useMemo(
|
||||
() => createSelector(selectGallerySlice, (gallery) => board.board_id === gallery.autoAddBoardId),
|
||||
[board.board_id]
|
||||
);
|
||||
|
||||
const isSelectedForAutoAdd = useAppSelector(selectIsSelectedForAutoAdd);
|
||||
const [isHovered, setIsHovered] = useState(false);
|
||||
const handleMouseOver = useCallback(() => {
|
||||
setIsHovered(true);
|
||||
@ -114,16 +105,16 @@ const GalleryBoard = ({ board, isSelected, setBoardToDelete }: GalleryBoardProps
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Box w="full" h="full" userSelect="none">
|
||||
<Box w="full" userSelect="none" px="1">
|
||||
<Flex
|
||||
onMouseOver={handleMouseOver}
|
||||
onMouseOut={handleMouseOut}
|
||||
position="relative"
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
aspectRatio="1/1"
|
||||
borderRadius="base"
|
||||
w="full"
|
||||
h="full"
|
||||
my="2"
|
||||
userSelect="none"
|
||||
>
|
||||
<BoardContextMenu board={board} setBoardToDelete={setBoardToDelete}>
|
||||
{(ref) => (
|
||||
@ -135,68 +126,62 @@ const GalleryBoard = ({ board, isSelected, setBoardToDelete }: GalleryBoardProps
|
||||
ref={ref}
|
||||
onClick={handleSelectBoard}
|
||||
w="full"
|
||||
h="full"
|
||||
position="relative"
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
justifyContent="space-between"
|
||||
borderRadius="base"
|
||||
cursor="pointer"
|
||||
bg="base.800"
|
||||
gap="6"
|
||||
p="1"
|
||||
>
|
||||
{board.archived && <ArchivedIcon />}
|
||||
{coverImage?.thumbnail_url ? (
|
||||
<Image
|
||||
src={coverImage?.thumbnail_url}
|
||||
draggable={false}
|
||||
objectFit="cover"
|
||||
w="full"
|
||||
h="full"
|
||||
maxH="full"
|
||||
borderRadius="base"
|
||||
borderBottomRadius="lg"
|
||||
/>
|
||||
) : (
|
||||
<Flex w="full" h="full" justifyContent="center" alignItems="center">
|
||||
<Icon boxSize={14} as={PiImagesSquare} mt={-6} opacity={0.7} color="base.500" />
|
||||
</Flex>
|
||||
)}
|
||||
{isSelectedForAutoAdd && <AutoAddIcon />}
|
||||
<SelectionOverlay isSelected={isSelected} isSelectedForCompare={false} isHovered={isHovered} />
|
||||
<Flex
|
||||
position="absolute"
|
||||
bottom={0}
|
||||
left={0}
|
||||
p={1}
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
w="full"
|
||||
maxW="full"
|
||||
borderBottomRadius="base"
|
||||
bg={isSelected ? 'invokeBlue.400' : 'base.600'}
|
||||
color={isSelected ? 'base.800' : 'base.100'}
|
||||
lineHeight="short"
|
||||
fontSize="xs"
|
||||
>
|
||||
<Editable
|
||||
value={localBoardName}
|
||||
isDisabled={isUpdateBoardLoading}
|
||||
submitOnBlur={true}
|
||||
onChange={handleChange}
|
||||
onSubmit={handleSubmit}
|
||||
w="full"
|
||||
>
|
||||
<EditablePreview
|
||||
p={0}
|
||||
fontWeight={isSelected ? 'bold' : 'normal'}
|
||||
textAlign="center"
|
||||
overflow="hidden"
|
||||
textOverflow="ellipsis"
|
||||
noOfLines={1}
|
||||
color="inherit"
|
||||
<Flex gap="6">
|
||||
{board.archived && <ArchivedIcon />}
|
||||
{coverImage?.thumbnail_url ? (
|
||||
<Image
|
||||
src={coverImage?.thumbnail_url}
|
||||
draggable={false}
|
||||
objectFit="cover"
|
||||
w="8"
|
||||
h="8"
|
||||
borderRadius="base"
|
||||
borderBottomRadius="lg"
|
||||
/>
|
||||
<EditableInput sx={editableInputStyles} />
|
||||
</Editable>
|
||||
) : (
|
||||
<Flex w="8" h="8" justifyContent="center" alignItems="center">
|
||||
<Icon boxSize={8} as={PiImagesSquare} opacity={0.7} color="base.500" />
|
||||
</Flex>
|
||||
)}
|
||||
|
||||
<SelectionOverlay isSelected={isSelected} isSelectedForCompare={false} isHovered={isHovered} />
|
||||
<Flex
|
||||
p={1}
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
color={isSelected ? 'base.100' : 'base.400'}
|
||||
lineHeight="short"
|
||||
fontSize="md"
|
||||
>
|
||||
<Editable
|
||||
value={localBoardName}
|
||||
isDisabled={isUpdateBoardLoading}
|
||||
submitOnBlur={true}
|
||||
onChange={handleChange}
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
<EditablePreview
|
||||
p={0}
|
||||
fontSize="md"
|
||||
textOverflow="ellipsis"
|
||||
noOfLines={1}
|
||||
color="inherit"
|
||||
w="fit-content"
|
||||
/>
|
||||
<EditableInput sx={editableInputStyles} />
|
||||
</Editable>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Text justifySelf="end" color="base.600">
|
||||
{`${t('boards.imagesWithCount', { count: board.image_count })}`}
|
||||
</Text>
|
||||
|
||||
<IAIDroppable data={droppableData} dropLabel={<Text fontSize="md">{t('unifiedCanvas.move')}</Text>} />
|
||||
</Flex>
|
||||
|
@ -3,7 +3,6 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import IAIDroppable from 'common/components/IAIDroppable';
|
||||
import SelectionOverlay from 'common/components/SelectionOverlay';
|
||||
import type { RemoveFromBoardDropData } from 'features/dnd/types';
|
||||
import AutoAddIcon from 'features/gallery/components/Boards/AutoAddIcon';
|
||||
import { BoardTotalsTooltip } from 'features/gallery/components/Boards/BoardsList/BoardTotalsTooltip';
|
||||
import NoBoardBoardContextMenu from 'features/gallery/components/Boards/NoBoardBoardContextMenu';
|
||||
import { autoAddBoardIdChanged, boardIdSelected } from 'features/gallery/store/gallerySlice';
|
||||
@ -18,7 +17,6 @@ interface Props {
|
||||
|
||||
const NoBoardBoard = memo(({ isSelected }: Props) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const autoAddBoardId = useAppSelector((s) => s.gallery.autoAddBoardId);
|
||||
const autoAssignBoardOnClick = useAppSelector((s) => s.gallery.autoAssignBoardOnClick);
|
||||
const boardName = useBoardName('none');
|
||||
const handleSelectBoard = useCallback(() => {
|
||||
@ -46,16 +44,16 @@ const NoBoardBoard = memo(({ isSelected }: Props) => {
|
||||
);
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Box w="full" userSelect="none">
|
||||
<Box w="full" userSelect="none" px="1">
|
||||
<Flex
|
||||
onMouseOver={handleMouseOver}
|
||||
onMouseOut={handleMouseOut}
|
||||
justifyContent="center"
|
||||
position="relative"
|
||||
alignItems="center"
|
||||
aspectRatio="1/1"
|
||||
borderRadius="base"
|
||||
w="full"
|
||||
h="full"
|
||||
my="2"
|
||||
userSelect="none"
|
||||
>
|
||||
<NoBoardBoardContextMenu>
|
||||
{(ref) => (
|
||||
@ -64,30 +62,22 @@ const NoBoardBoard = memo(({ isSelected }: Props) => {
|
||||
ref={ref}
|
||||
onClick={handleSelectBoard}
|
||||
w="full"
|
||||
position="relative"
|
||||
alignItems="center"
|
||||
borderRadius="base"
|
||||
cursor="pointer"
|
||||
bg="base.800"
|
||||
gap="6"
|
||||
p="1"
|
||||
>
|
||||
<Image
|
||||
src={InvokeLogoSVG}
|
||||
alt="invoke-ai-logo"
|
||||
opacity={0.7}
|
||||
mixBlendMode="overlay"
|
||||
w={8}
|
||||
h={8}
|
||||
minW={8}
|
||||
minH={8}
|
||||
userSelect="none"
|
||||
height="6"
|
||||
width="6"
|
||||
/>
|
||||
{autoAddBoardId === 'none' && <AutoAddIcon />}
|
||||
<Text
|
||||
p={1}
|
||||
lineHeight="short"
|
||||
fontSize="xs"
|
||||
color={isSelected ? 'blue' : 'white'}
|
||||
>
|
||||
<Text fontSize="md" color={isSelected ? 'base.100' : 'base.400'}>
|
||||
{boardName}
|
||||
</Text>
|
||||
<SelectionOverlay isSelected={isSelected} isSelectedForCompare={false} isHovered={isHovered} />
|
||||
|
@ -1,32 +0,0 @@
|
||||
import { Icon } from '@invoke-ai/ui-library';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PiPlusBold } from 'react-icons/pi';
|
||||
import { useCreateBoardMutation } from 'services/api/endpoints/boards';
|
||||
|
||||
type Props = {
|
||||
privateBoard: boolean;
|
||||
};
|
||||
|
||||
const AddBoardButton = ({ privateBoard }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const [createBoard] = useCreateBoardMutation();
|
||||
const DEFAULT_BOARD_NAME = t('boards.myBoard');
|
||||
const handleCreateBoard = useCallback(() => {
|
||||
createBoard({ DEFAULT_BOARD_NAME, privateBoard });
|
||||
}, [createBoard, DEFAULT_BOARD_NAME, privateBoard]);
|
||||
|
||||
return (
|
||||
<Icon
|
||||
as={PiPlusBold}
|
||||
boxSize={6}
|
||||
transitionProperty="common"
|
||||
transitionDuration="normal"
|
||||
color="base.400"
|
||||
onClick={handleCreateBoard}
|
||||
cursor="pointer"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(AddBoardButton);
|
@ -1,22 +0,0 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useGetBoardAssetsTotalQuery, useGetBoardImagesTotalQuery } from 'services/api/endpoints/boards';
|
||||
|
||||
type Props = {
|
||||
board_id: string;
|
||||
isArchived: boolean;
|
||||
};
|
||||
|
||||
export const BoardTotalsTooltip = ({ board_id, isArchived }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const { imagesTotal } = useGetBoardImagesTotalQuery(board_id, {
|
||||
selectFromResult: ({ data }) => {
|
||||
return { imagesTotal: data?.total ?? 0 };
|
||||
},
|
||||
});
|
||||
const { assetsTotal } = useGetBoardAssetsTotalQuery(board_id, {
|
||||
selectFromResult: ({ data }) => {
|
||||
return { assetsTotal: data?.total ?? 0 };
|
||||
},
|
||||
});
|
||||
return `${t('boards.imagesWithCount', { count: imagesTotal })}, ${t('boards.assetsWithCount', { count: assetsTotal })}${isArchived ? ` (${t('boards.archived')})` : ''}`;
|
||||
};
|
@ -1,120 +0,0 @@
|
||||
import { Collapse, Flex, Icon, Text } from '@invoke-ai/ui-library';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { overlayScrollbarsParams } from 'common/components/OverlayScrollbars/constants';
|
||||
import DeleteBoardModal from 'features/gallery/components/Boards/DeleteBoardModal';
|
||||
import GallerySettingsPopover from 'features/gallery/components/GallerySettingsPopover/GallerySettingsPopover';
|
||||
import { selectListBoardsQueryArgs } from 'features/gallery/store/gallerySelectors';
|
||||
import { OverlayScrollbarsComponent } from 'overlayscrollbars-react';
|
||||
import type { CSSProperties } from 'react';
|
||||
import { memo, useState } from 'react';
|
||||
import { PiCaretUpBold, PiPlusBold } from 'react-icons/pi';
|
||||
import { useListAllBoardsQuery } from 'services/api/endpoints/boards';
|
||||
import type { BoardDTO } from 'services/api/types';
|
||||
|
||||
import AddBoardButton from './AddBoardButton';
|
||||
import BoardsSearch from './BoardsSearch';
|
||||
import GalleryBoard from './GalleryBoard';
|
||||
import NoBoardBoard from './NoBoardBoard';
|
||||
|
||||
const overlayScrollbarsStyles: CSSProperties = {
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
};
|
||||
|
||||
const BoardsListWithPrivate = () => {
|
||||
const selectedBoardId = useAppSelector((s) => s.gallery.selectedBoardId);
|
||||
const boardSearchText = useAppSelector((s) => s.gallery.boardSearchText);
|
||||
const queryArgs = useAppSelector(selectListBoardsQueryArgs);
|
||||
const { data: boards } = useListAllBoardsQuery(queryArgs);
|
||||
const filteredPrivateBoards = boardSearchText
|
||||
? boards?.filter((board) => board.is_private && board.board_name.toLowerCase().includes(boardSearchText.toLowerCase()))
|
||||
: boards?.filter((board) => board.is_private);
|
||||
const filteredSharedBoards = boardSearchText
|
||||
? boards?.filter((board) => !board.is_private && board.board_name.toLowerCase().includes(boardSearchText.toLowerCase()))
|
||||
: boards?.filter((board) => !board.is_private);
|
||||
const [boardToDelete, setBoardToDelete] = useState<BoardDTO>();
|
||||
const [isPrivateBoardsOpen, setIsPrivateBoardsOpen] = useState(true);
|
||||
const [isSharedBoardsOpen, setIsSharedBoardsOpen] = useState(true);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Flex layerStyle="first" flexDir="column" gap={2} p={2} mt={2} borderRadius="base">
|
||||
<Flex gap={2} alignItems="center">
|
||||
<BoardsSearch />
|
||||
<GallerySettingsPopover />
|
||||
</Flex>
|
||||
<OverlayScrollbarsComponent defer style={overlayScrollbarsStyles} options={overlayScrollbarsParams.options}>
|
||||
<Flex borderBottom="1px" borderColor="base.400" my="2" justifyContent="space-between">
|
||||
<Flex
|
||||
onClick={() => setIsPrivateBoardsOpen(!isPrivateBoardsOpen)}
|
||||
gap={2}
|
||||
alignItems="center"
|
||||
cursor="pointer"
|
||||
>
|
||||
<Icon
|
||||
as={PiCaretUpBold}
|
||||
boxSize={6}
|
||||
transform={isPrivateBoardsOpen ? 'rotate(0deg)' : 'rotate(180deg)'}
|
||||
transitionProperty="common"
|
||||
transitionDuration="normal"
|
||||
color="base.400"
|
||||
/>
|
||||
<Text fontSize="md" fontWeight="medium">Private</Text>
|
||||
</Flex>
|
||||
<AddBoardButton privateBoard={true} />
|
||||
</Flex>
|
||||
<Collapse in={isPrivateBoardsOpen} animateOpacity>
|
||||
<Flex direction="column">
|
||||
<NoBoardBoard isSelected={selectedBoardId === 'none'} />
|
||||
{filteredPrivateBoards &&
|
||||
filteredPrivateBoards.map((board) => (
|
||||
<GalleryBoard
|
||||
board={board}
|
||||
isSelected={selectedBoardId === board.board_id}
|
||||
setBoardToDelete={setBoardToDelete}
|
||||
key={board.board_id}
|
||||
/>
|
||||
))}
|
||||
</Flex>
|
||||
</Collapse>
|
||||
<Flex borderBottom="1px" borderColor="base.400" my="2" justifyContent="space-between">
|
||||
<Flex
|
||||
onClick={() => setIsSharedBoardsOpen(!isSharedBoardsOpen)}
|
||||
gap={2}
|
||||
alignItems="center"
|
||||
cursor="pointer"
|
||||
>
|
||||
|
||||
<Icon
|
||||
as={PiCaretUpBold}
|
||||
boxSize={6}
|
||||
transform={isSharedBoardsOpen ? 'rotate(0deg)' : 'rotate(180deg)'}
|
||||
transitionProperty="common"
|
||||
transitionDuration="normal"
|
||||
color="base.400"
|
||||
/>
|
||||
<Text fontSize="md" fontWeight="medium">Shared</Text>
|
||||
</Flex>
|
||||
<AddBoardButton privateBoard={false} />
|
||||
</Flex>
|
||||
<Collapse in={isSharedBoardsOpen} animateOpacity>
|
||||
<Flex direction="column">
|
||||
{filteredSharedBoards &&
|
||||
filteredSharedBoards.map((board) => (
|
||||
<GalleryBoard
|
||||
board={board}
|
||||
isSelected={selectedBoardId === board.board_id}
|
||||
setBoardToDelete={setBoardToDelete}
|
||||
key={board.board_id}
|
||||
/>
|
||||
))}
|
||||
</Flex>
|
||||
</Collapse>
|
||||
</OverlayScrollbarsComponent>
|
||||
</Flex>
|
||||
<DeleteBoardModal boardToDelete={boardToDelete} setBoardToDelete={setBoardToDelete} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(BoardsListWithPrivate);
|
@ -1,66 +0,0 @@
|
||||
import { IconButton, Input, InputGroup, InputRightElement } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { boardSearchTextChanged } from 'features/gallery/store/gallerySlice';
|
||||
import type { ChangeEvent, KeyboardEvent } from 'react';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PiXBold } from 'react-icons/pi';
|
||||
|
||||
const BoardsSearch = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const boardSearchText = useAppSelector((s) => s.gallery.boardSearchText);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleBoardSearch = useCallback(
|
||||
(searchTerm: string) => {
|
||||
dispatch(boardSearchTextChanged(searchTerm));
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
const clearBoardSearch = useCallback(() => {
|
||||
dispatch(boardSearchTextChanged(''));
|
||||
}, [dispatch]);
|
||||
|
||||
const handleKeydown = useCallback(
|
||||
(e: KeyboardEvent<HTMLInputElement>) => {
|
||||
// exit search mode on escape
|
||||
if (e.key === 'Escape') {
|
||||
clearBoardSearch();
|
||||
}
|
||||
},
|
||||
[clearBoardSearch]
|
||||
);
|
||||
|
||||
const handleChange = useCallback(
|
||||
(e: ChangeEvent<HTMLInputElement>) => {
|
||||
handleBoardSearch(e.target.value);
|
||||
},
|
||||
[handleBoardSearch]
|
||||
);
|
||||
|
||||
return (
|
||||
<InputGroup>
|
||||
<Input
|
||||
placeholder={t('boards.searchBoard')}
|
||||
value={boardSearchText}
|
||||
onKeyDown={handleKeydown}
|
||||
onChange={handleChange}
|
||||
data-testid="board-search-input"
|
||||
/>
|
||||
{boardSearchText && boardSearchText.length && (
|
||||
<InputRightElement h="full" pe={2}>
|
||||
<IconButton
|
||||
onClick={clearBoardSearch}
|
||||
size="sm"
|
||||
variant="link"
|
||||
aria-label={t('boards.clearSearch')}
|
||||
icon={<PiXBold />}
|
||||
/>
|
||||
</InputRightElement>
|
||||
)}
|
||||
</InputGroup>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(BoardsSearch);
|
@ -1,194 +0,0 @@
|
||||
import type { SystemStyleObject } from '@invoke-ai/ui-library';
|
||||
import { Box, Editable, EditableInput, EditablePreview, Flex, Icon, Image, Text, Tooltip } from '@invoke-ai/ui-library';
|
||||
import { skipToken } from '@reduxjs/toolkit/query';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import IAIDroppable from 'common/components/IAIDroppable';
|
||||
import SelectionOverlay from 'common/components/SelectionOverlay';
|
||||
import type { AddToBoardDropData } from 'features/dnd/types';
|
||||
import BoardContextMenu from 'features/gallery/components/Boards/BoardContextMenu';
|
||||
import { BoardTotalsTooltip } from 'features/gallery/components/Boards/BoardsList/BoardTotalsTooltip';
|
||||
import { autoAddBoardIdChanged, boardIdSelected } from 'features/gallery/store/gallerySlice';
|
||||
import { memo, useCallback, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PiArchiveBold, PiImagesSquare } from 'react-icons/pi';
|
||||
import { useUpdateBoardMutation } from 'services/api/endpoints/boards';
|
||||
import { useGetImageDTOQuery } from 'services/api/endpoints/images';
|
||||
import type { BoardDTO } from 'services/api/types';
|
||||
|
||||
const editableInputStyles: SystemStyleObject = {
|
||||
p: 0,
|
||||
fontSize: 'md',
|
||||
w: '100%',
|
||||
};
|
||||
|
||||
const ArchivedIcon = () => {
|
||||
return (
|
||||
<Box position="absolute" top={1} insetInlineEnd={2} p={0} minW={0}>
|
||||
<Icon as={PiArchiveBold} fill="base.300" filter="drop-shadow(0px 0px 0.1rem var(--invoke-colors-base-800))" />
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
interface GalleryBoardProps {
|
||||
board: BoardDTO;
|
||||
isSelected: boolean;
|
||||
setBoardToDelete: (board?: BoardDTO) => void;
|
||||
}
|
||||
|
||||
const GalleryBoard = ({ board, isSelected, setBoardToDelete }: GalleryBoardProps) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const { t } = useTranslation();
|
||||
const autoAssignBoardOnClick = useAppSelector((s) => s.gallery.autoAssignBoardOnClick);
|
||||
|
||||
const [isHovered, setIsHovered] = useState(false);
|
||||
const handleMouseOver = useCallback(() => {
|
||||
setIsHovered(true);
|
||||
}, []);
|
||||
const handleMouseOut = useCallback(() => {
|
||||
setIsHovered(false);
|
||||
}, []);
|
||||
|
||||
const { currentData: coverImage } = useGetImageDTOQuery(board.cover_image_name ?? skipToken);
|
||||
|
||||
const { board_name, board_id } = board;
|
||||
const [localBoardName, setLocalBoardName] = useState(board_name);
|
||||
|
||||
const handleSelectBoard = useCallback(() => {
|
||||
dispatch(boardIdSelected({ boardId: board_id }));
|
||||
if (autoAssignBoardOnClick && !board.archived) {
|
||||
dispatch(autoAddBoardIdChanged(board_id));
|
||||
}
|
||||
}, [board_id, autoAssignBoardOnClick, dispatch, board.archived]);
|
||||
|
||||
const [updateBoard, { isLoading: isUpdateBoardLoading }] = useUpdateBoardMutation();
|
||||
|
||||
const droppableData: AddToBoardDropData = useMemo(
|
||||
() => ({
|
||||
id: board_id,
|
||||
actionType: 'ADD_TO_BOARD',
|
||||
context: { boardId: board_id },
|
||||
}),
|
||||
[board_id]
|
||||
);
|
||||
|
||||
const handleSubmit = useCallback(
|
||||
async (newBoardName: string) => {
|
||||
// empty strings are not allowed
|
||||
if (!newBoardName.trim()) {
|
||||
setLocalBoardName(board_name);
|
||||
return;
|
||||
}
|
||||
|
||||
// don't updated the board name if it hasn't changed
|
||||
if (newBoardName === board_name) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const { board_name } = await updateBoard({
|
||||
board_id,
|
||||
changes: { board_name: newBoardName },
|
||||
}).unwrap();
|
||||
|
||||
// update local state
|
||||
setLocalBoardName(board_name);
|
||||
} catch {
|
||||
// revert on error
|
||||
setLocalBoardName(board_name);
|
||||
}
|
||||
},
|
||||
[board_id, board_name, updateBoard]
|
||||
);
|
||||
|
||||
const handleChange = useCallback((newBoardName: string) => {
|
||||
setLocalBoardName(newBoardName);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Box w="full" userSelect="none" px="1">
|
||||
<Flex
|
||||
onMouseOver={handleMouseOver}
|
||||
onMouseOut={handleMouseOut}
|
||||
position="relative"
|
||||
alignItems="center"
|
||||
borderRadius="base"
|
||||
w="full"
|
||||
my="2"
|
||||
userSelect="none"
|
||||
>
|
||||
<BoardContextMenu board={board} setBoardToDelete={setBoardToDelete}>
|
||||
{(ref) => (
|
||||
<Tooltip
|
||||
label={<BoardTotalsTooltip board_id={board.board_id} isArchived={Boolean(board.archived)} />}
|
||||
openDelay={1000}
|
||||
>
|
||||
<Flex
|
||||
ref={ref}
|
||||
onClick={handleSelectBoard}
|
||||
w="full"
|
||||
alignItems="center"
|
||||
justifyContent="space-between"
|
||||
borderRadius="base"
|
||||
cursor="pointer"
|
||||
gap="6"
|
||||
p="1"
|
||||
>
|
||||
<Flex gap="6">
|
||||
{board.archived && <ArchivedIcon />}
|
||||
{coverImage?.thumbnail_url ? (
|
||||
<Image
|
||||
src={coverImage?.thumbnail_url}
|
||||
draggable={false}
|
||||
objectFit="cover"
|
||||
w="8"
|
||||
h="8"
|
||||
borderRadius="base"
|
||||
borderBottomRadius="lg"
|
||||
/>
|
||||
) : (
|
||||
<Flex w="8" h="8" justifyContent="center" alignItems="center">
|
||||
<Icon boxSize={8} as={PiImagesSquare} opacity={0.7} color="base.500" />
|
||||
</Flex>
|
||||
)}
|
||||
|
||||
<SelectionOverlay isSelected={isSelected} isSelectedForCompare={false} isHovered={isHovered} />
|
||||
<Flex
|
||||
p={1}
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
color={isSelected ? 'base.100' : 'base.400'}
|
||||
lineHeight="short"
|
||||
fontSize="md"
|
||||
>
|
||||
<Editable
|
||||
value={localBoardName}
|
||||
isDisabled={isUpdateBoardLoading}
|
||||
submitOnBlur={true}
|
||||
onChange={handleChange}
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
<EditablePreview
|
||||
p={0}
|
||||
fontSize="md"
|
||||
textOverflow="ellipsis"
|
||||
noOfLines={1}
|
||||
color="inherit"
|
||||
w="fit-content"
|
||||
/>
|
||||
<EditableInput sx={editableInputStyles} />
|
||||
</Editable>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Text justifySelf="end" color="base.600">{board.image_count} images</Text>
|
||||
|
||||
<IAIDroppable data={droppableData} dropLabel={<Text fontSize="md">{t('unifiedCanvas.move')}</Text>} />
|
||||
</Flex>
|
||||
</Tooltip>
|
||||
)}
|
||||
</BoardContextMenu>
|
||||
</Flex>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(GalleryBoard);
|
@ -1,96 +0,0 @@
|
||||
import { Box, Flex, Image, Text, Tooltip } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import IAIDroppable from 'common/components/IAIDroppable';
|
||||
import SelectionOverlay from 'common/components/SelectionOverlay';
|
||||
import type { RemoveFromBoardDropData } from 'features/dnd/types';
|
||||
import { BoardTotalsTooltip } from 'features/gallery/components/Boards/BoardsList/BoardTotalsTooltip';
|
||||
import NoBoardBoardContextMenu from 'features/gallery/components/Boards/NoBoardBoardContextMenu';
|
||||
import { autoAddBoardIdChanged, boardIdSelected } from 'features/gallery/store/gallerySlice';
|
||||
import InvokeLogoSVG from 'public/assets/images/invoke-symbol-wht-lrg.svg';
|
||||
import { memo, useCallback, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useBoardName } from 'services/api/hooks/useBoardName';
|
||||
|
||||
interface Props {
|
||||
isSelected: boolean;
|
||||
}
|
||||
|
||||
const NoBoardBoard = memo(({ isSelected }: Props) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const autoAssignBoardOnClick = useAppSelector((s) => s.gallery.autoAssignBoardOnClick);
|
||||
const boardName = useBoardName('none');
|
||||
const handleSelectBoard = useCallback(() => {
|
||||
dispatch(boardIdSelected({ boardId: 'none' }));
|
||||
if (autoAssignBoardOnClick) {
|
||||
dispatch(autoAddBoardIdChanged('none'));
|
||||
}
|
||||
}, [dispatch, autoAssignBoardOnClick]);
|
||||
const [isHovered, setIsHovered] = useState(false);
|
||||
|
||||
const handleMouseOver = useCallback(() => {
|
||||
setIsHovered(true);
|
||||
}, []);
|
||||
|
||||
const handleMouseOut = useCallback(() => {
|
||||
setIsHovered(false);
|
||||
}, []);
|
||||
|
||||
const droppableData: RemoveFromBoardDropData = useMemo(
|
||||
() => ({
|
||||
id: 'no_board',
|
||||
actionType: 'REMOVE_FROM_BOARD',
|
||||
}),
|
||||
[]
|
||||
);
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Box w="full" userSelect="none" px="1">
|
||||
<Flex
|
||||
onMouseOver={handleMouseOver}
|
||||
onMouseOut={handleMouseOut}
|
||||
position="relative"
|
||||
alignItems="center"
|
||||
borderRadius="base"
|
||||
w="full"
|
||||
my="2"
|
||||
userSelect="none"
|
||||
>
|
||||
<NoBoardBoardContextMenu>
|
||||
{(ref) => (
|
||||
<Tooltip label={<BoardTotalsTooltip board_id="none" isArchived={false} />} openDelay={1000}>
|
||||
<Flex
|
||||
ref={ref}
|
||||
onClick={handleSelectBoard}
|
||||
w="full"
|
||||
alignItems="center"
|
||||
borderRadius="base"
|
||||
cursor="pointer"
|
||||
gap="6"
|
||||
p="1"
|
||||
>
|
||||
<Image
|
||||
src={InvokeLogoSVG}
|
||||
alt="invoke-ai-logo"
|
||||
opacity={0.7}
|
||||
mixBlendMode="overlay"
|
||||
userSelect="none"
|
||||
height="6"
|
||||
width="6"
|
||||
/>
|
||||
<Text fontSize="md" color={isSelected ? 'base.100' : 'base.400'}>
|
||||
{boardName}
|
||||
</Text>
|
||||
<SelectionOverlay isSelected={isSelected} isSelectedForCompare={false} isHovered={isHovered} />
|
||||
<IAIDroppable data={droppableData} dropLabel={<Text fontSize="md">{t('unifiedCanvas.move')}</Text>} />
|
||||
</Flex>
|
||||
</Tooltip>
|
||||
)}
|
||||
</NoBoardBoardContextMenu>
|
||||
</Flex>
|
||||
</Box>
|
||||
);
|
||||
});
|
||||
|
||||
NoBoardBoard.displayName = 'HoverableBoard';
|
||||
|
||||
export default memo(NoBoardBoard);
|
@ -9,7 +9,6 @@ import { PiImagesBold } from 'react-icons/pi';
|
||||
import { RiServerLine } from 'react-icons/ri';
|
||||
|
||||
import BoardsList from './Boards/BoardsList/BoardsList';
|
||||
import BoardsListWithPrivate from './Boards/BoardsListWithPrivate/BoardsListWithPrivate';
|
||||
import GalleryBoardName from './GalleryBoardName';
|
||||
import GallerySettingsPopover from './GallerySettingsPopover/GallerySettingsPopover';
|
||||
import GalleryImageGrid from './ImageGrid/GalleryImageGrid';
|
||||
@ -19,7 +18,6 @@ import { GallerySearch } from './ImageGrid/GallerySearch';
|
||||
const ImageGalleryContent = () => {
|
||||
const { t } = useTranslation();
|
||||
const galleryView = useAppSelector((s) => s.gallery.galleryView);
|
||||
const allowPrivateBoards = useAppSelector((s) => s.config.allowPrivateBoards);
|
||||
const dispatch = useAppDispatch();
|
||||
const galleryHeader = useStore($galleryHeader);
|
||||
const { isOpen: isBoardListOpen, onToggle: onToggleBoardList } = useDisclosure({ defaultIsOpen: true });
|
||||
@ -44,21 +42,15 @@ const ImageGalleryContent = () => {
|
||||
gap={2}
|
||||
>
|
||||
{galleryHeader}
|
||||
{true ? (
|
||||
<Box>
|
||||
<Flex alignItems="center" justifyContent="space-between" gap={2}>
|
||||
<GalleryBoardName isOpen={isBoardListOpen} onToggle={onToggleBoardList} />
|
||||
<GallerySettingsPopover />
|
||||
</Flex>
|
||||
<Box>
|
||||
<BoardsListWithPrivate isOpen={isBoardListOpen} />
|
||||
</Box>
|
||||
) : (
|
||||
<Box>
|
||||
<Flex alignItems="center" justifyContent="space-between" gap={2}>
|
||||
<GalleryBoardName isOpen={isBoardListOpen} onToggle={onToggleBoardList} />
|
||||
<GallerySettingsPopover />
|
||||
</Flex>
|
||||
<Box>
|
||||
<BoardsList isOpen={isBoardListOpen} />
|
||||
</Box>
|
||||
<BoardsList isOpen={isBoardListOpen} />
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
<Flex alignItems="center" justifyContent="space-between" gap={2}>
|
||||
<Tabs index={galleryView === 'images' ? 0 : 1} variant="unstyled" size="sm" w="full">
|
||||
<TabList>
|
||||
|
@ -7293,145 +7293,145 @@ export type components = {
|
||||
project_id: string | null;
|
||||
};
|
||||
InvocationOutputMap: {
|
||||
save_image: components["schemas"]["ImageOutput"];
|
||||
integer_math: components["schemas"]["IntegerOutput"];
|
||||
segment_anything_processor: components["schemas"]["ImageOutput"];
|
||||
sdxl_refiner_compel_prompt: components["schemas"]["ConditioningOutput"];
|
||||
zoe_depth_image_processor: components["schemas"]["ImageOutput"];
|
||||
collect: components["schemas"]["CollectInvocationOutput"];
|
||||
range: components["schemas"]["IntegerCollectionOutput"];
|
||||
unsharp_mask: components["schemas"]["ImageOutput"];
|
||||
string_replace: components["schemas"]["StringOutput"];
|
||||
face_identifier: components["schemas"]["ImageOutput"];
|
||||
heuristic_resize: components["schemas"]["ImageOutput"];
|
||||
range_of_size: components["schemas"]["IntegerCollectionOutput"];
|
||||
latents_collection: components["schemas"]["LatentsCollectionOutput"];
|
||||
color_map_image_processor: components["schemas"]["ImageOutput"];
|
||||
img_ilerp: components["schemas"]["ImageOutput"];
|
||||
infill_patchmatch: components["schemas"]["ImageOutput"];
|
||||
face_off: components["schemas"]["FaceOffOutput"];
|
||||
string_collection: components["schemas"]["StringCollectionOutput"];
|
||||
sdxl_model_loader: components["schemas"]["SDXLModelLoaderOutput"];
|
||||
sdxl_lora_loader: components["schemas"]["SDXLLoRALoaderOutput"];
|
||||
string_join: components["schemas"]["StringOutput"];
|
||||
lblend: components["schemas"]["LatentsOutput"];
|
||||
conditioning_collection: components["schemas"]["ConditioningCollectionOutput"];
|
||||
string_split_neg: components["schemas"]["StringPosNegOutput"];
|
||||
img_watermark: components["schemas"]["ImageOutput"];
|
||||
infill_lama: components["schemas"]["ImageOutput"];
|
||||
div: components["schemas"]["IntegerOutput"];
|
||||
show_image: components["schemas"]["ImageOutput"];
|
||||
tile_to_properties: components["schemas"]["TileToPropertiesOutput"];
|
||||
sub: components["schemas"]["IntegerOutput"];
|
||||
normalbae_image_processor: components["schemas"]["ImageOutput"];
|
||||
invert_tensor_mask: components["schemas"]["MaskOutput"];
|
||||
create_gradient_mask: components["schemas"]["GradientMaskOutput"];
|
||||
string_split: components["schemas"]["String2Output"];
|
||||
step_param_easing: components["schemas"]["FloatCollectionOutput"];
|
||||
metadata: components["schemas"]["MetadataOutput"];
|
||||
img_pad_crop: components["schemas"]["ImageOutput"];
|
||||
integer: components["schemas"]["IntegerOutput"];
|
||||
img_mul: components["schemas"]["ImageOutput"];
|
||||
calculate_image_tiles: components["schemas"]["CalculateImageTilesOutput"];
|
||||
color: components["schemas"]["ColorOutput"];
|
||||
infill_rgba: components["schemas"]["ImageOutput"];
|
||||
t2i_adapter: components["schemas"]["T2IAdapterOutput"];
|
||||
denoise_latents: components["schemas"]["LatentsOutput"];
|
||||
img_lerp: components["schemas"]["ImageOutput"];
|
||||
img_channel_offset: components["schemas"]["ImageOutput"];
|
||||
img_crop: components["schemas"]["ImageOutput"];
|
||||
alpha_mask_to_tensor: components["schemas"]["MaskOutput"];
|
||||
color_correct: components["schemas"]["ImageOutput"];
|
||||
calculate_image_tiles_min_overlap: components["schemas"]["CalculateImageTilesOutput"];
|
||||
img_hue_adjust: components["schemas"]["ImageOutput"];
|
||||
lresize: components["schemas"]["LatentsOutput"];
|
||||
img_blur: components["schemas"]["ImageOutput"];
|
||||
compel: components["schemas"]["ConditioningOutput"];
|
||||
sdxl_lora_collection_loader: components["schemas"]["SDXLLoRALoaderOutput"];
|
||||
float_to_int: components["schemas"]["IntegerOutput"];
|
||||
boolean: components["schemas"]["BooleanOutput"];
|
||||
string_join_three: components["schemas"]["StringOutput"];
|
||||
add: components["schemas"]["IntegerOutput"];
|
||||
merge_tiles_to_image: components["schemas"]["ImageOutput"];
|
||||
core_metadata: components["schemas"]["MetadataOutput"];
|
||||
lscale: components["schemas"]["LatentsOutput"];
|
||||
mlsd_image_processor: components["schemas"]["ImageOutput"];
|
||||
image_collection: components["schemas"]["ImageCollectionOutput"];
|
||||
crop_latents: components["schemas"]["LatentsOutput"];
|
||||
image_mask_to_tensor: components["schemas"]["MaskOutput"];
|
||||
lora_collection_loader: components["schemas"]["LoRALoaderOutput"];
|
||||
ip_adapter: components["schemas"]["IPAdapterOutput"];
|
||||
pidi_image_processor: components["schemas"]["ImageOutput"];
|
||||
rand_int: components["schemas"]["IntegerOutput"];
|
||||
img_conv: components["schemas"]["ImageOutput"];
|
||||
scheduler: components["schemas"]["SchedulerOutput"];
|
||||
img_paste: components["schemas"]["ImageOutput"];
|
||||
noise: components["schemas"]["NoiseOutput"];
|
||||
img_scale: components["schemas"]["ImageOutput"];
|
||||
i2l: components["schemas"]["LatentsOutput"];
|
||||
main_model_loader: components["schemas"]["ModelLoaderOutput"];
|
||||
blank_image: components["schemas"]["ImageOutput"];
|
||||
mask_edge: components["schemas"]["ImageOutput"];
|
||||
seamless: components["schemas"]["SeamlessModeOutput"];
|
||||
esrgan: components["schemas"]["ImageOutput"];
|
||||
canvas_paste_back: components["schemas"]["ImageOutput"];
|
||||
mul: components["schemas"]["IntegerOutput"];
|
||||
dynamic_prompt: components["schemas"]["StringCollectionOutput"];
|
||||
controlnet: components["schemas"]["ControlOutput"];
|
||||
l2i: components["schemas"]["ImageOutput"];
|
||||
ideal_size: components["schemas"]["IdealSizeOutput"];
|
||||
latents: components["schemas"]["LatentsOutput"];
|
||||
midas_depth_image_processor: components["schemas"]["ImageOutput"];
|
||||
tomask: components["schemas"]["ImageOutput"];
|
||||
float_math: components["schemas"]["FloatOutput"];
|
||||
round_float: components["schemas"]["FloatOutput"];
|
||||
cv_inpaint: components["schemas"]["ImageOutput"];
|
||||
create_denoise_mask: components["schemas"]["DenoiseMaskOutput"];
|
||||
model_identifier: components["schemas"]["ModelIdentifierOutput"];
|
||||
pair_tile_image: components["schemas"]["PairTileImageOutput"];
|
||||
lineart_image_processor: components["schemas"]["ImageOutput"];
|
||||
img_nsfw: components["schemas"]["ImageOutput"];
|
||||
infill_cv2: components["schemas"]["ImageOutput"];
|
||||
clip_skip: components["schemas"]["CLIPSkipInvocationOutput"];
|
||||
dw_openpose_image_processor: components["schemas"]["ImageOutput"];
|
||||
img_resize: components["schemas"]["ImageOutput"];
|
||||
iterate: components["schemas"]["IterateInvocationOutput"];
|
||||
rectangle_mask: components["schemas"]["MaskOutput"];
|
||||
canny_image_processor: components["schemas"]["ImageOutput"];
|
||||
calculate_image_tiles_even_split: components["schemas"]["CalculateImageTilesOutput"];
|
||||
mask_from_id: components["schemas"]["ImageOutput"];
|
||||
metadata_item: components["schemas"]["MetadataItemOutput"];
|
||||
infill_tile: components["schemas"]["ImageOutput"];
|
||||
tiled_multi_diffusion_denoise_latents: components["schemas"]["LatentsOutput"];
|
||||
img_channel_multiply: components["schemas"]["ImageOutput"];
|
||||
boolean_collection: components["schemas"]["BooleanCollectionOutput"];
|
||||
lora_loader: components["schemas"]["LoRALoaderOutput"];
|
||||
float_collection: components["schemas"]["FloatCollectionOutput"];
|
||||
string: components["schemas"]["StringOutput"];
|
||||
freeu: components["schemas"]["UNetOutput"];
|
||||
lineart_anime_image_processor: components["schemas"]["ImageOutput"];
|
||||
depth_anything_image_processor: components["schemas"]["ImageOutput"];
|
||||
image: components["schemas"]["ImageOutput"];
|
||||
face_mask_detection: components["schemas"]["FaceMaskOutput"];
|
||||
rand_float: components["schemas"]["FloatOutput"];
|
||||
float: components["schemas"]["FloatOutput"];
|
||||
random_range: components["schemas"]["IntegerCollectionOutput"];
|
||||
integer_collection: components["schemas"]["IntegerCollectionOutput"];
|
||||
sdxl_refiner_model_loader: components["schemas"]["SDXLRefinerModelLoaderOutput"];
|
||||
mask_combine: components["schemas"]["ImageOutput"];
|
||||
tile_image_processor: components["schemas"]["ImageOutput"];
|
||||
img_chan: components["schemas"]["ImageOutput"];
|
||||
vae_loader: components["schemas"]["VAEOutput"];
|
||||
prompt_from_file: components["schemas"]["StringCollectionOutput"];
|
||||
float_range: components["schemas"]["FloatCollectionOutput"];
|
||||
img_blur: components["schemas"]["ImageOutput"];
|
||||
cv_inpaint: components["schemas"]["ImageOutput"];
|
||||
integer_math: components["schemas"]["IntegerOutput"];
|
||||
merge_metadata: components["schemas"]["MetadataOutput"];
|
||||
sdxl_compel_prompt: components["schemas"]["ConditioningOutput"];
|
||||
hed_image_processor: components["schemas"]["ImageOutput"];
|
||||
lora_selector: components["schemas"]["LoRASelectorOutput"];
|
||||
conditioning: components["schemas"]["ConditioningOutput"];
|
||||
mask_combine: components["schemas"]["ImageOutput"];
|
||||
sub: components["schemas"]["IntegerOutput"];
|
||||
crop_latents: components["schemas"]["LatentsOutput"];
|
||||
ideal_size: components["schemas"]["IdealSizeOutput"];
|
||||
leres_image_processor: components["schemas"]["ImageOutput"];
|
||||
mask_from_id: components["schemas"]["ImageOutput"];
|
||||
infill_cv2: components["schemas"]["ImageOutput"];
|
||||
boolean: components["schemas"]["BooleanOutput"];
|
||||
conditioning_collection: components["schemas"]["ConditioningCollectionOutput"];
|
||||
infill_patchmatch: components["schemas"]["ImageOutput"];
|
||||
float_math: components["schemas"]["FloatOutput"];
|
||||
image_collection: components["schemas"]["ImageCollectionOutput"];
|
||||
img_conv: components["schemas"]["ImageOutput"];
|
||||
sdxl_refiner_compel_prompt: components["schemas"]["ConditioningOutput"];
|
||||
metadata: components["schemas"]["MetadataOutput"];
|
||||
save_image: components["schemas"]["ImageOutput"];
|
||||
boolean_collection: components["schemas"]["BooleanCollectionOutput"];
|
||||
img_paste: components["schemas"]["ImageOutput"];
|
||||
conditioning: components["schemas"]["ConditioningOutput"];
|
||||
color_map_image_processor: components["schemas"]["ImageOutput"];
|
||||
img_scale: components["schemas"]["ImageOutput"];
|
||||
string_join: components["schemas"]["StringOutput"];
|
||||
noise: components["schemas"]["NoiseOutput"];
|
||||
core_metadata: components["schemas"]["MetadataOutput"];
|
||||
lresize: components["schemas"]["LatentsOutput"];
|
||||
face_mask_detection: components["schemas"]["FaceMaskOutput"];
|
||||
calculate_image_tiles_even_split: components["schemas"]["CalculateImageTilesOutput"];
|
||||
mlsd_image_processor: components["schemas"]["ImageOutput"];
|
||||
sdxl_lora_collection_loader: components["schemas"]["SDXLLoRALoaderOutput"];
|
||||
img_nsfw: components["schemas"]["ImageOutput"];
|
||||
main_model_loader: components["schemas"]["ModelLoaderOutput"];
|
||||
rand_int: components["schemas"]["IntegerOutput"];
|
||||
mul: components["schemas"]["IntegerOutput"];
|
||||
integer: components["schemas"]["IntegerOutput"];
|
||||
img_pad_crop: components["schemas"]["ImageOutput"];
|
||||
collect: components["schemas"]["CollectInvocationOutput"];
|
||||
pidi_image_processor: components["schemas"]["ImageOutput"];
|
||||
lora_loader: components["schemas"]["LoRALoaderOutput"];
|
||||
infill_tile: components["schemas"]["ImageOutput"];
|
||||
calculate_image_tiles: components["schemas"]["CalculateImageTilesOutput"];
|
||||
scheduler: components["schemas"]["SchedulerOutput"];
|
||||
midas_depth_image_processor: components["schemas"]["ImageOutput"];
|
||||
latents: components["schemas"]["LatentsOutput"];
|
||||
tile_to_properties: components["schemas"]["TileToPropertiesOutput"];
|
||||
round_float: components["schemas"]["FloatOutput"];
|
||||
add: components["schemas"]["IntegerOutput"];
|
||||
dynamic_prompt: components["schemas"]["StringCollectionOutput"];
|
||||
l2i: components["schemas"]["ImageOutput"];
|
||||
integer_collection: components["schemas"]["IntegerCollectionOutput"];
|
||||
model_identifier: components["schemas"]["ModelIdentifierOutput"];
|
||||
normalbae_image_processor: components["schemas"]["ImageOutput"];
|
||||
img_chan: components["schemas"]["ImageOutput"];
|
||||
image: components["schemas"]["ImageOutput"];
|
||||
face_off: components["schemas"]["FaceOffOutput"];
|
||||
img_hue_adjust: components["schemas"]["ImageOutput"];
|
||||
color: components["schemas"]["ColorOutput"];
|
||||
color_correct: components["schemas"]["ImageOutput"];
|
||||
string_split_neg: components["schemas"]["StringPosNegOutput"];
|
||||
clip_skip: components["schemas"]["CLIPSkipInvocationOutput"];
|
||||
face_identifier: components["schemas"]["ImageOutput"];
|
||||
string_join_three: components["schemas"]["StringOutput"];
|
||||
img_channel_offset: components["schemas"]["ImageOutput"];
|
||||
infill_rgba: components["schemas"]["ImageOutput"];
|
||||
show_image: components["schemas"]["ImageOutput"];
|
||||
infill_lama: components["schemas"]["ImageOutput"];
|
||||
img_mul: components["schemas"]["ImageOutput"];
|
||||
segment_anything_processor: components["schemas"]["ImageOutput"];
|
||||
sdxl_refiner_model_loader: components["schemas"]["SDXLRefinerModelLoaderOutput"];
|
||||
mediapipe_face_processor: components["schemas"]["ImageOutput"];
|
||||
image_mask_to_tensor: components["schemas"]["MaskOutput"];
|
||||
string_split: components["schemas"]["String2Output"];
|
||||
i2l: components["schemas"]["LatentsOutput"];
|
||||
zoe_depth_image_processor: components["schemas"]["ImageOutput"];
|
||||
float_to_int: components["schemas"]["IntegerOutput"];
|
||||
ip_adapter: components["schemas"]["IPAdapterOutput"];
|
||||
string: components["schemas"]["StringOutput"];
|
||||
rectangle_mask: components["schemas"]["MaskOutput"];
|
||||
lineart_anime_image_processor: components["schemas"]["ImageOutput"];
|
||||
alpha_mask_to_tensor: components["schemas"]["MaskOutput"];
|
||||
merge_tiles_to_image: components["schemas"]["ImageOutput"];
|
||||
div: components["schemas"]["IntegerOutput"];
|
||||
float_collection: components["schemas"]["FloatCollectionOutput"];
|
||||
tiled_multi_diffusion_denoise_latents: components["schemas"]["LatentsOutput"];
|
||||
seamless: components["schemas"]["SeamlessModeOutput"];
|
||||
float: components["schemas"]["FloatOutput"];
|
||||
lineart_image_processor: components["schemas"]["ImageOutput"];
|
||||
controlnet: components["schemas"]["ControlOutput"];
|
||||
step_param_easing: components["schemas"]["FloatCollectionOutput"];
|
||||
prompt_from_file: components["schemas"]["StringCollectionOutput"];
|
||||
rand_float: components["schemas"]["FloatOutput"];
|
||||
float_range: components["schemas"]["FloatCollectionOutput"];
|
||||
sdxl_lora_loader: components["schemas"]["SDXLLoRALoaderOutput"];
|
||||
latents_collection: components["schemas"]["LatentsCollectionOutput"];
|
||||
sdxl_compel_prompt: components["schemas"]["ConditioningOutput"];
|
||||
create_denoise_mask: components["schemas"]["DenoiseMaskOutput"];
|
||||
create_gradient_mask: components["schemas"]["GradientMaskOutput"];
|
||||
metadata_item: components["schemas"]["MetadataItemOutput"];
|
||||
img_ilerp: components["schemas"]["ImageOutput"];
|
||||
content_shuffle_image_processor: components["schemas"]["ImageOutput"];
|
||||
img_watermark: components["schemas"]["ImageOutput"];
|
||||
invert_tensor_mask: components["schemas"]["MaskOutput"];
|
||||
img_crop: components["schemas"]["ImageOutput"];
|
||||
esrgan: components["schemas"]["ImageOutput"];
|
||||
iterate: components["schemas"]["IterateInvocationOutput"];
|
||||
unsharp_mask: components["schemas"]["ImageOutput"];
|
||||
mask_edge: components["schemas"]["ImageOutput"];
|
||||
canvas_paste_back: components["schemas"]["ImageOutput"];
|
||||
dw_openpose_image_processor: components["schemas"]["ImageOutput"];
|
||||
tomask: components["schemas"]["ImageOutput"];
|
||||
random_range: components["schemas"]["IntegerCollectionOutput"];
|
||||
lora_selector: components["schemas"]["LoRASelectorOutput"];
|
||||
compel: components["schemas"]["ConditioningOutput"];
|
||||
img_resize: components["schemas"]["ImageOutput"];
|
||||
sdxl_model_loader: components["schemas"]["SDXLModelLoaderOutput"];
|
||||
lora_collection_loader: components["schemas"]["LoRALoaderOutput"];
|
||||
string_replace: components["schemas"]["StringOutput"];
|
||||
string_collection: components["schemas"]["StringCollectionOutput"];
|
||||
blank_image: components["schemas"]["ImageOutput"];
|
||||
freeu: components["schemas"]["UNetOutput"];
|
||||
img_lerp: components["schemas"]["ImageOutput"];
|
||||
range: components["schemas"]["IntegerCollectionOutput"];
|
||||
depth_anything_image_processor: components["schemas"]["ImageOutput"];
|
||||
t2i_adapter: components["schemas"]["T2IAdapterOutput"];
|
||||
pair_tile_image: components["schemas"]["PairTileImageOutput"];
|
||||
lscale: components["schemas"]["LatentsOutput"];
|
||||
tile_image_processor: components["schemas"]["ImageOutput"];
|
||||
heuristic_resize: components["schemas"]["ImageOutput"];
|
||||
calculate_image_tiles_min_overlap: components["schemas"]["CalculateImageTilesOutput"];
|
||||
denoise_latents: components["schemas"]["LatentsOutput"];
|
||||
vae_loader: components["schemas"]["VAEOutput"];
|
||||
lblend: components["schemas"]["LatentsOutput"];
|
||||
hed_image_processor: components["schemas"]["ImageOutput"];
|
||||
canny_image_processor: components["schemas"]["ImageOutput"];
|
||||
};
|
||||
/**
|
||||
* InvocationStartedEvent
|
||||
@ -15014,6 +15014,8 @@ export type operations = {
|
||||
query: {
|
||||
/** @description The name of the board to create */
|
||||
board_name: string;
|
||||
/** @description Whether the board is private */
|
||||
private_board?: boolean;
|
||||
};
|
||||
};
|
||||
responses: {
|
||||
|
@ -11,9 +11,7 @@ export type ListBoardsArgs = NonNullable<paths['/api/v1/boards/']['get']['parame
|
||||
export type DeleteBoardResult =
|
||||
paths['/api/v1/boards/{board_id}']['delete']['responses']['200']['content']['application/json'];
|
||||
|
||||
export type CreateBoardArg = paths['/api/v1/boards/']['post']['parameters']['query'] & {
|
||||
changes: paths['/api/v1/boards/']['post']['parameters']['query'];
|
||||
};
|
||||
export type CreateBoardArg = paths['/api/v1/boards/']['post']['parameters']['query'];
|
||||
|
||||
export type UpdateBoardArg = paths['/api/v1/boards/{board_id}']['patch']['parameters']['path'] & {
|
||||
changes: paths['/api/v1/boards/{board_id}']['patch']['requestBody']['content']['application/json'];
|
||||
|
Loading…
Reference in New Issue
Block a user