feat(ui): rework compare toolbar

This commit is contained in:
psychedelicious 2024-06-01 09:42:04 +10:00
parent 940de6a5c5
commit 8ea4067f83
5 changed files with 124 additions and 118 deletions

View File

@ -386,7 +386,7 @@
"sideBySide": "Side-by-Side",
"swapImages": "Swap Images",
"compareOptions": "Comparison Options",
"sliderFitLabel": "Stretch second image to fit",
"stretchToFit": "Stretch to Fit",
"exitCompare": "Exit Compare"
},
"hotkeys": {

View File

@ -0,0 +1,89 @@
import { Button, ButtonGroup, Flex, IconButton } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import {
comparedImagesSwapped,
comparisonModeChanged,
imageToCompareChanged,
sliderFitChanged,
} from 'features/gallery/store/gallerySlice';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { PiArrowsOutBold, PiSwapBold, PiXBold } from 'react-icons/pi';
export const CompareToolbar = 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]);
const setComparisonModeSideBySide = useCallback(() => {
dispatch(comparisonModeChanged('side-by-side'));
}, [dispatch]);
const swapImages = useCallback(() => {
dispatch(comparedImagesSwapped());
}, [dispatch]);
const toggleSliderFit = useCallback(() => {
dispatch(sliderFitChanged(sliderFit === 'contain' ? 'fill' : 'contain'));
}, [dispatch, sliderFit]);
const exitCompare = useCallback(() => {
dispatch(imageToCompareChanged(null));
}, [dispatch]);
return (
<Flex w="full" gap={2}>
<Flex flex={1} justifyContent="center">
<Flex gap={2} marginInlineEnd="auto">
<IconButton
icon={<PiSwapBold />}
aria-label={t('gallery.swapImages')}
tooltip={t('gallery.swapImages')}
onClick={swapImages}
/>
{comparisonMode === 'slider' && (
<IconButton
aria-label={t('gallery.stretchToFit')}
tooltip={t('gallery.stretchToFit')}
isDisabled={comparisonMode !== 'slider'}
onClick={toggleSliderFit}
colorScheme={sliderFit === 'fill' ? 'invokeBlue' : 'base'}
variant="outline"
icon={<PiArrowsOutBold />}
/>
)}
</Flex>
</Flex>
<Flex flex={1} gap={4} justifyContent="center">
<ButtonGroup variant="outline">
<Button
flexShrink={0}
onClick={setComparisonModeSlider}
colorScheme={comparisonMode === 'slider' ? 'invokeBlue' : 'base'}
>
{t('gallery.slider')}
</Button>
<Button
flexShrink={0}
onClick={setComparisonModeSideBySide}
colorScheme={comparisonMode === 'side-by-side' ? 'invokeBlue' : 'base'}
>
{t('gallery.sideBySide')}
</Button>
</ButtonGroup>
</Flex>
<Flex flex={1} justifyContent="center">
<Flex gap={2} marginInlineStart="auto">
<IconButton
icon={<PiXBold />}
aria-label={t('gallery.exitCompare')}
tooltip={t('gallery.exitCompare')}
onClick={exitCompare}
/>
</Flex>
</Flex>
</Flex>
);
});
CompareToolbar.displayName = 'CompareToolbar';

View File

