mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
feat(ui): rework compare toolbar
This commit is contained in:
parent
940de6a5c5
commit
8ea4067f83
@ -386,7 +386,7 @@
|
|||||||
"sideBySide": "Side-by-Side",
|
"sideBySide": "Side-by-Side",
|
||||||
"swapImages": "Swap Images",
|
"swapImages": "Swap Images",
|
||||||
"compareOptions": "Comparison Options",
|
"compareOptions": "Comparison Options",
|
||||||
"sliderFitLabel": "Stretch second image to fit",
|
"stretchToFit": "Stretch to Fit",
|
||||||
"exitCompare": "Exit Compare"
|
"exitCompare": "Exit Compare"
|
||||||
},
|
},
|
||||||
"hotkeys": {
|
"hotkeys": {
|
||||||
|
@ -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';
|
@ -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';
|
|
@ -1,19 +1,15 @@
|
|||||||
import { Box, Flex } from '@invoke-ai/ui-library';
|
import { Box, Flex } from '@invoke-ai/ui-library';
|
||||||
import { useAppSelector } from 'app/store/storeHooks';
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
|
import { CompareToolbar } from 'features/gallery/components/ImageViewer/CompareToolbar';
|
||||||
import CurrentImagePreview from 'features/gallery/components/ImageViewer/CurrentImagePreview';
|
import CurrentImagePreview from 'features/gallery/components/ImageViewer/CurrentImagePreview';
|
||||||
import { ImageComparison } from 'features/gallery/components/ImageViewer/ImageComparison';
|
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 { useImageViewer } from 'features/gallery/components/ImageViewer/useImageViewer';
|
||||||
|
import { ViewerToolbar } from 'features/gallery/components/ImageViewer/ViewerToolbar';
|
||||||
import type { InvokeTabName } from 'features/ui/store/tabMap';
|
import type { InvokeTabName } from 'features/ui/store/tabMap';
|
||||||
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
|
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
|
||||||
import { memo, useMemo } from 'react';
|
import { memo, useMemo } from 'react';
|
||||||
import { useHotkeys } from 'react-hotkeys-hook';
|
import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
|
|
||||||
import CurrentImageButtons from './CurrentImageButtons';
|
|
||||||
import { ViewerToggleMenu } from './ViewerToggleMenu';
|
|
||||||
|
|
||||||
const VIEWER_ENABLED_TABS: InvokeTabName[] = ['canvas', 'generation', 'workflows'];
|
const VIEWER_ENABLED_TABS: InvokeTabName[] = ['canvas', 'generation', 'workflows'];
|
||||||
|
|
||||||
export const ImageViewer = memo(() => {
|
export const ImageViewer = memo(() => {
|
||||||
@ -51,23 +47,8 @@ export const ImageViewer = memo(() => {
|
|||||||
justifyContent="center"
|
justifyContent="center"
|
||||||
zIndex={10} // reactflow puts its minimap at 5, so we need to be above that
|
zIndex={10} // reactflow puts its minimap at 5, so we need to be above that
|
||||||
>
|
>
|
||||||
<Flex w="full" gap={2}>
|
{isComparing && <CompareToolbar />}
|
||||||
<Flex flex={1} justifyContent="center">
|
{!isComparing && <ViewerToolbar />}
|
||||||
<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>
|
|
||||||
<Box w="full" h="full">
|
<Box w="full" h="full">
|
||||||
{!isComparing && <CurrentImagePreview />}
|
{!isComparing && <CurrentImagePreview />}
|
||||||
{isComparing && <ImageComparison />}
|
{isComparing && <ImageComparison />}
|
||||||
|
@ -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';
|
Loading…
x
Reference in New Issue
Block a user