@dxos/plugin-deck 0.7.5-main.499c70c → 0.7.5-main.5ae2ba8

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 (188) hide show
  1. package/dist/lib/browser/app-graph-builder-IYHAGFA3.mjs +151 -0
  2. package/dist/lib/browser/app-graph-builder-IYHAGFA3.mjs.map +7 -0
  3. package/dist/lib/browser/check-app-scheme-S3EYUPMF.mjs +33 -0
  4. package/dist/lib/browser/check-app-scheme-S3EYUPMF.mjs.map +7 -0
  5. package/dist/lib/browser/chunk-22AQ5IVX.mjs +17 -0
  6. package/dist/lib/browser/chunk-22AQ5IVX.mjs.map +7 -0
  7. package/dist/lib/browser/chunk-FT33W5CI.mjs +128 -0
  8. package/dist/lib/browser/chunk-FT33W5CI.mjs.map +7 -0
  9. package/dist/lib/browser/chunk-G2X3ZDCE.mjs +24 -0
  10. package/dist/lib/browser/chunk-G2X3ZDCE.mjs.map +7 -0
  11. package/dist/lib/browser/chunk-KANJBSIX.mjs +97 -0
  12. package/dist/lib/browser/chunk-KANJBSIX.mjs.map +7 -0
  13. package/dist/lib/browser/{chunk-GVOGPULO.mjs → chunk-N7TEPFVR.mjs} +5 -4
  14. package/dist/lib/browser/chunk-N7TEPFVR.mjs.map +7 -0
  15. package/dist/lib/browser/chunk-O4RFYYQ6.mjs +1114 -0
  16. package/dist/lib/browser/chunk-O4RFYYQ6.mjs.map +7 -0
  17. package/dist/lib/browser/index.mjs +104 -1776
  18. package/dist/lib/browser/index.mjs.map +4 -4
  19. package/dist/lib/browser/intent-resolver-ZD67BRUI.mjs +488 -0
  20. package/dist/lib/browser/intent-resolver-ZD67BRUI.mjs.map +7 -0
  21. package/dist/lib/browser/meta.json +1 -1
  22. package/dist/lib/browser/react-root-6ILKHD5J.mjs +45 -0
  23. package/dist/lib/browser/react-root-6ILKHD5J.mjs.map +7 -0
  24. package/dist/lib/browser/react-surface-O75FKXAI.mjs +39 -0
  25. package/dist/lib/browser/react-surface-O75FKXAI.mjs.map +7 -0
  26. package/dist/lib/browser/settings-H35U6NHE.mjs +28 -0
  27. package/dist/lib/browser/settings-H35U6NHE.mjs.map +7 -0
  28. package/dist/lib/browser/state-U4SHOPJW.mjs +129 -0
  29. package/dist/lib/browser/state-U4SHOPJW.mjs.map +7 -0
  30. package/dist/lib/browser/tools-64LXGLYR.mjs +59 -0
  31. package/dist/lib/browser/tools-64LXGLYR.mjs.map +7 -0
  32. package/dist/lib/browser/types.mjs +16 -4
  33. package/dist/lib/browser/url-handler-MVHTKUYA.mjs +72 -0
  34. package/dist/lib/browser/url-handler-MVHTKUYA.mjs.map +7 -0
  35. package/dist/types/src/DeckPlugin.d.ts +1 -5
  36. package/dist/types/src/DeckPlugin.d.ts.map +1 -1
  37. package/dist/types/src/capabilities/app-graph-builder.d.ts +181 -0
  38. package/dist/types/src/capabilities/app-graph-builder.d.ts.map +1 -0
  39. package/dist/types/src/capabilities/capabilities.d.ts +142 -0
  40. package/dist/types/src/capabilities/capabilities.d.ts.map +1 -0
  41. package/dist/types/src/capabilities/check-app-scheme.d.ts +4 -0
  42. package/dist/types/src/capabilities/check-app-scheme.d.ts.map +1 -0
  43. package/dist/types/src/capabilities/index.d.ts +190 -0
  44. package/dist/types/src/capabilities/index.d.ts.map +1 -0
  45. package/dist/types/src/capabilities/intent-resolver.d.ts +4 -0
  46. package/dist/types/src/capabilities/intent-resolver.d.ts.map +1 -0
  47. package/dist/types/src/capabilities/react-root.d.ts +7 -0
  48. package/dist/types/src/capabilities/react-root.d.ts.map +1 -0
  49. package/dist/types/src/capabilities/react-surface.d.ts +4 -0
  50. package/dist/types/src/capabilities/react-surface.d.ts.map +1 -0
  51. package/dist/types/src/capabilities/settings.d.ts +4 -0
  52. package/dist/types/src/capabilities/settings.d.ts.map +1 -0
  53. package/dist/types/src/capabilities/state.d.ts +79 -0
  54. package/dist/types/src/capabilities/state.d.ts.map +1 -0
  55. package/dist/types/src/capabilities/tools.d.ts +10 -0
  56. package/dist/types/src/capabilities/tools.d.ts.map +1 -0
  57. package/dist/types/src/capabilities/url-handler.d.ts +4 -0
  58. package/dist/types/src/capabilities/url-handler.d.ts.map +1 -0
  59. package/dist/types/src/components/DeckLayout/ActiveNode.d.ts +1 -2
  60. package/dist/types/src/components/DeckLayout/ActiveNode.d.ts.map +1 -1
  61. package/dist/types/src/components/DeckLayout/Banner.d.ts +5 -0
  62. package/dist/types/src/components/DeckLayout/Banner.d.ts.map +1 -0
  63. package/dist/types/src/components/DeckLayout/ComplementarySidebar.d.ts +1 -4
  64. package/dist/types/src/components/DeckLayout/ComplementarySidebar.d.ts.map +1 -1
  65. package/dist/types/src/components/DeckLayout/ContentEmpty.d.ts +1 -2
  66. package/dist/types/src/components/DeckLayout/ContentEmpty.d.ts.map +1 -1
  67. package/dist/types/src/components/DeckLayout/DeckLayout.d.ts +2 -7
  68. package/dist/types/src/components/DeckLayout/DeckLayout.d.ts.map +1 -1
  69. package/dist/types/src/components/DeckLayout/Fallback.d.ts +1 -2
  70. package/dist/types/src/components/DeckLayout/Fallback.d.ts.map +1 -1
  71. package/dist/types/src/components/DeckLayout/Fullscreen.d.ts +1 -2
  72. package/dist/types/src/components/DeckLayout/Fullscreen.d.ts.map +1 -1
  73. package/dist/types/src/components/DeckLayout/NodePlankHeading.d.ts +3 -3
  74. package/dist/types/src/components/DeckLayout/NodePlankHeading.d.ts.map +1 -1
  75. package/dist/types/src/components/DeckLayout/Plank.d.ts +8 -6
  76. package/dist/types/src/components/DeckLayout/Plank.d.ts.map +1 -1
  77. package/dist/types/src/components/DeckLayout/PlankControls.d.ts +2 -2
  78. package/dist/types/src/components/DeckLayout/PlankControls.d.ts.map +1 -1
  79. package/dist/types/src/components/DeckLayout/PlankError.d.ts +6 -6
  80. package/dist/types/src/components/DeckLayout/PlankError.d.ts.map +1 -1
  81. package/dist/types/src/components/DeckLayout/PlankLoading.d.ts +1 -2
  82. package/dist/types/src/components/DeckLayout/PlankLoading.d.ts.map +1 -1
  83. package/dist/types/src/components/DeckLayout/Sidebar.d.ts +1 -6
  84. package/dist/types/src/components/DeckLayout/Sidebar.d.ts.map +1 -1
  85. package/dist/types/src/components/DeckLayout/SidebarButton.d.ts +7 -0
  86. package/dist/types/src/components/DeckLayout/SidebarButton.d.ts.map +1 -0
  87. package/dist/types/src/components/DeckLayout/StatusBar.d.ts +1 -2
  88. package/dist/types/src/components/DeckLayout/StatusBar.d.ts.map +1 -1
  89. package/dist/types/src/components/DeckLayout/Toast.d.ts +2 -3
  90. package/dist/types/src/components/DeckLayout/Toast.d.ts.map +1 -1
  91. package/dist/types/src/components/DeckLayout/Topbar.d.ts +2 -0
  92. package/dist/types/src/components/DeckLayout/Topbar.d.ts.map +1 -0
  93. package/dist/types/src/components/LayoutSettings.d.ts +1 -2
  94. package/dist/types/src/components/LayoutSettings.d.ts.map +1 -1
  95. package/dist/types/src/components/fragments.d.ts +4 -0
  96. package/dist/types/src/components/fragments.d.ts.map +1 -0
  97. package/dist/types/src/components/index.d.ts +0 -2
  98. package/dist/types/src/components/index.d.ts.map +1 -1
  99. package/dist/types/src/events.d.ts +5 -0
  100. package/dist/types/src/events.d.ts.map +1 -0
  101. package/dist/types/src/hooks/useMainSize.d.ts +2 -2
  102. package/dist/types/src/hooks/useNode.d.ts.map +1 -1
  103. package/dist/types/src/index.d.ts +3 -2
  104. package/dist/types/src/index.d.ts.map +1 -1
  105. package/dist/types/src/layout.d.ts +5 -19
  106. package/dist/types/src/layout.d.ts.map +1 -1
  107. package/dist/types/src/meta.d.ts +4 -4
  108. package/dist/types/src/meta.d.ts.map +1 -1
  109. package/dist/types/src/translations.d.ts +4 -2
  110. package/dist/types/src/translations.d.ts.map +1 -1
  111. package/dist/types/src/types.d.ts +127 -20
  112. package/dist/types/src/types.d.ts.map +1 -1
  113. package/dist/types/src/util/index.d.ts +4 -2
  114. package/dist/types/src/util/index.d.ts.map +1 -1
  115. package/dist/types/src/util/layoutAppliesTopbar.d.ts +2 -0
  116. package/dist/types/src/util/layoutAppliesTopbar.d.ts.map +1 -0
  117. package/dist/types/src/util/set-active.d.ts +9 -0
  118. package/dist/types/src/util/set-active.d.ts.map +1 -0
  119. package/dist/types/src/util/useBreakpoints.d.ts +2 -0
  120. package/dist/types/src/util/useBreakpoints.d.ts.map +1 -0
  121. package/dist/types/src/util/useHoistStatusbar.d.ts +2 -0
  122. package/dist/types/src/util/useHoistStatusbar.d.ts.map +1 -0
  123. package/dist/types/tsconfig.tsbuildinfo +1 -1
  124. package/package.json +32 -37
  125. package/src/DeckPlugin.ts +87 -0
  126. package/src/capabilities/app-graph-builder.ts +113 -0
  127. package/src/capabilities/capabilities.ts +15 -0
  128. package/src/capabilities/check-app-scheme.ts +44 -0
  129. package/src/capabilities/index.ts +17 -0
  130. package/src/capabilities/intent-resolver.ts +368 -0
  131. package/src/capabilities/react-root.tsx +46 -0
  132. package/src/capabilities/react-surface.tsx +31 -0
  133. package/src/capabilities/settings.ts +21 -0
  134. package/src/capabilities/state.ts +108 -0
  135. package/src/capabilities/tools.ts +66 -0
  136. package/src/capabilities/url-handler.ts +65 -0
  137. package/src/components/DeckLayout/ActiveNode.tsx +2 -3
  138. package/src/components/DeckLayout/Banner.tsx +37 -0
  139. package/src/components/DeckLayout/ComplementarySidebar.tsx +188 -58
  140. package/src/components/DeckLayout/ContentEmpty.tsx +9 -4
  141. package/src/components/DeckLayout/DeckLayout.tsx +151 -99
  142. package/src/components/DeckLayout/Fullscreen.tsx +2 -3
  143. package/src/components/DeckLayout/NodePlankHeading.tsx +64 -77
  144. package/src/components/DeckLayout/Plank.tsx +35 -43
  145. package/src/components/DeckLayout/PlankControls.tsx +12 -11
  146. package/src/components/DeckLayout/PlankError.tsx +6 -5
  147. package/src/components/DeckLayout/Sidebar.tsx +19 -9
  148. package/src/components/DeckLayout/SidebarButton.tsx +68 -0
  149. package/src/components/DeckLayout/StatusBar.tsx +6 -12
  150. package/src/components/DeckLayout/Toast.tsx +2 -2
  151. package/src/components/DeckLayout/Topbar.tsx +11 -0
  152. package/src/components/LayoutSettings.tsx +8 -8
  153. package/src/components/fragments.ts +14 -0
  154. package/src/components/index.ts +0 -2
  155. package/src/events.ts +12 -0
  156. package/src/hooks/useMainSize.ts +3 -3
  157. package/src/hooks/useNode.ts +3 -1
  158. package/src/index.ts +3 -4
  159. package/src/layout.ts +43 -212
  160. package/src/meta.ts +3 -2
  161. package/src/translations.ts +8 -6
  162. package/src/types.ts +110 -36
  163. package/src/util/index.ts +4 -2
  164. package/src/util/layoutAppliesTopbar.ts +7 -0
  165. package/src/util/set-active.ts +47 -0
  166. package/src/util/useBreakpoints.ts +11 -0
  167. package/src/util/useHoistStatusbar.ts +20 -0
  168. package/dist/lib/browser/chunk-GVOGPULO.mjs.map +0 -7
  169. package/dist/lib/browser/chunk-ZC3K6C2W.mjs +0 -37
  170. package/dist/lib/browser/chunk-ZC3K6C2W.mjs.map +0 -7
  171. package/dist/lib/browser/meta.mjs +0 -9
  172. package/dist/lib/browser/meta.mjs.map +0 -7
  173. package/dist/types/src/components/DeckContext.d.ts +0 -8
  174. package/dist/types/src/components/DeckContext.d.ts.map +0 -1
  175. package/dist/types/src/components/LayoutContext.d.ts +0 -5
  176. package/dist/types/src/components/LayoutContext.d.ts.map +0 -1
  177. package/dist/types/src/layout.test.d.ts +0 -2
  178. package/dist/types/src/layout.test.d.ts.map +0 -1
  179. package/dist/types/src/util/check-app-scheme.d.ts +0 -2
  180. package/dist/types/src/util/check-app-scheme.d.ts.map +0 -1
  181. package/dist/types/src/util/layout-parts.d.ts +0 -7
  182. package/dist/types/src/util/layout-parts.d.ts.map +0 -1
  183. package/src/DeckPlugin.tsx +0 -623
  184. package/src/components/DeckContext.ts +0 -14
  185. package/src/components/LayoutContext.ts +0 -12
  186. package/src/layout.test.ts +0 -380
  187. package/src/util/check-app-scheme.ts +0 -21
  188. package/src/util/layout-parts.ts +0 -12
