@dxos/plugin-deck 0.6.13-main.ed424a1 → 0.6.13

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 (43) hide show
  1. package/dist/lib/browser/{chunk-GVOGPULO.mjs → chunk-YVHGFQQR.mjs} +1 -1
  2. package/dist/lib/browser/chunk-YVHGFQQR.mjs.map +7 -0
  3. package/dist/lib/browser/index.mjs +184 -239
  4. package/dist/lib/browser/index.mjs.map +3 -3
  5. package/dist/lib/browser/meta.json +1 -1
  6. package/dist/lib/browser/meta.mjs +1 -1
  7. package/dist/types/src/DeckPlugin.d.ts.map +1 -1
  8. package/dist/types/src/components/DeckLayout/ComplementarySidebar.d.ts +2 -2
  9. package/dist/types/src/components/DeckLayout/ComplementarySidebar.d.ts.map +1 -1
  10. package/dist/types/src/components/DeckLayout/DeckLayout.d.ts +4 -3
  11. package/dist/types/src/components/DeckLayout/DeckLayout.d.ts.map +1 -1
  12. package/dist/types/src/components/DeckLayout/NodePlankHeading.d.ts +3 -4
  13. package/dist/types/src/components/DeckLayout/NodePlankHeading.d.ts.map +1 -1
  14. package/dist/types/src/components/DeckLayout/Sidebar.d.ts +3 -2
  15. package/dist/types/src/components/DeckLayout/Sidebar.d.ts.map +1 -1
  16. package/dist/types/src/components/DeckLayout/StatusBar.d.ts +1 -3
  17. package/dist/types/src/components/DeckLayout/StatusBar.d.ts.map +1 -1
  18. package/dist/types/src/components/LayoutSettings.d.ts.map +1 -1
  19. package/dist/types/src/hooks/useNode.d.ts.map +1 -1
  20. package/dist/types/src/layout.d.ts.map +1 -1
  21. package/dist/types/src/meta.d.ts.map +1 -1
  22. package/dist/types/src/translations.d.ts +1 -3
  23. package/dist/types/src/translations.d.ts.map +1 -1
  24. package/dist/types/src/types.d.ts +1 -1
  25. package/dist/types/src/types.d.ts.map +1 -1
  26. package/dist/types/src/util/overscroll.d.ts +1 -1
  27. package/dist/types/src/util/overscroll.d.ts.map +1 -1
  28. package/package.json +29 -30
  29. package/src/DeckPlugin.tsx +71 -93
  30. package/src/components/DeckLayout/ComplementarySidebar.tsx +27 -72
  31. package/src/components/DeckLayout/DeckLayout.tsx +84 -63
  32. package/src/components/DeckLayout/NodePlankHeading.tsx +15 -15
  33. package/src/components/DeckLayout/Plank.tsx +3 -3
  34. package/src/components/DeckLayout/Sidebar.tsx +5 -6
  35. package/src/components/DeckLayout/StatusBar.tsx +3 -12
  36. package/src/components/LayoutSettings.tsx +8 -5
  37. package/src/hooks/useNode.ts +1 -5
  38. package/src/layout.ts +0 -1
  39. package/src/meta.ts +1 -3
  40. package/src/translations.ts +1 -3
  41. package/src/types.ts +1 -1
  42. package/src/util/overscroll.ts +5 -5
  43. package/dist/lib/browser/chunk-GVOGPULO.mjs.map +0 -7
@@ -2,7 +2,8 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- import { batch } from '@preact/signals-core';
5
+ import { ArrowsOut, type IconProps } from '@phosphor-icons/react';
6
+ import { batch, effect } from '@preact/signals-core';
6
7
  import { setAutoFreeze } from 'immer';
7
8
  import React, { type PropsWithChildren } from 'react';
8
9
 
@@ -32,7 +33,6 @@ import {
32
33
  } from '@dxos/app-framework';
33
34
  import { type UnsubscribeCallback } from '@dxos/async';
34
35
  import { create, getTypename, isReactiveObject } from '@dxos/echo-schema';
35
- import { scheduledEffect } from '@dxos/echo-signals/core';
36
36
  import { LocalStorageStore } from '@dxos/local-storage';
