mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
feat(ui): revised image comparison slider
Should work for any components and image now.
This commit is contained in:
parent
72bbcb2d94
commit
7a4bbd092e
@ -375,7 +375,9 @@
|
||||
"bulkDownloadRequestFailed": "Problem Preparing Download",
|
||||
"bulkDownloadFailed": "Download Failed",
|
||||
"problemDeletingImages": "Problem Deleting Images",
|
||||
"problemDeletingImagesDesc": "One or more images could not be deleted"
|
||||
"problemDeletingImagesDesc": "One or more images could not be deleted",
|
||||
"firstImage": "First Image",
|
||||
"secondImage": "Second Image"
|
||||
},
|
||||
"hotkeys": {
|
||||
"searchHotkeys": "Search Hotkeys",
|
||||
|
@ -54,7 +54,7 @@ const BBOX_SELECTED_STROKE = 'rgba(78, 190, 255, 1)';
|
||||
const BRUSH_BORDER_INNER_COLOR = 'rgba(0,0,0,1)';
|
||||
const BRUSH_BORDER_OUTER_COLOR = 'rgba(255,255,255,0.8)';
|
||||
// This is invokeai/frontend/web/public/assets/images/transparent_bg.png as a dataURL
|
||||
const STAGE_BG_DATAURL =
|
||||
export const STAGE_BG_DATAURL =
|
||||
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAIAAAAC64paAAAEsmlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4KPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iWE1QIENvcmUgNS41LjAiPgogPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgeG1sbnM6ZXhpZj0iaHR0cDovL25zLmFkb2JlLmNvbS9leGlmLzEuMC8iCiAgICB4bWxuczp0aWZmPSJodHRwOi8vbnMuYWRvYmUuY29tL3RpZmYvMS4wLyIKICAgIHhtbG5zOnBob3Rvc2hvcD0iaHR0cDovL25zLmFkb2JlLmNvbS9waG90b3Nob3AvMS4wLyIKICAgIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIKICAgIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIgogICAgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIKICAgZXhpZjpQaXhlbFhEaW1lbnNpb249IjIwIgogICBleGlmOlBpeGVsWURpbWVuc2lvbj0iMjAiCiAgIGV4aWY6Q29sb3JTcGFjZT0iMSIKICAgdGlmZjpJbWFnZVdpZHRoPSIyMCIKICAgdGlmZjpJbWFnZUxlbmd0aD0iMjAiCiAgIHRpZmY6UmVzb2x1dGlvblVuaXQ9IjIiCiAgIHRpZmY6WFJlc29sdXRpb249IjMwMC8xIgogICB0aWZmOllSZXNvbHV0aW9uPSIzMDAvMSIKICAgcGhvdG9zaG9wOkNvbG9yTW9kZT0iMyIKICAgcGhvdG9zaG9wOklDQ1Byb2ZpbGU9InNSR0IgSUVDNjE5NjYtMi4xIgogICB4bXA6TW9kaWZ5RGF0ZT0iMjAyNC0wNC0yM1QwODoyMDo0NysxMDowMCIKICAgeG1wOk1ldGFkYXRhRGF0ZT0iMjAyNC0wNC0yM1QwODoyMDo0NysxMDowMCI+CiAgIDx4bXBNTTpIaXN0b3J5PgogICAgPHJkZjpTZXE+CiAgICAgPHJkZjpsaQogICAgICBzdEV2dDphY3Rpb249InByb2R1Y2VkIgogICAgICBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZmZpbml0eSBQaG90byAxLjEwLjgiCiAgICAgIHN0RXZ0OndoZW49IjIwMjQtMDQtMjNUMDg6MjA6NDcrMTA6MDAiLz4KICAgIDwvcmRmOlNlcT4KICAgPC94bXBNTTpIaXN0b3J5PgogIDwvcmRmOkRlc2NyaXB0aW9uPgogPC9yZGY6UkRGPgo8L3g6eG1wbWV0YT4KPD94cGFja2V0IGVuZD0iciI/Pn9pdVgAAAGBaUNDUHNSR0IgSUVDNjE5NjYtMi4xAAAokXWR3yuDURjHP5uJmKghFy6WxpVpqMWNMgm1tGbKr5vt3S+1d3t73y3JrXKrKHHj1wV/AbfKtVJESq53TdywXs9rakv2nJ7zfM73nOfpnOeAPZJRVMPhAzWb18NTAffC4pK7oYiDTjpw4YgqhjYeCgWpaR8P2Kx457Vq1T73rzXHE4YCtkbhMUXT88LTwsG1vGbxrnC7ko7Ghc+F+3W5oPC9pcfKXLQ4VeYvi/VIeALsbcLuVBXHqlhJ66qwvByPmikov/exXuJMZOfnJPaId2MQZooAbmaYZAI/g4zK7MfLEAOyoka+7yd/lpzkKjJrrKOzSoo0efpFLUj1hMSk6AkZGdat/v/tq5EcHipXdwag/sU033qhYQdK26b5eWyapROoe4arbCU/dwQj76JvVzTPIbRuwsV1RYvtweUWdD1pUT36I9WJ25NJeD2DlkVw3ULTcrlnv/ucPkJkQ77qBvYPoE/Ot658AxagZ8FoS/a7AAAACXBIWXMAAC4jAAAuIwF4pT92AAAAL0lEQVQ4jWM8ffo0A25gYmKCR5YJjxxBMKp5ZGhm/P//Px7pM2fO0MrmUc0jQzMAB2EIhZC3pUYAAAAASUVORK5CYII=';
|
||||
|
||||
const mapId = (object: { id: string }) => object.id;
|
||||
|
@ -1,175 +1,274 @@
|
||||
import { Box, Flex, Icon } from '@invoke-ai/ui-library';
|
||||
import { useMeasure } from '@reactuses/core';
|
||||
import { Box, Flex, Icon, Image, Text } from '@invoke-ai/ui-library';
|
||||
import { useMeasure, type UseMeasureRect } from '@reactuses/core';
|
||||
import type { Dimensions } from 'features/canvas/store/canvasTypes';
|
||||
import { memo, useCallback, useMemo, useRef } from 'react';
|
||||
import { STAGE_BG_DATAURL } from 'features/controlLayers/util/renderers';
|
||||
import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PiCaretLeftBold, PiCaretRightBold } from 'react-icons/pi';
|
||||
import type { ImageDTO } from 'services/api/types';
|
||||
|
||||
const DROP_SHADOW = 'drop-shadow(0px 0px 4px rgb(0, 0, 0))';
|
||||
const INITIAL_POS = '50%';
|
||||
const HANDLE_WIDTH = 2;
|
||||
const HANDLE_WIDTH_PX = `${HANDLE_WIDTH}px`;
|
||||
const HANDLE_HITBOX = 20;
|
||||
const HANDLE_HITBOX_PX = `${HANDLE_HITBOX}px`;
|
||||
const HANDLE_INNER_LEFT_PX = `${HANDLE_HITBOX / 2 - HANDLE_WIDTH / 2}px`;
|
||||
const HANDLE_LEFT_INITIAL_PX = `calc(${INITIAL_POS} - ${HANDLE_HITBOX / 2}px)`;
|
||||
const HANDLE_INNER_LEFT_INITIAL_PX = `${HANDLE_HITBOX / 2 - HANDLE_WIDTH / 2}px`;
|
||||
|
||||
type Props = {
|
||||
/**
|
||||
* The first image to compare
|
||||
*/
|
||||
firstImage: ImageDTO;
|
||||
/**
|
||||
* The second image to compare
|
||||
*/
|
||||
secondImage: ImageDTO;
|
||||
/**
|
||||
* The size of the container, used for sizing.
|
||||
* If not provided, an internal container will be used, but this can cause a flicker effect as the component is first rendered.
|
||||
*/
|
||||
containerSize?: UseMeasureRect;
|
||||
/**
|
||||
* The ref of the container, used for sizing.
|
||||
* If not provided, an internal container will be used, but this can cause a flicker effect as the component is first rendered.
|
||||
*/
|
||||
containerRef?: React.RefObject<HTMLDivElement>;
|
||||
};
|
||||
|
||||
export const ImageSliderComparison = memo(({ firstImage, secondImage }: Props) => {
|
||||
const secondImageContainerRef = useRef<HTMLDivElement>(null);
|
||||
const handleRef = useRef<HTMLDivElement>(null);
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const [containerSize] = useMeasure(containerRef);
|
||||
export const ImageSliderComparison = memo(
|
||||
({ firstImage, secondImage, containerSize: containerSizeProp, containerRef: containerRefProp }: 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);
|
||||
// How wide the first image is
|
||||
const [width, setWidth] = useState(INITIAL_POS);
|
||||
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);
|
||||
const containerRef = useMemo(() => containerRefProp ?? _containerRef, [containerRefProp, _containerRef]);
|
||||
const containerSize = useMemo(() => containerSizeProp ?? _containerSize, [containerSizeProp, _containerSize]);
|
||||
// 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);
|
||||
|
||||
const updateHandlePos = useCallback((clientX: number) => {
|
||||
if (!secondImageContainerRef.current || !handleRef.current || !containerRef.current) {
|
||||
return;
|
||||
}
|
||||
const { x, width } = containerRef.current.getBoundingClientRect();
|
||||
const rawHandlePos = ((clientX - x) * 100) / width;
|
||||
const handleWidthPct = (HANDLE_WIDTH * 100) / width;
|
||||
const newHandlePos = Math.min(100 - handleWidthPct, Math.max(0, rawHandlePos));
|
||||
secondImageContainerRef.current.style.width = `${newHandlePos}%`;
|
||||
handleRef.current.style.left = `calc(${newHandlePos}% - ${HANDLE_HITBOX / 2}px)`;
|
||||
}, []);
|
||||
const updateHandlePos = useCallback(
|
||||
(clientX: number) => {
|
||||
if (!handleRef.current || !containerRef.current) {
|
||||
return;
|
||||
}
|
||||
lastMoveTimeRef.current = performance.now();
|
||||
const { x, width } = containerRef.current.getBoundingClientRect();
|
||||
const rawHandlePos = ((clientX - x) * 100) / width;
|
||||
const handleWidthPct = (HANDLE_WIDTH * 100) / width;
|
||||
const newHandlePos = Math.min(100 - handleWidthPct, Math.max(0, rawHandlePos));
|
||||
setWidth(`${newHandlePos}%`);
|
||||
setLeft(`calc(${newHandlePos}% - ${HANDLE_HITBOX / 2}px)`);
|
||||
},
|
||||
[containerRef]
|
||||
);
|
||||
|
||||
const onMouseMove = useCallback(
|
||||
(e: MouseEvent) => {
|
||||
updateHandlePos(e.clientX);
|
||||
},
|
||||
[updateHandlePos]
|
||||
);
|
||||
const onMouseMove = useCallback(
|
||||
(e: MouseEvent) => {
|
||||
if (rafRef.current === null && performance.now() > lastMoveTimeRef.current + 1000 / 60) {
|
||||
rafRef.current = window.requestAnimationFrame(() => {
|
||||
updateHandlePos(e.clientX);
|
||||
rafRef.current = null;
|
||||
});
|
||||
}
|
||||
},
|
||||
[updateHandlePos]
|
||||
);
|
||||
|
||||
const onMouseUp = useCallback(() => {
|
||||
window.removeEventListener('mousemove', onMouseMove);
|
||||
}, [onMouseMove]);
|
||||
const onMouseUp = useCallback(() => {
|
||||
window.removeEventListener('mousemove', onMouseMove);
|
||||
}, [onMouseMove]);
|
||||
|
||||
const onMouseDown = useCallback(
|
||||
(e: React.MouseEvent<HTMLDivElement>) => {
|
||||
updateHandlePos(e.clientX);
|
||||
window.addEventListener('mouseup', onMouseUp, { once: true });
|
||||
window.addEventListener('mousemove', onMouseMove);
|
||||
},
|
||||
[onMouseMove, onMouseUp, updateHandlePos]
|
||||
);
|
||||
const onMouseDown = useCallback(
|
||||
(e: React.MouseEvent<HTMLDivElement>) => {
|
||||
// Update the handle position immediately on click
|
||||
updateHandlePos(e.clientX);
|
||||
window.addEventListener('mouseup', onMouseUp, { once: true });
|
||||
window.addEventListener('mousemove', onMouseMove);
|
||||
},
|
||||
[onMouseMove, onMouseUp, updateHandlePos]
|
||||
);
|
||||
|
||||
const fittedSize = useMemo<Dimensions>(() => {
|
||||
// Fit the first image to the container
|
||||
const targetAspectRatio = containerSize.width / containerSize.height;
|
||||
const imageAspectRatio = firstImage.width / firstImage.height;
|
||||
const fittedSize = useMemo<Dimensions>(() => {
|
||||
// Fit the first image to the container
|
||||
if (containerSize.width === 0 || containerSize.height === 0) {
|
||||
return { width: firstImage.width, height: firstImage.height };
|
||||
}
|
||||
const targetAspectRatio = containerSize.width / containerSize.height;
|
||||
const imageAspectRatio = firstImage.width / firstImage.height;
|
||||
|
||||
if (firstImage.width <= containerSize.width && firstImage.height <= containerSize.height) {
|
||||
return { width: firstImage.width, height: firstImage.height };
|
||||
}
|
||||
if (firstImage.width <= containerSize.width && firstImage.height <= containerSize.height) {
|
||||
return { width: firstImage.width, height: firstImage.height };
|
||||
}
|
||||
|
||||
let width: number;
|
||||
let height: number;
|
||||
let width: number;
|
||||
let height: number;
|
||||
|
||||
if (imageAspectRatio > targetAspectRatio) {
|
||||
// Image is wider than container's aspect ratio
|
||||
width = containerSize.width;
|
||||
height = width / imageAspectRatio;
|
||||
} else {
|
||||
// Image is taller than container's aspect ratio
|
||||
height = containerSize.height;
|
||||
width = height * imageAspectRatio;
|
||||
}
|
||||
return { width, height };
|
||||
}, [containerSize.height, containerSize.width, firstImage]);
|
||||
if (imageAspectRatio > targetAspectRatio) {
|
||||
// Image is wider than container's aspect ratio
|
||||
width = containerSize.width;
|
||||
height = width / imageAspectRatio;
|
||||
} else {
|
||||
// Image is taller than container's aspect ratio
|
||||
height = containerSize.height;
|
||||
width = height * imageAspectRatio;
|
||||
}
|
||||
return { width, height };
|
||||
}, [containerSize, firstImage.height, firstImage.width]);
|
||||
|
||||
return (
|
||||
<Flex w="full" h="full" maxW="full" maxH="full" position="relative" alignItems="center" justifyContent="center" bg='green'>
|
||||
<Flex
|
||||
id="image-comparison-container"
|
||||
ref={containerRef}
|
||||
w="full"
|
||||
h="full"
|
||||
maxW="full"
|
||||
maxH="full"
|
||||
position="relative"
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
>
|
||||
<Box
|
||||
position="relative"
|
||||
id="image-comparison-second-image-container"
|
||||
w={fittedSize.width}
|
||||
h={fittedSize.height}
|
||||
useEffect(
|
||||
() => () => {
|
||||
if (rafRef.current !== null) {
|
||||
cancelAnimationFrame(rafRef.current);
|
||||
}
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
return (
|
||||
<Flex w="full" h="full" maxW="full" maxH="full" position="relative" alignItems="center" justifyContent="center">
|
||||
<Flex
|
||||
id="image-comparison-container"
|
||||
ref={_containerRef}
|
||||
w="full"
|
||||
h="full"
|
||||
maxW="full"
|
||||
maxH="full"
|
||||
backgroundImage={`url(${secondImage.image_url})`}
|
||||
backgroundSize="contain"
|
||||
backgroundRepeat="no-repeat"
|
||||
userSelect="none"
|
||||
overflow="hidden"
|
||||
position="relative"
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
>
|
||||
<Box
|
||||
id="image-comparison-first-image-container"
|
||||
ref={secondImageContainerRef}
|
||||
backgroundImage={`url(${firstImage.image_url})`}
|
||||
backgroundSize={`${fittedSize.width}px ${fittedSize.height}px`}
|
||||
backgroundPosition="top left"
|
||||
backgroundRepeat="no-repeat"
|
||||
w={INITIAL_POS}
|
||||
position="relative"
|
||||
id="image-comparison-second-image-container"
|
||||
w={fittedSize.width}
|
||||
h={fittedSize.height}
|
||||
maxW="full"
|
||||
maxH="full"
|
||||
position="absolute"
|
||||
top={0}
|
||||
left={0}
|
||||
right={0}
|
||||
bottom={0}
|
||||
/>
|
||||
<Flex
|
||||
id="image-comparison-handle"
|
||||
ref={handleRef}
|
||||
position="absolute"
|
||||
top={0}
|
||||
bottom={0}
|
||||
left={HANDLE_LEFT_INITIAL_PX}
|
||||
w={HANDLE_HITBOX_PX}
|
||||
tabIndex={-1}
|
||||
cursor="ew-resize"
|
||||
filter="drop-shadow(0px 0px 4px rgb(0, 0, 0))"
|
||||
userSelect="none"
|
||||
overflow="hidden"
|
||||
borderRadius="base"
|
||||
>
|
||||
<Box
|
||||
w={HANDLE_WIDTH_PX}
|
||||
h="full"
|
||||
bg="base.50"
|
||||
shadow="dark-lg"
|
||||
id="image-comparison-bg"
|
||||
position="absolute"
|
||||
top={0}
|
||||
left={HANDLE_INNER_LEFT_INITIAL_PX}
|
||||
left={0}
|
||||
right={0}
|
||||
bottom={0}
|
||||
backgroundImage={STAGE_BG_DATAURL}
|
||||
backgroundRepeat="repeat"
|
||||
opacity={0.2}
|
||||
zIndex={-1}
|
||||
/>
|
||||
<Flex
|
||||
gap={4}
|
||||
<Image
|
||||
src={secondImage.image_url}
|
||||
fallbackSrc={secondImage.thumbnail_url}
|
||||
w={secondImage.width}
|
||||
h={secondImage.height}
|
||||
maxW="full"
|
||||
maxH="full"
|
||||
objectFit="contain"
|
||||
objectPosition="top left"
|
||||
/>
|
||||
<Text
|
||||
position="absolute"
|
||||
left="50%"
|
||||
top="50%"
|
||||
transform="translate(-50%, 0)"
|
||||
filter="drop-shadow(0px 0px 4px rgb(0, 0, 0)) drop-shadow(0px 0px 4px rgb(0, 0, 0))"
|
||||
bottom={4}
|
||||
insetInlineEnd={4}
|
||||
textOverflow="clip"
|
||||
whiteSpace="nowrap"
|
||||
filter={DROP_SHADOW}
|
||||
color="base.50"
|
||||
>
|
||||
<Icon as={PiCaretLeftBold} />
|
||||
<Icon as={PiCaretRightBold} />
|
||||
{t('gallery.secondImage')}
|
||||
</Text>
|
||||
<Box
|
||||
id="image-comparison-first-image-container"
|
||||
position="absolute"
|
||||
top={0}
|
||||
left={0}
|
||||
right={0}
|
||||
bottom={0}
|
||||
w={width}
|
||||
overflow="hidden"
|
||||
>
|
||||
<Image
|
||||
src={firstImage.image_url}
|
||||
fallbackSrc={firstImage.thumbnail_url}
|
||||
w={fittedSize.width}
|
||||
h={fittedSize.height}
|
||||
objectFit="cover"
|
||||
objectPosition="top left"
|
||||
/>
|
||||
<Text
|
||||
position="absolute"
|
||||
bottom={4}
|
||||
insetInlineStart={4}
|
||||
textOverflow="clip"
|
||||
whiteSpace="nowrap"
|
||||
filter={DROP_SHADOW}
|
||||
color="base.50"
|
||||
>
|
||||
{t('gallery.firstImage')}
|
||||
</Text>
|
||||
</Box>
|
||||
<Flex
|
||||
id="image-comparison-handle"
|
||||
ref={handleRef}
|
||||
position="absolute"
|
||||
top={0}
|
||||
bottom={0}
|
||||
left={left}
|
||||
w={HANDLE_HITBOX_PX}
|
||||
tabIndex={-1}
|
||||
cursor="ew-resize"
|
||||
filter={DROP_SHADOW}
|
||||
>
|
||||
<Box
|
||||
id="image-comparison-handle-divider"
|
||||
w={HANDLE_WIDTH_PX}
|
||||
h="full"
|
||||
bg="base.50"
|
||||
shadow="dark-lg"
|
||||
position="absolute"
|
||||
top={0}
|
||||
left={HANDLE_INNER_LEFT_PX}
|
||||
/>
|
||||
<Flex
|
||||
id="image-comparison-handle-icons"
|
||||
gap={4}
|
||||
position="absolute"
|
||||
left="50%"
|
||||
top="50%"
|
||||
transform="translate(-50%, 0)"
|
||||
filter={DROP_SHADOW}
|
||||
>
|
||||
<Icon as={PiCaretLeftBold} />
|
||||
<Icon as={PiCaretRightBold} />
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Box
|
||||
id="image-comparison-interaction-overlay"
|
||||
position="absolute"
|
||||
top={0}
|
||||
right={0}
|
||||
bottom={0}
|
||||
left={0}
|
||||
onMouseDown={onMouseDown}
|
||||
userSelect="none"
|
||||
/>
|
||||
</Box>
|
||||
<Box
|
||||
id="image-comparison-interaction-overlay"
|
||||
position="absolute"
|
||||
top={0}
|
||||
right={0}
|
||||
bottom={0}
|
||||
left={0}
|
||||
onMouseDown={onMouseDown}
|
||||
userSelect="none"
|
||||
/>
|
||||
</Box>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
});
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
ImageSliderComparison.displayName = 'ImageSliderComparison';
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { 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 { ImageSliderComparison } from 'features/gallery/components/ImageViewer/ImageSliderComparison3';
|
||||
@ -7,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 } from 'react';
|
||||
import { memo, useMemo, useRef } from 'react';
|
||||
import { useHotkeys } from 'react-hotkeys-hook';
|
||||
|
||||
import CurrentImageButtons from './CurrentImageButtons';
|
||||
@ -19,6 +20,8 @@ export const ImageViewer = memo(() => {
|
||||
const { isOpen, onToggle, onClose } = 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;
|
||||
@ -40,6 +43,7 @@ export const ImageViewer = memo(() => {
|
||||
|
||||
return (
|
||||
<Flex
|
||||
ref={containerRef}
|
||||
layerStyle="first"
|
||||
borderRadius="base"
|
||||
position="absolute"
|
||||
@ -71,7 +75,14 @@ export const ImageViewer = memo(() => {
|
||||
</Flex>
|
||||
</Flex>
|
||||
{firstImage && !secondImage && <CurrentImagePreview />}
|
||||
{firstImage && secondImage && <ImageSliderComparison firstImage={firstImage} secondImage={secondImage} />}
|
||||
{firstImage && secondImage && (
|
||||
<ImageSliderComparison
|
||||
containerSize={containerSize}
|
||||
containerRef={containerRef}
|
||||
firstImage={firstImage}
|
||||
secondImage={secondImage}
|
||||
/>
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user