@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.
- package/dist/lib/browser/chunk-J5LGTIGS.mjs +10 -0
- package/dist/lib/browser/chunk-J5LGTIGS.mjs.map +7 -0
- package/dist/lib/browser/chunk-LSUP47BZ.mjs +24 -0
- package/dist/lib/browser/chunk-LSUP47BZ.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +11347 -83
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/meta.mjs +2 -3
- package/dist/lib/browser/types/index.mjs +66 -6
- package/dist/lib/browser/types/index.mjs.map +4 -4
- package/dist/lib/node-esm/{chunk-PIAXA43R.mjs → chunk-EN3JZNEY.mjs} +8 -5
- package/dist/lib/node-esm/chunk-EN3JZNEY.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-HSLMI22Q.mjs +11 -0
- package/dist/lib/node-esm/chunk-HSLMI22Q.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +11347 -83
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/lib/node-esm/meta.mjs +2 -3
- package/dist/lib/node-esm/types/index.mjs +66 -6
- package/dist/lib/node-esm/types/index.mjs.map +4 -4
- package/dist/types/src/ExplorerPlugin.d.ts +2 -1
- package/dist/types/src/ExplorerPlugin.d.ts.map +1 -1
- package/dist/types/src/capabilities/index.d.ts +2 -2
- package/dist/types/src/capabilities/index.d.ts.map +1 -1
- package/dist/types/src/capabilities/react-surface.d.ts +3 -2
- package/dist/types/src/capabilities/react-surface.d.ts.map +1 -1
- package/dist/types/src/components/Chart/Chart.d.ts.map +1 -1
- package/dist/types/src/components/Chart/Chart.stories.d.ts +8 -4
- package/dist/types/src/components/Chart/Chart.stories.d.ts.map +1 -1
- package/dist/types/src/components/Globe/Globe.d.ts.map +1 -1
- package/dist/types/src/components/Globe/Globe.stories.d.ts +8 -4
- package/dist/types/src/components/Globe/Globe.stories.d.ts.map +1 -1
- package/dist/types/src/components/Graph/D3ForceGraph.d.ts +6 -5
- package/dist/types/src/components/Graph/D3ForceGraph.d.ts.map +1 -1
- package/dist/types/src/components/Graph/D3ForceGraph.stories.d.ts +15 -4
- package/dist/types/src/components/Graph/D3ForceGraph.stories.d.ts.map +1 -1
- package/dist/types/src/components/Graph/ForceGraph.stories.d.ts +13 -4
- package/dist/types/src/components/Graph/ForceGraph.stories.d.ts.map +1 -1
- package/dist/types/src/components/Graph/adapter.d.ts +1 -1
- package/dist/types/src/components/Graph/adapter.d.ts.map +1 -1
- package/dist/types/src/components/Graph/testing.d.ts +1 -1
- package/dist/types/src/components/Graph/testing.d.ts.map +1 -1
- package/dist/types/src/components/Tree/Tree.d.ts.map +1 -1
- package/dist/types/src/components/Tree/Tree.stories.d.ts +13 -16
- package/dist/types/src/components/Tree/Tree.stories.d.ts.map +1 -1
- package/dist/types/src/components/Tree/testing/generator.d.ts.map +1 -1
- package/dist/types/src/components/Tree/types/tree.d.ts +19 -17
- package/dist/types/src/components/Tree/types/tree.d.ts.map +1 -1
- package/dist/types/src/components/Tree/types/types.d.ts +1 -1
- package/dist/types/src/components/Tree/types/types.d.ts.map +1 -1
- package/dist/types/src/components/index.d.ts +0 -4
- package/dist/types/src/components/index.d.ts.map +1 -1
- package/dist/types/src/containers/ExplorerContainer/ExplorerContainer.d.ts +6 -0
- package/dist/types/src/containers/ExplorerContainer/ExplorerContainer.d.ts.map +1 -0
- package/dist/types/src/containers/ExplorerContainer/index.d.ts +2 -0
- package/dist/types/src/containers/ExplorerContainer/index.d.ts.map +1 -0
- package/dist/types/src/containers/index.d.ts +3 -0
- package/dist/types/src/containers/index.d.ts.map +1 -0
- package/dist/types/src/hooks/useGraphModel.d.ts +2 -2
- package/dist/types/src/hooks/useGraphModel.d.ts.map +1 -1
- package/dist/types/src/meta.d.ts +2 -3
- package/dist/types/src/meta.d.ts.map +1 -1
- package/dist/types/src/translations.d.ts +34 -11
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/src/types/ExplorerAction.d.ts +6 -0
- package/dist/types/src/types/ExplorerAction.d.ts.map +1 -0
- package/dist/types/src/types/Graph.d.ts +23 -0
- package/dist/types/src/types/Graph.d.ts.map +1 -0
- package/dist/types/src/types/index.d.ts +2 -2
- package/dist/types/src/types/index.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +62 -44
- package/src/ExplorerPlugin.tsx +43 -53
- package/src/capabilities/index.ts +2 -3
- package/src/capabilities/react-surface.tsx +24 -15
- package/src/components/Chart/Chart.stories.tsx +9 -8
- package/src/components/Chart/Chart.tsx +1 -1
- package/src/components/Globe/Globe.stories.tsx +13 -12
- package/src/components/Globe/Globe.tsx +1 -1
- package/src/components/Graph/D3ForceGraph.stories.tsx +47 -28
- package/src/components/Graph/D3ForceGraph.tsx +83 -76
- package/src/components/Graph/ForceGraph.stories.tsx +47 -28
- package/src/components/Graph/ForceGraph.tsx +5 -5
- package/src/components/Graph/adapter.ts +14 -8
- package/src/components/Graph/testing.ts +13 -10
- package/src/components/Tree/Tree.stories.tsx +34 -25
- package/src/components/Tree/Tree.tsx +11 -6
- package/src/components/Tree/testing/generator.ts +4 -2
- package/src/components/Tree/types/tree.test.ts +9 -7
- package/src/components/Tree/types/tree.ts +42 -21
- package/src/components/Tree/types/types.ts +1 -1
- package/src/components/index.ts +0 -4
- package/src/containers/ExplorerContainer/ExplorerContainer.tsx +53 -0
- package/src/containers/ExplorerContainer/index.ts +5 -0
- package/src/containers/index.ts +7 -0
- package/src/hooks/useGraphModel.ts +19 -11
- package/src/meta.ts +9 -6
- package/src/translations.ts +17 -10
- package/src/types/ExplorerAction.ts +20 -0
- package/src/types/Graph.ts +49 -0
- package/src/types/index.ts +2 -2
- package/src/typings.d.ts +8 -0
- package/dist/lib/browser/ExplorerContainer-WYPM7YXU.mjs +0 -37
- package/dist/lib/browser/ExplorerContainer-WYPM7YXU.mjs.map +0 -7
- package/dist/lib/browser/chunk-4UVJNXCE.mjs +0 -11329
- package/dist/lib/browser/chunk-4UVJNXCE.mjs.map +0 -7
- package/dist/lib/browser/chunk-EF4BFHTI.mjs +0 -38
- package/dist/lib/browser/chunk-EF4BFHTI.mjs.map +0 -7
- package/dist/lib/browser/chunk-OAOY7SHY.mjs +0 -30
- package/dist/lib/browser/chunk-OAOY7SHY.mjs.map +0 -7
- package/dist/lib/browser/chunk-UL5EDJPE.mjs +0 -21
- package/dist/lib/browser/chunk-UL5EDJPE.mjs.map +0 -7
- package/dist/lib/browser/chunk-ZZX52LNU.mjs +0 -187
- package/dist/lib/browser/chunk-ZZX52LNU.mjs.map +0 -7
- package/dist/lib/browser/intent-resolver-XH2UO2FM.mjs +0 -24
- package/dist/lib/browser/intent-resolver-XH2UO2FM.mjs.map +0 -7
- package/dist/lib/browser/react-surface-5ENPAK3V.mjs +0 -31
- package/dist/lib/browser/react-surface-5ENPAK3V.mjs.map +0 -7
- package/dist/lib/node-esm/ExplorerContainer-S5GL733T.mjs +0 -38
- package/dist/lib/node-esm/ExplorerContainer-S5GL733T.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-6M6W7DKH.mjs +0 -189
- package/dist/lib/node-esm/chunk-6M6W7DKH.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-HCV3J2HM.mjs +0 -11331
- package/dist/lib/node-esm/chunk-HCV3J2HM.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-PIAXA43R.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-PPBUWC7F.mjs +0 -32
- package/dist/lib/node-esm/chunk-PPBUWC7F.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-VKCOKQRG.mjs +0 -39
- package/dist/lib/node-esm/chunk-VKCOKQRG.mjs.map +0 -7
- package/dist/lib/node-esm/intent-resolver-AGBBNA67.mjs +0 -25
- package/dist/lib/node-esm/intent-resolver-AGBBNA67.mjs.map +0 -7
- package/dist/lib/node-esm/react-surface-4MDNPYQ6.mjs +0 -32
- package/dist/lib/node-esm/react-surface-4MDNPYQ6.mjs.map +0 -7
- package/dist/types/src/capabilities/intent-resolver.d.ts +0 -4
- package/dist/types/src/capabilities/intent-resolver.d.ts.map +0 -1
- package/dist/types/src/components/ExplorerContainer.d.ts +0 -9
- package/dist/types/src/components/ExplorerContainer.d.ts.map +0 -1
- package/dist/types/src/types/schema.d.ts +0 -12
- package/dist/types/src/types/schema.d.ts.map +0 -1
- package/dist/types/src/types/types.d.ts +0 -18
- package/dist/types/src/types/types.d.ts.map +0 -1
- package/src/capabilities/intent-resolver.ts +0 -19
- package/src/components/ExplorerContainer.tsx +0 -36
- package/src/types/schema.ts +0 -16
- package/src/types/types.ts +0 -21
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import
|
|
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 {
|
|
19
|
-
import {
|
|
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 =
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
|
|
45
|
-
|
|
46
|
-
|
|
48
|
+
forces: {
|
|
49
|
+
point: {
|
|
50
|
+
strength: 0.01,
|
|
51
|
+
},
|
|
47
52
|
},
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
}, [context.current]);
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
}, []);
|
|
52
56
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
-
|
|
91
|
-
|
|
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
|
-
|
|
97
|
-
|
|
98
|
-
</
|
|
99
|
-
|
|
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 '@
|
|
5
|
+
import { type Meta, type StoryObj } from '@storybook/react-vite';
|
|
6
|
+
import React, { useState } from 'react';
|
|
6
7
|
|
|
7
|
-
import {
|
|
8
|
-
import
|
|
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 {
|
|
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 {
|
|
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 =
|
|
27
|
+
const generator = random as any as ValueGenerator;
|
|
25
28
|
|
|
26
|
-
|
|
29
|
+
random.seed(1);
|
|
27
30
|
|
|
28
31
|
const DefaultStory = () => {
|
|
29
32
|
const client = useClient();
|
|
30
33
|
const [space, setSpace] = useState<Space>();
|
|
31
|
-
const [
|
|
32
|
-
|
|
33
|
-
|
|
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 =
|
|
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
|
-
|
|
38
|
-
}, []);
|
|
43
|
+
setGraph(graph);
|
|
44
|
+
}, [client]);
|
|
39
45
|
|
|
40
46
|
const model = useGraphModel(space);
|
|
41
|
-
if (!model || !space || !
|
|
42
|
-
return
|
|
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
|
|
49
|
-
title: 'plugins/plugin-explorer/ForceGraph',
|
|
54
|
+
const meta = {
|
|
55
|
+
title: 'plugins/plugin-explorer/components/ForceGraph',
|
|
50
56
|
component: ForceGraph,
|
|
51
|
-
render:
|
|
57
|
+
render: DefaultStory,
|
|
52
58
|
decorators: [
|
|
59
|
+
withRegistry,
|
|
60
|
+
withTheme(),
|
|
61
|
+
withLayout(),
|
|
53
62
|
withClientProvider({
|
|
54
63
|
createSpace: true,
|
|
55
|
-
types: [
|
|
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
|
-
|
|
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 {
|
|
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 =
|
|
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
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
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.
|
|
11
|
+
const getObject = (objects: Obj.Unknown[]) => objects[Math.floor(Math.random() * objects.length)];
|
|
12
12
|
|
|
13
13
|
const defaultTypes: TypeSpec[] = [
|
|
14
|
-
{ type:
|
|
15
|
-
{ type:
|
|
16
|
-
{ type:
|
|
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'] = {
|
|
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
|
|
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(
|
|
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 '@
|
|
5
|
+
import { type Meta, type StoryObj } from '@storybook/react-vite';
|
|
6
|
+
import React, { useEffect, useState } from 'react';
|
|
6
7
|
|
|
7
|
-
import {
|
|
8
|
-
import
|
|
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/
|
|
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 {
|
|
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
|
-
|
|
21
|
+
random.seed(1);
|
|
22
|
+
|
|
23
|
+
type ComponentProps = ClientRepeatedComponentProps & { type?: TreeComponentProps<any>['variant'] };
|
|
22
24
|
|
|
23
|
-
const
|
|
25
|
+
const Component = ({ type }: ComponentProps) => {
|
|
24
26
|
const client = useClient();
|
|
25
|
-
const space = client.spaces.
|
|
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
|
-
|
|
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
|
|
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 {
|
|
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
|
|
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.
|
|
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 {
|
|
9
|
-
import {
|
|
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
|
-
|
|
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(
|
|
127
|
-
expect(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
|
-
|
|
131
|
+
Obj.change(tree.tree, () => {
|
|
132
|
+
node.ref = Ref.make(task);
|
|
133
|
+
});
|
|
132
134
|
});
|
|
133
135
|
});
|