mirror of
https://github.com/easydiffusion/easydiffusion.git
synced 2025-04-25 03:48:45 +02:00
Merge branch 'beta' into splash
This commit is contained in:
commit
7d936c72a4
.gitignore.prettierignore.prettierrc.jsonCHANGES.mdREADME.mdpackage.json
scripts
ui
easydiffusion
index.htmlmedia
css
js
plugins/ui
1
.gitignore
vendored
1
.gitignore
vendored
@ -3,3 +3,4 @@ installer
|
|||||||
installer.tar
|
installer.tar
|
||||||
dist
|
dist
|
||||||
.idea/*
|
.idea/*
|
||||||
|
node_modules/*
|
9
.prettierignore
Normal file
9
.prettierignore
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
*.min.*
|
||||||
|
*.py
|
||||||
|
*.json
|
||||||
|
*.html
|
||||||
|
/*
|
||||||
|
!/ui
|
||||||
|
/ui/easydiffusion
|
||||||
|
!/ui/plugins
|
||||||
|
!/ui/media
|
7
.prettierrc.json
Normal file
7
.prettierrc.json
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"printWidth": 120,
|
||||||
|
"tabWidth": 4,
|
||||||
|
"semi": false,
|
||||||
|
"arrowParens": "always",
|
||||||
|
"trailingComma": "es5"
|
||||||
|
}
|
14
CHANGES.md
14
CHANGES.md
@ -2,8 +2,9 @@
|
|||||||
|
|
||||||
## v2.5
|
## v2.5
|
||||||
### Major Changes
|
### Major Changes
|
||||||
- **Nearly twice as fast** - significantly faster speed of image generation. We're now pretty close to automatic1111's speed. Code contributions are welcome to make our project even faster: https://github.com/easydiffusion/sdkit/#is-it-fast
|
- **Nearly twice as fast** - significantly faster speed of image generation. Code contributions are welcome to make our project even faster: https://github.com/easydiffusion/sdkit/#is-it-fast
|
||||||
- **Mac M1/M2 support** - Experimental support for Mac M1/M2. Thanks @michaelgallacher, @JeLuf and vishae.
|
- **Mac M1/M2 support** - Experimental support for Mac M1/M2. Thanks @michaelgallacher, @JeLuf and vishae.
|
||||||
|
- **AMD support for Linux** - Experimental support for AMD GPUs on Linux. Thanks @DianaNites and @JeLuf.
|
||||||
- **Full support for Stable Diffusion 2.1 (including CPU)** - supports loading v1.4 or v2.0 or v2.1 models seamlessly. No need to enable "Test SD2", and no need to add `sd2_` to your SD 2.0 model file names. Works on CPU as well.
|
- **Full support for Stable Diffusion 2.1 (including CPU)** - supports loading v1.4 or v2.0 or v2.1 models seamlessly. No need to enable "Test SD2", and no need to add `sd2_` to your SD 2.0 model file names. Works on CPU as well.
|
||||||
- **Memory optimized Stable Diffusion 2.1** - you can now use Stable Diffusion 2.1 models, with the same low VRAM optimizations that we've always had for SD 1.4. Please note, the SD 2.0 and 2.1 models require more GPU and System RAM, as compared to the SD 1.4 and 1.5 models.
|
- **Memory optimized Stable Diffusion 2.1** - you can now use Stable Diffusion 2.1 models, with the same low VRAM optimizations that we've always had for SD 1.4. Please note, the SD 2.0 and 2.1 models require more GPU and System RAM, as compared to the SD 1.4 and 1.5 models.
|
||||||
- **11 new samplers!** - explore the new samplers, some of which can generate great images in less than 10 inference steps! We've added the Karras and UniPC samplers. Thanks @Schorny for the UniPC samplers.
|
- **11 new samplers!** - explore the new samplers, some of which can generate great images in less than 10 inference steps! We've added the Karras and UniPC samplers. Thanks @Schorny for the UniPC samplers.
|
||||||
@ -21,7 +22,18 @@
|
|||||||
Our focus continues to remain on an easy installation experience, and an easy user-interface. While still remaining pretty powerful, in terms of features and speed.
|
Our focus continues to remain on an easy installation experience, and an easy user-interface. While still remaining pretty powerful, in terms of features and speed.
|
||||||
|
|
||||||
### Detailed changelog
|
### Detailed changelog
|
||||||
|
* 2.5.37 - 19 May 2023 - (beta-only) Two more samplers: DDPM and DEIS. Also disables the samplers that aren't working yet in the Diffusers version. Thanks @ogmaresca.
|
||||||
|
* 2.5.37 - 19 May 2023 - (beta-only) Support CLIP-Skip. You can set this option under the models dropdown. Thanks @JeLuf.
|
||||||
|
* 2.5.37 - 19 May 2023 - (beta-only) More VRAM optimizations for all modes in diffusers. The VRAM usage for diffusers in "low" and "balanced" should now be equal or less than the non-diffusers version. Performs softmax in half precision, like sdkit does.
|
||||||
|
* 2.5.36 - 16 May 2023 - (beta-only) More VRAM optimizations for "balanced" VRAM usage mode.
|
||||||
|
* 2.5.36 - 11 May 2023 - (beta-only) More VRAM optimizations for "low" VRAM usage mode.
|
||||||
|
* 2.5.36 - 10 May 2023 - (beta-only) Bug fix for "meta" error when using a LoRA in 'low' VRAM usage mode.
|
||||||
|
* 2.5.35 - 8 May 2023 - Allow dragging a zoomed-in image (after opening an image with the "expand" button). Thanks @ogmaresca.
|
||||||
|
* 2.5.35 - 3 May 2023 - (beta-only) First round of VRAM Optimizations for the "Test Diffusers" version. This change significantly reduces the amount of VRAM used by the diffusers version during image generation. The VRAM usage is still not equal to the "non-diffusers" version, but more optimizations are coming soon.
|
||||||
|
* 2.5.34 - 22 Apr 2023 - Don't start the browser in an incognito new profile (on Windows). Thanks @JeLuf.
|
||||||
|
* 2.5.33 - 21 Apr 2023 - Install PyTorch 2.0 on new installations (on Windows and Linux).
|
||||||
* 2.5.32 - 19 Apr 2023 - Automatically check for black images, and set full-precision if necessary (for attn). This means custom models based on Stable Diffusion v2.1 will just work, without needing special command-line arguments or editing of yaml config files.
|
* 2.5.32 - 19 Apr 2023 - Automatically check for black images, and set full-precision if necessary (for attn). This means custom models based on Stable Diffusion v2.1 will just work, without needing special command-line arguments or editing of yaml config files.
|
||||||
|
* 2.5.32 - 18 Apr 2023 - Automatic support for AMD graphics cards on Linux. Thanks @DianaNites and @JeLuf.
|
||||||
* 2.5.31 - 10 Apr 2023 - Reduce VRAM usage while upscaling.
|
* 2.5.31 - 10 Apr 2023 - Reduce VRAM usage while upscaling.
|
||||||
* 2.5.31 - 6 Apr 2023 - Allow seeds upto `4,294,967,295`. Thanks @ogmaresca.
|
* 2.5.31 - 6 Apr 2023 - Allow seeds upto `4,294,967,295`. Thanks @ogmaresca.
|
||||||
* 2.5.31 - 6 Apr 2023 - Buttons to show the previous/next image in the image popup. Thanks @ogmaresca.
|
* 2.5.31 - 6 Apr 2023 - Buttons to show the previous/next image in the image popup. Thanks @ogmaresca.
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
# Easy Diffusion 2.5
|
# Easy Diffusion 2.5
|
||||||
### The easiest way to install and use [Stable Diffusion](https://github.com/CompVis/stable-diffusion) on your own computer.
|
### The easiest way to install and use [Stable Diffusion](https://github.com/CompVis/stable-diffusion) on your computer.
|
||||||
|
|
||||||
Does not require technical knowledge, does not require pre-installed software. 1-click install, powerful features, friendly community.
|
Does not require technical knowledge, does not require pre-installed software. 1-click install, powerful features, friendly community.
|
||||||
|
|
||||||
@ -16,6 +16,11 @@ Click the download button for your operating system:
|
|||||||
<a href="https://github.com/cmdr2/stable-diffusion-ui/releases/download/v2.5.24/Easy-Diffusion-Mac.zip"><img src="https://github.com/cmdr2/stable-diffusion-ui/raw/main/media/download-mac.png" width="200" /></a>
|
<a href="https://github.com/cmdr2/stable-diffusion-ui/releases/download/v2.5.24/Easy-Diffusion-Mac.zip"><img src="https://github.com/cmdr2/stable-diffusion-ui/raw/main/media/download-mac.png" width="200" /></a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
**Hardware requirements:**
|
||||||
|
- **Windows:** NVIDIA graphics card, or run on your CPU
|
||||||
|
- **Linux:** NVIDIA or AMD graphics card, or run on your CPU
|
||||||
|
- **Mac:** M1 or M2, or run on your CPU
|
||||||
|
|
||||||
The installer will take care of whatever is needed. If you face any problems, you can join the friendly [Discord community](https://discord.com/invite/u9yhsFmEkB) and ask for assistance.
|
The installer will take care of whatever is needed. If you face any problems, you can join the friendly [Discord community](https://discord.com/invite/u9yhsFmEkB) and ask for assistance.
|
||||||
|
|
||||||
## On Windows:
|
## On Windows:
|
||||||
@ -53,7 +58,7 @@ Just delete the `EasyDiffusion` folder to uninstall all the downloaded packages.
|
|||||||
|
|
||||||
### Image generation
|
### Image generation
|
||||||
- **Supports**: "*Text to Image*" and "*Image to Image*".
|
- **Supports**: "*Text to Image*" and "*Image to Image*".
|
||||||
- **19 Samplers**: `ddim`, `plms`, `heun`, `euler`, `euler_a`, `dpm2`, `dpm2_a`, `lms`, `dpm_solver_stability`, `dpmpp_2s_a`, `dpmpp_2m`, `dpmpp_sde`, `dpm_fast`, `dpm_adaptive`, `unipc_snr`, `unipc_tu`, `unipc_tq`, `unipc_snr_2`, `unipc_tu_2`.
|
- **21 Samplers**: `ddim`, `plms`, `heun`, `euler`, `euler_a`, `dpm2`, `dpm2_a`, `lms`, `dpm_solver_stability`, `dpmpp_2s_a`, `dpmpp_2m`, `dpmpp_sde`, `dpm_fast`, `dpm_adaptive`, `ddpm`, `deis`, `unipc_snr`, `unipc_tu`, `unipc_tq`, `unipc_snr_2`, `unipc_tu_2`.
|
||||||
- **In-Painting**: Specify areas of your image to paint into.
|
- **In-Painting**: Specify areas of your image to paint into.
|
||||||
- **Simple Drawing Tool**: Draw basic images to guide the AI, without needing an external drawing program.
|
- **Simple Drawing Tool**: Draw basic images to guide the AI, without needing an external drawing program.
|
||||||
- **Face Correction (GFPGAN)**
|
- **Face Correction (GFPGAN)**
|
||||||
|
9
package.json
Normal file
9
package.json
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"scripts": {
|
||||||
|
"prettier-fix": "npx prettier --write \"./**/*.js\"",
|
||||||
|
"prettier-check": "npx prettier --check \"./**/*.js\""
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"prettier": "^1.19.1"
|
||||||
|
}
|
||||||
|
}
|
@ -18,7 +18,7 @@ os_name = platform.system()
|
|||||||
modules_to_check = {
|
modules_to_check = {
|
||||||
"torch": ("1.11.0", "1.13.1", "2.0.0"),
|
"torch": ("1.11.0", "1.13.1", "2.0.0"),
|
||||||
"torchvision": ("0.12.0", "0.14.1", "0.15.1"),
|
"torchvision": ("0.12.0", "0.14.1", "0.15.1"),
|
||||||
"sdkit": "1.0.80",
|
"sdkit": "1.0.96",
|
||||||
"stable-diffusion-sdkit": "2.1.4",
|
"stable-diffusion-sdkit": "2.1.4",
|
||||||
"rich": "12.6.0",
|
"rich": "12.6.0",
|
||||||
"uvicorn": "0.19.0",
|
"uvicorn": "0.19.0",
|
||||||
@ -47,6 +47,11 @@ def install(module_name: str, module_version: str):
|
|||||||
module_version = "1.13.1+rocm5.2"
|
module_version = "1.13.1+rocm5.2"
|
||||||
elif module_name == "torchvision":
|
elif module_name == "torchvision":
|
||||||
module_version = "0.14.1+rocm5.2"
|
module_version = "0.14.1+rocm5.2"
|
||||||
|
elif os_name == "Darwin":
|
||||||
|
if module_name == "torch":
|
||||||
|
module_version = "1.13.1"
|
||||||
|
elif module_name == "torchvision":
|
||||||
|
module_version = "0.14.1"
|
||||||
|
|
||||||
install_cmd = f"python -m pip install --upgrade {module_name}=={module_version}"
|
install_cmd = f"python -m pip install --upgrade {module_name}=={module_version}"
|
||||||
if index_url:
|
if index_url:
|
||||||
@ -70,6 +75,10 @@ def init():
|
|||||||
if module_name in ("torch", "torchvision"):
|
if module_name in ("torch", "torchvision"):
|
||||||
if version(module_name) is None: # allow any torch version
|
if version(module_name) is None: # allow any torch version
|
||||||
requires_install = True
|
requires_install = True
|
||||||
|
elif os_name == "Darwin" and ( # force mac to downgrade from torch 2.0
|
||||||
|
version("torch").startswith("2.") or version("torchvision").startswith("0.15.")
|
||||||
|
):
|
||||||
|
requires_install = True
|
||||||
elif version(module_name) not in allowed_versions:
|
elif version(module_name) not in allowed_versions:
|
||||||
requires_install = True
|
requires_install = True
|
||||||
|
|
||||||
@ -121,10 +130,13 @@ def include_cuda_versions(module_versions: tuple) -> tuple:
|
|||||||
|
|
||||||
def is_amd_on_linux():
|
def is_amd_on_linux():
|
||||||
if os_name == "Linux":
|
if os_name == "Linux":
|
||||||
|
try:
|
||||||
with open("/proc/bus/pci/devices", "r") as f:
|
with open("/proc/bus/pci/devices", "r") as f:
|
||||||
device_info = f.read()
|
device_info = f.read()
|
||||||
if "amdgpu" in device_info and "nvidia" not in device_info:
|
if "amdgpu" in device_info and "nvidia" not in device_info:
|
||||||
return True
|
return True
|
||||||
|
except:
|
||||||
|
return False
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
45
scripts/get_config.py
Normal file
45
scripts/get_config.py
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import os
|
||||||
|
import argparse
|
||||||
|
|
||||||
|
# The config file is in the same directory as this script
|
||||||
|
config_directory = os.path.dirname(__file__)
|
||||||
|
config_yaml = os.path.join(config_directory, "config.yaml")
|
||||||
|
config_json = os.path.join(config_directory, "config.json")
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser(description='Get values from config file')
|
||||||
|
parser.add_argument('--default', dest='default', action='store',
|
||||||
|
help='default value, to be used if the setting is not defined in the config file')
|
||||||
|
parser.add_argument('key', metavar='key', nargs='+',
|
||||||
|
help='config key to return')
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
|
if os.path.isfile(config_yaml):
|
||||||
|
import yaml
|
||||||
|
with open(config_yaml, 'r') as configfile:
|
||||||
|
try:
|
||||||
|
config = yaml.safe_load(configfile)
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
exit()
|
||||||
|
elif os.path.isfile(config_json):
|
||||||
|
import json
|
||||||
|
with open(config_json, 'r') as configfile:
|
||||||
|
try:
|
||||||
|
config = json.load(configfile)
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
exit()
|
||||||
|
else:
|
||||||
|
config = {}
|
||||||
|
|
||||||
|
for k in args.key:
|
||||||
|
if k in config:
|
||||||
|
config = config[k]
|
||||||
|
else:
|
||||||
|
if args.default != None:
|
||||||
|
print(args.default)
|
||||||
|
exit()
|
||||||
|
|
||||||
|
print(config)
|
@ -12,6 +12,16 @@ if exist "scripts\user_config.bat" (
|
|||||||
@call scripts\user_config.bat
|
@call scripts\user_config.bat
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if exist "stable-diffusion\env" (
|
||||||
|
@set PYTHONPATH=%PYTHONPATH%;%cd%\stable-diffusion\env\lib\site-packages
|
||||||
|
)
|
||||||
|
|
||||||
|
if exist "scripts\get_config.py" (
|
||||||
|
@FOR /F "tokens=* USEBACKQ" %%F IN (`python scripts\get_config.py --default=main update_branch`) DO (
|
||||||
|
@SET update_branch=%%F
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
if "%update_branch%"=="" (
|
if "%update_branch%"=="" (
|
||||||
set update_branch=main
|
set update_branch=main
|
||||||
)
|
)
|
||||||
@ -58,6 +68,7 @@ if "%update_branch%"=="" (
|
|||||||
@copy sd-ui-files\scripts\on_sd_start.bat scripts\ /Y
|
@copy sd-ui-files\scripts\on_sd_start.bat scripts\ /Y
|
||||||
@copy sd-ui-files\scripts\check_modules.py scripts\ /Y
|
@copy sd-ui-files\scripts\check_modules.py scripts\ /Y
|
||||||
@copy sd-ui-files\scripts\check_models.py scripts\ /Y
|
@copy sd-ui-files\scripts\check_models.py scripts\ /Y
|
||||||
|
@copy sd-ui-files\scripts\get_config.py scripts\ /Y
|
||||||
@copy "sd-ui-files\scripts\Start Stable Diffusion UI.cmd" . /Y
|
@copy "sd-ui-files\scripts\Start Stable Diffusion UI.cmd" . /Y
|
||||||
@copy "sd-ui-files\scripts\Developer Console.cmd" . /Y
|
@copy "sd-ui-files\scripts\Developer Console.cmd" . /Y
|
||||||
|
|
||||||
|
@ -4,6 +4,8 @@ source ./scripts/functions.sh
|
|||||||
|
|
||||||
printf "\n\nEasy Diffusion\n\n"
|
printf "\n\nEasy Diffusion\n\n"
|
||||||
|
|
||||||
|
export PYTHONNOUSERSITE=y
|
||||||
|
|
||||||
if [ -f "scripts/config.sh" ]; then
|
if [ -f "scripts/config.sh" ]; then
|
||||||
source scripts/config.sh
|
source scripts/config.sh
|
||||||
fi
|
fi
|
||||||
@ -12,6 +14,11 @@ if [ -f "scripts/user_config.sh" ]; then
|
|||||||
source scripts/user_config.sh
|
source scripts/user_config.sh
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
export PYTHONPATH=$(pwd)/installer_files/env/lib/python3.8/site-packages:$(pwd)/stable-diffusion/env/lib/python3.8/site-packages
|
||||||
|
|
||||||
|
if [ -f "scripts/get_config.py" ]; then
|
||||||
|
export update_branch="$( python scripts/get_config.py --default=main update_branch )"
|
||||||
|
fi
|
||||||
|
|
||||||
if [ "$update_branch" == "" ]; then
|
if [ "$update_branch" == "" ]; then
|
||||||
export update_branch="main"
|
export update_branch="main"
|
||||||
@ -44,6 +51,7 @@ cp sd-ui-files/scripts/on_sd_start.sh scripts/
|
|||||||
cp sd-ui-files/scripts/bootstrap.sh scripts/
|
cp sd-ui-files/scripts/bootstrap.sh scripts/
|
||||||
cp sd-ui-files/scripts/check_modules.py scripts/
|
cp sd-ui-files/scripts/check_modules.py scripts/
|
||||||
cp sd-ui-files/scripts/check_models.py scripts/
|
cp sd-ui-files/scripts/check_models.py scripts/
|
||||||
|
cp sd-ui-files/scripts/get_config.py scripts/
|
||||||
cp sd-ui-files/scripts/start.sh .
|
cp sd-ui-files/scripts/start.sh .
|
||||||
cp sd-ui-files/scripts/developer_console.sh .
|
cp sd-ui-files/scripts/developer_console.sh .
|
||||||
cp sd-ui-files/scripts/functions.sh scripts/
|
cp sd-ui-files/scripts/functions.sh scripts/
|
||||||
|
@ -6,9 +6,10 @@
|
|||||||
@copy sd-ui-files\scripts\on_env_start.bat scripts\ /Y
|
@copy sd-ui-files\scripts\on_env_start.bat scripts\ /Y
|
||||||
@copy sd-ui-files\scripts\check_modules.py scripts\ /Y
|
@copy sd-ui-files\scripts\check_modules.py scripts\ /Y
|
||||||
@copy sd-ui-files\scripts\check_models.py scripts\ /Y
|
@copy sd-ui-files\scripts\check_models.py scripts\ /Y
|
||||||
|
@copy sd-ui-files\scripts\get_config.py scripts\ /Y
|
||||||
|
|
||||||
if exist "%cd%\profile" (
|
if exist "%cd%\profile" (
|
||||||
set USERPROFILE=%cd%\profile
|
set HF_HOME=%cd%\profile\.cache\huggingface
|
||||||
)
|
)
|
||||||
|
|
||||||
@rem set the correct installer path (current vs legacy)
|
@rem set the correct installer path (current vs legacy)
|
||||||
@ -103,14 +104,25 @@ call python --version
|
|||||||
|
|
||||||
@cd ..
|
@cd ..
|
||||||
@set SD_UI_PATH=%cd%\ui
|
@set SD_UI_PATH=%cd%\ui
|
||||||
|
|
||||||
|
@FOR /F "tokens=* USEBACKQ" %%F IN (`python scripts\get_config.py --default=9000 net listen_port`) DO (
|
||||||
|
@SET ED_BIND_PORT=%%F
|
||||||
|
)
|
||||||
|
|
||||||
|
@FOR /F "tokens=* USEBACKQ" %%F IN (`python scripts\get_config.py --default=False net listen_to_network`) DO (
|
||||||
|
if "%%F" EQU "True" (
|
||||||
|
@SET ED_BIND_IP=0.0.0.0
|
||||||
|
) else (
|
||||||
|
@SET ED_BIND_IP=127.0.0.1
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
@cd stable-diffusion
|
@cd stable-diffusion
|
||||||
|
|
||||||
@rem set any overrides
|
@rem set any overrides
|
||||||
set HF_HUB_DISABLE_SYMLINKS_WARNING=true
|
set HF_HUB_DISABLE_SYMLINKS_WARNING=true
|
||||||
|
|
||||||
@if NOT DEFINED SD_UI_BIND_PORT set SD_UI_BIND_PORT=9000
|
@uvicorn main:server_api --app-dir "%SD_UI_PATH%" --port %ED_BIND_PORT% --host %ED_BIND_IP% --log-level error
|
||||||
@if NOT DEFINED SD_UI_BIND_IP set SD_UI_BIND_IP=0.0.0.0
|
|
||||||
@uvicorn main:server_api --app-dir "%SD_UI_PATH%" --port %SD_UI_BIND_PORT% --host %SD_UI_BIND_IP% --log-level error
|
|
||||||
|
|
||||||
|
|
||||||
@pause
|
@pause
|
||||||
|
@ -5,6 +5,7 @@ cp sd-ui-files/scripts/on_env_start.sh scripts/
|
|||||||
cp sd-ui-files/scripts/bootstrap.sh scripts/
|
cp sd-ui-files/scripts/bootstrap.sh scripts/
|
||||||
cp sd-ui-files/scripts/check_modules.py scripts/
|
cp sd-ui-files/scripts/check_modules.py scripts/
|
||||||
cp sd-ui-files/scripts/check_models.py scripts/
|
cp sd-ui-files/scripts/check_models.py scripts/
|
||||||
|
cp sd-ui-files/scripts/get_config.py scripts/
|
||||||
|
|
||||||
source ./scripts/functions.sh
|
source ./scripts/functions.sh
|
||||||
|
|
||||||
@ -74,8 +75,17 @@ python --version
|
|||||||
|
|
||||||
cd ..
|
cd ..
|
||||||
export SD_UI_PATH=`pwd`/ui
|
export SD_UI_PATH=`pwd`/ui
|
||||||
|
export ED_BIND_PORT="$( python scripts/get_config.py --default=9000 net listen_port )"
|
||||||
|
case "$( python scripts/get_config.py --default=False net listen_to_network )" in
|
||||||
|
"True")
|
||||||
|
export ED_BIND_IP=0.0.0.0
|
||||||
|
;;
|
||||||
|
"False")
|
||||||
|
export ED_BIND_IP=127.0.0.1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
cd stable-diffusion
|
cd stable-diffusion
|
||||||
|
|
||||||
uvicorn main:server_api --app-dir "$SD_UI_PATH" --port ${SD_UI_BIND_PORT:-9000} --host ${SD_UI_BIND_IP:-0.0.0.0} --log-level error
|
uvicorn main:server_api --app-dir "$SD_UI_PATH" --port "$ED_BIND_PORT" --host "$ED_BIND_IP" --log-level error
|
||||||
|
|
||||||
read -p "Press any key to continue"
|
read -p "Press any key to continue"
|
||||||
|
@ -1,17 +1,16 @@
|
|||||||
|
import json
|
||||||
|
import logging
|
||||||
import os
|
import os
|
||||||
import socket
|
import socket
|
||||||
import sys
|
import sys
|
||||||
import json
|
|
||||||
import traceback
|
import traceback
|
||||||
import logging
|
|
||||||
import shlex
|
|
||||||
import urllib
|
import urllib
|
||||||
from rich.logging import RichHandler
|
import warnings
|
||||||
|
|
||||||
from sdkit.utils import log as sdkit_log # hack, so we can overwrite the log config
|
|
||||||
|
|
||||||
from easydiffusion import task_manager
|
from easydiffusion import task_manager
|
||||||
from easydiffusion.utils import log
|
from easydiffusion.utils import log
|
||||||
|
from rich.logging import RichHandler
|
||||||
|
from sdkit.utils import log as sdkit_log # hack, so we can overwrite the log config
|
||||||
|
|
||||||
# Remove all handlers associated with the root logger object.
|
# Remove all handlers associated with the root logger object.
|
||||||
for handler in logging.root.handlers[:]:
|
for handler in logging.root.handlers[:]:
|
||||||
@ -55,15 +54,42 @@ APP_CONFIG_DEFAULTS = {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
IMAGE_EXTENSIONS = [".png", ".apng", ".jpg", ".jpeg", ".jfif", ".pjpeg", ".pjp", ".jxl", ".gif", ".webp", ".avif", ".svg"]
|
IMAGE_EXTENSIONS = [
|
||||||
|
".png",
|
||||||
|
".apng",
|
||||||
|
".jpg",
|
||||||
|
".jpeg",
|
||||||
|
".jfif",
|
||||||
|
".pjpeg",
|
||||||
|
".pjp",
|
||||||
|
".jxl",
|
||||||
|
".gif",
|
||||||
|
".webp",
|
||||||
|
".avif",
|
||||||
|
".svg",
|
||||||
|
]
|
||||||
CUSTOM_MODIFIERS_DIR = os.path.abspath(os.path.join(SD_DIR, "..", "modifiers"))
|
CUSTOM_MODIFIERS_DIR = os.path.abspath(os.path.join(SD_DIR, "..", "modifiers"))
|
||||||
CUSTOM_MODIFIERS_PORTRAIT_EXTENSIONS=[".portrait", "_portrait", " portrait", "-portrait"]
|
CUSTOM_MODIFIERS_PORTRAIT_EXTENSIONS = [
|
||||||
CUSTOM_MODIFIERS_LANDSCAPE_EXTENSIONS=[".landscape", "_landscape", " landscape", "-landscape"]
|
".portrait",
|
||||||
|
"_portrait",
|
||||||
|
" portrait",
|
||||||
|
"-portrait",
|
||||||
|
]
|
||||||
|
CUSTOM_MODIFIERS_LANDSCAPE_EXTENSIONS = [
|
||||||
|
".landscape",
|
||||||
|
"_landscape",
|
||||||
|
" landscape",
|
||||||
|
"-landscape",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
def init():
|
def init():
|
||||||
os.makedirs(USER_UI_PLUGINS_DIR, exist_ok=True)
|
os.makedirs(USER_UI_PLUGINS_DIR, exist_ok=True)
|
||||||
os.makedirs(USER_SERVER_PLUGINS_DIR, exist_ok=True)
|
os.makedirs(USER_SERVER_PLUGINS_DIR, exist_ok=True)
|
||||||
|
|
||||||
|
# https://pytorch.org/docs/stable/storage.html
|
||||||
|
warnings.filterwarnings('ignore', category=UserWarning, message='TypedStorage is deprecated')
|
||||||
|
|
||||||
load_server_plugins()
|
load_server_plugins()
|
||||||
|
|
||||||
update_render_threads()
|
update_render_threads()
|
||||||
@ -81,14 +107,10 @@ def getConfig(default_val=APP_CONFIG_DEFAULTS):
|
|||||||
config["net"] = {}
|
config["net"] = {}
|
||||||
if os.getenv("SD_UI_BIND_PORT") is not None:
|
if os.getenv("SD_UI_BIND_PORT") is not None:
|
||||||
config["net"]["listen_port"] = int(os.getenv("SD_UI_BIND_PORT"))
|
config["net"]["listen_port"] = int(os.getenv("SD_UI_BIND_PORT"))
|
||||||
else:
|
|
||||||
config["net"]["listen_port"] = 9000
|
|
||||||
if os.getenv("SD_UI_BIND_IP") is not None:
|
if os.getenv("SD_UI_BIND_IP") is not None:
|
||||||
config["net"]["listen_to_network"] = os.getenv("SD_UI_BIND_IP") == "0.0.0.0"
|
config["net"]["listen_to_network"] = os.getenv("SD_UI_BIND_IP") == "0.0.0.0"
|
||||||
else:
|
|
||||||
config["net"]["listen_to_network"] = True
|
|
||||||
return config
|
return config
|
||||||
except Exception as e:
|
except Exception:
|
||||||
log.warn(traceback.format_exc())
|
log.warn(traceback.format_exc())
|
||||||
return default_val
|
return default_val
|
||||||
|
|
||||||
@ -101,50 +123,6 @@ def setConfig(config):
|
|||||||
except:
|
except:
|
||||||
log.error(traceback.format_exc())
|
log.error(traceback.format_exc())
|
||||||
|
|
||||||
try: # config.bat
|
|
||||||
config_bat_path = os.path.join(CONFIG_DIR, "config.bat")
|
|
||||||
config_bat = []
|
|
||||||
|
|
||||||
if "update_branch" in config:
|
|
||||||
config_bat.append(f"@set update_branch={config['update_branch']}")
|
|
||||||
|
|
||||||
config_bat.append(f"@set SD_UI_BIND_PORT={config['net']['listen_port']}")
|
|
||||||
bind_ip = "0.0.0.0" if config["net"]["listen_to_network"] else "127.0.0.1"
|
|
||||||
config_bat.append(f"@set SD_UI_BIND_IP={bind_ip}")
|
|
||||||
|
|
||||||
# Preserve these variables if they are set
|
|
||||||
for var in PRESERVE_CONFIG_VARS:
|
|
||||||
if os.getenv(var) is not None:
|
|
||||||
config_bat.append(f"@set {var}={os.getenv(var)}")
|
|
||||||
|
|
||||||
if len(config_bat) > 0:
|
|
||||||
with open(config_bat_path, "w", encoding="utf-8") as f:
|
|
||||||
f.write("\n".join(config_bat))
|
|
||||||
except:
|
|
||||||
log.error(traceback.format_exc())
|
|
||||||
|
|
||||||
try: # config.sh
|
|
||||||
config_sh_path = os.path.join(CONFIG_DIR, "config.sh")
|
|
||||||
config_sh = ["#!/bin/bash"]
|
|
||||||
|
|
||||||
if "update_branch" in config:
|
|
||||||
config_sh.append(f"export update_branch={config['update_branch']}")
|
|
||||||
|
|
||||||
config_sh.append(f"export SD_UI_BIND_PORT={config['net']['listen_port']}")
|
|
||||||
bind_ip = "0.0.0.0" if config["net"]["listen_to_network"] else "127.0.0.1"
|
|
||||||
config_sh.append(f"export SD_UI_BIND_IP={bind_ip}")
|
|
||||||
|
|
||||||
# Preserve these variables if they are set
|
|
||||||
for var in PRESERVE_CONFIG_VARS:
|
|
||||||
if os.getenv(var) is not None:
|
|
||||||
config_bat.append(f'export {var}="{shlex.quote(os.getenv(var))}"')
|
|
||||||
|
|
||||||
if len(config_sh) > 1:
|
|
||||||
with open(config_sh_path, "w", encoding="utf-8") as f:
|
|
||||||
f.write("\n".join(config_sh))
|
|
||||||
except:
|
|
||||||
log.error(traceback.format_exc())
|
|
||||||
|
|
||||||
|
|
||||||
def save_to_config(ckpt_model_name, vae_model_name, hypernetwork_model_name, vram_usage_level):
|
def save_to_config(ckpt_model_name, vae_model_name, hypernetwork_model_name, vram_usage_level):
|
||||||
config = getConfig()
|
config = getConfig()
|
||||||
@ -233,18 +211,19 @@ def getIPConfig():
|
|||||||
def open_browser():
|
def open_browser():
|
||||||
config = getConfig()
|
config = getConfig()
|
||||||
ui = config.get("ui", {})
|
ui = config.get("ui", {})
|
||||||
net = config.get("net", {"listen_port": 9000})
|
net = config.get("net", {})
|
||||||
port = net.get("listen_port", 9000)
|
port = net.get("listen_port", 9000)
|
||||||
if ui.get("open_browser_on_start", True):
|
if ui.get("open_browser_on_start", True):
|
||||||
import webbrowser
|
import webbrowser
|
||||||
|
|
||||||
webbrowser.open(f"http://localhost:{port}")
|
webbrowser.open(f"http://localhost:{port}")
|
||||||
|
|
||||||
|
|
||||||
def get_image_modifiers():
|
def get_image_modifiers():
|
||||||
modifiers_json_path = os.path.join(SD_UI_DIR, "modifiers.json")
|
modifiers_json_path = os.path.join(SD_UI_DIR, "modifiers.json")
|
||||||
|
|
||||||
modifier_categories = {}
|
modifier_categories = {}
|
||||||
original_category_order=[]
|
original_category_order = []
|
||||||
with open(modifiers_json_path, "r", encoding="utf-8") as f:
|
with open(modifiers_json_path, "r", encoding="utf-8") as f:
|
||||||
modifiers_file = json.load(f)
|
modifiers_file = json.load(f)
|
||||||
|
|
||||||
@ -254,14 +233,14 @@ def get_image_modifiers():
|
|||||||
|
|
||||||
# convert modifiers from a list of objects to a dict of dicts
|
# convert modifiers from a list of objects to a dict of dicts
|
||||||
for category_item in modifiers_file:
|
for category_item in modifiers_file:
|
||||||
category_name = category_item['category']
|
category_name = category_item["category"]
|
||||||
original_category_order.append(category_name)
|
original_category_order.append(category_name)
|
||||||
category = {}
|
category = {}
|
||||||
for modifier_item in category_item['modifiers']:
|
for modifier_item in category_item["modifiers"]:
|
||||||
modifier = {}
|
modifier = {}
|
||||||
for preview_item in modifier_item['previews']:
|
for preview_item in modifier_item["previews"]:
|
||||||
modifier[preview_item['name']] = preview_item['path']
|
modifier[preview_item["name"]] = preview_item["path"]
|
||||||
category[modifier_item['modifier']] = modifier
|
category[modifier_item["modifier"]] = modifier
|
||||||
modifier_categories[category_name] = category
|
modifier_categories[category_name] = category
|
||||||
|
|
||||||
def scan_directory(directory_path: str, category_name="Modifiers"):
|
def scan_directory(directory_path: str, category_name="Modifiers"):
|
||||||
@ -274,12 +253,27 @@ def get_image_modifiers():
|
|||||||
modifier_name = entry.name[: -len(file_extension[0])]
|
modifier_name = entry.name[: -len(file_extension[0])]
|
||||||
modifier_path = f"custom/{entry.path[len(CUSTOM_MODIFIERS_DIR) + 1:]}"
|
modifier_path = f"custom/{entry.path[len(CUSTOM_MODIFIERS_DIR) + 1:]}"
|
||||||
# URL encode path segments
|
# URL encode path segments
|
||||||
modifier_path = "/".join(map(lambda segment: urllib.parse.quote(segment), modifier_path.split("/")))
|
modifier_path = "/".join(
|
||||||
|
map(
|
||||||
|
lambda segment: urllib.parse.quote(segment),
|
||||||
|
modifier_path.split("/"),
|
||||||
|
)
|
||||||
|
)
|
||||||
is_portrait = True
|
is_portrait = True
|
||||||
is_landscape = True
|
is_landscape = True
|
||||||
|
|
||||||
portrait_extension = list(filter(lambda e: modifier_name.lower().endswith(e), CUSTOM_MODIFIERS_PORTRAIT_EXTENSIONS))
|
portrait_extension = list(
|
||||||
landscape_extension = list(filter(lambda e: modifier_name.lower().endswith(e), CUSTOM_MODIFIERS_LANDSCAPE_EXTENSIONS))
|
filter(
|
||||||
|
lambda e: modifier_name.lower().endswith(e),
|
||||||
|
CUSTOM_MODIFIERS_PORTRAIT_EXTENSIONS,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
landscape_extension = list(
|
||||||
|
filter(
|
||||||
|
lambda e: modifier_name.lower().endswith(e),
|
||||||
|
CUSTOM_MODIFIERS_LANDSCAPE_EXTENSIONS,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
if len(portrait_extension) > 0:
|
if len(portrait_extension) > 0:
|
||||||
is_landscape = False
|
is_landscape = False
|
||||||
@ -288,23 +282,23 @@ def get_image_modifiers():
|
|||||||
is_portrait = False
|
is_portrait = False
|
||||||
modifier_name = modifier_name[: -len(landscape_extension[0])]
|
modifier_name = modifier_name[: -len(landscape_extension[0])]
|
||||||
|
|
||||||
if (category_name not in modifier_categories):
|
if category_name not in modifier_categories:
|
||||||
modifier_categories[category_name] = {}
|
modifier_categories[category_name] = {}
|
||||||
|
|
||||||
category = modifier_categories[category_name]
|
category = modifier_categories[category_name]
|
||||||
|
|
||||||
if (modifier_name not in category):
|
if modifier_name not in category:
|
||||||
category[modifier_name] = {}
|
category[modifier_name] = {}
|
||||||
|
|
||||||
if (is_portrait or "portrait" not in category[modifier_name]):
|
if is_portrait or "portrait" not in category[modifier_name]:
|
||||||
category[modifier_name]["portrait"] = modifier_path
|
category[modifier_name]["portrait"] = modifier_path
|
||||||
|
|
||||||
if (is_landscape or "landscape" not in category[modifier_name]):
|
if is_landscape or "landscape" not in category[modifier_name]:
|
||||||
category[modifier_name]["landscape"] = modifier_path
|
category[modifier_name]["landscape"] = modifier_path
|
||||||
elif entry.is_dir():
|
elif entry.is_dir():
|
||||||
scan_directory(
|
scan_directory(
|
||||||
entry.path,
|
entry.path,
|
||||||
entry.name if directory_path==CUSTOM_MODIFIERS_DIR else f"{category_name}/{entry.name}",
|
entry.name if directory_path == CUSTOM_MODIFIERS_DIR else f"{category_name}/{entry.name}",
|
||||||
)
|
)
|
||||||
|
|
||||||
scan_directory(CUSTOM_MODIFIERS_DIR)
|
scan_directory(CUSTOM_MODIFIERS_DIR)
|
||||||
@ -317,12 +311,12 @@ def get_image_modifiers():
|
|||||||
# convert the modifiers back into a list of objects
|
# convert the modifiers back into a list of objects
|
||||||
modifier_categories_list = []
|
modifier_categories_list = []
|
||||||
for category_name in [*original_category_order, *custom_categories]:
|
for category_name in [*original_category_order, *custom_categories]:
|
||||||
category = { 'category': category_name, 'modifiers': [] }
|
category = {"category": category_name, "modifiers": []}
|
||||||
for modifier_name in sorted(modifier_categories[category_name].keys(), key=str.casefold):
|
for modifier_name in sorted(modifier_categories[category_name].keys(), key=str.casefold):
|
||||||
modifier = { 'modifier': modifier_name, 'previews': [] }
|
modifier = {"modifier": modifier_name, "previews": []}
|
||||||
for preview_name, preview_path in modifier_categories[category_name][modifier_name].items():
|
for preview_name, preview_path in modifier_categories[category_name][modifier_name].items():
|
||||||
modifier['previews'].append({ 'name': preview_name, 'path': preview_path })
|
modifier["previews"].append({"name": preview_name, "path": preview_path})
|
||||||
category['modifiers'].append(modifier)
|
category["modifiers"].append(modifier)
|
||||||
modifier_categories_list.append(category)
|
modifier_categories_list.append(category)
|
||||||
|
|
||||||
return modifier_categories_list
|
return modifier_categories_list
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import os
|
import os
|
||||||
import platform
|
import platform
|
||||||
import torch
|
|
||||||
import traceback
|
|
||||||
import re
|
import re
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
import torch
|
||||||
from easydiffusion.utils import log
|
from easydiffusion.utils import log
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@ -118,7 +118,10 @@ def auto_pick_devices(currently_active_devices):
|
|||||||
# These already-running devices probably aren't terrible, since they were picked in the past.
|
# These already-running devices probably aren't terrible, since they were picked in the past.
|
||||||
# Worst case, the user can restart the program and that'll get rid of them.
|
# Worst case, the user can restart the program and that'll get rid of them.
|
||||||
devices = list(
|
devices = list(
|
||||||
filter((lambda x: x["mem_free"] > mem_free_threshold or x["device"] in currently_active_devices), devices)
|
filter(
|
||||||
|
(lambda x: x["mem_free"] > mem_free_threshold or x["device"] in currently_active_devices),
|
||||||
|
devices,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
devices = list(map(lambda x: x["device"], devices))
|
devices = list(map(lambda x: x["device"], devices))
|
||||||
return devices
|
return devices
|
||||||
@ -162,6 +165,7 @@ def needs_to_force_full_precision(context):
|
|||||||
and (
|
and (
|
||||||
" 1660" in device_name
|
" 1660" in device_name
|
||||||
or " 1650" in device_name
|
or " 1650" in device_name
|
||||||
|
or " 1630" in device_name
|
||||||
or " t400" in device_name
|
or " t400" in device_name
|
||||||
or " t550" in device_name
|
or " t550" in device_name
|
||||||
or " t600" in device_name
|
or " t600" in device_name
|
||||||
@ -221,9 +225,9 @@ def is_device_compatible(device):
|
|||||||
try:
|
try:
|
||||||
_, mem_total = torch.cuda.mem_get_info(device)
|
_, mem_total = torch.cuda.mem_get_info(device)
|
||||||
mem_total /= float(10**9)
|
mem_total /= float(10**9)
|
||||||
if mem_total < 3.0:
|
if mem_total < 1.9:
|
||||||
if is_device_compatible.history.get(device) == None:
|
if is_device_compatible.history.get(device) == None:
|
||||||
log.warn(f"GPU {device} with less than 3 GB of VRAM is not compatible with Stable Diffusion")
|
log.warn(f"GPU {device} with less than 2 GB of VRAM is not compatible with Stable Diffusion")
|
||||||
is_device_compatible.history[device] = 1
|
is_device_compatible.history[device] = 1
|
||||||
return False
|
return False
|
||||||
except RuntimeError as e:
|
except RuntimeError as e:
|
||||||
|
@ -3,11 +3,17 @@ import os
|
|||||||
from easydiffusion import app
|
from easydiffusion import app
|
||||||
from easydiffusion.types import TaskData
|
from easydiffusion.types import TaskData
|
||||||
from easydiffusion.utils import log
|
from easydiffusion.utils import log
|
||||||
|
|
||||||
from sdkit import Context
|
from sdkit import Context
|
||||||
from sdkit.models import load_model, unload_model, scan_model
|
from sdkit.models import load_model, scan_model, unload_model
|
||||||
|
|
||||||
KNOWN_MODEL_TYPES = ["stable-diffusion", "vae", "hypernetwork", "gfpgan", "realesrgan", "lora"]
|
KNOWN_MODEL_TYPES = [
|
||||||
|
"stable-diffusion",
|
||||||
|
"vae",
|
||||||
|
"hypernetwork",
|
||||||
|
"gfpgan",
|
||||||
|
"realesrgan",
|
||||||
|
"lora",
|
||||||
|
]
|
||||||
MODEL_EXTENSIONS = {
|
MODEL_EXTENSIONS = {
|
||||||
"stable-diffusion": [".ckpt", ".safetensors"],
|
"stable-diffusion": [".ckpt", ".safetensors"],
|
||||||
"vae": [".vae.pt", ".ckpt", ".safetensors"],
|
"vae": [".vae.pt", ".ckpt", ".safetensors"],
|
||||||
@ -44,13 +50,15 @@ def load_default_models(context: Context):
|
|||||||
load_model(
|
load_model(
|
||||||
context,
|
context,
|
||||||
model_type,
|
model_type,
|
||||||
scan_model = context.model_paths[model_type] != None and not context.model_paths[model_type].endswith('.safetensors')
|
scan_model=context.model_paths[model_type] != None
|
||||||
|
and not context.model_paths[model_type].endswith(".safetensors"),
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.error(f"[red]Error while loading {model_type} model: {context.model_paths[model_type]}[/red]")
|
log.error(f"[red]Error while loading {model_type} model: {context.model_paths[model_type]}[/red]")
|
||||||
log.exception(e)
|
log.exception(e)
|
||||||
del context.model_paths[model_type]
|
del context.model_paths[model_type]
|
||||||
|
|
||||||
|
|
||||||
def unload_all(context: Context):
|
def unload_all(context: Context):
|
||||||
for model_type in KNOWN_MODEL_TYPES:
|
for model_type in KNOWN_MODEL_TYPES:
|
||||||
unload_model(context, model_type)
|
unload_model(context, model_type)
|
||||||
@ -114,7 +122,7 @@ def reload_models_if_necessary(context: Context, task_data: TaskData):
|
|||||||
if context.model_paths.get(model_type) != path
|
if context.model_paths.get(model_type) != path
|
||||||
}
|
}
|
||||||
|
|
||||||
if set_vram_optimizations(context): # reload SD
|
if set_vram_optimizations(context) or set_clip_skip(context, task_data): # reload SD
|
||||||
models_to_reload["stable-diffusion"] = model_paths_in_req["stable-diffusion"]
|
models_to_reload["stable-diffusion"] = model_paths_in_req["stable-diffusion"]
|
||||||
|
|
||||||
for model_type, model_path_in_req in models_to_reload.items():
|
for model_type, model_path_in_req in models_to_reload.items():
|
||||||
@ -149,6 +157,16 @@ def set_vram_optimizations(context: Context):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def set_clip_skip(context: Context, task_data: TaskData):
|
||||||
|
clip_skip = task_data.clip_skip
|
||||||
|
|
||||||
|
if clip_skip != context.clip_skip:
|
||||||
|
context.clip_skip = clip_skip
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def make_model_folders():
|
def make_model_folders():
|
||||||
for model_type in KNOWN_MODEL_TYPES:
|
for model_type in KNOWN_MODEL_TYPES:
|
||||||
model_dir_path = os.path.join(app.MODELS_DIR, model_type)
|
model_dir_path = os.path.join(app.MODELS_DIR, model_type)
|
||||||
@ -170,13 +188,23 @@ def is_malicious_model(file_path):
|
|||||||
if scan_result.issues_count > 0 or scan_result.infected_files > 0:
|
if scan_result.issues_count > 0 or scan_result.infected_files > 0:
|
||||||
log.warn(
|
log.warn(
|
||||||
":warning: [bold red]Scan %s: %d scanned, %d issue, %d infected.[/bold red]"
|
":warning: [bold red]Scan %s: %d scanned, %d issue, %d infected.[/bold red]"
|
||||||
% (file_path, scan_result.scanned_files, scan_result.issues_count, scan_result.infected_files)
|
% (
|
||||||
|
file_path,
|
||||||
|
scan_result.scanned_files,
|
||||||
|
scan_result.issues_count,
|
||||||
|
scan_result.infected_files,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
log.debug(
|
log.debug(
|
||||||
"Scan %s: [green]%d scanned, %d issue, %d infected.[/green]"
|
"Scan %s: [green]%d scanned, %d issue, %d infected.[/green]"
|
||||||
% (file_path, scan_result.scanned_files, scan_result.issues_count, scan_result.infected_files)
|
% (
|
||||||
|
file_path,
|
||||||
|
scan_result.scanned_files,
|
||||||
|
scan_result.issues_count,
|
||||||
|
scan_result.infected_files,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
return False
|
return False
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@ -204,13 +232,13 @@ def getModels():
|
|||||||
|
|
||||||
class MaliciousModelException(Exception):
|
class MaliciousModelException(Exception):
|
||||||
"Raised when picklescan reports a problem with a model"
|
"Raised when picklescan reports a problem with a model"
|
||||||
pass
|
|
||||||
|
|
||||||
def scan_directory(directory, suffixes, directoriesFirst: bool = True):
|
def scan_directory(directory, suffixes, directoriesFirst: bool = True):
|
||||||
nonlocal models_scanned
|
nonlocal models_scanned
|
||||||
tree = []
|
tree = []
|
||||||
for entry in sorted(
|
for entry in sorted(
|
||||||
os.scandir(directory), key=lambda entry: (entry.is_file() == directoriesFirst, entry.name.lower())
|
os.scandir(directory),
|
||||||
|
key=lambda entry: (entry.is_file() == directoriesFirst, entry.name.lower()),
|
||||||
):
|
):
|
||||||
if entry.is_file():
|
if entry.is_file():
|
||||||
matching_suffix = list(filter(lambda s: entry.name.endswith(s), suffixes))
|
matching_suffix = list(filter(lambda s: entry.name.endswith(s), suffixes))
|
||||||
|
@ -1,21 +1,23 @@
|
|||||||
import queue
|
|
||||||
import time
|
|
||||||
import json
|
import json
|
||||||
import pprint
|
import pprint
|
||||||
|
import queue
|
||||||
|
import time
|
||||||
|
|
||||||
from easydiffusion import device_manager
|
from easydiffusion import device_manager
|
||||||
from easydiffusion.types import TaskData, Response, Image as ResponseImage, UserInitiatedStop, GenerateImageRequest
|
from easydiffusion.types import GenerateImageRequest
|
||||||
from easydiffusion.utils import get_printable_request, save_images_to_disk, log
|
from easydiffusion.types import Image as ResponseImage
|
||||||
|
from easydiffusion.types import Response, TaskData, UserInitiatedStop
|
||||||
|
from easydiffusion.utils import get_printable_request, log, save_images_to_disk
|
||||||
from sdkit import Context
|
from sdkit import Context
|
||||||
from sdkit.generate import generate_images
|
|
||||||
from sdkit.filter import apply_filters
|
from sdkit.filter import apply_filters
|
||||||
|
from sdkit.generate import generate_images
|
||||||
from sdkit.utils import (
|
from sdkit.utils import (
|
||||||
img_to_buffer,
|
|
||||||
img_to_base64_str,
|
|
||||||
latent_samples_to_images,
|
|
||||||
diffusers_latent_samples_to_images,
|
diffusers_latent_samples_to_images,
|
||||||
gc,
|
gc,
|
||||||
|
img_to_base64_str,
|
||||||
|
img_to_buffer,
|
||||||
|
latent_samples_to_images,
|
||||||
|
get_device_usage,
|
||||||
)
|
)
|
||||||
|
|
||||||
context = Context() # thread-local
|
context = Context() # thread-local
|
||||||
@ -39,18 +41,29 @@ def init(device):
|
|||||||
app_config.get("test_diffusers", False) and app_config.get("update_branch", "main") != "main"
|
app_config.get("test_diffusers", False) and app_config.get("update_branch", "main") != "main"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
log.info("Device usage during initialization:")
|
||||||
|
get_device_usage(device, log_info=True, process_usage_only=False)
|
||||||
|
|
||||||
device_manager.device_init(context, device)
|
device_manager.device_init(context, device)
|
||||||
|
|
||||||
|
|
||||||
def make_images(
|
def make_images(
|
||||||
req: GenerateImageRequest, task_data: TaskData, data_queue: queue.Queue, task_temp_images: list, step_callback
|
req: GenerateImageRequest,
|
||||||
|
task_data: TaskData,
|
||||||
|
data_queue: queue.Queue,
|
||||||
|
task_temp_images: list,
|
||||||
|
step_callback,
|
||||||
):
|
):
|
||||||
context.stop_processing = False
|
context.stop_processing = False
|
||||||
print_task_info(req, task_data)
|
print_task_info(req, task_data)
|
||||||
|
|
||||||
images, seeds = make_images_internal(req, task_data, data_queue, task_temp_images, step_callback)
|
images, seeds = make_images_internal(req, task_data, data_queue, task_temp_images, step_callback)
|
||||||
|
|
||||||
res = Response(req, task_data, images=construct_response(images, seeds, task_data, base_seed=req.seed))
|
res = Response(
|
||||||
|
req,
|
||||||
|
task_data,
|
||||||
|
images=construct_response(images, seeds, task_data, base_seed=req.seed),
|
||||||
|
)
|
||||||
res = res.json()
|
res = res.json()
|
||||||
data_queue.put(json.dumps(res))
|
data_queue.put(json.dumps(res))
|
||||||
log.info("Task completed")
|
log.info("Task completed")
|
||||||
@ -59,14 +72,18 @@ def make_images(
|
|||||||
|
|
||||||
|
|
||||||
def print_task_info(req: GenerateImageRequest, task_data: TaskData):
|
def print_task_info(req: GenerateImageRequest, task_data: TaskData):
|
||||||
req_str = pprint.pformat(get_printable_request(req)).replace("[", "\[")
|
req_str = pprint.pformat(get_printable_request(req, task_data)).replace("[", "\[")
|
||||||
task_str = pprint.pformat(task_data.dict()).replace("[", "\[")
|
task_str = pprint.pformat(task_data.dict()).replace("[", "\[")
|
||||||
log.info(f"request: {req_str}")
|
log.info(f"request: {req_str}")
|
||||||
log.info(f"task data: {task_str}")
|
log.info(f"task data: {task_str}")
|
||||||
|
|
||||||
|
|
||||||
def make_images_internal(
|
def make_images_internal(
|
||||||
req: GenerateImageRequest, task_data: TaskData, data_queue: queue.Queue, task_temp_images: list, step_callback
|
req: GenerateImageRequest,
|
||||||
|
task_data: TaskData,
|
||||||
|
data_queue: queue.Queue,
|
||||||
|
task_temp_images: list,
|
||||||
|
step_callback,
|
||||||
):
|
):
|
||||||
images, user_stopped = generate_images_internal(
|
images, user_stopped = generate_images_internal(
|
||||||
req,
|
req,
|
||||||
@ -155,7 +172,12 @@ def filter_images(task_data: TaskData, images: list, user_stopped):
|
|||||||
def construct_response(images: list, seeds: list, task_data: TaskData, base_seed: int):
|
def construct_response(images: list, seeds: list, task_data: TaskData, base_seed: int):
|
||||||
return [
|
return [
|
||||||
ResponseImage(
|
ResponseImage(
|
||||||
data=img_to_base64_str(img, task_data.output_format, task_data.output_quality, task_data.output_lossless),
|
data=img_to_base64_str(
|
||||||
|
img,
|
||||||
|
task_data.output_format,
|
||||||
|
task_data.output_quality,
|
||||||
|
task_data.output_lossless,
|
||||||
|
),
|
||||||
seed=seed,
|
seed=seed,
|
||||||
)
|
)
|
||||||
for img, seed in zip(images, seeds)
|
for img, seed in zip(images, seeds)
|
||||||
|
@ -2,28 +2,30 @@
|
|||||||
Notes:
|
Notes:
|
||||||
async endpoints always run on the main thread. Without they run on the thread pool.
|
async endpoints always run on the main thread. Without they run on the thread pool.
|
||||||
"""
|
"""
|
||||||
|
import datetime
|
||||||
|
import mimetypes
|
||||||
import os
|
import os
|
||||||
import traceback
|
import traceback
|
||||||
import datetime
|
|
||||||
from typing import List, Union
|
from typing import List, Union
|
||||||
|
|
||||||
|
from easydiffusion import app, model_manager, task_manager
|
||||||
|
from easydiffusion.types import GenerateImageRequest, MergeRequest, TaskData
|
||||||
|
from easydiffusion.utils import log
|
||||||
from fastapi import FastAPI, HTTPException
|
from fastapi import FastAPI, HTTPException
|
||||||
from fastapi.staticfiles import StaticFiles
|
from fastapi.staticfiles import StaticFiles
|
||||||
from starlette.responses import FileResponse, JSONResponse, StreamingResponse
|
|
||||||
from pydantic import BaseModel, Extra
|
from pydantic import BaseModel, Extra
|
||||||
|
from starlette.responses import FileResponse, JSONResponse, StreamingResponse
|
||||||
from easydiffusion import app, model_manager, task_manager
|
|
||||||
from easydiffusion.types import TaskData, GenerateImageRequest, MergeRequest
|
|
||||||
from easydiffusion.utils import log
|
|
||||||
|
|
||||||
import mimetypes
|
|
||||||
|
|
||||||
log.info(f"started in {app.SD_DIR}")
|
log.info(f"started in {app.SD_DIR}")
|
||||||
log.info(f"started at {datetime.datetime.now():%x %X}")
|
log.info(f"started at {datetime.datetime.now():%x %X}")
|
||||||
|
|
||||||
server_api = FastAPI()
|
server_api = FastAPI()
|
||||||
|
|
||||||
NOCACHE_HEADERS = {"Cache-Control": "no-cache, no-store, must-revalidate", "Pragma": "no-cache", "Expires": "0"}
|
NOCACHE_HEADERS = {
|
||||||
|
"Cache-Control": "no-cache, no-store, must-revalidate",
|
||||||
|
"Pragma": "no-cache",
|
||||||
|
"Expires": "0",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class NoCacheStaticFiles(StaticFiles):
|
class NoCacheStaticFiles(StaticFiles):
|
||||||
@ -65,11 +67,17 @@ def init():
|
|||||||
name="custom-thumbnails",
|
name="custom-thumbnails",
|
||||||
)
|
)
|
||||||
|
|
||||||
server_api.mount("/media", NoCacheStaticFiles(directory=os.path.join(app.SD_UI_DIR, "media")), name="media")
|
server_api.mount(
|
||||||
|
"/media",
|
||||||
|
NoCacheStaticFiles(directory=os.path.join(app.SD_UI_DIR, "media")),
|
||||||
|
name="media",
|
||||||
|
)
|
||||||
|
|
||||||
for plugins_dir, dir_prefix in app.UI_PLUGINS_SOURCES:
|
for plugins_dir, dir_prefix in app.UI_PLUGINS_SOURCES:
|
||||||
server_api.mount(
|
server_api.mount(
|
||||||
f"/plugins/{dir_prefix}", NoCacheStaticFiles(directory=plugins_dir), name=f"plugins-{dir_prefix}"
|
f"/plugins/{dir_prefix}",
|
||||||
|
NoCacheStaticFiles(directory=plugins_dir),
|
||||||
|
name=f"plugins-{dir_prefix}",
|
||||||
)
|
)
|
||||||
|
|
||||||
@server_api.post("/app_config")
|
@server_api.post("/app_config")
|
||||||
@ -246,8 +254,8 @@ def render_internal(req: dict):
|
|||||||
|
|
||||||
def model_merge_internal(req: dict):
|
def model_merge_internal(req: dict):
|
||||||
try:
|
try:
|
||||||
from sdkit.train import merge_models
|
|
||||||
from easydiffusion.utils.save_utils import filename_regex
|
from easydiffusion.utils.save_utils import filename_regex
|
||||||
|
from sdkit.train import merge_models
|
||||||
|
|
||||||
mergeReq: MergeRequest = MergeRequest.parse_obj(req)
|
mergeReq: MergeRequest = MergeRequest.parse_obj(req)
|
||||||
|
|
||||||
@ -255,7 +263,11 @@ def model_merge_internal(req: dict):
|
|||||||
model_manager.resolve_model_to_use(mergeReq.model0, "stable-diffusion"),
|
model_manager.resolve_model_to_use(mergeReq.model0, "stable-diffusion"),
|
||||||
model_manager.resolve_model_to_use(mergeReq.model1, "stable-diffusion"),
|
model_manager.resolve_model_to_use(mergeReq.model1, "stable-diffusion"),
|
||||||
mergeReq.ratio,
|
mergeReq.ratio,
|
||||||
os.path.join(app.MODELS_DIR, "stable-diffusion", filename_regex.sub("_", mergeReq.out_path)),
|
os.path.join(
|
||||||
|
app.MODELS_DIR,
|
||||||
|
"stable-diffusion",
|
||||||
|
filename_regex.sub("_", mergeReq.out_path),
|
||||||
|
),
|
||||||
mergeReq.use_fp16,
|
mergeReq.use_fp16,
|
||||||
)
|
)
|
||||||
return JSONResponse({"status": "OK"}, headers=NOCACHE_HEADERS)
|
return JSONResponse({"status": "OK"}, headers=NOCACHE_HEADERS)
|
||||||
|
@ -7,16 +7,18 @@ Notes:
|
|||||||
import json
|
import json
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
TASK_TTL = 15 * 60 # seconds, Discard last session's task timeout
|
TASK_TTL = 30 * 60 # seconds, Discard last session's task timeout
|
||||||
|
|
||||||
import torch
|
import queue
|
||||||
import queue, threading, time, weakref
|
import threading
|
||||||
|
import time
|
||||||
|
import weakref
|
||||||
from typing import Any, Hashable
|
from typing import Any, Hashable
|
||||||
|
|
||||||
|
import torch
|
||||||
from easydiffusion import device_manager
|
from easydiffusion import device_manager
|
||||||
from easydiffusion.types import TaskData, GenerateImageRequest
|
from easydiffusion.types import GenerateImageRequest, TaskData
|
||||||
from easydiffusion.utils import log
|
from easydiffusion.utils import log
|
||||||
|
|
||||||
from sdkit.utils import gc
|
from sdkit.utils import gc
|
||||||
|
|
||||||
THREAD_NAME_PREFIX = ""
|
THREAD_NAME_PREFIX = ""
|
||||||
@ -167,7 +169,7 @@ class DataCache:
|
|||||||
raise Exception("DataCache.put" + ERR_LOCK_FAILED)
|
raise Exception("DataCache.put" + ERR_LOCK_FAILED)
|
||||||
try:
|
try:
|
||||||
self._base[key] = (self._get_ttl_time(ttl), value)
|
self._base[key] = (self._get_ttl_time(ttl), value)
|
||||||
except Exception as e:
|
except Exception:
|
||||||
log.error(traceback.format_exc())
|
log.error(traceback.format_exc())
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
@ -264,7 +266,7 @@ def thread_get_next_task():
|
|||||||
def thread_render(device):
|
def thread_render(device):
|
||||||
global current_state, current_state_error
|
global current_state, current_state_error
|
||||||
|
|
||||||
from easydiffusion import renderer, model_manager
|
from easydiffusion import model_manager, renderer
|
||||||
|
|
||||||
try:
|
try:
|
||||||
renderer.init(device)
|
renderer.init(device)
|
||||||
@ -337,7 +339,11 @@ def thread_render(device):
|
|||||||
|
|
||||||
current_state = ServerStates.Rendering
|
current_state = ServerStates.Rendering
|
||||||
task.response = renderer.make_images(
|
task.response = renderer.make_images(
|
||||||
task.render_request, task.task_data, task.buffer_queue, task.temp_images, step_callback
|
task.render_request,
|
||||||
|
task.task_data,
|
||||||
|
task.buffer_queue,
|
||||||
|
task.temp_images,
|
||||||
|
step_callback,
|
||||||
)
|
)
|
||||||
# Before looping back to the generator, mark cache as still alive.
|
# Before looping back to the generator, mark cache as still alive.
|
||||||
task_cache.keep(id(task), TASK_TTL)
|
task_cache.keep(id(task), TASK_TTL)
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
from pydantic import BaseModel
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
|
||||||
class GenerateImageRequest(BaseModel):
|
class GenerateImageRequest(BaseModel):
|
||||||
prompt: str = ""
|
prompt: str = ""
|
||||||
@ -47,6 +48,7 @@ class TaskData(BaseModel):
|
|||||||
metadata_output_format: str = "txt" # or "json"
|
metadata_output_format: str = "txt" # or "json"
|
||||||
stream_image_progress: bool = False
|
stream_image_progress: bool = False
|
||||||
stream_image_progress_interval: int = 5
|
stream_image_progress_interval: int = 5
|
||||||
|
clip_skip: bool = False
|
||||||
|
|
||||||
|
|
||||||
class MergeRequest(BaseModel):
|
class MergeRequest(BaseModel):
|
||||||
|
@ -1,14 +1,13 @@
|
|||||||
import os
|
import os
|
||||||
import time
|
|
||||||
import re
|
import re
|
||||||
|
import time
|
||||||
|
from datetime import datetime
|
||||||
|
from functools import reduce
|
||||||
|
|
||||||
from easydiffusion import app
|
from easydiffusion import app
|
||||||
from easydiffusion.types import TaskData, GenerateImageRequest
|
from easydiffusion.types import GenerateImageRequest, TaskData
|
||||||
from functools import reduce
|
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
from sdkit.utils import save_images, save_dicts
|
|
||||||
from numpy import base_repr
|
from numpy import base_repr
|
||||||
|
from sdkit.utils import save_dicts, save_images
|
||||||
|
|
||||||
filename_regex = re.compile("[^a-zA-Z0-9._-]")
|
filename_regex = re.compile("[^a-zA-Z0-9._-]")
|
||||||
img_number_regex = re.compile("([0-9]{5,})")
|
img_number_regex = re.compile("([0-9]{5,})")
|
||||||
@ -16,23 +15,24 @@ img_number_regex = re.compile("([0-9]{5,})")
|
|||||||
# keep in sync with `ui/media/js/dnd.js`
|
# keep in sync with `ui/media/js/dnd.js`
|
||||||
TASK_TEXT_MAPPING = {
|
TASK_TEXT_MAPPING = {
|
||||||
"prompt": "Prompt",
|
"prompt": "Prompt",
|
||||||
|
"negative_prompt": "Negative Prompt",
|
||||||
|
"seed": "Seed",
|
||||||
|
"use_stable_diffusion_model": "Stable Diffusion model",
|
||||||
|
"clip_skip": "Clip Skip",
|
||||||
|
"use_vae_model": "VAE model",
|
||||||
|
"sampler_name": "Sampler",
|
||||||
"width": "Width",
|
"width": "Width",
|
||||||
"height": "Height",
|
"height": "Height",
|
||||||
"seed": "Seed",
|
|
||||||
"num_inference_steps": "Steps",
|
"num_inference_steps": "Steps",
|
||||||
"guidance_scale": "Guidance Scale",
|
"guidance_scale": "Guidance Scale",
|
||||||
"prompt_strength": "Prompt Strength",
|
"prompt_strength": "Prompt Strength",
|
||||||
|
"use_lora_model": "LoRA model",
|
||||||
|
"lora_alpha": "LoRA Strength",
|
||||||
|
"use_hypernetwork_model": "Hypernetwork model",
|
||||||
|
"hypernetwork_strength": "Hypernetwork Strength",
|
||||||
"use_face_correction": "Use Face Correction",
|
"use_face_correction": "Use Face Correction",
|
||||||
"use_upscale": "Use Upscaling",
|
"use_upscale": "Use Upscaling",
|
||||||
"upscale_amount": "Upscale By",
|
"upscale_amount": "Upscale By",
|
||||||
"sampler_name": "Sampler",
|
|
||||||
"negative_prompt": "Negative Prompt",
|
|
||||||
"use_stable_diffusion_model": "Stable Diffusion model",
|
|
||||||
"use_vae_model": "VAE model",
|
|
||||||
"use_hypernetwork_model": "Hypernetwork model",
|
|
||||||
"hypernetwork_strength": "Hypernetwork Strength",
|
|
||||||
"use_lora_model": "LoRA model",
|
|
||||||
"lora_alpha": "LoRA Strength",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
time_placeholders = {
|
time_placeholders = {
|
||||||
@ -50,6 +50,7 @@ other_placeholders = {
|
|||||||
"$s": lambda req, task_data: str(req.seed),
|
"$s": lambda req, task_data: str(req.seed),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class ImageNumber:
|
class ImageNumber:
|
||||||
_factory = None
|
_factory = None
|
||||||
_evaluated = False
|
_evaluated = False
|
||||||
@ -57,12 +58,14 @@ class ImageNumber:
|
|||||||
def __init__(self, factory):
|
def __init__(self, factory):
|
||||||
self._factory = factory
|
self._factory = factory
|
||||||
self._evaluated = None
|
self._evaluated = None
|
||||||
|
|
||||||
def __call__(self) -> int:
|
def __call__(self) -> int:
|
||||||
if self._evaluated is None:
|
if self._evaluated is None:
|
||||||
self._evaluated = self._factory()
|
self._evaluated = self._factory()
|
||||||
return self._evaluated
|
return self._evaluated
|
||||||
|
|
||||||
def format_placeholders(format: str, req: GenerateImageRequest, task_data: TaskData, now = None):
|
|
||||||
|
def format_placeholders(format: str, req: GenerateImageRequest, task_data: TaskData, now=None):
|
||||||
if now is None:
|
if now is None:
|
||||||
now = time.time()
|
now = time.time()
|
||||||
|
|
||||||
@ -75,10 +78,12 @@ def format_placeholders(format: str, req: GenerateImageRequest, task_data: TaskD
|
|||||||
|
|
||||||
return format
|
return format
|
||||||
|
|
||||||
|
|
||||||
def format_folder_name(format: str, req: GenerateImageRequest, task_data: TaskData):
|
def format_folder_name(format: str, req: GenerateImageRequest, task_data: TaskData):
|
||||||
format = format_placeholders(format, req, task_data)
|
format = format_placeholders(format, req, task_data)
|
||||||
return filename_regex.sub("_", format)
|
return filename_regex.sub("_", format)
|
||||||
|
|
||||||
|
|
||||||
def format_file_name(
|
def format_file_name(
|
||||||
format: str,
|
format: str,
|
||||||
req: GenerateImageRequest,
|
req: GenerateImageRequest,
|
||||||
@ -93,7 +98,9 @@ def format_file_name(
|
|||||||
format = format.replace("$n", f"{folder_img_number():05}")
|
format = format.replace("$n", f"{folder_img_number():05}")
|
||||||
|
|
||||||
if "$tsb64" in format:
|
if "$tsb64" in format:
|
||||||
img_id = base_repr(int(now * 10000), 36)[-7:] + base_repr(int(batch_file_number), 36) # Base 36 conversion, 0-9, A-Z
|
img_id = base_repr(int(now * 10000), 36)[-7:] + base_repr(
|
||||||
|
int(batch_file_number), 36
|
||||||
|
) # Base 36 conversion, 0-9, A-Z
|
||||||
format = format.replace("$tsb64", img_id)
|
format = format.replace("$tsb64", img_id)
|
||||||
|
|
||||||
if "$ts" in format:
|
if "$ts" in format:
|
||||||
@ -101,6 +108,7 @@ def format_file_name(
|
|||||||
|
|
||||||
return filename_regex.sub("_", format)
|
return filename_regex.sub("_", format)
|
||||||
|
|
||||||
|
|
||||||
def save_images_to_disk(images: list, filtered_images: list, req: GenerateImageRequest, task_data: TaskData):
|
def save_images_to_disk(images: list, filtered_images: list, req: GenerateImageRequest, task_data: TaskData):
|
||||||
now = time.time()
|
now = time.time()
|
||||||
app_config = app.getConfig()
|
app_config = app.getConfig()
|
||||||
@ -126,7 +134,7 @@ def save_images_to_disk(images: list, filtered_images: list, req: GenerateImageR
|
|||||||
output_lossless=task_data.output_lossless,
|
output_lossless=task_data.output_lossless,
|
||||||
)
|
)
|
||||||
if task_data.metadata_output_format:
|
if task_data.metadata_output_format:
|
||||||
for metadata_output_format in task_data.metadata_output_format.split(','):
|
for metadata_output_format in task_data.metadata_output_format.split(","):
|
||||||
if metadata_output_format.lower() in ["json", "txt", "embed"]:
|
if metadata_output_format.lower() in ["json", "txt", "embed"]:
|
||||||
save_dicts(
|
save_dicts(
|
||||||
metadata_entries,
|
metadata_entries,
|
||||||
@ -142,7 +150,8 @@ def save_images_to_disk(images: list, filtered_images: list, req: GenerateImageR
|
|||||||
task_data,
|
task_data,
|
||||||
file_number,
|
file_number,
|
||||||
now=now,
|
now=now,
|
||||||
suffix="filtered")
|
suffix="filtered",
|
||||||
|
)
|
||||||
|
|
||||||
save_images(
|
save_images(
|
||||||
images,
|
images,
|
||||||
@ -171,27 +180,7 @@ def save_images_to_disk(images: list, filtered_images: list, req: GenerateImageR
|
|||||||
|
|
||||||
|
|
||||||
def get_metadata_entries_for_request(req: GenerateImageRequest, task_data: TaskData):
|
def get_metadata_entries_for_request(req: GenerateImageRequest, task_data: TaskData):
|
||||||
metadata = get_printable_request(req)
|
metadata = get_printable_request(req, task_data)
|
||||||
metadata.update(
|
|
||||||
{
|
|
||||||
"use_stable_diffusion_model": task_data.use_stable_diffusion_model,
|
|
||||||
"use_vae_model": task_data.use_vae_model,
|
|
||||||
"use_hypernetwork_model": task_data.use_hypernetwork_model,
|
|
||||||
"use_lora_model": task_data.use_lora_model,
|
|
||||||
"use_face_correction": task_data.use_face_correction,
|
|
||||||
"use_upscale": task_data.use_upscale,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
if metadata["use_upscale"] is not None:
|
|
||||||
metadata["upscale_amount"] = task_data.upscale_amount
|
|
||||||
if task_data.use_hypernetwork_model is None:
|
|
||||||
del metadata["hypernetwork_strength"]
|
|
||||||
if task_data.use_lora_model is None:
|
|
||||||
if "lora_alpha" in metadata:
|
|
||||||
del metadata["lora_alpha"]
|
|
||||||
app_config = app.getConfig()
|
|
||||||
if not app_config.get("test_diffusers", False) and "use_lora_model" in metadata:
|
|
||||||
del metadata["use_lora_model"]
|
|
||||||
|
|
||||||
# if text, format it in the text format expected by the UI
|
# if text, format it in the text format expected by the UI
|
||||||
is_txt_format = task_data.metadata_output_format.lower() == "txt"
|
is_txt_format = task_data.metadata_output_format.lower() == "txt"
|
||||||
@ -205,12 +194,33 @@ def get_metadata_entries_for_request(req: GenerateImageRequest, task_data: TaskD
|
|||||||
return entries
|
return entries
|
||||||
|
|
||||||
|
|
||||||
def get_printable_request(req: GenerateImageRequest):
|
def get_printable_request(req: GenerateImageRequest, task_data: TaskData):
|
||||||
metadata = req.dict()
|
req_metadata = req.dict()
|
||||||
del metadata["init_image"]
|
task_data_metadata = task_data.dict()
|
||||||
del metadata["init_image_mask"]
|
|
||||||
if req.init_image is None:
|
# Save the metadata in the order defined in TASK_TEXT_MAPPING
|
||||||
|
metadata = {}
|
||||||
|
for key in TASK_TEXT_MAPPING.keys():
|
||||||
|
if key in req_metadata:
|
||||||
|
metadata[key] = req_metadata[key]
|
||||||
|
elif key in task_data_metadata:
|
||||||
|
metadata[key] = task_data_metadata[key]
|
||||||
|
|
||||||
|
# Clean up the metadata
|
||||||
|
if req.init_image is None and "prompt_strength" in metadata:
|
||||||
del metadata["prompt_strength"]
|
del metadata["prompt_strength"]
|
||||||
|
if task_data.use_upscale is None and "upscale_amount" in metadata:
|
||||||
|
del metadata["upscale_amount"]
|
||||||
|
if task_data.use_hypernetwork_model is None and "hypernetwork_strength" in metadata:
|
||||||
|
del metadata["hypernetwork_strength"]
|
||||||
|
if task_data.use_lora_model is None and "lora_alpha" in metadata:
|
||||||
|
del metadata["lora_alpha"]
|
||||||
|
|
||||||
|
app_config = app.getConfig()
|
||||||
|
if not app_config.get("test_diffusers", False):
|
||||||
|
for key in (x for x in ["use_lora_model", "lora_alpha", "clip_skip"] if x in metadata):
|
||||||
|
del metadata[key]
|
||||||
|
|
||||||
return metadata
|
return metadata
|
||||||
|
|
||||||
|
|
||||||
@ -233,6 +243,7 @@ def make_filename_callback(
|
|||||||
|
|
||||||
return make_filename
|
return make_filename
|
||||||
|
|
||||||
|
|
||||||
def _calculate_img_number(save_dir_path: str, task_data: TaskData):
|
def _calculate_img_number(save_dir_path: str, task_data: TaskData):
|
||||||
def get_highest_img_number(accumulator: int, file: os.DirEntry) -> int:
|
def get_highest_img_number(accumulator: int, file: os.DirEntry) -> int:
|
||||||
if not file.is_file:
|
if not file.is_file:
|
||||||
@ -247,7 +258,7 @@ def _calculate_img_number(save_dir_path: str, task_data: TaskData):
|
|||||||
if not number_match:
|
if not number_match:
|
||||||
return accumulator
|
return accumulator
|
||||||
|
|
||||||
file_number = number_match.group().lstrip('0')
|
file_number = number_match.group().lstrip("0")
|
||||||
|
|
||||||
# Handle 00000
|
# Handle 00000
|
||||||
return int(file_number) if file_number else 0
|
return int(file_number) if file_number else 0
|
||||||
@ -273,7 +284,9 @@ def _calculate_img_number(save_dir_path: str, task_data: TaskData):
|
|||||||
_calculate_img_number.session_img_numbers[task_data.session_id] = calculated_img_number
|
_calculate_img_number.session_img_numbers[task_data.session_id] = calculated_img_number
|
||||||
return calculated_img_number
|
return calculated_img_number
|
||||||
|
|
||||||
|
|
||||||
_calculate_img_number.session_img_numbers = {}
|
_calculate_img_number.session_img_numbers = {}
|
||||||
|
|
||||||
|
|
||||||
def calculate_img_number(save_dir_path: str, task_data: TaskData):
|
def calculate_img_number(save_dir_path: str, task_data: TaskData):
|
||||||
return ImageNumber(lambda: _calculate_img_number(save_dir_path, task_data))
|
return ImageNumber(lambda: _calculate_img_number(save_dir_path, task_data))
|
||||||
|
@ -30,7 +30,7 @@
|
|||||||
<h1>
|
<h1>
|
||||||
<img id="logo_img" src="/media/images/icon-512x512.png" >
|
<img id="logo_img" src="/media/images/icon-512x512.png" >
|
||||||
Easy Diffusion
|
Easy Diffusion
|
||||||
<small><span id="version">v2.5.33</span> <span id="updateBranchLabel"></span></small>
|
<small><span id="version">v2.5.37</span> <span id="updateBranchLabel"></span></small>
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
<div id="server-status">
|
<div id="server-status">
|
||||||
@ -135,11 +135,14 @@
|
|||||||
<button id="reload-models" class="secondaryButton reloadModels"><i class='fa-solid fa-rotate'></i></button>
|
<button id="reload-models" class="secondaryButton reloadModels"><i class='fa-solid fa-rotate'></i></button>
|
||||||
<a href="https://github.com/cmdr2/stable-diffusion-ui/wiki/Custom-Models" target="_blank"><i class="fa-solid fa-circle-question help-btn"><span class="simple-tooltip top-left">Click to learn more about custom models</span></i></a>
|
<a href="https://github.com/cmdr2/stable-diffusion-ui/wiki/Custom-Models" target="_blank"><i class="fa-solid fa-circle-question help-btn"><span class="simple-tooltip top-left">Click to learn more about custom models</span></i></a>
|
||||||
</td></tr>
|
</td></tr>
|
||||||
<!-- <tr id="modelConfigSelection" class="pl-5"><td><label for="model_config">Model Config:</i></label></td><td>
|
<tr class="pl-5 displayNone" id="clip_skip_config">
|
||||||
<select id="model_config" name="model_config">
|
<td><label for="clip_skip">Clip Skip:</label></td>
|
||||||
</select>
|
<td>
|
||||||
</td></tr> -->
|
<input id="clip_skip" name="clip_skip" type="checkbox">
|
||||||
<tr class="pl-5"><td><label for="vae_model">Custom VAE:</i></label></td><td>
|
<a href="https://github.com/cmdr2/stable-diffusion-ui/wiki/Clip-Skip" target="_blank"><i class="fa-solid fa-circle-question help-btn"><span class="simple-tooltip top-left">Click to learn more about Clip Skip</span></i></a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="pl-5"><td><label for="vae_model">Custom VAE:</label></td><td>
|
||||||
<input id="vae_model" type="text" spellcheck="false" autocomplete="off" class="model-filter" data-path="" />
|
<input id="vae_model" type="text" spellcheck="false" autocomplete="off" class="model-filter" data-path="" />
|
||||||
<a href="https://github.com/cmdr2/stable-diffusion-ui/wiki/VAE-Variational-Auto-Encoder" target="_blank"><i class="fa-solid fa-circle-question help-btn"><span class="simple-tooltip top-left">Click to learn more about VAEs</span></i></a>
|
<a href="https://github.com/cmdr2/stable-diffusion-ui/wiki/VAE-Variational-Auto-Encoder" target="_blank"><i class="fa-solid fa-circle-question help-btn"><span class="simple-tooltip top-left">Click to learn more about VAEs</span></i></a>
|
||||||
</td></tr>
|
</td></tr>
|
||||||
@ -154,16 +157,18 @@
|
|||||||
<option value="dpm2_a">DPM2 Ancestral</option>
|
<option value="dpm2_a">DPM2 Ancestral</option>
|
||||||
<option value="lms">LMS</option>
|
<option value="lms">LMS</option>
|
||||||
<option value="dpm_solver_stability">DPM Solver (Stability AI)</option>
|
<option value="dpm_solver_stability">DPM Solver (Stability AI)</option>
|
||||||
<option value="dpmpp_2s_a">DPM++ 2s Ancestral (Karras)</option>
|
<option value="dpmpp_2s_a" class="k_diffusion-only">DPM++ 2s Ancestral (Karras)</option>
|
||||||
<option value="dpmpp_2m">DPM++ 2m (Karras)</option>
|
<option value="dpmpp_2m">DPM++ 2m (Karras)</option>
|
||||||
<option value="dpmpp_sde">DPM++ SDE (Karras)</option>
|
<option value="dpmpp_sde" class="k_diffusion-only">DPM++ SDE (Karras)</option>
|
||||||
<option value="dpm_fast">DPM Fast (Karras)</option>
|
<option value="dpm_fast" class="k_diffusion-only">DPM Fast (Karras)</option>
|
||||||
<option value="dpm_adaptive">DPM Adaptive (Karras)</option>
|
<option value="dpm_adaptive" class="k_diffusion-only">DPM Adaptive (Karras)</option>
|
||||||
<option value="unipc_snr">UniPC SNR</option>
|
<option value="ddpm" class="diffusers-only">DDPM</option>
|
||||||
|
<option value="deis" class="diffusers-only">DEIS</option>
|
||||||
|
<option value="unipc_snr" class="k_diffusion-only">UniPC SNR</option>
|
||||||
<option value="unipc_tu">UniPC TU</option>
|
<option value="unipc_tu">UniPC TU</option>
|
||||||
<option value="unipc_snr_2">UniPC SNR 2</option>
|
<option value="unipc_snr_2" class="k_diffusion-only">UniPC SNR 2</option>
|
||||||
<option value="unipc_tu_2">UniPC TU 2</option>
|
<option value="unipc_tu_2">UniPC TU 2</option>
|
||||||
<option value="unipc_tq">UniPC TQ</option>
|
<option value="unipc_tq" class="k_diffusion-only">UniPC TQ</option>
|
||||||
</select>
|
</select>
|
||||||
<a href="https://github.com/cmdr2/stable-diffusion-ui/wiki/How-to-Use#samplers" target="_blank"><i class="fa-solid fa-circle-question help-btn"><span class="simple-tooltip top-left">Click to learn more about samplers</span></i></a>
|
<a href="https://github.com/cmdr2/stable-diffusion-ui/wiki/How-to-Use#samplers" target="_blank"><i class="fa-solid fa-circle-question help-btn"><span class="simple-tooltip top-left">Click to learn more about samplers</span></i></a>
|
||||||
</td></tr>
|
</td></tr>
|
||||||
@ -217,14 +222,14 @@
|
|||||||
<tr class="pl-5"><td><label for="num_inference_steps">Inference Steps:</label></td><td> <input id="num_inference_steps" name="num_inference_steps" size="4" value="25" onkeypress="preventNonNumericalInput(event)"></td></tr>
|
<tr class="pl-5"><td><label for="num_inference_steps">Inference Steps:</label></td><td> <input id="num_inference_steps" name="num_inference_steps" size="4" value="25" onkeypress="preventNonNumericalInput(event)"></td></tr>
|
||||||
<tr class="pl-5"><td><label for="guidance_scale_slider">Guidance Scale:</label></td><td> <input id="guidance_scale_slider" name="guidance_scale_slider" class="editor-slider" value="75" type="range" min="11" max="500"> <input id="guidance_scale" name="guidance_scale" size="4" pattern="^[0-9\.]+$" onkeypress="preventNonNumericalInput(event)"></td></tr>
|
<tr class="pl-5"><td><label for="guidance_scale_slider">Guidance Scale:</label></td><td> <input id="guidance_scale_slider" name="guidance_scale_slider" class="editor-slider" value="75" type="range" min="11" max="500"> <input id="guidance_scale" name="guidance_scale" size="4" pattern="^[0-9\.]+$" onkeypress="preventNonNumericalInput(event)"></td></tr>
|
||||||
<tr id="prompt_strength_container" class="pl-5"><td><label for="prompt_strength_slider">Prompt Strength:</label></td><td> <input id="prompt_strength_slider" name="prompt_strength_slider" class="editor-slider" value="80" type="range" min="0" max="99"> <input id="prompt_strength" name="prompt_strength" size="4" pattern="^[0-9\.]+$" onkeypress="preventNonNumericalInput(event)"><br/></td></tr>
|
<tr id="prompt_strength_container" class="pl-5"><td><label for="prompt_strength_slider">Prompt Strength:</label></td><td> <input id="prompt_strength_slider" name="prompt_strength_slider" class="editor-slider" value="80" type="range" min="0" max="99"> <input id="prompt_strength" name="prompt_strength" size="4" pattern="^[0-9\.]+$" onkeypress="preventNonNumericalInput(event)"><br/></td></tr>
|
||||||
<tr id="lora_model_container" class="pl-5"><td><label for="lora_model">LoRA:</i></label></td><td>
|
<tr id="lora_model_container" class="pl-5"><td><label for="lora_model">LoRA:</label></td><td>
|
||||||
<input id="lora_model" type="text" spellcheck="false" autocomplete="off" class="model-filter" data-path="" />
|
<input id="lora_model" type="text" spellcheck="false" autocomplete="off" class="model-filter" data-path="" />
|
||||||
</td></tr>
|
</td></tr>
|
||||||
<tr id="lora_alpha_container" class="pl-5">
|
<tr id="lora_alpha_container" class="pl-5">
|
||||||
<td><label for="lora_alpha_slider">LoRA Strength:</label></td>
|
<td><label for="lora_alpha_slider">LoRA Strength:</label></td>
|
||||||
<td> <input id="lora_alpha_slider" name="lora_alpha_slider" class="editor-slider" value="50" type="range" min="0" max="100"> <input id="lora_alpha" name="lora_alpha" size="4" pattern="^[0-9\.]+$" onkeypress="preventNonNumericalInput(event)"><br/></td>
|
<td> <input id="lora_alpha_slider" name="lora_alpha_slider" class="editor-slider" value="50" type="range" min="0" max="100"> <input id="lora_alpha" name="lora_alpha" size="4" pattern="^[0-9\.]+$" onkeypress="preventNonNumericalInput(event)"><br/></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr class="pl-5"><td><label for="hypernetwork_model">Hypernetwork:</i></label></td><td>
|
<tr class="pl-5"><td><label for="hypernetwork_model">Hypernetwork:</label></td><td>
|
||||||
<input id="hypernetwork_model" type="text" spellcheck="false" autocomplete="off" class="model-filter" data-path="" />
|
<input id="hypernetwork_model" type="text" spellcheck="false" autocomplete="off" class="model-filter" data-path="" />
|
||||||
</td></tr>
|
</td></tr>
|
||||||
<tr id="hypernetwork_strength_container" class="pl-5">
|
<tr id="hypernetwork_strength_container" class="pl-5">
|
||||||
@ -238,7 +243,7 @@
|
|||||||
<option value="webp">webp</option>
|
<option value="webp">webp</option>
|
||||||
</select>
|
</select>
|
||||||
<span id="output_lossless_container" class="displayNone">
|
<span id="output_lossless_container" class="displayNone">
|
||||||
<input id="output_lossless" name="output_lossless" type="checkbox"><label for="output_lossless">Lossless</label></td></tr>
|
<input id="output_lossless" name="output_lossless" type="checkbox"><label for="output_lossless">Lossless</label>
|
||||||
</span>
|
</span>
|
||||||
</td></tr>
|
</td></tr>
|
||||||
<tr class="pl-5" id="output_quality_row"><td><label for="output_quality">Image Quality:</label></td><td>
|
<tr class="pl-5" id="output_quality_row"><td><label for="output_quality">Image Quality:</label></td><td>
|
||||||
|
@ -70,6 +70,14 @@
|
|||||||
max-height: calc(100vh - (var(--popup-padding) * 2) - 4px);
|
max-height: calc(100vh - (var(--popup-padding) * 2) - 4px);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#viewFullSizeImgModal img:not(.natural-zoom) {
|
||||||
|
cursor: grab;
|
||||||
|
}
|
||||||
|
|
||||||
|
#viewFullSizeImgModal .grabbing img:not(.natural-zoom) {
|
||||||
|
cursor: grabbing;
|
||||||
|
}
|
||||||
|
|
||||||
#viewFullSizeImgModal .content > div::-webkit-scrollbar-track, #viewFullSizeImgModal .content > div::-webkit-scrollbar-corner {
|
#viewFullSizeImgModal .content > div::-webkit-scrollbar-track, #viewFullSizeImgModal .content > div::-webkit-scrollbar-corner {
|
||||||
background: rgba(0, 0, 0, .5)
|
background: rgba(0, 0, 0, .5)
|
||||||
}
|
}
|
||||||
|
@ -1320,3 +1320,54 @@ body.wait-pause {
|
|||||||
.displayNone {
|
.displayNone {
|
||||||
display:none !important;
|
display:none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* TOAST NOTIFICATIONS */
|
||||||
|
.toast-notification {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 10px;
|
||||||
|
right: -300px;
|
||||||
|
width: 300px;
|
||||||
|
background-color: #333;
|
||||||
|
color: #fff;
|
||||||
|
padding: 10px 20px;
|
||||||
|
border-radius: 5px;
|
||||||
|
box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
|
||||||
|
z-index: 9999;
|
||||||
|
animation: slideInRight 0.5s ease forwards;
|
||||||
|
transition: bottom 0.5s ease; // Add a transition to smoothly reposition the toasts
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast-notification-error {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideInRight {
|
||||||
|
from {
|
||||||
|
right: -300px;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
right: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast-notification.hide {
|
||||||
|
animation: slideOutRight 0.5s ease forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideOutRight {
|
||||||
|
from {
|
||||||
|
right: 10px;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
right: -300px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideDown {
|
||||||
|
from {
|
||||||
|
bottom: 10px;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -58,7 +58,7 @@
|
|||||||
font-size: 10pt;
|
font-size: 10pt;
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
transition: none;
|
transition: none;
|
||||||
transition:property: none;
|
transition-property: none;
|
||||||
cursor: default;
|
cursor: default;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,7 +33,7 @@
|
|||||||
--input-height: 18px;
|
--input-height: 18px;
|
||||||
--tertiary-background-color: hsl(var(--main-hue), var(--main-saturation), calc(var(--value-base) + (2 * var(--value-step))));
|
--tertiary-background-color: hsl(var(--main-hue), var(--main-saturation), calc(var(--value-base) + (2 * var(--value-step))));
|
||||||
--tertiary-border-color: hsl(var(--main-hue), var(--main-saturation), calc(var(--value-base) + (3 * var(--value-step))));
|
--tertiary-border-color: hsl(var(--main-hue), var(--main-saturation), calc(var(--value-base) + (3 * var(--value-step))));
|
||||||
--tertiary-color: var(--input-text-color)
|
--tertiary-color: var(--input-text-color);
|
||||||
|
|
||||||
/* Main theme color, hex color fallback. */
|
/* Main theme color, hex color fallback. */
|
||||||
--theme-color-fallback: #673AB6;
|
--theme-color-fallback: #673AB6;
|
||||||
|
@ -13,6 +13,7 @@ const SETTINGS_IDS_LIST = [
|
|||||||
"num_outputs_total",
|
"num_outputs_total",
|
||||||
"num_outputs_parallel",
|
"num_outputs_parallel",
|
||||||
"stable_diffusion_model",
|
"stable_diffusion_model",
|
||||||
|
"clip_skip",
|
||||||
"vae_model",
|
"vae_model",
|
||||||
"hypernetwork_model",
|
"hypernetwork_model",
|
||||||
"lora_model",
|
"lora_model",
|
||||||
@ -52,27 +53,27 @@ const SETTINGS_IDS_LIST = [
|
|||||||
"auto_scroll",
|
"auto_scroll",
|
||||||
"zip_toggle",
|
"zip_toggle",
|
||||||
"tree_toggle",
|
"tree_toggle",
|
||||||
"json_toggle"
|
"json_toggle",
|
||||||
]
|
]
|
||||||
|
|
||||||
const IGNORE_BY_DEFAULT = [
|
const IGNORE_BY_DEFAULT = ["prompt"]
|
||||||
"prompt"
|
|
||||||
]
|
|
||||||
|
|
||||||
const SETTINGS_SECTIONS = [ // gets the "keys" property filled in with an ordered list of settings in this section via initSettings
|
const SETTINGS_SECTIONS = [
|
||||||
|
// gets the "keys" property filled in with an ordered list of settings in this section via initSettings
|
||||||
{ id: "editor-inputs", name: "Prompt" },
|
{ id: "editor-inputs", name: "Prompt" },
|
||||||
{ id: "editor-settings", name: "Image Settings" },
|
{ id: "editor-settings", name: "Image Settings" },
|
||||||
{ id: "system-settings", name: "System Settings" },
|
{ id: "system-settings", name: "System Settings" },
|
||||||
{ id: "container", name: "Other" }
|
{ id: "container", name: "Other" },
|
||||||
]
|
]
|
||||||
|
|
||||||
async function initSettings() {
|
async function initSettings() {
|
||||||
SETTINGS_IDS_LIST.forEach(id => {
|
SETTINGS_IDS_LIST.forEach((id) => {
|
||||||
var element = document.getElementById(id)
|
var element = document.getElementById(id)
|
||||||
if (!element) {
|
if (!element) {
|
||||||
console.error(`Missing settings element ${id}`)
|
console.error(`Missing settings element ${id}`)
|
||||||
}
|
}
|
||||||
if (id in SETTINGS) { // don't create it again
|
if (id in SETTINGS) {
|
||||||
|
// don't create it again
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
SETTINGS[id] = {
|
SETTINGS[id] = {
|
||||||
@ -81,28 +82,28 @@ async function initSettings() {
|
|||||||
label: getSettingLabel(element),
|
label: getSettingLabel(element),
|
||||||
default: getSetting(element),
|
default: getSetting(element),
|
||||||
value: getSetting(element),
|
value: getSetting(element),
|
||||||
ignore: IGNORE_BY_DEFAULT.includes(id)
|
ignore: IGNORE_BY_DEFAULT.includes(id),
|
||||||
}
|
}
|
||||||
element.addEventListener("input", settingChangeHandler)
|
element.addEventListener("input", settingChangeHandler)
|
||||||
element.addEventListener("change", settingChangeHandler)
|
element.addEventListener("change", settingChangeHandler)
|
||||||
})
|
})
|
||||||
var unsorted_settings_ids = [...SETTINGS_IDS_LIST]
|
var unsorted_settings_ids = [...SETTINGS_IDS_LIST]
|
||||||
SETTINGS_SECTIONS.forEach(section => {
|
SETTINGS_SECTIONS.forEach((section) => {
|
||||||
var name = section.name
|
var name = section.name
|
||||||
var element = document.getElementById(section.id)
|
var element = document.getElementById(section.id)
|
||||||
var unsorted_ids = unsorted_settings_ids.map(id => `#${id}`).join(",")
|
var unsorted_ids = unsorted_settings_ids.map((id) => `#${id}`).join(",")
|
||||||
var children = unsorted_ids == "" ? [] : Array.from(element.querySelectorAll(unsorted_ids));
|
var children = unsorted_ids == "" ? [] : Array.from(element.querySelectorAll(unsorted_ids))
|
||||||
section.keys = []
|
section.keys = []
|
||||||
children.forEach(e => {
|
children.forEach((e) => {
|
||||||
section.keys.push(e.id)
|
section.keys.push(e.id)
|
||||||
})
|
})
|
||||||
unsorted_settings_ids = unsorted_settings_ids.filter(id => children.find(e => e.id == id) == undefined)
|
unsorted_settings_ids = unsorted_settings_ids.filter((id) => children.find((e) => e.id == id) == undefined)
|
||||||
})
|
})
|
||||||
loadSettings()
|
loadSettings()
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSetting(element) {
|
function getSetting(element) {
|
||||||
if (element.dataset && 'path' in element.dataset) {
|
if (element.dataset && "path" in element.dataset) {
|
||||||
return element.dataset.path
|
return element.dataset.path
|
||||||
}
|
}
|
||||||
if (typeof element === "string" || element instanceof String) {
|
if (typeof element === "string" || element instanceof String) {
|
||||||
@ -114,7 +115,7 @@ function getSetting(element) {
|
|||||||
return element.value
|
return element.value
|
||||||
}
|
}
|
||||||
function setSetting(element, value) {
|
function setSetting(element, value) {
|
||||||
if (element.dataset && 'path' in element.dataset) {
|
if (element.dataset && "path" in element.dataset) {
|
||||||
element.dataset.path = value
|
element.dataset.path = value
|
||||||
return // no need to dispatch any event here because the models are not loaded yet
|
return // no need to dispatch any event here because the models are not loaded yet
|
||||||
}
|
}
|
||||||
@ -127,8 +128,7 @@ function setSetting(element, value) {
|
|||||||
}
|
}
|
||||||
if (element.type == "checkbox") {
|
if (element.type == "checkbox") {
|
||||||
element.checked = value
|
element.checked = value
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
element.value = value
|
element.value = value
|
||||||
}
|
}
|
||||||
element.dispatchEvent(new Event("input"))
|
element.dispatchEvent(new Event("input"))
|
||||||
@ -136,11 +136,11 @@ function setSetting(element, value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function saveSettings() {
|
function saveSettings() {
|
||||||
var saved_settings = Object.values(SETTINGS).map(setting => {
|
var saved_settings = Object.values(SETTINGS).map((setting) => {
|
||||||
return {
|
return {
|
||||||
key: setting.key,
|
key: setting.key,
|
||||||
value: setting.value,
|
value: setting.value,
|
||||||
ignore: setting.ignore
|
ignore: setting.ignore,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
localStorage.setItem(SETTINGS_KEY, JSON.stringify(saved_settings))
|
localStorage.setItem(SETTINGS_KEY, JSON.stringify(saved_settings))
|
||||||
@ -151,16 +151,16 @@ function loadSettings() {
|
|||||||
var saved_settings_text = localStorage.getItem(SETTINGS_KEY)
|
var saved_settings_text = localStorage.getItem(SETTINGS_KEY)
|
||||||
if (saved_settings_text) {
|
if (saved_settings_text) {
|
||||||
var saved_settings = JSON.parse(saved_settings_text)
|
var saved_settings = JSON.parse(saved_settings_text)
|
||||||
if (saved_settings.find(s => s.key == "auto_save_settings")?.value == false) {
|
if (saved_settings.find((s) => s.key == "auto_save_settings")?.value == false) {
|
||||||
setSetting("auto_save_settings", false)
|
setSetting("auto_save_settings", false)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
CURRENTLY_LOADING_SETTINGS = true
|
CURRENTLY_LOADING_SETTINGS = true
|
||||||
saved_settings.forEach(saved_setting => {
|
saved_settings.forEach((saved_setting) => {
|
||||||
var setting = SETTINGS[saved_setting.key]
|
var setting = SETTINGS[saved_setting.key]
|
||||||
if (!setting) {
|
if (!setting) {
|
||||||
console.warn(`Attempted to load setting ${saved_setting.key}, but no setting found`);
|
console.warn(`Attempted to load setting ${saved_setting.key}, but no setting found`)
|
||||||
return null;
|
return null
|
||||||
}
|
}
|
||||||
setting.ignore = saved_setting.ignore
|
setting.ignore = saved_setting.ignore
|
||||||
if (!setting.ignore) {
|
if (!setting.ignore) {
|
||||||
@ -169,10 +169,9 @@ function loadSettings() {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
CURRENTLY_LOADING_SETTINGS = false
|
CURRENTLY_LOADING_SETTINGS = false
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
CURRENTLY_LOADING_SETTINGS = true
|
CURRENTLY_LOADING_SETTINGS = true
|
||||||
tryLoadOldSettings();
|
tryLoadOldSettings()
|
||||||
CURRENTLY_LOADING_SETTINGS = false
|
CURRENTLY_LOADING_SETTINGS = false
|
||||||
saveSettings()
|
saveSettings()
|
||||||
}
|
}
|
||||||
@ -180,9 +179,9 @@ function loadSettings() {
|
|||||||
|
|
||||||
function loadDefaultSettingsSection(section_id) {
|
function loadDefaultSettingsSection(section_id) {
|
||||||
CURRENTLY_LOADING_SETTINGS = true
|
CURRENTLY_LOADING_SETTINGS = true
|
||||||
var section = SETTINGS_SECTIONS.find(s => s.id == section_id);
|
var section = SETTINGS_SECTIONS.find((s) => s.id == section_id)
|
||||||
section.keys.forEach(key => {
|
section.keys.forEach((key) => {
|
||||||
var setting = SETTINGS[key];
|
var setting = SETTINGS[key]
|
||||||
setting.value = setting.default
|
setting.value = setting.default
|
||||||
setSetting(setting.element, setting.value)
|
setSetting(setting.element, setting.value)
|
||||||
})
|
})
|
||||||
@ -218,10 +217,10 @@ function getSettingLabel(element) {
|
|||||||
|
|
||||||
function fillSaveSettingsConfigTable() {
|
function fillSaveSettingsConfigTable() {
|
||||||
saveSettingsConfigTable.textContent = ""
|
saveSettingsConfigTable.textContent = ""
|
||||||
SETTINGS_SECTIONS.forEach(section => {
|
SETTINGS_SECTIONS.forEach((section) => {
|
||||||
var section_row = `<tr><th>${section.name}</th><td></td></tr>`
|
var section_row = `<tr><th>${section.name}</th><td></td></tr>`
|
||||||
saveSettingsConfigTable.insertAdjacentHTML("beforeend", section_row)
|
saveSettingsConfigTable.insertAdjacentHTML("beforeend", section_row)
|
||||||
section.keys.forEach(key => {
|
section.keys.forEach((key) => {
|
||||||
var setting = SETTINGS[key]
|
var setting = SETTINGS[key]
|
||||||
var element = setting.element
|
var element = setting.element
|
||||||
var checkbox_id = `shouldsave_${element.id}`
|
var checkbox_id = `shouldsave_${element.id}`
|
||||||
@ -234,7 +233,7 @@ function fillSaveSettingsConfigTable() {
|
|||||||
var newrow = `<tr><td><label for="${checkbox_id}">${setting.label}</label></td><td><input id="${checkbox_id}" name="${checkbox_id}" ${is_checked} type="checkbox" ></td><td><small>(${value})</small></td></tr>`
|
var newrow = `<tr><td><label for="${checkbox_id}">${setting.label}</label></td><td><input id="${checkbox_id}" name="${checkbox_id}" ${is_checked} type="checkbox" ></td><td><small>(${value})</small></td></tr>`
|
||||||
saveSettingsConfigTable.insertAdjacentHTML("beforeend", newrow)
|
saveSettingsConfigTable.insertAdjacentHTML("beforeend", newrow)
|
||||||
var checkbox = document.getElementById(checkbox_id)
|
var checkbox = document.getElementById(checkbox_id)
|
||||||
checkbox.addEventListener("input", event => {
|
checkbox.addEventListener("input", (event) => {
|
||||||
setting.ignore = !checkbox.checked
|
setting.ignore = !checkbox.checked
|
||||||
saveSettings()
|
saveSettings()
|
||||||
})
|
})
|
||||||
@ -245,9 +244,6 @@ function fillSaveSettingsConfigTable() {
|
|||||||
|
|
||||||
// configureSettingsSaveBtn
|
// configureSettingsSaveBtn
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
var autoSaveSettings = document.getElementById("auto_save_settings")
|
var autoSaveSettings = document.getElementById("auto_save_settings")
|
||||||
var configSettingsButton = document.createElement("button")
|
var configSettingsButton = document.createElement("button")
|
||||||
configSettingsButton.textContent = "Configure"
|
configSettingsButton.textContent = "Configure"
|
||||||
@ -256,33 +252,32 @@ autoSaveSettings.insertAdjacentElement("beforebegin", configSettingsButton)
|
|||||||
autoSaveSettings.addEventListener("change", () => {
|
autoSaveSettings.addEventListener("change", () => {
|
||||||
configSettingsButton.style.display = autoSaveSettings.checked ? "block" : "none"
|
configSettingsButton.style.display = autoSaveSettings.checked ? "block" : "none"
|
||||||
})
|
})
|
||||||
configSettingsButton.addEventListener('click', () => {
|
configSettingsButton.addEventListener("click", () => {
|
||||||
fillSaveSettingsConfigTable()
|
fillSaveSettingsConfigTable()
|
||||||
saveSettingsConfigOverlay.classList.add("active")
|
saveSettingsConfigOverlay.classList.add("active")
|
||||||
})
|
})
|
||||||
resetImageSettingsButton.addEventListener('click', event => {
|
resetImageSettingsButton.addEventListener("click", (event) => {
|
||||||
loadDefaultSettingsSection("editor-settings");
|
loadDefaultSettingsSection("editor-settings")
|
||||||
event.stopPropagation()
|
event.stopPropagation()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
function tryLoadOldSettings() {
|
function tryLoadOldSettings() {
|
||||||
console.log("Loading old user settings")
|
console.log("Loading old user settings")
|
||||||
// load v1 auto-save.js settings
|
// load v1 auto-save.js settings
|
||||||
var old_map = {
|
var old_map = {
|
||||||
"guidance_scale_slider": "guidance_scale",
|
guidance_scale_slider: "guidance_scale",
|
||||||
"prompt_strength_slider": "prompt_strength"
|
prompt_strength_slider: "prompt_strength",
|
||||||
}
|
}
|
||||||
var settings_key_v1 = "user_settings"
|
var settings_key_v1 = "user_settings"
|
||||||
var saved_settings_text = localStorage.getItem(settings_key_v1)
|
var saved_settings_text = localStorage.getItem(settings_key_v1)
|
||||||
if (saved_settings_text) {
|
if (saved_settings_text) {
|
||||||
var saved_settings = JSON.parse(saved_settings_text)
|
var saved_settings = JSON.parse(saved_settings_text)
|
||||||
Object.keys(saved_settings.should_save).forEach(key => {
|
Object.keys(saved_settings.should_save).forEach((key) => {
|
||||||
key = key in old_map ? old_map[key] : key
|
key = key in old_map ? old_map[key] : key
|
||||||
if (!(key in SETTINGS)) return
|
if (!(key in SETTINGS)) return
|
||||||
SETTINGS[key].ignore = !saved_settings.should_save[key]
|
SETTINGS[key].ignore = !saved_settings.should_save[key]
|
||||||
});
|
})
|
||||||
Object.keys(saved_settings.values).forEach(key => {
|
Object.keys(saved_settings.values).forEach((key) => {
|
||||||
key = key in old_map ? old_map[key] : key
|
key = key in old_map ? old_map[key] : key
|
||||||
if (!(key in SETTINGS)) return
|
if (!(key in SETTINGS)) return
|
||||||
var setting = SETTINGS[key]
|
var setting = SETTINGS[key]
|
||||||
@ -290,38 +285,42 @@ function tryLoadOldSettings() {
|
|||||||
setting.value = saved_settings.values[key]
|
setting.value = saved_settings.values[key]
|
||||||
setSetting(setting.element, setting.value)
|
setSetting(setting.element, setting.value)
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
localStorage.removeItem(settings_key_v1)
|
localStorage.removeItem(settings_key_v1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// load old individually stored items
|
// load old individually stored items
|
||||||
var individual_settings_map = { // maps old localStorage-key to new SETTINGS-key
|
var individual_settings_map = {
|
||||||
"soundEnabled": "sound_toggle",
|
// maps old localStorage-key to new SETTINGS-key
|
||||||
"saveToDisk": "save_to_disk",
|
soundEnabled: "sound_toggle",
|
||||||
"useCPU": "use_cpu",
|
saveToDisk: "save_to_disk",
|
||||||
"diskPath": "diskPath",
|
useCPU: "use_cpu",
|
||||||
"useFaceCorrection": "use_face_correction",
|
diskPath: "diskPath",
|
||||||
"useUpscaling": "use_upscale",
|
useFaceCorrection: "use_face_correction",
|
||||||
"showOnlyFilteredImage": "show_only_filtered_image",
|
useUpscaling: "use_upscale",
|
||||||
"streamImageProgress": "stream_image_progress",
|
showOnlyFilteredImage: "show_only_filtered_image",
|
||||||
"outputFormat": "output_format",
|
streamImageProgress: "stream_image_progress",
|
||||||
"autoSaveSettings": "auto_save_settings",
|
outputFormat: "output_format",
|
||||||
};
|
autoSaveSettings: "auto_save_settings",
|
||||||
Object.keys(individual_settings_map).forEach(localStorageKey => {
|
}
|
||||||
var localStorageValue = localStorage.getItem(localStorageKey);
|
Object.keys(individual_settings_map).forEach((localStorageKey) => {
|
||||||
|
var localStorageValue = localStorage.getItem(localStorageKey)
|
||||||
if (localStorageValue !== null) {
|
if (localStorageValue !== null) {
|
||||||
let key = individual_settings_map[localStorageKey]
|
let key = individual_settings_map[localStorageKey]
|
||||||
var setting = SETTINGS[key]
|
var setting = SETTINGS[key]
|
||||||
if (!setting) {
|
if (!setting) {
|
||||||
console.warn(`Attempted to map old setting ${key}, but no setting found`);
|
console.warn(`Attempted to map old setting ${key}, but no setting found`)
|
||||||
return null;
|
return null
|
||||||
}
|
}
|
||||||
if (setting.element.type == "checkbox" && (typeof localStorageValue === "string" || localStorageValue instanceof String)) {
|
if (
|
||||||
|
setting.element.type == "checkbox" &&
|
||||||
|
(typeof localStorageValue === "string" || localStorageValue instanceof String)
|
||||||
|
) {
|
||||||
localStorageValue = localStorageValue == "true"
|
localStorageValue = localStorageValue == "true"
|
||||||
}
|
}
|
||||||
setting.value = localStorageValue
|
setting.value = localStorageValue
|
||||||
setSetting(setting.element, setting.value)
|
setSetting(setting.element, setting.value)
|
||||||
localStorage.removeItem(localStorageKey);
|
localStorage.removeItem(localStorageKey)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -1,25 +1,25 @@
|
|||||||
"use strict" // Opt in to a restricted variant of JavaScript
|
"use strict" // Opt in to a restricted variant of JavaScript
|
||||||
|
|
||||||
const EXT_REGEX = /(?:\.([^.]+))?$/
|
const EXT_REGEX = /(?:\.([^.]+))?$/
|
||||||
const TEXT_EXTENSIONS = ['txt', 'json']
|
const TEXT_EXTENSIONS = ["txt", "json"]
|
||||||
const IMAGE_EXTENSIONS = ['jpg', 'jpeg', 'png', 'bmp', 'tiff', 'tif', 'tga', 'webp']
|
const IMAGE_EXTENSIONS = ["jpg", "jpeg", "png", "bmp", "tiff", "tif", "tga", "webp"]
|
||||||
|
|
||||||
function parseBoolean(stringValue) {
|
function parseBoolean(stringValue) {
|
||||||
if (typeof stringValue === 'boolean') {
|
if (typeof stringValue === "boolean") {
|
||||||
return stringValue
|
return stringValue
|
||||||
}
|
}
|
||||||
if (typeof stringValue === 'number') {
|
if (typeof stringValue === "number") {
|
||||||
return stringValue !== 0
|
return stringValue !== 0
|
||||||
}
|
}
|
||||||
if (typeof stringValue !== 'string') {
|
if (typeof stringValue !== "string") {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
switch(stringValue?.toLowerCase()?.trim()) {
|
switch (stringValue?.toLowerCase()?.trim()) {
|
||||||
case "true":
|
case "true":
|
||||||
case "yes":
|
case "yes":
|
||||||
case "on":
|
case "on":
|
||||||
case "1":
|
case "1":
|
||||||
return true;
|
return true
|
||||||
|
|
||||||
case "false":
|
case "false":
|
||||||
case "no":
|
case "no":
|
||||||
@ -28,45 +28,51 @@ function parseBoolean(stringValue) {
|
|||||||
case "none":
|
case "none":
|
||||||
case null:
|
case null:
|
||||||
case undefined:
|
case undefined:
|
||||||
return false;
|
return false
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
return Boolean(JSON.parse(stringValue));
|
return Boolean(JSON.parse(stringValue))
|
||||||
} catch {
|
} catch {
|
||||||
return Boolean(stringValue)
|
return Boolean(stringValue)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// keep in sync with `ui/easydiffusion/utils/save_utils.py`
|
||||||
const TASK_MAPPING = {
|
const TASK_MAPPING = {
|
||||||
prompt: { name: 'Prompt',
|
prompt: {
|
||||||
|
name: "Prompt",
|
||||||
setUI: (prompt) => {
|
setUI: (prompt) => {
|
||||||
promptField.value = prompt
|
promptField.value = prompt
|
||||||
},
|
},
|
||||||
readUI: () => promptField.value,
|
readUI: () => promptField.value,
|
||||||
parse: (val) => val
|
parse: (val) => val,
|
||||||
},
|
},
|
||||||
negative_prompt: { name: 'Negative Prompt',
|
negative_prompt: {
|
||||||
|
name: "Negative Prompt",
|
||||||
setUI: (negative_prompt) => {
|
setUI: (negative_prompt) => {
|
||||||
negativePromptField.value = negative_prompt
|
negativePromptField.value = negative_prompt
|
||||||
},
|
},
|
||||||
readUI: () => negativePromptField.value,
|
readUI: () => negativePromptField.value,
|
||||||
parse: (val) => val
|
parse: (val) => val,
|
||||||
},
|
},
|
||||||
active_tags: { name: "Image Modifiers",
|
active_tags: {
|
||||||
|
name: "Image Modifiers",
|
||||||
setUI: (active_tags) => {
|
setUI: (active_tags) => {
|
||||||
refreshModifiersState(active_tags)
|
refreshModifiersState(active_tags)
|
||||||
},
|
},
|
||||||
readUI: () => activeTags.map(x => x.name),
|
readUI: () => activeTags.map((x) => x.name),
|
||||||
parse: (val) => val
|
parse: (val) => val,
|
||||||
},
|
},
|
||||||
inactive_tags: { name: "Inactive Image Modifiers",
|
inactive_tags: {
|
||||||
|
name: "Inactive Image Modifiers",
|
||||||
setUI: (inactive_tags) => {
|
setUI: (inactive_tags) => {
|
||||||
refreshInactiveTags(inactive_tags)
|
refreshInactiveTags(inactive_tags)
|
||||||
},
|
},
|
||||||
readUI: () => activeTags.filter(tag => tag.inactive === true).map(x => x.name),
|
readUI: () => activeTags.filter((tag) => tag.inactive === true).map((x) => x.name),
|
||||||
parse: (val) => val
|
parse: (val) => val,
|
||||||
},
|
},
|
||||||
width: { name: 'Width',
|
width: {
|
||||||
|
name: "Width",
|
||||||
setUI: (width) => {
|
setUI: (width) => {
|
||||||
const oldVal = widthField.value
|
const oldVal = widthField.value
|
||||||
widthField.value = width
|
widthField.value = width
|
||||||
@ -75,9 +81,10 @@ const TASK_MAPPING = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
readUI: () => parseInt(widthField.value),
|
readUI: () => parseInt(widthField.value),
|
||||||
parse: (val) => parseInt(val)
|
parse: (val) => parseInt(val),
|
||||||
},
|
},
|
||||||
height: { name: 'Height',
|
height: {
|
||||||
|
name: "Height",
|
||||||
setUI: (height) => {
|
setUI: (height) => {
|
||||||
const oldVal = heightField.value
|
const oldVal = heightField.value
|
||||||
heightField.value = height
|
heightField.value = height
|
||||||
@ -86,9 +93,10 @@ const TASK_MAPPING = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
readUI: () => parseInt(heightField.value),
|
readUI: () => parseInt(heightField.value),
|
||||||
parse: (val) => parseInt(val)
|
parse: (val) => parseInt(val),
|
||||||
},
|
},
|
||||||
seed: { name: 'Seed',
|
seed: {
|
||||||
|
name: "Seed",
|
||||||
setUI: (seed) => {
|
setUI: (seed) => {
|
||||||
if (!seed) {
|
if (!seed) {
|
||||||
randomSeedField.checked = true
|
randomSeedField.checked = true
|
||||||
@ -97,70 +105,80 @@ const TASK_MAPPING = {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
randomSeedField.checked = false
|
randomSeedField.checked = false
|
||||||
randomSeedField.dispatchEvent(new Event('change')) // let plugins know that the state of the random seed toggle changed
|
randomSeedField.dispatchEvent(new Event("change")) // let plugins know that the state of the random seed toggle changed
|
||||||
seedField.disabled = false
|
seedField.disabled = false
|
||||||
seedField.value = seed
|
seedField.value = seed
|
||||||
},
|
},
|
||||||
readUI: () => parseInt(seedField.value), // just return the value the user is seeing in the UI
|
readUI: () => parseInt(seedField.value), // just return the value the user is seeing in the UI
|
||||||
parse: (val) => parseInt(val)
|
parse: (val) => parseInt(val),
|
||||||
},
|
},
|
||||||
num_inference_steps: { name: 'Steps',
|
num_inference_steps: {
|
||||||
|
name: "Steps",
|
||||||
setUI: (num_inference_steps) => {
|
setUI: (num_inference_steps) => {
|
||||||
numInferenceStepsField.value = num_inference_steps
|
numInferenceStepsField.value = num_inference_steps
|
||||||
},
|
},
|
||||||
readUI: () => parseInt(numInferenceStepsField.value),
|
readUI: () => parseInt(numInferenceStepsField.value),
|
||||||
parse: (val) => parseInt(val)
|
parse: (val) => parseInt(val),
|
||||||
},
|
},
|
||||||
guidance_scale: { name: 'Guidance Scale',
|
guidance_scale: {
|
||||||
|
name: "Guidance Scale",
|
||||||
setUI: (guidance_scale) => {
|
setUI: (guidance_scale) => {
|
||||||
guidanceScaleField.value = guidance_scale
|
guidanceScaleField.value = guidance_scale
|
||||||
updateGuidanceScaleSlider()
|
updateGuidanceScaleSlider()
|
||||||
},
|
},
|
||||||
readUI: () => parseFloat(guidanceScaleField.value),
|
readUI: () => parseFloat(guidanceScaleField.value),
|
||||||
parse: (val) => parseFloat(val)
|
parse: (val) => parseFloat(val),
|
||||||
},
|
},
|
||||||
prompt_strength: { name: 'Prompt Strength',
|
prompt_strength: {
|
||||||
|
name: "Prompt Strength",
|
||||||
setUI: (prompt_strength) => {
|
setUI: (prompt_strength) => {
|
||||||
promptStrengthField.value = prompt_strength
|
promptStrengthField.value = prompt_strength
|
||||||
updatePromptStrengthSlider()
|
updatePromptStrengthSlider()
|
||||||
},
|
},
|
||||||
readUI: () => parseFloat(promptStrengthField.value),
|
readUI: () => parseFloat(promptStrengthField.value),
|
||||||
parse: (val) => parseFloat(val)
|
parse: (val) => parseFloat(val),
|
||||||
},
|
},
|
||||||
|
|
||||||
init_image: { name: 'Initial Image',
|
init_image: {
|
||||||
|
name: "Initial Image",
|
||||||
setUI: (init_image) => {
|
setUI: (init_image) => {
|
||||||
initImagePreview.src = init_image
|
initImagePreview.src = init_image
|
||||||
},
|
},
|
||||||
readUI: () => initImagePreview.src,
|
readUI: () => initImagePreview.src,
|
||||||
parse: (val) => val
|
parse: (val) => val,
|
||||||
},
|
},
|
||||||
mask: { name: 'Mask',
|
mask: {
|
||||||
|
name: "Mask",
|
||||||
setUI: (mask) => {
|
setUI: (mask) => {
|
||||||
setTimeout(() => { // add a delay to insure this happens AFTER the main image loads (which reloads the inpainter)
|
setTimeout(() => {
|
||||||
|
// add a delay to insure this happens AFTER the main image loads (which reloads the inpainter)
|
||||||
imageInpainter.setImg(mask)
|
imageInpainter.setImg(mask)
|
||||||
}, 250)
|
}, 250)
|
||||||
maskSetting.checked = Boolean(mask)
|
maskSetting.checked = Boolean(mask)
|
||||||
},
|
},
|
||||||
readUI: () => (maskSetting.checked ? imageInpainter.getImg() : undefined),
|
readUI: () => (maskSetting.checked ? imageInpainter.getImg() : undefined),
|
||||||
parse: (val) => val
|
parse: (val) => val,
|
||||||
},
|
},
|
||||||
preserve_init_image_color_profile: { name: 'Preserve Color Profile',
|
preserve_init_image_color_profile: {
|
||||||
|
name: "Preserve Color Profile",
|
||||||
setUI: (preserve_init_image_color_profile) => {
|
setUI: (preserve_init_image_color_profile) => {
|
||||||
applyColorCorrectionField.checked = parseBoolean(preserve_init_image_color_profile)
|
applyColorCorrectionField.checked = parseBoolean(preserve_init_image_color_profile)
|
||||||
},
|
},
|
||||||
readUI: () => applyColorCorrectionField.checked,
|
readUI: () => applyColorCorrectionField.checked,
|
||||||
parse: (val) => parseBoolean(val)
|
parse: (val) => parseBoolean(val),
|
||||||
},
|
},
|
||||||
|
|
||||||
use_face_correction: { name: 'Use Face Correction',
|
use_face_correction: {
|
||||||
|
name: "Use Face Correction",
|
||||||
setUI: (use_face_correction) => {
|
setUI: (use_face_correction) => {
|
||||||
const oldVal = gfpganModelField.value
|
const oldVal = gfpganModelField.value
|
||||||
gfpganModelField.value = getModelPath(use_face_correction, ['.pth'])
|
gfpganModelField.value = getModelPath(use_face_correction, [".pth"])
|
||||||
if (gfpganModelField.value) { // Is a valid value for the field.
|
if (gfpganModelField.value) {
|
||||||
|
// Is a valid value for the field.
|
||||||
useFaceCorrectionField.checked = true
|
useFaceCorrectionField.checked = true
|
||||||
gfpganModelField.disabled = false
|
gfpganModelField.disabled = false
|
||||||
} else { // Not a valid value, restore the old value and disable the filter.
|
} else {
|
||||||
|
// Not a valid value, restore the old value and disable the filter.
|
||||||
gfpganModelField.disabled = true
|
gfpganModelField.disabled = true
|
||||||
gfpganModelField.value = oldVal
|
gfpganModelField.value = oldVal
|
||||||
useFaceCorrectionField.checked = false
|
useFaceCorrectionField.checked = false
|
||||||
@ -169,17 +187,20 @@ const TASK_MAPPING = {
|
|||||||
//useFaceCorrectionField.checked = parseBoolean(use_face_correction)
|
//useFaceCorrectionField.checked = parseBoolean(use_face_correction)
|
||||||
},
|
},
|
||||||
readUI: () => (useFaceCorrectionField.checked ? gfpganModelField.value : undefined),
|
readUI: () => (useFaceCorrectionField.checked ? gfpganModelField.value : undefined),
|
||||||
parse: (val) => val
|
parse: (val) => val,
|
||||||
},
|
},
|
||||||
use_upscale: { name: 'Use Upscaling',
|
use_upscale: {
|
||||||
|
name: "Use Upscaling",
|
||||||
setUI: (use_upscale) => {
|
setUI: (use_upscale) => {
|
||||||
const oldVal = upscaleModelField.value
|
const oldVal = upscaleModelField.value
|
||||||
upscaleModelField.value = getModelPath(use_upscale, ['.pth'])
|
upscaleModelField.value = getModelPath(use_upscale, [".pth"])
|
||||||
if (upscaleModelField.value) { // Is a valid value for the field.
|
if (upscaleModelField.value) {
|
||||||
|
// Is a valid value for the field.
|
||||||
useUpscalingField.checked = true
|
useUpscalingField.checked = true
|
||||||
upscaleModelField.disabled = false
|
upscaleModelField.disabled = false
|
||||||
upscaleAmountField.disabled = false
|
upscaleAmountField.disabled = false
|
||||||
} else { // Not a valid value, restore the old value and disable the filter.
|
} else {
|
||||||
|
// Not a valid value, restore the old value and disable the filter.
|
||||||
upscaleModelField.disabled = true
|
upscaleModelField.disabled = true
|
||||||
upscaleAmountField.disabled = true
|
upscaleAmountField.disabled = true
|
||||||
upscaleModelField.value = oldVal
|
upscaleModelField.value = oldVal
|
||||||
@ -187,27 +208,30 @@ const TASK_MAPPING = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
readUI: () => (useUpscalingField.checked ? upscaleModelField.value : undefined),
|
readUI: () => (useUpscalingField.checked ? upscaleModelField.value : undefined),
|
||||||
parse: (val) => val
|
parse: (val) => val,
|
||||||
},
|
},
|
||||||
upscale_amount: { name: 'Upscale By',
|
upscale_amount: {
|
||||||
|
name: "Upscale By",
|
||||||
setUI: (upscale_amount) => {
|
setUI: (upscale_amount) => {
|
||||||
upscaleAmountField.value = upscale_amount
|
upscaleAmountField.value = upscale_amount
|
||||||
},
|
},
|
||||||
readUI: () => upscaleAmountField.value,
|
readUI: () => upscaleAmountField.value,
|
||||||
parse: (val) => val
|
parse: (val) => val,
|
||||||
},
|
},
|
||||||
sampler_name: { name: 'Sampler',
|
sampler_name: {
|
||||||
|
name: "Sampler",
|
||||||
setUI: (sampler_name) => {
|
setUI: (sampler_name) => {
|
||||||
samplerField.value = sampler_name
|
samplerField.value = sampler_name
|
||||||
},
|
},
|
||||||
readUI: () => samplerField.value,
|
readUI: () => samplerField.value,
|
||||||
parse: (val) => val
|
parse: (val) => val,
|
||||||
},
|
},
|
||||||
use_stable_diffusion_model: { name: 'Stable Diffusion model',
|
use_stable_diffusion_model: {
|
||||||
|
name: "Stable Diffusion model",
|
||||||
setUI: (use_stable_diffusion_model) => {
|
setUI: (use_stable_diffusion_model) => {
|
||||||
const oldVal = stableDiffusionModelField.value
|
const oldVal = stableDiffusionModelField.value
|
||||||
|
|
||||||
use_stable_diffusion_model = getModelPath(use_stable_diffusion_model, ['.ckpt', '.safetensors'])
|
use_stable_diffusion_model = getModelPath(use_stable_diffusion_model, [".ckpt", ".safetensors"])
|
||||||
stableDiffusionModelField.value = use_stable_diffusion_model
|
stableDiffusionModelField.value = use_stable_diffusion_model
|
||||||
|
|
||||||
if (!stableDiffusionModelField.value) {
|
if (!stableDiffusionModelField.value) {
|
||||||
@ -215,126 +239,154 @@ const TASK_MAPPING = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
readUI: () => stableDiffusionModelField.value,
|
readUI: () => stableDiffusionModelField.value,
|
||||||
parse: (val) => val
|
parse: (val) => val,
|
||||||
},
|
},
|
||||||
use_vae_model: { name: 'VAE model',
|
clip_skip: {
|
||||||
|
name: "Clip Skip",
|
||||||
|
setUI: (value) => {
|
||||||
|
clip_skip.checked = value
|
||||||
|
},
|
||||||
|
readUI: () => clip_skip.checked,
|
||||||
|
parse: (val) => Boolean(val),
|
||||||
|
},
|
||||||
|
use_vae_model: {
|
||||||
|
name: "VAE model",
|
||||||
setUI: (use_vae_model) => {
|
setUI: (use_vae_model) => {
|
||||||
const oldVal = vaeModelField.value
|
const oldVal = vaeModelField.value
|
||||||
use_vae_model = (use_vae_model === undefined || use_vae_model === null || use_vae_model === 'None' ? '' : use_vae_model)
|
use_vae_model =
|
||||||
|
use_vae_model === undefined || use_vae_model === null || use_vae_model === "None" ? "" : use_vae_model
|
||||||
|
|
||||||
if (use_vae_model !== '') {
|
if (use_vae_model !== "") {
|
||||||
use_vae_model = getModelPath(use_vae_model, ['.vae.pt', '.ckpt'])
|
use_vae_model = getModelPath(use_vae_model, [".vae.pt", ".ckpt"])
|
||||||
use_vae_model = use_vae_model !== '' ? use_vae_model : oldVal
|
use_vae_model = use_vae_model !== "" ? use_vae_model : oldVal
|
||||||
}
|
}
|
||||||
vaeModelField.value = use_vae_model
|
vaeModelField.value = use_vae_model
|
||||||
},
|
},
|
||||||
readUI: () => vaeModelField.value,
|
readUI: () => vaeModelField.value,
|
||||||
parse: (val) => val
|
parse: (val) => val,
|
||||||
},
|
},
|
||||||
use_lora_model: { name: 'LoRA model',
|
use_lora_model: {
|
||||||
|
name: "LoRA model",
|
||||||
setUI: (use_lora_model) => {
|
setUI: (use_lora_model) => {
|
||||||
const oldVal = loraModelField.value
|
const oldVal = loraModelField.value
|
||||||
use_lora_model = (use_lora_model === undefined || use_lora_model === null || use_lora_model === 'None' ? '' : use_lora_model)
|
use_lora_model =
|
||||||
|
use_lora_model === undefined || use_lora_model === null || use_lora_model === "None"
|
||||||
|
? ""
|
||||||
|
: use_lora_model
|
||||||
|
|
||||||
if (use_lora_model !== '') {
|
if (use_lora_model !== "") {
|
||||||
use_lora_model = getModelPath(use_lora_model, ['.ckpt', '.safetensors'])
|
use_lora_model = getModelPath(use_lora_model, [".ckpt", ".safetensors"])
|
||||||
use_lora_model = use_lora_model !== '' ? use_lora_model : oldVal
|
use_lora_model = use_lora_model !== "" ? use_lora_model : oldVal
|
||||||
}
|
}
|
||||||
loraModelField.value = use_lora_model
|
loraModelField.value = use_lora_model
|
||||||
},
|
},
|
||||||
readUI: () => loraModelField.value,
|
readUI: () => loraModelField.value,
|
||||||
parse: (val) => val
|
parse: (val) => val,
|
||||||
},
|
},
|
||||||
lora_alpha: { name: 'LoRA Strength',
|
lora_alpha: {
|
||||||
|
name: "LoRA Strength",
|
||||||
setUI: (lora_alpha) => {
|
setUI: (lora_alpha) => {
|
||||||
loraAlphaField.value = lora_alpha
|
loraAlphaField.value = lora_alpha
|
||||||
updateLoraAlphaSlider()
|
updateLoraAlphaSlider()
|
||||||
},
|
},
|
||||||
readUI: () => parseFloat(loraAlphaField.value),
|
readUI: () => parseFloat(loraAlphaField.value),
|
||||||
parse: (val) => parseFloat(val)
|
parse: (val) => parseFloat(val),
|
||||||
},
|
},
|
||||||
use_hypernetwork_model: { name: 'Hypernetwork model',
|
use_hypernetwork_model: {
|
||||||
|
name: "Hypernetwork model",
|
||||||
setUI: (use_hypernetwork_model) => {
|
setUI: (use_hypernetwork_model) => {
|
||||||
const oldVal = hypernetworkModelField.value
|
const oldVal = hypernetworkModelField.value
|
||||||
use_hypernetwork_model = (use_hypernetwork_model === undefined || use_hypernetwork_model === null || use_hypernetwork_model === 'None' ? '' : use_hypernetwork_model)
|
use_hypernetwork_model =
|
||||||
|
use_hypernetwork_model === undefined ||
|
||||||
|
use_hypernetwork_model === null ||
|
||||||
|
use_hypernetwork_model === "None"
|
||||||
|
? ""
|
||||||
|
: use_hypernetwork_model
|
||||||
|
|
||||||
if (use_hypernetwork_model !== '') {
|
if (use_hypernetwork_model !== "") {
|
||||||
use_hypernetwork_model = getModelPath(use_hypernetwork_model, ['.pt'])
|
use_hypernetwork_model = getModelPath(use_hypernetwork_model, [".pt"])
|
||||||
use_hypernetwork_model = use_hypernetwork_model !== '' ? use_hypernetwork_model : oldVal
|
use_hypernetwork_model = use_hypernetwork_model !== "" ? use_hypernetwork_model : oldVal
|
||||||
}
|
}
|
||||||
hypernetworkModelField.value = use_hypernetwork_model
|
hypernetworkModelField.value = use_hypernetwork_model
|
||||||
hypernetworkModelField.dispatchEvent(new Event('change'))
|
hypernetworkModelField.dispatchEvent(new Event("change"))
|
||||||
},
|
},
|
||||||
readUI: () => hypernetworkModelField.value,
|
readUI: () => hypernetworkModelField.value,
|
||||||
parse: (val) => val
|
parse: (val) => val,
|
||||||
},
|
},
|
||||||
hypernetwork_strength: { name: 'Hypernetwork Strength',
|
hypernetwork_strength: {
|
||||||
|
name: "Hypernetwork Strength",
|
||||||
setUI: (hypernetwork_strength) => {
|
setUI: (hypernetwork_strength) => {
|
||||||
hypernetworkStrengthField.value = hypernetwork_strength
|
hypernetworkStrengthField.value = hypernetwork_strength
|
||||||
updateHypernetworkStrengthSlider()
|
updateHypernetworkStrengthSlider()
|
||||||
},
|
},
|
||||||
readUI: () => parseFloat(hypernetworkStrengthField.value),
|
readUI: () => parseFloat(hypernetworkStrengthField.value),
|
||||||
parse: (val) => parseFloat(val)
|
parse: (val) => parseFloat(val),
|
||||||
},
|
},
|
||||||
|
|
||||||
num_outputs: { name: 'Parallel Images',
|
num_outputs: {
|
||||||
|
name: "Parallel Images",
|
||||||
setUI: (num_outputs) => {
|
setUI: (num_outputs) => {
|
||||||
numOutputsParallelField.value = num_outputs
|
numOutputsParallelField.value = num_outputs
|
||||||
},
|
},
|
||||||
readUI: () => parseInt(numOutputsParallelField.value),
|
readUI: () => parseInt(numOutputsParallelField.value),
|
||||||
parse: (val) => val
|
parse: (val) => val,
|
||||||
},
|
},
|
||||||
|
|
||||||
use_cpu: { name: 'Use CPU',
|
use_cpu: {
|
||||||
|
name: "Use CPU",
|
||||||
setUI: (use_cpu) => {
|
setUI: (use_cpu) => {
|
||||||
useCPUField.checked = use_cpu
|
useCPUField.checked = use_cpu
|
||||||
},
|
},
|
||||||
readUI: () => useCPUField.checked,
|
readUI: () => useCPUField.checked,
|
||||||
parse: (val) => val
|
parse: (val) => val,
|
||||||
},
|
},
|
||||||
|
|
||||||
stream_image_progress: { name: 'Stream Image Progress',
|
stream_image_progress: {
|
||||||
|
name: "Stream Image Progress",
|
||||||
setUI: (stream_image_progress) => {
|
setUI: (stream_image_progress) => {
|
||||||
streamImageProgressField.checked = (parseInt(numOutputsTotalField.value) > 50 ? false : stream_image_progress)
|
streamImageProgressField.checked = parseInt(numOutputsTotalField.value) > 50 ? false : stream_image_progress
|
||||||
},
|
},
|
||||||
readUI: () => streamImageProgressField.checked,
|
readUI: () => streamImageProgressField.checked,
|
||||||
parse: (val) => Boolean(val)
|
parse: (val) => Boolean(val),
|
||||||
},
|
},
|
||||||
show_only_filtered_image: { name: 'Show only the corrected/upscaled image',
|
show_only_filtered_image: {
|
||||||
|
name: "Show only the corrected/upscaled image",
|
||||||
setUI: (show_only_filtered_image) => {
|
setUI: (show_only_filtered_image) => {
|
||||||
showOnlyFilteredImageField.checked = show_only_filtered_image
|
showOnlyFilteredImageField.checked = show_only_filtered_image
|
||||||
},
|
},
|
||||||
readUI: () => showOnlyFilteredImageField.checked,
|
readUI: () => showOnlyFilteredImageField.checked,
|
||||||
parse: (val) => Boolean(val)
|
parse: (val) => Boolean(val),
|
||||||
},
|
},
|
||||||
output_format: { name: 'Output Format',
|
output_format: {
|
||||||
|
name: "Output Format",
|
||||||
setUI: (output_format) => {
|
setUI: (output_format) => {
|
||||||
outputFormatField.value = output_format
|
outputFormatField.value = output_format
|
||||||
},
|
},
|
||||||
readUI: () => outputFormatField.value,
|
readUI: () => outputFormatField.value,
|
||||||
parse: (val) => val
|
parse: (val) => val,
|
||||||
},
|
},
|
||||||
save_to_disk_path: { name: 'Save to disk path',
|
save_to_disk_path: {
|
||||||
|
name: "Save to disk path",
|
||||||
setUI: (save_to_disk_path) => {
|
setUI: (save_to_disk_path) => {
|
||||||
saveToDiskField.checked = Boolean(save_to_disk_path)
|
saveToDiskField.checked = Boolean(save_to_disk_path)
|
||||||
diskPathField.value = save_to_disk_path
|
diskPathField.value = save_to_disk_path
|
||||||
},
|
},
|
||||||
readUI: () => diskPathField.value,
|
readUI: () => diskPathField.value,
|
||||||
parse: (val) => val
|
parse: (val) => val,
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
function restoreTaskToUI(task, fieldsToSkip) {
|
function restoreTaskToUI(task, fieldsToSkip) {
|
||||||
fieldsToSkip = fieldsToSkip || []
|
fieldsToSkip = fieldsToSkip || []
|
||||||
|
|
||||||
if ('numOutputsTotal' in task) {
|
if ("numOutputsTotal" in task) {
|
||||||
numOutputsTotalField.value = task.numOutputsTotal
|
numOutputsTotalField.value = task.numOutputsTotal
|
||||||
}
|
}
|
||||||
if ('seed' in task) {
|
if ("seed" in task) {
|
||||||
randomSeedField.checked = false
|
randomSeedField.checked = false
|
||||||
seedField.value = task.seed
|
seedField.value = task.seed
|
||||||
}
|
}
|
||||||
if (!('reqBody' in task)) {
|
if (!("reqBody" in task)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for (const key in TASK_MAPPING) {
|
for (const key in TASK_MAPPING) {
|
||||||
@ -344,31 +396,31 @@ function restoreTaskToUI(task, fieldsToSkip) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// properly reset fields not present in the task
|
// properly reset fields not present in the task
|
||||||
if (!('use_hypernetwork_model' in task.reqBody)) {
|
if (!("use_hypernetwork_model" in task.reqBody)) {
|
||||||
hypernetworkModelField.value = ""
|
hypernetworkModelField.value = ""
|
||||||
hypernetworkModelField.dispatchEvent(new Event("change"))
|
hypernetworkModelField.dispatchEvent(new Event("change"))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!('use_lora_model' in task.reqBody)) {
|
if (!("use_lora_model" in task.reqBody)) {
|
||||||
loraModelField.value = ""
|
loraModelField.value = ""
|
||||||
loraModelField.dispatchEvent(new Event("change"))
|
loraModelField.dispatchEvent(new Event("change"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// restore the original prompt if provided (e.g. use settings), fallback to prompt as needed (e.g. copy/paste or d&d)
|
// restore the original prompt if provided (e.g. use settings), fallback to prompt as needed (e.g. copy/paste or d&d)
|
||||||
promptField.value = task.reqBody.original_prompt
|
promptField.value = task.reqBody.original_prompt
|
||||||
if (!('original_prompt' in task.reqBody)) {
|
if (!("original_prompt" in task.reqBody)) {
|
||||||
promptField.value = task.reqBody.prompt
|
promptField.value = task.reqBody.prompt
|
||||||
}
|
}
|
||||||
|
|
||||||
// properly reset checkboxes
|
// properly reset checkboxes
|
||||||
if (!('use_face_correction' in task.reqBody)) {
|
if (!("use_face_correction" in task.reqBody)) {
|
||||||
useFaceCorrectionField.checked = false
|
useFaceCorrectionField.checked = false
|
||||||
gfpganModelField.disabled = true
|
gfpganModelField.disabled = true
|
||||||
}
|
}
|
||||||
if (!('use_upscale' in task.reqBody)) {
|
if (!("use_upscale" in task.reqBody)) {
|
||||||
useUpscalingField.checked = false
|
useUpscalingField.checked = false
|
||||||
}
|
}
|
||||||
if (!('mask' in task.reqBody) && maskSetting.checked) {
|
if (!("mask" in task.reqBody) && maskSetting.checked) {
|
||||||
maskSetting.checked = false
|
maskSetting.checked = false
|
||||||
maskSetting.dispatchEvent(new Event("click"))
|
maskSetting.dispatchEvent(new Event("click"))
|
||||||
}
|
}
|
||||||
@ -379,15 +431,18 @@ function restoreTaskToUI(task, fieldsToSkip) {
|
|||||||
if (IMAGE_REGEX.test(initImagePreview.src) && task.reqBody.init_image == undefined) {
|
if (IMAGE_REGEX.test(initImagePreview.src) && task.reqBody.init_image == undefined) {
|
||||||
// hide source image
|
// hide source image
|
||||||
initImageClearBtn.dispatchEvent(new Event("click"))
|
initImageClearBtn.dispatchEvent(new Event("click"))
|
||||||
}
|
} else if (task.reqBody.init_image !== undefined) {
|
||||||
else if (task.reqBody.init_image !== undefined) {
|
|
||||||
// listen for inpainter loading event, which happens AFTER the main image loads (which reloads the inpainter)
|
// listen for inpainter loading event, which happens AFTER the main image loads (which reloads the inpainter)
|
||||||
initImagePreview.addEventListener('load', function() {
|
initImagePreview.addEventListener(
|
||||||
|
"load",
|
||||||
|
function() {
|
||||||
if (Boolean(task.reqBody.mask)) {
|
if (Boolean(task.reqBody.mask)) {
|
||||||
imageInpainter.setImg(task.reqBody.mask)
|
imageInpainter.setImg(task.reqBody.mask)
|
||||||
maskSetting.checked = true
|
maskSetting.checked = true
|
||||||
}
|
}
|
||||||
}, { once: true })
|
},
|
||||||
|
{ once: true }
|
||||||
|
)
|
||||||
initImagePreview.src = task.reqBody.init_image
|
initImagePreview.src = task.reqBody.init_image
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -397,28 +452,26 @@ function readUI() {
|
|||||||
reqBody[key] = TASK_MAPPING[key].readUI()
|
reqBody[key] = TASK_MAPPING[key].readUI()
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
'numOutputsTotal': parseInt(numOutputsTotalField.value),
|
numOutputsTotal: parseInt(numOutputsTotalField.value),
|
||||||
'seed': TASK_MAPPING['seed'].readUI(),
|
seed: TASK_MAPPING["seed"].readUI(),
|
||||||
'reqBody': reqBody
|
reqBody: reqBody,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function getModelPath(filename, extensions)
|
function getModelPath(filename, extensions) {
|
||||||
{
|
|
||||||
if (typeof filename !== "string") {
|
if (typeof filename !== "string") {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let pathIdx
|
let pathIdx
|
||||||
if (filename.includes('/models/stable-diffusion/')) {
|
if (filename.includes("/models/stable-diffusion/")) {
|
||||||
pathIdx = filename.indexOf('/models/stable-diffusion/') + 25 // Linux, Mac paths
|
pathIdx = filename.indexOf("/models/stable-diffusion/") + 25 // Linux, Mac paths
|
||||||
}
|
} else if (filename.includes("\\models\\stable-diffusion\\")) {
|
||||||
else if (filename.includes('\\models\\stable-diffusion\\')) {
|
pathIdx = filename.indexOf("\\models\\stable-diffusion\\") + 25 // Linux, Mac paths
|
||||||
pathIdx = filename.indexOf('\\models\\stable-diffusion\\') + 25 // Linux, Mac paths
|
|
||||||
}
|
}
|
||||||
if (pathIdx >= 0) {
|
if (pathIdx >= 0) {
|
||||||
filename = filename.slice(pathIdx)
|
filename = filename.slice(pathIdx)
|
||||||
}
|
}
|
||||||
extensions.forEach(ext => {
|
extensions.forEach((ext) => {
|
||||||
if (filename.endsWith(ext)) {
|
if (filename.endsWith(ext)) {
|
||||||
filename = filename.slice(0, filename.length - ext.length)
|
filename = filename.slice(0, filename.length - ext.length)
|
||||||
}
|
}
|
||||||
@ -427,26 +480,26 @@ function getModelPath(filename, extensions)
|
|||||||
}
|
}
|
||||||
|
|
||||||
const TASK_TEXT_MAPPING = {
|
const TASK_TEXT_MAPPING = {
|
||||||
prompt: 'Prompt',
|
prompt: "Prompt",
|
||||||
width: 'Width',
|
width: "Width",
|
||||||
height: 'Height',
|
height: "Height",
|
||||||
seed: 'Seed',
|
seed: "Seed",
|
||||||
num_inference_steps: 'Steps',
|
num_inference_steps: "Steps",
|
||||||
guidance_scale: 'Guidance Scale',
|
guidance_scale: "Guidance Scale",
|
||||||
prompt_strength: 'Prompt Strength',
|
prompt_strength: "Prompt Strength",
|
||||||
use_face_correction: 'Use Face Correction',
|
use_face_correction: "Use Face Correction",
|
||||||
use_upscale: 'Use Upscaling',
|
use_upscale: "Use Upscaling",
|
||||||
upscale_amount: 'Upscale By',
|
upscale_amount: "Upscale By",
|
||||||
sampler_name: 'Sampler',
|
sampler_name: "Sampler",
|
||||||
negative_prompt: 'Negative Prompt',
|
negative_prompt: "Negative Prompt",
|
||||||
use_stable_diffusion_model: 'Stable Diffusion model',
|
use_stable_diffusion_model: "Stable Diffusion model",
|
||||||
use_hypernetwork_model: 'Hypernetwork model',
|
use_hypernetwork_model: "Hypernetwork model",
|
||||||
hypernetwork_strength: 'Hypernetwork Strength'
|
hypernetwork_strength: "Hypernetwork Strength",
|
||||||
}
|
}
|
||||||
function parseTaskFromText(str) {
|
function parseTaskFromText(str) {
|
||||||
const taskReqBody = {}
|
const taskReqBody = {}
|
||||||
|
|
||||||
const lines = str.split('\n')
|
const lines = str.split("\n")
|
||||||
if (lines.length === 0) {
|
if (lines.length === 0) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -454,14 +507,14 @@ function parseTaskFromText(str) {
|
|||||||
// Prompt
|
// Prompt
|
||||||
let knownKeyOnFirstLine = false
|
let knownKeyOnFirstLine = false
|
||||||
for (let key in TASK_TEXT_MAPPING) {
|
for (let key in TASK_TEXT_MAPPING) {
|
||||||
if (lines[0].startsWith(TASK_TEXT_MAPPING[key] + ':')) {
|
if (lines[0].startsWith(TASK_TEXT_MAPPING[key] + ":")) {
|
||||||
knownKeyOnFirstLine = true
|
knownKeyOnFirstLine = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!knownKeyOnFirstLine) {
|
if (!knownKeyOnFirstLine) {
|
||||||
taskReqBody.prompt = lines[0]
|
taskReqBody.prompt = lines[0]
|
||||||
console.log('Prompt:', taskReqBody.prompt)
|
console.log("Prompt:", taskReqBody.prompt)
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const key in TASK_TEXT_MAPPING) {
|
for (const key in TASK_TEXT_MAPPING) {
|
||||||
@ -469,18 +522,18 @@ function parseTaskFromText(str) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
const name = TASK_TEXT_MAPPING[key];
|
const name = TASK_TEXT_MAPPING[key]
|
||||||
let val = undefined
|
let val = undefined
|
||||||
|
|
||||||
const reName = new RegExp(`${name}\\ *:\\ *(.*)(?:\\r\\n|\\r|\\n)*`, 'igm')
|
const reName = new RegExp(`${name}\\ *:\\ *(.*)(?:\\r\\n|\\r|\\n)*`, "igm")
|
||||||
const match = reName.exec(str);
|
const match = reName.exec(str)
|
||||||
if (match) {
|
if (match) {
|
||||||
str = str.slice(0, match.index) + str.slice(match.index + match[0].length)
|
str = str.slice(0, match.index) + str.slice(match.index + match[0].length)
|
||||||
val = match[1]
|
val = match[1]
|
||||||
}
|
}
|
||||||
if (val !== undefined) {
|
if (val !== undefined) {
|
||||||
taskReqBody[key] = TASK_MAPPING[key].parse(val.trim())
|
taskReqBody[key] = TASK_MAPPING[key].parse(val.trim())
|
||||||
console.log(TASK_MAPPING[key].name + ':', taskReqBody[key])
|
console.log(TASK_MAPPING[key].name + ":", taskReqBody[key])
|
||||||
if (!str) {
|
if (!str) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -490,18 +543,19 @@ function parseTaskFromText(str) {
|
|||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
const task = { reqBody: taskReqBody }
|
const task = { reqBody: taskReqBody }
|
||||||
if ('seed' in taskReqBody) {
|
if ("seed" in taskReqBody) {
|
||||||
task.seed = taskReqBody.seed
|
task.seed = taskReqBody.seed
|
||||||
}
|
}
|
||||||
return task
|
return task
|
||||||
}
|
}
|
||||||
|
|
||||||
async function parseContent(text) {
|
async function parseContent(text) {
|
||||||
text = text.trim();
|
text = text.trim()
|
||||||
if (text.startsWith('{') && text.endsWith('}')) {
|
if (text.startsWith("{") && text.endsWith("}")) {
|
||||||
try {
|
try {
|
||||||
const task = JSON.parse(text)
|
const task = JSON.parse(text)
|
||||||
if (!('reqBody' in task)) { // support the format saved to the disk, by the UI
|
if (!("reqBody" in task)) {
|
||||||
|
// support the format saved to the disk, by the UI
|
||||||
task.reqBody = Object.assign({}, task)
|
task.reqBody = Object.assign({}, task)
|
||||||
}
|
}
|
||||||
restoreTaskToUI(task)
|
restoreTaskToUI(task)
|
||||||
@ -513,7 +567,8 @@ async function parseContent(text) {
|
|||||||
}
|
}
|
||||||
// Normal txt file.
|
// Normal txt file.
|
||||||
const task = parseTaskFromText(text)
|
const task = parseTaskFromText(text)
|
||||||
if (text.toLowerCase().includes('seed:') && task) { // only parse valid task content
|
if (text.toLowerCase().includes("seed:") && task) {
|
||||||
|
// only parse valid task content
|
||||||
restoreTaskToUI(task)
|
restoreTaskToUI(task)
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
@ -530,21 +585,25 @@ async function readFile(file, i) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function dropHandler(ev) {
|
function dropHandler(ev) {
|
||||||
console.log('Content dropped...')
|
console.log("Content dropped...")
|
||||||
let items = []
|
let items = []
|
||||||
|
|
||||||
if (ev?.dataTransfer?.items) { // Use DataTransferItemList interface
|
if (ev?.dataTransfer?.items) {
|
||||||
|
// Use DataTransferItemList interface
|
||||||
items = Array.from(ev.dataTransfer.items)
|
items = Array.from(ev.dataTransfer.items)
|
||||||
items = items.filter(item => item.kind === 'file')
|
items = items.filter((item) => item.kind === "file")
|
||||||
items = items.map(item => item.getAsFile())
|
items = items.map((item) => item.getAsFile())
|
||||||
} else if (ev?.dataTransfer?.files) { // Use DataTransfer interface
|
} else if (ev?.dataTransfer?.files) {
|
||||||
|
// Use DataTransfer interface
|
||||||
items = Array.from(ev.dataTransfer.files)
|
items = Array.from(ev.dataTransfer.files)
|
||||||
}
|
}
|
||||||
|
|
||||||
items.forEach(item => {item.file_ext = EXT_REGEX.exec(item.name.toLowerCase())[1]})
|
items.forEach((item) => {
|
||||||
|
item.file_ext = EXT_REGEX.exec(item.name.toLowerCase())[1]
|
||||||
|
})
|
||||||
|
|
||||||
let text_items = items.filter(item => TEXT_EXTENSIONS.includes(item.file_ext))
|
let text_items = items.filter((item) => TEXT_EXTENSIONS.includes(item.file_ext))
|
||||||
let image_items = items.filter(item => IMAGE_EXTENSIONS.includes(item.file_ext))
|
let image_items = items.filter((item) => IMAGE_EXTENSIONS.includes(item.file_ext))
|
||||||
|
|
||||||
if (image_items.length > 0 && ev.target == initImageSelector) {
|
if (image_items.length > 0 && ev.target == initImageSelector) {
|
||||||
return // let the event bubble up, so that the Init Image filepicker can receive this
|
return // let the event bubble up, so that the Init Image filepicker can receive this
|
||||||
@ -554,7 +613,7 @@ function dropHandler(ev) {
|
|||||||
text_items.forEach(readFile)
|
text_items.forEach(readFile)
|
||||||
}
|
}
|
||||||
function dragOverHandler(ev) {
|
function dragOverHandler(ev) {
|
||||||
console.log('Content in drop zone')
|
console.log("Content in drop zone")
|
||||||
|
|
||||||
// Prevent default behavior (Prevent file/content from being opened)
|
// Prevent default behavior (Prevent file/content from being opened)
|
||||||
ev.preventDefault()
|
ev.preventDefault()
|
||||||
@ -562,73 +621,72 @@ function dragOverHandler(ev) {
|
|||||||
ev.dataTransfer.dropEffect = "copy"
|
ev.dataTransfer.dropEffect = "copy"
|
||||||
|
|
||||||
let img = new Image()
|
let img = new Image()
|
||||||
img.src = '//' + location.host + '/media/images/favicon-32x32.png'
|
img.src = "//" + location.host + "/media/images/favicon-32x32.png"
|
||||||
ev.dataTransfer.setDragImage(img, 16, 16)
|
ev.dataTransfer.setDragImage(img, 16, 16)
|
||||||
}
|
}
|
||||||
|
|
||||||
document.addEventListener("drop", dropHandler)
|
document.addEventListener("drop", dropHandler)
|
||||||
document.addEventListener("dragover", dragOverHandler)
|
document.addEventListener("dragover", dragOverHandler)
|
||||||
|
|
||||||
const TASK_REQ_NO_EXPORT = [
|
const TASK_REQ_NO_EXPORT = ["use_cpu", "save_to_disk_path"]
|
||||||
"use_cpu",
|
const resetSettings = document.getElementById("reset-image-settings")
|
||||||
"save_to_disk_path"
|
|
||||||
]
|
|
||||||
const resetSettings = document.getElementById('reset-image-settings')
|
|
||||||
|
|
||||||
function checkReadTextClipboardPermission (result) {
|
function checkReadTextClipboardPermission(result) {
|
||||||
if (result.state != "granted" && result.state != "prompt") {
|
if (result.state != "granted" && result.state != "prompt") {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// PASTE ICON
|
// PASTE ICON
|
||||||
const pasteIcon = document.createElement('i')
|
const pasteIcon = document.createElement("i")
|
||||||
pasteIcon.className = 'fa-solid fa-paste section-button'
|
pasteIcon.className = "fa-solid fa-paste section-button"
|
||||||
pasteIcon.innerHTML = `<span class="simple-tooltip top-left">Paste Image Settings</span>`
|
pasteIcon.innerHTML = `<span class="simple-tooltip top-left">Paste Image Settings</span>`
|
||||||
pasteIcon.addEventListener('click', async (event) => {
|
pasteIcon.addEventListener("click", async (event) => {
|
||||||
event.stopPropagation()
|
event.stopPropagation()
|
||||||
// Add css class 'active'
|
// Add css class 'active'
|
||||||
pasteIcon.classList.add('active')
|
pasteIcon.classList.add("active")
|
||||||
// In 350 ms remove the 'active' class
|
// In 350 ms remove the 'active' class
|
||||||
asyncDelay(350).then(() => pasteIcon.classList.remove('active'))
|
asyncDelay(350).then(() => pasteIcon.classList.remove("active"))
|
||||||
|
|
||||||
// Retrieve clipboard content and try to parse it
|
// Retrieve clipboard content and try to parse it
|
||||||
const text = await navigator.clipboard.readText();
|
const text = await navigator.clipboard.readText()
|
||||||
await parseContent(text)
|
await parseContent(text)
|
||||||
})
|
})
|
||||||
resetSettings.parentNode.insertBefore(pasteIcon, resetSettings)
|
resetSettings.parentNode.insertBefore(pasteIcon, resetSettings)
|
||||||
}
|
}
|
||||||
navigator.permissions.query({ name: "clipboard-read" }).then(checkReadTextClipboardPermission, (reason) => console.log('clipboard-read is not available. %o', reason))
|
navigator.permissions
|
||||||
|
.query({ name: "clipboard-read" })
|
||||||
|
.then(checkReadTextClipboardPermission, (reason) => console.log("clipboard-read is not available. %o", reason))
|
||||||
|
|
||||||
document.addEventListener('paste', async (event) => {
|
document.addEventListener("paste", async (event) => {
|
||||||
if (event.target) {
|
if (event.target) {
|
||||||
const targetTag = event.target.tagName.toLowerCase()
|
const targetTag = event.target.tagName.toLowerCase()
|
||||||
// Disable when targeting input elements.
|
// Disable when targeting input elements.
|
||||||
if (targetTag === 'input' || targetTag === 'textarea') {
|
if (targetTag === "input" || targetTag === "textarea") {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const paste = (event.clipboardData || window.clipboardData).getData('text')
|
const paste = (event.clipboardData || window.clipboardData).getData("text")
|
||||||
const selection = window.getSelection()
|
const selection = window.getSelection()
|
||||||
if (paste != "" && selection.toString().trim().length <= 0 && await parseContent(paste)) {
|
if (paste != "" && selection.toString().trim().length <= 0 && (await parseContent(paste))) {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// Adds a copy and a paste icon if the browser grants permission to write to clipboard.
|
// Adds a copy and a paste icon if the browser grants permission to write to clipboard.
|
||||||
function checkWriteToClipboardPermission (result) {
|
function checkWriteToClipboardPermission(result) {
|
||||||
if (result.state != "granted" && result.state != "prompt") {
|
if (result.state != "granted" && result.state != "prompt") {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// COPY ICON
|
// COPY ICON
|
||||||
const copyIcon = document.createElement('i')
|
const copyIcon = document.createElement("i")
|
||||||
copyIcon.className = 'fa-solid fa-clipboard section-button'
|
copyIcon.className = "fa-solid fa-clipboard section-button"
|
||||||
copyIcon.innerHTML = `<span class="simple-tooltip top-left">Copy Image Settings</span>`
|
copyIcon.innerHTML = `<span class="simple-tooltip top-left">Copy Image Settings</span>`
|
||||||
copyIcon.addEventListener('click', (event) => {
|
copyIcon.addEventListener("click", (event) => {
|
||||||
event.stopPropagation()
|
event.stopPropagation()
|
||||||
// Add css class 'active'
|
// Add css class 'active'
|
||||||
copyIcon.classList.add('active')
|
copyIcon.classList.add("active")
|
||||||
// In 350 ms remove the 'active' class
|
// In 350 ms remove the 'active' class
|
||||||
asyncDelay(350).then(() => copyIcon.classList.remove('active'))
|
asyncDelay(350).then(() => copyIcon.classList.remove("active"))
|
||||||
const uiState = readUI()
|
const uiState = readUI()
|
||||||
TASK_REQ_NO_EXPORT.forEach((key) => delete uiState.reqBody[key])
|
TASK_REQ_NO_EXPORT.forEach((key) => delete uiState.reqBody[key])
|
||||||
if (uiState.reqBody.init_image && !IMAGE_REGEX.test(uiState.reqBody.init_image)) {
|
if (uiState.reqBody.init_image && !IMAGE_REGEX.test(uiState.reqBody.init_image)) {
|
||||||
@ -641,8 +699,8 @@ function checkWriteToClipboardPermission (result) {
|
|||||||
}
|
}
|
||||||
// Determine which access we have to the clipboard. Clipboard access is only available on localhost or via TLS.
|
// Determine which access we have to the clipboard. Clipboard access is only available on localhost or via TLS.
|
||||||
navigator.permissions.query({ name: "clipboard-write" }).then(checkWriteToClipboardPermission, (e) => {
|
navigator.permissions.query({ name: "clipboard-write" }).then(checkWriteToClipboardPermission, (e) => {
|
||||||
if (e instanceof TypeError && typeof navigator?.clipboard?.writeText === 'function') {
|
if (e instanceof TypeError && typeof navigator?.clipboard?.writeText === "function") {
|
||||||
// Fix for firefox https://bugzilla.mozilla.org/show_bug.cgi?id=1560373
|
// Fix for firefox https://bugzilla.mozilla.org/show_bug.cgi?id=1560373
|
||||||
checkWriteToClipboardPermission({state:"granted"})
|
checkWriteToClipboardPermission({ state: "granted" })
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -6,17 +6,17 @@ const IMAGE_EDITOR_BUTTONS = [
|
|||||||
{
|
{
|
||||||
name: "Cancel",
|
name: "Cancel",
|
||||||
icon: "fa-regular fa-circle-xmark",
|
icon: "fa-regular fa-circle-xmark",
|
||||||
handler: editor => {
|
handler: (editor) => {
|
||||||
editor.hide()
|
editor.hide()
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Save",
|
name: "Save",
|
||||||
icon: "fa-solid fa-floppy-disk",
|
icon: "fa-solid fa-floppy-disk",
|
||||||
handler: editor => {
|
handler: (editor) => {
|
||||||
editor.saveImage()
|
editor.saveImage()
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
const defaultToolBegin = (editor, ctx, x, y, is_overlay = false) => {
|
const defaultToolBegin = (editor, ctx, x, y, is_overlay = false) => {
|
||||||
@ -46,7 +46,7 @@ const IMAGE_EDITOR_TOOLS = [
|
|||||||
cursor: "url(/media/images/fa-pencil.svg) 0 24, pointer",
|
cursor: "url(/media/images/fa-pencil.svg) 0 24, pointer",
|
||||||
begin: defaultToolBegin,
|
begin: defaultToolBegin,
|
||||||
move: defaultToolMove,
|
move: defaultToolMove,
|
||||||
end: defaultToolEnd
|
end: defaultToolEnd,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "erase",
|
id: "erase",
|
||||||
@ -76,7 +76,7 @@ const IMAGE_EDITOR_TOOLS = [
|
|||||||
},
|
},
|
||||||
setBrush: (editor, layer) => {
|
setBrush: (editor, layer) => {
|
||||||
layer.ctx.globalCompositeOperation = "destination-out"
|
layer.ctx.globalCompositeOperation = "destination-out"
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "fill",
|
id: "fill",
|
||||||
@ -91,7 +91,7 @@ const IMAGE_EDITOR_TOOLS = [
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
move: toolDoNothing,
|
move: toolDoNothing,
|
||||||
end: toolDoNothing
|
end: toolDoNothing,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "colorpicker",
|
id: "colorpicker",
|
||||||
@ -104,16 +104,16 @@ const IMAGE_EDITOR_TOOLS = [
|
|||||||
var drawn_rgb = editor.ctx_current.getImageData(x, y, 1, 1).data
|
var drawn_rgb = editor.ctx_current.getImageData(x, y, 1, 1).data
|
||||||
var drawn_opacity = drawn_rgb[3] / 255
|
var drawn_opacity = drawn_rgb[3] / 255
|
||||||
editor.custom_color_input.value = rgbToHex({
|
editor.custom_color_input.value = rgbToHex({
|
||||||
r: (drawn_rgb[0] * drawn_opacity) + (img_rgb[0] * (1 - drawn_opacity)),
|
r: drawn_rgb[0] * drawn_opacity + img_rgb[0] * (1 - drawn_opacity),
|
||||||
g: (drawn_rgb[1] * drawn_opacity) + (img_rgb[1] * (1 - drawn_opacity)),
|
g: drawn_rgb[1] * drawn_opacity + img_rgb[1] * (1 - drawn_opacity),
|
||||||
b: (drawn_rgb[2] * drawn_opacity) + (img_rgb[2] * (1 - drawn_opacity)),
|
b: drawn_rgb[2] * drawn_opacity + img_rgb[2] * (1 - drawn_opacity),
|
||||||
})
|
})
|
||||||
editor.custom_color_input.dispatchEvent(new Event("change"))
|
editor.custom_color_input.dispatchEvent(new Event("change"))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
move: toolDoNothing,
|
move: toolDoNothing,
|
||||||
end: toolDoNothing
|
end: toolDoNothing,
|
||||||
}
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
const IMAGE_EDITOR_ACTIONS = [
|
const IMAGE_EDITOR_ACTIONS = [
|
||||||
@ -123,7 +123,7 @@ const IMAGE_EDITOR_ACTIONS = [
|
|||||||
className: "load_mask",
|
className: "load_mask",
|
||||||
icon: "fa-regular fa-folder-open",
|
icon: "fa-regular fa-folder-open",
|
||||||
handler: (editor) => {
|
handler: (editor) => {
|
||||||
let el = document.createElement('input')
|
let el = document.createElement("input")
|
||||||
el.setAttribute("type", "file")
|
el.setAttribute("type", "file")
|
||||||
el.addEventListener("change", function() {
|
el.addEventListener("change", function() {
|
||||||
if (this.files.length === 0) {
|
if (this.files.length === 0) {
|
||||||
@ -133,7 +133,7 @@ const IMAGE_EDITOR_ACTIONS = [
|
|||||||
let reader = new FileReader()
|
let reader = new FileReader()
|
||||||
let file = this.files[0]
|
let file = this.files[0]
|
||||||
|
|
||||||
reader.addEventListener('load', function(event) {
|
reader.addEventListener("load", function(event) {
|
||||||
let maskData = reader.result
|
let maskData = reader.result
|
||||||
|
|
||||||
editor.layers.drawing.ctx.clearRect(0, 0, editor.width, editor.height)
|
editor.layers.drawing.ctx.clearRect(0, 0, editor.width, editor.height)
|
||||||
@ -151,7 +151,7 @@ const IMAGE_EDITOR_ACTIONS = [
|
|||||||
|
|
||||||
el.click()
|
el.click()
|
||||||
},
|
},
|
||||||
trackHistory: true
|
trackHistory: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "fill_all",
|
id: "fill_all",
|
||||||
@ -163,7 +163,7 @@ const IMAGE_EDITOR_ACTIONS = [
|
|||||||
editor.ctx_current.fill()
|
editor.ctx_current.fill()
|
||||||
editor.setBrush()
|
editor.setBrush()
|
||||||
},
|
},
|
||||||
trackHistory: true
|
trackHistory: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "clear",
|
id: "clear",
|
||||||
@ -173,7 +173,7 @@ const IMAGE_EDITOR_ACTIONS = [
|
|||||||
editor.ctx_current.clearRect(0, 0, editor.width, editor.height)
|
editor.ctx_current.clearRect(0, 0, editor.width, editor.height)
|
||||||
imageEditor.setImage(null, editor.width, editor.height) // properly reset the drawing canvas
|
imageEditor.setImage(null, editor.width, editor.height) // properly reset the drawing canvas
|
||||||
},
|
},
|
||||||
trackHistory: true
|
trackHistory: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "undo",
|
id: "undo",
|
||||||
@ -182,7 +182,7 @@ const IMAGE_EDITOR_ACTIONS = [
|
|||||||
handler: (editor) => {
|
handler: (editor) => {
|
||||||
editor.history.undo()
|
editor.history.undo()
|
||||||
},
|
},
|
||||||
trackHistory: false
|
trackHistory: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "redo",
|
id: "redo",
|
||||||
@ -191,8 +191,8 @@ const IMAGE_EDITOR_ACTIONS = [
|
|||||||
handler: (editor) => {
|
handler: (editor) => {
|
||||||
editor.history.redo()
|
editor.history.redo()
|
||||||
},
|
},
|
||||||
trackHistory: false
|
trackHistory: false,
|
||||||
}
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
var IMAGE_EDITOR_SECTIONS = [
|
var IMAGE_EDITOR_SECTIONS = [
|
||||||
@ -200,17 +200,17 @@ var IMAGE_EDITOR_SECTIONS = [
|
|||||||
name: "tool",
|
name: "tool",
|
||||||
title: "Tool",
|
title: "Tool",
|
||||||
default: "draw",
|
default: "draw",
|
||||||
options: Array.from(IMAGE_EDITOR_TOOLS.map(t => t.id)),
|
options: Array.from(IMAGE_EDITOR_TOOLS.map((t) => t.id)),
|
||||||
initElement: (element, option) => {
|
initElement: (element, option) => {
|
||||||
var tool_info = IMAGE_EDITOR_TOOLS.find(t => t.id == option)
|
var tool_info = IMAGE_EDITOR_TOOLS.find((t) => t.id == option)
|
||||||
element.className = "image-editor-button button"
|
element.className = "image-editor-button button"
|
||||||
var sub_element = document.createElement("div")
|
var sub_element = document.createElement("div")
|
||||||
var icon = document.createElement("i")
|
var icon = document.createElement("i")
|
||||||
tool_info.icon.split(" ").forEach(c => icon.classList.add(c))
|
tool_info.icon.split(" ").forEach((c) => icon.classList.add(c))
|
||||||
sub_element.appendChild(icon)
|
sub_element.appendChild(icon)
|
||||||
sub_element.append(tool_info.name)
|
sub_element.append(tool_info.name)
|
||||||
element.appendChild(sub_element)
|
element.appendChild(sub_element)
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "color",
|
name: "color",
|
||||||
@ -218,14 +218,46 @@ var IMAGE_EDITOR_SECTIONS = [
|
|||||||
default: "#f1c232",
|
default: "#f1c232",
|
||||||
options: [
|
options: [
|
||||||
"custom",
|
"custom",
|
||||||
"#ea9999", "#e06666", "#cc0000", "#990000", "#660000",
|
"#ea9999",
|
||||||
"#f9cb9c", "#f6b26b", "#e69138", "#b45f06", "#783f04",
|
"#e06666",
|
||||||
"#ffe599", "#ffd966", "#f1c232", "#bf9000", "#7f6000",
|
"#cc0000",
|
||||||
"#b6d7a8", "#93c47d", "#6aa84f", "#38761d", "#274e13",
|
"#990000",
|
||||||
"#a4c2f4", "#6d9eeb", "#3c78d8", "#1155cc", "#1c4587",
|
"#660000",
|
||||||
"#b4a7d6", "#8e7cc3", "#674ea7", "#351c75", "#20124d",
|
"#f9cb9c",
|
||||||
"#d5a6bd", "#c27ba0", "#a64d79", "#741b47", "#4c1130",
|
"#f6b26b",
|
||||||
"#ffffff", "#c0c0c0", "#838383", "#525252", "#000000",
|
"#e69138",
|
||||||
|
"#b45f06",
|
||||||
|
"#783f04",
|
||||||
|
"#ffe599",
|
||||||
|
"#ffd966",
|
||||||
|
"#f1c232",
|
||||||
|
"#bf9000",
|
||||||
|
"#7f6000",
|
||||||
|
"#b6d7a8",
|
||||||
|
"#93c47d",
|
||||||
|
"#6aa84f",
|
||||||
|
"#38761d",
|
||||||
|
"#274e13",
|
||||||
|
"#a4c2f4",
|
||||||
|
"#6d9eeb",
|
||||||
|
"#3c78d8",
|
||||||
|
"#1155cc",
|
||||||
|
"#1c4587",
|
||||||
|
"#b4a7d6",
|
||||||
|
"#8e7cc3",
|
||||||
|
"#674ea7",
|
||||||
|
"#351c75",
|
||||||
|
"#20124d",
|
||||||
|
"#d5a6bd",
|
||||||
|
"#c27ba0",
|
||||||
|
"#a64d79",
|
||||||
|
"#741b47",
|
||||||
|
"#4c1130",
|
||||||
|
"#ffffff",
|
||||||
|
"#c0c0c0",
|
||||||
|
"#838383",
|
||||||
|
"#525252",
|
||||||
|
"#000000",
|
||||||
],
|
],
|
||||||
initElement: (element, option) => {
|
initElement: (element, option) => {
|
||||||
if (option == "custom") {
|
if (option == "custom") {
|
||||||
@ -238,43 +270,42 @@ var IMAGE_EDITOR_SECTIONS = [
|
|||||||
input.click()
|
input.click()
|
||||||
}
|
}
|
||||||
element.appendChild(span)
|
element.appendChild(span)
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
element.style.background = option
|
element.style.background = option
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getCustom: editor => {
|
getCustom: (editor) => {
|
||||||
var input = editor.popup.querySelector(".image_editor_color input")
|
var input = editor.popup.querySelector(".image_editor_color input")
|
||||||
return input.value
|
return input.value
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "brush_size",
|
name: "brush_size",
|
||||||
title: "Brush Size",
|
title: "Brush Size",
|
||||||
default: 48,
|
default: 48,
|
||||||
options: [ 6, 12, 16, 24, 30, 40, 48, 64 ],
|
options: [6, 12, 16, 24, 30, 40, 48, 64],
|
||||||
initElement: (element, option) => {
|
initElement: (element, option) => {
|
||||||
element.parentElement.style.flex = option
|
element.parentElement.style.flex = option
|
||||||
element.style.width = option + "px"
|
element.style.width = option + "px"
|
||||||
element.style.height = option + "px"
|
element.style.height = option + "px"
|
||||||
element.style['margin-right'] = '2px'
|
element.style["margin-right"] = "2px"
|
||||||
element.style["border-radius"] = (option / 2).toFixed() + "px"
|
element.style["border-radius"] = (option / 2).toFixed() + "px"
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "opacity",
|
name: "opacity",
|
||||||
title: "Opacity",
|
title: "Opacity",
|
||||||
default: 0,
|
default: 0,
|
||||||
options: [ 0, 0.2, 0.4, 0.6, 0.8 ],
|
options: [0, 0.2, 0.4, 0.6, 0.8],
|
||||||
initElement: (element, option) => {
|
initElement: (element, option) => {
|
||||||
element.style.background = `repeating-conic-gradient(rgba(0, 0, 0, ${option}) 0% 25%, rgba(255, 255, 255, ${option}) 0% 50%) 50% / 10px 10px`
|
element.style.background = `repeating-conic-gradient(rgba(0, 0, 0, ${option}) 0% 25%, rgba(255, 255, 255, ${option}) 0% 50%) 50% / 10px 10px`
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "sharpness",
|
name: "sharpness",
|
||||||
title: "Sharpness",
|
title: "Sharpness",
|
||||||
default: 0,
|
default: 0,
|
||||||
options: [ 0, 0.05, 0.1, 0.2, 0.3 ],
|
options: [0, 0.05, 0.1, 0.2, 0.3],
|
||||||
initElement: (element, option) => {
|
initElement: (element, option) => {
|
||||||
var size = 32
|
var size = 32
|
||||||
var blur_amount = parseInt(option * size)
|
var blur_amount = parseInt(option * size)
|
||||||
@ -283,11 +314,11 @@ var IMAGE_EDITOR_SECTIONS = [
|
|||||||
sub_element.style.filter = `blur(${blur_amount}px)`
|
sub_element.style.filter = `blur(${blur_amount}px)`
|
||||||
sub_element.style.width = `${size - 2}px`
|
sub_element.style.width = `${size - 2}px`
|
||||||
sub_element.style.height = `${size - 2}px`
|
sub_element.style.height = `${size - 2}px`
|
||||||
sub_element.style['border-radius'] = `${size}px`
|
sub_element.style["border-radius"] = `${size}px`
|
||||||
element.style.background = "none"
|
element.style.background = "none"
|
||||||
element.appendChild(sub_element)
|
element.appendChild(sub_element)
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
class EditorHistory {
|
class EditorHistory {
|
||||||
@ -312,15 +343,15 @@ class EditorHistory {
|
|||||||
pushAction(action) {
|
pushAction(action) {
|
||||||
this.push({
|
this.push({
|
||||||
type: "action",
|
type: "action",
|
||||||
id: action
|
id: action,
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
editBegin(x, y) {
|
editBegin(x, y) {
|
||||||
this.current_edit = {
|
this.current_edit = {
|
||||||
type: "edit",
|
type: "edit",
|
||||||
id: this.editor.getOptionValue("tool"),
|
id: this.editor.getOptionValue("tool"),
|
||||||
options: Object.assign({}, this.editor.options),
|
options: Object.assign({}, this.editor.options),
|
||||||
points: [ { x: x, y: y } ]
|
points: [{ x: x, y: y }],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
editMove(x, y) {
|
editMove(x, y) {
|
||||||
@ -345,7 +376,7 @@ class EditorHistory {
|
|||||||
}
|
}
|
||||||
rewindTo(new_rewind_index) {
|
rewindTo(new_rewind_index) {
|
||||||
if (new_rewind_index < 0 || new_rewind_index > this.events.length) {
|
if (new_rewind_index < 0 || new_rewind_index > this.events.length) {
|
||||||
return; // do nothing if target index is out of bounds
|
return // do nothing if target index is out of bounds
|
||||||
}
|
}
|
||||||
|
|
||||||
var ctx = this.editor.layers.drawing.ctx
|
var ctx = this.editor.layers.drawing.ctx
|
||||||
@ -361,17 +392,16 @@ class EditorHistory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (snapshot_index != -1) {
|
if (snapshot_index != -1) {
|
||||||
ctx.putImageData(this.events[snapshot_index].snapshot, 0, 0);
|
ctx.putImageData(this.events[snapshot_index].snapshot, 0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var i = (snapshot_index + 1); i <= target_index; i++) {
|
for (var i = snapshot_index + 1; i <= target_index; i++) {
|
||||||
var event = this.events[i]
|
var event = this.events[i]
|
||||||
if (event.type == "action") {
|
if (event.type == "action") {
|
||||||
var action = IMAGE_EDITOR_ACTIONS.find(a => a.id == event.id)
|
var action = IMAGE_EDITOR_ACTIONS.find((a) => a.id == event.id)
|
||||||
action.handler(this.editor)
|
action.handler(this.editor)
|
||||||
}
|
} else if (event.type == "edit") {
|
||||||
else if (event.type == "edit") {
|
var tool = IMAGE_EDITOR_TOOLS.find((t) => t.id == event.id)
|
||||||
var tool = IMAGE_EDITOR_TOOLS.find(t => t.id == event.id)
|
|
||||||
this.editor.setBrush(this.editor.layers.drawing, event.options)
|
this.editor.setBrush(this.editor.layers.drawing, event.options)
|
||||||
|
|
||||||
var first_point = event.points[0]
|
var first_point = event.points[0]
|
||||||
@ -403,19 +433,15 @@ class ImageEditor {
|
|||||||
this.temp_previous_tool = null // used for the ctrl-colorpicker functionality
|
this.temp_previous_tool = null // used for the ctrl-colorpicker functionality
|
||||||
this.container = popup.querySelector(".editor-controls-center > div")
|
this.container = popup.querySelector(".editor-controls-center > div")
|
||||||
this.layers = {}
|
this.layers = {}
|
||||||
var layer_names = [
|
var layer_names = ["background", "drawing", "overlay"]
|
||||||
"background",
|
layer_names.forEach((name) => {
|
||||||
"drawing",
|
|
||||||
"overlay"
|
|
||||||
]
|
|
||||||
layer_names.forEach(name => {
|
|
||||||
let canvas = document.createElement("canvas")
|
let canvas = document.createElement("canvas")
|
||||||
canvas.className = `editor-canvas-${name}`
|
canvas.className = `editor-canvas-${name}`
|
||||||
this.container.appendChild(canvas)
|
this.container.appendChild(canvas)
|
||||||
this.layers[name] = {
|
this.layers[name] = {
|
||||||
name: name,
|
name: name,
|
||||||
canvas: canvas,
|
canvas: canvas,
|
||||||
ctx: canvas.getContext("2d")
|
ctx: canvas.getContext("2d"),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -434,7 +460,7 @@ class ImageEditor {
|
|||||||
// initialize editor controls
|
// initialize editor controls
|
||||||
this.options = {}
|
this.options = {}
|
||||||
this.optionElements = {}
|
this.optionElements = {}
|
||||||
IMAGE_EDITOR_SECTIONS.forEach(section => {
|
IMAGE_EDITOR_SECTIONS.forEach((section) => {
|
||||||
section.id = `image_editor_${section.name}`
|
section.id = `image_editor_${section.name}`
|
||||||
var sectionElement = document.createElement("div")
|
var sectionElement = document.createElement("div")
|
||||||
sectionElement.className = section.id
|
sectionElement.className = section.id
|
||||||
@ -452,7 +478,7 @@ class ImageEditor {
|
|||||||
var optionElement = document.createElement("div")
|
var optionElement = document.createElement("div")
|
||||||
optionHolder.appendChild(optionElement)
|
optionHolder.appendChild(optionElement)
|
||||||
section.initElement(optionElement, option)
|
section.initElement(optionElement, option)
|
||||||
optionElement.addEventListener("click", target => this.selectOption(section.name, index))
|
optionElement.addEventListener("click", (target) => this.selectOption(section.name, index))
|
||||||
optionsContainer.appendChild(optionHolder)
|
optionsContainer.appendChild(optionHolder)
|
||||||
this.optionElements[section.name].push(optionElement)
|
this.optionElements[section.name].push(optionElement)
|
||||||
})
|
})
|
||||||
@ -470,13 +496,13 @@ class ImageEditor {
|
|||||||
})
|
})
|
||||||
|
|
||||||
if (this.inpainter) {
|
if (this.inpainter) {
|
||||||
this.selectOption("color", IMAGE_EDITOR_SECTIONS.find(s => s.name == "color").options.indexOf("#ffffff"))
|
this.selectOption("color", IMAGE_EDITOR_SECTIONS.find((s) => s.name == "color").options.indexOf("#ffffff"))
|
||||||
this.selectOption("opacity", IMAGE_EDITOR_SECTIONS.find(s => s.name == "opacity").options.indexOf(0.4))
|
this.selectOption("opacity", IMAGE_EDITOR_SECTIONS.find((s) => s.name == "opacity").options.indexOf(0.4))
|
||||||
}
|
}
|
||||||
|
|
||||||
// initialize the right-side controls
|
// initialize the right-side controls
|
||||||
var buttonContainer = document.createElement("div")
|
var buttonContainer = document.createElement("div")
|
||||||
IMAGE_EDITOR_BUTTONS.forEach(button => {
|
IMAGE_EDITOR_BUTTONS.forEach((button) => {
|
||||||
var element = document.createElement("div")
|
var element = document.createElement("div")
|
||||||
var icon = document.createElement("i")
|
var icon = document.createElement("i")
|
||||||
element.className = "image-editor-button button"
|
element.className = "image-editor-button button"
|
||||||
@ -484,13 +510,13 @@ class ImageEditor {
|
|||||||
element.appendChild(icon)
|
element.appendChild(icon)
|
||||||
element.append(button.name)
|
element.append(button.name)
|
||||||
buttonContainer.appendChild(element)
|
buttonContainer.appendChild(element)
|
||||||
element.addEventListener("click", event => button.handler(this))
|
element.addEventListener("click", (event) => button.handler(this))
|
||||||
})
|
})
|
||||||
var actionsContainer = document.createElement("div")
|
var actionsContainer = document.createElement("div")
|
||||||
var actionsTitle = document.createElement("h4")
|
var actionsTitle = document.createElement("h4")
|
||||||
actionsTitle.textContent = "Actions"
|
actionsTitle.textContent = "Actions"
|
||||||
actionsContainer.appendChild(actionsTitle);
|
actionsContainer.appendChild(actionsTitle)
|
||||||
IMAGE_EDITOR_ACTIONS.forEach(action => {
|
IMAGE_EDITOR_ACTIONS.forEach((action) => {
|
||||||
var element = document.createElement("div")
|
var element = document.createElement("div")
|
||||||
var icon = document.createElement("i")
|
var icon = document.createElement("i")
|
||||||
element.className = "image-editor-button button"
|
element.className = "image-editor-button button"
|
||||||
@ -501,7 +527,7 @@ class ImageEditor {
|
|||||||
element.appendChild(icon)
|
element.appendChild(icon)
|
||||||
element.append(action.name)
|
element.append(action.name)
|
||||||
actionsContainer.appendChild(element)
|
actionsContainer.appendChild(element)
|
||||||
element.addEventListener("click", event => this.runAction(action.id))
|
element.addEventListener("click", (event) => this.runAction(action.id))
|
||||||
})
|
})
|
||||||
this.popup.querySelector(".editor-controls-right").appendChild(actionsContainer)
|
this.popup.querySelector(".editor-controls-right").appendChild(actionsContainer)
|
||||||
this.popup.querySelector(".editor-controls-right").appendChild(buttonContainer)
|
this.popup.querySelector(".editor-controls-right").appendChild(buttonContainer)
|
||||||
@ -530,8 +556,7 @@ class ImageEditor {
|
|||||||
var multiplier = max_size / width
|
var multiplier = max_size / width
|
||||||
width = (multiplier * width).toFixed()
|
width = (multiplier * width).toFixed()
|
||||||
height = (multiplier * height).toFixed()
|
height = (multiplier * height).toFixed()
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
var max_size = Math.min(parseInt(window.innerHeight * 0.9), height, 768)
|
var max_size = Math.min(parseInt(window.innerHeight * 0.9), height, 768)
|
||||||
var multiplier = max_size / height
|
var multiplier = max_size / height
|
||||||
width = (multiplier * width).toFixed()
|
width = (multiplier * width).toFixed()
|
||||||
@ -543,7 +568,7 @@ class ImageEditor {
|
|||||||
this.container.style.width = width + "px"
|
this.container.style.width = width + "px"
|
||||||
this.container.style.height = height + "px"
|
this.container.style.height = height + "px"
|
||||||
|
|
||||||
Object.values(this.layers).forEach(layer => {
|
Object.values(this.layers).forEach((layer) => {
|
||||||
layer.canvas.width = width
|
layer.canvas.width = width
|
||||||
layer.canvas.height = height
|
layer.canvas.height = height
|
||||||
})
|
})
|
||||||
@ -556,11 +581,11 @@ class ImageEditor {
|
|||||||
}
|
}
|
||||||
get tool() {
|
get tool() {
|
||||||
var tool_id = this.getOptionValue("tool")
|
var tool_id = this.getOptionValue("tool")
|
||||||
return IMAGE_EDITOR_TOOLS.find(t => t.id == tool_id);
|
return IMAGE_EDITOR_TOOLS.find((t) => t.id == tool_id)
|
||||||
}
|
}
|
||||||
loadTool() {
|
loadTool() {
|
||||||
this.drawing = false
|
this.drawing = false
|
||||||
this.container.style.cursor = this.tool.cursor;
|
this.container.style.cursor = this.tool.cursor
|
||||||
}
|
}
|
||||||
setImage(url, width, height) {
|
setImage(url, width, height) {
|
||||||
this.setSize(width, height)
|
this.setSize(width, height)
|
||||||
@ -574,8 +599,7 @@ class ImageEditor {
|
|||||||
this.layers.background.ctx.drawImage(image, 0, 0, this.width, this.height)
|
this.layers.background.ctx.drawImage(image, 0, 0, this.width, this.height)
|
||||||
}
|
}
|
||||||
image.src = url
|
image.src = url
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
this.layers.background.ctx.fillStyle = "#ffffff"
|
this.layers.background.ctx.fillStyle = "#ffffff"
|
||||||
this.layers.background.ctx.beginPath()
|
this.layers.background.ctx.beginPath()
|
||||||
this.layers.background.ctx.rect(0, 0, this.width, this.height)
|
this.layers.background.ctx.rect(0, 0, this.width, this.height)
|
||||||
@ -589,23 +613,24 @@ class ImageEditor {
|
|||||||
this.layers.background.ctx.drawImage(this.layers.drawing.canvas, 0, 0, this.width, this.height)
|
this.layers.background.ctx.drawImage(this.layers.drawing.canvas, 0, 0, this.width, this.height)
|
||||||
var base64 = this.layers.background.canvas.toDataURL()
|
var base64 = this.layers.background.canvas.toDataURL()
|
||||||
initImagePreview.src = base64 // this will trigger the rest of the app to use it
|
initImagePreview.src = base64 // this will trigger the rest of the app to use it
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
// This is an inpainter, so make sure the toggle is set accordingly
|
// This is an inpainter, so make sure the toggle is set accordingly
|
||||||
var is_blank = !this.layers.drawing.ctx
|
var is_blank = !this.layers.drawing.ctx
|
||||||
.getImageData(0, 0, this.width, this.height).data
|
.getImageData(0, 0, this.width, this.height)
|
||||||
.some(channel => channel !== 0)
|
.data.some((channel) => channel !== 0)
|
||||||
maskSetting.checked = !is_blank
|
maskSetting.checked = !is_blank
|
||||||
}
|
}
|
||||||
this.hide()
|
this.hide()
|
||||||
}
|
}
|
||||||
getImg() { // a drop-in replacement of the drawingboard version
|
getImg() {
|
||||||
|
// a drop-in replacement of the drawingboard version
|
||||||
return this.layers.drawing.canvas.toDataURL()
|
return this.layers.drawing.canvas.toDataURL()
|
||||||
}
|
}
|
||||||
setImg(dataUrl) { // a drop-in replacement of the drawingboard version
|
setImg(dataUrl) {
|
||||||
|
// a drop-in replacement of the drawingboard version
|
||||||
var image = new Image()
|
var image = new Image()
|
||||||
image.onload = () => {
|
image.onload = () => {
|
||||||
var ctx = this.layers.drawing.ctx;
|
var ctx = this.layers.drawing.ctx
|
||||||
ctx.clearRect(0, 0, this.width, this.height)
|
ctx.clearRect(0, 0, this.width, this.height)
|
||||||
ctx.globalCompositeOperation = "source-over"
|
ctx.globalCompositeOperation = "source-over"
|
||||||
ctx.globalAlpha = 1
|
ctx.globalAlpha = 1
|
||||||
@ -616,7 +641,7 @@ class ImageEditor {
|
|||||||
image.src = dataUrl
|
image.src = dataUrl
|
||||||
}
|
}
|
||||||
runAction(action_id) {
|
runAction(action_id) {
|
||||||
var action = IMAGE_EDITOR_ACTIONS.find(a => a.id == action_id)
|
var action = IMAGE_EDITOR_ACTIONS.find((a) => a.id == action_id)
|
||||||
if (action.trackHistory) {
|
if (action.trackHistory) {
|
||||||
this.history.pushAction(action_id)
|
this.history.pushAction(action_id)
|
||||||
}
|
}
|
||||||
@ -634,15 +659,16 @@ class ImageEditor {
|
|||||||
layer.ctx.strokeStyle = options.color
|
layer.ctx.strokeStyle = options.color
|
||||||
var sharpness = parseInt(options.sharpness * options.brush_size)
|
var sharpness = parseInt(options.sharpness * options.brush_size)
|
||||||
layer.ctx.filter = sharpness == 0 ? `none` : `blur(${sharpness}px)`
|
layer.ctx.filter = sharpness == 0 ? `none` : `blur(${sharpness}px)`
|
||||||
layer.ctx.globalAlpha = (1 - options.opacity)
|
layer.ctx.globalAlpha = 1 - options.opacity
|
||||||
layer.ctx.globalCompositeOperation = "source-over"
|
layer.ctx.globalCompositeOperation = "source-over"
|
||||||
var tool = IMAGE_EDITOR_TOOLS.find(t => t.id == options.tool)
|
var tool = IMAGE_EDITOR_TOOLS.find((t) => t.id == options.tool)
|
||||||
if (tool && tool.setBrush) {
|
if (tool && tool.setBrush) {
|
||||||
tool.setBrush(editor, layer)
|
tool.setBrush(editor, layer)
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else {
|
Object.values(["drawing", "overlay"])
|
||||||
Object.values([ "drawing", "overlay" ]).map(name => this.layers[name]).forEach(l => {
|
.map((name) => this.layers[name])
|
||||||
|
.forEach((l) => {
|
||||||
this.setBrush(l)
|
this.setBrush(l)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -650,13 +676,15 @@ class ImageEditor {
|
|||||||
get ctx_overlay() {
|
get ctx_overlay() {
|
||||||
return this.layers.overlay.ctx
|
return this.layers.overlay.ctx
|
||||||
}
|
}
|
||||||
get ctx_current() { // the idea is this will help support having custom layers and editing each one
|
get ctx_current() {
|
||||||
|
// the idea is this will help support having custom layers and editing each one
|
||||||
return this.layers.drawing.ctx
|
return this.layers.drawing.ctx
|
||||||
}
|
}
|
||||||
get canvas_current() {
|
get canvas_current() {
|
||||||
return this.layers.drawing.canvas
|
return this.layers.drawing.canvas
|
||||||
}
|
}
|
||||||
keyHandler(event) { // handles keybinds like ctrl+z, ctrl+y
|
keyHandler(event) {
|
||||||
|
// handles keybinds like ctrl+z, ctrl+y
|
||||||
if (!this.popup.classList.contains("active")) {
|
if (!this.popup.classList.contains("active")) {
|
||||||
document.removeEventListener("keydown", this.keyHandlerBound)
|
document.removeEventListener("keydown", this.keyHandlerBound)
|
||||||
document.removeEventListener("keyup", this.keyHandlerBound)
|
document.removeEventListener("keyup", this.keyHandlerBound)
|
||||||
@ -668,46 +696,50 @@ class ImageEditor {
|
|||||||
if ((event.key == "z" || event.key == "Z") && event.ctrlKey) {
|
if ((event.key == "z" || event.key == "Z") && event.ctrlKey) {
|
||||||
if (!event.shiftKey) {
|
if (!event.shiftKey) {
|
||||||
this.history.undo()
|
this.history.undo()
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
this.history.redo()
|
this.history.redo()
|
||||||
}
|
}
|
||||||
event.stopPropagation();
|
event.stopPropagation()
|
||||||
event.preventDefault();
|
event.preventDefault()
|
||||||
}
|
}
|
||||||
if (event.key == "y" && event.ctrlKey) {
|
if (event.key == "y" && event.ctrlKey) {
|
||||||
this.history.redo()
|
this.history.redo()
|
||||||
event.stopPropagation();
|
event.stopPropagation()
|
||||||
event.preventDefault();
|
event.preventDefault()
|
||||||
}
|
}
|
||||||
if (event.key === "Escape") {
|
if (event.key === "Escape") {
|
||||||
this.hide()
|
this.hide()
|
||||||
event.stopPropagation();
|
event.stopPropagation()
|
||||||
event.preventDefault();
|
event.preventDefault()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// dropper ctrl holding handler stuff
|
// dropper ctrl holding handler stuff
|
||||||
var dropper_active = this.temp_previous_tool != null;
|
var dropper_active = this.temp_previous_tool != null
|
||||||
if (dropper_active && !event.ctrlKey) {
|
if (dropper_active && !event.ctrlKey) {
|
||||||
this.selectOption("tool", IMAGE_EDITOR_TOOLS.findIndex(t => t.id == this.temp_previous_tool))
|
this.selectOption(
|
||||||
|
"tool",
|
||||||
|
IMAGE_EDITOR_TOOLS.findIndex((t) => t.id == this.temp_previous_tool)
|
||||||
|
)
|
||||||
this.temp_previous_tool = null
|
this.temp_previous_tool = null
|
||||||
}
|
} else if (!dropper_active && event.ctrlKey) {
|
||||||
else if (!dropper_active && event.ctrlKey) {
|
|
||||||
this.temp_previous_tool = this.getOptionValue("tool")
|
this.temp_previous_tool = this.getOptionValue("tool")
|
||||||
this.selectOption("tool", IMAGE_EDITOR_TOOLS.findIndex(t => t.id == "colorpicker"))
|
this.selectOption(
|
||||||
|
"tool",
|
||||||
|
IMAGE_EDITOR_TOOLS.findIndex((t) => t.id == "colorpicker")
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
mouseHandler(event) {
|
mouseHandler(event) {
|
||||||
var bbox = this.layers.overlay.canvas.getBoundingClientRect()
|
var bbox = this.layers.overlay.canvas.getBoundingClientRect()
|
||||||
var x = (event.clientX || 0) - bbox.left
|
var x = (event.clientX || 0) - bbox.left
|
||||||
var y = (event.clientY || 0) - bbox.top
|
var y = (event.clientY || 0) - bbox.top
|
||||||
var type = event.type;
|
var type = event.type
|
||||||
var touchmap = {
|
var touchmap = {
|
||||||
touchstart: "mousedown",
|
touchstart: "mousedown",
|
||||||
touchmove: "mousemove",
|
touchmove: "mousemove",
|
||||||
touchend: "mouseup",
|
touchend: "mouseup",
|
||||||
touchcancel: "mouseup"
|
touchcancel: "mouseup",
|
||||||
}
|
}
|
||||||
if (type in touchmap) {
|
if (type in touchmap) {
|
||||||
type = touchmap[type]
|
type = touchmap[type]
|
||||||
@ -744,15 +776,15 @@ class ImageEditor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
getOptionValue(section_name) {
|
getOptionValue(section_name) {
|
||||||
var section = IMAGE_EDITOR_SECTIONS.find(s => s.name == section_name)
|
var section = IMAGE_EDITOR_SECTIONS.find((s) => s.name == section_name)
|
||||||
return this.options && section_name in this.options ? this.options[section_name] : section.default
|
return this.options && section_name in this.options ? this.options[section_name] : section.default
|
||||||
}
|
}
|
||||||
selectOption(section_name, option_index) {
|
selectOption(section_name, option_index) {
|
||||||
var section = IMAGE_EDITOR_SECTIONS.find(s => s.name == section_name)
|
var section = IMAGE_EDITOR_SECTIONS.find((s) => s.name == section_name)
|
||||||
var value = section.options[option_index]
|
var value = section.options[option_index]
|
||||||
this.options[section_name] = value == "custom" ? section.getCustom(this) : value
|
this.options[section_name] = value == "custom" ? section.getCustom(this) : value
|
||||||
|
|
||||||
this.optionElements[section_name].forEach(element => element.classList.remove("active"))
|
this.optionElements[section_name].forEach((element) => element.classList.remove("active"))
|
||||||
this.optionElements[section_name][option_index].classList.add("active")
|
this.optionElements[section_name][option_index].classList.add("active")
|
||||||
|
|
||||||
// change the editor
|
// change the editor
|
||||||
@ -778,7 +810,6 @@ document.getElementById("init_image_button_inpaint").addEventListener("click", (
|
|||||||
|
|
||||||
img2imgUnload() // no init image when the app starts
|
img2imgUnload() // no init image when the app starts
|
||||||
|
|
||||||
|
|
||||||
function rgbToHex(rgb) {
|
function rgbToHex(rgb) {
|
||||||
function componentToHex(c) {
|
function componentToHex(c) {
|
||||||
var hex = parseInt(c).toString(16)
|
var hex = parseInt(c).toString(16)
|
||||||
@ -788,12 +819,14 @@ function rgbToHex(rgb) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function hexToRgb(hex) {
|
function hexToRgb(hex) {
|
||||||
var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)
|
||||||
return result ? {
|
return result
|
||||||
|
? {
|
||||||
r: parseInt(result[1], 16),
|
r: parseInt(result[1], 16),
|
||||||
g: parseInt(result[2], 16),
|
g: parseInt(result[2], 16),
|
||||||
b: parseInt(result[3], 16)
|
b: parseInt(result[3], 16),
|
||||||
} : null;
|
}
|
||||||
|
: null
|
||||||
}
|
}
|
||||||
|
|
||||||
function pixelCompare(int1, int2) {
|
function pixelCompare(int1, int2) {
|
||||||
@ -802,82 +835,93 @@ function pixelCompare(int1, int2) {
|
|||||||
|
|
||||||
// adapted from https://ben.akrin.com/canvas_fill/fill_04.html
|
// adapted from https://ben.akrin.com/canvas_fill/fill_04.html
|
||||||
function flood_fill(editor, the_canvas_context, x, y, color) {
|
function flood_fill(editor, the_canvas_context, x, y, color) {
|
||||||
pixel_stack = [{x:x, y:y}] ;
|
pixel_stack = [{ x: x, y: y }]
|
||||||
pixels = the_canvas_context.getImageData( 0, 0, editor.width, editor.height ) ;
|
pixels = the_canvas_context.getImageData(0, 0, editor.width, editor.height)
|
||||||
var linear_cords = ( y * editor.width + x ) * 4 ;
|
var linear_cords = (y * editor.width + x) * 4
|
||||||
var original_color = {r:pixels.data[linear_cords],
|
var original_color = {
|
||||||
g:pixels.data[linear_cords+1],
|
r: pixels.data[linear_cords],
|
||||||
b:pixels.data[linear_cords+2],
|
g: pixels.data[linear_cords + 1],
|
||||||
a:pixels.data[linear_cords+3]} ;
|
b: pixels.data[linear_cords + 2],
|
||||||
|
a: pixels.data[linear_cords + 3],
|
||||||
|
}
|
||||||
|
|
||||||
var opacity = color.a / 255;
|
var opacity = color.a / 255
|
||||||
var new_color = {
|
var new_color = {
|
||||||
r: parseInt((color.r * opacity) + (original_color.r * (1 - opacity))),
|
r: parseInt(color.r * opacity + original_color.r * (1 - opacity)),
|
||||||
g: parseInt((color.g * opacity) + (original_color.g * (1 - opacity))),
|
g: parseInt(color.g * opacity + original_color.g * (1 - opacity)),
|
||||||
b: parseInt((color.b * opacity) + (original_color.b * (1 - opacity)))
|
b: parseInt(color.b * opacity + original_color.b * (1 - opacity)),
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((pixelCompare(new_color.r, original_color.r) &&
|
if (
|
||||||
|
pixelCompare(new_color.r, original_color.r) &&
|
||||||
pixelCompare(new_color.g, original_color.g) &&
|
pixelCompare(new_color.g, original_color.g) &&
|
||||||
pixelCompare(new_color.b, original_color.b)))
|
pixelCompare(new_color.b, original_color.b)
|
||||||
{
|
) {
|
||||||
return; // This color is already the color we want, so do nothing
|
return // This color is already the color we want, so do nothing
|
||||||
}
|
}
|
||||||
var max_stack_size = editor.width * editor.height;
|
var max_stack_size = editor.width * editor.height
|
||||||
while( pixel_stack.length > 0 && pixel_stack.length < max_stack_size ) {
|
while (pixel_stack.length > 0 && pixel_stack.length < max_stack_size) {
|
||||||
new_pixel = pixel_stack.shift() ;
|
new_pixel = pixel_stack.shift()
|
||||||
x = new_pixel.x ;
|
x = new_pixel.x
|
||||||
y = new_pixel.y ;
|
y = new_pixel.y
|
||||||
|
|
||||||
linear_cords = ( y * editor.width + x ) * 4 ;
|
linear_cords = (y * editor.width + x) * 4
|
||||||
while( y-->=0 &&
|
while (
|
||||||
(pixelCompare(pixels.data[linear_cords], original_color.r) &&
|
y-- >= 0 &&
|
||||||
pixelCompare(pixels.data[linear_cords+1], original_color.g) &&
|
pixelCompare(pixels.data[linear_cords], original_color.r) &&
|
||||||
pixelCompare(pixels.data[linear_cords+2], original_color.b))) {
|
pixelCompare(pixels.data[linear_cords + 1], original_color.g) &&
|
||||||
linear_cords -= editor.width * 4 ;
|
pixelCompare(pixels.data[linear_cords + 2], original_color.b)
|
||||||
|
) {
|
||||||
|
linear_cords -= editor.width * 4
|
||||||
}
|
}
|
||||||
linear_cords += editor.width * 4 ;
|
linear_cords += editor.width * 4
|
||||||
y++ ;
|
y++
|
||||||
|
|
||||||
var reached_left = false ;
|
var reached_left = false
|
||||||
var reached_right = false ;
|
var reached_right = false
|
||||||
while( y++<editor.height &&
|
while (
|
||||||
(pixelCompare(pixels.data[linear_cords], original_color.r) &&
|
y++ < editor.height &&
|
||||||
pixelCompare(pixels.data[linear_cords+1], original_color.g) &&
|
pixelCompare(pixels.data[linear_cords], original_color.r) &&
|
||||||
pixelCompare(pixels.data[linear_cords+2], original_color.b))) {
|
pixelCompare(pixels.data[linear_cords + 1], original_color.g) &&
|
||||||
pixels.data[linear_cords] = new_color.r ;
|
pixelCompare(pixels.data[linear_cords + 2], original_color.b)
|
||||||
pixels.data[linear_cords+1] = new_color.g ;
|
) {
|
||||||
pixels.data[linear_cords+2] = new_color.b ;
|
pixels.data[linear_cords] = new_color.r
|
||||||
pixels.data[linear_cords+3] = 255 ;
|
pixels.data[linear_cords + 1] = new_color.g
|
||||||
|
pixels.data[linear_cords + 2] = new_color.b
|
||||||
|
pixels.data[linear_cords + 3] = 255
|
||||||
|
|
||||||
if( x>0 ) {
|
if (x > 0) {
|
||||||
if( pixelCompare(pixels.data[linear_cords-4], original_color.r) &&
|
if (
|
||||||
pixelCompare(pixels.data[linear_cords-4+1], original_color.g) &&
|
pixelCompare(pixels.data[linear_cords - 4], original_color.r) &&
|
||||||
pixelCompare(pixels.data[linear_cords-4+2], original_color.b)) {
|
pixelCompare(pixels.data[linear_cords - 4 + 1], original_color.g) &&
|
||||||
if( !reached_left ) {
|
pixelCompare(pixels.data[linear_cords - 4 + 2], original_color.b)
|
||||||
pixel_stack.push( {x:x-1, y:y} ) ;
|
) {
|
||||||
reached_left = true ;
|
if (!reached_left) {
|
||||||
|
pixel_stack.push({ x: x - 1, y: y })
|
||||||
|
reached_left = true
|
||||||
}
|
}
|
||||||
} else if( reached_left ) {
|
} else if (reached_left) {
|
||||||
reached_left = false ;
|
reached_left = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if( x<editor.width-1 ) {
|
if (x < editor.width - 1) {
|
||||||
if( pixelCompare(pixels.data[linear_cords+4], original_color.r) &&
|
if (
|
||||||
pixelCompare(pixels.data[linear_cords+4+1], original_color.g) &&
|
pixelCompare(pixels.data[linear_cords + 4], original_color.r) &&
|
||||||
pixelCompare(pixels.data[linear_cords+4+2], original_color.b)) {
|
pixelCompare(pixels.data[linear_cords + 4 + 1], original_color.g) &&
|
||||||
if( !reached_right ) {
|
pixelCompare(pixels.data[linear_cords + 4 + 2], original_color.b)
|
||||||
pixel_stack.push( {x:x+1,y:y} ) ;
|
) {
|
||||||
reached_right = true ;
|
if (!reached_right) {
|
||||||
|
pixel_stack.push({ x: x + 1, y: y })
|
||||||
|
reached_right = true
|
||||||
}
|
}
|
||||||
} else if( reached_right ) {
|
} else if (reached_right) {
|
||||||
reached_right = false ;
|
reached_right = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
linear_cords += editor.width * 4 ;
|
linear_cords += editor.width * 4
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
the_canvas_context.putImageData( pixels, 0, 0 ) ;
|
the_canvas_context.putImageData(pixels, 0, 0)
|
||||||
}
|
}
|
||||||
|
@ -11,56 +11,35 @@
|
|||||||
* @type {(() => (string | ImageModalRequest) | string | ImageModalRequest) => {}}
|
* @type {(() => (string | ImageModalRequest) | string | ImageModalRequest) => {}}
|
||||||
*/
|
*/
|
||||||
const imageModal = (function() {
|
const imageModal = (function() {
|
||||||
const backElem = createElement(
|
const backElem = createElement("i", undefined, ["fa-solid", "fa-arrow-left", "tertiaryButton"])
|
||||||
'i',
|
|
||||||
undefined,
|
|
||||||
['fa-solid', 'fa-arrow-left', 'tertiaryButton'],
|
|
||||||
)
|
|
||||||
|
|
||||||
const forwardElem = createElement(
|
const forwardElem = createElement("i", undefined, ["fa-solid", "fa-arrow-right", "tertiaryButton"])
|
||||||
'i',
|
|
||||||
undefined,
|
|
||||||
['fa-solid', 'fa-arrow-right', 'tertiaryButton'],
|
|
||||||
)
|
|
||||||
|
|
||||||
const zoomElem = createElement(
|
const zoomElem = createElement("i", undefined, ["fa-solid", "tertiaryButton"])
|
||||||
'i',
|
|
||||||
undefined,
|
|
||||||
['fa-solid', 'tertiaryButton'],
|
|
||||||
)
|
|
||||||
|
|
||||||
const closeElem = createElement(
|
const closeElem = createElement("i", undefined, ["fa-solid", "fa-xmark", "tertiaryButton"])
|
||||||
'i',
|
|
||||||
undefined,
|
|
||||||
['fa-solid', 'fa-xmark', 'tertiaryButton'],
|
|
||||||
)
|
|
||||||
|
|
||||||
const menuBarElem = createElement('div', undefined, 'menu-bar', [backElem, forwardElem, zoomElem, closeElem])
|
const menuBarElem = createElement("div", undefined, "menu-bar", [backElem, forwardElem, zoomElem, closeElem])
|
||||||
|
|
||||||
const imageContainer = createElement('div', undefined, 'image-wrapper')
|
const imageContainer = createElement("div", undefined, "image-wrapper")
|
||||||
|
|
||||||
const backdrop = createElement('div', undefined, 'backdrop')
|
const backdrop = createElement("div", undefined, "backdrop")
|
||||||
|
|
||||||
const modalContainer = createElement('div', undefined, 'content', [menuBarElem, imageContainer])
|
const modalContainer = createElement("div", undefined, "content", [menuBarElem, imageContainer])
|
||||||
|
|
||||||
const modalElem = createElement(
|
const modalElem = createElement("div", { id: "viewFullSizeImgModal" }, ["popup"], [backdrop, modalContainer])
|
||||||
'div',
|
|
||||||
{ id: 'viewFullSizeImgModal' },
|
|
||||||
['popup'],
|
|
||||||
[backdrop, modalContainer],
|
|
||||||
)
|
|
||||||
document.body.appendChild(modalElem)
|
document.body.appendChild(modalElem)
|
||||||
|
|
||||||
const setZoomLevel = (value) => {
|
const setZoomLevel = (value) => {
|
||||||
const img = imageContainer.querySelector('img')
|
const img = imageContainer.querySelector("img")
|
||||||
|
|
||||||
if (value) {
|
if (value) {
|
||||||
zoomElem.classList.remove('fa-magnifying-glass-plus')
|
zoomElem.classList.remove("fa-magnifying-glass-plus")
|
||||||
zoomElem.classList.add('fa-magnifying-glass-minus')
|
zoomElem.classList.add("fa-magnifying-glass-minus")
|
||||||
if (img) {
|
if (img) {
|
||||||
img.classList.remove('natural-zoom')
|
img.classList.remove("natural-zoom")
|
||||||
|
|
||||||
let zoomLevel = typeof value === 'number' ? value : img.dataset.zoomLevel
|
let zoomLevel = typeof value === "number" ? value : img.dataset.zoomLevel
|
||||||
if (!zoomLevel) {
|
if (!zoomLevel) {
|
||||||
zoomLevel = 100
|
zoomLevel = 100
|
||||||
}
|
}
|
||||||
@ -70,36 +49,93 @@ const imageModal = (function() {
|
|||||||
img.height = img.naturalHeight * (+zoomLevel / 100)
|
img.height = img.naturalHeight * (+zoomLevel / 100)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
zoomElem.classList.remove('fa-magnifying-glass-minus')
|
zoomElem.classList.remove("fa-magnifying-glass-minus")
|
||||||
zoomElem.classList.add('fa-magnifying-glass-plus')
|
zoomElem.classList.add("fa-magnifying-glass-plus")
|
||||||
if (img) {
|
if (img) {
|
||||||
img.classList.add('natural-zoom')
|
img.classList.add("natural-zoom")
|
||||||
img.removeAttribute('width')
|
img.removeAttribute("width")
|
||||||
img.removeAttribute('height')
|
img.removeAttribute("height")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
zoomElem.addEventListener(
|
zoomElem.addEventListener("click", () =>
|
||||||
'click',
|
setZoomLevel(imageContainer.querySelector("img")?.classList?.contains("natural-zoom"))
|
||||||
() => setZoomLevel(imageContainer.querySelector('img')?.classList?.contains('natural-zoom')),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const state = {
|
const initialState = () => ({
|
||||||
previous: undefined,
|
previous: undefined,
|
||||||
next: undefined,
|
next: undefined,
|
||||||
|
|
||||||
|
start: {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
},
|
||||||
|
|
||||||
|
scroll: {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const state = initialState()
|
||||||
|
|
||||||
|
// Allow grabbing the image to scroll
|
||||||
|
const stopGrabbing = (e) => {
|
||||||
|
if(imageContainer.classList.contains("grabbing")) {
|
||||||
|
imageContainer.classList.remove("grabbing")
|
||||||
|
e?.preventDefault()
|
||||||
|
console.log(`stopGrabbing()`, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const addImageGrabbing = (image) => {
|
||||||
|
image?.addEventListener('mousedown', (e) => {
|
||||||
|
if (!image.classList.contains("natural-zoom")) {
|
||||||
|
e.stopPropagation()
|
||||||
|
e.stopImmediatePropagation()
|
||||||
|
e.preventDefault()
|
||||||
|
|
||||||
|
imageContainer.classList.add("grabbing")
|
||||||
|
state.start.x = e.pageX - imageContainer.offsetLeft
|
||||||
|
state.scroll.x = imageContainer.scrollLeft
|
||||||
|
state.start.y = e.pageY - imageContainer.offsetTop
|
||||||
|
state.scroll.y = imageContainer.scrollTop
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
image?.addEventListener('mouseup', stopGrabbing)
|
||||||
|
image?.addEventListener('mouseleave', stopGrabbing)
|
||||||
|
image?.addEventListener('mousemove', (e) => {
|
||||||
|
if(imageContainer.classList.contains("grabbing")) {
|
||||||
|
e.stopPropagation()
|
||||||
|
e.stopImmediatePropagation()
|
||||||
|
e.preventDefault()
|
||||||
|
|
||||||
|
// Might need to increase this multiplier based on the image size to window size ratio
|
||||||
|
// The default 1:1 is pretty slow
|
||||||
|
const multiplier = 1.0
|
||||||
|
|
||||||
|
const deltaX = e.pageX - imageContainer.offsetLeft - state.start.x
|
||||||
|
imageContainer.scrollLeft = state.scroll.x - (deltaX * multiplier)
|
||||||
|
const deltaY = e.pageY - imageContainer.offsetTop - state.start.y
|
||||||
|
imageContainer.scrollTop = state.scroll.y - (deltaY * multiplier)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const clear = () => {
|
const clear = () => {
|
||||||
imageContainer.innerHTML = ''
|
imageContainer.innerHTML = ""
|
||||||
|
|
||||||
Object.keys(state).forEach(key => delete state[key])
|
Object.entries(initialState()).forEach(([key, value]) => state[key] = value)
|
||||||
|
|
||||||
|
stopGrabbing()
|
||||||
}
|
}
|
||||||
|
|
||||||
const close = () => {
|
const close = () => {
|
||||||
clear()
|
clear()
|
||||||
modalElem.classList.remove('active')
|
modalElem.classList.remove("active")
|
||||||
document.body.style.overflow = 'initial'
|
document.body.style.overflow = "initial"
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -113,27 +149,28 @@ const imageModal = (function() {
|
|||||||
|
|
||||||
clear()
|
clear()
|
||||||
|
|
||||||
const options = typeof optionsFactory === 'function' ? optionsFactory() : optionsFactory
|
const options = typeof optionsFactory === "function" ? optionsFactory() : optionsFactory
|
||||||
const src = typeof options === 'string' ? options : options.src
|
const src = typeof options === "string" ? options : options.src
|
||||||
|
|
||||||
const imgElem = createElement('img', { src }, 'natural-zoom')
|
const imgElem = createElement("img", { src }, "natural-zoom")
|
||||||
|
addImageGrabbing(imgElem)
|
||||||
imageContainer.appendChild(imgElem)
|
imageContainer.appendChild(imgElem)
|
||||||
modalElem.classList.add('active')
|
modalElem.classList.add("active")
|
||||||
document.body.style.overflow = 'hidden'
|
document.body.style.overflow = "hidden"
|
||||||
setZoomLevel(false)
|
setZoomLevel(false)
|
||||||
|
|
||||||
if (typeof options === 'object' && options.previous) {
|
if (typeof options === "object" && options.previous) {
|
||||||
state.previous = options.previous
|
state.previous = options.previous
|
||||||
backElem.style.display = 'unset'
|
backElem.style.display = "unset"
|
||||||
} else {
|
} else {
|
||||||
backElem.style.display = 'none'
|
backElem.style.display = "none"
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof options === 'object' && options.next) {
|
if (typeof options === "object" && options.next) {
|
||||||
state.next = options.next
|
state.next = options.next
|
||||||
forwardElem.style.display = 'unset'
|
forwardElem.style.display = "unset"
|
||||||
} else {
|
} else {
|
||||||
forwardElem.style.display = 'none'
|
forwardElem.style.display = "none"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -141,7 +178,7 @@ const imageModal = (function() {
|
|||||||
if (state.previous) {
|
if (state.previous) {
|
||||||
init(state.previous)
|
init(state.previous)
|
||||||
} else {
|
} else {
|
||||||
backElem.style.display = 'none'
|
backElem.style.display = "none"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -149,27 +186,27 @@ const imageModal = (function() {
|
|||||||
if (state.next) {
|
if (state.next) {
|
||||||
init(state.next)
|
init(state.next)
|
||||||
} else {
|
} else {
|
||||||
forwardElem.style.display = 'none'
|
forwardElem.style.display = "none"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
window.addEventListener('keydown', (e) => {
|
window.addEventListener("keydown", (e) => {
|
||||||
if (modalElem.classList.contains('active')) {
|
if (modalElem.classList.contains("active")) {
|
||||||
switch (e.key) {
|
switch (e.key) {
|
||||||
case 'Escape':
|
case "Escape":
|
||||||
close()
|
close()
|
||||||
break
|
break
|
||||||
case 'ArrowLeft':
|
case "ArrowLeft":
|
||||||
back()
|
back()
|
||||||
break
|
break
|
||||||
case 'ArrowRight':
|
case "ArrowRight":
|
||||||
forward()
|
forward()
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
window.addEventListener('click', (e) => {
|
window.addEventListener("click", (e) => {
|
||||||
if (modalElem.classList.contains('active')) {
|
if (modalElem.classList.contains("active")) {
|
||||||
if (e.target === backdrop || e.target === closeElem) {
|
if (e.target === backdrop || e.target === closeElem) {
|
||||||
close()
|
close()
|
||||||
}
|
}
|
||||||
@ -180,9 +217,9 @@ const imageModal = (function() {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
backElem.addEventListener('click', back)
|
backElem.addEventListener("click", back)
|
||||||
|
|
||||||
forwardElem.addEventListener('click', forward)
|
forwardElem.addEventListener("click", forward)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {() => (string | ImageModalRequest) | string | ImageModalRequest} optionsFactory
|
* @param {() => (string | ImageModalRequest) | string | ImageModalRequest} optionsFactory
|
||||||
|
@ -3,26 +3,26 @@ let modifiers = []
|
|||||||
let customModifiersGroupElement = undefined
|
let customModifiersGroupElement = undefined
|
||||||
let customModifiersInitialContent
|
let customModifiersInitialContent
|
||||||
|
|
||||||
let editorModifierEntries = document.querySelector('#editor-modifiers-entries')
|
let editorModifierEntries = document.querySelector("#editor-modifiers-entries")
|
||||||
let editorModifierTagsList = document.querySelector('#editor-inputs-tags-list')
|
let editorModifierTagsList = document.querySelector("#editor-inputs-tags-list")
|
||||||
let editorTagsContainer = document.querySelector('#editor-inputs-tags-container')
|
let editorTagsContainer = document.querySelector("#editor-inputs-tags-container")
|
||||||
let modifierCardSizeSlider = document.querySelector('#modifier-card-size-slider')
|
let modifierCardSizeSlider = document.querySelector("#modifier-card-size-slider")
|
||||||
let previewImageField = document.querySelector('#preview-image')
|
let previewImageField = document.querySelector("#preview-image")
|
||||||
let modifierSettingsBtn = document.querySelector('#modifier-settings-btn')
|
let modifierSettingsBtn = document.querySelector("#modifier-settings-btn")
|
||||||
let modifierSettingsOverlay = document.querySelector('#modifier-settings-config')
|
let modifierSettingsOverlay = document.querySelector("#modifier-settings-config")
|
||||||
let customModifiersTextBox = document.querySelector('#custom-modifiers-input')
|
let customModifiersTextBox = document.querySelector("#custom-modifiers-input")
|
||||||
let customModifierEntriesToolbar = document.querySelector('#editor-modifiers-entries-toolbar')
|
let customModifierEntriesToolbar = document.querySelector("#editor-modifiers-entries-toolbar")
|
||||||
|
|
||||||
const modifierThumbnailPath = 'media/modifier-thumbnails'
|
const modifierThumbnailPath = "media/modifier-thumbnails"
|
||||||
const activeCardClass = 'modifier-card-active'
|
const activeCardClass = "modifier-card-active"
|
||||||
const CUSTOM_MODIFIERS_KEY = "customModifiers"
|
const CUSTOM_MODIFIERS_KEY = "customModifiers"
|
||||||
|
|
||||||
function createModifierCard(name, previews, removeBy) {
|
function createModifierCard(name, previews, removeBy) {
|
||||||
const modifierCard = document.createElement('div')
|
const modifierCard = document.createElement("div")
|
||||||
let style = previewImageField.value
|
let style = previewImageField.value
|
||||||
let styleIndex = (style=='portrait') ? 0 : 1
|
let styleIndex = style == "portrait" ? 0 : 1
|
||||||
|
|
||||||
modifierCard.className = 'modifier-card'
|
modifierCard.className = "modifier-card"
|
||||||
modifierCard.innerHTML = `
|
modifierCard.innerHTML = `
|
||||||
<div class="modifier-card-overlay"></div>
|
<div class="modifier-card-overlay"></div>
|
||||||
<div class="modifier-card-image-container">
|
<div class="modifier-card-image-container">
|
||||||
@ -34,35 +34,35 @@ function createModifierCard(name, previews, removeBy) {
|
|||||||
<div class="modifier-card-label"><p></p></div>
|
<div class="modifier-card-label"><p></p></div>
|
||||||
</div>`
|
</div>`
|
||||||
|
|
||||||
const image = modifierCard.querySelector('.modifier-card-image')
|
const image = modifierCard.querySelector(".modifier-card-image")
|
||||||
const errorText = modifierCard.querySelector('.modifier-card-error-label')
|
const errorText = modifierCard.querySelector(".modifier-card-error-label")
|
||||||
const label = modifierCard.querySelector('.modifier-card-label')
|
const label = modifierCard.querySelector(".modifier-card-label")
|
||||||
|
|
||||||
errorText.innerText = 'No Image'
|
errorText.innerText = "No Image"
|
||||||
|
|
||||||
if (typeof previews == 'object') {
|
if (typeof previews == "object") {
|
||||||
image.src = previews[styleIndex]; // portrait
|
image.src = previews[styleIndex] // portrait
|
||||||
image.setAttribute('preview-type', style)
|
image.setAttribute("preview-type", style)
|
||||||
} else {
|
} else {
|
||||||
image.remove()
|
image.remove()
|
||||||
}
|
}
|
||||||
|
|
||||||
const maxLabelLength = 30
|
const maxLabelLength = 30
|
||||||
const cardLabel = removeBy ? name.replace('by ', '') : name
|
const cardLabel = removeBy ? name.replace("by ", "") : name
|
||||||
|
|
||||||
if(cardLabel.length <= maxLabelLength) {
|
if (cardLabel.length <= maxLabelLength) {
|
||||||
label.querySelector('p').innerText = cardLabel
|
label.querySelector("p").innerText = cardLabel
|
||||||
} else {
|
} else {
|
||||||
const tooltipText = document.createElement('span')
|
const tooltipText = document.createElement("span")
|
||||||
tooltipText.className = 'tooltip-text'
|
tooltipText.className = "tooltip-text"
|
||||||
tooltipText.innerText = name
|
tooltipText.innerText = name
|
||||||
|
|
||||||
label.classList.add('tooltip')
|
label.classList.add("tooltip")
|
||||||
label.appendChild(tooltipText)
|
label.appendChild(tooltipText)
|
||||||
|
|
||||||
label.querySelector('p').innerText = cardLabel.substring(0, maxLabelLength) + '...'
|
label.querySelector("p").innerText = cardLabel.substring(0, maxLabelLength) + "..."
|
||||||
}
|
}
|
||||||
label.querySelector('p').dataset.fullName = name // preserve the full name
|
label.querySelector("p").dataset.fullName = name // preserve the full name
|
||||||
|
|
||||||
return modifierCard
|
return modifierCard
|
||||||
}
|
}
|
||||||
@ -71,55 +71,58 @@ function createModifierGroup(modifierGroup, initiallyExpanded, removeBy) {
|
|||||||
const title = modifierGroup.category
|
const title = modifierGroup.category
|
||||||
const modifiers = modifierGroup.modifiers
|
const modifiers = modifierGroup.modifiers
|
||||||
|
|
||||||
const titleEl = document.createElement('h5')
|
const titleEl = document.createElement("h5")
|
||||||
titleEl.className = 'collapsible'
|
titleEl.className = "collapsible"
|
||||||
titleEl.innerText = title
|
titleEl.innerText = title
|
||||||
|
|
||||||
const modifiersEl = document.createElement('div')
|
const modifiersEl = document.createElement("div")
|
||||||
modifiersEl.classList.add('collapsible-content', 'editor-modifiers-leaf')
|
modifiersEl.classList.add("collapsible-content", "editor-modifiers-leaf")
|
||||||
|
|
||||||
if (initiallyExpanded === true) {
|
if (initiallyExpanded === true) {
|
||||||
titleEl.className += ' active'
|
titleEl.className += " active"
|
||||||
}
|
}
|
||||||
|
|
||||||
modifiers.forEach(modObj => {
|
modifiers.forEach((modObj) => {
|
||||||
const modifierName = modObj.modifier
|
const modifierName = modObj.modifier
|
||||||
const modifierPreviews = modObj?.previews?.map(preview => `${IMAGE_REGEX.test(preview.image) ? preview.image : modifierThumbnailPath + '/' + preview.path}`)
|
const modifierPreviews = modObj?.previews?.map(
|
||||||
|
(preview) =>
|
||||||
|
`${IMAGE_REGEX.test(preview.image) ? preview.image : modifierThumbnailPath + "/" + preview.path}`
|
||||||
|
)
|
||||||
|
|
||||||
const modifierCard = createModifierCard(modifierName, modifierPreviews, removeBy)
|
const modifierCard = createModifierCard(modifierName, modifierPreviews, removeBy)
|
||||||
|
|
||||||
if(typeof modifierCard == 'object') {
|
if (typeof modifierCard == "object") {
|
||||||
modifiersEl.appendChild(modifierCard)
|
modifiersEl.appendChild(modifierCard)
|
||||||
const trimmedName = trimModifiers(modifierName)
|
const trimmedName = trimModifiers(modifierName)
|
||||||
|
|
||||||
modifierCard.addEventListener('click', () => {
|
modifierCard.addEventListener("click", () => {
|
||||||
if (activeTags.map(x => trimModifiers(x.name)).includes(trimmedName)) {
|
if (activeTags.map((x) => trimModifiers(x.name)).includes(trimmedName)) {
|
||||||
// remove modifier from active array
|
// remove modifier from active array
|
||||||
activeTags = activeTags.filter(x => trimModifiers(x.name) != trimmedName)
|
activeTags = activeTags.filter((x) => trimModifiers(x.name) != trimmedName)
|
||||||
toggleCardState(trimmedName, false)
|
toggleCardState(trimmedName, false)
|
||||||
} else {
|
} else {
|
||||||
// add modifier to active array
|
// add modifier to active array
|
||||||
activeTags.push({
|
activeTags.push({
|
||||||
'name': modifierName,
|
name: modifierName,
|
||||||
'element': modifierCard.cloneNode(true),
|
element: modifierCard.cloneNode(true),
|
||||||
'originElement': modifierCard,
|
originElement: modifierCard,
|
||||||
'previews': modifierPreviews
|
previews: modifierPreviews,
|
||||||
})
|
})
|
||||||
toggleCardState(trimmedName, true)
|
toggleCardState(trimmedName, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
refreshTagsList()
|
refreshTagsList()
|
||||||
document.dispatchEvent(new Event('refreshImageModifiers'))
|
document.dispatchEvent(new Event("refreshImageModifiers"))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
let brk = document.createElement('br')
|
let brk = document.createElement("br")
|
||||||
brk.style.clear = 'both'
|
brk.style.clear = "both"
|
||||||
modifiersEl.appendChild(brk)
|
modifiersEl.appendChild(brk)
|
||||||
|
|
||||||
let e = document.createElement('div')
|
let e = document.createElement("div")
|
||||||
e.className = 'modifier-category'
|
e.className = "modifier-category"
|
||||||
e.appendChild(titleEl)
|
e.appendChild(titleEl)
|
||||||
e.appendChild(modifiersEl)
|
e.appendChild(modifiersEl)
|
||||||
|
|
||||||
@ -130,87 +133,98 @@ function createModifierGroup(modifierGroup, initiallyExpanded, removeBy) {
|
|||||||
|
|
||||||
function trimModifiers(tag) {
|
function trimModifiers(tag) {
|
||||||
// Remove trailing '-' and/or '+'
|
// Remove trailing '-' and/or '+'
|
||||||
tag = tag.replace(/[-+]+$/, '');
|
tag = tag.replace(/[-+]+$/, "")
|
||||||
// Remove parentheses at beginning and end
|
// Remove parentheses at beginning and end
|
||||||
return tag.replace(/^[(]+|[\s)]+$/g, '');
|
return tag.replace(/^[(]+|[\s)]+$/g, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadModifiers() {
|
async function loadModifiers() {
|
||||||
try {
|
try {
|
||||||
let res = await fetch('/get/modifiers')
|
let res = await fetch("/get/modifiers")
|
||||||
if (res.status === 200) {
|
if (res.status === 200) {
|
||||||
res = await res.json()
|
res = await res.json()
|
||||||
|
|
||||||
modifiers = res; // update global variable
|
modifiers = res // update global variable
|
||||||
|
|
||||||
res.reverse()
|
res.reverse()
|
||||||
|
|
||||||
res.forEach((modifierGroup, idx) => {
|
res.forEach((modifierGroup, idx) => {
|
||||||
createModifierGroup(modifierGroup, idx === res.length - 1, modifierGroup === 'Artist' ? true : false) // only remove "By " for artists
|
createModifierGroup(modifierGroup, idx === res.length - 1, modifierGroup === "Artist" ? true : false) // only remove "By " for artists
|
||||||
})
|
})
|
||||||
|
|
||||||
createCollapsibles(editorModifierEntries)
|
createCollapsibles(editorModifierEntries)
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('error fetching modifiers', e)
|
console.error("error fetching modifiers", e)
|
||||||
}
|
}
|
||||||
|
|
||||||
loadCustomModifiers()
|
loadCustomModifiers()
|
||||||
resizeModifierCards(modifierCardSizeSlider.value)
|
resizeModifierCards(modifierCardSizeSlider.value)
|
||||||
document.dispatchEvent(new Event('loadImageModifiers'))
|
document.dispatchEvent(new Event("loadImageModifiers"))
|
||||||
}
|
}
|
||||||
|
|
||||||
function refreshModifiersState(newTags, inactiveTags) {
|
function refreshModifiersState(newTags, inactiveTags) {
|
||||||
// clear existing modifiers
|
// clear existing modifiers
|
||||||
document.querySelector('#editor-modifiers').querySelectorAll('.modifier-card').forEach(modifierCard => {
|
document
|
||||||
const modifierName = modifierCard.querySelector('.modifier-card-label p').dataset.fullName // pick the full modifier name
|
.querySelector("#editor-modifiers")
|
||||||
if (activeTags.map(x => x.name).includes(modifierName)) {
|
.querySelectorAll(".modifier-card")
|
||||||
|
.forEach((modifierCard) => {
|
||||||
|
const modifierName = modifierCard.querySelector(".modifier-card-label p").dataset.fullName // pick the full modifier name
|
||||||
|
if (activeTags.map((x) => x.name).includes(modifierName)) {
|
||||||
modifierCard.classList.remove(activeCardClass)
|
modifierCard.classList.remove(activeCardClass)
|
||||||
modifierCard.querySelector('.modifier-card-image-overlay').innerText = '+'
|
modifierCard.querySelector(".modifier-card-image-overlay").innerText = "+"
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
activeTags = []
|
activeTags = []
|
||||||
|
|
||||||
// set new modifiers
|
// set new modifiers
|
||||||
newTags.forEach(tag => {
|
newTags.forEach((tag) => {
|
||||||
let found = false
|
let found = false
|
||||||
document.querySelector('#editor-modifiers').querySelectorAll('.modifier-card').forEach(modifierCard => {
|
document
|
||||||
const modifierName = modifierCard.querySelector('.modifier-card-label p').dataset.fullName
|
.querySelector("#editor-modifiers")
|
||||||
const shortModifierName = modifierCard.querySelector('.modifier-card-label p').innerText
|
.querySelectorAll(".modifier-card")
|
||||||
|
.forEach((modifierCard) => {
|
||||||
|
const modifierName = modifierCard.querySelector(".modifier-card-label p").dataset.fullName
|
||||||
|
const shortModifierName = modifierCard.querySelector(".modifier-card-label p").innerText
|
||||||
if (trimModifiers(tag) == trimModifiers(modifierName)) {
|
if (trimModifiers(tag) == trimModifiers(modifierName)) {
|
||||||
// add modifier to active array
|
// add modifier to active array
|
||||||
if (!activeTags.map(x => x.name).includes(tag)) { // only add each tag once even if several custom modifier cards share the same tag
|
if (!activeTags.map((x) => x.name).includes(tag)) {
|
||||||
|
// only add each tag once even if several custom modifier cards share the same tag
|
||||||
const imageModifierCard = modifierCard.cloneNode(true)
|
const imageModifierCard = modifierCard.cloneNode(true)
|
||||||
imageModifierCard.querySelector('.modifier-card-label p').innerText = tag.replace(modifierName, shortModifierName)
|
imageModifierCard.querySelector(".modifier-card-label p").innerText = tag.replace(
|
||||||
|
modifierName,
|
||||||
|
shortModifierName
|
||||||
|
)
|
||||||
activeTags.push({
|
activeTags.push({
|
||||||
'name': tag,
|
name: tag,
|
||||||
'element': imageModifierCard,
|
element: imageModifierCard,
|
||||||
'originElement': modifierCard
|
originElement: modifierCard,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
modifierCard.classList.add(activeCardClass)
|
modifierCard.classList.add(activeCardClass)
|
||||||
modifierCard.querySelector('.modifier-card-image-overlay').innerText = '-'
|
modifierCard.querySelector(".modifier-card-image-overlay").innerText = "-"
|
||||||
found = true
|
found = true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
if (found == false) { // custom tag went missing, create one here
|
if (found == false) {
|
||||||
|
// custom tag went missing, create one here
|
||||||
let modifierCard = createModifierCard(tag, undefined, false) // create a modifier card for the missing tag, no image
|
let modifierCard = createModifierCard(tag, undefined, false) // create a modifier card for the missing tag, no image
|
||||||
|
|
||||||
modifierCard.addEventListener('click', () => {
|
modifierCard.addEventListener("click", () => {
|
||||||
if (activeTags.map(x => x.name).includes(tag)) {
|
if (activeTags.map((x) => x.name).includes(tag)) {
|
||||||
// remove modifier from active array
|
// remove modifier from active array
|
||||||
activeTags = activeTags.filter(x => x.name != tag)
|
activeTags = activeTags.filter((x) => x.name != tag)
|
||||||
modifierCard.classList.remove(activeCardClass)
|
modifierCard.classList.remove(activeCardClass)
|
||||||
|
|
||||||
modifierCard.querySelector('.modifier-card-image-overlay').innerText = '+'
|
modifierCard.querySelector(".modifier-card-image-overlay").innerText = "+"
|
||||||
}
|
}
|
||||||
refreshTagsList()
|
refreshTagsList()
|
||||||
})
|
})
|
||||||
|
|
||||||
activeTags.push({
|
activeTags.push({
|
||||||
'name': tag,
|
name: tag,
|
||||||
'element': modifierCard,
|
element: modifierCard,
|
||||||
'originElement': undefined // no origin element for missing tags
|
originElement: undefined, // no origin element for missing tags
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -220,41 +234,44 @@ function refreshModifiersState(newTags, inactiveTags) {
|
|||||||
function refreshInactiveTags(inactiveTags) {
|
function refreshInactiveTags(inactiveTags) {
|
||||||
// update inactive tags
|
// update inactive tags
|
||||||
if (inactiveTags !== undefined && inactiveTags.length > 0) {
|
if (inactiveTags !== undefined && inactiveTags.length > 0) {
|
||||||
activeTags.forEach (tag => {
|
activeTags.forEach((tag) => {
|
||||||
if (inactiveTags.find(element => element === tag.name) !== undefined) {
|
if (inactiveTags.find((element) => element === tag.name) !== undefined) {
|
||||||
tag.inactive = true
|
tag.inactive = true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// update cards
|
// update cards
|
||||||
let overlays = document.querySelector('#editor-inputs-tags-list').querySelectorAll('.modifier-card-overlay')
|
let overlays = document.querySelector("#editor-inputs-tags-list").querySelectorAll(".modifier-card-overlay")
|
||||||
overlays.forEach (i => {
|
overlays.forEach((i) => {
|
||||||
let modifierName = i.parentElement.getElementsByClassName('modifier-card-label')[0].getElementsByTagName("p")[0].dataset.fullName
|
let modifierName = i.parentElement.getElementsByClassName("modifier-card-label")[0].getElementsByTagName("p")[0]
|
||||||
if (inactiveTags?.find(element => element === modifierName) !== undefined) {
|
.dataset.fullName
|
||||||
i.parentElement.classList.add('modifier-toggle-inactive')
|
if (inactiveTags?.find((element) => trimModifiers(element) === modifierName) !== undefined) {
|
||||||
|
i.parentElement.classList.add("modifier-toggle-inactive")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function refreshTagsList(inactiveTags) {
|
function refreshTagsList(inactiveTags) {
|
||||||
editorModifierTagsList.innerHTML = ''
|
editorModifierTagsList.innerHTML = ""
|
||||||
|
|
||||||
if (activeTags.length == 0) {
|
if (activeTags.length == 0) {
|
||||||
editorTagsContainer.style.display = 'none'
|
editorTagsContainer.style.display = "none"
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
editorTagsContainer.style.display = 'block'
|
editorTagsContainer.style.display = "block"
|
||||||
}
|
}
|
||||||
|
|
||||||
activeTags.forEach((tag, index) => {
|
activeTags.forEach((tag, index) => {
|
||||||
tag.element.querySelector('.modifier-card-image-overlay').innerText = '-'
|
tag.element.querySelector(".modifier-card-image-overlay").innerText = "-"
|
||||||
tag.element.classList.add('modifier-card-tiny')
|
tag.element.classList.add("modifier-card-tiny")
|
||||||
|
|
||||||
editorModifierTagsList.appendChild(tag.element)
|
editorModifierTagsList.appendChild(tag.element)
|
||||||
|
|
||||||
tag.element.addEventListener('click', () => {
|
tag.element.addEventListener("click", () => {
|
||||||
let idx = activeTags.findIndex(o => { return o.name === tag.name })
|
let idx = activeTags.findIndex((o) => {
|
||||||
|
return o.name === tag.name
|
||||||
|
})
|
||||||
|
|
||||||
if (idx !== -1) {
|
if (idx !== -1) {
|
||||||
toggleCardState(activeTags[idx].name, false)
|
toggleCardState(activeTags[idx].name, false)
|
||||||
@ -262,88 +279,91 @@ function refreshTagsList(inactiveTags) {
|
|||||||
activeTags.splice(idx, 1)
|
activeTags.splice(idx, 1)
|
||||||
refreshTagsList()
|
refreshTagsList()
|
||||||
}
|
}
|
||||||
document.dispatchEvent(new Event('refreshImageModifiers'))
|
document.dispatchEvent(new Event("refreshImageModifiers"))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
let brk = document.createElement('br')
|
let brk = document.createElement("br")
|
||||||
brk.style.clear = 'both'
|
brk.style.clear = "both"
|
||||||
editorModifierTagsList.appendChild(brk)
|
editorModifierTagsList.appendChild(brk)
|
||||||
refreshInactiveTags(inactiveTags)
|
refreshInactiveTags(inactiveTags)
|
||||||
document.dispatchEvent(new Event('refreshImageModifiers')) // notify plugins that the image tags have been refreshed
|
document.dispatchEvent(new Event("refreshImageModifiers")) // notify plugins that the image tags have been refreshed
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleCardState(modifierName, makeActive) {
|
function toggleCardState(modifierName, makeActive) {
|
||||||
document.querySelector('#editor-modifiers').querySelectorAll('.modifier-card').forEach(card => {
|
document
|
||||||
const name = card.querySelector('.modifier-card-label').innerText
|
.querySelector("#editor-modifiers")
|
||||||
if ( trimModifiers(modifierName) == trimModifiers(name)
|
.querySelectorAll(".modifier-card")
|
||||||
|| trimModifiers(modifierName) == 'by ' + trimModifiers(name)) {
|
.forEach((card) => {
|
||||||
if(makeActive) {
|
const name = card.querySelector(".modifier-card-label").innerText
|
||||||
|
if (
|
||||||
|
trimModifiers(modifierName) == trimModifiers(name) ||
|
||||||
|
trimModifiers(modifierName) == "by " + trimModifiers(name)
|
||||||
|
) {
|
||||||
|
if (makeActive) {
|
||||||
card.classList.add(activeCardClass)
|
card.classList.add(activeCardClass)
|
||||||
card.querySelector('.modifier-card-image-overlay').innerText = '-'
|
card.querySelector(".modifier-card-image-overlay").innerText = "-"
|
||||||
}
|
} else {
|
||||||
else{
|
|
||||||
card.classList.remove(activeCardClass)
|
card.classList.remove(activeCardClass)
|
||||||
card.querySelector('.modifier-card-image-overlay').innerText = '+'
|
card.querySelector(".modifier-card-image-overlay").innerText = "+"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function changePreviewImages(val) {
|
function changePreviewImages(val) {
|
||||||
const previewImages = document.querySelectorAll('.modifier-card-image-container img')
|
const previewImages = document.querySelectorAll(".modifier-card-image-container img")
|
||||||
|
|
||||||
let previewArr = []
|
let previewArr = []
|
||||||
|
|
||||||
modifiers.map(x => x.modifiers).forEach(x => previewArr.push(...x.map(m => m.previews)))
|
modifiers.map((x) => x.modifiers).forEach((x) => previewArr.push(...x.map((m) => m.previews)))
|
||||||
|
|
||||||
previewArr = previewArr.map(x => {
|
previewArr = previewArr.map((x) => {
|
||||||
let obj = {}
|
let obj = {}
|
||||||
|
|
||||||
x.forEach(preview => {
|
x.forEach((preview) => {
|
||||||
obj[preview.name] = preview.path
|
obj[preview.name] = preview.path
|
||||||
})
|
})
|
||||||
|
|
||||||
return obj
|
return obj
|
||||||
})
|
})
|
||||||
|
|
||||||
previewImages.forEach(previewImage => {
|
previewImages.forEach((previewImage) => {
|
||||||
const currentPreviewType = previewImage.getAttribute('preview-type')
|
const currentPreviewType = previewImage.getAttribute("preview-type")
|
||||||
const relativePreviewPath = previewImage.src.split(modifierThumbnailPath + '/').pop()
|
const relativePreviewPath = previewImage.src.split(modifierThumbnailPath + "/").pop()
|
||||||
|
|
||||||
const previews = previewArr.find(preview => relativePreviewPath == preview[currentPreviewType])
|
const previews = previewArr.find((preview) => relativePreviewPath == preview[currentPreviewType])
|
||||||
|
|
||||||
if(typeof previews == 'object') {
|
if (typeof previews == "object") {
|
||||||
let preview = null
|
let preview = null
|
||||||
|
|
||||||
if (val == 'portrait') {
|
if (val == "portrait") {
|
||||||
preview = previews.portrait
|
preview = previews.portrait
|
||||||
}
|
} else if (val == "landscape") {
|
||||||
else if (val == 'landscape') {
|
|
||||||
preview = previews.landscape
|
preview = previews.landscape
|
||||||
}
|
}
|
||||||
|
|
||||||
if(preview != null) {
|
if (preview != null) {
|
||||||
previewImage.src = `${modifierThumbnailPath}/${preview}`
|
previewImage.src = `${modifierThumbnailPath}/${preview}`
|
||||||
previewImage.setAttribute('preview-type', val)
|
previewImage.setAttribute("preview-type", val)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function resizeModifierCards(val) {
|
function resizeModifierCards(val) {
|
||||||
const cardSizePrefix = 'modifier-card-size_'
|
const cardSizePrefix = "modifier-card-size_"
|
||||||
const modifierCardClass = 'modifier-card'
|
const modifierCardClass = "modifier-card"
|
||||||
|
|
||||||
const modifierCards = document.querySelectorAll(`.${modifierCardClass}`)
|
const modifierCards = document.querySelectorAll(`.${modifierCardClass}`)
|
||||||
const cardSize = n => `${cardSizePrefix}${n}`
|
const cardSize = (n) => `${cardSizePrefix}${n}`
|
||||||
|
|
||||||
modifierCards.forEach(card => {
|
modifierCards.forEach((card) => {
|
||||||
// remove existing size classes
|
// remove existing size classes
|
||||||
const classes = card.className.split(' ').filter(c => !c.startsWith(cardSizePrefix))
|
const classes = card.className.split(" ").filter((c) => !c.startsWith(cardSizePrefix))
|
||||||
card.className = classes.join(' ').trim()
|
card.className = classes.join(" ").trim()
|
||||||
|
|
||||||
if(val != 0) {
|
if (val != 0) {
|
||||||
card.classList.add(cardSize(val))
|
card.classList.add(cardSize(val))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -352,7 +372,7 @@ function resizeModifierCards(val) {
|
|||||||
modifierCardSizeSlider.onchange = () => resizeModifierCards(modifierCardSizeSlider.value)
|
modifierCardSizeSlider.onchange = () => resizeModifierCards(modifierCardSizeSlider.value)
|
||||||
previewImageField.onchange = () => changePreviewImages(previewImageField.value)
|
previewImageField.onchange = () => changePreviewImages(previewImageField.value)
|
||||||
|
|
||||||
modifierSettingsBtn.addEventListener('click', function(e) {
|
modifierSettingsBtn.addEventListener("click", function(e) {
|
||||||
modifierSettingsOverlay.classList.add("active")
|
modifierSettingsOverlay.classList.add("active")
|
||||||
customModifiersTextBox.setSelectionRange(0, 0)
|
customModifiersTextBox.setSelectionRange(0, 0)
|
||||||
customModifiersTextBox.focus()
|
customModifiersTextBox.focus()
|
||||||
@ -360,7 +380,7 @@ modifierSettingsBtn.addEventListener('click', function(e) {
|
|||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
})
|
})
|
||||||
|
|
||||||
modifierSettingsOverlay.addEventListener('keydown', function(e) {
|
modifierSettingsOverlay.addEventListener("keydown", function(e) {
|
||||||
switch (e.key) {
|
switch (e.key) {
|
||||||
case "Escape": // Escape to cancel
|
case "Escape": // Escape to cancel
|
||||||
customModifiersTextBox.value = customModifiersInitialContent // undo the changes
|
customModifiersTextBox.value = customModifiersInitialContent // undo the changes
|
||||||
@ -368,7 +388,8 @@ modifierSettingsOverlay.addEventListener('keydown', function(e) {
|
|||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
break
|
break
|
||||||
case "Enter":
|
case "Enter":
|
||||||
if (e.ctrlKey) { // Ctrl+Enter to confirm
|
if (e.ctrlKey) {
|
||||||
|
// Ctrl+Enter to confirm
|
||||||
modifierSettingsOverlay.classList.remove("active")
|
modifierSettingsOverlay.classList.remove("active")
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
break
|
break
|
||||||
@ -383,7 +404,7 @@ function saveCustomModifiers() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function loadCustomModifiers() {
|
function loadCustomModifiers() {
|
||||||
PLUGINS['MODIFIERS_LOAD'].forEach(fn=>fn.loader.call())
|
PLUGINS["MODIFIERS_LOAD"].forEach((fn) => fn.loader.call())
|
||||||
}
|
}
|
||||||
|
|
||||||
customModifiersTextBox.addEventListener('change', saveCustomModifiers)
|
customModifiersTextBox.addEventListener("change", saveCustomModifiers)
|
||||||
|
1125
ui/media/js/main.js
1125
ui/media/js/main.js
File diff suppressed because it is too large
Load Diff
@ -9,7 +9,7 @@ var ParameterType = {
|
|||||||
select_multiple: "select_multiple",
|
select_multiple: "select_multiple",
|
||||||
slider: "slider",
|
slider: "slider",
|
||||||
custom: "custom",
|
custom: "custom",
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* JSDoc style
|
* JSDoc style
|
||||||
@ -24,7 +24,6 @@ var ParameterType = {
|
|||||||
* @property {boolean?} saveInAppConfig
|
* @property {boolean?} saveInAppConfig
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
/** @type {Array.<Parameter>} */
|
/** @type {Array.<Parameter>} */
|
||||||
var PARAMETERS = [
|
var PARAMETERS = [
|
||||||
{
|
{
|
||||||
@ -33,13 +32,14 @@ var PARAMETERS = [
|
|||||||
label: "Theme",
|
label: "Theme",
|
||||||
default: "theme-default",
|
default: "theme-default",
|
||||||
note: "customize the look and feel of the ui",
|
note: "customize the look and feel of the ui",
|
||||||
options: [ // Note: options expanded dynamically
|
options: [
|
||||||
|
// Note: options expanded dynamically
|
||||||
{
|
{
|
||||||
value: "theme-default",
|
value: "theme-default",
|
||||||
label: "Default"
|
label: "Default",
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
icon: "fa-palette"
|
icon: "fa-palette",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "save_to_disk",
|
id: "save_to_disk",
|
||||||
@ -55,7 +55,7 @@ var PARAMETERS = [
|
|||||||
label: "Save Location",
|
label: "Save Location",
|
||||||
render: (parameter) => {
|
render: (parameter) => {
|
||||||
return `<input id="${parameter.id}" name="${parameter.id}" size="30" disabled>`
|
return `<input id="${parameter.id}" name="${parameter.id}" size="30" disabled>`
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "metadata_output_format",
|
id: "metadata_output_format",
|
||||||
@ -66,19 +66,19 @@ var PARAMETERS = [
|
|||||||
options: [
|
options: [
|
||||||
{
|
{
|
||||||
value: "none",
|
value: "none",
|
||||||
label: "none"
|
label: "none",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: "txt",
|
value: "txt",
|
||||||
label: "txt"
|
label: "txt",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: "json",
|
value: "json",
|
||||||
label: "json"
|
label: "json",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: "embed",
|
value: "embed",
|
||||||
label: "embed"
|
label: "embed",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: "embed,txt",
|
value: "embed,txt",
|
||||||
@ -127,16 +127,17 @@ var PARAMETERS = [
|
|||||||
id: "vram_usage_level",
|
id: "vram_usage_level",
|
||||||
type: ParameterType.select,
|
type: ParameterType.select,
|
||||||
label: "GPU Memory Usage",
|
label: "GPU Memory Usage",
|
||||||
note: "Faster performance requires more GPU memory (VRAM)<br/><br/>" +
|
note:
|
||||||
|
"Faster performance requires more GPU memory (VRAM)<br/><br/>" +
|
||||||
"<b>Balanced:</b> nearly as fast as High, much lower VRAM usage<br/>" +
|
"<b>Balanced:</b> nearly as fast as High, much lower VRAM usage<br/>" +
|
||||||
"<b>High:</b> fastest, maximum GPU memory usage</br>" +
|
"<b>High:</b> fastest, maximum GPU memory usage</br>" +
|
||||||
"<b>Low:</b> slowest, recommended for GPUs with 3 to 4 GB memory",
|
"<b>Low:</b> slowest, recommended for GPUs with 3 to 4 GB memory",
|
||||||
icon: "fa-forward",
|
icon: "fa-forward",
|
||||||
default: "balanced",
|
default: "balanced",
|
||||||
options: [
|
options: [
|
||||||
{value: "balanced", label: "Balanced"},
|
{ value: "balanced", label: "Balanced" },
|
||||||
{value: "high", label: "High"},
|
{ value: "high", label: "High" },
|
||||||
{value: "low", label: "Low"}
|
{ value: "low", label: "Low" },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -172,14 +173,15 @@ var PARAMETERS = [
|
|||||||
id: "confirm_dangerous_actions",
|
id: "confirm_dangerous_actions",
|
||||||
type: ParameterType.checkbox,
|
type: ParameterType.checkbox,
|
||||||
label: "Confirm dangerous actions",
|
label: "Confirm dangerous actions",
|
||||||
note: "Actions that might lead to data loss must either be clicked with the shift key pressed, or confirmed in an 'Are you sure?' dialog",
|
note:
|
||||||
|
"Actions that might lead to data loss must either be clicked with the shift key pressed, or confirmed in an 'Are you sure?' dialog",
|
||||||
icon: "fa-check-double",
|
icon: "fa-check-double",
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "listen_to_network",
|
id: "listen_to_network",
|
||||||
type: ParameterType.checkbox,
|
type: ParameterType.checkbox,
|
||||||
label: "Make Stable Diffusion available on your network",
|
label: "Make Stable Diffusion available on your network. Please restart the program after changing this.",
|
||||||
note: "Other devices on your network can access this web page",
|
note: "Other devices on your network can access this web page",
|
||||||
icon: "fa-network-wired",
|
icon: "fa-network-wired",
|
||||||
default: true,
|
default: true,
|
||||||
@ -189,7 +191,7 @@ var PARAMETERS = [
|
|||||||
id: "listen_port",
|
id: "listen_port",
|
||||||
type: ParameterType.custom,
|
type: ParameterType.custom,
|
||||||
label: "Network port",
|
label: "Network port",
|
||||||
note: "Port that this server listens to. The '9000' part in 'http://localhost:9000'",
|
note: "Port that this server listens to. The '9000' part in 'http://localhost:9000'. Please restart the program after changing this.",
|
||||||
icon: "fa-anchor",
|
icon: "fa-anchor",
|
||||||
render: (parameter) => {
|
render: (parameter) => {
|
||||||
return `<input id="${parameter.id}" name="${parameter.id}" size="6" value="9000" onkeypress="preventNonNumericalInput(event)">`
|
return `<input id="${parameter.id}" name="${parameter.id}" size="6" value="9000" onkeypress="preventNonNumericalInput(event)">`
|
||||||
@ -200,7 +202,8 @@ var PARAMETERS = [
|
|||||||
id: "use_beta_channel",
|
id: "use_beta_channel",
|
||||||
type: ParameterType.checkbox,
|
type: ParameterType.checkbox,
|
||||||
label: "Beta channel",
|
label: "Beta channel",
|
||||||
note: "Get the latest features immediately (but could be less stable). Please restart the program after changing this.",
|
note:
|
||||||
|
"Get the latest features immediately (but could be less stable). Please restart the program after changing this.",
|
||||||
icon: "fa-fire",
|
icon: "fa-fire",
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
@ -208,15 +211,16 @@ var PARAMETERS = [
|
|||||||
id: "test_diffusers",
|
id: "test_diffusers",
|
||||||
type: ParameterType.checkbox,
|
type: ParameterType.checkbox,
|
||||||
label: "Test Diffusers",
|
label: "Test Diffusers",
|
||||||
note: "<b>Experimental! Can have bugs!</b> Use upcoming features (like LoRA) in our new engine. Please press Save, then restart the program after changing this.",
|
note:
|
||||||
|
"<b>Experimental! Can have bugs!</b> Use upcoming features (like LoRA) in our new engine. Please press Save, then restart the program after changing this.",
|
||||||
icon: "fa-bolt",
|
icon: "fa-bolt",
|
||||||
default: false,
|
default: false,
|
||||||
saveInAppConfig: true,
|
saveInAppConfig: true,
|
||||||
},
|
},
|
||||||
];
|
]
|
||||||
|
|
||||||
function getParameterSettingsEntry(id) {
|
function getParameterSettingsEntry(id) {
|
||||||
let parameter = PARAMETERS.filter(p => p.id === id)
|
let parameter = PARAMETERS.filter((p) => p.id === id)
|
||||||
if (parameter.length === 0) {
|
if (parameter.length === 0) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -224,12 +228,12 @@ function getParameterSettingsEntry(id) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function sliderUpdate(event) {
|
function sliderUpdate(event) {
|
||||||
if (event.srcElement.id.endsWith('-input')) {
|
if (event.srcElement.id.endsWith("-input")) {
|
||||||
let slider = document.getElementById(event.srcElement.id.slice(0,-6))
|
let slider = document.getElementById(event.srcElement.id.slice(0, -6))
|
||||||
slider.value = event.srcElement.value
|
slider.value = event.srcElement.value
|
||||||
slider.dispatchEvent(new Event("change"))
|
slider.dispatchEvent(new Event("change"))
|
||||||
} else {
|
} else {
|
||||||
let field = document.getElementById(event.srcElement.id+'-input')
|
let field = document.getElementById(event.srcElement.id + "-input")
|
||||||
field.value = event.srcElement.value
|
field.value = event.srcElement.value
|
||||||
field.dispatchEvent(new Event("change"))
|
field.dispatchEvent(new Event("change"))
|
||||||
}
|
}
|
||||||
@ -242,19 +246,21 @@ function sliderUpdate(event) {
|
|||||||
function getParameterElement(parameter) {
|
function getParameterElement(parameter) {
|
||||||
switch (parameter.type) {
|
switch (parameter.type) {
|
||||||
case ParameterType.checkbox:
|
case ParameterType.checkbox:
|
||||||
var is_checked = parameter.default ? " checked" : "";
|
var is_checked = parameter.default ? " checked" : ""
|
||||||
return `<input id="${parameter.id}" name="${parameter.id}"${is_checked} type="checkbox">`
|
return `<input id="${parameter.id}" name="${parameter.id}"${is_checked} type="checkbox">`
|
||||||
case ParameterType.select:
|
case ParameterType.select:
|
||||||
case ParameterType.select_multiple:
|
case ParameterType.select_multiple:
|
||||||
var options = (parameter.options || []).map(option => `<option value="${option.value}">${option.label}</option>`).join("")
|
var options = (parameter.options || [])
|
||||||
var multiple = (parameter.type == ParameterType.select_multiple ? 'multiple' : '')
|
.map((option) => `<option value="${option.value}">${option.label}</option>`)
|
||||||
|
.join("")
|
||||||
|
var multiple = parameter.type == ParameterType.select_multiple ? "multiple" : ""
|
||||||
return `<select id="${parameter.id}" name="${parameter.id}" ${multiple}>${options}</select>`
|
return `<select id="${parameter.id}" name="${parameter.id}" ${multiple}>${options}</select>`
|
||||||
case ParameterType.slider:
|
case ParameterType.slider:
|
||||||
return `<input id="${parameter.id}" name="${parameter.id}" class="editor-slider" type="range" value="${parameter.default}" min="${parameter.slider_min}" max="${parameter.slider_max}" oninput="sliderUpdate(event)"> <input id="${parameter.id}-input" name="${parameter.id}-input" size="4" value="${parameter.default}" pattern="^[0-9\.]+$" onkeypress="preventNonNumericalInput(event)" oninput="sliderUpdate(event)"> ${parameter.slider_unit}`
|
return `<input id="${parameter.id}" name="${parameter.id}" class="editor-slider" type="range" value="${parameter.default}" min="${parameter.slider_min}" max="${parameter.slider_max}" oninput="sliderUpdate(event)"> <input id="${parameter.id}-input" name="${parameter.id}-input" size="4" value="${parameter.default}" pattern="^[0-9\.]+$" onkeypress="preventNonNumericalInput(event)" oninput="sliderUpdate(event)"> ${parameter.slider_unit}`
|
||||||
case ParameterType.custom:
|
case ParameterType.custom:
|
||||||
return parameter.render(parameter)
|
return parameter.render(parameter)
|
||||||
default:
|
default:
|
||||||
console.error(`Invalid type ${parameter.type} for parameter ${parameter.id}`);
|
console.error(`Invalid type ${parameter.type} for parameter ${parameter.id}`)
|
||||||
return "ERROR: Invalid Type"
|
return "ERROR: Invalid Type"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -265,31 +271,31 @@ let parametersTable = document.querySelector("#system-settings .parameters-table
|
|||||||
* @param {Array<Parameter> | undefined} parameters
|
* @param {Array<Parameter> | undefined} parameters
|
||||||
* */
|
* */
|
||||||
function initParameters(parameters) {
|
function initParameters(parameters) {
|
||||||
parameters.forEach(parameter => {
|
parameters.forEach((parameter) => {
|
||||||
const element = getParameterElement(parameter)
|
const element = getParameterElement(parameter)
|
||||||
const elementWrapper = createElement('div')
|
const elementWrapper = createElement("div")
|
||||||
if (element instanceof Node) {
|
if (element instanceof Node) {
|
||||||
elementWrapper.appendChild(element)
|
elementWrapper.appendChild(element)
|
||||||
} else {
|
} else {
|
||||||
elementWrapper.innerHTML = element
|
elementWrapper.innerHTML = element
|
||||||
}
|
}
|
||||||
|
|
||||||
const note = typeof parameter.note === 'function' ? parameter.note(parameter) : parameter.note
|
const note = typeof parameter.note === "function" ? parameter.note(parameter) : parameter.note
|
||||||
const noteElements = []
|
const noteElements = []
|
||||||
if (note) {
|
if (note) {
|
||||||
const noteElement = createElement('small')
|
const noteElement = createElement("small")
|
||||||
if (note instanceof Node) {
|
if (note instanceof Node) {
|
||||||
noteElement.appendChild(note)
|
noteElement.appendChild(note)
|
||||||
} else {
|
} else {
|
||||||
noteElement.innerHTML = note || ''
|
noteElement.innerHTML = note || ""
|
||||||
}
|
}
|
||||||
noteElements.push(noteElement)
|
noteElements.push(noteElement)
|
||||||
}
|
}
|
||||||
|
|
||||||
const icon = parameter.icon ? [createElement('i', undefined, ['fa', parameter.icon])] : []
|
const icon = parameter.icon ? [createElement("i", undefined, ["fa", parameter.icon])] : []
|
||||||
|
|
||||||
const label = typeof parameter.label === 'function' ? parameter.label(parameter) : parameter.label
|
const label = typeof parameter.label === "function" ? parameter.label(parameter) : parameter.label
|
||||||
const labelElement = createElement('label', { for: parameter.id })
|
const labelElement = createElement("label", { for: parameter.id })
|
||||||
if (label instanceof Node) {
|
if (label instanceof Node) {
|
||||||
labelElement.appendChild(label)
|
labelElement.appendChild(label)
|
||||||
} else {
|
} else {
|
||||||
@ -297,12 +303,12 @@ function initParameters(parameters) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const newrow = createElement(
|
const newrow = createElement(
|
||||||
'div',
|
"div",
|
||||||
{ 'data-setting-id': parameter.id, 'data-save-in-app-config': parameter.saveInAppConfig },
|
{ "data-setting-id": parameter.id, "data-save-in-app-config": parameter.saveInAppConfig },
|
||||||
undefined,
|
undefined,
|
||||||
[
|
[
|
||||||
createElement('div', undefined, undefined, icon),
|
createElement("div", undefined, undefined, icon),
|
||||||
createElement('div', undefined, undefined, [labelElement, ...noteElements]),
|
createElement("div", undefined, undefined, [labelElement, ...noteElements]),
|
||||||
elementWrapper,
|
elementWrapper,
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
@ -314,22 +320,25 @@ function initParameters(parameters) {
|
|||||||
initParameters(PARAMETERS)
|
initParameters(PARAMETERS)
|
||||||
|
|
||||||
// listen to parameters from plugins
|
// listen to parameters from plugins
|
||||||
PARAMETERS.addEventListener('push', (...items) => {
|
PARAMETERS.addEventListener("push", (...items) => {
|
||||||
initParameters(items)
|
initParameters(items)
|
||||||
|
|
||||||
if (items.find(item => item.saveInAppConfig)) {
|
if (items.find((item) => item.saveInAppConfig)) {
|
||||||
console.log('Reloading app config for new parameters', items.map(p => p.id))
|
console.log(
|
||||||
|
"Reloading app config for new parameters",
|
||||||
|
items.map((p) => p.id)
|
||||||
|
)
|
||||||
getAppConfig()
|
getAppConfig()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
let vramUsageLevelField = document.querySelector('#vram_usage_level')
|
let vramUsageLevelField = document.querySelector("#vram_usage_level")
|
||||||
let useCPUField = document.querySelector('#use_cpu')
|
let useCPUField = document.querySelector("#use_cpu")
|
||||||
let autoPickGPUsField = document.querySelector('#auto_pick_gpus')
|
let autoPickGPUsField = document.querySelector("#auto_pick_gpus")
|
||||||
let useGPUsField = document.querySelector('#use_gpus')
|
let useGPUsField = document.querySelector("#use_gpus")
|
||||||
let saveToDiskField = document.querySelector('#save_to_disk')
|
let saveToDiskField = document.querySelector("#save_to_disk")
|
||||||
let diskPathField = document.querySelector('#diskPath')
|
let diskPathField = document.querySelector("#diskPath")
|
||||||
let metadataOutputFormatField = document.querySelector('#metadata_output_format')
|
let metadataOutputFormatField = document.querySelector("#metadata_output_format")
|
||||||
let listenToNetworkField = document.querySelector("#listen_to_network")
|
let listenToNetworkField = document.querySelector("#listen_to_network")
|
||||||
let listenPortField = document.querySelector("#listen_port")
|
let listenPortField = document.querySelector("#listen_port")
|
||||||
let useBetaChannelField = document.querySelector("#use_beta_channel")
|
let useBetaChannelField = document.querySelector("#use_beta_channel")
|
||||||
@ -337,35 +346,34 @@ let uiOpenBrowserOnStartField = document.querySelector("#ui_open_browser_on_star
|
|||||||
let confirmDangerousActionsField = document.querySelector("#confirm_dangerous_actions")
|
let confirmDangerousActionsField = document.querySelector("#confirm_dangerous_actions")
|
||||||
let testDiffusers = document.querySelector("#test_diffusers")
|
let testDiffusers = document.querySelector("#test_diffusers")
|
||||||
|
|
||||||
let saveSettingsBtn = document.querySelector('#save-system-settings-btn')
|
let saveSettingsBtn = document.querySelector("#save-system-settings-btn")
|
||||||
|
|
||||||
|
|
||||||
async function changeAppConfig(configDelta) {
|
async function changeAppConfig(configDelta) {
|
||||||
try {
|
try {
|
||||||
let res = await fetch('/app_config', {
|
let res = await fetch("/app_config", {
|
||||||
method: 'POST',
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json'
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
body: JSON.stringify(configDelta)
|
body: JSON.stringify(configDelta),
|
||||||
})
|
})
|
||||||
res = await res.json()
|
res = await res.json()
|
||||||
|
|
||||||
console.log('set config status response', res)
|
console.log("set config status response", res)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log('set config status error', e)
|
console.log("set config status error", e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getAppConfig() {
|
async function getAppConfig() {
|
||||||
try {
|
try {
|
||||||
let res = await fetch('/get/app_config')
|
let res = await fetch("/get/app_config")
|
||||||
const config = await res.json()
|
const config = await res.json()
|
||||||
|
|
||||||
applySettingsFromConfig(config)
|
applySettingsFromConfig(config)
|
||||||
|
|
||||||
// custom overrides
|
// custom overrides
|
||||||
if (config.update_branch === 'beta') {
|
if (config.update_branch === "beta") {
|
||||||
useBetaChannelField.checked = true
|
useBetaChannelField.checked = true
|
||||||
document.querySelector("#updateBranchLabel").innerText = "(beta)"
|
document.querySelector("#updateBranchLabel").innerText = "(beta)"
|
||||||
} else {
|
} else {
|
||||||
@ -380,45 +388,58 @@ async function getAppConfig() {
|
|||||||
if (config.net && config.net.listen_port !== undefined) {
|
if (config.net && config.net.listen_port !== undefined) {
|
||||||
listenPortField.value = config.net.listen_port
|
listenPortField.value = config.net.listen_port
|
||||||
}
|
}
|
||||||
if (config.test_diffusers === undefined || config.update_branch === 'main') {
|
|
||||||
testDiffusers.checked = false
|
const testDiffusersEnabled = config.test_diffusers && config.update_branch !== "main"
|
||||||
document.querySelector("#lora_model_container").style.display = 'none'
|
testDiffusers.checked = testDiffusersEnabled
|
||||||
document.querySelector("#lora_alpha_container").style.display = 'none'
|
|
||||||
|
if (!testDiffusersEnabled) {
|
||||||
|
document.querySelector("#lora_model_container").style.display = "none"
|
||||||
|
document.querySelector("#lora_alpha_container").style.display = "none"
|
||||||
|
|
||||||
|
document.querySelectorAll("#sampler_name option.diffusers-only").forEach(option => {
|
||||||
|
option.style.display = "none"
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
testDiffusers.checked = config.test_diffusers && config.update_branch !== 'main'
|
document.querySelector("#lora_model_container").style.display = ""
|
||||||
document.querySelector("#lora_model_container").style.display = (testDiffusers.checked ? '' : 'none')
|
document.querySelector("#lora_alpha_container").style.display = loraModelField.value ? "" : "none"
|
||||||
document.querySelector("#lora_alpha_container").style.display = (testDiffusers.checked && loraModelField.value !== "" ? '' : 'none')
|
|
||||||
|
document.querySelectorAll("#sampler_name option.k_diffusion-only").forEach(option => {
|
||||||
|
option.disabled = true
|
||||||
|
})
|
||||||
|
document.querySelector("#clip_skip_config").classList.remove("displayNone")
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('get config status response', config)
|
console.log("get config status response", config)
|
||||||
|
|
||||||
return config
|
return config
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log('get config status error', e)
|
console.log("get config status error", e)
|
||||||
|
|
||||||
return {}
|
return {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function applySettingsFromConfig(config) {
|
function applySettingsFromConfig(config) {
|
||||||
Array.from(parametersTable.children).forEach(parameterRow => {
|
Array.from(parametersTable.children).forEach((parameterRow) => {
|
||||||
if (parameterRow.dataset.settingId in config && parameterRow.dataset.saveInAppConfig === 'true') {
|
if (parameterRow.dataset.settingId in config && parameterRow.dataset.saveInAppConfig === "true") {
|
||||||
const configValue = config[parameterRow.dataset.settingId]
|
const configValue = config[parameterRow.dataset.settingId]
|
||||||
const parameterElement = document.getElementById(parameterRow.dataset.settingId) ||
|
const parameterElement =
|
||||||
parameterRow.querySelector('input') || parameterRow.querySelector('select')
|
document.getElementById(parameterRow.dataset.settingId) ||
|
||||||
|
parameterRow.querySelector("input") ||
|
||||||
|
parameterRow.querySelector("select")
|
||||||
|
|
||||||
switch (parameterElement?.tagName) {
|
switch (parameterElement?.tagName) {
|
||||||
case 'INPUT':
|
case "INPUT":
|
||||||
if (parameterElement.type === 'checkbox') {
|
if (parameterElement.type === "checkbox") {
|
||||||
parameterElement.checked = configValue
|
parameterElement.checked = configValue
|
||||||
} else {
|
} else {
|
||||||
parameterElement.value = configValue
|
parameterElement.value = configValue
|
||||||
}
|
}
|
||||||
parameterElement.dispatchEvent(new Event('change'))
|
parameterElement.dispatchEvent(new Event("change"))
|
||||||
break
|
break
|
||||||
case 'SELECT':
|
case "SELECT":
|
||||||
if (Array.isArray(configValue)) {
|
if (Array.isArray(configValue)) {
|
||||||
Array.from(parameterElement.options).forEach(option => {
|
Array.from(parameterElement.options).forEach((option) => {
|
||||||
if (configValue.includes(option.value || option.text)) {
|
if (configValue.includes(option.value || option.text)) {
|
||||||
option.selected = true
|
option.selected = true
|
||||||
}
|
}
|
||||||
@ -426,82 +447,85 @@ function applySettingsFromConfig(config) {
|
|||||||
} else {
|
} else {
|
||||||
parameterElement.value = configValue
|
parameterElement.value = configValue
|
||||||
}
|
}
|
||||||
parameterElement.dispatchEvent(new Event('change'))
|
parameterElement.dispatchEvent(new Event("change"))
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
saveToDiskField.addEventListener('change', function(e) {
|
saveToDiskField.addEventListener("change", function(e) {
|
||||||
diskPathField.disabled = !this.checked
|
diskPathField.disabled = !this.checked
|
||||||
metadataOutputFormatField.disabled = !this.checked
|
metadataOutputFormatField.disabled = !this.checked
|
||||||
})
|
})
|
||||||
|
|
||||||
function getCurrentRenderDeviceSelection() {
|
function getCurrentRenderDeviceSelection() {
|
||||||
let selectedGPUs = $('#use_gpus').val()
|
let selectedGPUs = $("#use_gpus").val()
|
||||||
|
|
||||||
if (useCPUField.checked && !autoPickGPUsField.checked) {
|
if (useCPUField.checked && !autoPickGPUsField.checked) {
|
||||||
return 'cpu'
|
return "cpu"
|
||||||
}
|
}
|
||||||
if (autoPickGPUsField.checked || selectedGPUs.length == 0) {
|
if (autoPickGPUsField.checked || selectedGPUs.length == 0) {
|
||||||
return 'auto'
|
return "auto"
|
||||||
}
|
}
|
||||||
|
|
||||||
return selectedGPUs.join(',')
|
return selectedGPUs.join(",")
|
||||||
}
|
}
|
||||||
|
|
||||||
useCPUField.addEventListener('click', function() {
|
useCPUField.addEventListener("click", function() {
|
||||||
let gpuSettingEntry = getParameterSettingsEntry('use_gpus')
|
let gpuSettingEntry = getParameterSettingsEntry("use_gpus")
|
||||||
let autoPickGPUSettingEntry = getParameterSettingsEntry('auto_pick_gpus')
|
let autoPickGPUSettingEntry = getParameterSettingsEntry("auto_pick_gpus")
|
||||||
if (this.checked) {
|
if (this.checked) {
|
||||||
gpuSettingEntry.style.display = 'none'
|
gpuSettingEntry.style.display = "none"
|
||||||
autoPickGPUSettingEntry.style.display = 'none'
|
autoPickGPUSettingEntry.style.display = "none"
|
||||||
autoPickGPUsField.setAttribute('data-old-value', autoPickGPUsField.checked)
|
autoPickGPUsField.setAttribute("data-old-value", autoPickGPUsField.checked)
|
||||||
autoPickGPUsField.checked = false
|
autoPickGPUsField.checked = false
|
||||||
} else if (useGPUsField.options.length >= MIN_GPUS_TO_SHOW_SELECTION) {
|
} else if (useGPUsField.options.length >= MIN_GPUS_TO_SHOW_SELECTION) {
|
||||||
gpuSettingEntry.style.display = ''
|
gpuSettingEntry.style.display = ""
|
||||||
autoPickGPUSettingEntry.style.display = ''
|
autoPickGPUSettingEntry.style.display = ""
|
||||||
let oldVal = autoPickGPUsField.getAttribute('data-old-value')
|
let oldVal = autoPickGPUsField.getAttribute("data-old-value")
|
||||||
if (oldVal === null || oldVal === undefined) { // the UI started with CPU selected by default
|
if (oldVal === null || oldVal === undefined) {
|
||||||
|
// the UI started with CPU selected by default
|
||||||
autoPickGPUsField.checked = true
|
autoPickGPUsField.checked = true
|
||||||
} else {
|
} else {
|
||||||
autoPickGPUsField.checked = (oldVal === 'true')
|
autoPickGPUsField.checked = oldVal === "true"
|
||||||
}
|
}
|
||||||
gpuSettingEntry.style.display = (autoPickGPUsField.checked ? 'none' : '')
|
gpuSettingEntry.style.display = autoPickGPUsField.checked ? "none" : ""
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
useGPUsField.addEventListener('click', function() {
|
useGPUsField.addEventListener("click", function() {
|
||||||
let selectedGPUs = $('#use_gpus').val()
|
let selectedGPUs = $("#use_gpus").val()
|
||||||
autoPickGPUsField.checked = (selectedGPUs.length === 0)
|
autoPickGPUsField.checked = selectedGPUs.length === 0
|
||||||
})
|
})
|
||||||
|
|
||||||
autoPickGPUsField.addEventListener('click', function() {
|
autoPickGPUsField.addEventListener("click", function() {
|
||||||
if (this.checked) {
|
if (this.checked) {
|
||||||
$('#use_gpus').val([])
|
$("#use_gpus").val([])
|
||||||
}
|
}
|
||||||
|
|
||||||
let gpuSettingEntry = getParameterSettingsEntry('use_gpus')
|
let gpuSettingEntry = getParameterSettingsEntry("use_gpus")
|
||||||
gpuSettingEntry.style.display = (this.checked ? 'none' : '')
|
gpuSettingEntry.style.display = this.checked ? "none" : ""
|
||||||
})
|
})
|
||||||
|
|
||||||
async function setDiskPath(defaultDiskPath, force=false) {
|
async function setDiskPath(defaultDiskPath, force = false) {
|
||||||
var diskPath = getSetting("diskPath")
|
var diskPath = getSetting("diskPath")
|
||||||
if (force || diskPath == '' || diskPath == undefined || diskPath == "undefined") {
|
if (force || diskPath == "" || diskPath == undefined || diskPath == "undefined") {
|
||||||
setSetting("diskPath", defaultDiskPath)
|
setSetting("diskPath", defaultDiskPath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function setDeviceInfo(devices) {
|
function setDeviceInfo(devices) {
|
||||||
let cpu = devices.all.cpu.name
|
let cpu = devices.all.cpu.name
|
||||||
let allGPUs = Object.keys(devices.all).filter(d => d != 'cpu')
|
let allGPUs = Object.keys(devices.all).filter((d) => d != "cpu")
|
||||||
let activeGPUs = Object.keys(devices.active)
|
let activeGPUs = Object.keys(devices.active)
|
||||||
|
|
||||||
function ID_TO_TEXT(d) {
|
function ID_TO_TEXT(d) {
|
||||||
let info = devices.all[d]
|
let info = devices.all[d]
|
||||||
if ("mem_free" in info && "mem_total" in info) {
|
if ("mem_free" in info && "mem_total" in info) {
|
||||||
return `${info.name} <small>(${d}) (${info.mem_free.toFixed(1)}Gb free / ${info.mem_total.toFixed(1)} Gb total)</small>`
|
return `${info.name} <small>(${d}) (${info.mem_free.toFixed(1)}Gb free / ${info.mem_total.toFixed(
|
||||||
|
1
|
||||||
|
)} Gb total)</small>`
|
||||||
} else {
|
} else {
|
||||||
return `${info.name} <small>(${d}) (no memory info)</small>`
|
return `${info.name} <small>(${d}) (no memory info)</small>`
|
||||||
}
|
}
|
||||||
@ -510,35 +534,35 @@ function setDeviceInfo(devices) {
|
|||||||
allGPUs = allGPUs.map(ID_TO_TEXT)
|
allGPUs = allGPUs.map(ID_TO_TEXT)
|
||||||
activeGPUs = activeGPUs.map(ID_TO_TEXT)
|
activeGPUs = activeGPUs.map(ID_TO_TEXT)
|
||||||
|
|
||||||
let systemInfoEl = document.querySelector('#system-info')
|
let systemInfoEl = document.querySelector("#system-info")
|
||||||
systemInfoEl.querySelector('#system-info-cpu').innerText = cpu
|
systemInfoEl.querySelector("#system-info-cpu").innerText = cpu
|
||||||
systemInfoEl.querySelector('#system-info-gpus-all').innerHTML = allGPUs.join('</br>')
|
systemInfoEl.querySelector("#system-info-gpus-all").innerHTML = allGPUs.join("</br>")
|
||||||
systemInfoEl.querySelector('#system-info-rendering-devices').innerHTML = activeGPUs.join('</br>')
|
systemInfoEl.querySelector("#system-info-rendering-devices").innerHTML = activeGPUs.join("</br>")
|
||||||
}
|
}
|
||||||
|
|
||||||
function setHostInfo(hosts) {
|
function setHostInfo(hosts) {
|
||||||
let port = listenPortField.value
|
let port = listenPortField.value
|
||||||
hosts = hosts.map(addr => `http://${addr}:${port}/`).map(url => `<div><a href="${url}">${url}</a></div>`)
|
hosts = hosts.map((addr) => `http://${addr}:${port}/`).map((url) => `<div><a href="${url}">${url}</a></div>`)
|
||||||
document.querySelector('#system-info-server-hosts').innerHTML = hosts.join('')
|
document.querySelector("#system-info-server-hosts").innerHTML = hosts.join("")
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getSystemInfo() {
|
async function getSystemInfo() {
|
||||||
try {
|
try {
|
||||||
const res = await SD.getSystemInfo()
|
const res = await SD.getSystemInfo()
|
||||||
let devices = res['devices']
|
let devices = res["devices"]
|
||||||
|
|
||||||
let allDeviceIds = Object.keys(devices['all']).filter(d => d !== 'cpu')
|
let allDeviceIds = Object.keys(devices["all"]).filter((d) => d !== "cpu")
|
||||||
let activeDeviceIds = Object.keys(devices['active']).filter(d => d !== 'cpu')
|
let activeDeviceIds = Object.keys(devices["active"]).filter((d) => d !== "cpu")
|
||||||
|
|
||||||
if (activeDeviceIds.length === 0) {
|
if (activeDeviceIds.length === 0) {
|
||||||
useCPUField.checked = true
|
useCPUField.checked = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if (allDeviceIds.length < MIN_GPUS_TO_SHOW_SELECTION || useCPUField.checked) {
|
if (allDeviceIds.length < MIN_GPUS_TO_SHOW_SELECTION || useCPUField.checked) {
|
||||||
let gpuSettingEntry = getParameterSettingsEntry('use_gpus')
|
let gpuSettingEntry = getParameterSettingsEntry("use_gpus")
|
||||||
gpuSettingEntry.style.display = 'none'
|
gpuSettingEntry.style.display = "none"
|
||||||
let autoPickGPUSettingEntry = getParameterSettingsEntry('auto_pick_gpus')
|
let autoPickGPUSettingEntry = getParameterSettingsEntry("auto_pick_gpus")
|
||||||
autoPickGPUSettingEntry.style.display = 'none'
|
autoPickGPUSettingEntry.style.display = "none"
|
||||||
}
|
}
|
||||||
|
|
||||||
if (allDeviceIds.length === 0) {
|
if (allDeviceIds.length === 0) {
|
||||||
@ -546,27 +570,27 @@ async function getSystemInfo() {
|
|||||||
useCPUField.disabled = true // no compatible GPUs, so make the CPU mandatory
|
useCPUField.disabled = true // no compatible GPUs, so make the CPU mandatory
|
||||||
}
|
}
|
||||||
|
|
||||||
autoPickGPUsField.checked = (devices['config'] === 'auto')
|
autoPickGPUsField.checked = devices["config"] === "auto"
|
||||||
|
|
||||||
useGPUsField.innerHTML = ''
|
useGPUsField.innerHTML = ""
|
||||||
allDeviceIds.forEach(device => {
|
allDeviceIds.forEach((device) => {
|
||||||
let deviceName = devices['all'][device]['name']
|
let deviceName = devices["all"][device]["name"]
|
||||||
let deviceOption = `<option value="${device}">${deviceName} (${device})</option>`
|
let deviceOption = `<option value="${device}">${deviceName} (${device})</option>`
|
||||||
useGPUsField.insertAdjacentHTML('beforeend', deviceOption)
|
useGPUsField.insertAdjacentHTML("beforeend", deviceOption)
|
||||||
})
|
})
|
||||||
|
|
||||||
if (autoPickGPUsField.checked) {
|
if (autoPickGPUsField.checked) {
|
||||||
let gpuSettingEntry = getParameterSettingsEntry('use_gpus')
|
let gpuSettingEntry = getParameterSettingsEntry("use_gpus")
|
||||||
gpuSettingEntry.style.display = 'none'
|
gpuSettingEntry.style.display = "none"
|
||||||
} else {
|
} else {
|
||||||
$('#use_gpus').val(activeDeviceIds)
|
$("#use_gpus").val(activeDeviceIds)
|
||||||
}
|
}
|
||||||
|
|
||||||
setDeviceInfo(devices)
|
setDeviceInfo(devices)
|
||||||
setHostInfo(res['hosts'])
|
setHostInfo(res["hosts"])
|
||||||
let force = false
|
let force = false
|
||||||
if (res['enforce_output_dir'] !== undefined) {
|
if (res["enforce_output_dir"] !== undefined) {
|
||||||
force = res['enforce_output_dir']
|
force = res["enforce_output_dir"]
|
||||||
if (force == true) {
|
if (force == true) {
|
||||||
saveToDiskField.checked = true
|
saveToDiskField.checked = true
|
||||||
metadataOutputFormatField.disabled = false
|
metadataOutputFormatField.disabled = false
|
||||||
@ -574,58 +598,62 @@ async function getSystemInfo() {
|
|||||||
saveToDiskField.disabled = force
|
saveToDiskField.disabled = force
|
||||||
diskPathField.disabled = force
|
diskPathField.disabled = force
|
||||||
}
|
}
|
||||||
setDiskPath(res['default_output_dir'], force)
|
setDiskPath(res["default_output_dir"], force)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log('error fetching devices', e)
|
console.log("error fetching devices", e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
saveSettingsBtn.addEventListener('click', function() {
|
saveSettingsBtn.addEventListener("click", function() {
|
||||||
if (listenPortField.value == '') {
|
if (listenPortField.value == "") {
|
||||||
alert('The network port field must not be empty.')
|
alert("The network port field must not be empty.")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (listenPortField.value < 1 || listenPortField.value > 65535) {
|
if (listenPortField.value < 1 || listenPortField.value > 65535) {
|
||||||
alert('The network port must be a number from 1 to 65535')
|
alert("The network port must be a number from 1 to 65535")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const updateBranch = (useBetaChannelField.checked ? 'beta' : 'main')
|
const updateBranch = useBetaChannelField.checked ? "beta" : "main"
|
||||||
|
|
||||||
const updateAppConfigRequest = {
|
const updateAppConfigRequest = {
|
||||||
'render_devices': getCurrentRenderDeviceSelection(),
|
render_devices: getCurrentRenderDeviceSelection(),
|
||||||
'update_branch': updateBranch,
|
update_branch: updateBranch,
|
||||||
}
|
}
|
||||||
|
|
||||||
Array.from(parametersTable.children).forEach(parameterRow => {
|
Array.from(parametersTable.children).forEach((parameterRow) => {
|
||||||
if (parameterRow.dataset.saveInAppConfig === 'true') {
|
if (parameterRow.dataset.saveInAppConfig === "true") {
|
||||||
const parameterElement = document.getElementById(parameterRow.dataset.settingId) ||
|
const parameterElement =
|
||||||
parameterRow.querySelector('input') || parameterRow.querySelector('select')
|
document.getElementById(parameterRow.dataset.settingId) ||
|
||||||
|
parameterRow.querySelector("input") ||
|
||||||
|
parameterRow.querySelector("select")
|
||||||
|
|
||||||
switch (parameterElement?.tagName) {
|
switch (parameterElement?.tagName) {
|
||||||
case 'INPUT':
|
case "INPUT":
|
||||||
if (parameterElement.type === 'checkbox') {
|
if (parameterElement.type === "checkbox") {
|
||||||
updateAppConfigRequest[parameterRow.dataset.settingId] = parameterElement.checked
|
updateAppConfigRequest[parameterRow.dataset.settingId] = parameterElement.checked
|
||||||
} else {
|
} else {
|
||||||
updateAppConfigRequest[parameterRow.dataset.settingId] = parameterElement.value
|
updateAppConfigRequest[parameterRow.dataset.settingId] = parameterElement.value
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
case 'SELECT':
|
case "SELECT":
|
||||||
if (parameterElement.multiple) {
|
if (parameterElement.multiple) {
|
||||||
updateAppConfigRequest[parameterRow.dataset.settingId] = Array.from(parameterElement.options)
|
updateAppConfigRequest[parameterRow.dataset.settingId] = Array.from(parameterElement.options)
|
||||||
.filter(option => option.selected)
|
.filter((option) => option.selected)
|
||||||
.map(option => option.value || option.text)
|
.map((option) => option.value || option.text)
|
||||||
} else {
|
} else {
|
||||||
updateAppConfigRequest[parameterRow.dataset.settingId] = parameterElement.value
|
updateAppConfigRequest[parameterRow.dataset.settingId] = parameterElement.value
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
console.error(`Setting parameter ${parameterRow.dataset.settingId} couldn't be saved to app.config - element #${parameter.id} is a <${parameterElement?.tagName} /> instead of a <input /> or a <select />!`)
|
console.error(
|
||||||
|
`Setting parameter ${parameterRow.dataset.settingId} couldn't be saved to app.config - element #${parameter.id} is a <${parameterElement?.tagName} /> instead of a <input /> or a <select />!`
|
||||||
|
)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const savePromise = changeAppConfig(updateAppConfigRequest)
|
const savePromise = changeAppConfig(updateAppConfigRequest)
|
||||||
saveSettingsBtn.classList.add('active')
|
saveSettingsBtn.classList.add("active")
|
||||||
Promise.all([savePromise, asyncDelay(300)]).then(() => saveSettingsBtn.classList.remove('active'))
|
Promise.all([savePromise, asyncDelay(300)]).then(() => saveSettingsBtn.classList.remove("active"))
|
||||||
})
|
})
|
||||||
|
@ -29,14 +29,20 @@ const PLUGINS = {
|
|||||||
MODIFIERS_LOAD: [],
|
MODIFIERS_LOAD: [],
|
||||||
TASK_CREATE: [],
|
TASK_CREATE: [],
|
||||||
OUTPUTS_FORMATS: new ServiceContainer(
|
OUTPUTS_FORMATS: new ServiceContainer(
|
||||||
function png() { return (reqBody) => new SD.RenderTask(reqBody) }
|
function png() {
|
||||||
, function jpeg() { return (reqBody) => new SD.RenderTask(reqBody) }
|
return (reqBody) => new SD.RenderTask(reqBody)
|
||||||
, function webp() { return (reqBody) => new SD.RenderTask(reqBody) }
|
},
|
||||||
|
function jpeg() {
|
||||||
|
return (reqBody) => new SD.RenderTask(reqBody)
|
||||||
|
},
|
||||||
|
function webp() {
|
||||||
|
return (reqBody) => new SD.RenderTask(reqBody)
|
||||||
|
}
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
PLUGINS.OUTPUTS_FORMATS.register = function(...args) {
|
PLUGINS.OUTPUTS_FORMATS.register = function(...args) {
|
||||||
const service = ServiceContainer.prototype.register.apply(this, args)
|
const service = ServiceContainer.prototype.register.apply(this, args)
|
||||||
if (typeof outputFormatField !== 'undefined') {
|
if (typeof outputFormatField !== "undefined") {
|
||||||
const newOption = document.createElement("option")
|
const newOption = document.createElement("option")
|
||||||
newOption.setAttribute("value", service.name)
|
newOption.setAttribute("value", service.name)
|
||||||
newOption.innerText = service.name
|
newOption.innerText = service.name
|
||||||
@ -46,13 +52,13 @@ PLUGINS.OUTPUTS_FORMATS.register = function(...args) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function loadScript(url) {
|
function loadScript(url) {
|
||||||
const script = document.createElement('script')
|
const script = document.createElement("script")
|
||||||
const promiseSrc = new PromiseSource()
|
const promiseSrc = new PromiseSource()
|
||||||
script.addEventListener('error', () => promiseSrc.reject(new Error(`Script "${url}" couldn't be loaded.`)))
|
script.addEventListener("error", () => promiseSrc.reject(new Error(`Script "${url}" couldn't be loaded.`)))
|
||||||
script.addEventListener('load', () => promiseSrc.resolve(url))
|
script.addEventListener("load", () => promiseSrc.resolve(url))
|
||||||
script.src = url + '?t=' + Date.now()
|
script.src = url + "?t=" + Date.now()
|
||||||
|
|
||||||
console.log('loading script', url)
|
console.log("loading script", url)
|
||||||
document.head.appendChild(script)
|
document.head.appendChild(script)
|
||||||
|
|
||||||
return promiseSrc.promise
|
return promiseSrc.promise
|
||||||
@ -60,7 +66,7 @@ function loadScript(url) {
|
|||||||
|
|
||||||
async function loadUIPlugins() {
|
async function loadUIPlugins() {
|
||||||
try {
|
try {
|
||||||
const res = await fetch('/get/ui_plugins')
|
const res = await fetch("/get/ui_plugins")
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
console.error(`Error HTTP${res.status} while loading plugins list. - ${res.statusText}`)
|
console.error(`Error HTTP${res.status} while loading plugins list. - ${res.statusText}`)
|
||||||
return
|
return
|
||||||
@ -69,6 +75,6 @@ async function loadUIPlugins() {
|
|||||||
const loadingPromises = plugins.map(loadScript)
|
const loadingPromises = plugins.map(loadScript)
|
||||||
return await Promise.allSettled(loadingPromises)
|
return await Promise.allSettled(loadingPromises)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log('error fetching plugin paths', e)
|
console.log("error fetching plugin paths", e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,8 +21,7 @@ let hypernetworkModelField = new ModelDropdown(document.querySelector('#hypernet
|
|||||||
|
|
||||||
3) Model dropdowns will be refreshed automatically when the reload models button is invoked.
|
3) Model dropdowns will be refreshed automatically when the reload models button is invoked.
|
||||||
*/
|
*/
|
||||||
class ModelDropdown
|
class ModelDropdown {
|
||||||
{
|
|
||||||
modelFilter //= document.querySelector("#model-filter")
|
modelFilter //= document.querySelector("#model-filter")
|
||||||
modelFilterArrow //= document.querySelector("#model-filter-arrow")
|
modelFilterArrow //= document.querySelector("#model-filter-arrow")
|
||||||
modelList //= document.querySelector("#model-list")
|
modelList //= document.querySelector("#model-list")
|
||||||
@ -59,11 +58,11 @@ class ModelDropdown
|
|||||||
set disabled(state) {
|
set disabled(state) {
|
||||||
this.modelFilter.disabled = state
|
this.modelFilter.disabled = state
|
||||||
if (this.modelFilterArrow) {
|
if (this.modelFilterArrow) {
|
||||||
this.modelFilterArrow.style.color = state ? 'dimgray' : ''
|
this.modelFilterArrow.style.color = state ? "dimgray" : ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
get modelElements() {
|
get modelElements() {
|
||||||
return this.modelList.querySelectorAll('.model-file')
|
return this.modelList.querySelectorAll(".model-file")
|
||||||
}
|
}
|
||||||
addEventListener(type, listener, options) {
|
addEventListener(type, listener, options) {
|
||||||
return this.modelFilter.addEventListener(type, listener, options)
|
return this.modelFilter.addEventListener(type, listener, options)
|
||||||
@ -83,20 +82,25 @@ class ModelDropdown
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* SEARCHABLE INPUT */
|
/* SEARCHABLE INPUT */
|
||||||
constructor (input, modelKey, noneEntry = '') {
|
|
||||||
|
constructor(input, modelKey, noneEntry = "") {
|
||||||
this.modelFilter = input
|
this.modelFilter = input
|
||||||
this.noneEntry = noneEntry
|
this.noneEntry = noneEntry
|
||||||
this.modelKey = modelKey
|
this.modelKey = modelKey
|
||||||
|
|
||||||
if (modelsOptions !== undefined) { // reuse models from cache (only useful for plugins, which are loaded after models)
|
if (modelsOptions !== undefined) {
|
||||||
|
// reuse models from cache (only useful for plugins, which are loaded after models)
|
||||||
this.inputModels = modelsOptions[this.modelKey]
|
this.inputModels = modelsOptions[this.modelKey]
|
||||||
this.populateModels()
|
this.populateModels()
|
||||||
}
|
}
|
||||||
document.addEventListener("refreshModels", this.bind(function(e) {
|
document.addEventListener(
|
||||||
|
"refreshModels",
|
||||||
|
this.bind(function(e) {
|
||||||
// reload the models
|
// reload the models
|
||||||
this.inputModels = modelsOptions[this.modelKey]
|
this.inputModels = modelsOptions[this.modelKey]
|
||||||
this.populateModels()
|
this.populateModels()
|
||||||
}, this))
|
}, this)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
saveCurrentSelection(elem, value, path) {
|
saveCurrentSelection(elem, value, path) {
|
||||||
@ -105,13 +109,13 @@ class ModelDropdown
|
|||||||
this.currentSelection.path = path
|
this.currentSelection.path = path
|
||||||
this.modelFilter.dataset.path = path
|
this.modelFilter.dataset.path = path
|
||||||
this.modelFilter.value = value
|
this.modelFilter.value = value
|
||||||
this.modelFilter.dispatchEvent(new Event('change'))
|
this.modelFilter.dispatchEvent(new Event("change"))
|
||||||
}
|
}
|
||||||
|
|
||||||
processClick(e) {
|
processClick(e) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
if (e.srcElement.classList.contains('model-file') || e.srcElement.classList.contains('fa-file')) {
|
if (e.srcElement.classList.contains("model-file") || e.srcElement.classList.contains("fa-file")) {
|
||||||
const elem = e.srcElement.classList.contains('model-file') ? e.srcElement : e.srcElement.parentElement
|
const elem = e.srcElement.classList.contains("model-file") ? e.srcElement : e.srcElement.parentElement
|
||||||
this.saveCurrentSelection(elem, elem.innerText, elem.dataset.path)
|
this.saveCurrentSelection(elem, elem.innerText, elem.dataset.path)
|
||||||
this.hideModelList()
|
this.hideModelList()
|
||||||
this.modelFilter.focus()
|
this.modelFilter.focus()
|
||||||
@ -126,35 +130,38 @@ class ModelDropdown
|
|||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
return modelElements.slice(0, index).reverse().find(e => e.style.display === 'list-item')
|
return modelElements
|
||||||
|
.slice(0, index)
|
||||||
|
.reverse()
|
||||||
|
.find((e) => e.style.display === "list-item")
|
||||||
}
|
}
|
||||||
|
|
||||||
getLastVisibleChild(elem) {
|
getLastVisibleChild(elem) {
|
||||||
let lastElementChild = elem.lastElementChild
|
let lastElementChild = elem.lastElementChild
|
||||||
if (lastElementChild.style.display == 'list-item') return lastElementChild
|
if (lastElementChild.style.display == "list-item") return lastElementChild
|
||||||
return this.getPreviousVisibleSibling(lastElementChild)
|
return this.getPreviousVisibleSibling(lastElementChild)
|
||||||
}
|
}
|
||||||
|
|
||||||
getNextVisibleSibling(elem) {
|
getNextVisibleSibling(elem) {
|
||||||
const modelElements = Array.from(this.modelElements)
|
const modelElements = Array.from(this.modelElements)
|
||||||
const index = modelElements.indexOf(elem)
|
const index = modelElements.indexOf(elem)
|
||||||
return modelElements.slice(index + 1).find(e => e.style.display === 'list-item')
|
return modelElements.slice(index + 1).find((e) => e.style.display === "list-item")
|
||||||
}
|
}
|
||||||
|
|
||||||
getFirstVisibleChild(elem) {
|
getFirstVisibleChild(elem) {
|
||||||
let firstElementChild = elem.firstElementChild
|
let firstElementChild = elem.firstElementChild
|
||||||
if (firstElementChild.style.display == 'list-item') return firstElementChild
|
if (firstElementChild.style.display == "list-item") return firstElementChild
|
||||||
return this.getNextVisibleSibling(firstElementChild)
|
return this.getNextVisibleSibling(firstElementChild)
|
||||||
}
|
}
|
||||||
|
|
||||||
selectModelEntry(elem) {
|
selectModelEntry(elem) {
|
||||||
if (elem) {
|
if (elem) {
|
||||||
if (this.highlightedModelEntry !== undefined) {
|
if (this.highlightedModelEntry !== undefined) {
|
||||||
this.highlightedModelEntry.classList.remove('selected')
|
this.highlightedModelEntry.classList.remove("selected")
|
||||||
}
|
}
|
||||||
this.saveCurrentSelection(elem, elem.innerText, elem.dataset.path)
|
this.saveCurrentSelection(elem, elem.innerText, elem.dataset.path)
|
||||||
elem.classList.add('selected')
|
elem.classList.add("selected")
|
||||||
elem.scrollIntoView({block: 'nearest'})
|
elem.scrollIntoView({ block: "nearest" })
|
||||||
this.highlightedModelEntry = elem
|
this.highlightedModelEntry = elem
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -163,11 +170,9 @@ class ModelDropdown
|
|||||||
const elem = this.getPreviousVisibleSibling(this.highlightedModelEntry)
|
const elem = this.getPreviousVisibleSibling(this.highlightedModelEntry)
|
||||||
if (elem) {
|
if (elem) {
|
||||||
this.selectModelEntry(elem)
|
this.selectModelEntry(elem)
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
//this.highlightedModelEntry.parentElement.parentElement.scrollIntoView({block: 'nearest'})
|
//this.highlightedModelEntry.parentElement.parentElement.scrollIntoView({block: 'nearest'})
|
||||||
this.highlightedModelEntry.closest('.model-list').scrollTop = 0
|
this.highlightedModelEntry.closest(".model-list").scrollTop = 0
|
||||||
}
|
}
|
||||||
this.modelFilter.select()
|
this.modelFilter.select()
|
||||||
}
|
}
|
||||||
@ -178,14 +183,14 @@ class ModelDropdown
|
|||||||
}
|
}
|
||||||
|
|
||||||
selectFirstFile() {
|
selectFirstFile() {
|
||||||
this.selectModelEntry(this.modelList.querySelector('.model-file'))
|
this.selectModelEntry(this.modelList.querySelector(".model-file"))
|
||||||
this.highlightedModelEntry.scrollIntoView({block: 'nearest'})
|
this.highlightedModelEntry.scrollIntoView({ block: "nearest" })
|
||||||
this.modelFilter.select()
|
this.modelFilter.select()
|
||||||
}
|
}
|
||||||
|
|
||||||
selectLastFile() {
|
selectLastFile() {
|
||||||
const elems = this.modelList.querySelectorAll('.model-file:last-child')
|
const elems = this.modelList.querySelectorAll(".model-file:last-child")
|
||||||
this.selectModelEntry(elems[elems.length -1])
|
this.selectModelEntry(elems[elems.length - 1])
|
||||||
this.modelFilter.select()
|
this.modelFilter.select()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -198,57 +203,57 @@ class ModelDropdown
|
|||||||
}
|
}
|
||||||
|
|
||||||
validEntrySelected() {
|
validEntrySelected() {
|
||||||
return (this.modelNoResult.style.display === 'none')
|
return this.modelNoResult.style.display === "none"
|
||||||
}
|
}
|
||||||
|
|
||||||
processKey(e) {
|
processKey(e) {
|
||||||
switch (e.key) {
|
switch (e.key) {
|
||||||
case 'Escape':
|
case "Escape":
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
this.resetSelection()
|
this.resetSelection()
|
||||||
break
|
break
|
||||||
case 'Enter':
|
case "Enter":
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
if (this.validEntrySelected()) {
|
if (this.validEntrySelected()) {
|
||||||
if (this.modelList.style.display != 'block') {
|
if (this.modelList.style.display != "block") {
|
||||||
this.showModelList()
|
this.showModelList()
|
||||||
}
|
} else {
|
||||||
else
|
this.saveCurrentSelection(
|
||||||
{
|
this.highlightedModelEntry,
|
||||||
this.saveCurrentSelection(this.highlightedModelEntry, this.highlightedModelEntry.innerText, this.highlightedModelEntry.dataset.path)
|
this.highlightedModelEntry.innerText,
|
||||||
|
this.highlightedModelEntry.dataset.path
|
||||||
|
)
|
||||||
this.hideModelList()
|
this.hideModelList()
|
||||||
this.showAllEntries()
|
this.showAllEntries()
|
||||||
}
|
}
|
||||||
this.modelFilter.focus()
|
this.modelFilter.focus()
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
this.resetSelection()
|
this.resetSelection()
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
case 'ArrowUp':
|
case "ArrowUp":
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
if (this.validEntrySelected()) {
|
if (this.validEntrySelected()) {
|
||||||
this.selectPreviousFile()
|
this.selectPreviousFile()
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
case 'ArrowDown':
|
case "ArrowDown":
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
if (this.validEntrySelected()) {
|
if (this.validEntrySelected()) {
|
||||||
this.selectNextFile()
|
this.selectNextFile()
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
case 'ArrowLeft':
|
case "ArrowLeft":
|
||||||
if (this.modelList.style.display != 'block') {
|
if (this.modelList.style.display != "block") {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
case 'ArrowRight':
|
case "ArrowRight":
|
||||||
if (this.modelList.style.display != 'block') {
|
if (this.modelList.style.display != "block") {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
case 'PageUp':
|
case "PageUp":
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
if (this.validEntrySelected()) {
|
if (this.validEntrySelected()) {
|
||||||
this.selectPreviousFile()
|
this.selectPreviousFile()
|
||||||
@ -261,7 +266,7 @@ class ModelDropdown
|
|||||||
this.selectPreviousFile()
|
this.selectPreviousFile()
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
case 'PageDown':
|
case "PageDown":
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
if (this.validEntrySelected()) {
|
if (this.validEntrySelected()) {
|
||||||
this.selectNextFile()
|
this.selectNextFile()
|
||||||
@ -274,7 +279,7 @@ class ModelDropdown
|
|||||||
this.selectNextFile()
|
this.selectNextFile()
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
case 'Home':
|
case "Home":
|
||||||
//if (this.modelList.style.display != 'block') {
|
//if (this.modelList.style.display != 'block') {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
if (this.validEntrySelected()) {
|
if (this.validEntrySelected()) {
|
||||||
@ -282,7 +287,7 @@ class ModelDropdown
|
|||||||
}
|
}
|
||||||
//}
|
//}
|
||||||
break
|
break
|
||||||
case 'End':
|
case "End":
|
||||||
//if (this.modelList.style.display != 'block') {
|
//if (this.modelList.style.display != 'block') {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
if (this.validEntrySelected()) {
|
if (this.validEntrySelected()) {
|
||||||
@ -301,29 +306,27 @@ class ModelDropdown
|
|||||||
}
|
}
|
||||||
|
|
||||||
showModelList() {
|
showModelList() {
|
||||||
this.modelList.style.display = 'block'
|
this.modelList.style.display = "block"
|
||||||
this.selectEntry()
|
this.selectEntry()
|
||||||
this.showAllEntries()
|
this.showAllEntries()
|
||||||
//this.modelFilter.value = ''
|
//this.modelFilter.value = ''
|
||||||
this.modelFilter.select() // preselect the entire string so user can just start typing.
|
this.modelFilter.select() // preselect the entire string so user can just start typing.
|
||||||
this.modelFilter.focus()
|
this.modelFilter.focus()
|
||||||
this.modelFilter.style.cursor = 'auto'
|
this.modelFilter.style.cursor = "auto"
|
||||||
}
|
}
|
||||||
|
|
||||||
hideModelList() {
|
hideModelList() {
|
||||||
this.modelList.style.display = 'none'
|
this.modelList.style.display = "none"
|
||||||
this.modelFilter.value = this.currentSelection.value
|
this.modelFilter.value = this.currentSelection.value
|
||||||
this.modelFilter.style.cursor = ''
|
this.modelFilter.style.cursor = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleModelList(e) {
|
toggleModelList(e) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
if (!this.modelFilter.disabled) {
|
if (!this.modelFilter.disabled) {
|
||||||
if (this.modelList.style.display != 'block') {
|
if (this.modelList.style.display != "block") {
|
||||||
this.showModelList()
|
this.showModelList()
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
this.hideModelList()
|
this.hideModelList()
|
||||||
this.modelFilter.select()
|
this.modelFilter.select()
|
||||||
}
|
}
|
||||||
@ -332,13 +335,13 @@ class ModelDropdown
|
|||||||
|
|
||||||
selectEntry(path) {
|
selectEntry(path) {
|
||||||
if (path !== undefined) {
|
if (path !== undefined) {
|
||||||
const entries = this.modelElements;
|
const entries = this.modelElements
|
||||||
|
|
||||||
for (const elem of entries) {
|
for (const elem of entries) {
|
||||||
if (elem.dataset.path == path) {
|
if (elem.dataset.path == path) {
|
||||||
this.saveCurrentSelection(elem, elem.innerText, elem.dataset.path)
|
this.saveCurrentSelection(elem, elem.innerText, elem.dataset.path)
|
||||||
this.highlightedModelEntry = elem
|
this.highlightedModelEntry = elem
|
||||||
elem.scrollIntoView({block: 'nearest'})
|
elem.scrollIntoView({ block: "nearest" })
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -347,14 +350,12 @@ class ModelDropdown
|
|||||||
if (this.currentSelection.elem !== undefined) {
|
if (this.currentSelection.elem !== undefined) {
|
||||||
// select the previous element
|
// select the previous element
|
||||||
if (this.highlightedModelEntry !== undefined && this.highlightedModelEntry != this.currentSelection.elem) {
|
if (this.highlightedModelEntry !== undefined && this.highlightedModelEntry != this.currentSelection.elem) {
|
||||||
this.highlightedModelEntry.classList.remove('selected')
|
this.highlightedModelEntry.classList.remove("selected")
|
||||||
}
|
}
|
||||||
this.currentSelection.elem.classList.add('selected')
|
this.currentSelection.elem.classList.add("selected")
|
||||||
this.highlightedModelEntry = this.currentSelection.elem
|
this.highlightedModelEntry = this.currentSelection.elem
|
||||||
this.currentSelection.elem.scrollIntoView({block: 'nearest'})
|
this.currentSelection.elem.scrollIntoView({ block: "nearest" })
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
this.selectFirstFile()
|
this.selectFirstFile()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -362,28 +363,28 @@ class ModelDropdown
|
|||||||
highlightModelAtPosition(e) {
|
highlightModelAtPosition(e) {
|
||||||
let elem = document.elementFromPoint(e.clientX, e.clientY)
|
let elem = document.elementFromPoint(e.clientX, e.clientY)
|
||||||
|
|
||||||
if (elem.classList.contains('model-file')) {
|
if (elem.classList.contains("model-file")) {
|
||||||
this.highlightModel(elem)
|
this.highlightModel(elem)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
highlightModel(elem) {
|
highlightModel(elem) {
|
||||||
if (elem.classList.contains('model-file')) {
|
if (elem.classList.contains("model-file")) {
|
||||||
if (this.highlightedModelEntry !== undefined && this.highlightedModelEntry != elem) {
|
if (this.highlightedModelEntry !== undefined && this.highlightedModelEntry != elem) {
|
||||||
this.highlightedModelEntry.classList.remove('selected')
|
this.highlightedModelEntry.classList.remove("selected")
|
||||||
}
|
}
|
||||||
elem.classList.add('selected')
|
elem.classList.add("selected")
|
||||||
this.highlightedModelEntry = elem
|
this.highlightedModelEntry = elem
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
showAllEntries() {
|
showAllEntries() {
|
||||||
this.modelList.querySelectorAll('li').forEach(function(li) {
|
this.modelList.querySelectorAll("li").forEach(function(li) {
|
||||||
if (li.id !== 'model-no-result') {
|
if (li.id !== "model-no-result") {
|
||||||
li.style.display = 'list-item'
|
li.style.display = "list-item"
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
this.modelNoResult.style.display = 'none'
|
this.modelNoResult.style.display = "none"
|
||||||
}
|
}
|
||||||
|
|
||||||
filterList(e) {
|
filterList(e) {
|
||||||
@ -391,37 +392,35 @@ class ModelDropdown
|
|||||||
let found = false
|
let found = false
|
||||||
let showAllChildren = false
|
let showAllChildren = false
|
||||||
|
|
||||||
this.modelList.querySelectorAll('li').forEach(function(li) {
|
this.modelList.querySelectorAll("li").forEach(function(li) {
|
||||||
if (li.classList.contains('model-folder')) {
|
if (li.classList.contains("model-folder")) {
|
||||||
showAllChildren = false
|
showAllChildren = false
|
||||||
}
|
}
|
||||||
if (filter == '') {
|
if (filter == "") {
|
||||||
li.style.display = 'list-item'
|
li.style.display = "list-item"
|
||||||
found = true
|
found = true
|
||||||
} else if (showAllChildren || li.textContent.toLowerCase().match(filter)) {
|
} else if (showAllChildren || li.textContent.toLowerCase().match(filter)) {
|
||||||
li.style.display = 'list-item'
|
li.style.display = "list-item"
|
||||||
if (li.classList.contains('model-folder') && li.firstChild.textContent.toLowerCase().match(filter)) {
|
if (li.classList.contains("model-folder") && li.firstChild.textContent.toLowerCase().match(filter)) {
|
||||||
showAllChildren = true
|
showAllChildren = true
|
||||||
}
|
}
|
||||||
found = true
|
found = true
|
||||||
} else {
|
} else {
|
||||||
li.style.display = 'none'
|
li.style.display = "none"
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
if (found) {
|
if (found) {
|
||||||
this.modelResult.style.display = 'list-item'
|
this.modelResult.style.display = "list-item"
|
||||||
this.modelNoResult.style.display = 'none'
|
this.modelNoResult.style.display = "none"
|
||||||
const elem = this.getNextVisibleSibling(this.modelList.querySelector('.model-file'))
|
const elem = this.getNextVisibleSibling(this.modelList.querySelector(".model-file"))
|
||||||
this.highlightModel(elem)
|
this.highlightModel(elem)
|
||||||
elem.scrollIntoView({block: 'nearest'})
|
elem.scrollIntoView({ block: "nearest" })
|
||||||
|
} else {
|
||||||
|
this.modelResult.style.display = "none"
|
||||||
|
this.modelNoResult.style.display = "list-item"
|
||||||
}
|
}
|
||||||
else
|
this.modelList.style.display = "block"
|
||||||
{
|
|
||||||
this.modelResult.style.display = 'none'
|
|
||||||
this.modelNoResult.style.display = 'list-item'
|
|
||||||
}
|
|
||||||
this.modelList.style.display = 'block'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* MODEL LOADER */
|
/* MODEL LOADER */
|
||||||
@ -458,17 +457,17 @@ class ModelDropdown
|
|||||||
* @param {Array<string>} models
|
* @param {Array<string>} models
|
||||||
*/
|
*/
|
||||||
sortStringArray(models) {
|
sortStringArray(models) {
|
||||||
models.sort((a, b) => a.localeCompare(b, undefined, { sensitivity: 'base' }))
|
models.sort((a, b) => a.localeCompare(b, undefined, { sensitivity: "base" }))
|
||||||
}
|
}
|
||||||
|
|
||||||
populateModels() {
|
populateModels() {
|
||||||
this.activeModel = this.modelFilter.dataset.path
|
this.activeModel = this.modelFilter.dataset.path
|
||||||
|
|
||||||
this.currentSelection = { elem: undefined, value: '', path: ''}
|
this.currentSelection = { elem: undefined, value: "", path: "" }
|
||||||
this.highlightedModelEntry = undefined
|
this.highlightedModelEntry = undefined
|
||||||
this.flatModelList = []
|
this.flatModelList = []
|
||||||
|
|
||||||
if(this.modelList !== undefined) {
|
if (this.modelList !== undefined) {
|
||||||
this.modelList.remove()
|
this.modelList.remove()
|
||||||
this.modelFilterArrow.remove()
|
this.modelFilterArrow.remove()
|
||||||
}
|
}
|
||||||
@ -478,39 +477,39 @@ class ModelDropdown
|
|||||||
createDropdown() {
|
createDropdown() {
|
||||||
// create dropdown entries
|
// create dropdown entries
|
||||||
let rootModelList = this.createRootModelList(this.inputModels)
|
let rootModelList = this.createRootModelList(this.inputModels)
|
||||||
this.modelFilter.insertAdjacentElement('afterend', rootModelList)
|
this.modelFilter.insertAdjacentElement("afterend", rootModelList)
|
||||||
this.modelFilter.insertAdjacentElement(
|
this.modelFilter.insertAdjacentElement(
|
||||||
'afterend',
|
"afterend",
|
||||||
createElement(
|
createElement("i", { id: `${this.modelFilter.id}-model-filter-arrow` }, [
|
||||||
'i',
|
"model-selector-arrow",
|
||||||
{ id: `${this.modelFilter.id}-model-filter-arrow` },
|
"fa-solid",
|
||||||
['model-selector-arrow', 'fa-solid', 'fa-angle-down'],
|
"fa-angle-down",
|
||||||
),
|
])
|
||||||
)
|
)
|
||||||
this.modelFilter.classList.add('model-selector')
|
this.modelFilter.classList.add("model-selector")
|
||||||
this.modelFilterArrow = document.querySelector(`#${this.modelFilter.id}-model-filter-arrow`)
|
this.modelFilterArrow = document.querySelector(`#${this.modelFilter.id}-model-filter-arrow`)
|
||||||
if (this.modelFilterArrow) {
|
if (this.modelFilterArrow) {
|
||||||
this.modelFilterArrow.style.color = this.modelFilter.disabled ? 'dimgray' : ''
|
this.modelFilterArrow.style.color = this.modelFilter.disabled ? "dimgray" : ""
|
||||||
}
|
}
|
||||||
this.modelList = document.querySelector(`#${this.modelFilter.id}-model-list`)
|
this.modelList = document.querySelector(`#${this.modelFilter.id}-model-list`)
|
||||||
this.modelResult = document.querySelector(`#${this.modelFilter.id}-model-result`)
|
this.modelResult = document.querySelector(`#${this.modelFilter.id}-model-result`)
|
||||||
this.modelNoResult = document.querySelector(`#${this.modelFilter.id}-model-no-result`)
|
this.modelNoResult = document.querySelector(`#${this.modelFilter.id}-model-no-result`)
|
||||||
|
|
||||||
if (this.modelFilterInitialized !== true) {
|
if (this.modelFilterInitialized !== true) {
|
||||||
this.modelFilter.addEventListener('input', this.bind(this.filterList, this))
|
this.modelFilter.addEventListener("input", this.bind(this.filterList, this))
|
||||||
this.modelFilter.addEventListener('focus', this.bind(this.modelListFocus, this))
|
this.modelFilter.addEventListener("focus", this.bind(this.modelListFocus, this))
|
||||||
this.modelFilter.addEventListener('blur', this.bind(this.hideModelList, this))
|
this.modelFilter.addEventListener("blur", this.bind(this.hideModelList, this))
|
||||||
this.modelFilter.addEventListener('click', this.bind(this.showModelList, this))
|
this.modelFilter.addEventListener("click", this.bind(this.showModelList, this))
|
||||||
this.modelFilter.addEventListener('keydown', this.bind(this.processKey, this))
|
this.modelFilter.addEventListener("keydown", this.bind(this.processKey, this))
|
||||||
|
|
||||||
this.modelFilterInitialized = true
|
this.modelFilterInitialized = true
|
||||||
}
|
}
|
||||||
this.modelFilterArrow.addEventListener('mousedown', this.bind(this.toggleModelList, this))
|
this.modelFilterArrow.addEventListener("mousedown", this.bind(this.toggleModelList, this))
|
||||||
this.modelList.addEventListener('mousemove', this.bind(this.highlightModelAtPosition, this))
|
this.modelList.addEventListener("mousemove", this.bind(this.highlightModelAtPosition, this))
|
||||||
this.modelList.addEventListener('mousedown', this.bind(this.processClick, this))
|
this.modelList.addEventListener("mousedown", this.bind(this.processClick, this))
|
||||||
|
|
||||||
let mf = this.modelFilter
|
let mf = this.modelFilter
|
||||||
this.modelFilter.addEventListener('focus', function() {
|
this.modelFilter.addEventListener("focus", function() {
|
||||||
let modelFilterStyle = window.getComputedStyle(mf)
|
let modelFilterStyle = window.getComputedStyle(mf)
|
||||||
rootModelList.style.minWidth = modelFilterStyle.width
|
rootModelList.style.minWidth = modelFilterStyle.width
|
||||||
})
|
})
|
||||||
@ -525,69 +524,57 @@ class ModelDropdown
|
|||||||
* @returns {HTMLElement}
|
* @returns {HTMLElement}
|
||||||
*/
|
*/
|
||||||
createModelNodeList(folderName, modelTree, isRootFolder) {
|
createModelNodeList(folderName, modelTree, isRootFolder) {
|
||||||
const listElement = createElement('ul')
|
const listElement = createElement("ul")
|
||||||
|
|
||||||
const foldersMap = new Map()
|
const foldersMap = new Map()
|
||||||
const modelsMap = new Map()
|
const modelsMap = new Map()
|
||||||
|
|
||||||
modelTree.forEach(model => {
|
modelTree.forEach((model) => {
|
||||||
if (Array.isArray(model)) {
|
if (Array.isArray(model)) {
|
||||||
const [childFolderName, childModels] = model
|
const [childFolderName, childModels] = model
|
||||||
foldersMap.set(
|
foldersMap.set(
|
||||||
childFolderName,
|
childFolderName,
|
||||||
this.createModelNodeList(
|
this.createModelNodeList(`${folderName || ""}/${childFolderName}`, childModels, false)
|
||||||
`${folderName || ''}/${childFolderName}`,
|
|
||||||
childModels,
|
|
||||||
false,
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
const classes = ['model-file']
|
const classes = ["model-file"]
|
||||||
if (isRootFolder) {
|
if (isRootFolder) {
|
||||||
classes.push('in-root-folder')
|
classes.push("in-root-folder")
|
||||||
}
|
}
|
||||||
// Remove the leading slash from the model path
|
// Remove the leading slash from the model path
|
||||||
const fullPath = folderName ? `${folderName.substring(1)}/${model}` : model
|
const fullPath = folderName ? `${folderName.substring(1)}/${model}` : model
|
||||||
modelsMap.set(
|
modelsMap.set(
|
||||||
model,
|
model,
|
||||||
createElement(
|
createElement("li", { "data-path": fullPath }, classes, [
|
||||||
'li',
|
createElement("i", undefined, ["fa-regular", "fa-file", "icon"]),
|
||||||
{ 'data-path': fullPath },
|
|
||||||
classes,
|
|
||||||
[
|
|
||||||
createElement('i', undefined, ['fa-regular', 'fa-file', 'icon']),
|
|
||||||
model,
|
model,
|
||||||
],
|
])
|
||||||
),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const childFolderNames = Array.from(foldersMap.keys())
|
const childFolderNames = Array.from(foldersMap.keys())
|
||||||
this.sortStringArray(childFolderNames)
|
this.sortStringArray(childFolderNames)
|
||||||
const folderElements = childFolderNames.map(name => foldersMap.get(name))
|
const folderElements = childFolderNames.map((name) => foldersMap.get(name))
|
||||||
|
|
||||||
const modelNames = Array.from(modelsMap.keys())
|
const modelNames = Array.from(modelsMap.keys())
|
||||||
this.sortStringArray(modelNames)
|
this.sortStringArray(modelNames)
|
||||||
const modelElements = modelNames.map(name => modelsMap.get(name))
|
const modelElements = modelNames.map((name) => modelsMap.get(name))
|
||||||
|
|
||||||
if (modelElements.length && folderName) {
|
if (modelElements.length && folderName) {
|
||||||
listElement.appendChild(
|
listElement.appendChild(
|
||||||
createElement(
|
createElement(
|
||||||
'li',
|
"li",
|
||||||
undefined,
|
undefined,
|
||||||
['model-folder'],
|
["model-folder"],
|
||||||
[
|
[createElement("i", undefined, ["fa-regular", "fa-folder-open", "icon"]), folderName.substring(1)]
|
||||||
createElement('i', undefined, ['fa-regular', 'fa-folder-open', 'icon']),
|
|
||||||
folderName.substring(1),
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// const allModelElements = isRootFolder ? [...folderElements, ...modelElements] : [...modelElements, ...folderElements]
|
// const allModelElements = isRootFolder ? [...folderElements, ...modelElements] : [...modelElements, ...folderElements]
|
||||||
const allModelElements = [...modelElements, ...folderElements]
|
const allModelElements = [...modelElements, ...folderElements]
|
||||||
allModelElements.forEach(e => listElement.appendChild(e))
|
allModelElements.forEach((e) => listElement.appendChild(e))
|
||||||
return listElement
|
return listElement
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -596,37 +583,21 @@ class ModelDropdown
|
|||||||
* @returns {HTMLElement}
|
* @returns {HTMLElement}
|
||||||
*/
|
*/
|
||||||
createRootModelList(modelTree) {
|
createRootModelList(modelTree) {
|
||||||
const rootList = createElement(
|
const rootList = createElement("ul", { id: `${this.modelFilter.id}-model-list` }, ["model-list"])
|
||||||
'ul',
|
|
||||||
{ id: `${this.modelFilter.id}-model-list` },
|
|
||||||
['model-list'],
|
|
||||||
)
|
|
||||||
rootList.appendChild(
|
rootList.appendChild(
|
||||||
createElement(
|
createElement("li", { id: `${this.modelFilter.id}-model-no-result` }, ["model-no-result"], "No result")
|
||||||
'li',
|
|
||||||
{ id: `${this.modelFilter.id}-model-no-result` },
|
|
||||||
['model-no-result'],
|
|
||||||
'No result'
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if (this.noneEntry) {
|
if (this.noneEntry) {
|
||||||
rootList.appendChild(
|
rootList.appendChild(
|
||||||
createElement(
|
createElement("li", { "data-path": "" }, ["model-file", "in-root-folder"], this.noneEntry)
|
||||||
'li',
|
|
||||||
{ 'data-path': '' },
|
|
||||||
['model-file', 'in-root-folder'],
|
|
||||||
this.noneEntry,
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (modelTree.length > 0) {
|
if (modelTree.length > 0) {
|
||||||
const containerListItem = createElement(
|
const containerListItem = createElement("li", { id: `${this.modelFilter.id}-model-result` }, [
|
||||||
'li',
|
"model-result",
|
||||||
{ id: `${this.modelFilter.id}-model-result` },
|
])
|
||||||
['model-result'],
|
|
||||||
)
|
|
||||||
//console.log(containerListItem)
|
//console.log(containerListItem)
|
||||||
containerListItem.appendChild(this.createModelNodeList(undefined, modelTree, true))
|
containerListItem.appendChild(this.createModelNodeList(undefined, modelTree, true))
|
||||||
rootList.appendChild(containerListItem)
|
rootList.appendChild(containerListItem)
|
||||||
@ -640,13 +611,16 @@ class ModelDropdown
|
|||||||
async function getModels() {
|
async function getModels() {
|
||||||
try {
|
try {
|
||||||
modelsCache = await SD.getModels()
|
modelsCache = await SD.getModels()
|
||||||
modelsOptions = modelsCache['options']
|
modelsOptions = modelsCache["options"]
|
||||||
if ("scan-error" in modelsCache) {
|
if ("scan-error" in modelsCache) {
|
||||||
// let previewPane = document.getElementById('tab-content-wrapper')
|
// let previewPane = document.getElementById('tab-content-wrapper')
|
||||||
let previewPane = document.getElementById('preview')
|
let previewPane = document.getElementById("preview")
|
||||||
previewPane.style.background="red"
|
previewPane.style.background = "red"
|
||||||
previewPane.style.textAlign="center"
|
previewPane.style.textAlign = "center"
|
||||||
previewPane.innerHTML = '<H1>🔥Malware alert!🔥</H1><h2>The file <i>' + modelsCache['scan-error'] + '</i> in your <tt>models/stable-diffusion</tt> folder is probably malware infected.</h2><h2>Please delete this file from the folder before proceeding!</h2>After deleting the file, reload this page.<br><br><button onClick="window.location.reload();">Reload Page</button>'
|
previewPane.innerHTML =
|
||||||
|
"<H1>🔥Malware alert!🔥</H1><h2>The file <i>" +
|
||||||
|
modelsCache["scan-error"] +
|
||||||
|
'</i> in your <tt>models/stable-diffusion</tt> folder is probably malware infected.</h2><h2>Please delete this file from the folder before proceeding!</h2>After deleting the file, reload this page.<br><br><button onClick="window.location.reload();">Reload Page</button>'
|
||||||
makeImageBtn.disabled = true
|
makeImageBtn.disabled = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -667,11 +641,11 @@ async function getModels() {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
// notify ModelDropdown objects to refresh
|
// notify ModelDropdown objects to refresh
|
||||||
document.dispatchEvent(new Event('refreshModels'))
|
document.dispatchEvent(new Event("refreshModels"))
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log('get models error', e)
|
console.log("get models error", e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// reload models button
|
// reload models button
|
||||||
document.querySelector('#reload-models').addEventListener('click', getModels)
|
document.querySelector("#reload-models").addEventListener("click", getModels)
|
||||||
|
@ -1,82 +1,85 @@
|
|||||||
const themeField = document.getElementById("theme");
|
const themeField = document.getElementById("theme")
|
||||||
var DEFAULT_THEME = {};
|
var DEFAULT_THEME = {}
|
||||||
var THEMES = []; // initialized in initTheme from data in css
|
var THEMES = [] // initialized in initTheme from data in css
|
||||||
|
|
||||||
function getThemeName(theme) {
|
function getThemeName(theme) {
|
||||||
theme = theme.replace("theme-", "");
|
theme = theme.replace("theme-", "")
|
||||||
theme = theme.split("-").map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
theme = theme
|
||||||
return theme;
|
.split("-")
|
||||||
|
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
||||||
|
.join(" ")
|
||||||
|
return theme
|
||||||
}
|
}
|
||||||
// init themefield
|
// init themefield
|
||||||
function initTheme() {
|
function initTheme() {
|
||||||
Array.from(document.styleSheets)
|
Array.from(document.styleSheets)
|
||||||
.filter(sheet => sheet.href?.startsWith(window.location.origin))
|
.filter((sheet) => sheet.href?.startsWith(window.location.origin))
|
||||||
.flatMap(sheet => Array.from(sheet.cssRules))
|
.flatMap((sheet) => Array.from(sheet.cssRules))
|
||||||
.forEach(rule => {
|
.forEach((rule) => {
|
||||||
var selector = rule.selectorText;
|
var selector = rule.selectorText
|
||||||
if (selector && selector.startsWith(".theme-") && !selector.includes(" ")) {
|
if (selector && selector.startsWith(".theme-") && !selector.includes(" ")) {
|
||||||
if (DEFAULT_THEME) { // re-add props that dont change (css needs this so they update correctly)
|
if (DEFAULT_THEME) {
|
||||||
|
// re-add props that dont change (css needs this so they update correctly)
|
||||||
Array.from(DEFAULT_THEME.rule.style)
|
Array.from(DEFAULT_THEME.rule.style)
|
||||||
.filter(cssVariable => !Array.from(rule.style).includes(cssVariable))
|
.filter((cssVariable) => !Array.from(rule.style).includes(cssVariable))
|
||||||
.forEach(cssVariable => {
|
.forEach((cssVariable) => {
|
||||||
rule.style.setProperty(cssVariable, DEFAULT_THEME.rule.style.getPropertyValue(cssVariable));
|
rule.style.setProperty(cssVariable, DEFAULT_THEME.rule.style.getPropertyValue(cssVariable))
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
var theme_key = selector.substring(1);
|
var theme_key = selector.substring(1)
|
||||||
THEMES.push({
|
THEMES.push({
|
||||||
key: theme_key,
|
key: theme_key,
|
||||||
name: getThemeName(theme_key),
|
name: getThemeName(theme_key),
|
||||||
rule: rule
|
rule: rule,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if (selector && selector == ":root") {
|
if (selector && selector == ":root") {
|
||||||
DEFAULT_THEME = {
|
DEFAULT_THEME = {
|
||||||
key: "theme-default",
|
key: "theme-default",
|
||||||
name: "Default",
|
name: "Default",
|
||||||
rule: rule
|
rule: rule,
|
||||||
};
|
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
})
|
||||||
THEMES.forEach(theme => {
|
|
||||||
var new_option = document.createElement("option");
|
|
||||||
new_option.setAttribute("value", theme.key);
|
|
||||||
new_option.innerText = theme.name;
|
|
||||||
themeField.appendChild(new_option);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
THEMES.forEach((theme) => {
|
||||||
|
var new_option = document.createElement("option")
|
||||||
|
new_option.setAttribute("value", theme.key)
|
||||||
|
new_option.innerText = theme.name
|
||||||
|
themeField.appendChild(new_option)
|
||||||
|
})
|
||||||
|
|
||||||
// setup the style transitions a second after app initializes, so initial style is instant
|
// setup the style transitions a second after app initializes, so initial style is instant
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
var body = document.querySelector("body");
|
var body = document.querySelector("body")
|
||||||
var style = document.createElement('style');
|
var style = document.createElement("style")
|
||||||
style.innerHTML = "* { transition: background 0.5s, color 0.5s, background-color 0.5s; }";
|
style.innerHTML = "* { transition: background 0.5s, color 0.5s, background-color 0.5s; }"
|
||||||
body.appendChild(style);
|
body.appendChild(style)
|
||||||
}, 1000);
|
}, 1000)
|
||||||
}
|
}
|
||||||
initTheme();
|
initTheme()
|
||||||
|
|
||||||
function themeFieldChanged() {
|
function themeFieldChanged() {
|
||||||
var theme_key = themeField.value;
|
var theme_key = themeField.value
|
||||||
|
|
||||||
var body = document.querySelector("body");
|
var body = document.querySelector("body")
|
||||||
body.classList.remove(...THEMES.map(theme => theme.key));
|
body.classList.remove(...THEMES.map((theme) => theme.key))
|
||||||
body.classList.add(theme_key);
|
body.classList.add(theme_key)
|
||||||
|
|
||||||
//
|
//
|
||||||
|
|
||||||
body.style = "";
|
body.style = ""
|
||||||
var theme = THEMES.find(t => t.key == theme_key);
|
var theme = THEMES.find((t) => t.key == theme_key)
|
||||||
let borderColor = undefined
|
let borderColor = undefined
|
||||||
if (theme) {
|
if (theme) {
|
||||||
borderColor = theme.rule.style.getPropertyValue('--input-border-color').trim()
|
borderColor = theme.rule.style.getPropertyValue("--input-border-color").trim()
|
||||||
if (!borderColor.startsWith('#')) {
|
if (!borderColor.startsWith("#")) {
|
||||||
borderColor = theme.rule.style.getPropertyValue('--theme-color-fallback')
|
borderColor = theme.rule.style.getPropertyValue("--theme-color-fallback")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
borderColor = DEFAULT_THEME.rule.style.getPropertyValue('--theme-color-fallback')
|
borderColor = DEFAULT_THEME.rule.style.getPropertyValue("--theme-color-fallback")
|
||||||
}
|
}
|
||||||
document.querySelector('meta[name="theme-color"]').setAttribute("content", borderColor)
|
document.querySelector('meta[name="theme-color"]').setAttribute("content", borderColor)
|
||||||
}
|
}
|
||||||
|
|
||||||
themeField.addEventListener('change', themeFieldChanged);
|
themeField.addEventListener("change", themeFieldChanged)
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
"use strict";
|
"use strict"
|
||||||
|
|
||||||
// https://gomakethings.com/finding-the-next-and-previous-sibling-elements-that-match-a-selector-with-vanilla-js/
|
// https://gomakethings.com/finding-the-next-and-previous-sibling-elements-that-match-a-selector-with-vanilla-js/
|
||||||
function getNextSibling(elem, selector) {
|
function getNextSibling(elem, selector) {
|
||||||
@ -20,32 +20,33 @@ function getNextSibling(elem, selector) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Panel Stuff */
|
/* Panel Stuff */
|
||||||
|
|
||||||
// true = open
|
// true = open
|
||||||
let COLLAPSIBLES_INITIALIZED = false;
|
let COLLAPSIBLES_INITIALIZED = false
|
||||||
const COLLAPSIBLES_KEY = "collapsibles";
|
const COLLAPSIBLES_KEY = "collapsibles"
|
||||||
const COLLAPSIBLE_PANELS = []; // filled in by createCollapsibles with all the elements matching .collapsible
|
const COLLAPSIBLE_PANELS = [] // filled in by createCollapsibles with all the elements matching .collapsible
|
||||||
|
|
||||||
// on-init call this for any panels that are marked open
|
// on-init call this for any panels that are marked open
|
||||||
function toggleCollapsible(element) {
|
function toggleCollapsible(element) {
|
||||||
const collapsibleHeader = element.querySelector(".collapsible");
|
const collapsibleHeader = element.querySelector(".collapsible")
|
||||||
const handle = element.querySelector(".collapsible-handle");
|
const handle = element.querySelector(".collapsible-handle")
|
||||||
collapsibleHeader.classList.toggle("active")
|
collapsibleHeader.classList.toggle("active")
|
||||||
let content = getNextSibling(collapsibleHeader, '.collapsible-content')
|
let content = getNextSibling(collapsibleHeader, ".collapsible-content")
|
||||||
if (!collapsibleHeader.classList.contains("active")) {
|
if (!collapsibleHeader.classList.contains("active")) {
|
||||||
content.style.display = "none"
|
content.style.display = "none"
|
||||||
if (handle != null) { // render results don't have a handle
|
if (handle != null) {
|
||||||
handle.innerHTML = '➕' // plus
|
// render results don't have a handle
|
||||||
|
handle.innerHTML = "➕" // plus
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
content.style.display = "block"
|
content.style.display = "block"
|
||||||
if (handle != null) { // render results don't have a handle
|
if (handle != null) {
|
||||||
handle.innerHTML = '➖' // minus
|
// render results don't have a handle
|
||||||
|
handle.innerHTML = "➖" // minus
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
document.dispatchEvent(new CustomEvent('collapsibleClick', { detail: collapsibleHeader }))
|
document.dispatchEvent(new CustomEvent("collapsibleClick", { detail: collapsibleHeader }))
|
||||||
|
|
||||||
if (COLLAPSIBLES_INITIALIZED && COLLAPSIBLE_PANELS.includes(element)) {
|
if (COLLAPSIBLES_INITIALIZED && COLLAPSIBLE_PANELS.includes(element)) {
|
||||||
saveCollapsibles()
|
saveCollapsibles()
|
||||||
@ -54,7 +55,7 @@ function toggleCollapsible(element) {
|
|||||||
|
|
||||||
function saveCollapsibles() {
|
function saveCollapsibles() {
|
||||||
let values = {}
|
let values = {}
|
||||||
COLLAPSIBLE_PANELS.forEach(element => {
|
COLLAPSIBLE_PANELS.forEach((element) => {
|
||||||
let value = element.querySelector(".collapsible").className.indexOf("active") !== -1
|
let value = element.querySelector(".collapsible").className.indexOf("active") !== -1
|
||||||
values[element.id] = value
|
values[element.id] = value
|
||||||
})
|
})
|
||||||
@ -72,31 +73,31 @@ function createCollapsibles(node) {
|
|||||||
if (save && c.parentElement.id) {
|
if (save && c.parentElement.id) {
|
||||||
COLLAPSIBLE_PANELS.push(c.parentElement)
|
COLLAPSIBLE_PANELS.push(c.parentElement)
|
||||||
}
|
}
|
||||||
let handle = document.createElement('span')
|
let handle = document.createElement("span")
|
||||||
handle.className = 'collapsible-handle'
|
handle.className = "collapsible-handle"
|
||||||
|
|
||||||
if (c.classList.contains("active")) {
|
if (c.classList.contains("active")) {
|
||||||
handle.innerHTML = '➖' // minus
|
handle.innerHTML = "➖" // minus
|
||||||
} else {
|
} else {
|
||||||
handle.innerHTML = '➕' // plus
|
handle.innerHTML = "➕" // plus
|
||||||
}
|
}
|
||||||
c.insertBefore(handle, c.firstChild)
|
c.insertBefore(handle, c.firstChild)
|
||||||
|
|
||||||
c.addEventListener('click', function() {
|
c.addEventListener("click", function() {
|
||||||
toggleCollapsible(c.parentElement)
|
toggleCollapsible(c.parentElement)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
if (save) {
|
if (save) {
|
||||||
let saved = localStorage.getItem(COLLAPSIBLES_KEY)
|
let saved = localStorage.getItem(COLLAPSIBLES_KEY)
|
||||||
if (!saved) {
|
if (!saved) {
|
||||||
saved = tryLoadOldCollapsibles();
|
saved = tryLoadOldCollapsibles()
|
||||||
}
|
}
|
||||||
if (!saved) {
|
if (!saved) {
|
||||||
saveCollapsibles()
|
saveCollapsibles()
|
||||||
saved = localStorage.getItem(COLLAPSIBLES_KEY)
|
saved = localStorage.getItem(COLLAPSIBLES_KEY)
|
||||||
}
|
}
|
||||||
let values = JSON.parse(saved)
|
let values = JSON.parse(saved)
|
||||||
COLLAPSIBLE_PANELS.forEach(element => {
|
COLLAPSIBLE_PANELS.forEach((element) => {
|
||||||
let value = element.querySelector(".collapsible").className.indexOf("active") !== -1
|
let value = element.querySelector(".collapsible").className.indexOf("active") !== -1
|
||||||
if (values[element.id] != value) {
|
if (values[element.id] != value) {
|
||||||
toggleCollapsible(element)
|
toggleCollapsible(element)
|
||||||
@ -108,24 +109,24 @@ function createCollapsibles(node) {
|
|||||||
|
|
||||||
function tryLoadOldCollapsibles() {
|
function tryLoadOldCollapsibles() {
|
||||||
const old_map = {
|
const old_map = {
|
||||||
"advancedPanelOpen": "editor-settings",
|
advancedPanelOpen: "editor-settings",
|
||||||
"modifiersPanelOpen": "editor-modifiers",
|
modifiersPanelOpen: "editor-modifiers",
|
||||||
"negativePromptPanelOpen": "editor-inputs-prompt"
|
negativePromptPanelOpen: "editor-inputs-prompt",
|
||||||
};
|
}
|
||||||
if (localStorage.getItem(Object.keys(old_map)[0])) {
|
if (localStorage.getItem(Object.keys(old_map)[0])) {
|
||||||
let result = {};
|
let result = {}
|
||||||
Object.keys(old_map).forEach(key => {
|
Object.keys(old_map).forEach((key) => {
|
||||||
const value = localStorage.getItem(key);
|
const value = localStorage.getItem(key)
|
||||||
if (value !== null) {
|
if (value !== null) {
|
||||||
result[old_map[key]] = (value == true || value == "true")
|
result[old_map[key]] = value == true || value == "true"
|
||||||
localStorage.removeItem(key)
|
localStorage.removeItem(key)
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
result = JSON.stringify(result)
|
result = JSON.stringify(result)
|
||||||
localStorage.setItem(COLLAPSIBLES_KEY, result)
|
localStorage.setItem(COLLAPSIBLES_KEY, result)
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
return null;
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
function permute(arr) {
|
function permute(arr) {
|
||||||
@ -134,10 +135,12 @@ function permute(arr) {
|
|||||||
let n_permutations = Math.pow(2, n)
|
let n_permutations = Math.pow(2, n)
|
||||||
for (let i = 0; i < n_permutations; i++) {
|
for (let i = 0; i < n_permutations; i++) {
|
||||||
let perm = []
|
let perm = []
|
||||||
let mask = Number(i).toString(2).padStart(n, '0')
|
let mask = Number(i)
|
||||||
|
.toString(2)
|
||||||
|
.padStart(n, "0")
|
||||||
|
|
||||||
for (let idx = 0; idx < mask.length; idx++) {
|
for (let idx = 0; idx < mask.length; idx++) {
|
||||||
if (mask[idx] === '1' && arr[idx].trim() !== '') {
|
if (mask[idx] === "1" && arr[idx].trim() !== "") {
|
||||||
perm.push(arr[idx])
|
perm.push(arr[idx])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -152,23 +155,23 @@ function permute(arr) {
|
|||||||
|
|
||||||
// https://stackoverflow.com/a/8212878
|
// https://stackoverflow.com/a/8212878
|
||||||
function millisecondsToStr(milliseconds) {
|
function millisecondsToStr(milliseconds) {
|
||||||
function numberEnding (number) {
|
function numberEnding(number) {
|
||||||
return (number > 1) ? 's' : ''
|
return number > 1 ? "s" : ""
|
||||||
}
|
}
|
||||||
|
|
||||||
let temp = Math.floor(milliseconds / 1000)
|
let temp = Math.floor(milliseconds / 1000)
|
||||||
let hours = Math.floor((temp %= 86400) / 3600)
|
let hours = Math.floor((temp %= 86400) / 3600)
|
||||||
let s = ''
|
let s = ""
|
||||||
if (hours) {
|
if (hours) {
|
||||||
s += hours + ' hour' + numberEnding(hours) + ' '
|
s += hours + " hour" + numberEnding(hours) + " "
|
||||||
}
|
}
|
||||||
let minutes = Math.floor((temp %= 3600) / 60)
|
let minutes = Math.floor((temp %= 3600) / 60)
|
||||||
if (minutes) {
|
if (minutes) {
|
||||||
s += minutes + ' minute' + numberEnding(minutes) + ' '
|
s += minutes + " minute" + numberEnding(minutes) + " "
|
||||||
}
|
}
|
||||||
let seconds = temp % 60
|
let seconds = temp % 60
|
||||||
if (!hours && minutes < 4 && seconds) {
|
if (!hours && minutes < 4 && seconds) {
|
||||||
s += seconds + ' second' + numberEnding(seconds)
|
s += seconds + " second" + numberEnding(seconds)
|
||||||
}
|
}
|
||||||
|
|
||||||
return s
|
return s
|
||||||
@ -176,101 +179,82 @@ function millisecondsToStr(milliseconds) {
|
|||||||
|
|
||||||
// https://rosettacode.org/wiki/Brace_expansion#JavaScript
|
// https://rosettacode.org/wiki/Brace_expansion#JavaScript
|
||||||
function BraceExpander() {
|
function BraceExpander() {
|
||||||
'use strict'
|
"use strict"
|
||||||
|
|
||||||
// Index of any closing brace matching the opening
|
// Index of any closing brace matching the opening
|
||||||
// brace at iPosn,
|
// brace at iPosn,
|
||||||
// with the indices of any immediately-enclosed commas.
|
// with the indices of any immediately-enclosed commas.
|
||||||
function bracePair(tkns, iPosn, iNest, lstCommas) {
|
function bracePair(tkns, iPosn, iNest, lstCommas) {
|
||||||
if (iPosn >= tkns.length || iPosn < 0) return null;
|
if (iPosn >= tkns.length || iPosn < 0) return null
|
||||||
|
|
||||||
let t = tkns[iPosn],
|
let t = tkns[iPosn],
|
||||||
n = (t === '{') ? (
|
n = t === "{" ? iNest + 1 : t === "}" ? iNest - 1 : iNest,
|
||||||
iNest + 1
|
lst = t === "," && iNest === 1 ? lstCommas.concat(iPosn) : lstCommas
|
||||||
) : (t === '}' ? (
|
|
||||||
iNest - 1
|
|
||||||
) : iNest),
|
|
||||||
lst = (t === ',' && iNest === 1) ? (
|
|
||||||
lstCommas.concat(iPosn)
|
|
||||||
) : lstCommas;
|
|
||||||
|
|
||||||
return n ? bracePair(tkns, iPosn + 1, n, lst) : {
|
return n
|
||||||
|
? bracePair(tkns, iPosn + 1, n, lst)
|
||||||
|
: {
|
||||||
close: iPosn,
|
close: iPosn,
|
||||||
commas: lst
|
commas: lst,
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse of a SYNTAGM subtree
|
// Parse of a SYNTAGM subtree
|
||||||
function andTree(dctSofar, tkns) {
|
function andTree(dctSofar, tkns) {
|
||||||
if (!tkns.length) return [dctSofar, []];
|
if (!tkns.length) return [dctSofar, []]
|
||||||
|
|
||||||
let dctParse = dctSofar ? dctSofar : {
|
let dctParse = dctSofar
|
||||||
|
? dctSofar
|
||||||
|
: {
|
||||||
fn: and,
|
fn: and,
|
||||||
args: []
|
args: [],
|
||||||
},
|
},
|
||||||
|
|
||||||
head = tkns[0],
|
head = tkns[0],
|
||||||
tail = head ? tkns.slice(1) : [],
|
tail = head ? tkns.slice(1) : [],
|
||||||
|
dctBrace = head === "{" ? bracePair(tkns, 0, 0, []) : null,
|
||||||
|
lstOR = dctBrace && dctBrace.close && dctBrace.commas.length ? splitAt(dctBrace.close + 1, tkns) : null
|
||||||
|
|
||||||
dctBrace = head === '{' ? bracePair(
|
return andTree(
|
||||||
tkns, 0, 0, []
|
{
|
||||||
) : null,
|
|
||||||
|
|
||||||
lstOR = dctBrace && (
|
|
||||||
dctBrace.close
|
|
||||||
) && dctBrace.commas.length ? (
|
|
||||||
splitAt(dctBrace.close + 1, tkns)
|
|
||||||
) : null;
|
|
||||||
|
|
||||||
return andTree({
|
|
||||||
fn: and,
|
fn: and,
|
||||||
args: dctParse.args.concat(
|
args: dctParse.args.concat(lstOR ? orTree(dctParse, lstOR[0], dctBrace.commas) : head),
|
||||||
lstOR ? (
|
},
|
||||||
orTree(dctParse, lstOR[0], dctBrace.commas)
|
lstOR ? lstOR[1] : tail
|
||||||
) : head
|
|
||||||
)
|
)
|
||||||
}, lstOR ? (
|
|
||||||
lstOR[1]
|
|
||||||
) : tail);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse of a PARADIGM subtree
|
// Parse of a PARADIGM subtree
|
||||||
function orTree(dctSofar, tkns, lstCommas) {
|
function orTree(dctSofar, tkns, lstCommas) {
|
||||||
if (!tkns.length) return [dctSofar, []];
|
if (!tkns.length) return [dctSofar, []]
|
||||||
let iLast = lstCommas.length;
|
let iLast = lstCommas.length
|
||||||
|
|
||||||
return {
|
return {
|
||||||
fn: or,
|
fn: or,
|
||||||
args: splitsAt(
|
args: splitsAt(lstCommas, tkns)
|
||||||
lstCommas, tkns
|
.map(function(x, i) {
|
||||||
).map(function (x, i) {
|
let ts = x.slice(1, i === iLast ? -1 : void 0)
|
||||||
let ts = x.slice(
|
|
||||||
1, i === iLast ? (
|
|
||||||
-1
|
|
||||||
) : void 0
|
|
||||||
);
|
|
||||||
|
|
||||||
return ts.length ? ts : [''];
|
return ts.length ? ts : [""]
|
||||||
}).map(function (ts) {
|
|
||||||
return ts.length > 1 ? (
|
|
||||||
andTree(null, ts)[0]
|
|
||||||
) : ts[0];
|
|
||||||
})
|
})
|
||||||
};
|
.map(function(ts) {
|
||||||
|
return ts.length > 1 ? andTree(null, ts)[0] : ts[0]
|
||||||
|
}),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// List of unescaped braces and commas, and remaining strings
|
// List of unescaped braces and commas, and remaining strings
|
||||||
function tokens(str) {
|
function tokens(str) {
|
||||||
// Filter function excludes empty splitting artefacts
|
// Filter function excludes empty splitting artefacts
|
||||||
let toS = function (x) {
|
let toS = function(x) {
|
||||||
return x.toString();
|
return x.toString()
|
||||||
};
|
}
|
||||||
|
|
||||||
return str.split(/(\\\\)/).filter(toS).reduce(function (a, s) {
|
return str
|
||||||
return a.concat(s.charAt(0) === '\\' ? s : s.split(
|
.split(/(\\\\)/)
|
||||||
/(\\*[{,}])/
|
.filter(toS)
|
||||||
).filter(toS));
|
.reduce(function(a, s) {
|
||||||
}, []);
|
return a.concat(s.charAt(0) === "\\" ? s : s.split(/(\\*[{,}])/).filter(toS))
|
||||||
|
}, [])
|
||||||
}
|
}
|
||||||
|
|
||||||
// PARSE TREE OPERATOR (1 of 2)
|
// PARSE TREE OPERATOR (1 of 2)
|
||||||
@ -278,76 +262,75 @@ function BraceExpander() {
|
|||||||
function and(args) {
|
function and(args) {
|
||||||
let lng = args.length,
|
let lng = args.length,
|
||||||
head = lng ? args[0] : null,
|
head = lng ? args[0] : null,
|
||||||
lstHead = "string" === typeof head ? (
|
lstHead = "string" === typeof head ? [head] : head
|
||||||
[head]
|
|
||||||
) : head;
|
|
||||||
|
|
||||||
return lng ? (
|
return lng
|
||||||
1 < lng ? lstHead.reduce(function (a, h) {
|
? 1 < lng
|
||||||
|
? lstHead.reduce(function(a, h) {
|
||||||
return a.concat(
|
return a.concat(
|
||||||
and(args.slice(1)).map(function (t) {
|
and(args.slice(1)).map(function(t) {
|
||||||
return h + t;
|
return h + t
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
}, []) : lstHead
|
}, [])
|
||||||
) : [];
|
: lstHead
|
||||||
|
: []
|
||||||
}
|
}
|
||||||
|
|
||||||
// PARSE TREE OPERATOR (2 of 2)
|
// PARSE TREE OPERATOR (2 of 2)
|
||||||
// Each option flattened
|
// Each option flattened
|
||||||
function or(args) {
|
function or(args) {
|
||||||
return args.reduce(function (a, b) {
|
return args.reduce(function(a, b) {
|
||||||
return a.concat(b);
|
return a.concat(b)
|
||||||
}, []);
|
}, [])
|
||||||
}
|
}
|
||||||
|
|
||||||
// One list split into two (first sublist length n)
|
// One list split into two (first sublist length n)
|
||||||
function splitAt(n, lst) {
|
function splitAt(n, lst) {
|
||||||
return n < lst.length + 1 ? [
|
return n < lst.length + 1 ? [lst.slice(0, n), lst.slice(n)] : [lst, []]
|
||||||
lst.slice(0, n), lst.slice(n)
|
|
||||||
] : [lst, []];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// One list split into several (sublist lengths [n])
|
// One list split into several (sublist lengths [n])
|
||||||
function splitsAt(lstN, lst) {
|
function splitsAt(lstN, lst) {
|
||||||
return lstN.reduceRight(function (a, x) {
|
return lstN.reduceRight(
|
||||||
return splitAt(x, a[0]).concat(a.slice(1));
|
function(a, x) {
|
||||||
}, [lst]);
|
return splitAt(x, a[0]).concat(a.slice(1))
|
||||||
|
},
|
||||||
|
[lst]
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Value of the parse tree
|
// Value of the parse tree
|
||||||
function evaluated(e) {
|
function evaluated(e) {
|
||||||
return typeof e === 'string' ? e :
|
return typeof e === "string" ? e : e.fn(e.args.map(evaluated))
|
||||||
e.fn(e.args.map(evaluated));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// JSON prettyprint (for parse tree, token list etc)
|
// JSON prettyprint (for parse tree, token list etc)
|
||||||
function pp(e) {
|
function pp(e) {
|
||||||
return JSON.stringify(e, function (k, v) {
|
return JSON.stringify(
|
||||||
return typeof v === 'function' ? (
|
e,
|
||||||
'[function ' + v.name + ']'
|
function(k, v) {
|
||||||
) : v;
|
return typeof v === "function" ? "[function " + v.name + "]" : v
|
||||||
}, 2)
|
},
|
||||||
|
2
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// ----------------------- MAIN ------------------------
|
// ----------------------- MAIN ------------------------
|
||||||
|
|
||||||
// s -> [s]
|
// s -> [s]
|
||||||
this.expand = function(s) {
|
this.expand = function(s) {
|
||||||
// BRACE EXPRESSION PARSED
|
// BRACE EXPRESSION PARSED
|
||||||
let dctParse = andTree(null, tokens(s))[0];
|
let dctParse = andTree(null, tokens(s))[0]
|
||||||
|
|
||||||
// ABSTRACT SYNTAX TREE LOGGED
|
// ABSTRACT SYNTAX TREE LOGGED
|
||||||
// console.log(pp(dctParse));
|
// console.log(pp(dctParse));
|
||||||
|
|
||||||
// AST EVALUATED TO LIST OF STRINGS
|
// AST EVALUATED TO LIST OF STRINGS
|
||||||
return evaluated(dctParse);
|
return evaluated(dctParse)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/** Pause the execution of an async function until timer elapse.
|
/** Pause the execution of an async function until timer elapse.
|
||||||
* @Returns a promise that will resolve after the specified timeout.
|
* @Returns a promise that will resolve after the specified timeout.
|
||||||
*/
|
*/
|
||||||
@ -360,12 +343,12 @@ function asyncDelay(timeout) {
|
|||||||
function PromiseSource() {
|
function PromiseSource() {
|
||||||
const srcPromise = new Promise((resolve, reject) => {
|
const srcPromise = new Promise((resolve, reject) => {
|
||||||
Object.defineProperties(this, {
|
Object.defineProperties(this, {
|
||||||
resolve: { value: resolve, writable: false }
|
resolve: { value: resolve, writable: false },
|
||||||
, reject: { value: reject, writable: false }
|
reject: { value: reject, writable: false },
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
Object.defineProperties(this, {
|
Object.defineProperties(this, {
|
||||||
promise: {value: makeQuerablePromise(srcPromise), writable: false}
|
promise: { value: makeQuerablePromise(srcPromise), writable: false },
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -375,7 +358,7 @@ function PromiseSource() {
|
|||||||
* If `immediate` is passed, trigger the function on the leading edge, instead of the trailing.
|
* If `immediate` is passed, trigger the function on the leading edge, instead of the trailing.
|
||||||
* @Returns a promise that will resolve to func return value.
|
* @Returns a promise that will resolve to func return value.
|
||||||
*/
|
*/
|
||||||
function debounce (func, wait, immediate) {
|
function debounce(func, wait, immediate) {
|
||||||
if (typeof wait === "undefined") {
|
if (typeof wait === "undefined") {
|
||||||
wait = 40
|
wait = 40
|
||||||
}
|
}
|
||||||
@ -399,11 +382,11 @@ function debounce (func, wait, immediate) {
|
|||||||
}
|
}
|
||||||
return function(...args) {
|
return function(...args) {
|
||||||
const callNow = Boolean(immediate && !timeout)
|
const callNow = Boolean(immediate && !timeout)
|
||||||
const context = this;
|
const context = this
|
||||||
if (timeout) {
|
if (timeout) {
|
||||||
clearTimeout(timeout)
|
clearTimeout(timeout)
|
||||||
}
|
}
|
||||||
timeout = setTimeout(function () {
|
timeout = setTimeout(function() {
|
||||||
if (!immediate) {
|
if (!immediate) {
|
||||||
applyFn(context, args)
|
applyFn(context, args)
|
||||||
}
|
}
|
||||||
@ -418,14 +401,14 @@ function debounce (func, wait, immediate) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function preventNonNumericalInput(e) {
|
function preventNonNumericalInput(e) {
|
||||||
e = e || window.event;
|
e = e || window.event
|
||||||
let charCode = (typeof e.which == "undefined") ? e.keyCode : e.which;
|
let charCode = typeof e.which == "undefined" ? e.keyCode : e.which
|
||||||
let charStr = String.fromCharCode(charCode);
|
let charStr = String.fromCharCode(charCode)
|
||||||
let re = e.target.getAttribute('pattern') || '^[0-9]+$'
|
let re = e.target.getAttribute("pattern") || "^[0-9]+$"
|
||||||
re = new RegExp(re)
|
re = new RegExp(re)
|
||||||
|
|
||||||
if (!charStr.match(re)) {
|
if (!charStr.match(re)) {
|
||||||
e.preventDefault();
|
e.preventDefault()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -434,15 +417,15 @@ function preventNonNumericalInput(e) {
|
|||||||
* @Notes Allows unit testing and use of the engine outside of a browser.
|
* @Notes Allows unit testing and use of the engine outside of a browser.
|
||||||
*/
|
*/
|
||||||
function getGlobal() {
|
function getGlobal() {
|
||||||
if (typeof globalThis === 'object') {
|
if (typeof globalThis === "object") {
|
||||||
return globalThis
|
return globalThis
|
||||||
} else if (typeof global === 'object') {
|
} else if (typeof global === "object") {
|
||||||
return global
|
return global
|
||||||
} else if (typeof self === 'object') {
|
} else if (typeof self === "object") {
|
||||||
return self
|
return self
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
return Function('return this')()
|
return Function("return this")()
|
||||||
} catch {
|
} catch {
|
||||||
// If the Function constructor fails, we're in a browser with eval disabled by CSP headers.
|
// If the Function constructor fails, we're in a browser with eval disabled by CSP headers.
|
||||||
return window
|
return window
|
||||||
@ -453,18 +436,18 @@ function getGlobal() {
|
|||||||
* @Returns true if x is an Array or a TypedArray, false otherwise.
|
* @Returns true if x is an Array or a TypedArray, false otherwise.
|
||||||
*/
|
*/
|
||||||
function isArrayOrTypedArray(x) {
|
function isArrayOrTypedArray(x) {
|
||||||
return Boolean(typeof x === 'object' && (Array.isArray(x) || (ArrayBuffer.isView(x) && !(x instanceof DataView))))
|
return Boolean(typeof x === "object" && (Array.isArray(x) || (ArrayBuffer.isView(x) && !(x instanceof DataView))))
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeQuerablePromise(promise) {
|
function makeQuerablePromise(promise) {
|
||||||
if (typeof promise !== 'object') {
|
if (typeof promise !== "object") {
|
||||||
throw new Error('promise is not an object.')
|
throw new Error("promise is not an object.")
|
||||||
}
|
}
|
||||||
if (!(promise instanceof Promise)) {
|
if (!(promise instanceof Promise)) {
|
||||||
throw new Error('Argument is not a promise.')
|
throw new Error("Argument is not a promise.")
|
||||||
}
|
}
|
||||||
// Don't modify a promise that's been already modified.
|
// Don't modify a promise that's been already modified.
|
||||||
if ('isResolved' in promise || 'isRejected' in promise || 'isPending' in promise) {
|
if ("isResolved" in promise || "isRejected" in promise || "isPending" in promise) {
|
||||||
return promise
|
return promise
|
||||||
}
|
}
|
||||||
let isPending = true
|
let isPending = true
|
||||||
@ -473,13 +456,13 @@ function makeQuerablePromise(promise) {
|
|||||||
let isResolved = false
|
let isResolved = false
|
||||||
let resolvedValue = undefined
|
let resolvedValue = undefined
|
||||||
const qurPro = promise.then(
|
const qurPro = promise.then(
|
||||||
function(val){
|
function(val) {
|
||||||
isResolved = true
|
isResolved = true
|
||||||
isPending = false
|
isPending = false
|
||||||
resolvedValue = val
|
resolvedValue = val
|
||||||
return val
|
return val
|
||||||
}
|
},
|
||||||
, function(reason) {
|
function(reason) {
|
||||||
rejectReason = reason
|
rejectReason = reason
|
||||||
isRejected = true
|
isRejected = true
|
||||||
isPending = false
|
isPending = false
|
||||||
@ -487,46 +470,46 @@ function makeQuerablePromise(promise) {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
Object.defineProperties(qurPro, {
|
Object.defineProperties(qurPro, {
|
||||||
'isResolved': {
|
isResolved: {
|
||||||
get: () => isResolved
|
get: () => isResolved,
|
||||||
}
|
},
|
||||||
, 'resolvedValue': {
|
resolvedValue: {
|
||||||
get: () => resolvedValue
|
get: () => resolvedValue,
|
||||||
}
|
},
|
||||||
, 'isPending': {
|
isPending: {
|
||||||
get: () => isPending
|
get: () => isPending,
|
||||||
}
|
},
|
||||||
, 'isRejected': {
|
isRejected: {
|
||||||
get: () => isRejected
|
get: () => isRejected,
|
||||||
}
|
},
|
||||||
, 'rejectReason': {
|
rejectReason: {
|
||||||
get: () => rejectReason
|
get: () => rejectReason,
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
return qurPro
|
return qurPro
|
||||||
}
|
}
|
||||||
|
|
||||||
/* inserts custom html to allow prettifying of inputs */
|
/* inserts custom html to allow prettifying of inputs */
|
||||||
function prettifyInputs(root_element) {
|
function prettifyInputs(root_element) {
|
||||||
root_element.querySelectorAll(`input[type="checkbox"]`).forEach(element => {
|
root_element.querySelectorAll(`input[type="checkbox"]`).forEach((element) => {
|
||||||
if (element.style.display === "none") {
|
if (element.style.display === "none") {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var parent = element.parentNode;
|
var parent = element.parentNode
|
||||||
if (!parent.classList.contains("input-toggle")) {
|
if (!parent.classList.contains("input-toggle")) {
|
||||||
var wrapper = document.createElement("div");
|
var wrapper = document.createElement("div")
|
||||||
wrapper.classList.add("input-toggle");
|
wrapper.classList.add("input-toggle")
|
||||||
parent.replaceChild(wrapper, element);
|
parent.replaceChild(wrapper, element)
|
||||||
wrapper.appendChild(element);
|
wrapper.appendChild(element)
|
||||||
var label = document.createElement("label");
|
var label = document.createElement("label")
|
||||||
label.htmlFor = element.id;
|
label.htmlFor = element.id
|
||||||
wrapper.appendChild(label);
|
wrapper.appendChild(label)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
class GenericEventSource {
|
class GenericEventSource {
|
||||||
#events = {};
|
#events = {}
|
||||||
#types = []
|
#types = []
|
||||||
constructor(...eventsTypes) {
|
constructor(...eventsTypes) {
|
||||||
if (Array.isArray(eventsTypes) && eventsTypes.length === 1 && Array.isArray(eventsTypes[0])) {
|
if (Array.isArray(eventsTypes) && eventsTypes.length === 1 && Array.isArray(eventsTypes[0])) {
|
||||||
@ -541,7 +524,7 @@ class GenericEventSource {
|
|||||||
*/
|
*/
|
||||||
addEventListener(name, handler) {
|
addEventListener(name, handler) {
|
||||||
if (!this.#types.includes(name)) {
|
if (!this.#types.includes(name)) {
|
||||||
throw new Error('Invalid event name.')
|
throw new Error("Invalid event name.")
|
||||||
}
|
}
|
||||||
if (this.#events.hasOwnProperty(name)) {
|
if (this.#events.hasOwnProperty(name)) {
|
||||||
this.#events[name].push(handler)
|
this.#events[name].push(handler)
|
||||||
@ -574,13 +557,15 @@ class GenericEventSource {
|
|||||||
if (evs.length <= 0) {
|
if (evs.length <= 0) {
|
||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
}
|
}
|
||||||
return Promise.allSettled(evs.map((callback) => {
|
return Promise.allSettled(
|
||||||
|
evs.map((callback) => {
|
||||||
try {
|
try {
|
||||||
return Promise.resolve(callback.apply(SD, args))
|
return Promise.resolve(callback.apply(SD, args))
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
return Promise.reject(ex)
|
return Promise.reject(ex)
|
||||||
}
|
}
|
||||||
}))
|
})
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -590,7 +575,7 @@ class ServiceContainer {
|
|||||||
constructor(...servicesParams) {
|
constructor(...servicesParams) {
|
||||||
servicesParams.forEach(this.register.bind(this))
|
servicesParams.forEach(this.register.bind(this))
|
||||||
}
|
}
|
||||||
get services () {
|
get services() {
|
||||||
return this.#services
|
return this.#services
|
||||||
}
|
}
|
||||||
get singletons() {
|
get singletons() {
|
||||||
@ -598,54 +583,52 @@ class ServiceContainer {
|
|||||||
}
|
}
|
||||||
register(params) {
|
register(params) {
|
||||||
if (ServiceContainer.isConstructor(params)) {
|
if (ServiceContainer.isConstructor(params)) {
|
||||||
if (typeof params.name !== 'string') {
|
if (typeof params.name !== "string") {
|
||||||
throw new Error('params.name is not a string.')
|
throw new Error("params.name is not a string.")
|
||||||
}
|
}
|
||||||
params = {name:params.name, definition:params}
|
params = { name: params.name, definition: params }
|
||||||
}
|
}
|
||||||
if (typeof params !== 'object') {
|
if (typeof params !== "object") {
|
||||||
throw new Error('params is not an object.')
|
throw new Error("params is not an object.")
|
||||||
}
|
}
|
||||||
[ 'name',
|
;["name", "definition"].forEach((key) => {
|
||||||
'definition',
|
|
||||||
].forEach((key) => {
|
|
||||||
if (!(key in params)) {
|
if (!(key in params)) {
|
||||||
console.error('Invalid service %o registration.', params)
|
console.error("Invalid service %o registration.", params)
|
||||||
throw new Error(`params.${key} is not defined.`)
|
throw new Error(`params.${key} is not defined.`)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
const opts = {definition: params.definition}
|
const opts = { definition: params.definition }
|
||||||
if ('dependencies' in params) {
|
if ("dependencies" in params) {
|
||||||
if (Array.isArray(params.dependencies)) {
|
if (Array.isArray(params.dependencies)) {
|
||||||
params.dependencies.forEach((dep) => {
|
params.dependencies.forEach((dep) => {
|
||||||
if (typeof dep !== 'string') {
|
if (typeof dep !== "string") {
|
||||||
throw new Error('dependency name is not a string.')
|
throw new Error("dependency name is not a string.")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
opts.dependencies = params.dependencies
|
opts.dependencies = params.dependencies
|
||||||
} else {
|
} else {
|
||||||
throw new Error('params.dependencies is not an array.')
|
throw new Error("params.dependencies is not an array.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (params.singleton) {
|
if (params.singleton) {
|
||||||
opts.singleton = true
|
opts.singleton = true
|
||||||
}
|
}
|
||||||
this.#services.set(params.name, opts)
|
this.#services.set(params.name, opts)
|
||||||
return Object.assign({name: params.name}, opts)
|
return Object.assign({ name: params.name }, opts)
|
||||||
}
|
}
|
||||||
get(name) {
|
get(name) {
|
||||||
const ctorInfos = this.#services.get(name)
|
const ctorInfos = this.#services.get(name)
|
||||||
if (!ctorInfos) {
|
if (!ctorInfos) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if(!ServiceContainer.isConstructor(ctorInfos.definition)) {
|
if (!ServiceContainer.isConstructor(ctorInfos.definition)) {
|
||||||
return ctorInfos.definition
|
return ctorInfos.definition
|
||||||
}
|
}
|
||||||
if(!ctorInfos.singleton) {
|
if (!ctorInfos.singleton) {
|
||||||
return this._createInstance(ctorInfos)
|
return this._createInstance(ctorInfos)
|
||||||
}
|
}
|
||||||
const singletonInstance = this.#singletons.get(name)
|
const singletonInstance = this.#singletons.get(name)
|
||||||
if(singletonInstance) {
|
if (singletonInstance) {
|
||||||
return singletonInstance
|
return singletonInstance
|
||||||
}
|
}
|
||||||
const newSingletonInstance = this._createInstance(ctorInfos)
|
const newSingletonInstance = this._createInstance(ctorInfos)
|
||||||
@ -655,7 +638,7 @@ class ServiceContainer {
|
|||||||
|
|
||||||
_getResolvedDependencies(service) {
|
_getResolvedDependencies(service) {
|
||||||
let classDependencies = []
|
let classDependencies = []
|
||||||
if(service.dependencies) {
|
if (service.dependencies) {
|
||||||
classDependencies = service.dependencies.map(this.get.bind(this))
|
classDependencies = service.dependencies.map(this.get.bind(this))
|
||||||
}
|
}
|
||||||
return classDependencies
|
return classDependencies
|
||||||
@ -671,10 +654,14 @@ class ServiceContainer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static isClass(definition) {
|
static isClass(definition) {
|
||||||
return typeof definition === 'function' && Boolean(definition.prototype) && definition.prototype.constructor === definition
|
return (
|
||||||
|
typeof definition === "function" &&
|
||||||
|
Boolean(definition.prototype) &&
|
||||||
|
definition.prototype.constructor === definition
|
||||||
|
)
|
||||||
}
|
}
|
||||||
static isConstructor(definition) {
|
static isConstructor(definition) {
|
||||||
return typeof definition === 'function'
|
return typeof definition === "function"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -693,14 +680,14 @@ function createElement(tagName, attributes, classes, textOrElements) {
|
|||||||
if (value !== undefined && value !== null) {
|
if (value !== undefined && value !== null) {
|
||||||
element.setAttribute(key, value)
|
element.setAttribute(key, value)
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
if (classes) {
|
if (classes) {
|
||||||
(Array.isArray(classes) ? classes : [classes]).forEach(className => element.classList.add(className))
|
;(Array.isArray(classes) ? classes : [classes]).forEach((className) => element.classList.add(className))
|
||||||
}
|
}
|
||||||
if (textOrElements) {
|
if (textOrElements) {
|
||||||
const children = Array.isArray(textOrElements) ? textOrElements : [textOrElements]
|
const children = Array.isArray(textOrElements) ? textOrElements : [textOrElements]
|
||||||
children.forEach(textOrElem => {
|
children.forEach((textOrElem) => {
|
||||||
if (textOrElem instanceof Node) {
|
if (textOrElem instanceof Node) {
|
||||||
element.appendChild(textOrElem)
|
element.appendChild(textOrElem)
|
||||||
} else {
|
} else {
|
||||||
@ -720,9 +707,193 @@ Array.prototype.addEventListener = function(method, callback) {
|
|||||||
const originalFunction = this[method]
|
const originalFunction = this[method]
|
||||||
if (originalFunction) {
|
if (originalFunction) {
|
||||||
this[method] = function() {
|
this[method] = function() {
|
||||||
console.log(`Array.${method}()`, arguments)
|
|
||||||
originalFunction.apply(this, arguments)
|
originalFunction.apply(this, arguments)
|
||||||
callback.apply(this, arguments)
|
callback.apply(this, arguments)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {object} TabOpenDetails
|
||||||
|
* @property {HTMLElement} contentElement
|
||||||
|
* @property {HTMLElement} labelElement
|
||||||
|
* @property {number} timesOpened
|
||||||
|
* @property {boolean} firstOpen
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {object} CreateTabRequest
|
||||||
|
* @property {string} id
|
||||||
|
* @property {string | Node | (() => (string | Node))} label
|
||||||
|
* Label text or an HTML element
|
||||||
|
* @property {string} icon
|
||||||
|
* @property {string | Node | Promise<string | Node> | (() => (string | Node | Promise<string | Node>)) | undefined} content
|
||||||
|
* HTML string or HTML element
|
||||||
|
* @property {((TabOpenDetails, Event) => (undefined | string | Node | Promise<string | Node>)) | undefined} onOpen
|
||||||
|
* If an HTML string or HTML element is returned, then that will replace the tab content
|
||||||
|
* @property {string | undefined} css
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {CreateTabRequest} request
|
||||||
|
*/
|
||||||
|
function createTab(request) {
|
||||||
|
if (!request?.id) {
|
||||||
|
console.error("createTab() error - id is required", Error().stack)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!request.label) {
|
||||||
|
console.error("createTab() error - label is required", Error().stack)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!request.icon) {
|
||||||
|
console.error("createTab() error - icon is required", Error().stack)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!request.content && !request.onOpen) {
|
||||||
|
console.error("createTab() error - content or onOpen required", Error().stack)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const tabsContainer = document.querySelector(".tab-container")
|
||||||
|
if (!tabsContainer) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const tabsContentWrapper = document.querySelector("#tab-content-wrapper")
|
||||||
|
if (!tabsContentWrapper) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// console.debug('creating tab: ', request)
|
||||||
|
|
||||||
|
if (request.css) {
|
||||||
|
document
|
||||||
|
.querySelector("body")
|
||||||
|
.insertAdjacentElement(
|
||||||
|
"beforeend",
|
||||||
|
createElement("style", { id: `tab-${request.id}-css` }, undefined, request.css)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const label = typeof request.label === "function" ? request.label() : request.label
|
||||||
|
const labelElement = label instanceof Node ? label : createElement("span", undefined, undefined, label)
|
||||||
|
|
||||||
|
const tab = createElement(
|
||||||
|
"span",
|
||||||
|
{ id: `tab-${request.id}`, "data-times-opened": 0 },
|
||||||
|
["tab"],
|
||||||
|
createElement("span", undefined, undefined, [
|
||||||
|
createElement("i", { style: "margin-right: 0.25em" }, [
|
||||||
|
"fa-solid",
|
||||||
|
`${request.icon.startsWith("fa-") ? "" : "fa-"}${request.icon}`,
|
||||||
|
"icon",
|
||||||
|
]),
|
||||||
|
labelElement,
|
||||||
|
])
|
||||||
|
)
|
||||||
|
|
||||||
|
tabsContainer.insertAdjacentElement("beforeend", tab)
|
||||||
|
|
||||||
|
const wrapper = createElement("div", { id: request.id }, ["tab-content-inner"], "Loading..")
|
||||||
|
|
||||||
|
const tabContent = createElement("div", { id: `tab-content-${request.id}` }, ["tab-content"], wrapper)
|
||||||
|
tabsContentWrapper.insertAdjacentElement("beforeend", tabContent)
|
||||||
|
|
||||||
|
linkTabContents(tab)
|
||||||
|
|
||||||
|
function replaceContent(resultFactory) {
|
||||||
|
if (resultFactory === undefined || resultFactory === null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const result = typeof resultFactory === "function" ? resultFactory() : resultFactory
|
||||||
|
if (result instanceof Promise) {
|
||||||
|
result.then(replaceContent)
|
||||||
|
} else if (result instanceof Node) {
|
||||||
|
wrapper.replaceChildren(result)
|
||||||
|
} else {
|
||||||
|
wrapper.innerHTML = result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
replaceContent(request.content)
|
||||||
|
|
||||||
|
tab.addEventListener("click", (e) => {
|
||||||
|
const timesOpened = +(tab.dataset.timesOpened || 0) + 1
|
||||||
|
tab.dataset.timesOpened = timesOpened
|
||||||
|
|
||||||
|
if (request.onOpen) {
|
||||||
|
const result = request.onOpen(
|
||||||
|
{
|
||||||
|
contentElement: wrapper,
|
||||||
|
labelElement,
|
||||||
|
timesOpened,
|
||||||
|
firstOpen: timesOpened === 1,
|
||||||
|
},
|
||||||
|
e
|
||||||
|
)
|
||||||
|
|
||||||
|
replaceContent(result)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/* TOAST NOTIFICATIONS */
|
||||||
|
function showToast(message, duration = 5000, error = false) {
|
||||||
|
const toast = document.createElement("div");
|
||||||
|
toast.classList.add("toast-notification");
|
||||||
|
if (error === true) {
|
||||||
|
toast.classList.add("toast-notification-error");
|
||||||
|
}
|
||||||
|
toast.innerHTML = message;
|
||||||
|
document.body.appendChild(toast);
|
||||||
|
|
||||||
|
// Set the position of the toast on the screen
|
||||||
|
const toastCount = document.querySelectorAll(".toast-notification").length;
|
||||||
|
const toastHeight = toast.offsetHeight;
|
||||||
|
const previousToastsHeight = Array.from(document.querySelectorAll(".toast-notification"))
|
||||||
|
.slice(0, -1) // exclude current toast
|
||||||
|
.reduce((totalHeight, toast) => totalHeight + toast.offsetHeight + 10, 0); // add 10 pixels for spacing
|
||||||
|
toast.style.bottom = `${10 + previousToastsHeight}px`;
|
||||||
|
toast.style.right = "10px";
|
||||||
|
|
||||||
|
// Delay the removal of the toast until animation has completed
|
||||||
|
const removeToast = () => {
|
||||||
|
toast.classList.add("hide");
|
||||||
|
const removeTimeoutId = setTimeout(() => {
|
||||||
|
toast.remove();
|
||||||
|
// Adjust the position of remaining toasts
|
||||||
|
const remainingToasts = document.querySelectorAll(".toast-notification");
|
||||||
|
const removedToastBottom = toast.getBoundingClientRect().bottom;
|
||||||
|
|
||||||
|
remainingToasts.forEach((toast) => {
|
||||||
|
if (toast.getBoundingClientRect().bottom < removedToastBottom) {
|
||||||
|
toast.classList.add("slide-down");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Wait for the slide-down animation to complete
|
||||||
|
setTimeout(() => {
|
||||||
|
// Remove the slide-down class after the animation has completed
|
||||||
|
const slidingToasts = document.querySelectorAll(".slide-down");
|
||||||
|
slidingToasts.forEach((toast) => {
|
||||||
|
toast.classList.remove("slide-down");
|
||||||
|
});
|
||||||
|
|
||||||
|
// Adjust the position of remaining toasts again, in case there are multiple toasts being removed at once
|
||||||
|
const remainingToastsDown = document.querySelectorAll(".toast-notification");
|
||||||
|
let heightSoFar = 0;
|
||||||
|
remainingToastsDown.forEach((toast) => {
|
||||||
|
toast.style.bottom = `${10 + heightSoFar}px`;
|
||||||
|
heightSoFar += toast.offsetHeight + 10; // add 10 pixels for spacing
|
||||||
|
});
|
||||||
|
}, 0); // The duration of the slide-down animation (in milliseconds)
|
||||||
|
}, 500);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Remove the toast after specified duration
|
||||||
|
setTimeout(removeToast, duration);
|
||||||
|
}
|
||||||
|
@ -1,28 +1,32 @@
|
|||||||
(function () {
|
;(function() {
|
||||||
"use strict"
|
"use strict"
|
||||||
|
|
||||||
let autoScroll = document.querySelector("#auto_scroll")
|
let autoScroll = document.querySelector("#auto_scroll")
|
||||||
|
|
||||||
// observe for changes in the preview pane
|
// observe for changes in the preview pane
|
||||||
var observer = new MutationObserver(function (mutations) {
|
var observer = new MutationObserver(function(mutations) {
|
||||||
mutations.forEach(function (mutation) {
|
mutations.forEach(function(mutation) {
|
||||||
if (mutation.target.className == 'img-batch') {
|
if (mutation.target.className == "img-batch") {
|
||||||
Autoscroll(mutation.target)
|
Autoscroll(mutation.target)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
observer.observe(document.getElementById('preview'), {
|
observer.observe(document.getElementById("preview"), {
|
||||||
childList: true,
|
childList: true,
|
||||||
subtree: true
|
subtree: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
function Autoscroll(target) {
|
function Autoscroll(target) {
|
||||||
if (autoScroll.checked && target !== null) {
|
if (autoScroll.checked && target !== null) {
|
||||||
const img = target.querySelector('img')
|
const img = target.querySelector("img")
|
||||||
img.addEventListener('load', function() {
|
img.addEventListener(
|
||||||
img.closest('.imageTaskContainer').scrollIntoView()
|
"load",
|
||||||
}, { once: true })
|
function() {
|
||||||
|
img?.closest(".imageTaskContainer").scrollIntoView()
|
||||||
|
},
|
||||||
|
{ once: true }
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})()
|
})()
|
||||||
|
@ -1,21 +1,22 @@
|
|||||||
(function () { "use strict"
|
;(function() {
|
||||||
if (typeof editorModifierTagsList !== 'object') {
|
"use strict"
|
||||||
console.error('editorModifierTagsList missing...')
|
if (typeof editorModifierTagsList !== "object") {
|
||||||
|
console.error("editorModifierTagsList missing...")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const styleSheet = document.createElement("style");
|
const styleSheet = document.createElement("style")
|
||||||
styleSheet.textContent = `
|
styleSheet.textContent = `
|
||||||
.modifier-card-tiny.drag-sort-active {
|
.modifier-card-tiny.drag-sort-active {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
border: 2px dashed white;
|
border: 2px dashed white;
|
||||||
opacity:0.2;
|
opacity:0.2;
|
||||||
}
|
}
|
||||||
`;
|
`
|
||||||
document.head.appendChild(styleSheet);
|
document.head.appendChild(styleSheet)
|
||||||
|
|
||||||
// observe for changes in tag list
|
// observe for changes in tag list
|
||||||
const observer = new MutationObserver(function (mutations) {
|
const observer = new MutationObserver(function(mutations) {
|
||||||
// mutations.forEach(function (mutation) {
|
// mutations.forEach(function (mutation) {
|
||||||
if (editorModifierTagsList.childNodes.length > 0) {
|
if (editorModifierTagsList.childNodes.length > 0) {
|
||||||
ModifierDragAndDrop(editorModifierTagsList)
|
ModifierDragAndDrop(editorModifierTagsList)
|
||||||
@ -24,70 +25,92 @@
|
|||||||
})
|
})
|
||||||
|
|
||||||
observer.observe(editorModifierTagsList, {
|
observer.observe(editorModifierTagsList, {
|
||||||
childList: true
|
childList: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
let current
|
let current
|
||||||
function ModifierDragAndDrop(target) {
|
function ModifierDragAndDrop(target) {
|
||||||
let overlays = document.querySelector('#editor-inputs-tags-list').querySelectorAll('.modifier-card-overlay')
|
let overlays = document.querySelector("#editor-inputs-tags-list").querySelectorAll(".modifier-card-overlay")
|
||||||
overlays.forEach (i => {
|
overlays.forEach((i) => {
|
||||||
i.parentElement.draggable = true;
|
i.parentElement.draggable = true
|
||||||
|
|
||||||
i.parentElement.ondragstart = (e) => {
|
i.parentElement.ondragstart = (e) => {
|
||||||
current = i
|
current = i
|
||||||
i.parentElement.getElementsByClassName('modifier-card-image-overlay')[0].innerText = ''
|
i.parentElement.getElementsByClassName("modifier-card-image-overlay")[0].innerText = ""
|
||||||
i.parentElement.draggable = true
|
i.parentElement.draggable = true
|
||||||
i.parentElement.classList.add('drag-sort-active')
|
i.parentElement.classList.add("drag-sort-active")
|
||||||
for(let item of document.querySelector('#editor-inputs-tags-list').getElementsByClassName('modifier-card-image-overlay')) {
|
for (let item of document
|
||||||
if (item.parentElement.parentElement.getElementsByClassName('modifier-card-overlay')[0] != current) {
|
.querySelector("#editor-inputs-tags-list")
|
||||||
item.parentElement.parentElement.getElementsByClassName('modifier-card-image-overlay')[0].style.opacity = 0
|
.getElementsByClassName("modifier-card-image-overlay")) {
|
||||||
if(item.parentElement.getElementsByClassName('modifier-card-image').length > 0) {
|
if (
|
||||||
item.parentElement.getElementsByClassName('modifier-card-image')[0].style.filter = 'none'
|
item.parentElement.parentElement.getElementsByClassName("modifier-card-overlay")[0] != current
|
||||||
|
) {
|
||||||
|
item.parentElement.parentElement.getElementsByClassName(
|
||||||
|
"modifier-card-image-overlay"
|
||||||
|
)[0].style.opacity = 0
|
||||||
|
if (item.parentElement.getElementsByClassName("modifier-card-image").length > 0) {
|
||||||
|
item.parentElement.getElementsByClassName("modifier-card-image")[0].style.filter = "none"
|
||||||
}
|
}
|
||||||
item.parentElement.parentElement.style.transform = 'none'
|
item.parentElement.parentElement.style.transform = "none"
|
||||||
item.parentElement.parentElement.style.boxShadow = 'none'
|
item.parentElement.parentElement.style.boxShadow = "none"
|
||||||
}
|
}
|
||||||
item.innerText = ''
|
item.innerText = ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
i.ondragenter = (e) => {
|
i.ondragenter = (e) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
if (i != current) {
|
if (i != current) {
|
||||||
let currentPos = 0, droppedPos = 0;
|
let currentPos = 0,
|
||||||
|
droppedPos = 0
|
||||||
for (let it = 0; it < overlays.length; it++) {
|
for (let it = 0; it < overlays.length; it++) {
|
||||||
if (current == overlays[it]) { currentPos = it; }
|
if (current == overlays[it]) {
|
||||||
if (i == overlays[it]) { droppedPos = it; }
|
currentPos = it
|
||||||
|
}
|
||||||
|
if (i == overlays[it]) {
|
||||||
|
droppedPos = it
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (i.parentElement != current.parentElement) {
|
if (i.parentElement != current.parentElement) {
|
||||||
let currentPos = 0, droppedPos = 0
|
let currentPos = 0,
|
||||||
|
droppedPos = 0
|
||||||
for (let it = 0; it < overlays.length; it++) {
|
for (let it = 0; it < overlays.length; it++) {
|
||||||
if (current == overlays[it]) { currentPos = it }
|
if (current == overlays[it]) {
|
||||||
if (i == overlays[it]) { droppedPos = it }
|
currentPos = it
|
||||||
|
}
|
||||||
|
if (i == overlays[it]) {
|
||||||
|
droppedPos = it
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (currentPos < droppedPos) {
|
if (currentPos < droppedPos) {
|
||||||
current = i.parentElement.parentNode.insertBefore(current.parentElement, i.parentElement.nextSibling).getElementsByClassName('modifier-card-overlay')[0]
|
current = i.parentElement.parentNode
|
||||||
|
.insertBefore(current.parentElement, i.parentElement.nextSibling)
|
||||||
|
.getElementsByClassName("modifier-card-overlay")[0]
|
||||||
} else {
|
} else {
|
||||||
current = i.parentElement.parentNode.insertBefore(current.parentElement, i.parentElement).getElementsByClassName('modifier-card-overlay')[0]
|
current = i.parentElement.parentNode
|
||||||
|
.insertBefore(current.parentElement, i.parentElement)
|
||||||
|
.getElementsByClassName("modifier-card-overlay")[0]
|
||||||
}
|
}
|
||||||
// update activeTags
|
// update activeTags
|
||||||
const tag = activeTags.splice(currentPos, 1)
|
const tag = activeTags.splice(currentPos, 1)
|
||||||
activeTags.splice(droppedPos, 0, tag[0])
|
activeTags.splice(droppedPos, 0, tag[0])
|
||||||
document.dispatchEvent(new Event('refreshImageModifiers'))
|
document.dispatchEvent(new Event("refreshImageModifiers"))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
i.ondragover = (e) => {
|
i.ondragover = (e) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
}
|
}
|
||||||
|
|
||||||
i.parentElement.ondragend = (e) => {
|
i.parentElement.ondragend = (e) => {
|
||||||
i.parentElement.classList.remove('drag-sort-active')
|
i.parentElement.classList.remove("drag-sort-active")
|
||||||
for(let item of document.querySelector('#editor-inputs-tags-list').getElementsByClassName('modifier-card-image-overlay')) {
|
for (let item of document
|
||||||
item.style.opacity = ''
|
.querySelector("#editor-inputs-tags-list")
|
||||||
item.innerText = '-'
|
.getElementsByClassName("modifier-card-image-overlay")) {
|
||||||
|
item.style.opacity = ""
|
||||||
|
item.innerText = "-"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
(function () {
|
;(function() {
|
||||||
"use strict"
|
"use strict"
|
||||||
|
|
||||||
const MAX_WEIGHT = 5
|
const MAX_WEIGHT = 5
|
||||||
|
|
||||||
if (typeof editorModifierTagsList !== 'object') {
|
if (typeof editorModifierTagsList !== "object") {
|
||||||
console.error('editorModifierTagsList missing...')
|
console.error("editorModifierTagsList missing...")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// observe for changes in tag list
|
// observe for changes in tag list
|
||||||
const observer = new MutationObserver(function (mutations) {
|
const observer = new MutationObserver(function(mutations) {
|
||||||
// mutations.forEach(function (mutation) {
|
// mutations.forEach(function (mutation) {
|
||||||
if (editorModifierTagsList.childNodes.length > 0) {
|
if (editorModifierTagsList.childNodes.length > 0) {
|
||||||
ModifierMouseWheel(editorModifierTagsList)
|
ModifierMouseWheel(editorModifierTagsList)
|
||||||
@ -18,18 +18,20 @@
|
|||||||
})
|
})
|
||||||
|
|
||||||
observer.observe(editorModifierTagsList, {
|
observer.observe(editorModifierTagsList, {
|
||||||
childList: true
|
childList: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
function ModifierMouseWheel(target) {
|
function ModifierMouseWheel(target) {
|
||||||
let overlays = document.querySelector('#editor-inputs-tags-list').querySelectorAll('.modifier-card-overlay')
|
let overlays = document.querySelector("#editor-inputs-tags-list").querySelectorAll(".modifier-card-overlay")
|
||||||
overlays.forEach (i => {
|
overlays.forEach((i) => {
|
||||||
i.onwheel = (e) => {
|
i.onwheel = (e) => {
|
||||||
if (e.ctrlKey == true) {
|
if (e.ctrlKey == true) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
|
||||||
const delta = Math.sign(event.deltaY)
|
const delta = Math.sign(event.deltaY)
|
||||||
let s = i.parentElement.getElementsByClassName('modifier-card-label')[0].getElementsByTagName("p")[0].innerText
|
let s = i.parentElement
|
||||||
|
.getElementsByClassName("modifier-card-label")[0]
|
||||||
|
.getElementsByTagName("p")[0].innerText
|
||||||
let t
|
let t
|
||||||
// find the corresponding tag
|
// find the corresponding tag
|
||||||
for (let it = 0; it < overlays.length; it++) {
|
for (let it = 0; it < overlays.length; it++) {
|
||||||
@ -38,43 +40,40 @@
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (s.charAt(0) !== '(' && s.charAt(s.length - 1) !== ')' && s.trim().includes(' ')) {
|
if (s.charAt(0) !== "(" && s.charAt(s.length - 1) !== ")" && s.trim().includes(" ")) {
|
||||||
s = '(' + s + ')'
|
s = "(" + s + ")"
|
||||||
t = '(' + t + ')'
|
t = "(" + t + ")"
|
||||||
}
|
}
|
||||||
if (delta < 0) {
|
if (delta < 0) {
|
||||||
// wheel scrolling up
|
// wheel scrolling up
|
||||||
if (s.substring(s.length - 1) == '-') {
|
if (s.substring(s.length - 1) == "-") {
|
||||||
s = s.substring(0, s.length - 1)
|
s = s.substring(0, s.length - 1)
|
||||||
t = t.substring(0, t.length - 1)
|
t = t.substring(0, t.length - 1)
|
||||||
}
|
} else {
|
||||||
else
|
if (s.substring(s.length - MAX_WEIGHT) !== "+".repeat(MAX_WEIGHT)) {
|
||||||
{
|
s = s + "+"
|
||||||
if (s.substring(s.length - MAX_WEIGHT) !== '+'.repeat(MAX_WEIGHT)) {
|
t = t + "+"
|
||||||
s = s + '+'
|
|
||||||
t = t + '+'
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else{
|
|
||||||
// wheel scrolling down
|
// wheel scrolling down
|
||||||
if (s.substring(s.length - 1) == '+') {
|
if (s.substring(s.length - 1) == "+") {
|
||||||
s = s.substring(0, s.length - 1)
|
s = s.substring(0, s.length - 1)
|
||||||
t = t.substring(0, t.length - 1)
|
t = t.substring(0, t.length - 1)
|
||||||
}
|
} else {
|
||||||
else
|
if (s.substring(s.length - MAX_WEIGHT) !== "-".repeat(MAX_WEIGHT)) {
|
||||||
{
|
s = s + "-"
|
||||||
if (s.substring(s.length - MAX_WEIGHT) !== '-'.repeat(MAX_WEIGHT)) {
|
t = t + "-"
|
||||||
s = s + '-'
|
|
||||||
t = t + '-'
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (s.charAt(0) === '(' && s.charAt(s.length - 1) === ')') {
|
if (s.charAt(0) === "(" && s.charAt(s.length - 1) === ")") {
|
||||||
s = s.substring(1, s.length - 1)
|
s = s.substring(1, s.length - 1)
|
||||||
t = t.substring(1, t.length - 1)
|
t = t.substring(1, t.length - 1)
|
||||||
}
|
}
|
||||||
i.parentElement.getElementsByClassName('modifier-card-label')[0].getElementsByTagName("p")[0].innerText = s
|
i.parentElement
|
||||||
|
.getElementsByClassName("modifier-card-label")[0]
|
||||||
|
.getElementsByTagName("p")[0].innerText = s
|
||||||
// update activeTags
|
// update activeTags
|
||||||
for (let it = 0; it < overlays.length; it++) {
|
for (let it = 0; it < overlays.length; it++) {
|
||||||
if (i == overlays[it]) {
|
if (i == overlays[it]) {
|
||||||
@ -82,7 +81,7 @@
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
document.dispatchEvent(new Event('refreshImageModifiers'))
|
document.dispatchEvent(new Event("refreshImageModifiers"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -1,31 +1,31 @@
|
|||||||
(function() {
|
;(function() {
|
||||||
PLUGINS['MODIFIERS_LOAD'].push({
|
PLUGINS["MODIFIERS_LOAD"].push({
|
||||||
loader: function() {
|
loader: function() {
|
||||||
let customModifiers = localStorage.getItem(CUSTOM_MODIFIERS_KEY, '')
|
let customModifiers = localStorage.getItem(CUSTOM_MODIFIERS_KEY, "")
|
||||||
customModifiersTextBox.value = customModifiers
|
customModifiersTextBox.value = customModifiers
|
||||||
|
|
||||||
if (customModifiersGroupElement !== undefined) {
|
if (customModifiersGroupElement !== undefined) {
|
||||||
customModifiersGroupElement.remove()
|
customModifiersGroupElement.remove()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (customModifiers && customModifiers.trim() !== '') {
|
if (customModifiers && customModifiers.trim() !== "") {
|
||||||
customModifiers = customModifiers.split('\n')
|
customModifiers = customModifiers.split("\n")
|
||||||
customModifiers = customModifiers.filter(m => m.trim() !== '')
|
customModifiers = customModifiers.filter((m) => m.trim() !== "")
|
||||||
customModifiers = customModifiers.map(function(m) {
|
customModifiers = customModifiers.map(function(m) {
|
||||||
return {
|
return {
|
||||||
"modifier": m
|
modifier: m,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
let customGroup = {
|
let customGroup = {
|
||||||
'category': 'Custom Modifiers',
|
category: "Custom Modifiers",
|
||||||
'modifiers': customModifiers
|
modifiers: customModifiers,
|
||||||
}
|
}
|
||||||
|
|
||||||
customModifiersGroupElement = createModifierGroup(customGroup, true)
|
customModifiersGroupElement = createModifierGroup(customGroup, true)
|
||||||
|
|
||||||
createCollapsibles(customModifiersGroupElement)
|
createCollapsibles(customModifiersGroupElement)
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
})()
|
})()
|
||||||
|
@ -26,8 +26,8 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|||||||
after `jasmine.js` and `jasmine_html.js`, but before `boot1.js` or any project
|
after `jasmine.js` and `jasmine_html.js`, but before `boot1.js` or any project
|
||||||
source files or spec files are loaded.
|
source files or spec files are loaded.
|
||||||
*/
|
*/
|
||||||
(function() {
|
;(function() {
|
||||||
const jasmineRequire = window.jasmineRequire || require('./jasmine.js');
|
const jasmineRequire = window.jasmineRequire || require("./jasmine.js")
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ## Require & Instantiate
|
* ## Require & Instantiate
|
||||||
@ -35,30 +35,30 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|||||||
* Require Jasmine's core files. Specifically, this requires and attaches all of Jasmine's code to the `jasmine` reference.
|
* Require Jasmine's core files. Specifically, this requires and attaches all of Jasmine's code to the `jasmine` reference.
|
||||||
*/
|
*/
|
||||||
const jasmine = jasmineRequire.core(jasmineRequire),
|
const jasmine = jasmineRequire.core(jasmineRequire),
|
||||||
global = jasmine.getGlobal();
|
global = jasmine.getGlobal()
|
||||||
global.jasmine = jasmine;
|
global.jasmine = jasmine
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Since this is being run in a browser and the results should populate to an HTML page, require the HTML-specific Jasmine code, injecting the same reference.
|
* Since this is being run in a browser and the results should populate to an HTML page, require the HTML-specific Jasmine code, injecting the same reference.
|
||||||
*/
|
*/
|
||||||
jasmineRequire.html(jasmine);
|
jasmineRequire.html(jasmine)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create the Jasmine environment. This is used to run all specs in a project.
|
* Create the Jasmine environment. This is used to run all specs in a project.
|
||||||
*/
|
*/
|
||||||
const env = jasmine.getEnv();
|
const env = jasmine.getEnv()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ## The Global Interface
|
* ## The Global Interface
|
||||||
*
|
*
|
||||||
* Build up the functions that will be exposed as the Jasmine public interface. A project can customize, rename or alias any of these functions as desired, provided the implementation remains unchanged.
|
* Build up the functions that will be exposed as the Jasmine public interface. A project can customize, rename or alias any of these functions as desired, provided the implementation remains unchanged.
|
||||||
*/
|
*/
|
||||||
const jasmineInterface = jasmineRequire.interface(jasmine, env);
|
const jasmineInterface = jasmineRequire.interface(jasmine, env)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add all of the Jasmine global/public interface to the global scope, so a project can use the public interface directly. For example, calling `describe` in specs instead of `jasmine.getEnv().describe`.
|
* Add all of the Jasmine global/public interface to the global scope, so a project can use the public interface directly. For example, calling `describe` in specs instead of `jasmine.getEnv().describe`.
|
||||||
*/
|
*/
|
||||||
for (const property in jasmineInterface) {
|
for (const property in jasmineInterface) {
|
||||||
global[property] = jasmineInterface[property];
|
global[property] = jasmineInterface[property]
|
||||||
}
|
}
|
||||||
})();
|
})()
|
||||||
|
@ -33,8 +33,8 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|||||||
after `boot0.js` is loaded and before this file is loaded.
|
after `boot0.js` is loaded and before this file is loaded.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
(function() {
|
;(function() {
|
||||||
const env = jasmine.getEnv();
|
const env = jasmine.getEnv()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ## Runner Parameters
|
* ## Runner Parameters
|
||||||
@ -44,29 +44,27 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|||||||
|
|
||||||
const queryString = new jasmine.QueryString({
|
const queryString = new jasmine.QueryString({
|
||||||
getWindowLocation: function() {
|
getWindowLocation: function() {
|
||||||
return window.location;
|
return window.location
|
||||||
}
|
},
|
||||||
});
|
})
|
||||||
|
|
||||||
const filterSpecs = !!queryString.getParam('spec');
|
const filterSpecs = !!queryString.getParam("spec")
|
||||||
|
|
||||||
const config = {
|
const config = {
|
||||||
stopOnSpecFailure: queryString.getParam('stopOnSpecFailure'),
|
stopOnSpecFailure: queryString.getParam("stopOnSpecFailure"),
|
||||||
stopSpecOnExpectationFailure: queryString.getParam(
|
stopSpecOnExpectationFailure: queryString.getParam("stopSpecOnExpectationFailure"),
|
||||||
'stopSpecOnExpectationFailure'
|
hideDisabled: queryString.getParam("hideDisabled"),
|
||||||
),
|
|
||||||
hideDisabled: queryString.getParam('hideDisabled')
|
|
||||||
};
|
|
||||||
|
|
||||||
const random = queryString.getParam('random');
|
|
||||||
|
|
||||||
if (random !== undefined && random !== '') {
|
|
||||||
config.random = random;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const seed = queryString.getParam('seed');
|
const random = queryString.getParam("random")
|
||||||
|
|
||||||
|
if (random !== undefined && random !== "") {
|
||||||
|
config.random = random
|
||||||
|
}
|
||||||
|
|
||||||
|
const seed = queryString.getParam("seed")
|
||||||
if (seed) {
|
if (seed) {
|
||||||
config.seed = seed;
|
config.seed = seed
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -76,57 +74,57 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|||||||
const htmlReporter = new jasmine.HtmlReporter({
|
const htmlReporter = new jasmine.HtmlReporter({
|
||||||
env: env,
|
env: env,
|
||||||
navigateWithNewParam: function(key, value) {
|
navigateWithNewParam: function(key, value) {
|
||||||
return queryString.navigateWithNewParam(key, value);
|
return queryString.navigateWithNewParam(key, value)
|
||||||
},
|
},
|
||||||
addToExistingQueryString: function(key, value) {
|
addToExistingQueryString: function(key, value) {
|
||||||
return queryString.fullStringWithNewParam(key, value);
|
return queryString.fullStringWithNewParam(key, value)
|
||||||
},
|
},
|
||||||
getContainer: function() {
|
getContainer: function() {
|
||||||
return document.body;
|
return document.body
|
||||||
},
|
},
|
||||||
createElement: function() {
|
createElement: function() {
|
||||||
return document.createElement.apply(document, arguments);
|
return document.createElement.apply(document, arguments)
|
||||||
},
|
},
|
||||||
createTextNode: function() {
|
createTextNode: function() {
|
||||||
return document.createTextNode.apply(document, arguments);
|
return document.createTextNode.apply(document, arguments)
|
||||||
},
|
},
|
||||||
timer: new jasmine.Timer(),
|
timer: new jasmine.Timer(),
|
||||||
filterSpecs: filterSpecs
|
filterSpecs: filterSpecs,
|
||||||
});
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The `jsApiReporter` also receives spec results, and is used by any environment that needs to extract the results from JavaScript.
|
* The `jsApiReporter` also receives spec results, and is used by any environment that needs to extract the results from JavaScript.
|
||||||
*/
|
*/
|
||||||
env.addReporter(jsApiReporter);
|
env.addReporter(jsApiReporter)
|
||||||
env.addReporter(htmlReporter);
|
env.addReporter(htmlReporter)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Filter which specs will be run by matching the start of the full name against the `spec` query param.
|
* Filter which specs will be run by matching the start of the full name against the `spec` query param.
|
||||||
*/
|
*/
|
||||||
const specFilter = new jasmine.HtmlSpecFilter({
|
const specFilter = new jasmine.HtmlSpecFilter({
|
||||||
filterString: function() {
|
filterString: function() {
|
||||||
return queryString.getParam('spec');
|
return queryString.getParam("spec")
|
||||||
}
|
},
|
||||||
});
|
})
|
||||||
|
|
||||||
config.specFilter = function(spec) {
|
config.specFilter = function(spec) {
|
||||||
return specFilter.matches(spec.getFullName());
|
return specFilter.matches(spec.getFullName())
|
||||||
};
|
}
|
||||||
|
|
||||||
env.configure(config);
|
env.configure(config)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ## Execution
|
* ## Execution
|
||||||
*
|
*
|
||||||
* Replace the browser window's `onload`, ensure it's called, and then run all of the loaded specs. This includes initializing the `HtmlReporter` instance and then executing the loaded Jasmine environment. All of this will happen after all of the specs are loaded.
|
* Replace the browser window's `onload`, ensure it's called, and then run all of the loaded specs. This includes initializing the `HtmlReporter` instance and then executing the loaded Jasmine environment. All of this will happen after all of the specs are loaded.
|
||||||
*/
|
*/
|
||||||
const currentWindowOnload = window.onload;
|
const currentWindowOnload = window.onload
|
||||||
|
|
||||||
window.onload = function() {
|
window.onload = function() {
|
||||||
if (currentWindowOnload) {
|
if (currentWindowOnload) {
|
||||||
currentWindowOnload();
|
currentWindowOnload()
|
||||||
}
|
}
|
||||||
htmlReporter.initialize();
|
htmlReporter.initialize()
|
||||||
env.execute();
|
env.execute()
|
||||||
};
|
}
|
||||||
})();
|
})()
|
||||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -2,34 +2,34 @@
|
|||||||
|
|
||||||
const JASMINE_SESSION_ID = `jasmine-${String(Date.now()).slice(8)}`
|
const JASMINE_SESSION_ID = `jasmine-${String(Date.now()).slice(8)}`
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function() {
|
||||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 15 * 60 * 1000 // Test timeout after 15 minutes
|
jasmine.DEFAULT_TIMEOUT_INTERVAL = 15 * 60 * 1000 // Test timeout after 15 minutes
|
||||||
jasmine.addMatchers({
|
jasmine.addMatchers({
|
||||||
toBeOneOf: function () {
|
toBeOneOf: function() {
|
||||||
return {
|
return {
|
||||||
compare: function (actual, expected) {
|
compare: function(actual, expected) {
|
||||||
return {
|
return {
|
||||||
pass: expected.includes(actual)
|
pass: expected.includes(actual),
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
},
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
describe('stable-diffusion-ui', function() {
|
describe("stable-diffusion-ui", function() {
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
expect(typeof SD).toBe('object')
|
expect(typeof SD).toBe("object")
|
||||||
expect(typeof SD.serverState).toBe('object')
|
expect(typeof SD.serverState).toBe("object")
|
||||||
expect(typeof SD.serverState.status).toBe('string')
|
expect(typeof SD.serverState.status).toBe("string")
|
||||||
})
|
})
|
||||||
it('should be able to reach the backend', async function() {
|
it("should be able to reach the backend", async function() {
|
||||||
expect(SD.serverState.status).toBe(SD.ServerStates.unavailable)
|
expect(SD.serverState.status).toBe(SD.ServerStates.unavailable)
|
||||||
SD.sessionId = JASMINE_SESSION_ID
|
SD.sessionId = JASMINE_SESSION_ID
|
||||||
await SD.init()
|
await SD.init()
|
||||||
expect(SD.isServerAvailable()).toBeTrue()
|
expect(SD.isServerAvailable()).toBeTrue()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('enfore the current task state', function() {
|
it("enfore the current task state", function() {
|
||||||
const task = new SD.Task()
|
const task = new SD.Task()
|
||||||
expect(task.status).toBe(SD.TaskStatus.init)
|
expect(task.status).toBe(SD.TaskStatus.init)
|
||||||
expect(task.isPending).toBeTrue()
|
expect(task.isPending).toBeTrue()
|
||||||
@ -65,149 +65,161 @@ describe('stable-diffusion-ui', function() {
|
|||||||
task._setStatus(SD.TaskStatus.completed)
|
task._setStatus(SD.TaskStatus.completed)
|
||||||
}).toThrowError()
|
}).toThrowError()
|
||||||
})
|
})
|
||||||
it('should be able to run tasks', async function() {
|
it("should be able to run tasks", async function() {
|
||||||
expect(typeof SD.Task.run).toBe('function')
|
expect(typeof SD.Task.run).toBe("function")
|
||||||
const promiseGenerator = (function*(val) {
|
const promiseGenerator = (function*(val) {
|
||||||
expect(val).toBe('start')
|
expect(val).toBe("start")
|
||||||
expect(yield 1 + 1).toBe(4)
|
expect(yield 1 + 1).toBe(4)
|
||||||
expect(yield 2 + 2).toBe(8)
|
expect(yield 2 + 2).toBe(8)
|
||||||
yield asyncDelay(500)
|
yield asyncDelay(500)
|
||||||
expect(yield 3 + 3).toBe(12)
|
expect(yield 3 + 3).toBe(12)
|
||||||
expect(yield 4 + 4).toBe(16)
|
expect(yield 4 + 4).toBe(16)
|
||||||
return 8 + 8
|
return 8 + 8
|
||||||
})('start')
|
})("start")
|
||||||
const callback = function({value, done}) {
|
const callback = function({ value, done }) {
|
||||||
return {value: 2 * value, done}
|
return { value: 2 * value, done }
|
||||||
}
|
}
|
||||||
expect(await SD.Task.run(promiseGenerator, {callback})).toBe(32)
|
expect(await SD.Task.run(promiseGenerator, { callback })).toBe(32)
|
||||||
})
|
})
|
||||||
it('should be able to queue tasks', async function() {
|
it("should be able to queue tasks", async function() {
|
||||||
expect(typeof SD.Task.enqueue).toBe('function')
|
expect(typeof SD.Task.enqueue).toBe("function")
|
||||||
const promiseGenerator = (function*(val) {
|
const promiseGenerator = (function*(val) {
|
||||||
expect(val).toBe('start')
|
expect(val).toBe("start")
|
||||||
expect(yield 1 + 1).toBe(4)
|
expect(yield 1 + 1).toBe(4)
|
||||||
expect(yield 2 + 2).toBe(8)
|
expect(yield 2 + 2).toBe(8)
|
||||||
yield asyncDelay(500)
|
yield asyncDelay(500)
|
||||||
expect(yield 3 + 3).toBe(12)
|
expect(yield 3 + 3).toBe(12)
|
||||||
expect(yield 4 + 4).toBe(16)
|
expect(yield 4 + 4).toBe(16)
|
||||||
return 8 + 8
|
return 8 + 8
|
||||||
})('start')
|
})("start")
|
||||||
const callback = function({value, done}) {
|
const callback = function({ value, done }) {
|
||||||
return {value: 2 * value, done}
|
return { value: 2 * value, done }
|
||||||
}
|
}
|
||||||
const gen = SD.Task.asGenerator({generator: promiseGenerator, callback})
|
const gen = SD.Task.asGenerator({ generator: promiseGenerator, callback })
|
||||||
expect(await SD.Task.enqueue(gen)).toBe(32)
|
expect(await SD.Task.enqueue(gen)).toBe(32)
|
||||||
})
|
})
|
||||||
it('should be able to chain handlers', async function() {
|
it("should be able to chain handlers", async function() {
|
||||||
expect(typeof SD.Task.enqueue).toBe('function')
|
expect(typeof SD.Task.enqueue).toBe("function")
|
||||||
const promiseGenerator = (function*(val) {
|
const promiseGenerator = (function*(val) {
|
||||||
expect(val).toBe('start')
|
expect(val).toBe("start")
|
||||||
expect(yield {test: '1'}).toEqual({test: '1', foo: 'bar'})
|
expect(yield { test: "1" }).toEqual({ test: "1", foo: "bar" })
|
||||||
expect(yield 2 + 2).toEqual(8)
|
expect(yield 2 + 2).toEqual(8)
|
||||||
yield asyncDelay(500)
|
yield asyncDelay(500)
|
||||||
expect(yield 3 + 3).toEqual(12)
|
expect(yield 3 + 3).toEqual(12)
|
||||||
expect(yield {test: 4}).toEqual({test: 8, foo: 'bar'})
|
expect(yield { test: 4 }).toEqual({ test: 8, foo: "bar" })
|
||||||
return {test: 8}
|
return { test: 8 }
|
||||||
})('start')
|
})("start")
|
||||||
const gen1 = SD.Task.asGenerator({generator: promiseGenerator, callback: function({value, done}) {
|
const gen1 = SD.Task.asGenerator({
|
||||||
|
generator: promiseGenerator,
|
||||||
|
callback: function({ value, done }) {
|
||||||
if (typeof value === "object") {
|
if (typeof value === "object") {
|
||||||
value['foo'] = 'bar'
|
value["foo"] = "bar"
|
||||||
}
|
}
|
||||||
return {value, done}
|
return { value, done }
|
||||||
}})
|
},
|
||||||
const gen2 = SD.Task.asGenerator({generator: gen1, callback: function({value, done}) {
|
})
|
||||||
if (typeof value === 'number') {
|
const gen2 = SD.Task.asGenerator({
|
||||||
|
generator: gen1,
|
||||||
|
callback: function({ value, done }) {
|
||||||
|
if (typeof value === "number") {
|
||||||
value = 2 * value
|
value = 2 * value
|
||||||
}
|
}
|
||||||
if (typeof value === 'object' && typeof value.test === 'number') {
|
if (typeof value === "object" && typeof value.test === "number") {
|
||||||
value.test = 2 * value.test
|
value.test = 2 * value.test
|
||||||
}
|
}
|
||||||
return {value, done}
|
return { value, done }
|
||||||
}})
|
},
|
||||||
expect(await SD.Task.enqueue(gen2)).toEqual({test:32, foo: 'bar'})
|
|
||||||
})
|
})
|
||||||
describe('ServiceContainer', function() {
|
expect(await SD.Task.enqueue(gen2)).toEqual({ test: 32, foo: "bar" })
|
||||||
it('should be able to register providers', function() {
|
})
|
||||||
|
describe("ServiceContainer", function() {
|
||||||
|
it("should be able to register providers", function() {
|
||||||
const cont = new ServiceContainer(
|
const cont = new ServiceContainer(
|
||||||
function foo() {
|
function foo() {
|
||||||
this.bar = ''
|
this.bar = ""
|
||||||
},
|
},
|
||||||
function bar() {
|
function bar() {
|
||||||
return () => 0
|
return () => 0
|
||||||
},
|
},
|
||||||
{ name: 'zero', definition: 0 },
|
{ name: "zero", definition: 0 },
|
||||||
{ name: 'ctx', definition: () => Object.create(null), singleton: true },
|
{ name: "ctx", definition: () => Object.create(null), singleton: true },
|
||||||
{ name: 'test',
|
{
|
||||||
|
name: "test",
|
||||||
definition: (ctx, missing, one, foo) => {
|
definition: (ctx, missing, one, foo) => {
|
||||||
expect(ctx).toEqual({ran: true})
|
expect(ctx).toEqual({ ran: true })
|
||||||
expect(one).toBe(1)
|
expect(one).toBe(1)
|
||||||
expect(typeof foo).toBe('object')
|
expect(typeof foo).toBe("object")
|
||||||
expect(foo.bar).toBeDefined()
|
expect(foo.bar).toBeDefined()
|
||||||
expect(typeof missing).toBe('undefined')
|
expect(typeof missing).toBe("undefined")
|
||||||
return {foo: 'bar'}
|
return { foo: "bar" }
|
||||||
}, dependencies: ['ctx', 'missing', 'one', 'foo']
|
},
|
||||||
|
dependencies: ["ctx", "missing", "one", "foo"],
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
const fooObj = cont.get('foo')
|
const fooObj = cont.get("foo")
|
||||||
expect(typeof fooObj).toBe('object')
|
expect(typeof fooObj).toBe("object")
|
||||||
fooObj.ran = true
|
fooObj.ran = true
|
||||||
|
|
||||||
const ctx = cont.get('ctx')
|
const ctx = cont.get("ctx")
|
||||||
expect(ctx).toEqual({})
|
expect(ctx).toEqual({})
|
||||||
ctx.ran = true
|
ctx.ran = true
|
||||||
|
|
||||||
const bar = cont.get('bar')
|
const bar = cont.get("bar")
|
||||||
expect(typeof bar).toBe('function')
|
expect(typeof bar).toBe("function")
|
||||||
expect(bar()).toBe(0)
|
expect(bar()).toBe(0)
|
||||||
|
|
||||||
cont.register({name: 'one', definition: 1})
|
cont.register({ name: "one", definition: 1 })
|
||||||
const test = cont.get('test')
|
const test = cont.get("test")
|
||||||
expect(typeof test).toBe('object')
|
expect(typeof test).toBe("object")
|
||||||
expect(test.foo).toBe('bar')
|
expect(test.foo).toBe("bar")
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
it('should be able to stream data in chunks', async function() {
|
it("should be able to stream data in chunks", async function() {
|
||||||
expect(SD.isServerAvailable()).toBeTrue()
|
expect(SD.isServerAvailable()).toBeTrue()
|
||||||
const nbr_steps = 15
|
const nbr_steps = 15
|
||||||
let res = await fetch('/render', {
|
let res = await fetch("/render", {
|
||||||
method: 'POST',
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json'
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
"prompt": "a photograph of an astronaut riding a horse",
|
prompt: "a photograph of an astronaut riding a horse",
|
||||||
"negative_prompt": "",
|
negative_prompt: "",
|
||||||
"width": 128,
|
width: 128,
|
||||||
"height": 128,
|
height: 128,
|
||||||
"seed": Math.floor(Math.random() * 10000000),
|
seed: Math.floor(Math.random() * 10000000),
|
||||||
|
|
||||||
"sampler": "plms",
|
sampler: "plms",
|
||||||
"use_stable_diffusion_model": "sd-v1-4",
|
use_stable_diffusion_model: "sd-v1-4",
|
||||||
"num_inference_steps": nbr_steps,
|
num_inference_steps: nbr_steps,
|
||||||
"guidance_scale": 7.5,
|
guidance_scale: 7.5,
|
||||||
|
|
||||||
"numOutputsParallel": 1,
|
numOutputsParallel: 1,
|
||||||
"stream_image_progress": true,
|
stream_image_progress: true,
|
||||||
"show_only_filtered_image": true,
|
show_only_filtered_image: true,
|
||||||
"output_format": "jpeg",
|
output_format: "jpeg",
|
||||||
|
|
||||||
"session_id": JASMINE_SESSION_ID,
|
session_id: JASMINE_SESSION_ID,
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
expect(res.ok).toBeTruthy()
|
expect(res.ok).toBeTruthy()
|
||||||
const renderRequest = await res.json()
|
const renderRequest = await res.json()
|
||||||
expect(typeof renderRequest.stream).toBe('string')
|
expect(typeof renderRequest.stream).toBe("string")
|
||||||
expect(renderRequest.task).toBeDefined()
|
expect(renderRequest.task).toBeDefined()
|
||||||
|
|
||||||
// Wait for server status to update.
|
// Wait for server status to update.
|
||||||
await SD.waitUntil(() => {
|
await SD.waitUntil(
|
||||||
console.log('Waiting for %s to be received...', renderRequest.task)
|
() => {
|
||||||
return (!SD.serverState.tasks || SD.serverState.tasks[String(renderRequest.task)])
|
console.log("Waiting for %s to be received...", renderRequest.task)
|
||||||
}, 250, 10 * 60 * 1000)
|
return !SD.serverState.tasks || SD.serverState.tasks[String(renderRequest.task)]
|
||||||
|
},
|
||||||
|
250,
|
||||||
|
10 * 60 * 1000
|
||||||
|
)
|
||||||
// Wait for task to start on server.
|
// Wait for task to start on server.
|
||||||
await SD.waitUntil(() => {
|
await SD.waitUntil(() => {
|
||||||
console.log('Waiting for %s to start...', renderRequest.task)
|
console.log("Waiting for %s to start...", renderRequest.task)
|
||||||
return !SD.serverState.tasks || SD.serverState.tasks[String(renderRequest.task)] !== 'pending'
|
return !SD.serverState.tasks || SD.serverState.tasks[String(renderRequest.task)] !== "pending"
|
||||||
}, 250)
|
}, 250)
|
||||||
|
|
||||||
const reader = new SD.ChunkedStreamReader(renderRequest.stream)
|
const reader = new SD.ChunkedStreamReader(renderRequest.stream)
|
||||||
@ -217,24 +229,24 @@ describe('stable-diffusion-ui', function() {
|
|||||||
if (!value || value.length <= 0) {
|
if (!value || value.length <= 0) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
return reader.readStreamAsJSON(value.join(''))
|
return reader.readStreamAsJSON(value.join(""))
|
||||||
}
|
}
|
||||||
reader.onNext = function({done, value}) {
|
reader.onNext = function({ done, value }) {
|
||||||
console.log(value)
|
console.log(value)
|
||||||
if (typeof value === 'object' && 'status' in value) {
|
if (typeof value === "object" && "status" in value) {
|
||||||
done = true
|
done = true
|
||||||
}
|
}
|
||||||
return {done, value}
|
return { done, value }
|
||||||
}
|
}
|
||||||
let lastUpdate = undefined
|
let lastUpdate = undefined
|
||||||
let stepCount = 0
|
let stepCount = 0
|
||||||
let complete = false
|
let complete = false
|
||||||
//for await (const stepUpdate of reader) {
|
//for await (const stepUpdate of reader) {
|
||||||
for await (const stepUpdate of reader.open()) {
|
for await (const stepUpdate of reader.open()) {
|
||||||
console.log('ChunkedStreamReader received ', stepUpdate)
|
console.log("ChunkedStreamReader received ", stepUpdate)
|
||||||
lastUpdate = stepUpdate
|
lastUpdate = stepUpdate
|
||||||
if (complete) {
|
if (complete) {
|
||||||
expect(stepUpdate.status).toBe('succeeded')
|
expect(stepUpdate.status).toBe("succeeded")
|
||||||
expect(stepUpdate.output).toHaveSize(1)
|
expect(stepUpdate.output).toHaveSize(1)
|
||||||
} else {
|
} else {
|
||||||
expect(stepUpdate.total_steps).toBe(nbr_steps)
|
expect(stepUpdate.total_steps).toBe(nbr_steps)
|
||||||
@ -246,37 +258,39 @@ describe('stable-diffusion-ui', function() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for(let i=1; i <= 5; ++i) {
|
for (let i = 1; i <= 5; ++i) {
|
||||||
res = await fetch(renderRequest.stream)
|
res = await fetch(renderRequest.stream)
|
||||||
expect(res.ok).toBeTruthy()
|
expect(res.ok).toBeTruthy()
|
||||||
const cachedResponse = await res.json()
|
const cachedResponse = await res.json()
|
||||||
console.log('Cache test %s received %o', i, cachedResponse)
|
console.log("Cache test %s received %o", i, cachedResponse)
|
||||||
expect(lastUpdate).toEqual(cachedResponse)
|
expect(lastUpdate).toEqual(cachedResponse)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('should be able to make renders', function() {
|
describe("should be able to make renders", function() {
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
expect(SD.isServerAvailable()).toBeTrue()
|
expect(SD.isServerAvailable()).toBeTrue()
|
||||||
})
|
})
|
||||||
it('basic inline request', async function() {
|
it("basic inline request", async function() {
|
||||||
let stepCount = 0
|
let stepCount = 0
|
||||||
let complete = false
|
let complete = false
|
||||||
const result = await SD.render({
|
const result = await SD.render(
|
||||||
"prompt": "a photograph of an astronaut riding a horse",
|
{
|
||||||
"width": 128,
|
prompt: "a photograph of an astronaut riding a horse",
|
||||||
"height": 128,
|
width: 128,
|
||||||
"num_inference_steps": 10,
|
height: 128,
|
||||||
"show_only_filtered_image": false,
|
num_inference_steps: 10,
|
||||||
|
show_only_filtered_image: false,
|
||||||
//"use_face_correction": 'GFPGANv1.3',
|
//"use_face_correction": 'GFPGANv1.3',
|
||||||
"use_upscale": "RealESRGAN_x4plus",
|
use_upscale: "RealESRGAN_x4plus",
|
||||||
"session_id": JASMINE_SESSION_ID,
|
session_id: JASMINE_SESSION_ID,
|
||||||
}, function(event) {
|
},
|
||||||
|
function(event) {
|
||||||
console.log(this, event)
|
console.log(this, event)
|
||||||
if ('update' in event) {
|
if ("update" in event) {
|
||||||
const stepUpdate = event.update
|
const stepUpdate = event.update
|
||||||
if (complete || (stepUpdate.status && stepUpdate.step === stepUpdate.total_steps)) {
|
if (complete || (stepUpdate.status && stepUpdate.step === stepUpdate.total_steps)) {
|
||||||
expect(stepUpdate.status).toBe('succeeded')
|
expect(stepUpdate.status).toBe("succeeded")
|
||||||
expect(stepUpdate.output).toHaveSize(2)
|
expect(stepUpdate.output).toHaveSize(2)
|
||||||
} else {
|
} else {
|
||||||
expect(stepUpdate.step).toBe(stepCount)
|
expect(stepUpdate.step).toBe(stepCount)
|
||||||
@ -287,29 +301,33 @@ describe('stable-diffusion-ui', function() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
)
|
||||||
console.log(result)
|
console.log(result)
|
||||||
expect(result.status).toBe('succeeded')
|
expect(result.status).toBe("succeeded")
|
||||||
expect(result.output).toHaveSize(2)
|
expect(result.output).toHaveSize(2)
|
||||||
})
|
})
|
||||||
it('post and reader request', async function() {
|
it("post and reader request", async function() {
|
||||||
const renderTask = new SD.RenderTask({
|
const renderTask = new SD.RenderTask({
|
||||||
"prompt": "a photograph of an astronaut riding a horse",
|
prompt: "a photograph of an astronaut riding a horse",
|
||||||
"width": 128,
|
width: 128,
|
||||||
"height": 128,
|
height: 128,
|
||||||
"seed": SD.MAX_SEED_VALUE,
|
seed: SD.MAX_SEED_VALUE,
|
||||||
"num_inference_steps": 10,
|
num_inference_steps: 10,
|
||||||
"session_id": JASMINE_SESSION_ID,
|
session_id: JASMINE_SESSION_ID,
|
||||||
})
|
})
|
||||||
expect(renderTask.status).toBe(SD.TaskStatus.init)
|
expect(renderTask.status).toBe(SD.TaskStatus.init)
|
||||||
|
|
||||||
const timeout = -1
|
const timeout = -1
|
||||||
const renderRequest = await renderTask.post(timeout)
|
const renderRequest = await renderTask.post(timeout)
|
||||||
expect(typeof renderRequest.stream).toBe('string')
|
expect(typeof renderRequest.stream).toBe("string")
|
||||||
expect(renderTask.status).toBe(SD.TaskStatus.waiting)
|
expect(renderTask.status).toBe(SD.TaskStatus.waiting)
|
||||||
expect(renderTask.streamUrl).toBe(renderRequest.stream)
|
expect(renderTask.streamUrl).toBe(renderRequest.stream)
|
||||||
|
|
||||||
await renderTask.waitUntil({state: SD.TaskStatus.processing, callback: () => console.log('Waiting for render task to start...') })
|
await renderTask.waitUntil({
|
||||||
|
state: SD.TaskStatus.processing,
|
||||||
|
callback: () => console.log("Waiting for render task to start..."),
|
||||||
|
})
|
||||||
expect(renderTask.status).toBe(SD.TaskStatus.processing)
|
expect(renderTask.status).toBe(SD.TaskStatus.processing)
|
||||||
|
|
||||||
let stepCount = 0
|
let stepCount = 0
|
||||||
@ -318,7 +336,7 @@ describe('stable-diffusion-ui', function() {
|
|||||||
for await (const stepUpdate of renderTask.reader.open()) {
|
for await (const stepUpdate of renderTask.reader.open()) {
|
||||||
console.log(stepUpdate)
|
console.log(stepUpdate)
|
||||||
if (complete || (stepUpdate.status && stepUpdate.step === stepUpdate.total_steps)) {
|
if (complete || (stepUpdate.status && stepUpdate.step === stepUpdate.total_steps)) {
|
||||||
expect(stepUpdate.status).toBe('succeeded')
|
expect(stepUpdate.status).toBe("succeeded")
|
||||||
expect(stepUpdate.output).toHaveSize(1)
|
expect(stepUpdate.output).toHaveSize(1)
|
||||||
} else {
|
} else {
|
||||||
expect(stepUpdate.step).toBe(stepCount)
|
expect(stepUpdate.step).toBe(stepCount)
|
||||||
@ -330,28 +348,28 @@ describe('stable-diffusion-ui', function() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
expect(renderTask.status).toBe(SD.TaskStatus.completed)
|
expect(renderTask.status).toBe(SD.TaskStatus.completed)
|
||||||
expect(renderTask.result.status).toBe('succeeded')
|
expect(renderTask.result.status).toBe("succeeded")
|
||||||
expect(renderTask.result.output).toHaveSize(1)
|
expect(renderTask.result.output).toHaveSize(1)
|
||||||
})
|
})
|
||||||
it('queued request', async function() {
|
it("queued request", async function() {
|
||||||
let stepCount = 0
|
let stepCount = 0
|
||||||
let complete = false
|
let complete = false
|
||||||
const renderTask = new SD.RenderTask({
|
const renderTask = new SD.RenderTask({
|
||||||
"prompt": "a photograph of an astronaut riding a horse",
|
prompt: "a photograph of an astronaut riding a horse",
|
||||||
"width": 128,
|
width: 128,
|
||||||
"height": 128,
|
height: 128,
|
||||||
"num_inference_steps": 10,
|
num_inference_steps: 10,
|
||||||
"show_only_filtered_image": false,
|
show_only_filtered_image: false,
|
||||||
//"use_face_correction": 'GFPGANv1.3',
|
//"use_face_correction": 'GFPGANv1.3',
|
||||||
"use_upscale": "RealESRGAN_x4plus",
|
use_upscale: "RealESRGAN_x4plus",
|
||||||
"session_id": JASMINE_SESSION_ID,
|
session_id: JASMINE_SESSION_ID,
|
||||||
})
|
})
|
||||||
await renderTask.enqueue(function(event) {
|
await renderTask.enqueue(function(event) {
|
||||||
console.log(this, event)
|
console.log(this, event)
|
||||||
if ('update' in event) {
|
if ("update" in event) {
|
||||||
const stepUpdate = event.update
|
const stepUpdate = event.update
|
||||||
if (complete || (stepUpdate.status && stepUpdate.step === stepUpdate.total_steps)) {
|
if (complete || (stepUpdate.status && stepUpdate.step === stepUpdate.total_steps)) {
|
||||||
expect(stepUpdate.status).toBe('succeeded')
|
expect(stepUpdate.status).toBe("succeeded")
|
||||||
expect(stepUpdate.output).toHaveSize(2)
|
expect(stepUpdate.output).toHaveSize(2)
|
||||||
} else {
|
} else {
|
||||||
expect(stepUpdate.step).toBe(stepCount)
|
expect(stepUpdate.step).toBe(stepCount)
|
||||||
@ -364,12 +382,12 @@ describe('stable-diffusion-ui', function() {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
console.log(renderTask.result)
|
console.log(renderTask.result)
|
||||||
expect(renderTask.result.status).toBe('succeeded')
|
expect(renderTask.result.status).toBe("succeeded")
|
||||||
expect(renderTask.result.output).toHaveSize(2)
|
expect(renderTask.result.output).toHaveSize(2)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
describe('# Special cases', function() {
|
describe("# Special cases", function() {
|
||||||
it('should throw an exception on set for invalid sessionId', function() {
|
it("should throw an exception on set for invalid sessionId", function() {
|
||||||
expect(function() {
|
expect(function() {
|
||||||
SD.sessionId = undefined
|
SD.sessionId = undefined
|
||||||
}).toThrowError("Can't set sessionId to undefined.")
|
}).toThrowError("Can't set sessionId to undefined.")
|
||||||
@ -386,16 +404,17 @@ if (!PLUGINS.SELFTEST) {
|
|||||||
PLUGINS.SELFTEST = {}
|
PLUGINS.SELFTEST = {}
|
||||||
}
|
}
|
||||||
loadUIPlugins().then(function() {
|
loadUIPlugins().then(function() {
|
||||||
console.log('loadCompleted', loadEvent)
|
console.log("loadCompleted", loadEvent)
|
||||||
describe('@Plugins', function() {
|
describe("@Plugins", function() {
|
||||||
it('exposes hooks to overide', function() {
|
it("exposes hooks to overide", function() {
|
||||||
expect(typeof PLUGINS.IMAGE_INFO_BUTTONS).toBe('object')
|
expect(typeof PLUGINS.IMAGE_INFO_BUTTONS).toBe("object")
|
||||||
expect(typeof PLUGINS.TASK_CREATE).toBe('object')
|
expect(typeof PLUGINS.TASK_CREATE).toBe("object")
|
||||||
})
|
})
|
||||||
describe('supports selftests', function() { // Hook to allow plugins to define tests.
|
describe("supports selftests", function() {
|
||||||
|
// Hook to allow plugins to define tests.
|
||||||
const pluginsTests = Object.keys(PLUGINS.SELFTEST).filter((key) => PLUGINS.SELFTEST.hasOwnProperty(key))
|
const pluginsTests = Object.keys(PLUGINS.SELFTEST).filter((key) => PLUGINS.SELFTEST.hasOwnProperty(key))
|
||||||
if (!pluginsTests || pluginsTests.length <= 0) {
|
if (!pluginsTests || pluginsTests.length <= 0) {
|
||||||
it('but nothing loaded...', function() {
|
it("but nothing loaded...", function() {
|
||||||
expect(true).toBeTruthy()
|
expect(true).toBeTruthy()
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
(function() {
|
;(function() {
|
||||||
"use strict"
|
"use strict"
|
||||||
|
|
||||||
///////////////////// Function section
|
///////////////////// Function section
|
||||||
@ -18,146 +18,133 @@
|
|||||||
return y
|
return y
|
||||||
}
|
}
|
||||||
function getCurrentTime() {
|
function getCurrentTime() {
|
||||||
const now = new Date();
|
const now = new Date()
|
||||||
let hours = now.getHours();
|
let hours = now.getHours()
|
||||||
let minutes = now.getMinutes();
|
let minutes = now.getMinutes()
|
||||||
let seconds = now.getSeconds();
|
let seconds = now.getSeconds()
|
||||||
|
|
||||||
hours = hours < 10 ? `0${hours}` : hours;
|
hours = hours < 10 ? `0${hours}` : hours
|
||||||
minutes = minutes < 10 ? `0${minutes}` : minutes;
|
minutes = minutes < 10 ? `0${minutes}` : minutes
|
||||||
seconds = seconds < 10 ? `0${seconds}` : seconds;
|
seconds = seconds < 10 ? `0${seconds}` : seconds
|
||||||
|
|
||||||
return `${hours}:${minutes}:${seconds}`;
|
return `${hours}:${minutes}:${seconds}`
|
||||||
}
|
}
|
||||||
|
|
||||||
function addLogMessage(message) {
|
function addLogMessage(message) {
|
||||||
const logContainer = document.getElementById('merge-log');
|
const logContainer = document.getElementById("merge-log")
|
||||||
logContainer.innerHTML += `<i>${getCurrentTime()}</i> ${message}<br>`;
|
logContainer.innerHTML += `<i>${getCurrentTime()}</i> ${message}<br>`
|
||||||
|
|
||||||
// Scroll to the bottom of the log
|
// Scroll to the bottom of the log
|
||||||
logContainer.scrollTop = logContainer.scrollHeight;
|
logContainer.scrollTop = logContainer.scrollHeight
|
||||||
|
|
||||||
document.querySelector('#merge-log-container').style.display = 'block'
|
document.querySelector("#merge-log-container").style.display = "block"
|
||||||
}
|
}
|
||||||
|
|
||||||
function addLogSeparator() {
|
function addLogSeparator() {
|
||||||
const logContainer = document.getElementById('merge-log');
|
const logContainer = document.getElementById("merge-log")
|
||||||
logContainer.innerHTML += '<hr>'
|
logContainer.innerHTML += "<hr>"
|
||||||
|
|
||||||
logContainer.scrollTop = logContainer.scrollHeight;
|
logContainer.scrollTop = logContainer.scrollHeight
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawDiagram(fn) {
|
function drawDiagram(fn) {
|
||||||
const SIZE = 300
|
const SIZE = 300
|
||||||
const canvas = document.getElementById('merge-canvas');
|
const canvas = document.getElementById("merge-canvas")
|
||||||
canvas.height = canvas.width = SIZE
|
canvas.height = canvas.width = SIZE
|
||||||
const ctx = canvas.getContext('2d');
|
const ctx = canvas.getContext("2d")
|
||||||
|
|
||||||
// Draw coordinate system
|
// Draw coordinate system
|
||||||
ctx.scale(1, -1);
|
ctx.scale(1, -1)
|
||||||
ctx.translate(0, -canvas.height);
|
ctx.translate(0, -canvas.height)
|
||||||
ctx.lineWidth = 1;
|
ctx.lineWidth = 1
|
||||||
ctx.beginPath();
|
ctx.beginPath()
|
||||||
|
|
||||||
ctx.strokeStyle = 'white'
|
ctx.strokeStyle = "white"
|
||||||
ctx.moveTo(0,0); ctx.lineTo(0,SIZE); ctx.lineTo(SIZE,SIZE); ctx.lineTo(SIZE,0); ctx.lineTo(0,0); ctx.lineTo(SIZE,SIZE);
|
ctx.moveTo(0, 0)
|
||||||
|
ctx.lineTo(0, SIZE)
|
||||||
|
ctx.lineTo(SIZE, SIZE)
|
||||||
|
ctx.lineTo(SIZE, 0)
|
||||||
|
ctx.lineTo(0, 0)
|
||||||
|
ctx.lineTo(SIZE, SIZE)
|
||||||
ctx.stroke()
|
ctx.stroke()
|
||||||
ctx.beginPath()
|
ctx.beginPath()
|
||||||
ctx.setLineDash([1,2])
|
ctx.setLineDash([1, 2])
|
||||||
const n = SIZE / 10
|
const n = SIZE / 10
|
||||||
for (let i=n; i<SIZE; i+=n) {
|
for (let i = n; i < SIZE; i += n) {
|
||||||
ctx.moveTo(0,i)
|
ctx.moveTo(0, i)
|
||||||
ctx.lineTo(SIZE,i)
|
ctx.lineTo(SIZE, i)
|
||||||
ctx.moveTo(i,0)
|
ctx.moveTo(i, 0)
|
||||||
ctx.lineTo(i,SIZE)
|
ctx.lineTo(i, SIZE)
|
||||||
}
|
}
|
||||||
ctx.stroke()
|
ctx.stroke()
|
||||||
ctx.beginPath()
|
ctx.beginPath()
|
||||||
ctx.setLineDash([])
|
ctx.setLineDash([])
|
||||||
ctx.beginPath();
|
ctx.beginPath()
|
||||||
ctx.strokeStyle = 'black'
|
ctx.strokeStyle = "black"
|
||||||
ctx.lineWidth = 3;
|
ctx.lineWidth = 3
|
||||||
// Plot function
|
// Plot function
|
||||||
const numSamples = 20;
|
const numSamples = 20
|
||||||
for (let i = 0; i <= numSamples; i++) {
|
for (let i = 0; i <= numSamples; i++) {
|
||||||
const x = i / numSamples;
|
const x = i / numSamples
|
||||||
const y = fn(x);
|
const y = fn(x)
|
||||||
|
|
||||||
const canvasX = x * SIZE;
|
const canvasX = x * SIZE
|
||||||
const canvasY = y * SIZE;
|
const canvasY = y * SIZE
|
||||||
|
|
||||||
if (i === 0) {
|
if (i === 0) {
|
||||||
ctx.moveTo(canvasX, canvasY);
|
ctx.moveTo(canvasX, canvasY)
|
||||||
} else {
|
} else {
|
||||||
ctx.lineTo(canvasX, canvasY);
|
ctx.lineTo(canvasX, canvasY)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ctx.stroke()
|
ctx.stroke()
|
||||||
// Plot alpha values (yellow boxes)
|
// Plot alpha values (yellow boxes)
|
||||||
let start = parseFloat( document.querySelector('#merge-start').value )
|
let start = parseFloat(document.querySelector("#merge-start").value)
|
||||||
let step = parseFloat( document.querySelector('#merge-step').value )
|
let step = parseFloat(document.querySelector("#merge-step").value)
|
||||||
let iterations = document.querySelector('#merge-count').value>>0
|
let iterations = document.querySelector("#merge-count").value >> 0
|
||||||
ctx.beginPath()
|
ctx.beginPath()
|
||||||
ctx.fillStyle = "yellow"
|
ctx.fillStyle = "yellow"
|
||||||
for (let i=0; i< iterations; i++) {
|
for (let i = 0; i < iterations; i++) {
|
||||||
const alpha = ( start + i * step ) / 100
|
const alpha = (start + i * step) / 100
|
||||||
const x = alpha*SIZE
|
const x = alpha * SIZE
|
||||||
const y = fn(alpha) * SIZE
|
const y = fn(alpha) * SIZE
|
||||||
if (x <= SIZE) {
|
if (x <= SIZE) {
|
||||||
ctx.rect(x-3,y-3,6,6)
|
ctx.rect(x - 3, y - 3, 6, 6)
|
||||||
ctx.fill()
|
ctx.fill()
|
||||||
} else {
|
} else {
|
||||||
ctx.strokeStyle = 'red'
|
ctx.strokeStyle = "red"
|
||||||
ctx.moveTo(0,0); ctx.lineTo(0,SIZE); ctx.lineTo(SIZE,SIZE); ctx.lineTo(SIZE,0); ctx.lineTo(0,0); ctx.lineTo(SIZE,SIZE);
|
ctx.moveTo(0, 0)
|
||||||
|
ctx.lineTo(0, SIZE)
|
||||||
|
ctx.lineTo(SIZE, SIZE)
|
||||||
|
ctx.lineTo(SIZE, 0)
|
||||||
|
ctx.lineTo(0, 0)
|
||||||
|
ctx.lineTo(SIZE, SIZE)
|
||||||
ctx.stroke()
|
ctx.stroke()
|
||||||
addLogMessage('<i>Warning: maximum ratio is ≥ 100%</i>')
|
addLogMessage("<i>Warning: maximum ratio is ≥ 100%</i>")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateChart() {
|
function updateChart() {
|
||||||
let fn = (x) => x
|
let fn = (x) => x
|
||||||
switch (document.querySelector('#merge-interpolation').value) {
|
switch (document.querySelector("#merge-interpolation").value) {
|
||||||
case 'SmoothStep':
|
case "SmoothStep":
|
||||||
fn = smoothstep
|
fn = smoothstep
|
||||||
break
|
break
|
||||||
case 'SmootherStep':
|
case "SmootherStep":
|
||||||
fn = smootherstep
|
fn = smootherstep
|
||||||
break
|
break
|
||||||
case 'SmoothestStep':
|
case "SmoothestStep":
|
||||||
fn = smootheststep
|
fn = smootheststep
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
drawDiagram(fn)
|
drawDiagram(fn)
|
||||||
}
|
}
|
||||||
|
createTab({
|
||||||
/////////////////////// Tab implementation
|
id: "merge",
|
||||||
document.querySelector('.tab-container')?.insertAdjacentHTML('beforeend', `
|
icon: "fa-code-merge",
|
||||||
<span id="tab-merge" class="tab">
|
label: "Merge models",
|
||||||
<span><i class="fa fa-code-merge icon"></i> Merge models</span>
|
css: `
|
||||||
</span>
|
|
||||||
`)
|
|
||||||
|
|
||||||
document.querySelector('#tab-content-wrapper')?.insertAdjacentHTML('beforeend', `
|
|
||||||
<div id="tab-content-merge" class="tab-content">
|
|
||||||
<div id="merge" class="tab-content-inner">
|
|
||||||
Loading..
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`)
|
|
||||||
|
|
||||||
const tabMerge = document.querySelector('#tab-merge')
|
|
||||||
if (tabMerge) {
|
|
||||||
linkTabContents(tabMerge)
|
|
||||||
}
|
|
||||||
const merge = document.querySelector('#merge')
|
|
||||||
if (!merge) {
|
|
||||||
// merge tab not found, dont exec plugin code.
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
document.querySelector('body').insertAdjacentHTML('beforeend', `
|
|
||||||
<style>
|
|
||||||
#tab-content-merge .tab-content-inner {
|
#tab-content-merge .tab-content-inner {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
padding: 10pt;
|
padding: 10pt;
|
||||||
@ -233,11 +220,8 @@
|
|||||||
}
|
}
|
||||||
.merge-container #merge-warning {
|
.merge-container #merge-warning {
|
||||||
color: rgb(153, 153, 153);
|
color: rgb(153, 153, 153);
|
||||||
}
|
}`,
|
||||||
</style>
|
content: `
|
||||||
`)
|
|
||||||
|
|
||||||
merge.innerHTML = `
|
|
||||||
<div class="merge-container panel-box">
|
<div class="merge-container panel-box">
|
||||||
<div class="merge-input">
|
<div class="merge-input">
|
||||||
<p><label for="#mergeModelA">Select Model A:</label></p>
|
<p><label for="#mergeModelA">Select Model A:</label></p>
|
||||||
@ -327,21 +311,25 @@
|
|||||||
<div class="merge-buttons">
|
<div class="merge-buttons">
|
||||||
<button id="merge-button" class="primaryButton">Merge models</button>
|
<button id="merge-button" class="primaryButton">Merge models</button>
|
||||||
</div>
|
</div>
|
||||||
</div>`
|
</div>`,
|
||||||
|
onOpen: ({ firstOpen }) => {
|
||||||
|
if (!firstOpen) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const tabSettingsSingle = document.querySelector('#tab-merge-opts-single')
|
const tabSettingsSingle = document.querySelector("#tab-merge-opts-single")
|
||||||
const tabSettingsBatch = document.querySelector('#tab-merge-opts-batch')
|
const tabSettingsBatch = document.querySelector("#tab-merge-opts-batch")
|
||||||
linkTabContents(tabSettingsSingle)
|
linkTabContents(tabSettingsSingle)
|
||||||
linkTabContents(tabSettingsBatch)
|
linkTabContents(tabSettingsBatch)
|
||||||
|
|
||||||
console.log('Activate')
|
console.log("Activate")
|
||||||
let mergeModelAField = new ModelDropdown(document.querySelector('#mergeModelA'), 'stable-diffusion')
|
let mergeModelAField = new ModelDropdown(document.querySelector("#mergeModelA"), "stable-diffusion")
|
||||||
let mergeModelBField = new ModelDropdown(document.querySelector('#mergeModelB'), 'stable-diffusion')
|
let mergeModelBField = new ModelDropdown(document.querySelector("#mergeModelB"), "stable-diffusion")
|
||||||
updateChart()
|
updateChart()
|
||||||
|
|
||||||
// slider
|
// slider
|
||||||
const singleMergeRatioField = document.querySelector('#single-merge-ratio')
|
const singleMergeRatioField = document.querySelector("#single-merge-ratio")
|
||||||
const singleMergeRatioSlider = document.querySelector('#single-merge-ratio-slider')
|
const singleMergeRatioSlider = document.querySelector("#single-merge-ratio-slider")
|
||||||
|
|
||||||
function updateSingleMergeRatio() {
|
function updateSingleMergeRatio() {
|
||||||
singleMergeRatioField.value = singleMergeRatioSlider.value / 10
|
singleMergeRatioField.value = singleMergeRatioSlider.value / 10
|
||||||
@ -359,21 +347,21 @@
|
|||||||
singleMergeRatioSlider.dispatchEvent(new Event("change"))
|
singleMergeRatioSlider.dispatchEvent(new Event("change"))
|
||||||
}
|
}
|
||||||
|
|
||||||
singleMergeRatioSlider.addEventListener('input', updateSingleMergeRatio)
|
singleMergeRatioSlider.addEventListener("input", updateSingleMergeRatio)
|
||||||
singleMergeRatioField.addEventListener('input', updateSingleMergeRatioSlider)
|
singleMergeRatioField.addEventListener("input", updateSingleMergeRatioSlider)
|
||||||
updateSingleMergeRatio()
|
updateSingleMergeRatio()
|
||||||
|
|
||||||
document.querySelector('.merge-config').addEventListener('change', updateChart)
|
document.querySelector(".merge-config").addEventListener("change", updateChart)
|
||||||
|
|
||||||
document.querySelector('#merge-button').addEventListener('click', async function(e) {
|
document.querySelector("#merge-button").addEventListener("click", async function(e) {
|
||||||
// Build request template
|
// Build request template
|
||||||
let model0 = mergeModelAField.value
|
let model0 = mergeModelAField.value
|
||||||
let model1 = mergeModelBField.value
|
let model1 = mergeModelBField.value
|
||||||
let request = { model0: model0, model1: model1 }
|
let request = { model0: model0, model1: model1 }
|
||||||
request['use_fp16'] = document.querySelector('#merge-fp').value == 'fp16'
|
request["use_fp16"] = document.querySelector("#merge-fp").value == "fp16"
|
||||||
let iterations = document.querySelector('#merge-count').value>>0
|
let iterations = document.querySelector("#merge-count").value >> 0
|
||||||
let start = parseFloat( document.querySelector('#merge-start').value )
|
let start = parseFloat(document.querySelector("#merge-start").value)
|
||||||
let step = parseFloat( document.querySelector('#merge-step').value )
|
let step = parseFloat(document.querySelector("#merge-step").value)
|
||||||
|
|
||||||
if (isTabActive(tabSettingsSingle)) {
|
if (isTabActive(tabSettingsSingle)) {
|
||||||
start = parseFloat(singleMergeRatioField.value)
|
start = parseFloat(singleMergeRatioField.value)
|
||||||
@ -385,74 +373,78 @@
|
|||||||
addLogMessage(`step = ${step}%`)
|
addLogMessage(`step = ${step}%`)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (start + (iterations-1) * step >= 100) {
|
if (start + (iterations - 1) * step >= 100) {
|
||||||
addLogMessage('<i>Aborting: maximum ratio is ≥ 100%</i>')
|
addLogMessage("<i>Aborting: maximum ratio is ≥ 100%</i>")
|
||||||
addLogMessage('Reduce the number of variations or the step size')
|
addLogMessage("Reduce the number of variations or the step size")
|
||||||
addLogSeparator()
|
addLogSeparator()
|
||||||
document.querySelector('#merge-count').focus()
|
document.querySelector("#merge-count").focus()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (document.querySelector('#merge-filename').value == "") {
|
if (document.querySelector("#merge-filename").value == "") {
|
||||||
addLogMessage('<i>Aborting: No output file name specified</i>')
|
addLogMessage("<i>Aborting: No output file name specified</i>")
|
||||||
addLogSeparator()
|
addLogSeparator()
|
||||||
document.querySelector('#merge-filename').focus()
|
document.querySelector("#merge-filename").focus()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Disable merge button
|
// Disable merge button
|
||||||
e.target.disabled=true
|
e.target.disabled = true
|
||||||
e.target.classList.add('disabled')
|
e.target.classList.add("disabled")
|
||||||
let cursor = $("body").css("cursor");
|
let cursor = $("body").css("cursor")
|
||||||
let label = document.querySelector('#merge-button').innerHTML
|
let label = document.querySelector("#merge-button").innerHTML
|
||||||
$("body").css("cursor", "progress");
|
$("body").css("cursor", "progress")
|
||||||
document.querySelector('#merge-button').innerHTML = 'Merging models ...'
|
document.querySelector("#merge-button").innerHTML = "Merging models ..."
|
||||||
|
|
||||||
addLogMessage("Merging models")
|
addLogMessage("Merging models")
|
||||||
addLogMessage("Model A: "+model0)
|
addLogMessage("Model A: " + model0)
|
||||||
addLogMessage("Model B: "+model1)
|
addLogMessage("Model B: " + model1)
|
||||||
|
|
||||||
// Batch main loop
|
// Batch main loop
|
||||||
for (let i=0; i<iterations; i++) {
|
for (let i = 0; i < iterations; i++) {
|
||||||
let alpha = ( start + i * step ) / 100
|
let alpha = (start + i * step) / 100
|
||||||
switch (document.querySelector('#merge-interpolation').value) {
|
switch (document.querySelector("#merge-interpolation").value) {
|
||||||
case 'SmoothStep':
|
case "SmoothStep":
|
||||||
alpha = smoothstep(alpha)
|
alpha = smoothstep(alpha)
|
||||||
break
|
break
|
||||||
case 'SmootherStep':
|
case "SmootherStep":
|
||||||
alpha = smootherstep(alpha)
|
alpha = smootherstep(alpha)
|
||||||
break
|
break
|
||||||
case 'SmoothestStep':
|
case "SmoothestStep":
|
||||||
alpha = smootheststep(alpha)
|
alpha = smootheststep(alpha)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
addLogMessage(`merging batch job ${i+1}/${iterations}, alpha = ${alpha.toFixed(5)}...`)
|
addLogMessage(`merging batch job ${i + 1}/${iterations}, alpha = ${alpha.toFixed(5)}...`)
|
||||||
|
|
||||||
request['out_path'] = document.querySelector('#merge-filename').value
|
request["out_path"] = document.querySelector("#merge-filename").value
|
||||||
request['out_path'] += '-' + alpha.toFixed(5) + '.' + document.querySelector('#merge-format').value
|
request["out_path"] += "-" + alpha.toFixed(5) + "." + document.querySelector("#merge-format").value
|
||||||
addLogMessage(` filename: ${request['out_path']}`)
|
addLogMessage(` filename: ${request["out_path"]}`)
|
||||||
|
|
||||||
request['ratio'] = alpha
|
request["ratio"] = alpha
|
||||||
let res = await fetch('/model/merge', {
|
let res = await fetch("/model/merge", {
|
||||||
method: 'POST',
|
method: "POST",
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify(request) })
|
body: JSON.stringify(request),
|
||||||
const data = await res.json();
|
})
|
||||||
|
const data = await res.json()
|
||||||
addLogMessage(JSON.stringify(data))
|
addLogMessage(JSON.stringify(data))
|
||||||
}
|
}
|
||||||
addLogMessage("<b>Done.</b> The models have been saved to your <tt>models/stable-diffusion</tt> folder.")
|
addLogMessage(
|
||||||
|
"<b>Done.</b> The models have been saved to your <tt>models/stable-diffusion</tt> folder."
|
||||||
|
)
|
||||||
addLogSeparator()
|
addLogSeparator()
|
||||||
// Re-enable merge button
|
// Re-enable merge button
|
||||||
$("body").css("cursor", cursor);
|
$("body").css("cursor", cursor)
|
||||||
document.querySelector('#merge-button').innerHTML = label
|
document.querySelector("#merge-button").innerHTML = label
|
||||||
e.target.disabled=false
|
e.target.disabled = false
|
||||||
e.target.classList.remove('disabled')
|
e.target.classList.remove("disabled")
|
||||||
|
|
||||||
// Update model list
|
// Update model list
|
||||||
stableDiffusionModelField.innerHTML = ''
|
stableDiffusionModelField.innerHTML = ""
|
||||||
vaeModelField.innerHTML = ''
|
vaeModelField.innerHTML = ""
|
||||||
hypernetworkModelField.innerHTML = ''
|
hypernetworkModelField.innerHTML = ""
|
||||||
await getModels()
|
await getModels()
|
||||||
})
|
})
|
||||||
|
},
|
||||||
|
})
|
||||||
})()
|
})()
|
||||||
|
@ -1,18 +1,18 @@
|
|||||||
(function () {
|
;(function() {
|
||||||
"use strict"
|
"use strict"
|
||||||
|
|
||||||
var styleSheet = document.createElement("style");
|
var styleSheet = document.createElement("style")
|
||||||
styleSheet.textContent = `
|
styleSheet.textContent = `
|
||||||
.modifier-card-tiny.modifier-toggle-inactive {
|
.modifier-card-tiny.modifier-toggle-inactive {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
border: 2px dashed red;
|
border: 2px dashed red;
|
||||||
opacity:0.2;
|
opacity:0.2;
|
||||||
}
|
}
|
||||||
`;
|
`
|
||||||
document.head.appendChild(styleSheet);
|
document.head.appendChild(styleSheet)
|
||||||
|
|
||||||
// observe for changes in tag list
|
// observe for changes in tag list
|
||||||
var observer = new MutationObserver(function (mutations) {
|
var observer = new MutationObserver(function(mutations) {
|
||||||
// mutations.forEach(function (mutation) {
|
// mutations.forEach(function (mutation) {
|
||||||
if (editorModifierTagsList.childNodes.length > 0) {
|
if (editorModifierTagsList.childNodes.length > 0) {
|
||||||
ModifierToggle()
|
ModifierToggle()
|
||||||
@ -21,32 +21,32 @@
|
|||||||
})
|
})
|
||||||
|
|
||||||
observer.observe(editorModifierTagsList, {
|
observer.observe(editorModifierTagsList, {
|
||||||
childList: true
|
childList: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
function ModifierToggle() {
|
function ModifierToggle() {
|
||||||
let overlays = document.querySelector('#editor-inputs-tags-list').querySelectorAll('.modifier-card-overlay')
|
let overlays = document.querySelector("#editor-inputs-tags-list").querySelectorAll(".modifier-card-overlay")
|
||||||
overlays.forEach (i => {
|
overlays.forEach((i) => {
|
||||||
i.oncontextmenu = (e) => {
|
i.oncontextmenu = (e) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
|
||||||
if (i.parentElement.classList.contains('modifier-toggle-inactive')) {
|
if (i.parentElement.classList.contains("modifier-toggle-inactive")) {
|
||||||
i.parentElement.classList.remove('modifier-toggle-inactive')
|
i.parentElement.classList.remove("modifier-toggle-inactive")
|
||||||
}
|
} else {
|
||||||
else
|
i.parentElement.classList.add("modifier-toggle-inactive")
|
||||||
{
|
|
||||||
i.parentElement.classList.add('modifier-toggle-inactive')
|
|
||||||
}
|
}
|
||||||
// refresh activeTags
|
// refresh activeTags
|
||||||
let modifierName = i.parentElement.getElementsByClassName('modifier-card-label')[0].getElementsByTagName("p")[0].dataset.fullName
|
let modifierName = i.parentElement
|
||||||
activeTags = activeTags.map(obj => {
|
.getElementsByClassName("modifier-card-label")[0]
|
||||||
|
.getElementsByTagName("p")[0].dataset.fullName
|
||||||
|
activeTags = activeTags.map((obj) => {
|
||||||
if (trimModifiers(obj.name) === trimModifiers(modifierName)) {
|
if (trimModifiers(obj.name) === trimModifiers(modifierName)) {
|
||||||
return {...obj, inactive: (obj.element.classList.contains('modifier-toggle-inactive'))};
|
return { ...obj, inactive: obj.element.classList.contains("modifier-toggle-inactive") }
|
||||||
}
|
}
|
||||||
|
|
||||||
return obj;
|
return obj
|
||||||
});
|
})
|
||||||
document.dispatchEvent(new Event('refreshImageModifiers'))
|
document.dispatchEvent(new Event("refreshImageModifiers"))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -1,64 +1,53 @@
|
|||||||
(function() {
|
;(function() {
|
||||||
// Register selftests when loaded by jasmine.
|
// Register selftests when loaded by jasmine.
|
||||||
if (typeof PLUGINS?.SELFTEST === 'object') {
|
if (typeof PLUGINS?.SELFTEST === "object") {
|
||||||
PLUGINS.SELFTEST["release-notes"] = function() {
|
PLUGINS.SELFTEST["release-notes"] = function() {
|
||||||
it('should be able to fetch CHANGES.md', async function() {
|
it("should be able to fetch CHANGES.md", async function() {
|
||||||
let releaseNotes = await fetch(`https://raw.githubusercontent.com/cmdr2/stable-diffusion-ui/main/CHANGES.md`)
|
let releaseNotes = await fetch(
|
||||||
|
`https://raw.githubusercontent.com/cmdr2/stable-diffusion-ui/main/CHANGES.md`
|
||||||
|
)
|
||||||
expect(releaseNotes.status).toBe(200)
|
expect(releaseNotes.status).toBe(200)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
document.querySelector('.tab-container')?.insertAdjacentHTML('beforeend', `
|
createTab({
|
||||||
<span id="tab-news" class="tab">
|
id: "news",
|
||||||
<span><i class="fa fa-bolt icon"></i> What's new?</span>
|
icon: "fa-bolt",
|
||||||
</span>
|
label: "What's new",
|
||||||
`)
|
css: `
|
||||||
|
|
||||||
document.querySelector('#tab-content-wrapper')?.insertAdjacentHTML('beforeend', `
|
|
||||||
<div id="tab-content-news" class="tab-content">
|
|
||||||
<div id="news" class="tab-content-inner">
|
|
||||||
Loading..
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`)
|
|
||||||
|
|
||||||
const tabNews = document.querySelector('#tab-news')
|
|
||||||
if (tabNews) {
|
|
||||||
linkTabContents(tabNews)
|
|
||||||
}
|
|
||||||
const news = document.querySelector('#news')
|
|
||||||
if (!news) {
|
|
||||||
// news tab not found, dont exec plugin code.
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
document.querySelector('body').insertAdjacentHTML('beforeend', `
|
|
||||||
<style>
|
|
||||||
#tab-content-news .tab-content-inner {
|
#tab-content-news .tab-content-inner {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
padding: 10pt;
|
padding: 10pt;
|
||||||
}
|
}
|
||||||
</style>
|
`,
|
||||||
`)
|
onOpen: async ({ firstOpen }) => {
|
||||||
|
if (firstOpen) {
|
||||||
|
const loadMarkedScriptPromise = loadScript("/media/js/marked.min.js")
|
||||||
|
|
||||||
loadScript('/media/js/marked.min.js').then(async function() {
|
let appConfig = await fetch("/get/app_config")
|
||||||
let appConfig = await fetch('/get/app_config')
|
|
||||||
if (!appConfig.ok) {
|
if (!appConfig.ok) {
|
||||||
console.error('[release-notes] Failed to get app_config.')
|
console.error("[release-notes] Failed to get app_config.")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
appConfig = await appConfig.json()
|
appConfig = await appConfig.json()
|
||||||
|
|
||||||
const updateBranch = appConfig.update_branch || 'main'
|
const updateBranch = appConfig.update_branch || "main"
|
||||||
|
|
||||||
let releaseNotes = await fetch(`https://raw.githubusercontent.com/cmdr2/stable-diffusion-ui/${updateBranch}/CHANGES.md`)
|
let releaseNotes = await fetch(
|
||||||
|
`https://raw.githubusercontent.com/cmdr2/stable-diffusion-ui/${updateBranch}/CHANGES.md`
|
||||||
|
)
|
||||||
if (!releaseNotes.ok) {
|
if (!releaseNotes.ok) {
|
||||||
console.error('[release-notes] Failed to get CHANGES.md.')
|
console.error("[release-notes] Failed to get CHANGES.md.")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
releaseNotes = await releaseNotes.text()
|
releaseNotes = await releaseNotes.text()
|
||||||
news.innerHTML = marked.parse(releaseNotes)
|
|
||||||
|
await loadMarkedScriptPromise
|
||||||
|
|
||||||
|
return marked.parse(releaseNotes)
|
||||||
|
}
|
||||||
|
},
|
||||||
})
|
})
|
||||||
})()
|
})()
|
@ -1,6 +1,7 @@
|
|||||||
/* SD-UI Selftest Plugin.js
|
/* SD-UI Selftest Plugin.js
|
||||||
*/
|
*/
|
||||||
(function() { "use strict"
|
;(function() {
|
||||||
|
"use strict"
|
||||||
const ID_PREFIX = "selftest-plugin"
|
const ID_PREFIX = "selftest-plugin"
|
||||||
|
|
||||||
const links = document.getElementById("community-links")
|
const links = document.getElementById("community-links")
|
||||||
@ -10,16 +11,18 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add link to Jasmine SpecRunner
|
// Add link to Jasmine SpecRunner
|
||||||
const pluginLink = document.createElement('li')
|
const pluginLink = document.createElement("li")
|
||||||
const options = {
|
const options = {
|
||||||
'stopSpecOnExpectationFailure': "true",
|
stopSpecOnExpectationFailure: "true",
|
||||||
'stopOnSpecFailure': 'false',
|
stopOnSpecFailure: "false",
|
||||||
'random': 'false',
|
random: "false",
|
||||||
'hideDisabled': 'false'
|
hideDisabled: "false",
|
||||||
}
|
}
|
||||||
const optStr = Object.entries(options).map(([key, val]) => `${key}=${val}`).join('&')
|
const optStr = Object.entries(options)
|
||||||
|
.map(([key, val]) => `${key}=${val}`)
|
||||||
|
.join("&")
|
||||||
pluginLink.innerHTML = `<a id="${ID_PREFIX}-starttest" href="${location.protocol}/plugins/core/SpecRunner.html?${optStr}" target="_blank"><i class="fa-solid fa-vial-circle-check"></i> Start SelfTest</a>`
|
pluginLink.innerHTML = `<a id="${ID_PREFIX}-starttest" href="${location.protocol}/plugins/core/SpecRunner.html?${optStr}" target="_blank"><i class="fa-solid fa-vial-circle-check"></i> Start SelfTest</a>`
|
||||||
links.appendChild(pluginLink)
|
links.appendChild(pluginLink)
|
||||||
|
|
||||||
console.log('%s loaded!', ID_PREFIX)
|
console.log("%s loaded!", ID_PREFIX)
|
||||||
})()
|
})()
|
Loading…
Reference in New Issue
Block a user