mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
feat(ui): use IAIDndImage for compare mode
This commit is contained in:
parent
4ef8cbd9d0
commit
0da36c1238
@ -1,32 +1,31 @@
|
||||
import type { UseMeasureRect } from '@reactuses/core';
|
||||
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import IAIDroppable from 'common/components/IAIDroppable';
|
||||
import type { SelectForCompareDropData } from 'features/dnd/types';
|
||||
import { ImageComparisonSideBySide } from 'features/gallery/components/ImageViewer/ImageComparisonSideBySide';
|
||||
import { ImageComparisonSlider } from 'features/gallery/components/ImageViewer/ImageComparisonSlider';
|
||||
import { selectGallerySlice } from 'features/gallery/store/gallerySlice';
|
||||
import type { PropsWithChildren } from 'react';
|
||||
import { memo } from 'react';
|
||||
|
||||
type Props = {
|
||||
containerSize: UseMeasureRect;
|
||||
};
|
||||
const selector = createMemoizedSelector(selectGallerySlice, (gallerySlice) => {
|
||||
const firstImage = gallerySlice.selection.slice(-1)[0] ?? null;
|
||||
const secondImage = gallerySlice.imageToCompare;
|
||||
return { firstImage, secondImage };
|
||||
});
|
||||
|
||||
export const ImageComparison = memo(({ containerSize }: Props) => {
|
||||
export const ImageComparison = memo(() => {
|
||||
const comparisonMode = useAppSelector((s) => s.gallery.comparisonMode);
|
||||
const { firstImage, secondImage } = useAppSelector((s) => {
|
||||
const firstImage = s.gallery.selection.slice(-1)[0] ?? null;
|
||||
const secondImage = s.gallery.imageToCompare;
|
||||
return { firstImage, secondImage };
|
||||
});
|
||||
const { firstImage, secondImage } = useAppSelector(selector);
|
||||
|
||||
if (!firstImage || !secondImage) {
|
||||
return <ImageComparisonWrapper>No images to compare</ImageComparisonWrapper>;
|
||||
return <ImageComparisonWrapper>Select an image to compare</ImageComparisonWrapper>;
|
||||
}
|
||||
|
||||
if (comparisonMode === 'slider') {
|
||||
return (
|
||||
<ImageComparisonWrapper>
|
||||
<ImageComparisonSlider containerSize={containerSize} firstImage={firstImage} secondImage={secondImage} />
|
||||
<ImageComparisonSlider firstImage={firstImage} secondImage={secondImage} />
|
||||
</ImageComparisonWrapper>
|
||||
);
|
||||
}
|
||||
|
@ -1,7 +1,8 @@
|
||||
import { Flex, Image } from '@invoke-ai/ui-library';
|
||||
import { Flex } from '@invoke-ai/ui-library';
|
||||
import IAIDndImage from 'common/components/IAIDndImage';
|
||||
import type { ImageDraggableData } from 'features/dnd/types';
|
||||
import ResizeHandle from 'features/ui/components/tabs/ResizeHandle';
|
||||
import { memo, useCallback, useRef } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { memo, useCallback, useMemo, useRef } from 'react';
|
||||
import type { ImperativePanelGroupHandle } from 'react-resizable-panels';
|
||||
import { Panel, PanelGroup } from 'react-resizable-panels';
|
||||
import type { ImageDTO } from 'services/api/types';
|
||||
@ -18,7 +19,6 @@ type Props = {
|
||||
};
|
||||
|
||||
export const ImageComparisonSideBySide = memo(({ firstImage, secondImage }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const panelGroupRef = useRef<ImperativePanelGroupHandle>(null);
|
||||
const onDoubleClickHandle = useCallback(() => {
|
||||
if (!panelGroupRef.current) {
|
||||
@ -27,21 +27,31 @@ export const ImageComparisonSideBySide = memo(({ firstImage, secondImage }: Prop
|
||||
panelGroupRef.current.setLayout([50, 50]);
|
||||
}, []);
|
||||
|
||||
const firstImageDraggableData = useMemo<ImageDraggableData>(
|
||||
() => ({
|
||||
id: 'image-compare-first-image',
|
||||
payloadType: 'IMAGE_DTO',
|
||||
payload: { imageDTO: firstImage },
|
||||
}),
|
||||
[firstImage]
|
||||
);
|
||||
|
||||
const secondImageDraggableData = useMemo<ImageDraggableData>(
|
||||
() => ({
|
||||
id: 'image-compare-second-image',
|
||||
payloadType: 'IMAGE_DTO',
|
||||
payload: { imageDTO: secondImage },
|
||||
}),
|
||||
[secondImage]
|
||||
);
|
||||
|
||||
return (
|
||||
<Flex w="full" h="full" maxW="full" maxH="full" position="relative" alignItems="center" justifyContent="center">
|
||||
<Flex w="full" h="full" maxW="full" maxH="full" position="absolute" alignItems="center" justifyContent="center">
|
||||
<PanelGroup ref={panelGroupRef} direction="horizontal" id="image-comparison-side-by-side">
|
||||
<Panel minSize={20}>
|
||||
<Flex w="full" h="full" alignItems="center" justifyContent="center">
|
||||
<Image
|
||||
src={firstImage.image_url}
|
||||
fallbackSrc={firstImage.thumbnail_url}
|
||||
objectFit="contain"
|
||||
w={firstImage.width}
|
||||
h={firstImage.height}
|
||||
maxW="full"
|
||||
maxH="full"
|
||||
/>
|
||||
<IAIDndImage imageDTO={firstImage} isDropDisabled={true} draggableData={firstImageDraggableData} />
|
||||
</Flex>
|
||||
</Panel>
|
||||
<ResizeHandle
|
||||
@ -52,15 +62,7 @@ export const ImageComparisonSideBySide = memo(({ firstImage, secondImage }: Prop
|
||||
|
||||
<Panel minSize={20}>
|
||||
<Flex w="full" h="full" alignItems="center" justifyContent="center">
|
||||
<Image
|
||||
src={secondImage.image_url}
|
||||
fallbackSrc={secondImage.thumbnail_url}
|
||||
objectFit="contain"
|
||||
w={secondImage.width}
|
||||
h={secondImage.height}
|
||||
maxW="full"
|
||||
maxH="full"
|
||||
/>
|
||||
<IAIDndImage imageDTO={secondImage} isDropDisabled={true} draggableData={secondImageDraggableData} />
|
||||
</Flex>
|
||||
</Panel>
|
||||
</PanelGroup>
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { Box, Flex, Icon, Image, Text } from '@invoke-ai/ui-library';
|
||||
import type { UseMeasureRect } from '@reactuses/core';
|
||||
import { useMeasure } from '@reactuses/core';
|
||||
import type { Dimensions } from 'features/canvas/store/canvasTypes';
|
||||
import { STAGE_BG_DATAURL } from 'features/controlLayers/util/renderers';
|
||||
import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
@ -25,13 +25,9 @@ type Props = {
|
||||
* The second image to compare
|
||||
*/
|
||||
secondImage: ImageDTO;
|
||||
/**
|
||||
* The size of the container, required to fit the component correctly and manage aspect ratios.
|
||||
*/
|
||||
containerSize: UseMeasureRect;
|
||||
};
|
||||
|
||||
export const ImageComparisonSlider = memo(({ firstImage, secondImage, containerSize }: Props) => {
|
||||
export const ImageComparisonSlider = memo(({ firstImage, secondImage }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
// How far the handle is from the left - this will be a CSS calculation that takes into account the handle width
|
||||
const [left, setLeft] = useState(HANDLE_LEFT_INITIAL_PX);
|
||||
@ -40,6 +36,7 @@ export const ImageComparisonSlider = memo(({ firstImage, secondImage, containerS
|
||||
const handleRef = useRef<HTMLDivElement>(null);
|
||||
// If the container size is not provided, use an internal ref and measure - can cause flicker on mount tho
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const [containerSize] = useMeasure(containerRef);
|
||||
// To keep things smooth, we use RAF to update the handle position & gate it to 60fps
|
||||
const rafRef = useRef<number | null>(null);
|
||||
const lastMoveTimeRef = useRef<number>(0);
|
||||
@ -94,13 +91,13 @@ export const ImageComparisonSlider = memo(({ firstImage, secondImage, containerS
|
||||
const targetAspectRatio = containerSize.width / containerSize.height;
|
||||
const imageAspectRatio = firstImage.width / firstImage.height;
|
||||
|
||||
let width: number;
|
||||
let height: number;
|
||||
|
||||
if (firstImage.width <= containerSize.width && firstImage.height <= containerSize.height) {
|
||||
return { width: firstImage.width, height: firstImage.height };
|
||||
}
|
||||
|
||||
let width: number;
|
||||
let height: number;
|
||||
|
||||
if (imageAspectRatio > targetAspectRatio) {
|
||||
// Image is wider than container's aspect ratio
|
||||
width = containerSize.width;
|
||||
@ -123,7 +120,16 @@ export const ImageComparisonSlider = memo(({ firstImage, secondImage, containerS
|
||||
);
|
||||
|
||||
return (
|
||||
<Flex w="full" h="full" maxW="full" maxH="full" position="relative" alignItems="center" justifyContent="center">
|
||||
<Flex
|
||||
ref={containerRef}
|
||||
w="full"
|
||||
h="full"
|
||||
maxW="full"
|
||||
maxH="full"
|
||||
position="relative"
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
>
|
||||
<Flex
|
||||
id="image-comparison-container"
|
||||
w="full"
|
||||
@ -135,7 +141,6 @@ export const ImageComparisonSlider = memo(({ firstImage, secondImage, containerS
|
||||
justifyContent="center"
|
||||
>
|
||||
<Box
|
||||
ref={containerRef}
|
||||
position="relative"
|
||||
id="image-comparison-second-image-container"
|
||||
w={fittedSize.width}
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { Box, Flex } from '@invoke-ai/ui-library';
|
||||
import { useMeasure } from '@reactuses/core';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import CurrentImagePreview from 'features/gallery/components/ImageViewer/CurrentImagePreview';
|
||||
import { ImageComparison } from 'features/gallery/components/ImageViewer/ImageComparison';
|
||||
@ -9,7 +8,7 @@ import { ToggleProgressButton } from 'features/gallery/components/ImageViewer/To
|
||||
import { useImageViewer } from 'features/gallery/components/ImageViewer/useImageViewer';
|
||||
import type { InvokeTabName } from 'features/ui/store/tabMap';
|
||||
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
|
||||
import { memo, useMemo, useRef } from 'react';
|
||||
import { memo, useMemo } from 'react';
|
||||
import { useHotkeys } from 'react-hotkeys-hook';
|
||||
|
||||
import CurrentImageButtons from './CurrentImageButtons';
|
||||
@ -21,8 +20,6 @@ export const ImageViewer = memo(() => {
|
||||
const { viewerMode, onToggle, openEditor } = useImageViewer();
|
||||
const activeTabName = useAppSelector(activeTabNameSelector);
|
||||
const isViewerEnabled = useMemo(() => VIEWER_ENABLED_TABS.includes(activeTabName), [activeTabName]);
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const [containerSize] = useMeasure(containerRef);
|
||||
const shouldShowViewer = useMemo(() => {
|
||||
if (!isViewerEnabled) {
|
||||
return false;
|
||||
@ -70,9 +67,9 @@ export const ImageViewer = memo(() => {
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Box ref={containerRef} w="full" h="full">
|
||||
<Box w="full" h="full">
|
||||
{viewerMode === 'view' && <CurrentImagePreview />}
|
||||
{viewerMode === 'compare' && <ImageComparison containerSize={containerSize} />}
|
||||
{viewerMode === 'compare' && <ImageComparison />}
|
||||
</Box>
|
||||
</Flex>
|
||||
);
|
||||
|
Loading…
Reference in New Issue
Block a user