@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.
Files changed (52) hide show
  1. package/dist/lib/browser/{ExplorerContainer-THLT3CQZ.mjs → ExplorerContainer-T5CTMBIS.mjs} +2 -2
  2. package/dist/lib/browser/{chunk-YN4YVXVL.mjs → chunk-SU3K2HL7.mjs} +241 -2
  3. package/dist/lib/{node-esm/chunk-JYW3CDBT.mjs.map → browser/chunk-SU3K2HL7.mjs.map} +4 -4
  4. package/dist/lib/browser/chunk-V23FAKIX.mjs +205 -0
  5. package/dist/lib/browser/chunk-V23FAKIX.mjs.map +7 -0
  6. package/dist/lib/browser/index.mjs +7 -7
  7. package/dist/lib/browser/meta.json +1 -1
  8. package/dist/lib/browser/{react-surface-UMUN6CTH.mjs → react-surface-Q7OT6GXC.mjs} +3 -3
  9. package/dist/lib/node/{ExplorerContainer-EJPGD4WR.cjs → ExplorerContainer-SO5XAXFS.cjs} +6 -6
  10. package/dist/lib/node/chunk-6GTOKVKH.cjs +236 -0
  11. package/dist/lib/node/chunk-6GTOKVKH.cjs.map +7 -0
  12. package/dist/lib/node/{chunk-H5CHI722.cjs → chunk-CQYBCGC4.cjs} +234 -8
  13. package/dist/lib/node/chunk-CQYBCGC4.cjs.map +7 -0
  14. package/dist/lib/node/index.cjs +10 -10
  15. package/dist/lib/node/meta.json +1 -1
  16. package/dist/lib/node/{react-surface-UVMGNAA6.cjs → react-surface-5G52HSJW.cjs} +7 -7
  17. package/dist/lib/node-esm/{ExplorerContainer-MEZP34AY.mjs → ExplorerContainer-DTUTEZLK.mjs} +2 -2
  18. package/dist/lib/node-esm/{chunk-JYW3CDBT.mjs → chunk-4IST6Y3Z.mjs} +240 -2
  19. package/dist/lib/{browser/chunk-YN4YVXVL.mjs.map → node-esm/chunk-4IST6Y3Z.mjs.map} +4 -4
  20. package/dist/lib/node-esm/chunk-Q2IQDIKJ.mjs +207 -0
  21. package/dist/lib/node-esm/chunk-Q2IQDIKJ.mjs.map +7 -0
  22. package/dist/lib/node-esm/index.mjs +7 -7
  23. package/dist/lib/node-esm/meta.json +1 -1
  24. package/dist/lib/node-esm/{react-surface-5BTZOU3H.mjs → react-surface-QSQVINGP.mjs} +3 -3
  25. package/dist/types/src/components/Graph/Graph.d.ts +0 -2
  26. package/dist/types/src/components/Graph/Graph.d.ts.map +1 -1
  27. package/dist/types/src/components/Graph/Graph.stories.d.ts +1 -1
  28. package/dist/types/src/components/Graph/Graph.stories.d.ts.map +1 -1
  29. package/dist/types/src/components/Graph/graph-model.d.ts +11 -5
  30. package/dist/types/src/components/Graph/graph-model.d.ts.map +1 -1
  31. package/dist/types/src/components/Tree/Tree.d.ts.map +1 -1
  32. package/dist/types/src/components/Tree/types.d.ts +1 -1
  33. package/dist/types/src/components/Tree/types.d.ts.map +1 -1
  34. package/package.json +25 -24
  35. package/src/components/Graph/Graph.stories.tsx +2 -2
  36. package/src/components/Graph/Graph.tsx +65 -125
  37. package/src/components/Graph/graph-model.ts +123 -71
  38. package/src/components/Tree/Tree.tsx +7 -2
  39. package/src/components/Tree/types.ts +2 -1
  40. package/dist/lib/browser/chunk-TDUEJNAN.mjs +0 -474
  41. package/dist/lib/browser/chunk-TDUEJNAN.mjs.map +0 -7
  42. package/dist/lib/node/chunk-H5CHI722.cjs.map +0 -7
  43. package/dist/lib/node/chunk-QASZ24JS.cjs +0 -495
  44. package/dist/lib/node/chunk-QASZ24JS.cjs.map +0 -7
  45. package/dist/lib/node-esm/chunk-45DTVHHL.mjs +0 -476
  46. package/dist/lib/node-esm/chunk-45DTVHHL.mjs.map +0 -7
  47. /package/dist/lib/browser/{ExplorerContainer-THLT3CQZ.mjs.map → ExplorerContainer-T5CTMBIS.mjs.map} +0 -0
  48. /package/dist/lib/browser/{react-surface-UMUN6CTH.mjs.map → react-surface-Q7OT6GXC.mjs.map} +0 -0
  49. /package/dist/lib/node/{ExplorerContainer-EJPGD4WR.cjs.map → ExplorerContainer-SO5XAXFS.cjs.map} +0 -0
  50. /package/dist/lib/node/{react-surface-UVMGNAA6.cjs.map → react-surface-5G52HSJW.cjs.map} +0 -0
  51. /package/dist/lib/node-esm/{ExplorerContainer-MEZP34AY.mjs.map → ExplorerContainer-DTUTEZLK.mjs.map} +0 -0
  52. /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.b19bfc8",
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.b19bfc8",
45
- "@dxos/async": "0.7.5-main.b19bfc8",
46
- "@dxos/echo-schema": "0.7.5-main.b19bfc8",
47
- "@dxos/gem-core": "0.7.5-main.b19bfc8",
48
- "@dxos/client": "0.7.5-main.b19bfc8",
49
- "@dxos/gem-spore": "0.7.5-main.b19bfc8",
50
- "@dxos/live-object": "0.7.5-main.b19bfc8",
51
- "@dxos/log": "0.7.5-main.b19bfc8",
52
- "@dxos/plugin-client": "0.7.5-main.b19bfc8",
53
- "@dxos/plugin-search": "0.7.5-main.b19bfc8",
54
- "@dxos/plugin-space": "0.7.5-main.b19bfc8",
55
- "@dxos/react-client": "0.7.5-main.b19bfc8",
56
- "@dxos/plugin-graph": "0.7.5-main.b19bfc8",
57
- "@dxos/react-ui-stack": "0.7.5-main.b19bfc8",
58
- "@dxos/util": "0.7.5-main.b19bfc8"
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.b19bfc8",
74
- "@dxos/plugin-outliner": "0.7.5-main.b19bfc8",
75
- "@dxos/random": "0.7.5-main.b19bfc8",
76
- "@dxos/react-ui": "0.7.5-main.b19bfc8",
77
- "@dxos/react-ui-theme": "0.7.5-main.b19bfc8",
78
- "@dxos/storybook-utils": "0.7.5-main.b19bfc8"
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.b19bfc8",
85
- "@dxos/react-ui-theme": "0.7.5-main.b19bfc8"
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 React, { type FC, useEffect, useMemo, useRef, useState } from 'react';
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 { getTypename, type ReactiveEchoObject, type Space } from '@dxos/client/echo';
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 { useThemeContext } from '@dxos/react-ui';
18
- import { mx } from '@dxos/react-ui-theme';
12
+ import { useAsyncState } from '@dxos/react-ui';
19
13
 
