@dxos/plugin-deck 0.7.5-main.9d26e3a → 0.7.5-main.b19bfc8

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-CYE6QZBQ.mjs +128 -0
  8. package/dist/lib/browser/chunk-CYE6QZBQ.mjs.map +7 -0
  9. package/dist/lib/browser/chunk-DIM5INBX.mjs +24 -0
  10. package/dist/lib/browser/chunk-DIM5INBX.mjs.map +7 -0
  11. package/dist/lib/browser/chunk-DIXE74SK.mjs +1097 -0
  12. package/dist/lib/browser/chunk-DIXE74SK.mjs.map +7 -0
  13. package/dist/lib/browser/chunk-KANJBSIX.mjs +97 -0
  14. package/dist/lib/browser/chunk-KANJBSIX.mjs.map +7 -0
  15. package/dist/lib/browser/{chunk-GVOGPULO.mjs → chunk-N7TEPFVR.mjs} +5 -4
  16. package/dist/lib/browser/chunk-N7TEPFVR.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-MWUADUNI.mjs +488 -0
  20. package/dist/lib/browser/intent-resolver-MWUADUNI.mjs.map +7 -0
  21. package/dist/lib/browser/meta.json +1 -1
  22. package/dist/lib/browser/react-root-IELFERPV.mjs +45 -0
  23. package/dist/lib/browser/react-root-IELFERPV.mjs.map +7 -0
  24. package/dist/lib/browser/react-surface-WL45R43W.mjs +39 -0
  25. package/dist/lib/browser/react-surface-WL45R43W.mjs.map +7 -0
  26. package/dist/lib/browser/settings-YONG3QB7.mjs +28 -0
  27. package/dist/lib/browser/settings-YONG3QB7.mjs.map +7 -0
  28. package/dist/lib/browser/state-MZZL5S2D.mjs +124 -0
  29. package/dist/lib/browser/state-MZZL5S2D.mjs.map +7 -0
  30. package/dist/lib/browser/tools-5LDJNU56.mjs +51 -0
  31. package/dist/lib/browser/tools-5LDJNU56.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 +121 -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 +107 -0
  135. package/src/capabilities/tools.ts +61 -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 +142 -59
  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 +104 -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
package/src/layout.ts CHANGED
@@ -4,245 +4,76 @@
4
4
 
5
5
  import { produce } from 'immer';
6
6
 
7
- import {
8
- type LayoutAdjustment,
9
- type LayoutCoordinate,
10
- type LayoutEntry,
11
- type LayoutPart,
12
- type LayoutParts,
13
- SLUG_ENTRY_SEPARATOR,
14
- SLUG_KEY_VALUE_SEPARATOR,
15
- SLUG_LIST_SEPARATOR,
16
- SLUG_PATH_SEPARATOR,
17
- } from '@dxos/app-framework';
7
+ import { type DeckAction, type NewPlankPositioning } from './types';
18
8
 
19
- import { type NewPlankPositioning } from './types';
9
+ type OpenLayoutEntryOptions = { key?: string; positioning?: NewPlankPositioning; pivotId?: string };
20
10
 
