@xh/hoist 70.0.0-SNAPSHOT.1731083521069 → 70.0.0-SNAPSHOT.1731623470295

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 (95) hide show
  1. package/CHANGELOG.md +57 -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/promise/Promise.d.ts +6 -5
  42. package/build/types/svc/GridAutosizeService.d.ts +2 -5
  43. package/build/types/svc/JsonBlobService.d.ts +45 -24
  44. package/cmp/filter/FilterChooserModel.ts +142 -125
  45. package/cmp/grid/Grid.ts +2 -10
  46. package/cmp/grid/GridModel.ts +18 -31
  47. package/cmp/grid/Types.ts +7 -21
  48. package/cmp/grid/columns/Column.ts +0 -1
  49. package/cmp/grid/impl/InitPersist.ts +71 -0
  50. package/cmp/grouping/GroupingChooserModel.ts +48 -57
  51. package/cmp/tab/TabContainerModel.ts +22 -36
  52. package/cmp/zoneGrid/Types.ts +6 -6
  53. package/cmp/zoneGrid/ZoneGridModel.ts +2 -7
  54. package/cmp/zoneGrid/impl/InitPersist.ts +70 -0
  55. package/core/HoistBase.ts +14 -22
  56. package/core/HoistBaseDecorators.ts +26 -28
  57. package/core/persist/CustomProvider.ts +7 -10
  58. package/core/persist/DashViewProvider.ts +8 -10
  59. package/core/persist/LocalStorageProvider.ts +9 -12
  60. package/core/persist/PersistOptions.ts +6 -4
  61. package/core/persist/Persistable.ts +23 -0
  62. package/core/persist/PersistenceProvider.ts +159 -79
  63. package/core/persist/PrefProvider.ts +9 -12
  64. package/core/persist/index.ts +2 -0
  65. package/core/persist/viewmanager/Types.ts +51 -0
  66. package/core/persist/viewmanager/ViewManagerModel.ts +515 -0
  67. package/core/persist/viewmanager/ViewManagerProvider.ts +51 -0
  68. package/core/persist/viewmanager/impl/ManageDialogModel.ts +274 -0
  69. package/core/persist/viewmanager/impl/SaveDialogModel.ts +112 -0
  70. package/core/persist/viewmanager/index.ts +2 -0
  71. package/desktop/cmp/button/ColAutosizeButton.ts +1 -1
  72. package/desktop/cmp/dash/DashConfig.ts +3 -1
  73. package/desktop/cmp/dash/DashModel.ts +1 -2
  74. package/desktop/cmp/dash/DashViewSpec.ts +1 -1
  75. package/desktop/cmp/dash/canvas/DashCanvasModel.ts +31 -30
  76. package/desktop/cmp/dash/container/DashContainerModel.ts +68 -43
  77. package/desktop/cmp/dash/container/impl/DashContainerUtils.ts +13 -4
  78. package/desktop/cmp/leftrightchooser/LeftRightChooserFilter.ts +1 -1
  79. package/desktop/cmp/panel/PanelModel.ts +33 -53
  80. package/desktop/cmp/store/impl/StoreFilterField.ts +1 -1
  81. package/desktop/cmp/viewmanager/ViewManager.scss +58 -0
  82. package/desktop/cmp/viewmanager/ViewManager.ts +274 -0
  83. package/desktop/cmp/viewmanager/cmp/ManageDialog.ts +197 -0
  84. package/desktop/cmp/viewmanager/cmp/SaveDialog.ts +89 -0
  85. package/desktop/cmp/viewmanager/index.ts +3 -0
  86. package/mobile/cmp/button/ColAutosizeButton.ts +1 -1
  87. package/package.json +1 -1
  88. package/promise/Promise.ts +6 -7
  89. package/svc/GridAutosizeService.ts +73 -36
  90. package/svc/JsonBlobService.ts +64 -31
  91. package/tsconfig.tsbuildinfo +1 -1
  92. package/build/types/cmp/grid/impl/GridPersistenceModel.d.ts +0 -41
  93. package/build/types/cmp/zoneGrid/impl/ZoneGridPersistenceModel.d.ts +0 -39
  94. package/cmp/grid/impl/GridPersistenceModel.ts +0 -174
  95. package/cmp/zoneGrid/impl/ZoneGridPersistenceModel.ts +0 -149
