70 lines
2.8 KiB
JavaScript
70 lines
2.8 KiB
JavaScript
// Pure resolution rules for the upstream-input preview panel. DOM-free so
|
|
// node:test covers them without a browser; the wiring lives in
|
|
// js/input_preview/index.js.
|
|
//
|
|
// Best-effort order for a connected source node:
|
|
// 1. cached execution previews (sourceNode.imgs[0].src)
|
|
// 2. an uploadable image/video file combo widget -> deterministic /view query
|
|
// 3. nothing -> the caller shows a quiet placeholder
|
|
|
|
import { splitMediaName } from "./video_preview.mjs";
|
|
|
|
// Core loader nodes keep the uploaded file name in a combo widget with one of
|
|
// these names (LoadImage uses "image", video loaders use "video").
|
|
const FILE_WIDGET_NAMES = new Set(["image", "video"]);
|
|
|
|
// Graph link stores differ by frontend generation: a Map on the modern
|
|
// frontend, a plain id-keyed object on legacy LiteGraph.
|
|
export function linkById(graph, linkId) {
|
|
const links = graph?.links;
|
|
if (links == null || linkId == null) return null;
|
|
const link = typeof links.get === "function" ? links.get(linkId) : links[linkId];
|
|
return link ?? null;
|
|
}
|
|
|
|
// Walk the named input's link back to the node feeding it, or null.
|
|
export function upstreamNode(node, inputName) {
|
|
const graph = node?.graph;
|
|
if (!graph) return null;
|
|
const input = node.inputs?.find((candidate) => candidate?.name === inputName);
|
|
const link = linkById(graph, input?.link);
|
|
if (!link) return null;
|
|
return graph.getNodeById?.(link.origin_id) ?? null;
|
|
}
|
|
|
|
// The widget whose callback fires when the user picks a different file.
|
|
export function sourceFileWidget(sourceNode) {
|
|
return sourceNode?.widgets?.find((widget) => FILE_WIDGET_NAMES.has(widget?.name)) ?? null;
|
|
}
|
|
|
|
// Deterministic /view query (no cache buster) for an input-folder file name,
|
|
// tolerating Windows separators and " [input]" annotations.
|
|
export function viewQueryForFile(name) {
|
|
const { filename, subfolder } = splitMediaName(name);
|
|
return new URLSearchParams({ filename, subfolder, type: "input" }).toString();
|
|
}
|
|
|
|
// Describe how to preview a source node:
|
|
// { kind: "url", url } cached preview image
|
|
// { kind: "view", isVideo, query } file widget, serve via api /view
|
|
// null nothing resolvable
|
|
export function describeSourcePreview(sourceNode) {
|
|
if (!sourceNode) return null;
|
|
const cached = sourceNode.imgs?.[0]?.src;
|
|
if (typeof cached === "string" && cached) return { kind: "url", url: cached };
|
|
const widget = sourceFileWidget(sourceNode);
|
|
if (widget && typeof widget.value === "string" && widget.value) {
|
|
return {
|
|
kind: "view",
|
|
isVideo: widget.name === "video",
|
|
query: viewQueryForFile(widget.value),
|
|
};
|
|
}
|
|
return null;
|
|
}
|
|
|
|
// Quiet ASCII placeholder copy; never an error.
|
|
export function placeholderText(connected, noun = "an image") {
|
|
return connected ? "source has no preview yet" : `connect ${noun} to preview`;
|
|
}
|