@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.
Files changed (64) hide show
  1. package/LICENSE +8 -0
  2. package/README.md +15 -0
  3. package/dist/lib/browser/chunk-U7MTKDFQ.mjs +13 -0
  4. package/dist/lib/browser/chunk-U7MTKDFQ.mjs.map +7 -0
  5. package/dist/lib/browser/index.mjs +1055 -0
  6. package/dist/lib/browser/index.mjs.map +7 -0
  7. package/dist/lib/browser/meta.json +1 -0
  8. package/dist/lib/browser/meta.mjs +11 -0
  9. package/dist/lib/browser/meta.mjs.map +7 -0
  10. package/dist/types/src/NavTreePlugin.d.ts +4 -0
  11. package/dist/types/src/NavTreePlugin.d.ts.map +1 -0
  12. package/dist/types/src/components/CommandsDialog.d.ts +5 -0
  13. package/dist/types/src/components/CommandsDialog.d.ts.map +1 -0
  14. package/dist/types/src/components/CommandsTrigger.d.ts +3 -0
  15. package/dist/types/src/components/CommandsTrigger.d.ts.map +1 -0
  16. package/dist/types/src/components/HaloButton.d.ts +12 -0
  17. package/dist/types/src/components/HaloButton.d.ts.map +1 -0
  18. package/dist/types/src/components/HaloButton.stories.d.ts +12 -0
  19. package/dist/types/src/components/HaloButton.stories.d.ts.map +1 -0
  20. package/dist/types/src/components/NavBarStart.d.ts +7 -0
  21. package/dist/types/src/components/NavBarStart.d.ts.map +1 -0
  22. package/dist/types/src/components/NavTreeContainer.d.ts +12 -0
  23. package/dist/types/src/components/NavTreeContainer.d.ts.map +1 -0
  24. package/dist/types/src/components/NavTreeDocumentTitle.d.ts +5 -0
  25. package/dist/types/src/components/NavTreeDocumentTitle.d.ts.map +1 -0
  26. package/dist/types/src/components/NavTreeFooter.d.ts +6 -0
  27. package/dist/types/src/components/NavTreeFooter.d.ts.map +1 -0
  28. package/dist/types/src/components/NotchStart.d.ts +3 -0
  29. package/dist/types/src/components/NotchStart.d.ts.map +1 -0
  30. package/dist/types/src/components/index.d.ts +6 -0
  31. package/dist/types/src/components/index.d.ts.map +1 -0
  32. package/dist/types/src/experimental/Tree.d.ts +56 -0
  33. package/dist/types/src/experimental/Tree.d.ts.map +1 -0
  34. package/dist/types/src/experimental/Tree.stories.d.ts +12 -0
  35. package/dist/types/src/experimental/Tree.stories.d.ts.map +1 -0
  36. package/dist/types/src/index.d.ts +5 -0
  37. package/dist/types/src/index.d.ts.map +1 -0
  38. package/dist/types/src/meta.d.ts +7 -0
  39. package/dist/types/src/meta.d.ts.map +1 -0
  40. package/dist/types/src/stories/Graph.stories.d.ts +17 -0
  41. package/dist/types/src/stories/Graph.stories.d.ts.map +1 -0
  42. package/dist/types/src/translations.d.ts +27 -0
  43. package/dist/types/src/translations.d.ts.map +1 -0
  44. package/dist/types/src/util.d.ts +32 -0
  45. package/dist/types/src/util.d.ts.map +1 -0
  46. package/package.json +77 -0
  47. package/src/NavTreePlugin.tsx +262 -0
  48. package/src/components/CommandsDialog.tsx +114 -0
  49. package/src/components/CommandsTrigger.tsx +34 -0
  50. package/src/components/HaloButton.stories.tsx +19 -0
  51. package/src/components/HaloButton.tsx +41 -0
  52. package/src/components/NavBarStart.tsx +48 -0
  53. package/src/components/NavTreeContainer.tsx +375 -0
  54. package/src/components/NavTreeDocumentTitle.tsx +17 -0
  55. package/src/components/NavTreeFooter.tsx +155 -0
  56. package/src/components/NotchStart.tsx +29 -0
  57. package/src/components/index.ts +9 -0
  58. package/src/experimental/Tree.stories.tsx +229 -0
  59. package/src/experimental/Tree.tsx +282 -0
  60. package/src/index.ts +10 -0
  61. package/src/meta.ts +12 -0
  62. package/src/stories/Graph.stories.tsx +162 -0
  63. package/src/translations.ts +28 -0
  64. package/src/util.ts +231 -0
