@dxos/plugin-navtree 0.8.1 → 0.8.2-main.2f9c567
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/{chunk-DN43QXSX.mjs → chunk-FBQKXPOR.mjs} +134 -47
- package/dist/lib/browser/chunk-FBQKXPOR.mjs.map +7 -0
- package/dist/lib/browser/{chunk-4RQJXI4D.mjs → chunk-TLQLMZXU.mjs} +1 -1
- package/dist/lib/browser/chunk-TLQLMZXU.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +3 -2
- package/dist/lib/browser/index.mjs.map +2 -2
- package/dist/lib/browser/{intent-resolver-BZMUS24C.mjs → intent-resolver-XCABZFA6.mjs} +5 -5
- package/dist/lib/browser/intent-resolver-XCABZFA6.mjs.map +7 -0
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/{react-surface-3HCM3SHJ.mjs → react-surface-FQEYRV4Q.mjs} +3 -3
- package/dist/lib/browser/{state-M5UJ3MHL.mjs → state-EW62QYEN.mjs} +17 -11
- package/dist/lib/browser/state-EW62QYEN.mjs.map +7 -0
- package/dist/types/src/capabilities/app-graph-builder.d.ts.map +1 -1
- package/dist/types/src/capabilities/capabilities.d.ts +7 -4
- package/dist/types/src/capabilities/capabilities.d.ts.map +1 -1
- package/dist/types/src/capabilities/index.d.ts +34 -3
- package/dist/types/src/capabilities/index.d.ts.map +1 -1
- package/dist/types/src/capabilities/intent-resolver.d.ts.map +1 -1
- package/dist/types/src/capabilities/state.d.ts +7 -4
- package/dist/types/src/capabilities/state.d.ts.map +1 -1
- package/dist/types/src/components/L1Panels.d.ts.map +1 -1
- package/dist/types/src/components/NavTreeContainer.d.ts.map +1 -1
- package/dist/types/src/components/NavTreeItemAction.d.ts.map +1 -1
- package/dist/types/src/components/NavTreeItemColumns.d.ts +1 -1
- package/dist/types/src/components/NavTreeItemColumns.d.ts.map +1 -1
- package/dist/types/src/components/types.d.ts +4 -0
- package/dist/types/src/components/types.d.ts.map +1 -1
- package/dist/types/src/testing/generator.d.ts +6 -6
- package/dist/types/src/testing/generator.d.ts.map +1 -1
- package/dist/types/src/translations.d.ts +1 -0
- package/dist/types/src/translations.d.ts.map +1 -1
- package/package.json +33 -31
- package/src/capabilities/capabilities.ts +5 -4
- package/src/capabilities/intent-resolver.ts +4 -3
- package/src/capabilities/state.ts +20 -14
- package/src/components/L1Panels.tsx +115 -30
- package/src/components/NavTree.stories.tsx +5 -5
- package/src/components/NavTreeContainer.tsx +16 -2
- package/src/components/NavTreeItemAction.tsx +10 -4
- package/src/components/NavTreeItemColumns.tsx +29 -27
- package/src/components/UserAccountAvatar.tsx +1 -1
- package/src/components/types.ts +4 -0
- package/src/stories/Graph.stories.tsx +4 -4
- package/src/testing/generator.ts +13 -11
- package/src/translations.ts +1 -0
- package/dist/lib/browser/chunk-4RQJXI4D.mjs.map +0 -7
- package/dist/lib/browser/chunk-DN43QXSX.mjs.map +0 -7
- package/dist/lib/browser/intent-resolver-BZMUS24C.mjs.map +0 -7
- package/dist/lib/browser/state-M5UJ3MHL.mjs.map +0 -7
- /package/dist/lib/browser/{react-surface-3HCM3SHJ.mjs.map → react-surface-FQEYRV4Q.mjs.map} +0 -0
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { type
|
|
1
|
+
import { Schema } from 'effect';
|
|
2
|
+
import { type Live } from '@dxos/live-object';
|
|
3
3
|
export type TestItem = {
|
|
4
4
|
id: string;
|
|
5
5
|
type: string;
|
|
6
6
|
} & Record<string, any>;
|
|
7
7
|
type ObjectDataGenerator = {
|
|
8
|
-
createSchema?: () =>
|
|
8
|
+
createSchema?: () => Schema.Schema.AnyNoContext;
|
|
9
9
|
createData: () => any;
|
|
10
10
|
};
|
|
11
|
-
type ObjectFactory<T extends
|
|
12
|
-
schema?:
|
|
11
|
+
type ObjectFactory<T extends Live<any>> = {
|
|
12
|
+
schema?: Schema.Schema.AnyNoContext;
|
|
13
13
|
createObject: () => T;
|
|
14
14
|
};
|
|
15
15
|
type ObjectFactoryMap = {
|
|
@@ -29,7 +29,7 @@ export declare class TestObjectGenerator {
|
|
|
29
29
|
types?: string[];
|
|
30
30
|
factories?: ObjectFactoryMap;
|
|
31
31
|
});
|
|
32
|
-
get schema():
|
|
32
|
+
get schema(): Schema.Schema.AnyNoContext[];
|
|
33
33
|
createObject({ types }?: {
|
|
34
34
|
types?: string[];
|
|
35
35
|
}): any;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generator.d.ts","sourceRoot":"","sources":["../../../../src/testing/generator.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"generator.d.ts","sourceRoot":"","sources":["../../../../src/testing/generator.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAIhC,OAAO,EAAQ,KAAK,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAQpD,MAAM,MAAM,QAAQ,GAAG;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;AAE1E,KAAK,mBAAmB,GAAG;IACzB,YAAY,CAAC,EAAE,MAAM,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC;IAChD,UAAU,EAAE,MAAM,GAAG,CAAC;CACvB,CAAC;AAEF,KAAK,aAAa,CAAC,CAAC,SAAS,IAAI,CAAC,GAAG,CAAC,IAAI;IACxC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC;IACpC,YAAY,EAAE,MAAM,CAAC,CAAC;CACvB,CAAC;AAEF,KAAK,gBAAgB,GAAG;IAAE,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,CAAC,GAAG,CAAC,CAAA;CAAE,CAAC;AAW/D,eAAO,MAAM,MAAM,UAAgC,CAAC;AACpD,eAAO,MAAM,QAAQ,UAAkB,CAAC;AAExC,eAAO,MAAM,iBAAiB,EAAE;IAAE,CAAC,IAAI,EAAE,MAAM,GAAG,mBAAmB,CAAA;CAgCpE,CAAC;AAEF;;GAEG;AACH,qBAAa,mBAAmB;IAC9B,SAAgB,SAAS,EAAE,gBAAgB,CAAC;gBAEhC,EAAE,KAAK,EAAE,SAAS,EAAE,GAAE;QAAE,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;QAAC,SAAS,CAAC,EAAE,gBAAgB,CAAA;KAAO;IASzF,IAAI,MAAM,IAAI,MAAM,CAAC,MAAM,CAAC,YAAY,EAAE,CAEzC;IAED,YAAY,CAAC,EAAE,KAAK,EAAE,GAAE;QAAE,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;KAAO;IAMjD,aAAa,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE;CAGtE;AAeD,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8EtB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"translations.d.ts","sourceRoot":"","sources":["../../../src/translations.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"translations.d.ts","sourceRoot":"","sources":["../../../src/translations.ts"],"names":[],"mappings":";;;;;;;;;;;;;;AAMA,wBAeE"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dxos/plugin-navtree",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.2-main.2f9c567",
|
|
4
4
|
"description": "DXOS Surface plugin for a tree view visualization of the graph.",
|
|
5
5
|
"homepage": "https://dxos.org",
|
|
6
6
|
"bugs": "https://github.com/dxos/dxos/issues",
|
|
@@ -29,29 +29,30 @@
|
|
|
29
29
|
"@radix-ui/react-dismissable-layer": "1.1.5",
|
|
30
30
|
"@radix-ui/react-use-controllable-state": "1.1.0",
|
|
31
31
|
"date-fns": "^3.3.1",
|
|
32
|
-
"
|
|
33
|
-
"@dxos/app-
|
|
34
|
-
"@dxos/
|
|
35
|
-
"@dxos/
|
|
36
|
-
"@dxos/
|
|
37
|
-
"@dxos/
|
|
38
|
-
"@dxos/
|
|
39
|
-
"@dxos/
|
|
40
|
-
"@dxos/
|
|
41
|
-
"@dxos/
|
|
42
|
-
"@dxos/
|
|
43
|
-
"@dxos/plugin-
|
|
44
|
-
"@dxos/plugin-
|
|
45
|
-
"@dxos/plugin-
|
|
46
|
-
"@dxos/plugin-theme": "0.8.
|
|
47
|
-
"@dxos/
|
|
48
|
-
"@dxos/
|
|
49
|
-
"@dxos/react-
|
|
50
|
-
"@dxos/react-ui-
|
|
51
|
-
"@dxos/react-ui-
|
|
52
|
-
"@dxos/react-ui-stack": "0.8.
|
|
53
|
-
"@dxos/react-ui-
|
|
54
|
-
"@dxos/
|
|
32
|
+
"effect": "^3.13.3",
|
|
33
|
+
"@dxos/app-framework": "0.8.2-main.2f9c567",
|
|
34
|
+
"@dxos/app-graph": "0.8.2-main.2f9c567",
|
|
35
|
+
"@dxos/debug": "0.8.2-main.2f9c567",
|
|
36
|
+
"@dxos/echo-schema": "0.8.2-main.2f9c567",
|
|
37
|
+
"@dxos/async": "0.8.2-main.2f9c567",
|
|
38
|
+
"@dxos/invariant": "0.8.2-main.2f9c567",
|
|
39
|
+
"@dxos/keyboard": "0.8.2-main.2f9c567",
|
|
40
|
+
"@dxos/live-object": "0.8.2-main.2f9c567",
|
|
41
|
+
"@dxos/local-storage": "0.8.2-main.2f9c567",
|
|
42
|
+
"@dxos/log": "0.8.2-main.2f9c567",
|
|
43
|
+
"@dxos/plugin-client": "0.8.2-main.2f9c567",
|
|
44
|
+
"@dxos/plugin-observability": "0.8.2-main.2f9c567",
|
|
45
|
+
"@dxos/plugin-deck": "0.8.2-main.2f9c567",
|
|
46
|
+
"@dxos/plugin-theme": "0.8.2-main.2f9c567",
|
|
47
|
+
"@dxos/protocols": "0.8.2-main.2f9c567",
|
|
48
|
+
"@dxos/plugin-graph": "0.8.2-main.2f9c567",
|
|
49
|
+
"@dxos/react-client": "0.8.2-main.2f9c567",
|
|
50
|
+
"@dxos/react-ui-list": "0.8.2-main.2f9c567",
|
|
51
|
+
"@dxos/react-ui-menu": "0.8.2-main.2f9c567",
|
|
52
|
+
"@dxos/react-ui-stack": "0.8.2-main.2f9c567",
|
|
53
|
+
"@dxos/react-ui-searchlist": "0.8.2-main.2f9c567",
|
|
54
|
+
"@dxos/react-ui-tabs": "0.8.2-main.2f9c567",
|
|
55
|
+
"@dxos/util": "0.8.2-main.2f9c567"
|
|
55
56
|
},
|
|
56
57
|
"devDependencies": {
|
|
57
58
|
"@phosphor-icons/react": "^2.1.5",
|
|
@@ -60,18 +61,19 @@
|
|
|
60
61
|
"react": "~18.2.0",
|
|
61
62
|
"react-dom": "~18.2.0",
|
|
62
63
|
"vite": "5.4.7",
|
|
63
|
-
"@dxos/echo-signals": "0.8.
|
|
64
|
-
"@dxos/
|
|
65
|
-
"@dxos/
|
|
66
|
-
"@dxos/storybook-utils": "0.8.
|
|
67
|
-
"@dxos/react-ui-theme": "0.8.
|
|
64
|
+
"@dxos/echo-signals": "0.8.2-main.2f9c567",
|
|
65
|
+
"@dxos/random": "0.8.2-main.2f9c567",
|
|
66
|
+
"@dxos/react-ui": "0.8.2-main.2f9c567",
|
|
67
|
+
"@dxos/storybook-utils": "0.8.2-main.2f9c567",
|
|
68
|
+
"@dxos/react-ui-theme": "0.8.2-main.2f9c567"
|
|
68
69
|
},
|
|
69
70
|
"peerDependencies": {
|
|
70
71
|
"@phosphor-icons/react": "^2.1.5",
|
|
72
|
+
"effect": "^3.13.3",
|
|
71
73
|
"react": "~18.2.0",
|
|
72
74
|
"react-dom": "~18.2.0",
|
|
73
|
-
"@dxos/react-ui": "0.8.
|
|
74
|
-
"@dxos/react-ui-theme": "0.8.
|
|
75
|
+
"@dxos/react-ui": "0.8.2-main.2f9c567",
|
|
76
|
+
"@dxos/react-ui-theme": "0.8.2-main.2f9c567"
|
|
75
77
|
},
|
|
76
78
|
"publishConfig": {
|
|
77
79
|
"access": "public"
|
|
@@ -3,17 +3,18 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { defineCapability } from '@dxos/app-framework';
|
|
6
|
-
import { type
|
|
6
|
+
import { type Live } from '@dxos/live-object';
|
|
7
7
|
|
|
8
8
|
import { NAVTREE_PLUGIN } from '../meta';
|
|
9
9
|
import { type NavTreeItemGraphNode } from '../types';
|
|
10
10
|
|
|
11
11
|
export namespace NavTreeCapabilities {
|
|
12
12
|
export const State = defineCapability<{
|
|
13
|
-
state: Map<string,
|
|
14
|
-
getItem: (path: string[]) =>
|
|
15
|
-
setItem: (path: string[], key: 'open' | 'current', next: boolean) => void;
|
|
13
|
+
state: Map<string, Live<{ open: boolean; current: boolean; alternateTree?: boolean }>>;
|
|
14
|
+
getItem: (path: string[]) => Live<{ open: boolean; current: boolean; alternateTree?: boolean }>;
|
|
15
|
+
setItem: (path: string[], key: 'open' | 'current' | 'alternateTree', next: boolean) => void;
|
|
16
16
|
isOpen: (path: string[], item: NavTreeItemGraphNode) => boolean;
|
|
17
17
|
isCurrent: (path: string[], item: NavTreeItemGraphNode) => boolean;
|
|
18
|
+
isAlternateTree: (path: string[], item: NavTreeItemGraphNode) => boolean;
|
|
18
19
|
}>(`${NAVTREE_PLUGIN}/capability/state`);
|
|
19
20
|
}
|
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
+
import { Schema } from 'effect';
|
|
6
|
+
|
|
5
7
|
import { contributes, type PluginsContext, Capabilities, createResolver, LayoutAction } from '@dxos/app-framework';
|
|
6
|
-
import { S } from '@dxos/echo-schema';
|
|
7
8
|
import { log } from '@dxos/log';
|
|
8
9
|
|
|
9
10
|
import { NavTreeCapabilities } from './capabilities';
|
|
@@ -13,8 +14,8 @@ export default (context: PluginsContext) =>
|
|
|
13
14
|
Capabilities.IntentResolver,
|
|
14
15
|
createResolver({
|
|
15
16
|
intent: LayoutAction.UpdateLayout,
|
|
16
|
-
filter: (data): data is
|
|
17
|
-
|
|
17
|
+
filter: (data): data is Schema.Schema.Type<typeof LayoutAction.Expose.fields.input> =>
|
|
18
|
+
Schema.is(LayoutAction.Expose.fields.input)(data),
|
|
18
19
|
resolve: async ({ subject }) => {
|
|
19
20
|
const { graph } = context.requestCapability(Capabilities.AppGraph);
|
|
20
21
|
const { getItem, setItem } = context.requestCapability(NavTreeCapabilities.State);
|
|
@@ -5,13 +5,13 @@
|
|
|
5
5
|
import { effect } from '@preact/signals-core';
|
|
6
6
|
|
|
7
7
|
import { Capabilities, contributes, type PluginsContext } from '@dxos/app-framework';
|
|
8
|
-
import { type
|
|
8
|
+
import { type Live, live } from '@dxos/live-object';
|
|
9
9
|
import { Path } from '@dxos/react-ui-list';
|
|
10
10
|
|
|
11
11
|
import { NavTreeCapabilities } from './capabilities';
|
|
12
12
|
import { NAVTREE_PLUGIN } from '../meta';
|
|
13
13
|
|
|
14
|
-
const KEY = `${NAVTREE_PLUGIN}/state`;
|
|
14
|
+
const KEY = `${NAVTREE_PLUGIN}/state/v1`;
|
|
15
15
|
|
|
16
16
|
const getInitialState = () => {
|
|
17
17
|
const stringified = localStorage.getItem(KEY);
|
|
@@ -24,11 +24,11 @@ const getInitialState = () => {
|
|
|
24
24
|
return cached.map(
|
|
25
25
|
([key, value]): [
|
|
26
26
|
string,
|
|
27
|
-
|
|
27
|
+
Live<{
|
|
28
28
|
open: boolean;
|
|
29
29
|
current: boolean;
|
|
30
30
|
}>,
|
|
31
|
-
] => [key,
|
|
31
|
+
] => [key, live({ open: value.open, current: false })],
|
|
32
32
|
);
|
|
33
33
|
} catch {}
|
|
34
34
|
};
|
|
@@ -36,22 +36,22 @@ const getInitialState = () => {
|
|
|
36
36
|
export default (context: PluginsContext) => {
|
|
37
37
|
const layout = context.requestCapability(Capabilities.Layout);
|
|
38
38
|
|
|
39
|
-
// TODO(wittjosiah): This currently needs to be not a
|
|
40
|
-
// If it is a
|
|
41
|
-
// Ideally this could be a
|
|
39
|
+
// TODO(wittjosiah): This currently needs to be not a Live at the root.
|
|
40
|
+
// If it is a Live then React errors when initializing new paths because of state change during render.
|
|
41
|
+
// Ideally this could be a Live but be able to access and update the root level without breaking render.
|
|
42
42
|
// Wrapping accesses and updates in `untracked` didn't seem to work in all cases.
|
|
43
|
-
const state = new Map<string,
|
|
43
|
+
const state = new Map<string, Live<{ open: boolean; current: boolean; alternateTree?: boolean }>>(
|
|
44
44
|
getInitialState() ?? [
|
|
45
45
|
// TODO(thure): Initialize these dynamically.
|
|
46
|
-
['root',
|
|
47
|
-
['root~dxos.org/plugin/space-spaces',
|
|
48
|
-
['root~dxos.org/plugin/files',
|
|
46
|
+
['root', live({ open: true, current: false })],
|
|
47
|
+
['root~dxos.org/plugin/space-spaces', live({ open: true, current: false })],
|
|
48
|
+
['root~dxos.org/plugin/files', live({ open: true, current: false })],
|
|
49
49
|
],
|
|
50
50
|
);
|
|
51
51
|
|
|
52
52
|
const getItem = (_path: string[]) => {
|
|
53
53
|
const path = Path.create(..._path);
|
|
54
|
-
const value = state.get(path) ??
|
|
54
|
+
const value = state.get(path) ?? live({ open: false, current: false, alternateTree: false });
|
|
55
55
|
if (!state.has(path)) {
|
|
56
56
|
state.set(path, value);
|
|
57
57
|
}
|
|
@@ -59,7 +59,7 @@ export default (context: PluginsContext) => {
|
|
|
59
59
|
return value;
|
|
60
60
|
};
|
|
61
61
|
|
|
62
|
-
const setItem = (path: string[], key: 'open' | 'current', next: boolean) => {
|
|
62
|
+
const setItem = (path: string[], key: 'open' | 'current' | 'alternateTree', next: boolean) => {
|
|
63
63
|
const value = getItem(path);
|
|
64
64
|
value[key] = next;
|
|
65
65
|
|
|
@@ -68,6 +68,10 @@ export default (context: PluginsContext) => {
|
|
|
68
68
|
|
|
69
69
|
const isOpen = (path: string[]) => getItem(path).open;
|
|
70
70
|
const isCurrent = (path: string[]) => getItem(path).current;
|
|
71
|
+
const isAlternateTree = (path: string[]) => {
|
|
72
|
+
const item = getItem(path);
|
|
73
|
+
return item.alternateTree ?? false;
|
|
74
|
+
};
|
|
71
75
|
|
|
72
76
|
let previous: string[] = [];
|
|
73
77
|
const unsubscribe = effect(() => {
|
|
@@ -93,5 +97,7 @@ export default (context: PluginsContext) => {
|
|
|
93
97
|
});
|
|
94
98
|
});
|
|
95
99
|
|
|
96
|
-
return contributes(NavTreeCapabilities.State, { state, getItem, setItem, isOpen, isCurrent }, () =>
|
|
100
|
+
return contributes(NavTreeCapabilities.State, { state, getItem, setItem, isOpen, isCurrent, isAlternateTree }, () =>
|
|
101
|
+
unsubscribe(),
|
|
102
|
+
);
|
|
97
103
|
};
|
|
@@ -2,14 +2,15 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import React, { useMemo } from 'react';
|
|
5
|
+
import React, { useCallback, useMemo } from 'react';
|
|
6
6
|
|
|
7
7
|
import { useLayout } from '@dxos/app-framework';
|
|
8
8
|
import { type Node } from '@dxos/app-graph';
|
|
9
|
-
import { Button, Icon, toLocalizedString, useTranslation } from '@dxos/react-ui';
|
|
9
|
+
import { Button, type ButtonProps, Icon, IconButton, toLocalizedString, useTranslation } from '@dxos/react-ui';
|
|
10
10
|
import { Tree } from '@dxos/react-ui-list';
|
|
11
11
|
import { Tabs } from '@dxos/react-ui-tabs';
|
|
12
12
|
import { hoverableControlItem, hoverableOpenControlItem, mx } from '@dxos/react-ui-theme';
|
|
13
|
+
import { byPosition } from '@dxos/util';
|
|
13
14
|
|
|
14
15
|
import { useNavTreeContext } from './NavTreeContext';
|
|
15
16
|
import { NavTreeItemColumns } from './NavTreeItemColumns';
|
|
@@ -17,12 +18,78 @@ import { useLoadDescendents } from '../hooks';
|
|
|
17
18
|
import { NAVTREE_PLUGIN } from '../meta';
|
|
18
19
|
import { l0ItemType } from '../util';
|
|
19
20
|
|
|
20
|
-
export type L1PanelProps = {
|
|
21
|
+
export type L1PanelProps = {
|
|
22
|
+
item: Node<any>;
|
|
23
|
+
path: string[];
|
|
24
|
+
currentItemId: string;
|
|
25
|
+
onBack?: () => void;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const headingBackButtonLabel =
|
|
29
|
+
'absolute inset-0 min-is-0 truncate flex items-center pis-6 transition-[transform,opacity] ease-in-out duration-200';
|
|
30
|
+
|
|
31
|
+
const HeadingBackButton = ({ title, onClick }: { title: string } & Pick<ButtonProps, 'onClick'>) => {
|
|
32
|
+
const { t } = useTranslation(NAVTREE_PLUGIN);
|
|
33
|
+
return (
|
|
34
|
+
<Button
|
|
35
|
+
variant='ghost'
|
|
36
|
+
classNames='pli-1 flex-1 relative group text-start justify-start bs-[--rail-action] font-normal'
|
|
37
|
+
onClick={onClick}
|
|
38
|
+
>
|
|
39
|
+
<Icon icon='ph--caret-left--bold' size={3} />
|
|
40
|
+
<span
|
|
41
|
+
className={mx(
|
|
42
|
+
headingBackButtonLabel,
|
|
43
|
+
'translate-y-2 opacity-0 group-hover:translate-y-0 group-hover:opacity-100 group-focus:translate-y-0 group-focus:opacity-100',
|
|
44
|
+
)}
|
|
45
|
+
>
|
|
46
|
+
{t('back label')}
|
|
47
|
+
</span>
|
|
48
|
+
<span
|
|
49
|
+
className={mx(
|
|
50
|
+
headingBackButtonLabel,
|
|
51
|
+
'translate-y-0 opacity-100 group-hover:-translate-y-2 group-hover:opacity-0 group-focus:-translate-y-2 group-focus:opacity-0',
|
|
52
|
+
)}
|
|
53
|
+
>
|
|
54
|
+
{title}
|
|
55
|
+
</span>
|
|
56
|
+
</Button>
|
|
57
|
+
);
|
|
58
|
+
};
|
|
21
59
|
|
|
22
60
|
const L1Panel = ({ item, path, currentItemId, onBack }: L1PanelProps) => {
|
|
23
61
|
const layout = useLayout();
|
|
24
|
-
const navTreeContext = useNavTreeContext();
|
|
62
|
+
const { isAlternateTree, setAlternateTree, ...navTreeContext } = useNavTreeContext();
|
|
25
63
|
const { t } = useTranslation(NAVTREE_PLUGIN);
|
|
64
|
+
|
|
65
|
+
// TODO(wittjosiah): Support multiple alternate trees.
|
|
66
|
+
const alternateTree = navTreeContext.getItems(item, 'alternate-tree')[0];
|
|
67
|
+
const alternatePath = useMemo(() => [...path, item.id], [item.id, path]);
|
|
68
|
+
const handleOpen = useCallback(() => setAlternateTree?.(alternatePath, true), [alternatePath, setAlternateTree]);
|
|
69
|
+
const isAlternate = isAlternateTree?.(alternatePath, item) ?? false;
|
|
70
|
+
const alternateGetItems = useCallback(
|
|
71
|
+
(node?: Node, disposition?: string) => {
|
|
72
|
+
const items = navTreeContext.getItems(node, disposition);
|
|
73
|
+
if (node === alternateTree) {
|
|
74
|
+
// TODO(wittjosiah): Sorting is expensive, limit to necessary items for now.
|
|
75
|
+
return items.toSorted((a, b) => byPosition(a.properties, b.properties));
|
|
76
|
+
}
|
|
77
|
+
return items;
|
|
78
|
+
},
|
|
79
|
+
[navTreeContext, alternateTree],
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
const handleBack = useCallback(() => {
|
|
83
|
+
if (isAlternate) {
|
|
84
|
+
setAlternateTree?.(alternatePath, false);
|
|
85
|
+
} else {
|
|
86
|
+
onBack?.();
|
|
87
|
+
}
|
|
88
|
+
}, [isAlternate, onBack, alternatePath, setAlternateTree]);
|
|
89
|
+
|
|
90
|
+
const title = toLocalizedString(item.properties.label, t);
|
|
91
|
+
const backCapable = item.id.startsWith('!') || isAlternate;
|
|
92
|
+
|
|
26
93
|
return (
|
|
27
94
|
<Tabs.Tabpanel
|
|
28
95
|
key={item.id}
|
|
@@ -32,38 +99,56 @@ const L1Panel = ({ item, path, currentItemId, onBack }: L1PanelProps) => {
|
|
|
32
99
|
item.id === currentItemId && 'grid',
|
|
33
100
|
]}
|
|
34
101
|
tabIndex={-1}
|
|
102
|
+
aria-label={title}
|
|
35
103
|
{...(!layout.sidebarOpen && { inert: 'true' })}
|
|
36
104
|
>
|
|
37
105
|
{item.id === currentItemId && (
|
|
38
106
|
<>
|
|
39
|
-
<h2 className='flex items-center border-be border-separator app-drag'>
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
107
|
+
<h2 className='flex items-center border-be border-separator app-drag pis-1'>
|
|
108
|
+
{backCapable ? (
|
|
109
|
+
<HeadingBackButton title={title} onClick={handleBack} />
|
|
110
|
+
) : (
|
|
111
|
+
<span className='flex-1 truncate min-is-0 pis-6'>{title}</span>
|
|
112
|
+
)}
|
|
113
|
+
{alternateTree && !isAlternate && (
|
|
114
|
+
<IconButton
|
|
115
|
+
variant='ghost'
|
|
116
|
+
classNames={mx('shrink-0', hoverableControlItem, hoverableOpenControlItem, 'pli-2 pointer-fine:pli-1')}
|
|
117
|
+
iconOnly
|
|
118
|
+
size={5}
|
|
119
|
+
density='coarse'
|
|
120
|
+
icon={alternateTree.properties.icon ?? 'ph--placeholder--regular'}
|
|
121
|
+
label={toLocalizedString(alternateTree.properties.label ?? alternateTree.id, t)}
|
|
122
|
+
data-testid='treeView.alternateTreeButton'
|
|
123
|
+
onClick={handleOpen}
|
|
124
|
+
/>
|
|
125
|
+
)}
|
|
126
|
+
<NavTreeItemColumns path={path} item={item} open density='coarse' />
|
|
55
127
|
</h2>
|
|
56
128
|
<div role='none' className='overflow-y-auto'>
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
129
|
+
{isAlternate ? (
|
|
130
|
+
<Tree
|
|
131
|
+
{...navTreeContext}
|
|
132
|
+
getItems={alternateGetItems}
|
|
133
|
+
id={alternateTree.id}
|
|
134
|
+
root={alternateTree}
|
|
135
|
+
path={alternatePath}
|
|
136
|
+
levelOffset={5}
|
|
137
|
+
gridTemplateColumns='[tree-row-start] 1fr min-content min-content min-content [tree-row-end]'
|
|
138
|
+
renderColumns={NavTreeItemColumns}
|
|
139
|
+
/>
|
|
140
|
+
) : (
|
|
141
|
+
<Tree
|
|
142
|
+
{...navTreeContext}
|
|
143
|
+
id={item.id}
|
|
144
|
+
root={item}
|
|
145
|
+
path={path}
|
|
146
|
+
levelOffset={5}
|
|
147
|
+
draggable
|
|
148
|
+
gridTemplateColumns='[tree-row-start] 1fr min-content min-content min-content [tree-row-end]'
|
|
149
|
+
renderColumns={NavTreeItemColumns}
|
|
150
|
+
/>
|
|
151
|
+
)}
|
|
67
152
|
</div>
|
|
68
153
|
</>
|
|
69
154
|
)}
|
|
@@ -10,7 +10,7 @@ import { type Meta } from '@storybook/react';
|
|
|
10
10
|
import React, { useEffect } from 'react';
|
|
11
11
|
|
|
12
12
|
import { isActionLike, type NodeArg } from '@dxos/app-graph';
|
|
13
|
-
import {
|
|
13
|
+
import { live, type Live } from '@dxos/live-object';
|
|
14
14
|
import { faker } from '@dxos/random';
|
|
15
15
|
import { isTreeData, type TreeData } from '@dxos/react-ui-list';
|
|
16
16
|
import { Path } from '@dxos/react-ui-list';
|
|
@@ -21,8 +21,8 @@ import { createTree, updateState } from '../testing';
|
|
|
21
21
|
|
|
22
22
|
faker.seed(1234);
|
|
23
23
|
|
|
24
|
-
const tree =
|
|
25
|
-
const state = new Map<string,
|
|
24
|
+
const tree = live<NodeArg<any>>(createTree());
|
|
25
|
+
const state = new Map<string, Live<{ open: boolean; current: boolean }>>();
|
|
26
26
|
|
|
27
27
|
const meta: Meta<typeof StorybookNavTree> = {
|
|
28
28
|
title: 'plugins/plugin-navtree/NavTree',
|
|
@@ -73,7 +73,7 @@ const meta: Meta<typeof StorybookNavTree> = {
|
|
|
73
73
|
}),
|
|
74
74
|
isOpen: (_path: string[]) => {
|
|
75
75
|
const path = Path.create(..._path);
|
|
76
|
-
const value = state.get(path) ??
|
|
76
|
+
const value = state.get(path) ?? live({ open: false, current: false });
|
|
77
77
|
if (!state.has(path)) {
|
|
78
78
|
state.set(path, value);
|
|
79
79
|
}
|
|
@@ -82,7 +82,7 @@ const meta: Meta<typeof StorybookNavTree> = {
|
|
|
82
82
|
},
|
|
83
83
|
isCurrent: (_path: string[]) => {
|
|
84
84
|
const path = Path.create(..._path);
|
|
85
|
-
const value = state.get(path) ??
|
|
85
|
+
const value = state.get(path) ?? live({ open: false, current: false });
|
|
86
86
|
if (!state.has(path)) {
|
|
87
87
|
state.set(path, value);
|
|
88
88
|
}
|
|
@@ -59,7 +59,7 @@ export const NavTreeContainer = memo(({ tab, popoverAnchorId, topbar }: NavTreeC
|
|
|
59
59
|
const { dispatchPromise: dispatch } = useIntentDispatcher();
|
|
60
60
|
const { graph } = useAppGraph();
|
|
61
61
|
const layout = useLayout();
|
|
62
|
-
const { isOpen, isCurrent, setItem } = useCapability(NavTreeCapabilities.State);
|
|
62
|
+
const { isOpen, isCurrent, isAlternateTree, setItem } = useCapability(NavTreeCapabilities.State);
|
|
63
63
|
|
|
64
64
|
const getActions = useCallback((node: Node) => naturalGetActions(graph, node), [graph]);
|
|
65
65
|
|
|
@@ -68,7 +68,10 @@ export const NavTreeContainer = memo(({ tab, popoverAnchorId, topbar }: NavTreeC
|
|
|
68
68
|
return graph.nodes(node ?? graph.root, {
|
|
69
69
|
filter: (node): node is Node => {
|
|
70
70
|
return untracked(() => {
|
|
71
|
-
if (
|
|
71
|
+
if (
|
|
72
|
+
!disposition &&
|
|
73
|
+
(node.properties.disposition === 'hidden' || node.properties.disposition === 'alternate-tree')
|
|
74
|
+
) {
|
|
72
75
|
return false;
|
|
73
76
|
} else if (!disposition) {
|
|
74
77
|
const action = isAction(node);
|
|
@@ -262,6 +265,13 @@ export const NavTreeContainer = memo(({ tab, popoverAnchorId, topbar }: NavTreeC
|
|
|
262
265
|
});
|
|
263
266
|
}, [graph]);
|
|
264
267
|
|
|
268
|
+
const setAlternateTree = useCallback(
|
|
269
|
+
(path: string[], open: boolean) => {
|
|
270
|
+
setItem(path, 'alternateTree', open);
|
|
271
|
+
},
|
|
272
|
+
[setItem],
|
|
273
|
+
);
|
|
274
|
+
|
|
265
275
|
const navTreeContextValue = useMemo(
|
|
266
276
|
() => ({
|
|
267
277
|
tab,
|
|
@@ -279,6 +289,8 @@ export const NavTreeContainer = memo(({ tab, popoverAnchorId, topbar }: NavTreeC
|
|
|
279
289
|
onOpenChange,
|
|
280
290
|
canDrop,
|
|
281
291
|
onSelect: handleSelect,
|
|
292
|
+
isAlternateTree,
|
|
293
|
+
setAlternateTree,
|
|
282
294
|
}),
|
|
283
295
|
[
|
|
284
296
|
tab,
|
|
@@ -296,6 +308,8 @@ export const NavTreeContainer = memo(({ tab, popoverAnchorId, topbar }: NavTreeC
|
|
|
296
308
|
onOpenChange,
|
|
297
309
|
canDrop,
|
|
298
310
|
handleSelect,
|
|
311
|
+
isAlternateTree,
|
|
312
|
+
setAlternateTree,
|
|
299
313
|
],
|
|
300
314
|
);
|
|
301
315
|
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import React, { type MutableRefObject, useRef } from 'react';
|
|
6
6
|
|
|
7
7
|
import { type Action, type Node } from '@dxos/app-graph';
|
|
8
|
-
import { useTranslation, toLocalizedString, IconButton } from '@dxos/react-ui';
|
|
8
|
+
import { useTranslation, toLocalizedString, IconButton, useDensityContext } from '@dxos/react-ui';
|
|
9
9
|
import { DropdownMenu, MenuProvider, type MenuItem } from '@dxos/react-ui-menu';
|
|
10
10
|
import { hoverableControlItem, hoverableOpenControlItem, mx } from '@dxos/react-ui-theme';
|
|
11
11
|
|
|
@@ -23,10 +23,14 @@ export type NavTreeItemActionMenuProps = ActionProperties & {
|
|
|
23
23
|
|
|
24
24
|
const fallbackIcon = 'ph--placeholder--regular';
|
|
25
25
|
|
|
26
|
-
const
|
|
26
|
+
const fineActionButtonProps = {
|
|
27
27
|
size: 4 as const,
|
|
28
28
|
density: 'fine' as const,
|
|
29
29
|
};
|
|
30
|
+
const coarseActionButtonProps = {
|
|
31
|
+
size: 5 as const,
|
|
32
|
+
density: 'coarse' as const,
|
|
33
|
+
};
|
|
30
34
|
|
|
31
35
|
export const NavTreeItemActionDropdownMenu = ({
|
|
32
36
|
label,
|
|
@@ -37,12 +41,13 @@ export const NavTreeItemActionDropdownMenu = ({
|
|
|
37
41
|
}: NavTreeItemActionMenuProps) => {
|
|
38
42
|
const { t } = useTranslation(NAVTREE_PLUGIN);
|
|
39
43
|
const suppressNextTooltip = useRef<boolean>(false);
|
|
44
|
+
const density = useDensityContext();
|
|
40
45
|
return (
|
|
41
46
|
<MenuProvider onAction={onAction}>
|
|
42
47
|
<DropdownMenu.Root items={menuActions as MenuItem[]} suppressNextTooltip={suppressNextTooltip}>
|
|
43
48
|
<DropdownMenu.Trigger asChild>
|
|
44
49
|
<IconButton
|
|
45
|
-
{...
|
|
50
|
+
{...(density === 'coarse' ? coarseActionButtonProps : fineActionButtonProps)}
|
|
46
51
|
classNames={mx('shrink-0 pli-2 pointer-fine:pli-1', hoverableControlItem, hoverableOpenControlItem)}
|
|
47
52
|
variant='ghost'
|
|
48
53
|
icon={icon ?? fallbackIcon}
|
|
@@ -63,9 +68,10 @@ export const NavTreeItemMonolithicAction = ({
|
|
|
63
68
|
data: invoke,
|
|
64
69
|
baseLabel,
|
|
65
70
|
}: Action & { parent: Node; onAction?: (action: Action) => void; baseLabel: string }) => {
|
|
71
|
+
const density = useDensityContext();
|
|
66
72
|
return (
|
|
67
73
|
<IconButton
|
|
68
|
-
{...
|
|
74
|
+
{...(density === 'coarse' ? coarseActionButtonProps : fineActionButtonProps)}
|
|
69
75
|
variant={variant}
|
|
70
76
|
classNames={mx(
|
|
71
77
|
'shrink-0',
|