@dxos/plugin-simple-layout 0.8.4-main.c85a9c8dae → 0.8.4-main.d05673bc65
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-TMZNLVT2.mjs → chunk-MDPEKLKR.mjs} +55 -62
- package/dist/lib/browser/chunk-MDPEKLKR.mjs.map +7 -0
- package/dist/lib/browser/{chunk-7VLT3S46.mjs → chunk-MRR7PXSM.mjs} +3 -3
- package/dist/lib/browser/{chunk-7VLT3S46.mjs.map → chunk-MRR7PXSM.mjs.map} +1 -1
- package/dist/lib/browser/index.mjs +6 -6
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/{operation-resolver-BYRIQOQT.mjs → operation-resolver-VTZ6HZ4B.mjs} +24 -35
- package/dist/lib/browser/operation-resolver-VTZ6HZ4B.mjs.map +7 -0
- package/dist/lib/browser/{react-root-MMB575WY.mjs → react-root-WVQYY2JA.mjs} +3 -3
- package/dist/lib/browser/{react-surface-M6CURANW.mjs → react-surface-VLBR37ED.mjs} +11 -8
- package/dist/lib/browser/{react-surface-M6CURANW.mjs.map → react-surface-VLBR37ED.mjs.map} +3 -3
- package/dist/lib/browser/{state-A3PGDWWZ.mjs → state-TXSMUWYI.mjs} +2 -2
- package/dist/lib/browser/{url-handler-HTIUY6WL.mjs → url-handler-RBRONH7S.mjs} +18 -19
- package/dist/lib/browser/url-handler-RBRONH7S.mjs.map +7 -0
- package/dist/lib/node-esm/{chunk-FLOYBAHE.mjs → chunk-DCKASLMP.mjs} +55 -62
- package/dist/lib/node-esm/chunk-DCKASLMP.mjs.map +7 -0
- package/dist/lib/node-esm/{chunk-VIDE5UMB.mjs → chunk-WMNTJ2MK.mjs} +3 -3
- package/dist/lib/node-esm/{chunk-VIDE5UMB.mjs.map → chunk-WMNTJ2MK.mjs.map} +1 -1
- package/dist/lib/node-esm/index.mjs +6 -6
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/lib/node-esm/{operation-resolver-BDTFNCS2.mjs → operation-resolver-R7CQ6ERU.mjs} +24 -35
- package/dist/lib/node-esm/operation-resolver-R7CQ6ERU.mjs.map +7 -0
- package/dist/lib/node-esm/{react-root-ENZKVSY4.mjs → react-root-XBNDM7BE.mjs} +3 -3
- package/dist/lib/node-esm/{react-surface-ITVNQYLG.mjs → react-surface-U5NHA367.mjs} +11 -8
- package/dist/lib/node-esm/{react-surface-ITVNQYLG.mjs.map → react-surface-U5NHA367.mjs.map} +3 -3
- package/dist/lib/node-esm/{state-ZCFZTTPL.mjs → state-JMX6FAG4.mjs} +2 -2
- package/dist/lib/node-esm/{url-handler-WBVVKVPC.mjs → url-handler-QSMCH3JB.mjs} +18 -19
- package/dist/lib/node-esm/url-handler-QSMCH3JB.mjs.map +7 -0
- package/dist/types/src/capabilities/operation-resolver/operation-resolver.d.ts.map +1 -1
- package/dist/types/src/capabilities/react-surface/react-surface.d.ts.map +1 -1
- package/dist/types/src/capabilities/url-handler/url-handler.d.ts +2 -2
- package/dist/types/src/capabilities/url-handler/url-handler.d.ts.map +1 -1
- package/dist/types/src/components/ContentLoading/ContentLoading.d.ts.map +1 -0
- package/dist/types/src/components/ContentLoading/ContentLoading.stories.d.ts.map +1 -0
- package/dist/types/src/components/ContentLoading/index.d.ts +2 -0
- package/dist/types/src/components/ContentLoading/index.d.ts.map +1 -0
- package/dist/types/src/components/NavBranch/NavBranch.d.ts +11 -0
- package/dist/types/src/components/NavBranch/NavBranch.d.ts.map +1 -0
- package/dist/types/src/components/NavBranch/index.d.ts +2 -0
- package/dist/types/src/components/NavBranch/index.d.ts.map +1 -0
- package/dist/types/src/components/SimpleLayout/Main.d.ts.map +1 -1
- package/dist/types/src/components/hooks.d.ts +4 -2
- package/dist/types/src/components/hooks.d.ts.map +1 -1
- package/dist/types/src/components/index.d.ts +1 -1
- package/dist/types/src/hooks/actions.d.ts +3 -4
- package/dist/types/src/hooks/actions.d.ts.map +1 -1
- package/dist/types/src/hooks/useAppBarProps.d.ts.map +1 -1
- package/dist/types/src/hooks/useDrawerActions.d.ts.map +1 -1
- package/dist/types/src/hooks/useNavbarActions.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +27 -27
- package/src/capabilities/operation-resolver/operation-resolver.ts +19 -34
- package/src/capabilities/react-surface/react-surface.tsx +8 -6
- package/src/capabilities/url-handler/url-handler.ts +11 -35
- package/src/components/ContentLoading/index.ts +5 -0
- package/src/components/Home/Home.tsx +3 -3
- package/src/components/{Workspace/Workspace.tsx → NavBranch/NavBranch.tsx} +18 -13
- package/src/components/{Workspace → NavBranch}/index.ts +1 -1
- package/src/components/SimpleLayout/Drawer.tsx +3 -12
- package/src/components/SimpleLayout/Main.tsx +3 -4
- package/src/components/hooks.ts +8 -8
- package/src/components/index.ts +1 -1
- package/src/hooks/actions.ts +13 -15
- package/src/hooks/useAppBarProps.ts +1 -2
- package/src/hooks/useDrawerActions.ts +7 -5
- package/src/hooks/useNavbarActions.ts +5 -4
- package/src/meta.ts +1 -1
- package/src/types/capabilities.ts +1 -1
- package/dist/lib/browser/chunk-TMZNLVT2.mjs.map +0 -7
- package/dist/lib/browser/operation-resolver-BYRIQOQT.mjs.map +0 -7
- package/dist/lib/browser/url-handler-HTIUY6WL.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-FLOYBAHE.mjs.map +0 -7
- package/dist/lib/node-esm/operation-resolver-BDTFNCS2.mjs.map +0 -7
- package/dist/lib/node-esm/url-handler-WBVVKVPC.mjs.map +0 -7
- package/dist/types/src/components/ContentLoading.d.ts.map +0 -1
- package/dist/types/src/components/ContentLoading.stories.d.ts.map +0 -1
- package/dist/types/src/components/Workspace/Workspace.d.ts +0 -11
- package/dist/types/src/components/Workspace/Workspace.d.ts.map +0 -1
- package/dist/types/src/components/Workspace/index.d.ts +0 -2
- package/dist/types/src/components/Workspace/index.d.ts.map +0 -1
- /package/dist/lib/browser/{react-root-MMB575WY.mjs.map → react-root-WVQYY2JA.mjs.map} +0 -0
- /package/dist/lib/browser/{state-A3PGDWWZ.mjs.map → state-TXSMUWYI.mjs.map} +0 -0
- /package/dist/lib/node-esm/{react-root-ENZKVSY4.mjs.map → react-root-XBNDM7BE.mjs.map} +0 -0
- /package/dist/lib/node-esm/{state-ZCFZTTPL.mjs.map → state-JMX6FAG4.mjs.map} +0 -0
- /package/dist/types/src/components/{ContentLoading.d.ts → ContentLoading/ContentLoading.d.ts} +0 -0
- /package/dist/types/src/components/{ContentLoading.stories.d.ts → ContentLoading/ContentLoading.stories.d.ts} +0 -0
- /package/src/components/{ContentLoading.stories.tsx → ContentLoading/ContentLoading.stories.tsx} +0 -0
- /package/src/components/{ContentLoading.tsx → ContentLoading/ContentLoading.tsx} +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dxos/plugin-simple-layout",
|
|
3
|
-
"version": "0.8.4-main.
|
|
3
|
+
"version": "0.8.4-main.d05673bc65",
|
|
4
4
|
"description": "Simple layout plugin for minimal UI contexts like popover windows.",
|
|
5
5
|
"homepage": "https://dxos.org",
|
|
6
6
|
"bugs": "https://github.com/dxos/dxos/issues",
|
|
@@ -31,20 +31,20 @@
|
|
|
31
31
|
"@radix-ui/react-context": "1.1.1",
|
|
32
32
|
"@tauri-apps/plugin-deep-link": "^2.2.0",
|
|
33
33
|
"@tauri-apps/plugin-haptics": "^2.3.2",
|
|
34
|
-
"@dxos/
|
|
35
|
-
"@dxos/echo": "0.8.4-main.
|
|
36
|
-
"@dxos/
|
|
37
|
-
"@dxos/
|
|
38
|
-
"@dxos/
|
|
39
|
-
"@dxos/
|
|
40
|
-
"@dxos/
|
|
41
|
-
"@dxos/
|
|
42
|
-
"@dxos/react-ui-
|
|
43
|
-
"@dxos/react-ui-mosaic": "0.8.4-main.
|
|
44
|
-
"@dxos/react-ui-
|
|
45
|
-
"@dxos/
|
|
46
|
-
"@dxos/
|
|
47
|
-
"@dxos/util": "0.8.4-main.
|
|
34
|
+
"@dxos/app-toolkit": "0.8.4-main.d05673bc65",
|
|
35
|
+
"@dxos/echo": "0.8.4-main.d05673bc65",
|
|
36
|
+
"@dxos/async": "0.8.4-main.d05673bc65",
|
|
37
|
+
"@dxos/app-framework": "0.8.4-main.d05673bc65",
|
|
38
|
+
"@dxos/log": "0.8.4-main.d05673bc65",
|
|
39
|
+
"@dxos/react-ui-attention": "0.8.4-main.d05673bc65",
|
|
40
|
+
"@dxos/operation": "0.8.4-main.d05673bc65",
|
|
41
|
+
"@dxos/plugin-graph": "0.8.4-main.d05673bc65",
|
|
42
|
+
"@dxos/react-ui-menu": "0.8.4-main.d05673bc65",
|
|
43
|
+
"@dxos/react-ui-mosaic": "0.8.4-main.d05673bc65",
|
|
44
|
+
"@dxos/react-ui-searchlist": "0.8.4-main.d05673bc65",
|
|
45
|
+
"@dxos/react-ui-stack": "0.8.4-main.d05673bc65",
|
|
46
|
+
"@dxos/schema": "0.8.4-main.d05673bc65",
|
|
47
|
+
"@dxos/util": "0.8.4-main.d05673bc65"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
50
|
"@types/react": "~19.2.7",
|
|
@@ -53,23 +53,23 @@
|
|
|
53
53
|
"react": "~19.2.3",
|
|
54
54
|
"react-dom": "~19.2.3",
|
|
55
55
|
"vite": "^7.1.11",
|
|
56
|
-
"@dxos/
|
|
57
|
-
"@dxos/plugin-
|
|
58
|
-
"@dxos/
|
|
59
|
-
"@dxos/plugin-
|
|
60
|
-
"@dxos/plugin-
|
|
61
|
-
"@dxos/
|
|
62
|
-
"@dxos/
|
|
63
|
-
"@dxos/
|
|
64
|
-
"@dxos/
|
|
65
|
-
"@dxos/
|
|
56
|
+
"@dxos/app-graph": "0.8.4-main.d05673bc65",
|
|
57
|
+
"@dxos/plugin-preview": "0.8.4-main.d05673bc65",
|
|
58
|
+
"@dxos/plugin-client": "0.8.4-main.d05673bc65",
|
|
59
|
+
"@dxos/plugin-search": "0.8.4-main.d05673bc65",
|
|
60
|
+
"@dxos/plugin-space": "0.8.4-main.d05673bc65",
|
|
61
|
+
"@dxos/plugin-testing": "0.8.4-main.d05673bc65",
|
|
62
|
+
"@dxos/schema": "0.8.4-main.d05673bc65",
|
|
63
|
+
"@dxos/react-ui": "0.8.4-main.d05673bc65",
|
|
64
|
+
"@dxos/ui-theme": "0.8.4-main.d05673bc65",
|
|
65
|
+
"@dxos/storybook-utils": "0.8.4-main.d05673bc65"
|
|
66
66
|
},
|
|
67
67
|
"peerDependencies": {
|
|
68
68
|
"effect": "3.19.16",
|
|
69
69
|
"react": "~19.2.3",
|
|
70
70
|
"react-dom": "~19.2.3",
|
|
71
|
-
"@dxos/ui-theme": "0.8.4-main.
|
|
72
|
-
"@dxos/react-ui": "0.8.4-main.
|
|
71
|
+
"@dxos/ui-theme": "0.8.4-main.d05673bc65",
|
|
72
|
+
"@dxos/react-ui": "0.8.4-main.d05673bc65"
|
|
73
73
|
},
|
|
74
74
|
"publishConfig": {
|
|
75
75
|
"access": "public"
|
|
@@ -5,21 +5,14 @@
|
|
|
5
5
|
import * as Effect from 'effect/Effect';
|
|
6
6
|
|
|
7
7
|
import { Capabilities, Capability } from '@dxos/app-framework';
|
|
8
|
-
import { LayoutOperation } from '@dxos/app-toolkit';
|
|
8
|
+
import { getCompanionVariant, LayoutOperation, isPinnedWorkspace } from '@dxos/app-toolkit';
|
|
9
9
|
import { Operation, OperationResolver } from '@dxos/operation';
|
|
10
|
-
import { ATTENDABLE_PATH_SEPARATOR } from '@dxos/react-ui-attention';
|
|
11
10
|
|
|
12
11
|
import { type SimpleLayoutState, SimpleLayoutState as SimpleLayoutStateCapability } from '../../types';
|
|
13
12
|
|
|
14
13
|
/** Maximum number of items to keep in navigation history. */
|
|
15
14
|
const MAX_HISTORY_LENGTH = 50;
|
|
16
15
|
|
|
17
|
-
/** Parse entry ID to extract primary ID and variant. */
|
|
18
|
-
const parseEntryId = (entryId: string) => {
|
|
19
|
-
const [id, variant] = entryId.split(ATTENDABLE_PATH_SEPARATOR);
|
|
20
|
-
return { id, variant };
|
|
21
|
-
};
|
|
22
|
-
|
|
23
16
|
export default Capability.makeModule(
|
|
24
17
|
Effect.fnUntraced(function* () {
|
|
25
18
|
const registry = yield* Capability.get(Capabilities.AtomRegistry);
|
|
@@ -51,6 +44,7 @@ export default Capability.makeModule(
|
|
|
51
44
|
//
|
|
52
45
|
// UpdateComplementary - Controls companion drawer.
|
|
53
46
|
//
|
|
47
|
+
// TODO(wittjosiah): Not sure if we should be using this for the drawer.
|
|
54
48
|
OperationResolver.make({
|
|
55
49
|
operation: LayoutOperation.UpdateComplementary,
|
|
56
50
|
handler: Effect.fnUntraced(function* (input) {
|
|
@@ -59,6 +53,13 @@ export default Capability.makeModule(
|
|
|
59
53
|
...state,
|
|
60
54
|
drawerState: 'closed',
|
|
61
55
|
}));
|
|
56
|
+
} else if (input.subject) {
|
|
57
|
+
const variant = getCompanionVariant(input.subject);
|
|
58
|
+
updateState((state) => ({
|
|
59
|
+
...state,
|
|
60
|
+
companionVariant: variant,
|
|
61
|
+
drawerState: input.state === 'expanded' ? 'expanded' : 'open',
|
|
62
|
+
}));
|
|
62
63
|
}
|
|
63
64
|
}),
|
|
64
65
|
}),
|
|
@@ -116,7 +117,7 @@ export default Capability.makeModule(
|
|
|
116
117
|
...state,
|
|
117
118
|
// TODO(wittjosiah): This is a hack to prevent the previous deck from being set for pinned items.
|
|
118
119
|
// Ideally this should be worked into the data model in a generic way.
|
|
119
|
-
previousWorkspace: !state.workspace
|
|
120
|
+
previousWorkspace: !isPinnedWorkspace(state.workspace) ? state.workspace : state.previousWorkspace,
|
|
120
121
|
workspace: input.subject,
|
|
121
122
|
active: undefined,
|
|
122
123
|
// Clear history when switching workspaces.
|
|
@@ -145,32 +146,16 @@ export default Capability.makeModule(
|
|
|
145
146
|
operation: LayoutOperation.Open,
|
|
146
147
|
handler: Effect.fnUntraced(function* (input) {
|
|
147
148
|
const id = input.subject[0];
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
// TODO(wittjosiah): Factor out the change-companion operation from deck to a common layout operation.
|
|
154
|
-
const isCompanionOfCurrent = variant && (primaryId === state.workspace || primaryId === state.active);
|
|
155
|
-
if (isCompanionOfCurrent) {
|
|
156
|
-
updateState((state) => ({
|
|
149
|
+
updateState((state) => {
|
|
150
|
+
const newHistory = state.active ? [...state.history, state.active] : state.history;
|
|
151
|
+
const trimmedHistory =
|
|
152
|
+
newHistory.length > MAX_HISTORY_LENGTH ? newHistory.slice(-MAX_HISTORY_LENGTH) : newHistory;
|
|
153
|
+
return {
|
|
157
154
|
...state,
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
// Regular navigation - update active and history (use full id for alternate-tree nodes).
|
|
163
|
-
updateState((state) => {
|
|
164
|
-
const newHistory = state.active ? [...state.history, state.active] : state.history;
|
|
165
|
-
const trimmedHistory =
|
|
166
|
-
newHistory.length > MAX_HISTORY_LENGTH ? newHistory.slice(-MAX_HISTORY_LENGTH) : newHistory;
|
|
167
|
-
return {
|
|
168
|
-
...state,
|
|
169
|
-
active: id,
|
|
170
|
-
history: trimmedHistory,
|
|
171
|
-
};
|
|
172
|
-
});
|
|
173
|
-
}
|
|
155
|
+
active: id,
|
|
156
|
+
history: trimmedHistory,
|
|
157
|
+
};
|
|
158
|
+
});
|
|
174
159
|
}),
|
|
175
160
|
}),
|
|
176
161
|
|
|
@@ -9,7 +9,7 @@ import { Capabilities, Capability } from '@dxos/app-framework';
|
|
|
9
9
|
import { Surface } from '@dxos/app-framework/ui';
|
|
10
10
|
import { Node } from '@dxos/plugin-graph';
|
|
11
11
|
|
|
12
|
-
import { Home,
|
|
12
|
+
import { Home, NavBranch } from '../../components';
|
|
13
13
|
import { meta } from '../../meta';
|
|
14
14
|
|
|
15
15
|
type SurfaceData = {
|
|
@@ -23,18 +23,20 @@ export default Capability.makeModule(() =>
|
|
|
23
23
|
Effect.succeed(
|
|
24
24
|
Capability.contributes(Capabilities.ReactSurface, [
|
|
25
25
|
Surface.create({
|
|
26
|
-
id: `${meta.id}
|
|
26
|
+
id: `${meta.id}.home`,
|
|
27
27
|
role: 'article',
|
|
28
28
|
filter: (data): data is SurfaceData => data.attendableId === Node.RootId,
|
|
29
29
|
component: () => <Home />,
|
|
30
30
|
}),
|
|
31
31
|
Surface.create({
|
|
32
|
-
id: `${meta.id}
|
|
32
|
+
id: `${meta.id}.nav-branch`,
|
|
33
33
|
role: 'article',
|
|
34
34
|
position: 'fallback',
|
|
35
|
-
filter: (data): data is SurfaceData =>
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
filter: (data): data is SurfaceData => {
|
|
36
|
+
const props = data.properties as Record<string, any>;
|
|
37
|
+
return ALLOWED_DISPOSITIONS.includes(props?.disposition) || props?.role === 'branch';
|
|
38
|
+
},
|
|
39
|
+
component: ({ data }) => <NavBranch id={data.attendableId} />,
|
|
38
40
|
}),
|
|
39
41
|
]),
|
|
40
42
|
),
|
|
@@ -5,17 +5,16 @@
|
|
|
5
5
|
import * as Effect from 'effect/Effect';
|
|
6
6
|
|
|
7
7
|
import { Capabilities, Capability } from '@dxos/app-framework';
|
|
8
|
-
import { LayoutOperation } from '@dxos/app-toolkit';
|
|
8
|
+
import { LayoutOperation, fromUrlPath, getWorkspaceFromPath, toUrlPath } from '@dxos/app-toolkit';
|
|
9
9
|
import { log } from '@dxos/log';
|
|
10
|
-
import { Node } from '@dxos/plugin-graph';
|
|
11
10
|
import { isTauri } from '@dxos/util';
|
|
12
11
|
|
|
13
12
|
import { type SimpleLayoutState, SimpleLayoutState as SimpleLayoutStateCapability } from '../../types';
|
|
14
13
|
|
|
15
14
|
/**
|
|
16
15
|
* URL handler for simple layout that syncs browser URL with layout state.
|
|
17
|
-
* URL
|
|
18
|
-
* Root is represented as
|
|
16
|
+
* URL paths map directly to qualified graph IDs with the leading `root` segment stripped.
|
|
17
|
+
* Root is represented as `/`.
|
|
19
18
|
*
|
|
20
19
|
* On mobile Tauri, also listens for deep links via the deep-link plugin.
|
|
21
20
|
*/
|
|
@@ -25,38 +24,33 @@ export default Capability.makeModule(
|
|
|
25
24
|
|
|
26
25
|
/**
|
|
27
26
|
* Handle navigation from a pathname.
|
|
28
|
-
*
|
|
27
|
+
* Restores the qualified graph ID and dispatches layout operations.
|
|
29
28
|
*/
|
|
30
29
|
const handlePathNavigation = (pathname: string) => {
|
|
31
30
|
log.info('[UrlHandler] Navigating to path', { pathname });
|
|
32
31
|
|
|
33
|
-
|
|
34
|
-
const
|
|
32
|
+
const qualifiedId = fromUrlPath(pathname);
|
|
33
|
+
const workspace = getWorkspaceFromPath(qualifiedId);
|
|
35
34
|
|
|
36
|
-
|
|
37
|
-
const targetWorkspace = !nextWorkspace || nextWorkspace === 'root' ? Node.RootId : nextWorkspace;
|
|
35
|
+
invokeSync(LayoutOperation.SwitchWorkspace, { subject: workspace });
|
|
38
36
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
invokeSync(LayoutOperation.Open, { subject: [nextActive] });
|
|
37
|
+
const activeId = qualifiedId !== workspace ? qualifiedId : undefined;
|
|
38
|
+
if (activeId) {
|
|
39
|
+
invokeSync(LayoutOperation.Open, { subject: [activeId] });
|
|
43
40
|
}
|
|
44
41
|
};
|
|
45
42
|
|
|
46
43
|
const onNavigation = handleNavigation(handlePathNavigation);
|
|
47
44
|
|
|
48
|
-
// Handle initial URL and listen for browser navigation.
|
|
49
45
|
yield* Effect.sync(() => onNavigation());
|
|
50
46
|
window.addEventListener('popstate', onNavigation);
|
|
51
47
|
|
|
52
|
-
// Set up deep link listener for mobile Tauri.
|
|
53
48
|
let unlistenDeepLink: (() => void) | undefined;
|
|
54
49
|
if (isTauri()) {
|
|
55
50
|
yield* Effect.tryPromise({
|
|
56
51
|
try: async () => {
|
|
57
52
|
const { getCurrent, onOpenUrl } = await import('@tauri-apps/plugin-deep-link');
|
|
58
53
|
|
|
59
|
-
// Check if app was launched via deep link (cold start).
|
|
60
54
|
const launchUrls = await getCurrent();
|
|
61
55
|
if (launchUrls && launchUrls.length > 0) {
|
|
62
56
|
log.info('[UrlHandler] App launched with deep links', { urls: launchUrls });
|
|
@@ -65,7 +59,6 @@ export default Capability.makeModule(
|
|
|
65
59
|
}
|
|
66
60
|
}
|
|
67
61
|
|
|
68
|
-
// Listen for deep links while app is running.
|
|
69
62
|
unlistenDeepLink = await onOpenUrl((urls) => {
|
|
70
63
|
log.info('[UrlHandler] Deep links received', { urls });
|
|
71
64
|
for (const url of urls) {
|
|
@@ -82,18 +75,16 @@ export default Capability.makeModule(
|
|
|
82
75
|
}).pipe(Effect.catchAll(() => Effect.void));
|
|
83
76
|
}
|
|
84
77
|
|
|
85
|
-
// Subscribe to state changes to update the URL.
|
|
86
78
|
let lastWorkspace: string | undefined;
|
|
87
79
|
let lastActive: string | undefined;
|
|
88
80
|
const unsubscribe = yield* Capabilities.subscribeAtom(SimpleLayoutStateCapability, (state: SimpleLayoutState) => {
|
|
89
81
|
const { workspace, active } = state;
|
|
90
82
|
|
|
91
|
-
// Only update URL if relevant state changed.
|
|
92
83
|
if (workspace !== lastWorkspace || active !== lastActive) {
|
|
93
84
|
lastWorkspace = workspace;
|
|
94
85
|
lastActive = active;
|
|
95
86
|
|
|
96
|
-
const path =
|
|
87
|
+
const path = active ? toUrlPath(active) : toUrlPath(workspace);
|
|
97
88
|
if (window.location.pathname !== path) {
|
|
98
89
|
history.pushState(null, '', `${path}${window.location.search}`);
|
|
99
90
|
}
|
|
@@ -110,27 +101,12 @@ export default Capability.makeModule(
|
|
|
110
101
|
}),
|
|
111
102
|
);
|
|
112
103
|
|
|
113
|
-
// TODO(wittjosiah): Instead of hardcoding redirect paths, we should either:
|
|
114
|
-
// 1. Validate that the workspace exists in the graph before navigating.
|
|
115
|
-
// 2. Implement more structured routing with explicit route definitions.
|
|
116
104
|
/**
|
|
117
105
|
* Check if a path is a special redirect path that shouldn't be navigated to.
|
|
118
106
|
* These paths are handled by other systems (e.g., OAuth).
|
|
119
107
|
*/
|
|
120
108
|
const isRedirectPath = (pathname: string): boolean => pathname.startsWith('/redirect/');
|
|
121
109
|
|
|
122
|
-
/**
|
|
123
|
-
* Build pathname from layout state. Root workspace is / or /root/{active}.
|
|
124
|
-
*/
|
|
125
|
-
const pathFromState = (workspace: string, active: string | undefined): string =>
|
|
126
|
-
workspace === Node.RootId
|
|
127
|
-
? active
|
|
128
|
-
? `/${Node.RootId}/${active}`
|
|
129
|
-
: '/'
|
|
130
|
-
: active
|
|
131
|
-
? `/${workspace}/${active}`
|
|
132
|
-
: `/${workspace}`;
|
|
133
|
-
|
|
134
110
|
/**
|
|
135
111
|
* Returns a handler for navigation events (initial load and popstate) that navigates to current pathname.
|
|
136
112
|
*/
|
|
@@ -16,7 +16,7 @@ import { mx } from '@dxos/ui-theme';
|
|
|
16
16
|
import { byPosition } from '@dxos/util';
|
|
17
17
|
|
|
18
18
|
import { meta } from '../../meta';
|
|
19
|
-
import {
|
|
19
|
+
import { useExpandPath } from '../hooks';
|
|
20
20
|
|
|
21
21
|
export type HomeProps = {};
|
|
22
22
|
|
|
@@ -28,7 +28,7 @@ export const Home = (_: HomeProps) => {
|
|
|
28
28
|
const userAccountItem = useItemsByDisposition('user-account')[0];
|
|
29
29
|
const pinnedItems = useItemsByDisposition('pin-end', true);
|
|
30
30
|
const workspaceItems = useItemsByDisposition('workspace');
|
|
31
|
-
|
|
31
|
+
useExpandPath(Node.RootId);
|
|
32
32
|
|
|
33
33
|
const items = useMemo(
|
|
34
34
|
() => [...(userAccountItem ? [userAccountItem] : []), ...pinnedItems, ...workspaceItems],
|
|
@@ -73,7 +73,7 @@ const WorkspaceTile: MosaicStackTileComponent<Node.Node> = (props) => {
|
|
|
73
73
|
const isSelected = selectedValue === data.id;
|
|
74
74
|
const cardRef = useRef<HTMLDivElement>(null);
|
|
75
75
|
|
|
76
|
-
|
|
76
|
+
useExpandPath(data.id);
|
|
77
77
|
|
|
78
78
|
const handleSelect = useCallback(
|
|
79
79
|
() => invokePromise(LayoutOperation.SwitchWorkspace, { subject: data.id }),
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import React, { useCallback, useEffect, useRef } from 'react';
|
|
5
|
+
import React, { useCallback, useEffect, useMemo, useRef } from 'react';
|
|
6
6
|
|
|
7
7
|
import { useOperationInvoker } from '@dxos/app-framework/ui';
|
|
8
8
|
import { LayoutOperation } from '@dxos/app-toolkit';
|
|
@@ -15,29 +15,33 @@ import { SearchList, useSearchListItem, useSearchListResults } from '@dxos/react
|
|
|
15
15
|
import { mx } from '@dxos/ui-theme';
|
|
16
16
|
|
|
17
17
|
import { meta } from '../../meta';
|
|
18
|
-
import {
|
|
18
|
+
import { useExpandPath } from '../hooks';
|
|
19
19
|
|
|
20
|
-
export type
|
|
20
|
+
export type NavBranchProps = {
|
|
21
21
|
id: string;
|
|
22
22
|
};
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
25
|
+
* Renders the children of a graph branch node as a searchable mosaic list.
|
|
26
|
+
* Used for any node with `role: 'branch'` or a workspace disposition, including
|
|
27
|
+
* spaces, collection sections, type sections, and schema nodes.
|
|
28
28
|
*/
|
|
29
|
-
export const
|
|
29
|
+
export const NavBranch = ({ id }: NavBranchProps) => {
|
|
30
30
|
const { t } = useTranslation(meta.id);
|
|
31
31
|
const { graph } = useAppGraph();
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
useLoadDescendents(id);
|
|
33
|
+
useExpandPath(id);
|
|
35
34
|
|
|
36
|
-
// Get direct children of the workspace node.
|
|
37
35
|
const children = useConnections(graph, id, 'child');
|
|
38
36
|
|
|
37
|
+
// TODO(wittjosiah): Move alternate-tree nodes to a non-child relation so they don't need filtering.
|
|
38
|
+
const visibleChildren = useMemo(
|
|
39
|
+
() => children.filter((node) => node.properties.disposition !== 'alternate-tree'),
|
|
40
|
+
[children],
|
|
41
|
+
);
|
|
42
|
+
|
|
39
43
|
const { results, handleSearch } = useSearchListResults({
|
|
40
|
-
items:
|
|
44
|
+
items: visibleChildren,
|
|
41
45
|
extract: (child) => toLocalizedString(child.properties.label, t),
|
|
42
46
|
});
|
|
43
47
|
|
|
@@ -46,6 +50,7 @@ export const Workspace = ({ id }: WorkspaceProps) => {
|
|
|
46
50
|
<Panel.Root>
|
|
47
51
|
<Panel.Toolbar asChild>
|
|
48
52
|
<Toolbar.Root>
|
|
53
|
+
{/* TODO(wittjosiah): Search should be pluggable. Must support searching via ECHO query inside a space. */}
|
|
49
54
|
<SearchList.Input placeholder={t('search placeholder')} autoFocus />
|
|
50
55
|
</Toolbar.Root>
|
|
51
56
|
</Panel.Toolbar>
|
|
@@ -54,7 +59,7 @@ export const Workspace = ({ id }: WorkspaceProps) => {
|
|
|
54
59
|
<Mosaic.Container asChild>
|
|
55
60
|
<ScrollArea.Root orientation='vertical'>
|
|
56
61
|
<ScrollArea.Viewport classNames='p-2'>
|
|
57
|
-
<Mosaic.Stack items={results} getId={(child) => child.id} Tile={
|
|
62
|
+
<Mosaic.Stack items={results} getId={(child) => child.id} Tile={NavBranchTile} />
|
|
58
63
|
</ScrollArea.Viewport>
|
|
59
64
|
</ScrollArea.Root>
|
|
60
65
|
</Mosaic.Container>
|
|
@@ -65,7 +70,7 @@ export const Workspace = ({ id }: WorkspaceProps) => {
|
|
|
65
70
|
);
|
|
66
71
|
};
|
|
67
72
|
|
|
68
|
-
const
|
|
73
|
+
const NavBranchTile: MosaicStackTileComponent<Node.Node> = (props) => {
|
|
69
74
|
const data = props.data;
|
|
70
75
|
const { t } = useTranslation(meta.id);
|
|
71
76
|
const { invokeSync } = useOperationInvoker();
|
|
@@ -5,10 +5,10 @@
|
|
|
5
5
|
import React, { useMemo } from 'react';
|
|
6
6
|
|
|
7
7
|
import { Surface } from '@dxos/app-framework/ui';
|
|
8
|
+
import { getCompanionVariant } from '@dxos/app-toolkit';
|
|
8
9
|
import { useAppGraph } from '@dxos/app-toolkit/ui';
|
|
9
10
|
import { type Node, useNode } from '@dxos/plugin-graph';
|
|
10
11
|
import { ErrorFallback, Panel } from '@dxos/react-ui';
|
|
11
|
-
import { ATTENDABLE_PATH_SEPARATOR } from '@dxos/react-ui-attention';
|
|
12
12
|
import { Menu, useMenuActions } from '@dxos/react-ui-menu';
|
|
13
13
|
|
|
14
14
|
import { useCompanions, useDrawerActions, useSimpleLayoutState } from '../../hooks';
|
|
@@ -67,12 +67,6 @@ export const Drawer = () => {
|
|
|
67
67
|
|
|
68
68
|
Drawer.displayName = DRAWER_NAME;
|
|
69
69
|
|
|
70
|
-
/** Parse entry ID to extract primary ID and variant. */
|
|
71
|
-
const parseEntryId = (entryId: string) => {
|
|
72
|
-
const [id, variant] = entryId.split(ATTENDABLE_PATH_SEPARATOR);
|
|
73
|
-
return { id, variant };
|
|
74
|
-
};
|
|
75
|
-
|
|
76
70
|
/**
|
|
77
71
|
* Resolves which companion to show based on variant preference.
|
|
78
72
|
* Falls back to first available if preferred variant not available.
|
|
@@ -85,10 +79,7 @@ const useSelectedCompanion = (companions: Node.Node[], preferredVariant?: string
|
|
|
85
79
|
|
|
86
80
|
// Try to find companion matching the preferred variant.
|
|
87
81
|
if (preferredVariant) {
|
|
88
|
-
const preferred = companions.find((c) =>
|
|
89
|
-
const { variant } = parseEntryId(c.id);
|
|
90
|
-
return variant === preferredVariant;
|
|
91
|
-
});
|
|
82
|
+
const preferred = companions.find((c) => getCompanionVariant(c.id) === preferredVariant);
|
|
92
83
|
if (preferred) {
|
|
93
84
|
return preferred;
|
|
94
85
|
}
|
|
@@ -99,7 +90,7 @@ const useSelectedCompanion = (companions: Node.Node[], preferredVariant?: string
|
|
|
99
90
|
}, [companions, preferredVariant]);
|
|
100
91
|
|
|
101
92
|
const companionId = selectedCompanion?.id;
|
|
102
|
-
const
|
|
93
|
+
const variant = companionId ? getCompanionVariant(companionId) : undefined;
|
|
103
94
|
|
|
104
95
|
return { selectedCompanion, companionId, variant };
|
|
105
96
|
};
|
|
@@ -13,8 +13,8 @@ import { mx } from '@dxos/ui-theme';
|
|
|
13
13
|
|
|
14
14
|
import { useAppBarProps, useNavbarActions, useSimpleLayoutState } from '../../hooks';
|
|
15
15
|
import { ContentLoading } from '../ContentLoading';
|
|
16
|
-
import {
|
|
17
|
-
import { useMobileLayout } from '../MobileLayout
|
|
16
|
+
import { useExpandPath } from '../hooks';
|
|
17
|
+
import { useMobileLayout } from '../MobileLayout';
|
|
18
18
|
|
|
19
19
|
import { AppBar } from './AppBar';
|
|
20
20
|
import { NavBar } from './NavBar';
|
|
@@ -47,8 +47,7 @@ export const Main = () => {
|
|
|
47
47
|
);
|
|
48
48
|
}, [id, node, node?.data, node?.properties, state.popoverAnchorId]);
|
|
49
49
|
|
|
50
|
-
|
|
51
|
-
useLoadDescendents(id);
|
|
50
|
+
useExpandPath(id);
|
|
52
51
|
|
|
53
52
|
// TODO(burdon): BUG: When showing ANY statusbar the size progressively shrinks when the keyboard opens/closes.
|
|
54
53
|
const showNavBar = !keyboardOpen && !state.isPopover && state.drawerState === 'closed';
|
package/src/components/hooks.ts
CHANGED
|
@@ -4,23 +4,23 @@
|
|
|
4
4
|
|
|
5
5
|
import { useEffect } from 'react';
|
|
6
6
|
|
|
7
|
+
import { expandAttendableId } from '@dxos/react-ui-attention';
|
|
7
8
|
import { useAppGraph } from '@dxos/app-toolkit/ui';
|
|
8
9
|
import { Graph } from '@dxos/plugin-graph';
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
|
-
*
|
|
12
|
+
* Expand graph nodes along the full path from root to the given node ID.
|
|
13
|
+
* Walks each progressive prefix, ensuring ancestor nodes are materialized
|
|
14
|
+
* before attempting to access their children.
|
|
12
15
|
*/
|
|
13
|
-
export const
|
|
16
|
+
export const useExpandPath = (nodeId?: string) => {
|
|
14
17
|
const { graph } = useAppGraph();
|
|
15
18
|
|
|
16
19
|
useEffect(() => {
|
|
17
20
|
if (nodeId) {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
Graph.getConnections(graph, nodeId, 'child').forEach((child) => {
|
|
22
|
-
Graph.expand(graph, child.id, 'child');
|
|
23
|
-
});
|
|
21
|
+
for (const prefix of expandAttendableId(nodeId)) {
|
|
22
|
+
Graph.expand(graph, prefix, 'child');
|
|
23
|
+
}
|
|
24
24
|
}
|
|
25
25
|
}, [nodeId, graph]);
|
|
26
26
|
};
|
package/src/components/index.ts
CHANGED
package/src/hooks/actions.ts
CHANGED
|
@@ -5,25 +5,23 @@
|
|
|
5
5
|
import { type Atom } from '@effect-atom/atom-react';
|
|
6
6
|
import * as Effect from 'effect/Effect';
|
|
7
7
|
|
|
8
|
-
import { type
|
|
9
|
-
import { type AppCapabilities, LayoutOperation } from '@dxos/app-toolkit';
|
|
8
|
+
import { type AppCapabilities, getCompanionVariant } from '@dxos/app-toolkit';
|
|
10
9
|
import { Node } from '@dxos/plugin-graph';
|
|
11
|
-
import { ATTENDABLE_PATH_SEPARATOR } from '@dxos/react-ui-attention';
|
|
12
10
|
import { type ActionGraphProps } from '@dxos/react-ui-menu';
|
|
13
11
|
import { byPosition } from '@dxos/util';
|
|
14
12
|
|
|
15
13
|
import { type SimpleLayoutState } from '../types';
|
|
16
14
|
|
|
17
15
|
// TODO(wittjosiah): Factor out to shared location with plugin-deck.
|
|
18
|
-
export const PLANK_COMPANION_TYPE = 'dxos.
|
|
16
|
+
export const PLANK_COMPANION_TYPE = 'org.dxos.plugin.deck.plank-companion';
|
|
19
17
|
|
|
20
18
|
export type CompanionActionsConfig = {
|
|
21
19
|
/** Prefix for companion action IDs (e.g. 'navbar' or 'drawer') */
|
|
22
20
|
idPrefix: string;
|
|
23
21
|
/** Optional: highlight companion with this variant */
|
|
24
22
|
selectedVariant?: string;
|
|
25
|
-
/**
|
|
26
|
-
|
|
23
|
+
/** State updater for toggling the drawer. */
|
|
24
|
+
updateState: (fn: (state: SimpleLayoutState) => SimpleLayoutState) => void;
|
|
27
25
|
};
|
|
28
26
|
|
|
29
27
|
/**
|
|
@@ -37,7 +35,7 @@ export const createCompanionActions = (
|
|
|
37
35
|
get: (atom: Atom.Atom<any>) => any,
|
|
38
36
|
config: CompanionActionsConfig,
|
|
39
37
|
): Pick<ActionGraphProps, 'nodes' | 'edges'> => {
|
|
40
|
-
const { idPrefix, selectedVariant,
|
|
38
|
+
const { idPrefix, selectedVariant, updateState } = config;
|
|
41
39
|
|
|
42
40
|
// Derive activeId from state atom.
|
|
43
41
|
const state = get(stateAtom);
|
|
@@ -52,12 +50,8 @@ export const createCompanionActions = (
|
|
|
52
50
|
const nodes: ActionGraphProps['nodes'] = [];
|
|
53
51
|
const edges: ActionGraphProps['edges'] = [];
|
|
54
52
|
|
|
55
|
-
// Add companion actions.
|
|
56
|
-
// TODO(burdon): Cap at 6 items.
|
|
57
53
|
companions.forEach((companion: Node.Node) => {
|
|
58
|
-
|
|
59
|
-
const [, companionVariant] = companion.id.split(ATTENDABLE_PATH_SEPARATOR);
|
|
60
|
-
|
|
54
|
+
const companionVariant = getCompanionVariant(companion.id);
|
|
61
55
|
const companionAction = {
|
|
62
56
|
id: `${idPrefix}-companion-${companion.id}`,
|
|
63
57
|
type: Node.ActionType,
|
|
@@ -65,15 +59,19 @@ export const createCompanionActions = (
|
|
|
65
59
|
icon: companion.properties.icon ?? 'ph--placeholder--regular',
|
|
66
60
|
label: companion.properties.label,
|
|
67
61
|
iconOnly: true,
|
|
68
|
-
// Conditionally add variant highlighting.
|
|
69
62
|
...(selectedVariant !== undefined && {
|
|
70
63
|
variant: selectedVariant === companionVariant ? 'primary' : 'ghost',
|
|
71
64
|
}),
|
|
72
65
|
},
|
|
73
66
|
data: () =>
|
|
74
67
|
Effect.sync(() =>
|
|
75
|
-
|
|
76
|
-
|
|
68
|
+
updateState((current) => {
|
|
69
|
+
const closing = current.companionVariant === companionVariant && current.drawerState !== 'closed';
|
|
70
|
+
return {
|
|
71
|
+
...current,
|
|
72
|
+
companionVariant: closing ? undefined : companionVariant,
|
|
73
|
+
drawerState: closing ? 'closed' : 'open',
|
|
74
|
+
};
|
|
77
75
|
}),
|
|
78
76
|
),
|
|
79
77
|
};
|
|
@@ -102,8 +102,7 @@ export const useAppBarProps = (): Omit<AppBarProps, 'classNames'> => {
|
|
|
102
102
|
}, [graph, invokeSync, state.active, state.history.length]);
|
|
103
103
|
|
|
104
104
|
// Compute popover anchor ID.
|
|
105
|
-
const popoverAnchorId =
|
|
106
|
-
node && state.popoverAnchorId === `dxos.org/ui/${meta.id}/${node.id}` ? state.popoverAnchorId : undefined;
|
|
105
|
+
const popoverAnchorId = node && state.popoverAnchorId === `${meta.id}:${node.id}` ? state.popoverAnchorId : undefined;
|
|
107
106
|
|
|
108
107
|
return {
|
|
109
108
|
title,
|