mirror of
https://github.com/invoke-ai/InvokeAI.git
synced 2026-01-23 00:38:08 -05:00
Compare commits
76 Commits
v3.6.0rc2
...
ryan/seaml
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9b763b9e4c | ||
|
|
7f3be627c2 | ||
|
|
cb7e56a9a3 | ||
|
|
1a710a4c12 | ||
|
|
d8d266d3be | ||
|
|
4716632c23 | ||
|
|
3c4150d153 | ||
|
|
b71b14d582 | ||
|
|
73481d4aec | ||
|
|
2c049a3b94 | ||
|
|
367de44a8b | ||
|
|
f5f378d04b | ||
|
|
823edbfdef | ||
|
|
29bbb27289 | ||
|
|
a23502f7ff | ||
|
|
ce64dbefce | ||
|
|
b47afdc3b5 | ||
|
|
cde9c3090f | ||
|
|
6924b04d7c | ||
|
|
83fbd4bdf2 | ||
|
|
6460dcc7e0 | ||
|
|
59aa009c93 | ||
|
|
59d2a012cd | ||
|
|
7e3b620830 | ||
|
|
e16b55816f | ||
|
|
895cb8637e | ||
|
|
fe5bceb1ed | ||
|
|
5d475a40f5 | ||
|
|
bca7ea1674 | ||
|
|
f27bb402fb | ||
|
|
dd32c632cd | ||
|
|
9e2e740033 | ||
|
|
d6362ce0bd | ||
|
|
2347a00a70 | ||
|
|
0b7dc721cf | ||
|
|
ac04a834ef | ||
|
|
bbca053b48 | ||
|
|
fcf2006502 | ||
|
|
ac0d0019bd | ||
|
|
2d922a0a65 | ||
|
|
8db14911d7 | ||
|
|
01bab58b20 | ||
|
|
7a57bc99cf | ||
|
|
d3b6d86e74 | ||
|
|
360b6cb286 | ||
|
|
8f9e9e639e | ||
|
|
6930d8ba41 | ||
|
|
7ad74e680d | ||
|
|
c56a6a4ddd | ||
|
|
afad764a00 | ||
|
|
49a72bd714 | ||
|
|
8cf14287b6 | ||
|
|
0db47dd5e7 | ||
|
|
71f6f77ae8 | ||
|
|
6f16229c41 | ||
|
|
0cc0d794d1 | ||
|
|
535639cb95 | ||
|
|
2250bca8d9 | ||
|
|
4ce39a5974 | ||
|
|
644e9287f0 | ||
|
|
6a5e0be022 | ||
|
|
707f0f7091 | ||
|
|
8e709fe05a | ||
|
|
154da609cb | ||
|
|
21975d6268 | ||
|
|
31035b3e63 | ||
|
|
6c05818887 | ||
|
|
77c5b051f0 | ||
|
|
4fdc4c15f9 | ||
|
|
1a4be78013 | ||
|
|
eb16ad3d6f | ||
|
|
1fee08639d | ||
|
|
7caaf40835 | ||
|
|
6bfe994622 | ||
|
|
8a6f03cd46 | ||
|
|
4ce9f9dc36 |
12
README.md
12
README.md
@@ -1,10 +1,10 @@
|
||||
<div align="center">
|
||||
|
||||

|
||||

