@dxos/plugin-deck 0.7.4-staging.f7e8224 → 0.7.5-feature-compute.4d9d99a
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/app-graph-builder-67VRUD5K.mjs +121 -0
- package/dist/lib/browser/app-graph-builder-67VRUD5K.mjs.map +7 -0
- package/dist/lib/browser/check-app-scheme-GEX6W2R5.mjs +33 -0
- package/dist/lib/browser/check-app-scheme-GEX6W2R5.mjs.map +7 -0
- package/dist/lib/browser/chunk-2M4PXYNB.mjs +1052 -0
- package/dist/lib/browser/chunk-2M4PXYNB.mjs.map +7 -0
- package/dist/lib/browser/chunk-2PJNBVCY.mjs +39 -0
- package/dist/lib/browser/chunk-2PJNBVCY.mjs.map +7 -0
- package/dist/lib/browser/chunk-4C2AFTET.mjs +186 -0
- package/dist/lib/browser/chunk-4C2AFTET.mjs.map +7 -0
- package/dist/lib/browser/chunk-5VFDMW5M.mjs +17 -0
- package/dist/lib/browser/chunk-5VFDMW5M.mjs.map +7 -0
- package/dist/lib/browser/{chunk-GVOGPULO.mjs → chunk-JQJ5UWVB.mjs} +3 -3
- package/dist/lib/browser/chunk-JQJ5UWVB.mjs.map +7 -0
- package/dist/lib/browser/chunk-KY5WXIXY.mjs +44 -0
- package/dist/lib/browser/chunk-KY5WXIXY.mjs.map +7 -0
- package/dist/lib/browser/deck-PLCSKPGL.mjs +26 -0
- package/dist/lib/browser/deck-PLCSKPGL.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +136 -1803
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/intent-resolver-FVOQSTBX.mjs +152 -0
- package/dist/lib/browser/intent-resolver-FVOQSTBX.mjs.map +7 -0
- package/dist/lib/browser/intent-resolver-K7GW4A2I.mjs +249 -0
- package/dist/lib/browser/intent-resolver-K7GW4A2I.mjs.map +7 -0
- package/dist/lib/browser/location-QHRBQBQN.mjs +35 -0
- package/dist/lib/browser/location-QHRBQBQN.mjs.map +7 -0
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/react-context-3BDW7W2N.mjs +32 -0
- package/dist/lib/browser/react-context-3BDW7W2N.mjs.map +7 -0
- package/dist/lib/browser/react-root-UL7ZDRVZ.mjs +50 -0
- package/dist/lib/browser/react-root-UL7ZDRVZ.mjs.map +7 -0
- package/dist/lib/browser/react-surface-VPNOGGNN.mjs +28 -0
- package/dist/lib/browser/react-surface-VPNOGGNN.mjs.map +7 -0
- package/dist/lib/browser/settings-FNWW6WIJ.mjs +29 -0
- package/dist/lib/browser/settings-FNWW6WIJ.mjs.map +7 -0
- package/dist/lib/browser/state-7I5BD7SE.mjs +34 -0
- package/dist/lib/browser/state-7I5BD7SE.mjs.map +7 -0
- package/dist/lib/browser/types.mjs +10 -5
- package/dist/lib/browser/url-handler-Z5B7LD3N.mjs +76 -0
- package/dist/lib/browser/url-handler-Z5B7LD3N.mjs.map +7 -0
- package/dist/types/src/DeckPlugin.d.ts +1 -14
- package/dist/types/src/DeckPlugin.d.ts.map +1 -1
- package/dist/types/src/capabilities/capabilities.d.ts +13 -0
- package/dist/types/src/capabilities/capabilities.d.ts.map +1 -0
- package/dist/types/src/capabilities/index.d.ts +5 -0
- package/dist/types/src/capabilities/index.d.ts.map +1 -0
- package/dist/types/src/capabilities/layout/app-graph-builder.d.ts +181 -0
- package/dist/types/src/capabilities/layout/app-graph-builder.d.ts.map +1 -0
- package/dist/types/src/capabilities/layout/deck.d.ts +4 -0
- package/dist/types/src/capabilities/layout/deck.d.ts.map +1 -0
- package/dist/types/src/capabilities/layout/index.d.ts +229 -0
- package/dist/types/src/capabilities/layout/index.d.ts.map +1 -0
- package/dist/types/src/capabilities/layout/intent-resolver.d.ts +4 -0
- package/dist/types/src/capabilities/layout/intent-resolver.d.ts.map +1 -0
- package/dist/types/src/capabilities/layout/react-context.d.ts +8 -0
- package/dist/types/src/capabilities/layout/react-context.d.ts.map +1 -0
- package/dist/types/src/capabilities/layout/react-root.d.ts +7 -0
- package/dist/types/src/capabilities/layout/react-root.d.ts.map +1 -0
- package/dist/types/src/capabilities/layout/state.d.ts +42 -0
- package/dist/types/src/capabilities/layout/state.d.ts.map +1 -0
- package/dist/types/src/capabilities/navigation/check-app-scheme.d.ts +4 -0
- package/dist/types/src/capabilities/navigation/check-app-scheme.d.ts.map +1 -0
- package/dist/types/src/capabilities/navigation/index.d.ts +5 -0
- package/dist/types/src/capabilities/navigation/index.d.ts.map +1 -0
- package/dist/types/src/capabilities/navigation/intent-resolver.d.ts +4 -0
- package/dist/types/src/capabilities/navigation/intent-resolver.d.ts.map +1 -0
- package/dist/types/src/capabilities/navigation/location.d.ts +4 -0
- package/dist/types/src/capabilities/navigation/location.d.ts.map +1 -0
- package/dist/types/src/capabilities/navigation/set-location.d.ts +10 -0
- package/dist/types/src/capabilities/navigation/set-location.d.ts.map +1 -0
- package/dist/types/src/capabilities/navigation/url-handler.d.ts +4 -0
- package/dist/types/src/capabilities/navigation/url-handler.d.ts.map +1 -0
- package/dist/types/src/capabilities/settings/index.d.ts +3 -0
- package/dist/types/src/capabilities/settings/index.d.ts.map +1 -0
- package/dist/types/src/capabilities/settings/react-surface.d.ts +4 -0
- package/dist/types/src/capabilities/settings/react-surface.d.ts.map +1 -0
- package/dist/types/src/capabilities/settings/settings.d.ts +4 -0
- package/dist/types/src/capabilities/settings/settings.d.ts.map +1 -0
- package/dist/types/src/components/DeckContext.d.ts +3 -0
- package/dist/types/src/components/DeckContext.d.ts.map +1 -1
- package/dist/types/src/components/DeckLayout/Banner.d.ts +6 -0
- package/dist/types/src/components/DeckLayout/Banner.d.ts.map +1 -0
- package/dist/types/src/components/DeckLayout/ComplementarySidebar.d.ts.map +1 -1
- package/dist/types/src/components/DeckLayout/ContentEmpty.d.ts.map +1 -1
- package/dist/types/src/components/DeckLayout/DeckLayout.d.ts.map +1 -1
- package/dist/types/src/components/DeckLayout/NodePlankHeading.d.ts.map +1 -1
- package/dist/types/src/components/DeckLayout/Plank.d.ts +1 -1
- package/dist/types/src/components/DeckLayout/Plank.d.ts.map +1 -1
- package/dist/types/src/components/DeckLayout/PlankControls.d.ts +2 -2
- package/dist/types/src/components/DeckLayout/PlankControls.d.ts.map +1 -1
- package/dist/types/src/components/DeckLayout/Sidebar.d.ts +1 -5
- package/dist/types/src/components/DeckLayout/Sidebar.d.ts.map +1 -1
- package/dist/types/src/components/DeckLayout/SidebarButton.d.ts +6 -0
- package/dist/types/src/components/DeckLayout/SidebarButton.d.ts.map +1 -0
- package/dist/types/src/components/DeckLayout/Toast.d.ts.map +1 -1
- package/dist/types/src/components/DeckLayout/Topbar.d.ts +3 -0
- package/dist/types/src/components/DeckLayout/Topbar.d.ts.map +1 -0
- package/dist/types/src/components/fragments.d.ts +2 -0
- package/dist/types/src/components/fragments.d.ts.map +1 -0
- package/dist/types/src/events.d.ts +4 -0
- package/dist/types/src/events.d.ts.map +1 -0
- package/dist/types/src/index.d.ts +3 -2
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/meta.d.ts +3 -4
- package/dist/types/src/meta.d.ts.map +1 -1
- package/dist/types/src/translations.d.ts +5 -0
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/src/types.d.ts +25 -17
- package/dist/types/src/types.d.ts.map +1 -1
- package/dist/types/src/util/index.d.ts +1 -1
- package/dist/types/src/util/index.d.ts.map +1 -1
- package/dist/types/src/util/useBreakpoints.d.ts +2 -0
- package/dist/types/src/util/useBreakpoints.d.ts.map +1 -0
- package/dist/types/src/util/useHoistStatusbar.d.ts +2 -0
- package/dist/types/src/util/useHoistStatusbar.d.ts.map +1 -0
- package/dist/types/tsconfig.tsbuildinfo +1 -0
- package/package.json +30 -35
- package/src/DeckPlugin.ts +123 -0
- package/src/capabilities/capabilities.ts +17 -0
- package/src/capabilities/index.ts +8 -0
- package/src/capabilities/layout/app-graph-builder.ts +101 -0
- package/src/capabilities/layout/deck.ts +25 -0
- package/src/capabilities/layout/index.ts +12 -0
- package/src/capabilities/layout/intent-resolver.ts +128 -0
- package/src/capabilities/layout/react-context.tsx +26 -0
- package/src/capabilities/layout/react-root.tsx +52 -0
- package/src/capabilities/layout/state.ts +32 -0
- package/src/capabilities/navigation/check-app-scheme.ts +44 -0
- package/src/capabilities/navigation/index.ts +10 -0
- package/src/capabilities/navigation/intent-resolver.ts +216 -0
- package/src/capabilities/navigation/location.ts +28 -0
- package/src/capabilities/navigation/set-location.ts +38 -0
- package/src/capabilities/navigation/url-handler.ts +67 -0
- package/src/capabilities/settings/index.ts +8 -0
- package/src/capabilities/settings/react-surface.tsx +23 -0
- package/src/capabilities/settings/settings.ts +22 -0
- package/src/components/DeckContext.ts +6 -1
- package/src/components/DeckLayout/ActiveNode.tsx +1 -1
- package/src/components/DeckLayout/Banner.tsx +37 -0
- package/src/components/DeckLayout/ComplementarySidebar.tsx +76 -53
- package/src/components/DeckLayout/ContentEmpty.tsx +10 -2
- package/src/components/DeckLayout/DeckLayout.tsx +31 -40
- package/src/components/DeckLayout/Fullscreen.tsx +1 -1
- package/src/components/DeckLayout/NodePlankHeading.tsx +30 -49
- package/src/components/DeckLayout/Plank.tsx +13 -11
- package/src/components/DeckLayout/PlankControls.tsx +3 -5
- package/src/components/DeckLayout/Sidebar.tsx +22 -26
- package/src/components/DeckLayout/SidebarButton.tsx +74 -0
- package/src/components/DeckLayout/StatusBar.tsx +2 -2
- package/src/components/DeckLayout/Toast.tsx +19 -6
- package/src/components/DeckLayout/Topbar.tsx +11 -0
- package/src/components/fragments.ts +6 -0
- package/src/events.ts +11 -0
- package/src/index.ts +3 -4
- package/src/meta.ts +2 -2
- package/src/translations.ts +5 -0
- package/src/types.ts +27 -37
- package/src/util/index.ts +1 -1
- package/src/util/useBreakpoints.ts +11 -0
- package/src/util/useHoistStatusbar.ts +15 -0
- package/dist/lib/browser/chunk-GVOGPULO.mjs.map +0 -7
- package/dist/lib/browser/chunk-NIRHDTX4.mjs +0 -17
- package/dist/lib/browser/chunk-NIRHDTX4.mjs.map +0 -7
- package/dist/lib/browser/meta.mjs +0 -9
- package/dist/lib/browser/meta.mjs.map +0 -7
- package/dist/types/src/util/check-app-scheme.d.ts +0 -2
- package/dist/types/src/util/check-app-scheme.d.ts.map +0 -1
- package/src/DeckPlugin.tsx +0 -657
- package/src/util/check-app-scheme.ts +0 -21
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { batch } from '@preact/signals-core';
|
|
6
|
+
import { pipe } from 'effect';
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
Capabilities,
|
|
10
|
+
chain,
|
|
11
|
+
contributes,
|
|
12
|
+
createIntent,
|
|
13
|
+
createResolver,
|
|
14
|
+
LayoutAction,
|
|
15
|
+
NavigationAction,
|
|
16
|
+
openIds,
|
|
17
|
+
SLUG_PATH_SEPARATOR,
|
|
18
|
+
type LayoutEntry,
|
|
19
|
+
type LayoutPart,
|
|
20
|
+
type PluginsContext,
|
|
21
|
+
} from '@dxos/app-framework';
|
|
22
|
+
import { isReactiveObject, getTypename } from '@dxos/live-object';
|
|
23
|
+
import { AttentionCapabilities } from '@dxos/plugin-attention';
|
|
24
|
+
import { ObservabilityAction } from '@dxos/plugin-observability/types';
|
|
25
|
+
|
|
26
|
+
import { setLocation } from './set-location';
|
|
27
|
+
import { closeEntry, incrementPlank, openEntry } from '../../layout';
|
|
28
|
+
import { DECK_PLUGIN } from '../../meta';
|
|
29
|
+
import { type DeckSettingsProps } from '../../types';
|
|
30
|
+
import { getEffectivePart } from '../../util';
|
|
31
|
+
|
|
32
|
+
// TODO(wittjosiah): Factor out navgiation from deck plugin.
|
|
33
|
+
export default (context: PluginsContext) =>
|
|
34
|
+
contributes(Capabilities.IntentResolver, [
|
|
35
|
+
createResolver(NavigationAction.Open, (data) => {
|
|
36
|
+
const { graph } = context.requestCapability(Capabilities.AppGraph);
|
|
37
|
+
const location = context.requestCapability(Capabilities.MutableLocation);
|
|
38
|
+
const layout = context.requestCapability(Capabilities.MutableLayout);
|
|
39
|
+
const attention = context.requestCapability(AttentionCapabilities.Attention);
|
|
40
|
+
const settings = context
|
|
41
|
+
.requestCapabilities(Capabilities.SettingsStore)[0]
|
|
42
|
+
?.getStore<DeckSettingsProps>(DECK_PLUGIN)?.value;
|
|
43
|
+
|
|
44
|
+
const previouslyOpenIds = new Set<string>(openIds(location.active));
|
|
45
|
+
const layoutMode = layout.layoutMode;
|
|
46
|
+
const toAttend = batch(() => {
|
|
47
|
+
const processLayoutEntry = (partName: string, entryString: string, currentLayout: any) => {
|
|
48
|
+
// TODO(burdon): Option to toggle?
|
|
49
|
+
const toggle = false;
|
|
50
|
+
const [id, path] = entryString.split(SLUG_PATH_SEPARATOR);
|
|
51
|
+
const layoutEntry: LayoutEntry = { id, ...(path ? { path } : {}) };
|
|
52
|
+
const effectivePart = getEffectivePart(partName as LayoutPart, layoutMode);
|
|
53
|
+
if (
|
|
54
|
+
toggle &&
|
|
55
|
+
layoutMode === 'deck' &&
|
|
56
|
+
effectivePart === 'main' &&
|
|
57
|
+
currentLayout[effectivePart]?.some((entry: LayoutEntry) => entry.id === id) &&
|
|
58
|
+
!data?.noToggle
|
|
59
|
+
) {
|
|
60
|
+
// If we're in deck mode and the main part is already open, toggle it closed.
|
|
61
|
+
return closeEntry(currentLayout, { part: effectivePart as LayoutPart, entryId: id });
|
|
62
|
+
} else {
|
|
63
|
+
return openEntry(currentLayout, effectivePart, layoutEntry, {
|
|
64
|
+
positioning: settings?.newPlankPositioning,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
let newLayout = location.active;
|
|
70
|
+
Object.entries(data.activeParts).forEach(([partName, layoutEntries]) => {
|
|
71
|
+
if (Array.isArray(layoutEntries)) {
|
|
72
|
+
layoutEntries.forEach((activePartEntry: string) => {
|
|
73
|
+
newLayout = processLayoutEntry(partName, activePartEntry, newLayout);
|
|
74
|
+
});
|
|
75
|
+
} else if (typeof layoutEntries === 'string') {
|
|
76
|
+
// Legacy single string entry.
|
|
77
|
+
newLayout = processLayoutEntry(partName, layoutEntries, newLayout);
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
return setLocation({ next: newLayout, layout, location, attention });
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
const ids = openIds(location.active);
|
|
85
|
+
const newlyOpen = ids.filter((i) => !previouslyOpenIds.has(i));
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
data: { open: ids },
|
|
89
|
+
intents: [
|
|
90
|
+
createIntent(LayoutAction.ScrollIntoView, { id: newlyOpen[0] ?? toAttend }),
|
|
91
|
+
...(toAttend ? [createIntent(NavigationAction.Expose, { id: toAttend })] : []),
|
|
92
|
+
...newlyOpen.map((id) => {
|
|
93
|
+
const active = graph?.findNode(id)?.data;
|
|
94
|
+
const typename = isReactiveObject(active) ? getTypename(active) : undefined;
|
|
95
|
+
return createIntent(ObservabilityAction.SendEvent, {
|
|
96
|
+
name: 'navigation.activate',
|
|
97
|
+
properties: {
|
|
98
|
+
id,
|
|
99
|
+
typename,
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
}),
|
|
103
|
+
],
|
|
104
|
+
};
|
|
105
|
+
}),
|
|
106
|
+
createResolver(NavigationAction.AddToActive, (data) => {
|
|
107
|
+
const location = context.requestCapability(Capabilities.MutableLocation);
|
|
108
|
+
const layout = context.requestCapability(Capabilities.MutableLayout);
|
|
109
|
+
const attention = context.requestCapability(AttentionCapabilities.Attention);
|
|
110
|
+
const settings = context
|
|
111
|
+
.requestCapabilities(Capabilities.SettingsStore)[0]
|
|
112
|
+
?.getStore<DeckSettingsProps>(DECK_PLUGIN)?.value;
|
|
113
|
+
|
|
114
|
+
const layoutEntry = { id: data.id };
|
|
115
|
+
const effectivePart = getEffectivePart(data.part, layout.layoutMode);
|
|
116
|
+
|
|
117
|
+
setLocation({
|
|
118
|
+
next: openEntry(location.active, effectivePart, layoutEntry, {
|
|
119
|
+
positioning: data.positioning ?? settings?.newPlankPositioning,
|
|
120
|
+
pivotId: data.pivotId,
|
|
121
|
+
}),
|
|
122
|
+
layout,
|
|
123
|
+
location,
|
|
124
|
+
attention,
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
const intents = [];
|
|
128
|
+
if (data.scrollIntoView && layout.layoutMode === 'deck') {
|
|
129
|
+
intents.push(createIntent(LayoutAction.ScrollIntoView, { id: data.id }));
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return { intents };
|
|
133
|
+
}),
|
|
134
|
+
createResolver(NavigationAction.Close, (data) => {
|
|
135
|
+
const location = context.requestCapability(Capabilities.MutableLocation);
|
|
136
|
+
const layout = context.requestCapability(Capabilities.MutableLayout);
|
|
137
|
+
const attention = context.requestCapability(AttentionCapabilities.Attention);
|
|
138
|
+
|
|
139
|
+
let newLayout = location.active;
|
|
140
|
+
const layoutMode = layout.layoutMode;
|
|
141
|
+
const intentParts = data.activeParts;
|
|
142
|
+
Object.keys(intentParts).forEach((partName: string) => {
|
|
143
|
+
const effectivePart = getEffectivePart(partName as LayoutPart, layoutMode);
|
|
144
|
+
const ids = intentParts[partName];
|
|
145
|
+
if (Array.isArray(ids)) {
|
|
146
|
+
ids.forEach((id: string) => {
|
|
147
|
+
newLayout = closeEntry(newLayout, { part: effectivePart, entryId: id });
|
|
148
|
+
});
|
|
149
|
+
} else {
|
|
150
|
+
// Legacy single string entry
|
|
151
|
+
newLayout = closeEntry(newLayout, { part: effectivePart, entryId: ids });
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
const toAttend = setLocation({ next: newLayout, layout, location, attention });
|
|
156
|
+
return { intents: [createIntent(LayoutAction.ScrollIntoView, { id: toAttend })] };
|
|
157
|
+
}),
|
|
158
|
+
createResolver(NavigationAction.Set, (data) => {
|
|
159
|
+
const layout = context.requestCapability(Capabilities.MutableLayout);
|
|
160
|
+
const location = context.requestCapability(Capabilities.MutableLocation);
|
|
161
|
+
const attention = context.requestCapability(AttentionCapabilities.Attention);
|
|
162
|
+
|
|
163
|
+
return batch(() => {
|
|
164
|
+
const toAttend = setLocation({ next: data.activeParts, layout, location, attention });
|
|
165
|
+
return { intents: [createIntent(LayoutAction.ScrollIntoView, { id: toAttend })] };
|
|
166
|
+
});
|
|
167
|
+
}),
|
|
168
|
+
createResolver(NavigationAction.Adjust, (adjustment) => {
|
|
169
|
+
const location = context.requestCapability(Capabilities.MutableLocation);
|
|
170
|
+
const layout = context.requestCapability(Capabilities.MutableLayout);
|
|
171
|
+
const attention = context.requestCapability(AttentionCapabilities.Attention);
|
|
172
|
+
|
|
173
|
+
return batch(() => {
|
|
174
|
+
if (adjustment.type === 'increment-end' || adjustment.type === 'increment-start') {
|
|
175
|
+
setLocation({
|
|
176
|
+
next: incrementPlank(location.active, {
|
|
177
|
+
type: adjustment.type,
|
|
178
|
+
layoutCoordinate: adjustment.layoutCoordinate,
|
|
179
|
+
}),
|
|
180
|
+
layout,
|
|
181
|
+
location,
|
|
182
|
+
attention,
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (adjustment.type === 'solo') {
|
|
187
|
+
const entryId = adjustment.layoutCoordinate.entryId;
|
|
188
|
+
if (layout.layoutMode !== 'solo') {
|
|
189
|
+
// Solo the entry.
|
|
190
|
+
return {
|
|
191
|
+
intents: [
|
|
192
|
+
// NOTE: The order of these is important.
|
|
193
|
+
pipe(
|
|
194
|
+
createIntent(NavigationAction.Open, { activeParts: { solo: [entryId] } }),
|
|
195
|
+
chain(LayoutAction.SetLayoutMode, { layoutMode: 'solo' }),
|
|
196
|
+
),
|
|
197
|
+
],
|
|
198
|
+
};
|
|
199
|
+
} else {
|
|
200
|
+
// Un-solo the current entry.
|
|
201
|
+
return {
|
|
202
|
+
intents: [
|
|
203
|
+
// NOTE: The order of these is important.
|
|
204
|
+
pipe(
|
|
205
|
+
createIntent(LayoutAction.SetLayoutMode, { layoutMode: 'deck' }),
|
|
206
|
+
chain(NavigationAction.Close, { activeParts: { solo: [entryId] } }),
|
|
207
|
+
chain(NavigationAction.Open, { activeParts: { main: [entryId] }, noToggle: true }),
|
|
208
|
+
chain(LayoutAction.ScrollIntoView, { id: entryId }),
|
|
209
|
+
),
|
|
210
|
+
],
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
}),
|
|
216
|
+
]);
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { Capabilities, contributes, type LayoutParts } from '@dxos/app-framework';
|
|
6
|
+
import { LocalStorageStore } from '@dxos/local-storage';
|
|
7
|
+
|
|
8
|
+
import { NAV_ID } from '../../components';
|
|
9
|
+
|
|
10
|
+
// NOTE: The key is this currently for backwards compatibility of storage.
|
|
11
|
+
const LOCATION_KEY = 'dxos.org/settings/layout';
|
|
12
|
+
|
|
13
|
+
export default () => {
|
|
14
|
+
// TODO(wittjosiah): This active state is not a generic navigation state but quite deck specific.
|
|
15
|
+
// It is also closely tied to the layout mode state (which also seems quite deck specific).
|
|
16
|
+
// The layout and navigation interfaces need to be revisited and cleaned up.
|
|
17
|
+
// Doing this cleanup should also help simplify some of the convoluted logic for managing it.
|
|
18
|
+
const location = new LocalStorageStore<Capabilities.MutableLocation>(LOCATION_KEY, {
|
|
19
|
+
active: { sidebar: [{ id: NAV_ID }] },
|
|
20
|
+
closed: [],
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
location
|
|
24
|
+
.prop({ key: 'active', type: LocalStorageStore.json<LayoutParts>() })
|
|
25
|
+
.prop({ key: 'closed', type: LocalStorageStore.json<string[]>() });
|
|
26
|
+
|
|
27
|
+
return contributes(Capabilities.Location, location.values, () => location.close());
|
|
28
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { openIds, type Layout, type LayoutParts, type Capabilities } from '@dxos/app-framework';
|
|
6
|
+
import { type AttentionManager } from '@dxos/plugin-attention';
|
|
7
|
+
|
|
8
|
+
export type SetLocationOptions = {
|
|
9
|
+
next: LayoutParts;
|
|
10
|
+
location: Capabilities.MutableLocation;
|
|
11
|
+
layout: Layout;
|
|
12
|
+
attention?: AttentionManager;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export const setLocation = ({ next, location, layout, attention }: SetLocationOptions) => {
|
|
16
|
+
const part = layout.layoutMode === 'solo' ? 'solo' : 'main';
|
|
17
|
+
const ids = openIds(next, [part]);
|
|
18
|
+
|
|
19
|
+
const current = openIds(location.active, [part]);
|
|
20
|
+
const removed = current.filter((id) => !ids.includes(id));
|
|
21
|
+
const closed = Array.from(new Set([...location.closed.filter((id) => !ids.includes(id)), ...removed]));
|
|
22
|
+
|
|
23
|
+
location.closed = closed;
|
|
24
|
+
location.active = next;
|
|
25
|
+
|
|
26
|
+
if (attention) {
|
|
27
|
+
const attended = attention.current;
|
|
28
|
+
const [attendedId] = Array.from(attended);
|
|
29
|
+
const isAttendedAvailable = !!attendedId && ids.includes(attendedId);
|
|
30
|
+
if (!isAttendedAvailable) {
|
|
31
|
+
const currentIds = location.active[part]?.map(({ id }) => id) ?? [];
|
|
32
|
+
const attendedIndex = currentIds.indexOf(attendedId);
|
|
33
|
+
// If outside of bounds, focus on the first/last plank, otherwise focus on the new plank in the same position.
|
|
34
|
+
const index = attendedIndex === -1 ? 0 : attendedIndex >= ids.length ? ids.length - 1 : attendedIndex;
|
|
35
|
+
return next[part]?.[index].id;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
Capabilities,
|
|
7
|
+
contributes,
|
|
8
|
+
createIntent,
|
|
9
|
+
LayoutAction,
|
|
10
|
+
type LayoutParts,
|
|
11
|
+
type PluginsContext,
|
|
12
|
+
} from '@dxos/app-framework';
|
|
13
|
+
import { scheduledEffect } from '@dxos/echo-signals/core';
|
|
14
|
+
import { AttentionCapabilities } from '@dxos/plugin-attention';
|
|
15
|
+
|
|
16
|
+
import { setLocation as naturalSetLocation } from './set-location';
|
|
17
|
+
import { NAV_ID } from '../../components';
|
|
18
|
+
import { mergeLayoutParts, removePart, soloPartToUri, uriToSoloPart } from '../../layout';
|
|
19
|
+
|
|
20
|
+
export default async (context: PluginsContext) => {
|
|
21
|
+
const { dispatchPromise: dispatch } = context.requestCapability(Capabilities.IntentDispatcher) ?? {};
|
|
22
|
+
const location = context.requestCapability(Capabilities.MutableLocation);
|
|
23
|
+
const layout = context.requestCapability(Capabilities.MutableLayout);
|
|
24
|
+
const attention = context.requestCapability(AttentionCapabilities.Attention);
|
|
25
|
+
|
|
26
|
+
const handleNavigation = async () => {
|
|
27
|
+
const setLocation = (next: LayoutParts) => naturalSetLocation({ next, layout, location, attention });
|
|
28
|
+
|
|
29
|
+
const pathname = window.location.pathname;
|
|
30
|
+
if (pathname === '/reset') {
|
|
31
|
+
setLocation({ sidebar: [{ id: NAV_ID }] });
|
|
32
|
+
location.closed = [];
|
|
33
|
+
layout.layoutMode = 'solo';
|
|
34
|
+
window.location.pathname = '/';
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const startingLayout = removePart(location.active, 'solo');
|
|
39
|
+
const layoutFromUri = uriToSoloPart(pathname);
|
|
40
|
+
if (!layoutFromUri) {
|
|
41
|
+
const toAttend = setLocation(startingLayout);
|
|
42
|
+
layout.layoutMode = 'deck';
|
|
43
|
+
await dispatch?.(createIntent(LayoutAction.ScrollIntoView, { id: toAttend }));
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const toAttend = setLocation(mergeLayoutParts(layoutFromUri, startingLayout));
|
|
48
|
+
layout.layoutMode = 'solo';
|
|
49
|
+
await dispatch?.(createIntent(LayoutAction.ScrollIntoView, { id: toAttend }));
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
await handleNavigation();
|
|
53
|
+
window.addEventListener('popstate', handleNavigation);
|
|
54
|
+
|
|
55
|
+
const unsubscribe = scheduledEffect(
|
|
56
|
+
() => ({ selectedPath: soloPartToUri(location.active) }),
|
|
57
|
+
({ selectedPath }) => {
|
|
58
|
+
// TODO(thure): In some browsers, this only preserves the most recent state change, even though this is not `history.replace`…
|
|
59
|
+
history.pushState(null, '', `/${selectedPath}${window.location.search}`);
|
|
60
|
+
},
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
return contributes(Capabilities.Null, null, () => {
|
|
64
|
+
window.removeEventListener('popstate', handleNavigation);
|
|
65
|
+
unsubscribe();
|
|
66
|
+
});
|
|
67
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import React from 'react';
|
|
6
|
+
|
|
7
|
+
import { Capabilities, contributes, useCapability } from '@dxos/app-framework';
|
|
8
|
+
|
|
9
|
+
import { LayoutSettings } from '../../components';
|
|
10
|
+
import { DECK_PLUGIN } from '../../meta';
|
|
11
|
+
import { type DeckSettingsProps } from '../../types';
|
|
12
|
+
|
|
13
|
+
export default () =>
|
|
14
|
+
contributes(Capabilities.ReactSurface, {
|
|
15
|
+
id: DECK_PLUGIN,
|
|
16
|
+
role: 'settings',
|
|
17
|
+
filter: (data): data is any => data.subject === DECK_PLUGIN,
|
|
18
|
+
component: () => {
|
|
19
|
+
const store = useCapability(Capabilities.SettingsStore);
|
|
20
|
+
const settings = store.getStore<DeckSettingsProps>(DECK_PLUGIN)!.value;
|
|
21
|
+
return <LayoutSettings settings={settings} />;
|
|
22
|
+
},
|
|
23
|
+
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { Capabilities, contributes } from '@dxos/app-framework';
|
|
6
|
+
import { create } from '@dxos/live-object';
|
|
7
|
+
|
|
8
|
+
import { DECK_PLUGIN } from '../../meta';
|
|
9
|
+
import { DeckSettingsSchema, type DeckSettingsProps } from '../../types';
|
|
10
|
+
|
|
11
|
+
export default () => {
|
|
12
|
+
const settings = create<DeckSettingsProps>({
|
|
13
|
+
showHints: false,
|
|
14
|
+
customSlots: false,
|
|
15
|
+
flatDeck: false,
|
|
16
|
+
enableNativeRedirect: false,
|
|
17
|
+
newPlankPositioning: 'start',
|
|
18
|
+
overscroll: 'centering',
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
return contributes(Capabilities.Settings, { schema: DeckSettingsSchema, prefix: DECK_PLUGIN, value: settings });
|
|
22
|
+
};
|
|
@@ -4,10 +4,15 @@
|
|
|
4
4
|
|
|
5
5
|
import { type Context, createContext, useContext } from 'react';
|
|
6
6
|
|
|
7
|
+
import { type LayoutMode } from '@dxos/app-framework';
|
|
7
8
|
import { raise } from '@dxos/debug';
|
|
8
9
|
|
|
9
10
|
export type PlankSizing = Record<string, number>;
|
|
10
|
-
export type DeckContextType = {
|
|
11
|
+
export type DeckContextType = {
|
|
12
|
+
plankSizing: PlankSizing;
|
|
13
|
+
currentUndoId: string | undefined;
|
|
14
|
+
layoutModeHistory: LayoutMode[];
|
|
15
|
+
};
|
|
11
16
|
|
|
12
17
|
export const DeckContext: Context<DeckContextType | null> = createContext<DeckContextType | null>(null);
|
|
13
18
|
|
|
@@ -21,7 +21,7 @@ export const ActiveNode = () => {
|
|
|
21
21
|
<div role='none' className='sr-only'>
|
|
22
22
|
{/* TODO(wittjosiah): Weird that this is a surface, feel like it's not really render logic.
|
|
23
23
|
Probably this lives in React-land currently in order to access translations? */}
|
|
24
|
-
<Surface role='document-title' data={{ activeNode }} limit={1} />
|
|
24
|
+
<Surface role='document-title' data={{ subject: activeNode }} limit={1} />
|
|
25
25
|
</div>
|
|
26
26
|
);
|
|
27
27
|
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import React from 'react';
|
|
6
|
+
|
|
7
|
+
import { Surface } from '@dxos/app-framework';
|
|
8
|
+
import { type ThemedClassName } from '@dxos/react-ui';
|
|
9
|
+
import { mx } from '@dxos/react-ui-theme';
|
|
10
|
+
|
|
11
|
+
import { CloseSidebarButton, ToggleSidebarButton } from './SidebarButton';
|
|
12
|
+
|
|
13
|
+
export const Banner = ({ variant, classNames }: ThemedClassName<{ variant?: 'topbar' | 'sidebar' }>) => {
|
|
14
|
+
return (
|
|
15
|
+
<header
|
|
16
|
+
className={mx(
|
|
17
|
+
'flex items-stretch relative plb-1 pis-1 pie-2',
|
|
18
|
+
variant === 'topbar' &&
|
|
19
|
+
'fixed inset-inline-0 block-start-[env(safe-area-inset-top)] bs-[--rail-size] border-be border-separator',
|
|
20
|
+
classNames,
|
|
21
|
+
)}
|
|
22
|
+
>
|
|
23
|
+
{variant === 'sidebar' ? <CloseSidebarButton /> : <ToggleSidebarButton />}
|
|
24
|
+
<span className='self-center grow mis-1'>Composer</span>
|
|
25
|
+
{variant === 'topbar' && (
|
|
26
|
+
<div role='none' className='absolute inset-0 pointer-events-none'>
|
|
27
|
+
<div role='none' className='grid bs-full pointer-fine:p-1 max-is-md mli-auto pointer-events-auto'>
|
|
28
|
+
<Surface role='search-input' limit={1} />
|
|
29
|
+
</div>
|
|
30
|
+
</div>
|
|
31
|
+
)}
|
|
32
|
+
<span role='none' className='grow' />
|
|
33
|
+
<Surface role='header-end' limit={1} />
|
|
34
|
+
<Surface role='notch-start' limit={1} />
|
|
35
|
+
</header>
|
|
36
|
+
);
|
|
37
|
+
};
|
|
@@ -2,25 +2,21 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import React, { useMemo } from 'react';
|
|
5
|
+
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
|
6
6
|
|
|
7
|
-
import {
|
|
8
|
-
type LayoutCoordinate,
|
|
9
|
-
NavigationAction,
|
|
10
|
-
SLUG_PATH_SEPARATOR,
|
|
11
|
-
Surface,
|
|
12
|
-
useIntentDispatcher,
|
|
13
|
-
} from '@dxos/app-framework';
|
|
7
|
+
import { createIntent, NavigationAction, SLUG_PATH_SEPARATOR, Surface, useIntentDispatcher } from '@dxos/app-framework';
|
|
14
8
|
import { useGraph } from '@dxos/plugin-graph';
|
|
15
|
-
import { Main, ScrollArea } from '@dxos/react-ui';
|
|
9
|
+
import { Main, ScrollArea, useTranslation, toLocalizedString } from '@dxos/react-ui';
|
|
16
10
|
import { useAttended } from '@dxos/react-ui-attention';
|
|
17
|
-
import { railGridHorizontal, StackContext } from '@dxos/react-ui-stack';
|
|
11
|
+
import { railGridHorizontal, StackContext, StackItem } from '@dxos/react-ui-stack';
|
|
12
|
+
import { Tabs } from '@dxos/react-ui-tabs';
|
|
18
13
|
import { mx } from '@dxos/react-ui-theme';
|
|
19
14
|
|
|
20
|
-
import { NodePlankHeading } from './NodePlankHeading';
|
|
21
15
|
import { PlankContentError } from './PlankError';
|
|
22
16
|
import { PlankLoading } from './PlankLoading';
|
|
17
|
+
import { CloseComplementarySidebarButton } from './SidebarButton';
|
|
23
18
|
import { useNode, useNodeActionExpander } from '../../hooks';
|
|
19
|
+
import { DECK_PLUGIN } from '../../meta';
|
|
24
20
|
import { type Panel } from '../../types';
|
|
25
21
|
import { useLayout } from '../LayoutContext';
|
|
26
22
|
|
|
@@ -32,56 +28,83 @@ export type ComplementarySidebarProps = {
|
|
|
32
28
|
export const ComplementarySidebar = ({ panels, current }: ComplementarySidebarProps) => {
|
|
33
29
|
const { popoverAnchorId } = useLayout();
|
|
34
30
|
const attended = useAttended();
|
|
35
|
-
const
|
|
36
|
-
const
|
|
31
|
+
const panelIds = useMemo(() => panels.map((p) => p.id), [panels]);
|
|
32
|
+
const activePanelId = panelIds.find((p) => p === current) ?? panels[0].id;
|
|
33
|
+
const activeEntryId = attended[0] ? `${attended[0]}${SLUG_PATH_SEPARATOR}${activePanelId}` : undefined;
|
|
37
34
|
const { graph } = useGraph();
|
|
38
|
-
const node = useNode(graph,
|
|
39
|
-
const
|
|
35
|
+
const node = useNode(graph, activeEntryId);
|
|
36
|
+
const { t } = useTranslation(DECK_PLUGIN);
|
|
37
|
+
const { dispatchPromise: dispatch } = useIntentDispatcher();
|
|
40
38
|
useNodeActionExpander(node);
|
|
41
39
|
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
void dispatch({ action: NavigationAction.OPEN, data: { activeParts: { complementary: id } } });
|
|
48
|
-
},
|
|
49
|
-
properties: {
|
|
50
|
-
label,
|
|
51
|
-
icon,
|
|
52
|
-
menuItemType: 'toggle',
|
|
53
|
-
isChecked: panel === id,
|
|
54
|
-
},
|
|
55
|
-
})),
|
|
56
|
-
[panel],
|
|
57
|
-
);
|
|
40
|
+
const [internalValue, setInternalValue] = useState(activePanelId);
|
|
41
|
+
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
setInternalValue(activePanelId);
|
|
44
|
+
}, [activePanelId]);
|
|
58
45
|
|
|
59
|
-
|
|
60
|
-
|
|
46
|
+
const handleValueChange = useCallback(
|
|
47
|
+
(nextValue: string) => {
|
|
48
|
+
setInternalValue(nextValue);
|
|
49
|
+
void dispatch(createIntent(NavigationAction.Open, { activeParts: { complementary: nextValue } }));
|
|
50
|
+
},
|
|
51
|
+
[dispatch],
|
|
52
|
+
);
|
|
61
53
|
|
|
62
54
|
// TODO(burdon): Scroll area should be controlled by surface.
|
|
63
55
|
return (
|
|
64
|
-
<Main.ComplementarySidebar>
|
|
65
|
-
<StackContext.Provider value={{ size: 'contain', orientation: 'horizontal',
|
|
56
|
+
<Main.ComplementarySidebar classNames='lg:block-start-[calc(env(safe-area-inset-top)+var(--rail-size))]'>
|
|
57
|
+
<StackContext.Provider value={{ size: 'contain', orientation: 'horizontal', rail: true }}>
|
|
66
58
|
<div role='none' className={mx(railGridHorizontal, 'grid grid-cols-[100%] bs-full')}>
|
|
67
|
-
<
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
59
|
+
<Tabs.Root
|
|
60
|
+
orientation='horizontal'
|
|
61
|
+
value={internalValue}
|
|
62
|
+
onValueChange={handleValueChange}
|
|
63
|
+
attendableId={attended[0]}
|
|
64
|
+
classNames='contents'
|
|
65
|
+
>
|
|
66
|
+
<StackItem.Heading classNames='border-be border-separator grid grid-cols-[1fr_min-content] items-stretch'>
|
|
67
|
+
<ScrollArea.Root classNames='flex-1 min-is-0'>
|
|
68
|
+
<ScrollArea.Viewport>
|
|
69
|
+
<Tabs.Tablist classNames='bs-[--rail-content] is-min items-stretch pis-[max(.5rem,env(safe-area-inset-left))] sm:pis-2'>
|
|
70
|
+
{panels.map((panel) => (
|
|
71
|
+
<Tabs.Tab key={panel.id} value={panel.id} classNames='!min-bs-0'>
|
|
72
|
+
{toLocalizedString(panel.label, t)}
|
|
73
|
+
</Tabs.Tab>
|
|
74
|
+
))}
|
|
75
|
+
</Tabs.Tablist>
|
|
76
|
+
<ScrollArea.Scrollbar orientation='horizontal'>
|
|
77
|
+
<ScrollArea.Thumb />
|
|
78
|
+
</ScrollArea.Scrollbar>
|
|
79
|
+
</ScrollArea.Viewport>
|
|
80
|
+
</ScrollArea.Root>
|
|
81
|
+
<CloseComplementarySidebarButton />
|
|
82
|
+
</StackItem.Heading>
|
|
83
|
+
<ScrollArea.Root>
|
|
84
|
+
<ScrollArea.Viewport>
|
|
85
|
+
{panels.map((panel) => (
|
|
86
|
+
<Tabs.Tabpanel key={panel.id} value={panel.id} classNames='pbe-[env(safe-area-inset-bottom)]'>
|
|
87
|
+
{panel.id === activePanelId && node && (
|
|
88
|
+
<Surface
|
|
89
|
+
key={activeEntryId}
|
|
90
|
+
role={`complementary--${activePanelId}`}
|
|
91
|
+
data={{
|
|
92
|
+
id: activeEntryId,
|
|
93
|
+
subject: node.properties.object ?? node.properties.space,
|
|
94
|
+
popoverAnchorId,
|
|
95
|
+
}}
|
|
96
|
+
fallback={PlankContentError}
|
|
97
|
+
placeholder={<PlankLoading />}
|
|
98
|
+
/>
|
|
99
|
+
)}
|
|
100
|
+
</Tabs.Tabpanel>
|
|
101
|
+
))}
|
|
102
|
+
<ScrollArea.Scrollbar orientation='vertical'>
|
|
103
|
+
<ScrollArea.Thumb />
|
|
104
|
+
</ScrollArea.Scrollbar>
|
|
105
|
+
</ScrollArea.Viewport>
|
|
106
|
+
</ScrollArea.Root>
|
|
107
|
+
</Tabs.Root>
|
|
85
108
|
</div>
|
|
86
109
|
</StackContext.Provider>
|
|
87
110
|
</Main.ComplementarySidebar>
|
|
@@ -5,15 +5,23 @@
|
|
|
5
5
|
import React from 'react';
|
|
6
6
|
|
|
7
7
|
import { Surface } from '@dxos/app-framework';
|
|
8
|
+
import { mx } from '@dxos/react-ui-theme';
|
|
9
|
+
|
|
10
|
+
import { ToggleSidebarButton } from './SidebarButton';
|
|
11
|
+
import { soloInlinePadding } from '../fragments';
|
|
8
12
|
|
|
9
13
|
export const ContentEmpty = () => {
|
|
10
14
|
return (
|
|
11
15
|
<div
|
|
12
16
|
role='none'
|
|
13
|
-
className='min-bs-screen is-dvw sm:is-full
|
|
17
|
+
className='min-bs-screen is-dvw sm:is-full p-8 grid grid-rows-[var(--rail-size)_1fr]'
|
|
14
18
|
data-testid='layoutPlugin.firstRunMessage'
|
|
15
19
|
>
|
|
16
|
-
<div role='
|
|
20
|
+
<div role='toolbar' className={mx(soloInlinePadding, 'flex items-stretch')}>
|
|
21
|
+
<ToggleSidebarButton />
|
|
22
|
+
<span role='none' className='grow' />
|
|
23
|
+
</div>
|
|
24
|
+
<div role='none' className='grid place-items-center'>
|
|
17
25
|
<Surface role='keyshortcuts' />
|
|
18
26
|
</div>
|
|
19
27
|
</div>
|