@dxos/plugin-deck 0.6.13 → 0.6.14-main.1366248

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 (57) hide show
  1. package/dist/lib/browser/{chunk-YVHGFQQR.mjs → chunk-GVOGPULO.mjs} +1 -1
  2. package/dist/lib/browser/chunk-GVOGPULO.mjs.map +7 -0
  3. package/dist/lib/browser/chunk-NIRHDTX4.mjs +17 -0
  4. package/dist/lib/browser/chunk-NIRHDTX4.mjs.map +7 -0
  5. package/dist/lib/browser/index.mjs +334 -314
  6. package/dist/lib/browser/index.mjs.map +4 -4
  7. package/dist/lib/browser/meta.json +1 -1
  8. package/dist/lib/browser/meta.mjs +1 -1
  9. package/dist/lib/browser/types.mjs +11 -0
  10. package/dist/lib/browser/types.mjs.map +7 -0
  11. package/dist/types/src/DeckPlugin.d.ts.map +1 -1
  12. package/dist/types/src/components/DeckLayout/ActiveNode.d.ts +1 -3
  13. package/dist/types/src/components/DeckLayout/ActiveNode.d.ts.map +1 -1
  14. package/dist/types/src/components/DeckLayout/ComplementarySidebar.d.ts +4 -4
  15. package/dist/types/src/components/DeckLayout/ComplementarySidebar.d.ts.map +1 -1
  16. package/dist/types/src/components/DeckLayout/DeckLayout.d.ts +5 -5
  17. package/dist/types/src/components/DeckLayout/DeckLayout.d.ts.map +1 -1
  18. package/dist/types/src/components/DeckLayout/NodePlankHeading.d.ts +9 -7
  19. package/dist/types/src/components/DeckLayout/NodePlankHeading.d.ts.map +1 -1
  20. package/dist/types/src/components/DeckLayout/Plank.d.ts +2 -2
  21. package/dist/types/src/components/DeckLayout/Plank.d.ts.map +1 -1
  22. package/dist/types/src/components/DeckLayout/PlankError.d.ts +1 -2
  23. package/dist/types/src/components/DeckLayout/PlankError.d.ts.map +1 -1
  24. package/dist/types/src/components/DeckLayout/Sidebar.d.ts +2 -3
  25. package/dist/types/src/components/DeckLayout/Sidebar.d.ts.map +1 -1
  26. package/dist/types/src/components/DeckLayout/StatusBar.d.ts +3 -1
  27. package/dist/types/src/components/DeckLayout/StatusBar.d.ts.map +1 -1
  28. package/dist/types/src/components/DeckLayout/Toast.d.ts.map +1 -1
  29. package/dist/types/src/components/LayoutSettings.d.ts.map +1 -1
  30. package/dist/types/src/hooks/useNode.d.ts.map +1 -1
  31. package/dist/types/src/layout.d.ts.map +1 -1
  32. package/dist/types/src/meta.d.ts.map +1 -1
  33. package/dist/types/src/translations.d.ts +1 -3
  34. package/dist/types/src/translations.d.ts.map +1 -1
  35. package/dist/types/src/types.d.ts +14 -2
  36. package/dist/types/src/types.d.ts.map +1 -1
  37. package/dist/types/src/util/overscroll.d.ts +1 -1
  38. package/dist/types/src/util/overscroll.d.ts.map +1 -1
  39. package/package.json +42 -33
  40. package/src/DeckPlugin.tsx +104 -80
  41. package/src/components/DeckLayout/ActiveNode.tsx +4 -1
  42. package/src/components/DeckLayout/ComplementarySidebar.tsx +59 -28
  43. package/src/components/DeckLayout/DeckLayout.tsx +67 -98
  44. package/src/components/DeckLayout/NodePlankHeading.tsx +130 -127
  45. package/src/components/DeckLayout/Plank.tsx +48 -32
  46. package/src/components/DeckLayout/PlankError.tsx +1 -9
  47. package/src/components/DeckLayout/Sidebar.tsx +7 -8
  48. package/src/components/DeckLayout/StatusBar.tsx +12 -3
  49. package/src/components/DeckLayout/Toast.tsx +3 -3
  50. package/src/components/LayoutSettings.tsx +17 -20
  51. package/src/hooks/useNode.ts +5 -1
  52. package/src/layout.ts +1 -0
  53. package/src/meta.ts +3 -1
  54. package/src/translations.ts +1 -3
  55. package/src/types.ts +16 -1
  56. package/src/util/overscroll.ts +5 -5
  57. package/dist/lib/browser/chunk-YVHGFQQR.mjs.map +0 -7
