124 lines
5.3 KiB
JavaScript
124 lines
5.3 KiB
JavaScript
import { app } from "../../scripts/app.js";
|
|
import { api } from "../../scripts/api.js";
|
|
import { PreviewLoader } from "./loader.mjs";
|
|
import { NativePreview } from "./native_preview.mjs";
|
|
|
|
const loader = new PreviewLoader((route, options) => api.fetchApi(route, options));
|
|
const views = new Map();
|
|
let activePromptId = null;
|
|
let visibilityTimer;
|
|
|
|
const nativeCanvas = () => app.extensionManager.setting.get("Comfy.VueNodes.Enabled") !== true;
|
|
const cachedResults = node => node.graph === app.rootGraph ? app.nodeOutputs[String(node.id)]?.encrypt_preview : undefined;
|
|
|
|
function checkVisibility() {
|
|
const enabled = nativeCanvas();
|
|
const visibleNodes = new Set(app.canvas?.visible_nodes || []);
|
|
for (const [node, view] of views) {
|
|
view.setVisible(enabled && !node.flags.collapsed && visibleNodes.has(node));
|
|
view.status();
|
|
}
|
|
}
|
|
|
|
function removeView(node) {
|
|
views.get(node)?.dispose();
|
|
views.delete(node);
|
|
if (!views.size) { clearInterval(visibilityTimer); visibilityTimer = null; }
|
|
}
|
|
|
|
api.addEventListener("execution_start", ({ detail }) => { activePromptId = detail.prompt_id; });
|
|
api.addEventListener("encrypt-preview", ({ detail }) => {
|
|
if (!activePromptId || detail.prompt_id !== activePromptId) return;
|
|
for (const [node, view] of views) {
|
|
if (String(node.id) === detail.node_id && node.graph === app.rootGraph) {
|
|
if (detail.event === "start") view.lastResults = cachedResults(node);
|
|
view.receive(detail);
|
|
}
|
|
}
|
|
});
|
|
|
|
app.registerExtension({
|
|
name: "Comfy.EncryptPreview",
|
|
beforeRegisterNodeDef(nodeType, nodeData) {
|
|
if (nodeData.name !== "EncryptPreviewImage") return;
|
|
const created = nodeType.prototype.onNodeCreated;
|
|
nodeType.prototype.onNodeCreated = function () {
|
|
const result = created?.apply(this, arguments);
|
|
const status = document.createElement("div");
|
|
status.className = "encrypt-preview-status";
|
|
Object.assign(status.style, { fontSize: "12px", lineHeight: "24px", padding: "0 4px", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" });
|
|
const widget = this.addDOMWidget("encrypt_preview_status", "div", status, {
|
|
serialize: false, hideOnZoom: false, margin: 0, getMinHeight: () => 24, getMaxHeight: () => 24,
|
|
getValue: () => "", setValue: () => {},
|
|
});
|
|
widget.serialize = false;
|
|
widget.computeLayoutSize = () => ({ minHeight: 24, maxHeight: 24, minWidth: 100 });
|
|
const view = new NativePreview(this, loader, (text, error) => {
|
|
const supported = nativeCanvas();
|
|
const message = supported ? text : "请关闭 Nodes 2.0 以显示预览";
|
|
const state = supported && error ? "error" : "info";
|
|
if (status.textContent === message && status.dataset.state === state) return;
|
|
status.textContent = message;
|
|
status.dataset.state = state;
|
|
status.title = status.textContent;
|
|
status.style.color = state === "error" ? "#ef7070" : "var(--input-text, #ddd)";
|
|
this.graph?.setDirtyCanvas(true);
|
|
});
|
|
view.status();
|
|
views.set(this, view);
|
|
this.setSize([Math.max(this.size[0], 320), Math.max(this.size[1], 320)]);
|
|
visibilityTimer ??= setInterval(checkVisibility, 500);
|
|
return result;
|
|
};
|
|
|
|
const draw = nodeType.prototype.onDrawBackground;
|
|
nodeType.prototype.onDrawBackground = function () {
|
|
const view = views.get(this);
|
|
const results = cachedResults(this);
|
|
// ComfyUI can restore history/workflow outputs without an executed event.
|
|
if (view && results && results !== view.lastResults) {
|
|
view.lastResults = results;
|
|
view.showResults(results);
|
|
}
|
|
if (!this.flags.collapsed && nativeCanvas()) view?.setVisible(true);
|
|
return draw?.apply(this, arguments);
|
|
};
|
|
|
|
const executed = nodeType.prototype.onExecuted;
|
|
nodeType.prototype.onExecuted = function (message) {
|
|
const result = executed?.apply(this, arguments);
|
|
const view = views.get(this);
|
|
if (view && message?.encrypt_preview) {
|
|
view.lastResults = message.encrypt_preview;
|
|
view.showResults(message.encrypt_preview);
|
|
}
|
|
return result;
|
|
};
|
|
|
|
const menu = nodeType.prototype.getExtraMenuOptions;
|
|
nodeType.prototype.getExtraMenuOptions = function (canvas, options) {
|
|
const result = menu?.apply(this, arguments);
|
|
const view = views.get(this);
|
|
if (!view?.total) return result;
|
|
const index = this.imageIndex ?? this.overIndex ?? 0;
|
|
// The native actions would use the thumbnail while the original is loading.
|
|
for (let i = options.length - 1; i >= 0; i--) {
|
|
if (["Open Image", "Save Image", "Copy Image", "Copy (Clipspace)", "Paste (Clipspace)", "Open in MaskEditor | Image Canvas"].includes(options[i]?.content)) options.splice(i, 1);
|
|
}
|
|
options.unshift(
|
|
{ content: "查看原图", callback: () => { view.select(index); view.reconcile(); } },
|
|
{ content: "保存原图", callback: () => view.save(index) },
|
|
{ content: "重试预览", callback: () => view.retry() },
|
|
);
|
|
return result;
|
|
};
|
|
|
|
const removed = nodeType.prototype.onRemoved;
|
|
nodeType.prototype.onRemoved = function () { removeView(this); return removed?.apply(this, arguments); };
|
|
},
|
|
beforeConfigureGraph() {
|
|
activePromptId = null;
|
|
for (const node of [...views.keys()]) removeView(node);
|
|
},
|
|
});
|