@@ -0,0 +1,87 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { setAutoFreeze } from 'immer';
6
+
7
+ import { allOf, Capabilities, contributes, defineModule, definePlugin, Events, oneOf } from '@dxos/app-framework';
8
+ import { translations as stackTranslations } from '@dxos/react-ui-stack';
9
+
10
+ import {
11
+ AppGraphBuilder,
12
+ CheckAppScheme,
13
+ DeckSettings,
14
+ DeckState,
15
+ LayoutIntentResolver,
16
+ ReactRoot,
17
+ ReactSurface,
18
+ Tools,
19
+ UrlHandler,
20
+ } from './capabilities';
21
+ import { DeckEvents } from './events';
22
+ import { meta } from './meta';
23
+ import translations from './translations';
24
+
25
+ // NOTE(Zan): When producing values with immer, we shouldn't auto-freeze them because
26
+ // our signal implementation needs to add some hidden properties to the produced values.
27
+ // TODO(Zan): Move this to a more global location if we use immer more broadly.
28
+ setAutoFreeze(false);
29
+
30
+ export const DeckPlugin = () =>
31
+ definePlugin(meta, [
32
+ defineModule({
33
+ id: `${meta.id}/module/check-app-scheme`,
34
+ activatesOn: Events.SettingsReady,
35
+ activate: CheckAppScheme,
36
+ }),
37
+ defineModule({
38
+ id: `${meta.id}/module/settings`,
39
+ activatesOn: Events.SetupSettings,
40
+ activate: DeckSettings,
41
+ }),
42
+ defineModule({
43
+ id: `${meta.id}/module/layout`,
44
+ // TODO(wittjosiah): Does not integrate with settings store.
45
+ // Should this be a different event?
46
+ // Should settings store be renamed to be more generic?
47
+ activatesOn: oneOf(Events.SetupSettings, Events.SetupAppGraph),
48
+ activatesAfter: [Events.LayoutReady, DeckEvents.StateReady],
49
+ activate: DeckState,
50
+ }),
51
+ defineModule({
52
+ id: `${meta.id}/module/translations`,
53
+ activatesOn: Events.SetupTranslations,
54
+ activate: () => contributes(Capabilities.Translations, [...translations, ...stackTranslations]),
55
+ }),
56
+ defineModule({
57
+ id: `${meta.id}/module/react-root`,
58
+ activatesOn: Events.Startup,
59
+ activatesBefore: [DeckEvents.SetupComplementaryPanels],
60
+ activate: ReactRoot,
61
+ }),
62
+ defineModule({
63
+ id: `${meta.id}/module/react-surface`,
64
+ activatesOn: Events.SetupReactSurface,
65
+ activate: ReactSurface,
66
+ }),
67
+ defineModule({
68
+ id: `${meta.id}/module/layout-intent-resolver`,
69
+ activatesOn: Events.SetupIntentResolver,
70
+ activate: LayoutIntentResolver,
71
+ }),
72
+ defineModule({
73
+ id: `${meta.id}/module/app-graph-builder`,
74
+ activatesOn: Events.SetupAppGraph,
75
+ activate: AppGraphBuilder,
76
+ }),
77
+ defineModule({
78
+ id: `${meta.id}/module/tools`,
79
+ activatesOn: Events.SetupArtifactDefinition,
80
+ activate: Tools,
81
+ }),
82
+ defineModule({
83
+ id: `${meta.id}/module/url`,
84
+ activatesOn: allOf(Events.DispatcherReady, DeckEvents.StateReady),
85
+ activate: UrlHandler,
86
+ }),
87
+ ]);
@@ -0,0 +1,113 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { Capabilities, contributes, createIntent, LayoutAction, type PluginsContext } from '@dxos/app-framework';
6
+ import { AttentionCapabilities } from '@dxos/plugin-attention';
7
+ import { createExtension, type Node, ROOT_ID } from '@dxos/plugin-graph';
8
+
9
+ import { DeckCapabilities } from './capabilities';
10
+ import { DECK_PLUGIN } from '../meta';
11
+
12
+ export default (context: PluginsContext) =>
13
+ contributes(
14
+ Capabilities.AppGraphBuilder,
15
+ createExtension({
16
+ id: DECK_PLUGIN,
17
+ filter: (node): node is Node<null> => node.id === ROOT_ID,
18
+ actions: () => {
19
+ const state = context.requestCapability(DeckCapabilities.MutableDeckState);
20
+
21
+ // NOTE(Zan): This is currently disabled.
22
+ // TODO(Zan): Fullscreen needs to know the active node and provide that to the layout part.
23
+ const _fullscreen = {
24
+ id: `${LayoutAction.UpdateLayout._tag}/fullscreen`,
25
+ data: async () => {
26
+ const { dispatchPromise: dispatch } = context.requestCapability(Capabilities.IntentDispatcher);
27
+ await dispatch(createIntent(LayoutAction.SetLayoutMode, { part: 'mode', options: { mode: 'fullscreen' } }));
28
+ },
29
+ properties: {
30
+ label: ['toggle fullscreen label', { ns: DECK_PLUGIN }],
31
+ icon: 'ph--arrows-out--regular',
32
+ keyBinding: {
33
+ macos: 'ctrl+meta+f',
34
+ windows: 'shift+ctrl+f',
35
+ },
36
+ },
37
+ };
38
+
39
+ const closeCurrent = {
40
+ id: `${LayoutAction.Close._tag}/current`,
41
+ data: async () => {
42
+ const attention = context.requestCapability(AttentionCapabilities.Attention);
43
+ const attended = attention.current.at(-1);
44
+ if (attended) {
45
+ const { dispatchPromise: dispatch } = context.requestCapability(Capabilities.IntentDispatcher);
46
+ await dispatch(
47
+ createIntent(LayoutAction.Close, { part: 'main', subject: [attended], options: { state: false } }),
48
+ );
49
+ }
50
+ },
51
+ properties: {
52
+ label: ['close current label', { ns: DECK_PLUGIN }],
53
+ icon: 'ph--x--regular',
54
+ },
55
+ };
56
+
57
+ const closeOthers = {
58
+ id: `${LayoutAction.Close._tag}/others`,
59
+ data: async () => {
60
+ const { dispatchPromise: dispatch } = context.requestCapability(Capabilities.IntentDispatcher);
61
+ const attention = context.requestCapability(AttentionCapabilities.Attention);
62
+ const attended = attention.current.at(-1);
63
+ const ids = state.deck.active.filter((id) => id !== attended) ?? [];
64
+ await dispatch(createIntent(LayoutAction.Close, { part: 'main', subject: ids, options: { state: false } }));
65
+ },
66
+ properties: {
67
+ label: ['close others label', { ns: DECK_PLUGIN }],
68
+ icon: 'ph--x-square--regular',
69
+ },
70
+ };
71
+
72
+ const closeAll = {
73
+ id: `${LayoutAction.Close._tag}/all`,
74
+ data: async () => {
75
+ const { dispatchPromise: dispatch } = context.requestCapability(Capabilities.IntentDispatcher);
76
+ await dispatch(
77
+ createIntent(LayoutAction.Close, {
78
+ part: 'main',
79
+ subject: state.deck.active,
80
+ options: { state: false },
81
+ }),
82
+ );
83
+ },
84
+ properties: {
85
+ label: ['close all label', { ns: DECK_PLUGIN }],
86
+ icon: 'ph--x-circle--regular',
87
+ },
88
+ };
89
+
90
+ const toggleSidebar = {
91
+ id: `${LayoutAction.UpdateSidebar._tag}/nav`,
92
+ data: async () => {
93
+ state.sidebarState = state.sidebarState === 'expanded' ? 'collapsed' : 'expanded';
94
+ },
95
+ properties: {
96
+ label: [
97
+ state.sidebarState === 'expanded' ? 'collapse navigation sidebar label' : 'open navigation sidebar label',
98
+ { ns: DECK_PLUGIN },
99
+ ],
100
+ icon: 'ph--sidebar--regular',
101
+ keyBinding: {
102
+ macos: 'meta+b',
103
+ },
104
+ disposition: 'pin-end',
105
+ position: 'hoist',
106
+ l0Breakpoint: 'lg',
107
+ },
108
+ };
109
+
110
+ return !state.deck.solo ? [closeCurrent, closeOthers, closeAll, toggleSidebar] : [toggleSidebar];
111
+ },
112
+ }),
113
+ );
@@ -0,0 +1,15 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { defineCapability } from '@dxos/app-framework';
6
+ import { type DeepReadonly } from '@dxos/util';
7
+
8
+ import { DECK_PLUGIN } from '../meta';
9
+ import { type DeckState, type Panel } from '../types';
10
+
11
+ export namespace DeckCapabilities {
12
+ export const DeckState = defineCapability<DeepReadonly<DeckState>>(`${DECK_PLUGIN}/capability/state`);
13
+ export const MutableDeckState = defineCapability<DeckState>(`${DECK_PLUGIN}/capability/state`);
14
+ export const ComplementaryPanel = defineCapability<Panel>(`${DECK_PLUGIN}/capability/complementary-panel`);
15
+ }
@@ -0,0 +1,44 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+ //
5
+ // Copyright 2025 DXOS.org
6
+ //
7
+
8
+ import { Capabilities, contributes, type PluginsContext } from '@dxos/app-framework';
9
+
10
+ import { DECK_PLUGIN } from '../meta';
11
+ import { type DeckSettingsProps } from '../types';
12
+
13
+ const isSocket = !!(globalThis as any).__args;
14
+
15
+ // TODO(mjamesderocher): Can we get this directly from Socket?
16
+ const appScheme = 'composer://';
17
+
18
+ // TODO(mjamesderocher): Factor out as part of NavigationPlugin.
19
+ const checkAppScheme = (url: string) => {
20
+ const iframe = document.createElement('iframe');
21
+ iframe.style.display = 'none';
22
+ document.body.appendChild(iframe);
23
+
24
+ iframe.src = url + window.location.pathname.replace(/^\/+/, '') + window.location.search;
25
+
26
+ const timer = setTimeout(() => {
27
+ document.body.removeChild(iframe);
28
+ }, 3000);
29
+
30
+ window.addEventListener('pagehide', (event) => {
31
+ clearTimeout(timer);
32
+ document.body.removeChild(iframe);
33
+ });
34
+ };
35
+
36
+ export default async (context: PluginsContext) => {
37
+ const settingsStore = context.requestCapability(Capabilities.SettingsStore);
38
+ const settings = settingsStore.getStore<DeckSettingsProps>(DECK_PLUGIN)?.value;
39
+ if (!isSocket && settings?.enableNativeRedirect) {
40
+ checkAppScheme(appScheme);
41
+ }
42
+
43
+ return contributes(Capabilities.Null, null);
44
+ };
@@ -0,0 +1,17 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { lazy } from '@dxos/app-framework';
6
+
7
+ export const AppGraphBuilder = lazy(() => import('./app-graph-builder'));
8
+ export const CheckAppScheme = lazy(() => import('./check-app-scheme'));
9
+ export const LayoutIntentResolver = lazy(() => import('./intent-resolver'));
10
+ export const ReactRoot = lazy(() => import('./react-root'));
11
+ export const ReactSurface = lazy(() => import('./react-surface'));
12
+ export const DeckSettings = lazy(() => import('./settings'));
13
+ export const DeckState = lazy(() => import('./state'));
14
+ export const Tools = lazy(() => import('./tools'));
15
+ export const UrlHandler = lazy(() => import('./url-handler'));
16
+
17
+ export * from './capabilities';
@@ -0,0 +1,368 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { batch } from '@preact/signals-core';
6
+ import { pipe } from 'effect';
7
+
8
+ import {
9
+ Capabilities,
10
+ createResolver,
11
+ contributes,
12
+ IntentAction,
13
+ LayoutAction,
14
+ type PluginsContext,
15
+ createIntent,
16
+ chain,
17
+ } from '@dxos/app-framework';
18
+ import { getTypename, S } from '@dxos/echo-schema';
19
+ import { isReactiveObject } from '@dxos/live-object';
20
+ import { log } from '@dxos/log';
21
+ import { AttentionCapabilities } from '@dxos/plugin-attention';
22
+ import { ObservabilityAction } from '@dxos/plugin-observability/types';
23
+ import { isNonNullable } from '@dxos/util';
24
+
25
+ import { DeckCapabilities } from './capabilities';
26
+ import { closeEntry, incrementPlank, openEntry } from '../layout';
27
+ import { DECK_PLUGIN } from '../meta';
28
+ import { DeckAction, type LayoutMode, type DeckSettingsProps, isLayoutMode, getMode } from '../types';
29
+ import { setActive } from '../util';
30
+
31
+ export default (context: PluginsContext) =>
32
+ contributes(Capabilities.IntentResolver, [
33
+ createResolver({
34
+ intent: IntentAction.ShowUndo,
35
+ resolve: (data) => {
36
+ const layout = context.requestCapability(DeckCapabilities.MutableDeckState);
37
+ const { undoPromise: undo } = context.requestCapability(Capabilities.IntentDispatcher);
38
+
39
+ // TODO(wittjosiah): Support undoing further back than the last action.
40
+ if (layout.currentUndoId) {
41
+ layout.toasts = layout.toasts.filter((toast) => toast.id !== layout.currentUndoId);
42
+ }
43
+ layout.currentUndoId = `${IntentAction.ShowUndo._tag}-${Date.now()}`;
44
+ layout.toasts = [
45
+ ...layout.toasts,
46
+ {
47
+ id: layout.currentUndoId,
48
+ title: data.message ?? ['undo available label', { ns: DECK_PLUGIN }],
49
+ duration: 10_000,
50
+ actionLabel: ['undo action label', { ns: DECK_PLUGIN }],
51
+ actionAlt: ['undo action alt', { ns: DECK_PLUGIN }],
52
+ closeLabel: ['undo close label', { ns: DECK_PLUGIN }],
53
+ onAction: () => undo(),
54
+ },
55
+ ];
56
+ },
57
+ }),
58
+ createResolver({
59
+ intent: LayoutAction.UpdateLayout,
60
+ // TODO(wittjosiah): This should be able to just be `S.is(LayoutAction.UpdateSidebar.fields.input)`
61
+ // but the filter is not being applied correctly.
62
+ filter: (data): data is S.Schema.Type<typeof LayoutAction.UpdateSidebar.fields.input> =>
63
+ S.is(LayoutAction.UpdateSidebar.fields.input)(data),
64
+ resolve: ({ options }) => {
65
+ const layout = context.requestCapability(DeckCapabilities.MutableDeckState);
66
+ const next = options?.state ?? layout.sidebarState;
67
+ if (next !== layout.sidebarState) {
68
+ layout.sidebarState = next;
69
+ }
70
+ },
71
+ }),
72
+ createResolver({
73
+ intent: LayoutAction.UpdateLayout,
74
+ // TODO(wittjosiah): This should be able to just be `S.is(LayoutAction.UpdateComplementary.fields.input)`
75
+ // but the filter is not being applied correctly.
76
+ filter: (data): data is S.Schema.Type<typeof LayoutAction.UpdateComplementary.fields.input> =>
77
+ S.is(LayoutAction.UpdateComplementary.fields.input)(data),
78
+ resolve: ({ subject, options }) => {
79
+ const layout = context.requestCapability(DeckCapabilities.MutableDeckState);
80
+
81
+ if (layout.complementarySidebarPanel !== subject) {
82
+ layout.complementarySidebarPanel = subject;
83
+ }
84
+
85
+ const next = subject ? 'expanded' : options?.state ?? layout.complementarySidebarState;
86
+ if (next !== layout.complementarySidebarState) {
87
+ layout.complementarySidebarState = next;
88
+ }
89
+ },
90
+ }),
91
+ createResolver({
92
+ intent: LayoutAction.UpdateLayout,
93
+ // TODO(wittjosiah): This should be able to just be `S.is(LayoutAction.UpdateDialog.fields.input)`
94
+ // but the filter is not being applied correctly.
95
+ filter: (data): data is S.Schema.Type<typeof LayoutAction.UpdateDialog.fields.input> =>
96
+ S.is(LayoutAction.UpdateDialog.fields.input)(data),
97
+ resolve: ({ subject, options }) => {
98
+ const layout = context.requestCapability(DeckCapabilities.MutableDeckState);
99
+ layout.dialogOpen = options.state ?? Boolean(subject);
100
+ layout.dialogContent = subject ? { component: subject, props: options.props } : null;
101
+ layout.dialogBlockAlign = options.blockAlign ?? 'center';
102
+ layout.dialogType = options.type ?? 'default';
103
+ },
104
+ }),
105
+ createResolver({
106
+ intent: LayoutAction.UpdateLayout,
107
+ // TODO(wittjosiah): This should be able to just be `S.is(LayoutAction.UpdatePopover.fields.input)`
108
+ // but the filter is not being applied correctly.
109
+ filter: (data): data is S.Schema.Type<typeof LayoutAction.UpdatePopover.fields.input> =>
110
+ S.is(LayoutAction.UpdatePopover.fields.input)(data),
111
+ resolve: ({ subject, options }) => {
112
+ const layout = context.requestCapability(DeckCapabilities.MutableDeckState);
113
+ layout.popoverOpen = options.state ?? Boolean(subject);
114
+ layout.popoverContent = subject ? { component: subject, props: options.props } : null;
115
+ layout.popoverAnchorId = options.anchorId;
116
+ layout.popoverSide = options.side;
117
+ },
118
+ }),
119
+ createResolver({
120
+ intent: LayoutAction.UpdateLayout,
121
+ // TODO(wittjosiah): This should be able to just be `S.is(LayoutAction.AddToast.fields.input)`
122
+ // but the filter is not being applied correctly.
123
+ filter: (data): data is S.Schema.Type<typeof LayoutAction.AddToast.fields.input> =>
124
+ S.is(LayoutAction.AddToast.fields.input)(data),
125
+ resolve: ({ subject }) => {
126
+ const layout = context.requestCapability(DeckCapabilities.MutableDeckState);
127
+ layout.toasts.push(subject);
128
+ },
129
+ }),
130
+ createResolver({
131
+ intent: LayoutAction.UpdateLayout,
132
+ // TODO(wittjosiah): This should be able to just be `S.is(LayoutAction.SetLayoutMode.fields.input)`
133
+ // but the filter is not being applied correctly.
134
+ filter: (data): data is S.Schema.Type<typeof LayoutAction.SetLayoutMode.fields.input> => {
135
+ if (!S.is(LayoutAction.SetLayoutMode.fields.input)(data)) {
136
+ return false;
137
+ }
138
+
139
+ if ('mode' in data.options) {
140
+ return isLayoutMode(data.options.mode);
141
+ }
142
+
143
+ return true;
144
+ },
145
+ resolve: ({ subject, options }) => {
146
+ const state = context.requestCapability(DeckCapabilities.MutableDeckState);
147
+
148
+ const setMode = (mode: LayoutMode) => {
149
+ const deck = state.deck;
150
+ const current = deck.solo ? [deck.solo] : deck.active;
151
+ // When un-soloing, the solo entry is added to the deck.
152
+ const next = (
153
+ mode !== 'deck' ? [subject ?? deck.solo ?? deck.active[0]] : [...deck.active, deck.solo]
154
+ ).filter(isNonNullable);
155
+
156
+ const removed = current.filter((id) => !next.includes(id));
157
+ const closed = Array.from(new Set([...deck.inactive.filter((id) => !next.includes(id)), ...removed]));
158
+ deck.inactive = closed;
159
+
160
+ if (mode !== 'deck' && next[0]) {
161
+ deck.solo = next[0];
162
+ } else if (mode === 'deck' && deck.solo) {
163
+ deck.solo = undefined;
164
+ deck.initialized = true;
165
+ }
166
+
167
+ if (mode === 'fullscreen' && !deck.fullscreen) {
168
+ deck.fullscreen = true;
169
+ } else if (mode !== 'fullscreen' && deck.fullscreen) {
170
+ deck.fullscreen = false;
171
+ }
172
+ };
173
+
174
+ return batch(() => {
175
+ if ('mode' in options) {
176
+ const current = getMode(state.deck);
177
+ if (current !== options.mode) {
178
+ state.previousMode[state.activeDeck] = current;
179
+ }
180
+ setMode(options.mode as LayoutMode);
181
+ } else if ('revert' in options) {
182
+ const last = state.previousMode[state.activeDeck];
183
+ setMode(last ?? 'solo');
184
+ } else {
185
+ log.warn('Invalid layout mode', options);
186
+ }
187
+ });
188
+ },
189
+ }),
190
+ createResolver({
191
+ intent: LayoutAction.UpdateLayout,
192
+ filter: (data): data is S.Schema.Type<typeof LayoutAction.SwitchWorkspace.fields.input> =>
193
+ S.is(LayoutAction.SwitchWorkspace.fields.input)(data),
194
+ resolve: ({ subject }) => {
195
+ const state = context.requestCapability(DeckCapabilities.MutableDeckState);
196
+ batch(() => {
197
+ // TODO(wittjosiah): This is a hack to prevent the previous deck from being set for pinned items.
198
+ // Ideally this should be worked into the data model in a generic way.
199
+ if (!state.activeDeck.startsWith('!')) {
200
+ state.previousDeck = state.activeDeck;
201
+ }
202
+ state.activeDeck = subject;
203
+ if (!state.decks[subject]) {
204
+ state.decks[subject] = { initialized: false, active: [], inactive: [], fullscreen: false, plankSizing: {} };
205
+ }
206
+ });
207
+
208
+ const first = state.deck.solo ? state.deck.solo : state.deck.active[0];
209
+ if (first) {
210
+ return {
211
+ intents: [createIntent(LayoutAction.ScrollIntoView, { part: 'current', subject: first })],
212
+ };
213
+ }
214
+ },
215
+ }),
216
+ createResolver({
217
+ intent: LayoutAction.UpdateLayout,
218
+ filter: (data): data is S.Schema.Type<typeof LayoutAction.RevertWorkspace.fields.input> =>
219
+ S.is(LayoutAction.RevertWorkspace.fields.input)(data),
220
+ resolve: () => {
221
+ const state = context.requestCapability(DeckCapabilities.MutableDeckState);
222
+ return {
223
+ intents: [createIntent(LayoutAction.SwitchWorkspace, { part: 'workspace', subject: state.previousDeck })],
224
+ };
225
+ },
226
+ }),
227
+ createResolver({
228
+ intent: LayoutAction.UpdateLayout,
229
+ filter: (data): data is S.Schema.Type<typeof LayoutAction.Open.fields.input> =>
230
+ S.is(LayoutAction.Open.fields.input)(data),
231
+ resolve: ({ subject, options }) => {
232
+ const { graph } = context.requestCapability(Capabilities.AppGraph);
233
+ const state = context.requestCapability(DeckCapabilities.MutableDeckState);
234
+ const attention = context.requestCapability(AttentionCapabilities.Attention);
235
+ const settings = context
236
+ .requestCapabilities(Capabilities.SettingsStore)[0]
237
+ ?.getStore<DeckSettingsProps>(DECK_PLUGIN)?.value;
238
+
239
+ const previouslyOpenIds = new Set<string>(state.deck.solo ? [state.deck.solo] : state.deck.active);
240
+ batch(() => {
241
+ const next = state.deck.solo
242
+ ? (subject as string[])
243
+ : subject.reduce(
244
+ (acc, entryId) =>
245
+ openEntry(acc, entryId, {
246
+ key: options?.key,
247
+ positioning: options?.positioning ?? settings?.newPlankPositioning,
248
+ pivotId: options?.pivotId,
249
+ }),
250
+ state.deck.active,
251
+ );
252
+
253
+ return setActive({ next, state, attention });
254
+ });
255
+
256
+ const ids = state.deck.solo ? [state.deck.solo] : state.deck.active;
257
+ const newlyOpen = ids.filter((i) => !previouslyOpenIds.has(i));
258
+
259
+ return {
260
+ intents: [
261
+ ...(options?.scrollIntoView !== false
262
+ ? [createIntent(LayoutAction.ScrollIntoView, { part: 'current', subject: newlyOpen[0] ?? subject[0] })]
263
+ : []),
264
+ createIntent(LayoutAction.Expose, { part: 'navigation', subject: newlyOpen[0] ?? subject[0] }),
265
+ ...newlyOpen.map((id) => {
266
+ const active = graph?.findNode(id)?.data;
267
+ const typename = isReactiveObject(active) ? getTypename(active) : undefined;
268
+ return createIntent(ObservabilityAction.SendEvent, {
269
+ name: 'navigation.activate',
270
+ properties: {
271
+ id,
272
+ typename,
273
+ },
274
+ });
275
+ }),
276
+ ],
277
+ };
278
+ },
279
+ }),
280
+ createResolver({
281
+ intent: LayoutAction.UpdateLayout,
282
+ filter: (data): data is S.Schema.Type<typeof LayoutAction.Close.fields.input> =>
283
+ S.is(LayoutAction.Close.fields.input)(data),
284
+ resolve: ({ subject }) => {
285
+ const state = context.requestCapability(DeckCapabilities.MutableDeckState);
286
+ const attention = context.requestCapability(AttentionCapabilities.Attention);
287
+ const active = state.deck.solo ? [state.deck.solo] : state.deck.active;
288
+ const next = subject.reduce((acc, id) => closeEntry(acc, id), active);
289
+ const toAttend = setActive({ next, state, attention });
290
+ return {
291
+ intents: toAttend ? [createIntent(LayoutAction.ScrollIntoView, { part: 'current', subject: toAttend })] : [],
292
+ };
293
+ },
294
+ }),
295
+ createResolver({
296
+ intent: LayoutAction.UpdateLayout,
297
+ filter: (data): data is S.Schema.Type<typeof LayoutAction.Set.fields.input> =>
298
+ S.is(LayoutAction.Set.fields.input)(data),
299
+ resolve: ({ subject }) => {
300
+ const state = context.requestCapability(DeckCapabilities.MutableDeckState);
301
+ const attention = context.requestCapability(AttentionCapabilities.Attention);
302
+ const toAttend = setActive({ next: subject as string[], state, attention });
303
+ return {
304
+ intents: toAttend ? [createIntent(LayoutAction.ScrollIntoView, { part: 'current', subject: toAttend })] : [],
305
+ };
306
+ },
307
+ }),
308
+ createResolver({
309
+ intent: LayoutAction.UpdateLayout,
310
+ filter: (data): data is S.Schema.Type<typeof LayoutAction.ScrollIntoView.fields.input> =>
311
+ S.is(LayoutAction.ScrollIntoView.fields.input)(data),
312
+ resolve: ({ subject }) => {
313
+ const layout = context.requestCapability(DeckCapabilities.MutableDeckState);
314
+ layout.scrollIntoView = subject;
315
+ },
316
+ }),
317
+ createResolver({
318
+ intent: DeckAction.UpdatePlankSize,
319
+ resolve: (data) => {
320
+ const state = context.requestCapability(DeckCapabilities.MutableDeckState);
321
+ state.deck.plankSizing[data.id] = data.size;
322
+ },
323
+ }),
324
+ createResolver({
325
+ intent: DeckAction.Adjust,
326
+ resolve: (adjustment) => {
327
+ const state = context.requestCapability(DeckCapabilities.MutableDeckState);
328
+ const attention = context.requestCapability(AttentionCapabilities.Attention);
329
+
330
+ return batch(() => {
331
+ if (adjustment.type === 'increment-end' || adjustment.type === 'increment-start') {
332
+ setActive({
333
+ next: incrementPlank(state.deck.active, adjustment),
334
+ state,
335
+ attention,
336
+ });
337
+ }
338
+
339
+ if (adjustment.type === 'solo') {
340
+ const entryId = adjustment.id;
341
+ if (!state.deck.solo) {
342
+ // Solo the entry.
343
+ return {
344
+ intents: [
345
+ createIntent(LayoutAction.SetLayoutMode, {
346
+ part: 'mode',
347
+ subject: entryId,
348
+ options: { mode: 'solo' },
349
+ }),
350
+ ],
351
+ };
352
+ } else {
353
+ // Un-solo the current entry.
354
+ return {
355
+ intents: [
356
+ // NOTE: The order of these is important.
357
+ pipe(
358
+ createIntent(LayoutAction.SetLayoutMode, { part: 'mode', options: { mode: 'deck' } }),
359
+ chain(LayoutAction.Open, { part: 'main', subject: [entryId] }),
360
+ ),
361
+ ],
362
+ };
363
+ }
364
+ }
365
+ });
366
+ },
367
+ }),
368
+ ]);