37
37
  import { log } from '@dxos/log';
38
38
  import { parseAttentionPlugin, type AttentionPluginProvides } from '@dxos/plugin-attention';
@@ -41,6 +41,7 @@ import { createExtension, type Node } from '@dxos/plugin-graph';
41
41
  import { ObservabilityAction } from '@dxos/plugin-observability/meta';
42
42
  import { fullyQualifiedId } from '@dxos/react-client/echo';
43
43
  import { translations as deckTranslations } from '@dxos/react-ui-deck';
44
+ import { Mosaic } from '@dxos/react-ui-mosaic';
44
45
 
45
46
  import {
46
47
  DeckLayout,
@@ -70,7 +71,7 @@ const isSocket = !!(globalThis as any).__args;
70
71
  // TODO(mjamesderocher): Can we get this directly from Socket?
71
72
  const appScheme = 'composer://';
72
73
 
73
- // TODO(burdon): Evolve into customizable prefs.
74
+ // TODO(burdon): Evolve into customizable prefs,.
74
75
  const customSlots: DeckLayoutProps['slots'] = {
75
76
  wallpaper: {
76
77
  classNames:
@@ -111,7 +112,7 @@ export const DeckPlugin = ({
111
112
  let handleNavigation: () => Promise<void> | undefined;
112
113
 
113
114
  const settings = new LocalStorageStore<DeckSettingsProps>('dxos.org/settings/layout', {
114
- showHints: true,
115
+ showFooter: false,
115
116
  customSlots: false,
116
117
  flatDeck: false,
117
118
  enableNativeRedirect: false,
@@ -189,28 +190,6 @@ export const DeckPlugin = ({
189
190
  }
190
191
  };
191
192
 
192
- /**
193
- * Update the active state and ensure that attention is on an active element.
194
- */
195
- const handleSetLocation = (next: LayoutParts) => {
196
- if (attentionPlugin) {
197
- const attended = attentionPlugin.provides.attention.attended;
198
- const [attendedId] = Array.from(attended);
199
- const ids = (layout.values.layoutMode === 'deck' ? next.main : next.solo)?.map(({ id }) => id) ?? [];
200
- const isAttendedAvailable = !!attendedId && ids.includes(attendedId);
201
- if (!isAttendedAvailable) {
202
- // Allow new plank to render before focusing.
203
- requestAnimationFrame(() => {
204
- const nextAttended = layout.values.layoutMode === 'solo' ? next.solo?.[0].id : next.main?.[0]?.id;
205
- const article = document.querySelector<HTMLElement>(`article[data-attendable-id="${nextAttended}"]`);
206
- article?.focus();
207
- });
208
- }
209
- }
210
-
211
- location.values.active = next;
212
- };
213
-
214
193
  return {
215
194
  meta,
216
195
  ready: async (plugins) => {
@@ -219,21 +198,16 @@ export const DeckPlugin = ({
219
198
  attentionPlugin = resolvePlugin(plugins, parseAttentionPlugin);
220
199
  clientPlugin = resolvePlugin(plugins, parseClientPlugin);
221
200
 
201
+ // prettier-ignore
222
202
  layout
223
203
  .prop({ key: 'layoutMode', storageKey: 'layout-mode', type: LocalStorageStore.enum<LayoutMode>() })
224
204
  .prop({ key: 'sidebarOpen', storageKey: 'sidebar-open', type: LocalStorageStore.bool() })
225
- .prop({
226
- key: 'complementarySidebarOpen',
227
- storageKey: 'complementary-sidebar-open',
228
- type: LocalStorageStore.bool(),
229
- });
230
-
231
- deck.prop({
232
- key: 'plankSizing',
233
- storageKey: 'plank-sizing',
234
- type: LocalStorageStore.json<Record<string, number>>(),
235
- });
205
+ .prop({ key: 'complementarySidebarOpen', storageKey: 'complementary-sidebar-open', type: LocalStorageStore.bool() });
236
206
 
207
+ // prettier-ignore
208
+ deck.prop({ key: 'plankSizing', storageKey: 'plank-sizing', type: LocalStorageStore.json<Record<string, number>>() });
209
+
210
+ // prettier-ignore
237
211
  location
238
212
  .prop({ key: 'active', storageKey: 'active', type: LocalStorageStore.json<LayoutParts>() })
239
213
  .prop({ key: 'closed', storageKey: 'closed', type: LocalStorageStore.json<string[]>() });
@@ -246,17 +220,14 @@ export const DeckPlugin = ({
246
220
  }),
247
221
  );
248
222
 
223
+ // prettier-ignore
249
224
  settings
250
- .prop({ key: 'showHints', storageKey: 'show-hints', type: LocalStorageStore.bool() })
225
+ .prop({ key: 'showFooter', storageKey: 'show-footer', type: LocalStorageStore.bool() })
251
226
  .prop({ key: 'customSlots', storageKey: 'customSlots', type: LocalStorageStore.bool() })
252
227
  .prop({ key: 'flatDeck', storageKey: 'flatDeck', type: LocalStorageStore.bool() })
253
228
  .prop({ key: 'enableNativeRedirect', storageKey: 'enable-native-redirect', type: LocalStorageStore.bool() })
254
229
  .prop({ key: 'disableDeck', storageKey: 'disable-deck', type: LocalStorageStore.bool() }) // Deprecated.
255
- .prop({
256
- key: 'newPlankPositioning',
257
- storageKey: 'newPlankPositioning',
258
- type: LocalStorageStore.enum<NewPlankPositioning>(),
259
- })
230
+ .prop({ key: 'newPlankPositioning', storageKey: 'newPlankPositioning', type: LocalStorageStore.enum<NewPlankPositioning>() })
260
231
  .prop({ key: 'overscroll', storageKey: 'overscroll', type: LocalStorageStore.enum<Overscroll>() });
261
232
 
262
233
  if (!isSocket && settings.values.enableNativeRedirect) {
@@ -266,7 +237,7 @@ export const DeckPlugin = ({
266
237
  handleNavigation = async () => {
267
238
  const pathname = window.location.pathname;
268
239
  if (pathname === '/reset') {
269
- handleSetLocation({ sidebar: [{ id: NAV_ID }] });
240
+ location.values.active = { sidebar: [{ id: NAV_ID }] };
270
241
  location.values.closed = [];
271
242
  layout.values.layoutMode = 'solo';
272
243
  window.location.pathname = '/';
@@ -279,7 +250,7 @@ export const DeckPlugin = ({
279
250
  }
280
251
 
281
252
  const startingLayout = removePart(location.values.active, 'solo');
282
- handleSetLocation(mergeLayoutParts(layoutFromUri, startingLayout));
253
+ location.values.active = mergeLayoutParts(layoutFromUri, startingLayout);
283
254
  layout.values.layoutMode = 'solo';
284
255
  };
285
256
 
@@ -287,13 +258,23 @@ export const DeckPlugin = ({
287
258
  window.addEventListener('popstate', handleNavigation);
288
259
 
289
260
  unsubscriptionCallbacks.push(
290
- scheduledEffect(
291
- () => ({ selectedPath: soloPartToUri(location.values.active) }),
292
- ({ selectedPath }) => {
293
- // TODO(thure): In some browsers, this only preserves the most recent state change, even though this is not `history.replace`…
294
- history.pushState(null, '', `/${selectedPath}${window.location.search}`);
295
- },
296
- ),
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
+ }),
297
278
  );
298
279
 
299
280
  layoutModeHistory.values.push(`${layout.values.layoutMode}`);
@@ -329,7 +310,8 @@ export const DeckPlugin = ({
329
310
  },
330
311
  properties: {
331
312
  label: ['toggle fullscreen label', { ns: DECK_PLUGIN }],
332
- icon: 'ph--arrows-out--regular',
313
+ icon: (props: IconProps) => <ArrowsOut {...props} />,
314
+ iconSymbol: 'ph--arrows-out--regular',
333
315
  keyBinding: {
334
316
  macos: 'ctrl+meta+f',
335
317
  windows: 'shift+ctrl+f',
@@ -347,27 +329,31 @@ export const DeckPlugin = ({
347
329
  ),
348
330
  root: () => {
349
331
  return (
350
- <DeckLayout
351
- layoutParts={location.values.active}
352
- showHints={settings.values.showHints}
353
- overscroll={settings.values.overscroll}
354
- flatDeck={settings.values.flatDeck}
355
- slots={settings.values.customSlots ? customSlots : undefined}
356
- toasts={layout.values.toasts}
357
- onDismissToast={(id) => {
358
- const index = layout.values.toasts.findIndex((toast) => toast.id === id);
359
- if (index !== -1) {
360
- // Allow time for the toast to animate out.
361
- // TODO(burdon): Factor out and unregister timeout.
362
- setTimeout(() => {
363
- if (layout.values.toasts[index].id === currentUndoId) {
364
- currentUndoId = undefined;
365
- }
366
- layout.values.toasts.splice(index, 1);
367
- }, 1_000);
368
- }
369
- }}
370
- />
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>
371
357
  );
372
358
  },
373
359
  surface: {
@@ -486,7 +472,7 @@ export const DeckPlugin = ({
486
472
  }
487
473
  });
488
474
 
489
- handleSetLocation(newLayout);
475
+ location.values.active = newLayout;
490
476
  });
491
477
 
492
478
  const ids = openIds(location.values.active);
@@ -536,12 +522,10 @@ export const DeckPlugin = ({
536
522
  const layoutEntry = { id: data.id };
537
523
  const effectivePart = getEffectivePart(data.part, layout.values.layoutMode);
538
524
 
539
- handleSetLocation(
540
- openEntry(location.values.active, effectivePart, layoutEntry, {
541
- positioning: data.positioning ?? settings.values.newPlankPositioning,
542
- pivotId: data.pivotId,
543
- }),
544
- );
525
+ location.values.active = openEntry(location.values.active, effectivePart, layoutEntry, {
526
+ positioning: data.positioning ?? settings.values.newPlankPositioning,
527
+ pivotId: data.pivotId,
528
+ });
545
529
 
546
530
  const intents = [];
547
531
  if (data.scrollIntoView && layout.values.layoutMode === 'deck') {
@@ -577,11 +561,7 @@ export const DeckPlugin = ({
577
561
  }
578
562
  });
579
563
 
580
- handleSetLocation(newLayout);
581
- // TODO(wittjosiah): This needs to also set the closed state.
582
- // The closed state should be the existing closed state plus the newly closed ids.
583
- // The closed state should also be updated when opening entries to remove the id from closed.
584
- // When SET is called the closed ids should also be calculated and set.
564
+ location.values.active = newLayout;
585
565
  return { data: true };
586
566
  });
587
567
  }
@@ -590,7 +570,7 @@ export const DeckPlugin = ({
590
570
  case NavigationAction.SET: {
591
571
  return batch(() => {
592
572
  if (isLayoutParts(intent.data?.activeParts)) {
593
- handleSetLocation(intent.data!.activeParts);
573
+ location.values.active = intent.data!.activeParts;
594
574
  }
595
575
  return { data: true };
596
576
  });
@@ -601,12 +581,10 @@ export const DeckPlugin = ({
601
581
  if (isLayoutAdjustment(intent.data)) {
602
582
  const adjustment = intent.data;
603
583
  if (adjustment.type === 'increment-end' || adjustment.type === 'increment-start') {
604
- handleSetLocation(
605
- incrementPlank(location.values.active, {
606
- type: adjustment.type,
607
- layoutCoordinate: adjustment.layoutCoordinate,
608
- }),
609
- );
584
+ location.values.active = incrementPlank(location.values.active, {
585
+ type: adjustment.type,
586
+ layoutCoordinate: adjustment.layoutCoordinate,
587
+ });
610
588
  }
611
589
 
612
590
  if (adjustment.type === 'solo') {
@@ -2,18 +2,12 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import React, { useMemo } from 'react';
5
+ import React from 'react';
6
6
 
7
- import {
8
- type LayoutParts,
9
- NavigationAction,
10
- SLUG_PATH_SEPARATOR,
11
- Surface,
12
- useIntentDispatcher,
13
- } from '@dxos/app-framework';
7
+ import { type LayoutParts, SLUG_PATH_SEPARATOR, Surface } from '@dxos/app-framework';
14
8
  import { useGraph } from '@dxos/plugin-graph';
15
9
  import { Main } from '@dxos/react-ui';
16
- import { useAttended } from '@dxos/react-ui-attention';
10
+ import { createAttendableAttributes } from '@dxos/react-ui-attention';
17
11
  import { deckGrid } from '@dxos/react-ui-deck';
18
12
  import { mx } from '@dxos/react-ui-theme';
19
13
 
@@ -21,83 +15,44 @@ import { NodePlankHeading } from './NodePlankHeading';
21
15
  import { PlankContentError } from './PlankError';
22
16
  import { PlankLoading } from './PlankLoading';
23
17
  import { useNode, useNodeActionExpander } from '../../hooks';
24
- import { DECK_PLUGIN } from '../../meta';
25
18
  import { useLayout } from '../LayoutContext';
26
19
 
27
20
  export type ComplementarySidebarProps = {
28
- context?: string;
21
+ id?: string;
29
22
  layoutParts: LayoutParts;
30
23
  flatDeck?: boolean;
31
24
  };
32
25
 
33
- const panels = ['comments', 'settings', 'debug'] as const;
34
-
35
- const nodes = [
36
- { id: 'comments', icon: 'ph--chat-text--regular' },
37
- { id: 'settings', icon: 'ph--gear--regular' },
38
- { id: 'debug', icon: 'ph--bug--regular' },
39
- ];
40
-
41
- type Panel = (typeof panels)[number];
42
- const getPanel = (part?: string): Panel => {
43
- if (part && panels.findIndex((panel) => panel === part) !== -1) {
44
- return part as Panel;
45
- } else {
46
- return 'settings';
47
- }
48
- };
49
-
50
- export const ComplementarySidebar = ({ layoutParts, flatDeck }: ComplementarySidebarProps) => {
26
+ export const ComplementarySidebar = ({ id, layoutParts, flatDeck }: ComplementarySidebarProps) => {
51
27
  const { popoverAnchorId } = useLayout();
52
- const attended = useAttended();
53
- const part = getPanel(layoutParts.complementary?.[0].id);
54
- const id = attended[0] ? `${attended[0]}${SLUG_PATH_SEPARATOR}${part}` : undefined;
55
28
  const { graph } = useGraph();
56
29
  const node = useNode(graph, id);
57
- const dispatch = useIntentDispatcher();
58
- useNodeActionExpander(node);
30
+ // const complementaryAvailable = useMemo(() => id === NAV_ID || !!node, [id, node]);
31
+ const complementaryAttrs = createAttendableAttributes(id?.split(SLUG_PATH_SEPARATOR)[0] ?? 'never');
59
32
 
60
- const actions = useMemo(
61
- () =>
62
- nodes.map(({ id, icon }) => ({
63
- id: `complementary-${id}`,
64
- data: () => {
65
- void dispatch({ action: NavigationAction.OPEN, data: { activeParts: { complementary: id } } });
66
- },
67
- properties: {
68
- label: [`${id} label`, { ns: DECK_PLUGIN }],
69
- icon,
70
- menuItemType: 'toggle',
71
- isChecked: part === id,
72
- },
73
- })),
74
- [part],
75
- );
33
+ useNodeActionExpander(node);
76
34
 
77
35
  return (
78
- <Main.ComplementarySidebar>
79
- <div role='none' className={mx(deckGrid, 'grid-cols-1 bs-full')}>
80
- <NodePlankHeading
81
- node={node}
82
- id={id}
83
- layoutParts={layoutParts}
84
- layoutPart='complementary'
85
- popoverAnchorId={popoverAnchorId}
86
- flatDeck={flatDeck}
87
- actions={actions}
88
- />
89
- {/* TODO(wittjosiah): Render some placeholder when node is undefined. */}
90
- <div className='row-span-2'>
91
- {node && (
92
- <Surface
93
- role={`complementary--${part}`}
94
- data={{ subject: node.properties.object, popoverAnchorId }}
95
- fallback={PlankContentError}
96
- placeholder={<PlankLoading />}
97
- />
98
- )}
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
+ />
99
54
  </div>
100
- </div>
55
+ ) : null}
101
56
  </Main.ComplementarySidebar>
102
57
  );
103
58
  };