Files
AstroCorp-ComfyUI-AstroCorp…/web/LoadImageClipboardWidget.js

199 lines
5.5 KiB
JavaScript

import { app } from "../../scripts/app.js";
import { api } from "../../scripts/api.js";
const IMAGE_WIDGET_NAME = "image";
function getImageExtension(type) {
if (type === "image/jpeg") return "jpg";
if (type === "image/webp") return "webp";
if (type === "image/gif") return "gif";
if (type === "image/bmp") return "bmp";
return "png";
}
function getTimestamp() {
const now = new Date();
const pad = (value) => String(value).padStart(2, "0");
return [
now.getFullYear(),
pad(now.getMonth() + 1),
pad(now.getDate()),
"_",
pad(now.getHours()),
pad(now.getMinutes()),
pad(now.getSeconds()),
].join("");
}
async function readClipboardImage() {
if (!navigator.clipboard?.read) {
throw new Error("This browser does not allow reading images from the clipboard.");
}
const clipboardItems = await navigator.clipboard.read();
for (const item of clipboardItems) {
const imageType = item.types.find((type) => type.startsWith("image/"));
if (imageType) {
return {
blob: await item.getType(imageType),
type: imageType,
};
}
}
throw new Error("No image was found in the clipboard.");
}
async function readPastedImage(event) {
const data = event.clipboardData || window.clipboardData;
const items = Array.from(data?.items ?? []);
const imageItem = items.find((item) => item.type.startsWith("image/"));
if (!imageItem) {
throw new Error("No image was found in the clipboard.");
}
const blob = imageItem.getAsFile();
if (!blob) {
throw new Error("Could not read the pasted image.");
}
return {
blob,
type: blob.type || imageItem.type,
};
}
function waitForPastedImage() {
return new Promise((resolve, reject) => {
const timeout = window.setTimeout(() => {
document.removeEventListener("paste", onPaste, true);
reject(new Error("No pasted image was received."));
}, 30000);
const onPaste = async (event) => {
try {
const image = await readPastedImage(event);
event.preventDefault();
event.stopImmediatePropagation();
window.clearTimeout(timeout);
document.removeEventListener("paste", onPaste, true);
resolve(image);
} catch (error) {
window.clearTimeout(timeout);
document.removeEventListener("paste", onPaste, true);
reject(error);
}
};
document.addEventListener("paste", onPaste, true);
});
}
async function uploadImageBlob(blob, type) {
const extension = getImageExtension(type);
const filename = `clipboard_${getTimestamp()}.${extension}`;
const file = new File([blob], filename, { type });
const formData = new FormData();
formData.append("image", file);
formData.append("type", "input");
const response = await api.fetchApi("/upload/image", {
method: "POST",
body: formData,
});
if (!response.ok) {
throw new Error(`Could not upload the clipboard image (${response.status}).`);
}
return await response.json();
}
async function uploadClipboardImage() {
try {
const { blob, type } = await readClipboardImage();
return await uploadImageBlob(blob, type);
} catch (error) {
alert("The browser blocked direct clipboard access. Press Ctrl+V now to paste the image.");
const { blob, type } = await waitForPastedImage();
return await uploadImageBlob(blob, type);
}
}
function getUploadedImagePath(uploadResult) {
if (!uploadResult.subfolder) return uploadResult.name;
const subfolder = uploadResult.subfolder.replaceAll("\\", "/").replace(/\/?$/, "/");
return `${subfolder}${uploadResult.name}`;
}
function setImageWidgetValue(node, value) {
const imageWidget = node.widgets?.find((widget) => widget.name === IMAGE_WIDGET_NAME);
if (!imageWidget) return;
const values = imageWidget.options?.values;
if (Array.isArray(values) && !values.includes(value)) {
values.push(value);
}
imageWidget.value = value;
if (typeof imageWidget.callback === "function") {
imageWidget.callback(value);
}
}
app.registerExtension({
name: "AstroCorp.LoadImageClipboardWidget",
async beforeRegisterNodeDef(nodeType, nodeData, app) {
if (nodeData.name !== "LoadImageClipboardNode") return;
const onNodeCreated = nodeType.prototype.onNodeCreated;
nodeType.prototype.onNodeCreated = function () {
onNodeCreated?.apply(this, arguments);
const useClipboardImage = async () => {
try {
const uploadResult = await uploadClipboardImage();
setImageWidgetValue(this, getUploadedImagePath(uploadResult));
this.setSize(this.computeSize(this.size));
this.setDirtyCanvas(true, true);
} catch (error) {
alert(error.message ?? error);
}
};
if (typeof this.addWidget === "function") {
this.addWidget(
"button",
"Use clipboard image",
null,
useClipboardImage
);
}
this.setSize(this.computeSize(this.size));
this.setDirtyCanvas(true, false);
};
},
});