|
||||
|
||||
# Invoke AI - Generative AI for Professional Creatives
|
||||
## Professional Creative Tools for Stable Diffusion, Custom-Trained Models, and more.
|
||||
To learn more about Invoke AI, get started instantly, or implement our Business solutions, visit [invoke.ai](https://invoke.ai)
|
||||
# Invoke - Professional Creative AI Tools for Visual Media
|
||||
## To learn more about Invoke, or implement our Business solutions, visit [invoke.com](https://www.invoke.com/about)
|
||||
|
||||
|
||||
|
||||
[![discord badge]][discord link]
|
||||
@@ -56,7 +56,9 @@ the foundation for multiple commercial products.
|
||||
|
||||
<div align="center">
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -1,24 +1,27 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
|
||||
<meta http-equiv="Pragma" content="no-cache">
|
||||
<meta http-equiv="Expires" content="0">
|
||||
<title>InvokeAI - A Stable Diffusion Toolkit</title>
|
||||
<link rel="shortcut icon" type="icon" href="favicon.ico" />
|
||||
<style>
|
||||
html,
|
||||
body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body dir="ltr">
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
|
||||
<meta http-equiv="Pragma" content="no-cache">
|
||||
<meta http-equiv="Expires" content="0">
|
||||
<title>InvokeAI - A Stable Diffusion Toolkit</title>
|
||||
<link rel="mask-icon" href="/invoke-key-ylw-sm.svg" color="#E6FD13" sizes="any" />
|
||||
<link rel="icon" href="/invoke-key-char-on-ylw.svg" />
|
||||
<style>
|
||||
html,
|
||||
body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body dir="ltr">
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
241
invokeai/frontend/web/patches/reselect@5.0.1.patch
Normal file
241
invokeai/frontend/web/patches/reselect@5.0.1.patch
Normal file
File diff suppressed because one or more lines are too long
14
invokeai/frontend/web/pnpm-lock.yaml
generated
14
invokeai/frontend/web/pnpm-lock.yaml
generated
@@ -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==}
|
||||
|
||||
4
invokeai/frontend/web/public/invoke-key-char-on-ylw.svg
Normal file
4
invokeai/frontend/web/public/invoke-key-char-on-ylw.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="32" height="32" rx="6" fill="#E6FD13"/>
|
||||
<path d="M19.2378 10.9H25V7H7V10.9H12.7622L19.2378 21.1H25V25H7V21.1H12.7622" stroke="#181818" stroke-width="1.5"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 272 B |
@@ -83,10 +83,6 @@
|
||||
"title": "خيارات التثبيت",
|
||||
"desc": "ثبت لوحة الخيارات"
|
||||
},
|
||||
"toggleViewer": {
|
||||
"title": "تبديل العارض",
|
||||
"desc": "فتح وإغلاق مشاهد الصور"
|
||||
},
|
||||
"toggleGallery": {
|
||||
"title": "تبديل المعرض",
|
||||
"desc": "فتح وإغلاق درابزين المعرض"
|
||||
@@ -147,10 +143,6 @@
|
||||
"title": "الصورة التالية",
|
||||
"desc": "عرض الصورة التالية في الصالة"
|
||||
},
|
||||
"toggleGalleryPin": {
|
||||
"title": "تبديل تثبيت الصالة",
|
||||
"desc": "يثبت ويفتح تثبيت الصالة على الواجهة الرسومية"
|
||||
},
|
||||
"increaseGalleryThumbSize": {
|
||||
"title": "زيادة حجم صورة الصالة",
|
||||
"desc": "يزيد حجم الصور المصغرة في الصالة"
|
||||
|
||||
@@ -168,10 +168,6 @@
|
||||
"title": "Optionen anheften",
|
||||
"desc": "Anheften des Optionsfeldes"
|
||||
},
|
||||
"toggleViewer": {
|
||||
"title": "Bildbetrachter umschalten",
|
||||
"desc": "Bildbetrachter öffnen und schließen"
|
||||
},
|
||||
"toggleGallery": {
|
||||
"title": "Galerie umschalten",
|
||||
"desc": "Öffnen und Schließen des Galerie-Schubfachs"
|
||||
@@ -232,10 +228,6 @@
|
||||
"title": "Nächstes Bild",
|
||||
"desc": "Nächstes Bild in Galerie anzeigen"
|
||||
},
|
||||
"toggleGalleryPin": {
|
||||
"title": "Galerie anheften umschalten",
|
||||
"desc": "Heftet die Galerie an die Benutzeroberfläche bzw. löst die sie"
|
||||
},
|
||||
"increaseGalleryThumbSize": {
|
||||
"title": "Größe der Galeriebilder erhöhen",
|
||||
"desc": "Vergrößert die Galerie-Miniaturansichten"
|
||||
|
||||
@@ -86,6 +86,7 @@
|
||||
"copyError": "$t(gallery.copy) Error",
|
||||
"close": "Close",
|
||||
"on": "On",
|
||||
"or": "or",
|
||||
"checkpoint": "Checkpoint",
|
||||
"communityLabel": "Community",
|
||||
"controlNet": "ControlNet",
|
||||
@@ -156,6 +157,7 @@
|
||||
"save": "Save",
|
||||
"saveAs": "Save As",
|
||||
"settingsLabel": "Settings",
|
||||
"preferencesLabel": "Preferences",
|
||||
"simple": "Simple",
|
||||
"somethingWentWrong": "Something went wrong",
|
||||
"statusConnected": "Connected",
|
||||
@@ -426,6 +428,9 @@
|
||||
"problemDeletingImagesDesc": "One or more images could not be deleted"
|
||||
},
|
||||
"hotkeys": {
|
||||
"searchHotkeys": "Search Hotkeys",
|
||||
"clearSearch": "Clear Search",
|
||||
"noHotkeysFound": "No Hotkeys Found",
|
||||
"acceptStagingImage": {
|
||||
"desc": "Accept Current Staging Area Image",
|
||||
"title": "Accept Staging Image"
|
||||
@@ -434,7 +439,7 @@
|
||||
"desc": "Opens the add node menu",
|
||||
"title": "Add Nodes"
|
||||
},
|
||||
"appHotkeys": "App Hotkeys",
|
||||
"appHotkeys": "App",
|
||||
"cancel": {
|
||||
"desc": "Cancel current queue item",
|
||||
"title": "Cancel"
|
||||
@@ -499,8 +504,8 @@
|
||||
"desc": "Focus the prompt input area",
|
||||
"title": "Focus Prompt"
|
||||
},
|
||||
"galleryHotkeys": "Gallery Hotkeys",
|
||||
"generalHotkeys": "General Hotkeys",
|
||||
"galleryHotkeys": "Gallery",
|
||||
"generalHotkeys": "General",
|
||||
"hideMask": {
|
||||
"desc": "Hide and unhide mask",
|
||||
"title": "Hide Mask"
|
||||
@@ -521,7 +526,7 @@
|
||||
"desc": "Generate an image",
|
||||
"title": "Invoke"
|
||||
},
|
||||
"keyboardShortcuts": "Keyboard Shortcuts",
|
||||
"keyboardShortcuts": "Hotkeys",
|
||||
"maximizeWorkSpace": {
|
||||
"desc": "Close panels and maximize work area",
|
||||
"title": "Maximize Workspace"
|
||||
@@ -542,7 +547,7 @@
|
||||
"desc": "Next Staging Area Image",
|
||||
"title": "Next Staging Image"
|
||||
},
|
||||
"nodesHotkeys": "Nodes Hotkeys",
|
||||
"nodesHotkeys": "Nodes",
|
||||
"pinOptions": {
|
||||
"desc": "Pin the options panel",
|
||||
"title": "Pin Options"
|
||||
@@ -611,31 +616,31 @@
|
||||
"desc": "Open and close the gallery drawer",
|
||||
"title": "Toggle Gallery"
|
||||
},
|
||||
"toggleGalleryPin": {
|
||||
"desc": "Pins and unpins the gallery to the UI",
|
||||
"title": "Toggle Gallery Pin"
|
||||
"toggleOptions": {
|
||||
"desc": "Open and close the options panel",
|
||||
"title": "Toggle Options"
|
||||
},
|
||||
"toggleOptionsAndGallery": {
|
||||
"desc": "Open and close the options and gallery panels",
|
||||
"title": "Toggle Options and Gallery"
|
||||
},
|
||||
"resetOptionsAndGallery": {
|
||||
"desc": "Resets the options and gallery panels",
|
||||
"title": "Reset Options and Gallery"
|
||||
},
|
||||
"toggleLayer": {
|
||||
"desc": "Toggles mask/base layer selection",
|
||||
"title": "Toggle Layer"
|
||||
},
|
||||
"toggleOptions": {
|
||||
"desc": "Open and close the options panel",
|
||||
"title": "Toggle Options"
|
||||
},
|
||||
"toggleSnap": {
|
||||
"desc": "Toggles Snap to Grid",
|
||||
"title": "Toggle Snap"
|
||||
},
|
||||
"toggleViewer": {
|
||||
"desc": "Open and close Image Viewer",
|
||||
"title": "Toggle Viewer"
|
||||
},
|
||||
"undoStroke": {
|
||||
"desc": "Undo a brush stroke",
|
||||
"title": "Undo Stroke"
|
||||
},
|
||||
"unifiedCanvasHotkeys": "Unified Canvas Hotkeys",
|
||||
"unifiedCanvasHotkeys": "Unified Canvas",
|
||||
"upscale": {
|
||||
"desc": "Upscale the current image",
|
||||
"title": "Upscale"
|
||||
@@ -1076,6 +1081,11 @@
|
||||
"aspect": "Aspect",
|
||||
"aspectRatio": "Aspect Ratio",
|
||||
"aspectRatioFree": "Free",
|
||||
"lockAspectRatio": "Lock Aspect Ratio",
|
||||
"swapDimensions": "Swap Dimensions",
|
||||
"setToOptimalSize": "Optimize size for model",
|
||||
"setToOptimalSizeTooSmall": "$t(parameters.setToOptimalSize) (may be too small)",
|
||||
"setToOptimalSizeTooLarge": "$t(parameters.setToOptimalSize) (may be too large)",
|
||||
"boundingBoxHeader": "Bounding Box",
|
||||
"boundingBoxHeight": "Bounding Box Height",
|
||||
"boundingBoxWidth": "Bounding Box Width",
|
||||
|
||||
@@ -127,10 +127,6 @@
|
||||
"title": "Fijar opciones",
|
||||
"desc": "Fijar el panel de opciones"
|
||||
},
|
||||
"toggleViewer": {
|
||||
"title": "Alternar visor",
|
||||
"desc": "Mostar y ocultar el visor de imágenes"
|
||||
},
|
||||
"toggleGallery": {
|
||||
"title": "Alternar galería",
|
||||
"desc": "Mostar y ocultar la galería de imágenes"
|
||||
@@ -191,10 +187,6 @@
|
||||
"title": "Imagen siguiente",
|
||||
"desc": "Muetra la imagen siguiente en la galería"
|
||||
},
|
||||
"toggleGalleryPin": {
|
||||
"title": "Alternar fijado de galería",
|
||||
"desc": "Fijar o desfijar la galería en la interfaz"
|
||||
},
|
||||
"increaseGalleryThumbSize": {
|
||||
"title": "Aumentar imagen en galería",
|
||||
"desc": "Aumenta el tamaño de las miniaturas de la galería"
|
||||
|
||||
@@ -96,10 +96,6 @@
|
||||
"title": "Epinglage des options",
|
||||
"desc": "Epingler le panneau d'options"
|
||||
},
|
||||
"toggleViewer": {
|
||||
"title": "Affichage de la visionneuse",
|
||||
"desc": "Afficher et masquer la visionneuse d'image"
|
||||
},
|
||||
"toggleGallery": {
|
||||
"title": "Affichage de la galerie",
|
||||
"desc": "Afficher et masquer la galerie"
|
||||
@@ -160,10 +156,6 @@
|
||||
"title": "Image suivante",
|
||||
"desc": "Afficher l'image suivante dans la galerie"
|
||||
},
|
||||
"toggleGalleryPin": {
|
||||
"title": "Activer/désactiver l'épinglage de la galerie",
|
||||
"desc": "Épingle ou dépingle la galerie à l'interface"
|
||||
},
|
||||
"increaseGalleryThumbSize": {
|
||||
"title": "Augmenter la taille des miniatures de la galerie",
|
||||
"desc": "Augmente la taille des miniatures de la galerie"
|
||||
|
||||
@@ -192,10 +192,6 @@
|
||||
"title": "הצמד הגדרות",
|
||||
"desc": "הצמד את פאנל ההגדרות"
|
||||
},
|
||||
"toggleViewer": {
|
||||
"title": "הצג את חלון ההצגה",
|
||||
"desc": "פתח וסגור את מציג התמונות"
|
||||
},
|
||||
"changeTabs": {
|
||||
"title": "החלף לשוניות",
|
||||
"desc": "החלף לאיזור עבודה אחר"
|
||||
@@ -236,10 +232,6 @@
|
||||
"title": "תמונה קודמת",
|
||||
"desc": "הצג את התמונה הקודמת בגלריה"
|
||||
},
|
||||
"toggleGalleryPin": {
|
||||
"title": "הצג את מצמיד הגלריה",
|
||||
"desc": "הצמדה וביטול הצמדה של הגלריה לממשק המשתמש"
|
||||
},
|
||||
"decreaseGalleryThumbSize": {
|
||||
"title": "הקטנת גודל תמונת גלריה",
|
||||
"desc": "מקטין את גודל התמונות הממוזערות של הגלריה"
|
||||
|
||||
@@ -114,7 +114,9 @@
|
||||
"nextPage": "Pagina successiva",
|
||||
"saveAs": "Salva come",
|
||||
"unsaved": "Non salvato",
|
||||
"direction": "Direzione"
|
||||
"direction": "Direzione",
|
||||
"advancedOptions": "Opzioni avanzate",
|
||||
"free": "Libero"
|
||||
},
|
||||
"gallery": {
|
||||
"generations": "Generazioni",
|
||||
@@ -132,7 +134,7 @@
|
||||
"noImagesInGallery": "Nessuna immagine da visualizzare",
|
||||
"deleteImage": "Elimina l'immagine",
|
||||
"deleteImagePermanent": "Le immagini eliminate non possono essere ripristinate.",
|
||||
"deleteImageBin": "Le immagini eliminate verranno spostate nel Cestino del tuo sistema operativo.",
|
||||
"deleteImageBin": "Le immagini eliminate verranno spostate nel cestino del tuo sistema operativo.",
|
||||
"assets": "Risorse",
|
||||
"autoAssignBoardOnClick": "Assegna automaticamente la bacheca al clic",
|
||||
"featuresWillReset": "Se elimini questa immagine, quelle funzionalità verranno immediatamente ripristinate.",
|
||||
@@ -157,18 +159,18 @@
|
||||
"problemDeletingImages": "Problema durante l'eliminazione delle immagini"
|
||||
},
|
||||
"hotkeys": {
|
||||
"keyboardShortcuts": "Tasti rapidi",
|
||||
"appHotkeys": "Tasti di scelta rapida dell'applicazione",
|
||||
"generalHotkeys": "Tasti di scelta rapida generali",
|
||||
"galleryHotkeys": "Tasti di scelta rapida della galleria",
|
||||
"unifiedCanvasHotkeys": "Tasti di scelta rapida Tela Unificata",
|
||||
"keyboardShortcuts": "Tasti di scelta rapida",
|
||||
"appHotkeys": "Applicazione",
|
||||
"generalHotkeys": "Generale",
|
||||
"galleryHotkeys": "Galleria",
|
||||
"unifiedCanvasHotkeys": "Tela Unificata",
|
||||
"invoke": {
|
||||
"title": "Invoke",
|
||||
"desc": "Genera un'immagine"
|
||||
},
|
||||
"cancel": {
|
||||
"title": "Annulla",
|
||||
"desc": "Annulla la generazione dell'immagine"
|
||||
"desc": "Annulla l'elemento della coda corrente"
|
||||
},
|
||||
"focusPrompt": {
|
||||
"title": "Metti a fuoco il Prompt",
|
||||
@@ -182,12 +184,8 @@
|
||||
"title": "Appunta le opzioni",
|
||||
"desc": "Blocca il pannello delle opzioni"
|
||||
},
|
||||
"toggleViewer": {
|
||||
"title": "Attiva/disattiva visualizzatore",
|
||||
"desc": "Apre e chiude il visualizzatore immagini"
|
||||
},
|
||||
"toggleGallery": {
|
||||
"title": "Attiva/disattiva Galleria",
|
||||
"title": "Attiva/disattiva galleria",
|
||||
"desc": "Apre e chiude il pannello della galleria"
|
||||
},
|
||||
"maximizeWorkSpace": {
|
||||
@@ -246,10 +244,6 @@
|
||||
"title": "Immagine successiva",
|
||||
"desc": "Visualizza l'immagine successiva nella galleria"
|
||||
},
|
||||
"toggleGalleryPin": {
|
||||
"title": "Attiva/disattiva il blocco della galleria",
|
||||
"desc": "Blocca/sblocca la galleria dall'interfaccia utente"
|
||||
},
|
||||
"increaseGalleryThumbSize": {
|
||||
"title": "Aumenta dimensione immagini nella galleria",
|
||||
"desc": "Aumenta la dimensione delle miniature della galleria"
|
||||
@@ -362,11 +356,26 @@
|
||||
"title": "Accetta l'immagine della sessione",
|
||||
"desc": "Accetta l'immagine dell'area della sessione corrente"
|
||||
},
|
||||
"nodesHotkeys": "Tasti di scelta rapida dei Nodi",
|
||||
"nodesHotkeys": "Nodi",
|
||||
"addNodes": {
|
||||
"title": "Aggiungi Nodi",
|
||||
"desc": "Apre il menu Aggiungi Nodi"
|
||||
}
|
||||
},
|
||||
"cancelAndClear": {
|
||||
"desc": "Annulla l'elemento della coda corrente e cancella tutti gli elementi in sospeso",
|
||||
"title": "Annulla e cancella"
|
||||
},
|
||||
"resetOptionsAndGallery": {
|
||||
"title": "Ripristina Opzioni e Galleria",
|
||||
"desc": "Reimposta le opzioni e i pannelli della galleria"
|
||||
},
|
||||
"searchHotkeys": "Cerca tasti di scelta rapida",
|
||||
"noHotkeysFound": "Nessun tasto di scelta rapida trovato",
|
||||
"toggleOptionsAndGallery": {
|
||||
"desc": "Apre e chiude le opzioni e i pannelli della galleria",
|
||||
"title": "Attiva/disattiva le Opzioni e la Galleria"
|
||||
},
|
||||
"clearSearch": "Cancella ricerca"
|
||||
},
|
||||
"modelManager": {
|
||||
"modelManager": "Gestione Modelli",
|
||||
@@ -581,8 +590,8 @@
|
||||
"hidePreview": "Nascondi l'anteprima",
|
||||
"showPreview": "Mostra l'anteprima",
|
||||
"noiseSettings": "Rumore",
|
||||
"seamlessXAxis": "Asse X",
|
||||
"seamlessYAxis": "Asse Y",
|
||||
"seamlessXAxis": "Piastrella senza cucitura Asse X",
|
||||
"seamlessYAxis": "Piastrella senza cucitura Asse Y",
|
||||
"scheduler": "Campionatore",
|
||||
"boundingBoxWidth": "Larghezza riquadro di delimitazione",
|
||||
"boundingBoxHeight": "Altezza riquadro di delimitazione",
|
||||
@@ -642,7 +651,14 @@
|
||||
"unmasked": "No maschera",
|
||||
"cfgRescaleMultiplier": "Moltiplicatore riscala CFG",
|
||||
"cfgRescale": "Riscala CFG",
|
||||
"useSize": "Usa Dimensioni"
|
||||
"useSize": "Usa Dimensioni",
|
||||
"setToOptimalSize": "Ottimizza le dimensioni per il modello",
|
||||
"setToOptimalSizeTooSmall": "$t(parameters.setToOptimalSize) (potrebbe essere troppo piccolo)",
|
||||
"imageSize": "Dimensione dell'immagine",
|
||||
"lockAspectRatio": "Blocca proporzioni",
|
||||
"swapDimensions": "Scambia dimensioni",
|
||||
"aspect": "Aspetto",
|
||||
"setToOptimalSizeTooLarge": "$t(parameters.setToOptimalSize) (potrebbe essere troppo grande)"
|
||||
},
|
||||
"settings": {
|
||||
"models": "Modelli",
|
||||
@@ -1213,12 +1229,13 @@
|
||||
"minConfidence": "Confidenza minima",
|
||||
"scribble": "Scribble",
|
||||
"amult": "Angolo di illuminazione",
|
||||
"coarse": "Approssimativo"
|
||||
"coarse": "Approssimativo",
|
||||
"resizeSimple": "Ridimensiona (semplice)"
|
||||
},
|
||||
"queue": {
|
||||
"queueFront": "Aggiungi all'inizio della coda",
|
||||
"queueBack": "Aggiungi alla coda",
|
||||
"queueCountPrediction": "Aggiungi {{predicted}} alla coda",
|
||||
"queueCountPrediction": "{{promptsCount}} prompt × {{iterations}} iterazioni -> {{count}} generazioni",
|
||||
"queue": "Coda",
|
||||
"status": "Stato",
|
||||
"pruneSucceeded": "Rimossi {{item_count}} elementi completati dalla coda",
|
||||
@@ -1276,7 +1293,8 @@
|
||||
"graphFailedToQueue": "Impossibile mettere in coda il grafico",
|
||||
"queueMaxExceeded": "È stato superato il limite massimo di {{max_queue_size}} e {{skip}} elementi verrebbero saltati",
|
||||
"batchFieldValues": "Valori Campi Lotto",
|
||||
"time": "Tempo"
|
||||
"time": "Tempo",
|
||||
"openQueue": "Apri coda"
|
||||
},
|
||||
"embedding": {
|
||||
"noMatchingEmbedding": "Nessun Incorporamento corrispondente",
|
||||
@@ -1296,7 +1314,12 @@
|
||||
"noLoRAsInstalled": "Nessun LoRA installato",
|
||||
"esrganModel": "Modello ESRGAN",
|
||||
"addLora": "Aggiungi LoRA",
|
||||
"noLoRAsLoaded": "Nessuna LoRA caricata"
|
||||
"noLoRAsLoaded": "Nessun LoRA caricato",
|
||||
"noMainModelSelected": "Nessun modello principale selezionato",
|
||||
"allLoRAsAdded": "Tutti i LoRA aggiunti",
|
||||
"defaultVAE": "VAE predefinito",
|
||||
"incompatibleBaseModel": "Modello base incompatibile",
|
||||
"loraAlreadyAdded": "LoRA già aggiunto"
|
||||
},
|
||||
"invocationCache": {
|
||||
"disable": "Disabilita",
|
||||
@@ -1330,7 +1353,9 @@
|
||||
"promptsWithCount_many": "{{count}} Prompt",
|
||||
"promptsWithCount_other": "{{count}} Prompt",
|
||||
"dynamicPrompts": "Prompt dinamici",
|
||||
"promptsPreview": "Anteprima dei prompt"
|
||||
"promptsPreview": "Anteprima dei prompt",
|
||||
"showDynamicPrompts": "Mostra prompt dinamici",
|
||||
"loading": "Generazione prompt dinamici..."
|
||||
},
|
||||
"popovers": {
|
||||
"paramScheduler": {
|
||||
@@ -1556,7 +1581,7 @@
|
||||
"scheduler": "Campionatore",
|
||||
"noModelsAvailable": "Nessun modello disponibile",
|
||||
"denoisingStrength": "Forza di riduzione del rumore",
|
||||
"concatPromptStyle": "Concatena Prompt & Stile",
|
||||
"concatPromptStyle": "Collega Prompt & Stile",
|
||||
"loading": "Caricamento...",
|
||||
"steps": "Passi",
|
||||
"refinerStart": "Inizio Affinamento",
|
||||
@@ -1567,7 +1592,8 @@
|
||||
"useRefiner": "Utilizza l'affinatore",
|
||||
"refinermodel": "Modello Affinatore",
|
||||
"posAestheticScore": "Punteggio estetico positivo",
|
||||
"posStylePrompt": "Prompt Stile positivo"
|
||||
"posStylePrompt": "Prompt Stile positivo",
|
||||
"freePromptStyle": "Prompt di stile manuale"
|
||||
},
|
||||
"metadata": {
|
||||
"initImage": "Immagine iniziale",
|
||||
@@ -1640,5 +1666,28 @@
|
||||
},
|
||||
"app": {
|
||||
"storeNotInitialized": "Il negozio non è inizializzato"
|
||||
},
|
||||
"accordions": {
|
||||
"compositing": {
|
||||
"infillTab": "Riempimento",
|
||||
"coherenceTab": "Passaggio di coerenza",
|
||||
"title": "Composizione"
|
||||
},
|
||||
"control": {
|
||||
"controlAdaptersTab": "Adattatori di Controllo",
|
||||
"ipTab": "Prompt immagine",
|
||||
"title": "Controllo"
|
||||
},
|
||||
"generation": {
|
||||
"title": "Generazione",
|
||||
"conceptsTab": "Concetti",
|
||||
"modelTab": "Modello"
|
||||
},
|
||||
"advanced": {
|
||||
"title": "Avanzate"
|
||||
},
|
||||
"image": {
|
||||
"title": "Immagine"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,10 +133,6 @@
|
||||
"title": "ピン",
|
||||
"desc": "オプションパネルを固定"
|
||||
},
|
||||
"toggleViewer": {
|
||||
"title": "ビュワーのトグル",
|
||||
"desc": "ビュワーを開閉"
|
||||
},
|
||||
"toggleGallery": {
|
||||
"title": "ギャラリーのトグル",
|
||||
"desc": "ギャラリードロワーの開閉"
|
||||
@@ -197,10 +193,6 @@
|
||||
"title": "次の画像",
|
||||
"desc": "ギャラリー内の1つ後の画像を表示"
|
||||
},
|
||||
"toggleGalleryPin": {
|
||||
"title": "ギャラリードロワーの固定",
|
||||
"desc": "ギャラリーをUIにピン留め/解除"
|
||||
},
|
||||
"increaseGalleryThumbSize": {
|
||||
"title": "ギャラリーの画像を拡大",
|
||||
"desc": "ギャラリーのサムネイル画像を拡大"
|
||||
|
||||
@@ -407,10 +407,6 @@
|
||||
"maxFaces": "Max Faces"
|
||||
},
|
||||
"hotkeys": {
|
||||
"toggleGalleryPin": {
|
||||
"title": "Gallery Pin 전환",
|
||||
"desc": "갤러리를 UI에 고정했다가 풉니다"
|
||||
},
|
||||
"toggleSnap": {
|
||||
"desc": "Snap을 Grid로 전환",
|
||||
"title": "Snap 전환"
|
||||
@@ -600,10 +596,6 @@
|
||||
"desc": "노드 추가 메뉴 열기",
|
||||
"title": "노드 추가"
|
||||
},
|
||||
"toggleViewer": {
|
||||
"desc": "이미지 뷰어 열기 및 닫기",
|
||||
"title": "Viewer 전환"
|
||||
},
|
||||
"undoStroke": {
|
||||
"title": "Stroke 실행 취소",
|
||||
"desc": "brush stroke 실행 취소"
|
||||
|
||||
@@ -148,10 +148,6 @@
|
||||
"title": "Zet Opties vast",
|
||||
"desc": "Zet het deelscherm Opties vast"
|
||||
},
|
||||
"toggleViewer": {
|
||||
"title": "Zet Viewer vast",
|
||||
"desc": "Opent of sluit Afbeeldingsviewer"
|
||||
},
|
||||
"toggleGallery": {
|
||||
"title": "Zet Galerij vast",
|
||||
"desc": "Opent of sluit het deelscherm Galerij"
|
||||
@@ -212,10 +208,6 @@
|
||||
"title": "Volgende afbeelding",
|
||||
"desc": "Toont de volgende afbeelding in de galerij"
|
||||
},
|
||||
"toggleGalleryPin": {
|
||||
"title": "Zet galerij vast/los",
|
||||
"desc": "Zet de galerij vast of los aan de gebruikersinterface"
|
||||
},
|
||||
"increaseGalleryThumbSize": {
|
||||
"title": "Vergroot afbeeldingsgrootte galerij",
|
||||
"desc": "Vergroot de grootte van de galerijminiaturen"
|
||||
|
||||
@@ -86,10 +86,6 @@
|
||||
"title": "Przypnij opcje",
|
||||
"desc": "Przypina panel opcji"
|
||||
},
|
||||
"toggleViewer": {
|
||||
"title": "Przełącz podgląd",
|
||||
"desc": "Otwiera lub zamyka widok podglądu"
|
||||
},
|
||||
"toggleGallery": {
|
||||
"title": "Przełącz galerię",
|
||||
"desc": "Wysuwa lub chowa galerię"
|
||||
@@ -150,10 +146,6 @@
|
||||
"title": "Następny obraz",
|
||||
"desc": "Aktywuje następny obraz z galerii"
|
||||
},
|
||||
"toggleGalleryPin": {
|
||||
"title": "Przypnij galerię",
|
||||
"desc": "Przypina lub odpina widok galerii"
|
||||
},
|
||||
"increaseGalleryThumbSize": {
|
||||
"title": "Powiększ obrazy",
|
||||
"desc": "Powiększa rozmiar obrazów w galerii"
|
||||
|
||||
@@ -81,10 +81,6 @@
|
||||
"hotkeys": {
|
||||
"generalHotkeys": "Atalhos Gerais",
|
||||
"galleryHotkeys": "Atalhos da Galeria",
|
||||
"toggleViewer": {
|
||||
"title": "Ativar Visualizador",
|
||||
"desc": "Abrir e fechar o Visualizador de Imagens"
|
||||
},
|
||||
"maximizeWorkSpace": {
|
||||
"desc": "Fechar painéis e maximixar área de trabalho",
|
||||
"title": "Maximizar a Área de Trabalho"
|
||||
@@ -232,10 +228,6 @@
|
||||
"title": "Apagar Imagem",
|
||||
"desc": "Apaga a imagem atual"
|
||||
},
|
||||
"toggleGalleryPin": {
|
||||
"title": "Ativar Fixar Galeria",
|
||||
"desc": "Fixa e desafixa a galeria na interface"
|
||||
},
|
||||
"increaseGalleryThumbSize": {
|
||||
"title": "Aumentar Tamanho da Galeria de Imagem",
|
||||
"desc": "Aumenta o tamanho das thumbs na galeria"
|
||||
|
||||
@@ -103,10 +103,6 @@
|
||||
"title": "Fixar Opções",
|
||||
"desc": "Fixar o painel de opções"
|
||||
},
|
||||
"toggleViewer": {
|
||||
"title": "Ativar Visualizador",
|
||||
"desc": "Abrir e fechar o Visualizador de Imagens"
|
||||
},
|
||||
"toggleGallery": {
|
||||
"title": "Ativar Galeria",
|
||||
"desc": "Abrir e fechar a gaveta da galeria"
|
||||
@@ -167,10 +163,6 @@
|
||||
"title": "Próxima Imagem",
|
||||
"desc": "Mostra a próxima imagem na galeria"
|
||||
},
|
||||
"toggleGalleryPin": {
|
||||
"title": "Ativar Fixar Galeria",
|
||||
"desc": "Fixa e desafixa a galeria na interface"
|
||||
},
|
||||
"increaseGalleryThumbSize": {
|
||||
"title": "Aumentar Tamanho da Galeria de Imagem",
|
||||
"desc": "Aumenta o tamanho das thumbs na galeria"
|
||||
|
||||
@@ -189,10 +189,6 @@
|
||||
"title": "Закрепить параметры",
|
||||
"desc": "Закрепить панель параметров"
|
||||
},
|
||||
"toggleViewer": {
|
||||
"title": "Показать просмотр",
|
||||
"desc": "Открывать и закрывать просмотрщик изображений"
|
||||
},
|
||||
"toggleGallery": {
|
||||
"title": "Показать галерею",
|
||||
"desc": "Открывать и закрывать ящик галереи"
|
||||
@@ -253,10 +249,6 @@
|
||||
"title": "Следующее изображение",
|
||||
"desc": "Отображение следующего изображения в галерее"
|
||||
},
|
||||
"toggleGalleryPin": {
|
||||
"title": "Закрепить галерею",
|
||||
"desc": "Закрепляет и открепляет галерею"
|
||||
},
|
||||
"increaseGalleryThumbSize": {
|
||||
"title": "Увеличить размер миниатюр галереи",
|
||||
"desc": "Увеличивает размер миниатюр галереи"
|
||||
|
||||
@@ -129,10 +129,6 @@
|
||||
"title": "Växla inställningar",
|
||||
"desc": "Öppna och stäng alternativpanelen"
|
||||
},
|
||||
"toggleViewer": {
|
||||
"title": "Växla visaren",
|
||||
"desc": "Öppna och stäng bildvisaren"
|
||||
},
|
||||
"toggleGallery": {
|
||||
"title": "Växla galleri",
|
||||
"desc": "Öppna eller stäng galleribyrån"
|
||||
@@ -193,10 +189,6 @@
|
||||
"title": "Nästa bild",
|
||||
"desc": "Visa nästa bild"
|
||||
},
|
||||
"toggleGalleryPin": {
|
||||
"title": "Växla gallerinål",
|
||||
"desc": "Nålar fast eller nålar av galleriet i gränssnittet"
|
||||
},
|
||||
"increaseGalleryThumbSize": {
|
||||
"title": "Förstora galleriets bildstorlek",
|
||||
"desc": "Förstora miniatyrbildernas storlek"
|
||||
|
||||
@@ -111,10 +111,6 @@
|
||||
"title": "Закріпити параметри",
|
||||
"desc": "Закріпити панель параметрів"
|
||||
},
|
||||
"toggleViewer": {
|
||||
"title": "Показати перегляд",
|
||||
"desc": "Відкривати і закривати переглядач зображень"
|
||||
},
|
||||
"toggleGallery": {
|
||||
"title": "Показати галерею",
|
||||
"desc": "Відкривати і закривати скриньку галереї"
|
||||
@@ -175,10 +171,6 @@
|
||||
"title": "Наступне зображення",
|
||||
"desc": "Відображення наступного зображення в галереї"
|
||||
},
|
||||
"toggleGalleryPin": {
|
||||
"title": "Закріпити галерею",
|
||||
"desc": "Закріплює і відкріплює галерею"
|
||||
},
|
||||
"increaseGalleryThumbSize": {
|
||||
"title": "Збільшити розмір мініатюр галереї",
|
||||
"desc": "Збільшує розмір мініатюр галереї"
|
||||
|
||||
@@ -189,10 +189,6 @@
|
||||
"title": "常开选项卡",
|
||||
"desc": "保持选项浮窗常开"
|
||||
},
|
||||
"toggleViewer": {
|
||||
"title": "切换图像查看器",
|
||||
"desc": "打开或关闭图像查看器"
|
||||
},
|
||||
"toggleGallery": {
|
||||
"title": "切换图库",
|
||||
"desc": "打开或关闭图库"
|
||||
@@ -253,10 +249,6 @@
|
||||
"title": "下一张图像",
|
||||
"desc": "显示图库中的下一张图像"
|
||||
},
|
||||
"toggleGalleryPin": {
|
||||
"title": "切换图库常开",
|
||||
"desc": "开关图库在界面中的常开模式"
|
||||
},
|
||||
"increaseGalleryThumbSize": {
|
||||
"title": "增大预览尺寸",
|
||||
"desc": "增大图库中预览的尺寸"
|
||||
|
||||
@@ -1,22 +1,21 @@
|
||||
import { Flex, Grid } from '@chakra-ui/react';
|
||||
import { useStore } from '@nanostores/react';
|
||||
import { Box } from '@chakra-ui/react';
|
||||
import { useSocketIO } from 'app/hooks/useSocketIO';
|
||||
import { useLogger } from 'app/logging/useLogger';
|
||||
import { appStarted } from 'app/store/middleware/listenerMiddleware/listeners/appStarted';
|
||||
import { $headerComponent } from 'app/store/nanostores/headerComponent';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import type { PartialAppConfig } from 'app/types/invokeai';
|
||||
import ImageUploader from 'common/components/ImageUploader';
|
||||
import ImageUploadOverlay from 'common/components/ImageUploadOverlay';
|
||||
import { useClearStorage } from 'common/hooks/useClearStorage';
|
||||
import { useFullscreenDropzone } from 'common/hooks/useFullscreenDropzone';
|
||||
import { useGlobalHotkeys } from 'common/hooks/useGlobalHotkeys';
|
||||
import { useGlobalModifiersInit } from 'common/hooks/useGlobalModifiers';
|
||||
import ChangeBoardModal from 'features/changeBoardModal/components/ChangeBoardModal';
|
||||
import DeleteImageModal from 'features/deleteImageModal/components/DeleteImageModal';
|
||||
import { DynamicPromptsModal } from 'features/dynamicPrompts/components/DynamicPromptsPreviewModal';
|
||||
import SiteHeader from 'features/system/components/SiteHeader';
|
||||
import { configChanged } from 'features/system/store/configSlice';
|
||||
import { languageSelector } from 'features/system/store/systemSelectors';
|
||||
import InvokeTabs from 'features/ui/components/InvokeTabs';
|
||||
import { AnimatePresence } from 'framer-motion';
|
||||
import i18n from 'i18n';
|
||||
import { size } from 'lodash-es';
|
||||
import { memo, useCallback, useEffect } from 'react';
|
||||
@@ -47,6 +46,9 @@ const App = ({ config = DEFAULT_CONFIG, selectedImage }: Props) => {
|
||||
useGlobalModifiersInit();
|
||||
useGlobalHotkeys();
|
||||
|
||||
const { dropzone, isHandlingUpload, setIsHandlingUpload } =
|
||||
useFullscreenDropzone();
|
||||
|
||||
const handleReset = useCallback(() => {
|
||||
clearStorage();
|
||||
location.reload();
|
||||
@@ -68,23 +70,30 @@ const App = ({ config = DEFAULT_CONFIG, selectedImage }: Props) => {
|
||||
dispatch(appStarted());
|
||||
}, [dispatch]);
|
||||
|
||||
const headerComponent = useStore($headerComponent);
|
||||
|
||||
return (
|
||||
<ErrorBoundary
|
||||
onReset={handleReset}
|
||||
FallbackComponent={AppErrorBoundaryFallback}
|
||||
>
|
||||
<Grid w="100vw" h="100vh" position="relative" overflow="hidden">
|
||||
<ImageUploader>
|
||||
<Grid p={4} gridAutoRows="min-content auto" w="full" h="full">
|
||||
{headerComponent || <SiteHeader />}
|
||||
<Flex gap={4} w="full" h="full">
|
||||
<InvokeTabs />
|
||||
</Flex>
|
||||
</Grid>
|
||||
</ImageUploader>
|
||||
</Grid>
|
||||
<Box
|
||||
id="invoke-app-wrapper"
|
||||
w="100vw"
|
||||
h="100vh"
|
||||
position="relative"
|
||||
overflow="hidden"
|
||||
{...dropzone.getRootProps()}
|
||||
>
|
||||
<input {...dropzone.getInputProps()} />
|
||||
<InvokeTabs />
|
||||
<AnimatePresence>
|
||||
{dropzone.isDragActive && isHandlingUpload && (
|
||||
<ImageUploadOverlay
|
||||
dropzone={dropzone}
|
||||
setIsHandlingUpload={setIsHandlingUpload}
|
||||
/>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</Box>
|
||||
<DeleteImageModal />
|
||||
<ChangeBoardModal />
|
||||
<DynamicPromptsModal />
|
||||
|
||||
@@ -4,10 +4,12 @@ import type { Middleware } from '@reduxjs/toolkit';
|
||||
import { $socketOptions } from 'app/hooks/useSocketIO';
|
||||
import { $authToken } from 'app/store/nanostores/authToken';
|
||||
import { $baseUrl } from 'app/store/nanostores/baseUrl';
|
||||
import { $customNavComponent } from 'app/store/nanostores/customNavComponent';
|
||||
import type { CustomStarUi } from 'app/store/nanostores/customStarUI';
|
||||
import { $customStarUI } from 'app/store/nanostores/customStarUI';
|
||||
import { $headerComponent } from 'app/store/nanostores/headerComponent';
|
||||
import { $galleryHeader } from 'app/store/nanostores/galleryHeader';
|
||||
import { $isDebugging } from 'app/store/nanostores/isDebugging';
|
||||
import { $logo } from 'app/store/nanostores/logo';
|
||||
import { $projectId } from 'app/store/nanostores/projectId';
|
||||
import { $queueId, DEFAULT_QUEUE_ID } from 'app/store/nanostores/queueId';
|
||||
import { $store } from 'app/store/nanostores/store';
|
||||
@@ -28,9 +30,10 @@ interface Props extends PropsWithChildren {
|
||||
apiUrl?: string;
|
||||
token?: string;
|
||||
config?: PartialAppConfig;
|
||||
headerComponent?: ReactNode;
|
||||
customNavComponent?: ReactNode;
|
||||
middleware?: Middleware[];
|
||||
projectId?: string;
|
||||
galleryHeader?: ReactNode;
|
||||
queueId?: string;
|
||||
selectedImage?: {
|
||||
imageName: string;
|
||||
@@ -39,20 +42,23 @@ interface Props extends PropsWithChildren {
|
||||
customStarUi?: CustomStarUi;
|
||||
socketOptions?: Partial<ManagerOptions & SocketOptions>;
|
||||
isDebugging?: boolean;
|
||||
logo?: ReactNode;
|
||||
}
|
||||
|
||||
const InvokeAIUI = ({
|
||||
apiUrl,
|
||||
token,
|
||||
config,
|
||||
headerComponent,
|
||||
customNavComponent,
|
||||
middleware,
|
||||
projectId,
|
||||
galleryHeader,
|
||||
queueId,
|
||||
selectedImage,
|
||||
customStarUi,
|
||||
socketOptions,
|
||||
isDebugging = false,
|
||||
logo,
|
||||
}: Props) => {
|
||||
useEffect(() => {
|
||||
// configure API client token
|
||||
@@ -108,14 +114,34 @@ const InvokeAIUI = ({
|
||||
}, [customStarUi]);
|
||||
|
||||
useEffect(() => {
|
||||
if (headerComponent) {
|
||||
$headerComponent.set(headerComponent);
|
||||
if (customNavComponent) {
|
||||
$customNavComponent.set(customNavComponent);
|
||||
}
|
||||
|
||||
return () => {
|
||||
$headerComponent.set(undefined);
|
||||
$customNavComponent.set(undefined);
|
||||
};
|
||||
}, [headerComponent]);
|
||||
}, [customNavComponent]);
|
||||
|
||||
useEffect(() => {
|
||||
if (galleryHeader) {
|
||||
$galleryHeader.set(galleryHeader);
|
||||
}
|
||||
|
||||
return () => {
|
||||
$galleryHeader.set(undefined);
|
||||
};
|
||||
}, [galleryHeader]);
|
||||
|
||||
useEffect(() => {
|
||||
if (logo) {
|
||||
$logo.set(logo);
|
||||
}
|
||||
|
||||
return () => {
|
||||
$logo.set(undefined);
|
||||
};
|
||||
}, [logo]);
|
||||
|
||||
useEffect(() => {
|
||||
if (socketOptions) {
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -3,19 +3,14 @@
|
||||
*/
|
||||
export const actionsDenylist = [
|
||||
// very spammy canvas actions
|
||||
'canvas/setCursorPosition',
|
||||
'canvas/setStageCoordinates',
|
||||
'canvas/setStageScale',
|
||||
'canvas/setIsDrawing',
|
||||
'canvas/setBoundingBoxCoordinates',
|
||||
'canvas/setBoundingBoxDimensions',
|
||||
'canvas/setIsDrawing',
|
||||
'canvas/addPointToCurrentLine',
|
||||
// bazillions during generation
|
||||
'socket/socketGeneratorProgress',
|
||||
'socket/appSocketGeneratorProgress',
|
||||
// every time user presses shift
|
||||
// 'hotkeys/shiftKeyPressed',
|
||||
// this happens after every state change
|
||||
'@@REMEMBER_PERSISTED',
|
||||
];
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
},
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -69,10 +69,12 @@ const predicate: AnyListenerPredicate<RootState> = (
|
||||
return isProcessorSelected && hasControlImage;
|
||||
};
|
||||
|
||||
const DEBOUNCE_MS = 300;
|
||||
|
||||
/**
|
||||
* Listener that automatically processes a ControlNet image when its processor parameters are changed.
|
||||
*
|
||||
* The network request is debounced by 1 second.
|
||||
* The network request is debounced.
|
||||
*/
|
||||
export const addControlNetAutoProcessListener = () => {
|
||||
startAppListening({
|
||||
@@ -85,7 +87,7 @@ export const addControlNetAutoProcessListener = () => {
|
||||
cancelActiveListeners();
|
||||
log.trace('ControlNet auto-process triggered');
|
||||
// Delay before starting actual work
|
||||
await delay(300);
|
||||
await delay(DEBOUNCE_MS);
|
||||
|
||||
dispatch(controlAdapterImageProcessed({ id }));
|
||||
},
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -13,7 +13,10 @@ import type {
|
||||
import { imageSelected } from 'features/gallery/store/gallerySlice';
|
||||
import { fieldImageValueChanged } from 'features/nodes/store/nodesSlice';
|
||||
import { workflowExposedFieldAdded } from 'features/nodes/store/workflowSlice';
|
||||
import { initialImageChanged } from 'features/parameters/store/generationSlice';
|
||||
import {
|
||||
initialImageChanged,
|
||||
selectOptimalDimension,
|
||||
} from 'features/parameters/store/generationSlice';
|
||||
import { imagesApi } from 'services/api/endpoints/images';
|
||||
|
||||
import { startAppListening } from '../';
|
||||
@@ -26,7 +29,7 @@ export const dndDropped = createAction<{
|
||||
export const addImageDroppedListener = () => {
|
||||
startAppListening({
|
||||
actionCreator: dndDropped,
|
||||
effect: async (action, { dispatch }) => {
|
||||
effect: async (action, { dispatch, getState }) => {
|
||||
const log = logger('dnd');
|
||||
const { activeData, overData } = action.payload;
|
||||
|
||||
@@ -115,7 +118,12 @@ export const addImageDroppedListener = () => {
|
||||
activeData.payloadType === 'IMAGE_DTO' &&
|
||||
activeData.payload.imageDTO
|
||||
) {
|
||||
dispatch(setInitialCanvasImage(activeData.payload.imageDTO));
|
||||
dispatch(
|
||||
setInitialCanvasImage(
|
||||
activeData.payload.imageDTO,
|
||||
selectOptimalDimension(getState())
|
||||
)
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,10 @@ import {
|
||||
controlAdapterIsEnabledChanged,
|
||||
} from 'features/controlAdapters/store/controlAdaptersSlice';
|
||||
import { fieldImageValueChanged } from 'features/nodes/store/nodesSlice';
|
||||
import { initialImageChanged } from 'features/parameters/store/generationSlice';
|
||||
import {
|
||||
initialImageChanged,
|
||||
selectOptimalDimension,
|
||||
} from 'features/parameters/store/generationSlice';
|
||||
import { addToast } from 'features/system/store/systemSlice';
|
||||
import { t } from 'i18next';
|
||||
import { omit } from 'lodash-es';
|
||||
@@ -76,7 +79,9 @@ export const addImageUploadedFulfilledListener = () => {
|
||||
}
|
||||
|
||||
if (postUploadAction?.type === 'SET_CANVAS_INITIAL_IMAGE') {
|
||||
dispatch(setInitialCanvasImage(imageDTO));
|
||||
dispatch(
|
||||
setInitialCanvasImage(imageDTO, selectOptimalDimension(state))
|
||||
);
|
||||
dispatch(
|
||||
addToast({
|
||||
...DEFAULT_UPLOADED_TOAST,
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { logger } from 'app/logging/logger';
|
||||
import { setBoundingBoxDimensions } from 'features/canvas/store/canvasSlice';
|
||||
import {
|
||||
controlAdapterIsEnabledChanged,
|
||||
selectControlAdapterAll,
|
||||
@@ -7,10 +6,8 @@ import {
|
||||
import { loraRemoved } from 'features/lora/store/loraSlice';
|
||||
import { modelSelected } from 'features/parameters/store/actions';
|
||||
import {
|
||||
heightChanged,
|
||||
modelChanged,
|
||||
vaeSelected,
|
||||
widthChanged,
|
||||
} from 'features/parameters/store/generationSlice';
|
||||
import { zParameterModel } from 'features/parameters/types/parameterSchemas';
|
||||
import { addToast } from 'features/system/store/systemSlice';
|
||||
@@ -84,22 +81,6 @@ export const addModelSelectedListener = () => {
|
||||
}
|
||||
}
|
||||
|
||||
// Update Width / Height / Bounding Box Dimensions on Model Change
|
||||
if (
|
||||
state.generation.model?.base_model !== newModel.base_model &&
|
||||
state.ui.shouldAutoChangeDimensions
|
||||
) {
|
||||
if (['sdxl', 'sdxl-refiner'].includes(newModel.base_model)) {
|
||||
dispatch(widthChanged(1024));
|
||||
dispatch(heightChanged(1024));
|
||||
dispatch(setBoundingBoxDimensions({ width: 1024, height: 1024 }));
|
||||
} else {
|
||||
dispatch(widthChanged(512));
|
||||
dispatch(heightChanged(512));
|
||||
dispatch(setBoundingBoxDimensions({ width: 512, height: 512 }));
|
||||
}
|
||||
}
|
||||
|
||||
dispatch(modelChanged(newModel));
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
import { atom } from 'nanostores';
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
export const $customNavComponent = atom<ReactNode | undefined>(undefined);
|
||||
@@ -1,4 +1,4 @@
|
||||
import { atom } from 'nanostores';
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
export const $headerComponent = atom<ReactNode | undefined>(undefined);
|
||||
export const $galleryHeader = atom<ReactNode | undefined>(undefined);
|
||||
4
invokeai/frontend/web/src/app/store/nanostores/logo.ts
Normal file
4
invokeai/frontend/web/src/app/store/nanostores/logo.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import { atom } from 'nanostores';
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
export const $logo = atom<ReactNode | undefined>(undefined);
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -65,7 +65,11 @@ export const IAINoContentFallback = memo((props: IAINoImageFallbackProps) => {
|
||||
return (
|
||||
<Flex sx={styles} {...rest}>
|
||||
{icon && <Icon as={icon} boxSize={boxSize} opacity={0.7} />}
|
||||
{props.label && <InvText textAlign="center">{props.label}</InvText>}
|
||||
{props.label && (
|
||||
<InvText textAlign="center" fontSize="md">
|
||||
{props.label}
|
||||
</InvText>
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -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]);
|
||||
|
||||
@@ -1,28 +1,47 @@
|
||||
import { Box, Flex, Heading } from '@chakra-ui/react';
|
||||
import type { AnimationProps } from 'framer-motion';
|
||||
import { motion } from 'framer-motion';
|
||||
import { memo } from 'react';
|
||||
import type { DropzoneState } from 'react-dropzone';
|
||||
import { useHotkeys } from 'react-hotkeys-hook';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const initial: AnimationProps['initial'] = {
|
||||
opacity: 0,
|
||||
};
|
||||
const animate: AnimationProps['animate'] = {
|
||||
opacity: 1,
|
||||
transition: { duration: 0.1 },
|
||||
};
|
||||
const exit: AnimationProps['exit'] = {
|
||||
opacity: 0,
|
||||
transition: { duration: 0.1 },
|
||||
};
|
||||
|
||||
type ImageUploadOverlayProps = {
|
||||
isDragAccept: boolean;
|
||||
isDragReject: boolean;
|
||||
dropzone: DropzoneState;
|
||||
setIsHandlingUpload: (isHandlingUpload: boolean) => void;
|
||||
};
|
||||
|
||||
const ImageUploadOverlay = (props: ImageUploadOverlayProps) => {
|
||||
const { t } = useTranslation();
|
||||
const {
|
||||
isDragAccept,
|
||||
isDragReject: _isDragAccept,
|
||||
setIsHandlingUpload,
|
||||
} = props;
|
||||
const { dropzone, setIsHandlingUpload } = props;
|
||||
|
||||
useHotkeys('esc', () => {
|
||||
setIsHandlingUpload(false);
|
||||
});
|
||||
useHotkeys(
|
||||
'esc',
|
||||
() => {
|
||||
setIsHandlingUpload(false);
|
||||
},
|
||||
[setIsHandlingUpload]
|
||||
);
|
||||
|
||||
return (
|
||||
<Box
|
||||
key="image-upload-overlay"
|
||||
initial={initial}
|
||||
animate={animate}
|
||||
exit={exit}
|
||||
as={motion.div}
|
||||
position="absolute"
|
||||
top={0}
|
||||
insetInlineStart={0}
|
||||
@@ -67,7 +86,7 @@ const ImageUploadOverlay = (props: ImageUploadOverlayProps) => {
|
||||
color="base.100"
|
||||
borderColor="base.200"
|
||||
>
|
||||
{isDragAccept ? (
|
||||
{dropzone.isDragAccept ? (
|
||||
<Heading size="lg">{t('gallery.dropToUpload')}</Heading>
|
||||
) : (
|
||||
<>
|
||||
|
||||
@@ -21,7 +21,7 @@ export const InvAccordionButton = (props: InvAccordionButtonProps) => {
|
||||
{children}
|
||||
<Spacer />
|
||||
{badges?.map((b, i) => (
|
||||
<InvBadge key={`${b}.${i}`} variant="solid">
|
||||
<InvBadge key={`${b}.${i}`} colorScheme="invokeBlue">
|
||||
{b}
|
||||
</InvBadge>
|
||||
))}
|
||||
|
||||
@@ -23,7 +23,7 @@ const invokeAIButton = defineStyle((_props) => {
|
||||
fontSize: 'sm',
|
||||
border: 'none',
|
||||
borderRadius: 'base',
|
||||
color: 'base.400',
|
||||
color: 'base.300',
|
||||
_hover: {},
|
||||
_expanded: {
|
||||
borderBottomRadius: 'none',
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import { defineStyle, defineStyleConfig } from '@chakra-ui/react';
|
||||
|
||||
const baseStyle = defineStyle((props) => ({
|
||||
fontSize: '9px',
|
||||
fontSize: 9,
|
||||
px: 2,
|
||||
py: 1,
|
||||
minW: 4,
|
||||
lineHeight: 1,
|
||||
borderRadius: 'sm',
|
||||
bg: `${props.colorScheme}.300`,
|
||||
bg: `${props.colorScheme}.200`,
|
||||
color: 'base.900',
|
||||
fontWeight: 'bold',
|
||||
letterSpacing: 0.5,
|
||||
letterSpacing: 0.6,
|
||||
wordBreak: 'break-all',
|
||||
whiteSpace: 'nowrap',
|
||||
textOverflow: 'ellipsis',
|
||||
@@ -20,7 +20,6 @@ const baseStyle = defineStyle((props) => ({
|
||||
export const badgeTheme = defineStyleConfig({
|
||||
baseStyle,
|
||||
defaultProps: {
|
||||
variant: 'solid',
|
||||
colorScheme: 'base',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -204,7 +204,7 @@ export const buttonTheme = defineStyleConfig({
|
||||
_hover: {
|
||||
bg: 'none',
|
||||
svg: {
|
||||
fill: 'base.500',
|
||||
fill: 'base.400',
|
||||
},
|
||||
},
|
||||
'&[data-selected="true"]': {
|
||||
|
||||
@@ -13,7 +13,7 @@ export const InvLabel = memo(
|
||||
ref
|
||||
) => {
|
||||
const shouldEnableInformationalPopovers = useAppSelector(
|
||||
(state) => state.system.shouldEnableInformationalPopovers
|
||||
(s) => s.system.shouldEnableInformationalPopovers
|
||||
);
|
||||
|
||||
const ctx = useContext(InvControlGroupContext);
|
||||
|
||||
@@ -1,33 +1,57 @@
|
||||
import { Divider, Flex } from '@chakra-ui/layout';
|
||||
import type { SystemStyleObject } from '@chakra-ui/react';
|
||||
import { Collapse, Icon, useDisclosure } from '@chakra-ui/react';
|
||||
import type { InvExpanderProps } from 'common/components/InvExpander/types';
|
||||
import { InvText } from 'common/components/InvText/wrapper';
|
||||
import { t } from 'i18next';
|
||||
import { BiCollapseVertical, BiExpandVertical } from 'react-icons/bi';
|
||||
|
||||
const buttonStyles: SystemStyleObject = {
|
||||
color: 'base.400',
|
||||
borderColor: 'base.400',
|
||||
transitionDuration: 'normal',
|
||||
transitionProperty: 'common',
|
||||
':hover, :hover *': {
|
||||
transitionDuration: 'normal',
|
||||
transitionProperty: 'common',
|
||||
color: 'base.300',
|
||||
borderColor: 'base.300',
|
||||
},
|
||||
};
|
||||
|
||||
export const InvExpander = ({
|
||||
children,
|
||||
label = t('common.advancedOptions'),
|
||||
defaultIsOpen = false,
|
||||
}: InvExpanderProps) => {
|
||||
const { isOpen, onToggle } = useDisclosure({ defaultIsOpen });
|
||||
|
||||
return (
|
||||
<Flex flexDir="column" w="full">
|
||||
<Flex flexDir="row" alignItems="center" gap={3} py={4}>
|
||||
<Divider w="unset" flexGrow={1} />
|
||||
<Flex
|
||||
as="button"
|
||||
onClick={onToggle}
|
||||
flexDir="row"
|
||||
alignItems="center"
|
||||
gap={2}
|
||||
>
|
||||
<Flex
|
||||
as="button"
|
||||
flexDir="row"
|
||||
alignItems="center"
|
||||
gap={3}
|
||||
py={4}
|
||||
px={2}
|
||||
onClick={onToggle}
|
||||
sx={buttonStyles}
|
||||
>
|
||||
<Divider w="unset" flexGrow={1} sx={buttonStyles} />
|
||||
<Flex flexDir="row" alignItems="center" gap={2}>
|
||||
<Icon
|
||||
as={isOpen ? BiCollapseVertical : BiExpandVertical}
|
||||
fontSize="12px"
|
||||
color="base.400"
|
||||
fontSize="14px"
|
||||
sx={buttonStyles}
|
||||
/>
|
||||
<InvText variant="subtext" fontSize="sm" flexShrink={0} mb="2px">
|
||||
<InvText
|
||||
variant="subtext"
|
||||
fontSize="sm"
|
||||
fontWeight="semibold"
|
||||
flexShrink={0}
|
||||
sx={buttonStyles}
|
||||
>
|
||||
{label}
|
||||
</InvText>
|
||||
</Flex>
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -16,6 +16,7 @@ export const baseStyle = definePartsStyle(() => ({
|
||||
header: {
|
||||
fontWeight: 'semibold',
|
||||
fontSize: 'lg',
|
||||
color: 'base.300',
|
||||
},
|
||||
closeButton: {
|
||||
opacity: 0.5,
|
||||
|
||||
@@ -1,35 +1,57 @@
|
||||
import { progressAnatomy as parts } from '@chakra-ui/anatomy';
|
||||
import {
|
||||
createMultiStyleConfigHelpers,
|
||||
defineStyle,
|
||||
} from '@chakra-ui/styled-system';
|
||||
import { createMultiStyleConfigHelpers } from '@chakra-ui/styled-system';
|
||||
import { generateStripe, getColorVar } from '@chakra-ui/theme-tools';
|
||||
|
||||
const { defineMultiStyleConfig, definePartsStyle } =
|
||||
createMultiStyleConfigHelpers(parts.keys);
|
||||
|
||||
const invokeAIFilledTrack = defineStyle((_props) => ({
|
||||
bg: 'invokeYellow.500',
|
||||
}));
|
||||
|
||||
const invokeAITrack = defineStyle((_props) => {
|
||||
return {
|
||||
bg: 'base.800',
|
||||
};
|
||||
});
|
||||
|
||||
const invokeAI = definePartsStyle((props) => ({
|
||||
filledTrack: invokeAIFilledTrack(props),
|
||||
track: invokeAITrack(props),
|
||||
}));
|
||||
|
||||
export const progressTheme = defineMultiStyleConfig({
|
||||
baseStyle: {
|
||||
track: { borderRadius: '2px' },
|
||||
},
|
||||
variants: {
|
||||
invokeAI,
|
||||
},
|
||||
defaultProps: {
|
||||
variant: 'invokeAI',
|
||||
},
|
||||
baseStyle: definePartsStyle(
|
||||
({ theme: t, colorScheme: c, hasStripe, isIndeterminate }) => {
|
||||
const bgColor = `${c}.300`;
|
||||
const addStripe = !isIndeterminate && hasStripe;
|
||||
const gradient = `linear-gradient(
|
||||
to right,
|
||||
transparent 0%,
|
||||
${getColorVar(t, bgColor)} 50%,
|
||||
transparent 100%
|
||||
)`;
|
||||
return {
|
||||
track: {
|
||||
borderRadius: '2px',
|
||||
bg: 'base.800',
|
||||
},
|
||||
filledTrack: {
|
||||
borderRadius: '2px',
|
||||
...(addStripe && generateStripe()),
|
||||
...(isIndeterminate ? { bgImage: gradient } : { bgColor }),
|
||||
},
|
||||
};
|
||||
}
|
||||
),
|
||||
});
|
||||
// export const progressTheme = defineMultiStyleConfig({
|
||||
// baseStyle: definePartsStyle(
|
||||
// ({ theme: t, colorScheme: c, hasStripe, isIndeterminate }) => {
|
||||
// const bgColor = `${c}.500`;
|
||||
// const addStripe = !isIndeterminate && hasStripe;
|
||||
// const gradient = `linear-gradient(
|
||||
// to right,
|
||||
// transparent 0%,
|
||||
// ${getColorVar(t, bgColor)} 50%,
|
||||
// transparent 100%
|
||||
// )`;
|
||||
// return {
|
||||
// track: {
|
||||
// borderRadius: '2px',
|
||||
// bg: 'base.800',
|
||||
// },
|
||||
// filledTrack: {
|
||||
// borderRadius: '2px',
|
||||
// ...(addStripe && generateStripe("xs")),
|
||||
// ...(isIndeterminate ? { bgImage: gradient } : { bgColor }),
|
||||
// },
|
||||
// };
|
||||
// }
|
||||
// ),
|
||||
// });
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -12,7 +12,7 @@ export const InvTab = memo(
|
||||
{children}
|
||||
<Spacer />
|
||||
{badges?.map((b, i) => (
|
||||
<InvBadge key={`${b}.${i}`} colorScheme="blue">
|
||||
<InvBadge key={`${b}.${i}`} colorScheme="invokeYellow">
|
||||
{b}
|
||||
</InvBadge>
|
||||
))}
|
||||
|
||||
@@ -3,6 +3,15 @@ import { memo } from 'react';
|
||||
|
||||
import type { InvTooltipProps } from './types';
|
||||
|
||||
const modifiers: InvTooltipProps['modifiers'] = [
|
||||
{
|
||||
name: 'preventOverflow',
|
||||
options: {
|
||||
padding: 12,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export const InvTooltip = memo(
|
||||
forwardRef<InvTooltipProps, typeof ChakraTooltip>(
|
||||
(props: InvTooltipProps, ref) => {
|
||||
@@ -12,6 +21,8 @@ export const InvTooltip = memo(
|
||||
ref={ref}
|
||||
hasArrow={hasArrow}
|
||||
placement={placement}
|
||||
arrowSize={8}
|
||||
modifiers={modifiers}
|
||||
{...rest}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -3,14 +3,15 @@ import { cssVar } from '@chakra-ui/theme-tools';
|
||||
|
||||
const $arrowBg = cssVar('popper-arrow-bg');
|
||||
|
||||
// define the base component styles
|
||||
const baseStyle = defineStyle(() => ({
|
||||
borderRadius: 'md',
|
||||
shadow: 'dark-lg',
|
||||
bg: 'base.200',
|
||||
color: 'base.800',
|
||||
[$arrowBg.variable]: 'colors.base.200',
|
||||
pb: 1.5,
|
||||
pt: 1,
|
||||
px: 2,
|
||||
pb: 1,
|
||||
}));
|
||||
|
||||
// export the component theme
|
||||
|
||||
@@ -1,31 +1,22 @@
|
||||
import { Box } from '@chakra-ui/react';
|
||||
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 type { AnimationProps } from 'framer-motion';
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
import type { PropsWithChildren } from 'react';
|
||||
import { memo, useCallback, useEffect, useState } from 'react';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import type { Accept, FileRejection } from 'react-dropzone';
|
||||
import { useDropzone } from 'react-dropzone';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useUploadImageMutation } from 'services/api/endpoints/images';
|
||||
import type { PostUploadAction } from 'services/api/types';
|
||||
|
||||
import ImageUploadOverlay from './ImageUploadOverlay';
|
||||
|
||||
const accept: Accept = {
|
||||
'image/png': ['.png'],
|
||||
'image/jpeg': ['.jpg', '.jpeg', '.png'],
|
||||
};
|
||||
|
||||
const dropzoneRootProps = { style: {} };
|
||||
|
||||
const selector = createMemoizedSelector(
|
||||
[stateSelector, activeTabNameSelector],
|
||||
({ gallery }, activeTabName) => {
|
||||
const selectPostUploadAction = createMemoizedSelector(
|
||||
activeTabNameSelector,
|
||||
(activeTabName) => {
|
||||
let postUploadAction: PostUploadAction = { type: 'TOAST' };
|
||||
|
||||
if (activeTabName === 'unifiedCanvas') {
|
||||
@@ -36,19 +27,15 @@ const selector = createMemoizedSelector(
|
||||
postUploadAction = { type: 'SET_INITIAL_IMAGE' };
|
||||
}
|
||||
|
||||
const { autoAddBoardId } = gallery;
|
||||
|
||||
return {
|
||||
autoAddBoardId,
|
||||
postUploadAction,
|
||||
};
|
||||
return postUploadAction;
|
||||
}
|
||||
);
|
||||
|
||||
const ImageUploader = (props: PropsWithChildren) => {
|
||||
const { autoAddBoardId, postUploadAction } = useAppSelector(selector);
|
||||
const toaster = useAppToaster();
|
||||
export const useFullscreenDropzone = () => {
|
||||
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();
|
||||
@@ -105,14 +92,7 @@ const ImageUploader = (props: PropsWithChildren) => {
|
||||
setIsHandlingUpload(true);
|
||||
}, []);
|
||||
|
||||
const {
|
||||
getRootProps,
|
||||
getInputProps,
|
||||
isDragAccept,
|
||||
isDragReject,
|
||||
isDragActive,
|
||||
inputRef,
|
||||
} = useDropzone({
|
||||
const dropzone = useDropzone({
|
||||
accept,
|
||||
noClick: true,
|
||||
onDrop,
|
||||
@@ -124,15 +104,17 @@ const ImageUploader = (props: PropsWithChildren) => {
|
||||
useEffect(() => {
|
||||
// This is a hack to allow pasting images into the uploader
|
||||
const handlePaste = async (e: ClipboardEvent) => {
|
||||
if (!inputRef.current) {
|
||||
if (!dropzone.inputRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.clipboardData?.files) {
|
||||
// Set the files on the inputRef
|
||||
inputRef.current.files = e.clipboardData.files;
|
||||
// Set the files on the dropzone.inputRef
|
||||
dropzone.inputRef.current.files = e.clipboardData.files;
|
||||
// Dispatch the change event, dropzone catches this and we get to use its own validation
|
||||
inputRef.current?.dispatchEvent(new Event('change', { bubbles: true }));
|
||||
dropzone.inputRef.current?.dispatchEvent(
|
||||
new Event('change', { bubbles: true })
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -142,42 +124,7 @@ const ImageUploader = (props: PropsWithChildren) => {
|
||||
return () => {
|
||||
document.removeEventListener('paste', handlePaste);
|
||||
};
|
||||
}, [inputRef]);
|
||||
}, [dropzone.inputRef]);
|
||||
|
||||
return (
|
||||
<Box {...getRootProps(dropzoneRootProps)}>
|
||||
<input {...getInputProps()} />
|
||||
{props.children}
|
||||
<AnimatePresence>
|
||||
{isDragActive && isHandlingUpload && (
|
||||
<motion.div
|
||||
key="image-upload-overlay"
|
||||
initial={initial}
|
||||
animate={animate}
|
||||
exit={exit}
|
||||
>
|
||||
<ImageUploadOverlay
|
||||
isDragAccept={isDragAccept}
|
||||
isDragReject={isDragReject}
|
||||
setIsHandlingUpload={setIsHandlingUpload}
|
||||
/>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(ImageUploader);
|
||||
|
||||
const initial: AnimationProps['initial'] = {
|
||||
opacity: 0,
|
||||
};
|
||||
const animate: AnimationProps['animate'] = {
|
||||
opacity: 1,
|
||||
transition: { duration: 0.1 },
|
||||
};
|
||||
const exit: AnimationProps['exit'] = {
|
||||
opacity: 0,
|
||||
transition: { duration: 0.1 },
|
||||
return { dropzone, isHandlingUpload, setIsHandlingUpload };
|
||||
};
|
||||
@@ -52,7 +52,7 @@ export const useGlobalHotkeys = () => {
|
||||
} = useCancelCurrentQueueItem();
|
||||
|
||||
useHotkeys(
|
||||
['shift+x', 'shift+enter'],
|
||||
['shift+x'],
|
||||
cancelQueueItem,
|
||||
{
|
||||
enabled: () => !isDisabledCancelQueueItem && !isLoadingCancelQueueItem,
|
||||
|
||||
@@ -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[]) => {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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}>
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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}>
|
||||
|
||||
@@ -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}>
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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')}
|
||||
|
||||
@@ -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 },
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
@@ -26,6 +24,8 @@ import {
|
||||
CANVAS_GRID_SIZE_COARSE,
|
||||
CANVAS_GRID_SIZE_FINE,
|
||||
} from 'features/canvas/store/constants';
|
||||
import { calculateNewSize } from 'features/parameters/components/ImageSize/calculateNewSize';
|
||||
import { selectOptimalDimension } from 'features/parameters/store/generationSlice';
|
||||
import type Konva from 'konva';
|
||||
import type { GroupConfig } from 'konva/lib/Group';
|
||||
import type { KonvaEventObject } from 'konva/lib/Node';
|
||||
@@ -36,45 +36,23 @@ import { Group, Rect, Transformer } from 'react-konva';
|
||||
|
||||
const borderDash = [4, 4];
|
||||
|
||||
const boundingBoxPreviewSelector = createMemoizedSelector(
|
||||
[stateSelector],
|
||||
({ canvas }) => {
|
||||
const {
|
||||
boundingBoxCoordinates,
|
||||
boundingBoxDimensions,
|
||||
stageScale,
|
||||
tool,
|
||||
shouldSnapToGrid,
|
||||
aspectRatio,
|
||||
} = canvas;
|
||||
|
||||
return {
|
||||
boundingBoxCoordinates,
|
||||
boundingBoxDimensions,
|
||||
stageScale,
|
||||
shouldSnapToGrid,
|
||||
tool,
|
||||
hitStrokeWidth: 20 / stageScale,
|
||||
aspectRatio,
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
type IAICanvasBoundingBoxPreviewProps = GroupConfig;
|
||||
|
||||
const IAICanvasBoundingBox = (props: IAICanvasBoundingBoxPreviewProps) => {
|
||||
const { ...rest } = props;
|
||||
const dispatch = useAppDispatch();
|
||||
const {
|
||||
boundingBoxCoordinates,
|
||||
boundingBoxDimensions,
|
||||
stageScale,
|
||||
shouldSnapToGrid,
|
||||
tool,
|
||||
hitStrokeWidth,
|
||||
aspectRatio,
|
||||
} = 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);
|
||||
@@ -140,72 +118,82 @@ const IAICanvasBoundingBox = (props: IAICanvasBoundingBoxPreviewProps) => {
|
||||
[dispatch, gridSize, shouldSnapToGrid]
|
||||
);
|
||||
|
||||
const handleOnTransform = useCallback(() => {
|
||||
/**
|
||||
* The Konva Transformer changes the object's anchor point and scale factor,
|
||||
* not its width and height. We need to un-scale the width and height before
|
||||
* setting the values.
|
||||
*/
|
||||
if (!shapeRef.current) {
|
||||
return;
|
||||
}
|
||||
const handleOnTransform = useCallback(
|
||||
(_e: KonvaEventObject<Event>) => {
|
||||
/**
|
||||
* The Konva Transformer changes the object's anchor point and scale factor,
|
||||
* not its width and height. We need to un-scale the width and height before
|
||||
* setting the values.
|
||||
*/
|
||||
if (!shapeRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
const rect = shapeRef.current;
|
||||
const rect = shapeRef.current;
|
||||
|
||||
const scaleX = rect.scaleX();
|
||||
const scaleY = rect.scaleY();
|
||||
const scaleX = rect.scaleX();
|
||||
const scaleY = rect.scaleY();
|
||||
|
||||
// undo the scaling
|
||||
const width = Math.round(rect.width() * scaleX);
|
||||
const height = Math.round(rect.height() * scaleY);
|
||||
// undo the scaling
|
||||
const width = Math.round(rect.width() * scaleX);
|
||||
const height = Math.round(rect.height() * scaleY);
|
||||
|
||||
const x = Math.round(rect.x());
|
||||
const y = Math.round(rect.y());
|
||||
const x = Math.round(rect.x());
|
||||
const y = Math.round(rect.y());
|
||||
|
||||
if (aspectRatio.isLocked) {
|
||||
const newDimensions = calculateNewSize(
|
||||
aspectRatio.value,
|
||||
width * height
|
||||
);
|
||||
dispatch(
|
||||
setBoundingBoxDimensions(
|
||||
{
|
||||
width: roundDownToMultipleMin(newDimensions.width, gridSize),
|
||||
height: roundDownToMultipleMin(newDimensions.height, gridSize),
|
||||
},
|
||||
optimalDimension
|
||||
)
|
||||
);
|
||||
} else {
|
||||
dispatch(
|
||||
setBoundingBoxDimensions(
|
||||
{
|
||||
width: roundDownToMultipleMin(width, gridSize),
|
||||
height: roundDownToMultipleMin(height, gridSize),
|
||||
},
|
||||
optimalDimension
|
||||
)
|
||||
);
|
||||
dispatch(
|
||||
aspectRatioChanged({
|
||||
isLocked: false,
|
||||
id: 'Free',
|
||||
value: width / height,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
if (aspectRatio.isLocked) {
|
||||
const newHeight = roundDownToMultipleMin(
|
||||
width / aspectRatio.value,
|
||||
gridSize
|
||||
);
|
||||
dispatch(
|
||||
setBoundingBoxDimensions({
|
||||
width: roundDownToMultipleMin(width, gridSize),
|
||||
height: newHeight,
|
||||
setBoundingBoxCoordinates({
|
||||
x: shouldSnapToGrid ? roundDownToMultiple(x, gridSize) : x,
|
||||
y: shouldSnapToGrid ? roundDownToMultiple(y, gridSize) : y,
|
||||
})
|
||||
);
|
||||
} else {
|
||||
dispatch(
|
||||
setBoundingBoxDimensions({
|
||||
width: roundDownToMultipleMin(width, gridSize),
|
||||
height: roundDownToMultipleMin(height, gridSize),
|
||||
})
|
||||
);
|
||||
dispatch(
|
||||
aspectRatioChanged({
|
||||
isLocked: false,
|
||||
id: 'Free',
|
||||
value: width / height,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
dispatch(
|
||||
setBoundingBoxCoordinates({
|
||||
x: shouldSnapToGrid ? roundDownToMultiple(x, gridSize) : x,
|
||||
y: shouldSnapToGrid ? roundDownToMultiple(y, gridSize) : y,
|
||||
})
|
||||
);
|
||||
|
||||
// Reset the scale now that the coords/dimensions have been un-scaled
|
||||
rect.scaleX(1);
|
||||
rect.scaleY(1);
|
||||
}, [
|
||||
aspectRatio.isLocked,
|
||||
aspectRatio.value,
|
||||
dispatch,
|
||||
shouldSnapToGrid,
|
||||
gridSize,
|
||||
]);
|
||||
// Reset the scale now that the coords/dimensions have been un-scaled
|
||||
rect.scaleX(1);
|
||||
rect.scaleY(1);
|
||||
},
|
||||
[
|
||||
aspectRatio.isLocked,
|
||||
aspectRatio.value,
|
||||
dispatch,
|
||||
shouldSnapToGrid,
|
||||
gridSize,
|
||||
optimalDimension,
|
||||
]
|
||||
);
|
||||
|
||||
const anchorDragBoundFunc = useCallback(
|
||||
(
|
||||
@@ -233,7 +221,6 @@ const IAICanvasBoundingBox = (props: IAICanvasBoundingBoxPreviewProps) => {
|
||||
x: roundDownToMultiple(newPos.x, scaledStep) + offsetX,
|
||||
y: roundDownToMultiple(newPos.y, scaledStep) + offsetY,
|
||||
};
|
||||
console.log({ oldPos, newPos, newCoordinates });
|
||||
|
||||
return newCoordinates;
|
||||
},
|
||||
@@ -311,6 +298,18 @@ const IAICanvasBoundingBox = (props: IAICanvasBoundingBoxPreviewProps) => {
|
||||
stageScale,
|
||||
]);
|
||||
|
||||
const enabledAnchors = useMemo(() => {
|
||||
if (tool !== 'move') {
|
||||
return emptyArray;
|
||||
}
|
||||
if (aspectRatio.isLocked) {
|
||||
// TODO: The math to resize the bbox when locked and using other handles is confusing.
|
||||
// Workaround for now is to only allow resizing from the bottom-right handle.
|
||||
return ['bottom-right'];
|
||||
}
|
||||
return undefined;
|
||||
}, [aspectRatio.isLocked, tool]);
|
||||
|
||||
return (
|
||||
<Group {...rest}>
|
||||
<Rect
|
||||
@@ -356,7 +355,7 @@ const IAICanvasBoundingBox = (props: IAICanvasBoundingBoxPreviewProps) => {
|
||||
borderEnabled={true}
|
||||
borderStroke="black"
|
||||
draggable={false}
|
||||
enabledAnchors={tool === 'move' ? undefined : emptyArray}
|
||||
enabledAnchors={enabledAnchors}
|
||||
flipEnabled={false}
|
||||
ignoreStroke={true}
|
||||
keepRatio={false}
|
||||
|
||||
@@ -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'],
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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'],
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'],
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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)
|
||||
);
|
||||
|
||||
@@ -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,
|
||||
@@ -11,6 +12,12 @@ import floorCoordinates from 'features/canvas/util/floorCoordinates';
|
||||
import getScaledBoundingBoxDimensions from 'features/canvas/util/getScaledBoundingBoxDimensions';
|
||||
import roundDimensionsToMultiple from 'features/canvas/util/roundDimensionsToMultiple';
|
||||
import type { AspectRatioState } from 'features/parameters/components/ImageSize/types';
|
||||
import { modelChanged } from 'features/parameters/store/generationSlice';
|
||||
import type { PayloadActionWithOptimalDimension } from 'features/parameters/store/types';
|
||||
import {
|
||||
getIsSizeOptimal,
|
||||
getOptimalDimension,
|
||||
} from 'features/parameters/util/optimalDimension';
|
||||
import type { IRect, Vector2d } from 'konva/lib/types';
|
||||
import { clamp, cloneDeep } from 'lodash-es';
|
||||
import type { RgbaColor } from 'react-colorful';
|
||||
@@ -86,6 +93,29 @@ export const initialCanvasState: CanvasState = {
|
||||
},
|
||||
};
|
||||
|
||||
const setBoundingBoxDimensionsReducer = (
|
||||
state: CanvasState,
|
||||
payload: Partial<Dimensions>,
|
||||
optimalDimension: number
|
||||
) => {
|
||||
const boundingBoxDimensions = payload;
|
||||
const newDimensions = roundDimensionsToMultiple(
|
||||
{
|
||||
...state.boundingBoxDimensions,
|
||||
...boundingBoxDimensions,
|
||||
},
|
||||
CANVAS_GRID_SIZE_FINE
|
||||
);
|
||||
state.boundingBoxDimensions = newDimensions;
|
||||
if (state.boundingBoxScaleMethod === 'auto') {
|
||||
const scaledDimensions = getScaledBoundingBoxDimensions(
|
||||
newDimensions,
|
||||
optimalDimension
|
||||
);
|
||||
state.scaledBoundingBoxDimensions = scaledDimensions;
|
||||
}
|
||||
};
|
||||
|
||||
export const canvasSlice = createSlice({
|
||||
name: 'canvas',
|
||||
initialState: initialCanvasState,
|
||||
@@ -132,117 +162,90 @@ export const canvasSlice = createSlice({
|
||||
state.isMaskEnabled = action.payload;
|
||||
state.layer = action.payload ? 'mask' : 'base';
|
||||
},
|
||||
setInitialCanvasImage: (state, action: PayloadAction<ImageDTO>) => {
|
||||
const image = action.payload;
|
||||
const { width, height } = image;
|
||||
const { stageDimensions } = state;
|
||||
setInitialCanvasImage: {
|
||||
reducer: (state, action: PayloadActionWithOptimalDimension<ImageDTO>) => {
|
||||
const { width, height, image_name } = action.payload;
|
||||
const { optimalDimension } = action.meta;
|
||||
const { stageDimensions } = state;
|
||||
|
||||
const newBoundingBoxDimensions = {
|
||||
width: roundDownToMultiple(
|
||||
clamp(width, CANVAS_GRID_SIZE_FINE, 512),
|
||||
CANVAS_GRID_SIZE_FINE
|
||||
),
|
||||
height: roundDownToMultiple(
|
||||
clamp(height, CANVAS_GRID_SIZE_FINE, 512),
|
||||
CANVAS_GRID_SIZE_FINE
|
||||
),
|
||||
};
|
||||
const newBoundingBoxDimensions = {
|
||||
width: roundDownToMultiple(
|
||||
clamp(width, CANVAS_GRID_SIZE_FINE, optimalDimension),
|
||||
CANVAS_GRID_SIZE_FINE
|
||||
),
|
||||
height: roundDownToMultiple(
|
||||
clamp(height, CANVAS_GRID_SIZE_FINE, optimalDimension),
|
||||
CANVAS_GRID_SIZE_FINE
|
||||
),
|
||||
};
|
||||
|
||||
const newBoundingBoxCoordinates = {
|
||||
x: roundToMultiple(
|
||||
width / 2 - newBoundingBoxDimensions.width / 2,
|
||||
CANVAS_GRID_SIZE_FINE
|
||||
),
|
||||
y: roundToMultiple(
|
||||
height / 2 - newBoundingBoxDimensions.height / 2,
|
||||
CANVAS_GRID_SIZE_FINE
|
||||
),
|
||||
};
|
||||
const newBoundingBoxCoordinates = {
|
||||
x: roundToMultiple(
|
||||
width / 2 - newBoundingBoxDimensions.width / 2,
|
||||
CANVAS_GRID_SIZE_FINE
|
||||
),
|
||||
y: roundToMultiple(
|
||||
height / 2 - newBoundingBoxDimensions.height / 2,
|
||||
CANVAS_GRID_SIZE_FINE
|
||||
),
|
||||
};
|
||||
|
||||
if (state.boundingBoxScaleMethod === 'auto') {
|
||||
const scaledDimensions = getScaledBoundingBoxDimensions(
|
||||
newBoundingBoxDimensions
|
||||
if (state.boundingBoxScaleMethod === 'auto') {
|
||||
const scaledDimensions = getScaledBoundingBoxDimensions(
|
||||
newBoundingBoxDimensions,
|
||||
optimalDimension
|
||||
);
|
||||
state.scaledBoundingBoxDimensions = scaledDimensions;
|
||||
}
|
||||
|
||||
state.boundingBoxDimensions = newBoundingBoxDimensions;
|
||||
state.boundingBoxCoordinates = newBoundingBoxCoordinates;
|
||||
|
||||
state.pastLayerStates.push(cloneDeep(state.layerState));
|
||||
|
||||
state.layerState = {
|
||||
...cloneDeep(initialLayerState),
|
||||
objects: [
|
||||
{
|
||||
kind: 'image',
|
||||
layer: 'base',
|
||||
x: 0,
|
||||
y: 0,
|
||||
width,
|
||||
height,
|
||||
imageName: image_name,
|
||||
},
|
||||
],
|
||||
};
|
||||
state.futureLayerStates = [];
|
||||
state.batchIds = [];
|
||||
|
||||
const newScale = calculateScale(
|
||||
stageDimensions.width,
|
||||
stageDimensions.height,
|
||||
width,
|
||||
height,
|
||||
STAGE_PADDING_PERCENTAGE
|
||||
);
|
||||
state.scaledBoundingBoxDimensions = scaledDimensions;
|
||||
}
|
||||
|
||||
state.boundingBoxDimensions = newBoundingBoxDimensions;
|
||||
state.boundingBoxCoordinates = newBoundingBoxCoordinates;
|
||||
|
||||
state.pastLayerStates.push(cloneDeep(state.layerState));
|
||||
|
||||
state.layerState = {
|
||||
...cloneDeep(initialLayerState),
|
||||
objects: [
|
||||
{
|
||||
kind: 'image',
|
||||
layer: 'base',
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: width,
|
||||
height: height,
|
||||
imageName: image.image_name,
|
||||
},
|
||||
],
|
||||
};
|
||||
state.futureLayerStates = [];
|
||||
state.batchIds = [];
|
||||
|
||||
const newScale = calculateScale(
|
||||
stageDimensions.width,
|
||||
stageDimensions.height,
|
||||
width,
|
||||
height,
|
||||
STAGE_PADDING_PERCENTAGE
|
||||
);
|
||||
|
||||
const newCoordinates = calculateCoordinates(
|
||||
stageDimensions.width,
|
||||
stageDimensions.height,
|
||||
0,
|
||||
0,
|
||||
width,
|
||||
height,
|
||||
newScale
|
||||
);
|
||||
state.stageScale = newScale;
|
||||
state.stageCoordinates = newCoordinates;
|
||||
},
|
||||
setBoundingBoxDimensions: (
|
||||
state,
|
||||
action: PayloadAction<Partial<Dimensions>>
|
||||
) => {
|
||||
const newDimensions = roundDimensionsToMultiple(
|
||||
{
|
||||
...state.boundingBoxDimensions,
|
||||
...action.payload,
|
||||
const newCoordinates = calculateCoordinates(
|
||||
stageDimensions.width,
|
||||
stageDimensions.height,
|
||||
0,
|
||||
0,
|
||||
width,
|
||||
height,
|
||||
newScale
|
||||
);
|
||||
state.stageScale = newScale;
|
||||
state.stageCoordinates = newCoordinates;
|
||||
},
|
||||
prepare: (payload: ImageDTO, optimalDimension: number) => ({
|
||||
payload,
|
||||
meta: {
|
||||
optimalDimension,
|
||||
},
|
||||
CANVAS_GRID_SIZE_FINE
|
||||
);
|
||||
state.boundingBoxDimensions = newDimensions;
|
||||
|
||||
if (state.boundingBoxScaleMethod === 'auto') {
|
||||
const scaledDimensions = getScaledBoundingBoxDimensions(newDimensions);
|
||||
state.scaledBoundingBoxDimensions = scaledDimensions;
|
||||
}
|
||||
},
|
||||
flipBoundingBoxAxes: (state) => {
|
||||
const [currWidth, currHeight] = [
|
||||
state.boundingBoxDimensions.width,
|
||||
state.boundingBoxDimensions.height,
|
||||
];
|
||||
const [currScaledWidth, currScaledHeight] = [
|
||||
state.scaledBoundingBoxDimensions.width,
|
||||
state.scaledBoundingBoxDimensions.height,
|
||||
];
|
||||
state.boundingBoxDimensions = {
|
||||
width: currHeight,
|
||||
height: currWidth,
|
||||
};
|
||||
state.scaledBoundingBoxDimensions = {
|
||||
width: currScaledHeight,
|
||||
height: currScaledWidth,
|
||||
};
|
||||
}),
|
||||
},
|
||||
setBoundingBoxCoordinates: (state, action: PayloadAction<Vector2d>) => {
|
||||
state.boundingBoxCoordinates = floorCoordinates(action.payload);
|
||||
@@ -518,38 +521,6 @@ export const canvasSlice = createSlice({
|
||||
|
||||
state.stageScale = newScale;
|
||||
state.stageCoordinates = newCoordinates;
|
||||
} else {
|
||||
const newScale = calculateScale(
|
||||
stageWidth,
|
||||
stageHeight,
|
||||
512,
|
||||
512,
|
||||
STAGE_PADDING_PERCENTAGE
|
||||
);
|
||||
|
||||
const newCoordinates = calculateCoordinates(
|
||||
stageWidth,
|
||||
stageHeight,
|
||||
0,
|
||||
0,
|
||||
512,
|
||||
512,
|
||||
newScale
|
||||
);
|
||||
|
||||
const newBoundingBoxDimensions = { width: 512, height: 512 };
|
||||
|
||||
state.stageScale = newScale;
|
||||
state.stageCoordinates = newCoordinates;
|
||||
state.boundingBoxCoordinates = { x: 0, y: 0 };
|
||||
state.boundingBoxDimensions = newBoundingBoxDimensions;
|
||||
|
||||
if (state.boundingBoxScaleMethod === 'auto') {
|
||||
const scaledDimensions = getScaledBoundingBoxDimensions(
|
||||
newBoundingBoxDimensions
|
||||
);
|
||||
state.scaledBoundingBoxDimensions = scaledDimensions;
|
||||
}
|
||||
}
|
||||
},
|
||||
nextStagingAreaImage: (state) => {
|
||||
@@ -601,75 +572,69 @@ export const canvasSlice = createSlice({
|
||||
state.shouldShowStagingImage = true;
|
||||
state.batchIds = [];
|
||||
},
|
||||
fitBoundingBoxToStage: (state) => {
|
||||
const {
|
||||
boundingBoxDimensions,
|
||||
boundingBoxCoordinates,
|
||||
stageDimensions,
|
||||
stageScale,
|
||||
} = state;
|
||||
const scaledStageWidth = stageDimensions.width / stageScale;
|
||||
const scaledStageHeight = stageDimensions.height / stageScale;
|
||||
setBoundingBoxScaleMethod: {
|
||||
reducer: (
|
||||
state,
|
||||
action: PayloadActionWithOptimalDimension<BoundingBoxScaleMethod>
|
||||
) => {
|
||||
const boundingBoxScaleMethod = action.payload;
|
||||
const { optimalDimension } = action.meta;
|
||||
state.boundingBoxScaleMethod = boundingBoxScaleMethod;
|
||||
|
||||
if (
|
||||
boundingBoxCoordinates.x < 0 ||
|
||||
boundingBoxCoordinates.x + boundingBoxDimensions.width >
|
||||
scaledStageWidth ||
|
||||
boundingBoxCoordinates.y < 0 ||
|
||||
boundingBoxCoordinates.y + boundingBoxDimensions.height >
|
||||
scaledStageHeight
|
||||
) {
|
||||
const newBoundingBoxDimensions = {
|
||||
width: roundDownToMultiple(
|
||||
clamp(scaledStageWidth, CANVAS_GRID_SIZE_FINE, 512),
|
||||
CANVAS_GRID_SIZE_FINE
|
||||
),
|
||||
height: roundDownToMultiple(
|
||||
clamp(scaledStageHeight, CANVAS_GRID_SIZE_FINE, 512),
|
||||
CANVAS_GRID_SIZE_FINE
|
||||
),
|
||||
};
|
||||
|
||||
const newBoundingBoxCoordinates = {
|
||||
x: roundToMultiple(
|
||||
scaledStageWidth / 2 - newBoundingBoxDimensions.width / 2,
|
||||
CANVAS_GRID_SIZE_FINE
|
||||
),
|
||||
y: roundToMultiple(
|
||||
scaledStageHeight / 2 - newBoundingBoxDimensions.height / 2,
|
||||
CANVAS_GRID_SIZE_FINE
|
||||
),
|
||||
};
|
||||
|
||||
state.boundingBoxDimensions = newBoundingBoxDimensions;
|
||||
state.boundingBoxCoordinates = newBoundingBoxCoordinates;
|
||||
|
||||
if (state.boundingBoxScaleMethod === 'auto') {
|
||||
if (boundingBoxScaleMethod === 'auto') {
|
||||
const scaledDimensions = getScaledBoundingBoxDimensions(
|
||||
newBoundingBoxDimensions
|
||||
state.boundingBoxDimensions,
|
||||
optimalDimension
|
||||
);
|
||||
state.scaledBoundingBoxDimensions = scaledDimensions;
|
||||
}
|
||||
}
|
||||
},
|
||||
setBoundingBoxScaleMethod: (
|
||||
state,
|
||||
action: PayloadAction<BoundingBoxScaleMethod>
|
||||
) => {
|
||||
state.boundingBoxScaleMethod = action.payload;
|
||||
|
||||
if (action.payload === 'auto') {
|
||||
const scaledDimensions = getScaledBoundingBoxDimensions(
|
||||
state.boundingBoxDimensions
|
||||
);
|
||||
state.scaledBoundingBoxDimensions = scaledDimensions;
|
||||
}
|
||||
},
|
||||
prepare: (payload: BoundingBoxScaleMethod, optimalDimension: number) => ({
|
||||
payload,
|
||||
meta: {
|
||||
optimalDimension,
|
||||
},
|
||||
}),
|
||||
},
|
||||
setScaledBoundingBoxDimensions: (
|
||||
state,
|
||||
action: PayloadAction<Dimensions>
|
||||
action: PayloadAction<Partial<Dimensions>>
|
||||
) => {
|
||||
state.scaledBoundingBoxDimensions = action.payload;
|
||||
state.scaledBoundingBoxDimensions = {
|
||||
...state.scaledBoundingBoxDimensions,
|
||||
...action.payload,
|
||||
};
|
||||
},
|
||||
setBoundingBoxDimensions: {
|
||||
reducer: (
|
||||
state,
|
||||
action: PayloadActionWithOptimalDimension<Partial<Dimensions>>
|
||||
) => {
|
||||
setBoundingBoxDimensionsReducer(
|
||||
state,
|
||||
action.payload,
|
||||
action.meta.optimalDimension
|
||||
);
|
||||
},
|
||||
prepare: (payload: Partial<Dimensions>, optimalDimension: number) => ({
|
||||
payload,
|
||||
meta: {
|
||||
optimalDimension,
|
||||
},
|
||||
}),
|
||||
},
|
||||
scaledBoundingBoxDimensionsReset: {
|
||||
reducer: (state, action: PayloadActionWithOptimalDimension) => {
|
||||
const scaledDimensions = getScaledBoundingBoxDimensions(
|
||||
state.boundingBoxDimensions,
|
||||
action.meta.optimalDimension
|
||||
);
|
||||
state.scaledBoundingBoxDimensions = scaledDimensions;
|
||||
},
|
||||
prepare: (payload: void, optimalDimension: number) => ({
|
||||
payload: undefined,
|
||||
meta: { optimalDimension },
|
||||
}),
|
||||
},
|
||||
setShouldShowStagingImage: (state, action: PayloadAction<boolean>) => {
|
||||
state.shouldShowStagingImage = action.payload;
|
||||
@@ -714,6 +679,22 @@ export const canvasSlice = createSlice({
|
||||
},
|
||||
},
|
||||
extraReducers: (builder) => {
|
||||
builder.addCase(modelChanged, (state, action) => {
|
||||
const optimalDimension = getOptimalDimension(action.payload);
|
||||
const { width, height } = state.boundingBoxDimensions;
|
||||
if (getIsSizeOptimal(width, height, optimalDimension)) {
|
||||
return;
|
||||
}
|
||||
setBoundingBoxDimensionsReducer(
|
||||
state,
|
||||
{
|
||||
width,
|
||||
height,
|
||||
},
|
||||
optimalDimension
|
||||
);
|
||||
});
|
||||
|
||||
builder.addCase(appSocketQueueItemStatusChanged, (state, action) => {
|
||||
const batch_status = action.payload.data.batch_status;
|
||||
if (!state.batchIds.includes(batch_status.batch_id)) {
|
||||
@@ -754,7 +735,6 @@ export const {
|
||||
commitColorPickerColor,
|
||||
commitStagingAreaImage,
|
||||
discardStagedImages,
|
||||
fitBoundingBoxToStage,
|
||||
nextStagingAreaImage,
|
||||
prevStagingAreaImage,
|
||||
redo,
|
||||
@@ -764,7 +744,6 @@ export const {
|
||||
setBoundingBoxDimensions,
|
||||
setBoundingBoxPreviewFill,
|
||||
setBoundingBoxScaleMethod,
|
||||
flipBoundingBoxAxes,
|
||||
setBrushColor,
|
||||
setBrushSize,
|
||||
setColorPickerColor,
|
||||
@@ -799,6 +778,9 @@ export const {
|
||||
canvasBatchIdAdded,
|
||||
canvasBatchIdsReset,
|
||||
aspectRatioChanged,
|
||||
scaledBoundingBoxDimensionsReset,
|
||||
} = canvasSlice.actions;
|
||||
|
||||
export default canvasSlice.reducer;
|
||||
|
||||
export const selectCanvasSlice = (state: RootState) => state.canvas;
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
@@ -2,19 +2,22 @@ import { roundToMultiple } from 'common/util/roundDownToMultiple';
|
||||
import type { Dimensions } from 'features/canvas/store/canvasTypes';
|
||||
import { CANVAS_GRID_SIZE_FINE } from 'features/canvas/store/constants';
|
||||
|
||||
const getScaledBoundingBoxDimensions = (dimensions: Dimensions) => {
|
||||
const getScaledBoundingBoxDimensions = (
|
||||
dimensions: Dimensions,
|
||||
optimalDimension: number
|
||||
) => {
|
||||
const { width, height } = dimensions;
|
||||
|
||||
const scaledDimensions = { width, height };
|
||||
const targetArea = 512 * 512;
|
||||
const targetArea = optimalDimension * optimalDimension;
|
||||
const aspectRatio = width / height;
|
||||
let currentArea = width * height;
|
||||
let maxDimension = 448;
|
||||
let maxDimension = optimalDimension - CANVAS_GRID_SIZE_FINE;
|
||||
while (currentArea < targetArea) {
|
||||
maxDimension += CANVAS_GRID_SIZE_FINE;
|
||||
if (width === height) {
|
||||
scaledDimensions.width = 512;
|
||||
scaledDimensions.height = 512;
|
||||
scaledDimensions.width = optimalDimension;
|
||||
scaledDimensions.height = optimalDimension;
|
||||
break;
|
||||
} else {
|
||||
if (aspectRatio > 1) {
|
||||
|
||||
@@ -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();
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user