Files
MajoorWaldi-ComfyUI-Majoor-…/ui/features/viewer/workflowGraphMap/workflowGraphMapActions.ts
T
Ewald 08c643c87d fix: address deep audit findings (backend/frontend/docs conventions)
Docs:
- Fix stale version/date banner in docs/DOCUMENTATION_INDEX.md (2.4.9 -> 2.5.1)
- Normalize CHANGELOG.md section headers to plain text (strip inconsistent
  emoji prefixes used in older entries)
- Add missing diacritics in French command labels (locales/fr/commands.json)
- Add ToC anchors for nested Installation subsections in README.md

Backend:
- Document the comfy_entrypoint() wrapper's rationale in __init__.py

Frontend:
- Wire tsconfig.strict.json into an actual npm script (typecheck:js:strict,
  now part of quality:strict) instead of leaving it dead/unenforced
- Fix the 8 strict-mode type errors this surfaced across
  comfyWorkflowParser.ts, canvasLoaderNode.ts, FloatingViewer.impl.ts and
  workflowGraphMapActions.ts
- Rebuild dist/ to match source

Deferred (too large/risky to blind-fix in this pass):
- ui/vue/** ESLint+Vue+TS linting: typescript-eslint currently has no
  compatible release for this repo's typescript@7.0.2, so adding
  eslint-plugin-vue/typescript-eslint is blocked upstream
- Converting all 44 .vue SFCs to lang="ts" with typed defineProps
- Eliminating the ~230 any/as any occurrences under ui/vue
- Auditing/reducing the ~1082 except Exception: blocks repo-wide
2026-09-11 19:14:28 +02:00

224 lines
7.8 KiB
TypeScript

import {
addNodeToHostGraph,
createHostNode,
getFirstSelectedHostCanvasNode,
getHostGraph,
importWorkflowIntoHostCanvas,
refreshHostCanvasGraph,
} from "../../../app/hostAdapter.js";
import { copyTextToClipboard } from "../../../utils/dom.js";
import { writeWidgetValue } from "../workflowSidebar/widgetAdapters.js";
import {
getNodeType,
getNodeParamEntries,
getNodeWidgetValueEntries,
} from "./workflowGraphMapData.js";
type LooseRecord = Record<string, any>;
type WidgetRecord = LooseRecord;
interface TransferResult {
ok: boolean;
count: number;
reason: "no-target" | "no-widgets" | "ok" | "no-match";
targetNode?: LooseRecord;
}
export async function copyNodeJson(node: any): Promise<boolean> {
if (!node) return false;
const text = JSON.stringify(_prepareNodeForExternalUse(node), null, 2);
return copyTextToClipboard(text);
}
export async function copyNodeParamValue(value: any): Promise<boolean> {
return copyTextToClipboard(formatParamClipboardValue(value));
}
export function formatParamClipboardValue(value: any): string {
if (value == null) return "";
if (typeof value === "string") return value;
if (typeof value === "number" || typeof value === "boolean") return String(value);
try {
return JSON.stringify(value, null, 2);
} catch {
return String(value);
}
}
export function importWorkflowToCanvas(workflow: any): boolean {
return importWorkflowIntoHostCanvas(workflow);
}
export function importNodeToCurrentGraph(node: LooseRecord | null | undefined): boolean {
const graph = getHostGraph();
if (!node || !graph || typeof graph.add !== "function") return false;
const nodeType = String(node?.type || node?.class_type || node?.comfyClass || "").trim();
let created: LooseRecord | null = null;
try {
created = createHostNode(nodeType) as LooseRecord | null;
} catch (e: any) {
console.debug?.("[MFV Graph Map] createNode failed", e);
}
if (!created) return false;
try {
const clone =
typeof structuredClone === "function"
? structuredClone(node)
: JSON.parse(JSON.stringify(node));
_normalizeExternalNodeClone(clone, node);
delete clone.id;
if (Array.isArray(clone.pos)) {
clone.pos = [Number(clone.pos[0] || 0) + 32, Number(clone.pos[1] || 0) + 32];
}
if (typeof created.configure === "function") {
created.configure(clone);
} else {
Object.assign(created, clone);
}
return addNodeToHostGraph(created);
} catch (e: any) {
console.debug?.("[MFV Graph Map] import node failed", e);
return false;
}
}
export function transferNodeParamsToSelectedCanvasNode(
sourceNode: LooseRecord | null | undefined,
): TransferResult {
const targetNode = getSingleSelectedCanvasNode();
if (!sourceNode || !targetNode) return { ok: false, count: 0, reason: "no-target" };
const sourceEntries = _getTransferParamEntries(sourceNode);
const targetWidgets = Array.isArray(targetNode.widgets) ? targetNode.widgets : [];
if (!sourceEntries.length || !targetWidgets.length) {
return { ok: false, count: 0, reason: "no-widgets" };
}
const targetByName = new Map<string, { widget: WidgetRecord; index: number }>();
targetWidgets.forEach((widget, index) => {
for (const key of _widgetKeys(widget)) {
if (!targetByName.has(key)) targetByName.set(key, { widget, index });
}
});
const sourceType = _normalizeType(getNodeType(sourceNode));
const targetType = _normalizeType(targetNode?.type || targetNode?.comfyClass || targetNode?.class_type);
const allowPositionFallback = Boolean(sourceType && targetType && sourceType === targetType);
const usedTargets = new Set<number>();
let count = 0;
for (const entry of sourceEntries) {
const key = _normalizeName(entry.label);
let target = key ? targetByName.get(key) : null;
const entryIndex = entry.index;
if ((!target || usedTargets.has(target.index)) && allowPositionFallback && typeof entryIndex === "number" && Number.isInteger(entryIndex)) {
const positional = targetWidgets[entryIndex];
if (positional) target = { widget: positional, index: entryIndex };
}
if (!target || usedTargets.has(target.index)) continue;
if (writeWidgetValue(target.widget, _cloneValue(entry.value), targetNode)) {
usedTargets.add(target.index);
count += 1;
}
}
refreshHostCanvasGraph();
return {
ok: count > 0,
count,
reason: count > 0 ? "ok" : "no-match",
targetNode,
};
}
export function getSingleSelectedCanvasNode(): LooseRecord | null {
return (getFirstSelectedHostCanvasNode() as LooseRecord | null) || null;
}
function _getTransferParamEntries(sourceNode: LooseRecord): Array<{ label: any; value: any; index?: number }> {
const paramEntries = getNodeParamEntries(sourceNode);
if (Array.isArray(paramEntries) && paramEntries.length) {
return paramEntries.map(([label, value]) => ({ label, value }));
}
return getNodeWidgetValueEntries(sourceNode);
}
function _prepareNodeForExternalUse(node: LooseRecord): LooseRecord {
const clone =
typeof structuredClone === "function"
? structuredClone(node)
: JSON.parse(JSON.stringify(node));
_normalizeExternalNodeClone(clone, node);
return clone;
}
function _normalizeExternalNodeClone(clone: LooseRecord, sourceNode: LooseRecord) {
const promptInputs =
sourceNode?._mjrPromptInputs && typeof sourceNode._mjrPromptInputs === "object"
? sourceNode._mjrPromptInputs
: null;
if (promptInputs) {
const normalizedWidgets = _buildKnownWidgetValuesFromPrompt(sourceNode, promptInputs);
if (normalizedWidgets) clone.widgets_values = normalizedWidgets;
}
delete clone._mjrPromptInputs;
delete clone._mjrSubgraphProxyParams;
}
function _buildKnownWidgetValuesFromPrompt(sourceNode: LooseRecord, promptInputs: LooseRecord): any[] | null {
const type = _normalizeType(getNodeType(sourceNode));
const current = getNodeWidgetValueEntries(sourceNode);
const currentByLabel = new Map(current.map((entry) => [_normalizeName(entry.label), entry.value]));
if (type.includes("ksamplerselect")) {
if (promptInputs.sampler_name == null) return null;
return [_cloneValue(promptInputs.sampler_name)];
}
if (type.includes("ksampler")) {
const controlAfterGenerate =
promptInputs.control_after_generate ?? currentByLabel.get("control_after_generate") ?? current[1]?.value;
return [
_cloneValue(promptInputs.seed),
_cloneValue(controlAfterGenerate),
_cloneValue(promptInputs.steps),
_cloneValue(promptInputs.cfg),
_cloneValue(promptInputs.sampler_name),
_cloneValue(promptInputs.scheduler),
_cloneValue(promptInputs.denoise),
];
}
return null;
}
function _widgetKeys(widget: WidgetRecord): string[] {
return [widget?.name, widget?.label, widget?.options?.name, widget?.options?.label]
.map(_normalizeName)
.filter(Boolean);
}
function _normalizeName(value: any): string {
return String(value ?? "")
.trim()
.toLowerCase()
.replace(/\s+/g, "_");
}
function _normalizeType(value: any): string {
return String(value ?? "").trim().toLowerCase();
}
function _cloneValue(value: any): any {
if (value == null || typeof value !== "object") return value;
try {
return typeof structuredClone === "function"
? structuredClone(value)
: JSON.parse(JSON.stringify(value));
} catch {
return value;
}
}