@weasel-js/diagram 1.10.1 → 1.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1160 -26
- package/dist/chunk-43LDZ6L4.js +2053 -0
- package/dist/chunk-43LDZ6L4.js.map +1 -0
- package/dist/chunk-CI3I3I6T.js +3600 -0
- package/dist/chunk-CI3I3I6T.js.map +1 -0
- package/dist/chunk-IM6DHWZO.js +3220 -0
- package/dist/chunk-IM6DHWZO.js.map +1 -0
- package/dist/chunk-QKSWZEJH.js +312 -0
- package/dist/chunk-QKSWZEJH.js.map +1 -0
- package/dist/chunk-T6DQT553.js +1431 -0
- package/dist/chunk-T6DQT553.js.map +1 -0
- package/dist/chunk-WDIE35AK.js +3 -0
- package/dist/chunk-WDIE35AK.js.map +1 -0
- package/dist/dataTypes-B-iEwKZp.d.ts +553 -0
- package/dist/effect-audio.d.ts +138 -0
- package/dist/effect-audio.js +527 -0
- package/dist/effect-audio.js.map +1 -0
- package/dist/effect.d.ts +782 -0
- package/dist/effect.js +2542 -0
- package/dist/effect.js.map +1 -0
- package/dist/icons.table.d.ts +6 -0
- package/dist/icons.table.js +32 -0
- package/dist/icons.table.js.map +1 -0
- package/dist/icons.table.json +24 -0
- package/dist/index.d.ts +2270 -355
- package/dist/index.js +953 -1331
- package/dist/index.js.map +1 -1
- package/dist/layout.d.ts +3 -2
- package/dist/layout.entry-CH9gc0CY.d.ts +512 -0
- package/dist/layout.js +3 -1
- package/dist/stageTypes-DvnR8cKQ.d.ts +189 -0
- package/dist/{layout.entry-BUkiRKuX.d.ts → types-BbHunLXm.d.ts} +382 -261
- package/package.json +32 -7
- package/dist/chunk-JZ4CVLAB.js +0 -1247
- package/dist/chunk-JZ4CVLAB.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,1436 +1,1058 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
import {
|
|
1
|
+
import './chunk-WDIE35AK.js';
|
|
2
|
+
import { createConnectAction, avoidSceneInput, sceneEdges, inOrUnder, withHopsData, edgePolylines, themeWrites, hopDataOps, diagramDropParentAt, containerBuild } from './chunk-CI3I3I6T.js';
|
|
3
|
+
export { ADD_LANE_ACTION_ID, CONNECT_ACTION_ID, CONTAINER_STENCILS, DEFAULT_EDGE_STROKE, DiagramView, EASED_LAYOUTS, EDGE_END_AFFORDANCE_KIND, EDGE_END_LAYER_ID, EDIT_CONTAINER_TITLE_ACTION_ID, GRAB_EDGE_END_ACTION_ID, GRAB_LABEL_ACTION_ID, GRAB_PORT_ACTION_ID, GRAB_WAYPOINT_ACTION_ID, LANE_AFFORDANCE_KIND, LANE_LAYER_ID, LANE_RESIZE_ACTION_ID, LAYOUTS, LAYOUT_ACTION_ID, MOVE_LABEL_ACTION_ID, PORT_AFFORDANCE_KIND, PORT_LAYER_ID, RECONNECT_ACTION_ID, RELEASE_MEMBERS_ACTION_ID, REMOVE_WAYPOINT_ACTION_ID, REORDER_ACTION_ID, RESET_LABEL_ACTION_ID, RESET_ROUTE_ACTION_ID, ROUTER_LABELS, ROUTE_EDIT_ACTION_ID, SELECT_LABEL_EDGE_ACTION_ID, SET_HOPS_ACTION_ID, SET_ROUTER_ACTION_IDS, STEP_REORDER_ACTION_ID, WAYPOINT_AFFORDANCE_KIND, WAYPOINT_HIT_PX, WAYPOINT_LAYER_ID, addLane, applyLayout, avoidSceneInput, bodyAt, commitEdgeToScene, commitLabelToScene, commitLaneResizeToScene, commitReconnectToScene, commitRouteToScene, commitRouterToScene, connectBinding, containerBuild, createAddLaneAction, createConnectAction, createDiagramContribution, createEdgeEndAffordance, createLabelDragAction, createLaneAffordance, createLaneResizeAction, createLayoutAction, createPortAffordance, createReconnectAction, createReleaseMembersAction, createRemoveWaypointAction, createReorderAction, createResetLabelAction, createResetRouteAction, createRouteEditAction, createSetHopsAction, createSetRouterAction, createSpecHops, createSpecLabelPlacer, createSpecRouter, createStepReorderAction, createWaypointAffordance, defaultCanConnect, diagramCanDropInto, diagramContainers, diagramDropParentAt, diagramEdgeEnds, diagramLabelDrag, diagramLayoutOf, diagramPorts, diagramResizable, diagramShape, diagramWaypoints, dragSegment, dragVertex, easedProducer, edgeEndLayer, edgeEndScratchOf, editContainerTitleAction, editContainerTitleBinding, forceProducer, grabPortAction, grabPortBindings, groupReorderContribution, groupReorderSlots, headerIdOf, insertWaypoint, isBodyEnd, laneLayer, laneResizeFrame, laneScratchOf, lanesOf, layoutPoses, movedLabelData, pinSegment, poolAxisOf, poolsOf, portLayer, portScratchOf, reconnectBinding, regionHeaderAt, registerDiagramShape, releaseMembers, removeSegment, removeWaypoint, reorderBinding, reorderContribution, routeHandles, routeStateOf, routeWrites, routedData, routerActions, routesOf, sceneParticipants, segmentAxis, sequenceSlots, themeWrites, unpinSegment, useLiveLayout, useMirroredSelection, waypointLayer, waypointScratchOf, withRouter } from './chunk-CI3I3I6T.js';
|
|
4
|
+
import { createAvoidRouter, HOPS_DOC_KEY, edgeHops, placeLabels } from './chunk-T6DQT553.js';
|
|
5
|
+
export { AVOID_DEFAULTS, DEFAULT_HOP_SIZE, HOPS_DOC_KEY, createAvoidRouter, edgeHops, placeLabels, simplifyStops } from './chunk-T6DQT553.js';
|
|
6
|
+
import { NODE, NODE_STROKE_WIDTH, rowTextStyle, DEFAULT_FONT, bodyLayoutOf, offPageTagLook, diagramThemeOf, offPageSplits, offPageTagOf, DIAGRAM_THEMES, DIAGRAM_THEME_KEY, themeDocValue, DIAGRAM_ROLES, diagramLabelOf, diagramContainerOf, diagramGroupOf, offPageTagId, offPageFarText, offPageTagSpec, offPageTagData, offPageTagSize, relayoutBodies, labelSize, labelStyleOf } from './chunk-43LDZ6L4.js';
|
|
7
|
+
export { ANCHOR_DERIVE_POSE, CONTAINER_HEADER, CONTAINER_LAYOUTS, DEFAULT_LANE_SIZE, DEFAULT_POOL_ACROSS, DIAGRAM_ANCHOR, DIAGRAM_CONTAINER_LAYOUT, DIAGRAM_EDGE, DIAGRAM_GROUP, DIAGRAM_LABEL, DIAGRAM_LANE_COLUMNS_LAYOUT, DIAGRAM_LANE_ROWS_LAYOUT, DIAGRAM_OFF_PAGE_TAG, DIAGRAM_POOL_COLUMNS_LAYOUT, DIAGRAM_POOL_ROWS_LAYOUT, DIAGRAM_ROLES, DIAGRAM_THEMES, DIAGRAM_THEME_KEY, EDGE_DERIVE_PATH, EDGE_STROKE_WIDTH, EDIT_EDGE_LABEL_ACTION_ID, GROUP_DERIVE_POSE, LABEL_DERIVE_POSE, LABEL_DRAG_AFFORDANCE_KIND, LABEL_DRAG_LAYER_ID, LABEL_PLACEMENT, LANE_HEADER_SIZE, LANE_STROKE_WIDTH, MIN_LANE_SIZE, NODE_STROKE_WIDTH, OFF_PAGE_STUB, OFF_PAGE_TAG_DERIVE_POSE, OFF_PAGE_TAG_PADDING, POOL_HEADER_SIZE, ROUTERS, anchorDerivePose, avoid, bezier, bodyBox, boxAround, buildGraph, containerShape, dataEdgeStyle, dataNodeStyle, dataOffPage, dataRegionStyle, diagramAnchorOf, diagramContainerLayout, diagramContainerOf, diagramGroupOf, diagramLabelOf, diagramMarkOf, diagramPages, diagramScene, diagramThemeFrom, diagramThemeOf, draggableLabels, dropEmptyEdgeLabel, edgeDerivePath, edgeIdOf, edgeLabelSpec, edgeRouter, editEdgeLabelAction, editEdgeLabelBinding, firstLanes, groupDerivePose, headerBox, labelDerivePose, labelDragLayer, labelScratchOf, labelSize, labelUnder, laneHeader, laneStackLayout, markShape, nextOffPageRef, offPageFarText, offPageSplits, offPageTagData, offPageTagDerivePose, offPageTagId, offPageTagLook, offPageTagOf, offPageTagSize, offPageTagSpec, offPageTagSpecs, offPageTagText, orthogonal, poolHeader, regionSpecs, registerMarkShape, relayoutBodies, resolveEnd, roleEdgeStyle, roleStyle, rowTextStyle, selfLoop, stackedLanes, straight, themeDocValue, themeEdgeStroke, themeFont, themeLabelFont, themeLabelStyle, titleIdOf, titlePose, withDiagramRegistry, withHops } from './chunk-43LDZ6L4.js';
|
|
8
|
+
import { buildBody, diagramEdgeOf, samePoints, diagramNodeOf, labelCenter } from './chunk-IM6DHWZO.js';
|
|
9
|
+
export { COMPASS, DEFAULT_GROUP_INSET, DEFAULT_NODE_GAP, DEFAULT_PORTS, DEFAULT_RANK_GAP, DIAGRAM_TRAIT_KEY, PORT_LABEL_GAP, anchorOffset, axesFor, backEdges, bodyOutline, bodyTrait, boxForContent, buildBody, canvasMeasure, chain, contentBox, createDiagramNodes, dataKeyReader, diagramEdgeOf, diagramNodeOf, expandGroupEdges, footprintNode, force, forceRelaxation, forestOf, graphOrder, insetOf, isPinned, labelCenter, layered, layeredPlan, layeredRoutes, layoutBody, layoutOwnsPort, layoutOwnsRoute, layoutRowPorts, layoutWritesRoute, marginOf, measureBody, outlinePath, outlinePolyline, packAcross, packClusters, pinnedSet, planOf, plannerPlan, portLabelBox, portLabelsOf, portOf, portSitesOf, portsOf, portsOnOutline, rankGaps, ranksByLength, ranksOf, rayHit, routeHolds, seededOrder, settle, sizeToBody, subpathOf, translated, tree, untilSettled } from './chunk-IM6DHWZO.js';
|
|
10
|
+
import { useMemo, useRef, useEffect } from 'react';
|
|
11
|
+
import { bodyPredicate, AUTO_POSE_DESCRIPTOR, defineTool, createInsertOp, INSERT_REPOSITION_BINDING, useDepRegistry, useLatest, useDepSource, commitOps, createSetDataOp, useOptionalDepRegistry, useTransformFollower, documentPose, placedPagesOf, PAGES_DOC_KEY, ActionDisabledReason, measureTextBounds, writtenBy, documentDepOf, createDeleteOp, createTransformOp, fitViewToPage, parseColor } from '@weasel-js/core';
|
|
12
|
+
import { PATH_M } from '@weasel-js/core/math';
|
|
9
13
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
14
|
+
var CONNECTOR_TOOL_ID = "diagram.connector";
|
|
15
|
+
var CONNECTOR_ACTION_ID = "diagram.connectorDraw";
|
|
16
|
+
function connectorTool(opts) {
|
|
17
|
+
return defineTool({
|
|
18
|
+
id: CONNECTOR_TOOL_ID,
|
|
19
|
+
hookName: "useConnectorTool",
|
|
20
|
+
cursor: "crosshair",
|
|
21
|
+
presentation: { label: "Connector", icon: (
|
|
22
|
+
/* @icon */
|
|
23
|
+
"core:arrow"
|
|
24
|
+
), group: "draw" },
|
|
25
|
+
actions: [createConnectAction({ ...opts, id: CONNECTOR_ACTION_ID, startFrom: "body", bodies: true })],
|
|
26
|
+
bindings: [{ spec: { kind: "drag" }, actionId: CONNECTOR_ACTION_ID }]
|
|
27
|
+
});
|
|
15
28
|
}
|
|
16
|
-
function
|
|
17
|
-
const
|
|
18
|
-
return
|
|
29
|
+
function useConnectorTool(opts) {
|
|
30
|
+
const { participants, read: read2, geometry, canConnect, showsPort, snapRadius, router, routers, commit, edgeStroke } = opts;
|
|
31
|
+
return useMemo(
|
|
32
|
+
() => connectorTool(opts),
|
|
33
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps -- `opts` is the fields listed
|
|
34
|
+
[participants, read2, geometry, canConnect, showsPort, snapRadius, router, routers, commit, edgeStroke]
|
|
35
|
+
);
|
|
19
36
|
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
37
|
+
var FLOWCHART_STENCILS = [
|
|
38
|
+
{ id: "process", label: "Process", outline: "rect", size: { width: 120, height: 60 }, icon: (
|
|
39
|
+
/* @icon */
|
|
40
|
+
"shape:rectangle"
|
|
41
|
+
) },
|
|
42
|
+
{ id: "decision", label: "Decision", outline: "diamond", size: { width: 120, height: 80 }, icon: (
|
|
43
|
+
/* @icon */
|
|
44
|
+
"shape:diamond"
|
|
45
|
+
) },
|
|
46
|
+
{ id: "terminal", label: "Terminal", outline: "stadium", size: { width: 120, height: 50 }, icon: (
|
|
47
|
+
/* @icon */
|
|
48
|
+
"shape:stadium"
|
|
49
|
+
) },
|
|
50
|
+
{ id: "data", label: "Data", outline: "parallelogram", size: { width: 130, height: 60 }, icon: (
|
|
51
|
+
/* @icon */
|
|
52
|
+
"shape:parallelogram"
|
|
53
|
+
) },
|
|
54
|
+
{ id: "manual", label: "Manual", outline: "trapezoid", size: { width: 130, height: 60 }, icon: (
|
|
55
|
+
/* @icon */
|
|
56
|
+
"shape:trapezoidSide"
|
|
57
|
+
) },
|
|
58
|
+
{ id: "preparation", label: "Preparation", outline: "hexagon", size: { width: 130, height: 60 }, icon: (
|
|
59
|
+
/* @icon */
|
|
60
|
+
"shape:hexagonFlat"
|
|
61
|
+
) },
|
|
62
|
+
{ id: "connector", label: "Connector", outline: "ellipse", size: { width: 50, height: 50 }, icon: (
|
|
63
|
+
/* @icon */
|
|
64
|
+
"shape:circle"
|
|
65
|
+
) }
|
|
66
|
+
];
|
|
67
|
+
var stencilKind = (id) => `stencil:${id}`;
|
|
68
|
+
function stencilIdOf(kind) {
|
|
69
|
+
return kind.startsWith("stencil:") ? kind.slice("stencil:".length) : null;
|
|
23
70
|
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
71
|
+
var DEFAULT_ROWS = [{ kind: "label", text: "" }];
|
|
72
|
+
function placedBounds(stencil, b) {
|
|
73
|
+
if (b.width > 0 && b.height > 0) return b;
|
|
74
|
+
const { width, height } = stencil.size;
|
|
75
|
+
return { x: b.x + b.width / 2 - width / 2, y: b.y + b.height / 2 - height / 2, width, height };
|
|
76
|
+
}
|
|
77
|
+
function stencilBuild(stencil, bounds, opts) {
|
|
78
|
+
const id = opts.newId();
|
|
79
|
+
const style = opts.style ?? NODE;
|
|
80
|
+
const at = placedBounds(stencil, bounds);
|
|
81
|
+
const spec = { outline: stencil.outline, rows: stencil.rows ?? DEFAULT_ROWS };
|
|
82
|
+
const built = buildBody(
|
|
83
|
+
spec,
|
|
84
|
+
at,
|
|
85
|
+
{
|
|
86
|
+
id,
|
|
87
|
+
layer: opts.layer,
|
|
88
|
+
...opts.measure ? { measure: opts.measure } : {},
|
|
89
|
+
body: (trait) => ({
|
|
90
|
+
diagram: { ...trait, ...bodyLayoutOf(spec, trait) },
|
|
91
|
+
fill: { paint: { color: style.fill } },
|
|
92
|
+
stroke: { paint: { color: style.stroke }, width: style.strokeWidth ?? NODE_STROKE_WIDTH, ...style.dash ? { dash: style.dash } : {} }
|
|
93
|
+
}),
|
|
94
|
+
row: (text, box) => box.row.kind === "label" || box.row.kind === "field" ? {
|
|
95
|
+
text,
|
|
96
|
+
style: rowTextStyle(box.row, opts.font ?? DEFAULT_FONT),
|
|
97
|
+
fill: { paint: { color: style.text } },
|
|
98
|
+
bodyRow: box.index
|
|
99
|
+
} : null
|
|
40
100
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
101
|
+
);
|
|
102
|
+
const ops = built.specs.map((spec2, k) => createInsertOp({
|
|
103
|
+
node: { ...spec2, id: spec2.id ?? `${id}/${k}`, parent: spec2.parent ?? opts.parent ?? null },
|
|
104
|
+
label: `Insert ${stencil.label}`
|
|
105
|
+
}));
|
|
106
|
+
return { id, ops };
|
|
44
107
|
}
|
|
45
|
-
function
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
108
|
+
function stencilTool(stencil) {
|
|
109
|
+
return defineTool({
|
|
110
|
+
id: stencilKind(stencil.id),
|
|
111
|
+
hookName: "useStencilTool",
|
|
112
|
+
capabilities: ["creates-shapes"],
|
|
113
|
+
cursor: "crosshair",
|
|
114
|
+
presentation: { label: stencil.label, icon: stencil.icon, group: "stencil" },
|
|
115
|
+
bindings: [
|
|
116
|
+
{ spec: { kind: "drag" }, actionId: "insert", opts: { params: { kind: stencilKind(stencil.id) } } },
|
|
117
|
+
INSERT_REPOSITION_BINDING
|
|
118
|
+
]
|
|
119
|
+
});
|
|
55
120
|
}
|
|
56
|
-
function
|
|
57
|
-
|
|
58
|
-
return (node, deps) => {
|
|
59
|
-
const anchor = diagramAnchorOf(node);
|
|
60
|
-
const dep = deps[0];
|
|
61
|
-
if (anchor === null || dep === void 0) return null;
|
|
62
|
-
const target = geometry.getBounds(dep.pose);
|
|
63
|
-
const own = geometry.getBounds(node.pose);
|
|
64
|
-
const self = anchor.self ?? anchor.at;
|
|
65
|
-
const x = target.x + anchor.at.u * target.width + (anchor.offset?.x ?? 0) - self.u * own.width;
|
|
66
|
-
const y = target.y + anchor.at.v * target.height + (anchor.offset?.y ?? 0) - self.v * own.height;
|
|
67
|
-
return translatePoseViaDescriptor(node.pose, x - own.x, y - own.y, geometry);
|
|
68
|
-
};
|
|
121
|
+
function useStencilTool(stencil) {
|
|
122
|
+
return useMemo(() => stencilTool(stencil), [stencil]);
|
|
69
123
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
124
|
+
function useStencilInsert(stencils, opts = {}) {
|
|
125
|
+
const registry = useDepRegistry();
|
|
126
|
+
const stencilsRef = useLatest(stencils);
|
|
127
|
+
const optsRef = useLatest(opts);
|
|
128
|
+
useDepSource("insert", (below) => {
|
|
129
|
+
const scene = registry.get("scene");
|
|
130
|
+
const fallback = below();
|
|
131
|
+
const layer = () => optsRef.current.layer ?? fallback?.layer?.() ?? scene?.layers[0]?.id ?? "default";
|
|
132
|
+
const stencilOf = (kind) => {
|
|
133
|
+
const id = stencilIdOf(kind);
|
|
134
|
+
if (id === null) return void 0;
|
|
135
|
+
return stencilsRef.current.find((s) => s.id === id) ?? optsRef.current.containers?.find((s) => s.id === id);
|
|
136
|
+
};
|
|
137
|
+
const builtAt = (stencil, bounds) => {
|
|
138
|
+
if (scene === void 0) return null;
|
|
139
|
+
const center = { x: bounds.x + bounds.width / 2, y: bounds.y + bounds.height / 2 };
|
|
140
|
+
const role = "role" in stencil ? stencil.role : null;
|
|
141
|
+
const { style, measure, font, containerStyle, laneStyles } = optsRef.current;
|
|
142
|
+
const common = {
|
|
143
|
+
layer: layer(),
|
|
144
|
+
newId: () => scene.mintId(),
|
|
145
|
+
parent: diagramDropParentAt(scene, center, role)
|
|
146
|
+
};
|
|
147
|
+
if ("role" in stencil) {
|
|
148
|
+
const boxStyle = containerStyle ?? style;
|
|
149
|
+
return containerBuild(stencil, bounds, {
|
|
150
|
+
...common,
|
|
151
|
+
...boxStyle ? { style: boxStyle } : {},
|
|
152
|
+
...laneStyles ? { laneStyles } : {}
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
return stencilBuild(stencil, bounds, {
|
|
156
|
+
...common,
|
|
157
|
+
...style ? { style } : {},
|
|
158
|
+
...measure ? { measure } : {},
|
|
159
|
+
...font ? { font } : {}
|
|
160
|
+
});
|
|
161
|
+
};
|
|
162
|
+
const build = (bounds, extras, ends) => {
|
|
163
|
+
const stencil = stencilOf(extras.kind);
|
|
164
|
+
if (stencil === void 0) return fallback?.build?.(bounds, extras, ends) ?? null;
|
|
165
|
+
const built = builtAt(stencil, bounds);
|
|
166
|
+
return built === null ? null : { id: built.id, ops: built.ops };
|
|
167
|
+
};
|
|
168
|
+
return {
|
|
169
|
+
layer,
|
|
170
|
+
build,
|
|
171
|
+
commit(bounds, extras, ends) {
|
|
172
|
+
const stencil = stencilOf(extras.kind);
|
|
173
|
+
if (stencil === void 0) return fallback?.commit(bounds, extras, ends) ?? null;
|
|
174
|
+
const built = build(bounds, extras, ends);
|
|
175
|
+
if (built === null) return null;
|
|
176
|
+
const deps = { scene, applyOps: registry.get("applyOps"), selection: registry.get("selection") };
|
|
177
|
+
commitOps(deps, built.ops, `Insert ${stencil.label}`);
|
|
178
|
+
return built.id;
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
});
|
|
83
182
|
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
const path = deps[0]?.path ?? null;
|
|
90
|
-
if (path === null) return null;
|
|
91
|
-
const t = typeof label.at === "number" ? label.at : NAMED[label.at] ?? 0.5;
|
|
92
|
-
const station = pointAlongPath(path, t);
|
|
93
|
-
if (station === null) return null;
|
|
94
|
-
const bounds = geometry.getBounds(node.pose);
|
|
95
|
-
const nx = station.tangent.y;
|
|
96
|
-
const ny = -station.tangent.x;
|
|
97
|
-
const offset = label.offset ?? 0;
|
|
98
|
-
const reach = offset + Math.sign(offset) * (Math.abs(nx) * bounds.width + Math.abs(ny) * bounds.height) / 2;
|
|
99
|
-
const x = station.point.x + nx * reach;
|
|
100
|
-
const y = station.point.y + ny * reach;
|
|
101
|
-
return translatePoseViaDescriptor(
|
|
102
|
-
node.pose,
|
|
103
|
-
x - (bounds.x + bounds.width / 2),
|
|
104
|
-
y - (bounds.y + bounds.height / 2),
|
|
105
|
-
geometry
|
|
106
|
-
);
|
|
107
|
-
};
|
|
183
|
+
var EPS = 1e-6;
|
|
184
|
+
function offsetOf(geometry, before, after) {
|
|
185
|
+
const a = geometry.getBounds(before);
|
|
186
|
+
const b = geometry.getBounds(after);
|
|
187
|
+
return { x: b.x - a.x, y: b.y - a.y };
|
|
108
188
|
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
function selfLoop(ports, standoff) {
|
|
115
|
-
const facing = ports.filter((p) => p.normal !== null);
|
|
116
|
-
const most = (dir, among) => among.reduce(
|
|
117
|
-
(best, p) => best === void 0 || dot(p.normal, dir) > dot(best.normal, dir) ? p : best,
|
|
118
|
-
void 0
|
|
119
|
-
);
|
|
120
|
-
const from = most({ x: 1, y: 0 }, facing);
|
|
121
|
-
if (from === void 0) return null;
|
|
122
|
-
const to = most({ x: 0, y: -1 }, facing.filter((p) => Math.abs(dot(p.normal, from.normal)) < 0.5));
|
|
123
|
-
if (to === void 0) return null;
|
|
124
|
-
const out = { x: from.point.x + from.normal.x * standoff, y: from.point.y + from.normal.y * standoff };
|
|
125
|
-
const reach = dot({ x: to.point.x - out.x, y: to.point.y - out.y }, to.normal) + standoff;
|
|
126
|
-
return { from, to, via: { x: out.x + to.normal.x * reach, y: out.y + to.normal.y * reach } };
|
|
189
|
+
function carried(edge, d) {
|
|
190
|
+
const move = (points) => points.map((p) => ({ x: p.x + d.x, y: p.y + d.y }));
|
|
191
|
+
const waypoints = move(edge.waypoints ?? []);
|
|
192
|
+
const owned = edge.laidOut !== void 0 && samePoints(edge.waypoints ?? [], edge.laidOut);
|
|
193
|
+
return { ...edge, waypoints, ...owned ? { laidOut: waypoints } : {} };
|
|
127
194
|
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
const
|
|
136
|
-
const
|
|
137
|
-
if (
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
const
|
|
142
|
-
|
|
143
|
-
const arriveHorizontal = arriving == null ? !leaveHorizontal : isHorizontal(arriving);
|
|
144
|
-
if (leaveHorizontal !== arriveHorizontal) {
|
|
145
|
-
out.push(leaveHorizontal ? { x: b.x, y: a.y } : { x: a.x, y: b.y });
|
|
146
|
-
out.push(b);
|
|
147
|
-
continue;
|
|
148
|
-
}
|
|
149
|
-
const prev = out.length > 1 ? out[out.length - 2] : null;
|
|
150
|
-
if (leaveHorizontal) {
|
|
151
|
-
const mid = (a.x + b.x) / 2;
|
|
152
|
-
if (prev !== null && (mid - a.x) * (a.x - prev.x) < 0) out.push({ x: a.x, y: b.y }, b);
|
|
153
|
-
else out.push({ x: mid, y: a.y }, { x: mid, y: b.y }, b);
|
|
154
|
-
} else {
|
|
155
|
-
const mid = (a.y + b.y) / 2;
|
|
156
|
-
if (prev !== null && (mid - a.y) * (a.y - prev.y) < 0) out.push({ x: b.x, y: a.y }, b);
|
|
157
|
-
else out.push({ x: a.x, y: mid }, { x: b.x, y: mid }, b);
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
return out;
|
|
161
|
-
};
|
|
162
|
-
function isHorizontal(normal) {
|
|
163
|
-
return Math.abs(normal?.x ?? 1) >= Math.abs(normal?.y ?? 0);
|
|
164
|
-
}
|
|
165
|
-
var bezier = (req) => {
|
|
166
|
-
const stops = [req.from.point, ...req.waypoints, req.to.point];
|
|
167
|
-
const out = [stops[0]];
|
|
168
|
-
for (let i = 1; i < stops.length; i++) {
|
|
169
|
-
const a = stops[i - 1];
|
|
170
|
-
const b = stops[i];
|
|
171
|
-
const outN = i === 1 ? req.from.normal : through(stops, i - 1);
|
|
172
|
-
const inN = i === stops.length - 1 ? req.to.normal : negated(through(stops, i));
|
|
173
|
-
const leg = portCurvePoints(
|
|
174
|
-
a.x,
|
|
175
|
-
a.y,
|
|
176
|
-
b.x,
|
|
177
|
-
b.y,
|
|
178
|
-
outN == null ? null : [outN.x, outN.y],
|
|
179
|
-
inN == null ? null : [inN.x, inN.y],
|
|
180
|
-
BEZIER_SAMPLES
|
|
181
|
-
);
|
|
182
|
-
for (const [x, y] of leg) out.push({ x, y });
|
|
195
|
+
function followers(sc, geometry, frame) {
|
|
196
|
+
const out = [];
|
|
197
|
+
for (const node of sc.renderOrderNodes()) {
|
|
198
|
+
const edge = diagramEdgeOf(node);
|
|
199
|
+
const ends = node.dependsOn;
|
|
200
|
+
if (edge === null || (edge.waypoints ?? []).length === 0 || !Array.isArray(ends) || ends.length !== 2) continue;
|
|
201
|
+
const [from, to] = ends.map((id) => sc.get(id));
|
|
202
|
+
const fromAfter = frame.get(ends[0]);
|
|
203
|
+
const toAfter = frame.get(ends[1]);
|
|
204
|
+
if (from === void 0 || to === void 0 || fromAfter === void 0 || toAfter === void 0) continue;
|
|
205
|
+
const d = offsetOf(geometry, from.pose, fromAfter);
|
|
206
|
+
const e = offsetOf(geometry, to.pose, toAfter);
|
|
207
|
+
if (Math.abs(d.x - e.x) > EPS || Math.abs(d.y - e.y) > EPS || d.x === 0 && d.y === 0) continue;
|
|
208
|
+
const data = node.data;
|
|
209
|
+
out.push({ id: node.id, pose: node.pose, data: { ...data, diagram: carried(edge, d) } });
|
|
183
210
|
}
|
|
184
211
|
return out;
|
|
185
|
-
};
|
|
186
|
-
var BEZIER_SAMPLES = 16;
|
|
187
|
-
function through(stops, i) {
|
|
188
|
-
const dx = stops[i + 1].x - stops[i - 1].x;
|
|
189
|
-
const dy = stops[i + 1].y - stops[i - 1].y;
|
|
190
|
-
const len = Math.hypot(dx, dy);
|
|
191
|
-
return len === 0 ? null : { x: dx / len, y: dy / len };
|
|
192
|
-
}
|
|
193
|
-
var negated = (v) => v === null ? null : { x: -v.x, y: -v.y };
|
|
194
|
-
var ROUTERS = Object.freeze({
|
|
195
|
-
straight,
|
|
196
|
-
orthogonal,
|
|
197
|
-
bezier
|
|
198
|
-
});
|
|
199
|
-
function diagramEdgeOf(node) {
|
|
200
|
-
const data = node.data;
|
|
201
|
-
if (data === null || typeof data !== "object") return null;
|
|
202
|
-
const edge = data.diagram;
|
|
203
|
-
if (edge === null || typeof edge !== "object") return null;
|
|
204
|
-
return "from" in edge && "to" in edge ? edge : null;
|
|
205
|
-
}
|
|
206
|
-
function resolveEnd(end, dep, toward, opts, allPorts) {
|
|
207
|
-
if (end.port !== void 0) {
|
|
208
|
-
const named = portOf(dep.node, dep.pose, end.port, opts);
|
|
209
|
-
if (named !== void 0) return named;
|
|
210
|
-
}
|
|
211
|
-
let best;
|
|
212
|
-
let bestScore = -Infinity;
|
|
213
|
-
for (const port of allPorts(dep)) {
|
|
214
|
-
const dx = toward.x - port.point.x;
|
|
215
|
-
const dy = toward.y - port.point.y;
|
|
216
|
-
const len = Math.hypot(dx, dy) || 1;
|
|
217
|
-
const facing = port.normal === null ? 0 : (port.normal.x * dx + port.normal.y * dy) / len;
|
|
218
|
-
const score = facing - len / 1e6;
|
|
219
|
-
if (score > bestScore) {
|
|
220
|
-
bestScore = score;
|
|
221
|
-
best = port;
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
return best;
|
|
225
212
|
}
|
|
226
|
-
function
|
|
227
|
-
const
|
|
228
|
-
|
|
229
|
-
const
|
|
230
|
-
const
|
|
231
|
-
|
|
232
|
-
return { x: b.x + b.width / 2, y: b.y + b.height / 2 };
|
|
233
|
-
};
|
|
234
|
-
const standoffOf = (d) => {
|
|
235
|
-
const b = geometry.getBounds(d.pose);
|
|
236
|
-
return Math.min(b.width, b.height) / 2;
|
|
237
|
-
};
|
|
238
|
-
return (node, deps) => {
|
|
239
|
-
const edge = diagramEdgeOf(node);
|
|
240
|
-
if (edge === null) return null;
|
|
241
|
-
const [fromDep, toDep] = deps;
|
|
242
|
-
if (fromDep === void 0 || toDep === void 0) return null;
|
|
243
|
-
const loop = fromDep.node.id === toDep.node.id && edge.from.port === void 0 && edge.to.port === void 0 ? selfLoop(allPorts(fromDep), opts.selfLoopStandoff ?? standoffOf(fromDep)) : null;
|
|
244
|
-
const from = loop?.from ?? resolveEnd(edge.from, fromDep, centerOf(toDep), portOpts, allPorts);
|
|
245
|
-
const to = loop?.to ?? resolveEnd(edge.to, toDep, centerOf(fromDep), portOpts, allPorts);
|
|
246
|
-
if (from === void 0 || to === void 0) return null;
|
|
247
|
-
const authored = edge.waypoints ?? [];
|
|
248
|
-
const waypoints = loop !== null && authored.length === 0 ? [loop.via] : authored;
|
|
249
|
-
const routers = opts.routers ?? ROUTERS;
|
|
250
|
-
const router = edge.router !== void 0 && routers[edge.router] || straight;
|
|
251
|
-
const points = router({ from, to, waypoints });
|
|
252
|
-
return points.length < 2 ? null : polylineFromPoints(points);
|
|
253
|
-
};
|
|
213
|
+
function posesMovedBy(sc, ops) {
|
|
214
|
+
const written = writtenBy(sc, ops);
|
|
215
|
+
if (written === null) return null;
|
|
216
|
+
const moved = /* @__PURE__ */ new Map();
|
|
217
|
+
for (const [id, w] of written) if (w.pose !== sc.get(id)?.pose) moved.set(id, w.pose);
|
|
218
|
+
return moved;
|
|
254
219
|
}
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
220
|
+
function waypointFollower(scene, geometry = AUTO_POSE_DESCRIPTOR) {
|
|
221
|
+
const sc = scene;
|
|
222
|
+
const geo = geometry;
|
|
258
223
|
return {
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
...
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
224
|
+
preview: (frame) => frame.size < 2 ? [] : followers(sc, geo, frame),
|
|
225
|
+
finish(ops, label) {
|
|
226
|
+
const moved = posesMovedBy(sc, ops);
|
|
227
|
+
if (moved === null || moved.size < 2) return [...ops];
|
|
228
|
+
const changes = followers(sc, geo, moved).map((c) => createSetDataOp({
|
|
229
|
+
id: c.id,
|
|
230
|
+
from: sc.get(c.id).data,
|
|
231
|
+
to: c.data,
|
|
232
|
+
label
|
|
233
|
+
}));
|
|
234
|
+
return [...ops, ...changes];
|
|
269
235
|
}
|
|
270
236
|
};
|
|
271
237
|
}
|
|
272
238
|
|
|
273
|
-
// src/
|
|
274
|
-
var
|
|
275
|
-
var PORT_AFFORDANCE_KIND = `layer:${PORT_LAYER_ID}`;
|
|
276
|
-
var DEFAULT_HIT_PX = 8;
|
|
277
|
-
var DEFAULT_SIZE_PX = 7;
|
|
278
|
-
function portScratchOf(hit) {
|
|
279
|
-
const payload = hit?.payload;
|
|
280
|
-
return payload !== void 0 && typeof payload.portId === "string" ? payload : null;
|
|
281
|
-
}
|
|
282
|
-
function createPortAffordance(participants, opts = {}) {
|
|
283
|
-
const {
|
|
284
|
-
id = PORT_LAYER_ID,
|
|
285
|
-
hitRadiusPx = DEFAULT_HIT_PX,
|
|
286
|
-
sizePx = DEFAULT_SIZE_PX,
|
|
287
|
-
strength = "exclusive"
|
|
288
|
-
} = opts;
|
|
289
|
-
const portOpts = {
|
|
290
|
-
...opts.geometry ? { geometry: opts.geometry } : {},
|
|
291
|
-
...opts.read ? { read: opts.read } : {}
|
|
292
|
-
};
|
|
293
|
-
const paint = sizePx <= 0 ? void 0 : {
|
|
294
|
-
kind: "square",
|
|
295
|
-
sizePx,
|
|
296
|
-
...opts.fill ? { fill: opts.fill } : {},
|
|
297
|
-
...opts.stroke ? { stroke: opts.stroke } : {}
|
|
298
|
-
};
|
|
299
|
-
return {
|
|
300
|
-
id,
|
|
301
|
-
regions(state) {
|
|
302
|
-
const out = [];
|
|
303
|
-
for (const { node, pose } of participants()) {
|
|
304
|
-
if (opts.shows !== void 0 && !opts.shows(node, state)) continue;
|
|
305
|
-
for (const port of portsOf(node, pose, portOpts)) {
|
|
306
|
-
out.push({
|
|
307
|
-
id: `${node.id}:${port.id}`,
|
|
308
|
-
targetId: null,
|
|
309
|
-
shape: { kind: "point", x: port.point.x, y: port.point.y, hitRadiusPx },
|
|
310
|
-
...paint ? { paint } : {},
|
|
311
|
-
...opts.cursor !== void 0 ? { cursor: opts.cursor } : {},
|
|
312
|
-
strength,
|
|
313
|
-
claimedKinds: ["pointer"],
|
|
314
|
-
bind: () => ({
|
|
315
|
-
initialScratch: {
|
|
316
|
-
targetId: node.id,
|
|
317
|
-
nodeId: node.id,
|
|
318
|
-
portId: port.id,
|
|
319
|
-
port
|
|
320
|
-
}
|
|
321
|
-
})
|
|
322
|
-
});
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
return out;
|
|
326
|
-
}
|
|
327
|
-
};
|
|
328
|
-
}
|
|
239
|
+
// src/passOrder.ts
|
|
240
|
+
var DIAGRAM_PASS_ORDER = { offPage: 100, avoid: 200, labels: 300, hops: 400 };
|
|
329
241
|
|
|
330
|
-
// src/
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
if (from.type === void 0 || to.type === void 0) return true;
|
|
335
|
-
return from.type === to.type;
|
|
336
|
-
};
|
|
337
|
-
var DEFAULT_SNAP = 16;
|
|
338
|
-
var DEFAULT_EDGE_STROKE = {
|
|
339
|
-
paint: { color: "#7ba7c7" },
|
|
340
|
-
width: 2,
|
|
341
|
-
markerEnd: "arrow"
|
|
342
|
-
};
|
|
343
|
-
var DEFAULT_ROUTER = "straight";
|
|
344
|
-
function connectBinding(actionId = CONNECT_ACTION_ID) {
|
|
345
|
-
return {
|
|
346
|
-
spec: { kind: "drag", target: `affordance:${PORT_AFFORDANCE_KIND}` },
|
|
347
|
-
actionId
|
|
348
|
-
};
|
|
242
|
+
// src/avoid/install.ts
|
|
243
|
+
function withRoute(data, edge, route) {
|
|
244
|
+
const { route: _r, ...rest } = edge;
|
|
245
|
+
return { ...data, diagram: route === null ? rest : { ...rest, route } };
|
|
349
246
|
}
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
id
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
}
|
|
247
|
+
function routeDataOps(scene, routes) {
|
|
248
|
+
const ops = [];
|
|
249
|
+
for (const [id, route] of routes) {
|
|
250
|
+
const node = scene.get(id);
|
|
251
|
+
const edge = node === void 0 ? null : diagramEdgeOf(node);
|
|
252
|
+
if (node === void 0 || edge === null) continue;
|
|
253
|
+
const had = edge.route;
|
|
254
|
+
if (route === null ? had === void 0 : had !== void 0 && samePoints(had, route)) continue;
|
|
255
|
+
ops.push(createSetDataOp({ id, from: node.data, to: withRoute(node.data, edge, route), label: "Route" }));
|
|
256
|
+
}
|
|
257
|
+
return ops;
|
|
360
258
|
}
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
259
|
+
var touched = (s) => s.added.size + s.removed.size + s.posed.size + s.reparented.size + s.dataChanged.size > 0;
|
|
260
|
+
function offsetOf2(geometry, before, after) {
|
|
261
|
+
const a = geometry.getBounds(before);
|
|
262
|
+
const b = geometry.getBounds(after);
|
|
263
|
+
return { x: b.x - a.x, y: b.y - a.y };
|
|
366
264
|
}
|
|
367
|
-
function
|
|
368
|
-
const
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
to: { port: edge.to.id },
|
|
373
|
-
router: edge.router
|
|
374
|
-
};
|
|
375
|
-
const source = scene.get(edge.from.nodeId);
|
|
376
|
-
scene.add({
|
|
377
|
-
kind: "leaf",
|
|
378
|
-
layer: source?.layer ?? scene.layers[0].id,
|
|
379
|
-
pose: { x: 0, y: 0, width: 0, height: 0 },
|
|
380
|
-
data: { diagram: trait, stroke: DEFAULT_EDGE_STROKE },
|
|
381
|
-
dependsOn: [edge.from.nodeId, edge.to.nodeId],
|
|
382
|
-
// Without this the edge re-resolves on every dependency move and paints
|
|
383
|
-
// nothing. `EDGE_DERIVE_PATH` rather than a fresh `edgeDerivePath()` so the
|
|
384
|
-
// scene can find its registry key and the edge survives `toJSON`.
|
|
385
|
-
derivePath: EDGE_DERIVE_PATH
|
|
265
|
+
function carried2(data, input) {
|
|
266
|
+
const edges = input.edges.map((e) => {
|
|
267
|
+
const shown = data.get(e.id);
|
|
268
|
+
const waypoints = shown === void 0 ? void 0 : diagramEdgeOf({ data: shown })?.waypoints;
|
|
269
|
+
return waypoints === void 0 ? e : { ...e, waypoints };
|
|
386
270
|
});
|
|
271
|
+
return { obstacles: input.obstacles, edges };
|
|
387
272
|
}
|
|
388
|
-
function
|
|
389
|
-
const
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
};
|
|
401
|
-
const route = routers[routerKey] ?? routers["straight"] ?? ROUTERS["straight"];
|
|
402
|
-
function targetPort(from, at) {
|
|
403
|
-
let best = null;
|
|
404
|
-
let bestD2 = snapRadius * snapRadius;
|
|
405
|
-
for (const { node, pose } of opts.participants()) {
|
|
406
|
-
for (const port of portsOf(node, pose, portOpts)) {
|
|
407
|
-
if (!canConnect(from, port)) continue;
|
|
408
|
-
const d2 = (port.point.x - at.x) ** 2 + (port.point.y - at.y) ** 2;
|
|
409
|
-
if (d2 <= bestD2) {
|
|
410
|
-
bestD2 = d2;
|
|
411
|
-
best = port;
|
|
412
|
-
}
|
|
413
|
-
}
|
|
273
|
+
function avoidFollower(scene, router, opts) {
|
|
274
|
+
const sc = scene;
|
|
275
|
+
const geometry = opts.geometry ?? AUTO_POSE_DESCRIPTOR;
|
|
276
|
+
let last = null;
|
|
277
|
+
const still = (frame) => {
|
|
278
|
+
if (last === null || last.frame.size !== frame.size) return false;
|
|
279
|
+
const px = opts.pixelSize?.() ?? 1;
|
|
280
|
+
for (const [id, pose] of frame) {
|
|
281
|
+
const was = last.frame.get(id);
|
|
282
|
+
if (was === void 0) return false;
|
|
283
|
+
const d = offsetOf2(geometry, was, pose);
|
|
284
|
+
if (Math.abs(d.x) >= px || Math.abs(d.y) >= px) return false;
|
|
414
285
|
}
|
|
415
|
-
return
|
|
416
|
-
}
|
|
417
|
-
function endAt(from, at) {
|
|
418
|
-
const port = targetPort(from, at);
|
|
419
|
-
return {
|
|
420
|
-
port,
|
|
421
|
-
end: port ?? { id: "", nodeId: "", point: at, normal: null }
|
|
422
|
-
};
|
|
423
|
-
}
|
|
286
|
+
return true;
|
|
287
|
+
};
|
|
424
288
|
return {
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
const
|
|
433
|
-
|
|
434
|
-
const
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
open: true,
|
|
438
|
-
deps: ctx.deps
|
|
439
|
-
};
|
|
440
|
-
return {
|
|
441
|
-
kind: "diagram-connect",
|
|
442
|
-
onMove(moveCtx) {
|
|
443
|
-
scratch.current = { x: moveCtx.world.x, y: moveCtx.world.y };
|
|
444
|
-
},
|
|
445
|
-
overlay() {
|
|
446
|
-
if (!scratch.open) return null;
|
|
447
|
-
const { end } = endAt(scratch.from, scratch.current);
|
|
448
|
-
const points = route({ from: scratch.from, to: end, waypoints: [] });
|
|
449
|
-
if (points.length < 2) return null;
|
|
450
|
-
return { kind: "polyline", points, role: "connector" };
|
|
451
|
-
},
|
|
452
|
-
onEnd(endCtx, reason) {
|
|
453
|
-
scratch.open = false;
|
|
454
|
-
if (reason === "cancel") return;
|
|
455
|
-
const { port } = endAt(scratch.from, { x: endCtx.world.x, y: endCtx.world.y });
|
|
456
|
-
if (port === null) return;
|
|
457
|
-
commit(
|
|
458
|
-
{ from: scratch.from, to: port, router: routerKey },
|
|
459
|
-
{ ...endCtx, deps: scratch.deps }
|
|
460
|
-
);
|
|
461
|
-
}
|
|
462
|
-
};
|
|
289
|
+
preview(frame, data) {
|
|
290
|
+
if (frame.size === 0) return [];
|
|
291
|
+
if (still(frame)) return last.out;
|
|
292
|
+
const input = carried2(data, avoidSceneInput(scene, opts, (id) => frame.get(id)));
|
|
293
|
+
const routes = router.update(input, { preview: true, budgetMs: opts.frameBudgetMs ?? 4 });
|
|
294
|
+
const out = [];
|
|
295
|
+
for (const [id, route] of routes) {
|
|
296
|
+
const node = sc.get(id);
|
|
297
|
+
const shown = data.get(id) ?? node?.data;
|
|
298
|
+
const edge = node === void 0 ? null : diagramEdgeOf({ data: shown });
|
|
299
|
+
if (node === void 0 || edge === null) continue;
|
|
300
|
+
out.push({ id, pose: node.pose, data: withRoute(shown, edge, route) });
|
|
463
301
|
}
|
|
302
|
+
last = { frame, out };
|
|
303
|
+
return out;
|
|
464
304
|
},
|
|
465
|
-
|
|
305
|
+
finish: (ops) => [...ops]
|
|
466
306
|
};
|
|
467
307
|
}
|
|
468
|
-
function
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
};
|
|
308
|
+
function installAvoidRouting(scene, opts = {}) {
|
|
309
|
+
const sc = scene;
|
|
310
|
+
const router = createAvoidRouter(opts);
|
|
311
|
+
router.seed(avoidSceneInput(scene, opts));
|
|
312
|
+
const dispose = scene.addEditPass((summary) => {
|
|
313
|
+
if (!touched(summary)) return null;
|
|
314
|
+
const ops = routeDataOps(sc, router.update(avoidSceneInput(scene, opts)));
|
|
315
|
+
return ops.length === 0 ? null : ops;
|
|
316
|
+
}, { order: DIAGRAM_PASS_ORDER.avoid });
|
|
317
|
+
return { router, follower: avoidFollower(scene, router, opts), dispose };
|
|
477
318
|
}
|
|
478
|
-
function
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
319
|
+
function useAvoidRouting(scene, opts = {}) {
|
|
320
|
+
const deps = useOptionalDepRegistry();
|
|
321
|
+
const latest = useLatest(opts);
|
|
322
|
+
const follower = useRef(null);
|
|
323
|
+
useEffect(() => {
|
|
324
|
+
const pixelSize = () => latest.current.pixelSize?.() ?? 1 / Math.abs(deps?.get("view")?.get().scale.x || 1);
|
|
325
|
+
const routing = installAvoidRouting(scene, { ...latest.current, pixelSize });
|
|
326
|
+
follower.current = routing.follower;
|
|
327
|
+
return () => {
|
|
328
|
+
routing.dispose();
|
|
329
|
+
follower.current = null;
|
|
330
|
+
};
|
|
331
|
+
}, [scene, deps, latest]);
|
|
332
|
+
useTransformFollower(() => follower.current);
|
|
484
333
|
}
|
|
485
334
|
|
|
486
|
-
// src/
|
|
487
|
-
function
|
|
488
|
-
const
|
|
335
|
+
// src/hops/follower.ts
|
|
336
|
+
function hopFollower(scene) {
|
|
337
|
+
const sc = scene;
|
|
489
338
|
return {
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
339
|
+
preview(frame, data) {
|
|
340
|
+
if (frame.size === 0) return [];
|
|
341
|
+
const edges = sceneEdges(sc);
|
|
342
|
+
const moved = /* @__PURE__ */ new Set();
|
|
343
|
+
for (const e of edges) {
|
|
344
|
+
if (frame.has(e.node.id) || e.ends.some((end) => inOrUnder(sc, end, frame))) moved.add(e.node.id);
|
|
345
|
+
}
|
|
346
|
+
const out = [];
|
|
347
|
+
for (const e of edges) {
|
|
348
|
+
const hops = e.trait.hops;
|
|
349
|
+
if (hops === void 0 || hops.length === 0) continue;
|
|
350
|
+
if (!moved.has(e.node.id) && !hops.some((h) => h.over.some((id) => moved.has(id)))) continue;
|
|
351
|
+
const shown = data.get(e.node.id) ?? e.node.data;
|
|
352
|
+
const trait = diagramEdgeOf({ data: shown }) ?? e.trait;
|
|
353
|
+
out.push({ id: e.node.id, pose: e.node.pose, data: withHopsData(shown, trait, []) });
|
|
354
|
+
}
|
|
355
|
+
return out;
|
|
356
|
+
},
|
|
357
|
+
finish: (ops) => [...ops]
|
|
500
358
|
};
|
|
501
359
|
}
|
|
502
360
|
|
|
503
|
-
// src/
|
|
504
|
-
function
|
|
505
|
-
const
|
|
506
|
-
const
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
361
|
+
// src/hops/install.ts
|
|
362
|
+
function boxOf(points, pad) {
|
|
363
|
+
const b = { x0: Infinity, y0: Infinity, x1: -Infinity, y1: -Infinity };
|
|
364
|
+
for (const p of points) {
|
|
365
|
+
b.x0 = Math.min(b.x0, p.x - pad);
|
|
366
|
+
b.y0 = Math.min(b.y0, p.y - pad);
|
|
367
|
+
b.x1 = Math.max(b.x1, p.x + pad);
|
|
368
|
+
b.y1 = Math.max(b.y1, p.y + pad);
|
|
369
|
+
}
|
|
370
|
+
return b;
|
|
371
|
+
}
|
|
372
|
+
var meets = (a, b) => a.x0 <= b.x1 && b.x0 <= a.x1 && a.y0 <= b.y1 && b.y0 <= a.y1;
|
|
373
|
+
function reordered(before, now) {
|
|
374
|
+
const was = new Set(before);
|
|
375
|
+
const is = new Set(now);
|
|
376
|
+
const a = before.filter((id) => is.has(id));
|
|
377
|
+
const b = now.filter((id) => was.has(id));
|
|
378
|
+
return a.some((id, i) => id !== b[i]);
|
|
379
|
+
}
|
|
380
|
+
function allPolylines(edges, polylineOf) {
|
|
381
|
+
const out = /* @__PURE__ */ new Map();
|
|
382
|
+
for (const e of edges) {
|
|
383
|
+
const p = polylineOf(e);
|
|
384
|
+
if (p !== null) out.set(e.node.id, p);
|
|
385
|
+
}
|
|
386
|
+
return out;
|
|
387
|
+
}
|
|
388
|
+
function candidates(sc, edges, held, s) {
|
|
389
|
+
const changed = /* @__PURE__ */ new Set([...s.added, ...s.removed, ...s.posed, ...s.reparented, ...s.dataChanged]);
|
|
390
|
+
const derived = (id) => sc.get(id)?.derivePose !== void 0;
|
|
391
|
+
return edges.filter((e) => changed.has(e.node.id) || !held.polylines.has(e.node.id) || e.ends.some((end) => inOrUnder(sc, end, changed) || derived(end)));
|
|
392
|
+
}
|
|
393
|
+
function installHops(scene, opts = {}) {
|
|
394
|
+
const sc = scene;
|
|
395
|
+
const polylineOf = edgePolylines(sc, opts);
|
|
396
|
+
let held = null;
|
|
397
|
+
const full = (edges, r) => {
|
|
398
|
+
const order = edges.map((e) => e.node.id);
|
|
399
|
+
held = { polylines: allPolylines(edges, polylineOf), order, r };
|
|
400
|
+
return hopDataOps(sc, edgeHops(held.polylines, order, r));
|
|
514
401
|
};
|
|
515
|
-
|
|
516
|
-
const
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
402
|
+
const pass = (summary) => {
|
|
403
|
+
const value = sc.doc.get(HOPS_DOC_KEY);
|
|
404
|
+
const keyed = summary.docChanged.has(HOPS_DOC_KEY);
|
|
405
|
+
if (value === void 0) {
|
|
406
|
+
held = null;
|
|
407
|
+
return keyed ? hopDataOps(sc, new Map(sceneEdges(sc).map((e) => [e.node.id, []]))) : null;
|
|
408
|
+
}
|
|
409
|
+
const edges = sceneEdges(sc);
|
|
410
|
+
const order = edges.map((e) => e.node.id);
|
|
411
|
+
if (keyed || held === null || held.r !== value.size || reordered(held.order, order)) return full(edges, value.size);
|
|
412
|
+
const h = held;
|
|
413
|
+
const r = h.r;
|
|
414
|
+
const boxes = [];
|
|
415
|
+
const dirty = /* @__PURE__ */ new Set();
|
|
416
|
+
for (const e of candidates(sc, edges, h, summary)) {
|
|
417
|
+
const was = h.polylines.get(e.node.id);
|
|
418
|
+
const now = polylineOf(e);
|
|
419
|
+
if (was !== void 0 && now !== null && samePoints(was, now)) continue;
|
|
420
|
+
dirty.add(e.node.id);
|
|
421
|
+
if (was !== void 0) boxes.push(boxOf(was, r));
|
|
422
|
+
if (now === null) h.polylines.delete(e.node.id);
|
|
423
|
+
else {
|
|
424
|
+
boxes.push(boxOf(now, r));
|
|
425
|
+
h.polylines.set(e.node.id, now);
|
|
525
426
|
}
|
|
526
427
|
}
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
if (known !== void 0) return known;
|
|
533
|
-
const out = [];
|
|
534
|
-
for (const member of direct.get(id)) {
|
|
535
|
-
if (groupIds.has(member)) out.push(...flatten(member));
|
|
536
|
-
else out.push(member);
|
|
428
|
+
const present = new Set(order);
|
|
429
|
+
for (const [id, was] of h.polylines) {
|
|
430
|
+
if (present.has(id)) continue;
|
|
431
|
+
boxes.push(boxOf(was, r));
|
|
432
|
+
h.polylines.delete(id);
|
|
537
433
|
}
|
|
538
|
-
|
|
539
|
-
return
|
|
434
|
+
h.order = order;
|
|
435
|
+
if (boxes.length === 0) return null;
|
|
436
|
+
const affected = new Set(dirty);
|
|
437
|
+
for (const [id, points] of h.polylines) {
|
|
438
|
+
if (affected.has(id)) continue;
|
|
439
|
+
const box = boxOf(points, 0);
|
|
440
|
+
if (boxes.some((b) => meets(box, b))) affected.add(id);
|
|
441
|
+
}
|
|
442
|
+
const ops = hopDataOps(sc, edgeHops(h.polylines, order, r, affected));
|
|
443
|
+
return ops.length === 0 ? null : ops;
|
|
540
444
|
};
|
|
541
|
-
const
|
|
542
|
-
|
|
543
|
-
const
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
445
|
+
const on = sc.doc.get(HOPS_DOC_KEY);
|
|
446
|
+
if (on !== void 0) {
|
|
447
|
+
const edges = sceneEdges(sc);
|
|
448
|
+
held = { polylines: allPolylines(edges, polylineOf), order: edges.map((e) => e.node.id), r: on.size };
|
|
449
|
+
}
|
|
450
|
+
const dispose = scene.addEditPass(pass, { order: DIAGRAM_PASS_ORDER.hops });
|
|
451
|
+
return { follower: hopFollower(scene), dispose };
|
|
452
|
+
}
|
|
453
|
+
function useHops(scene, opts = {}) {
|
|
454
|
+
const latest = useLatest(opts);
|
|
455
|
+
const follower = useRef(null);
|
|
456
|
+
useEffect(() => {
|
|
457
|
+
const hops = installHops(scene, latest.current);
|
|
458
|
+
follower.current = hops.follower;
|
|
459
|
+
return () => {
|
|
460
|
+
hops.dispose();
|
|
461
|
+
follower.current = null;
|
|
549
462
|
};
|
|
550
|
-
});
|
|
463
|
+
}, [scene, latest]);
|
|
464
|
+
useTransformFollower(() => follower.current);
|
|
465
|
+
}
|
|
466
|
+
var isRect = (pose) => typeof pose === "object" && pose !== null && typeof pose.width === "number" && typeof pose.height === "number";
|
|
467
|
+
function labelFitOps(scene, summary, measure) {
|
|
468
|
+
const ops = [];
|
|
469
|
+
const sizes = /* @__PURE__ */ new Map();
|
|
470
|
+
for (const id of summary.dataChanged) {
|
|
471
|
+
const node = scene.get(id);
|
|
472
|
+
if (node === void 0 || diagramLabelOf(node) === null || !isRect(node.pose)) continue;
|
|
473
|
+
const data = node.data;
|
|
474
|
+
if (typeof data.text !== "string") continue;
|
|
475
|
+
const size = labelSize(data.text, data.style ?? labelStyleOf(DEFAULT_FONT), measure);
|
|
476
|
+
if (Math.abs(size.width - node.pose.width) < 1e-6 && Math.abs(size.height - node.pose.height) < 1e-6) continue;
|
|
477
|
+
sizes.set(id, size);
|
|
478
|
+
ops.push(createTransformOp({ id, from: node.pose, to: { ...node.pose, ...size }, label: "Fit label" }));
|
|
479
|
+
}
|
|
480
|
+
return { ops, sizes };
|
|
551
481
|
}
|
|
552
482
|
|
|
553
|
-
// src/
|
|
554
|
-
function
|
|
555
|
-
|
|
556
|
-
const
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
483
|
+
// src/labels/install.ts
|
|
484
|
+
function runsOf(path) {
|
|
485
|
+
if (path === null || path.kind !== "polygon") return [];
|
|
486
|
+
const runs = [];
|
|
487
|
+
for (let i = 0; i < path.commands.length; i++) {
|
|
488
|
+
if (path.commands[i] === PATH_M || runs.length === 0) runs.push([]);
|
|
489
|
+
runs[runs.length - 1].push({ x: path.coords[2 * i], y: path.coords[2 * i + 1] });
|
|
490
|
+
}
|
|
491
|
+
return runs.filter((r) => r.length >= 2);
|
|
492
|
+
}
|
|
493
|
+
function read(sc, geometry, reader, sizes) {
|
|
494
|
+
const out = { shapes: [], routes: /* @__PURE__ */ new Map(), labels: [] };
|
|
495
|
+
for (const node of sc.renderOrderNodes()) {
|
|
496
|
+
const ends = Array.isArray(node.dependsOn) ? node.dependsOn : [];
|
|
561
497
|
if (diagramEdgeOf(node) !== null) {
|
|
562
|
-
const
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
}
|
|
498
|
+
const path = documentDepOf(sc, node.id)?.path ?? null;
|
|
499
|
+
const [points, ...more] = runsOf(path);
|
|
500
|
+
if (path !== null && points !== void 0) out.routes.set(node.id, { id: node.id, points, more, path, ends });
|
|
566
501
|
continue;
|
|
567
502
|
}
|
|
568
|
-
const
|
|
569
|
-
if (
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
groupNodes.push({
|
|
573
|
-
id: node.id,
|
|
574
|
-
members: Array.isArray(deps) ? deps : [],
|
|
575
|
-
inset: insetOf(trait.group.inset),
|
|
576
|
-
...trait.group.minWidth !== void 0 ? { minWidth: trait.group.minWidth } : {}
|
|
577
|
-
});
|
|
503
|
+
const placement = diagramLabelOf(node);
|
|
504
|
+
if (placement !== null) {
|
|
505
|
+
const { width, height } = sizes?.get(node.id) ?? geometry.getBounds(node.pose);
|
|
506
|
+
if (ends[0] !== void 0) out.labels.push({ id: node.id, edge: ends[0], placement, size: { width, height }, node });
|
|
578
507
|
continue;
|
|
579
508
|
}
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
bounds: geometry.getBounds(pose),
|
|
583
|
-
pinned: trait.pinned === true
|
|
584
|
-
};
|
|
585
|
-
nodes.push(entry);
|
|
586
|
-
byId.set(entry.id, entry);
|
|
509
|
+
if (diagramContainerOf(node) !== null || diagramGroupOf(node) !== null || diagramNodeOf(node, reader) === null) continue;
|
|
510
|
+
out.shapes.push({ id: node.id, box: geometry.getBounds(documentPose(sc, node)) });
|
|
587
511
|
}
|
|
588
|
-
|
|
589
|
-
const live = nestGroups(groupNodes, (id) => byId.has(id)).map((g) => {
|
|
590
|
-
for (const id of g.direct) {
|
|
591
|
-
const node = byId.get(id);
|
|
592
|
-
if (node !== void 0) node.group = g.id;
|
|
593
|
-
}
|
|
594
|
-
return {
|
|
595
|
-
id: g.id,
|
|
596
|
-
members: g.members,
|
|
597
|
-
inset: declared.get(g.id).inset,
|
|
598
|
-
...declared.get(g.id).minWidth !== void 0 ? { minWidth: declared.get(g.id).minWidth } : {},
|
|
599
|
-
...g.parent !== void 0 ? { parent: g.parent } : {}
|
|
600
|
-
};
|
|
601
|
-
});
|
|
602
|
-
const known = (id) => byId.has(id) || live.some((g) => g.id === id);
|
|
603
|
-
const edges = expandGroupEdges(candidates.filter((e) => known(e.from) && known(e.to)), live);
|
|
604
|
-
const out = /* @__PURE__ */ new Map();
|
|
605
|
-
const into = /* @__PURE__ */ new Map();
|
|
606
|
-
for (const edge of edges) {
|
|
607
|
-
push(out, edge.from, edge);
|
|
608
|
-
push(into, edge.to, edge);
|
|
609
|
-
}
|
|
610
|
-
const NONE = Object.freeze([]);
|
|
611
|
-
return {
|
|
612
|
-
nodes,
|
|
613
|
-
edges,
|
|
614
|
-
groups: live,
|
|
615
|
-
node: (id) => byId.get(id),
|
|
616
|
-
outgoing: (id) => out.get(id) ?? NONE,
|
|
617
|
-
incoming: (id) => into.get(id) ?? NONE
|
|
618
|
-
};
|
|
619
|
-
}
|
|
620
|
-
function push(index, key, edge) {
|
|
621
|
-
const list = index.get(key);
|
|
622
|
-
if (list === void 0) index.set(key, [edge]);
|
|
623
|
-
else list.push(edge);
|
|
512
|
+
return out;
|
|
624
513
|
}
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
function
|
|
628
|
-
for (
|
|
629
|
-
|
|
630
|
-
|
|
514
|
+
var grow = (b, by) => ({ x: b.x - by, y: b.y - by, width: b.width + 2 * by, height: b.height + 2 * by });
|
|
515
|
+
var meets2 = (a, b) => a.x <= b.x + b.width && b.x <= a.x + a.width && a.y <= b.y + b.height && b.y <= a.y + a.height;
|
|
516
|
+
function routeMeets(points, box) {
|
|
517
|
+
for (let i = 0; i + 1 < points.length; i++) {
|
|
518
|
+
const a = points[i];
|
|
519
|
+
const b = points[i + 1];
|
|
520
|
+
const seg = { x: Math.min(a.x, b.x), y: Math.min(a.y, b.y), width: Math.abs(a.x - b.x), height: Math.abs(a.y - b.y) };
|
|
521
|
+
if (meets2(seg, box)) return true;
|
|
631
522
|
}
|
|
632
|
-
|
|
633
|
-
const nested = nestGroups(groups, (id) => nodeIds.has(id));
|
|
634
|
-
const depthOf = /* @__PURE__ */ new Map();
|
|
635
|
-
const byId = new Map(nested.map((g) => [g.id, g]));
|
|
636
|
-
const depth = (id) => {
|
|
637
|
-
const parent = byId.get(id).parent;
|
|
638
|
-
let d = depthOf.get(id);
|
|
639
|
-
if (d === void 0) depthOf.set(id, d = parent === void 0 ? 0 : depth(parent) + 1);
|
|
640
|
-
return d;
|
|
641
|
-
};
|
|
642
|
-
return nested.map((g, i) => ({ g, i })).sort((a, b) => depth(a.g.id) - depth(b.g.id) || a.i - b.i).map(({ g }) => {
|
|
643
|
-
const group = declared.get(g.id);
|
|
644
|
-
const pad = group.padding ?? opts.padding;
|
|
645
|
-
const title = group.label ? opts.title(group.label) : null;
|
|
646
|
-
const inset = { top: pad + (title ? title.height + pad / 2 : 0), right: pad, bottom: pad, left: pad };
|
|
647
|
-
return {
|
|
648
|
-
group,
|
|
649
|
-
direct: g.direct,
|
|
650
|
-
members: g.members,
|
|
651
|
-
inset,
|
|
652
|
-
pad,
|
|
653
|
-
title,
|
|
654
|
-
...title ? { minWidth: title.width + 2 * pad } : {},
|
|
655
|
-
...g.parent !== void 0 ? { parent: g.parent } : {}
|
|
656
|
-
};
|
|
657
|
-
});
|
|
523
|
+
return false;
|
|
658
524
|
}
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
const
|
|
662
|
-
const
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
const box = boxAround({
|
|
670
|
-
x,
|
|
671
|
-
y,
|
|
672
|
-
width: Math.max(...rs.map((r) => r.x + r.width)) - x,
|
|
673
|
-
height: Math.max(...rs.map((r) => r.y + r.height)) - y
|
|
674
|
-
}, g);
|
|
675
|
-
boxes.set(id, box);
|
|
676
|
-
return box;
|
|
677
|
-
};
|
|
678
|
-
for (const g of groups) boxOf(g.group.id);
|
|
679
|
-
return boxes;
|
|
680
|
-
}
|
|
681
|
-
function groupSpecs(groups, placedAt, styleOf) {
|
|
682
|
-
const boxes = groupBoxes(groups, placedAt);
|
|
683
|
-
const specs = [];
|
|
684
|
-
for (const g of groups) {
|
|
685
|
-
const { style, text } = styleOf(g.group);
|
|
686
|
-
const { x, y, width, height } = boxes.get(g.group.id);
|
|
687
|
-
specs.push({
|
|
688
|
-
id: g.group.id,
|
|
689
|
-
kind: "leaf",
|
|
690
|
-
layer: "main",
|
|
691
|
-
pose: { x, y, width, height },
|
|
692
|
-
data: {
|
|
693
|
-
diagram: { outline: "rect", group: { inset: g.inset, ...g.minWidth !== void 0 ? { minWidth: g.minWidth } : {} } },
|
|
694
|
-
fill: { paint: { color: style.fill } },
|
|
695
|
-
stroke: {
|
|
696
|
-
paint: { color: style.stroke },
|
|
697
|
-
width: style.strokeWidth ?? 1,
|
|
698
|
-
...style.dash ? { dash: style.dash } : {}
|
|
699
|
-
}
|
|
700
|
-
},
|
|
701
|
-
dependsOn: g.direct,
|
|
702
|
-
derivePose: GROUP_DERIVE_POSE
|
|
703
|
-
});
|
|
704
|
-
if (g.title === null || !g.group.label) continue;
|
|
705
|
-
const anchor = { at: { u: 0, v: 0 }, offset: { x: g.pad, y: g.pad } };
|
|
706
|
-
specs.push({
|
|
707
|
-
id: `${g.group.id}/label`,
|
|
708
|
-
kind: "leaf",
|
|
709
|
-
layer: "main",
|
|
710
|
-
pickable: false,
|
|
711
|
-
pose: { x: x + g.pad, y: y + g.pad, width: g.title.width, height: g.title.height },
|
|
712
|
-
data: { diagram: { anchor }, text: g.group.label, style: text, fill: { paint: { color: style.text } } },
|
|
713
|
-
dependsOn: [g.group.id],
|
|
714
|
-
derivePose: ANCHOR_DERIVE_POSE
|
|
715
|
-
});
|
|
525
|
+
var runsIn = (r) => [r.points, ...r.more ?? []];
|
|
526
|
+
function changesOf(scene, summary, seen) {
|
|
527
|
+
const touched2 = (id) => summary.added.has(id) || summary.posed.has(id) || summary.dataChanged.has(id);
|
|
528
|
+
const edges = /* @__PURE__ */ new Set();
|
|
529
|
+
const routes = [];
|
|
530
|
+
for (const r of scene.routes.values()) {
|
|
531
|
+
if (!touched2(r.id) && !r.ends.some((e) => touched2(e) || summary.removed.has(e))) continue;
|
|
532
|
+
edges.add(r.id);
|
|
533
|
+
routes.push(...runsIn(r));
|
|
534
|
+
routes.push(...seen.routes.get(r.id) ?? []);
|
|
716
535
|
}
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
up: { out: "n", in: "s" },
|
|
724
|
-
right: { out: "e", in: "w" },
|
|
725
|
-
left: { out: "w", in: "e" }
|
|
726
|
-
};
|
|
727
|
-
function layeredRoutes(input) {
|
|
728
|
-
const { graph, boxes, members, compass, clearance } = input;
|
|
729
|
-
const direction = input.direction ?? "down";
|
|
730
|
-
const axes = axesFor(direction);
|
|
731
|
-
const faces = FACES[direction];
|
|
732
|
-
const ranks = ranksOf(graph, backEdges(graph));
|
|
733
|
-
const span = (id) => {
|
|
734
|
-
const ids = members.get(id) ?? [id];
|
|
735
|
-
const rs = ids.map((m) => ranks.get(m)).filter((r) => r !== void 0);
|
|
736
|
-
return rs.length === 0 ? null : [Math.min(...rs), Math.max(...rs)];
|
|
737
|
-
};
|
|
738
|
-
const lo = (r, axis) => axis === "x" ? r.x : r.y;
|
|
739
|
-
const hi = (r, axis) => axis === "x" ? r.x + r.width : r.y + r.height;
|
|
740
|
-
const mid = (r, axis) => (lo(r, axis) + hi(r, axis)) / 2;
|
|
741
|
-
const byRank = /* @__PURE__ */ new Map();
|
|
742
|
-
for (const node of graph.nodes) {
|
|
743
|
-
const rank = ranks.get(node.id);
|
|
744
|
-
const box = boxes.get(node.id);
|
|
745
|
-
if (rank === void 0 || box === void 0) continue;
|
|
746
|
-
byRank.set(rank, [...byRank.get(rank) ?? [], box]);
|
|
536
|
+
const shapes = [];
|
|
537
|
+
for (const s of scene.shapes) {
|
|
538
|
+
if (!touched2(s.id)) continue;
|
|
539
|
+
shapes.push(s.box);
|
|
540
|
+
const was = seen.shapes.get(s.id);
|
|
541
|
+
if (was !== void 0) shapes.push(was);
|
|
747
542
|
}
|
|
748
|
-
const
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
const a = boxes.get(edge.from);
|
|
753
|
-
const b = boxes.get(edge.to);
|
|
754
|
-
if (from === null || to === null || a === void 0 || b === void 0 || from[1] >= to[0]) continue;
|
|
755
|
-
const route = {};
|
|
756
|
-
if (compass.has(edge.from)) route.fromPort = faces.out;
|
|
757
|
-
if (compass.has(edge.to)) route.toPort = faces.in;
|
|
758
|
-
const near = axes.sign === 1 ? hi : lo;
|
|
759
|
-
const far = axes.sign === 1 ? lo : hi;
|
|
760
|
-
const start = { cross: mid(a, axes.cross), rank: near(a, axes.rank) };
|
|
761
|
-
const end = { cross: mid(b, axes.cross), rank: far(b, axes.rank) };
|
|
762
|
-
const ends = /* @__PURE__ */ new Set([edge.from, edge.to, ...members.get(edge.from) ?? [], ...members.get(edge.to) ?? []]);
|
|
763
|
-
const groupsInWay = [...members].filter(([g, ms]) => !ends.has(g) && !ms.some((m) => ends.has(m)));
|
|
764
|
-
const stops = [];
|
|
765
|
-
let blocked = false;
|
|
766
|
-
for (let k = from[1] + 1; k < to[0]; k++) {
|
|
767
|
-
const row = byRank.get(k) ?? [];
|
|
768
|
-
if (row.length === 0) continue;
|
|
769
|
-
const bandLo = Math.min(...row.map((r) => lo(r, axes.rank)));
|
|
770
|
-
const bandHi = Math.max(...row.map((r) => hi(r, axes.rank)));
|
|
771
|
-
const obstacles = [...row];
|
|
772
|
-
for (const [g] of groupsInWay) {
|
|
773
|
-
const box = boxes.get(g);
|
|
774
|
-
if (box !== void 0 && lo(box, axes.rank) < bandHi && hi(box, axes.rank) > bandLo) obstacles.push(box);
|
|
775
|
-
}
|
|
776
|
-
const t = ((bandLo + bandHi) / 2 - start.rank) / (end.rank - start.rank);
|
|
777
|
-
const ideal = start.cross + (end.cross - start.cross) * t;
|
|
778
|
-
const cross = freeAt(ideal, obstacles.map((r) => [lo(r, axes.cross) - clearance, hi(r, axes.cross) + clearance]));
|
|
779
|
-
if (cross !== ideal) blocked = true;
|
|
780
|
-
const [first, last] = axes.sign === 1 ? [bandLo, bandHi] : [bandHi, bandLo];
|
|
781
|
-
stops.push({ cross, rank: first }, { cross, rank: last });
|
|
782
|
-
}
|
|
783
|
-
if (blocked) {
|
|
784
|
-
route.waypoints = stops.map((s) => axes.rank === "y" ? { x: s.cross, y: s.rank } : { x: s.rank, y: s.cross });
|
|
785
|
-
}
|
|
786
|
-
if (route.fromPort !== void 0 || route.toPort !== void 0 || route.waypoints !== void 0) {
|
|
787
|
-
routes.set(edge.id, route);
|
|
788
|
-
}
|
|
543
|
+
for (const id of summary.removed) {
|
|
544
|
+
const shape = seen.shapes.get(id);
|
|
545
|
+
if (shape !== void 0) shapes.push(shape);
|
|
546
|
+
routes.push(...seen.routes.get(id) ?? []);
|
|
789
547
|
}
|
|
790
|
-
return routes;
|
|
791
|
-
}
|
|
792
|
-
function authoredOver(edges, input) {
|
|
793
|
-
const routes = layeredRoutes({ ...input, edges });
|
|
794
|
-
return new Map(edges.map((e) => {
|
|
795
|
-
const r = routes.get(e.id);
|
|
796
|
-
const fromPort = e.fromPort ?? r?.fromPort;
|
|
797
|
-
const toPort = e.toPort ?? r?.toPort;
|
|
798
|
-
const waypoints = e.waypoints ?? r?.waypoints;
|
|
799
|
-
return [e.id, { ...fromPort ? { fromPort } : {}, ...toPort ? { toPort } : {}, ...waypoints ? { waypoints } : {} }];
|
|
800
|
-
}));
|
|
548
|
+
return { touched: touched2, edges, routes, shapes };
|
|
801
549
|
}
|
|
802
|
-
function
|
|
803
|
-
const
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
550
|
+
function boxesOf(scene, label, gap) {
|
|
551
|
+
const route = scene.routes.get(label.edge);
|
|
552
|
+
if (route === void 0) return [];
|
|
553
|
+
const { placed, ...preferred } = label.placement;
|
|
554
|
+
const out = [];
|
|
555
|
+
for (const placement of placed === void 0 ? [preferred] : [label.placement, preferred]) {
|
|
556
|
+
const c = labelCenter(route.path, placement, label.size);
|
|
557
|
+
if (c !== null) out.push(grow({ x: c.x - label.size.width / 2, y: c.y - label.size.height / 2, ...label.size }, gap));
|
|
809
558
|
}
|
|
810
|
-
|
|
811
|
-
|
|
559
|
+
return out;
|
|
560
|
+
}
|
|
561
|
+
function movingOf(scene, summary, seen, gap) {
|
|
562
|
+
const changed = changesOf(scene, summary, seen);
|
|
563
|
+
const boxes = new Map(scene.labels.map((l) => [l.id, boxesOf(scene, l, gap)]));
|
|
564
|
+
const first = /* @__PURE__ */ new Set();
|
|
565
|
+
for (const l of scene.labels) {
|
|
566
|
+
const own = boxes.get(l.id);
|
|
567
|
+
const hit = own.some((box) => changed.shapes.some((s) => meets2(s, box)) || changed.routes.some((r) => routeMeets(r, box)));
|
|
568
|
+
if (changed.edges.has(l.edge) || changed.touched(l.id) || hit) first.add(l.id);
|
|
812
569
|
}
|
|
813
|
-
|
|
570
|
+
const moving = new Set(first);
|
|
571
|
+
for (const l of scene.labels) {
|
|
572
|
+
if (moving.has(l.id)) continue;
|
|
573
|
+
const own = boxes.get(l.id);
|
|
574
|
+
for (const id of first) if (boxes.get(id).some((b) => own.some((o) => meets2(b, o)))) moving.add(l.id);
|
|
575
|
+
}
|
|
576
|
+
return moving;
|
|
814
577
|
}
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
const
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
578
|
+
var sameSpot = (a, b) => a === b || a !== void 0 && b !== void 0 && Math.abs(a.at - b.at) < 1e-9 && Math.abs(a.offset - b.offset) < 1e-9;
|
|
579
|
+
function withSpot(data, label, spot) {
|
|
580
|
+
const { placed: _p, ...rest } = label;
|
|
581
|
+
const diagram = data.diagram;
|
|
582
|
+
return { ...data, diagram: { ...diagram, label: spot === void 0 ? rest : { ...rest, placed: spot } } };
|
|
583
|
+
}
|
|
584
|
+
function placementOps(scene, summary, opts, seen) {
|
|
585
|
+
const sc = scene;
|
|
586
|
+
const geometry = opts.geometry ?? AUTO_POSE_DESCRIPTOR;
|
|
587
|
+
const fit = labelFitOps(sc, summary, opts.measure);
|
|
588
|
+
const reading = read(sc, geometry, opts.read, fit.sizes);
|
|
589
|
+
const moving = movingOf(reading, summary, seen, opts.gap ?? 2);
|
|
590
|
+
seen.shapes = new Map(reading.shapes.map((s) => [s.id, s.box]));
|
|
591
|
+
seen.routes = new Map([...reading.routes.values()].map((r) => [r.id, runsIn(r)]));
|
|
592
|
+
if (moving.size === 0) return fit.ops;
|
|
593
|
+
const labels = reading.labels.map((l) => ({ ...l, moving: moving.has(l.id) }));
|
|
594
|
+
const placed = placeLabels({ shapes: reading.shapes, routes: [...reading.routes.values()], labels }, opts);
|
|
595
|
+
const ops = [...fit.ops];
|
|
596
|
+
for (const l of labels) {
|
|
597
|
+
if (!placed.has(l.id)) continue;
|
|
598
|
+
const spot = placed.get(l.id) ?? void 0;
|
|
599
|
+
if (sameSpot(l.placement.placed, spot)) continue;
|
|
600
|
+
ops.push(createSetDataOp({ id: l.id, from: l.node.data, to: withSpot(l.node.data, l.placement, spot), label: "Place label" }));
|
|
834
601
|
}
|
|
835
|
-
return
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
602
|
+
return ops;
|
|
603
|
+
}
|
|
604
|
+
var any = (s) => s.added.size + s.removed.size + s.posed.size + s.reparented.size + s.dataChanged.size > 0;
|
|
605
|
+
function installLabelPlacement(scene, opts = {}) {
|
|
606
|
+
const sc = scene;
|
|
607
|
+
const first = read(sc, opts.geometry ?? AUTO_POSE_DESCRIPTOR, opts.read);
|
|
608
|
+
const seen = {
|
|
609
|
+
shapes: new Map(first.shapes.map((s) => [s.id, s.box])),
|
|
610
|
+
routes: new Map([...first.routes.values()].map((r) => [r.id, runsIn(r)]))
|
|
842
611
|
};
|
|
612
|
+
const dispose = scene.addEditPass((summary) => {
|
|
613
|
+
if (!any(summary)) return null;
|
|
614
|
+
const ops = placementOps(scene, summary, opts, seen);
|
|
615
|
+
return ops.length === 0 ? null : ops;
|
|
616
|
+
}, { order: DIAGRAM_PASS_ORDER.labels });
|
|
617
|
+
return { dispose };
|
|
843
618
|
}
|
|
844
|
-
function
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
boxes: new Map([...placedAt, ...groupBoxes(groups, placedAt)]),
|
|
848
|
-
members: new Map(groups.map((g) => [g.group.id, g.members])),
|
|
849
|
-
compass: /* @__PURE__ */ new Set([...data.nodes.filter((n) => n.ports === void 0).map((n) => n.id), ...groups.map((g) => g.group.id)]),
|
|
850
|
-
clearance: (layoutOptions.nodeGap ?? DEFAULT_NODE_GAP) / 2,
|
|
851
|
-
...layoutOptions.direction ? { direction: layoutOptions.direction } : {}
|
|
852
|
-
});
|
|
619
|
+
function useLabelPlacement(scene, opts = {}) {
|
|
620
|
+
const latest = useLatest(opts);
|
|
621
|
+
useEffect(() => installLabelPlacement(scene, latest.current).dispose, [scene, latest]);
|
|
853
622
|
}
|
|
854
|
-
function
|
|
855
|
-
return
|
|
856
|
-
const seed = seeds[i].bounds;
|
|
857
|
-
const at = moved.get(n.id) ?? seed;
|
|
858
|
-
return [n.id, { x: at.x, y: at.y, width: seed.width, height: seed.height }];
|
|
859
|
-
}));
|
|
623
|
+
function documentBoundsOf(scene, node, geometry = AUTO_POSE_DESCRIPTOR) {
|
|
624
|
+
return geometry.getBounds(documentPose(scene, node));
|
|
860
625
|
}
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
const
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
for (const n of data.nodes) {
|
|
874
|
-
if (ids.has(n.id)) throw new Error(`diagramScene: node id "${n.id}" appears more than once`);
|
|
875
|
-
ids.add(n.id);
|
|
626
|
+
var ENDS = ["from", "to"];
|
|
627
|
+
var sameSplit = (a, b) => a === void 0 ? b === null : b !== null && a.ref === b.ref && a.from === b.from && a.to === b.to;
|
|
628
|
+
function withSplit(data, edge, split) {
|
|
629
|
+
const { offPage: _o, ...rest } = edge;
|
|
630
|
+
return { ...data, diagram: split === null ? rest : { ...rest, offPage: split } };
|
|
631
|
+
}
|
|
632
|
+
function reached(scene, s) {
|
|
633
|
+
const ids = /* @__PURE__ */ new Set([...s.added, ...s.posed, ...s.reparented, ...s.dataChanged]);
|
|
634
|
+
const out = /* @__PURE__ */ new Set();
|
|
635
|
+
for (const id of s.removed) {
|
|
636
|
+
const tagged = /^(.*)\/offpage-(?:from|to)$/.exec(id);
|
|
637
|
+
if (tagged !== null) out.add(tagged[1]);
|
|
876
638
|
}
|
|
877
|
-
const
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
title: (text) => measure(text, titleStyle)
|
|
882
|
-
});
|
|
883
|
-
const seen = /* @__PURE__ */ new Set();
|
|
884
|
-
const edges = data.edges.filter((e) => {
|
|
885
|
-
const id = edgeIdOf(e);
|
|
886
|
-
if (seen.has(id) || !ids.has(e.from) || !ids.has(e.to)) return false;
|
|
887
|
-
seen.add(id);
|
|
888
|
-
return true;
|
|
889
|
-
});
|
|
890
|
-
const bodies = data.nodes.map((n) => ({
|
|
891
|
-
outline: n.outline ?? opts.outline ?? "rect",
|
|
892
|
-
rows: n.rows ?? (n.lines ?? []).map((text) => ({ kind: "label", text })),
|
|
893
|
-
padding: n.padding ?? opts.padding ?? 8,
|
|
894
|
-
gap: n.gap ?? opts.gap ?? 4,
|
|
895
|
-
verticalAlign: n.verticalAlign ?? opts.verticalAlign ?? "center",
|
|
896
|
-
...n.ports ? { ports: n.ports } : {}
|
|
897
|
-
}));
|
|
898
|
-
const seeds = data.nodes.map((n, i) => {
|
|
899
|
-
const pose = sizeToBody(
|
|
900
|
-
{ ...n.at ?? { x: i, y: i }, width: n.width ?? opts.minWidth ?? MIN_WIDTH, height: n.height ?? 0 },
|
|
901
|
-
measureBody(bodies[i], measure)
|
|
902
|
-
);
|
|
903
|
-
return { id: n.id, bounds: pose, pinned: n.pinned === true };
|
|
904
|
-
});
|
|
905
|
-
const graphGroups = groups.map((g) => ({
|
|
906
|
-
id: g.group.id,
|
|
907
|
-
members: g.members,
|
|
908
|
-
inset: g.inset,
|
|
909
|
-
...g.minWidth !== void 0 ? { minWidth: g.minWidth } : {},
|
|
910
|
-
...g.parent !== void 0 ? { parent: g.parent } : {}
|
|
911
|
-
}));
|
|
912
|
-
const seedOf = new Map(seeds.map((n) => [n.id, n]));
|
|
913
|
-
for (const g of groups) {
|
|
914
|
-
for (const m of g.direct) {
|
|
915
|
-
const seed = seedOf.get(m);
|
|
916
|
-
if (seed !== void 0) seed.group = g.group.id;
|
|
917
|
-
}
|
|
639
|
+
for (const node of scene.renderOrderNodes()) {
|
|
640
|
+
if (diagramEdgeOf(node) === null) continue;
|
|
641
|
+
const deps = node.dependsOn;
|
|
642
|
+
if (ids.has(node.id) || Array.isArray(deps) && deps.some((d) => ids.has(d))) out.add(node.id);
|
|
918
643
|
}
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
const
|
|
925
|
-
const
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
style: opts.groupStyle?.(g) ?? GROUP,
|
|
932
|
-
text: styleOf(titleStyle)
|
|
933
|
-
})));
|
|
934
|
-
for (const [i, n] of data.nodes.entries()) {
|
|
935
|
-
const seed = seeds[i].bounds;
|
|
936
|
-
const at = placedAt.get(n.id);
|
|
937
|
-
const style = opts.nodeStyle?.(n) ?? NODE;
|
|
938
|
-
const textData = (text, s) => ({ text, style: styleOf(s), fill: { paint: { color: style.text } } });
|
|
939
|
-
const built = buildBody(
|
|
940
|
-
bodies[i],
|
|
941
|
-
{ x: at.x, y: at.y, width: seed.width, height: seed.height },
|
|
942
|
-
{
|
|
943
|
-
id: n.id,
|
|
944
|
-
layer: "main",
|
|
945
|
-
measure,
|
|
946
|
-
body: (trait) => ({
|
|
947
|
-
diagram: n.pinned === true ? { ...trait, pinned: true } : trait,
|
|
948
|
-
fill: { paint: { color: style.fill } },
|
|
949
|
-
stroke: {
|
|
950
|
-
paint: { color: style.stroke },
|
|
951
|
-
width: style.strokeWidth ?? 2,
|
|
952
|
-
...style.dash ? { dash: style.dash } : {}
|
|
953
|
-
}
|
|
954
|
-
}),
|
|
955
|
-
row: (text, box) => text === "" ? null : textData(text, box.row.kind === "label" || box.row.kind === "field" ? box.row.style : void 0),
|
|
956
|
-
portLabel: (text) => textData(text, void 0)
|
|
957
|
-
}
|
|
958
|
-
);
|
|
959
|
-
for (const [k, s] of built.specs.entries()) {
|
|
960
|
-
specs.push({ ...s, ...k === 0 ? {} : { id: `${n.id}/${k}` } });
|
|
644
|
+
return out;
|
|
645
|
+
}
|
|
646
|
+
function tagOps(scene, edge, split, pages, look) {
|
|
647
|
+
const ops = [];
|
|
648
|
+
let lookNow = null;
|
|
649
|
+
const lookOf = () => lookNow ??= look();
|
|
650
|
+
for (const end of ENDS) {
|
|
651
|
+
const id = offPageTagId(edge.id, end);
|
|
652
|
+
const tag = scene.get(id);
|
|
653
|
+
if (split === null) {
|
|
654
|
+
if (tag !== void 0) ops.push(createDeleteOp({ node: tag, label: "Off-page" }));
|
|
655
|
+
continue;
|
|
961
656
|
}
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
data: {
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
to: toPort ? { port: toPort } : {},
|
|
979
|
-
router: e.router ?? opts.router ?? "bezier",
|
|
980
|
-
...waypoints ? { waypoints } : {}
|
|
981
|
-
},
|
|
982
|
-
stroke: {
|
|
983
|
-
paint: { color: style.stroke },
|
|
984
|
-
width: style.width ?? 1.5,
|
|
985
|
-
...style.dash ? { dash: style.dash } : {},
|
|
986
|
-
...markerStart !== null ? { markerStart } : {},
|
|
987
|
-
...markerEnd !== null ? { markerEnd } : {}
|
|
988
|
-
}
|
|
989
|
-
},
|
|
990
|
-
dependsOn: [e.from, e.to],
|
|
991
|
-
derivePath: EDGE_DERIVE_PATH
|
|
992
|
-
});
|
|
993
|
-
if (e.label) {
|
|
994
|
-
const labelStyle = { ...font, fontSize: font.fontSize - 2 };
|
|
995
|
-
specs.push({
|
|
996
|
-
id: `${id}/label`,
|
|
997
|
-
kind: "leaf",
|
|
998
|
-
layer: "main",
|
|
999
|
-
pickable: false,
|
|
1000
|
-
pose: { x: 0, y: 0, width: measure(e.label, labelStyle).width, height: labelStyle.fontSize },
|
|
1001
|
-
data: {
|
|
1002
|
-
diagram: { label: e.labelPlacement ?? opts.labelPlacement ?? LABEL_PLACEMENT },
|
|
1003
|
-
text: e.label,
|
|
1004
|
-
style: labelStyle,
|
|
1005
|
-
fill: { paint: { color: style.label ?? style.stroke } }
|
|
1006
|
-
},
|
|
1007
|
-
dependsOn: [id],
|
|
1008
|
-
derivePose: LABEL_DERIVE_POSE
|
|
1009
|
-
});
|
|
657
|
+
const text = offPageFarText(pages, split, end);
|
|
658
|
+
if (tag === void 0) {
|
|
659
|
+
const spec = offPageTagSpec(edge.id, end, text, { ...lookOf(), layer: edge.layer });
|
|
660
|
+
ops.push(createInsertOp({ node: { ...spec, parent: null }, label: "Off-page" }));
|
|
661
|
+
} else if (tag.data.text !== text) {
|
|
662
|
+
const old = tag.data;
|
|
663
|
+
const kept = {
|
|
664
|
+
...lookOf(),
|
|
665
|
+
...old.style ? { style: old.style } : {},
|
|
666
|
+
...old.fill?.paint?.color ? { fill: old.fill.paint.color } : {},
|
|
667
|
+
...old.background?.paint?.color ? { background: old.background.paint.color } : {},
|
|
668
|
+
...old.padding !== void 0 ? { padding: old.padding } : {}
|
|
669
|
+
};
|
|
670
|
+
ops.push(createSetDataOp({ id, from: tag.data, to: { ...tag.data, ...offPageTagData(edge.id, end, text, kept) }, label: "Off-page" }));
|
|
671
|
+
const pose = tag.pose;
|
|
672
|
+
ops.push(createTransformOp({ id, from: tag.pose, to: { ...pose, ...offPageTagSize(text, kept) }, label: "Off-page" }));
|
|
1010
673
|
}
|
|
1011
674
|
}
|
|
1012
|
-
return
|
|
1013
|
-
}
|
|
1014
|
-
var LAYOUTS2 = Object.freeze({
|
|
1015
|
-
layered,
|
|
1016
|
-
tree,
|
|
1017
|
-
force
|
|
1018
|
-
});
|
|
1019
|
-
function applyLayout(scene, result, opts = {}) {
|
|
1020
|
-
const poses = layoutPoses(scene, result, opts.geometry);
|
|
1021
|
-
if (poses.size === 0) return 0;
|
|
1022
|
-
scene.batch(opts.label ?? "Layout", () => {
|
|
1023
|
-
for (const [id, pose] of poses) scene.setPose(id, pose);
|
|
1024
|
-
});
|
|
1025
|
-
let moved = 0;
|
|
1026
|
-
for (const id of result.keys()) if (poses.has(id)) moved++;
|
|
1027
|
-
return moved;
|
|
675
|
+
return ops;
|
|
1028
676
|
}
|
|
1029
|
-
function
|
|
1030
|
-
const
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
const
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
677
|
+
function offPageOps(scene, edges, opts = {}) {
|
|
678
|
+
const sc = scene;
|
|
679
|
+
const geometry = opts.geometry;
|
|
680
|
+
const look = opts.look ?? (() => offPageTagLook(diagramThemeOf(sc, opts.theme)));
|
|
681
|
+
const wanted = edges === "all" ? null : new Set(edges);
|
|
682
|
+
const pages = placedPagesOf(sc);
|
|
683
|
+
const boxOf2 = (id) => {
|
|
684
|
+
const node = sc.get(id);
|
|
685
|
+
return node === void 0 ? null : documentBoundsOf(scene, node, geometry);
|
|
686
|
+
};
|
|
687
|
+
const inputs = [];
|
|
688
|
+
const used = [];
|
|
689
|
+
for (const node of sc.renderOrderNodes()) {
|
|
690
|
+
const trait = diagramEdgeOf(node);
|
|
691
|
+
if (trait === null) continue;
|
|
692
|
+
if (wanted !== null && !wanted.has(node.id)) {
|
|
693
|
+
if (trait.offPage !== void 0) used.push(trait.offPage.ref);
|
|
694
|
+
continue;
|
|
1043
695
|
}
|
|
696
|
+
const deps = node.dependsOn;
|
|
697
|
+
const from = Array.isArray(deps) ? boxOf2(deps[0]) : null;
|
|
698
|
+
const to = Array.isArray(deps) ? boxOf2(deps[1]) : null;
|
|
699
|
+
if (from === null || to === null) continue;
|
|
700
|
+
inputs.push({ id: node.id, from, to, offPage: trait.offPage, node, trait });
|
|
1044
701
|
}
|
|
1045
|
-
return
|
|
1046
|
-
}
|
|
1047
|
-
|
|
1048
|
-
const
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
702
|
+
if (inputs.length === 0) return [];
|
|
703
|
+
const splits = offPageSplits({ pages, edges: inputs, used });
|
|
704
|
+
const ops = [];
|
|
705
|
+
for (const edge of inputs) {
|
|
706
|
+
const split = splits.get(edge.id) ?? null;
|
|
707
|
+
if (!sameSplit(edge.trait.offPage, split)) {
|
|
708
|
+
ops.push(createSetDataOp({ id: edge.id, from: edge.node.data, to: withSplit(edge.node.data, edge.trait, split), label: "Off-page" }));
|
|
709
|
+
}
|
|
710
|
+
ops.push(...tagOps(sc, edge.node, split, pages, look));
|
|
1054
711
|
}
|
|
1055
|
-
return
|
|
712
|
+
return ops;
|
|
1056
713
|
}
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
714
|
+
function installOffPageMarkers(scene, opts = {}) {
|
|
715
|
+
const sc = scene;
|
|
716
|
+
return scene.addEditPass((summary) => {
|
|
717
|
+
const edges = summary.docChanged.has(PAGES_DOC_KEY) ? "all" : reached(sc, summary);
|
|
718
|
+
if (edges !== "all" && edges.size === 0) return null;
|
|
719
|
+
const ops = offPageOps(scene, edges, opts);
|
|
720
|
+
return ops.length === 0 ? null : ops;
|
|
721
|
+
}, { order: DIAGRAM_PASS_ORDER.offPage });
|
|
722
|
+
}
|
|
723
|
+
function useOffPageMarkers(scene, opts = {}) {
|
|
724
|
+
const latest = useLatest(opts);
|
|
725
|
+
useEffect(() => installOffPageMarkers(scene, latest.current), [scene, latest]);
|
|
726
|
+
}
|
|
727
|
+
var FOLLOW_OFF_PAGE_ACTION_ID = "diagram.followOffPage";
|
|
728
|
+
function offPageTargetOf(scene, tagId) {
|
|
729
|
+
const node = scene.get(tagId);
|
|
730
|
+
const tag = node === void 0 ? null : offPageTagOf(node);
|
|
731
|
+
const edgeNode = tag === null ? void 0 : scene.get(tag.edge);
|
|
732
|
+
const split = edgeNode === void 0 ? void 0 : diagramEdgeOf(edgeNode)?.offPage;
|
|
733
|
+
const deps = edgeNode?.dependsOn;
|
|
734
|
+
if (tag === null || split === void 0 || !Array.isArray(deps)) return null;
|
|
735
|
+
const far = tag.end === "from" ? 1 : 0;
|
|
736
|
+
return { tag: tagId, edge: tag.edge, node: deps[far], page: far === 1 ? split.to : split.from };
|
|
737
|
+
}
|
|
738
|
+
function tagUnder(deps, at) {
|
|
739
|
+
if (deps?.scene === void 0 || at === void 0) return null;
|
|
740
|
+
const id = deps.nodeAtPoint?.(at);
|
|
741
|
+
return id == null ? null : offPageTargetOf(deps.scene, id);
|
|
742
|
+
}
|
|
743
|
+
function followDefault(target, ctx, padding) {
|
|
744
|
+
const { scene, selection, view } = ctx.deps;
|
|
745
|
+
if (scene === void 0) return;
|
|
746
|
+
selection?.set([target.node]);
|
|
747
|
+
const page = placedPagesOf(scene).find((p) => p.id === target.page);
|
|
748
|
+
const size = view?.hostSize?.();
|
|
749
|
+
if (page === void 0 || view === void 0 || size == null) return;
|
|
750
|
+
view.set(fitViewToPage(page, size, view.get(), { padding }));
|
|
751
|
+
}
|
|
752
|
+
function createFollowOffPageAction(opts = {}) {
|
|
753
|
+
const padding = opts.padding ?? 24;
|
|
754
|
+
const follow = opts.follow ?? ((target, ctx) => followDefault(target, ctx, padding));
|
|
1064
755
|
return {
|
|
1065
|
-
id:
|
|
1066
|
-
label:
|
|
1067
|
-
group:
|
|
1068
|
-
requires: ["scene"],
|
|
756
|
+
id: FOLLOW_OFF_PAGE_ACTION_ID,
|
|
757
|
+
label: "Go to the other page",
|
|
758
|
+
group: "diagram",
|
|
759
|
+
requires: ["scene", "nodeAtPoint", "selection", "view"],
|
|
760
|
+
eligible: { mode: { in: ["normal", "isolation"] } },
|
|
1069
761
|
invoker: {
|
|
1070
762
|
timing: "immediate",
|
|
1071
|
-
run
|
|
1072
|
-
const
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
...opts.geometry ? { geometry: opts.geometry } : {},
|
|
1077
|
-
...opts.label ? { label: opts.label } : {}
|
|
1078
|
-
});
|
|
763
|
+
run(deps, params) {
|
|
764
|
+
const world = { x: Number(params?.worldX), y: Number(params?.worldY) };
|
|
765
|
+
const target = tagUnder(deps, world);
|
|
766
|
+
if (target === null) return;
|
|
767
|
+
follow(target, { world, modifiers: { alt: false, shift: false, meta: false, ctrl: false }, deps });
|
|
1079
768
|
}
|
|
1080
769
|
},
|
|
1081
|
-
enabled: () => true
|
|
770
|
+
enabled: (deps, at) => tagUnder(deps, at) !== null ? true : ActionDisabledReason.NotApplicable
|
|
1082
771
|
};
|
|
1083
772
|
}
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
var easeInOut = (t) => t < 0.5 ? 2 * t * t : 1 - (-2 * t + 2) ** 2 / 2;
|
|
1088
|
-
var REST_DISTANCE = 0.1;
|
|
1089
|
-
var REST_TICKS = 5;
|
|
1090
|
-
function forceProducer(graph, opts = {}) {
|
|
1091
|
-
const geometry = opts.geometry ?? AUTO_POSE_DESCRIPTOR;
|
|
1092
|
-
const relaxation = forceRelaxation(graph, opts);
|
|
1093
|
-
const dragAlpha = opts.dragAlpha ?? 0.3;
|
|
1094
|
-
const restDistance = opts.restDistance ?? REST_DISTANCE;
|
|
1095
|
-
const restTicks = opts.restTicks ?? REST_TICKS;
|
|
1096
|
-
const was = relaxation.bodies.map((b) => ({ x: b.x, y: b.y }));
|
|
1097
|
-
let still = 0;
|
|
1098
|
-
return ({ pinned }) => {
|
|
1099
|
-
for (const node of graph.nodes) {
|
|
1100
|
-
const body = relaxation.byId.get(node.id);
|
|
1101
|
-
if (body === void 0) continue;
|
|
1102
|
-
const held = pinned.get(node.id);
|
|
1103
|
-
if (held !== void 0) {
|
|
1104
|
-
const bounds = geometry.getBounds(held);
|
|
1105
|
-
body.fx = center(bounds, "x");
|
|
1106
|
-
body.fy = center(bounds, "y");
|
|
1107
|
-
} else if (!relaxation.pinned.has(node.id)) {
|
|
1108
|
-
delete body.fx;
|
|
1109
|
-
delete body.fy;
|
|
1110
|
-
}
|
|
1111
|
-
}
|
|
1112
|
-
relaxation.sim.alphaTarget(pinned.size > 0 ? dragAlpha : 0);
|
|
1113
|
-
relaxation.sim.tick();
|
|
1114
|
-
let moved = 0;
|
|
1115
|
-
relaxation.bodies.forEach((body, i) => {
|
|
1116
|
-
const at = was[i];
|
|
1117
|
-
moved = Math.max(moved, Math.hypot(body.x - at.x, body.y - at.y));
|
|
1118
|
-
at.x = body.x;
|
|
1119
|
-
at.y = body.y;
|
|
1120
|
-
});
|
|
1121
|
-
still = pinned.size === 0 && moved < restDistance ? still + 1 : 0;
|
|
1122
|
-
const done = still >= restTicks || relaxation.sim.isSettled();
|
|
1123
|
-
return { result: relaxation.placed(), done };
|
|
1124
|
-
};
|
|
773
|
+
var onBody = bodyPredicate((target) => target === "selected-body" || target === "unselected-body");
|
|
774
|
+
function followOffPageBinding(actionId = FOLLOW_OFF_PAGE_ACTION_ID) {
|
|
775
|
+
return { spec: { kind: "doubleClick", target: { kindOf: onBody } }, actionId };
|
|
1125
776
|
}
|
|
1126
|
-
function
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
const t = Math.min((frame + 1) / frames, 1);
|
|
1134
|
-
const u = easing(t);
|
|
1135
|
-
const result = /* @__PURE__ */ new Map();
|
|
1136
|
-
for (const [id, to] of target) {
|
|
1137
|
-
if (pinned.has(id)) continue;
|
|
1138
|
-
const start = from.get(id);
|
|
1139
|
-
if (start === void 0) continue;
|
|
1140
|
-
result.set(id, {
|
|
1141
|
-
x: start.x + (to.x - start.x) * u,
|
|
1142
|
-
y: start.y + (to.y - start.y) * u
|
|
1143
|
-
});
|
|
777
|
+
function diagramOffPage(opts = {}) {
|
|
778
|
+
return {
|
|
779
|
+
contribution: {
|
|
780
|
+
id: "weasel-diagram-off-page",
|
|
781
|
+
eligibility: { always: true },
|
|
782
|
+
actions: [createFollowOffPageAction(opts)],
|
|
783
|
+
bindings: [followOffPageBinding()]
|
|
1144
784
|
}
|
|
1145
|
-
return { result, done: t >= 1 };
|
|
1146
785
|
};
|
|
1147
786
|
}
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
}
|
|
1153
|
-
|
|
1154
|
-
const
|
|
1155
|
-
const
|
|
1156
|
-
const
|
|
1157
|
-
const
|
|
1158
|
-
const
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
}
|
|
1175
|
-
const fn = typeof algorithm === "function" ? algorithm : LAYOUTS2[algorithm] ?? layered;
|
|
1176
|
-
producer.current = easedProducer(graph, fn, {
|
|
1177
|
-
...o.layout ? { layout: o.layout } : {},
|
|
1178
|
-
...o.frames === void 0 ? {} : { frames: o.frames },
|
|
1179
|
-
...o.easing ? { easing: o.easing } : {}
|
|
1180
|
-
});
|
|
1181
|
-
}, [optsRef]);
|
|
1182
|
-
const step = useCallback((ctx) => {
|
|
1183
|
-
const o = optsRef.current;
|
|
1184
|
-
if (producer.current === null) return { poses: [], done: true };
|
|
1185
|
-
const { result, done } = producer.current({
|
|
1186
|
-
pinned: ctx.pinned,
|
|
1187
|
-
frame: producerFrame.current++
|
|
1188
|
-
});
|
|
1189
|
-
let placed2 = result;
|
|
1190
|
-
if (ctx.pinned.size > 0) {
|
|
1191
|
-
const held = new Map(result);
|
|
1192
|
-
for (const id of ctx.pinned.keys()) held.delete(id);
|
|
1193
|
-
placed2 = held;
|
|
1194
|
-
}
|
|
1195
|
-
const poses = layoutPoses(o.scene, placed2, o.geometry);
|
|
1196
|
-
return { poses: [...poses], done };
|
|
1197
|
-
}, [optsRef]);
|
|
1198
|
-
const run = usePoseRun({
|
|
1199
|
-
scene: opts.scene,
|
|
1200
|
-
step,
|
|
1201
|
-
onCommit: (moved) => optsRef.current.onSettle?.(moved),
|
|
1202
|
-
...opts.label ? { label: opts.label } : {},
|
|
1203
|
-
...opts.requestFrame ? { requestFrame: opts.requestFrame } : {},
|
|
1204
|
-
...opts.cancelFrame ? { cancelFrame: opts.cancelFrame } : {}
|
|
1205
|
-
});
|
|
1206
|
-
useEffect(() => {
|
|
1207
|
-
if (opts.reheatOnGraphChange === false) return;
|
|
1208
|
-
return opts.scene.subscribe(() => {
|
|
1209
|
-
if (!run.isRunning()) return;
|
|
1210
|
-
const o = optsRef.current;
|
|
1211
|
-
const graph = buildGraph(o.source, {
|
|
1212
|
-
...o.read ? { read: o.read } : {},
|
|
1213
|
-
...o.geometry ? { geometry: o.geometry } : {}
|
|
1214
|
-
});
|
|
1215
|
-
if (shapeOf(graph) === shape.current) return;
|
|
1216
|
-
build();
|
|
1217
|
-
});
|
|
1218
|
-
}, [opts.scene, opts.reheatOnGraphChange, run, build, optsRef]);
|
|
1219
|
-
return useMemo(() => ({
|
|
1220
|
-
start() {
|
|
1221
|
-
if (run.isRunning()) return;
|
|
1222
|
-
build();
|
|
1223
|
-
run.start();
|
|
1224
|
-
},
|
|
1225
|
-
stop: () => {
|
|
1226
|
-
run.stop();
|
|
1227
|
-
},
|
|
1228
|
-
cancel: () => {
|
|
1229
|
-
run.cancel();
|
|
787
|
+
|
|
788
|
+
// src/theme/fromTokens.ts
|
|
789
|
+
function diagramThemeFromTokens(read2, opts = {}) {
|
|
790
|
+
const paper = DIAGRAM_THEMES.paper;
|
|
791
|
+
const t = (name, fallback) => read2(`--wzl-${name}`).trim() || fallback;
|
|
792
|
+
const fg = t("fg", paper.roles.default.text);
|
|
793
|
+
const muted = t("fg-muted", paper.roles.muted.text);
|
|
794
|
+
const raised = t("surface-raised", paper.roles.default.fill);
|
|
795
|
+
const sunken = t("surface-sunken", paper.roles.muted.fill);
|
|
796
|
+
const border = t("border-strong", t("border", paper.roles.default.stroke));
|
|
797
|
+
const accent = t("accent", paper.roles.accent.stroke);
|
|
798
|
+
const success = t("success", paper.roles.positive.stroke);
|
|
799
|
+
const warning = t("warning", paper.roles.warning.stroke);
|
|
800
|
+
const danger = t("danger", paper.roles.negative.stroke);
|
|
801
|
+
const size = Number.parseFloat(read2("--wzl-font-size"));
|
|
802
|
+
const shape = (stroke) => ({ fill: raised, stroke, text: fg });
|
|
803
|
+
return {
|
|
804
|
+
id: opts.id ?? "tokens",
|
|
805
|
+
name: opts.name ?? "Match chrome",
|
|
806
|
+
roles: {
|
|
807
|
+
default: shape(border),
|
|
808
|
+
accent: { fill: t("accent-soft", raised), stroke: accent, text: fg },
|
|
809
|
+
muted: { fill: sunken, stroke: border, text: muted },
|
|
810
|
+
positive: shape(success),
|
|
811
|
+
warning: shape(warning),
|
|
812
|
+
negative: shape(danger)
|
|
1230
813
|
},
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
814
|
+
defaultRole: "default",
|
|
815
|
+
edge: { ...paper.edge, stroke: muted },
|
|
816
|
+
label: { fill: fg },
|
|
817
|
+
container: { ...paper.container, fill: sunken, stroke: border, text: muted },
|
|
818
|
+
lane: {
|
|
819
|
+
header: { fill: sunken, stroke: border, text: fg, strokeWidth: 1 },
|
|
820
|
+
bodies: [
|
|
821
|
+
{ fill: t("surface", paper.page), stroke: border, text: fg, strokeWidth: 1 },
|
|
822
|
+
{ fill: raised, stroke: border, text: fg, strokeWidth: 1 }
|
|
823
|
+
]
|
|
1235
824
|
},
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
const
|
|
1245
|
-
|
|
825
|
+
page: t("surface", paper.page),
|
|
826
|
+
font: { family: t("font-ui", paper.font.family), size: Number.isFinite(size) && size > 0 ? size : paper.font.size },
|
|
827
|
+
series: [accent, success, warning, danger, t("accent-strong", accent), muted]
|
|
828
|
+
};
|
|
829
|
+
}
|
|
830
|
+
var FALLBACK_SIZE = 16;
|
|
831
|
+
var rendererMeasure = (text, style) => {
|
|
832
|
+
const fontSize = style.fontSize ?? FALLBACK_SIZE;
|
|
833
|
+
const { width } = measureTextBounds(text, {
|
|
834
|
+
fontSize,
|
|
835
|
+
...style.fontFamily !== void 0 ? { fontFamily: style.fontFamily } : {},
|
|
836
|
+
...style.bold === true ? { fontWeight: 700 } : {}
|
|
837
|
+
});
|
|
838
|
+
return { width: Math.ceil(width), height: Math.ceil(fontSize * 1.2) };
|
|
839
|
+
};
|
|
840
|
+
|
|
841
|
+
// src/theme/applyTheme.ts
|
|
842
|
+
function diagramStyleOf(node) {
|
|
843
|
+
const trait = node.data?.diagram;
|
|
844
|
+
if (trait === null || typeof trait !== "object") return null;
|
|
845
|
+
const kind = "from" in trait && "to" in trait ? "connector" : "outline" in trait && !("group" in trait) && !("container" in trait) ? "shape" : null;
|
|
846
|
+
if (kind === null) return null;
|
|
847
|
+
const role = trait.style;
|
|
1246
848
|
return {
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
if (outline === void 0) return [];
|
|
1252
|
-
const d = node.data;
|
|
1253
|
-
const box = geometry.getBounds(pose);
|
|
1254
|
-
const declaredFill = d.fill === void 0 ? opts.defaultFill : d.fill;
|
|
1255
|
-
const declaredStroke = d.stroke === void 0 ? opts.defaultStroke : d.stroke;
|
|
1256
|
-
const fill = declaredFill && commandFill(declaredFill, box);
|
|
1257
|
-
const stroke = declaredStroke && commandStroke(declaredStroke, box);
|
|
1258
|
-
return [{
|
|
1259
|
-
kind: "path",
|
|
1260
|
-
path: outlinePath(outline, box),
|
|
1261
|
-
...fill ? { fill } : {},
|
|
1262
|
-
...stroke ? { stroke } : {}
|
|
1263
|
-
}];
|
|
1264
|
-
},
|
|
1265
|
-
// The drawn boundary, so picking and clipping follow the diamond rather
|
|
1266
|
-
// than its bounding box.
|
|
1267
|
-
silhouette: (node, pose) => {
|
|
1268
|
-
const outline = outlineOf(node);
|
|
1269
|
-
return outline === void 0 ? null : outlinePath(outline, geometry.getBounds(pose));
|
|
1270
|
-
}
|
|
849
|
+
kind,
|
|
850
|
+
...role !== void 0 ? { role } : {},
|
|
851
|
+
own: trait.ownStyle ?? [],
|
|
852
|
+
ownFont: trait.ownFont ?? []
|
|
1271
853
|
};
|
|
1272
854
|
}
|
|
1273
|
-
function
|
|
1274
|
-
|
|
855
|
+
function writeAll(scene, writes, measure) {
|
|
856
|
+
const bodies = [];
|
|
857
|
+
for (const [id, data] of writes) {
|
|
858
|
+
const node = scene.get(id);
|
|
859
|
+
if (node.data?.style?.fontSize !== data?.style?.fontSize && node.parent !== null) bodies.push(node.parent);
|
|
860
|
+
scene.update(id, { data });
|
|
861
|
+
}
|
|
862
|
+
relayoutBodies(scene, bodies, measure);
|
|
1275
863
|
}
|
|
1276
|
-
function
|
|
1277
|
-
const
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
var AMBIENT = [dragPanContribution()];
|
|
1290
|
-
var NO_MARQUEE = { areaSelect: null };
|
|
1291
|
-
var DiagramView = forwardRef(function DiagramView2(props, ref) {
|
|
1292
|
-
return /* @__PURE__ */ jsx(WeaselProvider, { children: /* @__PURE__ */ jsx(Inner, { ...props, apiRef: ref }) });
|
|
1293
|
-
});
|
|
1294
|
-
function Inner({
|
|
1295
|
-
specs,
|
|
1296
|
-
width,
|
|
1297
|
-
height,
|
|
1298
|
-
selected,
|
|
1299
|
-
onSelect,
|
|
1300
|
-
onMove,
|
|
1301
|
-
onConnect,
|
|
1302
|
-
enableKeybindings,
|
|
1303
|
-
canConnect,
|
|
1304
|
-
portOptions,
|
|
1305
|
-
live: liveOpts,
|
|
1306
|
-
minScale,
|
|
1307
|
-
maxScale,
|
|
1308
|
-
anchor,
|
|
1309
|
-
fitPadding,
|
|
1310
|
-
background,
|
|
1311
|
-
view,
|
|
1312
|
-
onViewChange,
|
|
1313
|
-
className,
|
|
1314
|
-
apiRef
|
|
1315
|
-
}) {
|
|
1316
|
-
useEffect(() => registerDiagramShape(), []);
|
|
1317
|
-
const registry = useMemo(() => withDiagramRegistry(), []);
|
|
1318
|
-
const scene = useScene({
|
|
1319
|
-
systemLayers: [{ id: "main" }],
|
|
1320
|
-
initial: specs,
|
|
1321
|
-
registry
|
|
864
|
+
function applyDiagramTheme(scene, theme, label = `Theme: ${theme.name}`, opts = {}) {
|
|
865
|
+
const from = opts.from ?? diagramThemeOf(scene);
|
|
866
|
+
scene.batch(label, () => {
|
|
867
|
+
writeAll(scene, themeWrites(scene, theme, void 0, from), opts.measure);
|
|
868
|
+
scene.doc.set(DIAGRAM_THEME_KEY, themeDocValue(theme));
|
|
869
|
+
}, opts.coalesceKey !== void 0 ? { coalesceKey: opts.coalesceKey } : {});
|
|
870
|
+
}
|
|
871
|
+
var withTrait = (data, trait) => ({ ...data, diagram: trait });
|
|
872
|
+
function setDiagramStyle(scene, ids, role, opts = {}) {
|
|
873
|
+
const theme = opts.theme ?? diagramThemeOf(scene);
|
|
874
|
+
const styled = ids.filter((id) => {
|
|
875
|
+
const n = scene.get(id);
|
|
876
|
+
return n !== void 0 && diagramStyleOf(n) !== null;
|
|
1322
877
|
});
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
return
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
878
|
+
if (styled.length === 0) return;
|
|
879
|
+
scene.batch(opts.label ?? "Set style", () => {
|
|
880
|
+
for (const id of styled) {
|
|
881
|
+
const data = scene.get(id).data;
|
|
882
|
+
const { ownStyle: _, ...trait } = data.diagram;
|
|
883
|
+
scene.update(id, { data: withTrait(data, { ...trait, style: role }) });
|
|
884
|
+
}
|
|
885
|
+
writeAll(scene, themeWrites(scene, theme, new Set(styled)));
|
|
886
|
+
});
|
|
887
|
+
}
|
|
888
|
+
var painted = (paint, color) => ({ ...paint, paint: { ...paint?.paint, color } });
|
|
889
|
+
function textNodesOf(scene, id, kind) {
|
|
890
|
+
if (kind === "shape") return scene.childrenOf(id).filter((c) => typeof scene.get(c)?.data?.text === "string");
|
|
891
|
+
return scene.renderOrderNodes().filter((n) => {
|
|
892
|
+
const deps = n.dependsOn;
|
|
893
|
+
return Array.isArray(deps) && deps.length === 1 && deps[0] === id && "label" in (n.data.diagram ?? {});
|
|
894
|
+
}).map((n) => n.id);
|
|
895
|
+
}
|
|
896
|
+
function setDiagramColor(scene, ids, field, color, opts = {}) {
|
|
897
|
+
scene.batch(opts.label ?? `Set ${field} color`, () => {
|
|
898
|
+
for (const id of ids) {
|
|
899
|
+
const node = scene.get(id);
|
|
900
|
+
const state = node === void 0 ? null : diagramStyleOf(node);
|
|
901
|
+
if (state === null || state.kind === "connector" && field === "fill") continue;
|
|
902
|
+
const data = node.data;
|
|
903
|
+
const own = state.own.includes(field) ? state.own : [...state.own, field];
|
|
904
|
+
const next = withTrait(data, { ...data.diagram, ownStyle: own });
|
|
905
|
+
if (field !== "text") next[field] = painted(data[field], color);
|
|
906
|
+
scene.update(id, { data: next });
|
|
907
|
+
if (field !== "text") continue;
|
|
908
|
+
for (const t of textNodesOf(scene, id, state.kind)) {
|
|
909
|
+
const text = scene.get(t).data;
|
|
910
|
+
scene.update(t, { data: { ...text, fill: painted(text.fill, color) } });
|
|
1346
911
|
}
|
|
1347
|
-
known.current = now;
|
|
1348
|
-
if (moves.length > 0) onMoveRef.current?.(moves);
|
|
1349
|
-
});
|
|
1350
|
-
}, [scene, onMoveRef]);
|
|
1351
|
-
useEffect(() => {
|
|
1352
|
-
if (applied.current === specs) return;
|
|
1353
|
-
syncing.current = true;
|
|
1354
|
-
try {
|
|
1355
|
-
reconcileSpecs(scene, applied.current, specs);
|
|
1356
|
-
} finally {
|
|
1357
|
-
syncing.current = false;
|
|
1358
912
|
}
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
...portOptions,
|
|
1375
|
-
participants: source,
|
|
1376
|
-
...canConnect ? { canConnect } : {},
|
|
1377
|
-
commit: ({ from, to }) => onConnectRef.current?.({
|
|
1378
|
-
from: from.nodeId,
|
|
1379
|
-
fromPort: from.id,
|
|
1380
|
-
to: to.nodeId,
|
|
1381
|
-
toPort: to.id
|
|
1382
|
-
})
|
|
1383
|
-
}) : null, [connecting, source, canConnect, portOptions, onConnectRef]);
|
|
1384
|
-
const canvasRef = useRef(null);
|
|
1385
|
-
useEffect(() => ports ? canvasRef.current?.registerLayer(ports.layer) : void 0, [ports]);
|
|
1386
|
-
const ambient = useMemo(() => ports ? [...AMBIENT, ports.contribution] : AMBIENT, [ports]);
|
|
1387
|
-
const controlled = view !== void 0;
|
|
1388
|
-
const defaultView = useMemo(
|
|
1389
|
-
() => controlled ? void 0 : fitDiagram(specs, { width, height }, { minScale, maxScale, anchor, padding: fitPadding }),
|
|
1390
|
-
// The fit is where the view opens; a later `specs` keeps the view it has.
|
|
1391
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1392
|
-
[controlled, width, height, minScale, maxScale, anchor, fitPadding]
|
|
1393
|
-
);
|
|
1394
|
-
return /* @__PURE__ */ jsx(
|
|
1395
|
-
SceneCanvas,
|
|
1396
|
-
{
|
|
1397
|
-
ref: canvasRef,
|
|
1398
|
-
scene,
|
|
1399
|
-
selection,
|
|
1400
|
-
features: onMove ? ["view", "pick", "move"] : ["view", "pick"],
|
|
1401
|
-
enableKeybindings: enableKeybindings ?? (onMove !== void 0 || onConnect !== void 0),
|
|
1402
|
-
ambient,
|
|
1403
|
-
actions: NO_MARQUEE,
|
|
1404
|
-
onClick,
|
|
1405
|
-
width,
|
|
1406
|
-
height,
|
|
1407
|
-
className,
|
|
1408
|
-
...defaultView ? { defaultView } : {},
|
|
1409
|
-
...view ? { view } : {},
|
|
1410
|
-
...onViewChange ? { onViewChange } : {},
|
|
1411
|
-
...background ? { backgroundFill: background } : {}
|
|
913
|
+
});
|
|
914
|
+
}
|
|
915
|
+
function setDiagramFontSize(scene, ids, size, opts = {}) {
|
|
916
|
+
scene.batch(opts.label ?? "Set text size", () => {
|
|
917
|
+
const writes = /* @__PURE__ */ new Map();
|
|
918
|
+
for (const id of ids) {
|
|
919
|
+
const node = scene.get(id);
|
|
920
|
+
const state = node === void 0 ? null : diagramStyleOf(node);
|
|
921
|
+
if (state === null) continue;
|
|
922
|
+
const data = node.data;
|
|
923
|
+
if (!state.ownFont.includes("size")) scene.update(id, { data: withTrait(data, { ...data.diagram, ownFont: [...state.ownFont, "size"] }) });
|
|
924
|
+
for (const t of textNodesOf(scene, id, state.kind)) {
|
|
925
|
+
const text = scene.get(t).data;
|
|
926
|
+
writes.set(t, { ...text, style: { ...text.style, fontSize: size } });
|
|
927
|
+
}
|
|
1412
928
|
}
|
|
1413
|
-
|
|
929
|
+
writeAll(scene, writes, opts.measure);
|
|
930
|
+
});
|
|
1414
931
|
}
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
932
|
+
|
|
933
|
+
// src/theme/themeEdit.ts
|
|
934
|
+
var isShippedTheme = (theme) => DIAGRAM_THEMES[theme.id] === theme;
|
|
935
|
+
function forkTheme(theme) {
|
|
936
|
+
return { ...structuredClone(theme), id: `${theme.id}-edited`, name: `${theme.name} (edited)` };
|
|
937
|
+
}
|
|
938
|
+
var editableTheme = (theme) => isShippedTheme(theme) ? forkTheme(theme) : theme;
|
|
939
|
+
function themeValue(theme, path) {
|
|
940
|
+
let at = theme;
|
|
941
|
+
for (const key of path) at = at?.[key];
|
|
942
|
+
return at;
|
|
943
|
+
}
|
|
944
|
+
function setIn(at, path, value) {
|
|
945
|
+
if (path.length === 0) return value;
|
|
946
|
+
const [key, ...rest] = path;
|
|
947
|
+
if (Array.isArray(at)) {
|
|
948
|
+
const copy = [...at];
|
|
949
|
+
copy[key] = setIn(copy[key], rest, value);
|
|
950
|
+
return copy;
|
|
951
|
+
}
|
|
952
|
+
const obj = at ?? {};
|
|
953
|
+
return { ...obj, [key]: setIn(obj[key], rest, value) };
|
|
954
|
+
}
|
|
955
|
+
var setThemeValue = (theme, path, value) => setIn(theme, path, value);
|
|
956
|
+
function themeRoles(theme) {
|
|
957
|
+
const shipped = new Map(DIAGRAM_ROLES.map((r) => [r.id, r.label]));
|
|
958
|
+
const ids = [
|
|
959
|
+
...DIAGRAM_ROLES.map((r) => r.id).filter((id) => id in theme.roles),
|
|
960
|
+
...Object.keys(theme.roles).filter((id) => !shipped.has(id))
|
|
961
|
+
];
|
|
962
|
+
return ids.map((id) => ({ id, label: theme.roleLabels?.[id] ?? shipped.get(id) ?? id }));
|
|
963
|
+
}
|
|
964
|
+
var slug = (label) => label.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") || "role";
|
|
965
|
+
function addThemeRole(theme, label) {
|
|
966
|
+
const base = slug(label);
|
|
967
|
+
let id = base;
|
|
968
|
+
for (let n = 2; id in theme.roles; n++) id = `${base}-${n}`;
|
|
969
|
+
return {
|
|
970
|
+
theme: { ...theme, roles: { ...theme.roles, [id]: { ...theme.roles[theme.defaultRole] } }, roleLabels: { ...theme.roleLabels, [id]: label } },
|
|
971
|
+
id
|
|
1425
972
|
};
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
}
|
|
973
|
+
}
|
|
974
|
+
var renameThemeRole = (theme, id, label) => id in theme.roles ? { ...theme, roleLabels: { ...theme.roleLabels, [id]: label } } : theme;
|
|
975
|
+
function removeThemeRole(theme, id) {
|
|
976
|
+
if (id === theme.defaultRole || !(id in theme.roles)) return theme;
|
|
977
|
+
const { [id]: _, ...roles } = theme.roles;
|
|
978
|
+
const { [id]: __, ...labels } = theme.roleLabels ?? {};
|
|
979
|
+
return { ...theme, roles, ...theme.roleLabels ? { roleLabels: labels } : {} };
|
|
980
|
+
}
|
|
981
|
+
var DIAGRAM_THEME_EXTENSION = ".weasel-theme.json";
|
|
982
|
+
var themeToJson = (theme) => `${JSON.stringify(theme, null, 2)}
|
|
983
|
+
`;
|
|
984
|
+
var isObject = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
|
|
985
|
+
function isColor(v) {
|
|
986
|
+
if (typeof v !== "string") return false;
|
|
987
|
+
try {
|
|
988
|
+
parseColor(v);
|
|
989
|
+
return true;
|
|
990
|
+
} catch {
|
|
991
|
+
return false;
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
function checker(errors) {
|
|
995
|
+
const color = (path, v) => {
|
|
996
|
+
if (!isColor(v)) errors.push(`${path}: ${JSON.stringify(v)} is not a color`);
|
|
997
|
+
};
|
|
998
|
+
const box = (path, v) => {
|
|
999
|
+
if (!isObject(v)) return void errors.push(`${path}: missing`);
|
|
1000
|
+
for (const field of ["fill", "stroke", "text"]) color(`${path}.${field}`, v[field]);
|
|
1001
|
+
};
|
|
1002
|
+
const text = (path, v) => {
|
|
1003
|
+
if (typeof v !== "string" || v === "") errors.push(`${path}: missing`);
|
|
1004
|
+
};
|
|
1005
|
+
const font = (path, v) => {
|
|
1006
|
+
if (!isObject(v)) return void errors.push(`${path}: missing`);
|
|
1007
|
+
text(`${path}.family`, v.family);
|
|
1008
|
+
if (typeof v.size !== "number" || !(v.size > 0)) errors.push(`${path}.size: ${JSON.stringify(v.size)} is not a size`);
|
|
1009
|
+
};
|
|
1010
|
+
return { color, box, text, font };
|
|
1011
|
+
}
|
|
1012
|
+
function checkTheme(t, errors) {
|
|
1013
|
+
const { color, box, text, font } = checker(errors);
|
|
1014
|
+
text("id", t.id);
|
|
1015
|
+
text("name", t.name);
|
|
1016
|
+
if (!isObject(t.roles) || Object.keys(t.roles).length === 0) errors.push("roles: missing");
|
|
1017
|
+
else for (const [id, style] of Object.entries(t.roles)) box(`roles.${id}`, style);
|
|
1018
|
+
if (typeof t.defaultRole !== "string") errors.push("defaultRole: missing");
|
|
1019
|
+
else if (isObject(t.roles) && !(t.defaultRole in t.roles)) errors.push(`defaultRole: no role ${JSON.stringify(t.defaultRole)}`);
|
|
1020
|
+
if (t.roleLabels !== void 0) {
|
|
1021
|
+
if (!isObject(t.roleLabels)) errors.push("roleLabels: not an object");
|
|
1022
|
+
else for (const [id, label] of Object.entries(t.roleLabels)) text(`roleLabels.${id}`, label);
|
|
1023
|
+
}
|
|
1024
|
+
if (!isObject(t.edge)) errors.push("edge: missing");
|
|
1025
|
+
else color("edge.stroke", t.edge.stroke);
|
|
1026
|
+
if (!isObject(t.label)) errors.push("label: missing");
|
|
1027
|
+
else {
|
|
1028
|
+
color("label.fill", t.label.fill);
|
|
1029
|
+
if (t.label.font !== void 0) font("label.font", t.label.font);
|
|
1030
|
+
}
|
|
1031
|
+
box("container", t.container);
|
|
1032
|
+
if (!isObject(t.lane)) errors.push("lane: missing");
|
|
1033
|
+
else {
|
|
1034
|
+
box("lane.header", t.lane.header);
|
|
1035
|
+
if (!Array.isArray(t.lane.bodies) || t.lane.bodies.length === 0) errors.push("lane.bodies: missing");
|
|
1036
|
+
else t.lane.bodies.forEach((b, i) => box(`lane.bodies.${i}`, b));
|
|
1037
|
+
}
|
|
1038
|
+
color("page", t.page);
|
|
1039
|
+
font("font", t.font);
|
|
1040
|
+
if (!Array.isArray(t.series)) errors.push("series: missing");
|
|
1041
|
+
else t.series.forEach((c, i) => color(`series.${i}`, c));
|
|
1042
|
+
}
|
|
1043
|
+
function parseDiagramTheme(json) {
|
|
1044
|
+
let raw;
|
|
1045
|
+
try {
|
|
1046
|
+
raw = JSON.parse(json);
|
|
1047
|
+
} catch (err) {
|
|
1048
|
+
return { errors: [`not JSON: ${err.message}`] };
|
|
1049
|
+
}
|
|
1050
|
+
if (!isObject(raw)) return { errors: ["a theme is an object"] };
|
|
1051
|
+
const errors = [];
|
|
1052
|
+
checkTheme(raw, errors);
|
|
1053
|
+
return errors.length > 0 ? { errors } : { theme: raw };
|
|
1432
1054
|
}
|
|
1433
1055
|
|
|
1434
|
-
export {
|
|
1056
|
+
export { CONNECTOR_ACTION_ID, CONNECTOR_TOOL_ID, DIAGRAM_PASS_ORDER, DIAGRAM_THEME_EXTENSION, FLOWCHART_STENCILS, FOLLOW_OFF_PAGE_ACTION_ID, addThemeRole, applyDiagramTheme, connectorTool, createFollowOffPageAction, diagramOffPage, diagramStyleOf, diagramThemeFromTokens, documentBoundsOf, editableTheme, followOffPageBinding, forkTheme, hopFollower, installAvoidRouting, installHops, installLabelPlacement, installOffPageMarkers, isShippedTheme, offPageOps, offPageTargetOf, parseDiagramTheme, removeThemeRole, renameThemeRole, rendererMeasure, routeDataOps, setDiagramColor, setDiagramFontSize, setDiagramStyle, setThemeValue, stencilBuild, stencilIdOf, stencilKind, stencilTool, themeRoles, themeToJson, themeValue, useAvoidRouting, useConnectorTool, useHops, useLabelPlacement, useOffPageMarkers, useStencilInsert, useStencilTool, waypointFollower };
|
|
1435
1057
|
//# sourceMappingURL=index.js.map
|
|
1436
1058
|
//# sourceMappingURL=index.js.map
|