From 181e40926d8d01c3c847f0d9e55ede3f2fc86305 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Fri, 23 Aug 2024 20:32:49 +1000 Subject: [PATCH] feat(ui): disable most interaction while filtering --- .../features/controlLayers/components/Filters/Filter.tsx | 4 ++-- .../controlLayers/components/Tool/ToolBboxButton.tsx | 6 ++++-- .../controlLayers/components/Tool/ToolBrushButton.tsx | 6 ++++-- .../controlLayers/components/Tool/ToolChooser.tsx | 2 +- ...ToolEyeDropperButton.tsx => ToolColorPickerButton.tsx} | 6 ++++-- .../controlLayers/components/Tool/ToolEraserButton.tsx | 6 ++++-- .../controlLayers/components/Tool/ToolMoveButton.tsx | 6 ++++-- .../controlLayers/components/Tool/ToolRectButton.tsx | 6 ++++-- .../controlLayers/components/Tool/ToolViewButton.tsx | 6 ++++-- .../src/features/controlLayers/hooks/useIsFiltering.ts | 8 ++++++++ .../features/controlLayers/konva/CanvasFilterModule.ts | 4 +++- 11 files changed, 42 insertions(+), 18 deletions(-) rename invokeai/frontend/web/src/features/controlLayers/components/Tool/{ToolEyeDropperButton.tsx => ToolColorPickerButton.tsx} (86%) create mode 100644 invokeai/frontend/web/src/features/controlLayers/hooks/useIsFiltering.ts diff --git a/invokeai/frontend/web/src/features/controlLayers/components/Filters/Filter.tsx b/invokeai/frontend/web/src/features/controlLayers/components/Filters/Filter.tsx index b49e23110d7..c88ed9c3edd 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/Filters/Filter.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/Filters/Filter.tsx @@ -11,8 +11,8 @@ import { PiCheckBold, PiShootingStarBold, PiXBold } from 'react-icons/pi'; export const Filter = memo(() => { const { t } = useTranslation(); const canvasManager = useCanvasManager(); - const adapter = useStore(canvasManager.filter.$adapter); const config = useStore(canvasManager.filter.$config); + const isFiltering = useStore(canvasManager.filter.$isFiltering); const isProcessing = useStore(canvasManager.filter.$isProcessing); const previewFilter = useCallback(() => { @@ -41,7 +41,7 @@ export const Filter = memo(() => { [canvasManager.filter.$config] ); - if (!adapter) { + if (!isFiltering) { return null; } diff --git a/invokeai/frontend/web/src/features/controlLayers/components/Tool/ToolBboxButton.tsx b/invokeai/frontend/web/src/features/controlLayers/components/Tool/ToolBboxButton.tsx index 05ae60117a0..07841e9d062 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/Tool/ToolBboxButton.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/Tool/ToolBboxButton.tsx @@ -1,5 +1,6 @@ import { IconButton } from '@invoke-ai/ui-library'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { useIsFiltering } from 'features/controlLayers/hooks/useIsFiltering'; import { useIsTransforming } from 'features/controlLayers/hooks/useIsTransforming'; import { toolChanged } from 'features/controlLayers/store/canvasV2Slice'; import { memo, useCallback, useMemo } from 'react'; @@ -10,12 +11,13 @@ import { PiBoundingBoxBold } from 'react-icons/pi'; export const ToolBboxButton = memo(() => { const { t } = useTranslation(); const dispatch = useAppDispatch(); + const isFiltering = useIsFiltering(); const isTransforming = useIsTransforming(); const isStaging = useAppSelector((s) => s.canvasV2.session.isStaging); const isSelected = useAppSelector((s) => s.canvasV2.tool.selected === 'bbox'); const isDisabled = useMemo(() => { - return isTransforming || isStaging; - }, [isStaging, isTransforming]); + return isTransforming || isFiltering || isStaging; + }, [isFiltering, isStaging, isTransforming]); const onClick = useCallback(() => { dispatch(toolChanged('bbox')); diff --git a/invokeai/frontend/web/src/features/controlLayers/components/Tool/ToolBrushButton.tsx b/invokeai/frontend/web/src/features/controlLayers/components/Tool/ToolBrushButton.tsx index 4efa2c35917..666b110ef42 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/Tool/ToolBrushButton.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/Tool/ToolBrushButton.tsx @@ -1,5 +1,6 @@ import { IconButton } from '@invoke-ai/ui-library'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { useIsFiltering } from 'features/controlLayers/hooks/useIsFiltering'; import { useIsTransforming } from 'features/controlLayers/hooks/useIsTransforming'; import { toolChanged } from 'features/controlLayers/store/canvasV2Slice'; import { isDrawableEntityType } from 'features/controlLayers/store/types'; @@ -11,6 +12,7 @@ import { PiPaintBrushBold } from 'react-icons/pi'; export const ToolBrushButton = memo(() => { const { t } = useTranslation(); const dispatch = useAppDispatch(); + const isFiltering = useIsFiltering(); const isTransforming = useIsTransforming(); const isStaging = useAppSelector((s) => s.canvasV2.session.isStaging); const isSelected = useAppSelector((s) => s.canvasV2.tool.selected === 'brush'); @@ -22,8 +24,8 @@ export const ToolBrushButton = memo(() => { }); const isDisabled = useMemo(() => { - return isTransforming || isStaging || !isDrawingToolAllowed; - }, [isDrawingToolAllowed, isStaging, isTransforming]); + return isTransforming || isFiltering || isStaging || !isDrawingToolAllowed; + }, [isDrawingToolAllowed, isFiltering, isStaging, isTransforming]); const onClick = useCallback(() => { dispatch(toolChanged('brush')); diff --git a/invokeai/frontend/web/src/features/controlLayers/components/Tool/ToolChooser.tsx b/invokeai/frontend/web/src/features/controlLayers/components/Tool/ToolChooser.tsx index bb3a424d4ea..6e2b294289a 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/Tool/ToolChooser.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/Tool/ToolChooser.tsx @@ -1,7 +1,7 @@ import { ButtonGroup } from '@invoke-ai/ui-library'; import { ToolBboxButton } from 'features/controlLayers/components/Tool/ToolBboxButton'; import { ToolBrushButton } from 'features/controlLayers/components/Tool/ToolBrushButton'; -import { ToolColorPickerButton } from 'features/controlLayers/components/Tool/ToolEyeDropperButton'; +import { ToolColorPickerButton } from 'features/controlLayers/components/Tool/ToolColorPickerButton'; import { ToolMoveButton } from 'features/controlLayers/components/Tool/ToolMoveButton'; import { ToolRectButton } from 'features/controlLayers/components/Tool/ToolRectButton'; import { useCanvasDeleteLayerHotkey } from 'features/controlLayers/hooks/useCanvasDeleteLayerHotkey'; diff --git a/invokeai/frontend/web/src/features/controlLayers/components/Tool/ToolEyeDropperButton.tsx b/invokeai/frontend/web/src/features/controlLayers/components/Tool/ToolColorPickerButton.tsx similarity index 86% rename from invokeai/frontend/web/src/features/controlLayers/components/Tool/ToolEyeDropperButton.tsx rename to invokeai/frontend/web/src/features/controlLayers/components/Tool/ToolColorPickerButton.tsx index de791a8afc3..5e271e8b829 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/Tool/ToolEyeDropperButton.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/Tool/ToolColorPickerButton.tsx @@ -1,5 +1,6 @@ import { IconButton } from '@invoke-ai/ui-library'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { useIsFiltering } from 'features/controlLayers/hooks/useIsFiltering'; import { useIsTransforming } from 'features/controlLayers/hooks/useIsTransforming'; import { toolChanged } from 'features/controlLayers/store/canvasV2Slice'; import { memo, useCallback, useMemo } from 'react'; @@ -10,13 +11,14 @@ import { PiEyedropperBold } from 'react-icons/pi'; export const ToolColorPickerButton = memo(() => { const { t } = useTranslation(); const dispatch = useAppDispatch(); + const isFiltering = useIsFiltering(); const isTransforming = useIsTransforming(); const isSelected = useAppSelector((s) => s.canvasV2.tool.selected === 'colorPicker'); const isStaging = useAppSelector((s) => s.canvasV2.session.isStaging); const isDisabled = useMemo(() => { - return isTransforming || isStaging; - }, [isStaging, isTransforming]); + return isTransforming || isFiltering || isStaging; + }, [isFiltering, isStaging, isTransforming]); const onClick = useCallback(() => { dispatch(toolChanged('colorPicker')); diff --git a/invokeai/frontend/web/src/features/controlLayers/components/Tool/ToolEraserButton.tsx b/invokeai/frontend/web/src/features/controlLayers/components/Tool/ToolEraserButton.tsx index 62d66507695..eb10687d2d0 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/Tool/ToolEraserButton.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/Tool/ToolEraserButton.tsx @@ -1,5 +1,6 @@ import { IconButton } from '@invoke-ai/ui-library'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { useIsFiltering } from 'features/controlLayers/hooks/useIsFiltering'; import { useIsTransforming } from 'features/controlLayers/hooks/useIsTransforming'; import { toolChanged } from 'features/controlLayers/store/canvasV2Slice'; import { isDrawableEntityType } from 'features/controlLayers/store/types'; @@ -11,6 +12,7 @@ import { PiEraserBold } from 'react-icons/pi'; export const ToolEraserButton = memo(() => { const { t } = useTranslation(); const dispatch = useAppDispatch(); + const isFiltering = useIsFiltering(); const isTransforming = useIsTransforming(); const isStaging = useAppSelector((s) => s.canvasV2.session.isStaging); const isSelected = useAppSelector((s) => s.canvasV2.tool.selected === 'eraser'); @@ -21,8 +23,8 @@ export const ToolEraserButton = memo(() => { return isDrawableEntityType(s.canvasV2.selectedEntityIdentifier.type); }); const isDisabled = useMemo(() => { - return isTransforming || isStaging || !isDrawingToolAllowed; - }, [isDrawingToolAllowed, isStaging, isTransforming]); + return isTransforming || isFiltering || isStaging || !isDrawingToolAllowed; + }, [isDrawingToolAllowed, isFiltering, isStaging, isTransforming]); const onClick = useCallback(() => { dispatch(toolChanged('eraser')); diff --git a/invokeai/frontend/web/src/features/controlLayers/components/Tool/ToolMoveButton.tsx b/invokeai/frontend/web/src/features/controlLayers/components/Tool/ToolMoveButton.tsx index d4912758d0d..8a9b00ae414 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/Tool/ToolMoveButton.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/Tool/ToolMoveButton.tsx @@ -1,5 +1,6 @@ import { IconButton } from '@invoke-ai/ui-library'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { useIsFiltering } from 'features/controlLayers/hooks/useIsFiltering'; import { useIsTransforming } from 'features/controlLayers/hooks/useIsTransforming'; import { toolChanged } from 'features/controlLayers/store/canvasV2Slice'; import { isDrawableEntityType } from 'features/controlLayers/store/types'; @@ -11,6 +12,7 @@ import { PiCursorBold } from 'react-icons/pi'; export const ToolMoveButton = memo(() => { const { t } = useTranslation(); const dispatch = useAppDispatch(); + const isFiltering = useIsFiltering(); const isTransforming = useIsTransforming(); const isSelected = useAppSelector((s) => s.canvasV2.tool.selected === 'move'); const isStaging = useAppSelector((s) => s.canvasV2.session.isStaging); @@ -21,8 +23,8 @@ export const ToolMoveButton = memo(() => { return isDrawableEntityType(s.canvasV2.selectedEntityIdentifier.type); }); const isDisabled = useMemo(() => { - return isTransforming || isStaging || !isDrawingToolAllowed; - }, [isDrawingToolAllowed, isStaging, isTransforming]); + return isTransforming || isFiltering || isStaging || !isDrawingToolAllowed; + }, [isDrawingToolAllowed, isFiltering, isStaging, isTransforming]); const onClick = useCallback(() => { dispatch(toolChanged('move')); diff --git a/invokeai/frontend/web/src/features/controlLayers/components/Tool/ToolRectButton.tsx b/invokeai/frontend/web/src/features/controlLayers/components/Tool/ToolRectButton.tsx index dd57f074f5f..9c908d16f85 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/Tool/ToolRectButton.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/Tool/ToolRectButton.tsx @@ -1,5 +1,6 @@ import { IconButton } from '@invoke-ai/ui-library'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { useIsFiltering } from 'features/controlLayers/hooks/useIsFiltering'; import { useIsTransforming } from 'features/controlLayers/hooks/useIsTransforming'; import { toolChanged } from 'features/controlLayers/store/canvasV2Slice'; import { isDrawableEntityType } from 'features/controlLayers/store/types'; @@ -12,6 +13,7 @@ export const ToolRectButton = memo(() => { const { t } = useTranslation(); const dispatch = useAppDispatch(); const isSelected = useAppSelector((s) => s.canvasV2.tool.selected === 'rect'); + const isFiltering = useIsFiltering(); const isTransforming = useIsTransforming(); const isStaging = useAppSelector((s) => s.canvasV2.session.isStaging); const isDrawingToolAllowed = useAppSelector((s) => { @@ -22,8 +24,8 @@ export const ToolRectButton = memo(() => { }); const isDisabled = useMemo(() => { - return isTransforming || isStaging || !isDrawingToolAllowed; - }, [isDrawingToolAllowed, isStaging, isTransforming]); + return isTransforming || isFiltering || isStaging || !isDrawingToolAllowed; + }, [isDrawingToolAllowed, isFiltering, isStaging, isTransforming]); const onClick = useCallback(() => { dispatch(toolChanged('rect')); diff --git a/invokeai/frontend/web/src/features/controlLayers/components/Tool/ToolViewButton.tsx b/invokeai/frontend/web/src/features/controlLayers/components/Tool/ToolViewButton.tsx index 3fe209a78ba..be45717be67 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/Tool/ToolViewButton.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/Tool/ToolViewButton.tsx @@ -1,5 +1,6 @@ import { IconButton } from '@invoke-ai/ui-library'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { useIsFiltering } from 'features/controlLayers/hooks/useIsFiltering'; import { useIsTransforming } from 'features/controlLayers/hooks/useIsTransforming'; import { toolChanged } from 'features/controlLayers/store/canvasV2Slice'; import { memo, useCallback, useMemo } from 'react'; @@ -11,11 +12,12 @@ export const ToolViewButton = memo(() => { const { t } = useTranslation(); const dispatch = useAppDispatch(); const isTransforming = useIsTransforming(); + const isFiltering = useIsFiltering(); const isStaging = useAppSelector((s) => s.canvasV2.session.isStaging); const isSelected = useAppSelector((s) => s.canvasV2.tool.selected === 'view'); const isDisabled = useMemo(() => { - return isTransforming || isStaging; - }, [isStaging, isTransforming]); + return isTransforming || isFiltering || isStaging; + }, [isFiltering, isStaging, isTransforming]); const onClick = useCallback(() => { dispatch(toolChanged('view')); }, [dispatch]); diff --git a/invokeai/frontend/web/src/features/controlLayers/hooks/useIsFiltering.ts b/invokeai/frontend/web/src/features/controlLayers/hooks/useIsFiltering.ts new file mode 100644 index 00000000000..66fe0123565 --- /dev/null +++ b/invokeai/frontend/web/src/features/controlLayers/hooks/useIsFiltering.ts @@ -0,0 +1,8 @@ +import { useStore } from '@nanostores/react'; +import { useCanvasManager } from 'features/controlLayers/contexts/CanvasManagerProviderGate'; + +export const useIsFiltering = () => { + const canvasManager = useCanvasManager(); + const isFiltering = useStore(canvasManager.filter.$isFiltering); + return isFiltering; +}; diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasFilterModule.ts b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasFilterModule.ts index a0900210487..b6e95a1d8b3 100644 --- a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasFilterModule.ts +++ b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasFilterModule.ts @@ -4,7 +4,7 @@ import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager'; import { getPrefixedId } from 'features/controlLayers/konva/util'; import type { CanvasEntityIdentifier, CanvasImageState, FilterConfig } from 'features/controlLayers/store/types'; import { IMAGE_FILTERS, imageDTOToImageObject } from 'features/controlLayers/store/types'; -import { atom } from 'nanostores'; +import { atom, computed } from 'nanostores'; import type { Logger } from 'roarr'; import { getImageDTO } from 'services/api/endpoints/images'; import type { BatchConfig, ImageDTO, S } from 'services/api/types'; @@ -23,6 +23,7 @@ export class CanvasFilterModule { imageState: CanvasImageState | null = null; $adapter = atom(null); + $isFiltering = computed(this.$adapter, (adapter) => Boolean(adapter)); $isProcessing = atom(false); $config = atom(IMAGE_FILTERS.canny_image_processor.buildDefaults()); @@ -46,6 +47,7 @@ export class CanvasFilterModule { return; } this.$adapter.set(entity.adapter); + this.manager.stateApi.setTool('view'); }; previewFilter = async () => {