@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
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { type NodeArg } from '@dxos/app-graph';
|
|
6
|
+
import { create, S, type ReactiveObject, TypedObject } from '@dxos/echo-schema';
|
|
7
|
+
import { faker } from '@dxos/random';
|
|
8
|
+
|
|
9
|
+
// TODO(burdon): Reconcile with @dxos/plugin-debug, @dxos/react-ui/testing.
|
|
10
|
+
// TODO(burdon): Bug when adding stale objects to space (e.g., static objects already added in previous story invocation).
|
|
11
|
+
|
|
12
|
+
// TODO(burdon): Util.
|
|
13
|
+
export const range = <T>(fn: (i: number) => T | undefined, length: number): T[] =>
|
|
14
|
+
Array.from({ length })
|
|
15
|
+
.map((_, i) => fn(i))
|
|
16
|
+
.filter(Boolean) as T[];
|
|
17
|
+
|
|
18
|
+
// TODO(burdon): Commit to using ECHO to generate all test data? Or convert from raw data?
|
|
19
|
+
export type TestItem = { id: string; type: string } & Record<string, any>;
|
|
20
|
+
|
|
21
|
+
type ObjectDataGenerator = {
|
|
22
|
+
createSchema?: () => S.Schema<any>;
|
|
23
|
+
createData: () => any;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
type ObjectFactory<T extends ReactiveObject<any>> = {
|
|
27
|
+
schema?: S.Schema<any>; // TODO(burdon): Support both typed and expando schema.
|
|
28
|
+
createObject: () => T;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
type ObjectFactoryMap = { [type: string]: ObjectFactory<any> };
|
|
32
|
+
|
|
33
|
+
const createFactory = ({ createSchema, createData }: ObjectDataGenerator) => {
|
|
34
|
+
const schema = createSchema?.();
|
|
35
|
+
return {
|
|
36
|
+
schema,
|
|
37
|
+
createObject: () => (schema ? create(schema, createData()) : create(createData())),
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
// TODO(burdon): Handle restricted values.
|
|
42
|
+
export const Status = ['pending', 'active', 'done'];
|
|
43
|
+
export const Priority = [1, 2, 3, 4, 5];
|
|
44
|
+
|
|
45
|
+
export const defaultGenerators: { [type: string]: ObjectDataGenerator } = {
|
|
46
|
+
document: {
|
|
47
|
+
createData: () => ({
|
|
48
|
+
title: faker.lorem.sentence(3),
|
|
49
|
+
body: faker.lorem.sentences({ min: 1, max: faker.number.int({ min: 1, max: 3 }) }),
|
|
50
|
+
}),
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
image: {
|
|
54
|
+
createData: () => ({
|
|
55
|
+
title: faker.lorem.sentence(3),
|
|
56
|
+
image: faker.helpers.arrayElement(data.images),
|
|
57
|
+
body: faker.datatype.boolean() ? faker.lorem.sentences() : undefined,
|
|
58
|
+
}),
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
project: {
|
|
62
|
+
createSchema: () =>
|
|
63
|
+
class ProjectType extends TypedObject({ typename: 'dxos.test.Project', version: '0.1.0' })({
|
|
64
|
+
title: S.String,
|
|
65
|
+
repo: S.String,
|
|
66
|
+
status: S.String,
|
|
67
|
+
priority: S.Number,
|
|
68
|
+
}) {},
|
|
69
|
+
createData: () => ({
|
|
70
|
+
title: faker.commerce.productName(),
|
|
71
|
+
repo: faker.datatype.boolean({ probability: 0.3 }) ? faker.internet.url() : undefined,
|
|
72
|
+
status: faker.helpers.arrayElement(Status),
|
|
73
|
+
priority: faker.helpers.arrayElement(Priority),
|
|
74
|
+
}),
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Typed object generator.
|
|
80
|
+
*/
|
|
81
|
+
export class TestObjectGenerator {
|
|
82
|
+
public readonly factories: ObjectFactoryMap;
|
|
83
|
+
|
|
84
|
+
constructor({ types, factories }: { types?: string[]; factories?: ObjectFactoryMap } = {}) {
|
|
85
|
+
this.factories =
|
|
86
|
+
factories ??
|
|
87
|
+
(types ?? Object.keys(defaultGenerators)).reduce<ObjectFactoryMap>((acc, type) => {
|
|
88
|
+
acc[type] = createFactory(defaultGenerators[type]);
|
|
89
|
+
return acc;
|
|
90
|
+
}, {});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
get schema(): S.Schema<any>[] {
|
|
94
|
+
return Object.values(this.factories).map((f) => f.schema!);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
createObject({ types }: { types?: string[] } = {}) {
|
|
98
|
+
const type = faker.helpers.arrayElement(types ?? Object.keys(this.factories));
|
|
99
|
+
const factory = this.factories[type];
|
|
100
|
+
return factory?.createObject();
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
createObjects({ types, length }: { types?: string[]; length: number }) {
|
|
104
|
+
return range(() => this.createObject({ types }), length);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// https://unsplash.com
|
|
109
|
+
// TODO(burdon): Use https://picsum.photos?
|
|
110
|
+
const data = {
|
|
111
|
+
images: [
|
|
112
|
+
'/images/image-1.png',
|
|
113
|
+
'/images/image-2.png',
|
|
114
|
+
'/images/image-3.png',
|
|
115
|
+
'/images/image-4.png',
|
|
116
|
+
'/images/image-5.png',
|
|
117
|
+
'/images/image-6.png',
|
|
118
|
+
],
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
export const createTree = () => {
|
|
122
|
+
const generator = new TestObjectGenerator({ types: ['document'] });
|
|
123
|
+
|
|
124
|
+
const initialContent = {
|
|
125
|
+
id: 'root',
|
|
126
|
+
data: null,
|
|
127
|
+
type: 'root',
|
|
128
|
+
properties: {
|
|
129
|
+
label: 'Root',
|
|
130
|
+
icon: 'ph--circle--regular',
|
|
131
|
+
},
|
|
132
|
+
nodes: [...Array(4)].map(() => {
|
|
133
|
+
const l0 = generator.createObject();
|
|
134
|
+
return {
|
|
135
|
+
id: faker.string.uuid(),
|
|
136
|
+
data: null,
|
|
137
|
+
type: 'collection',
|
|
138
|
+
properties: {
|
|
139
|
+
label: l0.title,
|
|
140
|
+
icon: 'ph--horse--regular',
|
|
141
|
+
},
|
|
142
|
+
nodes: [
|
|
143
|
+
...[...Array(4)].map(() => {
|
|
144
|
+
const l1 = generator.createObject();
|
|
145
|
+
return {
|
|
146
|
+
id: faker.string.uuid(),
|
|
147
|
+
data: null,
|
|
148
|
+
type: 'document',
|
|
149
|
+
properties: {
|
|
150
|
+
label: l1.title,
|
|
151
|
+
icon: 'ph--butterfly--regular',
|
|
152
|
+
},
|
|
153
|
+
nodes: [
|
|
154
|
+
{
|
|
155
|
+
id: `${faker.string.uuid()}__a1`,
|
|
156
|
+
data: () => {},
|
|
157
|
+
type: 'action',
|
|
158
|
+
properties: {
|
|
159
|
+
label: faker.lorem.words(2),
|
|
160
|
+
icon: 'ph--boat--regular',
|
|
161
|
+
},
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
id: `${faker.string.uuid()}__a2`,
|
|
165
|
+
data: () => {},
|
|
166
|
+
type: 'action',
|
|
167
|
+
properties: {
|
|
168
|
+
label: faker.lorem.words(2),
|
|
169
|
+
icon: 'ph--train-simple--regular',
|
|
170
|
+
},
|
|
171
|
+
},
|
|
172
|
+
],
|
|
173
|
+
} satisfies NodeArg<any>;
|
|
174
|
+
}),
|
|
175
|
+
{
|
|
176
|
+
id: `${faker.string.uuid()}__a1`,
|
|
177
|
+
data: () => {},
|
|
178
|
+
type: 'action',
|
|
179
|
+
properties: {
|
|
180
|
+
label: faker.lorem.words(2),
|
|
181
|
+
icon: 'ph--boat--regular',
|
|
182
|
+
},
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
id: `${faker.string.uuid()}__a2`,
|
|
186
|
+
data: () => {},
|
|
187
|
+
type: 'action',
|
|
188
|
+
properties: {
|
|
189
|
+
label: faker.lorem.words(2),
|
|
190
|
+
icon: 'ph--train-simple--regular',
|
|
191
|
+
},
|
|
192
|
+
},
|
|
193
|
+
],
|
|
194
|
+
} satisfies NodeArg<any>;
|
|
195
|
+
}),
|
|
196
|
+
} satisfies NodeArg<any>;
|
|
197
|
+
|
|
198
|
+
return initialContent;
|
|
199
|
+
};
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { type Instruction } from '@atlaskit/pragmatic-drag-and-drop-hitbox/tree-item';
|
|
6
|
+
|
|
7
|
+
import { isActionLike, type Node, type NodeArg } from '@dxos/app-graph';
|
|
8
|
+
import { log } from '@dxos/log';
|
|
9
|
+
import { Path } from '@dxos/react-ui-mosaic';
|
|
10
|
+
|
|
11
|
+
import { type NavTreeItem } from '../types';
|
|
12
|
+
|
|
13
|
+
export const flattenTree = (tree: NodeArg<any>, open: string[]): NavTreeItem[] => {
|
|
14
|
+
return Array.from(
|
|
15
|
+
visitor({
|
|
16
|
+
node: tree,
|
|
17
|
+
getItem,
|
|
18
|
+
isOpen: ({ path }) => open.includes(Path.create(...path)),
|
|
19
|
+
}),
|
|
20
|
+
);
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export function* visitor({
|
|
24
|
+
node,
|
|
25
|
+
getItem,
|
|
26
|
+
isOpen,
|
|
27
|
+
}: {
|
|
28
|
+
node: NodeArg<any>;
|
|
29
|
+
getItem: (node: NodeArg<any>, parent?: readonly string[]) => NavTreeItem;
|
|
30
|
+
isOpen?: (node: NavTreeItem) => boolean;
|
|
31
|
+
}): Generator<NavTreeItem> {
|
|
32
|
+
const stack: [NodeArg<any>, NavTreeItem][] = [[node, getItem(node)]];
|
|
33
|
+
while (stack.length > 0) {
|
|
34
|
+
const [node, item] = stack.pop()!;
|
|
35
|
+
if (item.path.length > 1) {
|
|
36
|
+
yield item;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const children = Array.from(node.nodes ?? []);
|
|
40
|
+
if (item.path.length === 1 || isOpen?.(item)) {
|
|
41
|
+
for (let i = children.length - 1; i >= 0; i--) {
|
|
42
|
+
const child = children[i];
|
|
43
|
+
stack.push([child, getItem(child, item.path)]);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Ensures that the same item is not created multiple times, causing the tree to be re-rendered.
|
|
50
|
+
const itemsCache: Record<string, NavTreeItem> = {};
|
|
51
|
+
|
|
52
|
+
export const getItem = (arg: NodeArg<any>, parent?: readonly string[]): NavTreeItem => {
|
|
53
|
+
const cachedItem = itemsCache[arg.id];
|
|
54
|
+
if (cachedItem) {
|
|
55
|
+
return cachedItem;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const children = arg.nodes?.filter((node) => !isActionLike(node)) ?? [];
|
|
59
|
+
const actions = arg.nodes?.filter((node) => isActionLike(node)) ?? [];
|
|
60
|
+
const node: Node = {
|
|
61
|
+
id: arg.id,
|
|
62
|
+
type: arg.type,
|
|
63
|
+
properties: arg.properties ?? {},
|
|
64
|
+
data: arg.data ?? null,
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const item = {
|
|
68
|
+
id: arg.id,
|
|
69
|
+
label: arg.properties!.label,
|
|
70
|
+
icon: arg.properties!.icon,
|
|
71
|
+
path: parent ? [...parent, arg.id] : [arg.id],
|
|
72
|
+
...(children.length > 0 && {
|
|
73
|
+
parentOf: children.map(({ id }) => id),
|
|
74
|
+
}),
|
|
75
|
+
node,
|
|
76
|
+
actions,
|
|
77
|
+
groupedActions: {},
|
|
78
|
+
};
|
|
79
|
+
itemsCache[node.id] = item;
|
|
80
|
+
return item;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
export const invalidateCache = (id: string) => {
|
|
84
|
+
delete itemsCache[id];
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const removeItem = (tree: NodeArg<any>, source: NavTreeItem) => {
|
|
88
|
+
const parent = getNode(tree, source.path.slice(1, -1));
|
|
89
|
+
const index = parent.nodes!.findIndex(({ id }) => id === source.id);
|
|
90
|
+
const item = parent.nodes![index];
|
|
91
|
+
parent.nodes!.splice(index, 1);
|
|
92
|
+
return item;
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const getNode = (tree: NodeArg<any>, path: string[]) => {
|
|
96
|
+
let node = tree;
|
|
97
|
+
for (const part of path) {
|
|
98
|
+
node = node.nodes!.find(({ id }) => id === part)!;
|
|
99
|
+
}
|
|
100
|
+
return node;
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
// TODO(wittjosiah): Reconcile with react-ui-list/Tree/testing.
|
|
104
|
+
export const updateState = ({
|
|
105
|
+
state,
|
|
106
|
+
instruction,
|
|
107
|
+
source,
|
|
108
|
+
target,
|
|
109
|
+
}: {
|
|
110
|
+
state: NodeArg<any>;
|
|
111
|
+
instruction: Instruction;
|
|
112
|
+
source: NavTreeItem;
|
|
113
|
+
target: NavTreeItem;
|
|
114
|
+
}) => {
|
|
115
|
+
switch (instruction.type) {
|
|
116
|
+
case 'reorder-above': {
|
|
117
|
+
invalidateCache(source.id);
|
|
118
|
+
const item = removeItem(state, source);
|
|
119
|
+
const parent = getNode(state, target.path.slice(1, -1));
|
|
120
|
+
const index = parent.nodes!.findIndex(({ id }) => id === target.id);
|
|
121
|
+
invalidateCache(target.id);
|
|
122
|
+
parent.nodes!.splice(index, 0, item);
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
case 'reorder-below': {
|
|
127
|
+
invalidateCache(source.id);
|
|
128
|
+
const item = removeItem(state, source);
|
|
129
|
+
const parent = getNode(state, target.path.slice(1, -1));
|
|
130
|
+
const index = parent.nodes!.findIndex(({ id }) => id === target.id);
|
|
131
|
+
invalidateCache(target.id);
|
|
132
|
+
parent.nodes!.splice(index + 1, 0, item);
|
|
133
|
+
break;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
case 'make-child': {
|
|
137
|
+
invalidateCache(source.id);
|
|
138
|
+
const item = removeItem(state, source);
|
|
139
|
+
const parent = getNode(state, target.path.slice(1));
|
|
140
|
+
invalidateCache(target.id);
|
|
141
|
+
parent.nodes!.push(item);
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
case 'instruction-blocked':
|
|
146
|
+
break;
|
|
147
|
+
|
|
148
|
+
default:
|
|
149
|
+
log.warn('Unsupported instruction', instruction);
|
|
150
|
+
break;
|
|
151
|
+
}
|
|
152
|
+
};
|
package/src/translations.ts
CHANGED
|
@@ -2,8 +2,6 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { translations as navtreeTranslations } from '@dxos/react-ui-navtree';
|
|
6
|
-
|
|
7
5
|
import { NAVTREE_PLUGIN } from './meta';
|
|
8
6
|
|
|
9
7
|
export default [
|
|
@@ -21,8 +19,10 @@ export default [
|
|
|
21
19
|
'released message': 'This version released {{released}}.',
|
|
22
20
|
'see release label': 'See release on GitHub',
|
|
23
21
|
'powered by dxos message': 'Powered by <dxos>DXOS</dxos>',
|
|
22
|
+
'tree item actions label': 'More actions',
|
|
23
|
+
'tree item searchlist input placeholder': 'Search for actions…',
|
|
24
|
+
'empty branch message': ' ',
|
|
24
25
|
},
|
|
25
26
|
},
|
|
26
27
|
},
|
|
27
|
-
...navtreeTranslations,
|
|
28
28
|
];
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { type Action, type ActionLike, type Node } from '@dxos/app-graph';
|
|
6
|
+
import { type Label } from '@dxos/react-ui';
|
|
7
|
+
import { type ItemType } from '@dxos/react-ui-list';
|
|
8
|
+
|
|
9
|
+
export type NavTreeItem = ItemType & {
|
|
10
|
+
node: Node;
|
|
11
|
+
actions: ActionLike[];
|
|
12
|
+
groupedActions: Record<string, Action[]>;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
type SharedProperties = {
|
|
16
|
+
label: Label;
|
|
17
|
+
icon?: string;
|
|
18
|
+
disabled?: boolean;
|
|
19
|
+
testId?: string;
|
|
20
|
+
className?: string;
|
|
21
|
+
headingClassName?: string;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export type NodeProperties = SharedProperties & {
|
|
25
|
+
role?: string;
|
|
26
|
+
error?: string;
|
|
27
|
+
modified?: boolean;
|
|
28
|
+
palette?: string;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export type ActionProperties = SharedProperties & {
|
|
32
|
+
disposition?: string;
|
|
33
|
+
hidden?: boolean;
|
|
34
|
+
caller?: string;
|
|
35
|
+
menuType?: 'searchList' | 'dropdown';
|
|
36
|
+
keyBinding?: string | KeyBinding;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Platform-specific key binding.
|
|
41
|
+
*/
|
|
42
|
+
export type KeyBinding = {
|
|
43
|
+
// NOTE: Keys come from `getHostPlatform` in `@dxos/util`.
|
|
44
|
+
// TODO(thure): Dedupe (similar in react-ui-deck)
|
|
45
|
+
windows?: string;
|
|
46
|
+
macos?: string;
|
|
47
|
+
ios?: string;
|
|
48
|
+
linux?: string;
|
|
49
|
+
unknown?: string;
|
|
50
|
+
};
|
package/src/util.ts
CHANGED
|
@@ -12,19 +12,14 @@ import {
|
|
|
12
12
|
isAction,
|
|
13
13
|
} from '@dxos/app-graph';
|
|
14
14
|
import { Path } from '@dxos/react-ui-mosaic';
|
|
15
|
-
import {
|
|
16
|
-
type NavTreeActionNode,
|
|
17
|
-
type NavTreeItemActions,
|
|
18
|
-
type NavTreeItemNode,
|
|
19
|
-
type NavTreeItemNodeProperties,
|
|
20
|
-
type OpenItemIds,
|
|
21
|
-
} from '@dxos/react-ui-navtree';
|
|
22
15
|
import { type MaybePromise, nonNullable } from '@dxos/util';
|
|
23
16
|
|
|
17
|
+
import { type NodeProperties, type NavTreeItem } from './types';
|
|
18
|
+
|
|
24
19
|
export type NavTreeItemGraphNode = Node<
|
|
25
20
|
any,
|
|
26
|
-
|
|
27
|
-
|
|
21
|
+
Partial<
|
|
22
|
+
NodeProperties & {
|
|
28
23
|
persistenceClass: string;
|
|
29
24
|
persistenceKey: string;
|
|
30
25
|
acceptPersistenceClass: Set<string>;
|
|
@@ -33,9 +28,9 @@ export type NavTreeItemGraphNode = Node<
|
|
|
33
28
|
onCopy: (activeNode: NavTreeItemGraphNode, index?: number) => MaybePromise<void>;
|
|
34
29
|
onTransferStart: (activeNode: NavTreeItemGraphNode, index?: number) => MaybePromise<void>;
|
|
35
30
|
onTransferEnd: (activeNode: NavTreeItemGraphNode, destinationParent: NavTreeItemGraphNode) => MaybePromise<void>;
|
|
36
|
-
}
|
|
31
|
+
}
|
|
32
|
+
>
|
|
37
33
|
>;
|
|
38
|
-
export type NavTreeItem = NavTreeItemNode<NavTreeItemGraphNode>;
|
|
39
34
|
|
|
40
35
|
export const getParent = (
|
|
41
36
|
graph: Graph,
|
|
@@ -73,9 +68,9 @@ export const resolveMigrationOperation = (
|
|
|
73
68
|
const activeKey = activeNode.properties.persistenceKey;
|
|
74
69
|
if (activeKey && persistenceParent?.properties.acceptPersistenceKey) {
|
|
75
70
|
return persistenceParent.properties.acceptPersistenceKey.has(activeKey) &&
|
|
76
|
-
|
|
71
|
+
persistenceParent.properties.onTransferStart
|
|
77
72
|
? 'transfer'
|
|
78
|
-
:
|
|
73
|
+
: persistenceParent.properties.onCopy
|
|
79
74
|
? 'copy'
|
|
80
75
|
: 'reject';
|
|
81
76
|
} else {
|
|
@@ -103,7 +98,7 @@ export const sortActions = (actions: Action[]): Action[] =>
|
|
|
103
98
|
return 1;
|
|
104
99
|
});
|
|
105
100
|
|
|
106
|
-
export const getTreeItemNode = (treeItems:
|
|
101
|
+
export const getTreeItemNode = (treeItems: NavTreeItem[], path?: string[]): NavTreeItem | undefined => {
|
|
107
102
|
if (!path) {
|
|
108
103
|
return undefined;
|
|
109
104
|
}
|
|
@@ -117,7 +112,7 @@ export const getChildren = (
|
|
|
117
112
|
graph: Graph,
|
|
118
113
|
node: NavTreeItemGraphNode,
|
|
119
114
|
filter?: NodeFilter,
|
|
120
|
-
path: string[] = [],
|
|
115
|
+
path: readonly string[] = [],
|
|
121
116
|
): NavTreeItemGraphNode[] => {
|
|
122
117
|
return graph
|
|
123
118
|
.nodes(node, { relation: 'outbound', filter })
|
|
@@ -129,15 +124,19 @@ export const getChildren = (
|
|
|
129
124
|
.filter(nonNullable) as NavTreeItemGraphNode[];
|
|
130
125
|
};
|
|
131
126
|
|
|
132
|
-
type FlattenedActions = Required<Pick<
|
|
127
|
+
type FlattenedActions = Required<Pick<NavTreeItem, 'actions' | 'groupedActions'>>;
|
|
133
128
|
|
|
134
|
-
export const getActions = (graph: Graph, node:
|
|
129
|
+
export const getActions = (graph: Graph, node: Node): FlattenedActions => {
|
|
135
130
|
return graph.actions(node).reduce(
|
|
136
131
|
(acc: FlattenedActions, arg) => {
|
|
137
|
-
|
|
132
|
+
if (arg.properties.disposition === 'item') {
|
|
133
|
+
return acc;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
acc.actions.push(arg);
|
|
138
137
|
if (!isAction(arg)) {
|
|
139
138
|
const actionGroup = graph.actions(arg);
|
|
140
|
-
acc.groupedActions[arg.id] = actionGroup
|
|
139
|
+
acc.groupedActions[arg.id] = actionGroup;
|
|
141
140
|
}
|
|
142
141
|
return acc;
|
|
143
142
|
},
|
|
@@ -157,50 +156,39 @@ export const expandActions = (graph: Graph, node: Node) => {
|
|
|
157
156
|
return Promise.all([graph.expand(node, 'outbound', ACTION_TYPE), graph.expand(node, 'outbound', ACTION_GROUP_TYPE)]);
|
|
158
157
|
};
|
|
159
158
|
|
|
160
|
-
function* navTreeItemVisitor(
|
|
161
|
-
graph
|
|
162
|
-
node
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
actions: l0Actions,
|
|
177
|
-
groupedActions: l0GroupedActions,
|
|
178
|
-
},
|
|
179
|
-
];
|
|
180
|
-
|
|
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)];
|
|
181
175
|
while (stack.length > 0) {
|
|
182
176
|
const nextItem = stack.pop()!;
|
|
183
|
-
if (
|
|
177
|
+
if (nextItem.path.length > 1) {
|
|
184
178
|
yield nextItem;
|
|
185
179
|
}
|
|
186
|
-
const {
|
|
180
|
+
const { node, path } = nextItem;
|
|
187
181
|
const children = getChildren(graph, node, filter, path);
|
|
188
|
-
|
|
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
189
|
for (let i = children.length - 1; i >= 0; i--) {
|
|
190
190
|
const child = children[i] as NavTreeItemGraphNode;
|
|
191
|
-
|
|
192
|
-
const childChildren = getChildren(graph, child, filter, childPath);
|
|
193
|
-
const { actions: childActions, groupedActions: childGroupedActions } = getActions(graph, child);
|
|
194
|
-
stack.push({
|
|
195
|
-
id: Path.create(...childPath),
|
|
196
|
-
node: child,
|
|
197
|
-
path: childPath,
|
|
198
|
-
...((childChildren?.length ?? 0) > 0 && {
|
|
199
|
-
parentOf: childChildren!.map(({ id }) => Path.create(...childPath, id)),
|
|
200
|
-
}),
|
|
201
|
-
actions: childActions,
|
|
202
|
-
groupedActions: childGroupedActions,
|
|
203
|
-
});
|
|
191
|
+
stack.push(getItem(child, path, filter));
|
|
204
192
|
}
|
|
205
193
|
}
|
|
206
194
|
}
|
|
@@ -212,18 +200,19 @@ function* navTreeItemVisitor(
|
|
|
212
200
|
export const treeItemsFromRootNode = (
|
|
213
201
|
graph: Graph,
|
|
214
202
|
rootNode: NavTreeItemGraphNode,
|
|
215
|
-
|
|
216
|
-
|
|
203
|
+
open: string[],
|
|
204
|
+
getItem: (node: NavTreeItemGraphNode, parent: readonly string[]) => NavTreeItem,
|
|
205
|
+
path?: readonly string[],
|
|
217
206
|
filter?: NodeFilter,
|
|
218
207
|
): NavTreeItem[] => {
|
|
219
|
-
return Array.from(navTreeItemVisitor(graph, rootNode,
|
|
208
|
+
return Array.from(navTreeItemVisitor({ graph, node: rootNode, open, getItem, path, filter }));
|
|
220
209
|
};
|
|
221
210
|
|
|
222
|
-
export const expandOpenGraphNodes = (graph: Graph,
|
|
211
|
+
export const expandOpenGraphNodes = (graph: Graph, open: string[]) => {
|
|
223
212
|
return Promise.all(
|
|
224
|
-
|
|
225
|
-
.map((
|
|
226
|
-
const node = graph.findNode(Path.last(
|
|
213
|
+
open
|
|
214
|
+
.map((id) => {
|
|
215
|
+
const node = graph.findNode(Path.last(id));
|
|
227
216
|
return node && expandChildrenAndActions(graph, node as NavTreeItemGraphNode);
|
|
228
217
|
})
|
|
229
218
|
.filter(nonNullable),
|