package/package.json CHANGED
@@ -1,19 +1,24 @@
1
1
  {
2
2
  "name": "@dxos/plugin-deck",
3
- "version": "0.6.13",
3
+ "version": "0.6.14-main.1366248",
4
4
  "description": "DXOS Surface plugin for the main application layout.",
5
5
  "homepage": "https://dxos.org",
6
6
  "bugs": "https://github.com/dxos/dxos/issues",
7
7
  "license": "MIT",
8
8
  "author": "DXOS.org",
9
+ "sideEffects": true,
9
10
  "exports": {
10
11
  ".": {
11
- "browser": "./dist/lib/browser/index.mjs",
12
- "types": "./dist/types/src/index.d.ts"
12
+ "types": "./dist/types/src/index.d.ts",
13
+ "browser": "./dist/lib/browser/index.mjs"
13
14
  },
14
15
  "./meta": {
15
- "browser": "./dist/lib/browser/meta.mjs",
16
- "types": "./dist/types/src/meta.d.ts"
16
+ "types": "./dist/types/src/meta.d.ts",
17
+ "browser": "./dist/lib/browser/meta.mjs"
18
+ },
19
+ "./types": {
20
+ "types": "./dist/types/src/types.d.ts",
21
+ "browser": "./dist/lib/browser/types.mjs"
17
22
  }
18
23
  },
19
24
  "types": "dist/types/src/index.d.ts",
@@ -21,6 +26,9 @@
21
26
  "*": {
22
27
  "meta": [
23
28
  "dist/types/src/meta.d.ts"
29
+ ],
30
+ "types": [
31
+ "dist/types/src/types.d.ts"
24
32
  ]
25
33
  }
26
34
  },
@@ -32,26 +40,27 @@
32
40
  "@fluentui/react-tabster": "^9.19.0",
33
41
  "@preact/signals-core": "^1.6.0",
34
42
  "immer": "^10.1.1",
35
- "@dxos/app-framework": "0.6.13",
36
- "@dxos/async": "0.6.13",
37
- "@dxos/echo-schema": "0.6.13",
38
- "@dxos/debug": "0.6.13",
39
- "@dxos/keyboard": "0.6.13",
40
- "@dxos/local-storage": "0.6.13",
41
- "@dxos/invariant": "0.6.13",
42
- "@dxos/log": "0.6.13",
43
- "@dxos/plugin-attention": "0.6.13",
44
- "@dxos/plugin-client": "0.6.13",
45
- "@dxos/plugin-graph": "0.6.13",
46
- "@dxos/plugin-observability": "0.6.13",
47
- "@dxos/plugin-settings": "0.6.13",
48
- "@dxos/plugin-theme": "0.6.13",
49
- "@dxos/react-client": "0.6.13",
50
- "@dxos/react-ui-attention": "0.6.13",
51
- "@dxos/react-ui-deck": "0.6.13",
52
- "@dxos/util": "0.6.13",
53
- "@dxos/react-ui-mosaic": "0.6.13",
54
- "@dxos/react-ui-text-tooltip": "0.6.13"
43
+ "@dxos/app-framework": "0.6.14-main.1366248",
44
+ "@dxos/debug": "0.6.14-main.1366248",
45
+ "@dxos/echo-schema": "0.6.14-main.1366248",
46
+ "@dxos/async": "0.6.14-main.1366248",
47
+ "@dxos/echo-signals": "0.6.14-main.1366248",
48
+ "@dxos/invariant": "0.6.14-main.1366248",
49
+ "@dxos/keyboard": "0.6.14-main.1366248",
50
+ "@dxos/local-storage": "0.6.14-main.1366248",
51
+ "@dxos/plugin-attention": "0.6.14-main.1366248",
52
+ "@dxos/log": "0.6.14-main.1366248",
53
+ "@dxos/plugin-client": "0.6.14-main.1366248",
54
+ "@dxos/plugin-graph": "0.6.14-main.1366248",
55
+ "@dxos/plugin-observability": "0.6.14-main.1366248",
56
+ "@dxos/plugin-theme": "0.6.14-main.1366248",
57
+ "@dxos/react-client": "0.6.14-main.1366248",
58
+ "@dxos/react-ui-attention": "0.6.14-main.1366248",
59
+ "@dxos/react-ui-deck": "0.6.14-main.1366248",
60
+ "@dxos/react-ui-data": "0.6.14-main.1366248",
61
+ "@dxos/react-ui-text-tooltip": "0.6.14-main.1366248",
62
+ "@dxos/util": "0.6.14-main.1366248",
63
+ "@dxos/react-ui-mosaic": "0.6.14-main.1366248"
55
64
  },
