@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.
Files changed (83) hide show
  1. package/CHANGELOG.md +39 -3
  2. package/admin/differ/DifferModel.ts +5 -7
  3. package/appcontainer/AppContainerModel.ts +8 -10
  4. package/appcontainer/AppStateModel.ts +3 -2
  5. package/appcontainer/PageStateModel.ts +1 -2
  6. package/appcontainer/SizingModeModel.ts +2 -2
  7. package/cmp/ag-grid/AgGrid.ts +4 -3
  8. package/cmp/ag-grid/AgGridModel.ts +19 -14
  9. package/cmp/chart/Chart.ts +4 -3
  10. package/cmp/dataview/DataViewModel.ts +2 -2
  11. package/cmp/filter/FilterChooserModel.ts +5 -5
  12. package/cmp/grid/Grid.ts +9 -7
  13. package/cmp/grid/GridContextMenu.ts +2 -2
  14. package/cmp/grid/GridModel.ts +13 -21
  15. package/cmp/grid/Types.ts +6 -5
  16. package/cmp/grid/columns/Column.ts +12 -12
  17. package/cmp/grid/columns/ColumnGroup.ts +17 -6
  18. package/cmp/grid/helpers/GridCountLabel.ts +5 -4
  19. package/cmp/grid/impl/ColumnWidthCalculator.ts +3 -3
  20. package/cmp/grid/impl/GridPersistenceModel.ts +11 -4
  21. package/cmp/grid/impl/Utils.ts +1 -1
  22. package/cmp/grid/renderers/MultiFieldRenderer.ts +28 -22
  23. package/cmp/grouping/GroupingChooserModel.ts +2 -2
  24. package/cmp/relativetimestamp/RelativeTimestamp.ts +5 -2
  25. package/cmp/tab/TabContainerModel.ts +2 -2
  26. package/cmp/zoneGrid/ZoneGridModel.ts +1 -1
  27. package/cmp/zoneGrid/impl/ZoneGridPersistenceModel.ts +10 -4
  28. package/core/HoistBase.ts +48 -8
  29. package/core/HoistBaseDecorators.ts +11 -6
  30. package/core/HoistComponent.ts +1 -3
  31. package/core/elem.ts +2 -2
  32. package/core/exception/ExceptionHandler.ts +4 -4
  33. package/core/impl/InstallServices.ts +2 -8
  34. package/core/load/LoadSupport.ts +10 -11
  35. package/core/model/HoistModel.ts +1 -1
  36. package/data/Store.ts +1 -1
  37. package/data/UrlStore.ts +3 -3
  38. package/data/cube/aggregate/UniqueAggregator.ts +3 -5
  39. package/data/filter/CompoundFilter.ts +5 -3
  40. package/data/filter/FieldFilter.ts +4 -3
  41. package/data/filter/Filter.ts +2 -3
  42. package/data/filter/FunctionFilter.ts +2 -1
  43. package/data/impl/RecordSet.ts +5 -5
  44. package/desktop/appcontainer/ToastSource.ts +1 -1
  45. package/desktop/cmp/button/ColChooserButton.ts +7 -5
  46. package/desktop/cmp/button/ZoneMapperButton.ts +7 -5
  47. package/desktop/cmp/dash/canvas/DashCanvasModel.ts +2 -2
  48. package/desktop/cmp/dash/container/DashContainerModel.ts +2 -2
  49. package/desktop/cmp/dock/DockViewModel.ts +21 -11
  50. package/desktop/cmp/dock/impl/DockView.ts +4 -2
  51. package/desktop/cmp/form/FormField.ts +2 -2
  52. package/desktop/cmp/grid/editors/BooleanEditor.ts +5 -1
  53. package/desktop/cmp/input/DateInput.ts +1 -3
  54. package/desktop/cmp/panel/Panel.ts +4 -2
  55. package/desktop/cmp/panel/PanelModel.ts +5 -5
  56. package/desktop/cmp/rest/Actions.ts +15 -9
  57. package/desktop/cmp/treemap/TreeMap.ts +7 -10
  58. package/inspector/instances/InstancesModel.ts +6 -6
  59. package/mobile/cmp/button/ColAutosizeButton.ts +4 -3
  60. package/mobile/cmp/button/ColChooserButton.ts +4 -3
  61. package/mobile/cmp/button/ExpandCollapseButton.ts +4 -3
  62. package/mobile/cmp/button/ZoneMapperButton.ts +4 -3
  63. package/mobile/cmp/input/DateInput.ts +1 -1
  64. package/mobile/cmp/input/Select.ts +3 -3
  65. package/mobile/cmp/panel/Panel.ts +4 -2
  66. package/package.json +2 -2
  67. package/svc/AutoRefreshService.ts +3 -3
  68. package/svc/ChangelogService.ts +3 -3
  69. package/svc/EnvironmentService.ts +1 -1
  70. package/svc/FetchService.ts +5 -5
  71. package/svc/GridAutosizeService.ts +4 -7
  72. package/svc/GridExportService.ts +3 -8
  73. package/svc/IdentityService.ts +1 -1
  74. package/svc/TrackService.ts +9 -15
  75. package/svc/WebSocketService.ts +14 -15
  76. package/utils/async/AsyncUtils.ts +3 -2
  77. package/utils/async/Timer.ts +5 -4
  78. package/utils/datetime/LocalDate.ts +13 -13
  79. package/utils/js/BrowserUtils.ts +8 -8
  80. package/utils/js/Decorators.ts +18 -3
  81. package/utils/js/LangUtils.ts +10 -9
  82. package/utils/js/LogUtils.ts +66 -26
  83. 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
