mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
feat(ui): boards list layout & style tweaking
This commit is contained in:
parent
907b257984
commit
81cf47dd99
@ -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"
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Box, Collapse, Flex, Icon, Text, useDisclosure } from '@invoke-ai/ui-library';
|
import { Collapse, Flex, Icon, Text, useDisclosure } from '@invoke-ai/ui-library';
|
||||||
import { EMPTY_ARRAY } from 'app/store/constants';
|
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';
|
||||||
@ -45,80 +45,89 @@ const BoardsList = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Flex layerStyle="first" flexDir="column" gap={2} p={2} mt={2} borderRadius="base">
|
<Flex layerStyle="first" flexDir="column" borderRadius="base">
|
||||||
<Flex gap={2} alignItems="center">
|
<Flex gap={2} alignItems="center" pb={2}>
|
||||||
<BoardsSearch />
|
<BoardsSearch />
|
||||||
<GallerySettingsPopover />
|
<GallerySettingsPopover />
|
||||||
</Flex>
|
</Flex>
|
||||||
<OverlayScrollbarsComponent defer style={overlayScrollbarsStyles} options={overlayScrollbarsParams.options}>
|
{allowPrivateBoards && (
|
||||||
<Box maxH={346}>
|
<>
|
||||||
{allowPrivateBoards && (
|
<Flex w="full" gap={2}>
|
||||||
<>
|
<Flex
|
||||||
<Flex borderBottom="1px" borderColor="base.400" my="2" justifyContent="space-between">
|
flexGrow={1}
|
||||||
<Flex onClick={privateBoardsDisclosure.onToggle} gap={2} alignItems="center" cursor="pointer">
|
onClick={privateBoardsDisclosure.onToggle}
|
||||||
<Icon
|
gap={2}
|
||||||
as={PiCaretUpBold}
|
alignItems="center"
|
||||||
boxSize={6}
|
cursor="pointer"
|
||||||
transform={privateBoardsDisclosure.isOpen ? 'rotate(0deg)' : 'rotate(180deg)'}
|
>
|
||||||
transitionProperty="common"
|
|
||||||
transitionDuration="normal"
|
|
||||||
color="base.400"
|
|
||||||
/>
|
|
||||||
<Text fontSize="md" fontWeight="medium">
|
|
||||||
{t('boards.private')}
|
|
||||||
</Text>
|
|
||||||
</Flex>
|
|
||||||
<AddBoardButton isPrivateBoard={true} />
|
|
||||||
</Flex>
|
|
||||||
<Collapse in={privateBoardsDisclosure.isOpen} animateOpacity>
|
|
||||||
<Flex direction="column">
|
|
||||||
<NoBoardBoard isSelected={selectedBoardId === 'none'} />
|
|
||||||
{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={sharedBoardsDisclosure.onToggle} gap={2} alignItems="center" cursor="pointer">
|
|
||||||
<Icon
|
<Icon
|
||||||
as={PiCaretUpBold}
|
as={PiCaretUpBold}
|
||||||
boxSize={6}
|
boxSize={4}
|
||||||
transform={sharedBoardsDisclosure.isOpen ? 'rotate(0deg)' : 'rotate(180deg)'}
|
transform={privateBoardsDisclosure.isOpen ? 'rotate(0deg)' : 'rotate(180deg)'}
|
||||||
transitionProperty="common"
|
transitionProperty="common"
|
||||||
transitionDuration="normal"
|
transitionDuration="normal"
|
||||||
color="base.400"
|
color="base.400"
|
||||||
/>
|
/>
|
||||||
<Text fontSize="md" fontWeight="medium">
|
<Text fontSize="md" fontWeight="medium" userSelect="none">
|
||||||
{allowPrivateBoards ? t('boards.shared') : t('boards.boards')}
|
{t('boards.private')}
|
||||||
</Text>
|
</Text>
|
||||||
</Flex>
|
</Flex>
|
||||||
<AddBoardButton isPrivateBoard={false} />
|
<AddBoardButton isPrivateBoard={true} />
|
||||||
</Flex>
|
</Flex>
|
||||||
<Collapse in={sharedBoardsDisclosure.isOpen} animateOpacity>
|
<Collapse in={privateBoardsDisclosure.isOpen} animateOpacity>
|
||||||
<Flex direction="column">
|
<OverlayScrollbarsComponent
|
||||||
{filteredSharedBoards.map((board) => (
|
defer
|
||||||
<GalleryBoard
|
style={overlayScrollbarsStyles}
|
||||||
board={board}
|
options={overlayScrollbarsParams.options}
|
||||||
isSelected={selectedBoardId === board.board_id}
|
>
|
||||||
setBoardToDelete={setBoardToDelete}
|
<Flex direction="column" maxH={346} gap={1}>
|
||||||
key={board.board_id}
|
<NoBoardBoard isSelected={selectedBoardId === 'none'} />
|
||||||
/>
|
{filteredPrivateBoards.map((board) => (
|
||||||
))}
|
<GalleryBoard
|
||||||
</Flex>
|
board={board}
|
||||||
|
isSelected={selectedBoardId === board.board_id}
|
||||||
|
setBoardToDelete={setBoardToDelete}
|
||||||
|
key={board.board_id}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Flex>
|
||||||
|
</OverlayScrollbarsComponent>
|
||||||
</Collapse>
|
</Collapse>
|
||||||
</Box>
|
</>
|
||||||
</OverlayScrollbarsComponent>
|
)}
|
||||||
|
<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>
|
||||||
|
<Collapse in={sharedBoardsDisclosure.isOpen} animateOpacity>
|
||||||
|
<OverlayScrollbarsComponent defer style={overlayScrollbarsStyles} options={overlayScrollbarsParams.options}>
|
||||||
|
<Flex direction="column" maxH={346} gap={1}>
|
||||||
|
{filteredSharedBoards.map((board) => (
|
||||||
|
<GalleryBoard
|
||||||
|
board={board}
|
||||||
|
isSelected={selectedBoardId === board.board_id}
|
||||||
|
setBoardToDelete={setBoardToDelete}
|
||||||
|
key={board.board_id}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Flex>
|
||||||
|
</OverlayScrollbarsComponent>
|
||||||
|
</Collapse>
|
||||||
</Flex>
|
</Flex>
|
||||||
<DeleteBoardModal boardToDelete={boardToDelete} setBoardToDelete={setBoardToDelete} />
|
<DeleteBoardModal boardToDelete={boardToDelete} setBoardToDelete={setBoardToDelete} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default memo(BoardsList);
|
export default memo(BoardsList);
|
||||||
|
@ -1,16 +1,25 @@
|
|||||||
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 {
|
||||||
|
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 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 } 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';
|
||||||
@ -19,14 +28,13 @@ const editableInputStyles: SystemStyleObject = {
|
|||||||
p: 0,
|
p: 0,
|
||||||
fontSize: 'md',
|
fontSize: 'md',
|
||||||
w: '100%',
|
w: '100%',
|
||||||
|
_focusVisible: {
|
||||||
|
p: 0,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
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 {
|
||||||
@ -39,65 +47,51 @@ const GalleryBoard = ({ board, isSelected, setBoardToDelete }: GalleryBoardProps
|
|||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const autoAssignBoardOnClick = useAppSelector((s) => s.gallery.autoAssignBoardOnClick);
|
const autoAssignBoardOnClick = useAppSelector((s) => s.gallery.autoAssignBoardOnClick);
|
||||||
|
const editingDisclosure = useDisclosure();
|
||||||
const [isHovered, setIsHovered] = useState(false);
|
const [localBoardName, setLocalBoardName] = useState(board.board_name);
|
||||||
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) => {
|
||||||
@ -105,92 +99,88 @@ const GalleryBoard = ({ board, isSelected, setBoardToDelete }: GalleryBoardProps
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box w="full" userSelect="none" px="1">
|
<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}
|
||||||
alignItems="center"
|
>
|
||||||
borderRadius="base"
|
<Flex
|
||||||
w="full"
|
position="relative"
|
||||||
my="2"
|
ref={ref}
|
||||||
userSelect="none"
|
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"
|
||||||
alignItems="center"
|
noOfLines={1}
|
||||||
justifyContent="space-between"
|
w="fit-content"
|
||||||
borderRadius="base"
|
wordBreak="break-all"
|
||||||
cursor="pointer"
|
color={isSelected ? 'base.100' : 'base.400'}
|
||||||
gap="6"
|
fontWeight={isSelected ? 'semibold' : 'normal'}
|
||||||
p="1"
|
/>
|
||||||
>
|
<EditableInput sx={editableInputStyles} />
|
||||||
<Flex gap="6">
|
</Editable>
|
||||||
{board.archived && <ArchivedIcon />}
|
{board.archived && !editingDisclosure.isOpen && (
|
||||||
{coverImage?.thumbnail_url ? (
|
<Icon
|
||||||
<Image
|
as={PiArchiveBold}
|
||||||
src={coverImage?.thumbnail_url}
|
fill="base.300"
|
||||||
draggable={false}
|
filter="drop-shadow(0px 0px 0.1rem var(--invoke-colors-base-800))"
|
||||||
objectFit="cover"
|
/>
|
||||||
w="8"
|
)}
|
||||||
h="8"
|
<IAIDroppable data={droppableData} dropLabel={<Text fontSize="md">{t('unifiedCanvas.move')}</Text>} />
|
||||||
borderRadius="base"
|
</Flex>
|
||||||
borderBottomRadius="lg"
|
</Tooltip>
|
||||||
/>
|
)}
|
||||||
) : (
|
</BoardContextMenu>
|
||||||
<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>
|
|
||||||
</Tooltip>
|
|
||||||
)}
|
|
||||||
</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,13 +1,12 @@
|
|||||||
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 { 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 { useBoardName } from 'services/api/hooks/useBoardName';
|
import { useBoardName } from 'services/api/hooks/useBoardName';
|
||||||
|
|
||||||
@ -15,6 +14,10 @@ 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 autoAssignBoardOnClick = useAppSelector((s) => s.gallery.autoAssignBoardOnClick);
|
const autoAssignBoardOnClick = useAppSelector((s) => s.gallery.autoAssignBoardOnClick);
|
||||||
@ -25,15 +28,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(
|
||||||
() => ({
|
() => ({
|
||||||
@ -44,50 +38,47 @@ const NoBoardBoard = memo(({ isSelected }: Props) => {
|
|||||||
);
|
);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
return (
|
return (
|
||||||
<Box w="full" userSelect="none" px="1">
|
<NoBoardBoardContextMenu>
|
||||||
<Flex
|
{(ref) => (
|
||||||
onMouseOver={handleMouseOver}
|
<Tooltip label={<BoardTotalsTooltip board_id="none" isArchived={false} />} openDelay={1000}>
|
||||||
onMouseOut={handleMouseOut}
|
<Flex
|
||||||
position="relative"
|
position="relative"
|
||||||
alignItems="center"
|
ref={ref}
|
||||||
borderRadius="base"
|
onClick={handleSelectBoard}
|
||||||
w="full"
|
w="full"
|
||||||
my="2"
|
alignItems="center"
|
||||||
userSelect="none"
|
borderRadius="base"
|
||||||
>
|
cursor="pointer"
|
||||||
<NoBoardBoardContextMenu>
|
px={2}
|
||||||
{(ref) => (
|
py={1}
|
||||||
<Tooltip label={<BoardTotalsTooltip board_id="none" isArchived={false} />} openDelay={1000}>
|
gap={2}
|
||||||
<Flex
|
bg={isSelected ? 'base.800' : undefined}
|
||||||
ref={ref}
|
_hover={_hover}
|
||||||
onClick={handleSelectBoard}
|
>
|
||||||
w="full"
|
<Flex w={8} h={8} justifyContent="center" alignItems="center">
|
||||||
alignItems="center"
|
{/* iconified from public/assets/images/invoke-symbol-wht-lrg.svg */}
|
||||||
borderRadius="base"
|
<Icon boxSize={6} opacity={1} stroke="base.500" viewBox="0 0 66 66" fill="none">
|
||||||
cursor="pointer"
|
<path
|
||||||
gap="6"
|
d="M43.9137 16H63.1211V3H3.12109V16H22.3285L43.9137 50H63.1211V63H3.12109V50H22.3285"
|
||||||
p="1"
|
strokeWidth="5"
|
||||||
>
|
|
||||||
<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'}>
|
</Icon>
|
||||||
{boardName}
|
</Flex>
|
||||||
</Text>
|
|
||||||
<SelectionOverlay isSelected={isSelected} isSelectedForCompare={false} isHovered={isHovered} />
|
<Text
|
||||||
<IAIDroppable data={droppableData} dropLabel={<Text fontSize="md">{t('unifiedCanvas.move')}</Text>} />
|
fontSize="md"
|
||||||
</Flex>
|
color={isSelected ? 'base.100' : 'base.400'}
|
||||||
</Tooltip>
|
fontWeight={isSelected ? 'semibold' : 'normal'}
|
||||||
)}
|
noOfLines={1}
|
||||||
</NoBoardBoardContextMenu>
|
flexShrink={0}
|
||||||
</Flex>
|
>
|
||||||
</Box>
|
{boardName}
|
||||||
|
</Text>
|
||||||
|
<IAIDroppable data={droppableData} dropLabel={<Text fontSize="md">{t('unifiedCanvas.move')}</Text>} />
|
||||||
|
</Flex>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
</NoBoardBoardContextMenu>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -16,7 +16,6 @@ const GalleryBoardName = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex
|
<Flex
|
||||||
my="1"
|
|
||||||
justifyContent="center"
|
justifyContent="center"
|
||||||
fontSize="md"
|
fontSize="md"
|
||||||
fontWeight="bold"
|
fontWeight="bold"
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Box, Button, ButtonGroup, Flex, Tab, TabList, Tabs } 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';
|
||||||
@ -40,10 +40,8 @@ const ImageGalleryContent = () => {
|
|||||||
gap={2}
|
gap={2}
|
||||||
>
|
>
|
||||||
{galleryHeader}
|
{galleryHeader}
|
||||||
<Box>
|
<BoardsList />
|
||||||
<BoardsList />
|
<GalleryBoardName />
|
||||||
<GalleryBoardName />
|
|
||||||
</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>
|
||||||
|
Loading…
Reference in New Issue
Block a user