mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
feat(ui): boards styling
- Refine layout - Update colors - more minimal, fewer shaded boxes - Add indicator for search icons showing a search term is entered - Handle new `projectName` and `projectUrl` ui props
This commit is contained in:
parent
dfd94bbd0b
commit
48a57f0da8
@ -40,11 +40,11 @@ const BoardsList = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Flex layerStyle="first" flexDir="column" gap={2} p={2} my={2} borderRadius="base" maxHeight="100%">
|
<Flex flexDir="column" gap={2} borderRadius="base" maxHeight="100%">
|
||||||
<OverlayScrollbarsComponent defer style={overlayScrollbarsStyles} options={overlayScrollbarsParams.options}>
|
<OverlayScrollbarsComponent defer style={overlayScrollbarsStyles} options={overlayScrollbarsParams.options}>
|
||||||
{allowPrivateBoards && (
|
{allowPrivateBoards && (
|
||||||
<>
|
<>
|
||||||
<Flex w="full" justifyContent="space-between" alignItems="center">
|
<Flex w="full" justifyContent="space-between" alignItems="center" ps={2}>
|
||||||
<Text fontSize="md" fontWeight="medium" userSelect="none">
|
<Text fontSize="md" fontWeight="medium" userSelect="none">
|
||||||
{t('boards.private')}
|
{t('boards.private')}
|
||||||
</Text>
|
</Text>
|
||||||
@ -63,7 +63,7 @@ const BoardsList = () => {
|
|||||||
</Flex>
|
</Flex>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<Flex w="full" justifyContent="space-between" alignItems="center">
|
<Flex w="full" justifyContent="space-between" alignItems="center" ps={2}>
|
||||||
<Text fontSize="md" fontWeight="medium" userSelect="none">
|
<Text fontSize="md" fontWeight="medium" userSelect="none">
|
||||||
{allowPrivateBoards ? t('boards.shared') : t('boards.boards')}
|
{allowPrivateBoards ? t('boards.shared') : t('boards.boards')}
|
||||||
</Text>
|
</Text>
|
||||||
|
@ -40,7 +40,7 @@ const BoardsSearch = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<InputGroup>
|
<InputGroup pt={2}>
|
||||||
<Input
|
<Input
|
||||||
placeholder={t('boards.searchBoard')}
|
placeholder={t('boards.searchBoard')}
|
||||||
value={boardSearchText}
|
value={boardSearchText}
|
||||||
|
@ -35,7 +35,7 @@ const editableInputStyles: SystemStyleObject = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const _hover: SystemStyleObject = {
|
const _hover: SystemStyleObject = {
|
||||||
bg: 'base.800',
|
bg: 'base.850',
|
||||||
};
|
};
|
||||||
|
|
||||||
interface GalleryBoardProps {
|
interface GalleryBoardProps {
|
||||||
@ -118,7 +118,7 @@ const GalleryBoard = ({ board, isSelected, setBoardToDelete }: GalleryBoardProps
|
|||||||
py={1}
|
py={1}
|
||||||
px={2}
|
px={2}
|
||||||
gap={2}
|
gap={2}
|
||||||
bg={isSelected ? 'base.800' : undefined}
|
bg={isSelected ? 'base.850' : undefined}
|
||||||
_hover={_hover}
|
_hover={_hover}
|
||||||
>
|
>
|
||||||
<CoverImage board={board} />
|
<CoverImage board={board} />
|
||||||
@ -147,13 +147,7 @@ const GalleryBoard = ({ board, isSelected, setBoardToDelete }: GalleryBoardProps
|
|||||||
<EditableInput sx={editableInputStyles} />
|
<EditableInput sx={editableInputStyles} />
|
||||||
</Editable>
|
</Editable>
|
||||||
{autoAddBoardId === board.board_id && !editingDisclosure.isOpen && <AutoAddBadge />}
|
{autoAddBoardId === board.board_id && !editingDisclosure.isOpen && <AutoAddBadge />}
|
||||||
{board.archived && !editingDisclosure.isOpen && (
|
{board.archived && !editingDisclosure.isOpen && <Icon as={PiArchiveBold} fill="base.300" />}
|
||||||
<Icon
|
|
||||||
as={PiArchiveBold}
|
|
||||||
fill="base.300"
|
|
||||||
filter="drop-shadow(0px 0px 0.1rem var(--invoke-colors-base-800))"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{!editingDisclosure.isOpen && <Text variant="subtext">{board.image_count}</Text>}
|
{!editingDisclosure.isOpen && <Text variant="subtext">{board.image_count}</Text>}
|
||||||
|
|
||||||
<IAIDroppable data={droppableData} dropLabel={<Text fontSize="md">{t('unifiedCanvas.move')}</Text>} />
|
<IAIDroppable data={droppableData} dropLabel={<Text fontSize="md">{t('unifiedCanvas.move')}</Text>} />
|
||||||
|
@ -17,7 +17,7 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const _hover: SystemStyleObject = {
|
const _hover: SystemStyleObject = {
|
||||||
bg: 'base.800',
|
bg: 'base.850',
|
||||||
};
|
};
|
||||||
|
|
||||||
const NoBoardBoard = memo(({ isSelected }: Props) => {
|
const NoBoardBoard = memo(({ isSelected }: Props) => {
|
||||||
@ -71,7 +71,7 @@ const NoBoardBoard = memo(({ isSelected }: Props) => {
|
|||||||
px={2}
|
px={2}
|
||||||
py={1}
|
py={1}
|
||||||
gap={2}
|
gap={2}
|
||||||
bg={isSelected ? 'base.800' : undefined}
|
bg={isSelected ? 'base.850' : undefined}
|
||||||
_hover={_hover}
|
_hover={_hover}
|
||||||
>
|
>
|
||||||
<Flex w={8} h={8} justifyContent="center" alignItems="center">
|
<Flex w={8} h={8} justifyContent="center" alignItems="center">
|
||||||
|
@ -8,7 +8,16 @@ const GalleryBoardName = () => {
|
|||||||
const boardName = useBoardName(selectedBoardId);
|
const boardName = useBoardName(selectedBoardId);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex as="button" w="full" borderWidth={1} borderRadius="base" alignItems="center" justifyContent="center" px={2}>
|
<Flex
|
||||||
|
as="button"
|
||||||
|
h="full"
|
||||||
|
w="full"
|
||||||
|
borderWidth={1}
|
||||||
|
borderRadius="base"
|
||||||
|
alignItems="center"
|
||||||
|
justifyContent="center"
|
||||||
|
px={2}
|
||||||
|
>
|
||||||
<Text fontWeight="semibold" fontSize="md" noOfLines={1} wordBreak="break-all" color="base.200">
|
<Text fontWeight="semibold" fontSize="md" noOfLines={1} wordBreak="break-all" color="base.200">
|
||||||
{boardName}
|
{boardName}
|
||||||
</Text>
|
</Text>
|
||||||
|
@ -0,0 +1,30 @@
|
|||||||
|
import { Flex, Link, Text } from '@invoke-ai/ui-library';
|
||||||
|
import { useStore } from '@nanostores/react';
|
||||||
|
import { $projectName, $projectUrl } from 'app/store/nanostores/projectId';
|
||||||
|
import { memo } from 'react';
|
||||||
|
|
||||||
|
import GalleryBoardName from './GalleryBoardName';
|
||||||
|
|
||||||
|
const GalleryHeader = () => {
|
||||||
|
const projectName = useStore($projectName);
|
||||||
|
const projectUrl = useStore($projectUrl);
|
||||||
|
|
||||||
|
if (projectName && projectUrl) {
|
||||||
|
return (
|
||||||
|
<Flex gap={2} w="full" alignItems="center" justifyContent="space-evenly" pe={2}>
|
||||||
|
<Text fontSize="md" fontWeight="semibold" noOfLines={1} w="full" textAlign="center">
|
||||||
|
<Link href={projectUrl}>{projectName}</Link>
|
||||||
|
</Text>
|
||||||
|
<GalleryBoardName />
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Flex w="full" pe={2}>
|
||||||
|
<GalleryBoardName />
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(GalleryHeader);
|
@ -17,7 +17,7 @@ const GallerySettingsPopover = () => {
|
|||||||
return (
|
return (
|
||||||
<Popover isLazy>
|
<Popover isLazy>
|
||||||
<PopoverTrigger>
|
<PopoverTrigger>
|
||||||
<IconButton aria-label={t('gallery.gallerySettings')} size="sm" icon={<RiSettings4Fill />} />
|
<IconButton aria-label={t('gallery.gallerySettings')} icon={<RiSettings4Fill />} variant="link" h="full" />
|
||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
<PopoverContent>
|
<PopoverContent>
|
||||||
<PopoverBody>
|
<PopoverBody>
|
||||||
|
@ -1,18 +1,16 @@
|
|||||||
import type { ChakraProps } from '@invoke-ai/ui-library';
|
import type { ChakraProps } from '@invoke-ai/ui-library';
|
||||||
import { Box, Collapse, Flex, IconButton, Tab, TabList, Tabs, useDisclosure } from '@invoke-ai/ui-library';
|
import { Box, Collapse, Flex, IconButton, Spacer, Tab, TabList, Tabs, useDisclosure } from '@invoke-ai/ui-library';
|
||||||
import { useStore } from '@nanostores/react';
|
|
||||||
import { $galleryHeader } from 'app/store/nanostores/galleryHeader';
|
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
|
import GalleryHeader from 'features/gallery/components/GalleryHeader';
|
||||||
import { galleryViewChanged } from 'features/gallery/store/gallerySlice';
|
import { galleryViewChanged } from 'features/gallery/store/gallerySlice';
|
||||||
import ResizeHandle from 'features/ui/components/tabs/ResizeHandle';
|
import ResizeHandle from 'features/ui/components/tabs/ResizeHandle';
|
||||||
import { memo, useCallback, useRef } from 'react';
|
import { memo, useCallback, useRef } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { PiImagesBold, PiMagnifyingGlassBold } from 'react-icons/pi';
|
import { PiMagnifyingGlassBold } from 'react-icons/pi';
|
||||||
import { Panel, PanelGroup } from 'react-resizable-panels';
|
import { Panel, PanelGroup } from 'react-resizable-panels';
|
||||||
|
|
||||||
import BoardsList from './Boards/BoardsList/BoardsList';
|
import BoardsList from './Boards/BoardsList/BoardsList';
|
||||||
import BoardsSearch from './Boards/BoardsList/BoardsSearch';
|
import BoardsSearch from './Boards/BoardsList/BoardsSearch';
|
||||||
import GalleryBoardName from './GalleryBoardName';
|
|
||||||
import GallerySettingsPopover from './GallerySettingsPopover/GallerySettingsPopover';
|
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';
|
||||||
@ -20,29 +18,22 @@ import { GallerySearch } from './ImageGrid/GallerySearch';
|
|||||||
|
|
||||||
const baseStyles: ChakraProps['sx'] = {
|
const baseStyles: ChakraProps['sx'] = {
|
||||||
fontWeight: 'semibold',
|
fontWeight: 'semibold',
|
||||||
fontSize: 'md',
|
fontSize: 'sm',
|
||||||
color: 'base.300',
|
color: 'base.300',
|
||||||
borderBottom: '1px solid',
|
|
||||||
borderBottomColor: 'invokeBlue.800',
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const selectedStyles: ChakraProps['sx'] = {
|
const selectedStyles: ChakraProps['sx'] = {
|
||||||
borderColor: 'invokeBlue.800',
|
borderColor: 'base.800',
|
||||||
borderBottomColor: 'base.850',
|
borderBottomColor: 'base.900',
|
||||||
color: 'invokeBlue.300',
|
color: 'invokeBlue.300',
|
||||||
};
|
};
|
||||||
|
|
||||||
const searchIconStyles: ChakraProps['sx'] = {
|
|
||||||
borderBottom: '1px solid',
|
|
||||||
borderBottomColor: 'invokeBlue.800',
|
|
||||||
maxW: '16',
|
|
||||||
};
|
|
||||||
|
|
||||||
const ImageGalleryContent = () => {
|
const ImageGalleryContent = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const galleryView = useAppSelector((s) => s.gallery.galleryView);
|
const galleryView = useAppSelector((s) => s.gallery.galleryView);
|
||||||
|
const searchTerm = useAppSelector((s) => s.gallery.searchTerm);
|
||||||
|
const boardSearchText = useAppSelector((s) => s.gallery.boardSearchText);
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const galleryHeader = useStore($galleryHeader);
|
|
||||||
const searchDisclosure = useDisclosure({ defaultIsOpen: false });
|
const searchDisclosure = useDisclosure({ defaultIsOpen: false });
|
||||||
const boardSearchDisclosure = useDisclosure({ defaultIsOpen: false });
|
const boardSearchDisclosure = useDisclosure({ defaultIsOpen: false });
|
||||||
|
|
||||||
@ -57,62 +48,81 @@ const ImageGalleryContent = () => {
|
|||||||
const panelGroupRef = useRef(null);
|
const panelGroupRef = useRef(null);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex layerStyle="first" position="relative" flexDirection="column" h="full" w="full" p={2} gap={2}>
|
<Flex position="relative" flexDirection="column" h="full" w="full" pt={2}>
|
||||||
<Flex alignItems="center" justifyContent="space-between" gap={2}>
|
<Flex alignItems="center" gap={2}>
|
||||||
{galleryHeader}
|
<GalleryHeader />
|
||||||
<GalleryBoardName />
|
|
||||||
<GallerySettingsPopover />
|
<GallerySettingsPopover />
|
||||||
|
<Box position="relative" h="full">
|
||||||
<IconButton
|
<IconButton
|
||||||
|
w="full"
|
||||||
|
h="full"
|
||||||
onClick={boardSearchDisclosure.onToggle}
|
onClick={boardSearchDisclosure.onToggle}
|
||||||
tooltip={`${t('gallery.displayBoardSearch')}`}
|
tooltip={`${t('gallery.displayBoardSearch')}`}
|
||||||
aria-label={t('gallery.displayBoardSearch')}
|
aria-label={t('gallery.displayBoardSearch')}
|
||||||
icon={<PiMagnifyingGlassBold />}
|
icon={<PiMagnifyingGlassBold />}
|
||||||
size="sm"
|
variant="link"
|
||||||
/>
|
/>
|
||||||
|
{boardSearchText && (
|
||||||
|
<Box
|
||||||
|
position="absolute"
|
||||||
|
w={2}
|
||||||
|
h={2}
|
||||||
|
bg="invokeBlue.300"
|
||||||
|
borderRadius="full"
|
||||||
|
insetBlockStart={2}
|
||||||
|
insetInlineEnd={2}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
</Flex>
|
</Flex>
|
||||||
<PanelGroup ref={panelGroupRef} direction="vertical">
|
<PanelGroup ref={panelGroupRef} direction="vertical">
|
||||||
<Panel>
|
<Panel>
|
||||||
<Collapse in={boardSearchDisclosure.isOpen}>
|
<Collapse in={boardSearchDisclosure.isOpen}>
|
||||||
<Box mt="2">
|
|
||||||
<BoardsSearch />
|
<BoardsSearch />
|
||||||
</Box>
|
|
||||||
</Collapse>
|
</Collapse>
|
||||||
<BoardsList />
|
<BoardsList />
|
||||||
</Panel>
|
</Panel>
|
||||||
<ResizeHandle orientation="horizontal" />
|
<ResizeHandle orientation="horizontal" />
|
||||||
<Panel>
|
<Panel>
|
||||||
<Flex flexDirection="column" alignItems="center" justifyContent="space-between" gap={2} h="full">
|
<Flex flexDirection="column" alignItems="center" justifyContent="space-between" h="full" w="full">
|
||||||
<Tabs isFitted index={galleryView === 'images' ? 0 : 1} variant="enclosed" size="sm" w="full" my="2">
|
<Tabs index={galleryView === 'images' ? 0 : 1} variant="enclosed" display="flex" flexDir="column" w="full">
|
||||||
<TabList fontSize="sm" borderBottom="none">
|
<TabList gap={2} fontSize="sm" borderColor="base.800">
|
||||||
<Tab sx={baseStyles} _selected={selectedStyles} onClick={handleClickImages} data-testid="images-tab">
|
<Tab sx={baseStyles} _selected={selectedStyles} onClick={handleClickImages} data-testid="images-tab">
|
||||||
<Flex alignItems="center" justifyContent="center" gap="2" w="full">
|
|
||||||
<PiImagesBold size="16px" />
|
|
||||||
{t('parameters.images')}
|
{t('parameters.images')}
|
||||||
</Flex>
|
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab sx={baseStyles} _selected={selectedStyles} onClick={handleClickAssets} data-testid="assets-tab">
|
<Tab sx={baseStyles} _selected={selectedStyles} onClick={handleClickAssets} data-testid="assets-tab">
|
||||||
<Flex alignItems="center" justifyContent="center" gap="2" w="full">
|
|
||||||
<PiImagesBold size="16px" />
|
|
||||||
{t('gallery.assets')}
|
{t('gallery.assets')}
|
||||||
</Flex>
|
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab sx={searchIconStyles} onClick={searchDisclosure.onToggle}>
|
<Spacer />
|
||||||
|
<Box position="relative">
|
||||||
<IconButton
|
<IconButton
|
||||||
|
w="full"
|
||||||
|
h="full"
|
||||||
|
onClick={searchDisclosure.onToggle}
|
||||||
tooltip={`${t('gallery.displaySearch')}`}
|
tooltip={`${t('gallery.displaySearch')}`}
|
||||||
aria-label={t('gallery.displaySearch')}
|
aria-label={t('gallery.displaySearch')}
|
||||||
icon={<PiMagnifyingGlassBold />}
|
icon={<PiMagnifyingGlassBold />}
|
||||||
fontSize={16}
|
variant="link"
|
||||||
textAlign="center"
|
|
||||||
color="base.400"
|
|
||||||
variant="unstyled"
|
|
||||||
minW="unset"
|
|
||||||
/>
|
/>
|
||||||
</Tab>
|
{searchTerm && (
|
||||||
|
<Box
|
||||||
|
position="absolute"
|
||||||
|
w={2}
|
||||||
|
h={2}
|
||||||
|
bg="invokeBlue.300"
|
||||||
|
borderRadius="full"
|
||||||
|
insetBlockStart={2}
|
||||||
|
insetInlineEnd={2}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
</TabList>
|
</TabList>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
<Box mb="2" w="full">
|
<Box w="full">
|
||||||
<Collapse in={searchDisclosure.isOpen}>
|
<Collapse in={searchDisclosure.isOpen}>
|
||||||
|
<Box w="full" pt={2}>
|
||||||
<GallerySearch />
|
<GallerySearch />
|
||||||
|
</Box>
|
||||||
</Collapse>
|
</Collapse>
|
||||||
</Box>
|
</Box>
|
||||||
<GalleryImageGrid />
|
<GalleryImageGrid />
|
||||||
|
@ -124,7 +124,7 @@ const Content = () => {
|
|||||||
}, [calculateNewLimit, container, dispatch]);
|
}, [calculateNewLimit, container, dispatch]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box position="relative" w="full" h="full">
|
<Box position="relative" w="full" h="full" mt={2}>
|
||||||
<Box
|
<Box
|
||||||
ref={containerRef}
|
ref={containerRef}
|
||||||
position="absolute"
|
position="absolute"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user