199 lines
5.5 KiB
JavaScript
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);
|
|
};
|
|
},
|
|
});
|