Files
Lerc-canvas_tab/web/main.js
T

324 lines
8.4 KiB
JavaScript

import { app } from "/scripts/app.js";
import { api } from "/scripts/api.js"
import { ComfyWidgets } from "/scripts/widgets.js";
let myticker =0;
let dummyBlob;
let editor= {}; // one image editor for all nodes, otherwise communication is messy when reload happens
// for now that means only one editor node is practical.
// adding a protocol that identifies multiple nodes would allow the one editor
// to serve multiple nodes.
const plugin_name = "canvas_link";
const editor_path = "/extensions/canvas_tab/page/index.html"
function setSignal(key) {
const keyName = plugin_name+":"+key;
localStorage.setItem(keyName, 'true');
}
function clearSignal(key) {
const keyName = plugin_name+":"+key;
localStorage.removeItem(keyName);
}
function getSignal(key) {
const keyName = plugin_name+":"+key;
return localStorage.getItem(keyName) === 'true';
}
function checkAndClear(key) {
const keyName = plugin_name+":"+key;
let result=localStorage.getItem(keyName) === 'true';
if (result) localStorage.removeItem(keyName);
return result;
}
app.registerExtension({
name: "canvas_tab",
async init() {
console.log("init:"+this.name)
checkForExistingEditor();
const blankImage=document.createElement("canvas");
blankImage.width=64;
blankImage.height=64;
blankImage.toBlob(a=>dummyBlob=a)
addEventListener("message",handleWindowMessage)
},
async beforeRegisterNodeDef(nodeType, nodeData, app) {
},
async getCustomWidgets(app) {
return {
CANVAS(node,inputName,inputData,app) {
return addCanvasWidget(node,inputName,inputData, app)
}
}
},
nodeCreated(node) {
const title = node.getTitle();
// node.type not set at this point?
if (title==="Edit In Another Tab") {
initEditorNode(node);
addDragDropSupport(node);
}
else if (title==="Send to Editor Tab") {
initTransmitNode(node);
addDragDropSupport(node);
}
}
});
function initEditorNode(node)
{
node.collected_images = [];
node.addWidget("button","Edit","bingo", (widget,graphCanvas, node, {x,y}, event) => focusEditor(node));
node.widgets.reverse();// because auto created widget get put in first
node.canvasWidget=node.widgets[1];
node.maskWidget=node.widgets[2];
console.log(node);
node.maskWidget.onTopOf = node.canvasWidget;
editor.channel.port1.postMessage({retransmit:true})
return;
}
function addDragDropSupport(node) {
// 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");
return !!image;
}
return false;
};
// On drop upload files
node.onDragDrop = function (e) {
let handled = false;
for (const file of e.dataTransfer.files) {
if (file.type.startsWith("image/")) {
const url = URL.createObjectURL(file)
transmitImages([url])
setTimeout(_=>URL.revokeObjectURL(url),1000);
handled = true;
}
}
return handled;
};
}
function transmitImages(images) {
console.log("transmit",editor)
if (!editor.window || editor.window.closed) openEditor();
if(editor.channel) {
editor.channel.port1.postMessage({images})
} else {
//try again after half a second just in case we caught it setting up.
setTimeout(_=>{editor?.channel?.port1.postMessage({images})}, 500);
}
}
function initTransmitNode(node)
{
node.collected_images = [];
node.onExecuted = (output)=> {
if (output?.collected_images) {
transmitImages(output.collected_images);
}
}
return;
}
function openEditor() {
editor = {};// start over with new editor;
if (getSignal('clientPage')) {
//if clientPage is set, there might be a new page to replace the lost one
setSignal('findImageEditor')
setTimeout(_=>{
if (checkAndClear("findImageEditor")) {
//if the flag is still set by here, assume there's no-one out there
console.log("open window a")
editor.window = window.open(editor_path, plugin_name);
}
} ,1000)
} else {
console.log("open window b")
editor.window = window.open(editor_path, plugin_name);
}
}
function focusEditor() {
if (!editor.window || editor.window.closed) {
openEditor();
} else {
editor.window.focus();
}
}
async function uploadBlob(blob,filename="blob") {
try {
// Wrap file in formdata so it includes filename
const body = new FormData();
body.append("image", blob,filename);
const resp = await api.fetchApi("/upload/image", {
method: "POST",
body,
});
if (resp.status === 200) {
return await resp.json();;
} else {
alert(resp.status + " - " + resp.statusText);
}
} catch (error) {
alert(error);
}
}
function addCanvasWidget(node,name,inputData,app) {
const widget = {
type: inputData[0],
name,
size: [128,128],
image: null,
sendBlobRequired : true,
uploadedBlobName : "",
_blob: null,
background :"#8888",
_value : "",
get value() {
return this._value
},
set value(newValue) {
this._value=newValue
},
get blob() {
return this._blob;
},
set blob(newValue) {
this._blob=newValue;
this.sendBlobRequired = true;
},
draw(ctx, node, width, y) {
let [nodeWidth,nodeHeight] = node.size;
if (this.onTopOf) {
ctx.globalAlpha=0.5;
y= this.onTopOf.last_y;
} else {
ctx.globalAlpha=1;
ctx.fillStyle = this.background;
ctx.fillRect(0,y,width,nodeHeight-y);
}
if (this.image) {
const imageAspect = this.image.width/this.image.height;
let height = nodeHeight-y;
const widgetAspect = width / height;
let targetWidth,targetHeight;
if (imageAspect>widgetAspect) {
targetWidth=width;
targetHeight=width/imageAspect;
} else {
targetHeight=height;
targetWidth=height*imageAspect;
}
ctx.drawImage(this.image, (width-targetWidth)/2,y+(height-targetHeight)/2,targetWidth,targetHeight);
}
},
computeSize(...args) {
return [128,128];
},
async serializeValue(nodeId,widgetIndex) {
let widget = node.widgets[widgetIndex];
let blob = widget.blob;
if (!(blob instanceof Blob)) blob = dummyBlob;
if (widget.sendBlobRequired) {
let result = await uploadBlob(blob,widget.name+"_Image.png")
if (result) {
widget.uploadedBlobName = result.name;
widget.sendBlobRequired=false;
}
}
console.log(widget)
return widget.uploadedBlobName;
}
}
node.addCustomWidget(widget);
return widget;
}
function initiateCommunication() {
if (editor.window && !editor.window.closed) {
editor.channel= new MessageChannel();
editor.window.postMessage('Initiate communication', '*', [editor.channel.port2]);
editor.channel.port1.onmessage = messageFromEditor;
}
}
function loadBlobIntoWidget(widget, blob) {
const objectURL = URL.createObjectURL(blob);
const img = new Image();
img.onload = () => {
widget.blob = blob;
widget.image = img;
app.graph.setDirtyCanvas(true);
URL.revokeObjectURL(objectURL);
};
img.src = objectURL;
}
function handleWindowMessage(e) {
if (typeof e.data === "object" && e.data?.category === plugin_name) {
let data = e.data.data;
if (data == "Editor Here") {
editor.window = e.source;
initiateCommunication();
} else console.log("window message received",e)
}
}
function messageFromEditor(event) {
const nodes = app.graph.findNodesByType("Canvas_Tab");
//send same thing to all of the Canvas_Tab nodes
if (event.data.image instanceof Blob) {
for (const node of nodes) {
loadBlobIntoWidget(node.canvasWidget,event.data.image);
}
}
if (event.data.mask instanceof Blob) {
for (const node of nodes) {
loadBlobIntoWidget(node.maskWidget,event.data.mask);
}
} else {
console.log('Message received from Image Editor:', event.data);
}
}
function checkForExistingEditor() {
if (getSignal('clientPage')) {
setSignal("findImageEditor")
// Signal the Image Editor page to identify itself for reattachment
}
}