mirror of
https://github.com/invoke-ai/InvokeAI.git
synced 2026-02-13 02:45:17 -05:00
wip
This commit is contained in:
@@ -50,7 +50,7 @@ import { newCanvasFromImageDndTarget } from 'features/dnd/dnd';
|
||||
import { DndDropTarget } from 'features/dnd/DndDropTarget';
|
||||
import { DndImage } from 'features/dnd/DndImage';
|
||||
import { newCanvasFromImage } from 'features/imageActions/actions';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { memo, useCallback, useEffect, useMemo } from 'react';
|
||||
import { useHotkeys } from 'react-hotkeys-hook';
|
||||
import { Trans, useTranslation } from 'react-i18next';
|
||||
import { PiDotsThreeOutlineVerticalFill, PiUploadBold } from 'react-icons/pi';
|
||||
@@ -283,7 +283,9 @@ const GenerateWithStartingImageAndInpaintMask = memo(() => {
|
||||
GenerateWithStartingImageAndInpaintMask.displayName = 'GenerateWithStartingImageAndInpaintMask';
|
||||
|
||||
const SimpleActiveSession = memo(() => {
|
||||
const dispatch = useAppDispatch();
|
||||
const { getState, dispatch } = useAppStore();
|
||||
const selectedImage = useAppSelector(selectSelectedImage);
|
||||
|
||||
const isStaging = useAppSelector(selectIsStaging);
|
||||
|
||||
const onReset = useCallback(() => {
|
||||
@@ -302,13 +304,64 @@ const SimpleActiveSession = memo(() => {
|
||||
|
||||
useHotkeys(['left'], selectPrev, { preventDefault: true }, [selectPrev]);
|
||||
|
||||
const vary = useCallback(() => {
|
||||
if (!selectedImage) {
|
||||
return;
|
||||
}
|
||||
newCanvasFromImage({
|
||||
imageDTO: selectedImage.imageDTO,
|
||||
type: 'raster_layer',
|
||||
withResize: true,
|
||||
getState,
|
||||
dispatch,
|
||||
});
|
||||
}, [dispatch, getState, selectedImage]);
|
||||
|
||||
const useAsControl = useCallback(() => {
|
||||
if (!selectedImage) {
|
||||
return;
|
||||
}
|
||||
newCanvasFromImage({
|
||||
imageDTO: selectedImage.imageDTO,
|
||||
type: 'control_layer',
|
||||
withResize: true,
|
||||
getState,
|
||||
dispatch,
|
||||
});
|
||||
}, [dispatch, getState, selectedImage]);
|
||||
|
||||
const edit = useCallback(() => {
|
||||
if (!selectedImage) {
|
||||
return;
|
||||
}
|
||||
newCanvasFromImage({
|
||||
imageDTO: selectedImage.imageDTO,
|
||||
type: 'raster_layer',
|
||||
withInpaintMask: true,
|
||||
getState,
|
||||
dispatch,
|
||||
});
|
||||
}, [dispatch, getState, selectedImage]);
|
||||
|
||||
return (
|
||||
<Flex flexDir="column" w="full" h="full" alignItems="center" justifyContent="center" gap={2}>
|
||||
<Flex>
|
||||
<Text fontSize="lg" fontWeight="bold">
|
||||
Simple Session (staging view) {isStaging && 'STAGING'}
|
||||
</Text>
|
||||
<Button onClick={onReset}>reset</Button>
|
||||
<Spacer />
|
||||
<Button onClick={onReset}>Start Over</Button>
|
||||
</Flex>
|
||||
<Flex gap={2}>
|
||||
<Button onClick={vary} tooltip="Vary the image using Image to Image">
|
||||
Vary
|
||||
</Button>
|
||||
<Button onClick={useAsControl} tooltip="Use this image to control a new Text to Image generation">
|
||||
Use as Control
|
||||
</Button>
|
||||
<Button onClick={edit} tooltip="Edit parts of this image with Inpainting">
|
||||
Edit
|
||||
</Button>
|
||||
</Flex>
|
||||
<SelectedImage />
|
||||
<SessionImages />
|
||||
@@ -343,7 +396,11 @@ const SelectedImage = memo(() => {
|
||||
);
|
||||
}
|
||||
|
||||
return <Text>No images</Text>;
|
||||
return (
|
||||
<Flex alignItems="center" justifyContent="center" minH={0} minW={0} h="full">
|
||||
<Text>No images</Text>
|
||||
</Flex>
|
||||
);
|
||||
});
|
||||
SelectedImage.displayName = 'SelectedImage';
|
||||
|
||||
@@ -361,6 +418,8 @@ const SessionImages = memo(() => {
|
||||
});
|
||||
SessionImages.displayName = 'SessionImages';
|
||||
|
||||
const getStagingImageId = (imageDTO: ImageDTO) => `staging-image-${imageDTO.image_name}`;
|
||||
|
||||
const sx = {
|
||||
objectFit: 'contain',
|
||||
maxW: 'full',
|
||||
@@ -381,8 +440,14 @@ const SessionImage = memo(({ index, imageDTO }: { index: number; imageDTO: Image
|
||||
const onClick = useCallback(() => {
|
||||
dispatch(stagingAreaImageSelected({ index }));
|
||||
}, [dispatch, index]);
|
||||
useEffect(() => {
|
||||
if (selectedImageIndex === index) {
|
||||
document.getElementById(getStagingImageId(imageDTO))?.scrollIntoView();
|
||||
}
|
||||
}, [imageDTO, index, selectedImageIndex]);
|
||||
return (
|
||||
<DndImage
|
||||
id={getStagingImageId(imageDTO)}
|
||||
imageDTO={imageDTO}
|
||||
asThumbnail
|
||||
onClick={onClick}
|
||||
|
||||
@@ -9,7 +9,7 @@ import { createSingleImageDragPreview, setSingleImageDragPreview } from 'feature
|
||||
import { firefoxDndFix } from 'features/dnd/util';
|
||||
import { useImageContextMenu } from 'features/gallery/components/ImageContextMenu/ImageContextMenu';
|
||||
import { $imageViewer } from 'features/gallery/components/ImageViewer/useImageViewer';
|
||||
import { memo, useEffect, useState } from 'react';
|
||||
import { memo, useEffect, useRef, useState } from 'react';
|
||||
import type { ImageDTO } from 'services/api/types';
|
||||
|
||||
const sx = {
|
||||
@@ -34,10 +34,11 @@ export namespace DndImage {
|
||||
export const DndImage = memo(({ imageDTO, asThumbnail, ...rest }: DndImage.Props) => {
|
||||
const store = useAppStore();
|
||||
const [isDragging, setIsDragging] = useState(false);
|
||||
const [element, ref] = useState<HTMLImageElement | null>(null);
|
||||
const ref = useRef<HTMLImageElement>(null);
|
||||
const [dragPreviewState, setDragPreviewState] = useState<DndDragPreviewSingleImageState | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const element = ref.current;
|
||||
if (!element) {
|
||||
return;
|
||||
}
|
||||
@@ -66,9 +67,9 @@ export const DndImage = memo(({ imageDTO, asThumbnail, ...rest }: DndImage.Props
|
||||
},
|
||||
})
|
||||
);
|
||||
}, [imageDTO, element, store]);
|
||||
}, [imageDTO, store]);
|
||||
|
||||
useImageContextMenu(imageDTO, element);
|
||||
useImageContextMenu(imageDTO, ref);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -7,6 +7,7 @@ import MultipleSelectionMenuItems from 'features/gallery/components/ImageContext
|
||||
import SingleSelectionMenuItems from 'features/gallery/components/ImageContextMenu/SingleSelectionMenuItems';
|
||||
import { selectSelectionCount } from 'features/gallery/store/gallerySelectors';
|
||||
import { map } from 'nanostores';
|
||||
import type { RefObject } from 'react';
|
||||
import { memo, useCallback, useEffect, useRef } from 'react';
|
||||
import type { ImageDTO } from 'services/api/types';
|
||||
|
||||
@@ -43,7 +44,7 @@ const onClose = () => {
|
||||
* Map of elements to image DTOs. This is used to determine which image DTO to show the context menu for, depending on
|
||||
* the target of the context menu or long press event.
|
||||
*/
|
||||
const elToImageMap = new Map<HTMLDivElement, ImageDTO>();
|
||||
const elToImageMap = new Map<HTMLElement, ImageDTO>();
|
||||
|
||||
/**
|
||||
* Given a target node, find the first registered parent element that contains the target node and return the imageDTO
|
||||
@@ -59,17 +60,20 @@ const getImageDTOFromMap = (target: Node): ImageDTO | undefined => {
|
||||
* @param imageDTO The image DTO to register the context menu for.
|
||||
* @param targetRef The ref of the target element that should trigger the context menu.
|
||||
*/
|
||||
export const useImageContextMenu = (imageDTO: ImageDTO | undefined, targetRef: HTMLDivElement | null) => {
|
||||
export const useImageContextMenu = (imageDTO: ImageDTO | undefined, ref: RefObject<HTMLElement>) => {
|
||||
useEffect(() => {
|
||||
if (!targetRef || !imageDTO) {
|
||||
if (!imageDTO) {
|
||||
return;
|
||||
}
|
||||
const el = ref.current;
|
||||
if (!el) {
|
||||
return;
|
||||
}
|
||||
const el = targetRef;
|
||||
elToImageMap.set(el, imageDTO);
|
||||
return () => {
|
||||
elToImageMap.delete(el);
|
||||
};
|
||||
}, [imageDTO, targetRef]);
|
||||
}, [imageDTO, ref]);
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -19,7 +19,7 @@ import { SizedSkeletonLoader } from 'features/gallery/components/ImageGrid/Sized
|
||||
import { $imageViewer } from 'features/gallery/components/ImageViewer/useImageViewer';
|
||||
import { imageToCompareChanged, selectGallerySlice } from 'features/gallery/store/gallerySlice';
|
||||
import type { MouseEventHandler } from 'react';
|
||||
import { memo, useCallback, useEffect, useId, useMemo, useState } from 'react';
|
||||
import { memo, useCallback, useEffect, useId, useMemo, useRef, useState } from 'react';
|
||||
import type { ImageDTO } from 'services/api/types';
|
||||
|
||||
// This class name is used to calculate the number of images that fit in the gallery
|
||||
@@ -89,7 +89,7 @@ export const GalleryImage = memo(({ imageDTO }: Props) => {
|
||||
const [dragPreviewState, setDragPreviewState] = useState<
|
||||
DndDragPreviewSingleImageState | DndDragPreviewMultipleImageState | null
|
||||
>(null);
|
||||
const [element, ref] = useState<HTMLImageElement | null>(null);
|
||||
const ref = useRef<HTMLImageElement>(null);
|
||||
const dndId = useId();
|
||||
const selectIsSelectedForCompare = useMemo(
|
||||
() => createSelector(selectGallerySlice, (gallery) => gallery.imageToCompare?.image_name === imageDTO.image_name),
|
||||
@@ -111,6 +111,7 @@ export const GalleryImage = memo(({ imageDTO }: Props) => {
|
||||
const isSelected = useAppSelector(selectIsSelected);
|
||||
|
||||
useEffect(() => {
|
||||
const element = ref.current;
|
||||
if (!element) {
|
||||
return;
|
||||
}
|
||||
@@ -175,7 +176,7 @@ export const GalleryImage = memo(({ imageDTO }: Props) => {
|
||||
},
|
||||
})
|
||||
);
|
||||
}, [imageDTO, element, store, dndId]);
|
||||
}, [imageDTO, store, dndId]);
|
||||
|
||||
const [isHovered, setIsHovered] = useState(false);
|
||||
|
||||
@@ -211,7 +212,7 @@ export const GalleryImage = memo(({ imageDTO }: Props) => {
|
||||
|
||||
const dataTestId = useMemo(() => getGalleryImageDataTestId(imageDTO.image_name), [imageDTO.image_name]);
|
||||
|
||||
useImageContextMenu(imageDTO, element);
|
||||
useImageContextMenu(imageDTO, ref);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
Reference in New Issue
Block a user