feat(ui): rough out undo/redo on canvas

This commit is contained in:
psychedelicious 2024-08-27 12:05:45 +10:00
parent 89c79276f3
commit f126a61f66
94 changed files with 586 additions and 431 deletions

View File

@ -4,7 +4,8 @@ import {
sessionStagingAreaImageAccepted,
sessionStagingAreaReset,
} from 'features/controlLayers/store/canvasSessionSlice';
import { rasterLayerAdded } from 'features/controlLayers/store/canvasV2Slice';
import { rasterLayerAdded } from 'features/controlLayers/store/canvasSlice';
import { selectCanvasSlice } from 'features/controlLayers/store/selectors';
import type { CanvasRasterLayerState } from 'features/controlLayers/store/types';
import { imageDTOToImageObject } from 'features/controlLayers/store/types';
import { toast } from 'features/toast/toast';
@ -58,7 +59,7 @@ export const addStagingListeners = (startAppListening: AppStartListening) => {
const stagingAreaImage = state.canvasSession.stagedImages[index];
assert(stagingAreaImage, 'No staged image found to accept');
const { x, y } = state.canvasV2.bbox.rect;
const { x, y } = selectCanvasSlice(state).bbox.rect;
const { imageDTO, offsetX, offsetY } = stagingAreaImage;
const imageObject = imageDTOToImageObject(imageDTO);

View File

@ -1,6 +1,7 @@
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
import { selectCanvasSlice } from 'features/controlLayers/store/selectors';
import { getImageUsage } from 'features/deleteImageModal/store/selectors';
import { nodeEditorReset } from 'features/nodes/store/nodesSlice';
import { nodeEditorReset, selectNodesSlice } from 'features/nodes/store/nodesSlice';
import { imagesApi } from 'services/api/endpoints/images';
export const addDeleteBoardAndImagesFulfilledListener = (startAppListening: AppStartListening) => {
@ -13,10 +14,12 @@ export const addDeleteBoardAndImagesFulfilledListener = (startAppListening: AppS
let wasNodeEditorReset = false;
const { nodes, canvasV2 } = getState();
const state = getState();
const nodes = selectNodesSlice(state);
const canvas = selectCanvasSlice(state);
deleted_images.forEach((image_name) => {
const imageUsage = getImageUsage(nodes.present, canvasV2, image_name);
const imageUsage = getImageUsage(nodes, canvas, image_name);
if (imageUsage.isNodesImage && !wasNodeEditorReset) {
dispatch(nodeEditorReset());

View File

@ -1,7 +1,8 @@
import { logger } from 'app/logging/logger';
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
import type { AppDispatch, RootState } from 'app/store/store';
import { entityDeleted, ipaImageChanged } from 'features/controlLayers/store/canvasV2Slice';
import { entityDeleted, ipaImageChanged } from 'features/controlLayers/store/canvasSlice';
import { selectCanvasSlice } from 'features/controlLayers/store/selectors';
import { getEntityIdentifier } from 'features/controlLayers/store/types';
import { imageDeletionConfirmed } from 'features/deleteImageModal/store/actions';
import { isModalOpenChanged } from 'features/deleteImageModal/store/slice';
@ -40,7 +41,7 @@ const deleteNodesImages = (state: RootState, dispatch: AppDispatch, imageDTO: Im
};
// const deleteControlAdapterImages = (state: RootState, dispatch: AppDispatch, imageDTO: ImageDTO) => {
// state.canvasV2.controlAdapters.entities.forEach(({ id, imageObject, processedImageObject }) => {
// state.canvas.present.controlAdapters.entities.forEach(({ id, imageObject, processedImageObject }) => {
// if (
// imageObject?.image.image_name === imageDTO.image_name ||
// processedImageObject?.image.image_name === imageDTO.image_name
@ -52,7 +53,7 @@ const deleteNodesImages = (state: RootState, dispatch: AppDispatch, imageDTO: Im
// };
const deleteIPAdapterImages = (state: RootState, dispatch: AppDispatch, imageDTO: ImageDTO) => {
state.canvasV2.ipAdapters.entities.forEach((entity) => {
selectCanvasSlice(state).ipAdapters.entities.forEach((entity) => {
if (entity.ipAdapter.image?.image_name === imageDTO.image_name) {
dispatch(ipaImageChanged({ entityIdentifier: getEntityIdentifier(entity), imageDTO: null }));
}
@ -60,7 +61,7 @@ const deleteIPAdapterImages = (state: RootState, dispatch: AppDispatch, imageDTO
};
const deleteLayerImages = (state: RootState, dispatch: AppDispatch, imageDTO: ImageDTO) => {
state.canvasV2.rasterLayers.entities.forEach(({ id, objects }) => {
selectCanvasSlice(state).rasterLayers.entities.forEach(({ id, objects }) => {
let shouldDelete = false;
for (const obj of objects) {
if (obj.type === 'image' && obj.image.image_name === imageDTO.image_name) {

View File

@ -6,7 +6,8 @@ import {
ipaImageChanged,
rasterLayerAdded,
rgIPAdapterImageChanged,
} from 'features/controlLayers/store/canvasV2Slice';
} from 'features/controlLayers/store/canvasSlice';
import { selectCanvasSlice } from 'features/controlLayers/store/selectors';
import type { CanvasControlLayerState, CanvasRasterLayerState } from 'features/controlLayers/store/types';
import { imageDTOToImageObject } from 'features/controlLayers/store/types';
import type { TypesafeDraggableData, TypesafeDroppableData } from 'features/dnd/types';
@ -85,7 +86,7 @@ export const addImageDroppedListener = (startAppListening: AppStartListening) =>
activeData.payload.imageDTO
) {
const imageObject = imageDTOToImageObject(activeData.payload.imageDTO);
const { x, y } = getState().canvasV2.bbox.rect;
const { x, y } = selectCanvasSlice(getState()).bbox.rect;
const overrides: Partial<CanvasRasterLayerState> = {
objects: [imageObject],
position: { x, y },
@ -103,7 +104,7 @@ export const addImageDroppedListener = (startAppListening: AppStartListening) =>
activeData.payload.imageDTO
) {
const imageObject = imageDTOToImageObject(activeData.payload.imageDTO);
const { x, y } = getState().canvasV2.bbox.rect;
const { x, y } = selectCanvasSlice(getState()).bbox.rect;
const overrides: Partial<CanvasControlLayerState> = {
objects: [imageObject],
position: { x, y },

View File

@ -1,6 +1,6 @@
import { logger } from 'app/logging/logger';
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
import { ipaImageChanged, rgIPAdapterImageChanged } from 'features/controlLayers/store/canvasV2Slice';
import { ipaImageChanged, rgIPAdapterImageChanged } from 'features/controlLayers/store/canvasSlice';
import { selectListBoardsQueryArgs } from 'features/gallery/store/gallerySelectors';
import { fieldImageValueChanged } from 'features/nodes/store/nodesSlice';
import { upscaleInitialImageChanged } from 'features/parameters/store/upscaleSlice';

View File

@ -46,7 +46,7 @@ export const addModelSelectedListener = (startAppListening: AppStartListening) =
}
// handle incompatible controlnets
// state.canvasV2.controlAdapters.entities.forEach((ca) => {
// state.canvas.present.controlAdapters.entities.forEach((ca) => {
// if (ca.model?.base !== newBaseModel) {
// modelsCleared += 1;
// if (ca.isEnabled) {

View File

@ -8,9 +8,10 @@ import {
controlLayerModelChanged,
ipaModelChanged,
rgIPAdapterModelChanged,
} from 'features/controlLayers/store/canvasV2Slice';
} from 'features/controlLayers/store/canvasSlice';
import { loraDeleted } from 'features/controlLayers/store/lorasSlice';
import { modelChanged, refinerModelChanged, vaeSelected } 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/DocumentSize/calculateNewSize';
import { postProcessingModelChanged, upscaleModelChanged } from 'features/parameters/store/upscaleSlice';
@ -81,15 +82,12 @@ 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(state.canvasV2.bbox.rect.width, state.canvasV2.bbox.rect.height, optimalDimension)) {
if (getIsSizeOptimal(bbox.rect.width, bbox.rect.height, optimalDimension)) {
return;
}
const { width, height } = calculateNewSize(
state.canvasV2.bbox.aspectRatio.value,
optimalDimension * optimalDimension
);
const { width, height } = calculateNewSize(bbox.aspectRatio.value, optimalDimension * optimalDimension);
dispatch(bboxWidthChanged({ width }));
dispatch(bboxHeightChanged({ height }));
@ -172,7 +170,7 @@ const handleLoRAModels: ModelHandler = (models, state, dispatch, _log) => {
const handleControlAdapterModels: ModelHandler = (models, state, dispatch, _log) => {
const caModels = models.filter(isControlNetOrT2IAdapterModelConfig);
state.canvasV2.controlLayers.entities.forEach((entity) => {
selectCanvasSlice(state).controlLayers.entities.forEach((entity) => {
const isModelAvailable = caModels.some((m) => m.key === entity.controlAdapter.model?.key);
if (isModelAvailable) {
return;
@ -183,7 +181,7 @@ const handleControlAdapterModels: ModelHandler = (models, state, dispatch, _log)
const handleIPAdapterModels: ModelHandler = (models, state, dispatch, _log) => {
const ipaModels = models.filter(isIPAdapterModelConfig);
state.canvasV2.ipAdapters.entities.forEach((entity) => {
selectCanvasSlice(state).ipAdapters.entities.forEach((entity) => {
const isModelAvailable = ipaModels.some((m) => m.key === entity.ipAdapter.model?.key);
if (isModelAvailable) {
return;
@ -191,7 +189,7 @@ const handleIPAdapterModels: ModelHandler = (models, state, dispatch, _log) => {
dispatch(ipaModelChanged({ entityIdentifier: getEntityIdentifier(entity), modelConfig: null }));
});
state.canvasV2.regions.entities.forEach((entity) => {
selectCanvasSlice(state).regions.entities.forEach((entity) => {
entity.ipAdapters.forEach(({ id: ipAdapterId, model }) => {
const isModelAvailable = ipaModels.some((m) => m.key === model?.key);
if (isModelAvailable) {

View File

@ -1,5 +1,5 @@
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
import { bboxHeightChanged, bboxWidthChanged } from 'features/controlLayers/store/canvasV2Slice';
import { bboxHeightChanged, bboxWidthChanged } from 'features/controlLayers/store/canvasSlice';
import {
setCfgRescaleMultiplier,
setCfgScale,

View File

@ -8,7 +8,7 @@ import { deepClone } from 'common/util/deepClone';
import { changeBoardModalSlice } from 'features/changeBoardModal/store/slice';
import { canvasSessionPersistConfig, canvasSessionSlice } from 'features/controlLayers/store/canvasSessionSlice';
import { canvasSettingsPersistConfig, canvasSettingsSlice } from 'features/controlLayers/store/canvasSettingsSlice';
import { canvasV2PersistConfig, canvasV2Slice } from 'features/controlLayers/store/canvasV2Slice';
import { canvasPersistConfig, canvasSlice } from 'features/controlLayers/store/canvasSlice';
import { lorasPersistConfig, lorasSlice } from 'features/controlLayers/store/lorasSlice';
import { paramsPersistConfig, paramsSlice } from 'features/controlLayers/store/paramsSlice';
import { toolPersistConfig, toolSlice } from 'features/controlLayers/store/toolSlice';
@ -58,7 +58,7 @@ const allReducers = {
[queueSlice.name]: queueSlice.reducer,
[workflowSlice.name]: workflowSlice.reducer,
[hrfSlice.name]: hrfSlice.reducer,
[canvasV2Slice.name]: canvasV2Slice.reducer,
[canvasSlice.name]: undoable(canvasSlice.reducer),
[workflowSettingsSlice.name]: workflowSettingsSlice.reducer,
[upscaleSlice.name]: upscaleSlice.reducer,
[stylePresetSlice.name]: stylePresetSlice.reducer,
@ -104,7 +104,7 @@ const persistConfigs: { [key in keyof typeof allReducers]?: PersistConfig } = {
[dynamicPromptsPersistConfig.name]: dynamicPromptsPersistConfig,
[modelManagerV2PersistConfig.name]: modelManagerV2PersistConfig,
[hrfPersistConfig.name]: hrfPersistConfig,
[canvasV2PersistConfig.name]: canvasV2PersistConfig,
[canvasPersistConfig.name]: canvasPersistConfig,
[workflowSettingsPersistConfig.name]: workflowSettingsPersistConfig,
[upscalePersistConfig.name]: upscalePersistConfig,
[stylePresetPersistConfig.name]: stylePresetPersistConfig,

View File

@ -3,7 +3,7 @@ import { $isConnected } from 'app/hooks/useSocketIO';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { useAppSelector } from 'app/store/storeHooks';
import { selectParamsSlice } from 'features/controlLayers/store/paramsSlice';
import { selectCanvasV2Slice } from 'features/controlLayers/store/selectors';
import { selectCanvasSlice } from 'features/controlLayers/store/selectors';
import { selectDynamicPromptsSlice } from 'features/dynamicPrompts/store/dynamicPromptsSlice';
import { getShouldProcessPrompt } from 'features/dynamicPrompts/util/getShouldProcessPrompt';
import { $templates, selectNodesSlice } from 'features/nodes/store/nodesSlice';
@ -34,14 +34,14 @@ const createSelector = (templates: Templates, isConnected: boolean) =>
selectNodesSlice,
selectWorkflowSettingsSlice,
selectDynamicPromptsSlice,
selectCanvasV2Slice,
selectCanvasSlice,
selectParamsSlice,
selectUpscalelice,
selectConfigSlice,
selectActiveTab,
],
(system, nodes, workflowSettings, dynamicPrompts, canvasV2, params, upscale, config, activeTabName) => {
const { bbox } = canvasV2;
(system, nodes, workflowSettings, dynamicPrompts, canvas, params, upscale, config, activeTabName) => {
const { bbox } = canvas;
const { model, positivePrompt } = params;
const reasons: { prefix?: string; content: string }[] = [];
@ -124,7 +124,7 @@ const createSelector = (templates: Templates, isConnected: boolean) =>
reasons.push({ content: i18n.t('parameters.invoke.noModelSelected') });
}
canvasV2.controlLayers.entities
canvas.controlLayers.entities
.filter((controlLayer) => controlLayer.isEnabled)
.forEach((controlLayer, i) => {
const layerLiteral = i18n.t('controlLayers.layers_one');
@ -154,7 +154,7 @@ const createSelector = (templates: Templates, isConnected: boolean) =>
}
});
canvasV2.ipAdapters.entities
canvas.ipAdapters.entities
.filter((entity) => entity.isEnabled)
.forEach((entity, i) => {
const layerLiteral = i18n.t('controlLayers.layers_one');
@ -182,7 +182,7 @@ const createSelector = (templates: Templates, isConnected: boolean) =>
}
});
canvasV2.regions.entities
canvas.regions.entities
.filter((entity) => entity.isEnabled)
.forEach((entity, i) => {
const layerLiteral = i18n.t('controlLayers.layers_one');
@ -219,7 +219,7 @@ const createSelector = (templates: Templates, isConnected: boolean) =>
}
});
canvasV2.rasterLayers.entities
canvas.rasterLayers.entities
.filter((entity) => entity.isEnabled)
.forEach((entity, i) => {
const layerLiteral = i18n.t('controlLayers.layers_one');

View File

@ -6,7 +6,7 @@ import {
ipaAdded,
rasterLayerAdded,
rgAdded,
} from 'features/controlLayers/store/canvasV2Slice';
} from 'features/controlLayers/store/canvasSlice';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { PiPlusBold } from 'react-icons/pi';

View File

@ -7,7 +7,7 @@ import {
ipaAdded,
rasterLayerAdded,
rgAdded,
} from 'features/controlLayers/store/canvasV2Slice';
} from 'features/controlLayers/store/canvasSlice';
import { selectEntityCount } from 'features/controlLayers/store/selectors';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';

View File

@ -1,7 +1,7 @@
import { Badge } from '@invoke-ai/ui-library';
import { useAppSelector } from 'app/store/storeHooks';
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
import { selectEntityOrThrow } from 'features/controlLayers/store/selectors';
import { selectCanvasSlice, selectEntityOrThrow } from 'features/controlLayers/store/selectors';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
@ -9,7 +9,7 @@ export const ControlLayerBadges = memo(() => {
const entityIdentifier = useEntityIdentifierContext('control_layer');
const { t } = useTranslation();
const withTransparencyEffect = useAppSelector(
(s) => selectEntityOrThrow(s.canvasV2, entityIdentifier).withTransparencyEffect
(s) => selectEntityOrThrow(selectCanvasSlice(s), entityIdentifier).withTransparencyEffect
);
return (

View File

@ -11,7 +11,7 @@ import {
controlLayerControlModeChanged,
controlLayerModelChanged,
controlLayerWeightChanged,
} from 'features/controlLayers/store/canvasV2Slice';
} from 'features/controlLayers/store/canvasSlice';
import type { ControlModeV2 } from 'features/controlLayers/store/types';
import { memo, useCallback } from 'react';
import type { ControlNetModelConfig, T2IAdapterModelConfig } from 'services/api/types';

View File

@ -3,15 +3,15 @@ import { useAppSelector } from 'app/store/storeHooks';
import { CanvasEntityGroupList } from 'features/controlLayers/components/common/CanvasEntityGroupList';
import { ControlLayer } from 'features/controlLayers/components/ControlLayer/ControlLayer';
import { mapId } from 'features/controlLayers/konva/util';
import { selectCanvasV2Slice } from 'features/controlLayers/store/selectors';
import { selectCanvasSlice, selectSelectedEntityIdentifier } from 'features/controlLayers/store/selectors';
import { memo } from 'react';
const selectEntityIds = createMemoizedSelector(selectCanvasV2Slice, (canvasV2) => {
return canvasV2.controlLayers.entities.map(mapId).reverse();
const selectEntityIds = createMemoizedSelector(selectCanvasSlice, (canvas) => {
return canvas.controlLayers.entities.map(mapId).reverse();
});
export const ControlLayerEntityList = memo(() => {
const isSelected = useAppSelector((s) => Boolean(s.canvasV2.selectedEntityIdentifier?.type === 'control_layer'));
const isSelected = useAppSelector((s) => selectSelectedEntityIdentifier(s)?.type === 'control_layer');
const layerIds = useAppSelector(selectEntityIds);
if (layerIds.length === 0) {

View File

@ -1,7 +1,7 @@
import { MenuItem } from '@invoke-ai/ui-library';
import { useAppDispatch } from 'app/store/storeHooks';
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
import { controlLayerConvertedToRasterLayer } from 'features/controlLayers/store/canvasV2Slice';
import { controlLayerConvertedToRasterLayer } from 'features/controlLayers/store/canvasSlice';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { PiLightningBold } from 'react-icons/pi';

View File

@ -2,8 +2,8 @@ import { MenuItem } from '@invoke-ai/ui-library';
import { createSelector } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
import { controlLayerWithTransparencyEffectToggled } from 'features/controlLayers/store/canvasV2Slice';
import { selectCanvasV2Slice, selectEntityOrThrow } from 'features/controlLayers/store/selectors';
import { controlLayerWithTransparencyEffectToggled } from 'features/controlLayers/store/canvasSlice';
import { selectCanvasSlice, selectEntityOrThrow } from 'features/controlLayers/store/selectors';
import { memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { PiDropHalfBold } from 'react-icons/pi';
@ -14,8 +14,8 @@ export const ControlLayerMenuItemsTransparencyEffect = memo(() => {
const entityIdentifier = useEntityIdentifierContext('control_layer');
const selectWithTransparencyEffect = useMemo(
() =>
createSelector(selectCanvasV2Slice, (canvasV2) => {
const entity = selectEntityOrThrow(canvasV2, entityIdentifier);
createSelector(selectCanvasSlice, (canvas) => {
const entity = selectEntityOrThrow(canvas, entityIdentifier);
return entity.withTransparencyEffect;
}),
[entityIdentifier]

View File

@ -1,10 +1,14 @@
import { Box, Flex, Text } from '@invoke-ai/ui-library';
import { useStore } from '@nanostores/react';
import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/store/storeHooks';
import { useCanvasManager } from 'features/controlLayers/contexts/CanvasManagerProviderGate';
import { selectCanvasSlice } from 'features/controlLayers/store/selectors';
import { round } from 'lodash-es';
import { memo } from 'react';
const selectBbox = createSelector(selectCanvasSlice, (canvas) => canvas.bbox);
export const HeadsUpDisplay = memo(() => {
const canvasManager = useCanvasManager();
const stageAttrs = useStore(canvasManager.stateApi.$stageAttrs);
@ -13,7 +17,7 @@ export const HeadsUpDisplay = memo(() => {
const isMouseDown = useStore(canvasManager.stateApi.$isMouseDown);
const lastMouseDownPos = useStore(canvasManager.stateApi.$lastMouseDownPos);
const lastAddedPoint = useStore(canvasManager.stateApi.$lastAddedPoint);
const bbox = useAppSelector((s) => s.canvasV2.bbox);
const bbox = useAppSelector(selectBbox);
return (
<Flex flexDir="column" bg="blackAlpha.400" borderBottomEndRadius="base" p={2} minW={64} gap={2}>

View File

@ -5,7 +5,7 @@ import { $isConnected } from 'app/hooks/useSocketIO';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAIDndImage from 'common/components/IAIDndImage';
import IAIDndImageIcon from 'common/components/IAIDndImageIcon';
import { bboxHeightChanged, bboxWidthChanged } from 'features/controlLayers/store/canvasV2Slice';
import { bboxHeightChanged, bboxWidthChanged } from 'features/controlLayers/store/canvasSlice';
import { selectOptimalDimension } from 'features/controlLayers/store/selectors';
import type { ImageWithDims } from 'features/controlLayers/store/types';
import type { ImageDraggableData, TypesafeDroppableData } from 'features/dnd/types';

View File

@ -1,18 +1,22 @@
/* eslint-disable i18next/no-literal-string */
import { createSelector } from '@reduxjs/toolkit';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { useAppSelector } from 'app/store/storeHooks';
import { CanvasEntityGroupList } from 'features/controlLayers/components/common/CanvasEntityGroupList';
import { IPAdapter } from 'features/controlLayers/components/IPAdapter/IPAdapter';
import { mapId } from 'features/controlLayers/konva/util';
import { selectCanvasV2Slice } from 'features/controlLayers/store/selectors';
import { selectCanvasSlice, selectSelectedEntityIdentifier } from 'features/controlLayers/store/selectors';
import { memo } from 'react';
const selectEntityIds = createMemoizedSelector(selectCanvasV2Slice, (canvasV2) => {
return canvasV2.ipAdapters.entities.map(mapId).reverse();
const selectEntityIds = createMemoizedSelector(selectCanvasSlice, (canvas) => {
return canvas.ipAdapters.entities.map(mapId).reverse();
});
const selectIsSelected = createSelector(selectSelectedEntityIdentifier, (selectedEntityIdentifier) => {
return selectedEntityIdentifier?.type === 'ip_adapter';
});
export const IPAdapterList = memo(() => {
const isSelected = useAppSelector((s) => Boolean(s.canvasV2.selectedEntityIdentifier?.type === 'ip_adapter'));
const isSelected = useAppSelector(selectIsSelected);
const ipaIds = useAppSelector(selectEntityIds);
if (ipaIds.length === 0) {

View File

@ -1,4 +1,5 @@
import { Box, Flex } from '@invoke-ai/ui-library';
import { createSelector } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { BeginEndStepPct } from 'features/controlLayers/components/common/BeginEndStepPct';
import { CanvasEntitySettingsWrapper } from 'features/controlLayers/components/common/CanvasEntitySettingsWrapper';
@ -12,8 +13,8 @@ import {
ipaMethodChanged,
ipaModelChanged,
ipaWeightChanged,
} from 'features/controlLayers/store/canvasV2Slice';
import { selectEntityOrThrow } from 'features/controlLayers/store/selectors';
} from 'features/controlLayers/store/canvasSlice';
import { selectCanvasSlice, selectEntityOrThrow } from 'features/controlLayers/store/selectors';
import type { CLIPVisionModelV2, IPMethodV2 } from 'features/controlLayers/store/types';
import type { IPAImageDropData } from 'features/dnd/types';
import { memo, useCallback, useMemo } from 'react';
@ -25,7 +26,11 @@ import { IPAdapterModel } from './IPAdapterModel';
export const IPAdapterSettings = memo(() => {
const dispatch = useAppDispatch();
const entityIdentifier = useEntityIdentifierContext('ip_adapter');
const ipAdapter = useAppSelector((s) => selectEntityOrThrow(s.canvasV2, entityIdentifier).ipAdapter);
const selectIPAdapter = useMemo(
() => createSelector(selectCanvasSlice, (s) => selectEntityOrThrow(s, entityIdentifier).ipAdapter),
[entityIdentifier]
);
const ipAdapter = useAppSelector(selectIPAdapter);
const onChangeBeginEndStepPct = useCallback(
(beginEndStepPct: [number, number]) => {

View File

@ -1,17 +1,22 @@
import { createSelector } from '@reduxjs/toolkit';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { useAppSelector } from 'app/store/storeHooks';
import { CanvasEntityGroupList } from 'features/controlLayers/components/common/CanvasEntityGroupList';
import { InpaintMask } from 'features/controlLayers/components/InpaintMask/InpaintMask';
import { mapId } from 'features/controlLayers/konva/util';
import { selectCanvasV2Slice } from 'features/controlLayers/store/selectors';
import { selectCanvasSlice, selectSelectedEntityIdentifier } from 'features/controlLayers/store/selectors';
import { memo } from 'react';
const selectEntityIds = createMemoizedSelector(selectCanvasV2Slice, (canvasV2) => {
return canvasV2.inpaintMasks.entities.map(mapId).reverse();
const selectEntityIds = createMemoizedSelector(selectCanvasSlice, (canvas) => {
return canvas.inpaintMasks.entities.map(mapId).reverse();
});
const selectIsSelected = createSelector(selectSelectedEntityIdentifier, (selectedEntityIdentifier) => {
return selectedEntityIdentifier?.type === 'inpaint_mask';
});
export const InpaintMaskList = memo(() => {
const isSelected = useAppSelector((s) => Boolean(s.canvasV2.selectedEntityIdentifier?.type === 'inpaint_mask'));
const isSelected = useAppSelector(selectIsSelected);
const entityIds = useAppSelector(selectEntityIds);
if (entityIds.length === 0) {

View File

@ -1,20 +1,25 @@
import { Flex, Popover, PopoverBody, PopoverContent, PopoverTrigger } from '@invoke-ai/ui-library';
import { createSelector } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import RgbColorPicker from 'common/components/RgbColorPicker';
import { rgbColorToString } from 'common/util/colorCodeTransformers';
import { MaskFillStyle } from 'features/controlLayers/components/common/MaskFillStyle';
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
import { inpaintMaskFillColorChanged, inpaintMaskFillStyleChanged } from 'features/controlLayers/store/canvasV2Slice';
import { selectEntityOrThrow } from 'features/controlLayers/store/selectors';
import { inpaintMaskFillColorChanged, inpaintMaskFillStyleChanged } from 'features/controlLayers/store/canvasSlice';
import { selectCanvasSlice, selectEntityOrThrow } from 'features/controlLayers/store/selectors';
import type { FillStyle, RgbColor } from 'features/controlLayers/store/types';
import { memo, useCallback } from 'react';
import { memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
export const InpaintMaskMaskFillColorPicker = memo(() => {
const { t } = useTranslation();
const dispatch = useAppDispatch();
const entityIdentifier = useEntityIdentifierContext('inpaint_mask');
const fill = useAppSelector((s) => selectEntityOrThrow(s.canvasV2, entityIdentifier).fill);
const selectFill = useMemo(
() => createSelector(selectCanvasSlice, (canvas) => selectEntityOrThrow(canvas, entityIdentifier).fill),
[entityIdentifier]
);
const fill = useAppSelector(selectFill);
const onChangeFillColor = useCallback(
(color: RgbColor) => {

View File

@ -1,17 +1,21 @@
import { createSelector } from '@reduxjs/toolkit';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { useAppSelector } from 'app/store/storeHooks';
import { CanvasEntityGroupList } from 'features/controlLayers/components/common/CanvasEntityGroupList';
import { RasterLayer } from 'features/controlLayers/components/RasterLayer/RasterLayer';
import { mapId } from 'features/controlLayers/konva/util';
import { selectCanvasV2Slice } from 'features/controlLayers/store/selectors';
import { selectCanvasSlice, selectSelectedEntityIdentifier } from 'features/controlLayers/store/selectors';
import { memo } from 'react';
const selectEntityIds = createMemoizedSelector(selectCanvasV2Slice, (canvasV2) => {
return canvasV2.rasterLayers.entities.map(mapId).reverse();
const selectEntityIds = createMemoizedSelector(selectCanvasSlice, (canvas) => {
return canvas.rasterLayers.entities.map(mapId).reverse();
});
const selectIsSelected = createSelector(selectSelectedEntityIdentifier, (selectedEntityIdentifier) => {
return selectedEntityIdentifier?.type === 'raster_layer';
});
export const RasterLayerEntityList = memo(() => {
const isSelected = useAppSelector((s) => Boolean(s.canvasV2.selectedEntityIdentifier?.type === 'raster_layer'));
const isSelected = useAppSelector(selectIsSelected);
const layerIds = useAppSelector(selectEntityIds);
if (layerIds.length === 0) {

View File

@ -1,7 +1,7 @@
import { MenuItem } from '@invoke-ai/ui-library';
import { useAppDispatch } from 'app/store/storeHooks';
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
import { rasterLayerConvertedToControlLayer } from 'features/controlLayers/store/canvasV2Slice';
import { rasterLayerConvertedToControlLayer } from 'features/controlLayers/store/canvasSlice';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { PiLightningBold } from 'react-icons/pi';

View File

@ -6,8 +6,8 @@ import {
rgIPAdapterAdded,
rgNegativePromptChanged,
rgPositivePromptChanged,
} from 'features/controlLayers/store/canvasV2Slice';
import { selectCanvasV2Slice, selectEntityOrThrow } from 'features/controlLayers/store/selectors';
} from 'features/controlLayers/store/canvasSlice';
import { selectCanvasSlice, selectEntityOrThrow } from 'features/controlLayers/store/selectors';
import { useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { PiPlusBold } from 'react-icons/pi';
@ -18,8 +18,8 @@ export const RegionalGuidanceAddPromptsIPAdapterButtons = () => {
const dispatch = useAppDispatch();
const selectValidActions = useMemo(
() =>
createMemoizedSelector(selectCanvasV2Slice, (canvasV2) => {
const entity = selectEntityOrThrow(canvasV2, entityIdentifier);
createMemoizedSelector(selectCanvasSlice, (canvas) => {
const entity = selectEntityOrThrow(canvas, entityIdentifier);
return {
canAddPositivePrompt: entity?.positivePrompt === null,
canAddNegativePrompt: entity?.negativePrompt === null,

View File

@ -1,14 +1,19 @@
import { Badge } from '@invoke-ai/ui-library';
import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/store/storeHooks';
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
import { selectEntityOrThrow } from 'features/controlLayers/store/selectors';
import { memo } from 'react';
import { selectCanvasSlice, selectEntityOrThrow } from 'features/controlLayers/store/selectors';
import { memo, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
export const RegionalGuidanceBadges = memo(() => {
const entityIdentifier = useEntityIdentifierContext('regional_guidance');
const { t } = useTranslation();
const autoNegative = useAppSelector((s) => selectEntityOrThrow(s.canvasV2, entityIdentifier).autoNegative);
const selectAutoNegative = useMemo(
() => createSelector(selectCanvasSlice, (canvas) => selectEntityOrThrow(canvas, entityIdentifier).autoNegative),
[entityIdentifier]
);
const autoNegative = useAppSelector(selectAutoNegative);
return (
<>

View File

@ -1,17 +1,21 @@
import { createSelector } from '@reduxjs/toolkit';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { useAppSelector } from 'app/store/storeHooks';
import { CanvasEntityGroupList } from 'features/controlLayers/components/common/CanvasEntityGroupList';
import { RegionalGuidance } from 'features/controlLayers/components/RegionalGuidance/RegionalGuidance';
import { mapId } from 'features/controlLayers/konva/util';
import { selectCanvasV2Slice } from 'features/controlLayers/store/selectors';
import { selectCanvasSlice, selectSelectedEntityIdentifier } from 'features/controlLayers/store/selectors';
import { memo } from 'react';
const selectEntityIds = createMemoizedSelector(selectCanvasV2Slice, (canvasV2) => {
return canvasV2.regions.entities.map(mapId).reverse();
const selectEntityIds = createMemoizedSelector(selectCanvasSlice, (canvas) => {
return canvas.regions.entities.map(mapId).reverse();
});
const selectIsSelected = createSelector(selectSelectedEntityIdentifier, (selectedEntityIdentifier) => {
return selectedEntityIdentifier?.type === 'raster_layer';
});
export const RegionalGuidanceEntityList = memo(() => {
const isSelected = useAppSelector((s) => Boolean(s.canvasV2.selectedEntityIdentifier?.type === 'regional_guidance'));
const isSelected = useAppSelector(selectIsSelected);
const rgIds = useAppSelector(selectEntityIds);
if (rgIds.length === 0) {

View File

@ -1,4 +1,5 @@
import { Box, Flex, IconButton, Spacer, Text } from '@invoke-ai/ui-library';
import { createSelector } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { BeginEndStepPct } from 'features/controlLayers/components/common/BeginEndStepPct';
import { Weight } from 'features/controlLayers/components/common/Weight';
@ -14,8 +15,8 @@ import {
rgIPAdapterMethodChanged,
rgIPAdapterModelChanged,
rgIPAdapterWeightChanged,
} from 'features/controlLayers/store/canvasV2Slice';
import { selectRegionalGuidanceIPAdapter } from 'features/controlLayers/store/selectors';
} from 'features/controlLayers/store/canvasSlice';
import { selectCanvasSlice, selectRegionalGuidanceIPAdapter } from 'features/controlLayers/store/selectors';
import type { CLIPVisionModelV2, IPMethodV2 } from 'features/controlLayers/store/types';
import type { RGIPAdapterImageDropData } from 'features/dnd/types';
import { memo, useCallback, useMemo } from 'react';
@ -34,11 +35,16 @@ export const RegionalGuidanceIPAdapterSettings = memo(({ ipAdapterId, ipAdapterN
const onDeleteIPAdapter = useCallback(() => {
dispatch(rgIPAdapterDeleted({ entityIdentifier, ipAdapterId }));
}, [dispatch, entityIdentifier, ipAdapterId]);
const ipAdapter = useAppSelector((s) => {
const ipa = selectRegionalGuidanceIPAdapter(s.canvasV2, entityIdentifier, ipAdapterId);
assert(ipa, `Regional GuidanceIP Adapter with id ${ipAdapterId} not found`);
return ipa;
});
const selectIPAdapter = useMemo(
() =>
createSelector(selectCanvasSlice, (canvas) => {
const ipAdapter = selectRegionalGuidanceIPAdapter(canvas, entityIdentifier, ipAdapterId);
assert(ipAdapter, `Regional GuidanceIP Adapter with id ${ipAdapterId} not found`);
return ipAdapter;
}),
[entityIdentifier, ipAdapterId]
);
const ipAdapter = useAppSelector(selectIPAdapter);
const onChangeBeginEndStepPct = useCallback(
(beginEndStepPct: [number, number]) => {

View File

@ -4,7 +4,7 @@ import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { useAppSelector } from 'app/store/storeHooks';
import { RegionalGuidanceIPAdapterSettings } from 'features/controlLayers/components/RegionalGuidance/RegionalGuidanceIPAdapterSettings';
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
import { selectCanvasV2Slice, selectEntityOrThrow } from 'features/controlLayers/store/selectors';
import { selectCanvasSlice, selectEntityOrThrow } from 'features/controlLayers/store/selectors';
import { Fragment, memo, useMemo } from 'react';
export const RegionalGuidanceIPAdapters = memo(() => {
@ -12,8 +12,8 @@ export const RegionalGuidanceIPAdapters = memo(() => {
const selectIPAdapterIds = useMemo(
() =>
createMemoizedSelector(selectCanvasV2Slice, (canvasV2) => {
const ipAdapterIds = selectEntityOrThrow(canvasV2, entityIdentifier).ipAdapters.map(({ id }) => id);
createMemoizedSelector(selectCanvasSlice, (canvas) => {
const ipAdapterIds = selectEntityOrThrow(canvas, entityIdentifier).ipAdapters.map(({ id }) => id);
if (ipAdapterIds.length === 0) {
return EMPTY_ARRAY;
}

View File

@ -1,20 +1,25 @@
import { Flex, Popover, PopoverBody, PopoverContent, PopoverTrigger } from '@invoke-ai/ui-library';
import { createSelector } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import RgbColorPicker from 'common/components/RgbColorPicker';
import { rgbColorToString } from 'common/util/colorCodeTransformers';
import { MaskFillStyle } from 'features/controlLayers/components/common/MaskFillStyle';
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
import { rgFillColorChanged, rgFillStyleChanged } from 'features/controlLayers/store/canvasV2Slice';
import { selectEntityOrThrow } from 'features/controlLayers/store/selectors';
import { rgFillColorChanged, rgFillStyleChanged } from 'features/controlLayers/store/canvasSlice';
import { selectCanvasSlice, selectEntityOrThrow } from 'features/controlLayers/store/selectors';
import type { FillStyle, RgbColor } from 'features/controlLayers/store/types';
import { memo, useCallback } from 'react';
import { memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
export const RegionalGuidanceMaskFillColorPicker = memo(() => {
const entityIdentifier = useEntityIdentifierContext('regional_guidance');
const { t } = useTranslation();
const dispatch = useAppDispatch();
const fill = useAppSelector((s) => selectEntityOrThrow(s.canvasV2, entityIdentifier).fill);
const selectFill = useMemo(
() => createSelector(selectCanvasSlice, (canvas) => selectEntityOrThrow(canvas, entityIdentifier).fill),
[entityIdentifier]
);
const fill = useAppSelector(selectFill);
const onChangeFillColor = useCallback(
(color: RgbColor) => {
dispatch(rgFillColorChanged({ entityIdentifier, color }));

View File

@ -6,8 +6,8 @@ import {
rgIPAdapterAdded,
rgNegativePromptChanged,
rgPositivePromptChanged,
} from 'features/controlLayers/store/canvasV2Slice';
import { selectCanvasV2Slice, selectEntity } from 'features/controlLayers/store/selectors';
} from 'features/controlLayers/store/canvasSlice';
import { selectCanvasSlice, selectEntity } from 'features/controlLayers/store/selectors';
import { memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
@ -17,8 +17,8 @@ export const RegionalGuidanceMenuItemsAddPromptsAndIPAdapter = memo(() => {
const dispatch = useAppDispatch();
const selectValidActions = useMemo(
() =>
createMemoizedSelector(selectCanvasV2Slice, (canvasV2) => {
const entity = selectEntity(canvasV2, entityIdentifier);
createMemoizedSelector(selectCanvasSlice, (canvas) => {
const entity = selectEntity(canvas, entityIdentifier);
return {
canAddPositivePrompt: entity?.positivePrompt === null,
canAddNegativePrompt: entity?.negativePrompt === null,

View File

@ -1,9 +1,10 @@
import { MenuItem } from '@invoke-ai/ui-library';
import { createSelector } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
import { rgAutoNegativeToggled } from 'features/controlLayers/store/canvasV2Slice';
import { selectEntityOrThrow } from 'features/controlLayers/store/selectors';
import { memo, useCallback } from 'react';
import { rgAutoNegativeToggled } from 'features/controlLayers/store/canvasSlice';
import { selectCanvasSlice, selectEntityOrThrow } from 'features/controlLayers/store/selectors';
import { memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { PiSelectionInverseBold } from 'react-icons/pi';
@ -11,7 +12,11 @@ export const RegionalGuidanceMenuItemsAutoNegative = memo(() => {
const entityIdentifier = useEntityIdentifierContext('regional_guidance');
const { t } = useTranslation();
const dispatch = useAppDispatch();
const autoNegative = useAppSelector((s) => selectEntityOrThrow(s.canvasV2, entityIdentifier).autoNegative);
const selectAutoNegative = useMemo(
() => createSelector(selectCanvasSlice, (canvas) => selectEntityOrThrow(canvas, entityIdentifier).autoNegative),
[entityIdentifier]
);
const autoNegative = useAppSelector(selectAutoNegative);
const onClick = useCallback(() => {
dispatch(rgAutoNegativeToggled({ entityIdentifier }));
}, [dispatch, entityIdentifier]);

View File

@ -1,19 +1,25 @@
import { Box, Textarea } from '@invoke-ai/ui-library';
import { createSelector } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { RegionalGuidanceDeletePromptButton } from 'features/controlLayers/components/RegionalGuidance/RegionalGuidanceDeletePromptButton';
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
import { rgNegativePromptChanged } from 'features/controlLayers/store/canvasV2Slice';
import { selectEntityOrThrow } from 'features/controlLayers/store/selectors';
import { rgNegativePromptChanged } from 'features/controlLayers/store/canvasSlice';
import { selectCanvasSlice, selectEntityOrThrow } from 'features/controlLayers/store/selectors';
import { PromptOverlayButtonWrapper } from 'features/parameters/components/Prompts/PromptOverlayButtonWrapper';
import { AddPromptTriggerButton } from 'features/prompt/AddPromptTriggerButton';
import { PromptPopover } from 'features/prompt/PromptPopover';
import { usePrompt } from 'features/prompt/usePrompt';
import { memo, useCallback, useRef } from 'react';
import { memo, useCallback, useMemo, useRef } from 'react';
import { useTranslation } from 'react-i18next';
export const RegionalGuidanceNegativePrompt = memo(() => {
const entityIdentifier = useEntityIdentifierContext('regional_guidance');
const prompt = useAppSelector((s) => selectEntityOrThrow(s.canvasV2, entityIdentifier).negativePrompt ?? '');
const selectPrompt = useMemo(
() =>
createSelector(selectCanvasSlice, (canvas) => selectEntityOrThrow(canvas, entityIdentifier).negativePrompt ?? ''),
[entityIdentifier]
);
const prompt = useAppSelector(selectPrompt);
const dispatch = useAppDispatch();
const textareaRef = useRef<HTMLTextAreaElement>(null);
const { t } = useTranslation();

View File

@ -1,19 +1,25 @@
import { Box, Textarea } from '@invoke-ai/ui-library';
import { createSelector } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { RegionalGuidanceDeletePromptButton } from 'features/controlLayers/components/RegionalGuidance/RegionalGuidanceDeletePromptButton';
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
import { rgPositivePromptChanged } from 'features/controlLayers/store/canvasV2Slice';
import { selectEntityOrThrow } from 'features/controlLayers/store/selectors';
import { rgPositivePromptChanged } from 'features/controlLayers/store/canvasSlice';
import { selectCanvasSlice, selectEntityOrThrow } from 'features/controlLayers/store/selectors';
import { PromptOverlayButtonWrapper } from 'features/parameters/components/Prompts/PromptOverlayButtonWrapper';
import { AddPromptTriggerButton } from 'features/prompt/AddPromptTriggerButton';
import { PromptPopover } from 'features/prompt/PromptPopover';
import { usePrompt } from 'features/prompt/usePrompt';
import { memo, useCallback, useRef } from 'react';
import { memo, useCallback, useMemo, useRef } from 'react';
import { useTranslation } from 'react-i18next';
export const RegionalGuidancePositivePrompt = memo(() => {
const entityIdentifier = useEntityIdentifierContext('regional_guidance');
const prompt = useAppSelector((s) => selectEntityOrThrow(s.canvasV2, entityIdentifier).positivePrompt ?? '');
const selectPrompt = useMemo(
() =>
createSelector(selectCanvasSlice, (canvas) => selectEntityOrThrow(canvas, entityIdentifier).positivePrompt ?? ''),
[entityIdentifier]
);
const prompt = useAppSelector(selectPrompt);
const dispatch = useAppDispatch();
const textareaRef = useRef<HTMLTextAreaElement>(null);
const { t } = useTranslation();

View File

@ -1,10 +1,11 @@
import { Divider } from '@invoke-ai/ui-library';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { useAppSelector } from 'app/store/storeHooks';
import { CanvasEntitySettingsWrapper } from 'features/controlLayers/components/common/CanvasEntitySettingsWrapper';
import { RegionalGuidanceAddPromptsIPAdapterButtons } from 'features/controlLayers/components/RegionalGuidance/RegionalGuidanceAddPromptsIPAdapterButtons';
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
import { selectEntityOrThrow } from 'features/controlLayers/store/selectors';
import { memo } from 'react';
import { selectCanvasSlice, selectEntityOrThrow } from 'features/controlLayers/store/selectors';
import { memo, useMemo } from 'react';
import { RegionalGuidanceIPAdapters } from './RegionalGuidanceIPAdapters';
import { RegionalGuidanceNegativePrompt } from './RegionalGuidanceNegativePrompt';
@ -12,30 +13,38 @@ import { RegionalGuidancePositivePrompt } from './RegionalGuidancePositivePrompt
export const RegionalGuidanceSettings = memo(() => {
const entityIdentifier = useEntityIdentifierContext('regional_guidance');
const hasPositivePrompt = useAppSelector(
(s) => selectEntityOrThrow(s.canvasV2, entityIdentifier).positivePrompt !== null
const selectFlags = useMemo(
() =>
createMemoizedSelector(selectCanvasSlice, (canvas) => {
const entity = selectEntityOrThrow(canvas, entityIdentifier);
return {
hasPositivePrompt: entity.positivePrompt !== null,
hasNegativePrompt: entity.negativePrompt !== null,
hasIPAdapters: entity.ipAdapters.length > 0,
};
}),
[entityIdentifier]
);
const hasNegativePrompt = useAppSelector(
(s) => selectEntityOrThrow(s.canvasV2, entityIdentifier).negativePrompt !== null
);
const hasIPAdapters = useAppSelector((s) => selectEntityOrThrow(s.canvasV2, entityIdentifier).ipAdapters.length > 0);
const flags = useAppSelector(selectFlags);
return (
<CanvasEntitySettingsWrapper>
{!hasPositivePrompt && !hasNegativePrompt && !hasIPAdapters && <RegionalGuidanceAddPromptsIPAdapterButtons />}
{hasPositivePrompt && (
{!flags.hasPositivePrompt && !flags.hasNegativePrompt && !flags.hasIPAdapters && (
<RegionalGuidanceAddPromptsIPAdapterButtons />
)}
{flags.hasPositivePrompt && (
<>
<RegionalGuidancePositivePrompt />
{(hasNegativePrompt || hasIPAdapters) && <Divider />}
{(flags.hasNegativePrompt || flags.hasIPAdapters) && <Divider />}
</>
)}
{hasNegativePrompt && (
{flags.hasNegativePrompt && (
<>
<RegionalGuidanceNegativePrompt />
{hasIPAdapters && <Divider />}
{flags.hasIPAdapters && <Divider />}
</>
)}
{hasIPAdapters && <RegionalGuidanceIPAdapters />}
{flags.hasIPAdapters && <RegionalGuidanceIPAdapters />}
</CanvasEntitySettingsWrapper>
);
});

View File

@ -1,6 +1,6 @@
import { Button } from '@invoke-ai/ui-library';
import { useAppDispatch } from 'app/store/storeHooks';
import { canvasReset } from 'features/controlLayers/store/canvasV2Slice';
import { canvasReset } from 'features/controlLayers/store/canvasSlice';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';

View File

@ -3,7 +3,8 @@ import { useAppSelector } from 'app/store/storeHooks';
import { useSelectTool, useToolIsSelected } from 'features/controlLayers/components/Tool/hooks';
import { useIsFiltering } from 'features/controlLayers/hooks/useIsFiltering';
import { useIsTransforming } from 'features/controlLayers/hooks/useIsTransforming';
import { isDrawableEntityType } from 'features/controlLayers/store/types';
import { selectIsStaging } from 'features/controlLayers/store/canvasSessionSlice';
import { selectIsSelectedEntityDrawable } from 'features/controlLayers/store/selectors';
import { memo, useMemo } from 'react';
import { useHotkeys } from 'react-hotkeys-hook';
import { useTranslation } from 'react-i18next';
@ -13,15 +14,10 @@ export const ToolBrushButton = memo(() => {
const { t } = useTranslation();
const isFiltering = useIsFiltering();
const isTransforming = useIsTransforming();
const isStaging = useAppSelector((s) => s.canvasSession.isStaging);
const isStaging = useAppSelector(selectIsStaging);
const selectBrush = useSelectTool('brush');
const isSelected = useToolIsSelected('brush');
const isDrawingToolAllowed = useAppSelector((s) => {
if (!s.canvasV2.selectedEntityIdentifier?.type) {
return false;
}
return isDrawableEntityType(s.canvasV2.selectedEntityIdentifier.type);
});
const isDrawingToolAllowed = useAppSelector(selectIsSelectedEntityDrawable);
const isDisabled = useMemo(() => {
return isTransforming || isFiltering || isStaging || !isDrawingToolAllowed;

View File

@ -3,7 +3,8 @@ import { useAppSelector } from 'app/store/storeHooks';
import { useSelectTool, useToolIsSelected } from 'features/controlLayers/components/Tool/hooks';
import { useIsFiltering } from 'features/controlLayers/hooks/useIsFiltering';
import { useIsTransforming } from 'features/controlLayers/hooks/useIsTransforming';
import { isDrawableEntityType } from 'features/controlLayers/store/types';
import { selectIsStaging } from 'features/controlLayers/store/canvasSessionSlice';
import { selectIsSelectedEntityDrawable } from 'features/controlLayers/store/selectors';
import { memo, useMemo } from 'react';
import { useHotkeys } from 'react-hotkeys-hook';
import { useTranslation } from 'react-i18next';
@ -13,15 +14,10 @@ export const ToolEraserButton = memo(() => {
const { t } = useTranslation();
const isFiltering = useIsFiltering();
const isTransforming = useIsTransforming();
const isStaging = useAppSelector((s) => s.canvasSession.isStaging);
const isStaging = useAppSelector(selectIsStaging);
const selectEraser = useSelectTool('eraser');
const isSelected = useToolIsSelected('eraser');
const isDrawingToolAllowed = useAppSelector((s) => {
if (!s.canvasV2.selectedEntityIdentifier?.type) {
return false;
}
return isDrawableEntityType(s.canvasV2.selectedEntityIdentifier.type);
});
const isDrawingToolAllowed = useAppSelector(selectIsSelectedEntityDrawable);
const isDisabled = useMemo(() => {
return isTransforming || isFiltering || isStaging || !isDrawingToolAllowed;
}, [isDrawingToolAllowed, isFiltering, isStaging, isTransforming]);

View File

@ -3,7 +3,8 @@ import { useAppSelector } from 'app/store/storeHooks';
import { useSelectTool, useToolIsSelected } from 'features/controlLayers/components/Tool/hooks';
import { useIsFiltering } from 'features/controlLayers/hooks/useIsFiltering';
import { useIsTransforming } from 'features/controlLayers/hooks/useIsTransforming';
import { isDrawableEntityType } from 'features/controlLayers/store/types';
import { selectIsStaging } from 'features/controlLayers/store/canvasSessionSlice';
import { selectIsSelectedEntityDrawable } from 'features/controlLayers/store/selectors';
import { memo, useMemo } from 'react';
import { useHotkeys } from 'react-hotkeys-hook';
import { useTranslation } from 'react-i18next';
@ -15,13 +16,8 @@ export const ToolMoveButton = memo(() => {
const isTransforming = useIsTransforming();
const selectMove = useSelectTool('move');
const isSelected = useToolIsSelected('move');
const isStaging = useAppSelector((s) => s.canvasSession.isStaging);
const isDrawingToolAllowed = useAppSelector((s) => {
if (!s.canvasV2.selectedEntityIdentifier?.type) {
return false;
}
return isDrawableEntityType(s.canvasV2.selectedEntityIdentifier.type);
});
const isStaging = useAppSelector(selectIsStaging);
const isDrawingToolAllowed = useAppSelector(selectIsSelectedEntityDrawable);
const isDisabled = useMemo(() => {
return isTransforming || isFiltering || isStaging || !isDrawingToolAllowed;
}, [isDrawingToolAllowed, isFiltering, isStaging, isTransforming]);

View File

@ -3,7 +3,8 @@ import { useAppSelector } from 'app/store/storeHooks';
import { useSelectTool, useToolIsSelected } from 'features/controlLayers/components/Tool/hooks';
import { useIsFiltering } from 'features/controlLayers/hooks/useIsFiltering';
import { useIsTransforming } from 'features/controlLayers/hooks/useIsTransforming';
import { isDrawableEntityType } from 'features/controlLayers/store/types';
import { selectIsStaging } from 'features/controlLayers/store/canvasSessionSlice';
import { selectIsSelectedEntityDrawable } from 'features/controlLayers/store/selectors';
import { memo, useMemo } from 'react';
import { useHotkeys } from 'react-hotkeys-hook';
import { useTranslation } from 'react-i18next';
@ -15,13 +16,8 @@ export const ToolRectButton = memo(() => {
const isSelected = useToolIsSelected('rect');
const isFiltering = useIsFiltering();
const isTransforming = useIsTransforming();
const isStaging = useAppSelector((s) => s.canvasSession.isStaging);
const isDrawingToolAllowed = useAppSelector((s) => {
if (!s.canvasV2.selectedEntityIdentifier?.type) {
return false;
}
return isDrawableEntityType(s.canvasV2.selectedEntityIdentifier.type);
});
const isStaging = useAppSelector(selectIsStaging);
const isDrawingToolAllowed = useAppSelector(selectIsSelectedEntityDrawable);
const isDisabled = useMemo(() => {
return isTransforming || isFiltering || isStaging || !isDrawingToolAllowed;

View File

@ -5,22 +5,25 @@ import { memo, useCallback } from 'react';
import { useHotkeys } from 'react-hotkeys-hook';
import { useTranslation } from 'react-i18next';
import { PiArrowClockwiseBold, PiArrowCounterClockwiseBold } from 'react-icons/pi';
import { useDispatch } from 'react-redux';
import { ActionCreators } from 'redux-undo';
export const UndoRedoButtonGroup = memo(() => {
const { t } = useTranslation();
const dispatch = useDispatch();
const mayUndo = useAppSelector(() => false);
const mayUndo = useAppSelector(() => true);
const handleUndo = useCallback(() => {
// TODO(psyche): Implement undo
// dispatch(undo());
}, []);
dispatch(ActionCreators.undo());
}, [dispatch]);
useHotkeys(['meta+z', 'ctrl+z'], handleUndo, { enabled: mayUndo, preventDefault: true }, [mayUndo, handleUndo]);
const mayRedo = useAppSelector(() => false);
const mayRedo = useAppSelector(() => true);
const handleRedo = useCallback(() => {
// TODO(psyche): Implement redo
// dispatch(redo());
}, []);
dispatch(ActionCreators.redo());
}, [dispatch]);
useHotkeys(['meta+shift+z', 'ctrl+shift+z'], handleRedo, { enabled: mayRedo, preventDefault: true }, [
mayRedo,
handleRedo,

View File

@ -3,7 +3,7 @@ import { useAppDispatch } from 'app/store/storeHooks';
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
import { useEntityIsSelected } from 'features/controlLayers/hooks/useEntityIsSelected';
import { useEntitySelectionColor } from 'features/controlLayers/hooks/useEntitySelectionColor';
import { entitySelected } from 'features/controlLayers/store/canvasV2Slice';
import { entitySelected } from 'features/controlLayers/store/canvasSlice';
import type { PropsWithChildren } from 'react';
import { memo, useCallback } from 'react';

View File

@ -3,7 +3,7 @@ import { useAppDispatch } from 'app/store/storeHooks';
import { stopPropagation } from 'common/util/stopPropagation';
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
import { useEntityIsEnabled } from 'features/controlLayers/hooks/useEntityIsEnabled';
import { entityIsEnabledToggled } from 'features/controlLayers/store/canvasV2Slice';
import { entityIsEnabledToggled } from 'features/controlLayers/store/canvasSlice';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { PiCheckBold } from 'react-icons/pi';

View File

@ -7,41 +7,41 @@ import {
entityArrangedForwardOne,
entityArrangedToBack,
entityArrangedToFront,
} from 'features/controlLayers/store/canvasV2Slice';
import { selectCanvasV2Slice } from 'features/controlLayers/store/selectors';
import type { CanvasEntityIdentifier, CanvasV2State } from 'features/controlLayers/store/types';
} from 'features/controlLayers/store/canvasSlice';
import { selectCanvasSlice } from 'features/controlLayers/store/selectors';
import type { CanvasEntityIdentifier, CanvasState } from 'features/controlLayers/store/types';
import { memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { PiArrowDownBold, PiArrowLineDownBold, PiArrowLineUpBold, PiArrowUpBold } from 'react-icons/pi';
const getIndexAndCount = (
canvasV2: CanvasV2State,
canvas: CanvasState,
{ id, type }: CanvasEntityIdentifier
): { index: number; count: number } => {
if (type === 'raster_layer') {
return {
index: canvasV2.rasterLayers.entities.findIndex((entity) => entity.id === id),
count: canvasV2.rasterLayers.entities.length,
index: canvas.rasterLayers.entities.findIndex((entity) => entity.id === id),
count: canvas.rasterLayers.entities.length,
};
} else if (type === 'control_layer') {
return {
index: canvasV2.controlLayers.entities.findIndex((entity) => entity.id === id),
count: canvasV2.controlLayers.entities.length,
index: canvas.controlLayers.entities.findIndex((entity) => entity.id === id),
count: canvas.controlLayers.entities.length,
};
} else if (type === 'regional_guidance') {
return {
index: canvasV2.regions.entities.findIndex((entity) => entity.id === id),
count: canvasV2.regions.entities.length,
index: canvas.regions.entities.findIndex((entity) => entity.id === id),
count: canvas.regions.entities.length,
};
} else if (type === 'inpaint_mask') {
return {
index: canvasV2.inpaintMasks.entities.findIndex((entity) => entity.id === id),
count: canvasV2.inpaintMasks.entities.length,
index: canvas.inpaintMasks.entities.findIndex((entity) => entity.id === id),
count: canvas.inpaintMasks.entities.length,
};
} else if (type === 'ip_adapter') {
return {
index: canvasV2.ipAdapters.entities.findIndex((entity) => entity.id === id),
count: canvasV2.ipAdapters.entities.length,
index: canvas.ipAdapters.entities.findIndex((entity) => entity.id === id),
count: canvas.ipAdapters.entities.length,
};
} else {
return {
@ -57,8 +57,8 @@ export const CanvasEntityMenuItemsArrange = memo(() => {
const entityIdentifier = useEntityIdentifierContext();
const selectValidActions = useMemo(
() =>
createMemoizedSelector(selectCanvasV2Slice, (canvasV2) => {
const { index, count } = getIndexAndCount(canvasV2, entityIdentifier);
createMemoizedSelector(selectCanvasSlice, (canvas) => {
const { index, count } = getIndexAndCount(canvas, entityIdentifier);
return {
canMoveForwardOne: index < count - 1,
canMoveBackwardOne: index > 0,

View File

@ -1,7 +1,7 @@
import { MenuItem } from '@invoke-ai/ui-library';
import { useAppDispatch } from 'app/store/storeHooks';
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
import { entityDeleted } from 'features/controlLayers/store/canvasV2Slice';
import { entityDeleted } from 'features/controlLayers/store/canvasSlice';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { PiTrashSimpleBold } from 'react-icons/pi';

View File

@ -1,7 +1,7 @@
import { MenuItem } from '@invoke-ai/ui-library';
import { useAppDispatch } from 'app/store/storeHooks';
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
import { entityDuplicated } from 'features/controlLayers/store/canvasV2Slice';
import { entityDuplicated } from 'features/controlLayers/store/canvasSlice';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { PiCopyFill } from 'react-icons/pi';

View File

@ -15,8 +15,12 @@ import {
} from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { snapToNearest } from 'features/controlLayers/konva/util';
import { entityOpacityChanged } from 'features/controlLayers/store/canvasV2Slice';
import { selectEntity } from 'features/controlLayers/store/selectors';
import { entityOpacityChanged } from 'features/controlLayers/store/canvasSlice';
import {
selectCanvasSlice,
selectEntity,
selectSelectedEntityIdentifier,
} from 'features/controlLayers/store/selectors';
import { isDrawableEntity } from 'features/controlLayers/store/types';
import { clamp, round } from 'lodash-es';
import type { KeyboardEvent } from 'react';
@ -59,13 +63,14 @@ const snapCandidates = marks.slice(1, marks.length - 1);
export const CanvasEntityOpacity = memo(() => {
const { t } = useTranslation();
const dispatch = useAppDispatch();
const selectedEntityIdentifier = useAppSelector((s) => s.canvasV2.selectedEntityIdentifier);
const selectedEntityIdentifier = useAppSelector(selectSelectedEntityIdentifier);
const opacity = useAppSelector((s) => {
const selectedEntityIdentifier = s.canvasV2.selectedEntityIdentifier;
const selectedEntityIdentifier = selectSelectedEntityIdentifier(s);
if (!selectedEntityIdentifier) {
return null;
}
const selectedEntity = selectEntity(s.canvasV2, selectedEntityIdentifier);
const canvas = selectCanvasSlice(s);
const selectedEntity = selectEntity(canvas, selectedEntityIdentifier);
if (!selectedEntity) {
return null;
}

View File

@ -5,7 +5,7 @@ import { rgbColorToString } from 'common/util/colorCodeTransformers';
import { useEntityAdapter } from 'features/controlLayers/contexts/EntityAdapterContext';
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
import { TRANSPARENCY_CHECKER_PATTERN } from 'features/controlLayers/konva/constants';
import { selectCanvasV2Slice, selectEntity } from 'features/controlLayers/store/selectors';
import { selectCanvasSlice, selectEntity } from 'features/controlLayers/store/selectors';
import { memo, useEffect, useMemo, useRef } from 'react';
import { useSelector } from 'react-redux';
@ -18,7 +18,7 @@ export const CanvasEntityPreviewImage = memo(() => {
const adapter = useEntityAdapter();
const selectMaskColor = useMemo(
() =>
createSelector(selectCanvasV2Slice, (state) => {
createSelector(selectCanvasSlice, (state) => {
const entity = selectEntity(state, entityIdentifier);
if (!entity) {
return null;

View File

@ -4,7 +4,7 @@ import { useBoolean } from 'common/hooks/useBoolean';
import { CanvasEntityTitle } from 'features/controlLayers/components/common/CanvasEntityTitle';
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
import { useEntityTitle } from 'features/controlLayers/hooks/useEntityTitle';
import { entityNameChanged } from 'features/controlLayers/store/canvasV2Slice';
import { entityNameChanged } from 'features/controlLayers/store/canvasSlice';
import type { ChangeEvent, KeyboardEvent } from 'react';
import { memo, useCallback, useEffect, useRef, useState } from 'react';

View File

@ -2,7 +2,7 @@ import { IconButton } from '@invoke-ai/ui-library';
import { useAppDispatch } from 'app/store/storeHooks';
import { useEntityTypeIsHidden } from 'features/controlLayers/hooks/useEntityTypeIsHidden';
import { useEntityTypeString } from 'features/controlLayers/hooks/useEntityTypeString';
import { allEntitiesOfTypeIsHiddenToggled } from 'features/controlLayers/store/canvasV2Slice';
import { allEntitiesOfTypeIsHiddenToggled } from 'features/controlLayers/store/canvasSlice';
import type { CanvasEntityIdentifier } from 'features/controlLayers/store/types';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';

View File

@ -1,14 +1,14 @@
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { useAssertSingleton } from 'common/hooks/useAssertSingleton';
import { entityDeleted } from 'features/controlLayers/store/canvasV2Slice';
import { selectCanvasV2Slice } from 'features/controlLayers/store/selectors';
import { entityDeleted } from 'features/controlLayers/store/canvasSlice';
import { selectCanvasSlice } from 'features/controlLayers/store/selectors';
import { useCallback, useMemo } from 'react';
import { useHotkeys } from 'react-hotkeys-hook';
const selectSelectedEntityIdentifier = createMemoizedSelector(
selectCanvasV2Slice,
(canvasV2State) => canvasV2State.selectedEntityIdentifier
selectCanvasSlice,
(canvasState) => canvasState.selectedEntityIdentifier
);
export function useCanvasDeleteLayerHotkey() {

View File

@ -1,14 +1,14 @@
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { useAssertSingleton } from 'common/hooks/useAssertSingleton';
import { entityReset } from 'features/controlLayers/store/canvasV2Slice';
import { selectCanvasV2Slice } from 'features/controlLayers/store/selectors';
import { entityReset } from 'features/controlLayers/store/canvasSlice';
import { selectCanvasSlice } from 'features/controlLayers/store/selectors';
import { useCallback, useMemo } from 'react';
import { useHotkeys } from 'react-hotkeys-hook';
const selectSelectedEntityIdentifier = createMemoizedSelector(
selectCanvasV2Slice,
(canvasV2State) => canvasV2State.selectedEntityIdentifier
selectCanvasSlice,
(canvasState) => canvasState.selectedEntityIdentifier
);
export function useCanvasResetLayerHotkey() {

View File

@ -1,14 +1,14 @@
import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/store/storeHooks';
import { selectCanvasV2Slice, selectEntity } from 'features/controlLayers/store/selectors';
import { selectCanvasSlice, selectEntity } from 'features/controlLayers/store/selectors';
import type { CanvasEntityIdentifier } from 'features/controlLayers/store/types';
import { useMemo } from 'react';
export const useEntityIsEnabled = (entityIdentifier: CanvasEntityIdentifier) => {
const selectIsEnabled = useMemo(
() =>
createSelector(selectCanvasV2Slice, (canvasV2) => {
const entity = selectEntity(canvasV2, entityIdentifier);
createSelector(selectCanvasSlice, (canvas) => {
const entity = selectEntity(canvas, entityIdentifier);
if (!entity) {
return false;
} else {

View File

@ -1,12 +1,18 @@
import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/store/storeHooks';
import { selectSelectedEntityIdentifier } from 'features/controlLayers/store/selectors';
import type { CanvasEntityIdentifier } from 'features/controlLayers/store/types';
import { useMemo } from 'react';
export const useEntityIsSelected = (entityIdentifier: CanvasEntityIdentifier) => {
const selectedEntityIdentifier = useAppSelector((s) => s.canvasV2.selectedEntityIdentifier);
const isSelected = useMemo(() => {
return selectedEntityIdentifier?.id === entityIdentifier.id;
}, [selectedEntityIdentifier, entityIdentifier.id]);
const selectIsSelected = useMemo(
() =>
createSelector(selectSelectedEntityIdentifier, (selectedEntityIdentifier) => {
return selectedEntityIdentifier?.id === entityIdentifier.id;
}),
[entityIdentifier.id]
);
const isSelected = useAppSelector(selectIsSelected);
return isSelected;
};

View File

@ -1,14 +1,14 @@
import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/store/storeHooks';
import { selectCanvasV2Slice, selectEntity } from 'features/controlLayers/store/selectors';
import { selectCanvasSlice, selectEntity } from 'features/controlLayers/store/selectors';
import { type CanvasEntityIdentifier, isDrawableEntity } from 'features/controlLayers/store/types';
import { useMemo } from 'react';
export const useEntityObjectCount = (entityIdentifier: CanvasEntityIdentifier) => {
const selectObjectCount = useMemo(
() =>
createSelector(selectCanvasV2Slice, (canvasV2) => {
const entity = selectEntity(canvasV2, entityIdentifier);
createSelector(selectCanvasSlice, (canvas) => {
const entity = selectEntity(canvas, entityIdentifier);
if (!entity) {
return 0;
} else if (isDrawableEntity(entity)) {

View File

@ -1,15 +1,15 @@
import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/store/storeHooks';
import { rgbColorToString } from 'common/util/colorCodeTransformers';
import { selectCanvasV2Slice, selectEntity } from 'features/controlLayers/store/selectors';
import { selectCanvasSlice, selectEntity } from 'features/controlLayers/store/selectors';
import type { CanvasEntityIdentifier } from 'features/controlLayers/store/types';
import { useMemo } from 'react';
export const useEntitySelectionColor = (entityIdentifier: CanvasEntityIdentifier) => {
const selectSelectionColor = useMemo(
() =>
createSelector(selectCanvasV2Slice, (canvasV2) => {
const entity = selectEntity(canvasV2, entityIdentifier);
createSelector(selectCanvasSlice, (canvas) => {
const entity = selectEntity(canvas, entityIdentifier);
if (!entity) {
return 'base.400';
} else if (entity.type === 'inpaint_mask') {

View File

@ -1,15 +1,15 @@
import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/store/storeHooks';
import { useEntityObjectCount } from 'features/controlLayers/hooks/useEntityObjectCount';
import { selectCanvasV2Slice, selectEntity } from 'features/controlLayers/store/selectors';
import { selectCanvasSlice, selectEntity } from 'features/controlLayers/store/selectors';
import type { CanvasEntityIdentifier } from 'features/controlLayers/store/types';
import { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { assert } from 'tsafe';
const createSelectName = (entityIdentifier: CanvasEntityIdentifier) =>
createSelector(selectCanvasV2Slice, (canvasV2) => {
const entity = selectEntity(canvasV2, entityIdentifier);
createSelector(selectCanvasSlice, (canvas) => {
const entity = selectEntity(canvas, entityIdentifier);
if (!entity) {
return null;
}

View File

@ -1,24 +1,24 @@
import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/store/storeHooks';
import { selectCanvasV2Slice } from 'features/controlLayers/store/selectors';
import { selectCanvasSlice } from 'features/controlLayers/store/selectors';
import type { CanvasEntityIdentifier } from 'features/controlLayers/store/types';
import { useMemo } from 'react';
export const useEntityTypeCount = (type: CanvasEntityIdentifier['type']): number => {
const selectEntityCount = useMemo(
() =>
createSelector(selectCanvasV2Slice, (canvasV2) => {
createSelector(selectCanvasSlice, (canvas) => {
switch (type) {
case 'control_layer':
return canvasV2.controlLayers.entities.length;
return canvas.controlLayers.entities.length;
case 'raster_layer':
return canvasV2.rasterLayers.entities.length;
return canvas.rasterLayers.entities.length;
case 'inpaint_mask':
return canvasV2.inpaintMasks.entities.length;
return canvas.inpaintMasks.entities.length;
case 'regional_guidance':
return canvasV2.regions.entities.length;
return canvas.regions.entities.length;
case 'ip_adapter':
return canvasV2.ipAdapters.entities.length;
return canvas.ipAdapters.entities.length;
default:
return 0;
}

View File

@ -1,22 +1,22 @@
import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/store/storeHooks';
import { selectCanvasV2Slice } from 'features/controlLayers/store/selectors';
import { selectCanvasSlice } from 'features/controlLayers/store/selectors';
import type { CanvasEntityIdentifier } from 'features/controlLayers/store/types';
import { useMemo } from 'react';
export const useEntityTypeIsHidden = (type: CanvasEntityIdentifier['type']): boolean => {
const selectIsHidden = useMemo(
() =>
createSelector(selectCanvasV2Slice, (canvasV2) => {
createSelector(selectCanvasSlice, (canvas) => {
switch (type) {
case 'control_layer':
return canvasV2.controlLayers.isHidden;
return canvas.controlLayers.isHidden;
case 'raster_layer':
return canvasV2.rasterLayers.isHidden;
return canvas.rasterLayers.isHidden;
case 'inpaint_mask':
return canvasV2.inpaintMasks.isHidden;
return canvas.inpaintMasks.isHidden;
case 'regional_guidance':
return canvasV2.regions.isHidden;
return canvas.regions.isHidden;
case 'ip_adapter':
default:
return false;

View File

@ -1,7 +1,7 @@
import { createMemoizedAppSelector } from 'app/store/createMemoizedSelector';
import { useAppSelector } from 'app/store/storeHooks';
import { deepClone } from 'common/util/deepClone';
import { selectCanvasV2Slice, selectEntityOrThrow } from 'features/controlLayers/store/selectors';
import { selectCanvasSlice, selectEntityOrThrow } from 'features/controlLayers/store/selectors';
import type {
CanvasEntityIdentifier,
ControlNetConfig,
@ -16,8 +16,8 @@ import { useControlNetAndT2IAdapterModels, useIPAdapterModels } from 'services/a
export const useControlLayerControlAdapter = (entityIdentifier: CanvasEntityIdentifier<'control_layer'>) => {
const selectControlAdapter = useMemo(
() =>
createMemoizedAppSelector(selectCanvasV2Slice, (canvasV2) => {
const layer = selectEntityOrThrow(canvasV2, entityIdentifier);
createMemoizedAppSelector(selectCanvasSlice, (canvas) => {
const layer = selectEntityOrThrow(canvas, entityIdentifier);
return layer.controlAdapter;
}),
[entityIdentifier]

View File

@ -6,7 +6,7 @@ import { CanvasModuleBase } from 'features/controlLayers/konva/CanvasModuleBase'
import { getPrefixedId } from 'features/controlLayers/konva/util';
import type { CanvasSessionState } from 'features/controlLayers/store/canvasSessionSlice';
import type { CanvasSettingsState } from 'features/controlLayers/store/canvasSettingsSlice';
import type { CanvasV2State } from 'features/controlLayers/store/types';
import type { CanvasState } from 'features/controlLayers/store/types';
import type { Logger } from 'roarr';
export class CanvasRenderingModule extends CanvasModuleBase {
@ -18,7 +18,7 @@ export class CanvasRenderingModule extends CanvasModuleBase {
manager: CanvasManager;
subscriptions = new Set<() => void>();
state: CanvasV2State | null = null;
state: CanvasState | null = null;
settings: CanvasSettingsState | null = null;
session: CanvasSessionState | null = null;
@ -119,7 +119,7 @@ export class CanvasRenderingModule extends CanvasModuleBase {
}
};
renderRasterLayers = async (state: CanvasV2State, prevState: CanvasV2State | null) => {
renderRasterLayers = async (state: CanvasState, prevState: CanvasState | null) => {
const adapterMap = this.manager.adapters.rasterLayers;
if (!prevState || state.rasterLayers.isHidden !== prevState.rasterLayers.isHidden) {
@ -148,7 +148,7 @@ export class CanvasRenderingModule extends CanvasModuleBase {
}
};
renderControlLayers = async (prevState: CanvasV2State | null, state: CanvasV2State) => {
renderControlLayers = async (prevState: CanvasState | null, state: CanvasState) => {
const adapterMap = this.manager.adapters.controlLayers;
if (!prevState || state.controlLayers.isHidden !== prevState.controlLayers.isHidden) {
@ -177,7 +177,7 @@ export class CanvasRenderingModule extends CanvasModuleBase {
}
};
renderRegionalGuidance = async (prevState: CanvasV2State | null, state: CanvasV2State) => {
renderRegionalGuidance = async (prevState: CanvasState | null, state: CanvasState) => {
const adapterMap = this.manager.adapters.regionMasks;
if (!prevState || state.regions.isHidden !== prevState.regions.isHidden) {
@ -211,7 +211,7 @@ export class CanvasRenderingModule extends CanvasModuleBase {
}
};
renderInpaintMasks = async (state: CanvasV2State, prevState: CanvasV2State | null) => {
renderInpaintMasks = async (state: CanvasState, prevState: CanvasState | null) => {
const adapterMap = this.manager.adapters.inpaintMasks;
if (!prevState || state.inpaintMasks.isHidden !== prevState.inpaintMasks.isHidden) {
@ -245,7 +245,7 @@ export class CanvasRenderingModule extends CanvasModuleBase {
}
};
renderBbox = (state: CanvasV2State, prevState: CanvasV2State | null) => {
renderBbox = (state: CanvasState, prevState: CanvasState | null) => {
if (!prevState || state.bbox !== prevState.bbox) {
this.manager.preview.bbox.render();
}
@ -257,7 +257,7 @@ export class CanvasRenderingModule extends CanvasModuleBase {
}
};
arrangeEntities = (state: CanvasV2State, prevState: CanvasV2State | null) => {
arrangeEntities = (state: CanvasState, prevState: CanvasState | null) => {
if (
!prevState ||
state.rasterLayers.entities !== prevState.rasterLayers.entities ||

View File

@ -14,8 +14,8 @@ import {
entityRectAdded,
entityReset,
entitySelected,
} from 'features/controlLayers/store/canvasV2Slice';
import { selectAllRenderableEntities } from 'features/controlLayers/store/selectors';
} from 'features/controlLayers/store/canvasSlice';
import { selectAllRenderableEntities, selectCanvasSlice } from 'features/controlLayers/store/selectors';
import {
brushWidthChanged,
eraserWidthChanged,
@ -100,7 +100,7 @@ export class CanvasStateApiModule extends CanvasModuleBase {
// Reminder - use arrow functions to avoid binding issues
getCanvasState = () => {
return this.store.getState().canvasV2;
return selectCanvasSlice(this.store.getState());
};
resetEntity = (arg: EntityIdentifierPayload) => {
this.store.dispatch(entityReset(arg));

View File

@ -1,14 +1,14 @@
import type { PayloadAction, SliceCaseReducers } from '@reduxjs/toolkit';
import { deepClone } from 'common/util/deepClone';
import { roundDownToMultiple, roundToMultiple } from 'common/util/roundDownToMultiple';
import type { BoundingBoxScaleMethod, CanvasV2State, Dimensions } from 'features/controlLayers/store/types';
import type { BoundingBoxScaleMethod, CanvasState, Dimensions } from 'features/controlLayers/store/types';
import { getScaledBoundingBoxDimensions } from 'features/controlLayers/util/getScaledBoundingBoxDimensions';
import { calculateNewSize } from 'features/parameters/components/DocumentSize/calculateNewSize';
import { ASPECT_RATIO_MAP, initialAspectRatioState } from 'features/parameters/components/DocumentSize/constants';
import type { AspectRatioID } from 'features/parameters/components/DocumentSize/types';
import type { IRect } from 'konva/lib/types';
const syncScaledSize = (state: CanvasV2State) => {
const syncScaledSize = (state: CanvasState) => {
if (state.bbox.scaleMethod === 'auto') {
const { width, height } = state.bbox.rect;
state.bbox.scaledSize = getScaledBoundingBoxDimensions({ width, height }, state.bbox.optimalDimension);
@ -116,4 +116,4 @@ export const bboxReducers = {
syncScaledSize(state);
},
} satisfies SliceCaseReducers<CanvasV2State>;
} satisfies SliceCaseReducers<CanvasState>;

View File

@ -1,6 +1,6 @@
import { createAction, createSlice, type PayloadAction } from '@reduxjs/toolkit';
import type { PersistConfig } from 'app/store/store';
import { canvasV2Slice } from 'features/controlLayers/store/canvasV2Slice';
import { createAction, createSelector, createSlice, type PayloadAction } from '@reduxjs/toolkit';
import type { PersistConfig, RootState } from 'app/store/store';
import { canvasSlice } from 'features/controlLayers/store/canvasSlice';
import type { SessionMode, StagingAreaImage } from 'features/controlLayers/store/types';
export type CanvasSessionState = {
@ -79,5 +79,9 @@ export const canvasSessionPersistConfig: PersistConfig<CanvasSessionState> = {
persistDenylist: [],
};
export const sessionStagingAreaImageAccepted = createAction<{ index: number }>(
`${canvasV2Slice.name}/sessionStagingAreaImageAccepted`
`${canvasSlice.name}/sessionStagingAreaImageAccepted`
);
export const selectCanvasSessionSlice = (s: RootState) => s.canvasSession;
export const selectIsStaging = createSelector(selectCanvasSessionSlice, (canvasSession) => canvasSession.isStaging);

View File

@ -1,5 +1,5 @@
import { createSlice, type PayloadAction } from '@reduxjs/toolkit';
import type { PersistConfig } from 'app/store/store';
import type { PersistConfig, RootState } from 'app/store/store';
export type CanvasSettingsState = {
imageSmoothing: boolean;
@ -51,3 +51,4 @@ export const canvasSettingsPersistConfig: PersistConfig<CanvasSettingsState> = {
migrate,
persistDenylist: [],
};
export const selectCanvasSettingsSlice = (s: RootState) => s.canvasSettings;

View File

@ -22,7 +22,7 @@ import { assert } from 'tsafe';
import type {
CanvasEntityIdentifier,
CanvasV2State,
CanvasState,
EntityBrushLineAddedPayload,
EntityEraserLineAddedPayload,
EntityIdentifierPayload,
@ -32,7 +32,7 @@ import type {
} from './types';
import { getEntityIdentifier, isDrawableEntity } from './types';
const initialState: CanvasV2State = {
const initialState: CanvasState = {
_version: 3,
selectedEntityIdentifier: null,
rasterLayers: {
@ -64,8 +64,8 @@ const initialState: CanvasV2State = {
},
};
export const canvasV2Slice = createSlice({
name: 'canvasV2',
export const canvasSlice = createSlice({
name: 'canvas',
initialState,
reducers: {
// undoable canvas state
@ -217,7 +217,7 @@ export const canvasV2Slice = createSlice({
entityDeleted: (state, action: PayloadAction<EntityIdentifierPayload>) => {
const { entityIdentifier } = action.payload;
let selectedEntityIdentifier: CanvasV2State['selectedEntityIdentifier'] = null;
let selectedEntityIdentifier: CanvasState['selectedEntityIdentifier'] = null;
const allEntities = selectAllEntities(state);
const index = allEntities.findIndex((entity) => entity.id === entityIdentifier.id);
const nextIndex = allEntities.length > 1 ? (index + 1) % allEntities.length : -1;
@ -438,15 +438,15 @@ export const {
// inpaintMaskRecalled,
inpaintMaskFillColorChanged,
inpaintMaskFillStyleChanged,
} = canvasV2Slice.actions;
} = canvasSlice.actions;
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
const migrate = (state: any): any => {
return state;
};
export const canvasV2PersistConfig: PersistConfig<CanvasV2State> = {
name: canvasV2Slice.name,
export const canvasPersistConfig: PersistConfig<CanvasState> = {
name: canvasSlice.name,
initialState,
migrate,
persistDenylist: [],

View File

@ -9,7 +9,7 @@ import type { ControlNetModelConfig, T2IAdapterModelConfig } from 'services/api/
import type {
CanvasControlLayerState,
CanvasRasterLayerState,
CanvasV2State,
CanvasState,
ControlModeV2,
ControlNetConfig,
EntityIdentifierPayload,
@ -159,4 +159,4 @@ export const controlLayersReducers = {
}
layer.withTransparencyEffect = !layer.withTransparencyEffect;
},
} satisfies SliceCaseReducers<CanvasV2State>;
} satisfies SliceCaseReducers<CanvasState>;

View File

@ -3,7 +3,7 @@ import { getPrefixedId } from 'features/controlLayers/konva/util';
import { selectEntity } from 'features/controlLayers/store/selectors';
import type {
CanvasInpaintMaskState,
CanvasV2State,
CanvasState,
EntityIdentifierPayload,
FillStyle,
RgbColor,
@ -68,4 +68,4 @@ export const inpaintMaskReducers = {
}
entity.fill.style = style;
},
} satisfies SliceCaseReducers<CanvasV2State>;
} satisfies SliceCaseReducers<CanvasState>;

View File

@ -8,7 +8,7 @@ import type { ImageDTO, IPAdapterModelConfig } from 'services/api/types';
import type {
CanvasIPAdapterState,
CanvasV2State,
CanvasState,
CLIPVisionModelV2,
EntityIdentifierPayload,
IPMethodV2,
@ -104,4 +104,4 @@ export const ipAdaptersReducers = {
}
entity.ipAdapter.beginEndStepPct = beginEndStepPct;
},
} satisfies SliceCaseReducers<CanvasV2State>;
} satisfies SliceCaseReducers<CanvasState>;

View File

@ -4,7 +4,7 @@ import { getPrefixedId } from 'features/controlLayers/konva/util';
import { selectEntity } from 'features/controlLayers/store/selectors';
import { merge } from 'lodash-es';
import type { CanvasControlLayerState, CanvasRasterLayerState, CanvasV2State, EntityIdentifierPayload } from './types';
import type { CanvasControlLayerState, CanvasRasterLayerState, CanvasState, EntityIdentifierPayload } from './types';
import { getEntityIdentifier, initialControlNet } from './types';
export const rasterLayersReducers = {
@ -67,4 +67,4 @@ export const rasterLayersReducers = {
payload: { ...payload, newId: getPrefixedId('control_layer') },
}),
},
} satisfies SliceCaseReducers<CanvasV2State>;
} satisfies SliceCaseReducers<CanvasState>;

View File

@ -3,7 +3,7 @@ import { deepClone } from 'common/util/deepClone';
import { getPrefixedId } from 'features/controlLayers/konva/util';
import { selectEntity, selectRegionalGuidanceIPAdapter } from 'features/controlLayers/store/selectors';
import type {
CanvasV2State,
CanvasState,
CLIPVisionModelV2,
EntityIdentifierPayload,
FillStyle,
@ -29,7 +29,7 @@ const DEFAULT_MASK_COLORS: RgbColor[] = [
{ r: 161, g: 120, b: 214 }, // rgb(161, 120, 214)
];
const getRGMaskFill = (state: CanvasV2State): RgbColor => {
const getRGMaskFill = (state: CanvasState): RgbColor => {
const lastFill = state.regions.entities.slice(-1)[0]?.fill.color;
let i = DEFAULT_MASK_COLORS.findIndex((c) => isEqual(c, lastFill));
if (i === -1) {
@ -249,4 +249,4 @@ export const regionsReducers = {
}
ipAdapter.clipVisionModel = clipVisionModel;
},
} satisfies SliceCaseReducers<CanvasV2State>;
} satisfies SliceCaseReducers<CanvasState>;

View File

@ -1,22 +1,23 @@
import { createSelector } from '@reduxjs/toolkit';
import type { RootState } from 'app/store/store';
import { selectParamsSlice } from 'features/controlLayers/store/paramsSlice';
import type {
CanvasControlLayerState,
CanvasEntityIdentifier,
CanvasEntityState,
CanvasInpaintMaskState,
CanvasRasterLayerState,
CanvasRegionalGuidanceState,
CanvasV2State,
import {
type CanvasControlLayerState,
type CanvasEntityIdentifier,
type CanvasEntityState,
type CanvasInpaintMaskState,
type CanvasRasterLayerState,
type CanvasRegionalGuidanceState,
type CanvasState,
isDrawableEntityType,
} from 'features/controlLayers/store/types';
import { getOptimalDimension } from 'features/parameters/util/optimalDimension';
import { assert } from 'tsafe';
/**
* Selects the canvasV2 slice from the root state
* Selects the canvas slice from the root state
*/
export const selectCanvasV2Slice = (state: RootState) => state.canvasV2;
export const selectCanvasSlice = (state: RootState) => state.canvas.present;
/**
* Selects the total canvas entity count:
@ -28,13 +29,13 @@ export const selectCanvasV2Slice = (state: RootState) => state.canvasV2;
*
* It does not check for validity of the entities.
*/
export const selectEntityCount = createSelector(selectCanvasV2Slice, (canvasV2) => {
export const selectEntityCount = createSelector(selectCanvasSlice, (canvas) => {
return (
canvasV2.regions.entities.length +
canvasV2.ipAdapters.entities.length +
canvasV2.rasterLayers.entities.length +
canvasV2.controlLayers.entities.length +
canvasV2.inpaintMasks.entities.length
canvas.regions.entities.length +
canvas.ipAdapters.entities.length +
canvas.rasterLayers.entities.length +
canvas.controlLayers.entities.length +
canvas.inpaintMasks.entities.length
);
});
@ -46,11 +47,11 @@ export const selectOptimalDimension = createSelector(selectParamsSlice, (params)
});
/**
* Selects a single entity from the canvasV2 slice. If the entity identifier is narrowed to a specific type, the
* Selects a single entity from the canvas slice. If the entity identifier is narrowed to a specific type, the
* return type will be narrowed as well.
*/
export function selectEntity<T extends CanvasEntityIdentifier>(
state: CanvasV2State,
state: CanvasState,
entityIdentifier: T
): Extract<CanvasEntityState, T> | undefined {
const { id, type } = entityIdentifier;
@ -80,11 +81,11 @@ export function selectEntity<T extends CanvasEntityIdentifier>(
}
/**
* Selected an entity from the canvasV2 slice. If the entity is not found, an error is thrown.
* Selected an entity from the canvas slice. If the entity is not found, an error is thrown.
* Wrapper around {@link selectEntity}.
*/
export function selectEntityOrThrow<T extends CanvasEntityIdentifier>(
state: CanvasV2State,
state: CanvasState,
entityIdentifier: T
): Extract<CanvasEntityState, T> {
const entity = selectEntity(state, entityIdentifier);
@ -96,7 +97,7 @@ export function selectEntityOrThrow<T extends CanvasEntityIdentifier>(
* Selects all entities of the given type.
*/
export function selectAllEntitiesOfType<T extends CanvasEntityState['type']>(
state: CanvasV2State,
state: CanvasState,
type: T
): Extract<CanvasEntityState, { type: T }>[] {
let entities: CanvasEntityState[] = [];
@ -126,7 +127,7 @@ export function selectAllEntitiesOfType<T extends CanvasEntityState['type']>(
/**
* Selects all entities, in the order they are displayed in the list.
*/
export function selectAllEntities(state: CanvasV2State): CanvasEntityState[] {
export function selectAllEntities(state: CanvasState): CanvasEntityState[] {
// These are in the same order as they are displayed in the list!
return [
...state.inpaintMasks.entities.toReversed(),
@ -145,7 +146,7 @@ export function selectAllEntities(state: CanvasV2State): CanvasEntityState[] {
* - Regional guidance
*/
export function selectAllRenderableEntities(
state: CanvasV2State
state: CanvasState
): (CanvasRasterLayerState | CanvasControlLayerState | CanvasInpaintMaskState | CanvasRegionalGuidanceState)[] {
return [
...state.rasterLayers.entities,
@ -159,7 +160,7 @@ export function selectAllRenderableEntities(
* Selects the IP adapter for the specific Regional Guidance layer.
*/
export function selectRegionalGuidanceIPAdapter(
state: CanvasV2State,
state: CanvasState,
entityIdentifier: CanvasEntityIdentifier<'regional_guidance'>,
ipAdapterId: string
) {
@ -169,3 +170,19 @@ export function selectRegionalGuidanceIPAdapter(
}
return entity.ipAdapters.find((ipAdapter) => ipAdapter.id === ipAdapterId);
}
export const selectSelectedEntityIdentifier = createSelector(
selectCanvasSlice,
(canvas) => canvas.selectedEntityIdentifier
);
export const selectIsSelectedEntityDrawable = createSelector(
selectSelectedEntityIdentifier,
(selectedEntityIdentifier) => {
if (!selectedEntityIdentifier) {
return false;
}
return isDrawableEntityType(selectedEntityIdentifier.type);
}
);

View File

@ -692,7 +692,7 @@ export type StagingAreaImage = {
export type SessionMode = 'generate' | 'compose';
export type CanvasV2State = {
export type CanvasState = {
_version: 3;
selectedEntityIdentifier: CanvasEntityIdentifier | null;
inpaintMasks: {

View File

@ -1,7 +1,7 @@
import { ConfirmationAlertDialog, Divider, Flex, FormControl, FormLabel, Switch, Text } from '@invoke-ai/ui-library';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { selectCanvasV2Slice } from 'features/controlLayers/store/selectors';
import { selectCanvasSlice } from 'features/controlLayers/store/selectors';
import { imageDeletionConfirmed } from 'features/deleteImageModal/store/actions';
import { getImageUsage, selectImageUsage } from 'features/deleteImageModal/store/selectors';
import {
@ -20,11 +20,11 @@ import { useTranslation } from 'react-i18next';
import ImageUsageMessage from './ImageUsageMessage';
const selectImageUsages = createMemoizedSelector(
[selectDeleteImageModalSlice, selectNodesSlice, selectCanvasV2Slice, selectImageUsage],
(deleteImageModal, nodes, canvasV2, imagesUsage) => {
[selectDeleteImageModalSlice, selectNodesSlice, selectCanvasSlice, selectImageUsage],
(deleteImageModal, nodes, canvas, imagesUsage) => {
const { imagesToDelete } = deleteImageModal;
const allImageUsage = (imagesToDelete ?? []).map(({ image_name }) => getImageUsage(nodes, canvasV2, image_name));
const allImageUsage = (imagesToDelete ?? []).map(({ image_name }) => getImageUsage(nodes, canvas, image_name));
const imageUsageSummary: ImageUsage = {
isLayerImage: some(allImageUsage, (i) => i.isLayerImage),

View File

@ -1,6 +1,6 @@
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { selectCanvasV2Slice } from 'features/controlLayers/store/selectors';
import type { CanvasV2State } from 'features/controlLayers/store/types';
import { selectCanvasSlice } from 'features/controlLayers/store/selectors';
import type { CanvasState } from 'features/controlLayers/store/types';
import { selectDeleteImageModalSlice } from 'features/deleteImageModal/store/slice';
import { selectNodesSlice } from 'features/nodes/store/nodesSlice';
import type { NodesState } from 'features/nodes/store/types';
@ -10,14 +10,14 @@ import { some } from 'lodash-es';
import type { ImageUsage } from './types';
// TODO(psyche): handle image deletion (canvas sessions?)
export const getImageUsage = (nodes: NodesState, canvasV2: CanvasV2State, image_name: string) => {
export const getImageUsage = (nodes: NodesState, canvas: CanvasState, image_name: string) => {
const isNodesImage = nodes.nodes
.filter(isInvocationNode)
.some((node) =>
some(node.data.inputs, (input) => isImageFieldInputInstance(input) && input.value?.image_name === image_name)
);
const isIPAdapterImage = canvasV2.ipAdapters.entities.some(
const isIPAdapterImage = canvas.ipAdapters.entities.some(
({ ipAdapter }) => ipAdapter.image?.image_name === image_name
);
@ -34,15 +34,15 @@ export const getImageUsage = (nodes: NodesState, canvasV2: CanvasV2State, image_
export const selectImageUsage = createMemoizedSelector(
selectDeleteImageModalSlice,
selectNodesSlice,
selectCanvasV2Slice,
(deleteImageModal, nodes, canvasV2) => {
selectCanvasSlice,
(deleteImageModal, nodes, canvas) => {
const { imagesToDelete } = deleteImageModal;
if (!imagesToDelete.length) {
return [];
}
const imagesUsage = imagesToDelete.map((i) => getImageUsage(nodes, canvasV2, i.image_name));
const imagesUsage = imagesToDelete.map((i) => getImageUsage(nodes, canvas, i.image_name));
return imagesUsage;
}

View File

@ -13,7 +13,7 @@ import {
import { skipToken } from '@reduxjs/toolkit/query';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { useAppSelector } from 'app/store/storeHooks';
import { selectCanvasV2Slice } from 'features/controlLayers/store/selectors';
import { selectCanvasSlice } from 'features/controlLayers/store/selectors';
import ImageUsageMessage from 'features/deleteImageModal/components/ImageUsageMessage';
import { getImageUsage } from 'features/deleteImageModal/store/selectors';
import type { ImageUsage } from 'features/deleteImageModal/store/types';
@ -39,8 +39,8 @@ const DeleteBoardModal = (props: Props) => {
const selectImageUsageSummary = useMemo(
() =>
createMemoizedSelector([selectNodesSlice, selectCanvasV2Slice], (nodes, canvasV2) => {
const allImageUsage = (boardImageNames ?? []).map((imageName) => getImageUsage(nodes, canvasV2, imageName));
createMemoizedSelector([selectNodesSlice, selectCanvasSlice], (nodes, canvas) => {
const allImageUsage = (boardImageNames ?? []).map((imageName) => getImageUsage(nodes, canvas, imageName));
const imageUsageSummary: ImageUsage = {
isLayerImage: some(allImageUsage, (i) => i.isLayerImage),

View File

@ -1,5 +1,5 @@
import { getStore } from 'app/store/nanostores/store';
import { bboxHeightChanged, bboxWidthChanged } from 'features/controlLayers/store/canvasV2Slice';
import { bboxHeightChanged, bboxWidthChanged } from 'features/controlLayers/store/canvasSlice';
import { loraAllDeleted, loraRecalled } from 'features/controlLayers/store/lorasSlice';
import {
negativePrompt2Changed,

View File

@ -1,6 +1,6 @@
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
import { getPrefixedId } from 'features/controlLayers/konva/util';
import type { CanvasV2State, Dimensions } from 'features/controlLayers/store/types';
import type { CanvasState, Dimensions } from 'features/controlLayers/store/types';
import type { Graph } from 'features/nodes/util/graph/generation/Graph';
import { isEqual } from 'lodash-es';
import type { Invocation } from 'services/api/types';
@ -13,7 +13,7 @@ export const addImageToImage = async (
vaeSource: Invocation<'main_model_loader' | 'sdxl_model_loader' | 'seamless' | 'vae_loader'>,
originalSize: Dimensions,
scaledSize: Dimensions,
bbox: CanvasV2State['bbox'],
bbox: CanvasState['bbox'],
denoising_start: number,
fp32: boolean
): Promise<Invocation<'img_resize' | 'l2i'>> => {

View File

@ -1,6 +1,9 @@
import type { RootState } from 'app/store/store';
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
import { getPrefixedId } from 'features/controlLayers/konva/util';
import { selectCanvasSessionSlice } from 'features/controlLayers/store/canvasSessionSlice';
import { selectParamsSlice } from 'features/controlLayers/store/paramsSlice';
import { selectCanvasSlice } from 'features/controlLayers/store/selectors';
import type { Dimensions } from 'features/controlLayers/store/types';
import type { Graph } from 'features/nodes/util/graph/generation/Graph';
import { isEqual } from 'lodash-es';
@ -21,8 +24,11 @@ export const addInpaint = async (
): Promise<Invocation<'canvas_v2_mask_and_crop'>> => {
denoise.denoising_start = denoising_start;
const { params, canvasV2, canvasSession } = state;
const { bbox } = canvasV2;
const params = selectParamsSlice(state);
const canvasSession = selectCanvasSessionSlice(state);
const canvas = selectCanvasSlice(state);
const { bbox } = canvas;
const { mode } = canvasSession;
const initialImage = await manager.compositor.getCompositeRasterLayerImageDTO(bbox.rect);

View File

@ -1,6 +1,9 @@
import type { RootState } from 'app/store/store';
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
import { getPrefixedId } from 'features/controlLayers/konva/util';
import { selectCanvasSessionSlice } from 'features/controlLayers/store/canvasSessionSlice';
import { selectParamsSlice } from 'features/controlLayers/store/paramsSlice';
import { selectCanvasSlice } from 'features/controlLayers/store/selectors';
import type { Dimensions } from 'features/controlLayers/store/types';
import type { Graph } from 'features/nodes/util/graph/generation/Graph';
import { getInfill } from 'features/nodes/util/graph/graphBuilderUtils';
@ -22,8 +25,11 @@ export const addOutpaint = async (
): Promise<Invocation<'canvas_v2_mask_and_crop'>> => {
denoise.denoising_start = denoising_start;
const { params, canvasV2, canvasSession } = state;
const { bbox } = canvasV2;
const params = selectParamsSlice(state);
const canvasSession = selectCanvasSessionSlice(state);
const canvas = selectCanvasSlice(state);
const { bbox } = canvas;
const { mode } = canvasSession;
const initialImage = await manager.compositor.getCompositeRasterLayerImageDTO(bbox.rect);

View File

@ -2,6 +2,10 @@ import { logger } from 'app/logging/logger';
import type { RootState } from 'app/store/store';
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
import { getPrefixedId } from 'features/controlLayers/konva/util';
import { selectCanvasSessionSlice } from 'features/controlLayers/store/canvasSessionSlice';
import { selectCanvasSettingsSlice } from 'features/controlLayers/store/canvasSettingsSlice';
import { selectParamsSlice } from 'features/controlLayers/store/paramsSlice';
import { selectCanvasSlice } from 'features/controlLayers/store/selectors';
import { fetchModelConfigWithTypeGuard } from 'features/metadata/util/modelFetchingHelpers';
import { addControlNets, addT2IAdapters } from 'features/nodes/util/graph/generation/addControlAdapters';
import { addImageToImage } from 'features/nodes/util/graph/generation/addImageToImage';
@ -31,8 +35,12 @@ export const buildSD1Graph = async (
const generationMode = manager.compositor.getGenerationMode();
log.debug({ generationMode }, 'Building SD1/SD2 graph');
const { canvasV2, params, canvasSettings, canvasSession } = state;
const { bbox } = canvasV2;
const params = selectParamsSlice(state);
const canvasSession = selectCanvasSessionSlice(state);
const canvasSettings = selectCanvasSettingsSlice(state);
const canvas = selectCanvasSlice(state);
const { bbox } = canvas;
const {
model,
@ -208,9 +216,9 @@ export const buildSD1Graph = async (
});
const controlNetResult = await addControlNets(
manager,
state.canvasV2.controlLayers.entities,
canvas.controlLayers.entities,
g,
state.canvasV2.bbox.rect,
canvas.bbox.rect,
controlNetCollector,
modelConfig.base
);
@ -226,9 +234,9 @@ export const buildSD1Graph = async (
});
const t2iAdapterResult = await addT2IAdapters(
manager,
state.canvasV2.controlLayers.entities,
canvas.controlLayers.entities,
g,
state.canvasV2.bbox.rect,
canvas.bbox.rect,
t2iAdapterCollector,
modelConfig.base
);
@ -242,13 +250,13 @@ export const buildSD1Graph = async (
type: 'collect',
id: getPrefixedId('ip_adapter_collector'),
});
const ipAdapterResult = addIPAdapters(state.canvasV2.ipAdapters.entities, g, ipAdapterCollector, modelConfig.base);
const ipAdapterResult = addIPAdapters(canvas.ipAdapters.entities, g, ipAdapterCollector, modelConfig.base);
const regionsResult = await addRegions(
manager,
state.canvasV2.regions.entities,
canvas.regions.entities,
g,
state.canvasV2.bbox.rect,
canvas.bbox.rect,
modelConfig.base,
denoise,
posCond,

View File

@ -2,6 +2,10 @@ import { logger } from 'app/logging/logger';
import type { RootState } from 'app/store/store';
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
import { getPrefixedId } from 'features/controlLayers/konva/util';
import { selectCanvasSessionSlice } from 'features/controlLayers/store/canvasSessionSlice';
import { selectCanvasSettingsSlice } from 'features/controlLayers/store/canvasSettingsSlice';
import { selectParamsSlice } from 'features/controlLayers/store/paramsSlice';
import { selectCanvasSlice } from 'features/controlLayers/store/selectors';
import { fetchModelConfigWithTypeGuard } from 'features/metadata/util/modelFetchingHelpers';
import { addControlNets, addT2IAdapters } from 'features/nodes/util/graph/generation/addControlAdapters';
import { addImageToImage } from 'features/nodes/util/graph/generation/addImageToImage';
@ -31,8 +35,12 @@ export const buildSDXLGraph = async (
const generationMode = manager.compositor.getGenerationMode();
log.debug({ generationMode }, 'Building SDXL graph');
const { params, canvasV2, canvasSettings, canvasSession } = state;
const { bbox } = canvasV2;
const params = selectParamsSlice(state);
const canvasSession = selectCanvasSessionSlice(state);
const canvasSettings = selectCanvasSettingsSlice(state);
const canvas = selectCanvasSlice(state);
const { bbox } = canvas;
const {
model,
@ -211,9 +219,9 @@ export const buildSDXLGraph = async (
});
const controlNetResult = await addControlNets(
manager,
state.canvasV2.controlLayers.entities,
canvas.controlLayers.entities,
g,
state.canvasV2.bbox.rect,
canvas.bbox.rect,
controlNetCollector,
modelConfig.base
);
@ -229,9 +237,9 @@ export const buildSDXLGraph = async (
});
const t2iAdapterResult = await addT2IAdapters(
manager,
state.canvasV2.controlLayers.entities,
canvas.controlLayers.entities,
g,
state.canvasV2.bbox.rect,
canvas.bbox.rect,
t2iAdapterCollector,
modelConfig.base
);
@ -245,13 +253,13 @@ export const buildSDXLGraph = async (
type: 'collect',
id: getPrefixedId('ip_adapter_collector'),
});
const ipAdapterResult = addIPAdapters(state.canvasV2.ipAdapters.entities, g, ipAdapterCollector, modelConfig.base);
const ipAdapterResult = addIPAdapters(canvas.ipAdapters.entities, g, ipAdapterCollector, modelConfig.base);
const regionsResult = await addRegions(
manager,
state.canvasV2.regions.entities,
canvas.regions.entities,
g,
state.canvasV2.bbox.rect,
canvas.bbox.rect,
modelConfig.base,
denoise,
posCond,

View File

@ -1,6 +1,6 @@
import type { RootState } from 'app/store/store';
import type { ParamsState } from 'features/controlLayers/store/paramsSlice';
import type { CanvasV2State } from 'features/controlLayers/store/types';
import type { CanvasState } from 'features/controlLayers/store/types';
import type { BoardField } from 'features/nodes/types/common';
import type { Graph } from 'features/nodes/util/graph/generation/Graph';
import { buildPresetModifiedPrompt } from 'features/stylePresets/hooks/usePresetModifiedPrompts';
@ -62,7 +62,7 @@ export const getPresetModifiedPrompts = (
};
};
export const getSizes = (bboxState: CanvasV2State['bbox']) => {
export const getSizes = (bboxState: CanvasState['bbox']) => {
const originalSize = pick(bboxState.rect, 'width', 'height');
const scaledSize = ['auto', 'manual'].includes(bboxState.scaleMethod) ? bboxState.scaledSize : originalSize;
return { originalSize, scaledSize };

View File

@ -1,16 +1,20 @@
import type { ComboboxOnChange, ComboboxOption } from '@invoke-ai/ui-library';
import { Combobox, FormControl, FormLabel } from '@invoke-ai/ui-library';
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/canvasV2Slice';
import { bboxScaleMethodChanged } from 'features/controlLayers/store/canvasSlice';
import { selectCanvasSlice } from 'features/controlLayers/store/selectors';
import { isBoundingBoxScaleMethod } from 'features/controlLayers/store/types';
import { memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
const selectScaleMethod = createSelector(selectCanvasSlice, (canvas) => canvas.bbox.scaleMethod);
const ParamScaleBeforeProcessing = () => {
const dispatch = useAppDispatch();
const { t } = useTranslation();
const scaleMethod = useAppSelector((s) => s.canvasV2.bbox.scaleMethod);
const scaleMethod = useAppSelector(selectScaleMethod);
const OPTIONS: ComboboxOption[] = useMemo(
() => [

View File

@ -1,22 +1,26 @@
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
import { createSelector } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { bboxScaledSizeChanged } from 'features/controlLayers/store/canvasV2Slice';
import { selectOptimalDimension } from 'features/controlLayers/store/selectors';
import { bboxScaledSizeChanged } from 'features/controlLayers/store/canvasSlice';
import { selectCanvasSlice, selectOptimalDimension } from 'features/controlLayers/store/selectors';
import { selectConfigSlice } from 'features/system/store/configSlice';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
const selectIsManual = createSelector(selectCanvasSlice, (canvas) => canvas.bbox.scaleMethod === 'manual');
const selectScaledHeight = createSelector(selectCanvasSlice, (canvas) => canvas.bbox.scaledSize.height);
const selectScaledBoundingBoxHeightConfig = createSelector(
selectConfigSlice,
(config) => config.sd.scaledBoundingBoxHeight
);
const ParamScaledHeight = () => {
const { t } = useTranslation();
const dispatch = useAppDispatch();
const optimalDimension = useAppSelector(selectOptimalDimension);
const isManual = useAppSelector((s) => s.canvasV2.bbox.scaleMethod === 'manual');
const height = useAppSelector((s) => s.canvasV2.bbox.scaledSize.height);
const sliderMin = useAppSelector((s) => s.config.sd.scaledBoundingBoxHeight.sliderMin);
const sliderMax = useAppSelector((s) => s.config.sd.scaledBoundingBoxHeight.sliderMax);
const numberInputMin = useAppSelector((s) => s.config.sd.scaledBoundingBoxHeight.numberInputMin);
const numberInputMax = useAppSelector((s) => s.config.sd.scaledBoundingBoxHeight.numberInputMax);
const coarseStep = useAppSelector((s) => s.config.sd.scaledBoundingBoxHeight.coarseStep);
const fineStep = useAppSelector((s) => s.config.sd.scaledBoundingBoxHeight.fineStep);
const isManual = useAppSelector(selectIsManual);
const scaledHeight = useAppSelector(selectScaledHeight);
const config = useAppSelector(selectScaledBoundingBoxHeightConfig);
const onChange = useCallback(
(height: number) => {
@ -29,21 +33,21 @@ const ParamScaledHeight = () => {
<FormControl isDisabled={!isManual}>
<FormLabel>{t('parameters.scaledHeight')}</FormLabel>
<CompositeSlider
min={sliderMin}
max={sliderMax}
step={coarseStep}
fineStep={fineStep}
value={height}
min={config.sliderMin}
max={config.sliderMax}
step={config.coarseStep}
fineStep={config.fineStep}
value={scaledHeight}
onChange={onChange}
marks
defaultValue={optimalDimension}
/>
<CompositeNumberInput
min={numberInputMin}
max={numberInputMax}
step={coarseStep}
fineStep={fineStep}
value={height}
min={config.numberInputMin}
max={config.numberInputMax}
step={config.coarseStep}
fineStep={config.fineStep}
value={scaledHeight}
onChange={onChange}
defaultValue={optimalDimension}
/>

View File

@ -1,22 +1,26 @@
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
import { createSelector } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { bboxScaledSizeChanged } from 'features/controlLayers/store/canvasV2Slice';
import { selectOptimalDimension } from 'features/controlLayers/store/selectors';
import { bboxScaledSizeChanged } from 'features/controlLayers/store/canvasSlice';
import { selectCanvasSlice, selectOptimalDimension } from 'features/controlLayers/store/selectors';
import { selectConfigSlice } from 'features/system/store/configSlice';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
const selectIsManual = createSelector(selectCanvasSlice, (canvas) => canvas.bbox.scaleMethod === 'manual');
const selectScaledWidth = createSelector(selectCanvasSlice, (canvas) => canvas.bbox.scaledSize.width);
const selectScaledBoundingBoxWidthConfig = createSelector(
selectConfigSlice,
(config) => config.sd.scaledBoundingBoxWidth
);
const ParamScaledWidth = () => {
const { t } = useTranslation();
const dispatch = useAppDispatch();
const optimalDimension = useAppSelector(selectOptimalDimension);
const isManual = useAppSelector((s) => s.canvasV2.bbox.scaleMethod === 'manual');
const width = useAppSelector((s) => s.canvasV2.bbox.scaledSize.width);
const sliderMin = useAppSelector((s) => s.config.sd.scaledBoundingBoxWidth.sliderMin);
const sliderMax = useAppSelector((s) => s.config.sd.scaledBoundingBoxWidth.sliderMax);
const numberInputMin = useAppSelector((s) => s.config.sd.scaledBoundingBoxWidth.numberInputMin);
const numberInputMax = useAppSelector((s) => s.config.sd.scaledBoundingBoxWidth.numberInputMax);
const coarseStep = useAppSelector((s) => s.config.sd.scaledBoundingBoxWidth.coarseStep);
const fineStep = useAppSelector((s) => s.config.sd.scaledBoundingBoxWidth.fineStep);
const isManual = useAppSelector(selectIsManual);
const scaledWidth = useAppSelector(selectScaledWidth);
const config = useAppSelector(selectScaledBoundingBoxWidthConfig);
const onChange = useCallback(
(width: number) => {
dispatch(bboxScaledSizeChanged({ width }));
@ -28,21 +32,21 @@ const ParamScaledWidth = () => {
<FormControl isDisabled={!isManual}>
<FormLabel>{t('parameters.scaledWidth')}</FormLabel>
<CompositeSlider
min={sliderMin}
max={sliderMax}
step={coarseStep}
fineStep={fineStep}
value={width}
min={config.sliderMin}
max={config.sliderMax}
step={config.coarseStep}
fineStep={config.fineStep}
value={scaledWidth}
onChange={onChange}
defaultValue={optimalDimension}
marks
/>
<CompositeNumberInput
min={numberInputMin}
max={numberInputMax}
step={coarseStep}
fineStep={fineStep}
value={width}
min={config.numberInputMin}
max={config.numberInputMax}
step={config.coarseStep}
fineStep={config.fineStep}
value={scaledWidth}
onChange={onChange}
defaultValue={optimalDimension}
/>

View File

@ -1,22 +1,22 @@
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
import { createSelector } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
import { bboxHeightChanged } from 'features/controlLayers/store/canvasV2Slice';
import { selectOptimalDimension } from 'features/controlLayers/store/selectors';
import { bboxHeightChanged } from 'features/controlLayers/store/canvasSlice';
import { selectCanvasSlice, selectOptimalDimension } from 'features/controlLayers/store/selectors';
import { selectConfigSlice } from 'features/system/store/configSlice';
import { memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
const selectHeight = createSelector(selectCanvasSlice, (canvas) => canvas.bbox.rect.height);
const selectHeightConfig = createSelector(selectConfigSlice, (config) => config.sd.height);
export const ParamHeight = memo(() => {
const { t } = useTranslation();
const dispatch = useAppDispatch();
const optimalDimension = useAppSelector(selectOptimalDimension);
const height = useAppSelector((s) => s.canvasV2.bbox.rect.height);
const sliderMin = useAppSelector((s) => s.config.sd.height.sliderMin);
const sliderMax = useAppSelector((s) => s.config.sd.height.sliderMax);
const numberInputMin = useAppSelector((s) => s.config.sd.height.numberInputMin);
const numberInputMax = useAppSelector((s) => s.config.sd.height.numberInputMax);
const coarseStep = useAppSelector((s) => s.config.sd.height.coarseStep);
const fineStep = useAppSelector((s) => s.config.sd.height.fineStep);
const height = useAppSelector(selectHeight);
const config = useAppSelector(selectHeightConfig);
const onChange = useCallback(
(v: number) => {
@ -25,7 +25,10 @@ export const ParamHeight = memo(() => {
[dispatch]
);
const marks = useMemo(() => [sliderMin, optimalDimension, sliderMax], [sliderMin, optimalDimension, sliderMax]);
const marks = useMemo(
() => [config.sliderMin, optimalDimension, config.sliderMax],
[config.sliderMin, config.sliderMax, optimalDimension]
);
return (
<FormControl>
@ -36,20 +39,20 @@ export const ParamHeight = memo(() => {
value={height}
defaultValue={optimalDimension}
onChange={onChange}
min={sliderMin}
max={sliderMax}
step={coarseStep}
fineStep={fineStep}
min={config.sliderMin}
max={config.sliderMax}
step={config.coarseStep}
fineStep={config.fineStep}
marks={marks}
/>
<CompositeNumberInput
value={height}
defaultValue={optimalDimension}
onChange={onChange}
min={numberInputMin}
max={numberInputMax}
step={coarseStep}
fineStep={fineStep}
min={config.numberInputMin}
max={config.numberInputMax}
step={config.coarseStep}
fineStep={config.fineStep}
/>
</FormControl>
);

View File

@ -1,22 +1,22 @@
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
import { createSelector } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
import { bboxWidthChanged } from 'features/controlLayers/store/canvasV2Slice';
import { selectOptimalDimension } from 'features/controlLayers/store/selectors';
import { bboxWidthChanged } from 'features/controlLayers/store/canvasSlice';
import { selectCanvasSlice, selectOptimalDimension } from 'features/controlLayers/store/selectors';
import { selectConfigSlice } from 'features/system/store/configSlice';
import { memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
const selectWidth = createSelector(selectCanvasSlice, (canvas) => canvas.bbox.rect.width);
const selectWidthConfig = createSelector(selectConfigSlice, (config) => config.sd.width);
export const ParamWidth = memo(() => {
const { t } = useTranslation();
const dispatch = useAppDispatch();
const width = useAppSelector((s) => s.canvasV2.bbox.rect.width);
const width = useAppSelector(selectWidth);
const optimalDimension = useAppSelector(selectOptimalDimension);
const sliderMin = useAppSelector((s) => s.config.sd.width.sliderMin);
const sliderMax = useAppSelector((s) => s.config.sd.width.sliderMax);
const numberInputMin = useAppSelector((s) => s.config.sd.width.numberInputMin);
const numberInputMax = useAppSelector((s) => s.config.sd.width.numberInputMax);
const coarseStep = useAppSelector((s) => s.config.sd.width.coarseStep);
const fineStep = useAppSelector((s) => s.config.sd.width.fineStep);
const config = useAppSelector(selectWidthConfig);
const onChange = useCallback(
(v: number) => {
@ -25,7 +25,10 @@ export const ParamWidth = memo(() => {
[dispatch]
);
const marks = useMemo(() => [sliderMin, optimalDimension, sliderMax], [sliderMin, optimalDimension, sliderMax]);
const marks = useMemo(
() => [config.sliderMin, optimalDimension, config.sliderMax],
[config.sliderMax, config.sliderMin, optimalDimension]
);
return (
<FormControl>
@ -36,20 +39,20 @@ export const ParamWidth = memo(() => {
value={width}
onChange={onChange}
defaultValue={optimalDimension}
min={sliderMin}
max={sliderMax}
step={coarseStep}
fineStep={fineStep}
min={config.sliderMin}
max={config.sliderMax}
step={config.coarseStep}
fineStep={config.fineStep}
marks={marks}
/>
<CompositeNumberInput
value={width}
onChange={onChange}
defaultValue={optimalDimension}
min={numberInputMin}
max={numberInputMax}
step={coarseStep}
fineStep={fineStep}
min={config.numberInputMin}
max={config.numberInputMax}
step={config.coarseStep}
fineStep={config.fineStep}
/>
</FormControl>
);

View File

@ -1,18 +1,22 @@
import type { ComboboxOption, SystemStyleObject } from '@invoke-ai/ui-library';
import { Combobox, FormControl, FormLabel } from '@invoke-ai/ui-library';
import { createSelector } from '@reduxjs/toolkit';
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/canvasV2Slice';
import { bboxAspectRatioIdChanged } from 'features/controlLayers/store/canvasSlice';
import { selectCanvasSlice } from 'features/controlLayers/store/selectors';
import { ASPECT_RATIO_OPTIONS } from 'features/parameters/components/DocumentSize/constants';
import { isAspectRatioID } from 'features/parameters/components/DocumentSize/types';
import { memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
const selectAspectRatioID = createSelector(selectCanvasSlice, (canvas) => canvas.bbox.aspectRatio.id);
export const AspectRatioSelect = memo(() => {
const { t } = useTranslation();
const dispatch = useAppDispatch();
const id = useAppSelector((s) => s.canvasV2.bbox.aspectRatio.id);
const id = useAppSelector(selectAspectRatioID);
const onChange = useCallback(
(v: SingleValue<ComboboxOption>) => {

View File

@ -1,14 +1,18 @@
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/canvasV2Slice';
import { bboxAspectRatioLockToggled } from 'features/controlLayers/store/canvasSlice';
import { selectCanvasSlice } from 'features/controlLayers/store/selectors';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { PiLockSimpleFill, PiLockSimpleOpenBold } from 'react-icons/pi';
const selectAspectRatioIsLocked = createSelector(selectCanvasSlice, (canvas) => canvas.bbox.aspectRatio.isLocked);
export const LockAspectRatioButton = memo(() => {
const { t } = useTranslation();
const dispatch = useAppDispatch();
const isLocked = useAppSelector((s) => s.canvasV2.bbox.aspectRatio.isLocked);
const isLocked = useAppSelector(selectAspectRatioIsLocked);
const onClick = useCallback(() => {
dispatch(bboxAspectRatioLockToggled());
}, [dispatch]);

View File

@ -1,17 +1,21 @@
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/canvasV2Slice';
import { selectOptimalDimension } from 'features/controlLayers/store/selectors';
import { bboxSizeOptimized } from 'features/controlLayers/store/canvasSlice';
import { selectCanvasSlice, selectOptimalDimension } from 'features/controlLayers/store/selectors';
import { getIsSizeTooLarge, getIsSizeTooSmall } from 'features/parameters/util/optimalDimension';
import { memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { RiSparklingFill } from 'react-icons/ri';
const selectWidth = createSelector(selectCanvasSlice, (canvas) => canvas.bbox.rect.width);
const selectHeight = createSelector(selectCanvasSlice, (canvas) => canvas.bbox.rect.height);
export const SetOptimalSizeButton = memo(() => {
const { t } = useTranslation();
const dispatch = useAppDispatch();
const width = useAppSelector((s) => s.canvasV2.bbox.rect.width);
const height = useAppSelector((s) => s.canvasV2.bbox.rect.height);
const width = useAppSelector(selectWidth);
const height = useAppSelector(selectHeight);
const optimalDimension = useAppSelector(selectOptimalDimension);
const isSizeTooSmall = useMemo(
() => getIsSizeTooSmall(width, height, optimalDimension),

View File

@ -1,6 +1,6 @@
import { IconButton } from '@invoke-ai/ui-library';
import { useAppDispatch } from 'app/store/storeHooks';
import { bboxDimensionsSwapped } from 'features/controlLayers/store/canvasV2Slice';
import { bboxDimensionsSwapped } from 'features/controlLayers/store/canvasSlice';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { PiArrowsDownUpBold } from 'react-icons/pi';

View File

@ -3,7 +3,7 @@ import { Expander, Flex, FormControlGroup, StandaloneAccordion } from '@invoke-a
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { useAppSelector } from 'app/store/storeHooks';
import { selectParamsSlice } from 'features/controlLayers/store/paramsSlice';
import { selectCanvasV2Slice } from 'features/controlLayers/store/selectors';
import { selectCanvasSlice } from 'features/controlLayers/store/selectors';
import { HrfSettings } from 'features/hrf/components/HrfSettings';
import { selectHrfSlice } from 'features/hrf/store/hrfSlice';
import ParamScaleBeforeProcessing from 'features/parameters/components/Canvas/InfillAndScaling/ParamScaleBeforeProcessing';
@ -20,15 +20,15 @@ import { memo } from 'react';
import { useTranslation } from 'react-i18next';
const selector = createMemoizedSelector(
[selectHrfSlice, selectCanvasV2Slice, selectParamsSlice],
(hrf, canvasV2, params) => {
[selectHrfSlice, selectCanvasSlice, selectParamsSlice],
(hrf, canvas, params) => {
const { shouldRandomizeSeed, model } = params;
const { hrfEnabled } = hrf;
const badges: string[] = [];
const isSDXL = model?.base === 'sdxl';
const { aspectRatio } = canvasV2.bbox;
const { width, height } = canvasV2.bbox.rect;
const { aspectRatio } = canvas.bbox;
const { width, height } = canvas.bbox.rect;
badges.push(`${width}×${height}`);
badges.push(aspectRatio.id);