@dxos/plugin-explorer 0.8.4-main.422d1c7879 → 0.8.4-main.4f23b4e393

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 (130) hide show
  1. package/dist/lib/neutral/ExplorerContainer-BOXGUSRG.mjs +41 -0
  2. package/dist/lib/neutral/ExplorerContainer-BOXGUSRG.mjs.map +7 -0
  3. package/dist/lib/neutral/ExplorerPlugin.mjs +26 -0
  4. package/dist/lib/neutral/ExplorerPlugin.mjs.map +7 -0
  5. package/dist/lib/neutral/capabilities/index.mjs +11 -0
  6. package/dist/lib/neutral/capabilities/index.mjs.map +7 -0
  7. package/dist/lib/{browser/types/index.mjs → neutral/chunk-7SPMPHRS.mjs} +10 -8
  8. package/dist/lib/neutral/chunk-7SPMPHRS.mjs.map +7 -0
  9. package/dist/lib/{browser/chunk-LSUP47BZ.mjs → neutral/chunk-HPIS2WXY.mjs} +1 -1
  10. package/dist/lib/{browser/chunk-LSUP47BZ.mjs.map → neutral/chunk-HPIS2WXY.mjs.map} +2 -2
  11. package/dist/lib/{browser → neutral/components}/index.mjs +140 -240
  12. package/dist/lib/{node-esm → neutral/components}/index.mjs.map +4 -4
  13. package/dist/lib/neutral/containers/index.mjs +9 -0
  14. package/dist/lib/neutral/containers/index.mjs.map +7 -0
  15. package/dist/lib/neutral/create-object-F6TKVAGV.mjs +39 -0
  16. package/dist/lib/neutral/create-object-F6TKVAGV.mjs.map +7 -0
  17. package/dist/lib/neutral/hooks/index.mjs +45 -0
  18. package/dist/lib/neutral/hooks/index.mjs.map +7 -0
  19. package/dist/lib/neutral/index.mjs +14 -0
  20. package/dist/lib/neutral/meta.json +1 -0
  21. package/dist/lib/{browser → neutral}/meta.mjs +1 -1
  22. package/dist/lib/neutral/plugin.mjs +12 -0
  23. package/dist/lib/neutral/plugin.mjs.map +7 -0
  24. package/dist/lib/neutral/react-surface-U3JEY7V7.mjs +26 -0
  25. package/dist/lib/neutral/react-surface-U3JEY7V7.mjs.map +7 -0
  26. package/dist/lib/neutral/translations.mjs +33 -0
  27. package/dist/lib/neutral/translations.mjs.map +7 -0
  28. package/dist/lib/neutral/types/index.mjs +10 -0
  29. package/dist/types/data/cities.d.ts +4 -4
  30. package/dist/types/data/cities.d.ts.map +1 -1
  31. package/dist/types/data/countries-110m.d.ts +19 -22
  32. package/dist/types/data/countries-110m.d.ts.map +1 -1
  33. package/dist/types/src/ExplorerPlugin.d.ts +1 -0
  34. package/dist/types/src/ExplorerPlugin.d.ts.map +1 -1
  35. package/dist/types/src/ExplorerPlugin.test.d.ts +2 -0
  36. package/dist/types/src/ExplorerPlugin.test.d.ts.map +1 -0
  37. package/dist/types/src/capabilities/create-object.d.ts +11 -0
  38. package/dist/types/src/capabilities/create-object.d.ts.map +1 -0
  39. package/dist/types/src/capabilities/index.d.ts +6 -0
  40. package/dist/types/src/capabilities/index.d.ts.map +1 -1
  41. package/dist/types/src/capabilities/react-surface.d.ts.map +1 -1
  42. package/dist/types/src/components/Chart/Chart.d.ts.map +1 -1
  43. package/dist/types/src/components/Chart/Chart.stories.d.ts +4 -1
  44. package/dist/types/src/components/Chart/Chart.stories.d.ts.map +1 -1
  45. package/dist/types/src/components/Globe/Globe.d.ts.map +1 -1
  46. package/dist/types/src/components/Globe/Globe.stories.d.ts +5 -2
  47. package/dist/types/src/components/Globe/Globe.stories.d.ts.map +1 -1
  48. package/dist/types/src/components/Graph/CanvasForceGraph.d.ts +13 -0
  49. package/dist/types/src/components/Graph/CanvasForceGraph.d.ts.map +1 -0
  50. package/dist/types/src/components/Graph/{D3ForceGraph.stories.d.ts → CanvasForceGraph.stories.d.ts} +3 -3
  51. package/dist/types/src/components/Graph/CanvasForceGraph.stories.d.ts.map +1 -0
  52. package/dist/types/src/components/Graph/ForceGraph.d.ts +12 -5
  53. package/dist/types/src/components/Graph/ForceGraph.d.ts.map +1 -1
  54. package/dist/types/src/components/Graph/ForceGraph.stories.d.ts +3 -1
  55. package/dist/types/src/components/Graph/ForceGraph.stories.d.ts.map +1 -1
  56. package/dist/types/src/components/Graph/{adapter.d.ts → graph-adapter.d.ts} +1 -1
  57. package/dist/types/src/components/Graph/graph-adapter.d.ts.map +1 -0
  58. package/dist/types/src/components/Graph/index.d.ts +1 -1
  59. package/dist/types/src/components/Graph/index.d.ts.map +1 -1
  60. package/dist/types/src/components/Graph/testing.d.ts.map +1 -1
  61. package/dist/types/src/components/Tree/Tree.d.ts.map +1 -1
  62. package/dist/types/src/components/Tree/Tree.stories.d.ts +5 -1
  63. package/dist/types/src/components/Tree/Tree.stories.d.ts.map +1 -1
  64. package/dist/types/src/components/Tree/layout/HierarchicalEdgeBundling.d.ts.map +1 -1
  65. package/dist/types/src/components/Tree/layout/RadialTree.d.ts.map +1 -1
  66. package/dist/types/src/components/Tree/layout/TidyTree.d.ts.map +1 -1
  67. package/dist/types/src/components/Tree/testing/generator.d.ts.map +1 -1
  68. package/dist/types/src/components/Tree/types/tree.d.ts +6 -6
  69. package/dist/types/src/components/Tree/types/tree.d.ts.map +1 -1
  70. package/dist/types/src/components/Tree/types/types.d.ts.map +1 -1
  71. package/dist/types/src/components/plot.d.ts.map +1 -1
  72. package/dist/types/src/containers/ExplorerContainer/ExplorerContainer.d.ts +1 -1
  73. package/dist/types/src/containers/ExplorerContainer/ExplorerContainer.d.ts.map +1 -1
  74. package/dist/types/src/hooks/useGraphModel.d.ts +2 -2
  75. package/dist/types/src/hooks/useGraphModel.d.ts.map +1 -1
  76. package/dist/types/src/index.d.ts +1 -3
  77. package/dist/types/src/index.d.ts.map +1 -1
  78. package/dist/types/src/plugin.d.ts +3 -0
  79. package/dist/types/src/plugin.d.ts.map +1 -0
  80. package/dist/types/src/translations.d.ts +17 -17
  81. package/dist/types/src/translations.d.ts.map +1 -1
  82. package/dist/types/src/types/Graph.d.ts +3 -4
  83. package/dist/types/src/types/Graph.d.ts.map +1 -1
  84. package/dist/types/tsconfig.tsbuildinfo +1 -1
  85. package/package.json +94 -51
  86. package/src/ExplorerPlugin.test.ts +26 -0
  87. package/src/ExplorerPlugin.tsx +6 -35
  88. package/src/capabilities/create-object.ts +36 -0
  89. package/src/capabilities/index.ts +1 -0
  90. package/src/components/Chart/Chart.stories.tsx +14 -20
  91. package/src/components/Globe/Globe.stories.tsx +17 -19
  92. package/src/components/Graph/CanvasForceGraph.stories.tsx +83 -0
  93. package/src/components/Graph/CanvasForceGraph.tsx +124 -0
  94. package/src/components/Graph/ForceGraph.stories.tsx +68 -36
  95. package/src/components/Graph/ForceGraph.tsx +104 -85
  96. package/src/components/Graph/index.ts +1 -1
  97. package/src/components/Tree/Tree.stories.tsx +40 -35
  98. package/src/components/Tree/testing/generator.ts +1 -1
  99. package/src/components/Tree/types/tree.test.ts +1 -1
  100. package/src/components/Tree/types/tree.ts +9 -9
  101. package/src/containers/ExplorerContainer/ExplorerContainer.tsx +10 -13
  102. package/src/hooks/useGraphModel.ts +10 -6
  103. package/src/index.ts +1 -4
  104. package/src/plugin.ts +9 -0
  105. package/src/translations.ts +1 -1
  106. package/src/types/ExplorerAction.ts +1 -1
  107. package/src/types/Graph.ts +2 -3
  108. package/dist/lib/browser/index.mjs.map +0 -7
  109. package/dist/lib/browser/meta.json +0 -1
  110. package/dist/lib/browser/types/index.mjs.map +0 -7
  111. package/dist/lib/node-esm/chunk-EN3JZNEY.mjs +0 -26
  112. package/dist/lib/node-esm/chunk-EN3JZNEY.mjs.map +0 -7
  113. package/dist/lib/node-esm/chunk-HSLMI22Q.mjs +0 -11
  114. package/dist/lib/node-esm/index.mjs +0 -11375
  115. package/dist/lib/node-esm/meta.json +0 -1
  116. package/dist/lib/node-esm/meta.mjs +0 -9
  117. package/dist/lib/node-esm/types/index.mjs +0 -71
  118. package/dist/lib/node-esm/types/index.mjs.map +0 -7
  119. package/dist/types/src/components/Graph/D3ForceGraph.d.ts +0 -15
  120. package/dist/types/src/components/Graph/D3ForceGraph.d.ts.map +0 -1
  121. package/dist/types/src/components/Graph/D3ForceGraph.stories.d.ts.map +0 -1
  122. package/dist/types/src/components/Graph/adapter.d.ts.map +0 -1
  123. package/src/components/Graph/D3ForceGraph.stories.tsx +0 -83
  124. package/src/components/Graph/D3ForceGraph.tsx +0 -108
  125. /package/dist/lib/{browser → neutral}/chunk-J5LGTIGS.mjs +0 -0
  126. /package/dist/lib/{browser → neutral}/chunk-J5LGTIGS.mjs.map +0 -0
  127. /package/dist/lib/{browser/meta.mjs.map → neutral/index.mjs.map} +0 -0
  128. /package/dist/lib/{node-esm → neutral}/meta.mjs.map +0 -0
  129. /package/dist/lib/{node-esm/chunk-HSLMI22Q.mjs.map → neutral/types/index.mjs.map} +0 -0
  130. /package/src/components/Graph/{adapter.ts → graph-adapter.ts} +0 -0
