@dxos/plugin-explorer 0.8.4-main.dedc0f3 → 0.8.4-main.dfabb4ec29

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 (194) hide show
  1. package/dist/lib/neutral/ExplorerContainer-5TOK2ZEY.mjs +40 -0
  2. package/dist/lib/neutral/ExplorerContainer-5TOK2ZEY.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/neutral/chunk-5X5ATGCS.mjs +73 -0
  8. package/dist/lib/neutral/chunk-5X5ATGCS.mjs.map +7 -0
  9. package/dist/lib/neutral/chunk-HPIS2WXY.mjs +24 -0
  10. package/dist/lib/neutral/chunk-HPIS2WXY.mjs.map +7 -0
  11. package/dist/lib/neutral/chunk-J5LGTIGS.mjs +10 -0
  12. package/dist/lib/{browser/chunk-VNOGW2JS.mjs → neutral/components/index.mjs} +344 -411
  13. package/dist/lib/{node-esm/chunk-ODMJ7DPA.mjs.map → neutral/components/index.mjs.map} +4 -4
  14. package/dist/lib/neutral/containers/index.mjs +9 -0
  15. package/dist/lib/neutral/containers/index.mjs.map +7 -0
  16. package/dist/lib/neutral/create-object-F6TKVAGV.mjs +39 -0
  17. package/dist/lib/neutral/create-object-F6TKVAGV.mjs.map +7 -0
  18. package/dist/lib/neutral/hooks/index.mjs +40 -0
  19. package/dist/lib/neutral/hooks/index.mjs.map +7 -0
  20. package/dist/lib/neutral/index.mjs +14 -0
  21. package/dist/lib/neutral/meta.json +1 -0
  22. package/dist/lib/{browser → neutral}/meta.mjs +2 -3
  23. package/dist/lib/neutral/plugin.mjs +12 -0
  24. package/dist/lib/neutral/plugin.mjs.map +7 -0
  25. package/dist/lib/neutral/react-surface-U3JEY7V7.mjs +26 -0
  26. package/dist/lib/neutral/react-surface-U3JEY7V7.mjs.map +7 -0
  27. package/dist/lib/neutral/translations.mjs +33 -0
  28. package/dist/lib/neutral/translations.mjs.map +7 -0
  29. package/dist/lib/neutral/types/index.mjs +10 -0
  30. package/dist/types/data/cities.d.ts +4 -4
  31. package/dist/types/data/cities.d.ts.map +1 -1
  32. package/dist/types/data/countries-110m.d.ts +19 -22
  33. package/dist/types/data/countries-110m.d.ts.map +1 -1
  34. package/dist/types/src/ExplorerPlugin.d.ts +3 -1
  35. package/dist/types/src/ExplorerPlugin.d.ts.map +1 -1
  36. package/dist/types/src/ExplorerPlugin.test.d.ts +2 -0
  37. package/dist/types/src/ExplorerPlugin.test.d.ts.map +1 -0
  38. package/dist/types/src/capabilities/create-object.d.ts +11 -0
  39. package/dist/types/src/capabilities/create-object.d.ts.map +1 -0
  40. package/dist/types/src/capabilities/index.d.ts +8 -2
  41. package/dist/types/src/capabilities/index.d.ts.map +1 -1
  42. package/dist/types/src/capabilities/react-surface.d.ts +3 -2
  43. package/dist/types/src/capabilities/react-surface.d.ts.map +1 -1
  44. package/dist/types/src/components/Chart/Chart.d.ts.map +1 -1
  45. package/dist/types/src/components/Chart/Chart.stories.d.ts +7 -2
  46. package/dist/types/src/components/Chart/Chart.stories.d.ts.map +1 -1
  47. package/dist/types/src/components/Globe/Globe.d.ts.map +1 -1
  48. package/dist/types/src/components/Globe/Globe.stories.d.ts +8 -3
  49. package/dist/types/src/components/Globe/Globe.stories.d.ts.map +1 -1
  50. package/dist/types/src/components/Graph/CanvasForceGraph.d.ts +13 -0
  51. package/dist/types/src/components/Graph/CanvasForceGraph.d.ts.map +1 -0
  52. package/dist/types/src/components/Graph/CanvasForceGraph.stories.d.ts +17 -0
  53. package/dist/types/src/components/Graph/CanvasForceGraph.stories.d.ts.map +1 -0
  54. package/dist/types/src/components/Graph/ForceGraph.d.ts +12 -5
  55. package/dist/types/src/components/Graph/ForceGraph.d.ts.map +1 -1
  56. package/dist/types/src/components/Graph/ForceGraph.stories.d.ts +7 -3
  57. package/dist/types/src/components/Graph/ForceGraph.stories.d.ts.map +1 -1
  58. package/dist/types/src/components/Graph/{adapter.d.ts → graph-adapter.d.ts} +2 -2
  59. package/dist/types/src/components/Graph/graph-adapter.d.ts.map +1 -0
  60. package/dist/types/src/components/Graph/index.d.ts +1 -1
  61. package/dist/types/src/components/Graph/index.d.ts.map +1 -1
  62. package/dist/types/src/components/Graph/testing.d.ts.map +1 -1
  63. package/dist/types/src/components/Tree/Tree.d.ts.map +1 -1
  64. package/dist/types/src/components/Tree/Tree.stories.d.ts +5 -2
  65. package/dist/types/src/components/Tree/Tree.stories.d.ts.map +1 -1
  66. package/dist/types/src/components/Tree/layout/HierarchicalEdgeBundling.d.ts.map +1 -1
  67. package/dist/types/src/components/Tree/layout/RadialTree.d.ts.map +1 -1
  68. package/dist/types/src/components/Tree/layout/TidyTree.d.ts.map +1 -1
  69. package/dist/types/src/components/Tree/testing/generator.d.ts.map +1 -1
  70. package/dist/types/src/components/Tree/types/tree.d.ts +19 -17
  71. package/dist/types/src/components/Tree/types/tree.d.ts.map +1 -1
  72. package/dist/types/src/components/Tree/types/types.d.ts +1 -1
  73. package/dist/types/src/components/Tree/types/types.d.ts.map +1 -1
  74. package/dist/types/src/components/index.d.ts +0 -4
  75. package/dist/types/src/components/index.d.ts.map +1 -1
  76. package/dist/types/src/components/plot.d.ts.map +1 -1
  77. package/dist/types/src/containers/ExplorerContainer/ExplorerContainer.d.ts +6 -0
  78. package/dist/types/src/containers/ExplorerContainer/ExplorerContainer.d.ts.map +1 -0
  79. package/dist/types/src/containers/ExplorerContainer/index.d.ts +2 -0
  80. package/dist/types/src/containers/ExplorerContainer/index.d.ts.map +1 -0
  81. package/dist/types/src/containers/index.d.ts +3 -0
  82. package/dist/types/src/containers/index.d.ts.map +1 -0
  83. package/dist/types/src/hooks/useGraphModel.d.ts.map +1 -1
  84. package/dist/types/src/index.d.ts +1 -3
  85. package/dist/types/src/index.d.ts.map +1 -1
  86. package/dist/types/src/meta.d.ts +2 -3
  87. package/dist/types/src/meta.d.ts.map +1 -1
  88. package/dist/types/src/plugin.d.ts +3 -0
  89. package/dist/types/src/plugin.d.ts.map +1 -0
  90. package/dist/types/src/translations.d.ts +34 -13
  91. package/dist/types/src/translations.d.ts.map +1 -1
  92. package/dist/types/src/types/ExplorerAction.d.ts +6 -0
  93. package/dist/types/src/types/ExplorerAction.d.ts.map +1 -0
  94. package/dist/types/src/types/Graph.d.ts +23 -0
  95. package/dist/types/src/types/Graph.d.ts.map +1 -0
  96. package/dist/types/src/types/index.d.ts +2 -2
  97. package/dist/types/src/types/index.d.ts.map +1 -1
  98. package/dist/types/tsconfig.tsbuildinfo +1 -1
  99. package/package.json +107 -59
  100. package/src/ExplorerPlugin.test.ts +26 -0
  101. package/src/ExplorerPlugin.tsx +15 -54
  102. package/src/capabilities/create-object.ts +36 -0
  103. package/src/capabilities/index.ts +3 -3
  104. package/src/capabilities/react-surface.tsx +24 -15
  105. package/src/components/Chart/Chart.stories.tsx +19 -25
  106. package/src/components/Globe/Globe.stories.tsx +22 -24
  107. package/src/components/Graph/CanvasForceGraph.stories.tsx +83 -0
  108. package/src/components/Graph/CanvasForceGraph.tsx +124 -0
  109. package/src/components/Graph/ForceGraph.stories.tsx +83 -35
  110. package/src/components/Graph/ForceGraph.tsx +104 -85
  111. package/src/components/Graph/{adapter.ts → graph-adapter.ts} +14 -8
  112. package/src/components/Graph/index.ts +1 -1
  113. package/src/components/Graph/testing.ts +11 -8
  114. package/src/components/Tree/Tree.stories.tsx +44 -38
  115. package/src/components/Tree/Tree.tsx +10 -5
  116. package/src/components/Tree/testing/generator.ts +4 -2
  117. package/src/components/Tree/types/tree.test.ts +7 -6
  118. package/src/components/Tree/types/tree.ts +42 -21
  119. package/src/components/Tree/types/types.ts +1 -1
  120. package/src/components/index.ts +0 -4
  121. package/src/containers/ExplorerContainer/ExplorerContainer.tsx +51 -0
  122. package/src/containers/ExplorerContainer/index.ts +5 -0
  123. package/src/containers/index.ts +7 -0
  124. package/src/hooks/useGraphModel.ts +17 -10
  125. package/src/index.ts +1 -4
  126. package/src/meta.ts +9 -6
  127. package/src/plugin.ts +9 -0
  128. package/src/translations.ts +17 -12
  129. package/src/types/ExplorerAction.ts +20 -0
  130. package/src/types/Graph.ts +49 -0
  131. package/src/types/index.ts +2 -2
  132. package/src/typings.d.ts +8 -0
  133. package/dist/lib/browser/ExplorerContainer-7MTDS2TQ.mjs +0 -37
  134. package/dist/lib/browser/ExplorerContainer-7MTDS2TQ.mjs.map +0 -7
  135. package/dist/lib/browser/chunk-2MKBRIUT.mjs +0 -31
  136. package/dist/lib/browser/chunk-2MKBRIUT.mjs.map +0 -7
  137. package/dist/lib/browser/chunk-3YITRGGW.mjs +0 -203
  138. package/dist/lib/browser/chunk-3YITRGGW.mjs.map +0 -7
  139. package/dist/lib/browser/chunk-CZZ3DDR7.mjs +0 -38
  140. package/dist/lib/browser/chunk-CZZ3DDR7.mjs.map +0 -7
  141. package/dist/lib/browser/chunk-UL5EDJPE.mjs +0 -21
  142. package/dist/lib/browser/chunk-UL5EDJPE.mjs.map +0 -7
  143. package/dist/lib/browser/chunk-VNOGW2JS.mjs.map +0 -7
  144. package/dist/lib/browser/index.mjs +0 -112
  145. package/dist/lib/browser/index.mjs.map +0 -7
  146. package/dist/lib/browser/intent-resolver-7MVEYNX7.mjs +0 -24
  147. package/dist/lib/browser/intent-resolver-7MVEYNX7.mjs.map +0 -7
  148. package/dist/lib/browser/meta.json +0 -1
  149. package/dist/lib/browser/react-surface-VLGQKYBI.mjs +0 -31
  150. package/dist/lib/browser/react-surface-VLGQKYBI.mjs.map +0 -7
  151. package/dist/lib/browser/types/index.mjs +0 -10
  152. package/dist/lib/node-esm/ExplorerContainer-OBSRVHZZ.mjs +0 -38
  153. package/dist/lib/node-esm/ExplorerContainer-OBSRVHZZ.mjs.map +0 -7
  154. package/dist/lib/node-esm/chunk-3ODK27PU.mjs +0 -33
  155. package/dist/lib/node-esm/chunk-3ODK27PU.mjs.map +0 -7
  156. package/dist/lib/node-esm/chunk-4GWDNZ4Z.mjs +0 -39
  157. package/dist/lib/node-esm/chunk-4GWDNZ4Z.mjs.map +0 -7
  158. package/dist/lib/node-esm/chunk-GFYXDQQV.mjs +0 -205
  159. package/dist/lib/node-esm/chunk-GFYXDQQV.mjs.map +0 -7
  160. package/dist/lib/node-esm/chunk-ODMJ7DPA.mjs +0 -11343
  161. package/dist/lib/node-esm/chunk-PIAXA43R.mjs +0 -23
  162. package/dist/lib/node-esm/chunk-PIAXA43R.mjs.map +0 -7
  163. package/dist/lib/node-esm/index.mjs +0 -113
  164. package/dist/lib/node-esm/index.mjs.map +0 -7
  165. package/dist/lib/node-esm/intent-resolver-NL3SR2XF.mjs +0 -25
  166. package/dist/lib/node-esm/intent-resolver-NL3SR2XF.mjs.map +0 -7
  167. package/dist/lib/node-esm/meta.json +0 -1
  168. package/dist/lib/node-esm/meta.mjs +0 -10
  169. package/dist/lib/node-esm/react-surface-BYJABDS5.mjs +0 -32
  170. package/dist/lib/node-esm/react-surface-BYJABDS5.mjs.map +0 -7
  171. package/dist/lib/node-esm/types/index.mjs +0 -11
  172. package/dist/types/src/capabilities/intent-resolver.d.ts +0 -4
  173. package/dist/types/src/capabilities/intent-resolver.d.ts.map +0 -1
  174. package/dist/types/src/components/ExplorerContainer.d.ts +0 -9
  175. package/dist/types/src/components/ExplorerContainer.d.ts.map +0 -1
  176. package/dist/types/src/components/Graph/D3ForceGraph.d.ts +0 -14
  177. package/dist/types/src/components/Graph/D3ForceGraph.d.ts.map +0 -1
  178. package/dist/types/src/components/Graph/D3ForceGraph.stories.d.ts +0 -20
  179. package/dist/types/src/components/Graph/D3ForceGraph.stories.d.ts.map +0 -1
  180. package/dist/types/src/components/Graph/adapter.d.ts.map +0 -1
  181. package/dist/types/src/types/schema.d.ts +0 -12
  182. package/dist/types/src/types/schema.d.ts.map +0 -1
  183. package/dist/types/src/types/types.d.ts +0 -18
  184. package/dist/types/src/types/types.d.ts.map +0 -1
  185. package/src/capabilities/intent-resolver.ts +0 -19
  186. package/src/components/ExplorerContainer.tsx +0 -37
  187. package/src/components/Graph/D3ForceGraph.stories.tsx +0 -67
  188. package/src/components/Graph/D3ForceGraph.tsx +0 -101
  189. package/src/types/schema.ts +0 -16
  190. package/src/types/types.ts +0 -22
  191. /package/dist/lib/{browser/meta.mjs.map → neutral/chunk-J5LGTIGS.mjs.map} +0 -0
  192. /package/dist/lib/{browser/types → neutral}/index.mjs.map +0 -0
  193. /package/dist/lib/{node-esm → neutral}/meta.mjs.map +0 -0
  194. /package/dist/lib/{node-esm → neutral}/types/index.mjs.map +0 -0
