doc-survival-kit 2.0.0 → 3.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of doc-survival-kit might be problematic. Click here for more details.
- package/diagram/diagrams.js +240 -0
- package/diagram/events.js +603 -0
- package/diagram/globals.js +137 -0
- package/diagram/links.js +141 -0
- package/diagram/persistence.js +295 -0
- package/diagram/render.js +633 -0
- package/diagram/shape-ops.js +236 -0
- package/diagram/text-edit.js +292 -0
- package/diagram.html +8 -1
- package/package.json +9 -9
- package/diagram.js +0 -2556
|
@@ -0,0 +1,603 @@
|
|
|
1
|
+
// ══════════════════════════════════════
|
|
2
|
+
// events.js — Événements souris/clavier, hit-tests, création de flèches et initialisation
|
|
3
|
+
// ══════════════════════════════════════
|
|
4
|
+
|
|
5
|
+
// ── Hit-test ──
|
|
6
|
+
function shapeAt(x, y) {
|
|
7
|
+
var diag = getCurrentDiagram();
|
|
8
|
+
if (!diag) return null;
|
|
9
|
+
for (var i = diag.shapes.length - 1; i >= 0; i--) {
|
|
10
|
+
var s = diag.shapes[i];
|
|
11
|
+
var lx = x, ly = y;
|
|
12
|
+
if (s.rotation) {
|
|
13
|
+
var scx = s.x + s.w / 2, scy = s.y + s.h / 2;
|
|
14
|
+
var rad = -s.rotation * Math.PI / 180;
|
|
15
|
+
var cos = Math.cos(rad), sin = Math.sin(rad);
|
|
16
|
+
var ddx = x - scx, ddy = y - scy;
|
|
17
|
+
lx = scx + ddx * cos - ddy * sin;
|
|
18
|
+
ly = scy + ddx * sin + ddy * cos;
|
|
19
|
+
}
|
|
20
|
+
if (lx >= s.x && lx <= s.x + s.w && ly >= s.y && ly <= s.y + s.h) return s;
|
|
21
|
+
}
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function arrowIdAt(x, y) {
|
|
26
|
+
var groups = document.querySelectorAll(".arrow-group");
|
|
27
|
+
for (var i = 0; i < groups.length; i++) {
|
|
28
|
+
var lines = groups[i].querySelectorAll("line");
|
|
29
|
+
if (lines.length < 2) continue;
|
|
30
|
+
var hit = lines[lines.length - 1];
|
|
31
|
+
var x1 = +hit.getAttribute("x1"), y1 = +hit.getAttribute("y1");
|
|
32
|
+
var x2 = +hit.getAttribute("x2"), y2 = +hit.getAttribute("y2");
|
|
33
|
+
if (distToSeg(x, y, x1, y1, x2, y2) < 8) {
|
|
34
|
+
return groups[i].getAttribute("data-id");
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function distToSeg(px, py, x1, y1, x2, y2) {
|
|
41
|
+
var dx = x2 - x1, dy = y2 - y1;
|
|
42
|
+
var lenSq = dx * dx + dy * dy;
|
|
43
|
+
if (lenSq === 0) return Math.hypot(px - x1, py - y1);
|
|
44
|
+
var t = Math.max(0, Math.min(1, ((px - x1) * dx + (py - y1) * dy) / lenSq));
|
|
45
|
+
return Math.hypot(px - (x1 + t * dx), py - (y1 + t * dy));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// ── Créer une flèche ──
|
|
49
|
+
function createArrow(fromId, toId) {
|
|
50
|
+
var diag = getCurrentDiagram();
|
|
51
|
+
if (diag.arrows.some(function (a) { return a.from === fromId && a.to === toId; })) return;
|
|
52
|
+
pushHistory();
|
|
53
|
+
var arrow = { id: "a" + Date.now(), from: fromId, to: toId, label: "" };
|
|
54
|
+
diag.arrows.push(arrow);
|
|
55
|
+
saveDiagrammes();
|
|
56
|
+
selectedId = arrow.id; selectedType = "arrow";
|
|
57
|
+
selectedIds = [];
|
|
58
|
+
renderAll();
|
|
59
|
+
startArrowTextEdit(arrow.id);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// ── Événements souris ──
|
|
63
|
+
function onMouseDown(e) {
|
|
64
|
+
if (e.button !== 0) return;
|
|
65
|
+
if (editingShapeId || editingArrowId) { confirmTextEdit(); return; }
|
|
66
|
+
|
|
67
|
+
// Mode verrouillé : pan uniquement
|
|
68
|
+
if (boardLocked) {
|
|
69
|
+
panStart = { cx: e.clientX, cy: e.clientY, px: viewTransform.x, py: viewTransform.y };
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
var pt = svgPoint(e.clientX, e.clientY);
|
|
74
|
+
|
|
75
|
+
// Mode pick (copie de style)
|
|
76
|
+
if (pickMode) {
|
|
77
|
+
var srcShape = shapeAt(pt.x, pt.y);
|
|
78
|
+
if (srcShape) applyPickMode(srcShape);
|
|
79
|
+
cancelPickMode();
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Clic sur un point de connexion → début de flèche
|
|
84
|
+
var connDot = e.target.closest(".conn-dot");
|
|
85
|
+
if (connDot) {
|
|
86
|
+
arrowSrcId = connDot.getAttribute("data-shape-id");
|
|
87
|
+
var srcShape = getCurrentDiagram().shapes.find(function (s) { return s.id === arrowSrcId; });
|
|
88
|
+
var ta = document.getElementById("tempArrow");
|
|
89
|
+
ta.setAttribute("x1", srcShape.x + srcShape.w / 2);
|
|
90
|
+
ta.setAttribute("y1", srcShape.y + srcShape.h / 2);
|
|
91
|
+
ta.setAttribute("x2", pt.x); ta.setAttribute("y2", pt.y);
|
|
92
|
+
ta.style.display = "";
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Clic sur un chevron de redimensionnement de colonne
|
|
97
|
+
var colSepEl = e.target.closest("[data-col-sep]");
|
|
98
|
+
if (colSepEl) {
|
|
99
|
+
var csColIdx = parseInt(colSepEl.getAttribute("data-col-sep"), 10);
|
|
100
|
+
var csSid = colSepEl.getAttribute("data-shape-id");
|
|
101
|
+
var csShape = getCurrentDiagram().shapes.find(function (s) { return s.id === csSid; });
|
|
102
|
+
if (csShape) {
|
|
103
|
+
var csCw = getColWidths(csShape);
|
|
104
|
+
dragState = { type: "col-resize", id: csSid, colIdx: csColIdx, sx: pt.x, origWidths: csCw.slice(), snapshot: JSON.stringify(diagramsList) };
|
|
105
|
+
}
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Clic sur la poignée de resize
|
|
110
|
+
var grip = e.target.closest(".resize-grip");
|
|
111
|
+
if (grip) {
|
|
112
|
+
var sid = grip.getAttribute("data-shape-id");
|
|
113
|
+
var shape = getCurrentDiagram().shapes.find(function (s) { return s.id === sid; });
|
|
114
|
+
if (shape) {
|
|
115
|
+
dragState = { type: "resize", id: sid, sx: pt.x, sy: pt.y, ow: shape.w, oh: shape.h, snapshot: JSON.stringify(diagramsList) };
|
|
116
|
+
}
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
var shape = shapeAt(pt.x, pt.y);
|
|
121
|
+
|
|
122
|
+
if (currentTool === "select") {
|
|
123
|
+
if (shape) {
|
|
124
|
+
// Détection du double-clic par horodatage (avant tout renderAll)
|
|
125
|
+
var now = Date.now();
|
|
126
|
+
if (now - lastClickTime < 350 && lastClickShapeId === shape.id) {
|
|
127
|
+
lastClickTime = 0; lastClickShapeId = null;
|
|
128
|
+
if (shape.type === "table") {
|
|
129
|
+
var tDblRows = shape.rows || 3;
|
|
130
|
+
var tDblOffsets = getColOffsets(shape);
|
|
131
|
+
var tDblCw = getColWidths(shape);
|
|
132
|
+
var tDblRx = pt.x - shape.x;
|
|
133
|
+
var tDblC = tDblCw.length - 1;
|
|
134
|
+
for (var tci3 = 0; tci3 < tDblCw.length; tci3++) {
|
|
135
|
+
if (tDblRx < tDblOffsets[tci3] + tDblCw[tci3]) { tDblC = tci3; break; }
|
|
136
|
+
}
|
|
137
|
+
var tDblR = Math.min(tDblRows - 1, Math.max(0, Math.floor((pt.y - shape.y) / (shape.h / tDblRows))));
|
|
138
|
+
startTableCellEdit(shape.id, tDblR, tDblC);
|
|
139
|
+
} else {
|
|
140
|
+
startTextEdit(shape.id);
|
|
141
|
+
}
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
lastClickTime = now; lastClickShapeId = shape.id; lastClickArrowId = null;
|
|
145
|
+
|
|
146
|
+
if (e.shiftKey) {
|
|
147
|
+
// Shift+clic : ajouter/retirer de la multi-sélection
|
|
148
|
+
var idx = selectedIds.indexOf(shape.id);
|
|
149
|
+
if (idx === -1) {
|
|
150
|
+
selectedIds.push(shape.id);
|
|
151
|
+
selectedId = shape.id; selectedType = "shape";
|
|
152
|
+
} else {
|
|
153
|
+
selectedIds.splice(idx, 1);
|
|
154
|
+
selectedId = selectedIds.length > 0 ? selectedIds[selectedIds.length - 1] : null;
|
|
155
|
+
selectedType = selectedId ? "shape" : null;
|
|
156
|
+
}
|
|
157
|
+
document.getElementById("colorPanel").style.display = selectedIds.length > 0 ? "flex" : "none";
|
|
158
|
+
renderAll();
|
|
159
|
+
} else if (selectedIds.indexOf(shape.id) !== -1 && selectedIds.length > 1) {
|
|
160
|
+
// Clic sur une forme déjà dans la multi-sélection → multi-déplacement
|
|
161
|
+
var diag = getCurrentDiagram();
|
|
162
|
+
dragState = {
|
|
163
|
+
type: "multi-move",
|
|
164
|
+
sx: pt.x, sy: pt.y,
|
|
165
|
+
origPositions: selectedIds.map(function (id) {
|
|
166
|
+
var s = diag.shapes.find(function (sh) { return sh.id === id; });
|
|
167
|
+
return { id: id, ox: s ? s.x : 0, oy: s ? s.y : 0 };
|
|
168
|
+
}),
|
|
169
|
+
snapshot: JSON.stringify(diagramsList),
|
|
170
|
+
};
|
|
171
|
+
} else {
|
|
172
|
+
// Clic simple → sélection unique
|
|
173
|
+
selectedIds = [shape.id];
|
|
174
|
+
selectedId = shape.id; selectedType = "shape";
|
|
175
|
+
dragState = { type: "move", id: shape.id, sx: pt.x, sy: pt.y, ox: shape.x, oy: shape.y, snapshot: JSON.stringify(diagramsList) };
|
|
176
|
+
document.getElementById("colorPanel").style.display = "flex";
|
|
177
|
+
renderAll();
|
|
178
|
+
}
|
|
179
|
+
} else {
|
|
180
|
+
var aid = arrowIdAt(pt.x, pt.y);
|
|
181
|
+
if (aid) {
|
|
182
|
+
// Détection du double-clic sur une flèche
|
|
183
|
+
var now2 = Date.now();
|
|
184
|
+
if (now2 - lastClickTime < 350 && lastClickArrowId === aid) {
|
|
185
|
+
lastClickTime = 0; lastClickArrowId = null;
|
|
186
|
+
selectedId = aid; selectedType = "arrow";
|
|
187
|
+
selectedIds = [];
|
|
188
|
+
renderAll();
|
|
189
|
+
startArrowTextEdit(aid);
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
lastClickTime = now2; lastClickArrowId = aid;
|
|
193
|
+
|
|
194
|
+
selectedId = aid; selectedType = "arrow";
|
|
195
|
+
selectedIds = [];
|
|
196
|
+
document.getElementById("colorPanel").style.display = "none";
|
|
197
|
+
renderAll();
|
|
198
|
+
} else if (e.shiftKey) {
|
|
199
|
+
// Shift+drag sur fond vide → lasso de sélection
|
|
200
|
+
rubberBandState = { sx: pt.x, sy: pt.y };
|
|
201
|
+
} else {
|
|
202
|
+
// Pan
|
|
203
|
+
panStart = { cx: e.clientX, cy: e.clientY, px: viewTransform.x, py: viewTransform.y };
|
|
204
|
+
selectedId = null; selectedType = null;
|
|
205
|
+
selectedIds = [];
|
|
206
|
+
document.getElementById("colorPanel").style.display = "none";
|
|
207
|
+
renderAll();
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
} else if (currentTool === "arrow") {
|
|
212
|
+
if (shape && shape.type !== "postit") {
|
|
213
|
+
if (!arrowSrcId) {
|
|
214
|
+
arrowSrcId = shape.id;
|
|
215
|
+
var ta = document.getElementById("tempArrow");
|
|
216
|
+
ta.setAttribute("x1", shape.x + shape.w / 2);
|
|
217
|
+
ta.setAttribute("y1", shape.y + shape.h / 2);
|
|
218
|
+
ta.setAttribute("x2", pt.x); ta.setAttribute("y2", pt.y);
|
|
219
|
+
ta.style.display = "";
|
|
220
|
+
} else if (arrowSrcId !== shape.id) {
|
|
221
|
+
createArrow(arrowSrcId, shape.id);
|
|
222
|
+
arrowSrcId = null;
|
|
223
|
+
document.getElementById("tempArrow").style.display = "none";
|
|
224
|
+
}
|
|
225
|
+
} else {
|
|
226
|
+
arrowSrcId = null;
|
|
227
|
+
document.getElementById("tempArrow").style.display = "none";
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
} else {
|
|
231
|
+
// Outils forme : placer au clic
|
|
232
|
+
addShape(currentTool, pt.x, pt.y);
|
|
233
|
+
setTool("select");
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function onMouseMove(e) {
|
|
238
|
+
var pt = svgPoint(e.clientX, e.clientY);
|
|
239
|
+
|
|
240
|
+
// Mise à jour de la flèche temporaire
|
|
241
|
+
if (arrowSrcId) {
|
|
242
|
+
var ta = document.getElementById("tempArrow");
|
|
243
|
+
if (ta.style.display !== "none") {
|
|
244
|
+
ta.setAttribute("x2", pt.x); ta.setAttribute("y2", pt.y);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (rubberBandState) {
|
|
249
|
+
var rb = document.getElementById("rubberBand");
|
|
250
|
+
var rbX = Math.min(rubberBandState.sx, pt.x);
|
|
251
|
+
var rbY = Math.min(rubberBandState.sy, pt.y);
|
|
252
|
+
rb.setAttribute("x", rbX); rb.setAttribute("y", rbY);
|
|
253
|
+
rb.setAttribute("width", Math.abs(pt.x - rubberBandState.sx));
|
|
254
|
+
rb.setAttribute("height", Math.abs(pt.y - rubberBandState.sy));
|
|
255
|
+
rb.style.display = "";
|
|
256
|
+
} else if (dragState) {
|
|
257
|
+
dragState.moved = true;
|
|
258
|
+
var diag = getCurrentDiagram();
|
|
259
|
+
if (dragState.type === "multi-move") {
|
|
260
|
+
var dx = pt.x - dragState.sx;
|
|
261
|
+
var dy = pt.y - dragState.sy;
|
|
262
|
+
dragState.origPositions.forEach(function (op) {
|
|
263
|
+
var s = diag.shapes.find(function (sh) { return sh.id === op.id; });
|
|
264
|
+
if (s) { s.x = Math.round(op.ox + dx); s.y = Math.round(op.oy + dy); }
|
|
265
|
+
});
|
|
266
|
+
renderAll();
|
|
267
|
+
} else {
|
|
268
|
+
var shape = diag.shapes.find(function (s) { return s.id === dragState.id; });
|
|
269
|
+
if (!shape) return;
|
|
270
|
+
if (dragState.type === "move") {
|
|
271
|
+
shape.x = Math.round(dragState.ox + pt.x - dragState.sx);
|
|
272
|
+
shape.y = Math.round(dragState.oy + pt.y - dragState.sy);
|
|
273
|
+
} else if (dragState.type === "resize") {
|
|
274
|
+
var newW = Math.max(60, Math.round(dragState.ow + pt.x - dragState.sx));
|
|
275
|
+
var newH = Math.max(30, Math.round(dragState.oh + pt.y - dragState.sy));
|
|
276
|
+
if (shape.type === "table" && shape.colWidths && shape.colWidths.length === (shape.cols || 3)) {
|
|
277
|
+
var oldW2 = shape.w;
|
|
278
|
+
shape.colWidths = shape.colWidths.map(function (cw) { return cw * newW / oldW2; });
|
|
279
|
+
}
|
|
280
|
+
shape.w = newW;
|
|
281
|
+
shape.h = newH;
|
|
282
|
+
} else if (dragState.type === "col-resize") {
|
|
283
|
+
var crCw = dragState.origWidths.slice();
|
|
284
|
+
var crDx = pt.x - dragState.sx;
|
|
285
|
+
var crCi = dragState.colIdx;
|
|
286
|
+
var MIN_COL = 20;
|
|
287
|
+
var crLeft = Math.max(MIN_COL, crCw[crCi] + crDx);
|
|
288
|
+
var crRight = Math.max(MIN_COL, crCw[crCi + 1] - crDx);
|
|
289
|
+
var crTotal = crCw[crCi] + crCw[crCi + 1];
|
|
290
|
+
crLeft = Math.min(crTotal - MIN_COL, crLeft);
|
|
291
|
+
crRight = crTotal - crLeft;
|
|
292
|
+
crCw[crCi] = crLeft;
|
|
293
|
+
crCw[crCi + 1] = crRight;
|
|
294
|
+
shape.colWidths = crCw;
|
|
295
|
+
}
|
|
296
|
+
renderAll();
|
|
297
|
+
}
|
|
298
|
+
} else if (panStart) {
|
|
299
|
+
viewTransform.x = panStart.px + (e.clientX - panStart.cx);
|
|
300
|
+
viewTransform.y = panStart.py + (e.clientY - panStart.cy);
|
|
301
|
+
updateViewport();
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function onMouseUp(e) {
|
|
306
|
+
var pt = svgPoint(e.clientX, e.clientY);
|
|
307
|
+
|
|
308
|
+
// En mode verrouillé : détecter un clic (sans déplacement) sur une forme liée
|
|
309
|
+
if (boardLocked && panStart) {
|
|
310
|
+
var movedPx = Math.abs(e.clientX - panStart.cx) + Math.abs(e.clientY - panStart.cy);
|
|
311
|
+
if (movedPx < 5 && !e.shiftKey) {
|
|
312
|
+
var shape = shapeAt(pt.x, pt.y);
|
|
313
|
+
if (shape && shape.linkedDiagramId) {
|
|
314
|
+
var target = findDiagramById(shape.linkedDiagramId, diagramsList);
|
|
315
|
+
if (target) {
|
|
316
|
+
hideLinkPicker();
|
|
317
|
+
diagNavStack.push(currentDiagramId);
|
|
318
|
+
if (diagNavStack.length > 30) diagNavStack.shift();
|
|
319
|
+
panStart = null;
|
|
320
|
+
selectDiagramme(shape.linkedDiagramId);
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
} else if (shape && shape.externalUrl) {
|
|
324
|
+
panStart = null;
|
|
325
|
+
window.open(shape.externalUrl, "_blank");
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
panStart = null;
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// Fin de tracé de flèche via conn-dot
|
|
334
|
+
if (arrowSrcId && !dragState) {
|
|
335
|
+
document.getElementById("tempArrow").style.display = "none";
|
|
336
|
+
if (currentTool !== "arrow") {
|
|
337
|
+
var target = shapeAt(pt.x, pt.y);
|
|
338
|
+
if (target && target.id !== arrowSrcId && target.type !== "postit") {
|
|
339
|
+
createArrow(arrowSrcId, target.id);
|
|
340
|
+
}
|
|
341
|
+
arrowSrcId = null;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
if (rubberBandState) {
|
|
346
|
+
document.getElementById("rubberBand").style.display = "none";
|
|
347
|
+
var minX = Math.min(rubberBandState.sx, pt.x);
|
|
348
|
+
var minY = Math.min(rubberBandState.sy, pt.y);
|
|
349
|
+
var maxX = Math.max(rubberBandState.sx, pt.x);
|
|
350
|
+
var maxY = Math.max(rubberBandState.sy, pt.y);
|
|
351
|
+
if (maxX - minX > 4 || maxY - minY > 4) {
|
|
352
|
+
var diag = getCurrentDiagram();
|
|
353
|
+
selectedIds = (diag.shapes || []).filter(function (s) {
|
|
354
|
+
return s.x < maxX && s.x + s.w > minX && s.y < maxY && s.y + s.h > minY;
|
|
355
|
+
}).map(function (s) { return s.id; });
|
|
356
|
+
selectedId = selectedIds.length > 0 ? selectedIds[0] : null;
|
|
357
|
+
selectedType = selectedIds.length > 0 ? "shape" : null;
|
|
358
|
+
document.getElementById("colorPanel").style.display = selectedIds.length > 0 ? "flex" : "none";
|
|
359
|
+
}
|
|
360
|
+
rubberBandState = null;
|
|
361
|
+
renderAll();
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
if (dragState) {
|
|
365
|
+
var wasMoved = dragState.moved;
|
|
366
|
+
var draggedId = dragState.id;
|
|
367
|
+
var dragType = dragState.type;
|
|
368
|
+
if (dragState.moved && dragState.snapshot) {
|
|
369
|
+
historyStack.push(dragState.snapshot);
|
|
370
|
+
if (historyStack.length > MAX_HISTORY) historyStack.shift();
|
|
371
|
+
}
|
|
372
|
+
saveDiagrammes();
|
|
373
|
+
dragState = null;
|
|
374
|
+
renderAll();
|
|
375
|
+
// Naviguer vers le diagramme lié si clic sans déplacement
|
|
376
|
+
if (!wasMoved && dragType === "move" && draggedId && !e.shiftKey) {
|
|
377
|
+
var diag = getCurrentDiagram();
|
|
378
|
+
var clickedShape = diag ? diag.shapes.find(function (s) { return s.id === draggedId; }) : null;
|
|
379
|
+
if (clickedShape && clickedShape.linkedDiagramId) {
|
|
380
|
+
var target = findDiagramById(clickedShape.linkedDiagramId, diagramsList);
|
|
381
|
+
if (target) {
|
|
382
|
+
hideLinkPicker();
|
|
383
|
+
diagNavStack.push(currentDiagramId);
|
|
384
|
+
if (diagNavStack.length > 30) diagNavStack.shift();
|
|
385
|
+
selectDiagramme(clickedShape.linkedDiagramId);
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
} else if (clickedShape && clickedShape.externalUrl) {
|
|
389
|
+
window.open(clickedShape.externalUrl, "_blank");
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
panStart = null;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
function onWheel(e) {
|
|
399
|
+
e.preventDefault();
|
|
400
|
+
var svg = document.getElementById("canvas");
|
|
401
|
+
var r = svg.getBoundingClientRect();
|
|
402
|
+
applyZoom(e.deltaY < 0 ? 1.12 : 1 / 1.12, e.clientX - r.left, e.clientY - r.top);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
// ── Init ──
|
|
407
|
+
document.addEventListener("DOMContentLoaded", function () {
|
|
408
|
+
diagramsList = loadDiagrammes();
|
|
409
|
+
if (!localStorage.getItem("mes_diagrammes")) saveDiagrammes();
|
|
410
|
+
var savedDiagId = localStorage.getItem("current_diagram_id");
|
|
411
|
+
if (savedDiagId && findDiagramById(savedDiagId, diagramsList)) {
|
|
412
|
+
currentDiagramId = savedDiagId;
|
|
413
|
+
} else {
|
|
414
|
+
var savedIdx = parseInt(localStorage.getItem("current_diagram_idx"), 10);
|
|
415
|
+
if (!isNaN(savedIdx) && savedIdx >= 0 && savedIdx < diagramsList.length) {
|
|
416
|
+
currentDiagramId = String(diagramsList[savedIdx].id);
|
|
417
|
+
} else {
|
|
418
|
+
currentDiagramId = diagramsList[0] ? String(diagramsList[0].id) : null;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
restoreLockForDiagram(currentDiagramId);
|
|
422
|
+
checkDiffDiagrammes();
|
|
423
|
+
renderAll();
|
|
424
|
+
updateLockBtn();
|
|
425
|
+
updateBackBtn();
|
|
426
|
+
|
|
427
|
+
var canvas = document.getElementById("canvas");
|
|
428
|
+
canvas.addEventListener("mousedown", onMouseDown);
|
|
429
|
+
canvas.addEventListener("mousemove", onMouseMove);
|
|
430
|
+
canvas.addEventListener("mouseup", onMouseUp);
|
|
431
|
+
canvas.addEventListener("wheel", onWheel, { passive: false });
|
|
432
|
+
|
|
433
|
+
var titleInput = document.getElementById("diagramTitle");
|
|
434
|
+
titleInput.addEventListener("change", onTitleChange);
|
|
435
|
+
titleInput.addEventListener("blur", onTitleChange);
|
|
436
|
+
titleInput.addEventListener("keydown", function (e) {
|
|
437
|
+
if (e.key === "Enter") {
|
|
438
|
+
e.preventDefault();
|
|
439
|
+
if (pendingNewDiagram) confirmerNouveauDiagramme();
|
|
440
|
+
else titleInput.blur();
|
|
441
|
+
}
|
|
442
|
+
if (e.key === "Escape" && pendingNewDiagram) annulerNouveauDiagramme();
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
var textInput = document.getElementById("shapeTextInput");
|
|
446
|
+
textInput.addEventListener("keydown", function (e) {
|
|
447
|
+
if (e.key === "Enter") { e.preventDefault(); confirmTextEdit(); }
|
|
448
|
+
if (e.key === "Escape") {
|
|
449
|
+
document.getElementById("textOverlay").style.display = "none";
|
|
450
|
+
editingShapeId = null;
|
|
451
|
+
}
|
|
452
|
+
});
|
|
453
|
+
textInput.addEventListener("blur", function () {
|
|
454
|
+
// Small delay to allow click on modal-confirm etc.
|
|
455
|
+
setTimeout(confirmTextEdit, 100);
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
var postitInput = document.getElementById("postitTextInput");
|
|
459
|
+
postitInput.addEventListener("keydown", function (e) {
|
|
460
|
+
if (e.key === "Escape") {
|
|
461
|
+
document.getElementById("textOverlay").style.display = "none";
|
|
462
|
+
postitInput.style.display = "none";
|
|
463
|
+
document.getElementById("shapeTextInput").style.display = "block";
|
|
464
|
+
editingShapeId = null;
|
|
465
|
+
renderAll(); // restaure la visibilité du texte SVG
|
|
466
|
+
}
|
|
467
|
+
if (e.key === "Tab" && editingTableCell !== null) {
|
|
468
|
+
e.preventDefault();
|
|
469
|
+
var diag = getCurrentDiagram();
|
|
470
|
+
var shape = diag.shapes.find(function (s) { return s.id === editingShapeId; });
|
|
471
|
+
if (!shape) return;
|
|
472
|
+
// Sauvegarder la cellule courante
|
|
473
|
+
if (!shape.cells) shape.cells = [];
|
|
474
|
+
while (shape.cells.length <= editingTableCell.row) shape.cells.push([]);
|
|
475
|
+
shape.cells[editingTableCell.row][editingTableCell.col] = postitInput.value;
|
|
476
|
+
var rows = shape.rows || 3;
|
|
477
|
+
var cols = shape.cols || 3;
|
|
478
|
+
var nextRow = editingTableCell.row;
|
|
479
|
+
var nextCol = editingTableCell.col;
|
|
480
|
+
if (e.shiftKey) {
|
|
481
|
+
// Shift+Tab : cellule précédente, s'arrête à la première
|
|
482
|
+
nextCol -= 1;
|
|
483
|
+
if (nextCol < 0) {
|
|
484
|
+
if (nextRow > 0) { nextRow--; nextCol = cols - 1; }
|
|
485
|
+
else { nextCol = 0; }
|
|
486
|
+
}
|
|
487
|
+
} else {
|
|
488
|
+
// Tab : cellule suivante, ajoute une ligne à la fin
|
|
489
|
+
nextCol += 1;
|
|
490
|
+
if (nextCol >= cols) { nextRow++; nextCol = 0; }
|
|
491
|
+
if (nextRow >= rows) {
|
|
492
|
+
pushHistory();
|
|
493
|
+
shape.rows = rows + 1;
|
|
494
|
+
if (!shape.cells) shape.cells = [];
|
|
495
|
+
while (shape.cells.length < shape.rows) shape.cells.push([]);
|
|
496
|
+
shape.cells[shape.rows - 1] = new Array(cols).fill("");
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
var shapeId = editingShapeId;
|
|
500
|
+
saveDiagrammes();
|
|
501
|
+
renderAll();
|
|
502
|
+
startTableCellEdit(shapeId, nextRow, nextCol);
|
|
503
|
+
}
|
|
504
|
+
// Enter ajoute un saut de ligne (comportement natif textarea — pas de preventDefault)
|
|
505
|
+
});
|
|
506
|
+
postitInput.addEventListener("blur", function () {
|
|
507
|
+
setTimeout(confirmTextEdit, 100);
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
document.addEventListener("keydown", function (e) {
|
|
511
|
+
if (document.activeElement.tagName === "INPUT" || document.activeElement.tagName === "TEXTAREA") return;
|
|
512
|
+
if ((e.ctrlKey || e.metaKey) && e.key === "z") { e.preventDefault(); undoAction(); return; }
|
|
513
|
+
if ((e.ctrlKey || e.metaKey) && e.key === "a") {
|
|
514
|
+
e.preventDefault();
|
|
515
|
+
var diag = getCurrentDiagram();
|
|
516
|
+
if (diag && diag.shapes.length > 0) {
|
|
517
|
+
selectedIds = diag.shapes.map(function (s) { return s.id; });
|
|
518
|
+
selectedType = "shape";
|
|
519
|
+
selectedId = selectedIds[selectedIds.length - 1];
|
|
520
|
+
renderAll();
|
|
521
|
+
document.getElementById("colorPanel").style.display = "flex";
|
|
522
|
+
syncColorPanel();
|
|
523
|
+
}
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
if (boardLocked) return;
|
|
527
|
+
if (e.key === "Delete" || e.key === "Backspace") deleteSelected();
|
|
528
|
+
if (e.key === "Escape") {
|
|
529
|
+
hideLinkPicker();
|
|
530
|
+
arrowSrcId = null;
|
|
531
|
+
document.getElementById("tempArrow").style.display = "none";
|
|
532
|
+
setTool("select");
|
|
533
|
+
selectedIds = []; selectedId = null; selectedType = null;
|
|
534
|
+
document.getElementById("colorPanel").style.display = "none";
|
|
535
|
+
renderAll();
|
|
536
|
+
}
|
|
537
|
+
if ((e.ctrlKey || e.metaKey) && e.key === "x") {
|
|
538
|
+
e.preventDefault();
|
|
539
|
+
if (selectedIds.length === 0) return;
|
|
540
|
+
var diag = getCurrentDiagram();
|
|
541
|
+
clipboard = selectedIds.map(function (id) {
|
|
542
|
+
var s = diag.shapes.find(function (sh) { return sh.id === id; });
|
|
543
|
+
return s ? JSON.parse(JSON.stringify(s)) : null;
|
|
544
|
+
}).filter(Boolean);
|
|
545
|
+
deleteSelected();
|
|
546
|
+
return;
|
|
547
|
+
}
|
|
548
|
+
if ((e.ctrlKey || e.metaKey) && e.key === "c") {
|
|
549
|
+
e.preventDefault();
|
|
550
|
+
if (selectedIds.length === 0) return;
|
|
551
|
+
var diag = getCurrentDiagram();
|
|
552
|
+
clipboard = selectedIds.map(function (id) {
|
|
553
|
+
var s = diag.shapes.find(function (sh) { return sh.id === id; });
|
|
554
|
+
return s ? JSON.parse(JSON.stringify(s)) : null;
|
|
555
|
+
}).filter(Boolean);
|
|
556
|
+
}
|
|
557
|
+
if ((e.ctrlKey || e.metaKey) && e.key === "v") {
|
|
558
|
+
e.preventDefault();
|
|
559
|
+
if (clipboard.length > 0) {
|
|
560
|
+
pasteShapes();
|
|
561
|
+
return;
|
|
562
|
+
}
|
|
563
|
+
if (!navigator.clipboard || !navigator.clipboard.read) return;
|
|
564
|
+
navigator.clipboard.read().then(function (clipItems) {
|
|
565
|
+
for (var i = 0; i < clipItems.length; i++) {
|
|
566
|
+
for (var j = 0; j < clipItems[i].types.length; j++) {
|
|
567
|
+
if (clipItems[i].types[j].indexOf("image") !== -1) {
|
|
568
|
+
clipItems[i].getType(clipItems[i].types[j]).then(function (blob) {
|
|
569
|
+
handleImagePaste(blob);
|
|
570
|
+
});
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
}).catch(function () {});
|
|
576
|
+
}
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
// Quand la fenêtre reprend le focus, l'utilisateur revient d'une autre app
|
|
580
|
+
// où il a peut-être copié une image → vider le clipboard interne
|
|
581
|
+
window.addEventListener("focus", function () {
|
|
582
|
+
clipboard = [];
|
|
583
|
+
});
|
|
584
|
+
|
|
585
|
+
document.getElementById("modalPremiereSauvegardeDiag").addEventListener("click", function (e) {
|
|
586
|
+
if (e.target === this) this.classList.remove("open");
|
|
587
|
+
});
|
|
588
|
+
|
|
589
|
+
|
|
590
|
+
document.addEventListener("mousedown", function (e) {
|
|
591
|
+
var lp = document.getElementById("linkPickerPanel");
|
|
592
|
+
if (lp && lp.style.display !== "none") {
|
|
593
|
+
var lbtn = document.getElementById("btnShapeLink");
|
|
594
|
+
if (!lp.contains(e.target) && e.target !== lbtn) hideLinkPicker();
|
|
595
|
+
}
|
|
596
|
+
var panel = document.getElementById("diagramListPanel");
|
|
597
|
+
if (!panel.classList.contains("open")) return;
|
|
598
|
+
var burgerBtn = document.querySelector(".diagram-tool[onclick=\"toggleDiagramList()\"]");
|
|
599
|
+
if (!panel.contains(e.target) && (!burgerBtn || !burgerBtn.contains(e.target))) {
|
|
600
|
+
panel.classList.remove("open");
|
|
601
|
+
}
|
|
602
|
+
});
|
|
603
|
+
});
|