@dxos/plugin-navtree 0.7.1-staging.8709ba7 → 0.7.2-staging.6d26b2a

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/dist/lib/browser/index.mjs +190 -187
  2. package/dist/lib/browser/index.mjs.map +3 -3
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/types/src/NavTreePlugin.d.ts.map +1 -1
  5. package/dist/types/src/components/NavTree.d.ts +7 -5
  6. package/dist/types/src/components/NavTree.d.ts.map +1 -1
  7. package/dist/types/src/components/NavTree.stories.d.ts.map +1 -1
  8. package/dist/types/src/components/NavTreeContainer.d.ts +2 -6
  9. package/dist/types/src/components/NavTreeContainer.d.ts.map +1 -1
  10. package/dist/types/src/components/NavTreeItemAction.stories.d.ts +1 -0
  11. package/dist/types/src/components/NavTreeItemAction.stories.d.ts.map +1 -1
  12. package/dist/types/src/stories/Graph.stories.d.ts +2 -1
  13. package/dist/types/src/stories/Graph.stories.d.ts.map +1 -1
  14. package/dist/types/src/testing/index.d.ts +1 -1
  15. package/dist/types/src/testing/index.d.ts.map +1 -1
  16. package/dist/types/src/testing/update-state.d.ts +10 -0
  17. package/dist/types/src/testing/update-state.d.ts.map +1 -0
  18. package/dist/types/src/types.d.ts +12 -3
  19. package/dist/types/src/types.d.ts.map +1 -1
  20. package/dist/types/src/util.d.ts +1 -20
  21. package/dist/types/src/util.d.ts.map +1 -1
  22. package/package.json +28 -28
  23. package/src/NavTreePlugin.tsx +103 -90
  24. package/src/components/NavTree.stories.tsx +73 -69
  25. package/src/components/NavTree.tsx +30 -20
  26. package/src/components/NavTreeContainer.tsx +71 -47
  27. package/src/stories/Graph.stories.tsx +68 -96
  28. package/src/testing/index.ts +1 -1
  29. package/src/testing/update-state.ts +70 -0
  30. package/src/types.ts +18 -3
  31. package/src/util.ts +2 -94
  32. package/dist/types/src/testing/visitor.d.ts +0 -18
  33. package/dist/types/src/testing/visitor.d.ts.map +0 -1
  34. package/src/testing/visitor.ts +0 -152
@@ -4,49 +4,82 @@
4
4
 
5
5
  import { monitorForElements } from '@atlaskit/pragmatic-drag-and-drop/element/adapter';
6
6
  import { extractInstruction, type Instruction } from '@atlaskit/pragmatic-drag-and-drop-hitbox/tree-item';
7
- import React, { useCallback, useEffect } from 'react';
7
+ import { untracked } from '@preact/signals-core';
8
+ import React, { memo, useCallback, useEffect } from 'react';
8
9
 
9
10
  import { NavigationAction, Surface, useIntentDispatcher } from '@dxos/app-framework';
10
11
  import { isAction, isActionLike, type Node } from '@dxos/app-graph';
11
12
  import { useGraph } from '@dxos/plugin-graph';
12
13
  import { isEchoObject, isSpace } from '@dxos/react-client/echo';
13
14
  import { ElevationProvider, useMediaQuery, useSidebars } from '@dxos/react-ui';
14
- import { isItem } from '@dxos/react-ui-list';
15
+ import { isTreeData, type TreeData, type PropsFromTreeItem } from '@dxos/react-ui-list';
16
+ import { mx } from '@dxos/react-ui-theme';
15
17
  import { arrayMove } from '@dxos/util';
16
18
 
17
19
  import { NAV_TREE_ITEM, NavTree, type NavTreeProps } from './NavTree';
18
20
  import { NavTreeFooter } from './NavTreeFooter';
19
21
  import { NAVTREE_PLUGIN } from '../meta';