56
65
  "devDependencies": {
57
66
  "@phosphor-icons/react": "^2.1.5",
@@ -59,17 +68,17 @@
59
68
  "@types/react-dom": "~18.2.0",
60
69
  "react": "~18.2.0",
61
70
  "react-dom": "~18.2.0",
62
- "vite": "^5.3.4",
63
- "@dxos/storybook-utils": "0.6.13",
64
- "@dxos/react-ui": "0.6.13",
65
- "@dxos/react-ui-theme": "0.6.13"
71
+ "vite": "5.4.7",
72
+ "@dxos/react-ui": "0.6.14-main.1366248",
73
+ "@dxos/react-ui-theme": "0.6.14-main.1366248",
74
+ "@dxos/storybook-utils": "0.6.14-main.1366248"
66
75
  },
67
76
  "peerDependencies": {
68
77
  "@phosphor-icons/react": "^2.0.5",
69
- "react": "^18.0.0",
70
- "react-dom": "^18.0.0",
71
- "@dxos/react-ui": "0.6.13",
72
- "@dxos/react-ui-theme": "0.6.13"
78
+ "react": "~18.2.0",
79
+ "react-dom": "~18.2.0",
80
+ "@dxos/react-ui": "0.6.14-main.1366248",
81
+ "@dxos/react-ui-theme": "0.6.14-main.1366248"
73
82
  },
