468 lines
14 KiB
JavaScript
468 lines
14 KiB
JavaScript
import { app } from "../../../scripts/app.js";
|
|
import { api } from "../../../scripts/api.js"
|
|
|
|
|
|
function showImageOnNode(node, name, imageType) {
|
|
const img = new Image();
|
|
img.onload = () => {
|
|
node.imgs = [img];
|
|
app.graph.setDirtyCanvas(true);
|
|
};
|
|
let folder_separator = name.lastIndexOf("/");
|
|
let subfolder = "";
|
|
if (imageType === "temp") {
|
|
subfolder = "";
|
|
} else if (folder_separator > -1) {
|
|
subfolder = name.substring(0, folder_separator);
|
|
name = name.substring(folder_separator + 1);
|
|
}
|
|
img.src = api.apiURL(`/view?filename=${encodeURIComponent(name)}&type=${imageType}&subfolder=${subfolder}${app.getPreviewFormatParam()}${app.getRandParam()}`);
|
|
node.setSizeForImage?.();
|
|
}
|
|
|
|
|
|
// Global variables
|
|
var uploadByPathNodes = []; // Used by widget: IMAGEUPLOAD_BYPATH
|
|
|
|
|
|
// Handle Python -> JS messages
|
|
|
|
function updatePreviewSignalHandle(evt) {
|
|
const name = evt.detail.preview_filename;
|
|
const imageType = evt.detail.preview_type;
|
|
const ogPath = evt.detail.path;
|
|
|
|
for (let i = uploadByPathNodes.length - 1; i >=0; i--) {
|
|
const node = uploadByPathNodes[i];
|
|
let stopTrackingNode = false;
|
|
if (!node || !(node.id) || !(app.graph._nodes_by_id[node.id])) {
|
|
stopTrackingNode = true;
|
|
} else {
|
|
try {
|
|
const doUpdate = node.isAutoPreviewUpdate() && !!(node.getPath()) && (ogPath === node.getPath());
|
|
if (doUpdate) {
|
|
console.log("Update preview for node " + node.id);
|
|
showImageOnNode(node, name, imageType);
|
|
}
|
|
} catch (error) {
|
|
console.log(error);
|
|
stopTrackingNode = true;
|
|
}
|
|
}
|
|
|
|
if (stopTrackingNode)
|
|
uploadByPathNodes.splice(i, 1);
|
|
}
|
|
}
|
|
|
|
api.addEventListener("kap-update-preview", updatePreviewSignalHandle);
|
|
|
|
// Load image by path
|
|
app.registerExtension({
|
|
name: "kap.loadimage.dedup",
|
|
|
|
async beforeRegisterNodeDef(nodeType, nodeData, app) {
|
|
if (nodeData?.input?.required?.image?.[1]?.kap_load_image_dedup === true) {
|
|
nodeData.input.required.upload = ["IMAGEUPLOAD_DEDUP"];
|
|
}
|
|
if (nodeType.comfyClass === "KLoadImageByPathAdvanced" || nodeType.comfyClass === "KLoadImageByPath") {
|
|
//if (nodeData?.input?.required?.image?.[1]?.kap_load_image_by_path === true) {
|
|
nodeData.input.required.upload = ["IMAGEUPLOAD_BYPATH"];
|
|
}
|
|
},
|
|
|
|
getCustomWidgets(app) {
|
|
return {
|
|
// Ref: ComfyWidgets.IMAGEUPLOAD (https://github.com/comfyanonymous/ComfyUI/blob/7f4725f6b3f72dd8bdb60dae5dd2c3e943263bcf/web/scripts/widgets.js#L359)
|
|
IMAGEUPLOAD_DEDUP(node, inputName, inputData, app) {
|
|
const imageWidget = node.widgets.find((w) => w.name === (inputData[1]?.widget ?? "image"));
|
|
|
|
let uploadWidget;
|
|
|
|
function showImage(name) {
|
|
showImageOnNode(node, name, "input");
|
|
}
|
|
|
|
var default_value = imageWidget.value;
|
|
Object.defineProperty(imageWidget, "value", {
|
|
set : function(value) {
|
|
this._real_value = value;
|
|
},
|
|
|
|
get : function() {
|
|
let value = "";
|
|
if (this._real_value) {
|
|
value = this._real_value;
|
|
} else {
|
|
return default_value;
|
|
}
|
|
|
|
if (value.filename) {
|
|
let real_value = value;
|
|
value = "";
|
|
if (real_value.subfolder) {
|
|
value = real_value.subfolder + "/";
|
|
}
|
|
|
|
value += real_value.filename;
|
|
|
|
if(real_value.type && real_value.type !== "input")
|
|
value += ` [${real_value.type}]`;
|
|
}
|
|
return value;
|
|
}
|
|
});
|
|
|
|
// Add our own callback to the combo widget to render an image when it changes
|
|
const cb = node.callback;
|
|
imageWidget.callback = function () {
|
|
showImage(imageWidget.value);
|
|
if (cb) {
|
|
return cb.apply(this, arguments);
|
|
}
|
|
};
|
|
|
|
// On load if we have a value then render the image
|
|
// The value isnt set immediately so we need to wait a moment
|
|
// No change callbacks seem to be fired on initial setting of the value
|
|
requestAnimationFrame(() => {
|
|
if (imageWidget.value) {
|
|
showImage(imageWidget.value);
|
|
}
|
|
});
|
|
|
|
async function uploadFile(file, updateNode, pasted = false) {
|
|
try {
|
|
// Wrap file in formdata so it includes filename
|
|
const body = new FormData();
|
|
body.append("image", file);
|
|
const overwriteWidget = node.widgets.find(w => w.name === "overwrite_option");
|
|
body.append("overwrite", overwriteWidget.value);
|
|
if (pasted) body.append("subfolder", "pasted");
|
|
|
|
const resp = await api.fetchApi("/kap/upload/image-dedup", {
|
|
method: "POST",
|
|
body,
|
|
});
|
|
|
|
if (resp.status === 200) {
|
|
const data = await resp.json();
|
|
// Add the file to the dropdown list and update the widget value
|
|
let path = data.name;
|
|
if (data.subfolder) path = data.subfolder + "/" + path;
|
|
|
|
if (!imageWidget.options.values.includes(path)) {
|
|
imageWidget.options.values.push(path);
|
|
}
|
|
|
|
if (updateNode) {
|
|
showImage(path);
|
|
imageWidget.value = path;
|
|
}
|
|
} else {
|
|
alert(resp.status + " - " + resp.statusText);
|
|
}
|
|
} catch (error) {
|
|
alert(error);
|
|
}
|
|
}
|
|
|
|
const fileInput = document.createElement("input");
|
|
Object.assign(fileInput, {
|
|
type: "file",
|
|
accept: "image/jpeg,image/png,image/webp",
|
|
style: "display: none",
|
|
onchange: async () => {
|
|
if (fileInput.files.length) {
|
|
await uploadFile(fileInput.files[0], true);
|
|
}
|
|
},
|
|
});
|
|
document.body.append(fileInput);
|
|
|
|
// Create the button widget for selecting the files
|
|
uploadWidget = node.addWidget("button", inputName, "image", () => {
|
|
fileInput.click();
|
|
});
|
|
uploadWidget.label = "choose file to upload";
|
|
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");
|
|
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;
|
|
};
|
|
|
|
node.pasteFile = function(file) {
|
|
if (file.type.startsWith("image/")) {
|
|
const is_pasted = (file.name === "image.png") &&
|
|
(file.lastModified - Date.now() < 2000);
|
|
uploadFile(file, true, is_pasted);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
return { widget: uploadWidget };
|
|
},
|
|
|
|
IMAGEUPLOAD_BYPATH(node, inputName, inputData, app) {
|
|
// Widget returned
|
|
//let refreshPreviewWidget;
|
|
|
|
const imageWidget = node.widgets.find((w) => w.name === (inputData[1]?.widget ?? "image"));
|
|
const isAdvanced = node.comfyClass === "KLoadImageByPathAdvanced";
|
|
|
|
Object.assign(node, {
|
|
getPath: function() {
|
|
updateImageWidgetValue();
|
|
let path = imageWidget.value;
|
|
return path;
|
|
},
|
|
isAutoPreviewUpdate: function() {
|
|
const w = node.widgets.find(w => w.name === "auto_preview_update");
|
|
return w?.value ?? true;
|
|
},
|
|
});
|
|
|
|
//var default_value = imageWidget.value;
|
|
//Object.defineProperty(imageWidget, "value", {
|
|
// set : function(value) {
|
|
// this._real_value = value;
|
|
// },
|
|
|
|
// get : function() {
|
|
// let value = "";
|
|
// if (this._real_value) {
|
|
// value = this._real_value;
|
|
// } else {
|
|
// return default_value;
|
|
// }
|
|
|
|
// if (value.filename) {
|
|
// let real_value = value;
|
|
// value = "";
|
|
// if (real_value.subfolder) {
|
|
// value = real_value.subfolder + "/";
|
|
// }
|
|
|
|
// value += real_value.filename;
|
|
|
|
// if(real_value.type && real_value.type !== "input")
|
|
// value += ` [${real_value.type}]`;
|
|
// }
|
|
// return value;
|
|
// }
|
|
//});
|
|
|
|
// Add our own callback to the string widget to render an image when it changes
|
|
const cb = node.callback;
|
|
imageWidget.callback = function () {
|
|
updateWatchlist({ updateNode: true });
|
|
if (cb) {
|
|
return cb.apply(this, arguments);
|
|
}
|
|
};
|
|
|
|
// On load if we have a value then render the image
|
|
// The value isnt set immediately so we need to wait a moment
|
|
// No change callbacks seem to be fired on initial setting of the value
|
|
requestAnimationFrame(() => {
|
|
if (imageWidget.value) {
|
|
//showImageOnNode(node, imageWidget.value);
|
|
updateWatchlist({ updateNode: true });
|
|
}
|
|
});
|
|
|
|
async function updatePreview() {
|
|
const body = new FormData();
|
|
body.append("image_path", node.getPath());
|
|
const resp = await api.fetchApi("/kap/upload/update-preview", {
|
|
method: "POST",
|
|
body,
|
|
});
|
|
|
|
if (resp.status === 200) {
|
|
const data = await resp.json();
|
|
const previewName = data.preview_filename;
|
|
const previewImageType = data.preview_image_type;
|
|
showImageOnNode(node, previewName, previewImageType);
|
|
} else {
|
|
alert(`Generating preview for '${node.getPath()}' was not successful (node id: ${node.id})`);
|
|
}
|
|
}
|
|
|
|
async function updateWatchlist({ updateNode = true }) {
|
|
try {
|
|
// Watchlist consists of images from all loadbypath widgets, and the first item is the path of the image in the current node
|
|
const allImagePaths = [];
|
|
const selfInWatchlist = node.isAutoPreviewUpdate() && !!(node.getPath());
|
|
if (selfInWatchlist)
|
|
allImagePaths.push(node.getPath());
|
|
for (let i = uploadByPathNodes.length - 1; i >= 0; i--) {
|
|
const nd = uploadByPathNodes[i];
|
|
if (!nd || !(nd.id) || !(app.graph._nodes_by_id[nd.id])) {
|
|
uploadByPathNodes.splice(i, 1);
|
|
} else {
|
|
if (nd.id === node.id)
|
|
continue;
|
|
const path = nd.getPath?.();
|
|
if (path && nd.isAutoPreviewUpdate())
|
|
allImagePaths.push(path);
|
|
}
|
|
}
|
|
|
|
const body = new FormData();
|
|
body.append("all_image_paths", JSON.stringify(allImagePaths));
|
|
|
|
const resp = await api.fetchApi("/kap/upload/update-watchlist", {
|
|
method: "POST",
|
|
body,
|
|
});
|
|
|
|
if (resp.status === 200) {
|
|
const data = await resp.json();
|
|
// We are only updating for the image in the current node, which is the first item
|
|
if (selfInWatchlist && data.success[0]) {
|
|
const previewName = data.preview_names[0];
|
|
|
|
if (updateNode) {
|
|
showImageOnNode(node, previewName, data.preview_images_type);
|
|
}
|
|
} else if (!selfInWatchlist) {
|
|
// No preview update
|
|
} else {
|
|
// TODO Display error more elegantly
|
|
alert(`Generating preview for '${node.getPath()}' was not successful (node id: ${node.id}).` +
|
|
`\n Watchlist for files for auto preview update refreshed (#=${data.watchlist_size}).`);
|
|
}
|
|
|
|
} else {
|
|
alert(resp.status + " - " + resp.statusText);
|
|
}
|
|
} catch (error) {
|
|
alert(error);
|
|
}
|
|
}
|
|
|
|
// Refresh preview button widget
|
|
const updatePreviewWidget = node.addWidget("button", null, null, () => {
|
|
updatePreview();
|
|
});
|
|
updatePreviewWidget.label = "update preview";
|
|
updatePreviewWidget.serialize = false;
|
|
|
|
// Refresh watchers button widget
|
|
const refreshWatchWidget = node.addWidget("button", null, null, () => {
|
|
updateWatchlist({ updateNode: true });
|
|
});
|
|
refreshWatchWidget.label = "refresh auto preview";
|
|
refreshWatchWidget.serialize = false;
|
|
|
|
// Auto preview update
|
|
let autoPreviewUpdateWidget = null;
|
|
if (isAdvanced) {
|
|
autoPreviewUpdateWidget = node.addWidget("toggle",
|
|
"auto_preview_update", true,
|
|
() => {
|
|
updateWatchlist({ updateNode: true });
|
|
}
|
|
);
|
|
autoPreviewUpdateWidget.label = "update preview automatically";
|
|
autoPreviewUpdateWidget.serialize = true;
|
|
}
|
|
|
|
// Prefix substitution
|
|
function updateImageWidgetValue() {
|
|
let path = imageWidget.value ?? "";
|
|
|
|
if ((path.startsWith('"') && path.endsWith('"')) || (path.startsWith("'") && path.endsWith("'"))) {
|
|
path = path.substring(1, path.length - 1);
|
|
}
|
|
|
|
if (path && isPrefixSubEnabled()) {
|
|
const oldPrefix = prefixSubSrcWidget.value;
|
|
const newPrefix = prefixSubDestWidget.value;
|
|
if (path.startsWith(oldPrefix)) {
|
|
path = newPrefix + path.substring(oldPrefix.length);
|
|
} else {
|
|
console.log("The path '" + path + "' does not begin with prefix: '" +
|
|
oldPrefix + "'; nothing to replace.");
|
|
}
|
|
console.log(`Path (node id: ${node.id}): '${path}'`);
|
|
}
|
|
|
|
imageWidget.value = path;
|
|
}
|
|
|
|
function isPrefixSubEnabled() {
|
|
return enablePrefixSubWidget?.value ?? false;
|
|
}
|
|
|
|
function onPrefixChange() {
|
|
updateImageWidgetValue();
|
|
updateWatchlist({ updateNode: true });
|
|
}
|
|
|
|
let enablePrefixSubWidget = null;
|
|
let prefixSubSrcWidget = null;
|
|
let prefixSubDestWidget = null;
|
|
if (isAdvanced) {
|
|
enablePrefixSubWidget = node.addWidget("toggle",
|
|
"enable_prefix_substitution", false,
|
|
() => { onPrefixChange(); }
|
|
);
|
|
enablePrefixSubWidget.label = "enable prefix substitution";
|
|
enablePrefixSubWidget.serialize = true;
|
|
|
|
prefixSubSrcWidget = node.addWidget("text",
|
|
"prefix_substitution_source", "/mnt/point/",
|
|
() => { onPrefixChange(); }
|
|
);
|
|
prefixSubSrcWidget.label = "prefix to substitute";
|
|
prefixSubSrcWidget.serialize = true;
|
|
|
|
prefixSubDestWidget = node.addWidget("text",
|
|
"prefix_substitution_destination", "~/",
|
|
() => { onPrefixChange(); }
|
|
);
|
|
prefixSubDestWidget.label = "new prefix";
|
|
prefixSubDestWidget.serialize = true;
|
|
}
|
|
|
|
//
|
|
uploadByPathNodes.push(node);
|
|
|
|
node.onRemoved = function() {
|
|
for (let i = uploadByPathNodes.length - 1; i >= 0; i--) {
|
|
if (uploadByPathNodes[i].id === node.id)
|
|
uploadByPathNodes.splice(i, 1);
|
|
}
|
|
}
|
|
|
|
//return { widget: updatePreviewWidget };
|
|
return {};
|
|
},
|
|
};
|
|
},
|
|
});
|
|
|