@@ -3,19 +3,23 @@
3
3
  //
4
4
 
5
5
  import { type Meta, type StoryObj } from '@storybook/react-vite';
6
- import React, { useState } from 'react';
6
+ import * as Effect from 'effect/Effect';
7
+ import React, { useCallback, useMemo } from 'react';
7
8
 
8
- import { Type } from '@dxos/echo';
9
- import { View } from '@dxos/echo';
9
+ import { withPluginManager } from '@dxos/app-framework/testing';
10
+ import { Obj, Type, View } from '@dxos/echo';
11
+ import { SelectionModel } from '@dxos/graph';
12
+ import { ClientPlugin } from '@dxos/plugin-client/plugin';
13
+ import { initializeIdentity } from '@dxos/plugin-client/testing';
14
+ import { PreviewPlugin } from '@dxos/plugin-preview/testing';
15
+ import { StorybookPlugin, corePlugins } from '@dxos/plugin-testing';
10
16
  import { random } from '@dxos/random';
11
- import { useClient } from '@dxos/react-client';
12
- import { type Space } from '@dxos/react-client/echo';
13
- import { withClientProvider } from '@dxos/react-client/testing';
14
- import { useAsyncEffect } from '@dxos/react-ui';
17
+ import { useSpaces } from '@dxos/react-client/echo';
18
+ import { DxAnchorActivate } from '@dxos/react-ui';
19
+ import { type GraphProps, type GraphLayoutNode } from '@dxos/react-ui-graph';
15
20
  import { Loading, withLayout, withTheme } from '@dxos/react-ui/testing';
