@dxos/plugin-explorer 0.8.2-main.f11618f → 0.8.2-staging.42af850
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/ExplorerContainer-BBZ54DJS.mjs +37 -0
- package/dist/lib/browser/ExplorerContainer-BBZ54DJS.mjs.map +7 -0
- package/dist/lib/browser/{chunk-RRXH3JYB.mjs → chunk-73GQ46YO.mjs} +415 -155
- package/dist/lib/{node-esm/chunk-VNMYGDGZ.mjs.map → browser/chunk-73GQ46YO.mjs.map} +4 -4
- package/dist/lib/browser/{chunk-QLQLPZNI.mjs → chunk-73YTQHOT.mjs} +12 -11
- package/dist/lib/browser/chunk-73YTQHOT.mjs.map +7 -0
- package/dist/lib/browser/chunk-M2BGAY6H.mjs +177 -0
- package/dist/lib/browser/chunk-M2BGAY6H.mjs.map +7 -0
- package/dist/lib/browser/{chunk-Z2SDLMQM.mjs → chunk-OBAFAA5V.mjs} +3 -3
- package/dist/lib/browser/{chunk-Z2SDLMQM.mjs.map → chunk-OBAFAA5V.mjs.map} +1 -1
- package/dist/lib/browser/chunk-SLB2F5AO.mjs +30 -0
- package/dist/lib/browser/chunk-SLB2F5AO.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +15 -11
- package/dist/lib/browser/index.mjs.map +1 -1
- package/dist/lib/browser/{intent-resolver-P4YLQHXF.mjs → intent-resolver-FJDVBDE3.mjs} +3 -3
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/meta.mjs +1 -1
- package/dist/lib/browser/{react-surface-TPR4VUPE.mjs → react-surface-H3YDMXAQ.mjs} +5 -5
- package/dist/lib/browser/types/index.mjs +2 -2
- package/dist/lib/node/{ExplorerContainer-73AHSBAG.cjs → ExplorerContainer-MVP2AM7R.cjs} +24 -16
- package/dist/lib/node/ExplorerContainer-MVP2AM7R.cjs.map +7 -0
- package/dist/lib/node/chunk-4T4LCT5R.cjs +52 -0
- package/dist/lib/node/chunk-4T4LCT5R.cjs.map +7 -0
- package/dist/lib/node/{chunk-4QUNUHKB.cjs → chunk-72H5HBTK.cjs} +414 -153
- package/dist/lib/node/{chunk-4QUNUHKB.cjs.map → chunk-72H5HBTK.cjs.map} +4 -4
- package/dist/lib/node/{chunk-VB3QE6XY.cjs → chunk-BCDVG2CH.cjs} +6 -6
- package/dist/lib/node/{chunk-VB3QE6XY.cjs.map → chunk-BCDVG2CH.cjs.map} +1 -1
- package/dist/lib/node/{chunk-YLL7H7CZ.cjs → chunk-MLRYW4WQ.cjs} +15 -14
- package/dist/lib/node/chunk-MLRYW4WQ.cjs.map +7 -0
- package/dist/lib/node/chunk-NELWWGBU.cjs +204 -0
- package/dist/lib/node/chunk-NELWWGBU.cjs.map +7 -0
- package/dist/lib/node/index.cjs +34 -31
- package/dist/lib/node/index.cjs.map +1 -1
- package/dist/lib/node/{intent-resolver-T2R4PJVP.cjs → intent-resolver-DRT67ZU4.cjs} +8 -8
- package/dist/lib/node/meta.cjs +3 -3
- package/dist/lib/node/meta.cjs.map +1 -1
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node/{react-surface-OLIOGYOK.cjs → react-surface-6ESLSM33.cjs} +11 -11
- package/dist/lib/node/types/index.cjs +4 -4
- package/dist/lib/node/types/index.cjs.map +1 -1
- package/dist/lib/node-esm/ExplorerContainer-APGUQI4M.mjs +38 -0
- package/dist/lib/node-esm/ExplorerContainer-APGUQI4M.mjs.map +7 -0
- package/dist/lib/node-esm/{chunk-VNMYGDGZ.mjs → chunk-34X2VFQN.mjs} +415 -154
- package/dist/lib/{browser/chunk-RRXH3JYB.mjs.map → node-esm/chunk-34X2VFQN.mjs.map} +4 -4
- package/dist/lib/node-esm/{chunk-PUFSCMN4.mjs → chunk-3CMBLK6W.mjs} +3 -3
- package/dist/lib/node-esm/{chunk-PUFSCMN4.mjs.map → chunk-3CMBLK6W.mjs.map} +1 -1
- package/dist/lib/node-esm/{chunk-QZH2GDN5.mjs → chunk-N6VEANUZ.mjs} +12 -11
- package/dist/lib/node-esm/chunk-N6VEANUZ.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-PVII2K2B.mjs +179 -0
- package/dist/lib/node-esm/chunk-PVII2K2B.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-VSORIAHH.mjs +32 -0
- package/dist/lib/node-esm/chunk-VSORIAHH.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +15 -11
- package/dist/lib/node-esm/index.mjs.map +1 -1
- package/dist/lib/node-esm/{intent-resolver-OMUHLTGU.mjs → intent-resolver-4RBV644N.mjs} +3 -3
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/lib/node-esm/meta.mjs +1 -1
- package/dist/lib/node-esm/{react-surface-QLB55AWT.mjs → react-surface-ZEVL3FXG.mjs} +5 -5
- package/dist/lib/node-esm/types/index.mjs +2 -2
- package/dist/types/src/components/Chart/Chart.d.ts.map +1 -1
- package/dist/types/src/components/ExplorerContainer.d.ts +4 -3
- package/dist/types/src/components/ExplorerContainer.d.ts.map +1 -1
- package/dist/types/src/components/Globe/Globe.d.ts.map +1 -1
- package/dist/types/src/components/Graph/D3ForceGraph.d.ts +14 -0
- package/dist/types/src/components/Graph/D3ForceGraph.d.ts.map +1 -0
- package/dist/types/src/components/Graph/D3ForceGraph.stories.d.ts +6 -0
- package/dist/types/src/components/Graph/D3ForceGraph.stories.d.ts.map +1 -0
- package/dist/types/src/components/Graph/ForceGraph.d.ts +8 -0
- package/dist/types/src/components/Graph/ForceGraph.d.ts.map +1 -0
- package/dist/types/src/components/Graph/{Graph.stories.d.ts → ForceGraph.stories.d.ts} +1 -1
- package/dist/types/src/components/Graph/ForceGraph.stories.d.ts.map +1 -0
- package/dist/types/src/components/Graph/adapter.d.ts +21 -0
- package/dist/types/src/components/Graph/adapter.d.ts.map +1 -0
- package/dist/types/src/components/Graph/index.d.ts +2 -2
- package/dist/types/src/components/Graph/index.d.ts.map +1 -1
- package/dist/types/src/components/Graph/testing.d.ts +14 -0
- package/dist/types/src/components/Graph/testing.d.ts.map +1 -0
- package/dist/types/src/components/Tree/Tree.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 +8 -0
- package/dist/types/src/components/Tree/testing/generator.d.ts.map +1 -0
- package/dist/types/src/components/Tree/testing/index.d.ts +2 -0
- package/dist/types/src/components/Tree/testing/index.d.ts.map +1 -0
- package/dist/types/src/components/Tree/types/index.d.ts +3 -0
- package/dist/types/src/components/Tree/types/index.d.ts.map +1 -0
- package/dist/types/src/components/Tree/types/tree.d.ts +83 -0
- package/dist/types/src/components/Tree/types/tree.d.ts.map +1 -0
- package/dist/types/src/components/Tree/types/tree.test.d.ts +2 -0
- package/dist/types/src/components/Tree/types/tree.test.d.ts.map +1 -0
- package/dist/types/src/components/Tree/types/types.d.ts +8 -0
- package/dist/types/src/components/Tree/types/types.d.ts.map +1 -0
- package/dist/types/src/components/index.d.ts +2 -2
- package/dist/types/src/components/plot.d.ts.map +1 -1
- package/dist/types/src/hooks/index.d.ts +2 -0
- package/dist/types/src/hooks/index.d.ts.map +1 -0
- package/dist/types/src/hooks/useGraphModel.d.ts +4 -0
- package/dist/types/src/hooks/useGraphModel.d.ts.map +1 -0
- package/dist/types/src/index.d.ts +2 -1
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/translations.d.ts +2 -8
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/src/types/schema.d.ts +6 -6
- package/dist/types/src/types/schema.d.ts.map +1 -1
- package/dist/types/src/types/types.d.ts +6 -6
- package/dist/types/src/types/types.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +30 -27
- package/src/components/ExplorerContainer.tsx +11 -4
- package/src/components/Graph/D3ForceGraph.stories.tsx +64 -0
- package/src/components/Graph/D3ForceGraph.tsx +101 -0
- package/src/components/Graph/ForceGraph.stories.tsx +64 -0
- package/src/components/Graph/{Graph.tsx → ForceGraph.tsx} +19 -26
- package/src/components/Graph/adapter.ts +47 -0
- package/src/components/Graph/index.ts +2 -3
- package/src/components/Graph/testing.ts +57 -0
- package/src/components/Tree/Tree.stories.tsx +1 -1
- package/src/components/Tree/Tree.tsx +11 -18
- package/src/components/Tree/testing/generator.ts +46 -0
- package/src/components/Tree/testing/index.ts +5 -0
- package/src/components/Tree/types/index.ts +6 -0
- package/src/components/Tree/types/tree.test.ts +133 -0
- package/src/components/Tree/types/tree.ts +287 -0
- package/src/components/Tree/types/types.ts +41 -0
- package/src/hooks/index.ts +5 -0
- package/src/hooks/useGraphModel.ts +35 -0
- package/src/index.ts +2 -2
- package/src/meta.ts +2 -2
- package/src/types/schema.ts +5 -3
- package/src/types/types.ts +5 -5
- package/dist/lib/browser/ExplorerContainer-HL532ODG.mjs +0 -27
- package/dist/lib/browser/ExplorerContainer-HL532ODG.mjs.map +0 -7
- package/dist/lib/browser/chunk-QLQLPZNI.mjs.map +0 -7
- package/dist/lib/browser/chunk-SBLNE7FL.mjs +0 -205
- package/dist/lib/browser/chunk-SBLNE7FL.mjs.map +0 -7
- package/dist/lib/node/ExplorerContainer-73AHSBAG.cjs.map +0 -7
- package/dist/lib/node/chunk-OIHH6TVE.cjs +0 -236
- package/dist/lib/node/chunk-OIHH6TVE.cjs.map +0 -7
- package/dist/lib/node/chunk-YLL7H7CZ.cjs.map +0 -7
- package/dist/lib/node-esm/ExplorerContainer-NMI55PYM.mjs +0 -28
- package/dist/lib/node-esm/ExplorerContainer-NMI55PYM.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-QZH2GDN5.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-SZRRNWYT.mjs +0 -207
- package/dist/lib/node-esm/chunk-SZRRNWYT.mjs.map +0 -7
- package/dist/types/src/components/Graph/Graph.d.ts +0 -8
- package/dist/types/src/components/Graph/Graph.d.ts.map +0 -1
- package/dist/types/src/components/Graph/Graph.stories.d.ts.map +0 -1
- package/dist/types/src/components/Graph/graph-model.d.ts +0 -39
- package/dist/types/src/components/Graph/graph-model.d.ts.map +0 -1
- package/dist/types/src/components/Tree/types.d.ts +0 -8
- package/dist/types/src/components/Tree/types.d.ts.map +0 -1
- package/src/components/Graph/Graph.stories.tsx +0 -62
- package/src/components/Graph/graph-model.ts +0 -193
- package/src/components/Tree/types.ts +0 -40
- /package/dist/lib/browser/{intent-resolver-P4YLQHXF.mjs.map → intent-resolver-FJDVBDE3.mjs.map} +0 -0
- /package/dist/lib/browser/{react-surface-TPR4VUPE.mjs.map → react-surface-H3YDMXAQ.mjs.map} +0 -0
- /package/dist/lib/node/{intent-resolver-T2R4PJVP.cjs.map → intent-resolver-DRT67ZU4.cjs.map} +0 -0
- /package/dist/lib/node/{react-surface-OLIOGYOK.cjs.map → react-surface-6ESLSM33.cjs.map} +0 -0
- /package/dist/lib/node-esm/{intent-resolver-OMUHLTGU.mjs.map → intent-resolver-4RBV644N.mjs.map} +0 -0
- /package/dist/lib/node-esm/{react-surface-QLB55AWT.mjs.map → react-surface-ZEVL3FXG.mjs.map} +0 -0
|
@@ -2,16 +2,15 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import React, { useEffect, useState } from 'react';
|
|
6
|
-
import { useResizeDetector } from 'react-resize-detector';
|
|
5
|
+
import React, { useEffect, useRef, useState } from 'react';
|
|
7
6
|
|
|
8
7
|
import { type Space } from '@dxos/client/echo';
|
|
9
|
-
import { createSvgContext, SVG, SVGRoot } from '@dxos/gem-core';
|
|
10
8
|
import { useAsyncState } from '@dxos/react-ui';
|
|
9
|
+
import { SVG, type SVGContext } from '@dxos/react-ui-graph';
|
|
10
|
+
import { SpaceGraphModel } from '@dxos/schema';
|
|
11
11
|
|
|
12
12
|
import { HierarchicalEdgeBundling, RadialTree, TidyTree } from './layout';
|
|
13
13
|
import { mapGraphToTreeData, type TreeNode } from './types';
|
|
14
|
-
import { SpaceGraphModel } from '../Graph';
|
|
15
14
|
|
|
16
15
|
// TODO(burdon): Create dge bundling graph using d3.hierarchy.
|
|
17
16
|
// https://observablehq.com/@d3/hierarchical-edge-bundling?intent=fork
|
|
@@ -63,10 +62,7 @@ export type TreeComponentProps<N = unknown> = {
|
|
|
63
62
|
|
|
64
63
|
// TODO(burdon): Label accessor.
|
|
65
64
|
export const Tree = <N,>({ space, selected, variant = 'tidy', onNodeClick }: TreeComponentProps<N>) => {
|
|
66
|
-
const [model] = useAsyncState(
|
|
67
|
-
async () => (space ? new SpaceGraphModel().open(space, selected) : undefined),
|
|
68
|
-
[space, selected],
|
|
69
|
-
);
|
|
65
|
+
const [model] = useAsyncState(async () => (space ? new SpaceGraphModel().open(space) : undefined), [space, selected]);
|
|
70
66
|
|
|
71
67
|
const [tree, setTree] = useState<TreeNode>();
|
|
72
68
|
useEffect(() => {
|
|
@@ -76,11 +72,11 @@ export const Tree = <N,>({ space, selected, variant = 'tidy', onNodeClick }: Tre
|
|
|
76
72
|
}, true);
|
|
77
73
|
}, [model]);
|
|
78
74
|
|
|
79
|
-
const context =
|
|
80
|
-
const { ref, width = 0, height = 0 } = useResizeDetector();
|
|
75
|
+
const context = useRef<SVGContext>(null);
|
|
81
76
|
|
|
82
77
|
useEffect(() => {
|
|
83
|
-
if (
|
|
78
|
+
if (context.current) {
|
|
79
|
+
const { width, height } = context.current.size!;
|
|
84
80
|
const size = Math.min(width, height);
|
|
85
81
|
const radius = size * 0.4;
|
|
86
82
|
const options = {
|
|
@@ -98,17 +94,14 @@ export const Tree = <N,>({ space, selected, variant = 'tidy', onNodeClick }: Tre
|
|
|
98
94
|
|
|
99
95
|
if (tree) {
|
|
100
96
|
const renderer = renderers.get(variant);
|
|
101
|
-
renderer?.(context.
|
|
97
|
+
renderer?.(context.current!.svg, tree, options);
|
|
102
98
|
}
|
|
103
99
|
}
|
|
104
|
-
}, [
|
|
100
|
+
}, [context.current, tree]);
|
|
105
101
|
|
|
106
|
-
// TODO(burdon): Provider should expand.
|
|
107
102
|
return (
|
|
108
|
-
<div
|
|
109
|
-
<
|
|
110
|
-
<SVG />
|
|
111
|
-
</SVGRoot>
|
|
103
|
+
<div onClick={() => onNodeClick?.()}>
|
|
104
|
+
<SVG.Root ref={context} />
|
|
112
105
|
</div>
|
|
113
106
|
);
|
|
114
107
|
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { ObjectId } from '@dxos/echo-schema';
|
|
6
|
+
import { range } from '@dxos/util';
|
|
7
|
+
|
|
8
|
+
import { Tree, type TreeNodeType } from '../types';
|
|
9
|
+
|
|
10
|
+
type NumberOrNumberArray = number | number[];
|
|
11
|
+
|
|
12
|
+
const random = (min: number, max: number) => Math.floor(Math.random() * (max - min + 1)) + min;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Create hierarchical tree.
|
|
16
|
+
*/
|
|
17
|
+
export const createTree = (spec: NumberOrNumberArray[] = [], createText?: () => string): Tree => {
|
|
18
|
+
const tree = new Tree();
|
|
19
|
+
tree.root.data = { text: 'root' };
|
|
20
|
+
|
|
21
|
+
const createNodes = (parent: TreeNodeType, spec: NumberOrNumberArray = 0): TreeNodeType[] => {
|
|
22
|
+
const count = Array.isArray(spec) ? random(spec[0], spec[1]) : spec;
|
|
23
|
+
return range(count, (i) => ({
|
|
24
|
+
id: ObjectId.random(),
|
|
25
|
+
children: [],
|
|
26
|
+
data: {
|
|
27
|
+
text: createText?.() ?? [parent.data.text, i + 1].join('.'),
|
|
28
|
+
},
|
|
29
|
+
}));
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const createChildNodes = (parent: TreeNodeType, [count = 0, ...rest]: NumberOrNumberArray[]): TreeNodeType => {
|
|
33
|
+
const nodes = createNodes(parent, count);
|
|
34
|
+
nodes.forEach((n) => tree.addNode(parent, n));
|
|
35
|
+
if (rest.length) {
|
|
36
|
+
for (const node of nodes) {
|
|
37
|
+
createChildNodes(node, rest);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return parent;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
createChildNodes(tree.root, spec);
|
|
45
|
+
return tree;
|
|
46
|
+
};
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { describe, test } from 'vitest';
|
|
6
|
+
|
|
7
|
+
import { live, makeRef } from '@dxos/live-object';
|
|
8
|
+
import { faker } from '@dxos/random';
|
|
9
|
+
import { DataType } from '@dxos/schema';
|
|
10
|
+
|
|
11
|
+
import { type Tree } from './tree';
|
|
12
|
+
import { createTree } from '../testing';
|
|
13
|
+
|
|
14
|
+
faker.seed(0);
|
|
15
|
+
|
|
16
|
+
const print = (tree: Tree) => {
|
|
17
|
+
let count = 0;
|
|
18
|
+
tree.tranverse((node, i) => {
|
|
19
|
+
console.log(''.padStart(i * 2, ' '), node.data);
|
|
20
|
+
count++;
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
return count;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
describe('tree', () => {
|
|
27
|
+
test('tree', ({ expect }) => {
|
|
28
|
+
{
|
|
29
|
+
const tree = createTree();
|
|
30
|
+
let count = 0;
|
|
31
|
+
tree.tranverse(() => {
|
|
32
|
+
count++;
|
|
33
|
+
});
|
|
34
|
+
expect(count).to.eq(tree.size);
|
|
35
|
+
expect(count).to.eq(1);
|
|
36
|
+
}
|
|
37
|
+
{
|
|
38
|
+
const tree = createTree([10]);
|
|
39
|
+
let count = 0;
|
|
40
|
+
tree.tranverse(() => {
|
|
41
|
+
count++;
|
|
42
|
+
});
|
|
43
|
+
expect(count).to.eq(tree.size);
|
|
44
|
+
expect(count).to.eq(1 + 10);
|
|
45
|
+
}
|
|
46
|
+
{
|
|
47
|
+
const tree = createTree([10, 3, 1]);
|
|
48
|
+
let count = 0;
|
|
49
|
+
tree.tranverse(() => {
|
|
50
|
+
count++;
|
|
51
|
+
});
|
|
52
|
+
expect(count).to.eq(tree.size);
|
|
53
|
+
expect(count).to.eq(1 + 10 * (1 + 3 * (1 + 1))); // 71
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test('tree navigation', ({ expect }) => {
|
|
58
|
+
const tree = createTree([2, 3, 1]);
|
|
59
|
+
expect(tree.getParent(tree.root)).to.be.null;
|
|
60
|
+
|
|
61
|
+
const nodes = tree.getChildNodes(tree.root);
|
|
62
|
+
expect(nodes).to.have.length(2);
|
|
63
|
+
|
|
64
|
+
const first = nodes[0];
|
|
65
|
+
expect(first.children).to.have.length(3);
|
|
66
|
+
const parent = tree.getParent(first);
|
|
67
|
+
expect(parent).to.eq(tree.root);
|
|
68
|
+
|
|
69
|
+
const [c1, c2, c3] = tree.getChildNodes(first);
|
|
70
|
+
expect(tree.getParent(c1)).to.eq(first);
|
|
71
|
+
expect(tree.getParent(c2)).to.eq(first);
|
|
72
|
+
expect(tree.getParent(c3)).to.eq(first);
|
|
73
|
+
|
|
74
|
+
const [g1] = tree.getChildNodes(c1);
|
|
75
|
+
expect(tree.getParent(g1)).to.eq(c1);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* root root root
|
|
80
|
+
* └── 1 └── 1 └── 1
|
|
81
|
+
* ├── 1.1 ├── 1.1 ├── 1.1
|
|
82
|
+
* ├── 1.2 <- indent │ ├── 1.2 <- unindent ├── 1.2
|
|
83
|
+
* ├── 1.3 <- indent │ └── 1.3 │ └── 1.3
|
|
84
|
+
* ├── 1.4 ├── 1.4 ├── 1.4
|
|
85
|
+
* └── 1.5 └── 1.5 └── 1.5
|
|
86
|
+
*/
|
|
87
|
+
test('indent and unindent', async ({ expect }) => {
|
|
88
|
+
const tree = createTree([1, 5]);
|
|
89
|
+
const parent = tree.getNode(tree.root.children[0]);
|
|
90
|
+
|
|
91
|
+
{
|
|
92
|
+
const count = print(tree);
|
|
93
|
+
expect(count).to.eq(1 + 1 * (1 + 5));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Indent
|
|
97
|
+
{
|
|
98
|
+
const child = tree.getChildNodes(parent);
|
|
99
|
+
tree.indentNode(child[1]);
|
|
100
|
+
tree.indentNode(child[2]);
|
|
101
|
+
expect(parent.children).to.have.length(3);
|
|
102
|
+
expect(child[0].children).to.have.length(2);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
{
|
|
106
|
+
const count = print(tree);
|
|
107
|
+
expect(count).to.eq(1 + 1 * (1 + 5));
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Unindent
|
|
111
|
+
{
|
|
112
|
+
const child = tree.getChildNodes(parent);
|
|
113
|
+
const grandchild = tree.getChildNodes(child[0]);
|
|
114
|
+
tree.unindentNode(grandchild[0]);
|
|
115
|
+
expect(grandchild[0].children).to.have.length(1);
|
|
116
|
+
expect(parent.children).to.have.length(4);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
{
|
|
120
|
+
const count = print(tree);
|
|
121
|
+
expect(count).to.eq(1 + 1 * (1 + 5));
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test('task', ({ expect }) => {
|
|
126
|
+
const task = live(DataType.Task, { text: 'Test task.' });
|
|
127
|
+
expect(task.text).to.eq('Test task.');
|
|
128
|
+
|
|
129
|
+
const tree = createTree();
|
|
130
|
+
const node = tree.addNode(tree.root);
|
|
131
|
+
node.ref = makeRef(task);
|
|
132
|
+
});
|
|
133
|
+
});
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { Schema } from 'effect';
|
|
6
|
+
|
|
7
|
+
import { Type } from '@dxos/echo';
|
|
8
|
+
import { ObjectId, Ref, Expando } from '@dxos/echo-schema';
|
|
9
|
+
import { invariant } from '@dxos/invariant';
|
|
10
|
+
import { live } from '@dxos/live-object';
|
|
11
|
+
|
|
12
|
+
// TODO(burdon): Reconcile with @dxos/graph (i.e., common types).
|
|
13
|
+
|
|
14
|
+
export const TreeNodeType = Schema.Struct({
|
|
15
|
+
id: ObjectId,
|
|
16
|
+
children: Schema.mutable(Schema.Array(ObjectId)),
|
|
17
|
+
data: Schema.mutable(Schema.Record({ key: Schema.String, value: Schema.Any })),
|
|
18
|
+
ref: Schema.optional(Ref(Expando)), // TODO(burdon): Generic type?
|
|
19
|
+
}).pipe(Schema.mutable);
|
|
20
|
+
|
|
21
|
+
export interface TreeNodeType extends Schema.Schema.Type<typeof TreeNodeType> {}
|
|
22
|
+
|
|
23
|
+
export const TreeType = Schema.Struct({
|
|
24
|
+
root: ObjectId,
|
|
25
|
+
nodes: Schema.mutable(Schema.Record({ key: ObjectId, value: TreeNodeType })),
|
|
26
|
+
}).pipe(
|
|
27
|
+
Type.Obj({
|
|
28
|
+
typename: 'dxos.org/type/Tree',
|
|
29
|
+
version: '0.1.0',
|
|
30
|
+
}),
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
export interface TreeType extends Schema.Schema.Type<typeof TreeType> {}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Wrapper object for tree.
|
|
37
|
+
*/
|
|
38
|
+
export class Tree {
|
|
39
|
+
static create = (): TreeType => {
|
|
40
|
+
const id = ObjectId.random();
|
|
41
|
+
return live(TreeType, {
|
|
42
|
+
root: id,
|
|
43
|
+
nodes: {
|
|
44
|
+
[id]: {
|
|
45
|
+
id,
|
|
46
|
+
children: [],
|
|
47
|
+
data: { text: '' }, // TODO(burdon): Generic.
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
private _tree: TreeType;
|
|
54
|
+
|
|
55
|
+
constructor(tree?: TreeType) {
|
|
56
|
+
this._tree = tree ?? Tree.create();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
get tree() {
|
|
60
|
+
return this._tree;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// TODO(burdon): Make reactive.
|
|
64
|
+
get size() {
|
|
65
|
+
return Object.keys(this._tree.nodes).length;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
get root() {
|
|
69
|
+
return this.getNode(this._tree.root);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
//
|
|
73
|
+
// Traversal
|
|
74
|
+
//
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Recursively traverse the tree until the callback returns a value.
|
|
78
|
+
*/
|
|
79
|
+
tranverse<T>(
|
|
80
|
+
callback: (node: TreeNodeType, depth: number) => T | void,
|
|
81
|
+
root: ObjectId = this._tree.root,
|
|
82
|
+
depth = 0,
|
|
83
|
+
): T | void {
|
|
84
|
+
const node = this._tree.nodes[root];
|
|
85
|
+
const result = callback(node, depth);
|
|
86
|
+
if (result !== undefined) {
|
|
87
|
+
return result;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
for (const childId of node.children) {
|
|
91
|
+
const result = this.tranverse(callback, childId, depth + 1);
|
|
92
|
+
if (result !== undefined) {
|
|
93
|
+
return result;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
getNode(id: ObjectId): TreeNodeType {
|
|
99
|
+
const node = this._tree.nodes[id];
|
|
100
|
+
invariant(node);
|
|
101
|
+
return node;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Get the children of a node.
|
|
106
|
+
*/
|
|
107
|
+
getChildNodes(node: TreeNodeType): Array<TreeNodeType> {
|
|
108
|
+
return node.children.map((id) => this.getNode(id));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Get the parent of a node.
|
|
113
|
+
*/
|
|
114
|
+
getParent(node: TreeNodeType): TreeNodeType | null {
|
|
115
|
+
const parent = this.tranverse((n) => {
|
|
116
|
+
if (n.children.includes(node.id)) {
|
|
117
|
+
return n;
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
return parent ?? null;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Get the next node in the tree.
|
|
126
|
+
*/
|
|
127
|
+
getNext(node: TreeNodeType, hierarchical = true): TreeNodeType | undefined {
|
|
128
|
+
if (hierarchical && node.children.length) {
|
|
129
|
+
// First child.
|
|
130
|
+
return this.getChildNodes(node)[0];
|
|
131
|
+
} else {
|
|
132
|
+
const parent = this.getParent(node);
|
|
133
|
+
if (parent) {
|
|
134
|
+
const idx = this.getChildNodes(parent).findIndex(({ id }) => id === node.id);
|
|
135
|
+
if (idx < parent.children.length - 1) {
|
|
136
|
+
// Next sibling.
|
|
137
|
+
return this.getNode(parent.children[idx + 1]);
|
|
138
|
+
} else {
|
|
139
|
+
// Get parent's next sibling.
|
|
140
|
+
return this.getNext(parent, false);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Get the previous node in the tree.
|
|
148
|
+
*/
|
|
149
|
+
getPrevious(node: TreeNodeType, hierarchical = true): TreeNodeType | undefined {
|
|
150
|
+
const parent = this.getParent(node)!;
|
|
151
|
+
const idx = this.getChildNodes(parent).findIndex(({ id }) => id === node.id);
|
|
152
|
+
if (idx === 0) {
|
|
153
|
+
if (hierarchical) {
|
|
154
|
+
return parent;
|
|
155
|
+
}
|
|
156
|
+
} else {
|
|
157
|
+
const previous = this.getNode(parent.children[idx - 1]);
|
|
158
|
+
if (hierarchical && previous.children.length) {
|
|
159
|
+
return this.getLastDescendent(previous);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return previous;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Get the last descendent of a node.
|
|
168
|
+
*/
|
|
169
|
+
getLastDescendent(node: TreeNodeType): TreeNodeType | undefined {
|
|
170
|
+
const children = this.getChildNodes(node);
|
|
171
|
+
const last = children.length ? children[children.length - 1] : undefined;
|
|
172
|
+
if (last) {
|
|
173
|
+
return this.getLastDescendent(last);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return node;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
//
|
|
180
|
+
// Mutations
|
|
181
|
+
//
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Clear tree.
|
|
185
|
+
*/
|
|
186
|
+
clear(): void {
|
|
187
|
+
const root = this._tree.nodes[this._tree.root];
|
|
188
|
+
root.children.length = 0;
|
|
189
|
+
this._tree.nodes = {
|
|
190
|
+
[root.id]: root,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Add node.
|
|
196
|
+
*/
|
|
197
|
+
addNode(parent: TreeNodeType, node?: TreeNodeType, index?: number): TreeNodeType {
|
|
198
|
+
if (!node) {
|
|
199
|
+
const id = ObjectId.random();
|
|
200
|
+
node = { id, children: [], data: { text: '' } }; // TODO(burdon): Generic.
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
this._tree.nodes[node.id] = node;
|
|
204
|
+
parent.children.splice(index ?? parent.children.length, 0, node.id);
|
|
205
|
+
return node;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Delete node.
|
|
210
|
+
*/
|
|
211
|
+
deleteNode(parent: TreeNodeType, id: ObjectId): TreeNodeType | undefined {
|
|
212
|
+
const node = this._tree.nodes[id];
|
|
213
|
+
if (!node) {
|
|
214
|
+
return undefined;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
delete this._tree.nodes[node.id];
|
|
218
|
+
const idx = parent.children.findIndex((child) => child === id);
|
|
219
|
+
if (idx !== -1) {
|
|
220
|
+
parent.children.splice(idx, 1);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return node;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Move child node.
|
|
228
|
+
*/
|
|
229
|
+
moveNode(node: TreeNodeType, from: number, to: number): TreeNodeType | null {
|
|
230
|
+
invariant(from >= 0 && from < node.children.length);
|
|
231
|
+
invariant(to >= 0 && to < node.children.length);
|
|
232
|
+
if (from === to) {
|
|
233
|
+
return null;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const child = node.children[from];
|
|
237
|
+
node.children.splice(from, 1);
|
|
238
|
+
node.children.splice(to, 0, child);
|
|
239
|
+
return this.getNode(child);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Indent node.
|
|
244
|
+
*/
|
|
245
|
+
indentNode(node: TreeNodeType): void {
|
|
246
|
+
const parent = this.getParent(node);
|
|
247
|
+
if (!parent) {
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const idx = parent.children.findIndex((child) => child === node.id);
|
|
252
|
+
if (idx < 1 || idx >= parent.children.length) {
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const previous = this.getNode(parent.children[idx - 1]);
|
|
257
|
+
parent.children.splice(idx, 1);
|
|
258
|
+
previous.children.push(node.id);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Unindent node.
|
|
263
|
+
*/
|
|
264
|
+
unindentNode(node: TreeNodeType): void {
|
|
265
|
+
const parent = this.getParent(node);
|
|
266
|
+
if (!parent) {
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const ancestor = this.getParent(parent);
|
|
271
|
+
if (!ancestor) {
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Remove node from parent.
|
|
276
|
+
const nodeIdx = parent.children.findIndex((id) => id === node.id);
|
|
277
|
+
const [_, ...rest] = parent.children.splice(nodeIdx, parent.children.length - nodeIdx);
|
|
278
|
+
parent.children.splice(nodeIdx, parent.children.length - nodeIdx);
|
|
279
|
+
|
|
280
|
+
// Add to ancestor.
|
|
281
|
+
const parentIdx = this.getChildNodes(ancestor).findIndex((n) => n.id === parent.id);
|
|
282
|
+
ancestor.children.splice(parentIdx + 1, 0, node.id);
|
|
283
|
+
|
|
284
|
+
// Transplant following siblings to current node.
|
|
285
|
+
node.children.push(...rest);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { type GraphModel } from '@dxos/graph';
|
|
6
|
+
|
|
7
|
+
export type TreeNode = {
|
|
8
|
+
id: string;
|
|
9
|
+
label?: string;
|
|
10
|
+
children?: TreeNode[];
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export const mapGraphToTreeData = (model: GraphModel, maxDepth = 8): TreeNode | undefined => {
|
|
14
|
+
// TODO(burdon): Convert to common/graph.
|
|
15
|
+
// const mapNode = (node: N, depth = 0): TreeNode => {
|
|
16
|
+
// const treeNode: TreeNode = {
|
|
17
|
+
// id: model.idAccessor(node),
|
|
18
|
+
// label: model.idAccessor(node).slice(0, 8),
|
|
19
|
+
// };
|
|
20
|
+
|
|
21
|
+
// const links = model.graph.links.filter((link) => link.source === treeNode.id);
|
|
22
|
+
// if (depth < maxDepth) {
|
|
23
|
+
// treeNode.children = links.map((link) =>
|
|
24
|
+
// mapNode(model.graph.nodes.find((node) => model.idAccessor(node) === link.target)!, depth + 1),
|
|
25
|
+
// );
|
|
26
|
+
// }
|
|
27
|
+
|
|
28
|
+
// return treeNode;
|
|
29
|
+
// };
|
|
30
|
+
|
|
31
|
+
let data: TreeNode | undefined;
|
|
32
|
+
// TODO(burdon): Selection model.
|
|
33
|
+
// if (model.selected) {
|
|
34
|
+
// const node = model.graph.nodes.find((node) => model.idAccessor(node) === model.selected);
|
|
35
|
+
// if (node) {
|
|
36
|
+
// data = mapNode(node);
|
|
37
|
+
// }
|
|
38
|
+
// }
|
|
39
|
+
|
|
40
|
+
return data;
|
|
41
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { useEffect, useState } from 'react';
|
|
6
|
+
|
|
7
|
+
import { type Filter, type Space } from '@dxos/client/echo';
|
|
8
|
+
import { SpaceGraphModel, type SpaceGraphModelOptions } from '@dxos/schema';
|
|
9
|
+
|
|
10
|
+
// TODO(burdon): Factor out.
|
|
11
|
+
export const useGraphModel = (
|
|
12
|
+
space: Space | undefined,
|
|
13
|
+
filter?: Filter.Any | undefined,
|
|
14
|
+
options?: SpaceGraphModelOptions,
|
|
15
|
+
): SpaceGraphModel | undefined => {
|
|
16
|
+
const [model, setModel] = useState<SpaceGraphModel | undefined>(undefined);
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
if (!space) {
|
|
19
|
+
void model?.close();
|
|
20
|
+
setModel(undefined);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// TODO(burdon): Does this need to be a dependency?
|
|
25
|
+
if (!model) {
|
|
26
|
+
const model = new SpaceGraphModel().setFilter(filter).setOptions(options);
|
|
27
|
+
void model.open(space);
|
|
28
|
+
setModel(model);
|
|
29
|
+
} else {
|
|
30
|
+
model.setFilter(filter).setOptions(options);
|
|
31
|
+
}
|
|
32
|
+
}, [space, filter, options]);
|
|
33
|
+
|
|
34
|
+
return model;
|
|
35
|
+
};
|
package/src/index.ts
CHANGED
package/src/meta.ts
CHANGED
|
@@ -11,7 +11,7 @@ export const meta: PluginMeta = {
|
|
|
11
11
|
name: 'Explorer',
|
|
12
12
|
description: 'Install this plugin to view a hypergraph of all objects inside of your Space.',
|
|
13
13
|
icon: 'ph--graph--regular',
|
|
14
|
-
source: 'https://github.com/dxos/dxos/tree/main/packages/plugins/
|
|
15
|
-
tags: ['
|
|
14
|
+
source: 'https://github.com/dxos/dxos/tree/main/packages/plugins/plugin-explorer',
|
|
15
|
+
tags: ['labs'],
|
|
16
16
|
screenshots: ['https://dxos.network/plugin-details-explorer-dark.png'],
|
|
17
17
|
};
|
package/src/types/schema.ts
CHANGED
|
@@ -2,13 +2,15 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { Schema } from 'effect';
|
|
6
|
+
|
|
7
|
+
import { TypedObject } from '@dxos/echo-schema';
|
|
6
8
|
|
|
7
9
|
// TODO(burdon): Clashes with sdk/view.
|
|
8
10
|
export class ViewType extends TypedObject({
|
|
9
11
|
typename: 'dxos.org/type/ExplorerView',
|
|
10
12
|
version: '0.1.0',
|
|
11
13
|
})({
|
|
12
|
-
name:
|
|
13
|
-
type:
|
|
14
|
+
name: Schema.optional(Schema.String),
|
|
15
|
+
type: Schema.String,
|
|
14
16
|
}) {}
|
package/src/types/types.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { Schema } from 'effect';
|
|
6
6
|
|
|
7
7
|
import { ViewType } from './schema';
|
|
8
8
|
import { EXPLORER_PLUGIN } from '../meta';
|
|
@@ -10,11 +10,11 @@ import { EXPLORER_PLUGIN } from '../meta';
|
|
|
10
10
|
export namespace ExplorerAction {
|
|
11
11
|
const EXPLORER_ACTION = `${EXPLORER_PLUGIN}/action`;
|
|
12
12
|
|
|
13
|
-
export class Create extends
|
|
14
|
-
input:
|
|
15
|
-
name:
|
|
13
|
+
export class Create extends Schema.TaggedClass<Create>()(`${EXPLORER_ACTION}/create`, {
|
|
14
|
+
input: Schema.Struct({
|
|
15
|
+
name: Schema.optional(Schema.String),
|
|
16
16
|
}),
|
|
17
|
-
output:
|
|
17
|
+
output: Schema.Struct({
|
|
18
18
|
object: ViewType,
|
|
19
19
|
}),
|
|
20
20
|
}) {}
|