feat(ui): add fill mode for slider comparison

This commit is contained in:
psychedelicious 2024-05-31 19:46:23 +10:00
parent e4ce188500
commit 3501636018
5 changed files with 61 additions and 6 deletions

View File

@ -384,7 +384,9 @@
"selectAnImageToCompare": "Select an Image to Compare",
"slider": "Slider",
"sideBySide": "Side-by-Side",
"swapImages": "Swap"
"swapImages": "Swap Images",
"compareOptions": "Comparison Options",
"sliderFitLabel": "Stretch second image to fit"
},
"hotkeys": {
"searchHotkeys": "Search Hotkeys",

View File

@ -1,5 +1,6 @@
import { Box, Flex, Icon, Image, Text } from '@invoke-ai/ui-library';
import { useMeasure } from '@reactuses/core';
import { useAppSelector } from 'app/store/storeHooks';
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';
@ -29,6 +30,7 @@ type Props = {
export const ImageComparisonSlider = memo(({ firstImage, secondImage }: Props) => {
const { t } = useTranslation();
const sliderFit = useAppSelector((s) => s.gallery.sliderFit);
// 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
@ -165,11 +167,11 @@ export const ImageComparisonSlider = memo(({ firstImage, secondImage }: Props) =
<Image
src={secondImage.image_url}
fallbackSrc={secondImage.thumbnail_url}
w={secondImage.width}
h={secondImage.height}
w={sliderFit === 'fill' ? fittedSize.width : secondImage.width}
h={sliderFit === 'fill' ? fittedSize.height : secondImage.height}
maxW={fittedSize.width}
maxH={fittedSize.height}
objectFit="contain"
objectFit={sliderFit}
objectPosition="top left"
/>
<Text

View File

@ -1,13 +1,28 @@
import { Button, ButtonGroup } from '@invoke-ai/ui-library';
import {
Button,
ButtonGroup,
Flex,
FormControl,
FormLabel,
IconButton,
Popover,
PopoverBody,
PopoverContent,
PopoverTrigger,
Switch,
} from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { comparedImagesSwapped, comparisonModeChanged } from 'features/gallery/store/gallerySlice';
import { comparedImagesSwapped, comparisonModeChanged, sliderFitChanged } from 'features/gallery/store/gallerySlice';
import type { ChangeEvent } from 'react';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { PiGearBold } from 'react-icons/pi';
export const ImageComparisonToolbarButtons = memo(() => {
const { t } = useTranslation();
const dispatch = useAppDispatch();
const comparisonMode = useAppSelector((s) => s.gallery.comparisonMode);
const sliderFit = useAppSelector((s) => s.gallery.sliderFit);
const setComparisonModeSlider = useCallback(() => {
dispatch(comparisonModeChanged('slider'));
}, [dispatch]);
@ -17,6 +32,12 @@ export const ImageComparisonToolbarButtons = memo(() => {
const swapImages = useCallback(() => {
dispatch(comparedImagesSwapped());
}, [dispatch]);
const onSliderFitChanged = useCallback(
(e: ChangeEvent<HTMLInputElement>) => {
dispatch(sliderFitChanged(e.target.checked ? 'fill' : 'contain'));
},
[dispatch]
);
return (
<>
@ -31,6 +52,30 @@ export const ImageComparisonToolbarButtons = memo(() => {
{t('gallery.sideBySide')}
</Button>
</ButtonGroup>
<Popover isLazy>
<PopoverTrigger>
<IconButton
aria-label={t('gallery.compareOptions')}
tooltip={t('gallery.compareOptions')}
icon={<PiGearBold />}
/>
</PopoverTrigger>
<PopoverContent>
<PopoverBody>
<Flex direction="column" gap={2}>
<FormControl>
<FormLabel>{t('gallery.sliderFitLabel')}</FormLabel>
<Switch
isChecked={sliderFit === 'fill'}
isDisabled={comparisonMode !== 'slider'}
onChange={onSliderFitChanged}
/>
</FormControl>
</Flex>
</PopoverBody>
</PopoverContent>
</Popover>
<Button onClick={swapImages}>{t('gallery.swapImages')}</Button>
</>
);

View File

@ -24,6 +24,7 @@ const initialGalleryState: GalleryState = {
viewerMode: 'view',
imageToCompare: null,
comparisonMode: 'slider',
sliderFit: 'fill',
};
export const gallerySlice = createSlice({
@ -97,6 +98,9 @@ export const gallerySlice = createSlice({
state.imageToCompare = oldSelection[0] ?? null;
}
},
sliderFitChanged: (state, action: PayloadAction<'contain' | 'fill'>) => {
state.sliderFit = action.payload;
},
},
extraReducers: (builder) => {
builder.addMatcher(isAnyBoardDeleted, (state, action) => {
@ -138,6 +142,7 @@ export const {
imageToCompareChanged,
comparisonModeChanged,
comparedImagesSwapped,
sliderFitChanged,
} = gallerySlice.actions;
const isAnyBoardDeleted = isAnyOf(

View File

@ -24,5 +24,6 @@ export type GalleryState = {
alwaysShowImageSizeBadge: boolean;
imageToCompare: ImageDTO | null;
comparisonMode: ComparisonMode;
sliderFit: 'contain' | 'fill';
viewerMode: ViewerMode;
};