20
- import '@dxos/gem-spore/styles';
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, grid }) => {
52
- const model = useMemo(() => (space ? new SpaceGraphModel({ schema: true }).open(space) : undefined), [space]);
53
- const [selected, setSelected] = useState<string>();
54
- const { themeMode } = useThemeContext();
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 context = createSvgContext();
57
- const projector = useMemo(
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
- const [colorMap] = useState(new Map<string, string>());
86
-
87
- if (!model) {
88
- return null;
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
- if (selected) {
92
- return <Tree space={space} selected={selected} variant='tidy' onNodeClick={() => setSelected(undefined)} />;
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
- <SVGRoot context={context}>
97
- <SVG className={mx(defaultStyles, slots?.root?.className)}>
98
- <Markers arrowSize={6} />
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 { AST, EchoSchema, ReferenceAnnotationId, type S, SchemaValidator, StoredSchema } from '@dxos/echo-schema';
6
- import { type GraphData, type GraphLink, GraphModel } from '@dxos/gem-spore';
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, getSchema, getType } from '@dxos/react-client/echo';
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
- schema: S.Schema<any>;
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 _subscription?: Subscription;
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
- open(space: Space, objectId?: string) {
54
- if (!this._subscription) {
55
- // TODO(burdon): Filter.
56
- const query = space.db.query((object: ReactiveEchoObject<any>) => !(object instanceof CollectionType));
57
-
58
- this._subscription = query.subscribe(
59
- ({ objects }) => {
60
- this._objects = objects;
61
-
62
- // TODO(burdon): Normalize schema.
63
- this._graph.nodes = objects.map((object) => {
64
- if (object instanceof StoredSchema) {
65
- const effectSchema = space.db.schemaRegistry.getSchemaById(object.id)!;
66
- return { type: 'schema', id: object.id, schema: effectSchema.schema };
67
- }
68
-
69
- return { type: 'object', id: object.id, object };
70
- });
71
-
72
- this._graph.links = objects.reduce<GraphLink[]>((links, object) => {
73
- const objectSchema = getSchema(object);
74
- const typename = getType(object)?.objectId;
75
- if (objectSchema == null || typename == null) {
76
- log.info('no schema for object:', { id: object.id.slice(0, 8) });
77
- return links;
78
- }
79
-
80
- if (!(objectSchema instanceof EchoSchema)) {
81
- const idx = objects.findIndex((obj) => obj.id === typename);
82
- if (idx === -1) {
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
- schema: objectSchema,
106
+ data: { typename },
87
107
  });
88
108
  }
89
- }
90
-
91
- // Link to schema.
92
- if (this._options.schema) {
93
- links.push({
94
- id: `${object.id}-${typename}`,
95
- source: object.id,
96
- target: typename,
97
- });
98
- }
99
-
100
- // Parse schema to follow referenced objects.
101
- AST.getPropertySignatures(objectSchema.ast).forEach((prop) => {
102
- if (!SchemaValidator.hasTypeAnnotation(objectSchema, prop.name.toString(), ReferenceAnnotationId)) {
103
- return;
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
- const value = object[String(prop.name)];
106
- if (value) {
107
- const refs = Array.isArray(value) ? value : [value];
108
- for (const ref of refs) {
109
- if (objects.findIndex((obj) => obj.id === ref.id) !== -1) {
110
- links.push({
111
- id: `${object.id}-${String(prop.name)}-${ref.id}`,
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: ref.id,
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
- return links;
121
- }, []);
122
-
123
- this.triggerUpdate();
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
- if (this._subscription) {
135
- this._subscription();
136
- this._subscription = undefined;
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, useMemo, useState } from 'react';
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 = useMemo(() => (space ? new SpaceGraphModel().open(space, selected) : undefined), [space, selected]);
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(() => {
@@ -2,7 +2,8 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- import type { GraphModel } from '@dxos/gem-spore';
5
+ // TODO(burdon): Convert to common/graph.
6
+ import { type GraphModel } from '@dxos/gem-spore';
6
7
 
7
8
  export type TreeNode = {
8
9
  id: string;