Merge branch 'dragdrop-image-upload' of https://github.com/pythongosssss/ComfyUI into m1

This commit is contained in:
comfyanonymous 2023-03-16 02:39:55 -04:00
commit ef71d2c281
2 changed files with 109 additions and 33 deletions

View File

@ -291,9 +291,47 @@ class ComfyApp {
document.addEventListener("drop", async (event) => { document.addEventListener("drop", async (event) => {
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
const file = event.dataTransfer.files[0];
await this.handleFile(file); const n = this.dragOverNode;
this.dragOverNode = null;
// Node handles file drop, we dont use the built in onDropFile handler as its buggy
// If you drag multiple files it will call it multiple times with the same file
if (n && n.onDragDrop && (await n.onDragDrop(event))) {
return;
}
await this.handleFile(event.dataTransfer.files[0]);
}); });
// Always clear over node on drag leave
this.canvasEl.addEventListener("dragleave", async () => {
if (this.dragOverNode) {
this.dragOverNode = null;
this.graph.setDirtyCanvas(false, true);
}
});
// Add handler for dropping onto a specific node
this.canvasEl.addEventListener(
"dragover",
(e) => {
this.canvas.adjustMouseEvent(e);
const node = this.graph.getNodeOnPos(e.canvasX, e.canvasY);
if (node) {
if (node.onDragOver && node.onDragOver(e)) {
this.dragOverNode = node;
// dragover event is fired very frequently, run this on an animation frame
requestAnimationFrame(() => {
this.graph.setDirtyCanvas(false, true);
});
return;
}
}
this.dragOverNode = null;
},
false
);
} }
/** /**
@ -321,15 +359,22 @@ class ComfyApp {
} }
/** /**
* Draws currently executing node highlight and progress bar * Draws node highlights (executing, drag drop) and progress bar
*/ */
#addDrawNodeProgressHandler() { #addDrawNodeHandler() {
const orig = LGraphCanvas.prototype.drawNodeShape; const orig = LGraphCanvas.prototype.drawNodeShape;
const self = this; const self = this;
LGraphCanvas.prototype.drawNodeShape = function (node, ctx, size, fgcolor, bgcolor, selected, mouse_over) { LGraphCanvas.prototype.drawNodeShape = function (node, ctx, size, fgcolor, bgcolor, selected, mouse_over) {
const res = orig.apply(this, arguments); const res = orig.apply(this, arguments);
if (node.id + "" === self.runningNodeId) { let color = null;
if (node.id === +self.runningNodeId) {
color = "#0f0";
} else if (self.dragOverNode && node.id === self.dragOverNode.id) {
color = "dodgerblue";
}
if (color) {
const shape = node._shape || node.constructor.shape || LiteGraph.ROUND_SHAPE; const shape = node._shape || node.constructor.shape || LiteGraph.ROUND_SHAPE;
ctx.lineWidth = 1; ctx.lineWidth = 1;
ctx.globalAlpha = 0.8; ctx.globalAlpha = 0.8;
@ -355,7 +400,7 @@ class ComfyApp {
); );
else if (shape == LiteGraph.CIRCLE_SHAPE) else if (shape == LiteGraph.CIRCLE_SHAPE)
ctx.arc(size[0] * 0.5, size[1] * 0.5, size[0] * 0.5 + 6, 0, Math.PI * 2); ctx.arc(size[0] * 0.5, size[1] * 0.5, size[0] * 0.5 + 6, 0, Math.PI * 2);
ctx.strokeStyle = "#0f0"; ctx.strokeStyle = color;
ctx.stroke(); ctx.stroke();
ctx.strokeStyle = fgcolor; ctx.strokeStyle = fgcolor;
ctx.globalAlpha = 1; ctx.globalAlpha = 1;
@ -435,7 +480,7 @@ class ComfyApp {
await this.#loadExtensions(); await this.#loadExtensions();
// Create and mount the LiteGraph in the DOM // Create and mount the LiteGraph in the DOM
const canvasEl = Object.assign(document.createElement("canvas"), { id: "graph-canvas" }); const canvasEl = (this.canvasEl = Object.assign(document.createElement("canvas"), { id: "graph-canvas" }));
document.body.prepend(canvasEl); document.body.prepend(canvasEl);
this.graph = new LGraph(); this.graph = new LGraph();
@ -476,7 +521,7 @@ class ComfyApp {
// Save current workflow automatically // Save current workflow automatically
setInterval(() => localStorage.setItem("workflow", JSON.stringify(this.graph.serialize())), 1000); setInterval(() => localStorage.setItem("workflow", JSON.stringify(this.graph.serialize())), 1000);
this.#addDrawNodeProgressHandler(); this.#addDrawNodeHandler();
this.#addApiUpdateHandlers(); this.#addApiUpdateHandlers();
this.#addDropHandler(); this.#addDropHandler();
this.#addPasteHandler(); this.#addPasteHandler();

View File

@ -132,7 +132,7 @@ export const ComfyWidgets = {
function showImage(name) { function showImage(name) {
// Position the image somewhere sensible // Position the image somewhere sensible
if(!node.imageOffset) { if (!node.imageOffset) {
node.imageOffset = uploadWidget.last_y ? uploadWidget.last_y + 25 : 75; node.imageOffset = uploadWidget.last_y ? uploadWidget.last_y + 25 : 75;
} }
@ -162,6 +162,36 @@ export const ComfyWidgets = {
} }
}); });
async function uploadFile(file, updateNode) {
try {
// Wrap file in formdata so it includes filename
const body = new FormData();
body.append("image", file);
const resp = await fetch("/upload/image", {
method: "POST",
body,
});
if (resp.status === 200) {
const data = await resp.json();
// Add the file as an option and update the widget value
if (!imageWidget.options.values.includes(data.name)) {
imageWidget.options.values.push(data.name);
}
if (updateNode) {
showImage(data.name);
imageWidget.value = data.name;
}
} else {
alert(resp.status + " - " + resp.statusText);
}
} catch (error) {
alert(error);
}
}
const fileInput = document.createElement("input"); const fileInput = document.createElement("input");
Object.assign(fileInput, { Object.assign(fileInput, {
type: "file", type: "file",
@ -169,30 +199,7 @@ export const ComfyWidgets = {
style: "display: none", style: "display: none",
onchange: async () => { onchange: async () => {
if (fileInput.files.length) { if (fileInput.files.length) {
try { await uploadFile(fileInput.files[0], true);
// Wrap file in formdata so it includes filename
const body = new FormData();
body.append("image", fileInput.files[0]);
const resp = await fetch("/upload/image", {
method: "POST",
body,
});
if (resp.status === 200) {
const data = await resp.json();
showImage(data.name);
// Add the file as an option and update the widget value
if (!imageWidget.options.values.includes(data.name)) {
imageWidget.options.values.push(data.name);
}
imageWidget.value = data.name;
} else {
alert(resp.status + " - " + resp.statusText);
}
} catch (error) {
alert(error);
}
} }
}, },
}); });
@ -204,6 +211,30 @@ export const ComfyWidgets = {
}); });
uploadWidget.serialize = false; uploadWidget.serialize = false;
// Add handler to check if an image is being dragged over our node
node.onDragOver = function (e) {
if (e.dataTransfer && e.dataTransfer.items) {
const image = [...e.dataTransfer.items].find((f) => f.kind === "file" && f.type.startsWith("image/"));
return !!image;
}
return false;
};
// On drop upload files
node.onDragDrop = function (e) {
console.log("onDragDrop called");
let handled = false;
for (const file of e.dataTransfer.files) {
if (file.type.startsWith("image/")) {
uploadFile(file, !handled); // Dont await these, any order is fine, only update on first one
handled = true;
}
}
return handled;
};
return { widget: uploadWidget }; return { widget: uploadWidget };
}, },
}; };