@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.
- package/dist/lib/neutral/ExplorerContainer-BOXGUSRG.mjs +41 -0
- package/dist/lib/neutral/ExplorerContainer-BOXGUSRG.mjs.map +7 -0
- package/dist/lib/neutral/ExplorerPlugin.mjs +26 -0
- package/dist/lib/neutral/ExplorerPlugin.mjs.map +7 -0
- package/dist/lib/neutral/capabilities/index.mjs +11 -0
- package/dist/lib/neutral/capabilities/index.mjs.map +7 -0
- package/dist/lib/{browser/types/index.mjs → neutral/chunk-7SPMPHRS.mjs} +10 -8
- package/dist/lib/neutral/chunk-7SPMPHRS.mjs.map +7 -0
- package/dist/lib/{browser/chunk-LSUP47BZ.mjs → neutral/chunk-HPIS2WXY.mjs} +1 -1
- package/dist/lib/{browser/chunk-LSUP47BZ.mjs.map → neutral/chunk-HPIS2WXY.mjs.map} +2 -2
- package/dist/lib/{browser → neutral/components}/index.mjs +140 -240
- package/dist/lib/{node-esm → neutral/components}/index.mjs.map +4 -4
- package/dist/lib/neutral/containers/index.mjs +9 -0
- package/dist/lib/neutral/containers/index.mjs.map +7 -0
- package/dist/lib/neutral/create-object-F6TKVAGV.mjs +39 -0
- package/dist/lib/neutral/create-object-F6TKVAGV.mjs.map +7 -0
- package/dist/lib/neutral/hooks/index.mjs +45 -0
- package/dist/lib/neutral/hooks/index.mjs.map +7 -0
- package/dist/lib/neutral/index.mjs +14 -0
- package/dist/lib/neutral/meta.json +1 -0
- package/dist/lib/{browser → neutral}/meta.mjs +1 -1
- package/dist/lib/neutral/plugin.mjs +12 -0
- package/dist/lib/neutral/plugin.mjs.map +7 -0
- package/dist/lib/neutral/react-surface-U3JEY7V7.mjs +26 -0
- package/dist/lib/neutral/react-surface-U3JEY7V7.mjs.map +7 -0
- package/dist/lib/neutral/translations.mjs +33 -0
- package/dist/lib/neutral/translations.mjs.map +7 -0
- package/dist/lib/neutral/types/index.mjs +10 -0
- package/dist/types/data/cities.d.ts +4 -4
- package/dist/types/data/cities.d.ts.map +1 -1
- package/dist/types/data/countries-110m.d.ts +19 -22
- package/dist/types/data/countries-110m.d.ts.map +1 -1
- package/dist/types/src/ExplorerPlugin.d.ts +1 -0
- package/dist/types/src/ExplorerPlugin.d.ts.map +1 -1
- package/dist/types/src/ExplorerPlugin.test.d.ts +2 -0
- package/dist/types/src/ExplorerPlugin.test.d.ts.map +1 -0
- package/dist/types/src/capabilities/create-object.d.ts +11 -0
- package/dist/types/src/capabilities/create-object.d.ts.map +1 -0
- package/dist/types/src/capabilities/index.d.ts +6 -0
- package/dist/types/src/capabilities/index.d.ts.map +1 -1
- 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 +4 -1
- 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 +5 -2
- package/dist/types/src/components/Globe/Globe.stories.d.ts.map +1 -1
- package/dist/types/src/components/Graph/CanvasForceGraph.d.ts +13 -0
- package/dist/types/src/components/Graph/CanvasForceGraph.d.ts.map +1 -0
- package/dist/types/src/components/Graph/{D3ForceGraph.stories.d.ts → CanvasForceGraph.stories.d.ts} +3 -3
- package/dist/types/src/components/Graph/CanvasForceGraph.stories.d.ts.map +1 -0
- package/dist/types/src/components/Graph/ForceGraph.d.ts +12 -5
- package/dist/types/src/components/Graph/ForceGraph.d.ts.map +1 -1
- package/dist/types/src/components/Graph/ForceGraph.stories.d.ts +3 -1
- package/dist/types/src/components/Graph/ForceGraph.stories.d.ts.map +1 -1
- package/dist/types/src/components/Graph/{adapter.d.ts → graph-adapter.d.ts} +1 -1
- package/dist/types/src/components/Graph/graph-adapter.d.ts.map +1 -0
- package/dist/types/src/components/Graph/index.d.ts +1 -1
- package/dist/types/src/components/Graph/index.d.ts.map +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 +5 -1
- package/dist/types/src/components/Tree/Tree.stories.d.ts.map +1 -1
- package/dist/types/src/components/Tree/layout/HierarchicalEdgeBundling.d.ts.map +1 -1
- package/dist/types/src/components/Tree/layout/RadialTree.d.ts.map +1 -1
- package/dist/types/src/components/Tree/layout/TidyTree.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 +6 -6
- package/dist/types/src/components/Tree/types/tree.d.ts.map +1 -1
- package/dist/types/src/components/Tree/types/types.d.ts.map +1 -1
- package/dist/types/src/components/plot.d.ts.map +1 -1
- package/dist/types/src/containers/ExplorerContainer/ExplorerContainer.d.ts +1 -1
- package/dist/types/src/containers/ExplorerContainer/ExplorerContainer.d.ts.map +1 -1
- 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/index.d.ts +1 -3
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/plugin.d.ts +3 -0
- package/dist/types/src/plugin.d.ts.map +1 -0
- package/dist/types/src/translations.d.ts +17 -17
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/src/types/Graph.d.ts +3 -4
- package/dist/types/src/types/Graph.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +94 -51
- package/src/ExplorerPlugin.test.ts +26 -0
- package/src/ExplorerPlugin.tsx +6 -35
- package/src/capabilities/create-object.ts +36 -0
- package/src/capabilities/index.ts +1 -0
- package/src/components/Chart/Chart.stories.tsx +14 -20
- package/src/components/Globe/Globe.stories.tsx +17 -19
- package/src/components/Graph/CanvasForceGraph.stories.tsx +83 -0
- package/src/components/Graph/CanvasForceGraph.tsx +124 -0
- package/src/components/Graph/ForceGraph.stories.tsx +68 -36
- package/src/components/Graph/ForceGraph.tsx +104 -85
- package/src/components/Graph/index.ts +1 -1
- package/src/components/Tree/Tree.stories.tsx +40 -35
- package/src/components/Tree/testing/generator.ts +1 -1
- package/src/components/Tree/types/tree.test.ts +1 -1
- package/src/components/Tree/types/tree.ts +9 -9
- package/src/containers/ExplorerContainer/ExplorerContainer.tsx +10 -13
- package/src/hooks/useGraphModel.ts +10 -6
- package/src/index.ts +1 -4
- package/src/plugin.ts +9 -0
- package/src/translations.ts +1 -1
- package/src/types/ExplorerAction.ts +1 -1
- package/src/types/Graph.ts +2 -3
- package/dist/lib/browser/index.mjs.map +0 -7
- package/dist/lib/browser/meta.json +0 -1
- package/dist/lib/browser/types/index.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-EN3JZNEY.mjs +0 -26
- package/dist/lib/node-esm/chunk-EN3JZNEY.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-HSLMI22Q.mjs +0 -11
- package/dist/lib/node-esm/index.mjs +0 -11375
- package/dist/lib/node-esm/meta.json +0 -1
- package/dist/lib/node-esm/meta.mjs +0 -9
- package/dist/lib/node-esm/types/index.mjs +0 -71
- package/dist/lib/node-esm/types/index.mjs.map +0 -7
- package/dist/types/src/components/Graph/D3ForceGraph.d.ts +0 -15
- package/dist/types/src/components/Graph/D3ForceGraph.d.ts.map +0 -1
- package/dist/types/src/components/Graph/D3ForceGraph.stories.d.ts.map +0 -1
- package/dist/types/src/components/Graph/adapter.d.ts.map +0 -1
- package/src/components/Graph/D3ForceGraph.stories.tsx +0 -83
- package/src/components/Graph/D3ForceGraph.tsx +0 -108
- /package/dist/lib/{browser → neutral}/chunk-J5LGTIGS.mjs +0 -0
- /package/dist/lib/{browser → neutral}/chunk-J5LGTIGS.mjs.map +0 -0
- /package/dist/lib/{browser/meta.mjs.map → neutral/index.mjs.map} +0 -0
- /package/dist/lib/{node-esm → neutral}/meta.mjs.map +0 -0
- /package/dist/lib/{node-esm/chunk-HSLMI22Q.mjs.map → neutral/types/index.mjs.map} +0 -0
- /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
|
|
6
|
+
import * as Effect from 'effect/Effect';
|
|
7
|
+
import React, { useCallback, useMemo } from 'react';
|
|
7
8
|
|
|
8
|
-
import {
|
|
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 {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
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
|
|
33
|
-
const
|
|
34
|
-
const [graph, setGraph] = useState<Graph.Graph>();
|
|
36
|
+
const [space] = useSpaces();
|
|
37
|
+
const model = useGraphModel(space?.db);
|
|
35
38
|
|
|
36
|
-
|
|
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
|
|
47
|
-
|
|
48
|
-
|
|
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
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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 {
|
|
6
|
-
import
|
|
7
|
-
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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
|
+
);
|
|
@@ -3,53 +3,58 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { type Meta, type StoryObj } from '@storybook/react-vite';
|
|
6
|
-
import
|
|
7
|
-
|
|
8
|
-
|
|
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 {
|
|
11
|
-
import {
|
|
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 {
|
|
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
|
|
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
|
-
|
|
38
|
-
|
|
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={
|
|
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: [
|
|
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
|
-
|
|
69
|
+
variant: 'tidy',
|
|
65
70
|
},
|
|
66
71
|
};
|
|
67
72
|
|
|
68
73
|
export const Radial: Story = {
|
|
69
74
|
args: {
|
|
70
|
-
|
|
75
|
+
variant: 'radial',
|
|
71
76
|
},
|
|
72
77
|
};
|
|
73
78
|
|
|
74
79
|
export const Edge: Story = {
|
|
75
80
|
args: {
|
|
76
|
-
|
|
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.
|
|
19
|
+
Obj.update(tree.tree, () => {
|
|
20
20
|
tree.root.data = { text: 'root' };
|
|
21
21
|
});
|
|
22
22
|
|
|
@@ -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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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 {
|
|
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 {
|
|
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
|
|
22
|
-
useObject(
|
|
23
|
-
const
|
|
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(
|
|
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 (!
|
|
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={
|
|
41
|
+
<QueryEditor db={db} onChange={handleChange} />
|
|
45
42
|
</Toolbar.Root>
|
|
46
43
|
</Panel.Toolbar>
|
|
47
44
|
)}
|
|
48
45
|
<Panel.Content asChild>
|
|
49
|
-
<
|
|
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
|
|
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
|
-
|
|
14
|
+
db: Database.Database | undefined,
|
|
15
15
|
filter?: Filter.Any | undefined,
|
|
16
16
|
options?: SpaceGraphModelOptions,
|
|
17
|
-
|
|
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 (!
|
|
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(
|
|
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
|
-
}, [
|
|
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
|
};
|