From 6f5b74f6f7e7fe4476a101edf625193037717f4c Mon Sep 17 00:00:00 2001 From: JaredTherriault Date: Wed, 6 Mar 2024 14:57:53 -0800 Subject: [PATCH 01/17] Add undo support to autocomplete --- web/js/common/autocomplete.js | 68 ++++++++++++++++++++++++++--------- 1 file changed, 52 insertions(+), 16 deletions(-) diff --git a/web/js/common/autocomplete.js b/web/js/common/autocomplete.js index 8db269e..8df1863 100644 --- a/web/js/common/autocomplete.js +++ b/web/js/common/autocomplete.js @@ -4,16 +4,16 @@ import { addStylesheet } from "./utils.js"; addStylesheet(import.meta.url); /* - https://github.com/component/textarea-caret-position - The MIT License (MIT) + https://github.com/component/textarea-caret-position + The MIT License (MIT) - Copyright (c) 2015 Jonathan Ong me@jongleberry.com + Copyright (c) 2015 Jonathan Ong me@jongleberry.com - Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ const getCaretCoordinates = (function () { // We'll copy the properties below into the mirror div. @@ -157,19 +157,19 @@ const getCaretCoordinates = (function () { })(); /* - Key functions from: - https://github.com/yuku/textcomplete - © Yuku Takahashi - This software is licensed under the MIT license. + Key functions from: + https://github.com/yuku/textcomplete + © Yuku Takahashi - This software is licensed under the MIT license. - The MIT License (MIT) + The MIT License (MIT) - Copyright (c) 2015 Jonathan Ong me@jongleberry.com + Copyright (c) 2015 Jonathan Ong me@jongleberry.com - Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ const CHAR_CODE_ZERO = "0".charCodeAt(0); const CHAR_CODE_NINE = "9".charCodeAt(0); @@ -291,10 +291,46 @@ 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); + // Move selection to beginning of offset + this.el.selectionStart = this.el.selectionStart + offset; + + // Using execCommand to support undo, but since it's officially + // 'deprecated' we need a backup solution, but it won't support undo :( + let pasted = true; + try { + if (!document.execCommand("insertText", false, value)) { + pasted = false; + } + } catch (e) { + console.error("Error caught during execCommand:", e); + pasted = false; + } + + if (!pasted) { + console.error( + "execCommand unsuccessful; not supported. Adding text manually, no undo support."); + textarea.setRangeText(modifiedText, this.el.selectionStart, this.el.selectionEnd, 'end'); + } + this.el.selectionEnd = this.el.selectionStart = startPos + value.length + offset + (finalOffset ?? 0); } else { - this.el.value += value; + // Using execCommand to support undo, but since it's officially + // 'deprecated' we need a backup solution, but it won't support undo :( + let pasted = true; + try { + if (!document.execCommand("insertText", false, value)) { + pasted = false; + } + } catch (e) { + console.error("Error caught during execCommand:", e); + pasted = false; + } + + if (!pasted) { + console.error( + "execCommand unsuccessful; not supported. Adding text manually, no undo support."); + this.el.value += value; + } } } } From e87b1cc3df2be3ba8e3a8822b2cae8e4d7b0bdea Mon Sep 17 00:00:00 2001 From: JaredTherriault Date: Wed, 6 Mar 2024 16:11:51 -0800 Subject: [PATCH 02/17] Revert "Add undo support to autocomplete" This reverts commit 6f5b74f6f7e7fe4476a101edf625193037717f4c. --- web/js/common/autocomplete.js | 68 +++++++++-------------------------- 1 file changed, 16 insertions(+), 52 deletions(-) diff --git a/web/js/common/autocomplete.js b/web/js/common/autocomplete.js index 8df1863..8db269e 100644 --- a/web/js/common/autocomplete.js +++ b/web/js/common/autocomplete.js @@ -4,16 +4,16 @@ import { addStylesheet } from "./utils.js"; addStylesheet(import.meta.url); /* - https://github.com/component/textarea-caret-position - The MIT License (MIT) + https://github.com/component/textarea-caret-position + The MIT License (MIT) - Copyright (c) 2015 Jonathan Ong me@jongleberry.com + Copyright (c) 2015 Jonathan Ong me@jongleberry.com - Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ const getCaretCoordinates = (function () { // We'll copy the properties below into the mirror div. @@ -157,19 +157,19 @@ const getCaretCoordinates = (function () { })(); /* - Key functions from: - https://github.com/yuku/textcomplete - © Yuku Takahashi - This software is licensed under the MIT license. + Key functions from: + https://github.com/yuku/textcomplete + © Yuku Takahashi - This software is licensed under the MIT license. - The MIT License (MIT) + The MIT License (MIT) - Copyright (c) 2015 Jonathan Ong me@jongleberry.com + Copyright (c) 2015 Jonathan Ong me@jongleberry.com - Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ const CHAR_CODE_ZERO = "0".charCodeAt(0); const CHAR_CODE_NINE = "9".charCodeAt(0); @@ -291,46 +291,10 @@ class TextAreaCaretHelper { const startPos = this.el.selectionStart; const endPos = this.el.selectionEnd; - // Move selection to beginning of offset - this.el.selectionStart = this.el.selectionStart + offset; - - // Using execCommand to support undo, but since it's officially - // 'deprecated' we need a backup solution, but it won't support undo :( - let pasted = true; - try { - if (!document.execCommand("insertText", false, value)) { - pasted = false; - } - } catch (e) { - console.error("Error caught during execCommand:", e); - pasted = false; - } - - if (!pasted) { - console.error( - "execCommand unsuccessful; not supported. Adding text manually, no undo support."); - textarea.setRangeText(modifiedText, this.el.selectionStart, this.el.selectionEnd, 'end'); - } - + 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 { - // Using execCommand to support undo, but since it's officially - // 'deprecated' we need a backup solution, but it won't support undo :( - let pasted = true; - try { - if (!document.execCommand("insertText", false, value)) { - pasted = false; - } - } catch (e) { - console.error("Error caught during execCommand:", e); - pasted = false; - } - - if (!pasted) { - console.error( - "execCommand unsuccessful; not supported. Adding text manually, no undo support."); - this.el.value += value; - } + this.el.value += value; } } } From 11e019487eadda46f8d6cd8fccce8a1f1dfafe0c Mon Sep 17 00:00:00 2001 From: JaredTherriault Date: Wed, 6 Mar 2024 16:14:34 -0800 Subject: [PATCH 03/17] Remove whitespace changes from undo support --- web/js/common/autocomplete.js | 40 +++++++++++++++++++++++++++++++++-- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/web/js/common/autocomplete.js b/web/js/common/autocomplete.js index 8db269e..4f133ee 100644 --- a/web/js/common/autocomplete.js +++ b/web/js/common/autocomplete.js @@ -291,10 +291,46 @@ 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); + // Move selection to beginning of offset + this.el.selectionStart = this.el.selectionStart + offset; + + // Using execCommand to support undo, but since it's officially + // 'deprecated' we need a backup solution, but it won't support undo :( + let pasted = true; + try { + if (!document.execCommand("insertText", false, value)) { + pasted = false; + } + } catch (e) { + console.error("Error caught during execCommand:", e); + pasted = false; + } + + if (!pasted) { + console.error( + "execCommand unsuccessful; not supported. Adding text manually, no undo support."); + textarea.setRangeText(modifiedText, this.el.selectionStart, this.el.selectionEnd, 'end'); + } + this.el.selectionEnd = this.el.selectionStart = startPos + value.length + offset + (finalOffset ?? 0); } else { - this.el.value += value; + // Using execCommand to support undo, but since it's officially + // 'deprecated' we need a backup solution, but it won't support undo :( + let pasted = true; + try { + if (!document.execCommand("insertText", false, value)) { + pasted = false; + } + } catch (e) { + console.error("Error caught during execCommand:", e); + pasted = false; + } + + if (!pasted) { + console.error( + "execCommand unsuccessful; not supported. Adding text manually, no undo support."); + this.el.value += value; + } } } } From aa94a9f58c25b2d435fb63c50f72f31e14c3cf34 Mon Sep 17 00:00:00 2001 From: Mitchell Reynolds Date: Tue, 12 Mar 2024 22:40:03 -0400 Subject: [PATCH 04/17] Fix relative paths --- web/js/autocompleter.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/js/autocompleter.js b/web/js/autocompleter.js index 55d0f5e..5d12176 100644 --- a/web/js/autocompleter.js +++ b/web/js/autocompleter.js @@ -1,7 +1,7 @@ import { app } from "../../../scripts/app.js"; import { ComfyWidgets } from "../../../scripts/widgets.js"; -import { api } from "../../../../scripts/api.js"; -import { $el, ComfyDialog } from "../../../../scripts/ui.js"; +import { api } from "../../../scripts/api.js"; +import { $el, ComfyDialog } from "../../../scripts/ui.js"; import { TextAreaAutoComplete } from "./common/autocomplete.js"; import { ModelInfoDialog } from "./common/modelInfoDialog.js"; import { LoraInfoDialog } from "./modelInfo.js"; From 730f95b1615ca0651464432ce511362f5a774816 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=AB=98=E4=BC=9F?= Date: Thu, 14 Mar 2024 14:01:34 +0800 Subject: [PATCH 05/17] fix: Fix a bug in notify, line is "if unique_id and extra_pnginfo and workflow in extra_pnginfo[0]", Error: TypeError: argument of type NoneType is not iterable --- py/show_text.py | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/py/show_text.py b/py/show_text.py index f0fb564..5a013a5 100644 --- a/py/show_text.py +++ b/py/show_text.py @@ -19,12 +19,24 @@ class ShowText: CATEGORY = "utils" - def notify(self, text, unique_id = None, extra_pnginfo=None): - if unique_id and extra_pnginfo and "workflow" in extra_pnginfo[0]: - workflow = extra_pnginfo[0]["workflow"] - node = next((x for x in workflow["nodes"] if str(x["id"]) == unique_id[0]), None) - if node: - node["widgets_values"] = [text] + def notify(self, text, unique_id=None, extra_pnginfo=None): + if unique_id is not None and extra_pnginfo is not None: + if not isinstance(extra_pnginfo, list): + print("Error: extra_pnginfo is not a list") + elif ( + not isinstance(extra_pnginfo[0], dict) + or "workflow" not in extra_pnginfo[0] + ): + print("Error: extra_pnginfo[0] is not a dict or missing 'workflow' key") + else: + workflow = extra_pnginfo[0]["workflow"] + node = next( + (x for x in workflow["nodes"] if str(x["id"]) == str(unique_id[0])), + None, + ) + if node: + node["widgets_values"] = [text] + return {"ui": {"text": text}, "result": (text,)} From 865094ca5bf7ce6f684c6cf0f80287c3cc7ecd8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torbj=C3=B6rn=20L=C3=B6nnemark?= Date: Sat, 23 Mar 2024 16:31:07 +0100 Subject: [PATCH 06/17] Update lightbox with images generated while it is open This removes the need to close and re-open the lightbox to view images that were completed while it was open. --- web/js/common/lightbox.js | 10 ++++++++++ web/js/imageFeed.js | 2 ++ 2 files changed, 12 insertions(+) diff --git a/web/js/common/lightbox.js b/web/js/common/lightbox.js index 4034597..d351acf 100644 --- a/web/js/common/lightbox.js +++ b/web/js/common/lightbox.js @@ -107,6 +107,16 @@ export class Lightbox { this.img.src = img; this.img.style.opacity = 1; } + + async updateWithNewImage(img, feedDirection) { + // No-op if lightbox is not open + if (this.el.style.display === "none" || this.el.style.opacity === "0") return; + + // Ensure currently shown image does not change + const [method, shift] = feedDirection === "newest first" ? ["unshift", 1] : ["push", 0]; + this.images[method](img); + await this.update(shift); + } } export const lightbox = new Lightbox(); diff --git a/web/js/imageFeed.js b/web/js/imageFeed.js index 3a77c29..bd797f8 100644 --- a/web/js/imageFeed.js +++ b/web/js/imageFeed.js @@ -438,6 +438,8 @@ app.registerExtension({ ), ]) ); + // If lightbox is open, update it with new image + lightbox.updateWithNewImage(href, feedDirection.value); } } }); From dc34bf2e73b8111343325f964041f93cd9a5ebd3 Mon Sep 17 00:00:00 2001 From: pythongosssss <125205205+pythongosssss@users.noreply.github.com> Date: Sat, 23 Mar 2024 17:23:21 +0000 Subject: [PATCH 07/17] Use loras from node --- web/js/autocompleter.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/web/js/autocompleter.js b/web/js/autocompleter.js index 5d12176..ce4dde3 100644 --- a/web/js/autocompleter.js +++ b/web/js/autocompleter.js @@ -293,9 +293,14 @@ app.registerExtension({ } async function addLoras() { - const loras = await api - .fetchApi("/pysssss/loras", { cache: "no-store" }) - .then(res => res.json()); + let loras; + try { + loras = LiteGraph.registered_node_types["LoraLoader"]?.nodeData.input.required.lora_name[0]; + } catch (error) { + loras = await api + .fetchApi("/pysssss/loras", { cache: "no-store" }) + .then(res => res.json()); + } const words = {}; words["lora:"] = { text: "lora:" }; From d8c66bcf51368b886cf29a6766a6be7b383e49d7 Mon Sep 17 00:00:00 2001 From: pythongosssss <125205205+pythongosssss@users.noreply.github.com> Date: Sat, 23 Mar 2024 17:25:55 +0000 Subject: [PATCH 08/17] Fix quantifiers --- py/string_function.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/py/string_function.py b/py/string_function.py index 532e974..cdf9a00 100644 --- a/py/string_function.py +++ b/py/string_function.py @@ -7,11 +7,11 @@ class StringFunction: "required": { "action": (["append", "replace"], {}), "tidy_tags": (["yes", "no"], {}), - "text_a": ("STRING", {"multiline": True}), - "text_b": ("STRING", {"multiline": True}), + "text_a": ("STRING", {"multiline": True, "dynamicPrompts": False}), + "text_b": ("STRING", {"multiline": True, "dynamicPrompts": False}), }, "optional": { - "text_c": ("STRING", {"multiline": True}) + "text_c": ("STRING", {"multiline": True, "dynamicPrompts": False}) } } From f072d1228b91342414edae4899b63d675174f4dd Mon Sep 17 00:00:00 2001 From: pythongosssss <125205205+pythongosssss@users.noreply.github.com> Date: Sat, 23 Mar 2024 17:31:10 +0000 Subject: [PATCH 09/17] fix race condition loading loras --- web/js/autocompleter.js | 111 ++++++++++++++++++++-------------------- 1 file changed, 55 insertions(+), 56 deletions(-) diff --git a/web/js/autocompleter.js b/web/js/autocompleter.js index ce4dde3..b126fb9 100644 --- a/web/js/autocompleter.js +++ b/web/js/autocompleter.js @@ -275,62 +275,6 @@ const id = "pysssss.AutoCompleter"; app.registerExtension({ name: id, init() { - async function addEmbeddings() { - const embeddings = await api.getEmbeddings(); - const words = {}; - words["embedding:"] = { text: "embedding:" }; - - for (const emb of embeddings) { - const v = `embedding:${emb}`; - words[v] = { - text: v, - info: () => new EmbeddingInfoDialog(emb).show("embeddings", emb), - use_replacer: false, - }; - } - - TextAreaAutoComplete.updateWords("pysssss.embeddings", words); - } - - async function addLoras() { - let loras; - try { - loras = LiteGraph.registered_node_types["LoraLoader"]?.nodeData.input.required.lora_name[0]; - } catch (error) { - loras = await api - .fetchApi("/pysssss/loras", { cache: "no-store" }) - .then(res => res.json()); - } - const words = {}; - words["lora:"] = { text: "lora:" }; - - for (const lora of loras) { - const v = ``; - words[v] = { - text: v, - info: () => new LoraInfoDialog(lora).show("loras", lora), - use_replacer: false, - }; - } - - TextAreaAutoComplete.updateWords("pysssss.loras", words); - } - - // store global words with/without loras - Promise.all([addEmbeddings(), addCustomWords()]) - .then(() => { - TextAreaAutoComplete.globalWordsExclLoras = Object.assign( - {}, - TextAreaAutoComplete.globalWords - ); - }) - .then(addLoras) - .then(() => { - if (!TextAreaAutoComplete.lorasEnabled) { - toggleLoras(); // off by default - } - }); - const STRING = ComfyWidgets.STRING; const SKIP_WIDGETS = new Set(["ttN xyPlot.x_values", "ttN xyPlot.y_values"]); ComfyWidgets.STRING = function (node, inputName, inputData) { @@ -545,6 +489,61 @@ app.registerExtension({ TextAreaAutoComplete.insertOnEnter = localStorage.getItem(id + ".InsertOnEnter") !== "false"; TextAreaAutoComplete.lorasEnabled = localStorage.getItem(id + ".ShowLoras") === "true"; }, + setup() { + async function addEmbeddings() { + const embeddings = await api.getEmbeddings(); + const words = {}; + words["embedding:"] = { text: "embedding:" }; + + for (const emb of embeddings) { + const v = `embedding:${emb}`; + words[v] = { + text: v, + info: () => new EmbeddingInfoDialog(emb).show("embeddings", emb), + use_replacer: false, + }; + } + + TextAreaAutoComplete.updateWords("pysssss.embeddings", words); + } + + async function addLoras() { + let loras; + try { + loras = LiteGraph.registered_node_types["LoraLoader"]?.nodeData.input.required.lora_name[0]; + } catch (error) {} + + if (!loras?.length) { + loras = await api.fetchApi("/pysssss/loras", { cache: "no-store" }).then((res) => res.json()); + } + + const words = {}; + words["lora:"] = { text: "lora:" }; + + for (const lora of loras) { + const v = ``; + words[v] = { + text: v, + info: () => new LoraInfoDialog(lora).show("loras", lora), + use_replacer: false, + }; + } + + TextAreaAutoComplete.updateWords("pysssss.loras", words); + } + + // store global words with/without loras + Promise.all([addEmbeddings(), addCustomWords()]) + .then(() => { + TextAreaAutoComplete.globalWordsExclLoras = Object.assign({}, TextAreaAutoComplete.globalWords); + }) + .then(addLoras) + .then(() => { + if (!TextAreaAutoComplete.lorasEnabled) { + toggleLoras(); // off by default + } + }); + }, beforeRegisterNodeDef(_, def) { // Process each input to see if there is a custom word list for // { input: { required: { something: ["STRING", { "pysssss.autocomplete": ["groupid", ["custom", "words"] ] }] } } } From 128b4864800d0f046b36d21fb01e0ba363116e96 Mon Sep 17 00:00:00 2001 From: pythongosssss <125205205+pythongosssss@users.noreply.github.com> Date: Sun, 24 Mar 2024 16:23:50 +0000 Subject: [PATCH 10/17] change spinner class --- web/js/common/spinner.css | 10 +++++----- web/js/common/spinner.js | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/web/js/common/spinner.css b/web/js/common/spinner.css index 9a29451..3b47902 100644 --- a/web/js/common/spinner.css +++ b/web/js/common/spinner.css @@ -1,10 +1,10 @@ -.lds-ring { +.pysssss-lds-ring { display: inline-block; position: relative; width: 80px; height: 80px; } -.lds-ring div { +.pysssss-lds-ring div { box-sizing: border-box; display: block; position: absolute; @@ -16,13 +16,13 @@ animation: lds-ring 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite; border-color: #fff transparent transparent transparent; } -.lds-ring div:nth-child(1) { +.pysssss-lds-ring div:nth-child(1) { animation-delay: -0.45s; } -.lds-ring div:nth-child(2) { +.pysssss-lds-ring div:nth-child(2) { animation-delay: -0.3s; } -.lds-ring div:nth-child(3) { +.pysssss-lds-ring div:nth-child(3) { animation-delay: -0.15s; } @keyframes lds-ring { diff --git a/web/js/common/spinner.js b/web/js/common/spinner.js index f01315d..4075990 100644 --- a/web/js/common/spinner.js +++ b/web/js/common/spinner.js @@ -4,6 +4,6 @@ addStylesheet(import.meta.url); export function createSpinner() { const div = document.createElement("div"); - div.innerHTML = `
`; + div.innerHTML = `
`; return div.firstElementChild; } From 4d0f1216706b39a57545da2682d727755867bdd3 Mon Sep 17 00:00:00 2001 From: pythongosssss <125205205+pythongosssss@users.noreply.github.com> Date: Sun, 24 Mar 2024 16:24:45 +0000 Subject: [PATCH 11/17] Update for new menu --- web/js/common/lightbox.css | 2 +- web/js/imageFeed.js | 59 +++++++++++++++++++++++++++++++------- 2 files changed, 50 insertions(+), 11 deletions(-) diff --git a/web/js/common/lightbox.css b/web/js/common/lightbox.css index 4357d9d..41fa52d 100644 --- a/web/js/common/lightbox.css +++ b/web/js/common/lightbox.css @@ -4,7 +4,7 @@ position: fixed; top: 0; left: 0; - z-index: 999; + z-index: 1000; background: rgba(0, 0, 0, 0.6); display: flex; align-items: center; diff --git a/web/js/imageFeed.js b/web/js/imageFeed.js index bd797f8..b353485 100644 --- a/web/js/imageFeed.js +++ b/web/js/imageFeed.js @@ -15,6 +15,9 @@ $el("style", { display: flex; flex-direction: column; } + div > .pysssss-image-feed { + position: static; + } .pysssss-image-feed--top, .pysssss-image-feed--bottom { width: 100vw; min-height: 30px; @@ -206,7 +209,7 @@ $el("style", { app.registerExtension({ name: "pysssss.ImageFeed", - setup() { + async setup() { let visible = true; const showButton = $el("button.comfy-settings-btn", { textContent: "🖼️", @@ -216,6 +219,17 @@ app.registerExtension({ display: "none", }, }); + let showMenuButton; + if (!app.menu.element.style.display && app.menu?.settingsGroup) { + showMenuButton = new (await import("../../../scripts/ui/components/button.js")).ComfyButton({ + icon: "image-multiple", + action: () => showButton.click(), + tooltip: "Show Image Feed 🐍", + content: "Show Image Feed 🐍", + }); + showMenuButton.enabled = false; + app.menu.settingsGroup.append(showMenuButton); + } const getVal = (n, d) => { const v = localStorage.getItem("pysssss.ImageFeed." + n); @@ -229,11 +243,19 @@ app.registerExtension({ localStorage.setItem("pysssss.ImageFeed." + n, v); }; - const imageFeed = $el("div.pysssss-image-feed", { - parent: document.body, - }); + const imageFeed = $el("div.pysssss-image-feed"); const imageList = $el("div.pysssss-image-feed-list"); + function updateMenuParent(location) { + if (showMenuButton) { + document.querySelector(".comfyui-body-" + location).append(imageFeed); + } else { + if (!imageFeed.parent) { + document.body.append(imageFeed); + } + } + } + const feedLocation = app.ui.settings.addSetting({ id: "pysssss.ImageFeed.Location", name: "🐍 Image Feed Location", @@ -255,6 +277,8 @@ app.registerExtension({ oninput: (e) => { feedLocation.value = e.target.value; imageFeed.className = `pysssss-image-feed pysssss-image-feed--${feedLocation.value}`; + updateMenuParent(feedLocation.value); + window.dispatchEvent(new Event("resize")); }, }, ["left", "top", "right", "bottom"].map((m) => @@ -270,6 +294,7 @@ app.registerExtension({ }, onChange(value) { imageFeed.className = `pysssss-image-feed pysssss-image-feed--${value}`; + updateMenuParent(value); }, }); @@ -311,7 +336,10 @@ app.registerExtension({ const clearButton = $el("button.pysssss-image-feed-btn.clear-btn", { textContent: "Clear", - onclick: () => imageList.replaceChildren(), + onclick: () => { + imageList.replaceChildren(); + window.dispatchEvent(new Event("resize")); + }, }); const hideButton = $el("button.pysssss-image-feed-btn.hide-btn", { @@ -319,8 +347,10 @@ app.registerExtension({ onclick: () => { imageFeed.style.display = "none"; showButton.style.display = "unset"; + if (showMenuButton) showMenuButton.enabled = true; saveVal("Visible", 0); visible = false; + window.dispatchEvent(new Event("resize")); }, }); @@ -331,6 +361,7 @@ app.registerExtension({ saveVal("ImageSize", v); columnInput.max = Math.max(10, v, columnInput.max); columnInput.value = v; + window.dispatchEvent(new Event("resize")); } imageFeed.append( @@ -350,6 +381,7 @@ app.registerExtension({ e.target.parentElement.title = `Controls the maximum size of the image feed panel (${e.target.value}vh)`; imageFeed.style.setProperty("--max-size", e.target.value); saveVal("FeedSize", e.target.value); + window.dispatchEvent(new Event("resize")); }, $: (el) => { requestAnimationFrame(() => { @@ -398,10 +430,14 @@ app.registerExtension({ showButton.onclick = () => { imageFeed.style.display = "block"; showButton.style.display = "none"; + if (showMenuButton) showMenuButton.enabled = false; + saveVal("Visible", 1); visible = true; + window.dispatchEvent(new Event("resize")); }; document.querySelector(".comfy-settings-btn").after(showButton); + window.dispatchEvent(new Event("resize")); if (!+getVal("Visible", 1)) { hideButton.onclick(); @@ -409,16 +445,16 @@ app.registerExtension({ api.addEventListener("executed", ({ detail }) => { if (visible && detail?.output?.images) { - if(detail.node?.includes?.(":")) { + if (detail.node?.includes?.(":")) { // Ignore group nodes const n = app.graph.getNodeById(detail.node.split(":")[0]); - if(n?.getInnerNodes) return; + if (n?.getInnerNodes) return; } for (const src of detail.output.images) { - const href = `./view?filename=${encodeURIComponent(src.filename)}&type=${ - src.type - }&subfolder=${encodeURIComponent(src.subfolder)}&t=${+new Date()}`; + const href = `./view?filename=${encodeURIComponent(src.filename)}&type=${src.type}&subfolder=${encodeURIComponent( + src.subfolder + )}&t=${+new Date()}`; const method = feedDirection.value === "newest first" ? "prepend" : "append"; imageList[method]( @@ -440,6 +476,9 @@ app.registerExtension({ ); // If lightbox is open, update it with new image lightbox.updateWithNewImage(href, feedDirection.value); + setTimeout(() => { + window.dispatchEvent(new Event("resize")); + }, 0); } } }); From c955427c155593c9c29e63afeddd79fd67739f80 Mon Sep 17 00:00:00 2001 From: pythongosssss <125205205+pythongosssss@users.noreply.github.com> Date: Sun, 24 Mar 2024 16:26:09 +0000 Subject: [PATCH 12/17] fix crash --- web/js/imageFeed.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/js/imageFeed.js b/web/js/imageFeed.js index b353485..4cf4644 100644 --- a/web/js/imageFeed.js +++ b/web/js/imageFeed.js @@ -220,7 +220,7 @@ app.registerExtension({ }, }); let showMenuButton; - if (!app.menu.element.style.display && app.menu?.settingsGroup) { + if (!app.menu?.element.style.display && app.menu.settingsGroup) { showMenuButton = new (await import("../../../scripts/ui/components/button.js")).ComfyButton({ icon: "image-multiple", action: () => showButton.click(), From 96affb0a7e2d70dbd996e472204208c85a0441d3 Mon Sep 17 00:00:00 2001 From: pythongosssss <125205205+pythongosssss@users.noreply.github.com> Date: Sun, 24 Mar 2024 16:26:58 +0000 Subject: [PATCH 13/17] actually fix --- web/js/imageFeed.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/js/imageFeed.js b/web/js/imageFeed.js index 4cf4644..2fc0521 100644 --- a/web/js/imageFeed.js +++ b/web/js/imageFeed.js @@ -220,7 +220,7 @@ app.registerExtension({ }, }); let showMenuButton; - if (!app.menu?.element.style.display && app.menu.settingsGroup) { + if (!app.menu?.element.style.display && app.menu?.settingsGroup) { showMenuButton = new (await import("../../../scripts/ui/components/button.js")).ComfyButton({ icon: "image-multiple", action: () => showButton.click(), From 81ec1f11a9b2e01922504e6ffb3856a0ae0eeea1 Mon Sep 17 00:00:00 2001 From: pythongosssss <125205205+pythongosssss@users.noreply.github.com> Date: Sun, 24 Mar 2024 16:42:50 +0000 Subject: [PATCH 14/17] fixes for new menu --- web/js/common/autocomplete.js | 1 + web/js/imageFeed.js | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/web/js/common/autocomplete.js b/web/js/common/autocomplete.js index 4f133ee..d2b017a 100644 --- a/web/js/common/autocomplete.js +++ b/web/js/common/autocomplete.js @@ -238,6 +238,7 @@ class TextAreaCaretHelper { fontSize: computedStyle.fontSize, fontFamily: computedStyle.fontFamily, padding: "0", + position: "absolute", }); body.appendChild(tempNode); diff --git a/web/js/imageFeed.js b/web/js/imageFeed.js index 2fc0521..78af31c 100644 --- a/web/js/imageFeed.js +++ b/web/js/imageFeed.js @@ -478,7 +478,7 @@ app.registerExtension({ lightbox.updateWithNewImage(href, feedDirection.value); setTimeout(() => { window.dispatchEvent(new Event("resize")); - }, 0); + }, 1); } } }); From 8975a4dfd8849cb5ca619b10a3b2133343671000 Mon Sep 17 00:00:00 2001 From: pythongosssss <125205205+pythongosssss@users.noreply.github.com> Date: Sun, 24 Mar 2024 17:35:47 +0000 Subject: [PATCH 15/17] Add max autocomplete suggestions --- web/js/autocompleter.js | 24 ++++++++++++++++++++++++ web/js/common/autocomplete.css | 1 + web/js/common/autocomplete.js | 4 +++- 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/web/js/autocompleter.js b/web/js/autocompleter.js index b126fb9..7a81c1e 100644 --- a/web/js/autocompleter.js +++ b/web/js/autocompleter.js @@ -466,6 +466,29 @@ app.registerExtension({ ), ] ), + $el( + "label", + { + textContent: "Max suggestions: ", + style: { + display: "block", + }, + }, + [ + $el("input", { + type: "number", + value: +TextAreaAutoComplete.suggestionCount, + style: { + width: "80px" + }, + onchange: (event) => { + const value = +event.target.value; + TextAreaAutoComplete.suggestionCount = value;; + localStorage.setItem(id + ".SuggestionCount", TextAreaAutoComplete.suggestionCount); + }, + }), + ] + ), $el("button", { textContent: "Manage Custom Words", onclick: () => { @@ -488,6 +511,7 @@ app.registerExtension({ TextAreaAutoComplete.insertOnTab = localStorage.getItem(id + ".InsertOnTab") !== "false"; TextAreaAutoComplete.insertOnEnter = localStorage.getItem(id + ".InsertOnEnter") !== "false"; TextAreaAutoComplete.lorasEnabled = localStorage.getItem(id + ".ShowLoras") === "true"; + TextAreaAutoComplete.suggestionCount = +localStorage.getItem(id + ".SuggestionCount"); }, setup() { async function addEmbeddings() { diff --git a/web/js/common/autocomplete.css b/web/js/common/autocomplete.css index fa22a39..1f371d6 100644 --- a/web/js/common/autocomplete.css +++ b/web/js/common/autocomplete.css @@ -5,6 +5,7 @@ font-family: sans-serif; box-shadow: 3px 3px 8px rgba(0, 0, 0, 0.4); z-index: 9999; + overflow: auto; } .pysssss-autocomplete-item { diff --git a/web/js/common/autocomplete.js b/web/js/common/autocomplete.js index d2b017a..cb25b83 100644 --- a/web/js/common/autocomplete.js +++ b/web/js/common/autocomplete.js @@ -355,6 +355,7 @@ export class TextAreaAutoComplete { static insertOnEnter = true; static replacer = undefined; static lorasEnabled = false; + static suggestionCount = 20; /** @type {Record>} */ static groups = {}; @@ -529,7 +530,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, TextAreaAutoComplete.suggestionCount); } #update() { @@ -649,6 +650,7 @@ export class TextAreaAutoComplete { const position = this.helper.getCursorOffset(); this.dropdown.style.left = (position.left ?? 0) + "px"; this.dropdown.style.top = (position.top ?? 0) + "px"; + this.dropdown.style.maxHeight = (window.innerHeight - position.top) + "px"; } #hide() { From 764f0d0cc5229712cfd2494a99ded79ac360d9d0 Mon Sep 17 00:00:00 2001 From: pythongosssss <125205205+pythongosssss@users.noreply.github.com> Date: Sun, 24 Mar 2024 18:22:37 +0000 Subject: [PATCH 16/17] Support custom favicon --- .gitignore | 4 +++- web/js/faviconStatus.js | 20 ++++++++++++++++++-- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 5e9693e..8d303a4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ __pycache__ pysssss.json -user/autocomplete.txt \ No newline at end of file +user/autocomplete.txt +web/js/assets/favicon.user.ico +web/js/assets/favicon-active.user.ico \ No newline at end of file diff --git a/web/js/faviconStatus.js b/web/js/faviconStatus.js index b68eafc..15bebdc 100644 --- a/web/js/faviconStatus.js +++ b/web/js/faviconStatus.js @@ -6,7 +6,7 @@ import { app } from "../../../scripts/app.js"; app.registerExtension({ name: "pysssss.FaviconStatus", - setup() { + async setup() { let link = document.querySelector("link[rel~='icon']"); if (!link) { link = document.createElement("link"); @@ -14,8 +14,24 @@ app.registerExtension({ document.head.appendChild(link); } + const getUrl = (active, user) => new URL(`assets/favicon${active ? "-active" : ""}${user ? ".user" : ""}.ico`, import.meta.url); + const testUrl = async (active) => { + const url = getUrl(active, true); + const r = await fetch(url, { + method: "HEAD", + }); + if (r.status === 200) { + return url; + } + return getUrl(active, false); + }; + const activeUrl = await testUrl(true); + const idleUrl = await testUrl(false); + + console.log(activeUrl, idleUrl); + let executing = false; - const update = () => (link.href = new URL(`assets/favicon${executing ? "-active" : ""}.ico`, import.meta.url)); + const update = () => (link.href = executing ? activeUrl : idleUrl); for (const e of ["execution_start", "progress"]) { api.addEventListener(e, () => { From 82b5db59d5f531fbe5d7e71e05b458351083e4df Mon Sep 17 00:00:00 2001 From: pythongosssss <125205205+pythongosssss@users.noreply.github.com> Date: Sun, 24 Mar 2024 18:35:50 +0000 Subject: [PATCH 17/17] Remove log --- web/js/faviconStatus.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/web/js/faviconStatus.js b/web/js/faviconStatus.js index 15bebdc..3dcd196 100644 --- a/web/js/faviconStatus.js +++ b/web/js/faviconStatus.js @@ -28,8 +28,6 @@ app.registerExtension({ const activeUrl = await testUrl(true); const idleUrl = await testUrl(false); - console.log(activeUrl, idleUrl); - let executing = false; const update = () => (link.href = executing ? activeUrl : idleUrl);