From 1d1e4d02dc547975316a87f28bd6a2911aa61b52 Mon Sep 17 00:00:00 2001
From: psychedelicious <4822129+psychedelicious@users.noreply.github.com>
Date: Fri, 5 Apr 2024 17:03:29 +1100
Subject: [PATCH] feat(ui): rough out regional prompts store

---
 invokeai/frontend/web/src/app/store/store.ts  |   6 +
 .../store/regionalPromptsSlice.ts             | 126 ++++++++++++++++++
 2 files changed, 132 insertions(+)
 create mode 100644 invokeai/frontend/web/src/features/regionalPrompts/store/regionalPromptsSlice.ts

diff --git a/invokeai/frontend/web/src/app/store/store.ts b/invokeai/frontend/web/src/app/store/store.ts
index b538a3eaeb..4626f4e36b 100644
--- a/invokeai/frontend/web/src/app/store/store.ts
+++ b/invokeai/frontend/web/src/app/store/store.ts
@@ -21,6 +21,10 @@ import { workflowPersistConfig, workflowSlice } from 'features/nodes/store/workf
 import { generationPersistConfig, generationSlice } from 'features/parameters/store/generationSlice';
 import { postprocessingPersistConfig, postprocessingSlice } from 'features/parameters/store/postprocessingSlice';
 import { queueSlice } from 'features/queue/store/queueSlice';
+import {
+  regionalPromptsPersistConfig,
+  regionalPromptsSlice,
+} from 'features/regionalPrompts/store/regionalPromptsSlice';
 import { sdxlPersistConfig, sdxlSlice } from 'features/sdxl/store/sdxlSlice';
 import { configSlice } from 'features/system/store/configSlice';
 import { systemPersistConfig, systemSlice } from 'features/system/store/systemSlice';
@@ -59,6 +63,7 @@ const allReducers = {
   [queueSlice.name]: queueSlice.reducer,
   [workflowSlice.name]: workflowSlice.reducer,
   [hrfSlice.name]: hrfSlice.reducer,
+  [regionalPromptsSlice.name]: regionalPromptsSlice.reducer,
   [api.reducerPath]: api.reducer,
 };
 
@@ -103,6 +108,7 @@ const persistConfigs: { [key in keyof typeof allReducers]?: PersistConfig } = {
   [loraPersistConfig.name]: loraPersistConfig,
   [modelManagerV2PersistConfig.name]: modelManagerV2PersistConfig,
   [hrfPersistConfig.name]: hrfPersistConfig,
+  [regionalPromptsPersistConfig.name]: regionalPromptsPersistConfig,
 };
 
 const unserialize: UnserializeFunction = (data, key) => {
diff --git a/invokeai/frontend/web/src/features/regionalPrompts/store/regionalPromptsSlice.ts b/invokeai/frontend/web/src/features/regionalPrompts/store/regionalPromptsSlice.ts
new file mode 100644
index 0000000000..6a248ea527
--- /dev/null
+++ b/invokeai/frontend/web/src/features/regionalPrompts/store/regionalPromptsSlice.ts
@@ -0,0 +1,126 @@
+import type { EntityState } from '@reduxjs/toolkit';
+import { createEntityAdapter, createSlice } from '@reduxjs/toolkit';
+import { getSelectorsOptions } from 'app/store/createMemoizedSelector';
+import type { PersistConfig, RootState } from 'app/store/store';
+import type { RgbaColor } from 'react-colorful';
+import { v4 as uuidv4 } from 'uuid';
+
+type LayerObjectBase = {
+  id: string;
+  isSelected: boolean;
+};
+
+export type ImageObject = LayerObjectBase & {
+  kind: 'image';
+  imageName: string;
+  x: number;
+  y: number;
+  width: number;
+  height: number;
+};
+
+export type LineObject = LayerObjectBase & {
+  kind: 'line';
+  strokeWidth: number;
+  points: number[];
+  color: RgbaColor;
+};
+
+export type FillRectObject = LayerObjectBase & {
+  kind: 'fillRect';
+  x: number;
+  y: number;
+  width: number;
+  height: number;
+  color: RgbaColor;
+};
+
+export type LayerObject = ImageObject | LineObject | FillRectObject;
+
+export type PromptRegionLayer = {
+  id: string;
+  objects: EntityState<LayerObject, string>;
+  prompt: string;
+};
+
+export const layersAdapter = createEntityAdapter<PromptRegionLayer, string>({
+  selectId: (layer) => layer.id,
+});
+export const layersSelectors = layersAdapter.getSelectors(undefined, getSelectorsOptions);
+
+export const layerObjectsAdapter = createEntityAdapter<LayerObject, string>({
+  selectId: (obj) => obj.id,
+});
+export const layerObjectsSelectors = layerObjectsAdapter.getSelectors(undefined, getSelectorsOptions);
+
+const getMockState = () => {
+  // Mock data
+  const layer1ID = uuidv4();
+  const obj1ID = uuidv4();
+  const obj2ID = uuidv4();
+
+  const objectEntities: Record<string, LayerObject> = {
+    [obj1ID]: {
+      id: obj1ID,
+      kind: 'line',
+      isSelected: false,
+      color: { r: 255, g: 0, b: 0, a: 1 },
+      strokeWidth: 5,
+      points: [20, 20, 100, 100],
+    },
+    [obj2ID]: {
+      id: obj2ID,
+      kind: 'fillRect',
+      isSelected: false,
+      color: { r: 0, g: 255, b: 0, a: 1 },
+      x: 150,
+      y: 150,
+      width: 100,
+      height: 100,
+    },
+  };
+  const objectsInitialState = layerObjectsAdapter.getInitialState(undefined, objectEntities);
+  const entities: Record<string, PromptRegionLayer> = {
+    [layer1ID]: {
+      id: layer1ID,
+      prompt: 'strawberries',
+      objects: objectsInitialState,
+    },
+  };
+
+  return entities;
+};
+
+export const initialRegionalPromptsState = layersAdapter.getInitialState(
+  { _version: 1, selectedID: null },
+  getMockState()
+);
+
+export type RegionalPromptsState = typeof initialRegionalPromptsState;
+
+export const regionalPromptsSlice = createSlice({
+  name: 'regionalPrompts',
+  initialState: initialRegionalPromptsState,
+  reducers: {
+    layerAdded: layersAdapter.addOne,
+    layerRemoved: layersAdapter.removeOne,
+    layerUpdated: layersAdapter.updateOne,
+    layersReset: layersAdapter.removeAll,
+  },
+});
+
+export const { layerAdded, layerRemoved, layerUpdated, layersReset } = regionalPromptsSlice.actions;
+
+export const selectRegionalPromptsSlice = (state: RootState) => state.regionalPrompts;
+
+/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
+const migrateRegionalPromptsState = (state: any): any => {
+  return state;
+};
+
+export const regionalPromptsPersistConfig: PersistConfig<RegionalPromptsState> = {
+  name: regionalPromptsSlice.name,
+  initialState: initialRegionalPromptsState,
+  migrate: migrateRegionalPromptsState,
+  persistDenylist: [],
+};