import { app } from "../../scripts/app.js";
import { api } from "../../scripts/api.js";
import "./lib/idenode/ace-builds/src-min-noconflict/ace.js";
import {
createWindowModal,
makeElement,
findWidget,
THEMES_MODAL_WINDOW,
comfyuiDesktopConfirm,
comfyuiDesktopPrompt,
} from "./utils.js";
ace.config.set(
"basePath",
new URL(
"./lib/idenode/ace-builds/src-min-noconflict/",
import.meta.url
).toString()
);
// Constants
const MAX_CHAR_VARNAME = 50;
const LIST_THEMES = [
"ambiance",
"chaos",
"chrome",
"cloud9_day",
"cloud9_night",
"cloud9_night_low_color",
"clouds",
"clouds_midnight",
"cloud_editor",
"cloud_editor_dark",
"cobalt",
"crimson_editor",
"dawn",
"dracula",
"dreamweaver",
"eclipse",
"github",
"github_dark",
"github_light_default",
"gob",
"gruvbox",
"gruvbox_dark_hard",
"gruvbox_light_hard",
"idle_fingers",
"iplastic",
"katzenmilch",
"kr_theme",
"kuroir",
"merbivore",
"merbivore_soft",
"monokai",
"mono_industrial",
"nord_dark",
"one_dark",
"pastel_on_dark",
"solarized_dark",
"solarized_light",
"sqlserver",
"terminal",
"textmate",
"tomorrow",
"tomorrow_night",
"tomorrow_night_blue",
"tomorrow_night_bright",
"tomorrow_night_eighties",
"twilight",
"vibrant_ink",
"xcode",
];
const DEFAULT_TEMPLATES = {
js: `// !!! Attention, do not insert unverified code !!!
// ---- Example code ----
// Globals inputs variables: var1, var2, var3, user variables ...
const runCode = () => {
const date = new Date().toJSON().replace("T"," ");
return date.slice(0,date.indexOf("."))
}
result = \`Hello ComfyUI with us today \${runCode()}!\``,
py: `# !!! Attention, do not insert unverified code !!!
# ---- Example code ----
# Globals inputs variables: var1, var2, var3, user variables ...
from time import strftime
def runCode():
nowDataTime = strftime("%Y-%m-%d %H:%M:%S")
return f"Hello ComfyUI with us today {nowDataTime}!"
result = runCode()
`,
};
function getPostition(node, ctx, w_width, y, n_height) {
const margin = 5;
const rect = ctx.canvas.getBoundingClientRect();
const transform = new DOMMatrix()
.scaleSelf(rect.width / ctx.canvas.width, rect.height / ctx.canvas.height)
.multiplySelf(ctx.getTransform())
.translateSelf(margin, margin + y);
const scale = new DOMMatrix().scaleSelf(transform.a, transform.d);
return {
transformOrigin: "0 0",
transform: scale,
left: `${transform.a + transform.e + rect.left}px`,
top: `${transform.d + transform.f + rect.top}px`,
maxWidth: `${w_width - margin * 2}px`,
maxHeight: `${n_height - margin * 2 - y - 15}px`,
width: `${w_width - margin * 2}px`,
height: "90%",
position: "absolute",
scrollbarColor: "var(--descrip-text) var(--bg-color)",
scrollbarWidth: "thin",
zIndex: app.graph._nodes.indexOf(node),
};
}
// Create editor code
function codeEditor(node, inputName, inputData) {
const widget = {
type: "pycode",
name: inputName,
options: { hideOnZoom: true },
value:
inputData[1]?.default ||
`def my(a, b=1):
return a * b
result = str(my(23, 9))`,
draw(ctx, node, widget_width, y, widget_height) {
const hidden =
node.flags?.collapsed ||
(!!widget.options.hideOnZoom && app.canvas.ds.scale < 0.5) ||
widget.type === "converted-widget" ||
widget.type === "hidden";
widget.codeElement.hidden = hidden;
if (hidden) {
widget.options.onHide?.(widget);
return;
}
Object.assign(
this.codeElement.style,
getPostition(node, ctx, widget_width, y, node.size[1])
);
},
computeSize(...args) {
return [500, 250];
},
};
widget.codeElement = makeElement("pre", {
innerHTML: widget.value,
});
widget.editor = ace.edit(widget.codeElement);
widget.editor.setTheme("ace/theme/monokai");
widget.editor.session.setMode("ace/mode/python");
widget.codeElement.hidden = true;
document.body.appendChild(widget.codeElement);
const collapse = node.collapse;
node.collapse = function () {
collapse.apply(this, arguments);
if (this.flags?.collapsed) {
widget.codeElement.hidden = true;
} else {
if (this.flags?.collapsed === false) {
widget.codeElement.hidden = false;
}
}
};
return widget;
}
// Save data to workflow forced!
function saveValue() {
app?.extensionManager?.workflow?.activeWorkflow?.changeTracker?.checkState();
}
// Register extensions
app.registerExtension({
name: "alekpet.IDENode",
getCustomWidgets(app) {
return {
PYCODE: (node, inputName, inputData, app) => {
const widget = codeEditor(node, inputName, inputData);
widget.editor.getSession().on("change", function (e) {
widget.value = widget.editor.getValue();
saveValue();
});
const themeList = node.addWidget(
"combo",
"theme_highlight",
"monokai",
(v) => {
widget.editor.setTheme(`ace/theme/${themeList.value}`);
},
{
values: LIST_THEMES,
serialize: false,
}
);
const widgetLang_id = findWidget(node, "language", "name", "findIndex");
if (widgetLang_id !== -1) {
node.widgets[widgetLang_id].callback = async (v) => {
widget.editor.setTheme(`ace/theme/${themeList.value}`);
if (await comfyuiDesktopConfirm("Clear code?"))
widget.editor.setValue("");
if (await comfyuiDesktopConfirm("Paste template?")) {
let defaultCode = null;
if (v === "javascript") {
defaultCode = DEFAULT_TEMPLATES.js;
} else if (v === "python") {
defaultCode = DEFAULT_TEMPLATES.py;
}
if (defaultCode) {
widget.editor.setValue(defaultCode);
widget.editor.clearSelection();
}
}
widget.editor.session.setMode(`ace/mode/${v}`);
};
}
function makeValidVariable(
varName,
textContent,
regex = /^[a-z_][a-z0-9_]*$/i
) {
if (
!varName ||
varName.trim() === "" ||
varName.length > MAX_CHAR_VARNAME ||
!regex.test(varName)
) {
createWindowModal({
textTitle: "WARNING",
textBody: [
makeElement("div", {
style: { fontSize: "0.7rem" },
innerHTML: textContent,
}),
],
...THEMES_MODAL_WINDOW.warning,
options: {
auto: {
autohide: true,
autoremove: true,
autoshow: true,
timewait: 1000,
},
close: { showClose: false },
overlay: { overlay_enabled: true },
parent: widget.codeElement,
},
});
return false;
}
return true;
}
node.addWidget(
"button",
"Add Input variable",
"add_input_variable",
async () => {
// Input name variable and check
const nameInput = node?.inputs?.length
? `var${node.inputs.length + 1}`
: "var1";
let varName = await comfyuiDesktopPrompt(
"Variable",
"Enter input variable name:",
nameInput
);
varName = varName.trim();
if (
!makeValidVariable(
varName,
`