- console.error(
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
- console.error(
34
- "No GridModel available to ColChooserButton. Provide via a 'gridModel' prop, or context."
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
- console.error(
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
- console.error(
32
- "No ZoneGridModel available to ZoneMapperButton. Provide via a 'zoneGridModel' prop, or context."
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
- console.debug('DateInput value exceeded max/minDate bounds on change - reset to null.');
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
- ? reactCreatableSelect
276
- : reactSelect;
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
- console.error(e);
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
- console.warn(
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.2",
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": "^1.3.4",
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 {logDebug, withDefault} from '@xh/hoist/utils/js';
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 || document.hidden) return;
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.', this);
72
+ this.logDebug('Triggering application auto-refresh');
73
73
  await ctx.autoRefreshAsync();
74
74
  }
75
75
  }
@@ -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
- console.warn('Unable to mark changelog as read when latest version is SNAPSHOT.');
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
- console.warn(
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
- console.error(
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
- console.warn(
139
+ this.logWarn(
140
140
  `Version mismatch detected between client and server - ${clientVersion} vs ${appVersion}`
141
141
  );
142
142
  }
@@ -4,7 +4,7 @@
4
4
  *
5
5
  * Copyright © 2023 Extremely Heavy Industries Inc.
6
6
  */
7
- import {HoistService, XH, Exception, PlainObject, Thunkable, FetchResponse} from '@xh/hoist/core';
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: Thunkable<PlainObject>) {
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 PlainObject.
331
+ * When using `fetch`, provide a string. Otherwise, provide a JSON Serializable object
332
332
  */
333
- body?: PlainObject | string;
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?: PlainObject;
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
- console.debug('Autosize aborted, grid data is obsolete.');
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
- console.debug(
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
- console.debug('Column widths filled via GridAutosizeService', fillWidths);
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
- console.warn('Grid not rendered - unable to fill columns.');
170
+ this.logWarn('Grid not rendered - unable to fill columns.');
174
171
  return [];
175
172
  }
176
173
 
@@ -327,13 +327,8 @@ export class GridExportService extends HoistService {
327
327
  : it.exportName;
328
328
 
329
329
  if (!isString(ret)) {
330
- console.warn(
331
- 'Tried to export column ' +
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
- console.warn(
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
  );
@@ -62,7 +62,7 @@ export class IdentityService extends HoistService {
62
62
  try {
63
63
  await XH.appModel?.logoutAsync();
64
64
  } catch (e) {
65
- console.error('Error calling XH.appModel.logoutAsync()', e);
65
+ this.logError('Error calling XH.appModel.logoutAsync()', e);
66
66
  }
67
67
  return XH.fetchJson({url: 'xh/logout'})
68
68
  .then(() => XH.reloadApp())
@@ -45,19 +45,13 @@ export class TrackService extends HoistService {
45
45
 
46
46
  // Short-circuit if disabled...
47
47
  if (!this.enabled) {
48
- console.debug(
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
- console.warn(
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
- console.warn(
101
- `[TrackService] | Track log includes ${params.data.length} chars of JSON data | exceeds limit of ${maxDataLength} | data will not be persisted`,
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
- consoleMsg = ['[Track]', params.category, params.msg, elapsedStr]
109
- .filter(it => it != null)
110
- .join(' | ');
104
+ consoleMsgs = [params.category, params.msg, elapsedStr].filter(it => it != null);
111
105
 
112
- console.log(consoleMsg);
106
+ this.logInfo(...consoleMsgs);
113
107
 
114
108
  await XH.fetchJson({url: 'xh/track', params});
115
109
  } catch (e) {
116
- console.error(`[TrackService] | Failed to persist track log`, options, e);
110
+ this.logError('Failed to persist track log', options, e);
117
111
  }
118
112
  }
119
113
  }
@@ -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, observable, makeObservable} from '@xh/hoist/mobx';
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
- /** set to true to log all sent/received messages - very chatty. */
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
- console.error(
76
- 'WebSockets have been enabled on this client app, but are disabled on the server. ' +
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
- console.error('Failure creating WebSocket in WebSocketService', e);
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
- console.warn('Heartbeat found websocket not connected - attempting to reconnect.');
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
- console.debug('WebSocket connection opened', ev);
187
+ this.logDebug('WebSocket connection opened', ev);
189
188
  this.updateConnectedStatus();
190
189
  }
191
190
 
192
191
  onClose(ev) {
193
- console.debug('WebSocket connection closed', ev);
192
+ this.logDebug('WebSocket connection closed', ev);
194
193
  this.updateConnectedStatus();
195
194
  }
196
195
 
197
196
  onError(ev) {
198
- console.error('WebSocket connection error', ev);
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
- console.error('Error decoding websocket message', rawMsg, e);
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
- console.error(`Failure in subscription handler for topic ${message.topic}`, e);
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) console.log(...args);
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 browser tab is hidden (i.e. document.hidden is true) this loop will be executed
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 (document.hidden) {
59
+ if (!XH.pageIsVisible) {
59
60
  while (conditionFn()) fn();
60
61
  return;
61
62
  }
@@ -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
- console.error('Error executing timer:', e);
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
- console.warn(
156
- `Timer interval of ${ret}ms requested - forcing to min interval of ${min}ms.`
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 {throwIf, computeOnce} from '@xh/hoist/utils/js';
9
- import {isString, isNil} from 'lodash';
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(v: any): boolean {
103
- return v?.isLocalDate;
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
  }