@xh/hoist 59.0.3 → 59.1.0
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/CHANGELOG.md +34 -2
- package/admin/AdminUtils.ts +23 -0
- package/admin/tabs/activity/clienterrors/ClientErrorsModel.ts +2 -1
- package/admin/tabs/activity/feedback/FeedbackPanel.ts +3 -3
- package/admin/tabs/activity/tracking/ActivityTrackingModel.ts +2 -1
- package/admin/tabs/activity/tracking/detail/ActivityDetailModel.ts +3 -2
- package/admin/tabs/general/config/ConfigPanelModel.ts +2 -2
- package/admin/tabs/general/users/UserModel.ts +2 -2
- package/admin/tabs/monitor/MonitorResultsModel.ts +48 -11
- package/admin/tabs/monitor/MonitorResultsPanel.ts +71 -8
- package/admin/tabs/server/connectionpool/ConnPoolMonitorModel.ts +2 -2
- package/admin/tabs/server/ehcache/EhCacheModel.ts +2 -2
- package/admin/tabs/server/environment/ServerEnvModel.ts +3 -3
- package/admin/tabs/server/logLevel/LogLevelPanel.ts +3 -3
- package/admin/tabs/server/logViewer/LogViewerModel.ts +2 -2
- package/admin/tabs/server/memory/MemoryMonitorModel.ts +2 -2
- package/admin/tabs/server/services/ServiceModel.ts +3 -3
- package/admin/tabs/server/websocket/WebSocketModel.ts +3 -2
- package/admin/tabs/userData/jsonblob/JsonBlobModel.ts +2 -2
- package/admin/tabs/userData/prefs/PreferenceModel.ts +2 -2
- package/admin/tabs/userData/prefs/UserPreferencePanel.ts +3 -3
- package/appcontainer/ExceptionDialogModel.ts +6 -0
- package/cmp/error/ErrorBoundary.ts +68 -0
- package/cmp/error/ErrorBoundaryModel.ts +77 -0
- package/cmp/markdown/Markdown.ts +32 -0
- package/cmp/markdown/index.ts +1 -0
- package/core/XH.ts +5 -9
- package/core/exception/ExceptionHandler.ts +15 -0
- package/data/cube/View.ts +14 -2
- package/desktop/appcontainer/AppContainer.ts +17 -3
- package/desktop/appcontainer/Banner.scss +25 -0
- package/desktop/appcontainer/Banner.ts +2 -1
- package/desktop/cmp/dash/canvas/impl/DashCanvasView.ts +4 -1
- package/desktop/cmp/dash/container/impl/DashContainerView.ts +2 -1
- package/desktop/cmp/dock/impl/DockView.ts +2 -1
- package/desktop/cmp/error/ErrorMessage.scss +1 -0
- package/desktop/cmp/error/ErrorMessage.ts +61 -23
- package/desktop/cmp/input/Checkbox.scss +13 -0
- package/desktop/cmp/input/Checkbox.ts +2 -0
- package/desktop/cmp/modalsupport/ModalSupport.scss +2 -0
- package/desktop/cmp/panel/Panel.ts +37 -14
- package/desktop/cmp/panel/PanelModel.ts +35 -7
- package/desktop/cmp/panel/impl/PanelHeader.scss +5 -0
- package/desktop/cmp/panel/impl/PanelHeader.ts +53 -38
- package/desktop/cmp/tab/impl/Tab.ts +2 -1
- package/dynamics/desktop.ts +15 -17
- package/dynamics/mobile.ts +10 -8
- package/inspector/Inspector.scss +5 -10
- package/inspector/InspectorPanel.ts +2 -0
- package/kit/react-markdown/index.ts +11 -0
- package/mobile/appcontainer/AppContainer.ts +17 -3
- package/mobile/appcontainer/Banner.scss +25 -0
- package/mobile/appcontainer/Banner.ts +2 -1
- package/mobile/cmp/error/ErrorMessage.ts +58 -18
- package/mobile/cmp/navigator/PageModel.ts +1 -0
- package/mobile/cmp/navigator/impl/Page.ts +2 -1
- package/mobile/cmp/panel/Panel.ts +5 -1
- package/mobile/cmp/tab/impl/Tab.ts +2 -1
- package/package.json +5 -3
- package/styles/vars.scss +2 -0
- package/svc/AlertBannerService.ts +2 -2
- package/svc/GridExportService.ts +1 -1
- package/admin/tabs/monitor/MonitorResultsToolbar.ts +0 -66
- package/appcontainer/ErrorBoundary.ts +0 -36
|
@@ -13,7 +13,8 @@ import {
|
|
|
13
13
|
TaskObserver,
|
|
14
14
|
useContextModel,
|
|
15
15
|
uses,
|
|
16
|
-
hoistCmp
|
|
16
|
+
hoistCmp,
|
|
17
|
+
HoistModel
|
|
17
18
|
} from '@xh/hoist/core';
|
|
18
19
|
import {loadingIndicator} from '@xh/hoist/desktop/cmp/loadingindicator';
|
|
19
20
|
import {mask} from '@xh/hoist/desktop/cmp/mask';
|
|
@@ -30,17 +31,24 @@ import './Panel.scss';
|
|
|
30
31
|
import {PanelModel} from './PanelModel';
|
|
31
32
|
import {HotkeyConfig} from '@xh/hoist/kit/blueprint';
|
|
32
33
|
import {ContextMenuSpec} from '../contextmenu/ContextMenu';
|
|
34
|
+
import {errorBoundary} from '@xh/hoist/cmp/error/ErrorBoundary';
|
|
33
35
|
|
|
34
36
|
export interface PanelProps extends HoistProps<PanelModel>, Omit<BoxProps, 'title'> {
|
|
35
37
|
/** True to style panel header (if displayed) with reduced padding and font-size. */
|
|
36
38
|
compactHeader?: boolean;
|
|
37
39
|
|
|
40
|
+
/** CSS class name specific to the panel's header. */
|
|
41
|
+
headerClassName?: string;
|
|
42
|
+
|
|
38
43
|
/** Items to be added to the right-side of the panel's header. */
|
|
39
44
|
headerItems?: ReactNode[];
|
|
40
45
|
|
|
41
46
|
/** An icon placed at the left-side of the panel's header. */
|
|
42
47
|
icon?: ReactElement;
|
|
43
48
|
|
|
49
|
+
/** Icon to be used when the panel is collapsed. Defaults to `icon`. */
|
|
50
|
+
collapsedIcon?: ReactElement;
|
|
51
|
+
|
|
44
52
|
/** Context Menu to show on context clicking this panel. */
|
|
45
53
|
contextMenu?: ContextMenuSpec;
|
|
46
54
|
|
|
@@ -118,6 +126,8 @@ export const [Panel, panel] = hoistCmp.withFactory<PanelProps>({
|
|
|
118
126
|
icon,
|
|
119
127
|
compactHeader,
|
|
120
128
|
collapsedTitle,
|
|
129
|
+
collapsedIcon,
|
|
130
|
+
headerClassName,
|
|
121
131
|
headerItems,
|
|
122
132
|
mask: maskProp,
|
|
123
133
|
loadingIndicator: loadingIndicatorProp,
|
|
@@ -145,22 +155,23 @@ export const [Panel, panel] = hoistCmp.withFactory<PanelProps>({
|
|
|
145
155
|
const {
|
|
146
156
|
resizable,
|
|
147
157
|
collapsible,
|
|
148
|
-
|
|
158
|
+
isRenderedCollapsed,
|
|
149
159
|
renderMode,
|
|
150
160
|
vertical,
|
|
151
161
|
showSplitter,
|
|
152
162
|
refreshContextModel,
|
|
153
|
-
modalSupportModel
|
|
163
|
+
modalSupportModel,
|
|
164
|
+
errorBoundaryModel
|
|
154
165
|
} = model;
|
|
155
166
|
|
|
156
|
-
if (
|
|
167
|
+
if (isRenderedCollapsed) {
|
|
157
168
|
delete layoutProps[`min${vertical ? 'Height' : 'Width'}`];
|
|
158
169
|
delete layoutProps[vertical ? 'height' : 'width'];
|
|
159
170
|
}
|
|
160
171
|
|
|
161
172
|
let coreContents = null;
|
|
162
173
|
if (
|
|
163
|
-
!
|
|
174
|
+
!isRenderedCollapsed ||
|
|
164
175
|
renderMode === 'always' ||
|
|
165
176
|
(renderMode === 'lazy' && wasDisplayed.current)
|
|
166
177
|
) {
|
|
@@ -169,7 +180,7 @@ export const [Panel, panel] = hoistCmp.withFactory<PanelProps>({
|
|
|
169
180
|
};
|
|
170
181
|
|
|
171
182
|
coreContents = vframe({
|
|
172
|
-
style: {display:
|
|
183
|
+
style: {display: isRenderedCollapsed ? 'none' : 'flex'},
|
|
173
184
|
items: Children.toArray([
|
|
174
185
|
parseToolbar(tbar),
|
|
175
186
|
...castArray(children),
|
|
@@ -177,17 +188,30 @@ export const [Panel, panel] = hoistCmp.withFactory<PanelProps>({
|
|
|
177
188
|
])
|
|
178
189
|
});
|
|
179
190
|
}
|
|
180
|
-
if (!
|
|
191
|
+
if (!isRenderedCollapsed) wasDisplayed.current = true;
|
|
181
192
|
|
|
182
193
|
// decorate with hooks (internally conditional, of course)
|
|
183
194
|
coreContents = useContextMenu(coreContents, contextMenu);
|
|
184
195
|
coreContents = useHotkeys(coreContents, hotkeys);
|
|
185
196
|
|
|
186
|
-
//
|
|
197
|
+
// Apply error boundary to content *excluding* header and affordances.
|
|
198
|
+
if (errorBoundaryModel) {
|
|
199
|
+
coreContents = errorBoundary({model: errorBoundaryModel, item: coreContents});
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// 3) Prepare core layout with header above core. This is what layout props are trampolined to
|
|
187
203
|
let item = vbox({
|
|
188
204
|
className: 'xh-panel__content',
|
|
189
205
|
items: [
|
|
190
|
-
panelHeader({
|
|
206
|
+
panelHeader({
|
|
207
|
+
title,
|
|
208
|
+
icon,
|
|
209
|
+
compact: compactHeader,
|
|
210
|
+
collapsedTitle,
|
|
211
|
+
collapsedIcon,
|
|
212
|
+
className: headerClassName,
|
|
213
|
+
headerItems
|
|
214
|
+
}),
|
|
191
215
|
coreContents,
|
|
192
216
|
parseLoadDecorator(maskProp, 'mask', contextModel),
|
|
193
217
|
parseLoadDecorator(loadingIndicatorProp, 'loadingIndicator', contextModel)
|
|
@@ -195,23 +219,23 @@ export const [Panel, panel] = hoistCmp.withFactory<PanelProps>({
|
|
|
195
219
|
...rest
|
|
196
220
|
});
|
|
197
221
|
|
|
222
|
+
// 4) Additional optional wrappers
|
|
198
223
|
if (refreshContextModel) {
|
|
199
224
|
item = refreshContextView({model: refreshContextModel, item});
|
|
200
225
|
}
|
|
201
226
|
|
|
202
|
-
// 3) Wrap in modal support if needed. Inner frame ensures className is still present in
|
|
203
|
-
// DOM when rendered in Dialog
|
|
204
227
|
if (modalSupportModel) {
|
|
205
228
|
item = modalSupport({
|
|
206
229
|
model: modalSupportModel,
|
|
207
230
|
item: frame({
|
|
231
|
+
// Frame ensures className is still present when rendered in Dialog
|
|
208
232
|
item,
|
|
209
233
|
className: model.isModal ? className : undefined
|
|
210
234
|
})
|
|
211
235
|
});
|
|
212
236
|
}
|
|
213
237
|
|
|
214
|
-
//
|
|
238
|
+
// 5) Return wrapped in resizable affordances if needed, or equivalent layout box
|
|
215
239
|
item =
|
|
216
240
|
resizable || collapsible || showSplitter
|
|
217
241
|
? resizeContainer({ref, item, className})
|
|
@@ -221,9 +245,8 @@ export const [Panel, panel] = hoistCmp.withFactory<PanelProps>({
|
|
|
221
245
|
}
|
|
222
246
|
});
|
|
223
247
|
|
|
224
|
-
function parseLoadDecorator(prop, name, contextModel) {
|
|
248
|
+
function parseLoadDecorator(prop: any, name: string, contextModel: HoistModel) {
|
|
225
249
|
const cmp = (name === 'mask' ? mask : loadingIndicator) as any;
|
|
226
|
-
if (!prop) return null;
|
|
227
250
|
if (isValidElement(prop)) return prop;
|
|
228
251
|
if (prop === true) return cmp({isDisplayed: true});
|
|
229
252
|
if (prop === 'onLoad') {
|
|
@@ -25,6 +25,7 @@ import {throwIf} from '@xh/hoist/utils/js';
|
|
|
25
25
|
import {isNil, isNumber, isString} from 'lodash';
|
|
26
26
|
import {createRef} from 'react';
|
|
27
27
|
import {ModalSupportConfig, ModalSupportModel} from '../modalsupport/';
|
|
28
|
+
import {ErrorBoundaryConfig, ErrorBoundaryModel} from '@xh/hoist/cmp/error/ErrorBoundaryModel';
|
|
28
29
|
|
|
29
30
|
export interface PanelConfig {
|
|
30
31
|
/** Can panel be resized? */
|
|
@@ -62,10 +63,16 @@ export interface PanelConfig {
|
|
|
62
63
|
|
|
63
64
|
/**
|
|
64
65
|
* Set to true to enable built-in support for showing panel contents in a modal, or provide a
|
|
65
|
-
* config to further configure.
|
|
66
|
+
* config to further configure. Default false.
|
|
66
67
|
*/
|
|
67
68
|
modalSupport?: boolean | ModalSupportConfig;
|
|
68
69
|
|
|
70
|
+
/**
|
|
71
|
+
* Set to true to place an ErrorBoundary around the panel, or provide a
|
|
72
|
+
* config to further configure. Default false.
|
|
73
|
+
*/
|
|
74
|
+
errorBoundary?: boolean | ErrorBoundaryConfig;
|
|
75
|
+
|
|
69
76
|
/** How should collapsed content be rendered? Ignored if collapsible is false. */
|
|
70
77
|
renderMode?: RenderMode;
|
|
71
78
|
|
|
@@ -126,6 +133,7 @@ export class PanelModel extends HoistModel {
|
|
|
126
133
|
|
|
127
134
|
@managed modalSupportModel: ModalSupportModel;
|
|
128
135
|
@managed refreshContextModel: RefreshContextModel;
|
|
136
|
+
@managed errorBoundaryModel: ErrorBoundaryModel;
|
|
129
137
|
@managed provider: PersistenceProvider;
|
|
130
138
|
|
|
131
139
|
//----------------
|
|
@@ -136,11 +144,14 @@ export class PanelModel extends HoistModel {
|
|
|
136
144
|
//---------------------
|
|
137
145
|
// Observable State
|
|
138
146
|
//---------------------
|
|
139
|
-
/**
|
|
147
|
+
/**
|
|
148
|
+
* True when collapsed in its "home" location as per this model's state.
|
|
149
|
+
* See also {@link isRenderedCollapsed}, which takes modal state into account.
|
|
150
|
+
*/
|
|
140
151
|
@observable
|
|
141
152
|
collapsed: boolean = false;
|
|
142
153
|
|
|
143
|
-
/** Size in pixels or percents along sizing dimension.
|
|
154
|
+
/** Size in pixels or percents along sizing dimension. Used when object is *not* collapsed. */
|
|
144
155
|
@bindable
|
|
145
156
|
size: number | string = null;
|
|
146
157
|
|
|
@@ -157,8 +168,13 @@ export class PanelModel extends HoistModel {
|
|
|
157
168
|
return !!this.modalSupportModel;
|
|
158
169
|
}
|
|
159
170
|
|
|
171
|
+
/** True when both collapsed and not currently in a modal - i.e. *really* collapsed. */
|
|
172
|
+
get isRenderedCollapsed(): boolean {
|
|
173
|
+
return this.collapsed && !this.isModal;
|
|
174
|
+
}
|
|
175
|
+
|
|
160
176
|
get isActive(): boolean {
|
|
161
|
-
return !this.
|
|
177
|
+
return !this.isRenderedCollapsed;
|
|
162
178
|
}
|
|
163
179
|
|
|
164
180
|
//-----------------
|
|
@@ -177,6 +193,7 @@ export class PanelModel extends HoistModel {
|
|
|
177
193
|
defaultCollapsed = false,
|
|
178
194
|
side,
|
|
179
195
|
modalSupport = false,
|
|
196
|
+
errorBoundary = false,
|
|
180
197
|
renderMode = 'lazy',
|
|
181
198
|
refreshMode = 'onShowLazy',
|
|
182
199
|
persistWith = null,
|
|
@@ -227,9 +244,10 @@ export class PanelModel extends HoistModel {
|
|
|
227
244
|
this.refreshMode = refreshMode;
|
|
228
245
|
this.showSplitter = showSplitter;
|
|
229
246
|
this.showSplitterCollapseButton = showSplitterCollapseButton;
|
|
230
|
-
this.showHeaderCollapseButton = showHeaderCollapseButton;
|
|
231
|
-
this.showModalToggleButton = showModalToggleButton;
|
|
247
|
+
this.showHeaderCollapseButton = collapsible && showHeaderCollapseButton;
|
|
248
|
+
this.showModalToggleButton = modalSupport && showModalToggleButton;
|
|
232
249
|
|
|
250
|
+
// Set up various optional functionality;
|
|
233
251
|
if (modalSupport) {
|
|
234
252
|
this.modalSupportModel =
|
|
235
253
|
modalSupport === true
|
|
@@ -237,7 +255,13 @@ export class PanelModel extends HoistModel {
|
|
|
237
255
|
: new ModalSupportModel(modalSupport);
|
|
238
256
|
}
|
|
239
257
|
|
|
240
|
-
|
|
258
|
+
if (errorBoundary) {
|
|
259
|
+
this.errorBoundaryModel =
|
|
260
|
+
errorBoundary === true
|
|
261
|
+
? new ErrorBoundaryModel()
|
|
262
|
+
: new ErrorBoundaryModel(errorBoundary);
|
|
263
|
+
}
|
|
264
|
+
|
|
241
265
|
if (collapsible) {
|
|
242
266
|
this.refreshContextModel = new ManagedRefreshContextModel(this);
|
|
243
267
|
}
|
|
@@ -340,6 +364,10 @@ export class PanelModel extends HoistModel {
|
|
|
340
364
|
return this.side === 'top' || this.side === 'left';
|
|
341
365
|
}
|
|
342
366
|
|
|
367
|
+
get isCollapsedToLeftOrRight(): boolean {
|
|
368
|
+
return this.isRenderedCollapsed && !this.vertical;
|
|
369
|
+
}
|
|
370
|
+
|
|
343
371
|
enforceSizeLimits() {
|
|
344
372
|
if (this.collapsed) return;
|
|
345
373
|
|
|
@@ -38,6 +38,11 @@
|
|
|
38
38
|
color: var(--xh-panel-title-text-color) !important;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
// Ensure text color correct for Blueprint controls, e.g. a switch in headerItems
|
|
42
|
+
.bp4-control {
|
|
43
|
+
color: var(--xh-panel-title-text-color);
|
|
44
|
+
}
|
|
45
|
+
|
|
41
46
|
// Vertical mode - collapsed to left or right
|
|
42
47
|
&.xh-vbox {
|
|
43
48
|
width: var(--xh-title-height-px);
|
|
@@ -21,13 +21,39 @@ export const panelHeader = hoistCmp.factory({
|
|
|
21
21
|
|
|
22
22
|
render({className, ...props}) {
|
|
23
23
|
const panelModel = useContextModel(PanelModel),
|
|
24
|
-
{
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
24
|
+
{
|
|
25
|
+
isRenderedCollapsed,
|
|
26
|
+
isCollapsedToLeftOrRight,
|
|
27
|
+
collapsible,
|
|
28
|
+
isModal,
|
|
29
|
+
side,
|
|
30
|
+
showModalToggleButton,
|
|
31
|
+
showHeaderCollapseButton
|
|
32
|
+
} = panelModel,
|
|
33
|
+
{title, icon, compact} = props;
|
|
29
34
|
|
|
30
|
-
|
|
35
|
+
// Title and icon can vary based on collapsed state.
|
|
36
|
+
const collapsedTitle = withDefault(props.collapsedTitle, title),
|
|
37
|
+
displayedTitle = (isRenderedCollapsed ? collapsedTitle : title) ?? null,
|
|
38
|
+
collapsedIcon = withDefault(props.collapsedIcon, icon),
|
|
39
|
+
displayedIcon = (isRenderedCollapsed ? collapsedIcon : icon) ?? null;
|
|
40
|
+
|
|
41
|
+
// As can headerItems, which include app-specified controls (never shown when collapsed)
|
|
42
|
+
// as well as (maybe) built-in modal/collapse toggle buttons.
|
|
43
|
+
const headerItems = props.headerItems ?? [],
|
|
44
|
+
displayedHeaderItems = isRenderedCollapsed ? [] : [...headerItems];
|
|
45
|
+
|
|
46
|
+
// Return null if nothing to display.
|
|
47
|
+
if (isNil(displayedTitle) && isNil(displayedIcon) && isEmpty(displayedHeaderItems)) {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (showModalToggleButton && !isRenderedCollapsed) {
|
|
52
|
+
displayedHeaderItems.push(modalToggleButton());
|
|
53
|
+
}
|
|
54
|
+
if (showHeaderCollapseButton && !isModal) {
|
|
55
|
+
displayedHeaderItems.push(collapseToggleButton({panelModel}));
|
|
56
|
+
}
|
|
31
57
|
|
|
32
58
|
const onDoubleClick = () => {
|
|
33
59
|
if (isModal) {
|
|
@@ -41,12 +67,13 @@ export const panelHeader = hoistCmp.factory({
|
|
|
41
67
|
sideCls = `xh-panel-header--${side}`,
|
|
42
68
|
compactCls = compact ? 'xh-panel-header--compact' : null;
|
|
43
69
|
|
|
44
|
-
//
|
|
45
|
-
if (
|
|
46
|
-
return
|
|
47
|
-
className: classNames(className, compactCls),
|
|
70
|
+
// Return a vertically oriented header if collapsed to left or right side.
|
|
71
|
+
if (isCollapsedToLeftOrRight) {
|
|
72
|
+
return vbox({
|
|
73
|
+
className: classNames(className, sideCls, compactCls),
|
|
74
|
+
flex: 1,
|
|
48
75
|
items: [
|
|
49
|
-
|
|
76
|
+
displayedIcon,
|
|
50
77
|
displayedTitle
|
|
51
78
|
? box({
|
|
52
79
|
className: titleCls,
|
|
@@ -54,44 +81,38 @@ export const panelHeader = hoistCmp.factory({
|
|
|
54
81
|
item: span({className: `${titleCls}__inner`, item: displayedTitle})
|
|
55
82
|
})
|
|
56
83
|
: filler(),
|
|
57
|
-
|
|
58
|
-
className: 'xh-panel-header__items',
|
|
59
|
-
items: [
|
|
60
|
-
...(!collapsed || isModal ? headerItems : []),
|
|
61
|
-
modalButton({panelModel}),
|
|
62
|
-
collapseButton({panelModel})
|
|
63
|
-
],
|
|
64
|
-
onDoubleClick: e => e.stopPropagation()
|
|
65
|
-
})
|
|
84
|
+
...displayedHeaderItems
|
|
66
85
|
],
|
|
67
86
|
onDoubleClick
|
|
68
87
|
});
|
|
69
88
|
}
|
|
70
89
|
|
|
71
|
-
//
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
90
|
+
// Return a standard horizontal header otherwise.
|
|
91
|
+
// Panel is expanded, modal, and/or collapsed to the top or bottom side.
|
|
92
|
+
return hbox({
|
|
93
|
+
className: classNames(className, compactCls),
|
|
75
94
|
items: [
|
|
76
|
-
|
|
77
|
-
icon || null,
|
|
95
|
+
displayedIcon,
|
|
78
96
|
displayedTitle
|
|
79
97
|
? box({
|
|
80
98
|
className: titleCls,
|
|
99
|
+
flex: 1,
|
|
81
100
|
item: span({className: `${titleCls}__inner`, item: displayedTitle})
|
|
82
101
|
})
|
|
83
|
-
:
|
|
102
|
+
: filler(),
|
|
103
|
+
hbox({
|
|
104
|
+
className: 'xh-panel-header__items',
|
|
105
|
+
items: displayedHeaderItems,
|
|
106
|
+
onDoubleClick: e => e.stopPropagation()
|
|
107
|
+
})
|
|
84
108
|
],
|
|
85
109
|
onDoubleClick
|
|
86
110
|
});
|
|
87
111
|
}
|
|
88
112
|
});
|
|
89
113
|
|
|
90
|
-
const
|
|
91
|
-
const {
|
|
92
|
-
if (!showHeaderCollapseButton || !collapsible || isModal) return null;
|
|
93
|
-
|
|
94
|
-
const {vertical, collapsed, contentFirst} = panelModel,
|
|
114
|
+
const collapseToggleButton = hoistCmp.factory(({panelModel}) => {
|
|
115
|
+
const {vertical, collapsed, contentFirst} = panelModel as PanelModel,
|
|
95
116
|
directions = vertical ? ['chevronUp', 'chevronDown'] : ['chevronLeft', 'chevronRight'],
|
|
96
117
|
idx = contentFirst !== collapsed ? 0 : 1,
|
|
97
118
|
chevron = directions[idx];
|
|
@@ -102,9 +123,3 @@ const collapseButton = hoistCmp.factory(({panelModel}) => {
|
|
|
102
123
|
minimal: true
|
|
103
124
|
});
|
|
104
125
|
});
|
|
105
|
-
|
|
106
|
-
const modalButton = hoistCmp.factory(({panelModel}) => {
|
|
107
|
-
const {showModalToggleButton, hasModalSupport} = panelModel as PanelModel;
|
|
108
|
-
if (!showModalToggleButton || !hasModalSupport) return null;
|
|
109
|
-
return modalToggleButton();
|
|
110
|
-
});
|
|
@@ -9,6 +9,7 @@ import {TabModel} from '@xh/hoist/cmp/tab';
|
|
|
9
9
|
import {hoistCmp, refreshContextView, uses} from '@xh/hoist/core';
|
|
10
10
|
import {elementFromContent} from '@xh/hoist/utils/react';
|
|
11
11
|
import {useRef} from 'react';
|
|
12
|
+
import {errorBoundary} from '@xh/hoist/cmp/error/ErrorBoundary';
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* Wrapper for contents to be shown within a TabContainer. This Component is used by TabContainer's
|
|
@@ -43,7 +44,7 @@ export const tab = hoistCmp.factory({
|
|
|
43
44
|
className,
|
|
44
45
|
item: refreshContextView({
|
|
45
46
|
model: refreshContextModel,
|
|
46
|
-
item: elementFromContent(content, {flex: 1})
|
|
47
|
+
item: errorBoundary(elementFromContent(content, {flex: 1}))
|
|
47
48
|
})
|
|
48
49
|
});
|
|
49
50
|
}
|
package/dynamics/desktop.ts
CHANGED
|
@@ -15,37 +15,35 @@
|
|
|
15
15
|
*
|
|
16
16
|
* See the platform specific AppContainer where these implementations are actually provided.
|
|
17
17
|
*/
|
|
18
|
-
export let
|
|
19
|
-
export let
|
|
18
|
+
export let ColChooserModel = null;
|
|
19
|
+
export let ColumnHeaderFilterModel = null;
|
|
20
|
+
export let ModalSupportModel = null;
|
|
20
21
|
export let colChooser = null;
|
|
21
22
|
export let columnHeaderFilter = null;
|
|
23
|
+
export let dockContainerImpl = null;
|
|
24
|
+
export let errorMessage = null;
|
|
22
25
|
export let gridFilterDialog = null;
|
|
23
|
-
export let storeFilterFieldImpl = null;
|
|
24
26
|
export let pinPadImpl = null;
|
|
25
|
-
|
|
26
|
-
export let
|
|
27
|
-
export let ColumnHeaderFilterModel = null;
|
|
27
|
+
export let storeFilterFieldImpl = null;
|
|
28
|
+
export let tabContainerImpl = null;
|
|
28
29
|
export let useContextMenu = null;
|
|
29
30
|
|
|
30
|
-
export let ModalSupportModel = null;
|
|
31
|
-
|
|
32
31
|
/**
|
|
33
32
|
* Provide implementations of functions and classes exported in this file.
|
|
34
33
|
*
|
|
35
34
|
* Not for Application use.
|
|
36
35
|
*/
|
|
37
36
|
export function installDesktopImpls(impls) {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
pinPadImpl = impls.pinPadImpl;
|
|
37
|
+
ColChooserModel = impls.ColChooserModel;
|
|
38
|
+
ColumnHeaderFilterModel = impls.ColumnHeaderFilterModel;
|
|
39
|
+
ModalSupportModel = impls.ModalSupportModel;
|
|
42
40
|
colChooser = impls.colChooser;
|
|
43
41
|
columnHeaderFilter = impls.columnHeaderFilter;
|
|
42
|
+
dockContainerImpl = impls.dockContainerImpl;
|
|
43
|
+
errorMessage = impls.errorMessage;
|
|
44
44
|
gridFilterDialog = impls.gridFilterDialog;
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
45
|
+
pinPadImpl = impls.pinPadImpl;
|
|
46
|
+
storeFilterFieldImpl = impls.storeFilterFieldImpl;
|
|
47
|
+
tabContainerImpl = impls.tabContainerImpl;
|
|
48
48
|
useContextMenu = impls.useContextMenu;
|
|
49
|
-
|
|
50
|
-
ModalSupportModel = impls.ModalSupportModel;
|
|
51
49
|
}
|
package/dynamics/mobile.ts
CHANGED
|
@@ -15,11 +15,12 @@
|
|
|
15
15
|
*
|
|
16
16
|
* See the platform specific AppContainer where these implementations are actually provided.
|
|
17
17
|
*/
|
|
18
|
-
export let tabContainerImpl = null;
|
|
19
|
-
export let storeFilterFieldImpl = null;
|
|
20
|
-
export let pinPadImpl = null;
|
|
21
|
-
export let colChooser = null;
|
|
22
18
|
export let ColChooserModel = null;
|
|
19
|
+
export let colChooser = null;
|
|
20
|
+
export let errorMessage = null;
|
|
21
|
+
export let pinPadImpl = null;
|
|
22
|
+
export let storeFilterFieldImpl = null;
|
|
23
|
+
export let tabContainerImpl = null;
|
|
23
24
|
|
|
24
25
|
/**
|
|
25
26
|
* Provide implementations of functions and classes exported in this file.
|
|
@@ -27,9 +28,10 @@ export let ColChooserModel = null;
|
|
|
27
28
|
* Not for Application use.
|
|
28
29
|
*/
|
|
29
30
|
export function installMobileImpls(impls) {
|
|
30
|
-
tabContainerImpl = impls.tabContainerImpl;
|
|
31
|
-
storeFilterFieldImpl = impls.storeFilterFieldImpl;
|
|
32
|
-
pinPadImpl = impls.pinPadImpl;
|
|
33
|
-
colChooser = impls.colChooser;
|
|
34
31
|
ColChooserModel = impls.ColChooserModel;
|
|
32
|
+
colChooser = impls.colChooser;
|
|
33
|
+
errorMessage = impls.errorMessage;
|
|
34
|
+
pinPadImpl = impls.pinPadImpl;
|
|
35
|
+
storeFilterFieldImpl = impls.storeFilterFieldImpl;
|
|
36
|
+
tabContainerImpl = impls.tabContainerImpl;
|
|
35
37
|
}
|
package/inspector/Inspector.scss
CHANGED
|
@@ -1,14 +1,4 @@
|
|
|
1
1
|
.xh-inspector {
|
|
2
|
-
& > .xh-panel__content > .xh-panel-header {
|
|
3
|
-
background-color: hsl(33, 93%, 40%);
|
|
4
|
-
color: white;
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
.xh-modal-support__host > div > .xh-panel__content > .xh-panel-header {
|
|
8
|
-
background-color: hsl(33, 93%, 40%);
|
|
9
|
-
color: white;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
2
|
.xh-font-family-mono {
|
|
13
3
|
font-size: 0.9em;
|
|
14
4
|
}
|
|
@@ -21,3 +11,8 @@
|
|
|
21
11
|
background-color: var(--xh-bg);
|
|
22
12
|
}
|
|
23
13
|
}
|
|
14
|
+
|
|
15
|
+
.xh-inspector-panel-header {
|
|
16
|
+
background-color: hsl(33, 93%, 40%);
|
|
17
|
+
color: white;
|
|
18
|
+
}
|
|
@@ -26,11 +26,13 @@ export const inspectorPanel = hoistCmp.factory({
|
|
|
26
26
|
title: `Inspector - Hoist v${XH.environmentService.get('hoistReactVersion')}`,
|
|
27
27
|
icon: Icon.search(),
|
|
28
28
|
className: 'xh-inspector',
|
|
29
|
+
headerClassName: 'xh-inspector-panel-header',
|
|
29
30
|
modelConfig: {
|
|
30
31
|
defaultSize: 400,
|
|
31
32
|
side: 'bottom',
|
|
32
33
|
persistWith: XH.inspectorService.persistWith,
|
|
33
34
|
modalSupport: true,
|
|
35
|
+
errorBoundary: true,
|
|
34
36
|
showModalToggleButton: true,
|
|
35
37
|
showHeaderCollapseButton: false,
|
|
36
38
|
xhImpl: true
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This file belongs to Hoist, an application development toolkit
|
|
3
|
+
* developed by Extremely Heavy Industries (www.xh.io | info@xh.io)
|
|
4
|
+
*
|
|
5
|
+
* Copyright © 2023 Extremely Heavy Industries Inc.
|
|
6
|
+
*/
|
|
7
|
+
import {elementFactory} from '@xh/hoist/core';
|
|
8
|
+
import ReactMarkdown from 'react-markdown';
|
|
9
|
+
|
|
10
|
+
export {ReactMarkdown};
|
|
11
|
+
export const reactMarkdown = elementFactory(ReactMarkdown);
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Copyright © 2023 Extremely Heavy Industries Inc.
|
|
6
6
|
*/
|
|
7
7
|
import {AppContainerModel} from '@xh/hoist/appcontainer/AppContainerModel';
|
|
8
|
-
import {errorBoundary} from '@xh/hoist/
|
|
8
|
+
import {errorBoundary} from '@xh/hoist/cmp/error/ErrorBoundary';
|
|
9
9
|
import {fragment, frame, vframe, viewport} from '@xh/hoist/cmp/layout';
|
|
10
10
|
import {createElement, hoistCmp, refreshContextView, uses, XH} from '@xh/hoist/core';
|
|
11
11
|
import {installMobileImpls} from '@xh/hoist/dynamics/mobile';
|
|
@@ -30,13 +30,15 @@ import {messageSource} from './MessageSource';
|
|
|
30
30
|
import {optionsDialog} from './OptionsDialog';
|
|
31
31
|
import {toastSource} from './ToastSource';
|
|
32
32
|
import {versionBar} from './VersionBar';
|
|
33
|
+
import {errorMessage} from '../cmp/error/ErrorMessage';
|
|
33
34
|
|
|
34
35
|
installMobileImpls({
|
|
35
36
|
tabContainerImpl,
|
|
36
37
|
storeFilterFieldImpl,
|
|
37
38
|
pinPadImpl,
|
|
38
39
|
colChooser,
|
|
39
|
-
ColChooserModel
|
|
40
|
+
ColChooserModel,
|
|
41
|
+
errorMessage
|
|
40
42
|
});
|
|
41
43
|
|
|
42
44
|
/**
|
|
@@ -57,7 +59,19 @@ export const AppContainer = hoistCmp({
|
|
|
57
59
|
useOnMount(() => model.initAsync());
|
|
58
60
|
|
|
59
61
|
return fragment(
|
|
60
|
-
errorBoundary(
|
|
62
|
+
errorBoundary({
|
|
63
|
+
modelConfig: {
|
|
64
|
+
errorHandler: {
|
|
65
|
+
title: 'Critical Error',
|
|
66
|
+
message:
|
|
67
|
+
XH.clientAppName +
|
|
68
|
+
' encountered a critical error and cannot be displayed.',
|
|
69
|
+
requireReload: true
|
|
70
|
+
},
|
|
71
|
+
errorRenderer: () => null
|
|
72
|
+
},
|
|
73
|
+
item: viewForState()
|
|
74
|
+
}),
|
|
61
75
|
// Modal component helpers rendered here at top-level to support display of messages
|
|
62
76
|
// and exceptions at any point during the app lifecycle.
|
|
63
77
|
exceptionDialog(),
|
|
@@ -20,6 +20,31 @@ body.xh-app .xh-banner {
|
|
|
20
20
|
overflow: hidden;
|
|
21
21
|
white-space: nowrap;
|
|
22
22
|
text-overflow: ellipsis;
|
|
23
|
+
|
|
24
|
+
// Disallow most markdown styling except **bold**, *italics* and (links)
|
|
25
|
+
h1,
|
|
26
|
+
h2,
|
|
27
|
+
h3,
|
|
28
|
+
h4,
|
|
29
|
+
p,
|
|
30
|
+
ul,
|
|
31
|
+
li,
|
|
32
|
+
a,
|
|
33
|
+
code {
|
|
34
|
+
display: inline;
|
|
35
|
+
margin: unset;
|
|
36
|
+
padding: unset;
|
|
37
|
+
color: unset;
|
|
38
|
+
font-weight: unset;
|
|
39
|
+
font-size: unset;
|
|
40
|
+
font-family: unset;
|
|
41
|
+
list-style: none;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Render links with an underline
|
|
45
|
+
a {
|
|
46
|
+
text-decoration: underline;
|
|
47
|
+
}
|
|
23
48
|
}
|
|
24
49
|
|
|
25
50
|
&__action-button {
|
|
@@ -9,6 +9,7 @@ import {XH, uses, hoistCmp} from '@xh/hoist/core';
|
|
|
9
9
|
import {hframe, div} from '@xh/hoist/cmp/layout';
|
|
10
10
|
import {button} from '@xh/hoist/mobile/cmp/button';
|
|
11
11
|
import {Icon} from '@xh/hoist/icon';
|
|
12
|
+
import {markdown} from '@xh/hoist/cmp/markdown';
|
|
12
13
|
import {isEmpty, isFunction} from 'lodash';
|
|
13
14
|
import classNames from 'classnames';
|
|
14
15
|
|
|
@@ -41,7 +42,7 @@ export const banner = hoistCmp.factory({
|
|
|
41
42
|
icon,
|
|
42
43
|
div({
|
|
43
44
|
className: 'xh-banner__message',
|
|
44
|
-
item: message,
|
|
45
|
+
item: markdown({content: message}),
|
|
45
46
|
onClick
|
|
46
47
|
})
|
|
47
48
|
]
|