@xh/hoist 71.0.0-SNAPSHOT.1736268096149 → 71.0.0-SNAPSHOT.1736268305089

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.
@@ -15,7 +15,7 @@ export declare class DataAccess<T> {
15
15
  state: ViewUserState;
16
16
  }>;
17
17
  /** Fetch the latest version of a view. */
18
- fetchViewAsync(token: string): Promise<View<T>>;
18
+ fetchViewAsync(info: ViewInfo): Promise<View<T>>;
19
19
  /** Create a new view, owned by the current user.*/
20
20
  createViewAsync(spec: ViewCreateSpec): Promise<View<T>>;
21
21
  /** Update all aspects of a view's metadata.*/
@@ -183,7 +183,7 @@ export declare class ViewManagerModel<T = PlainObject> extends HoistModel {
183
183
  */
184
184
  private constructor();
185
185
  doLoadAsync(loadSpec: LoadSpec): Promise<void>;
186
- selectViewAsync(view: string | ViewInfo, opts?: {
186
+ selectViewAsync(info: ViewInfo, opts?: {
187
187
  alertUnsavedChanges: boolean;
188
188
  }): Promise<void>;
189
189
  saveAsAsync(spec: ViewCreateSpec): Promise<void>;
@@ -7,7 +7,5 @@ export declare class ViewManagerLocalModel extends HoistModel {
7
7
  readonly manageDialogModel: ManageDialogModel;
8
8
  readonly saveAsDialogModel: SaveAsDialogModel;
9
9
  isVisible: boolean;
10
- saveAsync(): Promise<void>;
11
- revertAsync(): Promise<void>;
12
10
  constructor(parent: ViewManagerModel);
13
11
  }
@@ -48,14 +48,14 @@ export class DataAccess<T> {
48
48
  }
49
49
 
50
50
  /** Fetch the latest version of a view. */
51
- async fetchViewAsync(token: string): Promise<View<T>> {
51
+ async fetchViewAsync(info: ViewInfo): Promise<View<T>> {
52
52
  const {model} = this;
53
- if (!token) return View.createDefault(model);
53
+ if (!info) return View.createDefault(model);
54
54
  try {
55
- const raw = await XH.fetchJson({url: 'xhView/get', params: {token}});
55
+ const raw = await XH.fetchJson({url: 'xhView/get', params: {token: info.token}});
56
56
  return View.fromBlob(raw, model);
57
57
  } catch (e) {
58
- throw XH.exception({message: `Unable to fetch view with token ${token}`, cause: e});
58
+ throw XH.exception({message: `Unable to fetch ${info.typedName}`, cause: e});
59
59
  }
60
60
  }
61
61
 
@@ -5,7 +5,6 @@
5
5
  * Copyright © 2025 Extremely Heavy Industries Inc.
6
6
  */
7
7
  import {SECONDS} from '@xh/hoist/utils/datetime';
8
- import {throwIf} from '@xh/hoist/utils/js';
9
8
  import {ViewManagerModel} from './ViewManagerModel';
10
9
  import {JsonBlob} from '@xh/hoist/svc';
11
10
  import {PlainObject, XH} from '@xh/hoist/core';
@@ -58,8 +57,6 @@ export class ViewInfo {
58
57
  readonly model: ViewManagerModel;
59
58
 
60
59
  constructor(blob: JsonBlob, model: ViewManagerModel) {
61
- throwIf(blob.type !== model.type, 'View type does not match ViewManager type.');
62
-
63
60
  this.token = blob.token;
64
61
  this.type = blob.type;
65
62
  this.name = blob.name;
@@ -21,7 +21,7 @@ import {fmtDateTime} from '@xh/hoist/format';
21
21
  import {action, bindable, makeObservable, observable, comparer, runInAction} from '@xh/hoist/mobx';
22
22
  import {olderThan, SECONDS} from '@xh/hoist/utils/datetime';
23
23
  import {executeIfFunction, pluralize, throwIf} from '@xh/hoist/utils/js';
24
- import {find, isEqual, isNil, isNull, isObject, isUndefined, lowerCase, uniqBy} from 'lodash';
24
+ import {find, isEqual, isNil, isNull, isUndefined, lowerCase, uniqBy} from 'lodash';
25
25
  import {ReactNode} from 'react';
26
26
  import {ViewInfo} from './ViewInfo';
27
27
  import {View} from './View';
@@ -337,7 +337,7 @@ export class ViewManagerModel<T = PlainObject> extends HoistModel {
337
337
  if (!view.isDefault) {
338
338
  const latestInfo = find(views, {token: view.token});
339
339
  if (latestInfo && latestInfo.lastUpdated > view.lastUpdated) {
340
- this.loadViewAsync(view.token, this.pendingValue);
340
+ this.loadViewAsync(latestInfo, this.pendingValue);
341
341
  }
342
342
  }
343
343
  } catch (e) {
@@ -346,12 +346,7 @@ export class ViewManagerModel<T = PlainObject> extends HoistModel {
346
346
  }
347
347
  }
348
348
 
349
- async selectViewAsync(
350
- view: string | ViewInfo,
351
- opts = {alertUnsavedChanges: true}
352
- ): Promise<void> {
353
- const token = isObject(view) ? view.token : view;
354
-
349
+ async selectViewAsync(info: ViewInfo, opts = {alertUnsavedChanges: true}): Promise<void> {
355
350
  // ensure any pending auto-save gets completed
356
351
  if (this.isValueDirty && this.isViewAutoSavable) {
357
352
  await this.maybeAutoSaveAsync();
@@ -367,7 +362,7 @@ export class ViewManagerModel<T = PlainObject> extends HoistModel {
367
362
  return;
368
363
  }
369
364
 
370
- return this.loadViewAsync(token);
365
+ await this.loadViewAsync(info).catch(e => this.handleException(e));
371
366
  }
372
367
 
373
368
  async saveAsAsync(spec: ViewCreateSpec): Promise<void> {
@@ -385,22 +380,26 @@ export class ViewManagerModel<T = PlainObject> extends HoistModel {
385
380
  return;
386
381
  }
387
382
  const {pendingValue, view, dataAccess} = this;
383
+ try {
384
+ if (!(await this.maybeConfirmSaveAsync(view, pendingValue))) {
385
+ return;
386
+ }
387
+ const updated = await dataAccess
388
+ .updateViewValueAsync(view, pendingValue.value)
389
+ .linkTo(this.saveTask);
388
390
 
389
- if (!(await this.maybeConfirmSaveAsync(view, pendingValue))) {
390
- return;
391
+ this.setAsView(updated);
392
+ this.noteSuccess(`Saved ${view.typedName}`);
393
+ } catch (e) {
394
+ this.handleException(e, {
395
+ message: `Failed to save ${view.typedName}. If this persists consider \`Save As...\`.`
396
+ });
391
397
  }
392
- const updated = await dataAccess
393
- .updateViewValueAsync(view, pendingValue.value)
394
- .linkTo(this.saveTask);
395
-
396
- this.setAsView(updated);
397
- this.noteSuccess(`Saved ${view.typedName}`);
398
-
399
398
  this.refreshAsync();
400
399
  }
401
400
 
402
401
  async resetAsync(): Promise<void> {
403
- return this.loadViewAsync(this.view.token);
402
+ await this.loadViewAsync(this.view.info).catch(e => this.handleException(e));
404
403
  }
405
404
 
406
405
  //--------------------------------
@@ -484,7 +483,7 @@ export class ViewManagerModel<T = PlainObject> extends HoistModel {
484
483
  const {views} = this;
485
484
 
486
485
  if (toDelete.some(view => view.isCurrentView) && !views.some(view => view.isCurrentView)) {
487
- await this.loadViewAsync(this.initialViewSpec?.(views)?.token);
486
+ await this.loadViewAsync(this.initialViewSpec?.(views));
488
487
  }
489
488
 
490
489
  if (exception) throw exception;
@@ -509,8 +508,8 @@ export class ViewManagerModel<T = PlainObject> extends HoistModel {
509
508
  });
510
509
 
511
510
  // 2) Initialize/choose initial view. Null is ok, and will yield default.
512
- let initialView: ViewInfo,
513
- initialTkn: string = initialState.currentView;
511
+ let initialView,
512
+ initialTkn = initialState.currentView;
514
513
  if (isUndefined(initialTkn)) {
515
514
  initialView = this.initialViewSpec?.(views);
516
515
  } else if (!isNull(initialTkn)) {
@@ -519,7 +518,7 @@ export class ViewManagerModel<T = PlainObject> extends HoistModel {
519
518
  initialView = null;
520
519
  }
521
520
 
522
- await this.loadViewAsync(initialView?.token, this.pendingValue);
521
+ await this.loadViewAsync(initialView, this.pendingValue);
523
522
  } catch (e) {
524
523
  // Always ensure at least default view is installed (other state defaults are fine)
525
524
  this.loadViewAsync(null, this.pendingValue);
@@ -570,13 +569,13 @@ export class ViewManagerModel<T = PlainObject> extends HoistModel {
570
569
  }
571
570
 
572
571
  private async loadViewAsync(
573
- token: string,
572
+ info: ViewInfo,
574
573
  pendingValue: PendingValue<T> = null
575
574
  ): Promise<void> {
576
575
  return this.dataAccess
577
- .fetchViewAsync(token)
576
+ .fetchViewAsync(info)
578
577
  .thenAction(latest => {
579
- this.setAsView(latest, pendingValue?.token == token ? pendingValue : null);
578
+ this.setAsView(latest, pendingValue?.token == info?.token ? pendingValue : null);
580
579
  this.providers.forEach(it => it.pushStateToTarget());
581
580
  this.lastPushed = Date.now();
582
581
  })
@@ -650,7 +649,7 @@ export class ViewManagerModel<T = PlainObject> extends HoistModel {
650
649
 
651
650
  private async maybeConfirmSaveAsync(view: View, pendingValue: PendingValue<T>) {
652
651
  // Get latest from server for reference
653
- const latest = await this.dataAccess.fetchViewAsync(view.token),
652
+ const latest = await this.dataAccess.fetchViewAsync(view.info),
654
653
  isGlobal = latest.isGlobal,
655
654
  isStale = latest.lastUpdated > pendingValue.baseUpdated;
656
655
  if (!isStale && !isGlobal) return true;
@@ -118,7 +118,7 @@ const menuButton = hoistCmp.factory<ViewManagerLocalModel>({
118
118
  const saveButton = hoistCmp.factory<ViewManagerLocalModel>({
119
119
  render({model, mode, ...rest}) {
120
120
  if (hideStateButton(model, mode)) return null;
121
- const {parent} = model,
121
+ const {parent, saveAsDialogModel} = model,
122
122
  {typeDisplayName, isLoading, isValueDirty} = parent;
123
123
  return button({
124
124
  className: 'xh-view-manager__save-button',
@@ -126,7 +126,9 @@ const saveButton = hoistCmp.factory<ViewManagerLocalModel>({
126
126
  tooltip: `Save changes to this ${typeDisplayName}`,
127
127
  intent: 'primary',
128
128
  disabled: !isValueDirty || isLoading,
129
- onClick: () => model.saveAsync(),
129
+ onClick: () => {
130
+ parent.isViewSavable ? parent.saveAsync() : saveAsDialogModel.open();
131
+ },
130
132
  ...rest
131
133
  });
132
134
  }
@@ -142,7 +144,7 @@ const revertButton = hoistCmp.factory<ViewManagerLocalModel>({
142
144
  tooltip: `Revert changes to this ${typeDisplayName}`,
143
145
  intent: 'danger',
144
146
  disabled: !isValueDirty || isLoading,
145
- onClick: () => model.revertAsync(),
147
+ onClick: () => model.parent.resetAsync(),
146
148
  ...rest
147
149
  });
148
150
  }
@@ -23,24 +23,6 @@ export class ViewManagerLocalModel extends HoistModel {
23
23
  @bindable
24
24
  isVisible = true;
25
25
 
26
- async saveAsync() {
27
- const {parent} = this,
28
- {view} = parent;
29
-
30
- if (!parent.isViewSavable) {
31
- this.saveAsDialogModel.open();
32
- return;
33
- }
34
-
35
- return parent.saveAsync().catchDefault({
36
- message: `Failed to save ${view.typedName}. If this persists consider \`Save As...\`.`
37
- });
38
- }
39
-
40
- async revertAsync() {
41
- return this.parent.resetAsync().catchDefault();
42
- }
43
-
44
26
  constructor(parent: ViewManagerModel) {
45
27
  super();
46
28
  makeObservable(this);
@@ -63,7 +63,7 @@ function getNavMenuItems(model: ViewManagerModel): ReactNode[] {
63
63
  className: 'xh-view-manager__menu-item',
64
64
  icon: view.isDefault ? Icon.check() : Icon.placeholder(),
65
65
  text: `Default ${startCase(typeDisplayName)}`,
66
- onClick: () => model.selectViewAsync(null).catchDefault()
66
+ onClick: () => model.selectViewAsync(null)
67
67
  })
68
68
  );
69
69
  }
@@ -89,7 +89,7 @@ function getOtherMenuItems(model: ViewManagerLocalModel): ReactNode[] {
89
89
  icon: Icon.save(),
90
90
  text: 'Save',
91
91
  disabled: !isViewSavable || !isValueDirty,
92
- onClick: () => model.saveAsync()
92
+ onClick: () => parent.saveAsync()
93
93
  }),
94
94
  menuItem({
95
95
  icon: Icon.placeholder(),
@@ -100,7 +100,7 @@ function getOtherMenuItems(model: ViewManagerLocalModel): ReactNode[] {
100
100
  icon: Icon.reset(),
101
101
  text: `Revert`,
102
102
  disabled: !isValueDirty,
103
- onClick: () => model.revertAsync()
103
+ onClick: () => parent.resetAsync()
104
104
  }),
105
105
  menuDivider({omit: !enableAutoSave}),
106
106
  menuItem({
@@ -168,6 +168,6 @@ function viewMenuItem(view: ViewInfo, model: ViewManagerModel): ReactNode {
168
168
  text: view.name,
169
169
  title: title.join(' | '),
170
170
  icon,
171
- onClick: () => model.selectViewAsync(view).catchDefault()
171
+ onClick: () => model.selectViewAsync(view)
172
172
  });
173
173
  }
@@ -86,7 +86,7 @@ export class ManageDialogModel extends HoistModel {
86
86
  }
87
87
 
88
88
  activateSelectedViewAndClose() {
89
- this.viewManagerModel.selectViewAsync(this.selectedView).catchDefault();
89
+ this.viewManagerModel.selectViewAsync(this.selectedView);
90
90
  this.close();
91
91
  }
92
92
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xh/hoist",
3
- "version": "71.0.0-SNAPSHOT.1736268096149",
3
+ "version": "71.0.0-SNAPSHOT.1736268305089",
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",