diff --git a/__init__.py b/__init__.py
index a9e47a6..a652b2e 100644
--- a/__init__.py
+++ b/__init__.py
@@ -13,6 +13,7 @@ from .modules.cda.py_int_to_string import PyIntToString
from .modules.cda.py_kv_float_create import PyKvFloatCreate
from .modules.cda.py_kv_int_create import PyKvIntCreate
from .modules.cda.py_kv_string_create import PyKvStringCreate
+from .modules.cda.py_list_to_string import PyListToString
from .modules.cda.py_string_to_datetime import PyStringToDatetime
from .modules.cda.py_string_to_dict import PyStringToDict
from .modules.cda.py_string_to_float import PyStringToFloat
@@ -71,6 +72,7 @@ from .modules.pandas_wrapper.pandas_dropna import PandasDropNA
from .modules.pandas_wrapper.pandas_eq import PandasEq
from .modules.pandas_wrapper.pandas_eq_scalar_float import PandasEqScalarFloat
from .modules.pandas_wrapper.pandas_eq_scalar_int import PandasEqScalarInt
+from .modules.pandas_wrapper.pandas_excel_file_sheet_names import PandasExcelFileSheetNames
from .modules.pandas_wrapper.pandas_exp import PandasExp
from .modules.pandas_wrapper.pandas_feature_split_to_numpy import PandasFeatureSplitToNumpy
from .modules.pandas_wrapper.pandas_feature_split_to_pt import PandasFeatureSplitToPt
@@ -253,6 +255,7 @@ NODE_CLASS_MAPPINGS: Dict[str, Type[T]] = {
"PandasEq": PandasEq,
"PandasEqScalarFloat": PandasEqScalarFloat,
"PandasEqScalarInt": PandasEqScalarInt,
+ "PandasExcelFileSheetNames": PandasExcelFileSheetNames,
"PandasExp": PandasExp,
"PandasFeatureSplitToNumpy": PandasFeatureSplitToNumpy,
"PandasFeatureSplitToPt": PandasFeatureSplitToPt,
@@ -361,6 +364,7 @@ NODE_CLASS_MAPPINGS: Dict[str, Type[T]] = {
"PyKvFloatCreate": PyKvFloatCreate,
"PyKvIntCreate": PyKvIntCreate,
"PyKvStringCreate": PyKvStringCreate,
+ "PyListToString": PyListToString,
"PyStringToDatetime": PyStringToDatetime,
"PyStringToDict": PyStringToDict,
"PyStringToFloat": PyStringToFloat,
@@ -446,6 +450,7 @@ NODE_DISPLAY_NAME_MAPPINGS: Dict[str, str] = {
"PandasEq": "Pandas Eq",
"PandasEqScalarFloat": "Pandas Eq Scalar Float",
"PandasEqScalarInt": "Pandas Eq Scalar Int",
+ "PandasExcelFileSheetNames": "Pandas Excel File Sheet Names",
"PandasExp": "Pandas Exp",
"PandasFeatureSplitToNumpy": "Pandas Feature Split To Numpy",
"PandasFeatureSplitToPt": "Pandas Feature Split To Pt",
@@ -554,6 +559,7 @@ NODE_DISPLAY_NAME_MAPPINGS: Dict[str, str] = {
"PyKvFloatCreate": "Py Kv Float Create",
"PyKvIntCreate": "Py Kv Int Create",
"PyKvStringCreate": "Py Kv String Create",
+ "PyListToString": "Py List To String",
"PyStringToDatetime": "Py String To Datetime",
"PyStringToDict": "Py String To Dict",
"PyStringToFloat": "Py String To Float",
diff --git a/docs/reference/node_reference.md b/docs/reference/node_reference.md
index bbb3b20..852d1d7 100644
--- a/docs/reference/node_reference.md
+++ b/docs/reference/node_reference.md
@@ -118,6 +118,7 @@
| [Pandas Create Series From List](pandas_create_series_from_list.md) | Creates a Pandas Series from a Python list. |
| [Pandas Create Series From List Index List](pandas_create_series_from_list_index_list.md) | Creates a Pandas Series from a Python list of cell values and a list for index. |
| [Pandas Create With Index](pandas_create_with_index.md) | Creates a pandas DataFrame using values entered in the text field. Input data is assumed to have an index. |
+| [Pandas Excel File Sheet Names](pandas_excel_file_sheet_names.md) | Returns the string list of sheet names contained in the Excel file. |
| [Pandas Load CSV](pandas_load_csv.md) | Loads CSV files into a pandas DataFrame. |
| [Pandas Load CSV With Encoding](pandas_load_csv_with_encoding.md) | Loads CSV files into a pandas DataFrame. |
| [Pandas Load CSV With Index](pandas_load_csv_with_index.md) | Loads CSV files into a pandas DataFrame. |
@@ -189,6 +190,7 @@
| [Py Kv Float Create](py_kv_float_create.md) | Creates a Python dictionary with a string key and a float value. |
| [Py Kv Int Create](py_kv_int_create.md) | Creates a Python dictionary with an string key and an integer value. |
| [Py Kv String Create](py_kv_string_create.md) | Creates a Python dictionary with a string key and a string value. |
+| [Py List To String](py_list_to_string.md) | Converts a Python list value to a Python string |
| [Py String To Datetime](py_string_to_datetime.md) | Converts a Python string to a Python datetime.datetime. |
| [Py String To Dict](py_string_to_dict.md) | Creates a Python dictionary using the string values entered in the text field. |
| [Py String To Float](py_string_to_float.md) | Converts a Python string to a float. |
diff --git a/docs/reference/pandas_excel_file_sheet_names.md b/docs/reference/pandas_excel_file_sheet_names.md
new file mode 100644
index 0000000..7302da4
--- /dev/null
+++ b/docs/reference/pandas_excel_file_sheet_names.md
@@ -0,0 +1,23 @@
+# Pandas Excel File Sheet Names
+Returns the string list of sheet names contained in the Excel file.
+
+This node uses ExcelFile method using the openpyxl package.
+The openpyxl package is specified as a dependency in requirements.txt, so you should not need to manually install it.
+See [Pandas documentation](https://pandas.pydata.org/docs/reference/api/pandas.ExcelFile.html) for details.
+
+To display the list, connect output to PyListToString node to convert the list to a string, then connect the output to Pandas Show Text node to display the string.
+
+## Input
+| Name | Data type |
+|---|---|
+| file_path | String |
+
+## Output
+| Data type |
+|---|
+| Pylist |
+
+
+Category: IO
+
+ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved.
diff --git a/docs/reference/py_list_to_string.md b/docs/reference/py_list_to_string.md
new file mode 100644
index 0000000..c45642d
--- /dev/null
+++ b/docs/reference/py_list_to_string.md
@@ -0,0 +1,17 @@
+# Py List To String
+Converts a Python list value to a Python string
+
+## Input
+| Name | Data type |
+|---|---|
+| value | Pylist |
+
+## Output
+| Data type |
+|---|
+| String |
+
+
+Category: Python wrapper
+
+ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved.
diff --git a/examples/workflows/excel_file_sheet_names.json b/examples/workflows/excel_file_sheet_names.json
new file mode 100644
index 0000000..c371b97
--- /dev/null
+++ b/examples/workflows/excel_file_sheet_names.json
@@ -0,0 +1 @@
+{"id":"d3e44069-d0c1-4d4b-b205-9aac7b243871","revision":0,"last_node_id":3,"last_link_id":2,"nodes":[{"id":2,"type":"PyListToString","pos":[743.5132446289062,244.38381958007812],"size":[214.20001220703125,26],"flags":{},"order":1,"mode":0,"inputs":[{"localized_name":"value","name":"value","type":"PYLIST","link":1}],"outputs":[{"localized_name":"STRING","name":"STRING","type":"STRING","links":[2]}],"properties":{"Node name for S&R":"PyListToString"}},{"id":1,"type":"PandasExcelFileSheetNames","pos":[350.7860107421875,242.56568908691406],"size":[365.4000244140625,58],"flags":{},"order":0,"mode":0,"inputs":[{"localized_name":"file_path","name":"file_path","type":"STRING","widget":{"name":"file_path"},"link":null}],"outputs":[{"localized_name":"PYLIST","name":"PYLIST","type":"PYLIST","links":[1]}],"properties":{"Node name for S&R":"PandasExcelFileSheetNames"},"widgets_values":["custom_nodes/data-analysis/examples/datasets/test_file.xlsx"]},{"id":3,"type":"PandasShowText","pos":[987.1495361328125,242.56565856933594],"size":[210,76],"flags":{},"order":2,"mode":0,"inputs":[{"localized_name":"text","name":"text","type":"STRING","link":2}],"outputs":[{"localized_name":"STRING","name":"STRING","shape":6,"type":"STRING","links":null}],"properties":{"Node name for S&R":"PandasShowText"},"widgets_values":["['Sheet1', 'FruitPrice']"]}],"links":[[1,1,0,2,0,"PYLIST"],[2,2,0,3,0,"STRING"]],"groups":[],"config":{},"extra":{"ds":{"scale":1.1000000000000005,"offset":[75.57754666842986,116.52528218135221]},"frontendVersion":"1.16.9","VHS_latentpreview":false,"VHS_latentpreviewrate":0,"VHS_MetadataImage":true,"VHS_KeepIntermediate":true},"version":0.4}
\ No newline at end of file
diff --git a/modules/cda/py_list_to_string.py b/modules/cda/py_list_to_string.py
new file mode 100644
index 0000000..d068968
--- /dev/null
+++ b/modules/cda/py_list_to_string.py
@@ -0,0 +1,39 @@
+from typing import Any, Dict, List
+
+class PyListToString:
+ """
+ Converts a Python list value to a Python string
+
+ category: Python wrapper
+ """
+
+ @classmethod
+ def INPUT_TYPES(cls) -> Dict[str, Any]:
+ """
+ Defines the input types for the function.
+
+ Returns:
+ Dict[str, Any]: A dictionary specifying required input types.
+ """
+ return {
+ "required": {
+ "value": ("PYLIST", {})
+ }
+ }
+
+ RETURN_TYPES: tuple = ("STRING",)
+ FUNCTION: str = "f"
+ CATEGORY: str = "Data Analysis"
+
+ def f(self, value: List) -> tuple:
+ """
+ Converts a Python list value to a Python string
+
+ Args:
+ value (list): List value to convert.
+
+ Returns:
+ tuple: A tuple containing the converted value.
+ """
+ return (str(value),)
+
\ No newline at end of file
diff --git a/modules/pandas_wrapper/pandas_excel_file_sheet_names.py b/modules/pandas_wrapper/pandas_excel_file_sheet_names.py
new file mode 100644
index 0000000..20f1251
--- /dev/null
+++ b/modules/pandas_wrapper/pandas_excel_file_sheet_names.py
@@ -0,0 +1,51 @@
+from typing import Any, Dict
+import pandas as pd
+
+
+class PandasExcelFileSheetNames:
+ """
+ Returns the string list of sheet names contained in the Excel file.
+
+ This node uses ExcelFile method using the openpyxl package.
+ The openpyxl package is specified as a dependency in requirements.txt, so you should not need to manually install it.
+ See [Pandas documentation](https://pandas.pydata.org/docs/reference/api/pandas.ExcelFile.html) for details.
+
+ To display the list, connect output to PyListToString node to convert the list to a string, then connect the output to Pandas Show Text node to display the string.
+
+ category: IO
+ """
+
+ @classmethod
+ def INPUT_TYPES(cls) -> Dict[str, Any]:
+ """
+ Defines the input types for the function.
+
+ Returns:
+ Dict[str, Any]: A dictionary specifying required input types.
+ """
+ return {
+ "required": {
+ "file_path": ("STRING", {"multiline": False}),
+ }
+ }
+
+ RETURN_TYPES: tuple = ("PYLIST",)
+ FUNCTION: str = "f"
+ CATEGORY: str = "Data Analysis"
+
+ @classmethod
+ def IS_CHANGED(cls, **kw):
+ return float("NaN")
+
+ def f(self, file_path: str) -> tuple:
+ """
+ Returns the string list of sheet names contained in the Excel file.
+
+ Args:
+ file_path (str): The path to the Excel file.
+
+ Returns:
+ tuple: A tuple containing a string list of sheet names.
+ """
+ pylist = pd.ExcelFile(file_path, engine="openpyxl").sheet_names
+ return (pylist,)
diff --git a/pyproject.toml b/pyproject.toml
index 51b8cc7..dca1e39 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,7 +1,7 @@
[project]
name = "data-analysis"
description = "Data analysis custom modules for ComfyUI – Integrates Pandas, Seaborn and Matplotlib into ComfyUI."
-version = "3.0.7"
+version = "3.1.0"
license = {file = "LICENSE"}
[project.urls]
diff --git a/requirements.txt b/requirements.txt
index b504a51..b689164 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -3,4 +3,5 @@ pandas>=2.2.3
seaborn>=0.13.2
scipy>=1.14.1
scikit-learn>=1.6.1
-openpyxl>=3.1.5
\ No newline at end of file
+openpyxl>=3.1.5
+lxml>=5.4.0
\ No newline at end of file
diff --git a/tests/cda/py_list_to_string_test.py b/tests/cda/py_list_to_string_test.py
new file mode 100644
index 0000000..965a59f
--- /dev/null
+++ b/tests/cda/py_list_to_string_test.py
@@ -0,0 +1,29 @@
+import os
+import sys
+import unittest
+
+PROJECT_ROOT = os.path.realpath(os.path.join(os.path.dirname(__file__), "..", ".."))
+MODULE_ROOT = os.path.join(PROJECT_ROOT, "modules")
+sys.path.append(MODULE_ROOT)
+from cda.py_list_to_string import PyListToString
+
+class TestPyListToString(unittest.TestCase):
+ def setUp(self):
+ """Set up test data."""
+ self.node = PyListToString()
+ self.test_cases = [
+ ([1,2,3], "[1, 2, 3]"),
+ (["apple", "banana"], "['apple', 'banana']"),
+ ([], "[]")
+ ]
+
+ def test_list_to_string(self):
+ """Test converting a list to string."""
+ for value, expected in self.test_cases:
+ with self.subTest(value=value):
+ retval = self.node.f(value=value)[0]
+ self.assertEqual(retval, expected, f"Mismatch for value={value}")
+
+
+if __name__ == "__main__":
+ unittest.main()
\ No newline at end of file
diff --git a/tests/pandas_wrapper/pandas_excel_file_sheet_names_test.py b/tests/pandas_wrapper/pandas_excel_file_sheet_names_test.py
new file mode 100644
index 0000000..234d23c
--- /dev/null
+++ b/tests/pandas_wrapper/pandas_excel_file_sheet_names_test.py
@@ -0,0 +1,34 @@
+import os
+import sys
+import unittest
+import pandas as pd
+
+PROJECT_ROOT = os.path.realpath(os.path.join(os.path.dirname(__file__), "..", ".."))
+MODULE_ROOT = os.path.join(PROJECT_ROOT, "modules")
+sys.path.append(MODULE_ROOT)
+from pandas_wrapper.pandas_excel_file_sheet_names import PandasExcelFileSheetNames
+
+
+class TestPandasExcelFileSheetNames(unittest.TestCase):
+ def setUp(self):
+ """Set up test data."""
+ self.node = PandasExcelFileSheetNames()
+ self.test_file_path = os.path.join(
+ os.path.dirname(__file__), "..", "..",
+ "examples", "datasets", "test_file.xlsx")
+
+ self.test_data_list = [
+ "Sheet1",
+ "FruitPrice"
+ ]
+
+ def test_excel_file_sheet_names(self):
+ """Reads sheet names from an excel file.
+ """
+ expected = self.test_data_list
+ actual = self.node.f(self.test_file_path)[0]
+ self.assertEqual(actual, expected)
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/web/js/ShowDataFrame.js b/web/js/ShowDataFrame.js
deleted file mode 100644
index 4fe2492..0000000
--- a/web/js/ShowDataFrame.js
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
-This code is based on
-https://github.com/pythongosssss/ComfyUI-Custom-Scripts/blob/main/web/js/showText.js
-
-See credit/credit.md for the full license.
-*/
-
-import { app } from "../../../scripts/app.js";
-import { ComfyWidgets } from "../../../scripts/widgets.js";
-
-// Displays input text on a node
-app.registerExtension({
- name: "HowToSD.ShowDataFrame",
- async beforeRegisterNodeDef(nodeType, nodeData, app) {
- if (nodeData.name === "PandasShowDataFrame") { // This needs to match NODE_CLASS_MAPPINGS in Python
- function populate(text) {
- if (this.widgets) {
- // Clear all widgets on the node
- for (let i = 0; i < this.widgets.length; i++) {
- // if onRemove method exists, call it.
- this.widgets[i].onRemove?.();
- }
- this.widgets.length = 0;
- }
-
- const v = [...text]; // Copy the text list (e.g. text.copy())
- if (!v[0]) {
- v.shift();
- }
- for (const list of v) {
- const w = ComfyWidgets["STRING"](this, "text0", ["STRING", { multiline: true }], app).widget;
- w.inputEl.readOnly = true;
- w.inputEl.style.opacity = 0.6;
- w.value = list;
- }
-
- requestAnimationFrame(() => {
- const sz = this.computeSize();
- if (sz[0] < this.size[0]) {
- sz[0] = this.size[0];
- }
- if (sz[1] < this.size[1]) {
- sz[1] = this.size[1];
- }
- this.onResize?.(sz);
- app.graph.setDirtyCanvas(true, false);
- });
- }
-
- // When the node is executed we will be sent the input text, display this in the widget
- const onExecuted = nodeType.prototype.onExecuted;
- nodeType.prototype.onExecuted = function (message) {
- onExecuted?.apply(this, arguments);
- populate.call(this, message.text);
- };
-
- const onConfigure = nodeType.prototype.onConfigure;
- nodeType.prototype.onConfigure = function () {
- onConfigure?.apply(this, arguments);
- if (this.widgets_values?.length) {
- populate.call(this, this.widgets_values.slice(+this.widgets_values.length > 1));
- }
- };
- }
- },
-});
\ No newline at end of file
diff --git a/web/js/ShowIndex.js b/web/js/ShowIndex.js
deleted file mode 100644
index cbf8217..0000000
--- a/web/js/ShowIndex.js
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
-This code is based on
-https://github.com/pythongosssss/ComfyUI-Custom-Scripts/blob/main/web/js/showText.js
-
-See credit/credit.md for the full license.
-*/
-
-import { app } from "../../../scripts/app.js";
-import { ComfyWidgets } from "../../../scripts/widgets.js";
-
-// Displays input text on a node
-app.registerExtension({
- name: "HowToSD.ShowIndex",
- async beforeRegisterNodeDef(nodeType, nodeData, app) {
- if (nodeData.name === "PandasShowIndex") { // This needs to match NODE_CLASS_MAPPINGS in Python
- function populate(text) {
- if (this.widgets) {
- // Clear all widgets on the node
- for (let i = 0; i < this.widgets.length; i++) {
- // if onRemove method exists, call it.
- this.widgets[i].onRemove?.();
- }
- this.widgets.length = 0;
- }
-
- const v = [...text]; // Copy the text list (e.g. text.copy())
- if (!v[0]) {
- v.shift();
- }
- for (const list of v) {
- const w = ComfyWidgets["STRING"](this, "text0", ["STRING", { multiline: true }], app).widget;
- w.inputEl.readOnly = true;
- w.inputEl.style.opacity = 0.6;
- w.value = list;
- }
-
- requestAnimationFrame(() => {
- const sz = this.computeSize();
- if (sz[0] < this.size[0]) {
- sz[0] = this.size[0];
- }
- if (sz[1] < this.size[1]) {
- sz[1] = this.size[1];
- }
- this.onResize?.(sz);
- app.graph.setDirtyCanvas(true, false);
- });
- }
-
- // When the node is executed we will be sent the input text, display this in the widget
- const onExecuted = nodeType.prototype.onExecuted;
- nodeType.prototype.onExecuted = function (message) {
- onExecuted?.apply(this, arguments);
- populate.call(this, message.text);
- };
-
- const onConfigure = nodeType.prototype.onConfigure;
- nodeType.prototype.onConfigure = function () {
- onConfigure?.apply(this, arguments);
- if (this.widgets_values?.length) {
- populate.call(this, this.widgets_values.slice(+this.widgets_values.length > 1));
- }
- };
- }
- },
-});
\ No newline at end of file
diff --git a/web/js/ShowText.js b/web/js/ShowText.js
index 1fa1df7..934d4ff 100644
--- a/web/js/ShowText.js
+++ b/web/js/ShowText.js
@@ -8,57 +8,134 @@ See credit/credit.md for the full license.
import { app } from "../../../scripts/app.js";
import { ComfyWidgets } from "../../../scripts/widgets.js";
-// Displays input text on a node
app.registerExtension({
name: "HowToSD.ShowText",
async beforeRegisterNodeDef(nodeType, nodeData, app) {
- if (["PandasShowText", "CDAShowInt", "CDAShowFloat"].includes(nodeData.name)) { // This needs to match NODE_CLASS_MAPPINGS in Python
+
+ // Each node needs to match NODE_CLASS_MAPPINGS in Python
+ const targetNodes = [
+ "PandasShowText",
+ "PandasShowIndex",
+ "PandasShowDataFrame",
+ "CDAShowInt",
+ "CDAShowFloat",
+ "NumpyShow",
+ "PandasShowSeries",
+ ]
+
+ const noConvertedFieldNodes = [
+ "CDAShowInt",
+ "CDAShowFloat",
+ ]
+
+ if (targetNodes.includes(nodeData.name)) {
+
function populate(text) {
+ // 'this' is not bound automatically — it's explicitly set via .call() in onExecuted.
if (this.widgets) {
- for (let i = 1; i < this.widgets.length; i++) {
+ // On older frontend versions there is a hidden converted-widget
+ // Legacy version: const isConvertedWidget = +!!this.inputs?.[0].widget;
+ const hasInputWidget = !!(this.inputs?.[0]?.widget); // Coerce to boolean: true if widget exists
+ let isConvertedWidget = hasInputWidget ? 1 : 0; // Convert boolean to integer (1 or 0)
+
+ if (noConvertedFieldNodes.includes(nodeData.name)){ // Remove all widgets
+ isConvertedWidget = 0;
+ }
+
+ // Remove all dynamically added widgets, preserving the auto-converted input widget if present
+ for (let i = isConvertedWidget; i < this.widgets.length; i++) {
this.widgets[i].onRemove?.();
}
- this.widgets.length = 1;
+ this.widgets.length = isConvertedWidget;
}
- const v = [...text];
- if (!v[0]) {
+ const v = [...text]; // Shallow copy of text array into v
+ // If the first element is empty or undefined, remove it from the array
+ if (!v[0]) {
v.shift();
}
- for (const list of v) {
- const w = ComfyWidgets["STRING"](this, "text2", ["STRING", { multiline: true }], app).widget;
- w.inputEl.readOnly = true;
- w.inputEl.style.opacity = 0.6;
- w.value = list;
+ for (let row of v) { // Iterate over v. v can be 1d or 2d.
+ if (!(row instanceof Array)) row = [row];
+ for (const elem of row) {
+ // Create a new STRING widget
+ // Use field name like "text_0", "text_1", accounting for existing widgets
+ const fieldName = "text_" + (this.widgets?.length ?? 0);
+ const w = ComfyWidgets["STRING"](
+ this,
+ fieldName,
+ ["STRING", { multiline: true }],
+ app).widget;
+ w.inputEl.readOnly = true;
+ w.inputEl.style.opacity = 0.6;
+ w.value = elem;
+ }
}
+ /*
+ Defer until the next animation frame so all widgets are rendered and layout is stable.
+ Then compute the node size needed to fit new content.
+ If computeSize underestimates it, use the current node size as the floor to preserve layout.
+ */
requestAnimationFrame(() => {
- const sz = this.computeSize();
- if (sz[0] < this.size[0]) {
- sz[0] = this.size[0];
- }
- if (sz[1] < this.size[1]) {
- sz[1] = this.size[1];
- }
- this.onResize?.(sz);
- app.graph.setDirtyCanvas(true, false);
+ const sz = this.computeSize(); // Compute size based on current widgets
+
+ // Adjust to ensure the node fits its current visual width/height
+ if (sz[0] < this.size[0]) sz[0] = this.size[0]; // Fit existing node width
+ if (sz[1] < this.size[1]) sz[1] = this.size[1]; // Fit existing node height
+
+ this.onResize?.(sz); // Apply the size adjustment
+ app.graph.setDirtyCanvas(true, false); // Trigger a redraw of the canvas
});
}
+ // **onExecute** setup
// When the node is executed we will be sent the input text, display this in the widget
const onExecuted = nodeType.prototype.onExecuted;
nodeType.prototype.onExecuted = function (message) {
+ // Call the original onExecuted handler (if one exists), forwarding all arguments
onExecuted?.apply(this, arguments);
+
+ // Call our populate function, binding 'this' to the current node
populate.call(this, message.text);
};
+ // **configure** setup
+ const VALUES = Symbol(); // Unique internal key to store widget values
+ const configure = nodeType.prototype.configure;
+
+ nodeType.prototype.configure = function () {
+ // Store unmodified widget values as they get removed on configure by new frontend
+ // This value is applied in onConfigure
+ this[VALUES] = arguments[0]?.widgets_values;
+
+ // Call the original configure handler, if it exists
+ return configure?.apply(this, arguments);
+ };
+
+ // **onConfigure** setup
+ // Populate node's widgets using the saved values in Configure
const onConfigure = nodeType.prototype.onConfigure;
nodeType.prototype.onConfigure = function () {
onConfigure?.apply(this, arguments);
- if (this.widgets_values?.length) {
- populate.call(this, this.widgets_values.slice(+this.widgets_values.length > 1));
+
+ // Read saved widget values
+ const widgets_values = this[VALUES];
+ if (widgets_values?.length) {
+ // In newer frontend there seems to be a delay in creating the initial widget
+ requestAnimationFrame(() => {
+ // Legacy code
+ // populate.call(this,
+ // widgets_values.slice(+(widgets_values.length > 1 && this.inputs?.[0].widget)));
+
+ const hasAutoWidget = widgets_values.length > 1 && this.inputs?.[0]?.widget;
+ let startIndex = hasAutoWidget ? 1 : 0;
+ if (noConvertedFieldNodes.includes(nodeData.name)){ // Remove all widgets
+ startIndex = 0;
+ }
+ populate.call(this, widgets_values.slice(startIndex));
+ });
}
};
}
},
-});
\ No newline at end of file
+});
diff --git a/web/js/ShowTextGeneric.js b/web/js/ShowTextGeneric.js
deleted file mode 100644
index e56aa46..0000000
--- a/web/js/ShowTextGeneric.js
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
-This code is based on
-https://github.com/pythongosssss/ComfyUI-Custom-Scripts/blob/main/web/js/showText.js
-
-See credit/credit.md for the full license.
-*/
-
-import { app } from "../../../scripts/app.js";
-import { ComfyWidgets } from "../../../scripts/widgets.js";
-
-// Displays input text on a node
-app.registerExtension({
- name: "HowToSD.ShowTextGeneric",
- async beforeRegisterNodeDef(nodeType, nodeData, app) {
- if (["NumpyShow", "PandasShowSeries"].includes(nodeData.name)) { // This needs to match NODE_CLASS_MAPPINGS in Python
- function populate(text) {
- if (this.widgets) {
- // Clear all widgets on the node
- for (let i = 0; i < this.widgets.length; i++) {
- // if onRemove method exists, call it.
- this.widgets[i].onRemove?.();
- }
- this.widgets.length = 0;
- }
-
- const v = [...text]; // Copy the text list (e.g. text.copy())
- if (!v[0]) {
- v.shift();
- }
- for (const list of v) {
- const w = ComfyWidgets["STRING"](this, "text0", ["STRING", { multiline: true }], app).widget;
- w.inputEl.readOnly = true;
- w.inputEl.style.opacity = 0.6;
- w.value = list;
- }
-
- requestAnimationFrame(() => {
- const sz = this.computeSize();
- if (sz[0] < this.size[0]) {
- sz[0] = this.size[0];
- }
- if (sz[1] < this.size[1]) {
- sz[1] = this.size[1];
- }
- this.onResize?.(sz);
- app.graph.setDirtyCanvas(true, false);
- });
- }
-
- // When the node is executed we will be sent the input text, display this in the widget
- const onExecuted = nodeType.prototype.onExecuted;
- nodeType.prototype.onExecuted = function (message) {
- onExecuted?.apply(this, arguments);
- populate.call(this, message.text);
- };
-
- const onConfigure = nodeType.prototype.onConfigure;
- nodeType.prototype.onConfigure = function () {
- onConfigure?.apply(this, arguments);
- if (this.widgets_values?.length) {
- populate.call(this, this.widgets_values.slice(+this.widgets_values.length > 1));
- }
- };
- }
- },
-});
\ No newline at end of file