From 3cac1dac846610376ddbdea648e2cef24305f602 Mon Sep 17 00:00:00 2001 From: Wutipong Wongsakuldej Date: Sat, 21 Oct 2023 18:51:30 +0700 Subject: [PATCH 01/21] sort workflow items. --- web/js/workflows.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/web/js/workflows.js b/web/js/workflows.js index df84c51..b979f6e 100644 --- a/web/js/workflows.js +++ b/web/js/workflows.js @@ -81,6 +81,9 @@ async function saveWorkflow(name, workflow, overwrite) { class PysssssWorkflows { async load() { this.workflows = await getWorkflows(); + if(this.workflows.length) { + this.workflows.sort(); + } this.loadMenu.style.display = this.workflows.length ? "flex" : "none"; } From efa71ad3da96093c2f2ea4ee8af7338e2d132718 Mon Sep 17 00:00:00 2001 From: Alexopus Date: Sun, 29 Oct 2023 10:03:10 +0100 Subject: [PATCH 02/21] Remove cfg file, add it to gitignore and create it at runtime if needed --- .gitignore | 4 +++- pysssss.json => pysssss.default.json | 0 pysssss.py | 18 ++++++++++++++---- 3 files changed, 17 insertions(+), 5 deletions(-) rename pysssss.json => pysssss.default.json (100%) diff --git a/.gitignore b/.gitignore index ed8ebf5..1f266bf 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ -__pycache__ \ No newline at end of file +__pycache__ +pysssss.json + diff --git a/pysssss.json b/pysssss.default.json similarity index 100% rename from pysssss.json rename to pysssss.default.json diff --git a/pysssss.py b/pysssss.py index a61a554..b1024f5 100644 --- a/pysssss.py +++ b/pysssss.py @@ -70,11 +70,21 @@ def get_extension_config(reload=False): return config config_path = get_ext_dir("pysssss.json") + default_config_path = get_ext_dir("pysssss.default.json") if not os.path.exists(config_path): - log("Missing pysssss.json, this extension may not work correctly. Please reinstall the extension.", - type="ERROR", always=True, name="???") - print(f"Extension path: {get_ext_dir()}") - return {"name": "Unknown", "version": -1} + if os.path.exists(default_config_path): + shutil.copy(default_config_path, config_path) + if not os.path.exists(config_path): + log(f"Failed to create config at {config_path}", type="ERROR", always=True, name="???") + print(f"Extension path: {get_ext_dir()}") + return {"name": "Unknown", "version": -1} + + else: + log("Missing pysssss.default.json, this extension may not work correctly. Please reinstall the extension.", + type="ERROR", always=True, name="???") + print(f"Extension path: {get_ext_dir()}") + return {"name": "Unknown", "version": -1} + with open(config_path, "r") as f: config = json.loads(f.read()) return config From 2e96d990793dbdf8ea80c36c1cd1e314b4c05bce Mon Sep 17 00:00:00 2001 From: DrJKL Date: Wed, 1 Nov 2023 14:42:51 -0700 Subject: [PATCH 03/21] Fix images[0] undefined issue Sometimes images is present but empty. --- web/js/showImageOnMenu.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/web/js/showImageOnMenu.js b/web/js/showImageOnMenu.js index c38b5e7..7e153eb 100644 --- a/web/js/showImageOnMenu.js +++ b/web/js/showImageOnMenu.js @@ -49,11 +49,13 @@ const ext = { api.addEventListener("executed", ({ detail }) => { if (!enabled) return; const images = detail?.output?.images; - if (!images) return; + if (!images || !images.length) return; const format = app.getPreviewFormatParam(); - const src = `/view?filename=${encodeURIComponent(images[0].filename)}&type=${ - images[0].type - }&subfolder=${encodeURIComponent(images[0].subfolder)}&t=${+new Date()}${format}`; + const src = [ + `/view?filename=${encodeURIComponent(images[0].filename)}`, + `type=${images[0].type}`, + `subfolder=${encodeURIComponent(images[0].subfolder)}`, + `t=${+new Date()}${format}`,].join('&'); show(src, detail.node); }); From 41b8e81b623b654945271ca8c04395d162608c9d Mon Sep 17 00:00:00 2001 From: DrJKL Date: Wed, 1 Nov 2023 16:02:11 -0700 Subject: [PATCH 04/21] Add keyboard shortcuts to the lightbox. Also catch errors when trying to loadImage. --- web/js/common/lightbox.js | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/web/js/common/lightbox.js b/web/js/common/lightbox.js index 7e07445..4034597 100644 --- a/web/js/common/lightbox.js +++ b/web/js/common/lightbox.js @@ -80,6 +80,12 @@ export class Lightbox { } async update(shift) { + if (shift < 0 && this.index <= 0) { + return; + } + if (shift > 0 && this.index >= this.images.length - 1) { + return; + } this.index += shift; this.prev.style.visibility = this.index ? "unset" : "hidden"; @@ -91,7 +97,11 @@ export class Lightbox { this.el.style.opacity = 1; this.img.style.opacity = 0; this.spinner.style.display = "inline-block"; - await loadImage(img); + try { + await loadImage(img); + } catch (err) { + console.error('failed to load image', img, err); + } this.spinner.style.display = "none"; this.link.href = img; this.img.src = img; @@ -100,3 +110,23 @@ export class Lightbox { } export const lightbox = new Lightbox(); + +addEventListener('keydown', (event) => { + if (lightbox.el.style.display === 'none') { + return; + } + const { key } = event; + switch (key) { + case 'ArrowLeft': + case 'a': + lightbox.update(-1); + break; + case 'ArrowRight': + case 'd': + lightbox.update(1); + break; + case 'Escape': + lightbox.close(); + break; + } +}); \ No newline at end of file From 4160197c58b5909780afe7cefa40c87d672c3e2a Mon Sep 17 00:00:00 2001 From: MML Date: Sun, 5 Nov 2023 19:23:26 -0500 Subject: [PATCH 05/21] Create constrain_image_for_video.py --- py/constrain_image_for_video.py | 72 +++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 py/constrain_image_for_video.py diff --git a/py/constrain_image_for_video.py b/py/constrain_image_for_video.py new file mode 100644 index 0000000..91c0c50 --- /dev/null +++ b/py/constrain_image_for_video.py @@ -0,0 +1,72 @@ +import torch +import numpy as np +from PIL import Image + +class ConstrainImageforVideo: + """ + A node that constrains an image to a maximum and minimum size while maintaining aspect ratio. + """ + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "images": ("IMAGE",), + "max_width": ("INT", {"default": 1024, "min": 0}), + "max_height": ("INT", {"default": 1024, "min": 0}), + "min_width": ("INT", {"default": 0, "min": 0}), + "min_height": ("INT", {"default": 0, "min": 0}), + "crop_if_required": (["yes", "no"], {"default": "no"}), + }, + } + + RETURN_TYPES = ("IMAGE",) + RETURN_NAMES = ("IMAGE",) + FUNCTION = "constrain_image_for_video" + CATEGORY = "image" + + def constrain_image_for_video(self, images, max_width, max_height, min_width, min_height, crop_if_required): + crop_if_required = crop_if_required == "yes" + results = [] + for image in images: + i = 255. * image.cpu().numpy() + img = Image.fromarray(np.clip(i, 0, 255).astype(np.uint8)).convert("RGB") + + current_width, current_height = img.size + aspect_ratio = current_width / current_height + + constrained_width = max(min(current_width, min_width), max_width) + constrained_height = max(min(current_height, min_height), max_height) + + if constrained_width / constrained_height > aspect_ratio: + constrained_width = max(int(constrained_height * aspect_ratio), min_width) + if crop_if_required: + constrained_height = int(current_height / (current_width / constrained_width)) + else: + constrained_height = max(int(constrained_width / aspect_ratio), min_height) + if crop_if_required: + constrained_width = int(current_width / (current_height / constrained_height)) + + resized_image = img.resize((constrained_width, constrained_height), Image.LANCZOS) + + if crop_if_required and (constrained_width > max_width or constrained_height > max_height): + left = max((constrained_width - max_width) // 2, 0) + top = max((constrained_height - max_height) // 2, 0) + right = min(constrained_width, max_width) + left + bottom = min(constrained_height, max_height) + top + resized_image = resized_image.crop((left, top, right, bottom)) + + resized_image = np.array(resized_image).astype(np.float32) / 255.0 + resized_image = torch.from_numpy(resized_image)[None,] + results.append(resized_image) + all_images = torch.cat(results, dim=0) + + return (all_images, all_images.size(0),) + +NODE_CLASS_MAPPINGS = { + "ConstrainImageforVideo|pysssss": ConstrainImageforVideo, +} + +NODE_DISPLAY_NAME_MAPPINGS = { + "ConstrainImageforVideo|pysssss": "Constrain Image for Video 🐍", +} From 96c290c4a217ab4f3a44b8a4beb3b7aedf33c645 Mon Sep 17 00:00:00 2001 From: Wangchong Zhou Date: Wed, 15 Nov 2023 21:41:13 +0800 Subject: [PATCH 06/21] Fix reroutePrimitive widget create config handling Possibly fix #118 Seems to be a regression after upgraded ComfyUI --- web/js/reroutePrimitive.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/js/reroutePrimitive.js b/web/js/reroutePrimitive.js index 1b94894..91c77ad 100644 --- a/web/js/reroutePrimitive.js +++ b/web/js/reroutePrimitive.js @@ -154,7 +154,7 @@ app.registerExtension({ v = this.widgets_values[0]; } let config = [link.type, {}]; - if (input.widget) { + if (input.widget?.config) { config = input.widget.config; } const { widget } = ComfyWidgets[widgetType](this, "value", config, app); From d4d4381d83301d138f7eff2b5a686ca7ce9f640e Mon Sep 17 00:00:00 2001 From: Henry Lee Date: Fri, 1 Dec 2023 12:49:40 +0000 Subject: [PATCH 07/21] Checkpoint Preview Visual Change --- web/js/betterCombos.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/web/js/betterCombos.js b/web/js/betterCombos.js index bf3690d..cdea67a 100644 --- a/web/js/betterCombos.js +++ b/web/js/betterCombos.js @@ -27,10 +27,11 @@ app.registerExtension({ left: 0; top: 0; transform: translate(-100%, 0); - width: 256px; - height: 256px; - background-size: cover; - background-position: center; + width: 384px; + height: 384px; + background-size: contain; + background-position: top right; + background-repeat: no-repeat; filter: brightness(65%); } `, From f1e46a7d099d0c520722883f5b24eb48c36cc80f Mon Sep 17 00:00:00 2001 From: Sample_Text <155495184+SampleTexting@users.noreply.github.com> Date: Tue, 2 Jan 2024 17:37:52 -0600 Subject: [PATCH 08/21] Adding relative pathing to imagefeed and showimageonmenu for functionality within stableswarmui --- web/js/imageFeed.js | 2 +- web/js/showImageOnMenu.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/web/js/imageFeed.js b/web/js/imageFeed.js index b477912..e214d22 100644 --- a/web/js/imageFeed.js +++ b/web/js/imageFeed.js @@ -410,7 +410,7 @@ app.registerExtension({ api.addEventListener("executed", ({ detail }) => { if (visible && detail?.output?.images) { for (const src of detail.output.images) { - const href = `/view?filename=${encodeURIComponent(src.filename)}&type=${ + const href = `./view?filename=${encodeURIComponent(src.filename)}&type=${ src.type }&subfolder=${encodeURIComponent(src.subfolder)}&t=${+new Date()}`; diff --git a/web/js/showImageOnMenu.js b/web/js/showImageOnMenu.js index c38b5e7..4f4bcbe 100644 --- a/web/js/showImageOnMenu.js +++ b/web/js/showImageOnMenu.js @@ -51,7 +51,7 @@ const ext = { const images = detail?.output?.images; if (!images) return; const format = app.getPreviewFormatParam(); - const src = `/view?filename=${encodeURIComponent(images[0].filename)}&type=${ + const src = `./view?filename=${encodeURIComponent(images[0].filename)}&type=${ images[0].type }&subfolder=${encodeURIComponent(images[0].subfolder)}&t=${+new Date()}${format}`; show(src, detail.node); From a615170965b12b0ebe4e9d9efa89fd802d174c29 Mon Sep 17 00:00:00 2001 From: pythongosssss <125205205+pythongosssss@users.noreply.github.com> Date: Wed, 3 Jan 2024 18:09:44 +0000 Subject: [PATCH 09/21] remove autocomplete --- user/autocomplete.txt | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 user/autocomplete.txt diff --git a/user/autocomplete.txt b/user/autocomplete.txt deleted file mode 100644 index e69de29..0000000 From 228ffbcb9c35ba1cc647d3705f5c9b5ece5da0b2 Mon Sep 17 00:00:00 2001 From: pythongosssss <125205205+pythongosssss@users.noreply.github.com> Date: Wed, 3 Jan 2024 18:09:54 +0000 Subject: [PATCH 10/21] make passthrough --- py/play_sound.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/py/play_sound.py b/py/play_sound.py index e2e6cf1..17688b1 100644 --- a/py/play_sound.py +++ b/py/play_sound.py @@ -19,8 +19,9 @@ class PlaySound: FUNCTION = "nop" INPUT_IS_LIST = True + OUTPUT_IS_LIST = (True,) OUTPUT_NODE = True - RETURN_TYPES = () + RETURN_TYPES = (any,) CATEGORY = "utils" @@ -28,7 +29,7 @@ class PlaySound: return float("NaN") def nop(self, any, mode, volume): - return {"ui": {"a": []}, "result": ()} + return {"ui": {"a": []}, "result": (any,)} NODE_CLASS_MAPPINGS = { From c9bae148b536d7147b4e10352948d3333b78d157 Mon Sep 17 00:00:00 2001 From: pythongosssss <125205205+pythongosssss@users.noreply.github.com> Date: Wed, 3 Jan 2024 18:19:13 +0000 Subject: [PATCH 11/21] allow custom sound --- py/play_sound.py | 5 +++-- web/js/playSound.js | 13 ++++++++++++- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/py/play_sound.py b/py/play_sound.py index 17688b1..65f599d 100644 --- a/py/play_sound.py +++ b/py/play_sound.py @@ -14,7 +14,8 @@ class PlaySound: return {"required": { "any": (any, {}), "mode": (["always", "on empty queue"], {}), - "volume": ("FLOAT", {"min": 0, "max": 1, "step": 0.1, "default": 0.5}) + "volume": ("FLOAT", {"min": 0, "max": 1, "step": 0.1, "default": 0.5}), + "file": ("STRING", { "default": "notify.mp3" }) }} FUNCTION = "nop" @@ -28,7 +29,7 @@ class PlaySound: def IS_CHANGED(self, **kwargs): return float("NaN") - def nop(self, any, mode, volume): + def nop(self, any, mode, volume, file): return {"ui": {"a": []}, "result": (any,)} diff --git a/web/js/playSound.js b/web/js/playSound.js index e472170..de71058 100644 --- a/web/js/playSound.js +++ b/web/js/playSound.js @@ -15,7 +15,18 @@ app.registerExtension({ return; } } - const url = new URL(`assets/notify.mp3`, import.meta.url); + let file = this.widgets[2].value; + if (!file) { + file = "notify.mp3"; + } + if (!file.startsWith("http")) { + if (!file.includes("/")) { + file = "assets/" + file; + } + file = new URL(file, import.meta.url) + } + + const url = new URL(file); const audio = new Audio(url); audio.volume = this.widgets[1].value; audio.play(); From 818078d5160db56f60e726db71af4f26383edbc0 Mon Sep 17 00:00:00 2001 From: pythongosssss <125205205+pythongosssss@users.noreply.github.com> Date: Wed, 3 Jan 2024 18:27:12 +0000 Subject: [PATCH 12/21] fix crash if workflow not present --- py/math_expression.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/py/math_expression.py b/py/math_expression.py index decebdc..aa7a17a 100644 --- a/py/math_expression.py +++ b/py/math_expression.py @@ -95,7 +95,7 @@ class MathExpression: return expression def get_widget_value(self, extra_pnginfo, prompt, node_name, widget_name): - workflow = extra_pnginfo["workflow"] + workflow = extra_pnginfo["workflow"] if "workflow" in extra_pnginfo else { "nodes": [] } node_id = None for node in workflow["nodes"]: name = node["type"] From e438a73bdc3b43df9fc6af003744672789fd12f3 Mon Sep 17 00:00:00 2001 From: pythongosssss <125205205+pythongosssss@users.noreply.github.com> Date: Wed, 3 Jan 2024 18:39:53 +0000 Subject: [PATCH 13/21] Support for efficent loader maybe --- web/js/modelInfo.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/web/js/modelInfo.js b/web/js/modelInfo.js index 6b198e6..d23f024 100644 --- a/web/js/modelInfo.js +++ b/web/js/modelInfo.js @@ -234,6 +234,8 @@ const infoHandler = { CheckpointLoader: "checkpoints", CheckpointLoaderSimple: "checkpoints", "CheckpointLoader|pysssss": "checkpoints", + "Efficient Loader": "checkpoints", + "Eff. Loader SDXL": "checkpoints", }; app.registerExtension({ From c2ebb1eccf6640dc598d83635c8188871fcad844 Mon Sep 17 00:00:00 2001 From: pythongosssss <125205205+pythongosssss@users.noreply.github.com> Date: Wed, 3 Jan 2024 18:57:32 +0000 Subject: [PATCH 14/21] Fix not showing stored value --- web/js/showText.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/web/js/showText.js b/web/js/showText.js index a16540e..75b659f 100644 --- a/web/js/showText.js +++ b/web/js/showText.js @@ -2,14 +2,14 @@ import { app } from "../../../scripts/app.js"; import { ComfyWidgets } from "../../../scripts/widgets.js"; // Displays input text on a node - +debugger app.registerExtension({ name: "pysssss.ShowText", async beforeRegisterNodeDef(nodeType, nodeData, app) { if (nodeData.name === "ShowText|pysssss") { function populate(text) { if (this.widgets) { - const pos = this.widgets.findIndex((w) => w.name === "text"); + const pos = this.widgets.findIndex((w) => w.name === "value"); if (pos !== -1) { for (let i = pos; i < this.widgets.length; i++) { this.widgets[i].onRemove?.(); @@ -19,7 +19,7 @@ app.registerExtension({ } for (const list of text) { - const w = ComfyWidgets["STRING"](this, "text", ["STRING", { multiline: true }], app).widget; + const w = ComfyWidgets["STRING"](this, "value", ["STRING", { multiline: true }], app).widget; w.inputEl.readOnly = true; w.inputEl.style.opacity = 0.6; w.value = list; From 9f66721ff813571b9484405d065fb80102bf15b3 Mon Sep 17 00:00:00 2001 From: pythongosssss <125205205+pythongosssss@users.noreply.github.com> Date: Wed, 3 Jan 2024 19:24:17 +0000 Subject: [PATCH 15/21] allow no file input when valdiating --- py/text_files.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/py/text_files.py b/py/text_files.py index 5b4f917..9a249ca 100644 --- a/py/text_files.py +++ b/py/text_files.py @@ -64,6 +64,8 @@ def get_file(root_dir, file): root_dir = get_dir_from_name(root_dir) root_dir = get_real_path(root_dir) + if not os.path.exists(root_dir): + os.mkdir(root_dir) full_path = os.path.join(root_dir, file) if not is_child_dir(root_dir, full_path): @@ -78,10 +80,13 @@ class TextFileNode: @classmethod def VALIDATE_INPUTS(self, root_dir, file, **kwargs): - self.file = get_file(root_dir, file) + if file == "[none]" or not file or not file.strip(): + return True + get_file(root_dir, file) return True def load_text(self, **kwargs): + self.file = get_file(kwargs["root_dir"], kwargs["file"]) with open(self.file, "r") as f: return (f.read(), ) @@ -168,17 +173,18 @@ class SaveText(TextFileNode): FUNCTION = "write_text" - def write_text(self, root_dir, file, append, insert, text): - if append == "new only" and os.path.exists(self.file): + def write_text(self, **kwargs): + self.file = get_file(kwargs["root_dir"], kwargs["file"]) + if kwargs["append"] == "new only" and os.path.exists(self.file): raise FileExistsError( self.file + " already exists and 'new only' is selected.") - with open(self.file, "a+" if append == "append" else "w") as f: + with open(self.file, "a+" if kwargs["append"] == "append" else "w") as f: is_append = f.tell() != 0 - if is_append and insert: + if is_append and kwargs["insert"]: f.write("\n") - f.write(text) + f.write(kwargs["text"]) - return super().load_text() + return super().load_text(**kwargs) NODE_CLASS_MAPPINGS = { From a7153c107866e26e6953296a4f72084e6ba14105 Mon Sep 17 00:00:00 2001 From: pythongosssss <125205205+pythongosssss@users.noreply.github.com> Date: Wed, 3 Jan 2024 19:28:17 +0000 Subject: [PATCH 16/21] Use actual name --- web/js/workflowImage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/js/workflowImage.js b/web/js/workflowImage.js index 0158f65..d6b73de 100644 --- a/web/js/workflowImage.js +++ b/web/js/workflowImage.js @@ -386,7 +386,7 @@ class SvgWorkflowImage extends WorkflowImage { return; } else if (file && (file.type === "image/jpeg" || file.name?.endsWith(".jpg") || file.name?.endsWith(".jpeg"))) { if ( - await new Promise((r) => { + await new Promise((resolve) => { try { // This shouldnt go in here but it's easier than refactoring handleFile const reader = new FileReader(); From a888f3e039daab95716aee5d3dcd6e798100e1f8 Mon Sep 17 00:00:00 2001 From: pythongosssss <125205205+pythongosssss@users.noreply.github.com> Date: Wed, 3 Jan 2024 19:32:08 +0000 Subject: [PATCH 17/21] oops --- web/js/showText.js | 1 - 1 file changed, 1 deletion(-) diff --git a/web/js/showText.js b/web/js/showText.js index 75b659f..110ba05 100644 --- a/web/js/showText.js +++ b/web/js/showText.js @@ -2,7 +2,6 @@ import { app } from "../../../scripts/app.js"; import { ComfyWidgets } from "../../../scripts/widgets.js"; // Displays input text on a node -debugger app.registerExtension({ name: "pysssss.ShowText", async beforeRegisterNodeDef(nodeType, nodeData, app) { From 9def62c3de054daa0bb102fe5cf177d8aca2f1b8 Mon Sep 17 00:00:00 2001 From: pythongosssss <125205205+pythongosssss@users.noreply.github.com> Date: Wed, 3 Jan 2024 20:00:25 +0000 Subject: [PATCH 18/21] better fix maybe --- web/js/showText.js | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/web/js/showText.js b/web/js/showText.js index 110ba05..62da82e 100644 --- a/web/js/showText.js +++ b/web/js/showText.js @@ -8,17 +8,18 @@ app.registerExtension({ if (nodeData.name === "ShowText|pysssss") { function populate(text) { if (this.widgets) { - const pos = this.widgets.findIndex((w) => w.name === "value"); - if (pos !== -1) { - for (let i = pos; i < this.widgets.length; i++) { - this.widgets[i].onRemove?.(); - } - this.widgets.length = pos; + for (let i = 1; i < this.widgets.length; i++) { + this.widgets[i].onRemove?.(); } + this.widgets.length = 1; } - for (const list of text) { - const w = ComfyWidgets["STRING"](this, "value", ["STRING", { multiline: true }], app).widget; + const v = [...text]; + if (!v[0]) { + v.shift(); + } + for (const list of v) { + const w = ComfyWidgets["STRING"](this, "text", ["STRING", { multiline: true }], app).widget; w.inputEl.readOnly = true; w.inputEl.style.opacity = 0.6; w.value = list; From 7279dd260e9efe5d4c103ef67d05581a5eb3886a Mon Sep 17 00:00:00 2001 From: pythongosssss <125205205+pythongosssss@users.noreply.github.com> Date: Wed, 3 Jan 2024 20:04:50 +0000 Subject: [PATCH 19/21] fix scaled position fixed enabled setting --- web/js/autocompleter.js | 3 ++- web/js/common/autocomplete.js | 22 ++++++++-------------- 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/web/js/autocompleter.js b/web/js/autocompleter.js index 46ad35f..3b0ce7a 100644 --- a/web/js/autocompleter.js +++ b/web/js/autocompleter.js @@ -312,7 +312,7 @@ app.registerExtension({ }; TextAreaAutoComplete.globalSeparator = localStorage.getItem(id + ".AutoSeparate") ?? ", "; - app.ui.settings.addSetting({ + const enabledSetting = app.ui.settings.addSetting({ id, name: "🐍 Text Autocomplete", defaultValue: true, @@ -383,6 +383,7 @@ app.registerExtension({ ]); }, }); + TextAreaAutoComplete.enabled = enabledSetting.value; }, beforeRegisterNodeDef(_, def) { // Process each input to see if there is a custom word list for diff --git a/web/js/common/autocomplete.js b/web/js/common/autocomplete.js index a8f232e..d04567f 100644 --- a/web/js/common/autocomplete.js +++ b/web/js/common/autocomplete.js @@ -175,8 +175,9 @@ const CHAR_CODE_ZERO = "0".charCodeAt(0); const CHAR_CODE_NINE = "9".charCodeAt(0); class TextAreaCaretHelper { - constructor(el) { + constructor(el, getScale) { this.el = el; + this.getScale = getScale; } #calculateElementOffset() { @@ -253,11 +254,12 @@ class TextAreaCaretHelper { } getCursorOffset() { + const scale = this.getScale(); const elOffset = this.#calculateElementOffset(); const elScroll = this.#getElScroll(); const cursorPosition = this.#getCursorPosition(); const lineHeight = this.#getLineHeightPx(); - const top = elOffset.top - elScroll.top + cursorPosition.top + lineHeight; + const top = elOffset.top - (elScroll.top * scale) + (cursorPosition.top + lineHeight) * scale; const left = elOffset.left - elScroll.left + cursorPosition.left; const clientTop = this.el.getBoundingClientRect().top; if (this.el.dir !== "rtl") { @@ -289,8 +291,7 @@ class TextAreaCaretHelper { const startPos = this.el.selectionStart; const endPos = this.el.selectionEnd; - this.el.value = - this.el.value.substring(0, startPos + offset) + value + this.el.value.substring(endPos, this.el.value.length); + this.el.value = this.el.value.substring(0, startPos + offset) + value + this.el.value.substring(endPos, this.el.value.length); this.el.selectionEnd = this.el.selectionStart = startPos + value.length + offset + (finalOffset ?? 0); } else { this.el.value += value; @@ -341,7 +342,7 @@ export class TextAreaAutoComplete { */ constructor(el, words = null, separator = null) { this.el = el; - this.helper = new TextAreaCaretHelper(el); + this.helper = new TextAreaCaretHelper(el, () => app.canvas.ds.scale); this.dropdown = $el("div.pysssss-autocomplete"); this.overrideWords = words; this.overrideSeparator = separator; @@ -481,10 +482,7 @@ export class TextAreaAutoComplete { ); const top = priorityMatches.length * 0.2; - return priorityMatches - .slice(0, top) - .concat(prefixMatches, priorityMatches.slice(top), includesMatches) - .slice(0, 20); + return priorityMatches.slice(0, top).concat(prefixMatches, priorityMatches.slice(top), includesMatches).slice(0, 20); } #update() { @@ -567,11 +565,7 @@ export class TextAreaAutoComplete { { onclick: () => { this.el.focus(); - this.helper.insertAtCursor( - (wordInfo.value ?? wordInfo.text) + this.separator, - -before.length, - wordInfo.caretOffset - ); + this.helper.insertAtCursor((wordInfo.value ?? wordInfo.text) + this.separator, -before.length, wordInfo.caretOffset); setTimeout(() => { this.#update(); }, 150); From 6519fa0471dc92906e2496181ac38db62e5d33ee Mon Sep 17 00:00:00 2001 From: pythongosssss <125205205+pythongosssss@users.noreply.github.com> Date: Wed, 3 Jan 2024 20:12:34 +0000 Subject: [PATCH 20/21] Replace _ with space --- web/js/autocompleter.js | 23 ++++++++++++++++++++++- web/js/common/autocomplete.js | 7 ++++++- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/web/js/autocompleter.js b/web/js/autocompleter.js index 3b0ce7a..8ce2304 100644 --- a/web/js/autocompleter.js +++ b/web/js/autocompleter.js @@ -356,7 +356,6 @@ app.registerExtension({ }, [ $el("input", { - id: id.replaceAll(".", "-"), type: "checkbox", checked: !!TextAreaAutoComplete.globalSeparator, onchange: (event) => { @@ -367,6 +366,26 @@ app.registerExtension({ }), ] ), + $el( + "label", + { + textContent: "Replace _ with space ", + style: { + display: "block", + }, + }, + [ + $el("input", { + type: "checkbox", + checked: !!TextAreaAutoComplete.replacer, + onchange: (event) => { + const checked = !!event.target.checked; + TextAreaAutoComplete.replacer = checked ? (v) => v.replaceAll("_", " ") : undefined; + localStorage.setItem(id + ".ReplaceUnderscore", checked); + }, + }), + ] + ), $el("button", { textContent: "Manage Custom Words", onclick: () => { @@ -384,6 +403,8 @@ app.registerExtension({ }, }); TextAreaAutoComplete.enabled = enabledSetting.value; + TextAreaAutoComplete.replacer = localStorage.getItem(id + ".ReplaceUnderscore") === "true" ? (v) => v.replaceAll("_", " ") : undefined; + }, beforeRegisterNodeDef(_, def) { // Process each input to see if there is a custom word list for diff --git a/web/js/common/autocomplete.js b/web/js/common/autocomplete.js index d04567f..862133b 100644 --- a/web/js/common/autocomplete.js +++ b/web/js/common/autocomplete.js @@ -314,6 +314,7 @@ class TextAreaCaretHelper { export class TextAreaAutoComplete { static globalSeparator = ""; static enabled = true; + static replacer = undefined; /** @type {Record>} */ static groups = {}; @@ -565,7 +566,11 @@ export class TextAreaAutoComplete { { onclick: () => { this.el.focus(); - this.helper.insertAtCursor((wordInfo.value ?? wordInfo.text) + this.separator, -before.length, wordInfo.caretOffset); + let value = wordInfo.value ?? wordInfo.text; + if(TextAreaAutoComplete.replacer) { + value = TextAreaAutoComplete.replacer(value); + } + this.helper.insertAtCursor(value + this.separator, -before.length, wordInfo.caretOffset); setTimeout(() => { this.#update(); }, 150); From ae391fb744a4779dce7a97ef663fa1736cc1a70d Mon Sep 17 00:00:00 2001 From: pythongosssss <125205205+pythongosssss@users.noreply.github.com> Date: Wed, 3 Jan 2024 20:28:13 +0000 Subject: [PATCH 21/21] ignore --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 1f266bf..5e9693e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ __pycache__ pysssss.json - +user/autocomplete.txt \ No newline at end of file