@dxos/plugin-explorer 0.7.5-main.b19bfc8 → 0.7.5-main.d9d2d4e
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/browser/{ExplorerContainer-THLT3CQZ.mjs → ExplorerContainer-T5CTMBIS.mjs} +2 -2
- package/dist/lib/browser/{chunk-YN4YVXVL.mjs → chunk-SU3K2HL7.mjs} +241 -2
- package/dist/lib/{node-esm/chunk-JYW3CDBT.mjs.map → browser/chunk-SU3K2HL7.mjs.map} +4 -4
- package/dist/lib/browser/chunk-V23FAKIX.mjs +205 -0
- package/dist/lib/browser/chunk-V23FAKIX.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +7 -7
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/{react-surface-UMUN6CTH.mjs → react-surface-Q7OT6GXC.mjs} +3 -3
- package/dist/lib/node/{ExplorerContainer-EJPGD4WR.cjs → ExplorerContainer-SO5XAXFS.cjs} +6 -6
- package/dist/lib/node/chunk-6GTOKVKH.cjs +236 -0
- package/dist/lib/node/chunk-6GTOKVKH.cjs.map +7 -0
- package/dist/lib/node/{chunk-H5CHI722.cjs → chunk-CQYBCGC4.cjs} +234 -8
- package/dist/lib/node/chunk-CQYBCGC4.cjs.map +7 -0
- package/dist/lib/node/index.cjs +10 -10
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node/{react-surface-UVMGNAA6.cjs → react-surface-5G52HSJW.cjs} +7 -7
- package/dist/lib/node-esm/{ExplorerContainer-MEZP34AY.mjs → ExplorerContainer-DTUTEZLK.mjs} +2 -2
- package/dist/lib/node-esm/{chunk-JYW3CDBT.mjs → chunk-4IST6Y3Z.mjs} +240 -2
- package/dist/lib/{browser/chunk-YN4YVXVL.mjs.map → node-esm/chunk-4IST6Y3Z.mjs.map} +4 -4
- package/dist/lib/node-esm/chunk-Q2IQDIKJ.mjs +207 -0
- package/dist/lib/node-esm/chunk-Q2IQDIKJ.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +7 -7
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/lib/node-esm/{react-surface-5BTZOU3H.mjs → react-surface-QSQVINGP.mjs} +3 -3
- package/dist/types/src/components/Graph/Graph.d.ts +0 -2
- package/dist/types/src/components/Graph/Graph.d.ts.map +1 -1
- package/dist/types/src/components/Graph/Graph.stories.d.ts +1 -1
- package/dist/types/src/components/Graph/Graph.stories.d.ts.map +1 -1
- package/dist/types/src/components/Graph/graph-model.d.ts +11 -5
- package/dist/types/src/components/Graph/graph-model.d.ts.map +1 -1
- package/dist/types/src/components/Tree/Tree.d.ts.map +1 -1
- package/dist/types/src/components/Tree/types.d.ts +1 -1
- package/dist/types/src/components/Tree/types.d.ts.map +1 -1
- package/package.json +25 -24
- package/src/components/Graph/Graph.stories.tsx +2 -2
- package/src/components/Graph/Graph.tsx +65 -125
- package/src/components/Graph/graph-model.ts +123 -71
- package/src/components/Tree/Tree.tsx +7 -2
- package/src/components/Tree/types.ts +2 -1
- package/dist/lib/browser/chunk-TDUEJNAN.mjs +0 -474
- package/dist/lib/browser/chunk-TDUEJNAN.mjs.map +0 -7
- package/dist/lib/node/chunk-H5CHI722.cjs.map +0 -7
- package/dist/lib/node/chunk-QASZ24JS.cjs +0 -495
- package/dist/lib/node/chunk-QASZ24JS.cjs.map +0 -7
- package/dist/lib/node-esm/chunk-45DTVHHL.mjs +0 -476
- package/dist/lib/node-esm/chunk-45DTVHHL.mjs.map +0 -7
- /package/dist/lib/browser/{ExplorerContainer-THLT3CQZ.mjs.map → ExplorerContainer-T5CTMBIS.mjs.map} +0 -0
- /package/dist/lib/browser/{react-surface-UMUN6CTH.mjs.map → react-surface-Q7OT6GXC.mjs.map} +0 -0
- /package/dist/lib/node/{ExplorerContainer-EJPGD4WR.cjs.map → ExplorerContainer-SO5XAXFS.cjs.map} +0 -0
- /package/dist/lib/node/{react-surface-UVMGNAA6.cjs.map → react-surface-5G52HSJW.cjs.map} +0 -0
- /package/dist/lib/node-esm/{ExplorerContainer-MEZP34AY.mjs.map → ExplorerContainer-DTUTEZLK.mjs.map} +0 -0
- /package/dist/lib/node-esm/{react-surface-5BTZOU3H.mjs.map → react-surface-QSQVINGP.mjs.map} +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dxos/plugin-explorer",
|
|
3
|
-
"version": "0.7.5-main.
|
|
3
|
+
"version": "0.7.5-main.d9d2d4e",
|
|
4
4
|
"description": "Braneframe data visualization plugin",
|
|
5
5
|
"homepage": "https://dxos.org",
|
|
6
6
|
"bugs": "https://github.com/dxos/dxos/issues",
|
|
@@ -37,25 +37,26 @@
|
|
|
37
37
|
"@observablehq/plot": "^0.6.11",
|
|
38
38
|
"@preact/signals-core": "^1.6.0",
|
|
39
39
|
"d3": "^7.9.0",
|
|
40
|
+
"force-graph": "1.49.3",
|
|
40
41
|
"lodash.defaultsdeep": "^4.6.1",
|
|
41
42
|
"lodash.get": "^4.4.2",
|
|
42
43
|
"react-resize-detector": "^11.0.1",
|
|
43
44
|
"topojson-client": "^3.1.0",
|
|
44
|
-
"@dxos/app-framework": "0.7.5-main.
|
|
45
|
-
"@dxos/
|
|
46
|
-
"@dxos/
|
|
47
|
-
"@dxos/
|
|
48
|
-
"@dxos/
|
|
49
|
-
"@dxos/gem-
|
|
50
|
-
"@dxos/live-object": "0.7.5-main.
|
|
51
|
-
"@dxos/log": "0.7.5-main.
|
|
52
|
-
"@dxos/plugin-
|
|
53
|
-
"@dxos/plugin-search": "0.7.5-main.
|
|
54
|
-
"@dxos/plugin-
|
|
55
|
-
"@dxos/
|
|
56
|
-
"@dxos/
|
|
57
|
-
"@dxos/
|
|
58
|
-
"@dxos/
|
|
45
|
+
"@dxos/app-framework": "0.7.5-main.d9d2d4e",
|
|
46
|
+
"@dxos/client": "0.7.5-main.d9d2d4e",
|
|
47
|
+
"@dxos/async": "0.7.5-main.d9d2d4e",
|
|
48
|
+
"@dxos/echo-schema": "0.7.5-main.d9d2d4e",
|
|
49
|
+
"@dxos/gem-spore": "0.7.5-main.d9d2d4e",
|
|
50
|
+
"@dxos/gem-core": "0.7.5-main.d9d2d4e",
|
|
51
|
+
"@dxos/live-object": "0.7.5-main.d9d2d4e",
|
|
52
|
+
"@dxos/log": "0.7.5-main.d9d2d4e",
|
|
53
|
+
"@dxos/plugin-graph": "0.7.5-main.d9d2d4e",
|
|
54
|
+
"@dxos/plugin-search": "0.7.5-main.d9d2d4e",
|
|
55
|
+
"@dxos/plugin-client": "0.7.5-main.d9d2d4e",
|
|
56
|
+
"@dxos/plugin-space": "0.7.5-main.d9d2d4e",
|
|
57
|
+
"@dxos/react-client": "0.7.5-main.d9d2d4e",
|
|
58
|
+
"@dxos/util": "0.7.5-main.d9d2d4e",
|
|
59
|
+
"@dxos/react-ui-stack": "0.7.5-main.d9d2d4e"
|
|
59
60
|
},
|
|
60
61
|
"devDependencies": {
|
|
61
62
|
"@phosphor-icons/react": "^2.1.5",
|
|
@@ -70,19 +71,19 @@
|
|
|
70
71
|
"react": "~18.2.0",
|
|
71
72
|
"react-dom": "~18.2.0",
|
|
72
73
|
"vite": "5.4.7",
|
|
73
|
-
"@dxos/echo-generator": "0.7.5-main.
|
|
74
|
-
"@dxos/plugin-outliner": "0.7.5-main.
|
|
75
|
-
"@dxos/random": "0.7.5-main.
|
|
76
|
-
"@dxos/react-ui": "0.7.5-main.
|
|
77
|
-
"@dxos/
|
|
78
|
-
"@dxos/
|
|
74
|
+
"@dxos/echo-generator": "0.7.5-main.d9d2d4e",
|
|
75
|
+
"@dxos/plugin-outliner": "0.7.5-main.d9d2d4e",
|
|
76
|
+
"@dxos/random": "0.7.5-main.d9d2d4e",
|
|
77
|
+
"@dxos/react-ui": "0.7.5-main.d9d2d4e",
|
|
78
|
+
"@dxos/storybook-utils": "0.7.5-main.d9d2d4e",
|
|
79
|
+
"@dxos/react-ui-theme": "0.7.5-main.d9d2d4e"
|
|
79
80
|
},
|
|
80
81
|
"peerDependencies": {
|
|
81
82
|
"@phosphor-icons/react": "^2.1.5",
|
|
82
83
|
"react": "~18.2.0",
|
|
83
84
|
"react-dom": "~18.2.0",
|
|
84
|
-
"@dxos/react-ui": "0.7.5-main.
|
|
85
|
-
"@dxos/react-ui-theme": "0.7.5-main.
|
|
85
|
+
"@dxos/react-ui": "0.7.5-main.d9d2d4e",
|
|
86
|
+
"@dxos/react-ui-theme": "0.7.5-main.d9d2d4e"
|
|
86
87
|
},
|
|
87
88
|
"publishConfig": {
|
|
88
89
|
"access": "public"
|
|
@@ -47,8 +47,6 @@ const Story = () => {
|
|
|
47
47
|
return <Graph space={space} />;
|
|
48
48
|
};
|
|
49
49
|
|
|
50
|
-
export const Default = {};
|
|
51
|
-
|
|
52
50
|
const meta: Meta = {
|
|
53
51
|
title: 'plugins/plugin-explorer/Graph',
|
|
54
52
|
component: Graph,
|
|
@@ -60,3 +58,5 @@ const meta: Meta = {
|
|
|
60
58
|
};
|
|
61
59
|
|
|
62
60
|
export default meta;
|
|
61
|
+
|
|
62
|
+
export const Default = {};
|
|
@@ -2,148 +2,88 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import
|
|
5
|
+
import { forceLink, forceManyBody } from 'd3';
|
|
6
|
+
import ForceGraph from 'force-graph';
|
|
7
|
+
import React, { type FC, useEffect, useRef } from 'react';
|
|
8
|
+
import { useResizeDetector } from 'react-resize-detector';
|
|
6
9
|
|
|
7
|
-
import {
|
|
8
|
-
import { createSvgContext, defaultGridStyles, Grid, SVG, SVGRoot, Zoom } from '@dxos/gem-core';
|
|
9
|
-
import {
|
|
10
|
-
defaultStyles,
|
|
11
|
-
Graph as GraphComponent,
|
|
12
|
-
GraphForceProjector,
|
|
13
|
-
type GraphLayoutNode,
|
|
14
|
-
Markers,
|
|
15
|
-
} from '@dxos/gem-spore';
|
|
10
|
+
import { type Space } from '@dxos/client/echo';
|
|
16
11
|
import { filterObjectsSync, type SearchResult } from '@dxos/plugin-search';
|
|
17
|
-
import {
|
|
18
|
-
import { mx } from '@dxos/react-ui-theme';
|
|
12
|
+
import { useAsyncState } from '@dxos/react-ui';
|
|
19
13
|
|
|
20
|
-
import '
|
|
21
|
-
|
|
22
|
-
import { type EchoGraphNode, SpaceGraphModel } from './graph-model';
|
|
23
|
-
import { Tree } from '../Tree';
|
|
24
|
-
|
|
25
|
-
type Slots = {
|
|
26
|
-
root?: { className?: string };
|
|
27
|
-
grid?: { className?: string };
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
const slots: Slots = {};
|
|
31
|
-
|
|
32
|
-
const colors = [
|
|
33
|
-
'[&>circle]:!fill-black-300 [&>circle]:!stroke-black-600',
|
|
34
|
-
'[&>circle]:!fill-slate-300 [&>circle]:!stroke-slate-600',
|
|
35
|
-
'[&>circle]:!fill-green-300 [&>circle]:!stroke-green-600',
|
|
36
|
-
'[&>circle]:!fill-sky-300 [&>circle]:!stroke-sky-600',
|
|
37
|
-
'[&>circle]:!fill-cyan-300 [&>circle]:!stroke-cyan-600',
|
|
38
|
-
'[&>circle]:!fill-rose-300 [&>circle]:!stroke-rose-600',
|
|
39
|
-
'[&>circle]:!fill-purple-300 [&>circle]:!stroke-purple-600',
|
|
40
|
-
'[&>circle]:!fill-orange-300 [&>circle]:!stroke-orange-600',
|
|
41
|
-
'[&>circle]:!fill-teal-300 [&>circle]:!stroke-teal-600',
|
|
42
|
-
'[&>circle]:!fill-indigo-300 [&>circle]:!stroke-indigo-600',
|
|
43
|
-
];
|
|
14
|
+
import { SpaceGraphModel } from './graph-model';
|
|
44
15
|
|
|
45
16
|
export type GraphProps = {
|
|
46
17
|
space: Space;
|
|
47
18
|
match?: RegExp;
|
|
48
|
-
grid?: boolean;
|
|
49
19
|
};
|
|
50
20
|
|
|
51
|
-
export const Graph: FC<GraphProps> = ({ space, match
|
|
52
|
-
const
|
|
53
|
-
const
|
|
54
|
-
const
|
|
21
|
+
export const Graph: FC<GraphProps> = ({ space, match }) => {
|
|
22
|
+
const { ref, width, height } = useResizeDetector({ refreshRate: 200 });
|
|
23
|
+
const rootRef = useRef<HTMLDivElement>(null);
|
|
24
|
+
const forceGraph = useRef<ForceGraph>();
|
|
55
25
|
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
new GraphForceProjector<EchoGraphNode>(context, {
|
|
60
|
-
forces: {
|
|
61
|
-
manyBody: {
|
|
62
|
-
strength: -100,
|
|
63
|
-
},
|
|
64
|
-
link: {
|
|
65
|
-
distance: 120,
|
|
66
|
-
},
|
|
67
|
-
radial: {
|
|
68
|
-
radius: 150,
|
|
69
|
-
strength: 0.05,
|
|
70
|
-
},
|
|
71
|
-
},
|
|
72
|
-
attributes: {
|
|
73
|
-
radius: (node: GraphLayoutNode<EchoGraphNode>) => (node.data?.type === 'schema' ? 12 : 8),
|
|
74
|
-
},
|
|
75
|
-
}),
|
|
76
|
-
[],
|
|
26
|
+
const [model] = useAsyncState(
|
|
27
|
+
async () => (space ? new SpaceGraphModel({ schema: true }).open(space) : undefined),
|
|
28
|
+
[space],
|
|
77
29
|
);
|
|
78
30
|
|
|
79
31
|
const filteredRef = useRef<SearchResult[]>();
|
|
80
32
|
filteredRef.current = filterObjectsSync(model?.objects ?? [], match);
|
|
81
|
-
useEffect(() => {
|
|
82
|
-
void projector.start();
|
|
83
|
-
}, [match]);
|
|
84
33
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
if (rootRef.current) {
|
|
36
|
+
// https://github.com/vasturiano/force-graph
|
|
37
|
+
forceGraph.current = new ForceGraph(rootRef.current)
|
|
38
|
+
.nodeRelSize(6)
|
|
39
|
+
.nodeLabel((node: any) => {
|
|
40
|
+
if (node.type === 'schema') {
|
|
41
|
+
return node.data.typename;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return node.id;
|
|
45
|
+
})
|
|
46
|
+
.nodeAutoColorBy((node: any) => (node.type === 'schema' ? 'schema' : node.data.typename))
|
|
47
|
+
.linkColor(() => 'rgba(255,255,255,0.25)');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return () => {
|
|
51
|
+
forceGraph.current?.pauseAnimation().graphData({ nodes: [], links: [] });
|
|
52
|
+
forceGraph.current = undefined;
|
|
53
|
+
};
|
|
54
|
+
}, []);
|
|
90
55
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
56
|
+
useEffect(() => {
|
|
57
|
+
if (forceGraph.current && width && height && model) {
|
|
58
|
+
forceGraph.current
|
|
59
|
+
.pauseAnimation()
|
|
60
|
+
.width(width)
|
|
61
|
+
.height(height)
|
|
62
|
+
.onEngineStop(() => {
|
|
63
|
+
handleZoomToFit();
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
// https://github.com/vasturiano/force-graph?tab=readme-ov-file#force-engine-d3-force-configuration
|
|
67
|
+
// .d3Force('center', forceCenter().strength(0.9))
|
|
68
|
+
.d3Force('link', forceLink().distance(160).strength(0.5))
|
|
69
|
+
.d3Force('charge', forceManyBody().strength(-30))
|
|
70
|
+
// .d3AlphaDecay(0.0228)
|
|
71
|
+
// .d3VelocityDecay(0.4)
|
|
72
|
+
|
|
73
|
+
.graphData(model.graph)
|
|
74
|
+
.warmupTicks(100)
|
|
75
|
+
.cooldownTime(1000)
|
|
76
|
+
.resumeAnimation();
|
|
77
|
+
}
|
|
78
|
+
}, [model, width, height]);
|
|
79
|
+
|
|
80
|
+
const handleZoomToFit = () => {
|
|
81
|
+
forceGraph.current?.zoomToFit(400, 40);
|
|
82
|
+
};
|
|
94
83
|
|
|
95
84
|
return (
|
|
96
|
-
<
|
|
97
|
-
<
|
|
98
|
-
|
|
99
|
-
{grid && <Grid className={slots?.grid?.className ?? defaultGridStyles(themeMode)} />}
|
|
100
|
-
<Zoom extent={[1 / 2, 4]}>
|
|
101
|
-
<GraphComponent
|
|
102
|
-
model={model}
|
|
103
|
-
projector={projector}
|
|
104
|
-
drag
|
|
105
|
-
arrows
|
|
106
|
-
onSelect={(node) => setSelected(node?.data?.id)}
|
|
107
|
-
labels={{
|
|
108
|
-
text: (node: GraphLayoutNode<ReactiveEchoObject<any>>) => {
|
|
109
|
-
if (filteredRef.current?.length && !filteredRef.current.some((object) => object.id === node.data?.id)) {
|
|
110
|
-
return undefined;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// TODO(burdon): Use schema.
|
|
114
|
-
return node.data?.label ?? node.data?.title ?? node.data?.name ?? node.data?.id.slice(0, 8);
|
|
115
|
-
},
|
|
116
|
-
}}
|
|
117
|
-
attributes={{
|
|
118
|
-
node: (node: GraphLayoutNode<ReactiveEchoObject<any>>) => {
|
|
119
|
-
let className: string | undefined;
|
|
120
|
-
if (node.data) {
|
|
121
|
-
const { object } = node.data;
|
|
122
|
-
if (object) {
|
|
123
|
-
const typename = getTypename(object);
|
|
124
|
-
if (typename) {
|
|
125
|
-
className = colorMap.get(typename);
|
|
126
|
-
if (!className) {
|
|
127
|
-
className = colors[colorMap.size % colors.length];
|
|
128
|
-
colorMap.set(typename, className);
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
const selected = filteredRef.current?.some((object) => object.id === node.data?.id);
|
|
135
|
-
const blur = !selected && !!filteredRef.current?.length;
|
|
136
|
-
return {
|
|
137
|
-
class: mx(className, blur && 'opacity-70'),
|
|
138
|
-
};
|
|
139
|
-
},
|
|
140
|
-
link: () => ({
|
|
141
|
-
class: '[&>path]:!stroke-neutral-300 dark:[&>path]:!stroke-neutral-700',
|
|
142
|
-
}),
|
|
143
|
-
}}
|
|
144
|
-
/>
|
|
145
|
-
</Zoom>
|
|
146
|
-
</SVG>
|
|
147
|
-
</SVGRoot>
|
|
85
|
+
<div ref={ref} className='relative grow' onClick={handleZoomToFit}>
|
|
86
|
+
<div ref={rootRef} className='absolute inset-0' />
|
|
87
|
+
</div>
|
|
148
88
|
);
|
|
149
89
|
};
|
|
@@ -2,26 +2,37 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
5
|
+
import { type UnsubscribeCallback } from '@dxos/async';
|
|
6
|
+
import {
|
|
7
|
+
getSchema,
|
|
8
|
+
getSchemaDXN,
|
|
9
|
+
AST,
|
|
10
|
+
type EchoSchema,
|
|
11
|
+
ReferenceAnnotationId,
|
|
12
|
+
SchemaValidator,
|
|
13
|
+
StoredSchema,
|
|
14
|
+
} from '@dxos/echo-schema';
|
|
15
|
+
import { type GraphData, GraphModel } from '@dxos/gem-spore';
|
|
7
16
|
import { log } from '@dxos/log';
|
|
8
17
|
import { CollectionType } from '@dxos/plugin-space/types';
|
|
9
|
-
import { type ReactiveEchoObject, type Space, type Subscription
|
|
18
|
+
import { Filter, type ReactiveEchoObject, type Space, type Subscription } from '@dxos/react-client/echo';
|
|
10
19
|
|
|
11
20
|
export type SpaceGraphModelOptions = {
|
|
12
21
|
schema?: boolean;
|
|
13
22
|
};
|
|
14
23
|
|
|
24
|
+
// TODO(burdon): Convert to common/graph.
|
|
25
|
+
|
|
15
26
|
type SchemaGraphNode = {
|
|
16
27
|
id: string;
|
|
17
28
|
type: 'schema';
|
|
18
|
-
|
|
29
|
+
data: { typename: string };
|
|
19
30
|
};
|
|
20
31
|
|
|
21
32
|
type ObjectGraphNode = {
|
|
22
33
|
id: string;
|
|
23
34
|
type: 'object';
|
|
24
|
-
object: ReactiveEchoObject<any
|
|
35
|
+
data: { typename: string; object: ReactiveEchoObject<any> };
|
|
25
36
|
};
|
|
26
37
|
|
|
27
38
|
export type EchoGraphNode = SchemaGraphNode | ObjectGraphNode;
|
|
@@ -35,8 +46,10 @@ export class SpaceGraphModel extends GraphModel<EchoGraphNode> {
|
|
|
35
46
|
links: [],
|
|
36
47
|
};
|
|
37
48
|
|
|
38
|
-
private
|
|
49
|
+
private _schema?: EchoSchema[];
|
|
50
|
+
private _schemaSubscription?: UnsubscribeCallback;
|
|
39
51
|
private _objects?: ReactiveEchoObject<any>[];
|
|
52
|
+
private _objectsSubscription?: Subscription;
|
|
40
53
|
|
|
41
54
|
constructor(private readonly _options: SpaceGraphModelOptions = {}) {
|
|
42
55
|
super();
|
|
@@ -50,80 +63,119 @@ export class SpaceGraphModel extends GraphModel<EchoGraphNode> {
|
|
|
50
63
|
return this._objects ?? [];
|
|
51
64
|
}
|
|
52
65
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
66
|
+
// TODO(burdon): Alternative diagram types:
|
|
67
|
+
// - https://observablehq.com/@d3/radial-tree/2
|
|
68
|
+
// - https://observablehq.com/@d3/disjoint-force-directed-graph/2
|
|
69
|
+
// - https://observablehq.com/@mbostock/tadpoles
|
|
70
|
+
// - https://observablehq.com/@d3/psr-b1919-21
|
|
71
|
+
// - https://vasturiano.github.io/react-force-graph/example/basic (3D)
|
|
72
|
+
|
|
73
|
+
async open(space: Space, objectId?: string) {
|
|
74
|
+
// TODO(burdon): Factor out graph builder to lib (use common/graph abstraction).
|
|
75
|
+
if (!this._schemaSubscription) {
|
|
76
|
+
// TODO(burdon): Normalize unsubscribe callbacks and merge handlers.
|
|
77
|
+
// TODO(burdon): Trigger initial subscription update.
|
|
78
|
+
// TODO(burdon): Normalize subscription cb for objects, schema, etc.
|
|
79
|
+
|
|
80
|
+
const schemaaQuery = space.db.schemaRegistry.query({});
|
|
81
|
+
const schemas = await schemaaQuery.run();
|
|
82
|
+
const onSchemaUpdate = ({ results }: { results: EchoSchema[] }) => (this._schema = results);
|
|
83
|
+
this._schemaSubscription = schemaaQuery.subscribe(onSchemaUpdate);
|
|
84
|
+
onSchemaUpdate({ results: schemas });
|
|
85
|
+
|
|
86
|
+
this._objectsSubscription = space.db
|
|
87
|
+
// TODO(burdon): ERROR: Cannot mix type and or filters.
|
|
88
|
+
.query(Filter.not(Filter.or(Filter.schema(StoredSchema), Filter.schema(CollectionType))))
|
|
89
|
+
.subscribe(
|
|
90
|
+
({ objects }) => {
|
|
91
|
+
this._objects = objects;
|
|
92
|
+
|
|
93
|
+
// Merge with current nodes.
|
|
94
|
+
const currentNodes = this._graph.nodes;
|
|
95
|
+
|
|
96
|
+
this._graph.nodes = [];
|
|
97
|
+
this._graph.links = [];
|
|
98
|
+
|
|
99
|
+
const addSchema = (typename: string) => {
|
|
100
|
+
const current = currentNodes.find((node) => node.id === typename);
|
|
101
|
+
if (typename) {
|
|
83
102
|
this._graph.nodes.push({
|
|
103
|
+
...current,
|
|
84
104
|
id: typename,
|
|
85
105
|
type: 'schema',
|
|
86
|
-
|
|
106
|
+
data: { typename },
|
|
87
107
|
});
|
|
88
108
|
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
//
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
if (
|
|
103
|
-
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
// Runtime schema.
|
|
112
|
+
space.db.graph.schemaRegistry.schemas.forEach((schema) => {
|
|
113
|
+
const typename = getSchemaDXN(schema)?.toTypename();
|
|
114
|
+
if (typename) {
|
|
115
|
+
addSchema(typename);
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// Database Schema.
|
|
120
|
+
this._schema?.forEach((schema) => {
|
|
121
|
+
const typename = getSchemaDXN(schema)?.toTypename();
|
|
122
|
+
if (typename) {
|
|
123
|
+
addSchema(typename);
|
|
104
124
|
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
// Database Objects.
|
|
128
|
+
this._objects.forEach((object) => {
|
|
129
|
+
const schema = getSchema(object);
|
|
130
|
+
if (schema) {
|
|
131
|
+
const typename = getSchemaDXN(schema)?.toTypename();
|
|
132
|
+
if (typename) {
|
|
133
|
+
const current = currentNodes.find((node) => node.id === object.id);
|
|
134
|
+
this._graph.nodes.push({ ...current, id: object.id, type: 'object', data: { typename, object } });
|
|
135
|
+
|
|
136
|
+
// Link to schema.
|
|
137
|
+
const schemaNode = this._graph.nodes.find(
|
|
138
|
+
(node) => node.type === 'schema' && node.data.typename === typename,
|
|
139
|
+
);
|
|
140
|
+
if (schemaNode) {
|
|
141
|
+
this._graph.links.push({
|
|
142
|
+
id: `${object.id}-${schemaNode.id}`,
|
|
112
143
|
source: object.id,
|
|
113
|
-
target:
|
|
144
|
+
target: schemaNode.id,
|
|
114
145
|
});
|
|
146
|
+
} else {
|
|
147
|
+
log.info('schema node not found', { typename });
|
|
115
148
|
}
|
|
149
|
+
|
|
150
|
+
// Link ot refs.
|
|
151
|
+
// TODO(burdon): This isn't working.
|
|
152
|
+
AST.getPropertySignatures(schema.ast).forEach((prop) => {
|
|
153
|
+
if (!SchemaValidator.hasTypeAnnotation(schema, prop.name.toString(), ReferenceAnnotationId)) {
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const value = object[String(prop.name)];
|
|
158
|
+
if (value) {
|
|
159
|
+
const refs = Array.isArray(value) ? value : [value];
|
|
160
|
+
for (const ref of refs) {
|
|
161
|
+
if (objects.findIndex((obj) => obj.id === ref.id) !== -1) {
|
|
162
|
+
this._graph.links.push({
|
|
163
|
+
id: `${object.id}-${String(prop.name)}-${ref.id}`,
|
|
164
|
+
source: object.id,
|
|
165
|
+
target: ref.id,
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
});
|
|
116
171
|
}
|
|
117
172
|
}
|
|
118
173
|
});
|
|
119
174
|
|
|
120
|
-
|
|
121
|
-
},
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
},
|
|
125
|
-
{ fire: true },
|
|
126
|
-
);
|
|
175
|
+
this.triggerUpdate();
|
|
176
|
+
},
|
|
177
|
+
{ fire: true },
|
|
178
|
+
);
|
|
127
179
|
}
|
|
128
180
|
|
|
129
181
|
this.setSelected(objectId);
|
|
@@ -131,10 +183,10 @@ export class SpaceGraphModel extends GraphModel<EchoGraphNode> {
|
|
|
131
183
|
}
|
|
132
184
|
|
|
133
185
|
close() {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
186
|
+
this._schemaSubscription?.();
|
|
187
|
+
this._schemaSubscription = undefined;
|
|
188
|
+
this._objectsSubscription?.();
|
|
189
|
+
this._objectsSubscription = undefined;
|
|
138
190
|
|
|
139
191
|
return this;
|
|
140
192
|
}
|
|
@@ -2,11 +2,12 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import React, { useEffect,
|
|
5
|
+
import React, { useEffect, useState } from 'react';
|
|
6
6
|
import { useResizeDetector } from 'react-resize-detector';
|
|
7
7
|
|
|
8
8
|
import { type Space } from '@dxos/client/echo';
|
|
9
9
|
import { createSvgContext, SVG, SVGRoot } from '@dxos/gem-core';
|
|
10
|
+
import { useAsyncState } from '@dxos/react-ui';
|
|
10
11
|
|
|
11
12
|
import { HierarchicalEdgeBundling, RadialTree, TidyTree } from './layout';
|
|
12
13
|
import { mapGraphToTreeData, type TreeNode } from './types';
|
|
@@ -62,7 +63,11 @@ export type TreeComponentProps<N = unknown> = {
|
|
|
62
63
|
|
|
63
64
|
// TODO(burdon): Label accessor.
|
|
64
65
|
export const Tree = <N,>({ space, selected, variant = 'tidy', onNodeClick }: TreeComponentProps<N>) => {
|
|
65
|
-
const model =
|
|
66
|
+
const [model] = useAsyncState(
|
|
67
|
+
async () => (space ? new SpaceGraphModel().open(space, selected) : undefined),
|
|
68
|
+
[space, selected],
|
|
69
|
+
);
|
|
70
|
+
|
|
66
71
|
const [tree, setTree] = useState<TreeNode>();
|
|
67
72
|
useEffect(() => {
|
|
68
73
|
return model?.subscribe(() => {
|