@dxos/plugin-explorer 0.8.4-main.f9ba587 → 0.8.4-main.fcfe5033a5

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 (145) 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/chunk-LSUP47BZ.mjs +24 -0
  4. package/dist/lib/browser/chunk-LSUP47BZ.mjs.map +7 -0
  5. package/dist/lib/browser/index.mjs +11347 -83
  6. package/dist/lib/browser/index.mjs.map +4 -4
  7. package/dist/lib/browser/meta.json +1 -1
  8. package/dist/lib/browser/meta.mjs +2 -3
  9. package/dist/lib/browser/types/index.mjs +66 -6
  10. package/dist/lib/browser/types/index.mjs.map +4 -4
  11. package/dist/lib/node-esm/{chunk-PIAXA43R.mjs → chunk-EN3JZNEY.mjs} +8 -5
  12. package/dist/lib/node-esm/chunk-EN3JZNEY.mjs.map +7 -0
  13. package/dist/lib/node-esm/chunk-HSLMI22Q.mjs +11 -0
  14. package/dist/lib/node-esm/chunk-HSLMI22Q.mjs.map +7 -0
  15. package/dist/lib/node-esm/index.mjs +11347 -83
  16. package/dist/lib/node-esm/index.mjs.map +4 -4
  17. package/dist/lib/node-esm/meta.json +1 -1
  18. package/dist/lib/node-esm/meta.mjs +2 -3
  19. package/dist/lib/node-esm/types/index.mjs +66 -6
  20. package/dist/lib/node-esm/types/index.mjs.map +4 -4
  21. package/dist/types/src/ExplorerPlugin.d.ts +2 -1
  22. package/dist/types/src/ExplorerPlugin.d.ts.map +1 -1
  23. package/dist/types/src/capabilities/index.d.ts +2 -2
  24. package/dist/types/src/capabilities/index.d.ts.map +1 -1
  25. package/dist/types/src/capabilities/react-surface.d.ts +3 -2
  26. package/dist/types/src/capabilities/react-surface.d.ts.map +1 -1
  27. package/dist/types/src/components/Chart/Chart.d.ts.map +1 -1
  28. package/dist/types/src/components/Chart/Chart.stories.d.ts +8 -4
  29. package/dist/types/src/components/Chart/Chart.stories.d.ts.map +1 -1
  30. package/dist/types/src/components/Globe/Globe.d.ts.map +1 -1
  31. package/dist/types/src/components/Globe/Globe.stories.d.ts +8 -4
  32. package/dist/types/src/components/Globe/Globe.stories.d.ts.map +1 -1
  33. package/dist/types/src/components/Graph/D3ForceGraph.d.ts +6 -5
  34. package/dist/types/src/components/Graph/D3ForceGraph.d.ts.map +1 -1
  35. package/dist/types/src/components/Graph/D3ForceGraph.stories.d.ts +15 -4
  36. package/dist/types/src/components/Graph/D3ForceGraph.stories.d.ts.map +1 -1
  37. package/dist/types/src/components/Graph/ForceGraph.stories.d.ts +13 -4
  38. package/dist/types/src/components/Graph/ForceGraph.stories.d.ts.map +1 -1
  39. package/dist/types/src/components/Graph/adapter.d.ts +1 -1
  40. package/dist/types/src/components/Graph/adapter.d.ts.map +1 -1
  41. package/dist/types/src/components/Graph/testing.d.ts +1 -1
  42. package/dist/types/src/components/Graph/testing.d.ts.map +1 -1
  43. package/dist/types/src/components/Tree/Tree.d.ts.map +1 -1
  44. package/dist/types/src/components/Tree/Tree.stories.d.ts +13 -16
  45. package/dist/types/src/components/Tree/Tree.stories.d.ts.map +1 -1
  46. package/dist/types/src/components/Tree/testing/generator.d.ts.map +1 -1
  47. package/dist/types/src/components/Tree/types/tree.d.ts +19 -17
  48. package/dist/types/src/components/Tree/types/tree.d.ts.map +1 -1
  49. package/dist/types/src/components/Tree/types/types.d.ts +1 -1
  50. package/dist/types/src/components/Tree/types/types.d.ts.map +1 -1
  51. package/dist/types/src/components/index.d.ts +0 -4
  52. package/dist/types/src/components/index.d.ts.map +1 -1
  53. package/dist/types/src/containers/ExplorerContainer/ExplorerContainer.d.ts +6 -0
  54. package/dist/types/src/containers/ExplorerContainer/ExplorerContainer.d.ts.map +1 -0
  55. package/dist/types/src/containers/ExplorerContainer/index.d.ts +2 -0
  56. package/dist/types/src/containers/ExplorerContainer/index.d.ts.map +1 -0
  57. package/dist/types/src/containers/index.d.ts +3 -0
  58. package/dist/types/src/containers/index.d.ts.map +1 -0
  59. package/dist/types/src/hooks/useGraphModel.d.ts +2 -2
  60. package/dist/types/src/hooks/useGraphModel.d.ts.map +1 -1
  61. package/dist/types/src/meta.d.ts +2 -3
  62. package/dist/types/src/meta.d.ts.map +1 -1
  63. package/dist/types/src/translations.d.ts +34 -11
  64. package/dist/types/src/translations.d.ts.map +1 -1
  65. package/dist/types/src/types/ExplorerAction.d.ts +6 -0
  66. package/dist/types/src/types/ExplorerAction.d.ts.map +1 -0
  67. package/dist/types/src/types/Graph.d.ts +23 -0
  68. package/dist/types/src/types/Graph.d.ts.map +1 -0
  69. package/dist/types/src/types/index.d.ts +2 -2
  70. package/dist/types/src/types/index.d.ts.map +1 -1
  71. package/dist/types/tsconfig.tsbuildinfo +1 -1
  72. package/package.json +62 -44
  73. package/src/ExplorerPlugin.tsx +43 -53
  74. package/src/capabilities/index.ts +2 -3
  75. package/src/capabilities/react-surface.tsx +24 -15
  76. package/src/components/Chart/Chart.stories.tsx +9 -8
  77. package/src/components/Chart/Chart.tsx +1 -1
  78. package/src/components/Globe/Globe.stories.tsx +13 -12
  79. package/src/components/Globe/Globe.tsx +1 -1
  80. package/src/components/Graph/D3ForceGraph.stories.tsx +47 -28
  81. package/src/components/Graph/D3ForceGraph.tsx +83 -76
  82. package/src/components/Graph/ForceGraph.stories.tsx +47 -28
  83. package/src/components/Graph/ForceGraph.tsx +5 -5
  84. package/src/components/Graph/adapter.ts +14 -8
  85. package/src/components/Graph/testing.ts +13 -10
  86. package/src/components/Tree/Tree.stories.tsx +34 -25
  87. package/src/components/Tree/Tree.tsx +11 -6
  88. package/src/components/Tree/testing/generator.ts +4 -2
  89. package/src/components/Tree/types/tree.test.ts +9 -7
  90. package/src/components/Tree/types/tree.ts +42 -21
  91. package/src/components/Tree/types/types.ts +1 -1
  92. package/src/components/index.ts +0 -4
  93. package/src/containers/ExplorerContainer/ExplorerContainer.tsx +53 -0
  94. package/src/containers/ExplorerContainer/index.ts +5 -0
  95. package/src/containers/index.ts +7 -0
  96. package/src/hooks/useGraphModel.ts +19 -11
  97. package/src/meta.ts +9 -6
  98. package/src/translations.ts +17 -10
  99. package/src/types/ExplorerAction.ts +20 -0
  100. package/src/types/Graph.ts +49 -0
  101. package/src/types/index.ts +2 -2
  102. package/src/typings.d.ts +8 -0
  103. package/dist/lib/browser/ExplorerContainer-WYPM7YXU.mjs +0 -37
  104. package/dist/lib/browser/ExplorerContainer-WYPM7YXU.mjs.map +0 -7
  105. package/dist/lib/browser/chunk-4UVJNXCE.mjs +0 -11329
  106. package/dist/lib/browser/chunk-4UVJNXCE.mjs.map +0 -7
  107. package/dist/lib/browser/chunk-EF4BFHTI.mjs +0 -38
  108. package/dist/lib/browser/chunk-EF4BFHTI.mjs.map +0 -7
  109. package/dist/lib/browser/chunk-OAOY7SHY.mjs +0 -30
  110. package/dist/lib/browser/chunk-OAOY7SHY.mjs.map +0 -7
  111. package/dist/lib/browser/chunk-UL5EDJPE.mjs +0 -21
  112. package/dist/lib/browser/chunk-UL5EDJPE.mjs.map +0 -7
  113. package/dist/lib/browser/chunk-ZZX52LNU.mjs +0 -187
  114. package/dist/lib/browser/chunk-ZZX52LNU.mjs.map +0 -7
  115. package/dist/lib/browser/intent-resolver-XH2UO2FM.mjs +0 -24
  116. package/dist/lib/browser/intent-resolver-XH2UO2FM.mjs.map +0 -7
  117. package/dist/lib/browser/react-surface-5ENPAK3V.mjs +0 -31
  118. package/dist/lib/browser/react-surface-5ENPAK3V.mjs.map +0 -7
  119. package/dist/lib/node-esm/ExplorerContainer-S5GL733T.mjs +0 -38
  120. package/dist/lib/node-esm/ExplorerContainer-S5GL733T.mjs.map +0 -7
  121. package/dist/lib/node-esm/chunk-6M6W7DKH.mjs +0 -189
  122. package/dist/lib/node-esm/chunk-6M6W7DKH.mjs.map +0 -7
  123. package/dist/lib/node-esm/chunk-HCV3J2HM.mjs +0 -11331
  124. package/dist/lib/node-esm/chunk-HCV3J2HM.mjs.map +0 -7
  125. package/dist/lib/node-esm/chunk-PIAXA43R.mjs.map +0 -7
  126. package/dist/lib/node-esm/chunk-PPBUWC7F.mjs +0 -32
  127. package/dist/lib/node-esm/chunk-PPBUWC7F.mjs.map +0 -7
  128. package/dist/lib/node-esm/chunk-VKCOKQRG.mjs +0 -39
  129. package/dist/lib/node-esm/chunk-VKCOKQRG.mjs.map +0 -7
  130. package/dist/lib/node-esm/intent-resolver-AGBBNA67.mjs +0 -25
  131. package/dist/lib/node-esm/intent-resolver-AGBBNA67.mjs.map +0 -7
  132. package/dist/lib/node-esm/react-surface-4MDNPYQ6.mjs +0 -32
  133. package/dist/lib/node-esm/react-surface-4MDNPYQ6.mjs.map +0 -7
  134. package/dist/types/src/capabilities/intent-resolver.d.ts +0 -4
  135. package/dist/types/src/capabilities/intent-resolver.d.ts.map +0 -1
  136. package/dist/types/src/components/ExplorerContainer.d.ts +0 -9
  137. package/dist/types/src/components/ExplorerContainer.d.ts.map +0 -1
  138. package/dist/types/src/types/schema.d.ts +0 -12
  139. package/dist/types/src/types/schema.d.ts.map +0 -1
  140. package/dist/types/src/types/types.d.ts +0 -18
  141. package/dist/types/src/types/types.d.ts.map +0 -1
  142. package/src/capabilities/intent-resolver.ts +0 -19
  143. package/src/components/ExplorerContainer.tsx +0 -36
  144. package/src/types/schema.ts +0 -16
  145. package/src/types/types.ts +0 -21
