feat(ui): board styles

This commit is contained in:
psychedelicious 2023-07-21 20:02:54 +10:00 committed by Kent Keirsey
parent c2c99b8650
commit 68f1f87c6f
19 changed files with 306 additions and 125 deletions

View File

@ -44,7 +44,7 @@ export const addBoardIdSelectedListener = () => {
() => () =>
imagesApi.endpoints.listImages.select(queryArgs)(getState()) imagesApi.endpoints.listImages.select(queryArgs)(getState())
.isSuccess, .isSuccess,
1000 5000
); );
if (isSuccess) { if (isSuccess) {

View File

@ -92,7 +92,10 @@ const IAICollapse = (props: IAIToggleCollapseProps) => {
sx={{ sx={{
p: 4, p: 4,
borderBottomRadius: 'base', borderBottomRadius: 'base',
bg: mode('base.100', 'base.800')(colorMode), bg: 'base.100',
_dark: {
bg: 'base.800',
},
}} }}
> >
{children} {children}

View File

@ -18,12 +18,20 @@ import {
import ImageMetadataOverlay from 'common/components/ImageMetadataOverlay'; import ImageMetadataOverlay from 'common/components/ImageMetadataOverlay';
import { useImageUploadButton } from 'common/hooks/useImageUploadButton'; import { useImageUploadButton } from 'common/hooks/useImageUploadButton';
import ImageContextMenu from 'features/gallery/components/ImageContextMenu/ImageContextMenu'; 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 { FaImage, FaUndo, FaUpload } from 'react-icons/fa';
import { ImageDTO, PostUploadAction } from 'services/api/types'; import { ImageDTO, PostUploadAction } from 'services/api/types';
import { mode } from 'theme/util/mode'; import { mode } from 'theme/util/mode';
import IAIDraggable from './IAIDraggable'; import IAIDraggable from './IAIDraggable';
import IAIDroppable from './IAIDroppable'; import IAIDroppable from './IAIDroppable';
import SelectionOverlay from './SelectionOverlay';
type IAIDndImageProps = { type IAIDndImageProps = {
imageDTO: ImageDTO | undefined; imageDTO: ImageDTO | undefined;
@ -49,6 +57,7 @@ type IAIDndImageProps = {
thumbnail?: boolean; thumbnail?: boolean;
noContentFallback?: ReactElement; noContentFallback?: ReactElement;
useThumbailFallback?: boolean; useThumbailFallback?: boolean;
withHoverOverlay?: boolean;
}; };
const IAIDndImage = (props: IAIDndImageProps) => { const IAIDndImage = (props: IAIDndImageProps) => {
@ -75,9 +84,17 @@ const IAIDndImage = (props: IAIDndImageProps) => {
resetIcon = <FaUndo />, resetIcon = <FaUndo />,
noContentFallback = <IAINoContentFallback icon={FaImage} />, noContentFallback = <IAINoContentFallback icon={FaImage} />,
useThumbailFallback, useThumbailFallback,
withHoverOverlay = false,
} = props; } = props;
const { colorMode } = useColorMode(); const { colorMode } = useColorMode();
const [isHovered, setIsHovered] = useState(false);
const handleMouseOver = useCallback(() => {
setIsHovered(true);
}, []);
const handleMouseOut = useCallback(() => {
setIsHovered(false);
}, []);
const { getUploadButtonProps, getUploadInputProps } = useImageUploadButton({ const { getUploadButtonProps, getUploadInputProps } = useImageUploadButton({
postUploadAction, postUploadAction,
@ -105,6 +122,8 @@ const IAIDndImage = (props: IAIDndImageProps) => {
{(ref) => ( {(ref) => (
<Flex <Flex
ref={ref} ref={ref}
onMouseOver={handleMouseOver}
onMouseOut={handleMouseOut}
sx={{ sx={{
width: 'full', width: 'full',
height: 'full', height: 'full',
@ -147,14 +166,14 @@ const IAIDndImage = (props: IAIDndImageProps) => {
maxW: 'full', maxW: 'full',
maxH: 'full', maxH: 'full',
borderRadius: 'base', borderRadius: 'base',
shadow: isSelected ? 'selected.light' : undefined,
_dark: {
shadow: isSelected ? 'selected.dark' : undefined,
},
...imageSx, ...imageSx,
}} }}
/> />
{withMetadataOverlay && <ImageMetadataOverlay image={imageDTO} />} {withMetadataOverlay && <ImageMetadataOverlay image={imageDTO} />}
<SelectionOverlay
isSelected={isSelected}
isHovered={withHoverOverlay ? isHovered : false}
/>
</Flex> </Flex>
)} )}
{!imageDTO && !isUploadDisabled && ( {!imageDTO && !isUploadDisabled && (

View File

@ -0,0 +1,42 @@
import { Box } from '@chakra-ui/react';
type Props = {
isSelected: boolean;
isHovered: boolean;
};
const SelectionOverlay = ({ isSelected, isHovered }: Props) => {
return (
<Box
className="selection-box"
sx={{
position: 'absolute',
top: 0,
insetInlineEnd: 0,
bottom: 0,
insetInlineStart: 0,
borderRadius: 'base',
opacity: isSelected ? 1 : 0.7,
transitionProperty: 'common',
transitionDuration: '0.1s',
shadow: isSelected
? isHovered
? 'hoverSelected.light'
: 'selected.light'
: isHovered
? 'hoverUnselected.light'
: undefined,
_dark: {
shadow: isSelected
? isHovered
? 'hoverSelected.dark'
: 'selected.dark'
: isHovered
? 'hoverUnselected.dark'
: undefined,
},
}}
/>
);
};
export default SelectionOverlay;

View File

@ -5,7 +5,7 @@ const AutoAddIcon = () => {
<Flex <Flex
sx={{ sx={{
position: 'absolute', position: 'absolute',
insetInlineStart: 0, insetInlineEnd: 0,
top: 0, top: 0,
p: 1, p: 1,
}} }}

View File

@ -1,13 +1,19 @@
import { MenuItem, MenuList } from '@chakra-ui/react'; import { MenuGroup, MenuItem, MenuList } from '@chakra-ui/react';
import { useAppDispatch } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { ContextMenu, ContextMenuProps } from 'chakra-ui-contextmenu'; import { ContextMenu, ContextMenuProps } from 'chakra-ui-contextmenu';
import { boardIdSelected } from 'features/gallery/store/gallerySlice'; import {
import { memo, useCallback } from 'react'; autoAddBoardIdChanged,
import { FaFolder } from 'react-icons/fa'; boardIdSelected,
} from 'features/gallery/store/gallerySlice';
import { MouseEvent, memo, useCallback, useMemo } from 'react';
import { FaFolder, FaPlus } from 'react-icons/fa';
import { BoardDTO } from 'services/api/types'; import { BoardDTO } from 'services/api/types';
import { menuListMotionProps } from 'theme/components/menu'; import { menuListMotionProps } from 'theme/components/menu';
import GalleryBoardContextMenuItems from './GalleryBoardContextMenuItems'; import GalleryBoardContextMenuItems from './GalleryBoardContextMenuItems';
import NoBoardContextMenuItems from './NoBoardContextMenuItems'; import NoBoardContextMenuItems from './NoBoardContextMenuItems';
import { useBoardName } from 'services/api/hooks/useBoardName';
import { createSelector } from '@reduxjs/toolkit';
import { stateSelector } from 'app/store/store';
type Props = { type Props = {
board?: BoardDTO; board?: BoardDTO;
@ -20,9 +26,30 @@ const BoardContextMenu = memo(
({ board, board_id, setBoardToDelete, children }: Props) => { ({ board, board_id, setBoardToDelete, children }: Props) => {
const dispatch = useAppDispatch(); 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(() => { const handleSelectBoard = useCallback(() => {
dispatch(boardIdSelected(board?.board_id ?? board_id)); dispatch(boardIdSelected(board_id));
}, [board?.board_id, board_id, dispatch]); }, [board_id, dispatch]);
const handleSetAutoAdd = useCallback(() => {
dispatch(autoAddBoardIdChanged(board_id));
}, [board_id, dispatch]);
const skipEvent = useCallback((e: MouseEvent<HTMLDivElement>) => {
e.preventDefault();
}, []);
return ( return (
<ContextMenu<HTMLDivElement> <ContextMenu<HTMLDivElement>
@ -35,17 +62,24 @@ const BoardContextMenu = memo(
<MenuList <MenuList
sx={{ visibility: 'visible !important' }} sx={{ visibility: 'visible !important' }}
motionProps={menuListMotionProps} motionProps={menuListMotionProps}
onContextMenu={skipEvent}
> >
<MenuItem icon={<FaFolder />} onClickCapture={handleSelectBoard}> <MenuGroup title={boardName}>
Select Board <MenuItem
</MenuItem> icon={<FaPlus />}
{!board && <NoBoardContextMenuItems />} isDisabled={isAutoAdd}
{board && ( onClick={handleSetAutoAdd}
<GalleryBoardContextMenuItems >
board={board} Auto-add to this Board
setBoardToDelete={setBoardToDelete} </MenuItem>
/> {!board && <NoBoardContextMenuItems />}
)} {board && (
<GalleryBoardContextMenuItems
board={board}
setBoardToDelete={setBoardToDelete}
/>
)}
</MenuGroup>
</MenuList> </MenuList>
)} )}
> >

View File

@ -76,7 +76,7 @@ const BoardsList = (props: Props) => {
<Grid <Grid
className="list-container" className="list-container"
sx={{ sx={{
gridTemplateColumns: `repeat(auto-fill, minmax(96px, 1fr));`, gridTemplateColumns: `repeat(auto-fill, minmax(108px, 1fr));`,
maxH: 346, maxH: 346,
}} }}
> >

View File

@ -26,6 +26,7 @@ import { useBoardTotal } from 'services/api/hooks/useBoardTotal';
import { BoardDTO } from 'services/api/types'; import { BoardDTO } from 'services/api/types';
import AutoAddIcon from '../AutoAddIcon'; import AutoAddIcon from '../AutoAddIcon';
import BoardContextMenu from '../BoardContextMenu'; import BoardContextMenu from '../BoardContextMenu';
import SelectionOverlay from 'common/components/SelectionOverlay';
const BASE_BADGE_STYLES: ChakraProps['sx'] = { const BASE_BADGE_STYLES: ChakraProps['sx'] = {
bg: 'base.500', bg: 'base.500',
@ -56,7 +57,13 @@ const GalleryBoard = memo(
); );
const { isSelectedForAutoAdd } = useAppSelector(selector); const { isSelectedForAutoAdd } = useAppSelector(selector);
const [isHovered, setIsHovered] = useState(false);
const handleMouseOver = useCallback(() => {
setIsHovered(true);
}, []);
const handleMouseOut = useCallback(() => {
setIsHovered(false);
}, []);
const { currentData: coverImage } = useGetImageDTOQuery( const { currentData: coverImage } = useGetImageDTOQuery(
board.cover_image_name ?? skipToken board.cover_image_name ?? skipToken
); );
@ -83,26 +90,30 @@ const GalleryBoard = memo(
); );
const handleSubmit = useCallback( const handleSubmit = useCallback(
(newBoardName: string) => { async (newBoardName: string) => {
if (!newBoardName) { // empty strings are not allowed
// empty strings are not allowed if (!newBoardName.trim()) {
setLocalBoardName(board_name); setLocalBoardName(board_name);
return; return;
} }
// don't updated the board name if it hasn't changed
if (newBoardName === board_name) { if (newBoardName === board_name) {
// don't updated the board name if it hasn't changed
return; return;
} }
updateBoard({ board_id, changes: { board_name: newBoardName } })
.unwrap() try {
.then((response) => { const { board_name } = await updateBoard({
// update local state board_id,
setLocalBoardName(response.board_name); changes: { board_name: newBoardName },
}) }).unwrap();
.catch(() => {
// revert on error // update local state
setLocalBoardName(board_name); setLocalBoardName(board_name);
}); } catch {
// revert on error
setLocalBoardName(board_name);
}
}, },
[board_id, board_name, updateBoard] [board_id, board_name, updateBoard]
); );
@ -116,6 +127,8 @@ const GalleryBoard = memo(
sx={{ w: 'full', h: 'full', touchAction: 'none', userSelect: 'none' }} sx={{ w: 'full', h: 'full', touchAction: 'none', userSelect: 'none' }}
> >
<Flex <Flex
onMouseOver={handleMouseOver}
onMouseOut={handleMouseOut}
sx={{ sx={{
position: 'relative', position: 'relative',
justifyContent: 'center', justifyContent: 'center',
@ -174,7 +187,7 @@ const GalleryBoard = memo(
boxSize={12} boxSize={12}
as={FaUser} as={FaUser}
sx={{ sx={{
mt: -3, mt: -6,
opacity: 0.7, opacity: 0.7,
color: 'base.500', color: 'base.500',
_dark: { _dark: {
@ -184,7 +197,7 @@ const GalleryBoard = memo(
/> />
</Flex> </Flex>
)} )}
<Flex {/* <Flex
sx={{ sx={{
position: 'absolute', position: 'absolute',
insetInlineEnd: 0, insetInlineEnd: 0,
@ -195,24 +208,11 @@ const GalleryBoard = memo(
<Badge variant="solid" sx={BASE_BADGE_STYLES}> <Badge variant="solid" sx={BASE_BADGE_STYLES}>
{totalImages}/{totalAssets} {totalImages}/{totalAssets}
</Badge> </Badge>
</Flex> </Flex> */}
{isSelectedForAutoAdd && <AutoAddIcon />} {isSelectedForAutoAdd && <AutoAddIcon />}
<Box <SelectionOverlay
className="selection-box" isSelected={isSelected}
sx={{ isHovered={isHovered}
position: 'absolute',
top: 0,
insetInlineEnd: 0,
bottom: 0,
insetInlineStart: 0,
borderRadius: 'base',
transitionProperty: 'common',
transitionDuration: 'common',
shadow: isSelected ? 'selected.light' : undefined,
_dark: {
shadow: isSelected ? 'selected.dark' : undefined,
},
}}
/> />
<Flex <Flex
sx={{ sx={{

View File

@ -1,13 +1,15 @@
import { Badge, Box, ChakraProps, Flex, Icon, Text } from '@chakra-ui/react'; import { Box, ChakraProps, Flex, Image, Text } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import { MoveBoardDropData } from 'app/components/ImageDnd/typesafeDnd'; import { MoveBoardDropData } from 'app/components/ImageDnd/typesafeDnd';
import { stateSelector } from 'app/store/store'; import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import InvokeAILogoImage from 'assets/images/logo.png';
import IAIDroppable from 'common/components/IAIDroppable'; import IAIDroppable from 'common/components/IAIDroppable';
import SelectionOverlay from 'common/components/SelectionOverlay';
import { boardIdSelected } from 'features/gallery/store/gallerySlice'; import { boardIdSelected } from 'features/gallery/store/gallerySlice';
import { memo, useCallback, useMemo } from 'react'; import { memo, useCallback, useMemo, useState } from 'react';
import { FaFolder } from 'react-icons/fa'; import { useBoardName } from 'services/api/hooks/useBoardName';
import { useBoardTotal } from 'services/api/hooks/useBoardTotal'; import { useBoardTotal } from 'services/api/hooks/useBoardTotal';
import AutoAddIcon from '../AutoAddIcon'; import AutoAddIcon from '../AutoAddIcon';
import BoardContextMenu from '../BoardContextMenu'; import BoardContextMenu from '../BoardContextMenu';
@ -33,9 +35,17 @@ const NoBoardBoard = memo(({ isSelected }: Props) => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const { totalImages, totalAssets } = useBoardTotal(undefined); const { totalImages, totalAssets } = useBoardTotal(undefined);
const { autoAddBoardId } = useAppSelector(selector); const { autoAddBoardId } = useAppSelector(selector);
const boardName = useBoardName(undefined);
const handleSelectBoard = useCallback(() => { const handleSelectBoard = useCallback(() => {
dispatch(boardIdSelected(undefined)); dispatch(boardIdSelected(undefined));
}, [dispatch]); }, [dispatch]);
const [isHovered, setIsHovered] = useState(false);
const handleMouseOver = useCallback(() => {
setIsHovered(true);
}, []);
const handleMouseOut = useCallback(() => {
setIsHovered(false);
}, []);
const droppableData: MoveBoardDropData = useMemo( const droppableData: MoveBoardDropData = useMemo(
() => ({ () => ({
@ -49,6 +59,8 @@ const NoBoardBoard = memo(({ isSelected }: Props) => {
return ( return (
<Box sx={{ w: 'full', h: 'full', touchAction: 'none', userSelect: 'none' }}> <Box sx={{ w: 'full', h: 'full', touchAction: 'none', userSelect: 'none' }}>
<Flex <Flex
onMouseOver={handleMouseOver}
onMouseOut={handleMouseOut}
sx={{ sx={{
position: 'relative', position: 'relative',
justifyContent: 'center', justifyContent: 'center',
@ -86,9 +98,9 @@ const NoBoardBoard = memo(({ isSelected }: Props) => {
alignItems: 'center', alignItems: 'center',
}} }}
> >
<Icon {/* <Icon
boxSize={12} boxSize={12}
as={FaFolder} as={FaBucket}
sx={{ sx={{
opacity: 0.7, opacity: 0.7,
color: 'base.500', color: 'base.500',
@ -96,9 +108,23 @@ const NoBoardBoard = memo(({ isSelected }: Props) => {
color: 'base.500', color: 'base.500',
}, },
}} }}
/> */}
<Image
src={InvokeAILogoImage}
alt="invoke-ai-logo"
sx={{
opacity: 0.4,
filter: 'grayscale(1)',
mt: -6,
w: 16,
h: 16,
minW: 16,
minH: 16,
userSelect: 'none',
}}
/> />
</Flex> </Flex>
<Flex {/* <Flex
sx={{ sx={{
position: 'absolute', position: 'absolute',
insetInlineEnd: 0, insetInlineEnd: 0,
@ -109,25 +135,33 @@ const NoBoardBoard = memo(({ isSelected }: Props) => {
<Badge variant="solid" sx={BASE_BADGE_STYLES}> <Badge variant="solid" sx={BASE_BADGE_STYLES}>
{totalImages}/{totalAssets} {totalImages}/{totalAssets}
</Badge> </Badge>
</Flex> </Flex> */}
{!autoAddBoardId && <AutoAddIcon />} {!autoAddBoardId && <AutoAddIcon />}
<Box <Flex
className="selection-box"
sx={{ sx={{
position: 'absolute', position: 'absolute',
top: 0,
insetInlineEnd: 0,
bottom: 0, bottom: 0,
insetInlineStart: 0, left: 0,
borderRadius: 'base', p: 1,
transitionProperty: 'common', justifyContent: 'center',
transitionDuration: 'common', alignItems: 'center',
shadow: isSelected ? 'selected.light' : undefined, w: 'full',
maxW: 'full',
borderBottomRadius: 'base',
bg: isSelected ? 'accent.400' : 'base.500',
color: isSelected ? 'base.50' : 'base.100',
_dark: { _dark: {
shadow: isSelected ? 'selected.dark' : undefined, bg: isSelected ? 'accent.500' : 'base.600',
color: isSelected ? 'base.50' : 'base.100',
}, },
lineHeight: 'short',
fontSize: 'xs',
fontWeight: isSelected ? 700 : 500,
}} }}
/> >
{boardName}
</Flex>
<SelectionOverlay isSelected={isSelected} isHovered={isHovered} />
<IAIDroppable <IAIDroppable
data={droppableData} data={droppableData}
dropLabel={<Text fontSize="md">Move</Text>} dropLabel={<Text fontSize="md">Move</Text>}

View File

@ -59,11 +59,11 @@ const GalleryBoardContextMenuItems = ({ board, setBoardToDelete }: Props) => {
</MenuItem> */} </MenuItem> */}
</> </>
)} )}
{!isSelectedForAutoAdd && ( {/* {!isSelectedForAutoAdd && (
<MenuItem icon={<FaPlus />} onClick={handleToggleAutoAdd}> <MenuItem icon={<FaPlus />} onClick={handleToggleAutoAdd}>
Auto-add to this Board Auto-add to this Board
</MenuItem> </MenuItem>
)} )} */}
<MenuItem <MenuItem
sx={{ color: 'error.600', _dark: { color: 'error.300' } }} sx={{ color: 'error.600', _dark: { color: 'error.300' } }}
icon={<FaTrash />} icon={<FaTrash />}

View File

@ -16,11 +16,11 @@ const NoBoardContextMenuItems = () => {
return ( return (
<> <>
{autoAddBoardId && ( {/* {autoAddBoardId && (
<MenuItem icon={<FaPlus />} onClick={handleDisableAutoAdd}> <MenuItem icon={<FaPlus />} onClick={handleDisableAutoAdd}>
Auto-add to this Board Auto-add to this Board
</MenuItem> </MenuItem>
)} )} */}
</> </>
); );
}; };

View File

@ -6,7 +6,6 @@ import { useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import { memo, useMemo } from 'react'; import { memo, useMemo } from 'react';
import { useBoardName } from 'services/api/hooks/useBoardName'; import { useBoardName } from 'services/api/hooks/useBoardName';
import { useBoardTotal } from 'services/api/hooks/useBoardTotal';
const selector = createSelector( const selector = createSelector(
[stateSelector], [stateSelector],
@ -27,24 +26,28 @@ const GalleryBoardName = (props: Props) => {
const { isOpen, onToggle } = props; const { isOpen, onToggle } = props;
const { selectedBoardId } = useAppSelector(selector); const { selectedBoardId } = useAppSelector(selector);
const boardName = useBoardName(selectedBoardId); const boardName = useBoardName(selectedBoardId);
const { totalImages, totalAssets } = useBoardTotal(selectedBoardId); // const { totalImages, totalAssets } = useBoardTotal(selectedBoardId);
const formattedBoardName = useMemo(() => { const formattedBoardName = useMemo(() => {
if (!boardName) {
return '';
}
if (boardName && (totalImages === undefined || totalAssets === undefined)) {
return boardName;
}
const count = `${totalImages}/${totalAssets}`;
if (boardName.length > 20) { if (boardName.length > 20) {
return `${boardName.substring(0, 20)}... (${count})`; return `${boardName.substring(0, 20)}...`;
} }
return `${boardName} (${count})`; return boardName;
}, [boardName, totalAssets, totalImages]); // 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 ( return (
<Flex <Flex

View File

@ -1,5 +1,6 @@
import { import {
Box, Box,
Button,
ButtonGroup, ButtonGroup,
Flex, Flex,
Spacer, Spacer,
@ -92,23 +93,42 @@ const ImageGalleryContent = () => {
> >
<Tabs <Tabs
index={galleryView === 'images' ? 0 : 1} index={galleryView === 'images' ? 0 : 1}
variant="line" variant="unstyled"
size="sm" size="sm"
sx={{ w: 'full' }} sx={{ w: 'full' }}
> >
<TabList> <TabList>
<Tab <ButtonGroup
onClick={handleClickImages} isAttached
sx={{ borderTopRadius: 'base', w: 'full' }} sx={{
w: 'full',
}}
> >
Images <Tab
</Tab> as={IAIButton}
<Tab size="sm"
onClick={handleClickAssets} isChecked={galleryView === 'images'}
sx={{ borderTopRadius: 'base', w: 'full' }} onClick={handleClickImages}
> sx={{
Assets w: 'full',
</Tab> }}
leftIcon={<FaImages />}
>
Images
</Tab>
<Tab
as={IAIButton}
size="sm"
isChecked={galleryView === 'assets'}
onClick={handleClickAssets}
sx={{
w: 'full',
}}
leftIcon={<FaServer />}
>
Assets
</Tab>
</ButtonGroup>
</TabList> </TabList>
</Tabs> </Tabs>
</Flex> </Flex>

View File

@ -106,6 +106,7 @@ const GalleryImage = (props: HoverableImageProps) => {
isDropDisabled={true} isDropDisabled={true}
isUploadDisabled={true} isUploadDisabled={true}
thumbnail={true} thumbnail={true}
withHoverOverlay
// resetIcon={<FaTrash />} // resetIcon={<FaTrash />}
// resetTooltip="Delete image" // resetTooltip="Delete image"
// withResetIcon // removed bc it's too easy to accidentally delete images // withResetIcon // removed bc it's too easy to accidentally delete images

View File

@ -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 { useAppSelector } from 'app/store/storeHooks';
import IAIButton from 'common/components/IAIButton'; import IAIButton from 'common/components/IAIButton';
import { IAINoContentFallback } from 'common/components/IAIImageFallback'; import { IAINoContentFallback } from 'common/components/IAIImageFallback';
import { IMAGE_LIMIT } from 'features/gallery//store/gallerySlice'; import { IMAGE_LIMIT } from 'features/gallery//store/gallerySlice';
import { selectListImagesBaseQueryArgs } from 'features/gallery/store/gallerySelectors';
import { import {
UseOverlayScrollbarsParams, UseOverlayScrollbarsParams,
useOverlayScrollbars, useOverlayScrollbars,
@ -15,11 +16,10 @@ import {
useLazyListImagesQuery, useLazyListImagesQuery,
useListImagesQuery, useListImagesQuery,
} from 'services/api/endpoints/images'; } from 'services/api/endpoints/images';
import { useBoardTotal } from 'services/api/hooks/useBoardTotal';
import GalleryImage from './GalleryImage'; import GalleryImage from './GalleryImage';
import ImageGridItemContainer from './ImageGridItemContainer'; import ImageGridItemContainer from './ImageGridItemContainer';
import ImageGridListContainer from './ImageGridListContainer'; import ImageGridListContainer from './ImageGridListContainer';
import { selectListImagesBaseQueryArgs } from 'features/gallery/store/gallerySelectors';
import { useBoardTotal } from 'services/api/hooks/useBoardTotal';
const overlayScrollbarsConfig: UseOverlayScrollbarsParams = { const overlayScrollbarsConfig: UseOverlayScrollbarsParams = {
defer: true, defer: true,
@ -87,20 +87,34 @@ const GalleryImageGrid = () => {
if (!currentData) { if (!currentData) {
return ( return (
<Box sx={{ w: 'full', h: 'full' }}> <Flex
<Spinner size="2xl" opacity={0.5} /> sx={{
</Box> w: 'full',
h: 'full',
alignItems: 'center',
justifyContent: 'center',
}}
>
<IAINoContentFallback label="Loading..." icon={FaImage} />
</Flex>
); );
} }
if (isSuccess && currentData?.ids.length === 0) { if (isSuccess && currentData?.ids.length === 0) {
return ( return (
<Box sx={{ w: 'full', h: 'full' }}> <Flex
sx={{
w: 'full',
h: 'full',
alignItems: 'center',
justifyContent: 'center',
}}
>
<IAINoContentFallback <IAINoContentFallback
label={t('gallery.noImagesInGallery')} label={t('gallery.noImagesInGallery')}
icon={FaImage} icon={FaImage}
/> />
</Box> </Flex>
); );
} }
@ -129,9 +143,7 @@ const GalleryImageGrid = () => {
loadingText="Loading" loadingText="Loading"
flexShrink={0} flexShrink={0}
> >
{areMoreAvailable {`Load More (${currentData.ids.length} of ${currentViewTotal})`}
? t('gallery.loadMore')
: t('gallery.allImagesLoaded')}
</IAIButton> </IAIButton>
</> </>
); );

View File

@ -78,7 +78,6 @@ const ParametersDrawer = () => {
}} }}
> >
<Flex <Flex
paddingTop={1.5}
paddingBottom={4} paddingBottom={4}
justifyContent="space-between" justifyContent="space-between"
alignItems="center" alignItems="center"

View File

@ -164,7 +164,7 @@ const ResizableDrawer = ({
sx={{ sx={{
borderColor: mode('base.200', 'base.800')(colorMode), borderColor: mode('base.200', 'base.800')(colorMode),
p: 4, p: 4,
bg: mode('base.100', 'base.900')(colorMode), bg: mode('base.50', 'base.900')(colorMode),
height: 'full', height: 'full',
shadow: isOpen ? 'dark-lg' : undefined, shadow: isOpen ? 'dark-lg' : undefined,
...containerStyles, ...containerStyles,

View File

@ -64,9 +64,23 @@ const invokeAI = defineStyle((props) => {
}; };
}); });
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({ export const buttonTheme = defineStyleConfig({
variants: { variants: {
invokeAI, invokeAI,
invokeAIOutline,
}, },
defaultProps: { defaultProps: {
variant: 'invokeAI', variant: 'invokeAI',

View File

@ -78,12 +78,12 @@ export const theme: ThemeOverride = {
hoverSelected: { hoverSelected: {
light: light:
'0px 0px 0px 1px var(--invokeai-colors-base-150), 0px 0px 0px 4px var(--invokeai-colors-accent-500)', '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: { hoverUnselected: {
light: light:
'0px 0px 0px 1px var(--invokeai-colors-base-150), 0px 0px 0px 4px var(--invokeai-colors-accent-200)', '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 4px var(--invokeai-colors-accent-600)', 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)`, nodeSelectedOutline: `0 0 0 2px var(--invokeai-colors-accent-450)`,
}, },