Skip to content

Commit aa58b4c

Browse files
author
Attila Cseh
committed
canvasSettingsSlice refactored
1 parent 400badb commit aa58b4c

File tree

4 files changed

+131
-143
lines changed

4 files changed

+131
-143
lines changed

invokeai/frontend/web/src/app/store/store.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import { deepClone } from 'common/util/deepClone';
2121
import { merge } from 'es-toolkit';
2222
import { omit, pick } from 'es-toolkit/compat';
2323
import { changeBoardModalSliceConfig } from 'features/changeBoardModal/store/slice';
24-
import { canvasSettingsSliceConfig } from 'features/controlLayers/store/canvasSettingsSlice';
24+
import { canvasSettingsReducer, canvasSettingsSliceConfig } from 'features/controlLayers/store/canvasSettingsSlice';
2525
import { canvasSliceConfig, migrateCanvas, undoableCanvasesReducer } from 'features/controlLayers/store/canvasSlice';
2626
import { canvasSessionSliceConfig } from 'features/controlLayers/store/canvasStagingAreaSlice';
2727
import { lorasSliceConfig } from 'features/controlLayers/store/lorasSlice';
@@ -89,7 +89,7 @@ const SLICE_CONFIGS = {
8989
const ALL_REDUCERS = {
9090
[api.reducerPath]: api.reducer,
9191
[canvasSessionSliceConfig.slice.reducerPath]: canvasSessionSliceConfig.slice.reducer,
92-
[canvasSettingsSliceConfig.slice.reducerPath]: canvasSettingsSliceConfig.slice.reducer,
92+
[canvasSettingsSliceConfig.slice.reducerPath]: canvasSettingsReducer,
9393
[canvasSliceConfig.slice.reducerPath]: undoableCanvasesReducer,
9494
[changeBoardModalSliceConfig.slice.reducerPath]: changeBoardModalSliceConfig.slice.reducer,
9595
[configSliceConfig.slice.reducerPath]: configSliceConfig.slice.reducer,

invokeai/frontend/web/src/features/controlLayers/store/canvasSettingsSlice.ts

Lines changed: 118 additions & 130 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import type { PayloadAction, Selector } from '@reduxjs/toolkit';
2-
import { createSelector, createSlice } from '@reduxjs/toolkit';
1+
import type { PayloadAction, Selector, UnknownAction } from '@reduxjs/toolkit';
2+
import { createSelector, createSlice, isAnyOf } from '@reduxjs/toolkit';
33
import type { RootState } from 'app/store/store';
44
import type { SliceConfig } from 'app/store/types';
55
import { isPlainObject } from 'es-toolkit';
@@ -18,7 +18,7 @@ import {
1818
const zAutoSwitchMode = z.enum(['off', 'switch_on_start', 'switch_on_finish']);
1919
export type AutoSwitchMode = z.infer<typeof zAutoSwitchMode>;
2020

21-
const zCanvasSharedSettingsState = z.object({
21+
const zCanvasSharedSettings = z.object({
2222
/**
2323
* Whether to show HUD (Heads-Up Display) on the canvas.
2424
*/
@@ -89,9 +89,9 @@ const zCanvasSharedSettingsState = z.object({
8989
*/
9090
stagingAreaAutoSwitch: zAutoSwitchMode,
9191
});
92-
type CanvasSharedSettingsState = z.infer<typeof zCanvasSharedSettingsState>;
92+
type CanvasSharedSettings = z.infer<typeof zCanvasSharedSettings>;
9393

94-
const zCanvasInstanceSettingsState = z.object({
94+
const zCanvasInstanceSettings = z.object({
9595
canvasId: z.string(),
9696
/**
9797
* The width of the brush tool.
@@ -108,16 +108,16 @@ const zCanvasInstanceSettingsState = z.object({
108108
bgColor: zRgbaColor,
109109
fgColor: zRgbaColor,
110110
});
111-
type CanvasInstanceSettingsState = z.infer<typeof zCanvasInstanceSettingsState>;
111+
type CanvasInstanceSettings = z.infer<typeof zCanvasInstanceSettings>;
112112

113113
const zCanvasSettingsState = z.object({
114114
_version: z.literal(1),
115-
shared: zCanvasSharedSettingsState,
116-
canvases: z.array(zCanvasInstanceSettingsState),
115+
shared: zCanvasSharedSettings,
116+
canvases: z.record(z.string(), zCanvasInstanceSettings),
117117
});
118118
type CanvasSettingsState = z.infer<typeof zCanvasSettingsState>;
119119

120-
const getInitialCanvasSharedSettingsState = (): CanvasSharedSettingsState => ({
120+
const getInitialCanvasSharedSettings = (): CanvasSharedSettings => ({
121121
showHUD: true,
122122
clipToBbox: false,
123123
dynamicGrid: false,
@@ -135,26 +135,26 @@ const getInitialCanvasSharedSettingsState = (): CanvasSharedSettingsState => ({
135135
saveAllImagesToGallery: false,
136136
stagingAreaAutoSwitch: 'switch_on_start',
137137
});
138-
const getInitialCanvasInstanceSettingsState = (canvasId: string): CanvasInstanceSettingsState => ({
138+
const getInitialCanvasInstanceSettings = (canvasId: string): CanvasInstanceSettings => ({
139139
canvasId,
140140
brushWidth: 50,
141141
eraserWidth: 50,
142142
activeColor: 'fgColor',
143143
bgColor: RGBA_BLACK,
144144
fgColor: RGBA_WHITE,
145145
});
146-
const getInitialState = (): CanvasSettingsState => ({
146+
const getInitialCanvasSettingsState = (): CanvasSettingsState => ({
147147
_version: 1,
148-
shared: getInitialCanvasSharedSettingsState(),
149-
canvases: [],
148+
shared: getInitialCanvasSharedSettings(),
149+
canvases: {},
150150
});
151151

152-
type CanvasPayload<T> = { canvasId: string } & T;
153-
type CanvasPayloadAction<T> = PayloadAction<CanvasPayload<T>>;
152+
type PayloadWithCanvasId<P> = P & { canvasId: string };
153+
type CanvasPayloadAction<P> = PayloadAction<PayloadWithCanvasId<P>>;
154154

155-
const slice = createSlice({
155+
const canvasSettingsSlice = createSlice({
156156
name: 'canvasSettings',
157-
initialState: getInitialState(),
157+
initialState: getInitialCanvasSettingsState(),
158158
reducers: {
159159
settingsClipToBboxChanged: (state, action: PayloadAction<{ clipToBbox: boolean }>) => {
160160
const { clipToBbox } = action.payload;
@@ -167,67 +167,6 @@ const slice = createSlice({
167167
settingsShowHUDToggled: (state) => {
168168
state.shared.showHUD = !state.shared.showHUD;
169169
},
170-
settingsBrushWidthChanged: (state, action: CanvasPayloadAction<{ brushWidth: number }>) => {
171-
const { canvasId, brushWidth } = action.payload;
172-
173-
const settings = state.canvases.find((settings) => settings.canvasId === canvasId);
174-
if (!settings) {
175-
return;
176-
}
177-
178-
settings.brushWidth = Math.round(brushWidth);
179-
},
180-
settingsEraserWidthChanged: (state, action: CanvasPayloadAction<{ eraserWidth: number }>) => {
181-
const { canvasId, eraserWidth } = action.payload;
182-
183-
const settings = state.canvases.find((settings) => settings.canvasId === canvasId);
184-
if (!settings) {
185-
return;
186-
}
187-
188-
settings.eraserWidth = Math.round(eraserWidth);
189-
},
190-
settingsActiveColorToggled: (state, action: CanvasPayloadAction<unknown>) => {
191-
const { canvasId } = action.payload;
192-
193-
const settings = state.canvases.find((settings) => settings.canvasId === canvasId);
194-
if (!settings) {
195-
return;
196-
}
197-
198-
settings.activeColor = settings.activeColor === 'bgColor' ? 'fgColor' : 'bgColor';
199-
},
200-
settingsBgColorChanged: (state, action: CanvasPayloadAction<{ bgColor: Partial<RgbaColor> }>) => {
201-
const { canvasId, bgColor } = action.payload;
202-
203-
const settings = state.canvases.find((settings) => settings.canvasId === canvasId);
204-
if (!settings) {
205-
return;
206-
}
207-
208-
settings.bgColor = { ...settings.bgColor, ...bgColor };
209-
},
210-
settingsFgColorChanged: (state, action: CanvasPayloadAction<{ fgColor: Partial<RgbaColor> }>) => {
211-
const { canvasId, fgColor } = action.payload;
212-
213-
const settings = state.canvases.find((settings) => settings.canvasId === canvasId);
214-
if (!settings) {
215-
return;
216-
}
217-
218-
settings.fgColor = { ...settings.fgColor, ...fgColor };
219-
},
220-
settingsColorsSetToDefault: (state, action: CanvasPayloadAction<unknown>) => {
221-
const { canvasId } = action.payload;
222-
223-
const settings = state.canvases.find((settings) => settings.canvasId === canvasId);
224-
if (!settings) {
225-
return;
226-
}
227-
228-
settings.bgColor = RGBA_BLACK;
229-
settings.fgColor = RGBA_WHITE;
230-
},
231170
settingsInvertScrollForToolWidthChanged: (state, action: PayloadAction<{ invertScrollForToolWidth: boolean }>) => {
232171
const { invertScrollForToolWidth } = action.payload;
233172

@@ -268,7 +207,7 @@ const slice = createSlice({
268207
},
269208
settingsStagingAreaAutoSwitchChanged: (
270209
state,
271-
action: PayloadAction<{ stagingAreaAutoSwitch: CanvasSharedSettingsState['stagingAreaAutoSwitch'] }>
210+
action: PayloadAction<{ stagingAreaAutoSwitch: CanvasSharedSettings['stagingAreaAutoSwitch'] }>
272211
) => {
273212
const { stagingAreaAutoSwitch } = action.payload;
274213

@@ -277,32 +216,62 @@ const slice = createSlice({
277216
},
278217
extraReducers(builder) {
279218
builder.addCase(canvasCreated, (state, action) => {
280-
const canvasSettings = getInitialCanvasInstanceSettingsState(action.payload.canvasId);
281-
state.canvases.push(canvasSettings);
219+
const canvasSettings = getInitialCanvasInstanceSettings(action.payload.canvasId);
220+
state.canvases[canvasSettings.canvasId] = canvasSettings;
282221
});
283222
builder.addCase(canvasRemoved, (state, action) => {
284-
state.canvases = state.canvases.filter((settings) => settings.canvasId !== action.payload.canvasId);
223+
delete state.canvases[action.payload.canvasId];
285224
});
286225
builder.addCase(canvasMultiCanvasMigrated, (state, action) => {
287-
const settings = state.canvases.find((settings) => settings.canvasId === MIGRATION_MULTI_CANVAS_ID_PLACEHOLDER);
226+
const settings = state.canvases[MIGRATION_MULTI_CANVAS_ID_PLACEHOLDER];
288227
if (!settings) {
289228
return;
290229
}
291230
settings.canvasId = action.payload.canvasId;
231+
state.canvases[settings.canvasId] = settings;
232+
delete state.canvases[MIGRATION_MULTI_CANVAS_ID_PLACEHOLDER];
292233
});
293234
},
294235
});
295236

237+
const canvasInstanceSettingsSlice = createSlice({
238+
name: 'canvasSettings',
239+
initialState: {} as CanvasInstanceSettings,
240+
reducers: {
241+
settingsBrushWidthChanged: (state, action: CanvasPayloadAction<{ brushWidth: number }>) => {
242+
const { brushWidth } = action.payload;
243+
244+
state.brushWidth = Math.round(brushWidth);
245+
},
246+
settingsEraserWidthChanged: (state, action: CanvasPayloadAction<{ eraserWidth: number }>) => {
247+
const { eraserWidth } = action.payload;
248+
249+
state.eraserWidth = Math.round(eraserWidth);
250+
},
251+
settingsActiveColorToggled: (state, _action: CanvasPayloadAction<unknown>) => {
252+
state.activeColor = state.activeColor === 'bgColor' ? 'fgColor' : 'bgColor';
253+
},
254+
settingsBgColorChanged: (state, action: CanvasPayloadAction<{ bgColor: Partial<RgbaColor> }>) => {
255+
const { bgColor } = action.payload;
256+
257+
state.bgColor = { ...state.bgColor, ...bgColor };
258+
},
259+
settingsFgColorChanged: (state, action: CanvasPayloadAction<{ fgColor: Partial<RgbaColor> }>) => {
260+
const { fgColor } = action.payload;
261+
262+
state.fgColor = { ...state.fgColor, ...fgColor };
263+
},
264+
settingsColorsSetToDefault: (state, _action: CanvasPayloadAction<unknown>) => {
265+
state.bgColor = RGBA_BLACK;
266+
state.fgColor = RGBA_WHITE;
267+
},
268+
},
269+
});
270+
296271
export const {
297272
settingsClipToBboxChanged,
298273
settingsDynamicGridToggled,
299274
settingsShowHUDToggled,
300-
settingsBrushWidthChanged,
301-
settingsEraserWidthChanged,
302-
settingsActiveColorToggled,
303-
settingsBgColorChanged,
304-
settingsFgColorChanged,
305-
settingsColorsSetToDefault,
306275
settingsInvertScrollForToolWidthChanged,
307276
settingsOutputOnlyMaskedRegionsToggled,
308277
settingsAutoProcessToggled,
@@ -316,28 +285,57 @@ export const {
316285
settingsRuleOfThirdsToggled,
317286
settingsSaveAllImagesToGalleryToggled,
318287
settingsStagingAreaAutoSwitchChanged,
319-
} = slice.actions;
288+
} = canvasSettingsSlice.actions;
289+
290+
export const {
291+
settingsBrushWidthChanged,
292+
settingsEraserWidthChanged,
293+
settingsActiveColorToggled,
294+
settingsBgColorChanged,
295+
settingsFgColorChanged,
296+
settingsColorsSetToDefault,
297+
} = canvasInstanceSettingsSlice.actions;
298+
299+
const isCanvasInstanceSettingsAction = isAnyOf(...Object.values(canvasInstanceSettingsSlice.actions));
300+
301+
export const canvasSettingsReducer = (state: CanvasSettingsState, action: UnknownAction): CanvasSettingsState => {
302+
state = canvasSettingsSlice.reducer(state, action);
320303

321-
export const canvasSettingsSliceConfig: SliceConfig<typeof slice> = {
322-
slice,
304+
if (!isCanvasInstanceSettingsAction(action)) {
305+
return state;
306+
}
307+
308+
const canvasId = action.payload.canvasId;
309+
310+
return {
311+
...state,
312+
canvases: {
313+
...state.canvases,
314+
[canvasId]: canvasInstanceSettingsSlice.reducer(state.canvases[canvasId], action),
315+
},
316+
};
317+
};
318+
319+
export const canvasSettingsSliceConfig: SliceConfig<typeof canvasSettingsSlice> = {
320+
slice: canvasSettingsSlice,
323321
schema: zCanvasSettingsState,
324-
getInitialState,
322+
getInitialState: getInitialCanvasSettingsState,
325323
persistConfig: {
326324
migrate: (state) => {
327325
assert(isPlainObject(state));
328326
if (!('_version' in state)) {
329327
// Migrate from v1: slice represented a canvas settings instance -> slice represents multiple canvas settings instances
330-
const canvas = {
328+
const settings = {
331329
canvasId: MIGRATION_MULTI_CANVAS_ID_PLACEHOLDER,
332330
...state,
333-
} as CanvasInstanceSettingsState;
331+
} as CanvasInstanceSettings;
334332

335333
state = {
336334
_version: 1,
337335
shared: {
338336
...state,
339337
},
340-
canvases: [canvas],
338+
canvases: { [settings.canvasId]: settings },
341339
};
342340
}
343341

@@ -359,52 +357,42 @@ export const buildSelectCanvasSettingsByCanvasId = (canvasId: string) =>
359357
);
360358
const selectCanvasSharedSettings = (state: RootState) => state.canvasSettings.shared;
361359
const selectCanvasInstanceSettings = (state: RootState, canvasId: string) => {
362-
const settings = state.canvasSettings.canvases.find((settings) => settings.canvasId === canvasId);
360+
const settings = state.canvasSettings.canvases[canvasId];
363361
assert(settings, 'Settings must exist for a canvas once the canvas has been created');
364362
return settings;
365363
};
366364

367365
const buildCanvasSharedSettingsSelector =
368-
<T>(selector: Selector<CanvasSharedSettingsState, T>) =>
366+
<T>(selector: Selector<CanvasSharedSettings, T>) =>
369367
(state: RootState) =>
370368
selector(selectCanvasSharedSettings(state));
371369
const buildCanvasInstanceSettingsSelector =
372-
<T>(selector: Selector<CanvasInstanceSettingsState, T>) =>
370+
<T>(selector: Selector<CanvasInstanceSettings, T>) =>
373371
(state: RootState, canvasId: string) =>
374372
selector(selectCanvasInstanceSettings(state, canvasId));
375373

376-
export const selectPreserveMask = buildCanvasSharedSettingsSelector((settings) => settings.preserveMask);
374+
export const selectPreserveMask = buildCanvasSharedSettingsSelector((state) => state.preserveMask);
377375
export const selectOutputOnlyMaskedRegions = buildCanvasSharedSettingsSelector(
378-
(settings) => settings.outputOnlyMaskedRegions
376+
(state) => state.outputOnlyMaskedRegions
379377
);
380-
export const selectDynamicGrid = buildCanvasSharedSettingsSelector((settings) => settings.dynamicGrid);
378+
export const selectDynamicGrid = buildCanvasSharedSettingsSelector((state) => state.dynamicGrid);
381379
export const selectInvertScrollForToolWidth = buildCanvasSharedSettingsSelector(
382-
(settings) => settings.invertScrollForToolWidth
383-
);
384-
export const selectBboxOverlay = buildCanvasSharedSettingsSelector((settings) => settings.bboxOverlay);
385-
export const selectShowHUD = buildCanvasSharedSettingsSelector((settings) => settings.showHUD);
386-
export const selectClipToBbox = buildCanvasSharedSettingsSelector((settings) => settings.clipToBbox);
387-
export const selectAutoProcess = buildCanvasSharedSettingsSelector((settings) => settings.autoProcess);
388-
export const selectSnapToGrid = buildCanvasSharedSettingsSelector((settings) => settings.snapToGrid);
389-
export const selectShowProgressOnCanvas = buildCanvasSharedSettingsSelector(
390-
(settings) => settings.showProgressOnCanvas
391-
);
392-
export const selectIsolatedStagingPreview = buildCanvasSharedSettingsSelector(
393-
(settings) => settings.isolatedStagingPreview
394-
);
395-
export const selectIsolatedLayerPreview = buildCanvasSharedSettingsSelector(
396-
(settings) => settings.isolatedLayerPreview
397-
);
398-
export const selectPressureSensitivity = buildCanvasSharedSettingsSelector((settings) => settings.pressureSensitivity);
399-
export const selectRuleOfThirds = buildCanvasSharedSettingsSelector((settings) => settings.ruleOfThirds);
400-
export const selectSaveAllImagesToGallery = buildCanvasSharedSettingsSelector(
401-
(settings) => settings.saveAllImagesToGallery
402-
);
403-
export const selectStagingAreaAutoSwitch = buildCanvasSharedSettingsSelector(
404-
(settings) => settings.stagingAreaAutoSwitch
380+
(state) => state.invertScrollForToolWidth
405381
);
406-
export const selectActiveColor = buildCanvasInstanceSettingsSelector((settings) => settings.activeColor);
407-
export const selectBgColor = buildCanvasInstanceSettingsSelector((settings) => settings.bgColor);
408-
export const selectFgColor = buildCanvasInstanceSettingsSelector((settings) => settings.fgColor);
409-
export const selectBrushWidth = buildCanvasInstanceSettingsSelector((settings) => settings.brushWidth);
410-
export const selectEraserWidth = buildCanvasInstanceSettingsSelector((settings) => settings.eraserWidth);
382+
export const selectBboxOverlay = buildCanvasSharedSettingsSelector((state) => state.bboxOverlay);
383+
export const selectShowHUD = buildCanvasSharedSettingsSelector((state) => state.showHUD);
384+
export const selectClipToBbox = buildCanvasSharedSettingsSelector((state) => state.clipToBbox);
385+
export const selectAutoProcess = buildCanvasSharedSettingsSelector((state) => state.autoProcess);
386+
export const selectSnapToGrid = buildCanvasSharedSettingsSelector((state) => state.snapToGrid);
387+
export const selectShowProgressOnCanvas = buildCanvasSharedSettingsSelector((state) => state.showProgressOnCanvas);
388+
export const selectIsolatedStagingPreview = buildCanvasSharedSettingsSelector((state) => state.isolatedStagingPreview);
389+
export const selectIsolatedLayerPreview = buildCanvasSharedSettingsSelector((state) => state.isolatedLayerPreview);
390+
export const selectPressureSensitivity = buildCanvasSharedSettingsSelector((state) => state.pressureSensitivity);
391+
export const selectRuleOfThirds = buildCanvasSharedSettingsSelector((state) => state.ruleOfThirds);
392+
export const selectSaveAllImagesToGallery = buildCanvasSharedSettingsSelector((state) => state.saveAllImagesToGallery);
393+
export const selectStagingAreaAutoSwitch = buildCanvasSharedSettingsSelector((state) => state.stagingAreaAutoSwitch);
394+
export const selectActiveColor = buildCanvasInstanceSettingsSelector((state) => state.activeColor);
395+
export const selectBgColor = buildCanvasInstanceSettingsSelector((state) => state.bgColor);
396+
export const selectFgColor = buildCanvasInstanceSettingsSelector((state) => state.fgColor);
397+
export const selectBrushWidth = buildCanvasInstanceSettingsSelector((state) => state.brushWidth);
398+
export const selectEraserWidth = buildCanvasInstanceSettingsSelector((state) => state.eraserWidth);

0 commit comments

Comments
 (0)