From 4a681dfe9107abd2f93d603e45c27f2e79783374 Mon Sep 17 00:00:00 2001 From: bia Date: Mon, 8 Jun 2026 17:27:45 +0800 Subject: [PATCH] =?UTF-8?q?=E8=8A=82=E7=82=B9=E7=BC=96=E8=BE=91=E5=99=A8?= =?UTF-8?q?=E6=94=B9=E7=94=A8=20Drawflow=20=E6=8B=96=E6=8B=BD=E8=BF=9E?= =?UTF-8?q?=E7=BA=BF=E7=89=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 中间画布换成 Drawflow:拖动节点摆位、从出口圆点拉线到目标=建跳转 - 出口端口动态映射 IR:线性next/choice选项/random分支/fight胜败 - 连线/拖动实时写回 IR;节点坐标持久化到 ir._layout(编译忽略) - 右栏表单保留并双向联动;改跳转目标触发画布重渲 - 工具栏:自动整理、加后继;防误删(右栏输入时 Del 不删节点) - 移除旧 tree.js --- web/static/app.js | 59 +++++--- web/static/form.js | 20 +-- web/static/graph.js | 208 +++++++++++++++++++++++++++++ web/static/index.html | 13 +- web/static/style.css | 42 +++++- web/static/tree.js | 157 ---------------------- web/static/vendor/drawflow.min.css | 1 + web/static/vendor/drawflow.min.js | 1 + 8 files changed, 305 insertions(+), 196 deletions(-) create mode 100644 web/static/graph.js delete mode 100644 web/static/tree.js create mode 100644 web/static/vendor/drawflow.min.css create mode 100644 web/static/vendor/drawflow.min.js diff --git a/web/static/app.js b/web/static/app.js index a521f80..f5cc183 100644 --- a/web/static/app.js +++ b/web/static/app.js @@ -79,7 +79,7 @@ App.current = group; App.ir = JSON.parse(JSON.stringify(d.ir)); App.status = d.status; App.selectedNode = null; App.dirty = false; $("graph-empty").style.display = "none"; - ["btn-save", "btn-validate", "btn-playtest", "btn-confirm", "btn-discard", "btn-addnode"].forEach(b => $(b).disabled = false); + ["btn-save", "btn-validate", "btn-playtest", "btn-confirm", "btn-discard", "btn-addnode", "btn-autolayout", "btn-addsucc"].forEach(b => $(b).disabled = false); renderAll(true); renderList(); updateDirty(); @@ -88,35 +88,33 @@ const ctx = () => ({ dict: App.dict, pointNames: (App.pointsets[(App.ir.stage || {}).point_set || App.ir.id] || {}).points || [], - onChange: structural => { App.dirty = true; updateDirty(); drawTree(); if (structural) { FormUI.renderMeta(App.ir, ctx()); FormUI.renderNode(App.ir, App.selectedNode, ctx()); } }, + onChange: structural => { + App.dirty = true; updateDirty(); + if (structural) { GraphUI.render(App.ir, App.selectedNode); FormUI.renderMeta(App.ir, ctx()); FormUI.renderNode(App.ir, App.selectedNode, ctx()); } + else { GraphUI.updateLabel(App.ir, App.selectedNode); } + }, selectNode: id => { App.selectedNode = id; }, }); - function drawTree() { - if (App.ir) renderTree(App.ir, { - selected: App.selectedNode, onSelect: selectNode, - onAddNext: addSuccessor, onDelete: deleteNode, - }); - } - function selectNode(id) { App.selectedNode = id; drawTree(); FormUI.renderNode(App.ir, id, ctx()); } + function selectNode(id) { App.selectedNode = id; GraphUI.select(id); FormUI.renderNode(App.ir, id, ctx()); } - // 节点快捷按钮:加后继 / 删除 + // 工具栏「加后继」:给当前选中节点建一个后继并自动接线(无选中则忽略) function addSuccessor(id) { const r = FormUI.addSuccessor(App.ir, id); if (!r) return; if (!r.linked) alert("该战斗节点的「胜→win」「败→lose」出口都已占用,\n新节点已创建但未自动接线——请在右栏把胜或败指向它(id: " + r.id + ")。"); - App.dirty = true; selectNode(r.id); FormUI.renderMeta(App.ir, ctx()); updateDirty(); + App.dirty = true; App.selectedNode = r.id; + GraphUI.render(App.ir, r.id); FormUI.renderMeta(App.ir, ctx()); FormUI.renderNode(App.ir, r.id, ctx()); updateDirty(); } - function deleteNode(id) { - const isEnding = (App.ir.endings || []).some(e => e.id === id); - if (!confirm("删除" + (isEnding ? "结局" : "节点") + " " + id + "?指向它的跳转需手动修复(校验会提示)。")) return; - if (isEnding) App.ir.endings = (App.ir.endings || []).filter(e => e.id !== id); - else App.ir.nodes = (App.ir.nodes || []).filter(n => n.id !== id); + // 纯数据删除节点/结局(供右栏删除按钮与画布 Del 键共用) + function removeFromIr(id) { + App.ir.nodes = (App.ir.nodes || []).filter(n => n.id !== id); + App.ir.endings = (App.ir.endings || []).filter(e => e.id !== id); + if (App.ir._layout) delete App.ir._layout[id]; if (App.selectedNode === id) App.selectedNode = null; - App.dirty = true; drawTree(); FormUI.renderMeta(App.ir, ctx()); - FormUI.renderNode(App.ir, App.selectedNode, ctx()); updateDirty(); + App.dirty = true; updateDirty(); } - function renderAll() { drawTree(); FormUI.renderMeta(App.ir, ctx()); FormUI.renderNode(App.ir, App.selectedNode, ctx()); } + function renderAll() { GraphUI.render(App.ir, App.selectedNode); FormUI.renderMeta(App.ir, ctx()); FormUI.renderNode(App.ir, App.selectedNode, ctx()); } function updateDirty() { $("btn-save").textContent = App.dirty ? "保存 *" : "保存"; @@ -127,7 +125,8 @@ $("btn-addnode").onclick = () => { if (!App.ir) return; const id = FormUI.newNode(App.ir); - App.dirty = true; selectNode(id); FormUI.renderMeta(App.ir, ctx()); updateDirty(); + App.dirty = true; App.selectedNode = id; + GraphUI.render(App.ir, id); FormUI.renderMeta(App.ir, ctx()); FormUI.renderNode(App.ir, id, ctx()); updateDirty(); }; // ---------- 保存 ---------- @@ -242,9 +241,27 @@ // ---------- 工具 ---------- function esc(s) { return String(s == null ? "" : s).replace(/&/g, "&").replace(//g, ">"); } - window.addEventListener("resize", drawTree); + + // ---------- 画布工具栏 ---------- + $("btn-autolayout").onclick = () => { + if (!App.ir) return; + App.ir._layout = null; // 清坐标 → render 时按自动布局重排 + GraphUI.render(App.ir, App.selectedNode); + App.dirty = true; updateDirty(); + }; + $("btn-addsucc").onclick = () => { + if (!App.ir || !App.selectedNode) { alert("先在画布上点选一个节点,再点「加后继」。"); return; } + addSuccessor(App.selectedNode); + }; // ---------- 启动 ---------- + GraphUI.init("drawflow", { + onSelect: id => selectNode(id), + onMove: (id, x, y) => { if (!App.ir) return; (App.ir._layout = App.ir._layout || {})[id] = { x: x, y: y }; App.dirty = true; updateDirty(); }, + onConnect: () => { App.dirty = true; updateDirty(); FormUI.renderNode(App.ir, App.selectedNode, ctx()); }, + onDisconnect: () => { App.dirty = true; updateDirty(); FormUI.renderNode(App.ir, App.selectedNode, ctx()); }, + onRemove: id => { removeFromIr(id); FormUI.renderNode(App.ir, App.selectedNode, ctx()); }, + }); (async function () { try { const r = await fetch("/api/events?status=all"); if (r.status === 401) { showLogin(); return; } hideLogin(); init(); } catch (e) { showLogin(); } diff --git a/web/static/form.js b/web/static/form.js index e189f34..35e5893 100644 --- a/web/static/form.js +++ b/web/static/form.js @@ -169,12 +169,12 @@ if (node.kind === "narration") { host.appendChild(field("说话者(可选)", sel(node.speaker || "P1", [{ value: "P1", label: "P1 玩家" }].concat(slots(ir)), v => { node.speaker = v; mut(1); }))); host.appendChild(field("文本", area(node.text, v => { node.text = v; mut(1); }))); - host.appendChild(field("下一步 next", sel(node.next, tgt, v => { node.next = v; ctx.onChange(false); }))); + host.appendChild(field("下一步 next", sel(node.next, tgt, v => { node.next = v; ctx.onChange(true); }))); } else if (node.kind === "dialogue") { host.appendChild(field("说话者 speaker", sel(node.speaker, [{ value: "P1", label: "P1 玩家" }].concat(slots(ir)), v => { node.speaker = v; mut(1); }))); host.appendChild(field("镜头 camera(点位,可选)", sel(node.camera, pointOpts(ir, ctx, node.camera), v => { node.camera = v || undefined; mut(1); }))); host.appendChild(field("文本", area(node.text, v => { node.text = v; mut(1); }))); - host.appendChild(field("下一步 next", sel(node.next, tgt, v => { node.next = v; ctx.onChange(false); }))); + host.appendChild(field("下一步 next", sel(node.next, tgt, v => { node.next = v; ctx.onChange(true); }))); } else if (node.kind === "choice" || node.kind === "choice_once") { const box = el("div", { class: "subbox" }); box.appendChild(el("div", { class: "hd" }, [ @@ -188,7 +188,7 @@ el("button", { class: "mini", onclick: () => { node.options.splice(i, 1); ctx.onChange(true); } }, ["删"]), ])); ob.appendChild(field("文本", txt(o.text, v => { o.text = v; ctx.onChange(false); }))); - ob.appendChild(field("跳转 goto", sel(o.goto, tgt, v => { o.goto = v; ctx.onChange(false); }))); + ob.appendChild(field("跳转 goto", sel(o.goto, tgt, v => { o.goto = v; ctx.onChange(true); }))); ob.appendChild(condEditor(ir, ctx, o.condition, (c, valOnly) => { if (c) o.condition = c; else delete o.condition; ctx.onChange(!valOnly); })); ob.appendChild(grantsEditor(ir, ctx, (o.reward || {}).grants, (gr, valOnly) => { o.reward = { grants: gr }; ctx.onChange(!valOnly); })); // skip @@ -214,7 +214,7 @@ (node.branches || []).forEach((b, i) => { box.appendChild(el("div", { class: "row2" }, [ field("权重", num(b.weight, v => { b.weight = v; ctx.onChange(false); })), - field("跳转", sel(b.goto, tgt, v => { b.goto = v; ctx.onChange(false); })), + field("跳转", sel(b.goto, tgt, v => { b.goto = v; ctx.onChange(true); })), el("button", { class: "mini", onclick: () => { node.branches.splice(i, 1); ctx.onChange(true); } }, ["删"]), ])); }); @@ -224,8 +224,8 @@ host.appendChild(campPicker("我方 camp1", ir, node, "camp1", ctx, true)); host.appendChild(campPicker("敌方 camp2", ir, node, "camp2", ctx, false)); host.appendChild(el("div", { class: "row2" }, [ - field("胜 → win", sel(node.win, tgt, v => { node.win = v; ctx.onChange(false); })), - field("败 → lose", sel(node.lose, tgt, v => { node.lose = v; ctx.onChange(false); })), + field("胜 → win", sel(node.win, tgt, v => { node.win = v; ctx.onChange(true); })), + field("败 → lose", sel(node.lose, tgt, v => { node.lose = v; ctx.onChange(true); })), ])); } else if (node.kind === "move") { host.appendChild(field("移动者 actor", sel(node.actor, [{ value: "P1", label: "P1 玩家" }].concat(slots(ir)), v => { node.actor = v; mut(1); }))); @@ -237,21 +237,21 @@ field("动作 ani", txt(node.ani, v => { node.ani = v; ctx.onChange(false); })), ])); } - host.appendChild(field("下一步 next", sel(node.next, tgt, v => { node.next = v; ctx.onChange(false); }))); + host.appendChild(field("下一步 next", sel(node.next, tgt, v => { node.next = v; ctx.onChange(true); }))); } else if (node.kind === "anim") { host.appendChild(field("角色 actor", sel(node.actor, [{ value: "P1", label: "P1 玩家" }].concat(slots(ir)), v => { node.actor = v; mut(1); }))); host.appendChild(el("div", { class: "row2" }, [ field("动画 ani", txt(node.ani, v => { node.ani = v; ctx.onChange(false); })), field("朝向 angle(可选)", num(node.angle, v => { if (v == null) delete node.angle; else node.angle = v; ctx.onChange(false); })), ])); - host.appendChild(field("下一步 next", sel(node.next, tgt, v => { node.next = v; ctx.onChange(false); }))); + host.appendChild(field("下一步 next", sel(node.next, tgt, v => { node.next = v; ctx.onChange(true); }))); } else if (node.kind === "reward") { host.appendChild(grantsEditor(ir, ctx, node.grants, (gr, valOnly) => { node.grants = gr; ctx.onChange(!valOnly); })); - host.appendChild(field("下一步 next", sel(node.next, tgt, v => { node.next = v; ctx.onChange(false); }))); + host.appendChild(field("下一步 next", sel(node.next, tgt, v => { node.next = v; ctx.onChange(true); }))); } else if (node.kind === "out_ref") { const seqs = (ir.sequences || []).map(s => ({ value: s.id, label: s.id })); host.appendChild(field("引用子序列 ref", sel(node.ref, [{ value: "", label: "(无)" }].concat(seqs), v => { node.ref = v; ctx.onChange(false); }))); - host.appendChild(field("出口接回 next", sel(node.next, tgt, v => { node.next = v; ctx.onChange(false); }))); + host.appendChild(field("出口接回 next", sel(node.next, tgt, v => { node.next = v; ctx.onChange(true); }))); } // 删除节点 diff --git a/web/static/graph.js b/web/static/graph.js new file mode 100644 index 0000000..e24689d --- /dev/null +++ b/web/static/graph.js @@ -0,0 +1,208 @@ +// graph.js — Drawflow 版分支图编辑器。 +// 单一数据源 = IR:拖线连接=改出口跳转,拖动节点=存坐标(ir._layout),结构变=整体重渲。 +// 暴露 window.GraphUI = { init, render, updateLabel, select, autoLayout }。 +(function () { + const ROW = 135, COL = 290; // 自动布局间距:COL=层间(横向) ROW=同层(纵向) + let editor = null, cb = {}, _ir = null; + let dfId2ir = {}, ir2dfId = {}, building = false; // building: 重渲期间抑制事件 + + // ---------- 显示辅助(移植自旧 tree.js) ---------- + function roleNames(ir) { + const m = {}; + (ir.roles || []).forEach(r => m[r.slot] = r.name + (r.archetype ? ("〔" + r.archetype + "〕") : "")); + return m; + } + function nameOf(ir, names, s) { return (s === "P1" ? "玩家" : "") || names[s] || s; } + function grantStr(ir, names, gr) { + if (gr.kind === "银两") return "银两 " + (gr.value > 0 ? "+" : "") + gr.value; + if (gr.kind === "道具") return "道具 " + gr.item + " ×" + gr.value; + if (gr.kind === "友好度") return nameOf(ir, names, gr.target) + " 友好度+" + gr.value; + if (gr.kind === "入门") return nameOf(ir, names, gr.target) + " 加入门派"; + return JSON.stringify(gr); + } + function summary(ir, names, n) { + if (n.kind === "narration") return ["旁白", (n.text || "").slice(0, 22)]; + if (n.kind === "dialogue") return ["对话 · " + nameOf(ir, names, n.speaker), (n.text || "").slice(0, 20)]; + if (n.kind === "choice") return ["选择 (" + (n.options || []).length + "项)", (n.options || []).map(o => o.text).join(" / ").slice(0, 24)]; + if (n.kind === "choice_once") return ["一次性选择", (n.options || []).map(o => o.text).join(" / ").slice(0, 24)]; + if (n.kind === "random") return ["随机分支", (n.branches || []).length + " 路"]; + if (n.kind === "fight") return ["战斗", "vs " + (n.camp2 || []).map(s => nameOf(ir, names, s)).join("、")]; + if (n.kind === "move") return ["走位 · " + nameOf(ir, names, n.actor), "→ " + (n.to || "")]; + if (n.kind === "anim") return ["动画 · " + nameOf(ir, names, n.actor), n.ani || ""]; + if (n.kind === "reward") return ["奖励结算", ""]; + if (n.kind === "out_ref") return ["引用子序列", "→ " + (n.ref || "")]; + if (n.kind === "ending") return ["★ 结局", n.summary || ""]; + return [n.kind, ""]; + } + function esc(s) { return String(s == null ? "" : s).replace(/&/g, "&").replace(//g, ">"); } + + // ---------- 出口模型:output_(i+1) ←→ IR 出口 ---------- + function isEnding(node) { return (_ir.endings || []).some(e => e.id === node.id); } + function getOutlets(node) { + const k = node.kind; + if (k === "choice" || k === "choice_once") + return (node.options || []).map((o, i) => ({ label: "选" + (i + 1) + (o.text ? "·" + o.text.slice(0, 6) : ""), target: o.goto || "" })); + if (k === "random") + return (node.branches || []).map((b) => ({ label: "权" + (b.weight != null ? b.weight : ""), target: b.goto || "" })); + if (k === "fight") + return [{ label: "胜", target: node.win || "" }, { label: "败", target: node.lose || "" }]; + if (isEnding(node)) return []; + return [{ label: "next", target: node.next || "" }]; // 线性 + } + function setOutlet(node, idx, target) { + const k = node.kind; + if (k === "choice" || k === "choice_once") { if (node.options && node.options[idx]) node.options[idx].goto = target; } + else if (k === "random") { if (node.branches && node.branches[idx]) node.branches[idx].goto = target; } + else if (k === "fight") { if (idx === 0) node.win = target; else node.lose = target; } + else { node.next = target; } + } + function findNode(irId) { + return (_ir.nodes || []).find(n => n.id === irId) || (_ir.endings || []).find(e => e.id === irId); + } + + // ---------- 节点 HTML ---------- + function nodeInner(ir, node) { + const names = roleNames(ir), end = isEnding(node); + const sm = summary(ir, names, end ? Object.assign({ kind: "ending" }, node) : node); + let h = '
#' + esc(node.id) + '
' + + '
' + esc(sm[0]) + '
' + + '
' + esc(sm[1] || "") + '
'; + if (end) { + const g = (node.grants && node.grants.length) ? node.grants.map(gr => grantStr(ir, names, gr)).join(",") : "无奖励"; + h += '
' + esc(g) + '
'; + } else { + const outs = getOutlets(node); + if (outs.length > 1) + h += '
' + outs.map((o, i) => '' + (i + 1) + "·" + esc(o.label) + '').join("") + '
'; + } + return h; + } + + // ---------- 自动布局:最长路径分层 + 每层顺序铺开 ---------- + function rawTargets(n) { + const k = n.kind; + if (k === "choice" || k === "choice_once") return (n.options || []).map(o => o.goto); + if (k === "random") return (n.branches || []).map(b => b.goto); + if (k === "fight") return [n.win, n.lose]; + return [n.next]; + } + function autoCoords(ir) { + const nodes = {}; + (ir.nodes || []).forEach(n => nodes[n.id] = n); + (ir.endings || []).forEach(e => nodes[e.id] = e); + const ids = Object.keys(nodes); + const edges = []; + (ir.nodes || []).forEach(n => rawTargets(n).forEach(t => { if (t && nodes[t]) edges.push([n.id, t]); })); + const layer = {}; ids.forEach(id => layer[id] = 0); + let changed = true, guard = 0; + while (changed && guard++ < 9999) { + changed = false; + edges.forEach(([u, v]) => { if (layer[v] < layer[u] + 1) { layer[v] = layer[u] + 1; changed = true; } }); + } + const perLayer = {}, pos = {}; + ids.forEach(id => { + const l = layer[id]; perLayer[l] = perLayer[l] || 0; + // 横向分层(左→右),契合 Drawflow 端口左右朝向 + pos[id] = { x: 40 + l * COL, y: 30 + perLayer[l] * ROW }; + perLayer[l]++; + }); + return pos; + } + function ensureLayout(ir) { + const layout = ir._layout || (ir._layout = {}); + const all = (ir.nodes || []).map(n => n.id).concat((ir.endings || []).map(e => e.id)); + if (all.some(id => !layout[id])) { + const auto = autoCoords(ir); + all.forEach(id => { if (!layout[id]) layout[id] = auto[id] || { x: 40, y: 30 }; }); + } + return layout; + } + + // ---------- 连线事件 ---------- + function handleConnect(info) { + const srcIr = dfId2ir[info.output_id], dstIr = dfId2ir[info.input_id]; + if (!srcIr || !dstIr) return; + const idx = parseInt(String(info.output_class).split("_")[1], 10) - 1; + const node = findNode(srcIr); if (!node) return; + const cur = getOutlets(node)[idx]; + const old = cur && cur.target; + if (old && old !== dstIr && ir2dfId[old]) { // 单出口单目标:先拆旧线 + building = true; + try { editor.removeSingleConnection(info.output_id, ir2dfId[old], info.output_class, "input_1"); } catch (e) {} + building = false; + } + setOutlet(node, idx, dstIr); + if (cb.onConnect) cb.onConnect(); + } + function handleDisconnect(info) { + const srcIr = dfId2ir[info.output_id], dstIr = dfId2ir[info.input_id]; + if (!srcIr) return; + const idx = parseInt(String(info.output_class).split("_")[1], 10) - 1; + const node = findNode(srcIr); if (!node) return; + const cur = getOutlets(node)[idx]; + if (cur && cur.target === dstIr) { setOutlet(node, idx, ""); if (cb.onDisconnect) cb.onDisconnect(); } + } + + // ---------- 公开接口 ---------- + window.GraphUI = { + init(containerId, callbacks) { + cb = callbacks || {}; + editor = new Drawflow(document.getElementById(containerId)); + editor.start(); + editor.on("nodeSelected", id => { if (building) return; const ir = dfId2ir[id]; if (ir && cb.onSelect) cb.onSelect(ir); }); + editor.on("nodeMoved", id => { + if (building) return; + const n = editor.getNodeFromId(id), ir = dfId2ir[id]; + if (ir && cb.onMove) cb.onMove(ir, Math.round(n.pos_x), Math.round(n.pos_y)); + }); + editor.on("connectionCreated", info => { if (building) return; handleConnect(info); }); + editor.on("connectionRemoved", info => { if (building) return; handleDisconnect(info); }); + editor.on("nodeRemoved", id => { if (building) return; const ir = dfId2ir[id]; if (ir && cb.onRemove) cb.onRemove(ir); }); + // 防误删:焦点在右栏输入框时按 Del/Backspace 不应删画布选中节点 + document.addEventListener("keydown", e => { + if (e.key === "Delete" || e.key === "Backspace") { + const a = document.activeElement; + if (a && (a.tagName === "INPUT" || a.tagName === "TEXTAREA" || a.isContentEditable)) e.stopPropagation(); + } + }, true); + }, + render(ir, selectedIrId) { + _ir = ir; building = true; + editor.clear(); + dfId2ir = {}; ir2dfId = {}; + const layout = ensureLayout(ir); + const all = (ir.nodes || []).map(n => ({ node: n, end: false })) + .concat((ir.endings || []).map(e => ({ node: e, end: true }))); + all.forEach(({ node, end }) => { + const outN = end ? 0 : getOutlets(node).length; + const pos = layout[node.id] || { x: 40, y: 30 }; + const dfId = editor.addNode(node.id, 1, outN, pos.x, pos.y, + "irnode kind-" + (end ? "ending" : node.kind), { irId: node.id }, nodeInner(ir, node)); + dfId2ir[dfId] = node.id; ir2dfId[node.id] = dfId; + }); + all.forEach(({ node, end }) => { + if (end) return; + getOutlets(node).forEach((o, i) => { + if (o.target && ir2dfId[o.target]) { + try { editor.addConnection(ir2dfId[node.id], ir2dfId[o.target], "output_" + (i + 1), "input_1"); } catch (e) {} + } + }); + }); + building = false; + if (selectedIrId) this.select(selectedIrId); + }, + updateLabel(ir, irId) { + _ir = ir; + const dfId = ir2dfId[irId]; if (!dfId) return; + const node = findNode(irId); if (!node) return; + const box = document.querySelector("#node-" + dfId + " .drawflow_content_node"); + if (box) box.innerHTML = nodeInner(ir, node); + }, + select(irId) { + document.querySelectorAll("#drawflow .drawflow-node.selected").forEach(n => n.classList.remove("selected")); + const dfId = ir2dfId[irId]; + if (dfId) { const el = document.getElementById("node-" + dfId); if (el) el.classList.add("selected"); } + }, + autoLayout(ir) { ir._layout = autoCoords(ir); this.render(ir, null); }, + }; +})(); diff --git a/web/static/index.html b/web/static/index.html index ffdc2b4..ae3e044 100644 --- a/web/static/index.html +++ b/web/static/index.html @@ -4,6 +4,7 @@ Story 协作编辑器 · M5 + @@ -51,9 +52,14 @@
- +
-
+
+ + + 拖出口圆点→目标节点 = 连跳转 · 拖动摆位 · 选中按 Del 删除 +
+
从左侧选择一个事件
@@ -107,7 +113,8 @@ - + + diff --git a/web/static/style.css b/web/static/style.css index 0458c61..524f4dc 100644 --- a/web/static/style.css +++ b/web/static/style.css @@ -39,12 +39,15 @@ button.mini { padding:2px 8px; font-size:12px; } .b-confirmed{ background:#1f3a24; color:#7ad88a; } .b-discarded{ background:#3a2020; color:#d88; } -#graph-pane { flex:1; position:relative; min-width:0; } -#graph { position:absolute; inset:0; overflow:auto; padding:30px; } -#svg { position:absolute; top:0; left:0; pointer-events:none; } -#layers { position:relative; z-index:2; } +#graph-pane { flex:1; min-width:0; display:flex; flex-direction:column; position:relative; } +#graph-tools { flex:none; display:flex; align-items:center; gap:8px; padding:6px 10px; + background:#19150f; border-bottom:1px solid #3a322a; } +#graph-tools .tip { font-size:11.5px; color:#7a7264; } +#drawflow { position:relative; flex:1; min-height:0; background:#161310; + background-image:radial-gradient(#2a2419 1.1px, transparent 1.1px); + background-size:22px 22px; } .empty-center { position:absolute; inset:0; display:flex; align-items:center; - justify-content:center; color:#6a6256; font-size:15px; } + justify-content:center; color:#6a6256; font-size:15px; pointer-events:none; } #edit-pane { width:370px; background:#1c1813; border-left:1px solid #3a322a; overflow:auto; flex:none; } @@ -82,6 +85,35 @@ button.mini { padding:2px 8px; font-size:12px; } clip-path: polygon(16px 0, calc(100% - 16px) 0, 100% 50%, calc(100% - 16px) 100%, 16px 100%, 0 50%); } .node.kind-choice .k, .node.kind-choice_once .k { color:#9ec0f0; } +/* ---- Drawflow 节点(拖拽连线版) ---- */ +#drawflow .drawflow-node { background:#262019; border:1.5px solid #4a4030; border-radius:9px; + padding:8px 12px; width:192px; color:#e8e0d4; box-shadow:0 2px 6px rgba(0,0,0,.4); } +#drawflow .drawflow-node:hover { border-color:#e6c878; } +#drawflow .drawflow-node.selected { border-color:#e6c878; box-shadow:0 0 0 2px rgba(230,200,120,.45); } +#drawflow .drawflow_content_node { width:100%; } +.drawflow-node .nid { font-size:10px; color:#6a6256; } +.drawflow-node .k { font-size:11px; color:#b89a5a; font-weight:bold; } +.drawflow-node .t { font-size:12.5px; margin-top:2px; line-height:1.35; color:#ddd3c2; word-break:break-all; } +.drawflow-node .outs { margin-top:5px; display:flex; flex-direction:column; gap:2px; } +.drawflow-node .outs span { font-size:10.5px; color:#9ec0f0; } +.drawflow-node .rw { font-size:11px; color:#c9a86a; margin-top:4px; + border-top:1px dashed #6a5630; padding-top:4px; } +#drawflow .kind-ending { background:#3a2a17; border-color:#e0a850; } +#drawflow .kind-ending .k { color:#f2c463; } +#drawflow .kind-fight { border-color:#7a4a4a; background:#2a1c1c; } +#drawflow .kind-fight .k { color:#d87878; } +#drawflow .kind-out_ref { border-style:dashed; border-color:#7a7ad8; background:#1d1d2a; } +#drawflow .kind-out_ref .k { color:#9e9ef0; } +#drawflow .kind-choice, #drawflow .kind-choice_once { background:#1d2840; border-color:#3a527a; } +#drawflow .kind-choice .k, #drawflow .kind-choice_once .k { color:#9ec0f0; } +/* 端口圆点 */ +#drawflow .drawflow-node .input, #drawflow .drawflow-node .output { + background:#e6c878; border:2px solid #161310; width:15px; height:15px; } +#drawflow .drawflow-node .input:hover, #drawflow .drawflow-node .output:hover { background:#f0d68a; } +/* 连线 */ +#drawflow .connection .main-path { stroke:#7a96c8; stroke-width:2.4px; } +#drawflow .connection .main-path:hover { stroke:#e6c878; } + /* ---- form ---- */ .fld { margin:9px 0; } .fld > label { display:block; font-size:12px; color:#9a8f7e; margin-bottom:3px; } diff --git a/web/static/tree.js b/web/static/tree.js deleted file mode 100644 index fd5e4cd..0000000 --- a/web/static/tree.js +++ /dev/null @@ -1,157 +0,0 @@ -// 分支树渲染(从 ir_to_html.py 的 TEMPLATE 抽出,加 onSelect 回调供编辑)。 -// 用法: renderTree(ir, { onSelect:id=>{}, selected:'n1' }) - -(function () { - const ROW = 150, SP = 232, NW = 188; - const COLOR = { next:"#6a6256", option:"#7aa0d8", random:"#a07ad8", - win:"#7ac88a", lose:"#d87878", ref:"#9e9ef0" }; - - function roleNames(ir) { - const m = {}; - (ir.roles || []).forEach(r => m[r.slot] = r.name + (r.archetype ? ("〔" + r.archetype + "〕") : "")); - return m; - } - function nameOf(ir, names, s) { return (s === "P1" ? "玩家" : "") || names[s] || s; } - - function grantStr(ir, names, gr) { - if (gr.kind === "银两") return "银两 " + (gr.value > 0 ? "+" : "") + gr.value; - if (gr.kind === "道具") return "道具 " + gr.item + " ×" + gr.value; - if (gr.kind === "友好度") return nameOf(ir, names, gr.target) + " 友好度+" + gr.value; - if (gr.kind === "入门") return nameOf(ir, names, gr.target) + " 加入门派"; - return JSON.stringify(gr); - } - - function summary(ir, names, n) { - if (n.kind === "narration") return ["旁白", (n.text || "").slice(0, 28)]; - if (n.kind === "dialogue") return ["对话 · " + nameOf(ir, names, n.speaker), (n.text || "").slice(0, 24)]; - if (n.kind === "choice") return ["选择 (" + (n.options || []).length + "项)", (n.options || []).map(o => o.text).join(" / ").slice(0, 30)]; - if (n.kind === "choice_once") return ["一次性选择", (n.options || []).map(o => o.text).join(" / ").slice(0, 30)]; - if (n.kind === "random") return ["随机分支", (n.branches || []).length + " 路"]; - if (n.kind === "fight") return ["战斗", "vs " + (n.camp2 || []).map(s => nameOf(ir, names, s)).join("、")]; - if (n.kind === "move") return ["走位 · " + nameOf(ir, names, n.actor), "→ " + (n.to || "")]; - if (n.kind === "anim") return ["动画 · " + nameOf(ir, names, n.actor), n.ani || ""]; - if (n.kind === "reward") return ["奖励结算", ""]; - if (n.kind === "out_ref") return ["引用子序列", "→ " + (n.ref || "")]; - if (n.kind === "ending") return ["★ 结局", n.summary || ""]; - return [n.kind, ""]; - } - - window.renderTree = function (ir, opts) { - opts = opts || {}; - const names = roleNames(ir); - const layersDiv = document.getElementById("layers"); - const svg = document.getElementById("svg"); - layersDiv.innerHTML = ""; svg.innerHTML = ""; - - // 节点 (含结局) - const nodes = {}; - (ir.nodes || []).forEach(n => nodes[n.id] = Object.assign({ _end: false }, n)); - (ir.endings || []).forEach(e => nodes[e.id] = Object.assign({ _end: true, kind: "ending" }, e)); - - // 边 - const edges = []; - const add = (u, v, type, label) => { if (v && nodes[v]) edges.push({ u, v, type, label: label || "" }); }; - (ir.nodes || []).forEach(n => { - if (n.next) add(n.id, n.next, n.kind === "out_ref" ? "ref" : "next"); - (n.options || []).forEach(o => add(n.id, o.goto, "option", o.text)); - (n.branches || []).forEach(b => add(n.id, b.goto, "random", "权重" + (b.weight != null ? b.weight : ""))); - if (n.kind === "fight") { add(n.id, n.win, "win", "胜"); add(n.id, n.lose, "lose", "败"); } - }); - - // 最长路径分层 - const layer = {}; Object.keys(nodes).forEach(id => layer[id] = 0); - let changed = true, guard = 0; - while (changed && guard++ < 999) { - changed = false; - edges.forEach(e => { if (layer[e.v] < layer[e.u] + 1) { layer[e.v] = layer[e.u] + 1; changed = true; } }); - } - - // 子树居中布局 - const childMap = {}; Object.keys(nodes).forEach(id => childMap[id] = []); - const indeg = {}; Object.keys(nodes).forEach(id => indeg[id] = 0); - const seenE = new Set(); - edges.forEach(e => { const k = e.u + ">" + e.v; if (!seenE.has(k)) { seenE.add(k); childMap[e.u].push(e.v); indeg[e.v]++; } }); - let roots = Object.keys(nodes).filter(id => indeg[id] === 0); - if (!roots.length) roots = [Object.keys(nodes)[0]]; - const xpos = {}; let nextX = 0; const vis = new Set(); - function assignX(id) { - if (!id || vis.has(id)) return; vis.add(id); - if (childMap[id].length === 0) { xpos[id] = nextX; nextX += SP; return; } - childMap[id].forEach(assignX); - const placed = childMap[id].map(c => xpos[c]).filter(v => v !== undefined); - xpos[id] = placed.length ? (Math.min(...placed) + Math.max(...placed)) / 2 : (nextX += SP, nextX - SP); - } - roots.forEach(assignX); - Object.keys(nodes).forEach(id => { if (xpos[id] === undefined) { xpos[id] = nextX; nextX += SP; } }); - - let maxX = 0, maxL = 0; - Object.keys(nodes).forEach(id => { - const n = nodes[id], [k, t] = summary(ir, names, n); - const d = document.createElement("div"); - d.className = "node kind-" + n.kind + (id === opts.selected ? " sel" : ""); - d.id = "node-" + id; - let inner = '
' + k + '
' + esc(t || id) + '
'; - if (n.kind === "ending") { - const g = (n.grants && n.grants.length) ? n.grants.map(gr => grantStr(ir, names, gr)).join(",") : "无奖励"; - inner += '
' + esc(g) + '
'; - } - d.innerHTML = inner; - d.style.left = xpos[id] + "px"; d.style.top = (layer[id] * ROW) + "px"; - d.onclick = () => opts.onSelect && opts.onSelect(id); - layersDiv.appendChild(d); - // 选中节点:浮出快捷按钮。作为画布独立元素按坐标定位,避免被 choice 等节点的 clip-path 裁切。 - if (id === opts.selected) { - const bar = document.createElement("div"); - bar.className = "node-acts"; - bar.style.left = (xpos[id] + NW + 2) + "px"; // 右缘对齐节点右上角 - bar.style.top = (layer[id] * ROW - 13) + "px"; - const mk = (cls, label, title, fn) => { - const b = document.createElement("button"); - b.className = "nact " + cls; b.textContent = label; b.title = title; - b.onclick = e => { e.stopPropagation(); fn(id); }; - return b; - }; - if (opts.onAddNext && !n._end) bar.appendChild(mk("add", "+后继", "新建一个节点并自动接到这里", opts.onAddNext)); - if (opts.onDelete) bar.appendChild(mk("del", "✕", n._end ? "删除此结局" : "删除此节点", opts.onDelete)); - layersDiv.appendChild(bar); - } - maxX = Math.max(maxX, xpos[id]); maxL = Math.max(maxL, layer[id]); - }); - layersDiv.style.width = (maxX + NW + 40) + "px"; - layersDiv.style.height = (maxL * ROW + 180) + "px"; - - drawEdges(edges); - }; - - function drawEdges(edges) { - const g = document.getElementById("graph"), svg = document.getElementById("svg"); - const gb = g.getBoundingClientRect(); - svg.setAttribute("width", g.scrollWidth); svg.setAttribute("height", g.scrollHeight); - let h = ''; - Object.entries(COLOR).forEach(([k, c]) => { - h += ''; - }); - h += ''; - edges.forEach(e => { - const a = document.getElementById("node-" + e.u), b = document.getElementById("node-" + e.v); - if (!a || !b) return; - const ra = a.getBoundingClientRect(), rb = b.getBoundingClientRect(); - const x1 = ra.left - gb.left + g.scrollLeft + ra.width / 2, y1 = ra.bottom - gb.top + g.scrollTop; - const x2 = rb.left - gb.left + g.scrollLeft + rb.width / 2, y2 = rb.top - gb.top + g.scrollTop; - const c = COLOR[e.type] || "#6a6256", my = (y1 + y2) / 2; - h += ''; - if (e.label) { - const t = 0.8, mt = 1 - t; - const lx = mt * mt * mt * x1 + 3 * mt * mt * t * x1 + 3 * mt * t * t * x2 + t * t * t * x2; - const ly = mt * mt * mt * y1 + 3 * mt * mt * t * my + 3 * mt * t * t * my + t * t * t * y2; - h += '' + esc(e.label.slice(0, 12)) + ''; - } - }); - svg.innerHTML = h; - } - - function esc(s) { return String(s).replace(/&/g, "&").replace(//g, ">"); } - window._treeGrantStr = grantStr; - window._treeNameOf = nameOf; - window._treeRoleNames = roleNames; -})(); diff --git a/web/static/vendor/drawflow.min.css b/web/static/vendor/drawflow.min.css new file mode 100644 index 0000000..c9c9442 --- /dev/null +++ b/web/static/vendor/drawflow.min.css @@ -0,0 +1 @@ +.drawflow,.drawflow .parent-node{position:relative}.parent-drawflow{display:flex;overflow:hidden;touch-action:none;outline:0}.drawflow{width:100%;height:100%;user-select:none;perspective:0}.drawflow .drawflow-node{display:flex;align-items:center;position:absolute;background:#0ff;width:160px;min-height:40px;border-radius:4px;border:2px solid #000;color:#000;z-index:2;padding:15px}.drawflow .drawflow-node.selected{background:red}.drawflow .drawflow-node:hover{cursor:move}.drawflow .drawflow-node .inputs,.drawflow .drawflow-node .outputs{width:0}.drawflow .drawflow-node .drawflow_content_node{width:100%;display:block}.drawflow .drawflow-node .input,.drawflow .drawflow-node .output{position:relative;width:20px;height:20px;background:#fff;border-radius:50%;border:2px solid #000;cursor:crosshair;z-index:1;margin-bottom:5px}.drawflow .drawflow-node .input{left:-27px;top:2px;background:#ff0}.drawflow .drawflow-node .output{right:-3px;top:2px}.drawflow svg{z-index:0;position:absolute;overflow:visible!important}.drawflow .connection{position:absolute;pointer-events:none;aspect-ratio:1/1}.drawflow .connection .main-path{fill:none;stroke-width:5px;stroke:#4682b4;pointer-events:all}.drawflow .connection .main-path:hover{stroke:#1266ab;cursor:pointer}.drawflow .connection .main-path.selected{stroke:#43b993}.drawflow .connection .point{cursor:move;stroke:#000;stroke-width:2;fill:#fff;pointer-events:all}.drawflow .connection .point.selected,.drawflow .connection .point:hover{fill:#1266ab}.drawflow .main-path{fill:none;stroke-width:5px;stroke:#4682b4}.drawflow-delete{position:absolute;display:block;width:30px;height:30px;background:#000;color:#fff;z-index:4;border:2px solid #fff;line-height:30px;font-weight:700;text-align:center;border-radius:50%;font-family:monospace;cursor:pointer}.drawflow>.drawflow-delete{margin-left:-15px;margin-top:15px}.parent-node .drawflow-delete{right:-15px;top:-15px} \ No newline at end of file diff --git a/web/static/vendor/drawflow.min.js b/web/static/vendor/drawflow.min.js new file mode 100644 index 0000000..ebdcbdd --- /dev/null +++ b/web/static/vendor/drawflow.min.js @@ -0,0 +1 @@ +!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.Drawflow=t():e.Drawflow=t()}("undefined"!=typeof self?self:this,(function(){return function(e){var t={};function n(i){if(t[i])return t[i].exports;var s=t[i]={i:i,l:!1,exports:{}};return e[i].call(s.exports,s,s.exports,n),s.l=!0,s.exports}return n.m=e,n.c=t,n.d=function(e,t,i){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:i})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var i=Object.create(null);if(n.r(i),Object.defineProperty(i,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var s in e)n.d(i,s,function(t){return e[t]}.bind(null,s));return i},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=0)}([function(e,t,n){"use strict";n.r(t),n.d(t,"default",(function(){return i}));class i{constructor(e,t=null,n=null){this.events={},this.container=e,this.precanvas=null,this.nodeId=1,this.ele_selected=null,this.node_selected=null,this.drag=!1,this.reroute=!1,this.reroute_fix_curvature=!1,this.curvature=.5,this.reroute_curvature_start_end=.5,this.reroute_curvature=.5,this.reroute_width=6,this.drag_point=!1,this.editor_selected=!1,this.connection=!1,this.connection_ele=null,this.connection_selected=null,this.canvas_x=0,this.canvas_y=0,this.pos_x=0,this.pos_x_start=0,this.pos_y=0,this.pos_y_start=0,this.mouse_x=0,this.mouse_y=0,this.line_path=5,this.first_click=null,this.force_first_input=!1,this.draggable_inputs=!0,this.useuuid=!1,this.parent=n,this.noderegister={},this.render=t,this.drawflow={drawflow:{Home:{data:{}}}},this.module="Home",this.editor_mode="edit",this.zoom=1,this.zoom_max=1.6,this.zoom_min=.5,this.zoom_value=.1,this.zoom_last_value=1,this.evCache=new Array,this.prevDiff=-1}start(){this.container.classList.add("parent-drawflow"),this.container.tabIndex=0,this.precanvas=document.createElement("div"),this.precanvas.classList.add("drawflow"),this.container.appendChild(this.precanvas),this.container.addEventListener("mouseup",this.dragEnd.bind(this)),this.container.addEventListener("mousemove",this.position.bind(this)),this.container.addEventListener("mousedown",this.click.bind(this)),this.container.addEventListener("touchend",this.dragEnd.bind(this)),this.container.addEventListener("touchmove",this.position.bind(this)),this.container.addEventListener("touchstart",this.click.bind(this)),this.container.addEventListener("contextmenu",this.contextmenu.bind(this)),this.container.addEventListener("keydown",this.key.bind(this)),this.container.addEventListener("wheel",this.zoom_enter.bind(this)),this.container.addEventListener("input",this.updateNodeValue.bind(this)),this.container.addEventListener("dblclick",this.dblclick.bind(this)),this.container.onpointerdown=this.pointerdown_handler.bind(this),this.container.onpointermove=this.pointermove_handler.bind(this),this.container.onpointerup=this.pointerup_handler.bind(this),this.container.onpointercancel=this.pointerup_handler.bind(this),this.container.onpointerout=this.pointerup_handler.bind(this),this.container.onpointerleave=this.pointerup_handler.bind(this),this.load()}pointerdown_handler(e){this.evCache.push(e)}pointermove_handler(e){for(var t=0;t100&&(n>this.prevDiff&&this.zoom_in(),n=n&&(n=parseInt(e)+1)}))})),this.nodeId=n}removeReouteConnectionSelected(){this.dispatch("connectionUnselected",!0),this.reroute_fix_curvature&&this.connection_selected.parentElement.querySelectorAll(".main-path").forEach((e,t)=>{e.classList.remove("selected")})}click(e){if(this.dispatch("click",e),"fixed"===this.editor_mode){if("parent-drawflow"!==e.target.classList[0]&&"drawflow"!==e.target.classList[0])return!1;this.ele_selected=e.target.closest(".parent-drawflow"),e.preventDefault()}else"view"===this.editor_mode?(null!=e.target.closest(".drawflow")||e.target.matches(".parent-drawflow"))&&(this.ele_selected=e.target.closest(".parent-drawflow"),e.preventDefault()):(this.first_click=e.target,this.ele_selected=e.target,0===e.button&&this.contextmenuDel(),null!=e.target.closest(".drawflow_content_node")&&(this.ele_selected=e.target.closest(".drawflow_content_node").parentElement));switch(this.ele_selected.classList[0]){case"drawflow-node":null!=this.node_selected&&(this.node_selected.classList.remove("selected"),this.node_selected!=this.ele_selected&&this.dispatch("nodeUnselected",!0)),null!=this.connection_selected&&(this.connection_selected.classList.remove("selected"),this.removeReouteConnectionSelected(),this.connection_selected=null),this.node_selected!=this.ele_selected&&this.dispatch("nodeSelected",this.ele_selected.id.slice(5)),this.node_selected=this.ele_selected,this.node_selected.classList.add("selected"),this.draggable_inputs?"SELECT"!==e.target.tagName&&(this.drag=!0):"INPUT"!==e.target.tagName&&"TEXTAREA"!==e.target.tagName&&"SELECT"!==e.target.tagName&&!0!==e.target.hasAttribute("contenteditable")&&(this.drag=!0);break;case"output":this.connection=!0,null!=this.node_selected&&(this.node_selected.classList.remove("selected"),this.node_selected=null,this.dispatch("nodeUnselected",!0)),null!=this.connection_selected&&(this.connection_selected.classList.remove("selected"),this.removeReouteConnectionSelected(),this.connection_selected=null),this.drawConnection(e.target);break;case"parent-drawflow":case"drawflow":null!=this.node_selected&&(this.node_selected.classList.remove("selected"),this.node_selected=null,this.dispatch("nodeUnselected",!0)),null!=this.connection_selected&&(this.connection_selected.classList.remove("selected"),this.removeReouteConnectionSelected(),this.connection_selected=null),this.editor_selected=!0;break;case"main-path":null!=this.node_selected&&(this.node_selected.classList.remove("selected"),this.node_selected=null,this.dispatch("nodeUnselected",!0)),null!=this.connection_selected&&(this.connection_selected.classList.remove("selected"),this.removeReouteConnectionSelected(),this.connection_selected=null),this.connection_selected=this.ele_selected,this.connection_selected.classList.add("selected");const t=this.connection_selected.parentElement.classList;t.length>1&&(this.dispatch("connectionSelected",{output_id:t[2].slice(14),input_id:t[1].slice(13),output_class:t[3],input_class:t[4]}),this.reroute_fix_curvature&&this.connection_selected.parentElement.querySelectorAll(".main-path").forEach((e,t)=>{e.classList.add("selected")}));break;case"point":this.drag_point=!0,this.ele_selected.classList.add("selected");break;case"drawflow-delete":this.node_selected&&this.removeNodeId(this.node_selected.id),this.connection_selected&&this.removeConnection(),null!=this.node_selected&&(this.node_selected.classList.remove("selected"),this.node_selected=null,this.dispatch("nodeUnselected",!0)),null!=this.connection_selected&&(this.connection_selected.classList.remove("selected"),this.removeReouteConnectionSelected(),this.connection_selected=null)}"touchstart"===e.type?(this.pos_x=e.touches[0].clientX,this.pos_x_start=e.touches[0].clientX,this.pos_y=e.touches[0].clientY,this.pos_y_start=e.touches[0].clientY,this.mouse_x=e.touches[0].clientX,this.mouse_y=e.touches[0].clientY):(this.pos_x=e.clientX,this.pos_x_start=e.clientX,this.pos_y=e.clientY,this.pos_y_start=e.clientY),["input","output","main-path"].includes(this.ele_selected.classList[0])&&e.preventDefault(),this.dispatch("clickEnd",e)}position(e){if("touchmove"===e.type)var t=e.touches[0].clientX,n=e.touches[0].clientY;else t=e.clientX,n=e.clientY;if(this.connection&&this.updateConnection(t,n),this.editor_selected&&(i=this.canvas_x+-(this.pos_x-t),s=this.canvas_y+-(this.pos_y-n),this.dispatch("translate",{x:i,y:s}),this.precanvas.style.transform="translate("+i+"px, "+s+"px) scale("+this.zoom+")"),this.drag){e.preventDefault();var i=(this.pos_x-t)*this.precanvas.clientWidth/(this.precanvas.clientWidth*this.zoom),s=(this.pos_y-n)*this.precanvas.clientHeight/(this.precanvas.clientHeight*this.zoom);this.pos_x=t,this.pos_y=n,this.ele_selected.style.top=this.ele_selected.offsetTop-s+"px",this.ele_selected.style.left=this.ele_selected.offsetLeft-i+"px",this.drawflow.drawflow[this.module].data[this.ele_selected.id.slice(5)].pos_x=this.ele_selected.offsetLeft-i,this.drawflow.drawflow[this.module].data[this.ele_selected.id.slice(5)].pos_y=this.ele_selected.offsetTop-s,this.updateConnectionNodes(this.ele_selected.id)}if(this.drag_point){i=(this.pos_x-t)*this.precanvas.clientWidth/(this.precanvas.clientWidth*this.zoom),s=(this.pos_y-n)*this.precanvas.clientHeight/(this.precanvas.clientHeight*this.zoom);this.pos_x=t,this.pos_y=n;var o=this.pos_x*(this.precanvas.clientWidth/(this.precanvas.clientWidth*this.zoom))-this.precanvas.getBoundingClientRect().x*(this.precanvas.clientWidth/(this.precanvas.clientWidth*this.zoom)),l=this.pos_y*(this.precanvas.clientHeight/(this.precanvas.clientHeight*this.zoom))-this.precanvas.getBoundingClientRect().y*(this.precanvas.clientHeight/(this.precanvas.clientHeight*this.zoom));this.ele_selected.setAttributeNS(null,"cx",o),this.ele_selected.setAttributeNS(null,"cy",l);const e=this.ele_selected.parentElement.classList[2].slice(9),c=this.ele_selected.parentElement.classList[1].slice(13),d=this.ele_selected.parentElement.classList[3],a=this.ele_selected.parentElement.classList[4];let r=Array.from(this.ele_selected.parentElement.children).indexOf(this.ele_selected)-1;if(this.reroute_fix_curvature){r-=this.ele_selected.parentElement.querySelectorAll(".main-path").length-1,r<0&&(r=0)}const h=e.slice(5),u=this.drawflow.drawflow[this.module].data[h].outputs[d].connections.findIndex((function(e,t){return e.node===c&&e.output===a}));this.drawflow.drawflow[this.module].data[h].outputs[d].connections[u].points[r]={pos_x:o,pos_y:l};const p=this.ele_selected.parentElement.classList[2].slice(9);this.updateConnectionNodes(p)}"touchmove"===e.type&&(this.mouse_x=t,this.mouse_y=n),this.dispatch("mouseMove",{x:t,y:n})}dragEnd(e){if("touchend"===e.type)var t=this.mouse_x,n=this.mouse_y,i=document.elementFromPoint(t,n);else t=e.clientX,n=e.clientY,i=e.target;if(this.drag&&(this.pos_x_start==t&&this.pos_y_start==n||this.dispatch("nodeMoved",this.ele_selected.id.slice(5))),this.drag_point&&(this.ele_selected.classList.remove("selected"),this.pos_x_start==t&&this.pos_y_start==n||this.dispatch("rerouteMoved",this.ele_selected.parentElement.classList[2].slice(14))),this.editor_selected&&(this.canvas_x=this.canvas_x+-(this.pos_x-t),this.canvas_y=this.canvas_y+-(this.pos_y-n),this.editor_selected=!1),!0===this.connection)if("input"===i.classList[0]||this.force_first_input&&(null!=i.closest(".drawflow_content_node")||"drawflow-node"===i.classList[0])){if(!this.force_first_input||null==i.closest(".drawflow_content_node")&&"drawflow-node"!==i.classList[0])s=i.parentElement.parentElement.id,o=i.classList[1];else{if(null!=i.closest(".drawflow_content_node"))var s=i.closest(".drawflow_content_node").parentElement.id;else var s=i.id;if(0===Object.keys(this.getNodeFromId(s.slice(5)).inputs).length)var o=!1;else var o="input_1"}var l=this.ele_selected.parentElement.parentElement.id,c=this.ele_selected.classList[1];if(l!==s&&!1!==o){if(0===this.container.querySelectorAll(".connection.node_in_"+s+".node_out_"+l+"."+c+"."+o).length){this.connection_ele.classList.add("node_in_"+s),this.connection_ele.classList.add("node_out_"+l),this.connection_ele.classList.add(c),this.connection_ele.classList.add(o);var d=s.slice(5),a=l.slice(5);this.drawflow.drawflow[this.module].data[a].outputs[c].connections.push({node:d,output:o}),this.drawflow.drawflow[this.module].data[d].inputs[o].connections.push({node:a,input:c}),this.updateConnectionNodes("node-"+a),this.updateConnectionNodes("node-"+d),this.dispatch("connectionCreated",{output_id:a,input_id:d,output_class:c,input_class:o})}else this.dispatch("connectionCancel",!0),this.connection_ele.remove();this.connection_ele=null}else this.dispatch("connectionCancel",!0),this.connection_ele.remove(),this.connection_ele=null}else this.dispatch("connectionCancel",!0),this.connection_ele.remove(),this.connection_ele=null;this.drag=!1,this.drag_point=!1,this.connection=!1,this.ele_selected=null,this.editor_selected=!1,this.dispatch("mouseUp",e)}contextmenu(e){if(this.dispatch("contextmenu",e),e.preventDefault(),"fixed"===this.editor_mode||"view"===this.editor_mode)return!1;if(this.precanvas.getElementsByClassName("drawflow-delete").length&&this.precanvas.getElementsByClassName("drawflow-delete")[0].remove(),this.node_selected||this.connection_selected){var t=document.createElement("div");t.classList.add("drawflow-delete"),t.innerHTML="x",this.node_selected&&this.node_selected.appendChild(t),this.connection_selected&&this.connection_selected.parentElement.classList.length>1&&(t.style.top=e.clientY*(this.precanvas.clientHeight/(this.precanvas.clientHeight*this.zoom))-this.precanvas.getBoundingClientRect().y*(this.precanvas.clientHeight/(this.precanvas.clientHeight*this.zoom))+"px",t.style.left=e.clientX*(this.precanvas.clientWidth/(this.precanvas.clientWidth*this.zoom))-this.precanvas.getBoundingClientRect().x*(this.precanvas.clientWidth/(this.precanvas.clientWidth*this.zoom))+"px",this.precanvas.appendChild(t))}}contextmenuDel(){this.precanvas.getElementsByClassName("drawflow-delete").length&&this.precanvas.getElementsByClassName("drawflow-delete")[0].remove()}key(e){if(this.dispatch("keydown",e),"fixed"===this.editor_mode||"view"===this.editor_mode)return!1;("Delete"===e.key||"Backspace"===e.key&&e.metaKey)&&(null!=this.node_selected&&"INPUT"!==this.first_click.tagName&&"TEXTAREA"!==this.first_click.tagName&&!0!==this.first_click.hasAttribute("contenteditable")&&this.removeNodeId(this.node_selected.id),null!=this.connection_selected&&this.removeConnection())}zoom_enter(e,t){e.ctrlKey&&(e.preventDefault(),e.deltaY>0?this.zoom_out():this.zoom_in())}zoom_refresh(){this.dispatch("zoom",this.zoom),this.canvas_x=this.canvas_x/this.zoom_last_value*this.zoom,this.canvas_y=this.canvas_y/this.zoom_last_value*this.zoom,this.zoom_last_value=this.zoom,this.precanvas.style.transform="translate("+this.canvas_x+"px, "+this.canvas_y+"px) scale("+this.zoom+")"}zoom_in(){this.zoomthis.zoom_min&&(this.zoom-=this.zoom_value,this.zoom_refresh())}zoom_reset(){1!=this.zoom&&(this.zoom=1,this.zoom_refresh())}createCurvature(e,t,n,i,s,o){var l=e,c=t,d=n,a=i,r=s;switch(o){case"open":if(e>=n)var h=l+Math.abs(d-l)*r,u=d-Math.abs(d-l)*(-1*r);else h=l+Math.abs(d-l)*r,u=d-Math.abs(d-l)*r;return" M "+l+" "+c+" C "+h+" "+c+" "+u+" "+a+" "+d+" "+a;case"close":if(e>=n)h=l+Math.abs(d-l)*(-1*r),u=d-Math.abs(d-l)*r;else h=l+Math.abs(d-l)*r,u=d-Math.abs(d-l)*r;return" M "+l+" "+c+" C "+h+" "+c+" "+u+" "+a+" "+d+" "+a;case"other":if(e>=n)h=l+Math.abs(d-l)*(-1*r),u=d-Math.abs(d-l)*(-1*r);else h=l+Math.abs(d-l)*r,u=d-Math.abs(d-l)*r;return" M "+l+" "+c+" C "+h+" "+c+" "+u+" "+a+" "+d+" "+a;default:return" M "+l+" "+c+" C "+(h=l+Math.abs(d-l)*r)+" "+c+" "+(u=d-Math.abs(d-l)*r)+" "+a+" "+d+" "+a}}drawConnection(e){var t=document.createElementNS("http://www.w3.org/2000/svg","svg");this.connection_ele=t;var n=document.createElementNS("http://www.w3.org/2000/svg","path");n.classList.add("main-path"),n.setAttributeNS(null,"d",""),t.classList.add("connection"),t.appendChild(n),this.precanvas.appendChild(t);var i=e.parentElement.parentElement.id.slice(5),s=e.classList[1];this.dispatch("connectionStart",{output_id:i,output_class:s})}updateConnection(e,t){const n=this.precanvas,i=this.zoom;let s=n.clientWidth/(n.clientWidth*i);s=s||0;let o=n.clientHeight/(n.clientHeight*i);o=o||0;var l=this.connection_ele.children[0],c=this.ele_selected.offsetWidth/2+(this.ele_selected.getBoundingClientRect().x-n.getBoundingClientRect().x)*s,d=this.ele_selected.offsetHeight/2+(this.ele_selected.getBoundingClientRect().y-n.getBoundingClientRect().y)*o,a=e*(this.precanvas.clientWidth/(this.precanvas.clientWidth*this.zoom))-this.precanvas.getBoundingClientRect().x*(this.precanvas.clientWidth/(this.precanvas.clientWidth*this.zoom)),r=t*(this.precanvas.clientHeight/(this.precanvas.clientHeight*this.zoom))-this.precanvas.getBoundingClientRect().y*(this.precanvas.clientHeight/(this.precanvas.clientHeight*this.zoom)),h=this.curvature,u=this.createCurvature(c,d,a,r,h,"openclose");l.setAttributeNS(null,"d",u)}addConnection(e,t,n,i){var s=this.getModuleFromNodeId(e);if(s===this.getModuleFromNodeId(t)){var o=this.getNodeFromId(e),l=!1;for(var c in o.outputs[n].connections){var d=o.outputs[n].connections[c];d.node==t&&d.output==i&&(l=!0)}if(!1===l){if(this.drawflow.drawflow[s].data[e].outputs[n].connections.push({node:t.toString(),output:i}),this.drawflow.drawflow[s].data[t].inputs[i].connections.push({node:e.toString(),input:n}),this.module===s){var a=document.createElementNS("http://www.w3.org/2000/svg","svg"),r=document.createElementNS("http://www.w3.org/2000/svg","path");r.classList.add("main-path"),r.setAttributeNS(null,"d",""),a.classList.add("connection"),a.classList.add("node_in_node-"+t),a.classList.add("node_out_node-"+e),a.classList.add(n),a.classList.add(i),a.appendChild(r),this.precanvas.appendChild(a),this.updateConnectionNodes("node-"+e),this.updateConnectionNodes("node-"+t)}this.dispatch("connectionCreated",{output_id:e,input_id:t,output_class:n,input_class:i})}}}updateConnectionNodes(e){const t="node_in_"+e,n="node_out_"+e;this.line_path;const i=this.container,s=this.precanvas,o=this.curvature,l=this.createCurvature,c=this.reroute_curvature,d=this.reroute_curvature_start_end,a=this.reroute_fix_curvature,r=this.reroute_width,h=this.zoom;let u=s.clientWidth/(s.clientWidth*h);u=u||0;let p=s.clientHeight/(s.clientHeight*h);p=p||0;const f=i.querySelectorAll("."+n);Object.keys(f).map((function(t,n){if(null===f[t].querySelector(".point")){var m=i.querySelector("#"+e),g=f[t].classList[1].replace("node_in_",""),_=i.querySelector("#"+g).querySelectorAll("."+f[t].classList[4])[0],w=_.offsetWidth/2+(_.getBoundingClientRect().x-s.getBoundingClientRect().x)*u,v=_.offsetHeight/2+(_.getBoundingClientRect().y-s.getBoundingClientRect().y)*p,y=m.querySelectorAll("."+f[t].classList[3])[0],C=y.offsetWidth/2+(y.getBoundingClientRect().x-s.getBoundingClientRect().x)*u,x=y.offsetHeight/2+(y.getBoundingClientRect().y-s.getBoundingClientRect().y)*p;const n=l(C,x,w,v,o,"openclose");f[t].children[0].setAttributeNS(null,"d",n)}else{const n=f[t].querySelectorAll(".point");let o="";const m=[];n.forEach((t,a)=>{if(0===a&&n.length-1==0){var f=i.querySelector("#"+e),g=((x=t).getBoundingClientRect().x-s.getBoundingClientRect().x)*u+r,_=(x.getBoundingClientRect().y-s.getBoundingClientRect().y)*p+r,w=(L=f.querySelectorAll("."+t.parentElement.classList[3])[0]).offsetWidth/2+(L.getBoundingClientRect().x-s.getBoundingClientRect().x)*u,v=L.offsetHeight/2+(L.getBoundingClientRect().y-s.getBoundingClientRect().y)*p,y=l(w,v,g,_,d,"open");o+=y,m.push(y);f=t;var C=t.parentElement.classList[1].replace("node_in_",""),x=(E=i.querySelector("#"+C)).querySelectorAll("."+t.parentElement.classList[4])[0];g=(R=E.querySelectorAll("."+t.parentElement.classList[4])[0]).offsetWidth/2+(R.getBoundingClientRect().x-s.getBoundingClientRect().x)*u,_=R.offsetHeight/2+(R.getBoundingClientRect().y-s.getBoundingClientRect().y)*p,w=(f.getBoundingClientRect().x-s.getBoundingClientRect().x)*u+r,v=(f.getBoundingClientRect().y-s.getBoundingClientRect().y)*p+r,y=l(w,v,g,_,d,"close");o+=y,m.push(y)}else if(0===a){var L;f=i.querySelector("#"+e),g=((x=t).getBoundingClientRect().x-s.getBoundingClientRect().x)*u+r,_=(x.getBoundingClientRect().y-s.getBoundingClientRect().y)*p+r,w=(L=f.querySelectorAll("."+t.parentElement.classList[3])[0]).offsetWidth/2+(L.getBoundingClientRect().x-s.getBoundingClientRect().x)*u,v=L.offsetHeight/2+(L.getBoundingClientRect().y-s.getBoundingClientRect().y)*p,y=l(w,v,g,_,d,"open");o+=y,m.push(y);f=t,g=((x=n[a+1]).getBoundingClientRect().x-s.getBoundingClientRect().x)*u+r,_=(x.getBoundingClientRect().y-s.getBoundingClientRect().y)*p+r,w=(f.getBoundingClientRect().x-s.getBoundingClientRect().x)*u+r,v=(f.getBoundingClientRect().y-s.getBoundingClientRect().y)*p+r,y=l(w,v,g,_,c,"other");o+=y,m.push(y)}else if(a===n.length-1){var E,R;f=t,C=t.parentElement.classList[1].replace("node_in_",""),x=(E=i.querySelector("#"+C)).querySelectorAll("."+t.parentElement.classList[4])[0],g=(R=E.querySelectorAll("."+t.parentElement.classList[4])[0]).offsetWidth/2+(R.getBoundingClientRect().x-s.getBoundingClientRect().x)*u,_=R.offsetHeight/2+(R.getBoundingClientRect().y-s.getBoundingClientRect().y)*p,w=(f.getBoundingClientRect().x-s.getBoundingClientRect().x)*(s.clientWidth/(s.clientWidth*h))+r,v=(f.getBoundingClientRect().y-s.getBoundingClientRect().y)*(s.clientHeight/(s.clientHeight*h))+r,y=l(w,v,g,_,d,"close");o+=y,m.push(y)}else{f=t,g=((x=n[a+1]).getBoundingClientRect().x-s.getBoundingClientRect().x)*(s.clientWidth/(s.clientWidth*h))+r,_=(x.getBoundingClientRect().y-s.getBoundingClientRect().y)*(s.clientHeight/(s.clientHeight*h))+r,w=(f.getBoundingClientRect().x-s.getBoundingClientRect().x)*(s.clientWidth/(s.clientWidth*h))+r,v=(f.getBoundingClientRect().y-s.getBoundingClientRect().y)*(s.clientHeight/(s.clientHeight*h))+r,y=l(w,v,g,_,c,"other");o+=y,m.push(y)}}),a?m.forEach((e,n)=>{f[t].children[n].setAttributeNS(null,"d",e)}):f[t].children[0].setAttributeNS(null,"d",o)}}));const m=i.querySelectorAll("."+t);Object.keys(m).map((function(t,n){if(null===m[t].querySelector(".point")){var h=i.querySelector("#"+e),f=m[t].classList[2].replace("node_out_",""),g=i.querySelector("#"+f).querySelectorAll("."+m[t].classList[3])[0],_=g.offsetWidth/2+(g.getBoundingClientRect().x-s.getBoundingClientRect().x)*u,w=g.offsetHeight/2+(g.getBoundingClientRect().y-s.getBoundingClientRect().y)*p,v=(h=h.querySelectorAll("."+m[t].classList[4])[0]).offsetWidth/2+(h.getBoundingClientRect().x-s.getBoundingClientRect().x)*u,y=h.offsetHeight/2+(h.getBoundingClientRect().y-s.getBoundingClientRect().y)*p;const n=l(_,w,v,y,o,"openclose");m[t].children[0].setAttributeNS(null,"d",n)}else{const n=m[t].querySelectorAll(".point");let o="";const h=[];n.forEach((t,a)=>{if(0===a&&n.length-1==0){var f=i.querySelector("#"+e),m=((C=t).getBoundingClientRect().x-s.getBoundingClientRect().x)*u+r,g=(C.getBoundingClientRect().y-s.getBoundingClientRect().y)*p+r,_=(E=f.querySelectorAll("."+t.parentElement.classList[4])[0]).offsetWidth/2+(E.getBoundingClientRect().x-s.getBoundingClientRect().x)*u,w=E.offsetHeight/2+(E.getBoundingClientRect().y-s.getBoundingClientRect().y)*p,v=l(m,g,_,w,d,"close");o+=v,h.push(v);f=t;var y=t.parentElement.classList[2].replace("node_out_",""),C=(L=i.querySelector("#"+y)).querySelectorAll("."+t.parentElement.classList[3])[0];m=(x=L.querySelectorAll("."+t.parentElement.classList[3])[0]).offsetWidth/2+(x.getBoundingClientRect().x-s.getBoundingClientRect().x)*u,g=x.offsetHeight/2+(x.getBoundingClientRect().y-s.getBoundingClientRect().y)*p,_=(f.getBoundingClientRect().x-s.getBoundingClientRect().x)*u+r,w=(f.getBoundingClientRect().y-s.getBoundingClientRect().y)*p+r,v=l(m,g,_,w,d,"open");o+=v,h.push(v)}else if(0===a){var x;f=t,y=t.parentElement.classList[2].replace("node_out_",""),C=(L=i.querySelector("#"+y)).querySelectorAll("."+t.parentElement.classList[3])[0],m=(x=L.querySelectorAll("."+t.parentElement.classList[3])[0]).offsetWidth/2+(x.getBoundingClientRect().x-s.getBoundingClientRect().x)*u,g=x.offsetHeight/2+(x.getBoundingClientRect().y-s.getBoundingClientRect().y)*p,_=(f.getBoundingClientRect().x-s.getBoundingClientRect().x)*u+r,w=(f.getBoundingClientRect().y-s.getBoundingClientRect().y)*p+r,v=l(m,g,_,w,d,"open");o+=v,h.push(v);f=t,_=((C=n[a+1]).getBoundingClientRect().x-s.getBoundingClientRect().x)*u+r,w=(C.getBoundingClientRect().y-s.getBoundingClientRect().y)*p+r,m=(f.getBoundingClientRect().x-s.getBoundingClientRect().x)*u+r,g=(f.getBoundingClientRect().y-s.getBoundingClientRect().y)*p+r,v=l(m,g,_,w,c,"other");o+=v,h.push(v)}else if(a===n.length-1){var L,E;f=t,y=t.parentElement.classList[1].replace("node_in_",""),C=(L=i.querySelector("#"+y)).querySelectorAll("."+t.parentElement.classList[4])[0],_=(E=L.querySelectorAll("."+t.parentElement.classList[4])[0]).offsetWidth/2+(E.getBoundingClientRect().x-s.getBoundingClientRect().x)*u,w=E.offsetHeight/2+(E.getBoundingClientRect().y-s.getBoundingClientRect().y)*p,m=(f.getBoundingClientRect().x-s.getBoundingClientRect().x)*u+r,g=(f.getBoundingClientRect().y-s.getBoundingClientRect().y)*p+r,v=l(m,g,_,w,d,"close");o+=v,h.push(v)}else{f=t,_=((C=n[a+1]).getBoundingClientRect().x-s.getBoundingClientRect().x)*u+r,w=(C.getBoundingClientRect().y-s.getBoundingClientRect().y)*p+r,m=(f.getBoundingClientRect().x-s.getBoundingClientRect().x)*u+r,g=(f.getBoundingClientRect().y-s.getBoundingClientRect().y)*p+r,v=l(m,g,_,w,c,"other");o+=v,h.push(v)}}),a?h.forEach((e,n)=>{m[t].children[n].setAttributeNS(null,"d",e)}):m[t].children[0].setAttributeNS(null,"d",o)}}))}dblclick(e){null!=this.connection_selected&&this.reroute&&this.createReroutePoint(this.connection_selected),"point"===e.target.classList[0]&&this.removeReroutePoint(e.target)}createReroutePoint(e){this.connection_selected.classList.remove("selected");const t=this.connection_selected.parentElement.classList[2].slice(9),n=this.connection_selected.parentElement.classList[1].slice(13),i=this.connection_selected.parentElement.classList[3],s=this.connection_selected.parentElement.classList[4];this.connection_selected=null;const o=document.createElementNS("http://www.w3.org/2000/svg","circle");o.classList.add("point");var l=this.pos_x*(this.precanvas.clientWidth/(this.precanvas.clientWidth*this.zoom))-this.precanvas.getBoundingClientRect().x*(this.precanvas.clientWidth/(this.precanvas.clientWidth*this.zoom)),c=this.pos_y*(this.precanvas.clientHeight/(this.precanvas.clientHeight*this.zoom))-this.precanvas.getBoundingClientRect().y*(this.precanvas.clientHeight/(this.precanvas.clientHeight*this.zoom));o.setAttributeNS(null,"cx",l),o.setAttributeNS(null,"cy",c),o.setAttributeNS(null,"r",this.reroute_width);let d=0;if(this.reroute_fix_curvature){const t=e.parentElement.querySelectorAll(".main-path").length;var a=document.createElementNS("http://www.w3.org/2000/svg","path");if(a.classList.add("main-path"),a.setAttributeNS(null,"d",""),e.parentElement.insertBefore(a,e.parentElement.children[t]),1===t)e.parentElement.appendChild(o);else{const n=Array.from(e.parentElement.children).indexOf(e);d=n,e.parentElement.insertBefore(o,e.parentElement.children[n+t+1])}}else e.parentElement.appendChild(o);const r=t.slice(5),h=this.drawflow.drawflow[this.module].data[r].outputs[i].connections.findIndex((function(e,t){return e.node===n&&e.output===s}));void 0===this.drawflow.drawflow[this.module].data[r].outputs[i].connections[h].points&&(this.drawflow.drawflow[this.module].data[r].outputs[i].connections[h].points=[]),this.reroute_fix_curvature?(d>0||this.drawflow.drawflow[this.module].data[r].outputs[i].connections[h].points!==[]?this.drawflow.drawflow[this.module].data[r].outputs[i].connections[h].points.splice(d,0,{pos_x:l,pos_y:c}):this.drawflow.drawflow[this.module].data[r].outputs[i].connections[h].points.push({pos_x:l,pos_y:c}),e.parentElement.querySelectorAll(".main-path").forEach((e,t)=>{e.classList.remove("selected")})):this.drawflow.drawflow[this.module].data[r].outputs[i].connections[h].points.push({pos_x:l,pos_y:c}),this.dispatch("addReroute",r),this.updateConnectionNodes(t)}removeReroutePoint(e){const t=e.parentElement.classList[2].slice(9),n=e.parentElement.classList[1].slice(13),i=e.parentElement.classList[3],s=e.parentElement.classList[4];let o=Array.from(e.parentElement.children).indexOf(e);const l=t.slice(5),c=this.drawflow.drawflow[this.module].data[l].outputs[i].connections.findIndex((function(e,t){return e.node===n&&e.output===s}));if(this.reroute_fix_curvature){const t=e.parentElement.querySelectorAll(".main-path").length;e.parentElement.children[t-1].remove(),o-=t,o<0&&(o=0)}else o--;this.drawflow.drawflow[this.module].data[l].outputs[i].connections[c].points.splice(o,1),e.remove(),this.dispatch("removeReroute",l),this.updateConnectionNodes(t)}registerNode(e,t,n=null,i=null){this.noderegister[e]={html:t,props:n,options:i}}getNodeFromId(e){var t=this.getModuleFromNodeId(e);return JSON.parse(JSON.stringify(this.drawflow.drawflow[t].data[e]))}getNodesFromName(e){var t=[];const n=this.drawflow.drawflow;return Object.keys(n).map((function(i,s){for(var o in n[i].data)n[i].data[o].name==e&&t.push(n[i].data[o].id)})),t}addNode(e,t,n,i,s,o,l,c,d=!1){if(this.useuuid)var a=this.getUuid();else a=this.nodeId;const r=document.createElement("div");r.classList.add("parent-node");const h=document.createElement("div");h.innerHTML="",h.setAttribute("id","node-"+a),h.classList.add("drawflow-node"),""!=o&&h.classList.add(...o.split(" "));const u=document.createElement("div");u.classList.add("inputs");const p=document.createElement("div");p.classList.add("outputs");const f={};for(var m=0;me(this.noderegister[c].html,{props:this.noderegister[c].props}),...this.noderegister[c].options}).$mount();_.appendChild(e.$el)}Object.entries(l).forEach((function(e,t){if("object"==typeof e[1])!function e(t,n,i){if(null===t)t=l[n];else t=t[n];null!==t&&Object.entries(t).forEach((function(n,s){if("object"==typeof n[1])e(t,n[0],i+"-"+n[0]);else for(var o=_.querySelectorAll("[df-"+i+"-"+n[0]+"]"),l=0;lt(this.noderegister[e.html].html,{props:this.noderegister[e.html].props}),...this.noderegister[e.html].options}).$mount();c.appendChild(t.$el)}Object.entries(e.data).forEach((function(t,n){if("object"==typeof t[1])!function t(n,i,s){if(null===n)n=e.data[i];else n=n[i];null!==n&&Object.entries(n).forEach((function(e,i){if("object"==typeof e[1])t(n,e[0],s+"-"+e[0]);else for(var o=c.querySelectorAll("[df-"+s+"-"+e[0]+"]"),l=0;l{const a=e.outputs[s].connections[o].node,r=e.outputs[s].connections[o].output,h=i.querySelector(".connection.node_in_node-"+a+".node_out_node-"+e.id+"."+s+"."+r);if(n&&0===d)for(var u=0;u{this.removeSingleConnection(e.id_output,e.id,e.output_class,e.input_class)}),delete this.drawflow.drawflow[n].data[e].inputs[t];const o=[],l=this.drawflow.drawflow[n].data[e].inputs;Object.keys(l).map((function(e,t){o.push(l[e])})),this.drawflow.drawflow[n].data[e].inputs={};const c=t.slice(6);let d=[];if(o.forEach((t,i)=>{t.connections.forEach((e,t)=>{d.push(e)}),this.drawflow.drawflow[n].data[e].inputs["input_"+(i+1)]=t}),d=new Set(d.map(e=>JSON.stringify(e))),d=Array.from(d).map(e=>JSON.parse(e)),this.module===n){this.container.querySelectorAll("#node-"+e+" .inputs .input").forEach((e,t)=>{const n=e.classList[1].slice(6);parseInt(c){this.drawflow.drawflow[n].data[t.node].outputs[t.input].connections.forEach((i,s)=>{if(i.node==e){const o=i.output.slice(6);if(parseInt(c){this.removeSingleConnection(e.id,e.id_input,e.output_class,e.input_class)}),delete this.drawflow.drawflow[n].data[e].outputs[t];const o=[],l=this.drawflow.drawflow[n].data[e].outputs;Object.keys(l).map((function(e,t){o.push(l[e])})),this.drawflow.drawflow[n].data[e].outputs={};const c=t.slice(7);let d=[];if(o.forEach((t,i)=>{t.connections.forEach((e,t)=>{d.push({node:e.node,output:e.output})}),this.drawflow.drawflow[n].data[e].outputs["output_"+(i+1)]=t}),d=new Set(d.map(e=>JSON.stringify(e))),d=Array.from(d).map(e=>JSON.parse(e)),this.module===n){this.container.querySelectorAll("#node-"+e+" .outputs .output").forEach((e,t)=>{const n=e.classList[1].slice(7);parseInt(c){this.drawflow.drawflow[n].data[t.node].inputs[t.output].connections.forEach((i,s)=>{if(i.node==e){const o=i.input.slice(7);if(parseInt(c)-1){this.module===s&&this.container.querySelector(".connection.node_in_node-"+t+".node_out_node-"+e+"."+n+"."+i).remove();var o=this.drawflow.drawflow[s].data[e].outputs[n].connections.findIndex((function(e,n){return e.node==t&&e.output===i}));this.drawflow.drawflow[s].data[e].outputs[n].connections.splice(o,1);var l=this.drawflow.drawflow[s].data[t].inputs[i].connections.findIndex((function(t,i){return t.node==e&&t.input===n}));return this.drawflow.drawflow[s].data[t].inputs[i].connections.splice(l,1),this.dispatch("connectionRemoved",{output_id:e,input_id:t,output_class:n,input_class:i}),!0}return!1}return!1}removeConnectionNodeId(e){const t="node_in_"+e,n="node_out_"+e,i=this.container.querySelectorAll("."+n);for(var s=i.length-1;s>=0;s--){var o=i[s].classList,l=this.drawflow.drawflow[this.module].data[o[1].slice(13)].inputs[o[4]].connections.findIndex((function(e,t){return e.node===o[2].slice(14)&&e.input===o[3]}));this.drawflow.drawflow[this.module].data[o[1].slice(13)].inputs[o[4]].connections.splice(l,1);var c=this.drawflow.drawflow[this.module].data[o[2].slice(14)].outputs[o[3]].connections.findIndex((function(e,t){return e.node===o[1].slice(13)&&e.output===o[4]}));this.drawflow.drawflow[this.module].data[o[2].slice(14)].outputs[o[3]].connections.splice(c,1),i[s].remove(),this.dispatch("connectionRemoved",{output_id:o[2].slice(14),input_id:o[1].slice(13),output_class:o[3],input_class:o[4]})}const d=this.container.querySelectorAll("."+t);for(s=d.length-1;s>=0;s--){o=d[s].classList,c=this.drawflow.drawflow[this.module].data[o[2].slice(14)].outputs[o[3]].connections.findIndex((function(e,t){return e.node===o[1].slice(13)&&e.output===o[4]}));this.drawflow.drawflow[this.module].data[o[2].slice(14)].outputs[o[3]].connections.splice(c,1);l=this.drawflow.drawflow[this.module].data[o[1].slice(13)].inputs[o[4]].connections.findIndex((function(e,t){return e.node===o[2].slice(14)&&e.input===o[3]}));this.drawflow.drawflow[this.module].data[o[1].slice(13)].inputs[o[4]].connections.splice(l,1),d[s].remove(),this.dispatch("connectionRemoved",{output_id:o[2].slice(14),input_id:o[1].slice(13),output_class:o[3],input_class:o[4]})}}getModuleFromNodeId(e){var t;const n=this.drawflow.drawflow;return Object.keys(n).map((function(i,s){Object.keys(n[i].data).map((function(n,s){n==e&&(t=i)}))})),t}addModule(e){this.drawflow.drawflow[e]={data:{}},this.dispatch("moduleCreated",e)}changeModule(e){this.dispatch("moduleChanged",e),this.module=e,this.precanvas.innerHTML="",this.canvas_x=0,this.canvas_y=0,this.pos_x=0,this.pos_y=0,this.mouse_x=0,this.mouse_y=0,this.zoom=1,this.zoom_last_value=1,this.precanvas.style.transform="",this.import(this.drawflow,!1)}removeModule(e){this.module===e&&this.changeModule("Home"),delete this.drawflow.drawflow[e],this.dispatch("moduleRemoved",e)}clearModuleSelected(){this.precanvas.innerHTML="",this.drawflow.drawflow[this.module]={data:{}}}clear(){this.precanvas.innerHTML="",this.drawflow={drawflow:{Home:{data:{}}}}}export(){const e=JSON.parse(JSON.stringify(this.drawflow));return this.dispatch("export",e),e}import(e,t=!0){this.clear(),this.drawflow=JSON.parse(JSON.stringify(e)),this.load(),t&&this.dispatch("import","import")}on(e,t){return"function"!=typeof t?(console.error("The listener callback must be a function, the given type is "+typeof t),!1):"string"!=typeof e?(console.error("The event name must be a string, the given type is "+typeof e),!1):(void 0===this.events[e]&&(this.events[e]={listeners:[]}),void this.events[e].listeners.push(t))}removeListener(e,t){if(!this.events[e])return!1;const n=this.events[e].listeners,i=n.indexOf(t);i>-1&&n.splice(i,1)}dispatch(e,t){if(void 0===this.events[e])return!1;this.events[e].listeners.forEach(e=>{e(t)})}getUuid(){for(var e=[],t=0;t<36;t++)e[t]="0123456789abcdef".substr(Math.floor(16*Math.random()),1);return e[14]="4",e[19]="0123456789abcdef".substr(3&e[19]|8,1),e[8]=e[13]=e[18]=e[23]="-",e.join("")}}}]).default})); \ No newline at end of file