@rocksoliddigital/payload-lineage 0.0.0-stage → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +143 -2
- package/dist/chunk-GL5UMO2C.js +203 -0
- package/dist/client.d.ts +104 -0
- package/dist/client.js +918 -0
- package/dist/index.d.ts +161 -0
- package/dist/index.js +81 -0
- package/dist/lineage.css +520 -0
- package/dist/rsc.d.ts +22 -0
- package/dist/rsc.js +176 -0
- package/package.json +77 -3
package/dist/client.js
ADDED
|
@@ -0,0 +1,918 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
// src/view/ErdCanvas.tsx
|
|
4
|
+
import "@xyflow/react/dist/style.css";
|
|
5
|
+
import "./lineage.css";
|
|
6
|
+
import {
|
|
7
|
+
Background,
|
|
8
|
+
Controls,
|
|
9
|
+
Handle,
|
|
10
|
+
MiniMap,
|
|
11
|
+
Panel,
|
|
12
|
+
Position,
|
|
13
|
+
ReactFlow,
|
|
14
|
+
ReactFlowProvider,
|
|
15
|
+
useReactFlow
|
|
16
|
+
} from "@xyflow/react";
|
|
17
|
+
|
|
18
|
+
// ../lineage-core/src/react.ts
|
|
19
|
+
import { useEffect, useState } from "react";
|
|
20
|
+
|
|
21
|
+
// ../lineage-core/src/layout.ts
|
|
22
|
+
import ELK from "elkjs/lib/elk.bundled.js";
|
|
23
|
+
function layoutInput(nodes, edges, { layerSpacing, nodeSpacing, placement }) {
|
|
24
|
+
return {
|
|
25
|
+
id: "root",
|
|
26
|
+
layoutOptions: {
|
|
27
|
+
"elk.algorithm": "layered",
|
|
28
|
+
"elk.direction": "RIGHT",
|
|
29
|
+
"elk.layered.spacing.nodeNodeBetweenLayers": String(layerSpacing),
|
|
30
|
+
"elk.spacing.nodeNode": String(nodeSpacing),
|
|
31
|
+
"elk.layered.nodePlacement.strategy": placement
|
|
32
|
+
},
|
|
33
|
+
children: nodes.map(({ id, width, height }) => ({ id, width, height })),
|
|
34
|
+
edges: edges.filter((e) => e.source !== e.target).map((e, i) => ({
|
|
35
|
+
id: `e${i}`,
|
|
36
|
+
sources: [e.source],
|
|
37
|
+
targets: [e.target],
|
|
38
|
+
...e.priority !== void 0 && {
|
|
39
|
+
layoutOptions: { "elk.layered.priority.direction": String(e.priority) }
|
|
40
|
+
}
|
|
41
|
+
}))
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
var elk;
|
|
45
|
+
async function layout(nodes, edges, options) {
|
|
46
|
+
elk ??= new ELK();
|
|
47
|
+
const result = await elk.layout(layoutInput(nodes, edges, options));
|
|
48
|
+
return new Map(result.children?.map((c) => [c.id, { x: c.x ?? 0, y: c.y ?? 0 }]));
|
|
49
|
+
}
|
|
50
|
+
function layoutKey(nodes, edges, { layerSpacing, nodeSpacing, placement }) {
|
|
51
|
+
return [
|
|
52
|
+
nodes.map((n) => `${n.id}:${n.width}x${n.height}`).sort().join("|"),
|
|
53
|
+
edges.map((e) => `${e.source}>${e.target}:${e.priority ?? 0}`).sort().join("|"),
|
|
54
|
+
`${layerSpacing}/${nodeSpacing}/${placement}`
|
|
55
|
+
].join("#");
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// ../lineage-core/src/react.ts
|
|
59
|
+
function useLayout(nodes, edges, options, onLayout) {
|
|
60
|
+
const [positions, setPositions] = useState(null);
|
|
61
|
+
const key = layoutKey(nodes, edges, options);
|
|
62
|
+
useEffect(() => {
|
|
63
|
+
let cancelled = false;
|
|
64
|
+
layout(nodes, edges, options).then((next) => {
|
|
65
|
+
if (cancelled) return;
|
|
66
|
+
setPositions(next);
|
|
67
|
+
onLayout?.();
|
|
68
|
+
});
|
|
69
|
+
return () => {
|
|
70
|
+
cancelled = true;
|
|
71
|
+
};
|
|
72
|
+
}, [key]);
|
|
73
|
+
return positions;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// src/view/ErdCanvas.tsx
|
|
77
|
+
import { useEffect as useEffect2, useMemo, useRef, useState as useState2 } from "react";
|
|
78
|
+
|
|
79
|
+
// src/view/erdModel.ts
|
|
80
|
+
var TABLE_WIDTH = 260;
|
|
81
|
+
var HEADER_HEIGHT = 44;
|
|
82
|
+
var ROW_HEIGHT = 24;
|
|
83
|
+
var FOOTER_HEIGHT = 26;
|
|
84
|
+
var ROW_LIMIT = 10;
|
|
85
|
+
function cardinalityOf(relation) {
|
|
86
|
+
if (relation.from.startsWith("global:")) return relation.many ? "one-to-many" : "one-to-one";
|
|
87
|
+
if (relation.many) return "many-to-many";
|
|
88
|
+
return relation.unique ? "one-to-one" : "many-to-one";
|
|
89
|
+
}
|
|
90
|
+
var LOOSENESS = ["one-to-one", "one-to-many", "many-to-one", "many-to-many"];
|
|
91
|
+
function describeCardinality(cardinality, from, to) {
|
|
92
|
+
if (from.kind === "global") {
|
|
93
|
+
return cardinality === "one-to-many" ? `The ${from.label} references many ${to.label}.` : `The ${from.label} references one ${to.singular}.`;
|
|
94
|
+
}
|
|
95
|
+
if (cardinality === "one-to-one") {
|
|
96
|
+
return `Each ${from.singular} references one ${to.singular}, and a ${to.singular} can be referenced by only one ${from.singular}.`;
|
|
97
|
+
}
|
|
98
|
+
if (cardinality === "many-to-one") {
|
|
99
|
+
return `Each ${from.singular} references one ${to.singular}; a ${to.singular} can be referenced by many ${from.label}.`;
|
|
100
|
+
}
|
|
101
|
+
return `Each ${from.singular} references many ${to.label}; a ${to.singular} can be referenced by many ${from.label}.`;
|
|
102
|
+
}
|
|
103
|
+
function erdEdges(relations) {
|
|
104
|
+
const edges = /* @__PURE__ */ new Map();
|
|
105
|
+
for (const r of relations) {
|
|
106
|
+
if (r.from === r.to) continue;
|
|
107
|
+
const id = `${r.from}.${r.field}->${r.to}`;
|
|
108
|
+
const edge = edges.get(id) ?? {
|
|
109
|
+
id,
|
|
110
|
+
from: r.from,
|
|
111
|
+
field: r.field,
|
|
112
|
+
to: r.to,
|
|
113
|
+
cardinality: cardinalityOf(r),
|
|
114
|
+
relations: []
|
|
115
|
+
};
|
|
116
|
+
edge.relations.push(r);
|
|
117
|
+
const own = cardinalityOf(r);
|
|
118
|
+
if (LOOSENESS.indexOf(own) > LOOSENESS.indexOf(edge.cardinality)) edge.cardinality = own;
|
|
119
|
+
edges.set(id, edge);
|
|
120
|
+
}
|
|
121
|
+
return [...edges.values()];
|
|
122
|
+
}
|
|
123
|
+
function selfReferences(entity, relations) {
|
|
124
|
+
return new Set(
|
|
125
|
+
relations.filter((r) => r.from === entity.id && r.to === entity.id).map((r) => r.field)
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
function visibleRows(entity, relations, { keysOnly, expanded }) {
|
|
129
|
+
const linked = new Set(relations.filter((r) => r.from === entity.id).map((r) => r.field));
|
|
130
|
+
const isKey = (f) => f.type === "id" || f.name === "id" || linked.has(f.name) || f.type === "join";
|
|
131
|
+
if (keysOnly) {
|
|
132
|
+
const rows2 = entity.fields.filter(isKey);
|
|
133
|
+
return { rows: rows2, hidden: entity.fields.length - rows2.length };
|
|
134
|
+
}
|
|
135
|
+
if (expanded || entity.fields.length <= ROW_LIMIT) return { rows: entity.fields, hidden: 0 };
|
|
136
|
+
const keys = entity.fields.filter(isKey).length;
|
|
137
|
+
let budget = Math.max(ROW_LIMIT - keys, 0);
|
|
138
|
+
const rows = entity.fields.filter((f) => isKey(f) || budget-- > 0);
|
|
139
|
+
return { rows, hidden: entity.fields.length - rows.length };
|
|
140
|
+
}
|
|
141
|
+
function tableHeight(rows, hidden) {
|
|
142
|
+
return HEADER_HEIGHT + rows * ROW_HEIGHT + (hidden ? FOOTER_HEIGHT : 0) + 4;
|
|
143
|
+
}
|
|
144
|
+
var ERD_LAYOUT = {
|
|
145
|
+
layerSpacing: 110,
|
|
146
|
+
nodeSpacing: 28,
|
|
147
|
+
placement: "NETWORK_SIMPLEX"
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
// src/view/model.ts
|
|
151
|
+
var NODE_WIDTH = 200;
|
|
152
|
+
var STACKED_BELOW = 960;
|
|
153
|
+
function fitPadding(layoutWidth) {
|
|
154
|
+
const right = layoutWidth > STACKED_BELOW ? "360px" : "24px";
|
|
155
|
+
return { top: "96px", right, bottom: "24px", left: "24px" };
|
|
156
|
+
}
|
|
157
|
+
var NODE_HEIGHT = 48;
|
|
158
|
+
function visibleGraph(graph, { hiddenKinds, hideUnconnected }) {
|
|
159
|
+
let nodes = graph.nodes.filter((n) => !hiddenKinds.has(n.kind));
|
|
160
|
+
let edges = edgesBetween(nodes, graph.edges);
|
|
161
|
+
if (hideUnconnected) {
|
|
162
|
+
const linked = new Set(
|
|
163
|
+
edges.filter((e) => e.source !== e.target).flatMap((e) => [e.source, e.target])
|
|
164
|
+
);
|
|
165
|
+
nodes = nodes.filter((n) => linked.has(n.id));
|
|
166
|
+
edges = edgesBetween(nodes, edges);
|
|
167
|
+
}
|
|
168
|
+
return { nodes, edges };
|
|
169
|
+
}
|
|
170
|
+
var LINEAGE_LAYOUT = {
|
|
171
|
+
layerSpacing: 80,
|
|
172
|
+
nodeSpacing: 16,
|
|
173
|
+
placement: "NETWORK_SIMPLEX"
|
|
174
|
+
};
|
|
175
|
+
function layoutEdges(edges) {
|
|
176
|
+
return edges.map(({ source, target, kind }) => ({
|
|
177
|
+
source,
|
|
178
|
+
target,
|
|
179
|
+
priority: kind === "relationship" ? 0 : 10
|
|
180
|
+
}));
|
|
181
|
+
}
|
|
182
|
+
function linksToItself(id, edges) {
|
|
183
|
+
return edges.some((e) => e.source === id && e.target === id);
|
|
184
|
+
}
|
|
185
|
+
function edgesBetween(nodes, edges) {
|
|
186
|
+
const ids = new Set(nodes.map((n) => n.id));
|
|
187
|
+
return edges.filter((e) => ids.has(e.source) && ids.has(e.target));
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// src/view/ErdCanvas.tsx
|
|
191
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
192
|
+
var ErdCanvas = (props) => /* @__PURE__ */ jsx(ReactFlowProvider, { children: /* @__PURE__ */ jsx(Canvas, { ...props }) });
|
|
193
|
+
var Canvas = ({ schema, adminURLs }) => {
|
|
194
|
+
const { fitView } = useReactFlow();
|
|
195
|
+
const layoutRef = useRef(null);
|
|
196
|
+
const [showGlobals, setShowGlobals] = useState2(true);
|
|
197
|
+
const [keysOnly, setKeysOnly] = useState2(false);
|
|
198
|
+
const [expanded, setExpanded] = useState2(/* @__PURE__ */ new Set());
|
|
199
|
+
const [hovered, setHovered] = useState2(null);
|
|
200
|
+
const [selected, setSelected] = useState2(null);
|
|
201
|
+
const entities = useMemo(
|
|
202
|
+
() => schema.entities.filter((e) => showGlobals || e.kind !== "global"),
|
|
203
|
+
[schema, showGlobals]
|
|
204
|
+
);
|
|
205
|
+
const relations = useMemo(() => {
|
|
206
|
+
const ids = new Set(entities.map((e) => e.id));
|
|
207
|
+
return schema.relations.filter((r) => ids.has(r.from) && ids.has(r.to));
|
|
208
|
+
}, [schema, entities]);
|
|
209
|
+
const lines = useMemo(() => erdEdges(relations), [relations]);
|
|
210
|
+
const tables = useMemo(
|
|
211
|
+
() => entities.map((entity) => {
|
|
212
|
+
const isExpanded = expanded.has(entity.id);
|
|
213
|
+
const { rows, hidden } = visibleRows(entity, relations, { keysOnly, expanded: isExpanded });
|
|
214
|
+
return {
|
|
215
|
+
entity,
|
|
216
|
+
rows,
|
|
217
|
+
hidden,
|
|
218
|
+
expanded: isExpanded,
|
|
219
|
+
// Room for the "+N more" or "Show fewer" button under the rows
|
|
220
|
+
height: tableHeight(rows.length, !keysOnly && (hidden || isExpanded) ? 1 : 0),
|
|
221
|
+
selfRefs: selfReferences(entity, relations),
|
|
222
|
+
linked: new Set(relations.filter((r) => r.from === entity.id).map((r) => r.field))
|
|
223
|
+
};
|
|
224
|
+
}),
|
|
225
|
+
[entities, relations, keysOnly, expanded]
|
|
226
|
+
);
|
|
227
|
+
const positions = useLayout(
|
|
228
|
+
useMemo(
|
|
229
|
+
() => tables.map((t) => ({ id: t.entity.id, width: TABLE_WIDTH, height: t.height })),
|
|
230
|
+
[tables]
|
|
231
|
+
),
|
|
232
|
+
useMemo(() => lines.map((l) => ({ source: l.from, target: l.to })), [lines]),
|
|
233
|
+
ERD_LAYOUT,
|
|
234
|
+
() => requestAnimationFrame(
|
|
235
|
+
() => fitView({ padding: fitPadding(layoutRef.current?.clientWidth ?? 0) })
|
|
236
|
+
)
|
|
237
|
+
);
|
|
238
|
+
const neighbours = useMemo(() => {
|
|
239
|
+
if (!hovered) return null;
|
|
240
|
+
const ids = /* @__PURE__ */ new Set([hovered]);
|
|
241
|
+
for (const l of lines) {
|
|
242
|
+
if (l.from === hovered) ids.add(l.to);
|
|
243
|
+
if (l.to === hovered) ids.add(l.from);
|
|
244
|
+
}
|
|
245
|
+
return ids;
|
|
246
|
+
}, [hovered, lines]);
|
|
247
|
+
const nodes = useMemo(() => {
|
|
248
|
+
if (!positions) return [];
|
|
249
|
+
return tables.flatMap((t) => {
|
|
250
|
+
const position = positions.get(t.entity.id);
|
|
251
|
+
if (!position) return [];
|
|
252
|
+
let state = "default";
|
|
253
|
+
if (neighbours)
|
|
254
|
+
state = t.entity.id === hovered ? "active" : neighbours.has(t.entity.id) ? "related" : "dimmed";
|
|
255
|
+
else if (t.entity.id === selected) state = "selected";
|
|
256
|
+
return [
|
|
257
|
+
{
|
|
258
|
+
id: t.entity.id,
|
|
259
|
+
type: "table",
|
|
260
|
+
position,
|
|
261
|
+
width: TABLE_WIDTH,
|
|
262
|
+
height: t.height,
|
|
263
|
+
data: {
|
|
264
|
+
entity: t.entity,
|
|
265
|
+
rows: t.rows,
|
|
266
|
+
hidden: t.hidden,
|
|
267
|
+
expanded: t.expanded,
|
|
268
|
+
canExpand: !keysOnly,
|
|
269
|
+
selfRefs: t.selfRefs,
|
|
270
|
+
linked: t.linked,
|
|
271
|
+
state,
|
|
272
|
+
onSelect: setSelected,
|
|
273
|
+
onTrace: setHovered,
|
|
274
|
+
onToggle: () => setExpanded((current) => {
|
|
275
|
+
const next = new Set(current);
|
|
276
|
+
if (next.has(t.entity.id)) next.delete(t.entity.id);
|
|
277
|
+
else next.add(t.entity.id);
|
|
278
|
+
return next;
|
|
279
|
+
})
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
];
|
|
283
|
+
});
|
|
284
|
+
}, [positions, tables, neighbours, hovered, selected, keysOnly]);
|
|
285
|
+
const edges = useMemo(
|
|
286
|
+
() => lines.map((l) => {
|
|
287
|
+
const on = neighbours && (l.from === hovered || l.to === hovered);
|
|
288
|
+
const state = neighbours ? on ? " rs-lineage__edge--active" : " rs-lineage__edge--dimmed" : "";
|
|
289
|
+
const kind = l.relations.every((r) => r.kind === "upload") ? "upload" : "relationship";
|
|
290
|
+
return {
|
|
291
|
+
id: l.id,
|
|
292
|
+
source: l.from,
|
|
293
|
+
sourceHandle: `row:${l.field}`,
|
|
294
|
+
target: l.to,
|
|
295
|
+
targetHandle: "table",
|
|
296
|
+
type: "smoothstep",
|
|
297
|
+
label: l.cardinality,
|
|
298
|
+
labelClassName: "rs-erd__cardinality",
|
|
299
|
+
labelBgClassName: "rs-erd__cardinality-bg",
|
|
300
|
+
className: `rs-lineage__edge rs-lineage__edge--${kind}${state}`,
|
|
301
|
+
animated: !!on
|
|
302
|
+
};
|
|
303
|
+
}),
|
|
304
|
+
[lines, neighbours, hovered]
|
|
305
|
+
);
|
|
306
|
+
useEffect2(() => {
|
|
307
|
+
const onKey = (event) => event.key === "Escape" && setSelected(null);
|
|
308
|
+
window.addEventListener("keydown", onKey);
|
|
309
|
+
return () => window.removeEventListener("keydown", onKey);
|
|
310
|
+
}, []);
|
|
311
|
+
const byId = useMemo(() => new Map(schema.entities.map((e) => [e.id, e])), [schema]);
|
|
312
|
+
const focused = selected ? byId.get(selected) : void 0;
|
|
313
|
+
const fieldCount = entities.reduce((n, e) => n + e.fields.length, 0);
|
|
314
|
+
return /* @__PURE__ */ jsxs("div", { className: "rs-lineage-layout", ref: layoutRef, children: [
|
|
315
|
+
/* @__PURE__ */ jsx("div", { className: "rs-lineage", "data-ready": positions ? "" : void 0, children: /* @__PURE__ */ jsxs(
|
|
316
|
+
ReactFlow,
|
|
317
|
+
{
|
|
318
|
+
nodes,
|
|
319
|
+
edges,
|
|
320
|
+
nodeTypes,
|
|
321
|
+
nodesDraggable: false,
|
|
322
|
+
nodesFocusable: false,
|
|
323
|
+
edgesFocusable: false,
|
|
324
|
+
nodesConnectable: false,
|
|
325
|
+
elementsSelectable: false,
|
|
326
|
+
minZoom: 0.1,
|
|
327
|
+
onNodeMouseEnter: (_, node) => setHovered(node.id),
|
|
328
|
+
onNodeMouseLeave: () => setHovered(null),
|
|
329
|
+
onNodeClick: (_, node) => setSelected(node.id),
|
|
330
|
+
onPaneClick: () => setSelected(null),
|
|
331
|
+
proOptions: { hideAttribution: true },
|
|
332
|
+
children: [
|
|
333
|
+
/* @__PURE__ */ jsx(Background, { gap: 24 }),
|
|
334
|
+
/* @__PURE__ */ jsx(Controls, { showInteractive: false }),
|
|
335
|
+
/* @__PURE__ */ jsx(
|
|
336
|
+
MiniMap,
|
|
337
|
+
{
|
|
338
|
+
pannable: true,
|
|
339
|
+
zoomable: true,
|
|
340
|
+
className: "rs-lineage__minimap",
|
|
341
|
+
nodeClassName: (node) => `rs-lineage__minimap-node--${node.data.entity.kind}`,
|
|
342
|
+
ariaLabel: "Data model overview"
|
|
343
|
+
}
|
|
344
|
+
),
|
|
345
|
+
/* @__PURE__ */ jsx(Panel, { position: "top-left", children: /* @__PURE__ */ jsxs("div", { className: "rs-lineage__toolbar", children: [
|
|
346
|
+
/* @__PURE__ */ jsxs("label", { className: "rs-lineage__toggle", children: [
|
|
347
|
+
/* @__PURE__ */ jsx(
|
|
348
|
+
"input",
|
|
349
|
+
{
|
|
350
|
+
type: "checkbox",
|
|
351
|
+
checked: showGlobals,
|
|
352
|
+
onChange: () => setShowGlobals((v) => !v)
|
|
353
|
+
}
|
|
354
|
+
),
|
|
355
|
+
/* @__PURE__ */ jsx("span", { className: "rs-lineage__swatch rs-lineage__swatch--global" }),
|
|
356
|
+
"Globals"
|
|
357
|
+
] }),
|
|
358
|
+
/* @__PURE__ */ jsxs("label", { className: "rs-lineage__toggle", children: [
|
|
359
|
+
/* @__PURE__ */ jsx("input", { type: "checkbox", checked: keysOnly, onChange: () => setKeysOnly((v) => !v) }),
|
|
360
|
+
"Key fields only"
|
|
361
|
+
] }),
|
|
362
|
+
/* @__PURE__ */ jsxs("span", { className: "rs-lineage__count", children: [
|
|
363
|
+
entities.length,
|
|
364
|
+
" tables, ",
|
|
365
|
+
fieldCount,
|
|
366
|
+
" fields, ",
|
|
367
|
+
lines.length,
|
|
368
|
+
" references"
|
|
369
|
+
] })
|
|
370
|
+
] }) })
|
|
371
|
+
]
|
|
372
|
+
}
|
|
373
|
+
) }),
|
|
374
|
+
/* @__PURE__ */ jsx(
|
|
375
|
+
Details,
|
|
376
|
+
{
|
|
377
|
+
entity: focused,
|
|
378
|
+
lines,
|
|
379
|
+
byId,
|
|
380
|
+
adminURLs,
|
|
381
|
+
onSelect: setSelected
|
|
382
|
+
}
|
|
383
|
+
)
|
|
384
|
+
] });
|
|
385
|
+
};
|
|
386
|
+
var Details = ({ entity, lines, byId, adminURLs, onSelect }) => {
|
|
387
|
+
if (!entity) {
|
|
388
|
+
return /* @__PURE__ */ jsxs("aside", { className: "rs-lineage__details", children: [
|
|
389
|
+
/* @__PURE__ */ jsx("p", { className: "rs-lineage__hint", children: "Hover a table to see what it references and what references it. Click it for the field paths behind each line. Esc clears the selection." }),
|
|
390
|
+
/* @__PURE__ */ jsxs("dl", { className: "rs-lineage__legend", children: [
|
|
391
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
392
|
+
/* @__PURE__ */ jsx("dt", { className: "rs-erd__key", children: "many-to-one" }),
|
|
393
|
+
/* @__PURE__ */ jsx("dd", { children: "each document references one; many can share it" })
|
|
394
|
+
] }),
|
|
395
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
396
|
+
/* @__PURE__ */ jsx("dt", { className: "rs-erd__key", children: "many-to-many" }),
|
|
397
|
+
/* @__PURE__ */ jsx("dd", { children: "a list of references: hasMany, or inside an array or blocks" })
|
|
398
|
+
] }),
|
|
399
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
400
|
+
/* @__PURE__ */ jsx("dt", { className: "rs-erd__key", children: "one-to-one" }),
|
|
401
|
+
/* @__PURE__ */ jsx("dd", { children: "a unique reference, or a global's single one" })
|
|
402
|
+
] }),
|
|
403
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
404
|
+
/* @__PURE__ */ jsx("dt", { className: "rs-erd__key", children: "one-to-many" }),
|
|
405
|
+
/* @__PURE__ */ jsx("dd", { children: "a global's list of references" })
|
|
406
|
+
] }),
|
|
407
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
408
|
+
/* @__PURE__ */ jsx("dt", { className: "rs-erd__key", children: "\u21BB" }),
|
|
409
|
+
/* @__PURE__ */ jsx("dd", { children: "points at its own collection" })
|
|
410
|
+
] }),
|
|
411
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
412
|
+
/* @__PURE__ */ jsx("dt", { className: "rs-erd__key", children: "*" }),
|
|
413
|
+
/* @__PURE__ */ jsx("dd", { children: "required" })
|
|
414
|
+
] })
|
|
415
|
+
] })
|
|
416
|
+
] });
|
|
417
|
+
}
|
|
418
|
+
const out = lines.filter((l) => l.from === entity.id);
|
|
419
|
+
const into = lines.filter((l) => l.to === entity.id);
|
|
420
|
+
const url = adminURLs[entity.id];
|
|
421
|
+
const item = (line, otherId) => {
|
|
422
|
+
const other = byId.get(otherId);
|
|
423
|
+
const from = byId.get(line.from);
|
|
424
|
+
const to = byId.get(line.to);
|
|
425
|
+
return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs("button", { type: "button", className: "rs-lineage__link", onClick: () => onSelect(other.id), children: [
|
|
426
|
+
/* @__PURE__ */ jsxs("span", { className: "rs-lineage__link-name", children: [
|
|
427
|
+
/* @__PURE__ */ jsx("span", { className: `rs-lineage__swatch rs-lineage__swatch--${other.kind}` }),
|
|
428
|
+
other.label,
|
|
429
|
+
/* @__PURE__ */ jsx("span", { className: "rs-erd__cardinality-tag", children: line.cardinality })
|
|
430
|
+
] }),
|
|
431
|
+
/* @__PURE__ */ jsx("span", { className: "rs-erd__sentence", children: describeCardinality(line.cardinality, from, to) }),
|
|
432
|
+
/* @__PURE__ */ jsx("span", { className: "rs-lineage__via", children: line.relations.map((r) => r.path).join(", ") })
|
|
433
|
+
] }) }, line.id);
|
|
434
|
+
};
|
|
435
|
+
return /* @__PURE__ */ jsxs("aside", { className: "rs-lineage__details", "aria-live": "polite", children: [
|
|
436
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
437
|
+
/* @__PURE__ */ jsxs("span", { className: `rs-lineage__kind rs-lineage__kind--${entity.kind}`, children: [
|
|
438
|
+
entity.kind,
|
|
439
|
+
entity.auth ? " \xB7 auth" : "",
|
|
440
|
+
entity.upload ? " \xB7 upload" : ""
|
|
441
|
+
] }),
|
|
442
|
+
/* @__PURE__ */ jsx("h2", { className: "rs-lineage__name", children: entity.label }),
|
|
443
|
+
/* @__PURE__ */ jsxs("code", { className: "rs-lineage__id", children: [
|
|
444
|
+
entity.slug,
|
|
445
|
+
" \xB7 ",
|
|
446
|
+
entity.fields.length,
|
|
447
|
+
" fields"
|
|
448
|
+
] })
|
|
449
|
+
] }),
|
|
450
|
+
url && /* @__PURE__ */ jsx("a", { className: "rs-lineage__open", href: url, children: "Open in the admin" }),
|
|
451
|
+
/* @__PURE__ */ jsxs("section", { children: [
|
|
452
|
+
/* @__PURE__ */ jsxs("h3", { className: "rs-lineage__heading", children: [
|
|
453
|
+
"References \xB7 ",
|
|
454
|
+
out.length
|
|
455
|
+
] }),
|
|
456
|
+
out.length ? /* @__PURE__ */ jsx("ul", { className: "rs-lineage__list", children: out.map((l) => item(l, l.to)) }) : /* @__PURE__ */ jsx("p", { className: "rs-lineage__hint", children: "None of its fields reference another collection." })
|
|
457
|
+
] }),
|
|
458
|
+
/* @__PURE__ */ jsxs("section", { children: [
|
|
459
|
+
/* @__PURE__ */ jsxs("h3", { className: "rs-lineage__heading", children: [
|
|
460
|
+
"Referenced by \xB7 ",
|
|
461
|
+
into.length
|
|
462
|
+
] }),
|
|
463
|
+
into.length ? /* @__PURE__ */ jsx("ul", { className: "rs-lineage__list", children: into.map((l) => item(l, l.from)) }) : /* @__PURE__ */ jsx("p", { className: "rs-lineage__hint", children: "Nothing references it." })
|
|
464
|
+
] })
|
|
465
|
+
] });
|
|
466
|
+
};
|
|
467
|
+
var TableView = ({
|
|
468
|
+
data: {
|
|
469
|
+
entity,
|
|
470
|
+
rows,
|
|
471
|
+
hidden,
|
|
472
|
+
expanded,
|
|
473
|
+
canExpand,
|
|
474
|
+
selfRefs,
|
|
475
|
+
linked,
|
|
476
|
+
state,
|
|
477
|
+
onToggle,
|
|
478
|
+
onSelect,
|
|
479
|
+
onTrace
|
|
480
|
+
}
|
|
481
|
+
}) => /* @__PURE__ */ jsxs("div", { className: `rs-erd__table rs-lineage__node--${entity.kind} rs-erd__table--${state}`, children: [
|
|
482
|
+
/* @__PURE__ */ jsx(
|
|
483
|
+
Handle,
|
|
484
|
+
{
|
|
485
|
+
id: "table",
|
|
486
|
+
className: "rs-lineage__handle",
|
|
487
|
+
type: "target",
|
|
488
|
+
position: Position.Left,
|
|
489
|
+
isConnectable: false
|
|
490
|
+
}
|
|
491
|
+
),
|
|
492
|
+
/* @__PURE__ */ jsxs(
|
|
493
|
+
"button",
|
|
494
|
+
{
|
|
495
|
+
type: "button",
|
|
496
|
+
className: "rs-erd__header",
|
|
497
|
+
title: entity.id,
|
|
498
|
+
"aria-label": `${entity.label}, ${entity.kind}: show its references`,
|
|
499
|
+
onClick: () => onSelect(entity.id),
|
|
500
|
+
onFocus: () => onTrace(entity.id),
|
|
501
|
+
onBlur: () => onTrace(null),
|
|
502
|
+
children: [
|
|
503
|
+
/* @__PURE__ */ jsx("span", { className: "rs-erd__title", children: entity.label }),
|
|
504
|
+
/* @__PURE__ */ jsx("span", { className: "rs-erd__badge", children: entity.kind === "global" ? "global" : entity.auth ? "auth" : entity.upload ? "upload" : entity.slug })
|
|
505
|
+
]
|
|
506
|
+
}
|
|
507
|
+
),
|
|
508
|
+
/* @__PURE__ */ jsx("ul", { className: "rs-erd__rows", children: rows.map((f) => /* @__PURE__ */ jsxs(
|
|
509
|
+
"li",
|
|
510
|
+
{
|
|
511
|
+
className: `rs-erd__row${linked.has(f.name) ? " rs-erd__row--link" : ""}`,
|
|
512
|
+
title: describe(f),
|
|
513
|
+
children: [
|
|
514
|
+
/* @__PURE__ */ jsxs("span", { className: "rs-erd__field", children: [
|
|
515
|
+
f.name,
|
|
516
|
+
f.required && /* @__PURE__ */ jsx("span", { className: "rs-erd__required", children: "*" }),
|
|
517
|
+
selfRefs.has(f.name) && /* @__PURE__ */ jsx("span", { className: "rs-erd__self", title: "Points at its own collection", children: "\u21BB" })
|
|
518
|
+
] }),
|
|
519
|
+
/* @__PURE__ */ jsxs("span", { className: "rs-erd__type", children: [
|
|
520
|
+
f.type === "blocks" ? `blocks (${f.blocks?.length ?? 0})` : f.type,
|
|
521
|
+
f.hasMany ? "[]" : ""
|
|
522
|
+
] }),
|
|
523
|
+
linked.has(f.name) && /* @__PURE__ */ jsx(
|
|
524
|
+
Handle,
|
|
525
|
+
{
|
|
526
|
+
id: `row:${f.name}`,
|
|
527
|
+
className: "rs-lineage__handle",
|
|
528
|
+
type: "source",
|
|
529
|
+
position: Position.Right,
|
|
530
|
+
isConnectable: false
|
|
531
|
+
}
|
|
532
|
+
)
|
|
533
|
+
]
|
|
534
|
+
},
|
|
535
|
+
f.name
|
|
536
|
+
)) }),
|
|
537
|
+
canExpand && (hidden > 0 || expanded) && /* @__PURE__ */ jsx(
|
|
538
|
+
"button",
|
|
539
|
+
{
|
|
540
|
+
type: "button",
|
|
541
|
+
className: "rs-erd__more nodrag",
|
|
542
|
+
onClick: (event) => {
|
|
543
|
+
event.stopPropagation();
|
|
544
|
+
onToggle();
|
|
545
|
+
},
|
|
546
|
+
children: expanded ? "Show fewer fields" : `+${hidden} more field${hidden === 1 ? "" : "s"}`
|
|
547
|
+
}
|
|
548
|
+
)
|
|
549
|
+
] });
|
|
550
|
+
function describe(f) {
|
|
551
|
+
const parts = [f.type];
|
|
552
|
+
if (f.hasMany) parts.push("has many");
|
|
553
|
+
if (f.required) parts.push("required");
|
|
554
|
+
if (f.localized) parts.push("localized");
|
|
555
|
+
if (f.unique) parts.push("unique");
|
|
556
|
+
if (f.relationTo) parts.push(`\u2192 ${f.relationTo.join(" | ")}`);
|
|
557
|
+
if (f.blocks) parts.push(`blocks: ${f.blocks.join(", ")}`);
|
|
558
|
+
return `${f.name}: ${parts.join(", ")}`;
|
|
559
|
+
}
|
|
560
|
+
var nodeTypes = { table: TableView };
|
|
561
|
+
|
|
562
|
+
// src/view/LineageCanvas.tsx
|
|
563
|
+
import "@xyflow/react/dist/style.css";
|
|
564
|
+
import "./lineage.css";
|
|
565
|
+
import {
|
|
566
|
+
Background as Background2,
|
|
567
|
+
Controls as Controls2,
|
|
568
|
+
Handle as Handle2,
|
|
569
|
+
MiniMap as MiniMap2,
|
|
570
|
+
Panel as Panel2,
|
|
571
|
+
Position as Position2,
|
|
572
|
+
ReactFlow as ReactFlow2,
|
|
573
|
+
ReactFlowProvider as ReactFlowProvider2,
|
|
574
|
+
useReactFlow as useReactFlow2
|
|
575
|
+
} from "@xyflow/react";
|
|
576
|
+
|
|
577
|
+
// ../lineage-core/src/traverse.ts
|
|
578
|
+
function adjacency(links) {
|
|
579
|
+
const up = /* @__PURE__ */ new Map();
|
|
580
|
+
const down = /* @__PURE__ */ new Map();
|
|
581
|
+
const add = (map, key, value) => {
|
|
582
|
+
const list = map.get(key);
|
|
583
|
+
if (list) list.push(value);
|
|
584
|
+
else map.set(key, [value]);
|
|
585
|
+
};
|
|
586
|
+
for (const { source, target } of links) {
|
|
587
|
+
add(up, target, source);
|
|
588
|
+
add(down, source, target);
|
|
589
|
+
}
|
|
590
|
+
return { up, down };
|
|
591
|
+
}
|
|
592
|
+
function walk(start, next, maxDepth = Infinity) {
|
|
593
|
+
const seen = /* @__PURE__ */ new Set();
|
|
594
|
+
let frontier = [start];
|
|
595
|
+
for (let level = 0; level < maxDepth && frontier.length; level++) {
|
|
596
|
+
const reached = [];
|
|
597
|
+
for (const id of frontier) {
|
|
598
|
+
for (const to of next.get(id) ?? []) {
|
|
599
|
+
if (to === start || seen.has(to)) continue;
|
|
600
|
+
seen.add(to);
|
|
601
|
+
reached.push(to);
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
frontier = reached;
|
|
605
|
+
}
|
|
606
|
+
return seen;
|
|
607
|
+
}
|
|
608
|
+
function trace(id, links) {
|
|
609
|
+
const { up, down } = adjacency(links);
|
|
610
|
+
return /* @__PURE__ */ new Set([id, ...walk(id, up), ...walk(id, down)]);
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
// src/view/LineageCanvas.tsx
|
|
614
|
+
import { useEffect as useEffect3, useMemo as useMemo2, useRef as useRef2, useState as useState3 } from "react";
|
|
615
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
616
|
+
var KINDS = [
|
|
617
|
+
{ kind: "collection", label: "Collections" },
|
|
618
|
+
{ kind: "block", label: "Blocks" },
|
|
619
|
+
{ kind: "global", label: "Globals" }
|
|
620
|
+
];
|
|
621
|
+
var LineageCanvas = (props) => /* @__PURE__ */ jsx2(ReactFlowProvider2, { children: /* @__PURE__ */ jsx2(Canvas2, { ...props }) });
|
|
622
|
+
var Canvas2 = ({ graph, adminURLs }) => {
|
|
623
|
+
const { fitView } = useReactFlow2();
|
|
624
|
+
const layoutRef = useRef2(null);
|
|
625
|
+
const [hiddenKinds, setHiddenKinds] = useState3(/* @__PURE__ */ new Set());
|
|
626
|
+
const [hideUnconnected, setHideUnconnected] = useState3(true);
|
|
627
|
+
const [hovered, setHovered] = useState3(null);
|
|
628
|
+
const [selected, setSelected] = useState3(null);
|
|
629
|
+
const byId = useMemo2(() => new Map(graph.nodes.map((n) => [n.id, n])), [graph]);
|
|
630
|
+
const visible = useMemo2(
|
|
631
|
+
() => visibleGraph(graph, { hiddenKinds, hideUnconnected }),
|
|
632
|
+
[graph, hiddenKinds, hideUnconnected]
|
|
633
|
+
);
|
|
634
|
+
const positions = useLayout(
|
|
635
|
+
useMemo2(
|
|
636
|
+
() => visible.nodes.map((n) => ({ id: n.id, width: NODE_WIDTH, height: NODE_HEIGHT })),
|
|
637
|
+
[visible.nodes]
|
|
638
|
+
),
|
|
639
|
+
useMemo2(() => layoutEdges(visible.edges), [visible.edges]),
|
|
640
|
+
LINEAGE_LAYOUT,
|
|
641
|
+
// Leave room on the right for the details panel when it's beside the graph
|
|
642
|
+
() => requestAnimationFrame(
|
|
643
|
+
() => fitView({ padding: fitPadding(layoutRef.current?.clientWidth ?? 0) })
|
|
644
|
+
)
|
|
645
|
+
);
|
|
646
|
+
const traced = useMemo2(
|
|
647
|
+
() => hovered ? trace(hovered, visible.edges) : null,
|
|
648
|
+
[hovered, visible.edges]
|
|
649
|
+
);
|
|
650
|
+
const nodes = useMemo2(() => {
|
|
651
|
+
if (!positions) return [];
|
|
652
|
+
return visible.nodes.flatMap((node) => {
|
|
653
|
+
const position = positions.get(node.id);
|
|
654
|
+
if (!position) return [];
|
|
655
|
+
let state = "default";
|
|
656
|
+
if (traced)
|
|
657
|
+
state = node.id === hovered ? "active" : traced.has(node.id) ? "related" : "dimmed";
|
|
658
|
+
else if (node.id === selected) state = "selected";
|
|
659
|
+
return [
|
|
660
|
+
{
|
|
661
|
+
id: node.id,
|
|
662
|
+
type: "content",
|
|
663
|
+
position,
|
|
664
|
+
width: NODE_WIDTH,
|
|
665
|
+
height: NODE_HEIGHT,
|
|
666
|
+
data: {
|
|
667
|
+
node,
|
|
668
|
+
state,
|
|
669
|
+
selfLink: linksToItself(node.id, visible.edges),
|
|
670
|
+
onSelect: setSelected,
|
|
671
|
+
onTrace: setHovered
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
];
|
|
675
|
+
});
|
|
676
|
+
}, [positions, visible, traced, hovered, selected]);
|
|
677
|
+
const edges = useMemo2(
|
|
678
|
+
() => visible.edges.filter((e) => e.source !== e.target).map((e) => {
|
|
679
|
+
const onTrace = traced?.has(e.source) && traced.has(e.target);
|
|
680
|
+
const state = traced ? onTrace ? " rs-lineage__edge--active" : " rs-lineage__edge--dimmed" : "";
|
|
681
|
+
return {
|
|
682
|
+
id: `${e.source}->${e.target}:${e.kind}`,
|
|
683
|
+
source: e.source,
|
|
684
|
+
target: e.target,
|
|
685
|
+
className: `rs-lineage__edge rs-lineage__edge--${e.kind}${state}`,
|
|
686
|
+
animated: !!onTrace
|
|
687
|
+
};
|
|
688
|
+
}),
|
|
689
|
+
[visible.edges, traced]
|
|
690
|
+
);
|
|
691
|
+
useEffect3(() => {
|
|
692
|
+
const onKey = (event) => event.key === "Escape" && setSelected(null);
|
|
693
|
+
window.addEventListener("keydown", onKey);
|
|
694
|
+
return () => window.removeEventListener("keydown", onKey);
|
|
695
|
+
}, []);
|
|
696
|
+
const toggleKind = (kind) => setHiddenKinds((current) => {
|
|
697
|
+
const next = new Set(current);
|
|
698
|
+
if (next.has(kind)) next.delete(kind);
|
|
699
|
+
else next.add(kind);
|
|
700
|
+
return next;
|
|
701
|
+
});
|
|
702
|
+
const focused = selected ? byId.get(selected) : void 0;
|
|
703
|
+
const links = visible.edges.length;
|
|
704
|
+
return (
|
|
705
|
+
// `data-ready` once the first layout is done, for tests to wait on
|
|
706
|
+
/* @__PURE__ */ jsxs2("div", { className: "rs-lineage-layout", ref: layoutRef, children: [
|
|
707
|
+
/* @__PURE__ */ jsx2("div", { className: "rs-lineage", "data-ready": positions ? "" : void 0, children: /* @__PURE__ */ jsxs2(
|
|
708
|
+
ReactFlow2,
|
|
709
|
+
{
|
|
710
|
+
nodes,
|
|
711
|
+
edges,
|
|
712
|
+
nodeTypes: nodeTypes2,
|
|
713
|
+
nodesDraggable: false,
|
|
714
|
+
nodesFocusable: false,
|
|
715
|
+
edgesFocusable: false,
|
|
716
|
+
nodesConnectable: false,
|
|
717
|
+
elementsSelectable: false,
|
|
718
|
+
minZoom: 0.1,
|
|
719
|
+
onNodeMouseEnter: (_, node) => setHovered(node.id),
|
|
720
|
+
onNodeMouseLeave: () => setHovered(null),
|
|
721
|
+
onNodeClick: (_, node) => setSelected(node.id),
|
|
722
|
+
onPaneClick: () => setSelected(null),
|
|
723
|
+
proOptions: { hideAttribution: true },
|
|
724
|
+
children: [
|
|
725
|
+
/* @__PURE__ */ jsx2(Background2, { gap: 24 }),
|
|
726
|
+
/* @__PURE__ */ jsx2(Controls2, { showInteractive: false }),
|
|
727
|
+
/* @__PURE__ */ jsx2(
|
|
728
|
+
MiniMap2,
|
|
729
|
+
{
|
|
730
|
+
pannable: true,
|
|
731
|
+
zoomable: true,
|
|
732
|
+
className: "rs-lineage__minimap",
|
|
733
|
+
nodeClassName: (node) => `rs-lineage__minimap-node--${node.data.node.kind}`,
|
|
734
|
+
ariaLabel: "Content lineage overview"
|
|
735
|
+
}
|
|
736
|
+
),
|
|
737
|
+
/* @__PURE__ */ jsx2(Panel2, { position: "top-left", children: /* @__PURE__ */ jsxs2("div", { className: "rs-lineage__toolbar", children: [
|
|
738
|
+
KINDS.map(({ kind, label }) => /* @__PURE__ */ jsxs2("label", { className: "rs-lineage__toggle", children: [
|
|
739
|
+
/* @__PURE__ */ jsx2(
|
|
740
|
+
"input",
|
|
741
|
+
{
|
|
742
|
+
type: "checkbox",
|
|
743
|
+
checked: !hiddenKinds.has(kind),
|
|
744
|
+
onChange: () => toggleKind(kind)
|
|
745
|
+
}
|
|
746
|
+
),
|
|
747
|
+
/* @__PURE__ */ jsx2("span", { className: `rs-lineage__swatch rs-lineage__swatch--${kind}` }),
|
|
748
|
+
label
|
|
749
|
+
] }, kind)),
|
|
750
|
+
/* @__PURE__ */ jsxs2("label", { className: "rs-lineage__toggle", children: [
|
|
751
|
+
/* @__PURE__ */ jsx2(
|
|
752
|
+
"input",
|
|
753
|
+
{
|
|
754
|
+
type: "checkbox",
|
|
755
|
+
checked: hideUnconnected,
|
|
756
|
+
onChange: () => setHideUnconnected((current) => !current)
|
|
757
|
+
}
|
|
758
|
+
),
|
|
759
|
+
"Hide unconnected"
|
|
760
|
+
] }),
|
|
761
|
+
/* @__PURE__ */ jsxs2("span", { className: "rs-lineage__count", children: [
|
|
762
|
+
visible.nodes.length,
|
|
763
|
+
" nodes, ",
|
|
764
|
+
links,
|
|
765
|
+
" link",
|
|
766
|
+
links === 1 ? "" : "s"
|
|
767
|
+
] })
|
|
768
|
+
] }) })
|
|
769
|
+
]
|
|
770
|
+
}
|
|
771
|
+
) }),
|
|
772
|
+
/* @__PURE__ */ jsx2(
|
|
773
|
+
Details2,
|
|
774
|
+
{
|
|
775
|
+
node: focused,
|
|
776
|
+
edges: visible.edges,
|
|
777
|
+
byId,
|
|
778
|
+
adminURLs,
|
|
779
|
+
onSelect: setSelected
|
|
780
|
+
}
|
|
781
|
+
)
|
|
782
|
+
] })
|
|
783
|
+
);
|
|
784
|
+
};
|
|
785
|
+
var Details2 = ({ node, edges, byId, adminURLs, onSelect }) => {
|
|
786
|
+
if (!node) {
|
|
787
|
+
return /* @__PURE__ */ jsxs2("aside", { className: "rs-lineage__details", children: [
|
|
788
|
+
/* @__PURE__ */ jsx2("p", { className: "rs-lineage__hint", children: "Hover a node to trace everything it's built from and everything that uses it. Click it to see the fields behind each link. Esc clears the selection." }),
|
|
789
|
+
/* @__PURE__ */ jsxs2("dl", { className: "rs-lineage__legend", children: [
|
|
790
|
+
/* @__PURE__ */ jsxs2("div", { children: [
|
|
791
|
+
/* @__PURE__ */ jsx2("dt", { className: "rs-lineage__line rs-lineage__line--relationship" }),
|
|
792
|
+
/* @__PURE__ */ jsx2("dd", { children: "relationship" })
|
|
793
|
+
] }),
|
|
794
|
+
/* @__PURE__ */ jsxs2("div", { children: [
|
|
795
|
+
/* @__PURE__ */ jsx2("dt", { className: "rs-lineage__line rs-lineage__line--upload" }),
|
|
796
|
+
/* @__PURE__ */ jsx2("dd", { children: "upload" })
|
|
797
|
+
] }),
|
|
798
|
+
/* @__PURE__ */ jsxs2("div", { children: [
|
|
799
|
+
/* @__PURE__ */ jsx2("dt", { className: "rs-lineage__line rs-lineage__line--blocks" }),
|
|
800
|
+
/* @__PURE__ */ jsx2("dd", { children: "blocks field" })
|
|
801
|
+
] })
|
|
802
|
+
] })
|
|
803
|
+
] });
|
|
804
|
+
}
|
|
805
|
+
const builtFrom = edges.filter((e) => e.target === node.id);
|
|
806
|
+
const usedBy = edges.filter((e) => e.source === node.id);
|
|
807
|
+
const url = adminURLs[node.id];
|
|
808
|
+
const item = (edge, otherId) => {
|
|
809
|
+
const other = byId.get(otherId);
|
|
810
|
+
return /* @__PURE__ */ jsx2("li", { children: /* @__PURE__ */ jsxs2("button", { type: "button", className: "rs-lineage__link", onClick: () => onSelect(other.id), children: [
|
|
811
|
+
/* @__PURE__ */ jsxs2("span", { className: "rs-lineage__link-name", children: [
|
|
812
|
+
/* @__PURE__ */ jsx2("span", { className: `rs-lineage__swatch rs-lineage__swatch--${other.kind}` }),
|
|
813
|
+
edge.source === edge.target ? "Itself" : other.label
|
|
814
|
+
] }),
|
|
815
|
+
/* @__PURE__ */ jsxs2("span", { className: "rs-lineage__via", children: [
|
|
816
|
+
edge.kind,
|
|
817
|
+
" \xB7 ",
|
|
818
|
+
edge.fields.join(", ")
|
|
819
|
+
] })
|
|
820
|
+
] }) }, `${edge.source}->${edge.target}:${edge.kind}`);
|
|
821
|
+
};
|
|
822
|
+
return /* @__PURE__ */ jsxs2("aside", { className: "rs-lineage__details", "aria-live": "polite", children: [
|
|
823
|
+
/* @__PURE__ */ jsxs2("div", { children: [
|
|
824
|
+
/* @__PURE__ */ jsx2("span", { className: `rs-lineage__kind rs-lineage__kind--${node.kind}`, children: node.kind }),
|
|
825
|
+
/* @__PURE__ */ jsx2("h2", { className: "rs-lineage__name", children: node.label }),
|
|
826
|
+
/* @__PURE__ */ jsx2("code", { className: "rs-lineage__id", children: node.id })
|
|
827
|
+
] }),
|
|
828
|
+
url && /* @__PURE__ */ jsx2("a", { className: "rs-lineage__open", href: url, children: "Open in the admin" }),
|
|
829
|
+
/* @__PURE__ */ jsxs2("section", { children: [
|
|
830
|
+
/* @__PURE__ */ jsxs2("h3", { className: "rs-lineage__heading", children: [
|
|
831
|
+
"Built from \xB7 ",
|
|
832
|
+
builtFrom.length
|
|
833
|
+
] }),
|
|
834
|
+
builtFrom.length ? /* @__PURE__ */ jsx2("ul", { className: "rs-lineage__list", children: builtFrom.map((e) => item(e, e.source)) }) : /* @__PURE__ */ jsx2("p", { className: "rs-lineage__hint", children: "None of its fields point at another collection or block." })
|
|
835
|
+
] }),
|
|
836
|
+
/* @__PURE__ */ jsxs2("section", { children: [
|
|
837
|
+
/* @__PURE__ */ jsxs2("h3", { className: "rs-lineage__heading", children: [
|
|
838
|
+
"Used by \xB7 ",
|
|
839
|
+
usedBy.length
|
|
840
|
+
] }),
|
|
841
|
+
usedBy.length ? /* @__PURE__ */ jsx2("ul", { className: "rs-lineage__list", children: usedBy.map((e) => item(e, e.target)) }) : /* @__PURE__ */ jsx2("p", { className: "rs-lineage__hint", children: "Nothing uses it." })
|
|
842
|
+
] })
|
|
843
|
+
] });
|
|
844
|
+
};
|
|
845
|
+
var ContentNodeView = ({
|
|
846
|
+
data: { node, state, selfLink, onSelect, onTrace }
|
|
847
|
+
}) => /* @__PURE__ */ jsxs2(
|
|
848
|
+
"div",
|
|
849
|
+
{
|
|
850
|
+
className: `rs-lineage__node rs-lineage__node--${node.kind} rs-lineage__node--${state}`,
|
|
851
|
+
title: node.id,
|
|
852
|
+
children: [
|
|
853
|
+
/* @__PURE__ */ jsx2(
|
|
854
|
+
Handle2,
|
|
855
|
+
{
|
|
856
|
+
className: "rs-lineage__handle",
|
|
857
|
+
type: "target",
|
|
858
|
+
position: Position2.Left,
|
|
859
|
+
isConnectable: false
|
|
860
|
+
}
|
|
861
|
+
),
|
|
862
|
+
/* @__PURE__ */ jsxs2(
|
|
863
|
+
"button",
|
|
864
|
+
{
|
|
865
|
+
type: "button",
|
|
866
|
+
className: "rs-lineage__select",
|
|
867
|
+
"aria-label": `${node.label}, ${node.kind}: show what it's built from and what uses it`,
|
|
868
|
+
onClick: () => onSelect(node.id),
|
|
869
|
+
onFocus: () => onTrace(node.id),
|
|
870
|
+
onBlur: () => onTrace(null),
|
|
871
|
+
children: [
|
|
872
|
+
/* @__PURE__ */ jsxs2("span", { className: "rs-lineage__label", children: [
|
|
873
|
+
node.label,
|
|
874
|
+
selfLink && /* @__PURE__ */ jsx2("span", { className: "rs-lineage__self", title: "Links to itself", children: "\u21BB" })
|
|
875
|
+
] }),
|
|
876
|
+
/* @__PURE__ */ jsxs2("span", { className: "rs-lineage__meta", children: [
|
|
877
|
+
node.kind,
|
|
878
|
+
" \xB7 ",
|
|
879
|
+
node.slug
|
|
880
|
+
] })
|
|
881
|
+
]
|
|
882
|
+
}
|
|
883
|
+
),
|
|
884
|
+
/* @__PURE__ */ jsx2(
|
|
885
|
+
Handle2,
|
|
886
|
+
{
|
|
887
|
+
className: "rs-lineage__handle",
|
|
888
|
+
type: "source",
|
|
889
|
+
position: Position2.Right,
|
|
890
|
+
isConnectable: false
|
|
891
|
+
}
|
|
892
|
+
)
|
|
893
|
+
]
|
|
894
|
+
}
|
|
895
|
+
);
|
|
896
|
+
var nodeTypes2 = { content: ContentNodeView };
|
|
897
|
+
|
|
898
|
+
// src/view/NavLink.tsx
|
|
899
|
+
import { Link, useConfig } from "@payloadcms/ui";
|
|
900
|
+
import { usePathname } from "next/navigation";
|
|
901
|
+
import { formatAdminURL } from "payload/shared";
|
|
902
|
+
import { Fragment, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
903
|
+
var LineageNavLinkClient = () => {
|
|
904
|
+
const { config } = useConfig();
|
|
905
|
+
const pathname = usePathname();
|
|
906
|
+
const href = formatAdminURL({ adminRoute: config.routes.admin, path: "/lineage" });
|
|
907
|
+
const active = pathname === href || pathname.startsWith(`${href}/`);
|
|
908
|
+
const label = /* @__PURE__ */ jsxs3(Fragment, { children: [
|
|
909
|
+
active && /* @__PURE__ */ jsx3("div", { className: "nav__link-indicator" }),
|
|
910
|
+
/* @__PURE__ */ jsx3("span", { className: "nav__link-label", children: "Lineage" })
|
|
911
|
+
] });
|
|
912
|
+
return pathname === href ? /* @__PURE__ */ jsx3("div", { className: "nav__link", id: "nav-lineage", children: label }) : /* @__PURE__ */ jsx3(Link, { className: "nav__link", href, id: "nav-lineage", prefetch: false, children: label });
|
|
913
|
+
};
|
|
914
|
+
export {
|
|
915
|
+
ErdCanvas,
|
|
916
|
+
LineageCanvas,
|
|
917
|
+
LineageNavLinkClient
|
|
918
|
+
};
|