living-documentation 7.37.0 → 7.38.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import { st, markDirty } from './state.js';
|
|
5
5
|
import { visEdgeProps } from './edge-rendering.js';
|
|
6
6
|
import { pushSnapshot } from './history.js';
|
|
7
|
+
import { t } from './t.js';
|
|
7
8
|
|
|
8
9
|
const DEFAULT_EDGE_COLOR = '#a8a29e';
|
|
9
10
|
const FREE_ARROW_STYLE_KEY = 'ld-free-arrow-style';
|
|
@@ -33,12 +34,55 @@ export function getLastFreeArrowStyle() {
|
|
|
33
34
|
catch { return {}; }
|
|
34
35
|
}
|
|
35
36
|
|
|
37
|
+
function isEdgeLocked(edge) {
|
|
38
|
+
if (!edge) return false;
|
|
39
|
+
const fromN = st.nodes && st.nodes.get(edge.from);
|
|
40
|
+
const toN = st.nodes && st.nodes.get(edge.to);
|
|
41
|
+
const isFreeArrow = fromN && fromN.shapeType === 'anchor' && toN && toN.shapeType === 'anchor';
|
|
42
|
+
return isFreeArrow ? !!(fromN.locked && toN.locked) : !!edge.edgeLocked;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function selectedEdgeLockState() {
|
|
46
|
+
const edgeIds = (st.selectedEdgeIds || []).filter((id) => st.edges && st.edges.get(id));
|
|
47
|
+
if (!edgeIds.length) return { allLocked: false, edgeIds };
|
|
48
|
+
return {
|
|
49
|
+
allLocked: edgeIds.every((id) => isEdgeLocked(st.edges.get(id))),
|
|
50
|
+
edgeIds,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function syncEdgeLockButton() {
|
|
55
|
+
const btn = document.getElementById('btnEdgeLock');
|
|
56
|
+
if (!btn) return;
|
|
57
|
+
const { allLocked } = selectedEdgeLockState();
|
|
58
|
+
btn.textContent = allLocked ? '🔓' : '🔒';
|
|
59
|
+
btn.title = t(allLocked ? 'diagram.edge_panel.unlock' : 'diagram.edge_panel.lock');
|
|
60
|
+
btn.setAttribute('aria-label', btn.title);
|
|
61
|
+
btn.classList.toggle('tool-active', allLocked);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function setEdgeLocked(edge, locked) {
|
|
65
|
+
if (!edge) return;
|
|
66
|
+
const fromN = st.nodes && st.nodes.get(edge.from);
|
|
67
|
+
const toN = st.nodes && st.nodes.get(edge.to);
|
|
68
|
+
const isFreeArrow = fromN && fromN.shapeType === 'anchor' && toN && toN.shapeType === 'anchor';
|
|
69
|
+
if (isFreeArrow) {
|
|
70
|
+
[edge.from, edge.to].forEach((nodeId) => {
|
|
71
|
+
st.nodes.update({ id: nodeId, locked, fixed: locked ? { x: true, y: true } : false, draggable: !locked });
|
|
72
|
+
const bn = st.network && st.network.body.nodes[nodeId];
|
|
73
|
+
if (bn) bn.refreshNeeded = true;
|
|
74
|
+
});
|
|
75
|
+
} else {
|
|
76
|
+
st.edges.update({ id: edge.id, edgeLocked: locked });
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
36
80
|
export function showEdgePanel() {
|
|
37
81
|
if (!st.selectedEdgeIds.length) return;
|
|
38
82
|
const e = st.edges.get(st.selectedEdgeIds[0]);
|
|
39
83
|
if (!e) return;
|
|
40
84
|
|
|
41
|
-
|
|
85
|
+
syncEdgeLockButton();
|
|
42
86
|
document.getElementById('edgePanelControls').classList.remove('hidden');
|
|
43
87
|
|
|
44
88
|
const dir = e.arrowDir ?? 'to';
|
|
@@ -69,33 +113,22 @@ export function showEdgePanel() {
|
|
|
69
113
|
}
|
|
70
114
|
|
|
71
115
|
export function toggleEdgeLock() {
|
|
72
|
-
|
|
116
|
+
const { allLocked, edgeIds } = selectedEdgeLockState();
|
|
117
|
+
if (!edgeIds.length) return;
|
|
118
|
+
const nextLocked = !allLocked;
|
|
73
119
|
pushSnapshot();
|
|
74
|
-
|
|
75
|
-
// long-press on the shape itself (see unlock-hold.js).
|
|
76
|
-
st.selectedEdgeIds.forEach((id) => {
|
|
77
|
-
const e = st.edges.get(id);
|
|
78
|
-
if (!e) return;
|
|
79
|
-
const fromN = st.nodes && st.nodes.get(e.from);
|
|
80
|
-
const toN = st.nodes && st.nodes.get(e.to);
|
|
81
|
-
const isFreeArrow = fromN && fromN.shapeType === 'anchor' && toN && toN.shapeType === 'anchor';
|
|
82
|
-
if (isFreeArrow) {
|
|
83
|
-
[e.from, e.to].forEach((nodeId) => {
|
|
84
|
-
st.nodes.update({ id: nodeId, locked: true, fixed: { x: true, y: true }, draggable: false });
|
|
85
|
-
const bn = st.network && st.network.body.nodes[nodeId];
|
|
86
|
-
if (bn) bn.refreshNeeded = true;
|
|
87
|
-
});
|
|
88
|
-
} else {
|
|
89
|
-
st.edges.update({ id, edgeLocked: true });
|
|
90
|
-
}
|
|
91
|
-
});
|
|
120
|
+
edgeIds.forEach((id) => setEdgeLocked(st.edges.get(id), nextLocked));
|
|
92
121
|
if (st.network) {
|
|
93
|
-
st.network.unselectAll();
|
|
94
122
|
st.network.redraw();
|
|
123
|
+
if (nextLocked) st.network.unselectAll();
|
|
124
|
+
}
|
|
125
|
+
if (nextLocked) {
|
|
126
|
+
st.selectedNodeIds = [];
|
|
127
|
+
st.selectedEdgeIds = [];
|
|
128
|
+
hideEdgePanel();
|
|
129
|
+
} else {
|
|
130
|
+
syncEdgeLockButton();
|
|
95
131
|
}
|
|
96
|
-
st.selectedNodeIds = [];
|
|
97
|
-
st.selectedEdgeIds = [];
|
|
98
|
-
hideEdgePanel();
|
|
99
132
|
markDirty();
|
|
100
133
|
}
|
|
101
134
|
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import { st, markDirty } from './state.js';
|
|
5
5
|
import { SHAPE_DEFAULTS } from './node-rendering.js';
|
|
6
6
|
import { pushSnapshot } from './history.js';
|
|
7
|
+
import { t } from './t.js';
|
|
7
8
|
|
|
8
9
|
// ── Last-used style persistence (per shape type) ──────────────────────────────
|
|
9
10
|
// Saves colorKey/fontSize/textAlign/textValign per shapeType to localStorage so
|
|
@@ -38,10 +39,61 @@ function forceRedraw() {
|
|
|
38
39
|
if (st.network) st.network.redraw();
|
|
39
40
|
}
|
|
40
41
|
|
|
42
|
+
function isEdgeLocked(edge) {
|
|
43
|
+
if (!edge) return false;
|
|
44
|
+
const fromN = st.nodes && st.nodes.get(edge.from);
|
|
45
|
+
const toN = st.nodes && st.nodes.get(edge.to);
|
|
46
|
+
const isFreeArrow = fromN && fromN.shapeType === 'anchor' && toN && toN.shapeType === 'anchor';
|
|
47
|
+
return isFreeArrow ? !!(fromN.locked && toN.locked) : !!(edge.edgeLocked || (fromN && fromN.locked && toN && toN.locked));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function selectedLockState() {
|
|
51
|
+
const nodeIds = (st.selectedNodeIds || []).filter((id) => {
|
|
52
|
+
const n = st.nodes && st.nodes.get(id);
|
|
53
|
+
return n && n.shapeType !== 'anchor';
|
|
54
|
+
});
|
|
55
|
+
const edgeIds = (st.selectedEdgeIds || []).filter((id) => st.edges && st.edges.get(id));
|
|
56
|
+
const total = nodeIds.length + edgeIds.length;
|
|
57
|
+
if (!total) return { allLocked: false, nodeIds, edgeIds };
|
|
58
|
+
|
|
59
|
+
const nodesLocked = nodeIds.every((id) => {
|
|
60
|
+
const n = st.nodes.get(id);
|
|
61
|
+
return !!(n && n.locked);
|
|
62
|
+
});
|
|
63
|
+
const edgesLocked = edgeIds.every((id) => isEdgeLocked(st.edges.get(id)));
|
|
64
|
+
return { allLocked: nodesLocked && edgesLocked, nodeIds, edgeIds };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function syncNodeLockButton() {
|
|
68
|
+
const btn = document.getElementById('btnNodeLock');
|
|
69
|
+
if (!btn) return;
|
|
70
|
+
const { allLocked } = selectedLockState();
|
|
71
|
+
btn.textContent = allLocked ? '🔓' : '🔒';
|
|
72
|
+
btn.title = t(allLocked ? 'diagram.node_panel.unlock' : 'diagram.node_panel.lock');
|
|
73
|
+
btn.setAttribute('aria-label', btn.title);
|
|
74
|
+
btn.classList.toggle('tool-active', allLocked);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function setEdgeLocked(edge, locked) {
|
|
78
|
+
if (!edge) return;
|
|
79
|
+
const fromN = st.nodes && st.nodes.get(edge.from);
|
|
80
|
+
const toN = st.nodes && st.nodes.get(edge.to);
|
|
81
|
+
const isFreeArrow = fromN && fromN.shapeType === 'anchor' && toN && toN.shapeType === 'anchor';
|
|
82
|
+
if (isFreeArrow) {
|
|
83
|
+
[edge.from, edge.to].forEach((nodeId) => {
|
|
84
|
+
st.nodes.update({ id: nodeId, locked, fixed: locked ? { x: true, y: true } : false, draggable: !locked });
|
|
85
|
+
const bn = st.network && st.network.body.nodes[nodeId];
|
|
86
|
+
if (bn) bn.refreshNeeded = true;
|
|
87
|
+
});
|
|
88
|
+
} else {
|
|
89
|
+
st.edges.update({ id: edge.id, edgeLocked: locked });
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
41
93
|
export function showNodePanel() {
|
|
42
94
|
document.getElementById('nodePanel').classList.remove('hidden');
|
|
43
95
|
document.getElementById('nodePanelControls').classList.remove('hidden');
|
|
44
|
-
|
|
96
|
+
syncNodeLockButton();
|
|
45
97
|
// Sync the opacity slider with the first selected node's current value so the
|
|
46
98
|
// slider reflects the live state rather than whatever position it was left at.
|
|
47
99
|
const slider = document.getElementById('nodeBgOpacity');
|
|
@@ -57,22 +109,27 @@ export function hideNodePanel() {
|
|
|
57
109
|
}
|
|
58
110
|
|
|
59
111
|
export function toggleNodeLock() {
|
|
60
|
-
|
|
112
|
+
const { allLocked, nodeIds, edgeIds } = selectedLockState();
|
|
113
|
+
if (!nodeIds.length && !edgeIds.length) return;
|
|
114
|
+
const nextLocked = !allLocked;
|
|
61
115
|
pushSnapshot();
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
st.selectedNodeIds.forEach((id) => {
|
|
65
|
-
st.nodes.update({ id, locked: true, fixed: { x: true, y: true }, draggable: false });
|
|
116
|
+
nodeIds.forEach((id) => {
|
|
117
|
+
st.nodes.update({ id, locked: nextLocked, fixed: nextLocked ? { x: true, y: true } : false, draggable: !nextLocked });
|
|
66
118
|
const bn = st.network && st.network.body.nodes[id];
|
|
67
119
|
if (bn) bn.refreshNeeded = true;
|
|
68
120
|
});
|
|
121
|
+
edgeIds.forEach((id) => setEdgeLocked(st.edges.get(id), nextLocked));
|
|
69
122
|
if (st.network) {
|
|
70
|
-
st.network.unselectAll();
|
|
71
123
|
st.network.redraw();
|
|
124
|
+
if (nextLocked) st.network.unselectAll();
|
|
125
|
+
}
|
|
126
|
+
if (nextLocked) {
|
|
127
|
+
st.selectedNodeIds = [];
|
|
128
|
+
st.selectedEdgeIds = [];
|
|
129
|
+
hideNodePanel();
|
|
130
|
+
} else {
|
|
131
|
+
syncNodeLockButton();
|
|
72
132
|
}
|
|
73
|
-
st.selectedNodeIds = [];
|
|
74
|
-
st.selectedEdgeIds = [];
|
|
75
|
-
hideNodePanel();
|
|
76
133
|
markDirty();
|
|
77
134
|
}
|
|
78
135
|
|
|
@@ -428,7 +428,8 @@
|
|
|
428
428
|
"diagram.sidebar.empty": "No diagrams",
|
|
429
429
|
"diagram.sidebar.delete_title": "Delete",
|
|
430
430
|
|
|
431
|
-
"diagram.node_panel.lock": "Lock
|
|
431
|
+
"diagram.node_panel.lock": "Lock selection",
|
|
432
|
+
"diagram.node_panel.unlock": "Unlock selection",
|
|
432
433
|
"diagram.node_panel.edit_label": "Edit text (double-click)",
|
|
433
434
|
"diagram.node_panel.edit_link": "Add / edit link",
|
|
434
435
|
"diagram.node_panel.bg_opacity": "Background opacity",
|
|
@@ -461,6 +462,7 @@
|
|
|
461
462
|
"diagram.link_panel.remove_btn": "Remove",
|
|
462
463
|
|
|
463
464
|
"diagram.edge_panel.lock": "Lock arrow",
|
|
465
|
+
"diagram.edge_panel.unlock": "Unlock arrow",
|
|
464
466
|
"diagram.edge_panel.no_arrow": "Simple line",
|
|
465
467
|
"diagram.edge_panel.arrow_to": "Directional arrow",
|
|
466
468
|
"diagram.edge_panel.arrow_both": "Bidirectional",
|
|
@@ -428,7 +428,8 @@
|
|
|
428
428
|
"diagram.sidebar.empty": "Aucun diagramme",
|
|
429
429
|
"diagram.sidebar.delete_title": "Supprimer",
|
|
430
430
|
|
|
431
|
-
"diagram.node_panel.lock": "Verrouiller la
|
|
431
|
+
"diagram.node_panel.lock": "Verrouiller la sélection",
|
|
432
|
+
"diagram.node_panel.unlock": "Déverrouiller la sélection",
|
|
432
433
|
"diagram.node_panel.edit_label": "Modifier le texte (double-clic)",
|
|
433
434
|
"diagram.node_panel.edit_link": "Ajouter / modifier un lien",
|
|
434
435
|
"diagram.node_panel.bg_opacity": "Opacité du fond",
|
|
@@ -461,6 +462,7 @@
|
|
|
461
462
|
"diagram.link_panel.remove_btn": "Retirer",
|
|
462
463
|
|
|
463
464
|
"diagram.edge_panel.lock": "Verrouiller la flèche",
|
|
465
|
+
"diagram.edge_panel.unlock": "Déverrouiller la flèche",
|
|
464
466
|
"diagram.edge_panel.no_arrow": "Trait simple",
|
|
465
467
|
"diagram.edge_panel.arrow_to": "Flèche directionnelle",
|
|
466
468
|
"diagram.edge_panel.arrow_both": "Bidirectionnelle",
|