mirror of
https://github.com/invoke-ai/InvokeAI.git
synced 2026-04-23 03:00:31 -04:00
feat(ui): more interaction restrictions
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
import { MenuItem } from '@invoke-ai/ui-library';
|
||||
import {
|
||||
useIsSavingCanvas,
|
||||
useSaveBboxAsControlLayer,
|
||||
useSaveBboxAsGlobalIPAdapter,
|
||||
useSaveBboxAsRasterLayer,
|
||||
@@ -8,13 +7,14 @@ import {
|
||||
useSaveBboxToGallery,
|
||||
useSaveCanvasToGallery,
|
||||
} from 'features/controlLayers/hooks/saveCanvasHooks';
|
||||
import { useCanvasIsBusy } from 'features/controlLayers/hooks/useCanvasIsBusy';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PiFloppyDiskBold, PiShareFatBold } from 'react-icons/pi';
|
||||
|
||||
export const CanvasContextMenuItems = memo(() => {
|
||||
const { t } = useTranslation();
|
||||
const isSaving = useIsSavingCanvas();
|
||||
const isBusy = useCanvasIsBusy();
|
||||
const saveCanvasToGallery = useSaveCanvasToGallery();
|
||||
const saveBboxToGallery = useSaveBboxToGallery();
|
||||
const saveBboxAsRegionalGuidanceIPAdapter = useSaveBboxAsRegionalGuidanceIPAdapter();
|
||||
@@ -24,22 +24,22 @@ export const CanvasContextMenuItems = memo(() => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<MenuItem icon={<PiFloppyDiskBold />} isLoading={isSaving.isTrue} onClick={saveCanvasToGallery}>
|
||||
<MenuItem icon={<PiFloppyDiskBold />} isDisabled={isBusy} onClick={saveCanvasToGallery}>
|
||||
{t('controlLayers.saveCanvasToGallery')}
|
||||
</MenuItem>
|
||||
<MenuItem icon={<PiFloppyDiskBold />} isLoading={isSaving.isTrue} onClick={saveBboxToGallery}>
|
||||
<MenuItem icon={<PiFloppyDiskBold />} isDisabled={isBusy} onClick={saveBboxToGallery}>
|
||||
{t('controlLayers.saveBboxToGallery')}
|
||||
</MenuItem>
|
||||
<MenuItem icon={<PiShareFatBold />} isLoading={isSaving.isTrue} onClick={saveBboxAsIPAdapter}>
|
||||
<MenuItem icon={<PiShareFatBold />} isDisabled={isBusy} onClick={saveBboxAsIPAdapter}>
|
||||
{t('controlLayers.sendBboxToGlobalIPAdapter')}
|
||||
</MenuItem>
|
||||
<MenuItem icon={<PiShareFatBold />} isLoading={isSaving.isTrue} onClick={saveBboxAsRegionalGuidanceIPAdapter}>
|
||||
<MenuItem icon={<PiShareFatBold />} isDisabled={isBusy} onClick={saveBboxAsRegionalGuidanceIPAdapter}>
|
||||
{t('controlLayers.sendBboxToRegionalIPAdapter')}
|
||||
</MenuItem>
|
||||
<MenuItem icon={<PiShareFatBold />} isLoading={isSaving.isTrue} onClick={saveBboxAsControlLayer}>
|
||||
<MenuItem icon={<PiShareFatBold />} isDisabled={isBusy} onClick={saveBboxAsControlLayer}>
|
||||
{t('controlLayers.sendBboxToControlLayer')}
|
||||
</MenuItem>
|
||||
<MenuItem icon={<PiShareFatBold />} isLoading={isSaving.isTrue} onClick={saveBboxAsRasterLayer}>
|
||||
<MenuItem icon={<PiShareFatBold />} isDisabled={isBusy} onClick={saveBboxAsRasterLayer}>
|
||||
{t('controlLayers.sendBboxToRasterLayer')}
|
||||
</MenuItem>
|
||||
</>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { IconButton } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { useCanvasIsBusy } from 'features/controlLayers/hooks/useCanvasIsBusy';
|
||||
import { entityDuplicated } from 'features/controlLayers/store/canvasSlice';
|
||||
import { selectSelectedEntityIdentifier } from 'features/controlLayers/store/selectors';
|
||||
import { memo, useCallback } from 'react';
|
||||
@@ -9,6 +10,7 @@ import { PiCopyFill } from 'react-icons/pi';
|
||||
export const EntityListSelectedEntityActionBarDuplicateButton = memo(() => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
const isBusy = useCanvasIsBusy();
|
||||
const selectedEntityIdentifier = useAppSelector(selectSelectedEntityIdentifier);
|
||||
const onClick = useCallback(() => {
|
||||
if (!selectedEntityIdentifier) {
|
||||
@@ -20,7 +22,7 @@ export const EntityListSelectedEntityActionBarDuplicateButton = memo(() => {
|
||||
return (
|
||||
<IconButton
|
||||
onClick={onClick}
|
||||
isDisabled={!selectedEntityIdentifier}
|
||||
isDisabled={!selectedEntityIdentifier || isBusy}
|
||||
size="sm"
|
||||
variant="link"
|
||||
alignSelf="stretch"
|
||||
|
||||
@@ -7,7 +7,8 @@ import { Weight } from 'features/controlLayers/components/common/Weight';
|
||||
import { ControlLayerControlAdapterControlMode } from 'features/controlLayers/components/ControlLayer/ControlLayerControlAdapterControlMode';
|
||||
import { ControlLayerControlAdapterModel } from 'features/controlLayers/components/ControlLayer/ControlLayerControlAdapterModel';
|
||||
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
|
||||
import { useIsSavingCanvas, usePullBboxIntoLayer } from 'features/controlLayers/hooks/saveCanvasHooks';
|
||||
import { usePullBboxIntoLayer } from 'features/controlLayers/hooks/saveCanvasHooks';
|
||||
import { useCanvasIsBusy } from 'features/controlLayers/hooks/useCanvasIsBusy';
|
||||
import { useEntityFilter } from 'features/controlLayers/hooks/useEntityFilter';
|
||||
import {
|
||||
controlLayerBeginEndStepPctChanged,
|
||||
@@ -71,7 +72,7 @@ export const ControlLayerControlAdapter = memo(() => {
|
||||
);
|
||||
|
||||
const pullBboxIntoLayer = usePullBboxIntoLayer(entityIdentifier);
|
||||
const isSaving = useIsSavingCanvas();
|
||||
const isBusy = useCanvasIsBusy();
|
||||
const postUploadAction = useMemo<PostUploadAction>(
|
||||
() => ({ type: 'REPLACE_LAYER_WITH_IMAGE', entityIdentifier }),
|
||||
[entityIdentifier]
|
||||
@@ -94,7 +95,7 @@ export const ControlLayerControlAdapter = memo(() => {
|
||||
/>
|
||||
<IconButton
|
||||
onClick={pullBboxIntoLayer}
|
||||
isLoading={isSaving.isTrue}
|
||||
isDisabled={isBusy}
|
||||
size="sm"
|
||||
alignSelf="stretch"
|
||||
variant="link"
|
||||
@@ -103,7 +104,7 @@ export const ControlLayerControlAdapter = memo(() => {
|
||||
icon={<PiBoundingBoxBold />}
|
||||
/>
|
||||
<IconButton
|
||||
isLoading={isSaving.isTrue}
|
||||
isDisabled={isBusy}
|
||||
size="sm"
|
||||
alignSelf="stretch"
|
||||
variant="link"
|
||||
|
||||
@@ -6,7 +6,8 @@ import { CanvasEntitySettingsWrapper } from 'features/controlLayers/components/c
|
||||
import { Weight } from 'features/controlLayers/components/common/Weight';
|
||||
import { IPAdapterMethod } from 'features/controlLayers/components/IPAdapter/IPAdapterMethod';
|
||||
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
|
||||
import { useIsSavingCanvas, usePullBboxIntoIPAdapter } from 'features/controlLayers/hooks/saveCanvasHooks';
|
||||
import { usePullBboxIntoIPAdapter } from 'features/controlLayers/hooks/saveCanvasHooks';
|
||||
import { useCanvasIsBusy } from 'features/controlLayers/hooks/useCanvasIsBusy';
|
||||
import {
|
||||
ipaBeginEndStepPctChanged,
|
||||
ipaCLIPVisionModelChanged,
|
||||
@@ -87,7 +88,7 @@ export const IPAdapterSettings = memo(() => {
|
||||
[entityIdentifier.id]
|
||||
);
|
||||
const pullBboxIntoIPAdapter = usePullBboxIntoIPAdapter(entityIdentifier);
|
||||
const isSaving = useIsSavingCanvas();
|
||||
const isBusy = useCanvasIsBusy();
|
||||
|
||||
return (
|
||||
<CanvasEntitySettingsWrapper>
|
||||
@@ -103,7 +104,7 @@ export const IPAdapterSettings = memo(() => {
|
||||
</Box>
|
||||
<IconButton
|
||||
onClick={pullBboxIntoIPAdapter}
|
||||
isLoading={isSaving.isTrue}
|
||||
isDisabled={isBusy}
|
||||
variant="ghost"
|
||||
aria-label={t('controlLayers.pullBboxIntoIPAdapter')}
|
||||
tooltip={t('controlLayers.pullBboxIntoIPAdapter')}
|
||||
|
||||
@@ -7,10 +7,8 @@ import { IPAdapterImagePreview } from 'features/controlLayers/components/IPAdapt
|
||||
import { IPAdapterMethod } from 'features/controlLayers/components/IPAdapter/IPAdapterMethod';
|
||||
import { IPAdapterModel } from 'features/controlLayers/components/IPAdapter/IPAdapterModel';
|
||||
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
|
||||
import {
|
||||
useIsSavingCanvas,
|
||||
usePullBboxIntoRegionalGuidanceIPAdapter,
|
||||
} from 'features/controlLayers/hooks/saveCanvasHooks';
|
||||
import { usePullBboxIntoRegionalGuidanceIPAdapter } from 'features/controlLayers/hooks/saveCanvasHooks';
|
||||
import { useCanvasIsBusy } from 'features/controlLayers/hooks/useCanvasIsBusy';
|
||||
import {
|
||||
rgIPAdapterBeginEndStepPctChanged,
|
||||
rgIPAdapterCLIPVisionModelChanged,
|
||||
@@ -107,7 +105,7 @@ export const RegionalGuidanceIPAdapterSettings = memo(({ ipAdapterId, ipAdapterN
|
||||
[entityIdentifier.id, ipAdapterId]
|
||||
);
|
||||
const pullBboxIntoIPAdapter = usePullBboxIntoRegionalGuidanceIPAdapter(entityIdentifier, ipAdapterId);
|
||||
const isSaving = useIsSavingCanvas();
|
||||
const isBusy = useCanvasIsBusy();
|
||||
|
||||
return (
|
||||
<Flex flexDir="column" gap={3}>
|
||||
@@ -135,7 +133,7 @@ export const RegionalGuidanceIPAdapterSettings = memo(({ ipAdapterId, ipAdapterN
|
||||
</Box>
|
||||
<IconButton
|
||||
onClick={pullBboxIntoIPAdapter}
|
||||
isLoading={isSaving.isTrue}
|
||||
isDisabled={isBusy}
|
||||
variant="ghost"
|
||||
aria-label={t('controlLayers.pullBboxIntoIPAdapter')}
|
||||
tooltip={t('controlLayers.pullBboxIntoIPAdapter')}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { IconButton } from '@invoke-ai/ui-library';
|
||||
import { useCanvasManager } from 'features/controlLayers/contexts/CanvasManagerProviderGate';
|
||||
import { useCanvasIsBusy } from 'features/controlLayers/hooks/useCanvasIsBusy';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PiArrowsOut } from 'react-icons/pi';
|
||||
@@ -7,6 +8,7 @@ import { PiArrowsOut } from 'react-icons/pi';
|
||||
export const CanvasToolbarFitBboxToLayersButton = memo(() => {
|
||||
const { t } = useTranslation();
|
||||
const canvasManager = useCanvasManager();
|
||||
const isBusy = useCanvasIsBusy();
|
||||
const onClick = useCallback(() => {
|
||||
canvasManager.bbox.fitToLayers();
|
||||
}, [canvasManager.bbox]);
|
||||
@@ -18,6 +20,7 @@ export const CanvasToolbarFitBboxToLayersButton = memo(() => {
|
||||
aria-label={t('controlLayers.fitBboxToLayers')}
|
||||
tooltip={t('controlLayers.fitBboxToLayers')}
|
||||
icon={<PiArrowsOut />}
|
||||
isDisabled={isBusy}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
import { IconButton, useShiftModifier } from '@invoke-ai/ui-library';
|
||||
import {
|
||||
useIsSavingCanvas,
|
||||
useSaveBboxToGallery,
|
||||
useSaveCanvasToGallery,
|
||||
} from 'features/controlLayers/hooks/saveCanvasHooks';
|
||||
import { useSaveBboxToGallery, useSaveCanvasToGallery } from 'features/controlLayers/hooks/saveCanvasHooks';
|
||||
import { useCanvasIsBusy } from 'features/controlLayers/hooks/useCanvasIsBusy';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PiFloppyDiskBold } from 'react-icons/pi';
|
||||
@@ -11,7 +8,7 @@ import { PiFloppyDiskBold } from 'react-icons/pi';
|
||||
export const CanvasToolbarSaveToGalleryButton = memo(() => {
|
||||
const { t } = useTranslation();
|
||||
const shift = useShiftModifier();
|
||||
const isSaving = useIsSavingCanvas();
|
||||
const isBusy = useCanvasIsBusy();
|
||||
const saveCanvasToGallery = useSaveCanvasToGallery();
|
||||
const saveBboxToGallery = useSaveBboxToGallery();
|
||||
|
||||
@@ -20,9 +17,9 @@ export const CanvasToolbarSaveToGalleryButton = memo(() => {
|
||||
variant="ghost"
|
||||
onClick={shift ? saveBboxToGallery : saveCanvasToGallery}
|
||||
icon={<PiFloppyDiskBold />}
|
||||
isLoading={isSaving.isTrue}
|
||||
aria-label={shift ? t('controlLayers.saveBboxToGallery') : t('controlLayers.saveCanvasToGallery')}
|
||||
tooltip={shift ? t('controlLayers.saveBboxToGallery') : t('controlLayers.saveCanvasToGallery')}
|
||||
isDisabled={isBusy}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
useAddRasterLayer,
|
||||
useAddRegionalGuidance,
|
||||
} from 'features/controlLayers/hooks/addLayerHooks';
|
||||
import { useCanvasIsBusy } from 'features/controlLayers/hooks/useCanvasIsBusy';
|
||||
import type { CanvasEntityIdentifier } from 'features/controlLayers/store/types';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@@ -17,6 +18,7 @@ type Props = {
|
||||
|
||||
export const CanvasEntityAddOfTypeButton = memo(({ type }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const isBusy = useCanvasIsBusy();
|
||||
const addInpaintMask = useAddInpaintMask();
|
||||
const addRegionalGuidance = useAddRegionalGuidance();
|
||||
const addRasterLayer = useAddRasterLayer();
|
||||
@@ -67,6 +69,7 @@ export const CanvasEntityAddOfTypeButton = memo(({ type }: Props) => {
|
||||
icon={<PiPlusBold />}
|
||||
onClick={onClick}
|
||||
alignSelf="stretch"
|
||||
isDisabled={isBusy}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { IconButton } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
|
||||
import { useCanvasIsBusy } from 'features/controlLayers/hooks/useCanvasIsBusy';
|
||||
import { entityDeleted } from 'features/controlLayers/store/canvasSlice';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@@ -10,6 +11,7 @@ export const CanvasEntityDeleteButton = memo(() => {
|
||||
const { t } = useTranslation();
|
||||
const entityIdentifier = useEntityIdentifierContext();
|
||||
const dispatch = useAppDispatch();
|
||||
const isBusy = useCanvasIsBusy();
|
||||
const onClick = useCallback(() => {
|
||||
dispatch(entityDeleted({ entityIdentifier }));
|
||||
}, [dispatch, entityIdentifier]);
|
||||
@@ -24,6 +26,7 @@ export const CanvasEntityDeleteButton = memo(() => {
|
||||
icon={<PiTrashSimpleFill />}
|
||||
onClick={onClick}
|
||||
colorScheme="error"
|
||||
isDisabled={isBusy}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { IconButton } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
|
||||
import { useCanvasIsBusy } from 'features/controlLayers/hooks/useCanvasIsBusy';
|
||||
import { useEntityIsEnabled } from 'features/controlLayers/hooks/useEntityIsEnabled';
|
||||
import { entityIsEnabledToggled } from 'features/controlLayers/store/canvasSlice';
|
||||
import { memo, useCallback } from 'react';
|
||||
@@ -11,6 +12,7 @@ export const CanvasEntityEnabledToggle = memo(() => {
|
||||
const { t } = useTranslation();
|
||||
const entityIdentifier = useEntityIdentifierContext();
|
||||
const isEnabled = useEntityIsEnabled(entityIdentifier);
|
||||
const isBusy = useCanvasIsBusy();
|
||||
const dispatch = useAppDispatch();
|
||||
const onClick = useCallback(() => {
|
||||
dispatch(entityIsEnabledToggled({ entityIdentifier }));
|
||||
@@ -25,6 +27,7 @@ export const CanvasEntityEnabledToggle = memo(() => {
|
||||
alignSelf="stretch"
|
||||
icon={isEnabled ? <PiCircleFill /> : <PiCircleBold />}
|
||||
onClick={onClick}
|
||||
isDisabled={isBusy}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { IconButton } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
|
||||
import { useCanvasIsBusy } from 'features/controlLayers/hooks/useCanvasIsBusy';
|
||||
import { useEntityIsLocked } from 'features/controlLayers/hooks/useEntityIsLocked';
|
||||
import { entityIsLockedToggled } from 'features/controlLayers/store/canvasSlice';
|
||||
import { memo, useCallback } from 'react';
|
||||
@@ -11,6 +12,7 @@ export const CanvasEntityIsLockedToggle = memo(() => {
|
||||
const { t } = useTranslation();
|
||||
const entityIdentifier = useEntityIdentifierContext();
|
||||
const isLocked = useEntityIsLocked(entityIdentifier);
|
||||
const isBusy = useCanvasIsBusy();
|
||||
const dispatch = useAppDispatch();
|
||||
const onClick = useCallback(() => {
|
||||
dispatch(entityIsLockedToggled({ entityIdentifier }));
|
||||
@@ -25,6 +27,7 @@ export const CanvasEntityIsLockedToggle = memo(() => {
|
||||
alignSelf="stretch"
|
||||
icon={isLocked ? <PiLockSimpleFill /> : <PiLockSimpleOpenBold />}
|
||||
onClick={onClick}
|
||||
isDisabled={isBusy}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -2,7 +2,7 @@ import { MenuItem } from '@invoke-ai/ui-library';
|
||||
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
|
||||
import { useCanvasIsBusy } from 'features/controlLayers/hooks/useCanvasIsBusy';
|
||||
import { useIsEntityInteractable } from 'features/controlLayers/hooks/useEntityIsInteractable';
|
||||
import {
|
||||
entityArrangedBackwardOne,
|
||||
entityArrangedForwardOne,
|
||||
@@ -56,7 +56,7 @@ export const CanvasEntityMenuItemsArrange = memo(() => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
const entityIdentifier = useEntityIdentifierContext();
|
||||
const isBusy = useCanvasIsBusy();
|
||||
const isInteractable = useIsEntityInteractable(entityIdentifier);
|
||||
const selectValidActions = useMemo(
|
||||
() =>
|
||||
createMemoizedSelector(selectCanvasSlice, (canvas) => {
|
||||
@@ -88,24 +88,32 @@ export const CanvasEntityMenuItemsArrange = memo(() => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<MenuItem onClick={moveToFront} isDisabled={!validActions.canMoveToFront || isBusy} icon={<PiArrowLineUpBold />}>
|
||||
<MenuItem
|
||||
onClick={moveToFront}
|
||||
isDisabled={!validActions.canMoveToFront || !isInteractable}
|
||||
icon={<PiArrowLineUpBold />}
|
||||
>
|
||||
{t('controlLayers.moveToFront')}
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
onClick={moveForwardOne}
|
||||
isDisabled={!validActions.canMoveForwardOne || isBusy}
|
||||
isDisabled={!validActions.canMoveForwardOne || !isInteractable}
|
||||
icon={<PiArrowUpBold />}
|
||||
>
|
||||
{t('controlLayers.moveForward')}
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
onClick={moveBackwardOne}
|
||||
isDisabled={!validActions.canMoveBackwardOne || isBusy}
|
||||
isDisabled={!validActions.canMoveBackwardOne || !isInteractable}
|
||||
icon={<PiArrowDownBold />}
|
||||
>
|
||||
{t('controlLayers.moveBackward')}
|
||||
</MenuItem>
|
||||
<MenuItem onClick={moveToBack} isDisabled={!validActions.canMoveToBack || isBusy} icon={<PiArrowLineDownBold />}>
|
||||
<MenuItem
|
||||
onClick={moveToBack}
|
||||
isDisabled={!validActions.canMoveToBack || !isInteractable}
|
||||
icon={<PiArrowLineDownBold />}
|
||||
>
|
||||
{t('controlLayers.moveToBack')}
|
||||
</MenuItem>
|
||||
</>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { logger } from 'app/logging/logger';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { buildUseBoolean } from 'common/hooks/useBoolean';
|
||||
import { isOk, withResultAsync } from 'common/util/result';
|
||||
import { useCanvasManager } from 'features/controlLayers/contexts/CanvasManagerProviderGate';
|
||||
import { selectDefaultControlAdapter, selectDefaultIPAdapter } from 'features/controlLayers/hooks/addLayerHooks';
|
||||
@@ -32,8 +31,6 @@ import type { ImageDTO } from 'services/api/types';
|
||||
|
||||
const log = logger('canvas');
|
||||
|
||||
export const [useIsSavingCanvas] = buildUseBoolean(false);
|
||||
|
||||
type UseSaveCanvasArg = {
|
||||
region: 'canvas' | 'bbox';
|
||||
saveToGallery: boolean;
|
||||
@@ -44,11 +41,8 @@ const useSaveCanvas = ({ region, saveToGallery, onSave }: UseSaveCanvasArg) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const canvasManager = useCanvasManager();
|
||||
const isSaving = useIsSavingCanvas();
|
||||
|
||||
const saveCanvas = useCallback(async () => {
|
||||
isSaving.setTrue();
|
||||
|
||||
const rect =
|
||||
region === 'bbox' ? canvasManager.stateApi.getBbox().rect : canvasManager.stage.getVisibleRect('raster_layer');
|
||||
|
||||
@@ -65,18 +59,7 @@ const useSaveCanvas = ({ region, saveToGallery, onSave }: UseSaveCanvasArg) => {
|
||||
log.error({ error: serializeError(result.error) }, 'Failed to save canvas to gallery');
|
||||
toast({ title: t('controlLayers.savedToGalleryError'), status: 'error' });
|
||||
}
|
||||
|
||||
isSaving.setFalse();
|
||||
}, [
|
||||
canvasManager.compositor,
|
||||
canvasManager.stage,
|
||||
canvasManager.stateApi,
|
||||
isSaving,
|
||||
onSave,
|
||||
region,
|
||||
saveToGallery,
|
||||
t,
|
||||
]);
|
||||
}, [canvasManager.compositor, canvasManager.stage, canvasManager.stateApi, onSave, region, saveToGallery, t]);
|
||||
|
||||
return saveCanvas;
|
||||
};
|
||||
|
||||
@@ -15,6 +15,7 @@ export const useEntityFilter = (entityIdentifier: CanvasEntityIdentifier | null)
|
||||
const adapter = useEntityAdapterSafe(entityIdentifier);
|
||||
const isBusy = useCanvasIsBusy();
|
||||
const isInteractable = useStore(adapter?.$isInteractable ?? $fallbackFalse);
|
||||
const isEmpty = useStore(adapter?.$isEmpty ?? $fallbackFalse);
|
||||
|
||||
const isDisabled = useMemo(() => {
|
||||
if (!entityIdentifier) {
|
||||
@@ -32,8 +33,11 @@ export const useEntityFilter = (entityIdentifier: CanvasEntityIdentifier | null)
|
||||
if (!isInteractable) {
|
||||
return true;
|
||||
}
|
||||
if (isEmpty) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}, [entityIdentifier, adapter, isBusy, isInteractable]);
|
||||
}, [entityIdentifier, adapter, isBusy, isInteractable, isEmpty]);
|
||||
|
||||
const start = useCallback(() => {
|
||||
if (isDisabled) {
|
||||
|
||||
@@ -15,20 +15,7 @@ export const useEntityTransform = (entityIdentifier: CanvasEntityIdentifier | nu
|
||||
const adapter = useEntityAdapterSafe(entityIdentifier);
|
||||
const isBusy = useCanvasIsBusy();
|
||||
const isInteractable = useStore(adapter?.$isInteractable ?? $fallbackFalse);
|
||||
|
||||
const start = useCallback(() => {
|
||||
if (!entityIdentifier) {
|
||||
return;
|
||||
}
|
||||
if (!isTransformableEntityIdentifier(entityIdentifier)) {
|
||||
return;
|
||||
}
|
||||
const adapter = canvasManager.getAdapter(entityIdentifier);
|
||||
if (!adapter) {
|
||||
return;
|
||||
}
|
||||
adapter.transformer.startTransform();
|
||||
}, [entityIdentifier, canvasManager]);
|
||||
const isEmpty = useStore(adapter?.$isEmpty ?? $fallbackFalse);
|
||||
|
||||
const isDisabled = useMemo(() => {
|
||||
if (!entityIdentifier) {
|
||||
@@ -46,8 +33,28 @@ export const useEntityTransform = (entityIdentifier: CanvasEntityIdentifier | nu
|
||||
if (!isInteractable) {
|
||||
return true;
|
||||
}
|
||||
if (isEmpty) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}, [entityIdentifier, adapter, isBusy, isInteractable]);
|
||||
}, [entityIdentifier, adapter, isBusy, isInteractable, isEmpty]);
|
||||
|
||||
const start = useCallback(() => {
|
||||
if (isDisabled) {
|
||||
return;
|
||||
}
|
||||
if (!entityIdentifier) {
|
||||
return;
|
||||
}
|
||||
if (!isTransformableEntityIdentifier(entityIdentifier)) {
|
||||
return;
|
||||
}
|
||||
const adapter = canvasManager.getAdapter(entityIdentifier);
|
||||
if (!adapter) {
|
||||
return;
|
||||
}
|
||||
adapter.transformer.startTransform();
|
||||
}, [isDisabled, entityIdentifier, canvasManager]);
|
||||
|
||||
return { isDisabled, start } as const;
|
||||
};
|
||||
|
||||
@@ -106,18 +106,15 @@ export abstract class CanvasEntityAdapterBase<
|
||||
*/
|
||||
$isHidden = atom(false);
|
||||
/**
|
||||
* Whether this entity has objects. This is computed based on the entity's objects.
|
||||
* Whether this entity is empty. This is computed based on the entity's objects.
|
||||
*/
|
||||
$hasObjects = atom(false);
|
||||
$isEmpty = atom(true);
|
||||
/**
|
||||
* Whether this entity is interactable. This is computed based on the entity's locked, disabled, and hidden states.
|
||||
*/
|
||||
$isInteractable = computed(
|
||||
[this.$isLocked, this.$isDisabled, this.$isHidden, this.$hasObjects],
|
||||
(isLocked, isDisabled, isHidden, hasObjects) => {
|
||||
return !isLocked && !isDisabled && !isHidden && hasObjects;
|
||||
}
|
||||
);
|
||||
$isInteractable = computed([this.$isLocked, this.$isDisabled, this.$isHidden], (isLocked, isDisabled, isHidden) => {
|
||||
return !isLocked && !isDisabled && !isHidden;
|
||||
});
|
||||
|
||||
constructor(entityIdentifier: CanvasEntityIdentifier<T['type']>, manager: CanvasManager, adapterType: U) {
|
||||
super();
|
||||
@@ -205,7 +202,7 @@ export abstract class CanvasEntityAdapterBase<
|
||||
* Synchronizes the entity's objects with the canvas.
|
||||
*/
|
||||
syncObjects = async () => {
|
||||
this.$hasObjects.set(this.state.objects.length > 0);
|
||||
this.$isEmpty.set(this.state.objects.length === 0);
|
||||
const didRender = await this.renderer.render();
|
||||
if (didRender) {
|
||||
// If the objects have changed, we need to recalculate the transformer's bounding box.
|
||||
|
||||
@@ -325,18 +325,6 @@ export class CanvasEntityObjectRenderer extends CanvasModuleBase {
|
||||
return didRender;
|
||||
};
|
||||
|
||||
hideObjects = (except: string[] = []) => {
|
||||
for (const renderer of this.renderers.values()) {
|
||||
renderer.setVisibility(except.includes(renderer.id));
|
||||
}
|
||||
};
|
||||
|
||||
showObjects = (except: string[] = []) => {
|
||||
for (const renderer of this.renderers.values()) {
|
||||
renderer.setVisibility(!except.includes(renderer.id));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Determines if the objects in the renderer require a pixel bbox calculation.
|
||||
*
|
||||
|
||||
@@ -487,7 +487,7 @@ export class CanvasEntityTransformer extends CanvasModuleBase {
|
||||
const tool = this.manager.tool.$tool.get();
|
||||
const isSelected = this.manager.stateApi.getIsSelected(this.parent.id);
|
||||
|
||||
if (!this.parent.$isInteractable.get()) {
|
||||
if (this.parent.$isEmpty.get()) {
|
||||
// The layer is totally empty, we can just disable the layer
|
||||
this.parent.konva.layer.listening(false);
|
||||
this._setInteractionMode('off');
|
||||
|
||||
Reference in New Issue
Block a user