living-documentation 7.42.0 → 7.44.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/custom-shapes.js +104 -0
- package/dist/src/frontend/diagram/main.js +10 -1
- package/dist/src/frontend/diagram/network.js +1042 -531
- package/dist/src/frontend/diagram/node-panel.js +47 -0
- package/dist/src/frontend/diagram/node-rendering.js +190 -0
- package/dist/src/frontend/diagram/persistence.js +2 -0
- package/dist/src/frontend/diagram/ports.js +49 -14
- package/dist/src/frontend/diagram/selection-overlay.js +64 -13
- package/dist/src/frontend/diagram/state.js +2 -0
- package/dist/src/frontend/diagram.html +99 -0
- package/dist/src/frontend/i18n/en.json +15 -1
- package/dist/src/frontend/i18n/fr.json +15 -1
- package/dist/src/frontend/shape-editor.html +685 -0
- package/dist/src/routes/shape-libraries.d.ts +3 -0
- package/dist/src/routes/shape-libraries.d.ts.map +1 -0
- package/dist/src/routes/shape-libraries.js +116 -0
- package/dist/src/routes/shape-libraries.js.map +1 -0
- package/dist/src/server.d.ts.map +1 -1
- package/dist/src/server.js +3 -0
- package/dist/src/server.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { st } from './state.js';
|
|
2
|
+
|
|
3
|
+
export const CUSTOM_SHAPE_TYPE = 'custom-shape';
|
|
4
|
+
export const CUSTOM_SHAPE_TOOL_PREFIX = 'custom-shape:';
|
|
5
|
+
export const CUSTOM_SHAPE_DEFAULT_SIZE = 65;
|
|
6
|
+
|
|
7
|
+
export const DEFAULT_CUSTOM_ANCHORS = [
|
|
8
|
+
{ id: 'N', x: 0.5, y: 0 },
|
|
9
|
+
{ id: 'NE', x: 1, y: 0 },
|
|
10
|
+
{ id: 'E', x: 1, y: 0.5 },
|
|
11
|
+
{ id: 'SE', x: 1, y: 1 },
|
|
12
|
+
{ id: 'S', x: 0.5, y: 1 },
|
|
13
|
+
{ id: 'SW', x: 0, y: 1 },
|
|
14
|
+
{ id: 'W', x: 0, y: 0.5 },
|
|
15
|
+
{ id: 'NW', x: 0, y: 0 },
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
export function isCustomShapeTool(shape) {
|
|
19
|
+
return typeof shape === 'string' && shape.startsWith(CUSTOM_SHAPE_TOOL_PREFIX);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function customShapeIdFromTool(shape) {
|
|
23
|
+
return isCustomShapeTool(shape) ? shape.slice(CUSTOM_SHAPE_TOOL_PREFIX.length) : null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function getCustomShapeDefinition(id) {
|
|
27
|
+
if (!id) return null;
|
|
28
|
+
return st.customShapeDefs && st.customShapeDefs.get(id) || null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function getCustomShapeDefaultSize(id) {
|
|
32
|
+
const def = getCustomShapeDefinition(id);
|
|
33
|
+
return [
|
|
34
|
+
(def && def.width) || CUSTOM_SHAPE_DEFAULT_SIZE,
|
|
35
|
+
(def && def.height) || CUSTOM_SHAPE_DEFAULT_SIZE,
|
|
36
|
+
];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function getCustomShapeAnchors(id) {
|
|
40
|
+
const def = getCustomShapeDefinition(id);
|
|
41
|
+
return def && Array.isArray(def.anchors) && def.anchors.length
|
|
42
|
+
? def.anchors
|
|
43
|
+
: DEFAULT_CUSTOM_ANCHORS;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function getCustomShapeLabelPlacement(id) {
|
|
47
|
+
const def = getCustomShapeDefinition(id);
|
|
48
|
+
return def && ['center', 'below', 'above', 'right', 'left'].includes(def.labelPlacement)
|
|
49
|
+
? def.labelPlacement
|
|
50
|
+
: 'below';
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export async function loadCustomShapeLibraries() {
|
|
54
|
+
try {
|
|
55
|
+
const res = await fetch('/api/shape-libraries');
|
|
56
|
+
if (!res.ok) throw new Error('shape libraries unavailable');
|
|
57
|
+
const store = await res.json();
|
|
58
|
+
const libraries = Array.isArray(store.libraries) ? store.libraries : [];
|
|
59
|
+
st.customShapeLibraries = libraries;
|
|
60
|
+
st.customShapeDefs = new Map();
|
|
61
|
+
libraries.forEach((library) => {
|
|
62
|
+
(library.shapes || []).forEach((shape) => st.customShapeDefs.set(shape.id, shape));
|
|
63
|
+
});
|
|
64
|
+
} catch {
|
|
65
|
+
st.customShapeLibraries = [];
|
|
66
|
+
st.customShapeDefs = new Map();
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function renderCustomShapeBar() {
|
|
71
|
+
const bar = document.getElementById('customShapeBar');
|
|
72
|
+
const body = document.getElementById('customShapeBarBody');
|
|
73
|
+
if (!bar || !body) return;
|
|
74
|
+
|
|
75
|
+
body.innerHTML = '';
|
|
76
|
+
const libraries = st.customShapeLibraries || [];
|
|
77
|
+
const shapes = libraries.flatMap((library) =>
|
|
78
|
+
(library.shapes || [])
|
|
79
|
+
.filter((shape) => shape.showInDiagram !== false)
|
|
80
|
+
.map((shape) => ({ ...shape, libraryName: library.name })),
|
|
81
|
+
);
|
|
82
|
+
bar.classList.toggle('hidden', shapes.length === 0);
|
|
83
|
+
|
|
84
|
+
shapes.forEach((shape) => {
|
|
85
|
+
const btn = document.createElement('button');
|
|
86
|
+
btn.type = 'button';
|
|
87
|
+
btn.className = 'custom-shape-btn';
|
|
88
|
+
btn.title = `${shape.libraryName || ''}${shape.libraryName ? ' · ' : ''}${shape.name}`;
|
|
89
|
+
btn.dataset.customShapeId = shape.id;
|
|
90
|
+
|
|
91
|
+
const img = document.createElement('img');
|
|
92
|
+
img.src = shape.imageSrc;
|
|
93
|
+
img.alt = '';
|
|
94
|
+
img.draggable = false;
|
|
95
|
+
btn.appendChild(img);
|
|
96
|
+
|
|
97
|
+
btn.addEventListener('click', () => {
|
|
98
|
+
window.dispatchEvent(new CustomEvent('diagram:setTool', {
|
|
99
|
+
detail: { tool: 'addNode', shape: `${CUSTOM_SHAPE_TOOL_PREFIX}${shape.id}` },
|
|
100
|
+
}));
|
|
101
|
+
});
|
|
102
|
+
body.appendChild(btn);
|
|
103
|
+
});
|
|
104
|
+
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
import { st, markDirty } from './state.js';
|
|
5
5
|
import { TOOL_BTN_MAP } from './constants.js';
|
|
6
|
-
import { showNodePanel, hideNodePanel, setNodeColor, setNodeBgOpacity, changeNodeFontSize, setTextAlign, setTextValign, changeZOrder, activateStamp, cancelStamp, stepRotate, toggleNodeLock } from './node-panel.js';
|
|
6
|
+
import { showNodePanel, hideNodePanel, setNodeColor, setNodeBgOpacity, changeNodeFontSize, setTextAlign, setTextValign, setCustomShapeLabelPlacement, changeZOrder, activateStamp, cancelStamp, stepRotate, toggleNodeLock } from './node-panel.js';
|
|
7
7
|
import { groupNodes, ungroupNodes } from './groups.js';
|
|
8
8
|
import { showLinkPanel, hideLinkPanel } from './link-panel.js';
|
|
9
9
|
import { hideEdgePanel, setEdgeArrow, setEdgeDashes, changeEdgeFontSize, stepEdgeLabelRotation, clearEdgePorts, setEdgeColor, changeEdgeWidth, toggleEdgeLock, resetEdgeLabelWidth, stepEdgeLabelOffset, resetEdgeLabelOffset } from './edge-panel.js';
|
|
@@ -22,6 +22,7 @@ import { promptImageName } from './image-name-modal.js';
|
|
|
22
22
|
import { showToast } from './toast.js';
|
|
23
23
|
import { t } from './t.js';
|
|
24
24
|
import { initEvidenceMode, toggleEvidenceMode } from './evidence.js';
|
|
25
|
+
import { customShapeIdFromTool, isCustomShapeTool } from './custom-shapes.js';
|
|
25
26
|
|
|
26
27
|
// ── Tool management ───────────────────────────────────────────────────────────
|
|
27
28
|
|
|
@@ -36,6 +37,12 @@ function setTool(tool, shape) {
|
|
|
36
37
|
const key = tool === 'addNode' ? `addNode:${shape || st.pendingShape}` : tool;
|
|
37
38
|
const btn = document.getElementById(TOOL_BTN_MAP[key]);
|
|
38
39
|
if (btn) btn.classList.add('tool-active');
|
|
40
|
+
document.querySelectorAll('.custom-shape-btn').forEach((b) => {
|
|
41
|
+
b.classList.toggle(
|
|
42
|
+
'tool-active',
|
|
43
|
+
tool === 'addNode' && isCustomShapeTool(shape || st.pendingShape) && b.dataset.customShapeId === customShapeIdFromTool(shape || st.pendingShape),
|
|
44
|
+
);
|
|
45
|
+
});
|
|
39
46
|
|
|
40
47
|
document.getElementById('vis-canvas').classList.toggle('cursor-crosshair', tool === 'addNode' || tool === 'addEdge');
|
|
41
48
|
|
|
@@ -157,6 +164,8 @@ initEvidenceMode();
|
|
|
157
164
|
document.getElementById('nodePanel').addEventListener('click', (e) => {
|
|
158
165
|
const colorBtn = e.target.closest('[data-color]');
|
|
159
166
|
if (colorBtn) setNodeColor(colorBtn.dataset.color);
|
|
167
|
+
const labelPlacementBtn = e.target.closest('[data-label-placement]');
|
|
168
|
+
if (labelPlacementBtn) setCustomShapeLabelPlacement(labelPlacementBtn.dataset.labelPlacement);
|
|
160
169
|
});
|
|
161
170
|
document.getElementById('btnNodeLock').addEventListener('click', toggleNodeLock);
|
|
162
171
|
document.getElementById('btnNodeLabelEdit').addEventListener('click', startLabelEdit);
|