16
- import { ViewModel } from '@dxos/schema';
21
+ import { type SpaceGraphEdge, type SpaceGraphNode, ViewModel } from '@dxos/schema';
17
22
  import { type ValueGenerator } from '@dxos/schema/testing';
18
- import { withRegistry } from '@dxos/storybook-utils';
19
23
  import { HasRelationship, Organization, Person, Pipeline } from '@dxos/types';
20
24
 
21
25
  import { useGraphModel } from '#hooks';
@@ -29,26 +33,39 @@ const generator = random as any as ValueGenerator;
29
33
  random.seed(1);
30
34
 
31
35
  const DefaultStory = () => {
32
- const client = useClient();
33
- const [space, setSpace] = useState<Space>();
34
- const [graph, setGraph] = useState<Graph.Graph>();
36
+ const [space] = useSpaces();
37
+ const model = useGraphModel(space?.db);
35
38
 
36
- useAsyncEffect(async () => {
37
- const space = client.spaces.get()[0];
38
- void generate(space, generator);
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);
42
- setSpace(space);
43
- setGraph(graph);
44
- }, [client]);
39
+ const selection = useMemo(() => new SelectionModel({ mode: 'single' }), []);
45
40
 
46
- const model = useGraphModel(space);
47
- if (!model || !space || !graph) {
48
- return <Loading data={{ model: !!model, space: !!space, graph: !!graph }} />;
41
+ const handleInspect = useCallback<NonNullable<GraphProps<SpaceGraphNode, SpaceGraphEdge>['onInspect']>>(
42
+ (node: GraphLayoutNode<SpaceGraphNode>, event) => {
43
+ const obj = node.data?.data?.object;
44
+ if (!obj) {
45
+ return;
46
+ }
47
+ const dxn = Obj.getDXN(obj)?.toString();
48
+ if (!dxn) {
49
+ return;
50
+ }
51
+ const target = event.target as HTMLElement;
52
+ target.dispatchEvent(
53
+ new DxAnchorActivate({
54
+ dxn,
55
+ label: Obj.getLabel(obj) ?? dxn,
56
+ trigger: target,
57
+ kind: 'card',
58
+ }),
59
+ );
60
+ },
61
+ [],
62
+ );
63
+
64
+ if (!space || !model) {
65
+ return <Loading data={{ space: !!space, model: !!model }} />;
49
66
  }
50
67
 
51
- return <ForceGraph model={model} />;
68
+ return <ForceGraph model={model} selection={selection} onInspect={handleInspect} />;
52
69
  };