@@ -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
- import { type SpaceGraphNode, type SpaceGraphModel, type SpaceGraphEdge } from '@dxos/schema';
20
-
18
+ import { type SpaceGraphEdge, type SpaceGraphModel, type SpaceGraphNode } from '@dxos/schema';
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,63 +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 { withLayout, withTheme, render } from '@dxos/storybook-utils';
18
+ import { withRegistry } from '@dxos/storybook-utils';
19
+ import { HasRelationship, Organization, Person, Pipeline } from '@dxos/types';
20
+
21
+ import { useGraphModel } from '#hooks';
22
+ import { Graph } from '#types';
18
23
 
19
24
  import { ForceGraph } from './ForceGraph';
20
25
  import { generate } from './testing';
21
- import { useGraphModel } from '../../hooks';
22
- import { ViewType } from '../../types';
23
26
 
24
- const generator = faker as any as ValueGenerator;
27
+ const generator = random as any as ValueGenerator;
25
28
 
26
- faker.seed(1);
29
+ random.seed(1);
27
30
 
28
31
  const DefaultStory = () => {
29
32
  const client = useClient();
30
33
  const [space, setSpace] = useState<Space>();
31
- const [view, setView] = useState<ViewType>();
32
- useEffect(() => {
33
- const space = client.spaces.default;
34
+ const [graph, setGraph] = useState<Graph.Graph>();
35
+
36
+ useAsyncEffect(async () => {
37
+ const space = client.spaces.get()[0];
34
38
  void generate(space, generator);
35
- 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);
36
42
  setSpace(space);
37
- setView(view);
38
- }, []);
43
+ setGraph(graph);
44
+ }, [client]);
39
45
 
