Files
MajoorWaldi-ComfyUI-Majoor-…/js/features/viewer/floatingViewerLoader.js
T
Ewald 2b10814b2e feat(mfv): ImageOps live preview crop + node panel selection sync
- ImageOps preview bridge: stream cropped contain-fit rect of __imageops_state.canvas (removes square scope/letterbox); fall back to full canvas when user pan/zoom is active. Signature-cached encode (WeakMap) avoids re-encoding on every poll tick.
- Floating viewer: skip metadata enrichment for imageops-live-preview source; preserve user pan/zoom across stream frames of the same node; remove inline CSS overrides that blocked the MFV transform.
- Workflow Nodes panel: graph-selected node is auto-expanded, promoted to first position in its container, scrolled into view, and gets a subtle blue highlight (is-selected-from-graph).
2026-04-24 23:47:27 +02:00

122 lines
4.1 KiB
JavaScript

import { ensureViewerMetadataAsset } from "./genInfo.js";
import { getAssetMetadata, getFileMetadataScoped } from "../../api/client.js";
import { MFV_MODES } from "./floatingViewerConstants.js";
const IMAGEOPS_LIVE_SOURCE = "imageops-live-preview";
function _isImageOpsLive(fileData) {
return String(fileData?._source || "") === IMAGEOPS_LIVE_SOURCE;
}
export function loadFloatingViewerMediaA(viewer, fileData, { autoMode = false } = {}) {
const prev = viewer._mediaA || null;
const isLive = _isImageOpsLive(fileData);
// Live ImageOps frames re-emit every ~150ms. Resetting the user's
// pan/zoom on every frame would defeat the MFV's interactive zoom.
// Preserve it as long as the streamed node hasn't changed.
const sameNodeStream =
isLive
&& _isImageOpsLive(prev)
&& String(prev?._nodeId || "") === String(fileData?._nodeId || "");
viewer._mediaA = fileData || null;
if (!sameNodeStream) {
viewer._resetMfvZoom();
}
if (autoMode && viewer._mode !== MFV_MODES.SIMPLE) {
viewer._mode = MFV_MODES.SIMPLE;
viewer._updateModeBtnUI();
}
// ImageOps live previews are transient client-side canvas snapshots;
// there is no backend asset to fetch metadata for. Skip the API round
// trip entirely (also avoids the enrichment-vs-new-emission race).
if (viewer._mediaA && !isLive && typeof ensureViewerMetadataAsset === "function") {
const gen = ++viewer._refreshGen;
(async () => {
try {
const enriched = await ensureViewerMetadataAsset(viewer._mediaA, {
getAssetMetadata,
getFileMetadataScoped,
});
if (viewer._refreshGen !== gen) return;
if (enriched && typeof enriched === "object") {
viewer._mediaA = enriched;
viewer._refresh();
}
} catch (e) {
console.debug?.("[MFV] metadata enrich error", e);
}
})();
} else {
viewer._refresh();
}
}
export function loadFloatingViewerMediaPair(viewer, a, b) {
viewer._mediaA = a || null;
viewer._mediaB = b || null;
viewer._resetMfvZoom();
if (viewer._mode === MFV_MODES.SIMPLE) {
viewer._mode = MFV_MODES.AB;
viewer._updateModeBtnUI();
}
const gen = ++viewer._refreshGen;
const hydrate = async (asset) => {
if (!asset) return asset;
try {
const enriched = await ensureViewerMetadataAsset(asset, {
getAssetMetadata,
getFileMetadataScoped,
});
return enriched || asset;
} catch {
return asset;
}
};
(async () => {
const [A, B] = await Promise.all([hydrate(viewer._mediaA), hydrate(viewer._mediaB)]);
if (viewer._refreshGen !== gen) return;
viewer._mediaA = A || null;
viewer._mediaB = B || null;
viewer._refresh();
})();
}
export function loadFloatingViewerMediaQuad(viewer, a, b, c, d) {
viewer._mediaA = a || null;
viewer._mediaB = b || null;
viewer._mediaC = c || null;
viewer._mediaD = d || null;
viewer._resetMfvZoom();
if (viewer._mode !== MFV_MODES.GRID) {
viewer._mode = MFV_MODES.GRID;
viewer._updateModeBtnUI();
}
const gen = ++viewer._refreshGen;
const hydrate = async (asset) => {
if (!asset) return asset;
try {
const enriched = await ensureViewerMetadataAsset(asset, {
getAssetMetadata,
getFileMetadataScoped,
});
return enriched || asset;
} catch {
return asset;
}
};
(async () => {
const [A, B, C, D] = await Promise.all([
hydrate(viewer._mediaA),
hydrate(viewer._mediaB),
hydrate(viewer._mediaC),
hydrate(viewer._mediaD),
]);
if (viewer._refreshGen !== gen) return;
viewer._mediaA = A || null;
viewer._mediaB = B || null;
viewer._mediaC = C || null;
viewer._mediaD = D || null;
viewer._refresh();
})();
}