节点视觉重构 + 右栏选项折叠 + 撤销按钮/快捷键
- kind 名做成顶边框标牌(legend,边框在文字处断开) - 去掉「开头」字(仅绿框)、去掉选择标题的项数 - 多出口节点每出口一行严格对齐右侧黄点 - 开头节点改为视口垂直居中(左侧) - 选择节点右栏选项改为可折叠,点开编辑单个 - 撤销/重做按钮(不可用时灰)+ R自动整理 + Enter加后继
This commit is contained in:
@ -61,27 +61,26 @@
|
||||
}
|
||||
|
||||
// ---------- 节点 HTML ----------
|
||||
function nodeInner(ir, node, isStart) {
|
||||
const KIND_CN = { narration: "旁白", dialogue: "对话", choice: "选择", choice_once: "一次性选择", random: "随机", fight: "战斗", move: "走位", anim: "动画", reward: "奖励", out_ref: "引用", ending: "结局" };
|
||||
function nodeInner(ir, node) {
|
||||
const names = roleNames(ir), end = isEnding(node);
|
||||
const startTag = isStart ? '<span class="startflag">▶ 开头</span> ' : '';
|
||||
const kind = end ? "ending" : node.kind;
|
||||
const label = '<div class="nlabel">' + esc(KIND_CN[kind] || kind) + '</div>';
|
||||
if (end) {
|
||||
const sm = summary(ir, names, Object.assign({ kind: "ending" }, node));
|
||||
const g = (node.grants && node.grants.length) ? node.grants.map(gr => grantStr(ir, names, gr)).join(",") : "无奖励";
|
||||
return '<div class="nid">' + startTag + '#' + esc(node.id) + '</div>'
|
||||
+ '<div class="k">' + esc(sm[0]) + '</div><div class="t">' + esc(sm[1] || "") + '</div>'
|
||||
+ '<div class="rw">' + esc(g) + '</div>';
|
||||
return label + '<div class="nbody"><div class="t">' + esc(node.summary || "") + '</div><div class="rw">' + esc(g) + '</div></div>';
|
||||
}
|
||||
const outs = getOutlets(node);
|
||||
// 多出口节点(选择/随机/战斗):顶部角标 + 每出口一行(右对齐,行高匹配端口间距 → 与黄点平齐)
|
||||
// 多出口(选择/随机/战斗):每出口一行(右对齐),是唯一流内容 → 垂直居中 → 与居中的黄点逐行平齐
|
||||
if (outs.length > 1) {
|
||||
const head = summary(ir, names, node)[0];
|
||||
return '<div class="ch-tag">' + (isStart ? '<span class="startflag">▶</span> ' : '') + '<span class="nid">#' + esc(node.id) + '</span> ' + esc(head) + '</div>'
|
||||
+ '<div class="ch-opts">' + outs.map(o => '<div class="ch-opt" title="' + esc(o.label) + '">' + esc(o.label) + '</div>').join("") + '</div>';
|
||||
return label + '<div class="ch-opts">' + outs.map(o => '<div class="ch-opt" title="' + esc(o.label) + '">' + esc(o.label) + '</div>').join("") + '</div>';
|
||||
}
|
||||
// 线性 / 单出口节点
|
||||
// 线性 / 单出口:角色(可选)+ 文本
|
||||
const sm = summary(ir, names, node);
|
||||
return '<div class="nid">' + startTag + '#' + esc(node.id) + '</div>'
|
||||
+ '<div class="k">' + esc(sm[0]) + '</div><div class="t">' + esc(sm[1] || "") + '</div>';
|
||||
const actor = node.speaker || node.actor;
|
||||
let body = actor ? ('<div class="who">' + esc(nameOf(ir, names, actor)) + '</div>') : "";
|
||||
body += '<div class="t">' + esc(sm[1] || "") + '</div>';
|
||||
return label + '<div class="nbody">' + body + '</div>';
|
||||
}
|
||||
|
||||
// ---------- 自动布局:最长路径分层 + 每层顺序铺开 ----------
|
||||
@ -203,7 +202,7 @@
|
||||
const outN = end ? 0 : getOutlets(node).length;
|
||||
const pos = layout[node.id] || { x: 40, y: 30 };
|
||||
const cls = "irnode kind-" + (end ? "ending" : node.kind) + (node.id === startId ? " isstart" : "");
|
||||
const dfId = editor.addNode(node.id, 1, outN, pos.x, pos.y, cls, { irId: node.id }, nodeInner(ir, node, node.id === startId));
|
||||
const dfId = editor.addNode(node.id, 1, outN, pos.x, pos.y, cls, { irId: node.id }, nodeInner(ir, node));
|
||||
dfId2ir[dfId] = node.id; ir2dfId[node.id] = dfId;
|
||||
});
|
||||
all.forEach(({ node, end }) => {
|
||||
@ -221,22 +220,27 @@
|
||||
_ir = ir;
|
||||
const dfId = ir2dfId[irId]; if (!dfId) return;
|
||||
const node = findNode(irId); if (!node) return;
|
||||
const startId = (ir.nodes && ir.nodes[0]) ? ir.nodes[0].id : null;
|
||||
const box = document.querySelector("#node-" + dfId + " .drawflow_content_node");
|
||||
if (box) box.innerHTML = nodeInner(ir, node, irId === startId);
|
||||
if (box) box.innerHTML = nodeInner(ir, node);
|
||||
},
|
||||
// 把画布平移到开头节点(ir.nodes[0])附近
|
||||
// 把画布平移到开头节点(ir.nodes[0]):水平靠左、垂直居中
|
||||
focusStart(ir) {
|
||||
const startId = (ir.nodes && ir.nodes[0]) ? ir.nodes[0].id : null;
|
||||
if (!startId) return;
|
||||
const p = (ir._layout || {})[startId]; if (!p) return;
|
||||
const z = editor.zoom || 1;
|
||||
const tx = 70 - p.x * z, ty = 90 - p.y * z;
|
||||
try {
|
||||
editor.canvas_x = tx; editor.canvas_y = ty;
|
||||
const pc = editor.precanvas || document.querySelector("#drawflow .drawflow");
|
||||
if (pc) pc.style.transform = "translate(" + tx + "px, " + ty + "px) scale(" + z + ")";
|
||||
} catch (e) {}
|
||||
const apply = () => {
|
||||
const p = (ir._layout || {})[startId]; if (!p) return;
|
||||
const z = editor.zoom || 1;
|
||||
const host = document.getElementById("drawflow");
|
||||
const H = (host && host.clientHeight) || 500;
|
||||
const tx = 70 - p.x * z, ty = H / 2 - p.y * z - 45; // 左侧 + 垂直居中(-45≈半节点高)
|
||||
try {
|
||||
editor.canvas_x = tx; editor.canvas_y = ty;
|
||||
const pc = editor.precanvas || document.querySelector("#drawflow .drawflow");
|
||||
if (pc) pc.style.transform = "translate(" + tx + "px, " + ty + "px) scale(" + z + ")";
|
||||
} catch (e) {}
|
||||
};
|
||||
apply();
|
||||
if (typeof requestAnimationFrame === "function") requestAnimationFrame(apply);
|
||||
},
|
||||
select(irId) {
|
||||
document.querySelectorAll("#drawflow .drawflow-node.selected").forEach(n => n.classList.remove("selected"));
|
||||
|
||||
Reference in New Issue
Block a user