doc-survival-kit 2.0.0 → 3.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of doc-survival-kit might be problematic. Click here for more details.
- package/diagram/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 +8 -8
- package/diagram.js +0 -2556
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
// ══════════════════════════════════════
|
|
2
|
+
// globals.js — État global, constantes et helpers de base
|
|
3
|
+
// ══════════════════════════════════════
|
|
4
|
+
|
|
5
|
+
// ── État global ──
|
|
6
|
+
var currentTool = "select";
|
|
7
|
+
var diagramsList = [];
|
|
8
|
+
var currentDiagramId = null;
|
|
9
|
+
var diagNavStack = [];
|
|
10
|
+
var diagExpandedIds = {};
|
|
11
|
+
var pendingParentId = null;
|
|
12
|
+
var pendingNavDiagId = null;
|
|
13
|
+
var viewTransform = { x: 60, y: 60, scale: 1 };
|
|
14
|
+
var selectedId = null;
|
|
15
|
+
var selectedType = null; // "shape" | "arrow"
|
|
16
|
+
var selectedIds = []; // multi-sélection (ids de formes)
|
|
17
|
+
var rubberBandState = null; // { sx, sy } pendant le lasso
|
|
18
|
+
var clipboard = []; // formes copiées (Ctrl+C / Ctrl+V)
|
|
19
|
+
var pendingImageBlob = null; // image en attente de sélection du dossier
|
|
20
|
+
var pendingNewDiagram = false; // true pendant la saisie du nom d'un nouveau diagramme
|
|
21
|
+
var dragState = null;
|
|
22
|
+
var panStart = null;
|
|
23
|
+
var arrowSrcId = null;
|
|
24
|
+
var editingShapeId = null;
|
|
25
|
+
var editingTableCell = null; // { row, col } | null
|
|
26
|
+
var pickMode = null; // "fontSize" | "fullStyle" | null
|
|
27
|
+
var pickTargetIds = []; // selectedIds sauvegardés pendant le pick
|
|
28
|
+
var lastClickTime = 0;
|
|
29
|
+
var lastClickShapeId = null;
|
|
30
|
+
var lastClickArrowId = null;
|
|
31
|
+
var editingArrowId = null;
|
|
32
|
+
var boardLocked = false;
|
|
33
|
+
var diagDragSrcId = null;
|
|
34
|
+
var historyStack = [];
|
|
35
|
+
var MAX_HISTORY = 50;
|
|
36
|
+
|
|
37
|
+
// ── Palette ──
|
|
38
|
+
var COLORS = {
|
|
39
|
+
"t-green": { fill: "rgba(209,250,229,0.75)", stroke: "#059669", text: "#047857" },
|
|
40
|
+
"t-violet": { fill: "rgba(237,233,254,0.75)", stroke: "#7c3aed", text: "#6d28d9" },
|
|
41
|
+
"t-amber": { fill: "rgba(254,243,199,0.75)", stroke: "#d97706", text: "#b45309" },
|
|
42
|
+
"t-sky": { fill: "rgba(224,242,254,0.75)", stroke: "#0284c7", text: "#0369a1" },
|
|
43
|
+
"t-rose": { fill: "rgba(255,228,230,0.75)", stroke: "#e11d48", text: "#be123c" },
|
|
44
|
+
"t-teal": { fill: "rgba(204,251,241,0.75)", stroke: "#0d9488", text: "#0f766e" },
|
|
45
|
+
"t-white": { fill: "rgba(255,255,255,0.95)", stroke: "#d4d4d4", text: "#404040" },
|
|
46
|
+
};
|
|
47
|
+
var DEFAULT_COLOR = "t-sky";
|
|
48
|
+
|
|
49
|
+
var DEFAULT_SIZES = {
|
|
50
|
+
rect: { w: 130, h: 50 },
|
|
51
|
+
rounded: { w: 130, h: 50 },
|
|
52
|
+
db: { w: 100, h: 65 },
|
|
53
|
+
cloud: { w: 110, h: 65 },
|
|
54
|
+
nuage: { w: 121, h: 96 },
|
|
55
|
+
text: { w: 110, h: 34 },
|
|
56
|
+
postit: { w: 130, h: 110 },
|
|
57
|
+
actor: { w: 60, h: 90 },
|
|
58
|
+
table: { w: 210, h: 120 },
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
// ── Helpers ──
|
|
62
|
+
function escDiag(s) {
|
|
63
|
+
return String(s)
|
|
64
|
+
.replace(/&/g, "&")
|
|
65
|
+
.replace(/</g, "<")
|
|
66
|
+
.replace(/>/g, ">")
|
|
67
|
+
.replace(/"/g, """);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function createSVGEl(tag) {
|
|
71
|
+
return document.createElementNS("http://www.w3.org/2000/svg", tag);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Découpe le texte d'un post-it en lignes qui tiennent dans maxWidth px.
|
|
75
|
+
// Respecte les \n explicites, puis fait du word-wrap sur les mots.
|
|
76
|
+
var _measureCanvas = null;
|
|
77
|
+
function measureText(str, fontSize, fontWeight) {
|
|
78
|
+
if (!_measureCanvas) _measureCanvas = document.createElement("canvas");
|
|
79
|
+
var ctx = _measureCanvas.getContext("2d");
|
|
80
|
+
ctx.font = (fontWeight || "600") + " " + (fontSize || 12) + "px \"Segoe UI\",system-ui,sans-serif";
|
|
81
|
+
return ctx.measureText(str).width;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function wrapPostitLines(text, maxWidth, fontSize) {
|
|
85
|
+
var fs = fontSize || 12;
|
|
86
|
+
var result = [];
|
|
87
|
+
(text || "").split("\n").forEach(function (line) {
|
|
88
|
+
if (line === "") { result.push(""); return; }
|
|
89
|
+
var words = line.split(" ");
|
|
90
|
+
var current = "";
|
|
91
|
+
words.forEach(function (word) {
|
|
92
|
+
// Si le mot seul dépasse maxWidth, le couper caractère par caractère
|
|
93
|
+
if (measureText(word, fs, "600") > maxWidth) {
|
|
94
|
+
if (current !== "") { result.push(current); current = ""; }
|
|
95
|
+
var chunk = "";
|
|
96
|
+
for (var ci = 0; ci < word.length; ci++) {
|
|
97
|
+
var chunkTest = chunk + word[ci];
|
|
98
|
+
if (measureText(chunkTest, fs, "600") > maxWidth) {
|
|
99
|
+
if (chunk !== "") result.push(chunk);
|
|
100
|
+
chunk = word[ci];
|
|
101
|
+
} else {
|
|
102
|
+
chunk = chunkTest;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
current = chunk;
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
var test = current ? current + " " + word : word;
|
|
109
|
+
if (current && measureText(test, fs, "600") > maxWidth) {
|
|
110
|
+
result.push(current);
|
|
111
|
+
current = word;
|
|
112
|
+
} else {
|
|
113
|
+
current = test;
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
if (current !== "") result.push(current);
|
|
117
|
+
});
|
|
118
|
+
return result;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// ── Helpers tableau ──
|
|
122
|
+
function getColWidths(shape) {
|
|
123
|
+
var cols = shape.cols || 3;
|
|
124
|
+
if (shape.colWidths && shape.colWidths.length === cols) return shape.colWidths;
|
|
125
|
+
var w = shape.w / cols;
|
|
126
|
+
var result = [];
|
|
127
|
+
for (var i = 0; i < cols; i++) result.push(w);
|
|
128
|
+
return result;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// xOffsets[i] = x du début de la colonne i (relatif à shape.x)
|
|
132
|
+
function getColOffsets(shape) {
|
|
133
|
+
var cw = getColWidths(shape);
|
|
134
|
+
var offsets = [0];
|
|
135
|
+
for (var i = 0; i < cw.length - 1; i++) offsets.push(offsets[i] + cw[i]);
|
|
136
|
+
return offsets;
|
|
137
|
+
}
|
package/diagram/links.js
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
// ══════════════════════════════════════
|
|
2
|
+
// links.js — Lien picker et URLs externes
|
|
3
|
+
// ══════════════════════════════════════
|
|
4
|
+
|
|
5
|
+
function syncColorPanel() {
|
|
6
|
+
var btn = document.getElementById("btnShapeLink");
|
|
7
|
+
if (!btn) return;
|
|
8
|
+
if (selectedIds.length === 1 && selectedType === "shape") {
|
|
9
|
+
var diag = getCurrentDiagram();
|
|
10
|
+
var shape = diag ? diag.shapes.find(function (s) { return s.id === selectedIds[0]; }) : null;
|
|
11
|
+
if (shape && shape.linkedDiagramId) {
|
|
12
|
+
btn.classList.add("active");
|
|
13
|
+
var linked = findDiagramById(shape.linkedDiagramId, diagramsList);
|
|
14
|
+
btn.title = linked ? "Lié à : " + linked.titre + " (cliquer pour délier)" : "Supprimer le lien";
|
|
15
|
+
} else if (shape && shape.externalUrl) {
|
|
16
|
+
btn.classList.add("active");
|
|
17
|
+
btn.title = "Lien externe : " + shape.externalUrl + " (cliquer pour délier)";
|
|
18
|
+
} else {
|
|
19
|
+
btn.classList.remove("active");
|
|
20
|
+
btn.title = "Lier à un diagramme enfant ou URL externe";
|
|
21
|
+
}
|
|
22
|
+
btn.style.display = "";
|
|
23
|
+
} else {
|
|
24
|
+
btn.classList.remove("active");
|
|
25
|
+
btn.style.display = "none";
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function toggleShapeLink() {
|
|
30
|
+
if (selectedIds.length !== 1 || selectedType !== "shape") return;
|
|
31
|
+
var diag = getCurrentDiagram();
|
|
32
|
+
var shape = diag ? diag.shapes.find(function (s) { return s.id === selectedIds[0]; }) : null;
|
|
33
|
+
if (!shape) return;
|
|
34
|
+
if (shape.linkedDiagramId || shape.externalUrl) {
|
|
35
|
+
pushHistory();
|
|
36
|
+
delete shape.linkedDiagramId;
|
|
37
|
+
delete shape.externalUrl;
|
|
38
|
+
saveDiagrammes();
|
|
39
|
+
renderAll();
|
|
40
|
+
syncColorPanel();
|
|
41
|
+
hideLinkPicker();
|
|
42
|
+
} else {
|
|
43
|
+
showLinkPicker();
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function showLinkPicker() {
|
|
48
|
+
var panel = document.getElementById("linkPickerPanel");
|
|
49
|
+
if (!panel) return;
|
|
50
|
+
var btn = document.getElementById("btnShapeLink");
|
|
51
|
+
var r = btn ? btn.getBoundingClientRect() : { bottom: 100, left: 200 };
|
|
52
|
+
panel.style.top = (r.bottom + 6) + "px";
|
|
53
|
+
panel.style.left = r.left + "px";
|
|
54
|
+
var all = flattenDiagrams(diagramsList);
|
|
55
|
+
var curId = String(currentDiagramId);
|
|
56
|
+
var items = all.filter(function (d) { return String(d.id) !== curId; });
|
|
57
|
+
var html = items.map(function (d) {
|
|
58
|
+
return '<div class="link-picker-item" onclick="lierForme(\'' + d.id + '\')">' + escDiag(d.titre) + '</div>';
|
|
59
|
+
}).join("");
|
|
60
|
+
html += '<div class="link-picker-new" onclick="creerEnfantEtLier()">+ Nouveau diagramme enfant</div>';
|
|
61
|
+
html += '<div class="link-picker-new link-picker-ext-toggle" onclick="showExternalLinkInput()" style="display:flex;align-items:center;gap:6px;">'
|
|
62
|
+
+ '<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>'
|
|
63
|
+
+ 'Lien externe (URL)</div>';
|
|
64
|
+
html += '<div id="linkPickerExtRow" style="display:none;padding:6px 8px;border-top:1px solid #e7e5e4;">'
|
|
65
|
+
+ '<input id="linkPickerExtUrl" type="text" placeholder="https://..." '
|
|
66
|
+
+ 'style="width:100%;box-sizing:border-box;padding:4px 6px;border:1px solid #d4d4d4;border-radius:4px;font-size:12px;" '
|
|
67
|
+
+ 'onkeydown="if(event.key===\'Enter\')lierFormeExterne()">'
|
|
68
|
+
+ '<div id="linkPickerExtErr" style="color:#e11d48;font-size:11px;margin-top:3px;display:none;">URL invalide (http://, https:// ou file://)</div>'
|
|
69
|
+
+ '</div>';
|
|
70
|
+
document.getElementById("linkPickerList").innerHTML = html;
|
|
71
|
+
panel.style.display = "block";
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function showExternalLinkInput() {
|
|
75
|
+
var row = document.getElementById("linkPickerExtRow");
|
|
76
|
+
if (!row) return;
|
|
77
|
+
row.style.display = "block";
|
|
78
|
+
var input = document.getElementById("linkPickerExtUrl");
|
|
79
|
+
if (input) setTimeout(function () { input.focus(); }, 10);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function lierFormeExterne() {
|
|
83
|
+
var input = document.getElementById("linkPickerExtUrl");
|
|
84
|
+
if (!input) return;
|
|
85
|
+
var url = input.value.trim();
|
|
86
|
+
var err = document.getElementById("linkPickerExtErr");
|
|
87
|
+
if (!/^(https?|file):\/\//i.test(url)) {
|
|
88
|
+
if (err) err.style.display = "block";
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
if (err) err.style.display = "none";
|
|
92
|
+
if (selectedIds.length !== 1) return;
|
|
93
|
+
var diag = getCurrentDiagram();
|
|
94
|
+
var shape = diag ? diag.shapes.find(function (s) { return s.id === selectedIds[0]; }) : null;
|
|
95
|
+
if (!shape) return;
|
|
96
|
+
pushHistory();
|
|
97
|
+
shape.externalUrl = url;
|
|
98
|
+
delete shape.linkedDiagramId;
|
|
99
|
+
hideLinkPicker();
|
|
100
|
+
saveDiagrammes();
|
|
101
|
+
renderAll();
|
|
102
|
+
syncColorPanel();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function hideLinkPicker() {
|
|
106
|
+
var panel = document.getElementById("linkPickerPanel");
|
|
107
|
+
if (panel) panel.style.display = "none";
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function lierForme(diagId) {
|
|
111
|
+
if (selectedIds.length !== 1) return;
|
|
112
|
+
var diag = getCurrentDiagram();
|
|
113
|
+
var shape = diag ? diag.shapes.find(function (s) { return s.id === selectedIds[0]; }) : null;
|
|
114
|
+
if (!shape) return;
|
|
115
|
+
pushHistory();
|
|
116
|
+
shape.linkedDiagramId = String(diagId);
|
|
117
|
+
delete shape.externalUrl;
|
|
118
|
+
hideLinkPicker();
|
|
119
|
+
saveDiagrammes();
|
|
120
|
+
renderAll();
|
|
121
|
+
syncColorPanel();
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function creerEnfantEtLier() {
|
|
125
|
+
if (selectedIds.length !== 1) return;
|
|
126
|
+
var diag = getCurrentDiagram();
|
|
127
|
+
var shape = diag ? diag.shapes.find(function (s) { return s.id === selectedIds[0]; }) : null;
|
|
128
|
+
if (!shape) return;
|
|
129
|
+
hideLinkPicker();
|
|
130
|
+
var titre = shape.text || "Sous-diagramme";
|
|
131
|
+
var child = { id: Date.now(), titre: titre, shapes: [], arrows: [], children: [] };
|
|
132
|
+
if (!diag.children) diag.children = [];
|
|
133
|
+
diag.children.push(child);
|
|
134
|
+
diagExpandedIds[String(diag.id)] = true;
|
|
135
|
+
pushHistory();
|
|
136
|
+
shape.linkedDiagramId = String(child.id);
|
|
137
|
+
saveDiagrammes();
|
|
138
|
+
renderAll();
|
|
139
|
+
renderDiagramList();
|
|
140
|
+
syncColorPanel();
|
|
141
|
+
}
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
// ══════════════════════════════════════
|
|
2
|
+
// persistence.js — Chargement, sauvegarde, historique, verrou, zoom, File System API, images
|
|
3
|
+
// ══════════════════════════════════════
|
|
4
|
+
|
|
5
|
+
// ── Persistance localStorage ──
|
|
6
|
+
function loadDiagrammes() {
|
|
7
|
+
try {
|
|
8
|
+
var stored = JSON.parse(localStorage.getItem("mes_diagrammes"));
|
|
9
|
+
if (stored && stored.length) {
|
|
10
|
+
var migrate = function (list) {
|
|
11
|
+
list.forEach(function (d) {
|
|
12
|
+
if (!d.children) d.children = [];
|
|
13
|
+
migrate(d.children);
|
|
14
|
+
});
|
|
15
|
+
};
|
|
16
|
+
migrate(stored);
|
|
17
|
+
return stored;
|
|
18
|
+
}
|
|
19
|
+
} catch (e) {}
|
|
20
|
+
var defaults = JSON.parse(JSON.stringify(diagrammesDefaut));
|
|
21
|
+
var migrate2 = function (list) {
|
|
22
|
+
list.forEach(function (d) {
|
|
23
|
+
if (!d.children) d.children = [];
|
|
24
|
+
migrate2(d.children);
|
|
25
|
+
});
|
|
26
|
+
};
|
|
27
|
+
migrate2(defaults);
|
|
28
|
+
return defaults;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function saveDiagrammes() {
|
|
32
|
+
localStorage.setItem("mes_diagrammes", JSON.stringify(diagramsList));
|
|
33
|
+
checkDiffDiagrammes();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function pushHistory() {
|
|
37
|
+
historyStack.push(JSON.stringify(diagramsList));
|
|
38
|
+
if (historyStack.length > MAX_HISTORY) historyStack.shift();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function undoAction() {
|
|
42
|
+
if (historyStack.length === 0) return;
|
|
43
|
+
diagramsList = JSON.parse(historyStack.pop());
|
|
44
|
+
if (!findDiagramById(currentDiagramId, diagramsList)) {
|
|
45
|
+
currentDiagramId = diagramsList[0] ? String(diagramsList[0].id) : null;
|
|
46
|
+
}
|
|
47
|
+
saveDiagrammes();
|
|
48
|
+
renderAll();
|
|
49
|
+
renderDiagramList();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function checkDiffDiagrammes() {
|
|
53
|
+
var stored = localStorage.getItem("mes_diagrammes");
|
|
54
|
+
var diff = stored !== JSON.stringify(diagrammesDefaut);
|
|
55
|
+
document.getElementById("btnSaveDiagram").style.display = diff ? "inline-flex" : "none";
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// ── Verrouillage par diagramme ──
|
|
59
|
+
var LOCK_OPEN_SVG = '<svg width="14" height="16" viewBox="0 0 14 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="1" y="7" width="12" height="8" rx="2"/><path d="M4 7V4.5a3 3 0 0 1 6 0"/></svg>';
|
|
60
|
+
var LOCK_CLOSED_SVG = '<svg width="14" height="16" viewBox="0 0 14 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="1" y="7" width="12" height="8" rx="2"/><path d="M4 7V5a3 3 0 0 1 6 0v2"/></svg>';
|
|
61
|
+
|
|
62
|
+
function getLockMap() {
|
|
63
|
+
try { return JSON.parse(localStorage.getItem("diagrammes_lock") || "{}"); } catch(e) { return {}; }
|
|
64
|
+
}
|
|
65
|
+
function saveCurrentLock() {
|
|
66
|
+
var diag = getCurrentDiagram();
|
|
67
|
+
if (!diag) return;
|
|
68
|
+
var map = getLockMap();
|
|
69
|
+
map[diag.id] = boardLocked;
|
|
70
|
+
localStorage.setItem("diagrammes_lock", JSON.stringify(map));
|
|
71
|
+
}
|
|
72
|
+
function restoreLockForDiagram(diagId) {
|
|
73
|
+
var diag = findDiagramById(diagId, diagramsList);
|
|
74
|
+
if (!diag) return;
|
|
75
|
+
var map = getLockMap();
|
|
76
|
+
boardLocked = map[diag.id] === true;
|
|
77
|
+
}
|
|
78
|
+
function toggleBoardLock() {
|
|
79
|
+
boardLocked = !boardLocked;
|
|
80
|
+
saveCurrentLock();
|
|
81
|
+
if (boardLocked) {
|
|
82
|
+
selectedId = null; selectedType = null;
|
|
83
|
+
selectedIds = [];
|
|
84
|
+
document.getElementById("colorPanel").style.display = "none";
|
|
85
|
+
renderAll();
|
|
86
|
+
}
|
|
87
|
+
updateLockBtn();
|
|
88
|
+
}
|
|
89
|
+
function updateLockBtn() {
|
|
90
|
+
var btn = document.getElementById("btnLock");
|
|
91
|
+
if (!btn) return;
|
|
92
|
+
btn.innerHTML = boardLocked ? LOCK_CLOSED_SVG : LOCK_OPEN_SVG;
|
|
93
|
+
btn.title = boardLocked ? "Déverrouiller le diagramme" : "Verrouiller le diagramme";
|
|
94
|
+
btn.classList.toggle("active", boardLocked);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// ── Zoom par diagramme ──
|
|
98
|
+
function getZoomMap() {
|
|
99
|
+
try { return JSON.parse(localStorage.getItem("diagrammes_zoom") || "{}"); } catch(e) { return {}; }
|
|
100
|
+
}
|
|
101
|
+
function saveCurrentZoom() {
|
|
102
|
+
var diag = getCurrentDiagram();
|
|
103
|
+
if (!diag) return;
|
|
104
|
+
var map = getZoomMap();
|
|
105
|
+
map[diag.id] = viewTransform.scale;
|
|
106
|
+
localStorage.setItem("diagrammes_zoom", JSON.stringify(map));
|
|
107
|
+
}
|
|
108
|
+
function restoreZoomForDiagram(diagId) {
|
|
109
|
+
var diag = findDiagramById(diagId, diagramsList);
|
|
110
|
+
if (!diag) return;
|
|
111
|
+
var map = getZoomMap();
|
|
112
|
+
viewTransform.scale = map[diag.id] !== undefined ? map[diag.id] : 1;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// ── File System Access API ──
|
|
116
|
+
var IDB_KEY_DIAG = "diagrammes";
|
|
117
|
+
var IDB_KEY_DIR = "diagrammes_dir";
|
|
118
|
+
|
|
119
|
+
function ouvrirDB_Diag() {
|
|
120
|
+
return new Promise(function (resolve, reject) {
|
|
121
|
+
var req = indexedDB.open("doc-survival-kit-db", 1);
|
|
122
|
+
req.onupgradeneeded = function (e) {
|
|
123
|
+
e.target.result.createObjectStore("fileHandles");
|
|
124
|
+
};
|
|
125
|
+
req.onsuccess = function (e) { resolve(e.target.result); };
|
|
126
|
+
req.onerror = function (e) { reject(e.target.error); };
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function sauvegarderHandle_Diag(handle) {
|
|
131
|
+
return ouvrirDB_Diag().then(function (db) {
|
|
132
|
+
return new Promise(function (resolve, reject) {
|
|
133
|
+
var tx = db.transaction("fileHandles", "readwrite");
|
|
134
|
+
tx.objectStore("fileHandles").put(handle, IDB_KEY_DIAG);
|
|
135
|
+
tx.oncomplete = resolve;
|
|
136
|
+
tx.onerror = function (e) { reject(e.target.error); };
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function recupererHandle_Diag() {
|
|
142
|
+
return ouvrirDB_Diag().then(function (db) {
|
|
143
|
+
return new Promise(function (resolve, reject) {
|
|
144
|
+
var tx = db.transaction("fileHandles", "readonly");
|
|
145
|
+
var req = tx.objectStore("fileHandles").get(IDB_KEY_DIAG);
|
|
146
|
+
req.onsuccess = function (e) { resolve(e.target.result || null); };
|
|
147
|
+
req.onerror = function (e) { reject(e.target.error); };
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function sauvegarderDirHandle(handle) {
|
|
153
|
+
return ouvrirDB_Diag().then(function (db) {
|
|
154
|
+
return new Promise(function (resolve, reject) {
|
|
155
|
+
var tx = db.transaction("fileHandles", "readwrite");
|
|
156
|
+
tx.objectStore("fileHandles").put(handle, IDB_KEY_DIR);
|
|
157
|
+
tx.oncomplete = resolve;
|
|
158
|
+
tx.onerror = function (e) { reject(e.target.error); };
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function recupererDirHandle() {
|
|
164
|
+
return ouvrirDB_Diag().then(function (db) {
|
|
165
|
+
return new Promise(function (resolve, reject) {
|
|
166
|
+
var tx = db.transaction("fileHandles", "readonly");
|
|
167
|
+
var req = tx.objectStore("fileHandles").get(IDB_KEY_DIR);
|
|
168
|
+
req.onsuccess = function (e) { resolve(e.target.result || null); };
|
|
169
|
+
req.onerror = function (e) { reject(e.target.error); };
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function enregistrerDiagrammes() {
|
|
175
|
+
if (!("showDirectoryPicker" in window)) return;
|
|
176
|
+
recupererHandle_Diag().then(function (handle) {
|
|
177
|
+
if (!handle) {
|
|
178
|
+
document.getElementById("erreurFichierDiag").style.display = "none";
|
|
179
|
+
document.getElementById("modalPremiereSauvegardeDiag").classList.add("open");
|
|
180
|
+
} else {
|
|
181
|
+
ecrireFichierDiag(handle);
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function fermerModalSauvegardeDiag() {
|
|
187
|
+
document.getElementById("modalPremiereSauvegardeDiag").classList.remove("open");
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function ouvrirSelecteurFichierDiag() {
|
|
191
|
+
fermerModalSauvegardeDiag();
|
|
192
|
+
var capturedDir = null;
|
|
193
|
+
window.showDirectoryPicker()
|
|
194
|
+
.then(function (dir) {
|
|
195
|
+
capturedDir = dir;
|
|
196
|
+
sauvegarderDirHandle(dir);
|
|
197
|
+
return dir.getFileHandle("diagrammes.js");
|
|
198
|
+
})
|
|
199
|
+
.then(function (handle) {
|
|
200
|
+
document.getElementById("erreurFichierDiag").style.display = "none";
|
|
201
|
+
return sauvegarderHandle_Diag(handle).then(function () {
|
|
202
|
+
return ecrireFichierDiag(handle);
|
|
203
|
+
}).then(function () {
|
|
204
|
+
if (pendingImageBlob && capturedDir) {
|
|
205
|
+
var blob = pendingImageBlob;
|
|
206
|
+
pendingImageBlob = null;
|
|
207
|
+
enregistrerImageDansBoard(blob, capturedDir);
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
})
|
|
211
|
+
.catch(function (err) {
|
|
212
|
+
if (err.name === "NotFoundError") {
|
|
213
|
+
document.getElementById("erreurFichierDiag").style.display = "block";
|
|
214
|
+
document.getElementById("modalPremiereSauvegardeDiag").classList.add("open");
|
|
215
|
+
} else if (err.name !== "AbortError") {
|
|
216
|
+
console.error(err);
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function ecrireFichierDiag(handle) {
|
|
222
|
+
return handle.queryPermission({ mode: "readwrite" })
|
|
223
|
+
.then(function (p) {
|
|
224
|
+
if (p !== "granted") return handle.requestPermission({ mode: "readwrite" });
|
|
225
|
+
return p;
|
|
226
|
+
})
|
|
227
|
+
.then(function (p) {
|
|
228
|
+
if (p !== "granted") return;
|
|
229
|
+
var content = "var diagrammesDefaut = " + JSON.stringify(diagramsList, null, 2) + ";\n";
|
|
230
|
+
return handle.createWritable().then(function (w) {
|
|
231
|
+
return w.write(content).then(function () { return w.close(); });
|
|
232
|
+
}).then(function () {
|
|
233
|
+
diagrammesDefaut = JSON.parse(JSON.stringify(diagramsList));
|
|
234
|
+
checkDiffDiagrammes();
|
|
235
|
+
});
|
|
236
|
+
})
|
|
237
|
+
.catch(function (err) { console.error(err); });
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// ── Image paste ──
|
|
241
|
+
function handleImagePaste(blob) {
|
|
242
|
+
recupererDirHandle().then(function (dirHandle) {
|
|
243
|
+
if (!dirHandle) {
|
|
244
|
+
pendingImageBlob = blob;
|
|
245
|
+
document.getElementById("erreurFichierDiag").style.display = "none";
|
|
246
|
+
document.getElementById("modalPremiereSauvegardeDiag").classList.add("open");
|
|
247
|
+
} else {
|
|
248
|
+
dirHandle.queryPermission({ mode: "readwrite" }).then(function (p) {
|
|
249
|
+
if (p !== "granted") return dirHandle.requestPermission({ mode: "readwrite" }).then(function (p2) { return p2; });
|
|
250
|
+
return p;
|
|
251
|
+
}).then(function (p) {
|
|
252
|
+
if (p === "granted") enregistrerImageDansBoard(blob, dirHandle);
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function enregistrerImageDansBoard(blob, dirHandle) {
|
|
259
|
+
var filename = "img_" + Date.now() + ".png";
|
|
260
|
+
var src = "images/" + filename;
|
|
261
|
+
dirHandle.getDirectoryHandle("images", { create: true })
|
|
262
|
+
.then(function (imagesDir) {
|
|
263
|
+
return imagesDir.getFileHandle(filename, { create: true });
|
|
264
|
+
})
|
|
265
|
+
.then(function (fileHandle) {
|
|
266
|
+
return fileHandle.createWritable().then(function (w) {
|
|
267
|
+
return w.write(blob).then(function () { return w.close(); });
|
|
268
|
+
});
|
|
269
|
+
})
|
|
270
|
+
.then(function () {
|
|
271
|
+
// Lire les dimensions réelles de l'image
|
|
272
|
+
var url = URL.createObjectURL(blob);
|
|
273
|
+
var img = new Image();
|
|
274
|
+
img.onload = function () {
|
|
275
|
+
URL.revokeObjectURL(url);
|
|
276
|
+
var maxW = 600;
|
|
277
|
+
var w = Math.min(img.naturalWidth, maxW);
|
|
278
|
+
var h = Math.round(img.naturalHeight * (w / img.naturalWidth));
|
|
279
|
+
// Centrer sur la vue courante
|
|
280
|
+
var svg = document.getElementById("canvas");
|
|
281
|
+
var r = svg.getBoundingClientRect();
|
|
282
|
+
var cx = Math.round((r.width / 2 - viewTransform.x) / viewTransform.scale - w / 2);
|
|
283
|
+
var cy = Math.round((r.height / 2 - viewTransform.y) / viewTransform.scale - h / 2);
|
|
284
|
+
var diag = getCurrentDiagram();
|
|
285
|
+
var shape = { id: "s" + Date.now(), type: "image", x: cx, y: cy, w: w, h: h, text: "", color: "", src: src };
|
|
286
|
+
diag.shapes.push(shape);
|
|
287
|
+
selectedIds = [shape.id]; selectedId = shape.id; selectedType = "shape";
|
|
288
|
+
saveDiagrammes();
|
|
289
|
+
renderAll();
|
|
290
|
+
document.getElementById("colorPanel").style.display = "none";
|
|
291
|
+
};
|
|
292
|
+
img.src = url;
|
|
293
|
+
})
|
|
294
|
+
.catch(function (err) { console.error("Image paste error:", err); });
|
|
295
|
+
}
|