@onerjs/shared-ui-components 8.48.4 → 8.48.5
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/fluent/hoc/buttonLine.js +2 -1
- package/fluent/hoc/buttonLine.js.map +1 -1
- package/fluent/primitives/contextMenu.js.map +1 -1
- package/fluent/primitives/popover.js.map +1 -1
- package/modularTool/components/errorBoundary.d.ts +31 -0
- package/modularTool/components/errorBoundary.js +91 -0
- package/modularTool/components/errorBoundary.js.map +1 -0
- package/modularTool/components/extensibleAccordion.d.ts +67 -0
- package/modularTool/components/extensibleAccordion.js +148 -0
- package/modularTool/components/extensibleAccordion.js.map +1 -0
- package/modularTool/components/pane.d.ts +4 -0
- package/modularTool/components/pane.js +20 -0
- package/modularTool/components/pane.js.map +1 -0
- package/modularTool/components/teachingMoment.d.ts +20 -0
- package/modularTool/components/teachingMoment.js +17 -0
- package/modularTool/components/teachingMoment.js.map +1 -0
- package/modularTool/components/theme.d.ts +10 -0
- package/modularTool/components/theme.js +24 -0
- package/modularTool/components/theme.js.map +1 -0
- package/modularTool/components/uxContextProvider.d.ts +2 -0
- package/modularTool/components/uxContextProvider.js +19 -0
- package/modularTool/components/uxContextProvider.js.map +1 -0
- package/modularTool/contexts/extensionManagerContext.d.ts +6 -0
- package/modularTool/contexts/extensionManagerContext.js +6 -0
- package/modularTool/contexts/extensionManagerContext.js.map +1 -0
- package/modularTool/contexts/settingsContext.d.ts +3 -0
- package/modularTool/contexts/settingsContext.js +6 -0
- package/modularTool/contexts/settingsContext.js.map +1 -0
- package/modularTool/extensibility/builtInsExtensionFeed.d.ts +21 -0
- package/modularTool/extensibility/builtInsExtensionFeed.js +26 -0
- package/modularTool/extensibility/builtInsExtensionFeed.js.map +1 -0
- package/modularTool/extensibility/extensionFeed.d.ts +113 -0
- package/modularTool/extensibility/extensionFeed.js +2 -0
- package/modularTool/extensibility/extensionFeed.js.map +1 -0
- package/modularTool/extensibility/extensionManager.d.ts +111 -0
- package/modularTool/extensibility/extensionManager.js +277 -0
- package/modularTool/extensibility/extensionManager.js.map +1 -0
- package/modularTool/hooks/observableHooks.d.ts +35 -0
- package/modularTool/hooks/observableHooks.js +84 -0
- package/modularTool/hooks/observableHooks.js.map +1 -0
- package/modularTool/hooks/resourceHooks.d.ts +20 -0
- package/modularTool/hooks/resourceHooks.js +101 -0
- package/modularTool/hooks/resourceHooks.js.map +1 -0
- package/modularTool/hooks/settingsHooks.d.ts +8 -0
- package/modularTool/hooks/settingsHooks.js +40 -0
- package/modularTool/hooks/settingsHooks.js.map +1 -0
- package/modularTool/hooks/teachingMomentHooks.d.ts +34 -0
- package/modularTool/hooks/teachingMomentHooks.js +89 -0
- package/modularTool/hooks/teachingMomentHooks.js.map +1 -0
- package/modularTool/hooks/themeHooks.d.ts +17 -0
- package/modularTool/hooks/themeHooks.js +38 -0
- package/modularTool/hooks/themeHooks.js.map +1 -0
- package/modularTool/hooks/useResizeHandle.d.ts +35 -0
- package/modularTool/hooks/useResizeHandle.js +75 -0
- package/modularTool/hooks/useResizeHandle.js.map +1 -0
- package/modularTool/misc/assert.d.ts +5 -0
- package/modularTool/misc/assert.js +10 -0
- package/modularTool/misc/assert.js.map +1 -0
- package/modularTool/misc/graphUtils.d.ts +44 -0
- package/modularTool/misc/graphUtils.js +90 -0
- package/modularTool/misc/graphUtils.js.map +1 -0
- package/modularTool/misc/observableCollection.d.ts +23 -0
- package/modularTool/misc/observableCollection.js +43 -0
- package/modularTool/misc/observableCollection.js.map +1 -0
- package/modularTool/modularTool.d.ts +42 -0
- package/modularTool/modularTool.js +223 -0
- package/modularTool/modularTool.js.map +1 -0
- package/modularTool/modularity/serviceContainer.d.ts +64 -0
- package/modularTool/modularity/serviceContainer.js +181 -0
- package/modularTool/modularity/serviceContainer.js.map +1 -0
- package/modularTool/modularity/serviceDefinition.d.ts +64 -0
- package/modularTool/modularity/serviceDefinition.js +11 -0
- package/modularTool/modularity/serviceDefinition.js.map +1 -0
- package/modularTool/services/extensionsListService.d.ts +3 -0
- package/modularTool/services/extensionsListService.js +202 -0
- package/modularTool/services/extensionsListService.js.map +1 -0
- package/modularTool/services/globalSettings.d.ts +3 -0
- package/modularTool/services/globalSettings.js +9 -0
- package/modularTool/services/globalSettings.js.map +1 -0
- package/modularTool/services/reactContextService.d.ts +18 -0
- package/modularTool/services/reactContextService.js +5 -0
- package/modularTool/services/reactContextService.js.map +1 -0
- package/modularTool/services/settingsService.d.ts +24 -0
- package/modularTool/services/settingsService.js +41 -0
- package/modularTool/services/settingsService.js.map +1 -0
- package/modularTool/services/settingsStore.d.ts +55 -0
- package/modularTool/services/settingsStore.js +35 -0
- package/modularTool/services/settingsStore.js.map +1 -0
- package/modularTool/services/shellService.d.ts +256 -0
- package/modularTool/services/shellService.js +729 -0
- package/modularTool/services/shellService.js.map +1 -0
- package/modularTool/services/shellSettingsService.d.ts +3 -0
- package/modularTool/services/shellSettingsService.js +35 -0
- package/modularTool/services/shellSettingsService.js.map +1 -0
- package/modularTool/services/themeSelectorService.d.ts +3 -0
- package/modularTool/services/themeSelectorService.js +42 -0
- package/modularTool/services/themeSelectorService.js.map +1 -0
- package/modularTool/services/themeService.d.ts +60 -0
- package/modularTool/services/themeService.js +69 -0
- package/modularTool/services/themeService.js.map +1 -0
- package/modularTool/themes/babylonTheme.d.ts +3 -0
- package/modularTool/themes/babylonTheme.js +36 -0
- package/modularTool/themes/babylonTheme.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,729 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { Button, Divider, Toolbar as FluentToolbar, makeStyles, Menu, MenuGroup, MenuGroupHeader, MenuItem, MenuList, MenuPopover, MenuTrigger, mergeClasses, SplitButton, Subtitle2Stronger, tokens, ToolbarRadioButton, } from "@fluentui/react-components";
|
|
3
|
+
import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
4
|
+
import { LayoutColumnTwoFocusLeftFilled, LayoutColumnTwoFocusRightFilled, LayoutColumnTwoSplitLeftFocusBottomLeftFilled, LayoutColumnTwoSplitLeftFocusTopLeftFilled, LayoutColumnTwoSplitRightFocusBottomRightFilled, LayoutColumnTwoSplitRightFocusTopRightFilled, MoreHorizontalRegular, PanelLeftContractRegular, PanelLeftExpandRegular, PanelRightContractRegular, PanelRightExpandRegular, PictureInPictureEnterRegular, } from "@fluentui/react-icons";
|
|
5
|
+
import { Fade as FluentFade } from "@fluentui/react-motion-components-preview";
|
|
6
|
+
import { Observable } from "@onerjs/core/Misc/observable.js";
|
|
7
|
+
import { ChildWindow } from "../../fluent/hoc/childWindow.js";
|
|
8
|
+
import { Collapse } from "../../fluent/primitives/collapse.js";
|
|
9
|
+
import { Tooltip } from "../../fluent/primitives/tooltip.js";
|
|
10
|
+
import { ErrorBoundary } from "../components/errorBoundary.js";
|
|
11
|
+
import { TeachingMoment } from "../components/teachingMoment.js";
|
|
12
|
+
import { Theme } from "../components/theme.js";
|
|
13
|
+
import { useOrderedObservableCollection } from "../hooks/observableHooks.js";
|
|
14
|
+
import { useSetting } from "../hooks/settingsHooks.js";
|
|
15
|
+
import { MakePopoverTeachingMoment } from "../hooks/teachingMomentHooks.js";
|
|
16
|
+
import { useResizeHandle } from "../hooks/useResizeHandle.js";
|
|
17
|
+
import { ObservableCollection } from "../misc/observableCollection.js";
|
|
18
|
+
/**
|
|
19
|
+
* Setting descriptor for persisting side pane dock location overrides.
|
|
20
|
+
*/
|
|
21
|
+
export const SidePaneDockOverridesSettingDescriptor = {
|
|
22
|
+
key: "SidePaneDockOverrides",
|
|
23
|
+
defaultValue: {},
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Setting descriptor for persisting the left side pane width adjustment.
|
|
27
|
+
*/
|
|
28
|
+
export const LeftSidePaneWidthAdjustSettingDescriptor = {
|
|
29
|
+
key: "Shell/LeftPane/WidthAdjust",
|
|
30
|
+
defaultValue: 0,
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Setting descriptor for persisting the left side pane height adjustment.
|
|
34
|
+
*/
|
|
35
|
+
export const LeftSidePaneHeightAdjustSettingDescriptor = {
|
|
36
|
+
key: "Shell/LeftPane/HeightAdjust",
|
|
37
|
+
defaultValue: 0,
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Setting descriptor for persisting the right side pane width adjustment.
|
|
41
|
+
*/
|
|
42
|
+
export const RightSidePaneWidthAdjustSettingDescriptor = {
|
|
43
|
+
key: "Shell/RightPane/WidthAdjust",
|
|
44
|
+
defaultValue: 0,
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* Setting descriptor for persisting the right side pane height adjustment.
|
|
48
|
+
*/
|
|
49
|
+
export const RightSidePaneHeightAdjustSettingDescriptor = {
|
|
50
|
+
key: "Shell/RightPane/HeightAdjust",
|
|
51
|
+
defaultValue: 0,
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* The unique identity symbol for the root component service.
|
|
55
|
+
*/
|
|
56
|
+
export const RootComponentServiceIdentity = Symbol("RootComponent");
|
|
57
|
+
/**
|
|
58
|
+
* The unique identity symbol for the shell service.
|
|
59
|
+
*/
|
|
60
|
+
export const ShellServiceIdentity = Symbol("ShellService");
|
|
61
|
+
const useStyles = makeStyles({
|
|
62
|
+
mainView: {
|
|
63
|
+
flex: 1,
|
|
64
|
+
display: "flex",
|
|
65
|
+
flexDirection: "column",
|
|
66
|
+
overflow: "hidden",
|
|
67
|
+
backgroundColor: tokens.colorTransparentBackground,
|
|
68
|
+
},
|
|
69
|
+
verticallyCentralContent: {
|
|
70
|
+
flexGrow: 1,
|
|
71
|
+
display: "flex",
|
|
72
|
+
overflow: "hidden",
|
|
73
|
+
backgroundColor: tokens.colorTransparentBackground,
|
|
74
|
+
},
|
|
75
|
+
barDiv: {
|
|
76
|
+
display: "flex",
|
|
77
|
+
flexDirection: "row",
|
|
78
|
+
flex: "0 0 auto",
|
|
79
|
+
height: "36px",
|
|
80
|
+
backgroundColor: tokens.colorNeutralBackground2,
|
|
81
|
+
pointerEvents: "auto",
|
|
82
|
+
},
|
|
83
|
+
bar: {
|
|
84
|
+
display: "flex",
|
|
85
|
+
flex: "1",
|
|
86
|
+
overflow: "hidden",
|
|
87
|
+
padding: `${tokens.spacingVerticalXXS} ${tokens.spacingHorizontalXXS}`,
|
|
88
|
+
borderBottom: `1px solid ${tokens.colorNeutralStroke2}`,
|
|
89
|
+
backgroundColor: tokens.colorNeutralBackground2,
|
|
90
|
+
},
|
|
91
|
+
barTop: {
|
|
92
|
+
borderTopWidth: 0,
|
|
93
|
+
},
|
|
94
|
+
barBottom: {
|
|
95
|
+
borderTop: `1px solid ${tokens.colorNeutralStroke2}`,
|
|
96
|
+
},
|
|
97
|
+
barLeft: {
|
|
98
|
+
marginRight: "auto",
|
|
99
|
+
display: "flex",
|
|
100
|
+
alignItems: "center",
|
|
101
|
+
flexDirection: "row",
|
|
102
|
+
columnGap: tokens.spacingHorizontalSNudge,
|
|
103
|
+
},
|
|
104
|
+
barRight: {
|
|
105
|
+
marginLeft: "auto",
|
|
106
|
+
display: "flex",
|
|
107
|
+
alignItems: "center",
|
|
108
|
+
flexDirection: "row-reverse",
|
|
109
|
+
columnGap: tokens.spacingHorizontalSNudge,
|
|
110
|
+
},
|
|
111
|
+
barItem: {
|
|
112
|
+
display: "flex",
|
|
113
|
+
},
|
|
114
|
+
paneTabListDiv: {
|
|
115
|
+
backgroundColor: tokens.colorNeutralBackground1,
|
|
116
|
+
flex: "0 0 auto",
|
|
117
|
+
display: "flex",
|
|
118
|
+
},
|
|
119
|
+
paneTabListDivLeft: {
|
|
120
|
+
flexDirection: "row-reverse",
|
|
121
|
+
},
|
|
122
|
+
paneTabListDivRight: {
|
|
123
|
+
flexDirection: "row",
|
|
124
|
+
},
|
|
125
|
+
paneCollapseButton: {
|
|
126
|
+
padding: `0 0 0 ${tokens.spacingHorizontalXS}`,
|
|
127
|
+
borderBottom: `1px solid ${tokens.colorNeutralStroke2}`,
|
|
128
|
+
},
|
|
129
|
+
paneCollapseButtonWithBorder: {
|
|
130
|
+
borderLeft: `1px solid ${tokens.colorNeutralStroke2}`,
|
|
131
|
+
},
|
|
132
|
+
collapseMenuPopover: {
|
|
133
|
+
minWidth: 0,
|
|
134
|
+
},
|
|
135
|
+
pane: {
|
|
136
|
+
backgroundColor: tokens.colorNeutralBackground2,
|
|
137
|
+
display: "flex",
|
|
138
|
+
flex: 1,
|
|
139
|
+
alignItems: "stretch",
|
|
140
|
+
overflow: "hidden",
|
|
141
|
+
},
|
|
142
|
+
paneLeft: {
|
|
143
|
+
flexDirection: "row",
|
|
144
|
+
},
|
|
145
|
+
paneRight: {
|
|
146
|
+
flexDirection: "row-reverse",
|
|
147
|
+
},
|
|
148
|
+
paneContainer: {
|
|
149
|
+
display: "flex",
|
|
150
|
+
flexDirection: "column",
|
|
151
|
+
overflowX: "hidden",
|
|
152
|
+
overflowY: "hidden",
|
|
153
|
+
zIndex: 1,
|
|
154
|
+
pointerEvents: "auto",
|
|
155
|
+
},
|
|
156
|
+
paneContent: {
|
|
157
|
+
display: "flex",
|
|
158
|
+
flexGrow: 1,
|
|
159
|
+
flexDirection: "column",
|
|
160
|
+
overflow: "hidden",
|
|
161
|
+
},
|
|
162
|
+
unselectedPane: {
|
|
163
|
+
display: "none",
|
|
164
|
+
},
|
|
165
|
+
paneHeaderDiv: {
|
|
166
|
+
display: "flex",
|
|
167
|
+
flexDirection: "row",
|
|
168
|
+
alignItems: "center",
|
|
169
|
+
height: "36px",
|
|
170
|
+
backgroundColor: tokens.colorNeutralBackground1,
|
|
171
|
+
color: tokens.colorNeutralForeground1,
|
|
172
|
+
},
|
|
173
|
+
paneHeaderText: {
|
|
174
|
+
flex: 1,
|
|
175
|
+
},
|
|
176
|
+
paneHeaderTextNoIcon: {
|
|
177
|
+
marginLeft: tokens.spacingHorizontalM,
|
|
178
|
+
},
|
|
179
|
+
paneHeaderIcon: {
|
|
180
|
+
display: "flex",
|
|
181
|
+
alignItems: "center",
|
|
182
|
+
justifyContent: "center",
|
|
183
|
+
height: "100%",
|
|
184
|
+
aspectRatio: "1",
|
|
185
|
+
fontSize: "20px",
|
|
186
|
+
},
|
|
187
|
+
paneHeaderButton: {
|
|
188
|
+
color: "inherit",
|
|
189
|
+
},
|
|
190
|
+
paneDivider: {
|
|
191
|
+
flex: "0 0 auto",
|
|
192
|
+
marginTop: tokens.spacingVerticalM,
|
|
193
|
+
margin: "0",
|
|
194
|
+
minHeight: tokens.spacingVerticalM,
|
|
195
|
+
cursor: "ns-resize",
|
|
196
|
+
alignItems: "end",
|
|
197
|
+
},
|
|
198
|
+
tabToolbar: {
|
|
199
|
+
padding: 0,
|
|
200
|
+
borderLeft: `1px solid ${tokens.colorNeutralStroke2}`,
|
|
201
|
+
borderRight: `1px solid ${tokens.colorNeutralStroke2}`,
|
|
202
|
+
},
|
|
203
|
+
tab: {
|
|
204
|
+
display: "flex",
|
|
205
|
+
height: "100%",
|
|
206
|
+
boxSizing: "border-box",
|
|
207
|
+
justifyContent: "center",
|
|
208
|
+
border: `1px solid ${tokens.colorNeutralStroke2}`,
|
|
209
|
+
borderTop: "none",
|
|
210
|
+
},
|
|
211
|
+
firstTab: {
|
|
212
|
+
borderLeftColor: "transparent",
|
|
213
|
+
},
|
|
214
|
+
lastTab: {
|
|
215
|
+
borderRightColor: "transparent",
|
|
216
|
+
},
|
|
217
|
+
selectedTab: {
|
|
218
|
+
borderBottom: "none",
|
|
219
|
+
},
|
|
220
|
+
unselectedTab: {
|
|
221
|
+
borderLeftColor: "transparent",
|
|
222
|
+
borderRightColor: "transparent",
|
|
223
|
+
},
|
|
224
|
+
tabRadioButton: {
|
|
225
|
+
backgroundColor: "transparent",
|
|
226
|
+
borderRadius: 0,
|
|
227
|
+
},
|
|
228
|
+
resizer: {
|
|
229
|
+
width: "8px",
|
|
230
|
+
cursor: "ew-resize",
|
|
231
|
+
zIndex: 1000,
|
|
232
|
+
},
|
|
233
|
+
resizerLeft: {
|
|
234
|
+
marginRight: "-8px",
|
|
235
|
+
transform: "translateX(-8px)",
|
|
236
|
+
},
|
|
237
|
+
resizerRight: {
|
|
238
|
+
marginLeft: "-8px",
|
|
239
|
+
transform: "translateX(8px)",
|
|
240
|
+
},
|
|
241
|
+
centralContent: {
|
|
242
|
+
position: "relative",
|
|
243
|
+
flexGrow: 1,
|
|
244
|
+
display: "flex",
|
|
245
|
+
overflow: "hidden",
|
|
246
|
+
backgroundColor: tokens.colorTransparentBackground,
|
|
247
|
+
"> *": {
|
|
248
|
+
pointerEvents: "auto",
|
|
249
|
+
},
|
|
250
|
+
},
|
|
251
|
+
expandButtonContainer: {
|
|
252
|
+
position: "absolute",
|
|
253
|
+
},
|
|
254
|
+
expandButtonContainerLeft: {
|
|
255
|
+
left: 0,
|
|
256
|
+
},
|
|
257
|
+
expandButtonContainerRight: {
|
|
258
|
+
right: 0,
|
|
259
|
+
},
|
|
260
|
+
expandButton: {},
|
|
261
|
+
});
|
|
262
|
+
const DockMenu = (props) => {
|
|
263
|
+
const { openOnContext, sidePaneId, dockOptions, children } = props;
|
|
264
|
+
const dockLeft = dockOptions.get("full-left");
|
|
265
|
+
const dockTopLeft = dockOptions.get("top-left");
|
|
266
|
+
const dockBottomLeft = dockOptions.get("bottom-left");
|
|
267
|
+
const dockRight = dockOptions.get("full-right");
|
|
268
|
+
const dockTopRight = dockOptions.get("top-right");
|
|
269
|
+
const dockBottomRight = dockOptions.get("bottom-right");
|
|
270
|
+
return (_jsxs(Menu, { openOnContext: openOnContext, children: [_jsx(MenuTrigger, { disableButtonEnhancement: true, children: children }), _jsx(Theme, { children: _jsx(MenuPopover, { children: _jsx(MenuList, { children: _jsxs(MenuGroup, { children: [_jsx(MenuGroupHeader, { children: "Dock" }), dockLeft && (_jsx(MenuItem, { icon: _jsx(LayoutColumnTwoFocusLeftFilled, {}), onClick: () => dockLeft(sidePaneId), children: "Left" })), dockTopLeft && (_jsx(MenuItem, { icon: _jsx(LayoutColumnTwoSplitLeftFocusTopLeftFilled, {}), onClick: () => dockTopLeft(sidePaneId), children: "Top Left" })), dockBottomLeft && (_jsx(MenuItem, { icon: _jsx(LayoutColumnTwoSplitLeftFocusBottomLeftFilled, {}), onClick: () => dockBottomLeft(sidePaneId), children: "Bottom Left" })), dockRight && (_jsx(MenuItem, { icon: _jsx(LayoutColumnTwoFocusRightFilled, {}), onClick: () => dockRight(sidePaneId), children: "Right" })), dockTopRight && (_jsx(MenuItem, { icon: _jsx(LayoutColumnTwoSplitRightFocusTopRightFilled, {}), onClick: () => dockTopRight(sidePaneId), children: "Top Right" })), dockBottomRight && (_jsx(MenuItem, { icon: _jsx(LayoutColumnTwoSplitRightFocusBottomRightFilled, {}), onClick: () => dockBottomRight(sidePaneId), children: "Bottom Right" }))] }) }) }) })] }));
|
|
271
|
+
};
|
|
272
|
+
const PaneHeader = (props) => {
|
|
273
|
+
const { id, title, dockOptions } = props;
|
|
274
|
+
const classes = useStyles();
|
|
275
|
+
return (_jsxs("div", { className: classes.paneHeaderDiv, children: [props.icon && (_jsx("div", { className: classes.paneHeaderIcon, children: _jsx(props.icon, {}) })), _jsx(Subtitle2Stronger, { className: mergeClasses(classes.paneHeaderText, !props.icon && classes.paneHeaderTextNoIcon), children: title }), _jsx(DockMenu, { sidePaneId: id, dockOptions: dockOptions, children: _jsx(Button, { className: classes.paneHeaderButton, appearance: "transparent", icon: _jsx(MoreHorizontalRegular, {}) }) })] }));
|
|
276
|
+
};
|
|
277
|
+
// This is a wrapper for an item in a toolbar that simply adds a teaching moment, which is useful for dynamically added items, possibly from extensions.
|
|
278
|
+
const ToolbarItem = (props) => {
|
|
279
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
280
|
+
const { verticalLocation, horizontalLocation, id, component: Component, displayName } = props;
|
|
281
|
+
const classes = useStyles();
|
|
282
|
+
const useTeachingMoment = useMemo(() => MakePopoverTeachingMoment(`Bar/${verticalLocation}/${horizontalLocation}/${displayName ?? id}`), [displayName, id]);
|
|
283
|
+
const teachingMoment = useTeachingMoment(props.teachingMoment === false);
|
|
284
|
+
const title = typeof props.teachingMoment === "object" ? props.teachingMoment.title : (displayName ?? id);
|
|
285
|
+
const description = typeof props.teachingMoment === "object" ? props.teachingMoment.description : `The "${displayName ?? id}" extension can be accessed here.`;
|
|
286
|
+
return (_jsxs(_Fragment, { children: [_jsx(TeachingMoment, { ...teachingMoment, shouldDisplay: teachingMoment.shouldDisplay, title: title, description: description }), _jsx("div", { className: classes.barItem, ref: teachingMoment.targetRef, children: _jsx(Component, {}) })] }));
|
|
287
|
+
};
|
|
288
|
+
// TODO: Handle overflow, possibly via https://react.fluentui.dev/?path=/docs/components-overflow--docs with priority.
|
|
289
|
+
// This component just renders a toolbar with left aligned toolbar items on the left and right aligned toolbar items on the right.
|
|
290
|
+
const Toolbar = ({ location, components }) => {
|
|
291
|
+
const classes = useStyles();
|
|
292
|
+
const leftComponents = useMemo(() => components.filter((entry) => entry.horizontalLocation === "left"), [components]);
|
|
293
|
+
const rightComponents = useMemo(() => components.filter((entry) => entry.horizontalLocation === "right"), [components]);
|
|
294
|
+
return (_jsx(_Fragment, { children: components.length > 0 && (_jsxs("div", { className: `${classes.bar} ${location === "top" ? classes.barTop : classes.barBottom}`, children: [_jsx("div", { className: classes.barLeft, children: leftComponents.map((entry) => (_jsx(ToolbarItem, { verticalLocation: location, horizontalLocation: entry.horizontalLocation, id: entry.key, component: entry.component, displayName: entry.displayName, teachingMoment: entry.teachingMoment }, entry.key))) }), _jsx("div", { className: classes.barRight, children: rightComponents.map((entry) => (_jsx(ToolbarItem, { verticalLocation: location, horizontalLocation: entry.horizontalLocation, id: entry.key, component: entry.component, displayName: entry.displayName, teachingMoment: entry.teachingMoment }, entry.key))) })] })) }));
|
|
295
|
+
};
|
|
296
|
+
// This is a wrapper for a tab in a side pane that simply adds a teaching moment, which is useful for dynamically added items, possibly from extensions.
|
|
297
|
+
const SidePaneTab = (props) => {
|
|
298
|
+
const { location, id, isSelected, isFirst, isLast, dockOptions,
|
|
299
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
300
|
+
icon: Icon, title, } = props;
|
|
301
|
+
const classes = useStyles();
|
|
302
|
+
const useTeachingMoment = useMemo(() => MakePopoverTeachingMoment(`Pane/${location}/${title ?? id}`), [title, id]);
|
|
303
|
+
const teachingMoment = useTeachingMoment(props.teachingMoment === false);
|
|
304
|
+
const tabClass = mergeClasses(classes.tab, isSelected ? classes.selectedTab : classes.unselectedTab, isFirst ? classes.firstTab : undefined, isLast ? classes.lastTab : undefined);
|
|
305
|
+
return (_jsxs(_Fragment, { children: [_jsx(TeachingMoment, { ...teachingMoment, shouldDisplay: teachingMoment.shouldDisplay, title: typeof props.teachingMoment === "object" ? props.teachingMoment.title : (title ?? "Extension"), description: typeof props.teachingMoment === "object" ? props.teachingMoment.description : `The "${title ?? id}" extension can be accessed here.` }), _jsx("div", { className: tabClass, children: _jsx(DockMenu, { openOnContext: true, sidePaneId: id, dockOptions: dockOptions, children: _jsx(Tooltip, { content: title ?? id, children: _jsx(ToolbarRadioButton, { ref: teachingMoment.targetRef, appearance: "transparent", className: classes.tabRadioButton, name: "selectedTab", value: id, icon: {
|
|
306
|
+
children: _jsx(Icon, {}),
|
|
307
|
+
} }) }) }) })] }));
|
|
308
|
+
};
|
|
309
|
+
// This hook provides a side pane container and the tab list.
|
|
310
|
+
// In "compact" mode, the tab list is integrated into the pane itself.
|
|
311
|
+
// In "full" mode, the returned tab list is later injected into the toolbar.
|
|
312
|
+
function usePane(location, defaultWidth, minWidth, sidePanes, onSelectSidePane, dockOperations, toolbarMode, topBarItems, bottomBarItems, initialCollapsed) {
|
|
313
|
+
const classes = useStyles();
|
|
314
|
+
const [topSelectedTab, setTopSelectedTab] = useState();
|
|
315
|
+
const [bottomSelectedTab, setBottomSelectedTab] = useState();
|
|
316
|
+
const [collapsed, setCollapsed] = useState(initialCollapsed);
|
|
317
|
+
const childWindow = useRef(null);
|
|
318
|
+
const [isChildWindowOpen, setIsChildWindowOpen] = useState(false);
|
|
319
|
+
const paneContainerRef = useRef(null);
|
|
320
|
+
const onExpandCollapseClick = useCallback(() => {
|
|
321
|
+
setCollapsed((collapsed) => !collapsed);
|
|
322
|
+
}, []);
|
|
323
|
+
const [paneWidthSetting, setPaneWidthSetting] = useSetting(location === "left" ? LeftSidePaneWidthAdjustSettingDescriptor : RightSidePaneWidthAdjustSettingDescriptor);
|
|
324
|
+
const [paneHeightSetting, setPaneHeightSetting] = useSetting(location === "left" ? LeftSidePaneHeightAdjustSettingDescriptor : RightSidePaneHeightAdjustSettingDescriptor);
|
|
325
|
+
const currentSidePanes = useMemo(() => sidePanes.filter((entry) => entry.horizontalLocation === location), [sidePanes, location]);
|
|
326
|
+
const topPanes = useMemo(() => currentSidePanes.filter((entry) => entry.verticalLocation === "top"), [currentSidePanes]);
|
|
327
|
+
const bottomPanes = useMemo(() => currentSidePanes.filter((entry) => entry.verticalLocation === "bottom"), [currentSidePanes]);
|
|
328
|
+
const getValidDockOperations = useCallback((verticalLocation) => {
|
|
329
|
+
const validDockOperations = new Map(dockOperations);
|
|
330
|
+
// Can't re-dock to the current location.
|
|
331
|
+
validDockOperations.delete(`${verticalLocation}-${location}`);
|
|
332
|
+
// Full would mean there are no bottom panes, so this is also re-docking to the current location.
|
|
333
|
+
validDockOperations.delete(`full-${location}`);
|
|
334
|
+
// If there is only one pane left, it can't be docked to the bottom (as this would leave no top panes).
|
|
335
|
+
if (currentSidePanes.length === 1) {
|
|
336
|
+
validDockOperations.delete(`bottom-${location}`);
|
|
337
|
+
}
|
|
338
|
+
return validDockOperations;
|
|
339
|
+
}, [location, dockOperations, currentSidePanes]);
|
|
340
|
+
const validTopDockOptions = useMemo(() => getValidDockOperations("top"), [getValidDockOperations]);
|
|
341
|
+
const validBottomDockOptions = useMemo(() => getValidDockOperations("bottom"), [getValidDockOperations]);
|
|
342
|
+
// Selects a default top tab (during initialization or if the selected tab is removed).
|
|
343
|
+
useEffect(() => {
|
|
344
|
+
if ((topSelectedTab && !topPanes.includes(topSelectedTab)) || (!topSelectedTab && topPanes.length > 0)) {
|
|
345
|
+
setTopSelectedTab(topPanes[0]);
|
|
346
|
+
}
|
|
347
|
+
else if (topSelectedTab && topPanes.length === 0) {
|
|
348
|
+
setTopSelectedTab(undefined);
|
|
349
|
+
}
|
|
350
|
+
}, [topSelectedTab, topPanes]);
|
|
351
|
+
// Selects a default bottom tab (during initialization or if the selected tab is removed).
|
|
352
|
+
useEffect(() => {
|
|
353
|
+
if ((bottomSelectedTab && !bottomPanes.includes(bottomSelectedTab)) || (!bottomSelectedTab && bottomPanes.length > 0)) {
|
|
354
|
+
setBottomSelectedTab(bottomPanes[0]);
|
|
355
|
+
}
|
|
356
|
+
else if (bottomSelectedTab && bottomPanes.length === 0) {
|
|
357
|
+
setBottomSelectedTab(undefined);
|
|
358
|
+
}
|
|
359
|
+
}, [bottomSelectedTab, bottomPanes]);
|
|
360
|
+
// Selects a tab when explicitly requested.
|
|
361
|
+
useEffect(() => {
|
|
362
|
+
const observer = onSelectSidePane.add((key) => {
|
|
363
|
+
const topPane = topPanes.find((entry) => entry.key === key);
|
|
364
|
+
if (topPane) {
|
|
365
|
+
setTopSelectedTab(topPane);
|
|
366
|
+
setCollapsed(false);
|
|
367
|
+
}
|
|
368
|
+
const bottomPane = bottomPanes.find((entry) => entry.key === key);
|
|
369
|
+
if (bottomPane) {
|
|
370
|
+
setBottomSelectedTab(bottomPane);
|
|
371
|
+
setCollapsed(false);
|
|
372
|
+
}
|
|
373
|
+
});
|
|
374
|
+
return () => observer.remove();
|
|
375
|
+
}, [topPanes, bottomPanes, onSelectSidePane]);
|
|
376
|
+
const setUndocked = useCallback((undocked) => {
|
|
377
|
+
if (!undocked) {
|
|
378
|
+
childWindow.current?.close();
|
|
379
|
+
}
|
|
380
|
+
else {
|
|
381
|
+
const paneContainer = paneContainerRef.current;
|
|
382
|
+
if (!paneContainer) {
|
|
383
|
+
// It shouldn't be possible to get here and have this ref be null, but just in case,
|
|
384
|
+
// bail out of the undock operation.
|
|
385
|
+
childWindow.current?.close();
|
|
386
|
+
}
|
|
387
|
+
else {
|
|
388
|
+
// This is the extra buffer needed on top of minWidth to account for window chrome to avoid a horizontal scrollbar.
|
|
389
|
+
const widthBuffer = 4;
|
|
390
|
+
// This offsets the window's top position to account for window chrome/title bar.
|
|
391
|
+
const topOffset = 100;
|
|
392
|
+
// Create the child window with approximately the same location and size as the side pane.
|
|
393
|
+
const bounds = paneContainer.getBoundingClientRect();
|
|
394
|
+
childWindow.current?.open({
|
|
395
|
+
defaultWidth: Math.max(bounds.width, minWidth + widthBuffer),
|
|
396
|
+
defaultHeight: bounds.height - topOffset,
|
|
397
|
+
defaultTop: bounds.top + window.screenY + topOffset,
|
|
398
|
+
defaultLeft: bounds.left + window.screenX,
|
|
399
|
+
title: location === "left" ? "Left" : "Right",
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}, [childWindow, location]);
|
|
404
|
+
const expandCollapseButton = useMemo(() => {
|
|
405
|
+
const expandCollapseIcon = location === "left" ? collapsed ? _jsx(PanelLeftExpandRegular, {}) : _jsx(PanelLeftContractRegular, {}) : collapsed ? _jsx(PanelRightExpandRegular, {}) : _jsx(PanelRightContractRegular, {});
|
|
406
|
+
return (_jsxs(Menu, { positioning: "below-end", children: [_jsx(MenuTrigger, { disableButtonEnhancement: true, children: (triggerProps) => (_jsx(Tooltip, { content: collapsed ? "Show Side Pane" : "Hide Side Pane", children: _jsx(SplitButton, { className: mergeClasses(classes.paneCollapseButton, location === "right" && toolbarMode === "compact" ? classes.paneCollapseButtonWithBorder : undefined), menuButton: triggerProps, primaryActionButton: { onClick: onExpandCollapseClick }, size: "small", appearance: "transparent", icon: expandCollapseIcon }) })) }), _jsx(MenuPopover, { className: classes.collapseMenuPopover, children: _jsx(MenuList, { children: _jsx(MenuItem, { icon: _jsx(PictureInPictureEnterRegular, {}), onClick: () => setUndocked(true), children: "Undock" }) }) })] }));
|
|
407
|
+
}, [collapsed, onExpandCollapseClick, location]);
|
|
408
|
+
const createPaneTabList = useCallback((paneComponents, toolbarMode, selectedTab, setSelectedTab, dockOptions) => {
|
|
409
|
+
return (_jsx(_Fragment, { children: paneComponents.length > 0 && (_jsxs("div", { className: `${classes.paneTabListDiv} ${location === "left" || toolbarMode === "compact" ? classes.paneTabListDivLeft : classes.paneTabListDivRight}`, children: [paneComponents.length > 1 && (_jsx(_Fragment, { children: _jsx(FluentToolbar, { className: classes.tabToolbar, checkedValues: { selectedTab: [selectedTab?.key ?? ""] }, onCheckedValueChange: (event, data) => {
|
|
410
|
+
const tab = paneComponents.find((entry) => entry.key === data.checkedItems[0]);
|
|
411
|
+
setSelectedTab(tab);
|
|
412
|
+
setCollapsed(false);
|
|
413
|
+
}, children: paneComponents.map((entry, index) => {
|
|
414
|
+
const isSelected = selectedTab?.key === entry.key;
|
|
415
|
+
return (_jsx(SidePaneTab, { location: location, id: entry.key, title: entry.title, icon: entry.icon, teachingMoment: entry.teachingMoment, isSelected: isSelected && !collapsed, isFirst: index === 0, isLast: index === paneComponents.length - 1, dockOptions: dockOptions }, entry.key));
|
|
416
|
+
}) }) })), toolbarMode === "full" && (_jsx(Collapse, { visible: !isChildWindowOpen, orientation: "horizontal", children: expandCollapseButton }))] })) }));
|
|
417
|
+
}, [location, collapsed, isChildWindowOpen, expandCollapseButton]);
|
|
418
|
+
// This memos the TabList to make it easy for the JSX to be inserted at the top of the pane (in "compact" mode) or returned to the caller to be used in the toolbar (in "full" mode).
|
|
419
|
+
const topPaneTabList = useMemo(() => createPaneTabList(topPanes, toolbarMode, topSelectedTab, setTopSelectedTab, validTopDockOptions), [createPaneTabList, topPanes, toolbarMode, topSelectedTab]);
|
|
420
|
+
const bottomPaneTabList = useMemo(() => createPaneTabList(bottomPanes, "compact", bottomSelectedTab, setBottomSelectedTab, validBottomDockOptions), [createPaneTabList, bottomPanes, bottomSelectedTab]);
|
|
421
|
+
// This manages the CSS variable that controls the width of the side pane.
|
|
422
|
+
const paneWidthAdjustCSSVar = "--pane-width-adjust";
|
|
423
|
+
const { elementRef: paneHorizontalResizeElementRef, handleRef: paneHorizontalResizeHandleRef, setValue: setPaneWidthAdjust, } = useResizeHandle({
|
|
424
|
+
growDirection: location === "left" ? "end" : "start",
|
|
425
|
+
variableName: paneWidthAdjustCSSVar,
|
|
426
|
+
minValue: minWidth - defaultWidth,
|
|
427
|
+
onChange: (value) => {
|
|
428
|
+
// Whenever the width is adjusted, store the value.
|
|
429
|
+
setPaneWidthSetting(value);
|
|
430
|
+
},
|
|
431
|
+
});
|
|
432
|
+
// This manages the CSS variable that controls the height of the bottom pane.
|
|
433
|
+
const paneHeightAdjustCSSVar = "--pane-height-adjust";
|
|
434
|
+
const { elementRef: paneVerticalResizeElementRef, handleRef: paneVerticalResizeHandleRef, setValue: setPaneHeightAdjust, } = useResizeHandle({
|
|
435
|
+
growDirection: "up",
|
|
436
|
+
variableName: paneHeightAdjustCSSVar,
|
|
437
|
+
onChange: (value) => {
|
|
438
|
+
// Whenever the height is adjusted, store the value.
|
|
439
|
+
setPaneHeightSetting(value);
|
|
440
|
+
},
|
|
441
|
+
});
|
|
442
|
+
// This ensures that when the component is first rendered, the CSS variable is set from storage.
|
|
443
|
+
useLayoutEffect(() => {
|
|
444
|
+
setPaneWidthAdjust(paneWidthSetting);
|
|
445
|
+
setPaneHeightAdjust(paneHeightSetting);
|
|
446
|
+
}, [paneWidthSetting, paneHeightSetting]);
|
|
447
|
+
// This effect closes the window if all panes have been removed.
|
|
448
|
+
useEffect(() => {
|
|
449
|
+
if (isChildWindowOpen && topPanes.length === 0 && bottomPanes.length === 0) {
|
|
450
|
+
childWindow.current?.close();
|
|
451
|
+
}
|
|
452
|
+
}, [childWindow, isChildWindowOpen, topPanes, bottomPanes]);
|
|
453
|
+
// This memoizes the pane itself, which may or may not include the tab list, depending on the toolbar mode.
|
|
454
|
+
const corePane = useMemo(() => {
|
|
455
|
+
return (_jsxs(_Fragment, { children: [toolbarMode === "compact" && (topPanes.length > 1 || topBarItems.length > 0) && (_jsx(_Fragment, { children: _jsxs("div", { className: classes.barDiv, children: [!isChildWindowOpen && location === "left" && expandCollapseButton, topPaneTabList, _jsx(Toolbar, { location: "top", components: topBarItems }), !isChildWindowOpen && location === "right" && expandCollapseButton] }) })), topPanes.length > 0 && (_jsx("div", { className: classes.paneContent, children: topSelectedTab && (_jsxs(_Fragment, { children: [_jsx(PaneHeader, { id: topSelectedTab.key, title: topSelectedTab.title, icon: topPanes.length > 1 ? undefined : topSelectedTab.icon, dockOptions: validTopDockOptions }), topPanes
|
|
456
|
+
.filter((pane) => pane.key === topSelectedTab.key || pane.keepMounted)
|
|
457
|
+
.map((pane) => (_jsx("div", { className: mergeClasses(classes.paneContent, pane.key !== topSelectedTab.key ? classes.unselectedPane : undefined), children: _jsx(ErrorBoundary, { name: pane.title, children: _jsx(pane.content, {}) }) }, pane.key)))] })) })), topPanes.length > 0 && bottomPanes.length > 0 && _jsx(Divider, { ref: paneVerticalResizeHandleRef, className: classes.paneDivider }), bottomPanes.length > 1 && (_jsx(_Fragment, { children: _jsx("div", { className: classes.barDiv, children: bottomPaneTabList }) })), bottomPanes.length > 0 && (_jsx("div", { ref: paneVerticalResizeElementRef, className: classes.paneContent, style: { height: `clamp(200px, calc(45% + var(${paneHeightAdjustCSSVar}, 0px)), 100% - 300px)`, flex: "0 0 auto" }, children: bottomSelectedTab && (_jsxs(_Fragment, { children: [_jsx(PaneHeader, { id: bottomSelectedTab.key, title: bottomSelectedTab.title, icon: bottomPanes.length > 1 ? undefined : bottomSelectedTab.icon, dockOptions: validBottomDockOptions }), bottomPanes
|
|
458
|
+
.filter((pane) => pane.key === bottomSelectedTab.key || pane.keepMounted)
|
|
459
|
+
.map((pane) => (_jsx("div", { className: mergeClasses(classes.paneContent, pane.key !== bottomSelectedTab.key ? classes.unselectedPane : undefined), children: _jsx(ErrorBoundary, { name: pane.title, children: _jsx(pane.content, {}) }) }, pane.key)))] })) })), toolbarMode === "compact" && bottomBarItems.length > 0 && (_jsx(_Fragment, { children: _jsx("div", { className: classes.barDiv, children: _jsx(Toolbar, { location: "bottom", components: bottomBarItems }) }) }))] }));
|
|
460
|
+
}, [
|
|
461
|
+
topPanes,
|
|
462
|
+
topSelectedTab,
|
|
463
|
+
validTopDockOptions,
|
|
464
|
+
bottomPanes,
|
|
465
|
+
bottomSelectedTab,
|
|
466
|
+
validBottomDockOptions,
|
|
467
|
+
topBarItems,
|
|
468
|
+
bottomBarItems,
|
|
469
|
+
topPaneTabList,
|
|
470
|
+
bottomPaneTabList,
|
|
471
|
+
isChildWindowOpen,
|
|
472
|
+
]);
|
|
473
|
+
// This deals with docked vs undocked state, where undocked is rendered into a separate window via a portal.
|
|
474
|
+
const pane = useMemo(() => {
|
|
475
|
+
return (_jsxs(_Fragment, { children: [!isChildWindowOpen && (_jsx("div", { ref: paneContainerRef, className: classes.paneContainer, children: (topPanes.length > 0 || bottomPanes.length > 0) && (_jsxs("div", { className: `${classes.pane} ${location === "left" ? classes.paneLeft : classes.paneRight}`, children: [_jsx(Collapse, { orientation: "horizontal", visible: !collapsed, children: _jsx("div", { ref: paneHorizontalResizeElementRef, className: classes.paneContainer, style: { width: `clamp(${minWidth}px, calc(${defaultWidth}px + var(${paneWidthAdjustCSSVar}, 0px)), 1000px)` }, children: corePane }) }), _jsx("div", { ref: paneHorizontalResizeHandleRef, className: `${classes.resizer} ${location === "left" ? classes.resizerLeft : classes.resizerRight}`, style: { pointerEvents: `${collapsed ? "none" : "auto"}` } })] })) })), _jsx(ChildWindow, { imperativeRef: childWindow, onOpenChange: (isOpen) => setIsChildWindowOpen(isOpen), children: corePane })] }));
|
|
476
|
+
}, [collapsed, corePane]);
|
|
477
|
+
const hasPanes = topPanes.length > 0 || bottomPanes.length > 0;
|
|
478
|
+
return [topPaneTabList, pane, collapsed, setCollapsed, isChildWindowOpen, setUndocked, hasPanes];
|
|
479
|
+
}
|
|
480
|
+
export function MakeShellServiceDefinition({ leftPaneDefaultWidth = 350, leftPaneMinWidth = 350, rightPaneDefaultWidth = 350, rightPaneMinWidth = 350, leftPaneDefaultCollapsed = false, rightPaneDefaultCollapsed = false, toolbarMode = "full", sidePaneRemapper = undefined, } = {}) {
|
|
481
|
+
return {
|
|
482
|
+
friendlyName: "Shell Service",
|
|
483
|
+
produces: [ShellServiceIdentity, RootComponentServiceIdentity],
|
|
484
|
+
factory: () => {
|
|
485
|
+
const toolbarItemCollection = new ObservableCollection();
|
|
486
|
+
const sidePaneCollection = new ObservableCollection();
|
|
487
|
+
const centralContentCollection = new ObservableCollection();
|
|
488
|
+
const onSelectSidePane = new Observable(undefined, true);
|
|
489
|
+
const onDockChanged = new Observable(undefined, true);
|
|
490
|
+
const onCollapseChanged = new Observable();
|
|
491
|
+
const leftSidePaneContainerState = {
|
|
492
|
+
isPresent: false,
|
|
493
|
+
isDocked: true,
|
|
494
|
+
dock: () => onDockChanged.notifyObservers({ location: "left", dock: true }),
|
|
495
|
+
undock: () => onDockChanged.notifyObservers({ location: "left", dock: false }),
|
|
496
|
+
isCollapsed: leftPaneDefaultCollapsed,
|
|
497
|
+
collapse: () => onCollapseChanged.notifyObservers({ location: "left", collapsed: true }),
|
|
498
|
+
expand: () => onCollapseChanged.notifyObservers({ location: "left", collapsed: false }),
|
|
499
|
+
};
|
|
500
|
+
const rightSidePaneContainerState = {
|
|
501
|
+
isPresent: false,
|
|
502
|
+
isDocked: true,
|
|
503
|
+
dock: () => onDockChanged.notifyObservers({ location: "right", dock: true }),
|
|
504
|
+
undock: () => onDockChanged.notifyObservers({ location: "right", dock: false }),
|
|
505
|
+
isCollapsed: rightPaneDefaultCollapsed,
|
|
506
|
+
collapse: () => onCollapseChanged.notifyObservers({ location: "right", collapsed: true }),
|
|
507
|
+
expand: () => onCollapseChanged.notifyObservers({ location: "right", collapsed: false }),
|
|
508
|
+
};
|
|
509
|
+
const rootComponent = () => {
|
|
510
|
+
const classes = useStyles();
|
|
511
|
+
const [sidePaneDockOverrides, setSidePaneDockOverrides] = useSetting(SidePaneDockOverridesSettingDescriptor);
|
|
512
|
+
// This function returns a promise that resolves after the dock change takes effect so that
|
|
513
|
+
// we can then select the re-docked pane.
|
|
514
|
+
const pendingPaneReselects = useRef([]);
|
|
515
|
+
const updateSidePaneDockOverride = useCallback((key, horizontalLocation, verticalLocation) => {
|
|
516
|
+
setSidePaneDockOverrides((current) => ({
|
|
517
|
+
...current,
|
|
518
|
+
[key]: { horizontalLocation, verticalLocation },
|
|
519
|
+
}));
|
|
520
|
+
pendingPaneReselects.current.push(key);
|
|
521
|
+
}, [setSidePaneDockOverrides]);
|
|
522
|
+
const toolbarItems = useOrderedObservableCollection(toolbarItemCollection);
|
|
523
|
+
const sidePanes = useOrderedObservableCollection(sidePaneCollection);
|
|
524
|
+
const coercedSidePaneCache = useRef(new Map());
|
|
525
|
+
const coercedSidePanes = useMemo(() => {
|
|
526
|
+
// First pass - apply overrides and respect the side pane mode.
|
|
527
|
+
const coercedSidePanes = sidePanes
|
|
528
|
+
.map((sidePaneDefinition) => {
|
|
529
|
+
let coercedSidePane = coercedSidePaneCache.current.get(sidePaneDefinition.key);
|
|
530
|
+
if (!coercedSidePane) {
|
|
531
|
+
coercedSidePane = { ...sidePaneDefinition };
|
|
532
|
+
coercedSidePaneCache.current.set(sidePaneDefinition.key, coercedSidePane);
|
|
533
|
+
}
|
|
534
|
+
const override = sidePaneDockOverrides[sidePaneDefinition.key];
|
|
535
|
+
if (override) {
|
|
536
|
+
// Override (user manually re-docked) has the highest priority.
|
|
537
|
+
coercedSidePane.horizontalLocation = override.horizontalLocation;
|
|
538
|
+
coercedSidePane.verticalLocation = override.verticalLocation;
|
|
539
|
+
}
|
|
540
|
+
else if (sidePaneRemapper) {
|
|
541
|
+
// A side pane remapper has the next highest priority.
|
|
542
|
+
const remapping = sidePaneRemapper(sidePaneDefinition);
|
|
543
|
+
if (!remapping) {
|
|
544
|
+
coercedSidePane = undefined;
|
|
545
|
+
}
|
|
546
|
+
else {
|
|
547
|
+
coercedSidePane.horizontalLocation = remapping.horizontalLocation;
|
|
548
|
+
coercedSidePane.verticalLocation = remapping.verticalLocation;
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
else {
|
|
552
|
+
// Otherwise use the default defined location.
|
|
553
|
+
coercedSidePane.horizontalLocation = sidePaneDefinition.horizontalLocation;
|
|
554
|
+
coercedSidePane.verticalLocation = sidePaneDefinition.verticalLocation;
|
|
555
|
+
}
|
|
556
|
+
return coercedSidePane;
|
|
557
|
+
})
|
|
558
|
+
.filter((sidePane) => !!sidePane);
|
|
559
|
+
// Second pass - correct any invalid state, specifically if there are only bottom panes, force them to be top panes.
|
|
560
|
+
for (const side of ["left", "right"]) {
|
|
561
|
+
const topPanes = coercedSidePanes.filter((entry) => entry.horizontalLocation === side && entry.verticalLocation === "top");
|
|
562
|
+
const bottomPanes = coercedSidePanes.filter((entry) => entry.horizontalLocation === side && entry.verticalLocation === "bottom");
|
|
563
|
+
if (bottomPanes.length > 0 && topPanes.length === 0) {
|
|
564
|
+
for (const pane of bottomPanes) {
|
|
565
|
+
pane.verticalLocation = "top";
|
|
566
|
+
updateSidePaneDockOverride(pane.key, side, "top");
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
// Cleanup any cached panes that are no longer present.
|
|
571
|
+
for (const key of coercedSidePaneCache.current.keys()) {
|
|
572
|
+
if (!coercedSidePanes.some((entry) => entry.key === key)) {
|
|
573
|
+
coercedSidePaneCache.current.delete(key);
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
return coercedSidePanes;
|
|
577
|
+
}, [sidePanes, sidePaneDockOverrides, updateSidePaneDockOverride, sidePaneRemapper]);
|
|
578
|
+
useEffect(() => {
|
|
579
|
+
for (const paneKey of pendingPaneReselects.current.splice(0)) {
|
|
580
|
+
onSelectSidePane.notifyObservers(paneKey);
|
|
581
|
+
}
|
|
582
|
+
}, [coercedSidePanes]);
|
|
583
|
+
const sidePaneDockOperations = useMemo(() => {
|
|
584
|
+
const sidePaneDockOperations = new Map();
|
|
585
|
+
for (const side of ["left", "right"]) {
|
|
586
|
+
const currentSidePanes = coercedSidePanes.filter((entry) => entry.horizontalLocation === side);
|
|
587
|
+
const dockTop = (sidePaneKey) => {
|
|
588
|
+
updateSidePaneDockOverride(sidePaneKey, side, "top");
|
|
589
|
+
};
|
|
590
|
+
const dockBottom = (sidePaneKey) => {
|
|
591
|
+
updateSidePaneDockOverride(sidePaneKey, side, "bottom");
|
|
592
|
+
};
|
|
593
|
+
if (currentSidePanes.some((entry) => entry.verticalLocation === "bottom")) {
|
|
594
|
+
// If there are bottom panes, there must also be top panes, and so top and bottom are valid locations.
|
|
595
|
+
sidePaneDockOperations.set(`top-${side}`, dockTop);
|
|
596
|
+
sidePaneDockOperations.set(`bottom-${side}`, dockBottom);
|
|
597
|
+
}
|
|
598
|
+
else if (currentSidePanes.length > 0) {
|
|
599
|
+
// If there are only top panes, then full and bottom are valid locations.
|
|
600
|
+
sidePaneDockOperations.set(`full-${side}`, dockTop);
|
|
601
|
+
sidePaneDockOperations.set(`bottom-${side}`, dockBottom);
|
|
602
|
+
}
|
|
603
|
+
else {
|
|
604
|
+
// If there are no panes, then only full is a valid location.
|
|
605
|
+
sidePaneDockOperations.set(`full-${side}`, dockTop);
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
return sidePaneDockOperations;
|
|
609
|
+
}, [coercedSidePanes]);
|
|
610
|
+
const hasLeftPanes = coercedSidePanes.some((entry) => entry.horizontalLocation === "left");
|
|
611
|
+
const hasRightPanes = coercedSidePanes.some((entry) => entry.horizontalLocation === "right");
|
|
612
|
+
useEffect(() => {
|
|
613
|
+
leftSidePaneContainerState.isPresent = hasLeftPanes;
|
|
614
|
+
rightSidePaneContainerState.isPresent = hasRightPanes;
|
|
615
|
+
return () => {
|
|
616
|
+
leftSidePaneContainerState.isPresent = false;
|
|
617
|
+
rightSidePaneContainerState.isPresent = false;
|
|
618
|
+
};
|
|
619
|
+
}, [hasLeftPanes, hasRightPanes]);
|
|
620
|
+
// If we are in compact toolbar mode, we may need to move toolbar items from the left to the right or vice versa,
|
|
621
|
+
// depending on whether there are any side panes on that side.
|
|
622
|
+
const coerceToolBarItemHorizontalLocation = useMemo(() => (item) => {
|
|
623
|
+
let horizontalLocation = item.horizontalLocation;
|
|
624
|
+
// Coercion is only needed in compact toolbar mode since there might not be a left or right pane.
|
|
625
|
+
if (toolbarMode === "compact") {
|
|
626
|
+
if (horizontalLocation === "left" && !hasLeftPanes) {
|
|
627
|
+
horizontalLocation = "right";
|
|
628
|
+
}
|
|
629
|
+
if (horizontalLocation === "right" && !hasRightPanes) {
|
|
630
|
+
horizontalLocation = "left";
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
return horizontalLocation;
|
|
634
|
+
}, [toolbarMode, hasLeftPanes, hasRightPanes]);
|
|
635
|
+
const topToolBarItems = useMemo(() => toolbarItems.filter((entry) => entry.verticalLocation === "top"), [toolbarItems]);
|
|
636
|
+
const bottomToolBarItems = useMemo(() => toolbarItems.filter((entry) => entry.verticalLocation === "bottom"), [toolbarItems]);
|
|
637
|
+
const topBarLeftItems = useMemo(() => topToolBarItems.filter((entry) => coerceToolBarItemHorizontalLocation(entry) === "left"), [topToolBarItems, coerceToolBarItemHorizontalLocation]);
|
|
638
|
+
const topBarRightItems = useMemo(() => topToolBarItems.filter((entry) => coerceToolBarItemHorizontalLocation(entry) === "right"), [topToolBarItems, coerceToolBarItemHorizontalLocation]);
|
|
639
|
+
const bottomBarLeftItems = useMemo(() => bottomToolBarItems.filter((entry) => coerceToolBarItemHorizontalLocation(entry) === "left"), [bottomToolBarItems, coerceToolBarItemHorizontalLocation]);
|
|
640
|
+
const bottomBarRightItems = useMemo(() => bottomToolBarItems.filter((entry) => coerceToolBarItemHorizontalLocation(entry) === "right"), [bottomToolBarItems, coerceToolBarItemHorizontalLocation]);
|
|
641
|
+
const centralContents = useOrderedObservableCollection(centralContentCollection);
|
|
642
|
+
const [leftPaneTabList, leftPane, leftPaneCollapsed, setLeftPaneCollapsed, leftPaneUndocked, setLeftPaneUndocked, leftPaneHasPanes] = usePane("left", leftPaneDefaultWidth, leftPaneMinWidth, coercedSidePanes, onSelectSidePane, sidePaneDockOperations, toolbarMode, topBarLeftItems, bottomBarLeftItems, leftPaneDefaultCollapsed);
|
|
643
|
+
useEffect(() => {
|
|
644
|
+
// Propagate shorter lived React component state out to longer lived service state.
|
|
645
|
+
leftSidePaneContainerState.isDocked = !leftPaneUndocked;
|
|
646
|
+
}, [leftPaneUndocked]);
|
|
647
|
+
useEffect(() => {
|
|
648
|
+
// Propagate shorter lived React component state out to longer lived service state.
|
|
649
|
+
leftSidePaneContainerState.isCollapsed = leftPaneCollapsed;
|
|
650
|
+
}, [leftPaneCollapsed]);
|
|
651
|
+
const [rightPaneTabList, rightPane, rightPaneCollapsed, setRightPaneCollapsed, rightPaneUndocked, setRightPaneUndocked, rightPaneHasPanes] = usePane("right", rightPaneDefaultWidth, rightPaneMinWidth, coercedSidePanes, onSelectSidePane, sidePaneDockOperations, toolbarMode, topBarRightItems, bottomBarRightItems, rightPaneDefaultCollapsed);
|
|
652
|
+
useEffect(() => {
|
|
653
|
+
// Propagate shorter lived React component state out to longer lived service state.
|
|
654
|
+
rightSidePaneContainerState.isDocked = !rightPaneUndocked;
|
|
655
|
+
}, [rightPaneUndocked]);
|
|
656
|
+
useEffect(() => {
|
|
657
|
+
// Propagate shorter lived React component state out to longer lived service state.
|
|
658
|
+
rightSidePaneContainerState.isCollapsed = rightPaneCollapsed;
|
|
659
|
+
}, [rightPaneCollapsed]);
|
|
660
|
+
useEffect(() => {
|
|
661
|
+
// If at the service level dock state change is requested, propagate to the React component state.
|
|
662
|
+
const observer = onDockChanged.add(({ location, dock }) => {
|
|
663
|
+
if (location === "left") {
|
|
664
|
+
setLeftPaneUndocked(!dock);
|
|
665
|
+
}
|
|
666
|
+
else {
|
|
667
|
+
setRightPaneUndocked(!dock);
|
|
668
|
+
}
|
|
669
|
+
});
|
|
670
|
+
return () => {
|
|
671
|
+
observer.remove();
|
|
672
|
+
leftSidePaneContainerState.isDocked = true;
|
|
673
|
+
rightSidePaneContainerState.isDocked = true;
|
|
674
|
+
};
|
|
675
|
+
}, [setLeftPaneUndocked, setRightPaneUndocked]);
|
|
676
|
+
useEffect(() => {
|
|
677
|
+
// If at the service level collapse state change is requested, propagate to the React component state.
|
|
678
|
+
const observer = onCollapseChanged.add(({ location, collapsed }) => {
|
|
679
|
+
if (location === "left") {
|
|
680
|
+
setLeftPaneCollapsed(collapsed);
|
|
681
|
+
}
|
|
682
|
+
else {
|
|
683
|
+
setRightPaneCollapsed(collapsed);
|
|
684
|
+
}
|
|
685
|
+
});
|
|
686
|
+
return () => {
|
|
687
|
+
observer.remove();
|
|
688
|
+
leftSidePaneContainerState.isCollapsed = false;
|
|
689
|
+
rightSidePaneContainerState.isCollapsed = false;
|
|
690
|
+
};
|
|
691
|
+
}, [setLeftPaneCollapsed, setRightPaneCollapsed]);
|
|
692
|
+
return (_jsxs("div", { className: classes.mainView, children: [toolbarMode === "full" && (_jsx(_Fragment, { children: _jsxs("div", { className: classes.barDiv, children: [leftPaneTabList, _jsx(Toolbar, { location: "top", components: topToolBarItems }), rightPaneTabList] }) })), _jsxs("div", { className: classes.verticallyCentralContent, children: [leftPane, _jsxs("div", { className: classes.centralContent, children: [centralContents.map((entry) => (_jsx(ErrorBoundary, { name: entry.key, children: _jsx(entry.component, {}) }, entry.key))), toolbarMode === "compact" && (_jsxs(_Fragment, { children: [_jsx(FluentFade, { visible: leftPaneCollapsed && leftPaneHasPanes, delay: 50, duration: 100, unmountOnExit: true, children: _jsx("div", { className: mergeClasses(classes.expandButtonContainer, classes.expandButtonContainerLeft), children: _jsx(Tooltip, { content: "Show Side Pane", children: _jsx(Button, { className: classes.expandButton, icon: _jsx(PanelLeftExpandRegular, {}), onClick: () => setLeftPaneCollapsed(false) }) }) }) }), _jsx(FluentFade, { visible: rightPaneCollapsed && rightPaneHasPanes, delay: 50, duration: 100, unmountOnExit: true, children: _jsx("div", { className: mergeClasses(classes.expandButtonContainer, classes.expandButtonContainerRight), children: _jsx(Tooltip, { content: "Show Side Pane", children: _jsx(Button, { className: classes.expandButton, icon: _jsx(PanelRightExpandRegular, {}), onClick: () => setRightPaneCollapsed(false) }) }) }) })] }))] }), rightPane] }), toolbarMode === "full" && (_jsx(_Fragment, { children: _jsx("div", { className: classes.barDiv, children: _jsx(Toolbar, { location: "bottom", components: bottomToolBarItems }) }) }))] }));
|
|
693
|
+
};
|
|
694
|
+
rootComponent.displayName = "Shell Service Root";
|
|
695
|
+
return {
|
|
696
|
+
addToolbarItem: (entry) => {
|
|
697
|
+
if (!entry.component.displayName) {
|
|
698
|
+
entry.component.displayName = `${entry.key} | ${entry.verticalLocation} ${entry.horizontalLocation} bar item`;
|
|
699
|
+
}
|
|
700
|
+
return toolbarItemCollection.add(entry);
|
|
701
|
+
},
|
|
702
|
+
addSidePane: (entry) => {
|
|
703
|
+
if (!entry.content.displayName) {
|
|
704
|
+
entry.content.displayName = `${entry.key} | ${entry.horizontalLocation} pane`;
|
|
705
|
+
}
|
|
706
|
+
return sidePaneCollection.add(entry);
|
|
707
|
+
},
|
|
708
|
+
addCentralContent: (entry) => centralContentCollection.add(entry),
|
|
709
|
+
get leftSidePaneContainer() {
|
|
710
|
+
return leftSidePaneContainerState.isPresent ? leftSidePaneContainerState : null;
|
|
711
|
+
},
|
|
712
|
+
get rightSidePaneContainer() {
|
|
713
|
+
return rightSidePaneContainerState.isPresent ? rightSidePaneContainerState : null;
|
|
714
|
+
},
|
|
715
|
+
onDockChanged,
|
|
716
|
+
get sidePanes() {
|
|
717
|
+
return [...sidePaneCollection.items].map((sidePaneDefinition) => {
|
|
718
|
+
return {
|
|
719
|
+
key: sidePaneDefinition.key,
|
|
720
|
+
select: () => onSelectSidePane.notifyObservers(sidePaneDefinition.key),
|
|
721
|
+
};
|
|
722
|
+
});
|
|
723
|
+
},
|
|
724
|
+
rootComponent,
|
|
725
|
+
};
|
|
726
|
+
},
|
|
727
|
+
};
|
|
728
|
+
}
|
|
729
|
+
//# sourceMappingURL=shellService.js.map
|