From 68f1f87c6fe7ad3f4a54b18f573559c6d55dac76 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Fri, 21 Jul 2023 20:02:54 +1000 Subject: [PATCH] feat(ui): board styles --- .../listeners/boardIdSelected.ts | 2 +- .../web/src/common/components/IAICollapse.tsx | 5 +- .../web/src/common/components/IAIDndImage.tsx | 29 ++++++-- .../common/components/SelectionOverlay.tsx | 42 +++++++++++ .../gallery/components/Boards/AutoAddIcon.tsx | 2 +- .../components/Boards/BoardContextMenu.tsx | 68 +++++++++++++----- .../Boards/BoardsList/BoardsList.tsx | 2 +- .../Boards/BoardsList/GalleryBoard.tsx | 68 +++++++++--------- .../Boards/BoardsList/NoBoardBoard.tsx | 70 ++++++++++++++----- .../Boards/GalleryBoardContextMenuItems.tsx | 4 +- .../Boards/NoBoardContextMenuItems.tsx | 4 +- .../gallery/components/GalleryBoardName.tsx | 33 +++++---- .../components/ImageGalleryContent.tsx | 44 ++++++++---- .../components/ImageGrid/GalleryImage.tsx | 1 + .../components/ImageGrid/GalleryImageGrid.tsx | 34 ++++++--- .../ui/components/ParametersDrawer.tsx | 1 - .../ResizableDrawer/ResizableDrawer.tsx | 2 +- .../web/src/theme/components/button.ts | 14 ++++ invokeai/frontend/web/src/theme/theme.ts | 6 +- 19 files changed, 306 insertions(+), 125 deletions(-) create mode 100644 invokeai/frontend/web/src/common/components/SelectionOverlay.tsx diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/boardIdSelected.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/boardIdSelected.ts index a2ac34969f..b44f87a081 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/boardIdSelected.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/boardIdSelected.ts @@ -44,7 +44,7 @@ export const addBoardIdSelectedListener = () => { () => imagesApi.endpoints.listImages.select(queryArgs)(getState()) .isSuccess, - 1000 + 5000 ); if (isSuccess) { diff --git a/invokeai/frontend/web/src/common/components/IAICollapse.tsx b/invokeai/frontend/web/src/common/components/IAICollapse.tsx index 09dc1392e2..0ce767ed9d 100644 --- a/invokeai/frontend/web/src/common/components/IAICollapse.tsx +++ b/invokeai/frontend/web/src/common/components/IAICollapse.tsx @@ -92,7 +92,10 @@ const IAICollapse = (props: IAIToggleCollapseProps) => { sx={{ p: 4, borderBottomRadius: 'base', - bg: mode('base.100', 'base.800')(colorMode), + bg: 'base.100', + _dark: { + bg: 'base.800', + }, }} > {children} diff --git a/invokeai/frontend/web/src/common/components/IAIDndImage.tsx b/invokeai/frontend/web/src/common/components/IAIDndImage.tsx index 57d54e155e..cf01a93197 100644 --- a/invokeai/frontend/web/src/common/components/IAIDndImage.tsx +++ b/invokeai/frontend/web/src/common/components/IAIDndImage.tsx @@ -18,12 +18,20 @@ import { import ImageMetadataOverlay from 'common/components/ImageMetadataOverlay'; import { useImageUploadButton } from 'common/hooks/useImageUploadButton'; import ImageContextMenu from 'features/gallery/components/ImageContextMenu/ImageContextMenu'; -import { MouseEvent, ReactElement, SyntheticEvent, memo } from 'react'; +import { + MouseEvent, + ReactElement, + SyntheticEvent, + memo, + useCallback, + useState, +} from 'react'; import { FaImage, FaUndo, FaUpload } from 'react-icons/fa'; import { ImageDTO, PostUploadAction } from 'services/api/types'; import { mode } from 'theme/util/mode'; import IAIDraggable from './IAIDraggable'; import IAIDroppable from './IAIDroppable'; +import SelectionOverlay from './SelectionOverlay'; type IAIDndImageProps = { imageDTO: ImageDTO | undefined; @@ -49,6 +57,7 @@ type IAIDndImageProps = { thumbnail?: boolean; noContentFallback?: ReactElement; useThumbailFallback?: boolean; + withHoverOverlay?: boolean; }; const IAIDndImage = (props: IAIDndImageProps) => { @@ -75,9 +84,17 @@ const IAIDndImage = (props: IAIDndImageProps) => { resetIcon = , noContentFallback = , useThumbailFallback, + withHoverOverlay = false, } = props; const { colorMode } = useColorMode(); + const [isHovered, setIsHovered] = useState(false); + const handleMouseOver = useCallback(() => { + setIsHovered(true); + }, []); + const handleMouseOut = useCallback(() => { + setIsHovered(false); + }, []); const { getUploadButtonProps, getUploadInputProps } = useImageUploadButton({ postUploadAction, @@ -105,6 +122,8 @@ const IAIDndImage = (props: IAIDndImageProps) => { {(ref) => ( { maxW: 'full', maxH: 'full', borderRadius: 'base', - shadow: isSelected ? 'selected.light' : undefined, - _dark: { - shadow: isSelected ? 'selected.dark' : undefined, - }, ...imageSx, }} /> {withMetadataOverlay && } + )} {!imageDTO && !isUploadDisabled && ( diff --git a/invokeai/frontend/web/src/common/components/SelectionOverlay.tsx b/invokeai/frontend/web/src/common/components/SelectionOverlay.tsx new file mode 100644 index 0000000000..9ff6cd341b --- /dev/null +++ b/invokeai/frontend/web/src/common/components/SelectionOverlay.tsx @@ -0,0 +1,42 @@ +import { Box } from '@chakra-ui/react'; + +type Props = { + isSelected: boolean; + isHovered: boolean; +}; +const SelectionOverlay = ({ isSelected, isHovered }: Props) => { + return ( + + ); +}; + +export default SelectionOverlay; diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/AutoAddIcon.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/AutoAddIcon.tsx index 53f0293afa..ffdde04ef5 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/AutoAddIcon.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/AutoAddIcon.tsx @@ -5,7 +5,7 @@ const AutoAddIcon = () => { { const dispatch = useAppDispatch(); + const selector = useMemo( + () => + createSelector(stateSelector, ({ gallery }) => { + const isSelected = gallery.selectedBoardId === board_id; + const isAutoAdd = gallery.autoAddBoardId === board_id; + return { isSelected, isAutoAdd }; + }), + [board_id] + ); + + const { isSelected, isAutoAdd } = useAppSelector(selector); + const boardName = useBoardName(board_id); + const handleSelectBoard = useCallback(() => { - dispatch(boardIdSelected(board?.board_id ?? board_id)); - }, [board?.board_id, board_id, dispatch]); + dispatch(boardIdSelected(board_id)); + }, [board_id, dispatch]); + + const handleSetAutoAdd = useCallback(() => { + dispatch(autoAddBoardIdChanged(board_id)); + }, [board_id, dispatch]); + + const skipEvent = useCallback((e: MouseEvent) => { + e.preventDefault(); + }, []); return ( @@ -35,17 +62,24 @@ const BoardContextMenu = memo( - } onClickCapture={handleSelectBoard}> - Select Board - - {!board && } - {board && ( - - )} + + } + isDisabled={isAutoAdd} + onClick={handleSetAutoAdd} + > + Auto-add to this Board + + {!board && } + {board && ( + + )} + )} > 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 1cd32f93b0..f20bb5a245 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 @@ -76,7 +76,7 @@ const BoardsList = (props: Props) => { 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 8b5d871799..dc484ec230 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 @@ -26,6 +26,7 @@ import { useBoardTotal } from 'services/api/hooks/useBoardTotal'; import { BoardDTO } from 'services/api/types'; import AutoAddIcon from '../AutoAddIcon'; import BoardContextMenu from '../BoardContextMenu'; +import SelectionOverlay from 'common/components/SelectionOverlay'; const BASE_BADGE_STYLES: ChakraProps['sx'] = { bg: 'base.500', @@ -56,7 +57,13 @@ const GalleryBoard = memo( ); const { isSelectedForAutoAdd } = useAppSelector(selector); - + const [isHovered, setIsHovered] = useState(false); + const handleMouseOver = useCallback(() => { + setIsHovered(true); + }, []); + const handleMouseOut = useCallback(() => { + setIsHovered(false); + }, []); const { currentData: coverImage } = useGetImageDTOQuery( board.cover_image_name ?? skipToken ); @@ -83,26 +90,30 @@ const GalleryBoard = memo( ); const handleSubmit = useCallback( - (newBoardName: string) => { - if (!newBoardName) { - // empty strings are not allowed + 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) { - // don't updated the board name if it hasn't changed return; } - updateBoard({ board_id, changes: { board_name: newBoardName } }) - .unwrap() - .then((response) => { - // update local state - setLocalBoardName(response.board_name); - }) - .catch(() => { - // revert on error - setLocalBoardName(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); + } }, [board_id, board_name, updateBoard] ); @@ -116,6 +127,8 @@ const GalleryBoard = memo( sx={{ w: 'full', h: 'full', touchAction: 'none', userSelect: 'none' }} > )} - {totalImages}/{totalAssets} - + */} {isSelectedForAutoAdd && } - { const dispatch = useAppDispatch(); const { totalImages, totalAssets } = useBoardTotal(undefined); const { autoAddBoardId } = useAppSelector(selector); + const boardName = useBoardName(undefined); const handleSelectBoard = useCallback(() => { dispatch(boardIdSelected(undefined)); }, [dispatch]); + const [isHovered, setIsHovered] = useState(false); + const handleMouseOver = useCallback(() => { + setIsHovered(true); + }, []); + const handleMouseOut = useCallback(() => { + setIsHovered(false); + }, []); const droppableData: MoveBoardDropData = useMemo( () => ({ @@ -49,6 +59,8 @@ const NoBoardBoard = memo(({ isSelected }: Props) => { return ( { alignItems: 'center', }} > - { color: 'base.500', }, }} + /> */} + invoke-ai-logo - { {totalImages}/{totalAssets} - + */} {!autoAddBoardId && } - + > + {boardName} + + Move} diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/GalleryBoardContextMenuItems.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/GalleryBoardContextMenuItems.tsx index e8bd1be992..4b036bfe7c 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/GalleryBoardContextMenuItems.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/GalleryBoardContextMenuItems.tsx @@ -59,11 +59,11 @@ const GalleryBoardContextMenuItems = ({ board, setBoardToDelete }: Props) => { */} )} - {!isSelectedForAutoAdd && ( + {/* {!isSelectedForAutoAdd && ( } onClick={handleToggleAutoAdd}> Auto-add to this Board - )} + )} */} } diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/NoBoardContextMenuItems.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/NoBoardContextMenuItems.tsx index 34b4c5f790..31366f6e6a 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/NoBoardContextMenuItems.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/NoBoardContextMenuItems.tsx @@ -16,11 +16,11 @@ const NoBoardContextMenuItems = () => { return ( <> - {autoAddBoardId && ( + {/* {autoAddBoardId && ( } onClick={handleDisableAutoAdd}> Auto-add to this Board - )} + )} */} ); }; diff --git a/invokeai/frontend/web/src/features/gallery/components/GalleryBoardName.tsx b/invokeai/frontend/web/src/features/gallery/components/GalleryBoardName.tsx index 7e2048e628..4667723e23 100644 --- a/invokeai/frontend/web/src/features/gallery/components/GalleryBoardName.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/GalleryBoardName.tsx @@ -6,7 +6,6 @@ import { useAppSelector } from 'app/store/storeHooks'; import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; import { memo, useMemo } from 'react'; import { useBoardName } from 'services/api/hooks/useBoardName'; -import { useBoardTotal } from 'services/api/hooks/useBoardTotal'; const selector = createSelector( [stateSelector], @@ -27,24 +26,28 @@ const GalleryBoardName = (props: Props) => { const { isOpen, onToggle } = props; const { selectedBoardId } = useAppSelector(selector); const boardName = useBoardName(selectedBoardId); - const { totalImages, totalAssets } = useBoardTotal(selectedBoardId); + // const { totalImages, totalAssets } = useBoardTotal(selectedBoardId); const formattedBoardName = useMemo(() => { - if (!boardName) { - return ''; - } - - if (boardName && (totalImages === undefined || totalAssets === undefined)) { - return boardName; - } - - const count = `${totalImages}/${totalAssets}`; - if (boardName.length > 20) { - return `${boardName.substring(0, 20)}... (${count})`; + return `${boardName.substring(0, 20)}...`; } - return `${boardName} (${count})`; - }, [boardName, totalAssets, totalImages]); + return boardName; + // if (!boardName) { + // return ''; + // } + + // if (boardName && (totalImages === undefined || totalAssets === undefined)) { + // return boardName; + // } + + // const count = `${totalImages}/${totalAssets}`; + + // if (boardName.length > 20) { + // return `${boardName.substring(0, 20)}... (${count})`; + // } + // return `${boardName} (${count})`; + }, [boardName]); return ( { > - - Images - - - Assets - + } + > + Images + + } + > + Assets + + diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryImage.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryImage.tsx index bf627b9591..866b1579bd 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryImage.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryImage.tsx @@ -106,6 +106,7 @@ const GalleryImage = (props: HoverableImageProps) => { isDropDisabled={true} isUploadDisabled={true} thumbnail={true} + withHoverOverlay // resetIcon={} // resetTooltip="Delete image" // withResetIcon // removed bc it's too easy to accidentally delete images diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryImageGrid.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryImageGrid.tsx index e4b996fc96..285327a971 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryImageGrid.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryImageGrid.tsx @@ -1,8 +1,9 @@ -import { Box, Spinner } from '@chakra-ui/react'; +import { Box, Flex } from '@chakra-ui/react'; import { useAppSelector } from 'app/store/storeHooks'; import IAIButton from 'common/components/IAIButton'; import { IAINoContentFallback } from 'common/components/IAIImageFallback'; import { IMAGE_LIMIT } from 'features/gallery//store/gallerySlice'; +import { selectListImagesBaseQueryArgs } from 'features/gallery/store/gallerySelectors'; import { UseOverlayScrollbarsParams, useOverlayScrollbars, @@ -15,11 +16,10 @@ import { useLazyListImagesQuery, useListImagesQuery, } from 'services/api/endpoints/images'; +import { useBoardTotal } from 'services/api/hooks/useBoardTotal'; import GalleryImage from './GalleryImage'; import ImageGridItemContainer from './ImageGridItemContainer'; import ImageGridListContainer from './ImageGridListContainer'; -import { selectListImagesBaseQueryArgs } from 'features/gallery/store/gallerySelectors'; -import { useBoardTotal } from 'services/api/hooks/useBoardTotal'; const overlayScrollbarsConfig: UseOverlayScrollbarsParams = { defer: true, @@ -87,20 +87,34 @@ const GalleryImageGrid = () => { if (!currentData) { return ( - - - + + + ); } if (isSuccess && currentData?.ids.length === 0) { return ( - + - + ); } @@ -129,9 +143,7 @@ const GalleryImageGrid = () => { loadingText="Loading" flexShrink={0} > - {areMoreAvailable - ? t('gallery.loadMore') - : t('gallery.allImagesLoaded')} + {`Load More (${currentData.ids.length} of ${currentViewTotal})`} ); diff --git a/invokeai/frontend/web/src/features/ui/components/ParametersDrawer.tsx b/invokeai/frontend/web/src/features/ui/components/ParametersDrawer.tsx index ee9cc4bbb8..89400c09e5 100644 --- a/invokeai/frontend/web/src/features/ui/components/ParametersDrawer.tsx +++ b/invokeai/frontend/web/src/features/ui/components/ParametersDrawer.tsx @@ -78,7 +78,6 @@ const ParametersDrawer = () => { }} > { }; }); +const invokeAIOutline = defineStyle((props) => { + const { colorScheme: c } = props; + const borderColor = mode(`gray.200`, `whiteAlpha.300`)(props); + return { + border: '1px solid', + borderColor: c === 'gray' ? borderColor : 'currentColor', + '.chakra-button__group[data-attached][data-orientation=horizontal] > &:not(:last-of-type)': + { marginEnd: '-1px' }, + '.chakra-button__group[data-attached][data-orientation=vertical] > &:not(:last-of-type)': + { marginBottom: '-1px' }, + }; +}); + export const buttonTheme = defineStyleConfig({ variants: { invokeAI, + invokeAIOutline, }, defaultProps: { variant: 'invokeAI', diff --git a/invokeai/frontend/web/src/theme/theme.ts b/invokeai/frontend/web/src/theme/theme.ts index 6f7a719e85..afed8688ee 100644 --- a/invokeai/frontend/web/src/theme/theme.ts +++ b/invokeai/frontend/web/src/theme/theme.ts @@ -78,12 +78,12 @@ export const theme: ThemeOverride = { hoverSelected: { light: '0px 0px 0px 1px var(--invokeai-colors-base-150), 0px 0px 0px 4px var(--invokeai-colors-accent-500)', - dark: '0px 0px 0px 1px var(--invokeai-colors-base-900), 0px 0px 0px 4px var(--invokeai-colors-accent-300)', + dark: '0px 0px 0px 1px var(--invokeai-colors-base-900), 0px 0px 0px 4px var(--invokeai-colors-accent-400)', }, hoverUnselected: { light: - '0px 0px 0px 1px var(--invokeai-colors-base-150), 0px 0px 0px 4px var(--invokeai-colors-accent-200)', - dark: '0px 0px 0px 1px var(--invokeai-colors-base-900), 0px 0px 0px 4px var(--invokeai-colors-accent-600)', + '0px 0px 0px 1px var(--invokeai-colors-base-150), 0px 0px 0px 3px var(--invokeai-colors-accent-500)', + dark: '0px 0px 0px 1px var(--invokeai-colors-base-900), 0px 0px 0px 3px var(--invokeai-colors-accent-400)', }, nodeSelectedOutline: `0 0 0 2px var(--invokeai-colors-accent-450)`, },