@xh/hoist 59.3.2 → 59.4.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 +25 -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 +8 -9
- 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 +2 -2
- 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/renderers/MultiFieldRenderer.ts +28 -22
- package/cmp/grouping/GroupingChooserModel.ts +2 -2
- package/cmp/tab/TabContainerModel.ts +2 -2
- package/cmp/zoneGrid/impl/ZoneGridPersistenceModel.ts +10 -4
- package/core/HoistBase.ts +44 -5
- package/core/elem.ts +2 -2
- package/core/impl/InstallServices.ts +2 -8
- package/core/load/LoadSupport.ts +3 -3
- package/core/model/HoistModel.ts +1 -1
- package/data/Store.ts +1 -1
- package/data/UrlStore.ts +3 -3
- 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/rest/Actions.ts +15 -9
- package/desktop/cmp/treemap/TreeMap.ts +4 -8
- package/package.json +1 -1
- package/svc/AutoRefreshService.ts +3 -3
- package/svc/FetchService.ts +5 -5
- package/svc/GridAutosizeService.ts +4 -7
- package/svc/TrackService.ts +6 -6
- package/svc/WebSocketService.ts +14 -15
- package/utils/async/AsyncUtils.ts +3 -2
- package/utils/async/Timer.ts +4 -3
- package/utils/js/BrowserUtils.ts +8 -8
- package/utils/js/LangUtils.ts +10 -9
- package/utils/js/LogUtils.ts +66 -26
- package/utils/react/LayoutPropUtils.ts +3 -3
|
@@ -22,7 +22,7 @@ import {mask} from '@xh/hoist/desktop/cmp/mask';
|
|
|
22
22
|
import '@xh/hoist/desktop/register';
|
|
23
23
|
import {Highcharts} from '@xh/hoist/kit/highcharts';
|
|
24
24
|
import {wait} from '@xh/hoist/promise';
|
|
25
|
-
import {logWithDebug
|
|
25
|
+
import {logWithDebug} from '@xh/hoist/utils/js';
|
|
26
26
|
import {
|
|
27
27
|
createObservableRef,
|
|
28
28
|
getLayoutProps,
|
|
@@ -193,13 +193,9 @@ class TreeMapLocalModel extends HoistModel {
|
|
|
193
193
|
if (parentDims.width === 0 || parentDims.height === 0) return;
|
|
194
194
|
|
|
195
195
|
assign(config.chart, parentDims, {renderTo: chartElem});
|
|
196
|
-
withDebug(
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
this.chart = Highcharts.chart(config);
|
|
200
|
-
},
|
|
201
|
-
this
|
|
202
|
-
);
|
|
196
|
+
this.withDebug(['Creating new TreeMap', `${newData.length} records`], () => {
|
|
197
|
+
this.chart = Highcharts.chart(config);
|
|
198
|
+
});
|
|
203
199
|
}
|
|
204
200
|
|
|
205
201
|
@logWithDebug
|
package/package.json
CHANGED
|
@@ -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/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/TrackService.ts
CHANGED
|
@@ -97,19 +97,19 @@ export class TrackService extends HoistService {
|
|
|
97
97
|
|
|
98
98
|
const {maxDataLength} = this.conf;
|
|
99
99
|
if (params.data?.length > maxDataLength) {
|
|
100
|
-
|
|
101
|
-
`
|
|
100
|
+
this.logWarn(
|
|
101
|
+
`Track log includes ${params.data.length} chars of JSON data`,
|
|
102
|
+
`exceeds limit of ${maxDataLength}`,
|
|
103
|
+
'data will not be persisted',
|
|
102
104
|
options.data
|
|
103
105
|
);
|
|
104
106
|
params.data = null;
|
|
105
107
|
}
|
|
106
108
|
|
|
107
109
|
const elapsedStr = params.elapsed != null ? `${params.elapsed}ms` : null,
|
|
108
|
-
|
|
109
|
-
.filter(it => it != null)
|
|
110
|
-
.join(' | ');
|
|
110
|
+
consoleMsgs = [params.category, params.msg, elapsedStr].filter(it => it != null);
|
|
111
111
|
|
|
112
|
-
|
|
112
|
+
this.logInfo(...consoleMsgs);
|
|
113
113
|
|
|
114
114
|
await XH.fetchJson({url: 'xh/track', params});
|
|
115
115
|
} catch (e) {
|
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 {logWarn, throwIf} from '@xh/hoist/utils/js';
|
|
11
11
|
import {isBoolean, isFinite, isFunction, isNil, isString, pull} from 'lodash';
|
|
12
12
|
|
|
13
13
|
/**
|
|
@@ -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;
|
package/utils/js/BrowserUtils.ts
CHANGED
|
@@ -10,8 +10,7 @@ import {pick} from 'lodash';
|
|
|
10
10
|
* Extract information (if available) about the client browser's window, screen, and network speed.
|
|
11
11
|
*/
|
|
12
12
|
export function getClientDeviceInfo() {
|
|
13
|
-
const data: any = pick(
|
|
14
|
-
window,
|
|
13
|
+
const data: any = pick(window, [
|
|
15
14
|
'screen',
|
|
16
15
|
'devicePixelRatio',
|
|
17
16
|
'screenX',
|
|
@@ -20,10 +19,10 @@ export function getClientDeviceInfo() {
|
|
|
20
19
|
'innerHeight',
|
|
21
20
|
'outerWidth',
|
|
22
21
|
'outerHeight'
|
|
23
|
-
);
|
|
22
|
+
]);
|
|
23
|
+
|
|
24
24
|
if (data.screen) {
|
|
25
|
-
data.screen = pick(
|
|
26
|
-
data.screen,
|
|
25
|
+
data.screen = pick(data.screen, [
|
|
27
26
|
'availWidth',
|
|
28
27
|
'availHeight',
|
|
29
28
|
'width',
|
|
@@ -33,14 +32,15 @@ export function getClientDeviceInfo() {
|
|
|
33
32
|
'availLeft',
|
|
34
33
|
'availTop',
|
|
35
34
|
'orientation'
|
|
36
|
-
);
|
|
35
|
+
]);
|
|
37
36
|
if (data.screen.orientation) {
|
|
38
|
-
data.screen.orientation = pick(data.screen.orientation, 'angle', 'type');
|
|
37
|
+
data.screen.orientation = pick(data.screen.orientation, ['angle', 'type']);
|
|
39
38
|
}
|
|
40
39
|
}
|
|
40
|
+
|
|
41
41
|
const nav = window.navigator as any;
|
|
42
42
|
if (nav.connection) {
|
|
43
|
-
data.connection = pick(nav.connection, 'downlink', 'effectiveType', 'rtt');
|
|
43
|
+
data.connection = pick(nav.connection, ['downlink', 'effectiveType', 'rtt']);
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
return data;
|
package/utils/js/LangUtils.ts
CHANGED
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
isEmpty,
|
|
13
13
|
isFunction,
|
|
14
14
|
isObject,
|
|
15
|
-
|
|
15
|
+
isPlainObject,
|
|
16
16
|
isUndefined,
|
|
17
17
|
mixin,
|
|
18
18
|
uniq,
|
|
@@ -53,22 +53,23 @@ export function withDefault<T>(...args: T[]): T {
|
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
/**
|
|
56
|
-
* Recursively freeze an object, preventing future modifications.
|
|
57
|
-
*
|
|
58
|
-
* freezable without side effects. This avoids freezing other types of objects where this routine
|
|
56
|
+
* Recursively freeze an object, preventing future modifications. Only the specific declared
|
|
57
|
+
* input types will be frozen. This avoids freezing other types of objects where this routine
|
|
59
58
|
* could be problematic - e.g. application or library classes (such as `moment`!) which rely on
|
|
60
59
|
* their internal state remaining mutable to function.
|
|
61
60
|
*/
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
61
|
+
export function deepFreeze<
|
|
62
|
+
T extends Record<string, unknown> | Array<unknown> | Map<unknown, unknown> | Set<unknown>
|
|
63
|
+
>(obj: T): Readonly<T> {
|
|
64
|
+
if (!(isPlainObject(obj) || isArray(obj) || obj instanceof Map || obj instanceof Set)) {
|
|
65
|
+
return obj;
|
|
66
|
+
}
|
|
65
67
|
|
|
66
68
|
const propNames = Object.getOwnPropertyNames(obj);
|
|
67
69
|
for (const name of propNames) {
|
|
68
70
|
deepFreeze(obj[name]);
|
|
69
71
|
}
|
|
70
|
-
|
|
71
|
-
return Object.freeze(obj);
|
|
72
|
+
return Object.freeze<T>(obj);
|
|
72
73
|
}
|
|
73
74
|
|
|
74
75
|
/**
|
package/utils/js/LogUtils.ts
CHANGED
|
@@ -4,10 +4,14 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Copyright © 2023 Extremely Heavy Industries Inc.
|
|
6
6
|
*/
|
|
7
|
+
import {Some} from '@xh/hoist/core';
|
|
7
8
|
import {castArray, isString} from 'lodash';
|
|
9
|
+
import {intersperse} from './LangUtils';
|
|
10
|
+
|
|
11
|
+
export type LogSource = string | {displayName: string} | {constructor: {name: string}};
|
|
8
12
|
|
|
9
13
|
/**
|
|
10
|
-
*
|
|
14
|
+
* Time and log execution of a function to `console.info()`.
|
|
11
15
|
*
|
|
12
16
|
* This method will log the provided message(s) with timing information in a single message *after*
|
|
13
17
|
* the tracked function returns.
|
|
@@ -20,61 +24,78 @@ import {castArray, isString} from 'lodash';
|
|
|
20
24
|
* @param fn - function to execute
|
|
21
25
|
* @param source - class, function or string to label the source of the message
|
|
22
26
|
*/
|
|
23
|
-
export function withInfo<T>(msgs:
|
|
27
|
+
export function withInfo<T>(msgs: Some<unknown>, fn: () => T, source?: LogSource): T {
|
|
24
28
|
return loggedDo(msgs, fn, source, 'info');
|
|
25
29
|
}
|
|
26
30
|
|
|
27
31
|
/**
|
|
28
|
-
*
|
|
32
|
+
* Time and log execution of a function to `console.debug()`.
|
|
29
33
|
* @see withInfo
|
|
30
34
|
*/
|
|
31
|
-
export function withDebug<T>(msgs:
|
|
35
|
+
export function withDebug<T>(msgs: Some<unknown>, fn: () => T, source?: LogSource): T {
|
|
32
36
|
return loggedDo(msgs, fn, source, 'debug');
|
|
33
37
|
}
|
|
34
38
|
|
|
35
39
|
/**
|
|
36
|
-
*
|
|
37
|
-
*
|
|
40
|
+
* Write to `console.log()` with standardized formatting and source info.
|
|
38
41
|
* @param msgs - message(s) to output
|
|
39
42
|
* @param source - class, function or string to label the source of the message
|
|
40
43
|
*/
|
|
41
|
-
export function logInfo(msgs:
|
|
44
|
+
export function logInfo(msgs: Some<unknown>, source?: LogSource) {
|
|
42
45
|
return loggedDo(msgs, null, source, 'info');
|
|
43
46
|
}
|
|
44
47
|
|
|
45
48
|
/**
|
|
46
|
-
*
|
|
47
|
-
* @
|
|
49
|
+
* Write to `console.debug()` with standardized formatting and source info.
|
|
50
|
+
* @param msgs - message(s) to output
|
|
51
|
+
* @param source - class, function or string to label the source of the message
|
|
48
52
|
*/
|
|
49
|
-
export function logDebug(msgs:
|
|
53
|
+
export function logDebug(msgs: Some<unknown>, source?: LogSource) {
|
|
50
54
|
return loggedDo(msgs, null, source, 'debug');
|
|
51
55
|
}
|
|
52
56
|
|
|
57
|
+
/**
|
|
58
|
+
* Write to `console.error()` with standardized formatting and source info.
|
|
59
|
+
* @param msgs - message(s) to output
|
|
60
|
+
* @param source - class, function or string to label the source of the message
|
|
61
|
+
*/
|
|
62
|
+
export function logError(msgs: Some<unknown>, source?: LogSource) {
|
|
63
|
+
return loggedDo(msgs, null, source, 'error');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Write to `console.warn()` with standardized formatting and source info.
|
|
68
|
+
* @param msgs - message(s) to output
|
|
69
|
+
* @param source - class, function or string to label the source of the message
|
|
70
|
+
*/
|
|
71
|
+
export function logWarn(msgs: Some<unknown>, source?: LogSource) {
|
|
72
|
+
return loggedDo(msgs, null, source, 'warn');
|
|
73
|
+
}
|
|
74
|
+
|
|
53
75
|
//----------------------------------
|
|
54
76
|
// Implementation
|
|
55
77
|
//----------------------------------
|
|
56
|
-
function loggedDo(
|
|
57
|
-
|
|
58
|
-
msgs = castArray(
|
|
59
|
-
const msg = msgs.join(' | ');
|
|
78
|
+
function loggedDo<T>(messages: Some<unknown>, fn: () => T, source: LogSource, level: LogLevel) {
|
|
79
|
+
let src = parseSource(source);
|
|
80
|
+
let msgs = castArray(messages);
|
|
60
81
|
|
|
61
82
|
// Support simple message only.
|
|
62
83
|
if (!fn) {
|
|
63
|
-
writeLog(
|
|
84
|
+
writeLog(msgs, src, level);
|
|
64
85
|
return;
|
|
65
86
|
}
|
|
66
87
|
|
|
67
88
|
// Otherwise, wrap the call to the provided fn.
|
|
68
|
-
let start, ret;
|
|
89
|
+
let start: number, ret: T;
|
|
69
90
|
const logCompletion = () => {
|
|
70
91
|
const elapsed = Date.now() - start;
|
|
71
|
-
writeLog(`${
|
|
92
|
+
writeLog([...msgs, `${elapsed}ms`], src, level);
|
|
72
93
|
},
|
|
73
94
|
logException = e => {
|
|
74
95
|
const elapsed = Date.now() - start;
|
|
75
96
|
writeLog(
|
|
76
|
-
|
|
77
|
-
|
|
97
|
+
[...msgs, `failed - ${e.message ?? e.name ?? 'Unknown error'}`, `${elapsed}ms`, e],
|
|
98
|
+
src,
|
|
78
99
|
level
|
|
79
100
|
);
|
|
80
101
|
};
|
|
@@ -96,14 +117,33 @@ function loggedDo(msgs, fn, source, level) {
|
|
|
96
117
|
return ret;
|
|
97
118
|
}
|
|
98
119
|
|
|
99
|
-
function parseSource(source) {
|
|
120
|
+
function parseSource(source: LogSource): string {
|
|
121
|
+
if (!source) return null;
|
|
100
122
|
if (isString(source)) return source;
|
|
101
|
-
if (source
|
|
102
|
-
if (source
|
|
103
|
-
return
|
|
123
|
+
if (source['displayName']) return source['displayName'];
|
|
124
|
+
if (source.constructor) return source.constructor.name;
|
|
125
|
+
return null;
|
|
104
126
|
}
|
|
105
127
|
|
|
106
|
-
function writeLog(
|
|
107
|
-
if (
|
|
108
|
-
|
|
128
|
+
function writeLog(msgs: unknown[], src: string, level: LogLevel) {
|
|
129
|
+
if (src) msgs = [`[${src}]`, ...msgs];
|
|
130
|
+
|
|
131
|
+
msgs = intersperse(msgs, '|');
|
|
132
|
+
|
|
133
|
+
switch (level) {
|
|
134
|
+
case 'error':
|
|
135
|
+
console.error(...msgs);
|
|
136
|
+
break;
|
|
137
|
+
case 'warn':
|
|
138
|
+
console.warn(...msgs);
|
|
139
|
+
break;
|
|
140
|
+
case 'debug':
|
|
141
|
+
console.debug(...msgs);
|
|
142
|
+
break;
|
|
143
|
+
case 'info':
|
|
144
|
+
console.log(...msgs);
|
|
145
|
+
break;
|
|
146
|
+
}
|
|
109
147
|
}
|
|
148
|
+
|
|
149
|
+
type LogLevel = 'error' | 'warn' | 'info' | 'debug';
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Copyright © 2023 Extremely Heavy Industries Inc.
|
|
6
6
|
*/
|
|
7
|
-
import {HoistProps, LayoutProps
|
|
7
|
+
import {HoistProps, LayoutProps} from '@xh/hoist/core';
|
|
8
8
|
import {forOwn, isEmpty, isNumber, isString, isNil, omit, pick} from 'lodash';
|
|
9
9
|
|
|
10
10
|
/**
|
|
@@ -40,9 +40,9 @@ import {forOwn, isEmpty, isNumber, isString, isNil, omit, pick} from 'lodash';
|
|
|
40
40
|
* that afforded by the underlying flexbox styles. In particular, it accepts flex and sizing props
|
|
41
41
|
* as raw numbers rather than strings.
|
|
42
42
|
*/
|
|
43
|
-
export function getLayoutProps(props:
|
|
43
|
+
export function getLayoutProps(props: HoistProps): LayoutProps {
|
|
44
44
|
// Harvest all keys of interest
|
|
45
|
-
const ret: LayoutProps = pick(props, allKeys);
|
|
45
|
+
const ret: LayoutProps = pick(props, allKeys) as LayoutProps;
|
|
46
46
|
|
|
47
47
|
// flexXXX: convert raw number to string
|
|
48
48
|
const flexConfig = pick(ret, flexKeys);
|