Compare commits

...

21 Commits

Author SHA1 Message Date
Ryan Dick
9b763b9e4c Fix issue with seamless context managers when seamless is not configured. 2024-01-05 10:31:58 -05:00
Sergey Borisov
7f3be627c2 Add more seamless configuration options. 2024-01-05 09:57:28 -05:00
psychedelicious
cb7e56a9a3 chore(ui): lint 2024-01-05 08:34:46 -05:00
psychedelicious
1a710a4c12 fix(ui): restore prev colors for workflow editor
Brand colors are now prefixed with "invoke".
2024-01-05 08:34:46 -05:00
psychedelicious
d8d266d3be fix(ui): fix field title spacing
Closes #5405
2024-01-05 08:34:46 -05:00
psychedelicious
4716632c23 fix(ui): tsc 2024-01-06 00:03:07 +11:00
psychedelicious
3c4150d153 fix(ui): update most other selectors
Just a few stragglers left. Good enough for now.
2024-01-06 00:03:07 +11:00
psychedelicious
b71b14d582 fix(ui): update workflow selectors 2024-01-06 00:03:07 +11:00
psychedelicious
73481d4aec feat(ui): clean up canvas selectors
Do not memoize unless absolutely necessary. Minor perf improvement
2024-01-06 00:03:07 +11:00
psychedelicious
2c049a3b94 feat(ui): clean up a few selectors that do not need to be memoized 2024-01-06 00:03:07 +11:00
psychedelicious
367de44a8b fix(ui): tidy remaining selectors
These were just using overly verbose syntax - like explicitly typing `state: RootState`, which is unnecessary.
2024-01-06 00:03:07 +11:00
psychedelicious
f5f378d04b fix(ui): revert back to lrumemoize 2024-01-06 00:03:07 +11:00
psychedelicious
823edbfdef fix(ui): fix more state => state selectors 2024-01-06 00:03:07 +11:00
psychedelicious
29bbb27289 fix(ui): re-add reselect patch
Accidentally removed it last commit.
2024-01-06 00:03:07 +11:00
psychedelicious
a23502f7ff fix(ui): do not use state => state as an input selector
This is a no-no, whoops!
2024-01-06 00:03:07 +11:00
psychedelicious
ce64dbefce chore(ui): lint 2024-01-06 00:03:07 +11:00
psychedelicious
b47afdc3b5 feat(ui): patch reselect to use lruMemoize only
Pending resolution of https://github.com/reduxjs/reselect/issues/635, we can patch `reselect` to use `lruMemoize` exclusively.

Pin RTK and react-redux versions too just to be safe.

This reduces the major GC events that were causing lag/stutters in the app, particularly in canvas and workflow editor.
2024-01-06 00:03:07 +11:00
psychedelicious
cde9c3090f fix(ui): use useAppSelector instead of useSelector 2024-01-06 00:03:07 +11:00
psychedelicious
6924b04d7c feat(ui): use lruMemoize for all entity adapter selectors 2024-01-06 00:03:07 +11:00
blessedcoolant
83fbd4bdf2 ui: slightly reposition floating bars. 2024-01-05 23:59:08 +11:00
Lincoln Stein
6460dcc7e0 use torch.bfloat16 on cuda systems 2024-01-04 23:25:52 -05:00
287 changed files with 2050 additions and 2192 deletions

View File

@@ -1,5 +1,6 @@
# Copyright (c) 2023 Kyle Schouviller (https://github.com/kyle0654)
import contextlib
from contextlib import ExitStack
from functools import singledispatchmethod
from typing import List, Literal, Optional, Union
@@ -716,10 +717,23 @@ class DenoiseLatentsInvocation(BaseInvocation):
**self.unet.unet.model_dump(),
context=context,
)
# Prepare seamless context, if configured.
seamless_context = contextlib.nullcontext()
seamless_config = self.unet.seamless
if seamless_config is not None:
seamless_context = set_seamless(
model=unet_info.context.model,
axes=seamless_config.axes,
skipped_layers=seamless_config.skipped_layers,
skip_second_resnet=seamless_config.skip_second_resnet,
skip_conv2=seamless_config.skip_conv2,
)
with (
ExitStack() as exit_stack,
ModelPatcher.apply_freeu(unet_info.context.model, self.unet.freeu_config),
set_seamless(unet_info.context.model, self.unet.seamless_axes),
seamless_context,
unet_info as unet,
# Apply the LoRA after unet has been moved to its target device for faster patching.
ModelPatcher.apply_lora_unet(unet, _lora_loader()),
@@ -826,7 +840,19 @@ class LatentsToImageInvocation(BaseInvocation, WithMetadata):
context=context,
)
with set_seamless(vae_info.context.model, self.vae.seamless_axes), vae_info as vae:
# Prepare seamless context, if configured.
seamless_context = contextlib.nullcontext()
seamless_config = self.vae.seamless
if seamless_config is not None:
seamless_context = set_seamless(
model=vae_info.context.model,
axes=seamless_config.axes,
skipped_layers=seamless_config.skipped_layers,
skip_second_resnet=seamless_config.skip_second_resnet,
skip_conv2=seamless_config.skip_conv2,
)
with seamless_context, vae_info as vae:
latents = latents.to(vae.device)
if self.fp32:
vae.to(dtype=torch.float32)

View File

@@ -19,6 +19,13 @@ from .baseinvocation import (
)
class SeamlessSettings(BaseModel):
axes: List[str] = Field(description="Axes('x' and 'y') to which apply seamless")
skipped_layers: int = Field(description="How much down layers skip when applying seamless")
skip_second_resnet: bool = Field(description="Skip or not second resnet in down blocks when applying seamless")
skip_conv2: bool = Field(description="Skip or not conv2 in down blocks when applying seamless")
class ModelInfo(BaseModel):
model_name: str = Field(description="Info to load submodel")
base_model: BaseModelType = Field(description="Base model")
@@ -36,8 +43,8 @@ class UNetField(BaseModel):
unet: ModelInfo = Field(description="Info to load unet submodel")
scheduler: ModelInfo = Field(description="Info to load scheduler submodel")
loras: List[LoraInfo] = Field(description="Loras to apply on model loading")
seamless_axes: List[str] = Field(default_factory=list, description='Axes("x" and "y") to which apply seamless')
freeu_config: Optional[FreeUConfig] = Field(default=None, description="FreeU configuration")
seamless: Optional[SeamlessSettings] = Field(default=None, description="Seamless settings applied to model")
class ClipField(BaseModel):
@@ -50,7 +57,7 @@ class ClipField(BaseModel):
class VaeField(BaseModel):
# TODO: better naming?
vae: ModelInfo = Field(description="Info to load vae submodel")
seamless_axes: List[str] = Field(default_factory=list, description='Axes("x" and "y") to which apply seamless')
seamless: Optional[SeamlessSettings] = Field(default=None, description="Seamless settings applied to model")
@invocation_output("unet_output")
@@ -451,6 +458,11 @@ class SeamlessModeInvocation(BaseInvocation):
)
seamless_y: bool = InputField(default=True, input=Input.Any, description="Specify whether Y axis is seamless")
seamless_x: bool = InputField(default=True, input=Input.Any, description="Specify whether X axis is seamless")
skipped_layers: int = InputField(default=0, input=Input.Any, description="How much model's down layers to skip")
skip_second_resnet: bool = InputField(
default=True, input=Input.Any, description="Skip or not second resnet in down layers"
)
skip_conv2: bool = InputField(default=True, input=Input.Any, description="Skip or not conv2 in down layers")
def invoke(self, context: InvocationContext) -> SeamlessModeOutput:
# Conditionally append 'x' and 'y' based on seamless_x and seamless_y
@@ -465,9 +477,19 @@ class SeamlessModeInvocation(BaseInvocation):
seamless_axes_list.append("y")
if unet is not None:
unet.seamless_axes = seamless_axes_list
unet.seamless = SeamlessSettings(
axes=seamless_axes_list,
skipped_layers=self.skipped_layers,
skip_second_resnet=self.skip_second_resnet,
skip_conv2=self.skip_conv2,
)
if vae is not None:
vae.seamless_axes = seamless_axes_list
vae.seamless = SeamlessSettings(
axes=seamless_axes_list,
skipped_layers=self.skipped_layers,
skip_second_resnet=self.skip_second_resnet,
skip_conv2=self.skip_conv2,
)
return SeamlessModeOutput(unet=unet, vae=vae)

View File

@@ -25,71 +25,55 @@ def _conv_forward_asymmetric(self, input, weight, bias):
@contextmanager
def set_seamless(model: Union[UNet2DConditionModel, AutoencoderKL], seamless_axes: List[str]):
def set_seamless(
model: Union[UNet2DConditionModel, AutoencoderKL],
axes: List[str],
skipped_layers: int,
skip_second_resnet: bool,
skip_conv2: bool,
):
try:
to_restore = []
for m_name, m in model.named_modules():
if isinstance(model, UNet2DConditionModel):
if ".attentions." in m_name:
if not isinstance(m, (nn.Conv2d, nn.ConvTranspose2d)):
continue
if isinstance(model, UNet2DConditionModel) and m_name.startswith("down_blocks.") and ".resnets." in m_name:
# down_blocks.1.resnets.1.conv1
_, block_num, _, resnet_num, submodule_name = m_name.split(".")
block_num = int(block_num)
resnet_num = int(resnet_num)
# if block_num >= seamless_down_blocks:
if block_num >= len(model.down_blocks) - skipped_layers:
continue
if ".resnets." in m_name:
if ".conv2" in m_name:
continue
if ".conv_shortcut" in m_name:
continue
"""
if isinstance(model, UNet2DConditionModel):
if False and ".upsamplers." in m_name:
if resnet_num > 0 and skip_second_resnet:
continue
if False and ".downsamplers." in m_name:
if submodule_name == "conv2" and skip_conv2:
continue
if True and ".resnets." in m_name:
if True and ".conv1" in m_name:
if False and "down_blocks" in m_name:
continue
if False and "mid_block" in m_name:
continue
if False and "up_blocks" in m_name:
continue
m.asymmetric_padding_mode = {}
m.asymmetric_padding = {}
m.asymmetric_padding_mode["x"] = "circular" if ("x" in axes) else "constant"
m.asymmetric_padding["x"] = (
m._reversed_padding_repeated_twice[0],
m._reversed_padding_repeated_twice[1],
0,
0,
)
m.asymmetric_padding_mode["y"] = "circular" if ("y" in axes) else "constant"
m.asymmetric_padding["y"] = (
0,
0,
m._reversed_padding_repeated_twice[2],
m._reversed_padding_repeated_twice[3],
)
if True and ".conv2" in m_name:
continue
if True and ".conv_shortcut" in m_name:
continue
if True and ".attentions." in m_name:
continue
if False and m_name in ["conv_in", "conv_out"]:
continue
"""
if isinstance(m, (nn.Conv2d, nn.ConvTranspose2d)):
m.asymmetric_padding_mode = {}
m.asymmetric_padding = {}
m.asymmetric_padding_mode["x"] = "circular" if ("x" in seamless_axes) else "constant"
m.asymmetric_padding["x"] = (
m._reversed_padding_repeated_twice[0],
m._reversed_padding_repeated_twice[1],
0,
0,
)
m.asymmetric_padding_mode["y"] = "circular" if ("y" in seamless_axes) else "constant"
m.asymmetric_padding["y"] = (
0,
0,
m._reversed_padding_repeated_twice[2],
m._reversed_padding_repeated_twice[3],
)
to_restore.append((m, m._conv_forward))
m._conv_forward = _conv_forward_asymmetric.__get__(m, nn.Conv2d)
to_restore.append((m, m._conv_forward))
m._conv_forward = _conv_forward_asymmetric.__get__(m, nn.Conv2d)
yield

View File

@@ -44,7 +44,7 @@ def torch_dtype(device: torch.device) -> torch.dtype:
if config.full_precision:
return torch.float32
if choose_precision(device) == "float16":
return torch.float16
return torch.bfloat16 if device.type == "cuda" else torch.float16
else:
return torch.float32

View File

@@ -68,7 +68,7 @@
"@fontsource-variable/inter": "^5.0.16",
"@mantine/form": "6.0.21",
"@nanostores/react": "^0.7.1",
"@reduxjs/toolkit": "^2.0.1",
"@reduxjs/toolkit": "2.0.1",
"@roarr/browser-log-writer": "^1.3.0",
"chakra-react-select": "^4.7.6",
"compare-versions": "^6.1.0",
@@ -94,7 +94,7 @@
"react-i18next": "^14.0.0",
"react-icons": "^4.12.0",
"react-konva": "^18.2.10",
"react-redux": "^9.0.4",
"react-redux": "9.0.4",
"react-resizable-panels": "^1.0.7",
"react-select": "5.8.0",
"react-textarea-autosize": "^8.5.3",
@@ -167,5 +167,10 @@
"vite-plugin-dts": "^3.7.0",
"vite-plugin-eslint": "^1.8.1",
"vite-tsconfig-paths": "^4.2.3"
},
"pnpm": {
"patchedDependencies": {
"reselect@5.0.1": "patches/reselect@5.0.1.patch"
}
}
}

File diff suppressed because one or more lines are too long

View File

@@ -4,6 +4,11 @@ settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
patchedDependencies:
reselect@5.0.1:
hash: kvbgwzjyy4x4fnh7znyocvb75q
path: patches/reselect@5.0.1.patch
dependencies:
'@chakra-ui/anatomy':
specifier: ^2.2.2
@@ -54,7 +59,7 @@ dependencies:
specifier: ^0.7.1
version: 0.7.1(nanostores@0.9.5)(react@18.2.0)
'@reduxjs/toolkit':
specifier: ^2.0.1
specifier: 2.0.1
version: 2.0.1(react-redux@9.0.4)(react@18.2.0)
'@roarr/browser-log-writer':
specifier: ^1.3.0
@@ -132,7 +137,7 @@ dependencies:
specifier: ^18.2.10
version: 18.2.10(konva@9.3.0)(react-dom@18.2.0)(react@18.2.0)
react-redux:
specifier: ^9.0.4
specifier: 9.0.4
version: 9.0.4(@types/react@18.2.46)(react@18.2.0)(redux@5.0.1)
react-resizable-panels:
specifier: ^1.0.7
@@ -4565,7 +4570,7 @@ packages:
react-redux: 9.0.4(@types/react@18.2.46)(react@18.2.0)(redux@5.0.1)
redux: 5.0.1
redux-thunk: 3.1.0(redux@5.0.1)
reselect: 5.0.1
reselect: 5.0.1(patch_hash=kvbgwzjyy4x4fnh7znyocvb75q)
dev: false
/@roarr/browser-log-writer@1.3.0:
@@ -11932,9 +11937,10 @@ packages:
hasBin: true
dev: true
/reselect@5.0.1:
/reselect@5.0.1(patch_hash=kvbgwzjyy4x4fnh7znyocvb75q):
resolution: {integrity: sha512-D72j2ubjgHpvuCiORWkOUxndHJrxDaSolheiz5CO+roz8ka97/4msh2E8F5qay4GawR5vzBt5MkbDHT+Rdy/Wg==}
dev: false
patched: true
/resize-observer-polyfill@1.5.1:
resolution: {integrity: sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==}

View File

