节点视觉重构 + 右栏选项折叠 + 撤销按钮/快捷键
- kind 名做成顶边框标牌(legend,边框在文字处断开) - 去掉「开头」字(仅绿框)、去掉选择标题的项数 - 多出口节点每出口一行严格对齐右侧黄点 - 开头节点改为视口垂直居中(左侧) - 选择节点右栏选项改为可折叠,点开编辑单个 - 撤销/重做按钮(不可用时灰)+ R自动整理 + Enter加后继
This commit is contained in:
@ -289,14 +289,19 @@
|
||||
try { toast("⚠ 操作失败:" + m); } catch (_) {}
|
||||
});
|
||||
|
||||
// ---------- 撤销 / 重做(Ctrl+Z / Ctrl+Y) ----------
|
||||
// ---------- 撤销 / 重做 ----------
|
||||
let undoStack = [], redoStack = [], snapTimer = null;
|
||||
function snapReset() { undoStack = App.ir ? [JSON.stringify(App.ir)] : []; redoStack = []; }
|
||||
function updateUndoButtons() {
|
||||
const u = $("btn-undo"), r = $("btn-redo");
|
||||
if (u) u.disabled = undoStack.length < 2;
|
||||
if (r) r.disabled = redoStack.length === 0;
|
||||
}
|
||||
function snapReset() { undoStack = App.ir ? [JSON.stringify(App.ir)] : []; redoStack = []; updateUndoButtons(); }
|
||||
function flushSnapshot() {
|
||||
if (!App.ir) return;
|
||||
const cur = JSON.stringify(App.ir);
|
||||
if (!undoStack.length || undoStack[undoStack.length - 1] !== cur) {
|
||||
undoStack.push(cur); if (undoStack.length > 60) undoStack.shift(); redoStack = [];
|
||||
undoStack.push(cur); if (undoStack.length > 60) undoStack.shift(); redoStack = []; updateUndoButtons();
|
||||
}
|
||||
}
|
||||
function scheduleSnapshot() { clearTimeout(snapTimer); snapTimer = setTimeout(flushSnapshot, 450); }
|
||||
@ -309,29 +314,42 @@
|
||||
if (undoStack.length < 2) return;
|
||||
redoStack.push(undoStack.pop());
|
||||
restoreState(undoStack[undoStack.length - 1]);
|
||||
toast("已撤销");
|
||||
updateUndoButtons(); toast("已撤销");
|
||||
}
|
||||
function redo() {
|
||||
if (!redoStack.length) return;
|
||||
const s = redoStack.pop(); undoStack.push(s); restoreState(s);
|
||||
toast("已重做");
|
||||
updateUndoButtons(); toast("已重做");
|
||||
}
|
||||
$("btn-undo").onclick = undo;
|
||||
$("btn-redo").onclick = redo;
|
||||
|
||||
// ---------- 快捷键:Ctrl+Z/Y 撤销重做、R 自动整理、Enter 加后继 ----------
|
||||
document.addEventListener("keydown", e => {
|
||||
if (!(e.ctrlKey || e.metaKey) || !App.ir) return;
|
||||
if (!App.ir) return;
|
||||
const a = document.activeElement;
|
||||
if (a && (a.tagName === "INPUT" || a.tagName === "TEXTAREA")) return; // 输入框内交给浏览器
|
||||
const k = e.key.toLowerCase();
|
||||
if (k === "z" && !e.shiftKey) { e.preventDefault(); undo(); }
|
||||
else if (k === "y" || (k === "z" && e.shiftKey)) { e.preventDefault(); redo(); }
|
||||
const inInput = a && (a.tagName === "INPUT" || a.tagName === "TEXTAREA");
|
||||
if (e.ctrlKey || e.metaKey) {
|
||||
if (inInput) return; // 输入框内的 Ctrl+Z 交给浏览器
|
||||
const k = e.key.toLowerCase();
|
||||
if (k === "z" && !e.shiftKey) { e.preventDefault(); undo(); }
|
||||
else if (k === "y" || (k === "z" && e.shiftKey)) { e.preventDefault(); redo(); }
|
||||
return;
|
||||
}
|
||||
if (e.altKey || inInput) return;
|
||||
if (e.key.toLowerCase() === "r") { e.preventDefault(); doAutoLayout(); }
|
||||
else if (e.key === "Enter" && App.selectedNode) { e.preventDefault(); addSuccessor(App.selectedNode); }
|
||||
});
|
||||
|
||||
// ---------- 画布工具栏 ----------
|
||||
$("btn-autolayout").onclick = () => {
|
||||
function doAutoLayout() {
|
||||
if (!App.ir) return;
|
||||
App.ir._layout = null; // 清坐标 → render 时按自动布局重排
|
||||
GraphUI.render(App.ir, App.selectedNode);
|
||||
App.dirty = true; updateDirty();
|
||||
};
|
||||
GraphUI.focusStart(App.ir);
|
||||
App.dirty = true; updateDirty(); scheduleSnapshot(); toast("已自动整理");
|
||||
}
|
||||
$("btn-autolayout").onclick = doAutoLayout;
|
||||
$("btn-addsucc").onclick = () => {
|
||||
if (!App.ir || !App.selectedNode) { alert("先在画布上点选一个节点,再点「加后继」。"); return; }
|
||||
addSuccessor(App.selectedNode);
|
||||
|
||||
Reference in New Issue
Block a user