doc-survival-kit 1.0.0 → 2.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.
Potentially problematic release.
This version of doc-survival-kit might be problematic. Click here for more details.
- package/diagram.html +92 -3
- package/diagram.js +1116 -132
- package/diagrammes.js +806 -159
- package/i18n/en.js +10 -1
- package/i18n/fr.js +10 -1
- package/images/img_1774709818351.png +0 -0
- package/images/img_1774711114086.png +0 -0
- package/images/img_1774711575731.png +0 -0
- package/images/img_1774712426980.png +0 -0
- package/package.json +1 -1
- package/style.css +82 -18
package/diagram.js
CHANGED
|
@@ -5,7 +5,11 @@
|
|
|
5
5
|
// ── État global ──
|
|
6
6
|
var currentTool = "select";
|
|
7
7
|
var diagramsList = [];
|
|
8
|
-
var
|
|
8
|
+
var currentDiagramId = null;
|
|
9
|
+
var diagNavStack = [];
|
|
10
|
+
var diagExpandedIds = {};
|
|
11
|
+
var pendingParentId = null;
|
|
12
|
+
var pendingNavDiagId = null;
|
|
9
13
|
var viewTransform = { x: 60, y: 60, scale: 1 };
|
|
10
14
|
var selectedId = null;
|
|
11
15
|
var selectedType = null; // "shape" | "arrow"
|
|
@@ -18,6 +22,7 @@ var dragState = null;
|
|
|
18
22
|
var panStart = null;
|
|
19
23
|
var arrowSrcId = null;
|
|
20
24
|
var editingShapeId = null;
|
|
25
|
+
var editingTableCell = null; // { row, col } | null
|
|
21
26
|
var pickMode = null; // "fontSize" | "fullStyle" | null
|
|
22
27
|
var pickTargetIds = []; // selectedIds sauvegardés pendant le pick
|
|
23
28
|
var lastClickTime = 0;
|
|
@@ -25,7 +30,27 @@ var lastClickShapeId = null;
|
|
|
25
30
|
var lastClickArrowId = null;
|
|
26
31
|
var editingArrowId = null;
|
|
27
32
|
var boardLocked = false;
|
|
28
|
-
var
|
|
33
|
+
var diagDragSrcId = null;
|
|
34
|
+
var historyStack = [];
|
|
35
|
+
var MAX_HISTORY = 50;
|
|
36
|
+
|
|
37
|
+
// ── Helpers tableau ──
|
|
38
|
+
function getColWidths(shape) {
|
|
39
|
+
var cols = shape.cols || 3;
|
|
40
|
+
if (shape.colWidths && shape.colWidths.length === cols) return shape.colWidths;
|
|
41
|
+
var w = shape.w / cols;
|
|
42
|
+
var result = [];
|
|
43
|
+
for (var i = 0; i < cols; i++) result.push(w);
|
|
44
|
+
return result;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// xOffsets[i] = x du début de la colonne i (relatif à shape.x)
|
|
48
|
+
function getColOffsets(shape) {
|
|
49
|
+
var cw = getColWidths(shape);
|
|
50
|
+
var offsets = [0];
|
|
51
|
+
for (var i = 0; i < cw.length - 1; i++) offsets.push(offsets[i] + cw[i]);
|
|
52
|
+
return offsets;
|
|
53
|
+
}
|
|
29
54
|
|
|
30
55
|
// ── Palette ──
|
|
31
56
|
var COLORS = {
|
|
@@ -35,6 +60,7 @@ var COLORS = {
|
|
|
35
60
|
"t-sky": { fill: "rgba(224,242,254,0.75)", stroke: "#0284c7", text: "#0369a1" },
|
|
36
61
|
"t-rose": { fill: "rgba(255,228,230,0.75)", stroke: "#e11d48", text: "#be123c" },
|
|
37
62
|
"t-teal": { fill: "rgba(204,251,241,0.75)", stroke: "#0d9488", text: "#0f766e" },
|
|
63
|
+
"t-white": { fill: "rgba(255,255,255,0.95)", stroke: "#d4d4d4", text: "#404040" },
|
|
38
64
|
};
|
|
39
65
|
var DEFAULT_COLOR = "t-sky";
|
|
40
66
|
|
|
@@ -43,8 +69,11 @@ var DEFAULT_SIZES = {
|
|
|
43
69
|
rounded: { w: 130, h: 50 },
|
|
44
70
|
db: { w: 100, h: 65 },
|
|
45
71
|
cloud: { w: 110, h: 65 },
|
|
72
|
+
nuage: { w: 121, h: 96 },
|
|
46
73
|
text: { w: 110, h: 34 },
|
|
47
74
|
postit: { w: 130, h: 110 },
|
|
75
|
+
actor: { w: 60, h: 90 },
|
|
76
|
+
table: { w: 210, h: 120 },
|
|
48
77
|
};
|
|
49
78
|
|
|
50
79
|
// ── Helpers ──
|
|
@@ -78,6 +107,22 @@ function wrapPostitLines(text, maxWidth, fontSize) {
|
|
|
78
107
|
var words = line.split(" ");
|
|
79
108
|
var current = "";
|
|
80
109
|
words.forEach(function (word) {
|
|
110
|
+
// Si le mot seul dépasse maxWidth, le couper caractère par caractère
|
|
111
|
+
if (measureText(word, fs, "600") > maxWidth) {
|
|
112
|
+
if (current !== "") { result.push(current); current = ""; }
|
|
113
|
+
var chunk = "";
|
|
114
|
+
for (var ci = 0; ci < word.length; ci++) {
|
|
115
|
+
var chunkTest = chunk + word[ci];
|
|
116
|
+
if (measureText(chunkTest, fs, "600") > maxWidth) {
|
|
117
|
+
if (chunk !== "") result.push(chunk);
|
|
118
|
+
chunk = word[ci];
|
|
119
|
+
} else {
|
|
120
|
+
chunk = chunkTest;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
current = chunk;
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
81
126
|
var test = current ? current + " " + word : word;
|
|
82
127
|
if (current && measureText(test, fs, "600") > maxWidth) {
|
|
83
128
|
result.push(current);
|
|
@@ -91,17 +136,103 @@ function wrapPostitLines(text, maxWidth, fontSize) {
|
|
|
91
136
|
return result;
|
|
92
137
|
}
|
|
93
138
|
|
|
139
|
+
function findDiagramById(id, list) {
|
|
140
|
+
if (!list) return null;
|
|
141
|
+
for (var i = 0; i < list.length; i++) {
|
|
142
|
+
if (String(list[i].id) === String(id)) return list[i];
|
|
143
|
+
var found = findDiagramById(id, list[i].children);
|
|
144
|
+
if (found) return found;
|
|
145
|
+
}
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function findParentListOf(id, list) {
|
|
150
|
+
if (!list) return null;
|
|
151
|
+
for (var i = 0; i < list.length; i++) {
|
|
152
|
+
if (String(list[i].id) === String(id)) return { list: list, index: i };
|
|
153
|
+
var found = findParentListOf(id, list[i].children);
|
|
154
|
+
if (found) return found;
|
|
155
|
+
}
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function flattenDiagrams(list, result) {
|
|
160
|
+
result = result || [];
|
|
161
|
+
(list || []).forEach(function (d) {
|
|
162
|
+
result.push(d);
|
|
163
|
+
flattenDiagrams(d.children, result);
|
|
164
|
+
});
|
|
165
|
+
return result;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function calcMaxExpandedDepth(list, depth) {
|
|
169
|
+
var max = depth;
|
|
170
|
+
(list || []).forEach(function (d) {
|
|
171
|
+
if (d.children && d.children.length > 0 && diagExpandedIds[String(d.id)]) {
|
|
172
|
+
var childMax = calcMaxExpandedDepth(d.children, depth + 1);
|
|
173
|
+
max = Math.max(max, childMax);
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
return max;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function updateSidebarWidth() {
|
|
180
|
+
var depth = calcMaxExpandedDepth(diagramsList, 0);
|
|
181
|
+
var totalW = 220 + depth * 14;
|
|
182
|
+
var panel = document.getElementById("diagramListPanel");
|
|
183
|
+
if (!panel) return;
|
|
184
|
+
panel.style.width = totalW + "px";
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function getAncestorPath(targetId, list, path) {
|
|
188
|
+
path = path || [];
|
|
189
|
+
for (var i = 0; i < (list || []).length; i++) {
|
|
190
|
+
var d = list[i];
|
|
191
|
+
if (String(d.id) === String(targetId)) return path;
|
|
192
|
+
var childPath = getAncestorPath(targetId, d.children, path.concat([String(d.id)]));
|
|
193
|
+
if (childPath !== null) return childPath;
|
|
194
|
+
}
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function updateBackBtn() {
|
|
199
|
+
var btn = document.getElementById("btnDiagBack");
|
|
200
|
+
if (!btn) return;
|
|
201
|
+
btn.style.display = diagNavStack.length > 0 ? "inline-flex" : "none";
|
|
202
|
+
}
|
|
203
|
+
|
|
94
204
|
function getCurrentDiagram() {
|
|
95
|
-
|
|
205
|
+
if (currentDiagramId !== null) {
|
|
206
|
+
var found = findDiagramById(currentDiagramId, diagramsList);
|
|
207
|
+
if (found) return found;
|
|
208
|
+
}
|
|
209
|
+
return diagramsList[0] || null;
|
|
96
210
|
}
|
|
97
211
|
|
|
98
212
|
// ── Persistance localStorage ──
|
|
99
213
|
function loadDiagrammes() {
|
|
100
214
|
try {
|
|
101
215
|
var stored = JSON.parse(localStorage.getItem("mes_diagrammes"));
|
|
102
|
-
if (stored && stored.length)
|
|
216
|
+
if (stored && stored.length) {
|
|
217
|
+
var migrate = function (list) {
|
|
218
|
+
list.forEach(function (d) {
|
|
219
|
+
if (!d.children) d.children = [];
|
|
220
|
+
migrate(d.children);
|
|
221
|
+
});
|
|
222
|
+
};
|
|
223
|
+
migrate(stored);
|
|
224
|
+
return stored;
|
|
225
|
+
}
|
|
103
226
|
} catch (e) {}
|
|
104
|
-
|
|
227
|
+
var defaults = JSON.parse(JSON.stringify(diagrammesDefaut));
|
|
228
|
+
var migrate2 = function (list) {
|
|
229
|
+
list.forEach(function (d) {
|
|
230
|
+
if (!d.children) d.children = [];
|
|
231
|
+
migrate2(d.children);
|
|
232
|
+
});
|
|
233
|
+
};
|
|
234
|
+
migrate2(defaults);
|
|
235
|
+
return defaults;
|
|
105
236
|
}
|
|
106
237
|
|
|
107
238
|
function saveDiagrammes() {
|
|
@@ -109,6 +240,22 @@ function saveDiagrammes() {
|
|
|
109
240
|
checkDiffDiagrammes();
|
|
110
241
|
}
|
|
111
242
|
|
|
243
|
+
function pushHistory() {
|
|
244
|
+
historyStack.push(JSON.stringify(diagramsList));
|
|
245
|
+
if (historyStack.length > MAX_HISTORY) historyStack.shift();
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function undoAction() {
|
|
249
|
+
if (historyStack.length === 0) return;
|
|
250
|
+
diagramsList = JSON.parse(historyStack.pop());
|
|
251
|
+
if (!findDiagramById(currentDiagramId, diagramsList)) {
|
|
252
|
+
currentDiagramId = diagramsList[0] ? String(diagramsList[0].id) : null;
|
|
253
|
+
}
|
|
254
|
+
saveDiagrammes();
|
|
255
|
+
renderAll();
|
|
256
|
+
renderDiagramList();
|
|
257
|
+
}
|
|
258
|
+
|
|
112
259
|
function checkDiffDiagrammes() {
|
|
113
260
|
var stored = localStorage.getItem("mes_diagrammes");
|
|
114
261
|
var diff = stored !== JSON.stringify(diagrammesDefaut);
|
|
@@ -123,14 +270,14 @@ function getLockMap() {
|
|
|
123
270
|
try { return JSON.parse(localStorage.getItem("diagrammes_lock") || "{}"); } catch(e) { return {}; }
|
|
124
271
|
}
|
|
125
272
|
function saveCurrentLock() {
|
|
126
|
-
var diag =
|
|
273
|
+
var diag = getCurrentDiagram();
|
|
127
274
|
if (!diag) return;
|
|
128
275
|
var map = getLockMap();
|
|
129
276
|
map[diag.id] = boardLocked;
|
|
130
277
|
localStorage.setItem("diagrammes_lock", JSON.stringify(map));
|
|
131
278
|
}
|
|
132
|
-
function restoreLockForDiagram(
|
|
133
|
-
var diag = diagramsList
|
|
279
|
+
function restoreLockForDiagram(diagId) {
|
|
280
|
+
var diag = findDiagramById(diagId, diagramsList);
|
|
134
281
|
if (!diag) return;
|
|
135
282
|
var map = getLockMap();
|
|
136
283
|
boardLocked = map[diag.id] === true;
|
|
@@ -159,14 +306,14 @@ function getZoomMap() {
|
|
|
159
306
|
try { return JSON.parse(localStorage.getItem("diagrammes_zoom") || "{}"); } catch(e) { return {}; }
|
|
160
307
|
}
|
|
161
308
|
function saveCurrentZoom() {
|
|
162
|
-
var diag =
|
|
309
|
+
var diag = getCurrentDiagram();
|
|
163
310
|
if (!diag) return;
|
|
164
311
|
var map = getZoomMap();
|
|
165
312
|
map[diag.id] = viewTransform.scale;
|
|
166
313
|
localStorage.setItem("diagrammes_zoom", JSON.stringify(map));
|
|
167
314
|
}
|
|
168
|
-
function restoreZoomForDiagram(
|
|
169
|
-
var diag = diagramsList
|
|
315
|
+
function restoreZoomForDiagram(diagId) {
|
|
316
|
+
var diag = findDiagramById(diagId, diagramsList);
|
|
170
317
|
if (!diag) return;
|
|
171
318
|
var map = getZoomMap();
|
|
172
319
|
viewTransform.scale = map[diag.id] !== undefined ? map[diag.id] : 1;
|
|
@@ -372,6 +519,7 @@ function updateViewport() {
|
|
|
372
519
|
);
|
|
373
520
|
document.getElementById("zoomLevel").textContent =
|
|
374
521
|
Math.round(viewTransform.scale * 100) + "%";
|
|
522
|
+
updateTableOverlay();
|
|
375
523
|
}
|
|
376
524
|
|
|
377
525
|
// ── Zoom ──
|
|
@@ -439,7 +587,7 @@ function renderShape(shape) {
|
|
|
439
587
|
g.appendChild(topEl);
|
|
440
588
|
|
|
441
589
|
} else if (shape.type === "cloud") {
|
|
442
|
-
// Ellipse
|
|
590
|
+
// Ellipse en trait continu = service externe / cloud
|
|
443
591
|
var el = createSVGEl("ellipse");
|
|
444
592
|
el.setAttribute("cx", shape.x + shape.w / 2);
|
|
445
593
|
el.setAttribute("cy", shape.y + shape.h / 2);
|
|
@@ -450,6 +598,211 @@ function renderShape(shape) {
|
|
|
450
598
|
el.setAttribute("stroke-width", sw);
|
|
451
599
|
g.appendChild(el);
|
|
452
600
|
|
|
601
|
+
} else if (shape.type === "nuage") {
|
|
602
|
+
// Nuage — contour extérieur du Bootstrap cloud icon (viewBox 0 0 16 16)
|
|
603
|
+
// Le path se referme exactement : endpoint = startpoint, Z explicite
|
|
604
|
+
var nuageG = createSVGEl("g");
|
|
605
|
+
nuageG.setAttribute("transform",
|
|
606
|
+
"translate(" + shape.x + "," + shape.y + ") scale(" + (shape.w / 16) + "," + (shape.h / 16) + ")"
|
|
607
|
+
);
|
|
608
|
+
var nuagePathEl = createSVGEl("path");
|
|
609
|
+
nuagePathEl.setAttribute("d",
|
|
610
|
+
"M4.406 3.342A5.53 5.53 0 0 1 8 2c2.69 0 4.923 2 5.166 4.579" +
|
|
611
|
+
"C14.758 6.804 16 8.137 16 9.773 16 11.569 14.502 13 12.687 13" +
|
|
612
|
+
"H3.781C1.708 13 0 11.366 0 9.318" +
|
|
613
|
+
"c0-1.763 1.266-3.223 2.942-3.593.143-.863.698-1.723 1.464-2.383Z"
|
|
614
|
+
);
|
|
615
|
+
nuagePathEl.setAttribute("fill", c.fill);
|
|
616
|
+
nuagePathEl.setAttribute("stroke", stroke);
|
|
617
|
+
nuagePathEl.setAttribute("stroke-width", sw);
|
|
618
|
+
nuagePathEl.setAttribute("vector-effect", "non-scaling-stroke");
|
|
619
|
+
nuageG.appendChild(nuagePathEl);
|
|
620
|
+
g.appendChild(nuageG);
|
|
621
|
+
|
|
622
|
+
} else if (shape.type === "actor") {
|
|
623
|
+
// Bonhomme UML (stick figure) — pas de fond, juste les traits
|
|
624
|
+
var acx = shape.x + shape.w / 2;
|
|
625
|
+
var aHeadR = Math.min(shape.w * 0.20, shape.h * 0.14);
|
|
626
|
+
var aHeadCY = shape.y + aHeadR + shape.h * 0.02;
|
|
627
|
+
// Tête
|
|
628
|
+
var aHead = createSVGEl("circle");
|
|
629
|
+
aHead.setAttribute("cx", acx);
|
|
630
|
+
aHead.setAttribute("cy", aHeadCY);
|
|
631
|
+
aHead.setAttribute("r", aHeadR);
|
|
632
|
+
aHead.setAttribute("fill", c.fill);
|
|
633
|
+
aHead.setAttribute("stroke", stroke);
|
|
634
|
+
aHead.setAttribute("stroke-width", sw);
|
|
635
|
+
g.appendChild(aHead);
|
|
636
|
+
// Corps
|
|
637
|
+
var aBodyTop = aHeadCY + aHeadR;
|
|
638
|
+
var aBodyBot = shape.y + shape.h * 0.60;
|
|
639
|
+
var aBody = createSVGEl("line");
|
|
640
|
+
aBody.setAttribute("x1", acx); aBody.setAttribute("y1", aBodyTop);
|
|
641
|
+
aBody.setAttribute("x2", acx); aBody.setAttribute("y2", aBodyBot);
|
|
642
|
+
aBody.setAttribute("stroke", stroke); aBody.setAttribute("stroke-width", sw);
|
|
643
|
+
aBody.setAttribute("stroke-linecap", "round");
|
|
644
|
+
g.appendChild(aBody);
|
|
645
|
+
// Bras
|
|
646
|
+
var aArmY = shape.y + shape.h * 0.38;
|
|
647
|
+
var aArmLX = shape.x + shape.w * 0.10;
|
|
648
|
+
var aArmRX = shape.x + shape.w * 0.90;
|
|
649
|
+
var aArms = createSVGEl("line");
|
|
650
|
+
aArms.setAttribute("x1", aArmLX); aArms.setAttribute("y1", aArmY);
|
|
651
|
+
aArms.setAttribute("x2", aArmRX); aArms.setAttribute("y2", aArmY);
|
|
652
|
+
aArms.setAttribute("stroke", stroke); aArms.setAttribute("stroke-width", sw);
|
|
653
|
+
aArms.setAttribute("stroke-linecap", "round");
|
|
654
|
+
g.appendChild(aArms);
|
|
655
|
+
// Jambe gauche
|
|
656
|
+
var aLegLX = shape.x + shape.w * 0.18;
|
|
657
|
+
var aLegRX = shape.x + shape.w * 0.82;
|
|
658
|
+
var aLegBotY = shape.y + shape.h * 0.82;
|
|
659
|
+
var aLegL = createSVGEl("line");
|
|
660
|
+
aLegL.setAttribute("x1", acx); aLegL.setAttribute("y1", aBodyBot);
|
|
661
|
+
aLegL.setAttribute("x2", aLegLX); aLegL.setAttribute("y2", aLegBotY);
|
|
662
|
+
aLegL.setAttribute("stroke", stroke); aLegL.setAttribute("stroke-width", sw);
|
|
663
|
+
aLegL.setAttribute("stroke-linecap", "round");
|
|
664
|
+
g.appendChild(aLegL);
|
|
665
|
+
// Jambe droite
|
|
666
|
+
var aLegR = createSVGEl("line");
|
|
667
|
+
aLegR.setAttribute("x1", acx); aLegR.setAttribute("y1", aBodyBot);
|
|
668
|
+
aLegR.setAttribute("x2", aLegRX); aLegR.setAttribute("y2", aLegBotY);
|
|
669
|
+
aLegR.setAttribute("stroke", stroke); aLegR.setAttribute("stroke-width", sw);
|
|
670
|
+
aLegR.setAttribute("stroke-linecap", "round");
|
|
671
|
+
g.appendChild(aLegR);
|
|
672
|
+
|
|
673
|
+
} else if (shape.type === "table") {
|
|
674
|
+
var tRows = shape.rows || 3;
|
|
675
|
+
var tCols = shape.cols || 3;
|
|
676
|
+
var tCells = shape.cells || [];
|
|
677
|
+
var tColWidths = getColWidths(shape);
|
|
678
|
+
var tColOffsets = getColOffsets(shape);
|
|
679
|
+
var cellH = shape.h / tRows;
|
|
680
|
+
var tfs = shape.fontSize || 12;
|
|
681
|
+
// Fond
|
|
682
|
+
var tBg = createSVGEl("rect");
|
|
683
|
+
tBg.setAttribute("x", shape.x); tBg.setAttribute("y", shape.y);
|
|
684
|
+
tBg.setAttribute("width", shape.w); tBg.setAttribute("height", shape.h);
|
|
685
|
+
tBg.setAttribute("rx", 3);
|
|
686
|
+
tBg.setAttribute("fill", c.fill); tBg.setAttribute("stroke", stroke);
|
|
687
|
+
tBg.setAttribute("stroke-width", sw);
|
|
688
|
+
g.appendChild(tBg);
|
|
689
|
+
// Lignes horizontales internes
|
|
690
|
+
for (var tri = 1; tri < tRows; tri++) {
|
|
691
|
+
var thl = createSVGEl("line");
|
|
692
|
+
thl.setAttribute("x1", shape.x); thl.setAttribute("y1", shape.y + tri * cellH);
|
|
693
|
+
thl.setAttribute("x2", shape.x + shape.w); thl.setAttribute("y2", shape.y + tri * cellH);
|
|
694
|
+
thl.setAttribute("stroke", stroke); thl.setAttribute("stroke-width", sw * 0.5);
|
|
695
|
+
g.appendChild(thl);
|
|
696
|
+
}
|
|
697
|
+
// Lignes verticales internes
|
|
698
|
+
for (var tci = 1; tci < tCols; tci++) {
|
|
699
|
+
var tvl = createSVGEl("line");
|
|
700
|
+
var tvlX = shape.x + tColOffsets[tci];
|
|
701
|
+
tvl.setAttribute("x1", tvlX); tvl.setAttribute("y1", shape.y);
|
|
702
|
+
tvl.setAttribute("x2", tvlX); tvl.setAttribute("y2", shape.y + shape.h);
|
|
703
|
+
tvl.setAttribute("stroke", stroke); tvl.setAttribute("stroke-width", sw * 0.5);
|
|
704
|
+
g.appendChild(tvl);
|
|
705
|
+
}
|
|
706
|
+
// Textes des cellules avec word wrap
|
|
707
|
+
var clineH = Math.round(tfs * 1.33);
|
|
708
|
+
for (var tri2 = 0; tri2 < tRows; tri2++) {
|
|
709
|
+
for (var tci2 = 0; tci2 < tCols; tci2++) {
|
|
710
|
+
var cellVal = (tCells[tri2] && tCells[tri2][tci2]) || "";
|
|
711
|
+
if (!cellVal) continue;
|
|
712
|
+
var cellW2 = tColWidths[tci2];
|
|
713
|
+
var cpad = 6;
|
|
714
|
+
var clines = wrapPostitLines(cellVal, cellW2 - cpad * 2, tfs);
|
|
715
|
+
var cCenterY = shape.y + tri2 * cellH + cellH / 2;
|
|
716
|
+
var cfirstY = cCenterY - clines.length * clineH / 2 + clineH * 0.5 + tfs * 0.5;
|
|
717
|
+
var cellCenterX = shape.x + tColOffsets[tci2] + cellW2 / 2;
|
|
718
|
+
var ctxt = createSVGEl("text");
|
|
719
|
+
ctxt.setAttribute("x", cellCenterX);
|
|
720
|
+
ctxt.setAttribute("y", cfirstY);
|
|
721
|
+
ctxt.setAttribute("text-anchor", "middle");
|
|
722
|
+
ctxt.setAttribute("fill", c.text);
|
|
723
|
+
ctxt.setAttribute("font-size", tfs);
|
|
724
|
+
ctxt.setAttribute("data-cell", tri2 + "-" + tci2);
|
|
725
|
+
ctxt.setAttribute("font-family", '"Segoe UI",system-ui,sans-serif');
|
|
726
|
+
ctxt.setAttribute("font-weight", "600");
|
|
727
|
+
ctxt.setAttribute("pointer-events", "none");
|
|
728
|
+
(function(cx, lines) {
|
|
729
|
+
lines.forEach(function(line, i) {
|
|
730
|
+
var ts = createSVGEl("tspan");
|
|
731
|
+
ts.setAttribute("x", cx);
|
|
732
|
+
if (i > 0) ts.setAttribute("dy", clineH + "px");
|
|
733
|
+
ts.textContent = line || " ";
|
|
734
|
+
ctxt.appendChild(ts);
|
|
735
|
+
});
|
|
736
|
+
})(cellCenterX, clines);
|
|
737
|
+
g.appendChild(ctxt);
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
// Poignées de redimensionnement des colonnes (chevrons au-dessus de chaque séparateur)
|
|
741
|
+
if (isSel && selectedIds.length === 1) {
|
|
742
|
+
for (var tsi = 1; tsi < tCols; tsi++) {
|
|
743
|
+
var hx = shape.x + tColOffsets[tsi];
|
|
744
|
+
var hy = shape.y - 13;
|
|
745
|
+
var hg = createSVGEl("g");
|
|
746
|
+
hg.setAttribute("data-col-sep", tsi - 1);
|
|
747
|
+
hg.setAttribute("data-shape-id", shape.id);
|
|
748
|
+
hg.style.cursor = "col-resize";
|
|
749
|
+
// Zone de clic transparente
|
|
750
|
+
var hArea = createSVGEl("rect");
|
|
751
|
+
hArea.setAttribute("x", hx - 9); hArea.setAttribute("y", hy - 7);
|
|
752
|
+
hArea.setAttribute("width", 18); hArea.setAttribute("height", 22);
|
|
753
|
+
hArea.setAttribute("fill", "transparent");
|
|
754
|
+
hArea.setAttribute("data-col-sep", tsi - 1);
|
|
755
|
+
hArea.setAttribute("data-shape-id", shape.id);
|
|
756
|
+
hg.appendChild(hArea);
|
|
757
|
+
// Chevron ∨
|
|
758
|
+
var chev = createSVGEl("path");
|
|
759
|
+
chev.setAttribute("d", "M" + (hx - 5) + "," + (hy - 1) + " L" + hx + "," + (hy + 5) + " L" + (hx + 5) + "," + (hy - 1));
|
|
760
|
+
chev.setAttribute("fill", "none");
|
|
761
|
+
chev.setAttribute("stroke", "#f97316");
|
|
762
|
+
chev.setAttribute("stroke-width", "1.8");
|
|
763
|
+
chev.setAttribute("stroke-linecap", "round");
|
|
764
|
+
chev.setAttribute("stroke-linejoin", "round");
|
|
765
|
+
chev.setAttribute("pointer-events", "none");
|
|
766
|
+
hg.appendChild(chev);
|
|
767
|
+
// Trait pointillé vers la table
|
|
768
|
+
var hvl = createSVGEl("line");
|
|
769
|
+
hvl.setAttribute("x1", hx); hvl.setAttribute("y1", hy + 5);
|
|
770
|
+
hvl.setAttribute("x2", hx); hvl.setAttribute("y2", shape.y);
|
|
771
|
+
hvl.setAttribute("stroke", "#f97316");
|
|
772
|
+
hvl.setAttribute("stroke-width", "1");
|
|
773
|
+
hvl.setAttribute("stroke-dasharray", "3,2");
|
|
774
|
+
hvl.setAttribute("pointer-events", "none");
|
|
775
|
+
hg.appendChild(hvl);
|
|
776
|
+
g.appendChild(hg);
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
// Poignée de redimensionnement
|
|
780
|
+
if (isSel && selectedIds.length === 1) {
|
|
781
|
+
var tGrip = createSVGEl("rect");
|
|
782
|
+
tGrip.setAttribute("x", shape.x + shape.w - 5); tGrip.setAttribute("y", shape.y + shape.h - 5);
|
|
783
|
+
tGrip.setAttribute("width", 10); tGrip.setAttribute("height", 10);
|
|
784
|
+
tGrip.setAttribute("rx", 2);
|
|
785
|
+
tGrip.setAttribute("fill", "#f97316");
|
|
786
|
+
tGrip.setAttribute("stroke", "#fff"); tGrip.setAttribute("stroke-width", 1.5);
|
|
787
|
+
tGrip.classList.add("resize-grip");
|
|
788
|
+
tGrip.setAttribute("data-shape-id", shape.id);
|
|
789
|
+
g.appendChild(tGrip);
|
|
790
|
+
}
|
|
791
|
+
// Points de connexion
|
|
792
|
+
var thcx = shape.x + shape.w / 2, thcy = shape.y + shape.h / 2;
|
|
793
|
+
[[thcx, shape.y], [thcx, shape.y + shape.h], [shape.x, thcy], [shape.x + shape.w, thcy]]
|
|
794
|
+
.forEach(function (pt) {
|
|
795
|
+
var tdot = createSVGEl("circle");
|
|
796
|
+
tdot.setAttribute("cx", pt[0]); tdot.setAttribute("cy", pt[1]);
|
|
797
|
+
tdot.setAttribute("r", 5);
|
|
798
|
+
tdot.setAttribute("fill", "#f97316");
|
|
799
|
+
tdot.setAttribute("stroke", "#fff"); tdot.setAttribute("stroke-width", 1.5);
|
|
800
|
+
tdot.classList.add("conn-dot");
|
|
801
|
+
tdot.setAttribute("data-shape-id", shape.id);
|
|
802
|
+
g.appendChild(tdot);
|
|
803
|
+
});
|
|
804
|
+
return g;
|
|
805
|
+
|
|
453
806
|
} else if (shape.type === "postit") {
|
|
454
807
|
var fold = 18;
|
|
455
808
|
var body = createSVGEl("path");
|
|
@@ -505,7 +858,9 @@ function renderShape(shape) {
|
|
|
505
858
|
// ── Texte ──
|
|
506
859
|
var textCy = shape.type === "db"
|
|
507
860
|
? shape.y + shape.h * 0.62
|
|
508
|
-
: shape.
|
|
861
|
+
: shape.type === "actor"
|
|
862
|
+
? shape.y + shape.h - 2
|
|
863
|
+
: shape.y + shape.h / 2;
|
|
509
864
|
var ta = shape.textAlign || "center";
|
|
510
865
|
var textAnchor = ta === "left" ? "start" : ta === "right" ? "end" : "middle";
|
|
511
866
|
var txt = createSVGEl("text");
|
|
@@ -539,12 +894,30 @@ function renderShape(shape) {
|
|
|
539
894
|
ts.textContent = line || " ";
|
|
540
895
|
txt.appendChild(ts);
|
|
541
896
|
});
|
|
542
|
-
} else if (shape.type === "
|
|
543
|
-
var
|
|
544
|
-
var
|
|
897
|
+
} else if (shape.type === "actor") {
|
|
898
|
+
var alines = wrapPostitLines(shape.text || "", shape.w * 1.2, fs);
|
|
899
|
+
var alineH = Math.round(fs * 1.33);
|
|
900
|
+
txt.setAttribute("text-anchor", "middle");
|
|
901
|
+
txt.removeAttribute("dominant-baseline");
|
|
902
|
+
alines.forEach(function (line, i) {
|
|
903
|
+
var ts = createSVGEl("tspan");
|
|
904
|
+
ts.setAttribute("x", shape.x + shape.w / 2);
|
|
905
|
+
if (i > 0) ts.setAttribute("dy", alineH + "px");
|
|
906
|
+
ts.textContent = line || " ";
|
|
907
|
+
txt.appendChild(ts);
|
|
908
|
+
});
|
|
909
|
+
} else if (shape.type === "rect" || shape.type === "rounded" || shape.type === "db" || shape.type === "cloud" || shape.type === "nuage") {
|
|
910
|
+
var wpad = shape.type === "cloud" ? Math.round(shape.w * 0.2) : shape.type === "nuage" ? Math.round(shape.w * 0.12) : 12;
|
|
911
|
+
// Pour nuage : vpad = offset depuis le haut visible (y=2/16) + marge interne
|
|
912
|
+
var wvpad = shape.type === "cloud" ? Math.round(shape.h * 0.12)
|
|
913
|
+
: shape.type === "nuage" ? Math.round(shape.h * 2 / 16) + 6
|
|
914
|
+
: 8;
|
|
545
915
|
var wlines = wrapPostitLines(shape.text || "", shape.w - wpad * 2, fs);
|
|
546
916
|
var wlineH = Math.round(fs * 1.33);
|
|
547
|
-
|
|
917
|
+
// Pour nuage : centre de la forme visible = milieu entre y=2/16 et y=13/16
|
|
918
|
+
var wcenterY = shape.type === "db" ? shape.y + shape.h * 0.62
|
|
919
|
+
: shape.type === "nuage" ? shape.y + shape.h * ((2 + 13) / 2 / 16)
|
|
920
|
+
: shape.y + shape.h / 2;
|
|
548
921
|
var dbCapH = shape.type === "db" ? Math.min(12, shape.h * 0.2) * 2 : 0;
|
|
549
922
|
var wfirstY = tv === "top"
|
|
550
923
|
? shape.y + dbCapH + wvpad + wlineH * 0.5
|
|
@@ -584,6 +957,36 @@ function renderShape(shape) {
|
|
|
584
957
|
// ── Texte non rendu pour les images ──
|
|
585
958
|
if (shape.type === "image") return g;
|
|
586
959
|
|
|
960
|
+
// ── Indicateur de lien (diagramme enfant ou lien externe) ──
|
|
961
|
+
if ((shape.linkedDiagramId || shape.externalUrl) && shape.type !== "image") {
|
|
962
|
+
var lnkColor = shape.externalUrl ? "#0284c7" : "#f97316";
|
|
963
|
+
var lnkCirc = createSVGEl("circle");
|
|
964
|
+
lnkCirc.setAttribute("cx", shape.x + shape.w - 5);
|
|
965
|
+
lnkCirc.setAttribute("cy", shape.y + 5);
|
|
966
|
+
lnkCirc.setAttribute("r", 6);
|
|
967
|
+
lnkCirc.setAttribute("fill", lnkColor);
|
|
968
|
+
lnkCirc.setAttribute("stroke", "#fff");
|
|
969
|
+
lnkCirc.setAttribute("stroke-width", 1.5);
|
|
970
|
+
lnkCirc.setAttribute("pointer-events", "none");
|
|
971
|
+
g.appendChild(lnkCirc);
|
|
972
|
+
var lnkTxt = createSVGEl("text");
|
|
973
|
+
lnkTxt.setAttribute("x", shape.x + shape.w - 5);
|
|
974
|
+
lnkTxt.setAttribute("y", shape.y + 8.5);
|
|
975
|
+
lnkTxt.setAttribute("text-anchor", "middle");
|
|
976
|
+
lnkTxt.setAttribute("font-size", "8");
|
|
977
|
+
lnkTxt.setAttribute("font-weight", "bold");
|
|
978
|
+
lnkTxt.setAttribute("fill", "#fff");
|
|
979
|
+
lnkTxt.setAttribute("pointer-events", "none");
|
|
980
|
+
lnkTxt.textContent = "\u2197";
|
|
981
|
+
g.appendChild(lnkTxt);
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
// ── Rotation ──
|
|
985
|
+
if (shape.rotation) {
|
|
986
|
+
var rcx = shape.x + shape.w / 2, rcy = shape.y + shape.h / 2;
|
|
987
|
+
g.setAttribute("transform", "rotate(" + shape.rotation + "," + rcx + "," + rcy + ")");
|
|
988
|
+
}
|
|
989
|
+
|
|
587
990
|
// ── Points de connexion (visibles au hover et en mode flèche — sauf postit) ──
|
|
588
991
|
if (shape.type !== "postit") {
|
|
589
992
|
var hcx = shape.x + shape.w / 2, hcy = shape.y + shape.h / 2;
|
|
@@ -611,16 +1014,35 @@ function renderShape(shape) {
|
|
|
611
1014
|
function getEdgePoint(shape, targetX, targetY) {
|
|
612
1015
|
var cx = shape.x + shape.w / 2;
|
|
613
1016
|
var cy = shape.y + shape.h / 2;
|
|
614
|
-
var
|
|
615
|
-
if (
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
var
|
|
619
|
-
|
|
1017
|
+
var lTargetX = targetX, lTargetY = targetY;
|
|
1018
|
+
if (shape.rotation) {
|
|
1019
|
+
var rad = -shape.rotation * Math.PI / 180;
|
|
1020
|
+
var cos = Math.cos(rad), sin = Math.sin(rad);
|
|
1021
|
+
var ddx = targetX - cx, ddy = targetY - cy;
|
|
1022
|
+
lTargetX = cx + ddx * cos - ddy * sin;
|
|
1023
|
+
lTargetY = cy + ddx * sin + ddy * cos;
|
|
1024
|
+
}
|
|
1025
|
+
var dx = lTargetX - cx, dy = lTargetY - cy;
|
|
1026
|
+
var p;
|
|
1027
|
+
if (dx === 0 && dy === 0) {
|
|
1028
|
+
p = { x: cx, y: shape.y };
|
|
620
1029
|
} else {
|
|
621
|
-
var
|
|
622
|
-
|
|
1030
|
+
var hw = shape.w / 2, hh = shape.h / 2;
|
|
1031
|
+
if (Math.abs(dx) * hh > Math.abs(dy) * hw) {
|
|
1032
|
+
var sx = dx > 0 ? 1 : -1;
|
|
1033
|
+
p = { x: cx + sx * hw, y: cy + dy * hw / Math.abs(dx) };
|
|
1034
|
+
} else {
|
|
1035
|
+
var sy = dy > 0 ? 1 : -1;
|
|
1036
|
+
p = { x: cx + dx * hh / Math.abs(dy), y: cy + sy * hh };
|
|
1037
|
+
}
|
|
623
1038
|
}
|
|
1039
|
+
if (shape.rotation) {
|
|
1040
|
+
var rad2 = shape.rotation * Math.PI / 180;
|
|
1041
|
+
var cos2 = Math.cos(rad2), sin2 = Math.sin(rad2);
|
|
1042
|
+
var ex = p.x - cx, ey = p.y - cy;
|
|
1043
|
+
p = { x: cx + ex * cos2 - ey * sin2, y: cy + ex * sin2 + ey * cos2 };
|
|
1044
|
+
}
|
|
1045
|
+
return p;
|
|
624
1046
|
}
|
|
625
1047
|
|
|
626
1048
|
function renderArrow(arrow, shapes) {
|
|
@@ -705,106 +1127,97 @@ function renderAll() {
|
|
|
705
1127
|
|
|
706
1128
|
updateViewport();
|
|
707
1129
|
renderDiagramList();
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
1130
|
+
syncColorPanel();
|
|
1131
|
+
updateTableOverlay();
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
function renderDiagramListLevel(list, depth) {
|
|
1135
|
+
return (list || []).map(function (d) {
|
|
1136
|
+
var isActive = String(d.id) === String(currentDiagramId);
|
|
1137
|
+
var isExpanded = !!diagExpandedIds[String(d.id)];
|
|
1138
|
+
var hasChildren = d.children && d.children.length > 0;
|
|
1139
|
+
var indent = 10 + depth * 14;
|
|
1140
|
+
var childrenHtml = (isExpanded && hasChildren)
|
|
1141
|
+
? '<div class="diagram-list-children">' + renderDiagramListLevel(d.children, depth + 1) + '</div>'
|
|
1142
|
+
: '';
|
|
714
1143
|
return (
|
|
715
|
-
'<div class="diagram-list-
|
|
716
|
-
'
|
|
717
|
-
'
|
|
718
|
-
'
|
|
719
|
-
'
|
|
720
|
-
'
|
|
721
|
-
'
|
|
722
|
-
'<span class="diagram-list-grip">⠿</span>' +
|
|
1144
|
+
'<div class="diagram-list-group">' +
|
|
1145
|
+
'<div class="diagram-list-item' + (isActive ? ' active' : '') + '"' +
|
|
1146
|
+
' style="padding-left:' + indent + 'px"' +
|
|
1147
|
+
' onclick="selectDiagramme(\'' + d.id + '\', true)">' +
|
|
1148
|
+
'<span class="diagram-list-expand" onclick="event.stopPropagation();toggleDiagExpand(\'' + d.id + '\')">' +
|
|
1149
|
+
(hasChildren ? (isExpanded ? '▼' : '►') : '<span style="display:inline-block;width:0.7em"></span>') +
|
|
1150
|
+
'</span>' +
|
|
723
1151
|
'<span class="diagram-list-name">' + escDiag(d.titre) + '</span>' +
|
|
724
|
-
(
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
1152
|
+
'<button class="diagram-list-add" onclick="event.stopPropagation();creerEnfantDiagramme(\'' + d.id + '\')" title="Ajouter un diagramme enfant">+</button>' +
|
|
1153
|
+
(depth > 0 || list.length > 1
|
|
1154
|
+
? '<button class="diagram-list-del" onclick="event.stopPropagation();supprimerDiagramme(\'' + d.id + '\')">×</button>'
|
|
1155
|
+
: '') +
|
|
1156
|
+
'</div>' +
|
|
1157
|
+
childrenHtml +
|
|
1158
|
+
'</div>'
|
|
728
1159
|
);
|
|
729
|
-
}).join(
|
|
730
|
-
}
|
|
731
|
-
|
|
732
|
-
function onDiagDragStart(e, idx) {
|
|
733
|
-
diagDragSrcIdx = idx;
|
|
734
|
-
e.dataTransfer.effectAllowed = "move";
|
|
735
|
-
e.currentTarget.classList.add("dragging");
|
|
1160
|
+
}).join('');
|
|
736
1161
|
}
|
|
737
1162
|
|
|
738
|
-
function
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
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");
|
|
1163
|
+
function renderDiagramList() {
|
|
1164
|
+
var el = document.getElementById("diagramList");
|
|
1165
|
+
if (!el) return;
|
|
1166
|
+
el.innerHTML = renderDiagramListLevel(diagramsList, 0);
|
|
1167
|
+
updateSidebarWidth();
|
|
752
1168
|
}
|
|
753
1169
|
|
|
754
|
-
function
|
|
755
|
-
|
|
756
|
-
if (
|
|
757
|
-
|
|
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;
|
|
1170
|
+
function toggleDiagExpand(id) {
|
|
1171
|
+
var key = String(id);
|
|
1172
|
+
if (diagExpandedIds[key]) {
|
|
1173
|
+
delete diagExpandedIds[key];
|
|
771
1174
|
} else {
|
|
772
|
-
|
|
773
|
-
currentDiagramIdx = adjustedCur >= idx ? adjustedCur + 1 : adjustedCur;
|
|
1175
|
+
diagExpandedIds[key] = true;
|
|
774
1176
|
}
|
|
775
|
-
localStorage.setItem("current_diagram_idx", currentDiagramIdx);
|
|
776
|
-
diagDragSrcIdx = null;
|
|
777
|
-
saveDiagrammes();
|
|
778
1177
|
renderDiagramList();
|
|
779
1178
|
}
|
|
780
1179
|
|
|
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
1180
|
// ── Gestion des diagrammes ──
|
|
789
|
-
function selectDiagramme(
|
|
1181
|
+
function selectDiagramme(id, clearStack) {
|
|
1182
|
+
if (clearStack) diagNavStack = [];
|
|
790
1183
|
saveCurrentZoom();
|
|
791
1184
|
saveCurrentLock();
|
|
792
|
-
|
|
793
|
-
localStorage.setItem("
|
|
794
|
-
selectedId = null;
|
|
1185
|
+
currentDiagramId = String(id);
|
|
1186
|
+
localStorage.setItem("current_diagram_id", String(id));
|
|
1187
|
+
selectedId = null; selectedType = null;
|
|
795
1188
|
selectedIds = [];
|
|
796
|
-
|
|
797
|
-
|
|
1189
|
+
pendingNavDiagId = null;
|
|
1190
|
+
restoreZoomForDiagram(id);
|
|
1191
|
+
restoreLockForDiagram(id);
|
|
798
1192
|
viewTransform.x = 60;
|
|
799
1193
|
viewTransform.y = 60;
|
|
800
1194
|
document.getElementById("colorPanel").style.display = "none";
|
|
801
1195
|
renderAll();
|
|
802
1196
|
updateLockBtn();
|
|
1197
|
+
updateBackBtn();
|
|
803
1198
|
document.getElementById("diagramListPanel").classList.remove("open");
|
|
804
1199
|
}
|
|
805
1200
|
|
|
1201
|
+
function goBackDiagram() {
|
|
1202
|
+
if (diagNavStack.length === 0) return;
|
|
1203
|
+
var prevId = diagNavStack.pop();
|
|
1204
|
+
selectDiagramme(prevId);
|
|
1205
|
+
}
|
|
1206
|
+
|
|
806
1207
|
function creerDiagramme() {
|
|
1208
|
+
pendingParentId = null;
|
|
1209
|
+
pendingNewDiagram = true;
|
|
1210
|
+
var input = document.getElementById("diagramTitle");
|
|
1211
|
+
input.value = "";
|
|
1212
|
+
input.placeholder = window.t ? window.t.diag_new_diagram : "Nouveau diagramme";
|
|
1213
|
+
input.focus();
|
|
1214
|
+
input.select();
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
function creerEnfantDiagramme(parentId) {
|
|
1218
|
+
pendingParentId = String(parentId);
|
|
807
1219
|
pendingNewDiagram = true;
|
|
1220
|
+
diagExpandedIds[String(parentId)] = true;
|
|
808
1221
|
var input = document.getElementById("diagramTitle");
|
|
809
1222
|
input.value = "";
|
|
810
1223
|
input.placeholder = window.t ? window.t.diag_new_diagram : "Nouveau diagramme";
|
|
@@ -818,10 +1231,21 @@ function confirmerNouveauDiagramme() {
|
|
|
818
1231
|
var input = document.getElementById("diagramTitle");
|
|
819
1232
|
var titre = input.value.trim() || (window.t ? window.t.diag_new_diagram : "Nouveau diagramme");
|
|
820
1233
|
input.placeholder = "";
|
|
821
|
-
var d = { id: Date.now(), titre: titre, shapes: [], arrows: [] };
|
|
822
|
-
|
|
1234
|
+
var d = { id: Date.now(), titre: titre, shapes: [], arrows: [], children: [] };
|
|
1235
|
+
if (pendingParentId) {
|
|
1236
|
+
var parent = findDiagramById(pendingParentId, diagramsList);
|
|
1237
|
+
if (parent) {
|
|
1238
|
+
if (!parent.children) parent.children = [];
|
|
1239
|
+
parent.children.push(d);
|
|
1240
|
+
} else {
|
|
1241
|
+
diagramsList.push(d);
|
|
1242
|
+
}
|
|
1243
|
+
pendingParentId = null;
|
|
1244
|
+
} else {
|
|
1245
|
+
diagramsList.push(d);
|
|
1246
|
+
}
|
|
823
1247
|
saveDiagrammes();
|
|
824
|
-
selectDiagramme(
|
|
1248
|
+
selectDiagramme(d.id);
|
|
825
1249
|
document.getElementById("diagramTitle").blur();
|
|
826
1250
|
}
|
|
827
1251
|
|
|
@@ -835,17 +1259,31 @@ function annulerNouveauDiagramme() {
|
|
|
835
1259
|
input.blur();
|
|
836
1260
|
}
|
|
837
1261
|
|
|
838
|
-
function supprimerDiagramme(
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
if (
|
|
842
|
-
|
|
1262
|
+
function supprimerDiagramme(id) {
|
|
1263
|
+
var info = findParentListOf(id, diagramsList);
|
|
1264
|
+
if (!info) return;
|
|
1265
|
+
if (info.list === diagramsList && info.list.length <= 1) return;
|
|
1266
|
+
pushHistory();
|
|
1267
|
+
info.list.splice(info.index, 1);
|
|
1268
|
+
if (!findDiagramById(currentDiagramId, diagramsList)) {
|
|
1269
|
+
currentDiagramId = diagramsList[0] ? String(diagramsList[0].id) : null;
|
|
1270
|
+
localStorage.setItem("current_diagram_id", currentDiagramId || "");
|
|
1271
|
+
}
|
|
843
1272
|
saveDiagrammes();
|
|
844
1273
|
renderAll();
|
|
845
1274
|
}
|
|
846
1275
|
|
|
847
1276
|
function toggleDiagramList() {
|
|
848
|
-
document.getElementById("diagramListPanel")
|
|
1277
|
+
var panel = document.getElementById("diagramListPanel");
|
|
1278
|
+
var isOpening = !panel.classList.contains("open");
|
|
1279
|
+
panel.classList.toggle("open");
|
|
1280
|
+
if (isOpening && currentDiagramId) {
|
|
1281
|
+
var path = getAncestorPath(currentDiagramId, diagramsList);
|
|
1282
|
+
if (path) {
|
|
1283
|
+
path.forEach(function (id) { diagExpandedIds[id] = true; });
|
|
1284
|
+
renderDiagramList();
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
849
1287
|
}
|
|
850
1288
|
|
|
851
1289
|
// ── Outil actif ──
|
|
@@ -873,9 +1311,9 @@ function setTool(tool) {
|
|
|
873
1311
|
|
|
874
1312
|
// ── Ajout d'une forme ──
|
|
875
1313
|
function addShape(type, x, y) {
|
|
1314
|
+
pushHistory();
|
|
876
1315
|
var diag = getCurrentDiagram();
|
|
877
1316
|
var size = DEFAULT_SIZES[type] || { w: 120, h: 50 };
|
|
878
|
-
var labels = { db: "Database", cloud: "Cloud", text: "Label", postit: "" };
|
|
879
1317
|
var shape = {
|
|
880
1318
|
id: "s" + Date.now(),
|
|
881
1319
|
type: type,
|
|
@@ -883,21 +1321,30 @@ function addShape(type, x, y) {
|
|
|
883
1321
|
y: Math.round(y - size.h / 2),
|
|
884
1322
|
w: size.w,
|
|
885
1323
|
h: size.h,
|
|
886
|
-
text:
|
|
1324
|
+
text: "",
|
|
887
1325
|
color: type === "postit" ? "t-amber" : DEFAULT_COLOR,
|
|
888
1326
|
};
|
|
1327
|
+
if (type === "table") {
|
|
1328
|
+
shape.rows = 3; shape.cols = 3;
|
|
1329
|
+
shape.cells = [["","",""],["","",""],["","",""]];
|
|
1330
|
+
var initW = shape.w / 3;
|
|
1331
|
+
shape.colWidths = [initW, initW, initW];
|
|
1332
|
+
}
|
|
889
1333
|
diag.shapes.push(shape);
|
|
890
1334
|
saveDiagrammes();
|
|
891
1335
|
selectedId = shape.id; selectedType = "shape";
|
|
892
1336
|
selectedIds = [shape.id];
|
|
893
1337
|
renderAll();
|
|
894
1338
|
document.getElementById("colorPanel").style.display = "flex";
|
|
895
|
-
|
|
1339
|
+
syncColorPanel();
|
|
1340
|
+
if (type !== "table") startTextEdit(shape.id);
|
|
896
1341
|
}
|
|
897
1342
|
|
|
898
1343
|
// ── Suppression ──
|
|
899
1344
|
function deleteSelected() {
|
|
900
1345
|
var diag = getCurrentDiagram();
|
|
1346
|
+
if (selectedIds.length === 0 && !(selectedId && selectedType === "arrow")) return;
|
|
1347
|
+
pushHistory();
|
|
901
1348
|
if (selectedIds.length > 0) {
|
|
902
1349
|
diag.shapes = diag.shapes.filter(function (s) { return selectedIds.indexOf(s.id) === -1; });
|
|
903
1350
|
diag.arrows = diag.arrows.filter(function (a) {
|
|
@@ -919,6 +1366,7 @@ function deleteSelected() {
|
|
|
919
1366
|
// ── Couleur ──
|
|
920
1367
|
function setShapeColor(color) {
|
|
921
1368
|
if (selectedIds.length === 0) return;
|
|
1369
|
+
pushHistory();
|
|
922
1370
|
var diag = getCurrentDiagram();
|
|
923
1371
|
selectedIds.forEach(function (id) {
|
|
924
1372
|
var shape = diag.shapes.find(function (s) { return s.id === id; });
|
|
@@ -931,6 +1379,7 @@ function setShapeColor(color) {
|
|
|
931
1379
|
|
|
932
1380
|
function setShapeTextValign(valign) {
|
|
933
1381
|
if (selectedIds.length === 0) return;
|
|
1382
|
+
pushHistory();
|
|
934
1383
|
var diag = getCurrentDiagram();
|
|
935
1384
|
selectedIds.forEach(function (id) {
|
|
936
1385
|
var shape = diag.shapes.find(function (s) { return s.id === id; });
|
|
@@ -943,6 +1392,7 @@ function setShapeTextValign(valign) {
|
|
|
943
1392
|
|
|
944
1393
|
function setShapeTextAlign(align) {
|
|
945
1394
|
if (selectedIds.length === 0) return;
|
|
1395
|
+
pushHistory();
|
|
946
1396
|
var diag = getCurrentDiagram();
|
|
947
1397
|
selectedIds.forEach(function (id) {
|
|
948
1398
|
var shape = diag.shapes.find(function (s) { return s.id === id; });
|
|
@@ -955,6 +1405,7 @@ function setShapeTextAlign(align) {
|
|
|
955
1405
|
|
|
956
1406
|
function changeShapeFontSize(delta) {
|
|
957
1407
|
if (selectedIds.length === 0) return;
|
|
1408
|
+
pushHistory();
|
|
958
1409
|
var diag = getCurrentDiagram();
|
|
959
1410
|
selectedIds.forEach(function (id) {
|
|
960
1411
|
var shape = diag.shapes.find(function (s) { return s.id === id; });
|
|
@@ -985,6 +1436,7 @@ function cancelPickMode() {
|
|
|
985
1436
|
}
|
|
986
1437
|
|
|
987
1438
|
function applyPickMode(srcShape) {
|
|
1439
|
+
pushHistory();
|
|
988
1440
|
var diag = getCurrentDiagram();
|
|
989
1441
|
pickTargetIds.forEach(function (id) {
|
|
990
1442
|
if (id === srcShape.id) return;
|
|
@@ -1000,6 +1452,7 @@ function applyPickMode(srcShape) {
|
|
|
1000
1452
|
shape.h = srcShape.h;
|
|
1001
1453
|
shape.textAlign = srcShape.textAlign || "center";
|
|
1002
1454
|
shape.textValign = srcShape.textValign || "middle";
|
|
1455
|
+
shape.rotation = srcShape.rotation || 0;
|
|
1003
1456
|
}
|
|
1004
1457
|
});
|
|
1005
1458
|
selectedIds = pickTargetIds.slice();
|
|
@@ -1008,11 +1461,48 @@ function applyPickMode(srcShape) {
|
|
|
1008
1461
|
document.getElementById("colorPanel").style.display = "flex";
|
|
1009
1462
|
}
|
|
1010
1463
|
|
|
1464
|
+
function rotateShape(delta) {
|
|
1465
|
+
var diag = getCurrentDiagram();
|
|
1466
|
+
if (!diag || selectedIds.length === 0) return;
|
|
1467
|
+
pushHistory();
|
|
1468
|
+
selectedIds.forEach(function (id) {
|
|
1469
|
+
var s = diag.shapes.find(function (sh) { return sh.id === id; });
|
|
1470
|
+
if (!s || s.type === "image") return;
|
|
1471
|
+
s.rotation = ((s.rotation || 0) + delta + 360) % 360;
|
|
1472
|
+
});
|
|
1473
|
+
saveDiagrammes();
|
|
1474
|
+
renderAll();
|
|
1475
|
+
}
|
|
1476
|
+
|
|
1477
|
+
function changeShapeOrder(delta) {
|
|
1478
|
+
var diag = getCurrentDiagram();
|
|
1479
|
+
if (!diag || selectedIds.length === 0) return;
|
|
1480
|
+
pushHistory();
|
|
1481
|
+
if (delta > 0) {
|
|
1482
|
+
// Avancer : parcourir de la fin vers le début
|
|
1483
|
+
for (var i = diag.shapes.length - 1; i >= 0; i--) {
|
|
1484
|
+
if (selectedIds.indexOf(diag.shapes[i].id) !== -1 && i < diag.shapes.length - 1) {
|
|
1485
|
+
var tmp = diag.shapes[i]; diag.shapes[i] = diag.shapes[i + 1]; diag.shapes[i + 1] = tmp;
|
|
1486
|
+
}
|
|
1487
|
+
}
|
|
1488
|
+
} else {
|
|
1489
|
+
// Reculer : parcourir du début vers la fin
|
|
1490
|
+
for (var i = 0; i < diag.shapes.length; i++) {
|
|
1491
|
+
if (selectedIds.indexOf(diag.shapes[i].id) !== -1 && i > 0) {
|
|
1492
|
+
var tmp = diag.shapes[i]; diag.shapes[i] = diag.shapes[i - 1]; diag.shapes[i - 1] = tmp;
|
|
1493
|
+
}
|
|
1494
|
+
}
|
|
1495
|
+
}
|
|
1496
|
+
saveDiagrammes();
|
|
1497
|
+
renderAll();
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1011
1500
|
// ── Édition texte inline ──
|
|
1012
1501
|
function startTextEdit(shapeId) {
|
|
1013
1502
|
var diag = getCurrentDiagram();
|
|
1014
1503
|
var shape = diag.shapes.find(function (s) { return s.id === shapeId; });
|
|
1015
1504
|
if (!shape || shape.type === "image") return;
|
|
1505
|
+
if (shape.type === "table") return;
|
|
1016
1506
|
editingShapeId = shapeId;
|
|
1017
1507
|
|
|
1018
1508
|
var svg = document.getElementById("canvas");
|
|
@@ -1023,15 +1513,21 @@ function startTextEdit(shapeId) {
|
|
|
1023
1513
|
var sh = shape.h * viewTransform.scale;
|
|
1024
1514
|
var c = COLORS[shape.color] || COLORS[DEFAULT_COLOR];
|
|
1025
1515
|
|
|
1026
|
-
var useTextarea = shape.type === "postit" || shape.type === "rect" || shape.type === "rounded" || shape.type === "db" || shape.type === "cloud";
|
|
1516
|
+
var useTextarea = shape.type === "postit" || shape.type === "rect" || shape.type === "rounded" || shape.type === "db" || shape.type === "cloud" || shape.type === "nuage";
|
|
1027
1517
|
if (useTextarea) {
|
|
1028
1518
|
var ta = document.getElementById("postitTextInput");
|
|
1029
1519
|
ta.value = shape.text || "";
|
|
1030
|
-
var hpad = shape.type === "cloud" ? Math.round(sw * 0.15) : 8;
|
|
1520
|
+
var hpad = shape.type === "cloud" ? Math.round(sw * 0.15) : shape.type === "nuage" ? Math.round(sw * 0.14) : 8;
|
|
1031
1521
|
var vtop, vheight;
|
|
1032
1522
|
if (shape.type === "cloud") {
|
|
1033
1523
|
vtop = sy + Math.round(sh * 0.12);
|
|
1034
1524
|
vheight = sh - Math.round(sh * 0.12) * 2;
|
|
1525
|
+
} else if (shape.type === "nuage") {
|
|
1526
|
+
// Le nuage Bootstrap : haut visible ≈ y=2/16, bas de la zone texte ≈ y=10/16
|
|
1527
|
+
var nuageTop = Math.round(sh * 2 / 16);
|
|
1528
|
+
var nuageBot = Math.round(sh * 10 / 16);
|
|
1529
|
+
vtop = sy + nuageTop;
|
|
1530
|
+
vheight = nuageBot - nuageTop - 4;
|
|
1035
1531
|
} else if (shape.type === "db") {
|
|
1036
1532
|
var dbRy = Math.min(12, shape.h * 0.2) * viewTransform.scale;
|
|
1037
1533
|
vtop = sy + dbRy * 2 + 4;
|
|
@@ -1057,9 +1553,15 @@ function startTextEdit(shapeId) {
|
|
|
1057
1553
|
} else {
|
|
1058
1554
|
var input = document.getElementById("shapeTextInput");
|
|
1059
1555
|
input.value = shape.text || "";
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1556
|
+
if (shape.type === "actor") {
|
|
1557
|
+
input.style.left = sx + "px";
|
|
1558
|
+
input.style.top = (sy + sh * 0.84) + "px";
|
|
1559
|
+
input.style.width = sw + "px";
|
|
1560
|
+
} else {
|
|
1561
|
+
input.style.left = (sx + sw * 0.08) + "px";
|
|
1562
|
+
input.style.top = (sy + sh * 0.22) + "px";
|
|
1563
|
+
input.style.width = (sw * 0.84) + "px";
|
|
1564
|
+
}
|
|
1063
1565
|
var editFs2 = shape.fontSize || 13;
|
|
1064
1566
|
input.style.fontSize = Math.max(10, editFs2 * viewTransform.scale) + "px";
|
|
1065
1567
|
input.style.color = c.text;
|
|
@@ -1076,18 +1578,32 @@ function confirmTextEdit() {
|
|
|
1076
1578
|
if (editingShapeId) {
|
|
1077
1579
|
var shape = diag.shapes.find(function (s) { return s.id === editingShapeId; });
|
|
1078
1580
|
if (shape) {
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1581
|
+
if (shape.type === "table" && editingTableCell !== null) {
|
|
1582
|
+
var tval = document.getElementById("postitTextInput").value;
|
|
1583
|
+
var oldCellVal = (shape.cells && shape.cells[editingTableCell.row] && shape.cells[editingTableCell.row][editingTableCell.col]) || "";
|
|
1584
|
+
if (tval !== oldCellVal) pushHistory();
|
|
1585
|
+
if (!shape.cells) shape.cells = [];
|
|
1586
|
+
while (shape.cells.length <= editingTableCell.row) shape.cells.push([]);
|
|
1587
|
+
shape.cells[editingTableCell.row][editingTableCell.col] = tval;
|
|
1588
|
+
editingTableCell = null;
|
|
1589
|
+
} else {
|
|
1590
|
+
var usesTextarea = shape.type === "postit" || shape.type === "rect" || shape.type === "rounded" || shape.type === "db" || shape.type === "cloud" || shape.type === "nuage";
|
|
1591
|
+
var val = usesTextarea
|
|
1592
|
+
? document.getElementById("postitTextInput").value
|
|
1593
|
+
: input.value;
|
|
1594
|
+
if (val !== (shape.text || "")) pushHistory();
|
|
1595
|
+
shape.text = val;
|
|
1596
|
+
}
|
|
1084
1597
|
saveDiagrammes();
|
|
1085
1598
|
renderAll();
|
|
1086
1599
|
}
|
|
1087
1600
|
editingShapeId = null;
|
|
1088
1601
|
} else if (editingArrowId) {
|
|
1089
1602
|
var arrow = (diag.arrows || []).find(function (a) { return a.id === editingArrowId; });
|
|
1090
|
-
if (arrow) {
|
|
1603
|
+
if (arrow) {
|
|
1604
|
+
if (input.value !== (arrow.label || "")) pushHistory();
|
|
1605
|
+
arrow.label = input.value; saveDiagrammes(); renderAll();
|
|
1606
|
+
}
|
|
1091
1607
|
editingArrowId = null;
|
|
1092
1608
|
}
|
|
1093
1609
|
document.getElementById("textOverlay").style.display = "none";
|
|
@@ -1095,6 +1611,285 @@ function confirmTextEdit() {
|
|
|
1095
1611
|
document.getElementById("shapeTextInput").style.display = "block";
|
|
1096
1612
|
}
|
|
1097
1613
|
|
|
1614
|
+
function startTableCellEdit(shapeId, row, col) {
|
|
1615
|
+
var diag = getCurrentDiagram();
|
|
1616
|
+
var shape = diag.shapes.find(function (s) { return s.id === shapeId; });
|
|
1617
|
+
if (!shape) return;
|
|
1618
|
+
editingShapeId = shapeId;
|
|
1619
|
+
editingTableCell = { row: row, col: col };
|
|
1620
|
+
|
|
1621
|
+
var tRows = shape.rows || 3;
|
|
1622
|
+
var scColWidths = getColWidths(shape);
|
|
1623
|
+
var scColOffsets = getColOffsets(shape);
|
|
1624
|
+
var cellW = scColWidths[col], cellH = shape.h / tRows;
|
|
1625
|
+
var cellX = shape.x + scColOffsets[col], cellY = shape.y + row * cellH;
|
|
1626
|
+
|
|
1627
|
+
var svgEl = document.getElementById("canvas");
|
|
1628
|
+
var sr = svgEl.getBoundingClientRect();
|
|
1629
|
+
var csx = cellX * viewTransform.scale + viewTransform.x + sr.left;
|
|
1630
|
+
var csy = cellY * viewTransform.scale + viewTransform.y + sr.top;
|
|
1631
|
+
var csw = cellW * viewTransform.scale, csh = cellH * viewTransform.scale;
|
|
1632
|
+
var cc = COLORS[shape.color] || COLORS[DEFAULT_COLOR];
|
|
1633
|
+
|
|
1634
|
+
var ta = document.getElementById("postitTextInput");
|
|
1635
|
+
ta.value = (shape.cells && shape.cells[row] && shape.cells[row][col]) || "";
|
|
1636
|
+
ta.style.left = (csx + 2) + "px";
|
|
1637
|
+
ta.style.top = (csy + 2) + "px";
|
|
1638
|
+
ta.style.width = (csw - 4) + "px";
|
|
1639
|
+
ta.style.height = (csh - 4) + "px";
|
|
1640
|
+
var tfs = shape.fontSize || 12;
|
|
1641
|
+
ta.style.fontSize = Math.max(10, tfs * viewTransform.scale) + "px";
|
|
1642
|
+
ta.style.color = cc.text;
|
|
1643
|
+
ta.style.display = "block";
|
|
1644
|
+
document.getElementById("shapeTextInput").style.display = "none";
|
|
1645
|
+
document.getElementById("textOverlay").style.display = "block";
|
|
1646
|
+
var sg = document.querySelector('#shapesLayer [data-id="' + shapeId + '"]');
|
|
1647
|
+
if (sg) { var cellTx = sg.querySelector('text[data-cell="' + row + '-' + col + '"]'); if (cellTx) cellTx.style.visibility = "hidden"; }
|
|
1648
|
+
setTimeout(function () { ta.focus(); ta.select(); }, 10);
|
|
1649
|
+
}
|
|
1650
|
+
|
|
1651
|
+
function addTableRow() {
|
|
1652
|
+
var diag = getCurrentDiagram();
|
|
1653
|
+
var shape = selectedIds.length === 1 ? diag.shapes.find(function (s) { return s.id === selectedIds[0]; }) : null;
|
|
1654
|
+
if (!shape || shape.type !== "table") return;
|
|
1655
|
+
pushHistory();
|
|
1656
|
+
shape.rows = (shape.rows || 3) + 1;
|
|
1657
|
+
if (!shape.cells) shape.cells = [];
|
|
1658
|
+
while (shape.cells.length < shape.rows) shape.cells.push([]);
|
|
1659
|
+
shape.cells[shape.rows - 1] = new Array(shape.cols || 3).fill("");
|
|
1660
|
+
saveDiagrammes(); renderAll();
|
|
1661
|
+
document.getElementById("colorPanel").style.display = "flex";
|
|
1662
|
+
syncColorPanel();
|
|
1663
|
+
}
|
|
1664
|
+
|
|
1665
|
+
function removeTableRow() {
|
|
1666
|
+
var diag = getCurrentDiagram();
|
|
1667
|
+
var shape = selectedIds.length === 1 ? diag.shapes.find(function (s) { return s.id === selectedIds[0]; }) : null;
|
|
1668
|
+
if (!shape || shape.type !== "table" || (shape.rows || 3) <= 1) return;
|
|
1669
|
+
pushHistory();
|
|
1670
|
+
shape.rows = (shape.rows || 3) - 1;
|
|
1671
|
+
if (shape.cells && shape.cells.length > shape.rows) shape.cells.splice(shape.rows);
|
|
1672
|
+
saveDiagrammes(); renderAll();
|
|
1673
|
+
document.getElementById("colorPanel").style.display = "flex";
|
|
1674
|
+
syncColorPanel();
|
|
1675
|
+
}
|
|
1676
|
+
|
|
1677
|
+
function addTableCol() {
|
|
1678
|
+
var diag = getCurrentDiagram();
|
|
1679
|
+
var shape = selectedIds.length === 1 ? diag.shapes.find(function (s) { return s.id === selectedIds[0]; }) : null;
|
|
1680
|
+
if (!shape || shape.type !== "table") return;
|
|
1681
|
+
pushHistory();
|
|
1682
|
+
var oldCols = shape.cols || 3;
|
|
1683
|
+
shape.cols = oldCols + 1;
|
|
1684
|
+
if (!shape.cells) shape.cells = [];
|
|
1685
|
+
var rows = shape.rows || 3;
|
|
1686
|
+
for (var ri = 0; ri < rows; ri++) {
|
|
1687
|
+
if (!shape.cells[ri]) shape.cells[ri] = [];
|
|
1688
|
+
shape.cells[ri].push("");
|
|
1689
|
+
}
|
|
1690
|
+
// Répartir la largeur : réduire les colonnes existantes proportionnellement
|
|
1691
|
+
var oldWidths = getColWidths({ cols: oldCols, w: shape.w, colWidths: shape.colWidths });
|
|
1692
|
+
var newColW = shape.w / shape.cols;
|
|
1693
|
+
var scale = (shape.w - newColW) / shape.w;
|
|
1694
|
+
shape.colWidths = oldWidths.map(function (w) { return w * scale; });
|
|
1695
|
+
shape.colWidths.push(newColW);
|
|
1696
|
+
saveDiagrammes(); renderAll();
|
|
1697
|
+
document.getElementById("colorPanel").style.display = "flex";
|
|
1698
|
+
syncColorPanel();
|
|
1699
|
+
}
|
|
1700
|
+
|
|
1701
|
+
function removeTableCol() {
|
|
1702
|
+
var diag = getCurrentDiagram();
|
|
1703
|
+
var shape = selectedIds.length === 1 ? diag.shapes.find(function (s) { return s.id === selectedIds[0]; }) : null;
|
|
1704
|
+
if (!shape || shape.type !== "table" || (shape.cols || 3) <= 1) return;
|
|
1705
|
+
pushHistory();
|
|
1706
|
+
var oldCols = shape.cols || 3;
|
|
1707
|
+
shape.cols = oldCols - 1;
|
|
1708
|
+
if (shape.cells) shape.cells.forEach(function (row) { if (row.length > shape.cols) row.splice(shape.cols); });
|
|
1709
|
+
// Retirer la dernière colonne et rescaler les restantes pour remplir shape.w
|
|
1710
|
+
var cw = getColWidths({ cols: oldCols, w: shape.w, colWidths: shape.colWidths });
|
|
1711
|
+
cw.splice(shape.cols);
|
|
1712
|
+
var total = cw.reduce(function (s, v) { return s + v; }, 0);
|
|
1713
|
+
shape.colWidths = cw.map(function (w) { return w * shape.w / total; });
|
|
1714
|
+
saveDiagrammes(); renderAll();
|
|
1715
|
+
document.getElementById("colorPanel").style.display = "flex";
|
|
1716
|
+
syncColorPanel();
|
|
1717
|
+
}
|
|
1718
|
+
|
|
1719
|
+
// ── Overlay tableau (boutons +/− ligne/colonne sur le canvas) ──
|
|
1720
|
+
function updateTableOverlay() {
|
|
1721
|
+
var ids = ["tcBtnAddCol","tcBtnRemoveCol","tcBtnAddRow","tcBtnRemoveRow"];
|
|
1722
|
+
var hide = function () { ids.forEach(function (id) { var el = document.getElementById(id); if (el) el.style.display = "none"; }); };
|
|
1723
|
+
if (boardLocked || selectedIds.length !== 1 || selectedType !== "shape") { hide(); return; }
|
|
1724
|
+
var diag = getCurrentDiagram();
|
|
1725
|
+
if (!diag) { hide(); return; }
|
|
1726
|
+
var shape = diag.shapes.find(function (s) { return s.id === selectedIds[0]; });
|
|
1727
|
+
if (!shape || shape.type !== "table") { hide(); return; }
|
|
1728
|
+
|
|
1729
|
+
var svg = document.getElementById("canvas");
|
|
1730
|
+
var sr = svg.getBoundingClientRect();
|
|
1731
|
+
var sc = viewTransform.scale;
|
|
1732
|
+
var tx = viewTransform.x, ty = viewTransform.y;
|
|
1733
|
+
var right = (shape.x + shape.w) * sc + tx + sr.left;
|
|
1734
|
+
var bottom = (shape.y + shape.h) * sc + ty + sr.top;
|
|
1735
|
+
var rows = shape.rows || 3;
|
|
1736
|
+
var cellH = shape.h * sc / rows;
|
|
1737
|
+
var BW = 22, GAP = 4;
|
|
1738
|
+
|
|
1739
|
+
// +/− col : à droite de la dernière ligne, centrés verticalement dans cette ligne
|
|
1740
|
+
var lastRowCy = bottom - cellH / 2;
|
|
1741
|
+
var ac = document.getElementById("tcBtnAddCol");
|
|
1742
|
+
var rc = document.getElementById("tcBtnRemoveCol");
|
|
1743
|
+
ac.style.left = (right + GAP) + "px"; ac.style.top = (lastRowCy - BW - 2) + "px";
|
|
1744
|
+
rc.style.left = (right + GAP) + "px"; rc.style.top = (lastRowCy + 2) + "px";
|
|
1745
|
+
ac.style.display = rc.style.display = "flex";
|
|
1746
|
+
|
|
1747
|
+
// +/− row : sous la dernière ligne, alignés à droite de la table
|
|
1748
|
+
var ar = document.getElementById("tcBtnAddRow");
|
|
1749
|
+
var rr = document.getElementById("tcBtnRemoveRow");
|
|
1750
|
+
ar.style.left = (right - BW * 2 - GAP) + "px"; ar.style.top = (bottom + GAP) + "px";
|
|
1751
|
+
rr.style.left = (right - BW - 2) + "px"; rr.style.top = (bottom + GAP) + "px";
|
|
1752
|
+
ar.style.display = rr.style.display = "flex";
|
|
1753
|
+
}
|
|
1754
|
+
|
|
1755
|
+
function syncColorPanel() {
|
|
1756
|
+
var btn = document.getElementById("btnShapeLink");
|
|
1757
|
+
if (!btn) return;
|
|
1758
|
+
if (selectedIds.length === 1 && selectedType === "shape") {
|
|
1759
|
+
var diag = getCurrentDiagram();
|
|
1760
|
+
var shape = diag ? diag.shapes.find(function (s) { return s.id === selectedIds[0]; }) : null;
|
|
1761
|
+
if (shape && shape.linkedDiagramId) {
|
|
1762
|
+
btn.classList.add("active");
|
|
1763
|
+
var linked = findDiagramById(shape.linkedDiagramId, diagramsList);
|
|
1764
|
+
btn.title = linked ? "Lié à : " + linked.titre + " (cliquer pour délier)" : "Supprimer le lien";
|
|
1765
|
+
} else if (shape && shape.externalUrl) {
|
|
1766
|
+
btn.classList.add("active");
|
|
1767
|
+
btn.title = "Lien externe : " + shape.externalUrl + " (cliquer pour délier)";
|
|
1768
|
+
} else {
|
|
1769
|
+
btn.classList.remove("active");
|
|
1770
|
+
btn.title = "Lier à un diagramme enfant ou URL externe";
|
|
1771
|
+
}
|
|
1772
|
+
btn.style.display = "";
|
|
1773
|
+
} else {
|
|
1774
|
+
btn.classList.remove("active");
|
|
1775
|
+
btn.style.display = "none";
|
|
1776
|
+
}
|
|
1777
|
+
}
|
|
1778
|
+
|
|
1779
|
+
function toggleShapeLink() {
|
|
1780
|
+
if (selectedIds.length !== 1 || selectedType !== "shape") return;
|
|
1781
|
+
var diag = getCurrentDiagram();
|
|
1782
|
+
var shape = diag ? diag.shapes.find(function (s) { return s.id === selectedIds[0]; }) : null;
|
|
1783
|
+
if (!shape) return;
|
|
1784
|
+
if (shape.linkedDiagramId || shape.externalUrl) {
|
|
1785
|
+
pushHistory();
|
|
1786
|
+
delete shape.linkedDiagramId;
|
|
1787
|
+
delete shape.externalUrl;
|
|
1788
|
+
saveDiagrammes();
|
|
1789
|
+
renderAll();
|
|
1790
|
+
syncColorPanel();
|
|
1791
|
+
hideLinkPicker();
|
|
1792
|
+
} else {
|
|
1793
|
+
showLinkPicker();
|
|
1794
|
+
}
|
|
1795
|
+
}
|
|
1796
|
+
|
|
1797
|
+
function showLinkPicker() {
|
|
1798
|
+
var panel = document.getElementById("linkPickerPanel");
|
|
1799
|
+
if (!panel) return;
|
|
1800
|
+
var btn = document.getElementById("btnShapeLink");
|
|
1801
|
+
var r = btn ? btn.getBoundingClientRect() : { bottom: 100, left: 200 };
|
|
1802
|
+
panel.style.top = (r.bottom + 6) + "px";
|
|
1803
|
+
panel.style.left = r.left + "px";
|
|
1804
|
+
var all = flattenDiagrams(diagramsList);
|
|
1805
|
+
var curId = String(currentDiagramId);
|
|
1806
|
+
var items = all.filter(function (d) { return String(d.id) !== curId; });
|
|
1807
|
+
var html = items.map(function (d) {
|
|
1808
|
+
return '<div class="link-picker-item" onclick="lierForme(\'' + d.id + '\')">' + escDiag(d.titre) + '</div>';
|
|
1809
|
+
}).join("");
|
|
1810
|
+
html += '<div class="link-picker-new" onclick="creerEnfantEtLier()">+ Nouveau diagramme enfant</div>';
|
|
1811
|
+
html += '<div class="link-picker-new link-picker-ext-toggle" onclick="showExternalLinkInput()" style="display:flex;align-items:center;gap:6px;">'
|
|
1812
|
+
+ '<svg width="14" height="14" viewBox="0 0 14 14"><circle cx="7" cy="7" r="6" fill="#0284c7" stroke="#fff" stroke-width="1.5"/><text x="7" y="10.5" text-anchor="middle" font-size="8" font-weight="bold" fill="#fff">\u2197</text></svg>'
|
|
1813
|
+
+ 'Lien externe (URL)</div>';
|
|
1814
|
+
html += '<div id="linkPickerExtRow" style="display:none;padding:6px 8px;border-top:1px solid #e7e5e4;">'
|
|
1815
|
+
+ '<input id="linkPickerExtUrl" type="text" placeholder="https://..." '
|
|
1816
|
+
+ 'style="width:100%;box-sizing:border-box;padding:4px 6px;border:1px solid #d4d4d4;border-radius:4px;font-size:12px;" '
|
|
1817
|
+
+ 'onkeydown="if(event.key===\'Enter\')lierFormeExterne()">'
|
|
1818
|
+
+ '<div id="linkPickerExtErr" style="color:#e11d48;font-size:11px;margin-top:3px;display:none;">URL invalide (http://, https:// ou file://)</div>'
|
|
1819
|
+
+ '</div>';
|
|
1820
|
+
document.getElementById("linkPickerList").innerHTML = html;
|
|
1821
|
+
panel.style.display = "block";
|
|
1822
|
+
}
|
|
1823
|
+
|
|
1824
|
+
function showExternalLinkInput() {
|
|
1825
|
+
var row = document.getElementById("linkPickerExtRow");
|
|
1826
|
+
if (!row) return;
|
|
1827
|
+
row.style.display = "block";
|
|
1828
|
+
var input = document.getElementById("linkPickerExtUrl");
|
|
1829
|
+
if (input) setTimeout(function () { input.focus(); }, 10);
|
|
1830
|
+
}
|
|
1831
|
+
|
|
1832
|
+
function lierFormeExterne() {
|
|
1833
|
+
var input = document.getElementById("linkPickerExtUrl");
|
|
1834
|
+
if (!input) return;
|
|
1835
|
+
var url = input.value.trim();
|
|
1836
|
+
var err = document.getElementById("linkPickerExtErr");
|
|
1837
|
+
if (!/^(https?|file):\/\//i.test(url)) {
|
|
1838
|
+
if (err) err.style.display = "block";
|
|
1839
|
+
return;
|
|
1840
|
+
}
|
|
1841
|
+
if (err) err.style.display = "none";
|
|
1842
|
+
if (selectedIds.length !== 1) return;
|
|
1843
|
+
var diag = getCurrentDiagram();
|
|
1844
|
+
var shape = diag ? diag.shapes.find(function (s) { return s.id === selectedIds[0]; }) : null;
|
|
1845
|
+
if (!shape) return;
|
|
1846
|
+
pushHistory();
|
|
1847
|
+
shape.externalUrl = url;
|
|
1848
|
+
delete shape.linkedDiagramId;
|
|
1849
|
+
hideLinkPicker();
|
|
1850
|
+
saveDiagrammes();
|
|
1851
|
+
renderAll();
|
|
1852
|
+
syncColorPanel();
|
|
1853
|
+
}
|
|
1854
|
+
|
|
1855
|
+
function hideLinkPicker() {
|
|
1856
|
+
var panel = document.getElementById("linkPickerPanel");
|
|
1857
|
+
if (panel) panel.style.display = "none";
|
|
1858
|
+
}
|
|
1859
|
+
|
|
1860
|
+
function lierForme(diagId) {
|
|
1861
|
+
if (selectedIds.length !== 1) return;
|
|
1862
|
+
var diag = getCurrentDiagram();
|
|
1863
|
+
var shape = diag ? diag.shapes.find(function (s) { return s.id === selectedIds[0]; }) : null;
|
|
1864
|
+
if (!shape) return;
|
|
1865
|
+
pushHistory();
|
|
1866
|
+
shape.linkedDiagramId = String(diagId);
|
|
1867
|
+
delete shape.externalUrl;
|
|
1868
|
+
hideLinkPicker();
|
|
1869
|
+
saveDiagrammes();
|
|
1870
|
+
renderAll();
|
|
1871
|
+
syncColorPanel();
|
|
1872
|
+
}
|
|
1873
|
+
|
|
1874
|
+
function creerEnfantEtLier() {
|
|
1875
|
+
if (selectedIds.length !== 1) return;
|
|
1876
|
+
var diag = getCurrentDiagram();
|
|
1877
|
+
var shape = diag ? diag.shapes.find(function (s) { return s.id === selectedIds[0]; }) : null;
|
|
1878
|
+
if (!shape) return;
|
|
1879
|
+
hideLinkPicker();
|
|
1880
|
+
var titre = shape.text || "Sous-diagramme";
|
|
1881
|
+
var child = { id: Date.now(), titre: titre, shapes: [], arrows: [], children: [] };
|
|
1882
|
+
if (!diag.children) diag.children = [];
|
|
1883
|
+
diag.children.push(child);
|
|
1884
|
+
diagExpandedIds[String(diag.id)] = true;
|
|
1885
|
+
pushHistory();
|
|
1886
|
+
shape.linkedDiagramId = String(child.id);
|
|
1887
|
+
saveDiagrammes();
|
|
1888
|
+
renderAll();
|
|
1889
|
+
renderDiagramList();
|
|
1890
|
+
syncColorPanel();
|
|
1891
|
+
}
|
|
1892
|
+
|
|
1098
1893
|
function startArrowTextEdit(arrowId) {
|
|
1099
1894
|
var diag = getCurrentDiagram();
|
|
1100
1895
|
var arrow = (diag.arrows || []).find(function (a) { return a.id === arrowId; });
|
|
@@ -1134,7 +1929,16 @@ function shapeAt(x, y) {
|
|
|
1134
1929
|
if (!diag) return null;
|
|
1135
1930
|
for (var i = diag.shapes.length - 1; i >= 0; i--) {
|
|
1136
1931
|
var s = diag.shapes[i];
|
|
1137
|
-
|
|
1932
|
+
var lx = x, ly = y;
|
|
1933
|
+
if (s.rotation) {
|
|
1934
|
+
var scx = s.x + s.w / 2, scy = s.y + s.h / 2;
|
|
1935
|
+
var rad = -s.rotation * Math.PI / 180;
|
|
1936
|
+
var cos = Math.cos(rad), sin = Math.sin(rad);
|
|
1937
|
+
var ddx = x - scx, ddy = y - scy;
|
|
1938
|
+
lx = scx + ddx * cos - ddy * sin;
|
|
1939
|
+
ly = scy + ddx * sin + ddy * cos;
|
|
1940
|
+
}
|
|
1941
|
+
if (lx >= s.x && lx <= s.x + s.w && ly >= s.y && ly <= s.y + s.h) return s;
|
|
1138
1942
|
}
|
|
1139
1943
|
return null;
|
|
1140
1944
|
}
|
|
@@ -1166,6 +1970,7 @@ function distToSeg(px, py, x1, y1, x2, y2) {
|
|
|
1166
1970
|
function createArrow(fromId, toId) {
|
|
1167
1971
|
var diag = getCurrentDiagram();
|
|
1168
1972
|
if (diag.arrows.some(function (a) { return a.from === fromId && a.to === toId; })) return;
|
|
1973
|
+
pushHistory();
|
|
1169
1974
|
var arrow = { id: "a" + Date.now(), from: fromId, to: toId, label: "" };
|
|
1170
1975
|
diag.arrows.push(arrow);
|
|
1171
1976
|
saveDiagrammes();
|
|
@@ -1219,13 +2024,26 @@ function onMouseDown(e) {
|
|
|
1219
2024
|
return;
|
|
1220
2025
|
}
|
|
1221
2026
|
|
|
2027
|
+
// Clic sur un chevron de redimensionnement de colonne
|
|
2028
|
+
var colSepEl = e.target.closest("[data-col-sep]");
|
|
2029
|
+
if (colSepEl) {
|
|
2030
|
+
var csColIdx = parseInt(colSepEl.getAttribute("data-col-sep"), 10);
|
|
2031
|
+
var csSid = colSepEl.getAttribute("data-shape-id");
|
|
2032
|
+
var csShape = getCurrentDiagram().shapes.find(function (s) { return s.id === csSid; });
|
|
2033
|
+
if (csShape) {
|
|
2034
|
+
var csCw = getColWidths(csShape);
|
|
2035
|
+
dragState = { type: "col-resize", id: csSid, colIdx: csColIdx, sx: pt.x, origWidths: csCw.slice(), snapshot: JSON.stringify(diagramsList) };
|
|
2036
|
+
}
|
|
2037
|
+
return;
|
|
2038
|
+
}
|
|
2039
|
+
|
|
1222
2040
|
// Clic sur la poignée de resize
|
|
1223
2041
|
var grip = e.target.closest(".resize-grip");
|
|
1224
2042
|
if (grip) {
|
|
1225
2043
|
var sid = grip.getAttribute("data-shape-id");
|
|
1226
2044
|
var shape = getCurrentDiagram().shapes.find(function (s) { return s.id === sid; });
|
|
1227
2045
|
if (shape) {
|
|
1228
|
-
dragState = { type: "resize", id: sid, sx: pt.x, sy: pt.y, ow: shape.w, oh: shape.h };
|
|
2046
|
+
dragState = { type: "resize", id: sid, sx: pt.x, sy: pt.y, ow: shape.w, oh: shape.h, snapshot: JSON.stringify(diagramsList) };
|
|
1229
2047
|
}
|
|
1230
2048
|
return;
|
|
1231
2049
|
}
|
|
@@ -1238,7 +2056,20 @@ function onMouseDown(e) {
|
|
|
1238
2056
|
var now = Date.now();
|
|
1239
2057
|
if (now - lastClickTime < 350 && lastClickShapeId === shape.id) {
|
|
1240
2058
|
lastClickTime = 0; lastClickShapeId = null;
|
|
1241
|
-
|
|
2059
|
+
if (shape.type === "table") {
|
|
2060
|
+
var tDblRows = shape.rows || 3;
|
|
2061
|
+
var tDblOffsets = getColOffsets(shape);
|
|
2062
|
+
var tDblCw = getColWidths(shape);
|
|
2063
|
+
var tDblRx = pt.x - shape.x;
|
|
2064
|
+
var tDblC = tDblCw.length - 1;
|
|
2065
|
+
for (var tci3 = 0; tci3 < tDblCw.length; tci3++) {
|
|
2066
|
+
if (tDblRx < tDblOffsets[tci3] + tDblCw[tci3]) { tDblC = tci3; break; }
|
|
2067
|
+
}
|
|
2068
|
+
var tDblR = Math.min(tDblRows - 1, Math.max(0, Math.floor((pt.y - shape.y) / (shape.h / tDblRows))));
|
|
2069
|
+
startTableCellEdit(shape.id, tDblR, tDblC);
|
|
2070
|
+
} else {
|
|
2071
|
+
startTextEdit(shape.id);
|
|
2072
|
+
}
|
|
1242
2073
|
return;
|
|
1243
2074
|
}
|
|
1244
2075
|
lastClickTime = now; lastClickShapeId = shape.id; lastClickArrowId = null;
|
|
@@ -1266,12 +2097,13 @@ function onMouseDown(e) {
|
|
|
1266
2097
|
var s = diag.shapes.find(function (sh) { return sh.id === id; });
|
|
1267
2098
|
return { id: id, ox: s ? s.x : 0, oy: s ? s.y : 0 };
|
|
1268
2099
|
}),
|
|
2100
|
+
snapshot: JSON.stringify(diagramsList),
|
|
1269
2101
|
};
|
|
1270
2102
|
} else {
|
|
1271
2103
|
// Clic simple → sélection unique
|
|
1272
2104
|
selectedIds = [shape.id];
|
|
1273
2105
|
selectedId = shape.id; selectedType = "shape";
|
|
1274
|
-
dragState = { type: "move", id: shape.id, sx: pt.x, sy: pt.y, ox: shape.x, oy: shape.y };
|
|
2106
|
+
dragState = { type: "move", id: shape.id, sx: pt.x, sy: pt.y, ox: shape.x, oy: shape.y, snapshot: JSON.stringify(diagramsList) };
|
|
1275
2107
|
document.getElementById("colorPanel").style.display = "flex";
|
|
1276
2108
|
renderAll();
|
|
1277
2109
|
}
|
|
@@ -1353,6 +2185,7 @@ function onMouseMove(e) {
|
|
|
1353
2185
|
rb.setAttribute("height", Math.abs(pt.y - rubberBandState.sy));
|
|
1354
2186
|
rb.style.display = "";
|
|
1355
2187
|
} else if (dragState) {
|
|
2188
|
+
dragState.moved = true;
|
|
1356
2189
|
var diag = getCurrentDiagram();
|
|
1357
2190
|
if (dragState.type === "multi-move") {
|
|
1358
2191
|
var dx = pt.x - dragState.sx;
|
|
@@ -1369,8 +2202,27 @@ function onMouseMove(e) {
|
|
|
1369
2202
|
shape.x = Math.round(dragState.ox + pt.x - dragState.sx);
|
|
1370
2203
|
shape.y = Math.round(dragState.oy + pt.y - dragState.sy);
|
|
1371
2204
|
} else if (dragState.type === "resize") {
|
|
1372
|
-
|
|
1373
|
-
|
|
2205
|
+
var newW = Math.max(60, Math.round(dragState.ow + pt.x - dragState.sx));
|
|
2206
|
+
var newH = Math.max(30, Math.round(dragState.oh + pt.y - dragState.sy));
|
|
2207
|
+
if (shape.type === "table" && shape.colWidths && shape.colWidths.length === (shape.cols || 3)) {
|
|
2208
|
+
var oldW2 = shape.w;
|
|
2209
|
+
shape.colWidths = shape.colWidths.map(function (cw) { return cw * newW / oldW2; });
|
|
2210
|
+
}
|
|
2211
|
+
shape.w = newW;
|
|
2212
|
+
shape.h = newH;
|
|
2213
|
+
} else if (dragState.type === "col-resize") {
|
|
2214
|
+
var crCw = dragState.origWidths.slice();
|
|
2215
|
+
var crDx = pt.x - dragState.sx;
|
|
2216
|
+
var crCi = dragState.colIdx;
|
|
2217
|
+
var MIN_COL = 20;
|
|
2218
|
+
var crLeft = Math.max(MIN_COL, crCw[crCi] + crDx);
|
|
2219
|
+
var crRight = Math.max(MIN_COL, crCw[crCi + 1] - crDx);
|
|
2220
|
+
var crTotal = crCw[crCi] + crCw[crCi + 1];
|
|
2221
|
+
crLeft = Math.min(crTotal - MIN_COL, crLeft);
|
|
2222
|
+
crRight = crTotal - crLeft;
|
|
2223
|
+
crCw[crCi] = crLeft;
|
|
2224
|
+
crCw[crCi + 1] = crRight;
|
|
2225
|
+
shape.colWidths = crCw;
|
|
1374
2226
|
}
|
|
1375
2227
|
renderAll();
|
|
1376
2228
|
}
|
|
@@ -1384,6 +2236,31 @@ function onMouseMove(e) {
|
|
|
1384
2236
|
function onMouseUp(e) {
|
|
1385
2237
|
var pt = svgPoint(e.clientX, e.clientY);
|
|
1386
2238
|
|
|
2239
|
+
// En mode verrouillé : détecter un clic (sans déplacement) sur une forme liée
|
|
2240
|
+
if (boardLocked && panStart) {
|
|
2241
|
+
var movedPx = Math.abs(e.clientX - panStart.cx) + Math.abs(e.clientY - panStart.cy);
|
|
2242
|
+
if (movedPx < 5 && !e.shiftKey) {
|
|
2243
|
+
var shape = shapeAt(pt.x, pt.y);
|
|
2244
|
+
if (shape && shape.linkedDiagramId) {
|
|
2245
|
+
var target = findDiagramById(shape.linkedDiagramId, diagramsList);
|
|
2246
|
+
if (target) {
|
|
2247
|
+
hideLinkPicker();
|
|
2248
|
+
diagNavStack.push(currentDiagramId);
|
|
2249
|
+
if (diagNavStack.length > 30) diagNavStack.shift();
|
|
2250
|
+
panStart = null;
|
|
2251
|
+
selectDiagramme(shape.linkedDiagramId);
|
|
2252
|
+
return;
|
|
2253
|
+
}
|
|
2254
|
+
} else if (shape && shape.externalUrl) {
|
|
2255
|
+
panStart = null;
|
|
2256
|
+
window.open(shape.externalUrl, "_blank");
|
|
2257
|
+
return;
|
|
2258
|
+
}
|
|
2259
|
+
}
|
|
2260
|
+
panStart = null;
|
|
2261
|
+
return;
|
|
2262
|
+
}
|
|
2263
|
+
|
|
1387
2264
|
// Fin de tracé de flèche via conn-dot
|
|
1388
2265
|
if (arrowSrcId && !dragState) {
|
|
1389
2266
|
document.getElementById("tempArrow").style.display = "none";
|
|
@@ -1416,9 +2293,34 @@ function onMouseUp(e) {
|
|
|
1416
2293
|
}
|
|
1417
2294
|
|
|
1418
2295
|
if (dragState) {
|
|
2296
|
+
var wasMoved = dragState.moved;
|
|
2297
|
+
var draggedId = dragState.id;
|
|
2298
|
+
var dragType = dragState.type;
|
|
2299
|
+
if (dragState.moved && dragState.snapshot) {
|
|
2300
|
+
historyStack.push(dragState.snapshot);
|
|
2301
|
+
if (historyStack.length > MAX_HISTORY) historyStack.shift();
|
|
2302
|
+
}
|
|
1419
2303
|
saveDiagrammes();
|
|
1420
2304
|
dragState = null;
|
|
1421
2305
|
renderAll();
|
|
2306
|
+
// Naviguer vers le diagramme lié si clic sans déplacement
|
|
2307
|
+
if (!wasMoved && dragType === "move" && draggedId && !e.shiftKey) {
|
|
2308
|
+
var diag = getCurrentDiagram();
|
|
2309
|
+
var clickedShape = diag ? diag.shapes.find(function (s) { return s.id === draggedId; }) : null;
|
|
2310
|
+
if (clickedShape && clickedShape.linkedDiagramId) {
|
|
2311
|
+
var target = findDiagramById(clickedShape.linkedDiagramId, diagramsList);
|
|
2312
|
+
if (target) {
|
|
2313
|
+
hideLinkPicker();
|
|
2314
|
+
diagNavStack.push(currentDiagramId);
|
|
2315
|
+
if (diagNavStack.length > 30) diagNavStack.shift();
|
|
2316
|
+
selectDiagramme(clickedShape.linkedDiagramId);
|
|
2317
|
+
return;
|
|
2318
|
+
}
|
|
2319
|
+
} else if (clickedShape && clickedShape.externalUrl) {
|
|
2320
|
+
window.open(clickedShape.externalUrl, "_blank");
|
|
2321
|
+
return;
|
|
2322
|
+
}
|
|
2323
|
+
}
|
|
1422
2324
|
}
|
|
1423
2325
|
panStart = null;
|
|
1424
2326
|
}
|
|
@@ -1431,9 +2333,11 @@ function onWheel(e) {
|
|
|
1431
2333
|
applyZoom(e.deltaY < 0 ? 1.12 : 1 / 1.12, e.clientX - r.left, e.clientY - r.top);
|
|
1432
2334
|
}
|
|
1433
2335
|
|
|
2336
|
+
|
|
1434
2337
|
// ── Coller des formes ──
|
|
1435
2338
|
function pasteShapes() {
|
|
1436
2339
|
if (clipboard.length === 0) return;
|
|
2340
|
+
pushHistory();
|
|
1437
2341
|
var diag = getCurrentDiagram();
|
|
1438
2342
|
var now = Date.now();
|
|
1439
2343
|
var pasted = clipboard.map(function (s, i) {
|
|
@@ -1456,14 +2360,22 @@ function pasteShapes() {
|
|
|
1456
2360
|
document.addEventListener("DOMContentLoaded", function () {
|
|
1457
2361
|
diagramsList = loadDiagrammes();
|
|
1458
2362
|
if (!localStorage.getItem("mes_diagrammes")) saveDiagrammes();
|
|
1459
|
-
var
|
|
1460
|
-
if (
|
|
1461
|
-
|
|
2363
|
+
var savedDiagId = localStorage.getItem("current_diagram_id");
|
|
2364
|
+
if (savedDiagId && findDiagramById(savedDiagId, diagramsList)) {
|
|
2365
|
+
currentDiagramId = savedDiagId;
|
|
2366
|
+
} else {
|
|
2367
|
+
var savedIdx = parseInt(localStorage.getItem("current_diagram_idx"), 10);
|
|
2368
|
+
if (!isNaN(savedIdx) && savedIdx >= 0 && savedIdx < diagramsList.length) {
|
|
2369
|
+
currentDiagramId = String(diagramsList[savedIdx].id);
|
|
2370
|
+
} else {
|
|
2371
|
+
currentDiagramId = diagramsList[0] ? String(diagramsList[0].id) : null;
|
|
2372
|
+
}
|
|
1462
2373
|
}
|
|
1463
|
-
restoreLockForDiagram(
|
|
2374
|
+
restoreLockForDiagram(currentDiagramId);
|
|
1464
2375
|
checkDiffDiagrammes();
|
|
1465
2376
|
renderAll();
|
|
1466
2377
|
updateLockBtn();
|
|
2378
|
+
updateBackBtn();
|
|
1467
2379
|
|
|
1468
2380
|
var canvas = document.getElementById("canvas");
|
|
1469
2381
|
canvas.addEventListener("mousedown", onMouseDown);
|
|
@@ -1505,6 +2417,43 @@ document.addEventListener("DOMContentLoaded", function () {
|
|
|
1505
2417
|
editingShapeId = null;
|
|
1506
2418
|
renderAll(); // restaure la visibilité du texte SVG
|
|
1507
2419
|
}
|
|
2420
|
+
if (e.key === "Tab" && editingTableCell !== null) {
|
|
2421
|
+
e.preventDefault();
|
|
2422
|
+
var diag = getCurrentDiagram();
|
|
2423
|
+
var shape = diag.shapes.find(function (s) { return s.id === editingShapeId; });
|
|
2424
|
+
if (!shape) return;
|
|
2425
|
+
// Sauvegarder la cellule courante
|
|
2426
|
+
if (!shape.cells) shape.cells = [];
|
|
2427
|
+
while (shape.cells.length <= editingTableCell.row) shape.cells.push([]);
|
|
2428
|
+
shape.cells[editingTableCell.row][editingTableCell.col] = postitInput.value;
|
|
2429
|
+
var rows = shape.rows || 3;
|
|
2430
|
+
var cols = shape.cols || 3;
|
|
2431
|
+
var nextRow = editingTableCell.row;
|
|
2432
|
+
var nextCol = editingTableCell.col;
|
|
2433
|
+
if (e.shiftKey) {
|
|
2434
|
+
// Shift+Tab : cellule précédente, s'arrête à la première
|
|
2435
|
+
nextCol -= 1;
|
|
2436
|
+
if (nextCol < 0) {
|
|
2437
|
+
if (nextRow > 0) { nextRow--; nextCol = cols - 1; }
|
|
2438
|
+
else { nextCol = 0; }
|
|
2439
|
+
}
|
|
2440
|
+
} else {
|
|
2441
|
+
// Tab : cellule suivante, ajoute une ligne à la fin
|
|
2442
|
+
nextCol += 1;
|
|
2443
|
+
if (nextCol >= cols) { nextRow++; nextCol = 0; }
|
|
2444
|
+
if (nextRow >= rows) {
|
|
2445
|
+
pushHistory();
|
|
2446
|
+
shape.rows = rows + 1;
|
|
2447
|
+
if (!shape.cells) shape.cells = [];
|
|
2448
|
+
while (shape.cells.length < shape.rows) shape.cells.push([]);
|
|
2449
|
+
shape.cells[shape.rows - 1] = new Array(cols).fill("");
|
|
2450
|
+
}
|
|
2451
|
+
}
|
|
2452
|
+
var shapeId = editingShapeId;
|
|
2453
|
+
saveDiagrammes();
|
|
2454
|
+
renderAll();
|
|
2455
|
+
startTableCellEdit(shapeId, nextRow, nextCol);
|
|
2456
|
+
}
|
|
1508
2457
|
// Enter ajoute un saut de ligne (comportement natif textarea — pas de preventDefault)
|
|
1509
2458
|
});
|
|
1510
2459
|
postitInput.addEventListener("blur", function () {
|
|
@@ -1513,12 +2462,41 @@ document.addEventListener("DOMContentLoaded", function () {
|
|
|
1513
2462
|
|
|
1514
2463
|
document.addEventListener("keydown", function (e) {
|
|
1515
2464
|
if (document.activeElement.tagName === "INPUT" || document.activeElement.tagName === "TEXTAREA") return;
|
|
2465
|
+
if ((e.ctrlKey || e.metaKey) && e.key === "z") { e.preventDefault(); undoAction(); return; }
|
|
2466
|
+
if ((e.ctrlKey || e.metaKey) && e.key === "a") {
|
|
2467
|
+
e.preventDefault();
|
|
2468
|
+
var diag = getCurrentDiagram();
|
|
2469
|
+
if (diag && diag.shapes.length > 0) {
|
|
2470
|
+
selectedIds = diag.shapes.map(function (s) { return s.id; });
|
|
2471
|
+
selectedType = "shape";
|
|
2472
|
+
selectedId = selectedIds[selectedIds.length - 1];
|
|
2473
|
+
renderAll();
|
|
2474
|
+
document.getElementById("colorPanel").style.display = "flex";
|
|
2475
|
+
syncColorPanel();
|
|
2476
|
+
}
|
|
2477
|
+
return;
|
|
2478
|
+
}
|
|
1516
2479
|
if (boardLocked) return;
|
|
1517
2480
|
if (e.key === "Delete" || e.key === "Backspace") deleteSelected();
|
|
1518
2481
|
if (e.key === "Escape") {
|
|
2482
|
+
hideLinkPicker();
|
|
1519
2483
|
arrowSrcId = null;
|
|
1520
2484
|
document.getElementById("tempArrow").style.display = "none";
|
|
1521
2485
|
setTool("select");
|
|
2486
|
+
selectedIds = []; selectedId = null; selectedType = null;
|
|
2487
|
+
document.getElementById("colorPanel").style.display = "none";
|
|
2488
|
+
renderAll();
|
|
2489
|
+
}
|
|
2490
|
+
if ((e.ctrlKey || e.metaKey) && e.key === "x") {
|
|
2491
|
+
e.preventDefault();
|
|
2492
|
+
if (selectedIds.length === 0) return;
|
|
2493
|
+
var diag = getCurrentDiagram();
|
|
2494
|
+
clipboard = selectedIds.map(function (id) {
|
|
2495
|
+
var s = diag.shapes.find(function (sh) { return sh.id === id; });
|
|
2496
|
+
return s ? JSON.parse(JSON.stringify(s)) : null;
|
|
2497
|
+
}).filter(Boolean);
|
|
2498
|
+
deleteSelected();
|
|
2499
|
+
return;
|
|
1522
2500
|
}
|
|
1523
2501
|
if ((e.ctrlKey || e.metaKey) && e.key === "c") {
|
|
1524
2502
|
e.preventDefault();
|
|
@@ -1561,7 +2539,13 @@ document.addEventListener("DOMContentLoaded", function () {
|
|
|
1561
2539
|
if (e.target === this) this.classList.remove("open");
|
|
1562
2540
|
});
|
|
1563
2541
|
|
|
2542
|
+
|
|
1564
2543
|
document.addEventListener("mousedown", function (e) {
|
|
2544
|
+
var lp = document.getElementById("linkPickerPanel");
|
|
2545
|
+
if (lp && lp.style.display !== "none") {
|
|
2546
|
+
var lbtn = document.getElementById("btnShapeLink");
|
|
2547
|
+
if (!lp.contains(e.target) && e.target !== lbtn) hideLinkPicker();
|
|
2548
|
+
}
|
|
1565
2549
|
var panel = document.getElementById("diagramListPanel");
|
|
1566
2550
|
if (!panel.classList.contains("open")) return;
|
|
1567
2551
|
var burgerBtn = document.querySelector(".diagram-tool[onclick=\"toggleDiagramList()\"]");
|