@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
@@ -4,17 +4,17 @@
4
4
  *
5
5
  * Copyright © 2023 Extremely Heavy Industries Inc.
6
6
  */
7
- import {HAlign, PlainObject, Some, Thunkable} from '@xh/hoist/core';
7
+ import {HAlign, PlainObject, Some, Thunkable, XH} from '@xh/hoist/core';
8
8
  import {genDisplayName} from '@xh/hoist/data';
9
+
10
+ import type {ColGroupDef} from '@xh/hoist/kit/ag-grid';
9
11
  import {throwIf, withDefault} from '@xh/hoist/utils/js';
10
- import {clone, isEmpty, isFunction, isString} from 'lodash';
12
+ import {clone, isEmpty, isFunction, isString, keysIn} from 'lodash';
11
13
  import {ReactNode} from 'react';
12
14
  import {GridModel} from '../GridModel';
13
15
  import {ColumnHeaderClassFn, ColumnHeaderNameFn} from '../Types';
14
16
  import {Column, ColumnSpec, getAgHeaderClassFn} from './Column';
15
17
 
16
- import type {ColGroupDef} from '@xh/hoist/kit/ag-grid';
17
-
18
18
  export interface ColumnGroupSpec {
19
19
  /** Column or ColumnGroup configs for children of this group.*/
20
20
  children: Array<ColumnGroupSpec | ColumnSpec>;
@@ -39,6 +39,8 @@ export interface ColumnGroupSpec {
39
39
 
40
40
  /** True to skip this column when adding to grid. */
41
41
  omit?: Thunkable<boolean>;
42
+
43
+ appData?: PlainObject;
42
44
  }
43
45
 
44
46
  /**
@@ -62,6 +64,8 @@ export class ColumnGroup {
62
64
  */
63
65
  agOptions?: PlainObject;
64
66
 
67
+ appData: PlainObject;
68
+
65
69
  /**
66
70
  * Not for application use. ColumnGroups are created internally by Hoist.
67
71
  *
@@ -83,6 +87,7 @@ export class ColumnGroup {
83
87
  headerAlign,
84
88
  agOptions,
85
89
  borders,
90
+ appData,
86
91
  ...rest
87
92
  } = config;
88
93
 
@@ -92,8 +97,6 @@ export class ColumnGroup {
92
97
  'Must specify groupId or a string headerName for a ColumnGroup'
93
98
  );
94
99
 
95
- Object.assign(this, rest);
96
-
97
100
  this.groupId = withDefault(groupId, headerName) as string;
98
101
  this.headerName = withDefault(headerName, genDisplayName(this.groupId));
99
102
  this.headerClass = headerClass;
@@ -102,6 +105,14 @@ export class ColumnGroup {
102
105
  this.children = children;
103
106
  this.gridModel = gridModel;
104
107
  this.agOptions = agOptions ? clone(agOptions) : {};
108
+ this.appData = appData ? clone(appData) : {};
109
+
110
+ if (!isEmpty(rest)) {
111
+ const keys = keysIn(rest);
112
+ throw XH.exception(
113
+ `Column group '${this.groupId}' configured with unsupported key(s) '${keys}'. Custom config data must be nested within the 'appData' property.`
114
+ );
115
+ }
105
116
  }
106
117
 
107
118
  getAgSpec(): ColGroupDef {
@@ -4,11 +4,11 @@
4
4
  *
5
5
  * Copyright © 2023 Extremely Heavy Industries Inc.
6
6
  */
7
- import {GridModel} from '../GridModel';
8
7
  import {box} from '@xh/hoist/cmp/layout';
9
8
  import {BoxProps, hoistCmp, HoistProps, useContextModel} from '@xh/hoist/core';
10
9
  import {fmtNumber} from '@xh/hoist/format';
11
- import {pluralize, singularize, withDefault} from '@xh/hoist/utils/js';
10
+ import {logError, pluralize, singularize, withDefault} from '@xh/hoist/utils/js';
11
+ import {GridModel} from '../GridModel';
12
12
 
13
13
  export interface GridCountLabelProps extends HoistProps, BoxProps {
14
14
  /** GridModel to which this component should bind. */
@@ -49,8 +49,9 @@ export const [GridCountLabel, gridCountLabel] = hoistCmp.withFactory<GridCountLa
49
49
  gridModel = withDefault(gridModel, useContextModel(GridModel));
50
50
 
51
51
  if (!gridModel) {
52
- console.error(
53
- "No GridModel available to GridCountLabel. Provide via a 'gridModel' prop, or context."
52
+ logError(
53
+ `GridModel not found - provide via 'gridModel' prop or context.`,
54
+ GridCountLabel
54
55
  );
55
56
  return '';
56
57
  }
@@ -9,7 +9,7 @@ import {GridAutosizeOptions} from '@xh/hoist/cmp/grid/GridAutosizeOptions';
9
9
  import {XH} from '@xh/hoist/core';
10
10
  import {CompoundFilter, FieldFilter, Filter, StoreRecord} from '@xh/hoist/data';
11
11
  import {forEachAsync} from '@xh/hoist/utils/async';
12
- import {stripTags} from '@xh/hoist/utils/js';
12
+ import {logWarn, stripTags} from '@xh/hoist/utils/js';
13
13
  import {
14
14
  forOwn,
15
15
  groupBy,
@@ -74,7 +74,7 @@ export class ColumnWidthCalculator {
74
74
  try {
75
75
  return this.getHeaderWidth(gridModel, column, autosizeIncludeHeaderIcons, bufferPx);
76
76
  } catch (e) {
77
- console.warn(`Error calculating max header width for column "${column.colId}".`, e);
77
+ logWarn([`Error calculating max header width for colId '${column.colId}'.`, e], this);
78
78
  } finally {
79
79
  this.resetHeaderClassNames();
80
80
  }
@@ -104,7 +104,7 @@ export class ColumnWidthCalculator {
104
104
  return await this.calcLevelWidthAsync(gridModel, records, column, options);
105
105
  }
106
106
  } catch (e) {
107
- console.warn(`Error calculating max data width for column "${column.colId}".`, e);
107
+ logWarn([`Error calculating max data width for colId '${column.colId}'.`, e], this);
108
108
  } finally {
109
109
  this.resetClassNames();
110
110
  }
@@ -4,11 +4,12 @@
4
4
  *
5
5
  * Copyright © 2023 Extremely Heavy Industries Inc.
6
6
  */
7
- import {HoistModel, managed, PersistenceProvider, PlainObject, XH} from '@xh/hoist/core';
7
+ import {GridSorterLike} from '@xh/hoist/cmp/grid';
8
+ import {HoistModel, managed, PersistenceProvider, Some, XH} from '@xh/hoist/core';
8
9
  import {action, makeObservable, observable} from '@xh/hoist/mobx';
9
10
  import {isUndefined} from 'lodash';
10
11
  import {GridModel} from '../GridModel';
11
- import {GridModelPersistOptions} from '../Types';
12
+ import {ColumnState, GridModelPersistOptions} from '../Types';
12
13
 
13
14
  /**
14
15
  * Model to manage persisting state from GridModel.
@@ -22,7 +23,13 @@ export class GridPersistenceModel extends HoistModel {
22
23
  gridModel: GridModel;
23
24
 
24
25
  @observable.ref
25
- state: PlainObject;
26
+ state: {
27
+ columns?: Partial<ColumnState>[];
28
+ sortBy?: Some<GridSorterLike>;
29
+ groupBy?: Some<string>;
30
+ version?: number;
31
+ autosize?: any;
32
+ };
26
33
 
27
34
  @managed
28
35
  provider: PersistenceProvider;
@@ -51,7 +58,7 @@ export class GridPersistenceModel extends HoistModel {
51
58
  run: state => this.provider.write(state)
52
59
  });
53
60
  } catch (e) {
54
- console.error(e);
61
+ this.logError(e);
55
62
  this.state = {version: this.VERSION};
56
63
  }
57
64
 
@@ -19,7 +19,7 @@ export function managedRenderer<T extends ColumnRenderer | GroupRowRenderer>(
19
19
  try {
20
20
  return fn.apply(null, arguments);
21
21
  } catch (e) {
22
- console.warn(`Renderer for '${identifier}' has thrown an error.`, e);
22
+ console.warn(`Renderer for '${identifier}' has thrown an error`, e);
23
23
  return '#ERROR';
24
24
  }
25
25
  } as unknown as T;
@@ -6,8 +6,8 @@
6
6
  */
7
7
  import {ColumnRenderer} from '@xh/hoist/cmp/grid';
8
8
  import {div, span} from '@xh/hoist/cmp/layout';
9
- import {throwIf, warnIf} from '@xh/hoist/utils/js';
10
- import {isString, partition} from 'lodash';
9
+ import {throwIf, warnIf, intersperse} from '@xh/hoist/utils/js';
10
+ import {isNil, isString, partition, pull} from 'lodash';
11
11
  import {ReactNode} from 'react';
12
12
 
13
13
  /**
@@ -30,24 +30,24 @@ export function multiFieldRenderer(value, context): ReactNode {
30
30
  );
31
31
 
32
32
  const {mainRenderer, delimiter, subFields = []} = multiFieldConfig,
33
- [topFields, bottomFields] = partition(subFields, it => it.position === 'top'),
34
- topRowItems = [],
35
- bottomRowItems = [];
33
+ [topFields, bottomFields] = partition(subFields, it => it.position === 'top');
36
34
 
37
- // Render main field to top row
38
- topRowItems.push(renderMainField(value, mainRenderer, context));
35
+ // Render main field and subfields to top row
36
+ let topRowItems: ReactNode[] = [
37
+ renderMainField(value, mainRenderer, context),
38
+ ...topFields.map(it => renderSubField(it, context))
39
+ ];
40
+ pull(topRowItems, null);
39
41
 
40
- // Render SubFields to top row
41
- topFields.forEach(it => {
42
- if (delimiter) topRowItems.push(renderDelimiter(delimiter));
43
- topRowItems.push(renderSubField(it, context));
44
- });
42
+ // Render subfield to bottom row
43
+ let bottomRowItems: ReactNode[] = bottomFields.map(it => renderSubField(it, context));
44
+ pull(bottomRowItems, null);
45
45
 
46
- // Render SubFields to bottom row
47
- bottomFields.forEach((it, idx) => {
48
- if (delimiter && idx > 0) bottomRowItems.push(renderDelimiter(delimiter));
49
- bottomRowItems.push(renderSubField(it, context));
50
- });
46
+ // Insert delimiter if applicable
47
+ if (delimiter) {
48
+ topRowItems = intersperse(topRowItems, renderDelimiter(delimiter));
49
+ bottomRowItems = intersperse(bottomRowItems, renderDelimiter(delimiter));
50
+ }
51
51
 
52
52
  return div({
53
53
  className: 'xh-multifield-renderer',
@@ -107,14 +107,20 @@ function renderSubField({colId, label}, context) {
107
107
 
108
108
  if (label && !isString(label)) label = headerName;
109
109
 
110
- return div({
111
- className: 'xh-multifield-renderer-field',
112
- items: [label ? `${label}: ` : null, renderValue(value, renderer, column, context)]
113
- });
110
+ const renderedVal = renderValue(value, renderer, column, context),
111
+ renderedValIsEmpty = renderedVal === '' || isNil(renderedVal);
112
+
113
+ return renderedValIsEmpty
114
+ ? null
115
+ : div({
116
+ className: 'xh-multifield-renderer-field',
117
+ items: [label ? `${label}: ` : null, renderedVal]
118
+ });
114
119
  }
115
120
 
116
121
  function renderValue(value, renderer, column, context) {
117
- return renderer ? renderer(value, {...context, column}) : value;
122
+ const ret = renderer ? renderer(value, {...context, column}) : value;
123
+ return isNil(ret) ? null : ret;
118
124
  }
119
125
 
120
126
  function renderDelimiter(delimiter) {
@@ -170,7 +170,7 @@ export class GroupingChooserModel extends HoistModel {
170
170
  run: state => this.provider.write(state)
171
171
  });
172
172
  } catch (e) {
173
- console.error(e);
173
+ this.logError(e);
174
174
  XH.safeDestroy(this.provider);
175
175
  this.provider = null;
176
176
  }
@@ -190,7 +190,7 @@ export class GroupingChooserModel extends HoistModel {
190
190
  @action
191
191
  setValue(value: string[]) {
192
192
  if (!this.validateValue(value)) {
193
- console.warn('Attempted to set GroupingChooser to invalid value: ', value);
193
+ this.logWarn('Attempted to set invalid value', value);
194
194
  return;
195
195
  }
196
196
  this.value = value;
@@ -20,7 +20,7 @@ import {fmtCompactDate, fmtDateTime} from '@xh/hoist/format';
20
20
  import {action, computed, makeObservable, observable} from '@xh/hoist/mobx';
21
21
  import {Timer} from '@xh/hoist/utils/async';
22
22
  import {DAYS, HOURS, LocalDate, SECONDS} from '@xh/hoist/utils/datetime';
23
- import {withDefault} from '@xh/hoist/utils/js';
23
+ import {logWarn, withDefault} from '@xh/hoist/utils/js';
24
24
 
25
25
  interface RelativeTimestampProps extends HoistProps, BoxProps {
26
26
  /**
@@ -231,7 +231,10 @@ function doFormat(timestamp: Date | number, opts: RelativeTimestampOptions): str
231
231
 
232
232
  // 1) Degenerate cases
233
233
  if (isFuture && !allowFuture) {
234
- console.warn(`Unexpected future date provided for timestamp: ${elapsed}ms in the future.`);
234
+ logWarn(
235
+ `Unexpected future date provided for timestamp: ${elapsed}ms in the future.`,
236
+ RelativeTimestamp
237
+ );
235
238
  return '[????]';
236
239
  }
237
240
 
@@ -144,7 +144,7 @@ export class TabContainerModel extends HoistModel {
144
144
 
145
145
  if (route) {
146
146
  if (XH.isMobileApp) {
147
- console.warn('Tab container routing is not supported for mobile applications.');
147
+ this.logWarn('TabContainer routing is not supported for mobile applications.');
148
148
  return;
149
149
  }
150
150
 
@@ -384,7 +384,7 @@ export class TabContainerModel extends HoistModel {
384
384
  this.provider = PersistenceProvider.create({path: 'tabContainer', ...persistWith});
385
385
  state = this.provider.read() || null;
386
386
  } catch (e) {
387
- console.error(e);
387
+ this.logError(e);
388
388
  XH.safeDestroy(this.provider);
389
389
  this.provider = null;
390
390
  }
@@ -620,7 +620,7 @@ export class ZoneGridModel extends HoistModel {
620
620
  ret[zone] = this.parseZoneMapping(zone, rawMapping);
621
621
  } catch (e) {
622
622
  if (strict) throw e;
623
- console.warn(e.message);
623
+ this.logWarn(e.message);
624
624
  ret[zone] = this._defaultState.mappings[zone];
625
625
  }
626
626
  });
@@ -4,11 +4,12 @@
4
4
  *
5
5
  * Copyright © 2023 Extremely Heavy Industries Inc.
6
6
  */
7
- import {HoistModel, managed, PersistenceProvider, PlainObject} from '@xh/hoist/core';
7
+ import {GridSorterLike} from '@xh/hoist/cmp/grid';
8
+ import {HoistModel, managed, PersistenceProvider, Some} from '@xh/hoist/core';
8
9
  import {action, makeObservable, observable} from '@xh/hoist/mobx';
9
10
  import {isUndefined} from 'lodash';
10
11
  import {ZoneGridModel} from '../ZoneGridModel';
11
- import {ZoneGridModelPersistOptions} from '../Types';
12
+ import {Zone, ZoneGridModelPersistOptions, ZoneMapping} from '../Types';
12
13
 
13
14
  /**
14
15
  * Model to manage persisting state from ZoneGridModel.
@@ -22,7 +23,12 @@ export class ZoneGridPersistenceModel extends HoistModel {
22
23
  zoneGridModel: ZoneGridModel;
23
24
 
24
25
  @observable.ref
25
- state: PlainObject;
26
+ state: {
27
+ sortBy?: GridSorterLike;
28
+ groupBy?: Some<string>;
29
+ version?: number;
30
+ mappings?: Record<Zone, Some<string | ZoneMapping>>;
31
+ };
26
32
 
27
33
  @managed
28
34
  provider: PersistenceProvider;
@@ -51,7 +57,7 @@ export class ZoneGridPersistenceModel extends HoistModel {
51
57
  run: state => this.provider.write(state)
52
58
  });
53
59
  } catch (e) {
54
- console.error(e);
60
+ this.logError(e);
55
61
  this.state = {version: this.VERSION};
56
62
  }
57
63
 
package/core/HoistBase.ts CHANGED
@@ -4,8 +4,17 @@
4
4
  *
5
5
  * Copyright © 2023 Extremely Heavy Industries Inc.
6
6
  */
7
- import {XH, PersistenceProvider, PersistOptions, DebounceSpec} from './';
8
- import {throwIf, getOrCreate} from '@xh/hoist/utils/js';
7
+ import {XH, PersistenceProvider, PersistOptions, DebounceSpec, Some} from './';
8
+ import {
9
+ throwIf,
10
+ getOrCreate,
11
+ logInfo,
12
+ logDebug,
13
+ logError,
14
+ logWarn,
15
+ withDebug,
16
+ withInfo
17
+ } from '@xh/hoist/utils/js';
9
18
  import {
10
19
  cloneDeep,
11
20
  debounce as lodashDebounce,
@@ -26,7 +35,7 @@ import {
26
35
  when as mobxWhen
27
36
  } from '@xh/hoist/mobx';
28
37
  import {IAutorunOptions, IReactionOptions} from 'mobx/dist/api/autorun';
29
- import {IReactionDisposer} from 'mobx/dist/internal';
38
+ import {IReactionDisposer, IEqualsComparer} from 'mobx/dist/internal';
30
39
 
31
40
  export interface HoistBaseClass {
32
41
  new (...args: any[]): HoistBase;
@@ -69,6 +78,33 @@ export abstract class HoistBase {
69
78
  /** Default persistence options for this object. */
70
79
  persistWith: PersistOptions = null;
71
80
 
81
+ //--------------------------------------------------
82
+ // Logging Delegates
83
+ //--------------------------------------------------
84
+ logInfo(...messages: unknown[]) {
85
+ logInfo(messages, this);
86
+ }
87
+
88
+ logWarn(...messages: unknown[]) {
89
+ logWarn(messages, this);
90
+ }
91
+
92
+ logError(...messages: unknown[]) {
93
+ logError(messages, this);
94
+ }
95
+
96
+ logDebug(...messages: unknown[]) {
97
+ logDebug(messages, this);
98
+ }
99
+
100
+ withInfo<T>(messages: Some<unknown>, fn: () => T): T {
101
+ return withInfo<T>(messages, fn, this);
102
+ }
103
+
104
+ withDebug<T>(messages: Some<unknown>, fn: () => T): T {
105
+ return withDebug<T>(messages, fn, this);
106
+ }
107
+
72
108
  /**
73
109
  * Add and start one or more managed reactions.
74
110
  *
@@ -104,12 +140,12 @@ export abstract class HoistBase {
104
140
  addReaction(...specs: ReactionSpec<any>[]): IReactionDisposer | IReactionDisposer[] {
105
141
  const disposers = specs.map(s => {
106
142
  if (!s) return null;
107
- let {track, when, run, debounce, ...opts} = s;
143
+ let {track, when, run, debounce, ...rest} = s;
108
144
  throwIf(
109
145
  (track && when) || (!track && !when),
110
146
  "Must specify either 'track' or 'when' in addReaction."
111
147
  );
112
- opts = parseReactionOptions(opts);
148
+ const opts = parseReactionOptions(rest);
113
149
  run = bindAndDebounce(this, run, debounce);
114
150
 
115
151
  const disposer = track ? mobxReaction(track, run, opts) : mobxWhen(when, run, opts);
@@ -231,9 +267,10 @@ export abstract class HoistBase {
231
267
  run: data => provider.write(data)
232
268
  });
233
269
  } catch (e) {
234
- console.error(
270
+ this.logError(
235
271
  `Failed to configure Persistence for '${property}'. Be sure to fully specify ` +
236
- `'persistWith' on this object or in the method call.`
272
+ `'persistWith' on this object or in the method call`,
273
+ e
237
274
  );
238
275
  }
239
276
  }
@@ -257,7 +294,7 @@ export abstract class HoistBase {
257
294
  /**
258
295
  * Object containing options accepted by MobX 'reaction' API as well as arguments below.
259
296
  */
260
- export interface ReactionSpec<T = any> extends IReactionOptions<T, any> {
297
+ export interface ReactionSpec<T = any> extends Omit<IReactionOptions<T, any>, 'equals'> {
261
298
  /**
262
299
  * Function returning data to observe - first arg to the underlying reaction() call.
263
300
  * Specify this or `when`.
@@ -275,6 +312,9 @@ export interface ReactionSpec<T = any> extends IReactionOptions<T, any> {
275
312
 
276
313
  /** Specify to debounce run function */
277
314
  debounce?: DebounceSpec;
315
+
316
+ /** Specify a default from {@link comparer} or a custom comparer function. */
317
+ equals?: keyof typeof comparer | IEqualsComparer<T>;
278
318
  }
279
319
 
280
320
  /**
@@ -6,7 +6,7 @@
6
6
  */
7
7
  import {cloneDeep, isUndefined} from 'lodash';
8
8
  import {wait} from '../promise';
9
- import {throwIf} from '../utils/js';
9
+ import {logError, throwIf} from '../utils/js';
10
10
  import {HoistBaseClass, PersistenceProvider, PersistOptions} from './';
11
11
 
12
12
  /**
@@ -66,9 +66,10 @@ function createPersistDescriptor(
66
66
  '@persist decorator should be applied to an instance of HoistBase'
67
67
  );
68
68
  if (descriptor.get || descriptor.set) {
69
- console.error(
69
+ logError(
70
70
  `Error defining ${property} : @persist or @persistWith should be defined closest ` +
71
- `to property, and after mobx annotation e.g. '@bindable @persist ${property}'`
71
+ `to property, and after mobx annotation e.g. '@bindable @persist ${property}'`,
72
+ target
72
73
  );
73
74
  return descriptor;
74
75
  }
@@ -89,9 +90,13 @@ function createPersistDescriptor(
89
90
  });
90
91
  });
91
92
  } catch (e) {
92
- console.error(
93
- `Failed to configure Persistence for '${property}'. Be sure to fully specify ` +
94
- `'persistWith' on this object or annotation.`
93
+ logError(
94
+ [
95
+ `Failed to configure Persistence for '${property}'. Be sure to fully specify ` +
96
+ `'persistWith' on this object or annotation`,
97
+ e
98
+ ],
99
+ target
95
100
  );
96
101
  }
97
102
 
@@ -282,9 +282,7 @@ function wrapWithModel(render: RenderFn, cfg: Config): RenderFn {
282
282
  if (!model && !spec.optional && spec instanceof UsesSpec) {
283
283
  console.error(`
284
284
  Failed to find model with selector '${formatSelector(spec.selector)}' for
285
- component '${
286
- cfg.displayName
287
- }'. Ensure the proper model is available via context, or
285
+ component '${cfg.displayName}'. Ensure the proper model is available via context, or
288
286
  specify explicitly using the 'model' prop.
289
287
  `);
290
288
  return cmpErrDisplay({...getLayoutProps(props), item: 'No model found'});
package/core/elem.ts CHANGED
@@ -14,7 +14,7 @@ import {
14
14
  ReactElement,
15
15
  ReactNode
16
16
  } from 'react';
17
- import {PlainObject, Some, Thunkable} from './types/Types';
17
+ import {Some, Thunkable} from './types/Types';
18
18
 
19
19
  /**
20
20
  * Alternative format for specifying React Elements in render functions. This type is designed to
@@ -39,7 +39,7 @@ import {PlainObject, Some, Thunkable} from './types/Types';
39
39
  * with this API. The '$' will be stripped from the prop name before passing it along to the
40
40
  * underlying component.
41
41
  */
42
- export type ElementSpec<P extends PlainObject> = P & {
42
+ export type ElementSpec<P> = P & {
43
43
  //---------------------------------------------
44
44
  // Enhanced attributes to support element factory
45
45
  //---------------------------------------------
@@ -6,7 +6,7 @@
6
6
  */
7
7
  import {Exception} from './Exception';
8
8
  import {fragment, span} from '@xh/hoist/cmp/layout';
9
- import {stripTags} from '@xh/hoist/utils/js';
9
+ import {logError, logWarn, stripTags} from '@xh/hoist/utils/js';
10
10
  import {Icon} from '@xh/hoist/icon';
11
11
  import {forOwn, has, isArray, isNil, isObject, omitBy, pick, set} from 'lodash';
12
12
  import {HoistException, PlainObject, XH} from '../';
@@ -189,7 +189,7 @@ export class ExceptionHandler {
189
189
  username = XH.getUsername();
190
190
 
191
191
  if (!username) {
192
- console.warn('Error report cannot be submitted to UI server - user unknown');
192
+ logWarn('Error report cannot be submitted to UI server - user unknown', this);
193
193
  return false;
194
194
  }
195
195
 
@@ -206,7 +206,7 @@ export class ExceptionHandler {
206
206
  });
207
207
  return true;
208
208
  } catch (e) {
209
- console.error('Exception while submitting error report to UI server', e);
209
+ logError(['Exception while submitting error report to UI server', e], this);
210
210
  return false;
211
211
  }
212
212
  }
@@ -261,7 +261,7 @@ export class ExceptionHandler {
261
261
  return stripTags(JSON.stringify(ret, null, 4));
262
262
  } catch (e) {
263
263
  const message = 'Failed to serialize error';
264
- console.error(message, exception, e);
264
+ logError([message, exception, e], this);
265
265
  return JSON.stringify({message}, null, 4);
266
266
  }
267
267
  }
@@ -6,7 +6,7 @@
6
6
  */
7
7
  import {HoistService, HoistServiceClass, Some, XH} from '@xh/hoist/core';
8
8
  import {instanceManager} from '@xh/hoist/core/impl/InstanceManager';
9
- import {throwIf, withDebug} from '@xh/hoist/utils/js';
9
+ import {throwIf} from '@xh/hoist/utils/js';
10
10
  import {camelCase, castArray} from 'lodash';
11
11
 
12
12
  /**
@@ -50,13 +50,7 @@ export async function installServicesAsync(serviceClasses: Some<HoistServiceClas
50
50
 
51
51
  async function initServicesInternalAsync(svcs: HoistService[]) {
52
52
  const promises = svcs.map(it => {
53
- return withDebug(
54
- `Initializing ${it.constructor.name}`,
55
- () => {
56
- return it.initAsync();
57
- },
58
- 'XH'
59
- );
53
+ return it.withDebug(`Initializing`, () => it.initAsync());
60
54
  });
61
55
 
62
56
  const results: any[] = await Promise.allSettled(promises),
@@ -4,11 +4,11 @@
4
4
  *
5
5
  * Copyright © 2023 Extremely Heavy Industries Inc.
6
6
  */
7
- import {HoistBase, managed, RefreshContextModel, TaskObserver} from '../';
7
+ import {HoistBase, managed, PlainObject, RefreshContextModel, TaskObserver} from '../';
8
8
  import {LoadSpec, Loadable} from './';
9
9
  import {makeObservable, observable, runInAction} from '@xh/hoist/mobx';
10
- import {throwIf} from '@xh/hoist/utils/js';
11
- import {isPlainObject} from 'lodash';
10
+ import {logDebug, logError, throwIf} from '@xh/hoist/utils/js';
11
+ import {isPlainObject, pull} from 'lodash';
12
12
 
13
13
  /**
14
14
  * Provides support for objects that participate in Hoist's loading/refresh lifecycle.
@@ -55,11 +55,11 @@ export class LoadSupport extends HoistBase implements Loadable {
55
55
  return this.doLoadAsync(newSpec);
56
56
  }
57
57
 
58
- async refreshAsync(meta?: object) {
58
+ async refreshAsync(meta?: PlainObject) {
59
59
  return this.loadAsync({meta, isRefresh: true});
60
60
  }
61
61
 
62
- async autoRefreshAsync(meta?: object) {
62
+ async autoRefreshAsync(meta?: PlainObject) {
63
63
  return this.loadAsync({meta, isAutoRefresh: true});
64
64
  }
65
65
 
@@ -98,18 +98,17 @@ export class LoadSupport extends HoistBase implements Loadable {
98
98
  if (target instanceof RefreshContextModel) return;
99
99
 
100
100
  const elapsed = this.lastLoadCompleted.getTime() - this.lastLoadRequested.getTime(),
101
- msg = `[${target.constructor.name}] | ${loadSpec.typeDisplay} | ${
102
- exception ? 'failed | ' : ''
103
- }${elapsed}ms`;
101
+ status = exception ? 'failed' : null,
102
+ msg = pull([loadSpec.typeDisplay, status, `${elapsed}ms`, exception], null);
104
103
 
105
104
  if (exception) {
106
105
  if (exception.isRoutine) {
107
- console.debug(msg, exception);
106
+ logDebug(msg, target);
108
107
  } else {
109
- console.error(msg, exception);
108
+ logError(msg, target);
110
109
  }
111
110
  } else {
112
- console.debug(msg);
111
+ logDebug(msg, target);
113
112
  }
114
113
  });
115
114
  }
@@ -69,7 +69,7 @@ export abstract class HoistModel extends HoistBase implements Loadable {
69
69
  @observable
70
70
  _componentProps = {};
71
71
  _modelLookup = null;
72
- _created = Date.now;
72
+ _created = Date.now();
73
73
 
74
74
  constructor() {
75
75
  super();