feat(ui): lock down bbox while staging

This commit is contained in:
psychedelicious
2024-09-19 23:01:07 +10:00
parent a5f4ade7e9
commit be53d82f66
16 changed files with 92 additions and 47 deletions

View File

@@ -1,9 +1,12 @@
import { logger } from 'app/logging/logger';
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
import { bboxOptimalDimensionChanged, bboxSyncedToOptimalDimension } from 'features/controlLayers/store/canvasSlice';
import { selectIsStaging } from 'features/controlLayers/store/canvasStagingAreaSlice';
import { loraDeleted } from 'features/controlLayers/store/lorasSlice';
import { modelChanged, vaeSelected } from 'features/controlLayers/store/paramsSlice';
import { modelSelected } from 'features/parameters/store/actions';
import { zParameterModel } from 'features/parameters/types/parameterSchemas';
import { getOptimalDimension } from 'features/parameters/util/optimalDimension';
import { toast } from 'features/toast/toast';
import { t } from 'i18next';
@@ -68,6 +71,11 @@ export const addModelSelectedListener = (startAppListening: AppStartListening) =
}
dispatch(modelChanged({ model: newModel, previousModel: state.params.model }));
// When staging, we don't want to change the bbox, but we must keep the optimal dimension in sync.
dispatch(bboxOptimalDimensionChanged({ optimalDimension: getOptimalDimension(newModel) }));
if (!selectIsStaging(state)) {
dispatch(bboxSyncedToOptimalDimension());
}
},
});
};

View File

