diff --git a/invokeai/app/services/boards/boards_common.py b/invokeai/app/services/boards/boards_common.py index 0cb54102bb..3f3190a06a 100644 --- a/invokeai/app/services/boards/boards_common.py +++ b/invokeai/app/services/boards/boards_common.py @@ -12,6 +12,8 @@ class BoardDTO(BoardRecord): """The URL of the thumbnail of the most recent image in the board.""" image_count: int = Field(description="The number of images in the board.") """The number of images in the board.""" + is_private: Optional[bool] = Field(description="Whether the board is private.") + """Whether the board is private.""" def board_record_to_dto(board_record: BoardRecord, cover_image_name: Optional[str], image_count: int) -> BoardDTO: diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json index 289159eaf0..32c527d883 100644 --- a/invokeai/frontend/web/public/locales/en.json +++ b/invokeai/frontend/web/public/locales/en.json @@ -20,6 +20,7 @@ "archiveBoard": "Archive Board", "archived": "Archived", "autoAddBoard": "Auto-Add Board", + "boards": "Boards", "bottomMessage": "Deleting this board and its images will reset any features currently using them.", "cancel": "Cancel", "changeBoard": "Change Board", @@ -35,8 +36,10 @@ "movingImagesToBoard_other": "Moving {{count}} images to board:", "myBoard": "My Board", "noMatching": "No matching Boards", + "private": "Private", "searchBoard": "Search Boards...", "selectBoard": "Select a Board", + "shared": "Shared", "topMessage": "This board contains images used in the following features:", "unarchiveBoard": "Unarchive Board", "uncategorized": "Uncategorized", diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/AddBoardButton.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/AddBoardButton.tsx index 5cd4d001f4..abf690f693 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/AddBoardButton.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/AddBoardButton.tsx @@ -4,13 +4,17 @@ import { useTranslation } from 'react-i18next'; import { PiPlusBold } from 'react-icons/pi'; import { useCreateBoardMutation } from 'services/api/endpoints/boards'; -const AddBoardButton = () => { +type Props = { + privateBoard: boolean; +}; + +const AddBoardButton = ({ privateBoard }: Props) => { const { t } = useTranslation(); const [createBoard, { isLoading }] = useCreateBoardMutation(); const DEFAULT_BOARD_NAME = t('boards.myBoard'); const handleCreateBoard = useCallback(() => { - createBoard(DEFAULT_BOARD_NAME); - }, [createBoard, DEFAULT_BOARD_NAME]); + createBoard({ board_name: DEFAULT_BOARD_NAME, private_board: privateBoard }); + }, [createBoard, DEFAULT_BOARD_NAME, privateBoard]); return ( { tooltip={t('boards.addBoard')} aria-label={t('boards.addBoard')} onClick={handleCreateBoard} - size="sm" + size="md" data-testid="add-board-button" + variant="ghost" /> ); }; 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 e47edd21fc..99b2becd61 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,11 +1,14 @@ -import { Collapse, Flex, Grid, GridItem } from '@invoke-ai/ui-library'; +import { Box, Collapse, Flex, Icon, Text } from '@invoke-ai/ui-library'; import { useAppSelector } from 'app/store/storeHooks'; import { overlayScrollbarsParams } from 'common/components/OverlayScrollbars/constants'; import DeleteBoardModal from 'features/gallery/components/Boards/DeleteBoardModal'; +import GallerySettingsPopover from 'features/gallery/components/GallerySettingsPopover/GallerySettingsPopover'; import { selectListBoardsQueryArgs } from 'features/gallery/store/gallerySelectors'; import { OverlayScrollbarsComponent } from 'overlayscrollbars-react'; import type { CSSProperties } from 'react'; -import { memo, useState } from 'react'; +import { memo, useCallback, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { PiCaretUpBold } from 'react-icons/pi'; import { useListAllBoardsQuery } from 'services/api/endpoints/boards'; import type { BoardDTO } from 'services/api/types'; @@ -19,56 +22,109 @@ const overlayScrollbarsStyles: CSSProperties = { width: '100%', }; -type Props = { - isOpen: boolean; -}; - -const BoardsList = (props: Props) => { - const { isOpen } = props; +const BoardsListWithPrivate = () => { const selectedBoardId = useAppSelector((s) => s.gallery.selectedBoardId); const boardSearchText = useAppSelector((s) => s.gallery.boardSearchText); + const allowPrivateBoards = useAppSelector((s) => s.config.allowPrivateBoards); const queryArgs = useAppSelector(selectListBoardsQueryArgs); const { data: boards } = useListAllBoardsQuery(queryArgs); const filteredBoards = boardSearchText ? boards?.filter((board) => board.board_name.toLowerCase().includes(boardSearchText.toLowerCase())) : boards; + const filteredPrivateBoards = filteredBoards?.filter((board) => board.is_private); + const filteredSharedBoards = boards?.filter((board) => !board.is_private); const [boardToDelete, setBoardToDelete] = useState(); + const [isPrivateBoardsOpen, setIsPrivateBoardsOpen] = useState(true); + const [isSharedBoardsOpen, setIsSharedBoardsOpen] = useState(true); + const { t } = useTranslation(); + + const handlePrivateBoardsToggle = useCallback( + () => setIsPrivateBoardsOpen(!isPrivateBoardsOpen), + [isPrivateBoardsOpen, setIsPrivateBoardsOpen] + ); + const handleSharedBoardsToggle = useCallback( + () => setIsSharedBoardsOpen(!isSharedBoardsOpen), + [isSharedBoardsOpen, setIsSharedBoardsOpen] + ); return ( <> - - - - - - - - - - - - {filteredBoards && - filteredBoards.map((board, index) => ( - + + + + + + + + {allowPrivateBoards && ( + <> + + + + + {t('boards.private')} + + + + + + + + {filteredPrivateBoards && + filteredPrivateBoards.map((board) => ( + + ))} + + + + )} + + + + + {allowPrivateBoards ? t('boards.shared') : t('boards.boards')} + + + + + + + {filteredSharedBoards && + filteredSharedBoards.map((board) => ( - - ))} - - - - + ))} + + + + + ); }; -export default memo(BoardsList); +export default memo(BoardsListWithPrivate); 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 fa5ed15930..7957633aa1 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 @@ -1,15 +1,13 @@ import type { SystemStyleObject } from '@invoke-ai/ui-library'; import { Box, Editable, EditableInput, EditablePreview, Flex, Icon, Image, Text, Tooltip } from '@invoke-ai/ui-library'; -import { createSelector } from '@reduxjs/toolkit'; import { skipToken } from '@reduxjs/toolkit/query'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import IAIDroppable from 'common/components/IAIDroppable'; import SelectionOverlay from 'common/components/SelectionOverlay'; import type { AddToBoardDropData } from 'features/dnd/types'; -import AutoAddIcon from 'features/gallery/components/Boards/AutoAddIcon'; import BoardContextMenu from 'features/gallery/components/Boards/BoardContextMenu'; import { BoardTotalsTooltip } from 'features/gallery/components/Boards/BoardsList/BoardTotalsTooltip'; -import { autoAddBoardIdChanged, boardIdSelected, selectGallerySlice } from 'features/gallery/store/gallerySlice'; +import { autoAddBoardIdChanged, boardIdSelected } from 'features/gallery/store/gallerySlice'; import { memo, useCallback, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { PiArchiveBold, PiImagesSquare } from 'react-icons/pi'; @@ -19,10 +17,8 @@ import type { BoardDTO } from 'services/api/types'; const editableInputStyles: SystemStyleObject = { p: 0, - _focusVisible: { - p: 0, - textAlign: 'center', - }, + fontSize: 'md', + w: '100%', }; const ArchivedIcon = () => { @@ -43,12 +39,7 @@ const GalleryBoard = ({ board, isSelected, setBoardToDelete }: GalleryBoardProps const dispatch = useAppDispatch(); const { t } = useTranslation(); const autoAssignBoardOnClick = useAppSelector((s) => s.gallery.autoAssignBoardOnClick); - const selectIsSelectedForAutoAdd = useMemo( - () => createSelector(selectGallerySlice, (gallery) => board.board_id === gallery.autoAddBoardId), - [board.board_id] - ); - const isSelectedForAutoAdd = useAppSelector(selectIsSelectedForAutoAdd); const [isHovered, setIsHovered] = useState(false); const handleMouseOver = useCallback(() => { setIsHovered(true); @@ -114,16 +105,16 @@ const GalleryBoard = ({ board, isSelected, setBoardToDelete }: GalleryBoardProps }, []); return ( - + {(ref) => ( @@ -135,68 +126,62 @@ const GalleryBoard = ({ board, isSelected, setBoardToDelete }: GalleryBoardProps ref={ref} onClick={handleSelectBoard} w="full" - h="full" - position="relative" - justifyContent="center" alignItems="center" + justifyContent="space-between" borderRadius="base" cursor="pointer" - bg="base.800" + gap="6" + p="1" > - {board.archived && } - {coverImage?.thumbnail_url ? ( - - ) : ( - - - - )} - {isSelectedForAutoAdd && } - - - - + {board.archived && } + {coverImage?.thumbnail_url ? ( + - - + ) : ( + + + + )} + + + + + + + + + + {`${t('boards.imagesWithCount', { count: board.image_count })}`} + {t('unifiedCanvas.move')}} /> 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 3f60dabf70..77563859a7 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 @@ -3,7 +3,6 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import IAIDroppable from 'common/components/IAIDroppable'; import SelectionOverlay from 'common/components/SelectionOverlay'; import type { RemoveFromBoardDropData } from 'features/dnd/types'; -import AutoAddIcon from 'features/gallery/components/Boards/AutoAddIcon'; import { BoardTotalsTooltip } from 'features/gallery/components/Boards/BoardsList/BoardTotalsTooltip'; import NoBoardBoardContextMenu from 'features/gallery/components/Boards/NoBoardBoardContextMenu'; import { autoAddBoardIdChanged, boardIdSelected } from 'features/gallery/store/gallerySlice'; @@ -18,7 +17,6 @@ interface Props { const NoBoardBoard = memo(({ isSelected }: Props) => { const dispatch = useAppDispatch(); - const autoAddBoardId = useAppSelector((s) => s.gallery.autoAddBoardId); const autoAssignBoardOnClick = useAppSelector((s) => s.gallery.autoAssignBoardOnClick); const boardName = useBoardName('none'); const handleSelectBoard = useCallback(() => { @@ -46,16 +44,16 @@ const NoBoardBoard = memo(({ isSelected }: Props) => { ); const { t } = useTranslation(); return ( - + {(ref) => ( @@ -64,30 +62,22 @@ const NoBoardBoard = memo(({ isSelected }: Props) => { ref={ref} onClick={handleSelectBoard} w="full" - position="relative" alignItems="center" borderRadius="base" cursor="pointer" - bg="base.800" + gap="6" + p="1" > invoke-ai-logo - {autoAddBoardId === 'none' && } - + {boardName} diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsListWithPrivate/AddBoardButton.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsListWithPrivate/AddBoardButton.tsx deleted file mode 100644 index 67e133ba22..0000000000 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsListWithPrivate/AddBoardButton.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import { Icon } from '@invoke-ai/ui-library'; -import { memo, useCallback } from 'react'; -import { useTranslation } from 'react-i18next'; -import { PiPlusBold } from 'react-icons/pi'; -import { useCreateBoardMutation } from 'services/api/endpoints/boards'; - -type Props = { - privateBoard: boolean; -}; - -const AddBoardButton = ({ privateBoard }: Props) => { - const { t } = useTranslation(); - const [createBoard] = useCreateBoardMutation(); - const DEFAULT_BOARD_NAME = t('boards.myBoard'); - const handleCreateBoard = useCallback(() => { - createBoard({ DEFAULT_BOARD_NAME, privateBoard }); - }, [createBoard, DEFAULT_BOARD_NAME, privateBoard]); - - return ( - - ); -}; - -export default memo(AddBoardButton); diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsListWithPrivate/BoardTotalsTooltip.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsListWithPrivate/BoardTotalsTooltip.tsx deleted file mode 100644 index b4c89a002d..0000000000 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsListWithPrivate/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/BoardsListWithPrivate/BoardsListWithPrivate.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsListWithPrivate/BoardsListWithPrivate.tsx deleted file mode 100644 index b87a96f272..0000000000 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsListWithPrivate/BoardsListWithPrivate.tsx +++ /dev/null @@ -1,120 +0,0 @@ -import { Collapse, Flex, Icon, Text } from '@invoke-ai/ui-library'; -import { useAppSelector } from 'app/store/storeHooks'; -import { overlayScrollbarsParams } from 'common/components/OverlayScrollbars/constants'; -import DeleteBoardModal from 'features/gallery/components/Boards/DeleteBoardModal'; -import GallerySettingsPopover from 'features/gallery/components/GallerySettingsPopover/GallerySettingsPopover'; -import { selectListBoardsQueryArgs } from 'features/gallery/store/gallerySelectors'; -import { OverlayScrollbarsComponent } from 'overlayscrollbars-react'; -import type { CSSProperties } from 'react'; -import { memo, useState } from 'react'; -import { PiCaretUpBold, PiPlusBold } from 'react-icons/pi'; -import { useListAllBoardsQuery } from 'services/api/endpoints/boards'; -import type { BoardDTO } from 'services/api/types'; - -import AddBoardButton from './AddBoardButton'; -import BoardsSearch from './BoardsSearch'; -import GalleryBoard from './GalleryBoard'; -import NoBoardBoard from './NoBoardBoard'; - -const overlayScrollbarsStyles: CSSProperties = { - height: '100%', - width: '100%', -}; - -const BoardsListWithPrivate = () => { - const selectedBoardId = useAppSelector((s) => s.gallery.selectedBoardId); - const boardSearchText = useAppSelector((s) => s.gallery.boardSearchText); - const queryArgs = useAppSelector(selectListBoardsQueryArgs); - const { data: boards } = useListAllBoardsQuery(queryArgs); - const filteredPrivateBoards = boardSearchText - ? boards?.filter((board) => board.is_private && board.board_name.toLowerCase().includes(boardSearchText.toLowerCase())) - : boards?.filter((board) => board.is_private); - const filteredSharedBoards = boardSearchText - ? boards?.filter((board) => !board.is_private && board.board_name.toLowerCase().includes(boardSearchText.toLowerCase())) - : boards?.filter((board) => !board.is_private); - const [boardToDelete, setBoardToDelete] = useState(); - const [isPrivateBoardsOpen, setIsPrivateBoardsOpen] = useState(true); - const [isSharedBoardsOpen, setIsSharedBoardsOpen] = useState(true); - - return ( - <> - - - - - - - - setIsPrivateBoardsOpen(!isPrivateBoardsOpen)} - gap={2} - alignItems="center" - cursor="pointer" - > - - Private - - - - - - - {filteredPrivateBoards && - filteredPrivateBoards.map((board) => ( - - ))} - - - - setIsSharedBoardsOpen(!isSharedBoardsOpen)} - gap={2} - alignItems="center" - cursor="pointer" - > - - - Shared - - - - - - {filteredSharedBoards && - filteredSharedBoards.map((board) => ( - - ))} - - - - - - - ); -}; - -export default memo(BoardsListWithPrivate); diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsListWithPrivate/BoardsSearch.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsListWithPrivate/BoardsSearch.tsx deleted file mode 100644 index 931c1e6cbb..0000000000 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsListWithPrivate/BoardsSearch.tsx +++ /dev/null @@ -1,66 +0,0 @@ -import { IconButton, Input, InputGroup, InputRightElement } from '@invoke-ai/ui-library'; -import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import { boardSearchTextChanged } from 'features/gallery/store/gallerySlice'; -import type { ChangeEvent, KeyboardEvent } from 'react'; -import { memo, useCallback } from 'react'; -import { useTranslation } from 'react-i18next'; -import { PiXBold } from 'react-icons/pi'; - -const BoardsSearch = () => { - const dispatch = useAppDispatch(); - const boardSearchText = useAppSelector((s) => s.gallery.boardSearchText); - const { t } = useTranslation(); - - const handleBoardSearch = useCallback( - (searchTerm: string) => { - dispatch(boardSearchTextChanged(searchTerm)); - }, - [dispatch] - ); - - const clearBoardSearch = useCallback(() => { - dispatch(boardSearchTextChanged('')); - }, [dispatch]); - - const handleKeydown = useCallback( - (e: KeyboardEvent) => { - // exit search mode on escape - if (e.key === 'Escape') { - clearBoardSearch(); - } - }, - [clearBoardSearch] - ); - - const handleChange = useCallback( - (e: ChangeEvent) => { - handleBoardSearch(e.target.value); - }, - [handleBoardSearch] - ); - - return ( - - - {boardSearchText && boardSearchText.length && ( - - } - /> - - )} - - ); -}; - -export default memo(BoardsSearch); diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsListWithPrivate/GalleryBoard.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsListWithPrivate/GalleryBoard.tsx deleted file mode 100644 index 7eaae8ee62..0000000000 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsListWithPrivate/GalleryBoard.tsx +++ /dev/null @@ -1,194 +0,0 @@ -import type { SystemStyleObject } from '@invoke-ai/ui-library'; -import { Box, Editable, EditableInput, EditablePreview, Flex, Icon, Image, Text, Tooltip } from '@invoke-ai/ui-library'; -import { skipToken } from '@reduxjs/toolkit/query'; -import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import IAIDroppable from 'common/components/IAIDroppable'; -import SelectionOverlay from 'common/components/SelectionOverlay'; -import type { AddToBoardDropData } from 'features/dnd/types'; -import BoardContextMenu from 'features/gallery/components/Boards/BoardContextMenu'; -import { BoardTotalsTooltip } from 'features/gallery/components/Boards/BoardsList/BoardTotalsTooltip'; -import { autoAddBoardIdChanged, boardIdSelected } from 'features/gallery/store/gallerySlice'; -import { memo, useCallback, useMemo, useState } from 'react'; -import { useTranslation } from 'react-i18next'; -import { PiArchiveBold, PiImagesSquare } from 'react-icons/pi'; -import { useUpdateBoardMutation } from 'services/api/endpoints/boards'; -import { useGetImageDTOQuery } from 'services/api/endpoints/images'; -import type { BoardDTO } from 'services/api/types'; - -const editableInputStyles: SystemStyleObject = { - p: 0, - fontSize: 'md', - w: '100%', -}; - -const ArchivedIcon = () => { - return ( - - - - ); -}; - -interface GalleryBoardProps { - board: BoardDTO; - isSelected: boolean; - setBoardToDelete: (board?: BoardDTO) => void; -} - -const GalleryBoard = ({ board, isSelected, setBoardToDelete }: GalleryBoardProps) => { - const dispatch = useAppDispatch(); - const { t } = useTranslation(); - const autoAssignBoardOnClick = useAppSelector((s) => s.gallery.autoAssignBoardOnClick); - - const [isHovered, setIsHovered] = useState(false); - const handleMouseOver = useCallback(() => { - setIsHovered(true); - }, []); - const handleMouseOut = useCallback(() => { - setIsHovered(false); - }, []); - - const { currentData: coverImage } = useGetImageDTOQuery(board.cover_image_name ?? skipToken); - - const { board_name, board_id } = board; - const [localBoardName, setLocalBoardName] = useState(board_name); - - const handleSelectBoard = useCallback(() => { - dispatch(boardIdSelected({ boardId: board_id })); - if (autoAssignBoardOnClick && !board.archived) { - dispatch(autoAddBoardIdChanged(board_id)); - } - }, [board_id, autoAssignBoardOnClick, dispatch, board.archived]); - - const [updateBoard, { isLoading: isUpdateBoardLoading }] = useUpdateBoardMutation(); - - const droppableData: AddToBoardDropData = useMemo( - () => ({ - id: board_id, - actionType: 'ADD_TO_BOARD', - context: { boardId: board_id }, - }), - [board_id] - ); - - const handleSubmit = useCallback( - async (newBoardName: string) => { - // empty strings are not allowed - if (!newBoardName.trim()) { - setLocalBoardName(board_name); - return; - } - - // don't updated the board name if it hasn't changed - if (newBoardName === board_name) { - return; - } - - try { - const { board_name } = await updateBoard({ - board_id, - changes: { board_name: newBoardName }, - }).unwrap(); - - // update local state - setLocalBoardName(board_name); - } catch { - // revert on error - setLocalBoardName(board_name); - } - }, - [board_id, board_name, updateBoard] - ); - - const handleChange = useCallback((newBoardName: string) => { - setLocalBoardName(newBoardName); - }, []); - - return ( - - - - {(ref) => ( - } - openDelay={1000} - > - - - {board.archived && } - {coverImage?.thumbnail_url ? ( - - ) : ( - - - - )} - - - - - - - - - - {board.image_count} images - - {t('unifiedCanvas.move')}} /> - - - )} - - - - ); -}; - -export default memo(GalleryBoard); diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsListWithPrivate/NoBoardBoard.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsListWithPrivate/NoBoardBoard.tsx deleted file mode 100644 index 77563859a7..0000000000 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsListWithPrivate/NoBoardBoard.tsx +++ /dev/null @@ -1,96 +0,0 @@ -import { Box, Flex, Image, Text, Tooltip } from '@invoke-ai/ui-library'; -import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import IAIDroppable from 'common/components/IAIDroppable'; -import SelectionOverlay from 'common/components/SelectionOverlay'; -import type { RemoveFromBoardDropData } from 'features/dnd/types'; -import { BoardTotalsTooltip } from 'features/gallery/components/Boards/BoardsList/BoardTotalsTooltip'; -import NoBoardBoardContextMenu from 'features/gallery/components/Boards/NoBoardBoardContextMenu'; -import { autoAddBoardIdChanged, boardIdSelected } from 'features/gallery/store/gallerySlice'; -import InvokeLogoSVG from 'public/assets/images/invoke-symbol-wht-lrg.svg'; -import { memo, useCallback, useMemo, useState } from 'react'; -import { useTranslation } from 'react-i18next'; -import { useBoardName } from 'services/api/hooks/useBoardName'; - -interface Props { - isSelected: boolean; -} - -const NoBoardBoard = memo(({ isSelected }: Props) => { - const dispatch = useAppDispatch(); - const autoAssignBoardOnClick = useAppSelector((s) => s.gallery.autoAssignBoardOnClick); - const boardName = useBoardName('none'); - const handleSelectBoard = useCallback(() => { - dispatch(boardIdSelected({ boardId: 'none' })); - if (autoAssignBoardOnClick) { - dispatch(autoAddBoardIdChanged('none')); - } - }, [dispatch, autoAssignBoardOnClick]); - const [isHovered, setIsHovered] = useState(false); - - const handleMouseOver = useCallback(() => { - setIsHovered(true); - }, []); - - const handleMouseOut = useCallback(() => { - setIsHovered(false); - }, []); - - const droppableData: RemoveFromBoardDropData = useMemo( - () => ({ - id: 'no_board', - actionType: 'REMOVE_FROM_BOARD', - }), - [] - ); - const { t } = useTranslation(); - return ( - - - - {(ref) => ( - } openDelay={1000}> - - invoke-ai-logo - - {boardName} - - - {t('unifiedCanvas.move')}} /> - - - )} - - - - ); -}); - -NoBoardBoard.displayName = 'HoverableBoard'; - -export default memo(NoBoardBoard); diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx index e21e7508fd..b0b147b510 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx @@ -9,7 +9,6 @@ import { PiImagesBold } from 'react-icons/pi'; import { RiServerLine } from 'react-icons/ri'; import BoardsList from './Boards/BoardsList/BoardsList'; -import BoardsListWithPrivate from './Boards/BoardsListWithPrivate/BoardsListWithPrivate'; import GalleryBoardName from './GalleryBoardName'; import GallerySettingsPopover from './GallerySettingsPopover/GallerySettingsPopover'; import GalleryImageGrid from './ImageGrid/GalleryImageGrid'; @@ -19,7 +18,6 @@ import { GallerySearch } from './ImageGrid/GallerySearch'; const ImageGalleryContent = () => { const { t } = useTranslation(); const galleryView = useAppSelector((s) => s.gallery.galleryView); - const allowPrivateBoards = useAppSelector((s) => s.config.allowPrivateBoards); const dispatch = useAppDispatch(); const galleryHeader = useStore($galleryHeader); const { isOpen: isBoardListOpen, onToggle: onToggleBoardList } = useDisclosure({ defaultIsOpen: true }); @@ -44,21 +42,15 @@ const ImageGalleryContent = () => { gap={2} > {galleryHeader} - {true ? ( + + + + + - - - ) : ( - - - - - - - - + - )} + diff --git a/invokeai/frontend/web/src/services/api/schema.ts b/invokeai/frontend/web/src/services/api/schema.ts index f64fba6e77..4318db2df4 100644 --- a/invokeai/frontend/web/src/services/api/schema.ts +++ b/invokeai/frontend/web/src/services/api/schema.ts @@ -7293,145 +7293,145 @@ export type components = { project_id: string | null; }; InvocationOutputMap: { - save_image: components["schemas"]["ImageOutput"]; - integer_math: components["schemas"]["IntegerOutput"]; - segment_anything_processor: components["schemas"]["ImageOutput"]; - sdxl_refiner_compel_prompt: components["schemas"]["ConditioningOutput"]; - zoe_depth_image_processor: components["schemas"]["ImageOutput"]; - collect: components["schemas"]["CollectInvocationOutput"]; - range: components["schemas"]["IntegerCollectionOutput"]; - unsharp_mask: components["schemas"]["ImageOutput"]; - string_replace: components["schemas"]["StringOutput"]; - face_identifier: components["schemas"]["ImageOutput"]; - heuristic_resize: components["schemas"]["ImageOutput"]; range_of_size: components["schemas"]["IntegerCollectionOutput"]; - latents_collection: components["schemas"]["LatentsCollectionOutput"]; - color_map_image_processor: components["schemas"]["ImageOutput"]; - img_ilerp: components["schemas"]["ImageOutput"]; - infill_patchmatch: components["schemas"]["ImageOutput"]; - face_off: components["schemas"]["FaceOffOutput"]; - string_collection: components["schemas"]["StringCollectionOutput"]; - sdxl_model_loader: components["schemas"]["SDXLModelLoaderOutput"]; - sdxl_lora_loader: components["schemas"]["SDXLLoRALoaderOutput"]; - string_join: components["schemas"]["StringOutput"]; - lblend: components["schemas"]["LatentsOutput"]; - conditioning_collection: components["schemas"]["ConditioningCollectionOutput"]; - string_split_neg: components["schemas"]["StringPosNegOutput"]; - img_watermark: components["schemas"]["ImageOutput"]; - infill_lama: components["schemas"]["ImageOutput"]; - div: components["schemas"]["IntegerOutput"]; - show_image: components["schemas"]["ImageOutput"]; - tile_to_properties: components["schemas"]["TileToPropertiesOutput"]; - sub: components["schemas"]["IntegerOutput"]; - normalbae_image_processor: components["schemas"]["ImageOutput"]; - invert_tensor_mask: components["schemas"]["MaskOutput"]; - create_gradient_mask: components["schemas"]["GradientMaskOutput"]; - string_split: components["schemas"]["String2Output"]; - step_param_easing: components["schemas"]["FloatCollectionOutput"]; - metadata: components["schemas"]["MetadataOutput"]; - img_pad_crop: components["schemas"]["ImageOutput"]; - integer: components["schemas"]["IntegerOutput"]; - img_mul: components["schemas"]["ImageOutput"]; - calculate_image_tiles: components["schemas"]["CalculateImageTilesOutput"]; - color: components["schemas"]["ColorOutput"]; - infill_rgba: components["schemas"]["ImageOutput"]; - t2i_adapter: components["schemas"]["T2IAdapterOutput"]; - denoise_latents: components["schemas"]["LatentsOutput"]; - img_lerp: components["schemas"]["ImageOutput"]; - img_channel_offset: components["schemas"]["ImageOutput"]; - img_crop: components["schemas"]["ImageOutput"]; - alpha_mask_to_tensor: components["schemas"]["MaskOutput"]; - color_correct: components["schemas"]["ImageOutput"]; - calculate_image_tiles_min_overlap: components["schemas"]["CalculateImageTilesOutput"]; - img_hue_adjust: components["schemas"]["ImageOutput"]; - lresize: components["schemas"]["LatentsOutput"]; - img_blur: components["schemas"]["ImageOutput"]; - compel: components["schemas"]["ConditioningOutput"]; - sdxl_lora_collection_loader: components["schemas"]["SDXLLoRALoaderOutput"]; - float_to_int: components["schemas"]["IntegerOutput"]; - boolean: components["schemas"]["BooleanOutput"]; - string_join_three: components["schemas"]["StringOutput"]; - add: components["schemas"]["IntegerOutput"]; - merge_tiles_to_image: components["schemas"]["ImageOutput"]; - core_metadata: components["schemas"]["MetadataOutput"]; - lscale: components["schemas"]["LatentsOutput"]; - mlsd_image_processor: components["schemas"]["ImageOutput"]; - image_collection: components["schemas"]["ImageCollectionOutput"]; - crop_latents: components["schemas"]["LatentsOutput"]; - image_mask_to_tensor: components["schemas"]["MaskOutput"]; - lora_collection_loader: components["schemas"]["LoRALoaderOutput"]; - ip_adapter: components["schemas"]["IPAdapterOutput"]; - pidi_image_processor: components["schemas"]["ImageOutput"]; - rand_int: components["schemas"]["IntegerOutput"]; - img_conv: components["schemas"]["ImageOutput"]; - scheduler: components["schemas"]["SchedulerOutput"]; - img_paste: components["schemas"]["ImageOutput"]; - noise: components["schemas"]["NoiseOutput"]; - img_scale: components["schemas"]["ImageOutput"]; - i2l: components["schemas"]["LatentsOutput"]; - main_model_loader: components["schemas"]["ModelLoaderOutput"]; - blank_image: components["schemas"]["ImageOutput"]; - mask_edge: components["schemas"]["ImageOutput"]; - seamless: components["schemas"]["SeamlessModeOutput"]; - esrgan: components["schemas"]["ImageOutput"]; - canvas_paste_back: components["schemas"]["ImageOutput"]; - mul: components["schemas"]["IntegerOutput"]; - dynamic_prompt: components["schemas"]["StringCollectionOutput"]; - controlnet: components["schemas"]["ControlOutput"]; - l2i: components["schemas"]["ImageOutput"]; - ideal_size: components["schemas"]["IdealSizeOutput"]; - latents: components["schemas"]["LatentsOutput"]; - midas_depth_image_processor: components["schemas"]["ImageOutput"]; - tomask: components["schemas"]["ImageOutput"]; - float_math: components["schemas"]["FloatOutput"]; - round_float: components["schemas"]["FloatOutput"]; - cv_inpaint: components["schemas"]["ImageOutput"]; - create_denoise_mask: components["schemas"]["DenoiseMaskOutput"]; - model_identifier: components["schemas"]["ModelIdentifierOutput"]; - pair_tile_image: components["schemas"]["PairTileImageOutput"]; - lineart_image_processor: components["schemas"]["ImageOutput"]; - img_nsfw: components["schemas"]["ImageOutput"]; - infill_cv2: components["schemas"]["ImageOutput"]; - clip_skip: components["schemas"]["CLIPSkipInvocationOutput"]; - dw_openpose_image_processor: components["schemas"]["ImageOutput"]; - img_resize: components["schemas"]["ImageOutput"]; - iterate: components["schemas"]["IterateInvocationOutput"]; - rectangle_mask: components["schemas"]["MaskOutput"]; - canny_image_processor: components["schemas"]["ImageOutput"]; - calculate_image_tiles_even_split: components["schemas"]["CalculateImageTilesOutput"]; - mask_from_id: components["schemas"]["ImageOutput"]; - metadata_item: components["schemas"]["MetadataItemOutput"]; - infill_tile: components["schemas"]["ImageOutput"]; - tiled_multi_diffusion_denoise_latents: components["schemas"]["LatentsOutput"]; img_channel_multiply: components["schemas"]["ImageOutput"]; - boolean_collection: components["schemas"]["BooleanCollectionOutput"]; - lora_loader: components["schemas"]["LoRALoaderOutput"]; - float_collection: components["schemas"]["FloatCollectionOutput"]; - string: components["schemas"]["StringOutput"]; - freeu: components["schemas"]["UNetOutput"]; - lineart_anime_image_processor: components["schemas"]["ImageOutput"]; - depth_anything_image_processor: components["schemas"]["ImageOutput"]; - image: components["schemas"]["ImageOutput"]; - face_mask_detection: components["schemas"]["FaceMaskOutput"]; - rand_float: components["schemas"]["FloatOutput"]; - float: components["schemas"]["FloatOutput"]; - random_range: components["schemas"]["IntegerCollectionOutput"]; - integer_collection: components["schemas"]["IntegerCollectionOutput"]; - sdxl_refiner_model_loader: components["schemas"]["SDXLRefinerModelLoaderOutput"]; - mask_combine: components["schemas"]["ImageOutput"]; - tile_image_processor: components["schemas"]["ImageOutput"]; - img_chan: components["schemas"]["ImageOutput"]; - vae_loader: components["schemas"]["VAEOutput"]; - prompt_from_file: components["schemas"]["StringCollectionOutput"]; - float_range: components["schemas"]["FloatCollectionOutput"]; + img_blur: components["schemas"]["ImageOutput"]; + cv_inpaint: components["schemas"]["ImageOutput"]; + integer_math: components["schemas"]["IntegerOutput"]; merge_metadata: components["schemas"]["MetadataOutput"]; - sdxl_compel_prompt: components["schemas"]["ConditioningOutput"]; - hed_image_processor: components["schemas"]["ImageOutput"]; - lora_selector: components["schemas"]["LoRASelectorOutput"]; - conditioning: components["schemas"]["ConditioningOutput"]; + mask_combine: components["schemas"]["ImageOutput"]; + sub: components["schemas"]["IntegerOutput"]; + crop_latents: components["schemas"]["LatentsOutput"]; + ideal_size: components["schemas"]["IdealSizeOutput"]; leres_image_processor: components["schemas"]["ImageOutput"]; + mask_from_id: components["schemas"]["ImageOutput"]; + infill_cv2: components["schemas"]["ImageOutput"]; + boolean: components["schemas"]["BooleanOutput"]; + conditioning_collection: components["schemas"]["ConditioningCollectionOutput"]; + infill_patchmatch: components["schemas"]["ImageOutput"]; + float_math: components["schemas"]["FloatOutput"]; + image_collection: components["schemas"]["ImageCollectionOutput"]; + img_conv: components["schemas"]["ImageOutput"]; + sdxl_refiner_compel_prompt: components["schemas"]["ConditioningOutput"]; + metadata: components["schemas"]["MetadataOutput"]; + save_image: components["schemas"]["ImageOutput"]; + boolean_collection: components["schemas"]["BooleanCollectionOutput"]; + img_paste: components["schemas"]["ImageOutput"]; + conditioning: components["schemas"]["ConditioningOutput"]; + color_map_image_processor: components["schemas"]["ImageOutput"]; + img_scale: components["schemas"]["ImageOutput"]; + string_join: components["schemas"]["StringOutput"]; + noise: components["schemas"]["NoiseOutput"]; + core_metadata: components["schemas"]["MetadataOutput"]; + lresize: components["schemas"]["LatentsOutput"]; + face_mask_detection: components["schemas"]["FaceMaskOutput"]; + calculate_image_tiles_even_split: components["schemas"]["CalculateImageTilesOutput"]; + mlsd_image_processor: components["schemas"]["ImageOutput"]; + sdxl_lora_collection_loader: components["schemas"]["SDXLLoRALoaderOutput"]; + img_nsfw: components["schemas"]["ImageOutput"]; + main_model_loader: components["schemas"]["ModelLoaderOutput"]; + rand_int: components["schemas"]["IntegerOutput"]; + mul: components["schemas"]["IntegerOutput"]; + integer: components["schemas"]["IntegerOutput"]; + img_pad_crop: components["schemas"]["ImageOutput"]; + collect: components["schemas"]["CollectInvocationOutput"]; + pidi_image_processor: components["schemas"]["ImageOutput"]; + lora_loader: components["schemas"]["LoRALoaderOutput"]; + infill_tile: components["schemas"]["ImageOutput"]; + calculate_image_tiles: components["schemas"]["CalculateImageTilesOutput"]; + scheduler: components["schemas"]["SchedulerOutput"]; + midas_depth_image_processor: components["schemas"]["ImageOutput"]; + latents: components["schemas"]["LatentsOutput"]; + tile_to_properties: components["schemas"]["TileToPropertiesOutput"]; + round_float: components["schemas"]["FloatOutput"]; + add: components["schemas"]["IntegerOutput"]; + dynamic_prompt: components["schemas"]["StringCollectionOutput"]; + l2i: components["schemas"]["ImageOutput"]; + integer_collection: components["schemas"]["IntegerCollectionOutput"]; + model_identifier: components["schemas"]["ModelIdentifierOutput"]; + normalbae_image_processor: components["schemas"]["ImageOutput"]; + img_chan: components["schemas"]["ImageOutput"]; + image: components["schemas"]["ImageOutput"]; + face_off: components["schemas"]["FaceOffOutput"]; + img_hue_adjust: components["schemas"]["ImageOutput"]; + color: components["schemas"]["ColorOutput"]; + color_correct: components["schemas"]["ImageOutput"]; + string_split_neg: components["schemas"]["StringPosNegOutput"]; + clip_skip: components["schemas"]["CLIPSkipInvocationOutput"]; + face_identifier: components["schemas"]["ImageOutput"]; + string_join_three: components["schemas"]["StringOutput"]; + img_channel_offset: components["schemas"]["ImageOutput"]; + infill_rgba: components["schemas"]["ImageOutput"]; + show_image: components["schemas"]["ImageOutput"]; + infill_lama: components["schemas"]["ImageOutput"]; + img_mul: components["schemas"]["ImageOutput"]; + segment_anything_processor: components["schemas"]["ImageOutput"]; + sdxl_refiner_model_loader: components["schemas"]["SDXLRefinerModelLoaderOutput"]; mediapipe_face_processor: components["schemas"]["ImageOutput"]; + image_mask_to_tensor: components["schemas"]["MaskOutput"]; + string_split: components["schemas"]["String2Output"]; + i2l: components["schemas"]["LatentsOutput"]; + zoe_depth_image_processor: components["schemas"]["ImageOutput"]; + float_to_int: components["schemas"]["IntegerOutput"]; + ip_adapter: components["schemas"]["IPAdapterOutput"]; + string: components["schemas"]["StringOutput"]; + rectangle_mask: components["schemas"]["MaskOutput"]; + lineart_anime_image_processor: components["schemas"]["ImageOutput"]; + alpha_mask_to_tensor: components["schemas"]["MaskOutput"]; + merge_tiles_to_image: components["schemas"]["ImageOutput"]; + div: components["schemas"]["IntegerOutput"]; + float_collection: components["schemas"]["FloatCollectionOutput"]; + tiled_multi_diffusion_denoise_latents: components["schemas"]["LatentsOutput"]; + seamless: components["schemas"]["SeamlessModeOutput"]; + float: components["schemas"]["FloatOutput"]; + lineart_image_processor: components["schemas"]["ImageOutput"]; + controlnet: components["schemas"]["ControlOutput"]; + step_param_easing: components["schemas"]["FloatCollectionOutput"]; + prompt_from_file: components["schemas"]["StringCollectionOutput"]; + rand_float: components["schemas"]["FloatOutput"]; + float_range: components["schemas"]["FloatCollectionOutput"]; + sdxl_lora_loader: components["schemas"]["SDXLLoRALoaderOutput"]; + latents_collection: components["schemas"]["LatentsCollectionOutput"]; + sdxl_compel_prompt: components["schemas"]["ConditioningOutput"]; + create_denoise_mask: components["schemas"]["DenoiseMaskOutput"]; + create_gradient_mask: components["schemas"]["GradientMaskOutput"]; + metadata_item: components["schemas"]["MetadataItemOutput"]; + img_ilerp: components["schemas"]["ImageOutput"]; content_shuffle_image_processor: components["schemas"]["ImageOutput"]; + img_watermark: components["schemas"]["ImageOutput"]; + invert_tensor_mask: components["schemas"]["MaskOutput"]; + img_crop: components["schemas"]["ImageOutput"]; + esrgan: components["schemas"]["ImageOutput"]; + iterate: components["schemas"]["IterateInvocationOutput"]; + unsharp_mask: components["schemas"]["ImageOutput"]; + mask_edge: components["schemas"]["ImageOutput"]; + canvas_paste_back: components["schemas"]["ImageOutput"]; + dw_openpose_image_processor: components["schemas"]["ImageOutput"]; + tomask: components["schemas"]["ImageOutput"]; + random_range: components["schemas"]["IntegerCollectionOutput"]; + lora_selector: components["schemas"]["LoRASelectorOutput"]; + compel: components["schemas"]["ConditioningOutput"]; + img_resize: components["schemas"]["ImageOutput"]; + sdxl_model_loader: components["schemas"]["SDXLModelLoaderOutput"]; + lora_collection_loader: components["schemas"]["LoRALoaderOutput"]; + string_replace: components["schemas"]["StringOutput"]; + string_collection: components["schemas"]["StringCollectionOutput"]; + blank_image: components["schemas"]["ImageOutput"]; + freeu: components["schemas"]["UNetOutput"]; + img_lerp: components["schemas"]["ImageOutput"]; + range: components["schemas"]["IntegerCollectionOutput"]; + depth_anything_image_processor: components["schemas"]["ImageOutput"]; + t2i_adapter: components["schemas"]["T2IAdapterOutput"]; + pair_tile_image: components["schemas"]["PairTileImageOutput"]; + lscale: components["schemas"]["LatentsOutput"]; + tile_image_processor: components["schemas"]["ImageOutput"]; + heuristic_resize: components["schemas"]["ImageOutput"]; + calculate_image_tiles_min_overlap: components["schemas"]["CalculateImageTilesOutput"]; + denoise_latents: components["schemas"]["LatentsOutput"]; + vae_loader: components["schemas"]["VAEOutput"]; + lblend: components["schemas"]["LatentsOutput"]; + hed_image_processor: components["schemas"]["ImageOutput"]; + canny_image_processor: components["schemas"]["ImageOutput"]; }; /** * InvocationStartedEvent @@ -15014,6 +15014,8 @@ export type operations = { query: { /** @description The name of the board to create */ board_name: string; + /** @description Whether the board is private */ + private_board?: boolean; }; }; responses: { diff --git a/invokeai/frontend/web/src/services/api/types.ts b/invokeai/frontend/web/src/services/api/types.ts index d5c857cefd..5beb5cbbf5 100644 --- a/invokeai/frontend/web/src/services/api/types.ts +++ b/invokeai/frontend/web/src/services/api/types.ts @@ -11,9 +11,7 @@ export type ListBoardsArgs = NonNullable