@xh/hoist 59.3.2 → 59.5.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 +39 -3
- package/admin/differ/DifferModel.ts +5 -7
- package/appcontainer/AppContainerModel.ts +8 -10
- package/appcontainer/AppStateModel.ts +3 -2
- package/appcontainer/PageStateModel.ts +1 -2
- package/appcontainer/SizingModeModel.ts +2 -2
- package/cmp/ag-grid/AgGrid.ts +4 -3
- package/cmp/ag-grid/AgGridModel.ts +19 -14
- package/cmp/chart/Chart.ts +4 -3
- package/cmp/dataview/DataViewModel.ts +2 -2
- package/cmp/filter/FilterChooserModel.ts +5 -5
- package/cmp/grid/Grid.ts +9 -7
- package/cmp/grid/GridContextMenu.ts +2 -2
- package/cmp/grid/GridModel.ts +13 -21
- package/cmp/grid/Types.ts +6 -5
- package/cmp/grid/columns/Column.ts +12 -12
- package/cmp/grid/columns/ColumnGroup.ts +17 -6
- package/cmp/grid/helpers/GridCountLabel.ts +5 -4
- package/cmp/grid/impl/ColumnWidthCalculator.ts +3 -3
- package/cmp/grid/impl/GridPersistenceModel.ts +11 -4
- package/cmp/grid/impl/Utils.ts +1 -1
- package/cmp/grid/renderers/MultiFieldRenderer.ts +28 -22
- package/cmp/grouping/GroupingChooserModel.ts +2 -2
- package/cmp/relativetimestamp/RelativeTimestamp.ts +5 -2
- package/cmp/tab/TabContainerModel.ts +2 -2
- package/cmp/zoneGrid/ZoneGridModel.ts +1 -1
- package/cmp/zoneGrid/impl/ZoneGridPersistenceModel.ts +10 -4
- package/core/HoistBase.ts +48 -8
- package/core/HoistBaseDecorators.ts +11 -6
- package/core/HoistComponent.ts +1 -3
- package/core/elem.ts +2 -2
- package/core/exception/ExceptionHandler.ts +4 -4
- package/core/impl/InstallServices.ts +2 -8
- package/core/load/LoadSupport.ts +10 -11
- package/core/model/HoistModel.ts +1 -1
- package/data/Store.ts +1 -1
- package/data/UrlStore.ts +3 -3
- package/data/cube/aggregate/UniqueAggregator.ts +3 -5
- package/data/filter/CompoundFilter.ts +5 -3
- package/data/filter/FieldFilter.ts +4 -3
- package/data/filter/Filter.ts +2 -3
- package/data/filter/FunctionFilter.ts +2 -1
- package/data/impl/RecordSet.ts +5 -5
- package/desktop/appcontainer/ToastSource.ts +1 -1
- package/desktop/cmp/button/ColChooserButton.ts +7 -5
- package/desktop/cmp/button/ZoneMapperButton.ts +7 -5
- package/desktop/cmp/dash/canvas/DashCanvasModel.ts +2 -2
- package/desktop/cmp/dash/container/DashContainerModel.ts +2 -2
- package/desktop/cmp/dock/DockViewModel.ts +21 -11
- package/desktop/cmp/dock/impl/DockView.ts +4 -2
- package/desktop/cmp/form/FormField.ts +2 -2
- package/desktop/cmp/grid/editors/BooleanEditor.ts +5 -1
- package/desktop/cmp/input/DateInput.ts +1 -3
- package/desktop/cmp/panel/Panel.ts +4 -2
- package/desktop/cmp/panel/PanelModel.ts +5 -5
- package/desktop/cmp/rest/Actions.ts +15 -9
- package/desktop/cmp/treemap/TreeMap.ts +7 -10
- package/inspector/instances/InstancesModel.ts +6 -6
- package/mobile/cmp/button/ColAutosizeButton.ts +4 -3
- package/mobile/cmp/button/ColChooserButton.ts +4 -3
- package/mobile/cmp/button/ExpandCollapseButton.ts +4 -3
- package/mobile/cmp/button/ZoneMapperButton.ts +4 -3
- package/mobile/cmp/input/DateInput.ts +1 -1
- package/mobile/cmp/input/Select.ts +3 -3
- package/mobile/cmp/panel/Panel.ts +4 -2
- package/package.json +2 -2
- package/svc/AutoRefreshService.ts +3 -3
- package/svc/ChangelogService.ts +3 -3
- package/svc/EnvironmentService.ts +1 -1
- package/svc/FetchService.ts +5 -5
- package/svc/GridAutosizeService.ts +4 -7
- package/svc/GridExportService.ts +3 -8
- package/svc/IdentityService.ts +1 -1
- package/svc/TrackService.ts +9 -15
- package/svc/WebSocketService.ts +14 -15
- package/utils/async/AsyncUtils.ts +3 -2
- package/utils/async/Timer.ts +5 -4
- package/utils/datetime/LocalDate.ts +13 -13
- package/utils/js/BrowserUtils.ts +8 -8
- package/utils/js/Decorators.ts +18 -3
- package/utils/js/LangUtils.ts +10 -9
- package/utils/js/LogUtils.ts +66 -26
- package/utils/react/LayoutPropUtils.ts +3 -3
|
@@ -10,7 +10,7 @@ import {hoistCmp, useContextModel} from '@xh/hoist/core';
|
|
|
10
10
|
import {Icon} from '@xh/hoist/icon';
|
|
11
11
|
import {button, ButtonProps} from '@xh/hoist/mobile/cmp/button';
|
|
12
12
|
import '@xh/hoist/mobile/register';
|
|
13
|
-
import {withDefault} from '@xh/hoist/utils/js';
|
|
13
|
+
import {logError, withDefault} from '@xh/hoist/utils/js';
|
|
14
14
|
|
|
15
15
|
export interface ColAutosizeButtonProps extends ButtonProps {
|
|
16
16
|
/** GridModel of the grid for which this button should autosize columns. */
|
|
@@ -31,8 +31,9 @@ export const [ColAutosizeButton, colAutosizeButton] = hoistCmp.withFactory<ColAu
|
|
|
31
31
|
gridModel = withDefault(gridModel, useContextModel(GridModel));
|
|
32
32
|
|
|
33
33
|
if (!gridModel?.autosizeEnabled) {
|
|
34
|
-
|
|
35
|
-
"No GridModel available with autosize enabled. Provide via a 'gridModel' prop, or context."
|
|
34
|
+
logError(
|
|
35
|
+
"No GridModel available with autosize enabled. Provide via a 'gridModel' prop, or context.",
|
|
36
|
+
ColAutosizeButton
|
|
36
37
|
);
|
|
37
38
|
return button({icon, disabled: true, ...props});
|
|
38
39
|
}
|
|
@@ -9,7 +9,7 @@ import {hoistCmp, useContextModel} from '@xh/hoist/core';
|
|
|
9
9
|
import {Icon} from '@xh/hoist/icon';
|
|
10
10
|
import {button, ButtonProps} from '@xh/hoist/mobile/cmp/button';
|
|
11
11
|
import '@xh/hoist/mobile/register';
|
|
12
|
-
import {withDefault} from '@xh/hoist/utils/js';
|
|
12
|
+
import {logError, withDefault} from '@xh/hoist/utils/js';
|
|
13
13
|
|
|
14
14
|
export interface ColChooserButtonProps extends ButtonProps {
|
|
15
15
|
/** GridModel of the grid for which this button should show a chooser. */
|
|
@@ -30,8 +30,9 @@ export const [ColChooserButton, colChooserButton] = hoistCmp.withFactory<ColChoo
|
|
|
30
30
|
gridModel = withDefault(gridModel, useContextModel(GridModel));
|
|
31
31
|
|
|
32
32
|
if (!gridModel) {
|
|
33
|
-
|
|
34
|
-
"No GridModel available
|
|
33
|
+
logError(
|
|
34
|
+
"No GridModel available. Provide via a 'gridModel' prop, or context.",
|
|
35
|
+
ColChooserButton
|
|
35
36
|
);
|
|
36
37
|
return button({icon, disabled: true, ...props});
|
|
37
38
|
}
|
|
@@ -9,7 +9,7 @@ import {hoistCmp, useContextModel} from '@xh/hoist/core';
|
|
|
9
9
|
import {Icon} from '@xh/hoist/icon';
|
|
10
10
|
import {button, ButtonProps} from '@xh/hoist/mobile/cmp/button';
|
|
11
11
|
import '@xh/hoist/mobile/register';
|
|
12
|
-
import {withDefault} from '@xh/hoist/utils/js';
|
|
12
|
+
import {logError, withDefault} from '@xh/hoist/utils/js';
|
|
13
13
|
import {isEmpty} from 'lodash';
|
|
14
14
|
|
|
15
15
|
export interface ExpandCollapseButtonProps extends ButtonProps {
|
|
@@ -28,8 +28,9 @@ export const [ExpandCollapseButton, expandCollapseButton] =
|
|
|
28
28
|
gridModel = withDefault(gridModel, useContextModel(GridModel));
|
|
29
29
|
|
|
30
30
|
if (!gridModel) {
|
|
31
|
-
|
|
32
|
-
"No GridModel available. Provide via a 'gridModel' prop, or context."
|
|
31
|
+
logError(
|
|
32
|
+
"No GridModel available. Provide via a 'gridModel' prop, or context.",
|
|
33
|
+
ExpandCollapseButton
|
|
33
34
|
);
|
|
34
35
|
return button({icon: Icon.expand(), disabled: true, ...props});
|
|
35
36
|
}
|
|
@@ -8,7 +8,7 @@ import {hoistCmp, useContextModel} from '@xh/hoist/core';
|
|
|
8
8
|
import {ZoneGridModel} from '../../../cmp/zoneGrid';
|
|
9
9
|
import {button, ButtonProps} from '@xh/hoist/mobile/cmp/button';
|
|
10
10
|
import {Icon} from '@xh/hoist/icon';
|
|
11
|
-
import {withDefault} from '@xh/hoist/utils/js';
|
|
11
|
+
import {logError, withDefault} from '@xh/hoist/utils/js';
|
|
12
12
|
import '@xh/hoist/mobile/register';
|
|
13
13
|
|
|
14
14
|
export interface ZoneMapperButtonProps extends ButtonProps {
|
|
@@ -28,8 +28,9 @@ export const [ZoneMapperButton, zoneMapperButton] = hoistCmp.withFactory<ZoneMap
|
|
|
28
28
|
zoneGridModel = withDefault(zoneGridModel, useContextModel(ZoneGridModel));
|
|
29
29
|
|
|
30
30
|
if (!zoneGridModel) {
|
|
31
|
-
|
|
32
|
-
"No ZoneGridModel available
|
|
31
|
+
logError(
|
|
32
|
+
"No ZoneGridModel available. Provide via a 'zoneGridModel' prop, or context.",
|
|
33
|
+
ZoneMapperButton
|
|
33
34
|
);
|
|
34
35
|
return button({icon, disabled: true, ...props});
|
|
35
36
|
}
|
|
@@ -161,7 +161,7 @@ class DateInputModel extends HoistInputModel {
|
|
|
161
161
|
if (date && this.isOutsideRange(date)) {
|
|
162
162
|
// Dates outside of min/max constraints are reset to null.
|
|
163
163
|
date = null;
|
|
164
|
-
|
|
164
|
+
this.logDebug('Value exceeded max/minDate bounds on change - reset to null.');
|
|
165
165
|
}
|
|
166
166
|
this.noteValueChange(date ? date.toDate() : null);
|
|
167
167
|
};
|
|
@@ -272,8 +272,8 @@ class SelectInputModel extends HoistInputModel {
|
|
|
272
272
|
? reactAsyncCreatableSelect
|
|
273
273
|
: reactAsyncSelect
|
|
274
274
|
: creatableMode
|
|
275
|
-
|
|
276
|
-
|
|
275
|
+
? reactCreatableSelect
|
|
276
|
+
: reactSelect;
|
|
277
277
|
}
|
|
278
278
|
|
|
279
279
|
@action
|
|
@@ -468,7 +468,7 @@ class SelectInputModel extends HoistInputModel {
|
|
|
468
468
|
return matchOpts;
|
|
469
469
|
})
|
|
470
470
|
.catch(e => {
|
|
471
|
-
|
|
471
|
+
this.logError(e);
|
|
472
472
|
throw e;
|
|
473
473
|
});
|
|
474
474
|
};
|
|
@@ -24,6 +24,7 @@ import {omitBy} from 'lodash';
|
|
|
24
24
|
import {isValidElement, ReactNode, ReactElement} from 'react';
|
|
25
25
|
import {panelHeader} from './impl/PanelHeader';
|
|
26
26
|
import './Panel.scss';
|
|
27
|
+
import {logWarn} from '@xh/hoist/utils/js';
|
|
27
28
|
|
|
28
29
|
export interface PanelProps extends HoistProps, Omit<BoxProps, 'title'> {
|
|
29
30
|
/** A toolbar to be docked at the bottom of the panel. */
|
|
@@ -141,8 +142,9 @@ function parseLoadDecorator(prop, name, contextModel) {
|
|
|
141
142
|
if (prop === 'onLoad') {
|
|
142
143
|
const loadModel = contextModel?.loadModel;
|
|
143
144
|
if (!loadModel) {
|
|
144
|
-
|
|
145
|
-
`Cannot use 'onLoad' for '${name}'. Context model does not implement loading
|
|
145
|
+
logWarn(
|
|
146
|
+
`Cannot use 'onLoad' for '${name}'. Context model does not implement loading.`,
|
|
147
|
+
Panel
|
|
146
148
|
);
|
|
147
149
|
return null;
|
|
148
150
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xh/hoist",
|
|
3
|
-
"version": "59.
|
|
3
|
+
"version": "59.5.0",
|
|
4
4
|
"description": "Hoist add-on for building and deploying React Applications.",
|
|
5
5
|
"repository": "github:xh/hoist-react",
|
|
6
6
|
"homepage": "https://xh.io",
|
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
"react-beautiful-dnd": "~13.1.0",
|
|
66
66
|
"react-dates": "~21.8.0",
|
|
67
67
|
"react-dropzone": "~10.2.2",
|
|
68
|
-
"react-grid-layout": "
|
|
68
|
+
"react-grid-layout": "1.4.3",
|
|
69
69
|
"react-markdown": "^8.0.7",
|
|
70
70
|
"react-onsenui": "~1.13.2",
|
|
71
71
|
"react-popper": "~2.3.0",
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import {HoistService, managed, XH} from '@xh/hoist/core';
|
|
8
8
|
import {Timer} from '@xh/hoist/utils/async';
|
|
9
9
|
import {olderThan, ONE_SECOND, SECONDS} from '@xh/hoist/utils/datetime';
|
|
10
|
-
import {
|
|
10
|
+
import {withDefault} from '@xh/hoist/utils/js';
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* Service to triggers an app-wide auto-refresh (if enabled, on a configurable interval) via the
|
|
@@ -58,7 +58,7 @@ export class AutoRefreshService extends HoistService {
|
|
|
58
58
|
// Implementation
|
|
59
59
|
//------------------------
|
|
60
60
|
private async onTimerAsync() {
|
|
61
|
-
if (!this.enabled ||
|
|
61
|
+
if (!this.enabled || !XH.pageIsVisible) return;
|
|
62
62
|
|
|
63
63
|
// Wait interval after lastCompleted -- this prevents extra refreshes if user refreshes
|
|
64
64
|
// manually, or loading slow. Note auto-loads skipped if any load in progress.
|
|
@@ -69,7 +69,7 @@ export class AutoRefreshService extends HoistService {
|
|
|
69
69
|
pendingLoad = lastRequested && lastRequested > lastCompleted;
|
|
70
70
|
|
|
71
71
|
if (!pendingLoad && olderThan(last, this.interval * SECONDS)) {
|
|
72
|
-
logDebug('Triggering application auto-refresh
|
|
72
|
+
this.logDebug('Triggering application auto-refresh');
|
|
73
73
|
await ctx.autoRefreshAsync();
|
|
74
74
|
}
|
|
75
75
|
}
|
package/svc/ChangelogService.ts
CHANGED
|
@@ -96,12 +96,12 @@ export class ChangelogService extends HoistService {
|
|
|
96
96
|
const {latestAvailableVersion, LAST_READ_PREF_KEY} = this;
|
|
97
97
|
|
|
98
98
|
if (includes(latestAvailableVersion, 'SNAPSHOT')) {
|
|
99
|
-
|
|
99
|
+
this.logWarn('Unable to mark changelog as read when latest version is SNAPSHOT.');
|
|
100
100
|
return;
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
if (!latestAvailableVersion || !XH.prefService.hasKey(LAST_READ_PREF_KEY)) {
|
|
104
|
-
|
|
104
|
+
this.logWarn(
|
|
105
105
|
'Unable to mark changelog as read - latest version or required pref not found.'
|
|
106
106
|
);
|
|
107
107
|
return;
|
|
@@ -147,7 +147,7 @@ export class ChangelogService extends HoistService {
|
|
|
147
147
|
|
|
148
148
|
return versions;
|
|
149
149
|
} catch (e) {
|
|
150
|
-
|
|
150
|
+
this.logError(
|
|
151
151
|
'Error parsing changelog JSON into versions - changelog will not be available',
|
|
152
152
|
e
|
|
153
153
|
);
|
|
@@ -136,7 +136,7 @@ export class EnvironmentService extends HoistService {
|
|
|
136
136
|
// prompted to refresh.
|
|
137
137
|
const clientVersion = this.get('clientVersion');
|
|
138
138
|
if (appVersion !== clientVersion) {
|
|
139
|
-
|
|
139
|
+
this.logWarn(
|
|
140
140
|
`Version mismatch detected between client and server - ${clientVersion} vs ${appVersion}`
|
|
141
141
|
);
|
|
142
142
|
}
|
package/svc/FetchService.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Copyright © 2023 Extremely Heavy Industries Inc.
|
|
6
6
|
*/
|
|
7
|
-
import {HoistService, XH, Exception, PlainObject,
|
|
7
|
+
import {HoistService, XH, Exception, PlainObject, FetchResponse, LoadSpec} from '@xh/hoist/core';
|
|
8
8
|
import {isLocalDate, SECONDS, ONE_MINUTE, olderThan} from '@xh/hoist/utils/datetime';
|
|
9
9
|
import {throwIf} from '@xh/hoist/utils/js';
|
|
10
10
|
import {StatusCodes} from 'http-status-codes';
|
|
@@ -66,7 +66,7 @@ export class FetchService extends HoistService {
|
|
|
66
66
|
* Set default headers to be sent with all subsequent requests.
|
|
67
67
|
* @param headers - to be sent with all fetch requests, or a function to generate.
|
|
68
68
|
*/
|
|
69
|
-
setDefaultHeaders(headers:
|
|
69
|
+
setDefaultHeaders(headers: PlainObject | ((arg: FetchOptions) => PlainObject)) {
|
|
70
70
|
this.defaultHeaders = headers;
|
|
71
71
|
}
|
|
72
72
|
|
|
@@ -328,9 +328,9 @@ export interface FetchOptions {
|
|
|
328
328
|
|
|
329
329
|
/**
|
|
330
330
|
* Data to send in the request body (for POSTs/PUTs of JSON).
|
|
331
|
-
* When using `fetch`, provide a string. Otherwise, provide a
|
|
331
|
+
* When using `fetch`, provide a string. Otherwise, provide a JSON Serializable object
|
|
332
332
|
*/
|
|
333
|
-
body?:
|
|
333
|
+
body?: any;
|
|
334
334
|
|
|
335
335
|
/**
|
|
336
336
|
* Parameters to encode and append as a query string, or send with the request body
|
|
@@ -360,7 +360,7 @@ export interface FetchOptions {
|
|
|
360
360
|
* Optional metadata about the underlying request. Passed through for downstream processing by
|
|
361
361
|
* utils such as {@link ExceptionHandler}.
|
|
362
362
|
*/
|
|
363
|
-
loadSpec?:
|
|
363
|
+
loadSpec?: LoadSpec;
|
|
364
364
|
|
|
365
365
|
/**
|
|
366
366
|
* Options to pass to the underlying fetch request.
|
|
@@ -60,24 +60,21 @@ export class GridAutosizeService extends HoistService {
|
|
|
60
60
|
);
|
|
61
61
|
|
|
62
62
|
if (!requiredWidths) {
|
|
63
|
-
|
|
63
|
+
this.logDebug('Autosize aborted, grid data is obsolete.');
|
|
64
64
|
return;
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
runInAction(() => {
|
|
68
68
|
// 4) Set columns to their required widths.
|
|
69
69
|
gridModel.applyColumnStateChanges(requiredWidths);
|
|
70
|
-
|
|
71
|
-
`Column widths autosized via GridAutosizeService (${records.length} records)`,
|
|
72
|
-
requiredWidths
|
|
73
|
-
);
|
|
70
|
+
this.logDebug(`Auto-sized columns`, `${records.length} records`, requiredWidths);
|
|
74
71
|
|
|
75
72
|
// 5) Grow columns to fill any remaining space, if enabled.
|
|
76
73
|
const {fillMode} = options;
|
|
77
74
|
if (fillMode && fillMode !== 'none') {
|
|
78
75
|
const fillWidths = this.calcFillWidths(gridModel, colIds, fillMode);
|
|
79
76
|
gridModel.applyColumnStateChanges(fillWidths);
|
|
80
|
-
|
|
77
|
+
this.logDebug('Auto-sized columns using fillMode', fillWidths);
|
|
81
78
|
}
|
|
82
79
|
});
|
|
83
80
|
}
|
|
@@ -170,7 +167,7 @@ export class GridAutosizeService extends HoistService {
|
|
|
170
167
|
available = agApi?.gridPanel?.eBodyViewport?.clientWidth;
|
|
171
168
|
|
|
172
169
|
if (!agApi || !isFinite(available)) {
|
|
173
|
-
|
|
170
|
+
this.logWarn('Grid not rendered - unable to fill columns.');
|
|
174
171
|
return [];
|
|
175
172
|
}
|
|
176
173
|
|
package/svc/GridExportService.ts
CHANGED
|
@@ -327,13 +327,8 @@ export class GridExportService extends HoistService {
|
|
|
327
327
|
: it.exportName;
|
|
328
328
|
|
|
329
329
|
if (!isString(ret)) {
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
it.colId +
|
|
333
|
-
' with an invalid "exportName", ' +
|
|
334
|
-
'probably caused by setting "headerName" to a React element. Please specify an ' +
|
|
335
|
-
'appropriate "exportName". Defaulting to ' +
|
|
336
|
-
it.colId
|
|
330
|
+
this.logWarn(
|
|
331
|
+
`Tried to export column '${it.colId}' with an invalid "exportName", probably caused by setting "headerName" to a React element. Please specify an appropriate "exportName". Defaulting to '${it.colId}'`
|
|
337
332
|
);
|
|
338
333
|
ret = it.colId;
|
|
339
334
|
}
|
|
@@ -345,7 +340,7 @@ export class GridExportService extends HoistService {
|
|
|
345
340
|
const headerCounts = countBy(headers.map(it => it.toLowerCase())),
|
|
346
341
|
dupeHeaders = keys(pickBy(headerCounts, it => it > 1));
|
|
347
342
|
if (type === 'excelTable' && !isEmpty(dupeHeaders)) {
|
|
348
|
-
|
|
343
|
+
this.logWarn(
|
|
349
344
|
'Excel tables require unique headers on each column. Consider using the "exportName" property to ensure unique headers. Duplicate headers: ',
|
|
350
345
|
dupeHeaders
|
|
351
346
|
);
|
package/svc/IdentityService.ts
CHANGED
|
@@ -62,7 +62,7 @@ export class IdentityService extends HoistService {
|
|
|
62
62
|
try {
|
|
63
63
|
await XH.appModel?.logoutAsync();
|
|
64
64
|
} catch (e) {
|
|
65
|
-
|
|
65
|
+
this.logError('Error calling XH.appModel.logoutAsync()', e);
|
|
66
66
|
}
|
|
67
67
|
return XH.fetchJson({url: 'xh/logout'})
|
|
68
68
|
.then(() => XH.reloadApp())
|
package/svc/TrackService.ts
CHANGED
|
@@ -45,19 +45,13 @@ export class TrackService extends HoistService {
|
|
|
45
45
|
|
|
46
46
|
// Short-circuit if disabled...
|
|
47
47
|
if (!this.enabled) {
|
|
48
|
-
|
|
49
|
-
'[TrackService] | Activity tracking disabled - activity will not be tracked.',
|
|
50
|
-
options
|
|
51
|
-
);
|
|
48
|
+
this.logDebug('Activity tracking disabled - activity will not be tracked.', options);
|
|
52
49
|
return;
|
|
53
50
|
}
|
|
54
51
|
|
|
55
52
|
// ...or invalid request (with warning for developer)...
|
|
56
53
|
if (!options.message) {
|
|
57
|
-
|
|
58
|
-
'[TrackService] | Required message not provided - activity will not be tracked.',
|
|
59
|
-
options
|
|
60
|
-
);
|
|
54
|
+
this.logWarn('Required message not provided - activity will not be tracked.', options);
|
|
61
55
|
return;
|
|
62
56
|
}
|
|
63
57
|
|
|
@@ -97,23 +91,23 @@ export class TrackService extends HoistService {
|
|
|
97
91
|
|
|
98
92
|
const {maxDataLength} = this.conf;
|
|
99
93
|
if (params.data?.length > maxDataLength) {
|
|
100
|
-
|
|
101
|
-
`
|
|
94
|
+
this.logWarn(
|
|
95
|
+
`Track log includes ${params.data.length} chars of JSON data`,
|
|
96
|
+
`exceeds limit of ${maxDataLength}`,
|
|
97
|
+
'data will not be persisted',
|
|
102
98
|
options.data
|
|
103
99
|
);
|
|
104
100
|
params.data = null;
|
|
105
101
|
}
|
|
106
102
|
|
|
107
103
|
const elapsedStr = params.elapsed != null ? `${params.elapsed}ms` : null,
|
|
108
|
-
|
|
109
|
-
.filter(it => it != null)
|
|
110
|
-
.join(' | ');
|
|
104
|
+
consoleMsgs = [params.category, params.msg, elapsedStr].filter(it => it != null);
|
|
111
105
|
|
|
112
|
-
|
|
106
|
+
this.logInfo(...consoleMsgs);
|
|
113
107
|
|
|
114
108
|
await XH.fetchJson({url: 'xh/track', params});
|
|
115
109
|
} catch (e) {
|
|
116
|
-
|
|
110
|
+
this.logError('Failed to persist track log', options, e);
|
|
117
111
|
}
|
|
118
112
|
}
|
|
119
113
|
}
|
package/svc/WebSocketService.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import {HoistService, XH} from '@xh/hoist/core';
|
|
8
8
|
import {Icon} from '@xh/hoist/icon';
|
|
9
|
-
import {action,
|
|
9
|
+
import {action, makeObservable, observable} from '@xh/hoist/mobx';
|
|
10
10
|
import {Timer} from '@xh/hoist/utils/async';
|
|
11
11
|
import {SECONDS} from '@xh/hoist/utils/datetime';
|
|
12
12
|
import {throwIf} from '@xh/hoist/utils/js';
|
|
@@ -55,11 +55,11 @@ export class WebSocketService extends HoistService {
|
|
|
55
55
|
return !!this.channelKey;
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
/**
|
|
58
|
+
/** Set to true to log all sent/received messages - very chatty. */
|
|
59
59
|
logMessages: boolean = false;
|
|
60
60
|
|
|
61
|
-
private _timer;
|
|
62
|
-
private _socket;
|
|
61
|
+
private _timer: Timer;
|
|
62
|
+
private _socket: WebSocket;
|
|
63
63
|
private _subsByTopic = {};
|
|
64
64
|
|
|
65
65
|
enabled: boolean = XH.appSpec.webSocketsEnabled;
|
|
@@ -72,9 +72,8 @@ export class WebSocketService extends HoistService {
|
|
|
72
72
|
override async initAsync() {
|
|
73
73
|
if (!this.enabled) return;
|
|
74
74
|
if (XH.environmentService.get('webSocketsEnabled') === false) {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
'Please adjust your server-side configuration to use WebSockets.'
|
|
75
|
+
this.logError(
|
|
76
|
+
`WebSockets enabled on this client app but disabled on server. Adjust your server-side config.`
|
|
78
77
|
);
|
|
79
78
|
this.enabled = false;
|
|
80
79
|
return;
|
|
@@ -151,7 +150,7 @@ export class WebSocketService extends HoistService {
|
|
|
151
150
|
};
|
|
152
151
|
this._socket = s;
|
|
153
152
|
} catch (e) {
|
|
154
|
-
|
|
153
|
+
this.logError('Failure creating WebSocket', e);
|
|
155
154
|
}
|
|
156
155
|
|
|
157
156
|
this.updateConnectedStatus();
|
|
@@ -170,7 +169,7 @@ export class WebSocketService extends HoistService {
|
|
|
170
169
|
if (this.connected) {
|
|
171
170
|
this.sendMessage({topic: this.HEARTBEAT_TOPIC, data: 'ping'});
|
|
172
171
|
} else {
|
|
173
|
-
|
|
172
|
+
this.logWarn('Heartbeat found websocket not connected - attempting to reconnect...');
|
|
174
173
|
this.disconnect();
|
|
175
174
|
this.connect();
|
|
176
175
|
}
|
|
@@ -185,17 +184,17 @@ export class WebSocketService extends HoistService {
|
|
|
185
184
|
// Socket events impl
|
|
186
185
|
//------------------------
|
|
187
186
|
onOpen(ev) {
|
|
188
|
-
|
|
187
|
+
this.logDebug('WebSocket connection opened', ev);
|
|
189
188
|
this.updateConnectedStatus();
|
|
190
189
|
}
|
|
191
190
|
|
|
192
191
|
onClose(ev) {
|
|
193
|
-
|
|
192
|
+
this.logDebug('WebSocket connection closed', ev);
|
|
194
193
|
this.updateConnectedStatus();
|
|
195
194
|
}
|
|
196
195
|
|
|
197
196
|
onError(ev) {
|
|
198
|
-
|
|
197
|
+
this.logError('WebSocket connection error', ev);
|
|
199
198
|
this.updateConnectedStatus();
|
|
200
199
|
}
|
|
201
200
|
|
|
@@ -221,7 +220,7 @@ export class WebSocketService extends HoistService {
|
|
|
221
220
|
|
|
222
221
|
this.notifySubscribers(msg);
|
|
223
222
|
} catch (e) {
|
|
224
|
-
|
|
223
|
+
this.logError('Error decoding websocket message', rawMsg, e);
|
|
225
224
|
}
|
|
226
225
|
this.updateConnectedStatus();
|
|
227
226
|
}
|
|
@@ -236,7 +235,7 @@ export class WebSocketService extends HoistService {
|
|
|
236
235
|
try {
|
|
237
236
|
sub.fn(message);
|
|
238
237
|
} catch (e) {
|
|
239
|
-
|
|
238
|
+
this.logError(`Handler for topic ${message.topic} threw`, e);
|
|
240
239
|
}
|
|
241
240
|
});
|
|
242
241
|
}
|
|
@@ -287,7 +286,7 @@ export class WebSocketService extends HoistService {
|
|
|
287
286
|
}
|
|
288
287
|
|
|
289
288
|
maybeLogMessage(...args) {
|
|
290
|
-
if (this.logMessages)
|
|
289
|
+
if (this.logMessages) this.logDebug(args);
|
|
291
290
|
}
|
|
292
291
|
}
|
|
293
292
|
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Copyright © 2023 Extremely Heavy Industries Inc.
|
|
6
6
|
*/
|
|
7
|
+
import {XH} from '@xh/hoist/core';
|
|
7
8
|
import {wait} from '@xh/hoist/promise';
|
|
8
9
|
|
|
9
10
|
/**
|
|
@@ -15,7 +16,7 @@ import {wait} from '@xh/hoist/promise';
|
|
|
15
16
|
* allowing ongoing rendering of UI updates (e.g. load masks) and generally keeping the browser
|
|
16
17
|
* event loop running.
|
|
17
18
|
*
|
|
18
|
-
* Note that if the
|
|
19
|
+
* Note that if the content tab is hidden (i.e. `!XH.pageIsVisible`) this loop will be executed
|
|
19
20
|
* without pauses. In this case the pauses would be unduly large due to throttling of the event
|
|
20
21
|
* loop by the browser, and there is no user benefit to avoiding blocking the main thread.
|
|
21
22
|
*
|
|
@@ -55,7 +56,7 @@ export async function whileAsync(
|
|
|
55
56
|
const {waitAfter = 50, waitFor = 0} = opts ?? {};
|
|
56
57
|
|
|
57
58
|
// Fallback to basic loop when doc hidden: no user benefit, and throttling causes outsize waits
|
|
58
|
-
if (
|
|
59
|
+
if (!XH.pageIsVisible) {
|
|
59
60
|
while (conditionFn()) fn();
|
|
60
61
|
return;
|
|
61
62
|
}
|
package/utils/async/Timer.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import {XH} from '@xh/hoist/core';
|
|
8
8
|
import {wait} from '@xh/hoist/promise';
|
|
9
9
|
import {MILLISECONDS, MINUTES, olderThan} from '@xh/hoist/utils/datetime';
|
|
10
|
-
import {throwIf} from '@xh/hoist/utils/js';
|
|
10
|
+
import {logError, logWarn, throwIf} from '@xh/hoist/utils/js';
|
|
11
11
|
import {isBoolean, isFinite, isFunction, isNil, isString, pull} from 'lodash';
|
|
12
12
|
|
|
13
13
|
/**
|
|
@@ -122,7 +122,7 @@ export class Timer {
|
|
|
122
122
|
try {
|
|
123
123
|
await (this.internalRunFn() as any).timeout(this.timeoutMs);
|
|
124
124
|
} catch (e) {
|
|
125
|
-
|
|
125
|
+
logError(['Error executing timer', e], this);
|
|
126
126
|
}
|
|
127
127
|
this.isRunning = false;
|
|
128
128
|
this.lastRun = new Date();
|
|
@@ -152,8 +152,9 @@ export class Timer {
|
|
|
152
152
|
if (ret > 0 && ret < min) {
|
|
153
153
|
if (!warnedIntervals.has(ret)) {
|
|
154
154
|
warnedIntervals.add(ret);
|
|
155
|
-
|
|
156
|
-
`
|
|
155
|
+
logWarn(
|
|
156
|
+
`Interval of ${ret}ms requested - forcing to min interval of ${min}ms.`,
|
|
157
|
+
this
|
|
157
158
|
);
|
|
158
159
|
}
|
|
159
160
|
ret = min;
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
* Copyright © 2023 Extremely Heavy Industries Inc.
|
|
6
6
|
*/
|
|
7
7
|
import {XH} from '@xh/hoist/core';
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
8
|
+
import {computeOnce, throwIf} from '@xh/hoist/utils/js';
|
|
9
|
+
import {isNil, isString} from 'lodash';
|
|
10
10
|
import moment, {Moment, MomentInput} from 'moment';
|
|
11
11
|
|
|
12
12
|
/**
|
|
@@ -25,9 +25,9 @@ export class LocalDate {
|
|
|
25
25
|
private static _instances = new Map();
|
|
26
26
|
static VALID_UNITS = ['year', 'quarter', 'month', 'week', 'day', 'date'];
|
|
27
27
|
|
|
28
|
-
private _isoString;
|
|
29
|
-
private _moment;
|
|
30
|
-
private _date;
|
|
28
|
+
private _isoString: string;
|
|
29
|
+
private _moment: Moment;
|
|
30
|
+
private _date: Date;
|
|
31
31
|
|
|
32
32
|
//------------------------
|
|
33
33
|
// Factories
|
|
@@ -99,8 +99,8 @@ export class LocalDate {
|
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
/** Is the input value a local Date? */
|
|
102
|
-
static isLocalDate(
|
|
103
|
-
return
|
|
102
|
+
static isLocalDate(val: any): boolean {
|
|
103
|
+
return !!val?.isLocalDate;
|
|
104
104
|
}
|
|
105
105
|
|
|
106
106
|
//--------------------
|
|
@@ -168,15 +168,15 @@ export class LocalDate {
|
|
|
168
168
|
//----------------
|
|
169
169
|
// Core overrides.
|
|
170
170
|
//----------------
|
|
171
|
-
toString() {
|
|
171
|
+
toString(): string {
|
|
172
172
|
return this._isoString;
|
|
173
173
|
}
|
|
174
174
|
|
|
175
|
-
toJSON() {
|
|
175
|
+
toJSON(): string {
|
|
176
176
|
return this._isoString;
|
|
177
177
|
}
|
|
178
178
|
|
|
179
|
-
valueOf() {
|
|
179
|
+
valueOf(): string {
|
|
180
180
|
return this._isoString;
|
|
181
181
|
}
|
|
182
182
|
|
|
@@ -281,7 +281,7 @@ export class LocalDate {
|
|
|
281
281
|
return this.isWeekday ? this : this.previousWeekday();
|
|
282
282
|
}
|
|
283
283
|
|
|
284
|
-
diff(other, unit = 'days'): number {
|
|
284
|
+
diff(other: LocalDate, unit: moment.unitOfTime.Diff = 'days'): number {
|
|
285
285
|
this.ensureUnitValid(unit);
|
|
286
286
|
return this._moment.diff(other._moment, unit);
|
|
287
287
|
}
|
|
@@ -290,7 +290,7 @@ export class LocalDate {
|
|
|
290
290
|
// Implementation
|
|
291
291
|
//-------------------
|
|
292
292
|
/** @internal - use one of the static factory methods instead. */
|
|
293
|
-
private constructor(s) {
|
|
293
|
+
private constructor(s: string) {
|
|
294
294
|
const m = moment(s, 'YYYY-MM-DD', true);
|
|
295
295
|
throwIf(
|
|
296
296
|
!m.isValid(),
|
|
@@ -315,6 +315,6 @@ export class LocalDate {
|
|
|
315
315
|
* Is the input value a local Date?
|
|
316
316
|
* Convenience alias for LocalDate.isLocalDate()
|
|
317
317
|
*/
|
|
318
|
-
export function isLocalDate(val): val is LocalDate {
|
|
318
|
+
export function isLocalDate(val: any): val is LocalDate {
|
|
319
319
|
return !!val?.isLocalDate;
|
|
320
320
|
}
|