feat(ui): abstract out and share logic between comparisons

This commit is contained in:
psychedelicious 2024-06-02 10:02:33 +10:00
parent 34d68a3663
commit 449bc4dbe5
13 changed files with 260 additions and 242 deletions

View File

@ -61,7 +61,6 @@
"@fontsource-variable/inter": "^5.0.18",
"@invoke-ai/ui-library": "^0.0.25",
"@nanostores/react": "^0.7.2",
"@reactuses/core": "^5.0.14",
"@reduxjs/toolkit": "2.2.3",
"@roarr/browser-log-writer": "^1.3.0",
"chakra-react-select": "^4.7.6",

View File

@ -35,9 +35,6 @@ dependencies:
'@nanostores/react':
specifier: ^0.7.2
version: 0.7.2(nanostores@0.10.3)(react@18.3.1)
'@reactuses/core':
specifier: ^5.0.14
version: 5.0.14(react@18.3.1)
'@reduxjs/toolkit':
specifier: 2.2.3
version: 2.2.3(react-redux@9.1.2)(react@18.3.1)
@ -3985,18 +3982,6 @@ packages:
- immer
dev: false
/@reactuses/core@5.0.14(react@18.3.1):
resolution: {integrity: sha512-lg640pRPOPT0HZ8XQAA1VRZ47fLIvSd2JrUTtKpzm4t3MtZvza+w2RHBGgPsdmtiLV3GsJJC9x5ge7XOQmiJ/Q==}
peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0
dependencies:
js-cookie: 3.0.5
lodash-es: 4.17.21
react: 18.3.1
screenfull: 5.2.0
use-sync-external-store: 1.2.2(react@18.3.1)
dev: false
/@reduxjs/toolkit@2.2.3(react-redux@9.1.2)(react@18.3.1):
resolution: {integrity: sha512-76dll9EnJXg4EVcI5YNxZA/9hSAmZsFqzMmNRHvIlzw2WS/twfcVX3ysYrWGJMClwEmChQFC4yRq74tn6fdzRA==}
peerDependencies:
@ -9683,11 +9668,6 @@ packages:
resolution: {integrity: sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ==}
dev: false
/js-cookie@3.0.5:
resolution: {integrity: sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==}
engines: {node: '>=14'}
dev: false
/js-tokens@4.0.0:
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}

View File