@@ -0,0 +1,124 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import { forceLink, forceManyBody } from 'd3';
6
+ import NativeForceGraph from 'force-graph';
7
+ import React, {
8
+ type ComponentPropsWithoutRef,
9
+ type Ref,
10
+ RefObject,
11
+ useCallback,
12
+ useEffect,
13
+ useRef,
14
+ useState,
15
+ } from 'react';
16
+ import { useResizeDetector } from 'react-resize-detector';
17
+
18
+ import { type SpaceGraphModel } from '@dxos/schema';
19
+ import { composable, composableProps } from '@dxos/ui-theme';
20
+
21
+ import { GraphAdapter } from './graph-adapter';
22
+
23
+ export type CanvasForceGraphProps = {
24
+ model?: SpaceGraphModel;
25
+ match?: RegExp;
26
+ };
27
+
28
+ /**
29
+ * More performance optimized version of the ForceGraph.
30
+ */
31
+ export const CanvasForceGraph = composable<HTMLDivElement, CanvasForceGraphProps>(
32
+ ({ model, match, onClick, ...props }, forwardedRef) => {
33
+ const { ref: resizeRef, width, height } = useResizeDetector({ refreshRate: 200 });
34
+ const setRef = useCallback(
35
+ (node: HTMLDivElement | null) => {
36
+ resizeRef(node);
37
+ assignRef(forwardedRef, node);
38
+ },
39
+ [resizeRef, forwardedRef],
40
+ );
41
+
42
+ const rootRef = useRef<HTMLDivElement>(null);
43
+ const forceGraph = useRef<NativeForceGraph>(null);
44
+
45
+ const [data, setData] = useState<GraphAdapter>();
46
+ useEffect(() => {
47
+ return model?.subscribe((model) => setData(new GraphAdapter(model.graph)));
48
+ }, [model]);
49
+
50
+ useEffect(() => {
51
+ if (rootRef.current) {
52
+ // https://github.com/vasturiano/force-graph
53
+ // https://github.com/vasturiano/3d-force-graph
54
+ forceGraph.current = new NativeForceGraph(rootRef.current)
55
+ // https://github.com/vasturiano/force-graph?tab=readme-ov-file#node-styling
56
+ .nodeRelSize(6)
57
+ .nodeLabel((node: any) => (node.type === 'schema' ? node.data.typename : (node.data.label ?? node.id)))
58
+ .nodeAutoColorBy((node: any) => (node.type === 'schema' ? 'schema' : node.data.typename))
59
+
60
+ // https://github.com/vasturiano/force-graph?tab=readme-ov-file#link-styling
61
+ .linkAutoColorBy((link: any) => link.type);
62
+ }
63
+
64
+ return () => {
65
+ forceGraph.current?.pauseAnimation().graphData({ nodes: [], links: [] });
66
+ forceGraph.current = null;
67
+ };
68
+ }, []);
69
+
70
+ useEffect(() => {
71
+ if (!data || !width || !height || !forceGraph.current) {
72
+ return;
73
+ }
74
+
75
+ // https://github.com/vasturiano/force-graph?tab=readme-ov-file#container-layout
76
+ forceGraph.current
77
+ .pauseAnimation()
78
+ .width(width)
79
+ .height(height)
80
+ .onEngineStop(() => handleZoomToFit())
81
+ .onNodeClick((node: any) => {
82
+ forceGraph.current?.emitParticle(node);
83
+ })
84
+
85
+ // https://github.com/vasturiano/force-graph?tab=readme-ov-file#force-engine-d3-force-configuration
86
+ // .d3Force('center', forceCenter().strength(0.9))
87
+ .d3Force('link', forceLink().distance(160).strength(0.5))
88
+ .d3Force('charge', forceManyBody().strength(-30))
89
+
90
+ .graphData(data)
91
+ .warmupTicks(100)
92
+ .cooldownTime(1_000)
93
+ .resumeAnimation();
94
+ }, [data, width, height]);
95
+
96
+ const handleZoomToFit = () => {
97
+ forceGraph.current?.zoomToFit(400, 40);
98
+ };
99
+
100
+ const handleClick = useCallback<NonNullable<ComponentPropsWithoutRef<'div'>['onClick']>>(
101
+ (event) => {
102
+ onClick?.(event);
103
+ if (!event.defaultPrevented) {
104
+ handleZoomToFit();
105
+ }
106
+ },
107
+ [onClick],
108
+ );
109
+
110
+ return (
111
+ <div {...composableProps(props, { classNames: 'relative grow' })} onClick={handleClick} ref={setRef}>
112
+ <div ref={rootRef} className='absolute inset-0' />
113
+ </div>
114
+ );
115
+ },
116
+ );
117
+
118
+ const assignRef = <T,>(ref: Ref<T> | undefined, value: T | null): void => {
119
+ if (typeof ref === 'function') {
120
+ ref(value);
121
+ } else if (ref) {
122
+ (ref as RefObject<T | null>).current = value;
123
+ }
124
+ };
@@ -2,62 +2,110 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- import '@dxos-theme';
6
-
7
5
  import { type Meta, type StoryObj } from '@storybook/react-vite';
