125 lines
5.0 KiB
JavaScript
125 lines
5.0 KiB
JavaScript
// Shared helpers for AusBoss frontend extensions.
|
|
//
|
|
// .mjs files under js/ are NOT auto-loaded by ComfyUI (only .js files are),
|
|
// which makes this the right home for import-only utilities. Per-node
|
|
// entry points live in js/<node>/index.js and import from here.
|
|
|
|
import { app } from "/scripts/app.js";
|
|
|
|
// Must match `version` in pyproject.toml — scripts/release_preflight.py
|
|
// enforces it. The backend serves its copy at /ausboss/pack_version; a
|
|
// mismatch means the browser cached JavaScript from an older install.
|
|
export const AUSBOSS_JS_VERSION = "2.5.1";
|
|
|
|
export const BRAND = "#00b4aa"; // AusBoss teal — keep node accents consistent.
|
|
export const BRAND_DARK = "#007f78";
|
|
export const BRAND_BODY = "#081413";
|
|
|
|
// Wrap a LiteGraph prototype callback without clobbering whoever hooked it
|
|
// first. Other node packs patch the same prototypes, so never assign
|
|
// `proto[name] = fn` directly — always chain.
|
|
export function chainCallback(proto, name, fn) {
|
|
const prior = proto[name];
|
|
proto[name] = function (...args) {
|
|
const result = prior?.apply(this, args);
|
|
fn.apply(this, args);
|
|
return result;
|
|
};
|
|
}
|
|
|
|
// chainCallback for handlers whose truthy return tells LiteGraph the event
|
|
// was consumed (onMouseDown skips the node drag): the chain reports consumed
|
|
// when either handler did. chainCallback would drop this handler's verdict.
|
|
export function chainHandler(proto, name, fn) {
|
|
const prior = proto[name];
|
|
proto[name] = function (...args) {
|
|
const result = prior?.apply(this, args);
|
|
const handled = fn.apply(this, args);
|
|
return result || handled;
|
|
};
|
|
}
|
|
|
|
// Every public AusBoss node, plus the pre-convention LaMa alias that saved
|
|
// workflows still load.
|
|
export function isAusbossNode(node) {
|
|
const comfyClass = node?.comfyClass || "";
|
|
return comfyClass.startsWith("AUSBOSS_NODES_") || comfyClass === "SimpleWatermarkRemover";
|
|
}
|
|
|
|
// One toast for every AusBoss message. The frontend's toast store queues a
|
|
// PrimeVue toast; a frontend without one gets the console instead, and this
|
|
// never throws - a message must not break the action that raised it.
|
|
export function showToast({ detail, severity = "info", summary = "AusBoss", life = 5000 } = {}) {
|
|
try {
|
|
const toast = app?.extensionManager?.toast;
|
|
if (typeof toast?.add === "function") {
|
|
toast.add({ severity, summary, detail, life });
|
|
return;
|
|
}
|
|
} catch (_error) {
|
|
// Toast store missing or incompatible: fall through to the console.
|
|
}
|
|
const line = `[AusBoss] ${summary === "AusBoss" ? "" : `${summary}: `}${detail}`;
|
|
if (severity === "error" || severity === "warn") console.warn(line);
|
|
else console.log(line);
|
|
}
|
|
|
|
// ComfyUI's undo/dirty tracker snapshots the graph on canvas mouse-up, but
|
|
// our DOM panels commit widget values and node.properties from click/change/
|
|
// pointerup handlers that run one phase later. Without a nudge those edits
|
|
// never mark the workflow modified and never enter undo history. Call this
|
|
// once per user gesture, AFTER the write has landed — and never from a
|
|
// workflow-load path, which must stay clean.
|
|
//
|
|
// The trailing timer batches a gesture's writes into a single snapshot and
|
|
// guarantees the capture runs after the handler that invoked us returned.
|
|
// A capture that finds nothing changed is a no-op inside the tracker.
|
|
let ausbossChangeTimer = null;
|
|
|
|
export function notifyAusbossChange() {
|
|
if (ausbossChangeTimer) clearTimeout(ausbossChangeTimer);
|
|
ausbossChangeTimer = setTimeout(() => {
|
|
ausbossChangeTimer = null;
|
|
try {
|
|
const workflow =
|
|
app?.extensionManager?.workflow?.activeWorkflow ??
|
|
app?.workflowManager?.activeWorkflow;
|
|
const tracker = workflow?.changeTracker;
|
|
if (!tracker) return;
|
|
if (typeof tracker.captureCanvasState === "function") tracker.captureCanvasState();
|
|
else tracker.checkState?.();
|
|
} catch (_error) {
|
|
// Best effort: the widget write itself already succeeded, and a
|
|
// frontend rename must never break the control that called us.
|
|
}
|
|
}, 120);
|
|
}
|
|
|
|
|
|
// The frontend sizes a DOM widget's wrapper as `widget.width ?? node.width`
|
|
// (GraphView, useDomWidget's updateWidgets). LiteGraph's layout plants
|
|
// widget.width during draws, and once planted it OUTRANKS the node width
|
|
// forever: resize the node narrower and the wrapper keeps the old width,
|
|
// parking the panel's right edge outside the node. The overflow clip cannot
|
|
// help - it clips at the wrapper's edge, which is the thing that is too
|
|
// wide. Discarding every write keeps the lookup on its node-width fallback,
|
|
// so the wrapper tracks the node in both directions for the life of the
|
|
// widget. Call it on every DOM widget right after addDOMWidget.
|
|
export function keepDomWidgetWidthAuto(widget) {
|
|
if (!widget) return widget;
|
|
try {
|
|
Object.defineProperty(widget, "width", {
|
|
get() {
|
|
return undefined;
|
|
},
|
|
set(_value) {
|
|
/* a planted layout width must never outrank the node */
|
|
},
|
|
configurable: true,
|
|
});
|
|
} catch (_error) {
|
|
// A frozen widget object keeps its old behavior; nothing to break.
|
|
}
|
|
return widget;
|
|
}
|