@prnt/dagr-explorer 0.1.3
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/CHANGELOG.md +93 -0
- package/LICENSE +21 -0
- package/README.md +441 -0
- package/dist/base.d.ts +63 -0
- package/dist/base.d.ts.map +1 -0
- package/dist/base.js +20 -0
- package/dist/base.js.map +1 -0
- package/dist/camera.d.ts +61 -0
- package/dist/camera.d.ts.map +1 -0
- package/dist/camera.js +142 -0
- package/dist/camera.js.map +1 -0
- package/dist/context.d.ts +110 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +71 -0
- package/dist/context.js.map +1 -0
- package/dist/dagr-explorer.d.ts +41 -0
- package/dist/dagr-explorer.d.ts.map +1 -0
- package/dist/dagr-explorer.js +13 -0
- package/dist/dagr-explorer.js.map +1 -0
- package/dist/errors.d.ts +55 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +57 -0
- package/dist/errors.js.map +1 -0
- package/dist/explorer-details.d.ts +44 -0
- package/dist/explorer-details.d.ts.map +1 -0
- package/dist/explorer-details.js +76 -0
- package/dist/explorer-details.js.map +1 -0
- package/dist/explorer-search.d.ts +31 -0
- package/dist/explorer-search.d.ts.map +1 -0
- package/dist/explorer-search.js +86 -0
- package/dist/explorer-search.js.map +1 -0
- package/dist/explorer-toolbar.d.ts +17 -0
- package/dist/explorer-toolbar.d.ts.map +1 -0
- package/dist/explorer-toolbar.js +44 -0
- package/dist/explorer-toolbar.js.map +1 -0
- package/dist/explorer-trace-toggle.d.ts +16 -0
- package/dist/explorer-trace-toggle.d.ts.map +1 -0
- package/dist/explorer-trace-toggle.js +8 -0
- package/dist/explorer-trace-toggle.js.map +1 -0
- package/dist/explorer-viewport.d.ts +69 -0
- package/dist/explorer-viewport.d.ts.map +1 -0
- package/dist/explorer-viewport.js +69 -0
- package/dist/explorer-viewport.js.map +1 -0
- package/dist/explorer-views.d.ts +24 -0
- package/dist/explorer-views.d.ts.map +1 -0
- package/dist/explorer-views.js +16 -0
- package/dist/explorer-views.js.map +1 -0
- package/dist/index.d.ts +48 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +33 -0
- package/dist/index.js.map +1 -0
- package/dist/isomorphic-layout-effect.d.ts +7 -0
- package/dist/isomorphic-layout-effect.d.ts.map +1 -0
- package/dist/isomorphic-layout-effect.js +7 -0
- package/dist/isomorphic-layout-effect.js.map +1 -0
- package/dist/labels.d.ts +67 -0
- package/dist/labels.d.ts.map +1 -0
- package/dist/labels.js +50 -0
- package/dist/labels.js.map +1 -0
- package/dist/layout.d.ts +82 -0
- package/dist/layout.d.ts.map +1 -0
- package/dist/layout.js +244 -0
- package/dist/layout.js.map +1 -0
- package/dist/navigation.d.ts +20 -0
- package/dist/navigation.d.ts.map +1 -0
- package/dist/navigation.js +44 -0
- package/dist/navigation.js.map +1 -0
- package/dist/root.d.ts +89 -0
- package/dist/root.d.ts.map +1 -0
- package/dist/root.js +401 -0
- package/dist/root.js.map +1 -0
- package/dist/search.d.ts +19 -0
- package/dist/search.d.ts.map +1 -0
- package/dist/search.js +31 -0
- package/dist/search.js.map +1 -0
- package/dist/size.d.ts +12 -0
- package/dist/size.d.ts.map +1 -0
- package/dist/size.js +20 -0
- package/dist/size.js.map +1 -0
- package/dist/svg-base.d.ts +33 -0
- package/dist/svg-base.d.ts.map +1 -0
- package/dist/svg-base.js +89 -0
- package/dist/svg-base.js.map +1 -0
- package/dist/types.d.ts +61 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/use-explorer-camera.d.ts +56 -0
- package/dist/use-explorer-camera.d.ts.map +1 -0
- package/dist/use-explorer-camera.js +641 -0
- package/dist/use-explorer-camera.js.map +1 -0
- package/dist/use-explorer.d.ts +23 -0
- package/dist/use-explorer.d.ts.map +1 -0
- package/dist/use-explorer.js +26 -0
- package/dist/use-explorer.js.map +1 -0
- package/dist/validate.d.ts +20 -0
- package/dist/validate.d.ts.map +1 -0
- package/dist/validate.js +99 -0
- package/dist/validate.js.map +1 -0
- package/dist/viewport-surface.d.ts +90 -0
- package/dist/viewport-surface.d.ts.map +1 -0
- package/dist/viewport-surface.js +485 -0
- package/dist/viewport-surface.js.map +1 -0
- package/dist/visible-set.d.ts +65 -0
- package/dist/visible-set.d.ts.map +1 -0
- package/dist/visible-set.js +168 -0
- package/dist/visible-set.js.map +1 -0
- package/package.json +62 -0
- package/src/base.ts +67 -0
- package/src/camera.ts +206 -0
- package/src/context.ts +168 -0
- package/src/dagr-explorer.tsx +85 -0
- package/src/errors.ts +85 -0
- package/src/explorer-details.tsx +156 -0
- package/src/explorer-search.tsx +140 -0
- package/src/explorer-toolbar.tsx +77 -0
- package/src/explorer-trace-toggle.tsx +34 -0
- package/src/explorer-viewport.tsx +181 -0
- package/src/explorer-views.tsx +61 -0
- package/src/index.ts +76 -0
- package/src/isomorphic-layout-effect.ts +7 -0
- package/src/labels.ts +109 -0
- package/src/layout.ts +298 -0
- package/src/navigation.ts +51 -0
- package/src/root.tsx +544 -0
- package/src/search.ts +36 -0
- package/src/size.ts +23 -0
- package/src/svg-base.tsx +173 -0
- package/src/types.ts +69 -0
- package/src/use-explorer-camera.ts +675 -0
- package/src/use-explorer.ts +32 -0
- package/src/validate.ts +173 -0
- package/src/viewport-surface.tsx +650 -0
- package/src/visible-set.ts +225 -0
- package/styles.css +253 -0
package/src/validate.ts
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { ExplorerDataError } from './errors.js';
|
|
2
|
+
import { resolveNodeSize } from './size.js';
|
|
3
|
+
import type { ExplorerEdge, ExplorerNode, ExplorerView } from './types.js';
|
|
4
|
+
|
|
5
|
+
const positive = (value: number): boolean => Number.isFinite(value) && value > 0;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Throws {@link ExplorerDataError} for a view that cannot be drawn honestly.
|
|
9
|
+
*
|
|
10
|
+
* Sizes are checked here rather than left to the layout engine, which accepts
|
|
11
|
+
* a zero-size node and reports `NaN` as a fault in its own config. Neither
|
|
12
|
+
* tells the caller which node to fix.
|
|
13
|
+
*
|
|
14
|
+
* Layout options are checked for the same reason: the engine rejects a bad
|
|
15
|
+
* spacing with an error that names no view, the renderer throws a bare
|
|
16
|
+
* `RangeError` for an unknown edge style, and an unknown direction would be
|
|
17
|
+
* read as `'down'` without a word.
|
|
18
|
+
*
|
|
19
|
+
* A self loop and parallel edges are valid data. What happens to them is
|
|
20
|
+
* `layout.ts`'s business.
|
|
21
|
+
*/
|
|
22
|
+
export function validateView<N extends ExplorerNode, E extends ExplorerEdge>(
|
|
23
|
+
view: ExplorerView<N, E>,
|
|
24
|
+
): void {
|
|
25
|
+
const where = `view "${view.id}"`;
|
|
26
|
+
|
|
27
|
+
if (view.id === '') {
|
|
28
|
+
throw new ExplorerDataError('INVALID_ID', 'A view has an empty id. Ids must not be empty', '');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const layoutOptions = view.layout;
|
|
32
|
+
if (layoutOptions !== undefined) {
|
|
33
|
+
for (const key of ['nodeSep', 'rankSep'] as const) {
|
|
34
|
+
const value = layoutOptions[key];
|
|
35
|
+
if (value !== undefined && !(Number.isFinite(value) && value >= 0)) {
|
|
36
|
+
throw new ExplorerDataError(
|
|
37
|
+
'INVALID_LAYOUT_OPTION',
|
|
38
|
+
`View "${view.id}" has layout.${key} ${String(value)}. It must be a finite number that is zero or greater`,
|
|
39
|
+
view.id,
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
const { direction, edgeStyle } = layoutOptions;
|
|
44
|
+
if (direction !== undefined && direction !== 'right' && direction !== 'down') {
|
|
45
|
+
throw new ExplorerDataError(
|
|
46
|
+
'INVALID_LAYOUT_OPTION',
|
|
47
|
+
`View "${view.id}" has layout.direction ${JSON.stringify(direction)}. It must be "right" or "down"`,
|
|
48
|
+
view.id,
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
if (edgeStyle !== undefined && edgeStyle !== 'smooth' && edgeStyle !== 'orthogonal') {
|
|
52
|
+
throw new ExplorerDataError(
|
|
53
|
+
'INVALID_LAYOUT_OPTION',
|
|
54
|
+
`View "${view.id}" has layout.edgeStyle ${JSON.stringify(edgeStyle)}. It must be "smooth" or "orthogonal"`,
|
|
55
|
+
view.id,
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const nodeIds = new Set<string>();
|
|
61
|
+
for (const node of view.nodes) {
|
|
62
|
+
if (node.id === '') {
|
|
63
|
+
throw new ExplorerDataError(
|
|
64
|
+
'INVALID_ID',
|
|
65
|
+
`A node in view "${view.id}" has an empty id. Ids must not be empty`,
|
|
66
|
+
'',
|
|
67
|
+
view.id,
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
if (nodeIds.has(node.id)) {
|
|
71
|
+
throw new ExplorerDataError(
|
|
72
|
+
'DUPLICATE_NODE_ID',
|
|
73
|
+
`Duplicate node id "${node.id}" in ${where}`,
|
|
74
|
+
node.id,
|
|
75
|
+
view.id,
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
nodeIds.add(node.id);
|
|
79
|
+
const size = resolveNodeSize(view.layout, node);
|
|
80
|
+
if (!positive(size.width) || !positive(size.height)) {
|
|
81
|
+
throw new ExplorerDataError(
|
|
82
|
+
'INVALID_NODE_SIZE',
|
|
83
|
+
`Node "${node.id}" in ${where} has size ${String(size.width)} by ${String(size.height)}. Width and height must be finite and greater than zero`,
|
|
84
|
+
node.id,
|
|
85
|
+
view.id,
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const edgeIds = new Set<string>();
|
|
91
|
+
for (const edge of view.edges) {
|
|
92
|
+
if (edge.id === '') {
|
|
93
|
+
throw new ExplorerDataError(
|
|
94
|
+
'INVALID_ID',
|
|
95
|
+
`An edge in view "${view.id}" has an empty id. Ids must not be empty`,
|
|
96
|
+
'',
|
|
97
|
+
view.id,
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
if (edgeIds.has(edge.id)) {
|
|
101
|
+
throw new ExplorerDataError(
|
|
102
|
+
'DUPLICATE_EDGE_ID',
|
|
103
|
+
`Duplicate edge id "${edge.id}" in ${where}`,
|
|
104
|
+
edge.id,
|
|
105
|
+
view.id,
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
edgeIds.add(edge.id);
|
|
109
|
+
for (const end of [edge.source, edge.target]) {
|
|
110
|
+
if (!nodeIds.has(end)) {
|
|
111
|
+
throw new ExplorerDataError(
|
|
112
|
+
'MISSING_EDGE_ENDPOINT',
|
|
113
|
+
`Edge "${edge.id}" in ${where} names missing node "${end}"`,
|
|
114
|
+
edge.id,
|
|
115
|
+
view.id,
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const groupIds = new Set<string>();
|
|
122
|
+
for (const group of view.groups ?? []) {
|
|
123
|
+
if (group.id === '') {
|
|
124
|
+
throw new ExplorerDataError(
|
|
125
|
+
'INVALID_ID',
|
|
126
|
+
`A group in view "${view.id}" has an empty id. Ids must not be empty`,
|
|
127
|
+
'',
|
|
128
|
+
view.id,
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
if (groupIds.has(group.id)) {
|
|
132
|
+
throw new ExplorerDataError(
|
|
133
|
+
'DUPLICATE_GROUP_ID',
|
|
134
|
+
`Duplicate group id "${group.id}" in ${where}`,
|
|
135
|
+
group.id,
|
|
136
|
+
view.id,
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
groupIds.add(group.id);
|
|
140
|
+
if (group.nodeIds.length === 0) {
|
|
141
|
+
throw new ExplorerDataError(
|
|
142
|
+
'EMPTY_GROUP',
|
|
143
|
+
`Group "${group.id}" in ${where} has no nodes`,
|
|
144
|
+
group.id,
|
|
145
|
+
view.id,
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
for (const id of group.nodeIds) {
|
|
149
|
+
if (!nodeIds.has(id)) {
|
|
150
|
+
throw new ExplorerDataError(
|
|
151
|
+
'MISSING_GROUP_MEMBER',
|
|
152
|
+
`Group "${group.id}" in ${where} names missing node "${id}"`,
|
|
153
|
+
group.id,
|
|
154
|
+
view.id,
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** Validates every view, and that no two share an id. Ids may repeat across views. */
|
|
162
|
+
export function validateViews<N extends ExplorerNode, E extends ExplorerEdge>(
|
|
163
|
+
views: readonly ExplorerView<N, E>[],
|
|
164
|
+
): void {
|
|
165
|
+
const viewIds = new Set<string>();
|
|
166
|
+
for (const view of views) {
|
|
167
|
+
if (viewIds.has(view.id)) {
|
|
168
|
+
throw new ExplorerDataError('DUPLICATE_VIEW_ID', `Duplicate view id "${view.id}"`, view.id);
|
|
169
|
+
}
|
|
170
|
+
viewIds.add(view.id);
|
|
171
|
+
validateView(view);
|
|
172
|
+
}
|
|
173
|
+
}
|