@xh/hoist 70.0.0-SNAPSHOT.1731022145122 → 70.0.0-SNAPSHOT.1731374612473

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 (93) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/build/types/cmp/filter/FilterChooserModel.d.ts +17 -12
  3. package/build/types/cmp/grid/GridModel.d.ts +5 -9
  4. package/build/types/cmp/grid/Types.d.ts +7 -19
  5. package/build/types/cmp/grid/columns/Column.d.ts +0 -1
  6. package/build/types/cmp/grid/impl/InitPersist.d.ts +7 -0
  7. package/build/types/cmp/grouping/GroupingChooserModel.d.ts +6 -8
  8. package/build/types/cmp/tab/TabContainerModel.d.ts +10 -4
  9. package/build/types/cmp/zoneGrid/Types.d.ts +6 -6
  10. package/build/types/cmp/zoneGrid/ZoneGridModel.d.ts +0 -2
  11. package/build/types/cmp/zoneGrid/impl/InitPersist.d.ts +7 -0
  12. package/build/types/core/HoistBase.d.ts +1 -1
  13. package/build/types/core/persist/CustomProvider.d.ts +5 -6
  14. package/build/types/core/persist/DashViewProvider.d.ts +6 -6
  15. package/build/types/core/persist/LocalStorageProvider.d.ts +4 -5
  16. package/build/types/core/persist/PersistOptions.d.ts +5 -4
  17. package/build/types/core/persist/Persistable.d.ts +14 -0
  18. package/build/types/core/persist/PersistenceProvider.d.ts +47 -34
  19. package/build/types/core/persist/PrefProvider.d.ts +5 -5
  20. package/build/types/core/persist/index.d.ts +2 -0
  21. package/build/types/core/persist/viewmanager/Types.d.ts +46 -0
  22. package/build/types/core/persist/viewmanager/ViewManagerModel.d.ts +149 -0
  23. package/build/types/core/persist/viewmanager/ViewManagerProvider.d.ts +10 -0
  24. package/build/types/core/persist/viewmanager/impl/ManageDialogModel.d.ts +30 -0
  25. package/build/types/core/persist/viewmanager/impl/SaveDialogModel.d.ts +23 -0
  26. package/build/types/core/persist/viewmanager/index.d.ts +2 -0
  27. package/build/types/desktop/cmp/button/ColAutosizeButton.d.ts +1 -1
  28. package/build/types/desktop/cmp/dash/DashConfig.d.ts +3 -1
  29. package/build/types/desktop/cmp/dash/DashModel.d.ts +1 -2
  30. package/build/types/desktop/cmp/dash/DashViewSpec.d.ts +1 -1
  31. package/build/types/desktop/cmp/dash/canvas/DashCanvasModel.d.ts +10 -2
  32. package/build/types/desktop/cmp/dash/container/DashContainerModel.d.ts +26 -10
  33. package/build/types/desktop/cmp/dash/container/impl/DashContainerUtils.d.ts +4 -2
  34. package/build/types/desktop/cmp/panel/PanelModel.d.ts +8 -4
  35. package/build/types/desktop/cmp/viewmanager/ViewManager.d.ts +22 -0
  36. package/build/types/desktop/cmp/viewmanager/cmp/ManageDialog.d.ts +6 -0
  37. package/build/types/desktop/cmp/viewmanager/cmp/SaveDialog.d.ts +2 -0
  38. package/build/types/desktop/cmp/viewmanager/index.d.ts +3 -0
  39. package/build/types/kit/blueprint/Wrappers.d.ts +1 -1
  40. package/build/types/mobile/cmp/button/ColAutosizeButton.d.ts +1 -1
  41. package/build/types/svc/GridAutosizeService.d.ts +2 -5
  42. package/build/types/svc/JsonBlobService.d.ts +45 -24
  43. package/cmp/filter/FilterChooserModel.ts +142 -125
  44. package/cmp/grid/Grid.ts +2 -10
  45. package/cmp/grid/GridModel.ts +18 -31
  46. package/cmp/grid/Types.ts +7 -21
  47. package/cmp/grid/columns/Column.ts +0 -1
  48. package/cmp/grid/impl/InitPersist.ts +71 -0
  49. package/cmp/grouping/GroupingChooserModel.ts +48 -57
  50. package/cmp/tab/TabContainerModel.ts +22 -36
  51. package/cmp/zoneGrid/Types.ts +6 -6
  52. package/cmp/zoneGrid/ZoneGridModel.ts +2 -7
  53. package/cmp/zoneGrid/impl/InitPersist.ts +70 -0
  54. package/core/HoistBase.ts +14 -22
  55. package/core/HoistBaseDecorators.ts +26 -28
  56. package/core/persist/CustomProvider.ts +7 -10
  57. package/core/persist/DashViewProvider.ts +8 -10
  58. package/core/persist/LocalStorageProvider.ts +9 -12
  59. package/core/persist/PersistOptions.ts +6 -4
  60. package/core/persist/Persistable.ts +23 -0
  61. package/core/persist/PersistenceProvider.ts +159 -79
  62. package/core/persist/PrefProvider.ts +9 -12
  63. package/core/persist/index.ts +2 -0
  64. package/core/persist/viewmanager/Types.ts +51 -0
  65. package/core/persist/viewmanager/ViewManagerModel.ts +515 -0
  66. package/core/persist/viewmanager/ViewManagerProvider.ts +51 -0
  67. package/core/persist/viewmanager/impl/ManageDialogModel.ts +274 -0
  68. package/core/persist/viewmanager/impl/SaveDialogModel.ts +112 -0
  69. package/core/persist/viewmanager/index.ts +2 -0
  70. package/desktop/cmp/button/ColAutosizeButton.ts +1 -1
  71. package/desktop/cmp/dash/DashConfig.ts +3 -1
  72. package/desktop/cmp/dash/DashModel.ts +1 -2
  73. package/desktop/cmp/dash/DashViewSpec.ts +1 -1
  74. package/desktop/cmp/dash/canvas/DashCanvasModel.ts +31 -30
  75. package/desktop/cmp/dash/container/DashContainerModel.ts +68 -43
  76. package/desktop/cmp/dash/container/impl/DashContainerUtils.ts +13 -4
  77. package/desktop/cmp/leftrightchooser/LeftRightChooserFilter.ts +1 -1
  78. package/desktop/cmp/panel/PanelModel.ts +33 -53
  79. package/desktop/cmp/store/impl/StoreFilterField.ts +1 -1
  80. package/desktop/cmp/viewmanager/ViewManager.scss +58 -0
  81. package/desktop/cmp/viewmanager/ViewManager.ts +274 -0
  82. package/desktop/cmp/viewmanager/cmp/ManageDialog.ts +197 -0
  83. package/desktop/cmp/viewmanager/cmp/SaveDialog.ts +89 -0
  84. package/desktop/cmp/viewmanager/index.ts +3 -0
  85. package/mobile/cmp/button/ColAutosizeButton.ts +1 -1
  86. package/package.json +1 -1
  87. package/svc/GridAutosizeService.ts +73 -36
  88. package/svc/JsonBlobService.ts +64 -31
  89. package/tsconfig.tsbuildinfo +1 -1
  90. package/build/types/cmp/grid/impl/GridPersistenceModel.d.ts +0 -41
  91. package/build/types/cmp/zoneGrid/impl/ZoneGridPersistenceModel.d.ts +0 -39
  92. package/cmp/grid/impl/GridPersistenceModel.ts +0 -174
  93. package/cmp/zoneGrid/impl/ZoneGridPersistenceModel.ts +0 -149
