mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
feat(ui): board styles
This commit is contained in:
parent
c2c99b8650
commit
68f1f87c6f
@ -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) {
|
||||||
|
@ -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}
|
||||||
|
@ -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 && (
|
||||||
|
@ -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;
|
@ -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,
|
||||||
}}
|
}}
|
||||||
|
@ -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>
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
@ -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,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
@ -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={{
|
||||||
|
@ -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>}
|
||||||
|
@ -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 />}
|
||||||
|
@ -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>
|
||||||
)}
|
)} */}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -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
|
||||||
|
@ -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>
|
||||||
|
@ -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
|
||||||
|
@ -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>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -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"
|
||||||
|
@ -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,
|
||||||
|
@ -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',
|
||||||
|
@ -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)`,
|
||||||
},
|
},
|
||||||
|
Loading…
Reference in New Issue
Block a user