living-documentation 7.38.0 → 7.40.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/frontend/diagram/clipboard.js +22 -7
- package/dist/src/frontend/diagram/node-panel.js +18 -0
- package/dist/src/frontend/diagram/node-rendering.js +1 -0
- package/dist/src/frontend/diagram/ports.js +1 -1
- package/dist/src/frontend/diagram/state.js +1 -0
- package/dist/src/frontend/diagram.html +5 -0
- package/dist/src/frontend/i18n/en.json +1 -0
- package/dist/src/frontend/i18n/fr.json +1 -0
- package/package.json +1 -1
|
@@ -48,15 +48,25 @@ async function _selectionToBlob() {
|
|
|
48
48
|
const cropW = Math.ceil(br.x - tl.x + PAD * 2);
|
|
49
49
|
const cropH = Math.ceil(br.y - tl.y + PAD * 2);
|
|
50
50
|
|
|
51
|
-
// Temporarily
|
|
51
|
+
// Temporarily clear every selection state so interactive chrome (selected
|
|
52
|
+
// edge color, label resize handles, anchor handles) does not appear in PNGs.
|
|
52
53
|
const savedNodeIds = [...st.selectedNodeIds];
|
|
54
|
+
const savedEdgeIds = [...(st.selectedEdgeIds || [])];
|
|
55
|
+
const savedNetworkNodeIds = st.network.getSelectedNodes();
|
|
56
|
+
const savedNetworkEdgeIds = st.network.getSelectedEdges();
|
|
53
57
|
st.network.unselectAll();
|
|
54
58
|
st.selectedNodeIds = [];
|
|
59
|
+
st.selectedEdgeIds = [];
|
|
60
|
+
st.exportingPng = true;
|
|
55
61
|
st.network.redraw();
|
|
62
|
+
await new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(resolve)));
|
|
56
63
|
|
|
57
64
|
const visCanvas = document.querySelector('#vis-canvas canvas');
|
|
58
65
|
if (!visCanvas) {
|
|
59
|
-
st.
|
|
66
|
+
st.exportingPng = false;
|
|
67
|
+
st.selectedNodeIds = savedNodeIds;
|
|
68
|
+
st.selectedEdgeIds = savedEdgeIds;
|
|
69
|
+
st.network.setSelection({ nodes: savedNetworkNodeIds, edges: savedNetworkEdgeIds });
|
|
60
70
|
return null;
|
|
61
71
|
}
|
|
62
72
|
|
|
@@ -73,9 +83,10 @@ async function _selectionToBlob() {
|
|
|
73
83
|
cropX * dpr, cropY * dpr, cropW * dpr, cropH * dpr,
|
|
74
84
|
0, 0, out.width, out.height
|
|
75
85
|
);
|
|
86
|
+
st.exportingPng = false;
|
|
76
87
|
|
|
77
88
|
const blob = await new Promise((res) => out.toBlob(res, 'image/png'));
|
|
78
|
-
return { blob, savedNodeIds };
|
|
89
|
+
return { blob, savedNodeIds, savedEdgeIds, savedNetworkNodeIds, savedNetworkEdgeIds };
|
|
79
90
|
}
|
|
80
91
|
|
|
81
92
|
// ── Copy selection as PNG (to clipboard) ─────────────────────────────────────
|
|
@@ -83,14 +94,16 @@ async function _selectionToBlob() {
|
|
|
83
94
|
export async function copySelectionAsPng() {
|
|
84
95
|
const result = await _selectionToBlob();
|
|
85
96
|
if (!result) return;
|
|
86
|
-
const { blob, savedNodeIds } = result;
|
|
97
|
+
const { blob, savedNodeIds, savedEdgeIds, savedNetworkNodeIds, savedNetworkEdgeIds } = result;
|
|
87
98
|
try {
|
|
88
99
|
await navigator.clipboard.write([new ClipboardItem({ 'image/png': blob })]);
|
|
89
100
|
showToast(t('diagram.toast.png_copied'));
|
|
90
101
|
} catch {
|
|
91
102
|
showToast(t('diagram.toast.png_copy_error'), 'error');
|
|
92
103
|
} finally {
|
|
93
|
-
st.
|
|
104
|
+
st.selectedNodeIds = savedNodeIds;
|
|
105
|
+
st.selectedEdgeIds = savedEdgeIds;
|
|
106
|
+
st.network.setSelection({ nodes: savedNetworkNodeIds, edges: savedNetworkEdgeIds });
|
|
94
107
|
}
|
|
95
108
|
}
|
|
96
109
|
|
|
@@ -99,7 +112,7 @@ export async function copySelectionAsPng() {
|
|
|
99
112
|
export async function saveSelectionAsPng(filename) {
|
|
100
113
|
const result = await _selectionToBlob();
|
|
101
114
|
if (!result) return;
|
|
102
|
-
const { blob, savedNodeIds } = result;
|
|
115
|
+
const { blob, savedNodeIds, savedEdgeIds, savedNetworkNodeIds, savedNetworkEdgeIds } = result;
|
|
103
116
|
try {
|
|
104
117
|
const name = filename.replace(/\.[^.]+$/, ''); // strip extension
|
|
105
118
|
await uploadImageBlob(blob, 'png', name);
|
|
@@ -107,7 +120,9 @@ export async function saveSelectionAsPng(filename) {
|
|
|
107
120
|
} catch {
|
|
108
121
|
showToast(t('diagram.toast.diagram_save_png_error'), 'error');
|
|
109
122
|
} finally {
|
|
110
|
-
st.
|
|
123
|
+
st.selectedNodeIds = savedNodeIds;
|
|
124
|
+
st.selectedEdgeIds = savedEdgeIds;
|
|
125
|
+
st.network.setSelection({ nodes: savedNetworkNodeIds, edges: savedNetworkEdgeIds });
|
|
111
126
|
}
|
|
112
127
|
}
|
|
113
128
|
|
|
@@ -74,6 +74,22 @@ function syncNodeLockButton() {
|
|
|
74
74
|
btn.classList.toggle('tool-active', allLocked);
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
+
function syncNodeFontSizeValue() {
|
|
78
|
+
const el = document.getElementById('nodeFontSizeValue');
|
|
79
|
+
if (!el) return;
|
|
80
|
+
const sizes = (st.selectedNodeIds || []).map((id) => {
|
|
81
|
+
const n = st.nodes && st.nodes.get(id);
|
|
82
|
+
return n && n.shapeType !== 'anchor' ? (n.fontSize || 13) : null;
|
|
83
|
+
}).filter((size) => size !== null);
|
|
84
|
+
|
|
85
|
+
if (!sizes.length) {
|
|
86
|
+
el.textContent = '–';
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
const first = sizes[0];
|
|
90
|
+
el.textContent = sizes.every((size) => size === first) ? String(first) : '–';
|
|
91
|
+
}
|
|
92
|
+
|
|
77
93
|
function setEdgeLocked(edge, locked) {
|
|
78
94
|
if (!edge) return;
|
|
79
95
|
const fromN = st.nodes && st.nodes.get(edge.from);
|
|
@@ -94,6 +110,7 @@ export function showNodePanel() {
|
|
|
94
110
|
document.getElementById('nodePanel').classList.remove('hidden');
|
|
95
111
|
document.getElementById('nodePanelControls').classList.remove('hidden');
|
|
96
112
|
syncNodeLockButton();
|
|
113
|
+
syncNodeFontSizeValue();
|
|
97
114
|
// Sync the opacity slider with the first selected node's current value so the
|
|
98
115
|
// slider reflects the live state rather than whatever position it was left at.
|
|
99
116
|
const slider = document.getElementById('nodeBgOpacity');
|
|
@@ -171,6 +188,7 @@ export function changeNodeFontSize(delta) {
|
|
|
171
188
|
st.nodes.update({ id, fontSize: newSize });
|
|
172
189
|
});
|
|
173
190
|
persistNodeStyle();
|
|
191
|
+
syncNodeFontSizeValue();
|
|
174
192
|
forceRedraw();
|
|
175
193
|
markDirty();
|
|
176
194
|
}
|
|
@@ -123,6 +123,7 @@ export function getNearestPort(nodeId, canvasPos) {
|
|
|
123
123
|
* `highlightedPort` (if any) is rendered larger and fully orange.
|
|
124
124
|
*/
|
|
125
125
|
export function drawPortDots(ctx, nodeId, highlightedPort) {
|
|
126
|
+
if (st.exportingPng) return;
|
|
126
127
|
for (const key of PORT_KEYS) {
|
|
127
128
|
const p = getPortPosition(nodeId, key);
|
|
128
129
|
if (!p) continue;
|
|
@@ -383,4 +384,3 @@ function _distToSegment(p, a, b) {
|
|
|
383
384
|
const t = Math.max(0, Math.min(1, ((p.x - a.x) * dx + (p.y - a.y) * dy) / len2));
|
|
384
385
|
return Math.hypot(p.x - (a.x + t * dx), p.y - (a.y + t * dy));
|
|
385
386
|
}
|
|
386
|
-
|
|
@@ -31,6 +31,7 @@ export const st = {
|
|
|
31
31
|
edgeLabelCanvasPos: {}, // edgeId → {x, y} canvas coords of last drawn label, used by label editor
|
|
32
32
|
edgeLabelBBox: {}, // edgeId → {cx, cy, w, h, rotation} canvas world coords, used by label resize
|
|
33
33
|
freeArrowFirstPoint: null, // addEdge two-click flow: {x, y} canvas coords of first click, or null
|
|
34
|
+
exportingPng: false, // suppress interactive-only canvas chrome during PNG export
|
|
34
35
|
};
|
|
35
36
|
|
|
36
37
|
export function markDirty() {
|
|
@@ -681,6 +681,11 @@
|
|
|
681
681
|
>
|
|
682
682
|
Aa−
|
|
683
683
|
</button>
|
|
684
|
+
<span
|
|
685
|
+
id="nodeFontSizeValue"
|
|
686
|
+
class="inline-flex items-center justify-center min-w-[2.25rem] h-6 px-1 text-[11px] font-mono text-gray-700 dark:text-gray-200 border border-gray-300 dark:border-gray-600 rounded bg-white/80 dark:bg-gray-800/80"
|
|
687
|
+
data-i18n-title="diagram.node_panel.font_size_value"
|
|
688
|
+
>13</span>
|
|
684
689
|
<button
|
|
685
690
|
id="btnNodeFontIncrease"
|
|
686
691
|
class="tool-btn !w-8 !h-6"
|
|
@@ -434,6 +434,7 @@
|
|
|
434
434
|
"diagram.node_panel.edit_link": "Add / edit link",
|
|
435
435
|
"diagram.node_panel.bg_opacity": "Background opacity",
|
|
436
436
|
"diagram.node_panel.font_decrease": "Decrease font size",
|
|
437
|
+
"diagram.node_panel.font_size_value": "Current font size",
|
|
437
438
|
"diagram.node_panel.font_increase": "Increase font size",
|
|
438
439
|
"diagram.node_panel.align_left": "Align left",
|
|
439
440
|
"diagram.node_panel.align_center": "Align center",
|
|
@@ -434,6 +434,7 @@
|
|
|
434
434
|
"diagram.node_panel.edit_link": "Ajouter / modifier un lien",
|
|
435
435
|
"diagram.node_panel.bg_opacity": "Opacité du fond",
|
|
436
436
|
"diagram.node_panel.font_decrease": "Réduire la police",
|
|
437
|
+
"diagram.node_panel.font_size_value": "Taille de police actuelle",
|
|
437
438
|
"diagram.node_panel.font_increase": "Agrandir la police",
|
|
438
439
|
"diagram.node_panel.align_left": "Aligner à gauche",
|
|
439
440
|
"diagram.node_panel.align_center": "Centrer horizontalement",
|