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/LICENSE +21 -0
- package/README.md +248 -0
- package/admin.html +329 -0
- package/bin/cli.js +77 -0
- package/diagram.html +234 -0
- package/diagram.js +1572 -0
- package/diagrammes.js +231 -0
- package/i18n/en.js +186 -0
- package/i18n/fr.js +187 -0
- package/i18n/i18n.js +27 -0
- package/images/diagram-overview.png +0 -0
- package/images/img_1774270331723.png +0 -0
- package/index.html +340 -0
- package/liens.js +391 -0
- package/mesLiens.js +66 -0
- package/mesNotes.js +90 -0
- package/notes.js +561 -0
- package/package.json +42 -0
- package/style.css +1460 -0
- package/taches.js +96 -0
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, "<")
|
|
55
|
+
.replace(/>/g, ">")
|
|
56
|
+
.replace(/"/g, """);
|
|
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
|
+
});
|