living-documentation 7.0.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.
- package/LICENSE +661 -0
- package/README.md +329 -0
- package/dist/bin/cli.d.ts +3 -0
- package/dist/bin/cli.d.ts.map +1 -0
- package/dist/bin/cli.js +62 -0
- package/dist/bin/cli.js.map +1 -0
- package/dist/src/frontend/admin.html +1073 -0
- package/dist/src/frontend/annotations.js +546 -0
- package/dist/src/frontend/boot.js +90 -0
- package/dist/src/frontend/config.js +19 -0
- package/dist/src/frontend/dark-mode.js +20 -0
- package/dist/src/frontend/diagram/alignment.js +161 -0
- package/dist/src/frontend/diagram/clipboard.js +172 -0
- package/dist/src/frontend/diagram/constants.js +109 -0
- package/dist/src/frontend/diagram/debug.js +43 -0
- package/dist/src/frontend/diagram/edge-panel.js +260 -0
- package/dist/src/frontend/diagram/edge-rendering.js +12 -0
- package/dist/src/frontend/diagram/grid.js +78 -0
- package/dist/src/frontend/diagram/groups.js +102 -0
- package/dist/src/frontend/diagram/history.js +153 -0
- package/dist/src/frontend/diagram/image-name-modal.js +48 -0
- package/dist/src/frontend/diagram/image-upload.js +36 -0
- package/dist/src/frontend/diagram/label-editor.js +115 -0
- package/dist/src/frontend/diagram/link-panel.js +144 -0
- package/dist/src/frontend/diagram/main.js +299 -0
- package/dist/src/frontend/diagram/network.js +1473 -0
- package/dist/src/frontend/diagram/node-panel.js +267 -0
- package/dist/src/frontend/diagram/node-rendering.js +773 -0
- package/dist/src/frontend/diagram/persistence.js +161 -0
- package/dist/src/frontend/diagram/ports.js +386 -0
- package/dist/src/frontend/diagram/selection-overlay.js +336 -0
- package/dist/src/frontend/diagram/state.js +39 -0
- package/dist/src/frontend/diagram/t.js +3 -0
- package/dist/src/frontend/diagram/toast.js +21 -0
- package/dist/src/frontend/diagram/unlock-hold.js +182 -0
- package/dist/src/frontend/diagram/zoom.js +20 -0
- package/dist/src/frontend/diagram-link-modal.js +137 -0
- package/dist/src/frontend/diagram.html +1279 -0
- package/dist/src/frontend/documents.js +373 -0
- package/dist/src/frontend/export.js +338 -0
- package/dist/src/frontend/i18n/en.json +406 -0
- package/dist/src/frontend/i18n/fr.json +406 -0
- package/dist/src/frontend/i18n.js +32 -0
- package/dist/src/frontend/image-paste.js +101 -0
- package/dist/src/frontend/index.html +2314 -0
- package/dist/src/frontend/misc.js +25 -0
- package/dist/src/frontend/new-doc-modal.js +260 -0
- package/dist/src/frontend/new-folder-modal.js +174 -0
- package/dist/src/frontend/search.js +157 -0
- package/dist/src/frontend/sidebar-helpers.js +58 -0
- package/dist/src/frontend/sidebar.js +182 -0
- package/dist/src/frontend/snippet-detect.js +25 -0
- package/dist/src/frontend/snippet-table.js +85 -0
- package/dist/src/frontend/snippet-tree.js +94 -0
- package/dist/src/frontend/snippets.js +534 -0
- package/dist/src/frontend/state.js +28 -0
- package/dist/src/frontend/utils.js +21 -0
- package/dist/src/frontend/vendor/wordcloud2.js +1187 -0
- package/dist/src/frontend/wordcloud.js +693 -0
- package/dist/src/lib/config.d.ts +17 -0
- package/dist/src/lib/config.d.ts.map +1 -0
- package/dist/src/lib/config.js +79 -0
- package/dist/src/lib/config.js.map +1 -0
- package/dist/src/lib/parser.d.ts +11 -0
- package/dist/src/lib/parser.d.ts.map +1 -0
- package/dist/src/lib/parser.js +111 -0
- package/dist/src/lib/parser.js.map +1 -0
- package/dist/src/mcp/server.d.ts +3 -0
- package/dist/src/mcp/server.d.ts.map +1 -0
- package/dist/src/mcp/server.js +986 -0
- package/dist/src/mcp/server.js.map +1 -0
- package/dist/src/mcp/tools/diagrams.d.ts +44 -0
- package/dist/src/mcp/tools/diagrams.d.ts.map +1 -0
- package/dist/src/mcp/tools/diagrams.js +245 -0
- package/dist/src/mcp/tools/diagrams.js.map +1 -0
- package/dist/src/mcp/tools/documents.d.ts +26 -0
- package/dist/src/mcp/tools/documents.d.ts.map +1 -0
- package/dist/src/mcp/tools/documents.js +127 -0
- package/dist/src/mcp/tools/documents.js.map +1 -0
- package/dist/src/mcp/tools/source.d.ts +29 -0
- package/dist/src/mcp/tools/source.d.ts.map +1 -0
- package/dist/src/mcp/tools/source.js +200 -0
- package/dist/src/mcp/tools/source.js.map +1 -0
- package/dist/src/routes/annotations.d.ts +3 -0
- package/dist/src/routes/annotations.d.ts.map +1 -0
- package/dist/src/routes/annotations.js +83 -0
- package/dist/src/routes/annotations.js.map +1 -0
- package/dist/src/routes/browse.d.ts +3 -0
- package/dist/src/routes/browse.d.ts.map +1 -0
- package/dist/src/routes/browse.js +75 -0
- package/dist/src/routes/browse.js.map +1 -0
- package/dist/src/routes/config.d.ts +3 -0
- package/dist/src/routes/config.d.ts.map +1 -0
- package/dist/src/routes/config.js +97 -0
- package/dist/src/routes/config.js.map +1 -0
- package/dist/src/routes/diagrams.d.ts +3 -0
- package/dist/src/routes/diagrams.d.ts.map +1 -0
- package/dist/src/routes/diagrams.js +69 -0
- package/dist/src/routes/diagrams.js.map +1 -0
- package/dist/src/routes/documents.d.ts +8 -0
- package/dist/src/routes/documents.d.ts.map +1 -0
- package/dist/src/routes/documents.js +332 -0
- package/dist/src/routes/documents.js.map +1 -0
- package/dist/src/routes/export.d.ts +3 -0
- package/dist/src/routes/export.d.ts.map +1 -0
- package/dist/src/routes/export.js +277 -0
- package/dist/src/routes/export.js.map +1 -0
- package/dist/src/routes/images.d.ts +3 -0
- package/dist/src/routes/images.d.ts.map +1 -0
- package/dist/src/routes/images.js +49 -0
- package/dist/src/routes/images.js.map +1 -0
- package/dist/src/routes/wordcloud.d.ts +3 -0
- package/dist/src/routes/wordcloud.d.ts.map +1 -0
- package/dist/src/routes/wordcloud.js +95 -0
- package/dist/src/routes/wordcloud.js.map +1 -0
- package/dist/src/server.d.ts +7 -0
- package/dist/src/server.d.ts.map +1 -0
- package/dist/src/server.js +76 -0
- package/dist/src/server.js.map +1 -0
- package/dist/starting-doc/.annotations.json +3 -0
- package/dist/starting-doc/.diagrams.json +1884 -0
- package/dist/starting-doc/.living-doc.json +39 -0
- package/dist/starting-doc/1_tutorial/2026_04_11_13_25_[General]_crer_vos_dossiers.md +16 -0
- package/dist/starting-doc/1_tutorial/2026_04_11_18_58_[General]_creer_un_document_dans_un_dossier.md +9 -0
- package/dist/starting-doc/1_tutorial/2026_04_12_09_00_[General]_editer_et_sauvegarder.md +39 -0
- package/dist/starting-doc/1_tutorial/2026_04_12_10_00_[General]_utiliser_les_snippets.md +71 -0
- package/dist/starting-doc/2026_04_08_20_52_[General]_welcome.md +17 -0
- package/dist/starting-doc/2026_04_11_12_55_[General]_premiers_pas.md +271 -0
- package/dist/starting-doc/2_guide/2026_04_08_00_04_[DOCUMENT]_utilisation_des_images_plein_ecran_lien_clickable.md +40 -0
- package/dist/starting-doc/2_guide/2026_04_08_23_38_[Configuration]_demarrage_de_living_documentation.md +32 -0
- package/dist/starting-doc/2_guide/2026_04_09_09_00_[NAVIGATION]_recherche_plein_texte.md +65 -0
- package/dist/starting-doc/2_guide/2026_04_09_10_00_[EXPORT]_exporter_en_pdf.md +43 -0
- package/dist/starting-doc/2_guide/2026_04_09_11_00_[Configuration]_configurer_le_panneau_admin.md +55 -0
- package/dist/starting-doc/2_guide/2026_04_09_12_00_[Configuration]_extra_files.md +68 -0
- package/dist/starting-doc/2_guide/2026_04_09_13_00_[WORDCLOUD]_word_cloud.md +54 -0
- package/dist/starting-doc/2_guide/2026_04_09_14_00_[DIAGRAM]_creer_et_lier_un_diagramme.md +77 -0
- package/dist/starting-doc/3_concept/2026_04_08_20_58_[DOCUMENTING]_ADRS.md +20 -0
- package/dist/starting-doc/3_concept/2026_04_08_22_15_[DOCUMENTING]_living_documentation.md +17 -0
- package/dist/starting-doc/3_concept/2026_04_08_22_46_[METHODOLOGY]_diataxis_architecture_du_contenu.md +16 -0
- package/dist/starting-doc/4_reference/2026_04_08_23_14_[FUNDAMENTALS]_the_living_documentation_tool.md +41 -0
- package/dist/starting-doc/4_reference/2026_04_09_01_00_[REFERENCE]_raccourcis_clavier.md +61 -0
- package/dist/starting-doc/4_reference/2026_04_09_02_00_[REFERENCE]_tokens_pattern_nommage.md +75 -0
- package/dist/starting-doc/4_reference/2026_04_09_03_00_[REFERENCE]_types_de_snippets.md +68 -0
- package/dist/starting-doc/4_reference/2026_04_11_17_31_[FUNDAMENTALS]_architecturer_une_documentation.md +12 -0
- package/dist/starting-doc/4_reference/2026_04_12_14_07_[FUNDAMENTALS]_dossiers_et_catgories.md +89 -0
- package/dist/starting-doc/images/admin_screenshot.png +0 -0
- package/dist/starting-doc/images/ajout-document.png +0 -0
- package/dist/starting-doc/images/ajouter-document-categorie.png +0 -0
- package/dist/starting-doc/images/ajouter_un_document_dans_un_dossier.png +0 -0
- package/dist/starting-doc/images/architecturer_une_documentation_reference.png +0 -0
- package/dist/starting-doc/images/cr_er_un_document.png +0 -0
- package/dist/starting-doc/images/creation-nouveau-dossier.png +0 -0
- package/dist/starting-doc/images/creer-document-context-engineering.png +0 -0
- package/dist/starting-doc/images/creer-dossier-only-tutoriel.png +0 -0
- package/dist/starting-doc/images/creer-dossier-tutoriel.png +0 -0
- package/dist/starting-doc/images/creer-dossiers-done.png +0 -0
- package/dist/starting-doc/images/creer-un-document.png +0 -0
- package/dist/starting-doc/images/creer-vos-dossiers-tutoriel.png +0 -0
- package/dist/starting-doc/images/creer-vos-dossiers.png +0 -0
- package/dist/starting-doc/images/decouverte_adrs.png +0 -0
- package/dist/starting-doc/images/diataxis.png +0 -0
- package/dist/starting-doc/images/diataxis_callout.png +0 -0
- package/dist/starting-doc/images/document-cree.png +0 -0
- package/dist/starting-doc/images/liens_snippets.png +0 -0
- package/dist/starting-doc/images/living_documentation.png +0 -0
- package/dist/starting-doc/images/npm_logo.png +0 -0
- package/dist/starting-doc/images/popup-creer-document.png +0 -0
- package/dist/starting-doc/images/popup-creer-dossier.png +0 -0
- package/dist/starting-doc/images/popup-dossier-cree.png +0 -0
- package/dist/starting-doc/images/quatre-dossiers-crees.png +0 -0
- package/dist/starting-doc/images/screenshot-living-doc.png +0 -0
- package/dist/starting-doc/images/the_living_documentation_tool.png +0 -0
- package/package.json +49 -0
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
// ── Node panel ────────────────────────────────────────────────────────────────
|
|
2
|
+
// Floating formatting toolbar for selected nodes (color, font, alignment, z-order).
|
|
3
|
+
|
|
4
|
+
import { st, markDirty } from './state.js';
|
|
5
|
+
import { SHAPE_DEFAULTS } from './node-rendering.js';
|
|
6
|
+
import { pushSnapshot } from './history.js';
|
|
7
|
+
|
|
8
|
+
// ── Last-used style persistence (per shape type) ──────────────────────────────
|
|
9
|
+
// Saves colorKey/fontSize/textAlign/textValign per shapeType to localStorage so
|
|
10
|
+
// the next shape of that type is created with the same style.
|
|
11
|
+
|
|
12
|
+
function persistNodeStyle() {
|
|
13
|
+
st.selectedNodeIds.forEach((id) => {
|
|
14
|
+
const n = st.nodes && st.nodes.get(id);
|
|
15
|
+
if (!n || !n.shapeType || n.shapeType === 'anchor') return;
|
|
16
|
+
localStorage.setItem('ld-node-style-' + n.shapeType, JSON.stringify({
|
|
17
|
+
colorKey: n.colorKey || 'c-gray',
|
|
18
|
+
fontSize: n.fontSize || null,
|
|
19
|
+
textAlign: n.textAlign || null,
|
|
20
|
+
textValign: n.textValign || null,
|
|
21
|
+
}));
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function getLastNodeStyle(shapeType) {
|
|
26
|
+
try { return JSON.parse(localStorage.getItem('ld-node-style-' + shapeType)) || {}; }
|
|
27
|
+
catch { return {}; }
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// All shapes are ctxRenderers — vis-network never re-reads the closure after
|
|
31
|
+
// nodes.update(). Force refreshNeeded + redraw so the new colorKey/fontSize/
|
|
32
|
+
// textAlign/textValign values are picked up on the next draw call via st.nodes.get(id).
|
|
33
|
+
function forceRedraw() {
|
|
34
|
+
st.selectedNodeIds.forEach((id) => {
|
|
35
|
+
const bn = st.network && st.network.body.nodes[id];
|
|
36
|
+
if (bn) bn.refreshNeeded = true;
|
|
37
|
+
});
|
|
38
|
+
if (st.network) st.network.redraw();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function showNodePanel() {
|
|
42
|
+
document.getElementById('nodePanel').classList.remove('hidden');
|
|
43
|
+
document.getElementById('nodePanelControls').classList.remove('hidden');
|
|
44
|
+
document.getElementById('btnNodeLock').classList.remove('tool-active');
|
|
45
|
+
// Sync the opacity slider with the first selected node's current value so the
|
|
46
|
+
// slider reflects the live state rather than whatever position it was left at.
|
|
47
|
+
const slider = document.getElementById('nodeBgOpacity');
|
|
48
|
+
if (slider && st.selectedNodeIds.length) {
|
|
49
|
+
const first = st.nodes.get(st.selectedNodeIds[0]);
|
|
50
|
+
const op = first && typeof first.bgOpacity === 'number' ? first.bgOpacity : 1;
|
|
51
|
+
slider.value = String(Math.round(op * 100));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function hideNodePanel() {
|
|
56
|
+
document.getElementById('nodePanel').classList.add('hidden');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function toggleNodeLock() {
|
|
60
|
+
if (!st.selectedNodeIds.length) return;
|
|
61
|
+
pushSnapshot();
|
|
62
|
+
// Locking is a one-way UI action — once locked, the only way back is the
|
|
63
|
+
// long-press on the shape itself (see unlock-hold.js).
|
|
64
|
+
st.selectedNodeIds.forEach((id) => {
|
|
65
|
+
st.nodes.update({ id, locked: true, fixed: { x: true, y: true }, draggable: false });
|
|
66
|
+
const bn = st.network && st.network.body.nodes[id];
|
|
67
|
+
if (bn) bn.refreshNeeded = true;
|
|
68
|
+
});
|
|
69
|
+
if (st.network) {
|
|
70
|
+
st.network.unselectAll();
|
|
71
|
+
st.network.redraw();
|
|
72
|
+
}
|
|
73
|
+
st.selectedNodeIds = [];
|
|
74
|
+
st.selectedEdgeIds = [];
|
|
75
|
+
hideNodePanel();
|
|
76
|
+
markDirty();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function setNodeColor(colorKey) {
|
|
80
|
+
if (!st.selectedNodeIds.length) return;
|
|
81
|
+
pushSnapshot();
|
|
82
|
+
st.selectedNodeIds.forEach((id) => {
|
|
83
|
+
const n = st.nodes.get(id);
|
|
84
|
+
if (!n) return;
|
|
85
|
+
st.nodes.update({ id, colorKey });
|
|
86
|
+
});
|
|
87
|
+
persistNodeStyle();
|
|
88
|
+
forceRedraw();
|
|
89
|
+
markDirty();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// The slider fires `input` on every step during a drag. The caller is expected
|
|
93
|
+
// to push a single snapshot on pointerdown (gesture start) so the whole drag
|
|
94
|
+
// collapses into one undoable action instead of one per step.
|
|
95
|
+
export function setNodeBgOpacity(opacity) {
|
|
96
|
+
if (!st.selectedNodeIds.length) return;
|
|
97
|
+
const clamped = Math.max(0, Math.min(1, opacity));
|
|
98
|
+
st.selectedNodeIds.forEach((id) => {
|
|
99
|
+
const n = st.nodes.get(id);
|
|
100
|
+
if (!n) return;
|
|
101
|
+
st.nodes.update({ id, bgOpacity: clamped });
|
|
102
|
+
});
|
|
103
|
+
forceRedraw();
|
|
104
|
+
markDirty();
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function changeNodeFontSize(delta) {
|
|
108
|
+
if (!st.selectedNodeIds.length) return;
|
|
109
|
+
pushSnapshot();
|
|
110
|
+
st.selectedNodeIds.forEach((id) => {
|
|
111
|
+
const n = st.nodes.get(id);
|
|
112
|
+
if (!n) return;
|
|
113
|
+
const newSize = Math.max(8, Math.min(48, (n.fontSize || 13) + delta));
|
|
114
|
+
st.nodes.update({ id, fontSize: newSize });
|
|
115
|
+
});
|
|
116
|
+
persistNodeStyle();
|
|
117
|
+
forceRedraw();
|
|
118
|
+
markDirty();
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function setTextAlign(align) {
|
|
122
|
+
if (!st.selectedNodeIds.length) return;
|
|
123
|
+
pushSnapshot();
|
|
124
|
+
st.selectedNodeIds.forEach((id) => {
|
|
125
|
+
const n = st.nodes.get(id);
|
|
126
|
+
if (!n) return;
|
|
127
|
+
st.nodes.update({ id, textAlign: align });
|
|
128
|
+
});
|
|
129
|
+
persistNodeStyle();
|
|
130
|
+
forceRedraw();
|
|
131
|
+
markDirty();
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export function setTextValign(valign) {
|
|
135
|
+
if (!st.selectedNodeIds.length) return;
|
|
136
|
+
pushSnapshot();
|
|
137
|
+
st.selectedNodeIds.forEach((id) => {
|
|
138
|
+
const n = st.nodes.get(id);
|
|
139
|
+
if (!n) return;
|
|
140
|
+
st.nodes.update({ id, textValign: valign });
|
|
141
|
+
});
|
|
142
|
+
persistNodeStyle();
|
|
143
|
+
forceRedraw();
|
|
144
|
+
markDirty();
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// ── Stamp (format painter) ────────────────────────────────────────────────────
|
|
148
|
+
// Uses a transparent DOM overlay (#stampOverlay) that intercepts canvas clicks
|
|
149
|
+
// during stamp mode. This bypasses vis-network's event system entirely, avoiding
|
|
150
|
+
// the deselectNode/click ordering problems that make st.activeStamp unreliable.
|
|
151
|
+
|
|
152
|
+
const STAMP_BTNS = { color: 'btnStampColor', fontSize: 'btnStampFontSize', size: 'btnStampSize' };
|
|
153
|
+
|
|
154
|
+
export function activateStamp(type) {
|
|
155
|
+
if (!st.stampTargetIds.length) return; // targets were saved on mousedown
|
|
156
|
+
st.activeStamp = type;
|
|
157
|
+
const overlay = document.getElementById('stampOverlay');
|
|
158
|
+
overlay.style.display = 'block';
|
|
159
|
+
Object.entries(STAMP_BTNS).forEach(([t, id]) =>
|
|
160
|
+
document.getElementById(id).classList.toggle('tool-active', t === type)
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export function cancelStamp() {
|
|
165
|
+
st.activeStamp = null;
|
|
166
|
+
st.stampTargetIds = [];
|
|
167
|
+
document.getElementById('stampOverlay').style.display = 'none';
|
|
168
|
+
Object.values(STAMP_BTNS).forEach((id) =>
|
|
169
|
+
document.getElementById(id).classList.remove('tool-active')
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function applyStamp(sourceId) {
|
|
174
|
+
const source = st.nodes.get(sourceId);
|
|
175
|
+
if (!source || !st.activeStamp || !st.stampTargetIds.length) return;
|
|
176
|
+
const type = st.activeStamp;
|
|
177
|
+
const targets = [...st.stampTargetIds]; // snapshot before cancelStamp clears the array
|
|
178
|
+
cancelStamp();
|
|
179
|
+
pushSnapshot();
|
|
180
|
+
|
|
181
|
+
targets.forEach((id) => {
|
|
182
|
+
if (id === sourceId) return;
|
|
183
|
+
const target = st.nodes.get(id);
|
|
184
|
+
if (!target) return;
|
|
185
|
+
if (type === 'color') st.nodes.update({ id, colorKey: source.colorKey || 'c-gray' });
|
|
186
|
+
if (type === 'rotation') st.nodes.update({ id, rotation: source.rotation || 0 });
|
|
187
|
+
if (type === 'fontSize') st.nodes.update({ id, fontSize: source.fontSize || 13 });
|
|
188
|
+
if (type === 'size') st.nodes.update({ id, nodeWidth: source.nodeWidth || null, nodeHeight: source.nodeHeight || null });
|
|
189
|
+
const bn = st.network && st.network.body.nodes[id];
|
|
190
|
+
if (bn) bn.refreshNeeded = true;
|
|
191
|
+
});
|
|
192
|
+
if (st.network) st.network.redraw();
|
|
193
|
+
markDirty();
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// getNodeAt() is unreliable for shape:'custom' (bounding box near-zero).
|
|
197
|
+
// Manual AABB hit test using DOMtoCanvas + node dimensions, topmost node first.
|
|
198
|
+
function getNodeAtDOMPoint(domX, domY) {
|
|
199
|
+
if (!st.network || !st.nodes) return undefined;
|
|
200
|
+
const cp = st.network.DOMtoCanvas({ x: domX, y: domY });
|
|
201
|
+
for (let i = st.canonicalOrder.length - 1; i >= 0; i--) {
|
|
202
|
+
const id = st.canonicalOrder[i];
|
|
203
|
+
const n = st.nodes.get(id);
|
|
204
|
+
const bn = st.network.body.nodes[id];
|
|
205
|
+
if (!n || !bn) continue;
|
|
206
|
+
const defaults = SHAPE_DEFAULTS[n.shapeType] || [100, 40];
|
|
207
|
+
const w = n.nodeWidth || defaults[0];
|
|
208
|
+
const h = n.nodeHeight || defaults[1];
|
|
209
|
+
const rot = n.rotation || 0;
|
|
210
|
+
let hw, hh;
|
|
211
|
+
if (rot === 0) {
|
|
212
|
+
hw = w / 2; hh = h / 2;
|
|
213
|
+
} else {
|
|
214
|
+
const cos = Math.abs(Math.cos(rot)); const sin = Math.abs(Math.sin(rot));
|
|
215
|
+
hw = (w * cos + h * sin) / 2;
|
|
216
|
+
hh = (w * sin + h * cos) / 2;
|
|
217
|
+
}
|
|
218
|
+
if (Math.abs(cp.x - bn.x) <= hw && Math.abs(cp.y - bn.y) <= hh) return id;
|
|
219
|
+
}
|
|
220
|
+
return undefined;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Wire the stamp overlay click.
|
|
224
|
+
document.getElementById('stampOverlay').addEventListener('click', (e) => {
|
|
225
|
+
if (!st.activeStamp || !st.network) return;
|
|
226
|
+
const rect = document.getElementById('vis-canvas').getBoundingClientRect();
|
|
227
|
+
const nodeId = getNodeAtDOMPoint(e.clientX - rect.left, e.clientY - rect.top);
|
|
228
|
+
if (nodeId !== undefined) {
|
|
229
|
+
applyStamp(nodeId);
|
|
230
|
+
} else {
|
|
231
|
+
cancelStamp();
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
// ── Step rotation ─────────────────────────────────────────────────────────────
|
|
236
|
+
|
|
237
|
+
export function stepRotate(degrees) {
|
|
238
|
+
if (!st.selectedNodeIds.length) return;
|
|
239
|
+
pushSnapshot();
|
|
240
|
+
const delta = degrees * (Math.PI / 180);
|
|
241
|
+
st.selectedNodeIds.forEach((id) => {
|
|
242
|
+
const n = st.nodes.get(id);
|
|
243
|
+
if (!n) return;
|
|
244
|
+
st.nodes.update({ id, rotation: (n.rotation || 0) + delta });
|
|
245
|
+
const bn = st.network && st.network.body.nodes[id];
|
|
246
|
+
if (bn) bn.refreshNeeded = true;
|
|
247
|
+
});
|
|
248
|
+
if (st.network) st.network.redraw();
|
|
249
|
+
markDirty();
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
export function changeZOrder(direction) {
|
|
253
|
+
// direction: +1 = bring to front (last in canonicalOrder = drawn on top)
|
|
254
|
+
// -1 = send to back (first in canonicalOrder = drawn below)
|
|
255
|
+
if (!st.selectedNodeIds.length) return;
|
|
256
|
+
pushSnapshot();
|
|
257
|
+
st.selectedNodeIds.forEach((id) => {
|
|
258
|
+
const idx = st.canonicalOrder.indexOf(id);
|
|
259
|
+
if (idx === -1) return;
|
|
260
|
+
st.canonicalOrder.splice(idx, 1);
|
|
261
|
+
if (direction > 0) st.canonicalOrder.push(id);
|
|
262
|
+
else st.canonicalOrder.unshift(id);
|
|
263
|
+
});
|
|
264
|
+
st.network.redraw();
|
|
265
|
+
st.network.selectNodes(st.selectedNodeIds);
|
|
266
|
+
markDirty();
|
|
267
|
+
}
|