74
83
  "publishConfig": {
75
84
  "access": "public"
@@ -2,8 +2,7 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- import { ArrowsOut, type IconProps } from '@phosphor-icons/react';
6
- import { batch, effect } from '@preact/signals-core';
5
+ import { batch } from '@preact/signals-core';
7
6
  import { setAutoFreeze } from 'immer';
8
7
  import React, { type PropsWithChildren } from 'react';
9
8
 
@@ -30,9 +29,11 @@ import {
30
29
  openIds,
31
30
  type LayoutMode,
32
31
  type IntentData,
32
+ filterPlugins,
33
33
  } from '@dxos/app-framework';
34
34
  import { type UnsubscribeCallback } from '@dxos/async';
35
35
  import { create, getTypename, isReactiveObject } from '@dxos/echo-schema';
36
+ import { scheduledEffect } from '@dxos/echo-signals/core';
36
37
  import { LocalStorageStore } from '@dxos/local-storage';
37
38
  import { log } from '@dxos/log';
38
39
  import { parseAttentionPlugin, type AttentionPluginProvides } from '@dxos/plugin-attention';
@@ -41,7 +42,6 @@ import { createExtension, type Node } from '@dxos/plugin-graph';
41
42
  import { ObservabilityAction } from '@dxos/plugin-observability/meta';
42
43
  import { fullyQualifiedId } from '@dxos/react-client/echo';
43
44
  import { translations as deckTranslations } from '@dxos/react-ui-deck';
44
- import { Mosaic } from '@dxos/react-ui-mosaic';
45
45
 
46
46
  import {
47
47
  DeckLayout,
@@ -63,7 +63,14 @@ import {
63
63
  } from './layout';
64
64
  import meta, { DECK_PLUGIN } from './meta';
65
65
  import translations from './translations';
66
- import { type NewPlankPositioning, type DeckPluginProvides, type DeckSettingsProps, type Overscroll } from './types';
66
+ import {
67
+ type NewPlankPositioning,
68
+ type DeckPluginProvides,
69
+ type DeckSettingsProps,
70
+ type Overscroll,
71
+ type Panel,
72
+ parsePanelPlugin,
73
+ } from './types';
67
74
  import { checkAppScheme, getEffectivePart } from './util';
68
75
 
69
76
  const isSocket = !!(globalThis as any).__args;
@@ -71,7 +78,7 @@ const isSocket = !!(globalThis as any).__args;
71
78
  // TODO(mjamesderocher): Can we get this directly from Socket?
72
79
  const appScheme = 'composer://';
73
80
 
74
- // TODO(burdon): Evolve into customizable prefs,.
81
+ // TODO(burdon): Evolve into customizable prefs.
75
82
  const customSlots: DeckLayoutProps['slots'] = {
76
83
  wallpaper: {
77
84
  classNames:
@@ -110,9 +117,10 @@ export const DeckPlugin = ({
110
117
  const unsubscriptionCallbacks = [] as (UnsubscribeCallback | undefined)[];
111
118
  let currentUndoId: string | undefined;
112
119
  let handleNavigation: () => Promise<void> | undefined;
120
+ const panels: Panel[] = [];
113
121
 
114
122
  const settings = new LocalStorageStore<DeckSettingsProps>('dxos.org/settings/layout', {
115
- showFooter: false,
123
+ showHints: false,
116
124
  customSlots: false,
117
125
  flatDeck: false,
118
126
  enableNativeRedirect: false,
@@ -190,6 +198,28 @@ export const DeckPlugin = ({
190
198
  }
191
199
  };
192
200
 
201
+ /**
202
+ * Update the active state and ensure that attention is on an active element.
203
+ */
204
+ const handleSetLocation = (next: LayoutParts) => {
205
+ if (attentionPlugin) {
206
+ const attended = attentionPlugin.provides.attention.attended;
207
+ const [attendedId] = Array.from(attended);
208
+ const ids = (layout.values.layoutMode === 'deck' ? next.main : next.solo)?.map(({ id }) => id) ?? [];
209
+ const isAttendedAvailable = !!attendedId && ids.includes(attendedId);
210
+ if (!isAttendedAvailable) {
211
+ // Allow new plank to render before focusing.
212
+ requestAnimationFrame(() => {
213
+ const nextAttended = layout.values.layoutMode === 'solo' ? next.solo?.[0].id : next.main?.[0]?.id;
214
+ const article = document.querySelector<HTMLElement>(`article[data-attendable-id="${nextAttended}"]`);
215
+ article?.focus();
216
+ });
217
+ }
218
+ }
219
+
220
+ location.values.active = next;
221
+ };
222
+
193
223
  return {
194
224
  meta,
195
225
  ready: async (plugins) => {
@@ -198,19 +228,20 @@ export const DeckPlugin = ({
198
228
  attentionPlugin = resolvePlugin(plugins, parseAttentionPlugin);
199
229
  clientPlugin = resolvePlugin(plugins, parseClientPlugin);
200
230
 
201
- // prettier-ignore
202
231
  layout
203
- .prop({ key: 'layoutMode', storageKey: 'layout-mode', type: LocalStorageStore.enum<LayoutMode>() })
204
- .prop({ key: 'sidebarOpen', storageKey: 'sidebar-open', type: LocalStorageStore.bool() })
205
- .prop({ key: 'complementarySidebarOpen', storageKey: 'complementary-sidebar-open', type: LocalStorageStore.bool() });
232
+ .prop({ key: 'layoutMode', type: LocalStorageStore.enum<LayoutMode>() })
233
+ .prop({ key: 'sidebarOpen', type: LocalStorageStore.bool() })
234
+ .prop({ key: 'complementarySidebarOpen', type: LocalStorageStore.bool() });
206
235
 
207
- // prettier-ignore
208
- deck.prop({ key: 'plankSizing', storageKey: 'plank-sizing', type: LocalStorageStore.json<Record<string, number>>() });
236
+ deck.prop({ key: 'plankSizing', type: LocalStorageStore.json<Record<string, number>>() });
209
237
 
210
- // prettier-ignore
211
238
  location
212
- .prop({ key: 'active', storageKey: 'active', type: LocalStorageStore.json<LayoutParts>() })
213
- .prop({ key: 'closed', storageKey: 'closed', type: LocalStorageStore.json<string[]>() });
239
+ .prop({ key: 'active', type: LocalStorageStore.json<LayoutParts>() })
240
+ .prop({ key: 'closed', type: LocalStorageStore.json<string[]>() });
241
+
242
+ panels.push(
243
+ ...filterPlugins(plugins, parsePanelPlugin).flatMap((plugin) => plugin.provides.complementary.panels),
244
+ );
214
245
 
215
246
  unsubscriptionCallbacks.push(
216
247
  clientPlugin?.provides.client.shell.onReset(() => {
@@ -220,15 +251,14 @@ export const DeckPlugin = ({
220
251
  }),
221
252
  );
222
253
 
223
- // prettier-ignore
224
254
  settings
225
- .prop({ key: 'showFooter', storageKey: 'show-footer', type: LocalStorageStore.bool() })
226
- .prop({ key: 'customSlots', storageKey: 'customSlots', type: LocalStorageStore.bool() })
227
- .prop({ key: 'flatDeck', storageKey: 'flatDeck', type: LocalStorageStore.bool() })
228
- .prop({ key: 'enableNativeRedirect', storageKey: 'enable-native-redirect', type: LocalStorageStore.bool() })
229
- .prop({ key: 'disableDeck', storageKey: 'disable-deck', type: LocalStorageStore.bool() }) // Deprecated.
230
- .prop({ key: 'newPlankPositioning', storageKey: 'newPlankPositioning', type: LocalStorageStore.enum<NewPlankPositioning>() })
231
- .prop({ key: 'overscroll', storageKey: 'overscroll', type: LocalStorageStore.enum<Overscroll>() });
255
+ .prop({ key: 'showHints', type: LocalStorageStore.bool() })
256
+ .prop({ key: 'customSlots', type: LocalStorageStore.bool() })
257
+ .prop({ key: 'flatDeck', type: LocalStorageStore.bool() })
258
+ .prop({ key: 'enableNativeRedirect', type: LocalStorageStore.bool() })
259
+ .prop({ key: 'disableDeck', type: LocalStorageStore.bool() }) // Deprecated.
260
+ .prop({ key: 'newPlankPositioning', type: LocalStorageStore.enum<NewPlankPositioning>() })
261
+ .prop({ key: 'overscroll', type: LocalStorageStore.enum<Overscroll>() });
232
262
 
233
263
  if (!isSocket && settings.values.enableNativeRedirect) {
234
264
  checkAppScheme(appScheme);
@@ -237,7 +267,7 @@ export const DeckPlugin = ({
237
267
  handleNavigation = async () => {
238
268
  const pathname = window.location.pathname;
239
269
  if (pathname === '/reset') {
240
- location.values.active = { sidebar: [{ id: NAV_ID }] };
270
+ handleSetLocation({ sidebar: [{ id: NAV_ID }] });
241
271
  location.values.closed = [];
242
272
  layout.values.layoutMode = 'solo';
243
273
  window.location.pathname = '/';
@@ -250,7 +280,7 @@ export const DeckPlugin = ({
250
280
  }
251
281
 
252
282
  const startingLayout = removePart(location.values.active, 'solo');
253
- location.values.active = mergeLayoutParts(layoutFromUri, startingLayout);
283
+ handleSetLocation(mergeLayoutParts(layoutFromUri, startingLayout));
254
284
  layout.values.layoutMode = 'solo';
255
285
  };
256
286
 
@@ -258,23 +288,13 @@ export const DeckPlugin = ({
258
288
  window.addEventListener('popstate', handleNavigation);
259
289
 
260
290
  unsubscriptionCallbacks.push(
261
- effect(() => {
262
- const selectedPath = soloPartToUri(location.values.active);
263
- // TODO(thure): In some browsers, this only preserves the most recent state change, even though this is not `history.replace`…
264
- history.pushState(null, '', `/${selectedPath}${window.location.search}`);
265
- }),
266
- );
267
-
268
- unsubscriptionCallbacks.push(
269
- effect(() => {
270
- const soloId = location.values.active.solo?.[0].id;
271
- if (layout.values.layoutMode === 'solo' && soloId && layout.values.scrollIntoView !== soloId) {
272
- void intentPlugin?.provides.intent.dispatch({
273
- action: LayoutAction.SCROLL_INTO_VIEW,
274
- data: { id: soloId },
275
- });
276
- }
277
- }),
291
+ scheduledEffect(
292
+ () => ({ selectedPath: soloPartToUri(location.values.active) }),
293
+ ({ selectedPath }) => {
294
+ // TODO(thure): In some browsers, this only preserves the most recent state change, even though this is not `history.replace`…
295
+ history.pushState(null, '', `/${selectedPath}${window.location.search}`);
296
+ },
297
+ ),
278
298
  );
279
299
 
280
300
  layoutModeHistory.values.push(`${layout.values.layoutMode}`);
@@ -310,8 +330,7 @@ export const DeckPlugin = ({
310
330
  },
311
331
  properties: {
312
332
  label: ['toggle fullscreen label', { ns: DECK_PLUGIN }],
313
- icon: (props: IconProps) => <ArrowsOut {...props} />,
314
- iconSymbol: 'ph--arrows-out--regular',
333
+ icon: 'ph--arrows-out--regular',
315
334
  keyBinding: {
316
335
  macos: 'ctrl+meta+f',
317
336
  windows: 'shift+ctrl+f',
@@ -329,31 +348,28 @@ export const DeckPlugin = ({
329
348
  ),
330
349
  root: () => {
331
350
  return (
332
- <Mosaic.Root>
333
- <DeckLayout
334
- attention={attentionPlugin?.provides.attention ?? { attended: new Set() }}
335
- layoutParts={location.values.active}
336
- showHintsFooter={settings.values.showFooter}
337
- overscroll={settings.values.overscroll}
338
- flatDeck={settings.values.flatDeck}
339
- slots={settings.values.customSlots ? customSlots : undefined}
340
- toasts={layout.values.toasts}
341
- onDismissToast={(id) => {
342
- const index = layout.values.toasts.findIndex((toast) => toast.id === id);
343
- if (index !== -1) {
344
- // Allow time for the toast to animate out.
345
- // TODO(burdon): Factor out and unregister timeout.
346
- setTimeout(() => {
347
- if (layout.values.toasts[index].id === currentUndoId) {
348
- currentUndoId = undefined;
349
- }
350
- layout.values.toasts.splice(index, 1);
351
- }, 1_000);
352
- }
353
- }}
354
- />
355
- <Mosaic.DragOverlay />
356
- </Mosaic.Root>
351
+ <DeckLayout
352
+ layoutParts={location.values.active}
353
+ showHints={settings.values.showHints}
354
+ overscroll={settings.values.overscroll}
355
+ flatDeck={settings.values.flatDeck}
356
+ slots={settings.values.customSlots ? customSlots : undefined}
357
+ toasts={layout.values.toasts}
358
+ panels={panels}
359
+ onDismissToast={(id) => {
360
+ const index = layout.values.toasts.findIndex((toast) => toast.id === id);
361
+ if (index !== -1) {
362
+ // Allow time for the toast to animate out.
363
+ // TODO(burdon): Factor out and unregister timeout.
364
+ setTimeout(() => {
365
+ if (layout.values.toasts[index].id === currentUndoId) {
366
+ currentUndoId = undefined;
367
+ }
368
+ layout.values.toasts.splice(index, 1);
369
+ }, 1_000);
370
+ }
371
+ }}
372
+ />
357
373
  );
358
374
  },
359
375
  surface: {
@@ -472,7 +488,7 @@ export const DeckPlugin = ({
472
488
  }
473
489
  });
474
490
 
475
- location.values.active = newLayout;
491
+ handleSetLocation(newLayout);
476
492
  });
477
493
 
478
494
  const ids = openIds(location.values.active);
@@ -522,10 +538,12 @@ export const DeckPlugin = ({
522
538
  const layoutEntry = { id: data.id };
523
539
  const effectivePart = getEffectivePart(data.part, layout.values.layoutMode);
524
540
 
525
- location.values.active = openEntry(location.values.active, effectivePart, layoutEntry, {
526
- positioning: data.positioning ?? settings.values.newPlankPositioning,
527
- pivotId: data.pivotId,
528
- });
541
+ handleSetLocation(
542
+ openEntry(location.values.active, effectivePart, layoutEntry, {
543
+ positioning: data.positioning ?? settings.values.newPlankPositioning,
544
+ pivotId: data.pivotId,
545
+ }),
546
+ );
529
547
 
530
548
  const intents = [];
531
549
  if (data.scrollIntoView && layout.values.layoutMode === 'deck') {
@@ -561,7 +579,11 @@ export const DeckPlugin = ({
561
579
  }
562
580
  });
563
581
 
564
- location.values.active = newLayout;
582
+ handleSetLocation(newLayout);
583
+ // TODO(wittjosiah): This needs to also set the closed state.
584
+ // The closed state should be the existing closed state plus the newly closed ids.
585
+ // The closed state should also be updated when opening entries to remove the id from closed.
586
+ // When SET is called the closed ids should also be calculated and set.
565
587
  return { data: true };
566
588
  });
567
589
  }
@@ -570,7 +592,7 @@ export const DeckPlugin = ({
570
592
  case NavigationAction.SET: {
571
593
  return batch(() => {
572
594
  if (isLayoutParts(intent.data?.activeParts)) {
573
- location.values.active = intent.data!.activeParts;
595
+ handleSetLocation(intent.data!.activeParts);
574
596
  }
575
597
  return { data: true };
576
598
  });
@@ -581,10 +603,12 @@ export const DeckPlugin = ({
581
603
  if (isLayoutAdjustment(intent.data)) {
582
604
  const adjustment = intent.data;
583
605
  if (adjustment.type === 'increment-end' || adjustment.type === 'increment-start') {
584
- location.values.active = incrementPlank(location.values.active, {
585
- type: adjustment.type,
586
- layoutCoordinate: adjustment.layoutCoordinate,
587
- });
606
+ handleSetLocation(
607
+ incrementPlank(location.values.active, {
608
+ type: adjustment.type,
609
+ layoutCoordinate: adjustment.layoutCoordinate,
610
+ }),
611
+ );
588
612
  }
589
613
 
590
614
  if (adjustment.type === 'solo') {
@@ -6,10 +6,13 @@ import React from 'react';
6
6
 
7
7
  import { Surface } from '@dxos/app-framework';
8
8
  import { useGraph } from '@dxos/plugin-graph';
9
+ import { useAttended } from '@dxos/react-ui-attention';
9
10
 
10
11
  import { useNode, useNodeActionExpander } from '../../hooks';
11
12
 
12
- export const ActiveNode = ({ id }: { id?: string }) => {
13
+ // TODO(burdon): Factor out to effect in plugin set document title.
14
+ export const ActiveNode = () => {
15
+ const [id] = useAttended();
13
16
  const { graph } = useGraph();
14
17
  const activeNode = useNode(graph, id);
15
18
  useNodeActionExpander(activeNode);
@@ -2,12 +2,18 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import React from 'react';
5
+ import React, { useMemo } from 'react';
6
6
 
7
- import { type LayoutParts, SLUG_PATH_SEPARATOR, Surface } from '@dxos/app-framework';
7
+ import {
8
+ type LayoutCoordinate,
9
+ NavigationAction,
10
+ SLUG_PATH_SEPARATOR,
11
+ Surface,
12
+ useIntentDispatcher,
13
+ } from '@dxos/app-framework';
8
14
  import { useGraph } from '@dxos/plugin-graph';
9
15
  import { Main } from '@dxos/react-ui';
10
- import { createAttendableAttributes } from '@dxos/react-ui-attention';
16
+ import { useAttended } from '@dxos/react-ui-attention';
11
17
  import { deckGrid } from '@dxos/react-ui-deck';
12
18
  import { mx } from '@dxos/react-ui-theme';
13
19
 
@@ -15,44 +21,69 @@ import { NodePlankHeading } from './NodePlankHeading';
15
21
  import { PlankContentError } from './PlankError';
16
22
  import { PlankLoading } from './PlankLoading';
17
23
  import { useNode, useNodeActionExpander } from '../../hooks';
24
+ import { type Panel } from '../../types';
18
25
  import { useLayout } from '../LayoutContext';
19
26
 
20
27
  export type ComplementarySidebarProps = {
21
- id?: string;
22
- layoutParts: LayoutParts;
28
+ panels: Panel[];
29
+ current?: string;
23
30
  flatDeck?: boolean;
24
31
  };
25
32
 
26
- export const ComplementarySidebar = ({ id, layoutParts, flatDeck }: ComplementarySidebarProps) => {
33
+ export const ComplementarySidebar = ({ panels, current, flatDeck }: ComplementarySidebarProps) => {
27
34
  const { popoverAnchorId } = useLayout();
35
+ const attended = useAttended();
36
+ const panel = (panels.find((p) => p.id === current) ?? panels[0])?.id;
37
+ const id = attended[0] ? `${attended[0]}${SLUG_PATH_SEPARATOR}${panel}` : undefined;
28
38
  const { graph } = useGraph();
29
39
  const node = useNode(graph, id);
30
- // const complementaryAvailable = useMemo(() => id === NAV_ID || !!node, [id, node]);
31
- const complementaryAttrs = createAttendableAttributes(id?.split(SLUG_PATH_SEPARATOR)[0] ?? 'never');
32
-
40
+ const dispatch = useIntentDispatcher();
33
41
  useNodeActionExpander(node);
34
42
 
43
+ const actions = useMemo(
44
+ () =>
45
+ panels.map(({ id, label, icon }) => ({
46
+ id: `complementary-${id}`,
47
+ data: () => {
48
+ void dispatch({ action: NavigationAction.OPEN, data: { activeParts: { complementary: id } } });
49
+ },
50
+ properties: {
51
+ label,
52
+ icon,
53
+ menuItemType: 'toggle',
54
+ isChecked: panel === id,
55
+ },
56
+ })),
57
+ [panel],
58
+ );
59
+
60
+ // TODO(wittjosiah): Ensure that id is always defined.
61
+ const coordinate: LayoutCoordinate = useMemo(() => ({ entryId: id ?? 'unknown', part: 'complementary' }), [id]);
62
+
63
+ // TODO(burdon): Debug panel doesn't change when switching even though id has chagned.
35
64
  return (
36
- <Main.ComplementarySidebar {...complementaryAttrs}>
37
- {node ? (
38
- <div role='none' className={mx(deckGrid, 'grid-cols-1 bs-full')}>
39
- <NodePlankHeading
40
- node={node}
41
- id={id}
42
- layoutParts={layoutParts}
43
- layoutPart='complementary'
44
- popoverAnchorId={popoverAnchorId}
45
- flatDeck={flatDeck}
46
- />
47
- <Surface
48
- role='article'
49
- data={{ subject: node.data, part: 'complementary', popoverAnchorId }}
50
- limit={1}
51
- fallback={PlankContentError}
52
- placeholder={<PlankLoading />}
53
- />
65
+ <Main.ComplementarySidebar>
66
+ <div role='none' className={mx(deckGrid, 'grid-cols-1 bs-full')}>
67
+ <NodePlankHeading
68
+ coordinate={coordinate}
69
+ node={node}
70
+ popoverAnchorId={popoverAnchorId}
71
+ flatDeck={flatDeck}
72
+ actions={actions}
73
+ />
74
+ <div className='row-span-2 divide-y divide-separator overflow-x-hidden overflow-y-scroll'>
75
+ {node && (
76
+ <Surface
77
+ key={id}
78
+ role={`complementary--${panel}`}
79
+ limit={1}
80
+ data={{ id, subject: node.properties.object ?? node.properties.space, popoverAnchorId }}
81
+ fallback={PlankContentError}
82
+ placeholder={<PlankLoading />}
83
+ />
84
+ )}
54
85
  </div>
55
- ) : null}
86
+ </div>
56
87
  </Main.ComplementarySidebar>
57
88
  );
58
89
  };