@dxos/plugin-navtree 0.7.4 → 0.7.5-feature-compute.4d9d99a
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/app-graph-builder-OF4MMBLH.mjs +42 -0
- package/dist/lib/browser/app-graph-builder-OF4MMBLH.mjs.map +7 -0
- package/dist/lib/browser/chunk-IMGT2LAL.mjs +658 -0
- package/dist/lib/browser/chunk-IMGT2LAL.mjs.map +7 -0
- package/dist/lib/browser/{chunk-PSVCPL4T.mjs → chunk-KOXMEAOS.mjs} +5 -3
- package/dist/lib/browser/chunk-KOXMEAOS.mjs.map +7 -0
- package/dist/lib/browser/chunk-OI7AAA6Q.mjs +15 -0
- package/dist/lib/browser/chunk-OI7AAA6Q.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +106 -1212
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/intent-resolver-74JPWYBP.mjs +42 -0
- package/dist/lib/browser/intent-resolver-74JPWYBP.mjs.map +7 -0
- package/dist/lib/browser/keyboard-MUBRTPI5.mjs +46 -0
- package/dist/lib/browser/keyboard-MUBRTPI5.mjs.map +7 -0
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/react-surface-6QWXI53A.mjs +189 -0
- package/dist/lib/browser/react-surface-6QWXI53A.mjs.map +7 -0
- package/dist/lib/browser/state-HT65TAYL.mjs +111 -0
- package/dist/lib/browser/state-HT65TAYL.mjs.map +7 -0
- package/dist/types/src/NavTreePlugin.d.ts +1 -3
- package/dist/types/src/NavTreePlugin.d.ts.map +1 -1
- package/dist/types/src/capabilities/app-graph-builder.d.ts +181 -0
- package/dist/types/src/capabilities/app-graph-builder.d.ts.map +1 -0
- package/dist/types/src/capabilities/capabilities.d.ts +17 -0
- package/dist/types/src/capabilities/capabilities.d.ts.map +1 -0
- package/dist/types/src/capabilities/index.d.ts +195 -0
- package/dist/types/src/capabilities/index.d.ts.map +1 -0
- package/dist/types/src/capabilities/intent-resolver.d.ts +4 -0
- package/dist/types/src/capabilities/intent-resolver.d.ts.map +1 -0
- package/dist/types/src/capabilities/keyboard.d.ts +4 -0
- package/dist/types/src/capabilities/keyboard.d.ts.map +1 -0
- package/dist/types/src/capabilities/react-surface.d.ts +4 -0
- package/dist/types/src/capabilities/react-surface.d.ts.map +1 -0
- package/dist/types/src/capabilities/state.d.ts +17 -0
- package/dist/types/src/capabilities/state.d.ts.map +1 -0
- package/dist/types/src/components/CommandsTrigger.d.ts.map +1 -1
- package/dist/types/src/components/NavTree.d.ts +2 -2
- package/dist/types/src/components/NavTree.d.ts.map +1 -1
- package/dist/types/src/components/NavTreeContainer.d.ts.map +1 -1
- package/dist/types/src/components/NavTreeFooter.d.ts.map +1 -1
- package/dist/types/src/components/NavTreeItemAction.d.ts +4 -8
- package/dist/types/src/components/NavTreeItemAction.d.ts.map +1 -1
- package/dist/types/src/components/NavTreeItemAction.stories.d.ts +0 -5
- package/dist/types/src/components/NavTreeItemAction.stories.d.ts.map +1 -1
- package/dist/types/src/components/NotchStart.d.ts.map +1 -1
- 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/events.d.ts +4 -0
- package/dist/types/src/events.d.ts.map +1 -0
- package/dist/types/src/index.d.ts +3 -2
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/meta.d.ts +2 -2
- package/dist/types/src/meta.d.ts.map +1 -1
- package/dist/types/src/translations.d.ts +0 -1
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/src/types.d.ts +1 -1
- package/dist/types/src/types.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -0
- package/package.json +31 -38
- package/src/NavTreePlugin.tsx +70 -287
- package/src/capabilities/app-graph-builder.ts +40 -0
- package/src/capabilities/capabilities.ts +18 -0
- package/src/capabilities/index.ts +13 -0
- package/src/capabilities/intent-resolver.ts +30 -0
- package/src/capabilities/keyboard.ts +54 -0
- package/src/capabilities/react-surface.tsx +82 -0
- package/src/capabilities/state.ts +100 -0
- package/src/components/CommandsDialog.tsx +3 -3
- package/src/components/CommandsTrigger.tsx +11 -8
- package/src/components/NavTree.tsx +6 -11
- package/src/components/NavTreeContainer.tsx +22 -56
- package/src/components/NavTreeFooter.tsx +21 -18
- package/src/components/NavTreeItemAction.stories.tsx +0 -2
- package/src/components/NavTreeItemAction.tsx +51 -317
- package/src/components/NotchStart.tsx +8 -7
- package/src/components/index.ts +1 -0
- package/src/events.ts +11 -0
- package/src/index.ts +3 -4
- package/src/meta.ts +3 -1
- package/src/translations.ts +0 -1
- package/src/types.ts +1 -1
- package/dist/lib/browser/chunk-PSVCPL4T.mjs.map +0 -7
- package/dist/lib/browser/meta.mjs +0 -11
- package/dist/lib/browser/meta.mjs.map +0 -7
package/src/NavTreePlugin.tsx
CHANGED
|
@@ -1,203 +1,46 @@
|
|
|
1
1
|
//
|
|
2
|
-
// Copyright
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { effect } from '@preact/signals-core';
|
|
6
|
-
import React from 'react';
|
|
7
|
-
|
|
8
5
|
import {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
6
|
+
definePlugin,
|
|
7
|
+
defineModule,
|
|
8
|
+
Events,
|
|
9
|
+
contributes,
|
|
10
|
+
Capabilities,
|
|
11
|
+
allOf,
|
|
12
|
+
oneOf,
|
|
14
13
|
NavigationAction,
|
|
15
|
-
|
|
16
|
-
parseIntentPlugin,
|
|
17
|
-
parseLayoutPlugin,
|
|
18
|
-
parseNavigationPlugin,
|
|
19
|
-
type Plugin,
|
|
20
|
-
type PluginDefinition,
|
|
21
|
-
resolvePlugin,
|
|
22
|
-
type SurfaceProvides,
|
|
23
|
-
type TranslationsProvides,
|
|
14
|
+
createIntent,
|
|
24
15
|
} from '@dxos/app-framework';
|
|
25
|
-
import { createExtension, type Graph, isAction, isGraphNode, type Node } from '@dxos/app-graph';
|
|
26
|
-
import { type UnsubscribeCallback } from '@dxos/async';
|
|
27
|
-
import { Keyboard } from '@dxos/keyboard';
|
|
28
|
-
import { create, type ReactiveObject } from '@dxos/live-object';
|
|
29
16
|
import { type TreeData } from '@dxos/react-ui-list';
|
|
30
|
-
import { Path } from '@dxos/react-ui-mosaic';
|
|
31
|
-
import { getHostPlatform } from '@dxos/util';
|
|
32
17
|
|
|
33
|
-
import {
|
|
34
|
-
import {
|
|
35
|
-
import
|
|
18
|
+
import { AppGraphBuilder, IntentResolver, Keyboard, ReactSurface, State } from './capabilities';
|
|
19
|
+
import { NODE_TYPE } from './components';
|
|
20
|
+
import { NavTreeEvents } from './events';
|
|
21
|
+
import { meta } from './meta';
|
|
36
22
|
import translations from './translations';
|
|
37
|
-
import { type NavTreeItemGraphNode } from './types';
|
|
38
|
-
import { expandChildrenAndActions } from './util';
|
|
39
|
-
|
|
40
|
-
export type NavTreePluginProvides = SurfaceProvides &
|
|
41
|
-
MetadataRecordsProvides &
|
|
42
|
-
GraphBuilderProvides &
|
|
43
|
-
TranslationsProvides &
|
|
44
|
-
IntentResolverProvides;
|
|
45
|
-
|
|
46
|
-
const KEY = `${NAVTREE_PLUGIN}/state`;
|
|
47
|
-
const getInitialState = () => {
|
|
48
|
-
const stringified = localStorage.getItem(KEY);
|
|
49
|
-
if (!stringified) {
|
|
50
|
-
return;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
try {
|
|
54
|
-
const cached: [string, { open: boolean; current: boolean }][] = JSON.parse(stringified);
|
|
55
|
-
return cached.map(
|
|
56
|
-
([key, value]): [
|
|
57
|
-
string,
|
|
58
|
-
ReactiveObject<{
|
|
59
|
-
open: boolean;
|
|
60
|
-
current: boolean;
|
|
61
|
-
}>,
|
|
62
|
-
] => [key, create({ open: value.open, current: false })],
|
|
63
|
-
);
|
|
64
|
-
} catch {}
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
export const NavTreePlugin = (): PluginDefinition<NavTreePluginProvides> => {
|
|
68
|
-
let graphPlugin: Plugin<GraphProvides> | undefined;
|
|
69
|
-
let graph: Graph | undefined;
|
|
70
|
-
let unsubscribe: UnsubscribeCallback | undefined;
|
|
71
|
-
|
|
72
|
-
// TODO(wittjosiah): This currently needs to be not a ReactiveObject at the root.
|
|
73
|
-
// If it is a ReactiveObject then React errors when initializing new paths because of state change during render.
|
|
74
|
-
// Ideally this could be a ReactiveObject but be able to access and update the root level without breaking render.
|
|
75
|
-
// Wrapping accesses and updates in `untracked` didn't seem to work in all cases.
|
|
76
|
-
const state = new Map<string, ReactiveObject<{ open: boolean; current: boolean }>>(
|
|
77
|
-
getInitialState() ?? [
|
|
78
|
-
// TODO(thure): Initialize these dynamically.
|
|
79
|
-
['root', create({ open: true, current: false })],
|
|
80
|
-
['root~dxos.org/plugin/space-spaces', create({ open: true, current: false })],
|
|
81
|
-
['root~dxos.org/plugin/files', create({ open: true, current: false })],
|
|
82
|
-
],
|
|
83
|
-
);
|
|
84
|
-
|
|
85
|
-
const getItem = (_path: string[]) => {
|
|
86
|
-
const path = Path.create(..._path);
|
|
87
|
-
const value = state.get(path) ?? create({ open: false, current: false });
|
|
88
|
-
if (!state.has(path)) {
|
|
89
|
-
state.set(path, value);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
return value;
|
|
93
|
-
};
|
|
94
|
-
|
|
95
|
-
const setItem = (path: string[], key: 'open' | 'current', next: boolean) => {
|
|
96
|
-
const value = getItem(path);
|
|
97
|
-
value[key] = next;
|
|
98
|
-
|
|
99
|
-
localStorage.setItem(KEY, JSON.stringify(Array.from(state.entries())));
|
|
100
|
-
};
|
|
101
|
-
|
|
102
|
-
const isOpen = (path: string[]) => getItem(path).open;
|
|
103
|
-
const isCurrent = (path: string[]) => getItem(path).current;
|
|
104
|
-
|
|
105
|
-
const handleOpenChange = ({ item: { id }, path, open }: { item: Node; path: string[]; open: boolean }) => {
|
|
106
|
-
// TODO(thure): This might become a localstorage leak; openItemIds that no longer exist should be removed from this map.
|
|
107
|
-
setItem(path, 'open', open);
|
|
108
|
-
|
|
109
|
-
if (graph) {
|
|
110
|
-
const node = graph.findNode(id);
|
|
111
|
-
return node && expandChildrenAndActions(graph, node as NavTreeItemGraphNode);
|
|
112
|
-
}
|
|
113
|
-
};
|
|
114
|
-
|
|
115
|
-
return {
|
|
116
|
-
meta,
|
|
117
|
-
ready: async (plugins) => {
|
|
118
|
-
const layout = resolvePlugin(plugins, parseLayoutPlugin)?.provides.layout;
|
|
119
|
-
const location = resolvePlugin(plugins, parseNavigationPlugin)?.provides.location;
|
|
120
|
-
graphPlugin = resolvePlugin(plugins, parseGraphPlugin);
|
|
121
|
-
graph = graphPlugin?.provides.graph;
|
|
122
|
-
if (!graph || !location || !layout) {
|
|
123
|
-
return;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
const soloPart = location?.active.solo?.[0];
|
|
127
|
-
const dispatch = resolvePlugin(plugins, parseIntentPlugin)?.provides.intent.dispatch;
|
|
128
|
-
if (dispatch && soloPart) {
|
|
129
|
-
void dispatch({ plugin: NAVTREE_PLUGIN, action: NavigationAction.EXPOSE, data: { id: soloPart.id } });
|
|
130
|
-
}
|
|
131
23
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
setItem(Path.parts(key), 'current', true);
|
|
153
|
-
});
|
|
154
|
-
});
|
|
155
|
-
});
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
// TODO(wittjosiah): Factor out.
|
|
159
|
-
// TODO(wittjosiah): Handle removal of actions.
|
|
160
|
-
graph.subscribeTraverse({
|
|
161
|
-
visitor: (node, path) => {
|
|
162
|
-
let shortcut: string | undefined;
|
|
163
|
-
if (typeof node.properties.keyBinding === 'object') {
|
|
164
|
-
const availablePlatforms = Object.keys(node.properties.keyBinding);
|
|
165
|
-
const platform = getHostPlatform();
|
|
166
|
-
shortcut = availablePlatforms.includes(platform)
|
|
167
|
-
? node.properties.keyBinding[platform]
|
|
168
|
-
: platform === 'ios'
|
|
169
|
-
? node.properties.keyBinding.macos // Fallback to macos if ios-specific bindings not provided.
|
|
170
|
-
: platform === 'linux' || platform === 'unknown'
|
|
171
|
-
? node.properties.keyBinding.windows // Fallback to windows if platform-specific bindings not provided.
|
|
172
|
-
: undefined;
|
|
173
|
-
} else {
|
|
174
|
-
shortcut = node.properties.keyBinding;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
if (shortcut && isAction(node)) {
|
|
178
|
-
Keyboard.singleton.getContext(path.slice(0, -1).join('/')).bind({
|
|
179
|
-
shortcut,
|
|
180
|
-
handler: () => {
|
|
181
|
-
void node.data({ node, caller: KEY_BINDING });
|
|
182
|
-
},
|
|
183
|
-
data: node.properties.label,
|
|
184
|
-
});
|
|
185
|
-
}
|
|
186
|
-
},
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
// TODO(burdon): Create context and plugin.
|
|
190
|
-
Keyboard.singleton.initialize();
|
|
191
|
-
Keyboard.singleton.setCurrentContext(graphPlugin?.provides.graph.root.id);
|
|
192
|
-
},
|
|
193
|
-
unload: async () => {
|
|
194
|
-
unsubscribe?.();
|
|
195
|
-
Keyboard.singleton.destroy();
|
|
196
|
-
},
|
|
197
|
-
provides: {
|
|
198
|
-
metadata: {
|
|
199
|
-
records: {
|
|
200
|
-
[NODE_TYPE]: {
|
|
24
|
+
export const NavTreePlugin = () =>
|
|
25
|
+
definePlugin(meta, [
|
|
26
|
+
defineModule({
|
|
27
|
+
id: `${meta.id}/module/state`,
|
|
28
|
+
activatesOn: allOf(Events.LayoutReady, Events.LocationReady),
|
|
29
|
+
activatesAfter: [NavTreeEvents.StateReady],
|
|
30
|
+
activate: State,
|
|
31
|
+
}),
|
|
32
|
+
defineModule({
|
|
33
|
+
id: `${meta.id}/module/translations`,
|
|
34
|
+
activatesOn: Events.SetupTranslations,
|
|
35
|
+
activate: () => contributes(Capabilities.Translations, translations),
|
|
36
|
+
}),
|
|
37
|
+
defineModule({
|
|
38
|
+
id: `${meta.id}/module/metadata`,
|
|
39
|
+
activatesOn: oneOf(Events.Startup, Events.SetupAppGraph),
|
|
40
|
+
activate: () =>
|
|
41
|
+
contributes(Capabilities.Metadata, {
|
|
42
|
+
id: NODE_TYPE,
|
|
43
|
+
metadata: {
|
|
201
44
|
parse: ({ item }: TreeData, type: string) => {
|
|
202
45
|
switch (type) {
|
|
203
46
|
case 'node':
|
|
@@ -209,101 +52,41 @@ export const NavTreePlugin = (): PluginDefinition<NavTreePluginProvides> => {
|
|
|
209
52
|
}
|
|
210
53
|
},
|
|
211
54
|
},
|
|
212
|
-
},
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
isOpen={isOpen}
|
|
228
|
-
isCurrent={isCurrent}
|
|
229
|
-
onOpenChange={handleOpenChange}
|
|
230
|
-
popoverAnchorId={data.popoverAnchorId as string}
|
|
231
|
-
/>
|
|
232
|
-
);
|
|
233
|
-
|
|
234
|
-
case 'document-title': {
|
|
235
|
-
return <NavTreeDocumentTitle node={isGraphNode(data.activeNode) ? data.activeNode : undefined} />;
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
case 'notch-start':
|
|
239
|
-
return <NotchStart />;
|
|
240
|
-
|
|
241
|
-
case 'search-input':
|
|
242
|
-
return {
|
|
243
|
-
node: <CommandsTrigger />,
|
|
244
|
-
disposition: 'fallback',
|
|
245
|
-
};
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
return null;
|
|
249
|
-
},
|
|
250
|
-
},
|
|
251
|
-
intent: {
|
|
252
|
-
resolver: async (intent) => {
|
|
253
|
-
switch (intent.action) {
|
|
254
|
-
case NavigationAction.EXPOSE: {
|
|
255
|
-
if (graph && intent.data?.id) {
|
|
256
|
-
const path = await graph.waitForPath({ target: intent.data.id });
|
|
257
|
-
[...Array(path.length)].forEach((_, index) => {
|
|
258
|
-
const subpath = path.slice(0, index);
|
|
259
|
-
const value = getItem(subpath);
|
|
260
|
-
if (!value.open) {
|
|
261
|
-
setItem(subpath, 'open', true);
|
|
262
|
-
}
|
|
263
|
-
});
|
|
264
|
-
}
|
|
265
|
-
break;
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
},
|
|
269
|
-
},
|
|
270
|
-
graph: {
|
|
271
|
-
builder: (plugins) => {
|
|
272
|
-
// TODO(burdon): Move to separate plugin (for keys and command k). Move bindings from LayoutPlugin.
|
|
273
|
-
const intentPlugin = resolvePlugin(plugins, parseIntentPlugin);
|
|
274
|
-
|
|
275
|
-
return [
|
|
276
|
-
createExtension({
|
|
277
|
-
id: NAVTREE_PLUGIN,
|
|
278
|
-
filter: (node): node is Node<null> => node.id === 'root',
|
|
279
|
-
actions: () => [
|
|
280
|
-
{
|
|
281
|
-
id: 'dxos.org/plugin/navtree/open-commands',
|
|
282
|
-
data: async () => {
|
|
283
|
-
await intentPlugin?.provides.intent.dispatch({
|
|
284
|
-
action: LayoutAction.SET_LAYOUT,
|
|
285
|
-
data: {
|
|
286
|
-
element: 'dialog',
|
|
287
|
-
component: `${NAVTREE_PLUGIN}/Commands`,
|
|
288
|
-
dialogBlockAlign: 'start',
|
|
289
|
-
},
|
|
290
|
-
});
|
|
291
|
-
},
|
|
292
|
-
properties: {
|
|
293
|
-
label: ['open commands label', { ns: NAVTREE_PLUGIN }],
|
|
294
|
-
icon: 'ph--magnifying-glass--regular',
|
|
295
|
-
keyBinding: {
|
|
296
|
-
macos: 'meta+k',
|
|
297
|
-
windows: 'ctrl+k',
|
|
298
|
-
},
|
|
299
|
-
},
|
|
300
|
-
},
|
|
301
|
-
],
|
|
302
|
-
}),
|
|
303
|
-
];
|
|
304
|
-
},
|
|
55
|
+
}),
|
|
56
|
+
}),
|
|
57
|
+
defineModule({
|
|
58
|
+
id: `${meta.id}/module/expose`,
|
|
59
|
+
activatesOn: allOf(Events.DispatcherReady, Events.LayoutReady, Events.LocationReady, NavTreeEvents.StateReady),
|
|
60
|
+
activate: async (context) => {
|
|
61
|
+
const location = context.requestCapability(Capabilities.Location);
|
|
62
|
+
const { dispatchPromise: dispatch } = context.requestCapability(Capabilities.IntentDispatcher);
|
|
63
|
+
|
|
64
|
+
const soloPart = location.active.solo?.[0];
|
|
65
|
+
if (dispatch && soloPart) {
|
|
66
|
+
await dispatch(createIntent(NavigationAction.Expose, { id: soloPart.id }));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return [];
|
|
305
70
|
},
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
71
|
+
}),
|
|
72
|
+
defineModule({
|
|
73
|
+
id: `${meta.id}/module/keyboard`,
|
|
74
|
+
activatesOn: Events.AppGraphReady,
|
|
75
|
+
activate: Keyboard,
|
|
76
|
+
}),
|
|
77
|
+
defineModule({
|
|
78
|
+
id: `${meta.id}/module/react-surface`,
|
|
79
|
+
activatesOn: Events.Startup,
|
|
80
|
+
activate: ReactSurface,
|
|
81
|
+
}),
|
|
82
|
+
defineModule({
|
|
83
|
+
id: `${meta.id}/module/intent-resolver`,
|
|
84
|
+
activatesOn: Events.SetupIntents,
|
|
85
|
+
activate: IntentResolver,
|
|
86
|
+
}),
|
|
87
|
+
defineModule({
|
|
88
|
+
id: `${meta.id}/module/app-graph-builder`,
|
|
89
|
+
activatesOn: Events.SetupAppGraph,
|
|
90
|
+
activate: AppGraphBuilder,
|
|
91
|
+
}),
|
|
92
|
+
]);
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { Capabilities, contributes, type PluginsContext, createIntent, LayoutAction } from '@dxos/app-framework';
|
|
6
|
+
import { createExtension, type Node } from '@dxos/plugin-graph';
|
|
7
|
+
|
|
8
|
+
import { COMMANDS_DIALOG, NAVTREE_PLUGIN } from '../meta';
|
|
9
|
+
|
|
10
|
+
export default (context: PluginsContext) =>
|
|
11
|
+
contributes(
|
|
12
|
+
Capabilities.AppGraphBuilder,
|
|
13
|
+
createExtension({
|
|
14
|
+
id: NAVTREE_PLUGIN,
|
|
15
|
+
filter: (node): node is Node<null> => node.id === 'root',
|
|
16
|
+
actions: () => [
|
|
17
|
+
{
|
|
18
|
+
id: COMMANDS_DIALOG,
|
|
19
|
+
data: async () => {
|
|
20
|
+
const { dispatchPromise: dispatch } = context.requestCapability(Capabilities.IntentDispatcher);
|
|
21
|
+
await dispatch(
|
|
22
|
+
createIntent(LayoutAction.SetLayout, {
|
|
23
|
+
element: 'dialog',
|
|
24
|
+
component: COMMANDS_DIALOG,
|
|
25
|
+
dialogBlockAlign: 'start',
|
|
26
|
+
}),
|
|
27
|
+
);
|
|
28
|
+
},
|
|
29
|
+
properties: {
|
|
30
|
+
label: ['open commands label', { ns: NAVTREE_PLUGIN }],
|
|
31
|
+
icon: 'ph--magnifying-glass--regular',
|
|
32
|
+
keyBinding: {
|
|
33
|
+
macos: 'meta+k',
|
|
34
|
+
windows: 'ctrl+k',
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
}),
|
|
40
|
+
);
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { defineCapability } from '@dxos/app-framework';
|
|
6
|
+
import { type ReactiveObject } from '@dxos/live-object';
|
|
7
|
+
|
|
8
|
+
import { NAVTREE_PLUGIN } from '../meta';
|
|
9
|
+
|
|
10
|
+
export namespace NavTreeCapabilities {
|
|
11
|
+
export const State = defineCapability<{
|
|
12
|
+
state: Map<string, ReactiveObject<{ open: boolean; current: boolean }>>;
|
|
13
|
+
getItem: (path: string[]) => ReactiveObject<{ open: boolean; current: boolean }>;
|
|
14
|
+
setItem: (path: string[], key: 'open' | 'current', next: boolean) => void;
|
|
15
|
+
isOpen: (path: string[]) => boolean;
|
|
16
|
+
isCurrent: (path: string[]) => boolean;
|
|
17
|
+
}>(`${NAVTREE_PLUGIN}/capability/state`);
|
|
18
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { lazy } from '@dxos/app-framework';
|
|
6
|
+
|
|
7
|
+
export const AppGraphBuilder = lazy(() => import('./app-graph-builder'));
|
|
8
|
+
export const IntentResolver = lazy(() => import('./intent-resolver'));
|
|
9
|
+
export const Keyboard = lazy(() => import('./keyboard'));
|
|
10
|
+
export const ReactSurface = lazy(() => import('./react-surface'));
|
|
11
|
+
export const State = lazy(() => import('./state'));
|
|
12
|
+
|
|
13
|
+
export * from './capabilities';
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { contributes, type PluginsContext, Capabilities, NavigationAction, createResolver } from '@dxos/app-framework';
|
|
6
|
+
import { log } from '@dxos/log';
|
|
7
|
+
|
|
8
|
+
import { NavTreeCapabilities } from './capabilities';
|
|
9
|
+
|
|
10
|
+
export default (context: PluginsContext) =>
|
|
11
|
+
contributes(
|
|
12
|
+
Capabilities.IntentResolver,
|
|
13
|
+
createResolver(NavigationAction.Expose, async ({ id }) => {
|
|
14
|
+
const { graph } = context.requestCapability(Capabilities.AppGraph);
|
|
15
|
+
const { getItem, setItem } = context.requestCapability(NavTreeCapabilities.State);
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
const path = await graph.waitForPath({ target: id }, { timeout: 1_000 });
|
|
19
|
+
[...Array(path.length)].forEach((_, index) => {
|
|
20
|
+
const subpath = path.slice(0, index);
|
|
21
|
+
const value = getItem(subpath);
|
|
22
|
+
if (!value.open) {
|
|
23
|
+
setItem(subpath, 'open', true);
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
} catch {
|
|
27
|
+
log.warn('Path to node not found', { id });
|
|
28
|
+
}
|
|
29
|
+
}),
|
|
30
|
+
);
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { Capabilities as AppCapabilities, contributes, type PluginsContext } from '@dxos/app-framework';
|
|
6
|
+
import { Keyboard } from '@dxos/keyboard';
|
|
7
|
+
import { isAction } from '@dxos/plugin-graph';
|
|
8
|
+
import { getHostPlatform } from '@dxos/util';
|
|
9
|
+
|
|
10
|
+
import { KEY_BINDING } from '../meta';
|
|
11
|
+
|
|
12
|
+
export default (context: PluginsContext) => {
|
|
13
|
+
const { graph } = context.requestCapability(AppCapabilities.AppGraph);
|
|
14
|
+
|
|
15
|
+
// TODO(wittjosiah): Factor out.
|
|
16
|
+
// TODO(wittjosiah): Handle removal of actions.
|
|
17
|
+
const unsubscribe = graph.subscribeTraverse({
|
|
18
|
+
visitor: (node, path) => {
|
|
19
|
+
let shortcut: string | undefined;
|
|
20
|
+
if (typeof node.properties.keyBinding === 'object') {
|
|
21
|
+
const availablePlatforms = Object.keys(node.properties.keyBinding);
|
|
22
|
+
const platform = getHostPlatform();
|
|
23
|
+
shortcut = availablePlatforms.includes(platform)
|
|
24
|
+
? node.properties.keyBinding[platform]
|
|
25
|
+
: platform === 'ios'
|
|
26
|
+
? node.properties.keyBinding.macos // Fallback to macos if ios-specific bindings not provided.
|
|
27
|
+
: platform === 'linux' || platform === 'unknown'
|
|
28
|
+
? node.properties.keyBinding.windows // Fallback to windows if platform-specific bindings not provided.
|
|
29
|
+
: undefined;
|
|
30
|
+
} else {
|
|
31
|
+
shortcut = node.properties.keyBinding;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (shortcut && isAction(node)) {
|
|
35
|
+
Keyboard.singleton.getContext(path.slice(0, -1).join('/')).bind({
|
|
36
|
+
shortcut,
|
|
37
|
+
handler: () => {
|
|
38
|
+
void node.data({ node, caller: KEY_BINDING });
|
|
39
|
+
},
|
|
40
|
+
data: node.properties.label,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// TODO(burdon): Create context and plugin.
|
|
47
|
+
Keyboard.singleton.initialize();
|
|
48
|
+
Keyboard.singleton.setCurrentContext(graph.root.id);
|
|
49
|
+
|
|
50
|
+
return contributes(AppCapabilities.Null, null, () => {
|
|
51
|
+
unsubscribe();
|
|
52
|
+
Keyboard.singleton.destroy();
|
|
53
|
+
});
|
|
54
|
+
};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import React, { useCallback } from 'react';
|
|
6
|
+
|
|
7
|
+
import { createSurface, Capabilities, contributes, useCapability } from '@dxos/app-framework';
|
|
8
|
+
import { isGraphNode, type Node } from '@dxos/plugin-graph';
|
|
9
|
+
|
|
10
|
+
import { NavTreeCapabilities } from './capabilities';
|
|
11
|
+
import {
|
|
12
|
+
CommandsDialogContent,
|
|
13
|
+
CommandsTrigger,
|
|
14
|
+
NavTreeDocumentTitle,
|
|
15
|
+
NotchStart,
|
|
16
|
+
NavTreeContainer,
|
|
17
|
+
} from '../components';
|
|
18
|
+
import { NavTreeFooter } from '../components/NavTreeFooter';
|
|
19
|
+
import { COMMANDS_DIALOG, NAVTREE_PLUGIN } from '../meta';
|
|
20
|
+
import { type NavTreeItemGraphNode } from '../types';
|
|
21
|
+
import { expandChildrenAndActions } from '../util';
|
|
22
|
+
|
|
23
|
+
export default () =>
|
|
24
|
+
contributes(Capabilities.ReactSurface, [
|
|
25
|
+
createSurface({
|
|
26
|
+
id: COMMANDS_DIALOG,
|
|
27
|
+
role: 'dialog',
|
|
28
|
+
filter: (data): data is { subject?: string } => data.component === COMMANDS_DIALOG,
|
|
29
|
+
component: ({ data }) => <CommandsDialogContent selected={data.subject} />,
|
|
30
|
+
}),
|
|
31
|
+
createSurface({
|
|
32
|
+
id: `${NAVTREE_PLUGIN}/navigation`,
|
|
33
|
+
role: 'navigation',
|
|
34
|
+
component: ({ data }) => {
|
|
35
|
+
const { graph } = useCapability(Capabilities.AppGraph);
|
|
36
|
+
const { isOpen, isCurrent, setItem } = useCapability(NavTreeCapabilities.State);
|
|
37
|
+
|
|
38
|
+
const handleOpenChange = useCallback(
|
|
39
|
+
({ item: { id }, path, open }: { item: Node; path: string[]; open: boolean }) => {
|
|
40
|
+
// TODO(thure): This might become a localstorage leak; openItemIds that no longer exist should be removed from this map.
|
|
41
|
+
setItem(path, 'open', open);
|
|
42
|
+
|
|
43
|
+
if (graph) {
|
|
44
|
+
const node = graph.findNode(id);
|
|
45
|
+
return node && expandChildrenAndActions(graph, node as NavTreeItemGraphNode);
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
[graph],
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<NavTreeContainer
|
|
53
|
+
isOpen={isOpen}
|
|
54
|
+
isCurrent={isCurrent}
|
|
55
|
+
onOpenChange={handleOpenChange}
|
|
56
|
+
popoverAnchorId={data.popoverAnchorId as string | undefined}
|
|
57
|
+
/>
|
|
58
|
+
);
|
|
59
|
+
},
|
|
60
|
+
}),
|
|
61
|
+
createSurface({
|
|
62
|
+
id: `${NAVTREE_PLUGIN}/document-title`,
|
|
63
|
+
role: 'document-title',
|
|
64
|
+
component: ({ data }) => <NavTreeDocumentTitle node={isGraphNode(data.subject) ? data.subject : undefined} />,
|
|
65
|
+
}),
|
|
66
|
+
createSurface({
|
|
67
|
+
id: `${NAVTREE_PLUGIN}/notch-start`,
|
|
68
|
+
role: 'notch-start',
|
|
69
|
+
component: () => <NotchStart />,
|
|
70
|
+
}),
|
|
71
|
+
createSurface({
|
|
72
|
+
id: `${NAVTREE_PLUGIN}/header-end`,
|
|
73
|
+
role: 'header-end',
|
|
74
|
+
component: () => <NavTreeFooter />,
|
|
75
|
+
}),
|
|
76
|
+
createSurface({
|
|
77
|
+
id: `${NAVTREE_PLUGIN}/search-input`,
|
|
78
|
+
role: 'search-input',
|
|
79
|
+
disposition: 'fallback',
|
|
80
|
+
component: () => <CommandsTrigger />,
|
|
81
|
+
}),
|
|
82
|
+
]);
|