+ PainterNode: fix piping image + PainterNode and PoseNode implemented past image from clipboard ctrl+v + Changed deprecated function checkState to capture CanvasState + update version
1137 lines
36 KiB
JavaScript
1137 lines
36 KiB
JavaScript
import { app } from "../../scripts/app.js";
|
|
import { api } from "../../scripts/api.js";
|
|
import "./lib/idenode/ace-builds/src-min-noconflict/ace.js";
|
|
import { addStylesheet } from "../../scripts/utils.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()
|
|
`,
|
|
};
|
|
|
|
// Default value select load code
|
|
const defaultComboValue = "-- Select code file --";
|
|
|
|
// RegExp
|
|
const symbolsIncorrectFileName = /[/\\?%*:|"<>]/g;
|
|
|
|
// Save data to workflow forced!
|
|
function saveValue() {
|
|
app?.extensionManager?.workflow?.activeWorkflow?.changeTracker?.captureCanvasState();
|
|
}
|
|
|
|
// Paint widget function
|
|
function paintWidget(paramsPaint) {
|
|
const oldDraw = this.drawWidget;
|
|
const that = this;
|
|
|
|
this.draw = function () {
|
|
// Save old params and set new
|
|
const old_params = {};
|
|
for (const param in paramsPaint) {
|
|
if (!LiteGraph.hasOwnProperty(param)) continue;
|
|
old_params[param] = LiteGraph[param];
|
|
LiteGraph[param] = paramsPaint[param];
|
|
}
|
|
|
|
oldDraw?.apply(that, arguments);
|
|
|
|
// Restore old params
|
|
for (const param in old_params) {
|
|
LiteGraph[param] = old_params[param];
|
|
}
|
|
};
|
|
}
|
|
|
|
// Load list codes
|
|
async function loadListCodes(language) {
|
|
try {
|
|
const response = await api.fetchApi(
|
|
`/alekpet/ide_node_load_codes/${language}`,
|
|
{
|
|
method: "GET",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
},
|
|
}
|
|
);
|
|
return await response.json();
|
|
} catch (e) {
|
|
console.error(e);
|
|
return { codes: [], language };
|
|
}
|
|
}
|
|
|
|
// Checks Vars
|
|
function makeValidVariable(
|
|
varName,
|
|
iotype,
|
|
textContent,
|
|
regex = /^[a-z_][a-z0-9_]*$/i
|
|
) {
|
|
if (
|
|
!varName ||
|
|
varName.trim() === "" ||
|
|
varName.length > MAX_CHAR_VARNAME ||
|
|
!regex.test(varName) ||
|
|
iotype.some((i) => i.name === 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;
|
|
}
|
|
|
|
// Register extensions
|
|
app.registerExtension({
|
|
name: "alekpet.IDENode",
|
|
init(app) {
|
|
addStylesheet("css/idenode/ide_node_styles.css", import.meta.url);
|
|
},
|
|
getCustomWidgets(app) {
|
|
return {
|
|
PYCODE: async (node, inputName, inputData, app) => {
|
|
// Wrapper and codeElement
|
|
const codeElementWrapper = makeElement("div", {
|
|
style: { height: "100%", margin: "-10px 0 10px 0" },
|
|
});
|
|
|
|
const codeElement = makeElement("pre", {
|
|
innerHTML:
|
|
inputData[1]?.default ||
|
|
`def my(a, b=1):
|
|
return a * b<br>
|
|
|
|
result = str(my(23, 9))`,
|
|
style: { height: "100%" },
|
|
});
|
|
|
|
codeElementWrapper.appendChild(codeElement);
|
|
|
|
// Editor
|
|
const editor = ace.edit(codeElement);
|
|
editor.setTheme("ace/theme/monokai");
|
|
editor.session.setMode("ace/mode/python");
|
|
|
|
// Theme list
|
|
const themeList = node.addWidget(
|
|
"combo",
|
|
"theme_highlight",
|
|
"monokai",
|
|
(v) => {
|
|
widget.editor.setTheme(`ace/theme/${themeList.value}`);
|
|
},
|
|
{
|
|
values: LIST_THEMES,
|
|
serialize: false,
|
|
}
|
|
);
|
|
|
|
// Languages list
|
|
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}`);
|
|
|
|
// Load list codes by language
|
|
const { codes } = await loadListCodes(v);
|
|
codeLoad.options.values = [defaultComboValue].concat(...codes);
|
|
codeLoad.value = defaultComboValue;
|
|
node.setDirtyCanvas(true, true);
|
|
};
|
|
}
|
|
|
|
// Load code
|
|
const codeLoad = node.addWidget(
|
|
"COMBO",
|
|
"Load code",
|
|
defaultComboValue,
|
|
async (codefile) => {
|
|
if (codefile === defaultComboValue) return;
|
|
|
|
// Confirm load code?
|
|
app.extensionManager.dialog
|
|
.confirm({
|
|
title: "Confirm load code",
|
|
message: "Are you sure you want load code?",
|
|
type: "default",
|
|
})
|
|
.then(async (result) => {
|
|
if (!result) {
|
|
// codeLoad.value = defaultComboValue;
|
|
return;
|
|
}
|
|
|
|
// Load code
|
|
fetch(
|
|
new URL(
|
|
`./lib/idenode/codes/${node.widgets[widgetLang_id].value}/${codefile}`,
|
|
import.meta.url
|
|
).toString()
|
|
)
|
|
.then((res) => res.json())
|
|
.then((res) => {
|
|
const { inputs, outputs, code, language } = res;
|
|
|
|
let lang = language ?? "python";
|
|
|
|
// Set code language
|
|
node.widgets[widgetLang_id].value = lang;
|
|
widget.editor.session.setMode(`ace/mode/${lang}`);
|
|
|
|
const currentWidth = node.size[0];
|
|
|
|
// Remove input
|
|
const inputsSet = new Set([
|
|
"language",
|
|
"theme_highlight",
|
|
"pycode",
|
|
...inputs.map((i) => i.name),
|
|
]);
|
|
|
|
for (let i = node.inputs.length - 1; i >= 0; i--) {
|
|
const input = node.inputs[i];
|
|
|
|
if (!inputsSet.has(input.name)) {
|
|
node.removeInput(i);
|
|
}
|
|
}
|
|
|
|
// Remove output
|
|
const outputSet = new Set([
|
|
"result",
|
|
...outputs.map((o) => o.name),
|
|
]);
|
|
|
|
for (let i = node.outputs.length - 1; i >= 0; i--) {
|
|
const output = node.outputs[i];
|
|
|
|
if (!outputSet.has(output.name)) {
|
|
node.removeOutput(i);
|
|
}
|
|
}
|
|
|
|
inputs.forEach((input) => {
|
|
const isNotInArray = !node.inputs.some(
|
|
(i) => i.name === input.name.trim()
|
|
);
|
|
if (isNotInArray)
|
|
node.addInput(input.name.trim(), input.type);
|
|
});
|
|
|
|
outputs.forEach((output) => {
|
|
const isNotInArray = !node.outputs.some(
|
|
(o) => o.name === output.name.trim()
|
|
);
|
|
if (isNotInArray)
|
|
node.addOutput(output.name.trim(), output.type);
|
|
});
|
|
|
|
// Set code
|
|
widget.editor.setValue(code);
|
|
widget.editor.clearSelection();
|
|
|
|
node.setSize([currentWidth, node.size[1]]);
|
|
node.setDirtyCanvas(true, true);
|
|
saveValue();
|
|
})
|
|
.catch((e) => {
|
|
createWindowModal({
|
|
textTitle: "ERROR",
|
|
textBody: [
|
|
makeElement("div", {
|
|
style: { fontSize: "0.7rem" },
|
|
innerHTML: e,
|
|
}),
|
|
],
|
|
...THEMES_MODAL_WINDOW.error,
|
|
options: {
|
|
auto: {
|
|
autohide: true,
|
|
autoremove: true,
|
|
autoshow: true,
|
|
timewait: 1500,
|
|
},
|
|
close: { showClose: false },
|
|
overlay: { overlay_enabled: true },
|
|
parent: widget.codeElement,
|
|
},
|
|
});
|
|
console.error(e);
|
|
});
|
|
});
|
|
},
|
|
{
|
|
values: [defaultComboValue],
|
|
default: defaultComboValue,
|
|
}
|
|
);
|
|
|
|
// Remove node
|
|
node.onRemoved = function () {
|
|
for (const w of node?.widgets) {
|
|
if (w?.codeElement) w.codeElement.remove();
|
|
}
|
|
};
|
|
|
|
// Add input vars
|
|
const buttonAddInput = makeElement("button", {
|
|
class: [
|
|
"ide_node_controls_buttons",
|
|
"button_ide_node_addinput",
|
|
"ide_node_controls_first_row",
|
|
],
|
|
textContent: "✚ INPUT VARIABLE",
|
|
title: "Add input variable",
|
|
onclick: async () => {
|
|
// Input name variable and check
|
|
const varsCount = node?.inputs.filter((i) =>
|
|
/^var[0-9]+$/.test(i?.name)
|
|
);
|
|
const nameInput = varsCount?.length
|
|
? `var${varsCount.length + 1}`
|
|
: "var1";
|
|
|
|
let varName = await comfyuiDesktopPrompt(
|
|
"Variable",
|
|
"Enter input variable name:",
|
|
nameInput
|
|
);
|
|
|
|
varName = varName.trim();
|
|
if (
|
|
!makeValidVariable(
|
|
varName,
|
|
node?.inputs,
|
|
`<h3 style="margin: 0;">Variable for <span style="color: limegreen">Input</span> name is incorrect!</h3><ul style="text-align:left;padding: 2px;margin-left: 5%;"><li>variable <span style="color:orange;font-weight: 600;">${varName}</span> already present in the inputs</li><li>starts with a number</li><li>has spaces or tabs</li><li>is empty</li><li>variable name is greater ${MAX_CHAR_VARNAME}</li></ul>`
|
|
)
|
|
)
|
|
return;
|
|
|
|
// Type variable and check
|
|
let type = await comfyuiDesktopPrompt(
|
|
"Type",
|
|
"Enter type data output (default: *):",
|
|
"*"
|
|
);
|
|
|
|
type = type.trim();
|
|
if (
|
|
!makeValidVariable(
|
|
type,
|
|
node?.inputs,
|
|
`<h3 style="margin: 0;">Type value is incorrect!</h3><ul style="text-align:left;padding: 2px;"><li>has spaces or tabs</li><li>is empty</li><li>type value length is greater ${MAX_CHAR_VARNAME}</li></ul>`,
|
|
/^[*a-z_][a-z0-9_]*$/i
|
|
)
|
|
)
|
|
return;
|
|
|
|
const currentWidth = node.size[0];
|
|
node.addInput(varName, type);
|
|
node.setSize([currentWidth, node.size[1]]);
|
|
saveValue();
|
|
},
|
|
});
|
|
|
|
// Add output vars
|
|
const buttonAddOutput = makeElement("button", {
|
|
class: [
|
|
"ide_node_controls_buttons",
|
|
"button_ide_node_addoutput",
|
|
"ide_node_controls_first_row",
|
|
],
|
|
innerHTML: "OUTPUT VARIABLE ✚",
|
|
title: "Add output variable",
|
|
onclick: async () => {
|
|
const currentWidth = node.size[0];
|
|
|
|
// Output name variable
|
|
const resultCount = node?.outputs.filter((i) =>
|
|
/^result[0-9]+$/.test(i?.name)
|
|
);
|
|
const nameOutput = resultCount?.length
|
|
? `result${resultCount.length + 1}`
|
|
: "result1";
|
|
|
|
let varName = await comfyuiDesktopPrompt(
|
|
"Variable",
|
|
"Enter output variable name:",
|
|
nameOutput
|
|
);
|
|
|
|
// Check output variable name
|
|
varName = varName.trim();
|
|
if (
|
|
!makeValidVariable(
|
|
varName,
|
|
node?.outputs,
|
|
`<h3 style="margin: 0;">Variable for <span style="color: pink">Output</span> name is incorrect!</h3><ul style="text-align:left;padding: 2px;margin-left: 5%;"><li>variable <span style="color:orange;font-weight: 600;">${varName}</span> already present in the outputs</li><li>starts with a number</li><li>has spaces or tabs</li><li>is empty</li><li>variable name is greater ${MAX_CHAR_VARNAME}</li></ul>`
|
|
)
|
|
)
|
|
return;
|
|
|
|
// Type variable and check
|
|
let type = await comfyuiDesktopPrompt(
|
|
"Type",
|
|
"Enter type data output (default: *):",
|
|
"*"
|
|
);
|
|
|
|
type = type.trim();
|
|
if (
|
|
!makeValidVariable(
|
|
type,
|
|
node?.outputs,
|
|
`<h3 style="margin: 0;">Type value is incorrect!</h3><ul style="text-align:left;padding: 2px;"><li>has spaces or tabs</li><li>is empty</li><li>type value length is greater ${MAX_CHAR_VARNAME}</li></ul>`,
|
|
/^[*a-z_][a-z0-9_]*$/i
|
|
)
|
|
)
|
|
return;
|
|
|
|
node.addOutput(varName, type);
|
|
node.setSize([currentWidth, node.size[1]]);
|
|
saveValue();
|
|
},
|
|
});
|
|
|
|
// Save file code
|
|
const buttonSave = makeElement("button", {
|
|
class: [
|
|
"ide_node_controls_buttons",
|
|
"button_ide_node_save",
|
|
"ide_node_controls_second_row",
|
|
],
|
|
textContent: "💾 save",
|
|
title: "Save code",
|
|
onclick: async () => {
|
|
comfyuiDesktopPrompt(
|
|
"Code filename",
|
|
"Enter filename for code (if exists will be rewrited)",
|
|
codeLoad.value === defaultComboValue ? "" : codeLoad.value
|
|
).then(async (save_filename) => {
|
|
if (
|
|
!save_filename ||
|
|
!save_filename.trim() ||
|
|
save_filename === defaultComboValue
|
|
) {
|
|
await app.extensionManager.toast.add({
|
|
severity: "warn",
|
|
summary: "Warning",
|
|
detail: `Filename is empty or equal '${defaultComboValue}'!`,
|
|
life: 3000,
|
|
});
|
|
return;
|
|
}
|
|
|
|
const filename = save_filename
|
|
.trim()
|
|
.replace(symbolsIncorrectFileName, "_");
|
|
|
|
api
|
|
.fetchApi("/alekpet/ide_node_save_code", {
|
|
method: "POST",
|
|
body: JSON.stringify({
|
|
filename,
|
|
inputs: node.inputs
|
|
.filter(
|
|
(i) =>
|
|
!["language", "pycode", "theme_highlight"].includes(
|
|
i.name
|
|
)
|
|
)
|
|
.map((i) => ({
|
|
name: i.name,
|
|
type: i.type,
|
|
})),
|
|
outputs: node.outputs.map((o) => ({
|
|
name: o.name,
|
|
type: o.type,
|
|
})),
|
|
language: node.widgets[widgetLang_id].value,
|
|
code: widget.editor.getValue(),
|
|
}),
|
|
})
|
|
.then((res) => res.json())
|
|
.then((data) => {
|
|
const { codes, message, status } = data;
|
|
|
|
if (status !== "Ok") {
|
|
throw new Error(message);
|
|
}
|
|
|
|
codeLoad.options.values = [defaultComboValue].concat(codes);
|
|
if (codeLoad.options.values.indexOf(filename) !== -1) {
|
|
codeLoad.value = filename;
|
|
node.setDirtyCanvas(true, true);
|
|
}
|
|
|
|
app.extensionManager.toast.add({
|
|
severity: "success",
|
|
summary: "Success",
|
|
detail: message,
|
|
life: 3000,
|
|
});
|
|
})
|
|
.catch((e) => {
|
|
app.extensionManager.toast.add({
|
|
severity: "error",
|
|
summary: "Error",
|
|
detail: e,
|
|
life: 3000,
|
|
});
|
|
console.error(e);
|
|
});
|
|
});
|
|
},
|
|
});
|
|
|
|
// Rename file code
|
|
const ButtonRename = makeElement("button", {
|
|
class: [
|
|
"ide_node_controls_buttons",
|
|
"button_ide_node_rename",
|
|
"ide_node_controls_second_row",
|
|
],
|
|
textContent: "✏️ rename",
|
|
title: "Rename file code",
|
|
onclick: (e) => {
|
|
const old_filename = codeLoad.value.trim();
|
|
|
|
if (!old_filename || old_filename === defaultComboValue) {
|
|
app.extensionManager.toast.add({
|
|
severity: "warn",
|
|
summary: "Warning",
|
|
detail: `Invalid element selected: '${old_filename}'!`,
|
|
life: 3000,
|
|
});
|
|
return;
|
|
}
|
|
|
|
comfyuiDesktopPrompt(
|
|
"Confirm file rename",
|
|
"Enter a filename to rename",
|
|
old_filename === defaultComboValue ? "" : old_filename
|
|
).then(async (new_filename) => {
|
|
if (
|
|
!new_filename ||
|
|
!new_filename.trim() ||
|
|
new_filename === old_filename ||
|
|
new_filename.trim() === defaultComboValue
|
|
) {
|
|
await app.extensionManager.toast.add({
|
|
severity: "warn",
|
|
summary: "Warning",
|
|
detail: `Filename is empty or equal '${defaultComboValue}'!`,
|
|
life: 3000,
|
|
});
|
|
return;
|
|
}
|
|
|
|
const filename = new_filename
|
|
.trim()
|
|
.replace(symbolsIncorrectFileName, "_");
|
|
const language = node.widgets[widgetLang_id].value;
|
|
|
|
api
|
|
.fetchApi("/alekpet/ide_node_rename_code", {
|
|
method: "POST",
|
|
body: JSON.stringify({
|
|
old_filename,
|
|
filename,
|
|
language,
|
|
}),
|
|
})
|
|
.then((res) => res.json())
|
|
.then((data) => {
|
|
const { status, message, codes } = data;
|
|
|
|
if (status !== "Ok") throw new Error(message);
|
|
|
|
codeLoad.options.values = [defaultComboValue].concat(codes);
|
|
if (codeLoad.options.values.indexOf(filename) !== -1) {
|
|
codeLoad.value = filename;
|
|
node.setDirtyCanvas(true, true);
|
|
}
|
|
|
|
app.extensionManager.toast.add({
|
|
severity: "success",
|
|
summary: "Success",
|
|
detail: message,
|
|
life: 3000,
|
|
});
|
|
})
|
|
.catch((e) => {
|
|
app.extensionManager.toast.add({
|
|
severity: "error",
|
|
summary: "Error",
|
|
detail: e,
|
|
life: 3000,
|
|
});
|
|
console.error(e);
|
|
});
|
|
});
|
|
},
|
|
});
|
|
|
|
// Remove file code
|
|
const ButtonRemove = makeElement("button", {
|
|
class: [
|
|
"ide_node_controls_buttons",
|
|
"button_ide_node_remove",
|
|
"ide_node_controls_second_row",
|
|
],
|
|
textContent: "🗑️ remove",
|
|
title: "Remove file code",
|
|
onclick: async (e) => {
|
|
const filename = codeLoad.value.trim();
|
|
if (!filename || filename === defaultComboValue) {
|
|
app.extensionManager.toast.add({
|
|
severity: "warn",
|
|
summary: "Warning",
|
|
detail: `Invalid element selected: '${filename}'!`,
|
|
life: 3000,
|
|
});
|
|
return;
|
|
}
|
|
|
|
const language = node.widgets[widgetLang_id].value;
|
|
const answerRemove = await app.extensionManager.dialog.confirm({
|
|
title: "Confirm remove",
|
|
message: `Are you sure you want remove filename ${filename} code?`,
|
|
type: "default",
|
|
});
|
|
|
|
if (!answerRemove) {
|
|
return;
|
|
}
|
|
|
|
api
|
|
.fetchApi("/alekpet/ide_node_remove_code", {
|
|
method: "POST",
|
|
body: JSON.stringify({
|
|
filename,
|
|
language,
|
|
}),
|
|
})
|
|
.then((res) => res.json())
|
|
.then((data) => {
|
|
const { status, message } = data;
|
|
|
|
if (status !== "Ok") throw new Error(message);
|
|
|
|
const itemSelected = codeLoad.options.values.indexOf(filename);
|
|
if (itemSelected !== -1) {
|
|
codeLoad.options.values.splice(itemSelected, 1);
|
|
codeLoad.value = defaultComboValue;
|
|
node.setDirtyCanvas(true, true);
|
|
}
|
|
|
|
app.extensionManager.toast.add({
|
|
severity: "success",
|
|
summary: "Success",
|
|
detail: message,
|
|
life: 3000,
|
|
});
|
|
})
|
|
.catch((e) => {
|
|
app.extensionManager.toast.add({
|
|
severity: "error",
|
|
summary: "Error",
|
|
detail: e,
|
|
life: 3000,
|
|
});
|
|
console.error(e);
|
|
});
|
|
},
|
|
});
|
|
|
|
// Clear code
|
|
const ButtonClear = makeElement("button", {
|
|
class: [
|
|
"ide_node_controls_buttons",
|
|
"button_ide_node_clear",
|
|
"ide_node_controls_second_row",
|
|
],
|
|
textContent: "🧹 clear",
|
|
title: "Clear code",
|
|
onclick: async () => {
|
|
// Confirm clear code?
|
|
await app.extensionManager.dialog
|
|
.confirm({
|
|
title: "Confirm clear code",
|
|
message: "Are you sure you want clear code?",
|
|
type: "default",
|
|
})
|
|
.then((result) => {
|
|
if (result) {
|
|
widget.editor.setValue("");
|
|
saveValue();
|
|
}
|
|
});
|
|
},
|
|
});
|
|
const IdeNodeControls = makeElement("div", {
|
|
class: ["ide_node_controls"],
|
|
children: [
|
|
buttonAddInput,
|
|
buttonAddOutput,
|
|
buttonSave,
|
|
ButtonRename,
|
|
ButtonRemove,
|
|
ButtonClear,
|
|
],
|
|
});
|
|
|
|
const widgetIdeNodeControls = node.addDOMWidget(
|
|
"widget_ide_node_controls",
|
|
"ide_node_controls",
|
|
IdeNodeControls,
|
|
{
|
|
serialize: false,
|
|
}
|
|
);
|
|
widgetIdeNodeControls.computeSize = () => [node.size[0], 60];
|
|
|
|
// Add DOMWidget
|
|
const widget = node.addDOMWidget(
|
|
"pycode",
|
|
"widget_pycode",
|
|
codeElementWrapper,
|
|
{
|
|
getValue() {
|
|
return widget.editor.getValue();
|
|
},
|
|
}
|
|
);
|
|
|
|
widget.editor = editor;
|
|
widget.codeElement = codeElement;
|
|
|
|
widget.editor.getSession().on("change", function (e) {
|
|
widget.value = widget.editor.getValue();
|
|
saveValue();
|
|
});
|
|
|
|
return widget;
|
|
},
|
|
};
|
|
},
|
|
|
|
async beforeRegisterNodeDef(nodeType, nodeData, app) {
|
|
// --- IDENode
|
|
if (nodeData.name === "IDENode") {
|
|
// Node Created
|
|
const onNodeCreated = nodeType.prototype.onNodeCreated;
|
|
nodeType.prototype.onNodeCreated = async function () {
|
|
const ret = onNodeCreated
|
|
? onNodeCreated.apply(this, arguments)
|
|
: undefined;
|
|
|
|
const node_title = await this.getTitle();
|
|
const nodeName = `${nodeData.name}_${this.id}`;
|
|
|
|
console.log(`Create ${nodeData.name}: ${nodeName}`);
|
|
this.name = nodeName;
|
|
|
|
// Create default inputs, when first create node
|
|
// if (this?.inputs?.length === 2) {
|
|
// ["var1", "var2", "var3"].forEach((inputName) => {
|
|
// const currentWidth = this.size[0];
|
|
// this.addInput(inputName, "*");
|
|
// this.setSize([currentWidth, this.size[1]]);
|
|
// });
|
|
// }
|
|
|
|
// Load list codes by language
|
|
const widgetLanguage = findWidget(this, "language", "name");
|
|
const widgetLoadCode = findWidget(this, "Load code", "name");
|
|
const { codes } = await loadListCodes(widgetLanguage.value);
|
|
widgetLoadCode.options.values = [defaultComboValue].concat(codes);
|
|
|
|
const widgetEditor = findWidget(this, "widget_pycode", "type");
|
|
// JS run
|
|
api.addEventListener("alekpet_js_result", async ({ detail }) => {
|
|
const { vars, unique_id } = detail;
|
|
|
|
if (
|
|
(vars && !Object.keys(vars).length) ||
|
|
+unique_id !== this.id ||
|
|
this._jsProcessing
|
|
) {
|
|
return;
|
|
}
|
|
|
|
this._jsProcessing = true;
|
|
|
|
await new Promise((res) => {
|
|
const edit_vars = JSON.parse(vars);
|
|
let code_run = "";
|
|
for (let [k, v] of Object.entries(edit_vars)) {
|
|
// Check type
|
|
if (v instanceof String || typeof v === "string") {
|
|
v = `"${v}"`;
|
|
} else if (v instanceof Object || typeof v === "object") {
|
|
v = JSON.stringify(v);
|
|
}
|
|
code_run += `let ${k} = ${v};\n`;
|
|
}
|
|
|
|
code_run += `${widgetEditor.editor.getValue()}\nreturn [${Object.keys(
|
|
edit_vars
|
|
)},]\n`;
|
|
let result_run_code = null;
|
|
try {
|
|
result_run_code = eval(`(function(){${code_run}}())`);
|
|
} catch (e) {
|
|
result_run_code = `Error in javascript code: ${e}`;
|
|
console.error(`<${this.name}> ${result_run_code}`);
|
|
createWindowModal({
|
|
textTitle: "Error in javascript code",
|
|
textBody: [
|
|
makeElement("div", {
|
|
style: { fontSize: "0.7rem" },
|
|
textContent: e,
|
|
}),
|
|
],
|
|
...THEMES_MODAL_WINDOW.error,
|
|
options: {
|
|
auto: {
|
|
autohide: true,
|
|
autoremove: true,
|
|
autoshow: true,
|
|
timewait: 1000,
|
|
},
|
|
close: { showClose: false },
|
|
overlay: { overlay_enabled: true },
|
|
parent: widgetEditor.codeElement,
|
|
},
|
|
});
|
|
res(Object.keys(edit_vars).map((v) => e.message));
|
|
}
|
|
res(result_run_code);
|
|
}).then((result_code) => {
|
|
api
|
|
.fetchApi("/alekpet/ide_node_check_js_complete", {
|
|
method: "POST",
|
|
body: JSON.stringify({
|
|
unique_id: this.id.toString(),
|
|
result_code,
|
|
}),
|
|
})
|
|
.then((res) => res.json())
|
|
.then((res) => {
|
|
res?.status === "Ok"
|
|
? console.log(
|
|
`%cJS complete ok: ${this.name}: ${res.status}`,
|
|
"color: green; font-weight: 600;"
|
|
)
|
|
: console.error(`Error JS complete: ${res.status}`);
|
|
this._jsProcessing = false;
|
|
})
|
|
.catch((err) => {
|
|
this._jsProcessing = false;
|
|
console.error(`Error JS complete: ${err}`);
|
|
});
|
|
});
|
|
});
|
|
|
|
this.setSize([500, 500]);
|
|
|
|
return ret;
|
|
};
|
|
|
|
const onDrawForeground = nodeType.prototype.onDrawForeground;
|
|
nodeType.prototype.onDrawForeground = function (ctx) {
|
|
const r = onDrawForeground?.apply?.(this, arguments);
|
|
|
|
if (this.flags?.collapsed) return r;
|
|
|
|
if (this?.outputs?.length) {
|
|
for (let o = 0; o < this.outputs.length; o++) {
|
|
const { name, type } = this.outputs[o];
|
|
const colorType = LGraphCanvas.link_type_colors[type.toUpperCase()];
|
|
const nameSize = ctx.measureText(name);
|
|
const typeSize = ctx.measureText(
|
|
`[${type === "*" ? "any" : type.toLowerCase()}]`
|
|
);
|
|
|
|
ctx.fillStyle = colorType === "" ? "#AAA" : colorType;
|
|
ctx.font = "12px Arial, sans-serif";
|
|
ctx.textAlign = "right";
|
|
ctx.fillText(
|
|
`[${type === "*" ? "any" : type.toLowerCase()}]`,
|
|
this.size[0] - nameSize.width - typeSize.width,
|
|
o * 20 + 19
|
|
);
|
|
}
|
|
}
|
|
|
|
if (this?.inputs?.length) {
|
|
const not_showing = ["language", "pycode", "theme_highlight"];
|
|
for (let i = 0; i < this.inputs.length; i++) {
|
|
const { name, type } = this.inputs[i];
|
|
if (not_showing.includes(name)) continue;
|
|
const colorType = LGraphCanvas.link_type_colors[type.toUpperCase()];
|
|
const nameSize = ctx.measureText(name);
|
|
ctx.fillStyle = !colorType || colorType === "" ? "#AAA" : colorType;
|
|
ctx.font = "12px Arial, sans-serif";
|
|
ctx.textAlign = "left";
|
|
ctx.fillText(
|
|
`[${type === "*" ? "any" : type.toLowerCase()}]`,
|
|
nameSize.width + 25,
|
|
i * 20 + 13 * not_showing.length - not_showing.length * 20
|
|
);
|
|
}
|
|
}
|
|
|
|
return r;
|
|
};
|
|
|
|
// Node Configure
|
|
const onConfigure = nodeType.prototype.onConfigure;
|
|
nodeType.prototype.onConfigure = function (node) {
|
|
onConfigure?.apply(this, arguments);
|
|
if (node?.widgets_values?.length) {
|
|
const widget_code_id = findWidget(
|
|
this,
|
|
"widget_pycode",
|
|
"type",
|
|
"findIndex"
|
|
);
|
|
const widget_theme_id = findWidget(
|
|
this,
|
|
"theme_highlight",
|
|
"name",
|
|
"findIndex"
|
|
);
|
|
const widget_language_id = findWidget(
|
|
this,
|
|
"language",
|
|
"name",
|
|
"findIndex"
|
|
);
|
|
|
|
const widget_load_code_id = findWidget(
|
|
this,
|
|
"Load code",
|
|
"name",
|
|
"findIndex"
|
|
);
|
|
|
|
if (node?.widgets_values?.length === 6) {
|
|
console.log(
|
|
"🔨 [IDENode] Found old version IDENode (6 saved widget values), convert saves value for new version (5)."
|
|
);
|
|
|
|
node?.widgets_values.splice(2, 3);
|
|
node?.widgets_values.splice(
|
|
2,
|
|
0,
|
|
...[this.widgets[widget_load_code_id].options?.default, ""]
|
|
);
|
|
}
|
|
|
|
this.widgets[widget_load_code_id].value =
|
|
this.widgets[widget_load_code_id].options?.default;
|
|
|
|
const editor = this.widgets[widget_code_id]?.editor;
|
|
|
|
if (editor) {
|
|
const lang = node?.widgets_values[widget_language_id].trim()
|
|
? node?.widgets_values[widget_language_id]
|
|
: "python";
|
|
const theme = node?.widgets_values[widget_theme_id].trim()
|
|
? node?.widgets_values[widget_theme_id]
|
|
: "monokai";
|
|
|
|
editor.setTheme(`ace/theme/${theme}`);
|
|
this.widgets[widget_theme_id].value = theme;
|
|
|
|
editor.session.setMode(`ace/mode/${lang}`);
|
|
this.widgets[widget_language_id].value = lang;
|
|
|
|
editor.setValue(node?.widgets_values[widget_code_id]);
|
|
editor.clearSelection();
|
|
}
|
|
}
|
|
};
|
|
|
|
// ExtraMenuOptions
|
|
const getExtraMenuOptions = nodeType.prototype.getExtraMenuOptions;
|
|
nodeType.prototype.getExtraMenuOptions = function (_, options) {
|
|
getExtraMenuOptions?.apply(this, arguments);
|
|
|
|
const past_index = options.length - 1;
|
|
const past = options[past_index];
|
|
|
|
if (!!past) {
|
|
// Inputs remove
|
|
for (const input_idx in this.inputs) {
|
|
const input = this.inputs[input_idx];
|
|
|
|
if (["language", "theme_highlight", "pycode"].includes(input.name))
|
|
continue;
|
|
|
|
options.splice(past_index + 1, 0, {
|
|
content: `Remove Input ${input.name}`,
|
|
callback: (e) => {
|
|
const currentWidth = this.size[0];
|
|
if (input.link) {
|
|
app.graph.removeLink(input.link);
|
|
}
|
|
this.removeInput(input_idx);
|
|
this.setSize([currentWidth, this.size[1]]);
|
|
saveValue();
|
|
},
|
|
});
|
|
}
|
|
|
|
// Output remove
|
|
for (const output_idx in this.outputs) {
|
|
const output = this.outputs[output_idx];
|
|
|
|
if (output.name === "result") continue;
|
|
|
|
options.splice(past_index + 1, 0, {
|
|
content: `Remove Output ${output.name}`,
|
|
callback: (e) => {
|
|
const currentWidth = this.size[0];
|
|
if (output.link) {
|
|
app.graph.removeLink(output.link);
|
|
}
|
|
this.removeOutput(output_idx);
|
|
this.setSize([currentWidth, this.size[1]]);
|
|
saveValue();
|
|
},
|
|
});
|
|
}
|
|
}
|
|
};
|
|
// end - ExtraMenuOptions
|
|
}
|
|
},
|
|
});
|