@dxos/plugin-deck 0.6.11 → 0.6.12-main.2d19bf1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/browser/{chunk-YVHGFQQR.mjs → chunk-GVOGPULO.mjs} +1 -1
- package/dist/lib/browser/chunk-GVOGPULO.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +220 -148
- package/dist/lib/browser/index.mjs.map +3 -3
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/meta.mjs +1 -1
- package/dist/types/src/DeckPlugin.d.ts.map +1 -1
- package/dist/types/src/components/DeckLayout/ComplementarySidebar.d.ts +2 -2
- package/dist/types/src/components/DeckLayout/ComplementarySidebar.d.ts.map +1 -1
- package/dist/types/src/components/DeckLayout/DeckLayout.d.ts +3 -4
- package/dist/types/src/components/DeckLayout/DeckLayout.d.ts.map +1 -1
- package/dist/types/src/components/DeckLayout/NodePlankHeading.d.ts +4 -3
- package/dist/types/src/components/DeckLayout/NodePlankHeading.d.ts.map +1 -1
- package/dist/types/src/components/DeckLayout/Sidebar.d.ts +2 -3
- package/dist/types/src/components/DeckLayout/Sidebar.d.ts.map +1 -1
- package/dist/types/src/components/DeckLayout/StatusBar.d.ts +3 -1
- package/dist/types/src/components/DeckLayout/StatusBar.d.ts.map +1 -1
- package/dist/types/src/components/LayoutSettings.d.ts.map +1 -1
- package/dist/types/src/hooks/useNode.d.ts.map +1 -1
- package/dist/types/src/layout.d.ts.map +1 -1
- package/dist/types/src/meta.d.ts.map +1 -1
- package/dist/types/src/translations.d.ts +3 -1
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/src/types.d.ts +1 -1
- package/dist/types/src/types.d.ts.map +1 -1
- package/package.json +29 -29
- package/src/DeckPlugin.tsx +81 -53
- package/src/components/DeckLayout/ComplementarySidebar.tsx +73 -22
- package/src/components/DeckLayout/DeckLayout.tsx +42 -60
- package/src/components/DeckLayout/NodePlankHeading.tsx +15 -15
- package/src/components/DeckLayout/Plank.tsx +2 -2
- package/src/components/DeckLayout/Sidebar.tsx +6 -5
- package/src/components/DeckLayout/StatusBar.tsx +10 -2
- package/src/components/LayoutSettings.tsx +5 -8
- package/src/hooks/useNode.ts +5 -1
- package/src/layout.ts +1 -0
- package/src/meta.ts +3 -1
- package/src/translations.ts +3 -1
- package/src/types.ts +1 -1
- package/dist/lib/browser/chunk-YVHGFQQR.mjs.map +0 -7
package/src/DeckPlugin.tsx
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { ArrowsOut, type IconProps } from '@phosphor-icons/react';
|
|
6
5
|
import { batch, effect } from '@preact/signals-core';
|
|
7
6
|
import { setAutoFreeze } from 'immer';
|
|
8
7
|
import React, { type PropsWithChildren } from 'react';
|
|
@@ -41,7 +40,6 @@ import { createExtension, type Node } from '@dxos/plugin-graph';
|
|
|
41
40
|
import { ObservabilityAction } from '@dxos/plugin-observability/meta';
|
|
42
41
|
import { fullyQualifiedId } from '@dxos/react-client/echo';
|
|
43
42
|
import { translations as deckTranslations } from '@dxos/react-ui-deck';
|
|
44
|
-
import { Mosaic } from '@dxos/react-ui-mosaic';
|
|
45
43
|
|
|
46
44
|
import {
|
|
47
45
|
DeckLayout,
|
|
@@ -71,7 +69,7 @@ const isSocket = !!(globalThis as any).__args;
|
|
|
71
69
|
// TODO(mjamesderocher): Can we get this directly from Socket?
|
|
72
70
|
const appScheme = 'composer://';
|
|
73
71
|
|
|
74
|
-
// TODO(burdon): Evolve into customizable prefs
|
|
72
|
+
// TODO(burdon): Evolve into customizable prefs.
|
|
75
73
|
const customSlots: DeckLayoutProps['slots'] = {
|
|
76
74
|
wallpaper: {
|
|
77
75
|
classNames:
|
|
@@ -112,7 +110,7 @@ export const DeckPlugin = ({
|
|
|
112
110
|
let handleNavigation: () => Promise<void> | undefined;
|
|
113
111
|
|
|
114
112
|
const settings = new LocalStorageStore<DeckSettingsProps>('dxos.org/settings/layout', {
|
|
115
|
-
|
|
113
|
+
showHints: true,
|
|
116
114
|
customSlots: false,
|
|
117
115
|
flatDeck: false,
|
|
118
116
|
enableNativeRedirect: false,
|
|
@@ -190,6 +188,25 @@ export const DeckPlugin = ({
|
|
|
190
188
|
}
|
|
191
189
|
};
|
|
192
190
|
|
|
191
|
+
/**
|
|
192
|
+
* Update the active state and ensure that attention is on an active element.
|
|
193
|
+
*/
|
|
194
|
+
const handleSetLocation = (next: LayoutParts) => {
|
|
195
|
+
if (attentionPlugin) {
|
|
196
|
+
const attended = attentionPlugin.provides.attention.attended;
|
|
197
|
+
const [attendedId] = Array.from(attended);
|
|
198
|
+
const ids = (layout.values.layoutMode === 'deck' ? next.main : next.solo)?.map(({ id }) => id) ?? [];
|
|
199
|
+
const isAttendedAvailable = !!attendedId && ids.includes(attendedId);
|
|
200
|
+
if (!isAttendedAvailable) {
|
|
201
|
+
const nextAttended = next.main?.[0]?.id;
|
|
202
|
+
const article = document.querySelector<HTMLElement>(`article[data-attendable-id="${nextAttended}"]`);
|
|
203
|
+
article?.focus();
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
location.values.active = next;
|
|
208
|
+
};
|
|
209
|
+
|
|
193
210
|
return {
|
|
194
211
|
meta,
|
|
195
212
|
ready: async (plugins) => {
|
|
@@ -198,16 +215,21 @@ export const DeckPlugin = ({
|
|
|
198
215
|
attentionPlugin = resolvePlugin(plugins, parseAttentionPlugin);
|
|
199
216
|
clientPlugin = resolvePlugin(plugins, parseClientPlugin);
|
|
200
217
|
|
|
201
|
-
// prettier-ignore
|
|
202
218
|
layout
|
|
203
219
|
.prop({ key: 'layoutMode', storageKey: 'layout-mode', type: LocalStorageStore.enum<LayoutMode>() })
|
|
204
220
|
.prop({ key: 'sidebarOpen', storageKey: 'sidebar-open', type: LocalStorageStore.bool() })
|
|
205
|
-
.prop({
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
221
|
+
.prop({
|
|
222
|
+
key: 'complementarySidebarOpen',
|
|
223
|
+
storageKey: 'complementary-sidebar-open',
|
|
224
|
+
type: LocalStorageStore.bool(),
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
deck.prop({
|
|
228
|
+
key: 'plankSizing',
|
|
229
|
+
storageKey: 'plank-sizing',
|
|
230
|
+
type: LocalStorageStore.json<Record<string, number>>(),
|
|
231
|
+
});
|
|
209
232
|
|
|
210
|
-
// prettier-ignore
|
|
211
233
|
location
|
|
212
234
|
.prop({ key: 'active', storageKey: 'active', type: LocalStorageStore.json<LayoutParts>() })
|
|
213
235
|
.prop({ key: 'closed', storageKey: 'closed', type: LocalStorageStore.json<string[]>() });
|
|
@@ -220,14 +242,17 @@ export const DeckPlugin = ({
|
|
|
220
242
|
}),
|
|
221
243
|
);
|
|
222
244
|
|
|
223
|
-
// prettier-ignore
|
|
224
245
|
settings
|
|
225
|
-
.prop({ key: '
|
|
246
|
+
.prop({ key: 'showHints', storageKey: 'show-hints', type: LocalStorageStore.bool() })
|
|
226
247
|
.prop({ key: 'customSlots', storageKey: 'customSlots', type: LocalStorageStore.bool() })
|
|
227
248
|
.prop({ key: 'flatDeck', storageKey: 'flatDeck', type: LocalStorageStore.bool() })
|
|
228
249
|
.prop({ key: 'enableNativeRedirect', storageKey: 'enable-native-redirect', type: LocalStorageStore.bool() })
|
|
229
250
|
.prop({ key: 'disableDeck', storageKey: 'disable-deck', type: LocalStorageStore.bool() }) // Deprecated.
|
|
230
|
-
.prop({
|
|
251
|
+
.prop({
|
|
252
|
+
key: 'newPlankPositioning',
|
|
253
|
+
storageKey: 'newPlankPositioning',
|
|
254
|
+
type: LocalStorageStore.enum<NewPlankPositioning>(),
|
|
255
|
+
})
|
|
231
256
|
.prop({ key: 'overscroll', storageKey: 'overscroll', type: LocalStorageStore.enum<Overscroll>() });
|
|
232
257
|
|
|
233
258
|
if (!isSocket && settings.values.enableNativeRedirect) {
|
|
@@ -237,7 +262,7 @@ export const DeckPlugin = ({
|
|
|
237
262
|
handleNavigation = async () => {
|
|
238
263
|
const pathname = window.location.pathname;
|
|
239
264
|
if (pathname === '/reset') {
|
|
240
|
-
|
|
265
|
+
handleSetLocation({ sidebar: [{ id: NAV_ID }] });
|
|
241
266
|
location.values.closed = [];
|
|
242
267
|
layout.values.layoutMode = 'solo';
|
|
243
268
|
window.location.pathname = '/';
|
|
@@ -250,7 +275,7 @@ export const DeckPlugin = ({
|
|
|
250
275
|
}
|
|
251
276
|
|
|
252
277
|
const startingLayout = removePart(location.values.active, 'solo');
|
|
253
|
-
|
|
278
|
+
handleSetLocation(mergeLayoutParts(layoutFromUri, startingLayout));
|
|
254
279
|
layout.values.layoutMode = 'solo';
|
|
255
280
|
};
|
|
256
281
|
|
|
@@ -310,8 +335,7 @@ export const DeckPlugin = ({
|
|
|
310
335
|
},
|
|
311
336
|
properties: {
|
|
312
337
|
label: ['toggle fullscreen label', { ns: DECK_PLUGIN }],
|
|
313
|
-
icon:
|
|
314
|
-
iconSymbol: 'ph--arrows-out--regular',
|
|
338
|
+
icon: 'ph--arrows-out--regular',
|
|
315
339
|
keyBinding: {
|
|
316
340
|
macos: 'ctrl+meta+f',
|
|
317
341
|
windows: 'shift+ctrl+f',
|
|
@@ -329,31 +353,27 @@ export const DeckPlugin = ({
|
|
|
329
353
|
),
|
|
330
354
|
root: () => {
|
|
331
355
|
return (
|
|
332
|
-
<
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
}}
|
|
354
|
-
/>
|
|
355
|
-
<Mosaic.DragOverlay />
|
|
356
|
-
</Mosaic.Root>
|
|
356
|
+
<DeckLayout
|
|
357
|
+
layoutParts={location.values.active}
|
|
358
|
+
showHints={settings.values.showHints}
|
|
359
|
+
overscroll={settings.values.overscroll}
|
|
360
|
+
flatDeck={settings.values.flatDeck}
|
|
361
|
+
slots={settings.values.customSlots ? customSlots : undefined}
|
|
362
|
+
toasts={layout.values.toasts}
|
|
363
|
+
onDismissToast={(id) => {
|
|
364
|
+
const index = layout.values.toasts.findIndex((toast) => toast.id === id);
|
|
365
|
+
if (index !== -1) {
|
|
366
|
+
// Allow time for the toast to animate out.
|
|
367
|
+
// TODO(burdon): Factor out and unregister timeout.
|
|
368
|
+
setTimeout(() => {
|
|
369
|
+
if (layout.values.toasts[index].id === currentUndoId) {
|
|
370
|
+
currentUndoId = undefined;
|
|
371
|
+
}
|
|
372
|
+
layout.values.toasts.splice(index, 1);
|
|
373
|
+
}, 1_000);
|
|
374
|
+
}
|
|
375
|
+
}}
|
|
376
|
+
/>
|
|
357
377
|
);
|
|
358
378
|
},
|
|
359
379
|
surface: {
|
|
@@ -472,7 +492,7 @@ export const DeckPlugin = ({
|
|
|
472
492
|
}
|
|
473
493
|
});
|
|
474
494
|
|
|
475
|
-
|
|
495
|
+
handleSetLocation(newLayout);
|
|
476
496
|
});
|
|
477
497
|
|
|
478
498
|
const ids = openIds(location.values.active);
|
|
@@ -522,10 +542,12 @@ export const DeckPlugin = ({
|
|
|
522
542
|
const layoutEntry = { id: data.id };
|
|
523
543
|
const effectivePart = getEffectivePart(data.part, layout.values.layoutMode);
|
|
524
544
|
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
545
|
+
handleSetLocation(
|
|
546
|
+
openEntry(location.values.active, effectivePart, layoutEntry, {
|
|
547
|
+
positioning: data.positioning ?? settings.values.newPlankPositioning,
|
|
548
|
+
pivotId: data.pivotId,
|
|
549
|
+
}),
|
|
550
|
+
);
|
|
529
551
|
|
|
530
552
|
const intents = [];
|
|
531
553
|
if (data.scrollIntoView && layout.values.layoutMode === 'deck') {
|
|
@@ -561,7 +583,11 @@ export const DeckPlugin = ({
|
|
|
561
583
|
}
|
|
562
584
|
});
|
|
563
585
|
|
|
564
|
-
|
|
586
|
+
handleSetLocation(newLayout);
|
|
587
|
+
// TODO(wittjosiah): This needs to also set the closed state.
|
|
588
|
+
// The closed state should be the existing closed state plus the newly closed ids.
|
|
589
|
+
// The closed state should also be updated when opening entries to remove the id from closed.
|
|
590
|
+
// When SET is called the closed ids should also be calculated and set.
|
|
565
591
|
return { data: true };
|
|
566
592
|
});
|
|
567
593
|
}
|
|
@@ -570,7 +596,7 @@ export const DeckPlugin = ({
|
|
|
570
596
|
case NavigationAction.SET: {
|
|
571
597
|
return batch(() => {
|
|
572
598
|
if (isLayoutParts(intent.data?.activeParts)) {
|
|
573
|
-
|
|
599
|
+
handleSetLocation(intent.data!.activeParts);
|
|
574
600
|
}
|
|
575
601
|
return { data: true };
|
|
576
602
|
});
|
|
@@ -581,10 +607,12 @@ export const DeckPlugin = ({
|
|
|
581
607
|
if (isLayoutAdjustment(intent.data)) {
|
|
582
608
|
const adjustment = intent.data;
|
|
583
609
|
if (adjustment.type === 'increment-end' || adjustment.type === 'increment-start') {
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
610
|
+
handleSetLocation(
|
|
611
|
+
incrementPlank(location.values.active, {
|
|
612
|
+
type: adjustment.type,
|
|
613
|
+
layoutCoordinate: adjustment.layoutCoordinate,
|
|
614
|
+
}),
|
|
615
|
+
);
|
|
588
616
|
}
|
|
589
617
|
|
|
590
618
|
if (adjustment.type === 'solo') {
|
|
@@ -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 {
|
|
7
|
+
import {
|
|
8
|
+
type LayoutParts,
|
|
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 {
|
|
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,89 @@ 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 { DECK_PLUGIN } from '../../meta';
|
|
18
25
|
import { useLayout } from '../LayoutContext';
|
|
19
26
|
|
|
20
27
|
export type ComplementarySidebarProps = {
|
|
21
|
-
|
|
28
|
+
context?: string;
|
|
22
29
|
layoutParts: LayoutParts;
|
|
23
30
|
flatDeck?: boolean;
|
|
24
31
|
};
|
|
25
32
|
|
|
26
|
-
|
|
33
|
+
const panels = ['comments', 'settings'] as const;
|
|
34
|
+
type Panel = (typeof panels)[number];
|
|
35
|
+
const getPanel = (part?: string): Panel => {
|
|
36
|
+
if (part && panels.findIndex((panel) => panel === part) !== -1) {
|
|
37
|
+
return part as Panel;
|
|
38
|
+
} else {
|
|
39
|
+
return 'settings';
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export const ComplementarySidebar = ({ layoutParts, flatDeck }: ComplementarySidebarProps) => {
|
|
27
44
|
const { popoverAnchorId } = useLayout();
|
|
45
|
+
const attended = useAttended();
|
|
46
|
+
const part = getPanel(layoutParts.complementary?.[0].id);
|
|
47
|
+
const id = attended[0] ? `${attended[0]}${SLUG_PATH_SEPARATOR}${part}` : undefined;
|
|
28
48
|
const { graph } = useGraph();
|
|
29
49
|
const node = useNode(graph, id);
|
|
30
|
-
|
|
31
|
-
const complementaryAttrs = createAttendableAttributes(id?.split(SLUG_PATH_SEPARATOR)[0] ?? 'never');
|
|
50
|
+
const dispatch = useIntentDispatcher();
|
|
32
51
|
|
|
33
52
|
useNodeActionExpander(node);
|
|
34
53
|
|
|
54
|
+
const actions = useMemo(
|
|
55
|
+
() => [
|
|
56
|
+
{
|
|
57
|
+
id: 'complementary-settings',
|
|
58
|
+
data: () => {
|
|
59
|
+
void dispatch({ action: NavigationAction.OPEN, data: { activeParts: { complementary: 'settings' } } });
|
|
60
|
+
},
|
|
61
|
+
properties: {
|
|
62
|
+
label: ['settings label', { ns: DECK_PLUGIN }],
|
|
63
|
+
icon: 'ph--gear--regular',
|
|
64
|
+
menuItemType: 'toggle',
|
|
65
|
+
isChecked: part === 'settings',
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
id: 'complementary-comments',
|
|
70
|
+
data: () => {
|
|
71
|
+
void dispatch({ action: NavigationAction.OPEN, data: { activeParts: { complementary: 'comments' } } });
|
|
72
|
+
},
|
|
73
|
+
properties: {
|
|
74
|
+
label: ['comments label', { ns: DECK_PLUGIN }],
|
|
75
|
+
icon: 'ph--chat-text--regular',
|
|
76
|
+
menuItemType: 'toggle',
|
|
77
|
+
isChecked: part === 'comments',
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
],
|
|
81
|
+
[part],
|
|
82
|
+
);
|
|
83
|
+
|
|
35
84
|
return (
|
|
36
|
-
<Main.ComplementarySidebar
|
|
37
|
-
{
|
|
38
|
-
<
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
85
|
+
<Main.ComplementarySidebar>
|
|
86
|
+
<div role='none' className={mx(deckGrid, 'grid-cols-1 bs-full')}>
|
|
87
|
+
<NodePlankHeading
|
|
88
|
+
node={node}
|
|
89
|
+
id={id}
|
|
90
|
+
layoutParts={layoutParts}
|
|
91
|
+
layoutPart='complementary'
|
|
92
|
+
popoverAnchorId={popoverAnchorId}
|
|
93
|
+
flatDeck={flatDeck}
|
|
94
|
+
actions={actions}
|
|
95
|
+
/>
|
|
96
|
+
{/* TODO(wittjosiah): Render some placeholder when node is undefined. */}
|
|
97
|
+
{node && (
|
|
47
98
|
<Surface
|
|
48
|
-
role=
|
|
49
|
-
data={{ subject: node.
|
|
99
|
+
role={`complementary--${part}`}
|
|
100
|
+
data={{ subject: node.properties.object, popoverAnchorId }}
|
|
50
101
|
limit={1}
|
|
51
102
|
fallback={PlankContentError}
|
|
52
103
|
placeholder={<PlankLoading />}
|
|
53
104
|
/>
|
|
54
|
-
|
|
55
|
-
|
|
105
|
+
)}
|
|
106
|
+
</div>
|
|
56
107
|
</Main.ComplementarySidebar>
|
|
57
108
|
);
|
|
58
109
|
};
|
|
@@ -3,11 +3,9 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { Sidebar as MenuIcon } from '@phosphor-icons/react';
|
|
6
|
-
import React, { useCallback, useEffect, useMemo, useRef,
|
|
6
|
+
import React, { useCallback, useEffect, useMemo, useRef, type UIEvent } from 'react';
|
|
7
7
|
|
|
8
8
|
import {
|
|
9
|
-
SLUG_PATH_SEPARATOR,
|
|
10
|
-
type Attention,
|
|
11
9
|
type LayoutEntry,
|
|
12
10
|
type LayoutParts,
|
|
13
11
|
Surface,
|
|
@@ -15,7 +13,8 @@ import {
|
|
|
15
13
|
firstIdInPart,
|
|
16
14
|
usePlugin,
|
|
17
15
|
} from '@dxos/app-framework';
|
|
18
|
-
import { Button, Dialog, Main, Popover, useTranslation } from '@dxos/react-ui';
|
|
16
|
+
import { Button, Dialog, Main, Popover, useOnTransition, useTranslation } from '@dxos/react-ui';
|
|
17
|
+
import { useAttended } from '@dxos/react-ui-attention';
|
|
19
18
|
import { Deck } from '@dxos/react-ui-deck';
|
|
20
19
|
import { getSize } from '@dxos/react-ui-theme';
|
|
21
20
|
|
|
@@ -35,11 +34,10 @@ import { useLayout } from '../LayoutContext';
|
|
|
35
34
|
|
|
36
35
|
export type DeckLayoutProps = {
|
|
37
36
|
layoutParts: LayoutParts;
|
|
38
|
-
attention: Attention;
|
|
39
37
|
toasts: ToastSchema[];
|
|
40
38
|
flatDeck?: boolean;
|
|
41
39
|
overscroll: Overscroll;
|
|
42
|
-
|
|
40
|
+
showHints: boolean;
|
|
43
41
|
slots?: {
|
|
44
42
|
wallpaper?: { classNames?: string };
|
|
45
43
|
};
|
|
@@ -48,11 +46,10 @@ export type DeckLayoutProps = {
|
|
|
48
46
|
|
|
49
47
|
export const DeckLayout = ({
|
|
50
48
|
layoutParts,
|
|
51
|
-
attention,
|
|
52
49
|
toasts,
|
|
53
50
|
flatDeck,
|
|
54
51
|
overscroll,
|
|
55
|
-
|
|
52
|
+
showHints,
|
|
56
53
|
slots,
|
|
57
54
|
onDismissToast,
|
|
58
55
|
}: DeckLayoutProps) => {
|
|
@@ -70,36 +67,42 @@ export const DeckLayout = ({
|
|
|
70
67
|
} = context;
|
|
71
68
|
const { t } = useTranslation(DECK_PLUGIN);
|
|
72
69
|
const { plankSizing } = useDeckContext();
|
|
70
|
+
const attended = useAttended();
|
|
73
71
|
const searchPlugin = usePlugin('dxos.org/plugin/search');
|
|
74
72
|
const fullScreenSlug = useMemo(() => firstIdInPart(layoutParts, 'fullScreen'), [layoutParts]);
|
|
75
73
|
|
|
76
|
-
const
|
|
77
|
-
const deckRef = useRef<HTMLDivElement
|
|
78
|
-
|
|
74
|
+
const scrollLeftRef = useRef<number | null>();
|
|
75
|
+
const deckRef = useRef<HTMLDivElement>(null);
|
|
76
|
+
|
|
77
|
+
// Ensure the first plank is attended when the deck is first rendered.
|
|
78
|
+
useEffect(() => {
|
|
79
|
+
const firstId = layoutMode === 'solo' ? firstIdInPart(layoutParts, 'solo') : firstIdInPart(layoutParts, 'main');
|
|
80
|
+
if (attended.length === 0 && firstId) {
|
|
81
|
+
// TODO(wittjosiah): Focusing the type button is a workaround.
|
|
82
|
+
// If the plank is directly focused on first load the focus ring appears.
|
|
83
|
+
document.querySelector<HTMLElement>(`article[data-attendable-id="${firstId}"] button`)?.focus();
|
|
84
|
+
}
|
|
85
|
+
}, []);
|
|
79
86
|
|
|
80
87
|
/**
|
|
81
88
|
* Clear scroll restoration state if the window is resized
|
|
82
89
|
*/
|
|
83
90
|
const handleResize = useCallback(() => {
|
|
84
|
-
|
|
91
|
+
scrollLeftRef.current = null;
|
|
85
92
|
}, []);
|
|
93
|
+
|
|
86
94
|
useEffect(() => {
|
|
87
95
|
window.addEventListener('resize', handleResize);
|
|
88
96
|
return () => window.removeEventListener('resize', handleResize);
|
|
89
97
|
}, [handleResize]);
|
|
90
98
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
useLayoutEffect(() => {
|
|
95
|
-
if (layoutMode !== 'deck') {
|
|
96
|
-
restoreScrollRef.current = true;
|
|
97
|
-
} else if (restoreScrollRef.current && deckRef.current && scrollLeft) {
|
|
98
|
-
// console.log('[restoring scrollLeft]', scrollLeft);
|
|
99
|
-
deckRef.current.scrollLeft = scrollLeft;
|
|
100
|
-
restoreScrollRef.current = false;
|
|
99
|
+
const restoreScroll = useCallback(() => {
|
|
100
|
+
if (deckRef.current && scrollLeftRef.current != null) {
|
|
101
|
+
deckRef.current.scrollLeft = scrollLeftRef.current;
|
|
101
102
|
}
|
|
102
|
-
}, [
|
|
103
|
+
}, []);
|
|
104
|
+
|
|
105
|
+
useOnTransition(layoutMode, (mode) => mode !== 'deck', 'deck', restoreScroll);
|
|
103
106
|
|
|
104
107
|
/**
|
|
105
108
|
* Save scroll position as the user scrolls
|
|
@@ -107,22 +110,13 @@ export const DeckLayout = ({
|
|
|
107
110
|
const handleScroll = useCallback(
|
|
108
111
|
(event: UIEvent) => {
|
|
109
112
|
if (layoutMode === 'deck' && event.currentTarget === event.target) {
|
|
110
|
-
|
|
111
|
-
setScrollLeft((event.target as HTMLDivElement).scrollLeft);
|
|
113
|
+
scrollLeftRef.current = (event.target as HTMLDivElement).scrollLeft;
|
|
112
114
|
}
|
|
113
115
|
},
|
|
114
116
|
[layoutMode],
|
|
115
117
|
);
|
|
116
118
|
|
|
117
|
-
const
|
|
118
|
-
const entry = layoutParts.complementary?.at(0);
|
|
119
|
-
if (entry) {
|
|
120
|
-
return entry.path ? `${entry.id}${SLUG_PATH_SEPARATOR}${entry.path}` : entry.id;
|
|
121
|
-
}
|
|
122
|
-
}, [layoutParts]);
|
|
123
|
-
|
|
124
|
-
const firstAttendedId = useMemo(() => Array.from(attention.attended ?? [])[0], [attention.attended]);
|
|
125
|
-
|
|
119
|
+
const firstAttendedId = attended[0];
|
|
126
120
|
useEffect(() => {
|
|
127
121
|
// TODO(burdon): Can we prevent the need to re-scroll since the planks are preserved?
|
|
128
122
|
// E.g., hide the deck and just move the solo article?
|
|
@@ -145,10 +139,12 @@ export const DeckLayout = ({
|
|
|
145
139
|
return parts;
|
|
146
140
|
}, [layoutParts.main, layoutParts.solo]);
|
|
147
141
|
|
|
148
|
-
const padding =
|
|
149
|
-
layoutMode === 'deck' && overscroll === 'centering'
|
|
150
|
-
|
|
151
|
-
|
|
142
|
+
const padding = useMemo(() => {
|
|
143
|
+
if (layoutMode === 'deck' && overscroll === 'centering') {
|
|
144
|
+
return calculateOverscroll(layoutParts.main, plankSizing, sidebarOpen, complementarySidebarOpen);
|
|
145
|
+
}
|
|
146
|
+
return {};
|
|
147
|
+
}, [layoutMode, overscroll, layoutParts.main, plankSizing, sidebarOpen, complementarySidebarOpen]);
|
|
152
148
|
|
|
153
149
|
if (layoutMode === 'fullscreen') {
|
|
154
150
|
return <Fullscreen id={fullScreenSlug} />;
|
|
@@ -173,25 +169,17 @@ export const DeckLayout = ({
|
|
|
173
169
|
<Main.Root
|
|
174
170
|
navigationSidebarOpen={context.sidebarOpen}
|
|
175
171
|
onNavigationSidebarOpenChange={(next) => (context.sidebarOpen = next)}
|
|
176
|
-
{
|
|
177
|
-
|
|
178
|
-
onComplementarySidebarOpenChange: (next) => (context.complementarySidebarOpen = next),
|
|
179
|
-
})}
|
|
172
|
+
complementarySidebarOpen={context.complementarySidebarOpen}
|
|
173
|
+
onComplementarySidebarOpenChange={(next) => (context.complementarySidebarOpen = next)}
|
|
180
174
|
>
|
|
181
175
|
{/* Notch */}
|
|
182
176
|
<Main.Notch classNames='z-[21]'>
|
|
183
177
|
<Surface role='notch-start' />
|
|
184
|
-
<Button
|
|
185
|
-
// disabled={!sidebarAvailable}
|
|
186
|
-
onClick={() => (context.sidebarOpen = !context.sidebarOpen)}
|
|
187
|
-
variant='ghost'
|
|
188
|
-
classNames='p-1'
|
|
189
|
-
>
|
|
178
|
+
<Button onClick={() => (context.sidebarOpen = !context.sidebarOpen)} variant='ghost' classNames='p-1'>
|
|
190
179
|
<span className='sr-only'>{t('open navigation sidebar label')}</span>
|
|
191
180
|
<MenuIcon weight='light' className={getSize(5)} />
|
|
192
181
|
</Button>
|
|
193
182
|
<Button
|
|
194
|
-
// disabled={!complementaryAvailable}
|
|
195
183
|
onClick={() => (context.complementarySidebarOpen = !context.complementarySidebarOpen)}
|
|
196
184
|
variant='ghost'
|
|
197
185
|
classNames='p-1'
|
|
@@ -203,10 +191,11 @@ export const DeckLayout = ({
|
|
|
203
191
|
</Main.Notch>
|
|
204
192
|
|
|
205
193
|
{/* Left sidebar. */}
|
|
206
|
-
<Sidebar
|
|
194
|
+
<Sidebar layoutParts={layoutParts} />
|
|
207
195
|
|
|
208
196
|
{/* Right sidebar. */}
|
|
209
|
-
|
|
197
|
+
{/* TODO(wittjosiah): Get context from layout parts. */}
|
|
198
|
+
<ComplementarySidebar context='comments' layoutParts={layoutParts} flatDeck={flatDeck} />
|
|
210
199
|
|
|
211
200
|
{/* Dialog overlay to dismiss dialogs. */}
|
|
212
201
|
<Main.Overlay />
|
|
@@ -245,15 +234,8 @@ export const DeckLayout = ({
|
|
|
245
234
|
</Main.Content>
|
|
246
235
|
)}
|
|
247
236
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
{/* Help hints. */}
|
|
251
|
-
{/* TODO(burdon): Need to make room for this in status bar. */}
|
|
252
|
-
{showHintsFooter && (
|
|
253
|
-
<div className='fixed bottom-0 left-0 right-0 h-[32px] z-[1] flex justify-center'>
|
|
254
|
-
<Surface role='hints' limit={1} />
|
|
255
|
-
</div>
|
|
256
|
-
)}
|
|
237
|
+
{/* Footer status. */}
|
|
238
|
+
<StatusBar showHints={showHints} />
|
|
257
239
|
|
|
258
240
|
{/* Global popovers. */}
|
|
259
241
|
<Popover.Portal>
|