@ -1,94 +0,0 @@
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,
imageToCompareChanged,
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]);
const setComparisonModeSideBySide = useCallback(() => {
dispatch(comparisonModeChanged('side-by-side'));
}, [dispatch]);
const swapImages = useCallback(() => {
dispatch(comparedImagesSwapped());
}, [dispatch]);
const onSliderFitChanged = useCallback(
(e: ChangeEvent<HTMLInputElement>) => {
dispatch(sliderFitChanged(e.target.checked ? 'fill' : 'contain'));
},
[dispatch]
);
const exitCompare = useCallback(() => {
dispatch(imageToCompareChanged(null));
}, [dispatch]);
return (
<>
<Popover isLazy>
<PopoverTrigger>
<IconButton
aria-label={t('gallery.compareOptions')}
tooltip={t('gallery.compareOptions')}
icon={<PiGearBold />}
/>
</PopoverTrigger>
<PopoverContent>
<PopoverBody>
<Flex direction="column" gap={2}>
<ButtonGroup variant="outline" size="sm" w="full">
<Button
flex={1}
onClick={setComparisonModeSlider}
colorScheme={comparisonMode === 'slider' ? 'invokeBlue' : 'base'}
>
{t('gallery.slider')}
</Button>
<Button
flex={1}
onClick={setComparisonModeSideBySide}
colorScheme={comparisonMode === 'side-by-side' ? 'invokeBlue' : 'base'}
>
{t('gallery.sideBySide')}
</Button>
</ButtonGroup>
<FormControl isDisabled={comparisonMode !== 'slider'}>
<FormLabel>{t('gallery.sliderFitLabel')}</FormLabel>
<Switch isChecked={sliderFit === 'fill'} onChange={onSliderFitChanged} />
</FormControl>
</Flex>
</PopoverBody>
</PopoverContent>
</Popover>
<Button onClick={swapImages}>{t('gallery.swapImages')}</Button>
<Button onClick={exitCompare}>{t('gallery.exitCompare')}</Button>
</>
);
});
ImageComparisonToolbarButtons.displayName = 'ImageComparisonToolbarButtons';

View File

@ -1,19 +1,15 @@
import { Box, Flex } from '@invoke-ai/ui-library';
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 { ImageComparisonToolbarButtons } from 'features/gallery/components/ImageViewer/ImageComparisonToolbarButtons';
import { ToggleMetadataViewerButton } from 'features/gallery/components/ImageViewer/ToggleMetadataViewerButton';
import { ToggleProgressButton } from 'features/gallery/components/ImageViewer/ToggleProgressButton';
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 CurrentImageButtons from './CurrentImageButtons';
import { ViewerToggleMenu } from './ViewerToggleMenu';
const VIEWER_ENABLED_TABS: InvokeTabName[] = ['canvas', 'generation', 'workflows'];
export const ImageViewer = memo(() => {
@ -51,23 +47,8 @@ export const ImageViewer = memo(() => {
justifyContent="center"
zIndex={10} // reactflow puts its minimap at 5, so we need to be above that
>
<Flex w="full" gap={2}>
<Flex flex={1} justifyContent="center">
<Flex gap={2} marginInlineEnd="auto">
<ToggleProgressButton />
<ToggleMetadataViewerButton />
</Flex>
</Flex>
<Flex flex={1} gap={2} justifyContent="center">
{!isComparing && <CurrentImageButtons />}
{isComparing && <ImageComparisonToolbarButtons />}
</Flex>
<Flex flex={1} justifyContent="center">
<Flex gap={2} marginInlineStart="auto">
<ViewerToggleMenu />
</Flex>
</Flex>
</Flex>
{isComparing && <CompareToolbar />}
{!isComparing && <ViewerToolbar />}
<Box w="full" h="full">
{!isComparing && <CurrentImagePreview />}
{isComparing && <ImageComparison />}

View File

@ -0,0 +1,30 @@
import { Flex } from '@invoke-ai/ui-library';
import { ToggleMetadataViewerButton } from 'features/gallery/components/ImageViewer/ToggleMetadataViewerButton';
import { ToggleProgressButton } from 'features/gallery/components/ImageViewer/ToggleProgressButton';
import { memo } from 'react';
import CurrentImageButtons from './CurrentImageButtons';
import { ViewerToggleMenu } from './ViewerToggleMenu';
export const ViewerToolbar = memo(() => {
return (
<Flex w="full" gap={2}>
<Flex flex={1} justifyContent="center">
<Flex gap={2} marginInlineEnd="auto">
<ToggleProgressButton />
<ToggleMetadataViewerButton />
</Flex>
</Flex>
<Flex flex={1} gap={2} justifyContent="center">
<CurrentImageButtons />
</Flex>
<Flex flex={1} justifyContent="center">
<Flex gap={2} marginInlineStart="auto">
<ViewerToggleMenu />
</Flex>
</Flex>
</Flex>
);
});
ViewerToolbar.displayName = 'ViewerToolbar';