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, sessionStagingAreaImageAccepted,
sessionStagingAreaReset, sessionStagingAreaReset,
} from 'features/controlLayers/store/canvasSessionSlice'; } 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 type { CanvasRasterLayerState } from 'features/controlLayers/store/types';
import { imageDTOToImageObject } from 'features/controlLayers/store/types'; import { imageDTOToImageObject } from 'features/controlLayers/store/types';
import { toast } from 'features/toast/toast'; import { toast } from 'features/toast/toast';
@ -58,7 +59,7 @@ export const addStagingListeners = (startAppListening: AppStartListening) => {
const stagingAreaImage = state.canvasSession.stagedImages[index]; const stagingAreaImage = state.canvasSession.stagedImages[index];
assert(stagingAreaImage, 'No staged image found to accept'); 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 { imageDTO, offsetX, offsetY } = stagingAreaImage;
const imageObject = imageDTOToImageObject(imageDTO); const imageObject = imageDTOToImageObject(imageDTO);

View File

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

View File

@ -1,7 +1,8 @@
import { logger } from 'app/logging/logger'; import { logger } from 'app/logging/logger';
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware'; import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
import type { AppDispatch, RootState } from 'app/store/store'; 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 { getEntityIdentifier } from 'features/controlLayers/store/types';
import { imageDeletionConfirmed } from 'features/deleteImageModal/store/actions'; import { imageDeletionConfirmed } from 'features/deleteImageModal/store/actions';
import { isModalOpenChanged } from 'features/deleteImageModal/store/slice'; 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) => { // 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 ( // if (
// imageObject?.image.image_name === imageDTO.image_name || // imageObject?.image.image_name === imageDTO.image_name ||
// processedImageObject?.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) => { 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) { if (entity.ipAdapter.image?.image_name === imageDTO.image_name) {
dispatch(ipaImageChanged({ entityIdentifier: getEntityIdentifier(entity), imageDTO: null })); 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) => { 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; let shouldDelete = false;
for (const obj of objects) { for (const obj of objects) {
if (obj.type === 'image' && obj.image.image_name === imageDTO.image_name) { if (obj.type === 'image' && obj.image.image_name === imageDTO.image_name) {

View File

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

View File

@ -1,6 +1,6 @@
import { logger } from 'app/logging/logger'; import { logger } from 'app/logging/logger';
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware'; 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 { selectListBoardsQueryArgs } from 'features/gallery/store/gallerySelectors';
import { fieldImageValueChanged } from 'features/nodes/store/nodesSlice'; import { fieldImageValueChanged } from 'features/nodes/store/nodesSlice';
import { upscaleInitialImageChanged } from 'features/parameters/store/upscaleSlice'; import { upscaleInitialImageChanged } from 'features/parameters/store/upscaleSlice';

View File

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

View File

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

View File

@ -1,5 +1,5 @@
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware'; 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 { import {
setCfgRescaleMultiplier, setCfgRescaleMultiplier,
setCfgScale, setCfgScale,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -11,7 +11,7 @@ import {
controlLayerControlModeChanged, controlLayerControlModeChanged,
controlLayerModelChanged, controlLayerModelChanged,
controlLayerWeightChanged, controlLayerWeightChanged,
} from 'features/controlLayers/store/canvasV2Slice'; } from 'features/controlLayers/store/canvasSlice';
import type { ControlModeV2 } from 'features/controlLayers/store/types'; import type { ControlModeV2 } from 'features/controlLayers/store/types';
import { memo, useCallback } from 'react'; import { memo, useCallback } from 'react';
import type { ControlNetModelConfig, T2IAdapterModelConfig } from 'services/api/types'; 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 { CanvasEntityGroupList } from 'features/controlLayers/components/common/CanvasEntityGroupList';
import { ControlLayer } from 'features/controlLayers/components/ControlLayer/ControlLayer'; import { ControlLayer } from 'features/controlLayers/components/ControlLayer/ControlLayer';
import { mapId } from 'features/controlLayers/konva/util'; 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'; import { memo } from 'react';
const selectEntityIds = createMemoizedSelector(selectCanvasV2Slice, (canvasV2) => { const selectEntityIds = createMemoizedSelector(selectCanvasSlice, (canvas) => {
return canvasV2.controlLayers.entities.map(mapId).reverse(); return canvas.controlLayers.entities.map(mapId).reverse();
}); });
export const ControlLayerEntityList = memo(() => { 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); const layerIds = useAppSelector(selectEntityIds);
if (layerIds.length === 0) { if (layerIds.length === 0) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,7 +1,7 @@
import { MenuItem } from '@invoke-ai/ui-library'; import { MenuItem } from '@invoke-ai/ui-library';
import { useAppDispatch } from 'app/store/storeHooks'; import { useAppDispatch } from 'app/store/storeHooks';
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext'; 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 { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { PiLightningBold } from 'react-icons/pi'; import { PiLightningBold } from 'react-icons/pi';

View File

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

View File

@ -1,14 +1,19 @@
import { Badge } from '@invoke-ai/ui-library'; import { Badge } from '@invoke-ai/ui-library';
import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext'; 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 { memo, useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
export const RegionalGuidanceBadges = memo(() => { export const RegionalGuidanceBadges = memo(() => {
const entityIdentifier = useEntityIdentifierContext('regional_guidance'); const entityIdentifier = useEntityIdentifierContext('regional_guidance');
const { t } = useTranslation(); 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 ( return (
<> <>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,6 @@
import { Button } from '@invoke-ai/ui-library'; import { Button } from '@invoke-ai/ui-library';
import { useAppDispatch } from 'app/store/storeHooks'; 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 { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next'; 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 { useSelectTool, useToolIsSelected } from 'features/controlLayers/components/Tool/hooks';
import { useIsFiltering } from 'features/controlLayers/hooks/useIsFiltering'; import { useIsFiltering } from 'features/controlLayers/hooks/useIsFiltering';
import { useIsTransforming } from 'features/controlLayers/hooks/useIsTransforming'; 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 { memo, useMemo } from 'react';
import { useHotkeys } from 'react-hotkeys-hook'; import { useHotkeys } from 'react-hotkeys-hook';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@ -13,15 +14,10 @@ export const ToolBrushButton = memo(() => {
const { t } = useTranslation(); const { t } = useTranslation();
const isFiltering = useIsFiltering(); const isFiltering = useIsFiltering();
const isTransforming = useIsTransforming(); const isTransforming = useIsTransforming();
const isStaging = useAppSelector((s) => s.canvasSession.isStaging); const isStaging = useAppSelector(selectIsStaging);
const selectBrush = useSelectTool('brush'); const selectBrush = useSelectTool('brush');
const isSelected = useToolIsSelected('brush'); const isSelected = useToolIsSelected('brush');
const isDrawingToolAllowed = useAppSelector((s) => { const isDrawingToolAllowed = useAppSelector(selectIsSelectedEntityDrawable);
if (!s.canvasV2.selectedEntityIdentifier?.type) {
return false;
}
return isDrawableEntityType(s.canvasV2.selectedEntityIdentifier.type);
});
const isDisabled = useMemo(() => { const isDisabled = useMemo(() => {
return isTransforming || isFiltering || isStaging || !isDrawingToolAllowed; 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 { useSelectTool, useToolIsSelected } from 'features/controlLayers/components/Tool/hooks';
import { useIsFiltering } from 'features/controlLayers/hooks/useIsFiltering'; import { useIsFiltering } from 'features/controlLayers/hooks/useIsFiltering';
import { useIsTransforming } from 'features/controlLayers/hooks/useIsTransforming'; 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 { memo, useMemo } from 'react';
import { useHotkeys } from 'react-hotkeys-hook'; import { useHotkeys } from 'react-hotkeys-hook';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@ -13,15 +14,10 @@ export const ToolEraserButton = memo(() => {
const { t } = useTranslation(); const { t } = useTranslation();
const isFiltering = useIsFiltering(); const isFiltering = useIsFiltering();
const isTransforming = useIsTransforming(); const isTransforming = useIsTransforming();
const isStaging = useAppSelector((s) => s.canvasSession.isStaging); const isStaging = useAppSelector(selectIsStaging);
const selectEraser = useSelectTool('eraser'); const selectEraser = useSelectTool('eraser');
const isSelected = useToolIsSelected('eraser'); const isSelected = useToolIsSelected('eraser');
const isDrawingToolAllowed = useAppSelector((s) => { const isDrawingToolAllowed = useAppSelector(selectIsSelectedEntityDrawable);
if (!s.canvasV2.selectedEntityIdentifier?.type) {
return false;
}
return isDrawableEntityType(s.canvasV2.selectedEntityIdentifier.type);
});
const isDisabled = useMemo(() => { const isDisabled = useMemo(() => {
return isTransforming || isFiltering || isStaging || !isDrawingToolAllowed; return isTransforming || isFiltering || isStaging || !isDrawingToolAllowed;
}, [isDrawingToolAllowed, isFiltering, isStaging, isTransforming]); }, [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 { useSelectTool, useToolIsSelected } from 'features/controlLayers/components/Tool/hooks';
import { useIsFiltering } from 'features/controlLayers/hooks/useIsFiltering'; import { useIsFiltering } from 'features/controlLayers/hooks/useIsFiltering';
import { useIsTransforming } from 'features/controlLayers/hooks/useIsTransforming'; 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 { memo, useMemo } from 'react';
import { useHotkeys } from 'react-hotkeys-hook'; import { useHotkeys } from 'react-hotkeys-hook';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@ -15,13 +16,8 @@ export const ToolMoveButton = memo(() => {
const isTransforming = useIsTransforming(); const isTransforming = useIsTransforming();
const selectMove = useSelectTool('move'); const selectMove = useSelectTool('move');
const isSelected = useToolIsSelected('move'); const isSelected = useToolIsSelected('move');
const isStaging = useAppSelector((s) => s.canvasSession.isStaging); const isStaging = useAppSelector(selectIsStaging);
const isDrawingToolAllowed = useAppSelector((s) => { const isDrawingToolAllowed = useAppSelector(selectIsSelectedEntityDrawable);
if (!s.canvasV2.selectedEntityIdentifier?.type) {
return false;
}
return isDrawableEntityType(s.canvasV2.selectedEntityIdentifier.type);
});
const isDisabled = useMemo(() => { const isDisabled = useMemo(() => {
return isTransforming || isFiltering || isStaging || !isDrawingToolAllowed; return isTransforming || isFiltering || isStaging || !isDrawingToolAllowed;
}, [isDrawingToolAllowed, isFiltering, isStaging, isTransforming]); }, [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 { useSelectTool, useToolIsSelected } from 'features/controlLayers/components/Tool/hooks';
import { useIsFiltering } from 'features/controlLayers/hooks/useIsFiltering'; import { useIsFiltering } from 'features/controlLayers/hooks/useIsFiltering';
import { useIsTransforming } from 'features/controlLayers/hooks/useIsTransforming'; 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 { memo, useMemo } from 'react';
import { useHotkeys } from 'react-hotkeys-hook'; import { useHotkeys } from 'react-hotkeys-hook';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@ -15,13 +16,8 @@ export const ToolRectButton = memo(() => {
const isSelected = useToolIsSelected('rect'); const isSelected = useToolIsSelected('rect');
const isFiltering = useIsFiltering(); const isFiltering = useIsFiltering();
const isTransforming = useIsTransforming(); const isTransforming = useIsTransforming();
const isStaging = useAppSelector((s) => s.canvasSession.isStaging); const isStaging = useAppSelector(selectIsStaging);
const isDrawingToolAllowed = useAppSelector((s) => { const isDrawingToolAllowed = useAppSelector(selectIsSelectedEntityDrawable);
if (!s.canvasV2.selectedEntityIdentifier?.type) {
return false;
}
return isDrawableEntityType(s.canvasV2.selectedEntityIdentifier.type);
});
const isDisabled = useMemo(() => { const isDisabled = useMemo(() => {
return isTransforming || isFiltering || isStaging || !isDrawingToolAllowed; return isTransforming || isFiltering || isStaging || !isDrawingToolAllowed;

View File

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

View File

@ -3,7 +3,7 @@ import { useAppDispatch } from 'app/store/storeHooks';
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext'; import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
import { useEntityIsSelected } from 'features/controlLayers/hooks/useEntityIsSelected'; import { useEntityIsSelected } from 'features/controlLayers/hooks/useEntityIsSelected';
import { useEntitySelectionColor } from 'features/controlLayers/hooks/useEntitySelectionColor'; 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 type { PropsWithChildren } from 'react';
import { memo, useCallback } 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 { stopPropagation } from 'common/util/stopPropagation';
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext'; import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
import { useEntityIsEnabled } from 'features/controlLayers/hooks/useEntityIsEnabled'; 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 { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { PiCheckBold } from 'react-icons/pi'; import { PiCheckBold } from 'react-icons/pi';

View File

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

View File

@ -1,7 +1,7 @@
import { MenuItem } from '@invoke-ai/ui-library'; import { MenuItem } from '@invoke-ai/ui-library';
import { useAppDispatch } from 'app/store/storeHooks'; import { useAppDispatch } from 'app/store/storeHooks';
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext'; 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 { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { PiTrashSimpleBold } from 'react-icons/pi'; import { PiTrashSimpleBold } from 'react-icons/pi';

View File

@ -1,7 +1,7 @@
import { MenuItem } from '@invoke-ai/ui-library'; import { MenuItem } from '@invoke-ai/ui-library';
import { useAppDispatch } from 'app/store/storeHooks'; import { useAppDispatch } from 'app/store/storeHooks';
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext'; 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 { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { PiCopyFill } from 'react-icons/pi'; import { PiCopyFill } from 'react-icons/pi';

View File

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

View File

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

View File

@ -4,7 +4,7 @@ import { useBoolean } from 'common/hooks/useBoolean';
import { CanvasEntityTitle } from 'features/controlLayers/components/common/CanvasEntityTitle'; import { CanvasEntityTitle } from 'features/controlLayers/components/common/CanvasEntityTitle';
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext'; import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
import { useEntityTitle } from 'features/controlLayers/hooks/useEntityTitle'; 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 type { ChangeEvent, KeyboardEvent } from 'react';
import { memo, useCallback, useEffect, useRef, useState } 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 { useAppDispatch } from 'app/store/storeHooks';
import { useEntityTypeIsHidden } from 'features/controlLayers/hooks/useEntityTypeIsHidden'; import { useEntityTypeIsHidden } from 'features/controlLayers/hooks/useEntityTypeIsHidden';
import { useEntityTypeString } from 'features/controlLayers/hooks/useEntityTypeString'; 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 type { CanvasEntityIdentifier } from 'features/controlLayers/store/types';
import { memo, useCallback } from 'react'; import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,6 @@
import { createAction, createSlice, type PayloadAction } from '@reduxjs/toolkit'; import { createAction, createSelector, createSlice, type PayloadAction } from '@reduxjs/toolkit';
import type { PersistConfig } from 'app/store/store'; import type { PersistConfig, RootState } from 'app/store/store';
import { canvasV2Slice } from 'features/controlLayers/store/canvasV2Slice'; import { canvasSlice } from 'features/controlLayers/store/canvasSlice';
import type { SessionMode, StagingAreaImage } from 'features/controlLayers/store/types'; import type { SessionMode, StagingAreaImage } from 'features/controlLayers/store/types';
export type CanvasSessionState = { export type CanvasSessionState = {
@ -79,5 +79,9 @@ export const canvasSessionPersistConfig: PersistConfig<CanvasSessionState> = {
persistDenylist: [], persistDenylist: [],
}; };
export const sessionStagingAreaImageAccepted = createAction<{ index: number }>( 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 { createSlice, type PayloadAction } from '@reduxjs/toolkit';
import type { PersistConfig } from 'app/store/store'; import type { PersistConfig, RootState } from 'app/store/store';
export type CanvasSettingsState = { export type CanvasSettingsState = {
imageSmoothing: boolean; imageSmoothing: boolean;
@ -51,3 +51,4 @@ export const canvasSettingsPersistConfig: PersistConfig<CanvasSettingsState> = {
migrate, migrate,
persistDenylist: [], persistDenylist: [],
}; };
export const selectCanvasSettingsSlice = (s: RootState) => s.canvasSettings;

View File

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

View File

@ -9,7 +9,7 @@ import type { ControlNetModelConfig, T2IAdapterModelConfig } from 'services/api/
import type { import type {
CanvasControlLayerState, CanvasControlLayerState,
CanvasRasterLayerState, CanvasRasterLayerState,
CanvasV2State, CanvasState,
ControlModeV2, ControlModeV2,
ControlNetConfig, ControlNetConfig,
EntityIdentifierPayload, EntityIdentifierPayload,
@ -159,4 +159,4 @@ export const controlLayersReducers = {
} }
layer.withTransparencyEffect = !layer.withTransparencyEffect; 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 { selectEntity } from 'features/controlLayers/store/selectors';
import type { import type {
CanvasInpaintMaskState, CanvasInpaintMaskState,
CanvasV2State, CanvasState,
EntityIdentifierPayload, EntityIdentifierPayload,
FillStyle, FillStyle,
RgbColor, RgbColor,
@ -68,4 +68,4 @@ export const inpaintMaskReducers = {
} }
entity.fill.style = style; 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 { import type {
CanvasIPAdapterState, CanvasIPAdapterState,
CanvasV2State, CanvasState,
CLIPVisionModelV2, CLIPVisionModelV2,
EntityIdentifierPayload, EntityIdentifierPayload,
IPMethodV2, IPMethodV2,
@ -104,4 +104,4 @@ export const ipAdaptersReducers = {
} }
entity.ipAdapter.beginEndStepPct = beginEndStepPct; 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 { selectEntity } from 'features/controlLayers/store/selectors';
import { merge } from 'lodash-es'; 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'; import { getEntityIdentifier, initialControlNet } from './types';
export const rasterLayersReducers = { export const rasterLayersReducers = {
@ -67,4 +67,4 @@ export const rasterLayersReducers = {
payload: { ...payload, newId: getPrefixedId('control_layer') }, 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 { getPrefixedId } from 'features/controlLayers/konva/util';
import { selectEntity, selectRegionalGuidanceIPAdapter } from 'features/controlLayers/store/selectors'; import { selectEntity, selectRegionalGuidanceIPAdapter } from 'features/controlLayers/store/selectors';
import type { import type {
CanvasV2State, CanvasState,
CLIPVisionModelV2, CLIPVisionModelV2,
EntityIdentifierPayload, EntityIdentifierPayload,
FillStyle, FillStyle,
@ -29,7 +29,7 @@ const DEFAULT_MASK_COLORS: RgbColor[] = [
{ r: 161, g: 120, b: 214 }, // rgb(161, 120, 214) { 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; const lastFill = state.regions.entities.slice(-1)[0]?.fill.color;
let i = DEFAULT_MASK_COLORS.findIndex((c) => isEqual(c, lastFill)); let i = DEFAULT_MASK_COLORS.findIndex((c) => isEqual(c, lastFill));
if (i === -1) { if (i === -1) {
@ -249,4 +249,4 @@ export const regionsReducers = {
} }
ipAdapter.clipVisionModel = clipVisionModel; ipAdapter.clipVisionModel = clipVisionModel;
}, },
} satisfies SliceCaseReducers<CanvasV2State>; } satisfies SliceCaseReducers<CanvasState>;

View File

@ -1,22 +1,23 @@
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import type { RootState } from 'app/store/store'; import type { RootState } from 'app/store/store';
import { selectParamsSlice } from 'features/controlLayers/store/paramsSlice'; import { selectParamsSlice } from 'features/controlLayers/store/paramsSlice';
import type { import {
CanvasControlLayerState, type CanvasControlLayerState,
CanvasEntityIdentifier, type CanvasEntityIdentifier,
CanvasEntityState, type CanvasEntityState,
CanvasInpaintMaskState, type CanvasInpaintMaskState,
CanvasRasterLayerState, type CanvasRasterLayerState,
CanvasRegionalGuidanceState, type CanvasRegionalGuidanceState,
CanvasV2State, type CanvasState,
isDrawableEntityType,
} from 'features/controlLayers/store/types'; } from 'features/controlLayers/store/types';
import { getOptimalDimension } from 'features/parameters/util/optimalDimension'; import { getOptimalDimension } from 'features/parameters/util/optimalDimension';
import { assert } from 'tsafe'; 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: * 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. * It does not check for validity of the entities.
*/ */
export const selectEntityCount = createSelector(selectCanvasV2Slice, (canvasV2) => { export const selectEntityCount = createSelector(selectCanvasSlice, (canvas) => {
return ( return (
canvasV2.regions.entities.length + canvas.regions.entities.length +
canvasV2.ipAdapters.entities.length + canvas.ipAdapters.entities.length +
canvasV2.rasterLayers.entities.length + canvas.rasterLayers.entities.length +
canvasV2.controlLayers.entities.length + canvas.controlLayers.entities.length +
canvasV2.inpaintMasks.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. * return type will be narrowed as well.
*/ */
export function selectEntity<T extends CanvasEntityIdentifier>( export function selectEntity<T extends CanvasEntityIdentifier>(
state: CanvasV2State, state: CanvasState,
entityIdentifier: T entityIdentifier: T
): Extract<CanvasEntityState, T> | undefined { ): Extract<CanvasEntityState, T> | undefined {
const { id, type } = entityIdentifier; 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}. * Wrapper around {@link selectEntity}.
*/ */
export function selectEntityOrThrow<T extends CanvasEntityIdentifier>( export function selectEntityOrThrow<T extends CanvasEntityIdentifier>(
state: CanvasV2State, state: CanvasState,
entityIdentifier: T entityIdentifier: T
): Extract<CanvasEntityState, T> { ): Extract<CanvasEntityState, T> {
const entity = selectEntity(state, entityIdentifier); const entity = selectEntity(state, entityIdentifier);
@ -96,7 +97,7 @@ export function selectEntityOrThrow<T extends CanvasEntityIdentifier>(
* Selects all entities of the given type. * Selects all entities of the given type.
*/ */
export function selectAllEntitiesOfType<T extends CanvasEntityState['type']>( export function selectAllEntitiesOfType<T extends CanvasEntityState['type']>(
state: CanvasV2State, state: CanvasState,
type: T type: T
): Extract<CanvasEntityState, { type: T }>[] { ): Extract<CanvasEntityState, { type: T }>[] {
let entities: CanvasEntityState[] = []; 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. * 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! // These are in the same order as they are displayed in the list!
return [ return [
...state.inpaintMasks.entities.toReversed(), ...state.inpaintMasks.entities.toReversed(),
@ -145,7 +146,7 @@ export function selectAllEntities(state: CanvasV2State): CanvasEntityState[] {
* - Regional guidance * - Regional guidance
*/ */
export function selectAllRenderableEntities( export function selectAllRenderableEntities(
state: CanvasV2State state: CanvasState
): (CanvasRasterLayerState | CanvasControlLayerState | CanvasInpaintMaskState | CanvasRegionalGuidanceState)[] { ): (CanvasRasterLayerState | CanvasControlLayerState | CanvasInpaintMaskState | CanvasRegionalGuidanceState)[] {
return [ return [
...state.rasterLayers.entities, ...state.rasterLayers.entities,
@ -159,7 +160,7 @@ export function selectAllRenderableEntities(
* Selects the IP adapter for the specific Regional Guidance layer. * Selects the IP adapter for the specific Regional Guidance layer.
*/ */
export function selectRegionalGuidanceIPAdapter( export function selectRegionalGuidanceIPAdapter(
state: CanvasV2State, state: CanvasState,
entityIdentifier: CanvasEntityIdentifier<'regional_guidance'>, entityIdentifier: CanvasEntityIdentifier<'regional_guidance'>,
ipAdapterId: string ipAdapterId: string
) { ) {
@ -169,3 +170,19 @@ export function selectRegionalGuidanceIPAdapter(
} }
return entity.ipAdapters.find((ipAdapter) => ipAdapter.id === ipAdapterId); 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 SessionMode = 'generate' | 'compose';
export type CanvasV2State = { export type CanvasState = {
_version: 3; _version: 3;
selectedEntityIdentifier: CanvasEntityIdentifier | null; selectedEntityIdentifier: CanvasEntityIdentifier | null;
inpaintMasks: { inpaintMasks: {

View File

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

View File

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

View File

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

View File

@ -1,5 +1,5 @@
import { getStore } from 'app/store/nanostores/store'; 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 { loraAllDeleted, loraRecalled } from 'features/controlLayers/store/lorasSlice';
import { import {
negativePrompt2Changed, negativePrompt2Changed,

View File

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

View File

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

View File

@ -1,6 +1,9 @@
import type { RootState } from 'app/store/store'; import type { RootState } from 'app/store/store';
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager'; import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
import { getPrefixedId } from 'features/controlLayers/konva/util'; 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 { Dimensions } from 'features/controlLayers/store/types';
import type { Graph } from 'features/nodes/util/graph/generation/Graph'; import type { Graph } from 'features/nodes/util/graph/generation/Graph';
import { getInfill } from 'features/nodes/util/graph/graphBuilderUtils'; import { getInfill } from 'features/nodes/util/graph/graphBuilderUtils';
@ -22,8 +25,11 @@ export const addOutpaint = async (
): Promise<Invocation<'canvas_v2_mask_and_crop'>> => { ): Promise<Invocation<'canvas_v2_mask_and_crop'>> => {
denoise.denoising_start = denoising_start; denoise.denoising_start = denoising_start;
const { params, canvasV2, canvasSession } = state; const params = selectParamsSlice(state);
const { bbox } = canvasV2; const canvasSession = selectCanvasSessionSlice(state);
const canvas = selectCanvasSlice(state);
const { bbox } = canvas;
const { mode } = canvasSession; const { mode } = canvasSession;
const initialImage = await manager.compositor.getCompositeRasterLayerImageDTO(bbox.rect); 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 { RootState } from 'app/store/store';
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager'; import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
import { getPrefixedId } from 'features/controlLayers/konva/util'; 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 { fetchModelConfigWithTypeGuard } from 'features/metadata/util/modelFetchingHelpers';
import { addControlNets, addT2IAdapters } from 'features/nodes/util/graph/generation/addControlAdapters'; import { addControlNets, addT2IAdapters } from 'features/nodes/util/graph/generation/addControlAdapters';
import { addImageToImage } from 'features/nodes/util/graph/generation/addImageToImage'; import { addImageToImage } from 'features/nodes/util/graph/generation/addImageToImage';
@ -31,8 +35,12 @@ export const buildSD1Graph = async (
const generationMode = manager.compositor.getGenerationMode(); const generationMode = manager.compositor.getGenerationMode();
log.debug({ generationMode }, 'Building SD1/SD2 graph'); log.debug({ generationMode }, 'Building SD1/SD2 graph');
const { canvasV2, params, canvasSettings, canvasSession } = state; const params = selectParamsSlice(state);
const { bbox } = canvasV2; const canvasSession = selectCanvasSessionSlice(state);
const canvasSettings = selectCanvasSettingsSlice(state);
const canvas = selectCanvasSlice(state);
const { bbox } = canvas;
const { const {
model, model,
@ -208,9 +216,9 @@ export const buildSD1Graph = async (
}); });
const controlNetResult = await addControlNets( const controlNetResult = await addControlNets(
manager, manager,
state.canvasV2.controlLayers.entities, canvas.controlLayers.entities,
g, g,
state.canvasV2.bbox.rect, canvas.bbox.rect,
controlNetCollector, controlNetCollector,
modelConfig.base modelConfig.base
); );
@ -226,9 +234,9 @@ export const buildSD1Graph = async (
}); });
const t2iAdapterResult = await addT2IAdapters( const t2iAdapterResult = await addT2IAdapters(
manager, manager,
state.canvasV2.controlLayers.entities, canvas.controlLayers.entities,
g, g,
state.canvasV2.bbox.rect, canvas.bbox.rect,
t2iAdapterCollector, t2iAdapterCollector,
modelConfig.base modelConfig.base
); );
@ -242,13 +250,13 @@ export const buildSD1Graph = async (
type: 'collect', type: 'collect',
id: getPrefixedId('ip_adapter_collector'), 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( const regionsResult = await addRegions(
manager, manager,
state.canvasV2.regions.entities, canvas.regions.entities,
g, g,
state.canvasV2.bbox.rect, canvas.bbox.rect,
modelConfig.base, modelConfig.base,
denoise, denoise,
posCond, posCond,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,14 +1,18 @@
import { IconButton } from '@invoke-ai/ui-library'; import { IconButton } from '@invoke-ai/ui-library';
import { createSelector } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; 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 { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { PiLockSimpleFill, PiLockSimpleOpenBold } from 'react-icons/pi'; import { PiLockSimpleFill, PiLockSimpleOpenBold } from 'react-icons/pi';
const selectAspectRatioIsLocked = createSelector(selectCanvasSlice, (canvas) => canvas.bbox.aspectRatio.isLocked);
export const LockAspectRatioButton = memo(() => { export const LockAspectRatioButton = memo(() => {
const { t } = useTranslation(); const { t } = useTranslation();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const isLocked = useAppSelector((s) => s.canvasV2.bbox.aspectRatio.isLocked); const isLocked = useAppSelector(selectAspectRatioIsLocked);
const onClick = useCallback(() => { const onClick = useCallback(() => {
dispatch(bboxAspectRatioLockToggled()); dispatch(bboxAspectRatioLockToggled());
}, [dispatch]); }, [dispatch]);

View File

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

View File

@ -1,6 +1,6 @@
import { IconButton } from '@invoke-ai/ui-library'; import { IconButton } from '@invoke-ai/ui-library';
import { useAppDispatch } from 'app/store/storeHooks'; 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 { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { PiArrowsDownUpBold } from 'react-icons/pi'; 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 { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { selectParamsSlice } from 'features/controlLayers/store/paramsSlice'; 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 { HrfSettings } from 'features/hrf/components/HrfSettings';
import { selectHrfSlice } from 'features/hrf/store/hrfSlice'; import { selectHrfSlice } from 'features/hrf/store/hrfSlice';
import ParamScaleBeforeProcessing from 'features/parameters/components/Canvas/InfillAndScaling/ParamScaleBeforeProcessing'; import ParamScaleBeforeProcessing from 'features/parameters/components/Canvas/InfillAndScaling/ParamScaleBeforeProcessing';
@ -20,15 +20,15 @@ import { memo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
const selector = createMemoizedSelector( const selector = createMemoizedSelector(
[selectHrfSlice, selectCanvasV2Slice, selectParamsSlice], [selectHrfSlice, selectCanvasSlice, selectParamsSlice],
(hrf, canvasV2, params) => { (hrf, canvas, params) => {
const { shouldRandomizeSeed, model } = params; const { shouldRandomizeSeed, model } = params;
const { hrfEnabled } = hrf; const { hrfEnabled } = hrf;
const badges: string[] = []; const badges: string[] = [];
const isSDXL = model?.base === 'sdxl'; const isSDXL = model?.base === 'sdxl';
const { aspectRatio } = canvasV2.bbox; const { aspectRatio } = canvas.bbox;
const { width, height } = canvasV2.bbox.rect; const { width, height } = canvas.bbox.rect;
badges.push(`${width}×${height}`); badges.push(`${width}×${height}`);
badges.push(aspectRatio.id); badges.push(aspectRatio.id);