@@ -0,0 +1,27 @@
1
+ declare const _default: ({
2
+ 'en-US': {
3
+ "react-ui-navtree": {
4
+ "tree item actions label": string;
5
+ "tree item searchlist input placeholder": string;
6
+ "empty branch message": string;
7
+ };
8
+ };
9
+ } | {
10
+ 'en-US': {
11
+ "dxos.org/plugin/navtree": {
12
+ 'open settings label': string;
13
+ 'open commands label': string;
14
+ 'commands dialog title': string;
15
+ 'command list input placeholder': string;
16
+ 'node actions menu invoker label': string;
17
+ 'warning title': string;
18
+ 'technology preview message': string;
19
+ 'learn more label': string;
20
+ 'released message': string;
21
+ 'see release label': string;
22
+ 'powered by dxos message': string;
23
+ };
24
+ };
25
+ })[];
26
+ export default _default;
27
+ //# sourceMappingURL=translations.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"translations.d.ts","sourceRoot":"","sources":["../../../src/translations.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AAQA,wBAmBE"}
@@ -0,0 +1,32 @@
1
+ import { type Action, type Node, type NodeFilter, type Graph } from '@dxos/app-graph';
2
+ import { type NavTreeItemNode, type NavTreeItemNodeProperties, type OpenItemIds } from '@dxos/react-ui-navtree';
3
+ import { type MaybePromise } from '@dxos/util';
4
+ export type NavTreeItemGraphNode = Node<any, NavTreeItemNodeProperties & Partial<{
5
+ persistenceClass: string;
6
+ persistenceKey: string;
7
+ acceptPersistenceClass: Set<string>;
8
+ acceptPersistenceKey: Set<string>;
9
+ onRearrangeChildren: (nextOrder: NavTreeItemGraphNode[]) => MaybePromise<void>;
10
+ onCopy: (activeNode: NavTreeItemGraphNode, index?: number) => MaybePromise<void>;
11
+ onTransferStart: (activeNode: NavTreeItemGraphNode, index?: number) => MaybePromise<void>;
12
+ onTransferEnd: (activeNode: NavTreeItemGraphNode, destinationParent: NavTreeItemGraphNode) => MaybePromise<void>;
13
+ }>>;
14
+ export type NavTreeItem = NavTreeItemNode<NavTreeItemGraphNode>;
15
+ export declare const getParent: (graph: Graph, node: NavTreeItemGraphNode, path: string[]) => NavTreeItemGraphNode | undefined;
16
+ export declare const getPersistenceParent: (graph: Graph, node: NavTreeItemGraphNode, path: string[], persistenceClass: string) => NavTreeItemGraphNode | null;
17
+ export declare const resolveMigrationOperation: (graph: Graph, activeNode: NavTreeItemGraphNode, destinationPath: string[], destinationRelatedNode?: NavTreeItemGraphNode) => "transfer" | "copy" | "reject";
18
+ export declare const sortActions: (actions: Action[]) => Action[];
19
+ export declare const getTreeItemNode: (treeItems: NavTreeItemNode[], path?: string[]) => NavTreeItemNode | undefined;
20
+ export declare const getChildren: (graph: Graph, node: NavTreeItemGraphNode, filter?: NodeFilter, path?: string[]) => NavTreeItemGraphNode[];
21
+ type FlattenedActions = Required<Pick<NavTreeItemNode, 'actions' | 'groupedActions'>>;
22
+ export declare const getActions: (graph: Graph, node: NavTreeItemGraphNode) => FlattenedActions;
23
+ export declare const expandChildrenAndActions: (graph: Graph, node: Node) => Promise<[void, [void, void]]>;
24
+ export declare const expandChildren: (graph: Graph, node: Node) => Promise<void>;
25
+ export declare const expandActions: (graph: Graph, node: Node) => Promise<[void, void]>;
26
+ /**
27
+ * Get a reactive tree node from a graph node.
28
+ */
29
+ export declare const treeItemsFromRootNode: (graph: Graph, rootNode: NavTreeItemGraphNode, openItemIds: OpenItemIds, path?: string[], filter?: NodeFilter) => NavTreeItem[];
30
+ export declare const expandOpenGraphNodes: (graph: Graph, openItemIds: OpenItemIds) => Promise<[void, [void, void]][]>;
31
+ export {};
32
+ //# sourceMappingURL=util.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../../../src/util.ts"],"names":[],"mappings":"AAIA,OAAO,EACL,KAAK,MAAM,EACX,KAAK,IAAI,EACT,KAAK,UAAU,EACf,KAAK,KAAK,EAIX,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EAGL,KAAK,eAAe,EACpB,KAAK,yBAAyB,EAC9B,KAAK,WAAW,EACjB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,KAAK,YAAY,EAAe,MAAM,YAAY,CAAC;AAE5D,MAAM,MAAM,oBAAoB,GAAG,IAAI,CACrC,GAAG,EACH,yBAAyB,GACvB,OAAO,CAAC;IACN,gBAAgB,EAAE,MAAM,CAAC;IACzB,cAAc,EAAE,MAAM,CAAC;IACvB,sBAAsB,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACpC,oBAAoB,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAClC,mBAAmB,EAAE,CAAC,SAAS,EAAE,oBAAoB,EAAE,KAAK,YAAY,CAAC,IAAI,CAAC,CAAC;IAC/E,MAAM,EAAE,CAAC,UAAU,EAAE,oBAAoB,EAAE,KAAK,CAAC,EAAE,MAAM,KAAK,YAAY,CAAC,IAAI,CAAC,CAAC;IACjF,eAAe,EAAE,CAAC,UAAU,EAAE,oBAAoB,EAAE,KAAK,CAAC,EAAE,MAAM,KAAK,YAAY,CAAC,IAAI,CAAC,CAAC;IAC1F,aAAa,EAAE,CAAC,UAAU,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,oBAAoB,KAAK,YAAY,CAAC,IAAI,CAAC,CAAC;CAClH,CAAC,CACL,CAAC;AACF,MAAM,MAAM,WAAW,GAAG,eAAe,CAAC,oBAAoB,CAAC,CAAC;AAEhE,eAAO,MAAM,SAAS,UACb,KAAK,QACN,oBAAoB,QACpB,MAAM,EAAE,KACb,oBAAoB,GAAG,SAGzB,CAAC;AAEF,eAAO,MAAM,oBAAoB,UACxB,KAAK,QACN,oBAAoB,QACpB,MAAM,EAAE,oBACI,MAAM,KACvB,oBAAoB,GAAG,IAOzB,CAAC;AAEF,eAAO,MAAM,yBAAyB,UAC7B,KAAK,cACA,oBAAoB,mBACf,MAAM,EAAE,2BACA,oBAAoB,KAC5C,UAAU,GAAG,MAAM,GAAG,QAsBxB,CAAC;AAGF,eAAO,MAAM,WAAW,YAAa,MAAM,EAAE,KAAG,MAAM,EAWlD,CAAC;AAEL,eAAO,MAAM,eAAe,cAAe,eAAe,EAAE,SAAS,MAAM,EAAE,KAAG,eAAe,GAAG,SAQjG,CAAC;AAEF,eAAO,MAAM,WAAW,UACf,KAAK,QACN,oBAAoB,WACjB,UAAU,SACb,MAAM,EAAE,KACb,oBAAoB,EAStB,CAAC;AAEF,KAAK,gBAAgB,GAAG,QAAQ,CAAC,IAAI,CAAC,eAAe,EAAE,SAAS,GAAG,gBAAgB,CAAC,CAAC,CAAC;AAEtF,eAAO,MAAM,UAAU,UAAW,KAAK,QAAQ,oBAAoB,KAAG,gBAYrE,CAAC;AAEF,eAAO,MAAM,wBAAwB,UAAW,KAAK,QAAQ,IAAI,kCAEhE,CAAC;AAEF,eAAO,MAAM,cAAc,UAAW,KAAK,QAAQ,IAAI,kBAEtD,CAAC;AAEF,eAAO,MAAM,aAAa,UAAW,KAAK,QAAQ,IAAI,0BAErD,CAAC;AAmDF;;GAEG;AACH,eAAO,MAAM,qBAAqB,UACzB,KAAK,YACF,oBAAoB,eACjB,WAAW,SAClB,MAAM,EAAE,WACL,UAAU,KAClB,WAAW,EAEb,CAAC;AAEF,eAAO,MAAM,oBAAoB,UAAW,KAAK,eAAe,WAAW,oCAS1E,CAAC"}
package/package.json ADDED
@@ -0,0 +1,77 @@
1
+ {
2
+ "name": "@dxos/plugin-navtree",
3
+ "version": "0.6.8-main.046e6cf",
4
+ "description": "DXOS Surface plugin for a tree view visualization of the graph.",
5
+ "homepage": "https://dxos.org",
6
+ "bugs": "https://github.com/dxos/dxos/issues",
7
+ "license": "MIT",
8
+ "author": "DXOS.org",
9
+ "exports": {
10
+ ".": {
11
+ "browser": "./dist/lib/browser/index.mjs",
12
+ "types": "./dist/types/src/index.d.ts"
13
+ },
14
+ "./meta": {
15
+ "browser": "./dist/lib/browser/meta.mjs",
16
+ "types": "./dist/types/src/meta.d.ts"
17
+ }
18
+ },
19
+ "types": "dist/types/src/index.d.ts",
20
+ "typesVersions": {
21
+ "*": {
22
+ "meta": [
23
+ "dist/types/src/meta.d.ts"
24
+ ]
25
+ }
26
+ },
27
+ "files": [
28
+ "dist",
29
+ "src"
30
+ ],
31
+ "dependencies": {
32
+ "@preact/signals-core": "^1.6.0",
33
+ "date-fns": "^3.3.1",
34
+ "@dxos/app-framework": "0.6.8-main.046e6cf",
35
+ "@dxos/app-graph": "0.6.8-main.046e6cf",
36
+ "@dxos/async": "0.6.8-main.046e6cf",
37
+ "@dxos/debug": "0.6.8-main.046e6cf",
38
+ "@dxos/echo-schema": "0.6.8-main.046e6cf",
39
+ "@dxos/keyboard": "0.6.8-main.046e6cf",
40
+ "@dxos/log": "0.6.8-main.046e6cf",
41
+ "@dxos/local-storage": "0.6.8-main.046e6cf",
42
+ "@dxos/plugin-client": "0.6.8-main.046e6cf",
43
+ "@dxos/plugin-graph": "0.6.8-main.046e6cf",
44
+ "@dxos/plugin-observability": "0.6.8-main.046e6cf",
45
+ "@dxos/plugin-theme": "0.6.8-main.046e6cf",
46
+ "@dxos/protocols": "0.6.8-main.046e6cf",
47
+ "@dxos/react-ui-deck": "0.6.8-main.046e6cf",
48
+ "@dxos/react-client": "0.6.8-main.046e6cf",
49
+ "@dxos/react-ui-mosaic": "0.6.8-main.046e6cf",
50
+ "@dxos/react-ui-navtree": "0.6.8-main.046e6cf",
51
+ "@dxos/react-ui-searchlist": "0.6.8-main.046e6cf",
52
+ "@dxos/util": "0.6.8-main.046e6cf"
53
+ },
54
+ "devDependencies": {
55
+ "@phosphor-icons/react": "^2.1.5",
56
+ "@types/react": "~18.2.0",
57
+ "@types/react-dom": "~18.2.0",
58
+ "react": "~18.2.0",
59
+ "react-dom": "~18.2.0",
60
+ "vite": "^5.3.4",
61
+ "@dxos/echo-signals": "0.6.8-main.046e6cf",
62
+ "@dxos/random": "0.6.8-main.046e6cf",
63
+ "@dxos/react-ui-theme": "0.6.8-main.046e6cf",
64
+ "@dxos/react-ui": "0.6.8-main.046e6cf",
65
+ "@dxos/storybook-utils": "0.6.8-main.046e6cf"
66
+ },
67
+ "peerDependencies": {
68
+ "@phosphor-icons/react": "^2.1.5",
69
+ "react": "^18.0.0",
70
+ "react-dom": "^18.0.0",
71
+ "@dxos/react-ui": "0.6.8-main.046e6cf",
72
+ "@dxos/react-ui-theme": "0.6.8-main.046e6cf"
73
+ },
74
+ "publishConfig": {
75
+ "access": "public"
76
+ }
77
+ }
@@ -0,0 +1,262 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import { MagnifyingGlass, type IconProps } from '@phosphor-icons/react';
6
+ import React from 'react';
7
+
8
+ import {
9
+ type GraphBuilderProvides,
10
+ resolvePlugin,
11
+ type MetadataRecordsProvides,
12
+ type PluginDefinition,
13
+ type SurfaceProvides,
14
+ type TranslationsProvides,
15
+ type Plugin,
16
+ type IntentResolverProvides,
17
+ parseIntentPlugin,
18
+ LayoutAction,
19
+ type GraphProvides,
20
+ parseGraphPlugin,
21
+ NavigationAction,
22
+ } from '@dxos/app-framework';
23
+ import { createExtension, type Graph, isAction, isGraphNode, type Node } from '@dxos/app-graph';
24
+ import { Keyboard } from '@dxos/keyboard';
25
+ import { LocalStorageStore } from '@dxos/local-storage';
26
+ import { Path } from '@dxos/react-ui-mosaic';
27
+ import { type OpenItemIds } from '@dxos/react-ui-navtree';
28
+ import { getHostPlatform } from '@dxos/util';
29
+
30
+ import {
31
+ CommandsDialogContent,
32
+ NODE_TYPE,
33
+ NavBarStart,
34
+ NavTreeContainer,
35
+ NavTreeDocumentTitle,
36
+ NotchStart,
37
+ } from './components';
38
+ import { CommandsTrigger } from './components/CommandsTrigger';
39
+ import meta, { KEY_BINDING, NAVTREE_PLUGIN } from './meta';
40
+ import translations from './translations';
41
+ import { expandOpenGraphNodes, getActions, getChildren, type NavTreeItem, type NavTreeItemGraphNode } from './util';
42
+
43
+ export type NavTreePluginProvides = SurfaceProvides &
44
+ MetadataRecordsProvides &
45
+ GraphBuilderProvides &
46
+ TranslationsProvides &
47
+ IntentResolverProvides;
48
+
49
+ type NavTreeState = { root?: NavTreeItemGraphNode; openItemIds: OpenItemIds };
50
+
51
+ export const NavTreePlugin = (): PluginDefinition<NavTreePluginProvides> => {
52
+ const state = new LocalStorageStore<NavTreeState>('dxos.org/settings/navtree', {
53
+ // TODO(thure): Do this dynamically.
54
+ openItemIds: { root: true, 'dxos.org/plugin/space-spaces': true, 'dxos.org/plugin/files': true },
55
+ });
56
+
57
+ let graphPlugin: Plugin<GraphProvides> | undefined;
58
+ let graph: Graph | undefined;
59
+
60
+ const handleOpenItemIdsChange = (nextOpenItemIds: OpenItemIds) => {
61
+ // TODO(thure): This might become a localstorage leak; openItemIds that no longer exist should be removed from this map.
62
+ state.values.openItemIds = nextOpenItemIds;
63
+ if (graph) {
64
+ void expandOpenGraphNodes(graph, nextOpenItemIds);
65
+ }
66
+ };
67
+
68
+ return {
69
+ meta,
70
+ ready: async (plugins) => {
71
+ graphPlugin = resolvePlugin(plugins, parseGraphPlugin);
72
+ graph = graphPlugin?.provides.graph;
73
+ if (!graph) {
74
+ return;
75
+ }
76
+
77
+ state.values.root = graph.root as NavTreeItemGraphNode;
78
+ getChildren(graph, state.values.root);
79
+ getActions(graph, state.values.root);
80
+
81
+ state.prop({ key: 'openItemIds', storageKey: 'openItemIds', type: LocalStorageStore.json<OpenItemIds>() });
82
+
83
+ void expandOpenGraphNodes(graph, state.values.openItemIds);
84
+
85
+ // TODO(wittjosiah): Factor out.
86
+ // TODO(wittjosiah): Handle removal of actions.
87
+ graph.subscribeTraverse({
88
+ visitor: (node, path) => {
89
+ let shortcut: string | undefined;
90
+ if (typeof node.properties.keyBinding === 'object') {
91
+ const availablePlatforms = Object.keys(node.properties.keyBinding);
92
+ const platform = getHostPlatform();
93
+ shortcut = availablePlatforms.includes(platform)
94
+ ? node.properties.keyBinding[platform]
95
+ : platform === 'ios'
96
+ ? node.properties.keyBinding.macos // Fallback to macos if ios-specific bindings not provided.
97
+ : platform === 'linux' || platform === 'unknown'
98
+ ? node.properties.keyBinding.windows // Fallback to windows if platform-specific bindings not provided.
99
+ : undefined;
100
+ } else {
101
+ shortcut = node.properties.keyBinding;
102
+ }
103
+
104
+ if (shortcut && isAction(node)) {
105
+ Keyboard.singleton.getContext(path.slice(0, -1).join('/')).bind({
106
+ shortcut,
107
+ handler: () => {
108
+ void node.data({ node, caller: KEY_BINDING });
109
+ },
110
+ data: node.properties.label,
111
+ });
112
+ }
113
+ },
114
+ });
115
+
116
+ // TODO(burdon): Create context and plugin.
117
+ Keyboard.singleton.initialize();
118
+ Keyboard.singleton.setCurrentContext(graphPlugin?.provides.graph.root.id);
119
+ },
120
+ unload: async () => {
121
+ Keyboard.singleton.destroy();
122
+ },
123
+ provides: {
124
+ metadata: {
125
+ records: {
126
+ [NODE_TYPE]: {
127
+ parse: (item: NavTreeItem, type: string) => {
128
+ switch (type) {
129
+ case 'node':
130
+ return item.node;
131
+ case 'object':
132
+ return item.node.data;
133
+ case 'view-object':
134
+ return { id: `${item.id}-view`, object: item.node.data };
135
+ }
136
+ },
137
+ },
138
+ },
139
+ },
140
+ surface: {
141
+ component: ({ data, role }) => {
142
+ switch (data.component) {
143
+ case `${NAVTREE_PLUGIN}/Commands`: {
144
+ const selected = typeof data.subject === 'string' ? data.subject : undefined;
145
+ return <CommandsDialogContent selected={selected} />;
146
+ }
147
+ }
148
+
149
+ switch (role) {
150
+ case 'navigation':
151
+ if (state.values.root && state.values.openItemIds) {
152
+ return (
153
+ <NavTreeContainer
154
+ root={state.values.root}
155
+ activeIds={data.activeIds as Set<string>}
156
+ openItemIds={state.values.openItemIds}
157
+ onOpenItemIdsChange={handleOpenItemIdsChange}
158
+ attended={data.attended as Set<string>}
159
+ popoverAnchorId={data.popoverAnchorId as string}
160
+ />
161
+ );
162
+ }
163
+ break;
164
+
165
+ case 'document-title': {
166
+ return <NavTreeDocumentTitle node={isGraphNode(data.activeNode) ? data.activeNode : undefined} />;
167
+ }
168
+
169
+ case 'navbar-start': {
170
+ if (state.values.root && data.activeNode) {
171
+ return {
172
+ node: (
173
+ <NavBarStart
174
+ activeNode={data.activeNode as Node}
175
+ popoverAnchorId={data.popoverAnchorId as string | undefined}
176
+ />
177
+ ),
178
+ disposition: 'hoist',
179
+ };
180
+ }
181
+ break;
182
+ }
183
+
184
+ case 'notch-start':
185
+ return <NotchStart />;
186
+
187
+ case 'search-input':
188
+ return {
189
+ node: <CommandsTrigger />,
190
+ disposition: 'fallback',
191
+ };
192
+ }
193
+
194
+ return null;
195
+ },
196
+ },
197
+ intent: {
198
+ resolver: (intent) => {
199
+ switch (intent.action) {
200
+ case NavigationAction.EXPOSE: {
201
+ if (graph && intent.data?.id) {
202
+ const path = graph.getPath({ target: intent.data.id });
203
+ if (Array.isArray(path)) {
204
+ const additionalOpenItems = [...Array(path.length - 1)].reduce((acc: OpenItemIds, _, index) => {
205
+ const itemId = Path.create(...path.slice(1, index + 1));
206
+ if (itemId.length > 0) {
207
+ acc[itemId] = true;
208
+ }
209
+ return acc;
210
+ }, {});
211
+ state.values.openItemIds = {
212
+ ...additionalOpenItems,
213
+ ...state.values.openItemIds,
214
+ };
215
+ }
216
+ }
217
+ break;
218
+ }
219
+ }
220
+ },
221
+ },
222
+ graph: {
223
+ builder: (plugins) => {
224
+ // TODO(burdon): Move to separate plugin (for keys and command k). Move bindings from LayoutPlugin.
225
+ const intentPlugin = resolvePlugin(plugins, parseIntentPlugin);
226
+
227
+ return [
228
+ createExtension({
229
+ id: NAVTREE_PLUGIN,
230
+ filter: (node): node is Node<null> => node.id === 'root',
231
+ actions: () => [
232
+ {
233
+ id: 'dxos.org/plugin/navtree/open-commands',
234
+ data: async () => {
235
+ await intentPlugin?.provides.intent.dispatch({
236
+ action: LayoutAction.SET_LAYOUT,
237
+ data: {
238
+ element: 'dialog',
239
+ component: `${NAVTREE_PLUGIN}/Commands`,
240
+ dialogBlockAlign: 'start',
241
+ },
242
+ });
243
+ },
244
+ properties: {
245
+ label: ['open commands label', { ns: NAVTREE_PLUGIN }],
246
+ icon: (props: IconProps) => <MagnifyingGlass {...props} />,
247
+ iconSymbol: 'ph--magnifying-glass--regular',
248
+ keyBinding: {
249
+ macos: 'meta+k',
250
+ windows: 'ctrl+k',
251
+ },
252
+ },
253
+ },
254
+ ],
255
+ }),
256
+ ];
257
+ },
258
+ },
259
+ translations,
260
+ },
261
+ };
262
+ };
@@ -0,0 +1,114 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import { DotOutline } from '@phosphor-icons/react';
6
+ import React, { useMemo, useState } from 'react';
7
+
8
+ import { useIntentDispatcher, LayoutAction } from '@dxos/app-framework';
9
+ import { type ActionLike, isActionGroup, isAction } from '@dxos/app-graph';
10
+ import { Keyboard, keySymbols } from '@dxos/keyboard';
11
+ import { useGraph } from '@dxos/plugin-graph';
12
+ import { Button, Dialog, useTranslation, toLocalizedString } from '@dxos/react-ui';
13
+ import { SearchList } from '@dxos/react-ui-searchlist';
14
+ import { descriptionText, getSize, mx } from '@dxos/react-ui-theme';
15
+ import { getHostPlatform } from '@dxos/util';
16
+
17
+ import { KEY_BINDING, NAVTREE_PLUGIN } from '../meta';
18
+
19
+ // TODO(wittjosiah): This probably deserves its own plugin but for now it lives here w/ other navigation UI.
20
+ export const CommandsDialogContent = ({ selected: initial }: { selected?: string }) => {
21
+ const { t } = useTranslation(NAVTREE_PLUGIN);
22
+ const [selected, setSelected] = useState<string | undefined>(initial);
23
+ const dispatch = useIntentDispatcher();
24
+ const { graph } = useGraph();
25
+
26
+ // Traverse graph.
27
+ // TODO(burdon): Factor out commonality with shortcut dialog.
28
+ const allActions = useMemo(() => {
29
+ // TODO(burdon): Get from navtree (not keyboard).
30
+ const current = Keyboard.singleton.getCurrentContext();
31
+ const actionMap = new Set<string>();
32
+ const actions: ActionLike[] = [];
33
+ graph?.traverse({
34
+ visitor: (node, path) => {
35
+ if (
36
+ (isAction(node) || isActionGroup(node)) &&
37
+ !actionMap.has(node.id) &&
38
+ current.startsWith(path.slice(0, -1).join('/'))
39
+ ) {
40
+ actionMap.add(node.id);
41
+ actions.push(node);
42
+ }
43
+ },
44
+ });
45
+
46
+ actions.sort((a, b) => {
47
+ return toLocalizedString(a.properties.label, t)
48
+ ?.toLowerCase()
49
+ .localeCompare(toLocalizedString(b.properties.label, t)?.toLowerCase());
50
+ });
51
+
52
+ // console.log(JSON.stringify(actions, undefined, 2));
53
+ return actions;
54
+ }, [graph]);
55
+
56
+ const group = allActions.find(({ id }) => id === selected);
57
+ const actions = isActionGroup(group) ? graph.actions(group) : allActions;
58
+
59
+ return (
60
+ <Dialog.Content classNames={['md:max-is-[30rem] overflow-hidden mbs-12']}>
61
+ <Dialog.Title>{t('commands dialog title', { ns: NAVTREE_PLUGIN })}</Dialog.Title>
62
+
63
+ {/* TODO(burdon): BUG: Overscrolls container. */}
64
+ <SearchList.Root label={t('command list input placeholder')} classNames='flex flex-col grow overflow-hidden my-2'>
65
+ <SearchList.Input placeholder={t('command list input placeholder')} classNames='px-1 my-2' />
66
+ <SearchList.Content classNames='max-bs-[24rem] overflow-auto'>
67
+ {actions?.map((action) => {
68
+ const label = toLocalizedString(action.properties.label, t);
69
+ const shortcut =
70
+ typeof action.properties.keyBinding === 'string'
71
+ ? action.properties.keyBinding
72
+ : action.properties.keyBinding?.[getHostPlatform()];
73
+ const Icon = action.properties.icon ?? DotOutline;
74
+ return (
75
+ <SearchList.Item
76
+ value={label}
77
+ key={action.id}
78
+ onSelect={() => {
79
+ if (action.properties.disabled) {
80
+ return;
81
+ }
82
+
83
+ if (isActionGroup(action)) {
84
+ setSelected(action.id);
85
+ return;
86
+ }
87
+
88
+ void dispatch({ action: LayoutAction.SET_LAYOUT, data: { element: 'dialog', state: false } });
89
+ setTimeout(() => {
90
+ const node = graph.nodes(group ?? action, { relation: 'inbound' })[0];
91
+ void (node && isAction(action) && action.data({ node, caller: KEY_BINDING }));
92
+ });
93
+ }}
94
+ classNames='flex items-center gap-2'
95
+ disabled={action.properties.disabled}
96
+ {...(action.properties?.testId && { 'data-testid': action.properties.testId })}
97
+ >
98
+ <Icon className={mx(getSize(4), 'shrink-0', !action.properties.icon && 'invisible')} />
99
+ <span className='grow truncate'>{label}</span>
100
+ {shortcut && <span className={mx('shrink-0', descriptionText)}>{keySymbols(shortcut).join('')}</span>}
101
+ </SearchList.Item>
102
+ );
103
+ })}
104
+ </SearchList.Content>
105
+ </SearchList.Root>
106
+
107
+ <Dialog.Close asChild>
108
+ <Button variant='primary' classNames='mbs-2'>
109
+ {t('close label', { ns: 'os' })}
110
+ </Button>
111
+ </Dialog.Close>
112
+ </Dialog.Content>
113
+ );
114
+ };
@@ -0,0 +1,34 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import { MagnifyingGlass } from '@phosphor-icons/react';
6
+ import React from 'react';
7
+
8
+ import { LayoutAction, useIntent } from '@dxos/app-framework';
9
+ import { Button, useTranslation } from '@dxos/react-ui';
10
+ import { getSize } from '@dxos/react-ui-theme';
11
+
12
+ import { NAVTREE_PLUGIN } from '../meta';
13
+
14
+ // TODO(thure): Refactor to be handled by a more appropriate plugin.
15
+ export const CommandsTrigger = () => {
16
+ const { dispatch } = useIntent();
17
+ const { t } = useTranslation(NAVTREE_PLUGIN);
18
+ return (
19
+ <Button
20
+ classNames='pis-3 pie-2 gap-2 mli-1 mbs-1 min-bs-0 bs-[--rail-action] justify-start'
21
+ onClick={() =>
22
+ dispatch({
23
+ action: LayoutAction.SET_LAYOUT,
24
+ data: { element: 'dialog', component: `${NAVTREE_PLUGIN}/Commands`, dialogBlockAlign: 'start' },
25
+ })
26
+ }
27
+ >
28
+ <span className='fg-description text-base font-normal grow text-start'>
29
+ {t('command list input placeholder')}
30
+ </span>
31
+ <MagnifyingGlass className={getSize(5)} />
32
+ </Button>
33
+ );
34
+ };
@@ -0,0 +1,19 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import '@dxosTheme';
6
+
7
+ import React from 'react';
8
+
9
+ import { HaloButton } from './HaloButton';
10
+
11
+ export default {
12
+ title: 'plugin-navtree/HaloButton',
13
+ component: HaloButton,
14
+ actions: { argTypesRegex: '^on.*' },
15
+ };
16
+
17
+ export const Normal = (props: any) => {
18
+ return <HaloButton {...props} />;
19
+ };
@@ -0,0 +1,41 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import React from 'react';
6
+
7
+ import { ConnectionState, useNetworkStatus } from '@dxos/react-client/mesh';
8
+ import { Avatar, type Size } from '@dxos/react-ui';
9
+ import { focusRing, mx } from '@dxos/react-ui-theme';
10
+ import { hexToFallback } from '@dxos/util';
11
+
12
+ const INTERNAL = 'https://avatars.githubusercontent.com/u/57182821';
13
+
14
+ export type HaloButtonProps = {
15
+ size?: Size;
16
+ identityKey?: string;
17
+ hue?: string;
18
+ emoji?: string;
19
+ internal?: boolean;
20
+ onClick?: () => void;
21
+ };
22
+
23
+ export const HaloButton = (props: HaloButtonProps) => {
24
+ const { onClick, identityKey, internal, size = 8 } = props;
25
+ const fallbackValue = hexToFallback(identityKey ?? '0');
26
+ const { swarm: connectionState } = useNetworkStatus();
27
+ return (
28
+ <button className={mx(focusRing, 'rounded grid place-items-center')} onClick={onClick}>
29
+ <Avatar.Root
30
+ size={size}
31
+ variant='circle'
32
+ status={connectionState === ConnectionState.OFFLINE ? 'error' : internal ? 'internal' : 'active'}
33
+ hue={props.hue || fallbackValue.hue}
34
+ >
35
+ <Avatar.Frame data-testid='treeView.haloButton' data-joyride='welcome/halo'>
36
+ {internal ? <Avatar.Image href={INTERNAL} /> : <Avatar.Fallback text={props.emoji || fallbackValue.emoji} />}
37
+ </Avatar.Frame>
38
+ </Avatar.Root>
39
+ </button>
40
+ );
41
+ };
@@ -0,0 +1,48 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import { DotsThreeVertical } from '@phosphor-icons/react';
6
+ import React, { Fragment } from 'react';
7
+
8
+ import { getGraph, type Node } from '@dxos/app-graph';
9
+ import { Popover, toLocalizedString, useTranslation } from '@dxos/react-ui';
10
+ import { PlankHeading } from '@dxos/react-ui-deck';
11
+
12
+ import { KEY_BINDING, NAVTREE_PLUGIN } from '../meta';
13
+
14
+ const TREE_ITEM_MAIN_HEADING = 'TreeItemMainHeading';
15
+
16
+ export const NavBarStart = ({ activeNode, popoverAnchorId }: { activeNode: Node; popoverAnchorId?: string }) => {
17
+ const { t } = useTranslation(NAVTREE_PLUGIN);
18
+
19
+ const ActionRoot =
20
+ popoverAnchorId === `dxos.org/ui/${TREE_ITEM_MAIN_HEADING}/${activeNode.id}` ||
21
+ popoverAnchorId === `dxos.org/ui/${KEY_BINDING}/${activeNode.id}`
22
+ ? Popover.Anchor
23
+ : Fragment;
24
+
25
+ const Icon = activeNode.properties?.icon ?? DotsThreeVertical;
26
+ const graph = getGraph(activeNode);
27
+ const actions = graph.actions(activeNode);
28
+ const label = toLocalizedString(activeNode.properties?.label, t);
29
+
30
+ const menuTriggerLabel = t('node actions menu invoker label');
31
+
32
+ return (
33
+ <>
34
+ <ActionRoot>
35
+ <PlankHeading.ActionsMenu
36
+ Icon={Icon}
37
+ attendableId={activeNode.id}
38
+ triggerLabel={menuTriggerLabel}
39
+ actions={actions}
40
+ onAction={(action) =>
41
+ typeof action.data === 'function' && action.data({ node: action as Node, caller: TREE_ITEM_MAIN_HEADING })
42
+ }
43
+ />
44
+ </ActionRoot>
45
+ <PlankHeading.Label attendableId={activeNode.id}>{label}</PlankHeading.Label>
46
+ </>
47
+ );
48
+ };