40
46
  const model = useGraphModel(space);
41
- if (!model || !space || !view) {
42
- return null;
47
+ if (!model || !space || !graph) {
48
+ return <Loading data={{ model: !!model, space: !!space, graph: !!graph }} />;
43
49
  }
44
50
 
45
51
  return <ForceGraph model={model} />;
46
52
  };
47
53
 
48
- const meta: Meta = {
49
- title: 'plugins/plugin-explorer/ForceGraph',
54
+ const meta = {
55
+ title: 'plugins/plugin-explorer/components/ForceGraph',
50
56
  component: ForceGraph,
51
- render: render(DefaultStory),
57
+ render: DefaultStory,
52
58
  decorators: [
59
+ withRegistry,
60
+ withTheme(),
61
+ withLayout(),
53
62
  withClientProvider({
54
63
  createSpace: true,
55
- 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
+ ],
56
72
  }),
57
- withTheme,
58
- withLayout({ fullscreen: true }),
59
73
  ],
60
- };
74
+ parameters: {
75
+ layout: 'fullscreen',
76
+ },
77
+ } satisfies Meta<typeof ForceGraph>;
61
78
 
62
79
  export default meta;
63
80
 
64
- export const Default = {};
81
+ type Story = StoryObj<typeof meta>;
82
+
83
+ export const Default: Story = {};
@@ -7,7 +7,7 @@ import NativeForceGraph from 'force-graph';
7
7
  import React, { type FC, useEffect, useRef, useState } from 'react';