@@ -0,0 +1,71 @@
1
+ /*
2
+ * This file belongs to Hoist, an application development toolkit
3
+ * developed by Extremely Heavy Industries (www.xh.io | info@xh.io)
4
+ *
5
+ * Copyright © 2024 Extremely Heavy Industries Inc.
6
+ */
7
+ import {PersistableState, PersistenceProvider} from '@xh/hoist/core';
8
+ import {isObject} from 'lodash';
9
+ import {GridModel} from '../GridModel';
10
+ import {GridModelPersistOptions} from '../Types';
11
+
12
+ /**
13
+ * Initialize persistence for a {@link GridModel} by applying its `persistWith` config.
14
+ * @internal
15
+ */
16
+ export function initPersist(
17
+ gridModel: GridModel,
18
+ {
19
+ persistColumns = true,
20
+ persistGrouping = true,
21
+ persistSort = true,
22
+ path = 'grid',
23
+ ...rootPersistWith
24
+ }: GridModelPersistOptions
25
+ ) {
26
+ if (persistColumns) {
27
+ const persistWith = isObject(persistColumns) ? persistColumns : rootPersistWith;
28
+ PersistenceProvider.create({
29
+ persistOptions: {
30
+ path: `${path}.columns`,
31
+ ...persistWith
32
+ },
33
+ target: {
34
+ getPersistableState: () => new PersistableState(gridModel.persistableColumnState),
35
+ setPersistableState: ({value}) => gridModel.setColumnState(value)
36
+ },
37
+ owner: gridModel
38
+ });
39
+ }
40
+
41
+ if (persistSort) {
42
+ const persistWith = isObject(persistSort) ? persistSort : rootPersistWith;
43
+ PersistenceProvider.create({
44
+ persistOptions: {
45
+ path: `${path}.sortBy`,
46
+ ...persistWith
47
+ },
48
+ target: {
49
+ getPersistableState: () =>
50
+ new PersistableState(gridModel.sortBy.map(it => it.toString())),
51
+ setPersistableState: ({value}) => gridModel.setSortBy(value)
52
+ },
53
+ owner: gridModel
54
+ });
55
+ }
56
+
57
+ if (persistGrouping) {
58
+ const persistWith = isObject(persistSort) ? persistSort : rootPersistWith;
59
+ PersistenceProvider.create({
60
+ persistOptions: {
61
+ path: `${path}.groupBy`,
62
+ ...persistWith
63
+ },
64
+ target: {
65
+ getPersistableState: () => new PersistableState(gridModel.groupBy),
66
+ setPersistableState: ({value}) => gridModel.setGroupBy(value)
67
+ },
68
+ owner: gridModel
69
+ });
70
+ }
71
+ }
@@ -5,19 +5,12 @@
5
5
  * Copyright © 2024 Extremely Heavy Industries Inc.
