feat(ui): upgrade redux and RTK

There are a few breaking changes, which I've addressed.

The vast majority of changes are related to new handling of `reselect`'s `createSelector` options.

For better or worse, we memoize just about all our selectors using lodash `isEqual` for `resultEqualityCheck`. The upgrade requires we explicitly set the `memoize` option to `lruMemoize` to continue using lodash here.

Doing that required changing our `defaultSelectorOptions`.

Instead of changing that and finding dozens of instances where we weren't using that and instead were defining selector options manually, I've created a pre-configured selector: `createMemoizedSelector`.

This is now used everywhere instead of `createSelector`.
This commit is contained in:
psychedelicious 2023-12-09 00:16:02 +11:00
parent 99f14b1dfe
commit 72cb8b83fe
215 changed files with 1651 additions and 2444 deletions

View File

@ -65,7 +65,7 @@
"@mantine/form": "^6.0.19", "@mantine/form": "^6.0.19",
"@mantine/hooks": "^6.0.19", "@mantine/hooks": "^6.0.19",
"@nanostores/react": "^0.7.1", "@nanostores/react": "^0.7.1",
"@reduxjs/toolkit": "^1.9.7", "@reduxjs/toolkit": "^2.0.1",
"@roarr/browser-log-writer": "^1.3.0", "@roarr/browser-log-writer": "^1.3.0",
"@storybook/manager-api": "^7.6.4", "@storybook/manager-api": "^7.6.4",
"@storybook/theming": "^7.6.4", "@storybook/theming": "^7.6.4",
@ -92,7 +92,7 @@
"react-i18next": "^13.5.0", "react-i18next": "^13.5.0",
"react-icons": "^4.12.0", "react-icons": "^4.12.0",
"react-konva": "^18.2.10", "react-konva": "^18.2.10",
"react-redux": "^8.1.3", "react-redux": "^9.0.2",
"react-resizable-panels": "^0.0.55", "react-resizable-panels": "^0.0.55",
"react-use": "^17.4.2", "react-use": "^17.4.2",
"react-virtuoso": "^4.6.2", "react-virtuoso": "^4.6.2",
@ -130,7 +130,6 @@
"@types/node": "^20.9.0", "@types/node": "^20.9.0",
"@types/react": "^18.2.37", "@types/react": "^18.2.37",
"@types/react-dom": "^18.2.17", "@types/react-dom": "^18.2.17",
"@types/react-redux": "^7.1.32",
"@types/uuid": "^9.0.7", "@types/uuid": "^9.0.7",
"@typescript-eslint/eslint-plugin": "^6.13.2", "@typescript-eslint/eslint-plugin": "^6.13.2",
"@typescript-eslint/parser": "^6.13.2", "@typescript-eslint/parser": "^6.13.2",

View File

@ -57,8 +57,8 @@ dependencies:
specifier: ^0.7.1 specifier: ^0.7.1
version: 0.7.1(nanostores@0.9.5)(react@18.2.0) version: 0.7.1(nanostores@0.9.5)(react@18.2.0)
'@reduxjs/toolkit': '@reduxjs/toolkit':
specifier: ^1.9.7 specifier: ^2.0.1
version: 1.9.7(react-redux@8.1.3)(react@18.2.0) version: 2.0.1(react-redux@9.0.2)(react@18.2.0)
'@roarr/browser-log-writer': '@roarr/browser-log-writer':
specifier: ^1.3.0 specifier: ^1.3.0
version: 1.3.0 version: 1.3.0
@ -138,8 +138,8 @@ dependencies:
specifier: ^18.2.10 specifier: ^18.2.10
version: 18.2.10(konva@9.2.3)(react-dom@18.2.0)(react@18.2.0) version: 18.2.10(konva@9.2.3)(react-dom@18.2.0)(react@18.2.0)
react-redux: react-redux:
specifier: ^8.1.3 specifier: ^9.0.2
version: 8.1.3(@types/react-dom@18.2.17)(@types/react@18.2.42)(react-dom@18.2.0)(react@18.2.0)(redux@4.2.1) version: 9.0.2(@types/react-dom@18.2.17)(@types/react@18.2.42)(react-dom@18.2.0)(react@18.2.0)(redux@5.0.0)
react-resizable-panels: react-resizable-panels:
specifier: ^0.0.55 specifier: ^0.0.55
version: 0.0.55(react-dom@18.2.0)(react@18.2.0) version: 0.0.55(react-dom@18.2.0)(react@18.2.0)
@ -157,7 +157,7 @@ dependencies:
version: 2.2.0 version: 2.2.0
redux-remember: redux-remember:
specifier: ^4.2.2 specifier: ^4.2.2
version: 4.2.2(redux@4.2.1) version: 4.2.2(redux@5.0.0)
roarr: roarr:
specifier: ^7.21.0 specifier: ^7.21.0
version: 7.21.0 version: 7.21.0
@ -226,9 +226,6 @@ devDependencies:
'@types/react-dom': '@types/react-dom':
specifier: ^18.2.17 specifier: ^18.2.17
version: 18.2.17 version: 18.2.17
'@types/react-redux':
specifier: ^7.1.32
version: 7.1.32
'@types/uuid': '@types/uuid':
specifier: ^9.0.7 specifier: ^9.0.7
version: 9.0.7 version: 9.0.7
@ -4493,23 +4490,23 @@ packages:
- immer - immer
dev: false dev: false
/@reduxjs/toolkit@1.9.7(react-redux@8.1.3)(react@18.2.0): /@reduxjs/toolkit@2.0.1(react-redux@9.0.2)(react@18.2.0):
resolution: {integrity: sha512-t7v8ZPxhhKgOKtU+uyJT13lu4vL7az5aFi4IdoDs/eS548edn2M8Ik9h8fxgvMjGoAUVFSt6ZC1P5cWmQ014QQ==} resolution: {integrity: sha512-fxIjrR9934cmS8YXIGd9e7s1XRsEU++aFc9DVNMFMRTM5Vtsg2DCRMj21eslGtDt43IUf9bJL3h5bwUlZleibA==}
peerDependencies: peerDependencies:
react: ^16.9.0 || ^17.0.0 || ^18 react: ^16.9.0 || ^17.0.0 || ^18
react-redux: ^7.2.1 || ^8.0.2 react-redux: ^7.2.1 || ^8.1.3 || ^9.0.0
peerDependenciesMeta: peerDependenciesMeta:
react: react:
optional: true optional: true
react-redux: react-redux:
optional: true optional: true
dependencies: dependencies:
immer: 9.0.21 immer: 10.0.3
react: 18.2.0 react: 18.2.0
react-redux: 8.1.3(@types/react-dom@18.2.17)(@types/react@18.2.42)(react-dom@18.2.0)(react@18.2.0)(redux@4.2.1) react-redux: 9.0.2(@types/react-dom@18.2.17)(@types/react@18.2.42)(react-dom@18.2.0)(react@18.2.0)(redux@5.0.0)
redux: 4.2.1 redux: 5.0.0
redux-thunk: 2.4.2(redux@4.2.1) redux-thunk: 3.1.0(redux@5.0.0)
reselect: 4.1.8 reselect: 5.0.1
dev: false dev: false
/@roarr/browser-log-writer@1.3.0: /@roarr/browser-log-writer@1.3.0:
@ -5802,12 +5799,6 @@ packages:
'@types/node': 20.10.4 '@types/node': 20.10.4
dev: true dev: true
/@types/hoist-non-react-statics@3.3.1:
resolution: {integrity: sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==}
dependencies:
'@types/react': 18.2.42
hoist-non-react-statics: 3.3.2
/@types/http-errors@2.0.4: /@types/http-errors@2.0.4:
resolution: {integrity: sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==} resolution: {integrity: sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==}
@ -5922,15 +5913,6 @@ packages:
'@types/react': 18.2.42 '@types/react': 18.2.42
dev: false dev: false
/@types/react-redux@7.1.32:
resolution: {integrity: sha512-YJYV0M27cyHHJIacaRsZRx5OETzK8KWjEGnix7UH3ngItYo4It0MUBzU6WNwqnwhbrPw5wx9KXluuoTZ85Gg7A==}
dependencies:
'@types/hoist-non-react-statics': 3.3.1
'@types/react': 18.2.42
hoist-non-react-statics: 3.3.2
redux: 4.2.1
dev: true
/@types/react@18.2.42: /@types/react@18.2.42:
resolution: {integrity: sha512-c1zEr96MjakLYus/wPnuWDo1/zErfdU9rNsIGmE+NV71nx88FG9Ttgo5dqorXTu/LImX2f63WBP986gJkMPNbA==} resolution: {integrity: sha512-c1zEr96MjakLYus/wPnuWDo1/zErfdU9rNsIGmE+NV71nx88FG9Ttgo5dqorXTu/LImX2f63WBP986gJkMPNbA==}
dependencies: dependencies:
@ -8959,6 +8941,7 @@ packages:
resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==} resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==}
dependencies: dependencies:
react-is: 16.13.1 react-is: 16.13.1
dev: false
/hosted-git-info@2.8.9: /hosted-git-info@2.8.9:
resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==} resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==}
@ -9049,8 +9032,8 @@ packages:
engines: {node: '>= 4'} engines: {node: '>= 4'}
dev: true dev: true
/immer@9.0.21: /immer@10.0.3:
resolution: {integrity: sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==} resolution: {integrity: sha512-pwupu3eWfouuaowscykeckFmVTpqbzW+rXFCX8rQLkZzM9ftBmU/++Ra+o+L27mz03zJTlyV4UUr+fdKNffo4A==}
dev: false dev: false
/import-fresh@3.3.0: /import-fresh@3.3.0:
@ -11029,6 +11012,7 @@ packages:
/react-is@18.2.0: /react-is@18.2.0:
resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==} resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==}
dev: true
/react-konva@18.2.10(konva@9.2.3)(react-dom@18.2.0)(react@18.2.0): /react-konva@18.2.10(konva@9.2.3)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-ohcX1BJINL43m4ynjZ24MxFI1syjBdrXhqVxYVDw2rKgr3yuS0x/6m1Y2Z4sl4T/gKhfreBx8KHisd0XC6OT1g==} resolution: {integrity: sha512-ohcX1BJINL43m4ynjZ24MxFI1syjBdrXhqVxYVDw2rKgr3yuS0x/6m1Y2Z4sl4T/gKhfreBx8KHisd0XC6OT1g==}
@ -11057,15 +11041,15 @@ packages:
scheduler: 0.23.0 scheduler: 0.23.0
dev: false dev: false
/react-redux@8.1.3(@types/react-dom@18.2.17)(@types/react@18.2.42)(react-dom@18.2.0)(react@18.2.0)(redux@4.2.1): /react-redux@9.0.2(@types/react-dom@18.2.17)(@types/react@18.2.42)(react-dom@18.2.0)(react@18.2.0)(redux@5.0.0):
resolution: {integrity: sha512-n0ZrutD7DaX/j9VscF+uTALI3oUPa/pO4Z3soOBIjuRn/FzVu6aehhysxZCLi6y7duMf52WNZGMl7CtuK5EnRw==} resolution: {integrity: sha512-34EI42cYZxJF59Iht6RDM5xDun5EdhV8CbJcTe+mYx97XMHLNYA6RrH9r/ZOZX3CetVCYfBEU9oAY9h3sZarsw==}
peerDependencies: peerDependencies:
'@types/react': ^16.8 || ^17.0 || ^18.0 '@types/react': ^18.2.41
'@types/react-dom': ^16.8 || ^17.0 || ^18.0 '@types/react-dom': ^18.2.17
react: ^16.8 || ^17.0 || ^18.0 react: ^18.0
react-dom: ^16.8 || ^17.0 || ^18.0 react-dom: ^18.0
react-native: '>=0.59' react-native: '>=0.71'
redux: ^4 || ^5.0.0-beta.0 redux: ^5.0.0
peerDependenciesMeta: peerDependenciesMeta:
'@types/react': '@types/react':
optional: true optional: true
@ -11078,16 +11062,12 @@ packages:
redux: redux:
optional: true optional: true
dependencies: dependencies:
'@babel/runtime': 7.23.5
'@types/hoist-non-react-statics': 3.3.1
'@types/react': 18.2.42 '@types/react': 18.2.42
'@types/react-dom': 18.2.17 '@types/react-dom': 18.2.17
'@types/use-sync-external-store': 0.0.3 '@types/use-sync-external-store': 0.0.3
hoist-non-react-statics: 3.3.2
react: 18.2.0 react: 18.2.0
react-dom: 18.2.0(react@18.2.0) react-dom: 18.2.0(react@18.2.0)
react-is: 18.2.0 redux: 5.0.0
redux: 4.2.1
use-sync-external-store: 1.2.0(react@18.2.0) use-sync-external-store: 1.2.0(react@18.2.0)
dev: false dev: false
@ -11329,26 +11309,25 @@ packages:
resolution: {integrity: sha512-GHESQC+Y0PV98ZBoaC6br6cDOsNiM1Cu4UleGMqMWCXX03jIr3BoozYVrRkLVVAl4sC216chakMnZOu6SwNdGA==} resolution: {integrity: sha512-GHESQC+Y0PV98ZBoaC6br6cDOsNiM1Cu4UleGMqMWCXX03jIr3BoozYVrRkLVVAl4sC216chakMnZOu6SwNdGA==}
dev: false dev: false
/redux-remember@4.2.2(redux@4.2.1): /redux-remember@4.2.2(redux@5.0.0):
resolution: {integrity: sha512-1pY3FNk70uZ7Djya/SXkwL13hu0T6iVYSVMFqUApR4n3BDoGYmmaSouX8T3Y1Rg2pmZFaWw7e8ZaOAH82s8IiA==} resolution: {integrity: sha512-1pY3FNk70uZ7Djya/SXkwL13hu0T6iVYSVMFqUApR4n3BDoGYmmaSouX8T3Y1Rg2pmZFaWw7e8ZaOAH82s8IiA==}
peerDependencies: peerDependencies:
redux: '*' redux: '*'
dependencies: dependencies:
redux: 4.2.1 redux: 5.0.0
dev: false dev: false
/redux-thunk@2.4.2(redux@4.2.1): /redux-thunk@3.1.0(redux@5.0.0):
resolution: {integrity: sha512-+P3TjtnP0k/FEjcBL5FZpoovtvrTNT/UXd4/sluaSyrURlSlhLSzEdfsTBW7WsKB6yPvgd7q/iZPICFjW4o57Q==} resolution: {integrity: sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==}
peerDependencies: peerDependencies:
redux: ^4 redux: ^5.0.0
dependencies: dependencies:
redux: 4.2.1 redux: 5.0.0
dev: false dev: false
/redux@4.2.1: /redux@5.0.0:
resolution: {integrity: sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==} resolution: {integrity: sha512-blLIYmYetpZMET6Q6uCY7Jtl/Im5OBldy+vNPauA8vvsdqyt66oep4EUpAMWNHauTC6xa9JuRPhRB72rY82QGA==}
dependencies: dev: false
'@babel/runtime': 7.23.5
/reflect.getprototypeof@1.0.3: /reflect.getprototypeof@1.0.3:
resolution: {integrity: sha512-TTAOZpkJ2YLxl7mVHWrNo3iDMEkYlva/kgFcXndqMgbo/AZUmmavEkdXV+hXtE4P8xdyEKRzalaFqZVuwIk/Nw==} resolution: {integrity: sha512-TTAOZpkJ2YLxl7mVHWrNo3iDMEkYlva/kgFcXndqMgbo/AZUmmavEkdXV+hXtE4P8xdyEKRzalaFqZVuwIk/Nw==}
@ -11452,8 +11431,8 @@ packages:
hasBin: true hasBin: true
dev: true dev: true
/reselect@4.1.8: /reselect@5.0.1:
resolution: {integrity: sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ==} resolution: {integrity: sha512-D72j2ubjgHpvuCiORWkOUxndHJrxDaSolheiz5CO+roz8ka97/4msh2E8F5qay4GawR5vzBt5MkbDHT+Rdy/Wg==}
dev: false dev: false
/resize-observer-polyfill@1.5.1: /resize-observer-polyfill@1.5.1:

View File

@ -1,4 +1,4 @@
import { createSelector } from '@reduxjs/toolkit'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store'; import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { useQueueBack } from 'features/queue/hooks/useQueueBack'; import { useQueueBack } from 'features/queue/hooks/useQueueBack';
@ -9,20 +9,14 @@ import {
shiftKeyPressed, shiftKeyPressed,
} from 'features/ui/store/hotkeysSlice'; } from 'features/ui/store/hotkeysSlice';
import { setActiveTab } from 'features/ui/store/uiSlice'; import { setActiveTab } from 'features/ui/store/uiSlice';
import { isEqual } from 'lodash-es';
import React, { memo } from 'react'; import React, { memo } from 'react';
import { isHotkeyPressed, useHotkeys } from 'react-hotkeys-hook'; import { isHotkeyPressed, useHotkeys } from 'react-hotkeys-hook';
const globalHotkeysSelector = createSelector( const globalHotkeysSelector = createMemoizedSelector(
[stateSelector], [stateSelector],
({ hotkeys }) => { ({ hotkeys }) => {
const { shift, ctrl, meta } = hotkeys; const { shift, ctrl, meta } = hotkeys;
return { shift, ctrl, meta }; return { shift, ctrl, meta };
},
{
memoizeOptions: {
resultEqualityCheck: isEqual,
},
} }
); );

View File

