Compare commits

..

19 Commits

Author SHA1 Message Date
psychedelicious
434afdd21d chore(ui): lint 2024-08-26 22:57:56 +10:00
psychedelicious
ea267c0e09 chore: release v4.2.9.dev4
Canvas dev build.
2024-08-26 22:46:07 +10:00
psychedelicious
35b483b83a feat(ui): rough out undo/redo on canvas UNDO ME? 2024-08-26 22:45:11 +10:00
psychedelicious
217b2759c3 fix(ui): handle error from internal konva method
We are dipping into konva's private API for preview images and it appears to be unsafe (got an error once). Wrapped in a try/catch.
2024-08-26 22:44:47 +10:00
psychedelicious
7d528dc03d feat(ui): split out loras state from canvas rendering state 2024-08-26 22:29:28 +10:00
psychedelicious
bc40c1a99f feat(ui): split out session state from canvas rendering state 2024-08-26 22:20:04 +10:00
psychedelicious
a2b508016e feat(ui): split out settings state from canvas rendering state 2024-08-26 22:02:56 +10:00
psychedelicious
3ce8294379 feat(ui): split out tool state from canvas rendering state 2024-08-26 21:52:43 +10:00
psychedelicious
9c3da8de8e feat(ui): split out params/compositing state from canvas rendering state
First step to restoring undo/redo - the undoable state must be in its own slice. So params and settings must be isolated.
2024-08-26 21:41:47 +10:00
psychedelicious
ccba597e58 feat(ui): add CanvasModuleBase class to standardize canvas APIs
I did this ages ago but undid it for some reason, not sure why. Caught a few issues related to subscriptions.
2024-08-26 21:12:31 +10:00
psychedelicious
8028943cdd feat(ui): move selected tool and tool buffer out of redux
This ephemeral state can live in the canvas classes.
2024-08-26 19:59:06 +10:00
psychedelicious
cdd8b60fd0 feat(ui): move ephemeral state into canvas classes
Things like `$lastCursorPos` are now created within the canvas drawing classes. Consumers in react access them via `useCanvasManager`.

For example:
```tsx
const canvasManager = useCanvasManager();
const lastCursorPos = useStore(canvasManager.stateApi.$lastCursorPos);
```
2024-08-26 19:14:56 +10:00
psychedelicious
c624754404 feat(ui): normalize all actions to accept an entityIdentifier
Previously, canvas actions specific to an entity type only needed the id of that entity type. This allowed you to pass in the id of an entity of the wrong type.

All actions for a specific entity now take a full entity identifier, and the entity identifier type can be narrowed.

`selectEntity` and `selectEntityOrThrow` now need a full entity identifier, and narrow their return values to a specific entity type _if_ the entity identifier is narrowed.

The types for canvas entities are updated with optional type parameters for this purpose.

All reducers, actions and components have been updated.
2024-08-26 18:52:28 +10:00
psychedelicious
0505634296 feat(ui): move events into modules who care about them 2024-08-26 10:34:59 +10:00
psychedelicious
e06ea5d595 fix(ui): color picker resets brush opacity 2024-08-26 08:58:16 +10:00
psychedelicious
838a0574a5 fix(ui): scaled bbox loses sync 2024-08-26 08:55:33 +10:00
psychedelicious
ed3581c70c feat(ui): add context menu to entity list 2024-08-24 19:50:36 +10:00
psychedelicious
522cae6a42 chore(ui): bump @invoke-ai/ui-library 2024-08-24 19:49:43 +10:00
psychedelicious
a9032a34f2 fix(ui): missing vae precision in graph builders 2024-08-24 18:35:30 +10:00
214 changed files with 3187 additions and 2573 deletions

View File