6
6
  */
7
7
 
8
- import {
9
- HoistModel,
10
- managed,
11
- PersistenceProvider,
12
- PersistOptions,
13
- PlainObject,
14
- XH
15
- } from '@xh/hoist/core';
8
+ import {HoistModel, PersistableState, PersistenceProvider, PersistOptions} from '@xh/hoist/core';
16
9
  import {genDisplayName} from '@xh/hoist/data';
17
10
  import {action, computed, makeObservable, observable} from '@xh/hoist/mobx';
18
11
  import {executeIfFunction, throwIf} from '@xh/hoist/utils/js';
19
12
  import {createObservableRef} from '@xh/hoist/utils/react';
20
- import {cloneDeep, difference, isArray, isEmpty, isEqual, isString, keys, sortBy} from 'lodash';
13
+ import {difference, isArray, isEmpty, isEqual, isObject, isString, keys, sortBy} from 'lodash';
21
14
 
22
15
  export interface GroupingChooserConfig {
23
16
  /**
@@ -62,11 +55,11 @@ export interface DimensionSpec {
62
55
  }
63
56
 
64
57
  export interface GroupingChooserPersistOptions extends PersistOptions {
65
- /** True (default) to save value to state. */
66
- persistValue?: boolean;
58
+ /** True (default) to include value or provide value-specific PersistOptions. */
59
+ persistValue?: boolean | PersistOptions;
67
60
 
68
- /** True (default) to include favorites. */
69
- persistFavorites?: boolean;
61
+ /** True (default) to include favorites or provide favorites-specific PersistOptions. */
62
+ persistFavorites?: boolean | PersistOptions;
70
63
  }
71
64
 
