refactor(ui): divvy up canvas state a bit

This commit is contained in:
psychedelicious 2024-06-20 21:16:36 +10:00
parent 0c9cf73702
commit 14d0bfbef6
20 changed files with 128 additions and 130 deletions

View File

@ -124,7 +124,7 @@ const createSelector = (templates: Templates) =>
reasons.push({ content: i18n.t('parameters.invoke.noModelSelected') });
}
canvasV2.controlAdapters
canvasV2.controlAdapters.entities
.filter((ca) => ca.isEnabled)
.forEach((ca, i) => {
const layerLiteral = i18n.t('controlLayers.layers_one');
@ -160,7 +160,7 @@ const createSelector = (templates: Templates) =>
}
});
canvasV2.ipAdapters
canvasV2.ipAdapters.entities
.filter((ipa) => ipa.isEnabled)
.forEach((ipa, i) => {
const layerLiteral = i18n.t('controlLayers.layers_one');
@ -188,7 +188,7 @@ const createSelector = (templates: Templates) =>
}
});
canvasV2.regions
canvasV2.regions.entities
.filter((rg) => rg.isEnabled)
.forEach((rg, i) => {
const layerLiteral = i18n.t('controlLayers.layers_one');
@ -225,7 +225,7 @@ const createSelector = (templates: Templates) =>
}
});
canvasV2.layers
canvasV2.layers.entities
.filter((l) => l.isEnabled)
.forEach((l, i) => {
const layerLiteral = i18n.t('controlLayers.layers_one');
@ -234,13 +234,6 @@ const createSelector = (templates: Templates) =>
const prefix = `${layerLiteral} #${layerNumber} (${layerType})`;
const problems: string[] = [];
// if (l.type === 'initial_image_layer') {
// // Must have an image
// if (!l.image) {
// problems.push(i18n.t('parameters.invoke.layer.initialImageNoImageSelected'));
// }
// }
if (problems.length) {
const content = upperFirst(problems.join(', '));
reasons.push({ prefix, content });

View File

@ -21,8 +21,8 @@ export const AddPromptButtons = ({ id }: AddPromptButtonProps) => {
const [addIPAdapter, isAddIPAdapterDisabled] = useAddIPAdapterToRGLayer(id);
const selectValidActions = useMemo(
() =>
createMemoizedSelector(selectCanvasV2Slice, (caState) => {
const rg = caState.regions.find((rg) => rg.id === id);
createMemoizedSelector(selectCanvasV2Slice, (canvasV2) => {
const rg = canvasV2.regions.entities.find((rg) => rg.id === id);
return {
canAddPositivePrompt: rg?.positivePrompt === null,
canAddNegativePrompt: rg?.negativePrompt === null,

View File

@ -32,8 +32,8 @@ export const CAActionsMenu = memo(({ id }: Props) => {
() =>
createMemoizedSelector(selectCanvasV2Slice, (canvasV2) => {
const ca = selectCAOrThrow(canvasV2, id);
const caIndex = canvasV2.controlAdapters.indexOf(ca);
const caCount = canvasV2.controlAdapters.length;
const caIndex = canvasV2.controlAdapters.entities.indexOf(ca);
const caCount = canvasV2.controlAdapters.entities.length;
return {
canMoveForward: caIndex < caCount - 1,
canMoveBackward: caIndex > 0,

View File

@ -15,11 +15,11 @@ import { selectCanvasV2Slice } from 'features/controlLayers/store/canvasV2Slice'
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
const selectEntityIds = createMemoizedSelector(selectCanvasV2Slice, (canvasV2State) => {
const rgIds = canvasV2State.regions.map(mapId).reverse();
const caIds = canvasV2State.controlAdapters.map(mapId).reverse();
const ipaIds = canvasV2State.ipAdapters.map(mapId).reverse();
const layerIds = canvasV2State.layers.map(mapId).reverse();
const selectEntityIds = createMemoizedSelector(selectCanvasV2Slice, (canvasV2) => {
const rgIds = canvasV2.regions.entities.map(mapId).reverse();
const caIds = canvasV2.controlAdapters.entities.map(mapId).reverse();
const ipaIds = canvasV2.ipAdapters.entities.map(mapId).reverse();
const layerIds = canvasV2.layers.entities.map(mapId).reverse();
const entityCount = rgIds.length + caIds.length + ipaIds.length + layerIds.length;
return { rgIds, caIds, ipaIds, layerIds, entityCount };
});

View File

@ -10,10 +10,10 @@ export const DeleteAllLayersButton = memo(() => {
const dispatch = useAppDispatch();
const entityCount = useAppSelector((s) => {
return (
s.canvasV2.regions.length +
s.canvasV2.controlAdapters.length +
s.canvasV2.ipAdapters.length +
s.canvasV2.layers.length
s.canvasV2.regions.entities.length +
s.canvasV2.controlAdapters.entities.length +
s.canvasV2.ipAdapters.entities.length +
s.canvasV2.layers.entities.length
);
});
const onClick = useCallback(() => {

View File

@ -32,8 +32,8 @@ export const LayerActionsMenu = memo(({ id }: Props) => {
() =>
createMemoizedSelector(selectCanvasV2Slice, (canvasV2) => {
const layer = selectLayerOrThrow(canvasV2, id);
const layerIndex = canvasV2.layers.indexOf(layer);
const layerCount = canvasV2.layers.length;
const layerIndex = canvasV2.layers.entities.indexOf(layer);
const layerCount = canvasV2.layers.entities.length;
return {
canMoveForward: layerIndex < layerCount - 1,
canMoveBackward: layerIndex > 0,

View File

@ -39,8 +39,8 @@ export const RGActionsMenu = memo(({ id }: Props) => {
() =>
createMemoizedSelector(selectCanvasV2Slice, (canvasV2) => {
const rg = selectRGOrThrow(canvasV2, id);
const rgIndex = canvasV2.regions.indexOf(rg);
const rgCount = canvasV2.regions.length;
const rgIndex = canvasV2.regions.entities.indexOf(rg);
const rgCount = canvasV2.regions.entities.length;
return {
isMoveForwardOneDisabled: rgIndex < rgCount - 1,
isMoveBackardOneDisabled: rgIndex > 0,

View File

@ -170,13 +170,13 @@ export const initializeRenderer = (
if (!identifier) {
selectedEntity = null;
} else if (identifier.type === 'layer') {
selectedEntity = canvasV2.layers.find((i) => i.id === identifier.id) ?? null;
selectedEntity = canvasV2.layers.entities.find((i) => i.id === identifier.id) ?? null;
} else if (identifier.type === 'control_adapter') {
selectedEntity = canvasV2.controlAdapters.find((i) => i.id === identifier.id) ?? null;
selectedEntity = canvasV2.controlAdapters.entities.find((i) => i.id === identifier.id) ?? null;
} else if (identifier.type === 'ip_adapter') {
selectedEntity = canvasV2.ipAdapters.find((i) => i.id === identifier.id) ?? null;
selectedEntity = canvasV2.ipAdapters.entities.find((i) => i.id === identifier.id) ?? null;
} else if (identifier.type === 'regional_guidance') {
selectedEntity = canvasV2.regions.find((i) => i.id === identifier.id) ?? null;
selectedEntity = canvasV2.regions.entities.find((i) => i.id === identifier.id) ?? null;
} else {
selectedEntity = null;
}
@ -304,23 +304,23 @@ export const initializeRenderer = (
if (
isFirstRender ||
canvasV2.layers !== prevCanvasV2.layers ||
canvasV2.layers.entities !== prevCanvasV2.layers.entities ||
canvasV2.tool.selected !== prevCanvasV2.tool.selected
) {
logIfDebugging('Rendering layers');
renderLayers(manager, canvasV2.layers, canvasV2.tool.selected, onPosChanged);
renderLayers(manager, canvasV2.layers.entities, canvasV2.tool.selected, onPosChanged);
}
if (
isFirstRender ||
canvasV2.regions !== prevCanvasV2.regions ||
canvasV2.regions.entities !== prevCanvasV2.regions.entities ||
canvasV2.settings.maskOpacity !== prevCanvasV2.settings.maskOpacity ||
canvasV2.tool.selected !== prevCanvasV2.tool.selected
) {
logIfDebugging('Rendering regions');
renderRegions(
manager,
canvasV2.regions,
canvasV2.regions.entities,
canvasV2.settings.maskOpacity,
canvasV2.tool.selected,
canvasV2.selectedEntityIdentifier,
@ -328,9 +328,9 @@ export const initializeRenderer = (
);
}
if (isFirstRender || canvasV2.controlAdapters !== prevCanvasV2.controlAdapters) {
if (isFirstRender || canvasV2.controlAdapters.entities !== prevCanvasV2.controlAdapters.entities) {
logIfDebugging('Rendering control adapters');
renderControlAdapters(manager, canvasV2.controlAdapters);
renderControlAdapters(manager, canvasV2.controlAdapters.entities);
}
if (isFirstRender || canvasV2.document !== prevCanvasV2.document) {
@ -355,12 +355,12 @@ export const initializeRenderer = (
if (
isFirstRender ||
canvasV2.layers !== prevCanvasV2.layers ||
canvasV2.controlAdapters !== prevCanvasV2.controlAdapters ||
canvasV2.regions !== prevCanvasV2.regions
canvasV2.layers.entities !== prevCanvasV2.layers.entities ||
canvasV2.controlAdapters.entities !== prevCanvasV2.controlAdapters.entities ||
canvasV2.regions.entities !== prevCanvasV2.regions.entities
) {
logIfDebugging('Arranging entities');
arrangeEntities(manager, canvasV2.layers, canvasV2.controlAdapters, canvasV2.regions);
arrangeEntities(manager, canvasV2.layers.entities, canvasV2.controlAdapters.entities, canvasV2.regions.entities);
}
prevCanvasV2 = canvasV2;

View File

@ -16,18 +16,17 @@ import { toolReducers } from 'features/controlLayers/store/toolReducers';
import { initialAspectRatioState } from 'features/parameters/components/ImageSize/constants';
import type { AspectRatioState } from 'features/parameters/components/ImageSize/types';
import { atom } from 'nanostores';
import type { ImageDTO } from 'services/api/types';
import type { CanvasEntityIdentifier, CanvasV2State, StageAttrs } from './types';
import { DEFAULT_RGBA_COLOR, imageDTOToImageWithDims } from './types';
import { DEFAULT_RGBA_COLOR } from './types';
const initialState: CanvasV2State = {
_version: 3,
selectedEntityIdentifier: null,
layers: [],
controlAdapters: [],
ipAdapters: [],
regions: [],
layers: { entities: [], baseLayerImageCache: null },
controlAdapters: { entities: [] },
ipAdapters: { entities: [] },
regions: { entities: [] },
loras: [],
inpaintMask: {
bbox: null,
@ -120,7 +119,6 @@ const initialState: CanvasV2State = {
refinerNegativeAestheticScore: 2.5,
refinerStart: 0.8,
},
baseLayerImageCache: null,
};
export const canvasV2Slice = createSlice({
@ -162,14 +160,11 @@ export const canvasV2Slice = createSlice({
state.selectedEntityIdentifier = action.payload;
},
allEntitiesDeleted: (state) => {
state.regions = [];
state.layers = [];
state.ipAdapters = [];
state.controlAdapters = [];
state.baseLayerImageCache = null;
},
baseLayerImageCacheChanged: (state, action: PayloadAction<ImageDTO | null>) => {
state.baseLayerImageCache = action.payload ? imageDTOToImageWithDims(action.payload) : null;
state.regions.entities = [];
state.layers.entities = [];
state.layers.baseLayerImageCache = null;
state.ipAdapters.entities = [];
state.controlAdapters.entities = [];
},
},
});

View File

@ -20,7 +20,7 @@ import type {
} from './types';
import { buildControlAdapterProcessorV2, imageDTOToImageObject } from './types';
export const selectCA = (state: CanvasV2State, id: string) => state.controlAdapters.find((ca) => ca.id === id);
export const selectCA = (state: CanvasV2State, id: string) => state.controlAdapters.entities.find((ca) => ca.id === id);
export const selectCAOrThrow = (state: CanvasV2State, id: string) => {
const ca = selectCA(state, id);
assert(ca, `Control Adapter with id ${id} not found`);
@ -31,7 +31,7 @@ export const controlAdaptersReducers = {
caAdded: {
reducer: (state, action: PayloadAction<{ id: string; config: ControlNetConfig | T2IAdapterConfig }>) => {
const { id, config } = action.payload;
state.controlAdapters.push({
state.controlAdapters.entities.push({
id,
type: 'control_adapter',
x: 0,
@ -52,7 +52,7 @@ export const controlAdaptersReducers = {
},
caRecalled: (state, action: PayloadAction<{ data: ControlAdapterEntity }>) => {
const { data } = action.payload;
state.controlAdapters.push(data);
state.controlAdapters.entities.push(data);
state.selectedEntityIdentifier = { type: 'control_adapter', id: data.id };
},
caIsEnabledToggled: (state, action: PayloadAction<{ id: string }>) => {
@ -83,10 +83,10 @@ export const controlAdaptersReducers = {
},
caDeleted: (state, action: PayloadAction<{ id: string }>) => {
const { id } = action.payload;
state.controlAdapters = state.controlAdapters.filter((ca) => ca.id !== id);
state.controlAdapters.entities = state.controlAdapters.entities.filter((ca) => ca.id !== id);
},
caAllDeleted: (state) => {
state.controlAdapters = [];
state.controlAdapters.entities = [];
},
caOpacityChanged: (state, action: PayloadAction<{ id: string; opacity: number }>) => {
const { id, opacity } = action.payload;
@ -102,7 +102,7 @@ export const controlAdaptersReducers = {
if (!ca) {
return;
}
moveOneToEnd(state.controlAdapters, ca);
moveOneToEnd(state.controlAdapters.entities, ca);
},
caMovedToFront: (state, action: PayloadAction<{ id: string }>) => {
const { id } = action.payload;
@ -110,7 +110,7 @@ export const controlAdaptersReducers = {
if (!ca) {
return;
}
moveToEnd(state.controlAdapters, ca);
moveToEnd(state.controlAdapters.entities, ca);
},
caMovedBackwardOne: (state, action: PayloadAction<{ id: string }>) => {
const { id } = action.payload;
@ -118,7 +118,7 @@ export const controlAdaptersReducers = {
if (!ca) {
return;
}
moveOneToStart(state.controlAdapters, ca);
moveOneToStart(state.controlAdapters.entities, ca);
},
caMovedToBack: (state, action: PayloadAction<{ id: string }>) => {
const { id } = action.payload;
@ -126,7 +126,7 @@ export const controlAdaptersReducers = {
if (!ca) {
return;
}
moveToStart(state.controlAdapters, ca);
moveToStart(state.controlAdapters.entities, ca);
},
caImageChanged: {
reducer: (state, action: PayloadAction<{ id: string; imageDTO: ImageDTO | null; objectId: string }>) => {
@ -195,11 +195,11 @@ export const controlAdaptersReducers = {
// We may need to convert the CA to match the model
if (ca.adapterType === 't2i_adapter' && ca.model.type === 'controlnet') {
const convertedCA: ControlNetData = { ...ca, adapterType: 'controlnet', controlMode: 'balanced' };
state.controlAdapters.splice(state.controlAdapters.indexOf(ca), 1, convertedCA);
state.controlAdapters.entities.splice(state.controlAdapters.entities.indexOf(ca), 1, convertedCA);
} else if (ca.adapterType === 'controlnet' && ca.model.type === 't2i_adapter') {
const { controlMode: _, ...rest } = ca;
const convertedCA: T2IAdapterData = { ...rest, adapterType: 't2i_adapter' };
state.controlAdapters.splice(state.controlAdapters.indexOf(ca), 1, convertedCA);
state.controlAdapters.entities.splice(state.controlAdapters.entities.indexOf(ca), 1, convertedCA);
}
},
caControlModeChanged: (state, action: PayloadAction<{ id: string; controlMode: ControlModeV2 }>) => {

View File

@ -13,7 +13,7 @@ import type {
} from './types';
import { imageDTOToImageObject } from './types';
export const selectIPA = (state: CanvasV2State, id: string) => state.ipAdapters.find((ipa) => ipa.id === id);
export const selectIPA = (state: CanvasV2State, id: string) => state.ipAdapters.entities.find((ipa) => ipa.id === id);
export const selectIPAOrThrow = (state: CanvasV2State, id: string) => {
const ipa = selectIPA(state, id);
assert(ipa, `IP Adapter with id ${id} not found`);
@ -30,14 +30,14 @@ export const ipAdaptersReducers = {
isEnabled: true,
...config,
};
state.ipAdapters.push(layer);
state.ipAdapters.entities.push(layer);
state.selectedEntityIdentifier = { type: 'ip_adapter', id };
},
prepare: (payload: { config: IPAdapterConfig }) => ({ payload: { id: uuidv4(), ...payload } }),
},
ipaRecalled: (state, action: PayloadAction<{ data: IPAdapterEntity }>) => {
const { data } = action.payload;
state.ipAdapters.push(data);
state.ipAdapters.entities.push(data);
state.selectedEntityIdentifier = { type: 'ip_adapter', id: data.id };
},
ipaIsEnabledToggled: (state, action: PayloadAction<{ id: string }>) => {
@ -49,10 +49,10 @@ export const ipAdaptersReducers = {
},
ipaDeleted: (state, action: PayloadAction<{ id: string }>) => {
const { id } = action.payload;
state.ipAdapters = state.ipAdapters.filter((ipa) => ipa.id !== id);
state.ipAdapters.entities = state.ipAdapters.entities.filter((ipa) => ipa.id !== id);
},
ipaAllDeleted: (state) => {
state.ipAdapters = [];
state.ipAdapters.entities = [];
},
ipaImageChanged: {
reducer: (state, action: PayloadAction<{ id: string; imageDTO: ImageDTO | null; objectId: string }>) => {

View File

@ -2,6 +2,7 @@ import type { PayloadAction, SliceCaseReducers } from '@reduxjs/toolkit';
import { moveOneToEnd, moveOneToStart, moveToEnd, moveToStart } from 'common/util/arrayUtils';
import { getBrushLineId, getEraserLineId, getRectShapeId } from 'features/controlLayers/konva/naming';
import type { IRect } from 'konva/lib/types';
import type { ImageDTO } from 'services/api/types';
import { assert } from 'tsafe';
import { v4 as uuidv4 } from 'uuid';
@ -14,9 +15,9 @@ import type {
PointAddedToLineArg,
RectShapeAddedArg,
} from './types';
import { imageDTOToImageObject, isLine } from './types';
import { imageDTOToImageObject, imageDTOToImageWithDims, isLine } from './types';
export const selectLayer = (state: CanvasV2State, id: string) => state.layers.find((layer) => layer.id === id);
export const selectLayer = (state: CanvasV2State, id: string) => state.layers.entities.find((layer) => layer.id === id);
export const selectLayerOrThrow = (state: CanvasV2State, id: string) => {
const layer = selectLayer(state, id);
assert(layer, `Layer with id ${id} not found`);
@ -27,7 +28,7 @@ export const layersReducers = {
layerAdded: {
reducer: (state, action: PayloadAction<{ id: string }>) => {
const { id } = action.payload;
state.layers.push({
state.layers.entities.push({
id,
type: 'layer',
isEnabled: true,
@ -39,15 +40,15 @@ export const layersReducers = {
y: 0,
});
state.selectedEntityIdentifier = { type: 'layer', id };
state.baseLayerImageCache = null;
state.layers.baseLayerImageCache = null;
},
prepare: () => ({ payload: { id: uuidv4() } }),
},
layerRecalled: (state, action: PayloadAction<{ data: LayerEntity }>) => {
const { data } = action.payload;
state.layers.push(data);
state.layers.entities.push(data);
state.selectedEntityIdentifier = { type: 'layer', id: data.id };
state.baseLayerImageCache = null;
state.layers.baseLayerImageCache = null;
},
layerIsEnabledToggled: (state, action: PayloadAction<{ id: string }>) => {
const { id } = action.payload;
@ -56,7 +57,7 @@ export const layersReducers = {
return;
}
layer.isEnabled = !layer.isEnabled;
state.baseLayerImageCache = null;
state.layers.baseLayerImageCache = null;
},
layerTranslated: (state, action: PayloadAction<{ id: string; x: number; y: number }>) => {
const { id, x, y } = action.payload;
@ -66,7 +67,7 @@ export const layersReducers = {
}
layer.x = x;
layer.y = y;
state.baseLayerImageCache = null;
state.layers.baseLayerImageCache = null;
},
layerBboxChanged: (state, action: PayloadAction<{ id: string; bbox: IRect | null }>) => {
const { id, bbox } = action.payload;
@ -92,16 +93,16 @@ export const layersReducers = {
layer.objects = [];
layer.bbox = null;
layer.bboxNeedsUpdate = false;
state.baseLayerImageCache = null;
state.layers.baseLayerImageCache = null;
},
layerDeleted: (state, action: PayloadAction<{ id: string }>) => {
const { id } = action.payload;
state.layers = state.layers.filter((l) => l.id !== id);
state.baseLayerImageCache = null;
state.layers.entities = state.layers.entities.filter((l) => l.id !== id);
state.layers.baseLayerImageCache = null;
},
layerAllDeleted: (state) => {
state.layers = [];
state.baseLayerImageCache = null;
state.layers.entities = [];
state.layers.baseLayerImageCache = null;
},
layerOpacityChanged: (state, action: PayloadAction<{ id: string; opacity: number }>) => {
const { id, opacity } = action.payload;
@ -110,7 +111,7 @@ export const layersReducers = {
return;
}
layer.opacity = opacity;
state.baseLayerImageCache = null;
state.layers.baseLayerImageCache = null;
},
layerMovedForwardOne: (state, action: PayloadAction<{ id: string }>) => {
const { id } = action.payload;
@ -118,8 +119,8 @@ export const layersReducers = {
if (!layer) {
return;
}
moveOneToEnd(state.layers, layer);
state.baseLayerImageCache = null;
moveOneToEnd(state.layers.entities, layer);
state.layers.baseLayerImageCache = null;
},
layerMovedToFront: (state, action: PayloadAction<{ id: string }>) => {
const { id } = action.payload;
@ -127,8 +128,8 @@ export const layersReducers = {
if (!layer) {
return;
}
moveToEnd(state.layers, layer);
state.baseLayerImageCache = null;
moveToEnd(state.layers.entities, layer);
state.layers.baseLayerImageCache = null;
},
layerMovedBackwardOne: (state, action: PayloadAction<{ id: string }>) => {
const { id } = action.payload;
@ -136,8 +137,8 @@ export const layersReducers = {
if (!layer) {
return;
}
moveOneToStart(state.layers, layer);
state.baseLayerImageCache = null;
moveOneToStart(state.layers.entities, layer);
state.layers.baseLayerImageCache = null;
},
layerMovedToBack: (state, action: PayloadAction<{ id: string }>) => {
const { id } = action.payload;
@ -145,8 +146,8 @@ export const layersReducers = {
if (!layer) {
return;
}
moveToStart(state.layers, layer);
state.baseLayerImageCache = null;
moveToStart(state.layers.entities, layer);
state.layers.baseLayerImageCache = null;
},
layerBrushLineAdded: {
reducer: (state, action: PayloadAction<BrushLineAddedArg & { lineId: string }>) => {
@ -165,7 +166,7 @@ export const layersReducers = {
clip,
});
layer.bboxNeedsUpdate = true;
state.baseLayerImageCache = null;
state.layers.baseLayerImageCache = null;
},
prepare: (payload: BrushLineAddedArg) => ({
payload: { ...payload, lineId: uuidv4() },
@ -187,7 +188,7 @@ export const layersReducers = {
clip,
});
layer.bboxNeedsUpdate = true;
state.baseLayerImageCache = null;
state.layers.baseLayerImageCache = null;
},
prepare: (payload: EraserLineAddedArg) => ({
payload: { ...payload, lineId: uuidv4() },
@ -205,7 +206,7 @@ export const layersReducers = {
}
lastObject.points.push(...point);
layer.bboxNeedsUpdate = true;
state.baseLayerImageCache = null;
state.layers.baseLayerImageCache = null;
},
layerRectAdded: {
reducer: (state, action: PayloadAction<RectShapeAddedArg & { rectId: string }>) => {
@ -225,7 +226,7 @@ export const layersReducers = {
color,
});
layer.bboxNeedsUpdate = true;
state.baseLayerImageCache = null;
state.layers.baseLayerImageCache = null;
},
prepare: (payload: RectShapeAddedArg) => ({ payload: { ...payload, rectId: uuidv4() } }),
},
@ -238,8 +239,11 @@ export const layersReducers = {
}
layer.objects.push(imageDTOToImageObject(id, objectId, imageDTO));
layer.bboxNeedsUpdate = true;
state.baseLayerImageCache = null;
state.layers.baseLayerImageCache = null;
},
prepare: (payload: ImageObjectAddedArg) => ({ payload: { ...payload, objectId: uuidv4() } }),
},
baseLayerImageCacheChanged: (state, action: PayloadAction<ImageDTO | null>) => {
state.layers.baseLayerImageCache = action.payload ? imageDTOToImageWithDims(action.payload) : null;
},
} satisfies SliceCaseReducers<CanvasV2State>;

View File

@ -26,7 +26,7 @@ import type {
} from './types';
import { isLine } from './types';
export const selectRG = (state: CanvasV2State, id: string) => state.regions.find((rg) => rg.id === id);
export const selectRG = (state: CanvasV2State, id: string) => state.regions.entities.find((rg) => rg.id === id);
export const selectRGOrThrow = (state: CanvasV2State, id: string) => {
const rg = selectRG(state, id);
assert(rg, `Region with id ${id} not found`);
@ -44,7 +44,7 @@ const DEFAULT_MASK_COLORS: RgbColor[] = [
];
const getRGMaskFill = (state: CanvasV2State): RgbColor => {
const lastFill = state.regions.slice(-1)[0]?.fill;
const lastFill = state.regions.entities.slice(-1)[0]?.fill;
let i = DEFAULT_MASK_COLORS.findIndex((c) => isEqual(c, lastFill));
if (i === -1) {
i = 0;
@ -75,7 +75,7 @@ export const regionsReducers = {
ipAdapters: [],
imageCache: null,
};
state.regions.push(rg);
state.regions.entities.push(rg);
state.selectedEntityIdentifier = { type: 'regional_guidance', id };
},
prepare: () => ({ payload: { id: uuidv4() } }),
@ -93,7 +93,7 @@ export const regionsReducers = {
},
rgRecalled: (state, action: PayloadAction<{ data: RegionEntity }>) => {
const { data } = action.payload;
state.regions.push(data);
state.regions.entities.push(data);
state.selectedEntityIdentifier = { type: 'regional_guidance', id: data.id };
},
rgIsEnabledToggled: (state, action: PayloadAction<{ id: string }>) => {
@ -121,10 +121,10 @@ export const regionsReducers = {
},
rgDeleted: (state, action: PayloadAction<{ id: string }>) => {
const { id } = action.payload;
state.regions = state.regions.filter((ca) => ca.id !== id);
state.regions.entities = state.regions.entities.filter((ca) => ca.id !== id);
},
rgAllDeleted: (state) => {
state.regions = [];
state.regions.entities = [];
},
rgMovedForwardOne: (state, action: PayloadAction<{ id: string }>) => {
const { id } = action.payload;
@ -132,7 +132,7 @@ export const regionsReducers = {
if (!rg) {
return;
}
moveOneToEnd(state.regions, rg);
moveOneToEnd(state.regions.entities, rg);
},
rgMovedToFront: (state, action: PayloadAction<{ id: string }>) => {
const { id } = action.payload;
@ -140,7 +140,7 @@ export const regionsReducers = {
if (!rg) {
return;
}
moveToEnd(state.regions, rg);
moveToEnd(state.regions.entities, rg);
},
rgMovedBackwardOne: (state, action: PayloadAction<{ id: string }>) => {
const { id } = action.payload;
@ -148,7 +148,7 @@ export const regionsReducers = {
if (!rg) {
return;
}
moveOneToStart(state.regions, rg);
moveOneToStart(state.regions.entities, rg);
},
rgMovedToBack: (state, action: PayloadAction<{ id: string }>) => {
const { id } = action.payload;
@ -156,7 +156,7 @@ export const regionsReducers = {
if (!rg) {
return;
}
moveToStart(state.regions, rg);
moveToStart(state.regions.entities, rg);
},
rgPositivePromptChanged: (state, action: PayloadAction<{ id: string; prompt: string | null }>) => {
const { id, prompt } = action.payload;

View File

@ -4,7 +4,10 @@ import { getOptimalDimension } from 'features/parameters/util/optimalDimension';
export const selectEntityCount = createSelector(selectCanvasV2Slice, (canvasV2) => {
return (
canvasV2.regions.length + canvasV2.controlAdapters.length + canvasV2.ipAdapters.length + canvasV2.layers.length
canvasV2.regions.entities.length +
canvasV2.controlAdapters.entities.length +
canvasV2.ipAdapters.entities.length +
canvasV2.layers.entities.length
);
});

View File

@ -796,10 +796,13 @@ export type CanvasV2State = {
_version: 3;
selectedEntityIdentifier: CanvasEntityIdentifier | null;
inpaintMask: InpaintMaskEntity;
layers: LayerEntity[];
controlAdapters: ControlAdapterEntity[];
ipAdapters: IPAdapterEntity[];
regions: RegionEntity[];
layers: {
baseLayerImageCache: ImageWithDims | null;
entities: LayerEntity[];
};
controlAdapters: { entities: ControlAdapterEntity[] };
ipAdapters: { entities: IPAdapterEntity[] };
regions: { entities: RegionEntity[] };
loras: LoRA[];
tool: {
selected: Tool;
@ -872,7 +875,6 @@ export type CanvasV2State = {
refinerNegativeAestheticScore: number;
refinerStart: number;
};
baseLayerImageCache: ImageWithDims | null;
};
export type StageAttrs = { x: number; y: number; width: number; height: number; scale: number };

View File

@ -11,7 +11,7 @@ import { some } from 'lodash-es';
import type { ImageUsage } from './types';
export const getImageUsage = (nodes: NodesState, canvasV2: CanvasV2State, image_name: string) => {
const isLayerImage = canvasV2.layers.some((layer) =>
const isLayerImage = canvasV2.layers.entities.some((layer) =>
layer.objects.some((obj) => obj.type === 'image' && obj.image.name === image_name)
);
@ -21,11 +21,11 @@ export const getImageUsage = (nodes: NodesState, canvasV2: CanvasV2State, image_
some(node.data.inputs, (input) => isImageFieldInputInstance(input) && input.value?.image_name === image_name)
);
const isControlAdapterImage = canvasV2.controlAdapters.some(
(ca) => ca.image?.name === image_name || ca.processedImage?.name === image_name
const isControlAdapterImage = canvasV2.controlAdapters.entities.some(
(ca) => ca.imageObject?.image.name === image_name || ca.processedImageObject?.image.name === image_name
);
const isIPAdapterImage = canvasV2.ipAdapters.some((ipa) => ipa.imageObject?.name === image_name);
const isIPAdapterImage = canvasV2.ipAdapters.entities.some((ipa) => ipa.imageObject?.image.name === image_name);
const imageUsage: ImageUsage = {
isLayerImage,

View File

@ -78,13 +78,13 @@ const getBaseLayer = async (layers: LayerEntity[], bbox: IRect, preview: boolean
export const getBaseLayerImage = async (): Promise<ImageDTO> => {
const { dispatch, getState } = getStore();
const state = getState();
if (state.canvasV2.baseLayerImageCache) {
const imageDTO = await getImageDTO(state.canvasV2.baseLayerImageCache.name);
if (state.canvasV2.layers.baseLayerImageCache) {
const imageDTO = await getImageDTO(state.canvasV2.layers.baseLayerImageCache.name);
if (imageDTO) {
return imageDTO;
}
}
const blob = await getBaseLayer(state.canvasV2.layers, state.canvasV2.bbox, true);
const blob = await getBaseLayer(state.canvasV2.layers.entities, state.canvasV2.bbox, true);
const file = new File([blob], 'image.png', { type: 'image/png' });
const req = dispatch(
imagesApi.endpoints.uploadImage.initiate({ file, image_category: 'general', is_intermediate: true })

View File

@ -156,10 +156,10 @@ export const buildGenerationTabGraph = async (state: RootState): Promise<GraphTy
const vaeSource = seamless ?? vaeLoader ?? modelLoader;
g.addEdge(vaeSource, 'vae', l2i, 'vae');
const _addedCAs = addControlAdapters(state.canvasV2.controlAdapters, g, denoise, modelConfig.base);
const _addedIPAs = addIPAdapters(state.canvasV2.ipAdapters, g, denoise, modelConfig.base);
const _addedCAs = addControlAdapters(state.canvasV2.controlAdapters.entities, g, denoise, modelConfig.base);
const _addedIPAs = addIPAdapters(state.canvasV2.ipAdapters.entities, g, denoise, modelConfig.base);
const _addedRegions = await addRegions(
state.canvasV2.regions,
state.canvasV2.regions.entities,
g,
state.canvasV2.document,
state.canvasV2.bbox,

View File

@ -152,10 +152,10 @@ export const buildGenerationTabSDXLGraph = async (state: RootState): Promise<Non
await addSDXLRefiner(state, g, denoise, seamless, posCond, negCond, l2i);
}
const _addedCAs = addControlAdapters(state.canvasV2.controlAdapters, g, denoise, modelConfig.base);
const _addedIPAs = addIPAdapters(state.canvasV2.ipAdapters, g, denoise, modelConfig.base);
const _addedCAs = addControlAdapters(state.canvasV2.controlAdapters.entities, g, denoise, modelConfig.base);
const _addedIPAs = addIPAdapters(state.canvasV2.ipAdapters.entities, g, denoise, modelConfig.base);
const _addedRegions = await addRegions(
state.canvasV2.regions,
state.canvasV2.regions.entities,
g,
state.canvasV2.document,
state.canvasV2.bbox,

View File

@ -5,6 +5,7 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { overlayScrollbarsParams } from 'common/components/OverlayScrollbars/constants';
import { ControlLayersPanelContent } from 'features/controlLayers/components/ControlLayersPanelContent';
import { $isPreviewVisible } from 'features/controlLayers/store/canvasV2Slice';
import { selectEntityCount } from 'features/controlLayers/store/selectors';
import { isImageViewerOpenChanged } from 'features/gallery/store/gallerySlice';
import { Prompts } from 'features/parameters/components/Prompts/Prompts';
import QueueControls from 'features/queue/components/QueueControls';
@ -41,7 +42,7 @@ const selectedStyles: ChakraProps['sx'] = {
const ParametersPanelTextToImage = () => {
const { t } = useTranslation();
const dispatch = useAppDispatch();
const controlLayersCount = useAppSelector((s) => s.canvasV2.layers.length);
const controlLayersCount = useAppSelector(selectEntityCount);
const controlLayersTitle = useMemo(() => {
if (controlLayersCount === 0) {
return t('controlLayers.controlLayers');