@ -65,7 +65,7 @@ export const addWorkflowLoadRequestedListener = (startAppListening: AppStartList
});
}
$needsFit.set(true)
$needsFit.set(true);
} catch (e) {
if (e instanceof WorkflowVersionError) {
// The workflow version was not recognized in the valid list of versions

View File

@ -0,0 +1,21 @@
import { useCallback, useMemo, useState } from 'react';
export const useBoolean = (initialValue: boolean) => {
const [isTrue, set] = useState(initialValue);
const setTrue = useCallback(() => set(true), []);
const setFalse = useCallback(() => set(false), []);
const toggle = useCallback(() => set((v) => !v), []);
const api = useMemo(
() => ({
isTrue,
set,
setTrue,
setFalse,
toggle,
}),
[isTrue, set, setTrue, setFalse, toggle]
);
return api;
};

View File

@ -1,6 +1,7 @@
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { useAppSelector } from 'app/store/storeHooks';
import { IAINoContentFallback } from 'common/components/IAIImageFallback';
import type { Dimensions } from 'features/canvas/store/canvasTypes';
import { ImageComparisonHover } from 'features/gallery/components/ImageViewer/ImageComparisonHover';
import { ImageComparisonSideBySide } from 'features/gallery/components/ImageViewer/ImageComparisonSideBySide';
import { ImageComparisonSlider } from 'features/gallery/components/ImageViewer/ImageComparisonSlider';
@ -15,7 +16,11 @@ const selector = createMemoizedSelector(selectGallerySlice, (gallerySlice) => {
return { firstImage, secondImage };
});
export const ImageComparison = memo(() => {
type Props = {
containerDims: Dimensions;
};
export const ImageComparison = memo(({ containerDims }: Props) => {
const { t } = useTranslation();
const comparisonMode = useAppSelector((s) => s.gallery.comparisonMode);
const { firstImage, secondImage } = useAppSelector(selector);
@ -26,15 +31,17 @@ export const ImageComparison = memo(() => {
}
if (comparisonMode === 'slider') {
return <ImageComparisonSlider firstImage={firstImage} secondImage={secondImage} />;
return <ImageComparisonSlider containerDims={containerDims} firstImage={firstImage} secondImage={secondImage} />;
}
if (comparisonMode === 'side-by-side') {
return <ImageComparisonSideBySide firstImage={firstImage} secondImage={secondImage} />;
return (
<ImageComparisonSideBySide containerDims={containerDims} firstImage={firstImage} secondImage={secondImage} />
);
}
if (comparisonMode === 'hover') {
return <ImageComparisonHover firstImage={firstImage} secondImage={secondImage} />;
return <ImageComparisonHover containerDims={containerDims} firstImage={firstImage} secondImage={secondImage} />;
}
});

View File

@ -1,101 +1,114 @@
import { Flex, Image, Text } from '@invoke-ai/ui-library';
import { Box, Flex, Image } from '@invoke-ai/ui-library';
import { useAppSelector } from 'app/store/storeHooks';
import { useBoolean } from 'common/hooks/useBoolean';
import { preventDefault } from 'common/util/stopPropagation';
import { DROP_SHADOW } from 'features/gallery/components/ImageViewer/useImageViewer';
import { memo, useCallback, useState } from 'react';
import { useTranslation } from 'react-i18next';
import type { ImageDTO } from 'services/api/types';
import type { Dimensions } from 'features/canvas/store/canvasTypes';
import { STAGE_BG_DATAURL } from 'features/controlLayers/util/renderers';
import { ImageComparisonLabel } from 'features/gallery/components/ImageViewer/ImageComparisonLabel';
import { memo, useMemo, useRef } from 'react';
type Props = {
/**
* The first image to compare
*/
firstImage: ImageDTO;
/**
* The second image to compare
*/
secondImage: ImageDTO;
};
import type { ComparisonProps } from './common';
import { fitDimsToContainer, getSecondImageDims } from './common';
export const ImageComparisonHover = memo(({ firstImage, secondImage }: Props) => {
const { t } = useTranslation();
export const ImageComparisonHover = memo(({ firstImage, secondImage, containerDims }: ComparisonProps) => {
const comparisonFit = useAppSelector((s) => s.gallery.comparisonFit);
const [isMouseOver, setIsMouseOver] = useState(false);
const onMouseOver = useCallback(() => {
setIsMouseOver(true);
}, []);
const onMouseOut = useCallback(() => {
setIsMouseOver(false);
}, []);
const imageContainerRef = useRef<HTMLDivElement>(null);
const mouseOver = useBoolean(false);
const fittedDims = useMemo<Dimensions>(
() => fitDimsToContainer(containerDims, firstImage),
[containerDims, firstImage]
);
const compareImageDims = useMemo<Dimensions>(
() => getSecondImageDims(comparisonFit, fittedDims, firstImage, secondImage),
[comparisonFit, fittedDims, firstImage, secondImage]
);
return (
<Flex w="full" h="full" maxW="full" maxH="full" position="relative" alignItems="center" justifyContent="center">
<Flex position="absolute" maxW="full" maxH="full" aspectRatio={firstImage.width / firstImage.height}>
<Image
id="image-comparison-first-image"
w={firstImage.width}
h={firstImage.height}
<Flex
id="image-comparison-wrapper"
w="full"
h="full"
maxW="full"
maxH="full"
position="absolute"
alignItems="center"
justifyContent="center"
>
<Box
ref={imageContainerRef}
position="relative"
id="image-comparison-hover-image-container"
w={fittedDims.width}
h={fittedDims.height}
maxW="full"
maxH="full"
src={firstImage.image_url}
fallbackSrc={firstImage.thumbnail_url}
objectFit="contain"
/>
<Text
position="absolute"
bottom={4}
insetInlineStart={4}
textOverflow="clip"
whiteSpace="nowrap"
filter={DROP_SHADOW}
color="base.50"
>
{t('gallery.viewerImage')}
</Text>
<Flex
position="absolute"
top={0}
right={0}
bottom={0}
left={0}
opacity={isMouseOver ? 1 : 0}
transitionDuration="0.2s"
transitionProperty="common"
userSelect="none"
overflow="hidden"
borderRadius="base"
>
<Image
id="image-comparison-second-image"
w={comparisonFit === 'fill' ? 'full' : secondImage.width}
h={comparisonFit === 'fill' ? 'full' : secondImage.height}
maxW={comparisonFit === 'contain' ? 'full' : undefined}
maxH={comparisonFit === 'contain' ? 'full' : undefined}
src={secondImage.image_url}
fallbackSrc={secondImage.thumbnail_url}
objectFit={comparisonFit}
id="image-comparison-hover-first-image"
src={firstImage.image_url}
fallbackSrc={firstImage.thumbnail_url}
w={fittedDims.width}
h={fittedDims.height}
maxW="full"
maxH="full"
objectFit="cover"
objectPosition="top left"
/>
<Text
<ImageComparisonLabel type="first" opacity={mouseOver.isTrue ? 0 : 1} />
<Box
id="image-comparison-hover-second-image-container"
position="absolute"
bottom={4}
insetInlineStart={4}
textOverflow="clip"
whiteSpace="nowrap"
filter={DROP_SHADOW}
color="base.50"
top={0}
left={0}
right={0}
bottom={0}
overflow="hidden"
opacity={mouseOver.isTrue ? 1 : 0}
transitionDuration="0.2s"
transitionProperty="common"
>
{t('gallery.compareImage')}
</Text>
</Flex>
<Flex
id="image-comparison-interaction-overlay"
position="absolute"
top={0}
right={0}
bottom={0}
left={0}
onMouseOver={onMouseOver}
onMouseOut={onMouseOut}
onContextMenu={preventDefault}
userSelect="none"
/>
<Box
id="image-comparison-hover-bg"
position="absolute"
top={0}
left={0}
right={0}
bottom={0}
backgroundImage={STAGE_BG_DATAURL}
backgroundRepeat="repeat"
opacity={0.2}
/>
<Image
position="relative"
id="image-comparison-hover-second-image"
src={secondImage.image_url}
fallbackSrc={secondImage.thumbnail_url}
w={compareImageDims.width}
h={compareImageDims.height}
maxW={fittedDims.width}
maxH={fittedDims.height}
objectFit={comparisonFit}
objectPosition="top left"
/>
<ImageComparisonLabel type="second" opacity={mouseOver.isTrue ? 1 : 0} />
</Box>
<Box
id="image-comparison-hover-interaction-overlay"
position="absolute"
top={0}
right={0}
bottom={0}
left={0}
onMouseOver={mouseOver.setTrue}
onMouseOut={mouseOver.setFalse}
onContextMenu={preventDefault}
userSelect="none"
/>
</Box>
</Flex>
</Flex>
);

View File

@ -0,0 +1,33 @@
import type { TextProps } from '@invoke-ai/ui-library';
import { Text } from '@invoke-ai/ui-library';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
import { DROP_SHADOW } from './common';
type Props = TextProps & {
type: 'first' | 'second';
};
export const ImageComparisonLabel = memo(({ type, ...rest }: Props) => {
const { t } = useTranslation();
return (
<Text
position="absolute"
bottom={4}
insetInlineEnd={type === 'first' ? undefined : 4}
insetInlineStart={type === 'first' ? 4 : undefined}
textOverflow="clip"
whiteSpace="nowrap"
filter={DROP_SHADOW}
color="base.50"
transitionDuration="0.2s"
transitionProperty="common"
{...rest}
>
{type === 'first' ? t('gallery.viewerImage') : t('gallery.compareImage')}
</Text>
);
});
ImageComparisonLabel.displayName = 'ImageComparisonLabel';

View File

@ -1,25 +1,12 @@
import { Flex, Image, Text } from '@invoke-ai/ui-library';
import { DROP_SHADOW } from 'features/gallery/components/ImageViewer/useImageViewer';
import { Flex, Image } from '@invoke-ai/ui-library';
import type { ComparisonProps } from 'features/gallery/components/ImageViewer/common';
import { ImageComparisonLabel } from 'features/gallery/components/ImageViewer/ImageComparisonLabel';
import ResizeHandle from 'features/ui/components/tabs/ResizeHandle';
import { memo, useCallback, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import type { ImperativePanelGroupHandle } from 'react-resizable-panels';
import { Panel, PanelGroup } from 'react-resizable-panels';
import type { ImageDTO } from 'services/api/types';
type Props = {
/**
* The first image to compare
*/
firstImage: ImageDTO;
/**
* The second image to compare
*/
secondImage: ImageDTO;
};
export const ImageComparisonSideBySide = memo(({ firstImage, secondImage }: Props) => {
const { t } = useTranslation();
export const ImageComparisonSideBySide = memo(({ firstImage, secondImage }: ComparisonProps) => {
const panelGroupRef = useRef<ImperativePanelGroupHandle>(null);
const onDoubleClickHandle = useCallback(() => {
if (!panelGroupRef.current) {
@ -44,19 +31,9 @@ export const ImageComparisonSideBySide = memo(({ firstImage, secondImage }: Prop
src={firstImage.image_url}
fallbackSrc={firstImage.thumbnail_url}
objectFit="contain"
borderRadius="base"
/>
<Text
position="absolute"
bottom={4}
insetInlineStart={4}
textOverflow="clip"
whiteSpace="nowrap"
filter={DROP_SHADOW}
color="base.50"
userSelect="none"
>
{t('gallery.viewerImage')}
</Text>
<ImageComparisonLabel type="first" />
</Flex>
</Flex>
</Panel>
@ -78,19 +55,9 @@ export const ImageComparisonSideBySide = memo(({ firstImage, secondImage }: Prop
src={secondImage.image_url}
fallbackSrc={secondImage.thumbnail_url}
objectFit="contain"
borderRadius="base"
/>
<Text
position="absolute"
bottom={4}
insetInlineStart={4}
textOverflow="clip"
whiteSpace="nowrap"
filter={DROP_SHADOW}
color="base.50"
userSelect="none"
>
{t('gallery.compareImage')}
</Text>
<ImageComparisonLabel type="second" />
</Flex>
</Flex>
</Panel>

View File

@ -1,15 +1,14 @@
import { Box, Flex, Icon, Image, Text } from '@invoke-ai/ui-library';
import { useMeasure } from '@reactuses/core';
import { Box, Flex, Icon, Image } from '@invoke-ai/ui-library';
import { useAppSelector } from 'app/store/storeHooks';
import { preventDefault } from 'common/util/stopPropagation';
import type { Dimensions } from 'features/canvas/store/canvasTypes';
import { STAGE_BG_DATAURL } from 'features/controlLayers/util/renderers';
import { ImageComparisonLabel } from 'features/gallery/components/ImageViewer/ImageComparisonLabel';
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';
import { DROP_SHADOW } from './useImageViewer';
import type { ComparisonProps } from './common';
import { DROP_SHADOW, fitDimsToContainer, getSecondImageDims } from './common';
const INITIAL_POS = '50%';
const HANDLE_WIDTH = 2;
@ -19,59 +18,28 @@ 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)`;
type Props = {
/**
* The first image to compare
*/
firstImage: ImageDTO;
/**
* The second image to compare
*/
secondImage: ImageDTO;
};
export const ImageComparisonSlider = memo(({ firstImage, secondImage }: Props) => {
const { t } = useTranslation();
export const ImageComparisonSlider = memo(({ firstImage, secondImage, containerDims }: ComparisonProps) => {
const comparisonFit = useAppSelector((s) => s.gallery.comparisonFit);
// 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);
// To manage aspect ratios, we need to know the size of the container
const imageContainerRef = useRef<HTMLDivElement>(null);
// 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 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;
const fittedDims = useMemo<Dimensions>(
() => fitDimsToContainer(containerDims, firstImage),
[containerDims, firstImage]
);
let width: number;
let height: number;
if (firstImage.width <= containerSize.width && firstImage.height <= containerSize.height) {
return { width: firstImage.width, height: firstImage.height };
}
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]);
const compareImageDims = useMemo<Dimensions>(
() => getSecondImageDims(comparisonFit, fittedDims, firstImage, secondImage),
[comparisonFit, fittedDims, firstImage, secondImage]
);
const updateHandlePos = useCallback((clientX: number) => {
if (!handleRef.current || !imageContainerRef.current) {
@ -122,16 +90,7 @@ export const ImageComparisonSlider = memo(({ firstImage, secondImage }: Props) =
);
return (
<Flex
ref={containerRef}
w="full"
h="full"
maxW="full"
maxH="full"
position="relative"
alignItems="center"
justifyContent="center"
>
<Flex w="full" h="full" maxW="full" maxH="full" position="relative" alignItems="center" justifyContent="center">
<Flex
id="image-comparison-wrapper"
w="full"
@ -146,8 +105,8 @@ export const ImageComparisonSlider = memo(({ firstImage, secondImage }: Props) =
ref={imageContainerRef}
position="relative"
id="image-comparison-image-container"
w={fittedSize.width}
h={fittedSize.height}
w={fittedDims.width}
h={fittedDims.height}
maxW="full"
maxH="full"
userSelect="none"
@ -170,24 +129,14 @@ export const ImageComparisonSlider = memo(({ firstImage, secondImage }: Props) =
id="image-comparison-second-image"
src={secondImage.image_url}
fallbackSrc={secondImage.thumbnail_url}
w={comparisonFit === 'fill' ? fittedSize.width : (fittedSize.width * secondImage.width) / firstImage.width}
h={comparisonFit === 'fill' ? fittedSize.height : (fittedSize.height * secondImage.height) / firstImage.height}
maxW={fittedSize.width}
maxH={fittedSize.height}
w={compareImageDims.width}
h={compareImageDims.height}
maxW={fittedDims.width}
maxH={fittedDims.height}
objectFit={comparisonFit}
objectPosition="top left"
/>
<Text
position="absolute"
bottom={4}
insetInlineEnd={4}
textOverflow="clip"
whiteSpace="nowrap"
filter={DROP_SHADOW}
color="base.50"
>
{t('gallery.compareImage')}
</Text>
<ImageComparisonLabel type="second" />
<Box
id="image-comparison-first-image-container"
position="absolute"
@ -202,22 +151,12 @@ export const ImageComparisonSlider = memo(({ firstImage, secondImage }: Props) =
id="image-comparison-first-image"
src={firstImage.image_url}
fallbackSrc={firstImage.thumbnail_url}
w={fittedSize.width}
h={fittedSize.height}
w={fittedDims.width}
h={fittedDims.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.viewerImage')}
</Text>
<ImageComparisonLabel type="first" />
</Box>
<Flex
id="image-comparison-handle"

View File

@ -3,12 +3,14 @@ import { useAppSelector } from 'app/store/storeHooks';
import { CompareToolbar } from 'features/gallery/components/ImageViewer/CompareToolbar';
import CurrentImagePreview from 'features/gallery/components/ImageViewer/CurrentImagePreview';
import { ImageComparison } from 'features/gallery/components/ImageViewer/ImageComparison';
import { useImageViewer } from 'features/gallery/components/ImageViewer/useImageViewer';
import { ViewerToolbar } from 'features/gallery/components/ImageViewer/ViewerToolbar';
import type { InvokeTabName } from 'features/ui/store/tabMap';
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
import { memo, useMemo } from 'react';
import { useHotkeys } from 'react-hotkeys-hook';
import { useMeasure } from 'react-use';
import { useImageViewer } from './useImageViewer';
const VIEWER_ENABLED_TABS: InvokeTabName[] = ['canvas', 'generation', 'workflows'];
@ -27,6 +29,7 @@ export const ImageViewer = memo(() => {
}
return isOpen;
}, [isOpen, isViewerEnabled, workflowsMode, activeTabName]);
const [containerRef, containerDims] = useMeasure<HTMLDivElement>();
useHotkeys('z', onToggle, { enabled: isViewerEnabled }, [isViewerEnabled, onToggle]);
useHotkeys('esc', onClose, { enabled: isViewerEnabled }, [isViewerEnabled, onClose]);
@ -52,9 +55,9 @@ export const ImageViewer = memo(() => {
>
{isComparing && <CompareToolbar />}
{!isComparing && <ViewerToolbar />}
<Box w="full" h="full">
<Box ref={containerRef} w="full" h="full">
{!isComparing && <CurrentImagePreview />}
{isComparing && <ImageComparison />}
{isComparing && <ImageComparison containerDims={containerDims} />}
</Box>
</Flex>
);

View File

@ -0,0 +1,57 @@
import type { Dimensions } from 'features/canvas/store/canvasTypes';
import type { ComparisonFit } from 'features/gallery/store/types';
import type { ImageDTO } from 'services/api/types';
export const DROP_SHADOW = 'drop-shadow(0px 0px 4px rgb(0, 0, 0)) drop-shadow(0px 0px 4px rgba(0, 0, 0, 0.3))';
export type ComparisonProps = {
firstImage: ImageDTO;
secondImage: ImageDTO;
containerDims: Dimensions;
};
export const fitDimsToContainer = (containerDims: Dimensions, imageDims: Dimensions): Dimensions => {
// Fall back to the image's dimensions if the container has no dimensions
if (containerDims.width === 0 || containerDims.height === 0) {
return { width: imageDims.width, height: imageDims.height };
}
// Fall back to the image's dimensions if the image fits within the container
if (imageDims.width <= containerDims.width && imageDims.height <= containerDims.height) {
return { width: imageDims.width, height: imageDims.height };
}
const targetAspectRatio = containerDims.width / containerDims.height;
const imageAspectRatio = imageDims.width / imageDims.height;
let width: number;
let height: number;
if (imageAspectRatio > targetAspectRatio) {
// Image is wider than container's aspect ratio
width = containerDims.width;
height = width / imageAspectRatio;
} else {
// Image is taller than container's aspect ratio
height = containerDims.height;
width = height * imageAspectRatio;
}
return { width, height };
};
/**
* Gets the dimensions of the second image in a comparison based on the comparison fit mode.
*/
export const getSecondImageDims = (
comparisonFit: ComparisonFit,
fittedDims: Dimensions,
firstImageDims: Dimensions,
secondImageDims: Dimensions
): Dimensions => {
const width =
comparisonFit === 'fill' ? fittedDims.width : (fittedDims.width * secondImageDims.width) / firstImageDims.width;
const height =
comparisonFit === 'fill' ? fittedDims.height : (fittedDims.height * secondImageDims.height) / firstImageDims.height;
return { width, height };
};

View File

@ -29,5 +29,3 @@ export const useImageViewer = () => {
return { isOpen, onOpen, onClose, onToggle };
};
export const DROP_SHADOW = 'drop-shadow(0px 0px 4px rgb(0, 0, 0)) drop-shadow(0px 0px 4px rgba(0, 0, 0, 0.3))';

View File

@ -8,6 +8,7 @@ export const IMAGE_LIMIT = 20;
export type GalleryView = 'images' | 'assets';
export type BoardId = 'none' | (string & Record<never, never>);
export type ComparisonMode = 'slider' | 'side-by-side' | 'hover';
export type ComparisonFit = 'contain' | 'fill';
export type GalleryState = {
selection: ImageDTO[];
@ -23,6 +24,6 @@ export type GalleryState = {
alwaysShowImageSizeBadge: boolean;
imageToCompare: ImageDTO | null;
comparisonMode: ComparisonMode;
comparisonFit: 'contain' | 'fill';
comparisonFit: ComparisonFit;
isImageViewerOpen: boolean;
};