@@ -0,0 +1,274 @@
1
+ import {FormModel} from '@xh/hoist/cmp/form';
2
+ import {GridAutosizeMode, GridModel} from '@xh/hoist/cmp/grid';
3
+ import {fragment, p} from '@xh/hoist/cmp/layout';
4
+ import {HoistModel, lookup, managed, TaskObserver, XH} from '@xh/hoist/core';
5
+ import {lengthIs, required} from '@xh/hoist/data';
6
+ import {Icon} from '@xh/hoist/icon';
7
+ import {makeObservable} from '@xh/hoist/mobx';
8
+ import {pluralize, throwIf} from '@xh/hoist/utils/js';
9
+ import {ViewManagerModel} from '../ViewManagerModel';
10
+
11
+ export class ManageDialogModel extends HoistModel {
12
+ @lookup(() => ViewManagerModel)
13
+ private viewManagerModel: ViewManagerModel;
14
+
15
+ @managed gridModel: GridModel;
16
+ @managed formModel: FormModel;
17
+
18
+ readonly saveTask = TaskObserver.trackLast();
19
+ readonly deleteTask = TaskObserver.trackLast();
20
+
21
+ get selectedId(): string {
22
+ return this.gridModel.selectedId as string;
23
+ }
24
+
25
+ get selectedIds(): string[] {
26
+ return this.gridModel.selectedIds as string[];
27
+ }
28
+
29
+ get hasMultiSelection(): boolean {
30
+ return this.selectedIds.length > 1;
31
+ }
32
+
33
+ get selIsShared(): boolean {
34
+ return this.gridModel.selectedRecords.some(rec => rec.data.isShared);
35
+ }
36
+
37
+ get canDelete(): boolean {
38
+ const {viewManagerModel, selIsShared, enableSharing, selectedIds} = this,
39
+ {views, enableDefault} = viewManagerModel;
40
+
41
+ // Can't delete shared views without manager role.
42
+ if (selIsShared && !enableSharing) return false;
43
+
44
+ // Can't delete all the views, unless default mode is enabled.
45
+ return enableDefault || views.length - selectedIds.length > 0;
46
+ }
47
+
48
+ get canEdit(): boolean {
49
+ return this.enableSharing || !this.selIsShared;
50
+ }
51
+
52
+ get enableSharing(): boolean {
53
+ return this.viewManagerModel.enableSharing;
54
+ }
55
+
56
+ get showSaveButton(): boolean {
57
+ const {formModel, viewManagerModel} = this;
58
+ return formModel.isDirty && !formModel.readonly && !viewManagerModel.loadModel.isPending;
59
+ }
60
+
61
+ get displayName(): string {
62
+ return this.viewManagerModel.displayName;
63
+ }
64
+
65
+ get enableFavorites(): boolean {
66
+ return this.viewManagerModel.enableFavorites;
67
+ }
68
+
69
+ constructor() {
70
+ super();
71
+ makeObservable(this);
72
+ }
73
+
74
+ override onLinked() {
75
+ super.onLinked();
76
+
77
+ this.gridModel = this.createGridModel();
78
+ this.formModel = this.createFormModel();
79
+
80
+ this.addReaction(
81
+ {
82
+ track: () => this.viewManagerModel.views,
83
+ run: () => this.refreshAsync()
84
+ },
85
+ {
86
+ track: () => this.gridModel.selectedRecord,
87
+ run: record => {
88
+ if (record) {
89
+ this.formModel.readonly = !this.canEdit;
90
+ this.formModel.init(record.data);
91
+ }
92
+ }
93
+ }
94
+ );
95
+ }
96
+
97
+ override async doLoadAsync() {
98
+ this.gridModel.loadData(this.viewManagerModel.views);
99
+ await this.ensureGridHasSelection();
100
+ }
101
+
102
+ async saveAsync() {
103
+ return this.doSaveAsync().linkTo(this.saveTask).catchDefault();
104
+ }
105
+
106
+ async deleteAsync() {
107
+ return this.doDeleteAsync().linkTo(this.deleteTask).catchDefault();
108
+ }
109
+
110
+ //------------------------
111
+ // Implementation
112
+ //------------------------
113
+ private async doSaveAsync() {
114
+ const {formModel, viewManagerModel, enableSharing, selectedId, gridModel, displayName} =
115
+ this,
116
+ {isDirty} = formModel,
117
+ {name, description, isShared} = formModel.getData(),
118
+ isValid = await formModel.validateAsync(),
119
+ {token, owner} = gridModel.selectedRecord.data,
120
+ isOwnView = owner === XH.getUsername();
121
+
122
+ if (!isValid || !selectedId || !isDirty) return;
123
+
124
+ // Additional sanity-check before POSTing an update - non-admins should never be modifying global views.
125
+ throwIf(
126
+ isShared && !enableSharing,
127
+ `Cannot save changes to shared ${displayName} - missing required permission.`
128
+ );
129
+
130
+ if (formModel.getField('isShared').isDirty) {
131
+ const confirmMsgs = [];
132
+ if (isShared) {
133
+ confirmMsgs.push(
134
+ `This ${displayName} will become visible to all other ${XH.appName} users.`
135
+ );
136
+ } else if (isOwnView) {
137
+ confirmMsgs.push(
138
+ `The selected ${displayName} will revert to being private to you. It will no longer be available to other users.`
139
+ );
140
+ } else {
141
+ confirmMsgs.push(
142
+ `The selected ${displayName} will revert to being private to its owner (${owner}).`,
143
+ `Note that you will no longer have access to this view, meaning you will not be able to undo this change.`
144
+ );
145
+ }
146
+
147
+ confirmMsgs.push('Are you sure you want to proceed?');
148
+
149
+ const confirmed = await XH.confirm({
150
+ message: fragment(confirmMsgs.map(msg => p(msg))),
151
+ confirmProps: {
152
+ text: 'Yes, update visibility',
153
+ outlined: true,
154
+ autoFocus: false,
155
+ intent: 'primary'
156
+ }
157
+ });
158
+
159
+ if (!confirmed) return;
160
+ }
161
+
162
+ await XH.jsonBlobService.updateAsync(token, {
163
+ name,
164
+ description,
165
+ acl: isShared ? '*' : null
166
+ });
167
+
168
+ await viewManagerModel.refreshAsync();
169
+ }
170
+
171
+ private async doDeleteAsync() {
172
+ const {viewManagerModel, gridModel, displayName, selectedIds, hasMultiSelection} = this,
173
+ count = selectedIds.length;
174
+ if (!count) return;
175
+
176
+ const confirmStr = hasMultiSelection
177
+ ? pluralize(displayName, count, true)
178
+ : `"${gridModel.selectedRecord.data.name}"`,
179
+ confirmed = await XH.confirm({
180
+ message: `Are you sure you want to delete ${confirmStr}?`,
181
+ confirmProps: {
182
+ text: `Yes, delete ${pluralize(displayName, count)}`,
183
+ outlined: true,
184
+ autoFocus: false,
185
+ intent: 'danger'
186
+ }
187
+ });
188
+ if (!confirmed) return;
189
+
190
+ for (const token of selectedIds) {
191
+ viewManagerModel.removeFavorite(token);
192
+ await XH.jsonBlobService.archiveAsync(token);
193
+ }
194
+
195
+ await viewManagerModel.refreshAsync();
196
+ }
197
+
198
+ private async ensureGridHasSelection() {
199
+ const {gridModel, viewManagerModel} = this,
200
+ {selectedToken} = viewManagerModel;
201
+
202
+ if (!gridModel.hasSelection) {
203
+ selectedToken
204
+ ? await gridModel.selectAsync(selectedToken)
205
+ : await gridModel.preSelectFirstAsync();
206
+ }
207
+ }
208
+
209
+ private createGridModel(): GridModel {
210
+ return new GridModel({
211
+ emptyText: `No saved ${pluralize(this.displayName)} found...`,
212
+ sortBy: 'name',
213
+ groupBy: 'group',
214
+ hideHeaders: true,
215
+ showGroupRowCounts: false,
216
+ selModel: 'multiple',
217
+ store: {
218
+ idSpec: 'token',
219
+ fields: [
220
+ {name: 'token', type: 'string'},
221
+ {name: 'name', type: 'string'},
222
+ {name: 'description', type: 'string'},
223
+ {name: 'isShared', type: 'bool'},
224
+ {name: 'isFavorite', type: 'bool'},
225
+ {name: 'acl', type: 'json'},
226
+ {name: 'meta', type: 'json'},
227
+ {name: 'dateCreated', type: 'date'},
228
+ {name: 'createdBy', type: 'string'},
229
+ {name: 'owner', type: 'string'},
230
+ {name: 'lastUpdatedBy', type: 'string'},
231
+ {name: 'lastUpdated', type: 'date'}
232
+ ]
233
+ },
234
+ autosizeOptions: {mode: GridAutosizeMode.DISABLED},
235
+ columns: [
236
+ {field: 'name', flex: true},
237
+ {
238
+ field: 'isFavorite',
239
+ omit: !this.enableFavorites,
240
+ width: 40,
241
+ align: 'center',
242
+ headerName: Icon.favorite(),
243
+ highlightOnChange: true,
244
+ renderer: v => {
245
+ return Icon.favorite({
246
+ prefix: v ? 'fas' : 'fal',
247
+ className: v ? 'xh-yellow' : 'xh-text-color-muted'
248
+ });
249
+ }
250
+ },
251
+ {field: 'group', hidden: true}
252
+ ],
253
+ onCellClicked: ({colDef, data: record}) => {
254
+ if (colDef.colId === 'isFavorite') {
255
+ this.viewManagerModel.toggleFavorite(record.id);
256
+ }
257
+ }
258
+ });
259
+ }
260
+
261
+ private createFormModel(): FormModel {
262
+ return new FormModel({
263
+ fields: [
264
+ {name: 'name', rules: [required, lengthIs({max: 255})]},
265
+ {name: 'description'},
266
+ {name: 'isShared', displayName: 'Shared'},
267
+ {name: 'owner', readonly: true},
268
+ {name: 'dateCreated', displayName: 'Created', readonly: true},
269
+ {name: 'lastUpdated', displayName: 'Updated', readonly: true},
270
+ {name: 'lastUpdatedBy', displayName: 'Updated By', readonly: true}
271
+ ]
272
+ });
273
+ }
274
+ }
@@ -0,0 +1,112 @@
1
+ import {FormModel} from '@xh/hoist/cmp/form';
2
+ import {HoistModel, managed, TaskObserver, XH} from '@xh/hoist/core';
3
+ import {ViewManagerModel} from '@xh/hoist/core/persist/viewmanager';
4
+ import {lengthIs, required} from '@xh/hoist/data';
5
+ import {makeObservable} from '@xh/hoist/mobx';
6
+ import {JsonBlob} from '@xh/hoist/svc';
7
+ import {action, observable} from 'mobx';
8
+ import {View} from '../Types';
9
+
10
+ export class SaveDialogModel extends HoistModel {
11
+ private readonly viewManagerModel: ViewManagerModel;
12
+
13
+ @managed readonly formModel: FormModel;
14
+ readonly saveTask = TaskObserver.trackLast();
15
+
16
+ @observable viewStub: Partial<View>;
17
+ @observable isOpen: boolean = false;
18
+
19
+ private resolveOpen: (value: JsonBlob) => void;
20
+ private invalidNames: string[] = [];
21
+
22
+ get type(): string {
23
+ return this.viewManagerModel.viewType;
24
+ }
25
+
26
+ get DisplayName(): string {
27
+ return this.viewManagerModel.DisplayName;
28
+ }
29
+
30
+ constructor(viewManagerModel: ViewManagerModel) {
31
+ super();
32
+ makeObservable(this);
33
+ this.viewManagerModel = viewManagerModel;
34
+ this.formModel = this.createFormModel();
35
+ }
36
+
37
+ @action
38
+ openAsync(viewStub: Partial<View>, invalidNames: string[]): Promise<JsonBlob> {
39
+ this.viewStub = viewStub;
40
+ this.invalidNames = invalidNames;
41
+
42
+ this.formModel.init({
43
+ name: viewStub.name ? `${viewStub.name} (COPY)` : '',
44
+ description: viewStub.description
45
+ });
46
+
47
+ this.isOpen = true;
48
+
49
+ return new Promise(resolve => {
50
+ this.resolveOpen = resolve;
51
+ });
52
+ }
53
+
54
+ cancel() {
55
+ this.close();
56
+ this.resolveOpen(null);
57
+ }
58
+
59
+ async saveAsAsync() {
60
+ return this.doSaveAsAsync().linkTo(this.saveTask);
61
+ }
62
+
63
+ //------------------------
64
+ // Implementation
65
+ //------------------------
66
+ private createFormModel(): FormModel {
67
+ return new FormModel({
68
+ fields: [
69
+ {
70
+ name: 'name',
71
+ rules: [
72
+ required,
73
+ lengthIs({max: 255}),
74
+ ({value}) => {
75
+ if (this.invalidNames.includes(value)) {
76
+ return `An entry with name "${value}" already exists`;
77
+ }
78
+ }
79
+ ]
80
+ },
81
+ {name: 'description'}
82
+ ]
83
+ });
84
+ }
85
+
86
+ private async doSaveAsAsync() {
87
+ const {formModel, viewStub, type} = this,
88
+ {name, description} = formModel.getData(),
89
+ isValid = await formModel.validateAsync();
90
+
91
+ if (!isValid) return;
92
+
93
+ try {
94
+ const newObj = await XH.jsonBlobService.createAsync({
95
+ type,
96
+ name,
97
+ description,
98
+ value: viewStub.value
99
+ });
100
+ this.close();
101
+ this.resolveOpen(newObj);
102
+ } catch (e) {
103
+ XH.handleException(e);
104
+ }
105
+ }
106
+
107
+ @action
108
+ private close() {
109
+ this.isOpen = false;
110
+ this.formModel.init();
111
+ }
112
+ }
@@ -0,0 +1,2 @@
1
+ export * from './ViewManagerModel';
2
+ export * from './Types';
@@ -16,7 +16,7 @@ export interface ColAutosizeButtonProps extends ButtonProps {
16
16
  gridModel?: GridModel;
17
17
 
18
18
  /** Options for the grid autosize. */
19
- autosizeOptions?: GridAutosizeOptions;
19
+ autosizeOptions?: Omit<GridAutosizeOptions, 'mode'>;
20
20
  }