@@ -3,12 +3,13 @@ import type { AppStartListening } from 'app/store/middleware/listenerMiddleware'
import type { AppDispatch, RootState } from 'app/store/store';
import type { SerializableObject } from 'common/types';
import {
bboxHeightChanged,
bboxWidthChanged,
bboxOptimalDimensionChanged,
bboxSyncedToOptimalDimension,
controlLayerModelChanged,
referenceImageIPAdapterModelChanged,
rgIPAdapterModelChanged,
} from 'features/controlLayers/store/canvasSlice';
import { selectIsStaging } from 'features/controlLayers/store/canvasStagingAreaSlice';
import { loraDeleted } from 'features/controlLayers/store/lorasSlice';
import {
clipEmbedModelSelected,
@@ -20,10 +21,9 @@ import {
} from 'features/controlLayers/store/paramsSlice';
import { selectCanvasSlice } from 'features/controlLayers/store/selectors';
import { getEntityIdentifier } from 'features/controlLayers/store/types';
import { calculateNewSize } from 'features/parameters/components/Bbox/calculateNewSize';
import { postProcessingModelChanged, upscaleModelChanged } from 'features/parameters/store/upscaleSlice';
import { zParameterModel, zParameterVAEModel } from 'features/parameters/types/parameterSchemas';
import { getIsSizeOptimal, getOptimalDimension } from 'features/parameters/util/optimalDimension';
import { getOptimalDimension } from 'features/parameters/util/optimalDimension';
import type { Logger } from 'roarr';
import { modelConfigsAdapterSelectors, modelsApi } from 'services/api/endpoints/models';
import type { AnyModelConfig } from 'services/api/types';
@@ -95,15 +95,11 @@ const handleMainModels: ModelHandler = (models, state, dispatch, log) => {
const result = zParameterModel.safeParse(defaultModelInList);
if (result.success) {
dispatch(modelChanged({ model: defaultModelInList, previousModel: currentModel }));
const { bbox } = selectCanvasSlice(state);
const optimalDimension = getOptimalDimension(defaultModelInList);
if (getIsSizeOptimal(bbox.rect.width, bbox.rect.height, optimalDimension)) {
return;
// When staging, we don't want to change the bbox, but we must keep the optimal dimension in sync.
dispatch(bboxOptimalDimensionChanged({ optimalDimension: getOptimalDimension(defaultModelInList) }));
if (!selectIsStaging(state)) {
dispatch(bboxSyncedToOptimalDimension());
}
const { width, height } = calculateNewSize(bbox.aspectRatio.value, optimalDimension * optimalDimension);
dispatch(bboxWidthChanged({ width }));
dispatch(bboxHeightChanged({ height }));
return;
}
}
@@ -116,6 +112,11 @@ const handleMainModels: ModelHandler = (models, state, dispatch, log) => {
}
dispatch(modelChanged({ model: result.data, previousModel: currentModel }));
// When staging, we don't want to change the bbox, but we must keep the optimal dimension in sync.
dispatch(bboxOptimalDimensionChanged({ optimalDimension: getOptimalDimension(result.data) }));
if (!selectIsStaging(state)) {
dispatch(bboxSyncedToOptimalDimension());
}
};
const handleRefinerModels: ModelHandler = (models, state, dispatch, _log) => {

View File

@@ -1,5 +1,6 @@
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
import { bboxHeightChanged, bboxWidthChanged } from 'features/controlLayers/store/canvasSlice';
import { selectIsStaging } from 'features/controlLayers/store/canvasStagingAreaSlice';
import {
setCfgRescaleMultiplier,
setCfgScale,
@@ -96,13 +97,15 @@ export const addSetDefaultSettingsListener = (startAppListening: AppStartListeni
}
}
const setSizeOptions = { updateAspectRatio: true, clamp: true };
if (width) {
const isStaging = selectIsStaging(getState());
if (!isStaging && width) {
if (isParameterWidth(width)) {
dispatch(bboxWidthChanged({ width, ...setSizeOptions }));
}
}
if (height) {
if (!isStaging && height) {
if (isParameterHeight(height)) {
dispatch(bboxHeightChanged({ height, ...setSizeOptions }));
}

View File

@@ -6,6 +6,7 @@ import IAIDndImage from 'common/components/IAIDndImage';
import IAIDndImageIcon from 'common/components/IAIDndImageIcon';
import { useNanoid } from 'common/hooks/useNanoid';
import { bboxHeightChanged, bboxWidthChanged } from 'features/controlLayers/store/canvasSlice';
import { selectIsStaging } from 'features/controlLayers/store/canvasStagingAreaSlice';
import { selectOptimalDimension } from 'features/controlLayers/store/selectors';
import type { ImageWithDims } from 'features/controlLayers/store/types';
import type { ImageDraggableData, TypesafeDroppableData } from 'features/dnd/types';
@@ -27,6 +28,7 @@ type Props = {
export const IPAdapterImagePreview = memo(({ image, onChangeImage, droppableData, postUploadAction }: Props) => {
const { t } = useTranslation();
const dispatch = useAppDispatch();
const isStaging = useAppSelector(selectIsStaging);
const isConnected = useStore($isConnected);
const optimalDimension = useAppSelector(selectOptimalDimension);
const shift = useShiftModifier();
@@ -95,6 +97,7 @@ export const IPAdapterImagePreview = memo(({ image, onChangeImage, droppableData
onClick={handleSetControlImageToDimensions}
icon={<PiRulerBold size={16} />}
tooltip={shift ? t('controlLayers.useSizeIgnoreModel') : t('controlLayers.useSizeOptimizeForModel')}
isDisabled={isStaging}
/>
</Flex>
)}

View File

@@ -6,7 +6,6 @@ import { deepClone } from 'common/util/deepClone';
import { roundDownToMultiple, roundToMultiple } from 'common/util/roundDownToMultiple';
import { getPrefixedId } from 'features/controlLayers/konva/util';
import { canvasReset } from 'features/controlLayers/store/actions';
import { modelChanged } from 'features/controlLayers/store/paramsSlice';
import {
selectAllEntities,
selectAllEntitiesOfType,
@@ -25,7 +24,7 @@ import { simplifyFlatNumbersArray } from 'features/controlLayers/util/simplify';
import { zModelIdentifierField } from 'features/nodes/types/common';
import { calculateNewSize } from 'features/parameters/components/Bbox/calculateNewSize';
import { ASPECT_RATIO_MAP } from 'features/parameters/components/Bbox/constants';
import { getIsSizeOptimal, getOptimalDimension } from 'features/parameters/util/optimalDimension';
import { getIsSizeOptimal } from 'features/parameters/util/optimalDimension';
import type { IRect } from 'konva/lib/types';
import { merge, omit } from 'lodash-es';
import type { UndoableOptions } from 'redux-undo';
@@ -749,6 +748,22 @@ export const canvasSlice = createSlice({
syncScaledSize(state);
},
bboxOptimalDimensionChanged: (state, action: PayloadAction<{ optimalDimension: number }>) => {
// When staging, we don't want to change the bbox, but we must keep the optimal dimension in sync.
// This action does the syncing. `bboxSyncedToOptimalDimension` below will actually change the bbox,
// and is only called when we are not staging.
const { optimalDimension } = action.payload;
state.bbox.optimalDimension = optimalDimension;
},
bboxSyncedToOptimalDimension: (state) => {
const { optimalDimension } = state.bbox;
if (!getIsSizeOptimal(state.bbox.rect.width, state.bbox.rect.height, optimalDimension)) {
const bboxDims = calculateNewSize(state.bbox.aspectRatio.value, optimalDimension * optimalDimension);
state.bbox.rect.width = bboxDims.width;
state.bbox.rect.height = bboxDims.height;
syncScaledSize(state);
}
},
//#region Shared entity
entitySelected: (state, action: PayloadAction<EntityIdentifierPayload>) => {
const { entityIdentifier } = action.payload;
@@ -1052,27 +1067,6 @@ export const canvasSlice = createSlice({
canvasClearHistory: () => {},
},
extraReducers(builder) {
builder.addCase(modelChanged, (state, action) => {
const { model, previousModel } = action.payload;
// If the model base changes (e.g. SD1.5 -> SDXL), we need to change a few things
if (model === null || previousModel?.base === model.base) {
return;
}
// Update the bbox size to match the new model's optimal size
const optimalDimension = getOptimalDimension(model);
state.bbox.optimalDimension = optimalDimension;
if (!getIsSizeOptimal(state.bbox.rect.width, state.bbox.rect.height, optimalDimension)) {
const bboxDims = calculateNewSize(state.bbox.aspectRatio.value, optimalDimension * optimalDimension);
state.bbox.rect.width = bboxDims.width;
state.bbox.rect.height = bboxDims.height;
syncScaledSize(state);
}
});
builder.addCase(canvasReset, (state) => {
return resetState(state);
});
@@ -1135,6 +1129,8 @@ export const {
bboxAspectRatioIdChanged,
bboxDimensionsSwapped,
bboxSizeOptimized,
bboxOptimalDimensionChanged,
bboxSyncedToOptimalDimension,
// Raster layers
rasterLayerAdded,
// rasterLayerRecalled,
@@ -1215,6 +1211,11 @@ export const canvasUndoableConfig: UndoableOptions<CanvasState, UnknownAction> =
if (!action.type.startsWith(canvasSlice.name)) {
return false;
}
if (bboxOptimalDimensionChanged.match(action)) {
// This action is not triggered by the user. it's dispatched when the model is changed and will have no visible
// effect on the canvas.
return false;
}
// Throttle rapid actions of the same type
filter = actionsThrottlingFilter(action);
return filter;

View File

@@ -4,6 +4,7 @@ import { skipToken } from '@reduxjs/toolkit/query';
import { adHocPostProcessingRequested } from 'app/store/middleware/listenerMiddleware/listeners/addAdHocPostProcessingRequestedListener';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { INTERACTION_SCOPES } from 'common/hooks/interactionScopes';
import { selectIsStaging } from 'features/controlLayers/store/canvasStagingAreaSlice';
import { DeleteImageButton } from 'features/deleteImageModal/components/DeleteImageButton';
import { imagesToDeleteSelected } from 'features/deleteImageModal/store/slice';
import SingleSelectionMenuItems from 'features/gallery/components/ImageContextMenu/SingleSelectionMenuItems';
@@ -34,6 +35,7 @@ import { $isConnected, $progressImage } from 'services/events/stores';
const CurrentImageButtons = () => {
const dispatch = useAppDispatch();
const isConnected = useStore($isConnected);
const isStaging = useAppSelector(selectIsStaging);
const lastSelectedImage = useAppSelector(selectLastSelectedImage);
const progressImage = useStore($progressImage);
const shouldDisableToolbarButtons = useMemo(() => {
@@ -59,8 +61,11 @@ const CurrentImageButtons = () => {
}, [getAndLoadEmbeddedWorkflow, lastSelectedImage]);
const handleUseSize = useCallback(() => {
if (isStaging) {
return;
}
parseAndRecallImageDimensions(lastSelectedImage);
}, [lastSelectedImage]);
}, [isStaging, lastSelectedImage]);
const handleClickUpscale = useCallback(() => {
if (!imageDTO) {
return;
@@ -179,6 +184,7 @@ const CurrentImageButtons = () => {
tooltip={`${t('parameters.useSize')} (D)`}
aria-label={`${t('parameters.useSize')} (D)`}
onClick={handleUseSize}
isDisabled={isStaging}
/>
<IconButton
isLoading={isLoadingMetadata}

View File

@@ -1,5 +1,6 @@
import { skipToken } from '@reduxjs/toolkit/query';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { selectIsStaging } from 'features/controlLayers/store/canvasStagingAreaSlice';
import { handlers, parseAndRecallAllMetadata, parseAndRecallPrompts } from 'features/metadata/util/handlers';
import { $stylePresetModalState } from 'features/stylePresets/store/stylePresetModal';
import {
@@ -17,6 +18,7 @@ export const useImageActions = (image_name?: string) => {
const dispatch = useAppDispatch();
const { t } = useTranslation();
const activeStylePresetId = useAppSelector(selectStylePresetActivePresetId);
const isStaging = useAppSelector(selectIsStaging);
const activeTabName = useAppSelector(selectActiveTab);
const { metadata, isLoading: isLoadingMetadata } = useDebouncedMetadata(image_name);
const [hasMetadata, setHasMetadata] = useState(false);
@@ -66,9 +68,9 @@ export const useImageActions = (image_name?: string) => {
}, [dispatch, activeStylePresetId, t]);
const recallAll = useCallback(() => {
parseAndRecallAllMetadata(metadata, activeTabName === 'canvas');
parseAndRecallAllMetadata(metadata, activeTabName === 'canvas', isStaging ? ['width', 'height'] : []);
clearStylePreset();
}, [activeTabName, metadata, clearStylePreset]);
}, [metadata, activeTabName, isStaging, clearStylePreset]);
const remix = useCallback(() => {
// Recalls all metadata parameters except seed

View File

@@ -4,6 +4,7 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import type { SingleValue } from 'chakra-react-select';
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
import { bboxAspectRatioIdChanged } from 'features/controlLayers/store/canvasSlice';
import { selectIsStaging } from 'features/controlLayers/store/canvasStagingAreaSlice';
import { selectAspectRatioID } from 'features/controlLayers/store/selectors';
import { isAspectRatioID } from 'features/controlLayers/store/types';
import { ASPECT_RATIO_OPTIONS } from 'features/parameters/components/Bbox/constants';
@@ -14,6 +15,7 @@ export const BboxAspectRatioSelect = memo(() => {
const { t } = useTranslation();
const dispatch = useAppDispatch();
const id = useAppSelector(selectAspectRatioID);
const isStaging = useAppSelector(selectIsStaging);
const onChange = useCallback(
(v: SingleValue<ComboboxOption>) => {
@@ -28,7 +30,7 @@ export const BboxAspectRatioSelect = memo(() => {
const value = useMemo(() => ASPECT_RATIO_OPTIONS.filter((o) => o.value === id)[0], [id]);
return (
<FormControl>
<FormControl isDisabled={isStaging}>
<InformationalPopover feature="paramAspect">
<FormLabel>{t('parameters.aspect')}</FormLabel>
</InformationalPopover>

View File

@@ -2,6 +2,7 @@ import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
import { bboxHeightChanged } from 'features/controlLayers/store/canvasSlice';
import { selectIsStaging } from 'features/controlLayers/store/canvasStagingAreaSlice';
import { selectHeight, selectOptimalDimension } from 'features/controlLayers/store/selectors';
import { selectHeightConfig } from 'features/system/store/configSlice';
import { memo, useCallback, useMemo } from 'react';
@@ -13,6 +14,7 @@ export const BboxHeight = memo(() => {
const optimalDimension = useAppSelector(selectOptimalDimension);
const height = useAppSelector(selectHeight);
const config = useAppSelector(selectHeightConfig);
const isStaging = useAppSelector(selectIsStaging);
const onChange = useCallback(
(v: number) => {
@@ -27,7 +29,7 @@ export const BboxHeight = memo(() => {
);
return (
<FormControl>
<FormControl isDisabled={isStaging}>
<InformationalPopover feature="paramHeight">
<FormLabel>{t('parameters.height')}</FormLabel>
</InformationalPopover>

View File

@@ -2,6 +2,7 @@ import { IconButton } from '@invoke-ai/ui-library';
import { createSelector } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { bboxAspectRatioLockToggled } from 'features/controlLayers/store/canvasSlice';
import { selectIsStaging } from 'features/controlLayers/store/canvasStagingAreaSlice';
import { selectCanvasSlice } from 'features/controlLayers/store/selectors';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
@@ -13,6 +14,7 @@ export const BboxLockAspectRatioButton = memo(() => {
const { t } = useTranslation();
const dispatch = useAppDispatch();
const isLocked = useAppSelector(selectAspectRatioIsLocked);
const isStaging = useAppSelector(selectIsStaging);
const onClick = useCallback(() => {
dispatch(bboxAspectRatioLockToggled());
}, [dispatch]);
@@ -25,6 +27,7 @@ export const BboxLockAspectRatioButton = memo(() => {
variant={isLocked ? 'outline' : 'ghost'}
size="sm"
icon={isLocked ? <PiLockSimpleFill /> : <PiLockSimpleOpenBold />}
isDisabled={isStaging}
/>
);
});

View File

@@ -4,6 +4,7 @@ import { createSelector } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
import { bboxScaleMethodChanged } from 'features/controlLayers/store/canvasSlice';
import { selectIsStaging } from 'features/controlLayers/store/canvasStagingAreaSlice';
import { selectCanvasSlice } from 'features/controlLayers/store/selectors';
import { isBoundingBoxScaleMethod } from 'features/controlLayers/store/types';
import { memo, useCallback, useMemo } from 'react';
@@ -15,6 +16,7 @@ const BboxScaleMethod = () => {
const dispatch = useAppDispatch();
const { t } = useTranslation();
const scaleMethod = useAppSelector(selectScaleMethod);
const isStaging = useAppSelector(selectIsStaging);
const OPTIONS: ComboboxOption[] = useMemo(
() => [
@@ -38,7 +40,7 @@ const BboxScaleMethod = () => {
const value = useMemo(() => OPTIONS.find((o) => o.value === scaleMethod), [scaleMethod, OPTIONS]);
return (
<FormControl>
<FormControl isDisabled={isStaging}>
<InformationalPopover feature="scaleBeforeProcessing">
<FormLabel>{t('parameters.scaleBeforeProcessing')}</FormLabel>
</InformationalPopover>

View File

@@ -2,6 +2,7 @@ import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@
import { createSelector } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { bboxScaledHeightChanged } from 'features/controlLayers/store/canvasSlice';
import { selectIsStaging } from 'features/controlLayers/store/canvasStagingAreaSlice';
import { selectCanvasSlice, selectOptimalDimension } from 'features/controlLayers/store/selectors';
import { selectConfigSlice } from 'features/system/store/configSlice';
import { memo, useCallback } from 'react';
@@ -17,6 +18,7 @@ const selectScaledBoundingBoxHeightConfig = createSelector(
const BboxScaledHeight = () => {
const { t } = useTranslation();
const dispatch = useAppDispatch();
const isStaging = useAppSelector(selectIsStaging);
const optimalDimension = useAppSelector(selectOptimalDimension);
const isManual = useAppSelector(selectIsManual);
const scaledHeight = useAppSelector(selectScaledHeight);
@@ -30,7 +32,7 @@ const BboxScaledHeight = () => {
);
return (
<FormControl isDisabled={!isManual}>
<FormControl isDisabled={!isManual || isStaging}>
<FormLabel>{t('parameters.scaledHeight')}</FormLabel>
<CompositeSlider
min={config.sliderMin}

View File

@@ -2,6 +2,7 @@ import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@
import { createSelector } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { bboxScaledWidthChanged } from 'features/controlLayers/store/canvasSlice';
import { selectIsStaging } from 'features/controlLayers/store/canvasStagingAreaSlice';
import { selectCanvasSlice, selectOptimalDimension } from 'features/controlLayers/store/selectors';
import { selectConfigSlice } from 'features/system/store/configSlice';
import { memo, useCallback } from 'react';
@@ -17,6 +18,7 @@ const selectScaledBoundingBoxWidthConfig = createSelector(
const BboxScaledWidth = () => {
const { t } = useTranslation();
const dispatch = useAppDispatch();
const isStaging = useAppSelector(selectIsStaging);
const optimalDimension = useAppSelector(selectOptimalDimension);
const isManual = useAppSelector(selectIsManual);
const scaledWidth = useAppSelector(selectScaledWidth);
@@ -29,7 +31,7 @@ const BboxScaledWidth = () => {
);
return (
<FormControl isDisabled={!isManual}>
<FormControl isDisabled={!isManual || isStaging}>
<FormLabel>{t('parameters.scaledWidth')}</FormLabel>
<CompositeSlider
min={config.sliderMin}

View File

@@ -2,6 +2,7 @@ import { IconButton } from '@invoke-ai/ui-library';
import { createSelector } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { bboxSizeOptimized } from 'features/controlLayers/store/canvasSlice';
import { selectIsStaging } from 'features/controlLayers/store/canvasStagingAreaSlice';
import { selectCanvasSlice, selectOptimalDimension } from 'features/controlLayers/store/selectors';
import { getIsSizeTooLarge, getIsSizeTooSmall } from 'features/parameters/util/optimalDimension';
import { memo, useCallback, useMemo } from 'react';
@@ -14,6 +15,7 @@ const selectHeight = createSelector(selectCanvasSlice, (canvas) => canvas.bbox.r
export const BboxSetOptimalSizeButton = memo(() => {
const { t } = useTranslation();
const dispatch = useAppDispatch();
const isStaging = useAppSelector(selectIsStaging);
const width = useAppSelector(selectWidth);
const height = useAppSelector(selectHeight);
const optimalDimension = useAppSelector(selectOptimalDimension);
@@ -47,6 +49,7 @@ export const BboxSetOptimalSizeButton = memo(() => {
size="sm"
icon={<PiSparkleFill />}
colorScheme={isSizeTooSmall || isSizeTooLarge ? 'warning' : 'base'}
isDisabled={isStaging}
/>
);
});

View File

@@ -1,6 +1,7 @@
import { IconButton } from '@invoke-ai/ui-library';
import { useAppDispatch } from 'app/store/storeHooks';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { bboxDimensionsSwapped } from 'features/controlLayers/store/canvasSlice';
import { selectIsStaging } from 'features/controlLayers/store/canvasStagingAreaSlice';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { PiArrowsDownUpBold } from 'react-icons/pi';
@@ -8,6 +9,7 @@ import { PiArrowsDownUpBold } from 'react-icons/pi';
export const BboxSwapDimensionsButton = memo(() => {
const { t } = useTranslation();
const dispatch = useAppDispatch();
const isStaging = useAppSelector(selectIsStaging);
const onClick = useCallback(() => {
dispatch(bboxDimensionsSwapped());
}, [dispatch]);
@@ -19,6 +21,7 @@ export const BboxSwapDimensionsButton = memo(() => {
variant="ghost"
size="sm"
icon={<PiArrowsDownUpBold />}
isDisabled={isStaging}
/>
);
});

View File

@@ -2,6 +2,7 @@ import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
import { bboxWidthChanged } from 'features/controlLayers/store/canvasSlice';
import { selectIsStaging } from 'features/controlLayers/store/canvasStagingAreaSlice';
import { selectOptimalDimension, selectWidth } from 'features/controlLayers/store/selectors';
import { selectWidthConfig } from 'features/system/store/configSlice';
import { memo, useCallback, useMemo } from 'react';
@@ -13,6 +14,7 @@ export const BboxWidth = memo(() => {
const width = useAppSelector(selectWidth);
const optimalDimension = useAppSelector(selectOptimalDimension);
const config = useAppSelector(selectWidthConfig);
const isStaging = useAppSelector(selectIsStaging);
const onChange = useCallback(
(v: number) => {
@@ -27,7 +29,7 @@ export const BboxWidth = memo(() => {
);
return (
<FormControl>
<FormControl isDisabled={isStaging}>
<InformationalPopover feature="paramWidth">
<FormLabel>{t('parameters.width')}</FormLabel>
</InformationalPopover>