@dxos/plugin-navtree 0.6.8-main.046e6cf
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/LICENSE +8 -0
- package/README.md +15 -0
- package/dist/lib/browser/chunk-U7MTKDFQ.mjs +13 -0
- package/dist/lib/browser/chunk-U7MTKDFQ.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +1055 -0
- package/dist/lib/browser/index.mjs.map +7 -0
- package/dist/lib/browser/meta.json +1 -0
- package/dist/lib/browser/meta.mjs +11 -0
- package/dist/lib/browser/meta.mjs.map +7 -0
- package/dist/types/src/NavTreePlugin.d.ts +4 -0
- package/dist/types/src/NavTreePlugin.d.ts.map +1 -0
- package/dist/types/src/components/CommandsDialog.d.ts +5 -0
- package/dist/types/src/components/CommandsDialog.d.ts.map +1 -0
- package/dist/types/src/components/CommandsTrigger.d.ts +3 -0
- package/dist/types/src/components/CommandsTrigger.d.ts.map +1 -0
- package/dist/types/src/components/HaloButton.d.ts +12 -0
- package/dist/types/src/components/HaloButton.d.ts.map +1 -0
- package/dist/types/src/components/HaloButton.stories.d.ts +12 -0
- package/dist/types/src/components/HaloButton.stories.d.ts.map +1 -0
- package/dist/types/src/components/NavBarStart.d.ts +7 -0
- package/dist/types/src/components/NavBarStart.d.ts.map +1 -0
- package/dist/types/src/components/NavTreeContainer.d.ts +12 -0
- package/dist/types/src/components/NavTreeContainer.d.ts.map +1 -0
- package/dist/types/src/components/NavTreeDocumentTitle.d.ts +5 -0
- package/dist/types/src/components/NavTreeDocumentTitle.d.ts.map +1 -0
- package/dist/types/src/components/NavTreeFooter.d.ts +6 -0
- package/dist/types/src/components/NavTreeFooter.d.ts.map +1 -0
- package/dist/types/src/components/NotchStart.d.ts +3 -0
- package/dist/types/src/components/NotchStart.d.ts.map +1 -0
- package/dist/types/src/components/index.d.ts +6 -0
- package/dist/types/src/components/index.d.ts.map +1 -0
- package/dist/types/src/experimental/Tree.d.ts +56 -0
- package/dist/types/src/experimental/Tree.d.ts.map +1 -0
- package/dist/types/src/experimental/Tree.stories.d.ts +12 -0
- package/dist/types/src/experimental/Tree.stories.d.ts.map +1 -0
- package/dist/types/src/index.d.ts +5 -0
- package/dist/types/src/index.d.ts.map +1 -0
- package/dist/types/src/meta.d.ts +7 -0
- package/dist/types/src/meta.d.ts.map +1 -0
- package/dist/types/src/stories/Graph.stories.d.ts +17 -0
- package/dist/types/src/stories/Graph.stories.d.ts.map +1 -0
- package/dist/types/src/translations.d.ts +27 -0
- package/dist/types/src/translations.d.ts.map +1 -0
- package/dist/types/src/util.d.ts +32 -0
- package/dist/types/src/util.d.ts.map +1 -0
- package/package.json +77 -0
- package/src/NavTreePlugin.tsx +262 -0
- package/src/components/CommandsDialog.tsx +114 -0
- package/src/components/CommandsTrigger.tsx +34 -0
- package/src/components/HaloButton.stories.tsx +19 -0
- package/src/components/HaloButton.tsx +41 -0
- package/src/components/NavBarStart.tsx +48 -0
- package/src/components/NavTreeContainer.tsx +375 -0
- package/src/components/NavTreeDocumentTitle.tsx +17 -0
- package/src/components/NavTreeFooter.tsx +155 -0
- package/src/components/NotchStart.tsx +29 -0
- package/src/components/index.ts +9 -0
- package/src/experimental/Tree.stories.tsx +229 -0
- package/src/experimental/Tree.tsx +282 -0
- package/src/index.ts +10 -0
- package/src/meta.ts +12 -0
- package/src/stories/Graph.stories.tsx +162 -0
- package/src/translations.ts +28 -0
- package/src/util.ts +231 -0
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import React, { type ReactNode, useCallback, useMemo } from 'react';
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
NavigationAction,
|
|
9
|
+
LayoutAction,
|
|
10
|
+
Surface,
|
|
11
|
+
SLUG_PATH_SEPARATOR,
|
|
12
|
+
SLUG_COLLECTION_INDICATOR,
|
|
13
|
+
useIntentDispatcher,
|
|
14
|
+
} from '@dxos/app-framework';
|
|
15
|
+
import { getGraph, isAction, isActionLike } from '@dxos/app-graph';
|
|
16
|
+
import { ElevationProvider, useMediaQuery, useSidebars } from '@dxos/react-ui';
|
|
17
|
+
import { type MosaicDropEvent, type MosaicMoveEvent, Path } from '@dxos/react-ui-mosaic';
|
|
18
|
+
import {
|
|
19
|
+
NavTree,
|
|
20
|
+
type NavTreeItemNode,
|
|
21
|
+
type NavTreeNode,
|
|
22
|
+
getLevel,
|
|
23
|
+
type NavTreeActionsNode,
|
|
24
|
+
type NavTreeProps,
|
|
25
|
+
type NavTreeItemMoveDetails,
|
|
26
|
+
} from '@dxos/react-ui-navtree';
|
|
27
|
+
import { arrayMove } from '@dxos/util';
|
|
28
|
+
|
|
29
|
+
import { NavTreeFooter } from './NavTreeFooter';
|
|
30
|
+
import { NAVTREE_PLUGIN } from '../meta';
|
|
31
|
+
import {
|
|
32
|
+
expandActions,
|
|
33
|
+
expandChildren,
|
|
34
|
+
getChildren,
|
|
35
|
+
getParent,
|
|
36
|
+
type NavTreeItem,
|
|
37
|
+
type NavTreeItemGraphNode,
|
|
38
|
+
resolveMigrationOperation,
|
|
39
|
+
treeItemsFromRootNode,
|
|
40
|
+
} from '../util';
|
|
41
|
+
|
|
42
|
+
// TODO(thure): Is NavTree truly authoritative in this regard?
|
|
43
|
+
export const NODE_TYPE = 'dxos/app-graph/node';
|
|
44
|
+
|
|
45
|
+
const renderPresence = (node: NavTreeNode): ReactNode => (
|
|
46
|
+
<Surface role='presence--glyph' data={{ object: node.data }} />
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
export const NavTreeContainer = ({
|
|
50
|
+
root,
|
|
51
|
+
activeIds,
|
|
52
|
+
openItemIds,
|
|
53
|
+
onOpenItemIdsChange,
|
|
54
|
+
attended,
|
|
55
|
+
popoverAnchorId,
|
|
56
|
+
}: {
|
|
57
|
+
root: NavTreeItemGraphNode;
|
|
58
|
+
activeIds: Set<string>;
|
|
59
|
+
openItemIds: Record<string, true>;
|
|
60
|
+
onOpenItemIdsChange: (nextOpenItemIds: Record<string, true>) => void;
|
|
61
|
+
attended: Set<string>;
|
|
62
|
+
popoverAnchorId?: string;
|
|
63
|
+
}) => {
|
|
64
|
+
const { closeNavigationSidebar } = useSidebars(NAVTREE_PLUGIN);
|
|
65
|
+
const [isLg] = useMediaQuery('lg', { ssr: false });
|
|
66
|
+
const dispatch = useIntentDispatcher();
|
|
67
|
+
|
|
68
|
+
const graph = useMemo(() => getGraph(root), [root]);
|
|
69
|
+
|
|
70
|
+
const items = treeItemsFromRootNode(graph, root, openItemIds);
|
|
71
|
+
|
|
72
|
+
const loadDescendents = useCallback(
|
|
73
|
+
(node: NavTreeNode | NavTreeActionsNode) => {
|
|
74
|
+
void expandActions(graph, node as NavTreeItemGraphNode);
|
|
75
|
+
if (!isActionLike(node)) {
|
|
76
|
+
void expandChildren(graph, node as NavTreeItemGraphNode).then(() =>
|
|
77
|
+
// Load one level deeper, which resolves some juddering observed on open/close.
|
|
78
|
+
Promise.all(
|
|
79
|
+
getChildren(graph, node as NavTreeItemGraphNode).flatMap((child) => [
|
|
80
|
+
expandActions(graph, child),
|
|
81
|
+
expandChildren(graph, child),
|
|
82
|
+
]),
|
|
83
|
+
),
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
[graph],
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
const handleNavigate = async ({ node, actions }: NavTreeItemNode) => {
|
|
91
|
+
if (!node.data) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// TODO(thure): Refactor opening related planks (comments in this case) to a generalized approach.
|
|
96
|
+
await dispatch({
|
|
97
|
+
action: NavigationAction.OPEN,
|
|
98
|
+
data: {
|
|
99
|
+
activeParts: node.data?.comments
|
|
100
|
+
? {
|
|
101
|
+
main: [node.id],
|
|
102
|
+
complementary: [`${node.id}${SLUG_PATH_SEPARATOR}comments${SLUG_COLLECTION_INDICATOR}`],
|
|
103
|
+
}
|
|
104
|
+
: {
|
|
105
|
+
main: [node.id],
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
await dispatch({ action: LayoutAction.SCROLL_INTO_VIEW, data: { id: node.id } });
|
|
111
|
+
|
|
112
|
+
const defaultAction = actions?.find((action) => action.properties?.disposition === 'default');
|
|
113
|
+
if (isAction(defaultAction)) {
|
|
114
|
+
void (defaultAction.data as () => void)();
|
|
115
|
+
}
|
|
116
|
+
!isLg && closeNavigationSidebar();
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const handleItemOpenChange = ({ id, actions, path }: NavTreeItemNode, nextOpen: boolean) => {
|
|
120
|
+
if (path) {
|
|
121
|
+
if (nextOpen) {
|
|
122
|
+
onOpenItemIdsChange({ ...openItemIds, [id]: true });
|
|
123
|
+
} else {
|
|
124
|
+
// TODO(thure): Filter vs single-remove, make setting?
|
|
125
|
+
const { [id]: _, ...nextOpenItemIds } = openItemIds;
|
|
126
|
+
onOpenItemIdsChange(nextOpenItemIds);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
// TODO(wittjosiah): This is a temporary solution to ensure spaces get enabled when they are expanded.
|
|
130
|
+
const defaultAction = actions?.find((action) => action.properties?.disposition === 'default');
|
|
131
|
+
if (isAction(defaultAction)) {
|
|
132
|
+
void (defaultAction.data as () => void)();
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
const resolveItemLevel = useCallback(
|
|
137
|
+
(overPosition: number | undefined, activeId: string | undefined, levelOffset: number) => {
|
|
138
|
+
if (!(typeof overPosition === 'number' && activeId)) {
|
|
139
|
+
return 1;
|
|
140
|
+
} else {
|
|
141
|
+
const nextItems = arrayMove(
|
|
142
|
+
items,
|
|
143
|
+
items.findIndex(({ id }) => id === activeId),
|
|
144
|
+
overPosition,
|
|
145
|
+
);
|
|
146
|
+
const previousItem: NavTreeItem | undefined = nextItems[overPosition - 1];
|
|
147
|
+
const nextItem: NavTreeItem | undefined = nextItems[overPosition + 1];
|
|
148
|
+
return Math.min(
|
|
149
|
+
previousItem ? getLevel(previousItem?.path) + 1 : 1,
|
|
150
|
+
Math.max(nextItem ? getLevel(nextItem?.path) : 1, getLevel(nextItems[overPosition].path) + levelOffset),
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
[items],
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
const handleMove: NavTreeProps['onMove'] = useCallback(
|
|
158
|
+
({ active, over, details }: MosaicMoveEvent<number, NavTreeItemMoveDetails>) => {
|
|
159
|
+
const levelOffset = Math.floor((details?.delta?.x ?? 0) / 16);
|
|
160
|
+
const overPosition = over.position ?? 0;
|
|
161
|
+
|
|
162
|
+
const nextItems = arrayMove(
|
|
163
|
+
items,
|
|
164
|
+
items.findIndex(({ id }) => id === active.item.id),
|
|
165
|
+
overPosition,
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
const previousItem: NavTreeItem | undefined = nextItems[overPosition - 1];
|
|
169
|
+
const nextItem: NavTreeItem | undefined = nextItems[overPosition + 1];
|
|
170
|
+
|
|
171
|
+
const activeNode = 'node' in active.item ? (active.item as NavTreeItem).node : undefined;
|
|
172
|
+
|
|
173
|
+
if (!activeNode || !previousItem || !previousItem.path) {
|
|
174
|
+
// console.log('[reject]', !activeNode, !previousItem, !previousItem.path);
|
|
175
|
+
// log.warn('Top-level rearrange before the first item of the NavTree is unsupported at this time.');
|
|
176
|
+
return { operation: 'reject' as const, details: { levelOffset } };
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const overLevel = resolveItemLevel(overPosition, active.item.id, levelOffset);
|
|
180
|
+
|
|
181
|
+
const previousLevel = getLevel(previousItem.path);
|
|
182
|
+
|
|
183
|
+
// console.log('[over]', overLevel, previousLevel, levelOffset);
|
|
184
|
+
|
|
185
|
+
if (previousLevel === overLevel - 1) {
|
|
186
|
+
if (Path.hasChild(previousItem.id, active.item.id)) {
|
|
187
|
+
// Previous is already parent of Active, rearrange.
|
|
188
|
+
const operation = previousItem.node.properties.onRearrangeChildren
|
|
189
|
+
? ('rearrange' as const)
|
|
190
|
+
: ('reject' as const);
|
|
191
|
+
return { operation, details: { levelOffset } };
|
|
192
|
+
} else {
|
|
193
|
+
// Previous is not yet parent of Active, check transfer or copy.
|
|
194
|
+
const operation = resolveMigrationOperation(graph, activeNode, previousItem.path, previousItem.node);
|
|
195
|
+
// console.log('[migration result]', operation, 'Previous is not yet parent of Active, check transfer or copy.');
|
|
196
|
+
return { operation, details: { levelOffset } };
|
|
197
|
+
}
|
|
198
|
+
} else if (previousLevel === overLevel) {
|
|
199
|
+
const parent = getParent(graph, previousItem.node, previousItem.path);
|
|
200
|
+
const parentPath = previousItem.path.slice(0, previousItem.path.length - 1);
|
|
201
|
+
if (Path.siblings(previousItem.id, active.item.id)) {
|
|
202
|
+
// Previous is already a sibling of Active, rearrange.
|
|
203
|
+
const operation = parent?.properties.onRearrangeChildren ? ('rearrange' as const) : ('reject' as const);
|
|
204
|
+
return { operation, details: { levelOffset } };
|
|
205
|
+
} else {
|
|
206
|
+
// Previous is not yet a sibling of Active, transfer/copy to Previous’s parent.
|
|
207
|
+
const operation = resolveMigrationOperation(graph, activeNode, parentPath, parent);
|
|
208
|
+
// console.log(
|
|
209
|
+
// '[migration result]',
|
|
210
|
+
// operation,
|
|
211
|
+
// 'Previous is not yet a sibling of Active, transfer/copy to Previous’s parent.',
|
|
212
|
+
// );
|
|
213
|
+
return { operation, details: { levelOffset } };
|
|
214
|
+
}
|
|
215
|
+
} else if (nextItem && nextItem.path) {
|
|
216
|
+
const nextLevel = getLevel(nextItem.path);
|
|
217
|
+
if (nextLevel === overLevel) {
|
|
218
|
+
const parent = getParent(graph, nextItem.node, nextItem.path);
|
|
219
|
+
const parentPath = nextItem.path.slice(0, nextItem.path.length - 1);
|
|
220
|
+
if (Path.siblings(nextItem.id, active.item.id)) {
|
|
221
|
+
// Next is already a sibling of Active, rearrange.
|
|
222
|
+
const operation = parent?.properties.onRearrangeChildren ? ('rearrange' as const) : ('reject' as const);
|
|
223
|
+
return { operation, details: { levelOffset } };
|
|
224
|
+
} else {
|
|
225
|
+
// Next is not yet a sibling of Active, transfer to Next’s parent.
|
|
226
|
+
const operation = resolveMigrationOperation(graph, activeNode, parentPath, parent);
|
|
227
|
+
// console.log(
|
|
228
|
+
// '[migration result]',
|
|
229
|
+
// operation,
|
|
230
|
+
// 'Next is not yet a sibling of Active, transfer to Next’s parent.',
|
|
231
|
+
// );
|
|
232
|
+
return { operation, details: { levelOffset } };
|
|
233
|
+
}
|
|
234
|
+
} else {
|
|
235
|
+
// console.log('[migration result]', 'reject');
|
|
236
|
+
return { operation: 'reject' as const, details: { levelOffset } };
|
|
237
|
+
}
|
|
238
|
+
} else {
|
|
239
|
+
// console.log('[migration result]', 'reject');
|
|
240
|
+
return { operation: 'reject' as const, details: { levelOffset } };
|
|
241
|
+
}
|
|
242
|
+
},
|
|
243
|
+
[root, items],
|
|
244
|
+
);
|
|
245
|
+
|
|
246
|
+
const handleDrop = useCallback(
|
|
247
|
+
({ operation, active, over, details = {} }: MosaicDropEvent<number, NavTreeItemMoveDetails>) => {
|
|
248
|
+
if (Path.first(over.path) !== root.id) {
|
|
249
|
+
return undefined;
|
|
250
|
+
}
|
|
251
|
+
const { levelOffset = 0 } = details;
|
|
252
|
+
const overPosition = over.position ?? 0;
|
|
253
|
+
|
|
254
|
+
const nextItems = arrayMove(
|
|
255
|
+
items,
|
|
256
|
+
items.findIndex(({ id }) => id === active.item.id),
|
|
257
|
+
overPosition,
|
|
258
|
+
);
|
|
259
|
+
|
|
260
|
+
const activeNode = 'node' in active.item ? (active.item as NavTreeItem).node : undefined;
|
|
261
|
+
const activeParentId = Path.parent(active.item.id);
|
|
262
|
+
|
|
263
|
+
if (!activeNode) {
|
|
264
|
+
return undefined;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const activeParent = getParent(graph, activeNode, (active.item as NavTreeItem).path ?? []);
|
|
268
|
+
|
|
269
|
+
if (operation === 'rearrange') {
|
|
270
|
+
void activeParent?.properties.onRearrangeChildren?.(
|
|
271
|
+
nextItems.filter(({ id }) => Path.hasChild(activeParentId, id)).map(({ node }) => node.data),
|
|
272
|
+
);
|
|
273
|
+
return null;
|
|
274
|
+
} else {
|
|
275
|
+
const previousItem: NavTreeItem | undefined = nextItems[overPosition - 1];
|
|
276
|
+
const nextItem: NavTreeItem | undefined = nextItems[overPosition + 1];
|
|
277
|
+
|
|
278
|
+
const overLevel = resolveItemLevel(overPosition, active.item.id, levelOffset);
|
|
279
|
+
|
|
280
|
+
const previousLevel = getLevel(previousItem.path);
|
|
281
|
+
|
|
282
|
+
if (operation === 'copy') {
|
|
283
|
+
if (previousLevel === overLevel - 1) {
|
|
284
|
+
void previousItem.node.properties.onCopy?.(activeNode, 0);
|
|
285
|
+
return null;
|
|
286
|
+
} else if (previousLevel === overLevel) {
|
|
287
|
+
const parent = getParent(graph, previousItem.node, previousItem.path ?? []);
|
|
288
|
+
void parent?.properties.onCopy?.(
|
|
289
|
+
activeNode,
|
|
290
|
+
getChildren(graph, parent).findIndex(({ id }) => id === previousItem.node.id) + 1,
|
|
291
|
+
);
|
|
292
|
+
return null;
|
|
293
|
+
} else if (nextItem && nextItem.path) {
|
|
294
|
+
const parent = getParent(graph, nextItem.node, nextItem.path ?? []);
|
|
295
|
+
void parent?.properties.onCopy?.(
|
|
296
|
+
activeNode,
|
|
297
|
+
getChildren(graph, parent).findIndex(({ id }) => id === nextItem.node.id),
|
|
298
|
+
);
|
|
299
|
+
return null;
|
|
300
|
+
}
|
|
301
|
+
} else if (operation === 'transfer') {
|
|
302
|
+
const onTransferEnd = activeParent?.properties.onTransferEnd;
|
|
303
|
+
if (!onTransferEnd) {
|
|
304
|
+
return undefined;
|
|
305
|
+
} else if (previousLevel === overLevel - 1) {
|
|
306
|
+
const onTransferStart = previousItem.node.properties.onTransferStart;
|
|
307
|
+
if (onTransferStart) {
|
|
308
|
+
void onTransferEnd(activeNode, previousItem.node);
|
|
309
|
+
void onTransferStart(activeNode, 0);
|
|
310
|
+
return null;
|
|
311
|
+
}
|
|
312
|
+
} else if (previousLevel === overLevel && previousItem.path) {
|
|
313
|
+
const parent = getParent(graph, previousItem.node, previousItem.path);
|
|
314
|
+
if (parent?.properties.onTransferStart) {
|
|
315
|
+
void onTransferEnd(activeNode, previousItem.node);
|
|
316
|
+
void parent.properties.onTransferStart(
|
|
317
|
+
activeNode,
|
|
318
|
+
getChildren(graph, parent).findIndex(({ id }) => id === previousItem.node.id) + 1,
|
|
319
|
+
);
|
|
320
|
+
return null;
|
|
321
|
+
}
|
|
322
|
+
} else if (nextItem && nextItem.path) {
|
|
323
|
+
const parent = getParent(graph, nextItem.node, nextItem.path);
|
|
324
|
+
if (parent?.properties.onTransferStart) {
|
|
325
|
+
void onTransferEnd(activeNode, previousItem.node);
|
|
326
|
+
void parent.properties.onTransferStart(
|
|
327
|
+
activeNode,
|
|
328
|
+
getChildren(graph, parent).findIndex(({ id }) => id === nextItem.node.id),
|
|
329
|
+
);
|
|
330
|
+
return null;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
return undefined;
|
|
336
|
+
},
|
|
337
|
+
[root, items],
|
|
338
|
+
);
|
|
339
|
+
|
|
340
|
+
const handleDragEnd = useCallback(() => {
|
|
341
|
+
onOpenItemIdsChange({ ...openItemIds });
|
|
342
|
+
}, [onOpenItemIdsChange, openItemIds]);
|
|
343
|
+
|
|
344
|
+
return (
|
|
345
|
+
<ElevationProvider elevation='chrome'>
|
|
346
|
+
<div
|
|
347
|
+
role='none'
|
|
348
|
+
className='bs-full overflow-hidden row-span-3 grid grid-cols-1 grid-rows-[min-content_1fr_min-content]'
|
|
349
|
+
>
|
|
350
|
+
<Surface role='search-input' limit={1} />
|
|
351
|
+
{/* TODO(thure): what gives this an inline `overflow: initial`? */}
|
|
352
|
+
<div role='none' className='!overflow-y-auto'>
|
|
353
|
+
<NavTree
|
|
354
|
+
id={root.id}
|
|
355
|
+
items={items}
|
|
356
|
+
current={activeIds}
|
|
357
|
+
attended={attended}
|
|
358
|
+
type={NODE_TYPE}
|
|
359
|
+
onNavigate={handleNavigate}
|
|
360
|
+
onItemOpenChange={handleItemOpenChange}
|
|
361
|
+
open={openItemIds}
|
|
362
|
+
onMove={handleMove}
|
|
363
|
+
onDrop={handleDrop}
|
|
364
|
+
onDragEnd={handleDragEnd}
|
|
365
|
+
popoverAnchorId={popoverAnchorId}
|
|
366
|
+
renderPresence={renderPresence}
|
|
367
|
+
resolveItemLevel={resolveItemLevel}
|
|
368
|
+
loadDescendents={loadDescendents}
|
|
369
|
+
/>
|
|
370
|
+
</div>
|
|
371
|
+
<NavTreeFooter />
|
|
372
|
+
</div>
|
|
373
|
+
</ElevationProvider>
|
|
374
|
+
);
|
|
375
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
import { useEffect } from 'react';
|
|
5
|
+
|
|
6
|
+
import { type Node } from '@dxos/app-graph';
|
|
7
|
+
import { toLocalizedString, useTranslation } from '@dxos/react-ui';
|
|
8
|
+
|
|
9
|
+
import { NAVTREE_PLUGIN } from '../meta';
|
|
10
|
+
|
|
11
|
+
export const NavTreeDocumentTitle = ({ node }: { node?: Node }) => {
|
|
12
|
+
const { t } = useTranslation(NAVTREE_PLUGIN);
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
document.title = node ? toLocalizedString(node.properties.label, t) : t('current app name', { ns: 'appkit' });
|
|
15
|
+
}, [node?.properties?.label]);
|
|
16
|
+
return null;
|
|
17
|
+
};
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { ArrowSquareOut, GearSix, Warning } from '@phosphor-icons/react';
|
|
6
|
+
import { formatDistance } from 'date-fns/formatDistance';
|
|
7
|
+
import React from 'react';
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
SettingsAction,
|
|
11
|
+
useResolvePlugin,
|
|
12
|
+
parseNavigationPlugin,
|
|
13
|
+
useIntentDispatcher,
|
|
14
|
+
type LayoutPart,
|
|
15
|
+
} from '@dxos/app-framework';
|
|
16
|
+
import { useConfig } from '@dxos/react-client';
|
|
17
|
+
import {
|
|
18
|
+
Button,
|
|
19
|
+
Tooltip,
|
|
20
|
+
Popover,
|
|
21
|
+
useSidebars,
|
|
22
|
+
useTranslation,
|
|
23
|
+
Link,
|
|
24
|
+
Message,
|
|
25
|
+
Trans,
|
|
26
|
+
useDefaultValue,
|
|
27
|
+
} from '@dxos/react-ui';
|
|
28
|
+
import { PlankHeading } from '@dxos/react-ui-deck';
|
|
29
|
+
import { getSize, mx } from '@dxos/react-ui-theme';
|
|
30
|
+
|
|
31
|
+
import { NAVTREE_PLUGIN } from '../meta';
|
|
32
|
+
|
|
33
|
+
const buttonStyles = 'pli-1.5 text-xs font-normal';
|
|
34
|
+
|
|
35
|
+
const repo = 'https://github.com/dxos/dxos';
|
|
36
|
+
|
|
37
|
+
export const NavTreeFooter = (props: { layoutPart?: LayoutPart }) => {
|
|
38
|
+
const layoutPart = useDefaultValue(props.layoutPart, 'sidebar');
|
|
39
|
+
const config = useConfig();
|
|
40
|
+
const { t } = useTranslation(NAVTREE_PLUGIN);
|
|
41
|
+
const { navigationSidebarOpen } = useSidebars(NAVTREE_PLUGIN);
|
|
42
|
+
const dispatch = useIntentDispatcher();
|
|
43
|
+
const { version, timestamp, commitHash } = config.values.runtime?.app?.build ?? {};
|
|
44
|
+
const navigationPlugin = useResolvePlugin(parseNavigationPlugin);
|
|
45
|
+
|
|
46
|
+
const releaseUrl =
|
|
47
|
+
config.values.runtime?.app?.env?.DX_ENVIRONMENT === 'production'
|
|
48
|
+
? `${repo}/releases/tag/v${version}`
|
|
49
|
+
: `${repo}/commit/${commitHash}`;
|
|
50
|
+
|
|
51
|
+
const previewUrl = 'https://docs.dxos.org/composer#technology-preview';
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<div
|
|
55
|
+
role='none'
|
|
56
|
+
className={mx(
|
|
57
|
+
'bs-[--rail-size] pbe-[env(safe-area-inset-bottom)] box-content separator-separator border-bs pli-1 flex justify-end',
|
|
58
|
+
layoutPart === 'complementary' && 'md:justify-end flex-row-reverse',
|
|
59
|
+
)}
|
|
60
|
+
>
|
|
61
|
+
<Popover.Root>
|
|
62
|
+
<Popover.Trigger asChild>
|
|
63
|
+
<Button variant='ghost' classNames={buttonStyles} {...(!navigationSidebarOpen && { tabIndex: -1 })}>
|
|
64
|
+
v{version}
|
|
65
|
+
</Button>
|
|
66
|
+
</Popover.Trigger>
|
|
67
|
+
<Popover.Portal>
|
|
68
|
+
<Popover.Content
|
|
69
|
+
side='top'
|
|
70
|
+
role='message'
|
|
71
|
+
classNames='z-[12] bg-warning-500 max-is-[min(calc(100vw-16px),40ch)]'
|
|
72
|
+
>
|
|
73
|
+
<Message.Root valence='warning' className='rounded-be-none p-5'>
|
|
74
|
+
<Message.Title>
|
|
75
|
+
<Warning weight='duotone' className='inline mie-2 is-6 bs-6' />
|
|
76
|
+
<span>{t('warning title')}</span>
|
|
77
|
+
</Message.Title>
|
|
78
|
+
<Message.Body>
|
|
79
|
+
{t('technology preview message')}
|
|
80
|
+
<br />
|
|
81
|
+
<Link href={previewUrl} target='_blank' rel='noreferrer' variant='neutral'>
|
|
82
|
+
{t('learn more label')}
|
|
83
|
+
<ArrowSquareOut className='inline mis-1' weight='bold' />
|
|
84
|
+
</Link>
|
|
85
|
+
</Message.Body>
|
|
86
|
+
</Message.Root>
|
|
87
|
+
<div role='none' className='plb-4 pli-5 space-b-2 text-base'>
|
|
88
|
+
{timestamp && (
|
|
89
|
+
<p>
|
|
90
|
+
{t('released message', {
|
|
91
|
+
released: formatDistance(new Date(timestamp), new Date(), { addSuffix: true }),
|
|
92
|
+
})}
|
|
93
|
+
<br />
|
|
94
|
+
<Link href={releaseUrl} target='_blank' rel='noreferrer' variant='neutral'>
|
|
95
|
+
{t('see release label')}
|
|
96
|
+
<ArrowSquareOut className='inline mis-1' weight='bold' />
|
|
97
|
+
</Link>
|
|
98
|
+
</p>
|
|
99
|
+
)}
|
|
100
|
+
<p>
|
|
101
|
+
<Trans
|
|
102
|
+
{...{
|
|
103
|
+
t,
|
|
104
|
+
i18nKey: 'powered by dxos message',
|
|
105
|
+
components: {
|
|
106
|
+
dxos: <Link href='https://dxos.org' target='_blank' rel='noreferrer' variant='neutral' />,
|
|
107
|
+
},
|
|
108
|
+
}}
|
|
109
|
+
/>
|
|
110
|
+
</p>
|
|
111
|
+
</div>
|
|
112
|
+
<Popover.Arrow />
|
|
113
|
+
</Popover.Content>
|
|
114
|
+
</Popover.Portal>
|
|
115
|
+
</Popover.Root>
|
|
116
|
+
|
|
117
|
+
<Tooltip.Root>
|
|
118
|
+
<Tooltip.Trigger asChild>
|
|
119
|
+
{/* TODO(burdon): Reconcile with action created by LayoutPlugin. */}
|
|
120
|
+
<Button
|
|
121
|
+
variant='ghost'
|
|
122
|
+
classNames={buttonStyles}
|
|
123
|
+
data-testid='treeView.openSettings'
|
|
124
|
+
data-joyride='welcome/settings'
|
|
125
|
+
{...(!navigationSidebarOpen && { tabIndex: -1 })}
|
|
126
|
+
onClick={() => dispatch({ action: SettingsAction.OPEN })}
|
|
127
|
+
>
|
|
128
|
+
<span className='sr-only'>{t('open settings label')}</span>
|
|
129
|
+
<GearSix className={mx(getSize(4), 'rotate-90')} />
|
|
130
|
+
</Button>
|
|
131
|
+
</Tooltip.Trigger>
|
|
132
|
+
<Tooltip.Portal>
|
|
133
|
+
<Tooltip.Content classNames='z-[12]'>
|
|
134
|
+
{t('open settings label')}
|
|
135
|
+
<Tooltip.Arrow />
|
|
136
|
+
</Tooltip.Content>
|
|
137
|
+
</Tooltip.Portal>
|
|
138
|
+
</Tooltip.Root>
|
|
139
|
+
|
|
140
|
+
{/* NOTE(thure): Unpinning from the NavTree’s default position in Deck is temporarily disabled. */}
|
|
141
|
+
{navigationPlugin?.meta.id === 'dxos.org/plugin/deck' && (
|
|
142
|
+
<PlankHeading.Controls
|
|
143
|
+
variant='hide-disabled'
|
|
144
|
+
capabilities={{
|
|
145
|
+
incrementStart: false,
|
|
146
|
+
incrementEnd: false,
|
|
147
|
+
}}
|
|
148
|
+
onClick={(_type) => {
|
|
149
|
+
// TODO(Zan): If we inmplement pinning again, we should dispatch here.
|
|
150
|
+
}}
|
|
151
|
+
/>
|
|
152
|
+
)}
|
|
153
|
+
</div>
|
|
154
|
+
);
|
|
155
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import React from 'react';
|
|
6
|
+
|
|
7
|
+
import { useIntentDispatcher, useResolvePlugin } from '@dxos/app-framework';
|
|
8
|
+
import { ClientAction } from '@dxos/plugin-client/meta';
|
|
9
|
+
import { parseObservabilityPlugin } from '@dxos/plugin-observability';
|
|
10
|
+
import { useIdentity } from '@dxos/react-client/halo';
|
|
11
|
+
|
|
12
|
+
import { HaloButton } from './HaloButton';
|
|
13
|
+
|
|
14
|
+
// TODO(thure): Refactor to be handled by a more appropriate plugin (ClientPlugin?).
|
|
15
|
+
export const NotchStart = () => {
|
|
16
|
+
const identity = useIdentity();
|
|
17
|
+
const observabilityPlugin = useResolvePlugin(parseObservabilityPlugin);
|
|
18
|
+
const dispatch = useIntentDispatcher();
|
|
19
|
+
return (
|
|
20
|
+
<HaloButton
|
|
21
|
+
size={8}
|
|
22
|
+
identityKey={identity?.identityKey.toHex()}
|
|
23
|
+
hue={identity?.profile?.data?.hue}
|
|
24
|
+
emoji={identity?.profile?.data?.emoji}
|
|
25
|
+
internal={observabilityPlugin?.provides?.observability?.group === 'dxos'}
|
|
26
|
+
onClick={() => dispatch({ action: ClientAction.SHARE_IDENTITY })}
|
|
27
|
+
/>
|
|
28
|
+
);
|
|
29
|
+
};
|