20
- import { type NavTreeItem } from '../types';
22
+ import { type NavTreeItemGraphNode } from '../types';
21
23
  import {
22
24
  expandActions,
23
25
  expandChildren,
26
+ getActions as naturalGetActions,
24
27
  getChildren,
25
28
  getParent,
26
29
  resolveMigrationOperation,
27
- type NavTreeItemGraphNode,
28
30
  } from '../util';
29
31
 
30
32
  // TODO(thure): Is NavTree truly authoritative in this regard?
31
33
  export const NODE_TYPE = 'dxos/app-graph/node';
32
34
 
33
- const renderPresence = ({ item }: { item: NavTreeItem }) => (
34
- <Surface role='presence--glyph' data={{ object: item.node.data, id: item.node.id }} />
35
+ const renderPresence = ({ node }: { node: Node }) => (
36
+ <Surface role='presence--glyph' data={{ object: node.data, id: node.id }} />
35
37
  );
36
38
 
37
39
  export type NavTreeContainerProps = {
38
- items: NavTreeItem[];
39
- current: string[];
40
- open: string[];
41
40
  popoverAnchorId?: string;
42
- } & Pick<NavTreeProps, 'onOpenChange'>;
41
+ } & Pick<NavTreeProps, 'isOpen' | 'isCurrent' | 'onOpenChange'>;
43
42
 