8
- import React, { useEffect, useState } from 'react';
6
+ import * as Effect from 'effect/Effect';
7
+ import React, { useCallback, useMemo } from 'react';
9
8
 
10
- import { Obj } from '@dxos/echo';
11
- import { faker } from '@dxos/random';
12
- import { useClient } from '@dxos/react-client';
13
- import { type Space } from '@dxos/react-client/echo';
14
- import { withClientProvider } from '@dxos/react-client/testing';
15
- import { DataType } from '@dxos/schema';
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';
16
+ import { random } from '@dxos/random';
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';
20
+ import { Loading, withLayout, withTheme } from '@dxos/react-ui/testing';
21
+ import { type SpaceGraphEdge, type SpaceGraphNode, ViewModel } from '@dxos/schema';
16
22
  import { type ValueGenerator } from '@dxos/schema/testing';
17
- import { render, withLayout, withTheme } from '@dxos/storybook-utils';
23
+ import { HasRelationship, Organization, Person, Pipeline } from '@dxos/types';
18
24
 
19
- import { useGraphModel } from '../../hooks';
20
- import { ViewType } from '../../types';
25
+ import { useGraphModel } from '#hooks';
26
+ import { Graph } from '#types';
21
27
 
22
28
  import { ForceGraph } from './ForceGraph';
