@dxos/plugin-navtree 0.6.14-main.2b6a0f3 → 0.6.14-main.f49f251
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/index.mjs +576 -386
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/types/src/NavTreePlugin.d.ts.map +1 -1
- package/dist/types/src/components/NavTree.d.ts +20 -0
- package/dist/types/src/components/NavTree.d.ts.map +1 -0
- package/dist/types/src/components/NavTree.stories.d.ts +7 -0
- package/dist/types/src/components/NavTree.stories.d.ts.map +1 -0
- package/dist/types/src/components/NavTreeContainer.d.ts +8 -8
- package/dist/types/src/components/NavTreeContainer.d.ts.map +1 -1
- package/dist/types/src/components/NavTreeItemAction.d.ts +23 -0
- package/dist/types/src/components/NavTreeItemAction.d.ts.map +1 -0
- package/dist/types/src/components/NavTreeItemAction.stories.d.ts +45 -0
- package/dist/types/src/components/NavTreeItemAction.stories.d.ts.map +1 -0
- package/dist/types/src/components/index.d.ts +1 -0
- package/dist/types/src/components/index.d.ts.map +1 -1
- package/dist/types/src/stories/Graph.stories.d.ts +1 -6
- package/dist/types/src/stories/Graph.stories.d.ts.map +1 -1
- package/dist/types/src/testing/generator.d.ts +86 -0
- package/dist/types/src/testing/generator.d.ts.map +1 -0
- package/dist/types/src/testing/index.d.ts +3 -0
- package/dist/types/src/testing/index.d.ts.map +1 -0
- package/dist/types/src/testing/visitor.d.ts +18 -0
- package/dist/types/src/testing/visitor.d.ts.map +1 -0
- package/dist/types/src/translations.d.ts +5 -10
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/src/types.d.ts +41 -0
- package/dist/types/src/types.d.ts.map +1 -0
- package/dist/types/src/util.d.ts +8 -9
- package/dist/types/src/util.d.ts.map +1 -1
- package/package.json +36 -33
- package/src/NavTreePlugin.tsx +88 -33
- package/src/components/NavTree.stories.tsx +104 -0
- package/src/components/NavTree.tsx +127 -0
- package/src/components/NavTreeContainer.tsx +141 -325
- package/src/components/NavTreeItemAction.stories.tsx +57 -0
- package/src/components/NavTreeItemAction.tsx +367 -0
- package/src/components/index.ts +1 -0
- package/src/experimental/Tree.stories.tsx +1 -1
- package/src/stories/Graph.stories.tsx +104 -98
- package/src/testing/generator.ts +199 -0
- package/src/testing/index.ts +6 -0
- package/src/testing/visitor.ts +152 -0
- package/src/translations.ts +3 -3
- package/src/types.ts +50 -0
- package/src/util.ts +52 -63
package/package.json
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dxos/plugin-navtree",
|
|
3
|
-
"version": "0.6.14-main.
|
|
3
|
+
"version": "0.6.14-main.f49f251",
|
|
4
4
|
"description": "DXOS Surface plugin for a tree view visualization of the graph.",
|
|
5
5
|
"homepage": "https://dxos.org",
|
|
6
6
|
"bugs": "https://github.com/dxos/dxos/issues",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"author": "DXOS.org",
|
|
9
|
-
"sideEffects":
|
|
9
|
+
"sideEffects": true,
|
|
10
10
|
"exports": {
|
|
11
11
|
".": {
|
|
12
|
-
"
|
|
13
|
-
"
|
|
12
|
+
"types": "./dist/types/src/index.d.ts",
|
|
13
|
+
"browser": "./dist/lib/browser/index.mjs"
|
|
14
14
|
},
|
|
15
15
|
"./meta": {
|
|
16
|
-
"
|
|
17
|
-
"
|
|
16
|
+
"types": "./dist/types/src/meta.d.ts",
|
|
17
|
+
"browser": "./dist/lib/browser/meta.mjs"
|
|
18
18
|
}
|
|
19
19
|
},
|
|
20
20
|
"types": "dist/types/src/index.d.ts",
|
|
@@ -30,28 +30,31 @@
|
|
|
30
30
|
"src"
|
|
31
31
|
],
|
|
32
32
|
"dependencies": {
|
|
33
|
+
"@atlaskit/pragmatic-drag-and-drop": "^1.4.0",
|
|
34
|
+
"@atlaskit/pragmatic-drag-and-drop-hitbox": "^1.0.3",
|
|
33
35
|
"@preact/signals-core": "^1.6.0",
|
|
36
|
+
"@radix-ui/react-use-controllable-state": "^1.0.0",
|
|
34
37
|
"date-fns": "^3.3.1",
|
|
35
|
-
"@dxos/app-
|
|
36
|
-
"@dxos/app-
|
|
37
|
-
"@dxos/async": "0.6.14-main.
|
|
38
|
-
"@dxos/
|
|
39
|
-
"@dxos/
|
|
40
|
-
"@dxos/
|
|
41
|
-
"@dxos/local-storage": "0.6.14-main.
|
|
42
|
-
"@dxos/
|
|
43
|
-
"@dxos/
|
|
44
|
-
"@dxos/plugin-
|
|
45
|
-
"@dxos/plugin-observability": "0.6.14-main.
|
|
46
|
-
"@dxos/plugin-theme": "0.6.14-main.
|
|
47
|
-
"@dxos/protocols": "0.6.14-main.
|
|
48
|
-
"@dxos/react-ui-deck": "0.6.14-main.
|
|
49
|
-
"@dxos/react-client": "0.6.14-main.
|
|
50
|
-
"@dxos/
|
|
51
|
-
"@dxos/react-ui-
|
|
52
|
-
"@dxos/react-ui-
|
|
53
|
-
"@dxos/react-ui-searchlist": "0.6.14-main.
|
|
54
|
-
"@dxos/util": "0.6.14-main.
|
|
38
|
+
"@dxos/app-graph": "0.6.14-main.f49f251",
|
|
39
|
+
"@dxos/app-framework": "0.6.14-main.f49f251",
|
|
40
|
+
"@dxos/async": "0.6.14-main.f49f251",
|
|
41
|
+
"@dxos/invariant": "0.6.14-main.f49f251",
|
|
42
|
+
"@dxos/echo-schema": "0.6.14-main.f49f251",
|
|
43
|
+
"@dxos/keyboard": "0.6.14-main.f49f251",
|
|
44
|
+
"@dxos/local-storage": "0.6.14-main.f49f251",
|
|
45
|
+
"@dxos/log": "0.6.14-main.f49f251",
|
|
46
|
+
"@dxos/plugin-graph": "0.6.14-main.f49f251",
|
|
47
|
+
"@dxos/plugin-client": "0.6.14-main.f49f251",
|
|
48
|
+
"@dxos/plugin-observability": "0.6.14-main.f49f251",
|
|
49
|
+
"@dxos/plugin-theme": "0.6.14-main.f49f251",
|
|
50
|
+
"@dxos/protocols": "0.6.14-main.f49f251",
|
|
51
|
+
"@dxos/react-ui-deck": "0.6.14-main.f49f251",
|
|
52
|
+
"@dxos/react-client": "0.6.14-main.f49f251",
|
|
53
|
+
"@dxos/debug": "0.6.14-main.f49f251",
|
|
54
|
+
"@dxos/react-ui-list": "0.6.14-main.f49f251",
|
|
55
|
+
"@dxos/react-ui-mosaic": "0.6.14-main.f49f251",
|
|
56
|
+
"@dxos/react-ui-searchlist": "0.6.14-main.f49f251",
|
|
57
|
+
"@dxos/util": "0.6.14-main.f49f251"
|
|
55
58
|
},
|
|
56
59
|
"devDependencies": {
|
|
57
60
|
"@phosphor-icons/react": "^2.1.5",
|
|
@@ -60,18 +63,18 @@
|
|
|
60
63
|
"react": "~18.2.0",
|
|
61
64
|
"react-dom": "~18.2.0",
|
|
62
65
|
"vite": "5.4.7",
|
|
63
|
-
"@dxos/
|
|
64
|
-
"@dxos/
|
|
65
|
-
"@dxos/react-ui": "0.6.14-main.
|
|
66
|
-
"@dxos/react-ui-theme": "0.6.14-main.
|
|
67
|
-
"@dxos/storybook-utils": "0.6.14-main.
|
|
66
|
+
"@dxos/random": "0.6.14-main.f49f251",
|
|
67
|
+
"@dxos/echo-signals": "0.6.14-main.f49f251",
|
|
68
|
+
"@dxos/react-ui": "0.6.14-main.f49f251",
|
|
69
|
+
"@dxos/react-ui-theme": "0.6.14-main.f49f251",
|
|
70
|
+
"@dxos/storybook-utils": "0.6.14-main.f49f251"
|
|
68
71
|
},
|
|
69
72
|
"peerDependencies": {
|
|
70
73
|
"@phosphor-icons/react": "^2.1.5",
|
|
71
74
|
"react": "~18.2.0",
|
|
72
75
|
"react-dom": "~18.2.0",
|
|
73
|
-
"@dxos/react-ui
|
|
74
|
-
"@dxos/react-ui": "0.6.14-main.
|
|
76
|
+
"@dxos/react-ui": "0.6.14-main.f49f251",
|
|
77
|
+
"@dxos/react-ui-theme": "0.6.14-main.f49f251"
|
|
75
78
|
},
|
|
76
79
|
"publishConfig": {
|
|
77
80
|
"access": "public"
|
package/src/NavTreePlugin.tsx
CHANGED
|
@@ -19,11 +19,11 @@ import {
|
|
|
19
19
|
parseGraphPlugin,
|
|
20
20
|
NavigationAction,
|
|
21
21
|
} from '@dxos/app-framework';
|
|
22
|
-
import { createExtension, type Graph, isAction, isGraphNode, type Node } from '@dxos/app-graph';
|
|
22
|
+
import { createExtension, type Graph, isAction, isGraphNode, type Node, type NodeFilter } from '@dxos/app-graph';
|
|
23
|
+
import { invariant } from '@dxos/invariant';
|
|
23
24
|
import { Keyboard } from '@dxos/keyboard';
|
|
24
25
|
import { LocalStorageStore } from '@dxos/local-storage';
|
|
25
26
|
import { Path } from '@dxos/react-ui-mosaic';
|
|
26
|
-
import { type OpenItemIds } from '@dxos/react-ui-navtree';
|
|
27
27
|
import { getHostPlatform } from '@dxos/util';
|
|
28
28
|
|
|
29
29
|
import {
|
|
@@ -37,7 +37,14 @@ import {
|
|
|
37
37
|
import { CommandsTrigger } from './components/CommandsTrigger';
|
|
38
38
|
import meta, { KEY_BINDING, NAVTREE_PLUGIN } from './meta';
|
|
39
39
|
import translations from './translations';
|
|
40
|
-
import {
|
|
40
|
+
import { type NavTreeItem } from './types';
|
|
41
|
+
import {
|
|
42
|
+
expandOpenGraphNodes,
|
|
43
|
+
getActions,
|
|
44
|
+
getChildren,
|
|
45
|
+
treeItemsFromRootNode,
|
|
46
|
+
type NavTreeItemGraphNode,
|
|
47
|
+
} from './util';
|
|
41
48
|
|
|
42
49
|
export type NavTreePluginProvides = SurfaceProvides &
|
|
43
50
|
MetadataRecordsProvides &
|
|
@@ -45,22 +52,77 @@ export type NavTreePluginProvides = SurfaceProvides &
|
|
|
45
52
|
TranslationsProvides &
|
|
46
53
|
IntentResolverProvides;
|
|
47
54
|
|
|
48
|
-
type NavTreeState = {
|
|
55
|
+
type NavTreeState = {
|
|
56
|
+
root?: NavTreeItemGraphNode;
|
|
57
|
+
flatTree: NavTreeItem[];
|
|
58
|
+
open: string[];
|
|
59
|
+
};
|
|
49
60
|
|
|
50
61
|
export const NavTreePlugin = (): PluginDefinition<NavTreePluginProvides> => {
|
|
62
|
+
let graphPlugin: Plugin<GraphProvides> | undefined;
|
|
63
|
+
let graph: Graph | undefined;
|
|
64
|
+
const itemCache = new Map<string, NavTreeItem>();
|
|
65
|
+
|
|
51
66
|
const state = new LocalStorageStore<NavTreeState>('dxos.org/settings/navtree', {
|
|
67
|
+
root: undefined,
|
|
68
|
+
get flatTree() {
|
|
69
|
+
if (!this.root || !graph) {
|
|
70
|
+
return [];
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return treeItemsFromRootNode(graph, this.root, this.open, getItem);
|
|
74
|
+
},
|
|
52
75
|
// TODO(thure): Do this dynamically.
|
|
53
|
-
|
|
76
|
+
open: ['root', 'root~dxos.org/plugin/space-spaces', 'root~dxos.org/plugin/files'],
|
|
54
77
|
});
|
|
55
78
|
|
|
56
|
-
|
|
57
|
-
|
|
79
|
+
const getItem = (node: NavTreeItemGraphNode, parent: readonly string[], filter?: NodeFilter) => {
|
|
80
|
+
invariant(graph);
|
|
81
|
+
const path = [...parent, node.id];
|
|
82
|
+
const { actions, groupedActions } = getActions(graph, node);
|
|
83
|
+
const children = getChildren(graph, node, filter, path);
|
|
84
|
+
const parentOf =
|
|
85
|
+
children.length > 0 ? children.map(({ id }) => id) : node.properties.role === 'branch' ? [] : undefined;
|
|
86
|
+
const item = {
|
|
87
|
+
id: node.id,
|
|
88
|
+
label: node.properties.label ?? node.id,
|
|
89
|
+
icon: node.properties.icon,
|
|
90
|
+
disabled: node.properties.disabled,
|
|
91
|
+
testId: node.properties.testId,
|
|
92
|
+
className: node.properties.className,
|
|
93
|
+
headingClassName: node.properties.headingClassName,
|
|
94
|
+
path,
|
|
95
|
+
parentOf,
|
|
96
|
+
node,
|
|
97
|
+
actions,
|
|
98
|
+
groupedActions,
|
|
99
|
+
} satisfies NavTreeItem;
|
|
58
100
|
|
|
59
|
-
|
|
101
|
+
const cachedItem = itemCache.get(node.id);
|
|
102
|
+
// TODO(wittjosiah): This is not a good enough check.
|
|
103
|
+
// Consider better ways to doing reactive transformations which retain referential equality.
|
|
104
|
+
if (cachedItem && JSON.stringify(item) === JSON.stringify(cachedItem)) {
|
|
105
|
+
return cachedItem;
|
|
106
|
+
} else {
|
|
107
|
+
itemCache.set(node.id, item);
|
|
108
|
+
return item;
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const handleOpenChange = (item: NavTreeItem, open: boolean) => {
|
|
113
|
+
const path = Path.create(...item.path);
|
|
60
114
|
// TODO(thure): This might become a localstorage leak; openItemIds that no longer exist should be removed from this map.
|
|
61
|
-
|
|
115
|
+
if (open) {
|
|
116
|
+
state.values.open.push(path);
|
|
117
|
+
} else {
|
|
118
|
+
const index = state.values.open.indexOf(path);
|
|
119
|
+
if (index > -1) {
|
|
120
|
+
state.values.open.splice(index, 1);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
62
124
|
if (graph) {
|
|
63
|
-
void expandOpenGraphNodes(graph,
|
|
125
|
+
void expandOpenGraphNodes(graph, state.values.open);
|
|
64
126
|
}
|
|
65
127
|
};
|
|
66
128
|
|
|
@@ -77,9 +139,9 @@ export const NavTreePlugin = (): PluginDefinition<NavTreePluginProvides> => {
|
|
|
77
139
|
getChildren(graph, state.values.root);
|
|
78
140
|
getActions(graph, state.values.root);
|
|
79
141
|
|
|
80
|
-
state.prop({ key: '
|
|
142
|
+
state.prop({ key: 'open', type: LocalStorageStore.json<string[]>() });
|
|
81
143
|
|
|
82
|
-
void expandOpenGraphNodes(graph, state.values.
|
|
144
|
+
void expandOpenGraphNodes(graph, state.values.open);
|
|
83
145
|
|
|
84
146
|
// TODO(wittjosiah): Factor out.
|
|
85
147
|
// TODO(wittjosiah): Handle removal of actions.
|
|
@@ -147,19 +209,15 @@ export const NavTreePlugin = (): PluginDefinition<NavTreePluginProvides> => {
|
|
|
147
209
|
|
|
148
210
|
switch (role) {
|
|
149
211
|
case 'navigation':
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
/>
|
|
160
|
-
);
|
|
161
|
-
}
|
|
162
|
-
break;
|
|
212
|
+
return (
|
|
213
|
+
<NavTreeContainer
|
|
214
|
+
items={state.values.flatTree}
|
|
215
|
+
current={data.activeIds as string[]}
|
|
216
|
+
open={state.values.open}
|
|
217
|
+
onOpenChange={handleOpenChange}
|
|
218
|
+
popoverAnchorId={data.popoverAnchorId as string}
|
|
219
|
+
/>
|
|
220
|
+
);
|
|
163
221
|
|
|
164
222
|
case 'document-title': {
|
|
165
223
|
return <NavTreeDocumentTitle node={isGraphNode(data.activeNode) ? data.activeNode : undefined} />;
|
|
@@ -200,17 +258,14 @@ export const NavTreePlugin = (): PluginDefinition<NavTreePluginProvides> => {
|
|
|
200
258
|
if (graph && intent.data?.id) {
|
|
201
259
|
const path = graph.getPath({ target: intent.data.id });
|
|
202
260
|
if (Array.isArray(path)) {
|
|
203
|
-
const additionalOpenItems = [...Array(path.length
|
|
204
|
-
const itemId = Path.create(...path.slice(
|
|
261
|
+
const additionalOpenItems = [...Array(path.length)].reduce((acc: string[], _, index) => {
|
|
262
|
+
const itemId = Path.create(...path.slice(0, index));
|
|
205
263
|
if (itemId.length > 0) {
|
|
206
|
-
acc
|
|
264
|
+
acc.push(itemId);
|
|
207
265
|
}
|
|
208
266
|
return acc;
|
|
209
|
-
},
|
|
210
|
-
state.values.
|
|
211
|
-
...additionalOpenItems,
|
|
212
|
-
...state.values.openItemIds,
|
|
213
|
-
};
|
|
267
|
+
}, []);
|
|
268
|
+
state.values.open.push(...additionalOpenItems);
|
|
214
269
|
}
|
|
215
270
|
}
|
|
216
271
|
break;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import '@dxos-theme';
|
|
6
|
+
|
|
7
|
+
import { monitorForElements } from '@atlaskit/pragmatic-drag-and-drop/element/adapter';
|
|
8
|
+
import { extractInstruction, type Instruction } from '@atlaskit/pragmatic-drag-and-drop-hitbox/tree-item';
|
|
9
|
+
import { type Meta } from '@storybook/react';
|
|
10
|
+
import React, { useEffect } from 'react';
|
|
11
|
+
|
|
12
|
+
import { type NodeArg } from '@dxos/app-graph';
|
|
13
|
+
import { create } from '@dxos/echo-schema';
|
|
14
|
+
import { faker } from '@dxos/random';
|
|
15
|
+
import { isItem } from '@dxos/react-ui-list';
|
|
16
|
+
import { Path } from '@dxos/react-ui-mosaic';
|
|
17
|
+
import { withLayout, withTheme } from '@dxos/storybook-utils';
|
|
18
|
+
|
|
19
|
+
import { NavTree, type NavTreeProps } from './NavTree';
|
|
20
|
+
import { createTree, flattenTree, updateState } from '../testing';
|
|
21
|
+
import { type NavTreeItem } from '../types';
|
|
22
|
+
|
|
23
|
+
faker.seed(1234);
|
|
24
|
+
|
|
25
|
+
type State = {
|
|
26
|
+
tree: NodeArg<any>;
|
|
27
|
+
flatTree: NavTreeItem[];
|
|
28
|
+
open: string[];
|
|
29
|
+
current: string[];
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const state = create<State>({
|
|
33
|
+
tree: createTree(),
|
|
34
|
+
get flatTree() {
|
|
35
|
+
return flattenTree(this.tree, this.open);
|
|
36
|
+
},
|
|
37
|
+
open: [],
|
|
38
|
+
current: [],
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
const Story = (args: Partial<NavTreeProps>) => {
|
|
42
|
+
// NOTE: If passed directly to args, this won't be reactive.
|
|
43
|
+
const items = state.flatTree;
|
|
44
|
+
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
return monitorForElements({
|
|
47
|
+
canMonitor: ({ source }) => isItem(source.data),
|
|
48
|
+
onDrop: ({ location, source }) => {
|
|
49
|
+
// Didn't drop on anything.
|
|
50
|
+
if (!location.current.dropTargets.length) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const target = location.current.dropTargets[0];
|
|
55
|
+
|
|
56
|
+
const instruction: Instruction | null = extractInstruction(target.data);
|
|
57
|
+
if (instruction !== null) {
|
|
58
|
+
updateState({
|
|
59
|
+
state: state.tree,
|
|
60
|
+
instruction,
|
|
61
|
+
source: source.data as NavTreeItem,
|
|
62
|
+
target: target.data as NavTreeItem,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
}, []);
|
|
68
|
+
|
|
69
|
+
return <NavTree items={items} open={state.open} current={state.current} {...args} />;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const meta: Meta<typeof NavTree> = {
|
|
73
|
+
title: 'plugins/plugin-navtree/NavTree',
|
|
74
|
+
component: NavTree,
|
|
75
|
+
render: Story,
|
|
76
|
+
decorators: [withTheme, withLayout({ tooltips: true })],
|
|
77
|
+
args: {
|
|
78
|
+
onOpenChange: (item: NavTreeItem, open: boolean) => {
|
|
79
|
+
const path = Path.create(...item.path);
|
|
80
|
+
if (open) {
|
|
81
|
+
state.open.push(path);
|
|
82
|
+
} else {
|
|
83
|
+
const index = state.open.indexOf(path);
|
|
84
|
+
if (index > -1) {
|
|
85
|
+
state.open.splice(index, 1);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
onSelect: (item: NavTreeItem, current: boolean) => {
|
|
90
|
+
if (current) {
|
|
91
|
+
state.current.push(item.id);
|
|
92
|
+
} else {
|
|
93
|
+
const index = state.current.indexOf(item.id);
|
|
94
|
+
if (index > -1) {
|
|
95
|
+
state.current.splice(index, 1);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
export default meta;
|
|
103
|
+
|
|
104
|
+
export const Default = {};
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import React, { Fragment, useCallback, useEffect, type FC } from 'react';
|
|
6
|
+
|
|
7
|
+
import { isAction, type Node } from '@dxos/app-graph';
|
|
8
|
+
import { Popover, toLocalizedString, Treegrid, useTranslation } from '@dxos/react-ui';
|
|
9
|
+
import { Tree, type TreeProps } from '@dxos/react-ui-list';
|
|
10
|
+
import { type MaybePromise } from '@dxos/util';
|
|
11
|
+
|
|
12
|
+
import { NavTreeItemAction } from './NavTreeItemAction';
|
|
13
|
+
import { NAVTREE_PLUGIN } from '../meta';
|
|
14
|
+
import { type NavTreeItem } from '../types';
|
|
15
|
+
|
|
16
|
+
export const NAV_TREE_ITEM = 'NavTreeItem';
|
|
17
|
+
|
|
18
|
+
export type NavTreeProps = Omit<TreeProps<NavTreeItem>, 'draggable' | 'gridTemplateColumns' | 'renderColumns'> &
|
|
19
|
+
Pick<NavTreeColumnsProps, 'loadDescendents' | 'renderPresence' | 'popoverAnchorId'>;
|
|
20
|
+
|
|
21
|
+
export const NavTree = ({ loadDescendents, renderPresence, popoverAnchorId, ...props }: NavTreeProps) => {
|
|
22
|
+
const renderColumns = useCallback<NonNullable<TreeProps<NavTreeItem>['renderColumns']>>(
|
|
23
|
+
({ item, menuOpen, setMenuOpen }) => {
|
|
24
|
+
return (
|
|
25
|
+
<NavTreeColumns
|
|
26
|
+
item={item}
|
|
27
|
+
menuOpen={menuOpen}
|
|
28
|
+
setMenuOpen={setMenuOpen}
|
|
29
|
+
loadDescendents={loadDescendents}
|
|
30
|
+
renderPresence={renderPresence}
|
|
31
|
+
popoverAnchorId={popoverAnchorId}
|
|
32
|
+
/>
|
|
33
|
+
);
|
|
34
|
+
},
|
|
35
|
+
[renderPresence, popoverAnchorId, loadDescendents],
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<Tree<NavTreeItem>
|
|
40
|
+
{...props}
|
|
41
|
+
draggable
|
|
42
|
+
gridTemplateColumns='[tree-row-start] 1fr min-content min-content min-content [tree-row-end]'
|
|
43
|
+
classNames='pbs-1'
|
|
44
|
+
renderColumns={renderColumns}
|
|
45
|
+
/>
|
|
46
|
+
);
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
type NavTreeColumnsProps = {
|
|
50
|
+
item: NavTreeItem;
|
|
51
|
+
menuOpen: boolean;
|
|
52
|
+
setMenuOpen: (open: boolean) => void;
|
|
53
|
+
loadDescendents?: (node: Node) => MaybePromise<void>;
|
|
54
|
+
renderPresence?: FC<{ item: NavTreeItem }>;
|
|
55
|
+
popoverAnchorId?: string;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const NavTreeColumns = ({
|
|
59
|
+
item,
|
|
60
|
+
menuOpen,
|
|
61
|
+
setMenuOpen,
|
|
62
|
+
loadDescendents,
|
|
63
|
+
renderPresence: Presence,
|
|
64
|
+
popoverAnchorId,
|
|
65
|
+
}: NavTreeColumnsProps) => {
|
|
66
|
+
const { t } = useTranslation(NAVTREE_PLUGIN);
|
|
67
|
+
|
|
68
|
+
const level = item.path.length - 2;
|
|
69
|
+
|
|
70
|
+
const [primaryAction, ...secondaryActions] = item.actions.toSorted((a, b) =>
|
|
71
|
+
a.properties?.disposition === 'toolbar' ? -1 : 1,
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
const actions = (primaryAction?.properties?.disposition === 'toolbar' ? secondaryActions : item.actions)
|
|
75
|
+
.flatMap((action) => (isAction(action) ? [action] : []))
|
|
76
|
+
.filter((action) => !action.properties?.hidden);
|
|
77
|
+
|
|
78
|
+
const ActionRoot = popoverAnchorId === `dxos.org/ui/${NAV_TREE_ITEM}/${item.id}` ? Popover.Anchor : Fragment;
|
|
79
|
+
|
|
80
|
+
// TODO(thure): Ideally this should not be necessary.
|
|
81
|
+
useEffect(() => {
|
|
82
|
+
const frame = requestAnimationFrame(() => {
|
|
83
|
+
void loadDescendents?.(item.node);
|
|
84
|
+
if (primaryAction && !isAction(primaryAction)) {
|
|
85
|
+
void loadDescendents?.(primaryAction);
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
return () => cancelAnimationFrame(frame);
|
|
89
|
+
}, [primaryAction]);
|
|
90
|
+
|
|
91
|
+
return (
|
|
92
|
+
<>
|
|
93
|
+
{primaryAction?.properties?.disposition === 'toolbar' ? (
|
|
94
|
+
<NavTreeItemAction
|
|
95
|
+
testId={primaryAction.properties?.testId}
|
|
96
|
+
label={toLocalizedString(primaryAction.properties?.label, t)}
|
|
97
|
+
icon={primaryAction.properties?.icon ?? 'ph--placeholder--regular'}
|
|
98
|
+
parent={item.node}
|
|
99
|
+
monolithic={isAction(primaryAction)}
|
|
100
|
+
menuActions={isAction(primaryAction) ? [primaryAction] : item.groupedActions[primaryAction.id]}
|
|
101
|
+
menuType={primaryAction.properties?.menuType}
|
|
102
|
+
caller={NAV_TREE_ITEM}
|
|
103
|
+
/>
|
|
104
|
+
) : (
|
|
105
|
+
<Treegrid.Cell />
|
|
106
|
+
)}
|
|
107
|
+
<ActionRoot>
|
|
108
|
+
{actions.length > 0 ? (
|
|
109
|
+
<NavTreeItemAction
|
|
110
|
+
testId={`navtree.treeItem.actionsLevel${level}`}
|
|
111
|
+
label={t('tree item actions label')}
|
|
112
|
+
icon='ph--dots-three-vertical--regular'
|
|
113
|
+
parent={item.node}
|
|
114
|
+
menuActions={actions}
|
|
115
|
+
menuType='dropdown'
|
|
116
|
+
caller={NAV_TREE_ITEM}
|
|
117
|
+
menuOpen={menuOpen}
|
|
118
|
+
onChangeMenuOpen={setMenuOpen}
|
|
119
|
+
/>
|
|
120
|
+
) : (
|
|
121
|
+
<Treegrid.Cell />
|
|
122
|
+
)}
|
|
123
|
+
</ActionRoot>
|
|
124
|
+
{Presence && <Presence item={item} />}
|
|
125
|
+
</>
|
|
126
|
+
);
|
|
127
|
+
};
|