44
- export const NavTreeContainer = ({ items, current, open, popoverAnchorId, ...props }: NavTreeContainerProps) => {
43
+ export const NavTreeContainer = memo(({ popoverAnchorId, ...props }: NavTreeContainerProps) => {
45
44
  const { closeNavigationSidebar } = useSidebars(NAVTREE_PLUGIN);
46
45
  const [isLg] = useMediaQuery('lg', { ssr: false });
47
46
  const dispatch = useIntentDispatcher();
48
47
  const { graph } = useGraph();
49
48
 
49
+ const getActions = useCallback((node: Node) => naturalGetActions(graph, node), [graph]);
50
+ const getItems = useCallback(
51
+ (node?: Node) => {
52
+ return graph.nodes(node ?? graph.root, {
53
+ filter: (node): node is Node => {
54
+ return untracked(() => {
55
+ const action = isActionLike(node);
56
+ const disposition = node.properties.disposition;
57
+ return !action || (action && disposition === 'item');
58
+ });
59
+ },
60
+ });
61
+ },
62
+ [graph],
63
+ );
64
+ const getProps = useCallback(
65
+ (node: Node, path: string[]): PropsFromTreeItem => {
66
+ const children = getChildren(graph, node, undefined, path);
67
+ const parentOf =
68
+ children.length > 0 ? children.map(({ id }) => id) : node.properties.role === 'branch' ? [] : undefined;
69
+ return {
70
+ id: node.id,
71
+ label: node.properties.label ?? node.id,
72
+ parentOf,
73
+ icon: node.properties.icon,
74
+ disabled: node.properties.disabled,
75
+ className: mx(node.properties.modified && 'italic', node.properties.className),
76
+ headingClassName: node.properties.headingClassName,
77
+ testId: node.properties.testId,
78
+ };
79
+ },
80
+ [graph],
81
+ );
82
+
50
83
  const loadDescendents = useCallback(
51
84
  (node: Node) => {
52
85
  void expandActions(graph, node);
@@ -62,26 +95,27 @@ export const NavTreeContainer = ({ items, current, open, popoverAnchorId, ...pro
62
95
  [graph],
63
96
  );
64
97
 
65
- const canDrop = useCallback((source: NavTreeItem, target: NavTreeItem) => {
66
- if (isSpace(target.node.data)) {
98
+ const canDrop = useCallback((source: TreeData, target: TreeData) => {
99
+ const sourceNode = source.item as Node;
100
+ const targetNode = target.item as Node;
101
+ if (isSpace(targetNode.data)) {
67
102
  // TODO(wittjosiah): Find a way to only allow space as source for rearranging.
68
- return isEchoObject(source.node.data) || isSpace(source.node.data);
69
- } else if (isEchoObject(target.node.data)) {
70
- return isEchoObject(source.node.data);
103
+ return isEchoObject(sourceNode.data) || isSpace(sourceNode.data);
104
+ } else if (isEchoObject(targetNode.data)) {
105
+ return isEchoObject(sourceNode.data);
71
106
  } else {
72
107
  return false;
73
108
  }
74
109
  }, []);
75
110
 
76
111
  const handleSelect = useCallback(
77
- ({ node, actions, path }: NavTreeItem) => {
112
+ ({ item: node }: { item: Node }) => {
78
113
  if (!node.data) {
79
114
  return;
80
115
  }
81
116
 
82
117
  if (isAction(node)) {
83
- const [parentId] = path.slice(1);
84
- const parent = items.find(({ id }) => id === parentId)?.node;
118
+ const [parent] = graph.nodes(node, { relation: 'inbound' });
85
119
  void (parent && node.data({ node: parent, caller: NAV_TREE_ITEM }));
86
120
  return;
87
121
  }
@@ -96,7 +130,7 @@ export const NavTreeContainer = ({ items, current, open, popoverAnchorId, ...pro
96
130
  },
97
131
  });
98
132
 
99
- const defaultAction = actions?.find((action) => action.properties?.disposition === 'default');
133
+ const defaultAction = graph.actions(node).find((action) => action.properties?.disposition === 'default');
100
134
  if (isAction(defaultAction)) {
101
135
  void (defaultAction.data as () => void)();
102
136
  }
@@ -105,69 +139,58 @@ export const NavTreeContainer = ({ items, current, open, popoverAnchorId, ...pro
105
139
  closeNavigationSidebar();
106
140
  }
107
141
  },
108
- [items, dispatch, isLg, closeNavigationSidebar],
142
+ [graph, dispatch, isLg, closeNavigationSidebar],
109
143
  );
110
144
 
111
145
  // TODO(wittjosiah): Factor out hook.
112
146
  useEffect(() => {
113
147
  return monitorForElements({
114
- canMonitor: ({ source }) => isItem(source.data),
148
+ canMonitor: ({ source }) => isTreeData(source.data),
115
149
  onDrop: ({ location, source }) => {
116
150
  // Didn't drop on anything.
117
151
  if (!location.current.dropTargets.length) {
118
152
  return;
119
153
  }
120
-
121
154
  const target = location.current.dropTargets[0];
122
155
  const instruction: Instruction | null = extractInstruction(target.data);
123
156
  if (instruction !== null && instruction.type !== 'instruction-blocked') {
124
- const sourceNode = source.data.node as NavTreeItemGraphNode;
125
- const targetNode = target.data.node as NavTreeItemGraphNode;
126
-
157
+ const sourceNode = source.data.item as NavTreeItemGraphNode;
158
+ const targetNode = target.data.item as NavTreeItemGraphNode;
127
159
  const sourcePath = source.data.path as string[];
128
160
  const targetPath = target.data.path as string[];
129
-
130
- const nextItems = instruction.type.startsWith('reorder')
131
- ? items.filter((item) => item.path.slice(0, -1).join() === targetPath.slice(0, -1).join())
132
- : items.filter((item) => item.path.slice(0, -1).join() === targetPath.join());
133
161
  const operation =
134
162
  sourcePath.slice(0, -1).join() === targetPath.slice(0, -1).join() && instruction.type !== 'make-child'
135
163
  ? 'rearrange'
136
164
  : resolveMigrationOperation(graph, sourceNode, targetPath, targetNode);
137
-
138
165
  const sourceParent = getParent(graph, sourceNode, sourcePath);
139
166
  const targetParent = getParent(graph, targetNode, targetPath);
140
-
141
- const sourceIndex = nextItems.findIndex(({ id }) => id === sourceNode.id);
142
- const targetIndex = nextItems.findIndex(({ id }) => id === targetNode.id);
167
+ const sourceItems = getItems(sourceParent);
168
+ const targetItems = getItems(targetParent);
169
+ const sourceIndex = sourceItems.findIndex(({ id }) => id === sourceNode.id);
170
+ const targetIndex = targetItems.findIndex(({ id }) => id === targetNode.id);
143
171
  const migrationIndex =
144
172
  instruction.type === 'make-child'
145
- ? nextItems.length > 0
146
- ? nextItems.length - 1
147
- : 0
173
+ ? undefined
148
174
  : instruction.type === 'reorder-below'
149
175
  ? targetIndex + 1
150
176
  : targetIndex;
151
-
152
177
  switch (operation) {
153
178
  case 'rearrange': {
179
+ const nextItems = sourceItems.map(({ data }) => data);
154
180
  arrayMove(nextItems, sourceIndex, targetIndex);
155
- void sourceParent?.properties.onRearrangeChildren?.(nextItems.map((item) => item.node.data));
181
+ void sourceParent?.properties.onRearrangeChildren?.(nextItems);
156
182
  break;
157
183
  }
158
-
159
184
  case 'copy': {
160
185
  const target = instruction.type === 'make-child' ? targetNode : targetParent;
161
186
  void target?.properties.onCopy?.(sourceNode, migrationIndex);
162
187
  break;
163
188
  }
164
-
165
189
  case 'transfer': {
166
190
  const target = instruction.type === 'make-child' ? targetNode : targetParent;
167
191
  if (!target?.properties.onTransferStart || !sourceParent?.properties.onTransferEnd) {
168
192
  break;
169
193
  }
170
-
171
194
  void target?.properties.onTransferStart(sourceNode, migrationIndex);
172
195
  void sourceParent?.properties.onTransferEnd?.(sourceNode, target);
173
196
  break;
@@ -176,7 +199,7 @@ export const NavTreeContainer = ({ items, current, open, popoverAnchorId, ...pro
176
199
  }
177
200
  },
178
201
  });
179
- }, [items]);
202
+ }, [graph]);
180
203
 
181
204
  return (
182
205
  <ElevationProvider elevation='chrome'>
@@ -191,9 +214,10 @@ export const NavTreeContainer = ({ items, current, open, popoverAnchorId, ...pro
191
214
  {/* TODO(thure): What gives this an inline `overflow: initial`? */}
192
215
  <div role='none' className='border-be border-separator !overflow-y-auto'>
193
216
  <NavTree
194
- items={items}
195
- open={open}
196
- current={current}
217
+ id={graph.root.id}
218
+ getActions={getActions}
219
+ getItems={getItems}
220
+ getProps={getProps}
197
221
  loadDescendents={loadDescendents}
198
222
  renderPresence={renderPresence}
199
223
  popoverAnchorId={popoverAnchorId}
@@ -207,4 +231,4 @@ export const NavTreeContainer = ({ items, current, open, popoverAnchorId, ...pro
207
231
  </div>
208
232
  </ElevationProvider>
209
233
  );
210
- };
234
+ });
@@ -10,18 +10,16 @@ import { batch } from '@preact/signals-core';
10
10
  import { type Meta } from '@storybook/react';
11
11
  import React, { useEffect } from 'react';
12
12
 
13
- import { Graph, ROOT_ID, type NodeFilter } from '@dxos/app-graph';
14
- import { create } from '@dxos/echo-schema';
13
+ import { Graph, ROOT_ID, type Node } from '@dxos/app-graph';
14
+ import { create, type ReactiveObject } from '@dxos/echo-schema';
15
15
  import { registerSignalsRuntime } from '@dxos/echo-signals/react';
16
- import { invariant } from '@dxos/invariant';
17
16
  import { faker } from '@dxos/random';
18
- import { isItem } from '@dxos/react-ui-list';
17
+ import { isTreeData, type PropsFromTreeItem } from '@dxos/react-ui-list';
19
18
  import { Path } from '@dxos/react-ui-mosaic';
20
19
  import { withLayout, withTheme } from '@dxos/storybook-utils';
21
20
 
22
- import { NavTree, type NavTreeProps } from '../components';
23
- import { type NavTreeItem } from '../types';
24
- import { getActions, getChildren, type NavTreeItemGraphNode, treeItemsFromRootNode } from '../util';
21
+ import { NavTree } from '../components';
22
+ import { getActions, getChildren } from '../util';
25
23
 
26
24
  faker.seed(3);
27
25
  registerSignalsRuntime();
@@ -50,106 +48,80 @@ const createGraph = () => {
50
48
  return graph;
51
49
  };
52
50
 
53
- type State = {
54
- flatTree: NavTreeItem[];
55
- open: string[];
56
- current: string[];
57
- };
58
-
59
51
  const graph = createGraph();
60
- const itemCache = new Map<string, NavTreeItem>();
61
- const state = create<State>({
62
- get flatTree() {
63
- return treeItemsFromRootNode(graph, graph.root, this.open, getItem);
64
- },
65
- open: [],
66
- current: [],
67
- });
68
-
69
- const getItem = (node: NavTreeItemGraphNode, parent: readonly string[], filter?: NodeFilter) => {
70
- invariant(graph);
71
- const path = [...parent, node.id];
72
- const { actions, groupedActions } = getActions(graph, node);
73
- const children = getChildren(graph, node, filter, path);
74
- const parentOf =
75
- children.length > 0 ? children.map(({ id }) => id) : node.properties.role === 'branch' ? [] : undefined;
76
- const item = {
77
- id: node.id,
78
- label: node.properties.label ?? node.id,
79
- icon: node.properties.icon,
80
- path,
81
- parentOf,
82
- node,
83
- actions,
84
- groupedActions,
85
- } satisfies NavTreeItem;
86
-
87
- const cachedItem = itemCache.get(node.id);
88
- // TODO(wittjosiah): This is not a good enough check.
89
- // Consider better ways to doing reactive transformations which retain referential equality.
90
- if (cachedItem && JSON.stringify(item) === JSON.stringify(cachedItem)) {
91
- return cachedItem;
92
- } else {
93
- itemCache.set(node.id, item);
94
- return item;
95
- }
96
- };
97
-
98
- const Story = (args: Partial<NavTreeProps>) => {
99
- // NOTE: If passed directly to args, this won't be reactive.
100
- const items = state.flatTree;
101
-
102
- useEffect(() => {
103
- return monitorForElements({
104
- canMonitor: ({ source }) => isItem(source.data),
105
- onDrop: ({ location, source }) => {
106
- // Didn't drop on anything.
107
- if (!location.current.dropTargets.length) {
108
- return;
109
- }
110
-
111
- const target = location.current.dropTargets[0];
112
-
113
- const instruction: Instruction | null = extractInstruction(target.data);
114
- if (instruction !== null) {
115
- // TODO(wittjosiah): Implement drop logic.
116
- console.log('update', instruction);
117
- }
118
- },
119
- });
120
- }, []);
121
-
122
- return <NavTree items={items} open={state.open} current={state.current} {...args} />;
123
- };
52
+ const state = new Map<string, ReactiveObject<{ open: boolean; current: boolean }>>();
124
53
 
125
54
  export const Default = {};
126
55
 
127
- const meta: Meta = {
56
+ const meta: Meta<typeof NavTree> = {
128
57
  title: 'plugins/plugin-navtree/Graph',
129
58
  component: NavTree,
130
- render: Story,
131
59
  decorators: [withTheme, withLayout({ tooltips: true })],
60
+ render: (args) => {
61
+ useEffect(() => {
62
+ return monitorForElements({
63
+ canMonitor: ({ source }) => isTreeData(source.data),
64
+ onDrop: ({ location, source }) => {
65
+ // Didn't drop on anything.
66
+ if (!location.current.dropTargets.length) {
67
+ return;
68
+ }
69
+
70
+ const target = location.current.dropTargets[0];
71
+
72
+ const instruction: Instruction | null = extractInstruction(target.data);
73
+ if (instruction !== null) {
74
+ // TODO(wittjosiah): Implement drop logic.
75
+ console.log('update', instruction);
76
+ }
77
+ },
78
+ });
79
+ }, []);
80
+
81
+ return <NavTree {...args} />;
82
+ },
132
83
  args: {
133
- onOpenChange: (item: NavTreeItem, open: boolean) => {
134
- const path = Path.create(...item.path);
135
- if (open) {
136
- state.open.push(path);
137
- } else {
138
- const index = state.open.indexOf(path);
139
- if (index > -1) {
140
- state.open.splice(index, 1);
141
- }
84
+ id: graph.root.id,
85
+ getActions: (node: Node) => getActions(graph, node),
86
+ getItems: (node?: Node) => graph.nodes(node ?? graph.root),
87
+ getProps: (node: Node, path: string[]) => {
88
+ const children = getChildren(graph, node, undefined, path);
89
+ const parentOf =
90
+ children.length > 0 ? children.map(({ id }) => id) : node.properties.role === 'branch' ? [] : undefined;
91
+ return {
92
+ id: node.id,
93
+ label: node.properties.label ?? node.id,
94
+ icon: node.properties.icon,
95
+ parentOf,
96
+ } satisfies PropsFromTreeItem;
97
+ },
98
+ isOpen: (_path: string[]) => {
99
+ const path = Path.create(..._path);
100
+ const value = state.get(path) ?? create({ open: false, current: false });
101
+ if (!state.has(path)) {
102
+ state.set(path, value);
142
103
  }
104
+
105
+ return value.open;
143
106
  },
144
- onSelect: (item: NavTreeItem, current: boolean) => {
145
- if (current) {
146
- state.current.push(item.id);
147
- } else {
148
- const index = state.current.indexOf(item.id);
149
- if (index > -1) {
150
- state.current.splice(index, 1);
151
- }
107
+ isCurrent: (_path: string[]) => {
108
+ const path = Path.create(..._path);
109
+ const value = state.get(path) ?? create({ open: false, current: false });
110
+ if (!state.has(path)) {
111
+ state.set(path, value);
152
112
  }
113
+
114
+ return value.current;
115
+ },
116
+ onOpenChange: ({ path: _path, open }) => {
117
+ const path = Path.create(..._path);
118
+ const object = state.get(path);
119
+ object!.open = open;
120
+ },
121
+ onSelect: ({ path: _path, current }) => {
122
+ const path = Path.create(..._path);
123
+ const object = state.get(path);
124
+ object!.current = current;
153
125
  },
154
126
  },
155
127
  };
@@ -3,4 +3,4 @@
3
3
  //
4
4
 
5
5
  export * from './generator';
6
- export * from './visitor';
6
+ export * from './update-state';
@@ -0,0 +1,70 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import { type Instruction } from '@atlaskit/pragmatic-drag-and-drop-hitbox/tree-item';
6
+
7
+ import { type NodeArg } from '@dxos/app-graph';
8
+ import { log } from '@dxos/log';
9
+ import { type TreeData } from '@dxos/react-ui-list';
10
+
11
+ const removeItem = (tree: NodeArg<any>, source: TreeData) => {
12
+ const parent = getNode(tree, source.path.slice(1, -1));
13
+ const index = parent.nodes!.findIndex(({ id }) => id === source.id);
14
+ const item = parent.nodes![index];
15
+ parent.nodes!.splice(index, 1);
16
+ return item;
17
+ };
18
+
19
+ const getNode = (tree: NodeArg<any>, path: string[]) => {
20
+ let node = tree;
21
+ for (const part of path) {
22
+ node = node.nodes!.find(({ id }) => id === part)!;
23
+ }
24
+ return node;
25
+ };
26
+
27
+ // TODO(wittjosiah): Reconcile with react-ui-list/Tree/testing.
28
+ export const updateState = ({
29
+ state,
30
+ instruction,
31
+ source,
32
+ target,
33
+ }: {
34
+ state: NodeArg<any>;
35
+ instruction: Instruction;
36
+ source: TreeData;
37
+ target: TreeData;
38
+ }) => {
39
+ switch (instruction.type) {
40
+ case 'reorder-above': {
41
+ const item = removeItem(state, source);
42
+ const parent = getNode(state, target.path.slice(1, -1));
43
+ const index = parent.nodes!.findIndex(({ id }) => id === target.id);
44
+ parent.nodes!.splice(index, 0, item);
45
+ break;
46
+ }
47
+
48
+ case 'reorder-below': {
49
+ const item = removeItem(state, source);
50
+ const parent = getNode(state, target.path.slice(1, -1));
51
+ const index = parent.nodes!.findIndex(({ id }) => id === target.id);
52
+ parent.nodes!.splice(index + 1, 0, item);
53
+ break;
54
+ }
55
+
56
+ case 'make-child': {
57
+ const item = removeItem(state, source);
58
+ const parent = getNode(state, target.path.slice(1));
59
+ parent.nodes!.push(item);
60
+ break;
61
+ }
62
+
63
+ case 'instruction-blocked':
64
+ break;
65
+
66
+ default:
67
+ log.warn('Unsupported instruction', instruction);
68
+ break;
69
+ }
70
+ };
package/src/types.ts CHANGED
@@ -4,10 +4,25 @@
4
4
 
5
5
  import { type Action, type ActionLike, type Node } from '@dxos/app-graph';
6
6
  import { type Label } from '@dxos/react-ui';
7
- import { type ItemType } from '@dxos/react-ui-list';
7
+ import { type MaybePromise } from '@dxos/util';
8
8
 
9
- export type NavTreeItem = ItemType & {
10
- node: Node;
9
+ export type NavTreeItemGraphNode = Node<
10
+ any,
11
+ Partial<
12
+ NodeProperties & {
13
+ persistenceClass: string;
14
+ persistenceKey: string;
15
+ acceptPersistenceClass: Set<string>;
16
+ acceptPersistenceKey: Set<string>;
17
+ onRearrangeChildren: (nextOrder: NavTreeItemGraphNode[]) => MaybePromise<void>;
18
+ onCopy: (activeNode: NavTreeItemGraphNode, index?: number) => MaybePromise<void>;
19
+ onTransferStart: (activeNode: NavTreeItemGraphNode, index?: number) => MaybePromise<void>;
20
+ onTransferEnd: (activeNode: NavTreeItemGraphNode, destinationParent: NavTreeItemGraphNode) => MaybePromise<void>;
21
+ }
22
+ >
23
+ >;
24
+
25
+ export type FlattenedActions = {
11
26
  actions: ActionLike[];
12
27
  groupedActions: Record<string, Action[]>;
13
28
  };
package/src/util.ts CHANGED
@@ -11,26 +11,9 @@ import {
11
11
  ACTION_GROUP_TYPE,
12
12
  isAction,
13
13
  } from '@dxos/app-graph';
14
- import { Path } from '@dxos/react-ui-mosaic';
15
- import { type MaybePromise, nonNullable } from '@dxos/util';
14
+ import { nonNullable } from '@dxos/util';
16
15
 
17
- import { type NodeProperties, type NavTreeItem } from './types';
18
-
19
- export type NavTreeItemGraphNode = Node<
20
- any,
21
- Partial<
22
- NodeProperties & {
23
- persistenceClass: string;
24
- persistenceKey: string;
25
- acceptPersistenceClass: Set<string>;
26
- acceptPersistenceKey: Set<string>;
27
- onRearrangeChildren: (nextOrder: NavTreeItemGraphNode[]) => MaybePromise<void>;
28
- onCopy: (activeNode: NavTreeItemGraphNode, index?: number) => MaybePromise<void>;
29
- onTransferStart: (activeNode: NavTreeItemGraphNode, index?: number) => MaybePromise<void>;
30
- onTransferEnd: (activeNode: NavTreeItemGraphNode, destinationParent: NavTreeItemGraphNode) => MaybePromise<void>;
31
- }
32
- >
33
- >;
16
+ import { type FlattenedActions, type NavTreeItemGraphNode } from './types';
34
17
 
35
18
  export const getParent = (
36
19
  graph: Graph,
@@ -98,16 +81,6 @@ export const sortActions = (actions: Action[]): Action[] =>
98
81
  return 1;
99
82
  });
100
83
 
101
- export const getTreeItemNode = (treeItems: NavTreeItem[], path?: string[]): NavTreeItem | undefined => {
102
- if (!path) {
103
- return undefined;
104
- }
105
-
106
- return treeItems.find((treeItem) => {
107
- return treeItem.path && !treeItem.path.find((id, index) => path[index] !== id);
108
- });
109
- };
110
-
111
84
  export const getChildren = (
112
85
  graph: Graph,
113
86
  node: NavTreeItemGraphNode,
@@ -124,8 +97,6 @@ export const getChildren = (
124
97
  .filter(nonNullable) as NavTreeItemGraphNode[];
125
98
  };
126
99
 
127
- type FlattenedActions = Required<Pick<NavTreeItem, 'actions' | 'groupedActions'>>;
128
-
129
100
  export const getActions = (graph: Graph, node: Node): FlattenedActions => {
130
101
  return graph.actions(node).reduce(
131
102
  (acc: FlattenedActions, arg) => {
@@ -155,66 +126,3 @@ export const expandChildren = (graph: Graph, node: Node) => {
155
126
  export const expandActions = (graph: Graph, node: Node) => {
156
127
  return Promise.all([graph.expand(node, 'outbound', ACTION_TYPE), graph.expand(node, 'outbound', ACTION_GROUP_TYPE)]);
157
128
  };
158
-
159
- function* navTreeItemVisitor({
160
- graph,
161
- node,
162
- open,
163
- getItem,
164
- path = [],
165
- filter,
166
- }: {
167
- graph: Graph;
168
- node: NavTreeItemGraphNode;
169
- open: string[];
170
- getItem: (node: NavTreeItemGraphNode, parent: readonly string[], filter?: NodeFilter) => NavTreeItem;
171
- path?: readonly string[];
172
- filter?: NodeFilter;
173
- }): Generator<NavTreeItem> {
174
- const stack: NavTreeItem[] = [getItem(node, path, filter)];
175
- while (stack.length > 0) {
176
- const nextItem = stack.pop()!;
177
- if (nextItem.path.length > 1) {
178
- yield nextItem;
179
- }
180
- const { node, path } = nextItem;
181
- const children = getChildren(graph, node, filter, path);
182
- const actions = graph.actions(node);
183
- if (path.length === 1 || open.includes(Path.create(...path))) {
184
- for (let i = actions.length - 1; i >= 0; i--) {
185
- if (actions[i].properties.disposition === 'item') {
186
- stack.push(getItem(actions[i], path, filter));
187
- }
188
- }
189
- for (let i = children.length - 1; i >= 0; i--) {
190
- const child = children[i] as NavTreeItemGraphNode;
191
- stack.push(getItem(child, path, filter));
192
- }
193
- }
194
- }
195
- }
196
-
197
- /**
198
- * Get a reactive tree node from a graph node.
199
- */
200
- export const treeItemsFromRootNode = (
201
- graph: Graph,
202
- rootNode: NavTreeItemGraphNode,
203
- open: string[],
204
- getItem: (node: NavTreeItemGraphNode, parent: readonly string[]) => NavTreeItem,
205
- path?: readonly string[],
206
- filter?: NodeFilter,
207
- ): NavTreeItem[] => {
208
- return Array.from(navTreeItemVisitor({ graph, node: rootNode, open, getItem, path, filter }));
209
- };
210
-
211
- export const expandOpenGraphNodes = (graph: Graph, open: string[]) => {
212
- return Promise.all(
213
- open
214
- .map((id) => {
215
- const node = graph.findNode(Path.last(id));
216
- return node && expandChildrenAndActions(graph, node as NavTreeItemGraphNode);
217
- })
218
- .filter(nonNullable),
219
- );
220
- };