@@ -1,5 +1,5 @@
import { PropsWithChildren, memo, useEffect } from 'react'; import { PropsWithChildren, memo, useEffect } from 'react';
import { modelChanged } from '../src/features/controlLayers/store/canvasV2Slice'; import { modelChanged } from '../src/features/controlLayers/store/paramsSlice';
import { useAppDispatch } from '../src/app/store/storeHooks'; import { useAppDispatch } from '../src/app/store/storeHooks';
import { useGlobalModifiersInit } from '@invoke-ai/ui-library'; import { useGlobalModifiersInit } from '@invoke-ai/ui-library';
/** /**

View File

@@ -58,7 +58,7 @@
"@dnd-kit/sortable": "^8.0.0", "@dnd-kit/sortable": "^8.0.0",
"@dnd-kit/utilities": "^3.2.2", "@dnd-kit/utilities": "^3.2.2",
"@fontsource-variable/inter": "^5.0.20", "@fontsource-variable/inter": "^5.0.20",
"@invoke-ai/ui-library": "^0.0.31", "@invoke-ai/ui-library": "^0.0.32",
"@nanostores/react": "^0.7.3", "@nanostores/react": "^0.7.3",
"@reduxjs/toolkit": "2.2.3", "@reduxjs/toolkit": "2.2.3",
"@roarr/browser-log-writer": "^1.3.0", "@roarr/browser-log-writer": "^1.3.0",

View File

@@ -24,8 +24,8 @@ dependencies:
specifier: ^5.0.20 specifier: ^5.0.20
version: 5.0.20 version: 5.0.20
'@invoke-ai/ui-library': '@invoke-ai/ui-library':
specifier: ^0.0.31 specifier: ^0.0.32
version: 0.0.31(@chakra-ui/form-control@2.2.0)(@chakra-ui/icon@3.2.0)(@chakra-ui/media-query@3.3.0)(@chakra-ui/menu@2.2.1)(@chakra-ui/spinner@2.1.0)(@chakra-ui/system@2.6.2)(@fontsource-variable/inter@5.0.20)(@types/react@18.3.3)(i18next@23.12.2)(react-dom@18.3.1)(react@18.3.1) version: 0.0.32(@chakra-ui/form-control@2.2.0)(@chakra-ui/icon@3.2.0)(@chakra-ui/media-query@3.3.0)(@chakra-ui/menu@2.2.1)(@chakra-ui/spinner@2.1.0)(@chakra-ui/system@2.6.2)(@fontsource-variable/inter@5.0.20)(@types/react@18.3.3)(i18next@23.12.2)(react-dom@18.3.1)(react@18.3.1)
'@nanostores/react': '@nanostores/react':
specifier: ^0.7.3 specifier: ^0.7.3
version: 0.7.3(nanostores@0.11.2)(react@18.3.1) version: 0.7.3(nanostores@0.11.2)(react@18.3.1)
@@ -40,7 +40,7 @@ dependencies:
version: 0.5.0 version: 0.5.0
chakra-react-select: chakra-react-select:
specifier: ^4.9.1 specifier: ^4.9.1
version: 4.9.1(@chakra-ui/form-control@2.2.0)(@chakra-ui/icon@3.2.0)(@chakra-ui/layout@2.3.1)(@chakra-ui/media-query@3.3.0)(@chakra-ui/menu@2.2.1)(@chakra-ui/spinner@2.1.0)(@chakra-ui/system@2.6.2)(@emotion/react@11.13.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1) version: 4.9.1(@chakra-ui/form-control@2.2.0)(@chakra-ui/icon@3.2.0)(@chakra-ui/layout@2.3.1)(@chakra-ui/media-query@3.3.0)(@chakra-ui/menu@2.2.1)(@chakra-ui/spinner@2.1.0)(@chakra-ui/system@2.6.2)(@emotion/react@11.13.3)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1)
compare-versions: compare-versions:
specifier: ^6.1.1 specifier: ^6.1.1
version: 6.1.1 version: 6.1.1
@@ -1752,6 +1752,13 @@ packages:
dependencies: dependencies:
regenerator-runtime: 0.14.1 regenerator-runtime: 0.14.1
/@babel/runtime@7.25.4:
resolution: {integrity: sha512-DSgLeL/FNcpXuzav5wfYvHCGvynXkJbn3Zvc3823AEe9nPwW9IK4UoCSS5yGymmQzN0pCPvivtgS6/8U2kkm1w==}
engines: {node: '>=6.9.0'}
dependencies:
regenerator-runtime: 0.14.1
dev: false
/@babel/template@7.24.0: /@babel/template@7.24.0:
resolution: {integrity: sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==} resolution: {integrity: sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==}
engines: {node: '>=6.9.0'} engines: {node: '>=6.9.0'}
@@ -1838,7 +1845,7 @@ packages:
'@chakra-ui/react-use-controllable-state': 2.1.0(react@18.3.1) '@chakra-ui/react-use-controllable-state': 2.1.0(react@18.3.1)
'@chakra-ui/react-use-merge-refs': 2.1.0(react@18.3.1) '@chakra-ui/react-use-merge-refs': 2.1.0(react@18.3.1)
'@chakra-ui/shared-utils': 2.0.5 '@chakra-ui/shared-utils': 2.0.5
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1) '@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
'@chakra-ui/transition': 2.1.0(framer-motion@10.18.0)(react@18.3.1) '@chakra-ui/transition': 2.1.0(framer-motion@10.18.0)(react@18.3.1)
framer-motion: 10.18.0(react-dom@18.3.1)(react@18.3.1) framer-motion: 10.18.0(react-dom@18.3.1)(react@18.3.1)
react: 18.3.1 react: 18.3.1
@@ -1854,7 +1861,7 @@ packages:
'@chakra-ui/react-context': 2.1.0(react@18.3.1) '@chakra-ui/react-context': 2.1.0(react@18.3.1)
'@chakra-ui/shared-utils': 2.0.5 '@chakra-ui/shared-utils': 2.0.5
'@chakra-ui/spinner': 2.1.0(@chakra-ui/system@2.6.2)(react@18.3.1) '@chakra-ui/spinner': 2.1.0(@chakra-ui/system@2.6.2)(react@18.3.1)
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1) '@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
react: 18.3.1 react: 18.3.1
dev: false dev: false
@@ -1872,7 +1879,7 @@ packages:
'@chakra-ui/react-children-utils': 2.0.6(react@18.3.1) '@chakra-ui/react-children-utils': 2.0.6(react@18.3.1)
'@chakra-ui/react-context': 2.1.0(react@18.3.1) '@chakra-ui/react-context': 2.1.0(react@18.3.1)
'@chakra-ui/shared-utils': 2.0.5 '@chakra-ui/shared-utils': 2.0.5
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1) '@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
react: 18.3.1 react: 18.3.1
dev: false dev: false
@@ -1885,7 +1892,7 @@ packages:
'@chakra-ui/react-children-utils': 2.0.6(react@18.3.1) '@chakra-ui/react-children-utils': 2.0.6(react@18.3.1)
'@chakra-ui/react-context': 2.1.0(react@18.3.1) '@chakra-ui/react-context': 2.1.0(react@18.3.1)
'@chakra-ui/shared-utils': 2.0.5 '@chakra-ui/shared-utils': 2.0.5
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1) '@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
react: 18.3.1 react: 18.3.1
dev: false dev: false
@@ -1905,7 +1912,7 @@ packages:
'@chakra-ui/react-use-merge-refs': 2.1.0(react@18.3.1) '@chakra-ui/react-use-merge-refs': 2.1.0(react@18.3.1)
'@chakra-ui/shared-utils': 2.0.5 '@chakra-ui/shared-utils': 2.0.5
'@chakra-ui/spinner': 2.1.0(@chakra-ui/system@2.6.2)(react@18.3.1) '@chakra-ui/spinner': 2.1.0(@chakra-ui/system@2.6.2)(react@18.3.1)
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1) '@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
react: 18.3.1 react: 18.3.1
dev: false dev: false
@@ -1916,7 +1923,7 @@ packages:
react: '>=18' react: '>=18'
dependencies: dependencies:
'@chakra-ui/shared-utils': 2.0.5 '@chakra-ui/shared-utils': 2.0.5
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1) '@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
react: 18.3.1 react: 18.3.1
dev: false dev: false
@@ -1935,7 +1942,7 @@ packages:
'@chakra-ui/react-use-safe-layout-effect': 2.1.0(react@18.3.1) '@chakra-ui/react-use-safe-layout-effect': 2.1.0(react@18.3.1)
'@chakra-ui/react-use-update-effect': 2.1.0(react@18.3.1) '@chakra-ui/react-use-update-effect': 2.1.0(react@18.3.1)
'@chakra-ui/shared-utils': 2.0.5 '@chakra-ui/shared-utils': 2.0.5
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1) '@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
'@chakra-ui/visually-hidden': 2.2.0(@chakra-ui/system@2.6.2)(react@18.3.1) '@chakra-ui/visually-hidden': 2.2.0(@chakra-ui/system@2.6.2)(react@18.3.1)
'@zag-js/focus-visible': 0.16.0 '@zag-js/focus-visible': 0.16.0
react: 18.3.1 react: 18.3.1
@@ -1958,7 +1965,7 @@ packages:
react: '>=18' react: '>=18'
dependencies: dependencies:
'@chakra-ui/icon': 3.2.0(@chakra-ui/system@2.6.2)(react@18.3.1) '@chakra-ui/icon': 3.2.0(@chakra-ui/system@2.6.2)(react@18.3.1)
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1) '@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
react: 18.3.1 react: 18.3.1
dev: false dev: false
@@ -1977,7 +1984,7 @@ packages:
'@chakra-ui/system': '>=2.0.0' '@chakra-ui/system': '>=2.0.0'
react: '>=18' react: '>=18'
dependencies: dependencies:
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1) '@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
react: 18.3.1 react: 18.3.1
dev: false dev: false
@@ -1992,13 +1999,13 @@ packages:
react: 18.3.1 react: 18.3.1
dev: false dev: false
/@chakra-ui/css-reset@2.3.0(@emotion/react@11.13.0)(react@18.3.1): /@chakra-ui/css-reset@2.3.0(@emotion/react@11.13.3)(react@18.3.1):
resolution: {integrity: sha512-cQwwBy5O0jzvl0K7PLTLgp8ijqLPKyuEMiDXwYzl95seD3AoeuoCLyzZcJtVqaUZ573PiBdAbY/IlZcwDOItWg==} resolution: {integrity: sha512-cQwwBy5O0jzvl0K7PLTLgp8ijqLPKyuEMiDXwYzl95seD3AoeuoCLyzZcJtVqaUZ573PiBdAbY/IlZcwDOItWg==}
peerDependencies: peerDependencies:
'@emotion/react': '>=10.0.35' '@emotion/react': '>=10.0.35'
react: '>=18' react: '>=18'
dependencies: dependencies:
'@emotion/react': 11.13.0(@types/react@18.3.3)(react@18.3.1) '@emotion/react': 11.13.3(@types/react@18.3.3)(react@18.3.1)
react: 18.3.1 react: 18.3.1
dev: false dev: false
@@ -2031,7 +2038,7 @@ packages:
'@chakra-ui/react-use-safe-layout-effect': 2.1.0(react@18.3.1) '@chakra-ui/react-use-safe-layout-effect': 2.1.0(react@18.3.1)
'@chakra-ui/react-use-update-effect': 2.1.0(react@18.3.1) '@chakra-ui/react-use-update-effect': 2.1.0(react@18.3.1)
'@chakra-ui/shared-utils': 2.0.5 '@chakra-ui/shared-utils': 2.0.5
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1) '@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
react: 18.3.1 react: 18.3.1
dev: false dev: false
@@ -2062,7 +2069,7 @@ packages:
'@chakra-ui/react-types': 2.0.7(react@18.3.1) '@chakra-ui/react-types': 2.0.7(react@18.3.1)
'@chakra-ui/react-use-merge-refs': 2.1.0(react@18.3.1) '@chakra-ui/react-use-merge-refs': 2.1.0(react@18.3.1)
'@chakra-ui/shared-utils': 2.0.5 '@chakra-ui/shared-utils': 2.0.5
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1) '@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
react: 18.3.1 react: 18.3.1
dev: false dev: false
@@ -2085,7 +2092,7 @@ packages:
react: '>=18' react: '>=18'
dependencies: dependencies:
'@chakra-ui/shared-utils': 2.0.5 '@chakra-ui/shared-utils': 2.0.5
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1) '@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
react: 18.3.1 react: 18.3.1
dev: false dev: false
@@ -2096,7 +2103,7 @@ packages:
react: '>=18' react: '>=18'
dependencies: dependencies:
'@chakra-ui/icon': 3.2.0(@chakra-ui/system@2.6.2)(react@18.3.1) '@chakra-ui/icon': 3.2.0(@chakra-ui/system@2.6.2)(react@18.3.1)
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1) '@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
react: 18.3.1 react: 18.3.1
dev: false dev: false
@@ -2108,7 +2115,7 @@ packages:
dependencies: dependencies:
'@chakra-ui/react-use-safe-layout-effect': 2.1.0(react@18.3.1) '@chakra-ui/react-use-safe-layout-effect': 2.1.0(react@18.3.1)
'@chakra-ui/shared-utils': 2.0.5 '@chakra-ui/shared-utils': 2.0.5
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1) '@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
react: 18.3.1 react: 18.3.1
dev: false dev: false
@@ -2123,7 +2130,7 @@ packages:
'@chakra-ui/react-children-utils': 2.0.6(react@18.3.1) '@chakra-ui/react-children-utils': 2.0.6(react@18.3.1)
'@chakra-ui/react-context': 2.1.0(react@18.3.1) '@chakra-ui/react-context': 2.1.0(react@18.3.1)
'@chakra-ui/shared-utils': 2.0.5 '@chakra-ui/shared-utils': 2.0.5
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1) '@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
react: 18.3.1 react: 18.3.1
dev: false dev: false
@@ -2139,7 +2146,7 @@ packages:
'@chakra-ui/react-children-utils': 2.0.6(react@18.3.1) '@chakra-ui/react-children-utils': 2.0.6(react@18.3.1)
'@chakra-ui/react-context': 2.1.0(react@18.3.1) '@chakra-ui/react-context': 2.1.0(react@18.3.1)
'@chakra-ui/shared-utils': 2.0.5 '@chakra-ui/shared-utils': 2.0.5
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1) '@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
react: 18.3.1 react: 18.3.1
dev: false dev: false
@@ -2164,7 +2171,7 @@ packages:
'@chakra-ui/breakpoint-utils': 2.0.8 '@chakra-ui/breakpoint-utils': 2.0.8
'@chakra-ui/react-env': 3.1.0(react@18.3.1) '@chakra-ui/react-env': 3.1.0(react@18.3.1)
'@chakra-ui/shared-utils': 2.0.5 '@chakra-ui/shared-utils': 2.0.5
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1) '@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
react: 18.3.1 react: 18.3.1
dev: false dev: false
@@ -2189,7 +2196,7 @@ packages:
'@chakra-ui/react-use-outside-click': 2.2.0(react@18.3.1) '@chakra-ui/react-use-outside-click': 2.2.0(react@18.3.1)
'@chakra-ui/react-use-update-effect': 2.1.0(react@18.3.1) '@chakra-ui/react-use-update-effect': 2.1.0(react@18.3.1)
'@chakra-ui/shared-utils': 2.0.5 '@chakra-ui/shared-utils': 2.0.5
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1) '@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
'@chakra-ui/transition': 2.1.0(framer-motion@10.18.0)(react@18.3.1) '@chakra-ui/transition': 2.1.0(framer-motion@10.18.0)(react@18.3.1)
framer-motion: 10.18.0(react-dom@18.3.1)(react@18.3.1) framer-motion: 10.18.0(react-dom@18.3.1)(react@18.3.1)
react: 18.3.1 react: 18.3.1
@@ -2216,7 +2223,7 @@ packages:
'@chakra-ui/react-use-outside-click': 2.2.0(react@18.3.1) '@chakra-ui/react-use-outside-click': 2.2.0(react@18.3.1)
'@chakra-ui/react-use-update-effect': 2.1.0(react@18.3.1) '@chakra-ui/react-use-update-effect': 2.1.0(react@18.3.1)
'@chakra-ui/shared-utils': 2.0.5 '@chakra-ui/shared-utils': 2.0.5
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1) '@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
'@chakra-ui/transition': 2.1.0(framer-motion@11.3.24)(react@18.3.1) '@chakra-ui/transition': 2.1.0(framer-motion@11.3.24)(react@18.3.1)
framer-motion: 11.3.24(react-dom@18.3.1)(react@18.3.1) framer-motion: 11.3.24(react-dom@18.3.1)(react@18.3.1)
react: 18.3.1 react: 18.3.1
@@ -2237,7 +2244,7 @@ packages:
'@chakra-ui/react-types': 2.0.7(react@18.3.1) '@chakra-ui/react-types': 2.0.7(react@18.3.1)
'@chakra-ui/react-use-merge-refs': 2.1.0(react@18.3.1) '@chakra-ui/react-use-merge-refs': 2.1.0(react@18.3.1)
'@chakra-ui/shared-utils': 2.0.5 '@chakra-ui/shared-utils': 2.0.5
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1) '@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
'@chakra-ui/transition': 2.1.0(framer-motion@10.18.0)(react@18.3.1) '@chakra-ui/transition': 2.1.0(framer-motion@10.18.0)(react@18.3.1)
aria-hidden: 1.2.4 aria-hidden: 1.2.4
framer-motion: 10.18.0(react-dom@18.3.1)(react@18.3.1) framer-motion: 10.18.0(react-dom@18.3.1)(react@18.3.1)
@@ -2266,7 +2273,7 @@ packages:
'@chakra-ui/react-use-safe-layout-effect': 2.1.0(react@18.3.1) '@chakra-ui/react-use-safe-layout-effect': 2.1.0(react@18.3.1)
'@chakra-ui/react-use-update-effect': 2.1.0(react@18.3.1) '@chakra-ui/react-use-update-effect': 2.1.0(react@18.3.1)
'@chakra-ui/shared-utils': 2.0.5 '@chakra-ui/shared-utils': 2.0.5
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1) '@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
react: 18.3.1 react: 18.3.1
dev: false dev: false
@@ -2290,7 +2297,7 @@ packages:
'@chakra-ui/react-use-controllable-state': 2.1.0(react@18.3.1) '@chakra-ui/react-use-controllable-state': 2.1.0(react@18.3.1)
'@chakra-ui/react-use-merge-refs': 2.1.0(react@18.3.1) '@chakra-ui/react-use-merge-refs': 2.1.0(react@18.3.1)
'@chakra-ui/shared-utils': 2.0.5 '@chakra-ui/shared-utils': 2.0.5
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1) '@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
react: 18.3.1 react: 18.3.1
dev: false dev: false
@@ -2312,7 +2319,7 @@ packages:
'@chakra-ui/react-use-focus-on-pointer-down': 2.1.0(react@18.3.1) '@chakra-ui/react-use-focus-on-pointer-down': 2.1.0(react@18.3.1)
'@chakra-ui/react-use-merge-refs': 2.1.0(react@18.3.1) '@chakra-ui/react-use-merge-refs': 2.1.0(react@18.3.1)
'@chakra-ui/shared-utils': 2.0.5 '@chakra-ui/shared-utils': 2.0.5
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1) '@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
framer-motion: 10.18.0(react-dom@18.3.1)(react@18.3.1) framer-motion: 10.18.0(react-dom@18.3.1)(react@18.3.1)
react: 18.3.1 react: 18.3.1
dev: false dev: false
@@ -2347,11 +2354,11 @@ packages:
react: '>=18' react: '>=18'
dependencies: dependencies:
'@chakra-ui/react-context': 2.1.0(react@18.3.1) '@chakra-ui/react-context': 2.1.0(react@18.3.1)
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1) '@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
react: 18.3.1 react: 18.3.1
dev: false dev: false
/@chakra-ui/provider@2.4.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react-dom@18.3.1)(react@18.3.1): /@chakra-ui/provider@2.4.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react-dom@18.3.1)(react@18.3.1):
resolution: {integrity: sha512-w0Tef5ZCJK1mlJorcSjItCSbyvVuqpvyWdxZiVQmE6fvSJR83wZof42ux0+sfWD+I7rHSfj+f9nzhNaEWClysw==} resolution: {integrity: sha512-w0Tef5ZCJK1mlJorcSjItCSbyvVuqpvyWdxZiVQmE6fvSJR83wZof42ux0+sfWD+I7rHSfj+f9nzhNaEWClysw==}
peerDependencies: peerDependencies:
'@emotion/react': ^11.0.0 '@emotion/react': ^11.0.0
@@ -2359,13 +2366,13 @@ packages:
react: '>=18' react: '>=18'
react-dom: '>=18' react-dom: '>=18'
dependencies: dependencies:
'@chakra-ui/css-reset': 2.3.0(@emotion/react@11.13.0)(react@18.3.1) '@chakra-ui/css-reset': 2.3.0(@emotion/react@11.13.3)(react@18.3.1)
'@chakra-ui/portal': 2.1.0(react-dom@18.3.1)(react@18.3.1) '@chakra-ui/portal': 2.1.0(react-dom@18.3.1)(react@18.3.1)
'@chakra-ui/react-env': 3.1.0(react@18.3.1) '@chakra-ui/react-env': 3.1.0(react@18.3.1)
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1) '@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
'@chakra-ui/utils': 2.0.15 '@chakra-ui/utils': 2.0.15
'@emotion/react': 11.13.0(@types/react@18.3.3)(react@18.3.1) '@emotion/react': 11.13.3(@types/react@18.3.3)(react@18.3.1)
'@emotion/styled': 11.13.0(@emotion/react@11.13.0)(@types/react@18.3.3)(react@18.3.1) '@emotion/styled': 11.13.0(@emotion/react@11.13.3)(@types/react@18.3.3)(react@18.3.1)
react: 18.3.1 react: 18.3.1
react-dom: 18.3.1(react@18.3.1) react-dom: 18.3.1(react@18.3.1)
dev: false dev: false
@@ -2381,7 +2388,7 @@ packages:
'@chakra-ui/react-types': 2.0.7(react@18.3.1) '@chakra-ui/react-types': 2.0.7(react@18.3.1)
'@chakra-ui/react-use-merge-refs': 2.1.0(react@18.3.1) '@chakra-ui/react-use-merge-refs': 2.1.0(react@18.3.1)
'@chakra-ui/shared-utils': 2.0.5 '@chakra-ui/shared-utils': 2.0.5
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1) '@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
'@zag-js/focus-visible': 0.16.0 '@zag-js/focus-visible': 0.16.0
react: 18.3.1 react: 18.3.1
dev: false dev: false
@@ -2581,7 +2588,7 @@ packages:
react: 18.3.1 react: 18.3.1
dev: false dev: false
/@chakra-ui/react@2.8.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(@types/react@18.3.3)(framer-motion@10.18.0)(react-dom@18.3.1)(react@18.3.1): /@chakra-ui/react@2.8.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(@types/react@18.3.3)(framer-motion@10.18.0)(react-dom@18.3.1)(react@18.3.1):
resolution: {integrity: sha512-Hn0moyxxyCDKuR9ywYpqgX8dvjqwu9ArwpIb9wHNYjnODETjLwazgNIliCVBRcJvysGRiV51U2/JtJVrpeCjUQ==} resolution: {integrity: sha512-Hn0moyxxyCDKuR9ywYpqgX8dvjqwu9ArwpIb9wHNYjnODETjLwazgNIliCVBRcJvysGRiV51U2/JtJVrpeCjUQ==}
peerDependencies: peerDependencies:
'@emotion/react': ^11.0.0 '@emotion/react': ^11.0.0
@@ -2600,7 +2607,7 @@ packages:
'@chakra-ui/close-button': 2.1.1(@chakra-ui/system@2.6.2)(react@18.3.1) '@chakra-ui/close-button': 2.1.1(@chakra-ui/system@2.6.2)(react@18.3.1)
'@chakra-ui/control-box': 2.1.0(@chakra-ui/system@2.6.2)(react@18.3.1) '@chakra-ui/control-box': 2.1.0(@chakra-ui/system@2.6.2)(react@18.3.1)
'@chakra-ui/counter': 2.1.0(react@18.3.1) '@chakra-ui/counter': 2.1.0(react@18.3.1)
'@chakra-ui/css-reset': 2.3.0(@emotion/react@11.13.0)(react@18.3.1) '@chakra-ui/css-reset': 2.3.0(@emotion/react@11.13.3)(react@18.3.1)
'@chakra-ui/editable': 3.1.0(@chakra-ui/system@2.6.2)(react@18.3.1) '@chakra-ui/editable': 3.1.0(@chakra-ui/system@2.6.2)(react@18.3.1)
'@chakra-ui/focus-lock': 2.1.0(@types/react@18.3.3)(react@18.3.1) '@chakra-ui/focus-lock': 2.1.0(@types/react@18.3.3)(react@18.3.1)
'@chakra-ui/form-control': 2.2.0(@chakra-ui/system@2.6.2)(react@18.3.1) '@chakra-ui/form-control': 2.2.0(@chakra-ui/system@2.6.2)(react@18.3.1)
@@ -2619,7 +2626,7 @@ packages:
'@chakra-ui/popper': 3.1.0(react@18.3.1) '@chakra-ui/popper': 3.1.0(react@18.3.1)
'@chakra-ui/portal': 2.1.0(react-dom@18.3.1)(react@18.3.1) '@chakra-ui/portal': 2.1.0(react-dom@18.3.1)(react@18.3.1)
'@chakra-ui/progress': 2.2.0(@chakra-ui/system@2.6.2)(react@18.3.1) '@chakra-ui/progress': 2.2.0(@chakra-ui/system@2.6.2)(react@18.3.1)
'@chakra-ui/provider': 2.4.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react-dom@18.3.1)(react@18.3.1) '@chakra-ui/provider': 2.4.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react-dom@18.3.1)(react@18.3.1)
'@chakra-ui/radio': 2.1.2(@chakra-ui/system@2.6.2)(react@18.3.1) '@chakra-ui/radio': 2.1.2(@chakra-ui/system@2.6.2)(react@18.3.1)
'@chakra-ui/react-env': 3.1.0(react@18.3.1) '@chakra-ui/react-env': 3.1.0(react@18.3.1)
'@chakra-ui/select': 2.1.2(@chakra-ui/system@2.6.2)(react@18.3.1) '@chakra-ui/select': 2.1.2(@chakra-ui/system@2.6.2)(react@18.3.1)
@@ -2631,7 +2638,7 @@ packages:
'@chakra-ui/stepper': 2.3.1(@chakra-ui/system@2.6.2)(react@18.3.1) '@chakra-ui/stepper': 2.3.1(@chakra-ui/system@2.6.2)(react@18.3.1)
'@chakra-ui/styled-system': 2.9.2 '@chakra-ui/styled-system': 2.9.2
'@chakra-ui/switch': 2.1.2(@chakra-ui/system@2.6.2)(framer-motion@10.18.0)(react@18.3.1) '@chakra-ui/switch': 2.1.2(@chakra-ui/system@2.6.2)(framer-motion@10.18.0)(react@18.3.1)
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1) '@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
'@chakra-ui/table': 2.1.0(@chakra-ui/system@2.6.2)(react@18.3.1) '@chakra-ui/table': 2.1.0(@chakra-ui/system@2.6.2)(react@18.3.1)
'@chakra-ui/tabs': 3.0.0(@chakra-ui/system@2.6.2)(react@18.3.1) '@chakra-ui/tabs': 3.0.0(@chakra-ui/system@2.6.2)(react@18.3.1)
'@chakra-ui/tag': 3.1.1(@chakra-ui/system@2.6.2)(react@18.3.1) '@chakra-ui/tag': 3.1.1(@chakra-ui/system@2.6.2)(react@18.3.1)
@@ -2643,8 +2650,8 @@ packages:
'@chakra-ui/transition': 2.1.0(framer-motion@10.18.0)(react@18.3.1) '@chakra-ui/transition': 2.1.0(framer-motion@10.18.0)(react@18.3.1)
'@chakra-ui/utils': 2.0.15 '@chakra-ui/utils': 2.0.15
'@chakra-ui/visually-hidden': 2.2.0(@chakra-ui/system@2.6.2)(react@18.3.1) '@chakra-ui/visually-hidden': 2.2.0(@chakra-ui/system@2.6.2)(react@18.3.1)
'@emotion/react': 11.13.0(@types/react@18.3.3)(react@18.3.1) '@emotion/react': 11.13.3(@types/react@18.3.3)(react@18.3.1)
'@emotion/styled': 11.13.0(@emotion/react@11.13.0)(@types/react@18.3.3)(react@18.3.1) '@emotion/styled': 11.13.0(@emotion/react@11.13.3)(@types/react@18.3.3)(react@18.3.1)
framer-motion: 10.18.0(react-dom@18.3.1)(react@18.3.1) framer-motion: 10.18.0(react-dom@18.3.1)(react@18.3.1)
react: 18.3.1 react: 18.3.1
react-dom: 18.3.1(react@18.3.1) react-dom: 18.3.1(react@18.3.1)
@@ -2660,7 +2667,7 @@ packages:
dependencies: dependencies:
'@chakra-ui/form-control': 2.2.0(@chakra-ui/system@2.6.2)(react@18.3.1) '@chakra-ui/form-control': 2.2.0(@chakra-ui/system@2.6.2)(react@18.3.1)
'@chakra-ui/shared-utils': 2.0.5 '@chakra-ui/shared-utils': 2.0.5
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1) '@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
react: 18.3.1 react: 18.3.1
dev: false dev: false
@@ -2677,7 +2684,7 @@ packages:
'@chakra-ui/media-query': 3.3.0(@chakra-ui/system@2.6.2)(react@18.3.1) '@chakra-ui/media-query': 3.3.0(@chakra-ui/system@2.6.2)(react@18.3.1)
'@chakra-ui/react-use-previous': 2.1.0(react@18.3.1) '@chakra-ui/react-use-previous': 2.1.0(react@18.3.1)
'@chakra-ui/shared-utils': 2.0.5 '@chakra-ui/shared-utils': 2.0.5
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1) '@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
react: 18.3.1 react: 18.3.1
dev: false dev: false
@@ -2687,7 +2694,7 @@ packages:
'@chakra-ui/system': '>=2.0.0' '@chakra-ui/system': '>=2.0.0'
react: '>=18' react: '>=18'
dependencies: dependencies:
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1) '@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
react: 18.3.1 react: 18.3.1
dev: false dev: false
@@ -2707,7 +2714,7 @@ packages:
'@chakra-ui/react-use-pan-event': 2.1.0(react@18.3.1) '@chakra-ui/react-use-pan-event': 2.1.0(react@18.3.1)
'@chakra-ui/react-use-size': 2.1.0(react@18.3.1) '@chakra-ui/react-use-size': 2.1.0(react@18.3.1)
'@chakra-ui/react-use-update-effect': 2.1.0(react@18.3.1) '@chakra-ui/react-use-update-effect': 2.1.0(react@18.3.1)
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1) '@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
react: 18.3.1 react: 18.3.1
dev: false dev: false
@@ -2718,7 +2725,7 @@ packages:
react: '>=18' react: '>=18'
dependencies: dependencies:
'@chakra-ui/shared-utils': 2.0.5 '@chakra-ui/shared-utils': 2.0.5
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1) '@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
react: 18.3.1 react: 18.3.1
dev: false dev: false
@@ -2731,7 +2738,7 @@ packages:
'@chakra-ui/icon': 3.2.0(@chakra-ui/system@2.6.2)(react@18.3.1) '@chakra-ui/icon': 3.2.0(@chakra-ui/system@2.6.2)(react@18.3.1)
'@chakra-ui/react-context': 2.1.0(react@18.3.1) '@chakra-ui/react-context': 2.1.0(react@18.3.1)
'@chakra-ui/shared-utils': 2.0.5 '@chakra-ui/shared-utils': 2.0.5
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1) '@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
react: 18.3.1 react: 18.3.1
dev: false dev: false
@@ -2744,7 +2751,7 @@ packages:
'@chakra-ui/icon': 3.2.0(@chakra-ui/system@2.6.2)(react@18.3.1) '@chakra-ui/icon': 3.2.0(@chakra-ui/system@2.6.2)(react@18.3.1)
'@chakra-ui/react-context': 2.1.0(react@18.3.1) '@chakra-ui/react-context': 2.1.0(react@18.3.1)
'@chakra-ui/shared-utils': 2.0.5 '@chakra-ui/shared-utils': 2.0.5
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1) '@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
react: 18.3.1 react: 18.3.1
dev: false dev: false
@@ -2765,12 +2772,12 @@ packages:
dependencies: dependencies:
'@chakra-ui/checkbox': 2.3.2(@chakra-ui/system@2.6.2)(react@18.3.1) '@chakra-ui/checkbox': 2.3.2(@chakra-ui/system@2.6.2)(react@18.3.1)
'@chakra-ui/shared-utils': 2.0.5 '@chakra-ui/shared-utils': 2.0.5
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1) '@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
framer-motion: 10.18.0(react-dom@18.3.1)(react@18.3.1) framer-motion: 10.18.0(react-dom@18.3.1)(react@18.3.1)
react: 18.3.1 react: 18.3.1
dev: false dev: false
/@chakra-ui/system@2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1): /@chakra-ui/system@2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1):
resolution: {integrity: sha512-EGtpoEjLrUu4W1fHD+a62XR+hzC5YfsWm+6lO0Kybcga3yYEij9beegO0jZgug27V+Rf7vns95VPVP6mFd/DEQ==} resolution: {integrity: sha512-EGtpoEjLrUu4W1fHD+a62XR+hzC5YfsWm+6lO0Kybcga3yYEij9beegO0jZgug27V+Rf7vns95VPVP6mFd/DEQ==}
peerDependencies: peerDependencies:
'@emotion/react': ^11.0.0 '@emotion/react': ^11.0.0
@@ -2783,8 +2790,8 @@ packages:
'@chakra-ui/styled-system': 2.9.2 '@chakra-ui/styled-system': 2.9.2
'@chakra-ui/theme-utils': 2.0.21 '@chakra-ui/theme-utils': 2.0.21
'@chakra-ui/utils': 2.0.15 '@chakra-ui/utils': 2.0.15
'@emotion/react': 11.13.0(@types/react@18.3.3)(react@18.3.1) '@emotion/react': 11.13.3(@types/react@18.3.3)(react@18.3.1)
'@emotion/styled': 11.13.0(@emotion/react@11.13.0)(@types/react@18.3.3)(react@18.3.1) '@emotion/styled': 11.13.0(@emotion/react@11.13.3)(@types/react@18.3.3)(react@18.3.1)
react: 18.3.1 react: 18.3.1
react-fast-compare: 3.2.2 react-fast-compare: 3.2.2
dev: false dev: false
@@ -2797,7 +2804,7 @@ packages:
dependencies: dependencies:
'@chakra-ui/react-context': 2.1.0(react@18.3.1) '@chakra-ui/react-context': 2.1.0(react@18.3.1)
'@chakra-ui/shared-utils': 2.0.5 '@chakra-ui/shared-utils': 2.0.5
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1) '@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
react: 18.3.1 react: 18.3.1
dev: false dev: false
@@ -2816,7 +2823,7 @@ packages:
'@chakra-ui/react-use-merge-refs': 2.1.0(react@18.3.1) '@chakra-ui/react-use-merge-refs': 2.1.0(react@18.3.1)
'@chakra-ui/react-use-safe-layout-effect': 2.1.0(react@18.3.1) '@chakra-ui/react-use-safe-layout-effect': 2.1.0(react@18.3.1)
'@chakra-ui/shared-utils': 2.0.5 '@chakra-ui/shared-utils': 2.0.5
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1) '@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
react: 18.3.1 react: 18.3.1
dev: false dev: false
@@ -2828,7 +2835,7 @@ packages:
dependencies: dependencies:
'@chakra-ui/icon': 3.2.0(@chakra-ui/system@2.6.2)(react@18.3.1) '@chakra-ui/icon': 3.2.0(@chakra-ui/system@2.6.2)(react@18.3.1)
'@chakra-ui/react-context': 2.1.0(react@18.3.1) '@chakra-ui/react-context': 2.1.0(react@18.3.1)
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1) '@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
react: 18.3.1 react: 18.3.1
dev: false dev: false
@@ -2840,7 +2847,7 @@ packages:
dependencies: dependencies:
'@chakra-ui/form-control': 2.2.0(@chakra-ui/system@2.6.2)(react@18.3.1) '@chakra-ui/form-control': 2.2.0(@chakra-ui/system@2.6.2)(react@18.3.1)
'@chakra-ui/shared-utils': 2.0.5 '@chakra-ui/shared-utils': 2.0.5
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1) '@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
react: 18.3.1 react: 18.3.1
dev: false dev: false
@@ -2891,7 +2898,7 @@ packages:
'@chakra-ui/react-use-update-effect': 2.1.0(react@18.3.1) '@chakra-ui/react-use-update-effect': 2.1.0(react@18.3.1)
'@chakra-ui/shared-utils': 2.0.5 '@chakra-ui/shared-utils': 2.0.5
'@chakra-ui/styled-system': 2.9.2 '@chakra-ui/styled-system': 2.9.2
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1) '@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
'@chakra-ui/theme': 3.3.1(@chakra-ui/styled-system@2.9.2) '@chakra-ui/theme': 3.3.1(@chakra-ui/styled-system@2.9.2)
framer-motion: 10.18.0(react-dom@18.3.1)(react@18.3.1) framer-motion: 10.18.0(react-dom@18.3.1)(react@18.3.1)
react: 18.3.1 react: 18.3.1
@@ -2914,7 +2921,7 @@ packages:
'@chakra-ui/react-use-event-listener': 2.1.0(react@18.3.1) '@chakra-ui/react-use-event-listener': 2.1.0(react@18.3.1)
'@chakra-ui/react-use-merge-refs': 2.1.0(react@18.3.1) '@chakra-ui/react-use-merge-refs': 2.1.0(react@18.3.1)
'@chakra-ui/shared-utils': 2.0.5 '@chakra-ui/shared-utils': 2.0.5
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1) '@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
framer-motion: 10.18.0(react-dom@18.3.1)(react@18.3.1) framer-motion: 10.18.0(react-dom@18.3.1)(react@18.3.1)
react: 18.3.1 react: 18.3.1
react-dom: 18.3.1(react@18.3.1) react-dom: 18.3.1(react@18.3.1)
@@ -2957,7 +2964,7 @@ packages:
'@chakra-ui/system': '>=2.0.0' '@chakra-ui/system': '>=2.0.0'
react: '>=18' react: '>=18'
dependencies: dependencies:
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1) '@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
react: 18.3.1 react: 18.3.1
dev: false dev: false
@@ -3040,10 +3047,10 @@ packages:
resolution: {integrity: sha512-y2WQb+oP8Jqvvclh8Q55gLUyb7UFvgv7eJfsj7td5TToBrIUtPay2kMrZi4xjq9qw2vD0ZR5fSho0yqoFgX7Rw==} resolution: {integrity: sha512-y2WQb+oP8Jqvvclh8Q55gLUyb7UFvgv7eJfsj7td5TToBrIUtPay2kMrZi4xjq9qw2vD0ZR5fSho0yqoFgX7Rw==}
dependencies: dependencies:
'@babel/helper-module-imports': 7.24.7 '@babel/helper-module-imports': 7.24.7
'@babel/runtime': 7.25.0 '@babel/runtime': 7.25.4
'@emotion/hash': 0.9.2 '@emotion/hash': 0.9.2
'@emotion/memoize': 0.9.0 '@emotion/memoize': 0.9.0
'@emotion/serialize': 1.3.0 '@emotion/serialize': 1.3.1
babel-plugin-macros: 3.1.0 babel-plugin-macros: 3.1.0
convert-source-map: 1.9.0 convert-source-map: 1.9.0
escape-string-regexp: 4.0.0 escape-string-regexp: 4.0.0
@@ -3131,8 +3138,8 @@ packages:
react: 18.3.1 react: 18.3.1
dev: false dev: false
/@emotion/react@11.13.0(@types/react@18.3.3)(react@18.3.1): /@emotion/react@11.13.3(@types/react@18.3.3)(react@18.3.1):
resolution: {integrity: sha512-WkL+bw1REC2VNV1goQyfxjx1GYJkcc23CRQkXX+vZNLINyfI7o+uUn/rTGPt/xJ3bJHd5GcljgnxHf4wRw5VWQ==} resolution: {integrity: sha512-lIsdU6JNrmYfJ5EbUCf4xW1ovy5wKQ2CkPRM4xogziOxH1nXxBSjpC9YqbFAP7circxMfYp+6x676BqWcEiixg==}
peerDependencies: peerDependencies:
'@types/react': '*' '@types/react': '*'
react: '>=16.8.0' react: '>=16.8.0'
@@ -3140,10 +3147,10 @@ packages:
'@types/react': '@types/react':
optional: true optional: true
dependencies: dependencies:
'@babel/runtime': 7.25.0 '@babel/runtime': 7.25.4
'@emotion/babel-plugin': 11.12.0 '@emotion/babel-plugin': 11.12.0
'@emotion/cache': 11.13.1 '@emotion/cache': 11.13.1
'@emotion/serialize': 1.3.0 '@emotion/serialize': 1.3.1
'@emotion/use-insertion-effect-with-fallbacks': 1.1.0(react@18.3.1) '@emotion/use-insertion-effect-with-fallbacks': 1.1.0(react@18.3.1)
'@emotion/utils': 1.4.0 '@emotion/utils': 1.4.0
'@emotion/weak-memoize': 0.4.0 '@emotion/weak-memoize': 0.4.0
@@ -3164,12 +3171,12 @@ packages:
csstype: 3.1.3 csstype: 3.1.3
dev: false dev: false
/@emotion/serialize@1.3.0: /@emotion/serialize@1.3.1:
resolution: {integrity: sha512-jACuBa9SlYajnpIVXB+XOXnfJHyckDfe6fOpORIM6yhBDlqGuExvDdZYHDQGoDf3bZXGv7tNr+LpLjJqiEQ6EA==} resolution: {integrity: sha512-dEPNKzBPU+vFPGa+z3axPRn8XVDetYORmDC0wAiej+TNcOZE70ZMJa0X7JdeoM6q/nWTMZeLpN/fTnD9o8MQBA==}
dependencies: dependencies:
'@emotion/hash': 0.9.2 '@emotion/hash': 0.9.2
'@emotion/memoize': 0.9.0 '@emotion/memoize': 0.9.0
'@emotion/unitless': 0.9.0 '@emotion/unitless': 0.10.0
'@emotion/utils': 1.4.0 '@emotion/utils': 1.4.0
csstype: 3.1.3 csstype: 3.1.3
dev: false dev: false
@@ -3182,7 +3189,7 @@ packages:
resolution: {integrity: sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==} resolution: {integrity: sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==}
dev: false dev: false
/@emotion/styled@11.13.0(@emotion/react@11.13.0)(@types/react@18.3.3)(react@18.3.1): /@emotion/styled@11.13.0(@emotion/react@11.13.3)(@types/react@18.3.3)(react@18.3.1):
resolution: {integrity: sha512-tkzkY7nQhW/zC4hztlwucpT8QEZ6eUzpXDRhww/Eej4tFfO0FxQYWRyg/c5CCXa4d/f174kqeXYjuQRnhzf6dA==} resolution: {integrity: sha512-tkzkY7nQhW/zC4hztlwucpT8QEZ6eUzpXDRhww/Eej4tFfO0FxQYWRyg/c5CCXa4d/f174kqeXYjuQRnhzf6dA==}
peerDependencies: peerDependencies:
'@emotion/react': ^11.0.0-rc.0 '@emotion/react': ^11.0.0-rc.0
@@ -3192,11 +3199,11 @@ packages:
'@types/react': '@types/react':
optional: true optional: true
dependencies: dependencies:
'@babel/runtime': 7.25.0 '@babel/runtime': 7.25.4
'@emotion/babel-plugin': 11.12.0 '@emotion/babel-plugin': 11.12.0
'@emotion/is-prop-valid': 1.3.0 '@emotion/is-prop-valid': 1.3.0
'@emotion/react': 11.13.0(@types/react@18.3.3)(react@18.3.1) '@emotion/react': 11.13.3(@types/react@18.3.3)(react@18.3.1)
'@emotion/serialize': 1.3.0 '@emotion/serialize': 1.3.1
'@emotion/use-insertion-effect-with-fallbacks': 1.1.0(react@18.3.1) '@emotion/use-insertion-effect-with-fallbacks': 1.1.0(react@18.3.1)
'@emotion/utils': 1.4.0 '@emotion/utils': 1.4.0
'@types/react': 18.3.3 '@types/react': 18.3.3
@@ -3205,12 +3212,12 @@ packages:
- supports-color - supports-color
dev: false dev: false
/@emotion/unitless@0.8.1: /@emotion/unitless@0.10.0:
resolution: {integrity: sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==} resolution: {integrity: sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==}
dev: false dev: false
/@emotion/unitless@0.9.0: /@emotion/unitless@0.8.1:
resolution: {integrity: sha512-TP6GgNZtmtFaFcsOgExdnfxLLpRDla4Q66tnenA9CktvVSdNKDvMVuUah4QvWPIpNjrWsGg3qeGo9a43QooGZQ==} resolution: {integrity: sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==}
dev: false dev: false
/@emotion/use-insertion-effect-with-fallbacks@1.0.1(react@18.3.1): /@emotion/use-insertion-effect-with-fallbacks@1.0.1(react@18.3.1):
@@ -3564,8 +3571,8 @@ packages:
prettier: 3.3.3 prettier: 3.3.3
dev: true dev: true
/@invoke-ai/ui-library@0.0.31(@chakra-ui/form-control@2.2.0)(@chakra-ui/icon@3.2.0)(@chakra-ui/media-query@3.3.0)(@chakra-ui/menu@2.2.1)(@chakra-ui/spinner@2.1.0)(@chakra-ui/system@2.6.2)(@fontsource-variable/inter@5.0.20)(@types/react@18.3.3)(i18next@23.12.2)(react-dom@18.3.1)(react@18.3.1): /@invoke-ai/ui-library@0.0.32(@chakra-ui/form-control@2.2.0)(@chakra-ui/icon@3.2.0)(@chakra-ui/media-query@3.3.0)(@chakra-ui/menu@2.2.1)(@chakra-ui/spinner@2.1.0)(@chakra-ui/system@2.6.2)(@fontsource-variable/inter@5.0.20)(@types/react@18.3.3)(i18next@23.12.2)(react-dom@18.3.1)(react@18.3.1):
resolution: {integrity: sha512-7LtOUN/bcGHc8jCRd2m22DvP2eeogqwM/shdXQpLH5RY2FzWJNXlWdVT4hIPGDu7znnk3xvXlZvo6tiGSjbnCQ==} resolution: {integrity: sha512-JxAoblrDu/cZ4ha9KO4ry5OWvyLUE1Dj28i+ciMaDNUpC/cN+IyiTbUBoFoPaoN5JP8Zpd/MYCcmF2qsziHDzg==}
peerDependencies: peerDependencies:
'@fontsource-variable/inter': ^5.0.16 '@fontsource-variable/inter': ^5.0.16
react: ^18.2.0 react: ^18.2.0
@@ -3575,14 +3582,14 @@ packages:
'@chakra-ui/icons': 2.1.1(@chakra-ui/system@2.6.2)(react@18.3.1) '@chakra-ui/icons': 2.1.1(@chakra-ui/system@2.6.2)(react@18.3.1)
'@chakra-ui/layout': 2.3.1(@chakra-ui/system@2.6.2)(react@18.3.1) '@chakra-ui/layout': 2.3.1(@chakra-ui/system@2.6.2)(react@18.3.1)
'@chakra-ui/portal': 2.1.0(react-dom@18.3.1)(react@18.3.1) '@chakra-ui/portal': 2.1.0(react-dom@18.3.1)(react@18.3.1)
'@chakra-ui/react': 2.8.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(@types/react@18.3.3)(framer-motion@10.18.0)(react-dom@18.3.1)(react@18.3.1) '@chakra-ui/react': 2.8.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(@types/react@18.3.3)(framer-motion@10.18.0)(react-dom@18.3.1)(react@18.3.1)
'@chakra-ui/styled-system': 2.9.2 '@chakra-ui/styled-system': 2.9.2
'@chakra-ui/theme-tools': 2.1.2(@chakra-ui/styled-system@2.9.2) '@chakra-ui/theme-tools': 2.1.2(@chakra-ui/styled-system@2.9.2)
'@emotion/react': 11.13.0(@types/react@18.3.3)(react@18.3.1) '@emotion/react': 11.13.3(@types/react@18.3.3)(react@18.3.1)
'@emotion/styled': 11.13.0(@emotion/react@11.13.0)(@types/react@18.3.3)(react@18.3.1) '@emotion/styled': 11.13.0(@emotion/react@11.13.3)(@types/react@18.3.3)(react@18.3.1)
'@fontsource-variable/inter': 5.0.20 '@fontsource-variable/inter': 5.0.20
'@nanostores/react': 0.7.3(nanostores@0.11.2)(react@18.3.1) '@nanostores/react': 0.7.3(nanostores@0.11.2)(react@18.3.1)
chakra-react-select: 4.9.1(@chakra-ui/form-control@2.2.0)(@chakra-ui/icon@3.2.0)(@chakra-ui/layout@2.3.1)(@chakra-ui/media-query@3.3.0)(@chakra-ui/menu@2.2.1)(@chakra-ui/spinner@2.1.0)(@chakra-ui/system@2.6.2)(@emotion/react@11.13.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1) chakra-react-select: 4.9.1(@chakra-ui/form-control@2.2.0)(@chakra-ui/icon@3.2.0)(@chakra-ui/layout@2.3.1)(@chakra-ui/media-query@3.3.0)(@chakra-ui/menu@2.2.1)(@chakra-ui/spinner@2.1.0)(@chakra-ui/system@2.6.2)(@emotion/react@11.13.3)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1)
framer-motion: 10.18.0(react-dom@18.3.1)(react@18.3.1) framer-motion: 10.18.0(react-dom@18.3.1)(react@18.3.1)
lodash-es: 4.17.21 lodash-es: 4.17.21
nanostores: 0.11.2 nanostores: 0.11.2
@@ -5774,7 +5781,7 @@ packages:
resolution: {integrity: sha512-y+CcFFwelSXpLZk/7fMB2mUbGtX9lKycf1MWJ7CaTIERyitVlyQx6C+sxcROU2BAJ24OiZyK+8wj2i8AlBoS3A==} resolution: {integrity: sha512-y+CcFFwelSXpLZk/7fMB2mUbGtX9lKycf1MWJ7CaTIERyitVlyQx6C+sxcROU2BAJ24OiZyK+8wj2i8AlBoS3A==}
engines: {node: '>=10'} engines: {node: '>=10'}
dependencies: dependencies:
tslib: 2.6.3 tslib: 2.7.0
dev: false dev: false
/aria-query@5.3.0: /aria-query@5.3.0:
@@ -6126,7 +6133,7 @@ packages:
type-detect: 4.0.8 type-detect: 4.0.8
dev: true dev: true
/chakra-react-select@4.9.1(@chakra-ui/form-control@2.2.0)(@chakra-ui/icon@3.2.0)(@chakra-ui/layout@2.3.1)(@chakra-ui/media-query@3.3.0)(@chakra-ui/menu@2.2.1)(@chakra-ui/spinner@2.1.0)(@chakra-ui/system@2.6.2)(@emotion/react@11.13.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1): /chakra-react-select@4.9.1(@chakra-ui/form-control@2.2.0)(@chakra-ui/icon@3.2.0)(@chakra-ui/layout@2.3.1)(@chakra-ui/media-query@3.3.0)(@chakra-ui/menu@2.2.1)(@chakra-ui/spinner@2.1.0)(@chakra-ui/system@2.6.2)(@emotion/react@11.13.3)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1):
resolution: {integrity: sha512-jmgfN+S/wnTaCp3pW30GYDIZ5J8jWcT1gIbhpw6RdKV+atm/U4/sT+gaHOHHhRL8xeaYip+iI/m8MPGREkve0w==} resolution: {integrity: sha512-jmgfN+S/wnTaCp3pW30GYDIZ5J8jWcT1gIbhpw6RdKV+atm/U4/sT+gaHOHHhRL8xeaYip+iI/m8MPGREkve0w==}
peerDependencies: peerDependencies:
'@chakra-ui/form-control': ^2.0.0 '@chakra-ui/form-control': ^2.0.0
@@ -6146,8 +6153,8 @@ packages:
'@chakra-ui/media-query': 3.3.0(@chakra-ui/system@2.6.2)(react@18.3.1) '@chakra-ui/media-query': 3.3.0(@chakra-ui/system@2.6.2)(react@18.3.1)
'@chakra-ui/menu': 2.2.1(@chakra-ui/system@2.6.2)(framer-motion@11.3.24)(react@18.3.1) '@chakra-ui/menu': 2.2.1(@chakra-ui/system@2.6.2)(framer-motion@11.3.24)(react@18.3.1)
'@chakra-ui/spinner': 2.1.0(@chakra-ui/system@2.6.2)(react@18.3.1) '@chakra-ui/spinner': 2.1.0(@chakra-ui/system@2.6.2)(react@18.3.1)
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1) '@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
'@emotion/react': 11.13.0(@types/react@18.3.3)(react@18.3.1) '@emotion/react': 11.13.3(@types/react@18.3.3)(react@18.3.1)
react: 18.3.1 react: 18.3.1
react-dom: 18.3.1(react@18.3.1) react-dom: 18.3.1(react@18.3.1)
react-select: 5.8.0(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1) react-select: 5.8.0(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1)
@@ -7572,7 +7579,7 @@ packages:
resolution: {integrity: sha512-QFaHbhv9WPUeLYBDe/PAuLKJ4Dd9OPvKs9xZBr3yLXnUrDNaVXKu2baDBXe3naPY30hgHYSsf2JW4jzas2mDEQ==} resolution: {integrity: sha512-QFaHbhv9WPUeLYBDe/PAuLKJ4Dd9OPvKs9xZBr3yLXnUrDNaVXKu2baDBXe3naPY30hgHYSsf2JW4jzas2mDEQ==}
engines: {node: '>=10'} engines: {node: '>=10'}
dependencies: dependencies:
tslib: 2.6.3 tslib: 2.7.0
dev: false dev: false
/for-each@0.3.3: /for-each@0.3.3:
@@ -7611,7 +7618,7 @@ packages:
dependencies: dependencies:
react: 18.3.1 react: 18.3.1
react-dom: 18.3.1(react@18.3.1) react-dom: 18.3.1(react@18.3.1)
tslib: 2.6.3 tslib: 2.7.0
optionalDependencies: optionalDependencies:
'@emotion/is-prop-valid': 0.8.8 '@emotion/is-prop-valid': 0.8.8
dev: false dev: false
@@ -9619,7 +9626,7 @@ packages:
peerDependencies: peerDependencies:
react: ^15.3.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 react: ^15.3.0 || ^16.0.0 || ^17.0.0 || ^18.0.0
dependencies: dependencies:
'@babel/runtime': 7.25.0 '@babel/runtime': 7.25.4
react: 18.3.1 react: 18.3.1
dev: false dev: false
@@ -9714,7 +9721,7 @@ packages:
'@types/react': '@types/react':
optional: true optional: true
dependencies: dependencies:
'@babel/runtime': 7.25.0 '@babel/runtime': 7.25.4
'@types/react': 18.3.3 '@types/react': 18.3.3
focus-lock: 1.3.5 focus-lock: 1.3.5
prop-types: 15.8.1 prop-types: 15.8.1
@@ -9776,7 +9783,7 @@ packages:
react-native: react-native:
optional: true optional: true
dependencies: dependencies:
'@babel/runtime': 7.25.0 '@babel/runtime': 7.25.4
html-parse-stringify: 3.0.1 html-parse-stringify: 3.0.1
i18next: 23.12.2 i18next: 23.12.2
react: 18.3.1 react: 18.3.1
@@ -9838,7 +9845,7 @@ packages:
'@types/react': 18.3.3 '@types/react': 18.3.3
react: 18.3.1 react: 18.3.1
react-style-singleton: 2.2.1(@types/react@18.3.3)(react@18.3.1) react-style-singleton: 2.2.1(@types/react@18.3.3)(react@18.3.1)
tslib: 2.6.3 tslib: 2.7.0
dev: false dev: false
/react-remove-scroll@2.5.10(@types/react@18.3.3)(react@18.3.1): /react-remove-scroll@2.5.10(@types/react@18.3.3)(react@18.3.1):
@@ -9855,7 +9862,7 @@ packages:
react: 18.3.1 react: 18.3.1
react-remove-scroll-bar: 2.3.6(@types/react@18.3.3)(react@18.3.1) react-remove-scroll-bar: 2.3.6(@types/react@18.3.3)(react@18.3.1)
react-style-singleton: 2.2.1(@types/react@18.3.3)(react@18.3.1) react-style-singleton: 2.2.1(@types/react@18.3.3)(react@18.3.1)
tslib: 2.6.3 tslib: 2.7.0
use-callback-ref: 1.3.2(@types/react@18.3.3)(react@18.3.1) use-callback-ref: 1.3.2(@types/react@18.3.3)(react@18.3.1)
use-sidecar: 1.1.2(@types/react@18.3.3)(react@18.3.1) use-sidecar: 1.1.2(@types/react@18.3.3)(react@18.3.1)
dev: false dev: false
@@ -9905,7 +9912,7 @@ packages:
get-nonce: 1.0.1 get-nonce: 1.0.1
invariant: 2.2.4 invariant: 2.2.4
react: 18.3.1 react: 18.3.1
tslib: 2.6.3 tslib: 2.7.0
dev: false dev: false
/react-transition-group@4.4.5(react-dom@18.3.1)(react@18.3.1): /react-transition-group@4.4.5(react-dom@18.3.1)(react@18.3.1):
@@ -11045,6 +11052,10 @@ packages:
/tslib@2.6.3: /tslib@2.6.3:
resolution: {integrity: sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==} resolution: {integrity: sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==}
/tslib@2.7.0:
resolution: {integrity: sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==}
dev: false
/tsutils@3.21.0(typescript@5.5.4): /tsutils@3.21.0(typescript@5.5.4):
resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==}
engines: {node: '>= 6'} engines: {node: '>= 6'}
@@ -11292,7 +11303,7 @@ packages:
dependencies: dependencies:
'@types/react': 18.3.3 '@types/react': 18.3.3
react: 18.3.1 react: 18.3.1
tslib: 2.6.3 tslib: 2.7.0
dev: false dev: false
/use-debounce@10.0.2(react@18.3.1): /use-debounce@10.0.2(react@18.3.1):
@@ -11338,7 +11349,7 @@ packages:
'@types/react': 18.3.3 '@types/react': 18.3.3
detect-node-es: 1.1.0 detect-node-es: 1.1.0
react: 18.3.1 react: 18.3.1
tslib: 2.6.3 tslib: 2.7.0
dev: false dev: false
/use-sync-external-store@1.2.0(react@18.3.1): /use-sync-external-store@1.2.0(react@18.3.1):

View File

@@ -1,10 +1,10 @@
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 { import {
rasterLayerAdded,
sessionStagingAreaImageAccepted, sessionStagingAreaImageAccepted,
sessionStagingAreaReset, sessionStagingAreaReset,
} from 'features/controlLayers/store/canvasV2Slice'; } from 'features/controlLayers/store/canvasSessionSlice';
import { rasterLayerAdded } from 'features/controlLayers/store/canvasV2Slice';
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';
@@ -55,10 +55,10 @@ export const addStagingListeners = (startAppListening: AppStartListening) => {
effect: (action, api) => { effect: (action, api) => {
const { index } = action.payload; const { index } = action.payload;
const state = api.getState(); const state = api.getState();
const stagingAreaImage = state.canvasV2.session.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 } = state.canvasV2.present.bbox.rect;
const { imageDTO, offsetX, offsetY } = stagingAreaImage; const { imageDTO, offsetX, offsetY } = stagingAreaImage;
const imageObject = imageDTOToImageObject(imageDTO); const imageObject = imageDTOToImageObject(imageDTO);

View File

@@ -1,5 +1,5 @@
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware'; import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
import { setInfillMethod } from 'features/controlLayers/store/canvasV2Slice'; import { setInfillMethod } from 'features/controlLayers/store/paramsSlice';
import { shouldUseNSFWCheckerChanged, shouldUseWatermarkerChanged } from 'features/system/store/systemSlice'; import { shouldUseNSFWCheckerChanged, shouldUseWatermarkerChanged } from 'features/system/store/systemSlice';
import { appInfoApi } from 'services/api/endpoints/appInfo'; import { appInfoApi } from 'services/api/endpoints/appInfo';
@@ -8,7 +8,7 @@ export const addAppConfigReceivedListener = (startAppListening: AppStartListenin
matcher: appInfoApi.endpoints.getAppConfig.matchFulfilled, matcher: appInfoApi.endpoints.getAppConfig.matchFulfilled,
effect: (action, { getState, dispatch }) => { effect: (action, { getState, dispatch }) => {
const { infill_methods = [], nsfw_methods = [], watermarking_methods = [] } = action.payload; const { infill_methods = [], nsfw_methods = [], watermarking_methods = [] } = action.payload;
const infillMethod = getState().canvasV2.compositing.infillMethod; const infillMethod = getState().params.infillMethod;
if (!infill_methods.includes(infillMethod)) { if (!infill_methods.includes(infillMethod)) {
// if there is no infill method, set it to the first one // if there is no infill method, set it to the first one

View File

@@ -16,7 +16,7 @@ export const addDeleteBoardAndImagesFulfilledListener = (startAppListening: AppS
const { nodes, canvasV2 } = getState(); const { nodes, canvasV2 } = getState();
deleted_images.forEach((image_name) => { deleted_images.forEach((image_name) => {
const imageUsage = getImageUsage(nodes.present, canvasV2, image_name); const imageUsage = getImageUsage(nodes.present, canvasV2.present, image_name);
if (imageUsage.isNodesImage && !wasNodeEditorReset) { if (imageUsage.isNodesImage && !wasNodeEditorReset) {
dispatch(nodeEditorReset()); dispatch(nodeEditorReset());

View File

@@ -5,7 +5,7 @@ import type { SerializableObject } from 'common/types';
import type { Result } from 'common/util/result'; import type { Result } from 'common/util/result';
import { isErr, withResult, withResultAsync } from 'common/util/result'; import { isErr, withResult, withResultAsync } from 'common/util/result';
import { $canvasManager } from 'features/controlLayers/konva/CanvasManager'; import { $canvasManager } from 'features/controlLayers/konva/CanvasManager';
import { sessionStagingAreaReset, sessionStartedStaging } from 'features/controlLayers/store/canvasV2Slice'; import { sessionStagingAreaReset, sessionStartedStaging } from 'features/controlLayers/store/canvasSessionSlice';
import { prepareLinearUIBatch } from 'features/nodes/util/graph/buildLinearBatchConfig'; import { prepareLinearUIBatch } from 'features/nodes/util/graph/buildLinearBatchConfig';
import { buildSD1Graph } from 'features/nodes/util/graph/generation/buildSD1Graph'; import { buildSD1Graph } from 'features/nodes/util/graph/generation/buildSD1Graph';
import { buildSDXLGraph } from 'features/nodes/util/graph/generation/buildSDXLGraph'; import { buildSDXLGraph } from 'features/nodes/util/graph/generation/buildSDXLGraph';
@@ -23,7 +23,7 @@ export const addEnqueueRequestedLinear = (startAppListening: AppStartListening)
enqueueRequested.match(action) && action.payload.tabName === 'generation', enqueueRequested.match(action) && action.payload.tabName === 'generation',
effect: async (action, { getState, dispatch }) => { effect: async (action, { getState, dispatch }) => {
const state = getState(); const state = getState();
const model = state.canvasV2.params.model; const model = state.params.model;
const { prepend } = action.payload; const { prepend } = action.payload;
const manager = $canvasManager.get(); const manager = $canvasManager.get();
@@ -31,13 +31,13 @@ export const addEnqueueRequestedLinear = (startAppListening: AppStartListening)
let didStartStaging = false; let didStartStaging = false;
if (!state.canvasV2.session.isStaging && state.canvasV2.session.mode === 'compose') { if (!state.canvasSession.isStaging && state.canvasSession.mode === 'compose') {
dispatch(sessionStartedStaging()); dispatch(sessionStartedStaging());
didStartStaging = true; didStartStaging = true;
} }
const abortStaging = () => { const abortStaging = () => {
if (didStartStaging && getState().canvasV2.session.isStaging) { if (didStartStaging && getState().canvasSession.isStaging) {
dispatch(sessionStagingAreaReset()); dispatch(sessionStagingAreaReset());
} }
}; };

View File

@@ -29,7 +29,7 @@ export const addEnqueueRequestedNodes = (startAppListening: AppStartListening) =
batch: { batch: {
graph, graph,
workflow: builtWorkflow, workflow: builtWorkflow,
runs: state.canvasV2.params.iterations, runs: state.params.iterations,
origin: 'workflows', origin: 'workflows',
}, },
prepend: action.payload.prepend, prepend: action.payload.prepend,

View File

@@ -2,6 +2,7 @@ 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/canvasV2Slice';
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';
import { selectListImagesQueryArgs } from 'features/gallery/store/gallerySelectors'; import { selectListImagesQueryArgs } from 'features/gallery/store/gallerySelectors';
@@ -39,7 +40,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.canvasV2.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
@@ -51,15 +52,15 @@ 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(({ id, ipAdapter }) => { state.canvasV2.present.ipAdapters.entities.forEach((entity) => {
if (ipAdapter.image?.image_name === imageDTO.image_name) { if (entity.ipAdapter.image?.image_name === imageDTO.image_name) {
dispatch(ipaImageChanged({ id, imageDTO: null })); dispatch(ipaImageChanged({ entityIdentifier: getEntityIdentifier(entity), imageDTO: null }));
} }
}); });
}; };
const deleteLayerImages = (state: RootState, dispatch: AppDispatch, imageDTO: ImageDTO) => { const deleteLayerImages = (state: RootState, dispatch: AppDispatch, imageDTO: ImageDTO) => {
state.canvasV2.rasterLayers.entities.forEach(({ id, objects }) => { state.canvasV2.present.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

@@ -51,7 +51,9 @@ export const addImageDroppedListener = (startAppListening: AppStartListening) =>
activeData.payload.imageDTO activeData.payload.imageDTO
) { ) {
const { id } = overData.context; const { id } = overData.context;
dispatch(ipaImageChanged({ id, imageDTO: activeData.payload.imageDTO })); dispatch(
ipaImageChanged({ entityIdentifier: { id, type: 'ip_adapter' }, imageDTO: activeData.payload.imageDTO })
);
return; return;
} }
@@ -64,7 +66,13 @@ export const addImageDroppedListener = (startAppListening: AppStartListening) =>
activeData.payload.imageDTO activeData.payload.imageDTO
) { ) {
const { id, ipAdapterId } = overData.context; const { id, ipAdapterId } = overData.context;
dispatch(rgIPAdapterImageChanged({ id, ipAdapterId, imageDTO: activeData.payload.imageDTO })); dispatch(
rgIPAdapterImageChanged({
entityIdentifier: { id, type: 'regional_guidance' },
ipAdapterId,
imageDTO: activeData.payload.imageDTO,
})
);
return; return;
} }
@@ -77,7 +85,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 } = getState().canvasV2.present.bbox.rect;
const overrides: Partial<CanvasRasterLayerState> = { const overrides: Partial<CanvasRasterLayerState> = {
objects: [imageObject], objects: [imageObject],
position: { x, y }, position: { x, y },
@@ -95,7 +103,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 } = getState().canvasV2.present.bbox.rect;
const overrides: Partial<CanvasControlLayerState> = { const overrides: Partial<CanvasControlLayerState> = {
objects: [imageObject], objects: [imageObject],
position: { x, y }, position: { x, y },

View File

@@ -89,14 +89,16 @@ export const addImageUploadedFulfilledListener = (startAppListening: AppStartLis
if (postUploadAction?.type === 'SET_IPA_IMAGE') { if (postUploadAction?.type === 'SET_IPA_IMAGE') {
const { id } = postUploadAction; const { id } = postUploadAction;
dispatch(ipaImageChanged({ id, imageDTO })); dispatch(ipaImageChanged({ entityIdentifier: { id, type: 'ip_adapter' }, imageDTO }));
toast({ ...DEFAULT_UPLOADED_TOAST, description: t('toast.setControlImage') }); toast({ ...DEFAULT_UPLOADED_TOAST, description: t('toast.setControlImage') });
return; return;
} }
if (postUploadAction?.type === 'SET_RG_IP_ADAPTER_IMAGE') { if (postUploadAction?.type === 'SET_RG_IP_ADAPTER_IMAGE') {
const { id, ipAdapterId } = postUploadAction; const { id, ipAdapterId } = postUploadAction;
dispatch(rgIPAdapterImageChanged({ id, ipAdapterId, imageDTO })); dispatch(
rgIPAdapterImageChanged({ entityIdentifier: { id, type: 'regional_guidance' }, ipAdapterId, imageDTO })
);
toast({ ...DEFAULT_UPLOADED_TOAST, description: t('toast.setControlImage') }); toast({ ...DEFAULT_UPLOADED_TOAST, description: t('toast.setControlImage') });
return; return;
} }

View File

@@ -1,6 +1,7 @@
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 { loraDeleted, modelChanged, vaeSelected } from 'features/controlLayers/store/canvasV2Slice'; import { loraDeleted } from 'features/controlLayers/store/lorasSlice';
import { modelChanged, vaeSelected } from 'features/controlLayers/store/paramsSlice';
import { modelSelected } from 'features/parameters/store/actions'; import { modelSelected } from 'features/parameters/store/actions';
import { zParameterModel } from 'features/parameters/types/parameterSchemas'; import { zParameterModel } from 'features/parameters/types/parameterSchemas';
import { toast } from 'features/toast/toast'; import { toast } from 'features/toast/toast';
@@ -23,14 +24,14 @@ export const addModelSelectedListener = (startAppListening: AppStartListening) =
const newModel = result.data; const newModel = result.data;
const newBaseModel = newModel.base; const newBaseModel = newModel.base;
const didBaseModelChange = state.canvasV2.params.model?.base !== newBaseModel; const didBaseModelChange = state.params.model?.base !== newBaseModel;
if (didBaseModelChange) { if (didBaseModelChange) {
// we may need to reset some incompatible submodels // we may need to reset some incompatible submodels
let modelsCleared = 0; let modelsCleared = 0;
// handle incompatible loras // handle incompatible loras
state.canvasV2.loras.forEach((lora) => { state.loras.loras.forEach((lora) => {
if (lora.model.base !== newBaseModel) { if (lora.model.base !== newBaseModel) {
dispatch(loraDeleted({ id: lora.id })); dispatch(loraDeleted({ id: lora.id }));
modelsCleared += 1; modelsCleared += 1;
@@ -38,14 +39,14 @@ export const addModelSelectedListener = (startAppListening: AppStartListening) =
}); });
// handle incompatible vae // handle incompatible vae
const { vae } = state.canvasV2.params; const { vae } = state.params;
if (vae && vae.base !== newBaseModel) { if (vae && vae.base !== newBaseModel) {
dispatch(vaeSelected(null)); dispatch(vaeSelected(null));
modelsCleared += 1; modelsCleared += 1;
} }
// handle incompatible controlnets // handle incompatible controlnets
// state.canvasV2.controlAdapters.entities.forEach((ca) => { // state.canvasV2.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) {
@@ -66,7 +67,7 @@ export const addModelSelectedListener = (startAppListening: AppStartListening) =
} }
} }
dispatch(modelChanged({ model: newModel, previousModel: state.canvasV2.params.model })); dispatch(modelChanged({ model: newModel, previousModel: state.params.model }));
}, },
}); });
}; };

View File

@@ -7,12 +7,11 @@ import {
bboxWidthChanged, bboxWidthChanged,
controlLayerModelChanged, controlLayerModelChanged,
ipaModelChanged, ipaModelChanged,
loraDeleted,
modelChanged,
refinerModelChanged,
rgIPAdapterModelChanged, rgIPAdapterModelChanged,
vaeSelected,
} from 'features/controlLayers/store/canvasV2Slice'; } from 'features/controlLayers/store/canvasV2Slice';
import { loraDeleted } from 'features/controlLayers/store/lorasSlice';
import { modelChanged, refinerModelChanged, vaeSelected } from 'features/controlLayers/store/paramsSlice';
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';
import { zParameterModel, zParameterVAEModel } from 'features/parameters/types/parameterSchemas'; import { zParameterModel, zParameterVAEModel } from 'features/parameters/types/parameterSchemas';
@@ -62,7 +61,7 @@ type ModelHandler = (
) => undefined; ) => undefined;
const handleMainModels: ModelHandler = (models, state, dispatch, log) => { const handleMainModels: ModelHandler = (models, state, dispatch, log) => {
const currentModel = state.canvasV2.params.model; const currentModel = state.params.model;
const mainModels = models.filter(isNonRefinerMainModelConfig); const mainModels = models.filter(isNonRefinerMainModelConfig);
if (mainModels.length === 0) { if (mainModels.length === 0) {
// No models loaded at all // No models loaded at all
@@ -84,11 +83,17 @@ const handleMainModels: ModelHandler = (models, state, dispatch, log) => {
dispatch(modelChanged({ model: defaultModelInList, previousModel: currentModel })); dispatch(modelChanged({ model: defaultModelInList, previousModel: currentModel }));
const optimalDimension = getOptimalDimension(defaultModelInList); const optimalDimension = getOptimalDimension(defaultModelInList);
if (getIsSizeOptimal(state.canvasV2.bbox.rect.width, state.canvasV2.bbox.rect.height, optimalDimension)) { if (
getIsSizeOptimal(
state.canvasV2.present.bbox.rect.width,
state.canvasV2.present.bbox.rect.height,
optimalDimension
)
) {
return; return;
} }
const { width, height } = calculateNewSize( const { width, height } = calculateNewSize(
state.canvasV2.bbox.aspectRatio.value, state.canvasV2.present.bbox.aspectRatio.value,
optimalDimension * optimalDimension optimalDimension * optimalDimension
); );
@@ -109,7 +114,7 @@ const handleMainModels: ModelHandler = (models, state, dispatch, log) => {
}; };
const handleRefinerModels: ModelHandler = (models, state, dispatch, _log) => { const handleRefinerModels: ModelHandler = (models, state, dispatch, _log) => {
const currentRefinerModel = state.canvasV2.params.refinerModel; const currentRefinerModel = state.params.refinerModel;
const refinerModels = models.filter(isRefinerMainModelModelConfig); const refinerModels = models.filter(isRefinerMainModelModelConfig);
if (models.length === 0) { if (models.length === 0) {
// No models loaded at all // No models loaded at all
@@ -128,7 +133,7 @@ const handleRefinerModels: ModelHandler = (models, state, dispatch, _log) => {
}; };
const handleVAEModels: ModelHandler = (models, state, dispatch, log) => { const handleVAEModels: ModelHandler = (models, state, dispatch, log) => {
const currentVae = state.canvasV2.params.vae; const currentVae = state.params.vae;
if (currentVae === null) { if (currentVae === null) {
// null is a valid VAE! it means "use the default with the main model" // null is a valid VAE! it means "use the default with the main model"
@@ -162,7 +167,7 @@ const handleVAEModels: ModelHandler = (models, state, dispatch, log) => {
const handleLoRAModels: ModelHandler = (models, state, dispatch, _log) => { const handleLoRAModels: ModelHandler = (models, state, dispatch, _log) => {
const loraModels = models.filter(isLoRAModelConfig); const loraModels = models.filter(isLoRAModelConfig);
state.canvasV2.loras.forEach((lora) => { state.loras.loras.forEach((lora) => {
const isLoRAAvailable = loraModels.some((m) => m.key === lora.model.key); const isLoRAAvailable = loraModels.some((m) => m.key === lora.model.key);
if (isLoRAAvailable) { if (isLoRAAvailable) {
return; return;
@@ -173,32 +178,34 @@ 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) => { state.canvasV2.present.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;
} }
dispatch(controlLayerModelChanged({ id: entity.id, modelConfig: null })); dispatch(controlLayerModelChanged({ entityIdentifier: getEntityIdentifier(entity), modelConfig: null }));
}); });
}; };
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) => { state.canvasV2.present.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;
} }
dispatch(ipaModelChanged({ id: entity.id, modelConfig: null })); dispatch(ipaModelChanged({ entityIdentifier: getEntityIdentifier(entity), modelConfig: null }));
}); });
state.canvasV2.regions.entities.forEach(({ id, ipAdapters }) => { state.canvasV2.present.regions.entities.forEach((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) {
return; return;
} }
dispatch(rgIPAdapterModelChanged({ id, ipAdapterId, modelConfig: null })); dispatch(
rgIPAdapterModelChanged({ entityIdentifier: getEntityIdentifier(entity), ipAdapterId, modelConfig: null })
);
}); });
}); });
}; };

View File

@@ -1,6 +1,6 @@
import { isAnyOf } from '@reduxjs/toolkit'; import { isAnyOf } from '@reduxjs/toolkit';
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware'; import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
import { positivePromptChanged } from 'features/controlLayers/store/canvasV2Slice'; import { positivePromptChanged } from 'features/controlLayers/store/paramsSlice';
import { import {
combinatorialToggled, combinatorialToggled,
isErrorChanged, isErrorChanged,

View File

@@ -1,14 +1,13 @@
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 { import {
bboxHeightChanged,
bboxWidthChanged,
setCfgRescaleMultiplier, setCfgRescaleMultiplier,
setCfgScale, setCfgScale,
setScheduler, setScheduler,
setSteps, setSteps,
vaePrecisionChanged, vaePrecisionChanged,
vaeSelected, vaeSelected,
} from 'features/controlLayers/store/canvasV2Slice'; } from 'features/controlLayers/store/paramsSlice';
import { setDefaultSettings } from 'features/parameters/store/actions'; import { setDefaultSettings } from 'features/parameters/store/actions';
import { import {
isParameterCFGRescaleMultiplier, isParameterCFGRescaleMultiplier,
@@ -31,7 +30,7 @@ export const addSetDefaultSettingsListener = (startAppListening: AppStartListeni
effect: async (action, { dispatch, getState }) => { effect: async (action, { dispatch, getState }) => {
const state = getState(); const state = getState();
const currentModel = state.canvasV2.params.model; const currentModel = state.params.model;
if (!currentModel) { if (!currentModel) {
return; return;

View File

@@ -6,7 +6,12 @@ import { errorHandler } from 'app/store/enhancers/reduxRemember/errors';
import type { SerializableObject } from 'common/types'; import type { SerializableObject } from 'common/types';
import { deepClone } from 'common/util/deepClone'; 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 { canvasSettingsPersistConfig, canvasSettingsSlice } from 'features/controlLayers/store/canvasSettingsSlice';
import { canvasV2PersistConfig, canvasV2Slice } from 'features/controlLayers/store/canvasV2Slice'; import { canvasV2PersistConfig, canvasV2Slice } from 'features/controlLayers/store/canvasV2Slice';
import { lorasPersistConfig, lorasSlice } from 'features/controlLayers/store/lorasSlice';
import { paramsPersistConfig, paramsSlice } from 'features/controlLayers/store/paramsSlice';
import { toolPersistConfig, toolSlice } from 'features/controlLayers/store/toolSlice';
import { deleteImageModalSlice } from 'features/deleteImageModal/store/slice'; import { deleteImageModalSlice } from 'features/deleteImageModal/store/slice';
import { dynamicPromptsPersistConfig, dynamicPromptsSlice } from 'features/dynamicPrompts/store/dynamicPromptsSlice'; import { dynamicPromptsPersistConfig, dynamicPromptsSlice } from 'features/dynamicPrompts/store/dynamicPromptsSlice';
import { galleryPersistConfig, gallerySlice } from 'features/gallery/store/gallerySlice'; import { galleryPersistConfig, gallerySlice } from 'features/gallery/store/gallerySlice';
@@ -53,10 +58,15 @@ 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, [canvasV2Slice.name]: undoable(canvasV2Slice.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,
[paramsSlice.name]: paramsSlice.reducer,
[toolSlice.name]: toolSlice.reducer,
[canvasSettingsSlice.name]: canvasSettingsSlice.reducer,
[canvasSessionSlice.name]: canvasSessionSlice.reducer,
[lorasSlice.name]: lorasSlice.reducer,
}; };
const rootReducer = combineReducers(allReducers); const rootReducer = combineReducers(allReducers);
@@ -98,6 +108,11 @@ const persistConfigs: { [key in keyof typeof allReducers]?: PersistConfig } = {
[workflowSettingsPersistConfig.name]: workflowSettingsPersistConfig, [workflowSettingsPersistConfig.name]: workflowSettingsPersistConfig,
[upscalePersistConfig.name]: upscalePersistConfig, [upscalePersistConfig.name]: upscalePersistConfig,
[stylePresetPersistConfig.name]: stylePresetPersistConfig, [stylePresetPersistConfig.name]: stylePresetPersistConfig,
[paramsPersistConfig.name]: paramsPersistConfig,
[toolPersistConfig.name]: toolPersistConfig,
[canvasSettingsPersistConfig.name]: canvasSettingsPersistConfig,
[canvasSessionPersistConfig.name]: canvasSessionPersistConfig,
[lorasPersistConfig.name]: lorasPersistConfig,
}; };
const unserialize: UnserializeFunction = (data, key) => { const unserialize: UnserializeFunction = (data, key) => {

View File

@@ -32,7 +32,7 @@ export const useGroupedModelCombobox = <T extends AnyModelConfig>(
arg: UseGroupedModelComboboxArg<T> arg: UseGroupedModelComboboxArg<T>
): UseGroupedModelComboboxReturn => { ): UseGroupedModelComboboxReturn => {
const { t } = useTranslation(); const { t } = useTranslation();
const base_model = useAppSelector((s) => s.canvasV2.params.model?.base ?? 'sdxl'); const base_model = useAppSelector((s) => s.params.model?.base ?? 'sdxl');
const { modelConfigs, selectedModel, getIsDisabled, onChange, isLoading, groupByType = false } = arg; const { modelConfigs, selectedModel, getIsDisabled, onChange, isLoading, groupByType = false } = arg;
const options = useMemo<GroupBase<ComboboxOption>[]>(() => { const options = useMemo<GroupBase<ComboboxOption>[]>(() => {
if (!modelConfigs) { if (!modelConfigs) {

View File

@@ -2,7 +2,8 @@ import { useStore } from '@nanostores/react';
import { $isConnected } from 'app/hooks/useSocketIO'; 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 { selectCanvasV2Slice } from 'features/controlLayers/store/canvasV2Slice'; import { selectParamsSlice } from 'features/controlLayers/store/paramsSlice';
import { selectCanvasV2Slice } 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,13 +35,14 @@ const createSelector = (templates: Templates, isConnected: boolean) =>
selectWorkflowSettingsSlice, selectWorkflowSettingsSlice,
selectDynamicPromptsSlice, selectDynamicPromptsSlice,
selectCanvasV2Slice, selectCanvasV2Slice,
selectParamsSlice,
selectUpscalelice, selectUpscalelice,
selectConfigSlice, selectConfigSlice,
selectActiveTab, selectActiveTab,
], ],
(system, nodes, workflowSettings, dynamicPrompts, canvasV2, upscale, config, activeTabName) => { (system, nodes, workflowSettings, dynamicPrompts, canvasV2, params, upscale, config, activeTabName) => {
const { bbox } = canvasV2; const { bbox } = canvasV2;
const { model, positivePrompt } = canvasV2.params; const { model, positivePrompt } = params;
const reasons: { prefix?: string; content: string }[] = []; const reasons: { prefix?: string; content: string }[] = [];

View File

@@ -11,7 +11,7 @@ import { memo } from 'react';
export const CanvasEntityList = memo(() => { export const CanvasEntityList = memo(() => {
return ( return (
<ScrollableContent> <ScrollableContent>
<Flex flexDir="column" gap={4} pt={2} data-testid="control-layers-layer-list"> <Flex flexDir="column" gap={4} pt={2} data-testid="control-layers-layer-list" w="full" h="full">
<CanvasEntityOpacity /> <CanvasEntityOpacity />
<InpaintMaskList /> <InpaintMaskList />
<RegionalGuidanceEntityList /> <RegionalGuidanceEntityList />

View File

@@ -0,0 +1,27 @@
import { IconButton, Menu, MenuButton, MenuList } from '@invoke-ai/ui-library';
import { CanvasEntityListMenuItems } from 'features/controlLayers/components/CanvasEntityList/CanvasEntityListMenuItems';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
import { PiDotsThreeOutlineFill } from 'react-icons/pi';
export const CanvasEntityListMenuButton = memo(() => {
const { t } = useTranslation();
return (
<Menu>
<MenuButton
as={IconButton}
aria-label={t('accessibility.menu')}
icon={<PiDotsThreeOutlineFill />}
variant="link"
data-testid="control-layers-add-layer-menu-button"
alignSelf="stretch"
/>
<MenuList>
<CanvasEntityListMenuItems />
</MenuList>
</Menu>
);
});
CanvasEntityListMenuButton.displayName = 'CanvasEntityListMenuButton';

View File

@@ -0,0 +1,67 @@
import { MenuDivider, MenuItem } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import {
allEntitiesDeleted,
controlLayerAdded,
inpaintMaskAdded,
ipaAdded,
rasterLayerAdded,
rgAdded,
} from 'features/controlLayers/store/canvasV2Slice';
import { selectEntityCount } from 'features/controlLayers/store/selectors';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { PiPlusBold, PiTrashSimpleBold } from 'react-icons/pi';
export const CanvasEntityListMenuItems = memo(() => {
const { t } = useTranslation();
const dispatch = useAppDispatch();
const hasEntities = useAppSelector((s) => {
const count = selectEntityCount(s);
return count > 0;
});
const addInpaintMask = useCallback(() => {
dispatch(inpaintMaskAdded({ isSelected: true }));
}, [dispatch]);
const addRegionalGuidance = useCallback(() => {
dispatch(rgAdded({ isSelected: true }));
}, [dispatch]);
const addRasterLayer = useCallback(() => {
dispatch(rasterLayerAdded({ isSelected: true }));
}, [dispatch]);
const addControlLayer = useCallback(() => {
dispatch(controlLayerAdded({ isSelected: true }));
}, [dispatch]);
const addIPAdapter = useCallback(() => {
dispatch(ipaAdded({ isSelected: true }));
}, [dispatch]);
const deleteAll = useCallback(() => {
dispatch(allEntitiesDeleted());
}, [dispatch]);
return (
<>
<MenuItem icon={<PiPlusBold />} onClick={addInpaintMask}>
{t('controlLayers.inpaintMask', { count: 1 })}
</MenuItem>
<MenuItem icon={<PiPlusBold />} onClick={addRegionalGuidance}>
{t('controlLayers.regionalGuidance', { count: 1 })}
</MenuItem>
<MenuItem icon={<PiPlusBold />} onClick={addRasterLayer}>
{t('controlLayers.rasterLayer', { count: 1 })}
</MenuItem>
<MenuItem icon={<PiPlusBold />} onClick={addControlLayer}>
{t('controlLayers.controlLayer', { count: 1 })}
</MenuItem>
<MenuItem icon={<PiPlusBold />} onClick={addIPAdapter}>
{t('controlLayers.ipAdapter', { count: 1 })}
</MenuItem>
<MenuDivider />
<MenuItem onClick={deleteAll} icon={<PiTrashSimpleBold />} color="error.300" isDisabled={!hasEntities}>
{t('controlLayers.deleteAll', { count: 1 })}
</MenuItem>
</>
);
});
CanvasEntityListMenuItems.displayName = 'CanvasEntityListMenu';

View File

@@ -1,77 +0,0 @@
import { IconButton, Menu, MenuButton, MenuDivider, MenuItem, MenuList } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import {
allEntitiesDeleted,
controlLayerAdded,
inpaintMaskAdded,
ipaAdded,
rasterLayerAdded,
rgAdded,
} from 'features/controlLayers/store/canvasV2Slice';
import { selectEntityCount } from 'features/controlLayers/store/selectors';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { PiDotsThreeOutlineFill, PiPlusBold, PiTrashSimpleBold } from 'react-icons/pi';
export const CanvasEntityListMenu = memo(() => {
const { t } = useTranslation();
const dispatch = useAppDispatch();
const hasEntities = useAppSelector((s) => {
const count = selectEntityCount(s);
return count > 0;
});
const addInpaintMask = useCallback(() => {
dispatch(inpaintMaskAdded({ isSelected: true }));
}, [dispatch]);
const addRegionalGuidance = useCallback(() => {
dispatch(rgAdded({ isSelected: true }));
}, [dispatch]);
const addRasterLayer = useCallback(() => {
dispatch(rasterLayerAdded({ isSelected: true }));
}, [dispatch]);
const addControlLayer = useCallback(() => {
dispatch(controlLayerAdded({ isSelected: true }));
}, [dispatch]);
const addIPAdapter = useCallback(() => {
dispatch(ipaAdded({ isSelected: true }));
}, [dispatch]);
const deleteAll = useCallback(() => {
dispatch(allEntitiesDeleted());
}, [dispatch]);
return (
<Menu>
<MenuButton
as={IconButton}
aria-label={t('accessibility.menu')}
icon={<PiDotsThreeOutlineFill />}
variant="link"
data-testid="control-layers-add-layer-menu-button"
alignSelf="stretch"
/>
<MenuList>
<MenuItem icon={<PiPlusBold />} onClick={addInpaintMask}>
{t('controlLayers.inpaintMask', { count: 1 })}
</MenuItem>
<MenuItem icon={<PiPlusBold />} onClick={addRegionalGuidance}>
{t('controlLayers.regionalGuidance', { count: 1 })}
</MenuItem>
<MenuItem icon={<PiPlusBold />} onClick={addRasterLayer}>
{t('controlLayers.rasterLayer', { count: 1 })}
</MenuItem>
<MenuItem icon={<PiPlusBold />} onClick={addControlLayer}>
{t('controlLayers.controlLayer', { count: 1 })}
</MenuItem>
<MenuItem icon={<PiPlusBold />} onClick={addIPAdapter}>
{t('controlLayers.ipAdapter', { count: 1 })}
</MenuItem>
<MenuDivider />
<MenuItem onClick={deleteAll} icon={<PiTrashSimpleBold />} color="error.300" isDisabled={!hasEntities}>
{t('controlLayers.deleteAll', { count: 1 })}
</MenuItem>
</MenuList>
</Menu>
);
});
CanvasEntityListMenu.displayName = 'CanvasEntityListMenu';

View File

@@ -1,13 +1,13 @@
import { Button, ButtonGroup } from '@invoke-ai/ui-library'; import { Button, ButtonGroup } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { sessionModeChanged } from 'features/controlLayers/store/canvasV2Slice'; import { sessionModeChanged } from 'features/controlLayers/store/canvasSessionSlice';
import { memo, useCallback } from 'react'; import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
export const CanvasModeSwitcher = memo(() => { export const CanvasModeSwitcher = memo(() => {
const { t } = useTranslation(); const { t } = useTranslation();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const mode = useAppSelector((s) => s.canvasV2.session.mode); const mode = useAppSelector((s) => s.canvasSession.mode);
const onClickGenerate = useCallback(() => dispatch(sessionModeChanged({ mode: 'generate' })), [dispatch]); const onClickGenerate = useCallback(() => dispatch(sessionModeChanged({ mode: 'generate' })), [dispatch]);
const onClickCompose = useCallback(() => dispatch(sessionModeChanged({ mode: 'compose' })), [dispatch]); const onClickCompose = useCallback(() => dispatch(sessionModeChanged({ mode: 'compose' })), [dispatch]);

View File

@@ -1,16 +1,32 @@
import { Box, ContextMenu, MenuList } from '@invoke-ai/ui-library';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { CanvasAddEntityButtons } from 'features/controlLayers/components/CanvasAddEntityButtons'; import { CanvasAddEntityButtons } from 'features/controlLayers/components/CanvasAddEntityButtons';
import { CanvasEntityList } from 'features/controlLayers/components/CanvasEntityList'; import { CanvasEntityList } from 'features/controlLayers/components/CanvasEntityList/CanvasEntityList';
import { CanvasEntityListMenuItems } from 'features/controlLayers/components/CanvasEntityList/CanvasEntityListMenuItems';
import { CanvasManagerProviderGate } from 'features/controlLayers/contexts/CanvasManagerProviderGate'; import { CanvasManagerProviderGate } from 'features/controlLayers/contexts/CanvasManagerProviderGate';
import { selectEntityCount } from 'features/controlLayers/store/selectors'; import { selectEntityCount } from 'features/controlLayers/store/selectors';
import { memo } from 'react'; import { memo, useCallback } from 'react';
export const CanvasPanelContent = memo(() => { export const CanvasPanelContent = memo(() => {
const hasEntities = useAppSelector((s) => selectEntityCount(s) > 0); const hasEntities = useAppSelector((s) => selectEntityCount(s) > 0);
const renderMenu = useCallback(
() => (
<MenuList>
<CanvasEntityListMenuItems />
</MenuList>
),
[]
);
return ( return (
<CanvasManagerProviderGate> <CanvasManagerProviderGate>
{!hasEntities && <CanvasAddEntityButtons />} <ContextMenu<HTMLDivElement> renderMenu={renderMenu}>
{hasEntities && <CanvasEntityList />} {(ref) => (
<Box ref={ref} w="full" h="full">
{!hasEntities && <CanvasAddEntityButtons />}
{hasEntities && <CanvasEntityList />}
</Box>
)}
</ContextMenu>
</CanvasManagerProviderGate> </CanvasManagerProviderGate>
); );
}); });

View File

@@ -14,10 +14,9 @@ import {
PopoverTrigger, PopoverTrigger,
} from '@invoke-ai/ui-library'; } from '@invoke-ai/ui-library';
import { useStore } from '@nanostores/react'; import { useStore } from '@nanostores/react';
import { $canvasManager } from 'features/controlLayers/konva/CanvasManager'; import { useCanvasManager } from 'features/controlLayers/contexts/CanvasManagerProviderGate';
import { MAX_CANVAS_SCALE, MIN_CANVAS_SCALE } from 'features/controlLayers/konva/constants'; import { MAX_CANVAS_SCALE, MIN_CANVAS_SCALE } from 'features/controlLayers/konva/constants';
import { snapToNearest } from 'features/controlLayers/konva/util'; import { snapToNearest } from 'features/controlLayers/konva/util';
import { $stageAttrs } from 'features/controlLayers/store/canvasV2Slice';
import { clamp, round } from 'lodash-es'; import { clamp, round } from 'lodash-es';
import { computed } from 'nanostores'; import { computed } from 'nanostores';
import type { KeyboardEvent } from 'react'; import type { KeyboardEvent } from 'react';
@@ -72,12 +71,10 @@ const sliderDefaultValue = mapScaleToSliderValue(100);
const snapCandidates = marks.slice(1, marks.length - 1); const snapCandidates = marks.slice(1, marks.length - 1);
const $scale = computed($stageAttrs, (attrs) => attrs.scale);
export const CanvasScale = memo(() => { export const CanvasScale = memo(() => {
const { t } = useTranslation(); const { t } = useTranslation();
const canvasManager = useStore($canvasManager); const canvasManager = useCanvasManager();
const scale = useStore($scale); const scale = useStore(computed(canvasManager.stateApi.$stageAttrs, (attrs) => attrs.scale));
const [localScale, setLocalScale] = useState(scale * 100); const [localScale, setLocalScale] = useState(scale * 100);
const onChangeSlider = useCallback( const onChangeSlider = useCallback(

View File

@@ -1,15 +1,15 @@
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 { selectControlLayerEntityOrThrow } from 'features/controlLayers/store/controlLayersReducers'; import { selectEntityOrThrow } from 'features/controlLayers/store/selectors';
import { memo } from 'react'; import { memo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
export const ControlLayerBadges = memo(() => { export const ControlLayerBadges = memo(() => {
const { id } = useEntityIdentifierContext(); const entityIdentifier = useEntityIdentifierContext('control_layer');
const { t } = useTranslation(); const { t } = useTranslation();
const withTransparencyEffect = useAppSelector( const withTransparencyEffect = useAppSelector(
(s) => selectControlLayerEntityOrThrow(s.canvasV2, id).withTransparencyEffect (s) => selectEntityOrThrow(s.canvasV2.present, entityIdentifier).withTransparencyEffect
); );
return ( return (

View File

@@ -18,35 +18,35 @@ import type { ControlNetModelConfig, T2IAdapterModelConfig } from 'services/api/
export const ControlLayerControlAdapter = memo(() => { export const ControlLayerControlAdapter = memo(() => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const entityIdentifier = useEntityIdentifierContext(); const entityIdentifier = useEntityIdentifierContext('control_layer');
const controlAdapter = useControlLayerControlAdapter(entityIdentifier); const controlAdapter = useControlLayerControlAdapter(entityIdentifier);
const onChangeBeginEndStepPct = useCallback( const onChangeBeginEndStepPct = useCallback(
(beginEndStepPct: [number, number]) => { (beginEndStepPct: [number, number]) => {
dispatch(controlLayerBeginEndStepPctChanged({ id: entityIdentifier.id, beginEndStepPct })); dispatch(controlLayerBeginEndStepPctChanged({ entityIdentifier, beginEndStepPct }));
}, },
[dispatch, entityIdentifier.id] [dispatch, entityIdentifier]
); );
const onChangeControlMode = useCallback( const onChangeControlMode = useCallback(
(controlMode: ControlModeV2) => { (controlMode: ControlModeV2) => {
dispatch(controlLayerControlModeChanged({ id: entityIdentifier.id, controlMode })); dispatch(controlLayerControlModeChanged({ entityIdentifier, controlMode }));
}, },
[dispatch, entityIdentifier.id] [dispatch, entityIdentifier]
); );
const onChangeWeight = useCallback( const onChangeWeight = useCallback(
(weight: number) => { (weight: number) => {
dispatch(controlLayerWeightChanged({ id: entityIdentifier.id, weight })); dispatch(controlLayerWeightChanged({ entityIdentifier, weight }));
}, },
[dispatch, entityIdentifier.id] [dispatch, entityIdentifier]
); );
const onChangeModel = useCallback( const onChangeModel = useCallback(
(modelConfig: ControlNetModelConfig | T2IAdapterModelConfig) => { (modelConfig: ControlNetModelConfig | T2IAdapterModelConfig) => {
dispatch(controlLayerModelChanged({ id: entityIdentifier.id, modelConfig })); dispatch(controlLayerModelChanged({ entityIdentifier, modelConfig }));
}, },
[dispatch, entityIdentifier.id] [dispatch, entityIdentifier]
); );
return ( return (

View File

@@ -18,7 +18,7 @@ export const ControlLayerControlAdapterModel = memo(({ modelKey, onChange: onCha
const { t } = useTranslation(); const { t } = useTranslation();
const entityIdentifier = useEntityIdentifierContext(); const entityIdentifier = useEntityIdentifierContext();
const canvasManager = useCanvasManager(); const canvasManager = useCanvasManager();
const currentBaseModel = useAppSelector((s) => s.canvasV2.params.model?.base); const currentBaseModel = useAppSelector((s) => s.params.model?.base);
const [modelConfigs, { isLoading }] = useControlNetAndT2IAdapterModels(); const [modelConfigs, { isLoading }] = useControlNetAndT2IAdapterModels();
const selectedModel = useMemo(() => modelConfigs.find((m) => m.key === modelKey), [modelConfigs, modelKey]); const selectedModel = useMemo(() => modelConfigs.find((m) => m.key === modelKey), [modelConfigs, modelKey]);

View File

@@ -3,7 +3,7 @@ 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/canvasV2Slice'; import { selectCanvasV2Slice } from 'features/controlLayers/store/selectors';
import { memo } from 'react'; import { memo } from 'react';
const selectEntityIds = createMemoizedSelector(selectCanvasV2Slice, (canvasV2) => { const selectEntityIds = createMemoizedSelector(selectCanvasV2Slice, (canvasV2) => {
@@ -11,7 +11,9 @@ const selectEntityIds = createMemoizedSelector(selectCanvasV2Slice, (canvasV2) =
}); });
export const ControlLayerEntityList = memo(() => { export const ControlLayerEntityList = memo(() => {
const isSelected = useAppSelector((s) => Boolean(s.canvasV2.selectedEntityIdentifier?.type === 'control_layer')); const isSelected = useAppSelector((s) =>
Boolean(s.canvasV2.present.selectedEntityIdentifier?.type === 'control_layer')
);
const layerIds = useAppSelector(selectEntityIds); const layerIds = useAppSelector(selectEntityIds);
if (layerIds.length === 0) { if (layerIds.length === 0) {

View File

@@ -9,11 +9,11 @@ import { PiLightningBold } from 'react-icons/pi';
export const ControlLayerMenuItemsControlToRaster = memo(() => { export const ControlLayerMenuItemsControlToRaster = memo(() => {
const { t } = useTranslation(); const { t } = useTranslation();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const entityIdentifier = useEntityIdentifierContext(); const entityIdentifier = useEntityIdentifierContext('control_layer');
const convertControlLayerToRasterLayer = useCallback(() => { const convertControlLayerToRasterLayer = useCallback(() => {
dispatch(controlLayerConvertedToRasterLayer({ id: entityIdentifier.id })); dispatch(controlLayerConvertedToRasterLayer({ entityIdentifier }));
}, [dispatch, entityIdentifier.id]); }, [dispatch, entityIdentifier]);
return ( return (
<MenuItem onClick={convertControlLayerToRasterLayer} icon={<PiLightningBold />}> <MenuItem onClick={convertControlLayerToRasterLayer} icon={<PiLightningBold />}>

View File

@@ -2,11 +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 { import { controlLayerWithTransparencyEffectToggled } from 'features/controlLayers/store/canvasV2Slice';
controlLayerWithTransparencyEffectToggled, import { selectCanvasV2Slice, selectEntityOrThrow } from 'features/controlLayers/store/selectors';
selectCanvasV2Slice,
} from 'features/controlLayers/store/canvasV2Slice';
import { selectControlLayerEntityOrThrow } from 'features/controlLayers/store/controlLayersReducers';
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,18 +11,18 @@ import { PiDropHalfBold } from 'react-icons/pi';
export const ControlLayerMenuItemsTransparencyEffect = memo(() => { export const ControlLayerMenuItemsTransparencyEffect = memo(() => {
const { t } = useTranslation(); const { t } = useTranslation();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const entityIdentifier = useEntityIdentifierContext(); const entityIdentifier = useEntityIdentifierContext('control_layer');
const selectWithTransparencyEffect = useMemo( const selectWithTransparencyEffect = useMemo(
() => () =>
createSelector(selectCanvasV2Slice, (canvasV2) => { createSelector(selectCanvasV2Slice, (canvasV2) => {
const entity = selectControlLayerEntityOrThrow(canvasV2, entityIdentifier.id); const entity = selectEntityOrThrow(canvasV2, entityIdentifier);
return entity.withTransparencyEffect; return entity.withTransparencyEffect;
}), }),
[entityIdentifier.id] [entityIdentifier]
); );
const withTransparencyEffect = useAppSelector(selectWithTransparencyEffect); const withTransparencyEffect = useAppSelector(selectWithTransparencyEffect);
const onToggle = useCallback(() => { const onToggle = useCallback(() => {
dispatch(controlLayerWithTransparencyEffectToggled({ id: entityIdentifier.id })); dispatch(controlLayerWithTransparencyEffectToggled({ entityIdentifier }));
}, [dispatch, entityIdentifier]); }, [dispatch, entityIdentifier]);
return ( return (

View File

@@ -33,9 +33,11 @@ export const CanvasEditor = memo(() => {
<ControlLayersToolbar /> <ControlLayersToolbar />
<StageComponent /> <StageComponent />
<Flex position="absolute" bottom={16} gap={2} align="center" justify="center"> <Flex position="absolute" bottom={16} gap={2} align="center" justify="center">
<StagingAreaIsStagingGate> <CanvasManagerProviderGate>
<StagingAreaToolbar /> <StagingAreaIsStagingGate>
</StagingAreaIsStagingGate> <StagingAreaToolbar />
</StagingAreaIsStagingGate>
</CanvasManagerProviderGate>
</Flex> </Flex>
<Flex position="absolute" bottom={16}> <Flex position="absolute" bottom={16}>
<CanvasManagerProviderGate> <CanvasManagerProviderGate>

View File

@@ -1,14 +1,12 @@
/* eslint-disable i18next/no-literal-string */ /* eslint-disable i18next/no-literal-string */
import { Flex, Spacer } from '@invoke-ai/ui-library'; import { Flex, Spacer } from '@invoke-ai/ui-library';
import { useAppSelector } from 'app/store/storeHooks';
import { CanvasModeSwitcher } from 'features/controlLayers/components/CanvasModeSwitcher'; import { CanvasModeSwitcher } from 'features/controlLayers/components/CanvasModeSwitcher';
import { CanvasResetViewButton } from 'features/controlLayers/components/CanvasResetViewButton'; import { CanvasResetViewButton } from 'features/controlLayers/components/CanvasResetViewButton';
import { CanvasScale } from 'features/controlLayers/components/CanvasScale'; import { CanvasScale } from 'features/controlLayers/components/CanvasScale';
import { CanvasSettingsPopover } from 'features/controlLayers/components/Settings/CanvasSettingsPopover'; import { CanvasSettingsPopover } from 'features/controlLayers/components/Settings/CanvasSettingsPopover';
import { ToolBrushWidth } from 'features/controlLayers/components/Tool/ToolBrushWidth';
import { ToolChooser } from 'features/controlLayers/components/Tool/ToolChooser'; import { ToolChooser } from 'features/controlLayers/components/Tool/ToolChooser';
import { ToolEraserWidth } from 'features/controlLayers/components/Tool/ToolEraserWidth';
import { ToolFillColorPicker } from 'features/controlLayers/components/Tool/ToolFillColorPicker'; import { ToolFillColorPicker } from 'features/controlLayers/components/Tool/ToolFillColorPicker';
import { ToolSettings } from 'features/controlLayers/components/Tool/ToolSettings';
import { UndoRedoButtonGroup } from 'features/controlLayers/components/UndoRedoButtonGroup'; import { UndoRedoButtonGroup } from 'features/controlLayers/components/UndoRedoButtonGroup';
import { CanvasManagerProviderGate } from 'features/controlLayers/contexts/CanvasManagerProviderGate'; import { CanvasManagerProviderGate } from 'features/controlLayers/contexts/CanvasManagerProviderGate';
import { ToggleProgressButton } from 'features/gallery/components/ImageViewer/ToggleProgressButton'; import { ToggleProgressButton } from 'features/gallery/components/ImageViewer/ToggleProgressButton';
@@ -16,15 +14,13 @@ import { ViewerToggleMenu } from 'features/gallery/components/ImageViewer/Viewer
import { memo } from 'react'; import { memo } from 'react';
export const ControlLayersToolbar = memo(() => { export const ControlLayersToolbar = memo(() => {
const tool = useAppSelector((s) => s.canvasV2.tool.selected);
return ( return (
<CanvasManagerProviderGate> <CanvasManagerProviderGate>
<Flex w="full" gap={2} alignItems="center"> <Flex w="full" gap={2} alignItems="center">
<ToggleProgressButton /> <ToggleProgressButton />
<ToolChooser /> <ToolChooser />
<Spacer /> <Spacer />
{tool === 'brush' && <ToolBrushWidth />} <ToolSettings />
{tool === 'eraser' && <ToolEraserWidth />}
<Spacer /> <Spacer />
<CanvasScale /> <CanvasScale />
<CanvasResetViewButton /> <CanvasResetViewButton />

View File

@@ -1,25 +1,19 @@
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 { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { import { useCanvasManager } from 'features/controlLayers/contexts/CanvasManagerProviderGate';
$isDrawing,
$isMouseDown,
$lastAddedPoint,
$lastCursorPos,
$lastMouseDownPos,
$stageAttrs,
} from 'features/controlLayers/store/canvasV2Slice';
import { round } from 'lodash-es'; import { round } from 'lodash-es';
import { memo } from 'react'; import { memo } from 'react';
export const HeadsUpDisplay = memo(() => { export const HeadsUpDisplay = memo(() => {
const stageAttrs = useStore($stageAttrs); const canvasManager = useCanvasManager();
const cursorPos = useStore($lastCursorPos); const stageAttrs = useStore(canvasManager.stateApi.$stageAttrs);
const isDrawing = useStore($isDrawing); const cursorPos = useStore(canvasManager.stateApi.$lastCursorPos);
const isMouseDown = useStore($isMouseDown); const isDrawing = useStore(canvasManager.stateApi.$isDrawing);
const lastMouseDownPos = useStore($lastMouseDownPos); const isMouseDown = useStore(canvasManager.stateApi.$isMouseDown);
const lastAddedPoint = useStore($lastAddedPoint); const lastMouseDownPos = useStore(canvasManager.stateApi.$lastMouseDownPos);
const bbox = useAppSelector((s) => s.canvasV2.bbox); const lastAddedPoint = useStore(canvasManager.stateApi.$lastAddedPoint);
const bbox = useAppSelector((s) => s.canvasV2.present.bbox);
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

@@ -4,7 +4,7 @@ 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/canvasV2Slice'; import { selectCanvasV2Slice } from 'features/controlLayers/store/selectors';
import { memo } from 'react'; import { memo } from 'react';
const selectEntityIds = createMemoizedSelector(selectCanvasV2Slice, (canvasV2) => { const selectEntityIds = createMemoizedSelector(selectCanvasV2Slice, (canvasV2) => {
@@ -12,7 +12,7 @@ const selectEntityIds = createMemoizedSelector(selectCanvasV2Slice, (canvasV2) =
}); });
export const IPAdapterList = memo(() => { export const IPAdapterList = memo(() => {
const isSelected = useAppSelector((s) => Boolean(s.canvasV2.selectedEntityIdentifier?.type === 'ip_adapter')); const isSelected = useAppSelector((s) => Boolean(s.canvasV2.present.selectedEntityIdentifier?.type === 'ip_adapter'));
const ipaIds = useAppSelector(selectEntityIds); const ipaIds = useAppSelector(selectEntityIds);
if (ipaIds.length === 0) { if (ipaIds.length === 0) {

View File

@@ -24,7 +24,7 @@ type Props = {
export const IPAdapterModel = memo(({ modelKey, onChangeModel, clipVisionModel, onChangeCLIPVisionModel }: Props) => { export const IPAdapterModel = memo(({ modelKey, onChangeModel, clipVisionModel, onChangeCLIPVisionModel }: Props) => {
const { t } = useTranslation(); const { t } = useTranslation();
const currentBaseModel = useAppSelector((s) => s.canvasV2.params.model?.base); const currentBaseModel = useAppSelector((s) => s.params.model?.base);
const [modelConfigs, { isLoading }] = useIPAdapterModels(); const [modelConfigs, { isLoading }] = useIPAdapterModels();
const selectedModel = useMemo(() => modelConfigs.find((m) => m.key === modelKey), [modelConfigs, modelKey]); const selectedModel = useMemo(() => modelConfigs.find((m) => m.key === modelKey), [modelConfigs, modelKey]);

View File

@@ -13,7 +13,7 @@ import {
ipaModelChanged, ipaModelChanged,
ipaWeightChanged, ipaWeightChanged,
} from 'features/controlLayers/store/canvasV2Slice'; } from 'features/controlLayers/store/canvasV2Slice';
import { selectIPAdapterEntityOrThrow } from 'features/controlLayers/store/ipAdaptersReducers'; import { 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';
@@ -24,53 +24,59 @@ import { IPAdapterModel } from './IPAdapterModel';
export const IPAdapterSettings = memo(() => { export const IPAdapterSettings = memo(() => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const { id } = useEntityIdentifierContext(); const entityIdentifier = useEntityIdentifierContext('ip_adapter');
const ipAdapter = useAppSelector((s) => selectIPAdapterEntityOrThrow(s.canvasV2, id).ipAdapter); const ipAdapter = useAppSelector((s) => selectEntityOrThrow(s.canvasV2.present, entityIdentifier).ipAdapter);
const onChangeBeginEndStepPct = useCallback( const onChangeBeginEndStepPct = useCallback(
(beginEndStepPct: [number, number]) => { (beginEndStepPct: [number, number]) => {
dispatch(ipaBeginEndStepPctChanged({ id, beginEndStepPct })); dispatch(ipaBeginEndStepPctChanged({ entityIdentifier, beginEndStepPct }));
}, },
[dispatch, id] [dispatch, entityIdentifier]
); );
const onChangeWeight = useCallback( const onChangeWeight = useCallback(
(weight: number) => { (weight: number) => {
dispatch(ipaWeightChanged({ id, weight })); dispatch(ipaWeightChanged({ entityIdentifier, weight }));
}, },
[dispatch, id] [dispatch, entityIdentifier]
); );
const onChangeIPMethod = useCallback( const onChangeIPMethod = useCallback(
(method: IPMethodV2) => { (method: IPMethodV2) => {
dispatch(ipaMethodChanged({ id, method })); dispatch(ipaMethodChanged({ entityIdentifier, method }));
}, },
[dispatch, id] [dispatch, entityIdentifier]
); );
const onChangeModel = useCallback( const onChangeModel = useCallback(
(modelConfig: IPAdapterModelConfig) => { (modelConfig: IPAdapterModelConfig) => {
dispatch(ipaModelChanged({ id, modelConfig })); dispatch(ipaModelChanged({ entityIdentifier, modelConfig }));
}, },
[dispatch, id] [dispatch, entityIdentifier]
); );
const onChangeCLIPVisionModel = useCallback( const onChangeCLIPVisionModel = useCallback(
(clipVisionModel: CLIPVisionModelV2) => { (clipVisionModel: CLIPVisionModelV2) => {
dispatch(ipaCLIPVisionModelChanged({ id, clipVisionModel })); dispatch(ipaCLIPVisionModelChanged({ entityIdentifier, clipVisionModel }));
}, },
[dispatch, id] [dispatch, entityIdentifier]
); );
const onChangeImage = useCallback( const onChangeImage = useCallback(
(imageDTO: ImageDTO | null) => { (imageDTO: ImageDTO | null) => {
dispatch(ipaImageChanged({ id, imageDTO })); dispatch(ipaImageChanged({ entityIdentifier, imageDTO }));
}, },
[dispatch, id] [dispatch, entityIdentifier]
); );
const droppableData = useMemo<IPAImageDropData>(() => ({ actionType: 'SET_IPA_IMAGE', context: { id }, id }), [id]); const droppableData = useMemo<IPAImageDropData>(
const postUploadAction = useMemo<IPALayerImagePostUploadAction>(() => ({ type: 'SET_IPA_IMAGE', id }), [id]); () => ({ actionType: 'SET_IPA_IMAGE', context: { id: entityIdentifier.id }, id: entityIdentifier.id }),
[entityIdentifier.id]
);
const postUploadAction = useMemo<IPALayerImagePostUploadAction>(
() => ({ type: 'SET_IPA_IMAGE', id: entityIdentifier.id }),
[entityIdentifier.id]
);
return ( return (
<CanvasEntitySettingsWrapper> <CanvasEntitySettingsWrapper>
@@ -95,7 +101,7 @@ export const IPAdapterSettings = memo(() => {
<IPAdapterImagePreview <IPAdapterImagePreview
image={ipAdapter.image ?? null} image={ipAdapter.image ?? null}
onChangeImage={onChangeImage} onChangeImage={onChangeImage}
ipAdapterId={id} ipAdapterId={entityIdentifier.id}
droppableData={droppableData} droppableData={droppableData}
postUploadAction={postUploadAction} postUploadAction={postUploadAction}
/> />

View File

@@ -3,7 +3,7 @@ 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/canvasV2Slice'; import { selectCanvasV2Slice } from 'features/controlLayers/store/selectors';
import { memo } from 'react'; import { memo } from 'react';
const selectEntityIds = createMemoizedSelector(selectCanvasV2Slice, (canvasV2) => { const selectEntityIds = createMemoizedSelector(selectCanvasV2Slice, (canvasV2) => {
@@ -11,7 +11,9 @@ const selectEntityIds = createMemoizedSelector(selectCanvasV2Slice, (canvasV2) =
}); });
export const InpaintMaskList = memo(() => { export const InpaintMaskList = memo(() => {
const isSelected = useAppSelector((s) => Boolean(s.canvasV2.selectedEntityIdentifier?.type === 'inpaint_mask')); const isSelected = useAppSelector((s) =>
Boolean(s.canvasV2.present.selectedEntityIdentifier?.type === 'inpaint_mask')
);
const entityIds = useAppSelector(selectEntityIds); const entityIds = useAppSelector(selectEntityIds);
if (entityIds.length === 0) { if (entityIds.length === 0) {

View File

@@ -5,7 +5,7 @@ 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/canvasV2Slice';
import { selectInpaintMaskEntityOrThrow } from 'features/controlLayers/store/inpaintMaskReducers'; import { 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 } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@@ -13,8 +13,8 @@ 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(); const entityIdentifier = useEntityIdentifierContext('inpaint_mask');
const fill = useAppSelector((s) => selectInpaintMaskEntityOrThrow(s.canvasV2, entityIdentifier.id).fill); const fill = useAppSelector((s) => selectEntityOrThrow(s.canvasV2.present, entityIdentifier).fill);
const onChangeFillColor = useCallback( const onChangeFillColor = useCallback(
(color: RgbColor) => { (color: RgbColor) => {

View File

@@ -3,7 +3,7 @@ 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/canvasV2Slice'; import { selectCanvasV2Slice } from 'features/controlLayers/store/selectors';
import { memo } from 'react'; import { memo } from 'react';
const selectEntityIds = createMemoizedSelector(selectCanvasV2Slice, (canvasV2) => { const selectEntityIds = createMemoizedSelector(selectCanvasV2Slice, (canvasV2) => {
@@ -11,7 +11,9 @@ const selectEntityIds = createMemoizedSelector(selectCanvasV2Slice, (canvasV2) =
}); });
export const RasterLayerEntityList = memo(() => { export const RasterLayerEntityList = memo(() => {
const isSelected = useAppSelector((s) => Boolean(s.canvasV2.selectedEntityIdentifier?.type === 'raster_layer')); const isSelected = useAppSelector((s) =>
Boolean(s.canvasV2.present.selectedEntityIdentifier?.type === 'raster_layer')
);
const layerIds = useAppSelector(selectEntityIds); const layerIds = useAppSelector(selectEntityIds);
if (layerIds.length === 0) { if (layerIds.length === 0) {

View File

@@ -9,11 +9,11 @@ import { PiLightningBold } from 'react-icons/pi';
export const RasterLayerMenuItemsRasterToControl = memo(() => { export const RasterLayerMenuItemsRasterToControl = memo(() => {
const { t } = useTranslation(); const { t } = useTranslation();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const entityIdentifier = useEntityIdentifierContext(); const entityIdentifier = useEntityIdentifierContext('raster_layer');
const convertRasterLayerToControlLayer = useCallback(() => { const convertRasterLayerToControlLayer = useCallback(() => {
dispatch(rasterLayerConvertedToControlLayer({ id: entityIdentifier.id })); dispatch(rasterLayerConvertedToControlLayer({ entityIdentifier }));
}, [dispatch, entityIdentifier.id]); }, [dispatch, entityIdentifier]);
return ( return (
<MenuItem onClick={convertRasterLayerToControlLayer} icon={<PiLightningBold />}> <MenuItem onClick={convertRasterLayerToControlLayer} icon={<PiLightningBold />}>

View File

@@ -1,44 +1,42 @@
import { Button, Flex } from '@invoke-ai/ui-library'; import { Button, Flex } 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 { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
import { import {
rgIPAdapterAdded, rgIPAdapterAdded,
rgNegativePromptChanged, rgNegativePromptChanged,
rgPositivePromptChanged, rgPositivePromptChanged,
selectCanvasV2Slice,
} from 'features/controlLayers/store/canvasV2Slice'; } from 'features/controlLayers/store/canvasV2Slice';
import { selectCanvasV2Slice, 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';
type AddPromptButtonProps = { export const RegionalGuidanceAddPromptsIPAdapterButtons = () => {
id: string; const entityIdentifier = useEntityIdentifierContext('regional_guidance');
};
export const RegionalGuidanceAddPromptsIPAdapterButtons = ({ id }: AddPromptButtonProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const selectValidActions = useMemo( const selectValidActions = useMemo(
() => () =>
createMemoizedSelector(selectCanvasV2Slice, (canvasV2) => { createMemoizedSelector(selectCanvasV2Slice, (canvasV2) => {
const rg = canvasV2.regions.entities.find((rg) => rg.id === id); const entity = selectEntityOrThrow(canvasV2, entityIdentifier);
return { return {
canAddPositivePrompt: rg?.positivePrompt === null, canAddPositivePrompt: entity?.positivePrompt === null,
canAddNegativePrompt: rg?.negativePrompt === null, canAddNegativePrompt: entity?.negativePrompt === null,
}; };
}), }),
[id] [entityIdentifier]
); );
const validActions = useAppSelector(selectValidActions); const validActions = useAppSelector(selectValidActions);
const addPositivePrompt = useCallback(() => { const addPositivePrompt = useCallback(() => {
dispatch(rgPositivePromptChanged({ id, prompt: '' })); dispatch(rgPositivePromptChanged({ entityIdentifier, prompt: '' }));
}, [dispatch, id]); }, [dispatch, entityIdentifier]);
const addNegativePrompt = useCallback(() => { const addNegativePrompt = useCallback(() => {
dispatch(rgNegativePromptChanged({ id, prompt: '' })); dispatch(rgNegativePromptChanged({ entityIdentifier, prompt: '' }));
}, [dispatch, id]); }, [dispatch, entityIdentifier]);
const addIPAdapter = useCallback(() => { const addIPAdapter = useCallback(() => {
dispatch(rgIPAdapterAdded({ id })); dispatch(rgIPAdapterAdded({ entityIdentifier }));
}, [dispatch, id]); }, [dispatch, entityIdentifier]);
return ( return (
<Flex w="full" p={2} justifyContent="space-between"> <Flex w="full" p={2} justifyContent="space-between">

View File

@@ -1,14 +1,14 @@
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 { selectRegionalGuidanceEntityOrThrow } from 'features/controlLayers/store/regionsReducers'; import { selectEntityOrThrow } from 'features/controlLayers/store/selectors';
import { memo } from 'react'; import { memo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
export const RegionalGuidanceBadges = memo(() => { export const RegionalGuidanceBadges = memo(() => {
const { id } = useEntityIdentifierContext(); const entityIdentifier = useEntityIdentifierContext('regional_guidance');
const { t } = useTranslation(); const { t } = useTranslation();
const autoNegative = useAppSelector((s) => selectRegionalGuidanceEntityOrThrow(s.canvasV2, id).autoNegative); const autoNegative = useAppSelector((s) => selectEntityOrThrow(s.canvasV2.present, entityIdentifier).autoNegative);
return ( return (
<> <>

View File

@@ -3,7 +3,7 @@ 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/canvasV2Slice'; import { selectCanvasV2Slice } from 'features/controlLayers/store/selectors';
import { memo } from 'react'; import { memo } from 'react';
const selectEntityIds = createMemoizedSelector(selectCanvasV2Slice, (canvasV2) => { const selectEntityIds = createMemoizedSelector(selectCanvasV2Slice, (canvasV2) => {
@@ -11,7 +11,9 @@ const selectEntityIds = createMemoizedSelector(selectCanvasV2Slice, (canvasV2) =
}); });
export const RegionalGuidanceEntityList = memo(() => { export const RegionalGuidanceEntityList = memo(() => {
const isSelected = useAppSelector((s) => Boolean(s.canvasV2.selectedEntityIdentifier?.type === 'regional_guidance')); const isSelected = useAppSelector((s) =>
Boolean(s.canvasV2.present.selectedEntityIdentifier?.type === 'regional_guidance')
);
const rgIds = useAppSelector(selectEntityIds); const rgIds = useAppSelector(selectEntityIds);
if (rgIds.length === 0) { if (rgIds.length === 0) {

View File

@@ -5,6 +5,7 @@ import { Weight } from 'features/controlLayers/components/common/Weight';
import { IPAdapterImagePreview } from 'features/controlLayers/components/IPAdapter/IPAdapterImagePreview'; import { IPAdapterImagePreview } from 'features/controlLayers/components/IPAdapter/IPAdapterImagePreview';
import { IPAdapterMethod } from 'features/controlLayers/components/IPAdapter/IPAdapterMethod'; import { IPAdapterMethod } from 'features/controlLayers/components/IPAdapter/IPAdapterMethod';
import { IPAdapterModel } from 'features/controlLayers/components/IPAdapter/IPAdapterModel'; import { IPAdapterModel } from 'features/controlLayers/components/IPAdapter/IPAdapterModel';
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
import { import {
rgIPAdapterBeginEndStepPctChanged, rgIPAdapterBeginEndStepPctChanged,
rgIPAdapterCLIPVisionModelChanged, rgIPAdapterCLIPVisionModelChanged,
@@ -14,7 +15,7 @@ import {
rgIPAdapterModelChanged, rgIPAdapterModelChanged,
rgIPAdapterWeightChanged, rgIPAdapterWeightChanged,
} from 'features/controlLayers/store/canvasV2Slice'; } from 'features/controlLayers/store/canvasV2Slice';
import { selectRegionalGuidanceEntityOrThrow } from 'features/controlLayers/store/regionsReducers'; import { 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';
@@ -23,71 +24,75 @@ import type { ImageDTO, IPAdapterModelConfig, RGIPAdapterImagePostUploadAction }
import { assert } from 'tsafe'; import { assert } from 'tsafe';
type Props = { type Props = {
id: string;
ipAdapterId: string; ipAdapterId: string;
ipAdapterNumber: number; ipAdapterNumber: number;
}; };
export const RegionalGuidanceIPAdapterSettings = memo(({ id, ipAdapterId, ipAdapterNumber }: Props) => { export const RegionalGuidanceIPAdapterSettings = memo(({ ipAdapterId, ipAdapterNumber }: Props) => {
const entityIdentifier = useEntityIdentifierContext('regional_guidance');
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const onDeleteIPAdapter = useCallback(() => { const onDeleteIPAdapter = useCallback(() => {
dispatch(rgIPAdapterDeleted({ id, ipAdapterId })); dispatch(rgIPAdapterDeleted({ entityIdentifier, ipAdapterId }));
}, [dispatch, ipAdapterId, id]); }, [dispatch, entityIdentifier, ipAdapterId]);
const ipAdapter = useAppSelector((s) => { const ipAdapter = useAppSelector((s) => {
const ipa = selectRegionalGuidanceEntityOrThrow(s.canvasV2, id).ipAdapters.find((ipa) => ipa.id === ipAdapterId); const ipa = selectRegionalGuidanceIPAdapter(s.canvasV2.present, entityIdentifier, ipAdapterId);
assert(ipa, `Regional GuidanceIP Adapter with id ${ipAdapterId} not found`); assert(ipa, `Regional GuidanceIP Adapter with id ${ipAdapterId} not found`);
return ipa; return ipa;
}); });
const onChangeBeginEndStepPct = useCallback( const onChangeBeginEndStepPct = useCallback(
(beginEndStepPct: [number, number]) => { (beginEndStepPct: [number, number]) => {
dispatch(rgIPAdapterBeginEndStepPctChanged({ id, ipAdapterId, beginEndStepPct })); dispatch(rgIPAdapterBeginEndStepPctChanged({ entityIdentifier, ipAdapterId, beginEndStepPct }));
}, },
[dispatch, ipAdapterId, id] [dispatch, entityIdentifier, ipAdapterId]
); );
const onChangeWeight = useCallback( const onChangeWeight = useCallback(
(weight: number) => { (weight: number) => {
dispatch(rgIPAdapterWeightChanged({ id, ipAdapterId, weight })); dispatch(rgIPAdapterWeightChanged({ entityIdentifier, ipAdapterId, weight }));
}, },
[dispatch, ipAdapterId, id] [dispatch, entityIdentifier, ipAdapterId]
); );
const onChangeIPMethod = useCallback( const onChangeIPMethod = useCallback(
(method: IPMethodV2) => { (method: IPMethodV2) => {
dispatch(rgIPAdapterMethodChanged({ id, ipAdapterId, method })); dispatch(rgIPAdapterMethodChanged({ entityIdentifier, ipAdapterId, method }));
}, },
[dispatch, ipAdapterId, id] [dispatch, entityIdentifier, ipAdapterId]
); );
const onChangeModel = useCallback( const onChangeModel = useCallback(
(modelConfig: IPAdapterModelConfig) => { (modelConfig: IPAdapterModelConfig) => {
dispatch(rgIPAdapterModelChanged({ id, ipAdapterId, modelConfig })); dispatch(rgIPAdapterModelChanged({ entityIdentifier, ipAdapterId, modelConfig }));
}, },
[dispatch, ipAdapterId, id] [dispatch, entityIdentifier, ipAdapterId]
); );
const onChangeCLIPVisionModel = useCallback( const onChangeCLIPVisionModel = useCallback(
(clipVisionModel: CLIPVisionModelV2) => { (clipVisionModel: CLIPVisionModelV2) => {
dispatch(rgIPAdapterCLIPVisionModelChanged({ id, ipAdapterId, clipVisionModel })); dispatch(rgIPAdapterCLIPVisionModelChanged({ entityIdentifier, ipAdapterId, clipVisionModel }));
}, },
[dispatch, ipAdapterId, id] [dispatch, entityIdentifier, ipAdapterId]
); );
const onChangeImage = useCallback( const onChangeImage = useCallback(
(imageDTO: ImageDTO | null) => { (imageDTO: ImageDTO | null) => {
dispatch(rgIPAdapterImageChanged({ id, ipAdapterId, imageDTO })); dispatch(rgIPAdapterImageChanged({ entityIdentifier, ipAdapterId, imageDTO }));
}, },
[dispatch, ipAdapterId, id] [dispatch, entityIdentifier, ipAdapterId]
); );
const droppableData = useMemo<RGIPAdapterImageDropData>( const droppableData = useMemo<RGIPAdapterImageDropData>(
() => ({ actionType: 'SET_RG_IP_ADAPTER_IMAGE', context: { id, ipAdapterId }, id }), () => ({
[ipAdapterId, id] actionType: 'SET_RG_IP_ADAPTER_IMAGE',
context: { id: entityIdentifier.id, ipAdapterId },
id: entityIdentifier.id,
}),
[entityIdentifier.id, ipAdapterId]
); );
const postUploadAction = useMemo<RGIPAdapterImagePostUploadAction>( const postUploadAction = useMemo<RGIPAdapterImagePostUploadAction>(
() => ({ type: 'SET_RG_IP_ADAPTER_IMAGE', id, ipAdapterId }), () => ({ type: 'SET_RG_IP_ADAPTER_IMAGE', id: entityIdentifier.id, ipAdapterId }),
[ipAdapterId, id] [entityIdentifier.id, ipAdapterId]
); );
return ( return (

View File

@@ -3,25 +3,23 @@ import { EMPTY_ARRAY } from 'app/store/constants';
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 { RegionalGuidanceIPAdapterSettings } from 'features/controlLayers/components/RegionalGuidance/RegionalGuidanceIPAdapterSettings'; import { RegionalGuidanceIPAdapterSettings } from 'features/controlLayers/components/RegionalGuidance/RegionalGuidanceIPAdapterSettings';
import { selectCanvasV2Slice } from 'features/controlLayers/store/canvasV2Slice'; import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
import { selectRegionalGuidanceEntityOrThrow } from 'features/controlLayers/store/regionsReducers'; import { selectCanvasV2Slice, selectEntityOrThrow } from 'features/controlLayers/store/selectors';
import { Fragment, memo, useMemo } from 'react'; import { Fragment, memo, useMemo } from 'react';
type Props = { export const RegionalGuidanceIPAdapters = memo(() => {
id: string; const entityIdentifier = useEntityIdentifierContext('regional_guidance');
};
export const RegionalGuidanceIPAdapters = memo(({ id }: Props) => {
const selectIPAdapterIds = useMemo( const selectIPAdapterIds = useMemo(
() => () =>
createMemoizedSelector(selectCanvasV2Slice, (canvasV2) => { createMemoizedSelector(selectCanvasV2Slice, (canvasV2) => {
const ipAdapterIds = selectRegionalGuidanceEntityOrThrow(canvasV2, id).ipAdapters.map(({ id }) => id); const ipAdapterIds = selectEntityOrThrow(canvasV2, entityIdentifier).ipAdapters.map(({ id }) => id);
if (ipAdapterIds.length === 0) { if (ipAdapterIds.length === 0) {
return EMPTY_ARRAY; return EMPTY_ARRAY;
} }
return ipAdapterIds; return ipAdapterIds;
}), }),
[id] [entityIdentifier]
); );
const ipAdapterIds = useAppSelector(selectIPAdapterIds); const ipAdapterIds = useAppSelector(selectIPAdapterIds);
@@ -35,7 +33,7 @@ export const RegionalGuidanceIPAdapters = memo(({ id }: Props) => {
{ipAdapterIds.map((ipAdapterId, index) => ( {ipAdapterIds.map((ipAdapterId, index) => (
<Fragment key={ipAdapterId}> <Fragment key={ipAdapterId}>
{index > 0 && <Divider />} {index > 0 && <Divider />}
<RegionalGuidanceIPAdapterSettings id={id} ipAdapterId={ipAdapterId} ipAdapterNumber={index + 1} /> <RegionalGuidanceIPAdapterSettings ipAdapterId={ipAdapterId} ipAdapterNumber={index + 1} />
</Fragment> </Fragment>
))} ))}
</> </>

View File

@@ -5,27 +5,27 @@ 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/canvasV2Slice';
import { selectRegionalGuidanceEntityOrThrow } from 'features/controlLayers/store/regionsReducers'; import { 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 } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
export const RegionalGuidanceMaskFillColorPicker = memo(() => { export const RegionalGuidanceMaskFillColorPicker = memo(() => {
const entityIdentifier = useEntityIdentifierContext(); const entityIdentifier = useEntityIdentifierContext('regional_guidance');
const { t } = useTranslation(); const { t } = useTranslation();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const fill = useAppSelector((s) => selectRegionalGuidanceEntityOrThrow(s.canvasV2, entityIdentifier.id).fill); const fill = useAppSelector((s) => selectEntityOrThrow(s.canvasV2.present, entityIdentifier).fill);
const onChangeFillColor = useCallback( const onChangeFillColor = useCallback(
(color: RgbColor) => { (color: RgbColor) => {
dispatch(rgFillColorChanged({ id: entityIdentifier.id, color })); dispatch(rgFillColorChanged({ entityIdentifier, color }));
}, },
[dispatch, entityIdentifier.id] [dispatch, entityIdentifier]
); );
const onChangeFillStyle = useCallback( const onChangeFillStyle = useCallback(
(style: FillStyle) => { (style: FillStyle) => {
dispatch(rgFillStyleChanged({ id: entityIdentifier.id, style })); dispatch(rgFillStyleChanged({ entityIdentifier, style }));
}, },
[dispatch, entityIdentifier.id] [dispatch, entityIdentifier]
); );
return ( return (
<Popover isLazy> <Popover isLazy>

View File

@@ -6,36 +6,36 @@ import {
rgIPAdapterAdded, rgIPAdapterAdded,
rgNegativePromptChanged, rgNegativePromptChanged,
rgPositivePromptChanged, rgPositivePromptChanged,
selectCanvasV2Slice,
} from 'features/controlLayers/store/canvasV2Slice'; } from 'features/controlLayers/store/canvasV2Slice';
import { selectCanvasV2Slice, 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';
export const RegionalGuidanceMenuItemsAddPromptsAndIPAdapter = memo(() => { export const RegionalGuidanceMenuItemsAddPromptsAndIPAdapter = memo(() => {
const { id } = useEntityIdentifierContext(); const entityIdentifier = useEntityIdentifierContext('regional_guidance');
const { t } = useTranslation(); const { t } = useTranslation();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const selectValidActions = useMemo( const selectValidActions = useMemo(
() => () =>
createMemoizedSelector(selectCanvasV2Slice, (canvasV2) => { createMemoizedSelector(selectCanvasV2Slice, (canvasV2) => {
const rg = canvasV2.regions.entities.find((rg) => rg.id === id); const entity = selectEntity(canvasV2, entityIdentifier);
return { return {
canAddPositivePrompt: rg?.positivePrompt === null, canAddPositivePrompt: entity?.positivePrompt === null,
canAddNegativePrompt: rg?.negativePrompt === null, canAddNegativePrompt: entity?.negativePrompt === null,
}; };
}), }),
[id] [entityIdentifier]
); );
const validActions = useAppSelector(selectValidActions); const validActions = useAppSelector(selectValidActions);
const addPositivePrompt = useCallback(() => { const addPositivePrompt = useCallback(() => {
dispatch(rgPositivePromptChanged({ id: id, prompt: '' })); dispatch(rgPositivePromptChanged({ entityIdentifier, prompt: '' }));
}, [dispatch, id]); }, [dispatch, entityIdentifier]);
const addNegativePrompt = useCallback(() => { const addNegativePrompt = useCallback(() => {
dispatch(rgNegativePromptChanged({ id: id, prompt: '' })); dispatch(rgNegativePromptChanged({ entityIdentifier, prompt: '' }));
}, [dispatch, id]); }, [dispatch, entityIdentifier]);
const addIPAdapter = useCallback(() => { const addIPAdapter = useCallback(() => {
dispatch(rgIPAdapterAdded({ id })); dispatch(rgIPAdapterAdded({ entityIdentifier }));
}, [dispatch, id]); }, [dispatch, entityIdentifier]);
return ( return (
<> <>

View File

@@ -2,19 +2,19 @@ import { MenuItem } from '@invoke-ai/ui-library';
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/canvasV2Slice';
import { selectRegionalGuidanceEntityOrThrow } from 'features/controlLayers/store/regionsReducers'; import { selectEntityOrThrow } 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 { PiSelectionInverseBold } from 'react-icons/pi'; import { PiSelectionInverseBold } from 'react-icons/pi';
export const RegionalGuidanceMenuItemsAutoNegative = memo(() => { export const RegionalGuidanceMenuItemsAutoNegative = memo(() => {
const { id } = useEntityIdentifierContext(); const entityIdentifier = useEntityIdentifierContext('regional_guidance');
const { t } = useTranslation(); const { t } = useTranslation();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const autoNegative = useAppSelector((s) => selectRegionalGuidanceEntityOrThrow(s.canvasV2, id).autoNegative); const autoNegative = useAppSelector((s) => selectEntityOrThrow(s.canvasV2.present, entityIdentifier).autoNegative);
const onClick = useCallback(() => { const onClick = useCallback(() => {
dispatch(rgAutoNegativeToggled({ id })); dispatch(rgAutoNegativeToggled({ entityIdentifier }));
}, [dispatch, id]); }, [dispatch, entityIdentifier]);
return ( return (
<MenuItem icon={<PiSelectionInverseBold />} onClick={onClick}> <MenuItem icon={<PiSelectionInverseBold />} onClick={onClick}>

View File

@@ -1,8 +1,9 @@
import { Box, Textarea } from '@invoke-ai/ui-library'; import { Box, Textarea } from '@invoke-ai/ui-library';
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 { rgNegativePromptChanged } from 'features/controlLayers/store/canvasV2Slice'; import { rgNegativePromptChanged } from 'features/controlLayers/store/canvasV2Slice';
import { selectRegionalGuidanceEntityOrThrow } from 'features/controlLayers/store/regionsReducers'; import { 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';
@@ -10,24 +11,21 @@ import { usePrompt } from 'features/prompt/usePrompt';
import { memo, useCallback, useRef } from 'react'; import { memo, useCallback, useRef } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
type Props = { export const RegionalGuidanceNegativePrompt = memo(() => {
id: string; const entityIdentifier = useEntityIdentifierContext('regional_guidance');
}; const prompt = useAppSelector((s) => selectEntityOrThrow(s.canvasV2.present, entityIdentifier).negativePrompt ?? '');
export const RegionalGuidanceNegativePrompt = memo(({ id }: Props) => {
const prompt = useAppSelector((s) => selectRegionalGuidanceEntityOrThrow(s.canvasV2, id).negativePrompt ?? '');
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const textareaRef = useRef<HTMLTextAreaElement>(null); const textareaRef = useRef<HTMLTextAreaElement>(null);
const { t } = useTranslation(); const { t } = useTranslation();
const _onChange = useCallback( const _onChange = useCallback(
(v: string) => { (v: string) => {
dispatch(rgNegativePromptChanged({ id, prompt: v })); dispatch(rgNegativePromptChanged({ entityIdentifier, prompt: v }));
}, },
[dispatch, id] [dispatch, entityIdentifier]
); );
const onDeletePrompt = useCallback(() => { const onDeletePrompt = useCallback(() => {
dispatch(rgNegativePromptChanged({ id, prompt: null })); dispatch(rgNegativePromptChanged({ entityIdentifier, prompt: null }));
}, [dispatch, id]); }, [dispatch, entityIdentifier]);
const { onChange, isOpen, onClose, onOpen, onSelect, onKeyDown } = usePrompt({ const { onChange, isOpen, onClose, onOpen, onSelect, onKeyDown } = usePrompt({
prompt, prompt,
textareaRef, textareaRef,

View File

@@ -1,8 +1,9 @@
import { Box, Textarea } from '@invoke-ai/ui-library'; import { Box, Textarea } from '@invoke-ai/ui-library';
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 { rgPositivePromptChanged } from 'features/controlLayers/store/canvasV2Slice'; import { rgPositivePromptChanged } from 'features/controlLayers/store/canvasV2Slice';
import { selectRegionalGuidanceEntityOrThrow } from 'features/controlLayers/store/regionsReducers'; import { 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';
@@ -10,24 +11,21 @@ import { usePrompt } from 'features/prompt/usePrompt';
import { memo, useCallback, useRef } from 'react'; import { memo, useCallback, useRef } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
type Props = { export const RegionalGuidancePositivePrompt = memo(() => {
id: string; const entityIdentifier = useEntityIdentifierContext('regional_guidance');
}; const prompt = useAppSelector((s) => selectEntityOrThrow(s.canvasV2.present, entityIdentifier).positivePrompt ?? '');
export const RegionalGuidancePositivePrompt = memo(({ id }: Props) => {
const prompt = useAppSelector((s) => selectRegionalGuidanceEntityOrThrow(s.canvasV2, id).positivePrompt ?? '');
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const textareaRef = useRef<HTMLTextAreaElement>(null); const textareaRef = useRef<HTMLTextAreaElement>(null);
const { t } = useTranslation(); const { t } = useTranslation();
const _onChange = useCallback( const _onChange = useCallback(
(v: string) => { (v: string) => {
dispatch(rgPositivePromptChanged({ id, prompt: v })); dispatch(rgPositivePromptChanged({ entityIdentifier, prompt: v }));
}, },
[dispatch, id] [dispatch, entityIdentifier]
); );
const onDeletePrompt = useCallback(() => { const onDeletePrompt = useCallback(() => {
dispatch(rgPositivePromptChanged({ id, prompt: null })); dispatch(rgPositivePromptChanged({ entityIdentifier, prompt: null }));
}, [dispatch, id]); }, [dispatch, entityIdentifier]);
const { onChange, isOpen, onClose, onOpen, onSelect, onKeyDown } = usePrompt({ const { onChange, isOpen, onClose, onOpen, onSelect, onKeyDown } = usePrompt({
prompt, prompt,
textareaRef, textareaRef,

View File

@@ -3,7 +3,7 @@ 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 { selectRegionalGuidanceEntityOrThrow } from 'features/controlLayers/store/regionsReducers'; import { selectEntityOrThrow } from 'features/controlLayers/store/selectors';
import { memo } from 'react'; import { memo } from 'react';
import { RegionalGuidanceIPAdapters } from './RegionalGuidanceIPAdapters'; import { RegionalGuidanceIPAdapters } from './RegionalGuidanceIPAdapters';
@@ -11,35 +11,33 @@ import { RegionalGuidanceNegativePrompt } from './RegionalGuidanceNegativePrompt
import { RegionalGuidancePositivePrompt } from './RegionalGuidancePositivePrompt'; import { RegionalGuidancePositivePrompt } from './RegionalGuidancePositivePrompt';
export const RegionalGuidanceSettings = memo(() => { export const RegionalGuidanceSettings = memo(() => {
const { id } = useEntityIdentifierContext(); const entityIdentifier = useEntityIdentifierContext('regional_guidance');
const hasPositivePrompt = useAppSelector( const hasPositivePrompt = useAppSelector(
(s) => selectRegionalGuidanceEntityOrThrow(s.canvasV2, id).positivePrompt !== null (s) => selectEntityOrThrow(s.canvasV2.present, entityIdentifier).positivePrompt !== null
); );
const hasNegativePrompt = useAppSelector( const hasNegativePrompt = useAppSelector(
(s) => selectRegionalGuidanceEntityOrThrow(s.canvasV2, id).negativePrompt !== null (s) => selectEntityOrThrow(s.canvasV2.present, entityIdentifier).negativePrompt !== null
); );
const hasIPAdapters = useAppSelector( const hasIPAdapters = useAppSelector(
(s) => selectRegionalGuidanceEntityOrThrow(s.canvasV2, id).ipAdapters.length > 0 (s) => selectEntityOrThrow(s.canvasV2.present, entityIdentifier).ipAdapters.length > 0
); );
return ( return (
<CanvasEntitySettingsWrapper> <CanvasEntitySettingsWrapper>
{!hasPositivePrompt && !hasNegativePrompt && !hasIPAdapters && ( {!hasPositivePrompt && !hasNegativePrompt && !hasIPAdapters && <RegionalGuidanceAddPromptsIPAdapterButtons />}
<RegionalGuidanceAddPromptsIPAdapterButtons id={id} />
)}
{hasPositivePrompt && ( {hasPositivePrompt && (
<> <>
<RegionalGuidancePositivePrompt id={id} /> <RegionalGuidancePositivePrompt />
{(hasNegativePrompt || hasIPAdapters) && <Divider />} {(hasNegativePrompt || hasIPAdapters) && <Divider />}
</> </>
)} )}
{hasNegativePrompt && ( {hasNegativePrompt && (
<> <>
<RegionalGuidanceNegativePrompt id={id} /> <RegionalGuidanceNegativePrompt />
{hasIPAdapters && <Divider />} {hasIPAdapters && <Divider />}
</> </>
)} )}
{hasIPAdapters && <RegionalGuidanceIPAdapters id={id} />} {hasIPAdapters && <RegionalGuidanceIPAdapters />}
</CanvasEntitySettingsWrapper> </CanvasEntitySettingsWrapper>
); );
}); });

View File

@@ -1,13 +1,13 @@
import { Checkbox, FormControl, FormLabel } from '@invoke-ai/ui-library'; import { Checkbox, FormControl, FormLabel } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { settingsAutoSaveToggled } from 'features/controlLayers/store/canvasV2Slice'; import { settingsAutoSaveToggled } from 'features/controlLayers/store/canvasSettingsSlice';
import { memo, useCallback } from 'react'; import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
export const CanvasSettingsAutoSaveCheckbox = memo(() => { export const CanvasSettingsAutoSaveCheckbox = memo(() => {
const { t } = useTranslation(); const { t } = useTranslation();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const autoSave = useAppSelector((s) => s.canvasV2.settings.autoSave); const autoSave = useAppSelector((s) => s.canvasSettings.autoSave);
const onChange = useCallback(() => dispatch(settingsAutoSaveToggled()), [dispatch]); const onChange = useCallback(() => dispatch(settingsAutoSaveToggled()), [dispatch]);
return ( return (
<FormControl w="full"> <FormControl w="full">

View File

@@ -1,6 +1,6 @@
import { Checkbox, FormControl, FormLabel } from '@invoke-ai/ui-library'; import { Checkbox, FormControl, FormLabel } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { clipToBboxChanged } from 'features/controlLayers/store/canvasV2Slice'; import { clipToBboxChanged } from 'features/controlLayers/store/canvasSettingsSlice';
import type { ChangeEvent } from 'react'; import type { ChangeEvent } from 'react';
import { memo, useCallback } from 'react'; import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@@ -8,7 +8,7 @@ import { useTranslation } from 'react-i18next';
export const CanvasSettingsClipToBboxCheckbox = memo(() => { export const CanvasSettingsClipToBboxCheckbox = memo(() => {
const { t } = useTranslation(); const { t } = useTranslation();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const clipToBbox = useAppSelector((s) => s.canvasV2.settings.clipToBbox); const clipToBbox = useAppSelector((s) => s.canvasSettings.clipToBbox);
const onChange = useCallback( const onChange = useCallback(
(e: ChangeEvent<HTMLInputElement>) => dispatch(clipToBboxChanged(e.target.checked)), (e: ChangeEvent<HTMLInputElement>) => dispatch(clipToBboxChanged(e.target.checked)),
[dispatch] [dispatch]

View File

@@ -1,13 +1,13 @@
import { FormControl, FormLabel, Switch } from '@invoke-ai/ui-library'; import { FormControl, FormLabel, Switch } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { settingsDynamicGridToggled } from 'features/controlLayers/store/canvasV2Slice'; import { settingsDynamicGridToggled } from 'features/controlLayers/store/canvasSettingsSlice';
import { memo, useCallback } from 'react'; import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
export const CanvasSettingsDynamicGridSwitch = memo(() => { export const CanvasSettingsDynamicGridSwitch = memo(() => {
const { t } = useTranslation(); const { t } = useTranslation();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const dynamicGrid = useAppSelector((s) => s.canvasV2.settings.dynamicGrid); const dynamicGrid = useAppSelector((s) => s.canvasSettings.dynamicGrid);
const onChange = useCallback(() => { const onChange = useCallback(() => {
dispatch(settingsDynamicGridToggled()); dispatch(settingsDynamicGridToggled());
}, [dispatch]); }, [dispatch]);

View File

@@ -1,6 +1,6 @@
import { Checkbox, FormControl, FormLabel } from '@invoke-ai/ui-library'; import { Checkbox, FormControl, FormLabel } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { invertScrollChanged } from 'features/controlLayers/store/canvasV2Slice'; import { invertScrollChanged } from 'features/controlLayers/store/toolSlice';
import type { ChangeEvent } from 'react'; import type { ChangeEvent } from 'react';
import { memo, useCallback } from 'react'; import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@@ -8,7 +8,7 @@ import { useTranslation } from 'react-i18next';
export const CanvasSettingsInvertScrollCheckbox = memo(() => { export const CanvasSettingsInvertScrollCheckbox = memo(() => {
const { t } = useTranslation(); const { t } = useTranslation();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const invertScroll = useAppSelector((s) => s.canvasV2.tool.invertScroll); const invertScroll = useAppSelector((s) => s.tool.invertScroll);
const onChange = useCallback( const onChange = useCallback(
(e: ChangeEvent<HTMLInputElement>) => dispatch(invertScrollChanged(e.target.checked)), (e: ChangeEvent<HTMLInputElement>) => dispatch(invertScrollChanged(e.target.checked)),
[dispatch] [dispatch]

View File

@@ -38,8 +38,8 @@ const useStageRenderer = (stage: Konva.Stage, container: HTMLDivElement | null,
} }
const manager = new CanvasManager(stage, container, store, socket); const manager = new CanvasManager(stage, container, store, socket);
const cleanup = manager.initialize(); manager.initialize();
return cleanup; return manager.destroy;
}, [asPreview, container, socket, stage, store]); }, [asPreview, container, socket, stage, store]);
useLayoutEffect(() => { useLayoutEffect(() => {
@@ -52,7 +52,7 @@ type Props = {
}; };
export const StageComponent = memo(({ asPreview = false }: Props) => { export const StageComponent = memo(({ asPreview = false }: Props) => {
const dynamicGrid = useAppSelector((s) => s.canvasV2.settings.dynamicGrid); const dynamicGrid = useAppSelector((s) => s.canvasSettings.dynamicGrid);
const [stage] = useState( const [stage] = useState(
() => () =>

View File

@@ -3,7 +3,7 @@ import type { PropsWithChildren } from 'react';
import { memo } from 'react'; import { memo } from 'react';
export const StagingAreaIsStagingGate = memo((props: PropsWithChildren) => { export const StagingAreaIsStagingGate = memo((props: PropsWithChildren) => {
const isStaging = useAppSelector((s) => s.canvasV2.session.isStaging); const isStaging = useAppSelector((s) => s.canvasSession.isStaging);
if (!isStaging) { if (!isStaging) {
return null; return null;

View File

@@ -2,14 +2,14 @@ import { Button, ButtonGroup, IconButton } from '@invoke-ai/ui-library';
import { useStore } from '@nanostores/react'; import { useStore } from '@nanostores/react';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { INTERACTION_SCOPES, useScopeOnMount } from 'common/hooks/interactionScopes'; import { INTERACTION_SCOPES, useScopeOnMount } from 'common/hooks/interactionScopes';
import { useCanvasManager } from 'features/controlLayers/contexts/CanvasManagerProviderGate';
import { import {
$shouldShowStagedImage,
sessionNextStagedImageSelected, sessionNextStagedImageSelected,
sessionPrevStagedImageSelected, sessionPrevStagedImageSelected,
sessionStagedImageDiscarded, sessionStagedImageDiscarded,
sessionStagingAreaImageAccepted, sessionStagingAreaImageAccepted,
sessionStagingAreaReset, sessionStagingAreaReset,
} from 'features/controlLayers/store/canvasV2Slice'; } from 'features/controlLayers/store/canvasSessionSlice';
import { memo, useCallback, useMemo } from 'react'; import { memo, useCallback, 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';
@@ -27,8 +27,9 @@ import { useChangeImageIsIntermediateMutation } from 'services/api/endpoints/ima
export const StagingAreaToolbar = memo(() => { export const StagingAreaToolbar = memo(() => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const session = useAppSelector((s) => s.canvasV2.session); const session = useAppSelector((s) => s.canvasSession);
const shouldShowStagedImage = useStore($shouldShowStagedImage); const canvasManager = useCanvasManager();
const shouldShowStagedImage = useStore(canvasManager.stateApi.$shouldShowStagedImage);
const images = useMemo(() => session.stagedImages, [session]); const images = useMemo(() => session.stagedImages, [session]);
const selectedImage = useMemo(() => { const selectedImage = useMemo(() => {
return images[session.selectedStagedImageIndex] ?? null; return images[session.selectedStagedImageIndex] ?? null;
@@ -70,8 +71,8 @@ export const StagingAreaToolbar = memo(() => {
}, [dispatch]); }, [dispatch]);
const onToggleShouldShowStagedImage = useCallback(() => { const onToggleShouldShowStagedImage = useCallback(() => {
$shouldShowStagedImage.set(!shouldShowStagedImage); canvasManager.stateApi.$shouldShowStagedImage.set(!shouldShowStagedImage);
}, [shouldShowStagedImage]); }, [canvasManager.stateApi.$shouldShowStagedImage, shouldShowStagedImage]);
const onSaveStagingImage = useCallback(() => { const onSaveStagingImage = useCallback(() => {
if (!selectedImage) { if (!selectedImage) {

View File

@@ -1,29 +1,25 @@
import { IconButton } from '@invoke-ai/ui-library'; import { IconButton } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
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 { toolChanged } from 'features/controlLayers/store/canvasV2Slice'; import { memo, useMemo } from 'react';
import { memo, useCallback, 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';
import { PiBoundingBoxBold } from 'react-icons/pi'; import { PiBoundingBoxBold } from 'react-icons/pi';
export const ToolBboxButton = memo(() => { export const ToolBboxButton = memo(() => {
const { t } = useTranslation(); const { t } = useTranslation();
const dispatch = useAppDispatch(); const selectBbox = useSelectTool('bbox');
const isSelected = useToolIsSelected('bbox');
const isFiltering = useIsFiltering(); const isFiltering = useIsFiltering();
const isTransforming = useIsTransforming(); const isTransforming = useIsTransforming();
const isStaging = useAppSelector((s) => s.canvasV2.session.isStaging); const isStaging = useAppSelector((s) => s.canvasSession.isStaging);
const isSelected = useAppSelector((s) => s.canvasV2.tool.selected === 'bbox');
const isDisabled = useMemo(() => { const isDisabled = useMemo(() => {
return isTransforming || isFiltering || isStaging; return isTransforming || isFiltering || isStaging;
}, [isFiltering, isStaging, isTransforming]); }, [isFiltering, isStaging, isTransforming]);
const onClick = useCallback(() => { useHotkeys('q', selectBbox, { enabled: !isDisabled || isSelected }, [selectBbox, isSelected, isDisabled]);
dispatch(toolChanged('bbox'));
}, [dispatch]);
useHotkeys('q', onClick, { enabled: !isDisabled || isSelected }, [onClick, isSelected, isDisabled]);
return ( return (
<IconButton <IconButton
@@ -32,7 +28,7 @@ export const ToolBboxButton = memo(() => {
icon={<PiBoundingBoxBold />} icon={<PiBoundingBoxBold />}
colorScheme={isSelected ? 'invokeBlue' : 'base'} colorScheme={isSelected ? 'invokeBlue' : 'base'}
variant="outline" variant="outline"
onClick={onClick} onClick={selectBbox}
isDisabled={isDisabled} isDisabled={isDisabled}
/> />
); );

View File

@@ -1,37 +1,33 @@
import { IconButton } from '@invoke-ai/ui-library'; import { IconButton } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
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 { toolChanged } from 'features/controlLayers/store/canvasV2Slice';
import { isDrawableEntityType } from 'features/controlLayers/store/types'; import { isDrawableEntityType } from 'features/controlLayers/store/types';
import { memo, useCallback, 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';
import { PiPaintBrushBold } from 'react-icons/pi'; import { PiPaintBrushBold } from 'react-icons/pi';
export const ToolBrushButton = memo(() => { export const ToolBrushButton = memo(() => {
const { t } = useTranslation(); const { t } = useTranslation();
const dispatch = useAppDispatch();
const isFiltering = useIsFiltering(); const isFiltering = useIsFiltering();
const isTransforming = useIsTransforming(); const isTransforming = useIsTransforming();
const isStaging = useAppSelector((s) => s.canvasV2.session.isStaging); const isStaging = useAppSelector((s) => s.canvasSession.isStaging);
const isSelected = useAppSelector((s) => s.canvasV2.tool.selected === 'brush'); const selectBrush = useSelectTool('brush');
const isSelected = useToolIsSelected('brush');
const isDrawingToolAllowed = useAppSelector((s) => { const isDrawingToolAllowed = useAppSelector((s) => {
if (!s.canvasV2.selectedEntityIdentifier?.type) { if (!s.canvasV2.present.selectedEntityIdentifier?.type) {
return false; return false;
} }
return isDrawableEntityType(s.canvasV2.selectedEntityIdentifier.type); return isDrawableEntityType(s.canvasV2.present.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]);
const onClick = useCallback(() => { useHotkeys('b', selectBrush, { enabled: !isDisabled || isSelected }, [isDisabled, isSelected, selectBrush]);
dispatch(toolChanged('brush'));
}, [dispatch]);
useHotkeys('b', onClick, { enabled: !isDisabled || isSelected }, [isDisabled, isSelected, onClick]);
return ( return (
<IconButton <IconButton
@@ -40,7 +36,7 @@ export const ToolBrushButton = memo(() => {
icon={<PiPaintBrushBold />} icon={<PiPaintBrushBold />}
colorScheme={isSelected ? 'invokeBlue' : 'base'} colorScheme={isSelected ? 'invokeBlue' : 'base'}
variant="outline" variant="outline"
onClick={onClick} onClick={selectBrush}
isDisabled={isDisabled} isDisabled={isDisabled}
/> />
); );

View File

@@ -10,7 +10,7 @@ import {
PopoverTrigger, PopoverTrigger,
} 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 { brushWidthChanged } from 'features/controlLayers/store/canvasV2Slice'; import { brushWidthChanged } from 'features/controlLayers/store/toolSlice';
import { memo, useCallback } from 'react'; import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@@ -20,7 +20,7 @@ const formatPx = (v: number | string) => `${v} px`;
export const ToolBrushWidth = memo(() => { export const ToolBrushWidth = memo(() => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const { t } = useTranslation(); const { t } = useTranslation();
const width = useAppSelector((s) => s.canvasV2.tool.brush.width); const width = useAppSelector((s) => s.tool.brush.width);
const onChange = useCallback( const onChange = useCallback(
(v: number) => { (v: number) => {
dispatch(brushWidthChanged(Math.round(v))); dispatch(brushWidthChanged(Math.round(v)));

View File

@@ -1,30 +1,30 @@
import { IconButton } from '@invoke-ai/ui-library'; import { IconButton } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
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 { toolChanged } from 'features/controlLayers/store/canvasV2Slice'; import { memo, useMemo } from 'react';
import { memo, useCallback, 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';
import { PiEyedropperBold } from 'react-icons/pi'; import { PiEyedropperBold } from 'react-icons/pi';
export const ToolColorPickerButton = memo(() => { export const ToolColorPickerButton = memo(() => {
const { t } = useTranslation(); const { t } = useTranslation();
const dispatch = useAppDispatch();
const isFiltering = useIsFiltering(); const isFiltering = useIsFiltering();
const isTransforming = useIsTransforming(); const isTransforming = useIsTransforming();
const isSelected = useAppSelector((s) => s.canvasV2.tool.selected === 'colorPicker'); const selectColorPicker = useSelectTool('colorPicker');
const isStaging = useAppSelector((s) => s.canvasV2.session.isStaging); const isSelected = useToolIsSelected('colorPicker');
const isStaging = useAppSelector((s) => s.canvasSession.isStaging);
const isDisabled = useMemo(() => { const isDisabled = useMemo(() => {
return isTransforming || isFiltering || isStaging; return isTransforming || isFiltering || isStaging;
}, [isFiltering, isStaging, isTransforming]); }, [isFiltering, isStaging, isTransforming]);
const onClick = useCallback(() => { useHotkeys('i', selectColorPicker, { enabled: !isDisabled || isSelected }, [
dispatch(toolChanged('colorPicker')); selectColorPicker,
}, [dispatch]); isSelected,
isDisabled,
useHotkeys('i', onClick, { enabled: !isDisabled || isSelected }, [onClick, isSelected, isDisabled]); ]);
return ( return (
<IconButton <IconButton
@@ -33,7 +33,7 @@ export const ToolColorPickerButton = memo(() => {
icon={<PiEyedropperBold />} icon={<PiEyedropperBold />}
colorScheme={isSelected ? 'invokeBlue' : 'base'} colorScheme={isSelected ? 'invokeBlue' : 'base'}
variant="outline" variant="outline"
onClick={onClick} onClick={selectColorPicker}
isDisabled={isDisabled} isDisabled={isDisabled}
/> />
); );

View File

@@ -1,36 +1,32 @@
import { IconButton } from '@invoke-ai/ui-library'; import { IconButton } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
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 { toolChanged } from 'features/controlLayers/store/canvasV2Slice';
import { isDrawableEntityType } from 'features/controlLayers/store/types'; import { isDrawableEntityType } from 'features/controlLayers/store/types';
import { memo, useCallback, 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';
import { PiEraserBold } from 'react-icons/pi'; import { PiEraserBold } from 'react-icons/pi';
export const ToolEraserButton = memo(() => { export const ToolEraserButton = memo(() => {
const { t } = useTranslation(); const { t } = useTranslation();
const dispatch = useAppDispatch();
const isFiltering = useIsFiltering(); const isFiltering = useIsFiltering();
const isTransforming = useIsTransforming(); const isTransforming = useIsTransforming();
const isStaging = useAppSelector((s) => s.canvasV2.session.isStaging); const isStaging = useAppSelector((s) => s.canvasSession.isStaging);
const isSelected = useAppSelector((s) => s.canvasV2.tool.selected === 'eraser'); const selectEraser = useSelectTool('eraser');
const isSelected = useToolIsSelected('eraser');
const isDrawingToolAllowed = useAppSelector((s) => { const isDrawingToolAllowed = useAppSelector((s) => {
if (!s.canvasV2.selectedEntityIdentifier?.type) { if (!s.canvasV2.present.selectedEntityIdentifier?.type) {
return false; return false;
} }
return isDrawableEntityType(s.canvasV2.selectedEntityIdentifier.type); return isDrawableEntityType(s.canvasV2.present.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]);
const onClick = useCallback(() => { useHotkeys('e', selectEraser, { enabled: !isDisabled || isSelected }, [isDisabled, isSelected, selectEraser]);
dispatch(toolChanged('eraser'));
}, [dispatch]);
useHotkeys('e', onClick, { enabled: !isDisabled || isSelected }, [isDisabled, isSelected, onClick]);
return ( return (
<IconButton <IconButton
@@ -39,7 +35,7 @@ export const ToolEraserButton = memo(() => {
icon={<PiEraserBold />} icon={<PiEraserBold />}
colorScheme={isSelected ? 'invokeBlue' : 'base'} colorScheme={isSelected ? 'invokeBlue' : 'base'}
variant="outline" variant="outline"
onClick={onClick} onClick={selectEraser}
isDisabled={isDisabled} isDisabled={isDisabled}
/> />
); );

View File

@@ -10,7 +10,7 @@ import {
PopoverTrigger, PopoverTrigger,
} 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 { eraserWidthChanged } from 'features/controlLayers/store/canvasV2Slice'; import { eraserWidthChanged } from 'features/controlLayers/store/toolSlice';
import { memo, useCallback } from 'react'; import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@@ -20,7 +20,7 @@ const formatPx = (v: number | string) => `${v} px`;
export const ToolEraserWidth = memo(() => { export const ToolEraserWidth = memo(() => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const { t } = useTranslation(); const { t } = useTranslation();
const width = useAppSelector((s) => s.canvasV2.tool.eraser.width); const width = useAppSelector((s) => s.tool.eraser.width);
const onChange = useCallback( const onChange = useCallback(
(v: number) => { (v: number) => {
dispatch(eraserWidthChanged(Math.round(v))); dispatch(eraserWidthChanged(Math.round(v)));

View File

@@ -2,14 +2,14 @@ import { Flex, Popover, PopoverBody, PopoverContent, PopoverTrigger } from '@inv
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAIColorPicker from 'common/components/IAIColorPicker'; import IAIColorPicker from 'common/components/IAIColorPicker';
import { rgbaColorToString } from 'common/util/colorCodeTransformers'; import { rgbaColorToString } from 'common/util/colorCodeTransformers';
import { fillChanged } from 'features/controlLayers/store/canvasV2Slice'; import { fillChanged } from 'features/controlLayers/store/toolSlice';
import type { RgbaColor } from 'features/controlLayers/store/types'; import type { RgbaColor } 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';
export const ToolFillColorPicker = memo(() => { export const ToolFillColorPicker = memo(() => {
const { t } = useTranslation(); const { t } = useTranslation();
const fill = useAppSelector((s) => s.canvasV2.tool.fill); const fill = useAppSelector((s) => s.tool.fill);
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const onChange = useCallback( const onChange = useCallback(
(color: RgbaColor) => { (color: RgbaColor) => {

View File

@@ -1,36 +1,32 @@
import { IconButton } from '@invoke-ai/ui-library'; import { IconButton } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
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 { toolChanged } from 'features/controlLayers/store/canvasV2Slice';
import { isDrawableEntityType } from 'features/controlLayers/store/types'; import { isDrawableEntityType } from 'features/controlLayers/store/types';
import { memo, useCallback, 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';
import { PiCursorBold } from 'react-icons/pi'; import { PiCursorBold } from 'react-icons/pi';
export const ToolMoveButton = memo(() => { export const ToolMoveButton = memo(() => {
const { t } = useTranslation(); const { t } = useTranslation();
const dispatch = useAppDispatch();
const isFiltering = useIsFiltering(); const isFiltering = useIsFiltering();
const isTransforming = useIsTransforming(); const isTransforming = useIsTransforming();
const isSelected = useAppSelector((s) => s.canvasV2.tool.selected === 'move'); const selectMove = useSelectTool('move');
const isStaging = useAppSelector((s) => s.canvasV2.session.isStaging); const isSelected = useToolIsSelected('move');
const isStaging = useAppSelector((s) => s.canvasSession.isStaging);
const isDrawingToolAllowed = useAppSelector((s) => { const isDrawingToolAllowed = useAppSelector((s) => {
if (!s.canvasV2.selectedEntityIdentifier?.type) { if (!s.canvasV2.present.selectedEntityIdentifier?.type) {
return false; return false;
} }
return isDrawableEntityType(s.canvasV2.selectedEntityIdentifier.type); return isDrawableEntityType(s.canvasV2.present.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]);
const onClick = useCallback(() => { useHotkeys('v', selectMove, { enabled: !isDisabled || isSelected }, [isDisabled, isSelected, selectMove]);
dispatch(toolChanged('move'));
}, [dispatch]);
useHotkeys('v', onClick, { enabled: !isDisabled || isSelected }, [isDisabled, isSelected, onClick]);
return ( return (
<IconButton <IconButton
@@ -39,7 +35,7 @@ export const ToolMoveButton = memo(() => {
icon={<PiCursorBold />} icon={<PiCursorBold />}
colorScheme={isSelected ? 'invokeBlue' : 'base'} colorScheme={isSelected ? 'invokeBlue' : 'base'}
variant="outline" variant="outline"
onClick={onClick} onClick={selectMove}
isDisabled={isDisabled} isDisabled={isDisabled}
/> />
); );

View File

@@ -1,37 +1,33 @@
import { IconButton } from '@invoke-ai/ui-library'; import { IconButton } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
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 { toolChanged } from 'features/controlLayers/store/canvasV2Slice';
import { isDrawableEntityType } from 'features/controlLayers/store/types'; import { isDrawableEntityType } from 'features/controlLayers/store/types';
import { memo, useCallback, 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';
import { PiRectangleBold } from 'react-icons/pi'; import { PiRectangleBold } from 'react-icons/pi';
export const ToolRectButton = memo(() => { export const ToolRectButton = memo(() => {
const { t } = useTranslation(); const { t } = useTranslation();
const dispatch = useAppDispatch(); const selectRect = useSelectTool('rect');
const isSelected = useAppSelector((s) => s.canvasV2.tool.selected === 'rect'); const isSelected = useToolIsSelected('rect');
const isFiltering = useIsFiltering(); const isFiltering = useIsFiltering();
const isTransforming = useIsTransforming(); const isTransforming = useIsTransforming();
const isStaging = useAppSelector((s) => s.canvasV2.session.isStaging); const isStaging = useAppSelector((s) => s.canvasSession.isStaging);
const isDrawingToolAllowed = useAppSelector((s) => { const isDrawingToolAllowed = useAppSelector((s) => {
if (!s.canvasV2.selectedEntityIdentifier?.type) { if (!s.canvasV2.present.selectedEntityIdentifier?.type) {
return false; return false;
} }
return isDrawableEntityType(s.canvasV2.selectedEntityIdentifier.type); return isDrawableEntityType(s.canvasV2.present.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]);
const onClick = useCallback(() => { useHotkeys('u', selectRect, { enabled: !isDisabled || isSelected }, [isDisabled, isSelected, selectRect]);
dispatch(toolChanged('rect'));
}, [dispatch]);
useHotkeys('u', onClick, { enabled: !isDisabled || isSelected }, [isDisabled, isSelected, onClick]);
return ( return (
<IconButton <IconButton
@@ -40,7 +36,7 @@ export const ToolRectButton = memo(() => {
icon={<PiRectangleBold />} icon={<PiRectangleBold />}
colorScheme={isSelected ? 'invokeBlue' : 'base'} colorScheme={isSelected ? 'invokeBlue' : 'base'}
variant="outline" variant="outline"
onClick={onClick} onClick={selectRect}
isDisabled={isDisabled} isDisabled={isDisabled}
/> />
); );

View File

@@ -0,0 +1,19 @@
import { useStore } from '@nanostores/react';
import { ToolBrushWidth } from 'features/controlLayers/components/Tool/ToolBrushWidth';
import { ToolEraserWidth } from 'features/controlLayers/components/Tool/ToolEraserWidth';
import { useCanvasManager } from 'features/controlLayers/contexts/CanvasManagerProviderGate';
import { memo } from 'react';
export const ToolSettings = memo(() => {
const canvasManager = useCanvasManager();
const tool = useStore(canvasManager.stateApi.$tool);
if (tool === 'brush') {
return <ToolBrushWidth />;
}
if (tool === 'eraser') {
return <ToolEraserWidth />;
}
return null;
});
ToolSettings.displayName = 'ToolSettings';

View File

@@ -1,28 +1,25 @@
import { IconButton } from '@invoke-ai/ui-library'; import { IconButton } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
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 { toolChanged } from 'features/controlLayers/store/canvasV2Slice'; import { memo, useMemo } from 'react';
import { memo, useCallback, 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';
import { PiHandBold } from 'react-icons/pi'; import { PiHandBold } from 'react-icons/pi';
export const ToolViewButton = memo(() => { export const ToolViewButton = memo(() => {
const { t } = useTranslation(); const { t } = useTranslation();
const dispatch = useAppDispatch();
const isTransforming = useIsTransforming(); const isTransforming = useIsTransforming();
const isFiltering = useIsFiltering(); const isFiltering = useIsFiltering();
const isStaging = useAppSelector((s) => s.canvasV2.session.isStaging); const isStaging = useAppSelector((s) => s.canvasSession.isStaging);
const isSelected = useAppSelector((s) => s.canvasV2.tool.selected === 'view'); const selectView = useSelectTool('view');
const isSelected = useToolIsSelected('view');
const isDisabled = useMemo(() => { const isDisabled = useMemo(() => {
return isTransforming || isFiltering || isStaging; return isTransforming || isFiltering || isStaging;
}, [isFiltering, isStaging, isTransforming]); }, [isFiltering, isStaging, isTransforming]);
const onClick = useCallback(() => {
dispatch(toolChanged('view'));
}, [dispatch]);
useHotkeys('h', onClick, { enabled: !isDisabled || isSelected }, [onClick, isSelected, isDisabled]); useHotkeys('h', selectView, { enabled: !isDisabled || isSelected }, [selectView, isSelected, isDisabled]);
return ( return (
<IconButton <IconButton
@@ -31,7 +28,7 @@ export const ToolViewButton = memo(() => {
icon={<PiHandBold />} icon={<PiHandBold />}
colorScheme={isSelected ? 'invokeBlue' : 'base'} colorScheme={isSelected ? 'invokeBlue' : 'base'}
variant="outline" variant="outline"
onClick={onClick} onClick={selectView}
isDisabled={isDisabled} isDisabled={isDisabled}
/> />
); );

View File

@@ -0,0 +1,19 @@
import { useStore } from '@nanostores/react';
import { useCanvasManager } from 'features/controlLayers/contexts/CanvasManagerProviderGate';
import type { Tool } from 'features/controlLayers/store/types';
import { computed } from 'nanostores';
import { useCallback } from 'react';
export const useToolIsSelected = (tool: Tool) => {
const canvasManager = useCanvasManager();
const isSelected = useStore(computed(canvasManager.stateApi.$tool, (t) => t === tool));
return isSelected;
};
export const useSelectTool = (tool: Tool) => {
const canvasManager = useCanvasManager();
const setTool = useCallback(() => {
canvasManager.stateApi.$tool.set(tool);
}, [canvasManager.stateApi.$tool, tool]);
return setTool;
};

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

@@ -56,7 +56,7 @@ export const CanvasEntityHeader = memo(({ children, ...rest }: FlexProps) => {
}, [entityIdentifier]); }, [entityIdentifier]);
return ( return (
<ContextMenu renderMenu={renderMenu}> <ContextMenu renderMenu={renderMenu} stopImmediatePropagation>
{(ref) => ( {(ref) => (
<Flex ref={ref} gap={2} alignItems="center" p={2} {...rest}> <Flex ref={ref} gap={2} alignItems="center" p={2} {...rest}>
{children} {children}

View File

@@ -7,8 +7,8 @@ import {
entityArrangedForwardOne, entityArrangedForwardOne,
entityArrangedToBack, entityArrangedToBack,
entityArrangedToFront, entityArrangedToFront,
selectCanvasV2Slice,
} from 'features/controlLayers/store/canvasV2Slice'; } from 'features/controlLayers/store/canvasV2Slice';
import { selectCanvasV2Slice } from 'features/controlLayers/store/selectors';
import type { CanvasEntityIdentifier, CanvasV2State } from 'features/controlLayers/store/types'; import type { CanvasEntityIdentifier, CanvasV2State } 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';

View File

@@ -15,7 +15,8 @@ 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, selectEntity } from 'features/controlLayers/store/canvasV2Slice'; import { entityOpacityChanged } from 'features/controlLayers/store/canvasV2Slice';
import { selectEntity } 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';
@@ -58,13 +59,13 @@ 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((s) => s.canvasV2.present.selectedEntityIdentifier);
const opacity = useAppSelector((s) => { const opacity = useAppSelector((s) => {
const selectedEntityIdentifier = s.canvasV2.selectedEntityIdentifier; const selectedEntityIdentifier = s.canvasV2.present.selectedEntityIdentifier;
if (!selectedEntityIdentifier) { if (!selectedEntityIdentifier) {
return null; return null;
} }
const selectedEntity = selectEntity(s.canvasV2, selectedEntityIdentifier); const selectedEntity = selectEntity(s.canvasV2.present, 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/canvasV2Slice'; import { selectCanvasV2Slice, 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';

View File

@@ -1,11 +1,16 @@
import type { CanvasEntityIdentifier } from 'features/controlLayers/store/types'; import type { CanvasEntityIdentifier, CanvasEntityType } from 'features/controlLayers/store/types';
import { createContext, useContext } from 'react'; import { createContext, useContext } from 'react';
import { assert } from 'tsafe'; import { assert } from 'tsafe';
export const EntityIdentifierContext = createContext<CanvasEntityIdentifier | null>(null); export const EntityIdentifierContext = createContext<CanvasEntityIdentifier | null>(null);
export const useEntityIdentifierContext = (): CanvasEntityIdentifier => { export const useEntityIdentifierContext = <T extends CanvasEntityType | undefined = CanvasEntityType>(
type?: T
): CanvasEntityIdentifier<T extends undefined ? CanvasEntityType : T> => {
const entityIdentifier = useContext(EntityIdentifierContext); const entityIdentifier = useContext(EntityIdentifierContext);
assert(entityIdentifier, 'useEntityIdentifier must be used within a EntityIdentifierProvider'); assert(entityIdentifier, 'useEntityIdentifier must be used within a EntityIdentifierProvider');
return entityIdentifier; if (type) {
assert(entityIdentifier.type === type, 'useEntityIdentifier must be used with the correct type');
}
return entityIdentifier as CanvasEntityIdentifier<T extends undefined ? CanvasEntityType : T>;
}; };

View File

@@ -1,7 +1,8 @@
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, selectCanvasV2Slice } from 'features/controlLayers/store/canvasV2Slice'; import { entityDeleted } from 'features/controlLayers/store/canvasV2Slice';
import { selectCanvasV2Slice } 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';
@@ -14,7 +15,7 @@ export function useCanvasDeleteLayerHotkey() {
useAssertSingleton(useCanvasDeleteLayerHotkey.name); useAssertSingleton(useCanvasDeleteLayerHotkey.name);
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const selectedEntityIdentifier = useAppSelector(selectSelectedEntityIdentifier); const selectedEntityIdentifier = useAppSelector(selectSelectedEntityIdentifier);
const isStaging = useAppSelector((s) => s.canvasV2.session.isStaging); const isStaging = useAppSelector((s) => s.canvasSession.isStaging);
const deleteSelectedLayer = useCallback(() => { const deleteSelectedLayer = useCallback(() => {
if (selectedEntityIdentifier === null) { if (selectedEntityIdentifier === null) {

View File

@@ -1,7 +1,8 @@
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, selectCanvasV2Slice } from 'features/controlLayers/store/canvasV2Slice'; import { entityReset } from 'features/controlLayers/store/canvasV2Slice';
import { selectCanvasV2Slice } 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';

View File

@@ -1,6 +1,6 @@
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/canvasV2Slice'; import { selectCanvasV2Slice, 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';

View File

@@ -3,7 +3,7 @@ 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 selectedEntityIdentifier = useAppSelector((s) => s.canvasV2.present.selectedEntityIdentifier);
const isSelected = useMemo(() => { const isSelected = useMemo(() => {
return selectedEntityIdentifier?.id === entityIdentifier.id; return selectedEntityIdentifier?.id === entityIdentifier.id;
}, [selectedEntityIdentifier, entityIdentifier.id]); }, [selectedEntityIdentifier, entityIdentifier.id]);

View File

@@ -1,6 +1,6 @@
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/canvasV2Slice'; import { selectCanvasV2Slice, 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';

View File

@@ -1,7 +1,7 @@
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/canvasV2Slice'; import { selectCanvasV2Slice, 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';

View File

@@ -1,7 +1,7 @@
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/canvasV2Slice'; import { selectCanvasV2Slice, 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';

View File

@@ -1,6 +1,6 @@
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/canvasV2Slice'; import { selectCanvasV2Slice } 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';

View File

@@ -1,6 +1,6 @@
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/canvasV2Slice'; import { selectCanvasV2Slice } 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';

View File

@@ -1,8 +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 } from 'features/controlLayers/store/canvasV2Slice'; import { selectCanvasV2Slice, selectEntityOrThrow } from 'features/controlLayers/store/selectors';
import { selectControlLayerEntityOrThrow } from 'features/controlLayers/store/controlLayersReducers';
import type { import type {
CanvasEntityIdentifier, CanvasEntityIdentifier,
ControlNetConfig, ControlNetConfig,
@@ -14,11 +13,11 @@ import { zModelIdentifierField } from 'features/nodes/types/common';
import { useMemo } from 'react'; import { useMemo } from 'react';
import { useControlNetAndT2IAdapterModels, useIPAdapterModels } from 'services/api/hooks/modelsByType'; import { useControlNetAndT2IAdapterModels, useIPAdapterModels } from 'services/api/hooks/modelsByType';
export const useControlLayerControlAdapter = (entityIdentifier: CanvasEntityIdentifier) => { export const useControlLayerControlAdapter = (entityIdentifier: CanvasEntityIdentifier<'control_layer'>) => {
const selectControlAdapter = useMemo( const selectControlAdapter = useMemo(
() => () =>
createMemoizedAppSelector(selectCanvasV2Slice, (canvasV2) => { createMemoizedAppSelector(selectCanvasV2Slice, (canvasV2) => {
const layer = selectControlLayerEntityOrThrow(canvasV2, entityIdentifier.id); const layer = selectEntityOrThrow(canvasV2, entityIdentifier);
return layer.controlAdapter; return layer.controlAdapter;
}), }),
[entityIdentifier] [entityIdentifier]
@@ -31,7 +30,7 @@ export const useControlLayerControlAdapter = (entityIdentifier: CanvasEntityIden
export const useDefaultControlAdapter = (): ControlNetConfig | T2IAdapterConfig => { export const useDefaultControlAdapter = (): ControlNetConfig | T2IAdapterConfig => {
const [modelConfigs] = useControlNetAndT2IAdapterModels(); const [modelConfigs] = useControlNetAndT2IAdapterModels();
const baseModel = useAppSelector((s) => s.canvasV2.params.model?.base); const baseModel = useAppSelector((s) => s.params.model?.base);
const defaultControlAdapter = useMemo(() => { const defaultControlAdapter = useMemo(() => {
const compatibleModels = modelConfigs.filter((m) => (baseModel ? m.base === baseModel : true)); const compatibleModels = modelConfigs.filter((m) => (baseModel ? m.base === baseModel : true));
@@ -52,7 +51,7 @@ export const useDefaultControlAdapter = (): ControlNetConfig | T2IAdapterConfig
export const useDefaultIPAdapter = (): IPAdapterConfig => { export const useDefaultIPAdapter = (): IPAdapterConfig => {
const [modelConfigs] = useIPAdapterModels(); const [modelConfigs] = useIPAdapterModels();
const baseModel = useAppSelector((s) => s.canvasV2.params.model?.base); const baseModel = useAppSelector((s) => s.params.model?.base);
const defaultControlAdapter = useMemo(() => { const defaultControlAdapter = useMemo(() => {
const compatibleModels = modelConfigs.filter((m) => (baseModel ? m.base === baseModel : true)); const compatibleModels = modelConfigs.filter((m) => (baseModel ? m.base === baseModel : true));

View File

@@ -1,29 +1,35 @@
import { getArbitraryBaseColor } from '@invoke-ai/ui-library'; import { getArbitraryBaseColor } from '@invoke-ai/ui-library';
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager'; import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
import { CanvasModuleBase } from 'features/controlLayers/konva/CanvasModuleBase';
import { getPrefixedId } from 'features/controlLayers/konva/util'; import { getPrefixedId } from 'features/controlLayers/konva/util';
import Konva from 'konva'; import Konva from 'konva';
import type { Logger } from 'roarr';
export class CanvasBackgroundModule { export class CanvasBackgroundModule extends CanvasModuleBase {
readonly type = 'background_grid'; readonly type = 'background';
static GRID_LINE_COLOR_COARSE = getArbitraryBaseColor(27); static GRID_LINE_COLOR_COARSE = getArbitraryBaseColor(27);
static GRID_LINE_COLOR_FINE = getArbitraryBaseColor(18); static GRID_LINE_COLOR_FINE = getArbitraryBaseColor(18);
id: string; id: string;
path: string[];
manager: CanvasManager; manager: CanvasManager;
subscriptions = new Set<() => void>();
log: Logger;
konva: { konva: {
layer: Konva.Layer; layer: Konva.Layer;
}; };
/**
* A set of subscriptions that should be cleaned up when the transformer is destroyed.
*/
subscriptions: Set<() => void> = new Set();
constructor(manager: CanvasManager) { constructor(manager: CanvasManager) {
super();
this.id = getPrefixedId(this.type); this.id = getPrefixedId(this.type);
this.manager = manager; this.manager = manager;
this.path = this.manager.path.concat(this.id);
this.log = this.manager.buildLogger(this.getLoggingContext);
this.log.debug('Creating background module');
this.konva = { layer: new Konva.Layer({ name: `${this.type}:layer`, listening: false }) }; this.konva = { layer: new Konva.Layer({ name: `${this.type}:layer`, listening: false }) };
this.subscriptions.add( this.subscriptions.add(
@@ -116,9 +122,8 @@ export class CanvasBackgroundModule {
} }
destroy = () => { destroy = () => {
for (const cleanup of this.subscriptions) { this.log.trace('Destroying background module');
cleanup(); this.subscriptions.forEach((unsubscribe) => unsubscribe());
}
this.konva.layer.destroy(); this.konva.layer.destroy();
}; };
@@ -145,4 +150,16 @@ export class CanvasBackgroundModule {
} }
return 256; return 256;
}; };
repr = () => {
return {
id: this.id,
path: this.path,
type: this.type,
};
};
getLoggingContext = () => {
return { ...this.manager.getLoggingContext(), path: this.path.join('.') };
};
} }

View File

@@ -1,6 +1,7 @@
import type { SerializableObject } from 'common/types'; import type { SerializableObject } from 'common/types';
import { roundToMultiple, roundToMultipleMin } from 'common/util/roundDownToMultiple'; import { roundToMultiple, roundToMultipleMin } from 'common/util/roundDownToMultiple';
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager'; import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
import { CanvasModuleBase } from 'features/controlLayers/konva/CanvasModuleBase';
import type { CanvasPreviewModule } from 'features/controlLayers/konva/CanvasPreviewModule'; import type { CanvasPreviewModule } from 'features/controlLayers/konva/CanvasPreviewModule';
import { getPrefixedId } from 'features/controlLayers/konva/util'; import { getPrefixedId } from 'features/controlLayers/konva/util';
import type { Rect } from 'features/controlLayers/store/types'; import type { Rect } from 'features/controlLayers/store/types';
@@ -22,14 +23,16 @@ const ALL_ANCHORS: string[] = [
const CORNER_ANCHORS: string[] = ['top-left', 'top-right', 'bottom-left', 'bottom-right']; const CORNER_ANCHORS: string[] = ['top-left', 'top-right', 'bottom-left', 'bottom-right'];
const NO_ANCHORS: string[] = []; const NO_ANCHORS: string[] = [];
export class CanvasBboxModule { export class CanvasBboxModule extends CanvasModuleBase {
readonly type = 'generation_bbox'; readonly type = 'bbox';
id: string; id: string;
path: string[]; path: string[];
parent: CanvasPreviewModule;
manager: CanvasManager; manager: CanvasManager;
log: Logger; log: Logger;
subscriptions: Set<() => void> = new Set();
parent: CanvasPreviewModule;
konva: { konva: {
group: Konva.Group; group: Konva.Group;
@@ -38,13 +41,14 @@ export class CanvasBboxModule {
}; };
constructor(parent: CanvasPreviewModule) { constructor(parent: CanvasPreviewModule) {
super();
this.id = getPrefixedId(this.type); this.id = getPrefixedId(this.type);
this.parent = parent; this.parent = parent;
this.manager = this.parent.manager; this.manager = this.parent.manager;
this.path = this.manager.path.concat(this.id); this.path = this.parent.path.concat(this.id);
this.log = this.manager.buildLogger(this.getLoggingContext); this.log = this.manager.buildLogger(this.getLoggingContext);
this.log.trace('Creating bbox'); this.log.debug('Creating bbox module');
// Create a stash to hold onto the last aspect ratio of the bbox - this allows for locking the aspect ratio when // Create a stash to hold onto the last aspect ratio of the bbox - this allows for locking the aspect ratio when
// transforming the bbox. // transforming the bbox.
@@ -228,17 +232,19 @@ export class CanvasBboxModule {
this.konva.transformer.nodes([this.konva.rect]); this.konva.transformer.nodes([this.konva.rect]);
this.konva.group.add(this.konva.rect); this.konva.group.add(this.konva.rect);
this.konva.group.add(this.konva.transformer); this.konva.group.add(this.konva.transformer);
this.subscriptions.add(this.manager.stateApi.$tool.listen(this.render));
} }
render() { render = () => {
this.log.trace('Rendering generation bbox'); this.log.trace('Rendering bbox module');
const bbox = this.manager.stateApi.getBbox(); const bbox = this.manager.stateApi.getBbox();
const toolState = this.manager.stateApi.getToolState(); const tool = this.manager.stateApi.$tool.get();
this.konva.group.visible(true); this.konva.group.visible(true);
this.parent.getLayer().listening(toolState.selected === 'bbox'); this.parent.getLayer().listening(tool === 'bbox');
this.konva.group.listening(toolState.selected === 'bbox'); this.konva.group.listening(tool === 'bbox');
this.konva.rect.setAttrs({ this.konva.rect.setAttrs({
x: bbox.rect.x, x: bbox.rect.x,
y: bbox.rect.y, y: bbox.rect.y,
@@ -246,15 +252,29 @@ export class CanvasBboxModule {
height: bbox.rect.height, height: bbox.rect.height,
scaleX: 1, scaleX: 1,
scaleY: 1, scaleY: 1,
listening: toolState.selected === 'bbox', listening: tool === 'bbox',
}); });
this.konva.transformer.setAttrs({ this.konva.transformer.setAttrs({
listening: toolState.selected === 'bbox', listening: tool === 'bbox',
enabledAnchors: toolState.selected === 'bbox' ? ALL_ANCHORS : NO_ANCHORS, enabledAnchors: tool === 'bbox' ? ALL_ANCHORS : NO_ANCHORS,
}); });
} };
repr = () => {
return {
id: this.id,
type: this.type,
path: this.path,
};
};
destroy = () => {
this.log.trace('Destroying bbox module');
this.subscriptions.forEach((unsubscribe) => unsubscribe());
this.konva.group.destroy();
};
getLoggingContext = (): SerializableObject => { getLoggingContext = (): SerializableObject => {
return { ...this.manager.getLoggingContext(), path: this.path.join('.') }; return { ...this.parent.getLoggingContext(), path: this.path.join('.') };
}; };
} }

View File

@@ -1,13 +1,13 @@
import type { SerializableObject } from 'common/types';
import { rgbaColorToString } from 'common/util/colorCodeTransformers'; import { rgbaColorToString } from 'common/util/colorCodeTransformers';
import { deepClone } from 'common/util/deepClone'; import { deepClone } from 'common/util/deepClone';
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager'; import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
import { CanvasModuleBase } from 'features/controlLayers/konva/CanvasModuleBase';
import type { CanvasObjectRenderer } from 'features/controlLayers/konva/CanvasObjectRenderer'; import type { CanvasObjectRenderer } from 'features/controlLayers/konva/CanvasObjectRenderer';
import type { CanvasBrushLineState } from 'features/controlLayers/store/types'; import type { CanvasBrushLineState } from 'features/controlLayers/store/types';
import Konva from 'konva'; import Konva from 'konva';
import type { Logger } from 'roarr'; import type { Logger } from 'roarr';
export class CanvasBrushLineRenderer { export class CanvasBrushLineRenderer extends CanvasModuleBase {
readonly type = 'brush_line_renderer'; readonly type = 'brush_line_renderer';
id: string; id: string;
@@ -15,6 +15,7 @@ export class CanvasBrushLineRenderer {
parent: CanvasObjectRenderer; parent: CanvasObjectRenderer;
manager: CanvasManager; manager: CanvasManager;
log: Logger; log: Logger;
subscriptions = new Set<() => void>();
state: CanvasBrushLineState; state: CanvasBrushLineState;
konva: { konva: {
@@ -23,6 +24,7 @@ export class CanvasBrushLineRenderer {
}; };
constructor(state: CanvasBrushLineState, parent: CanvasObjectRenderer) { constructor(state: CanvasBrushLineState, parent: CanvasObjectRenderer) {
super();
const { id, clip } = state; const { id, clip } = state;
this.id = id; this.id = id;
this.parent = parent; this.parent = parent;
@@ -30,7 +32,7 @@ export class CanvasBrushLineRenderer {
this.path = this.parent.path.concat(this.id); this.path = this.parent.path.concat(this.id);
this.log = this.manager.buildLogger(this.getLoggingContext); this.log = this.manager.buildLogger(this.getLoggingContext);
this.log.trace({ state }, 'Creating brush line'); this.log.debug({ state }, 'Creating brush line renderer module');
this.konva = { this.konva = {
group: new Konva.Group({ group: new Konva.Group({
@@ -69,26 +71,28 @@ export class CanvasBrushLineRenderer {
return false; return false;
} }
destroy() { destroy = () => {
this.log.trace('Destroying brush line'); this.log.debug('Destroying brush line renderer module');
this.subscriptions.forEach((unsubscribe) => unsubscribe());
this.konva.group.destroy(); this.konva.group.destroy();
} };
setVisibility(isVisible: boolean): void { setVisibility(isVisible: boolean): void {
this.log.trace({ isVisible }, 'Setting brush line visibility'); this.log.trace({ isVisible }, 'Setting brush line visibility');
this.konva.group.visible(isVisible); this.konva.group.visible(isVisible);
} }
repr() { repr = () => {
return { return {
id: this.id, id: this.id,
type: this.type, type: this.type,
path: this.path,
parent: this.parent.id, parent: this.parent.id,
state: deepClone(this.state), state: deepClone(this.state),
}; };
} };
getLoggingContext = (): SerializableObject => { getLoggingContext = () => {
return { ...this.parent.getLoggingContext(), path: this.path.join('.') }; return { ...this.parent.getLoggingContext(), path: this.path.join('.') };
}; };
} }

View File

@@ -1,26 +1,31 @@
import type { SerializableObject } from 'common/types';
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager'; import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
import { CanvasModuleBase } from 'features/controlLayers/konva/CanvasModuleBase';
import { getPrefixedId } from 'features/controlLayers/konva/util'; import { getPrefixedId } from 'features/controlLayers/konva/util';
import type { GenerationMode } from 'features/controlLayers/store/types'; import type { GenerationMode } from 'features/controlLayers/store/types';
import { LRUCache } from 'lru-cache'; import { LRUCache } from 'lru-cache';
import type { Logger } from 'roarr'; import type { Logger } from 'roarr';
export class CanvasCacheModule { export class CanvasCacheModule extends CanvasModuleBase {
readonly type = 'cache';
id: string; id: string;
path: string[]; path: string[];
log: Logger; log: Logger;
manager: CanvasManager; manager: CanvasManager;
subscriptions = new Set<() => void>();
imageNameCache = new LRUCache<string, string>({ max: 100 }); imageNameCache = new LRUCache<string, string>({ max: 100 });
canvasElementCache = new LRUCache<string, HTMLCanvasElement>({ max: 32 }); canvasElementCache = new LRUCache<string, HTMLCanvasElement>({ max: 32 });
generationModeCache = new LRUCache<string, GenerationMode>({ max: 100 }); generationModeCache = new LRUCache<string, GenerationMode>({ max: 100 });
constructor(manager: CanvasManager) { constructor(manager: CanvasManager) {
super();
this.id = getPrefixedId('cache'); this.id = getPrefixedId('cache');
this.manager = manager; this.manager = manager;
this.path = this.manager.path.concat(this.id); this.path = this.manager.path.concat(this.id);
this.log = this.manager.buildLogger(this.getLoggingContext); this.log = this.manager.buildLogger(this.getLoggingContext);
this.log.debug('Creating canvas cache');
this.log.debug('Creating cache module');
} }
clearAll = () => { clearAll = () => {
@@ -29,7 +34,21 @@ export class CanvasCacheModule {
this.generationModeCache.clear(); this.generationModeCache.clear();
}; };
getLoggingContext = (): SerializableObject => { repr = () => {
return {
id: this.id,
path: this.path,
type: this.type,
};
};
destroy = () => {
this.log.debug('Destroying cache module');
this.subscriptions.forEach((unsubscribe) => unsubscribe());
this.clearAll();
};
getLoggingContext = () => {
return { ...this.manager.getLoggingContext(), path: this.path.join('.') }; return { ...this.manager.getLoggingContext(), path: this.path.join('.') };
}; };
} }

View File

@@ -1,5 +1,6 @@
import type { SerializableObject } from 'common/types'; import type { SerializableObject } from 'common/types';
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager'; import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
import { CanvasModuleBase } from 'features/controlLayers/konva/CanvasModuleBase';
import { import {
canvasToBlob, canvasToBlob,
canvasToImageData, canvasToImageData,
@@ -14,18 +15,22 @@ import type { ImageDTO } from 'services/api/types';
import stableHash from 'stable-hash'; import stableHash from 'stable-hash';
import { assert } from 'tsafe'; import { assert } from 'tsafe';
export class CanvasCompositorModule { export class CanvasCompositorModule extends CanvasModuleBase {
readonly type = 'compositor';
id: string; id: string;
path: string[]; path: string[];
log: Logger; log: Logger;
manager: CanvasManager; manager: CanvasManager;
subscriptions = new Set<() => void>();
constructor(manager: CanvasManager) { constructor(manager: CanvasManager) {
super();
this.id = getPrefixedId('canvas_compositor'); this.id = getPrefixedId('canvas_compositor');
this.manager = manager; this.manager = manager;
this.path = this.manager.path.concat(this.id); this.path = this.manager.path.concat(this.id);
this.log = this.manager.buildLogger(this.getLoggingContext); this.log = this.manager.buildLogger(this.getLoggingContext);
this.log.debug('Creating canvas compositor'); this.log.debug('Creating compositor module');
} }
getCompositeRasterLayerEntityIds = (): string[] => { getCompositeRasterLayerEntityIds = (): string[] => {
@@ -237,7 +242,20 @@ export class CanvasCompositorModule {
return generationMode; return generationMode;
} }
getLoggingContext = (): SerializableObject => { repr = () => {
return {
id: this.id,
type: this.type,
path: this.path,
};
};
destroy = () => {
this.log.trace('Destroying compositor module');
this.subscriptions.forEach((unsubscribe) => unsubscribe());
};
getLoggingContext = () => {
return { ...this.manager.getLoggingContext(), path: this.path.join('.') }; return { ...this.manager.getLoggingContext(), path: this.path.join('.') };
}; };
} }

View File

@@ -1,12 +1,12 @@
import type { SerializableObject } from 'common/types';
import { deepClone } from 'common/util/deepClone'; import { deepClone } from 'common/util/deepClone';
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager'; import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
import { CanvasModuleBase } from 'features/controlLayers/konva/CanvasModuleBase';
import type { CanvasObjectRenderer } from 'features/controlLayers/konva/CanvasObjectRenderer'; import type { CanvasObjectRenderer } from 'features/controlLayers/konva/CanvasObjectRenderer';
import type { CanvasEraserLineState } from 'features/controlLayers/store/types'; import type { CanvasEraserLineState } from 'features/controlLayers/store/types';
import Konva from 'konva'; import Konva from 'konva';
import type { Logger } from 'roarr'; import type { Logger } from 'roarr';
export class CanvasEraserLineRenderer { export class CanvasEraserLineRenderer extends CanvasModuleBase {
readonly type = 'eraser_line_renderer'; readonly type = 'eraser_line_renderer';
id: string; id: string;
@@ -14,6 +14,7 @@ export class CanvasEraserLineRenderer {
parent: CanvasObjectRenderer; parent: CanvasObjectRenderer;
manager: CanvasManager; manager: CanvasManager;
log: Logger; log: Logger;
subscriptions = new Set<() => void>();
state: CanvasEraserLineState; state: CanvasEraserLineState;
konva: { konva: {
@@ -22,6 +23,7 @@ export class CanvasEraserLineRenderer {
}; };
constructor(state: CanvasEraserLineState, parent: CanvasObjectRenderer) { constructor(state: CanvasEraserLineState, parent: CanvasObjectRenderer) {
super();
const { id, clip } = state; const { id, clip } = state;
this.id = id; this.id = id;
this.parent = parent; this.parent = parent;
@@ -29,7 +31,7 @@ export class CanvasEraserLineRenderer {
this.path = this.parent.path.concat(this.id); this.path = this.parent.path.concat(this.id);
this.log = this.manager.buildLogger(this.getLoggingContext); this.log = this.manager.buildLogger(this.getLoggingContext);
this.log.trace({ state }, 'Creating eraser line'); this.log.debug({ state }, 'Creating eraser line renderer module');
this.konva = { this.konva = {
group: new Konva.Group({ group: new Konva.Group({
@@ -68,26 +70,28 @@ export class CanvasEraserLineRenderer {
return false; return false;
} }
destroy() { destroy = () => {
this.log.trace('Destroying eraser line'); this.log.debug('Destroying eraser line renderer module');
this.subscriptions.forEach((unsubscribe) => unsubscribe());
this.konva.group.destroy(); this.konva.group.destroy();
} };
setVisibility(isVisible: boolean): void { setVisibility(isVisible: boolean): void {
this.log.trace({ isVisible }, 'Setting brush line visibility'); this.log.trace({ isVisible }, 'Setting brush line visibility');
this.konva.group.visible(isVisible); this.konva.group.visible(isVisible);
} }
repr() { repr = () => {
return { return {
id: this.id, id: this.id,
type: this.type, type: this.type,
path: this.path,
parent: this.parent.id, parent: this.parent.id,
state: deepClone(this.state), state: deepClone(this.state),
}; };
} };
getLoggingContext = (): SerializableObject => { getLoggingContext = () => {
return { ...this.parent.getLoggingContext(), path: this.path.join('.') }; return { ...this.parent.getLoggingContext(), path: this.path.join('.') };
}; };
} }

View File

@@ -1,6 +1,7 @@
import type { SerializableObject } from 'common/types'; import type { SerializableObject } from 'common/types';
import type { CanvasLayerAdapter } from 'features/controlLayers/konva/CanvasLayerAdapter'; import type { CanvasLayerAdapter } from 'features/controlLayers/konva/CanvasLayerAdapter';
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager'; import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
import { CanvasModuleBase } from 'features/controlLayers/konva/CanvasModuleBase';
import { getPrefixedId } from 'features/controlLayers/konva/util'; import { getPrefixedId } from 'features/controlLayers/konva/util';
import type { CanvasEntityIdentifier, CanvasImageState, FilterConfig } from 'features/controlLayers/store/types'; import type { CanvasEntityIdentifier, CanvasImageState, FilterConfig } from 'features/controlLayers/store/types';
import { IMAGE_FILTERS, imageDTOToImageObject } from 'features/controlLayers/store/types'; import { IMAGE_FILTERS, imageDTOToImageObject } from 'features/controlLayers/store/types';
@@ -10,15 +11,14 @@ import { getImageDTO } from 'services/api/endpoints/images';
import type { BatchConfig, ImageDTO, S } from 'services/api/types'; import type { BatchConfig, ImageDTO, S } from 'services/api/types';
import { assert } from 'tsafe'; import { assert } from 'tsafe';
const TYPE = 'entity_filter_preview'; export class CanvasFilterModule extends CanvasModuleBase {
readonly type = 'canvas_filter';
export class CanvasFilterModule {
readonly type = TYPE;
id: string; id: string;
path: string[]; path: string[];
manager: CanvasManager; manager: CanvasManager;
log: Logger; log: Logger;
subscriptions = new Set<() => void>();
imageState: CanvasImageState | null = null; imageState: CanvasImageState | null = null;
@@ -28,11 +28,13 @@ export class CanvasFilterModule {
$config = atom<FilterConfig>(IMAGE_FILTERS.canny_image_processor.buildDefaults()); $config = atom<FilterConfig>(IMAGE_FILTERS.canny_image_processor.buildDefaults());
constructor(manager: CanvasManager) { constructor(manager: CanvasManager) {
super();
this.id = getPrefixedId(this.type); this.id = getPrefixedId(this.type);
this.manager = manager; this.manager = manager;
this.path = this.manager.path.concat(this.id); this.path = this.manager.path.concat(this.id);
this.log = this.manager.buildLogger(this.getLoggingContext); this.log = this.manager.buildLogger(this.getLoggingContext);
this.log.trace('Creating filter');
this.log.debug('Creating filter module');
} }
initialize = (entityIdentifier: CanvasEntityIdentifier) => { initialize = (entityIdentifier: CanvasEntityIdentifier) => {
@@ -47,7 +49,7 @@ export class CanvasFilterModule {
return; return;
} }
this.$adapter.set(entity.adapter); this.$adapter.set(entity.adapter);
this.manager.stateApi.setTool('view'); this.manager.stateApi.$tool.set('view');
}; };
previewFilter = async () => { previewFilter = async () => {
@@ -167,17 +169,19 @@ export class CanvasFilterModule {
}; };
destroy = () => { destroy = () => {
this.log.trace('Destroying filter'); this.log.trace('Destroying filter module');
this.subscriptions.forEach((unsubscribe) => unsubscribe());
}; };
repr = () => { repr = () => {
return { return {
id: this.id, id: this.id,
type: this.type, type: this.type,
path: this.path,
}; };
}; };
getLoggingContext = (): SerializableObject => { getLoggingContext = () => {
return { ...this.manager.getLoggingContext(), path: this.path.join('.') }; return { ...this.manager.getLoggingContext(), path: this.path.join('.') };
}; };
} }

View File

@@ -1,8 +1,8 @@
import { Mutex } from 'async-mutex'; import { Mutex } from 'async-mutex';
import type { SerializableObject } from 'common/types';
import { deepClone } from 'common/util/deepClone'; import { deepClone } from 'common/util/deepClone';
import type { CanvasFilterModule } from 'features/controlLayers/konva/CanvasFilterModule'; import type { CanvasFilterModule } from 'features/controlLayers/konva/CanvasFilterModule';
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager'; import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
import { CanvasModuleBase } from 'features/controlLayers/konva/CanvasModuleBase';
import type { CanvasObjectRenderer } from 'features/controlLayers/konva/CanvasObjectRenderer'; import type { CanvasObjectRenderer } from 'features/controlLayers/konva/CanvasObjectRenderer';
import type { CanvasStagingAreaModule } from 'features/controlLayers/konva/CanvasStagingAreaModule'; import type { CanvasStagingAreaModule } from 'features/controlLayers/konva/CanvasStagingAreaModule';
import { loadImage } from 'features/controlLayers/konva/util'; import { loadImage } from 'features/controlLayers/konva/util';
@@ -12,7 +12,7 @@ import Konva from 'konva';
import type { Logger } from 'roarr'; import type { Logger } from 'roarr';
import { getImageDTO } from 'services/api/endpoints/images'; import { getImageDTO } from 'services/api/endpoints/images';
export class CanvasImageRenderer { export class CanvasImageRenderer extends CanvasModuleBase {
readonly type = 'image_renderer'; readonly type = 'image_renderer';
id: string; id: string;
@@ -20,6 +20,7 @@ export class CanvasImageRenderer {
parent: CanvasObjectRenderer | CanvasStagingAreaModule | CanvasFilterModule; parent: CanvasObjectRenderer | CanvasStagingAreaModule | CanvasFilterModule;
manager: CanvasManager; manager: CanvasManager;
log: Logger; log: Logger;
subscriptions = new Set<() => void>();
state: CanvasImageState; state: CanvasImageState;
konva: { konva: {
@@ -33,6 +34,7 @@ export class CanvasImageRenderer {
mutex = new Mutex(); mutex = new Mutex();
constructor(state: CanvasImageState, parent: CanvasObjectRenderer | CanvasStagingAreaModule | CanvasFilterModule) { constructor(state: CanvasImageState, parent: CanvasObjectRenderer | CanvasStagingAreaModule | CanvasFilterModule) {
super();
const { id, image } = state; const { id, image } = state;
const { width, height } = image; const { width, height } = image;
this.id = id; this.id = id;
@@ -41,7 +43,7 @@ export class CanvasImageRenderer {
this.path = this.parent.path.concat(this.id); this.path = this.parent.path.concat(this.id);
this.log = this.manager.buildLogger(this.getLoggingContext); this.log = this.manager.buildLogger(this.getLoggingContext);
this.log.trace({ state }, 'Creating image'); this.log.debug({ state }, 'Creating image renderer module');
this.konva = { this.konva = {
group: new Konva.Group({ name: `${this.type}:group`, listening: false }), group: new Konva.Group({ name: `${this.type}:group`, listening: false }),
@@ -166,7 +168,8 @@ export class CanvasImageRenderer {
}; };
destroy = () => { destroy = () => {
this.log.trace('Destroying image'); this.log.debug('Destroying image renderer module');
this.subscriptions.forEach((unsubscribe) => unsubscribe());
this.konva.group.destroy(); this.konva.group.destroy();
}; };
@@ -179,6 +182,7 @@ export class CanvasImageRenderer {
return { return {
id: this.id, id: this.id,
type: this.type, type: this.type,
path: this.path,
parent: this.parent.id, parent: this.parent.id,
isLoading: this.isLoading, isLoading: this.isLoading,
isError: this.isError, isError: this.isError,
@@ -186,7 +190,7 @@ export class CanvasImageRenderer {
}; };
}; };
getLoggingContext = (): SerializableObject => { getLoggingContext = () => {
return { ...this.parent.getLoggingContext(), path: this.path.join('.') }; return { ...this.parent.getLoggingContext(), path: this.path.join('.') };
}; };
} }

View File

@@ -1,13 +1,17 @@
import type { SerializableObject } from 'common/types'; import type { SerializableObject } from 'common/types';
import { deepClone } from 'common/util/deepClone'; import { deepClone } from 'common/util/deepClone';
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager'; import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
import { CanvasModuleBase } from 'features/controlLayers/konva/CanvasModuleBase';
import { CanvasObjectRenderer } from 'features/controlLayers/konva/CanvasObjectRenderer'; import { CanvasObjectRenderer } from 'features/controlLayers/konva/CanvasObjectRenderer';
import { CanvasTransformer } from 'features/controlLayers/konva/CanvasTransformer'; import { CanvasTransformer } from 'features/controlLayers/konva/CanvasTransformer';
import { getLastPointOfLine } from 'features/controlLayers/konva/util';
import type { import type {
CanvasBrushLineState,
CanvasControlLayerState, CanvasControlLayerState,
CanvasEntityIdentifier, CanvasEntityIdentifier,
CanvasEraserLineState,
CanvasRasterLayerState, CanvasRasterLayerState,
CanvasV2State, Coordinate,
Rect, Rect,
} from 'features/controlLayers/store/types'; } from 'features/controlLayers/store/types';
import { getEntityIdentifier } from 'features/controlLayers/store/types'; import { getEntityIdentifier } from 'features/controlLayers/store/types';
@@ -18,12 +22,13 @@ import type { Logger } from 'roarr';
import stableHash from 'stable-hash'; import stableHash from 'stable-hash';
import { assert } from 'tsafe'; import { assert } from 'tsafe';
export class CanvasLayerAdapter { export class CanvasLayerAdapter extends CanvasModuleBase {
readonly type = 'layer_adapter'; readonly type = 'layer_adapter';
id: string; id: string;
path: string[]; path: string[];
manager: CanvasManager; manager: CanvasManager;
subscriptions = new Set<() => void>();
log: Logger; log: Logger;
state: CanvasRasterLayerState | CanvasControlLayerState; state: CanvasRasterLayerState | CanvasControlLayerState;
@@ -37,11 +42,14 @@ export class CanvasLayerAdapter {
isFirstRender: boolean = true; isFirstRender: boolean = true;
constructor(state: CanvasLayerAdapter['state'], manager: CanvasLayerAdapter['manager']) { constructor(state: CanvasLayerAdapter['state'], manager: CanvasLayerAdapter['manager']) {
super();
this.id = state.id; this.id = state.id;
this.manager = manager; this.manager = manager;
this.path = this.manager.path.concat(this.id); this.path = this.manager.path.concat(this.id);
this.log = this.manager.buildLogger(this.getLoggingContext); this.log = this.manager.buildLogger(this.getLoggingContext);
this.log.debug({ state }, 'Creating layer');
this.log.debug({ state }, 'Creating layer adapter module');
this.state = state; this.state = state;
this.konva = { this.konva = {
@@ -67,18 +75,14 @@ export class CanvasLayerAdapter {
}; };
destroy = (): void => { destroy = (): void => {
this.log.debug('Destroying layer'); this.log.debug('Destroying layer adapter module');
// We need to call the destroy method on all children so they can do their own cleanup. this.subscriptions.forEach((unsubscribe) => unsubscribe());
this.transformer.destroy();
this.renderer.destroy(); this.renderer.destroy();
this.transformer.destroy();
this.konva.layer.destroy(); this.konva.layer.destroy();
}; };
update = async (arg?: { update = async (arg?: { state: CanvasLayerAdapter['state'] }) => {
state: CanvasLayerAdapter['state'];
toolState: CanvasV2State['tool'];
isSelected: boolean;
}) => {
const state = get(arg, 'state', this.state); const state = get(arg, 'state', this.state);
if (!this.isFirstRender && state === this.state) { if (!this.isFirstRender && state === this.state) {
@@ -140,6 +144,7 @@ export class CanvasLayerAdapter {
return { return {
id: this.id, id: this.id,
type: this.type, type: this.type,
path: this.path,
state: deepClone(this.state), state: deepClone(this.state),
transformer: this.transformer.repr(), transformer: this.transformer.repr(),
renderer: this.renderer.repr(), renderer: this.renderer.repr(),
@@ -180,6 +185,19 @@ export class CanvasLayerAdapter {
return stableHash(arg); return stableHash(arg);
}; };
getLastPointOfLastLine = (type: CanvasBrushLineState['type'] | CanvasEraserLineState['type']): Coordinate | null => {
const lastObject = this.state.objects[this.state.objects.length - 1];
if (!lastObject) {
return null;
}
if (lastObject.type === type) {
return getLastPointOfLine(lastObject.points);
}
return null;
};
logDebugInfo(msg = 'Debug info') { logDebugInfo(msg = 'Debug info') {
const info = { const info = {
repr: this.repr(), repr: this.repr(),

View File

@@ -6,6 +6,7 @@ import { SyncableMap } from 'common/util/SyncableMap/SyncableMap';
import { CanvasCacheModule } from 'features/controlLayers/konva/CanvasCacheModule'; import { CanvasCacheModule } from 'features/controlLayers/konva/CanvasCacheModule';
import { CanvasCompositorModule } from 'features/controlLayers/konva/CanvasCompositorModule'; import { CanvasCompositorModule } from 'features/controlLayers/konva/CanvasCompositorModule';
import { CanvasFilterModule } from 'features/controlLayers/konva/CanvasFilterModule'; import { CanvasFilterModule } from 'features/controlLayers/konva/CanvasFilterModule';
import { CanvasModuleBase } from 'features/controlLayers/konva/CanvasModuleBase';
import { CanvasRenderingModule } from 'features/controlLayers/konva/CanvasRenderingModule'; import { CanvasRenderingModule } from 'features/controlLayers/konva/CanvasRenderingModule';
import { CanvasStageModule } from 'features/controlLayers/konva/CanvasStageModule'; import { CanvasStageModule } from 'features/controlLayers/konva/CanvasStageModule';
import { CanvasWorkerModule } from 'features/controlLayers/konva/CanvasWorkerModule.js'; import { CanvasWorkerModule } from 'features/controlLayers/konva/CanvasWorkerModule.js';
@@ -19,20 +20,22 @@ import type { CanvasLayerAdapter } from './CanvasLayerAdapter';
import type { CanvasMaskAdapter } from './CanvasMaskAdapter'; import type { CanvasMaskAdapter } from './CanvasMaskAdapter';
import { CanvasPreviewModule } from './CanvasPreviewModule'; import { CanvasPreviewModule } from './CanvasPreviewModule';
import { CanvasStateApiModule } from './CanvasStateApiModule'; import { CanvasStateApiModule } from './CanvasStateApiModule';
import { setStageEventHandlers } from './events';
export const $canvasManager = atom<CanvasManager | null>(null); export const $canvasManager = atom<CanvasManager | null>(null);
const TYPE = 'manager';
export class CanvasManager { export class CanvasManager extends CanvasModuleBase {
readonly type = TYPE; readonly type = 'manager';
id: string; id: string;
path: string[]; path: string[];
manager: CanvasManager;
log: Logger;
store: AppStore; store: AppStore;
socket: AppSocket; socket: AppSocket;
subscriptions = new Set<() => void>();
adapters = { adapters = {
rasterLayers: new SyncableMap<string, CanvasLayerAdapter>(), rasterLayers: new SyncableMap<string, CanvasLayerAdapter>(),
controlLayers: new SyncableMap<string, CanvasLayerAdapter>(), controlLayers: new SyncableMap<string, CanvasLayerAdapter>(),
@@ -61,8 +64,21 @@ export class CanvasManager {
_isDebugging: boolean = false; _isDebugging: boolean = false;
constructor(stage: Konva.Stage, container: HTMLDivElement, store: AppStore, socket: AppSocket) { constructor(stage: Konva.Stage, container: HTMLDivElement, store: AppStore, socket: AppSocket) {
super();
this.id = getPrefixedId(this.type); this.id = getPrefixedId(this.type);
this.path = [this.id]; this.path = [this.id];
this.manager = this;
this.log = logger('canvas').child((message) => {
return {
...message,
context: {
...this.getLoggingContext(),
...message.context,
},
};
});
this.log.debug('Creating canvas manager module');
this.store = store; this.store = store;
this.socket = socket; this.socket = socket;
@@ -81,16 +97,6 @@ export class CanvasManager {
this.stage.addLayer(this.background.konva.layer); this.stage.addLayer(this.background.konva.layer);
} }
log = logger('canvas').child((message) => {
return {
...message,
context: {
...this.getLoggingContext(),
...message.context,
},
};
});
enableDebugging() { enableDebugging() {
this._isDebugging = true; this._isDebugging = true;
this.logDebugInfo(); this.logDebugInfo();
@@ -101,37 +107,61 @@ export class CanvasManager {
} }
initialize = () => { initialize = () => {
this.log.debug('Initializing canvas manager'); this.log.debug('Initializing canvas manager module');
// These atoms require the canvas manager to be set up before we can provide their initial values // These atoms require the canvas manager to be set up before we can provide their initial values
this.stateApi.$transformingEntity.set(null); this.stateApi.$transformingEntity.set(null);
this.stateApi.$toolState.set(this.stateApi.getToolState()); this.stateApi.$toolState.set(this.stateApi.getToolState());
this.stateApi.$selectedEntityIdentifier.set(this.stateApi.getState().selectedEntityIdentifier); this.stateApi.$selectedEntityIdentifier.set(this.stateApi.getCanvasState().selectedEntityIdentifier);
this.stateApi.$currentFill.set(this.stateApi.getCurrentFill()); this.stateApi.$currentFill.set(this.stateApi.getCurrentFill());
this.stateApi.$selectedEntity.set(this.stateApi.getSelectedEntity()); this.stateApi.$selectedEntity.set(this.stateApi.getSelectedEntity());
const cleanupEventHandlers = setStageEventHandlers(this); this.subscriptions.add(this.store.subscribe(this.renderer.render));
const cleanupStage = this.stage.initialize(); this.stage.initialize();
const cleanupStore = this.store.subscribe(this.renderer.render); };
return () => { destroy = () => {
this.log.debug('Cleaning up canvas manager'); this.log.debug('Destroying canvas manager module');
for (const adapter of this.adapters.getAll()) { this.subscriptions.forEach((unsubscribe) => unsubscribe());
adapter.destroy(); for (const adapter of this.adapters.getAll()) {
} adapter.destroy();
this.background.destroy(); }
this.preview.destroy(); this.stateApi.destroy();
cleanupStore(); this.preview.destroy();
cleanupEventHandlers(); this.background.destroy();
cleanupStage(); this.filter.destroy();
}; this.worker.destroy();
this.renderer.destroy();
this.compositor.destroy();
this.stage.destroy();
$canvasManager.set(null);
}; };
setCanvasManager = () => { setCanvasManager = () => {
this.log.debug('Setting canvas manager'); this.log.debug('Setting canvas manager global');
$canvasManager.set(this); $canvasManager.set(this);
}; };
repr = () => {
return {
id: this.id,
type: this.type,
path: this.path,
rasterLayers: Array.from(this.adapters.rasterLayers.values()).map((adapter) => adapter.repr()),
controlLayers: Array.from(this.adapters.controlLayers.values()).map((adapter) => adapter.repr()),
inpaintMasks: Array.from(this.adapters.inpaintMasks.values()).map((adapter) => adapter.repr()),
regionMasks: Array.from(this.adapters.regionMasks.values()).map((adapter) => adapter.repr()),
stateApi: this.stateApi.repr(),
preview: this.preview.repr(),
background: this.background.repr(),
filter: this.filter.repr(),
worker: this.worker.repr(),
renderer: this.renderer.repr(),
compositor: this.compositor.repr(),
stage: this.stage.repr(),
};
};
getLoggingContext = (): SerializableObject => { getLoggingContext = (): SerializableObject => {
return { return {
path: this.path.join('.'), path: this.path.join('.'),
@@ -153,11 +183,6 @@ export class CanvasManager {
logDebugInfo() { logDebugInfo() {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.log('Canvas manager', this); console.log('Canvas manager', this);
for (const adapter of this.adapters.getAll()) { this.log.debug({ manager: this.repr() }, 'Canvas manager');
// eslint-disable-next-line no-console
console.log(adapter.id, adapter);
}
} }
getPrefixedId = getPrefixedId;
} }

View File

@@ -1,13 +1,17 @@
import type { SerializableObject } from 'common/types'; import type { SerializableObject } from 'common/types';
import { deepClone } from 'common/util/deepClone'; import { deepClone } from 'common/util/deepClone';
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager'; import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
import { CanvasModuleBase } from 'features/controlLayers/konva/CanvasModuleBase';
import { CanvasObjectRenderer } from 'features/controlLayers/konva/CanvasObjectRenderer'; import { CanvasObjectRenderer } from 'features/controlLayers/konva/CanvasObjectRenderer';
import { CanvasTransformer } from 'features/controlLayers/konva/CanvasTransformer'; import { CanvasTransformer } from 'features/controlLayers/konva/CanvasTransformer';
import { getLastPointOfLine } from 'features/controlLayers/konva/util';
import type { import type {
CanvasBrushLineState,
CanvasEntityIdentifier, CanvasEntityIdentifier,
CanvasEraserLineState,
CanvasInpaintMaskState, CanvasInpaintMaskState,
CanvasRegionalGuidanceState, CanvasRegionalGuidanceState,
CanvasV2State, Coordinate,
Rect, Rect,
} from 'features/controlLayers/store/types'; } from 'features/controlLayers/store/types';
import { getEntityIdentifier } from 'features/controlLayers/store/types'; import { getEntityIdentifier } from 'features/controlLayers/store/types';
@@ -17,13 +21,14 @@ import { get, omit } from 'lodash-es';
import type { Logger } from 'roarr'; import type { Logger } from 'roarr';
import stableHash from 'stable-hash'; import stableHash from 'stable-hash';
export class CanvasMaskAdapter { export class CanvasMaskAdapter extends CanvasModuleBase {
readonly type = 'mask_adapter'; readonly type = 'mask_adapter';
id: string; id: string;
path: string[]; path: string[];
manager: CanvasManager; manager: CanvasManager;
log: Logger; log: Logger;
subscriptions = new Set<() => void>();
state: CanvasInpaintMaskState | CanvasRegionalGuidanceState; state: CanvasInpaintMaskState | CanvasRegionalGuidanceState;
@@ -37,11 +42,14 @@ export class CanvasMaskAdapter {
}; };
constructor(state: CanvasMaskAdapter['state'], manager: CanvasMaskAdapter['manager']) { constructor(state: CanvasMaskAdapter['state'], manager: CanvasMaskAdapter['manager']) {
super();
this.id = state.id; this.id = state.id;
this.manager = manager; this.manager = manager;
this.path = this.manager.path.concat(this.id); this.path = this.manager.path.concat(this.id);
this.log = this.manager.buildLogger(this.getLoggingContext); this.log = this.manager.buildLogger(this.getLoggingContext);
this.log.debug({ state }, 'Creating mask');
this.log.debug({ state }, 'Creating mask adapter module');
this.state = state; this.state = state;
this.konva = { this.konva = {
@@ -67,18 +75,14 @@ export class CanvasMaskAdapter {
}; };
destroy = (): void => { destroy = (): void => {
this.log.debug('Destroying mask'); this.log.debug('Destroying mask adapter module');
// We need to call the destroy method on all children so they can do their own cleanup.
this.transformer.destroy(); this.transformer.destroy();
this.renderer.destroy(); this.renderer.destroy();
this.konva.layer.destroy(); this.konva.layer.destroy();
}; };
update = async (arg?: { update = async (arg?: { state: CanvasMaskAdapter['state'] }) => {
state: CanvasMaskAdapter['state'];
toolState: CanvasV2State['tool'];
isSelected: boolean;
}) => {
const state = get(arg, 'state', this.state); const state = get(arg, 'state', this.state);
if (!this.isFirstRender && state === this.state && state.fill === this.state.fill) { if (!this.isFirstRender && state === this.state && state.fill === this.state.fill) {
@@ -136,10 +140,24 @@ export class CanvasMaskAdapter {
this.konva.layer.visible(isEnabled); this.konva.layer.visible(isEnabled);
}; };
getLastPointOfLastLine = (type: CanvasBrushLineState['type'] | CanvasEraserLineState['type']): Coordinate | null => {
const lastObject = this.state.objects[this.state.objects.length - 1];
if (!lastObject) {
return null;
}
if (lastObject.type === type) {
return getLastPointOfLine(lastObject.points);
}
return null;
};
repr = () => { repr = () => {
return { return {
id: this.id, id: this.id,
type: this.type, type: this.type,
path: this.path,
state: deepClone(this.state), state: deepClone(this.state),
}; };
}; };
@@ -165,7 +183,8 @@ export class CanvasMaskAdapter {
const canvas = this.renderer.getCanvas(rect, attrs); const canvas = this.renderer.getCanvas(rect, attrs);
return canvas; return canvas;
}; };
getLoggingContext = (): SerializableObject => {
getLoggingContext = () => {
return { ...this.manager.getLoggingContext(), path: this.path.join('.') }; return { ...this.manager.getLoggingContext(), path: this.path.join('.') };
}; };
} }

View File

@@ -0,0 +1,20 @@
import type { SerializableObject } from 'common/types';
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
import type { Logger } from 'roarr';
export abstract class CanvasModuleBase {
abstract id: string;
abstract type: string;
abstract path: string[];
abstract manager: CanvasManager;
abstract log: Logger;
abstract subscriptions: Set<() => void>;
abstract getLoggingContext: () => SerializableObject;
abstract destroy: () => void;
abstract repr: () => SerializableObject & {
id: string;
path: string[];
type: string;
};
}

View File

@@ -1,4 +1,3 @@
import type { SerializableObject } from 'common/types';
import { rgbColorToString } from 'common/util/colorCodeTransformers'; import { rgbColorToString } from 'common/util/colorCodeTransformers';
import { CanvasBrushLineRenderer } from 'features/controlLayers/konva/CanvasBrushLine'; import { CanvasBrushLineRenderer } from 'features/controlLayers/konva/CanvasBrushLine';
import { CanvasEraserLineRenderer } from 'features/controlLayers/konva/CanvasEraserLine'; import { CanvasEraserLineRenderer } from 'features/controlLayers/konva/CanvasEraserLine';
@@ -6,6 +5,7 @@ import { CanvasImageRenderer } from 'features/controlLayers/konva/CanvasImage';
import type { CanvasLayerAdapter } from 'features/controlLayers/konva/CanvasLayerAdapter'; import type { CanvasLayerAdapter } from 'features/controlLayers/konva/CanvasLayerAdapter';
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager'; import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
import type { CanvasMaskAdapter } from 'features/controlLayers/konva/CanvasMaskAdapter'; import type { CanvasMaskAdapter } from 'features/controlLayers/konva/CanvasMaskAdapter';
import { CanvasModuleBase } from 'features/controlLayers/konva/CanvasModuleBase';
import { CanvasRectRenderer } from 'features/controlLayers/konva/CanvasRect'; import { CanvasRectRenderer } from 'features/controlLayers/konva/CanvasRect';
import { LightnessToAlphaFilter } from 'features/controlLayers/konva/filters'; import { LightnessToAlphaFilter } from 'features/controlLayers/konva/filters';
import { getPatternSVG } from 'features/controlLayers/konva/patterns/getPatternSVG'; import { getPatternSVG } from 'features/controlLayers/konva/patterns/getPatternSVG';
@@ -30,6 +30,7 @@ import type { GroupConfig } from 'konva/lib/Group';
import { debounce } from 'lodash-es'; import { debounce } from 'lodash-es';
import { atom } from 'nanostores'; import { atom } from 'nanostores';
import type { Logger } from 'roarr'; import type { Logger } from 'roarr';
import { serializeError } from 'serialize-error';
import { getImageDTO, uploadImage } from 'services/api/endpoints/images'; import { getImageDTO, uploadImage } from 'services/api/endpoints/images';
import type { ImageDTO } from 'services/api/types'; import type { ImageDTO } from 'services/api/types';
import { assert } from 'tsafe'; import { assert } from 'tsafe';
@@ -55,8 +56,8 @@ type AnyObjectState = CanvasBrushLineState | CanvasEraserLineState | CanvasImage
/** /**
* Handles rendering of objects for a canvas entity. * Handles rendering of objects for a canvas entity.
*/ */
export class CanvasObjectRenderer { export class CanvasObjectRenderer extends CanvasModuleBase {
readonly type = 'object_renderer'; readonly type = 'entity_object_renderer';
id: string; id: string;
path: string[]; path: string[];
@@ -123,12 +124,13 @@ export class CanvasObjectRenderer {
$canvasCache = atom<{ canvas: HTMLCanvasElement; rect: Rect } | null>(null); $canvasCache = atom<{ canvas: HTMLCanvasElement; rect: Rect } | null>(null);
constructor(parent: CanvasLayerAdapter | CanvasMaskAdapter) { constructor(parent: CanvasLayerAdapter | CanvasMaskAdapter) {
super();
this.id = getPrefixedId(this.type); this.id = getPrefixedId(this.type);
this.parent = parent; this.parent = parent;
this.path = this.parent.path.concat(this.id); this.path = this.parent.path.concat(this.id);
this.manager = parent.manager; this.manager = parent.manager;
this.log = this.manager.buildLogger(this.getLoggingContext); this.log = this.manager.buildLogger(this.getLoggingContext);
this.log.trace('Creating object renderer'); this.log.debug('Creating entity object renderer module');
this.konva = { this.konva = {
objectGroup: new Konva.Group({ name: `${this.type}:object_group`, listening: false }), objectGroup: new Konva.Group({ name: `${this.type}:object_group`, listening: false }),
@@ -156,11 +158,12 @@ export class CanvasObjectRenderer {
this.parent.konva.layer.add(this.konva.compositing.group); this.parent.konva.layer.add(this.konva.compositing.group);
} }
// When switching tool, commit the buffer. This is necessary to prevent the buffer from being lost when the
// user switches tool mid-drawing, for example by pressing space to pan the stage. It's easy to press space
// to pan _before_ releasing the mouse button, which would cause the buffer to be lost if we didn't commit it.
this.subscriptions.add( this.subscriptions.add(
this.manager.stateApi.$toolState.listen((newVal, oldVal) => { this.manager.stateApi.$tool.listen(() => {
if (newVal.selected !== oldVal.selected) { this.commitBuffer();
this.commitBuffer();
}
}) })
); );
@@ -548,17 +551,22 @@ export class CanvasObjectRenderer {
if (this.parent.transformer.pixelRect.width === 0 || this.parent.transformer.pixelRect.height === 0) { if (this.parent.transformer.pixelRect.width === 0 || this.parent.transformer.pixelRect.height === 0) {
return; return;
} }
const canvas = this.konva.objectGroup._getCachedSceneCanvas()._canvas as HTMLCanvasElement | undefined | null; try {
if (canvas) { const canvas = this.konva.objectGroup._getCachedSceneCanvas()._canvas as HTMLCanvasElement | undefined | null;
const nodeRect = this.parent.transformer.nodeRect; if (canvas) {
const pixelRect = this.parent.transformer.pixelRect; const nodeRect = this.parent.transformer.nodeRect;
const rect = { const pixelRect = this.parent.transformer.pixelRect;
x: pixelRect.x - nodeRect.x, const rect = {
y: pixelRect.y - nodeRect.y, x: pixelRect.x - nodeRect.x,
width: pixelRect.width, y: pixelRect.y - nodeRect.y,
height: pixelRect.height, width: pixelRect.width,
}; height: pixelRect.height,
this.$canvasCache.set({ rect, canvas }); };
this.$canvasCache.set({ rect, canvas });
}
} catch (error) {
// We are using an internal Konva method, so we need to catch any errors that may occur.
this.log.warn({ error: serializeError(error) }, 'Failed to update preview canvas');
} }
}, 300); }, 300);
@@ -596,11 +604,8 @@ export class CanvasObjectRenderer {
* Destroys this renderer and all of its object renderers. * Destroys this renderer and all of its object renderers.
*/ */
destroy = () => { destroy = () => {
this.log.trace('Destroying object renderer'); this.log.debug('Destroying entity object renderer module');
for (const cleanup of this.subscriptions) { this.subscriptions.forEach((unsubscribe) => unsubscribe());
this.log.trace('Cleaning up listener');
cleanup();
}
for (const renderer of this.renderers.values()) { for (const renderer of this.renderers.values()) {
renderer.destroy(); renderer.destroy();
} }
@@ -615,13 +620,14 @@ export class CanvasObjectRenderer {
return { return {
id: this.id, id: this.id,
type: this.type, type: this.type,
path: this.path,
parent: this.parent.id, parent: this.parent.id,
renderers: Array.from(this.renderers.values()).map((renderer) => renderer.repr()), renderers: Array.from(this.renderers.values()).map((renderer) => renderer.repr()),
buffer: this.bufferRenderer?.repr(), buffer: this.bufferRenderer?.repr(),
}; };
}; };
getLoggingContext = (): SerializableObject => { getLoggingContext = () => {
return { ...this.parent.getLoggingContext(), path: this.path.join('.') }; return { ...this.parent.getLoggingContext(), path: this.path.join('.') };
}; };
} }

Some files were not shown because too many files have changed in this diff Show More