This commit is contained in:
psychedelicious
2025-05-30 19:25:50 +10:00
parent 4dc3f1bcee
commit 7308428f32
4 changed files with 88 additions and 17 deletions

View File

@@ -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}

View File

@@ -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 (
<>

View File

@@ -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]);
};
/**

View File

@@ -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 (
<>