21
- // Part feature support
22
- const partsThatSupportIncrement = ['main'] as LayoutPart[];
23
-
24
- //
25
- // --- Layout Parts Manipulation ----------------------------------------------
26
-
27
- type OpenLayoutEntryOptions = { positioning?: NewPlankPositioning; pivotId?: string };
28
-
29
- export const openEntry = (
30
- layout: LayoutParts,
31
- part: LayoutPart,
32
- entry: LayoutEntry,
33
- options?: OpenLayoutEntryOptions,
34
- ): LayoutParts => {
35
- return produce(layout, (draft) => {
36
- const layoutPart = draft[part];
37
- // If the part doesn't exist, create it.
38
- if (!layoutPart) {
39
- draft[part] = [entry];
11
+ export const openEntry = (deck: string[], entryId: string, options?: OpenLayoutEntryOptions): string[] => {
12
+ return produce(deck, (draft) => {
13
+ // Check that the entry is not already in the part
14
+ if (draft.find((id) => id === entryId)) {
40
15
  return;
41
16
  }
42
- if (part === 'main') {
43
- // Check that the entry is not already in the part
44
- if (layoutPart.find((e) => e.id === entry.id)) {
17
+
18
+ const key = options?.key;
19
+ const plankPositioning = options?.positioning ?? 'start';
20
+ const pivotId = options?.pivotId;
21
+
22
+ if (key) {
23
+ const index = draft.findIndex((id) => id.split('+')[0] === key);
24
+ if (index !== -1) {
25
+ draft.splice(index, 1, entryId);
45
26
  return;
46
27
  }
28
+ }
47
29
 
48
- const plankPositioning = options?.positioning ?? 'start';
49
- const pivotId = options?.pivotId;
50
-
51
- if (pivotId) {
52
- const pivotIndex = layoutPart.findIndex((e) => e.id === pivotId);
53
- if (pivotIndex !== -1) {
54
- if (plankPositioning === 'start') {
55
- layoutPart.splice(pivotIndex, 0, entry);
56
- } else {
57
- layoutPart.splice(pivotIndex + 1, 0, entry);
58
- }
59
- return;
30
+ if (pivotId) {
31
+ const pivotIndex = draft.findIndex((id) => id === pivotId);
32
+ if (pivotIndex !== -1) {
33
+ if (plankPositioning === 'start') {
34
+ draft.splice(pivotIndex, 0, entryId);
35
+ } else {
36
+ draft.splice(pivotIndex + 1, 0, entryId);
60
37
  }
38
+ return;
61
39
  }
40
+ }
62
41
 
63
- // If no pivot found or provided, fall back to original behavior
64
- if (plankPositioning === 'start') {
65
- layoutPart.unshift(entry);
66
- } else {
67
- layoutPart.push(entry);
68
- }
42
+ // If no pivot found or provided, fall back to original behavior
43
+ if (plankPositioning === 'start') {
44
+ draft.unshift(entryId);
69
45
  } else {
70
- // If the part is not main, we're going to replace the single entry in the part with the new entry.
71
- draft[part] = [entry];
46
+ draft.push(entryId);
72
47
  }
73
48
  });
74
49
  };
75
50
 
76
- export const closeEntry = (layout: LayoutParts, layoutCoordinate: LayoutCoordinate): LayoutParts => {
77
- return produce(layout, (draft) => {
78
- const { part, entryId: slugId } = layoutCoordinate;
79
- const layoutPart = draft[part];
80
- if (!layoutPart) {
81
- return;
82
- }
83
-
84
- const index = layoutPart.findIndex((entry) => entry.id === slugId);
85
- if (index === -1) {
86
- return;
87
- }
88
-
89
- // If there's only one entry in the layout part, remove the whole part from the layout.
90
- if (layoutPart.length === 1) {
91
- delete draft[part];
92
- } else {
93
- layoutPart.splice(index, 1);
51
+ export const closeEntry = (deck: string[], entryId: string): string[] => {
52
+ return produce(deck, (draft) => {
53
+ const index = draft.findIndex((id) => id === entryId);
54
+ if (index !== -1) {
55
+ draft.splice(index, 1);
94
56
  }
95
57
  });
96
58
  };
97
59
 
98
- export const incrementPlank = (layout: LayoutParts, adjustment: LayoutAdjustment): LayoutParts => {
99
- return produce(layout, (draft) => {
100
- const { layoutCoordinate, type } = adjustment;
101
- const { part, entryId } = layoutCoordinate;
102
-
103
- // Only allow adjustments in the 'main' part
104
- if (partsThatSupportIncrement.includes(part) === false) {
105
- return;
106
- }
107
-
108
- const layoutPart = draft[part];
109
- if (!layoutPart) {
110
- return;
111
- }
112
- const index = layoutPart.findIndex((entry) => entry.id === entryId);
60
+ export const incrementPlank = (deck: string[], adjustment: DeckAction.Adjustment): string[] => {
61
+ return produce(deck, (draft) => {
62
+ const index = draft.findIndex((id) => id === adjustment.id);
113
63
  if (
114
64
  index === -1 ||
115
- (type === 'increment-start' && index === 0) ||
116
- (type === 'increment-end' && index === layoutPart.length - 1)
65
+ (adjustment.type === 'increment-start' && index === 0) ||
66
+ (adjustment.type === 'increment-end' && index === draft.length - 1)
117
67
  ) {
118
68
  return;
119
69
  }
120
70
 
121
- if (type === 'increment-start') {
71
+ if (adjustment.type === 'increment-start') {
122
72
  // Swap the current item with the previous item.
123
- [layoutPart[index - 1], layoutPart[index]] = [layoutPart[index], layoutPart[index - 1]];
124
- } else if (type === 'increment-end') {
73
+ [draft[index - 1], draft[index]] = [draft[index], draft[index - 1]];
74
+ } else if (adjustment.type === 'increment-end') {
125
75
  // Swap the current item with the next item.
126
- [layoutPart[index], layoutPart[index + 1]] = [layoutPart[index + 1], layoutPart[index]];
76
+ [draft[index], draft[index + 1]] = [draft[index + 1], draft[index]];
127
77
  }
128
78
  });
129
79
  };
130
-
131
- export const removePart = (layout: LayoutParts, part: LayoutPart): LayoutParts => {
132
- return produce(layout, (draft) => {
133
- delete draft[part];
134
- });
135
- };
136
-
137
- export const mergeLayoutParts = (...layoutParts: LayoutParts[]): LayoutParts => {
138
- return layoutParts.reduce(
139
- (merged, current) =>
140
- produce(merged, (draft) => {
141
- Object.entries(current).forEach(([part, entries]) => {
142
- const typedPart = part as LayoutPart;
143
-
144
- if (!draft[typedPart]) {
145
- draft[typedPart] = [];
146
- }
147
-
148
- const partEntries = draft[typedPart] as LayoutEntry[];
149
-
150
- entries.forEach((entry) => {
151
- const existingIndex = partEntries.findIndex((e) => e.id === entry.id);
152
- if (existingIndex !== -1) {
153
- partEntries[existingIndex] = entry;
154
- } else {
155
- partEntries.push(entry);
156
- }
157
- });
158
- });
159
- }),
160
- {} as LayoutParts,
161
- );
162
- };
163
-
164
- //
165
- // URI Projection
166
- //
167
-
168
- const parseLayoutEntry = (itemString: string): LayoutEntry => {
169
- // Layout entries are in the form of 'id~path' or just 'id'
170
- const [id, path] = itemString.split(SLUG_PATH_SEPARATOR);
171
- const entry: LayoutEntry = { id };
172
- if (path) {
173
- entry.path = path;
174
- }
175
- return entry;
176
- };
177
-
178
- export const uriToSoloPart = (uri: string): LayoutParts | undefined => {
179
- // Now after the domain part, there will be a single ID with an optional path
180
- const parts = uri.split('/');
181
- const slug = parts[parts.length - 1]; // Take the last part of the URI
182
-
183
- if (slug.length > 0) {
184
- return {
185
- solo: [parseLayoutEntry(slug)],
186
- };
187
- }
188
-
189
- return undefined;
190
- };
191
-
192
- export const soloPartToUri = (layout: LayoutParts): string => {
193
- const soloPart = layout?.solo;
194
- if (!soloPart || soloPart.length === 0) {
195
- return '';
196
- }
197
-
198
- const entry = soloPart[0];
199
- return `${entry.id}${entry.path ? SLUG_PATH_SEPARATOR + entry.path : ''}`;
200
- };
201
-
202
- /**
203
- * Converts a URI string into a LayoutParts object.
204
- * @deprecated Keeping these as a reference for now. We should remove these once we're sure we don't need them.
205
- */
206
- export const uriToActiveParts = (uri: string): LayoutParts => {
207
- const parts = uri.split('/');
208
- const slug = parts[parts.length - 1]; // Take the last part of the URI
209
-
210
- if (!slug) {
211
- return {}; // Return an empty object if the slug is empty
212
- }
213
-
214
- return slug.split(SLUG_ENTRY_SEPARATOR).reduce((acc: LayoutParts, partDescriptor) => {
215
- const [part, layoutEntry] = partDescriptor.split(SLUG_KEY_VALUE_SEPARATOR);
216
- if (part && layoutEntry) {
217
- // TODO(Zan): Remove this cast.
218
- acc[part as LayoutPart] = layoutEntry.split(SLUG_LIST_SEPARATOR).map(parseLayoutEntry);
219
- }
220
- return acc;
221
- }, {} as LayoutParts);
222
- };
223
-
224
- const formatLayoutEntry = ({ id, path }: LayoutEntry): string => {
225
- // NOTE(Zan): Format = `[SOLO_INDICATOR] ID [PATH_SEPARATOR PATH]`.
226
- let entry = '';
227
- entry += id;
228
- if (path) {
229
- entry += `${SLUG_PATH_SEPARATOR}${path}`;
230
- }
231
- return entry;
232
- };
233
-
234
- const formatPartDescriptor = (part: LayoutPart, layoutEntries: LayoutEntry[]): string => {
235
- const formattedEntries = layoutEntries.map(formatLayoutEntry).join(SLUG_LIST_SEPARATOR);
236
- return `${part}${SLUG_KEY_VALUE_SEPARATOR}${formattedEntries}`;
237
- };
238
-
239
- /**
240
- * Converts a LayoutParts object into a URI string.
241
- * @deprecated Keeping these as a reference for now. We should remove these once we're sure we don't need them.
242
- */
243
- export const activePartsToUri = (activeParts: LayoutParts): string => {
244
- return Object.entries(activeParts)
245
- .filter(([, layoutEntries]) => layoutEntries.length > 0) // Only include non-empty parts
246
- .map(([part, layoutEntries]) => formatPartDescriptor(part as LayoutPart, layoutEntries))
247
- .join(SLUG_ENTRY_SEPARATOR);
248
- };
package/src/meta.ts CHANGED
@@ -4,9 +4,10 @@
4
4
 
5
5
  import { type PluginMeta } from '@dxos/app-framework';
6
6
 
7
- export const DECK_PLUGIN = 'dxos.org/plugin/deck';
7
+ export const DECK_PLUGIN = 'dxos.org/plugin/deck' as const;
8
8
 
9
- export default {
9
+ export const meta = {
10
10
  id: DECK_PLUGIN,
11
11
  name: 'Deck',
12
+ icon: 'ph--columns--regular',
12
13
  } satisfies PluginMeta;
@@ -8,9 +8,12 @@ export default [
8
8
  {
9
9
  'en-US': {
10
10
  [DECK_PLUGIN]: {
11
+ 'plugin name': 'Deck',
11
12
  'main header label': 'Main header',
12
- 'open navigation sidebar label': 'Open navigation sidebar',
13
- 'open complementary sidebar label': 'Open sidebar',
13
+ 'open navigation sidebar label': 'Open sidebar',
14
+ 'collapse navigation sidebar label': 'Minimize sidebar',
15
+ 'open complementary sidebar label': 'Open context sidebar',
16
+ 'close complementary sidebar label': 'Minimize context sidebar',
14
17
  'plugin error message': 'Content failed to render.',
15
18
  'content fallback message': 'Unsupported',
16
19
  'content fallback description':
@@ -18,7 +21,6 @@ export default [
18
21
  'toggle fullscreen label': 'Toggle fullscreen',
19
22
  'settings show hints label': 'Show hints',
20
23
  'settings native redirect label': 'Enable native url redirect (experimental)',
21
- 'settings custom slots': 'Theme option (experimental)',
22
24
  'settings new plank position start label': 'Start',
23
25
  'settings new plank position end label': 'End',
24
26
  'select new plank positioning placeholder': 'Select new plank positioning',
@@ -30,7 +32,7 @@ export default [
30
32
  'error fallback message': 'Unable to open this item',
31
33
  'plank heading fallback label': 'Untitled',
32
34
  'actions menu label': 'Options',
33
- 'settings deck label': 'Disable Deck',
35
+ 'settings deck label': 'Disable deck',
34
36
  'reload required message': 'Reload required.',
35
37
  'pending heading': 'Loading…',
36
38
  'insert plank label': 'Open',
@@ -43,11 +45,11 @@ export default [
43
45
  'show solo plank label': 'Maximize',
44
46
  'close label': 'Close',
45
47
  'minify label': 'Minify',
46
- 'settings overscroll label': 'Plank Overscrolling',
48
+ 'settings overscroll label': 'Plank overscrolling',
47
49
  'select overscroll placeholder': 'Select plank overscrolling behavior',
48
50
  'settings overscroll centering label': 'Centering',
49
51
  'settings overscroll none label': 'None',
50
- 'settings flat deck': 'Flatten deck appearance',
52
+ 'settings enable ide-style statusbar label': 'IDE-style statusbar',
51
53
  'close current label': 'Close current plank',
52
54
  'close others label': 'Close other planks',
53
55
  'close all label': 'Close all planks',
package/src/types.ts CHANGED
@@ -2,17 +2,9 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- import type {
6
- Plugin,
7
- GraphBuilderProvides,
8
- IntentResolverProvides,
9
- LayoutProvides,
10
- LocationProvides,
11
- SettingsProvides,
12
- SurfaceProvides,
13
- TranslationsProvides,
14
- } from '@dxos/app-framework';
5
+ import { LayoutAction } from '@dxos/app-framework';
15
6
  import { S } from '@dxos/echo-schema';
7
+ import { type Node } from '@dxos/plugin-graph';
16
8
  import { type Label } from '@dxos/react-ui';
17
9
 
18
10
  import { DECK_PLUGIN } from './meta';
@@ -24,40 +16,116 @@ export type NewPlankPositioning = (typeof NewPlankPositions)[number];
24
16
  export const OverscrollOptions = ['none', 'centering'] as const;
25
17
  export type Overscroll = (typeof OverscrollOptions)[number];
26
18
 
27
- // TODO(wittjosiah): Include a predicate for whether the panel is visible for the current subject.
28
- export type Panel = { id: string; label: Label; icon: string };
29
-
30
- // TODO(wittjosiah): Is this generic enough to be in the app framework?
31
- export type PanelProvides = {
32
- complementary: {
33
- panels: Panel[];
34
- };
19
+ export type Panel = {
20
+ id: string;
21
+ label: Label;
22
+ icon: string;
23
+ filter?: (node: Node) => boolean;
35
24
  };
36
25
 
37
- export const parsePanelPlugin = (plugin?: Plugin) =>
38
- Array.isArray((plugin?.provides as any).complementary?.panels) ? (plugin as Plugin<PanelProvides>) : undefined;
39
-
40
- export type DeckSettingsProps = {
41
- showHints: boolean;
42
- customSlots: boolean;
43
- flatDeck: boolean;
44
- enableNativeRedirect: boolean;
45
- disableDeck: boolean;
46
- newPlankPositioning: NewPlankPositioning;
47
- overscroll: Overscroll;
26
+ export const DeckSettingsSchema = S.mutable(
27
+ S.Struct({
28
+ showHints: S.Boolean,
29
+ enableNativeRedirect: S.Boolean,
30
+ enableIdeStyleStatusbar: S.Boolean,
31
+ newPlankPositioning: S.Literal(...NewPlankPositions),
32
+ overscroll: S.Literal(...OverscrollOptions),
33
+ }),
34
+ );
35
+
36
+ export type DeckSettingsProps = S.Schema.Type<typeof DeckSettingsSchema>;
37
+
38
+ const LayoutMode = S.Union(S.Literal('deck'), S.Literal('solo'), S.Literal('fullscreen'));
39
+ export const isLayoutMode = (value: any): value is LayoutMode => S.is(LayoutMode)(value);
40
+ export type LayoutMode = S.Schema.Type<typeof LayoutMode>;
41
+
42
+ export const PlankSizing = S.Record({ key: S.String, value: S.Number });
43
+ export type PlankSizing = S.Schema.Type<typeof PlankSizing>;
44
+
45
+ export const Deck = S.Struct({
46
+ initialized: S.Boolean.annotations({
47
+ description: "If false, the deck has not yet left solo mode and new planks should be solo'd.",
48
+ }),
49
+ active: S.mutable(S.Array(S.String)),
50
+ inactive: S.mutable(S.Array(S.String)),
51
+ solo: S.optional(S.String),
52
+ fullscreen: S.Boolean,
53
+ plankSizing: S.mutable(PlankSizing),
54
+ });
55
+ export type Deck = S.Schema.Type<typeof Deck>;
56
+
57
+ export const DeckState = S.mutable(
58
+ S.Struct({
59
+ sidebarState: S.Literal('closed', 'collapsed', 'expanded'),
60
+ complementarySidebarState: S.Literal('closed', 'collapsed', 'expanded'),
61
+ complementarySidebarPanel: S.optional(S.String),
62
+
63
+ dialogOpen: S.Boolean,
64
+ /**
65
+ * Data to be passed to the dialog Surface.
66
+ */
67
+ dialogContent: S.optional(S.Any),
68
+ dialogBlockAlign: S.optional(S.Literal('start', 'center', 'end')),
69
+ dialogType: S.optional(S.Literal('default', 'alert')),
70
+
71
+ popoverOpen: S.Boolean,
72
+ popoverSide: S.optional(S.Literal('top', 'right', 'bottom', 'left')),
73
+ /**
74
+ * Data to be passed to the popover Surface.
75
+ */
76
+ popoverContent: S.optional(S.Any),
77
+ popoverAnchorId: S.optional(S.String),
78
+
79
+ toasts: S.mutable(S.Array(LayoutAction.Toast)),
80
+ currentUndoId: S.optional(S.String),
81
+
82
+ activeDeck: S.String,
83
+ previousDeck: S.String,
84
+ decks: S.mutable(S.Record({ key: S.String, value: S.mutable(Deck) })),
85
+ previousMode: S.mutable(S.Record({ key: S.String, value: LayoutMode })),
86
+ deck: S.mutable(Deck),
87
+
88
+ /**
89
+ * The identifier of a component to scroll into view when it is mounted.
90
+ */
91
+ scrollIntoView: S.optional(S.String),
92
+ }),
93
+ );
94
+
95
+ export type DeckState = S.Schema.Type<typeof DeckState>;
96
+
97
+ export const getMode = (deck: Deck): LayoutMode => {
98
+ if (deck.solo) {
99
+ return deck.fullscreen ? 'fullscreen' : 'solo';
100
+ }
101
+
102
+ return 'deck';
48
103
  };
49
104
 
50
- export type DeckPluginProvides = SurfaceProvides &
51
- IntentResolverProvides &
52
- GraphBuilderProvides &
53
- TranslationsProvides &
54
- SettingsProvides<DeckSettingsProps> &
55
- LayoutProvides &
56
- LocationProvides;
105
+ // NOTE: Chosen from RFC 1738’s `safe` characters: http://www.faqs.org/rfcs/rfc1738.html
106
+ export const SLUG_PATH_SEPARATOR = '~';
57
107
 
58
108
  export const DECK_ACTION = `${DECK_PLUGIN}/action`;
59
109
 
60
110
  export namespace DeckAction {
111
+ const PartAdjustmentSchema = S.Union(
112
+ S.Literal('close').annotations({ description: 'Close the plank.' }),
113
+ S.Literal('solo').annotations({ description: 'Solo the plank.' }),
114
+ S.Literal('increment-start').annotations({ description: 'Move the plank towards the start of the deck.' }),
115
+ S.Literal('increment-end').annotations({ description: 'Move the plank towards the end of the deck.' }),
116
+ );
117
+ export type PartAdjustment = S.Schema.Type<typeof PartAdjustmentSchema>;
118
+ export const Adjustment = S.mutable(S.Struct({ id: S.String, type: PartAdjustmentSchema }));
119
+ export type Adjustment = S.Schema.Type<typeof Adjustment>;
120
+
121
+ /**
122
+ * An atomic transaction to apply to the deck, describing which element to move to which location.
123
+ */
124
+ export class Adjust extends S.TaggedClass<Adjust>()(`${DECK_ACTION}/adjust`, {
125
+ input: Adjustment,
126
+ output: S.Void,
127
+ }) {}
128
+
61
129
  export class UpdatePlankSize extends S.TaggedClass<UpdatePlankSize>()(`${DECK_ACTION}/update-plank-size`, {
62
130
  input: S.Struct({
63
131
  id: S.String,
package/src/util/index.ts CHANGED
@@ -2,6 +2,8 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- export * from './check-app-scheme';
6
- export * from './layout-parts';
7
5
  export * from './overscroll';
6
+ export * from './set-active';
7
+ export * from './useBreakpoints';
8
+ export * from './layoutAppliesTopbar';
9
+ export * from './useHoistStatusbar';
@@ -0,0 +1,7 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ export const layoutAppliesTopbar = (breakpoint: string) => {
6
+ return document.body.getAttribute('data-platform') === 'win' && breakpoint === 'desktop';
7
+ };
@@ -0,0 +1,47 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { batch } from '@preact/signals-core';
6
+
7
+ import { type AttentionManager } from '@dxos/plugin-attention';
8
+
9
+ import { type DeckState } from '../types';
10
+
11
+ export type SetActiveOptions = {
12
+ next: string[];
13
+ state: DeckState;
14
+ attention?: AttentionManager;
15
+ };
16
+
17
+ export const setActive = ({ next, state, attention }: SetActiveOptions) => {
18
+ return batch(() => {
19
+ const active = state.deck.solo ? [state.deck.solo] : state.deck.active;
20
+ const removed = active.filter((id) => !next.includes(id));
21
+ const closed = Array.from(new Set([...state.deck.inactive.filter((id) => !next.includes(id)), ...removed]));
22
+
23
+ state.deck.inactive = closed;
24
+
25
+ if (state.deck.solo || !state.deck.initialized) {
26
+ state.deck.solo = next[0];
27
+ } else {
28
+ state.deck.active = next;
29
+ }
30
+
31
+ if (state.deck.fullscreen && !state.deck.solo) {
32
+ state.deck.fullscreen = false;
33
+ }
34
+
35
+ if (attention) {
36
+ const attended = attention.current;
37
+ const [attendedId] = Array.from(attended);
38
+ const isAttendedAvailable = !!attendedId && next.includes(attendedId);
39
+ if (!isAttendedAvailable) {
40
+ const attendedIndex = active.indexOf(attendedId);
41
+ // If outside of bounds, focus on the first/last plank, otherwise focus on the new plank in the same position.
42
+ const index = attendedIndex === -1 ? 0 : attendedIndex >= next.length ? next.length - 1 : attendedIndex;
43
+ return next[index];
44
+ }
45
+ }
46
+ });
47
+ };
@@ -0,0 +1,11 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { useMediaQuery } from '@dxos/react-ui';
6
+
7
+ export const useBreakpoints = () => {
8
+ const [isNotMobile] = useMediaQuery('md');
9
+ const [isDesktop] = useMediaQuery('lg');
10
+ return isDesktop ? 'desktop' : isNotMobile ? 'tablet' : 'mobile';
11
+ };
@@ -0,0 +1,20 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { useMemo } from 'react';
6
+
7
+ import { Capabilities, useCapability } from '@dxos/app-framework';
8
+ import { useThemeContext } from '@dxos/react-ui';
9
+
10
+ import { DECK_PLUGIN } from '../meta';
11
+ import type { DeckSettingsProps } from '../types';
12
+
13
+ export const useHoistStatusbar = (breakpoint: string) => {
14
+ const enableIdeStyleStatusbar = useCapability(Capabilities.SettingsStore).getStore<DeckSettingsProps>(DECK_PLUGIN)!
15
+ .value.enableIdeStyleStatusbar;
16
+ const { safeAreaPadding } = useThemeContext();
17
+ return useMemo(() => {
18
+ return breakpoint === 'desktop' && enableIdeStyleStatusbar && safeAreaPadding?.bottom === 0;
19
+ }, [enableIdeStyleStatusbar, breakpoint, safeAreaPadding?.bottom]);
20
+ };
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../../src/meta.ts"],
4
- "sourcesContent": ["//\n// Copyright 2023 DXOS.org\n//\n\nimport { type PluginMeta } from '@dxos/app-framework';\n\nexport const DECK_PLUGIN = 'dxos.org/plugin/deck';\n\nexport default {\n id: DECK_PLUGIN,\n name: 'Deck',\n} satisfies PluginMeta;\n"],
5
- "mappings": ";AAMO,IAAMA,cAAc;AAE3B,IAAA,eAAe;EACbC,IAAID;EACJE,MAAM;AACR;",
6
- "names": ["DECK_PLUGIN", "id", "name"]
7
- }
@@ -1,37 +0,0 @@
1
- import {
2
- DECK_PLUGIN
3
- } from "./chunk-GVOGPULO.mjs";
4
-
5
- // packages/plugins/plugin-deck/src/types.ts
6
- import { S } from "@dxos/echo-schema";
7
- var NewPlankPositions = [
8
- "start",
9
- "end"
10
- ];
11
- var OverscrollOptions = [
12
- "none",
13
- "centering"
14
- ];
15
- var parsePanelPlugin = (plugin) => Array.isArray(plugin?.provides.complementary?.panels) ? plugin : void 0;
16
- var DECK_ACTION = `${DECK_PLUGIN}/action`;
17
- var DeckAction;
18
- (function(DeckAction2) {
19
- class UpdatePlankSize extends S.TaggedClass()(`${DECK_ACTION}/update-plank-size`, {
20
- input: S.Struct({
21
- id: S.String,
22
- size: S.Number
23
- }),
24
- output: S.Void
25
- }) {
26
- }
27
- DeckAction2.UpdatePlankSize = UpdatePlankSize;
28
- })(DeckAction || (DeckAction = {}));
29
-
30
- export {
31
- NewPlankPositions,
32
- OverscrollOptions,
33
- parsePanelPlugin,
34
- DECK_ACTION,
35
- DeckAction
36
- };
37
- //# sourceMappingURL=chunk-ZC3K6C2W.mjs.map
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../../src/types.ts"],
4
- "sourcesContent": ["//\n// Copyright 2023 DXOS.org\n//\n\nimport type {\n Plugin,\n GraphBuilderProvides,\n IntentResolverProvides,\n LayoutProvides,\n LocationProvides,\n SettingsProvides,\n SurfaceProvides,\n TranslationsProvides,\n} from '@dxos/app-framework';\nimport { S } from '@dxos/echo-schema';\nimport { type Label } from '@dxos/react-ui';\n\nimport { DECK_PLUGIN } from './meta';\n\n// TODO(Zan): In the future we should consider adding new planks adjacent to the attended plank.\nexport const NewPlankPositions = ['start', 'end'] as const;\nexport type NewPlankPositioning = (typeof NewPlankPositions)[number];\n\nexport const OverscrollOptions = ['none', 'centering'] as const;\nexport type Overscroll = (typeof OverscrollOptions)[number];\n\n// TODO(wittjosiah): Include a predicate for whether the panel is visible for the current subject.\nexport type Panel = { id: string; label: Label; icon: string };\n\n// TODO(wittjosiah): Is this generic enough to be in the app framework?\nexport type PanelProvides = {\n complementary: {\n panels: Panel[];\n };\n};\n\nexport const parsePanelPlugin = (plugin?: Plugin) =>\n Array.isArray((plugin?.provides as any).complementary?.panels) ? (plugin as Plugin<PanelProvides>) : undefined;\n\nexport type DeckSettingsProps = {\n showHints: boolean;\n customSlots: boolean;\n flatDeck: boolean;\n enableNativeRedirect: boolean;\n disableDeck: boolean;\n newPlankPositioning: NewPlankPositioning;\n overscroll: Overscroll;\n};\n\nexport type DeckPluginProvides = SurfaceProvides &\n IntentResolverProvides &\n GraphBuilderProvides &\n TranslationsProvides &\n SettingsProvides<DeckSettingsProps> &\n LayoutProvides &\n LocationProvides;\n\nexport const DECK_ACTION = `${DECK_PLUGIN}/action`;\n\nexport namespace DeckAction {\n export class UpdatePlankSize extends S.TaggedClass<UpdatePlankSize>()(`${DECK_ACTION}/update-plank-size`, {\n input: S.Struct({\n id: S.String,\n size: S.Number,\n }),\n output: S.Void,\n }) {}\n}\n"],
5
- "mappings": ";;;;;AAcA,SAASA,SAAS;AAMX,IAAMC,oBAAoB;EAAC;EAAS;;AAGpC,IAAMC,oBAAoB;EAAC;EAAQ;;AAanC,IAAMC,mBAAmB,CAACC,WAC/BC,MAAMC,QAASF,QAAQG,SAAiBC,eAAeC,MAAAA,IAAWL,SAAmCM;AAoBhG,IAAMC,cAAc,GAAGC,WAAAA;;UAEbC,aAAAA;EACR,MAAMC,wBAAwBC,EAAEC,YAAW,EAAoB,GAAGL,WAAAA,sBAAiC;IACxGM,OAAOF,EAAEG,OAAO;MACdC,IAAIJ,EAAEK;MACNC,MAAMN,EAAEO;IACV,CAAA;IACAC,QAAQR,EAAES;EACZ,CAAA,EAAA;EAAI;cANSV,kBAAAA;AAOf,GARiBD,eAAAA,aAAAA,CAAAA,EAAAA;",
6
- "names": ["S", "NewPlankPositions", "OverscrollOptions", "parsePanelPlugin", "plugin", "Array", "isArray", "provides", "complementary", "panels", "undefined", "DECK_ACTION", "DECK_PLUGIN", "DeckAction", "UpdatePlankSize", "S", "TaggedClass", "input", "Struct", "id", "String", "size", "Number", "output", "Void"]
7
- }