doc-survival-kit 2.0.0 → 3.11.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.

Potentially problematic release.


This version of doc-survival-kit might be problematic. Click here for more details.

package/diagram.js DELETED
@@ -1,2556 +0,0 @@
1
- // ══════════════════════════════════════
2
- // mesDiagrammes — Éditeur SVG
3
- // ══════════════════════════════════════
4
-
5
- // ── État global ──
6
- var currentTool = "select";
7
- var diagramsList = [];
8
- var currentDiagramId = null;
9
- var diagNavStack = [];
10
- var diagExpandedIds = {};
11
- var pendingParentId = null;
12
- var pendingNavDiagId = null;
13
- var viewTransform = { x: 60, y: 60, scale: 1 };
14
- var selectedId = null;
15
- var selectedType = null; // "shape" | "arrow"
16
- var selectedIds = []; // multi-sélection (ids de formes)
17
- var rubberBandState = null; // { sx, sy } pendant le lasso
18
- var clipboard = []; // formes copiées (Ctrl+C / Ctrl+V)
19
- var pendingImageBlob = null; // image en attente de sélection du dossier
20
- var pendingNewDiagram = false; // true pendant la saisie du nom d'un nouveau diagramme
21
- var dragState = null;
22
- var panStart = null;
23
- var arrowSrcId = null;
24
- var editingShapeId = null;
25
- var editingTableCell = null; // { row, col } | null
26
- var pickMode = null; // "fontSize" | "fullStyle" | null
27
- var pickTargetIds = []; // selectedIds sauvegardés pendant le pick
28
- var lastClickTime = 0;
29
- var lastClickShapeId = null;
30
- var lastClickArrowId = null;
31
- var editingArrowId = null;
32
- var boardLocked = false;
33
- var diagDragSrcId = null;
34
- var historyStack = [];
35
- var MAX_HISTORY = 50;
36
-
37
- // ── Helpers tableau ──
38
- function getColWidths(shape) {
39
- var cols = shape.cols || 3;
40
- if (shape.colWidths && shape.colWidths.length === cols) return shape.colWidths;
41
- var w = shape.w / cols;
42
- var result = [];
43
- for (var i = 0; i < cols; i++) result.push(w);
44
- return result;
45
- }
46
-
47
- // xOffsets[i] = x du début de la colonne i (relatif à shape.x)
48
- function getColOffsets(shape) {
49
- var cw = getColWidths(shape);
50
- var offsets = [0];
51
- for (var i = 0; i < cw.length - 1; i++) offsets.push(offsets[i] + cw[i]);
52
- return offsets;
53
- }
54
-
55
- // ── Palette ──
56
- var COLORS = {
57
- "t-green": { fill: "rgba(209,250,229,0.75)", stroke: "#059669", text: "#047857" },
58
- "t-violet": { fill: "rgba(237,233,254,0.75)", stroke: "#7c3aed", text: "#6d28d9" },
59
- "t-amber": { fill: "rgba(254,243,199,0.75)", stroke: "#d97706", text: "#b45309" },
60
- "t-sky": { fill: "rgba(224,242,254,0.75)", stroke: "#0284c7", text: "#0369a1" },
61
- "t-rose": { fill: "rgba(255,228,230,0.75)", stroke: "#e11d48", text: "#be123c" },
62
- "t-teal": { fill: "rgba(204,251,241,0.75)", stroke: "#0d9488", text: "#0f766e" },
63
- "t-white": { fill: "rgba(255,255,255,0.95)", stroke: "#d4d4d4", text: "#404040" },
64
- };
65
- var DEFAULT_COLOR = "t-sky";
66
-
67
- var DEFAULT_SIZES = {
68
- rect: { w: 130, h: 50 },
69
- rounded: { w: 130, h: 50 },
70
- db: { w: 100, h: 65 },
71
- cloud: { w: 110, h: 65 },
72
- nuage: { w: 121, h: 96 },
73
- text: { w: 110, h: 34 },
74
- postit: { w: 130, h: 110 },
75
- actor: { w: 60, h: 90 },
76
- table: { w: 210, h: 120 },
77
- };
78
-
79
- // ── Helpers ──
80
- function escDiag(s) {
81
- return String(s)
82
- .replace(/&/g, "&amp;")
83
- .replace(/</g, "&lt;")
84
- .replace(/>/g, "&gt;")
85
- .replace(/"/g, "&quot;");
86
- }
87
-
88
- function createSVGEl(tag) {
89
- return document.createElementNS("http://www.w3.org/2000/svg", tag);
90
- }
91
-
92
- // Découpe le texte d'un post-it en lignes qui tiennent dans maxWidth px.
93
- // Respecte les \n explicites, puis fait du word-wrap sur les mots.
94
- var _measureCanvas = null;
95
- function measureText(str, fontSize, fontWeight) {
96
- if (!_measureCanvas) _measureCanvas = document.createElement("canvas");
97
- var ctx = _measureCanvas.getContext("2d");
98
- ctx.font = (fontWeight || "600") + " " + (fontSize || 12) + "px \"Segoe UI\",system-ui,sans-serif";
99
- return ctx.measureText(str).width;
100
- }
101
-
102
- function wrapPostitLines(text, maxWidth, fontSize) {
103
- var fs = fontSize || 12;
104
- var result = [];
105
- (text || "").split("\n").forEach(function (line) {
106
- if (line === "") { result.push(""); return; }
107
- var words = line.split(" ");
108
- var current = "";
109
- words.forEach(function (word) {
110
- // Si le mot seul dépasse maxWidth, le couper caractère par caractère
111
- if (measureText(word, fs, "600") > maxWidth) {
112
- if (current !== "") { result.push(current); current = ""; }
113
- var chunk = "";
114
- for (var ci = 0; ci < word.length; ci++) {
115
- var chunkTest = chunk + word[ci];
116
- if (measureText(chunkTest, fs, "600") > maxWidth) {
117
- if (chunk !== "") result.push(chunk);
118
- chunk = word[ci];
119
- } else {
120
- chunk = chunkTest;
121
- }
122
- }
123
- current = chunk;
124
- return;
125
- }
126
- var test = current ? current + " " + word : word;
127
- if (current && measureText(test, fs, "600") > maxWidth) {
128
- result.push(current);
129
- current = word;
130
- } else {
131
- current = test;
132
- }
133
- });
134
- if (current !== "") result.push(current);
135
- });
136
- return result;
137
- }
138
-
139
- function findDiagramById(id, list) {
140
- if (!list) return null;
141
- for (var i = 0; i < list.length; i++) {
142
- if (String(list[i].id) === String(id)) return list[i];
143
- var found = findDiagramById(id, list[i].children);
144
- if (found) return found;
145
- }
146
- return null;
147
- }
148
-
149
- function findParentListOf(id, list) {
150
- if (!list) return null;
151
- for (var i = 0; i < list.length; i++) {
152
- if (String(list[i].id) === String(id)) return { list: list, index: i };
153
- var found = findParentListOf(id, list[i].children);
154
- if (found) return found;
155
- }
156
- return null;
157
- }
158
-
159
- function flattenDiagrams(list, result) {
160
- result = result || [];
161
- (list || []).forEach(function (d) {
162
- result.push(d);
163
- flattenDiagrams(d.children, result);
164
- });
165
- return result;
166
- }
167
-
168
- function calcMaxExpandedDepth(list, depth) {
169
- var max = depth;
170
- (list || []).forEach(function (d) {
171
- if (d.children && d.children.length > 0 && diagExpandedIds[String(d.id)]) {
172
- var childMax = calcMaxExpandedDepth(d.children, depth + 1);
173
- max = Math.max(max, childMax);
174
- }
175
- });
176
- return max;
177
- }
178
-
179
- function updateSidebarWidth() {
180
- var depth = calcMaxExpandedDepth(diagramsList, 0);
181
- var totalW = 220 + depth * 14;
182
- var panel = document.getElementById("diagramListPanel");
183
- if (!panel) return;
184
- panel.style.width = totalW + "px";
185
- }
186
-
187
- function getAncestorPath(targetId, list, path) {
188
- path = path || [];
189
- for (var i = 0; i < (list || []).length; i++) {
190
- var d = list[i];
191
- if (String(d.id) === String(targetId)) return path;
192
- var childPath = getAncestorPath(targetId, d.children, path.concat([String(d.id)]));
193
- if (childPath !== null) return childPath;
194
- }
195
- return null;
196
- }
197
-
198
- function updateBackBtn() {
199
- var btn = document.getElementById("btnDiagBack");
200
- if (!btn) return;
201
- btn.style.display = diagNavStack.length > 0 ? "inline-flex" : "none";
202
- }
203
-
204
- function getCurrentDiagram() {
205
- if (currentDiagramId !== null) {
206
- var found = findDiagramById(currentDiagramId, diagramsList);
207
- if (found) return found;
208
- }
209
- return diagramsList[0] || null;
210
- }
211
-
212
- // ── Persistance localStorage ──
213
- function loadDiagrammes() {
214
- try {
215
- var stored = JSON.parse(localStorage.getItem("mes_diagrammes"));
216
- if (stored && stored.length) {
217
- var migrate = function (list) {
218
- list.forEach(function (d) {
219
- if (!d.children) d.children = [];
220
- migrate(d.children);
221
- });
222
- };
223
- migrate(stored);
224
- return stored;
225
- }
226
- } catch (e) {}
227
- var defaults = JSON.parse(JSON.stringify(diagrammesDefaut));
228
- var migrate2 = function (list) {
229
- list.forEach(function (d) {
230
- if (!d.children) d.children = [];
231
- migrate2(d.children);
232
- });
233
- };
234
- migrate2(defaults);
235
- return defaults;
236
- }
237
-
238
- function saveDiagrammes() {
239
- localStorage.setItem("mes_diagrammes", JSON.stringify(diagramsList));
240
- checkDiffDiagrammes();
241
- }
242
-
243
- function pushHistory() {
244
- historyStack.push(JSON.stringify(diagramsList));
245
- if (historyStack.length > MAX_HISTORY) historyStack.shift();
246
- }
247
-
248
- function undoAction() {
249
- if (historyStack.length === 0) return;
250
- diagramsList = JSON.parse(historyStack.pop());
251
- if (!findDiagramById(currentDiagramId, diagramsList)) {
252
- currentDiagramId = diagramsList[0] ? String(diagramsList[0].id) : null;
253
- }
254
- saveDiagrammes();
255
- renderAll();
256
- renderDiagramList();
257
- }
258
-
259
- function checkDiffDiagrammes() {
260
- var stored = localStorage.getItem("mes_diagrammes");
261
- var diff = stored !== JSON.stringify(diagrammesDefaut);
262
- document.getElementById("btnSaveDiagram").style.display = diff ? "inline-flex" : "none";
263
- }
264
-
265
- // ── Verrouillage par diagramme ──
266
- var LOCK_OPEN_SVG = '<svg width="14" height="16" viewBox="0 0 14 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="1" y="7" width="12" height="8" rx="2"/><path d="M4 7V4.5a3 3 0 0 1 6 0"/></svg>';
267
- var LOCK_CLOSED_SVG = '<svg width="14" height="16" viewBox="0 0 14 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="1" y="7" width="12" height="8" rx="2"/><path d="M4 7V5a3 3 0 0 1 6 0v2"/></svg>';
268
-
269
- function getLockMap() {
270
- try { return JSON.parse(localStorage.getItem("diagrammes_lock") || "{}"); } catch(e) { return {}; }
271
- }
272
- function saveCurrentLock() {
273
- var diag = getCurrentDiagram();
274
- if (!diag) return;
275
- var map = getLockMap();
276
- map[diag.id] = boardLocked;
277
- localStorage.setItem("diagrammes_lock", JSON.stringify(map));
278
- }
279
- function restoreLockForDiagram(diagId) {
280
- var diag = findDiagramById(diagId, diagramsList);
281
- if (!diag) return;
282
- var map = getLockMap();
283
- boardLocked = map[diag.id] === true;
284
- }
285
- function toggleBoardLock() {
286
- boardLocked = !boardLocked;
287
- saveCurrentLock();
288
- if (boardLocked) {
289
- selectedId = null; selectedType = null;
290
- selectedIds = [];
291
- document.getElementById("colorPanel").style.display = "none";
292
- renderAll();
293
- }
294
- updateLockBtn();
295
- }
296
- function updateLockBtn() {
297
- var btn = document.getElementById("btnLock");
298
- if (!btn) return;
299
- btn.innerHTML = boardLocked ? LOCK_CLOSED_SVG : LOCK_OPEN_SVG;
300
- btn.title = boardLocked ? "Déverrouiller le diagramme" : "Verrouiller le diagramme";
301
- btn.classList.toggle("active", boardLocked);
302
- }
303
-
304
- // ── Zoom par diagramme ──
305
- function getZoomMap() {
306
- try { return JSON.parse(localStorage.getItem("diagrammes_zoom") || "{}"); } catch(e) { return {}; }
307
- }
308
- function saveCurrentZoom() {
309
- var diag = getCurrentDiagram();
310
- if (!diag) return;
311
- var map = getZoomMap();
312
- map[diag.id] = viewTransform.scale;
313
- localStorage.setItem("diagrammes_zoom", JSON.stringify(map));
314
- }
315
- function restoreZoomForDiagram(diagId) {
316
- var diag = findDiagramById(diagId, diagramsList);
317
- if (!diag) return;
318
- var map = getZoomMap();
319
- viewTransform.scale = map[diag.id] !== undefined ? map[diag.id] : 1;
320
- }
321
-
322
- // ── File System Access API ──
323
- var IDB_KEY_DIAG = "diagrammes";
324
- var IDB_KEY_DIR = "diagrammes_dir";
325
-
326
- function ouvrirDB_Diag() {
327
- return new Promise(function (resolve, reject) {
328
- var req = indexedDB.open("doc-survival-kit-db", 1);
329
- req.onupgradeneeded = function (e) {
330
- e.target.result.createObjectStore("fileHandles");
331
- };
332
- req.onsuccess = function (e) { resolve(e.target.result); };
333
- req.onerror = function (e) { reject(e.target.error); };
334
- });
335
- }
336
-
337
- function sauvegarderHandle_Diag(handle) {
338
- return ouvrirDB_Diag().then(function (db) {
339
- return new Promise(function (resolve, reject) {
340
- var tx = db.transaction("fileHandles", "readwrite");
341
- tx.objectStore("fileHandles").put(handle, IDB_KEY_DIAG);
342
- tx.oncomplete = resolve;
343
- tx.onerror = function (e) { reject(e.target.error); };
344
- });
345
- });
346
- }
347
-
348
- function recupererHandle_Diag() {
349
- return ouvrirDB_Diag().then(function (db) {
350
- return new Promise(function (resolve, reject) {
351
- var tx = db.transaction("fileHandles", "readonly");
352
- var req = tx.objectStore("fileHandles").get(IDB_KEY_DIAG);
353
- req.onsuccess = function (e) { resolve(e.target.result || null); };
354
- req.onerror = function (e) { reject(e.target.error); };
355
- });
356
- });
357
- }
358
-
359
- function sauvegarderDirHandle(handle) {
360
- return ouvrirDB_Diag().then(function (db) {
361
- return new Promise(function (resolve, reject) {
362
- var tx = db.transaction("fileHandles", "readwrite");
363
- tx.objectStore("fileHandles").put(handle, IDB_KEY_DIR);
364
- tx.oncomplete = resolve;
365
- tx.onerror = function (e) { reject(e.target.error); };
366
- });
367
- });
368
- }
369
-
370
- function recupererDirHandle() {
371
- return ouvrirDB_Diag().then(function (db) {
372
- return new Promise(function (resolve, reject) {
373
- var tx = db.transaction("fileHandles", "readonly");
374
- var req = tx.objectStore("fileHandles").get(IDB_KEY_DIR);
375
- req.onsuccess = function (e) { resolve(e.target.result || null); };
376
- req.onerror = function (e) { reject(e.target.error); };
377
- });
378
- });
379
- }
380
-
381
- function enregistrerDiagrammes() {
382
- if (!("showDirectoryPicker" in window)) return;
383
- recupererHandle_Diag().then(function (handle) {
384
- if (!handle) {
385
- document.getElementById("erreurFichierDiag").style.display = "none";
386
- document.getElementById("modalPremiereSauvegardeDiag").classList.add("open");
387
- } else {
388
- ecrireFichierDiag(handle);
389
- }
390
- });
391
- }
392
-
393
- function fermerModalSauvegardeDiag() {
394
- document.getElementById("modalPremiereSauvegardeDiag").classList.remove("open");
395
- }
396
-
397
- function ouvrirSelecteurFichierDiag() {
398
- fermerModalSauvegardeDiag();
399
- var capturedDir = null;
400
- window.showDirectoryPicker()
401
- .then(function (dir) {
402
- capturedDir = dir;
403
- sauvegarderDirHandle(dir);
404
- return dir.getFileHandle("diagrammes.js");
405
- })
406
- .then(function (handle) {
407
- document.getElementById("erreurFichierDiag").style.display = "none";
408
- return sauvegarderHandle_Diag(handle).then(function () {
409
- return ecrireFichierDiag(handle);
410
- }).then(function () {
411
- if (pendingImageBlob && capturedDir) {
412
- var blob = pendingImageBlob;
413
- pendingImageBlob = null;
414
- enregistrerImageDansBoard(blob, capturedDir);
415
- }
416
- });
417
- })
418
- .catch(function (err) {
419
- if (err.name === "NotFoundError") {
420
- document.getElementById("erreurFichierDiag").style.display = "block";
421
- document.getElementById("modalPremiereSauvegardeDiag").classList.add("open");
422
- } else if (err.name !== "AbortError") {
423
- console.error(err);
424
- }
425
- });
426
- }
427
-
428
- function ecrireFichierDiag(handle) {
429
- return handle.queryPermission({ mode: "readwrite" })
430
- .then(function (p) {
431
- if (p !== "granted") return handle.requestPermission({ mode: "readwrite" });
432
- return p;
433
- })
434
- .then(function (p) {
435
- if (p !== "granted") return;
436
- var content = "var diagrammesDefaut = " + JSON.stringify(diagramsList, null, 2) + ";\n";
437
- return handle.createWritable().then(function (w) {
438
- return w.write(content).then(function () { return w.close(); });
439
- }).then(function () {
440
- diagrammesDefaut = JSON.parse(JSON.stringify(diagramsList));
441
- checkDiffDiagrammes();
442
- });
443
- })
444
- .catch(function (err) { console.error(err); });
445
- }
446
-
447
- // ── Image paste ──
448
- function handleImagePaste(blob) {
449
- recupererDirHandle().then(function (dirHandle) {
450
- if (!dirHandle) {
451
- pendingImageBlob = blob;
452
- document.getElementById("erreurFichierDiag").style.display = "none";
453
- document.getElementById("modalPremiereSauvegardeDiag").classList.add("open");
454
- } else {
455
- dirHandle.queryPermission({ mode: "readwrite" }).then(function (p) {
456
- if (p !== "granted") return dirHandle.requestPermission({ mode: "readwrite" }).then(function (p2) { return p2; });
457
- return p;
458
- }).then(function (p) {
459
- if (p === "granted") enregistrerImageDansBoard(blob, dirHandle);
460
- });
461
- }
462
- });
463
- }
464
-
465
- function enregistrerImageDansBoard(blob, dirHandle) {
466
- var filename = "img_" + Date.now() + ".png";
467
- var src = "images/" + filename;
468
- dirHandle.getDirectoryHandle("images", { create: true })
469
- .then(function (imagesDir) {
470
- return imagesDir.getFileHandle(filename, { create: true });
471
- })
472
- .then(function (fileHandle) {
473
- return fileHandle.createWritable().then(function (w) {
474
- return w.write(blob).then(function () { return w.close(); });
475
- });
476
- })
477
- .then(function () {
478
- // Lire les dimensions réelles de l'image
479
- var url = URL.createObjectURL(blob);
480
- var img = new Image();
481
- img.onload = function () {
482
- URL.revokeObjectURL(url);
483
- var maxW = 600;
484
- var w = Math.min(img.naturalWidth, maxW);
485
- var h = Math.round(img.naturalHeight * (w / img.naturalWidth));
486
- // Centrer sur la vue courante
487
- var svg = document.getElementById("canvas");
488
- var r = svg.getBoundingClientRect();
489
- var cx = Math.round((r.width / 2 - viewTransform.x) / viewTransform.scale - w / 2);
490
- var cy = Math.round((r.height / 2 - viewTransform.y) / viewTransform.scale - h / 2);
491
- var diag = getCurrentDiagram();
492
- var shape = { id: "s" + Date.now(), type: "image", x: cx, y: cy, w: w, h: h, text: "", color: "", src: src };
493
- diag.shapes.push(shape);
494
- selectedIds = [shape.id]; selectedId = shape.id; selectedType = "shape";
495
- saveDiagrammes();
496
- renderAll();
497
- document.getElementById("colorPanel").style.display = "none";
498
- };
499
- img.src = url;
500
- })
501
- .catch(function (err) { console.error("Image paste error:", err); });
502
- }
503
-
504
- // ── Coordonnées SVG ──
505
- function svgPoint(clientX, clientY) {
506
- var svg = document.getElementById("canvas");
507
- var rect = svg.getBoundingClientRect();
508
- return {
509
- x: (clientX - rect.left - viewTransform.x) / viewTransform.scale,
510
- y: (clientY - rect.top - viewTransform.y) / viewTransform.scale,
511
- };
512
- }
513
-
514
- function updateViewport() {
515
- var vp = document.getElementById("viewport");
516
- vp.setAttribute(
517
- "transform",
518
- "translate(" + viewTransform.x + "," + viewTransform.y + ") scale(" + viewTransform.scale + ")"
519
- );
520
- document.getElementById("zoomLevel").textContent =
521
- Math.round(viewTransform.scale * 100) + "%";
522
- updateTableOverlay();
523
- }
524
-
525
- // ── Zoom ──
526
- function zoomIn() { applyZoom(1.2, null, null); }
527
- function zoomOut() { applyZoom(1 / 1.2, null, null); }
528
- function resetZoom() { viewTransform = { x: 60, y: 60, scale: 1 }; updateViewport(); saveCurrentZoom(); }
529
-
530
- function applyZoom(factor, cx, cy) {
531
- var newScale = Math.min(4, Math.max(0.15, viewTransform.scale * factor));
532
- if (cx !== null && cy !== null) {
533
- viewTransform.x = cx - (cx - viewTransform.x) * (newScale / viewTransform.scale);
534
- viewTransform.y = cy - (cy - viewTransform.y) * (newScale / viewTransform.scale);
535
- }
536
- viewTransform.scale = newScale;
537
- updateViewport();
538
- saveCurrentZoom();
539
- }
540
-
541
- // ── Rendu des formes ──
542
- function renderShape(shape) {
543
- var c = COLORS[shape.color] || COLORS[DEFAULT_COLOR];
544
- var isSel = selectedIds.indexOf(shape.id) !== -1;
545
- var stroke = isSel ? "#f97316" : c.stroke;
546
- var sw = isSel ? 2.5 : 1.5;
547
-
548
- var g = createSVGEl("g");
549
- g.setAttribute("data-id", shape.id);
550
- g.setAttribute("data-type", "shape");
551
- g.classList.add("shape-group");
552
-
553
- // ── Fond selon le type ──
554
- if (shape.type === "rect" || shape.type === "rounded") {
555
- var rx = shape.type === "rounded" ? 12 : 3;
556
- var r = createSVGEl("rect");
557
- r.setAttribute("x", shape.x); r.setAttribute("y", shape.y);
558
- r.setAttribute("width", shape.w); r.setAttribute("height", shape.h);
559
- r.setAttribute("rx", rx);
560
- r.setAttribute("fill", c.fill); r.setAttribute("stroke", stroke);
561
- r.setAttribute("stroke-width", sw);
562
- g.appendChild(r);
563
-
564
- } else if (shape.type === "db") {
565
- var ry = Math.min(12, shape.h * 0.2);
566
- var cx = shape.x + shape.w / 2;
567
- // Corps : path latéral + arc bas
568
- var body = createSVGEl("path");
569
- body.setAttribute("d", [
570
- "M", shape.x, shape.y + ry,
571
- "L", shape.x, shape.y + shape.h - ry,
572
- "A", shape.w / 2, ry, 0, 0, 0, shape.x + shape.w, shape.y + shape.h - ry,
573
- "L", shape.x + shape.w, shape.y + ry,
574
- "A", shape.w / 2, ry, 0, 0, 0, shape.x, shape.y + ry,
575
- "Z",
576
- ].join(" "));
577
- body.setAttribute("fill", c.fill);
578
- body.setAttribute("stroke", stroke);
579
- body.setAttribute("stroke-width", sw);
580
- g.appendChild(body);
581
- // Ellipse du haut (face visible)
582
- var topEl = createSVGEl("ellipse");
583
- topEl.setAttribute("cx", cx); topEl.setAttribute("cy", shape.y + ry);
584
- topEl.setAttribute("rx", shape.w / 2); topEl.setAttribute("ry", ry);
585
- topEl.setAttribute("fill", c.fill);
586
- topEl.setAttribute("stroke", stroke); topEl.setAttribute("stroke-width", sw);
587
- g.appendChild(topEl);
588
-
589
- } else if (shape.type === "cloud") {
590
- // Ellipse en trait continu = service externe / cloud
591
- var el = createSVGEl("ellipse");
592
- el.setAttribute("cx", shape.x + shape.w / 2);
593
- el.setAttribute("cy", shape.y + shape.h / 2);
594
- el.setAttribute("rx", shape.w / 2);
595
- el.setAttribute("ry", shape.h / 2);
596
- el.setAttribute("fill", c.fill);
597
- el.setAttribute("stroke", stroke);
598
- el.setAttribute("stroke-width", sw);
599
- g.appendChild(el);
600
-
601
- } else if (shape.type === "nuage") {
602
- // Nuage — contour extérieur du Bootstrap cloud icon (viewBox 0 0 16 16)
603
- // Le path se referme exactement : endpoint = startpoint, Z explicite
604
- var nuageG = createSVGEl("g");
605
- nuageG.setAttribute("transform",
606
- "translate(" + shape.x + "," + shape.y + ") scale(" + (shape.w / 16) + "," + (shape.h / 16) + ")"
607
- );
608
- var nuagePathEl = createSVGEl("path");
609
- nuagePathEl.setAttribute("d",
610
- "M4.406 3.342A5.53 5.53 0 0 1 8 2c2.69 0 4.923 2 5.166 4.579" +
611
- "C14.758 6.804 16 8.137 16 9.773 16 11.569 14.502 13 12.687 13" +
612
- "H3.781C1.708 13 0 11.366 0 9.318" +
613
- "c0-1.763 1.266-3.223 2.942-3.593.143-.863.698-1.723 1.464-2.383Z"
614
- );
615
- nuagePathEl.setAttribute("fill", c.fill);
616
- nuagePathEl.setAttribute("stroke", stroke);
617
- nuagePathEl.setAttribute("stroke-width", sw);
618
- nuagePathEl.setAttribute("vector-effect", "non-scaling-stroke");
619
- nuageG.appendChild(nuagePathEl);
620
- g.appendChild(nuageG);
621
-
622
- } else if (shape.type === "actor") {
623
- // Bonhomme UML (stick figure) — pas de fond, juste les traits
624
- var acx = shape.x + shape.w / 2;
625
- var aHeadR = Math.min(shape.w * 0.20, shape.h * 0.14);
626
- var aHeadCY = shape.y + aHeadR + shape.h * 0.02;
627
- // Tête
628
- var aHead = createSVGEl("circle");
629
- aHead.setAttribute("cx", acx);
630
- aHead.setAttribute("cy", aHeadCY);
631
- aHead.setAttribute("r", aHeadR);
632
- aHead.setAttribute("fill", c.fill);
633
- aHead.setAttribute("stroke", stroke);
634
- aHead.setAttribute("stroke-width", sw);
635
- g.appendChild(aHead);
636
- // Corps
637
- var aBodyTop = aHeadCY + aHeadR;
638
- var aBodyBot = shape.y + shape.h * 0.60;
639
- var aBody = createSVGEl("line");
640
- aBody.setAttribute("x1", acx); aBody.setAttribute("y1", aBodyTop);
641
- aBody.setAttribute("x2", acx); aBody.setAttribute("y2", aBodyBot);
642
- aBody.setAttribute("stroke", stroke); aBody.setAttribute("stroke-width", sw);
643
- aBody.setAttribute("stroke-linecap", "round");
644
- g.appendChild(aBody);
645
- // Bras
646
- var aArmY = shape.y + shape.h * 0.38;
647
- var aArmLX = shape.x + shape.w * 0.10;
648
- var aArmRX = shape.x + shape.w * 0.90;
649
- var aArms = createSVGEl("line");
650
- aArms.setAttribute("x1", aArmLX); aArms.setAttribute("y1", aArmY);
651
- aArms.setAttribute("x2", aArmRX); aArms.setAttribute("y2", aArmY);
652
- aArms.setAttribute("stroke", stroke); aArms.setAttribute("stroke-width", sw);
653
- aArms.setAttribute("stroke-linecap", "round");
654
- g.appendChild(aArms);
655
- // Jambe gauche
656
- var aLegLX = shape.x + shape.w * 0.18;
657
- var aLegRX = shape.x + shape.w * 0.82;
658
- var aLegBotY = shape.y + shape.h * 0.82;
659
- var aLegL = createSVGEl("line");
660
- aLegL.setAttribute("x1", acx); aLegL.setAttribute("y1", aBodyBot);
661
- aLegL.setAttribute("x2", aLegLX); aLegL.setAttribute("y2", aLegBotY);
662
- aLegL.setAttribute("stroke", stroke); aLegL.setAttribute("stroke-width", sw);
663
- aLegL.setAttribute("stroke-linecap", "round");
664
- g.appendChild(aLegL);
665
- // Jambe droite
666
- var aLegR = createSVGEl("line");
667
- aLegR.setAttribute("x1", acx); aLegR.setAttribute("y1", aBodyBot);
668
- aLegR.setAttribute("x2", aLegRX); aLegR.setAttribute("y2", aLegBotY);
669
- aLegR.setAttribute("stroke", stroke); aLegR.setAttribute("stroke-width", sw);
670
- aLegR.setAttribute("stroke-linecap", "round");
671
- g.appendChild(aLegR);
672
-
673
- } else if (shape.type === "table") {
674
- var tRows = shape.rows || 3;
675
- var tCols = shape.cols || 3;
676
- var tCells = shape.cells || [];
677
- var tColWidths = getColWidths(shape);
678
- var tColOffsets = getColOffsets(shape);
679
- var cellH = shape.h / tRows;
680
- var tfs = shape.fontSize || 12;
681
- // Fond
682
- var tBg = createSVGEl("rect");
683
- tBg.setAttribute("x", shape.x); tBg.setAttribute("y", shape.y);
684
- tBg.setAttribute("width", shape.w); tBg.setAttribute("height", shape.h);
685
- tBg.setAttribute("rx", 3);
686
- tBg.setAttribute("fill", c.fill); tBg.setAttribute("stroke", stroke);
687
- tBg.setAttribute("stroke-width", sw);
688
- g.appendChild(tBg);
689
- // Lignes horizontales internes
690
- for (var tri = 1; tri < tRows; tri++) {
691
- var thl = createSVGEl("line");
692
- thl.setAttribute("x1", shape.x); thl.setAttribute("y1", shape.y + tri * cellH);
693
- thl.setAttribute("x2", shape.x + shape.w); thl.setAttribute("y2", shape.y + tri * cellH);
694
- thl.setAttribute("stroke", stroke); thl.setAttribute("stroke-width", sw * 0.5);
695
- g.appendChild(thl);
696
- }
697
- // Lignes verticales internes
698
- for (var tci = 1; tci < tCols; tci++) {
699
- var tvl = createSVGEl("line");
700
- var tvlX = shape.x + tColOffsets[tci];
701
- tvl.setAttribute("x1", tvlX); tvl.setAttribute("y1", shape.y);
702
- tvl.setAttribute("x2", tvlX); tvl.setAttribute("y2", shape.y + shape.h);
703
- tvl.setAttribute("stroke", stroke); tvl.setAttribute("stroke-width", sw * 0.5);
704
- g.appendChild(tvl);
705
- }
706
- // Textes des cellules avec word wrap
707
- var clineH = Math.round(tfs * 1.33);
708
- for (var tri2 = 0; tri2 < tRows; tri2++) {
709
- for (var tci2 = 0; tci2 < tCols; tci2++) {
710
- var cellVal = (tCells[tri2] && tCells[tri2][tci2]) || "";
711
- if (!cellVal) continue;
712
- var cellW2 = tColWidths[tci2];
713
- var cpad = 6;
714
- var clines = wrapPostitLines(cellVal, cellW2 - cpad * 2, tfs);
715
- var cCenterY = shape.y + tri2 * cellH + cellH / 2;
716
- var cfirstY = cCenterY - clines.length * clineH / 2 + clineH * 0.5 + tfs * 0.5;
717
- var cellCenterX = shape.x + tColOffsets[tci2] + cellW2 / 2;
718
- var ctxt = createSVGEl("text");
719
- ctxt.setAttribute("x", cellCenterX);
720
- ctxt.setAttribute("y", cfirstY);
721
- ctxt.setAttribute("text-anchor", "middle");
722
- ctxt.setAttribute("fill", c.text);
723
- ctxt.setAttribute("font-size", tfs);
724
- ctxt.setAttribute("data-cell", tri2 + "-" + tci2);
725
- ctxt.setAttribute("font-family", '"Segoe UI",system-ui,sans-serif');
726
- ctxt.setAttribute("font-weight", "600");
727
- ctxt.setAttribute("pointer-events", "none");
728
- (function(cx, lines) {
729
- lines.forEach(function(line, i) {
730
- var ts = createSVGEl("tspan");
731
- ts.setAttribute("x", cx);
732
- if (i > 0) ts.setAttribute("dy", clineH + "px");
733
- ts.textContent = line || " ";
734
- ctxt.appendChild(ts);
735
- });
736
- })(cellCenterX, clines);
737
- g.appendChild(ctxt);
738
- }
739
- }
740
- // Poignées de redimensionnement des colonnes (chevrons au-dessus de chaque séparateur)
741
- if (isSel && selectedIds.length === 1) {
742
- for (var tsi = 1; tsi < tCols; tsi++) {
743
- var hx = shape.x + tColOffsets[tsi];
744
- var hy = shape.y - 13;
745
- var hg = createSVGEl("g");
746
- hg.setAttribute("data-col-sep", tsi - 1);
747
- hg.setAttribute("data-shape-id", shape.id);
748
- hg.style.cursor = "col-resize";
749
- // Zone de clic transparente
750
- var hArea = createSVGEl("rect");
751
- hArea.setAttribute("x", hx - 9); hArea.setAttribute("y", hy - 7);
752
- hArea.setAttribute("width", 18); hArea.setAttribute("height", 22);
753
- hArea.setAttribute("fill", "transparent");
754
- hArea.setAttribute("data-col-sep", tsi - 1);
755
- hArea.setAttribute("data-shape-id", shape.id);
756
- hg.appendChild(hArea);
757
- // Chevron ∨
758
- var chev = createSVGEl("path");
759
- chev.setAttribute("d", "M" + (hx - 5) + "," + (hy - 1) + " L" + hx + "," + (hy + 5) + " L" + (hx + 5) + "," + (hy - 1));
760
- chev.setAttribute("fill", "none");
761
- chev.setAttribute("stroke", "#f97316");
762
- chev.setAttribute("stroke-width", "1.8");
763
- chev.setAttribute("stroke-linecap", "round");
764
- chev.setAttribute("stroke-linejoin", "round");
765
- chev.setAttribute("pointer-events", "none");
766
- hg.appendChild(chev);
767
- // Trait pointillé vers la table
768
- var hvl = createSVGEl("line");
769
- hvl.setAttribute("x1", hx); hvl.setAttribute("y1", hy + 5);
770
- hvl.setAttribute("x2", hx); hvl.setAttribute("y2", shape.y);
771
- hvl.setAttribute("stroke", "#f97316");
772
- hvl.setAttribute("stroke-width", "1");
773
- hvl.setAttribute("stroke-dasharray", "3,2");
774
- hvl.setAttribute("pointer-events", "none");
775
- hg.appendChild(hvl);
776
- g.appendChild(hg);
777
- }
778
- }
779
- // Poignée de redimensionnement
780
- if (isSel && selectedIds.length === 1) {
781
- var tGrip = createSVGEl("rect");
782
- tGrip.setAttribute("x", shape.x + shape.w - 5); tGrip.setAttribute("y", shape.y + shape.h - 5);
783
- tGrip.setAttribute("width", 10); tGrip.setAttribute("height", 10);
784
- tGrip.setAttribute("rx", 2);
785
- tGrip.setAttribute("fill", "#f97316");
786
- tGrip.setAttribute("stroke", "#fff"); tGrip.setAttribute("stroke-width", 1.5);
787
- tGrip.classList.add("resize-grip");
788
- tGrip.setAttribute("data-shape-id", shape.id);
789
- g.appendChild(tGrip);
790
- }
791
- // Points de connexion
792
- var thcx = shape.x + shape.w / 2, thcy = shape.y + shape.h / 2;
793
- [[thcx, shape.y], [thcx, shape.y + shape.h], [shape.x, thcy], [shape.x + shape.w, thcy]]
794
- .forEach(function (pt) {
795
- var tdot = createSVGEl("circle");
796
- tdot.setAttribute("cx", pt[0]); tdot.setAttribute("cy", pt[1]);
797
- tdot.setAttribute("r", 5);
798
- tdot.setAttribute("fill", "#f97316");
799
- tdot.setAttribute("stroke", "#fff"); tdot.setAttribute("stroke-width", 1.5);
800
- tdot.classList.add("conn-dot");
801
- tdot.setAttribute("data-shape-id", shape.id);
802
- g.appendChild(tdot);
803
- });
804
- return g;
805
-
806
- } else if (shape.type === "postit") {
807
- var fold = 18;
808
- var body = createSVGEl("path");
809
- body.setAttribute("d", [
810
- "M", shape.x, shape.y,
811
- "L", shape.x + shape.w - fold, shape.y,
812
- "L", shape.x + shape.w, shape.y + fold,
813
- "L", shape.x + shape.w, shape.y + shape.h,
814
- "L", shape.x, shape.y + shape.h,
815
- "Z",
816
- ].join(" "));
817
- body.setAttribute("fill", c.fill);
818
- body.setAttribute("stroke", stroke);
819
- body.setAttribute("stroke-width", sw);
820
- g.appendChild(body);
821
- // Triangle du coin replié
822
- var foldTri = createSVGEl("path");
823
- foldTri.setAttribute("d", [
824
- "M", shape.x + shape.w - fold, shape.y,
825
- "L", shape.x + shape.w, shape.y + fold,
826
- "L", shape.x + shape.w - fold, shape.y + fold,
827
- "Z",
828
- ].join(" "));
829
- foldTri.setAttribute("fill", c.stroke);
830
- foldTri.setAttribute("fill-opacity", "0.25");
831
- foldTri.setAttribute("stroke", stroke);
832
- foldTri.setAttribute("stroke-width", sw * 0.7);
833
- g.appendChild(foldTri);
834
-
835
- } else if (shape.type === "image") {
836
- var imgEl = createSVGEl("image");
837
- imgEl.setAttribute("x", shape.x); imgEl.setAttribute("y", shape.y);
838
- imgEl.setAttribute("width", shape.w); imgEl.setAttribute("height", shape.h);
839
- imgEl.setAttribute("href", shape.src);
840
- imgEl.setAttribute("preserveAspectRatio", "xMidYMid meet");
841
- g.appendChild(imgEl);
842
- // Bordure de sélection
843
- if (isSel) {
844
- var selRect = createSVGEl("rect");
845
- selRect.setAttribute("x", shape.x); selRect.setAttribute("y", shape.y);
846
- selRect.setAttribute("width", shape.w); selRect.setAttribute("height", shape.h);
847
- selRect.setAttribute("fill", "none");
848
- selRect.setAttribute("stroke", "#f97316"); selRect.setAttribute("stroke-width", "2");
849
- selRect.setAttribute("stroke-dasharray", "6,3");
850
- selRect.setAttribute("pointer-events", "none");
851
- g.appendChild(selRect);
852
- }
853
-
854
- } else if (shape.type === "text") {
855
- // pas de fond — juste le texte
856
- }
857
-
858
- // ── Texte ──
859
- var textCy = shape.type === "db"
860
- ? shape.y + shape.h * 0.62
861
- : shape.type === "actor"
862
- ? shape.y + shape.h - 2
863
- : shape.y + shape.h / 2;
864
- var ta = shape.textAlign || "center";
865
- var textAnchor = ta === "left" ? "start" : ta === "right" ? "end" : "middle";
866
- var txt = createSVGEl("text");
867
- txt.setAttribute("x", shape.x + shape.w / 2);
868
- txt.setAttribute("y", textCy);
869
- txt.setAttribute("text-anchor", textAnchor);
870
- txt.setAttribute("dominant-baseline", "middle");
871
- var fs = shape.fontSize || (shape.type === "text" ? 13 : 12);
872
- txt.setAttribute("fill", shape.type === "text" ? "#292524" : c.text);
873
- txt.setAttribute("font-size", fs);
874
- txt.setAttribute("font-family", '"Segoe UI",system-ui,sans-serif');
875
- txt.setAttribute("font-weight", shape.type === "text" ? "400" : "600");
876
- txt.setAttribute("pointer-events", "none");
877
- var tv = shape.textValign || "middle";
878
- if (shape.type === "postit") {
879
- var pad = 14;
880
- var lines = wrapPostitLines(shape.text || "", shape.w - pad * 2, fs);
881
- var lineH = Math.round(fs * 1.42);
882
- var firstY = tv === "top"
883
- ? shape.y + pad + lineH * 0.5
884
- : tv === "bottom"
885
- ? shape.y + shape.h - pad - (lines.length - 1) * lineH - fs * 0.2
886
- : shape.y + (shape.h - lines.length * lineH) / 2 + lineH * 0.5 + fs * 0.3;
887
- var tspanX = ta === "left" ? shape.x + pad : ta === "right" ? shape.x + shape.w - pad : shape.x + shape.w / 2;
888
- txt.setAttribute("y", firstY);
889
- txt.removeAttribute("dominant-baseline");
890
- lines.forEach(function (line, i) {
891
- var ts = createSVGEl("tspan");
892
- ts.setAttribute("x", tspanX);
893
- if (i > 0) ts.setAttribute("dy", lineH + "px");
894
- ts.textContent = line || " ";
895
- txt.appendChild(ts);
896
- });
897
- } else if (shape.type === "actor") {
898
- var alines = wrapPostitLines(shape.text || "", shape.w * 1.2, fs);
899
- var alineH = Math.round(fs * 1.33);
900
- txt.setAttribute("text-anchor", "middle");
901
- txt.removeAttribute("dominant-baseline");
902
- alines.forEach(function (line, i) {
903
- var ts = createSVGEl("tspan");
904
- ts.setAttribute("x", shape.x + shape.w / 2);
905
- if (i > 0) ts.setAttribute("dy", alineH + "px");
906
- ts.textContent = line || " ";
907
- txt.appendChild(ts);
908
- });
909
- } else if (shape.type === "rect" || shape.type === "rounded" || shape.type === "db" || shape.type === "cloud" || shape.type === "nuage") {
910
- var wpad = shape.type === "cloud" ? Math.round(shape.w * 0.2) : shape.type === "nuage" ? Math.round(shape.w * 0.12) : 12;
911
- // Pour nuage : vpad = offset depuis le haut visible (y=2/16) + marge interne
912
- var wvpad = shape.type === "cloud" ? Math.round(shape.h * 0.12)
913
- : shape.type === "nuage" ? Math.round(shape.h * 2 / 16) + 6
914
- : 8;
915
- var wlines = wrapPostitLines(shape.text || "", shape.w - wpad * 2, fs);
916
- var wlineH = Math.round(fs * 1.33);
917
- // Pour nuage : centre de la forme visible = milieu entre y=2/16 et y=13/16
918
- var wcenterY = shape.type === "db" ? shape.y + shape.h * 0.62
919
- : shape.type === "nuage" ? shape.y + shape.h * ((2 + 13) / 2 / 16)
920
- : shape.y + shape.h / 2;
921
- var dbCapH = shape.type === "db" ? Math.min(12, shape.h * 0.2) * 2 : 0;
922
- var wfirstY = tv === "top"
923
- ? shape.y + dbCapH + wvpad + wlineH * 0.5
924
- : tv === "bottom"
925
- ? shape.y + shape.h - wvpad - (wlines.length - 1) * wlineH - fs * 0.2
926
- : wcenterY - wlines.length * wlineH / 2 + wlineH * 0.5 + fs * 0.5;
927
- var wtspanX = ta === "left" ? shape.x + wpad : ta === "right" ? shape.x + shape.w - wpad : shape.x + shape.w / 2;
928
- txt.setAttribute("x", wtspanX);
929
- txt.setAttribute("y", wfirstY);
930
- txt.removeAttribute("dominant-baseline");
931
- wlines.forEach(function (line, i) {
932
- var ts = createSVGEl("tspan");
933
- ts.setAttribute("x", wtspanX);
934
- if (i > 0) ts.setAttribute("dy", wlineH + "px");
935
- ts.textContent = line || " ";
936
- txt.appendChild(ts);
937
- });
938
- } else {
939
- txt.textContent = shape.text || "";
940
- }
941
- g.appendChild(txt);
942
-
943
- // ── Poignée de redimensionnement (coin bas-droit, sélection unique seulement) ──
944
- if (isSel && selectedIds.length === 1) {
945
- var grip = createSVGEl("rect");
946
- grip.setAttribute("x", shape.x + shape.w - 5);
947
- grip.setAttribute("y", shape.y + shape.h - 5);
948
- grip.setAttribute("width", 10); grip.setAttribute("height", 10);
949
- grip.setAttribute("rx", 2);
950
- grip.setAttribute("fill", "#f97316");
951
- grip.setAttribute("stroke", "#fff"); grip.setAttribute("stroke-width", 1.5);
952
- grip.classList.add("resize-grip");
953
- grip.setAttribute("data-shape-id", shape.id);
954
- g.appendChild(grip);
955
- }
956
-
957
- // ── Texte non rendu pour les images ──
958
- if (shape.type === "image") return g;
959
-
960
- // ── Indicateur de lien (diagramme enfant ou lien externe) ──
961
- if ((shape.linkedDiagramId || shape.externalUrl) && shape.type !== "image") {
962
- var lnkColor = shape.externalUrl ? "#0284c7" : "#f97316";
963
- var lnkCirc = createSVGEl("circle");
964
- lnkCirc.setAttribute("cx", shape.x + shape.w - 5);
965
- lnkCirc.setAttribute("cy", shape.y + 5);
966
- lnkCirc.setAttribute("r", 6);
967
- lnkCirc.setAttribute("fill", lnkColor);
968
- lnkCirc.setAttribute("stroke", "#fff");
969
- lnkCirc.setAttribute("stroke-width", 1.5);
970
- lnkCirc.setAttribute("pointer-events", "none");
971
- g.appendChild(lnkCirc);
972
- var lnkTxt = createSVGEl("text");
973
- lnkTxt.setAttribute("x", shape.x + shape.w - 5);
974
- lnkTxt.setAttribute("y", shape.y + 8.5);
975
- lnkTxt.setAttribute("text-anchor", "middle");
976
- lnkTxt.setAttribute("font-size", "8");
977
- lnkTxt.setAttribute("font-weight", "bold");
978
- lnkTxt.setAttribute("fill", "#fff");
979
- lnkTxt.setAttribute("pointer-events", "none");
980
- lnkTxt.textContent = "\u2197";
981
- g.appendChild(lnkTxt);
982
- }
983
-
984
- // ── Rotation ──
985
- if (shape.rotation) {
986
- var rcx = shape.x + shape.w / 2, rcy = shape.y + shape.h / 2;
987
- g.setAttribute("transform", "rotate(" + shape.rotation + "," + rcx + "," + rcy + ")");
988
- }
989
-
990
- // ── Points de connexion (visibles au hover et en mode flèche — sauf postit) ──
991
- if (shape.type !== "postit") {
992
- var hcx = shape.x + shape.w / 2, hcy = shape.y + shape.h / 2;
993
- [
994
- [hcx, shape.y],
995
- [hcx, shape.y + shape.h],
996
- [shape.x, hcy],
997
- [shape.x + shape.w, hcy],
998
- ].forEach(function (pt) {
999
- var dot = createSVGEl("circle");
1000
- dot.setAttribute("cx", pt[0]); dot.setAttribute("cy", pt[1]);
1001
- dot.setAttribute("r", 5);
1002
- dot.setAttribute("fill", "#f97316");
1003
- dot.setAttribute("stroke", "#fff"); dot.setAttribute("stroke-width", 1.5);
1004
- dot.classList.add("conn-dot");
1005
- dot.setAttribute("data-shape-id", shape.id);
1006
- g.appendChild(dot);
1007
- });
1008
- }
1009
-
1010
- return g;
1011
- }
1012
-
1013
- // ── Rendu d'une flèche ──
1014
- function getEdgePoint(shape, targetX, targetY) {
1015
- var cx = shape.x + shape.w / 2;
1016
- var cy = shape.y + shape.h / 2;
1017
- var lTargetX = targetX, lTargetY = targetY;
1018
- if (shape.rotation) {
1019
- var rad = -shape.rotation * Math.PI / 180;
1020
- var cos = Math.cos(rad), sin = Math.sin(rad);
1021
- var ddx = targetX - cx, ddy = targetY - cy;
1022
- lTargetX = cx + ddx * cos - ddy * sin;
1023
- lTargetY = cy + ddx * sin + ddy * cos;
1024
- }
1025
- var dx = lTargetX - cx, dy = lTargetY - cy;
1026
- var p;
1027
- if (dx === 0 && dy === 0) {
1028
- p = { x: cx, y: shape.y };
1029
- } else {
1030
- var hw = shape.w / 2, hh = shape.h / 2;
1031
- if (Math.abs(dx) * hh > Math.abs(dy) * hw) {
1032
- var sx = dx > 0 ? 1 : -1;
1033
- p = { x: cx + sx * hw, y: cy + dy * hw / Math.abs(dx) };
1034
- } else {
1035
- var sy = dy > 0 ? 1 : -1;
1036
- p = { x: cx + dx * hh / Math.abs(dy), y: cy + sy * hh };
1037
- }
1038
- }
1039
- if (shape.rotation) {
1040
- var rad2 = shape.rotation * Math.PI / 180;
1041
- var cos2 = Math.cos(rad2), sin2 = Math.sin(rad2);
1042
- var ex = p.x - cx, ey = p.y - cy;
1043
- p = { x: cx + ex * cos2 - ey * sin2, y: cy + ex * sin2 + ey * cos2 };
1044
- }
1045
- return p;
1046
- }
1047
-
1048
- function renderArrow(arrow, shapes) {
1049
- var from = shapes.find(function (s) { return s.id === arrow.from; });
1050
- var to = shapes.find(function (s) { return s.id === arrow.to; });
1051
- if (!from || !to) return null;
1052
-
1053
- var toCx = to.x + to.w / 2, toCy = to.y + to.h / 2;
1054
- var frCx = from.x + from.w / 2, frCy = from.y + from.h / 2;
1055
- var fp = getEdgePoint(from, toCx, toCy);
1056
- var tp = getEdgePoint(to, frCx, frCy);
1057
-
1058
- // Raccourcir légèrement la pointe pour ne pas chevauchner la forme
1059
- var dx = tp.x - fp.x, dy = tp.y - fp.y;
1060
- var len = Math.sqrt(dx * dx + dy * dy);
1061
- if (len > 16) {
1062
- var off = 7 / len;
1063
- tp = { x: tp.x - dx * off, y: tp.y - dy * off };
1064
- }
1065
-
1066
- var isSel = selectedId === arrow.id && selectedType === "arrow";
1067
- var stroke = isSel ? "#f97316" : "#a8a29e";
1068
- var markerId = isSel ? "arrowhead-sel" : "arrowhead";
1069
-
1070
- var g = createSVGEl("g");
1071
- g.setAttribute("data-id", arrow.id);
1072
- g.setAttribute("data-type", "arrow");
1073
- g.classList.add("arrow-group");
1074
-
1075
- var line = createSVGEl("line");
1076
- line.setAttribute("x1", fp.x); line.setAttribute("y1", fp.y);
1077
- line.setAttribute("x2", tp.x); line.setAttribute("y2", tp.y);
1078
- line.setAttribute("stroke", stroke);
1079
- line.setAttribute("stroke-width", isSel ? 2 : 1.5);
1080
- line.setAttribute("marker-end", "url(#" + markerId + ")");
1081
- g.appendChild(line);
1082
-
1083
- // Zone de clic élargie
1084
- var hit = createSVGEl("line");
1085
- hit.setAttribute("x1", fp.x); hit.setAttribute("y1", fp.y);
1086
- hit.setAttribute("x2", tp.x); hit.setAttribute("y2", tp.y);
1087
- hit.setAttribute("stroke", "transparent");
1088
- hit.setAttribute("stroke-width", 14);
1089
- g.appendChild(hit);
1090
-
1091
- // Label
1092
- if (arrow.label) {
1093
- var mx = (fp.x + tp.x) / 2, my = (fp.y + tp.y) / 2;
1094
- var lbl = createSVGEl("text");
1095
- lbl.setAttribute("x", mx); lbl.setAttribute("y", my - 7);
1096
- lbl.setAttribute("text-anchor", "middle");
1097
- lbl.setAttribute("font-size", "10");
1098
- lbl.setAttribute("font-family", '"Cascadia Code","SF Mono",Consolas,monospace');
1099
- lbl.setAttribute("fill", "#a8a29e");
1100
- lbl.setAttribute("pointer-events", "none");
1101
- lbl.textContent = arrow.label;
1102
- g.appendChild(lbl);
1103
- }
1104
-
1105
- return g;
1106
- }
1107
-
1108
- // ── Rendu complet ──
1109
- function renderAll() {
1110
- var diag = getCurrentDiagram();
1111
- if (!diag) return;
1112
-
1113
- document.getElementById("diagramTitle").value = diag.titre;
1114
-
1115
- var arrowsLayer = document.getElementById("arrowsLayer");
1116
- arrowsLayer.innerHTML = "";
1117
- (diag.arrows || []).forEach(function (a) {
1118
- var el = renderArrow(a, diag.shapes);
1119
- if (el) arrowsLayer.appendChild(el);
1120
- });
1121
-
1122
- var shapesLayer = document.getElementById("shapesLayer");
1123
- shapesLayer.innerHTML = "";
1124
- (diag.shapes || []).forEach(function (s) {
1125
- shapesLayer.appendChild(renderShape(s));
1126
- });
1127
-
1128
- updateViewport();
1129
- renderDiagramList();
1130
- syncColorPanel();
1131
- updateTableOverlay();
1132
- }
1133
-
1134
- function renderDiagramListLevel(list, depth) {
1135
- return (list || []).map(function (d) {
1136
- var isActive = String(d.id) === String(currentDiagramId);
1137
- var isExpanded = !!diagExpandedIds[String(d.id)];
1138
- var hasChildren = d.children && d.children.length > 0;
1139
- var indent = 10 + depth * 14;
1140
- var childrenHtml = (isExpanded && hasChildren)
1141
- ? '<div class="diagram-list-children">' + renderDiagramListLevel(d.children, depth + 1) + '</div>'
1142
- : '';
1143
- return (
1144
- '<div class="diagram-list-group">' +
1145
- '<div class="diagram-list-item' + (isActive ? ' active' : '') + '"' +
1146
- ' style="padding-left:' + indent + 'px"' +
1147
- ' onclick="selectDiagramme(\'' + d.id + '\', true)">' +
1148
- '<span class="diagram-list-expand" onclick="event.stopPropagation();toggleDiagExpand(\'' + d.id + '\')">' +
1149
- (hasChildren ? (isExpanded ? '&#9660;' : '&#9658;') : '<span style="display:inline-block;width:0.7em"></span>') +
1150
- '</span>' +
1151
- '<span class="diagram-list-name">' + escDiag(d.titre) + '</span>' +
1152
- '<button class="diagram-list-add" onclick="event.stopPropagation();creerEnfantDiagramme(\'' + d.id + '\')" title="Ajouter un diagramme enfant">+</button>' +
1153
- (depth > 0 || list.length > 1
1154
- ? '<button class="diagram-list-del" onclick="event.stopPropagation();supprimerDiagramme(\'' + d.id + '\')">×</button>'
1155
- : '') +
1156
- '</div>' +
1157
- childrenHtml +
1158
- '</div>'
1159
- );
1160
- }).join('');
1161
- }
1162
-
1163
- function renderDiagramList() {
1164
- var el = document.getElementById("diagramList");
1165
- if (!el) return;
1166
- el.innerHTML = renderDiagramListLevel(diagramsList, 0);
1167
- updateSidebarWidth();
1168
- }
1169
-
1170
- function toggleDiagExpand(id) {
1171
- var key = String(id);
1172
- if (diagExpandedIds[key]) {
1173
- delete diagExpandedIds[key];
1174
- } else {
1175
- diagExpandedIds[key] = true;
1176
- }
1177
- renderDiagramList();
1178
- }
1179
-
1180
- // ── Gestion des diagrammes ──
1181
- function selectDiagramme(id, clearStack) {
1182
- if (clearStack) diagNavStack = [];
1183
- saveCurrentZoom();
1184
- saveCurrentLock();
1185
- currentDiagramId = String(id);
1186
- localStorage.setItem("current_diagram_id", String(id));
1187
- selectedId = null; selectedType = null;
1188
- selectedIds = [];
1189
- pendingNavDiagId = null;
1190
- restoreZoomForDiagram(id);
1191
- restoreLockForDiagram(id);
1192
- viewTransform.x = 60;
1193
- viewTransform.y = 60;
1194
- document.getElementById("colorPanel").style.display = "none";
1195
- renderAll();
1196
- updateLockBtn();
1197
- updateBackBtn();
1198
- document.getElementById("diagramListPanel").classList.remove("open");
1199
- }
1200
-
1201
- function goBackDiagram() {
1202
- if (diagNavStack.length === 0) return;
1203
- var prevId = diagNavStack.pop();
1204
- selectDiagramme(prevId);
1205
- }
1206
-
1207
- function creerDiagramme() {
1208
- pendingParentId = null;
1209
- pendingNewDiagram = true;
1210
- var input = document.getElementById("diagramTitle");
1211
- input.value = "";
1212
- input.placeholder = window.t ? window.t.diag_new_diagram : "Nouveau diagramme";
1213
- input.focus();
1214
- input.select();
1215
- }
1216
-
1217
- function creerEnfantDiagramme(parentId) {
1218
- pendingParentId = String(parentId);
1219
- pendingNewDiagram = true;
1220
- diagExpandedIds[String(parentId)] = true;
1221
- var input = document.getElementById("diagramTitle");
1222
- input.value = "";
1223
- input.placeholder = window.t ? window.t.diag_new_diagram : "Nouveau diagramme";
1224
- input.focus();
1225
- input.select();
1226
- }
1227
-
1228
- function confirmerNouveauDiagramme() {
1229
- if (!pendingNewDiagram) return;
1230
- pendingNewDiagram = false;
1231
- var input = document.getElementById("diagramTitle");
1232
- var titre = input.value.trim() || (window.t ? window.t.diag_new_diagram : "Nouveau diagramme");
1233
- input.placeholder = "";
1234
- var d = { id: Date.now(), titre: titre, shapes: [], arrows: [], children: [] };
1235
- if (pendingParentId) {
1236
- var parent = findDiagramById(pendingParentId, diagramsList);
1237
- if (parent) {
1238
- if (!parent.children) parent.children = [];
1239
- parent.children.push(d);
1240
- } else {
1241
- diagramsList.push(d);
1242
- }
1243
- pendingParentId = null;
1244
- } else {
1245
- diagramsList.push(d);
1246
- }
1247
- saveDiagrammes();
1248
- selectDiagramme(d.id);
1249
- document.getElementById("diagramTitle").blur();
1250
- }
1251
-
1252
- function annulerNouveauDiagramme() {
1253
- if (!pendingNewDiagram) return;
1254
- pendingNewDiagram = false;
1255
- var diag = getCurrentDiagram();
1256
- var input = document.getElementById("diagramTitle");
1257
- input.value = diag ? diag.titre : "";
1258
- input.placeholder = "";
1259
- input.blur();
1260
- }
1261
-
1262
- function supprimerDiagramme(id) {
1263
- var info = findParentListOf(id, diagramsList);
1264
- if (!info) return;
1265
- if (info.list === diagramsList && info.list.length <= 1) return;
1266
- pushHistory();
1267
- info.list.splice(info.index, 1);
1268
- if (!findDiagramById(currentDiagramId, diagramsList)) {
1269
- currentDiagramId = diagramsList[0] ? String(diagramsList[0].id) : null;
1270
- localStorage.setItem("current_diagram_id", currentDiagramId || "");
1271
- }
1272
- saveDiagrammes();
1273
- renderAll();
1274
- }
1275
-
1276
- function toggleDiagramList() {
1277
- var panel = document.getElementById("diagramListPanel");
1278
- var isOpening = !panel.classList.contains("open");
1279
- panel.classList.toggle("open");
1280
- if (isOpening && currentDiagramId) {
1281
- var path = getAncestorPath(currentDiagramId, diagramsList);
1282
- if (path) {
1283
- path.forEach(function (id) { diagExpandedIds[id] = true; });
1284
- renderDiagramList();
1285
- }
1286
- }
1287
- }
1288
-
1289
- // ── Outil actif ──
1290
- function setTool(tool) {
1291
- currentTool = tool;
1292
- document.querySelectorAll(".diagram-tool[id^='tool']").forEach(function (btn) {
1293
- btn.classList.remove("active");
1294
- });
1295
- var btnId = "tool" + tool.charAt(0).toUpperCase() + tool.slice(1);
1296
- var btn = document.getElementById(btnId);
1297
- if (btn) btn.classList.add("active");
1298
-
1299
- arrowSrcId = null;
1300
- selectedIds = [];
1301
- document.getElementById("tempArrow").style.display = "none";
1302
- document.getElementById("canvas").setAttribute(
1303
- "data-tool", tool
1304
- );
1305
- if (tool !== "select") {
1306
- selectedId = null; selectedType = null;
1307
- document.getElementById("colorPanel").style.display = "none";
1308
- }
1309
- renderAll();
1310
- }
1311
-
1312
- // ── Ajout d'une forme ──
1313
- function addShape(type, x, y) {
1314
- pushHistory();
1315
- var diag = getCurrentDiagram();
1316
- var size = DEFAULT_SIZES[type] || { w: 120, h: 50 };
1317
- var shape = {
1318
- id: "s" + Date.now(),
1319
- type: type,
1320
- x: Math.round(x - size.w / 2),
1321
- y: Math.round(y - size.h / 2),
1322
- w: size.w,
1323
- h: size.h,
1324
- text: "",
1325
- color: type === "postit" ? "t-amber" : DEFAULT_COLOR,
1326
- };
1327
- if (type === "table") {
1328
- shape.rows = 3; shape.cols = 3;
1329
- shape.cells = [["","",""],["","",""],["","",""]];
1330
- var initW = shape.w / 3;
1331
- shape.colWidths = [initW, initW, initW];
1332
- }
1333
- diag.shapes.push(shape);
1334
- saveDiagrammes();
1335
- selectedId = shape.id; selectedType = "shape";
1336
- selectedIds = [shape.id];
1337
- renderAll();
1338
- document.getElementById("colorPanel").style.display = "flex";
1339
- syncColorPanel();
1340
- if (type !== "table") startTextEdit(shape.id);
1341
- }
1342
-
1343
- // ── Suppression ──
1344
- function deleteSelected() {
1345
- var diag = getCurrentDiagram();
1346
- if (selectedIds.length === 0 && !(selectedId && selectedType === "arrow")) return;
1347
- pushHistory();
1348
- if (selectedIds.length > 0) {
1349
- diag.shapes = diag.shapes.filter(function (s) { return selectedIds.indexOf(s.id) === -1; });
1350
- diag.arrows = diag.arrows.filter(function (a) {
1351
- return selectedIds.indexOf(a.from) === -1 && selectedIds.indexOf(a.to) === -1;
1352
- });
1353
- selectedIds = [];
1354
- selectedId = null; selectedType = null;
1355
- } else if (selectedId && selectedType === "arrow") {
1356
- diag.arrows = diag.arrows.filter(function (a) { return a.id !== selectedId; });
1357
- selectedId = null; selectedType = null;
1358
- } else {
1359
- return;
1360
- }
1361
- document.getElementById("colorPanel").style.display = "none";
1362
- saveDiagrammes();
1363
- renderAll();
1364
- }
1365
-
1366
- // ── Couleur ──
1367
- function setShapeColor(color) {
1368
- if (selectedIds.length === 0) return;
1369
- pushHistory();
1370
- var diag = getCurrentDiagram();
1371
- selectedIds.forEach(function (id) {
1372
- var shape = diag.shapes.find(function (s) { return s.id === id; });
1373
- if (shape) shape.color = color;
1374
- });
1375
- saveDiagrammes();
1376
- renderAll();
1377
- document.getElementById("colorPanel").style.display = "flex";
1378
- }
1379
-
1380
- function setShapeTextValign(valign) {
1381
- if (selectedIds.length === 0) return;
1382
- pushHistory();
1383
- var diag = getCurrentDiagram();
1384
- selectedIds.forEach(function (id) {
1385
- var shape = diag.shapes.find(function (s) { return s.id === id; });
1386
- if (shape) shape.textValign = valign;
1387
- });
1388
- saveDiagrammes();
1389
- renderAll();
1390
- document.getElementById("colorPanel").style.display = "flex";
1391
- }
1392
-
1393
- function setShapeTextAlign(align) {
1394
- if (selectedIds.length === 0) return;
1395
- pushHistory();
1396
- var diag = getCurrentDiagram();
1397
- selectedIds.forEach(function (id) {
1398
- var shape = diag.shapes.find(function (s) { return s.id === id; });
1399
- if (shape) shape.textAlign = align;
1400
- });
1401
- saveDiagrammes();
1402
- renderAll();
1403
- document.getElementById("colorPanel").style.display = "flex";
1404
- }
1405
-
1406
- function changeShapeFontSize(delta) {
1407
- if (selectedIds.length === 0) return;
1408
- pushHistory();
1409
- var diag = getCurrentDiagram();
1410
- selectedIds.forEach(function (id) {
1411
- var shape = diag.shapes.find(function (s) { return s.id === id; });
1412
- if (!shape) return;
1413
- var current = shape.fontSize || (shape.type === "text" ? 13 : 12);
1414
- shape.fontSize = Math.max(8, Math.min(28, current + delta));
1415
- });
1416
- saveDiagrammes();
1417
- renderAll();
1418
- document.getElementById("colorPanel").style.display = "flex";
1419
- }
1420
-
1421
- // ── Copie de style (pick mode) ──
1422
- function startPickMode(mode) {
1423
- pickTargetIds = selectedIds.slice();
1424
- pickMode = mode;
1425
- document.getElementById("canvas").style.cursor = "crosshair";
1426
- document.getElementById("btnPickFont").classList.toggle("diagram-pick-active", mode === "fontSize");
1427
- document.getElementById("btnPickStyle").classList.toggle("diagram-pick-active", mode === "fullStyle");
1428
- }
1429
-
1430
- function cancelPickMode() {
1431
- pickMode = null;
1432
- pickTargetIds = [];
1433
- document.getElementById("canvas").style.cursor = "";
1434
- document.getElementById("btnPickFont").classList.remove("diagram-pick-active");
1435
- document.getElementById("btnPickStyle").classList.remove("diagram-pick-active");
1436
- }
1437
-
1438
- function applyPickMode(srcShape) {
1439
- pushHistory();
1440
- var diag = getCurrentDiagram();
1441
- pickTargetIds.forEach(function (id) {
1442
- if (id === srcShape.id) return;
1443
- var shape = diag.shapes.find(function (s) { return s.id === id; });
1444
- if (!shape) return;
1445
- if (pickMode === "fontSize") {
1446
- shape.fontSize = srcShape.fontSize || (srcShape.type === "text" ? 13 : 12);
1447
- } else if (pickMode === "fullStyle") {
1448
- shape.fontSize = srcShape.fontSize || (srcShape.type === "text" ? 13 : 12);
1449
- shape.color = srcShape.color;
1450
- shape.type = srcShape.type;
1451
- shape.w = srcShape.w;
1452
- shape.h = srcShape.h;
1453
- shape.textAlign = srcShape.textAlign || "center";
1454
- shape.textValign = srcShape.textValign || "middle";
1455
- shape.rotation = srcShape.rotation || 0;
1456
- }
1457
- });
1458
- selectedIds = pickTargetIds.slice();
1459
- saveDiagrammes();
1460
- renderAll();
1461
- document.getElementById("colorPanel").style.display = "flex";
1462
- }
1463
-
1464
- function rotateShape(delta) {
1465
- var diag = getCurrentDiagram();
1466
- if (!diag || selectedIds.length === 0) return;
1467
- pushHistory();
1468
- selectedIds.forEach(function (id) {
1469
- var s = diag.shapes.find(function (sh) { return sh.id === id; });
1470
- if (!s || s.type === "image") return;
1471
- s.rotation = ((s.rotation || 0) + delta + 360) % 360;
1472
- });
1473
- saveDiagrammes();
1474
- renderAll();
1475
- }
1476
-
1477
- function changeShapeOrder(delta) {
1478
- var diag = getCurrentDiagram();
1479
- if (!diag || selectedIds.length === 0) return;
1480
- pushHistory();
1481
- if (delta > 0) {
1482
- // Avancer : parcourir de la fin vers le début
1483
- for (var i = diag.shapes.length - 1; i >= 0; i--) {
1484
- if (selectedIds.indexOf(diag.shapes[i].id) !== -1 && i < diag.shapes.length - 1) {
1485
- var tmp = diag.shapes[i]; diag.shapes[i] = diag.shapes[i + 1]; diag.shapes[i + 1] = tmp;
1486
- }
1487
- }
1488
- } else {
1489
- // Reculer : parcourir du début vers la fin
1490
- for (var i = 0; i < diag.shapes.length; i++) {
1491
- if (selectedIds.indexOf(diag.shapes[i].id) !== -1 && i > 0) {
1492
- var tmp = diag.shapes[i]; diag.shapes[i] = diag.shapes[i - 1]; diag.shapes[i - 1] = tmp;
1493
- }
1494
- }
1495
- }
1496
- saveDiagrammes();
1497
- renderAll();
1498
- }
1499
-
1500
- // ── Édition texte inline ──
1501
- function startTextEdit(shapeId) {
1502
- var diag = getCurrentDiagram();
1503
- var shape = diag.shapes.find(function (s) { return s.id === shapeId; });
1504
- if (!shape || shape.type === "image") return;
1505
- if (shape.type === "table") return;
1506
- editingShapeId = shapeId;
1507
-
1508
- var svg = document.getElementById("canvas");
1509
- var sr = svg.getBoundingClientRect();
1510
- var sx = shape.x * viewTransform.scale + viewTransform.x + sr.left;
1511
- var sy = shape.y * viewTransform.scale + viewTransform.y + sr.top;
1512
- var sw = shape.w * viewTransform.scale;
1513
- var sh = shape.h * viewTransform.scale;
1514
- var c = COLORS[shape.color] || COLORS[DEFAULT_COLOR];
1515
-
1516
- var useTextarea = shape.type === "postit" || shape.type === "rect" || shape.type === "rounded" || shape.type === "db" || shape.type === "cloud" || shape.type === "nuage";
1517
- if (useTextarea) {
1518
- var ta = document.getElementById("postitTextInput");
1519
- ta.value = shape.text || "";
1520
- var hpad = shape.type === "cloud" ? Math.round(sw * 0.15) : shape.type === "nuage" ? Math.round(sw * 0.14) : 8;
1521
- var vtop, vheight;
1522
- if (shape.type === "cloud") {
1523
- vtop = sy + Math.round(sh * 0.12);
1524
- vheight = sh - Math.round(sh * 0.12) * 2;
1525
- } else if (shape.type === "nuage") {
1526
- // Le nuage Bootstrap : haut visible ≈ y=2/16, bas de la zone texte ≈ y=10/16
1527
- var nuageTop = Math.round(sh * 2 / 16);
1528
- var nuageBot = Math.round(sh * 10 / 16);
1529
- vtop = sy + nuageTop;
1530
- vheight = nuageBot - nuageTop - 4;
1531
- } else if (shape.type === "db") {
1532
- var dbRy = Math.min(12, shape.h * 0.2) * viewTransform.scale;
1533
- vtop = sy + dbRy * 2 + 4;
1534
- vheight = sh - dbRy * 2 - 12;
1535
- } else {
1536
- vtop = sy + 8;
1537
- vheight = sh - 16;
1538
- }
1539
- ta.style.left = (sx + hpad) + "px";
1540
- ta.style.top = vtop + "px";
1541
- ta.style.width = (sw - hpad * 2) + "px";
1542
- ta.style.height = vheight + "px";
1543
- var editFs = shape.fontSize || (shape.type === "text" ? 13 : 12);
1544
- ta.style.fontSize = Math.max(10, editFs * viewTransform.scale) + "px";
1545
- ta.style.color = c.text;
1546
- ta.style.display = "block";
1547
- document.getElementById("shapeTextInput").style.display = "none";
1548
- document.getElementById("textOverlay").style.display = "block";
1549
- // Masquer le texte SVG pour éviter la double lecture
1550
- var sg = document.querySelector('#shapesLayer [data-id="' + shapeId + '"]');
1551
- if (sg) { var tx = sg.querySelector("text"); if (tx) tx.style.visibility = "hidden"; }
1552
- setTimeout(function () { ta.focus(); ta.select(); }, 10);
1553
- } else {
1554
- var input = document.getElementById("shapeTextInput");
1555
- input.value = shape.text || "";
1556
- if (shape.type === "actor") {
1557
- input.style.left = sx + "px";
1558
- input.style.top = (sy + sh * 0.84) + "px";
1559
- input.style.width = sw + "px";
1560
- } else {
1561
- input.style.left = (sx + sw * 0.08) + "px";
1562
- input.style.top = (sy + sh * 0.22) + "px";
1563
- input.style.width = (sw * 0.84) + "px";
1564
- }
1565
- var editFs2 = shape.fontSize || 13;
1566
- input.style.fontSize = Math.max(10, editFs2 * viewTransform.scale) + "px";
1567
- input.style.color = c.text;
1568
- input.style.display = "block";
1569
- document.getElementById("postitTextInput").style.display = "none";
1570
- document.getElementById("textOverlay").style.display = "block";
1571
- setTimeout(function () { input.focus(); input.select(); }, 10);
1572
- }
1573
- }
1574
-
1575
- function confirmTextEdit() {
1576
- var input = document.getElementById("shapeTextInput");
1577
- var diag = getCurrentDiagram();
1578
- if (editingShapeId) {
1579
- var shape = diag.shapes.find(function (s) { return s.id === editingShapeId; });
1580
- if (shape) {
1581
- if (shape.type === "table" && editingTableCell !== null) {
1582
- var tval = document.getElementById("postitTextInput").value;
1583
- var oldCellVal = (shape.cells && shape.cells[editingTableCell.row] && shape.cells[editingTableCell.row][editingTableCell.col]) || "";
1584
- if (tval !== oldCellVal) pushHistory();
1585
- if (!shape.cells) shape.cells = [];
1586
- while (shape.cells.length <= editingTableCell.row) shape.cells.push([]);
1587
- shape.cells[editingTableCell.row][editingTableCell.col] = tval;
1588
- editingTableCell = null;
1589
- } else {
1590
- var usesTextarea = shape.type === "postit" || shape.type === "rect" || shape.type === "rounded" || shape.type === "db" || shape.type === "cloud" || shape.type === "nuage";
1591
- var val = usesTextarea
1592
- ? document.getElementById("postitTextInput").value
1593
- : input.value;
1594
- if (val !== (shape.text || "")) pushHistory();
1595
- shape.text = val;
1596
- }
1597
- saveDiagrammes();
1598
- renderAll();
1599
- }
1600
- editingShapeId = null;
1601
- } else if (editingArrowId) {
1602
- var arrow = (diag.arrows || []).find(function (a) { return a.id === editingArrowId; });
1603
- if (arrow) {
1604
- if (input.value !== (arrow.label || "")) pushHistory();
1605
- arrow.label = input.value; saveDiagrammes(); renderAll();
1606
- }
1607
- editingArrowId = null;
1608
- }
1609
- document.getElementById("textOverlay").style.display = "none";
1610
- document.getElementById("postitTextInput").style.display = "none";
1611
- document.getElementById("shapeTextInput").style.display = "block";
1612
- }
1613
-
1614
- function startTableCellEdit(shapeId, row, col) {
1615
- var diag = getCurrentDiagram();
1616
- var shape = diag.shapes.find(function (s) { return s.id === shapeId; });
1617
- if (!shape) return;
1618
- editingShapeId = shapeId;
1619
- editingTableCell = { row: row, col: col };
1620
-
1621
- var tRows = shape.rows || 3;
1622
- var scColWidths = getColWidths(shape);
1623
- var scColOffsets = getColOffsets(shape);
1624
- var cellW = scColWidths[col], cellH = shape.h / tRows;
1625
- var cellX = shape.x + scColOffsets[col], cellY = shape.y + row * cellH;
1626
-
1627
- var svgEl = document.getElementById("canvas");
1628
- var sr = svgEl.getBoundingClientRect();
1629
- var csx = cellX * viewTransform.scale + viewTransform.x + sr.left;
1630
- var csy = cellY * viewTransform.scale + viewTransform.y + sr.top;
1631
- var csw = cellW * viewTransform.scale, csh = cellH * viewTransform.scale;
1632
- var cc = COLORS[shape.color] || COLORS[DEFAULT_COLOR];
1633
-
1634
- var ta = document.getElementById("postitTextInput");
1635
- ta.value = (shape.cells && shape.cells[row] && shape.cells[row][col]) || "";
1636
- ta.style.left = (csx + 2) + "px";
1637
- ta.style.top = (csy + 2) + "px";
1638
- ta.style.width = (csw - 4) + "px";
1639
- ta.style.height = (csh - 4) + "px";
1640
- var tfs = shape.fontSize || 12;
1641
- ta.style.fontSize = Math.max(10, tfs * viewTransform.scale) + "px";
1642
- ta.style.color = cc.text;
1643
- ta.style.display = "block";
1644
- document.getElementById("shapeTextInput").style.display = "none";
1645
- document.getElementById("textOverlay").style.display = "block";
1646
- var sg = document.querySelector('#shapesLayer [data-id="' + shapeId + '"]');
1647
- if (sg) { var cellTx = sg.querySelector('text[data-cell="' + row + '-' + col + '"]'); if (cellTx) cellTx.style.visibility = "hidden"; }
1648
- setTimeout(function () { ta.focus(); ta.select(); }, 10);
1649
- }
1650
-
1651
- function addTableRow() {
1652
- var diag = getCurrentDiagram();
1653
- var shape = selectedIds.length === 1 ? diag.shapes.find(function (s) { return s.id === selectedIds[0]; }) : null;
1654
- if (!shape || shape.type !== "table") return;
1655
- pushHistory();
1656
- shape.rows = (shape.rows || 3) + 1;
1657
- if (!shape.cells) shape.cells = [];
1658
- while (shape.cells.length < shape.rows) shape.cells.push([]);
1659
- shape.cells[shape.rows - 1] = new Array(shape.cols || 3).fill("");
1660
- saveDiagrammes(); renderAll();
1661
- document.getElementById("colorPanel").style.display = "flex";
1662
- syncColorPanel();
1663
- }
1664
-
1665
- function removeTableRow() {
1666
- var diag = getCurrentDiagram();
1667
- var shape = selectedIds.length === 1 ? diag.shapes.find(function (s) { return s.id === selectedIds[0]; }) : null;
1668
- if (!shape || shape.type !== "table" || (shape.rows || 3) <= 1) return;
1669
- pushHistory();
1670
- shape.rows = (shape.rows || 3) - 1;
1671
- if (shape.cells && shape.cells.length > shape.rows) shape.cells.splice(shape.rows);
1672
- saveDiagrammes(); renderAll();
1673
- document.getElementById("colorPanel").style.display = "flex";
1674
- syncColorPanel();
1675
- }
1676
-
1677
- function addTableCol() {
1678
- var diag = getCurrentDiagram();
1679
- var shape = selectedIds.length === 1 ? diag.shapes.find(function (s) { return s.id === selectedIds[0]; }) : null;
1680
- if (!shape || shape.type !== "table") return;
1681
- pushHistory();
1682
- var oldCols = shape.cols || 3;
1683
- shape.cols = oldCols + 1;
1684
- if (!shape.cells) shape.cells = [];
1685
- var rows = shape.rows || 3;
1686
- for (var ri = 0; ri < rows; ri++) {
1687
- if (!shape.cells[ri]) shape.cells[ri] = [];
1688
- shape.cells[ri].push("");
1689
- }
1690
- // Répartir la largeur : réduire les colonnes existantes proportionnellement
1691
- var oldWidths = getColWidths({ cols: oldCols, w: shape.w, colWidths: shape.colWidths });
1692
- var newColW = shape.w / shape.cols;
1693
- var scale = (shape.w - newColW) / shape.w;
1694
- shape.colWidths = oldWidths.map(function (w) { return w * scale; });
1695
- shape.colWidths.push(newColW);
1696
- saveDiagrammes(); renderAll();
1697
- document.getElementById("colorPanel").style.display = "flex";
1698
- syncColorPanel();
1699
- }
1700
-
1701
- function removeTableCol() {
1702
- var diag = getCurrentDiagram();
1703
- var shape = selectedIds.length === 1 ? diag.shapes.find(function (s) { return s.id === selectedIds[0]; }) : null;
1704
- if (!shape || shape.type !== "table" || (shape.cols || 3) <= 1) return;
1705
- pushHistory();
1706
- var oldCols = shape.cols || 3;
1707
- shape.cols = oldCols - 1;
1708
- if (shape.cells) shape.cells.forEach(function (row) { if (row.length > shape.cols) row.splice(shape.cols); });
1709
- // Retirer la dernière colonne et rescaler les restantes pour remplir shape.w
1710
- var cw = getColWidths({ cols: oldCols, w: shape.w, colWidths: shape.colWidths });
1711
- cw.splice(shape.cols);
1712
- var total = cw.reduce(function (s, v) { return s + v; }, 0);
1713
- shape.colWidths = cw.map(function (w) { return w * shape.w / total; });
1714
- saveDiagrammes(); renderAll();
1715
- document.getElementById("colorPanel").style.display = "flex";
1716
- syncColorPanel();
1717
- }
1718
-
1719
- // ── Overlay tableau (boutons +/− ligne/colonne sur le canvas) ──
1720
- function updateTableOverlay() {
1721
- var ids = ["tcBtnAddCol","tcBtnRemoveCol","tcBtnAddRow","tcBtnRemoveRow"];
1722
- var hide = function () { ids.forEach(function (id) { var el = document.getElementById(id); if (el) el.style.display = "none"; }); };
1723
- if (boardLocked || selectedIds.length !== 1 || selectedType !== "shape") { hide(); return; }
1724
- var diag = getCurrentDiagram();
1725
- if (!diag) { hide(); return; }
1726
- var shape = diag.shapes.find(function (s) { return s.id === selectedIds[0]; });
1727
- if (!shape || shape.type !== "table") { hide(); return; }
1728
-
1729
- var svg = document.getElementById("canvas");
1730
- var sr = svg.getBoundingClientRect();
1731
- var sc = viewTransform.scale;
1732
- var tx = viewTransform.x, ty = viewTransform.y;
1733
- var right = (shape.x + shape.w) * sc + tx + sr.left;
1734
- var bottom = (shape.y + shape.h) * sc + ty + sr.top;
1735
- var rows = shape.rows || 3;
1736
- var cellH = shape.h * sc / rows;
1737
- var BW = 22, GAP = 4;
1738
-
1739
- // +/− col : à droite de la dernière ligne, centrés verticalement dans cette ligne
1740
- var lastRowCy = bottom - cellH / 2;
1741
- var ac = document.getElementById("tcBtnAddCol");
1742
- var rc = document.getElementById("tcBtnRemoveCol");
1743
- ac.style.left = (right + GAP) + "px"; ac.style.top = (lastRowCy - BW - 2) + "px";
1744
- rc.style.left = (right + GAP) + "px"; rc.style.top = (lastRowCy + 2) + "px";
1745
- ac.style.display = rc.style.display = "flex";
1746
-
1747
- // +/− row : sous la dernière ligne, alignés à droite de la table
1748
- var ar = document.getElementById("tcBtnAddRow");
1749
- var rr = document.getElementById("tcBtnRemoveRow");
1750
- ar.style.left = (right - BW * 2 - GAP) + "px"; ar.style.top = (bottom + GAP) + "px";
1751
- rr.style.left = (right - BW - 2) + "px"; rr.style.top = (bottom + GAP) + "px";
1752
- ar.style.display = rr.style.display = "flex";
1753
- }
1754
-
1755
- function syncColorPanel() {
1756
- var btn = document.getElementById("btnShapeLink");
1757
- if (!btn) return;
1758
- if (selectedIds.length === 1 && selectedType === "shape") {
1759
- var diag = getCurrentDiagram();
1760
- var shape = diag ? diag.shapes.find(function (s) { return s.id === selectedIds[0]; }) : null;
1761
- if (shape && shape.linkedDiagramId) {
1762
- btn.classList.add("active");
1763
- var linked = findDiagramById(shape.linkedDiagramId, diagramsList);
1764
- btn.title = linked ? "Lié à : " + linked.titre + " (cliquer pour délier)" : "Supprimer le lien";
1765
- } else if (shape && shape.externalUrl) {
1766
- btn.classList.add("active");
1767
- btn.title = "Lien externe : " + shape.externalUrl + " (cliquer pour délier)";
1768
- } else {
1769
- btn.classList.remove("active");
1770
- btn.title = "Lier à un diagramme enfant ou URL externe";
1771
- }
1772
- btn.style.display = "";
1773
- } else {
1774
- btn.classList.remove("active");
1775
- btn.style.display = "none";
1776
- }
1777
- }
1778
-
1779
- function toggleShapeLink() {
1780
- if (selectedIds.length !== 1 || selectedType !== "shape") return;
1781
- var diag = getCurrentDiagram();
1782
- var shape = diag ? diag.shapes.find(function (s) { return s.id === selectedIds[0]; }) : null;
1783
- if (!shape) return;
1784
- if (shape.linkedDiagramId || shape.externalUrl) {
1785
- pushHistory();
1786
- delete shape.linkedDiagramId;
1787
- delete shape.externalUrl;
1788
- saveDiagrammes();
1789
- renderAll();
1790
- syncColorPanel();
1791
- hideLinkPicker();
1792
- } else {
1793
- showLinkPicker();
1794
- }
1795
- }
1796
-
1797
- function showLinkPicker() {
1798
- var panel = document.getElementById("linkPickerPanel");
1799
- if (!panel) return;
1800
- var btn = document.getElementById("btnShapeLink");
1801
- var r = btn ? btn.getBoundingClientRect() : { bottom: 100, left: 200 };
1802
- panel.style.top = (r.bottom + 6) + "px";
1803
- panel.style.left = r.left + "px";
1804
- var all = flattenDiagrams(diagramsList);
1805
- var curId = String(currentDiagramId);
1806
- var items = all.filter(function (d) { return String(d.id) !== curId; });
1807
- var html = items.map(function (d) {
1808
- return '<div class="link-picker-item" onclick="lierForme(\'' + d.id + '\')">' + escDiag(d.titre) + '</div>';
1809
- }).join("");
1810
- html += '<div class="link-picker-new" onclick="creerEnfantEtLier()">+ Nouveau diagramme enfant</div>';
1811
- html += '<div class="link-picker-new link-picker-ext-toggle" onclick="showExternalLinkInput()" style="display:flex;align-items:center;gap:6px;">'
1812
- + '<svg width="14" height="14" viewBox="0 0 14 14"><circle cx="7" cy="7" r="6" fill="#0284c7" stroke="#fff" stroke-width="1.5"/><text x="7" y="10.5" text-anchor="middle" font-size="8" font-weight="bold" fill="#fff">\u2197</text></svg>'
1813
- + 'Lien externe (URL)</div>';
1814
- html += '<div id="linkPickerExtRow" style="display:none;padding:6px 8px;border-top:1px solid #e7e5e4;">'
1815
- + '<input id="linkPickerExtUrl" type="text" placeholder="https://..." '
1816
- + 'style="width:100%;box-sizing:border-box;padding:4px 6px;border:1px solid #d4d4d4;border-radius:4px;font-size:12px;" '
1817
- + 'onkeydown="if(event.key===\'Enter\')lierFormeExterne()">'
1818
- + '<div id="linkPickerExtErr" style="color:#e11d48;font-size:11px;margin-top:3px;display:none;">URL invalide (http://, https:// ou file://)</div>'
1819
- + '</div>';
1820
- document.getElementById("linkPickerList").innerHTML = html;
1821
- panel.style.display = "block";
1822
- }
1823
-
1824
- function showExternalLinkInput() {
1825
- var row = document.getElementById("linkPickerExtRow");
1826
- if (!row) return;
1827
- row.style.display = "block";
1828
- var input = document.getElementById("linkPickerExtUrl");
1829
- if (input) setTimeout(function () { input.focus(); }, 10);
1830
- }
1831
-
1832
- function lierFormeExterne() {
1833
- var input = document.getElementById("linkPickerExtUrl");
1834
- if (!input) return;
1835
- var url = input.value.trim();
1836
- var err = document.getElementById("linkPickerExtErr");
1837
- if (!/^(https?|file):\/\//i.test(url)) {
1838
- if (err) err.style.display = "block";
1839
- return;
1840
- }
1841
- if (err) err.style.display = "none";
1842
- if (selectedIds.length !== 1) return;
1843
- var diag = getCurrentDiagram();
1844
- var shape = diag ? diag.shapes.find(function (s) { return s.id === selectedIds[0]; }) : null;
1845
- if (!shape) return;
1846
- pushHistory();
1847
- shape.externalUrl = url;
1848
- delete shape.linkedDiagramId;
1849
- hideLinkPicker();
1850
- saveDiagrammes();
1851
- renderAll();
1852
- syncColorPanel();
1853
- }
1854
-
1855
- function hideLinkPicker() {
1856
- var panel = document.getElementById("linkPickerPanel");
1857
- if (panel) panel.style.display = "none";
1858
- }
1859
-
1860
- function lierForme(diagId) {
1861
- if (selectedIds.length !== 1) return;
1862
- var diag = getCurrentDiagram();
1863
- var shape = diag ? diag.shapes.find(function (s) { return s.id === selectedIds[0]; }) : null;
1864
- if (!shape) return;
1865
- pushHistory();
1866
- shape.linkedDiagramId = String(diagId);
1867
- delete shape.externalUrl;
1868
- hideLinkPicker();
1869
- saveDiagrammes();
1870
- renderAll();
1871
- syncColorPanel();
1872
- }
1873
-
1874
- function creerEnfantEtLier() {
1875
- if (selectedIds.length !== 1) return;
1876
- var diag = getCurrentDiagram();
1877
- var shape = diag ? diag.shapes.find(function (s) { return s.id === selectedIds[0]; }) : null;
1878
- if (!shape) return;
1879
- hideLinkPicker();
1880
- var titre = shape.text || "Sous-diagramme";
1881
- var child = { id: Date.now(), titre: titre, shapes: [], arrows: [], children: [] };
1882
- if (!diag.children) diag.children = [];
1883
- diag.children.push(child);
1884
- diagExpandedIds[String(diag.id)] = true;
1885
- pushHistory();
1886
- shape.linkedDiagramId = String(child.id);
1887
- saveDiagrammes();
1888
- renderAll();
1889
- renderDiagramList();
1890
- syncColorPanel();
1891
- }
1892
-
1893
- function startArrowTextEdit(arrowId) {
1894
- var diag = getCurrentDiagram();
1895
- var arrow = (diag.arrows || []).find(function (a) { return a.id === arrowId; });
1896
- if (!arrow) return;
1897
- var from = diag.shapes.find(function (s) { return s.id === arrow.from; });
1898
- var to = diag.shapes.find(function (s) { return s.id === arrow.to; });
1899
- if (!from || !to) return;
1900
-
1901
- var toCx = to.x + to.w / 2, toCy = to.y + to.h / 2;
1902
- var frCx = from.x + from.w / 2, frCy = from.y + from.h / 2;
1903
- var fp = getEdgePoint(from, toCx, toCy);
1904
- var tp = getEdgePoint(to, frCx, frCy);
1905
- var mx = (fp.x + tp.x) / 2;
1906
- var my = (fp.y + tp.y) / 2;
1907
-
1908
- var svg = document.getElementById("canvas");
1909
- var sr = svg.getBoundingClientRect();
1910
- var sx = mx * viewTransform.scale + viewTransform.x + sr.left;
1911
- var sy = my * viewTransform.scale + viewTransform.y + sr.top;
1912
-
1913
- editingArrowId = arrowId;
1914
- var input = document.getElementById("shapeTextInput");
1915
- input.value = arrow.label || "";
1916
- input.style.left = (sx - 60) + "px";
1917
- input.style.top = (sy - 12) + "px";
1918
- input.style.width = "120px";
1919
- input.style.fontSize = Math.max(10, 11 * viewTransform.scale) + "px";
1920
- input.style.color = "#78716c";
1921
-
1922
- document.getElementById("textOverlay").style.display = "block";
1923
- setTimeout(function () { input.focus(); input.select(); }, 10);
1924
- }
1925
-
1926
- // ── Hit-test ──
1927
- function shapeAt(x, y) {
1928
- var diag = getCurrentDiagram();
1929
- if (!diag) return null;
1930
- for (var i = diag.shapes.length - 1; i >= 0; i--) {
1931
- var s = diag.shapes[i];
1932
- var lx = x, ly = y;
1933
- if (s.rotation) {
1934
- var scx = s.x + s.w / 2, scy = s.y + s.h / 2;
1935
- var rad = -s.rotation * Math.PI / 180;
1936
- var cos = Math.cos(rad), sin = Math.sin(rad);
1937
- var ddx = x - scx, ddy = y - scy;
1938
- lx = scx + ddx * cos - ddy * sin;
1939
- ly = scy + ddx * sin + ddy * cos;
1940
- }
1941
- if (lx >= s.x && lx <= s.x + s.w && ly >= s.y && ly <= s.y + s.h) return s;
1942
- }
1943
- return null;
1944
- }
1945
-
1946
- function arrowIdAt(x, y) {
1947
- var groups = document.querySelectorAll(".arrow-group");
1948
- for (var i = 0; i < groups.length; i++) {
1949
- var lines = groups[i].querySelectorAll("line");
1950
- if (lines.length < 2) continue;
1951
- var hit = lines[lines.length - 1];
1952
- var x1 = +hit.getAttribute("x1"), y1 = +hit.getAttribute("y1");
1953
- var x2 = +hit.getAttribute("x2"), y2 = +hit.getAttribute("y2");
1954
- if (distToSeg(x, y, x1, y1, x2, y2) < 8) {
1955
- return groups[i].getAttribute("data-id");
1956
- }
1957
- }
1958
- return null;
1959
- }
1960
-
1961
- function distToSeg(px, py, x1, y1, x2, y2) {
1962
- var dx = x2 - x1, dy = y2 - y1;
1963
- var lenSq = dx * dx + dy * dy;
1964
- if (lenSq === 0) return Math.hypot(px - x1, py - y1);
1965
- var t = Math.max(0, Math.min(1, ((px - x1) * dx + (py - y1) * dy) / lenSq));
1966
- return Math.hypot(px - (x1 + t * dx), py - (y1 + t * dy));
1967
- }
1968
-
1969
- // ── Créer une flèche ──
1970
- function createArrow(fromId, toId) {
1971
- var diag = getCurrentDiagram();
1972
- if (diag.arrows.some(function (a) { return a.from === fromId && a.to === toId; })) return;
1973
- pushHistory();
1974
- var arrow = { id: "a" + Date.now(), from: fromId, to: toId, label: "" };
1975
- diag.arrows.push(arrow);
1976
- saveDiagrammes();
1977
- selectedId = arrow.id; selectedType = "arrow";
1978
- selectedIds = [];
1979
- renderAll();
1980
- startArrowTextEdit(arrow.id);
1981
- }
1982
-
1983
- // ── Renommer le diagramme ──
1984
- function onTitleChange() {
1985
- if (pendingNewDiagram) return;
1986
- var diag = getCurrentDiagram();
1987
- if (!diag) return;
1988
- diag.titre = document.getElementById("diagramTitle").value || "Diagramme";
1989
- saveDiagrammes();
1990
- renderDiagramList();
1991
- }
1992
-
1993
- // ── Événements souris ──
1994
- function onMouseDown(e) {
1995
- if (e.button !== 0) return;
1996
- if (editingShapeId || editingArrowId) { confirmTextEdit(); return; }
1997
-
1998
- // Mode verrouillé : pan uniquement
1999
- if (boardLocked) {
2000
- panStart = { cx: e.clientX, cy: e.clientY, px: viewTransform.x, py: viewTransform.y };
2001
- return;
2002
- }
2003
-
2004
- var pt = svgPoint(e.clientX, e.clientY);
2005
-
2006
- // Mode pick (copie de style)
2007
- if (pickMode) {
2008
- var srcShape = shapeAt(pt.x, pt.y);
2009
- if (srcShape) applyPickMode(srcShape);
2010
- cancelPickMode();
2011
- return;
2012
- }
2013
-
2014
- // Clic sur un point de connexion → début de flèche
2015
- var connDot = e.target.closest(".conn-dot");
2016
- if (connDot) {
2017
- arrowSrcId = connDot.getAttribute("data-shape-id");
2018
- var srcShape = getCurrentDiagram().shapes.find(function (s) { return s.id === arrowSrcId; });
2019
- var ta = document.getElementById("tempArrow");
2020
- ta.setAttribute("x1", srcShape.x + srcShape.w / 2);
2021
- ta.setAttribute("y1", srcShape.y + srcShape.h / 2);
2022
- ta.setAttribute("x2", pt.x); ta.setAttribute("y2", pt.y);
2023
- ta.style.display = "";
2024
- return;
2025
- }
2026
-
2027
- // Clic sur un chevron de redimensionnement de colonne
2028
- var colSepEl = e.target.closest("[data-col-sep]");
2029
- if (colSepEl) {
2030
- var csColIdx = parseInt(colSepEl.getAttribute("data-col-sep"), 10);
2031
- var csSid = colSepEl.getAttribute("data-shape-id");
2032
- var csShape = getCurrentDiagram().shapes.find(function (s) { return s.id === csSid; });
2033
- if (csShape) {
2034
- var csCw = getColWidths(csShape);
2035
- dragState = { type: "col-resize", id: csSid, colIdx: csColIdx, sx: pt.x, origWidths: csCw.slice(), snapshot: JSON.stringify(diagramsList) };
2036
- }
2037
- return;
2038
- }
2039
-
2040
- // Clic sur la poignée de resize
2041
- var grip = e.target.closest(".resize-grip");
2042
- if (grip) {
2043
- var sid = grip.getAttribute("data-shape-id");
2044
- var shape = getCurrentDiagram().shapes.find(function (s) { return s.id === sid; });
2045
- if (shape) {
2046
- dragState = { type: "resize", id: sid, sx: pt.x, sy: pt.y, ow: shape.w, oh: shape.h, snapshot: JSON.stringify(diagramsList) };
2047
- }
2048
- return;
2049
- }
2050
-
2051
- var shape = shapeAt(pt.x, pt.y);
2052
-
2053
- if (currentTool === "select") {
2054
- if (shape) {
2055
- // Détection du double-clic par horodatage (avant tout renderAll)
2056
- var now = Date.now();
2057
- if (now - lastClickTime < 350 && lastClickShapeId === shape.id) {
2058
- lastClickTime = 0; lastClickShapeId = null;
2059
- if (shape.type === "table") {
2060
- var tDblRows = shape.rows || 3;
2061
- var tDblOffsets = getColOffsets(shape);
2062
- var tDblCw = getColWidths(shape);
2063
- var tDblRx = pt.x - shape.x;
2064
- var tDblC = tDblCw.length - 1;
2065
- for (var tci3 = 0; tci3 < tDblCw.length; tci3++) {
2066
- if (tDblRx < tDblOffsets[tci3] + tDblCw[tci3]) { tDblC = tci3; break; }
2067
- }
2068
- var tDblR = Math.min(tDblRows - 1, Math.max(0, Math.floor((pt.y - shape.y) / (shape.h / tDblRows))));
2069
- startTableCellEdit(shape.id, tDblR, tDblC);
2070
- } else {
2071
- startTextEdit(shape.id);
2072
- }
2073
- return;
2074
- }
2075
- lastClickTime = now; lastClickShapeId = shape.id; lastClickArrowId = null;
2076
-
2077
- if (e.shiftKey) {
2078
- // Shift+clic : ajouter/retirer de la multi-sélection
2079
- var idx = selectedIds.indexOf(shape.id);
2080
- if (idx === -1) {
2081
- selectedIds.push(shape.id);
2082
- selectedId = shape.id; selectedType = "shape";
2083
- } else {
2084
- selectedIds.splice(idx, 1);
2085
- selectedId = selectedIds.length > 0 ? selectedIds[selectedIds.length - 1] : null;
2086
- selectedType = selectedId ? "shape" : null;
2087
- }
2088
- document.getElementById("colorPanel").style.display = selectedIds.length > 0 ? "flex" : "none";
2089
- renderAll();
2090
- } else if (selectedIds.indexOf(shape.id) !== -1 && selectedIds.length > 1) {
2091
- // Clic sur une forme déjà dans la multi-sélection → multi-déplacement
2092
- var diag = getCurrentDiagram();
2093
- dragState = {
2094
- type: "multi-move",
2095
- sx: pt.x, sy: pt.y,
2096
- origPositions: selectedIds.map(function (id) {
2097
- var s = diag.shapes.find(function (sh) { return sh.id === id; });
2098
- return { id: id, ox: s ? s.x : 0, oy: s ? s.y : 0 };
2099
- }),
2100
- snapshot: JSON.stringify(diagramsList),
2101
- };
2102
- } else {
2103
- // Clic simple → sélection unique
2104
- selectedIds = [shape.id];
2105
- selectedId = shape.id; selectedType = "shape";
2106
- dragState = { type: "move", id: shape.id, sx: pt.x, sy: pt.y, ox: shape.x, oy: shape.y, snapshot: JSON.stringify(diagramsList) };
2107
- document.getElementById("colorPanel").style.display = "flex";
2108
- renderAll();
2109
- }
2110
- } else {
2111
- var aid = arrowIdAt(pt.x, pt.y);
2112
- if (aid) {
2113
- // Détection du double-clic sur une flèche
2114
- var now2 = Date.now();
2115
- if (now2 - lastClickTime < 350 && lastClickArrowId === aid) {
2116
- lastClickTime = 0; lastClickArrowId = null;
2117
- selectedId = aid; selectedType = "arrow";
2118
- selectedIds = [];
2119
- renderAll();
2120
- startArrowTextEdit(aid);
2121
- return;
2122
- }
2123
- lastClickTime = now2; lastClickArrowId = aid;
2124
-
2125
- selectedId = aid; selectedType = "arrow";
2126
- selectedIds = [];
2127
- document.getElementById("colorPanel").style.display = "none";
2128
- renderAll();
2129
- } else if (e.shiftKey) {
2130
- // Shift+drag sur fond vide → lasso de sélection
2131
- rubberBandState = { sx: pt.x, sy: pt.y };
2132
- } else {
2133
- // Pan
2134
- panStart = { cx: e.clientX, cy: e.clientY, px: viewTransform.x, py: viewTransform.y };
2135
- selectedId = null; selectedType = null;
2136
- selectedIds = [];
2137
- document.getElementById("colorPanel").style.display = "none";
2138
- renderAll();
2139
- }
2140
- }
2141
-
2142
- } else if (currentTool === "arrow") {
2143
- if (shape && shape.type !== "postit") {
2144
- if (!arrowSrcId) {
2145
- arrowSrcId = shape.id;
2146
- var ta = document.getElementById("tempArrow");
2147
- ta.setAttribute("x1", shape.x + shape.w / 2);
2148
- ta.setAttribute("y1", shape.y + shape.h / 2);
2149
- ta.setAttribute("x2", pt.x); ta.setAttribute("y2", pt.y);
2150
- ta.style.display = "";
2151
- } else if (arrowSrcId !== shape.id) {
2152
- createArrow(arrowSrcId, shape.id);
2153
- arrowSrcId = null;
2154
- document.getElementById("tempArrow").style.display = "none";
2155
- }
2156
- } else {
2157
- arrowSrcId = null;
2158
- document.getElementById("tempArrow").style.display = "none";
2159
- }
2160
-
2161
- } else {
2162
- // Outils forme : placer au clic
2163
- addShape(currentTool, pt.x, pt.y);
2164
- setTool("select");
2165
- }
2166
- }
2167
-
2168
- function onMouseMove(e) {
2169
- var pt = svgPoint(e.clientX, e.clientY);
2170
-
2171
- // Mise à jour de la flèche temporaire
2172
- if (arrowSrcId) {
2173
- var ta = document.getElementById("tempArrow");
2174
- if (ta.style.display !== "none") {
2175
- ta.setAttribute("x2", pt.x); ta.setAttribute("y2", pt.y);
2176
- }
2177
- }
2178
-
2179
- if (rubberBandState) {
2180
- var rb = document.getElementById("rubberBand");
2181
- var rbX = Math.min(rubberBandState.sx, pt.x);
2182
- var rbY = Math.min(rubberBandState.sy, pt.y);
2183
- rb.setAttribute("x", rbX); rb.setAttribute("y", rbY);
2184
- rb.setAttribute("width", Math.abs(pt.x - rubberBandState.sx));
2185
- rb.setAttribute("height", Math.abs(pt.y - rubberBandState.sy));
2186
- rb.style.display = "";
2187
- } else if (dragState) {
2188
- dragState.moved = true;
2189
- var diag = getCurrentDiagram();
2190
- if (dragState.type === "multi-move") {
2191
- var dx = pt.x - dragState.sx;
2192
- var dy = pt.y - dragState.sy;
2193
- dragState.origPositions.forEach(function (op) {
2194
- var s = diag.shapes.find(function (sh) { return sh.id === op.id; });
2195
- if (s) { s.x = Math.round(op.ox + dx); s.y = Math.round(op.oy + dy); }
2196
- });
2197
- renderAll();
2198
- } else {
2199
- var shape = diag.shapes.find(function (s) { return s.id === dragState.id; });
2200
- if (!shape) return;
2201
- if (dragState.type === "move") {
2202
- shape.x = Math.round(dragState.ox + pt.x - dragState.sx);
2203
- shape.y = Math.round(dragState.oy + pt.y - dragState.sy);
2204
- } else if (dragState.type === "resize") {
2205
- var newW = Math.max(60, Math.round(dragState.ow + pt.x - dragState.sx));
2206
- var newH = Math.max(30, Math.round(dragState.oh + pt.y - dragState.sy));
2207
- if (shape.type === "table" && shape.colWidths && shape.colWidths.length === (shape.cols || 3)) {
2208
- var oldW2 = shape.w;
2209
- shape.colWidths = shape.colWidths.map(function (cw) { return cw * newW / oldW2; });
2210
- }
2211
- shape.w = newW;
2212
- shape.h = newH;
2213
- } else if (dragState.type === "col-resize") {
2214
- var crCw = dragState.origWidths.slice();
2215
- var crDx = pt.x - dragState.sx;
2216
- var crCi = dragState.colIdx;
2217
- var MIN_COL = 20;
2218
- var crLeft = Math.max(MIN_COL, crCw[crCi] + crDx);
2219
- var crRight = Math.max(MIN_COL, crCw[crCi + 1] - crDx);
2220
- var crTotal = crCw[crCi] + crCw[crCi + 1];
2221
- crLeft = Math.min(crTotal - MIN_COL, crLeft);
2222
- crRight = crTotal - crLeft;
2223
- crCw[crCi] = crLeft;
2224
- crCw[crCi + 1] = crRight;
2225
- shape.colWidths = crCw;
2226
- }
2227
- renderAll();
2228
- }
2229
- } else if (panStart) {
2230
- viewTransform.x = panStart.px + (e.clientX - panStart.cx);
2231
- viewTransform.y = panStart.py + (e.clientY - panStart.cy);
2232
- updateViewport();
2233
- }
2234
- }
2235
-
2236
- function onMouseUp(e) {
2237
- var pt = svgPoint(e.clientX, e.clientY);
2238
-
2239
- // En mode verrouillé : détecter un clic (sans déplacement) sur une forme liée
2240
- if (boardLocked && panStart) {
2241
- var movedPx = Math.abs(e.clientX - panStart.cx) + Math.abs(e.clientY - panStart.cy);
2242
- if (movedPx < 5 && !e.shiftKey) {
2243
- var shape = shapeAt(pt.x, pt.y);
2244
- if (shape && shape.linkedDiagramId) {
2245
- var target = findDiagramById(shape.linkedDiagramId, diagramsList);
2246
- if (target) {
2247
- hideLinkPicker();
2248
- diagNavStack.push(currentDiagramId);
2249
- if (diagNavStack.length > 30) diagNavStack.shift();
2250
- panStart = null;
2251
- selectDiagramme(shape.linkedDiagramId);
2252
- return;
2253
- }
2254
- } else if (shape && shape.externalUrl) {
2255
- panStart = null;
2256
- window.open(shape.externalUrl, "_blank");
2257
- return;
2258
- }
2259
- }
2260
- panStart = null;
2261
- return;
2262
- }
2263
-
2264
- // Fin de tracé de flèche via conn-dot
2265
- if (arrowSrcId && !dragState) {
2266
- document.getElementById("tempArrow").style.display = "none";
2267
- if (currentTool !== "arrow") {
2268
- var target = shapeAt(pt.x, pt.y);
2269
- if (target && target.id !== arrowSrcId && target.type !== "postit") {
2270
- createArrow(arrowSrcId, target.id);
2271
- }
2272
- arrowSrcId = null;
2273
- }
2274
- }
2275
-
2276
- if (rubberBandState) {
2277
- document.getElementById("rubberBand").style.display = "none";
2278
- var minX = Math.min(rubberBandState.sx, pt.x);
2279
- var minY = Math.min(rubberBandState.sy, pt.y);
2280
- var maxX = Math.max(rubberBandState.sx, pt.x);
2281
- var maxY = Math.max(rubberBandState.sy, pt.y);
2282
- if (maxX - minX > 4 || maxY - minY > 4) {
2283
- var diag = getCurrentDiagram();
2284
- selectedIds = (diag.shapes || []).filter(function (s) {
2285
- return s.x < maxX && s.x + s.w > minX && s.y < maxY && s.y + s.h > minY;
2286
- }).map(function (s) { return s.id; });
2287
- selectedId = selectedIds.length > 0 ? selectedIds[0] : null;
2288
- selectedType = selectedIds.length > 0 ? "shape" : null;
2289
- document.getElementById("colorPanel").style.display = selectedIds.length > 0 ? "flex" : "none";
2290
- }
2291
- rubberBandState = null;
2292
- renderAll();
2293
- }
2294
-
2295
- if (dragState) {
2296
- var wasMoved = dragState.moved;
2297
- var draggedId = dragState.id;
2298
- var dragType = dragState.type;
2299
- if (dragState.moved && dragState.snapshot) {
2300
- historyStack.push(dragState.snapshot);
2301
- if (historyStack.length > MAX_HISTORY) historyStack.shift();
2302
- }
2303
- saveDiagrammes();
2304
- dragState = null;
2305
- renderAll();
2306
- // Naviguer vers le diagramme lié si clic sans déplacement
2307
- if (!wasMoved && dragType === "move" && draggedId && !e.shiftKey) {
2308
- var diag = getCurrentDiagram();
2309
- var clickedShape = diag ? diag.shapes.find(function (s) { return s.id === draggedId; }) : null;
2310
- if (clickedShape && clickedShape.linkedDiagramId) {
2311
- var target = findDiagramById(clickedShape.linkedDiagramId, diagramsList);
2312
- if (target) {
2313
- hideLinkPicker();
2314
- diagNavStack.push(currentDiagramId);
2315
- if (diagNavStack.length > 30) diagNavStack.shift();
2316
- selectDiagramme(clickedShape.linkedDiagramId);
2317
- return;
2318
- }
2319
- } else if (clickedShape && clickedShape.externalUrl) {
2320
- window.open(clickedShape.externalUrl, "_blank");
2321
- return;
2322
- }
2323
- }
2324
- }
2325
- panStart = null;
2326
- }
2327
-
2328
-
2329
- function onWheel(e) {
2330
- e.preventDefault();
2331
- var svg = document.getElementById("canvas");
2332
- var r = svg.getBoundingClientRect();
2333
- applyZoom(e.deltaY < 0 ? 1.12 : 1 / 1.12, e.clientX - r.left, e.clientY - r.top);
2334
- }
2335
-
2336
-
2337
- // ── Coller des formes ──
2338
- function pasteShapes() {
2339
- if (clipboard.length === 0) return;
2340
- pushHistory();
2341
- var diag = getCurrentDiagram();
2342
- var now = Date.now();
2343
- var pasted = clipboard.map(function (s, i) {
2344
- return Object.assign({}, JSON.parse(JSON.stringify(s)), {
2345
- id: "s" + (now + i),
2346
- x: s.x + 20,
2347
- y: s.y + 20,
2348
- });
2349
- });
2350
- pasted.forEach(function (s) { diag.shapes.push(s); });
2351
- selectedIds = pasted.map(function (s) { return s.id; });
2352
- selectedId = selectedIds[0]; selectedType = "shape";
2353
- clipboard = pasted.map(function (s) { return JSON.parse(JSON.stringify(s)); });
2354
- saveDiagrammes();
2355
- renderAll();
2356
- document.getElementById("colorPanel").style.display = "flex";
2357
- }
2358
-
2359
- // ── Init ──
2360
- document.addEventListener("DOMContentLoaded", function () {
2361
- diagramsList = loadDiagrammes();
2362
- if (!localStorage.getItem("mes_diagrammes")) saveDiagrammes();
2363
- var savedDiagId = localStorage.getItem("current_diagram_id");
2364
- if (savedDiagId && findDiagramById(savedDiagId, diagramsList)) {
2365
- currentDiagramId = savedDiagId;
2366
- } else {
2367
- var savedIdx = parseInt(localStorage.getItem("current_diagram_idx"), 10);
2368
- if (!isNaN(savedIdx) && savedIdx >= 0 && savedIdx < diagramsList.length) {
2369
- currentDiagramId = String(diagramsList[savedIdx].id);
2370
- } else {
2371
- currentDiagramId = diagramsList[0] ? String(diagramsList[0].id) : null;
2372
- }
2373
- }
2374
- restoreLockForDiagram(currentDiagramId);
2375
- checkDiffDiagrammes();
2376
- renderAll();
2377
- updateLockBtn();
2378
- updateBackBtn();
2379
-
2380
- var canvas = document.getElementById("canvas");
2381
- canvas.addEventListener("mousedown", onMouseDown);
2382
- canvas.addEventListener("mousemove", onMouseMove);
2383
- canvas.addEventListener("mouseup", onMouseUp);
2384
- canvas.addEventListener("wheel", onWheel, { passive: false });
2385
-
2386
- var titleInput = document.getElementById("diagramTitle");
2387
- titleInput.addEventListener("change", onTitleChange);
2388
- titleInput.addEventListener("blur", onTitleChange);
2389
- titleInput.addEventListener("keydown", function (e) {
2390
- if (e.key === "Enter") {
2391
- e.preventDefault();
2392
- if (pendingNewDiagram) confirmerNouveauDiagramme();
2393
- else titleInput.blur();
2394
- }
2395
- if (e.key === "Escape" && pendingNewDiagram) annulerNouveauDiagramme();
2396
- });
2397
-
2398
- var textInput = document.getElementById("shapeTextInput");
2399
- textInput.addEventListener("keydown", function (e) {
2400
- if (e.key === "Enter") { e.preventDefault(); confirmTextEdit(); }
2401
- if (e.key === "Escape") {
2402
- document.getElementById("textOverlay").style.display = "none";
2403
- editingShapeId = null;
2404
- }
2405
- });
2406
- textInput.addEventListener("blur", function () {
2407
- // Small delay to allow click on modal-confirm etc.
2408
- setTimeout(confirmTextEdit, 100);
2409
- });
2410
-
2411
- var postitInput = document.getElementById("postitTextInput");
2412
- postitInput.addEventListener("keydown", function (e) {
2413
- if (e.key === "Escape") {
2414
- document.getElementById("textOverlay").style.display = "none";
2415
- postitInput.style.display = "none";
2416
- document.getElementById("shapeTextInput").style.display = "block";
2417
- editingShapeId = null;
2418
- renderAll(); // restaure la visibilité du texte SVG
2419
- }
2420
- if (e.key === "Tab" && editingTableCell !== null) {
2421
- e.preventDefault();
2422
- var diag = getCurrentDiagram();
2423
- var shape = diag.shapes.find(function (s) { return s.id === editingShapeId; });
2424
- if (!shape) return;
2425
- // Sauvegarder la cellule courante
2426
- if (!shape.cells) shape.cells = [];
2427
- while (shape.cells.length <= editingTableCell.row) shape.cells.push([]);
2428
- shape.cells[editingTableCell.row][editingTableCell.col] = postitInput.value;
2429
- var rows = shape.rows || 3;
2430
- var cols = shape.cols || 3;
2431
- var nextRow = editingTableCell.row;
2432
- var nextCol = editingTableCell.col;
2433
- if (e.shiftKey) {
2434
- // Shift+Tab : cellule précédente, s'arrête à la première
2435
- nextCol -= 1;
2436
- if (nextCol < 0) {
2437
- if (nextRow > 0) { nextRow--; nextCol = cols - 1; }
2438
- else { nextCol = 0; }
2439
- }
2440
- } else {
2441
- // Tab : cellule suivante, ajoute une ligne à la fin
2442
- nextCol += 1;
2443
- if (nextCol >= cols) { nextRow++; nextCol = 0; }
2444
- if (nextRow >= rows) {
2445
- pushHistory();
2446
- shape.rows = rows + 1;
2447
- if (!shape.cells) shape.cells = [];
2448
- while (shape.cells.length < shape.rows) shape.cells.push([]);
2449
- shape.cells[shape.rows - 1] = new Array(cols).fill("");
2450
- }
2451
- }
2452
- var shapeId = editingShapeId;
2453
- saveDiagrammes();
2454
- renderAll();
2455
- startTableCellEdit(shapeId, nextRow, nextCol);
2456
- }
2457
- // Enter ajoute un saut de ligne (comportement natif textarea — pas de preventDefault)
2458
- });
2459
- postitInput.addEventListener("blur", function () {
2460
- setTimeout(confirmTextEdit, 100);
2461
- });
2462
-
2463
- document.addEventListener("keydown", function (e) {
2464
- if (document.activeElement.tagName === "INPUT" || document.activeElement.tagName === "TEXTAREA") return;
2465
- if ((e.ctrlKey || e.metaKey) && e.key === "z") { e.preventDefault(); undoAction(); return; }
2466
- if ((e.ctrlKey || e.metaKey) && e.key === "a") {
2467
- e.preventDefault();
2468
- var diag = getCurrentDiagram();
2469
- if (diag && diag.shapes.length > 0) {
2470
- selectedIds = diag.shapes.map(function (s) { return s.id; });
2471
- selectedType = "shape";
2472
- selectedId = selectedIds[selectedIds.length - 1];
2473
- renderAll();
2474
- document.getElementById("colorPanel").style.display = "flex";
2475
- syncColorPanel();
2476
- }
2477
- return;
2478
- }
2479
- if (boardLocked) return;
2480
- if (e.key === "Delete" || e.key === "Backspace") deleteSelected();
2481
- if (e.key === "Escape") {
2482
- hideLinkPicker();
2483
- arrowSrcId = null;
2484
- document.getElementById("tempArrow").style.display = "none";
2485
- setTool("select");
2486
- selectedIds = []; selectedId = null; selectedType = null;
2487
- document.getElementById("colorPanel").style.display = "none";
2488
- renderAll();
2489
- }
2490
- if ((e.ctrlKey || e.metaKey) && e.key === "x") {
2491
- e.preventDefault();
2492
- if (selectedIds.length === 0) return;
2493
- var diag = getCurrentDiagram();
2494
- clipboard = selectedIds.map(function (id) {
2495
- var s = diag.shapes.find(function (sh) { return sh.id === id; });
2496
- return s ? JSON.parse(JSON.stringify(s)) : null;
2497
- }).filter(Boolean);
2498
- deleteSelected();
2499
- return;
2500
- }
2501
- if ((e.ctrlKey || e.metaKey) && e.key === "c") {
2502
- e.preventDefault();
2503
- if (selectedIds.length === 0) return;
2504
- var diag = getCurrentDiagram();
2505
- clipboard = selectedIds.map(function (id) {
2506
- var s = diag.shapes.find(function (sh) { return sh.id === id; });
2507
- return s ? JSON.parse(JSON.stringify(s)) : null;
2508
- }).filter(Boolean);
2509
- }
2510
- if ((e.ctrlKey || e.metaKey) && e.key === "v") {
2511
- e.preventDefault();
2512
- if (clipboard.length > 0) {
2513
- pasteShapes();
2514
- return;
2515
- }
2516
- if (!navigator.clipboard || !navigator.clipboard.read) return;
2517
- navigator.clipboard.read().then(function (clipItems) {
2518
- for (var i = 0; i < clipItems.length; i++) {
2519
- for (var j = 0; j < clipItems[i].types.length; j++) {
2520
- if (clipItems[i].types[j].indexOf("image") !== -1) {
2521
- clipItems[i].getType(clipItems[i].types[j]).then(function (blob) {
2522
- handleImagePaste(blob);
2523
- });
2524
- return;
2525
- }
2526
- }
2527
- }
2528
- }).catch(function () {});
2529
- }
2530
- });
2531
-
2532
- // Quand la fenêtre reprend le focus, l'utilisateur revient d'une autre app
2533
- // où il a peut-être copié une image → vider le clipboard interne
2534
- window.addEventListener("focus", function () {
2535
- clipboard = [];
2536
- });
2537
-
2538
- document.getElementById("modalPremiereSauvegardeDiag").addEventListener("click", function (e) {
2539
- if (e.target === this) this.classList.remove("open");
2540
- });
2541
-
2542
-
2543
- document.addEventListener("mousedown", function (e) {
2544
- var lp = document.getElementById("linkPickerPanel");
2545
- if (lp && lp.style.display !== "none") {
2546
- var lbtn = document.getElementById("btnShapeLink");
2547
- if (!lp.contains(e.target) && e.target !== lbtn) hideLinkPicker();
2548
- }
2549
- var panel = document.getElementById("diagramListPanel");
2550
- if (!panel.classList.contains("open")) return;
2551
- var burgerBtn = document.querySelector(".diagram-tool[onclick=\"toggleDiagramList()\"]");
2552
- if (!panel.contains(e.target) && (!burgerBtn || !burgerBtn.contains(e.target))) {
2553
- panel.classList.remove("open");
2554
- }
2555
- });
2556
- });