21
21
 
22
22
  /**
@@ -6,11 +6,13 @@
6
6
  */
7
7
 
8
8
  import {MenuItemLike, PersistOptions} from '@xh/hoist/core';
9
+ import {DashViewState} from '@xh/hoist/desktop/cmp/dash/DashViewModel';
10
+ import {DashViewSpec} from '@xh/hoist/desktop/cmp/dash/DashViewSpec';
9
11
 
10
12
  /**
11
13
  * Base interface for {@link DashCanvasConfig} and {@link DashContainerConfig}.
12
14
  */
13
- export interface DashConfig<VSPEC, VSTATE> {
15
+ export interface DashConfig<VSPEC extends DashViewSpec, VSTATE extends DashViewState> {
14
16
  /**
15
17
  * A collection of viewSpecs, each describing a type of view that can be displayed in this
16
18
  * container.
@@ -5,7 +5,7 @@
5
5
  * Copyright © 2024 Extremely Heavy Industries Inc.
6
6
  */
7
7
  import {bindable, makeObservable, observable} from '@xh/hoist/mobx';
8
- import {HoistModel, managed, PersistenceProvider, RefreshContextModel} from '@xh/hoist/core';
8
+ import {HoistModel, managed, RefreshContextModel} from '@xh/hoist/core';
9
9
 
10
10
  /**
11
11
  * Base Model for {@link DashCanvasModel} and {@link DashContainerModel}.
@@ -40,7 +40,6 @@ export abstract class DashModel<VSPEC, VSTATE, VMODEL> extends HoistModel {
40
40
  // Implementation properties
41
41
  //------------------------
42
42
  protected restoreState: any;
43
- protected provider: PersistenceProvider;
44
43
 
45
44
  constructor() {
46
45
  super();
@@ -21,7 +21,7 @@ export interface DashViewSpec {
21
21
  content: Content;
22
22
 
23
23
  /** Title text added to the tab header. */
24
- title: string;
24
+ title?: string;
25
25
 
26
26
  /** An icon placed at the left-side of the tab header. */
27
27
  icon?: ReactElement;
@@ -4,7 +4,7 @@
4
4
  *
5
5
  * Copyright © 2024 Extremely Heavy Industries Inc.
6
6
  */
7
- import {PersistenceProvider, XH} from '@xh/hoist/core';
7
+ import {Persistable, PersistableState, PersistenceProvider, XH} from '@xh/hoist/core';
8
8
  import {required} from '@xh/hoist/data';
9
9
  import {DashCanvasViewModel, DashCanvasViewSpec, DashConfig, DashViewState, DashModel} from '../';
10
10
  import '@xh/hoist/desktop/register';
@@ -64,11 +64,10 @@ export interface DashCanvasItemLayout {
64
64
  * Model for {@link DashCanvas}, managing all configurable options for the component and publishing
65
65
  * the observable state of its current widgets and their layout.
66
66
  */
67
- export class DashCanvasModel extends DashModel<
68
- DashCanvasViewSpec,
69
- DashCanvasItemState,
70
- DashCanvasViewModel
71
- > {
67
+ export class DashCanvasModel
68
+ extends DashModel<DashCanvasViewSpec, DashCanvasItemState, DashCanvasViewModel>
69
+ implements Persistable<{state: DashCanvasItemState[]}>
70
+ {
72
71
  //-----------------------------
73
72
  // Settable State
74
73
  //------------------------------
@@ -182,26 +181,23 @@ export class DashCanvasModel extends DashModel<
182
181
  this.addViewButtonText = addViewButtonText;
183
182
  this.extraMenuItems = extraMenuItems;
184
183
 
185
- // Read state from provider -- fail gently
186
- let persistState = null;
184
+ this.loadState(initialState);
185
+ this.state = this.buildState();
186
+
187
187
  if (persistWith) {
188
- try {
189
- this.provider = PersistenceProvider.create({path: 'dashCanvas', ...persistWith});
190
- persistState = this.provider.read();
191
- } catch (e) {
192
- this.logError(e);
193
- XH.safeDestroy(this.provider);
194
- this.provider = null;
195
- }
188
+ PersistenceProvider.create({
189
+ persistOptions: {
190
+ path: 'dashCanvas',
191
+ ...persistWith
192
+ },
193
+ target: this
194
+ });
196
195
  }
197
196
 
198
- this.loadState(persistState?.state ?? initialState);
199
- this.state = this.buildState();
200
-
201
197
  this.addReaction(
202
198
  {
203
199
  track: () => this.viewState,
204
- run: () => this.publishState()
200
+ run: () => (this.state = this.buildState())
205
201
  },
206
202
  {
207
203
  when: () => !!this.ref.current,
@@ -238,7 +234,6 @@ export class DashCanvasModel extends DashModel<
238
234
  this.columns = restoreState.columns;
239
235
  this.rowHeight = restoreState.rowHeight;
240
236
  this.loadState(restoreState.initialState);
241
- this.provider?.clear();
242
237
  }
243
238
 
244
239
  /**
@@ -318,6 +313,18 @@ export class DashCanvasModel extends DashModel<
318
313
  this.getView(id)?.ensureVisible();
319
314
  }
320
315
 
316
+ //------------------------
317
+ // Persistable Interface
318
+ //------------------------
319
+ getPersistableState(): PersistableState<{state: DashCanvasItemState[]}> {
320
+ return new PersistableState({state: this.state});
321
+ }
322
+
323
+ setPersistableState(persistableState: PersistableState<{state: DashCanvasItemState[]}>) {
324
+ const {state} = persistableState.value;
325
+ if (state) this.loadState(state);
326
+ }
327
+
321
328
  //------------------------
322
329
  // Implementation
323
330
  //------------------------
@@ -396,7 +403,7 @@ export class DashCanvasModel extends DashModel<
396
403
  if (!layoutChanged) return;
397
404
 
398
405
  this.layout = layout;
399
- if (!this.isLoadingState) this.publishState();
406
+ if (!this.isLoadingState) this.state = this.buildState();
400
407
 
401
408
  // Check if scrollbar visibility has changed, and force resize event if so
402
409
  const node = this.ref.current;
@@ -409,7 +416,7 @@ export class DashCanvasModel extends DashModel<
409
416
  }
410
417
 
411
418
  @action
412
- private loadState(state) {
419
+ private loadState(state: DashCanvasItemState[]) {
413
420
  this.isLoadingState = true;
414
421
  try {
415
422
  this.clear();
@@ -427,13 +434,7 @@ export class DashCanvasModel extends DashModel<
427
434
  }
428
435
  }
429
436
 
430
- @action
431
- private publishState() {
432
- this.state = this.buildState();
433
- this.provider?.write({state: this.state});
434
- }
435
-
436
- private buildState() {
437
+ private buildState(): DashCanvasItemState[] {
437
438
  const {viewState} = this;
438
439
 
439
440
  return this.layout.map(it => {