23
29
  import { generate } from './testing';
24
30
 
25
- const generator = faker as any as ValueGenerator;
31
+ const generator = random as any as ValueGenerator;
26
32
 
27
- faker.seed(1);
33
+ random.seed(1);
28
34
 
29
35
  const DefaultStory = () => {
30
- const client = useClient();
31
- const [space, setSpace] = useState<Space>();
32
- const [view, setView] = useState<ViewType>();
33
- useEffect(() => {
34
- const space = client.spaces.default;
35
- void generate(space, generator);
36
- const view = space.db.add(Obj.make(ViewType, { name: '', type: '' }));
37
- setSpace(space);
38
- setView(view);
39
- }, []);
40
-
36
+ const [space] = useSpaces();
41
37
  const model = useGraphModel(space);
42
- if (!model || !space || !view) {
43
- return null;
38
+
39
+ const selection = useMemo(() => new SelectionModel({ mode: 'single' }), []);
40
+
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 }} />;
44
66
  }
45
67
 
46
- return <ForceGraph model={model} />;
68
+ return <ForceGraph model={model} selection={selection} onInspect={handleInspect} />;
47
69
  };
48
70
 
49
71
  const meta = {
50
- title: 'plugins/plugin-explorer/ForceGraph',
72
+ title: 'plugins/plugin-explorer/components/ForceGraph',
51
73
  component: ForceGraph,
52
- render: render(DefaultStory),
74
+ render: DefaultStory,
53
75
  decorators: [
54
- withClientProvider({
55
- createSpace: true,
56
- types: [ViewType, DataType.HasRelationship, DataType.Organization, DataType.Project, DataType.Person],
76
+ withTheme(),
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(),
103
+ ],
57
104
  }),
58
- withTheme,
59
- withLayout({ fullscreen: true }),
60
105
  ],
106
+ parameters: {
107
+ layout: 'fullscreen',
108
+ },
61
109
  } satisfies Meta<typeof ForceGraph>;
62
110
 
63
111
  export default meta;
@@ -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>();
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 = undefined;
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
+ );
@@ -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() {
@@ -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';
@@ -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,