@dxos/plugin-explorer 0.8.4-main.f5c0578 → 0.8.4-main.fcc0d83b33

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 (155) hide show
  1. package/dist/lib/browser/chunk-J5LGTIGS.mjs +10 -0
  2. package/dist/lib/browser/chunk-J5LGTIGS.mjs.map +7 -0
  3. package/dist/lib/browser/index.mjs +11323 -94
  4. package/dist/lib/browser/index.mjs.map +4 -4
  5. package/dist/lib/browser/meta.json +1 -1
  6. package/dist/lib/browser/meta.mjs +21 -5
  7. package/dist/lib/browser/meta.mjs.map +4 -4
  8. package/dist/lib/browser/translations.mjs +33 -0
  9. package/dist/lib/browser/translations.mjs.map +7 -0
  10. package/dist/lib/browser/types/index.mjs +68 -6
  11. package/dist/lib/browser/types/index.mjs.map +4 -4
  12. package/dist/lib/node-esm/chunk-HSLMI22Q.mjs +11 -0
  13. package/dist/lib/node-esm/chunk-HSLMI22Q.mjs.map +7 -0
  14. package/dist/lib/node-esm/index.mjs +11323 -94
  15. package/dist/lib/node-esm/index.mjs.map +4 -4
  16. package/dist/lib/node-esm/meta.json +1 -1
  17. package/dist/lib/node-esm/meta.mjs +21 -5
  18. package/dist/lib/node-esm/meta.mjs.map +4 -4
  19. package/dist/lib/node-esm/translations.mjs +34 -0
  20. package/dist/lib/node-esm/translations.mjs.map +7 -0
  21. package/dist/lib/node-esm/types/index.mjs +68 -6
  22. package/dist/lib/node-esm/types/index.mjs.map +4 -4
  23. package/dist/types/data/cities.d.ts +4 -4
  24. package/dist/types/data/cities.d.ts.map +1 -1
  25. package/dist/types/data/countries-110m.d.ts +19 -22
  26. package/dist/types/data/countries-110m.d.ts.map +1 -1
  27. package/dist/types/src/ExplorerPlugin.d.ts +2 -1
  28. package/dist/types/src/ExplorerPlugin.d.ts.map +1 -1
  29. package/dist/types/src/capabilities/index.d.ts +2 -2
  30. package/dist/types/src/capabilities/index.d.ts.map +1 -1
  31. package/dist/types/src/capabilities/react-surface.d.ts +3 -2
  32. package/dist/types/src/capabilities/react-surface.d.ts.map +1 -1
  33. package/dist/types/src/components/Chart/Chart.d.ts.map +1 -1
  34. package/dist/types/src/components/Chart/Chart.stories.d.ts +8 -4
  35. package/dist/types/src/components/Chart/Chart.stories.d.ts.map +1 -1
  36. package/dist/types/src/components/Globe/Globe.d.ts.map +1 -1
  37. package/dist/types/src/components/Globe/Globe.stories.d.ts +8 -4
  38. package/dist/types/src/components/Globe/Globe.stories.d.ts.map +1 -1
  39. package/dist/types/src/components/Graph/D3ForceGraph.d.ts +6 -5
  40. package/dist/types/src/components/Graph/D3ForceGraph.d.ts.map +1 -1
  41. package/dist/types/src/components/Graph/D3ForceGraph.stories.d.ts +15 -4
  42. package/dist/types/src/components/Graph/D3ForceGraph.stories.d.ts.map +1 -1
  43. package/dist/types/src/components/Graph/ForceGraph.stories.d.ts +13 -4
  44. package/dist/types/src/components/Graph/ForceGraph.stories.d.ts.map +1 -1
  45. package/dist/types/src/components/Graph/adapter.d.ts +1 -1
  46. package/dist/types/src/components/Graph/adapter.d.ts.map +1 -1
  47. package/dist/types/src/components/Graph/testing.d.ts.map +1 -1
  48. package/dist/types/src/components/Tree/Tree.d.ts.map +1 -1
  49. package/dist/types/src/components/Tree/Tree.stories.d.ts +13 -16
  50. package/dist/types/src/components/Tree/Tree.stories.d.ts.map +1 -1
  51. package/dist/types/src/components/Tree/layout/HierarchicalEdgeBundling.d.ts.map +1 -1
  52. package/dist/types/src/components/Tree/layout/RadialTree.d.ts.map +1 -1
  53. package/dist/types/src/components/Tree/layout/TidyTree.d.ts.map +1 -1
  54. package/dist/types/src/components/Tree/testing/generator.d.ts.map +1 -1
  55. package/dist/types/src/components/Tree/types/tree.d.ts +19 -17
  56. package/dist/types/src/components/Tree/types/tree.d.ts.map +1 -1
  57. package/dist/types/src/components/Tree/types/types.d.ts +1 -1
  58. package/dist/types/src/components/Tree/types/types.d.ts.map +1 -1
  59. package/dist/types/src/components/index.d.ts +0 -4
  60. package/dist/types/src/components/index.d.ts.map +1 -1
  61. package/dist/types/src/components/plot.d.ts.map +1 -1
  62. package/dist/types/src/containers/ExplorerContainer/ExplorerContainer.d.ts +6 -0
  63. package/dist/types/src/containers/ExplorerContainer/ExplorerContainer.d.ts.map +1 -0
  64. package/dist/types/src/containers/ExplorerContainer/index.d.ts +2 -0
  65. package/dist/types/src/containers/ExplorerContainer/index.d.ts.map +1 -0
  66. package/dist/types/src/containers/index.d.ts +3 -0
  67. package/dist/types/src/containers/index.d.ts.map +1 -0
  68. package/dist/types/src/hooks/useGraphModel.d.ts.map +1 -1
  69. package/dist/types/src/index.d.ts +0 -1
  70. package/dist/types/src/index.d.ts.map +1 -1
  71. package/dist/types/src/meta.d.ts +2 -3
  72. package/dist/types/src/meta.d.ts.map +1 -1
  73. package/dist/types/src/translations.d.ts +34 -13
  74. package/dist/types/src/translations.d.ts.map +1 -1
  75. package/dist/types/src/types/ExplorerAction.d.ts +6 -0
  76. package/dist/types/src/types/ExplorerAction.d.ts.map +1 -0
  77. package/dist/types/src/types/Graph.d.ts +23 -0
  78. package/dist/types/src/types/Graph.d.ts.map +1 -0
  79. package/dist/types/src/types/index.d.ts +2 -2
  80. package/dist/types/src/types/index.d.ts.map +1 -1
  81. package/dist/types/tsconfig.tsbuildinfo +1 -1
  82. package/package.json +66 -54
  83. package/src/ExplorerPlugin.tsx +43 -54
  84. package/src/capabilities/index.ts +2 -3
  85. package/src/capabilities/react-surface.tsx +24 -15
  86. package/src/components/Chart/Chart.stories.tsx +8 -8
  87. package/src/components/Globe/Globe.stories.tsx +11 -11
  88. package/src/components/Graph/D3ForceGraph.stories.tsx +46 -28
  89. package/src/components/Graph/D3ForceGraph.tsx +82 -75
  90. package/src/components/Graph/ForceGraph.stories.tsx +46 -28
  91. package/src/components/Graph/ForceGraph.tsx +3 -3
  92. package/src/components/Graph/adapter.ts +14 -8
  93. package/src/components/Graph/testing.ts +11 -8
  94. package/src/components/Tree/Tree.stories.tsx +33 -24
  95. package/src/components/Tree/Tree.tsx +10 -5
  96. package/src/components/Tree/testing/generator.ts +4 -2
  97. package/src/components/Tree/types/tree.test.ts +7 -6
  98. package/src/components/Tree/types/tree.ts +42 -21
  99. package/src/components/Tree/types/types.ts +1 -1
  100. package/src/components/index.ts +0 -4
  101. package/src/containers/ExplorerContainer/ExplorerContainer.tsx +53 -0
  102. package/src/containers/ExplorerContainer/index.ts +5 -0
  103. package/src/containers/index.ts +7 -0
  104. package/src/hooks/useGraphModel.ts +17 -10
  105. package/src/index.ts +0 -1
  106. package/src/meta.ts +9 -6
  107. package/src/translations.ts +17 -12
  108. package/src/types/ExplorerAction.ts +20 -0
  109. package/src/types/Graph.ts +49 -0
  110. package/src/types/index.ts +2 -2
  111. package/src/typings.d.ts +8 -0
  112. package/dist/lib/browser/ExplorerContainer-5QHLD2B2.mjs +0 -37
  113. package/dist/lib/browser/ExplorerContainer-5QHLD2B2.mjs.map +0 -7
  114. package/dist/lib/browser/chunk-2MKBRIUT.mjs +0 -31
  115. package/dist/lib/browser/chunk-2MKBRIUT.mjs.map +0 -7
  116. package/dist/lib/browser/chunk-CZZ3DDR7.mjs +0 -38
  117. package/dist/lib/browser/chunk-CZZ3DDR7.mjs.map +0 -7
  118. package/dist/lib/browser/chunk-L4U4MPSZ.mjs +0 -190
  119. package/dist/lib/browser/chunk-L4U4MPSZ.mjs.map +0 -7
  120. package/dist/lib/browser/chunk-LGK64HLU.mjs +0 -11089
  121. package/dist/lib/browser/chunk-LGK64HLU.mjs.map +0 -7
  122. package/dist/lib/browser/chunk-UL5EDJPE.mjs +0 -21
  123. package/dist/lib/browser/chunk-UL5EDJPE.mjs.map +0 -7
  124. package/dist/lib/browser/intent-resolver-7MVEYNX7.mjs +0 -24
  125. package/dist/lib/browser/intent-resolver-7MVEYNX7.mjs.map +0 -7
  126. package/dist/lib/browser/react-surface-FABRDFTF.mjs +0 -31
  127. package/dist/lib/browser/react-surface-FABRDFTF.mjs.map +0 -7
  128. package/dist/lib/node-esm/ExplorerContainer-AMYAVLO4.mjs +0 -38
  129. package/dist/lib/node-esm/ExplorerContainer-AMYAVLO4.mjs.map +0 -7
  130. package/dist/lib/node-esm/chunk-3ODK27PU.mjs +0 -33
  131. package/dist/lib/node-esm/chunk-3ODK27PU.mjs.map +0 -7
  132. package/dist/lib/node-esm/chunk-4GWDNZ4Z.mjs +0 -39
  133. package/dist/lib/node-esm/chunk-4GWDNZ4Z.mjs.map +0 -7
  134. package/dist/lib/node-esm/chunk-MCOXQ3ML.mjs +0 -192
  135. package/dist/lib/node-esm/chunk-MCOXQ3ML.mjs.map +0 -7
  136. package/dist/lib/node-esm/chunk-PIAXA43R.mjs +0 -23
  137. package/dist/lib/node-esm/chunk-PIAXA43R.mjs.map +0 -7
  138. package/dist/lib/node-esm/chunk-W4ZNCGOD.mjs +0 -11091
  139. package/dist/lib/node-esm/chunk-W4ZNCGOD.mjs.map +0 -7
  140. package/dist/lib/node-esm/intent-resolver-NL3SR2XF.mjs +0 -25
  141. package/dist/lib/node-esm/intent-resolver-NL3SR2XF.mjs.map +0 -7
  142. package/dist/lib/node-esm/react-surface-EYCZUAAI.mjs +0 -32
  143. package/dist/lib/node-esm/react-surface-EYCZUAAI.mjs.map +0 -7
  144. package/dist/types/src/capabilities/intent-resolver.d.ts +0 -4
  145. package/dist/types/src/capabilities/intent-resolver.d.ts.map +0 -1
  146. package/dist/types/src/components/ExplorerContainer.d.ts +0 -9
  147. package/dist/types/src/components/ExplorerContainer.d.ts.map +0 -1
  148. package/dist/types/src/types/schema.d.ts +0 -12
  149. package/dist/types/src/types/schema.d.ts.map +0 -1
  150. package/dist/types/src/types/types.d.ts +0 -18
  151. package/dist/types/src/types/types.d.ts.map +0 -1
  152. package/src/capabilities/intent-resolver.ts +0 -19
  153. package/src/components/ExplorerContainer.tsx +0 -37
  154. package/src/types/schema.ts +0 -16
  155. package/src/types/types.ts +0 -22
