feat(ui): staging area image visibility toggle

This commit is contained in:
psychedelicious 2024-06-28 18:43:28 +10:00
parent ec6361e5cb
commit b823c31ec6
5 changed files with 44 additions and 7 deletions

View File

@ -1,6 +1,8 @@
import { Button, ButtonGroup, IconButton } from '@invoke-ai/ui-library'; import { Button, ButtonGroup, IconButton } from '@invoke-ai/ui-library';
import { useStore } from '@nanostores/react';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { import {
$shouldShowStagedImage,
stagingAreaImageAccepted, stagingAreaImageAccepted,
stagingAreaImageDiscarded, stagingAreaImageDiscarded,
stagingAreaNextImageSelected, stagingAreaNextImageSelected,
@ -11,7 +13,16 @@ import type { CanvasV2State } from 'features/controlLayers/store/types';
import { memo, useCallback, useMemo } from 'react'; import { memo, useCallback, useMemo } from 'react';
import { useHotkeys } from 'react-hotkeys-hook'; import { useHotkeys } from 'react-hotkeys-hook';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { PiArrowLeftBold, PiArrowRightBold, PiCheckBold, PiTrashSimpleBold, PiXBold } from 'react-icons/pi'; import {
PiArrowLeftBold,
PiArrowRightBold,
PiCheckBold,
PiEyeBold,
PiEyeSlashBold,
PiFloppyDiskBold,
PiTrashSimpleBold,
PiXBold,
} from 'react-icons/pi';
export const StagingAreaToolbar = memo(() => { export const StagingAreaToolbar = memo(() => {
const stagingArea = useAppSelector((s) => s.canvasV2.stagingArea); const stagingArea = useAppSelector((s) => s.canvasV2.stagingArea);
@ -31,6 +42,7 @@ type Props = {
export const StagingAreaToolbarContent = memo(({ stagingArea }: Props) => { export const StagingAreaToolbarContent = memo(({ stagingArea }: Props) => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const shouldShowStagedImage = useStore($shouldShowStagedImage);
const images = useMemo(() => stagingArea.images, [stagingArea]); const images = useMemo(() => stagingArea.images, [stagingArea]);
const imageDTO = useMemo(() => { const imageDTO = useMemo(() => {
if (stagingArea.selectedImageIndex === null) { if (stagingArea.selectedImageIndex === null) {
@ -74,6 +86,12 @@ export const StagingAreaToolbarContent = memo(({ stagingArea }: Props) => {
dispatch(stagingAreaReset()); dispatch(stagingAreaReset());
}, [dispatch, stagingArea]); }, [dispatch, stagingArea]);
const onToggleShouldShowStagedImage = useCallback(() => {
$shouldShowStagedImage.set(!shouldShowStagedImage);
}, [shouldShowStagedImage]);
const onSaveStagingImage = useCallback(() => {}, []);
useHotkeys(['left'], onPrev, { useHotkeys(['left'], onPrev, {
preventDefault: true, preventDefault: true,
}); });
@ -95,6 +113,7 @@ export const StagingAreaToolbarContent = memo(({ stagingArea }: Props) => {
icon={<PiArrowLeftBold />} icon={<PiArrowLeftBold />}
onClick={onPrev} onClick={onPrev}
colorScheme="invokeBlue" colorScheme="invokeBlue"
isDisabled={images.length <= 1 || !shouldShowStagedImage}
/> />
<Button <Button
colorScheme="base" colorScheme="base"
@ -107,6 +126,7 @@ export const StagingAreaToolbarContent = memo(({ stagingArea }: Props) => {
icon={<PiArrowRightBold />} icon={<PiArrowRightBold />}
onClick={onNext} onClick={onNext}
colorScheme="invokeBlue" colorScheme="invokeBlue"
isDisabled={images.length <= 1 || !shouldShowStagedImage}
/> />
</ButtonGroup> </ButtonGroup>
<ButtonGroup borderRadius="base" shadow="dark-lg"> <ButtonGroup borderRadius="base" shadow="dark-lg">
@ -117,14 +137,22 @@ export const StagingAreaToolbarContent = memo(({ stagingArea }: Props) => {
onClick={onAccept} onClick={onAccept}
colorScheme="invokeBlue" colorScheme="invokeBlue"
/> />
{/* <IconButton <IconButton
tooltip={shouldShowStagedImage ? t('unifiedCanvas.showResultsOn') : t('unifiedCanvas.showResultsOff')}
aria-label={shouldShowStagedImage ? t('unifiedCanvas.showResultsOn') : t('unifiedCanvas.showResultsOff')}
data-alert={!shouldShowStagedImage}
icon={shouldShowStagedImage ? <PiEyeBold /> : <PiEyeSlashBold />}
onClick={onToggleShouldShowStagedImage}
colorScheme="invokeBlue"
/>
<IconButton
tooltip={`${t('unifiedCanvas.saveToGallery')} (Shift+S)`} tooltip={`${t('unifiedCanvas.saveToGallery')} (Shift+S)`}
aria-label={t('unifiedCanvas.saveToGallery')} aria-label={t('unifiedCanvas.saveToGallery')}
isDisabled={!imageDTO || !imageDTO.is_intermediate} isDisabled={!imageDTO || !imageDTO.is_intermediate}
icon={<PiFloppyDiskBold />} icon={<PiFloppyDiskBold />}
onClick={handleSaveToGallery} onClick={onSaveStagingImage}
colorScheme="invokeBlue" colorScheme="invokeBlue"
/> */} />
<IconButton <IconButton
tooltip={`${t('unifiedCanvas.discardCurrent')}`} tooltip={`${t('unifiedCanvas.discardCurrent')}`}
aria-label={t('unifiedCanvas.discardCurrent')} aria-label={t('unifiedCanvas.discardCurrent')}

View File

@ -52,6 +52,7 @@ export type StateApi = {
getSelectedEntity: () => CanvasEntity | null; getSelectedEntity: () => CanvasEntity | null;
getSpaceKey: () => boolean; getSpaceKey: () => boolean;
setSpaceKey: (val: boolean) => void; setSpaceKey: (val: boolean) => void;
getShouldShowStagedImage: () => boolean;
getBbox: () => CanvasV2State['bbox']; getBbox: () => CanvasV2State['bbox'];
getSettings: () => CanvasV2State['settings']; getSettings: () => CanvasV2State['settings'];
onBrushLineAdded: (arg: BrushLineAddedArg, entityType: CanvasEntity['type']) => void; onBrushLineAdded: (arg: BrushLineAddedArg, entityType: CanvasEntity['type']) => void;
@ -275,7 +276,7 @@ export class KonvaNodeManager {
} }
renderStagingArea() { renderStagingArea() {
this.preview.stagingArea.render(this.stateApi.getStagingAreaState()); this.preview.stagingArea.render(this.stateApi.getStagingAreaState(), this.stateApi.getShouldShowStagedImage());
} }
fitDocument() { fitDocument() {

View File

@ -7,6 +7,7 @@ import { setStageEventHandlers } from 'features/controlLayers/konva/events';
import { KonvaNodeManager, setNodeManager } from 'features/controlLayers/konva/nodeManager'; import { KonvaNodeManager, setNodeManager } from 'features/controlLayers/konva/nodeManager';
import { updateBboxes } from 'features/controlLayers/konva/renderers/entityBbox'; import { updateBboxes } from 'features/controlLayers/konva/renderers/entityBbox';
import { import {
$shouldShowStagedImage,
$stageAttrs, $stageAttrs,
bboxChanged, bboxChanged,
brushWidthChanged, brushWidthChanged,
@ -303,6 +304,7 @@ export const initializeRenderer = (
getMaskOpacity, getMaskOpacity,
getInpaintMaskState, getInpaintMaskState,
getStagingAreaState, getStagingAreaState,
getShouldShowStagedImage: $shouldShowStagedImage.get,
// Read-write state // Read-write state
setTool, setTool,
@ -443,6 +445,9 @@ export const initializeRenderer = (
const unsubscribeRenderer = subscribe(renderCanvas); const unsubscribeRenderer = subscribe(renderCanvas);
// When we this flag, we need to render the staging area
$shouldShowStagedImage.subscribe(manager.renderStagingArea.bind(manager));
logIfDebugging('First render of konva stage'); logIfDebugging('First render of konva stage');
// On first render, the document should be fit to the stage. // On first render, the document should be fit to the stage.
manager.renderDocumentSizeOverlay(); manager.renderDocumentSizeOverlay();
@ -454,6 +459,7 @@ export const initializeRenderer = (
logIfDebugging('Cleaning up konva renderer'); logIfDebugging('Cleaning up konva renderer');
unsubscribeRenderer(); unsubscribeRenderer();
cleanupListeners(); cleanupListeners();
$shouldShowStagedImage.off();
resizeObserver.disconnect(); resizeObserver.disconnect();
}; };
}; };

View File

@ -3,7 +3,6 @@ import type { CanvasV2State } from 'features/controlLayers/store/types';
import Konva from 'konva'; import Konva from 'konva';
import { assert } from 'tsafe'; import { assert } from 'tsafe';
export class CanvasStagingArea { export class CanvasStagingArea {
group: Konva.Group; group: Konva.Group;
image: KonvaImage | null; image: KonvaImage | null;
@ -13,7 +12,7 @@ export class CanvasStagingArea {
this.image = null; this.image = null;
} }
async render(stagingArea: CanvasV2State['stagingArea']) { async render(stagingArea: CanvasV2State['stagingArea'], shouldShowStagedImage: boolean) {
if (!stagingArea || stagingArea.selectedImageIndex === null) { if (!stagingArea || stagingArea.selectedImageIndex === null) {
if (this.image) { if (this.image) {
this.image.destroy(); this.image.destroy();
@ -29,6 +28,7 @@ export class CanvasStagingArea {
if (!this.image.isLoading && !this.image.isError && this.image.imageName !== imageDTO.image_name) { if (!this.image.isLoading && !this.image.isError && this.image.imageName !== imageDTO.image_name) {
await this.image.updateImageSource(imageDTO.image_name); await this.image.updateImageSource(imageDTO.image_name);
} }
this.image.konvaImageGroup.visible(shouldShowStagedImage);
} else { } else {
const { image_name, width, height } = imageDTO; const { image_name, width, height } = imageDTO;
this.image = new KonvaImage({ this.image = new KonvaImage({
@ -49,6 +49,7 @@ export class CanvasStagingArea {
}); });
this.group.add(this.image.konvaImageGroup); this.group.add(this.image.konvaImageGroup);
await this.image.updateImageSource(imageDTO.image_name); await this.image.updateImageSource(imageDTO.image_name);
this.image.konvaImageGroup.visible(shouldShowStagedImage);
} }
} }
} }

View File

@ -357,6 +357,7 @@ export const $stageAttrs = atom<StageAttrs>({
height: 0, height: 0,
scale: 0, scale: 0,
}); });
export const $shouldShowStagedImage = atom(true);
export const canvasV2PersistConfig: PersistConfig<CanvasV2State> = { export const canvasV2PersistConfig: PersistConfig<CanvasV2State> = {
name: canvasV2Slice.name, name: canvasV2Slice.name,