72
65
  export class GroupingChooserModel extends HoistModel {
@@ -76,9 +69,6 @@ export class GroupingChooserModel extends HoistModel {
76
69
  allowEmpty: boolean;
77
70
  maxDepth: number;
78
71
  commitOnChange: boolean;
79
-
80
- @managed provider: PersistenceProvider = null;
81
- persistValue: boolean = false;
82
72
  persistFavorites: boolean = false;
83
73
 
84
74
  // Implementation fields for Control
@@ -140,34 +130,10 @@ export class GroupingChooserModel extends HoistModel {
140
130
  throwIf(isEmpty(value) && !this.allowEmpty, 'Initial value cannot be empty.');
141
131
  throwIf(!this.validateValue(value), 'Initial value is invalid.');
142
132
 
143
- // Read state from provider -- fail gently
144
- if (persistWith) {
145
- try {
146
- this.provider = PersistenceProvider.create({
147
- path: 'groupingChooser',
148
- ...persistWith
149
- });
150
- this.persistValue = persistWith.persistValue ?? true;
151
- this.persistFavorites = persistWith.persistFavorites ?? true;
152
-
153
- const state = cloneDeep(this.provider.read());
154
- if (this.persistValue && state?.value && this.validateValue(state?.value)) {
155
- value = state.value;
156
- }
157
- if (this.persistFavorites && state?.favorites) {
158
- favorites = state.favorites;
159
- }
160
-
161
- this.addReaction({
162
- track: () => this.persistState,
163
- run: state => this.provider.write(state)
164
- });
165
- } catch (e) {
166
- this.logError(e);
167
- XH.safeDestroy(this.provider);
168
- this.provider = null;
169
- }
170
- }
133
+ this.setValue(value);
134
+ this.setFavorites(favorites);
135
+
136
+ if (persistWith) this.initPersist(persistWith);
171
137
 
172
138
  this.addReaction({
173
139
  track: () => this.pendingValue,
@@ -175,9 +141,6 @@ export class GroupingChooserModel extends HoistModel {
175
141
  if (this.commitOnChange) this.setValue(this.pendingValue);
176
142
  }
177
143
  });
178
-
179
- this.setValue(value);
180
- this.setFavorites(favorites);
181
144
  }
182
145
 
183
146
  @action
@@ -320,19 +283,47 @@ export class GroupingChooserModel extends HoistModel {
320
283
  return this.favorites?.some(v => isEqual(v, value));
321
284
  }
322
285
 
323
- //-------------------------
324
- // Persistence handling
325
- //-------------------------
326
- get persistState() {
327
- const ret: PlainObject = {};
328
- if (this.persistValue) ret.value = this.value;
329
- if (this.persistFavorites) ret.favorites = this.favorites;
330
- return ret;
331
- }
332
-
333
286
  //------------------------
334
287
  // Implementation
335
288
  //------------------------
289
+ private initPersist({
290
+ persistValue = true,
291
+ persistFavorites = true,
292
+ path = 'groupingChooser',
293
+ ...rootPersistWith
294
+ }: GroupingChooserPersistOptions) {
295
+ if (persistValue) {
296
+ const persistWith = isObject(persistValue) ? persistValue : rootPersistWith;
297
+ PersistenceProvider.create({
298
+ persistOptions: {
299
+ path: `${path}.value`,
300
+ ...persistWith
301
+ },
302
+ target: {
303
+ getPersistableState: () => new PersistableState(this.value),
304
+ setPersistableState: ({value}) => this.setValue(value)
305
+ },
306
+ owner: this
307
+ });
308
+ }
309
+
310
+ if (persistFavorites) {
311
+ const persistWith = isObject(persistFavorites) ? persistFavorites : rootPersistWith,
312
+ provider = PersistenceProvider.create({
313
+ persistOptions: {
314
+ path: `${path}.favorites`,
315
+ ...persistWith
316
+ },
317
+ target: {
318
+ getPersistableState: () => new PersistableState(this.favorites),
319
+ setPersistableState: ({value}) => this.setFavorites(value)
320
+ },
321
+ owner: this
322
+ });
323
+ if (provider) this.persistFavorites = true;
324
+ }
325
+ }
326
+
336
327
  private normalizeDimensions(
337
328
  dims: Array<DimensionSpec | string>
338
329
  ): Record<string, DimensionSpec> {
@@ -7,6 +7,8 @@
7
7
  import {
8
8
  HoistModel,
9
9
  managed,
10
+ Persistable,
11
+ PersistableState,
10
12
  PersistenceProvider,
11
13
  PersistOptions,
12
14
  RefreshContextModel,
@@ -18,7 +20,7 @@ import {action, makeObservable, observable} from '@xh/hoist/mobx';
18
20
  import {wait} from '@xh/hoist/promise';
19
21
  import {isOmitted} from '@xh/hoist/utils/impl';
20
22
  import {ensureUniqueBy, throwIf} from '@xh/hoist/utils/js';
21
- import {difference, find, findLast, isString, isUndefined, without} from 'lodash';
23
+ import {difference, find, findLast, isString, without} from 'lodash';
22
24
  import {ReactNode} from 'react';
23
25
  import {TabConfig, TabModel} from './TabModel';
24
26
  import {TabSwitcherProps} from './TabSwitcherProps';
@@ -85,7 +87,7 @@ export interface TabContainerConfig {
85
87
  *
86
88
  * Note: Routing is currently enabled for desktop applications only.
87
89
  */
88
- export class TabContainerModel extends HoistModel {
90
+ export class TabContainerModel extends HoistModel implements Persistable<{activeTabId: string}> {
89
91
  declare config: TabContainerConfig;
90
92
 
91
93
  @managed
@@ -102,7 +104,6 @@ export class TabContainerModel extends HoistModel {
102
104
  renderMode: RenderMode;
103
105
  refreshMode: RefreshMode;
104
106
  emptyText: ReactNode;
105
- provider: PersistenceProvider;
106
107
 
107
108
  @managed
108
109
  refreshContextModel: RefreshContextModel;
@@ -156,7 +157,13 @@ export class TabContainerModel extends HoistModel {
156
157
 
157
158
  this.forwardRouterToTab(this.activeTabId);
158
159
  } else if (persistWith) {
159
- this.setupStateProvider(persistWith);
160
+ PersistenceProvider.create({
161
+ persistOptions: {
162
+ path: 'tabContainer',
163
+ ...persistWith
164
+ },
165
+ target: this
166
+ });
160
167
  }
161
168
 
162
169
  if (track) {
@@ -320,6 +327,17 @@ export class TabContainerModel extends HoistModel {
320
327
  if (target) this.activateTab(target);
321
328
  }
322
329
 
330
+ //-------------------------
331
+ // Persistable Interface
332
+ //-------------------------
333
+ getPersistableState(): PersistableState<{activeTabId: string}> {
334
+ return new PersistableState({activeTabId: this.activeTabId});
335
+ }
336
+
337
+ setPersistableState(state: PersistableState<{activeTabId: string}>): void {
338
+ this.activateTab(state.value.activeTabId);
339
+ }
340
+
323
341
  //-------------------------
324
342
  // Implementation
325
343
  //-------------------------
@@ -374,38 +392,6 @@ export class TabContainerModel extends HoistModel {
374
392
 
375
393
  return null;
376
394
  }
377
-
378
- private setupStateProvider(persistWith) {
379
- // Read state from provider -- fail gently
380
- let state = null;
381
-
382
- try {
383
- this.provider = PersistenceProvider.create({path: 'tabContainer', ...persistWith});
384
- state = this.provider.read() || null;
385
- } catch (e) {
386
- this.logError(e);
387
- XH.safeDestroy(this.provider);
388
- this.provider = null;
389
- }
390
-
391
- // Initialize state, or clear if state's activeTabId no longer exists
392
- if (!isUndefined(state?.activeTabId)) {
393
- const id = state.activeTabId;
394
- if (this.findTab(id)) {
395
- this.activateTab(id);
396
- } else {
397
- this.provider.clear();
398
- }
399
- }
400
-
401
- // Attach to provider last
402
- if (this.provider) {
403
- this.addReaction({
404
- track: () => this.activeTabId,
405
- run: activeTabId => this.provider.write({activeTabId})
406
- });
407
- }
408
- }
409
395
  }
410
396
 
411
397
  export interface AddTabOptions {
@@ -38,10 +38,10 @@ export interface ZoneField {
38
38
  }
39
39
 
40
40
  export interface ZoneGridModelPersistOptions extends PersistOptions {
41
- /** True to include mapping information (default true) */
42
- persistMapping?: boolean;
43
- /** True to include grouping information (default true) */
44
- persistGrouping?: boolean;
45
- /** True to include sorting information (default true) */
46
- persistSort?: boolean;
41
+ /** True (default) to include mapping state or provide mapping-specific PersistOptions. */
42
+ persistMappings?: boolean | PersistOptions;
43
+ /** True (default) to include grouping state or provide grouping-specific PersistOptions. */
44
+ persistGrouping?: boolean | PersistOptions;
45
+ /** True (default) to include sort state or provide sort-specific PersistOptions. */
46
+ persistSort?: boolean | PersistOptions;
47
47
  }
@@ -56,7 +56,7 @@ import {action, bindable, makeObservable, observable} from '@xh/hoist/mobx';
56
56
  import {executeIfFunction, throwIf, withDefault} from '@xh/hoist/utils/js';
57
57
  import {castArray, find, forOwn, isEmpty, isFinite, isPlainObject, isString} from 'lodash';
58
58
  import {ReactNode} from 'react';
59
- import {ZoneGridPersistenceModel} from './impl/ZoneGridPersistenceModel';
59
+ import {initPersist} from './impl/InitPersist';
60
60
  import {ZoneMapperConfig, ZoneMapperModel} from './impl/ZoneMapperModel';
61
61
  import {Zone, ZoneGridModelPersistOptions, ZoneLimit, ZoneMapping} from './Types';
62
62
 
@@ -315,7 +315,6 @@ export class ZoneGridModel extends HoistModel {
315
315
  restoreDefaultsWarning: ReactNode;
316
316
 
317
317
  private _defaultState; // initial state provided to ctor - powers restoreDefaults().
318
- @managed persistenceModel: ZoneGridPersistenceModel;
319
318
 
320
319
  constructor(config: ZoneGridConfig) {
321
320
  super();
@@ -363,9 +362,7 @@ export class ZoneGridModel extends HoistModel {
363
362
  this.setGroupBy(groupBy);
364
363
 
365
364
  this.mapperModel = this.parseMapperModel(zoneMapperModel);
366
- this.persistenceModel = persistWith
367
- ? new ZoneGridPersistenceModel(this, persistWith)
368
- : null;
365
+ if (persistWith) initPersist(this, persistWith);
369
366
 
370
367
  this.addReaction({
371
368
  track: () => [this.leftColumnSpec, this.rightColumnSpec],
@@ -398,8 +395,6 @@ export class ZoneGridModel extends HoistModel {
398
395
  this.setSortBy(sortBy);
399
396
  this.setGroupBy(groupBy);
400
397
 
401
- this.persistenceModel?.clear();
402
-
403
398
  if (this.restoreDefaultsFn) {
404
399
  await this.restoreDefaultsFn();
405
400
  }
@@ -0,0 +1,70 @@
1
+ /*
2
+ * This file belongs to Hoist, an application development toolkit
3
+ * developed by Extremely Heavy Industries (www.xh.io | info@xh.io)
4
+ *
5
+ * Copyright © 2024 Extremely Heavy Industries Inc.
6
+ */
7
+ import {PersistableState, PersistenceProvider} from '@xh/hoist/core';
8
+ import {isObject} from 'lodash';
9
+ import {ZoneGridModel} from '../ZoneGridModel';
10
+ import {ZoneGridModelPersistOptions} from '../Types';
11
+
12
+ /**
13
+ * Initialize persistence for a {@link ZoneGridModel} by applying its `persistWith` config.
14
+ * @internal
15
+ */
16
+ export function initPersist(
17
+ zoneGridModel: ZoneGridModel,
18
+ {
19
+ persistMappings = true,
20
+ persistGrouping = true,
21
+ persistSort = true,
22
+ path = 'zoneGrid',
23
+ ...rootPersistWith
24
+ }: ZoneGridModelPersistOptions
25
+ ) {
26
+ if (persistMappings) {
27
+ const persistWith = isObject(persistMappings) ? persistMappings : rootPersistWith;
28
+ PersistenceProvider.create({
29
+ persistOptions: {
30
+ path: `${path}.mappings`,
31
+ ...persistWith
32
+ },
33
+ target: {
34
+ getPersistableState: () => new PersistableState(zoneGridModel.mappings),
35
+ setPersistableState: ({value}) => zoneGridModel.setMappings(value)
36
+ },
37
+ owner: zoneGridModel
38
+ });
39
+ }
40
+
41
+ if (persistGrouping) {
42
+ const persistWith = isObject(persistGrouping) ? persistGrouping : rootPersistWith;
43
+ PersistenceProvider.create({
44
+ persistOptions: {
45
+ path: `${path}.groupBy`,
46
+ ...persistWith
47
+ },
48
+ target: {
49
+ getPersistableState: () => new PersistableState(zoneGridModel.groupBy),
50
+ setPersistableState: ({value}) => zoneGridModel.setGroupBy(value)
51
+ },
52
+ owner: zoneGridModel
53
+ });
54
+ }
55
+
56
+ if (persistSort) {
57
+ const persistWith = isObject(persistSort) ? persistSort : rootPersistWith;
58
+ PersistenceProvider.create({
59
+ persistOptions: {
60
+ path: `${path}.sortBy`,
61
+ ...persistWith
62
+ },
63
+ target: {
64
+ getPersistableState: () => new PersistableState(zoneGridModel.sortBy?.toString()),
65
+ setPersistableState: ({value}) => zoneGridModel.setSortBy(value)
66
+ },
67
+ owner: zoneGridModel
68
+ });
69
+ }
70
+ }
package/core/HoistBase.ts CHANGED
@@ -4,7 +4,8 @@
4
4
  *
5
5
  * Copyright © 2024 Extremely Heavy Industries Inc.
6
6
  */
7
- import {XH, PersistenceProvider, PersistOptions, DebounceSpec, Some} from './';
7
+ import {runInAction} from 'mobx';
8
+ import {XH, PersistenceProvider, PersistOptions, DebounceSpec, Some, PersistableState} from './';
8
9
  import {
9
10
  throwIf,
10
11
  getOrCreate,
@@ -16,19 +17,16 @@ import {
16
17
  withInfo
17
18
  } from '@xh/hoist/utils/js';
18
19
  import {
19
- cloneDeep,
20
20
  debounce as lodashDebounce,
21
21
  isFunction,
22
22
  isNil,
23
23
  isNumber,
24
24
  isPlainObject,
25
25
  isString,
26
- isUndefined,
27
26
  upperFirst
28
27
  } from 'lodash';
29
28
  import {
30
29
  action,
31
- runInAction,
32
30
  comparer,
33
31
  autorun as mobxAutorun,
34
32
  reaction as mobxReaction,
@@ -258,26 +256,20 @@ export abstract class HoistBase {
258
256
  * @param options - options governing the persistence of this object. These will be applied
259
257
  * on top of any default persistWith options defined on the instance itself.
260
258
  */
261
- markPersist(property: string, options: PersistOptions = {}) {
259
+ markPersist(property: keyof this & string, options: PersistOptions = {}) {
262
260
  // Read from and attach to Provider, failing gently
263
- try {
264
- const persistWith = {path: property, ...this.persistWith, ...options},
265
- provider = this.markManaged(PersistenceProvider.create(persistWith)),
266
- providerState = provider.read();
267
- if (!isUndefined(providerState)) {
268
- runInAction(() => (this[property] = cloneDeep(providerState)));
261
+ PersistenceProvider.create({
262
+ persistOptions: {
263
+ path: property,
264
+ ...this.persistWith,
265
+ ...options
266
+ },
267
+ owner: this,
268
+ target: {
269
+ getPersistableState: () => new PersistableState(this[property]),
270
+ setPersistableState: state => runInAction(() => (this[property] = state.value))
269
271
  }
270
- this.addReaction({
271
- track: () => this[property],
272
- run: data => provider.write(data)
273
- });
274
- } catch (e) {
275
- this.logError(
276
- `Failed to configure Persistence for '${property}'. Be sure to fully specify ` +
277
- `'persistWith' on this object or in the method call`,
278
- e
279
- );
280
- }
272
+ });
281
273
  }
282
274
 
283
275
  /** @returns true if this instance has been destroyed. */
@@ -4,10 +4,8 @@
4
4
  *
5
5
  * Copyright © 2024 Extremely Heavy Industries Inc.
6
6
  */
7
- import {cloneDeep, isUndefined} from 'lodash';
8
- import {wait} from '../promise';
9
7
  import {logError, throwIf} from '../utils/js';
10
- import {HoistBaseClass, PersistenceProvider, PersistOptions} from './';
8
+ import {HoistBaseClass, PersistableState, PersistenceProvider, PersistOptions} from './';
11
9
 
12
10
  /**
13
11
  * Decorator to make a property "managed". Managed properties are designed to hold objects that
@@ -74,34 +72,34 @@ function createPersistDescriptor(
74
72
  return descriptor;
75
73
  }
76
74
  const codeValue = descriptor.initializer;
75
+ let hasInitialized = false,
76
+ ret;
77
77
  const initializer = function () {
78
- let providerState;
78
+ // Initializer can be called multiple times when stacking decorators.
79
+ if (hasInitialized) return ret;
79
80
 
80
- // Read from and attach to Provider.
81
- // Fail gently -- initialization exceptions causes stack overflows for MobX.
82
- try {
83
- const persistWith = {path: property, ...this.persistWith, ...options},
84
- provider = this.markManaged(PersistenceProvider.create(persistWith));
85
- providerState = cloneDeep(provider.read());
86
- wait().then(() => {
87
- this.addReaction({
88
- track: () => this[property],
89
- run: data => provider.write(data)
90
- });
91
- });
92
- } catch (e) {
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
100
- );
101
- }
81
+ // codeValue undefined if no initial in-code value provided, otherwise call to get initial value.
82
+ ret = codeValue?.call(this);
102
83
 
103
- // 2) Return data from provider data *or* code, if provider not yet set or failed
104
- return !isUndefined(providerState) ? providerState : codeValue?.call(this);
84
+ const persistOptions = {path: property, ...this.persistWith, ...options};
85
+ PersistenceProvider.create({
86
+ persistOptions,
87
+ owner: this,
88
+ target: {
89
+ getPersistableState: () =>
90
+ new PersistableState(hasInitialized ? this[property] : ret),
91
+ setPersistableState: state => {
92
+ if (!hasInitialized) {
93
+ ret = state.value;
94
+ } else {
95
+ this[property] = state.value;
96
+ }
97
+ }
98
+ }
99
+ });
100
+
101
+ hasInitialized = true;
102
+ return ret;
105
103
  };
106
104
  return {...descriptor, initializer};
107
105
  }
@@ -5,7 +5,7 @@
5
5
  * Copyright © 2024 Extremely Heavy Industries Inc.
6
6
  */
7
7
 
8
- import {PersistenceProvider, PersistOptions} from './';
8
+ import {PersistenceProvider, PersistenceProviderConfig} from './';
9
9
  import {throwIf} from '@xh/hoist/utils/js';
10
10
 
11
11
  /**
@@ -15,16 +15,17 @@ import {throwIf} from '@xh/hoist/utils/js';
15
15
  * This provider allows applications to use the Persistence API to populate and read state from
16
16
  * components without actually writing to any pre-defined storage.
17
17
  */
18
- export class CustomProvider extends PersistenceProvider {
19
- getData;
20
- setData;
18
+ export class CustomProvider<S> extends PersistenceProvider<S> {
19
+ readonly getData;
20
+ readonly setData;
21
21
 
22
- constructor({getData, setData, ...rest}: PersistOptions) {
22
+ constructor(cfg: PersistenceProviderConfig<S>) {
23
+ super(cfg);
24
+ const {getData, setData} = cfg.persistOptions;
23
25
  throwIf(
24
26
  !getData || !setData,
25
27
  `CustomProvider requires a 'getData' and a 'setData' function.`
26
28
  );
27
- super(rest);
28
29
  this.getData = getData;
29
30
  this.setData = setData;
30
31
  }
@@ -39,8 +40,4 @@ export class CustomProvider extends PersistenceProvider {
39
40
  override writeRaw(data) {
40
41
  this.setData(data);
41
42
  }
42
-
43
- override clearRaw() {
44
- this.setData(null);
45
- }
46
43
  }
@@ -5,18 +5,20 @@
5
5
  * Copyright © 2024 Extremely Heavy Industries Inc.
6
6
  */
7
7
 
8
- import {PersistenceProvider, PersistOptions} from './';
8
+ import type {DashViewModel} from '@xh/hoist/desktop/cmp/dash'; // Import type only
9
9
  import {throwIf} from '@xh/hoist/utils/js';
10
+ import {PersistenceProvider, PersistenceProviderConfig} from './';
10
11
 
11
12
  /**
12
13
  * PersistenceProvider that stores state within a DashViewModel.
13
14
  */
14
- export class DashViewProvider extends PersistenceProvider {
15
- dashViewModel;
15
+ export class DashViewProvider<S> extends PersistenceProvider<S> {
16
+ readonly dashViewModel: DashViewModel;
16
17
 
17
- constructor({dashViewModel, ...rest}: PersistOptions) {
18
+ constructor(cfg: PersistenceProviderConfig<S>) {
19
+ super(cfg);
20
+ const {dashViewModel} = cfg.persistOptions;
18
21
  throwIf(!dashViewModel, `DashViewProvider requires a 'dashViewModel'.`);
19
- super(rest);
20
22
  this.dashViewModel = dashViewModel;
21
23
  }
22
24
 
@@ -29,10 +31,6 @@ export class DashViewProvider extends PersistenceProvider {
29
31
  }
30
32
 
31
33
  override writeRaw(data) {
32
- this.dashViewModel.setViewState(data);
33
- }
34
-
35
- override clearRaw() {
36
- return this.dashViewModel.setViewState(null);
34
+ this.dashViewModel.viewState = data;
37
35
  }
38
36
  }