53
70
 
54
71
  const meta = {
@@ -56,18 +73,33 @@ const meta = {
56
73
  component: ForceGraph,
57
74
  render: DefaultStory,
58
75
  decorators: [
59
- withRegistry,
60
76
  withTheme(),
61
- withLayout(),
62
- withClientProvider({
63
- createSpace: true,
64
- types: [
65
- Graph.Graph,
66
- View.View,
67
- HasRelationship.HasRelationship,
68
- Organization.Organization,
69
- Pipeline.Pipeline,
70
- Person.Person,
77
+ withLayout({ layout: 'fullscreen' }),
78
+ withPluginManager({
79
+ plugins: [
80
+ ...corePlugins(),
81
+ StorybookPlugin({}),
82
+ ClientPlugin({
83
+ types: [
84
+ Graph.Graph,
85
+ View.View,
86
+ HasRelationship.HasRelationship,
87
+ Organization.Organization,
88
+ Pipeline.Pipeline,
89
+ Person.Person,
90
+ ],
91
+ onClientInitialized: ({ client }) =>
92
+ Effect.gen(function* () {
93
+ const { personalSpace } = yield* initializeIdentity(client);
94
+ yield* Effect.promise(() => generate(personalSpace, generator));
95
+ const { view } = yield* Effect.promise(() =>
96
+ ViewModel.makeFromDatabase({ db: personalSpace.db, typename: Type.getTypename(Graph.Graph) }),
97
+ );
98
+ personalSpace.db.add(Graph.make({ name: 'Test', view }));
99
+ yield* Effect.promise(() => personalSpace.db.flush({ indexes: true }));
100
+ }),
101
+ }),
102
+ PreviewPlugin(),
71
103
  ],
72
104
  }),
73
105
  ],
@@ -2,91 +2,110 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- import { forceLink, forceManyBody } from 'd3';
6
- import NativeForceGraph from 'force-graph';
7
- import React, { type FC, useEffect, useRef, useState } from 'react';
8
- import { useResizeDetector } from 'react-resize-detector';
9
-
10
- import { type SearchResult, filterObjectsSync } from '@dxos/plugin-search';
11
- import { type SpaceGraphModel } from '@dxos/schema';
12
-
13
- import { GraphAdapter } from './adapter';
5
+ import { Atom, useAtomValue } from '@effect-atom/atom-react';
6
+ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
7
+
8
+ import { Obj } from '@dxos/echo';
9
+ import { SelectionModel } from '@dxos/graph';
10
+ import {
11
+ type GraphController,
12
+ GraphForceProjector,
13
+ type GraphLayoutNode,
14
+ type GraphProps,
15
+ SVG,
16
+ type SVGContext,
17
+ } from '@dxos/react-ui-graph';
18
+ import { type SpaceGraphEdge, type SpaceGraphModel, type SpaceGraphNode } from '@dxos/schema';
19
+ import { composable, composableProps, getHashStyles } from '@dxos/ui-theme';
20
+ import '@dxos/react-ui-graph/styles/graph.css';
21
+
22
+ const EMPTY_ATOM = Atom.make<{ nodes: SpaceGraphNode[]; edges: SpaceGraphEdge[] }>({ nodes: [], edges: [] });
14
23
 
15
24
  export type ForceGraphProps = {
16
25
  model?: SpaceGraphModel;
17
- match?: RegExp;
18
- };
19
-
20
- export const ForceGraph: FC<ForceGraphProps> = ({ model, match }) => {
21
- const { ref, width, height } = useResizeDetector({ refreshRate: 200 });
22
- const rootRef = useRef<HTMLDivElement>(null);
23
- const forceGraph = useRef<NativeForceGraph>(null);
24
-
25
- const filteredRef = useRef<SearchResult[]>([]);
26
- filteredRef.current = filterObjectsSync(model?.objects ?? [], match);
27
-
28
- const [data, setData] = useState<GraphAdapter>();
29
- useEffect(() => {
30
- return model?.subscribe((model) => {
31
- setData(new GraphAdapter(model.graph));
32
- });
33
- }, [model]);
34
-
35
- useEffect(() => {
36
- if (rootRef.current) {
37
- // https://github.com/vasturiano/force-graph
38
- // https://github.com/vasturiano/3d-force-graph
39
- forceGraph.current = new NativeForceGraph(rootRef.current)
40
- // https://github.com/vasturiano/force-graph?tab=readme-ov-file#node-styling
41
- .nodeRelSize(6)
42
- .nodeLabel((node: any) => (node.type === 'schema' ? node.data.typename : (node.data.label ?? node.id)))
43
- .nodeAutoColorBy((node: any) => (node.type === 'schema' ? 'schema' : node.data.typename))
44
-
45
- // https://github.com/vasturiano/force-graph?tab=readme-ov-file#link-styling
46
- .linkAutoColorBy((link: any) => link.type);
47
- }
48
-
49
- return () => {
50
- forceGraph.current?.pauseAnimation().graphData({ nodes: [], links: [] });
51
- forceGraph.current = null;
52
- };
53
- }, []);
54
-
55
- useEffect(() => {
56
- if (!data || !width || !height || !forceGraph.current) {
57
- return;
58
- }
59
-
60
- // https://github.com/vasturiano/force-graph?tab=readme-ov-file#container-layout
61
- forceGraph.current
62
- .pauseAnimation()
63
- .width(width)
64
- .height(height)
65
- .onEngineStop(() => {
66
- handleZoomToFit();
67
- })
68
- .onNodeClick((node: any) => {
69
- forceGraph.current?.emitParticle(node);
70
- })
71
-
72
- // https://github.com/vasturiano/force-graph?tab=readme-ov-file#force-engine-d3-force-configuration
73
- // .d3Force('center', forceCenter().strength(0.9))
74
- .d3Force('link', forceLink().distance(160).strength(0.5))
75
- .d3Force('charge', forceManyBody().strength(-30))
76
-
77
- .graphData(data)
78
- .warmupTicks(100)
79
- .cooldownTime(1_000)
80
- .resumeAnimation();
81
- }, [data, width, height, forceGraph.current]);
82
-
83
- const handleZoomToFit = () => {
84
- forceGraph.current?.zoomToFit(400, 40);
85
- };
86
-
87
- return (
88
- <div ref={ref} className='relative grow' onClick={handleZoomToFit}>
89
- <div ref={rootRef} className='absolute inset-0' />
90
- </div>
91
- );
92
- };
26
+ grid?: boolean;
27
+ selection?: SelectionModel;
28
+ onInspect?: GraphProps<SpaceGraphNode, SpaceGraphEdge>['onInspect'];
29
+ } & Pick<GraphProps, 'drag'>;
30
+
31
+ export const ForceGraph = composable<HTMLDivElement, ForceGraphProps>(
32
+ ({ model, selection: selectionProp, grid, drag, onInspect, ...props }, forwardedRef) => {
33
+ // TODO(wittjosiah): This should go into Graph.tsx but for some reason doesn't work.
34
+ useAtomValue(model?.graphAtom ?? EMPTY_ATOM);
35
+
36
+ const graph = useRef<GraphController>(null);
37
+ const selection = useMemo(() => selectionProp ?? new SelectionModel(), [selectionProp]);
38
+ useEffect(() => {
39
+ const unsubscribe = selection.subscribe(() => graph.current?.repaint());
40
+ return unsubscribe;
41
+ }, [selection]);
42
+
43
+ const svgRef = useRef<SVGContext>(null);
44
+ const [projector, setProjector] = useState<GraphForceProjector>();
45
+ useEffect(() => {
46
+ if (svgRef.current) {
47
+ setProjector(
48
+ new GraphForceProjector(svgRef.current, {
49
+ attributes: {
50
+ // TODO(burdon): Check type (currently assumes Employee property).
51
+ // Edge shouldn't contribute to force if it's not active.
52
+ linkForce: (edge) => edge.data?.object?.active !== false,
53
+ },
54
+ forces: {
55
+ point: {
56
+ strength: 0.01,
57
+ },
58
+ },
59
+ }),
60
+ );
61
+ }
62
+ // SVG.Graph owns projector start/stop; nothing to clean up here.
63
+ }, []);
64
+
65
+ const handleSelect = useCallback<NonNullable<GraphProps['onSelect']>>(
66
+ (node) => {
67
+ if (selection.contains(node.id)) {
68
+ selection.remove(node.id);
69
+ } else {
70
+ selection.add(node.id);
71
+ }
72
+ },
73
+ [selection],
74
+ );
75
+
76
+ return (
77
+ <div {...composableProps(props, { classNames: 'dx-container' })} ref={forwardedRef}>
78
+ <SVG.Root ref={svgRef}>
79
+ <SVG.Markers />
80
+ {grid && <SVG.Grid axis />}
81
+ <SVG.Zoom extent={[1 / 2, 2]}>
82
+ <SVG.Graph<SpaceGraphNode, SpaceGraphEdge>
83
+ drag={drag}
84
+ ref={graph}
85
+ model={model}
86
+ projector={projector}
87
+ labels={{
88
+ text: (node) => node.data?.data.label ?? node.id,
89
+ }}
90
+ attributes={{
91
+ node: (node: GraphLayoutNode<SpaceGraphNode>) => {
92
+ const obj = node.data?.data.object;
93
+ return {
94
+ data: {
95
+ color: getHashStyles(obj && Obj.getTypename(obj))?.hue,
96
+ },
97
+ classes: {
98
+ 'dx-selected': selection.contains(node.id),
99
+ },
100
+ };
101
+ },
102
+ }}
103
+ onSelect={handleSelect}
104
+ onInspect={onInspect}
105
+ />
106
+ </SVG.Zoom>
107
+ </SVG.Root>
108
+ </div>
109
+ );
110
+ },
111
+ );
@@ -2,5 +2,5 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- export * from './D3ForceGraph';
5
+ export * from './CanvasForceGraph';
6
6
  export * from './ForceGraph';
@@ -3,53 +3,58 @@
3
3
  //
4
4
 
5
5
  import { type Meta, type StoryObj } from '@storybook/react-vite';
6
- import React, { useEffect, useState } from 'react';
7
-
8
- import { invariant } from '@dxos/invariant';
6
+ import * as Effect from 'effect/Effect';
7
+ import React from 'react';
8
+
9
+ import { withPluginManager } from '@dxos/app-framework/testing';
10
+ import { Filter } from '@dxos/echo';
11
+ import { ClientPlugin } from '@dxos/plugin-client/plugin';
12
+ import { initializeIdentity } from '@dxos/plugin-client/testing';
13
+ import { corePlugins } from '@dxos/plugin-testing';
9
14
  import { random } from '@dxos/random';
10
- import { useClient } from '@dxos/react-client';
11
- import { type ClientRepeatedComponentProps, ClientRepeater } from '@dxos/react-client/testing';
12
- import { withLayout, withTheme } from '@dxos/react-ui/testing';
13
- import { withRegistry } from '@dxos/storybook-utils';
15
+ import { useQuery, useSpaces } from '@dxos/react-client/echo';
16
+ import { Loading, withLayout, withTheme } from '@dxos/react-ui/testing';
14
17
 
18
+ import { createTree } from './testing';
15
19
  import { Tree, type TreeComponentProps } from './Tree';
16
- import { Tree as TreeModel, TreeType } from './types';
17
-
18
- // TODO(burdon): Storybook for Graph/Tree/Plot (generics); incl. GraphModel.
19
- // TODO(burdon): Type for all Explorer components (Space, Object, Query, etc.) incl.
20
+ import { TreeType } from './types';
20
21
 
21
22
  random.seed(1);
22
23
 
23
- type ComponentProps = ClientRepeatedComponentProps & { type?: TreeComponentProps<any>['variant'] };
24
-
25
- const Component = ({ type }: ComponentProps) => {
26
- const client = useClient();
27
- const space = client.spaces.get()[0];
28
- invariant(space, 'Tree story requires at least one space');
29
- const [object, setObject] = useState<TreeType>();
30
- useEffect(() => {
31
- setTimeout(() => {
32
- const tree = space.db.add(TreeModel.create());
33
- setObject(tree);
34
- });
35
- }, []);
24
+ type StoryArgs = { variant?: TreeComponentProps<any>['variant'] };
36
25
 
37
- if (!object) {
38
- return null;
26
+ const DefaultStory = ({ variant }: StoryArgs) => {
27
+ const [space] = useSpaces();
28
+ const [tree] = useQuery(space?.db, Filter.type(TreeType));
29
+ if (!space || !tree) {
30
+ return <Loading data={{ space: !!space, tree: !!tree }} />;
39
31
  }
40
32
 
41
- return <Tree space={space} selected={object?.id} variant={type} />;
42
- };
43
-
44
- const DefaultStory = () => {
45
- return <ClientRepeater component={Component} types={[TreeType]} createSpace />;
33
+ return <Tree space={space} selected={tree.id} variant={variant} />;
46
34
  };
47
35
 
48
36
  const meta = {
49
37
  title: 'plugins/plugin-explorer/components/Tree',
50
38
  component: Tree as any,
51
39
  render: DefaultStory,
52
- decorators: [withRegistry, withTheme(), withLayout()],
40
+ decorators: [
41
+ withTheme(),
42
+ withLayout({ layout: 'fullscreen' }),
43
+ withPluginManager({
44
+ plugins: [
45
+ ...corePlugins(),
46
+ ClientPlugin({
47
+ types: [TreeType],
48
+ onClientInitialized: ({ client }) =>
49
+ Effect.gen(function* () {
50
+ const { personalSpace } = yield* initializeIdentity(client);
51
+ const tree = createTree([3, [2, 4], [1, 3]]).tree;
52
+ personalSpace.db.add(tree);
53
+ }),
54
+ }),
55
+ ],
56
+ }),
57
+ ],
53
58
  parameters: {
54
59
  layout: 'fullscreen',
55
60
  },
@@ -61,18 +66,18 @@ type Story = StoryObj<typeof meta>;
61
66
 
62
67
  export const Tidy: Story = {
63
68
  args: {
64
- type: 'tidy',
69
+ variant: 'tidy',
65
70
  },
66
71
  };
67
72
 
68
73
  export const Radial: Story = {
69
74
  args: {
70
- type: 'radial',
75
+ variant: 'radial',
71
76
  },
72
77
  };
73
78
 
74
79
  export const Edge: Story = {
75
80
  args: {
76
- type: 'edge',
81
+ variant: 'edge',
77
82
  },
78
83
  };
@@ -16,7 +16,7 @@ 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
- Obj.change(tree.tree, () => {
19
+ Obj.update(tree.tree, () => {
20
20
  tree.root.data = { text: 'root' };
21
21
  });
22
22
 
@@ -128,7 +128,7 @@ describe('tree', () => {
128
128
 
129
129
  const tree = createTree();
130
130
  const node = tree.addNode(tree.root);
131
- Obj.change(tree.tree, () => {
131
+ Obj.update(tree.tree, () => {
132
132
  node.ref = Ref.make(task);
133
133
  });
134
134
  });
@@ -185,7 +185,7 @@ export class Tree {
185
185
  clear(): void {
186
186
  const root = this._tree.nodes[this._tree.root];
187
187
  root.children.length = 0;
188
- Obj.change(this._tree, (obj) => {
188
+ Obj.update(this._tree, (obj) => {
189
189
  obj.nodes = {
190
190
  [root.id]: root,
191
191
  };
@@ -202,7 +202,7 @@ export class Tree {
202
202
  }
203
203
 
204
204
  const nodeToAdd = node;
205
- Obj.change(this._tree, (obj) => {
205
+ Obj.update(this._tree, (obj) => {
206
206
  obj.nodes[nodeToAdd.id] = nodeToAdd;
207
207
  parent.children.splice(index ?? parent.children.length, 0, nodeToAdd.id);
208
208
  });
@@ -218,12 +218,12 @@ export class Tree {
218
218
  return undefined;
219
219
  }
220
220
 
221
- Obj.change(this._tree, (obj) => {
221
+ Obj.update(this._tree, (obj) => {
222
222
  delete obj.nodes[node.id];
223
223
  });
224
224
  const idx = parent.children.findIndex((child) => child === id);
225
225
  if (idx !== -1) {
226
- Obj.change(this._tree, () => {
226
+ Obj.update(this._tree, () => {
227
227
  parent.children.splice(idx, 1);
228
228
  });
229
229
  }
@@ -242,7 +242,7 @@ export class Tree {
242
242
  }
243
243
 
244
244
  const child = node.children[from];
245
- Obj.change(this._tree, () => {
245
+ Obj.update(this._tree, () => {
246
246
  node.children.splice(from, 1);
247
247
  node.children.splice(to, 0, child);
248
248
  });
@@ -264,7 +264,7 @@ export class Tree {
264
264
  }
265
265
 
266
266
  const previous = this.getNode(parent.children[idx - 1]);
267
- Obj.change(this._tree, () => {
267
+ Obj.update(this._tree, () => {
268
268
  parent.children.splice(idx, 1);
269
269
  previous.children.push(node.id);
270
270
  });
@@ -287,19 +287,19 @@ export class Tree {
287
287
  // Remove node from parent and get following siblings.
288
288
  const nodeIdx = parent.children.findIndex((id) => id === node.id);
289
289
  let rest: Key.ObjectId[] = [];
290
- Obj.change(this._tree, () => {
290
+ Obj.update(this._tree, () => {
291
291
  const removed = parent.children.splice(nodeIdx, parent.children.length - nodeIdx);
292
292
  rest = removed.slice(1); // Skip the node itself.
293
293
  });
294
294
 
295
295
  // Add to ancestor.
296
296
  const parentIdx = this.getChildNodes(ancestor).findIndex((n) => n.id === parent.id);
297
- Obj.change(this._tree, () => {
297
+ Obj.update(this._tree, () => {
298
298
  ancestor.children.splice(parentIdx + 1, 0, node.id);
299
299
  });
300
300
 
301
301
  // Transplant following siblings to current node.
302
- Obj.change(this._tree, () => {
302
+ Obj.update(this._tree, () => {
303
303
  node.children.push(...rest);
304
304
  });
305
305
  }
@@ -5,25 +5,22 @@
5
5
  import React, { useCallback, useMemo, useState } from 'react';
6
6
 
7
7
  import { type AppSurface } from '@dxos/app-toolkit/ui';
8
- import { type Filter } from '@dxos/echo';
9
- import { type View } from '@dxos/echo';
8
+ import { type Filter, Obj, type View } from '@dxos/echo';
10
9
  import { QueryBuilder } from '@dxos/echo-query';
11
- import { useGlobalSearch } from '@dxos/plugin-search';
12
- import { getSpace, useObject } from '@dxos/react-client/echo';
10
+ import { useObject } from '@dxos/react-client/echo';
13
11
  import { Panel, Toolbar } from '@dxos/react-ui';
14
12
  import { QueryEditor, type QueryEditorProps } from '@dxos/react-ui-components';
15
13
 
16
- import { D3ForceGraph } from '#components';
14
+ import { ForceGraph } from '#components';
17
15
  import { useGraphModel } from '#hooks';
18
16
 
19
17
  export type ExplorerContainerProps = AppSurface.ObjectArticleProps<View.View>;
20
18
 
21
- export const ExplorerContainer = ({ role, subject: view, attendableId: _attendableId }: ExplorerContainerProps) => {
22
- useObject(view);
23
- const space = view && getSpace(view);
19
+ export const ExplorerContainer = ({ role, subject, attendableId: _attendableId }: ExplorerContainerProps) => {
20
+ const [view] = useObject(subject);
21
+ const db = view && Obj.getDatabase(view);
24
22
  const [filter, setFilter] = useState<Filter.Any>();
25
- const model = useGraphModel(space, filter);
26
- const { match } = useGlobalSearch();
23
+ const model = useGraphModel(db, filter);
27
24
 
28
25
  const builder = useMemo(() => new QueryBuilder(), []);
29
26
  const handleChange = useCallback<NonNullable<QueryEditorProps['onChange']>>((value) => {
@@ -32,7 +29,7 @@ export const ExplorerContainer = ({ role, subject: view, attendableId: _attendab
32
29
 
33
30
  const showToolbar = role === 'article';
34
31
 
35
- if (!space || !model) {
32
+ if (!db || !model) {
36
33
  return null;
37
34
  }
38
35
 
@@ -41,12 +38,12 @@ export const ExplorerContainer = ({ role, subject: view, attendableId: _attendab
41
38
  {showToolbar && (
42
39
  <Panel.Toolbar asChild>
43
40
  <Toolbar.Root>
44
- <QueryEditor db={space.db} onChange={handleChange} />
41
+ <QueryEditor db={db} onChange={handleChange} />
45
42
  </Toolbar.Root>
46
43
  </Panel.Toolbar>
47
44
  )}
48
45
  <Panel.Content asChild>
49
- <D3ForceGraph model={model} match={match} />
46
+ <ForceGraph model={model} />
50
47
  </Panel.Content>
51
48
  </Panel.Root>
52
49
  );
@@ -6,38 +6,42 @@ import { useEffect, useState } from 'react';
6
6
 
7
7
  import { Capabilities } from '@dxos/app-framework';
8
8
  import { useCapability } from '@dxos/app-framework/ui';
9
- import { type Filter, type Queue, type Space } from '@dxos/client/echo';
9
+ import { type Database, type Entity, type Filter } from '@dxos/echo';
10
10
  import { SpaceGraphModel, type SpaceGraphModelOptions } from '@dxos/schema';
11
11
 
12
12
  // TODO(burdon): Factor out.
13
13
  export const useGraphModel = (
14
- space: Space | undefined,
14
+ db: Database.Database | undefined,
15
15
  filter?: Filter.Any | undefined,
16
16
  options?: SpaceGraphModelOptions,
17
- queue?: Queue,
17
+ items?: readonly Entity.Unknown[],
18
18
  ): SpaceGraphModel | undefined => {
19
19
  const registry = useCapability(Capabilities.AtomRegistry);
20
20
  const [model, setModel] = useState<SpaceGraphModel | undefined>(undefined);
21
21
 
22
22
  useEffect(() => {
23
- if (!space) {
23
+ if (!db) {
24
24
  setModel(undefined);
25
25
  return;
26
26
  }
27
27
 
28
28
  const newModel = new SpaceGraphModel(registry);
29
- void newModel.open(space.db, queue);
29
+ void newModel.open(db);
30
30
  setModel(newModel);
31
31
 
32
32
  return () => {
33
33
  setModel(undefined);
34
34
  void newModel.close();
35
35
  };
36
- }, [space, registry, queue]);
36
+ }, [db, registry]);
37
37
 
38
38
  useEffect(() => {
39
39
  model?.setFilter(filter).setOptions(options);
40
40
  }, [model, filter, options]);
41
41
 
42
+ useEffect(() => {
43
+ model?.setItems(items);
44
+ }, [model, items]);
45
+
42
46
  return model;
43
47
  };
package/src/index.ts CHANGED
@@ -2,8 +2,5 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- export * from './components';
6
- export * from './hooks';
7
5
  export * from './meta';
8
-
9
- export * from './ExplorerPlugin';
6
+ export * from './types';