mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
feat(ui): improve drag and drop ux
This commit is contained in:
parent
b1e1e3efc7
commit
fa4d88e163
@ -3,6 +3,11 @@ import {
|
||||
DragEndEvent,
|
||||
DragOverlay,
|
||||
DragStartEvent,
|
||||
KeyboardSensor,
|
||||
MouseSensor,
|
||||
TouchSensor,
|
||||
useSensor,
|
||||
useSensors,
|
||||
} from '@dnd-kit/core';
|
||||
import { PropsWithChildren, memo, useCallback, useState } from 'react';
|
||||
import OverlayDragImage from './OverlayDragImage';
|
||||
@ -32,8 +37,23 @@ const ImageDndContext = (props: ImageDndContextProps) => {
|
||||
[draggedImage]
|
||||
);
|
||||
|
||||
const mouseSensor = useSensor(MouseSensor, {
|
||||
activationConstraint: { distance: 15 },
|
||||
});
|
||||
|
||||
const touchSensor = useSensor(TouchSensor, {
|
||||
activationConstraint: { distance: 15 },
|
||||
});
|
||||
const keyboardSensor = useSensor(KeyboardSensor);
|
||||
|
||||
const sensors = useSensors(mouseSensor, touchSensor, keyboardSensor);
|
||||
|
||||
return (
|
||||
<DndContext onDragStart={handleDragStart} onDragEnd={handleDragEnd}>
|
||||
<DndContext
|
||||
onDragStart={handleDragStart}
|
||||
onDragEnd={handleDragEnd}
|
||||
sensors={sensors}
|
||||
>
|
||||
{props.children}
|
||||
<DragOverlay dropAnimation={null}>
|
||||
{draggedImage && <OverlayDragImage image={draggedImage} />}
|
||||
|
@ -10,8 +10,8 @@ const OverlayDragImage = (props: OverlayDragImageProps) => {
|
||||
return (
|
||||
<Image
|
||||
sx={{
|
||||
maxW: 32,
|
||||
maxH: 32,
|
||||
maxW: 36,
|
||||
maxH: 36,
|
||||
borderRadius: 'base',
|
||||
shadow: 'dark-lg',
|
||||
}}
|
||||
|
@ -1,42 +0,0 @@
|
||||
import { ButtonGroup, Flex, Spacer, Text } from '@chakra-ui/react';
|
||||
import IAIIconButton from 'common/components/IAIIconButton';
|
||||
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { FaUndo, FaUpload } from 'react-icons/fa';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import { useCallback } from 'react';
|
||||
import { clearInitialImage } from 'features/parameters/store/generationSlice';
|
||||
import useImageUploader from 'common/hooks/useImageUploader';
|
||||
|
||||
const InitialImageButtons = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const { t } = useTranslation();
|
||||
const { openUploader } = useImageUploader();
|
||||
|
||||
const handleResetInitialImage = useCallback(() => {
|
||||
dispatch(clearInitialImage());
|
||||
}, [dispatch]);
|
||||
|
||||
return (
|
||||
<Flex w="full" alignItems="center">
|
||||
<Text size="sm" fontWeight={500} color="base.300">
|
||||
{t('parameters.initialImage')}
|
||||
</Text>
|
||||
<Spacer />
|
||||
<ButtonGroup>
|
||||
<IAIIconButton
|
||||
icon={<FaUndo />}
|
||||
aria-label={t('accessibility.reset')}
|
||||
onClick={handleResetInitialImage}
|
||||
/>
|
||||
<IAIIconButton
|
||||
icon={<FaUpload />}
|
||||
onClick={openUploader}
|
||||
aria-label={t('common.upload')}
|
||||
/>
|
||||
</ButtonGroup>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default InitialImageButtons;
|
@ -14,7 +14,7 @@ import { useGetUrl } from 'common/util/getUrl';
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
import { SyntheticEvent } from 'react';
|
||||
import { memo, useRef } from 'react';
|
||||
import { FaImage, FaUndo } from 'react-icons/fa';
|
||||
import { FaImage, FaTimes } from 'react-icons/fa';
|
||||
import { ImageDTO } from 'services/api';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
@ -53,9 +53,8 @@ const IAISelectableImage = (props: IAISelectableImageProps) => {
|
||||
{image && (
|
||||
<Flex
|
||||
sx={{
|
||||
position: 'relative',
|
||||
w: 'full',
|
||||
h: 'full',
|
||||
position: 'relative',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
@ -82,7 +81,7 @@ const IAISelectableImage = (props: IAISelectableImageProps) => {
|
||||
<IAIIconButton
|
||||
size={resetIconSize}
|
||||
aria-label="Reset Image"
|
||||
icon={<FaUndo />}
|
||||
icon={<FaTimes />}
|
||||
onClick={onReset}
|
||||
/>
|
||||
</Box>
|
||||
@ -184,7 +183,7 @@ const DropOverlay = (props: DropOverlayProps) => {
|
||||
transitionDuration: '0.15s',
|
||||
}}
|
||||
>
|
||||
<Text sx={{ fontSize: '2xl', fontWeight: 600, color: 'base.50' }}>
|
||||
<Text sx={{ fontSize: '2xl', fontWeight: 600, color: 'base.200' }}>
|
||||
Drop Image
|
||||
</Text>
|
||||
</Flex>
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Flex, Icon } from '@chakra-ui/react';
|
||||
import { Box, Flex, Icon } from '@chakra-ui/react';
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { systemSelector } from 'features/system/store/systemSelectors';
|
||||
@ -55,10 +55,7 @@ const CurrentImageDisplay = () => {
|
||||
}}
|
||||
>
|
||||
{hasAnImageToDisplay ? (
|
||||
<>
|
||||
<CurrentImageButtons />
|
||||
<CurrentImagePreview />
|
||||
</>
|
||||
<CurrentImagePreview />
|
||||
) : (
|
||||
<Icon
|
||||
as={FaImage}
|
||||
@ -69,6 +66,11 @@ const CurrentImageDisplay = () => {
|
||||
/>
|
||||
)}
|
||||
</Flex>
|
||||
{hasAnImageToDisplay && (
|
||||
<Box sx={{ position: 'absolute', top: 0 }}>
|
||||
<CurrentImageButtons />
|
||||
</Box>
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
@ -15,6 +15,7 @@ import ImageMetadataOverlay from 'common/components/ImageMetadataOverlay';
|
||||
import { configSelector } from '../../system/store/configSelectors';
|
||||
import { useAppToaster } from 'app/components/Toaster';
|
||||
import { imageSelected } from '../store/gallerySlice';
|
||||
import { useDraggable } from '@dnd-kit/core';
|
||||
|
||||
export const imagesSelector = createSelector(
|
||||
[uiSelector, gallerySelector, systemSelector],
|
||||
@ -46,7 +47,6 @@ const CurrentImagePreview = () => {
|
||||
const {
|
||||
shouldShowImageDetails,
|
||||
image,
|
||||
shouldHidePreview,
|
||||
progressImage,
|
||||
shouldShowProgressInViewer,
|
||||
shouldAntialiasProgressImage,
|
||||
@ -56,16 +56,12 @@ const CurrentImagePreview = () => {
|
||||
const toaster = useAppToaster();
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const handleDragStart = useCallback(
|
||||
(e: DragEvent<HTMLDivElement>) => {
|
||||
if (!image) {
|
||||
return;
|
||||
}
|
||||
e.dataTransfer.setData('invokeai/imageName', image.image_name);
|
||||
e.dataTransfer.effectAllowed = 'move';
|
||||
const { attributes, listeners, setNodeRef } = useDraggable({
|
||||
id: `currentImage_${image?.image_name}`,
|
||||
data: {
|
||||
image,
|
||||
},
|
||||
[image]
|
||||
);
|
||||
});
|
||||
|
||||
const handleError = useCallback(() => {
|
||||
dispatch(imageSelected());
|
||||
@ -105,24 +101,32 @@ const CurrentImagePreview = () => {
|
||||
/>
|
||||
) : (
|
||||
image && (
|
||||
<>
|
||||
<Flex
|
||||
sx={{
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
position: 'absolute',
|
||||
}}
|
||||
>
|
||||
<Image
|
||||
ref={setNodeRef}
|
||||
{...listeners}
|
||||
{...attributes}
|
||||
src={getUrl(image.image_url)}
|
||||
fallbackStrategy="beforeLoadOrError"
|
||||
fallback={<ImageFallbackSpinner />}
|
||||
onDragStart={handleDragStart}
|
||||
sx={{
|
||||
objectFit: 'contain',
|
||||
maxWidth: '100%',
|
||||
maxHeight: '100%',
|
||||
height: 'auto',
|
||||
position: 'absolute',
|
||||
borderRadius: 'base',
|
||||
touchAction: 'none',
|
||||
}}
|
||||
onError={handleError}
|
||||
/>
|
||||
<ImageMetadataOverlay image={image} />
|
||||
</>
|
||||
</Flex>
|
||||
)
|
||||
)}
|
||||
{shouldShowImageDetails && image && 'metadata' in image && (
|
||||
|
@ -40,7 +40,6 @@ import {
|
||||
import { useAppToaster } from 'app/components/Toaster';
|
||||
import { ImageDTO } from 'services/api';
|
||||
import { useDraggable } from '@dnd-kit/core';
|
||||
import { CSS } from '@dnd-kit/utilities';
|
||||
|
||||
export const selector = createSelector(
|
||||
[gallerySelector, systemSelector, lightboxSelector, activeTabNameSelector],
|
||||
@ -120,7 +119,7 @@ const HoverableImage = memo((props: HoverableImageProps) => {
|
||||
useRecallParameters();
|
||||
|
||||
const { attributes, listeners, setNodeRef } = useDraggable({
|
||||
id: image_name,
|
||||
id: `galleryImage_${image_name}`,
|
||||
data: {
|
||||
image,
|
||||
},
|
||||
@ -153,14 +152,6 @@ const HoverableImage = memo((props: HoverableImageProps) => {
|
||||
dispatch(imageSelected(image));
|
||||
}, [image, dispatch]);
|
||||
|
||||
const handleDragStart = useCallback(
|
||||
(e: DragEvent<HTMLDivElement>) => {
|
||||
e.dataTransfer.setData('invokeai/imageName', image.image_name);
|
||||
e.dataTransfer.effectAllowed = 'move';
|
||||
},
|
||||
[image]
|
||||
);
|
||||
|
||||
// Recall parameters handlers
|
||||
const handleRecallPrompt = useCallback(() => {
|
||||
recallBothPrompts(
|
||||
@ -225,7 +216,7 @@ const HoverableImage = memo((props: HoverableImageProps) => {
|
||||
ref={setNodeRef}
|
||||
{...listeners}
|
||||
{...attributes}
|
||||
sx={{ w: 'full', h: 'full' }}
|
||||
sx={{ w: 'full', h: 'full', touchAction: 'none' }}
|
||||
>
|
||||
<ContextMenu<HTMLDivElement>
|
||||
menuProps={{ size: 'sm', isLazy: true }}
|
||||
|
@ -32,6 +32,16 @@ const ImageInputFieldComponent = (
|
||||
[dispatch, field.name, nodeId]
|
||||
);
|
||||
|
||||
const handleReset = useCallback(() => {
|
||||
dispatch(
|
||||
fieldValueChanged({
|
||||
nodeId,
|
||||
fieldName: field.name,
|
||||
value: undefined,
|
||||
})
|
||||
);
|
||||
}, [dispatch, field.name, nodeId]);
|
||||
|
||||
return (
|
||||
<Flex
|
||||
sx={{
|
||||
@ -41,7 +51,12 @@ const ImageInputFieldComponent = (
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
<IAISelectableImage image={field.value} onChange={handleChange} />
|
||||
<IAISelectableImage
|
||||
image={field.value}
|
||||
onChange={handleChange}
|
||||
onReset={handleReset}
|
||||
resetIconSize="sm"
|
||||
/>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { Flex } from '@chakra-ui/react';
|
||||
import InitialImagePreview from './InitialImagePreview';
|
||||
import InitialImageButtons from 'common/components/InitialImageButtons';
|
||||
|
||||
const InitialImageDisplay = () => {
|
||||
return (
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Flex, Icon, Image } from '@chakra-ui/react';
|
||||
import { Flex } from '@chakra-ui/react';
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { useGetUrl } from 'common/util/getUrl';
|
||||
@ -6,14 +6,10 @@ import {
|
||||
clearInitialImage,
|
||||
initialImageChanged,
|
||||
} from 'features/parameters/store/generationSlice';
|
||||
import { DragEvent, useCallback } from 'react';
|
||||
import { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import ImageMetadataOverlay from 'common/components/ImageMetadataOverlay';
|
||||
import { generationSelector } from 'features/parameters/store/generationSelectors';
|
||||
import { initialImageSelected } from 'features/parameters/store/actions';
|
||||
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
||||
import ImageFallbackSpinner from 'features/gallery/components/ImageFallbackSpinner';
|
||||
import { FaImage } from 'react-icons/fa';
|
||||
import { configSelector } from '../../../../system/store/configSelectors';
|
||||
import { useAppToaster } from 'app/components/Toaster';
|
||||
import IAISelectableImage from 'features/controlNet/components/parameters/IAISelectableImage';
|
||||
@ -76,41 +72,12 @@ const InitialImagePreview = () => {
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
// onDrop={handleDrop}
|
||||
>
|
||||
<IAISelectableImage
|
||||
image={initialImage}
|
||||
onChange={handleChange}
|
||||
onReset={handleReset}
|
||||
/>
|
||||
{/* {initialImage?.image_url && (
|
||||
<>
|
||||
<Image
|
||||
src={getUrl(initialImage?.image_url)}
|
||||
fallbackStrategy="beforeLoadOrError"
|
||||
fallback={<ImageFallbackSpinner />}
|
||||
onError={handleError}
|
||||
sx={{
|
||||
objectFit: 'contain',
|
||||
maxWidth: '100%',
|
||||
maxHeight: '100%',
|
||||
height: 'auto',
|
||||
position: 'absolute',
|
||||
borderRadius: 'base',
|
||||
}}
|
||||
/>
|
||||
<ImageMetadataOverlay image={initialImage} />
|
||||
</>
|
||||
)}
|
||||
{!initialImage?.image_url && (
|
||||
<Icon
|
||||
as={FaImage}
|
||||
sx={{
|
||||
boxSize: 24,
|
||||
color: 'base.500',
|
||||
}}
|
||||
/>
|
||||
)} */}
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user