diff --git a/invokeai/frontend/web/src/app/components/ImageDnd/ImageDndContext.tsx b/invokeai/frontend/web/src/app/components/ImageDnd/ImageDndContext.tsx index 1b8687bf8e1..3a540083378 100644 --- a/invokeai/frontend/web/src/app/components/ImageDnd/ImageDndContext.tsx +++ b/invokeai/frontend/web/src/app/components/ImageDnd/ImageDndContext.tsx @@ -6,18 +6,18 @@ import { useSensor, useSensors, } from '@dnd-kit/core'; -import { PropsWithChildren, memo, useCallback, useState } from 'react'; -import DragPreview from './DragPreview'; import { snapCenterToCursor } from '@dnd-kit/modifiers'; +import { imageDropped } from 'app/store/middleware/listenerMiddleware/listeners/imageDropped'; +import { useAppDispatch } from 'app/store/storeHooks'; import { AnimatePresence, motion } from 'framer-motion'; +import { PropsWithChildren, memo, useCallback, useState } from 'react'; +import DragPreview from './DragPreview'; import { DndContext, DragEndEvent, DragStartEvent, TypesafeDraggableData, } from './typesafeDnd'; -import { useAppDispatch } from 'app/store/storeHooks'; -import { imageDropped } from 'app/store/middleware/listenerMiddleware/listeners/imageDropped'; type ImageDndContextProps = PropsWithChildren; @@ -49,11 +49,11 @@ const ImageDndContext = (props: ImageDndContextProps) => { ); const mouseSensor = useSensor(MouseSensor, { - activationConstraint: { delay: 150, tolerance: 5 }, + activationConstraint: { distance: 10 }, }); const touchSensor = useSensor(TouchSensor, { - activationConstraint: { delay: 150, tolerance: 5 }, + activationConstraint: { distance: 10 }, }); // TODO: Use KeyboardSensor - needs composition of multiple collisionDetection algos diff --git a/invokeai/frontend/web/src/common/components/IAIDndImage.tsx b/invokeai/frontend/web/src/common/components/IAIDndImage.tsx index 7cc4eadc75f..dacef3ee94c 100644 --- a/invokeai/frontend/web/src/common/components/IAIDndImage.tsx +++ b/invokeai/frontend/web/src/common/components/IAIDndImage.tsx @@ -1,5 +1,4 @@ import { - Box, ChakraProps, Flex, Icon, @@ -10,9 +9,6 @@ import { import { TypesafeDraggableData, TypesafeDroppableData, - isValidDrop, - useDraggable, - useDroppable, } from 'app/components/ImageDnd/typesafeDnd'; import IAIIconButton from 'common/components/IAIIconButton'; import { @@ -21,14 +17,13 @@ import { } from 'common/components/IAIImageFallback'; import ImageMetadataOverlay from 'common/components/ImageMetadataOverlay'; import { useImageUploadButton } from 'common/hooks/useImageUploadButton'; -import { AnimatePresence } from 'framer-motion'; -import { MouseEvent, ReactElement, SyntheticEvent, memo, useRef } from 'react'; +import { MouseEvent, ReactElement, SyntheticEvent, memo } from 'react'; import { FaImage, FaUndo, FaUpload } from 'react-icons/fa'; import { PostUploadAction } from 'services/api/thunks/image'; import { ImageDTO } from 'services/api/types'; import { mode } from 'theme/util/mode'; -import { v4 as uuidv4 } from 'uuid'; -import IAIDropOverlay from './IAIDropOverlay'; +import IAIDraggable from './IAIDraggable'; +import IAIDroppable from './IAIDroppable'; type IAIDndImageProps = { imageDTO: ImageDTO | undefined; @@ -144,30 +139,6 @@ const IAIDndImage = (props: IAIDndImageProps) => { }} /> {withMetadataOverlay && } - {onClickReset && withResetIcon && ( - - )} )} {!imageDTO && !isUploadDisabled && ( @@ -198,84 +169,44 @@ const IAIDndImage = (props: IAIDndImageProps) => { )} {!imageDTO && isUploadDisabled && noContentFallback} - - + {imageDTO && ( + + )} + {onClickReset && withResetIcon && imageDTO && ( + + )} ); }; export default memo(IAIDndImage); - -type DroppableProps = { - dropLabel?: string; - disabled?: boolean; - data?: TypesafeDroppableData; -}; - -const Droppable = memo((props: DroppableProps) => { - const { dropLabel, data, disabled } = props; - const dndId = useRef(uuidv4()); - - const { isOver, setNodeRef, active } = useDroppable({ - id: dndId.current, - disabled, - data, - }); - - return ( - - - {isValidDrop(data, active) && ( - - )} - - - ); -}); - -Droppable.displayName = 'Droppable'; - -type DraggableProps = { - disabled?: boolean; - data?: TypesafeDraggableData; - onClick?: (event: MouseEvent) => void; -}; - -const Draggable = memo((props: DraggableProps) => { - const { data, disabled, onClick } = props; - const dndId = useRef(uuidv4()); - - const { attributes, listeners, setNodeRef } = useDraggable({ - id: dndId.current, - disabled, - data, - }); - - return ( - - ); -}); - -Draggable.displayName = 'Draggable'; diff --git a/invokeai/frontend/web/src/common/components/IAIDraggable.tsx b/invokeai/frontend/web/src/common/components/IAIDraggable.tsx new file mode 100644 index 00000000000..a1b8c197739 --- /dev/null +++ b/invokeai/frontend/web/src/common/components/IAIDraggable.tsx @@ -0,0 +1,38 @@ +import { Box } from '@chakra-ui/react'; +import { + TypesafeDraggableData, + useDraggable, +} from 'app/components/ImageDnd/typesafeDnd'; +import { MouseEvent, memo, useRef } from 'react'; +import { v4 as uuidv4 } from 'uuid'; + +type IAIDraggableProps = { + disabled?: boolean; + data?: TypesafeDraggableData; + onClick?: (event: MouseEvent) => void; +}; + +const IAIDraggable = (props: IAIDraggableProps) => { + const { data, disabled, onClick } = props; + const dndId = useRef(uuidv4()); + + const { attributes, listeners, setNodeRef } = useDraggable({ + id: dndId.current, + disabled, + data, + }); + + return ( + + ); +}; + +export default memo(IAIDraggable); diff --git a/invokeai/frontend/web/src/common/components/IAIDroppable.tsx b/invokeai/frontend/web/src/common/components/IAIDroppable.tsx new file mode 100644 index 00000000000..7833a61e286 --- /dev/null +++ b/invokeai/frontend/web/src/common/components/IAIDroppable.tsx @@ -0,0 +1,45 @@ +import { Box } from '@chakra-ui/react'; +import { + TypesafeDroppableData, + isValidDrop, + useDroppable, +} from 'app/components/ImageDnd/typesafeDnd'; +import { AnimatePresence } from 'framer-motion'; +import { memo, useRef } from 'react'; +import { v4 as uuidv4 } from 'uuid'; +import IAIDropOverlay from './IAIDropOverlay'; + +type IAIDroppableProps = { + dropLabel?: string; + disabled?: boolean; + data?: TypesafeDroppableData; +}; + +const IAIDroppable = (props: IAIDroppableProps) => { + const { dropLabel, data, disabled } = props; + const dndId = useRef(uuidv4()); + + const { isOver, setNodeRef, active } = useDroppable({ + id: dndId.current, + disabled, + data, + }); + + return ( + + + {isValidDrop(data, active) && ( + + )} + + + ); +}; + +export default memo(IAIDroppable); diff --git a/invokeai/frontend/web/src/features/batch/components/BatchImageContainer.tsx b/invokeai/frontend/web/src/features/batch/components/BatchImageContainer.tsx index 09e6b8afd74..30b07a5591d 100644 --- a/invokeai/frontend/web/src/features/batch/components/BatchImageContainer.tsx +++ b/invokeai/frontend/web/src/features/batch/components/BatchImageContainer.tsx @@ -1,11 +1,7 @@ import { Box } from '@chakra-ui/react'; +import { AddToBatchDropData } from 'app/components/ImageDnd/typesafeDnd'; +import IAIDroppable from 'common/components/IAIDroppable'; import BatchImageGrid from './BatchImageGrid'; -import IAIDropOverlay from 'common/components/IAIDropOverlay'; -import { - AddToBatchDropData, - isValidDrop, - useDroppable, -} from 'app/components/ImageDnd/typesafeDnd'; const droppableData: AddToBatchDropData = { id: 'batch', @@ -13,17 +9,10 @@ const droppableData: AddToBatchDropData = { }; const BatchImageContainer = () => { - const { isOver, setNodeRef, active } = useDroppable({ - id: 'batch-manager', - data: droppableData, - }); - return ( - + - {isValidDrop(droppableData, active) && ( - - )} + ); }; diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/AllImagesBoard.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/AllImagesBoard.tsx index 918e9390f9e..7dd747c15b6 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/AllImagesBoard.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/AllImagesBoard.tsx @@ -1,16 +1,11 @@ import { Flex, useColorMode } from '@chakra-ui/react'; -import { FaImages } from 'react-icons/fa'; +import { MoveBoardDropData } from 'app/components/ImageDnd/typesafeDnd'; +import IAIDroppable from 'common/components/IAIDroppable'; +import { IAINoContentFallback } from 'common/components/IAIImageFallback'; import { boardIdSelected } from 'features/gallery/store/gallerySlice'; +import { FaImages } from 'react-icons/fa'; import { useDispatch } from 'react-redux'; -import { IAINoContentFallback } from 'common/components/IAIImageFallback'; -import { AnimatePresence } from 'framer-motion'; -import IAIDropOverlay from 'common/components/IAIDropOverlay'; import { mode } from 'theme/util/mode'; -import { - MoveBoardDropData, - isValidDrop, - useDroppable, -} from 'app/components/ImageDnd/typesafeDnd'; const AllImagesBoard = ({ isSelected }: { isSelected: boolean }) => { const dispatch = useDispatch(); @@ -26,11 +21,6 @@ const AllImagesBoard = ({ isSelected }: { isSelected: boolean }) => { context: { boardId: null }, }; - const { isOver, setNodeRef, active } = useDroppable({ - id: `board_droppable_all_images`, - data: droppableData, - }); - return ( { }} > { _dark: { border: '2px solid var(--invokeai-colors-base-800)' }, }} /> - - {isValidDrop(droppableData, active) && ( - - )} - + { onClickDeleteBoardImages(board); }, [board, onClickDeleteBoardImages]); - const droppableData: MoveBoardDropData = { - id: board_id, - actionType: 'MOVE_BOARD', - context: { boardId: board_id }, - }; - - const { isOver, setNodeRef, active } = useDroppable({ - id: `board_droppable_${board_id}`, - data: droppableData, - }); + const droppableData: MoveBoardDropData = useMemo( + () => ({ + id: board_id, + actionType: 'MOVE_BOARD', + context: { boardId: board_id }, + }), + [board_id] + ); return ( @@ -127,7 +120,6 @@ const HoverableBoard = memo(({ board, isSelected }: HoverableBoardProps) => { }} > { > {board.image_count} - - {isValidDrop(droppableData, active) && ( - - )} - +