@@ -2,11 +2,11 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- import React, { type FC, useCallback, useEffect, useMemo, useRef } from 'react';
5
+ import { Atom, useAtomValue } from '@effect-atom/atom-react';
6
+ import React, { type ComponentPropsWithoutRef, useCallback, useEffect, useMemo, useRef } from 'react';
6
7
 
7
8
  import { Obj } from '@dxos/echo';
8
9
  import { SelectionModel } from '@dxos/graph';
9
- import { type ThemedClassName } from '@dxos/react-ui';
10
10
  import {
11
11
  type GraphController,
12
12
  GraphForceProjector,
@@ -15,87 +15,94 @@ import {
15
15
  SVG,
16
16
  type SVGContext,
17
17
  } from '@dxos/react-ui-graph';
18
- import { getHashColor } from '@dxos/react-ui-theme';
19
18
  import { type SpaceGraphEdge, type SpaceGraphModel, type SpaceGraphNode } from '@dxos/schema';
20
-
19
+ import { composable, composableProps, getHashStyles } from '@dxos/ui-theme';
21
20
  import '@dxos/react-ui-graph/styles/graph.css';
22
21
 
23
- export type D3ForceGraphProps = ThemedClassName<
24
- {
25
- model?: SpaceGraphModel;
26
- match?: RegExp;
27
- selection?: SelectionModel;
28
- grid?: boolean;
29
- } & Pick<GraphProps, 'drag'>
30
- >;
22
+ export type D3ForceGraphProps = {
23
+ model?: SpaceGraphModel;
24
+ match?: RegExp;
25
+ selection?: SelectionModel;
26
+ grid?: boolean;
27
+ } & Pick<GraphProps, 'drag'> &
28
+ ComponentPropsWithoutRef<'div'>;
29
+
30
+ const EMPTY_ATOM = Atom.make<{ nodes: SpaceGraphNode[]; edges: SpaceGraphEdge[] }>({ nodes: [], edges: [] });
31
+
32
+ export const D3ForceGraph = composable<HTMLDivElement, D3ForceGraphProps>(
33
+ ({ model, selection: _selection, grid, drag, ...props }, forwardedRef) => {
34
+ // TODO(wittjosiah): This should go into Graph.tsx but for some reason doesn't work.
35
+ useAtomValue(model?.graphAtom ?? EMPTY_ATOM);
31
36
 
32
- export const D3ForceGraph: FC<D3ForceGraphProps> = ({ classNames, model, selection: _selection, grid, ...props }) => {
33
- const context = useRef<SVGContext>(null);
34
- const projector = useMemo<GraphForceProjector | undefined>(() => {
35
- if (context.current) {
36
- return new GraphForceProjector(context.current, {
37
- attributes: {
38
- linkForce: (edge) => {
39
- // TODO(burdon): Check type (currently assumes Employee property).
40
- // Edge shouldn't contribute to force if it's not active.
41
- return edge.data?.object?.active !== false;
37
+ const svgRef = useRef<SVGContext>(null);
38
+ const projector = useMemo<GraphForceProjector | undefined>(() => {
39
+ if (svgRef.current) {
40
+ return new GraphForceProjector(svgRef.current, {
41
+ attributes: {
42
+ linkForce: (edge) => {
43
+ // TODO(burdon): Check type (currently assumes Employee property).
44
+ // Edge shouldn't contribute to force if it's not active.
45
+ return edge.data?.object?.active !== false;
46
+ },
42
47
  },
43
- },
44
- forces: {
45
- point: {
46
- strength: 0.01,
48
+ forces: {
49
+ point: {
50
+ strength: 0.01,
51
+ },
47
52
  },
48
- },
49
- });
50
- }
51
- }, [context.current]);
53
+ });
54
+ }
55
+ }, []);
52
56
 
53
- const graph = useRef<GraphController>(null);
54
- const selection = useMemo(() => _selection ?? new SelectionModel(), [_selection]);
55
- useEffect(() => graph.current?.repaint(), [selection.selected.value]);
57
+ const graph = useRef<GraphController>(null);
58
+ const selection = useMemo(() => _selection ?? new SelectionModel(), [_selection]);
59
+ useEffect(() => selection.subscribe(() => graph.current?.repaint()), [selection]);
56
60
 
57
- const handleSelect = useCallback<NonNullable<GraphProps['onSelect']>>(
58
- (node) => {
59
- if (selection.contains(node.id)) {
60
- selection.remove(node.id);
61
- } else {
62
- selection.add(node.id);
63
- }
64
- },
65
- [selection],
66
- );
61
+ const handleSelect = useCallback<NonNullable<GraphProps['onSelect']>>(
62
+ (node) => {
63
+ if (selection.contains(node.id)) {
64
+ selection.remove(node.id);
65
+ } else {
66
+ selection.add(node.id);
67
+ }
68
+ },
69
+ [selection],
70
+ );
67
71
 
68
- return (
69
- <SVG.Root ref={context} classNames={classNames}>
70
- <SVG.Markers />
71
- {grid && <SVG.Grid axis />}
72
- <SVG.Zoom extent={[1 / 2, 2]}>
73
- <SVG.Graph<SpaceGraphNode, SpaceGraphEdge>
74
- {...props}
75
- ref={graph}
76
- model={model}
77
- projector={projector}
78
- labels={{
79
- text: (node) => {
80
- return node.data?.data.label ?? node.id;
81
- },
82
- }}
83
- attributes={{
84
- node: (node: GraphLayoutNode<SpaceGraphNode>) => {
85
- const obj = node.data?.data.object;
86
- return {
87
- data: {
88
- color: getHashColor(obj && Obj.getTypename(obj))?.color,
72
+ return (
73
+ <div {...composableProps(props, { classNames: 'dx-container' })} ref={forwardedRef}>
74
+ <SVG.Root ref={svgRef}>
75
+ <SVG.Markers />
76
+ {grid && <SVG.Grid axis />}
77
+ <SVG.Zoom extent={[1 / 2, 2]}>
78
+ <SVG.Graph<SpaceGraphNode, SpaceGraphEdge>
79
+ drag={drag}
80
+ ref={graph}
81
+ model={model}
82
+ projector={projector}
83
+ labels={{
84
+ text: (node) => {
85
+ return node.data?.data.label ?? node.id;
89
86
  },
90
- classes: {
91
- 'dx-selected': selection.contains(node.id),
87
+ }}
88
+ attributes={{
89
+ node: (node: GraphLayoutNode<SpaceGraphNode>) => {
90
+ const obj = node.data?.data.object;
91
+ return {
92
+ data: {
93
+ color: getHashStyles(obj && Obj.getTypename(obj))?.hue,
94
+ },
95
+ classes: {
96
+ 'dx-selected': selection.contains(node.id),
97
+ },
98
+ };
92
99
  },
93
- };
94
- },
95
- }}
96
- onSelect={handleSelect}
97
- />
98
- </SVG.Zoom>
99
- </SVG.Root>
100
- );
101
- };
100
+ }}
101
+ onSelect={handleSelect}
102
+ />
103
+ </SVG.Zoom>
104
+ </SVG.Root>
105
+ </div>
106
+ );
107
+ },
108
+ );
@@ -2,64 +2,82 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- import '@dxos-theme';
5
+ import { type Meta, type StoryObj } from '@storybook/react-vite';
6
+ import React, { useState } from 'react';
6
7
 
7
- import { type Meta } from '@storybook/react-vite';
8
- import React, { useEffect, useState } from 'react';
9
-
10
- import { Obj } from '@dxos/echo';
11
- import { faker } from '@dxos/random';
8
+ import { Type } from '@dxos/echo';
9
+ import { View } from '@dxos/echo';
10
+ import { random } from '@dxos/random';
12
11
  import { useClient } from '@dxos/react-client';
13
12
  import { type Space } from '@dxos/react-client/echo';
14
13
  import { withClientProvider } from '@dxos/react-client/testing';
15
- import { DataType } from '@dxos/schema';
14
+ import { useAsyncEffect } from '@dxos/react-ui';
15
+ import { Loading, withLayout, withTheme } from '@dxos/react-ui/testing';
16
+ import { ViewModel } from '@dxos/schema';
16
17
  import { type ValueGenerator } from '@dxos/schema/testing';
17
- import { render, withLayout, withTheme } from '@dxos/storybook-utils';
18
+ import { withRegistry } from '@dxos/storybook-utils';
19
+ import { HasRelationship, Organization, Person, Pipeline } from '@dxos/types';
18
20
 
19
- import { useGraphModel } from '../../hooks';
20
- import { ViewType } from '../../types';
21
+ import { useGraphModel } from '#hooks';
22
+ import { Graph } from '#types';
21
23
 
22
24
  import { ForceGraph } from './ForceGraph';
23
25
  import { generate } from './testing';
24
26
 
25
- const generator = faker as any as ValueGenerator;
27
+ const generator = random as any as ValueGenerator;
26
28
 
27
- faker.seed(1);
29
+ random.seed(1);
28
30
 
29
31
  const DefaultStory = () => {
30
32
  const client = useClient();
31
33
  const [space, setSpace] = useState<Space>();
32
- const [view, setView] = useState<ViewType>();
33
- useEffect(() => {
34
- const space = client.spaces.default;
34
+ const [graph, setGraph] = useState<Graph.Graph>();
35
+
36
+ useAsyncEffect(async () => {
37
+ const space = client.spaces.get()[0];
35
38
  void generate(space, generator);
36
- const view = space.db.add(Obj.make(ViewType, { name: '', type: '' }));
39
+ const { view } = await ViewModel.makeFromDatabase({ db: space.db, typename: Type.getTypename(Graph.Graph) });
40
+ const graph = Graph.make({ name: 'Test', view });
41
+ space.db.add(graph);
37
42
  setSpace(space);
38
- setView(view);
39
- }, []);
43
+ setGraph(graph);
44
+ }, [client]);
40
45
 
41
46
  const model = useGraphModel(space);
42
- if (!model || !space || !view) {
43
- return null;
47
+ if (!model || !space || !graph) {
48
+ return <Loading data={{ model: !!model, space: !!space, graph: !!graph }} />;
44
49
  }
45
50
 
46
51
  return <ForceGraph model={model} />;
47
52
  };
48
53
 
49
- const meta: Meta = {
50
- title: 'plugins/plugin-explorer/ForceGraph',
54
+ const meta = {
55
+ title: 'plugins/plugin-explorer/components/ForceGraph',
51
56
  component: ForceGraph,
52
- render: render(DefaultStory),
57
+ render: DefaultStory,
53
58
  decorators: [
59
+ withRegistry,
60
+ withTheme(),
61
+ withLayout(),
54
62
  withClientProvider({
55
63
  createSpace: true,
56
- types: [ViewType, DataType.HasRelationship, DataType.Organization, DataType.Project, DataType.Person],
64
+ types: [
65
+ Graph.Graph,
66
+ View.View,
67
+ HasRelationship.HasRelationship,
68
+ Organization.Organization,
69
+ Pipeline.Pipeline,
70
+ Person.Person,
71
+ ],
57
72
  }),
58
- withTheme,
59
- withLayout({ fullscreen: true }),
60
73
  ],
61
- };
74
+ parameters: {
75
+ layout: 'fullscreen',
76
+ },
77
+ } satisfies Meta<typeof ForceGraph>;
62
78
 
63
79
  export default meta;
64
80
 
65
- export const Default = {};
81
+ type Story = StoryObj<typeof meta>;
82
+
83
+ export const Default: Story = {};
@@ -20,9 +20,9 @@ export type ForceGraphProps = {
20
20
  export const ForceGraph: FC<ForceGraphProps> = ({ model, match }) => {
21
21
  const { ref, width, height } = useResizeDetector({ refreshRate: 200 });
22
22
  const rootRef = useRef<HTMLDivElement>(null);
23
- const forceGraph = useRef<NativeForceGraph>();
23
+ const forceGraph = useRef<NativeForceGraph>(null);
24
24
 
25
- const filteredRef = useRef<SearchResult[]>();
25
+ const filteredRef = useRef<SearchResult[]>([]);
26
26
  filteredRef.current = filterObjectsSync(model?.objects ?? [], match);
27
27
 
28
28
  const [data, setData] = useState<GraphAdapter>();
@@ -48,7 +48,7 @@ export const ForceGraph: FC<ForceGraphProps> = ({ model, match }) => {
48
48
 
49
49
  return () => {
50
50
  forceGraph.current?.pauseAnimation().graphData({ nodes: [], links: [] });
51
- forceGraph.current = undefined;
51
+ forceGraph.current = null;
52
52
  };
53
53
  }, []);
54
54
 
@@ -25,19 +25,25 @@ export class GraphAdapter implements GraphData {
25
25
  private readonly _nodes: GraphNode[] = [];
26
26
  private readonly _links: GraphLink[] = [];
27
27
 
28
- constructor(private readonly graph: Graph) {
29
- this._nodes = graph.nodes.map((node) => ({
28
+ constructor(private readonly graph: Graph.Any) {
29
+ this._nodes = graph.nodes.map((node: Graph.Node.Any) => ({
30
30
  id: node.id,
31
31
  type: node.type,
32
32
  data: node.data,
33
33
  }));
34
34
 
35
- this._links = graph.edges.map((edge) => ({
36
- type: edge.type,
37
- source: edge.source,
38
- target: edge.target,
39
- data: edge.data,
40
- }));
35
+ // Build a set of node IDs for efficient lookup.
36
+ const nodeIds = new Set(this._nodes.map((node) => node.id));
37
+
38
+ // Filter out edges where source or target node doesn't exist.
39
+ this._links = graph.edges
40
+ .filter((edge: Graph.Edge.Any) => nodeIds.has(edge.source) && nodeIds.has(edge.target))
41
+ .map((edge: Graph.Edge.Any) => ({
42
+ type: edge.type,
43
+ source: edge.source,
44
+ target: edge.target,
45
+ data: edge.data,
46
+ }));
41
47
  }
42
48
 
43
49
  get nodes() {
@@ -4,16 +4,16 @@
4
4
 
5
5
  import { type Space } from '@dxos/client/echo';
6
6
  import { type Obj, Query, Relation } from '@dxos/echo';
7
- import { DataType } from '@dxos/schema';
8
7
  import { type TypeSpec, type ValueGenerator, createObjectFactory } from '@dxos/schema/testing';
8
+ import { HasRelationship, Organization, Person, Pipeline } from '@dxos/types';
9
9
  import { range } from '@dxos/util';
10
10
 
11
- const getObject = (objects: Obj.Any[]) => objects[Math.floor(Math.random() * objects.length)];
11
+ const getObject = (objects: Obj.Unknown[]) => objects[Math.floor(Math.random() * objects.length)];
12
12
 
13
13
  const defaultTypes: TypeSpec[] = [
14
- { type: DataType.Organization, count: 5 },
15
- { type: DataType.Project, count: 5 },
16
- { type: DataType.Person, count: 10 },
14
+ { type: Organization.Organization, count: 5 },
15
+ { type: Pipeline.Pipeline, count: 5 },
16
+ { type: Person.Person, count: 10 },
17
17
  ];
18
18
 
19
19
  export type GenerateOptions = {
@@ -24,7 +24,10 @@ export type GenerateOptions = {
24
24
  };
25
25
  };
26
26
 
27
- const defaultRelations: GenerateOptions['relations'] = { kind: 'friend', count: 10 };
27
+ const defaultRelations: GenerateOptions['relations'] = {
28
+ kind: 'friend',
29
+ count: 10,
30
+ };
28
31
 
29
32
  /**
30
33
  * @deprecated Use @dxos/schema.
@@ -38,13 +41,13 @@ export const generate = async (
38
41
  await createObjects(spec);
39
42
 
40
43
  // Add relations between objects.
41
- const { objects: contacts } = await space.db.query(Query.type(DataType.Person)).run();
44
+ const contacts = await space.db.query(Query.type(Person.Person)).run();
42
45
  for (const _ of range(relations.count)) {
43
46
  const source = getObject(contacts);
44
47
  const target = getObject(contacts);
45
48
  if (source.id !== target.id) {
46
49
  space.db.add(
47
- Relation.make(DataType.HasRelationship, {
50
+ Relation.make(HasRelationship.HasRelationship, {
48
51
  [Relation.Source]: source,
49
52
  [Relation.Target]: target,
50
53
  kind: relations.kind,
@@ -2,15 +2,15 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- import '@dxos-theme';
5
+ import { type Meta, type StoryObj } from '@storybook/react-vite';
6
+ import React, { useEffect, useState } from 'react';
6
7
 
7
- import { type Meta } from '@storybook/react-vite';
8
- import React, { type FC, useEffect, useState } from 'react';
9
-
10
- import { faker } from '@dxos/random';
8
+ import { invariant } from '@dxos/invariant';
9
+ import { random } from '@dxos/random';
11
10
  import { useClient } from '@dxos/react-client';
12
11
  import { type ClientRepeatedComponentProps, ClientRepeater } from '@dxos/react-client/testing';
13
- import { withLayout, withTheme } from '@dxos/storybook-utils';
12
+ import { withLayout, withTheme } from '@dxos/react-ui/testing';
13
+ import { withRegistry } from '@dxos/storybook-utils';
14
14
 
15
15
  import { Tree, type TreeComponentProps } from './Tree';
16
16
  import { Tree as TreeModel, TreeType } from './types';
@@ -18,11 +18,14 @@ import { Tree as TreeModel, TreeType } from './types';
18
18
  // TODO(burdon): Storybook for Graph/Tree/Plot (generics); incl. GraphModel.
19
19
  // TODO(burdon): Type for all Explorer components (Space, Object, Query, etc.) incl.
20
20
 
21
- faker.seed(1);
21
+ random.seed(1);
22
+
23
+ type ComponentProps = ClientRepeatedComponentProps & { type?: TreeComponentProps<any>['variant'] };
22
24
 
23
- const Story: FC<ClientRepeatedComponentProps & { type?: TreeComponentProps<any>['variant'] }> = ({ type }) => {
25
+ const Component = ({ type }: ComponentProps) => {
24
26
  const client = useClient();
25
- const space = client.spaces.default;
27
+ const space = client.spaces.get()[0];
28
+ invariant(space, 'Tree story requires at least one space');
26
29
  const [object, setObject] = useState<TreeType>();
27
30
  useEffect(() => {
28
31
  setTimeout(() => {
@@ -38,32 +41,38 @@ const Story: FC<ClientRepeatedComponentProps & { type?: TreeComponentProps<any>[
38
41
  return <Tree space={space} selected={object?.id} variant={type} />;
39
42
  };
40
43
 
41
- export const Tidy = {
44
+ const DefaultStory = () => {
45
+ return <ClientRepeater component={Component} types={[TreeType]} createSpace />;
46
+ };
47
+
48
+ const meta = {
49
+ title: 'plugins/plugin-explorer/components/Tree',
50
+ component: Tree as any,
51
+ render: DefaultStory,
52
+ decorators: [withRegistry, withTheme(), withLayout()],
53
+ parameters: {
54
+ layout: 'fullscreen',
55
+ },
56
+ } satisfies Meta<typeof DefaultStory>;
57
+
58
+ export default meta;
59
+
60
+ type Story = StoryObj<typeof meta>;
61
+
62
+ export const Tidy: Story = {
42
63
  args: {
43
64
  type: 'tidy',
44
65
  },
45
66
  };
46
67
 
47
- export const Radial = {
68
+ export const Radial: Story = {
48
69
  args: {
49
70
  type: 'radial',
50
71
  },
51
72
  };
52
73
 
53
- export const Edge = {
74
+ export const Edge: Story = {
54
75
  args: {
55
76
  type: 'edge',
56
77
  },
57
78
  };
58
-
59
- const meta: Meta = {
60
- title: 'plugins/plugin-explorer/Tree',
61
- component: Tree,
62
- render: () => <ClientRepeater component={Story} types={[TreeType]} createSpace />,
63
- decorators: [withTheme, withLayout({ fullscreen: true })],
64
- parameters: {
65
- layout: 'fullscreen',
66
- },
67
- };
68
-
69
- export default meta;
@@ -2,7 +2,8 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- import React, { useEffect, useRef, useState } from 'react';
5
+ import { RegistryContext } from '@effect-atom/atom-react';
6
+ import React, { useContext, useEffect, useRef, useState } from 'react';
6
7
 
7
8
  import { type Space } from '@dxos/client/echo';
8
9
  import { useAsyncState } from '@dxos/react-ui';
@@ -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] = useAsyncState(async () => (space ? new SpaceGraphModel().open(space) : undefined), [space, selected]);
66
+ const registry = useContext(RegistryContext);
67
+ const [model] = useAsyncState(
68
+ async () => (space ? new SpaceGraphModel(registry).open(space.db) : undefined),
69
+ [space, selected, registry],
70
+ );
66
71
 
67
72
  const [tree, setTree] = useState<TreeNode>();
68
73
  useEffect(() => {
@@ -75,8 +80,8 @@ export const Tree = <N,>({ space, selected, variant = 'tidy', onNodeClick }: Tre
75
80
  const context = useRef<SVGContext>(null);
76
81
 
77
82
  useEffect(() => {
78
- if (context.current) {
79
- const { width, height } = context.current.size!;
83
+ if (context.current?.size) {
84
+ const { width, height } = context.current.size;
80
85
  const size = Math.min(width, height);
81
86
  const radius = size * 0.4;
82
87
  const options = {
@@ -100,7 +105,7 @@ export const Tree = <N,>({ space, selected, variant = 'tidy', onNodeClick }: Tre
100
105
  }, [context.current, tree]);
101
106
 
102
107
  return (
103
- <div onClick={() => onNodeClick?.()}>
108
+ <div className='grow' onClick={() => onNodeClick?.()}>
104
109
  <SVG.Root ref={context} />
105
110
  </div>
106
111
  );
@@ -2,7 +2,7 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import { Key } from '@dxos/echo';
5
+ import { Key, Obj } from '@dxos/echo';
6
6
  import { range } from '@dxos/util';
7
7
 
8
8
  import { Tree, type TreeNodeType } from '../types';
@@ -16,7 +16,9 @@ const random = (min: number, max: number) => Math.floor(Math.random() * (max - m
16
16
  */
17
17
  export const createTree = (spec: NumberOrNumberArray[] = [], createText?: () => string): Tree => {
18
18
  const tree = new Tree();
19
- tree.root.data = { text: 'root' };
19
+ Obj.update(tree.tree, () => {
20
+ tree.root.data = { text: 'root' };
21
+ });
20
22
 
21
23
  const createNodes = (parent: TreeNodeType, spec: NumberOrNumberArray = 0): TreeNodeType[] => {
22
24
  const count = Array.isArray(spec) ? random(spec[0], spec[1]) : spec;
@@ -5,14 +5,13 @@
5
5
  import { describe, test } from 'vitest';
6
6
 
7
7
  import { Obj, Ref } from '@dxos/echo';
8
- import { faker } from '@dxos/random';
9
- import { DataType } from '@dxos/schema';
8
+ import { random } from '@dxos/random';
9
+ import { Task } from '@dxos/types';
10
10
 
11
11
  import { createTree } from '../testing';
12
-
13
12
  import { type Tree } from './tree';
14
13
 
15
- faker.seed(0);
14
+ random.seed(0);
16
15
 
17
16
  const print = (tree: Tree) => {
18
17
  let count = 0;
@@ -124,11 +123,13 @@ describe('tree', () => {
124
123
  });
125
124
 
126
125
  test('task', ({ expect }) => {
127
- const task = Obj.make(DataType.Task, { title: 'Test task.' });
126
+ const task = Obj.make(Task.Task, { title: 'Test task.' });
128
127
  expect(task.title).to.eq('Test task.');
129
128
 
130
129
  const tree = createTree();
131
130
  const node = tree.addNode(tree.root);
132
- node.ref = Ref.make(task);
131
+ Obj.update(tree.tree, () => {
132
+ node.ref = Ref.make(task);
133
+ });
133
134
  });
134
135
  });