mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
update IAIDndImage to use children for icons, add UI for shift+delete to delete images from gallery
This commit is contained in:
parent
767a612746
commit
a512fdc0f6
@ -1,22 +1,10 @@
|
|||||||
import {
|
import { ChakraProps, Flex, Icon, Image, useColorMode } from '@chakra-ui/react';
|
||||||
ChakraProps,
|
|
||||||
Flex,
|
|
||||||
Icon,
|
|
||||||
Image,
|
|
||||||
useColorMode,
|
|
||||||
useColorModeValue,
|
|
||||||
} from '@chakra-ui/react';
|
|
||||||
import IAIIconButton from 'common/components/IAIIconButton';
|
|
||||||
import {
|
import {
|
||||||
IAILoadingImageFallback,
|
IAILoadingImageFallback,
|
||||||
IAINoContentFallback,
|
IAINoContentFallback,
|
||||||
} from 'common/components/IAIImageFallback';
|
} from 'common/components/IAIImageFallback';
|
||||||
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 {
|
|
||||||
TypesafeDraggableData,
|
|
||||||
TypesafeDroppableData,
|
|
||||||
} from 'features/dnd/types';
|
|
||||||
import ImageContextMenu from 'features/gallery/components/ImageContextMenu/ImageContextMenu';
|
import ImageContextMenu from 'features/gallery/components/ImageContextMenu/ImageContextMenu';
|
||||||
import {
|
import {
|
||||||
MouseEvent,
|
MouseEvent,
|
||||||
@ -26,22 +14,22 @@ import {
|
|||||||
useCallback,
|
useCallback,
|
||||||
useState,
|
useState,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
import { FaImage, FaUndo, FaUpload } from 'react-icons/fa';
|
import { FaImage, 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';
|
import SelectionOverlay from './SelectionOverlay';
|
||||||
|
import {
|
||||||
|
TypesafeDraggableData,
|
||||||
|
TypesafeDroppableData,
|
||||||
|
} from 'features/dnd/types';
|
||||||
|
|
||||||
type IAIDndImageProps = {
|
type IAIDndImageProps = {
|
||||||
imageDTO: ImageDTO | undefined;
|
imageDTO: ImageDTO | undefined;
|
||||||
onError?: (event: SyntheticEvent<HTMLImageElement>) => void;
|
onError?: (event: SyntheticEvent<HTMLImageElement>) => void;
|
||||||
onLoad?: (event: SyntheticEvent<HTMLImageElement>) => void;
|
onLoad?: (event: SyntheticEvent<HTMLImageElement>) => void;
|
||||||
onClick?: (event: MouseEvent<HTMLDivElement>) => void;
|
onClick?: (event: MouseEvent<HTMLDivElement>) => void;
|
||||||
onClickReset?: (event: MouseEvent<HTMLButtonElement>) => void;
|
|
||||||
withResetIcon?: boolean;
|
|
||||||
resetIcon?: ReactElement;
|
|
||||||
resetTooltip?: string;
|
|
||||||
withMetadataOverlay?: boolean;
|
withMetadataOverlay?: boolean;
|
||||||
isDragDisabled?: boolean;
|
isDragDisabled?: boolean;
|
||||||
isDropDisabled?: boolean;
|
isDropDisabled?: boolean;
|
||||||
@ -58,15 +46,16 @@ type IAIDndImageProps = {
|
|||||||
noContentFallback?: ReactElement;
|
noContentFallback?: ReactElement;
|
||||||
useThumbailFallback?: boolean;
|
useThumbailFallback?: boolean;
|
||||||
withHoverOverlay?: boolean;
|
withHoverOverlay?: boolean;
|
||||||
|
children?: JSX.Element;
|
||||||
|
onMouseOver?: () => void;
|
||||||
|
onMouseOut?: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const IAIDndImage = (props: IAIDndImageProps) => {
|
const IAIDndImage = (props: IAIDndImageProps) => {
|
||||||
const {
|
const {
|
||||||
imageDTO,
|
imageDTO,
|
||||||
onClickReset,
|
|
||||||
onError,
|
onError,
|
||||||
onClick,
|
onClick,
|
||||||
withResetIcon = false,
|
|
||||||
withMetadataOverlay = false,
|
withMetadataOverlay = false,
|
||||||
isDropDisabled = false,
|
isDropDisabled = false,
|
||||||
isDragDisabled = false,
|
isDragDisabled = false,
|
||||||
@ -80,32 +69,30 @@ const IAIDndImage = (props: IAIDndImageProps) => {
|
|||||||
dropLabel,
|
dropLabel,
|
||||||
isSelected = false,
|
isSelected = false,
|
||||||
thumbnail = false,
|
thumbnail = false,
|
||||||
resetTooltip = 'Reset',
|
|
||||||
resetIcon = <FaUndo />,
|
|
||||||
noContentFallback = <IAINoContentFallback icon={FaImage} />,
|
noContentFallback = <IAINoContentFallback icon={FaImage} />,
|
||||||
useThumbailFallback,
|
useThumbailFallback,
|
||||||
withHoverOverlay = false,
|
withHoverOverlay = false,
|
||||||
|
children,
|
||||||
|
onMouseOver,
|
||||||
|
onMouseOut,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const { colorMode } = useColorMode();
|
const { colorMode } = useColorMode();
|
||||||
const [isHovered, setIsHovered] = useState(false);
|
const [isHovered, setIsHovered] = useState(false);
|
||||||
const handleMouseOver = useCallback(() => {
|
const handleMouseOver = useCallback(() => {
|
||||||
|
if (onMouseOver) onMouseOver();
|
||||||
setIsHovered(true);
|
setIsHovered(true);
|
||||||
}, []);
|
}, [onMouseOver]);
|
||||||
const handleMouseOut = useCallback(() => {
|
const handleMouseOut = useCallback(() => {
|
||||||
|
if (onMouseOut) onMouseOut();
|
||||||
setIsHovered(false);
|
setIsHovered(false);
|
||||||
}, []);
|
}, [onMouseOut]);
|
||||||
|
|
||||||
const { getUploadButtonProps, getUploadInputProps } = useImageUploadButton({
|
const { getUploadButtonProps, getUploadInputProps } = useImageUploadButton({
|
||||||
postUploadAction,
|
postUploadAction,
|
||||||
isDisabled: isUploadDisabled,
|
isDisabled: isUploadDisabled,
|
||||||
});
|
});
|
||||||
|
|
||||||
const resetIconShadow = useColorModeValue(
|
|
||||||
`drop-shadow(0px 0px 0.1rem var(--invokeai-colors-base-600))`,
|
|
||||||
`drop-shadow(0px 0px 0.1rem var(--invokeai-colors-base-800))`
|
|
||||||
);
|
|
||||||
|
|
||||||
const uploadButtonStyles = isUploadDisabled
|
const uploadButtonStyles = isUploadDisabled
|
||||||
? {}
|
? {}
|
||||||
: {
|
: {
|
||||||
@ -212,30 +199,6 @@ const IAIDndImage = (props: IAIDndImageProps) => {
|
|||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{onClickReset && withResetIcon && imageDTO && (
|
|
||||||
<IAIIconButton
|
|
||||||
onClick={onClickReset}
|
|
||||||
aria-label={resetTooltip}
|
|
||||||
tooltip={resetTooltip}
|
|
||||||
icon={resetIcon}
|
|
||||||
size="sm"
|
|
||||||
variant="link"
|
|
||||||
sx={{
|
|
||||||
position: 'absolute',
|
|
||||||
top: 1,
|
|
||||||
insetInlineEnd: 1,
|
|
||||||
p: 0,
|
|
||||||
minW: 0,
|
|
||||||
svg: {
|
|
||||||
transitionProperty: 'common',
|
|
||||||
transitionDuration: 'normal',
|
|
||||||
fill: 'base.100',
|
|
||||||
_hover: { fill: 'base.50' },
|
|
||||||
filter: resetIconShadow,
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{!isDropDisabled && (
|
{!isDropDisabled && (
|
||||||
<IAIDroppable
|
<IAIDroppable
|
||||||
data={droppableData}
|
data={droppableData}
|
||||||
@ -243,6 +206,7 @@ const IAIDndImage = (props: IAIDndImageProps) => {
|
|||||||
dropLabel={dropLabel}
|
dropLabel={dropLabel}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
{children}
|
||||||
</Flex>
|
</Flex>
|
||||||
)}
|
)}
|
||||||
</ImageContextMenu>
|
</ImageContextMenu>
|
||||||
|
@ -0,0 +1,46 @@
|
|||||||
|
import { JSXElementConstructor, ReactElement, memo, MouseEvent } from 'react';
|
||||||
|
import IAIIconButton from './IAIIconButton';
|
||||||
|
import { SystemStyleObject, useColorModeValue } from '@chakra-ui/react';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
onClick: (event: MouseEvent<HTMLButtonElement>) => void;
|
||||||
|
tooltip: string;
|
||||||
|
icon?: ReactElement<any, string | JSXElementConstructor<any>>;
|
||||||
|
styleOverrides?: SystemStyleObject;
|
||||||
|
};
|
||||||
|
|
||||||
|
const IAIDndImageIcon = (props: Props) => {
|
||||||
|
const { onClick, tooltip, icon, styleOverrides } = props;
|
||||||
|
|
||||||
|
const resetIconShadow = useColorModeValue(
|
||||||
|
`drop-shadow(0px 0px 0.1rem var(--invokeai-colors-base-600))`,
|
||||||
|
`drop-shadow(0px 0px 0.1rem var(--invokeai-colors-base-800))`
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<IAIIconButton
|
||||||
|
onClick={onClick}
|
||||||
|
aria-label={tooltip}
|
||||||
|
tooltip={tooltip}
|
||||||
|
icon={icon}
|
||||||
|
size="sm"
|
||||||
|
variant="link"
|
||||||
|
sx={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: 1,
|
||||||
|
insetInlineEnd: 1,
|
||||||
|
p: 0,
|
||||||
|
minW: 0,
|
||||||
|
svg: {
|
||||||
|
transitionProperty: 'common',
|
||||||
|
transitionDuration: 'normal',
|
||||||
|
fill: 'base.100',
|
||||||
|
_hover: { fill: 'base.50' },
|
||||||
|
filter: resetIconShadow,
|
||||||
|
},
|
||||||
|
...styleOverrides,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(IAIDndImageIcon);
|
@ -1,4 +1,10 @@
|
|||||||
import { Box, Flex, Spinner, SystemStyleObject } from '@chakra-ui/react';
|
import {
|
||||||
|
Box,
|
||||||
|
Flex,
|
||||||
|
Spinner,
|
||||||
|
SystemStyleObject,
|
||||||
|
useColorModeValue,
|
||||||
|
} from '@chakra-ui/react';
|
||||||
import { createSelector } from '@reduxjs/toolkit';
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { skipToken } from '@reduxjs/toolkit/dist/query';
|
import { skipToken } from '@reduxjs/toolkit/dist/query';
|
||||||
import {
|
import {
|
||||||
@ -16,6 +22,8 @@ import {
|
|||||||
ControlNetConfig,
|
ControlNetConfig,
|
||||||
controlNetImageChanged,
|
controlNetImageChanged,
|
||||||
} from '../store/controlNetSlice';
|
} from '../store/controlNetSlice';
|
||||||
|
import { FaUndo } from 'react-icons/fa';
|
||||||
|
import IAIDndImageIcon from '../../../common/components/IAIDndImageIcon';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
controlNet: ControlNetConfig;
|
controlNet: ControlNetConfig;
|
||||||
@ -93,6 +101,11 @@ const ControlNetImagePreview = (props: Props) => {
|
|||||||
[controlNetId]
|
[controlNetId]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const resetIconShadow = useColorModeValue(
|
||||||
|
`drop-shadow(0px 0px 0.1rem var(--invokeai-colors-base-600))`,
|
||||||
|
`drop-shadow(0px 0px 0.1rem var(--invokeai-colors-base-800))`
|
||||||
|
);
|
||||||
|
|
||||||
const shouldShowProcessedImage =
|
const shouldShowProcessedImage =
|
||||||
controlImage &&
|
controlImage &&
|
||||||
processedControlImage &&
|
processedControlImage &&
|
||||||
@ -119,11 +132,15 @@ const ControlNetImagePreview = (props: Props) => {
|
|||||||
droppableData={droppableData}
|
droppableData={droppableData}
|
||||||
imageDTO={controlImage}
|
imageDTO={controlImage}
|
||||||
isDropDisabled={shouldShowProcessedImage || !isEnabled}
|
isDropDisabled={shouldShowProcessedImage || !isEnabled}
|
||||||
onClickReset={handleResetControlImage}
|
|
||||||
postUploadAction={postUploadAction}
|
postUploadAction={postUploadAction}
|
||||||
resetTooltip="Reset Control Image"
|
>
|
||||||
withResetIcon={Boolean(controlImage)}
|
<IAIDndImageIcon
|
||||||
/>
|
onClick={handleResetControlImage}
|
||||||
|
icon={controlImage ? <FaUndo /> : undefined}
|
||||||
|
tooltip="Reset Control Image"
|
||||||
|
/>
|
||||||
|
</IAIDndImage>
|
||||||
|
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
@ -143,10 +160,13 @@ const ControlNetImagePreview = (props: Props) => {
|
|||||||
imageDTO={processedControlImage}
|
imageDTO={processedControlImage}
|
||||||
isUploadDisabled={true}
|
isUploadDisabled={true}
|
||||||
isDropDisabled={!isEnabled}
|
isDropDisabled={!isEnabled}
|
||||||
onClickReset={handleResetControlImage}
|
>
|
||||||
resetTooltip="Reset Control Image"
|
<IAIDndImageIcon
|
||||||
withResetIcon={Boolean(controlImage)}
|
onClick={handleResetControlImage}
|
||||||
/>
|
icon={controlImage ? <FaUndo /> : undefined}
|
||||||
|
tooltip="Reset Control Image"
|
||||||
|
/>
|
||||||
|
</IAIDndImage>
|
||||||
</Box>
|
</Box>
|
||||||
{pendingControlImages.includes(controlNetId) && (
|
{pendingControlImages.includes(controlNetId) && (
|
||||||
<Flex
|
<Flex
|
||||||
|
@ -11,7 +11,6 @@ import {
|
|||||||
autoAssignBoardOnClickChanged,
|
autoAssignBoardOnClickChanged,
|
||||||
setGalleryImageMinimumWidth,
|
setGalleryImageMinimumWidth,
|
||||||
shouldAutoSwitchChanged,
|
shouldAutoSwitchChanged,
|
||||||
shouldShowDeleteButtonChanged,
|
|
||||||
} from 'features/gallery/store/gallerySlice';
|
} from 'features/gallery/store/gallerySlice';
|
||||||
import { ChangeEvent, useCallback } from 'react';
|
import { ChangeEvent, useCallback } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@ -26,14 +25,12 @@ const selector = createSelector(
|
|||||||
galleryImageMinimumWidth,
|
galleryImageMinimumWidth,
|
||||||
shouldAutoSwitch,
|
shouldAutoSwitch,
|
||||||
autoAssignBoardOnClick,
|
autoAssignBoardOnClick,
|
||||||
shouldShowDeleteButton,
|
|
||||||
} = state.gallery;
|
} = state.gallery;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
galleryImageMinimumWidth,
|
galleryImageMinimumWidth,
|
||||||
shouldAutoSwitch,
|
shouldAutoSwitch,
|
||||||
autoAssignBoardOnClick,
|
autoAssignBoardOnClick,
|
||||||
shouldShowDeleteButton,
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
defaultSelectorOptions
|
defaultSelectorOptions
|
||||||
@ -43,12 +40,8 @@ const GallerySettingsPopover = () => {
|
|||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const {
|
const { galleryImageMinimumWidth, shouldAutoSwitch, autoAssignBoardOnClick } =
|
||||||
galleryImageMinimumWidth,
|
useAppSelector(selector);
|
||||||
shouldAutoSwitch,
|
|
||||||
autoAssignBoardOnClick,
|
|
||||||
shouldShowDeleteButton,
|
|
||||||
} = useAppSelector(selector);
|
|
||||||
|
|
||||||
const handleChangeGalleryImageMinimumWidth = useCallback(
|
const handleChangeGalleryImageMinimumWidth = useCallback(
|
||||||
(v: number) => {
|
(v: number) => {
|
||||||
@ -68,13 +61,6 @@ const GallerySettingsPopover = () => {
|
|||||||
[dispatch]
|
[dispatch]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleChangeShowDeleteButton = useCallback(
|
|
||||||
(e: ChangeEvent<HTMLInputElement>) => {
|
|
||||||
dispatch(shouldShowDeleteButtonChanged(e.target.checked));
|
|
||||||
},
|
|
||||||
[dispatch]
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<IAIPopover
|
<IAIPopover
|
||||||
triggerComponent={
|
triggerComponent={
|
||||||
@ -90,7 +76,7 @@ const GallerySettingsPopover = () => {
|
|||||||
<IAISlider
|
<IAISlider
|
||||||
value={galleryImageMinimumWidth}
|
value={galleryImageMinimumWidth}
|
||||||
onChange={handleChangeGalleryImageMinimumWidth}
|
onChange={handleChangeGalleryImageMinimumWidth}
|
||||||
min={32}
|
min={45}
|
||||||
max={256}
|
max={256}
|
||||||
hideTooltip={true}
|
hideTooltip={true}
|
||||||
label={t('gallery.galleryImageSize')}
|
label={t('gallery.galleryImageSize')}
|
||||||
@ -102,11 +88,6 @@ const GallerySettingsPopover = () => {
|
|||||||
isChecked={shouldAutoSwitch}
|
isChecked={shouldAutoSwitch}
|
||||||
onChange={handleChangeAutoSwitch}
|
onChange={handleChangeAutoSwitch}
|
||||||
/>
|
/>
|
||||||
<IAISwitch
|
|
||||||
label="Show Delete Button"
|
|
||||||
isChecked={shouldShowDeleteButton}
|
|
||||||
onChange={handleChangeShowDeleteButton}
|
|
||||||
/>
|
|
||||||
<IAISimpleCheckbox
|
<IAISimpleCheckbox
|
||||||
label={t('gallery.autoAssignBoardOnClick')}
|
label={t('gallery.autoAssignBoardOnClick')}
|
||||||
isChecked={autoAssignBoardOnClick}
|
isChecked={autoAssignBoardOnClick}
|
||||||
|
@ -36,7 +36,7 @@ import {
|
|||||||
import { ImageDTO } from 'services/api/types';
|
import { ImageDTO } from 'services/api/types';
|
||||||
import { useDebounce } from 'use-debounce';
|
import { useDebounce } from 'use-debounce';
|
||||||
import { sentImageToCanvas, sentImageToImg2Img } from '../../store/actions';
|
import { sentImageToCanvas, sentImageToImg2Img } from '../../store/actions';
|
||||||
import { BsBookmarkStar, BsFillBookmarkStarFill } from 'react-icons/bs';
|
import { MdStar, MdStarBorder } from 'react-icons/md';
|
||||||
|
|
||||||
type SingleSelectionMenuItemsProps = {
|
type SingleSelectionMenuItemsProps = {
|
||||||
imageDTO: ImageDTO;
|
imageDTO: ImageDTO;
|
||||||
@ -211,15 +211,12 @@ const SingleSelectionMenuItems = (props: SingleSelectionMenuItemsProps) => {
|
|||||||
Change Board
|
Change Board
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
{imageDTO.pinned ? (
|
{imageDTO.pinned ? (
|
||||||
<MenuItem icon={<BsBookmarkStar />} onClickCapture={handleUnpinImage}>
|
<MenuItem icon={<MdStar />} onClickCapture={handleUnpinImage}>
|
||||||
Unpin Image
|
Unstar Image
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
) : (
|
) : (
|
||||||
<MenuItem
|
<MenuItem icon={<MdStarBorder />} onClickCapture={handlePinImage}>
|
||||||
icon={<BsFillBookmarkStarFill />}
|
Star Image
|
||||||
onClickCapture={handlePinImage}
|
|
||||||
>
|
|
||||||
Pin Image
|
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
)}
|
)}
|
||||||
<MenuItem
|
<MenuItem
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Box, Flex } from '@chakra-ui/react';
|
import { Box, Flex, useColorModeValue } from '@chakra-ui/react';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import IAIDndImage from 'common/components/IAIDndImage';
|
import IAIDndImage from 'common/components/IAIDndImage';
|
||||||
import IAIFillSkeleton from 'common/components/IAIFillSkeleton';
|
import IAIFillSkeleton from 'common/components/IAIFillSkeleton';
|
||||||
@ -9,12 +9,14 @@ import {
|
|||||||
TypesafeDraggableData,
|
TypesafeDraggableData,
|
||||||
} from 'features/dnd/types';
|
} from 'features/dnd/types';
|
||||||
import { useMultiselect } from 'features/gallery/hooks/useMultiselect.ts';
|
import { useMultiselect } from 'features/gallery/hooks/useMultiselect.ts';
|
||||||
import { MouseEvent, memo, useCallback, useMemo } from 'react';
|
import { MouseEvent, memo, useCallback, useMemo, useState } from 'react';
|
||||||
import { BsBookmarkStar, BsFillBookmarkStarFill } from 'react-icons/bs';
|
import { FaTrash } from 'react-icons/fa';
|
||||||
|
import { MdStar, MdStarBorder } from 'react-icons/md';
|
||||||
import {
|
import {
|
||||||
useChangeImagePinnedMutation,
|
useChangeImagePinnedMutation,
|
||||||
useGetImageDTOQuery,
|
useGetImageDTOQuery,
|
||||||
} from 'services/api/endpoints/images';
|
} from 'services/api/endpoints/images';
|
||||||
|
import IAIDndImageIcon from '../../../../common/components/IAIDndImageIcon';
|
||||||
|
|
||||||
interface HoverableImageProps {
|
interface HoverableImageProps {
|
||||||
imageName: string;
|
imageName: string;
|
||||||
@ -70,6 +72,33 @@ const GalleryImage = (props: HoverableImageProps) => {
|
|||||||
}
|
}
|
||||||
}, [togglePin, imageDTO]);
|
}, [togglePin, imageDTO]);
|
||||||
|
|
||||||
|
const [isHovered, setIsHovered] = useState(false);
|
||||||
|
|
||||||
|
const pinIcon = useMemo(() => {
|
||||||
|
if (imageDTO?.pinned) return <MdStar size="20" />;
|
||||||
|
if (!imageDTO?.pinned && isHovered) return <MdStarBorder size="20" />;
|
||||||
|
}, [imageDTO?.pinned, isHovered]);
|
||||||
|
|
||||||
|
const resetIconShadow = useColorModeValue(
|
||||||
|
`drop-shadow(0px 0px 0.1rem var(--invokeai-colors-base-600))`,
|
||||||
|
`drop-shadow(0px 0px 0.1rem var(--invokeai-colors-base-800))`
|
||||||
|
);
|
||||||
|
|
||||||
|
const iconButtonStyles = {
|
||||||
|
position: 'absolute',
|
||||||
|
top: 1,
|
||||||
|
insetInlineEnd: 1,
|
||||||
|
p: 0,
|
||||||
|
minW: 0,
|
||||||
|
svg: {
|
||||||
|
transitionProperty: 'common',
|
||||||
|
transitionDuration: 'normal',
|
||||||
|
fill: 'base.100',
|
||||||
|
_hover: { fill: 'base.50' },
|
||||||
|
filter: resetIconShadow,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
if (!imageDTO) {
|
if (!imageDTO) {
|
||||||
return <IAIFillSkeleton />;
|
return <IAIFillSkeleton />;
|
||||||
}
|
}
|
||||||
@ -91,18 +120,34 @@ const GalleryImage = (props: HoverableImageProps) => {
|
|||||||
draggableData={draggableData}
|
draggableData={draggableData}
|
||||||
isSelected={isSelected}
|
isSelected={isSelected}
|
||||||
minSize={0}
|
minSize={0}
|
||||||
onClickReset={togglePinnedState}
|
|
||||||
imageSx={{ w: 'full', h: 'full' }}
|
imageSx={{ w: 'full', h: 'full' }}
|
||||||
isDropDisabled={true}
|
isDropDisabled={true}
|
||||||
isUploadDisabled={true}
|
isUploadDisabled={true}
|
||||||
thumbnail={true}
|
thumbnail={true}
|
||||||
withHoverOverlay
|
withHoverOverlay
|
||||||
resetIcon={
|
onMouseOver={() => setIsHovered(true)}
|
||||||
imageDTO.pinned ? <BsFillBookmarkStarFill /> : <BsBookmarkStar />
|
onMouseOut={() => setIsHovered(false)}
|
||||||
}
|
>
|
||||||
resetTooltip="Pin image"
|
<>
|
||||||
withResetIcon={true}
|
<IAIDndImageIcon
|
||||||
/>
|
onClick={togglePinnedState}
|
||||||
|
icon={pinIcon}
|
||||||
|
tooltip={imageDTO.pinned ? 'Unstar' : 'Star'}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{isHovered && shouldShowDeleteButton && (
|
||||||
|
<IAIDndImageIcon
|
||||||
|
onClick={handleDelete}
|
||||||
|
icon={<FaTrash />}
|
||||||
|
tooltip={'Delete'}
|
||||||
|
styleOverrides={{
|
||||||
|
bottom: 1,
|
||||||
|
top: 'auto',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
</IAIDndImage>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { Box, Flex } from '@chakra-ui/react';
|
import { Box, Flex } from '@chakra-ui/react';
|
||||||
import { useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, 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 { selectListImagesBaseQueryArgs } from 'features/gallery/store/gallerySelectors';
|
import { selectListImagesBaseQueryArgs } from 'features/gallery/store/gallerySelectors';
|
||||||
@ -20,6 +20,8 @@ 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 { useHotkeys } from 'react-hotkeys-hook';
|
||||||
|
import { shouldShowDeleteButtonChanged } from '../../store/gallerySlice';
|
||||||
|
|
||||||
const overlayScrollbarsConfig: UseOverlayScrollbarsParams = {
|
const overlayScrollbarsConfig: UseOverlayScrollbarsParams = {
|
||||||
defer: true,
|
defer: true,
|
||||||
@ -36,6 +38,7 @@ const overlayScrollbarsConfig: UseOverlayScrollbarsParams = {
|
|||||||
|
|
||||||
const GalleryImageGrid = () => {
|
const GalleryImageGrid = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
const rootRef = useRef<HTMLDivElement>(null);
|
const rootRef = useRef<HTMLDivElement>(null);
|
||||||
const [scroller, setScroller] = useState<HTMLElement | null>(null);
|
const [scroller, setScroller] = useState<HTMLElement | null>(null);
|
||||||
const [initialize, osInstance] = useOverlayScrollbars(
|
const [initialize, osInstance] = useOverlayScrollbars(
|
||||||
@ -85,6 +88,23 @@ const GalleryImageGrid = () => {
|
|||||||
return () => osInstance()?.destroy();
|
return () => osInstance()?.destroy();
|
||||||
}, [scroller, initialize, osInstance]);
|
}, [scroller, initialize, osInstance]);
|
||||||
|
|
||||||
|
useHotkeys(
|
||||||
|
'shift',
|
||||||
|
() => {
|
||||||
|
dispatch(shouldShowDeleteButtonChanged(true));
|
||||||
|
},
|
||||||
|
[shouldShowDeleteButtonChanged]
|
||||||
|
);
|
||||||
|
|
||||||
|
useHotkeys(
|
||||||
|
'shift',
|
||||||
|
() => {
|
||||||
|
dispatch(shouldShowDeleteButtonChanged(false));
|
||||||
|
},
|
||||||
|
{ keyup: true },
|
||||||
|
[shouldShowDeleteButtonChanged]
|
||||||
|
);
|
||||||
|
|
||||||
if (!currentData) {
|
if (!currentData) {
|
||||||
return (
|
return (
|
||||||
<Flex
|
<Flex
|
||||||
|
@ -78,11 +78,8 @@ const ImageInputFieldComponent = (
|
|||||||
imageDTO={imageDTO}
|
imageDTO={imageDTO}
|
||||||
droppableData={droppableData}
|
droppableData={droppableData}
|
||||||
draggableData={draggableData}
|
draggableData={draggableData}
|
||||||
onClickReset={handleReset}
|
|
||||||
withResetIcon
|
|
||||||
thumbnail
|
|
||||||
useThumbailFallback
|
|
||||||
postUploadAction={postUploadAction}
|
postUploadAction={postUploadAction}
|
||||||
|
useThumbailFallback
|
||||||
/>
|
/>
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
|
Loading…
Reference in New Issue
Block a user