353 lines
9.9 KiB
JavaScript
353 lines
9.9 KiB
JavaScript
/* WAS GB Player v2024.12.28.1135 */
|
|
import { app } from "../../scripts/app.js";
|
|
|
|
console.log("[WAS GBA] ===== SCRIPT LOADED v2024.12.28.1135 =====");
|
|
|
|
const EXT_NAME = "WAS.GameBoyPlayer";
|
|
const NODE_NAME = "ComfyGameBoyPlayer";
|
|
|
|
const DEFAULT_NODE_SIZE = [730, 480];
|
|
|
|
const STATE = {
|
|
container: null,
|
|
nodeIdToIframe: new Map(),
|
|
cleanupIntervalId: null,
|
|
cleanupListenersAttached: false,
|
|
lastScaleByNode: new Map(),
|
|
};
|
|
|
|
function readCssVar(style, name) {
|
|
const v = style.getPropertyValue(name);
|
|
return v ? String(v).trim() : "";
|
|
}
|
|
|
|
function computeThemeTokens() {
|
|
const rootStyle = getComputedStyle(document.documentElement);
|
|
const bodyStyle = getComputedStyle(document.body);
|
|
|
|
const bg =
|
|
readCssVar(rootStyle, "--comfy-menu-bg") ||
|
|
readCssVar(rootStyle, "--background-color") ||
|
|
readCssVar(rootStyle, "--bg-color") ||
|
|
bodyStyle.backgroundColor ||
|
|
"";
|
|
const fg =
|
|
readCssVar(rootStyle, "--input-text") ||
|
|
readCssVar(rootStyle, "--text-color") ||
|
|
readCssVar(rootStyle, "--fg-color") ||
|
|
bodyStyle.color ||
|
|
"";
|
|
const border =
|
|
readCssVar(rootStyle, "--border-color") ||
|
|
readCssVar(rootStyle, "--comfy-border-color") ||
|
|
"";
|
|
const accent =
|
|
readCssVar(rootStyle, "--primary-color") ||
|
|
readCssVar(rootStyle, "--accent-color") ||
|
|
readCssVar(rootStyle, "--comfy-accent") ||
|
|
"";
|
|
|
|
return {
|
|
bg,
|
|
fg,
|
|
border,
|
|
accent,
|
|
};
|
|
}
|
|
|
|
function postThemeToIframe(iframe) {
|
|
try {
|
|
const payload = {
|
|
type: "was-gba-theme",
|
|
tokens: computeThemeTokens(),
|
|
};
|
|
iframe.contentWindow?.postMessage(payload, "*");
|
|
} catch (e) {
|
|
}
|
|
}
|
|
|
|
function postScaleToIframe(iframe, scale) {
|
|
try {
|
|
const payload = {
|
|
type: "was-gba-scale",
|
|
scale: scale,
|
|
};
|
|
iframe.contentWindow?.postMessage(payload, "*");
|
|
} catch (e) {
|
|
}
|
|
}
|
|
|
|
function attachThemeSync(iframe, nodeId) {
|
|
if (iframe.__wasThemeSyncAttached) return;
|
|
iframe.__wasThemeSyncAttached = true;
|
|
|
|
const onLoad = () => {
|
|
postThemeToIframe(iframe);
|
|
const scale = app.canvas?.ds?.scale ?? 1;
|
|
postScaleToIframe(iframe, scale);
|
|
STATE.lastScaleByNode.set(String(nodeId), scale);
|
|
};
|
|
iframe.addEventListener("load", onLoad);
|
|
|
|
const onMsg = (ev) => {
|
|
try {
|
|
if (ev?.source !== iframe.contentWindow) return;
|
|
if (!ev?.data || typeof ev.data !== "object") return;
|
|
if (ev.data.type !== "was-gba-request-theme") return;
|
|
postThemeToIframe(iframe);
|
|
const scale = app.canvas?.ds?.scale ?? 1;
|
|
postScaleToIframe(iframe, scale);
|
|
} catch (e) {
|
|
}
|
|
};
|
|
window.addEventListener("message", onMsg);
|
|
|
|
const mo = new MutationObserver(() => postThemeToIframe(iframe));
|
|
mo.observe(document.documentElement, { attributes: true, attributeFilter: ["class", "style"] });
|
|
|
|
iframe.__wasThemeSyncCleanup = () => {
|
|
try {
|
|
iframe.removeEventListener("load", onLoad);
|
|
} catch (e) {
|
|
}
|
|
try {
|
|
window.removeEventListener("message", onMsg);
|
|
} catch (e) {
|
|
}
|
|
try {
|
|
mo.disconnect();
|
|
} catch (e) {
|
|
}
|
|
};
|
|
}
|
|
|
|
function getActiveGraphNodes() {
|
|
const g = app?.graph || app?.canvas?.graph;
|
|
const nodes = g?._nodes || g?.nodes;
|
|
return Array.isArray(nodes) ? nodes : [];
|
|
}
|
|
|
|
function isGbPlayerNode(node) {
|
|
try {
|
|
return (
|
|
node?.comfyClass === NODE_NAME ||
|
|
node?.type === NODE_NAME ||
|
|
node?.constructor?.comfyClass === NODE_NAME ||
|
|
node?.constructor?.type === NODE_NAME
|
|
);
|
|
} catch (e) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function removeIframeByKey(key) {
|
|
try {
|
|
const iframe = STATE.nodeIdToIframe.get(key);
|
|
if (!iframe) return;
|
|
try {
|
|
iframe.__wasThemeSyncCleanup?.();
|
|
} catch (e) {
|
|
}
|
|
iframe.remove();
|
|
} catch (e) {
|
|
} finally {
|
|
STATE.nodeIdToIframe.delete(key);
|
|
}
|
|
}
|
|
|
|
function cleanupOrphanIframes() {
|
|
try {
|
|
const nodes = getActiveGraphNodes();
|
|
const activeIds = new Set(
|
|
nodes
|
|
.filter((n) => isGbPlayerNode(n))
|
|
.map((n) => String(n.id))
|
|
);
|
|
|
|
for (const key of Array.from(STATE.nodeIdToIframe.keys())) {
|
|
if (!activeIds.has(key)) {
|
|
removeIframeByKey(key);
|
|
}
|
|
}
|
|
|
|
if (STATE.nodeIdToIframe.size === 0 && STATE.container) {
|
|
try {
|
|
STATE.container.remove();
|
|
} catch (e) {
|
|
}
|
|
STATE.container = null;
|
|
}
|
|
} catch (e) {
|
|
}
|
|
}
|
|
|
|
function ensureCleanupRunning() {
|
|
if (STATE.cleanupIntervalId != null) return;
|
|
STATE.cleanupIntervalId = window.setInterval(() => cleanupOrphanIframes(), 1000);
|
|
if (!STATE.cleanupListenersAttached) {
|
|
STATE.cleanupListenersAttached = true;
|
|
const onVis = () => cleanupOrphanIframes();
|
|
const onFocus = () => cleanupOrphanIframes();
|
|
window.addEventListener("visibilitychange", onVis);
|
|
window.addEventListener("focus", onFocus);
|
|
}
|
|
}
|
|
|
|
function getContainer() {
|
|
if (STATE.container) return STATE.container;
|
|
const el = document.createElement("div");
|
|
el.id = "was-gba-overlay";
|
|
el.style.position = "absolute";
|
|
el.style.left = "0";
|
|
el.style.top = "0";
|
|
el.style.width = "0";
|
|
el.style.height = "0";
|
|
el.style.pointerEvents = "none";
|
|
el.style.zIndex = "100";
|
|
document.body.appendChild(el);
|
|
STATE.container = el;
|
|
return el;
|
|
}
|
|
|
|
function ensureIframeForNode(node) {
|
|
cleanupOrphanIframes();
|
|
ensureCleanupRunning();
|
|
|
|
const key = String(node.id);
|
|
const existing = STATE.nodeIdToIframe.get(key);
|
|
if (existing) return existing;
|
|
|
|
const iframe = document.createElement("iframe");
|
|
iframe.setAttribute("sandbox", "allow-scripts allow-forms allow-downloads");
|
|
iframe.style.position = "absolute";
|
|
iframe.style.border = "0";
|
|
iframe.style.pointerEvents = "auto";
|
|
iframe.style.background = "#000";
|
|
iframe.style.left = "0px";
|
|
iframe.style.top = "0px";
|
|
iframe.style.width = "0px";
|
|
iframe.style.height = "0px";
|
|
iframe.style.display = "none";
|
|
iframe.style.borderRadius = "8px";
|
|
|
|
const src = "extensions/ComfyUI_GB_Player/gba/player/index.html";
|
|
iframe.src = src;
|
|
|
|
attachThemeSync(iframe, node.id);
|
|
|
|
getContainer().appendChild(iframe);
|
|
STATE.nodeIdToIframe.set(key, iframe);
|
|
|
|
const oldOnRemoved = node.onRemoved;
|
|
node.onRemoved = function () {
|
|
try {
|
|
removeIframeByKey(String(this.id));
|
|
cleanupOrphanIframes();
|
|
} catch (e) {
|
|
}
|
|
return oldOnRemoved ? oldOnRemoved.apply(this, arguments) : undefined;
|
|
};
|
|
|
|
return iframe;
|
|
}
|
|
|
|
function updateIframeRect(node, iframe) {
|
|
const canvas = app.canvas;
|
|
const canvasEl = canvas?.canvas;
|
|
if (!canvasEl) return;
|
|
|
|
const rect = canvasEl.getBoundingClientRect();
|
|
const ds = canvas.ds;
|
|
const scale = ds?.scale ?? 1;
|
|
const offset = ds?.offset ?? [0, 0];
|
|
|
|
const x = rect.left + (node.pos[0] + offset[0]) * scale;
|
|
const y = rect.top + (node.pos[1] + offset[1]) * scale;
|
|
const w = node.size[0] * scale;
|
|
const h = node.size[1] * scale;
|
|
|
|
const titleHRaw =
|
|
(typeof node?.title_height === "number" && Number.isFinite(node.title_height) && node.title_height) ||
|
|
(typeof node?.constructor?.title_height === "number" &&
|
|
Number.isFinite(node.constructor.title_height) &&
|
|
node.constructor.title_height) ||
|
|
globalThis?.LiteGraph?.NODE_TITLE_HEIGHT ||
|
|
30;
|
|
const titleH = titleHRaw * scale;
|
|
const insetX = 8 * scale;
|
|
const insetTop = 0;
|
|
const insetBottom = 8 * scale;
|
|
const headerAdjust = 20 * scale;
|
|
const innerX = x + insetX;
|
|
const innerY = y + titleH + insetTop - headerAdjust;
|
|
const innerW = w - insetX * 2;
|
|
const innerH = h - titleH - insetTop - insetBottom + headerAdjust;
|
|
|
|
const nx = Number.isFinite(innerX) ? innerX : 0;
|
|
const ny = Number.isFinite(innerY) ? innerY : 0;
|
|
const nw = Number.isFinite(innerW) ? innerW : 0;
|
|
const nh = Number.isFinite(innerH) ? innerH : 0;
|
|
|
|
iframe.style.left = `${Math.round(nx)}px`;
|
|
iframe.style.top = `${Math.round(ny)}px`;
|
|
iframe.style.width = `${Math.max(0, Math.round(nw))}px`;
|
|
iframe.style.height = `${Math.max(0, Math.round(nh))}px`;
|
|
|
|
const isCollapsed = !!node.flags?.collapsed;
|
|
const hasArea = nw >= 2 && nh >= 2;
|
|
iframe.style.display = !isCollapsed && hasArea ? "block" : "none";
|
|
}
|
|
|
|
app.registerExtension({
|
|
name: EXT_NAME,
|
|
async beforeRegisterNodeDef(nodeType, nodeData) {
|
|
if (nodeData?.name !== NODE_NAME) return;
|
|
|
|
ensureCleanupRunning();
|
|
|
|
const oldOnNodeCreated = nodeType.prototype.onNodeCreated;
|
|
nodeType.prototype.onNodeCreated = function () {
|
|
const r = oldOnNodeCreated ? oldOnNodeCreated.apply(this, arguments) : undefined;
|
|
try {
|
|
if (!Array.isArray(this.size) || this.size.length < 2) {
|
|
this.size = [...DEFAULT_NODE_SIZE];
|
|
} else {
|
|
this.size[0] = Math.max(this.size[0] ?? 0, DEFAULT_NODE_SIZE[0]);
|
|
this.size[1] = Math.max(this.size[1] ?? 0, DEFAULT_NODE_SIZE[1]);
|
|
}
|
|
this.setDirtyCanvas?.(true, true);
|
|
} catch (e) {
|
|
}
|
|
return r;
|
|
};
|
|
|
|
const oldOnDrawForeground = nodeType.prototype.onDrawForeground;
|
|
nodeType.prototype.onDrawForeground = function (ctx) {
|
|
const r = oldOnDrawForeground ? oldOnDrawForeground.apply(this, arguments) : undefined;
|
|
try {
|
|
const iframe = ensureIframeForNode(this);
|
|
updateIframeRect(this, iframe);
|
|
const scale = app.canvas?.ds?.scale ?? 1;
|
|
const nodeKey = String(this.id);
|
|
if (STATE.lastScaleByNode.get(nodeKey) !== scale) {
|
|
STATE.lastScaleByNode.set(nodeKey, scale);
|
|
postScaleToIframe(iframe, scale);
|
|
}
|
|
} catch (e) {
|
|
}
|
|
return r;
|
|
};
|
|
|
|
const oldOnResize = nodeType.prototype.onResize;
|
|
nodeType.prototype.onResize = function (size) {
|
|
const r = oldOnResize ? oldOnResize.apply(this, arguments) : undefined;
|
|
try {
|
|
const iframe = ensureIframeForNode(this);
|
|
updateIframeRect(this, iframe);
|
|
} catch (e) {
|
|
}
|
|
return r;
|
|
};
|
|
},
|
|
});
|