@ -1,8 +1,7 @@
import { createSelector } from '@reduxjs/toolkit';
import { createLogWriter } from '@roarr/browser-log-writer'; import { createLogWriter } from '@roarr/browser-log-writer';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store'; import { stateSelector } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { isEqual } from 'lodash-es';
import { useEffect, useMemo } from 'react'; import { useEffect, useMemo } from 'react';
import { ROARR, Roarr } from 'roarr'; import { ROARR, Roarr } from 'roarr';
import { import {
@ -13,22 +12,14 @@ import {
logger, logger,
} from './logger'; } from './logger';
const selector = createSelector( const selector = createMemoizedSelector(stateSelector, ({ system }) => {
stateSelector, const { consoleLogLevel, shouldLogToConsole } = system;
({ system }) => {
const { consoleLogLevel, shouldLogToConsole } = system;
return { return {
consoleLogLevel, consoleLogLevel,
shouldLogToConsole, shouldLogToConsole,
}; };
}, });
{
memoizeOptions: {
resultEqualityCheck: isEqual,
},
}
);
export const useLogger = (namespace: LoggerNamespace) => { export const useLogger = (namespace: LoggerNamespace) => {
const { consoleLogLevel, shouldLogToConsole } = useAppSelector(selector); const { consoleLogLevel, shouldLogToConsole } = useAppSelector(selector);

View File

@ -0,0 +1,12 @@
import { createSelectorCreator, lruMemoize } from '@reduxjs/toolkit';
import { isEqual } from 'lodash-es';
/**
* A memoized selector creator that uses LRU cache and lodash's isEqual for equality check.
*/
export const createMemoizedSelector = createSelectorCreator({
memoize: lruMemoize,
memoizeOptions: {
resultEqualityCheck: isEqual,
},
});

View File

@ -1,10 +1,10 @@
import { AnyAction } from '@reduxjs/toolkit'; import { UnknownAction } from '@reduxjs/toolkit';
import { isAnyGraphBuilt } from 'features/nodes/store/actions'; import { isAnyGraphBuilt } from 'features/nodes/store/actions';
import { nodeTemplatesBuilt } from 'features/nodes/store/nodesSlice'; import { nodeTemplatesBuilt } from 'features/nodes/store/nodesSlice';
import { receivedOpenAPISchema } from 'services/api/thunks/schema'; import { receivedOpenAPISchema } from 'services/api/thunks/schema';
import { Graph } from 'services/api/types'; import { Graph } from 'services/api/types';
export const actionSanitizer = <A extends AnyAction>(action: A): A => { export const actionSanitizer = <A extends UnknownAction>(action: A): A => {
if (isAnyGraphBuilt(action)) { if (isAnyGraphBuilt(action)) {
if (action.payload.nodes) { if (action.payload.nodes) {
const sanitizedNodes: Graph['nodes'] = {}; const sanitizedNodes: Graph['nodes'] = {};

View File

@ -1,11 +1,10 @@
import type { TypedAddListener, TypedStartListening } from '@reduxjs/toolkit'; import type { TypedAddListener, TypedStartListening } from '@reduxjs/toolkit';
import { import {
AnyAction, UnknownAction,
ListenerEffect, ListenerEffect,
addListener, addListener,
createListenerMiddleware, createListenerMiddleware,
} from '@reduxjs/toolkit'; } from '@reduxjs/toolkit';
import type { AppDispatch, RootState } from 'app/store/store'; import type { AppDispatch, RootState } from 'app/store/store';
import { addCommitStagingAreaImageListener } from './listeners/addCommitStagingAreaImageListener'; import { addCommitStagingAreaImageListener } from './listeners/addCommitStagingAreaImageListener';
import { addFirstListImagesListener } from './listeners/addFirstListImagesListener.ts'; import { addFirstListImagesListener } from './listeners/addFirstListImagesListener.ts';
@ -87,7 +86,7 @@ export const addAppListener = addListener as TypedAddListener<
>; >;
export type AppListenerEffect = ListenerEffect< export type AppListenerEffect = ListenerEffect<
AnyAction, UnknownAction,
RootState, RootState,
AppDispatch AppDispatch
>; >;

View File

@ -52,7 +52,11 @@ export const addBoardIdSelectedListener = () => {
const { data: boardImagesData } = const { data: boardImagesData } =
imagesApi.endpoints.listImages.select(queryArgs)(getState()); imagesApi.endpoints.listImages.select(queryArgs)(getState());
if (boardImagesData) { if (
boardImagesData &&
boardIdSelected.match(action) &&
action.payload.selectedImageName
) {
const firstImage = imagesSelectors.selectAll(boardImagesData)[0]; const firstImage = imagesSelectors.selectAll(boardImagesData)[0];
const selectedImage = imagesSelectors.selectById( const selectedImage = imagesSelectors.selectById(
boardImagesData, boardImagesData,

View File

@ -24,7 +24,7 @@ export const addSocketQueueItemStatusChangedEventListener = () => {
dispatch( dispatch(
queueApi.util.updateQueryData('listQueueItems', undefined, (draft) => { queueApi.util.updateQueryData('listQueueItems', undefined, (draft) => {
queueItemsAdapter.updateOne(draft, { queueItemsAdapter.updateOne(draft, {
id: queue_item.item_id, id: String(queue_item.item_id),
changes: queue_item, changes: queue_item,
}); });
}) })

View File

@ -1,6 +1,6 @@
import { import {
AnyAction,
ThunkDispatch, ThunkDispatch,
UnknownAction,
autoBatchEnhancer, autoBatchEnhancer,
combineReducers, combineReducers,
configureStore, configureStore,
@ -27,7 +27,6 @@ import { createStore as createIDBKeyValStore, get, set } from 'idb-keyval';
import dynamicMiddlewares from 'redux-dynamic-middlewares'; import dynamicMiddlewares from 'redux-dynamic-middlewares';
import { Driver, rememberEnhancer, rememberReducer } from 'redux-remember'; import { Driver, rememberEnhancer, rememberReducer } from 'redux-remember';
import { api } from 'services/api'; import { api } from 'services/api';
import { authToastMiddleware } from 'services/api/authToastMiddleware';
import { STORAGE_PREFIX } from './constants'; import { STORAGE_PREFIX } from './constants';
import { serialize } from './enhancers/reduxRemember/serialize'; import { serialize } from './enhancers/reduxRemember/serialize';
import { unserialize } from './enhancers/reduxRemember/unserialize'; import { unserialize } from './enhancers/reduxRemember/unserialize';
@ -90,8 +89,16 @@ const idbKeyValDriver: Driver = {
export const createStore = (uniqueStoreKey?: string, persist = true) => export const createStore = (uniqueStoreKey?: string, persist = true) =>
configureStore({ configureStore({
reducer: rememberedRootReducer, reducer: rememberedRootReducer,
enhancers: (existingEnhancers) => { middleware: (getDefaultMiddleware) =>
const _enhancers = existingEnhancers.concat(autoBatchEnhancer()); getDefaultMiddleware({
serializableCheck: false,
immutableCheck: false,
})
.concat(api.middleware)
.concat(dynamicMiddlewares)
.prepend(listenerMiddleware.middleware),
enhancers: (getDefaultEnhancers) => {
const _enhancers = getDefaultEnhancers().concat(autoBatchEnhancer());
if (persist) { if (persist) {
_enhancers.push( _enhancers.push(
rememberEnhancer(idbKeyValDriver, rememberedKeys, { rememberEnhancer(idbKeyValDriver, rememberedKeys, {
@ -106,15 +113,6 @@ export const createStore = (uniqueStoreKey?: string, persist = true) =>
} }
return _enhancers; return _enhancers;
}, },
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
serializableCheck: false,
immutableCheck: false,
})
.concat(api.middleware)
.concat(dynamicMiddlewares)
.concat(authToastMiddleware)
.prepend(listenerMiddleware.middleware),
devTools: { devTools: {
actionSanitizer, actionSanitizer,
stateSanitizer, stateSanitizer,
@ -145,6 +143,6 @@ export type AppGetState = ReturnType<
>; >;
export type RootState = ReturnType<ReturnType<typeof createStore>['getState']>; export type RootState = ReturnType<ReturnType<typeof createStore>['getState']>;
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
export type AppThunkDispatch = ThunkDispatch<RootState, any, AnyAction>; export type AppThunkDispatch = ThunkDispatch<RootState, any, UnknownAction>;
export type AppDispatch = ReturnType<typeof createStore>['dispatch']; export type AppDispatch = ReturnType<typeof createStore>['dispatch'];
export const stateSelector = (state: RootState) => state; export const stateSelector = (state: RootState) => state;

View File

@ -1,7 +0,0 @@
import { isEqual } from 'lodash-es';
export const defaultSelectorOptions = {
memoizeOptions: {
resultEqualityCheck: isEqual,
},
};

View File

@ -12,7 +12,6 @@
* - increment it in `onPaneClick` * - increment it in `onPaneClick`
* - `useEffect()` to close the menu when `globalContextMenuCloseTrigger` changes * - `useEffect()` to close the menu when `globalContextMenuCloseTrigger` changes
*/ */
import { import {
Menu, Menu,
MenuButton, MenuButton,

View File

@ -19,7 +19,6 @@ import { useAppDispatch } from 'app/store/storeHooks';
import { stopPastePropagation } from 'common/util/stopPastePropagation'; import { stopPastePropagation } from 'common/util/stopPastePropagation';
import { shiftKeyPressed } from 'features/ui/store/hotkeysSlice'; import { shiftKeyPressed } from 'features/ui/store/hotkeysSlice';
import { clamp } from 'lodash-es'; import { clamp } from 'lodash-es';
import { import {
FocusEvent, FocusEvent,
KeyboardEvent, KeyboardEvent,

View File

@ -1,9 +1,8 @@
import { Box } from '@chakra-ui/react'; import { Box } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit';
import { useAppToaster } from 'app/components/Toaster'; import { useAppToaster } from 'app/components/Toaster';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store'; import { stateSelector } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import { activeTabNameSelector } from 'features/ui/store/uiSelectors'; import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
import { AnimatePresence, motion } from 'framer-motion'; import { AnimatePresence, motion } from 'framer-motion';
import { import {
@ -20,7 +19,7 @@ import { useUploadImageMutation } from 'services/api/endpoints/images';
import { PostUploadAction } from 'services/api/types'; import { PostUploadAction } from 'services/api/types';
import ImageUploadOverlay from './ImageUploadOverlay'; import ImageUploadOverlay from './ImageUploadOverlay';
const selector = createSelector( const selector = createMemoizedSelector(
[stateSelector, activeTabNameSelector], [stateSelector, activeTabNameSelector],
({ gallery }, activeTabName) => { ({ gallery }, activeTabName) => {
let postUploadAction: PostUploadAction = { type: 'TOAST' }; let postUploadAction: PostUploadAction = { type: 'TOAST' };
@ -39,8 +38,7 @@ const selector = createSelector(
autoAddBoardId, autoAddBoardId,
postUploadAction, postUploadAction,
}; };
}, }
defaultSelectorOptions
); );
type ImageUploaderProps = { type ImageUploaderProps = {

View File

@ -1,7 +1,6 @@
import { createSelector } from '@reduxjs/toolkit'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store'; import { stateSelector } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import { selectControlAdapterAll } from 'features/controlAdapters/store/controlAdaptersSlice'; import { selectControlAdapterAll } from 'features/controlAdapters/store/controlAdaptersSlice';
import { isControlNetOrT2IAdapter } from 'features/controlAdapters/store/types'; import { isControlNetOrT2IAdapter } from 'features/controlAdapters/store/types';
import { isInvocationNode } from 'features/nodes/types/invocation'; import { isInvocationNode } from 'features/nodes/types/invocation';
@ -10,7 +9,7 @@ import i18n from 'i18next';
import { forEach } from 'lodash-es'; import { forEach } from 'lodash-es';
import { getConnectedEdges } from 'reactflow'; import { getConnectedEdges } from 'reactflow';
const selector = createSelector( const selector = createMemoizedSelector(
[stateSelector, activeTabNameSelector], [stateSelector, activeTabNameSelector],
( (
{ controlAdapters, generation, system, nodes, dynamicPrompts }, { controlAdapters, generation, system, nodes, dynamicPrompts },
@ -125,8 +124,7 @@ const selector = createSelector(
} }
return { isReady: !reasons.length, reasons }; return { isReady: !reasons.length, reasons };
}, }
defaultSelectorOptions
); );
export const useIsReadyToEnqueue = () => { export const useIsReadyToEnqueue = () => {

View File

@ -1,5 +1,4 @@
// https://stackoverflow.com/a/73731908 // https://stackoverflow.com/a/73731908
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
export function useSingleAndDoubleClick( export function useSingleAndDoubleClick(

View File

@ -1,16 +1,7 @@
import { Box, chakra, Flex } from '@chakra-ui/react'; import { Box, chakra, Flex } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import {
canvasSelector,
isStagingSelector,
} from 'features/canvas/store/canvasSelectors';
import Konva from 'konva';
import { KonvaEventObject } from 'konva/lib/Node';
import { Vector2d } from 'konva/lib/types';
import { memo, useCallback, useEffect, useRef } from 'react';
import { Layer, Stage } from 'react-konva';
import useCanvasDragMove from 'features/canvas/hooks/useCanvasDragMove'; import useCanvasDragMove from 'features/canvas/hooks/useCanvasDragMove';
import useCanvasHotkeys from 'features/canvas/hooks/useCanvasHotkeys'; import useCanvasHotkeys from 'features/canvas/hooks/useCanvasHotkeys';
import useCanvasMouseDown from 'features/canvas/hooks/useCanvasMouseDown'; import useCanvasMouseDown from 'features/canvas/hooks/useCanvasMouseDown';
@ -18,11 +9,17 @@ import useCanvasMouseMove from 'features/canvas/hooks/useCanvasMouseMove';
import useCanvasMouseOut from 'features/canvas/hooks/useCanvasMouseOut'; import useCanvasMouseOut from 'features/canvas/hooks/useCanvasMouseOut';
import useCanvasMouseUp from 'features/canvas/hooks/useCanvasMouseUp'; import useCanvasMouseUp from 'features/canvas/hooks/useCanvasMouseUp';
import useCanvasWheel from 'features/canvas/hooks/useCanvasZoom'; import useCanvasWheel from 'features/canvas/hooks/useCanvasZoom';
import { isStagingSelector } from 'features/canvas/store/canvasSelectors';
import { canvasResized } from 'features/canvas/store/canvasSlice'; import { canvasResized } from 'features/canvas/store/canvasSlice';
import { import {
setCanvasBaseLayer, setCanvasBaseLayer,
setCanvasStage, setCanvasStage,
} from 'features/canvas/util/konvaInstanceProvider'; } from 'features/canvas/util/konvaInstanceProvider';
import Konva from 'konva';
import { KonvaEventObject } from 'konva/lib/Node';
import { Vector2d } from 'konva/lib/types';
import { memo, useCallback, useEffect, useRef } from 'react';
import { Layer, Stage } from 'react-konva';
import IAICanvasBoundingBoxOverlay from './IAICanvasBoundingBoxOverlay'; import IAICanvasBoundingBoxOverlay from './IAICanvasBoundingBoxOverlay';
import IAICanvasGrid from './IAICanvasGrid'; import IAICanvasGrid from './IAICanvasGrid';
import IAICanvasIntermediateImage from './IAICanvasIntermediateImage'; import IAICanvasIntermediateImage from './IAICanvasIntermediateImage';
@ -35,9 +32,9 @@ import IAICanvasStatusText from './IAICanvasStatusText';
import IAICanvasBoundingBox from './IAICanvasToolbar/IAICanvasBoundingBox'; import IAICanvasBoundingBox from './IAICanvasToolbar/IAICanvasBoundingBox';
import IAICanvasToolPreview from './IAICanvasToolPreview'; import IAICanvasToolPreview from './IAICanvasToolPreview';
const selector = createSelector( const selector = createMemoizedSelector(
[canvasSelector, isStagingSelector], [stateSelector, isStagingSelector],
(canvas, isStaging) => { ({ canvas }, isStaging) => {
const { const {
isMaskEnabled, isMaskEnabled,
stageScale, stageScale,
@ -83,8 +80,7 @@ const selector = createSelector(
shouldShowIntermediates, shouldShowIntermediates,
shouldAntialias, shouldAntialias,
}; };
}, }
defaultSelectorOptions
); );
const ChakraStage = chakra(Stage, { const ChakraStage = chakra(Stage, {

View File

@ -1,38 +1,28 @@
import { createSelector } from '@reduxjs/toolkit'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { isEqual } from 'lodash-es';
import { Group, Rect } from 'react-konva';
import { canvasSelector } from 'features/canvas/store/canvasSelectors';
import { memo } from 'react'; import { memo } from 'react';
import { Group, Rect } from 'react-konva';
const selector = createSelector( const selector = createMemoizedSelector(stateSelector, ({ canvas }) => {
canvasSelector, const {
(canvas) => { boundingBoxCoordinates,
const { boundingBoxDimensions,
boundingBoxCoordinates, stageDimensions,
boundingBoxDimensions, stageScale,
stageDimensions, shouldDarkenOutsideBoundingBox,
stageScale, stageCoordinates,
shouldDarkenOutsideBoundingBox, } = canvas;
stageCoordinates,
} = canvas;
return { return {
boundingBoxCoordinates, boundingBoxCoordinates,
boundingBoxDimensions, boundingBoxDimensions,
shouldDarkenOutsideBoundingBox, shouldDarkenOutsideBoundingBox,
stageCoordinates, stageCoordinates,
stageDimensions, stageDimensions,
stageScale, stageScale,
}; };
}, });
{
memoizeOptions: {
resultEqualityCheck: isEqual,
},
}
);
const IAICanvasBoundingBoxOverlay = () => { const IAICanvasBoundingBoxOverlay = () => {
const { const {
boundingBoxCoordinates, boundingBoxCoordinates,

View File

@ -1,26 +1,16 @@
// Grid drawing adapted from https://longviewcoder.com/2021/12/08/konva-a-better-grid/ // Grid drawing adapted from https://longviewcoder.com/2021/12/08/konva-a-better-grid/
import { useColorMode, useToken } from '@chakra-ui/react'; import { useColorMode, useToken } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { canvasSelector } from 'features/canvas/store/canvasSelectors'; import { range } from 'lodash-es';
import { isEqual, range } from 'lodash-es';
import { ReactNode, memo, useCallback, useLayoutEffect, useState } from 'react'; import { ReactNode, memo, useCallback, useLayoutEffect, useState } from 'react';
import { Group, Line as KonvaLine } from 'react-konva'; import { Group, Line as KonvaLine } from 'react-konva';
const selector = createSelector( const selector = createMemoizedSelector([stateSelector], ({ canvas }) => {
[canvasSelector], const { stageScale, stageCoordinates, stageDimensions } = canvas;
(canvas) => { return { stageScale, stageCoordinates, stageDimensions };
const { stageScale, stageCoordinates, stageDimensions } = canvas; });
return { stageScale, stageCoordinates, stageDimensions };
},
{
memoizeOptions: {
resultEqualityCheck: isEqual,
},
}
);
const IAICanvasGrid = () => { const IAICanvasGrid = () => {
const { stageScale, stageCoordinates, stageDimensions } = const { stageScale, stageCoordinates, stageDimensions } =

View File

@ -1,4 +1,4 @@
import { skipToken } from '@reduxjs/toolkit/dist/query'; import { skipToken } from '@reduxjs/toolkit/query';
import { $authToken } from 'app/store/nanostores/authToken'; import { $authToken } from 'app/store/nanostores/authToken';
import { memo } from 'react'; import { memo } from 'react';
import { Image } from 'react-konva'; import { Image } from 'react-konva';

View File

@ -2,31 +2,22 @@ import { createSelector } from '@reduxjs/toolkit';
import { stateSelector } from 'app/store/store'; import { stateSelector } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { ImageConfig } from 'konva/lib/shapes/Image'; import { ImageConfig } from 'konva/lib/shapes/Image';
import { isEqual } from 'lodash-es';
import { memo, useEffect, useState } from 'react'; import { memo, useEffect, useState } from 'react';
import { Image as KonvaImage } from 'react-konva'; import { Image as KonvaImage } from 'react-konva';
const selector = createSelector( const selector = createSelector([stateSelector], ({ system, canvas }) => {
[stateSelector], const { denoiseProgress } = system;
({ system, canvas }) => { const { boundingBox } = canvas.layerState.stagingArea;
const { denoiseProgress } = system; const { batchIds } = canvas;
const { boundingBox } = canvas.layerState.stagingArea;
const { batchIds } = canvas;
return { return {
boundingBox, boundingBox,
progressImage: progressImage:
denoiseProgress && batchIds.includes(denoiseProgress.batch_id) denoiseProgress && batchIds.includes(denoiseProgress.batch_id)
? denoiseProgress.progress_image ? denoiseProgress.progress_image
: undefined, : undefined,
}; };
}, });
{
memoizeOptions: {
resultEqualityCheck: isEqual,
},
}
);
type Props = Omit<ImageConfig, 'image'>; type Props = Omit<ImageConfig, 'image'>;

View File

@ -1,17 +1,16 @@
import { createSelector } from '@reduxjs/toolkit'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { canvasSelector } from 'features/canvas/store/canvasSelectors';
import { RectConfig } from 'konva/lib/shapes/Rect';
import { Rect } from 'react-konva';
import { rgbaColorToString } from 'features/canvas/util/colorToString'; import { rgbaColorToString } from 'features/canvas/util/colorToString';
import Konva from 'konva'; import Konva from 'konva';
import { RectConfig } from 'konva/lib/shapes/Rect';
import { isNumber } from 'lodash-es'; import { isNumber } from 'lodash-es';
import { memo, useCallback, useEffect, useRef, useState } from 'react'; import { memo, useCallback, useEffect, useRef, useState } from 'react';
import { Rect } from 'react-konva';
export const canvasMaskCompositerSelector = createSelector( export const canvasMaskCompositerSelector = createMemoizedSelector(
canvasSelector, stateSelector,
(canvas) => { ({ canvas }) => {
const { maskColor, stageCoordinates, stageDimensions, stageScale } = canvas; const { maskColor, stageCoordinates, stageDimensions, stageScale } = canvas;
return { return {

View File

@ -1,22 +1,15 @@
import { createSelector } from '@reduxjs/toolkit'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { canvasSelector } from 'features/canvas/store/canvasSelectors';
import { GroupConfig } from 'konva/lib/Group';
import { isEqual } from 'lodash-es';
import { Group, Line } from 'react-konva';
import { isCanvasMaskLine } from 'features/canvas/store/canvasTypes'; import { isCanvasMaskLine } from 'features/canvas/store/canvasTypes';
import { GroupConfig } from 'konva/lib/Group';
import { memo } from 'react'; import { memo } from 'react';
import { Group, Line } from 'react-konva';
export const canvasLinesSelector = createSelector( export const canvasLinesSelector = createMemoizedSelector(
[canvasSelector], [stateSelector],
(canvas) => { ({ canvas }) => {
return { objects: canvas.layerState.objects }; return { objects: canvas.layerState.objects };
},
{
memoizeOptions: {
resultEqualityCheck: isEqual,
},
} }
); );

View File

@ -1,35 +1,25 @@
import { createSelector } from '@reduxjs/toolkit'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { canvasSelector } from 'features/canvas/store/canvasSelectors';
import { rgbaColorToString } from 'features/canvas/util/colorToString';
import { isEqual } from 'lodash-es';
import { Group, Line, Rect } from 'react-konva';
import { import {
isCanvasBaseImage, isCanvasBaseImage,
isCanvasBaseLine, isCanvasBaseLine,
isCanvasEraseRect, isCanvasEraseRect,
isCanvasFillRect, isCanvasFillRect,
} from 'features/canvas/store/canvasTypes'; } from 'features/canvas/store/canvasTypes';
import IAICanvasImage from './IAICanvasImage'; import { rgbaColorToString } from 'features/canvas/util/colorToString';
import { memo } from 'react'; import { memo } from 'react';
import { Group, Line, Rect } from 'react-konva';
import IAICanvasImage from './IAICanvasImage';
const selector = createSelector( const selector = createMemoizedSelector([stateSelector], ({ canvas }) => {
[canvasSelector], const {
(canvas) => { layerState: { objects },
const { } = canvas;
layerState: { objects }, return {
} = canvas; objects,
return { };
objects, });
};
},
{
memoizeOptions: {
resultEqualityCheck: isEqual,
},
}
);
const IAICanvasObjectRenderer = () => { const IAICanvasObjectRenderer = () => {
const { objects } = useAppSelector(selector); const { objects } = useAppSelector(selector);

View File

@ -1,46 +1,37 @@
import { createSelector } from '@reduxjs/toolkit'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { canvasSelector } from 'features/canvas/store/canvasSelectors';
import { GroupConfig } from 'konva/lib/Group'; import { GroupConfig } from 'konva/lib/Group';
import { isEqual } from 'lodash-es';
import { memo } from 'react'; import { memo } from 'react';
import { Group, Rect } from 'react-konva'; import { Group, Rect } from 'react-konva';
import IAICanvasImage from './IAICanvasImage'; import IAICanvasImage from './IAICanvasImage';
const selector = createSelector( const selector = createMemoizedSelector([stateSelector], ({ canvas }) => {
[canvasSelector], const {
(canvas) => { layerState,
const { shouldShowStagingImage,
layerState, shouldShowStagingOutline,
shouldShowStagingImage, boundingBoxCoordinates: stageBoundingBoxCoordinates,
shouldShowStagingOutline, boundingBoxDimensions: stageBoundingBoxDimensions,
boundingBoxCoordinates: stageBoundingBoxCoordinates, } = canvas;
boundingBoxDimensions: stageBoundingBoxDimensions,
} = canvas;
const { selectedImageIndex, images, boundingBox } = layerState.stagingArea; const { selectedImageIndex, images, boundingBox } = layerState.stagingArea;
return { return {
currentStagingAreaImage: currentStagingAreaImage:
images.length > 0 && selectedImageIndex !== undefined images.length > 0 && selectedImageIndex !== undefined
? images[selectedImageIndex] ? images[selectedImageIndex]
: undefined, : undefined,
isOnFirstImage: selectedImageIndex === 0, isOnFirstImage: selectedImageIndex === 0,
isOnLastImage: selectedImageIndex === images.length - 1, isOnLastImage: selectedImageIndex === images.length - 1,
shouldShowStagingImage, shouldShowStagingImage,
shouldShowStagingOutline, shouldShowStagingOutline,
x: boundingBox?.x ?? stageBoundingBoxCoordinates.x, x: boundingBox?.x ?? stageBoundingBoxCoordinates.x,
y: boundingBox?.y ?? stageBoundingBoxCoordinates.y, y: boundingBox?.y ?? stageBoundingBoxCoordinates.y,
width: boundingBox?.width ?? stageBoundingBoxDimensions.width, width: boundingBox?.width ?? stageBoundingBoxDimensions.width,
height: boundingBox?.height ?? stageBoundingBoxDimensions.height, height: boundingBox?.height ?? stageBoundingBoxDimensions.height,
}; };
}, });
{
memoizeOptions: {
resultEqualityCheck: isEqual,
},
}
);
type Props = GroupConfig; type Props = GroupConfig;

View File

@ -1,8 +1,11 @@
import { ButtonGroup, Flex } from '@chakra-ui/react'; import { ButtonGroup, Flex } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit'; import { skipToken } from '@reduxjs/toolkit/query';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAIButton from 'common/components/IAIButton';
import IAIIconButton from 'common/components/IAIIconButton'; import IAIIconButton from 'common/components/IAIIconButton';
import { canvasSelector } from 'features/canvas/store/canvasSelectors'; import { stagingAreaImageSaved } from 'features/canvas/store/actions';
import { import {
commitStagingAreaImage, commitStagingAreaImage,
discardStagedImages, discardStagedImages,
@ -11,10 +14,6 @@ import {
setShouldShowStagingImage, setShouldShowStagingImage,
setShouldShowStagingOutline, setShouldShowStagingOutline,
} from 'features/canvas/store/canvasSlice'; } from 'features/canvas/store/canvasSlice';
import { skipToken } from '@reduxjs/toolkit/dist/query';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import IAIButton from 'common/components/IAIButton';
import { memo, useCallback } from 'react'; 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';
@ -28,30 +27,25 @@ import {
FaTimes, FaTimes,
} from 'react-icons/fa'; } from 'react-icons/fa';
import { useGetImageDTOQuery } from 'services/api/endpoints/images'; import { useGetImageDTOQuery } from 'services/api/endpoints/images';
import { stagingAreaImageSaved } from 'features/canvas/store/actions';
const selector = createSelector( const selector = createMemoizedSelector([stateSelector], ({ canvas }) => {
[canvasSelector], const {
(canvas) => { layerState: {
const { stagingArea: { images, selectedImageIndex },
layerState: { },
stagingArea: { images, selectedImageIndex }, shouldShowStagingOutline,
}, shouldShowStagingImage,
shouldShowStagingOutline, } = canvas;
shouldShowStagingImage,
} = canvas;
return { return {
currentIndex: selectedImageIndex, currentIndex: selectedImageIndex,
total: images.length, total: images.length,
currentStagingAreaImage: currentStagingAreaImage:
images.length > 0 ? images[selectedImageIndex] : undefined, images.length > 0 ? images[selectedImageIndex] : undefined,
shouldShowStagingImage, shouldShowStagingImage,
shouldShowStagingOutline, shouldShowStagingOutline,
}; };
}, });
defaultSelectorOptions
);
const IAICanvasStagingAreaToolbar = () => { const IAICanvasStagingAreaToolbar = () => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();

View File

@ -1,74 +1,65 @@
import { Box, Flex } from '@chakra-ui/react'; import { Box, Flex } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { canvasSelector } from 'features/canvas/store/canvasSelectors';
import GenerationModeStatusText from 'features/parameters/components/Parameters/Canvas/GenerationModeStatusText';
import { isEqual } from 'lodash-es';
import { useTranslation } from 'react-i18next';
import roundToHundreth from 'features/canvas/util/roundToHundreth'; import roundToHundreth from 'features/canvas/util/roundToHundreth';
import IAICanvasStatusTextCursorPos from './IAICanvasStatusText/IAICanvasStatusTextCursorPos'; import GenerationModeStatusText from 'features/parameters/components/Parameters/Canvas/GenerationModeStatusText';
import { memo } from 'react'; import { memo } from 'react';
import { useTranslation } from 'react-i18next';
import IAICanvasStatusTextCursorPos from './IAICanvasStatusText/IAICanvasStatusTextCursorPos';
const warningColor = 'var(--invokeai-colors-warning-500)'; const warningColor = 'var(--invokeai-colors-warning-500)';
const selector = createSelector( const selector = createMemoizedSelector([stateSelector], ({ canvas }) => {
[canvasSelector], const {
(canvas) => { stageDimensions: { width: stageWidth, height: stageHeight },
const { stageCoordinates: { x: stageX, y: stageY },
stageDimensions: { width: stageWidth, height: stageHeight }, boundingBoxDimensions: { width: boxWidth, height: boxHeight },
stageCoordinates: { x: stageX, y: stageY }, scaledBoundingBoxDimensions: {
boundingBoxDimensions: { width: boxWidth, height: boxHeight }, width: scaledBoxWidth,
scaledBoundingBoxDimensions: { height: scaledBoxHeight,
width: scaledBoxWidth,
height: scaledBoxHeight,
},
boundingBoxCoordinates: { x: boxX, y: boxY },
stageScale,
shouldShowCanvasDebugInfo,
layer,
boundingBoxScaleMethod,
shouldPreserveMaskedArea,
} = canvas;
let boundingBoxColor = 'inherit';
if (
(boundingBoxScaleMethod === 'none' &&
(boxWidth < 512 || boxHeight < 512)) ||
(boundingBoxScaleMethod === 'manual' &&
scaledBoxWidth * scaledBoxHeight < 512 * 512)
) {
boundingBoxColor = warningColor;
}
const activeLayerColor = layer === 'mask' ? warningColor : 'inherit';
return {
activeLayerColor,
layer,
boundingBoxColor,
boundingBoxCoordinatesString: `(${roundToHundreth(
boxX
)}, ${roundToHundreth(boxY)})`,
boundingBoxDimensionsString: `${boxWidth}×${boxHeight}`,
scaledBoundingBoxDimensionsString: `${scaledBoxWidth}×${scaledBoxHeight}`,
canvasCoordinatesString: `${roundToHundreth(stageX)}×${roundToHundreth(
stageY
)}`,
canvasDimensionsString: `${stageWidth}×${stageHeight}`,
canvasScaleString: Math.round(stageScale * 100),
shouldShowCanvasDebugInfo,
shouldShowBoundingBox: boundingBoxScaleMethod !== 'auto',
shouldShowScaledBoundingBox: boundingBoxScaleMethod !== 'none',
shouldPreserveMaskedArea,
};
},
{
memoizeOptions: {
resultEqualityCheck: isEqual,
}, },
boundingBoxCoordinates: { x: boxX, y: boxY },
stageScale,
shouldShowCanvasDebugInfo,
layer,
boundingBoxScaleMethod,
shouldPreserveMaskedArea,
} = canvas;
let boundingBoxColor = 'inherit';
if (
(boundingBoxScaleMethod === 'none' &&
(boxWidth < 512 || boxHeight < 512)) ||
(boundingBoxScaleMethod === 'manual' &&
scaledBoxWidth * scaledBoxHeight < 512 * 512)
) {
boundingBoxColor = warningColor;
} }
);
const activeLayerColor = layer === 'mask' ? warningColor : 'inherit';
return {
activeLayerColor,
layer,
boundingBoxColor,
boundingBoxCoordinatesString: `(${roundToHundreth(boxX)}, ${roundToHundreth(
boxY
)})`,
boundingBoxDimensionsString: `${boxWidth}×${boxHeight}`,
scaledBoundingBoxDimensionsString: `${scaledBoxWidth}×${scaledBoxHeight}`,
canvasCoordinatesString: `${roundToHundreth(stageX)}×${roundToHundreth(
stageY
)}`,
canvasDimensionsString: `${stageWidth}×${stageHeight}`,
canvasScaleString: Math.round(stageScale * 100),
shouldShowCanvasDebugInfo,
shouldShowBoundingBox: boundingBoxScaleMethod !== 'auto',
shouldShowScaledBoundingBox: boundingBoxScaleMethod !== 'none',
shouldPreserveMaskedArea,
};
});
const IAICanvasStatusText = () => { const IAICanvasStatusText = () => {
const { const {

View File

@ -1,15 +1,13 @@
import { Box } from '@chakra-ui/react'; import { Box } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { canvasSelector } from 'features/canvas/store/canvasSelectors';
import roundToHundreth from 'features/canvas/util/roundToHundreth'; import roundToHundreth from 'features/canvas/util/roundToHundreth';
import { isEqual } from 'lodash-es';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
const cursorPositionSelector = createSelector( const cursorPositionSelector = createMemoizedSelector(
[canvasSelector], [stateSelector],
(canvas) => { ({ canvas }) => {
const { cursorPosition } = canvas; const { cursorPosition } = canvas;
const { cursorX, cursorY } = cursorPosition const { cursorX, cursorY } = cursorPosition
@ -21,11 +19,6 @@ const cursorPositionSelector = createSelector(
cursorY cursorY
)})`, )})`,
}; };
},
{
memoizeOptions: {
resultEqualityCheck: isEqual,
},
} }
); );

View File

@ -1,20 +1,18 @@
import { createSelector } from '@reduxjs/toolkit'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { canvasSelector } from 'features/canvas/store/canvasSelectors';
import { rgbaColorToString } from 'features/canvas/util/colorToString'; import { rgbaColorToString } from 'features/canvas/util/colorToString';
import { GroupConfig } from 'konva/lib/Group';
import { isEqual } from 'lodash-es';
import { Circle, Group } from 'react-konva';
import { import {
COLOR_PICKER_SIZE, COLOR_PICKER_SIZE,
COLOR_PICKER_STROKE_RADIUS, COLOR_PICKER_STROKE_RADIUS,
} from 'features/canvas/util/constants'; } from 'features/canvas/util/constants';
import { GroupConfig } from 'konva/lib/Group';
import { memo } from 'react'; import { memo } from 'react';
import { Circle, Group } from 'react-konva';
const canvasBrushPreviewSelector = createSelector( const canvasBrushPreviewSelector = createMemoizedSelector(
canvasSelector, stateSelector,
(canvas) => { ({ canvas }) => {
const { const {
cursorPosition, cursorPosition,
brushSize, brushSize,
@ -105,11 +103,6 @@ const canvasBrushPreviewSelector = createSelector(
dotRadius: 1.5 / stageScale, dotRadius: 1.5 / stageScale,
clip, clip,
}; };
},
{
memoizeOptions: {
resultEqualityCheck: isEqual,
},
} }
); );

View File

@ -1,4 +1,4 @@
import { createSelector } from '@reduxjs/toolkit'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store'; import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { import {
@ -17,13 +17,11 @@ import Konva from 'konva';
import { GroupConfig } from 'konva/lib/Group'; import { GroupConfig } from 'konva/lib/Group';
import { KonvaEventObject } from 'konva/lib/Node'; import { KonvaEventObject } from 'konva/lib/Node';
import { Vector2d } from 'konva/lib/types'; import { Vector2d } from 'konva/lib/types';
import { isEqual } from 'lodash-es';
import { memo, useCallback, useEffect, useRef, useState } from 'react'; import { memo, useCallback, useEffect, useRef, useState } from 'react';
import { useHotkeys } from 'react-hotkeys-hook'; import { useHotkeys } from 'react-hotkeys-hook';
import { Group, Rect, Transformer } from 'react-konva'; import { Group, Rect, Transformer } from 'react-konva';
const boundingBoxPreviewSelector = createSelector( const boundingBoxPreviewSelector = createMemoizedSelector(
[stateSelector], [stateSelector],
({ canvas, generation }) => { ({ canvas, generation }) => {
const { const {
@ -51,11 +49,6 @@ const boundingBoxPreviewSelector = createSelector(
hitStrokeWidth: 20 / stageScale, hitStrokeWidth: 20 / stageScale,
aspectRatio, aspectRatio,
}; };
},
{
memoizeOptions: {
resultEqualityCheck: isEqual,
},
} }
); );

View File

@ -1,5 +1,6 @@
import { Box, ButtonGroup, Flex } from '@chakra-ui/react'; import { Box, ButtonGroup, Flex } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAIButton from 'common/components/IAIButton'; import IAIButton from 'common/components/IAIButton';
import IAIColorPicker from 'common/components/IAIColorPicker'; import IAIColorPicker from 'common/components/IAIColorPicker';
@ -7,10 +8,7 @@ import IAIIconButton from 'common/components/IAIIconButton';
import IAIPopover from 'common/components/IAIPopover'; import IAIPopover from 'common/components/IAIPopover';
import IAISimpleCheckbox from 'common/components/IAISimpleCheckbox'; import IAISimpleCheckbox from 'common/components/IAISimpleCheckbox';
import { canvasMaskSavedToGallery } from 'features/canvas/store/actions'; import { canvasMaskSavedToGallery } from 'features/canvas/store/actions';
import { import { isStagingSelector } from 'features/canvas/store/canvasSelectors';
canvasSelector,
isStagingSelector,
} from 'features/canvas/store/canvasSelectors';
import { import {
clearMask, clearMask,
setIsMaskEnabled, setIsMaskEnabled,
@ -19,17 +17,15 @@ import {
setShouldPreserveMaskedArea, setShouldPreserveMaskedArea,
} from 'features/canvas/store/canvasSlice'; } from 'features/canvas/store/canvasSlice';
import { rgbaColorToString } from 'features/canvas/util/colorToString'; import { rgbaColorToString } from 'features/canvas/util/colorToString';
import { isEqual } from 'lodash-es';
import { ChangeEvent, memo, useCallback } from 'react'; import { ChangeEvent, memo, useCallback } from 'react';
import { RgbaColor } from 'react-colorful'; import { RgbaColor } from 'react-colorful';
import { useHotkeys } from 'react-hotkeys-hook'; import { useHotkeys } from 'react-hotkeys-hook';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { FaMask, FaSave, FaTrash } from 'react-icons/fa'; import { FaMask, FaSave, FaTrash } from 'react-icons/fa';
export const selector = createSelector( export const selector = createMemoizedSelector(
[canvasSelector, isStagingSelector], [stateSelector, isStagingSelector],
(canvas, isStaging) => { ({ canvas }, isStaging) => {
const { maskColor, layer, isMaskEnabled, shouldPreserveMaskedArea } = const { maskColor, layer, isMaskEnabled, shouldPreserveMaskedArea } =
canvas; canvas;
@ -41,11 +37,6 @@ export const selector = createSelector(
shouldPreserveMaskedArea, shouldPreserveMaskedArea,
isStaging, isStaging,
}; };
},
{
memoizeOptions: {
resultEqualityCheck: isEqual,
},
} }
); );
const IAICanvasMaskOptions = () => { const IAICanvasMaskOptions = () => {

View File

@ -1,18 +1,15 @@
import { createSelector } from '@reduxjs/toolkit'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAIIconButton from 'common/components/IAIIconButton'; import IAIIconButton from 'common/components/IAIIconButton';
import { redo } from 'features/canvas/store/canvasSlice';
import { activeTabNameSelector } from 'features/ui/store/uiSelectors'; import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
import { useCallback } from 'react';
import { useHotkeys } from 'react-hotkeys-hook'; import { useHotkeys } from 'react-hotkeys-hook';
import { useTranslation } from 'react-i18next';
import { FaRedo } from 'react-icons/fa'; import { FaRedo } from 'react-icons/fa';
import { redo } from 'features/canvas/store/canvasSlice'; const canvasRedoSelector = createMemoizedSelector(
import { stateSelector } from 'app/store/store';
import { isEqual } from 'lodash-es';
import { useTranslation } from 'react-i18next';
import { useCallback } from 'react';
const canvasRedoSelector = createSelector(
[stateSelector, activeTabNameSelector], [stateSelector, activeTabNameSelector],
({ canvas }, activeTabName) => { ({ canvas }, activeTabName) => {
const { futureLayerStates } = canvas; const { futureLayerStates } = canvas;
@ -21,11 +18,6 @@ const canvasRedoSelector = createSelector(
canRedo: futureLayerStates.length > 0, canRedo: futureLayerStates.length > 0,
activeTabName, activeTabName,
}; };
},
{
memoizeOptions: {
resultEqualityCheck: isEqual,
},
} }
); );

View File

@ -1,10 +1,11 @@
import { Flex } from '@chakra-ui/react'; import { Flex } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAISimpleCheckbox from 'common/components/IAISimpleCheckbox';
import IAIIconButton from 'common/components/IAIIconButton'; import IAIIconButton from 'common/components/IAIIconButton';
import IAIPopover from 'common/components/IAIPopover'; import IAIPopover from 'common/components/IAIPopover';
import { canvasSelector } from 'features/canvas/store/canvasSelectors'; import IAISimpleCheckbox from 'common/components/IAISimpleCheckbox';
import ClearCanvasHistoryButtonModal from 'features/canvas/components/ClearCanvasHistoryButtonModal';
import { import {
setShouldAntialias, setShouldAntialias,
setShouldAutoSave, setShouldAutoSave,
@ -16,17 +17,14 @@ import {
setShouldShowIntermediates, setShouldShowIntermediates,
setShouldSnapToGrid, setShouldSnapToGrid,
} from 'features/canvas/store/canvasSlice'; } from 'features/canvas/store/canvasSlice';
import { isEqual } from 'lodash-es';
import { ChangeEvent, memo, useCallback } from 'react'; import { ChangeEvent, 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 { FaWrench } from 'react-icons/fa'; import { FaWrench } from 'react-icons/fa';
import ClearCanvasHistoryButtonModal from 'features/canvas/components/ClearCanvasHistoryButtonModal';
export const canvasControlsSelector = createSelector( export const canvasControlsSelector = createMemoizedSelector(
[canvasSelector], [stateSelector],
(canvas) => { ({ canvas }) => {
const { const {
shouldAutoSave, shouldAutoSave,
shouldCropToBoundingBoxOnSave, shouldCropToBoundingBoxOnSave,
@ -50,11 +48,6 @@ export const canvasControlsSelector = createSelector(
shouldRestrictStrokesToBox, shouldRestrictStrokesToBox,
shouldAntialias, shouldAntialias,
}; };
},
{
memoizeOptions: {
resultEqualityCheck: isEqual,
},
} }
); );

View File

@ -1,5 +1,5 @@
import { ButtonGroup, Flex, Box } from '@chakra-ui/react'; import { Box, ButtonGroup, Flex } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store'; import { stateSelector } from 'app/store/store';
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';
@ -14,10 +14,9 @@ import {
setBrushSize, setBrushSize,
setTool, setTool,
} from 'features/canvas/store/canvasSlice'; } from 'features/canvas/store/canvasSlice';
import { clamp, isEqual } from 'lodash-es'; import { clamp } from 'lodash-es';
import { memo, useCallback } from 'react'; import { memo, useCallback } from 'react';
import { RgbaColor } from 'react-colorful'; import { RgbaColor } from 'react-colorful';
import { useHotkeys } from 'react-hotkeys-hook'; import { useHotkeys } from 'react-hotkeys-hook';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { import {
@ -29,7 +28,7 @@ import {
FaSlidersH, FaSlidersH,
} from 'react-icons/fa'; } from 'react-icons/fa';
export const selector = createSelector( export const selector = createMemoizedSelector(
[stateSelector, isStagingSelector], [stateSelector, isStagingSelector],
({ canvas }, isStaging) => { ({ canvas }, isStaging) => {
const { tool, brushColor, brushSize } = canvas; const { tool, brushColor, brushSize } = canvas;
@ -40,11 +39,6 @@ export const selector = createSelector(
brushColor, brushColor,
brushSize, brushSize,
}; };
},
{
memoizeOptions: {
resultEqualityCheck: isEqual,
},
} }
); );

View File

@ -1,9 +1,10 @@
import { Box, ButtonGroup, Flex } from '@chakra-ui/react'; import { Box, ButtonGroup, Flex } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store'; import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAIIconButton from 'common/components/IAIIconButton'; import IAIIconButton from 'common/components/IAIIconButton';
import IAIMantineSelect from 'common/components/IAIMantineSelect'; import IAIMantineSelect from 'common/components/IAIMantineSelect';
import { useCopyImageToClipboard } from 'common/hooks/useCopyImageToClipboard';
import { useImageUploadButton } from 'common/hooks/useImageUploadButton'; import { useImageUploadButton } from 'common/hooks/useImageUploadButton';
import { useSingleAndDoubleClick } from 'common/hooks/useSingleAndDoubleClick'; import { useSingleAndDoubleClick } from 'common/hooks/useSingleAndDoubleClick';
import { import {
@ -25,8 +26,6 @@ import {
LAYER_NAMES_DICT, LAYER_NAMES_DICT,
} from 'features/canvas/store/canvasTypes'; } from 'features/canvas/store/canvasTypes';
import { getCanvasBaseLayer } from 'features/canvas/util/konvaInstanceProvider'; import { getCanvasBaseLayer } from 'features/canvas/util/konvaInstanceProvider';
import { useCopyImageToClipboard } from 'common/hooks/useCopyImageToClipboard';
import { isEqual } from 'lodash-es';
import { memo, useCallback } from 'react'; 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';
@ -46,7 +45,7 @@ import IAICanvasSettingsButtonPopover from './IAICanvasSettingsButtonPopover';
import IAICanvasToolChooserOptions from './IAICanvasToolChooserOptions'; import IAICanvasToolChooserOptions from './IAICanvasToolChooserOptions';
import IAICanvasUndoButton from './IAICanvasUndoButton'; import IAICanvasUndoButton from './IAICanvasUndoButton';
export const selector = createSelector( export const selector = createMemoizedSelector(
[stateSelector, isStagingSelector], [stateSelector, isStagingSelector],
({ canvas }, isStaging) => { ({ canvas }, isStaging) => {
const { tool, shouldCropToBoundingBoxOnSave, layer, isMaskEnabled } = const { tool, shouldCropToBoundingBoxOnSave, layer, isMaskEnabled } =
@ -59,11 +58,6 @@ export const selector = createSelector(
layer, layer,
shouldCropToBoundingBoxOnSave, shouldCropToBoundingBoxOnSave,
}; };
},
{
memoizeOptions: {
resultEqualityCheck: isEqual,
},
} }
); );

View File

@ -1,18 +1,15 @@
import { createSelector } from '@reduxjs/toolkit'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAIIconButton from 'common/components/IAIIconButton'; import IAIIconButton from 'common/components/IAIIconButton';
import { useHotkeys } from 'react-hotkeys-hook';
import { FaUndo } from 'react-icons/fa';
import { undo } from 'features/canvas/store/canvasSlice'; import { undo } from 'features/canvas/store/canvasSlice';
import { activeTabNameSelector } from 'features/ui/store/uiSelectors'; import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
import { isEqual } from 'lodash-es';
import { useTranslation } from 'react-i18next';
import { stateSelector } from 'app/store/store';
import { useCallback } from 'react'; import { useCallback } from 'react';
import { useHotkeys } from 'react-hotkeys-hook';
import { useTranslation } from 'react-i18next';
import { FaUndo } from 'react-icons/fa';
const canvasUndoSelector = createSelector( const canvasUndoSelector = createMemoizedSelector(
[stateSelector, activeTabNameSelector], [stateSelector, activeTabNameSelector],
({ canvas }, activeTabName) => { ({ canvas }, activeTabName) => {
const { pastLayerStates } = canvas; const { pastLayerStates } = canvas;
@ -21,11 +18,6 @@ const canvasUndoSelector = createSelector(
canUndo: pastLayerStates.length > 0, canUndo: pastLayerStates.length > 0,
activeTabName, activeTabName,
}; };
},
{
memoizeOptions: {
resultEqualityCheck: isEqual,
},
} }
); );

View File

@ -1,21 +1,18 @@
import { createSelector } from '@reduxjs/toolkit'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { import { isStagingSelector } from 'features/canvas/store/canvasSelectors';
canvasSelector,
isStagingSelector,
} from 'features/canvas/store/canvasSelectors';
import { import {
setIsMovingStage, setIsMovingStage,
setStageCoordinates, setStageCoordinates,
} from 'features/canvas/store/canvasSlice'; } from 'features/canvas/store/canvasSlice';
import { KonvaEventObject } from 'konva/lib/Node'; import { KonvaEventObject } from 'konva/lib/Node';
import { isEqual } from 'lodash-es'; import { isEqual } from 'lodash-es';
import { useCallback } from 'react'; import { useCallback } from 'react';
const selector = createSelector( const selector = createMemoizedSelector(
[canvasSelector, isStagingSelector], [stateSelector, isStagingSelector],
(canvas, isStaging) => { ({ canvas }, isStaging) => {
const { tool, isMovingBoundingBox } = canvas; const { tool, isMovingBoundingBox } = canvas;
return { return {
tool, tool,

View File

@ -1,9 +1,7 @@
import { createSelector } from '@reduxjs/toolkit'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { import { isStagingSelector } from 'features/canvas/store/canvasSelectors';
canvasSelector,
isStagingSelector,
} from 'features/canvas/store/canvasSelectors';
import { import {
clearMask, clearMask,
resetCanvasInteractionState, resetCanvasInteractionState,
@ -12,17 +10,15 @@ import {
setShouldSnapToGrid, setShouldSnapToGrid,
setTool, setTool,
} from 'features/canvas/store/canvasSlice'; } from 'features/canvas/store/canvasSlice';
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
import { isEqual } from 'lodash-es';
import { useRef } from 'react';
import { useHotkeys } from 'react-hotkeys-hook';
import { CanvasTool } from 'features/canvas/store/canvasTypes'; import { CanvasTool } from 'features/canvas/store/canvasTypes';
import { getCanvasStage } from 'features/canvas/util/konvaInstanceProvider'; import { getCanvasStage } from 'features/canvas/util/konvaInstanceProvider';
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
import { useRef } from 'react';
import { useHotkeys } from 'react-hotkeys-hook';
const selector = createSelector( const selector = createMemoizedSelector(
[canvasSelector, activeTabNameSelector, isStagingSelector], [stateSelector, activeTabNameSelector, isStagingSelector],
(canvas, activeTabName, isStaging) => { ({ canvas }, activeTabName, isStaging) => {
const { const {
cursorPosition, cursorPosition,
shouldLockBoundingBox, shouldLockBoundingBox,
@ -42,11 +38,6 @@ const selector = createSelector(
isMaskEnabled, isMaskEnabled,
shouldSnapToGrid, shouldSnapToGrid,
}; };
},
{
memoizeOptions: {
resultEqualityCheck: isEqual,
},
} }
); );

View File

@ -1,26 +1,23 @@
import { createSelector } from '@reduxjs/toolkit'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { import { isStagingSelector } from 'features/canvas/store/canvasSelectors';
canvasSelector,
isStagingSelector,
} from 'features/canvas/store/canvasSelectors';
import { import {
addLine, addLine,
setIsDrawing, setIsDrawing,
setIsMovingStage, setIsMovingStage,
} from 'features/canvas/store/canvasSlice'; } from 'features/canvas/store/canvasSlice';
import getScaledCursorPosition from 'features/canvas/util/getScaledCursorPosition';
import { activeTabNameSelector } from 'features/ui/store/uiSelectors'; import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
import Konva from 'konva'; import Konva from 'konva';
import { KonvaEventObject } from 'konva/lib/Node'; import { KonvaEventObject } from 'konva/lib/Node';
import { isEqual } from 'lodash-es'; import { isEqual } from 'lodash-es';
import { MutableRefObject, useCallback } from 'react'; import { MutableRefObject, useCallback } from 'react';
import getScaledCursorPosition from 'features/canvas/util/getScaledCursorPosition';
import useColorPicker from './useColorUnderCursor'; import useColorPicker from './useColorUnderCursor';
const selector = createSelector( const selector = createMemoizedSelector(
[activeTabNameSelector, canvasSelector, isStagingSelector], [activeTabNameSelector, stateSelector, isStagingSelector],
(activeTabName, canvas, isStaging) => { (activeTabName, { canvas }, isStaging) => {
const { tool } = canvas; const { tool } = canvas;
return { return {
tool, tool,

View File

@ -1,25 +1,22 @@
import { createSelector } from '@reduxjs/toolkit'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { import { isStagingSelector } from 'features/canvas/store/canvasSelectors';
canvasSelector,
isStagingSelector,
} from 'features/canvas/store/canvasSelectors';
import { import {
addPointToCurrentLine, addPointToCurrentLine,
setCursorPosition, setCursorPosition,
} from 'features/canvas/store/canvasSlice'; } from 'features/canvas/store/canvasSlice';
import getScaledCursorPosition from 'features/canvas/util/getScaledCursorPosition';
import { activeTabNameSelector } from 'features/ui/store/uiSelectors'; import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
import Konva from 'konva'; import Konva from 'konva';
import { Vector2d } from 'konva/lib/types'; import { Vector2d } from 'konva/lib/types';
import { isEqual } from 'lodash-es'; import { isEqual } from 'lodash-es';
import { MutableRefObject, useCallback } from 'react'; import { MutableRefObject, useCallback } from 'react';
import getScaledCursorPosition from 'features/canvas/util/getScaledCursorPosition';
import useColorPicker from './useColorUnderCursor'; import useColorPicker from './useColorUnderCursor';
const selector = createSelector( const selector = createMemoizedSelector(
[activeTabNameSelector, canvasSelector, isStagingSelector], [activeTabNameSelector, stateSelector, isStagingSelector],
(activeTabName, canvas, isStaging) => { (activeTabName, { canvas }, isStaging) => {
const { tool, isDrawing } = canvas; const { tool, isDrawing } = canvas;
return { return {
tool, tool,

View File

@ -1,25 +1,22 @@
import { createSelector } from '@reduxjs/toolkit'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { import { isStagingSelector } from 'features/canvas/store/canvasSelectors';
canvasSelector,
isStagingSelector,
} from 'features/canvas/store/canvasSelectors';
import { import {
// addPointToCurrentEraserLine, // addPointToCurrentEraserLine,
addPointToCurrentLine, addPointToCurrentLine,
setIsDrawing, setIsDrawing,
setIsMovingStage, setIsMovingStage,
} from 'features/canvas/store/canvasSlice'; } from 'features/canvas/store/canvasSlice';
import getScaledCursorPosition from 'features/canvas/util/getScaledCursorPosition';
import { activeTabNameSelector } from 'features/ui/store/uiSelectors'; import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
import Konva from 'konva'; import Konva from 'konva';
import { isEqual } from 'lodash-es'; import { isEqual } from 'lodash-es';
import { MutableRefObject, useCallback } from 'react'; import { MutableRefObject, useCallback } from 'react';
import getScaledCursorPosition from 'features/canvas/util/getScaledCursorPosition';
const selector = createSelector( const selector = createMemoizedSelector(
[activeTabNameSelector, canvasSelector, isStagingSelector], [activeTabNameSelector, stateSelector, isStagingSelector],
(activeTabName, canvas, isStaging) => { (activeTabName, { canvas }, isStaging) => {
const { tool, isDrawing } = canvas; const { tool, isDrawing } = canvas;
return { return {
tool, tool,

View File

@ -1,24 +1,23 @@
import { createSelector } from '@reduxjs/toolkit'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { canvasSelector } from 'features/canvas/store/canvasSelectors';
import { import {
setStageCoordinates, setStageCoordinates,
setStageScale, setStageScale,
} from 'features/canvas/store/canvasSlice'; } from 'features/canvas/store/canvasSlice';
import Konva from 'konva';
import { KonvaEventObject } from 'konva/lib/Node';
import { clamp, isEqual } from 'lodash-es';
import { MutableRefObject, useCallback } from 'react';
import { import {
CANVAS_SCALE_BY, CANVAS_SCALE_BY,
MAX_CANVAS_SCALE, MAX_CANVAS_SCALE,
MIN_CANVAS_SCALE, MIN_CANVAS_SCALE,
} from 'features/canvas/util/constants'; } from 'features/canvas/util/constants';
import Konva from 'konva';
import { KonvaEventObject } from 'konva/lib/Node';
import { clamp, isEqual } from 'lodash-es';
import { MutableRefObject, useCallback } from 'react';
const selector = createSelector( const selector = createMemoizedSelector(
[canvasSelector], [stateSelector],
(canvas) => { ({ canvas }) => {
const { isMoveStageKeyHeld, stageScale } = canvas; const { isMoveStageKeyHeld, stageScale } = canvas;
return { return {
isMoveStageKeyHeld, isMoveStageKeyHeld,

View File

@ -1,10 +1,8 @@
import { createSelector } from '@reduxjs/toolkit'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { RootState, stateSelector } from 'app/store/store'; import { RootState, stateSelector } from 'app/store/store';
import { CanvasImage, CanvasState, isCanvasBaseImage } from './canvasTypes'; import { CanvasImage, isCanvasBaseImage } from './canvasTypes';
export const canvasSelector = (state: RootState): CanvasState => state.canvas; export const isStagingSelector = createMemoizedSelector(
export const isStagingSelector = createSelector(
[stateSelector], [stateSelector],
({ canvas }) => ({ canvas }) =>
canvas.batchIds.length > 0 || canvas.batchIds.length > 0 ||

View File

@ -8,25 +8,24 @@ import {
Flex, Flex,
Text, Text,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store'; import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import IAIButton from 'common/components/IAIButton'; import IAIButton from 'common/components/IAIButton';
import IAIMantineSearchableSelect from 'common/components/IAIMantineSearchableSelect'; import IAIMantineSearchableSelect from 'common/components/IAIMantineSearchableSelect';
import {
changeBoardReset,
isModalOpenChanged,
} from 'features/changeBoardModal/store/slice';
import { memo, useCallback, useMemo, useRef, useState } from 'react'; import { memo, useCallback, useMemo, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useListAllBoardsQuery } from 'services/api/endpoints/boards'; import { useListAllBoardsQuery } from 'services/api/endpoints/boards';
import { import {
useAddImagesToBoardMutation, useAddImagesToBoardMutation,
useRemoveImagesFromBoardMutation, useRemoveImagesFromBoardMutation,
} from 'services/api/endpoints/images'; } from 'services/api/endpoints/images';
import {
changeBoardReset,
isModalOpenChanged,
} from 'features/changeBoardModal/store/slice';
import { useTranslation } from 'react-i18next';
const selector = createSelector( const selector = createMemoizedSelector(
[stateSelector], [stateSelector],
({ changeBoardModal }) => { ({ changeBoardModal }) => {
const { isModalOpen, imagesToChange } = changeBoardModal; const { isModalOpen, imagesToChange } = changeBoardModal;
@ -35,8 +34,7 @@ const selector = createSelector(
isModalOpen, isModalOpen,
imagesToChange, imagesToChange,
}; };
}, }
defaultSelectorOptions
); );
const ChangeBoardModal = () => { const ChangeBoardModal = () => {

View File

@ -9,7 +9,6 @@ import {
} from 'features/controlAdapters/store/controlAdaptersSlice'; } from 'features/controlAdapters/store/controlAdaptersSlice';
import ParamControlAdapterModel from './parameters/ParamControlAdapterModel'; import ParamControlAdapterModel from './parameters/ParamControlAdapterModel';
import ParamControlAdapterWeight from './parameters/ParamControlAdapterWeight'; import ParamControlAdapterWeight from './parameters/ParamControlAdapterWeight';
import { ChevronUpIcon } from '@chakra-ui/icons'; import { ChevronUpIcon } from '@chakra-ui/icons';
import IAIIconButton from 'common/components/IAIIconButton'; import IAIIconButton from 'common/components/IAIIconButton';
import IAISwitch from 'common/components/IAISwitch'; import IAISwitch from 'common/components/IAISwitch';

View File

@ -1,9 +1,8 @@
import { Box, Flex, Spinner } from '@chakra-ui/react'; import { Box, Flex, Spinner } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit'; import { skipToken } from '@reduxjs/toolkit/query';
import { skipToken } from '@reduxjs/toolkit/dist/query'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store'; import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import IAIDndImage from 'common/components/IAIDndImage'; import IAIDndImage from 'common/components/IAIDndImage';
import IAIDndImageIcon from 'common/components/IAIDndImageIcon'; import IAIDndImageIcon from 'common/components/IAIDndImageIcon';
import { setBoundingBoxDimensions } from 'features/canvas/store/canvasSlice'; import { setBoundingBoxDimensions } from 'features/canvas/store/canvasSlice';
@ -33,7 +32,7 @@ type Props = {
isSmall?: boolean; isSmall?: boolean;
}; };
const selector = createSelector( const selector = createMemoizedSelector(
stateSelector, stateSelector,
({ controlAdapters, gallery, system }) => { ({ controlAdapters, gallery, system }) => {
const { pendingControlImages } = controlAdapters; const { pendingControlImages } = controlAdapters;
@ -45,8 +44,7 @@ const selector = createSelector(
autoAddBoardId, autoAddBoardId,
isConnected, isConnected,
}; };
}, }
defaultSelectorOptions
); );
const ControlAdapterImagePreview = ({ isSmall, id }: Props) => { const ControlAdapterImagePreview = ({ isSmall, id }: Props) => {

View File

@ -1,11 +1,11 @@
import { ButtonGroup, Divider, Flex } from '@chakra-ui/react'; import { ButtonGroup, Divider, Flex } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store'; import { stateSelector } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import IAIButton from 'common/components/IAIButton'; import IAIButton from 'common/components/IAIButton';
import IAICollapse from 'common/components/IAICollapse'; import IAICollapse from 'common/components/IAICollapse';
import ControlAdapterConfig from 'features/controlAdapters/components/ControlAdapterConfig'; import ControlAdapterConfig from 'features/controlAdapters/components/ControlAdapterConfig';
import { useAddControlAdapter } from 'features/controlAdapters/hooks/useAddControlAdapter';
import { import {
selectAllControlNets, selectAllControlNets,
selectAllIPAdapters, selectAllIPAdapters,
@ -19,9 +19,8 @@ import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
import { Fragment, memo } from 'react'; import { Fragment, memo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { FaPlus } from 'react-icons/fa'; import { FaPlus } from 'react-icons/fa';
import { useAddControlAdapter } from 'features/controlAdapters/hooks/useAddControlAdapter';
const selector = createSelector( const selector = createMemoizedSelector(
[stateSelector], [stateSelector],
({ controlAdapters }) => { ({ controlAdapters }) => {
const activeLabel: string[] = []; const activeLabel: string[] = [];
@ -68,8 +67,7 @@ const selector = createSelector(
activeLabel: activeLabel.join(', '), activeLabel: activeLabel.join(', '),
isError, // TODO: Add some visual indicator that the control adapters are in an error state isError, // TODO: Add some visual indicator that the control adapters are in an error state
}; };
}, }
defaultSelectorOptions
); );
const ControlAdaptersCollapse = () => { const ControlAdaptersCollapse = () => {

View File

@ -1,7 +1,6 @@
import { createSelector } from '@reduxjs/toolkit'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store'; import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import IAIMantineSearchableSelect from 'common/components/IAIMantineSearchableSelect'; import IAIMantineSearchableSelect from 'common/components/IAIMantineSearchableSelect';
import IAIMantineSelectItemWithTooltip from 'common/components/IAIMantineSelectItemWithTooltip'; import IAIMantineSelectItemWithTooltip from 'common/components/IAIMantineSelectItemWithTooltip';
import { useControlAdapterIsEnabled } from 'features/controlAdapters/hooks/useControlAdapterIsEnabled'; import { useControlAdapterIsEnabled } from 'features/controlAdapters/hooks/useControlAdapterIsEnabled';
@ -18,14 +17,10 @@ type ParamControlAdapterModelProps = {
id: string; id: string;
}; };
const selector = createSelector( const selector = createMemoizedSelector(stateSelector, ({ generation }) => {
stateSelector, const { model } = generation;
({ generation }) => { return { mainModel: model };
const { model } = generation; });
return { mainModel: model };
},
defaultSelectorOptions
);
const ParamControlAdapterModel = ({ id }: ParamControlAdapterModelProps) => { const ParamControlAdapterModel = ({ id }: ParamControlAdapterModelProps) => {
const isEnabled = useControlAdapterIsEnabled(id); const isEnabled = useControlAdapterIsEnabled(id);

View File

@ -1,53 +1,47 @@
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { createSelector } from '@reduxjs/toolkit';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import IAIMantineSearchableSelect, { import IAIMantineSearchableSelect, {
IAISelectDataType, IAISelectDataType,
} from 'common/components/IAIMantineSearchableSelect'; } from 'common/components/IAIMantineSearchableSelect';
import { useControlAdapterIsEnabled } from 'features/controlAdapters/hooks/useControlAdapterIsEnabled'; import { useControlAdapterIsEnabled } from 'features/controlAdapters/hooks/useControlAdapterIsEnabled';
import { useControlAdapterProcessorNode } from 'features/controlAdapters/hooks/useControlAdapterProcessorNode'; import { useControlAdapterProcessorNode } from 'features/controlAdapters/hooks/useControlAdapterProcessorNode';
import { CONTROLNET_PROCESSORS } from 'features/controlAdapters/store/constants';
import { controlAdapterProcessortTypeChanged } from 'features/controlAdapters/store/controlAdaptersSlice';
import { ControlAdapterProcessorType } from 'features/controlAdapters/store/types';
import { configSelector } from 'features/system/store/configSelectors'; import { configSelector } from 'features/system/store/configSelectors';
import { map } from 'lodash-es'; import { map } from 'lodash-es';
import { memo, useCallback } from 'react'; import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { CONTROLNET_PROCESSORS } from 'features/controlAdapters/store/constants';
import { controlAdapterProcessortTypeChanged } from 'features/controlAdapters/store/controlAdaptersSlice';
import { ControlAdapterProcessorType } from 'features/controlAdapters/store/types';
type Props = { type Props = {
id: string; id: string;
}; };
const selector = createSelector( const selector = createMemoizedSelector(configSelector, (config) => {
configSelector, const controlNetProcessors: IAISelectDataType[] = map(
(config) => { CONTROLNET_PROCESSORS,
const controlNetProcessors: IAISelectDataType[] = map( (p) => ({
CONTROLNET_PROCESSORS, value: p.type,
(p) => ({ label: p.label,
value: p.type, })
label: p.label, )
}) .sort((a, b) =>
// sort 'none' to the top
a.value === 'none'
? -1
: b.value === 'none'
? 1
: a.label.localeCompare(b.label)
) )
.sort((a, b) => .filter(
// sort 'none' to the top (d) =>
a.value === 'none' !config.sd.disabledControlNetProcessors.includes(
? -1 d.value as ControlAdapterProcessorType
: b.value === 'none' )
? 1 );
: a.label.localeCompare(b.label)
)
.filter(
(d) =>
!config.sd.disabledControlNetProcessors.includes(
d.value as ControlAdapterProcessorType
)
);
return controlNetProcessors; return controlNetProcessors;
}, });
defaultSelectorOptions
);
const ParamControlAdapterProcessorSelect = ({ id }: Props) => { const ParamControlAdapterProcessorSelect = ({ id }: Props) => {
const isEnabled = useControlAdapterIsEnabled(id); const isEnabled = useControlAdapterIsEnabled(id);

View File

@ -1,17 +1,14 @@
import { createSelector } from '@reduxjs/toolkit'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store'; import { stateSelector } from 'app/store/store';
import { useMemo } from 'react';
import { selectControlAdapterById } from 'features/controlAdapters/store/controlAdaptersSlice';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; import { selectControlAdapterById } from 'features/controlAdapters/store/controlAdaptersSlice';
import { useMemo } from 'react';
export const useControlAdapter = (id: string) => { export const useControlAdapter = (id: string) => {
const selector = useMemo( const selector = useMemo(
() => () =>
createSelector( createMemoizedSelector(stateSelector, ({ controlAdapters }) =>
stateSelector, selectControlAdapterById(controlAdapters, id)
({ controlAdapters }) => selectControlAdapterById(controlAdapters, id),
defaultSelectorOptions
), ),
[id] [id]
); );

View File

@ -1,26 +1,21 @@
import { createSelector } from '@reduxjs/toolkit'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store'; import { stateSelector } from 'app/store/store';
import { useMemo } from 'react';
import { selectControlAdapterById } from 'features/controlAdapters/store/controlAdaptersSlice';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; import { selectControlAdapterById } from 'features/controlAdapters/store/controlAdaptersSlice';
import { useMemo } from 'react';
export const useControlAdapterBeginEndStepPct = (id: string) => { export const useControlAdapterBeginEndStepPct = (id: string) => {
const selector = useMemo( const selector = useMemo(
() => () =>
createSelector( createMemoizedSelector(stateSelector, ({ controlAdapters }) => {
stateSelector, const cn = selectControlAdapterById(controlAdapters, id);
({ controlAdapters }) => { return cn
const cn = selectControlAdapterById(controlAdapters, id); ? {
return cn beginStepPct: cn.beginStepPct,
? { endStepPct: cn.endStepPct,
beginStepPct: cn.beginStepPct, }
endStepPct: cn.endStepPct, : undefined;
} }),
: undefined;
},
defaultSelectorOptions
),
[id] [id]
); );

View File

@ -1,18 +1,16 @@
import { createSelector } from '@reduxjs/toolkit'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store'; import { stateSelector } from 'app/store/store';
import { useMemo } from 'react';
import { selectControlAdapterById } from 'features/controlAdapters/store/controlAdaptersSlice';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; import { selectControlAdapterById } from 'features/controlAdapters/store/controlAdaptersSlice';
import { useMemo } from 'react';
export const useControlAdapterControlImage = (id: string) => { export const useControlAdapterControlImage = (id: string) => {
const selector = useMemo( const selector = useMemo(
() => () =>
createSelector( createMemoizedSelector(
stateSelector, stateSelector,
({ controlAdapters }) => ({ controlAdapters }) =>
selectControlAdapterById(controlAdapters, id)?.controlImage, selectControlAdapterById(controlAdapters, id)?.controlImage
defaultSelectorOptions
), ),
[id] [id]
); );

View File

@ -1,25 +1,20 @@
import { createSelector } from '@reduxjs/toolkit'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store'; import { stateSelector } from 'app/store/store';
import { useMemo } from 'react';
import { selectControlAdapterById } from 'features/controlAdapters/store/controlAdaptersSlice';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; import { selectControlAdapterById } from 'features/controlAdapters/store/controlAdaptersSlice';
import { isControlNet } from 'features/controlAdapters/store/types'; import { isControlNet } from 'features/controlAdapters/store/types';
import { useMemo } from 'react';
export const useControlAdapterControlMode = (id: string) => { export const useControlAdapterControlMode = (id: string) => {
const selector = useMemo( const selector = useMemo(
() => () =>
createSelector( createMemoizedSelector(stateSelector, ({ controlAdapters }) => {
stateSelector, const ca = selectControlAdapterById(controlAdapters, id);
({ controlAdapters }) => { if (ca && isControlNet(ca)) {
const ca = selectControlAdapterById(controlAdapters, id); return ca.controlMode;
if (ca && isControlNet(ca)) { }
return ca.controlMode; return undefined;
} }),
return undefined;
},
defaultSelectorOptions
),
[id] [id]
); );

View File

@ -1,18 +1,16 @@
import { createSelector } from '@reduxjs/toolkit'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store'; import { stateSelector } from 'app/store/store';
import { useMemo } from 'react';
import { selectControlAdapterById } from 'features/controlAdapters/store/controlAdaptersSlice';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; import { selectControlAdapterById } from 'features/controlAdapters/store/controlAdaptersSlice';
import { useMemo } from 'react';
export const useControlAdapterIsEnabled = (id: string) => { export const useControlAdapterIsEnabled = (id: string) => {
const selector = useMemo( const selector = useMemo(
() => () =>
createSelector( createMemoizedSelector(
stateSelector, stateSelector,
({ controlAdapters }) => ({ controlAdapters }) =>
selectControlAdapterById(controlAdapters, id)?.isEnabled ?? false, selectControlAdapterById(controlAdapters, id)?.isEnabled ?? false
defaultSelectorOptions
), ),
[id] [id]
); );

View File

@ -1,18 +1,16 @@
import { createSelector } from '@reduxjs/toolkit'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store'; import { stateSelector } from 'app/store/store';
import { useMemo } from 'react';
import { selectControlAdapterById } from 'features/controlAdapters/store/controlAdaptersSlice';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; import { selectControlAdapterById } from 'features/controlAdapters/store/controlAdaptersSlice';
import { useMemo } from 'react';
export const useControlAdapterModel = (id: string) => { export const useControlAdapterModel = (id: string) => {
const selector = useMemo( const selector = useMemo(
() => () =>
createSelector( createMemoizedSelector(
stateSelector, stateSelector,
({ controlAdapters }) => ({ controlAdapters }) =>
selectControlAdapterById(controlAdapters, id)?.model, selectControlAdapterById(controlAdapters, id)?.model
defaultSelectorOptions
), ),
[id] [id]
); );

View File

@ -1,25 +1,20 @@
import { createSelector } from '@reduxjs/toolkit'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store'; import { stateSelector } from 'app/store/store';
import { useMemo } from 'react';
import { selectControlAdapterById } from 'features/controlAdapters/store/controlAdaptersSlice';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; import { selectControlAdapterById } from 'features/controlAdapters/store/controlAdaptersSlice';
import { isControlNetOrT2IAdapter } from 'features/controlAdapters/store/types'; import { isControlNetOrT2IAdapter } from 'features/controlAdapters/store/types';
import { useMemo } from 'react';
export const useControlAdapterProcessedControlImage = (id: string) => { export const useControlAdapterProcessedControlImage = (id: string) => {
const selector = useMemo( const selector = useMemo(
() => () =>
createSelector( createMemoizedSelector(stateSelector, ({ controlAdapters }) => {
stateSelector, const ca = selectControlAdapterById(controlAdapters, id);
({ controlAdapters }) => {
const ca = selectControlAdapterById(controlAdapters, id);
return ca && isControlNetOrT2IAdapter(ca) return ca && isControlNetOrT2IAdapter(ca)
? ca.processedControlImage ? ca.processedControlImage
: undefined; : undefined;
}, }),
defaultSelectorOptions
),
[id] [id]
); );

View File

@ -1,25 +1,20 @@
import { createSelector } from '@reduxjs/toolkit'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store'; import { stateSelector } from 'app/store/store';
import { useMemo } from 'react';
import { selectControlAdapterById } from 'features/controlAdapters/store/controlAdaptersSlice';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; import { selectControlAdapterById } from 'features/controlAdapters/store/controlAdaptersSlice';
import { isControlNetOrT2IAdapter } from 'features/controlAdapters/store/types'; import { isControlNetOrT2IAdapter } from 'features/controlAdapters/store/types';
import { useMemo } from 'react';
export const useControlAdapterProcessorNode = (id: string) => { export const useControlAdapterProcessorNode = (id: string) => {
const selector = useMemo( const selector = useMemo(
() => () =>
createSelector( createMemoizedSelector(stateSelector, ({ controlAdapters }) => {
stateSelector, const ca = selectControlAdapterById(controlAdapters, id);
({ controlAdapters }) => {
const ca = selectControlAdapterById(controlAdapters, id);
return ca && isControlNetOrT2IAdapter(ca) return ca && isControlNetOrT2IAdapter(ca)
? ca.processorNode ? ca.processorNode
: undefined; : undefined;
}, }),
defaultSelectorOptions
),
[id] [id]
); );

View File

@ -1,25 +1,20 @@
import { createSelector } from '@reduxjs/toolkit'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store'; import { stateSelector } from 'app/store/store';
import { useMemo } from 'react';
import { selectControlAdapterById } from 'features/controlAdapters/store/controlAdaptersSlice';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; import { selectControlAdapterById } from 'features/controlAdapters/store/controlAdaptersSlice';
import { isControlNetOrT2IAdapter } from 'features/controlAdapters/store/types'; import { isControlNetOrT2IAdapter } from 'features/controlAdapters/store/types';
import { useMemo } from 'react';
export const useControlAdapterProcessorType = (id: string) => { export const useControlAdapterProcessorType = (id: string) => {
const selector = useMemo( const selector = useMemo(
() => () =>
createSelector( createMemoizedSelector(stateSelector, ({ controlAdapters }) => {
stateSelector, const ca = selectControlAdapterById(controlAdapters, id);
({ controlAdapters }) => {
const ca = selectControlAdapterById(controlAdapters, id);
return ca && isControlNetOrT2IAdapter(ca) return ca && isControlNetOrT2IAdapter(ca)
? ca.processorType ? ca.processorType
: undefined; : undefined;
}, }),
defaultSelectorOptions
),
[id] [id]
); );

View File

@ -1,25 +1,20 @@
import { createSelector } from '@reduxjs/toolkit'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store'; import { stateSelector } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import { useMemo } from 'react';
import { selectControlAdapterById } from 'features/controlAdapters/store/controlAdaptersSlice'; import { selectControlAdapterById } from 'features/controlAdapters/store/controlAdaptersSlice';
import { isControlNetOrT2IAdapter } from 'features/controlAdapters/store/types'; import { isControlNetOrT2IAdapter } from 'features/controlAdapters/store/types';
import { useMemo } from 'react';
export const useControlAdapterResizeMode = (id: string) => { export const useControlAdapterResizeMode = (id: string) => {
const selector = useMemo( const selector = useMemo(
() => () =>
createSelector( createMemoizedSelector(stateSelector, ({ controlAdapters }) => {
stateSelector, const ca = selectControlAdapterById(controlAdapters, id);
({ controlAdapters }) => { if (ca && isControlNetOrT2IAdapter(ca)) {
const ca = selectControlAdapterById(controlAdapters, id); return ca.resizeMode;
if (ca && isControlNetOrT2IAdapter(ca)) { }
return ca.resizeMode; return undefined;
} }),
return undefined;
},
defaultSelectorOptions
),
[id] [id]
); );

View File

@ -1,25 +1,20 @@
import { createSelector } from '@reduxjs/toolkit'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store'; import { stateSelector } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import { useMemo } from 'react';
import { selectControlAdapterById } from 'features/controlAdapters/store/controlAdaptersSlice'; import { selectControlAdapterById } from 'features/controlAdapters/store/controlAdaptersSlice';
import { isControlNetOrT2IAdapter } from 'features/controlAdapters/store/types'; import { isControlNetOrT2IAdapter } from 'features/controlAdapters/store/types';
import { useMemo } from 'react';
export const useControlAdapterShouldAutoConfig = (id: string) => { export const useControlAdapterShouldAutoConfig = (id: string) => {
const selector = useMemo( const selector = useMemo(
() => () =>
createSelector( createMemoizedSelector(stateSelector, ({ controlAdapters }) => {
stateSelector, const ca = selectControlAdapterById(controlAdapters, id);
({ controlAdapters }) => { if (ca && isControlNetOrT2IAdapter(ca)) {
const ca = selectControlAdapterById(controlAdapters, id); return ca.shouldAutoConfig;
if (ca && isControlNetOrT2IAdapter(ca)) { }
return ca.shouldAutoConfig; return undefined;
} }),
return undefined;
},
defaultSelectorOptions
),
[id] [id]
); );

View File

@ -1,18 +1,16 @@
import { createSelector } from '@reduxjs/toolkit'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store'; import { stateSelector } from 'app/store/store';
import { useMemo } from 'react';
import { selectControlAdapterById } from 'features/controlAdapters/store/controlAdaptersSlice';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; import { selectControlAdapterById } from 'features/controlAdapters/store/controlAdaptersSlice';
import { useMemo } from 'react';
export const useControlAdapterType = (id: string) => { export const useControlAdapterType = (id: string) => {
const selector = useMemo( const selector = useMemo(
() => () =>
createSelector( createMemoizedSelector(
stateSelector, stateSelector,
({ controlAdapters }) => ({ controlAdapters }) =>
selectControlAdapterById(controlAdapters, id)?.type, selectControlAdapterById(controlAdapters, id)?.type
defaultSelectorOptions
), ),
[id] [id]
); );

View File

@ -1,18 +1,16 @@
import { createSelector } from '@reduxjs/toolkit'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store'; import { stateSelector } from 'app/store/store';
import { useMemo } from 'react';
import { selectControlAdapterById } from 'features/controlAdapters/store/controlAdaptersSlice';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; import { selectControlAdapterById } from 'features/controlAdapters/store/controlAdaptersSlice';
import { useMemo } from 'react';
export const useControlAdapterWeight = (id: string) => { export const useControlAdapterWeight = (id: string) => {
const selector = useMemo( const selector = useMemo(
() => () =>
createSelector( createMemoizedSelector(
stateSelector, stateSelector,
({ controlAdapters }) => ({ controlAdapters }) =>
selectControlAdapterById(controlAdapters, id)?.weight, selectControlAdapterById(controlAdapters, id)?.weight
defaultSelectorOptions
), ),
[id] [id]
); );

View File

@ -35,7 +35,9 @@ import {
isT2IAdapter, isT2IAdapter,
} from './types'; } from './types';
export const caAdapter = createEntityAdapter<ControlAdapterConfig>(); export const caAdapter = createEntityAdapter<ControlAdapterConfig, string>({
selectId: (ca) => ca.id,
});
export const { export const {
selectById: selectControlAdapterById, selectById: selectControlAdapterById,
@ -259,7 +261,7 @@ export const controlAdaptersSlice = createSlice({
return; return;
} }
const update: Update<ControlNetConfig | T2IAdapterConfig> = { const update: Update<ControlNetConfig | T2IAdapterConfig, string> = {
id, id,
changes: { model }, changes: { model },
}; };
@ -398,7 +400,7 @@ export const controlAdaptersSlice = createSlice({
return; return;
} }
const update: Update<ControlNetConfig | T2IAdapterConfig> = { const update: Update<ControlNetConfig | T2IAdapterConfig, string> = {
id, id,
changes: { shouldAutoConfig: !cn.shouldAutoConfig }, changes: { shouldAutoConfig: !cn.shouldAutoConfig },
}; };

View File

@ -425,7 +425,7 @@ export type ControlAdapterConfig =
export type ControlAdapterType = ControlAdapterConfig['type']; export type ControlAdapterType = ControlAdapterConfig['type'];
export type ControlAdaptersState = EntityState<ControlAdapterConfig> & { export type ControlAdaptersState = EntityState<ControlAdapterConfig, string> & {
pendingControlImages: string[]; pendingControlImages: string[];
}; };

View File

@ -9,16 +9,11 @@ import {
Flex, Flex,
Text, Text,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store'; import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import IAIButton from 'common/components/IAIButton'; import IAIButton from 'common/components/IAIButton';
import IAISwitch from 'common/components/IAISwitch'; import IAISwitch from 'common/components/IAISwitch';
import { setShouldConfirmOnDelete } from 'features/system/store/systemSlice';
import { some } from 'lodash-es';
import { ChangeEvent, memo, useCallback, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import { imageDeletionConfirmed } from 'features/deleteImageModal/store/actions'; import { imageDeletionConfirmed } from 'features/deleteImageModal/store/actions';
import { import {
getImageUsage, getImageUsage,
@ -29,9 +24,13 @@ import {
isModalOpenChanged, isModalOpenChanged,
} from 'features/deleteImageModal/store/slice'; } from 'features/deleteImageModal/store/slice';
import { ImageUsage } from 'features/deleteImageModal/store/types'; import { ImageUsage } from 'features/deleteImageModal/store/types';
import { setShouldConfirmOnDelete } from 'features/system/store/systemSlice';
import { some } from 'lodash-es';
import { ChangeEvent, memo, useCallback, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import ImageUsageMessage from './ImageUsageMessage'; import ImageUsageMessage from './ImageUsageMessage';
const selector = createSelector( const selector = createMemoizedSelector(
[stateSelector, selectImageUsage], [stateSelector, selectImageUsage],
(state, imagesUsage) => { (state, imagesUsage) => {
const { system, config, deleteImageModal } = state; const { system, config, deleteImageModal } = state;
@ -58,8 +57,7 @@ const selector = createSelector(
isModalOpen, isModalOpen,
imageUsageSummary, imageUsageSummary,
}; };
}, }
defaultSelectorOptions
); );
const DeleteImageModal = () => { const DeleteImageModal = () => {

View File

@ -1,12 +1,11 @@
import { createSelector } from '@reduxjs/toolkit'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { RootState } from 'app/store/store'; import { RootState } from 'app/store/store';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import { isInvocationNode } from 'features/nodes/types/invocation';
import { some } from 'lodash-es';
import { ImageUsage } from './types';
import { selectControlAdapterAll } from 'features/controlAdapters/store/controlAdaptersSlice'; import { selectControlAdapterAll } from 'features/controlAdapters/store/controlAdaptersSlice';
import { isControlNetOrT2IAdapter } from 'features/controlAdapters/store/types'; import { isControlNetOrT2IAdapter } from 'features/controlAdapters/store/types';
import { isImageFieldInputInstance } from 'features/nodes/types/field'; import { isImageFieldInputInstance } from 'features/nodes/types/field';
import { isInvocationNode } from 'features/nodes/types/invocation';
import { some } from 'lodash-es';
import { ImageUsage } from './types';
export const getImageUsage = (state: RootState, image_name: string) => { export const getImageUsage = (state: RootState, image_name: string) => {
const { generation, canvas, nodes, controlAdapters } = state; const { generation, canvas, nodes, controlAdapters } = state;
@ -41,7 +40,7 @@ export const getImageUsage = (state: RootState, image_name: string) => {
return imageUsage; return imageUsage;
}; };
export const selectImageUsage = createSelector( export const selectImageUsage = createMemoizedSelector(
[(state: RootState) => state], [(state: RootState) => state],
(state) => { (state) => {
const { imagesToDelete } = state.deleteImageModal; const { imagesToDelete } = state.deleteImageModal;
@ -55,6 +54,5 @@ export const selectImageUsage = createSelector(
); );
return imagesUsage; return imagesUsage;
}, }
defaultSelectorOptions
); );

View File

@ -1,12 +1,12 @@
import type { Modifier } from '@dnd-kit/core'; import type { Modifier } from '@dnd-kit/core';
import { getEventCoordinates } from '@dnd-kit/utilities'; import { getEventCoordinates } from '@dnd-kit/utilities';
import { createSelector } from '@reduxjs/toolkit'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store'; import { stateSelector } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { activeTabNameSelector } from 'features/ui/store/uiSelectors'; import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
import { useCallback } from 'react'; import { useCallback } from 'react';
const selectZoom = createSelector( const selectZoom = createMemoizedSelector(
[stateSelector, activeTabNameSelector], [stateSelector, activeTabNameSelector],
({ nodes }, activeTabName) => ({ nodes }, activeTabName) =>
activeTabName === 'nodes' ? nodes.viewport.zoom : 1 activeTabName === 'nodes' ? nodes.viewport.zoom : 1

View File

@ -1,11 +1,11 @@
import { Flex } from '@chakra-ui/react'; import { Flex } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store'; import { stateSelector } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import IAICollapse from 'common/components/IAICollapse'; import IAICollapse from 'common/components/IAICollapse';
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
import { memo, useMemo } from 'react'; import { memo, useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
import ParamDynamicPromptsMaxPrompts from './ParamDynamicPromptsMaxPrompts'; import ParamDynamicPromptsMaxPrompts from './ParamDynamicPromptsMaxPrompts';
import ParamDynamicPromptsPreview from './ParamDynamicPromptsPreview'; import ParamDynamicPromptsPreview from './ParamDynamicPromptsPreview';
import ParamDynamicPromptsSeedBehaviour from './ParamDynamicPromptsSeedBehaviour'; import ParamDynamicPromptsSeedBehaviour from './ParamDynamicPromptsSeedBehaviour';
@ -14,7 +14,7 @@ const ParamDynamicPromptsCollapse = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const selectActiveLabel = useMemo( const selectActiveLabel = useMemo(
() => () =>
createSelector(stateSelector, ({ dynamicPrompts }) => { createMemoizedSelector(stateSelector, ({ dynamicPrompts }) => {
const count = dynamicPrompts.prompts.length; const count = dynamicPrompts.prompts.length;
if (count > 1) { if (count > 1) {
return t('dynamicPrompts.promptsWithCount_other', { return t('dynamicPrompts.promptsWithCount_other', {

View File

@ -1,21 +1,16 @@
import { createSelector } from '@reduxjs/toolkit'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store'; import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import IAISwitch from 'common/components/IAISwitch'; import IAISwitch from 'common/components/IAISwitch';
import { combinatorialToggled } from 'features/dynamicPrompts/store/dynamicPromptsSlice';
import { memo, useCallback } from 'react'; import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { combinatorialToggled } from 'features/dynamicPrompts/store/dynamicPromptsSlice';
const selector = createSelector( const selector = createMemoizedSelector(stateSelector, (state) => {
stateSelector, const { combinatorial } = state.dynamicPrompts;
(state) => {
const { combinatorial } = state.dynamicPrompts;
return { combinatorial }; return { combinatorial };
}, });
defaultSelectorOptions
);
const ParamDynamicPromptsCombinatorial = () => { const ParamDynamicPromptsCombinatorial = () => {
const { combinatorial } = useAppSelector(selector); const { combinatorial } = useAppSelector(selector);

View File

@ -1,33 +1,28 @@
import { createSelector } from '@reduxjs/toolkit'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store'; import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; import IAIInformationalPopover from 'common/components/IAIInformationalPopover/IAIInformationalPopover';
import IAISlider from 'common/components/IAISlider'; import IAISlider from 'common/components/IAISlider';
import { memo, useCallback } from 'react';
import { import {
maxPromptsChanged, maxPromptsChanged,
maxPromptsReset, maxPromptsReset,
} from 'features/dynamicPrompts/store/dynamicPromptsSlice'; } from 'features/dynamicPrompts/store/dynamicPromptsSlice';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import IAIInformationalPopover from 'common/components/IAIInformationalPopover/IAIInformationalPopover';
const selector = createSelector( const selector = createMemoizedSelector(stateSelector, (state) => {
stateSelector, const { maxPrompts, combinatorial } = state.dynamicPrompts;
(state) => { const { min, sliderMax, inputMax } =
const { maxPrompts, combinatorial } = state.dynamicPrompts; state.config.sd.dynamicPrompts.maxPrompts;
const { min, sliderMax, inputMax } =
state.config.sd.dynamicPrompts.maxPrompts;
return { return {
maxPrompts, maxPrompts,
min, min,
sliderMax, sliderMax,
inputMax, inputMax,
isDisabled: !combinatorial, isDisabled: !combinatorial,
}; };
}, });
defaultSelectorOptions
);
const ParamDynamicPromptsMaxPrompts = () => { const ParamDynamicPromptsMaxPrompts = () => {
const { maxPrompts, min, sliderMax, inputMax, isDisabled } = const { maxPrompts, min, sliderMax, inputMax, isDisabled } =

View File

@ -8,31 +8,26 @@ import {
Spinner, Spinner,
Text, Text,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store'; import { stateSelector } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import { IAINoContentFallback } from 'common/components/IAIImageFallback'; import { IAINoContentFallback } from 'common/components/IAIImageFallback';
import IAIInformationalPopover from 'common/components/IAIInformationalPopover/IAIInformationalPopover'; import IAIInformationalPopover from 'common/components/IAIInformationalPopover/IAIInformationalPopover';
import ScrollableContent from 'features/nodes/components/sidePanel/ScrollableContent'; import ScrollableContent from 'features/nodes/components/sidePanel/ScrollableContent';
import { memo } from 'react'; import { memo } from 'react';
import { FaCircleExclamation } from 'react-icons/fa6';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { FaCircleExclamation } from 'react-icons/fa6';
const selector = createSelector( const selector = createMemoizedSelector(stateSelector, (state) => {
stateSelector, const { isLoading, isError, prompts, parsingError } = state.dynamicPrompts;
(state) => {
const { isLoading, isError, prompts, parsingError } = state.dynamicPrompts;
return { return {
prompts, prompts,
parsingError, parsingError,
isError, isError,
isLoading, isLoading,
}; };
}, });
defaultSelectorOptions
);
const listItemStyles: ChakraProps['sx'] = { const listItemStyles: ChakraProps['sx'] = {
'&::marker': { color: 'base.500', _dark: { color: 'base.500' } }, '&::marker': { color: 'base.500', _dark: { color: 'base.500' } },

View File

@ -1,8 +1,7 @@
import { SelectItem } from '@mantine/core'; import { SelectItem } from '@mantine/core';
import { createSelector } from '@reduxjs/toolkit'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store'; import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import IAIMantineSearchableSelect from 'common/components/IAIMantineSearchableSelect'; import IAIMantineSearchableSelect from 'common/components/IAIMantineSearchableSelect';
import IAIMantineSelectItemWithTooltip from 'common/components/IAIMantineSelectItemWithTooltip'; import IAIMantineSelectItemWithTooltip from 'common/components/IAIMantineSelectItemWithTooltip';
import { autoAddBoardIdChanged } from 'features/gallery/store/gallerySlice'; import { autoAddBoardIdChanged } from 'features/gallery/store/gallerySlice';
@ -10,18 +9,14 @@ import { memo, useCallback, useRef } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useListAllBoardsQuery } from 'services/api/endpoints/boards'; import { useListAllBoardsQuery } from 'services/api/endpoints/boards';
const selector = createSelector( const selector = createMemoizedSelector([stateSelector], ({ gallery }) => {
[stateSelector], const { autoAddBoardId, autoAssignBoardOnClick } = gallery;
({ gallery }) => {
const { autoAddBoardId, autoAssignBoardOnClick } = gallery;
return { return {
autoAddBoardId, autoAddBoardId,
autoAssignBoardOnClick, autoAssignBoardOnClick,
}; };
}, });
defaultSelectorOptions
);
const BoardAutoAddSelect = () => { const BoardAutoAddSelect = () => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();

View File

@ -1,25 +1,24 @@
import { MenuGroup, MenuItem, MenuList } from '@chakra-ui/react'; import { MenuGroup, MenuItem, MenuList } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store'; import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import { import {
IAIContextMenu, IAIContextMenu,
IAIContextMenuProps, IAIContextMenuProps,
} from 'common/components/IAIContextMenu'; } from 'common/components/IAIContextMenu';
import { autoAddBoardIdChanged } from 'features/gallery/store/gallerySlice'; import { autoAddBoardIdChanged } from 'features/gallery/store/gallerySlice';
import { BoardId } from 'features/gallery/store/types'; import { BoardId } from 'features/gallery/store/types';
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
import { addToast } from 'features/system/store/systemSlice';
import { MouseEvent, memo, useCallback, useMemo } from 'react'; import { MouseEvent, memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { FaDownload, FaPlus } from 'react-icons/fa'; import { FaDownload, FaPlus } from 'react-icons/fa';
import { useBulkDownloadImagesMutation } from 'services/api/endpoints/images';
import { useBoardName } from 'services/api/hooks/useBoardName'; import { useBoardName } from 'services/api/hooks/useBoardName';
import { BoardDTO } from 'services/api/types'; import { BoardDTO } from 'services/api/types';
import { menuListMotionProps } from 'theme/components/menu'; import { menuListMotionProps } from 'theme/components/menu';
import GalleryBoardContextMenuItems from './GalleryBoardContextMenuItems'; import GalleryBoardContextMenuItems from './GalleryBoardContextMenuItems';
import NoBoardContextMenuItems from './NoBoardContextMenuItems'; import NoBoardContextMenuItems from './NoBoardContextMenuItems';
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
import { useBulkDownloadImagesMutation } from 'services/api/endpoints/images';
import { addToast } from 'features/system/store/systemSlice';
type Props = { type Props = {
board?: BoardDTO; board?: BoardDTO;
@ -39,15 +38,11 @@ const BoardContextMenu = ({
const selector = useMemo( const selector = useMemo(
() => () =>
createSelector( createMemoizedSelector(stateSelector, ({ gallery }) => {
stateSelector, const isAutoAdd = gallery.autoAddBoardId === board_id;
({ gallery }) => { const autoAssignBoardOnClick = gallery.autoAssignBoardOnClick;
const isAutoAdd = gallery.autoAddBoardId === board_id; return { isAutoAdd, autoAssignBoardOnClick };
const autoAssignBoardOnClick = gallery.autoAssignBoardOnClick; }),
return { isAutoAdd, autoAssignBoardOnClick };
},
defaultSelectorOptions
),
[board_id] [board_id]
); );

View File

@ -1,26 +1,21 @@
import { Collapse, Flex, Grid, GridItem } from '@chakra-ui/react'; import { Collapse, Flex, Grid, GridItem } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store'; import { stateSelector } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; import DeleteBoardModal from 'features/gallery/components/Boards/DeleteBoardModal';
import { OverlayScrollbarsComponent } from 'overlayscrollbars-react'; import { OverlayScrollbarsComponent } from 'overlayscrollbars-react';
import { memo, useState } from 'react'; import { memo, useState } from 'react';
import { useListAllBoardsQuery } from 'services/api/endpoints/boards'; import { useListAllBoardsQuery } from 'services/api/endpoints/boards';
import { BoardDTO } from 'services/api/types'; import { BoardDTO } from 'services/api/types';
import DeleteBoardModal from 'features/gallery/components/Boards/DeleteBoardModal';
import AddBoardButton from './AddBoardButton'; import AddBoardButton from './AddBoardButton';
import BoardsSearch from './BoardsSearch'; import BoardsSearch from './BoardsSearch';
import GalleryBoard from './GalleryBoard'; import GalleryBoard from './GalleryBoard';
import NoBoardBoard from './NoBoardBoard'; import NoBoardBoard from './NoBoardBoard';
const selector = createSelector( const selector = createMemoizedSelector([stateSelector], ({ gallery }) => {
[stateSelector], const { selectedBoardId, boardSearchText } = gallery;
({ gallery }) => { return { selectedBoardId, boardSearchText };
const { selectedBoardId, boardSearchText } = gallery; });
return { selectedBoardId, boardSearchText };
},
defaultSelectorOptions
);
type Props = { type Props = {
isOpen: boolean; isOpen: boolean;

View File

@ -5,10 +5,9 @@ import {
InputGroup, InputGroup,
InputRightElement, InputRightElement,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store'; import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import { boardSearchTextChanged } from 'features/gallery/store/gallerySlice'; import { boardSearchTextChanged } from 'features/gallery/store/gallerySlice';
import { import {
ChangeEvent, ChangeEvent,
@ -20,14 +19,10 @@ import {
} from 'react'; } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
const selector = createSelector( const selector = createMemoizedSelector([stateSelector], ({ gallery }) => {
[stateSelector], const { boardSearchText } = gallery;
({ gallery }) => { return { boardSearchText };
const { boardSearchText } = gallery; });
return { boardSearchText };
},
defaultSelectorOptions
);
const BoardsSearch = () => { const BoardsSearch = () => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();

View File

@ -9,19 +9,21 @@ import {
Text, Text,
Tooltip, Tooltip,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit'; import { skipToken } from '@reduxjs/toolkit/query';
import { skipToken } from '@reduxjs/toolkit/dist/query'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store'; import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import IAIDroppable from 'common/components/IAIDroppable'; import IAIDroppable from 'common/components/IAIDroppable';
import SelectionOverlay from 'common/components/SelectionOverlay'; import SelectionOverlay from 'common/components/SelectionOverlay';
import { AddToBoardDropData } from 'features/dnd/types'; import { AddToBoardDropData } from 'features/dnd/types';
import AutoAddIcon from 'features/gallery/components/Boards/AutoAddIcon';
import BoardContextMenu from 'features/gallery/components/Boards/BoardContextMenu';
import { import {
autoAddBoardIdChanged, autoAddBoardIdChanged,
boardIdSelected, boardIdSelected,
} from 'features/gallery/store/gallerySlice'; } from 'features/gallery/store/gallerySlice';
import { memo, useCallback, useMemo, useState } from 'react'; import { memo, useCallback, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { FaUser } from 'react-icons/fa'; import { FaUser } from 'react-icons/fa';
import { import {
useGetBoardAssetsTotalQuery, useGetBoardAssetsTotalQuery,
@ -30,9 +32,6 @@ import {
} from 'services/api/endpoints/boards'; } from 'services/api/endpoints/boards';
import { useGetImageDTOQuery } from 'services/api/endpoints/images'; import { useGetImageDTOQuery } from 'services/api/endpoints/images';
import { BoardDTO } from 'services/api/types'; import { BoardDTO } from 'services/api/types';
import AutoAddIcon from 'features/gallery/components/Boards/AutoAddIcon';
import BoardContextMenu from 'features/gallery/components/Boards/BoardContextMenu';
import { useTranslation } from 'react-i18next';
interface GalleryBoardProps { interface GalleryBoardProps {
board: BoardDTO; board: BoardDTO;
@ -48,20 +47,15 @@ const GalleryBoard = ({
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const selector = useMemo( const selector = useMemo(
() => () =>
createSelector( createMemoizedSelector(stateSelector, ({ gallery }) => {
stateSelector, const isSelectedForAutoAdd = board.board_id === gallery.autoAddBoardId;
({ gallery }) => { const autoAssignBoardOnClick = gallery.autoAssignBoardOnClick;
const isSelectedForAutoAdd =
board.board_id === gallery.autoAddBoardId;
const autoAssignBoardOnClick = gallery.autoAssignBoardOnClick;
return { return {
isSelectedForAutoAdd, isSelectedForAutoAdd,
autoAssignBoardOnClick, autoAssignBoardOnClick,
}; };
}, }),
defaultSelectorOptions
),
[board.board_id] [board.board_id]
); );

View File

@ -1,38 +1,33 @@
import { Box, Flex, Image, Text, Tooltip } from '@chakra-ui/react'; import { Box, Flex, Image, Text, Tooltip } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store'; import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import InvokeAILogoImage from 'assets/images/logo.png'; import InvokeAILogoImage from 'assets/images/logo.png';
import IAIDroppable from 'common/components/IAIDroppable'; import IAIDroppable from 'common/components/IAIDroppable';
import SelectionOverlay from 'common/components/SelectionOverlay'; import SelectionOverlay from 'common/components/SelectionOverlay';
import { RemoveFromBoardDropData } from 'features/dnd/types'; import { RemoveFromBoardDropData } from 'features/dnd/types';
import AutoAddIcon from 'features/gallery/components/Boards/AutoAddIcon';
import BoardContextMenu from 'features/gallery/components/Boards/BoardContextMenu';
import { import {
autoAddBoardIdChanged, autoAddBoardIdChanged,
boardIdSelected, boardIdSelected,
} from 'features/gallery/store/gallerySlice'; } from 'features/gallery/store/gallerySlice';
import { memo, useCallback, useMemo, useState } from 'react'; import { memo, useCallback, useMemo, useState } from 'react';
import { useBoardName } from 'services/api/hooks/useBoardName'; import { useTranslation } from 'react-i18next';
import AutoAddIcon from 'features/gallery/components/Boards/AutoAddIcon';
import BoardContextMenu from 'features/gallery/components/Boards/BoardContextMenu';
import { import {
useGetBoardAssetsTotalQuery, useGetBoardAssetsTotalQuery,
useGetBoardImagesTotalQuery, useGetBoardImagesTotalQuery,
} from 'services/api/endpoints/boards'; } from 'services/api/endpoints/boards';
import { useTranslation } from 'react-i18next'; import { useBoardName } from 'services/api/hooks/useBoardName';
interface Props { interface Props {
isSelected: boolean; isSelected: boolean;
} }
const selector = createSelector( const selector = createMemoizedSelector(stateSelector, ({ gallery }) => {
stateSelector, const { autoAddBoardId, autoAssignBoardOnClick } = gallery;
({ gallery }) => { return { autoAddBoardId, autoAssignBoardOnClick };
const { autoAddBoardId, autoAssignBoardOnClick } = gallery; });
return { autoAddBoardId, autoAssignBoardOnClick };
},
defaultSelectorOptions
);
const NoBoardBoard = memo(({ isSelected }: Props) => { const NoBoardBoard = memo(({ isSelected }: Props) => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();

View File

@ -9,8 +9,8 @@ import {
Skeleton, Skeleton,
Text, Text,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit'; import { skipToken } from '@reduxjs/toolkit/query';
import { skipToken } from '@reduxjs/toolkit/dist/query'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store'; import { stateSelector } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import IAIButton from 'common/components/IAIButton'; import IAIButton from 'common/components/IAIButton';
@ -43,7 +43,7 @@ const DeleteBoardModal = (props: Props) => {
const selectImageUsageSummary = useMemo( const selectImageUsageSummary = useMemo(
() => () =>
createSelector([stateSelector], (state) => { createMemoizedSelector([stateSelector], (state) => {
const allImageUsage = (boardImageNames ?? []).map((imageName) => const allImageUsage = (boardImageNames ?? []).map((imageName) =>
getImageUsage(state, imageName) getImageUsage(state, imageName)
); );

View File

@ -1,6 +1,3 @@
import { createSelector } from '@reduxjs/toolkit';
import { isEqual } from 'lodash-es';
import { import {
ButtonGroup, ButtonGroup,
Flex, Flex,
@ -8,8 +5,9 @@ import {
MenuButton, MenuButton,
MenuList, MenuList,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { skipToken } from '@reduxjs/toolkit/dist/query'; import { skipToken } from '@reduxjs/toolkit/query';
import { useAppToaster } from 'app/components/Toaster'; import { useAppToaster } from 'app/components/Toaster';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { upscaleRequested } from 'app/store/middleware/listenerMiddleware/listeners/upscaleRequested'; import { upscaleRequested } from 'app/store/middleware/listenerMiddleware/listeners/upscaleRequested';
import { stateSelector } from 'app/store/store'; import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
@ -45,7 +43,7 @@ import { useGetImageDTOQuery } from 'services/api/endpoints/images';
import { useDebouncedMetadata } from 'services/api/hooks/useDebouncedMetadata'; import { useDebouncedMetadata } from 'services/api/hooks/useDebouncedMetadata';
import { menuListMotionProps } from 'theme/components/menu'; import { menuListMotionProps } from 'theme/components/menu';
const currentImageButtonsSelector = createSelector( const currentImageButtonsSelector = createMemoizedSelector(
[stateSelector, activeTabNameSelector], [stateSelector, activeTabNameSelector],
({ gallery, system, ui, config }, activeTabName) => { ({ gallery, system, ui, config }, activeTabName) => {
const { isConnected, shouldConfirmOnDelete, denoiseProgress } = system; const { isConnected, shouldConfirmOnDelete, denoiseProgress } = system;
@ -72,11 +70,6 @@ const currentImageButtonsSelector = createSelector(
lastSelectedImage, lastSelectedImage,
shouldFetchMetadataFromApi, shouldFetchMetadataFromApi,
}; };
},
{
memoizeOptions: {
resultEqualityCheck: isEqual,
},
} }
); );

View File

@ -1,5 +1,4 @@
import { Flex } from '@chakra-ui/react'; import { Flex } from '@chakra-ui/react';
import CurrentImageButtons from './CurrentImageButtons'; import CurrentImageButtons from './CurrentImageButtons';
import CurrentImagePreview from './CurrentImagePreview'; import CurrentImagePreview from './CurrentImagePreview';
import { memo } from 'react'; import { memo } from 'react';

View File

@ -1,6 +1,6 @@
import { Box, Flex, Image } from '@chakra-ui/react'; import { Box, Flex, Image } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit'; import { skipToken } from '@reduxjs/toolkit/query';
import { skipToken } from '@reduxjs/toolkit/dist/query'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store'; import { stateSelector } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import IAIDndImage from 'common/components/IAIDndImage'; import IAIDndImage from 'common/components/IAIDndImage';
@ -9,19 +9,18 @@ import {
TypesafeDraggableData, TypesafeDraggableData,
TypesafeDroppableData, TypesafeDroppableData,
} from 'features/dnd/types'; } from 'features/dnd/types';
import ImageMetadataViewer from 'features/gallery/components/ImageMetadataViewer/ImageMetadataViewer';
import NextPrevImageButtons from 'features/gallery/components/NextPrevImageButtons';
import { useNextPrevImage } from 'features/gallery/hooks/useNextPrevImage'; import { useNextPrevImage } from 'features/gallery/hooks/useNextPrevImage';
import { selectLastSelectedImage } from 'features/gallery/store/gallerySelectors'; import { selectLastSelectedImage } from 'features/gallery/store/gallerySelectors';
import { AnimatePresence, motion } from 'framer-motion'; import { AnimatePresence, motion } from 'framer-motion';
import { isEqual } from 'lodash-es';
import { memo, useCallback, useMemo, useRef, useState } from 'react'; import { memo, useCallback, useMemo, useRef, useState } from 'react';
import { useHotkeys } from 'react-hotkeys-hook'; import { useHotkeys } from 'react-hotkeys-hook';
import { useTranslation } from 'react-i18next';
import { FaImage } from 'react-icons/fa'; import { FaImage } from 'react-icons/fa';
import { useGetImageDTOQuery } from 'services/api/endpoints/images'; import { useGetImageDTOQuery } from 'services/api/endpoints/images';
import ImageMetadataViewer from 'features/gallery/components/ImageMetadataViewer/ImageMetadataViewer';
import NextPrevImageButtons from 'features/gallery/components/NextPrevImageButtons';
import { useTranslation } from 'react-i18next';
export const imagesSelector = createSelector( export const imagesSelector = createMemoizedSelector(
[stateSelector, selectLastSelectedImage], [stateSelector, selectLastSelectedImage],
({ ui, system }, lastSelectedImage) => { ({ ui, system }, lastSelectedImage) => {
const { const {
@ -38,11 +37,6 @@ export const imagesSelector = createSelector(
shouldShowProgressInViewer, shouldShowProgressInViewer,
shouldAntialiasProgressImage, shouldAntialiasProgressImage,
}; };
},
{
memoizeOptions: {
resultEqualityCheck: isEqual,
},
} }
); );

View File

@ -1,21 +1,16 @@
import { ChevronUpIcon } from '@chakra-ui/icons'; import { ChevronUpIcon } from '@chakra-ui/icons';
import { Button, Flex, Text } from '@chakra-ui/react'; import { Button, Flex, Text } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store'; import { stateSelector } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import { memo, useMemo } from 'react'; import { memo, useMemo } from 'react';
import { useBoardName } from 'services/api/hooks/useBoardName'; import { useBoardName } from 'services/api/hooks/useBoardName';
const selector = createSelector( const selector = createMemoizedSelector([stateSelector], (state) => {
[stateSelector], const { selectedBoardId } = state.gallery;
(state) => {
const { selectedBoardId } = state.gallery;
return { selectedBoardId }; return { selectedBoardId };
}, });
defaultSelectorOptions
);
type Props = { type Props = {
isOpen: boolean; isOpen: boolean;

View File

@ -1,10 +1,10 @@
import { Flex } from '@chakra-ui/react'; import { Flex } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store'; import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import IAIIconButton from 'common/components/IAIIconButton'; import IAIIconButton from 'common/components/IAIIconButton';
import IAIPopover from 'common/components/IAIPopover'; import IAIPopover from 'common/components/IAIPopover';
import IAISimpleCheckbox from 'common/components/IAISimpleCheckbox';
import IAISlider from 'common/components/IAISlider'; import IAISlider from 'common/components/IAISlider';
import IAISwitch from 'common/components/IAISwitch'; import IAISwitch from 'common/components/IAISwitch';
import { import {
@ -16,25 +16,17 @@ import { ChangeEvent, memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { FaWrench } from 'react-icons/fa'; import { FaWrench } from 'react-icons/fa';
import BoardAutoAddSelect from './Boards/BoardAutoAddSelect'; import BoardAutoAddSelect from './Boards/BoardAutoAddSelect';
import IAISimpleCheckbox from 'common/components/IAISimpleCheckbox';
const selector = createSelector( const selector = createMemoizedSelector([stateSelector], (state) => {
[stateSelector], const { galleryImageMinimumWidth, shouldAutoSwitch, autoAssignBoardOnClick } =
(state) => { state.gallery;
const {
galleryImageMinimumWidth,
shouldAutoSwitch,
autoAssignBoardOnClick,
} = state.gallery;
return { return {
galleryImageMinimumWidth, galleryImageMinimumWidth,
shouldAutoSwitch, shouldAutoSwitch,
autoAssignBoardOnClick, autoAssignBoardOnClick,
}; };
}, });
defaultSelectorOptions
);
const GallerySettingsPopover = () => { const GallerySettingsPopover = () => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();

View File

@ -1,4 +1,7 @@
import { MenuList } from '@chakra-ui/react'; import { MenuList } from '@chakra-ui/react';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks';
import { import {
IAIContextMenu, IAIContextMenu,
IAIContextMenuProps, IAIContextMenuProps,
@ -6,27 +9,19 @@ import {
import { MouseEvent, memo, useCallback } from 'react'; import { MouseEvent, memo, useCallback } from 'react';
import { ImageDTO } from 'services/api/types'; import { ImageDTO } from 'services/api/types';
import { menuListMotionProps } from 'theme/components/menu'; import { menuListMotionProps } from 'theme/components/menu';
import SingleSelectionMenuItems from './SingleSelectionMenuItems';
import { createSelector } from '@reduxjs/toolkit';
import { stateSelector } from 'app/store/store';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import { useAppSelector } from 'app/store/storeHooks';
import MultipleSelectionMenuItems from './MultipleSelectionMenuItems'; import MultipleSelectionMenuItems from './MultipleSelectionMenuItems';
import SingleSelectionMenuItems from './SingleSelectionMenuItems';
type Props = { type Props = {
imageDTO: ImageDTO | undefined; imageDTO: ImageDTO | undefined;
children: IAIContextMenuProps<HTMLDivElement>['children']; children: IAIContextMenuProps<HTMLDivElement>['children'];
}; };
const selector = createSelector( const selector = createMemoizedSelector([stateSelector], ({ gallery }) => {
[stateSelector], const selectionCount = gallery.selection.length;
({ gallery }) => {
const selectionCount = gallery.selection.length;
return { selectionCount }; return { selectionCount };
}, });
defaultSelectorOptions
);
const ImageContextMenu = ({ imageDTO, children }: Props) => { const ImageContextMenu = ({ imageDTO, children }: Props) => {
const { selectionCount } = useAppSelector(selector); const { selectionCount } = useAppSelector(selector);

View File

@ -8,31 +8,26 @@ import {
VStack, VStack,
useDisclosure, useDisclosure,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store'; import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import IAIButton from 'common/components/IAIButton'; import IAIButton from 'common/components/IAIButton';
import { memo, useCallback, useRef } from 'react';
import { FaImages, FaServer } from 'react-icons/fa';
import { galleryViewChanged } from 'features/gallery/store/gallerySlice'; import { galleryViewChanged } from 'features/gallery/store/gallerySlice';
import { memo, useCallback, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import { FaImages, FaServer } from 'react-icons/fa';
import BoardsList from './Boards/BoardsList/BoardsList'; import BoardsList from './Boards/BoardsList/BoardsList';
import GalleryBoardName from './GalleryBoardName'; import GalleryBoardName from './GalleryBoardName';
import GallerySettingsPopover from './GallerySettingsPopover'; import GallerySettingsPopover from './GallerySettingsPopover';
import GalleryImageGrid from './ImageGrid/GalleryImageGrid'; import GalleryImageGrid from './ImageGrid/GalleryImageGrid';
import { useTranslation } from 'react-i18next';
const selector = createSelector( const selector = createMemoizedSelector([stateSelector], (state) => {
[stateSelector], const { galleryView } = state.gallery;
(state) => {
const { galleryView } = state.gallery;
return { return {
galleryView, galleryView,
}; };
}, });
defaultSelectorOptions
);
const ImageGalleryContent = () => { const ImageGalleryContent = () => {
const { t } = useTranslation(); const { t } = useTranslation();

View File

@ -1,16 +1,15 @@
import { createSelector } from '@reduxjs/toolkit'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store'; import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import { selectListImagesBaseQueryArgs } from 'features/gallery/store/gallerySelectors'; import { selectListImagesBaseQueryArgs } from 'features/gallery/store/gallerySelectors';
import { selectionChanged } from 'features/gallery/store/gallerySlice';
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
import { MouseEvent, useCallback, useMemo } from 'react'; import { MouseEvent, useCallback, useMemo } from 'react';
import { useListImagesQuery } from 'services/api/endpoints/images'; import { useListImagesQuery } from 'services/api/endpoints/images';
import { ImageDTO } from 'services/api/types'; import { ImageDTO } from 'services/api/types';
import { imagesSelectors } from 'services/api/util'; import { imagesSelectors } from 'services/api/util';
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
import { selectionChanged } from 'features/gallery/store/gallerySlice';
const selector = createSelector( const selector = createMemoizedSelector(
[stateSelector, selectListImagesBaseQueryArgs], [stateSelector, selectListImagesBaseQueryArgs],
({ gallery }, queryArgs) => { ({ gallery }, queryArgs) => {
const selection = gallery.selection; const selection = gallery.selection;
@ -19,8 +18,7 @@ const selector = createSelector(
queryArgs, queryArgs,
selection, selection,
}; };
}, }
defaultSelectorOptions
); );
export const useMultiselect = (imageDTO?: ImageDTO) => { export const useMultiselect = (imageDTO?: ImageDTO) => {

View File

@ -1,20 +1,20 @@
import { createSelector } from '@reduxjs/toolkit'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store'; import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { selectListImagesBaseQueryArgs } from 'features/gallery/store/gallerySelectors';
import { imageSelected } from 'features/gallery/store/gallerySlice'; import { imageSelected } from 'features/gallery/store/gallerySlice';
import { clamp, isEqual } from 'lodash-es'; import { IMAGE_LIMIT } from 'features/gallery/store/types';
import { clamp } from 'lodash-es';
import { useCallback } from 'react'; import { useCallback } from 'react';
import { boardsApi } from 'services/api/endpoints/boards'; import { boardsApi } from 'services/api/endpoints/boards';
import { import {
imagesApi, imagesApi,
useLazyListImagesQuery, useLazyListImagesQuery,
} from 'services/api/endpoints/images'; } from 'services/api/endpoints/images';
import { selectListImagesBaseQueryArgs } from 'features/gallery/store/gallerySelectors';
import { IMAGE_LIMIT } from 'features/gallery/store/types';
import { ListImagesArgs } from 'services/api/types'; import { ListImagesArgs } from 'services/api/types';
import { imagesAdapter } from 'services/api/util'; import { imagesAdapter } from 'services/api/util';
export const nextPrevImageButtonsSelector = createSelector( export const nextPrevImageButtonsSelector = createMemoizedSelector(
[stateSelector, selectListImagesBaseQueryArgs], [stateSelector, selectListImagesBaseQueryArgs],
(state, baseQueryArgs) => { (state, baseQueryArgs) => {
const { data, status } = const { data, status } =
@ -80,11 +80,6 @@ export const nextPrevImageButtonsSelector = createSelector(
prevImage, prevImage,
queryArgs, queryArgs,
}; };
},
{
memoizeOptions: {
resultEqualityCheck: isEqual,
},
} }
); );

View File

@ -1,6 +1,5 @@
import { createSelector } from '@reduxjs/toolkit'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { RootState } from 'app/store/store'; import { RootState } from 'app/store/store';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import { ListImagesArgs } from 'services/api/types'; import { ListImagesArgs } from 'services/api/types';
import { import {
ASSETS_CATEGORIES, ASSETS_CATEGORIES,
@ -10,13 +9,12 @@ import {
export const gallerySelector = (state: RootState) => state.gallery; export const gallerySelector = (state: RootState) => state.gallery;
export const selectLastSelectedImage = createSelector( export const selectLastSelectedImage = createMemoizedSelector(
(state: RootState) => state, (state: RootState) => state,
(state) => state.gallery.selection[state.gallery.selection.length - 1], (state) => state.gallery.selection[state.gallery.selection.length - 1]
defaultSelectorOptions
); );
export const selectListImagesBaseQueryArgs = createSelector( export const selectListImagesBaseQueryArgs = createMemoizedSelector(
[(state: RootState) => state], [(state: RootState) => state],
(state) => { (state) => {
const { selectedBoardId, galleryView } = state.gallery; const { selectedBoardId, galleryView } = state.gallery;
@ -32,6 +30,5 @@ export const selectListImagesBaseQueryArgs = createSelector(
}; };
return listImagesBaseQueryArgs; return listImagesBaseQueryArgs;
}, }
defaultSelectorOptions
); );

View File

@ -1,26 +1,21 @@
import { Flex } from '@chakra-ui/react'; import { Flex } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store'; import { stateSelector } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import IAICollapse from 'common/components/IAICollapse'; import IAICollapse from 'common/components/IAICollapse';
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
import { size } from 'lodash-es'; import { size } from 'lodash-es';
import { memo } from 'react'; import { memo } from 'react';
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus'; import { useTranslation } from 'react-i18next';
import ParamLoraList from './ParamLoraList'; import ParamLoraList from './ParamLoraList';
import ParamLoRASelect from './ParamLoraSelect'; import ParamLoRASelect from './ParamLoraSelect';
import { useTranslation } from 'react-i18next';
const selector = createSelector( const selector = createMemoizedSelector(stateSelector, (state) => {
stateSelector, const loraCount = size(state.lora.loras);
(state) => { return {
const loraCount = size(state.lora.loras); activeLabel: loraCount > 0 ? `${loraCount} Active` : undefined,
return { };
activeLabel: loraCount > 0 ? `${loraCount} Active` : undefined, });
};
},
defaultSelectorOptions
);
const ParamLoraCollapse = () => { const ParamLoraCollapse = () => {
const { t } = useTranslation(); const { t } = useTranslation();

View File

@ -1,19 +1,14 @@
import { Divider, Flex } from '@chakra-ui/react'; import { Divider, Flex } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store'; import { stateSelector } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import { map } from 'lodash-es'; import { map } from 'lodash-es';
import ParamLora from './ParamLora';
import { memo } from 'react'; import { memo } from 'react';
import ParamLora from './ParamLora';
const selector = createSelector( const selector = createMemoizedSelector(stateSelector, ({ lora }) => {
stateSelector, return { lorasArray: map(lora.loras) };
({ lora }) => { });
return { lorasArray: map(lora.loras) };
},
defaultSelectorOptions
);
const ParamLoraList = () => { const ParamLoraList = () => {
const { lorasArray } = useAppSelector(selector); const { lorasArray } = useAppSelector(selector);

View File

@ -1,9 +1,8 @@
import { Flex, Text } from '@chakra-ui/react'; import { Flex, Text } from '@chakra-ui/react';
import { SelectItem } from '@mantine/core'; import { SelectItem } from '@mantine/core';
import { createSelector } from '@reduxjs/toolkit'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { RootState, stateSelector } from 'app/store/store'; import { RootState, stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import IAIMantineSearchableSelect from 'common/components/IAIMantineSearchableSelect'; import IAIMantineSearchableSelect from 'common/components/IAIMantineSearchableSelect';
import IAIMantineSelectItemWithTooltip from 'common/components/IAIMantineSelectItemWithTooltip'; import IAIMantineSelectItemWithTooltip from 'common/components/IAIMantineSelectItemWithTooltip';
import { loraAdded } from 'features/lora/store/loraSlice'; import { loraAdded } from 'features/lora/store/loraSlice';
@ -13,13 +12,9 @@ import { memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useGetLoRAModelsQuery } from 'services/api/endpoints/models'; import { useGetLoRAModelsQuery } from 'services/api/endpoints/models';
const selector = createSelector( const selector = createMemoizedSelector(stateSelector, ({ lora }) => ({
stateSelector, loras: lora.loras,
({ lora }) => ({ }));
loras: lora.loras,
}),
defaultSelectorOptions
);
const ParamLoRASelect = () => { const ParamLoRASelect = () => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();

View File

@ -1,7 +1,6 @@
import { Flex } from '@chakra-ui/react'; import { Flex } from '@chakra-ui/react';
import { useAppDispatch } from 'app/store/storeHooks'; import { useAppDispatch } from 'app/store/storeHooks';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { SelectItem } from '@mantine/core'; import { SelectItem } from '@mantine/core';
import { useForm } from '@mantine/form'; import { useForm } from '@mantine/form';
import IAIButton from 'common/components/IAIButton'; import IAIButton from 'common/components/IAIButton';

View File

@ -1,5 +1,4 @@
import { Flex, Text } from '@chakra-ui/react'; import { Flex, Text } from '@chakra-ui/react';
import { useState } from 'react'; import { useState } from 'react';
import { import {
MainModelConfigEntity, MainModelConfigEntity,

View File

@ -16,7 +16,6 @@ import IAIInput from 'common/components/IAIInput';
import { addToast } from 'features/system/store/systemSlice'; import { addToast } from 'features/system/store/systemSlice';
import { ChangeEvent, useCallback, useEffect, useState } from 'react'; import { ChangeEvent, useCallback, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useConvertMainModelsMutation } from 'services/api/endpoints/models'; import { useConvertMainModelsMutation } from 'services/api/endpoints/models';
import { CheckpointModelConfig } from 'services/api/types'; import { CheckpointModelConfig } from 'services/api/types';

View File

@ -240,7 +240,7 @@ const modelsFilter = <
| LoRAModelConfigEntity | LoRAModelConfigEntity
| OnnxModelConfigEntity, | OnnxModelConfigEntity,
>( >(
data: EntityState<T> | undefined, data: EntityState<T, string> | undefined,
model_type: ModelType, model_type: ModelType,
model_format: ModelFormat | undefined, model_format: ModelFormat | undefined,
nameFilter: string nameFilter: string

View File

@ -5,11 +5,10 @@ import {
PopoverBody, PopoverBody,
PopoverContent, PopoverContent,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit';
import { useAppToaster } from 'app/components/Toaster'; import { useAppToaster } from 'app/components/Toaster';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store'; import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import IAIMantineSearchableSelect from 'common/components/IAIMantineSearchableSelect'; import IAIMantineSearchableSelect from 'common/components/IAIMantineSearchableSelect';
import { useBuildNode } from 'features/nodes/hooks/useBuildNode'; import { useBuildNode } from 'features/nodes/hooks/useBuildNode';
import { import {
@ -62,58 +61,54 @@ const AddNodePopover = () => {
(state) => state.nodes.connectionStartParams?.handleType (state) => state.nodes.connectionStartParams?.handleType
); );
const selector = createSelector( const selector = createMemoizedSelector([stateSelector], ({ nodes }) => {
[stateSelector], // If we have a connection in progress, we need to filter the node choices
({ nodes }) => { const filteredNodeTemplates = fieldFilter
// If we have a connection in progress, we need to filter the node choices ? filter(nodes.nodeTemplates, (template) => {
const filteredNodeTemplates = fieldFilter const handles =
? filter(nodes.nodeTemplates, (template) => { handleFilter == 'source' ? template.inputs : template.outputs;
const handles =
handleFilter == 'source' ? template.inputs : template.outputs;
return some(handles, (handle) => { return some(handles, (handle) => {
const sourceType = const sourceType =
handleFilter == 'source' ? fieldFilter : handle.type; handleFilter == 'source' ? fieldFilter : handle.type;
const targetType = const targetType =
handleFilter == 'target' ? fieldFilter : handle.type; handleFilter == 'target' ? fieldFilter : handle.type;
return validateSourceAndTargetTypes(sourceType, targetType); return validateSourceAndTargetTypes(sourceType, targetType);
}); });
}) })
: map(nodes.nodeTemplates); : map(nodes.nodeTemplates);
const data: NodeTemplate[] = map(filteredNodeTemplates, (template) => { const data: NodeTemplate[] = map(filteredNodeTemplates, (template) => {
return { return {
label: template.title, label: template.title,
value: template.type, value: template.type,
description: template.description, description: template.description,
tags: template.tags, tags: template.tags,
}; };
});
//We only want these nodes if we're not filtered
if (fieldFilter === null) {
data.push({
label: t('nodes.currentImage'),
value: 'current_image',
description: t('nodes.currentImageDescription'),
tags: ['progress'],
}); });
//We only want these nodes if we're not filtered data.push({
if (fieldFilter === null) { label: t('nodes.notes'),
data.push({ value: 'notes',
label: t('nodes.currentImage'), description: t('nodes.notesDescription'),
value: 'current_image', tags: ['notes'],
description: t('nodes.currentImageDescription'), });
tags: ['progress'], }
});
data.push({ data.sort((a, b) => a.label.localeCompare(b.label));
label: t('nodes.notes'),
value: 'notes',
description: t('nodes.notesDescription'),
tags: ['notes'],
});
}
data.sort((a, b) => a.label.localeCompare(b.label)); return { data };
});
return { data };
},
defaultSelectorOptions
);
const { data } = useAppSelector(selector); const { data } = useAppSelector(selector);
const isOpen = useAppSelector((state) => state.nodes.isAddNodePopoverOpen); const isOpen = useAppSelector((state) => state.nodes.isAddNodePopoverOpen);

View File

@ -1,8 +1,7 @@
import { useToken } from '@chakra-ui/react'; import { useToken } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store'; import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import { useIsValidConnection } from 'features/nodes/hooks/useIsValidConnection'; import { useIsValidConnection } from 'features/nodes/hooks/useIsValidConnection';
import { import {
connectionEnded, connectionEnded,
@ -67,17 +66,13 @@ const nodeTypes = {
// TODO: can we support reactflow? if not, we could style the attribution so it matches the app // TODO: can we support reactflow? if not, we could style the attribution so it matches the app
const proOptions: ProOptions = { hideAttribution: true }; const proOptions: ProOptions = { hideAttribution: true };
const selector = createSelector( const selector = createMemoizedSelector(stateSelector, ({ nodes }) => {
stateSelector, const { shouldSnapToGrid, selectionMode } = nodes;
({ nodes }) => { return {
const { shouldSnapToGrid, selectionMode } = nodes; shouldSnapToGrid,
return { selectionMode,
shouldSnapToGrid, };
selectionMode, });
};
},
defaultSelectorOptions
);
export const Flow = () => { export const Flow = () => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();

View File

@ -1,12 +1,12 @@
import { createSelector } from '@reduxjs/toolkit'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store'; import { stateSelector } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { colorTokenToCssVar } from 'common/util/colorTokenToCssVar'; import { colorTokenToCssVar } from 'common/util/colorTokenToCssVar';
import { getFieldColor } from 'features/nodes/components/flow/edges/util/getEdgeColor';
import { memo } from 'react'; import { memo } from 'react';
import { ConnectionLineComponentProps, getBezierPath } from 'reactflow'; import { ConnectionLineComponentProps, getBezierPath } from 'reactflow';
import { getFieldColor } from 'features/nodes/components/flow/edges/util/getEdgeColor';
const selector = createSelector(stateSelector, ({ nodes }) => { const selector = createMemoizedSelector(stateSelector, ({ nodes }) => {
const { shouldAnimateEdges, connectionStartFieldType, shouldColorEdges } = const { shouldAnimateEdges, connectionStartFieldType, shouldColorEdges } =
nodes; nodes;

View File

@ -1,6 +1,5 @@
import { createSelector } from '@reduxjs/toolkit'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store'; import { stateSelector } from 'app/store/store';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import { colorTokenToCssVar } from 'common/util/colorTokenToCssVar'; import { colorTokenToCssVar } from 'common/util/colorTokenToCssVar';
import { isInvocationNode } from 'features/nodes/types/invocation'; import { isInvocationNode } from 'features/nodes/types/invocation';
import { getFieldColor } from './getEdgeColor'; import { getFieldColor } from './getEdgeColor';
@ -12,31 +11,26 @@ export const makeEdgeSelector = (
targetHandleId: string | null | undefined, targetHandleId: string | null | undefined,
selected?: boolean selected?: boolean
) => ) =>
createSelector( createMemoizedSelector(stateSelector, ({ nodes }) => {
stateSelector, const sourceNode = nodes.nodes.find((node) => node.id === source);
({ nodes }) => { const targetNode = nodes.nodes.find((node) => node.id === target);
const sourceNode = nodes.nodes.find((node) => node.id === source);
const targetNode = nodes.nodes.find((node) => node.id === target);
const isInvocationToInvocationEdge = const isInvocationToInvocationEdge =
isInvocationNode(sourceNode) && isInvocationNode(targetNode); isInvocationNode(sourceNode) && isInvocationNode(targetNode);
const isSelected = const isSelected = sourceNode?.selected || targetNode?.selected || selected;
sourceNode?.selected || targetNode?.selected || selected; const sourceType = isInvocationToInvocationEdge
const sourceType = isInvocationToInvocationEdge ? sourceNode?.data?.outputs[sourceHandleId || '']?.type
? sourceNode?.data?.outputs[sourceHandleId || '']?.type : undefined;
: undefined;
const stroke = const stroke =
sourceType && nodes.shouldColorEdges sourceType && nodes.shouldColorEdges
? getFieldColor(sourceType) ? getFieldColor(sourceType)
: colorTokenToCssVar('base.500'); : colorTokenToCssVar('base.500');
return { return {
isSelected, isSelected,
shouldAnimate: nodes.shouldAnimateEdges && isSelected, shouldAnimate: nodes.shouldAnimateEdges && isSelected,
stroke, stroke,
}; };
}, });
defaultSelectorOptions
);

View File

@ -1,25 +1,28 @@
import { useState, PropsWithChildren, memo, useCallback } from 'react';
import { useSelector } from 'react-redux';
import { createSelector } from '@reduxjs/toolkit';
import { Flex, Image, Text } from '@chakra-ui/react'; import { Flex, Image, Text } from '@chakra-ui/react';
import { motion } from 'framer-motion'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { NodeProps } from 'reactflow'; import { stateSelector } from 'app/store/store';
import NodeWrapper from 'features/nodes/components/flow/nodes/common/NodeWrapper';
import NextPrevImageButtons from 'features/gallery/components/NextPrevImageButtons';
import IAIDndImage from 'common/components/IAIDndImage'; import IAIDndImage from 'common/components/IAIDndImage';
import { IAINoContentFallback } from 'common/components/IAIImageFallback'; import { IAINoContentFallback } from 'common/components/IAIImageFallback';
import NextPrevImageButtons from 'features/gallery/components/NextPrevImageButtons';
import NodeWrapper from 'features/nodes/components/flow/nodes/common/NodeWrapper';
import { DRAG_HANDLE_CLASSNAME } from 'features/nodes/types/constants'; import { DRAG_HANDLE_CLASSNAME } from 'features/nodes/types/constants';
import { stateSelector } from 'app/store/store'; import { motion } from 'framer-motion';
import { PropsWithChildren, memo, useCallback, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useSelector } from 'react-redux';
import { NodeProps } from 'reactflow';
const selector = createSelector(stateSelector, ({ system, gallery }) => { const selector = createMemoizedSelector(
const imageDTO = gallery.selection[gallery.selection.length - 1]; stateSelector,
({ system, gallery }) => {
const imageDTO = gallery.selection[gallery.selection.length - 1];
return { return {
imageDTO, imageDTO,
progressImage: system.denoiseProgress?.progress_image, progressImage: system.denoiseProgress?.progress_image,
}; };
}); }
);
const CurrentImageNode = (props: NodeProps) => { const CurrentImageNode = (props: NodeProps) => {
const { progressImage, imageDTO } = useSelector(selector); const { progressImage, imageDTO } = useSelector(selector);

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