doc-survival-kit 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/diagram.js ADDED
@@ -0,0 +1,1572 @@
1
+ // ══════════════════════════════════════
2
+ // mesDiagrammes — Éditeur SVG
3
+ // ══════════════════════════════════════
4
+
5
+ // ── État global ──
6
+ var currentTool = "select";
7
+ var diagramsList = [];
8
+ var currentDiagramIdx = 0;
9
+ var viewTransform = { x: 60, y: 60, scale: 1 };
10
+ var selectedId = null;
11
+ var selectedType = null; // "shape" | "arrow"
12
+ var selectedIds = []; // multi-sélection (ids de formes)
13
+ var rubberBandState = null; // { sx, sy } pendant le lasso
14
+ var clipboard = []; // formes copiées (Ctrl+C / Ctrl+V)
15
+ var pendingImageBlob = null; // image en attente de sélection du dossier
16
+ var pendingNewDiagram = false; // true pendant la saisie du nom d'un nouveau diagramme
17
+ var dragState = null;
18
+ var panStart = null;
19
+ var arrowSrcId = null;
20
+ var editingShapeId = null;
21
+ var pickMode = null; // "fontSize" | "fullStyle" | null
22
+ var pickTargetIds = []; // selectedIds sauvegardés pendant le pick
23
+ var lastClickTime = 0;
24
+ var lastClickShapeId = null;
25
+ var lastClickArrowId = null;
26
+ var editingArrowId = null;
27
+ var boardLocked = false;
28
+ var diagDragSrcIdx = null;
29
+
30
+ // ── Palette ──
31
+ var COLORS = {
32
+ "t-green": { fill: "rgba(209,250,229,0.75)", stroke: "#059669", text: "#047857" },
33
+ "t-violet": { fill: "rgba(237,233,254,0.75)", stroke: "#7c3aed", text: "#6d28d9" },
34
+ "t-amber": { fill: "rgba(254,243,199,0.75)", stroke: "#d97706", text: "#b45309" },
35
+ "t-sky": { fill: "rgba(224,242,254,0.75)", stroke: "#0284c7", text: "#0369a1" },
36
+ "t-rose": { fill: "rgba(255,228,230,0.75)", stroke: "#e11d48", text: "#be123c" },
37
+ "t-teal": { fill: "rgba(204,251,241,0.75)", stroke: "#0d9488", text: "#0f766e" },
38
+ };
39
+ var DEFAULT_COLOR = "t-sky";
40
+
41
+ var DEFAULT_SIZES = {
42
+ rect: { w: 130, h: 50 },
43
+ rounded: { w: 130, h: 50 },
44
+ db: { w: 100, h: 65 },
45
+ cloud: { w: 110, h: 65 },
46
+ text: { w: 110, h: 34 },
47
+ postit: { w: 130, h: 110 },
48
+ };
49
+
50
+ // ── Helpers ──
51
+ function escDiag(s) {
52
+ return String(s)
53
+ .replace(/&/g, "&")
54
+ .replace(/</g, "&lt;")
55
+ .replace(/>/g, "&gt;")
56
+ .replace(/"/g, "&quot;");
57
+ }
58
+
59
+ function createSVGEl(tag) {
60
+ return document.createElementNS("http://www.w3.org/2000/svg", tag);
61
+ }
62
+
63
+ // Découpe le texte d'un post-it en lignes qui tiennent dans maxWidth px.
64
+ // Respecte les \n explicites, puis fait du word-wrap sur les mots.
65
+ var _measureCanvas = null;
66
+ function measureText(str, fontSize, fontWeight) {
67
+ if (!_measureCanvas) _measureCanvas = document.createElement("canvas");
68
+ var ctx = _measureCanvas.getContext("2d");
69
+ ctx.font = (fontWeight || "600") + " " + (fontSize || 12) + "px \"Segoe UI\",system-ui,sans-serif";
70
+ return ctx.measureText(str).width;
71
+ }
72
+
73
+ function wrapPostitLines(text, maxWidth, fontSize) {
74
+ var fs = fontSize || 12;
75
+ var result = [];
76
+ (text || "").split("\n").forEach(function (line) {
77
+ if (line === "") { result.push(""); return; }
78
+ var words = line.split(" ");
79
+ var current = "";
80
+ words.forEach(function (word) {
81
+ var test = current ? current + " " + word : word;
82
+ if (current && measureText(test, fs, "600") > maxWidth) {
83
+ result.push(current);
84
+ current = word;
85
+ } else {
86
+ current = test;
87
+ }
88
+ });
89
+ if (current !== "") result.push(current);
90
+ });
91
+ return result;
92
+ }
93
+
94
+ function getCurrentDiagram() {
95
+ return diagramsList[currentDiagramIdx] || null;
96
+ }
97
+
98
+ // ── Persistance localStorage ──
99
+ function loadDiagrammes() {
100
+ try {
101
+ var stored = JSON.parse(localStorage.getItem("mes_diagrammes"));
102
+ if (stored && stored.length) return stored;
103
+ } catch (e) {}
104
+ return JSON.parse(JSON.stringify(diagrammesDefaut));
105
+ }
106
+
107
+ function saveDiagrammes() {
108
+ localStorage.setItem("mes_diagrammes", JSON.stringify(diagramsList));
109
+ checkDiffDiagrammes();
110
+ }
111
+
112
+ function checkDiffDiagrammes() {
113
+ var stored = localStorage.getItem("mes_diagrammes");
114
+ var diff = stored !== JSON.stringify(diagrammesDefaut);
115
+ document.getElementById("btnSaveDiagram").style.display = diff ? "inline-flex" : "none";
116
+ }
117
+
118
+ // ── Verrouillage par diagramme ──
119
+ 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>';
120
+ 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>';
121
+
122
+ function getLockMap() {
123
+ try { return JSON.parse(localStorage.getItem("diagrammes_lock") || "{}"); } catch(e) { return {}; }
124
+ }
125
+ function saveCurrentLock() {
126
+ var diag = diagramsList[currentDiagramIdx];
127
+ if (!diag) return;
128
+ var map = getLockMap();
129
+ map[diag.id] = boardLocked;
130
+ localStorage.setItem("diagrammes_lock", JSON.stringify(map));
131
+ }
132
+ function restoreLockForDiagram(idx) {
133
+ var diag = diagramsList[idx];
134
+ if (!diag) return;
135
+ var map = getLockMap();
136
+ boardLocked = map[diag.id] === true;
137
+ }
138
+ function toggleBoardLock() {
139
+ boardLocked = !boardLocked;
140
+ saveCurrentLock();
141
+ if (boardLocked) {
142
+ selectedId = null; selectedType = null;
143
+ selectedIds = [];
144
+ document.getElementById("colorPanel").style.display = "none";
145
+ renderAll();
146
+ }
147
+ updateLockBtn();
148
+ }
149
+ function updateLockBtn() {
150
+ var btn = document.getElementById("btnLock");
151
+ if (!btn) return;
152
+ btn.innerHTML = boardLocked ? LOCK_CLOSED_SVG : LOCK_OPEN_SVG;
153
+ btn.title = boardLocked ? "Déverrouiller le diagramme" : "Verrouiller le diagramme";
154
+ btn.classList.toggle("active", boardLocked);
155
+ }
156
+
157
+ // ── Zoom par diagramme ──
158
+ function getZoomMap() {
159
+ try { return JSON.parse(localStorage.getItem("diagrammes_zoom") || "{}"); } catch(e) { return {}; }
160
+ }
161
+ function saveCurrentZoom() {
162
+ var diag = diagramsList[currentDiagramIdx];
163
+ if (!diag) return;
164
+ var map = getZoomMap();
165
+ map[diag.id] = viewTransform.scale;
166
+ localStorage.setItem("diagrammes_zoom", JSON.stringify(map));
167
+ }
168
+ function restoreZoomForDiagram(idx) {
169
+ var diag = diagramsList[idx];
170
+ if (!diag) return;
171
+ var map = getZoomMap();
172
+ viewTransform.scale = map[diag.id] !== undefined ? map[diag.id] : 1;
173
+ }
174
+
175
+ // ── File System Access API ──
176
+ var IDB_KEY_DIAG = "diagrammes";
177
+ var IDB_KEY_DIR = "diagrammes_dir";
178
+
179
+ function ouvrirDB_Diag() {
180
+ return new Promise(function (resolve, reject) {
181
+ var req = indexedDB.open("doc-survival-kit-db", 1);
182
+ req.onupgradeneeded = function (e) {
183
+ e.target.result.createObjectStore("fileHandles");
184
+ };
185
+ req.onsuccess = function (e) { resolve(e.target.result); };
186
+ req.onerror = function (e) { reject(e.target.error); };
187
+ });
188
+ }
189
+
190
+ function sauvegarderHandle_Diag(handle) {
191
+ return ouvrirDB_Diag().then(function (db) {
192
+ return new Promise(function (resolve, reject) {
193
+ var tx = db.transaction("fileHandles", "readwrite");
194
+ tx.objectStore("fileHandles").put(handle, IDB_KEY_DIAG);
195
+ tx.oncomplete = resolve;
196
+ tx.onerror = function (e) { reject(e.target.error); };
197
+ });
198
+ });
199
+ }
200
+
201
+ function recupererHandle_Diag() {
202
+ return ouvrirDB_Diag().then(function (db) {
203
+ return new Promise(function (resolve, reject) {
204
+ var tx = db.transaction("fileHandles", "readonly");
205
+ var req = tx.objectStore("fileHandles").get(IDB_KEY_DIAG);
206
+ req.onsuccess = function (e) { resolve(e.target.result || null); };
207
+ req.onerror = function (e) { reject(e.target.error); };
208
+ });
209
+ });
210
+ }
211
+
212
+ function sauvegarderDirHandle(handle) {
213
+ return ouvrirDB_Diag().then(function (db) {
214
+ return new Promise(function (resolve, reject) {
215
+ var tx = db.transaction("fileHandles", "readwrite");
216
+ tx.objectStore("fileHandles").put(handle, IDB_KEY_DIR);
217
+ tx.oncomplete = resolve;
218
+ tx.onerror = function (e) { reject(e.target.error); };
219
+ });
220
+ });
221
+ }
222
+
223
+ function recupererDirHandle() {
224
+ return ouvrirDB_Diag().then(function (db) {
225
+ return new Promise(function (resolve, reject) {
226
+ var tx = db.transaction("fileHandles", "readonly");
227
+ var req = tx.objectStore("fileHandles").get(IDB_KEY_DIR);
228
+ req.onsuccess = function (e) { resolve(e.target.result || null); };
229
+ req.onerror = function (e) { reject(e.target.error); };
230
+ });
231
+ });
232
+ }
233
+
234
+ function enregistrerDiagrammes() {
235
+ if (!("showDirectoryPicker" in window)) return;
236
+ recupererHandle_Diag().then(function (handle) {
237
+ if (!handle) {
238
+ document.getElementById("erreurFichierDiag").style.display = "none";
239
+ document.getElementById("modalPremiereSauvegardeDiag").classList.add("open");
240
+ } else {
241
+ ecrireFichierDiag(handle);
242
+ }
243
+ });
244
+ }
245
+
246
+ function fermerModalSauvegardeDiag() {
247
+ document.getElementById("modalPremiereSauvegardeDiag").classList.remove("open");
248
+ }
249
+
250
+ function ouvrirSelecteurFichierDiag() {
251
+ fermerModalSauvegardeDiag();
252
+ var capturedDir = null;
253
+ window.showDirectoryPicker()
254
+ .then(function (dir) {
255
+ capturedDir = dir;
256
+ sauvegarderDirHandle(dir);
257
+ return dir.getFileHandle("diagrammes.js");
258
+ })
259
+ .then(function (handle) {
260
+ document.getElementById("erreurFichierDiag").style.display = "none";
261
+ return sauvegarderHandle_Diag(handle).then(function () {
262
+ return ecrireFichierDiag(handle);
263
+ }).then(function () {
264
+ if (pendingImageBlob && capturedDir) {
265
+ var blob = pendingImageBlob;
266
+ pendingImageBlob = null;
267
+ enregistrerImageDansBoard(blob, capturedDir);
268
+ }
269
+ });
270
+ })
271
+ .catch(function (err) {
272
+ if (err.name === "NotFoundError") {
273
+ document.getElementById("erreurFichierDiag").style.display = "block";
274
+ document.getElementById("modalPremiereSauvegardeDiag").classList.add("open");
275
+ } else if (err.name !== "AbortError") {
276
+ console.error(err);
277
+ }
278
+ });
279
+ }
280
+
281
+ function ecrireFichierDiag(handle) {
282
+ return handle.queryPermission({ mode: "readwrite" })
283
+ .then(function (p) {
284
+ if (p !== "granted") return handle.requestPermission({ mode: "readwrite" });
285
+ return p;
286
+ })
287
+ .then(function (p) {
288
+ if (p !== "granted") return;
289
+ var content = "var diagrammesDefaut = " + JSON.stringify(diagramsList, null, 2) + ";\n";
290
+ return handle.createWritable().then(function (w) {
291
+ return w.write(content).then(function () { return w.close(); });
292
+ }).then(function () {
293
+ diagrammesDefaut = JSON.parse(JSON.stringify(diagramsList));
294
+ checkDiffDiagrammes();
295
+ });
296
+ })
297
+ .catch(function (err) { console.error(err); });
298
+ }
299
+
300
+ // ── Image paste ──
301
+ function handleImagePaste(blob) {
302
+ recupererDirHandle().then(function (dirHandle) {
303
+ if (!dirHandle) {
304
+ pendingImageBlob = blob;
305
+ document.getElementById("erreurFichierDiag").style.display = "none";
306
+ document.getElementById("modalPremiereSauvegardeDiag").classList.add("open");
307
+ } else {
308
+ dirHandle.queryPermission({ mode: "readwrite" }).then(function (p) {
309
+ if (p !== "granted") return dirHandle.requestPermission({ mode: "readwrite" }).then(function (p2) { return p2; });
310
+ return p;
311
+ }).then(function (p) {
312
+ if (p === "granted") enregistrerImageDansBoard(blob, dirHandle);
313
+ });
314
+ }
315
+ });
316
+ }
317
+
318
+ function enregistrerImageDansBoard(blob, dirHandle) {
319
+ var filename = "img_" + Date.now() + ".png";
320
+ var src = "images/" + filename;
321
+ dirHandle.getDirectoryHandle("images", { create: true })
322
+ .then(function (imagesDir) {
323
+ return imagesDir.getFileHandle(filename, { create: true });
324
+ })
325
+ .then(function (fileHandle) {
326
+ return fileHandle.createWritable().then(function (w) {
327
+ return w.write(blob).then(function () { return w.close(); });
328
+ });
329
+ })
330
+ .then(function () {
331
+ // Lire les dimensions réelles de l'image
332
+ var url = URL.createObjectURL(blob);
333
+ var img = new Image();
334
+ img.onload = function () {
335
+ URL.revokeObjectURL(url);
336
+ var maxW = 600;
337
+ var w = Math.min(img.naturalWidth, maxW);
338
+ var h = Math.round(img.naturalHeight * (w / img.naturalWidth));
339
+ // Centrer sur la vue courante
340
+ var svg = document.getElementById("canvas");
341
+ var r = svg.getBoundingClientRect();
342
+ var cx = Math.round((r.width / 2 - viewTransform.x) / viewTransform.scale - w / 2);
343
+ var cy = Math.round((r.height / 2 - viewTransform.y) / viewTransform.scale - h / 2);
344
+ var diag = getCurrentDiagram();
345
+ var shape = { id: "s" + Date.now(), type: "image", x: cx, y: cy, w: w, h: h, text: "", color: "", src: src };
346
+ diag.shapes.push(shape);
347
+ selectedIds = [shape.id]; selectedId = shape.id; selectedType = "shape";
348
+ saveDiagrammes();
349
+ renderAll();
350
+ document.getElementById("colorPanel").style.display = "none";
351
+ };
352
+ img.src = url;
353
+ })
354
+ .catch(function (err) { console.error("Image paste error:", err); });
355
+ }
356
+
357
+ // ── Coordonnées SVG ──
358
+ function svgPoint(clientX, clientY) {
359
+ var svg = document.getElementById("canvas");
360
+ var rect = svg.getBoundingClientRect();
361
+ return {
362
+ x: (clientX - rect.left - viewTransform.x) / viewTransform.scale,
363
+ y: (clientY - rect.top - viewTransform.y) / viewTransform.scale,
364
+ };
365
+ }
366
+
367
+ function updateViewport() {
368
+ var vp = document.getElementById("viewport");
369
+ vp.setAttribute(
370
+ "transform",
371
+ "translate(" + viewTransform.x + "," + viewTransform.y + ") scale(" + viewTransform.scale + ")"
372
+ );
373
+ document.getElementById("zoomLevel").textContent =
374
+ Math.round(viewTransform.scale * 100) + "%";
375
+ }
376
+
377
+ // ── Zoom ──
378
+ function zoomIn() { applyZoom(1.2, null, null); }
379
+ function zoomOut() { applyZoom(1 / 1.2, null, null); }
380
+ function resetZoom() { viewTransform = { x: 60, y: 60, scale: 1 }; updateViewport(); saveCurrentZoom(); }
381
+
382
+ function applyZoom(factor, cx, cy) {
383
+ var newScale = Math.min(4, Math.max(0.15, viewTransform.scale * factor));
384
+ if (cx !== null && cy !== null) {
385
+ viewTransform.x = cx - (cx - viewTransform.x) * (newScale / viewTransform.scale);
386
+ viewTransform.y = cy - (cy - viewTransform.y) * (newScale / viewTransform.scale);
387
+ }
388
+ viewTransform.scale = newScale;
389
+ updateViewport();
390
+ saveCurrentZoom();
391
+ }
392
+
393
+ // ── Rendu des formes ──
394
+ function renderShape(shape) {
395
+ var c = COLORS[shape.color] || COLORS[DEFAULT_COLOR];
396
+ var isSel = selectedIds.indexOf(shape.id) !== -1;
397
+ var stroke = isSel ? "#f97316" : c.stroke;
398
+ var sw = isSel ? 2.5 : 1.5;
399
+
400
+ var g = createSVGEl("g");
401
+ g.setAttribute("data-id", shape.id);
402
+ g.setAttribute("data-type", "shape");
403
+ g.classList.add("shape-group");
404
+
405
+ // ── Fond selon le type ──
406
+ if (shape.type === "rect" || shape.type === "rounded") {
407
+ var rx = shape.type === "rounded" ? 12 : 3;
408
+ var r = createSVGEl("rect");
409
+ r.setAttribute("x", shape.x); r.setAttribute("y", shape.y);
410
+ r.setAttribute("width", shape.w); r.setAttribute("height", shape.h);
411
+ r.setAttribute("rx", rx);
412
+ r.setAttribute("fill", c.fill); r.setAttribute("stroke", stroke);
413
+ r.setAttribute("stroke-width", sw);
414
+ g.appendChild(r);
415
+
416
+ } else if (shape.type === "db") {
417
+ var ry = Math.min(12, shape.h * 0.2);
418
+ var cx = shape.x + shape.w / 2;
419
+ // Corps : path latéral + arc bas
420
+ var body = createSVGEl("path");
421
+ body.setAttribute("d", [
422
+ "M", shape.x, shape.y + ry,
423
+ "L", shape.x, shape.y + shape.h - ry,
424
+ "A", shape.w / 2, ry, 0, 0, 0, shape.x + shape.w, shape.y + shape.h - ry,
425
+ "L", shape.x + shape.w, shape.y + ry,
426
+ "A", shape.w / 2, ry, 0, 0, 0, shape.x, shape.y + ry,
427
+ "Z",
428
+ ].join(" "));
429
+ body.setAttribute("fill", c.fill);
430
+ body.setAttribute("stroke", stroke);
431
+ body.setAttribute("stroke-width", sw);
432
+ g.appendChild(body);
433
+ // Ellipse du haut (face visible)
434
+ var topEl = createSVGEl("ellipse");
435
+ topEl.setAttribute("cx", cx); topEl.setAttribute("cy", shape.y + ry);
436
+ topEl.setAttribute("rx", shape.w / 2); topEl.setAttribute("ry", ry);
437
+ topEl.setAttribute("fill", c.fill);
438
+ topEl.setAttribute("stroke", stroke); topEl.setAttribute("stroke-width", sw);
439
+ g.appendChild(topEl);
440
+
441
+ } else if (shape.type === "cloud") {
442
+ // Ellipse à bordure pointillée = service externe / cloud
443
+ var el = createSVGEl("ellipse");
444
+ el.setAttribute("cx", shape.x + shape.w / 2);
445
+ el.setAttribute("cy", shape.y + shape.h / 2);
446
+ el.setAttribute("rx", shape.w / 2);
447
+ el.setAttribute("ry", shape.h / 2);
448
+ el.setAttribute("fill", c.fill);
449
+ el.setAttribute("stroke", stroke);
450
+ el.setAttribute("stroke-width", sw);
451
+ g.appendChild(el);
452
+
453
+ } else if (shape.type === "postit") {
454
+ var fold = 18;
455
+ var body = createSVGEl("path");
456
+ body.setAttribute("d", [
457
+ "M", shape.x, shape.y,
458
+ "L", shape.x + shape.w - fold, shape.y,
459
+ "L", shape.x + shape.w, shape.y + fold,
460
+ "L", shape.x + shape.w, shape.y + shape.h,
461
+ "L", shape.x, shape.y + shape.h,
462
+ "Z",
463
+ ].join(" "));
464
+ body.setAttribute("fill", c.fill);
465
+ body.setAttribute("stroke", stroke);
466
+ body.setAttribute("stroke-width", sw);
467
+ g.appendChild(body);
468
+ // Triangle du coin replié
469
+ var foldTri = createSVGEl("path");
470
+ foldTri.setAttribute("d", [
471
+ "M", shape.x + shape.w - fold, shape.y,
472
+ "L", shape.x + shape.w, shape.y + fold,
473
+ "L", shape.x + shape.w - fold, shape.y + fold,
474
+ "Z",
475
+ ].join(" "));
476
+ foldTri.setAttribute("fill", c.stroke);
477
+ foldTri.setAttribute("fill-opacity", "0.25");
478
+ foldTri.setAttribute("stroke", stroke);
479
+ foldTri.setAttribute("stroke-width", sw * 0.7);
480
+ g.appendChild(foldTri);
481
+
482
+ } else if (shape.type === "image") {
483
+ var imgEl = createSVGEl("image");
484
+ imgEl.setAttribute("x", shape.x); imgEl.setAttribute("y", shape.y);
485
+ imgEl.setAttribute("width", shape.w); imgEl.setAttribute("height", shape.h);
486
+ imgEl.setAttribute("href", shape.src);
487
+ imgEl.setAttribute("preserveAspectRatio", "xMidYMid meet");
488
+ g.appendChild(imgEl);
489
+ // Bordure de sélection
490
+ if (isSel) {
491
+ var selRect = createSVGEl("rect");
492
+ selRect.setAttribute("x", shape.x); selRect.setAttribute("y", shape.y);
493
+ selRect.setAttribute("width", shape.w); selRect.setAttribute("height", shape.h);
494
+ selRect.setAttribute("fill", "none");
495
+ selRect.setAttribute("stroke", "#f97316"); selRect.setAttribute("stroke-width", "2");
496
+ selRect.setAttribute("stroke-dasharray", "6,3");
497
+ selRect.setAttribute("pointer-events", "none");
498
+ g.appendChild(selRect);
499
+ }
500
+
501
+ } else if (shape.type === "text") {
502
+ // pas de fond — juste le texte
503
+ }
504
+
505
+ // ── Texte ──
506
+ var textCy = shape.type === "db"
507
+ ? shape.y + shape.h * 0.62
508
+ : shape.y + shape.h / 2;
509
+ var ta = shape.textAlign || "center";
510
+ var textAnchor = ta === "left" ? "start" : ta === "right" ? "end" : "middle";
511
+ var txt = createSVGEl("text");
512
+ txt.setAttribute("x", shape.x + shape.w / 2);
513
+ txt.setAttribute("y", textCy);
514
+ txt.setAttribute("text-anchor", textAnchor);
515
+ txt.setAttribute("dominant-baseline", "middle");
516
+ var fs = shape.fontSize || (shape.type === "text" ? 13 : 12);
517
+ txt.setAttribute("fill", shape.type === "text" ? "#292524" : c.text);
518
+ txt.setAttribute("font-size", fs);
519
+ txt.setAttribute("font-family", '"Segoe UI",system-ui,sans-serif');
520
+ txt.setAttribute("font-weight", shape.type === "text" ? "400" : "600");
521
+ txt.setAttribute("pointer-events", "none");
522
+ var tv = shape.textValign || "middle";
523
+ if (shape.type === "postit") {
524
+ var pad = 14;
525
+ var lines = wrapPostitLines(shape.text || "", shape.w - pad * 2, fs);
526
+ var lineH = Math.round(fs * 1.42);
527
+ var firstY = tv === "top"
528
+ ? shape.y + pad + lineH * 0.5
529
+ : tv === "bottom"
530
+ ? shape.y + shape.h - pad - (lines.length - 1) * lineH - fs * 0.2
531
+ : shape.y + (shape.h - lines.length * lineH) / 2 + lineH * 0.5 + fs * 0.3;
532
+ var tspanX = ta === "left" ? shape.x + pad : ta === "right" ? shape.x + shape.w - pad : shape.x + shape.w / 2;
533
+ txt.setAttribute("y", firstY);
534
+ txt.removeAttribute("dominant-baseline");
535
+ lines.forEach(function (line, i) {
536
+ var ts = createSVGEl("tspan");
537
+ ts.setAttribute("x", tspanX);
538
+ if (i > 0) ts.setAttribute("dy", lineH + "px");
539
+ ts.textContent = line || " ";
540
+ txt.appendChild(ts);
541
+ });
542
+ } else if (shape.type === "rect" || shape.type === "rounded" || shape.type === "db" || shape.type === "cloud") {
543
+ var wpad = shape.type === "cloud" ? Math.round(shape.w * 0.2) : 12;
544
+ var wvpad = shape.type === "cloud" ? Math.round(shape.h * 0.12) : 8;
545
+ var wlines = wrapPostitLines(shape.text || "", shape.w - wpad * 2, fs);
546
+ var wlineH = Math.round(fs * 1.33);
547
+ var wcenterY = shape.type === "db" ? shape.y + shape.h * 0.62 : shape.y + shape.h / 2;
548
+ var dbCapH = shape.type === "db" ? Math.min(12, shape.h * 0.2) * 2 : 0;
549
+ var wfirstY = tv === "top"
550
+ ? shape.y + dbCapH + wvpad + wlineH * 0.5
551
+ : tv === "bottom"
552
+ ? shape.y + shape.h - wvpad - (wlines.length - 1) * wlineH - fs * 0.2
553
+ : wcenterY - wlines.length * wlineH / 2 + wlineH * 0.5 + fs * 0.5;
554
+ var wtspanX = ta === "left" ? shape.x + wpad : ta === "right" ? shape.x + shape.w - wpad : shape.x + shape.w / 2;
555
+ txt.setAttribute("x", wtspanX);
556
+ txt.setAttribute("y", wfirstY);
557
+ txt.removeAttribute("dominant-baseline");
558
+ wlines.forEach(function (line, i) {
559
+ var ts = createSVGEl("tspan");
560
+ ts.setAttribute("x", wtspanX);
561
+ if (i > 0) ts.setAttribute("dy", wlineH + "px");
562
+ ts.textContent = line || " ";
563
+ txt.appendChild(ts);
564
+ });
565
+ } else {
566
+ txt.textContent = shape.text || "";
567
+ }
568
+ g.appendChild(txt);
569
+
570
+ // ── Poignée de redimensionnement (coin bas-droit, sélection unique seulement) ──
571
+ if (isSel && selectedIds.length === 1) {
572
+ var grip = createSVGEl("rect");
573
+ grip.setAttribute("x", shape.x + shape.w - 5);
574
+ grip.setAttribute("y", shape.y + shape.h - 5);
575
+ grip.setAttribute("width", 10); grip.setAttribute("height", 10);
576
+ grip.setAttribute("rx", 2);
577
+ grip.setAttribute("fill", "#f97316");
578
+ grip.setAttribute("stroke", "#fff"); grip.setAttribute("stroke-width", 1.5);
579
+ grip.classList.add("resize-grip");
580
+ grip.setAttribute("data-shape-id", shape.id);
581
+ g.appendChild(grip);
582
+ }
583
+
584
+ // ── Texte non rendu pour les images ──
585
+ if (shape.type === "image") return g;
586
+
587
+ // ── Points de connexion (visibles au hover et en mode flèche — sauf postit) ──
588
+ if (shape.type !== "postit") {
589
+ var hcx = shape.x + shape.w / 2, hcy = shape.y + shape.h / 2;
590
+ [
591
+ [hcx, shape.y],
592
+ [hcx, shape.y + shape.h],
593
+ [shape.x, hcy],
594
+ [shape.x + shape.w, hcy],
595
+ ].forEach(function (pt) {
596
+ var dot = createSVGEl("circle");
597
+ dot.setAttribute("cx", pt[0]); dot.setAttribute("cy", pt[1]);
598
+ dot.setAttribute("r", 5);
599
+ dot.setAttribute("fill", "#f97316");
600
+ dot.setAttribute("stroke", "#fff"); dot.setAttribute("stroke-width", 1.5);
601
+ dot.classList.add("conn-dot");
602
+ dot.setAttribute("data-shape-id", shape.id);
603
+ g.appendChild(dot);
604
+ });
605
+ }
606
+
607
+ return g;
608
+ }
609
+
610
+ // ── Rendu d'une flèche ──
611
+ function getEdgePoint(shape, targetX, targetY) {
612
+ var cx = shape.x + shape.w / 2;
613
+ var cy = shape.y + shape.h / 2;
614
+ var dx = targetX - cx, dy = targetY - cy;
615
+ if (dx === 0 && dy === 0) return { x: cx, y: shape.y };
616
+ var hw = shape.w / 2, hh = shape.h / 2;
617
+ if (Math.abs(dx) * hh > Math.abs(dy) * hw) {
618
+ var sx = dx > 0 ? 1 : -1;
619
+ return { x: cx + sx * hw, y: cy + dy * hw / Math.abs(dx) };
620
+ } else {
621
+ var sy = dy > 0 ? 1 : -1;
622
+ return { x: cx + dx * hh / Math.abs(dy), y: cy + sy * hh };
623
+ }
624
+ }
625
+
626
+ function renderArrow(arrow, shapes) {
627
+ var from = shapes.find(function (s) { return s.id === arrow.from; });
628
+ var to = shapes.find(function (s) { return s.id === arrow.to; });
629
+ if (!from || !to) return null;
630
+
631
+ var toCx = to.x + to.w / 2, toCy = to.y + to.h / 2;
632
+ var frCx = from.x + from.w / 2, frCy = from.y + from.h / 2;
633
+ var fp = getEdgePoint(from, toCx, toCy);
634
+ var tp = getEdgePoint(to, frCx, frCy);
635
+
636
+ // Raccourcir légèrement la pointe pour ne pas chevauchner la forme
637
+ var dx = tp.x - fp.x, dy = tp.y - fp.y;
638
+ var len = Math.sqrt(dx * dx + dy * dy);
639
+ if (len > 16) {
640
+ var off = 7 / len;
641
+ tp = { x: tp.x - dx * off, y: tp.y - dy * off };
642
+ }
643
+
644
+ var isSel = selectedId === arrow.id && selectedType === "arrow";
645
+ var stroke = isSel ? "#f97316" : "#a8a29e";
646
+ var markerId = isSel ? "arrowhead-sel" : "arrowhead";
647
+
648
+ var g = createSVGEl("g");
649
+ g.setAttribute("data-id", arrow.id);
650
+ g.setAttribute("data-type", "arrow");
651
+ g.classList.add("arrow-group");
652
+
653
+ var line = createSVGEl("line");
654
+ line.setAttribute("x1", fp.x); line.setAttribute("y1", fp.y);
655
+ line.setAttribute("x2", tp.x); line.setAttribute("y2", tp.y);
656
+ line.setAttribute("stroke", stroke);
657
+ line.setAttribute("stroke-width", isSel ? 2 : 1.5);
658
+ line.setAttribute("marker-end", "url(#" + markerId + ")");
659
+ g.appendChild(line);
660
+
661
+ // Zone de clic élargie
662
+ var hit = createSVGEl("line");
663
+ hit.setAttribute("x1", fp.x); hit.setAttribute("y1", fp.y);
664
+ hit.setAttribute("x2", tp.x); hit.setAttribute("y2", tp.y);
665
+ hit.setAttribute("stroke", "transparent");
666
+ hit.setAttribute("stroke-width", 14);
667
+ g.appendChild(hit);
668
+
669
+ // Label
670
+ if (arrow.label) {
671
+ var mx = (fp.x + tp.x) / 2, my = (fp.y + tp.y) / 2;
672
+ var lbl = createSVGEl("text");
673
+ lbl.setAttribute("x", mx); lbl.setAttribute("y", my - 7);
674
+ lbl.setAttribute("text-anchor", "middle");
675
+ lbl.setAttribute("font-size", "10");
676
+ lbl.setAttribute("font-family", '"Cascadia Code","SF Mono",Consolas,monospace');
677
+ lbl.setAttribute("fill", "#a8a29e");
678
+ lbl.setAttribute("pointer-events", "none");
679
+ lbl.textContent = arrow.label;
680
+ g.appendChild(lbl);
681
+ }
682
+
683
+ return g;
684
+ }
685
+
686
+ // ── Rendu complet ──
687
+ function renderAll() {
688
+ var diag = getCurrentDiagram();
689
+ if (!diag) return;
690
+
691
+ document.getElementById("diagramTitle").value = diag.titre;
692
+
693
+ var arrowsLayer = document.getElementById("arrowsLayer");
694
+ arrowsLayer.innerHTML = "";
695
+ (diag.arrows || []).forEach(function (a) {
696
+ var el = renderArrow(a, diag.shapes);
697
+ if (el) arrowsLayer.appendChild(el);
698
+ });
699
+
700
+ var shapesLayer = document.getElementById("shapesLayer");
701
+ shapesLayer.innerHTML = "";
702
+ (diag.shapes || []).forEach(function (s) {
703
+ shapesLayer.appendChild(renderShape(s));
704
+ });
705
+
706
+ updateViewport();
707
+ renderDiagramList();
708
+ }
709
+
710
+ function renderDiagramList() {
711
+ var el = document.getElementById("diagramList");
712
+ el.innerHTML = diagramsList.map(function (d, i) {
713
+ var active = i === currentDiagramIdx ? " active" : "";
714
+ return (
715
+ '<div class="diagram-list-item' + active + '" draggable="true"' +
716
+ ' ondragstart="onDiagDragStart(event,' + i + ')"' +
717
+ ' ondragover="onDiagDragOver(event,' + i + ')"' +
718
+ ' ondragleave="onDiagDragLeave(event)"' +
719
+ ' ondrop="onDiagDrop(event,' + i + ')"' +
720
+ ' ondragend="onDiagDragEnd()"' +
721
+ ' onclick="selectDiagramme(' + i + ')">' +
722
+ '<span class="diagram-list-grip">⠿</span>' +
723
+ '<span class="diagram-list-name">' + escDiag(d.titre) + '</span>' +
724
+ (diagramsList.length > 1
725
+ ? '<button class="diagram-list-del" onclick="event.stopPropagation();supprimerDiagramme(' + i + ')">×</button>'
726
+ : "") +
727
+ "</div>"
728
+ );
729
+ }).join("");
730
+ }
731
+
732
+ function onDiagDragStart(e, idx) {
733
+ diagDragSrcIdx = idx;
734
+ e.dataTransfer.effectAllowed = "move";
735
+ e.currentTarget.classList.add("dragging");
736
+ }
737
+
738
+ function onDiagDragOver(e, idx) {
739
+ e.preventDefault();
740
+ e.dataTransfer.dropEffect = "move";
741
+ document.querySelectorAll(".diagram-list-item").forEach(function (el) {
742
+ el.classList.remove("drag-over-after", "drag-over-before");
743
+ });
744
+ if (idx !== diagDragSrcIdx) {
745
+ var goingDown = diagDragSrcIdx < idx;
746
+ e.currentTarget.classList.add(goingDown ? "drag-over-after" : "drag-over-before");
747
+ }
748
+ }
749
+
750
+ function onDiagDragLeave(e) {
751
+ e.currentTarget.classList.remove("drag-over-after", "drag-over-before");
752
+ }
753
+
754
+ function onDiagDrop(e, idx) {
755
+ e.preventDefault();
756
+ if (diagDragSrcIdx === null || diagDragSrcIdx === idx) {
757
+ onDiagDragEnd();
758
+ return;
759
+ }
760
+ var src = diagDragSrcIdx;
761
+ var item = diagramsList.splice(src, 1)[0];
762
+
763
+ // insertAt = idx dans les deux cas :
764
+ // - descente (src < idx) : après remove, la cible est à idx-1, on insère à idx = après elle ✓
765
+ // - montée (src > idx) : après remove, la cible est toujours à idx, on insère à idx = avant elle ✓
766
+ diagramsList.splice(idx, 0, item);
767
+
768
+ var cur = currentDiagramIdx;
769
+ if (cur === src) {
770
+ currentDiagramIdx = idx;
771
+ } else {
772
+ var adjustedCur = cur > src ? cur - 1 : cur;
773
+ currentDiagramIdx = adjustedCur >= idx ? adjustedCur + 1 : adjustedCur;
774
+ }
775
+ localStorage.setItem("current_diagram_idx", currentDiagramIdx);
776
+ diagDragSrcIdx = null;
777
+ saveDiagrammes();
778
+ renderDiagramList();
779
+ }
780
+
781
+ function onDiagDragEnd() {
782
+ diagDragSrcIdx = null;
783
+ document.querySelectorAll(".diagram-list-item").forEach(function (el) {
784
+ el.classList.remove("dragging", "drag-over-after", "drag-over-before");
785
+ });
786
+ }
787
+
788
+ // ── Gestion des diagrammes ──
789
+ function selectDiagramme(idx) {
790
+ saveCurrentZoom();
791
+ saveCurrentLock();
792
+ currentDiagramIdx = idx;
793
+ localStorage.setItem("current_diagram_idx", idx);
794
+ selectedId = null; selectedType = null;
795
+ selectedIds = [];
796
+ restoreZoomForDiagram(idx);
797
+ restoreLockForDiagram(idx);
798
+ viewTransform.x = 60;
799
+ viewTransform.y = 60;
800
+ document.getElementById("colorPanel").style.display = "none";
801
+ renderAll();
802
+ updateLockBtn();
803
+ document.getElementById("diagramListPanel").classList.remove("open");
804
+ }
805
+
806
+ function creerDiagramme() {
807
+ pendingNewDiagram = true;
808
+ var input = document.getElementById("diagramTitle");
809
+ input.value = "";
810
+ input.placeholder = window.t ? window.t.diag_new_diagram : "Nouveau diagramme";
811
+ input.focus();
812
+ input.select();
813
+ }
814
+
815
+ function confirmerNouveauDiagramme() {
816
+ if (!pendingNewDiagram) return;
817
+ pendingNewDiagram = false;
818
+ var input = document.getElementById("diagramTitle");
819
+ var titre = input.value.trim() || (window.t ? window.t.diag_new_diagram : "Nouveau diagramme");
820
+ input.placeholder = "";
821
+ var d = { id: Date.now(), titre: titre, shapes: [], arrows: [] };
822
+ diagramsList.push(d);
823
+ saveDiagrammes();
824
+ selectDiagramme(diagramsList.length - 1);
825
+ document.getElementById("diagramTitle").blur();
826
+ }
827
+
828
+ function annulerNouveauDiagramme() {
829
+ if (!pendingNewDiagram) return;
830
+ pendingNewDiagram = false;
831
+ var diag = getCurrentDiagram();
832
+ var input = document.getElementById("diagramTitle");
833
+ input.value = diag ? diag.titre : "";
834
+ input.placeholder = "";
835
+ input.blur();
836
+ }
837
+
838
+ function supprimerDiagramme(idx) {
839
+ if (diagramsList.length <= 1) return;
840
+ diagramsList.splice(idx, 1);
841
+ if (currentDiagramIdx >= diagramsList.length) currentDiagramIdx = diagramsList.length - 1;
842
+ localStorage.setItem("current_diagram_idx", currentDiagramIdx);
843
+ saveDiagrammes();
844
+ renderAll();
845
+ }
846
+
847
+ function toggleDiagramList() {
848
+ document.getElementById("diagramListPanel").classList.toggle("open");
849
+ }
850
+
851
+ // ── Outil actif ──
852
+ function setTool(tool) {
853
+ currentTool = tool;
854
+ document.querySelectorAll(".diagram-tool[id^='tool']").forEach(function (btn) {
855
+ btn.classList.remove("active");
856
+ });
857
+ var btnId = "tool" + tool.charAt(0).toUpperCase() + tool.slice(1);
858
+ var btn = document.getElementById(btnId);
859
+ if (btn) btn.classList.add("active");
860
+
861
+ arrowSrcId = null;
862
+ selectedIds = [];
863
+ document.getElementById("tempArrow").style.display = "none";
864
+ document.getElementById("canvas").setAttribute(
865
+ "data-tool", tool
866
+ );
867
+ if (tool !== "select") {
868
+ selectedId = null; selectedType = null;
869
+ document.getElementById("colorPanel").style.display = "none";
870
+ }
871
+ renderAll();
872
+ }
873
+
874
+ // ── Ajout d'une forme ──
875
+ function addShape(type, x, y) {
876
+ var diag = getCurrentDiagram();
877
+ var size = DEFAULT_SIZES[type] || { w: 120, h: 50 };
878
+ var labels = { db: "Database", cloud: "Cloud", text: "Label", postit: "" };
879
+ var shape = {
880
+ id: "s" + Date.now(),
881
+ type: type,
882
+ x: Math.round(x - size.w / 2),
883
+ y: Math.round(y - size.h / 2),
884
+ w: size.w,
885
+ h: size.h,
886
+ text: type in labels ? labels[type] : "Service",
887
+ color: type === "postit" ? "t-amber" : DEFAULT_COLOR,
888
+ };
889
+ diag.shapes.push(shape);
890
+ saveDiagrammes();
891
+ selectedId = shape.id; selectedType = "shape";
892
+ selectedIds = [shape.id];
893
+ renderAll();
894
+ document.getElementById("colorPanel").style.display = "flex";
895
+ startTextEdit(shape.id);
896
+ }
897
+
898
+ // ── Suppression ──
899
+ function deleteSelected() {
900
+ var diag = getCurrentDiagram();
901
+ if (selectedIds.length > 0) {
902
+ diag.shapes = diag.shapes.filter(function (s) { return selectedIds.indexOf(s.id) === -1; });
903
+ diag.arrows = diag.arrows.filter(function (a) {
904
+ return selectedIds.indexOf(a.from) === -1 && selectedIds.indexOf(a.to) === -1;
905
+ });
906
+ selectedIds = [];
907
+ selectedId = null; selectedType = null;
908
+ } else if (selectedId && selectedType === "arrow") {
909
+ diag.arrows = diag.arrows.filter(function (a) { return a.id !== selectedId; });
910
+ selectedId = null; selectedType = null;
911
+ } else {
912
+ return;
913
+ }
914
+ document.getElementById("colorPanel").style.display = "none";
915
+ saveDiagrammes();
916
+ renderAll();
917
+ }
918
+
919
+ // ── Couleur ──
920
+ function setShapeColor(color) {
921
+ if (selectedIds.length === 0) return;
922
+ var diag = getCurrentDiagram();
923
+ selectedIds.forEach(function (id) {
924
+ var shape = diag.shapes.find(function (s) { return s.id === id; });
925
+ if (shape) shape.color = color;
926
+ });
927
+ saveDiagrammes();
928
+ renderAll();
929
+ document.getElementById("colorPanel").style.display = "flex";
930
+ }
931
+
932
+ function setShapeTextValign(valign) {
933
+ if (selectedIds.length === 0) return;
934
+ var diag = getCurrentDiagram();
935
+ selectedIds.forEach(function (id) {
936
+ var shape = diag.shapes.find(function (s) { return s.id === id; });
937
+ if (shape) shape.textValign = valign;
938
+ });
939
+ saveDiagrammes();
940
+ renderAll();
941
+ document.getElementById("colorPanel").style.display = "flex";
942
+ }
943
+
944
+ function setShapeTextAlign(align) {
945
+ if (selectedIds.length === 0) return;
946
+ var diag = getCurrentDiagram();
947
+ selectedIds.forEach(function (id) {
948
+ var shape = diag.shapes.find(function (s) { return s.id === id; });
949
+ if (shape) shape.textAlign = align;
950
+ });
951
+ saveDiagrammes();
952
+ renderAll();
953
+ document.getElementById("colorPanel").style.display = "flex";
954
+ }
955
+
956
+ function changeShapeFontSize(delta) {
957
+ if (selectedIds.length === 0) return;
958
+ var diag = getCurrentDiagram();
959
+ selectedIds.forEach(function (id) {
960
+ var shape = diag.shapes.find(function (s) { return s.id === id; });
961
+ if (!shape) return;
962
+ var current = shape.fontSize || (shape.type === "text" ? 13 : 12);
963
+ shape.fontSize = Math.max(8, Math.min(28, current + delta));
964
+ });
965
+ saveDiagrammes();
966
+ renderAll();
967
+ document.getElementById("colorPanel").style.display = "flex";
968
+ }
969
+
970
+ // ── Copie de style (pick mode) ──
971
+ function startPickMode(mode) {
972
+ pickTargetIds = selectedIds.slice();
973
+ pickMode = mode;
974
+ document.getElementById("canvas").style.cursor = "crosshair";
975
+ document.getElementById("btnPickFont").classList.toggle("diagram-pick-active", mode === "fontSize");
976
+ document.getElementById("btnPickStyle").classList.toggle("diagram-pick-active", mode === "fullStyle");
977
+ }
978
+
979
+ function cancelPickMode() {
980
+ pickMode = null;
981
+ pickTargetIds = [];
982
+ document.getElementById("canvas").style.cursor = "";
983
+ document.getElementById("btnPickFont").classList.remove("diagram-pick-active");
984
+ document.getElementById("btnPickStyle").classList.remove("diagram-pick-active");
985
+ }
986
+
987
+ function applyPickMode(srcShape) {
988
+ var diag = getCurrentDiagram();
989
+ pickTargetIds.forEach(function (id) {
990
+ if (id === srcShape.id) return;
991
+ var shape = diag.shapes.find(function (s) { return s.id === id; });
992
+ if (!shape) return;
993
+ if (pickMode === "fontSize") {
994
+ shape.fontSize = srcShape.fontSize || (srcShape.type === "text" ? 13 : 12);
995
+ } else if (pickMode === "fullStyle") {
996
+ shape.fontSize = srcShape.fontSize || (srcShape.type === "text" ? 13 : 12);
997
+ shape.color = srcShape.color;
998
+ shape.type = srcShape.type;
999
+ shape.w = srcShape.w;
1000
+ shape.h = srcShape.h;
1001
+ shape.textAlign = srcShape.textAlign || "center";
1002
+ shape.textValign = srcShape.textValign || "middle";
1003
+ }
1004
+ });
1005
+ selectedIds = pickTargetIds.slice();
1006
+ saveDiagrammes();
1007
+ renderAll();
1008
+ document.getElementById("colorPanel").style.display = "flex";
1009
+ }
1010
+
1011
+ // ── Édition texte inline ──
1012
+ function startTextEdit(shapeId) {
1013
+ var diag = getCurrentDiagram();
1014
+ var shape = diag.shapes.find(function (s) { return s.id === shapeId; });
1015
+ if (!shape || shape.type === "image") return;
1016
+ editingShapeId = shapeId;
1017
+
1018
+ var svg = document.getElementById("canvas");
1019
+ var sr = svg.getBoundingClientRect();
1020
+ var sx = shape.x * viewTransform.scale + viewTransform.x + sr.left;
1021
+ var sy = shape.y * viewTransform.scale + viewTransform.y + sr.top;
1022
+ var sw = shape.w * viewTransform.scale;
1023
+ var sh = shape.h * viewTransform.scale;
1024
+ var c = COLORS[shape.color] || COLORS[DEFAULT_COLOR];
1025
+
1026
+ var useTextarea = shape.type === "postit" || shape.type === "rect" || shape.type === "rounded" || shape.type === "db" || shape.type === "cloud";
1027
+ if (useTextarea) {
1028
+ var ta = document.getElementById("postitTextInput");
1029
+ ta.value = shape.text || "";
1030
+ var hpad = shape.type === "cloud" ? Math.round(sw * 0.15) : 8;
1031
+ var vtop, vheight;
1032
+ if (shape.type === "cloud") {
1033
+ vtop = sy + Math.round(sh * 0.12);
1034
+ vheight = sh - Math.round(sh * 0.12) * 2;
1035
+ } else if (shape.type === "db") {
1036
+ var dbRy = Math.min(12, shape.h * 0.2) * viewTransform.scale;
1037
+ vtop = sy + dbRy * 2 + 4;
1038
+ vheight = sh - dbRy * 2 - 12;
1039
+ } else {
1040
+ vtop = sy + 8;
1041
+ vheight = sh - 16;
1042
+ }
1043
+ ta.style.left = (sx + hpad) + "px";
1044
+ ta.style.top = vtop + "px";
1045
+ ta.style.width = (sw - hpad * 2) + "px";
1046
+ ta.style.height = vheight + "px";
1047
+ var editFs = shape.fontSize || (shape.type === "text" ? 13 : 12);
1048
+ ta.style.fontSize = Math.max(10, editFs * viewTransform.scale) + "px";
1049
+ ta.style.color = c.text;
1050
+ ta.style.display = "block";
1051
+ document.getElementById("shapeTextInput").style.display = "none";
1052
+ document.getElementById("textOverlay").style.display = "block";
1053
+ // Masquer le texte SVG pour éviter la double lecture
1054
+ var sg = document.querySelector('#shapesLayer [data-id="' + shapeId + '"]');
1055
+ if (sg) { var tx = sg.querySelector("text"); if (tx) tx.style.visibility = "hidden"; }
1056
+ setTimeout(function () { ta.focus(); ta.select(); }, 10);
1057
+ } else {
1058
+ var input = document.getElementById("shapeTextInput");
1059
+ input.value = shape.text || "";
1060
+ input.style.left = (sx + sw * 0.08) + "px";
1061
+ input.style.top = (sy + sh * 0.22) + "px";
1062
+ input.style.width = (sw * 0.84) + "px";
1063
+ var editFs2 = shape.fontSize || 13;
1064
+ input.style.fontSize = Math.max(10, editFs2 * viewTransform.scale) + "px";
1065
+ input.style.color = c.text;
1066
+ input.style.display = "block";
1067
+ document.getElementById("postitTextInput").style.display = "none";
1068
+ document.getElementById("textOverlay").style.display = "block";
1069
+ setTimeout(function () { input.focus(); input.select(); }, 10);
1070
+ }
1071
+ }
1072
+
1073
+ function confirmTextEdit() {
1074
+ var input = document.getElementById("shapeTextInput");
1075
+ var diag = getCurrentDiagram();
1076
+ if (editingShapeId) {
1077
+ var shape = diag.shapes.find(function (s) { return s.id === editingShapeId; });
1078
+ if (shape) {
1079
+ var usesTextarea = shape.type === "postit" || shape.type === "rect" || shape.type === "rounded" || shape.type === "db" || shape.type === "cloud";
1080
+ var val = usesTextarea
1081
+ ? document.getElementById("postitTextInput").value
1082
+ : input.value;
1083
+ shape.text = val;
1084
+ saveDiagrammes();
1085
+ renderAll();
1086
+ }
1087
+ editingShapeId = null;
1088
+ } else if (editingArrowId) {
1089
+ var arrow = (diag.arrows || []).find(function (a) { return a.id === editingArrowId; });
1090
+ if (arrow) { arrow.label = input.value; saveDiagrammes(); renderAll(); }
1091
+ editingArrowId = null;
1092
+ }
1093
+ document.getElementById("textOverlay").style.display = "none";
1094
+ document.getElementById("postitTextInput").style.display = "none";
1095
+ document.getElementById("shapeTextInput").style.display = "block";
1096
+ }
1097
+
1098
+ function startArrowTextEdit(arrowId) {
1099
+ var diag = getCurrentDiagram();
1100
+ var arrow = (diag.arrows || []).find(function (a) { return a.id === arrowId; });
1101
+ if (!arrow) return;
1102
+ var from = diag.shapes.find(function (s) { return s.id === arrow.from; });
1103
+ var to = diag.shapes.find(function (s) { return s.id === arrow.to; });
1104
+ if (!from || !to) return;
1105
+
1106
+ var toCx = to.x + to.w / 2, toCy = to.y + to.h / 2;
1107
+ var frCx = from.x + from.w / 2, frCy = from.y + from.h / 2;
1108
+ var fp = getEdgePoint(from, toCx, toCy);
1109
+ var tp = getEdgePoint(to, frCx, frCy);
1110
+ var mx = (fp.x + tp.x) / 2;
1111
+ var my = (fp.y + tp.y) / 2;
1112
+
1113
+ var svg = document.getElementById("canvas");
1114
+ var sr = svg.getBoundingClientRect();
1115
+ var sx = mx * viewTransform.scale + viewTransform.x + sr.left;
1116
+ var sy = my * viewTransform.scale + viewTransform.y + sr.top;
1117
+
1118
+ editingArrowId = arrowId;
1119
+ var input = document.getElementById("shapeTextInput");
1120
+ input.value = arrow.label || "";
1121
+ input.style.left = (sx - 60) + "px";
1122
+ input.style.top = (sy - 12) + "px";
1123
+ input.style.width = "120px";
1124
+ input.style.fontSize = Math.max(10, 11 * viewTransform.scale) + "px";
1125
+ input.style.color = "#78716c";
1126
+
1127
+ document.getElementById("textOverlay").style.display = "block";
1128
+ setTimeout(function () { input.focus(); input.select(); }, 10);
1129
+ }
1130
+
1131
+ // ── Hit-test ──
1132
+ function shapeAt(x, y) {
1133
+ var diag = getCurrentDiagram();
1134
+ if (!diag) return null;
1135
+ for (var i = diag.shapes.length - 1; i >= 0; i--) {
1136
+ var s = diag.shapes[i];
1137
+ if (x >= s.x && x <= s.x + s.w && y >= s.y && y <= s.y + s.h) return s;
1138
+ }
1139
+ return null;
1140
+ }
1141
+
1142
+ function arrowIdAt(x, y) {
1143
+ var groups = document.querySelectorAll(".arrow-group");
1144
+ for (var i = 0; i < groups.length; i++) {
1145
+ var lines = groups[i].querySelectorAll("line");
1146
+ if (lines.length < 2) continue;
1147
+ var hit = lines[lines.length - 1];
1148
+ var x1 = +hit.getAttribute("x1"), y1 = +hit.getAttribute("y1");
1149
+ var x2 = +hit.getAttribute("x2"), y2 = +hit.getAttribute("y2");
1150
+ if (distToSeg(x, y, x1, y1, x2, y2) < 8) {
1151
+ return groups[i].getAttribute("data-id");
1152
+ }
1153
+ }
1154
+ return null;
1155
+ }
1156
+
1157
+ function distToSeg(px, py, x1, y1, x2, y2) {
1158
+ var dx = x2 - x1, dy = y2 - y1;
1159
+ var lenSq = dx * dx + dy * dy;
1160
+ if (lenSq === 0) return Math.hypot(px - x1, py - y1);
1161
+ var t = Math.max(0, Math.min(1, ((px - x1) * dx + (py - y1) * dy) / lenSq));
1162
+ return Math.hypot(px - (x1 + t * dx), py - (y1 + t * dy));
1163
+ }
1164
+
1165
+ // ── Créer une flèche ──
1166
+ function createArrow(fromId, toId) {
1167
+ var diag = getCurrentDiagram();
1168
+ if (diag.arrows.some(function (a) { return a.from === fromId && a.to === toId; })) return;
1169
+ var arrow = { id: "a" + Date.now(), from: fromId, to: toId, label: "" };
1170
+ diag.arrows.push(arrow);
1171
+ saveDiagrammes();
1172
+ selectedId = arrow.id; selectedType = "arrow";
1173
+ selectedIds = [];
1174
+ renderAll();
1175
+ startArrowTextEdit(arrow.id);
1176
+ }
1177
+
1178
+ // ── Renommer le diagramme ──
1179
+ function onTitleChange() {
1180
+ if (pendingNewDiagram) return;
1181
+ var diag = getCurrentDiagram();
1182
+ if (!diag) return;
1183
+ diag.titre = document.getElementById("diagramTitle").value || "Diagramme";
1184
+ saveDiagrammes();
1185
+ renderDiagramList();
1186
+ }
1187
+
1188
+ // ── Événements souris ──
1189
+ function onMouseDown(e) {
1190
+ if (e.button !== 0) return;
1191
+ if (editingShapeId || editingArrowId) { confirmTextEdit(); return; }
1192
+
1193
+ // Mode verrouillé : pan uniquement
1194
+ if (boardLocked) {
1195
+ panStart = { cx: e.clientX, cy: e.clientY, px: viewTransform.x, py: viewTransform.y };
1196
+ return;
1197
+ }
1198
+
1199
+ var pt = svgPoint(e.clientX, e.clientY);
1200
+
1201
+ // Mode pick (copie de style)
1202
+ if (pickMode) {
1203
+ var srcShape = shapeAt(pt.x, pt.y);
1204
+ if (srcShape) applyPickMode(srcShape);
1205
+ cancelPickMode();
1206
+ return;
1207
+ }
1208
+
1209
+ // Clic sur un point de connexion → début de flèche
1210
+ var connDot = e.target.closest(".conn-dot");
1211
+ if (connDot) {
1212
+ arrowSrcId = connDot.getAttribute("data-shape-id");
1213
+ var srcShape = getCurrentDiagram().shapes.find(function (s) { return s.id === arrowSrcId; });
1214
+ var ta = document.getElementById("tempArrow");
1215
+ ta.setAttribute("x1", srcShape.x + srcShape.w / 2);
1216
+ ta.setAttribute("y1", srcShape.y + srcShape.h / 2);
1217
+ ta.setAttribute("x2", pt.x); ta.setAttribute("y2", pt.y);
1218
+ ta.style.display = "";
1219
+ return;
1220
+ }
1221
+
1222
+ // Clic sur la poignée de resize
1223
+ var grip = e.target.closest(".resize-grip");
1224
+ if (grip) {
1225
+ var sid = grip.getAttribute("data-shape-id");
1226
+ var shape = getCurrentDiagram().shapes.find(function (s) { return s.id === sid; });
1227
+ if (shape) {
1228
+ dragState = { type: "resize", id: sid, sx: pt.x, sy: pt.y, ow: shape.w, oh: shape.h };
1229
+ }
1230
+ return;
1231
+ }
1232
+
1233
+ var shape = shapeAt(pt.x, pt.y);
1234
+
1235
+ if (currentTool === "select") {
1236
+ if (shape) {
1237
+ // Détection du double-clic par horodatage (avant tout renderAll)
1238
+ var now = Date.now();
1239
+ if (now - lastClickTime < 350 && lastClickShapeId === shape.id) {
1240
+ lastClickTime = 0; lastClickShapeId = null;
1241
+ startTextEdit(shape.id);
1242
+ return;
1243
+ }
1244
+ lastClickTime = now; lastClickShapeId = shape.id; lastClickArrowId = null;
1245
+
1246
+ if (e.shiftKey) {
1247
+ // Shift+clic : ajouter/retirer de la multi-sélection
1248
+ var idx = selectedIds.indexOf(shape.id);
1249
+ if (idx === -1) {
1250
+ selectedIds.push(shape.id);
1251
+ selectedId = shape.id; selectedType = "shape";
1252
+ } else {
1253
+ selectedIds.splice(idx, 1);
1254
+ selectedId = selectedIds.length > 0 ? selectedIds[selectedIds.length - 1] : null;
1255
+ selectedType = selectedId ? "shape" : null;
1256
+ }
1257
+ document.getElementById("colorPanel").style.display = selectedIds.length > 0 ? "flex" : "none";
1258
+ renderAll();
1259
+ } else if (selectedIds.indexOf(shape.id) !== -1 && selectedIds.length > 1) {
1260
+ // Clic sur une forme déjà dans la multi-sélection → multi-déplacement
1261
+ var diag = getCurrentDiagram();
1262
+ dragState = {
1263
+ type: "multi-move",
1264
+ sx: pt.x, sy: pt.y,
1265
+ origPositions: selectedIds.map(function (id) {
1266
+ var s = diag.shapes.find(function (sh) { return sh.id === id; });
1267
+ return { id: id, ox: s ? s.x : 0, oy: s ? s.y : 0 };
1268
+ }),
1269
+ };
1270
+ } else {
1271
+ // Clic simple → sélection unique
1272
+ selectedIds = [shape.id];
1273
+ selectedId = shape.id; selectedType = "shape";
1274
+ dragState = { type: "move", id: shape.id, sx: pt.x, sy: pt.y, ox: shape.x, oy: shape.y };
1275
+ document.getElementById("colorPanel").style.display = "flex";
1276
+ renderAll();
1277
+ }
1278
+ } else {
1279
+ var aid = arrowIdAt(pt.x, pt.y);
1280
+ if (aid) {
1281
+ // Détection du double-clic sur une flèche
1282
+ var now2 = Date.now();
1283
+ if (now2 - lastClickTime < 350 && lastClickArrowId === aid) {
1284
+ lastClickTime = 0; lastClickArrowId = null;
1285
+ selectedId = aid; selectedType = "arrow";
1286
+ selectedIds = [];
1287
+ renderAll();
1288
+ startArrowTextEdit(aid);
1289
+ return;
1290
+ }
1291
+ lastClickTime = now2; lastClickArrowId = aid;
1292
+
1293
+ selectedId = aid; selectedType = "arrow";
1294
+ selectedIds = [];
1295
+ document.getElementById("colorPanel").style.display = "none";
1296
+ renderAll();
1297
+ } else if (e.shiftKey) {
1298
+ // Shift+drag sur fond vide → lasso de sélection
1299
+ rubberBandState = { sx: pt.x, sy: pt.y };
1300
+ } else {
1301
+ // Pan
1302
+ panStart = { cx: e.clientX, cy: e.clientY, px: viewTransform.x, py: viewTransform.y };
1303
+ selectedId = null; selectedType = null;
1304
+ selectedIds = [];
1305
+ document.getElementById("colorPanel").style.display = "none";
1306
+ renderAll();
1307
+ }
1308
+ }
1309
+
1310
+ } else if (currentTool === "arrow") {
1311
+ if (shape && shape.type !== "postit") {
1312
+ if (!arrowSrcId) {
1313
+ arrowSrcId = shape.id;
1314
+ var ta = document.getElementById("tempArrow");
1315
+ ta.setAttribute("x1", shape.x + shape.w / 2);
1316
+ ta.setAttribute("y1", shape.y + shape.h / 2);
1317
+ ta.setAttribute("x2", pt.x); ta.setAttribute("y2", pt.y);
1318
+ ta.style.display = "";
1319
+ } else if (arrowSrcId !== shape.id) {
1320
+ createArrow(arrowSrcId, shape.id);
1321
+ arrowSrcId = null;
1322
+ document.getElementById("tempArrow").style.display = "none";
1323
+ }
1324
+ } else {
1325
+ arrowSrcId = null;
1326
+ document.getElementById("tempArrow").style.display = "none";
1327
+ }
1328
+
1329
+ } else {
1330
+ // Outils forme : placer au clic
1331
+ addShape(currentTool, pt.x, pt.y);
1332
+ setTool("select");
1333
+ }
1334
+ }
1335
+
1336
+ function onMouseMove(e) {
1337
+ var pt = svgPoint(e.clientX, e.clientY);
1338
+
1339
+ // Mise à jour de la flèche temporaire
1340
+ if (arrowSrcId) {
1341
+ var ta = document.getElementById("tempArrow");
1342
+ if (ta.style.display !== "none") {
1343
+ ta.setAttribute("x2", pt.x); ta.setAttribute("y2", pt.y);
1344
+ }
1345
+ }
1346
+
1347
+ if (rubberBandState) {
1348
+ var rb = document.getElementById("rubberBand");
1349
+ var rbX = Math.min(rubberBandState.sx, pt.x);
1350
+ var rbY = Math.min(rubberBandState.sy, pt.y);
1351
+ rb.setAttribute("x", rbX); rb.setAttribute("y", rbY);
1352
+ rb.setAttribute("width", Math.abs(pt.x - rubberBandState.sx));
1353
+ rb.setAttribute("height", Math.abs(pt.y - rubberBandState.sy));
1354
+ rb.style.display = "";
1355
+ } else if (dragState) {
1356
+ var diag = getCurrentDiagram();
1357
+ if (dragState.type === "multi-move") {
1358
+ var dx = pt.x - dragState.sx;
1359
+ var dy = pt.y - dragState.sy;
1360
+ dragState.origPositions.forEach(function (op) {
1361
+ var s = diag.shapes.find(function (sh) { return sh.id === op.id; });
1362
+ if (s) { s.x = Math.round(op.ox + dx); s.y = Math.round(op.oy + dy); }
1363
+ });
1364
+ renderAll();
1365
+ } else {
1366
+ var shape = diag.shapes.find(function (s) { return s.id === dragState.id; });
1367
+ if (!shape) return;
1368
+ if (dragState.type === "move") {
1369
+ shape.x = Math.round(dragState.ox + pt.x - dragState.sx);
1370
+ shape.y = Math.round(dragState.oy + pt.y - dragState.sy);
1371
+ } else if (dragState.type === "resize") {
1372
+ shape.w = Math.max(60, Math.round(dragState.ow + pt.x - dragState.sx));
1373
+ shape.h = Math.max(30, Math.round(dragState.oh + pt.y - dragState.sy));
1374
+ }
1375
+ renderAll();
1376
+ }
1377
+ } else if (panStart) {
1378
+ viewTransform.x = panStart.px + (e.clientX - panStart.cx);
1379
+ viewTransform.y = panStart.py + (e.clientY - panStart.cy);
1380
+ updateViewport();
1381
+ }
1382
+ }
1383
+
1384
+ function onMouseUp(e) {
1385
+ var pt = svgPoint(e.clientX, e.clientY);
1386
+
1387
+ // Fin de tracé de flèche via conn-dot
1388
+ if (arrowSrcId && !dragState) {
1389
+ document.getElementById("tempArrow").style.display = "none";
1390
+ if (currentTool !== "arrow") {
1391
+ var target = shapeAt(pt.x, pt.y);
1392
+ if (target && target.id !== arrowSrcId && target.type !== "postit") {
1393
+ createArrow(arrowSrcId, target.id);
1394
+ }
1395
+ arrowSrcId = null;
1396
+ }
1397
+ }
1398
+
1399
+ if (rubberBandState) {
1400
+ document.getElementById("rubberBand").style.display = "none";
1401
+ var minX = Math.min(rubberBandState.sx, pt.x);
1402
+ var minY = Math.min(rubberBandState.sy, pt.y);
1403
+ var maxX = Math.max(rubberBandState.sx, pt.x);
1404
+ var maxY = Math.max(rubberBandState.sy, pt.y);
1405
+ if (maxX - minX > 4 || maxY - minY > 4) {
1406
+ var diag = getCurrentDiagram();
1407
+ selectedIds = (diag.shapes || []).filter(function (s) {
1408
+ return s.x < maxX && s.x + s.w > minX && s.y < maxY && s.y + s.h > minY;
1409
+ }).map(function (s) { return s.id; });
1410
+ selectedId = selectedIds.length > 0 ? selectedIds[0] : null;
1411
+ selectedType = selectedIds.length > 0 ? "shape" : null;
1412
+ document.getElementById("colorPanel").style.display = selectedIds.length > 0 ? "flex" : "none";
1413
+ }
1414
+ rubberBandState = null;
1415
+ renderAll();
1416
+ }
1417
+
1418
+ if (dragState) {
1419
+ saveDiagrammes();
1420
+ dragState = null;
1421
+ renderAll();
1422
+ }
1423
+ panStart = null;
1424
+ }
1425
+
1426
+
1427
+ function onWheel(e) {
1428
+ e.preventDefault();
1429
+ var svg = document.getElementById("canvas");
1430
+ var r = svg.getBoundingClientRect();
1431
+ applyZoom(e.deltaY < 0 ? 1.12 : 1 / 1.12, e.clientX - r.left, e.clientY - r.top);
1432
+ }
1433
+
1434
+ // ── Coller des formes ──
1435
+ function pasteShapes() {
1436
+ if (clipboard.length === 0) return;
1437
+ var diag = getCurrentDiagram();
1438
+ var now = Date.now();
1439
+ var pasted = clipboard.map(function (s, i) {
1440
+ return Object.assign({}, JSON.parse(JSON.stringify(s)), {
1441
+ id: "s" + (now + i),
1442
+ x: s.x + 20,
1443
+ y: s.y + 20,
1444
+ });
1445
+ });
1446
+ pasted.forEach(function (s) { diag.shapes.push(s); });
1447
+ selectedIds = pasted.map(function (s) { return s.id; });
1448
+ selectedId = selectedIds[0]; selectedType = "shape";
1449
+ clipboard = pasted.map(function (s) { return JSON.parse(JSON.stringify(s)); });
1450
+ saveDiagrammes();
1451
+ renderAll();
1452
+ document.getElementById("colorPanel").style.display = "flex";
1453
+ }
1454
+
1455
+ // ── Init ──
1456
+ document.addEventListener("DOMContentLoaded", function () {
1457
+ diagramsList = loadDiagrammes();
1458
+ if (!localStorage.getItem("mes_diagrammes")) saveDiagrammes();
1459
+ var savedIdx = parseInt(localStorage.getItem("current_diagram_idx"), 10);
1460
+ if (!isNaN(savedIdx) && savedIdx >= 0 && savedIdx < diagramsList.length) {
1461
+ currentDiagramIdx = savedIdx;
1462
+ }
1463
+ restoreLockForDiagram(currentDiagramIdx);
1464
+ checkDiffDiagrammes();
1465
+ renderAll();
1466
+ updateLockBtn();
1467
+
1468
+ var canvas = document.getElementById("canvas");
1469
+ canvas.addEventListener("mousedown", onMouseDown);
1470
+ canvas.addEventListener("mousemove", onMouseMove);
1471
+ canvas.addEventListener("mouseup", onMouseUp);
1472
+ canvas.addEventListener("wheel", onWheel, { passive: false });
1473
+
1474
+ var titleInput = document.getElementById("diagramTitle");
1475
+ titleInput.addEventListener("change", onTitleChange);
1476
+ titleInput.addEventListener("blur", onTitleChange);
1477
+ titleInput.addEventListener("keydown", function (e) {
1478
+ if (e.key === "Enter") {
1479
+ e.preventDefault();
1480
+ if (pendingNewDiagram) confirmerNouveauDiagramme();
1481
+ else titleInput.blur();
1482
+ }
1483
+ if (e.key === "Escape" && pendingNewDiagram) annulerNouveauDiagramme();
1484
+ });
1485
+
1486
+ var textInput = document.getElementById("shapeTextInput");
1487
+ textInput.addEventListener("keydown", function (e) {
1488
+ if (e.key === "Enter") { e.preventDefault(); confirmTextEdit(); }
1489
+ if (e.key === "Escape") {
1490
+ document.getElementById("textOverlay").style.display = "none";
1491
+ editingShapeId = null;
1492
+ }
1493
+ });
1494
+ textInput.addEventListener("blur", function () {
1495
+ // Small delay to allow click on modal-confirm etc.
1496
+ setTimeout(confirmTextEdit, 100);
1497
+ });
1498
+
1499
+ var postitInput = document.getElementById("postitTextInput");
1500
+ postitInput.addEventListener("keydown", function (e) {
1501
+ if (e.key === "Escape") {
1502
+ document.getElementById("textOverlay").style.display = "none";
1503
+ postitInput.style.display = "none";
1504
+ document.getElementById("shapeTextInput").style.display = "block";
1505
+ editingShapeId = null;
1506
+ renderAll(); // restaure la visibilité du texte SVG
1507
+ }
1508
+ // Enter ajoute un saut de ligne (comportement natif textarea — pas de preventDefault)
1509
+ });
1510
+ postitInput.addEventListener("blur", function () {
1511
+ setTimeout(confirmTextEdit, 100);
1512
+ });
1513
+
1514
+ document.addEventListener("keydown", function (e) {
1515
+ if (document.activeElement.tagName === "INPUT" || document.activeElement.tagName === "TEXTAREA") return;
1516
+ if (boardLocked) return;
1517
+ if (e.key === "Delete" || e.key === "Backspace") deleteSelected();
1518
+ if (e.key === "Escape") {
1519
+ arrowSrcId = null;
1520
+ document.getElementById("tempArrow").style.display = "none";
1521
+ setTool("select");
1522
+ }
1523
+ if ((e.ctrlKey || e.metaKey) && e.key === "c") {
1524
+ e.preventDefault();
1525
+ if (selectedIds.length === 0) return;
1526
+ var diag = getCurrentDiagram();
1527
+ clipboard = selectedIds.map(function (id) {
1528
+ var s = diag.shapes.find(function (sh) { return sh.id === id; });
1529
+ return s ? JSON.parse(JSON.stringify(s)) : null;
1530
+ }).filter(Boolean);
1531
+ }
1532
+ if ((e.ctrlKey || e.metaKey) && e.key === "v") {
1533
+ e.preventDefault();
1534
+ if (clipboard.length > 0) {
1535
+ pasteShapes();
1536
+ return;
1537
+ }
1538
+ if (!navigator.clipboard || !navigator.clipboard.read) return;
1539
+ navigator.clipboard.read().then(function (clipItems) {
1540
+ for (var i = 0; i < clipItems.length; i++) {
1541
+ for (var j = 0; j < clipItems[i].types.length; j++) {
1542
+ if (clipItems[i].types[j].indexOf("image") !== -1) {
1543
+ clipItems[i].getType(clipItems[i].types[j]).then(function (blob) {
1544
+ handleImagePaste(blob);
1545
+ });
1546
+ return;
1547
+ }
1548
+ }
1549
+ }
1550
+ }).catch(function () {});
1551
+ }
1552
+ });
1553
+
1554
+ // Quand la fenêtre reprend le focus, l'utilisateur revient d'une autre app
1555
+ // où il a peut-être copié une image → vider le clipboard interne
1556
+ window.addEventListener("focus", function () {
1557
+ clipboard = [];
1558
+ });
1559
+
1560
+ document.getElementById("modalPremiereSauvegardeDiag").addEventListener("click", function (e) {
1561
+ if (e.target === this) this.classList.remove("open");
1562
+ });
1563
+
1564
+ document.addEventListener("mousedown", function (e) {
1565
+ var panel = document.getElementById("diagramListPanel");
1566
+ if (!panel.classList.contains("open")) return;
1567
+ var burgerBtn = document.querySelector(".diagram-tool[onclick=\"toggleDiagramList()\"]");
1568
+ if (!panel.contains(e.target) && (!burgerBtn || !burgerBtn.contains(e.target))) {
1569
+ panel.classList.remove("open");
1570
+ }
1571
+ });
1572
+ });