@xh/hoist 71.0.0-SNAPSHOT.1734118787755 → 71.0.0-SNAPSHOT.1734552619121

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.
package/CHANGELOG.md CHANGED
@@ -9,6 +9,7 @@
9
9
  * Handle delete and update collisions more gracefully.
10
10
  * Support for `settleTime`,
11
11
  * Improved management UI Dialog.
12
+ * Support for "global" views.
12
13
  * New `SessionStorageService` and associated persistence provider provides support for saving
13
14
  tab local data across reloads.
14
15
 
@@ -2,7 +2,7 @@ import { HoistModel, LoadSpec, PersistOptions, PlainObject, TaskObserver, Thunka
2
2
  import type { ViewManagerProvider } from '@xh/hoist/core';
3
3
  import { ViewInfo } from './ViewInfo';
4
4
  import { View } from './View';
5
- import { ViewToBlobApi, ViewCreateSpec } from './ViewToBlobApi';
5
+ import { ViewCreateSpec, ViewUpdateSpec } from './ViewToBlobApi';
6
6
  export interface ViewManagerConfig {
7
7
  /**
8
8
  * True (default) to allow user to opt in to automatically saving changes to their current view.
@@ -14,6 +14,11 @@ export interface ViewManagerConfig {
14
14
  * in advance, so that there is a clear initial selection for users without any private views.
15
15
  */
16
16
  enableDefault?: boolean;
17
+ /**
18
+ * True (default) to enable "global" views - i.e. views that are not owned by a user and are
19
+ * available to all.
20
+ */
21
+ enableGlobal?: boolean;
17
22
  /**
18
23
  * True (default) to allow users to share their views with other users.
19
24
  */
@@ -101,6 +106,7 @@ export declare class ViewManagerModel<T = PlainObject> extends HoistModel {
101
106
  readonly globalDisplayName: string;
102
107
  readonly enableAutoSave: boolean;
103
108
  readonly enableDefault: boolean;
109
+ readonly enableGlobal: boolean;
104
110
  readonly enableSharing: boolean;
105
111
  readonly manageGlobal: boolean;
106
112
  readonly settleTime: number;
@@ -137,11 +143,9 @@ export declare class ViewManagerModel<T = PlainObject> extends HoistModel {
137
143
  * @internal
138
144
  */
139
145
  providers: ViewManagerProvider<any>[];
140
- /**
141
- * Data access for persisting views.
142
- * @internal
143
- */
144
- api: ViewToBlobApi<T>;
146
+ /** Data access for persisting views. */
147
+ private api;
148
+ /** Last time changes were pushed to linked persistence providers */
145
149
  private lastPushed;
146
150
  get isValueDirty(): boolean;
147
151
  get isViewSavable(): boolean;
@@ -173,6 +177,11 @@ export declare class ViewManagerModel<T = PlainObject> extends HoistModel {
173
177
  userUnpin(view: ViewInfo): void;
174
178
  isUserPinned(view: ViewInfo): boolean | null;
175
179
  validateViewNameAsync(name: string, existing?: ViewInfo): Promise<string>;
180
+ /** Update all aspects of a view's metadata.*/
181
+ updateViewInfoAsync(view: ViewInfo, updates: ViewUpdateSpec): Promise<View<T>>;
182
+ /** Promote a view to global visibility/ownership status. */
183
+ makeViewGlobalAsync(view: ViewInfo): Promise<View<T>>;
184
+ deleteViewsAsync(toDelete: ViewInfo[]): Promise<void>;
176
185
  private initAsync;
177
186
  private loadViewAsync;
178
187
  private maybeAutoSaveAsync;
@@ -35,8 +35,7 @@ export declare class ViewToBlobApi<T> {
35
35
  makeViewGlobalAsync(view: ViewInfo): Promise<View<T>>;
36
36
  /** Update a view's value. */
37
37
  updateViewValueAsync(view: View<T>, value: Partial<T>): Promise<View<T>>;
38
- /** Delete a view. */
39
- deleteViewAsync(view: ViewInfo): Promise<void>;
38
+ deleteViewsAsync(views: ViewInfo[]): Promise<void>;
40
39
  private trackChange;
41
40
  private ensureEditable;
42
41
  }
@@ -6,5 +6,6 @@ export declare class ViewManagerLocalModel extends HoistModel {
6
6
  readonly parent: ViewManagerModel;
7
7
  readonly manageDialogModel: ManageDialogModel;
8
8
  readonly saveAsDialogModel: SaveAsDialogModel;
9
+ isVisible: boolean;
9
10
  constructor(parent: ViewManagerModel);
10
11
  }
@@ -1,6 +1,7 @@
1
1
  import { GridModel } from '@xh/hoist/cmp/grid';
2
+ import { ManageDialogModel } from './ManageDialogModel';
2
3
  /**
3
4
  * Default management dialog for ViewManager.
4
5
  */
5
- export declare const manageDialog: import("@xh/hoist/core").ElementFactory<import("@xh/hoist/core").DefaultHoistProps<any>>;
6
+ export declare const manageDialog: import("@xh/hoist/core").ElementFactory<import("@xh/hoist/core").DefaultHoistProps<ManageDialogModel>>;
6
7
  export declare const viewsGrid: import("@xh/hoist/core").ElementFactory<import("@xh/hoist/core").DefaultHoistProps<GridModel>>;
@@ -21,10 +21,6 @@ export declare class ManageDialogModel extends HoistModel {
21
21
  get gridModel(): GridModel;
22
22
  get selectedView(): ViewInfo;
23
23
  get selectedViews(): ViewInfo[];
24
- get manageGlobal(): boolean;
25
- get typeDisplayName(): string;
26
- get globalDisplayName(): string;
27
- get enableSharing(): boolean;
28
24
  constructor(viewManagerModel: ViewManagerModel);
29
25
  open(): void;
30
26
  close(): void;
@@ -1 +1,2 @@
1
- export declare const viewMultiPanel: import("@xh/hoist/core").ElementFactory<import("@xh/hoist/core").DefaultHoistProps<any>>;
1
+ import { ManageDialogModel } from './ManageDialogModel';
2
+ export declare const viewMultiPanel: import("@xh/hoist/core").ElementFactory<import("@xh/hoist/core").DefaultHoistProps<ManageDialogModel>>;
@@ -21,14 +21,15 @@ import {
21
21
  import type {ViewManagerProvider} from '@xh/hoist/core';
22
22
  import {genDisplayName} from '@xh/hoist/data';
23
23
  import {fmtDateTime} from '@xh/hoist/format';
24
- import {action, bindable, makeObservable, observable, runInAction, when} from '@xh/hoist/mobx';
24
+ import {action, bindable, makeObservable, observable, when} from '@xh/hoist/mobx';
25
25
  import {olderThan, SECONDS} from '@xh/hoist/utils/datetime';
26
26
  import {executeIfFunction, pluralize, throwIf} from '@xh/hoist/utils/js';
27
27
  import {find, isEmpty, isEqual, isNil, isObject, lowerCase, pickBy} from 'lodash';
28
+ import {runInAction} from 'mobx';
28
29
  import {ReactNode} from 'react';
29
30
  import {ViewInfo} from './ViewInfo';
30
31
  import {View} from './View';
31
- import {ViewToBlobApi, ViewCreateSpec} from './ViewToBlobApi';
32
+ import {ViewToBlobApi, ViewCreateSpec, ViewUpdateSpec} from './ViewToBlobApi';
32
33
 
33
34
  export interface ViewManagerConfig {
34
35
  /**
@@ -43,6 +44,12 @@ export interface ViewManagerConfig {
43
44
  */
44
45
  enableDefault?: boolean;
45
46
 
47
+ /**
48
+ * True (default) to enable "global" views - i.e. views that are not owned by a user and are
49
+ * available to all.
50
+ */
51
+ enableGlobal?: boolean;
52
+
46
53
  /**
47
54
  * True (default) to allow users to share their views with other users.
48
55
  */
@@ -145,6 +152,7 @@ export class ViewManagerModel<T = PlainObject> extends HoistModel {
145
152
  readonly globalDisplayName: string;
146
153
  readonly enableAutoSave: boolean;
147
154
  readonly enableDefault: boolean;
155
+ readonly enableGlobal: boolean;
148
156
  readonly enableSharing: boolean;
149
157
  readonly manageGlobal: boolean;
150
158
  readonly settleTime: number;
@@ -193,13 +201,10 @@ export class ViewManagerModel<T = PlainObject> extends HoistModel {
193
201
  */
194
202
  providers: ViewManagerProvider<any>[] = [];
195
203
 
196
- /**
197
- * Data access for persisting views.
198
- * @internal
199
- */
200
- api: ViewToBlobApi<T>;
204
+ /** Data access for persisting views. */
205
+ private api: ViewToBlobApi<T>;
201
206
 
202
- // Last time changes were pushed to linked persistence providers
207
+ /** Last time changes were pushed to linked persistence providers */
203
208
  private lastPushed: number = null;
204
209
 
205
210
  //---------------
@@ -216,13 +221,7 @@ export class ViewManagerModel<T = PlainObject> extends HoistModel {
216
221
 
217
222
  get isViewAutoSavable(): boolean {
218
223
  const {enableAutoSave, autoSave, view} = this;
219
- return (
220
- enableAutoSave &&
221
- autoSave &&
222
- !view.isShared &&
223
- !view.isDefault &&
224
- !XH.identityService.isImpersonating
225
- );
224
+ return enableAutoSave && autoSave && view.isOwned && !XH.identityService.isImpersonating;
226
225
  }
227
226
 
228
227
  get autoSaveUnavailableReason(): string {
@@ -272,6 +271,7 @@ export class ViewManagerModel<T = PlainObject> extends HoistModel {
272
271
  manageGlobal = false,
273
272
  enableAutoSave = true,
274
273
  enableDefault = true,
274
+ enableGlobal = true,
275
275
  enableSharing = true,
276
276
  settleTime = 1000,
277
277
  initialViewSpec = null
@@ -290,6 +290,7 @@ export class ViewManagerModel<T = PlainObject> extends HoistModel {
290
290
  this.persistWith = persistWith;
291
291
  this.manageGlobal = executeIfFunction(manageGlobal) ?? false;
292
292
  this.enableDefault = enableDefault;
293
+ this.enableGlobal = enableGlobal;
293
294
  this.enableSharing = enableSharing;
294
295
  this.enableAutoSave = enableAutoSave;
295
296
  this.settleTime = settleTime;
@@ -438,6 +439,34 @@ export class ViewManagerModel<T = PlainObject> extends HoistModel {
438
439
  return null;
439
440
  }
440
441
 
442
+ /** Update all aspects of a view's metadata.*/
443
+ async updateViewInfoAsync(view: ViewInfo, updates: ViewUpdateSpec): Promise<View<T>> {
444
+ return this.api.updateViewInfoAsync(view, updates);
445
+ }
446
+
447
+ /** Promote a view to global visibility/ownership status. */
448
+ async makeViewGlobalAsync(view: ViewInfo): Promise<View<T>> {
449
+ return this.api.makeViewGlobalAsync(view);
450
+ }
451
+
452
+ async deleteViewsAsync(toDelete: ViewInfo[]): Promise<void> {
453
+ let exception;
454
+ try {
455
+ await this.api.deleteViewsAsync(toDelete);
456
+ } catch (e) {
457
+ exception = e;
458
+ }
459
+
460
+ await this.refreshAsync();
461
+ const {views} = this;
462
+
463
+ if (toDelete.some(view => view.isCurrentView) && !views.some(view => view.isCurrentView)) {
464
+ await this.loadViewAsync(this.initialViewSpec?.(views));
465
+ }
466
+
467
+ if (exception) throw exception;
468
+ }
469
+
441
470
  //------------------
442
471
  // Implementation
443
472
  //------------------
@@ -7,7 +7,7 @@
7
7
 
8
8
  import {PlainObject, XH} from '@xh/hoist/core';
9
9
  import {pluralize, throwIf} from '@xh/hoist/utils/js';
10
- import {omit, pick} from 'lodash';
10
+ import {isEmpty, omit, pick} from 'lodash';
11
11
  import {ViewInfo} from './ViewInfo';
12
12
  import {View} from './View';
13
13
  import {ViewManagerModel} from './ViewManagerModel';
@@ -50,7 +50,13 @@ export class ViewToBlobApi<T> {
50
50
  type: model.type,
51
51
  includeValue: false
52
52
  });
53
- return blobs.map(b => new ViewInfo(b, model));
53
+ return blobs
54
+ .map(b => new ViewInfo(b, model))
55
+ .filter(
56
+ view =>
57
+ (model.enableGlobal || !view.isGlobal) &&
58
+ (model.enableSharing || !view.isShared)
59
+ );
54
60
  } catch (e) {
55
61
  throw XH.exception({
56
62
  message: `Unable to fetch ${pluralize(model.typeDisplayName)}`,
@@ -151,21 +157,31 @@ export class ViewToBlobApi<T> {
151
157
  }
152
158
  }
153
159
 
154
- /** Delete a view. */
155
- async deleteViewAsync(view: ViewInfo) {
156
- try {
157
- this.ensureEditable(view);
158
- await XH.jsonBlobService.archiveAsync(view.token);
159
- this.trackChange('Deleted View', view);
160
- } catch (e) {
161
- throw XH.exception({message: `Unable to delete ${view.typedName}`, cause: e});
160
+ async deleteViewsAsync(views: ViewInfo[]) {
161
+ views.forEach(v => this.ensureEditable(v));
162
+ const results = await Promise.allSettled(
163
+ views.map(v => XH.jsonBlobService.archiveAsync(v.token))
164
+ ),
165
+ outcome = results.map((result, idx) => ({result, view: views[idx]})),
166
+ failed = outcome.filter(({result}) => result.status === 'rejected') as Array<{
167
+ result: PromiseRejectedResult;
168
+ view: ViewInfo;
169
+ }>;
170
+
171
+ this.trackChange(`Deleted ${pluralize('View', views.length - failed.length, true)}`);
172
+
173
+ if (!isEmpty(failed)) {
174
+ throw XH.exception({
175
+ message: `Failed to delete ${pluralize(this.model.typeDisplayName, failed.length, true)}: ${failed.map(({view}) => view.name).join(', ')}`,
176
+ cause: failed.map(({result}) => result.reason)
177
+ });
162
178
  }
163
179
  }
164
180
 
165
181
  //------------------
166
182
  // Implementation
167
183
  //------------------
168
- private trackChange(message: string, v: View | ViewInfo) {
184
+ private trackChange(message: string, v?: View | ViewInfo) {
169
185
  XH.track({
170
186
  message,
171
187
  category: 'Views',
@@ -12,6 +12,7 @@ import {ViewManagerModel} from '@xh/hoist/cmp/viewmanager';
12
12
  import {button, ButtonProps} from '@xh/hoist/desktop/cmp/button';
13
13
  import {Icon} from '@xh/hoist/icon';
14
14
  import {popover} from '@xh/hoist/kit/blueprint';
15
+ import {useOnVisibleChange} from '@xh/hoist/utils/react';
15
16
  import {startCase} from 'lodash';
16
17
  import {viewMenu} from './ViewMenu';
17
18
  import {ViewManagerLocalModel} from './ViewManagerLocalModel';
@@ -67,6 +68,7 @@ export const [ViewManager, viewManager] = hoistCmp.withFactory<ViewManagerProps>
67
68
  save = saveButton({model: locModel, mode: showSaveButton, ...saveButtonProps}),
68
69
  revert = revertButton({model: locModel, mode: showRevertButton, ...revertButtonProps}),
69
70
  menu = popover({
71
+ disabled: !locModel.isVisible, // Prevent orphaned popover menu
70
72
  item: menuButton({model: locModel, ...menuButtonProps}),
71
73
  content: viewMenu({model: locModel}),
72
74
  placement: 'bottom-start',
@@ -75,7 +77,8 @@ export const [ViewManager, viewManager] = hoistCmp.withFactory<ViewManagerProps>
75
77
  return fragment(
76
78
  hbox({
77
79
  className,
78
- items: buttonSide == 'left' ? [revert, save, menu] : [menu, save, revert]
80
+ items: buttonSide == 'left' ? [revert, save, menu] : [menu, save, revert],
81
+ ref: useOnVisibleChange(isVisible => (locModel.isVisible = isVisible))
79
82
  }),
80
83
  manageDialog({model: locModel.manageDialogModel}),
81
84
  saveAsDialog({model: locModel.saveAsDialogModel})
@@ -6,6 +6,7 @@
6
6
  */
7
7
 
8
8
  import {HoistModel, managed} from '@xh/hoist/core';
9
+ import {bindable, makeObservable} from '@xh/hoist/mobx';
9
10
  import {ManageDialogModel} from './dialog/ManageDialogModel';
10
11
  import {SaveAsDialogModel} from './dialog/SaveAsDialogModel';
11
12
  import {ViewManagerModel} from '@xh/hoist/cmp/viewmanager';
@@ -19,8 +20,12 @@ export class ViewManagerLocalModel extends HoistModel {
19
20
  @managed
20
21
  readonly saveAsDialogModel: SaveAsDialogModel;
21
22
 
23
+ @bindable
24
+ isVisible = true;
25
+
22
26
  constructor(parent: ViewManagerModel) {
23
27
  super();
28
+ makeObservable(this);
24
29
  this.parent = parent;
25
30
  this.manageDialogModel = new ManageDialogModel(parent);
26
31
  this.saveAsDialogModel = new SaveAsDialogModel(parent);
@@ -24,7 +24,7 @@ import {viewPanel} from './ViewPanel';
24
24
  /**
25
25
  * Default management dialog for ViewManager.
26
26
  */
27
- export const manageDialog = hoistCmp.factory({
27
+ export const manageDialog = hoistCmp.factory<ManageDialogModel>({
28
28
  displayName: 'ManageDialog',
29
29
  className: 'xh-view-manager__manage-dialog',
30
30
  model: uses(() => ManageDialogModel),
@@ -32,11 +32,11 @@ export const manageDialog = hoistCmp.factory({
32
32
  render({model, className}) {
33
33
  if (!model.isOpen) return null;
34
34
 
35
- const {typeDisplayName, updateTask, loadTask, selectedViews} = model,
35
+ const {updateTask, loadTask, selectedViews, viewManagerModel} = model,
36
36
  count = selectedViews.length;
37
37
 
38
38
  return dialog({
39
- title: `Manage ${capitalize(pluralize(typeDisplayName))}`,
39
+ title: `Manage ${capitalize(pluralize(viewManagerModel.typeDisplayName))}`,
40
40
  icon: Icon.gear(),
41
41
  className,
42
42
  isOpen: true,
@@ -103,7 +103,7 @@ export const viewsGrid = hoistCmp.factory<GridModel>({
103
103
 
104
104
  const placeholderPanel = hoistCmp.factory<ManageDialogModel>({
105
105
  render({model}) {
106
- return placeholder(Icon.gears(), `Select a ${model.typeDisplayName}`);
106
+ return placeholder(Icon.gears(), `Select a ${model.viewManagerModel.typeDisplayName}`);
107
107
  }
108
108
  });
109
109
 
@@ -17,7 +17,7 @@ import {viewsGrid} from '@xh/hoist/desktop/cmp/viewmanager/dialog/ManageDialog';
17
17
  import {Icon} from '@xh/hoist/icon';
18
18
  import {action, bindable, computed, makeObservable, observable, runInAction} from '@xh/hoist/mobx';
19
19
  import {pluralize} from '@xh/hoist/utils/js';
20
- import {capitalize, isEmpty, some, startCase} from 'lodash';
20
+ import {capitalize, compact, isEmpty, some, startCase} from 'lodash';
21
21
  import {ReactNode} from 'react';
22
22
  import {ViewPanelModel} from './ViewPanelModel';
23
23
 
@@ -67,22 +67,6 @@ export class ManageDialogModel extends HoistModel {
67
67
  return this.gridModel.selectedRecords.map(it => it.data.view) as ViewInfo[];
68
68
  }
69
69
 
70
- get manageGlobal(): boolean {
71
- return this.viewManagerModel.manageGlobal;
72
- }
73
-
74
- get typeDisplayName(): string {
75
- return this.viewManagerModel.typeDisplayName;
76
- }
77
-
78
- get globalDisplayName(): string {
79
- return this.viewManagerModel.globalDisplayName;
80
- }
81
-
82
- get enableSharing(): boolean {
83
- return this.viewManagerModel.enableSharing;
84
- }
85
-
86
70
  constructor(viewManagerModel: ViewManagerModel) {
87
71
  super();
88
72
  makeObservable(this);
@@ -108,15 +92,22 @@ export class ManageDialogModel extends HoistModel {
108
92
 
109
93
  override async doLoadAsync(loadSpec: LoadSpec) {
110
94
  const {tabContainerModel} = this,
111
- {view, ownedViews, globalViews, sharedViews} = this.viewManagerModel;
95
+ {enableGlobal, enableSharing, view, ownedViews, globalViews, sharedViews} =
96
+ this.viewManagerModel;
112
97
 
113
98
  runInAction(() => {
114
99
  this.ownedGridModel.loadData(ownedViews);
115
- this.globalGridModel.loadData(globalViews);
116
- this.sharedGridModel.loadData(sharedViews);
117
100
  tabContainerModel.setTabTitle('owned', this.ownedTabTitle);
118
- tabContainerModel.setTabTitle('global', this.globalTabTitle);
119
- tabContainerModel.setTabTitle('shared', this.sharedTabTitle);
101
+
102
+ if (enableGlobal) {
103
+ this.globalGridModel.loadData(globalViews);
104
+ tabContainerModel.setTabTitle('global', this.globalTabTitle);
105
+ }
106
+
107
+ if (enableSharing) {
108
+ this.sharedGridModel.loadData(sharedViews);
109
+ tabContainerModel.setTabTitle('shared', this.sharedTabTitle);
110
+ }
120
111
  });
121
112
  if (!loadSpec.isRefresh && !view.isDefault) {
122
113
  await this.selectViewAsync(view.info);
@@ -145,38 +136,51 @@ export class ManageDialogModel extends HoistModel {
145
136
  // Implementation
146
137
  //------------------------
147
138
  private init() {
139
+ const {enableGlobal, enableSharing} = this.viewManagerModel;
140
+
148
141
  this.ownedGridModel = this.createGridModel('owned');
149
- this.globalGridModel = this.createGridModel('global');
150
- this.sharedGridModel = this.createGridModel('shared');
142
+ if (enableGlobal) this.globalGridModel = this.createGridModel('global');
143
+ if (enableSharing) this.sharedGridModel = this.createGridModel('shared');
144
+ const gridModels = compact([
145
+ this.ownedGridModel,
146
+ this.globalGridModel,
147
+ this.sharedGridModel
148
+ ]);
149
+
151
150
  this.tabContainerModel = this.createTabContainerModel();
152
151
  this.viewPanelModel = new ViewPanelModel(this);
153
- const gridModels = [this.ownedGridModel, this.globalGridModel, this.sharedGridModel];
152
+
154
153
  this.addReaction({
155
154
  track: () => this.filter,
156
155
  run: f => gridModels.forEach(m => m.store.setFilter(f)),
157
156
  fireImmediately: true
158
157
  });
159
- gridModels.forEach(gm => {
160
- this.addReaction({
161
- track: () => gm.hasSelection,
162
- run: hasSelection => {
163
- gridModels.forEach(it => {
164
- if (it != gm && hasSelection) it.clearSelection();
165
- });
166
- }
158
+
159
+ // Only allow one selection at a time across all grids
160
+ if (gridModels.length > 1) {
161
+ gridModels.forEach(gm => {
162
+ this.addReaction({
163
+ track: () => gm.hasSelection,
164
+ run: hasSelection => {
165
+ gridModels.forEach(it => {
166
+ if (it != gm && hasSelection) it.clearSelection();
167
+ });
168
+ }
169
+ });
167
170
  });
168
- });
171
+ }
169
172
  }
170
173
 
171
174
  private async doUpdateAsync(view: ViewInfo, update: ViewUpdateSpec) {
172
175
  const {viewManagerModel} = this;
173
- await viewManagerModel.api.updateViewInfoAsync(view, update);
176
+ await viewManagerModel.updateViewInfoAsync(view, update);
174
177
  await viewManagerModel.refreshAsync();
175
178
  await this.refreshAsync();
176
179
  }
177
180
 
178
181
  private async doDeleteAsync(views: ViewInfo[]) {
179
- const {viewManagerModel, typeDisplayName} = this,
182
+ const {viewManagerModel} = this,
183
+ {typeDisplayName} = viewManagerModel,
180
184
  count = views.length;
181
185
 
182
186
  if (!count) return;
@@ -208,12 +212,7 @@ export class ManageDialogModel extends HoistModel {
208
212
  });
209
213
  if (!confirmed) return;
210
214
 
211
- for (const view of views) {
212
- await viewManagerModel.api.deleteViewAsync(view);
213
- }
214
-
215
- await viewManagerModel.refreshAsync();
216
- await this.refreshAsync();
215
+ return viewManagerModel.deleteViewsAsync(views).finally(() => this.refreshAsync());
217
216
  }
218
217
 
219
218
  private async doMakeGlobalAsync(view: ViewInfo) {
@@ -236,7 +235,7 @@ export class ManageDialogModel extends HoistModel {
236
235
  if (!confirmed) return;
237
236
 
238
237
  const {viewManagerModel} = this;
239
- const updated = await viewManagerModel.api.makeViewGlobalAsync(view);
238
+ const updated = await viewManagerModel.makeViewGlobalAsync(view);
240
239
  await viewManagerModel.refreshAsync();
241
240
  await this.refreshAsync();
242
241
  await this.selectViewAsync(updated.info); // reselect -- will have moved tabs!
@@ -250,7 +249,7 @@ export class ManageDialogModel extends HoistModel {
250
249
  }
251
250
 
252
251
  private createGridModel(type: 'owned' | 'global' | 'shared'): GridModel {
253
- const {typeDisplayName, globalDisplayName} = this;
252
+ const {typeDisplayName, globalDisplayName} = this.viewManagerModel;
254
253
 
255
254
  const modifier =
256
255
  type == 'owned' ? `personal` : type == 'global' ? globalDisplayName : 'shared';
@@ -320,13 +319,12 @@ export class ManageDialogModel extends HoistModel {
320
319
  }
321
320
 
322
321
  private createTabContainerModel(): TabContainerModel {
323
- const view = this.typeDisplayName,
322
+ const {enableGlobal, enableSharing, globalDisplayName, typeDisplayName} =
323
+ this.viewManagerModel,
324
+ view = typeDisplayName,
324
325
  views = pluralize(view),
325
- globalViews = `${this.globalDisplayName} ${views}`,
326
- {enableSharing} = this.viewManagerModel;
327
-
328
- return new TabContainerModel({
329
- tabs: [
326
+ globalViews = `${globalDisplayName} ${views}`,
327
+ tabs = [
330
328
  {
331
329
  id: 'owned',
332
330
  title: this.ownedTabTitle,
@@ -340,50 +338,58 @@ export class ManageDialogModel extends HoistModel {
340
338
  : ''
341
339
  )
342
340
  })
343
- },
344
- {
345
- id: 'global',
346
- title: this.globalTabTitle,
347
- content: viewsGrid({
348
- model: this.globalGridModel,
349
- helpText: fragment(
350
- Icon.globe(),
351
- `This tab shows ${globalViews} available to everyone. ${capitalize(globalViews)} can be pinned by default so they appear automatically in everyone's menu, but you can choose which ${views} you would like to see by pinning/unpinning them at any time.`
352
- )
353
- })
354
- },
355
- {
356
- id: 'shared',
357
- title: this.sharedTabTitle,
358
- content: viewsGrid({
359
- model: this.sharedGridModel,
360
- helpText: fragment(
361
- Icon.users(),
362
- `This tab shows ${views} shared by other ${XH.appName} users. You can pin these ${views} to add them to your menu and access them directly. Only the owner will be able to save changes to a shared ${view}, but you can save as a copy to make it your own.`
363
- )
364
- })
365
341
  }
366
- ]
367
- });
342
+ ];
343
+
344
+ if (enableGlobal) {
345
+ tabs.push({
346
+ id: 'global',
347
+ title: this.globalTabTitle,
348
+ content: viewsGrid({
349
+ model: this.globalGridModel,
350
+ helpText: fragment(
351
+ Icon.globe(),
352
+ `This tab shows ${globalViews} available to everyone. ${capitalize(globalViews)} can be pinned by default so they appear automatically in everyone's menu, but you can choose which ${views} you would like to see by pinning/unpinning them at any time.`
353
+ )
354
+ })
355
+ });
356
+ }
357
+
358
+ if (enableSharing) {
359
+ tabs.push({
360
+ id: 'shared',
361
+ title: this.sharedTabTitle,
362
+ content: viewsGrid({
363
+ model: this.sharedGridModel,
364
+ helpText: fragment(
365
+ Icon.users(),
366
+ `This tab shows ${views} shared by other ${XH.appName} users. You can pin these ${views} to add them to your menu and access them directly. Only the owner will be able to save changes to a shared ${view}, but you can save as a copy to make it your own.`
367
+ )
368
+ })
369
+ });
370
+ }
371
+
372
+ return new TabContainerModel({tabs});
368
373
  }
369
374
 
370
375
  private get ownedTabTitle(): ReactNode {
371
376
  return hbox(
372
- `My ${startCase(pluralize(this.typeDisplayName))}`,
377
+ `My ${startCase(pluralize(this.viewManagerModel.typeDisplayName))}`,
373
378
  badge(this.ownedGridModel.store.allCount)
374
379
  );
375
380
  }
376
381
 
377
382
  private get globalTabTitle(): ReactNode {
383
+ const {globalDisplayName, typeDisplayName} = this.viewManagerModel;
378
384
  return hbox(
379
- `${startCase(this.globalDisplayName)} ${startCase(pluralize(this.typeDisplayName))}`,
385
+ `${startCase(globalDisplayName)} ${startCase(pluralize(typeDisplayName))}`,
380
386
  badge(this.globalGridModel.store.allCount)
381
387
  );
382
388
  }
383
389
 
384
390
  private get sharedTabTitle(): ReactNode {
385
391
  return hbox(
386
- `Shared ${startCase(pluralize(this.typeDisplayName))}`,
392
+ `Shared ${startCase(pluralize(this.viewManagerModel.typeDisplayName))}`,
387
393
  badge(this.sharedGridModel.store.allCount)
388
394
  );
389
395
  }
@@ -14,7 +14,7 @@ import {pluralize} from '@xh/hoist/utils/js';
14
14
  import {every, isEmpty, some} from 'lodash';
15
15
  import {ManageDialogModel} from './ManageDialogModel';
16
16
 
17
- export const viewMultiPanel = hoistCmp.factory({
17
+ export const viewMultiPanel = hoistCmp.factory<ManageDialogModel>({
18
18
  model: uses(() => ManageDialogModel),
19
19
  render({model}) {
20
20
  const views = model.selectedViews;
@@ -25,7 +25,7 @@ export const viewMultiPanel = hoistCmp.factory({
25
25
  className: 'xh-view-manager__manage-dialog__form',
26
26
  item: placeholder(
27
27
  Icon.gears(),
28
- `${views.length} selected ${pluralize(model.typeDisplayName)}`,
28
+ `${views.length} selected ${pluralize(model.viewManagerModel.typeDisplayName)}`,
29
29
  vspacer(),
30
30
  buttons()
31
31
  )