@@ -11,7 +11,7 @@ import { memo, useCallback, useEffect } from 'react';
*/
const Toaster = () => {
const dispatch = useAppDispatch();
const toastQueue = useAppSelector((state) => state.system.toastQueue);
const toastQueue = useAppSelector((s) => s.system.toastQueue);
const toast = useToast();
useEffect(() => {
toastQueue.forEach((t) => {

View File

@@ -1,6 +1,4 @@
import { createLogWriter } from '@roarr/browser-log-writer';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks';
import { useEffect, useMemo } from 'react';
import { ROARR, Roarr } from 'roarr';
@@ -8,17 +6,9 @@ import { ROARR, Roarr } from 'roarr';
import type { LoggerNamespace } from './logger';
import { $logger, BASE_CONTEXT, LOG_LEVEL_MAP, logger } from './logger';
const selector = createMemoizedSelector(stateSelector, ({ system }) => {
const { consoleLogLevel, shouldLogToConsole } = system;
return {
consoleLogLevel,
shouldLogToConsole,
};
});
export const useLogger = (namespace: LoggerNamespace) => {
const { consoleLogLevel, shouldLogToConsole } = useAppSelector(selector);
const consoleLogLevel = useAppSelector((s) => s.system.consoleLogLevel);
const shouldLogToConsole = useAppSelector((s) => s.system.shouldLogToConsole);
// The provided Roarr browser log writer uses localStorage to config logging to console
useEffect(() => {

View File

@@ -1,4 +1,9 @@
import { createSelectorCreator, lruMemoize } from '@reduxjs/toolkit';
import {
createDraftSafeSelectorCreator,
createSelectorCreator,
lruMemoize,
} from '@reduxjs/toolkit';
import type { GetSelectorsOptions } from '@reduxjs/toolkit/dist/entities/state_selectors';
import { isEqual } from 'lodash-es';
/**
@@ -19,3 +24,12 @@ export const createLruSelector = createSelectorCreator({
memoize: lruMemoize,
argsMemoize: lruMemoize,
});
export const createLruDraftSafeSelector = createDraftSafeSelectorCreator({
memoize: lruMemoize,
argsMemoize: lruMemoize,
});
export const getSelectorsOptions: GetSelectorsOptions = {
createSelector: createLruDraftSafeSelector,
};

View File

@@ -8,7 +8,6 @@ import { initialPostprocessingState } from 'features/parameters/store/postproces
import { initialSDXLState } from 'features/sdxl/store/sdxlSlice';
import { initialConfigState } from 'features/system/store/configSlice';
import { initialSystemState } from 'features/system/store/systemSlice';
import { initialHotkeysState } from 'features/ui/store/hotkeysSlice';
import { initialUIState } from 'features/ui/store/uiSlice';
import { defaultsDeep } from 'lodash-es';
import type { UnserializeFunction } from 'redux-remember';
@@ -24,7 +23,6 @@ const initialStates: {
system: initialSystemState,
config: initialConfigState,
ui: initialUIState,
hotkeys: initialHotkeysState,
controlAdapters: initialControlAdapterState,
dynamicPrompts: initialDynamicPromptsState,
sdxl: initialSDXLState,

View File

@@ -3,7 +3,7 @@ import { imageSelected } from 'features/gallery/store/gallerySlice';
import { IMAGE_CATEGORIES } from 'features/gallery/store/types';
import { imagesApi } from 'services/api/endpoints/images';
import type { ImageCache } from 'services/api/types';
import { getListImagesUrl, imagesAdapter } from 'services/api/util';
import { getListImagesUrl, imagesSelectors } from 'services/api/util';
import { startAppListening } from '..';
@@ -33,7 +33,7 @@ export const addFirstListImagesListener = () => {
if (data.ids.length > 0) {
// Select the first image
const firstImage = imagesAdapter.getSelectors().selectAll(data)[0];
const firstImage = imagesSelectors.selectAll(data)[0];
dispatch(imageSelected(firstImage ?? null));
}
},

View File

@@ -20,9 +20,15 @@ export const addDeleteBoardAndImagesFulfilledListener = () => {
let wasNodeEditorReset = false;
let wereControlAdaptersReset = false;
const state = getState();
const { generation, canvas, nodes, controlAdapters } = getState();
deleted_images.forEach((image_name) => {
const imageUsage = getImageUsage(state, image_name);
const imageUsage = getImageUsage(
generation,
canvas,
nodes,
controlAdapters,
image_name
);
if (imageUsage.isInitialImage && !wasInitialImageReset) {
dispatch(clearInitialImage());

View File

@@ -17,7 +17,7 @@ import { clearInitialImage } from 'features/parameters/store/generationSlice';
import { clamp, forEach } from 'lodash-es';
import { api } from 'services/api';
import { imagesApi } from 'services/api/endpoints/images';
import { imagesAdapter } from 'services/api/util';
import { imagesSelectors } from 'services/api/util';
import { startAppListening } from '..';
@@ -53,9 +53,7 @@ export const addRequestedSingleImageDeletionListener = () => {
const { data } =
imagesApi.endpoints.listImages.select(baseQueryArgs)(state);
const cachedImageDTOs = data
? imagesAdapter.getSelectors().selectAll(data)
: [];
const cachedImageDTOs = data ? imagesSelectors.selectAll(data) : [];
const deletedImageIndex = cachedImageDTOs.findIndex(
(i) => i.image_name === image_name
@@ -187,7 +185,7 @@ export const addRequestedMultipleImageDeletionListener = () => {
imagesApi.endpoints.listImages.select(baseQueryArgs)(state);
const newSelectedImageDTO = data
? imagesAdapter.getSelectors().selectAll(data)[0]
? imagesSelectors.selectAll(data)[0]
: undefined;
if (newSelectedImageDTO) {

View File

@@ -17,9 +17,9 @@ import {
import { refinerModelChanged } from 'features/sdxl/store/sdxlSlice';
import { forEach, some } from 'lodash-es';
import {
mainModelsAdapter,
mainModelsAdapterSelectors,
modelsApi,
vaeModelsAdapter,
vaeModelsAdapterSelectors,
} from 'services/api/endpoints/models';
import type { TypeGuardFor } from 'services/api/types';
@@ -43,7 +43,7 @@ export const addModelsLoadedListener = () => {
);
const currentModel = getState().generation.model;
const models = mainModelsAdapter.getSelectors().selectAll(action.payload);
const models = mainModelsAdapterSelectors.selectAll(action.payload);
if (models.length === 0) {
// No models loaded at all
@@ -94,7 +94,7 @@ export const addModelsLoadedListener = () => {
);
const currentModel = getState().sdxl.refinerModel;
const models = mainModelsAdapter.getSelectors().selectAll(action.payload);
const models = mainModelsAdapterSelectors.selectAll(action.payload);
if (models.length === 0) {
// No models loaded at all
@@ -145,9 +145,7 @@ export const addModelsLoadedListener = () => {
return;
}
const firstModel = vaeModelsAdapter
.getSelectors()
.selectAll(action.payload)[0];
const firstModel = vaeModelsAdapterSelectors.selectAll(action.payload)[0];
if (!firstModel) {
// No custom VAEs loaded at all; use the default

View File

@@ -1,7 +1,10 @@
import { modelChanged } from 'features/parameters/store/generationSlice';
import { setActiveTab } from 'features/ui/store/uiSlice';
import { NON_REFINER_BASE_MODELS } from 'services/api/constants';
import { mainModelsAdapter, modelsApi } from 'services/api/endpoints/models';
import {
mainModelsAdapterSelectors,
modelsApi,
} from 'services/api/endpoints/models';
import { startAppListening } from '..';
@@ -37,8 +40,7 @@ export const addTabChangedListener = () => {
}
// need to filter out all the invalid canvas models (currently refiner & any)
const validCanvasModels = mainModelsAdapter
.getSelectors()
const validCanvasModels = mainModelsAdapterSelectors
.selectAll(models)
.filter((model) =>
['sd-1', 'sd-2', 'sdxl'].includes(model.base_model)

View File

@@ -151,6 +151,3 @@ export type RootState = ReturnType<ReturnType<typeof createStore>['getState']>;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type AppThunkDispatch = ThunkDispatch<RootState, any, UnknownAction>;
export type AppDispatch = ReturnType<typeof createStore>['dispatch'];
export function stateSelector(state: RootState) {
return state;
}

View File

@@ -32,7 +32,7 @@ const IAIInformationalPopover = ({
...rest
}: Props) => {
const shouldEnableInformationalPopovers = useAppSelector(
(state) => state.system.shouldEnableInformationalPopovers
(s) => s.system.shouldEnableInformationalPopovers
);
const data = useMemo(() => POPOVER_DATA[feature], [feature]);

View File

@@ -21,7 +21,7 @@ export const InvAccordionButton = (props: InvAccordionButtonProps) => {
{children}
<Spacer />
{badges?.map((b, i) => (
<InvBadge key={`${b}.${i}`} colorScheme="blue">
<InvBadge key={`${b}.${i}`} colorScheme="invokeBlue">
{b}
</InvBadge>
))}

View File

@@ -28,7 +28,13 @@ const meta: Meta<typeof InvButton> = {
export default meta;
type Story = StoryObj<typeof InvButton>;
const colorSchemes = ['base', 'invokeYellow', 'red', 'green', 'blue'] as const;
const colorSchemes = [
'base',
'invokeYellow',
'invokeRed',
'invokeGreen',
'invokeBlue',
] as const;
const variants = ['solid', 'outline', 'ghost', 'link'] as const;
const sizes = ['xs', 'sm', 'md', 'lg'] as const;

View File

@@ -12,7 +12,7 @@ export const InvButton = memo(
<InvTooltip label={tooltip}>
<Button
ref={ref}
colorScheme={isChecked ? 'blue' : 'base'}
colorScheme={isChecked ? 'invokeBlue' : 'base'}
{...rest}
>
{children}
@@ -22,7 +22,11 @@ export const InvButton = memo(
}
return (
<Button ref={ref} colorScheme={isChecked ? 'blue' : 'base'} {...rest}>
<Button
ref={ref}
colorScheme={isChecked ? 'invokeBlue' : 'base'}
{...rest}
>
{children}
</Button>
);

View File

@@ -13,7 +13,7 @@ export const InvLabel = memo(
ref
) => {
const shouldEnableInformationalPopovers = useAppSelector(
(state) => state.system.shouldEnableInformationalPopovers
(s) => s.system.shouldEnableInformationalPopovers
);
const ctx = useContext(InvControlGroupContext);

View File

@@ -1,11 +1,11 @@
import { defineStyle, defineStyleConfig } from '@chakra-ui/react';
const blue = defineStyle(() => ({
color: 'blue.300',
const invokeBlue = defineStyle(() => ({
color: 'invokeBlue.300',
}));
export const headingTheme = defineStyleConfig({
variants: {
blue,
invokeBlue,
},
});

View File

@@ -11,7 +11,7 @@ export const InvIconButton = memo(
<InvTooltip label={tooltip}>
<IconButton
ref={ref}
colorScheme={isChecked ? 'blue' : 'base'}
colorScheme={isChecked ? 'invokeBlue' : 'base'}
{...rest}
/>
</InvTooltip>
@@ -21,7 +21,7 @@ export const InvIconButton = memo(
return (
<IconButton
ref={ref}
colorScheme={isChecked ? 'blue' : 'base'}
colorScheme={isChecked ? 'invokeBlue' : 'base'}
{...rest}
/>
);

View File

@@ -30,7 +30,7 @@ export const useGroupedModelInvSelect = <T extends AnyModelConfigEntity>(
): UseGroupedModelInvSelectReturn => {
const { t } = useTranslation();
const base_model = useAppSelector(
(state) => state.generation.model?.base_model ?? 'sdxl'
(s) => s.generation.model?.base_model ?? 'sdxl'
);
const { modelEntities, selectedModel, getIsDisabled, onChange, isLoading } =
arg;

View File

@@ -1,6 +1,5 @@
import { useAppToaster } from 'app/components/Toaster';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks';
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
import { useCallback, useEffect, useState } from 'react';
@@ -15,9 +14,9 @@ const accept: Accept = {
'image/jpeg': ['.jpg', '.jpeg', '.png'],
};
const selector = createMemoizedSelector(
[stateSelector, activeTabNameSelector],
({ gallery }, activeTabName) => {
const selectPostUploadAction = createMemoizedSelector(
activeTabNameSelector,
(activeTabName) => {
let postUploadAction: PostUploadAction = { type: 'TOAST' };
if (activeTabName === 'unifiedCanvas') {
@@ -28,19 +27,15 @@ const selector = createMemoizedSelector(
postUploadAction = { type: 'SET_INITIAL_IMAGE' };
}
const { autoAddBoardId } = gallery;
return {
autoAddBoardId,
postUploadAction,
};
return postUploadAction;
}
);
export const useFullscreenDropzone = () => {
const { autoAddBoardId, postUploadAction } = useAppSelector(selector);
const toaster = useAppToaster();
const { t } = useTranslation();
const toaster = useAppToaster();
const postUploadAction = useAppSelector(selectPostUploadAction);
const autoAddBoardId = useAppSelector((s) => s.gallery.autoAddBoardId);
const [isHandlingUpload, setIsHandlingUpload] = useState<boolean>(false);
const [uploadImage] = useUploadImageMutation();

View File

@@ -32,9 +32,7 @@ export const useImageUploadButton = ({
postUploadAction,
isDisabled,
}: UseImageUploadButtonArgs) => {
const autoAddBoardId = useAppSelector(
(state) => state.gallery.autoAddBoardId
);
const autoAddBoardId = useAppSelector((s) => s.gallery.autoAddBoardId);
const [uploadImage] = useUploadImageMutation();
const onDropAccepted = useCallback(
(files: File[]) => {

View File

@@ -1,26 +1,39 @@
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks';
import { selectControlAdapterAll } from 'features/controlAdapters/store/controlAdaptersSlice';
import {
selectControlAdapterAll,
selectControlAdaptersSlice,
} from 'features/controlAdapters/store/controlAdaptersSlice';
import { isControlNetOrT2IAdapter } from 'features/controlAdapters/store/types';
import { selectDynamicPromptsSlice } from 'features/dynamicPrompts/store/dynamicPromptsSlice';
import { getShouldProcessPrompt } from 'features/dynamicPrompts/util/getShouldProcessPrompt';
import { selectNodesSlice } from 'features/nodes/store/nodesSlice';
import { selectNodeTemplatesSlice } from 'features/nodes/store/nodeTemplatesSlice';
import { isInvocationNode } from 'features/nodes/types/invocation';
import { selectGenerationSlice } from 'features/parameters/store/generationSlice';
import { selectSystemSlice } from 'features/system/store/systemSlice';
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
import i18n from 'i18next';
import { forEach } from 'lodash-es';
import { getConnectedEdges } from 'reactflow';
const selector = createMemoizedSelector(
[stateSelector, activeTabNameSelector],
[
selectControlAdaptersSlice,
selectGenerationSlice,
selectSystemSlice,
selectNodesSlice,
selectNodeTemplatesSlice,
selectDynamicPromptsSlice,
activeTabNameSelector,
],
(
{
controlAdapters,
generation,
system,
nodes,
nodeTemplates,
dynamicPrompts,
},
controlAdapters,
generation,
system,
nodes,
nodeTemplates,
dynamicPrompts,
activeTabName
) => {
const { initialImage, model, positivePrompt } = generation;

View File

@@ -1,7 +1,6 @@
import { Box, chakra, Flex } from '@chakra-ui/react';
import { useStore } from '@nanostores/react';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import useCanvasDragMove from 'features/canvas/hooks/useCanvasDragMove';
import useCanvasHotkeys from 'features/canvas/hooks/useCanvasHotkeys';
@@ -17,7 +16,10 @@ import {
$isTransformingBoundingBox,
} from 'features/canvas/store/canvasNanostore';
import { isStagingSelector } from 'features/canvas/store/canvasSelectors';
import { canvasResized } from 'features/canvas/store/canvasSlice';
import {
canvasResized,
selectCanvasSlice,
} from 'features/canvas/store/canvasSlice';
import {
setCanvasBaseLayer,
setCanvasStage,
@@ -40,57 +42,34 @@ import IAICanvasStatusText from './IAICanvasStatusText';
import IAICanvasBoundingBox from './IAICanvasToolbar/IAICanvasBoundingBox';
import IAICanvasToolPreview from './IAICanvasToolPreview';
const selector = createMemoizedSelector(
[stateSelector, isStagingSelector],
({ canvas }, isStaging) => {
const {
isMaskEnabled,
stageScale,
shouldShowBoundingBox,
stageDimensions,
stageCoordinates,
tool,
shouldShowIntermediates,
shouldRestrictStrokesToBox,
shouldShowGrid,
shouldAntialias,
} = canvas;
return {
isMaskEnabled,
shouldShowBoundingBox,
shouldShowGrid,
stageCoordinates,
stageDimensions,
stageScale,
tool,
isStaging,
shouldShowIntermediates,
shouldAntialias,
shouldRestrictStrokesToBox,
};
}
);
const selector = createMemoizedSelector(selectCanvasSlice, (canvas) => {
return {
stageCoordinates: canvas.stageCoordinates,
stageDimensions: canvas.stageDimensions,
};
});
const ChakraStage = chakra(Stage, {
shouldForwardProp: (prop) => !['sx'].includes(prop),
});
const IAICanvas = () => {
const {
isMaskEnabled,
shouldShowBoundingBox,
shouldShowGrid,
stageCoordinates,
stageDimensions,
stageScale,
tool,
isStaging,
shouldShowIntermediates,
shouldAntialias,
shouldRestrictStrokesToBox,
} = useAppSelector(selector);
useCanvasHotkeys();
const isStaging = useAppSelector(isStagingSelector);
const isMaskEnabled = useAppSelector((s) => s.canvas.isMaskEnabled);
const shouldShowBoundingBox = useAppSelector(
(s) => s.canvas.shouldShowBoundingBox
);
const shouldShowGrid = useAppSelector((s) => s.canvas.shouldShowGrid);
const stageScale = useAppSelector((s) => s.canvas.stageScale);
const tool = useAppSelector((s) => s.canvas.tool);
const shouldShowIntermediates = useAppSelector(
(s) => s.canvas.shouldShowIntermediates
);
const shouldAntialias = useAppSelector((s) => s.canvas.shouldAntialias);
const shouldRestrictStrokesToBox = useAppSelector(
(s) => s.canvas.shouldRestrictStrokesToBox
);
const { stageCoordinates, stageDimensions } = useAppSelector(selector);
const dispatch = useAppDispatch();
const containerRef = useRef<HTMLDivElement>(null);
const stageRef = useRef<Konva.Stage | null>(null);
@@ -99,6 +78,7 @@ const IAICanvas = () => {
const isMovingStage = useStore($isMovingStage);
const isTransformingBoundingBox = useStore($isTransformingBoundingBox);
const isMouseOverBoundingBox = useStore($isMouseOverBoundingBox);
useCanvasHotkeys();
const canvasStageRefCallback = useCallback((el: Konva.Stage) => {
setCanvasStage(el as Konva.Stage);
stageRef.current = el;

View File

@@ -1,37 +1,36 @@
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks';
import { selectCanvasSlice } from 'features/canvas/store/canvasSlice';
import { memo } from 'react';
import { Group, Rect } from 'react-konva';
const selector = createMemoizedSelector(stateSelector, ({ canvas }) => {
const selector = createMemoizedSelector(selectCanvasSlice, (canvas) => {
const {
boundingBoxCoordinates,
boundingBoxDimensions,
stageDimensions,
stageScale,
shouldDarkenOutsideBoundingBox,
stageCoordinates,
} = canvas;
return {
boundingBoxCoordinates,
boundingBoxDimensions,
shouldDarkenOutsideBoundingBox,
stageCoordinates,
stageDimensions,
stageScale,
};
});
const IAICanvasBoundingBoxOverlay = () => {
const {
boundingBoxCoordinates,
boundingBoxDimensions,
shouldDarkenOutsideBoundingBox,
stageCoordinates,
stageDimensions,
stageScale,
} = useAppSelector(selector);
const shouldDarkenOutsideBoundingBox = useAppSelector(
(s) => s.canvas.shouldDarkenOutsideBoundingBox
);
const stageScale = useAppSelector((s) => s.canvas.stageScale);
return (
<Group listening={false}>

View File

@@ -1,23 +1,25 @@
// Grid drawing adapted from https://longviewcoder.com/2021/12/08/konva-a-better-grid/
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks';
import { selectCanvasSlice } from 'features/canvas/store/canvasSlice';
import type { ReactElement } from 'react';
import { memo, useCallback, useMemo } from 'react';
import { Group, Line as KonvaLine } from 'react-konva';
import { getArbitraryBaseColor } from 'theme/colors';
const selector = createMemoizedSelector([stateSelector], ({ canvas }) => {
const { stageScale, stageCoordinates, stageDimensions } = canvas;
return { stageScale, stageCoordinates, stageDimensions };
const selector = createMemoizedSelector(selectCanvasSlice, (canvas) => {
return {
stageCoordinates: canvas.stageCoordinates,
stageDimensions: canvas.stageDimensions,
};
});
const baseGridLineColor = getArbitraryBaseColor(27);
const fineGridLineColor = getArbitraryBaseColor(18);
const IAICanvasGrid = () => {
const { stageScale, stageCoordinates, stageDimensions } =
useAppSelector(selector);
const { stageCoordinates, stageDimensions } = useAppSelector(selector);
const stageScale = useAppSelector((s) => s.canvas.stageScale);
const gridSpacing = useMemo(() => {
if (stageScale >= 2) {

View File

@@ -1,32 +1,28 @@
import {
createLruSelector,
createMemoizedSelector,
} from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { useAppSelector } from 'app/store/storeHooks';
import { selectCanvasSlice } from 'features/canvas/store/canvasSlice';
import { selectSystemSlice } from 'features/system/store/systemSlice';
import { memo, useEffect, useState } from 'react';
import { Image as KonvaImage } from 'react-konva';
const progressImageSelector = createLruSelector(
[stateSelector],
({ system, canvas }) => {
const progressImageSelector = createMemoizedSelector(
[selectSystemSlice, selectCanvasSlice],
(system, canvas) => {
const { denoiseProgress } = system;
const { batchIds } = canvas;
return denoiseProgress && batchIds.includes(denoiseProgress.batch_id)
? denoiseProgress.progress_image
: undefined;
return {
progressImage:
denoiseProgress && batchIds.includes(denoiseProgress.batch_id)
? denoiseProgress.progress_image
: undefined,
boundingBox: canvas.layerState.stagingArea.boundingBox,
};
}
);
const boundingBoxSelector = createMemoizedSelector(
[stateSelector],
({ canvas }) => canvas.layerState.stagingArea.boundingBox
);
const IAICanvasIntermediateImage = () => {
const progressImage = useAppSelector(progressImageSelector);
const boundingBox = useAppSelector(boundingBoxSelector);
const { progressImage, boundingBox } = useAppSelector(progressImageSelector);
const [loadedImageElement, setLoadedImageElement] =
useState<HTMLImageElement | null>(null);

View File

@@ -1,7 +1,8 @@
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks';
import { selectCanvasSlice } from 'features/canvas/store/canvasSlice';
import { rgbaColorToString } from 'features/canvas/util/colorToString';
import { getColoredMaskSVG } from 'features/canvas/util/getColoredMaskSVG';
import type Konva from 'konva';
import type { RectConfig } from 'konva/lib/shapes/Rect';
import { isNumber } from 'lodash-es';
@@ -9,109 +10,27 @@ import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { Rect } from 'react-konva';
export const canvasMaskCompositerSelector = createMemoizedSelector(
stateSelector,
({ canvas }) => {
const { maskColor, stageCoordinates, stageDimensions, stageScale } = canvas;
selectCanvasSlice,
(canvas) => {
return {
stageCoordinates,
stageDimensions,
stageScale,
maskColorString: rgbaColorToString(maskColor),
stageCoordinates: canvas.stageCoordinates,
stageDimensions: canvas.stageDimensions,
};
}
);
type IAICanvasMaskCompositerProps = RectConfig;
const getColoredSVG = (color: string) => {
return `data:image/svg+xml;utf8,<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="60px" height="60px" viewBox="0 0 30 30" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.5;">
<g transform="matrix(0.5,0,0,0.5,0,0)">
<path d="M-3.5,63.5L64,-4" style="fill:none;stroke:black;stroke-width:1px;"/>
</g>
<g transform="matrix(0.5,0,0,0.5,0,2.5)">
<path d="M-3.5,63.5L64,-4" style="fill:none;stroke:black;stroke-width:1px;"/>
</g>
<g transform="matrix(0.5,0,0,0.5,0,5)">
<path d="M-3.5,63.5L64,-4" style="fill:none;stroke:black;stroke-width:1px;"/>
</g>
<g transform="matrix(0.5,0,0,0.5,0,7.5)">
<path d="M-3.5,63.5L64,-4" style="fill:none;stroke:black;stroke-width:1px;"/>
</g>
<g transform="matrix(0.5,0,0,0.5,0,10)">
<path d="M-3.5,63.5L64,-4" style="fill:none;stroke:black;stroke-width:1px;"/>
</g>
<g transform="matrix(0.5,0,0,0.5,0,12.5)">
<path d="M-3.5,63.5L64,-4" style="fill:none;stroke:black;stroke-width:1px;"/>
</g>
<g transform="matrix(0.5,0,0,0.5,0,15)">
<path d="M-3.5,63.5L64,-4" style="fill:none;stroke:black;stroke-width:1px;"/>
</g>
<g transform="matrix(0.5,0,0,0.5,0,17.5)">
<path d="M-3.5,63.5L64,-4" style="fill:none;stroke:black;stroke-width:1px;"/>
</g>
<g transform="matrix(0.5,0,0,0.5,0,20)">
<path d="M-3.5,63.5L64,-4" style="fill:none;stroke:black;stroke-width:1px;"/>
</g>
<g transform="matrix(0.5,0,0,0.5,0,22.5)">
<path d="M-3.5,63.5L64,-4" style="fill:none;stroke:black;stroke-width:1px;"/>
</g>
<g transform="matrix(0.5,0,0,0.5,0,25)">
<path d="M-3.5,63.5L64,-4" style="fill:none;stroke:black;stroke-width:1px;"/>
</g>
<g transform="matrix(0.5,0,0,0.5,0,27.5)">
<path d="M-3.5,63.5L64,-4" style="fill:none;stroke:black;stroke-width:1px;"/>
</g>
<g transform="matrix(0.5,0,0,0.5,0,30)">
<path d="M-3.5,63.5L64,-4" style="fill:none;stroke:black;stroke-width:1px;"/>
</g>
<g transform="matrix(0.5,0,0,0.5,0,-2.5)">
<path d="M-3.5,63.5L64,-4" style="fill:none;stroke:black;stroke-width:1px;"/>
</g>
<g transform="matrix(0.5,0,0,0.5,0,-5)">
<path d="M-3.5,63.5L64,-4" style="fill:none;stroke:black;stroke-width:1px;"/>
</g>
<g transform="matrix(0.5,0,0,0.5,0,-7.5)">
<path d="M-3.5,63.5L64,-4" style="fill:none;stroke:black;stroke-width:1px;"/>
</g>
<g transform="matrix(0.5,0,0,0.5,0,-10)">
<path d="M-3.5,63.5L64,-4" style="fill:none;stroke:black;stroke-width:1px;"/>
</g>
<g transform="matrix(0.5,0,0,0.5,0,-12.5)">
<path d="M-3.5,63.5L64,-4" style="fill:none;stroke:black;stroke-width:1px;"/>
</g>
<g transform="matrix(0.5,0,0,0.5,0,-15)">
<path d="M-3.5,63.5L64,-4" style="fill:none;stroke:black;stroke-width:1px;"/>
</g>
<g transform="matrix(0.5,0,0,0.5,0,-17.5)">
<path d="M-3.5,63.5L64,-4" style="fill:none;stroke:black;stroke-width:1px;"/>
</g>
<g transform="matrix(0.5,0,0,0.5,0,-20)">
<path d="M-3.5,63.5L64,-4" style="fill:none;stroke:black;stroke-width:1px;"/>
</g>
<g transform="matrix(0.5,0,0,0.5,0,-22.5)">
<path d="M-3.5,63.5L64,-4" style="fill:none;stroke:black;stroke-width:1px;"/>
</g>
<g transform="matrix(0.5,0,0,0.5,0,-25)">
<path d="M-3.5,63.5L64,-4" style="fill:none;stroke:black;stroke-width:1px;"/>
</g>
<g transform="matrix(0.5,0,0,0.5,0,-27.5)">
<path d="M-3.5,63.5L64,-4" style="fill:none;stroke:black;stroke-width:1px;"/>
</g>
<g transform="matrix(0.5,0,0,0.5,0,-30)">
<path d="M-3.5,63.5L64,-4" style="fill:none;stroke:black;stroke-width:1px;"/>
</g>
</svg>`.replaceAll('black', color);
};
const IAICanvasMaskCompositer = (props: IAICanvasMaskCompositerProps) => {
const { ...rest } = props;
const { maskColorString, stageCoordinates, stageDimensions, stageScale } =
useAppSelector(canvasMaskCompositerSelector);
const { stageCoordinates, stageDimensions } = useAppSelector(
canvasMaskCompositerSelector
);
const stageScale = useAppSelector((s) => s.canvas.stageScale);
const maskColorString = useAppSelector((s) =>
rgbaColorToString(s.canvas.maskColor)
);
const [fillPatternImage, setFillPatternImage] =
useState<HTMLImageElement | null>(null);
@@ -132,14 +51,14 @@ const IAICanvasMaskCompositer = (props: IAICanvasMaskCompositerProps) => {
image.onload = () => {
setFillPatternImage(image);
};
image.src = getColoredSVG(maskColorString);
image.src = getColoredMaskSVG(maskColorString);
}, [fillPatternImage, maskColorString]);
useEffect(() => {
if (!fillPatternImage) {
return;
}
fillPatternImage.src = getColoredSVG(maskColorString);
fillPatternImage.src = getColoredMaskSVG(maskColorString);
}, [fillPatternImage, maskColorString]);
useEffect(() => {

View File

@@ -1,18 +1,9 @@
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks';
import { isCanvasMaskLine } from 'features/canvas/store/canvasTypes';
import type { GroupConfig } from 'konva/lib/Group';
import { memo } from 'react';
import { Group, Line } from 'react-konva';
export const canvasLinesSelector = createMemoizedSelector(
[stateSelector],
({ canvas }) => {
return canvas.layerState.objects.filter(isCanvasMaskLine);
}
);
type InpaintingCanvasLinesProps = GroupConfig;
/**
@@ -21,7 +12,7 @@ type InpaintingCanvasLinesProps = GroupConfig;
* Uses globalCompositeOperation to handle the brush and eraser tools.
*/
const IAICanvasLines = (props: InpaintingCanvasLinesProps) => {
const objects = useAppSelector((state) => state.canvas.layerState.objects);
const objects = useAppSelector((s) => s.canvas.layerState.objects);
return (
<Group listening={false} {...props}>

View File

@@ -12,7 +12,7 @@ import { Group, Line, Rect } from 'react-konva';
import IAICanvasImage from './IAICanvasImage';
const IAICanvasObjectRenderer = () => {
const objects = useAppSelector((state) => state.canvas.layerState.objects);
const objects = useAppSelector((s) => s.canvas.layerState.objects);
return (
<Group name="outpainting-objects" listening={false}>

View File

@@ -1,6 +1,6 @@
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks';
import { selectCanvasSlice } from 'features/canvas/store/canvasSlice';
import type { GroupConfig } from 'konva/lib/Group';
import { memo } from 'react';
import { Group, Rect } from 'react-konva';
@@ -9,7 +9,7 @@ import IAICanvasImage from './IAICanvasImage';
const dash = [4, 4];
const selector = createMemoizedSelector([stateSelector], ({ canvas }) => {
const selector = createMemoizedSelector(selectCanvasSlice, (canvas) => {
const {
layerState,
shouldShowStagingImage,

View File

@@ -1,7 +1,6 @@
import { Flex } from '@chakra-ui/react';
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 { InvButton } from 'common/components/InvButton/InvButton';
import { InvButtonGroup } from 'common/components/InvButtonGroup/InvButtonGroup';
@@ -11,6 +10,7 @@ import {
discardStagedImages,
nextStagingAreaImage,
prevStagingAreaImage,
selectCanvasSlice,
setShouldShowStagingImage,
setShouldShowStagingOutline,
} from 'features/canvas/store/canvasSlice';
@@ -29,7 +29,7 @@ import {
} from 'react-icons/fa';
import { useGetImageDTOQuery } from 'services/api/endpoints/images';
const selector = createMemoizedSelector([stateSelector], ({ canvas }) => {
const selector = createMemoizedSelector(selectCanvasSlice, (canvas) => {
const {
layerState: {
stagingArea: { images, selectedImageIndex },
@@ -142,7 +142,7 @@ const IAICanvasStagingAreaToolbar = () => {
aria-label={`${t('unifiedCanvas.previous')} (Left)`}
icon={<FaArrowLeft />}
onClick={handlePrevImage}
colorScheme="blue"
colorScheme="invokeBlue"
isDisabled={!shouldShowStagingImage}
/>
<InvButton
@@ -156,7 +156,7 @@ const IAICanvasStagingAreaToolbar = () => {
aria-label={`${t('unifiedCanvas.next')} (Right)`}
icon={<FaArrowRight />}
onClick={handleNextImage}
colorScheme="blue"
colorScheme="invokeBlue"
isDisabled={!shouldShowStagingImage}
/>
</InvButtonGroup>
@@ -166,7 +166,7 @@ const IAICanvasStagingAreaToolbar = () => {
aria-label={`${t('unifiedCanvas.accept')} (Enter)`}
icon={<FaCheck />}
onClick={handleAccept}
colorScheme="blue"
colorScheme="invokeBlue"
/>
<InvIconButton
tooltip={
@@ -182,7 +182,7 @@ const IAICanvasStagingAreaToolbar = () => {
data-alert={!shouldShowStagingImage}
icon={shouldShowStagingImage ? <FaEye /> : <FaEyeSlash />}
onClick={handleToggleShouldShowStagingImage}
colorScheme="blue"
colorScheme="invokeBlue"
/>
<InvIconButton
tooltip={t('unifiedCanvas.saveToGallery')}
@@ -190,7 +190,7 @@ const IAICanvasStagingAreaToolbar = () => {
isDisabled={!imageDTO || !imageDTO.is_intermediate}
icon={<FaSave />}
onClick={handleSaveToGallery}
colorScheme="blue"
colorScheme="invokeBlue"
/>
<InvIconButton
tooltip={t('unifiedCanvas.discardAll')}

View File

@@ -1,7 +1,7 @@
import { Box, Flex } from '@chakra-ui/react';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks';
import { selectCanvasSlice } from 'features/canvas/store/canvasSlice';
import roundToHundreth from 'features/canvas/util/roundToHundreth';
import GenerationModeStatusText from 'features/parameters/components/Canvas/GenerationModeStatusText';
import { memo } from 'react';
@@ -11,7 +11,7 @@ import IAICanvasStatusTextCursorPos from './IAICanvasStatusText/IAICanvasStatusT
const warningColor = 'var(--invokeai-colors-warning-500)';
const selector = createMemoizedSelector([stateSelector], ({ canvas }) => {
const selector = createMemoizedSelector(selectCanvasSlice, (canvas) => {
const {
stageDimensions: { width: stageWidth, height: stageHeight },
stageCoordinates: { x: stageX, y: stageY },

View File

@@ -1,12 +1,12 @@
import { useStore } from '@nanostores/react';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks';
import {
$cursorPosition,
$isMovingBoundingBox,
$isTransformingBoundingBox,
} from 'features/canvas/store/canvasNanostore';
import { selectCanvasSlice } from 'features/canvas/store/canvasSlice';
import { rgbaColorToString } from 'features/canvas/util/colorToString';
import {
COLOR_PICKER_SIZE,
@@ -17,16 +17,9 @@ import { memo, useMemo } from 'react';
import { Circle, Group } from 'react-konva';
const canvasBrushPreviewSelector = createMemoizedSelector(
stateSelector,
({ canvas }) => {
selectCanvasSlice,
(canvas) => {
const {
brushSize,
colorPickerColor,
maskColor,
brushColor,
tool,
layer,
stageScale,
stageDimensions,
boundingBoxCoordinates,
boundingBoxDimensions,
@@ -82,17 +75,6 @@ const canvasBrushPreviewSelector = createMemoizedSelector(
// : undefined;
return {
radius: brushSize / 2,
colorPickerOuterRadius: COLOR_PICKER_SIZE / stageScale,
colorPickerInnerRadius:
(COLOR_PICKER_SIZE - COLOR_PICKER_STROKE_RADIUS + 1) / stageScale,
maskColorString: rgbaColorToString({ ...maskColor, a: 0.5 }),
brushColorString: rgbaColorToString(brushColor),
colorPickerColorString: rgbaColorToString(colorPickerColor),
tool,
layer,
strokeWidth: 1.5 / stageScale,
dotRadius: 1.5 / stageScale,
clip,
stageDimensions,
};
@@ -103,20 +85,28 @@ const canvasBrushPreviewSelector = createMemoizedSelector(
* Draws a black circle around the canvas brush preview.
*/
const IAICanvasToolPreview = (props: GroupConfig) => {
const {
radius,
maskColorString,
tool,
layer,
dotRadius,
strokeWidth,
brushColorString,
colorPickerColorString,
colorPickerInnerRadius,
colorPickerOuterRadius,
clip,
stageDimensions,
} = useAppSelector(canvasBrushPreviewSelector);
const radius = useAppSelector((s) => s.canvas.brushSize / 2);
const maskColorString = useAppSelector((s) =>
rgbaColorToString({ ...s.canvas.maskColor, a: 0.5 })
);
const tool = useAppSelector((s) => s.canvas.tool);
const layer = useAppSelector((s) => s.canvas.layer);
const dotRadius = useAppSelector((s) => 1.5 / s.canvas.stageScale);
const strokeWidth = useAppSelector((s) => 1.5 / s.canvas.stageScale);
const brushColorString = useAppSelector((s) =>
rgbaColorToString(s.canvas.brushColor)
);
const colorPickerColorString = useAppSelector((s) =>
rgbaColorToString(s.canvas.colorPickerColor)
);
const colorPickerInnerRadius = useAppSelector(
(s) =>
(COLOR_PICKER_SIZE - COLOR_PICKER_STROKE_RADIUS + 1) / s.canvas.stageScale
);
const colorPickerOuterRadius = useAppSelector(
(s) => COLOR_PICKER_SIZE / s.canvas.stageScale
);
const { clip, stageDimensions } = useAppSelector(canvasBrushPreviewSelector);
const cursorPosition = useStore($cursorPosition);
const isMovingBoundingBox = useStore($isMovingBoundingBox);

View File

@@ -1,6 +1,4 @@
import { useStore } from '@nanostores/react';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { $shift } from 'common/hooks/useGlobalModifiers';
import {
@@ -38,47 +36,23 @@ import { Group, Rect, Transformer } from 'react-konva';
const borderDash = [4, 4];
const boundingBoxPreviewSelector = createMemoizedSelector(
[stateSelector, selectOptimalDimension],
({ canvas }, optimalDimension) => {
const {
boundingBoxCoordinates,
boundingBoxDimensions,
stageScale,
tool,
shouldSnapToGrid,
aspectRatio,
} = canvas;
return {
boundingBoxCoordinates,
boundingBoxDimensions,
stageScale,
shouldSnapToGrid,
tool,
hitStrokeWidth: 20 / stageScale,
aspectRatio,
optimalDimension,
};
}
);
type IAICanvasBoundingBoxPreviewProps = GroupConfig;
const IAICanvasBoundingBox = (props: IAICanvasBoundingBoxPreviewProps) => {
const { ...rest } = props;
const dispatch = useAppDispatch();
const {
boundingBoxCoordinates,
boundingBoxDimensions,
stageScale,
shouldSnapToGrid,
tool,
hitStrokeWidth,
aspectRatio,
optimalDimension,
} = useAppSelector(boundingBoxPreviewSelector);
const boundingBoxCoordinates = useAppSelector(
(s) => s.canvas.boundingBoxCoordinates
);
const boundingBoxDimensions = useAppSelector(
(s) => s.canvas.boundingBoxDimensions
);
const stageScale = useAppSelector((s) => s.canvas.stageScale);
const shouldSnapToGrid = useAppSelector((s) => s.canvas.shouldSnapToGrid);
const tool = useAppSelector((s) => s.canvas.tool);
const hitStrokeWidth = useAppSelector((s) => 20 / s.canvas.stageScale);
const aspectRatio = useAppSelector((s) => s.canvas.aspectRatio);
const optimalDimension = useAppSelector(selectOptimalDimension);
const transformerRef = useRef<Konva.Transformer>(null);
const shapeRef = useRef<Konva.Rect>(null);
const shift = useStore($shift);

View File

@@ -1,6 +1,4 @@
import { Box, Flex } from '@chakra-ui/react';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAIColorPicker from 'common/components/IAIColorPicker';
import { InvButton } from 'common/components/InvButton/InvButton';
@@ -22,7 +20,6 @@ import {
setMaskColor,
setShouldPreserveMaskedArea,
} from 'features/canvas/store/canvasSlice';
import { rgbaColorToString } from 'features/canvas/util/colorToString';
import type { ChangeEvent } from 'react';
import { memo, useCallback } from 'react';
import type { RgbaColor } from 'react-colorful';
@@ -30,33 +27,16 @@ import { useHotkeys } from 'react-hotkeys-hook';
import { useTranslation } from 'react-i18next';
import { FaMask, FaSave, FaTrash } from 'react-icons/fa';
export const selector = createMemoizedSelector(
[stateSelector, isStagingSelector],
({ canvas }, isStaging) => {
const { maskColor, layer, isMaskEnabled, shouldPreserveMaskedArea } =
canvas;
return {
layer,
maskColor,
maskColorString: rgbaColorToString(maskColor),
isMaskEnabled,
shouldPreserveMaskedArea,
isStaging,
};
}
);
const IAICanvasMaskOptions = () => {
const dispatch = useAppDispatch();
const { t } = useTranslation();
const {
layer,
maskColor,
isMaskEnabled,
shouldPreserveMaskedArea,
isStaging,
} = useAppSelector(selector);
const layer = useAppSelector((s) => s.canvas.layer);
const maskColor = useAppSelector((s) => s.canvas.maskColor);
const isMaskEnabled = useAppSelector((s) => s.canvas.isMaskEnabled);
const shouldPreserveMaskedArea = useAppSelector(
(s) => s.canvas.shouldPreserveMaskedArea
);
const isStaging = useAppSelector(isStagingSelector);
useHotkeys(
['q'],

View File

@@ -1,5 +1,3 @@
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { InvIconButton } from 'common/components/InvIconButton/InvIconButton';
import { redo } from 'features/canvas/store/canvasSlice';
@@ -9,21 +7,10 @@ import { useHotkeys } from 'react-hotkeys-hook';
import { useTranslation } from 'react-i18next';
import { FaRedo } from 'react-icons/fa';
const canvasRedoSelector = createMemoizedSelector(
[stateSelector, activeTabNameSelector],
({ canvas }, activeTabName) => {
const { futureLayerStates } = canvas;
return {
canRedo: futureLayerStates.length > 0,
activeTabName,
};
}
);
const IAICanvasRedoButton = () => {
const dispatch = useAppDispatch();
const { canRedo, activeTabName } = useAppSelector(canvasRedoSelector);
const canRedo = useAppSelector((s) => s.canvas.futureLayerStates.length > 0);
const activeTabName = useAppSelector(activeTabNameSelector);
const { t } = useTranslation();

View File

@@ -1,6 +1,4 @@
import { Flex } from '@chakra-ui/react';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { InvCheckbox } from 'common/components/InvCheckbox/wrapper';
import { InvControl } from 'common/components/InvControl/InvControl';
@@ -28,50 +26,28 @@ import { useHotkeys } from 'react-hotkeys-hook';
import { useTranslation } from 'react-i18next';
import { FaWrench } from 'react-icons/fa';
export const canvasControlsSelector = createMemoizedSelector(
[stateSelector],
({ canvas }) => {
const {
shouldAutoSave,
shouldCropToBoundingBoxOnSave,
shouldDarkenOutsideBoundingBox,
shouldShowCanvasDebugInfo,
shouldShowGrid,
shouldShowIntermediates,
shouldSnapToGrid,
shouldRestrictStrokesToBox,
shouldAntialias,
} = canvas;
return {
shouldAutoSave,
shouldCropToBoundingBoxOnSave,
shouldDarkenOutsideBoundingBox,
shouldShowCanvasDebugInfo,
shouldShowGrid,
shouldShowIntermediates,
shouldSnapToGrid,
shouldRestrictStrokesToBox,
shouldAntialias,
};
}
);
const IAICanvasSettingsButtonPopover = () => {
const dispatch = useAppDispatch();
const { t } = useTranslation();
const {
shouldAutoSave,
shouldCropToBoundingBoxOnSave,
shouldDarkenOutsideBoundingBox,
shouldShowCanvasDebugInfo,
shouldShowGrid,
shouldShowIntermediates,
shouldSnapToGrid,
shouldRestrictStrokesToBox,
shouldAntialias,
} = useAppSelector(canvasControlsSelector);
const shouldAutoSave = useAppSelector((s) => s.canvas.shouldAutoSave);
const shouldCropToBoundingBoxOnSave = useAppSelector(
(s) => s.canvas.shouldCropToBoundingBoxOnSave
);
const shouldDarkenOutsideBoundingBox = useAppSelector(
(s) => s.canvas.shouldDarkenOutsideBoundingBox
);
const shouldShowCanvasDebugInfo = useAppSelector(
(s) => s.canvas.shouldShowCanvasDebugInfo
);
const shouldShowGrid = useAppSelector((s) => s.canvas.shouldShowGrid);
const shouldShowIntermediates = useAppSelector(
(s) => s.canvas.shouldShowIntermediates
);
const shouldSnapToGrid = useAppSelector((s) => s.canvas.shouldSnapToGrid);
const shouldRestrictStrokesToBox = useAppSelector(
(s) => s.canvas.shouldRestrictStrokesToBox
);
const shouldAntialias = useAppSelector((s) => s.canvas.shouldAntialias);
useHotkeys(
['n'],

View File

@@ -1,6 +1,4 @@
import { Box, Flex } from '@chakra-ui/react';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAIColorPicker from 'common/components/IAIColorPicker';
import { InvButtonGroup } from 'common/components/InvButtonGroup/InvButtonGroup';
@@ -36,23 +34,12 @@ import {
FaTimes,
} from 'react-icons/fa';
export const selector = createMemoizedSelector(
[stateSelector, isStagingSelector],
({ canvas }, isStaging) => {
const { tool, brushColor, brushSize } = canvas;
return {
tool,
isStaging,
brushColor,
brushSize,
};
}
);
const IAICanvasToolChooserOptions = () => {
const dispatch = useAppDispatch();
const { tool, brushColor, brushSize, isStaging } = useAppSelector(selector);
const tool = useAppSelector((s) => s.canvas.tool);
const brushColor = useAppSelector((s) => s.canvas.brushColor);
const brushSize = useAppSelector((s) => s.canvas.brushSize);
const isStaging = useAppSelector(isStagingSelector);
const { t } = useTranslation();
useHotkeys(

View File

@@ -1,6 +1,4 @@
import { Flex } from '@chakra-ui/react';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { InvButtonGroup } from 'common/components/InvButtonGroup/InvButtonGroup';
import { InvControl } from 'common/components/InvControl/InvControl';
@@ -48,27 +46,13 @@ import IAICanvasSettingsButtonPopover from './IAICanvasSettingsButtonPopover';
import IAICanvasToolChooserOptions from './IAICanvasToolChooserOptions';
import IAICanvasUndoButton from './IAICanvasUndoButton';
export const selector = createMemoizedSelector(
[stateSelector, isStagingSelector],
({ canvas }, isStaging) => {
const { tool, shouldCropToBoundingBoxOnSave, layer, isMaskEnabled } =
canvas;
return {
isStaging,
isMaskEnabled,
tool,
layer,
shouldCropToBoundingBoxOnSave,
};
}
);
const IAICanvasToolbar = () => {
const dispatch = useAppDispatch();
const { isStaging, isMaskEnabled, layer, tool } = useAppSelector(selector);
const isMaskEnabled = useAppSelector((s) => s.canvas.isMaskEnabled);
const layer = useAppSelector((s) => s.canvas.layer);
const tool = useAppSelector((s) => s.canvas.tool);
const isStaging = useAppSelector(isStagingSelector);
const canvasBaseLayer = getCanvasBaseLayer();
const { t } = useTranslation();
const { isClipboardAPIAvailable } = useCopyImageToClipboard();

View File

@@ -1,5 +1,3 @@
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { InvIconButton } from 'common/components/InvIconButton/InvIconButton';
import { undo } from 'features/canvas/store/canvasSlice';
@@ -9,24 +7,11 @@ import { useHotkeys } from 'react-hotkeys-hook';
import { useTranslation } from 'react-i18next';
import { FaUndo } from 'react-icons/fa';
const canvasUndoSelector = createMemoizedSelector(
[stateSelector, activeTabNameSelector],
({ canvas }, activeTabName) => {
const { pastLayerStates } = canvas;
return {
canUndo: pastLayerStates.length > 0,
activeTabName,
};
}
);
const IAICanvasUndoButton = () => {
const dispatch = useAppDispatch();
const { t } = useTranslation();
const { canUndo, activeTabName } = useAppSelector(canvasUndoSelector);
const activeTabName = useAppSelector(activeTabNameSelector);
const canUndo = useAppSelector((s) => s.canvas.pastLayerStates.length > 0);
const handleUndo = useCallback(() => {
dispatch(undo());

View File

@@ -12,7 +12,7 @@ import { useCallback } from 'react';
const useCanvasDrag = () => {
const dispatch = useAppDispatch();
const isStaging = useAppSelector(isStagingSelector);
const tool = useAppSelector((state) => state.canvas.tool);
const tool = useAppSelector((s) => s.canvas.tool);
const isMovingBoundingBox = useStore($isMovingBoundingBox);
const handleDragStart = useCallback(() => {
if (!((tool === 'move' || isStaging) && !isMovingBoundingBox)) {

View File

@@ -6,18 +6,18 @@ import { useEffect, useState } from 'react';
import { useDebounce } from 'react-use';
export const useCanvasGenerationMode = () => {
const layerState = useAppSelector((state) => state.canvas.layerState);
const layerState = useAppSelector((s) => s.canvas.layerState);
const boundingBoxCoordinates = useAppSelector(
(state) => state.canvas.boundingBoxCoordinates
(s) => s.canvas.boundingBoxCoordinates
);
const boundingBoxDimensions = useAppSelector(
(state) => state.canvas.boundingBoxDimensions
(s) => s.canvas.boundingBoxDimensions
);
const isMaskEnabled = useAppSelector((state) => state.canvas.isMaskEnabled);
const isMaskEnabled = useAppSelector((s) => s.canvas.isMaskEnabled);
const shouldPreserveMaskedArea = useAppSelector(
(state) => state.canvas.shouldPreserveMaskedArea
(s) => s.canvas.shouldPreserveMaskedArea
);
const [generationMode, setGenerationMode] = useState<
GenerationMode | undefined

View File

@@ -1,5 +1,3 @@
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import {
resetCanvasInteractionState,
@@ -16,49 +14,24 @@ import {
import type { CanvasTool } from 'features/canvas/store/canvasTypes';
import { getCanvasStage } from 'features/canvas/util/konvaInstanceProvider';
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
import { useRef } from 'react';
import { useCallback, useRef } from 'react';
import { useHotkeys } from 'react-hotkeys-hook';
const selector = createMemoizedSelector(
[stateSelector, activeTabNameSelector, isStagingSelector],
({ canvas }, activeTabName, isStaging) => {
const {
shouldLockBoundingBox,
shouldShowBoundingBox,
tool,
isMaskEnabled,
shouldSnapToGrid,
} = canvas;
return {
activeTabName,
shouldLockBoundingBox,
shouldShowBoundingBox,
tool,
isStaging,
isMaskEnabled,
shouldSnapToGrid,
};
}
);
const useInpaintingCanvasHotkeys = () => {
const dispatch = useAppDispatch();
const {
activeTabName,
shouldShowBoundingBox,
tool,
isStaging,
isMaskEnabled,
shouldSnapToGrid,
} = useAppSelector(selector);
const activeTabName = useAppSelector(activeTabNameSelector);
const shouldShowBoundingBox = useAppSelector(
(s) => s.canvas.shouldShowBoundingBox
);
const tool = useAppSelector((s) => s.canvas.tool);
const isStaging = useAppSelector(isStagingSelector);
const isMaskEnabled = useAppSelector((s) => s.canvas.isMaskEnabled);
const shouldSnapToGrid = useAppSelector((s) => s.canvas.shouldSnapToGrid);
const previousToolRef = useRef<CanvasTool | null>(null);
const canvasStage = getCanvasStage();
// Beta Keys
const handleClearMask = () => dispatch(clearMask());
const handleClearMask = useCallback(() => dispatch(clearMask()), [dispatch]);
useHotkeys(
['shift+c'],

View File

@@ -1,5 +1,3 @@
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import {
setIsDrawing,
@@ -8,7 +6,6 @@ import {
import { isStagingSelector } from 'features/canvas/store/canvasSelectors';
import { addLine } from 'features/canvas/store/canvasSlice';
import getScaledCursorPosition from 'features/canvas/util/getScaledCursorPosition';
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
import type Konva from 'konva';
import type { KonvaEventObject } from 'konva/lib/Node';
import type { MutableRefObject } from 'react';
@@ -16,21 +13,10 @@ import { useCallback } from 'react';
import useColorPicker from './useColorUnderCursor';
const selector = createMemoizedSelector(
[activeTabNameSelector, stateSelector, isStagingSelector],
(activeTabName, { canvas }, isStaging) => {
const { tool } = canvas;
return {
tool,
activeTabName,
isStaging,
};
}
);
const useCanvasMouseDown = (stageRef: MutableRefObject<Konva.Stage | null>) => {
const dispatch = useAppDispatch();
const { tool, isStaging } = useAppSelector(selector);
const tool = useAppSelector((s) => s.canvas.tool);
const isStaging = useAppSelector(isStagingSelector);
const { commitColorUnderCursor } = useColorPicker();
return useCallback(

View File

@@ -1,6 +1,4 @@
import { useStore } from '@nanostores/react';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import {
$isDrawing,
@@ -9,7 +7,6 @@ import {
import { isStagingSelector } from 'features/canvas/store/canvasSelectors';
import { addPointToCurrentLine } from 'features/canvas/store/canvasSlice';
import getScaledCursorPosition from 'features/canvas/util/getScaledCursorPosition';
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
import type Konva from 'konva';
import type { Vector2d } from 'konva/lib/types';
import type { MutableRefObject } from 'react';
@@ -17,18 +14,6 @@ import { useCallback } from 'react';
import useColorPicker from './useColorUnderCursor';
const selector = createMemoizedSelector(
[activeTabNameSelector, stateSelector, isStagingSelector],
(activeTabName, { canvas }, isStaging) => {
const { tool } = canvas;
return {
tool,
activeTabName,
isStaging,
};
}
);
const useCanvasMouseMove = (
stageRef: MutableRefObject<Konva.Stage | null>,
didMouseMoveRef: MutableRefObject<boolean>,
@@ -36,7 +21,8 @@ const useCanvasMouseMove = (
) => {
const dispatch = useAppDispatch();
const isDrawing = useStore($isDrawing);
const { tool, isStaging } = useAppSelector(selector);
const tool = useAppSelector((s) => s.canvas.tool);
const isStaging = useAppSelector(isStagingSelector);
const { updateColorUnderCursor } = useColorPicker();
return useCallback(() => {

View File

@@ -1,6 +1,4 @@
import { useStore } from '@nanostores/react';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import {
$isDrawing,
@@ -8,32 +6,20 @@ import {
setIsMovingStage,
} from 'features/canvas/store/canvasNanostore';
import { isStagingSelector } from 'features/canvas/store/canvasSelectors';
import {
// addPointToCurrentEraserLine,
addPointToCurrentLine,
} from 'features/canvas/store/canvasSlice';
import { addPointToCurrentLine } from 'features/canvas/store/canvasSlice';
import getScaledCursorPosition from 'features/canvas/util/getScaledCursorPosition';
import type Konva from 'konva';
import type { MutableRefObject } from 'react';
import { useCallback } from 'react';
const selector = createMemoizedSelector(
[stateSelector, isStagingSelector],
({ canvas }, isStaging) => {
return {
tool: canvas.tool,
isStaging,
};
}
);
const useCanvasMouseUp = (
stageRef: MutableRefObject<Konva.Stage | null>,
didMouseMoveRef: MutableRefObject<boolean>
) => {
const dispatch = useAppDispatch();
const isDrawing = useStore($isDrawing);
const { tool, isStaging } = useAppSelector(selector);
const tool = useAppSelector((s) => s.canvas.tool);
const isStaging = useAppSelector(isStagingSelector);
return useCallback(() => {
if (tool === 'move' || isStaging) {

View File

@@ -1,6 +1,4 @@
import { useStore } from '@nanostores/react';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { $isMoveStageKeyHeld } from 'features/canvas/store/canvasNanostore';
import {
@@ -18,14 +16,9 @@ import { clamp } from 'lodash-es';
import type { MutableRefObject } from 'react';
import { useCallback } from 'react';
const selector = createMemoizedSelector(
[stateSelector],
(state) => state.canvas.stageScale
);
const useCanvasWheel = (stageRef: MutableRefObject<Konva.Stage | null>) => {
const dispatch = useAppDispatch();
const stageScale = useAppSelector(selector);
const stageScale = useAppSelector((s) => s.canvas.stageScale);
const isMoveStageKeyHeld = useStore($isMoveStageKeyHeld);
return useCallback(

View File

@@ -1,18 +1,17 @@
import { createSelector } from '@reduxjs/toolkit';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import type { RootState } from 'app/store/store';
import { stateSelector } from 'app/store/store';
import type { CanvasImage } from './canvasTypes';
import { selectCanvasSlice } from './canvasSlice';
import { isCanvasBaseImage } from './canvasTypes';
export const isStagingSelector = createMemoizedSelector(
[stateSelector],
({ canvas }) =>
export const isStagingSelector = createSelector(
selectCanvasSlice,
(canvas) =>
canvas.batchIds.length > 0 ||
canvas.layerState.stagingArea.images.length > 0
);
export const initialCanvasImageSelector = (
state: RootState
): CanvasImage | undefined =>
state.canvas.layerState.objects.find(isCanvasBaseImage);
export const initialCanvasImageSelector = createMemoizedSelector(
selectCanvasSlice,
(canvas) => canvas.layerState.objects.find(isCanvasBaseImage)
);

View File

@@ -1,5 +1,6 @@
import type { PayloadAction } from '@reduxjs/toolkit';
import { createSlice } from '@reduxjs/toolkit';
import type { RootState } from 'app/store/store';
import {
roundDownToMultiple,
roundToMultiple,
@@ -781,3 +782,5 @@ export const {
} = canvasSlice.actions;
export default canvasSlice.reducer;
export const selectCanvasSlice = (state: RootState) => state.canvas;

View File

@@ -0,0 +1,81 @@
export const getColoredMaskSVG = (color: string) => {
return `data:image/svg+xml;utf8,<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="60px" height="60px" viewBox="0 0 30 30" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.5;">
<g transform="matrix(0.5,0,0,0.5,0,0)">
<path d="M-3.5,63.5L64,-4" style="fill:none;stroke:black;stroke-width:1px;"/>
</g>
<g transform="matrix(0.5,0,0,0.5,0,2.5)">
<path d="M-3.5,63.5L64,-4" style="fill:none;stroke:black;stroke-width:1px;"/>
</g>
<g transform="matrix(0.5,0,0,0.5,0,5)">
<path d="M-3.5,63.5L64,-4" style="fill:none;stroke:black;stroke-width:1px;"/>
</g>
<g transform="matrix(0.5,0,0,0.5,0,7.5)">
<path d="M-3.5,63.5L64,-4" style="fill:none;stroke:black;stroke-width:1px;"/>
</g>
<g transform="matrix(0.5,0,0,0.5,0,10)">
<path d="M-3.5,63.5L64,-4" style="fill:none;stroke:black;stroke-width:1px;"/>
</g>
<g transform="matrix(0.5,0,0,0.5,0,12.5)">
<path d="M-3.5,63.5L64,-4" style="fill:none;stroke:black;stroke-width:1px;"/>
</g>
<g transform="matrix(0.5,0,0,0.5,0,15)">
<path d="M-3.5,63.5L64,-4" style="fill:none;stroke:black;stroke-width:1px;"/>
</g>
<g transform="matrix(0.5,0,0,0.5,0,17.5)">
<path d="M-3.5,63.5L64,-4" style="fill:none;stroke:black;stroke-width:1px;"/>
</g>
<g transform="matrix(0.5,0,0,0.5,0,20)">
<path d="M-3.5,63.5L64,-4" style="fill:none;stroke:black;stroke-width:1px;"/>
</g>
<g transform="matrix(0.5,0,0,0.5,0,22.5)">
<path d="M-3.5,63.5L64,-4" style="fill:none;stroke:black;stroke-width:1px;"/>
</g>
<g transform="matrix(0.5,0,0,0.5,0,25)">
<path d="M-3.5,63.5L64,-4" style="fill:none;stroke:black;stroke-width:1px;"/>
</g>
<g transform="matrix(0.5,0,0,0.5,0,27.5)">
<path d="M-3.5,63.5L64,-4" style="fill:none;stroke:black;stroke-width:1px;"/>
</g>
<g transform="matrix(0.5,0,0,0.5,0,30)">
<path d="M-3.5,63.5L64,-4" style="fill:none;stroke:black;stroke-width:1px;"/>
</g>
<g transform="matrix(0.5,0,0,0.5,0,-2.5)">
<path d="M-3.5,63.5L64,-4" style="fill:none;stroke:black;stroke-width:1px;"/>
</g>
<g transform="matrix(0.5,0,0,0.5,0,-5)">
<path d="M-3.5,63.5L64,-4" style="fill:none;stroke:black;stroke-width:1px;"/>
</g>
<g transform="matrix(0.5,0,0,0.5,0,-7.5)">
<path d="M-3.5,63.5L64,-4" style="fill:none;stroke:black;stroke-width:1px;"/>
</g>
<g transform="matrix(0.5,0,0,0.5,0,-10)">
<path d="M-3.5,63.5L64,-4" style="fill:none;stroke:black;stroke-width:1px;"/>
</g>
<g transform="matrix(0.5,0,0,0.5,0,-12.5)">
<path d="M-3.5,63.5L64,-4" style="fill:none;stroke:black;stroke-width:1px;"/>
</g>
<g transform="matrix(0.5,0,0,0.5,0,-15)">
<path d="M-3.5,63.5L64,-4" style="fill:none;stroke:black;stroke-width:1px;"/>
</g>
<g transform="matrix(0.5,0,0,0.5,0,-17.5)">
<path d="M-3.5,63.5L64,-4" style="fill:none;stroke:black;stroke-width:1px;"/>
</g>
<g transform="matrix(0.5,0,0,0.5,0,-20)">
<path d="M-3.5,63.5L64,-4" style="fill:none;stroke:black;stroke-width:1px;"/>
</g>
<g transform="matrix(0.5,0,0,0.5,0,-22.5)">
<path d="M-3.5,63.5L64,-4" style="fill:none;stroke:black;stroke-width:1px;"/>
</g>
<g transform="matrix(0.5,0,0,0.5,0,-25)">
<path d="M-3.5,63.5L64,-4" style="fill:none;stroke:black;stroke-width:1px;"/>
</g>
<g transform="matrix(0.5,0,0,0.5,0,-27.5)">
<path d="M-3.5,63.5L64,-4" style="fill:none;stroke:black;stroke-width:1px;"/>
</g>
<g transform="matrix(0.5,0,0,0.5,0,-30)">
<path d="M-3.5,63.5L64,-4" style="fill:none;stroke:black;stroke-width:1px;"/>
</g>
</svg>`.replaceAll('black', color);
};

View File

@@ -1,6 +1,5 @@
import { Flex } from '@chakra-ui/react';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { InvConfirmationAlertDialog } from 'common/components/InvConfirmationAlertDialog/InvConfirmationAlertDialog';
import { InvControl } from 'common/components/InvControl/InvControl';
@@ -13,6 +12,7 @@ import { InvText } from 'common/components/InvText/wrapper';
import {
changeBoardReset,
isModalOpenChanged,
selectChangeBoardModalSlice,
} from 'features/changeBoardModal/store/slice';
import { memo, useCallback, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
@@ -22,23 +22,17 @@ import {
useRemoveImagesFromBoardMutation,
} from 'services/api/endpoints/images';
const selector = createMemoizedSelector(
[stateSelector],
({ changeBoardModal }) => {
const { isModalOpen, imagesToChange } = changeBoardModal;
return {
isModalOpen,
imagesToChange,
};
}
const selectImagesToChange = createMemoizedSelector(
selectChangeBoardModalSlice,
(changeBoardModal) => changeBoardModal.imagesToChange
);
const ChangeBoardModal = () => {
const dispatch = useAppDispatch();
const [selectedBoard, setSelectedBoard] = useState<string | null>();
const { data: boards, isFetching } = useListAllBoardsQuery();
const { imagesToChange, isModalOpen } = useAppSelector(selector);
const isModalOpen = useAppSelector((s) => s.changeBoardModal.isModalOpen);
const imagesToChange = useAppSelector(selectImagesToChange);
const [addImagesToBoard] = useAddImagesToBoardMutation();
const [removeImagesFromBoard] = useRemoveImagesFromBoardMutation();
const { t } = useTranslation();

View File

@@ -1,5 +1,6 @@
import type { PayloadAction } from '@reduxjs/toolkit';
import { createSlice } from '@reduxjs/toolkit';
import type { RootState } from 'app/store/store';
import type { ImageDTO } from 'services/api/types';
import { initialState } from './initialState';
@@ -25,3 +26,6 @@ export const { isModalOpenChanged, imagesToChangeSelected, changeBoardReset } =
changeBoardModal.actions;
export default changeBoardModal.reducer;
export const selectChangeBoardModalSlice = (state: RootState) =>
state.changeBoardModal;

View File

@@ -2,7 +2,6 @@ import type { SystemStyleObject } from '@chakra-ui/react';
import { Box, Flex, Spinner } from '@chakra-ui/react';
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 IAIDndImage from 'common/components/IAIDndImage';
import IAIDndImageIcon from 'common/components/IAIDndImageIcon';
@@ -10,7 +9,10 @@ import { setBoundingBoxDimensions } from 'features/canvas/store/canvasSlice';
import { useControlAdapterControlImage } from 'features/controlAdapters/hooks/useControlAdapterControlImage';
import { useControlAdapterProcessedControlImage } from 'features/controlAdapters/hooks/useControlAdapterProcessedControlImage';
import { useControlAdapterProcessorType } from 'features/controlAdapters/hooks/useControlAdapterProcessorType';
import { controlAdapterImageChanged } from 'features/controlAdapters/store/controlAdaptersSlice';
import {
controlAdapterImageChanged,
selectControlAdaptersSlice,
} from 'features/controlAdapters/store/controlAdaptersSlice';
import type {
TypesafeDraggableData,
TypesafeDroppableData,
@@ -37,36 +39,22 @@ type Props = {
isSmall?: boolean;
};
const selector = createMemoizedSelector(
[stateSelector, activeTabNameSelector, selectOptimalDimension],
({ controlAdapters, gallery, system }, activeTabName, optimalDimension) => {
const { pendingControlImages } = controlAdapters;
const { autoAddBoardId } = gallery;
const { isConnected } = system;
return {
pendingControlImages,
autoAddBoardId,
isConnected,
activeTabName,
optimalDimension,
};
}
const selectPendingControlImages = createMemoizedSelector(
selectControlAdaptersSlice,
(controlAdapters) => controlAdapters.pendingControlImages
);
const ControlAdapterImagePreview = ({ isSmall, id }: Props) => {
const { t } = useTranslation();
const dispatch = useAppDispatch();
const controlImageName = useControlAdapterControlImage(id);
const processedControlImageName = useControlAdapterProcessedControlImage(id);
const processorType = useControlAdapterProcessorType(id);
const dispatch = useAppDispatch();
const { t } = useTranslation();
const {
pendingControlImages,
autoAddBoardId,
isConnected,
activeTabName,
optimalDimension,
} = useAppSelector(selector);
const autoAddBoardId = useAppSelector((s) => s.gallery.autoAddBoardId);
const isConnected = useAppSelector((s) => s.system.isConnected);
const activeTabName = useAppSelector(activeTabNameSelector);
const optimalDimension = useAppSelector(selectOptimalDimension);
const pendingControlImages = useAppSelector(selectPendingControlImages);
const [isMouseOverImage, setIsMouseOverImage] = useState(false);

View File

@@ -1,5 +1,4 @@
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { InvControl } from 'common/components/InvControl/InvControl';
import { InvSelect } from 'common/components/InvSelect/InvSelect';
@@ -10,6 +9,7 @@ import { useControlAdapterModel } from 'features/controlAdapters/hooks/useContro
import { useControlAdapterModelEntities } from 'features/controlAdapters/hooks/useControlAdapterModelEntities';
import { useControlAdapterType } from 'features/controlAdapters/hooks/useControlAdapterType';
import { controlAdapterModelChanged } from 'features/controlAdapters/store/controlAdaptersSlice';
import { selectGenerationSlice } from 'features/parameters/store/generationSlice';
import { pick } from 'lodash-es';
import { memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
@@ -24,10 +24,10 @@ type ParamControlAdapterModelProps = {
id: string;
};
const selector = createMemoizedSelector(stateSelector, ({ generation }) => {
const { model } = generation;
return { mainModel: model };
});
const selectMainModel = createMemoizedSelector(
selectGenerationSlice,
(generation) => generation.model
);
const ParamControlAdapterModel = ({ id }: ParamControlAdapterModelProps) => {
const isEnabled = useControlAdapterIsEnabled(id);
@@ -35,9 +35,9 @@ const ParamControlAdapterModel = ({ id }: ParamControlAdapterModelProps) => {
const model = useControlAdapterModel(id);
const dispatch = useAppDispatch();
const currentBaseModel = useAppSelector(
(state) => state.generation.model?.base_model
(s) => s.generation.model?.base_model
);
const { mainModel } = useAppSelector(selector);
const mainModel = useAppSelector(selectMainModel);
const { t } = useTranslation();
const models = useControlAdapterModelEntities(controlAdapterType);

View File

@@ -20,7 +20,7 @@ type Props = {
id: string;
};
const selector = createMemoizedSelector(configSelector, (config) => {
const selectOptions = createMemoizedSelector(configSelector, (config) => {
const options: InvSelectOption[] = map(CONTROLNET_PROCESSORS, (p) => ({
value: p.type,
label: p.label,
@@ -47,7 +47,7 @@ const ParamControlAdapterProcessorSelect = ({ id }: Props) => {
const isEnabled = useControlAdapterIsEnabled(id);
const processorNode = useControlAdapterProcessorNode(id);
const dispatch = useAppDispatch();
const options = useAppSelector(selector);
const options = useAppSelector(selectOptions);
const { t } = useTranslation();
const onChange = useCallback<InvSelectOnChange>(

View File

@@ -6,9 +6,7 @@ import { useCallback, useMemo } from 'react';
import { useControlAdapterModels } from './useControlAdapterModels';
export const useAddControlAdapter = (type: ControlAdapterType) => {
const baseModel = useAppSelector(
(state) => state.generation.model?.base_model
);
const baseModel = useAppSelector((s) => s.generation.model?.base_model);
const dispatch = useAppDispatch();
const models = useControlAdapterModels(type);

View File

@@ -1,13 +1,15 @@
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks';
import { selectControlAdapterById } from 'features/controlAdapters/store/controlAdaptersSlice';
import {
selectControlAdapterById,
selectControlAdaptersSlice,
} from 'features/controlAdapters/store/controlAdaptersSlice';
import { useMemo } from 'react';
export const useControlAdapter = (id: string) => {
const selector = useMemo(
() =>
createMemoizedSelector(stateSelector, ({ controlAdapters }) =>
createMemoizedSelector(selectControlAdaptersSlice, (controlAdapters) =>
selectControlAdapterById(controlAdapters, id)
),
[id]

View File

@@ -1,13 +1,15 @@
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks';
import { selectControlAdapterById } from 'features/controlAdapters/store/controlAdaptersSlice';
import {
selectControlAdapterById,
selectControlAdaptersSlice,
} from 'features/controlAdapters/store/controlAdaptersSlice';
import { useMemo } from 'react';
export const useControlAdapterBeginEndStepPct = (id: string) => {
const selector = useMemo(
() =>
createMemoizedSelector(stateSelector, ({ controlAdapters }) => {
createMemoizedSelector(selectControlAdaptersSlice, (controlAdapters) => {
const cn = selectControlAdapterById(controlAdapters, id);
return cn
? {

View File

@@ -1,21 +1,23 @@
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/store/storeHooks';
import { selectControlAdapterById } from 'features/controlAdapters/store/controlAdaptersSlice';
import {
selectControlAdapterById,
selectControlAdaptersSlice,
} from 'features/controlAdapters/store/controlAdaptersSlice';
import { useMemo } from 'react';
export const useControlAdapterControlImage = (id: string) => {
const selector = useMemo(
() =>
createMemoizedSelector(
stateSelector,
({ controlAdapters }) =>
createSelector(
selectControlAdaptersSlice,
(controlAdapters) =>
selectControlAdapterById(controlAdapters, id)?.controlImage
),
[id]
);
const weight = useAppSelector(selector);
const controlImageName = useAppSelector(selector);
return weight;
return controlImageName;
};

View File

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

View File

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

View File

@@ -1,15 +1,17 @@
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks';
import { selectControlAdapterById } from 'features/controlAdapters/store/controlAdaptersSlice';
import {
selectControlAdapterById,
selectControlAdaptersSlice,
} from 'features/controlAdapters/store/controlAdaptersSlice';
import { useMemo } from 'react';
export const useControlAdapterModel = (id: string) => {
const selector = useMemo(
() =>
createMemoizedSelector(
stateSelector,
({ controlAdapters }) =>
selectControlAdaptersSlice,
(controlAdapters) =>
selectControlAdapterById(controlAdapters, id)?.model
),
[id]

View File

@@ -1,9 +1,9 @@
import type { ControlAdapterType } from 'features/controlAdapters/store/types';
import { useMemo } from 'react';
import {
controlNetModelsAdapter,
ipAdapterModelsAdapter,
t2iAdapterModelsAdapter,
controlNetModelsAdapterSelectors,
ipAdapterModelsAdapterSelectors,
t2iAdapterModelsAdapterSelectors,
useGetControlNetModelsQuery,
useGetIPAdapterModelsQuery,
useGetT2IAdapterModelsQuery,
@@ -14,7 +14,7 @@ export const useControlAdapterModels = (type?: ControlAdapterType) => {
const controlNetModels = useMemo(
() =>
controlNetModelsData
? controlNetModelsAdapter.getSelectors().selectAll(controlNetModelsData)
? controlNetModelsAdapterSelectors.selectAll(controlNetModelsData)
: [],
[controlNetModelsData]
);
@@ -23,7 +23,7 @@ export const useControlAdapterModels = (type?: ControlAdapterType) => {
const t2iAdapterModels = useMemo(
() =>
t2iAdapterModelsData
? t2iAdapterModelsAdapter.getSelectors().selectAll(t2iAdapterModelsData)
? t2iAdapterModelsAdapterSelectors.selectAll(t2iAdapterModelsData)
: [],
[t2iAdapterModelsData]
);
@@ -31,7 +31,7 @@ export const useControlAdapterModels = (type?: ControlAdapterType) => {
const ipAdapterModels = useMemo(
() =>
ipAdapterModelsData
? ipAdapterModelsAdapter.getSelectors().selectAll(ipAdapterModelsData)
? ipAdapterModelsAdapterSelectors.selectAll(ipAdapterModelsData)
: [],
[ipAdapterModelsData]
);

View File

@@ -1,14 +1,16 @@
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/store/storeHooks';
import { selectControlAdapterById } from 'features/controlAdapters/store/controlAdaptersSlice';
import {
selectControlAdapterById,
selectControlAdaptersSlice,
} from 'features/controlAdapters/store/controlAdaptersSlice';
import { isControlNetOrT2IAdapter } from 'features/controlAdapters/store/types';
import { useMemo } from 'react';
export const useControlAdapterProcessedControlImage = (id: string) => {
const selector = useMemo(
() =>
createMemoizedSelector(stateSelector, ({ controlAdapters }) => {
createSelector(selectControlAdaptersSlice, (controlAdapters) => {
const ca = selectControlAdapterById(controlAdapters, id);
return ca && isControlNetOrT2IAdapter(ca)

View File

@@ -1,14 +1,16 @@
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks';
import { selectControlAdapterById } from 'features/controlAdapters/store/controlAdaptersSlice';
import {
selectControlAdapterById,
selectControlAdaptersSlice,
} from 'features/controlAdapters/store/controlAdaptersSlice';
import { isControlNetOrT2IAdapter } from 'features/controlAdapters/store/types';
import { useMemo } from 'react';
export const useControlAdapterProcessorNode = (id: string) => {
const selector = useMemo(
() =>
createMemoizedSelector(stateSelector, ({ controlAdapters }) => {
createMemoizedSelector(selectControlAdaptersSlice, (controlAdapters) => {
const ca = selectControlAdapterById(controlAdapters, id);
return ca && isControlNetOrT2IAdapter(ca)

View File

@@ -1,14 +1,16 @@
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/store/storeHooks';
import { selectControlAdapterById } from 'features/controlAdapters/store/controlAdaptersSlice';
import {
selectControlAdapterById,
selectControlAdaptersSlice,
} from 'features/controlAdapters/store/controlAdaptersSlice';
import { isControlNetOrT2IAdapter } from 'features/controlAdapters/store/types';
import { useMemo } from 'react';
export const useControlAdapterProcessorType = (id: string) => {
const selector = useMemo(
() =>
createMemoizedSelector(stateSelector, ({ controlAdapters }) => {
createSelector(selectControlAdaptersSlice, (controlAdapters) => {
const ca = selectControlAdapterById(controlAdapters, id);
return ca && isControlNetOrT2IAdapter(ca)

View File

@@ -1,14 +1,16 @@
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks';
import { selectControlAdapterById } from 'features/controlAdapters/store/controlAdaptersSlice';
import {
selectControlAdapterById,
selectControlAdaptersSlice,
} from 'features/controlAdapters/store/controlAdaptersSlice';
import { isControlNetOrT2IAdapter } from 'features/controlAdapters/store/types';
import { useMemo } from 'react';
export const useControlAdapterResizeMode = (id: string) => {
const selector = useMemo(
() =>
createMemoizedSelector(stateSelector, ({ controlAdapters }) => {
createMemoizedSelector(selectControlAdaptersSlice, (controlAdapters) => {
const ca = selectControlAdapterById(controlAdapters, id);
if (ca && isControlNetOrT2IAdapter(ca)) {
return ca.resizeMode;

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,7 @@
import type { PayloadAction, Update } from '@reduxjs/toolkit';
import { createEntityAdapter, createSlice, isAnyOf } from '@reduxjs/toolkit';
import { getSelectorsOptions } from 'app/store/createMemoizedSelector';
import type { RootState } from 'app/store/store';
import { buildControlAdapter } from 'features/controlAdapters/util/buildControlAdapter';
import type {
ParameterControlNetModel,
@@ -36,6 +38,10 @@ import {
export const caAdapter = createEntityAdapter<ControlAdapterConfig, string>({
selectId: (ca) => ca.id,
});
export const caAdapterSelectors = caAdapter.getSelectors(
undefined,
getSelectorsOptions
);
export const {
selectById: selectControlAdapterById,
@@ -43,7 +49,7 @@ export const {
selectEntities: selectControlAdapterEntities,
selectIds: selectControlAdapterIds,
selectTotal: selectControlAdapterTotal,
} = caAdapter.getSelectors();
} = caAdapterSelectors;
export const initialControlAdapterState: ControlAdaptersState =
caAdapter.getInitialState<{
@@ -484,3 +490,6 @@ export const isAnyControlAdapterAdded = isAnyOf(
controlAdapterAddedFromImage,
controlAdapterRecalled
);
export const selectControlAdaptersSlice = (state: RootState) =>
state.controlAdapters;

View File

@@ -12,7 +12,7 @@ type DeleteImageButtonProps = Omit<InvIconButtonProps, 'aria-label'> & {
export const DeleteImageButton = memo((props: DeleteImageButtonProps) => {
const { onClick, isDisabled } = props;
const { t } = useTranslation();
const isConnected = useAppSelector((state) => state.system.isConnected);
const isConnected = useAppSelector((s) => s.system.isConnected);
return (
<InvIconButton

View File

@@ -1,11 +1,12 @@
import { Divider, Flex } from '@chakra-ui/react';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { InvConfirmationAlertDialog } from 'common/components/InvConfirmationAlertDialog/InvConfirmationAlertDialog';
import { InvControl } from 'common/components/InvControl/InvControl';
import { InvSwitch } from 'common/components/InvSwitch/wrapper';
import { InvText } from 'common/components/InvText/wrapper';
import { selectCanvasSlice } from 'features/canvas/store/canvasSlice';
import { selectControlAdaptersSlice } from 'features/controlAdapters/store/controlAdaptersSlice';
import { imageDeletionConfirmed } from 'features/deleteImageModal/store/actions';
import {
getImageUsage,
@@ -14,8 +15,11 @@ import {
import {
imageDeletionCanceled,
isModalOpenChanged,
selectDeleteImageModalSlice,
} from 'features/deleteImageModal/store/slice';
import type { ImageUsage } from 'features/deleteImageModal/store/types';
import { selectNodesSlice } from 'features/nodes/store/nodesSlice';
import { selectGenerationSlice } from 'features/parameters/store/generationSlice';
import { setShouldConfirmOnDelete } from 'features/system/store/systemSlice';
import { some } from 'lodash-es';
import type { ChangeEvent } from 'react';
@@ -24,16 +28,27 @@ import { useTranslation } from 'react-i18next';
import ImageUsageMessage from './ImageUsageMessage';
const selector = createMemoizedSelector(
[stateSelector, selectImageUsage],
(state, imagesUsage) => {
const { system, config, deleteImageModal } = state;
const { shouldConfirmOnDelete } = system;
const { canRestoreDeletedImagesFromBin } = config;
const { imagesToDelete, isModalOpen } = deleteImageModal;
const selectImageUsages = createMemoizedSelector(
[
selectDeleteImageModalSlice,
selectGenerationSlice,
selectCanvasSlice,
selectNodesSlice,
selectControlAdaptersSlice,
selectImageUsage,
],
(
deleteImageModal,
generation,
canvas,
nodes,
controlAdapters,
imagesUsage
) => {
const { imagesToDelete } = deleteImageModal;
const allImageUsage = (imagesToDelete ?? []).map(({ image_name }) =>
getImageUsage(state, image_name)
getImageUsage(generation, canvas, nodes, controlAdapters, image_name)
);
const imageUsageSummary: ImageUsage = {
@@ -44,11 +59,8 @@ const selector = createMemoizedSelector(
};
return {
shouldConfirmOnDelete,
canRestoreDeletedImagesFromBin,
imagesToDelete,
imagesUsage,
isModalOpen,
imageUsageSummary,
};
}
@@ -57,15 +69,15 @@ const selector = createMemoizedSelector(
const DeleteImageModal = () => {
const dispatch = useAppDispatch();
const { t } = useTranslation();
const {
shouldConfirmOnDelete,
canRestoreDeletedImagesFromBin,
imagesToDelete,
imagesUsage,
isModalOpen,
imageUsageSummary,
} = useAppSelector(selector);
const shouldConfirmOnDelete = useAppSelector(
(s) => s.system.shouldConfirmOnDelete
);
const canRestoreDeletedImagesFromBin = useAppSelector(
(s) => s.config.canRestoreDeletedImagesFromBin
);
const isModalOpen = useAppSelector((s) => s.deleteImageModal.isModalOpen);
const { imagesToDelete, imagesUsage, imageUsageSummary } =
useAppSelector(selectImageUsages);
const handleChangeShouldConfirmOnDelete = useCallback(
(e: ChangeEvent<HTMLInputElement>) =>

View File

@@ -1,15 +1,30 @@
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import type { RootState } from 'app/store/store';
import { selectControlAdapterAll } from 'features/controlAdapters/store/controlAdaptersSlice';
import { selectCanvasSlice } from 'features/canvas/store/canvasSlice';
import type { CanvasState } from 'features/canvas/store/canvasTypes';
import {
selectControlAdapterAll,
selectControlAdaptersSlice,
} from 'features/controlAdapters/store/controlAdaptersSlice';
import type { ControlAdaptersState } from 'features/controlAdapters/store/types';
import { isControlNetOrT2IAdapter } from 'features/controlAdapters/store/types';
import { selectDeleteImageModalSlice } from 'features/deleteImageModal/store/slice';
import { selectNodesSlice } from 'features/nodes/store/nodesSlice';
import type { NodesState } from 'features/nodes/store/types';
import { isImageFieldInputInstance } from 'features/nodes/types/field';
import { isInvocationNode } from 'features/nodes/types/invocation';
import { selectGenerationSlice } from 'features/parameters/store/generationSlice';
import type { GenerationState } from 'features/parameters/store/types';
import { some } from 'lodash-es';
import type { ImageUsage } from './types';
export const getImageUsage = (state: RootState, image_name: string) => {
const { generation, canvas, nodes, controlAdapters } = state;
export const getImageUsage = (
generation: GenerationState,
canvas: CanvasState,
nodes: NodesState,
controlAdapters: ControlAdaptersState,
image_name: string
) => {
const isInitialImage = generation.initialImage?.imageName === image_name;
const isCanvasImage = canvas.layerState.objects.some(
@@ -42,16 +57,20 @@ export const getImageUsage = (state: RootState, image_name: string) => {
};
export const selectImageUsage = createMemoizedSelector(
[(state: RootState) => state],
(state) => {
const { imagesToDelete } = state.deleteImageModal;
selectDeleteImageModalSlice,
selectGenerationSlice,
selectCanvasSlice,
selectNodesSlice,
selectControlAdaptersSlice,
(deleteImageModal, generation, canvas, nodes, controlAdapters) => {
const { imagesToDelete } = deleteImageModal;
if (!imagesToDelete.length) {
return [];
}
const imagesUsage = imagesToDelete.map((i) =>
getImageUsage(state, i.image_name)
getImageUsage(generation, canvas, nodes, controlAdapters, i.image_name)
);
return imagesUsage;

View File

@@ -1,5 +1,6 @@
import type { PayloadAction } from '@reduxjs/toolkit';
import { createSlice } from '@reduxjs/toolkit';
import type { RootState } from 'app/store/store';
import type { ImageDTO } from 'services/api/types';
import { initialDeleteImageState } from './initialState';
@@ -28,3 +29,6 @@ export const {
} = deleteImageModal.actions;
export default deleteImageModal.reducer;
export const selectDeleteImageModalSlice = (state: RootState) =>
state.deleteImageModal;

View File

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

View File

@@ -1,5 +1,3 @@
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { InvControl } from 'common/components/InvControl/InvControl';
import { InvSwitch } from 'common/components/InvSwitch/wrapper';
@@ -7,14 +5,8 @@ import { combinatorialToggled } from 'features/dynamicPrompts/store/dynamicPromp
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
const selector = createMemoizedSelector(stateSelector, (state) => {
const { combinatorial } = state.dynamicPrompts;
return { combinatorial };
});
const ParamDynamicPromptsCombinatorial = () => {
const { combinatorial } = useAppSelector(selector);
const combinatorial = useAppSelector((s) => s.dynamicPrompts.combinatorial);
const dispatch = useAppDispatch();
const { t } = useTranslation();

View File

@@ -1,5 +1,3 @@
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { InvControl } from 'common/components/InvControl/InvControl';
import { InvSlider } from 'common/components/InvSlider/InvSlider';
@@ -7,24 +5,19 @@ import { maxPromptsChanged } from 'features/dynamicPrompts/store/dynamicPromptsS
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
const selector = createMemoizedSelector(stateSelector, (state) => {
const { maxPrompts, combinatorial } = state.dynamicPrompts;
const { min, sliderMax, inputMax, initial } =
state.config.sd.dynamicPrompts.maxPrompts;
return {
maxPrompts,
min,
sliderMax,
inputMax,
initial,
isDisabled: !combinatorial,
};
});
const ParamDynamicPromptsMaxPrompts = () => {
const { maxPrompts, min, sliderMax, inputMax, initial, isDisabled } =
useAppSelector(selector);
const maxPrompts = useAppSelector((s) => s.dynamicPrompts.maxPrompts);
const min = useAppSelector((s) => s.config.sd.dynamicPrompts.maxPrompts.min);
const sliderMax = useAppSelector(
(s) => s.config.sd.dynamicPrompts.maxPrompts.sliderMax
);
const inputMax = useAppSelector(
(s) => s.config.sd.dynamicPrompts.maxPrompts.inputMax
);
const initial = useAppSelector(
(s) => s.config.sd.dynamicPrompts.maxPrompts.initial
);
const isDisabled = useAppSelector((s) => !s.dynamicPrompts.combinatorial);
const dispatch = useAppDispatch();
const { t } = useTranslation();

View File

@@ -1,26 +1,20 @@
import type { ChakraProps } from '@chakra-ui/react';
import { Flex, ListItem, OrderedList, Spinner } from '@chakra-ui/react';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks';
import { IAINoContentFallback } from 'common/components/IAIImageFallback';
import IAIInformationalPopover from 'common/components/IAIInformationalPopover/IAIInformationalPopover';
import { InvText } from 'common/components/InvText/wrapper';
import ScrollableContent from 'common/components/OverlayScrollbars/ScrollableContent';
import { selectDynamicPromptsSlice } from 'features/dynamicPrompts/store/dynamicPromptsSlice';
import { memo, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { FaCircleExclamation } from 'react-icons/fa6';
const selector = createMemoizedSelector(stateSelector, (state) => {
const { isLoading, isError, prompts, parsingError } = state.dynamicPrompts;
return {
prompts,
parsingError,
isError,
isLoading,
};
});
const selectPrompts = createMemoizedSelector(
selectDynamicPromptsSlice,
(dynamicPrompts) => dynamicPrompts.prompts
);
const listItemStyles: ChakraProps['sx'] = {
'&::marker': { color: 'base.500' },
@@ -28,8 +22,10 @@ const listItemStyles: ChakraProps['sx'] = {
const ParamDynamicPromptsPreview = () => {
const { t } = useTranslation();
const { prompts, parsingError, isLoading, isError } =
useAppSelector(selector);
const parsingError = useAppSelector((s) => s.dynamicPrompts.parsingError);
const isError = useAppSelector((s) => s.dynamicPrompts.isError);
const isLoading = useAppSelector((s) => s.dynamicPrompts.isLoading);
const prompts = useAppSelector(selectPrompts);
const label = useMemo(() => {
let _label = `${t('dynamicPrompts.promptsPreview')} (${prompts.length})`;

View File

@@ -15,9 +15,7 @@ import { useTranslation } from 'react-i18next';
const ParamDynamicPromptsSeedBehaviour = () => {
const dispatch = useAppDispatch();
const { t } = useTranslation();
const seedBehaviour = useAppSelector(
(state) => state.dynamicPrompts.seedBehaviour
);
const seedBehaviour = useAppSelector((s) => s.dynamicPrompts.seedBehaviour);
const options = useMemo<InvSelectOption[]>(() => {
return [

View File

@@ -14,7 +14,7 @@ const loadingStyles: SystemStyleObject = {
export const ShowDynamicPromptsPreviewButton = memo(() => {
const { t } = useTranslation();
const isLoading = useAppSelector((state) => state.dynamicPrompts.isLoading);
const isLoading = useAppSelector((s) => s.dynamicPrompts.isLoading);
const { isOpen, onOpen } = useDynamicPromptsModal();
return (
<InvTooltip

View File

@@ -1,5 +1,6 @@
import type { PayloadAction } from '@reduxjs/toolkit';
import { createSlice } from '@reduxjs/toolkit';
import type { RootState } from 'app/store/store';
import { z } from 'zod';
export const zSeedBehaviour = z.enum(['PER_ITERATION', 'PER_PROMPT']);
@@ -74,3 +75,6 @@ export const {
} = dynamicPromptsSlice.actions;
export default dynamicPromptsSlice.reducer;
export const selectDynamicPromptsSlice = (state: RootState) =>
state.dynamicPrompts;

View File

@@ -34,7 +34,7 @@ export const EmbeddingPopover = memo((props: EmbeddingPopoverProps) => {
p={0}
insetBlockStart={-1}
shadow="dark-lg"
borderColor="blue.300"
borderColor="invokeBlue.300"
borderWidth="2px"
borderStyle="solid"
>

View File

@@ -18,7 +18,7 @@ export const EmbeddingSelect = memo(
const { t } = useTranslation();
const currentBaseModel = useAppSelector(
(state) => state.generation.model?.base_model
(s) => s.generation.model?.base_model
);
const getIsDisabled = useCallback(

View File

@@ -6,7 +6,7 @@ const AutoAddIcon = () => {
const { t } = useTranslation();
return (
<Flex position="absolute" insetInlineEnd={0} top={0} p={1}>
<Badge variant="solid" bg="blue.500">
<Badge variant="solid" bg="invokeBlue.500">
{t('common.auto')}
</Badge>
</Flex>

View File

@@ -1,5 +1,3 @@
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { InvControl } from 'common/components/InvControl/InvControl';
import { InvSelect } from 'common/components/InvSelect/InvSelect';
@@ -12,19 +10,13 @@ import { memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { useListAllBoardsQuery } from 'services/api/endpoints/boards';
const selector = createMemoizedSelector([stateSelector], ({ gallery }) => {
const { autoAddBoardId, autoAssignBoardOnClick } = gallery;
return {
autoAddBoardId,
autoAssignBoardOnClick,
};
});
const BoardAutoAddSelect = () => {
const dispatch = useAppDispatch();
const { t } = useTranslation();
const { autoAddBoardId, autoAssignBoardOnClick } = useAppSelector(selector);
const autoAddBoardId = useAppSelector((s) => s.gallery.autoAddBoardId);
const autoAssignBoardOnClick = useAppSelector(
(s) => s.gallery.autoAssignBoardOnClick
);
const { options, hasBoards } = useListAllBoardsQuery(undefined, {
selectFromResult: ({ data }) => {
const options: InvSelectOption[] = [

View File

@@ -1,12 +1,14 @@
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { createSelector } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import type { InvContextMenuProps } from 'common/components/InvContextMenu/InvContextMenu';
import { InvContextMenu } from 'common/components/InvContextMenu/InvContextMenu';
import { InvMenuItem } from 'common/components/InvMenu/InvMenuItem';
import { InvMenuList } from 'common/components/InvMenu/InvMenuList';
import { InvMenuGroup } from 'common/components/InvMenu/wrapper';
import { autoAddBoardIdChanged } from 'features/gallery/store/gallerySlice';
import {
autoAddBoardIdChanged,
selectGallerySlice,
} from 'features/gallery/store/gallerySlice';
import type { BoardId } from 'features/gallery/store/types';
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
import { addToast } from 'features/system/store/systemSlice';
@@ -35,18 +37,19 @@ const BoardContextMenu = ({
}: Props) => {
const { t } = useTranslation();
const dispatch = useAppDispatch();
const selector = useMemo(
const autoAssignBoardOnClick = useAppSelector(
(s) => s.gallery.autoAssignBoardOnClick
);
const selectIsSelectedForAutoAdd = useMemo(
() =>
createMemoizedSelector(stateSelector, ({ gallery }) => {
const isAutoAdd = gallery.autoAddBoardId === board_id;
const autoAssignBoardOnClick = gallery.autoAssignBoardOnClick;
return { isAutoAdd, autoAssignBoardOnClick };
}),
[board_id]
createSelector(
selectGallerySlice,
(gallery) => board && board.board_id === gallery.autoAddBoardId
),
[board]
);
const { isAutoAdd, autoAssignBoardOnClick } = useAppSelector(selector);
const isSelectedForAutoAdd = useAppSelector(selectIsSelectedForAutoAdd);
const boardName = useBoardName(board_id);
const isBulkDownloadEnabled =
useFeatureStatus('bulkDownload').isFeatureEnabled;
@@ -97,7 +100,7 @@ const BoardContextMenu = ({
<InvMenuGroup title={boardName}>
<InvMenuItem
icon={<FaPlus />}
isDisabled={isAutoAdd || autoAssignBoardOnClick}
isDisabled={isSelectedForAutoAdd || autoAssignBoardOnClick}
onClick={handleSetAutoAdd}
>
{t('boards.menuItemAutoAdd')}
@@ -125,8 +128,8 @@ const BoardContextMenu = ({
boardName,
handleBulkDownload,
handleSetAutoAdd,
isAutoAdd,
isBulkDownloadEnabled,
isSelectedForAutoAdd,
setBoardToDelete,
skipEvent,
t,

View File

@@ -1,6 +1,4 @@
import { Collapse, Flex, Grid, GridItem } from '@chakra-ui/react';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks';
import { overlayScrollbarsParams } from 'common/components/OverlayScrollbars/constants';
import DeleteBoardModal from 'features/gallery/components/Boards/DeleteBoardModal';
@@ -20,18 +18,14 @@ const overlayScrollbarsStyles: CSSProperties = {
width: '100%',
};
const selector = createMemoizedSelector([stateSelector], ({ gallery }) => {
const { selectedBoardId, boardSearchText } = gallery;
return { selectedBoardId, boardSearchText };
});
type Props = {
isOpen: boolean;
};
const BoardsList = (props: Props) => {
const { isOpen } = props;
const { selectedBoardId, boardSearchText } = useAppSelector(selector);
const selectedBoardId = useAppSelector((s) => s.gallery.selectedBoardId);
const boardSearchText = useAppSelector((s) => s.gallery.boardSearchText);
const { data: boards } = useListAllBoardsQuery();
const filteredBoards = boardSearchText
? boards?.filter((board) =>

View File

@@ -1,7 +1,5 @@
import { CloseIcon } from '@chakra-ui/icons';
import { Input, InputGroup, InputRightElement } from '@chakra-ui/react';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { InvIconButton } from 'common/components/InvIconButton/InvIconButton';
import { boardSearchTextChanged } from 'features/gallery/store/gallerySlice';
@@ -9,14 +7,9 @@ import type { ChangeEvent, KeyboardEvent } from 'react';
import { memo, useCallback, useEffect, useRef } from 'react';
import { useTranslation } from 'react-i18next';
const selector = createMemoizedSelector([stateSelector], ({ gallery }) => {
const { boardSearchText } = gallery;
return { boardSearchText };
});
const BoardsSearch = () => {
const dispatch = useAppDispatch();
const { boardSearchText } = useAppSelector(selector);
const boardSearchText = useAppSelector((s) => s.gallery.boardSearchText);
const inputRef = useRef<HTMLInputElement>(null);
const { t } = useTranslation();

View File

@@ -8,9 +8,8 @@ import {
Icon,
Image,
} 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 IAIDroppable from 'common/components/IAIDroppable';
import { InvText } from 'common/components/InvText/wrapper';
@@ -22,6 +21,7 @@ import BoardContextMenu from 'features/gallery/components/Boards/BoardContextMen
import {
autoAddBoardIdChanged,
boardIdSelected,
selectGallerySlice,
} from 'features/gallery/store/gallerySlice';
import { memo, useCallback, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
@@ -54,22 +54,19 @@ const GalleryBoard = ({
setBoardToDelete,
}: GalleryBoardProps) => {
const dispatch = useAppDispatch();
const selector = useMemo(
const autoAssignBoardOnClick = useAppSelector(
(s) => s.gallery.autoAssignBoardOnClick
);
const selectIsSelectedForAutoAdd = useMemo(
() =>
createMemoizedSelector(stateSelector, ({ gallery }) => {
const isSelectedForAutoAdd = board.board_id === gallery.autoAddBoardId;
const autoAssignBoardOnClick = gallery.autoAssignBoardOnClick;
return {
isSelectedForAutoAdd,
autoAssignBoardOnClick,
};
}),
createSelector(
selectGallerySlice,
(gallery) => board.board_id === gallery.autoAddBoardId
),
[board.board_id]
);
const { isSelectedForAutoAdd, autoAssignBoardOnClick } =
useAppSelector(selector);
const isSelectedForAutoAdd = useAppSelector(selectIsSelectedForAutoAdd);
const [isHovered, setIsHovered] = useState(false);
const handleMouseOver = useCallback(() => {
setIsHovered(true);
@@ -221,7 +218,7 @@ const GalleryBoard = ({
w="full"
maxW="full"
borderBottomRadius="base"
bg={isSelected ? 'blue.500' : 'base.600'}
bg={isSelected ? 'invokeBlue.500' : 'base.600'}
color={isSelected ? 'base.50' : 'base.100'}
lineHeight="short"
fontSize="xs"

View File

@@ -1,6 +1,4 @@
import { Box, Flex, Image } from '@chakra-ui/react';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import InvokeAILogoImage from 'assets/images/logo.png';
import IAIDroppable from 'common/components/IAIDroppable';
@@ -26,14 +24,12 @@ interface Props {
isSelected: boolean;
}
const selector = createMemoizedSelector(stateSelector, ({ gallery }) => {
const { autoAddBoardId, autoAssignBoardOnClick } = gallery;
return { autoAddBoardId, autoAssignBoardOnClick };
});
const NoBoardBoard = memo(({ isSelected }: Props) => {
const dispatch = useAppDispatch();
const { autoAddBoardId, autoAssignBoardOnClick } = useAppSelector(selector);
const autoAddBoardId = useAppSelector((s) => s.gallery.autoAddBoardId);
const autoAssignBoardOnClick = useAppSelector(
(s) => s.gallery.autoAssignBoardOnClick
);
const boardName = useBoardName('none');
const handleSelectBoard = useCallback(() => {
dispatch(boardIdSelected({ boardId: 'none' }));
@@ -128,7 +124,7 @@ const NoBoardBoard = memo(({ isSelected }: Props) => {
w="full"
maxW="full"
borderBottomRadius="base"
bg={isSelected ? 'blue.500' : 'base.600'}
bg={isSelected ? 'invokeBlue.500' : 'base.600'}
color={isSelected ? 'base.50' : 'base.100'}
lineHeight="short"
fontSize="xs"

View File

@@ -1,7 +1,6 @@
import { Flex, Skeleton } from '@chakra-ui/react';
import { skipToken } from '@reduxjs/toolkit/query';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks';
import {
InvAlertDialog,
@@ -13,9 +12,13 @@ import {
} from 'common/components/InvAlertDialog/wrapper';
import { InvButton } from 'common/components/InvButton/InvButton';
import { InvText } from 'common/components/InvText/wrapper';
import { selectCanvasSlice } from 'features/canvas/store/canvasSlice';
import { selectControlAdaptersSlice } from 'features/controlAdapters/store/controlAdaptersSlice';
import ImageUsageMessage from 'features/deleteImageModal/components/ImageUsageMessage';
import { getImageUsage } from 'features/deleteImageModal/store/selectors';
import type { ImageUsage } from 'features/deleteImageModal/store/types';
import { selectNodesSlice } from 'features/nodes/store/nodesSlice';
import { selectGenerationSlice } from 'features/parameters/store/generationSlice';
import { some } from 'lodash-es';
import { memo, useCallback, useMemo, useRef } from 'react';
import { useTranslation } from 'react-i18next';
@@ -35,26 +38,35 @@ const DeleteBoardModal = (props: Props) => {
const { boardToDelete, setBoardToDelete } = props;
const { t } = useTranslation();
const canRestoreDeletedImagesFromBin = useAppSelector(
(state) => state.config.canRestoreDeletedImagesFromBin
(s) => s.config.canRestoreDeletedImagesFromBin
);
const { currentData: boardImageNames, isFetching: isFetchingBoardNames } =
useListAllImageNamesForBoardQuery(boardToDelete?.board_id ?? skipToken);
const selectImageUsageSummary = useMemo(
() =>
createMemoizedSelector([stateSelector], (state) => {
const allImageUsage = (boardImageNames ?? []).map((imageName) =>
getImageUsage(state, imageName)
);
createMemoizedSelector(
[
selectGenerationSlice,
selectCanvasSlice,
selectNodesSlice,
selectControlAdaptersSlice,
],
(generation, canvas, nodes, controlAdapters) => {
const allImageUsage = (boardImageNames ?? []).map((imageName) =>
getImageUsage(generation, canvas, nodes, controlAdapters, imageName)
);
const imageUsageSummary: ImageUsage = {
isInitialImage: some(allImageUsage, (i) => i.isInitialImage),
isCanvasImage: some(allImageUsage, (i) => i.isCanvasImage),
isNodesImage: some(allImageUsage, (i) => i.isNodesImage),
isControlImage: some(allImageUsage, (i) => i.isControlImage),
};
return { imageUsageSummary };
}),
const imageUsageSummary: ImageUsage = {
isInitialImage: some(allImageUsage, (i) => i.isInitialImage),
isCanvasImage: some(allImageUsage, (i) => i.isCanvasImage),
isNodesImage: some(allImageUsage, (i) => i.isNodesImage),
isControlImage: some(allImageUsage, (i) => i.isControlImage),
};
return imageUsageSummary;
}
),
[boardImageNames]
);
@@ -64,7 +76,7 @@ const DeleteBoardModal = (props: Props) => {
const [deleteBoardAndImages, { isLoading: isDeleteBoardAndImagesLoading }] =
useDeleteBoardAndImagesMutation();
const { imageUsageSummary } = useAppSelector(selectImageUsageSummary);
const imageUsageSummary = useAppSelector(selectImageUsageSummary);
const handleDeleteBoardOnly = useCallback(() => {
if (!boardToDelete) {

View File

@@ -1,9 +1,8 @@
import { Flex } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit';
import { skipToken } from '@reduxjs/toolkit/query';
import { useAppToaster } from 'app/components/Toaster';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { upscaleRequested } from 'app/store/middleware/listenerMiddleware/listeners/upscaleRequested';
import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { InvButtonGroup } from 'common/components/InvButtonGroup/InvButtonGroup';
import { InvIconButton } from 'common/components/InvIconButton/InvIconButton';
@@ -13,12 +12,14 @@ import { DeleteImageButton } from 'features/deleteImageModal/components/DeleteIm
import { imagesToDeleteSelected } from 'features/deleteImageModal/store/slice';
import SingleSelectionMenuItems from 'features/gallery/components/ImageContextMenu/SingleSelectionMenuItems';
import { sentImageToImg2Img } from 'features/gallery/store/actions';
import { selectLastSelectedImage } from 'features/gallery/store/gallerySelectors';
import { selectGallerySlice } from 'features/gallery/store/gallerySlice';
import ParamUpscalePopover from 'features/parameters/components/Upscale/ParamUpscaleSettings';
import { useRecallParameters } from 'features/parameters/hooks/useRecallParameters';
import { initialImageSelected } from 'features/parameters/store/actions';
import { useIsQueueMutationInProgress } from 'features/queue/hooks/useIsQueueMutationInProgress';
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
import { selectSystemSlice } from 'features/system/store/systemSlice';
import {
setShouldShowImageDetails,
setShouldShowProgressInViewer,
@@ -39,45 +40,29 @@ import { FaCircleNodes, FaEllipsis } from 'react-icons/fa6';
import { useGetImageDTOQuery } from 'services/api/endpoints/images';
import { useDebouncedMetadata } from 'services/api/hooks/useDebouncedMetadata';
const currentImageButtonsSelector = createMemoizedSelector(
[stateSelector, activeTabNameSelector],
({ gallery, system, ui, config }, activeTabName) => {
const { isConnected, shouldConfirmOnDelete, denoiseProgress } = system;
const {
shouldShowImageDetails,
shouldHidePreview,
shouldShowProgressInViewer,
} = ui;
const { shouldFetchMetadataFromApi } = config;
const lastSelectedImage = gallery.selection[gallery.selection.length - 1];
return {
shouldConfirmOnDelete,
isConnected,
shouldDisableToolbarButtons:
Boolean(denoiseProgress?.progress_image) || !lastSelectedImage,
shouldShowImageDetails,
activeTabName,
shouldHidePreview,
shouldShowProgressInViewer,
lastSelectedImage,
shouldFetchMetadataFromApi,
};
const selectShouldDisableToolbarButtons = createSelector(
selectSystemSlice,
selectGallerySlice,
selectLastSelectedImage,
(system, gallery, lastSelectedImage) => {
const hasProgressImage = Boolean(system.denoiseProgress?.progress_image);
return hasProgressImage || !lastSelectedImage;
}
);
const CurrentImageButtons = () => {
const dispatch = useAppDispatch();
const {
isConnected,
shouldDisableToolbarButtons,
shouldShowImageDetails,
lastSelectedImage,
shouldShowProgressInViewer,
} = useAppSelector(currentImageButtonsSelector);
const isConnected = useAppSelector((s) => s.system.isConnected);
const shouldShowImageDetails = useAppSelector(
(s) => s.ui.shouldShowImageDetails
);
const shouldShowProgressInViewer = useAppSelector(
(s) => s.ui.shouldShowProgressInViewer
);
const lastSelectedImage = useAppSelector(selectLastSelectedImage);
const shouldDisableToolbarButtons = useAppSelector(
selectShouldDisableToolbarButtons
);
const isUpscalingEnabled = useFeatureStatus('upscaling').isFeatureEnabled;
const isQueueMutationInProgress = useIsQueueMutationInProgress();

View File

@@ -1,7 +1,6 @@
import { Box, 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 { useAppSelector } from 'app/store/storeHooks';
import IAIDndImage from 'common/components/IAIDndImage';
import { IAINoContentFallback } from 'common/components/IAIImageFallback';
@@ -23,32 +22,22 @@ import { useTranslation } from 'react-i18next';
import { FaImage } from 'react-icons/fa';
import { useGetImageDTOQuery } from 'services/api/endpoints/images';
export const imagesSelector = createMemoizedSelector(
[stateSelector, selectLastSelectedImage],
({ ui, system }, lastSelectedImage) => {
const {
shouldShowImageDetails,
shouldHidePreview,
shouldShowProgressInViewer,
} = ui;
const { denoiseProgress } = system;
return {
shouldShowImageDetails,
shouldHidePreview,
imageName: lastSelectedImage?.image_name,
hasDenoiseProgress: Boolean(denoiseProgress),
shouldShowProgressInViewer,
};
}
const selectLastSelectedImageName = createSelector(
selectLastSelectedImage,
(lastSelectedImage) => lastSelectedImage?.image_name
);
const CurrentImagePreview = () => {
const {
shouldShowImageDetails,
imageName,
hasDenoiseProgress,
shouldShowProgressInViewer,
} = useAppSelector(imagesSelector);
const shouldShowImageDetails = useAppSelector(
(s) => s.ui.shouldShowImageDetails
);
const imageName = useAppSelector(selectLastSelectedImageName);
const hasDenoiseProgress = useAppSelector((s) =>
Boolean(s.system.denoiseProgress)
);
const shouldShowProgressInViewer = useAppSelector(
(s) => s.ui.shouldShowProgressInViewer
);
const {
handlePrevImage,

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