Files
DemonAlone 8859d89dc1 v2.3.0
minor fix
2026-09-07 09:55:11 +03:00

223 lines
8.0 KiB
JavaScript

import { app } from "../../scripts/app.js";
console.log("[miniPaint] Extension loaded");
let miniPaintWindow = null;
let modalContainer = null;
// === Loading original image from node ===
async function getImageFromNode(node) {
// 1. Try to get file name from 'image' widget
const imageWidget = node.widgets?.find(w => w.name === "image");
if (imageWidget && imageWidget.value) {
const filename = imageWidget.value;
// Form URL for fetching file (standard ComfyUI endpoint)
const url = `/view?filename=${encodeURIComponent(filename)}`;
try {
const response = await fetch(url);
if (response.ok) {
const blob = await response.blob();
const img = new Image();
await new Promise((resolve, reject) => {
img.onload = resolve;
img.onerror = reject;
img.src = URL.createObjectURL(blob);
});
return img;
} else {
console.warn("Failed to load image from /view, falling back to DOM element.");
}
} catch (e) {
console.warn("Fetch error, falling back to DOM element.", e);
}
}
// 2. Fallback: try to extract HTMLImageElement from node.imgs or node.image
let imgElement = null;
if (node.imgs) {
const idx = node.activated_index || 0;
const item = node.imgs[idx];
if (item && item.img) imgElement = item.img;
else if (item instanceof HTMLImageElement) imgElement = item;
else imgElement = item;
} else if (node.image) {
if (node.image.img) imgElement = node.image.img;
else if (node.image instanceof HTMLImageElement) imgElement = node.image;
else imgElement = node.image;
}
if (imgElement && imgElement instanceof HTMLImageElement) {
return imgElement;
}
throw new Error("Unable to retrieve original image from node.");
}
// === Sending image to server (temporary buffer) ===
async function uploadImageToBuffer(imageElement) {
// Use naturalWidth/Height if available, otherwise take standard dimensions
const width = imageElement.naturalWidth || imageElement.width;
const height = imageElement.naturalHeight || imageElement.height;
const tempCanvas = document.createElement("canvas");
tempCanvas.width = width;
tempCanvas.height = height;
const ctx = tempCanvas.getContext("2d");
ctx.drawImage(imageElement, 0, 0, width, height);
// Get Blob (PNG binary data)
const blob = await new Promise(resolve => tempCanvas.toBlob(resolve, "image/png"));
const formData = new FormData();
formData.append("image", blob, "mypaint_edit.png");
const response = await fetch("/mypaint/upload", {
method: "POST",
body: formData
});
if (!response.ok) throw new Error("Upload failed");
}
// === Save handler for specific node ===
function setupSaveHandler(node) {
// Remove previous handler for this node if exists
if (node._miniPaintHandler) {
window.removeEventListener("message", node._miniPaintHandler);
}
const handler = (e) => {
// Check that the message came from our own source
if (e.origin !== window.location.origin) {
console.warn("Ignored message from untrusted origin:", e.origin);
return;
}
if (e.data && e.data.type === "MINIPAINT_SAVED") {
const imageWidget = node.widgets?.find(w => w.name === "image");
if (imageWidget) {
imageWidget.value = e.data.filename;
if (imageWidget.callback) imageWidget.callback(e.data.filename);
node.setDirtyCanvas(true, true);
}
}
};
window.addEventListener("message", handler);
node._miniPaintHandler = handler;
}
// === Opening in separate window ===
async function openMiniPaintWindow(node) {
const img = await getImageFromNode(node);
await uploadImageToBuffer(img);
const editorUrl = "/mypaint/index.html";
if (miniPaintWindow && !miniPaintWindow.closed) {
miniPaintWindow.focus();
miniPaintWindow.postMessage({ type: "RELOAD_BUFFER" }, window.location.origin);
} else {
miniPaintWindow = window.open(editorUrl, "ComfyMiniPaintWindow");
}
setupSaveHandler(node);
}
// === Opening in a modal window (iframe) ===
async function openMiniPaintModal(node) {
const img = await getImageFromNode(node);
await uploadImageToBuffer(img);
// Close previous modal window if exists
if (modalContainer) {
document.body.removeChild(modalContainer);
modalContainer = null;
}
// Create container
modalContainer = document.createElement("div");
modalContainer.style.position = "fixed";
modalContainer.style.top = "0";
modalContainer.style.left = "0";
modalContainer.style.width = "100%";
modalContainer.style.height = "100%";
modalContainer.style.backgroundColor = "rgba(0,0,0,0.7)";
modalContainer.style.zIndex = "100000";
modalContainer.style.display = "flex";
modalContainer.style.alignItems = "center";
modalContainer.style.justifyContent = "center";
// Create iframe with editor
const iframe = document.createElement("iframe");
iframe.src = "/mypaint/index.html";
iframe.style.width = "90%";
iframe.style.height = "90%";
iframe.style.border = "none";
iframe.style.borderRadius = "8px";
iframe.style.boxShadow = "0 4px 20px rgba(0,0,0,0.3)";
// Close button
const closeBtn = document.createElement("button");
closeBtn.innerHTML = "❌";
closeBtn.style.position = "absolute";
closeBtn.style.top = "20px";
closeBtn.style.right = "30px";
closeBtn.style.backgroundColor = "#fff";
closeBtn.style.border = "none";
closeBtn.style.borderRadius = "20px";
closeBtn.style.width = "40px";
closeBtn.style.height = "40px";
closeBtn.style.fontSize = "24px";
closeBtn.style.cursor = "pointer";
closeBtn.style.zIndex = "100001";
closeBtn.style.boxShadow = "0 2px 8px rgba(0,0,0,0.2)";
closeBtn.onclick = () => {
if (modalContainer) {
document.body.removeChild(modalContainer);
modalContainer = null;
}
};
modalContainer.appendChild(iframe);
modalContainer.appendChild(closeBtn);
document.body.appendChild(modalContainer);
setupSaveHandler(node);
}
// === Extension registration ===
app.registerExtension({
name: "demonalone.MiniPaint",
async beforeRegisterNodeDef(nodeType, nodeData, app) {
const orig = nodeType.prototype.getExtraMenuOptions;
nodeType.prototype.getExtraMenuOptions = function(canvas, options) {
orig?.apply(this, arguments);
if (this.imgs || this.image) {
// Create submenu structure for MiniPaint
const miniPaintSubmenu = [
{
content: "Edit in miniPaint (New Window)",
callback: async () => {
try {
await openMiniPaintWindow(this);
} catch (err) {
console.error("MiniPaint (window) error:", err);
}
}
},
{
content: "Edit in miniPaint (Pop-up)",
callback: async () => {
try {
await openMiniPaintModal(this);
} catch (err) {
console.error("MiniPaint (Pop-up) error:", err);
}
}
}
];
options.push({
content: "🖼️miniPaint",
has_submenu: true,
submenu: {
options: miniPaintSubmenu
}
});
}
};
}
});