living-documentation 7.37.0 → 7.39.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,78 @@ 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 syncNodeFontSizeValue() {
|
|
78
|
+
const el = document.getElementById('nodeFontSizeValue');
|
|
79
|
+
if (!el) return;
|
|
80
|
+
const sizes = (st.selectedNodeIds || []).map((id) => {
|
|
81
|
+
const n = st.nodes && st.nodes.get(id);
|
|
82
|
+
return n && n.shapeType !== 'anchor' ? (n.fontSize || 13) : null;
|
|
83
|
+
}).filter((size) => size !== null);
|
|
84
|
+
|
|
85
|
+
if (!sizes.length) {
|
|
86
|
+
el.textContent = '–';
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
const first = sizes[0];
|
|
90
|
+
el.textContent = sizes.every((size) => size === first) ? String(first) : '–';
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function setEdgeLocked(edge, locked) {
|
|
94
|
+
if (!edge) return;
|
|
95
|
+
const fromN = st.nodes && st.nodes.get(edge.from);
|
|
96
|
+
const toN = st.nodes && st.nodes.get(edge.to);
|
|
97
|
+
const isFreeArrow = fromN && fromN.shapeType === 'anchor' && toN && toN.shapeType === 'anchor';
|
|
98
|
+
if (isFreeArrow) {
|
|
99
|
+
[edge.from, edge.to].forEach((nodeId) => {
|
|
100
|
+
st.nodes.update({ id: nodeId, locked, fixed: locked ? { x: true, y: true } : false, draggable: !locked });
|
|
101
|
+
const bn = st.network && st.network.body.nodes[nodeId];
|
|
102
|
+
if (bn) bn.refreshNeeded = true;
|
|
103
|
+
});
|
|
104
|
+
} else {
|
|
105
|
+
st.edges.update({ id: edge.id, edgeLocked: locked });
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
41
109
|
export function showNodePanel() {
|
|
42
110
|
document.getElementById('nodePanel').classList.remove('hidden');
|
|
43
111
|
document.getElementById('nodePanelControls').classList.remove('hidden');
|
|
44
|
-
|
|
112
|
+
syncNodeLockButton();
|
|
113
|
+
syncNodeFontSizeValue();
|
|
45
114
|
// Sync the opacity slider with the first selected node's current value so the
|
|
46
115
|
// slider reflects the live state rather than whatever position it was left at.
|
|
47
116
|
const slider = document.getElementById('nodeBgOpacity');
|
|
@@ -57,22 +126,27 @@ export function hideNodePanel() {
|
|
|
57
126
|
}
|
|
58
127
|
|
|
59
128
|
export function toggleNodeLock() {
|
|
60
|
-
|
|
129
|
+
const { allLocked, nodeIds, edgeIds } = selectedLockState();
|
|
130
|
+
if (!nodeIds.length && !edgeIds.length) return;
|
|
131
|
+
const nextLocked = !allLocked;
|
|
61
132
|
pushSnapshot();
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
st.selectedNodeIds.forEach((id) => {
|
|
65
|
-
st.nodes.update({ id, locked: true, fixed: { x: true, y: true }, draggable: false });
|
|
133
|
+
nodeIds.forEach((id) => {
|
|
134
|
+
st.nodes.update({ id, locked: nextLocked, fixed: nextLocked ? { x: true, y: true } : false, draggable: !nextLocked });
|
|
66
135
|
const bn = st.network && st.network.body.nodes[id];
|
|
67
136
|
if (bn) bn.refreshNeeded = true;
|
|
68
137
|
});
|
|
138
|
+
edgeIds.forEach((id) => setEdgeLocked(st.edges.get(id), nextLocked));
|
|
69
139
|
if (st.network) {
|
|
70
|
-
st.network.unselectAll();
|
|
71
140
|
st.network.redraw();
|
|
141
|
+
if (nextLocked) st.network.unselectAll();
|
|
142
|
+
}
|
|
143
|
+
if (nextLocked) {
|
|
144
|
+
st.selectedNodeIds = [];
|
|
145
|
+
st.selectedEdgeIds = [];
|
|
146
|
+
hideNodePanel();
|
|
147
|
+
} else {
|
|
148
|
+
syncNodeLockButton();
|
|
72
149
|
}
|
|
73
|
-
st.selectedNodeIds = [];
|
|
74
|
-
st.selectedEdgeIds = [];
|
|
75
|
-
hideNodePanel();
|
|
76
150
|
markDirty();
|
|
77
151
|
}
|
|
78
152
|
|
|
@@ -114,6 +188,7 @@ export function changeNodeFontSize(delta) {
|
|
|
114
188
|
st.nodes.update({ id, fontSize: newSize });
|
|
115
189
|
});
|
|
116
190
|
persistNodeStyle();
|
|
191
|
+
syncNodeFontSizeValue();
|
|
117
192
|
forceRedraw();
|
|
118
193
|
markDirty();
|
|
119
194
|
}
|
|
@@ -681,6 +681,11 @@
|
|
|
681
681
|
>
|
|
682
682
|
Aa−
|
|
683
683
|
</button>
|
|
684
|
+
<span
|
|
685
|
+
id="nodeFontSizeValue"
|
|
686
|
+
class="inline-flex items-center justify-center min-w-[2.25rem] h-6 px-1 text-[11px] font-mono text-gray-700 dark:text-gray-200 border border-gray-300 dark:border-gray-600 rounded bg-white/80 dark:bg-gray-800/80"
|
|
687
|
+
data-i18n-title="diagram.node_panel.font_size_value"
|
|
688
|
+
>13</span>
|
|
684
689
|
<button
|
|
685
690
|
id="btnNodeFontIncrease"
|
|
686
691
|
class="tool-btn !w-8 !h-6"
|
|
@@ -428,11 +428,13 @@
|
|
|
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",
|
|
435
436
|
"diagram.node_panel.font_decrease": "Decrease font size",
|
|
437
|
+
"diagram.node_panel.font_size_value": "Current font size",
|
|
436
438
|
"diagram.node_panel.font_increase": "Increase font size",
|
|
437
439
|
"diagram.node_panel.align_left": "Align left",
|
|
438
440
|
"diagram.node_panel.align_center": "Align center",
|
|
@@ -461,6 +463,7 @@
|
|
|
461
463
|
"diagram.link_panel.remove_btn": "Remove",
|
|
462
464
|
|
|
463
465
|
"diagram.edge_panel.lock": "Lock arrow",
|
|
466
|
+
"diagram.edge_panel.unlock": "Unlock arrow",
|
|
464
467
|
"diagram.edge_panel.no_arrow": "Simple line",
|
|
465
468
|
"diagram.edge_panel.arrow_to": "Directional arrow",
|
|
466
469
|
"diagram.edge_panel.arrow_both": "Bidirectional",
|
|
@@ -428,11 +428,13 @@
|
|
|
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",
|
|
435
436
|
"diagram.node_panel.font_decrease": "Réduire la police",
|
|
437
|
+
"diagram.node_panel.font_size_value": "Taille de police actuelle",
|
|
436
438
|
"diagram.node_panel.font_increase": "Agrandir la police",
|
|
437
439
|
"diagram.node_panel.align_left": "Aligner à gauche",
|
|
438
440
|
"diagram.node_panel.align_center": "Centrer horizontalement",
|
|
@@ -461,6 +463,7 @@
|
|
|
461
463
|
"diagram.link_panel.remove_btn": "Retirer",
|
|
462
464
|
|
|
463
465
|
"diagram.edge_panel.lock": "Verrouiller la flèche",
|
|
466
|
+
"diagram.edge_panel.unlock": "Déverrouiller la flèche",
|
|
464
467
|
"diagram.edge_panel.no_arrow": "Trait simple",
|
|
465
468
|
"diagram.edge_panel.arrow_to": "Flèche directionnelle",
|
|
466
469
|
"diagram.edge_panel.arrow_both": "Bidirectionnelle",
|