From 2172e4d292a8f6d50dd719d5542e3ee4e67d3fd2 Mon Sep 17 00:00:00 2001 From: Mary Hipp Date: Tue, 23 Jul 2024 12:28:31 -0400 Subject: [PATCH] board UI updates: font tweaks, add cover image to tooltip, move uncategorized out of board list, allow collapsible board list if private enabled --- invokeai/frontend/web/public/locales/en.json | 1 + .../Boards/BoardsList/BoardTooltip.tsx | 45 ++++++ .../Boards/BoardsList/BoardTotalsTooltip.tsx | 22 --- .../Boards/BoardsList/BoardsList.tsx | 147 ++++++++---------- .../Boards/BoardsList/BoardsListWrapper.tsx | 35 +++++ .../Boards/BoardsList/GalleryBoard.tsx | 26 ++-- .../Boards/BoardsList/NoBoardBoard.tsx | 42 ++--- .../gallery/components/GalleryBoardName.tsx | 4 +- .../components/ImageGalleryContent.tsx | 4 +- 9 files changed, 176 insertions(+), 150 deletions(-) create mode 100644 invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/BoardTooltip.tsx delete mode 100644 invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/BoardTotalsTooltip.tsx create mode 100644 invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/BoardsListWrapper.tsx diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json index c3bfd0c054..98ca676384 100644 --- a/invokeai/frontend/web/public/locales/en.json +++ b/invokeai/frontend/web/public/locales/en.json @@ -38,6 +38,7 @@ "movingImagesToBoard_one": "Moving {{count}} image to board:", "movingImagesToBoard_other": "Moving {{count}} images to board:", "myBoard": "My Board", + "noBoards": "No {{boardType}} Boards", "noMatching": "No matching Boards", "private": "Private Boards", "searchBoard": "Search Boards...", diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/BoardTooltip.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/BoardTooltip.tsx new file mode 100644 index 0000000000..6c90d7b049 --- /dev/null +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/BoardTooltip.tsx @@ -0,0 +1,45 @@ +import { Flex, Image, Text } from '@invoke-ai/ui-library'; +import { skipToken } from '@reduxjs/toolkit/query'; +import { useTranslation } from 'react-i18next'; +import { useGetBoardAssetsTotalQuery, useGetBoardImagesTotalQuery } from 'services/api/endpoints/boards'; +import { useGetImageDTOQuery } from 'services/api/endpoints/images'; +import type { BoardDTO } from 'services/api/types'; + +type Props = { + board: BoardDTO | null; +}; + +export const BoardTooltip = ({ board }: Props) => { + const { t } = useTranslation(); + const { imagesTotal } = useGetBoardImagesTotalQuery(board?.board_id || 'none', { + selectFromResult: ({ data }) => { + return { imagesTotal: data?.total ?? 0 }; + }, + }); + const { assetsTotal } = useGetBoardAssetsTotalQuery(board?.board_id || 'none', { + selectFromResult: ({ data }) => { + return { assetsTotal: data?.total ?? 0 }; + }, + }); + const { currentData: coverImage } = useGetImageDTOQuery(board?.cover_image_name ?? skipToken); + + return ( + + {coverImage && ( + + )} + + {t('boards.imagesWithCount', { count: imagesTotal })}, {t('boards.assetsWithCount', { count: assetsTotal })} + + {board?.archived && ({t('boards.archived')})} + + ); +}; diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/BoardTotalsTooltip.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/BoardTotalsTooltip.tsx deleted file mode 100644 index b4c89a002d..0000000000 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/BoardTotalsTooltip.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import { useTranslation } from 'react-i18next'; -import { useGetBoardAssetsTotalQuery, useGetBoardImagesTotalQuery } from 'services/api/endpoints/boards'; - -type Props = { - board_id: string; - isArchived: boolean; -}; - -export const BoardTotalsTooltip = ({ board_id, isArchived }: Props) => { - const { t } = useTranslation(); - const { imagesTotal } = useGetBoardImagesTotalQuery(board_id, { - selectFromResult: ({ data }) => { - return { imagesTotal: data?.total ?? 0 }; - }, - }); - const { assetsTotal } = useGetBoardAssetsTotalQuery(board_id, { - selectFromResult: ({ data }) => { - return { assetsTotal: data?.total ?? 0 }; - }, - }); - return `${t('boards.imagesWithCount', { count: imagesTotal })}, ${t('boards.assetsWithCount', { count: assetsTotal })}${isArchived ? ` (${t('boards.archived')})` : ''}`; -}; diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/BoardsList.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/BoardsList.tsx index 4325281e0f..6fed3e31ef 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/BoardsList.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/BoardsList.tsx @@ -1,101 +1,89 @@ -import { Box, Flex, Text } from '@invoke-ai/ui-library'; +import { Button, Collapse, Flex, Icon, Text, useDisclosure } from '@invoke-ai/ui-library'; import { EMPTY_ARRAY } from 'app/store/constants'; import { useAppSelector } from 'app/store/storeHooks'; -import { overlayScrollbarsParams } from 'common/components/OverlayScrollbars/constants'; import DeleteBoardModal from 'features/gallery/components/Boards/DeleteBoardModal'; import { selectListBoardsQueryArgs } from 'features/gallery/store/gallerySelectors'; -import { OverlayScrollbarsComponent } from 'overlayscrollbars-react'; -import type { CSSProperties } from 'react'; -import { memo, useMemo, useState } from 'react'; +import { useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; +import { PiCaretDownBold, PiCaretRightBold } from 'react-icons/pi'; import { useListAllBoardsQuery } from 'services/api/endpoints/boards'; import type { BoardDTO } from 'services/api/types'; import AddBoardButton from './AddBoardButton'; import GalleryBoard from './GalleryBoard'; -import NoBoardBoard from './NoBoardBoard'; -const overlayScrollbarsStyles: CSSProperties = { - height: '100%', - width: '100%', -}; - -const BoardsList = () => { +export const BoardsList = ({ isPrivate }: { isPrivate?: boolean }) => { + const { t } = useTranslation(); const selectedBoardId = useAppSelector((s) => s.gallery.selectedBoardId); const boardSearchText = useAppSelector((s) => s.gallery.boardSearchText); - const allowPrivateBoards = useAppSelector((s) => s.config.allowPrivateBoards); const queryArgs = useAppSelector(selectListBoardsQueryArgs); const { data: boards } = useListAllBoardsQuery(queryArgs); const [boardToDelete, setBoardToDelete] = useState(); - const { t } = useTranslation(); + const allowPrivateBoards = useAppSelector((s) => s.config.allowPrivateBoards); + const { isOpen, onToggle } = useDisclosure({ defaultIsOpen: true }); - 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]); + const filteredBoards = useMemo(() => { + if (!boards) { + return EMPTY_ARRAY; + } + + return boards.filter((board) => { + if (boardSearchText) { + return board.is_private === isPrivate && board.board_name.toLowerCase().includes(boardSearchText.toLowerCase()); + } else { + return board.is_private === !!isPrivate; + } + }); + }, [boardSearchText, boards, isPrivate]); + + const boardListTitle = useMemo(() => { + if (allowPrivateBoards) { + return isPrivate ? t('boards.private') : t('boards.shared'); + } else { + return t('boards.boards'); + } + }, [isPrivate, allowPrivateBoards, t]); return ( <> - - - - {allowPrivateBoards && ( - - - - {t('boards.private')} - - - - - - {filteredPrivateBoards.map((board) => ( - - ))} - - - )} - - - - {allowPrivateBoards ? t('boards.shared') : t('boards.boards')} + + + {allowPrivateBoards ? ( + + ) : ( + + {boardListTitle} + + )} + + + + <> + {!filteredBoards.length ? ( + + {t('boards.noBoards', { boardType: isPrivate ? 'Private' : '' })} + + ) : ( - {!allowPrivateBoards && } - {filteredSharedBoards.map((board) => ( + {filteredBoards.map((board) => ( { /> ))} - - - - + )} + + + ); }; -export default memo(BoardsList); diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/BoardsListWrapper.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/BoardsListWrapper.tsx new file mode 100644 index 0000000000..a6d0ac5521 --- /dev/null +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/BoardsListWrapper.tsx @@ -0,0 +1,35 @@ +import { Box, Spacer } from '@invoke-ai/ui-library'; +import { useAppSelector } from 'app/store/storeHooks'; +import { overlayScrollbarsParams } from 'common/components/OverlayScrollbars/constants'; +import { OverlayScrollbarsComponent } from 'overlayscrollbars-react'; +import type { CSSProperties } from 'react'; +import { memo } from 'react'; + +import { BoardsList } from './BoardsList'; +import NoBoardBoard from './NoBoardBoard'; + +const overlayScrollbarsStyles: CSSProperties = { + height: '100%', + width: '100%', +}; + +const BoardsListWrapper = () => { + const selectedBoardId = useAppSelector((s) => s.gallery.selectedBoardId); + const allowPrivateBoards = useAppSelector((s) => s.config.allowPrivateBoards); + + return ( + <> + + + + + + {allowPrivateBoards && } + + + + + + ); +}; +export default memo(BoardsListWrapper); diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/GalleryBoard.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/GalleryBoard.tsx index 8f348b5c41..6f07825cb2 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/GalleryBoard.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/GalleryBoard.tsx @@ -17,7 +17,7 @@ import IAIDroppable from 'common/components/IAIDroppable'; import type { AddToBoardDropData } from 'features/dnd/types'; import { AutoAddBadge } from 'features/gallery/components/Boards/AutoAddBadge'; import BoardContextMenu from 'features/gallery/components/Boards/BoardContextMenu'; -import { BoardTotalsTooltip } from 'features/gallery/components/Boards/BoardsList/BoardTotalsTooltip'; +import { BoardTooltip } from 'features/gallery/components/Boards/BoardsList/BoardTooltip'; import { autoAddBoardIdChanged, boardIdSelected } from 'features/gallery/store/gallerySlice'; import type { MouseEvent, MouseEventHandler, MutableRefObject } from 'react'; import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'; @@ -115,12 +115,7 @@ const GalleryBoard = ({ board, isSelected, setBoardToDelete }: GalleryBoardProps return ( {(ref) => ( - } - openDelay={1000} - placement="left" - closeOnScroll - > + } openDelay={1000} placement="left" closeOnScroll> @@ -149,17 +144,18 @@ const GalleryBoard = ({ board, isSelected, setBoardToDelete }: GalleryBoardProps onChange={onChange} onSubmit={onSubmit} isPreviewFocusable={false} + fontSize="sm" > @@ -197,8 +193,8 @@ const CoverImage = ({ board }: { board: BoardDTO }) => { src={coverImage.thumbnail_url} draggable={false} objectFit="cover" - w={8} - h={8} + w={10} + h={10} borderRadius="base" borderBottomRadius="lg" /> @@ -206,8 +202,8 @@ const CoverImage = ({ board }: { board: BoardDTO }) => { } return ( - - + + ); }; diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/NoBoardBoard.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/NoBoardBoard.tsx index 14bf3d5742..e234d1a4b1 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/NoBoardBoard.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/NoBoardBoard.tsx @@ -4,7 +4,7 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import IAIDroppable from 'common/components/IAIDroppable'; import type { RemoveFromBoardDropData } from 'features/dnd/types'; import { AutoAddBadge } from 'features/gallery/components/Boards/AutoAddBadge'; -import { BoardTotalsTooltip } from 'features/gallery/components/Boards/BoardsList/BoardTotalsTooltip'; +import { BoardTooltip } from 'features/gallery/components/Boards/BoardsList/BoardTooltip'; import NoBoardBoardContextMenu from 'features/gallery/components/Boards/NoBoardBoardContextMenu'; import { autoAddBoardIdChanged, boardIdSelected } from 'features/gallery/store/gallerySlice'; import { memo, useCallback, useMemo } from 'react'; @@ -29,7 +29,6 @@ const NoBoardBoard = memo(({ isSelected }: Props) => { }); const autoAddBoardId = useAppSelector((s) => s.gallery.autoAddBoardId); const autoAssignBoardOnClick = useAppSelector((s) => s.gallery.autoAssignBoardOnClick); - const boardSearchText = useAppSelector((s) => s.gallery.boardSearchText); const boardName = useBoardName('none'); const handleSelectBoard = useCallback(() => { dispatch(boardIdSelected({ boardId: 'none' })); @@ -46,25 +45,12 @@ const NoBoardBoard = memo(({ isSelected }: Props) => { [] ); - const filteredOut = useMemo(() => { - return boardSearchText ? !boardName.toLowerCase().includes(boardSearchText.toLowerCase()) : false; - }, [boardName, boardSearchText]); - const { t } = useTranslation(); - if (filteredOut) { - return null; - } - return ( {(ref) => ( - } - openDelay={1000} - placement="left" - closeOnScroll - > + } openDelay={1000} placement="left" closeOnScroll> { cursor="pointer" px={2} py={1} - gap={2} + gap={4} bg={isSelected ? 'base.850' : undefined} _hover={_hover} > - - {/* iconified from public/assets/images/invoke-symbol-wht-lrg.svg */} - - - - + {/* iconified from public/assets/images/invoke-symbol-wht-lrg.svg */} + + + diff --git a/invokeai/frontend/web/src/features/gallery/components/GalleryBoardName.tsx b/invokeai/frontend/web/src/features/gallery/components/GalleryBoardName.tsx index 8ede311f9e..39bea0ab62 100644 --- a/invokeai/frontend/web/src/features/gallery/components/GalleryBoardName.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/GalleryBoardName.tsx @@ -17,11 +17,11 @@ const GalleryBoardName = (props: Props) => { as="button" h="full" w="full" - borderWidth={1} + layerStyle="second" borderRadius="base" alignItems="center" justifyContent="center" - px={2} + p={1} > {boardName} diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx index 5a096f5cef..ffc65d1ff6 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx @@ -23,7 +23,7 @@ import { PiMagnifyingGlassBold } from 'react-icons/pi'; import type { ImperativePanelGroupHandle } from 'react-resizable-panels'; import { Panel, PanelGroup } from 'react-resizable-panels'; -import BoardsList from './Boards/BoardsList/BoardsList'; +import BoardsListWrapper from './Boards/BoardsList/BoardsListWrapper'; import BoardsSearch from './Boards/BoardsList/BoardsSearch'; import GallerySettingsPopover from './GallerySettingsPopover/GallerySettingsPopover'; import GalleryImageGrid from './ImageGrid/GalleryImageGrid'; @@ -118,7 +118,7 @@ const ImageGalleryContent = () => { - +