8
8
  import { useResizeDetector } from 'react-resize-detector';
9
9
 
10
- import { filterObjectsSync, type SearchResult } from '@dxos/plugin-search';
10
+ import { type SearchResult, filterObjectsSync } from '@dxos/plugin-search';
11
11
  import { type SpaceGraphModel } from '@dxos/schema';
12
12
 
13
13
  import { GraphAdapter } from './adapter';
@@ -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>();
@@ -39,7 +39,7 @@ export const ForceGraph: FC<ForceGraphProps> = ({ model, match }) => {
39
39
  forceGraph.current = new NativeForceGraph(rootRef.current)
40
40
  // https://github.com/vasturiano/force-graph?tab=readme-ov-file#node-styling
41
41
  .nodeRelSize(6)
42
- .nodeLabel((node: any) => (node.type === 'schema' ? node.data.typename : node.data.label ?? node.id))
42
+ .nodeLabel((node: any) => (node.type === 'schema' ? node.data.typename : (node.data.label ?? node.id)))
43
43
  .nodeAutoColorBy((node: any) => (node.type === 'schema' ? 'schema' : node.data.typename))
44
44
 
45
45
  // https://github.com/vasturiano/force-graph?tab=readme-ov-file#link-styling
@@ -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() {
@@ -3,17 +3,17 @@
3
3
  //
4
4
 
5
5
  import { type Space } from '@dxos/client/echo';
6
- import { Query, Relation, type Obj } from '@dxos/echo';
7
- import { DataType } from '@dxos/schema';
8
- import { createObjectFactory, type ValueGenerator, type TypeSpec } from '@dxos/schema/testing';
6
+ import { type Obj, Query, Relation } from '@dxos/echo';
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,27 +2,30 @@
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
- import { TreeType, Tree as TreeModel } from './types';
16
+ import { Tree as TreeModel, TreeType } from './types';
17
17
 
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';
@@ -10,7 +11,7 @@ import { SVG, type SVGContext } from '@dxos/react-ui-graph';
10
11
  import { SpaceGraphModel } from '@dxos/schema';
11
12
 
12
13
  import { HierarchicalEdgeBundling, RadialTree, TidyTree } from './layout';
13
- import { mapGraphToTreeData, type TreeNode } from './types';
14
+ import { type TreeNode, mapGraphToTreeData } from './types';
14
15
 
15
16
  // TODO(burdon): Create dge bundling graph using d3.hierarchy.
16
17
  // https://observablehq.com/@d3/hierarchical-edge-bundling?intent=fork
@@ -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.change(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,13 +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
- import { type Tree } from './tree';
12
11
  import { createTree } from '../testing';
12
+ import { type Tree } from './tree';
13
13
 
14
- faker.seed(0);
14
+ random.seed(0);
15
15
 
16
16
  const print = (tree: Tree) => {
17
17
  let count = 0;
@@ -123,11 +123,13 @@ describe('tree', () => {
123
123
  });
124
124
 
125
125
  test('task', ({ expect }) => {
126
- const task = Obj.make(DataType.Task, { text: 'Test task.' });
127
- expect(task.text).to.eq('Test task.');
126
+ const task = Obj.make(Task.Task, { title: 'Test task.' });
127
+ expect(task.title).to.eq('Test task.');
128
128
 
129
129
  const tree = createTree();
130
130
  const node = tree.addNode(tree.root);
131
- node.ref = Ref.make(task);
131
+ Obj.change(tree.tree, () => {
132
+ node.ref = Ref.make(task);
133
+ });
132
134
  });
133
135
  });