mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
Boards UI update and add support for private boards (#6588)
## Summary Update Boards UI in the gallery and adds support for creating and displaying private boards <!--A description of the changes in this PR. Include the kind of change (fix, feature, docs, etc), the "why" and the "how". Screenshots or videos are useful for frontend changes.--> ## Related Issues / Discussions <!--WHEN APPLICABLE: List any related issues or discussions on github or discord. If this PR closes an issue, please use the "Closes #1234" format, so that the issue will be automatically closed when the PR merges.--> ## QA Instructions Can view private boards by setting config.allowPrivateBoards to true <!--WHEN APPLICABLE: Describe how you have tested the changes in this PR. Provide enough detail that a reviewer can reproduce your tests.--> ## Merge Plan <!--WHEN APPLICABLE: Large PRs, or PRs that touch sensitive things like DB schemas, may need some care when merging. For example, a careful rebase by the change author, timing to not interfere with a pending release, or a message to contributors on discord after merging.--> ## Checklist - [ ] _The PR has a short but descriptive title, suitable for a changelog_ - [ ] _Tests added / updated (if applicable)_ - [ ] _Documentation added / updated (if applicable)_
This commit is contained in:
commit
c90b5541e8
@ -31,6 +31,7 @@ class DeleteBoardResult(BaseModel):
|
|||||||
)
|
)
|
||||||
async def create_board(
|
async def create_board(
|
||||||
board_name: str = Query(description="The name of the board to create"),
|
board_name: str = Query(description="The name of the board to create"),
|
||||||
|
is_private: bool = Query(default=False, description="Whether the board is private"),
|
||||||
) -> BoardDTO:
|
) -> BoardDTO:
|
||||||
"""Creates a board"""
|
"""Creates a board"""
|
||||||
try:
|
try:
|
||||||
|
@ -24,6 +24,8 @@ class BoardRecord(BaseModelExcludeNull):
|
|||||||
"""The name of the cover image of the board."""
|
"""The name of the cover image of the board."""
|
||||||
archived: bool = Field(description="Whether or not the board is archived.")
|
archived: bool = Field(description="Whether or not the board is archived.")
|
||||||
"""Whether or not the board is archived."""
|
"""Whether or not the board is archived."""
|
||||||
|
is_private: Optional[bool] = Field(default=None, description="Whether the board is private.")
|
||||||
|
"""Whether the board is private."""
|
||||||
|
|
||||||
|
|
||||||
def deserialize_board_record(board_dict: dict) -> BoardRecord:
|
def deserialize_board_record(board_dict: dict) -> BoardRecord:
|
||||||
@ -38,6 +40,7 @@ def deserialize_board_record(board_dict: dict) -> BoardRecord:
|
|||||||
updated_at = board_dict.get("updated_at", get_iso_timestamp())
|
updated_at = board_dict.get("updated_at", get_iso_timestamp())
|
||||||
deleted_at = board_dict.get("deleted_at", get_iso_timestamp())
|
deleted_at = board_dict.get("deleted_at", get_iso_timestamp())
|
||||||
archived = board_dict.get("archived", False)
|
archived = board_dict.get("archived", False)
|
||||||
|
is_private = board_dict.get("is_private", False)
|
||||||
|
|
||||||
return BoardRecord(
|
return BoardRecord(
|
||||||
board_id=board_id,
|
board_id=board_id,
|
||||||
@ -47,6 +50,7 @@ def deserialize_board_record(board_dict: dict) -> BoardRecord:
|
|||||||
updated_at=updated_at,
|
updated_at=updated_at,
|
||||||
deleted_at=deleted_at,
|
deleted_at=deleted_at,
|
||||||
archived=archived,
|
archived=archived,
|
||||||
|
is_private=is_private,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -17,9 +17,12 @@
|
|||||||
},
|
},
|
||||||
"boards": {
|
"boards": {
|
||||||
"addBoard": "Add Board",
|
"addBoard": "Add Board",
|
||||||
|
"addPrivateBoard": "Add Private Board",
|
||||||
|
"addSharedBoard": "Add Shared Board",
|
||||||
"archiveBoard": "Archive Board",
|
"archiveBoard": "Archive Board",
|
||||||
"archived": "Archived",
|
"archived": "Archived",
|
||||||
"autoAddBoard": "Auto-Add Board",
|
"autoAddBoard": "Auto-Add Board",
|
||||||
|
"boards": "Boards",
|
||||||
"selectedForAutoAdd": "Selected for Auto-Add",
|
"selectedForAutoAdd": "Selected for Auto-Add",
|
||||||
"bottomMessage": "Deleting this board and its images will reset any features currently using them.",
|
"bottomMessage": "Deleting this board and its images will reset any features currently using them.",
|
||||||
"cancel": "Cancel",
|
"cancel": "Cancel",
|
||||||
@ -36,8 +39,10 @@
|
|||||||
"movingImagesToBoard_other": "Moving {{count}} images to board:",
|
"movingImagesToBoard_other": "Moving {{count}} images to board:",
|
||||||
"myBoard": "My Board",
|
"myBoard": "My Board",
|
||||||
"noMatching": "No matching Boards",
|
"noMatching": "No matching Boards",
|
||||||
|
"private": "Private",
|
||||||
"searchBoard": "Search Boards...",
|
"searchBoard": "Search Boards...",
|
||||||
"selectBoard": "Select a Board",
|
"selectBoard": "Select a Board",
|
||||||
|
"shared": "Shared",
|
||||||
"topMessage": "This board contains images used in the following features:",
|
"topMessage": "This board contains images used in the following features:",
|
||||||
"unarchiveBoard": "Unarchive Board",
|
"unarchiveBoard": "Unarchive Board",
|
||||||
"uncategorized": "Uncategorized",
|
"uncategorized": "Uncategorized",
|
||||||
|
@ -15,8 +15,6 @@ export const addArchivedOrDeletedBoardListener = (startAppListening: AppStartLis
|
|||||||
matcher: isAnyOf(
|
matcher: isAnyOf(
|
||||||
// Updating a board may change its archived status
|
// Updating a board may change its archived status
|
||||||
boardsApi.endpoints.updateBoard.matchFulfilled,
|
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
|
// If a board is deleted, we'll need to reset the auto-add board
|
||||||
imagesApi.endpoints.deleteBoard.matchFulfilled,
|
imagesApi.endpoints.deleteBoard.matchFulfilled,
|
||||||
imagesApi.endpoints.deleteBoardAndImages.matchFulfilled,
|
imagesApi.endpoints.deleteBoardAndImages.matchFulfilled,
|
||||||
|
@ -65,6 +65,7 @@ export type AppConfig = {
|
|||||||
*/
|
*/
|
||||||
shouldUpdateImagesOnConnect: boolean;
|
shouldUpdateImagesOnConnect: boolean;
|
||||||
shouldFetchMetadataFromApi: boolean;
|
shouldFetchMetadataFromApi: boolean;
|
||||||
|
allowPrivateBoards: boolean;
|
||||||
disabledTabs: InvokeTabName[];
|
disabledTabs: InvokeTabName[];
|
||||||
disabledFeatures: AppFeature[];
|
disabledFeatures: AppFeature[];
|
||||||
disabledSDFeatures: SDFeature[];
|
disabledSDFeatures: SDFeature[];
|
||||||
|
@ -52,8 +52,8 @@ const IAIDropOverlay = (props: Props) => {
|
|||||||
bottom={0.5}
|
bottom={0.5}
|
||||||
opacity={1}
|
opacity={1}
|
||||||
borderWidth={2}
|
borderWidth={2}
|
||||||
borderColor={isOver ? 'base.50' : 'base.300'}
|
borderColor={isOver ? 'base.300' : 'base.500'}
|
||||||
borderRadius="lg"
|
borderRadius="base"
|
||||||
borderStyle="dashed"
|
borderStyle="dashed"
|
||||||
transitionProperty="common"
|
transitionProperty="common"
|
||||||
transitionDuration="0.1s"
|
transitionDuration="0.1s"
|
||||||
|
@ -0,0 +1,14 @@
|
|||||||
|
import { Badge } from '@invoke-ai/ui-library';
|
||||||
|
import { memo } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
export const AutoAddBadge = memo(() => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
return (
|
||||||
|
<Badge color="invokeBlue.400" borderColor="invokeBlue.700" borderWidth={1} bg="transparent" flexShrink={0}>
|
||||||
|
{t('common.auto')}
|
||||||
|
</Badge>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
AutoAddBadge.displayName = 'AutoAddBadge';
|
@ -1,16 +0,0 @@
|
|||||||
import { Badge, Flex } from '@invoke-ai/ui-library';
|
|
||||||
import { memo } from 'react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
|
|
||||||
const AutoAddIcon = () => {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
return (
|
|
||||||
<Flex position="absolute" insetInlineEnd={0} top={0} p={1}>
|
|
||||||
<Badge variant="solid" bg="invokeBlue.400">
|
|
||||||
{t('common.auto')}
|
|
||||||
</Badge>
|
|
||||||
</Flex>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default memo(AutoAddIcon);
|
|
@ -1,26 +1,48 @@
|
|||||||
import { IconButton } from '@invoke-ai/ui-library';
|
import { IconButton } from '@invoke-ai/ui-library';
|
||||||
import { memo, useCallback } from 'react';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
|
import { boardIdSelected } from 'features/gallery/store/gallerySlice';
|
||||||
|
import { memo, useCallback, useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { PiPlusBold } from 'react-icons/pi';
|
import { PiPlusBold } from 'react-icons/pi';
|
||||||
import { useCreateBoardMutation } from 'services/api/endpoints/boards';
|
import { useCreateBoardMutation } from 'services/api/endpoints/boards';
|
||||||
|
|
||||||
const AddBoardButton = () => {
|
type Props = {
|
||||||
|
isPrivateBoard: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
const AddBoardButton = ({ isPrivateBoard }: Props) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const allowPrivateBoards = useAppSelector((s) => s.config.allowPrivateBoards);
|
||||||
const [createBoard, { isLoading }] = useCreateBoardMutation();
|
const [createBoard, { isLoading }] = useCreateBoardMutation();
|
||||||
const DEFAULT_BOARD_NAME = t('boards.myBoard');
|
const label = useMemo(() => {
|
||||||
const handleCreateBoard = useCallback(() => {
|
if (!allowPrivateBoards) {
|
||||||
createBoard(DEFAULT_BOARD_NAME);
|
return t('boards.addBoard');
|
||||||
}, [createBoard, DEFAULT_BOARD_NAME]);
|
}
|
||||||
|
if (isPrivateBoard) {
|
||||||
|
return t('boards.addPrivateBoard');
|
||||||
|
}
|
||||||
|
return t('boards.addSharedBoard');
|
||||||
|
}, [allowPrivateBoards, isPrivateBoard, t]);
|
||||||
|
const handleCreateBoard = useCallback(async () => {
|
||||||
|
try {
|
||||||
|
const board = await createBoard({ board_name: t('boards.myBoard'), is_private: isPrivateBoard }).unwrap();
|
||||||
|
dispatch(boardIdSelected({ boardId: board.board_id }));
|
||||||
|
} catch {
|
||||||
|
//no-op
|
||||||
|
}
|
||||||
|
}, [t, createBoard, isPrivateBoard, dispatch]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<IconButton
|
<IconButton
|
||||||
icon={<PiPlusBold />}
|
icon={<PiPlusBold />}
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
tooltip={t('boards.addBoard')}
|
tooltip={label}
|
||||||
aria-label={t('boards.addBoard')}
|
aria-label={label}
|
||||||
onClick={handleCreateBoard}
|
onClick={handleCreateBoard}
|
||||||
size="sm"
|
size="md"
|
||||||
data-testid="add-board-button"
|
data-testid="add-board-button"
|
||||||
|
variant="ghost"
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,11 +1,15 @@
|
|||||||
import { Collapse, Flex, Grid, GridItem } from '@invoke-ai/ui-library';
|
import { Collapse, Flex, Icon, Text, useDisclosure } from '@invoke-ai/ui-library';
|
||||||
|
import { EMPTY_ARRAY } from 'app/store/constants';
|
||||||
import { useAppSelector } from 'app/store/storeHooks';
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
import { overlayScrollbarsParams } from 'common/components/OverlayScrollbars/constants';
|
import { overlayScrollbarsParams } from 'common/components/OverlayScrollbars/constants';
|
||||||
import DeleteBoardModal from 'features/gallery/components/Boards/DeleteBoardModal';
|
import DeleteBoardModal from 'features/gallery/components/Boards/DeleteBoardModal';
|
||||||
|
import GallerySettingsPopover from 'features/gallery/components/GallerySettingsPopover/GallerySettingsPopover';
|
||||||
import { selectListBoardsQueryArgs } from 'features/gallery/store/gallerySelectors';
|
import { selectListBoardsQueryArgs } from 'features/gallery/store/gallerySelectors';
|
||||||
import { OverlayScrollbarsComponent } from 'overlayscrollbars-react';
|
import { OverlayScrollbarsComponent } from 'overlayscrollbars-react';
|
||||||
import type { CSSProperties } from 'react';
|
import type { CSSProperties } from 'react';
|
||||||
import { memo, useState } from 'react';
|
import { memo, useMemo, useState } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { PiCaretUpBold } from 'react-icons/pi';
|
||||||
import { useListAllBoardsQuery } from 'services/api/endpoints/boards';
|
import { useListAllBoardsQuery } from 'services/api/endpoints/boards';
|
||||||
import type { BoardDTO } from 'services/api/types';
|
import type { BoardDTO } from 'services/api/types';
|
||||||
|
|
||||||
@ -19,56 +23,112 @@ const overlayScrollbarsStyles: CSSProperties = {
|
|||||||
width: '100%',
|
width: '100%',
|
||||||
};
|
};
|
||||||
|
|
||||||
type Props = {
|
const BoardsList = () => {
|
||||||
isOpen: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
const BoardsList = (props: Props) => {
|
|
||||||
const { isOpen } = props;
|
|
||||||
const selectedBoardId = useAppSelector((s) => s.gallery.selectedBoardId);
|
const selectedBoardId = useAppSelector((s) => s.gallery.selectedBoardId);
|
||||||
const boardSearchText = useAppSelector((s) => s.gallery.boardSearchText);
|
const boardSearchText = useAppSelector((s) => s.gallery.boardSearchText);
|
||||||
|
const allowPrivateBoards = useAppSelector((s) => s.config.allowPrivateBoards);
|
||||||
const queryArgs = useAppSelector(selectListBoardsQueryArgs);
|
const queryArgs = useAppSelector(selectListBoardsQueryArgs);
|
||||||
const { data: boards } = useListAllBoardsQuery(queryArgs);
|
const { data: boards } = useListAllBoardsQuery(queryArgs);
|
||||||
const filteredBoards = boardSearchText
|
|
||||||
? boards?.filter((board) => board.board_name.toLowerCase().includes(boardSearchText.toLowerCase()))
|
|
||||||
: boards;
|
|
||||||
const [boardToDelete, setBoardToDelete] = useState<BoardDTO>();
|
const [boardToDelete, setBoardToDelete] = useState<BoardDTO>();
|
||||||
|
const privateBoardsDisclosure = useDisclosure({ defaultIsOpen: false });
|
||||||
|
const sharedBoardsDisclosure = useDisclosure({ defaultIsOpen: false });
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const { filteredPrivateBoards, filteredSharedBoards } = useMemo(() => {
|
||||||
|
const filteredBoards = boardSearchText
|
||||||
|
? boards?.filter((board) => board.board_name.toLowerCase().includes(boardSearchText.toLowerCase()))
|
||||||
|
: boards;
|
||||||
|
const filteredPrivateBoards = filteredBoards?.filter((board) => board.is_private) ?? EMPTY_ARRAY;
|
||||||
|
const filteredSharedBoards = filteredBoards?.filter((board) => !board.is_private) ?? EMPTY_ARRAY;
|
||||||
|
return { filteredPrivateBoards, filteredSharedBoards };
|
||||||
|
}, [boardSearchText, boards]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Collapse in={isOpen} animateOpacity>
|
<Flex layerStyle="first" flexDir="column" borderRadius="base">
|
||||||
<Flex layerStyle="first" flexDir="column" gap={2} p={2} mt={2} borderRadius="base">
|
<Flex gap={2} alignItems="center" pb={2}>
|
||||||
<Flex gap={2} alignItems="center">
|
<BoardsSearch />
|
||||||
<BoardsSearch />
|
<GallerySettingsPopover />
|
||||||
<AddBoardButton />
|
</Flex>
|
||||||
</Flex>
|
{allowPrivateBoards && (
|
||||||
<OverlayScrollbarsComponent defer style={overlayScrollbarsStyles} options={overlayScrollbarsParams.options}>
|
<>
|
||||||
<Grid
|
<Flex w="full" gap={2}>
|
||||||
className="list-container"
|
<Flex
|
||||||
data-testid="boards-list"
|
flexGrow={1}
|
||||||
gridTemplateColumns="repeat(auto-fill, minmax(90px, 1fr))"
|
onClick={privateBoardsDisclosure.onToggle}
|
||||||
maxH={346}
|
gap={2}
|
||||||
>
|
alignItems="center"
|
||||||
<GridItem p={1.5} data-testid="no-board">
|
cursor="pointer"
|
||||||
<NoBoardBoard isSelected={selectedBoardId === 'none'} />
|
>
|
||||||
</GridItem>
|
<Icon
|
||||||
{filteredBoards &&
|
as={PiCaretUpBold}
|
||||||
filteredBoards.map((board, index) => (
|
boxSize={4}
|
||||||
<GridItem key={board.board_id} p={1.5} data-testid={`board-${index}`}>
|
transform={privateBoardsDisclosure.isOpen ? 'rotate(0deg)' : 'rotate(180deg)'}
|
||||||
|
transitionProperty="common"
|
||||||
|
transitionDuration="normal"
|
||||||
|
color="base.400"
|
||||||
|
/>
|
||||||
|
<Text fontSize="md" fontWeight="medium" userSelect="none">
|
||||||
|
{t('boards.private')}
|
||||||
|
</Text>
|
||||||
|
</Flex>
|
||||||
|
<AddBoardButton isPrivateBoard={true} />
|
||||||
|
</Flex>
|
||||||
|
<Collapse in={privateBoardsDisclosure.isOpen} animateOpacity>
|
||||||
|
<OverlayScrollbarsComponent
|
||||||
|
defer
|
||||||
|
style={overlayScrollbarsStyles}
|
||||||
|
options={overlayScrollbarsParams.options}
|
||||||
|
>
|
||||||
|
<Flex direction="column" maxH={346} gap={1}>
|
||||||
|
{allowPrivateBoards && <NoBoardBoard isSelected={selectedBoardId === 'none'} />}
|
||||||
|
{filteredPrivateBoards.map((board) => (
|
||||||
<GalleryBoard
|
<GalleryBoard
|
||||||
board={board}
|
board={board}
|
||||||
isSelected={selectedBoardId === board.board_id}
|
isSelected={selectedBoardId === board.board_id}
|
||||||
setBoardToDelete={setBoardToDelete}
|
setBoardToDelete={setBoardToDelete}
|
||||||
|
key={board.board_id}
|
||||||
/>
|
/>
|
||||||
</GridItem>
|
))}
|
||||||
))}
|
</Flex>
|
||||||
</Grid>
|
</OverlayScrollbarsComponent>
|
||||||
</OverlayScrollbarsComponent>
|
</Collapse>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<Flex w="full" gap={2}>
|
||||||
|
<Flex onClick={sharedBoardsDisclosure.onToggle} gap={2} alignItems="center" cursor="pointer" flexGrow={1}>
|
||||||
|
<Icon
|
||||||
|
as={PiCaretUpBold}
|
||||||
|
boxSize={4}
|
||||||
|
transform={sharedBoardsDisclosure.isOpen ? 'rotate(0deg)' : 'rotate(180deg)'}
|
||||||
|
transitionProperty="common"
|
||||||
|
transitionDuration="normal"
|
||||||
|
color="base.400"
|
||||||
|
/>
|
||||||
|
<Text fontSize="md" fontWeight="medium" userSelect="none">
|
||||||
|
{allowPrivateBoards ? t('boards.shared') : t('boards.boards')}
|
||||||
|
</Text>
|
||||||
|
</Flex>
|
||||||
|
<AddBoardButton isPrivateBoard={false} />
|
||||||
</Flex>
|
</Flex>
|
||||||
</Collapse>
|
<Collapse in={sharedBoardsDisclosure.isOpen} animateOpacity>
|
||||||
|
<OverlayScrollbarsComponent defer style={overlayScrollbarsStyles} options={overlayScrollbarsParams.options}>
|
||||||
|
<Flex direction="column" maxH={346} gap={1}>
|
||||||
|
{!allowPrivateBoards && <NoBoardBoard isSelected={selectedBoardId === 'none'} />}
|
||||||
|
{filteredSharedBoards.map((board) => (
|
||||||
|
<GalleryBoard
|
||||||
|
board={board}
|
||||||
|
isSelected={selectedBoardId === board.board_id}
|
||||||
|
setBoardToDelete={setBoardToDelete}
|
||||||
|
key={board.board_id}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Flex>
|
||||||
|
</OverlayScrollbarsComponent>
|
||||||
|
</Collapse>
|
||||||
|
</Flex>
|
||||||
<DeleteBoardModal boardToDelete={boardToDelete} setBoardToDelete={setBoardToDelete} />
|
<DeleteBoardModal boardToDelete={boardToDelete} setBoardToDelete={setBoardToDelete} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default memo(BoardsList);
|
export default memo(BoardsList);
|
||||||
|
@ -1,36 +1,41 @@
|
|||||||
import type { SystemStyleObject } from '@invoke-ai/ui-library';
|
import type { SystemStyleObject } from '@invoke-ai/ui-library';
|
||||||
import { Box, Editable, EditableInput, EditablePreview, Flex, Icon, Image, Text, Tooltip } from '@invoke-ai/ui-library';
|
import {
|
||||||
import { createSelector } from '@reduxjs/toolkit';
|
Editable,
|
||||||
|
EditableInput,
|
||||||
|
EditablePreview,
|
||||||
|
Flex,
|
||||||
|
Icon,
|
||||||
|
Image,
|
||||||
|
Text,
|
||||||
|
Tooltip,
|
||||||
|
useDisclosure,
|
||||||
|
} from '@invoke-ai/ui-library';
|
||||||
import { skipToken } from '@reduxjs/toolkit/query';
|
import { skipToken } from '@reduxjs/toolkit/query';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import IAIDroppable from 'common/components/IAIDroppable';
|
import IAIDroppable from 'common/components/IAIDroppable';
|
||||||
import SelectionOverlay from 'common/components/SelectionOverlay';
|
|
||||||
import type { AddToBoardDropData } from 'features/dnd/types';
|
import type { AddToBoardDropData } from 'features/dnd/types';
|
||||||
import AutoAddIcon from 'features/gallery/components/Boards/AutoAddIcon';
|
import { AutoAddBadge } from 'features/gallery/components/Boards/AutoAddBadge';
|
||||||
import BoardContextMenu from 'features/gallery/components/Boards/BoardContextMenu';
|
import BoardContextMenu from 'features/gallery/components/Boards/BoardContextMenu';
|
||||||
import { BoardTotalsTooltip } from 'features/gallery/components/Boards/BoardsList/BoardTotalsTooltip';
|
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 { memo, useCallback, useMemo, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { PiArchiveBold, PiImagesSquare } from 'react-icons/pi';
|
import { PiArchiveBold, PiImageSquare } from 'react-icons/pi';
|
||||||
import { useUpdateBoardMutation } from 'services/api/endpoints/boards';
|
import { useUpdateBoardMutation } from 'services/api/endpoints/boards';
|
||||||
import { useGetImageDTOQuery } from 'services/api/endpoints/images';
|
import { useGetImageDTOQuery } from 'services/api/endpoints/images';
|
||||||
import type { BoardDTO } from 'services/api/types';
|
import type { BoardDTO } from 'services/api/types';
|
||||||
|
|
||||||
const editableInputStyles: SystemStyleObject = {
|
const editableInputStyles: SystemStyleObject = {
|
||||||
p: 0,
|
p: 0,
|
||||||
|
fontSize: 'md',
|
||||||
|
w: '100%',
|
||||||
_focusVisible: {
|
_focusVisible: {
|
||||||
p: 0,
|
p: 0,
|
||||||
textAlign: 'center',
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const ArchivedIcon = () => {
|
const _hover: SystemStyleObject = {
|
||||||
return (
|
bg: 'base.800',
|
||||||
<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 {
|
interface GalleryBoardProps {
|
||||||
@ -42,71 +47,53 @@ interface GalleryBoardProps {
|
|||||||
const GalleryBoard = ({ board, isSelected, setBoardToDelete }: GalleryBoardProps) => {
|
const GalleryBoard = ({ board, isSelected, setBoardToDelete }: GalleryBoardProps) => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const autoAddBoardId = useAppSelector((s) => s.gallery.autoAddBoardId);
|
||||||
const autoAssignBoardOnClick = useAppSelector((s) => s.gallery.autoAssignBoardOnClick);
|
const autoAssignBoardOnClick = useAppSelector((s) => s.gallery.autoAssignBoardOnClick);
|
||||||
const selectIsSelectedForAutoAdd = useMemo(
|
const editingDisclosure = useDisclosure();
|
||||||
() => createSelector(selectGallerySlice, (gallery) => board.board_id === gallery.autoAddBoardId),
|
const [localBoardName, setLocalBoardName] = useState(board.board_name);
|
||||||
[board.board_id]
|
|
||||||
);
|
|
||||||
|
|
||||||
const isSelectedForAutoAdd = useAppSelector(selectIsSelectedForAutoAdd);
|
|
||||||
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(() => {
|
const handleSelectBoard = useCallback(() => {
|
||||||
dispatch(boardIdSelected({ boardId: board_id }));
|
dispatch(boardIdSelected({ boardId: board.board_id }));
|
||||||
if (autoAssignBoardOnClick) {
|
if (autoAssignBoardOnClick) {
|
||||||
dispatch(autoAddBoardIdChanged(board_id));
|
dispatch(autoAddBoardIdChanged(board.board_id));
|
||||||
}
|
}
|
||||||
}, [board_id, autoAssignBoardOnClick, dispatch]);
|
}, [dispatch, board.board_id, autoAssignBoardOnClick]);
|
||||||
|
|
||||||
const [updateBoard, { isLoading: isUpdateBoardLoading }] = useUpdateBoardMutation();
|
const [updateBoard, { isLoading: isUpdateBoardLoading }] = useUpdateBoardMutation();
|
||||||
|
|
||||||
const droppableData: AddToBoardDropData = useMemo(
|
const droppableData: AddToBoardDropData = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
id: board_id,
|
id: board.board_id,
|
||||||
actionType: 'ADD_TO_BOARD',
|
actionType: 'ADD_TO_BOARD',
|
||||||
context: { boardId: board_id },
|
context: { boardId: board.board_id },
|
||||||
}),
|
}),
|
||||||
[board_id]
|
[board.board_id]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleSubmit = useCallback(
|
const handleSubmit = useCallback(
|
||||||
async (newBoardName: string) => {
|
async (newBoardName: string) => {
|
||||||
// empty strings are not allowed
|
|
||||||
if (!newBoardName.trim()) {
|
if (!newBoardName.trim()) {
|
||||||
setLocalBoardName(board_name);
|
// empty strings are not allowed
|
||||||
return;
|
setLocalBoardName(board.board_name);
|
||||||
}
|
} else if (newBoardName === board.board_name) {
|
||||||
|
// don't updated the board name if it hasn't changed
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
const { board_name } = await updateBoard({
|
||||||
|
board_id: board.board_id,
|
||||||
|
changes: { board_name: newBoardName },
|
||||||
|
}).unwrap();
|
||||||
|
|
||||||
// don't updated the board name if it hasn't changed
|
// update local state
|
||||||
if (newBoardName === board_name) {
|
setLocalBoardName(board_name);
|
||||||
return;
|
} catch {
|
||||||
}
|
// revert on error
|
||||||
|
setLocalBoardName(board.board_name);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
editingDisclosure.onClose();
|
||||||
},
|
},
|
||||||
[board_id, board_name, updateBoard]
|
[board.board_id, board.board_name, editingDisclosure, updateBoard]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleChange = useCallback((newBoardName: string) => {
|
const handleChange = useCallback((newBoardName: string) => {
|
||||||
@ -114,98 +101,91 @@ const GalleryBoard = ({ board, isSelected, setBoardToDelete }: GalleryBoardProps
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box w="full" h="full" userSelect="none">
|
<BoardContextMenu board={board} setBoardToDelete={setBoardToDelete}>
|
||||||
<Flex
|
{(ref) => (
|
||||||
onMouseOver={handleMouseOver}
|
<Tooltip
|
||||||
onMouseOut={handleMouseOut}
|
label={<BoardTotalsTooltip board_id={board.board_id} isArchived={Boolean(board.archived)} />}
|
||||||
position="relative"
|
openDelay={1000}
|
||||||
justifyContent="center"
|
>
|
||||||
alignItems="center"
|
<Flex
|
||||||
aspectRatio="1/1"
|
position="relative"
|
||||||
w="full"
|
ref={ref}
|
||||||
h="full"
|
onClick={handleSelectBoard}
|
||||||
>
|
w="full"
|
||||||
<BoardContextMenu board={board} setBoardToDelete={setBoardToDelete}>
|
alignItems="center"
|
||||||
{(ref) => (
|
borderRadius="base"
|
||||||
<Tooltip
|
cursor="pointer"
|
||||||
label={<BoardTotalsTooltip board_id={board.board_id} isArchived={Boolean(board.archived)} />}
|
py={1}
|
||||||
openDelay={1000}
|
px={2}
|
||||||
|
gap={2}
|
||||||
|
bg={isSelected ? 'base.800' : undefined}
|
||||||
|
_hover={_hover}
|
||||||
|
>
|
||||||
|
<CoverImage board={board} />
|
||||||
|
<Editable
|
||||||
|
as={Flex}
|
||||||
|
alignItems="center"
|
||||||
|
gap={4}
|
||||||
|
flexGrow={1}
|
||||||
|
onEdit={editingDisclosure.onOpen}
|
||||||
|
value={localBoardName}
|
||||||
|
isDisabled={isUpdateBoardLoading}
|
||||||
|
submitOnBlur={true}
|
||||||
|
onChange={handleChange}
|
||||||
|
onSubmit={handleSubmit}
|
||||||
>
|
>
|
||||||
<Flex
|
<EditablePreview
|
||||||
ref={ref}
|
p={0}
|
||||||
onClick={handleSelectBoard}
|
fontSize="md"
|
||||||
w="full"
|
textOverflow="ellipsis"
|
||||||
h="full"
|
noOfLines={1}
|
||||||
position="relative"
|
w="fit-content"
|
||||||
justifyContent="center"
|
wordBreak="break-all"
|
||||||
alignItems="center"
|
color={isSelected ? 'base.100' : 'base.400'}
|
||||||
borderRadius="base"
|
fontWeight={isSelected ? 'semibold' : 'normal'}
|
||||||
cursor="pointer"
|
/>
|
||||||
bg="base.800"
|
<EditableInput sx={editableInputStyles} />
|
||||||
>
|
</Editable>
|
||||||
{board.archived && <ArchivedIcon />}
|
{autoAddBoardId === board.board_id && !editingDisclosure.isOpen && <AutoAddBadge />}
|
||||||
{coverImage?.thumbnail_url ? (
|
{board.archived && !editingDisclosure.isOpen && (
|
||||||
<Image
|
<Icon
|
||||||
src={coverImage?.thumbnail_url}
|
as={PiArchiveBold}
|
||||||
draggable={false}
|
fill="base.300"
|
||||||
objectFit="cover"
|
filter="drop-shadow(0px 0px 0.1rem var(--invoke-colors-base-800))"
|
||||||
w="full"
|
/>
|
||||||
h="full"
|
)}
|
||||||
maxH="full"
|
{!editingDisclosure.isOpen && <Text variant="subtext">{board.image_count}</Text>}
|
||||||
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"
|
|
||||||
/>
|
|
||||||
<EditableInput sx={editableInputStyles} />
|
|
||||||
</Editable>
|
|
||||||
</Flex>
|
|
||||||
|
|
||||||
<IAIDroppable data={droppableData} dropLabel={<Text fontSize="md">{t('unifiedCanvas.move')}</Text>} />
|
<IAIDroppable data={droppableData} dropLabel={<Text fontSize="md">{t('unifiedCanvas.move')}</Text>} />
|
||||||
</Flex>
|
</Flex>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
</BoardContextMenu>
|
</BoardContextMenu>
|
||||||
</Flex>
|
|
||||||
</Box>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default memo(GalleryBoard);
|
export default memo(GalleryBoard);
|
||||||
|
|
||||||
|
const CoverImage = ({ board }: { board: BoardDTO }) => {
|
||||||
|
const { currentData: coverImage } = useGetImageDTOQuery(board.cover_image_name ?? skipToken);
|
||||||
|
|
||||||
|
if (coverImage) {
|
||||||
|
return (
|
||||||
|
<Image
|
||||||
|
src={coverImage.thumbnail_url}
|
||||||
|
draggable={false}
|
||||||
|
objectFit="cover"
|
||||||
|
w={8}
|
||||||
|
h={8}
|
||||||
|
borderRadius="base"
|
||||||
|
borderBottomRadius="lg"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Flex w={8} h={8} justifyContent="center" alignItems="center">
|
||||||
|
<Icon boxSize={8} as={PiImageSquare} opacity={0.7} color="base.500" />
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
@ -1,23 +1,32 @@
|
|||||||
import { Box, Flex, Image, Text, Tooltip } from '@invoke-ai/ui-library';
|
import type { SystemStyleObject } from '@invoke-ai/ui-library';
|
||||||
|
import { Flex, Icon, Text, Tooltip } from '@invoke-ai/ui-library';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import IAIDroppable from 'common/components/IAIDroppable';
|
import IAIDroppable from 'common/components/IAIDroppable';
|
||||||
import SelectionOverlay from 'common/components/SelectionOverlay';
|
|
||||||
import type { RemoveFromBoardDropData } from 'features/dnd/types';
|
import type { RemoveFromBoardDropData } from 'features/dnd/types';
|
||||||
import AutoAddIcon from 'features/gallery/components/Boards/AutoAddIcon';
|
import { AutoAddBadge } from 'features/gallery/components/Boards/AutoAddBadge';
|
||||||
import { BoardTotalsTooltip } from 'features/gallery/components/Boards/BoardsList/BoardTotalsTooltip';
|
import { BoardTotalsTooltip } from 'features/gallery/components/Boards/BoardsList/BoardTotalsTooltip';
|
||||||
import NoBoardBoardContextMenu from 'features/gallery/components/Boards/NoBoardBoardContextMenu';
|
import NoBoardBoardContextMenu from 'features/gallery/components/Boards/NoBoardBoardContextMenu';
|
||||||
import { autoAddBoardIdChanged, boardIdSelected } from 'features/gallery/store/gallerySlice';
|
import { autoAddBoardIdChanged, boardIdSelected } from 'features/gallery/store/gallerySlice';
|
||||||
import InvokeLogoSVG from 'public/assets/images/invoke-symbol-wht-lrg.svg';
|
import { memo, useCallback, useMemo } from 'react';
|
||||||
import { memo, useCallback, useMemo, useState } from 'react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { useGetBoardImagesTotalQuery } from 'services/api/endpoints/boards';
|
||||||
import { useBoardName } from 'services/api/hooks/useBoardName';
|
import { useBoardName } from 'services/api/hooks/useBoardName';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
isSelected: boolean;
|
isSelected: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const _hover: SystemStyleObject = {
|
||||||
|
bg: 'base.800',
|
||||||
|
};
|
||||||
|
|
||||||
const NoBoardBoard = memo(({ isSelected }: Props) => {
|
const NoBoardBoard = memo(({ isSelected }: Props) => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
const { imagesTotal } = useGetBoardImagesTotalQuery('none', {
|
||||||
|
selectFromResult: ({ data }) => {
|
||||||
|
return { imagesTotal: data?.total ?? 0 };
|
||||||
|
},
|
||||||
|
});
|
||||||
const autoAddBoardId = useAppSelector((s) => s.gallery.autoAddBoardId);
|
const autoAddBoardId = useAppSelector((s) => s.gallery.autoAddBoardId);
|
||||||
const autoAssignBoardOnClick = useAppSelector((s) => s.gallery.autoAssignBoardOnClick);
|
const autoAssignBoardOnClick = useAppSelector((s) => s.gallery.autoAssignBoardOnClick);
|
||||||
const boardName = useBoardName('none');
|
const boardName = useBoardName('none');
|
||||||
@ -27,15 +36,6 @@ const NoBoardBoard = memo(({ isSelected }: Props) => {
|
|||||||
dispatch(autoAddBoardIdChanged('none'));
|
dispatch(autoAddBoardIdChanged('none'));
|
||||||
}
|
}
|
||||||
}, [dispatch, autoAssignBoardOnClick]);
|
}, [dispatch, autoAssignBoardOnClick]);
|
||||||
const [isHovered, setIsHovered] = useState(false);
|
|
||||||
|
|
||||||
const handleMouseOver = useCallback(() => {
|
|
||||||
setIsHovered(true);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleMouseOut = useCallback(() => {
|
|
||||||
setIsHovered(false);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const droppableData: RemoveFromBoardDropData = useMemo(
|
const droppableData: RemoveFromBoardDropData = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
@ -46,74 +46,49 @@ const NoBoardBoard = memo(({ isSelected }: Props) => {
|
|||||||
);
|
);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
return (
|
return (
|
||||||
<Box w="full" h="full" userSelect="none">
|
<NoBoardBoardContextMenu>
|
||||||
<Flex
|
{(ref) => (
|
||||||
onMouseOver={handleMouseOver}
|
<Tooltip label={<BoardTotalsTooltip board_id="none" isArchived={false} />} openDelay={1000}>
|
||||||
onMouseOut={handleMouseOut}
|
<Flex
|
||||||
position="relative"
|
position="relative"
|
||||||
justifyContent="center"
|
ref={ref}
|
||||||
alignItems="center"
|
onClick={handleSelectBoard}
|
||||||
aspectRatio="1/1"
|
w="full"
|
||||||
borderRadius="base"
|
alignItems="center"
|
||||||
w="full"
|
borderRadius="base"
|
||||||
h="full"
|
cursor="pointer"
|
||||||
>
|
px={2}
|
||||||
<NoBoardBoardContextMenu>
|
py={1}
|
||||||
{(ref) => (
|
gap={2}
|
||||||
<Tooltip label={<BoardTotalsTooltip board_id="none" isArchived={false} />} openDelay={1000}>
|
bg={isSelected ? 'base.800' : undefined}
|
||||||
<Flex
|
_hover={_hover}
|
||||||
ref={ref}
|
>
|
||||||
onClick={handleSelectBoard}
|
<Flex w={8} h={8} justifyContent="center" alignItems="center">
|
||||||
w="full"
|
{/* iconified from public/assets/images/invoke-symbol-wht-lrg.svg */}
|
||||||
h="full"
|
<Icon boxSize={6} opacity={1} stroke="base.500" viewBox="0 0 66 66" fill="none">
|
||||||
position="relative"
|
<path
|
||||||
justifyContent="center"
|
d="M43.9137 16H63.1211V3H3.12109V16H22.3285L43.9137 50H63.1211V63H3.12109V50H22.3285"
|
||||||
alignItems="center"
|
strokeWidth="5"
|
||||||
borderRadius="base"
|
/>
|
||||||
cursor="pointer"
|
</Icon>
|
||||||
bg="base.800"
|
</Flex>
|
||||||
>
|
|
||||||
<Flex w="full" h="full" justifyContent="center" alignItems="center">
|
<Text
|
||||||
<Image
|
fontSize="md"
|
||||||
src={InvokeLogoSVG}
|
color={isSelected ? 'base.100' : 'base.400'}
|
||||||
alt="invoke-ai-logo"
|
fontWeight={isSelected ? 'semibold' : 'normal'}
|
||||||
opacity={0.7}
|
noOfLines={1}
|
||||||
mixBlendMode="overlay"
|
flexGrow={1}
|
||||||
mt={-6}
|
>
|
||||||
w={16}
|
{boardName}
|
||||||
h={16}
|
</Text>
|
||||||
minW={16}
|
{autoAddBoardId === 'none' && <AutoAddBadge />}
|
||||||
minH={16}
|
<Text variant="subtext">{imagesTotal}</Text>
|
||||||
userSelect="none"
|
<IAIDroppable data={droppableData} dropLabel={<Text fontSize="md">{t('unifiedCanvas.move')}</Text>} />
|
||||||
/>
|
</Flex>
|
||||||
</Flex>
|
</Tooltip>
|
||||||
{autoAddBoardId === 'none' && <AutoAddIcon />}
|
)}
|
||||||
<Flex
|
</NoBoardBoardContextMenu>
|
||||||
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"
|
|
||||||
fontWeight={isSelected ? 'bold' : 'normal'}
|
|
||||||
>
|
|
||||||
{boardName}
|
|
||||||
</Flex>
|
|
||||||
<SelectionOverlay isSelected={isSelected} isSelectedForCompare={false} isHovered={isHovered} />
|
|
||||||
<IAIDroppable data={droppableData} dropLabel={<Text fontSize="md">{t('unifiedCanvas.move')}</Text>} />
|
|
||||||
</Flex>
|
|
||||||
</Tooltip>
|
|
||||||
)}
|
|
||||||
</NoBoardBoardContextMenu>
|
|
||||||
</Flex>
|
|
||||||
</Box>
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,48 +1,17 @@
|
|||||||
import { Button, Flex, Icon, Spacer } from '@invoke-ai/ui-library';
|
import { Flex, Text } from '@invoke-ai/ui-library';
|
||||||
import { useAppSelector } from 'app/store/storeHooks';
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
import { memo, useMemo } from 'react';
|
import { memo } from 'react';
|
||||||
import { PiCaretUpBold } from 'react-icons/pi';
|
|
||||||
import { useBoardName } from 'services/api/hooks/useBoardName';
|
import { useBoardName } from 'services/api/hooks/useBoardName';
|
||||||
|
|
||||||
type Props = {
|
const GalleryBoardName = () => {
|
||||||
isOpen: boolean;
|
|
||||||
onToggle: () => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
const GalleryBoardName = (props: Props) => {
|
|
||||||
const { isOpen, onToggle } = props;
|
|
||||||
const selectedBoardId = useAppSelector((s) => s.gallery.selectedBoardId);
|
const selectedBoardId = useAppSelector((s) => s.gallery.selectedBoardId);
|
||||||
const boardName = useBoardName(selectedBoardId);
|
const boardName = useBoardName(selectedBoardId);
|
||||||
|
|
||||||
const formattedBoardName = useMemo(() => {
|
|
||||||
if (boardName.length > 20) {
|
|
||||||
return `${boardName.substring(0, 20)}...`;
|
|
||||||
}
|
|
||||||
return boardName;
|
|
||||||
}, [boardName]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex
|
<Flex w="full" borderWidth={1} borderRadius="base" alignItems="center" justifyContent="center" px={2}>
|
||||||
as={Button}
|
<Text fontWeight="semibold" fontSize="md" noOfLines={1} wordBreak="break-all" color="base.200">
|
||||||
onClick={onToggle}
|
{boardName}
|
||||||
size="sm"
|
</Text>
|
||||||
position="relative"
|
|
||||||
gap={2}
|
|
||||||
w="full"
|
|
||||||
justifyContent="center"
|
|
||||||
alignItems="center"
|
|
||||||
px={2}
|
|
||||||
>
|
|
||||||
<Spacer />
|
|
||||||
{formattedBoardName}
|
|
||||||
<Spacer />
|
|
||||||
<Icon
|
|
||||||
as={PiCaretUpBold}
|
|
||||||
boxSize={4}
|
|
||||||
transform={isOpen ? 'rotate(0deg)' : 'rotate(180deg)'}
|
|
||||||
transitionProperty="common"
|
|
||||||
transitionDuration="normal"
|
|
||||||
/>
|
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Box, Button, ButtonGroup, Flex, Tab, TabList, Tabs, useDisclosure } from '@invoke-ai/ui-library';
|
import { Button, ButtonGroup, Flex, Tab, TabList, Tabs } from '@invoke-ai/ui-library';
|
||||||
import { useStore } from '@nanostores/react';
|
import { useStore } from '@nanostores/react';
|
||||||
import { $galleryHeader } from 'app/store/nanostores/galleryHeader';
|
import { $galleryHeader } from 'app/store/nanostores/galleryHeader';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
@ -10,7 +10,6 @@ import { RiServerLine } from 'react-icons/ri';
|
|||||||
|
|
||||||
import BoardsList from './Boards/BoardsList/BoardsList';
|
import BoardsList from './Boards/BoardsList/BoardsList';
|
||||||
import GalleryBoardName from './GalleryBoardName';
|
import GalleryBoardName from './GalleryBoardName';
|
||||||
import GallerySettingsPopover from './GallerySettingsPopover/GallerySettingsPopover';
|
|
||||||
import GalleryImageGrid from './ImageGrid/GalleryImageGrid';
|
import GalleryImageGrid from './ImageGrid/GalleryImageGrid';
|
||||||
import { GalleryPagination } from './ImageGrid/GalleryPagination';
|
import { GalleryPagination } from './ImageGrid/GalleryPagination';
|
||||||
import { GallerySearch } from './ImageGrid/GallerySearch';
|
import { GallerySearch } from './ImageGrid/GallerySearch';
|
||||||
@ -20,7 +19,6 @@ const ImageGalleryContent = () => {
|
|||||||
const galleryView = useAppSelector((s) => s.gallery.galleryView);
|
const galleryView = useAppSelector((s) => s.gallery.galleryView);
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const galleryHeader = useStore($galleryHeader);
|
const galleryHeader = useStore($galleryHeader);
|
||||||
const { isOpen: isBoardListOpen, onToggle: onToggleBoardList } = useDisclosure({ defaultIsOpen: true });
|
|
||||||
|
|
||||||
const handleClickImages = useCallback(() => {
|
const handleClickImages = useCallback(() => {
|
||||||
dispatch(galleryViewChanged('images'));
|
dispatch(galleryViewChanged('images'));
|
||||||
@ -42,15 +40,8 @@ const ImageGalleryContent = () => {
|
|||||||
gap={2}
|
gap={2}
|
||||||
>
|
>
|
||||||
{galleryHeader}
|
{galleryHeader}
|
||||||
<Box>
|
<BoardsList />
|
||||||
<Flex alignItems="center" justifyContent="space-between" gap={2}>
|
<GalleryBoardName />
|
||||||
<GalleryBoardName isOpen={isBoardListOpen} onToggle={onToggleBoardList} />
|
|
||||||
<GallerySettingsPopover />
|
|
||||||
</Flex>
|
|
||||||
<Box>
|
|
||||||
<BoardsList isOpen={isBoardListOpen} />
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
<Flex alignItems="center" justifyContent="space-between" gap={2}>
|
<Flex alignItems="center" justifyContent="space-between" gap={2}>
|
||||||
<Tabs index={galleryView === 'images' ? 0 : 1} variant="unstyled" size="sm" w="full">
|
<Tabs index={galleryView === 'images' ? 0 : 1} variant="unstyled" size="sm" w="full">
|
||||||
<TabList>
|
<TabList>
|
||||||
|
@ -18,6 +18,7 @@ const initialConfigState: AppConfig = {
|
|||||||
isLocal: true,
|
isLocal: true,
|
||||||
shouldUpdateImagesOnConnect: false,
|
shouldUpdateImagesOnConnect: false,
|
||||||
shouldFetchMetadataFromApi: false,
|
shouldFetchMetadataFromApi: false,
|
||||||
|
allowPrivateBoards: false,
|
||||||
disabledTabs: [],
|
disabledTabs: [],
|
||||||
disabledFeatures: ['lightbox', 'faceRestore', 'batches'],
|
disabledFeatures: ['lightbox', 'faceRestore', 'batches'],
|
||||||
disabledSDFeatures: ['variation', 'symmetry', 'hires', 'perlinNoise', 'noiseThreshold'],
|
disabledSDFeatures: ['variation', 'symmetry', 'hires', 'perlinNoise', 'noiseThreshold'],
|
||||||
|
@ -1,5 +1,11 @@
|
|||||||
import { ASSETS_CATEGORIES, IMAGE_CATEGORIES } from 'features/gallery/store/types';
|
import { ASSETS_CATEGORIES, IMAGE_CATEGORIES } from 'features/gallery/store/types';
|
||||||
import type { BoardDTO, ListBoardsArgs, OffsetPaginatedResults_ImageDTO_, UpdateBoardArg } from 'services/api/types';
|
import type {
|
||||||
|
BoardDTO,
|
||||||
|
CreateBoardArg,
|
||||||
|
ListBoardsArgs,
|
||||||
|
OffsetPaginatedResults_ImageDTO_,
|
||||||
|
UpdateBoardArg,
|
||||||
|
} from 'services/api/types';
|
||||||
import { getListImagesUrl } from 'services/api/util';
|
import { getListImagesUrl } from 'services/api/util';
|
||||||
|
|
||||||
import type { ApiTagDescription } from '..';
|
import type { ApiTagDescription } from '..';
|
||||||
@ -87,11 +93,11 @@ export const boardsApi = api.injectEndpoints({
|
|||||||
* Boards Mutations
|
* Boards Mutations
|
||||||
*/
|
*/
|
||||||
|
|
||||||
createBoard: build.mutation<BoardDTO, string>({
|
createBoard: build.mutation<BoardDTO, CreateBoardArg>({
|
||||||
query: (board_name) => ({
|
query: ({ board_name, is_private }) => ({
|
||||||
url: buildBoardsUrl(),
|
url: buildBoardsUrl(),
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
params: { board_name },
|
params: { board_name, is_private },
|
||||||
}),
|
}),
|
||||||
invalidatesTags: [{ type: 'Board', id: LIST_TAG }],
|
invalidatesTags: [{ type: 'Board', id: LIST_TAG }],
|
||||||
}),
|
}),
|
||||||
|
@ -1058,6 +1058,11 @@ export type components = {
|
|||||||
* @description Whether or not the board is archived.
|
* @description Whether or not the board is archived.
|
||||||
*/
|
*/
|
||||||
archived: boolean;
|
archived: boolean;
|
||||||
|
/**
|
||||||
|
* Is Private
|
||||||
|
* @description Whether the board is private.
|
||||||
|
*/
|
||||||
|
is_private?: boolean | null;
|
||||||
/**
|
/**
|
||||||
* Image Count
|
* Image Count
|
||||||
* @description The number of images in the board.
|
* @description The number of images in the board.
|
||||||
@ -6561,6 +6566,12 @@ export type components = {
|
|||||||
* @default false
|
* @default false
|
||||||
*/
|
*/
|
||||||
tiled?: boolean;
|
tiled?: boolean;
|
||||||
|
/**
|
||||||
|
* Tile Size
|
||||||
|
* @description The tile size for VAE tiling in pixels (image space). If set to 0, the default tile size for the
|
||||||
|
* @default 0
|
||||||
|
*/
|
||||||
|
tile_size?: number;
|
||||||
/**
|
/**
|
||||||
* Fp32
|
* Fp32
|
||||||
* @description Whether or not to use full float32 precision
|
* @description Whether or not to use full float32 precision
|
||||||
@ -7293,145 +7304,145 @@ export type components = {
|
|||||||
project_id: string | null;
|
project_id: string | null;
|
||||||
};
|
};
|
||||||
InvocationOutputMap: {
|
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"];
|
rectangle_mask: components["schemas"]["MaskOutput"];
|
||||||
canny_image_processor: components["schemas"]["ImageOutput"];
|
hed_image_processor: components["schemas"]["ImageOutput"];
|
||||||
calculate_image_tiles_even_split: components["schemas"]["CalculateImageTilesOutput"];
|
compel: components["schemas"]["ConditioningOutput"];
|
||||||
|
img_resize: components["schemas"]["ImageOutput"];
|
||||||
|
ideal_size: components["schemas"]["IdealSizeOutput"];
|
||||||
|
rand_int: components["schemas"]["IntegerOutput"];
|
||||||
|
clip_skip: components["schemas"]["CLIPSkipInvocationOutput"];
|
||||||
|
string_collection: components["schemas"]["StringCollectionOutput"];
|
||||||
|
create_gradient_mask: components["schemas"]["GradientMaskOutput"];
|
||||||
|
round_float: components["schemas"]["FloatOutput"];
|
||||||
|
scheduler: components["schemas"]["SchedulerOutput"];
|
||||||
|
main_model_loader: components["schemas"]["ModelLoaderOutput"];
|
||||||
|
string_split: components["schemas"]["String2Output"];
|
||||||
mask_from_id: components["schemas"]["ImageOutput"];
|
mask_from_id: components["schemas"]["ImageOutput"];
|
||||||
metadata_item: components["schemas"]["MetadataItemOutput"];
|
collect: components["schemas"]["CollectInvocationOutput"];
|
||||||
infill_tile: components["schemas"]["ImageOutput"];
|
heuristic_resize: components["schemas"]["ImageOutput"];
|
||||||
tiled_multi_diffusion_denoise_latents: components["schemas"]["LatentsOutput"];
|
tomask: components["schemas"]["ImageOutput"];
|
||||||
img_channel_multiply: components["schemas"]["ImageOutput"];
|
|
||||||
boolean_collection: components["schemas"]["BooleanCollectionOutput"];
|
boolean_collection: components["schemas"]["BooleanCollectionOutput"];
|
||||||
lora_loader: components["schemas"]["LoRALoaderOutput"];
|
core_metadata: components["schemas"]["MetadataOutput"];
|
||||||
float_collection: components["schemas"]["FloatCollectionOutput"];
|
canny_image_processor: components["schemas"]["ImageOutput"];
|
||||||
string: components["schemas"]["StringOutput"];
|
string_replace: 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"];
|
face_mask_detection: components["schemas"]["FaceMaskOutput"];
|
||||||
|
integer: components["schemas"]["IntegerOutput"];
|
||||||
|
img_watermark: components["schemas"]["ImageOutput"];
|
||||||
|
img_crop: components["schemas"]["ImageOutput"];
|
||||||
|
t2i_adapter: components["schemas"]["T2IAdapterOutput"];
|
||||||
|
create_denoise_mask: components["schemas"]["DenoiseMaskOutput"];
|
||||||
rand_float: components["schemas"]["FloatOutput"];
|
rand_float: components["schemas"]["FloatOutput"];
|
||||||
float: components["schemas"]["FloatOutput"];
|
zoe_depth_image_processor: components["schemas"]["ImageOutput"];
|
||||||
random_range: components["schemas"]["IntegerCollectionOutput"];
|
face_off: components["schemas"]["FaceOffOutput"];
|
||||||
integer_collection: components["schemas"]["IntegerCollectionOutput"];
|
tile_to_properties: components["schemas"]["TileToPropertiesOutput"];
|
||||||
sdxl_refiner_model_loader: components["schemas"]["SDXLRefinerModelLoaderOutput"];
|
color_map_image_processor: components["schemas"]["ImageOutput"];
|
||||||
mask_combine: components["schemas"]["ImageOutput"];
|
lineart_anime_image_processor: components["schemas"]["ImageOutput"];
|
||||||
tile_image_processor: components["schemas"]["ImageOutput"];
|
face_identifier: components["schemas"]["ImageOutput"];
|
||||||
|
float_math: components["schemas"]["FloatOutput"];
|
||||||
|
mediapipe_face_processor: components["schemas"]["ImageOutput"];
|
||||||
|
img_channel_multiply: components["schemas"]["ImageOutput"];
|
||||||
|
metadata_item: components["schemas"]["MetadataItemOutput"];
|
||||||
|
img_ilerp: components["schemas"]["ImageOutput"];
|
||||||
|
conditioning: components["schemas"]["ConditioningOutput"];
|
||||||
|
pidi_image_processor: components["schemas"]["ImageOutput"];
|
||||||
|
seamless: components["schemas"]["SeamlessModeOutput"];
|
||||||
|
latents: components["schemas"]["LatentsOutput"];
|
||||||
img_chan: components["schemas"]["ImageOutput"];
|
img_chan: components["schemas"]["ImageOutput"];
|
||||||
vae_loader: components["schemas"]["VAEOutput"];
|
model_identifier: components["schemas"]["ModelIdentifierOutput"];
|
||||||
|
noise: components["schemas"]["NoiseOutput"];
|
||||||
|
string_join: components["schemas"]["StringOutput"];
|
||||||
|
blank_image: components["schemas"]["ImageOutput"];
|
||||||
|
calculate_image_tiles: components["schemas"]["CalculateImageTilesOutput"];
|
||||||
|
invert_tensor_mask: components["schemas"]["MaskOutput"];
|
||||||
|
save_image: components["schemas"]["ImageOutput"];
|
||||||
|
unsharp_mask: components["schemas"]["ImageOutput"];
|
||||||
|
image_mask_to_tensor: components["schemas"]["MaskOutput"];
|
||||||
|
step_param_easing: components["schemas"]["FloatCollectionOutput"];
|
||||||
|
merge_tiles_to_image: components["schemas"]["ImageOutput"];
|
||||||
|
integer_collection: components["schemas"]["IntegerCollectionOutput"];
|
||||||
|
calculate_image_tiles_even_split: components["schemas"]["CalculateImageTilesOutput"];
|
||||||
|
integer_math: components["schemas"]["IntegerOutput"];
|
||||||
|
range: components["schemas"]["IntegerCollectionOutput"];
|
||||||
prompt_from_file: components["schemas"]["StringCollectionOutput"];
|
prompt_from_file: components["schemas"]["StringCollectionOutput"];
|
||||||
|
segment_anything_processor: components["schemas"]["ImageOutput"];
|
||||||
|
freeu: components["schemas"]["UNetOutput"];
|
||||||
|
sub: components["schemas"]["IntegerOutput"];
|
||||||
|
lresize: components["schemas"]["LatentsOutput"];
|
||||||
|
float: components["schemas"]["FloatOutput"];
|
||||||
|
float_collection: components["schemas"]["FloatCollectionOutput"];
|
||||||
|
dynamic_prompt: components["schemas"]["StringCollectionOutput"];
|
||||||
|
infill_lama: components["schemas"]["ImageOutput"];
|
||||||
|
l2i: components["schemas"]["ImageOutput"];
|
||||||
|
img_lerp: components["schemas"]["ImageOutput"];
|
||||||
|
ip_adapter: components["schemas"]["IPAdapterOutput"];
|
||||||
|
lora_collection_loader: components["schemas"]["LoRALoaderOutput"];
|
||||||
|
color: components["schemas"]["ColorOutput"];
|
||||||
|
tiled_multi_diffusion_denoise_latents: components["schemas"]["LatentsOutput"];
|
||||||
|
cv_inpaint: components["schemas"]["ImageOutput"];
|
||||||
|
lscale: components["schemas"]["LatentsOutput"];
|
||||||
|
string: components["schemas"]["StringOutput"];
|
||||||
|
sdxl_refiner_compel_prompt: components["schemas"]["ConditioningOutput"];
|
||||||
|
string_join_three: components["schemas"]["StringOutput"];
|
||||||
|
midas_depth_image_processor: components["schemas"]["ImageOutput"];
|
||||||
|
esrgan: components["schemas"]["ImageOutput"];
|
||||||
|
sdxl_refiner_model_loader: components["schemas"]["SDXLRefinerModelLoaderOutput"];
|
||||||
|
mul: components["schemas"]["IntegerOutput"];
|
||||||
|
normalbae_image_processor: components["schemas"]["ImageOutput"];
|
||||||
|
infill_rgba: components["schemas"]["ImageOutput"];
|
||||||
|
sdxl_model_loader: components["schemas"]["SDXLModelLoaderOutput"];
|
||||||
|
vae_loader: components["schemas"]["VAEOutput"];
|
||||||
|
float_to_int: components["schemas"]["IntegerOutput"];
|
||||||
|
lora_selector: components["schemas"]["LoRASelectorOutput"];
|
||||||
|
crop_latents: components["schemas"]["LatentsOutput"];
|
||||||
|
img_mul: components["schemas"]["ImageOutput"];
|
||||||
float_range: components["schemas"]["FloatCollectionOutput"];
|
float_range: components["schemas"]["FloatCollectionOutput"];
|
||||||
merge_metadata: components["schemas"]["MetadataOutput"];
|
merge_metadata: components["schemas"]["MetadataOutput"];
|
||||||
sdxl_compel_prompt: components["schemas"]["ConditioningOutput"];
|
img_blur: components["schemas"]["ImageOutput"];
|
||||||
hed_image_processor: components["schemas"]["ImageOutput"];
|
boolean: components["schemas"]["BooleanOutput"];
|
||||||
lora_selector: components["schemas"]["LoRASelectorOutput"];
|
tile_image_processor: components["schemas"]["ImageOutput"];
|
||||||
conditioning: components["schemas"]["ConditioningOutput"];
|
mlsd_image_processor: components["schemas"]["ImageOutput"];
|
||||||
|
infill_patchmatch: components["schemas"]["ImageOutput"];
|
||||||
|
img_pad_crop: components["schemas"]["ImageOutput"];
|
||||||
leres_image_processor: components["schemas"]["ImageOutput"];
|
leres_image_processor: components["schemas"]["ImageOutput"];
|
||||||
mediapipe_face_processor: components["schemas"]["ImageOutput"];
|
sdxl_lora_loader: components["schemas"]["SDXLLoRALoaderOutput"];
|
||||||
|
dw_openpose_image_processor: components["schemas"]["ImageOutput"];
|
||||||
|
img_scale: components["schemas"]["ImageOutput"];
|
||||||
|
pair_tile_image: components["schemas"]["PairTileImageOutput"];
|
||||||
|
lblend: components["schemas"]["LatentsOutput"];
|
||||||
|
range_of_size: components["schemas"]["IntegerCollectionOutput"];
|
||||||
|
image_collection: components["schemas"]["ImageCollectionOutput"];
|
||||||
|
calculate_image_tiles_min_overlap: components["schemas"]["CalculateImageTilesOutput"];
|
||||||
|
img_channel_offset: components["schemas"]["ImageOutput"];
|
||||||
|
alpha_mask_to_tensor: components["schemas"]["MaskOutput"];
|
||||||
|
infill_cv2: components["schemas"]["ImageOutput"];
|
||||||
|
mask_combine: components["schemas"]["ImageOutput"];
|
||||||
|
string_split_neg: components["schemas"]["StringPosNegOutput"];
|
||||||
|
sdxl_lora_collection_loader: components["schemas"]["SDXLLoRALoaderOutput"];
|
||||||
|
lineart_image_processor: components["schemas"]["ImageOutput"];
|
||||||
|
img_nsfw: components["schemas"]["ImageOutput"];
|
||||||
|
image: components["schemas"]["ImageOutput"];
|
||||||
content_shuffle_image_processor: components["schemas"]["ImageOutput"];
|
content_shuffle_image_processor: components["schemas"]["ImageOutput"];
|
||||||
|
canvas_paste_back: components["schemas"]["ImageOutput"];
|
||||||
|
iterate: components["schemas"]["IterateInvocationOutput"];
|
||||||
|
div: components["schemas"]["IntegerOutput"];
|
||||||
|
latents_collection: components["schemas"]["LatentsCollectionOutput"];
|
||||||
|
img_conv: components["schemas"]["ImageOutput"];
|
||||||
|
mask_edge: components["schemas"]["ImageOutput"];
|
||||||
|
conditioning_collection: components["schemas"]["ConditioningCollectionOutput"];
|
||||||
|
img_hue_adjust: components["schemas"]["ImageOutput"];
|
||||||
|
depth_anything_image_processor: components["schemas"]["ImageOutput"];
|
||||||
|
lora_loader: components["schemas"]["LoRALoaderOutput"];
|
||||||
|
sdxl_compel_prompt: components["schemas"]["ConditioningOutput"];
|
||||||
|
add: components["schemas"]["IntegerOutput"];
|
||||||
|
controlnet: components["schemas"]["ControlOutput"];
|
||||||
|
color_correct: components["schemas"]["ImageOutput"];
|
||||||
|
random_range: components["schemas"]["IntegerCollectionOutput"];
|
||||||
|
denoise_latents: components["schemas"]["LatentsOutput"];
|
||||||
|
metadata: components["schemas"]["MetadataOutput"];
|
||||||
|
i2l: components["schemas"]["LatentsOutput"];
|
||||||
|
show_image: components["schemas"]["ImageOutput"];
|
||||||
|
img_paste: components["schemas"]["ImageOutput"];
|
||||||
|
infill_tile: components["schemas"]["ImageOutput"];
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
* InvocationStartedEvent
|
* InvocationStartedEvent
|
||||||
@ -7769,6 +7780,12 @@ export type components = {
|
|||||||
* @default false
|
* @default false
|
||||||
*/
|
*/
|
||||||
tiled?: boolean;
|
tiled?: boolean;
|
||||||
|
/**
|
||||||
|
* Tile Size
|
||||||
|
* @description The tile size for VAE tiling in pixels (image space). If set to 0, the default tile size for the
|
||||||
|
* @default 0
|
||||||
|
*/
|
||||||
|
tile_size?: number;
|
||||||
/**
|
/**
|
||||||
* Fp32
|
* Fp32
|
||||||
* @description Whether or not to use full float32 precision
|
* @description Whether or not to use full float32 precision
|
||||||
@ -15014,6 +15031,8 @@ export type operations = {
|
|||||||
query: {
|
query: {
|
||||||
/** @description The name of the board to create */
|
/** @description The name of the board to create */
|
||||||
board_name: string;
|
board_name: string;
|
||||||
|
/** @description Whether the board is private */
|
||||||
|
is_private?: boolean;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
responses: {
|
responses: {
|
||||||
|
@ -11,6 +11,8 @@ export type ListBoardsArgs = NonNullable<paths['/api/v1/boards/']['get']['parame
|
|||||||
export type DeleteBoardResult =
|
export type DeleteBoardResult =
|
||||||
paths['/api/v1/boards/{board_id}']['delete']['responses']['200']['content']['application/json'];
|
paths['/api/v1/boards/{board_id}']['delete']['responses']['200']['content']['application/json'];
|
||||||
|
|
||||||
|
export type CreateBoardArg = paths['/api/v1/boards/']['post']['parameters']['query'];
|
||||||
|
|
||||||
export type UpdateBoardArg = paths['/api/v1/boards/{board_id}']['patch']['parameters']['path'] & {
|
export type UpdateBoardArg = paths['/api/v1/boards/{board_id}']['patch']['parameters']['path'] & {
|
||||||
changes: paths['/api/v1/boards/{board_id}']['patch']['requestBody']['content']['application/json'];
|
changes: paths['/api/v1/boards/{board_id}']['patch']['requestBody']['content']['application/json'];
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user