@xh/hoist 76.0.0-SNAPSHOT.1757113228632 → 76.0.0-SNAPSHOT.1757274887417

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
@@ -15,6 +15,13 @@
15
15
  toolbar for clarity.
16
16
  * Added new ability to specify nested tab containers in a single declarative config. Apps may now
17
17
  provide a spec for a nested tab container directly to the `TabConfig.content` property.
18
+ * Improvements to View Management:
19
+ ** Allow users to create 'Global' views directly in 'Save/Save As' Dialog.
20
+ ** Simplify presentation/edit of view visibility to new "Visibility" control
21
+ ** Support for the 'isDefaultPinned' attribute on global views has been removed. All global
22
+ views will be pinned by default. This feature was deemed too confusing, and not useful in
23
+ practice. App maintainers should ensure that all global views are appropriate and well
24
+ organized enough to be shown immediately to new users in the view menu.
18
25
 
19
26
  ### 🐞 Bug Fixes
20
27
 
@@ -20,8 +20,6 @@ export declare class DataAccess<T> {
20
20
  createViewAsync(spec: ViewCreateSpec): Promise<View<T>>;
21
21
  /** Update all aspects of a view's metadata.*/
22
22
  updateViewInfoAsync(view: ViewInfo, updates: ViewUpdateSpec): Promise<View<T>>;
23
- /** Promote a view to global visibility/ownership status. */
24
- makeViewGlobalAsync(view: ViewInfo): Promise<View<T>>;
25
23
  /** Update a view's value. */
26
24
  updateViewValueAsync(view: View<T>, value: Partial<T>): Promise<View<T>>;
27
25
  deleteViewsAsync(views: ViewInfo[]): Promise<void>;
@@ -24,12 +24,6 @@ export declare class ViewInfo {
24
24
  readonly isGlobal: boolean;
25
25
  /** Optional group name used for bucketing this view in display. */
26
26
  readonly group: string;
27
- /**
28
- * True if this view should be pinned by default to all users' menus, where it will appear
29
- * unless the user has explicitly unpinned it. Only applicable for global views, can be enabled
30
- * by view managers to promote especially important global views and ensure users see them.
31
- */
32
- readonly isDefaultPinned: boolean;
33
27
  /**
34
28
  * Original meta-data on views associated JsonBlob.
35
29
  * Not typically used by applications.
@@ -47,8 +41,10 @@ export declare class ViewInfo {
47
41
  /**
48
42
  * True if this view should appear on the users easy access menu.
49
43
  *
50
- * This value is computed with the user persisted state along with the View's
51
- * `defaultPinned` property.
44
+ * This value is computed with the user persisted state for the view.
45
+ *
46
+ * If the user has not set pinning state for the view, global views and
47
+ * owned views are pinned by default.
52
48
  */
53
49
  get isPinned(): boolean;
54
50
  /**
@@ -8,16 +8,11 @@ export interface ViewCreateSpec {
8
8
  group: string;
9
9
  description: string;
10
10
  isShared: boolean;
11
- isPinned: boolean;
11
+ isGlobal: boolean;
12
+ isPinned?: boolean;
12
13
  value: PlainObject;
13
14
  }
14
- export interface ViewUpdateSpec {
15
- name?: string;
16
- group?: string;
17
- description?: string;
18
- isShared?: boolean;
19
- isDefaultPinned?: boolean;
20
- }
15
+ export type ViewUpdateSpec = Partial<Omit<ViewCreateSpec, 'value'>>;
21
16
  export interface ViewUserState {
22
17
  currentView?: string;
23
18
  userPinned: Record<string, boolean>;
@@ -209,11 +204,15 @@ export declare class ViewManagerModel<T = PlainObject> extends HoistModel {
209
204
  userPin(view: ViewInfo): void;
210
205
  userUnpin(view: ViewInfo): void;
211
206
  isUserPinned(view: ViewInfo): boolean | null;
212
- validateViewNameAsync(name: string, existing?: ViewInfo): Promise<string>;
207
+ /**
208
+ * Validate a name for a view.
209
+ * @param name - candidate name to validate
210
+ * @param existing - existing view that will have the name. null if the name is for a new view.
211
+ * @param isGlobal - true if the name is for a global view.
212
+ */
213
+ validateViewNameAsync(name: string, existing: ViewInfo, isGlobal: boolean): Promise<string>;
213
214
  /** Update all aspects of a view's metadata.*/
214
215
  updateViewInfoAsync(view: ViewInfo, updates: ViewUpdateSpec): Promise<View<T>>;
215
- /** Promote a view to global visibility/ownership status. */
216
- makeViewGlobalAsync(view: ViewInfo): Promise<View<T>>;
217
216
  deleteViewsAsync(toDelete: ViewInfo[]): Promise<void>;
218
217
  /**
219
218
  * Called by {@link ViewManagerProvider} to receive state changes from this model.
@@ -28,12 +28,10 @@ export declare class ManageDialogModel extends HoistModel {
28
28
  doLoadAsync(loadSpec: LoadSpec): Promise<void>;
29
29
  deleteAsync(views: ViewInfo[]): Promise<void>;
30
30
  updateAsync(view: ViewInfo, update: ViewUpdateSpec): Promise<void>;
31
- makeGlobalAsync(view: ViewInfo): Promise<void>;
32
31
  togglePinned(views: ViewInfo[]): void;
33
32
  private init;
34
33
  private doUpdateAsync;
35
34
  private doDeleteAsync;
36
- private doMakeGlobalAsync;
37
35
  private selectViewAsync;
38
36
  private createGridModel;
39
37
  private createTabContainerModel;
@@ -1,3 +1,11 @@
1
1
  import { ViewManagerModel } from '@xh/hoist/cmp/viewmanager';
2
2
  import { SelectOption } from '@xh/hoist/core';
3
- export declare function getGroupOptions(model: ViewManagerModel, type: 'owned' | 'global'): SelectOption[];
3
+ export declare function getGroupOptions(vmm: ViewManagerModel, isGlobal: boolean): SelectOption[];
4
+ /**
5
+ * Support for "Visibility" concept used in default view editing/creation.
6
+ * This tri-state selection will translate into boolean `isGlobal` and `isShared`
7
+ * flag settings.
8
+ */
9
+ export type Visibility = 'private' | 'shared' | 'global';
10
+ export declare function getVisibilityOptions(vmm: ViewManagerModel): SelectOption[];
11
+ export declare function getVisibilityInfo(vmm: ViewManagerModel, val: Visibility): string;
@@ -88,17 +88,6 @@ export class DataAccess<T> {
88
88
  }
89
89
  }
90
90
 
91
- /** Promote a view to global visibility/ownership status. */
92
- async makeViewGlobalAsync(view: ViewInfo): Promise<View<T>> {
93
- try {
94
- this.ensureEditable(view);
95
- const raw = await XH.fetchJson({url: 'xhView/makeGlobal', params: {token: view.token}});
96
- return View.fromBlob(raw, this.model);
97
- } catch (e) {
98
- throw XH.exception({message: `Unable to update ${view.typedName}`, cause: e});
99
- }
100
- }
101
-
102
91
  /** Update a view's value. */
103
92
  async updateViewValueAsync(view: View<T>, value: Partial<T>): Promise<View<T>> {
104
93
  try {
@@ -41,13 +41,6 @@ export class ViewInfo {
41
41
  /** Optional group name used for bucketing this view in display. */
42
42
  readonly group: string;
43
43
 
44
- /**
45
- * True if this view should be pinned by default to all users' menus, where it will appear
46
- * unless the user has explicitly unpinned it. Only applicable for global views, can be enabled
47
- * by view managers to promote especially important global views and ensure users see them.
48
- */
49
- readonly isDefaultPinned: boolean;
50
-
51
44
  /**
52
45
  * Original meta-data on views associated JsonBlob.
53
46
  * Not typically used by applications.
@@ -73,7 +66,6 @@ export class ViewInfo {
73
66
  this.isGlobal = !this.owner;
74
67
 
75
68
  this.group = this.meta.group ?? null;
76
- this.isDefaultPinned = !!(this.isGlobal && this.meta.isDefaultPinned);
77
69
  this.isShared = !!(!this.isGlobal && this.meta.isShared);
78
70
 
79
71
  // Round to seconds. See: https://github.com/xh/hoist-core/issues/423
@@ -98,11 +90,13 @@ export class ViewInfo {
98
90
  /**
99
91
  * True if this view should appear on the users easy access menu.
100
92
  *
101
- * This value is computed with the user persisted state along with the View's
102
- * `defaultPinned` property.
93
+ * This value is computed with the user persisted state for the view.
94
+ *
95
+ * If the user has not set pinning state for the view, global views and
96
+ * owned views are pinned by default.
103
97
  */
104
98
  get isPinned(): boolean {
105
- return this.isUserPinned ?? this.isDefaultPinned;
99
+ return this.isUserPinned ?? (this.isGlobal || this.isOwned);
106
100
  }
107
101
 
108
102
  /**
@@ -32,17 +32,12 @@ export interface ViewCreateSpec {
32
32
  group: string;
33
33
  description: string;
34
34
  isShared: boolean;
35
- isPinned: boolean;
35
+ isGlobal: boolean;
36
+ isPinned?: boolean;
36
37
  value: PlainObject;
37
38
  }
38
39
 
39
- export interface ViewUpdateSpec {
40
- name?: string;
41
- group?: string;
42
- description?: string;
43
- isShared?: boolean;
44
- isDefaultPinned?: boolean;
45
- }
40
+ export type ViewUpdateSpec = Partial<Omit<ViewCreateSpec, 'value'>>;
46
41
 
47
42
  export interface ViewUserState {
48
43
  currentView?: string;
@@ -466,14 +461,24 @@ export class ViewManagerModel<T = PlainObject> extends HoistModel {
466
461
  //-----------------
467
462
  // Management
468
463
  //-----------------
469
- async validateViewNameAsync(name: string, existing: ViewInfo = null): Promise<string> {
464
+ /**
465
+ * Validate a name for a view.
466
+ * @param name - candidate name to validate
467
+ * @param existing - existing view that will have the name. null if the name is for a new view.
468
+ * @param isGlobal - true if the name is for a global view.
469
+ */
470
+ async validateViewNameAsync(
471
+ name: string,
472
+ existing: ViewInfo,
473
+ isGlobal: boolean
474
+ ): Promise<string> {
470
475
  const maxLength = 50;
471
476
  name = name?.trim();
472
477
  if (!name) return 'Name is required';
473
478
  if (name.length > maxLength) {
474
479
  return `Name cannot be longer than ${maxLength} characters`;
475
480
  }
476
- const views = existing?.isGlobal ? this.globalViews : this.ownedViews;
481
+ const views = isGlobal ? this.globalViews : this.ownedViews;
477
482
  if (views.some(view => view.name === name && view.token != existing?.token)) {
478
483
  return `A ${this.typeDisplayName} with name '${name}' already exists.`;
479
484
  }
@@ -485,11 +490,6 @@ export class ViewManagerModel<T = PlainObject> extends HoistModel {
485
490
  return this.dataAccess.updateViewInfoAsync(view, updates);
486
491
  }
487
492
 
488
- /** Promote a view to global visibility/ownership status. */
489
- async makeViewGlobalAsync(view: ViewInfo): Promise<View<T>> {
490
- return this.dataAccess.makeViewGlobalAsync(view);
491
- }
492
-
493
493
  async deleteViewsAsync(toDelete: ViewInfo[]): Promise<void> {
494
494
  let exception;
495
495
  try {
@@ -122,10 +122,6 @@ export class ManageDialogModel extends HoistModel {
122
122
  return this.doUpdateAsync(view, update).linkTo(this.updateTask).catchDefault();
123
123
  }
124
124
 
125
- async makeGlobalAsync(view: ViewInfo) {
126
- return this.doMakeGlobalAsync(view).linkTo(this.updateTask).catchDefault();
127
- }
128
-
129
125
  @action
130
126
  togglePinned(views: ViewInfo[]) {
131
127
  const allPinned = every(views, 'isPinned'),
@@ -177,9 +173,10 @@ export class ManageDialogModel extends HoistModel {
177
173
 
178
174
  private async doUpdateAsync(view: ViewInfo, update: ViewUpdateSpec) {
179
175
  const {viewManagerModel} = this;
180
- await viewManagerModel.updateViewInfoAsync(view, update);
176
+ const updated = await viewManagerModel.updateViewInfoAsync(view, update);
181
177
  await viewManagerModel.refreshAsync();
182
178
  await this.refreshAsync();
179
+ await this.selectViewAsync(updated.info); // reselect -- may have moved tabs!
183
180
  }
184
181
 
185
182
  private async doDeleteAsync(views: ViewInfo[]) {
@@ -219,40 +216,6 @@ export class ManageDialogModel extends HoistModel {
219
216
  return viewManagerModel.deleteViewsAsync(views).finally(() => this.refreshAsync());
220
217
  }
221
218
 
222
- private async doMakeGlobalAsync(view: ViewInfo) {
223
- const {globalDisplayName, typeDisplayName, globalViews} = this.viewManagerModel,
224
- {typedName} = view;
225
-
226
- if (some(globalViews, {name: view.name})) {
227
- XH.alert({
228
- title: 'Alert',
229
- message: `There is already a ${globalDisplayName} ${typedName}. Please rename or edit it instead.`
230
- });
231
- return;
232
- }
233
-
234
- const msgs = [
235
- `The ${typedName} will become a ${globalDisplayName} ${typeDisplayName} visible to all other ${XH.appName} users.`,
236
- strong('Are you sure you want to proceed?')
237
- ];
238
- const confirmed = await XH.confirm({
239
- message: fragment(msgs.map(m => p(m))),
240
- confirmProps: {
241
- text: `Yes, change visibility`,
242
- outlined: true,
243
- autoFocus: false,
244
- intent: 'primary'
245
- }
246
- });
247
- if (!confirmed) return;
248
-
249
- const {viewManagerModel} = this;
250
- const updated = await viewManagerModel.makeViewGlobalAsync(view);
251
- await viewManagerModel.refreshAsync();
252
- await this.refreshAsync();
253
- await this.selectViewAsync(updated.info); // reselect -- will have moved tabs!
254
- }
255
-
256
219
  private async selectViewAsync(view: ViewInfo) {
257
220
  this.tabContainerModel.activateTab(
258
221
  view.isOwned ? 'owned' : view.isGlobal ? 'global' : 'shared'
@@ -362,7 +325,7 @@ export class ManageDialogModel extends HoistModel {
362
325
  Icon.globe(),
363
326
  `This tab shows ${globalViews} available to everyone.`,
364
327
  br(),
365
- `${capitalize(globalViews)} can be set to appear automatically in everyone's menu, but you can choose which ${views} you would like to see by pinning/unpinning them at any time.`
328
+ `${capitalize(globalViews)} appear by default in everyone's menu, but you can choose which ${views} you would like to see by pinning/unpinning them at any time.`
366
329
  )
367
330
  })
368
331
  });
@@ -6,17 +6,17 @@
6
6
  */
7
7
 
8
8
  import {form} from '@xh/hoist/cmp/form';
9
- import {filler, hbox, vframe} from '@xh/hoist/cmp/layout';
9
+ import {filler, vframe} from '@xh/hoist/cmp/layout';
10
10
  import {hoistCmp, uses} from '@xh/hoist/core';
11
11
  import {button} from '@xh/hoist/desktop/cmp/button';
12
12
  import {formField} from '@xh/hoist/desktop/cmp/form';
13
- import {select, switchInput, textArea, textInput} from '@xh/hoist/desktop/cmp/input';
13
+ import {select, textArea, textInput} from '@xh/hoist/desktop/cmp/input';
14
14
  import {panel} from '@xh/hoist/desktop/cmp/panel';
15
15
  import {toolbar} from '@xh/hoist/desktop/cmp/toolbar';
16
16
  import {dialog} from '@xh/hoist/kit/blueprint';
17
17
  import {startCase} from 'lodash';
18
18
  import {SaveAsDialogModel} from './SaveAsDialogModel';
19
- import {getGroupOptions} from './Utils';
19
+ import {getGroupOptions, getVisibilityOptions, getVisibilityInfo} from './Utils';
20
20
 
21
21
  /**
22
22
  * Default Save As dialog used by ViewManager.
@@ -43,6 +43,13 @@ export const saveAsDialog = hoistCmp.factory<SaveAsDialogModel>({
43
43
 
44
44
  const formPanel = hoistCmp.factory<SaveAsDialogModel>({
45
45
  render({model}) {
46
+ const {parent, formModel} = model,
47
+ {visibility} = formModel.values,
48
+ isGlobal = visibility === 'global',
49
+ groupOptions = getGroupOptions(parent, isGlobal),
50
+ visOptions = getVisibilityOptions(parent),
51
+ visInfo = getVisibilityInfo(parent, visibility);
52
+
46
53
  return panel({
47
54
  item: form({
48
55
  fieldDefaults: {
@@ -69,7 +76,7 @@ const formPanel = hoistCmp.factory<SaveAsDialogModel>({
69
76
  enableCreate: true,
70
77
  enableClear: true,
71
78
  placeholder: 'Select optional group....',
72
- options: getGroupOptions(model.parent, 'owned')
79
+ options: groupOptions
73
80
  })
74
81
  }),
75
82
  formField({
@@ -79,15 +86,12 @@ const formPanel = hoistCmp.factory<SaveAsDialogModel>({
79
86
  height: 70
80
87
  })
81
88
  }),
82
- hbox(
83
- formField({
84
- field: 'isShared',
85
- label: 'Share?',
86
- labelTextAlign: 'left',
87
- omit: !model.parent.enableSharing,
88
- item: switchInput()
89
- })
90
- )
89
+ formField({
90
+ field: 'visibility',
91
+ omit: visOptions.length === 1,
92
+ item: select({options: visOptions, enableFilter: false}),
93
+ info: visInfo
94
+ })
91
95
  ]
92
96
  })
93
97
  }),
@@ -6,9 +6,11 @@
6
6
  */
7
7
 
8
8
  import {FormModel} from '@xh/hoist/cmp/form';
9
+ import {p, strong} from '@xh/hoist/cmp/layout';
9
10
  import {HoistModel, managed, XH} from '@xh/hoist/core';
10
11
  import {makeObservable, action, observable} from '@xh/hoist/mobx';
11
12
  import {ViewManagerModel} from '@xh/hoist/cmp/viewmanager';
13
+ import {some} from 'lodash';
12
14
 
13
15
  /**
14
16
  * Backing model for ViewManagerModel's SaveAs
@@ -30,15 +32,14 @@ export class SaveAsDialogModel extends HoistModel {
30
32
  open() {
31
33
  const {parent, formModel} = this,
32
34
  src = parent.view,
33
- name = parent.ownedViews.some(it => it.name === src.name)
34
- ? `Copy of ${src.name}`
35
- : src.name;
35
+ name = some(parent.ownedViews, {name: src.name}) ? `Copy of ${src.name}` : src.name;
36
36
 
37
37
  formModel.init({
38
38
  name,
39
39
  group: src.group,
40
40
  description: src.description,
41
- isShared: false
41
+ visibility: 'private',
42
+ isPinned: !!src.info?.isPinned
42
43
  });
43
44
 
44
45
  this.isOpen = true;
@@ -52,7 +53,6 @@ export class SaveAsDialogModel extends HoistModel {
52
53
  async saveAsAsync() {
53
54
  try {
54
55
  await this.doSaveAsAsync().linkTo(this.parent.saveTask);
55
- this.close();
56
56
  } catch (e) {
57
57
  XH.handleException(e);
58
58
  }
@@ -66,29 +66,60 @@ export class SaveAsDialogModel extends HoistModel {
66
66
  fields: [
67
67
  {
68
68
  name: 'name',
69
- rules: [({value}) => this.parent.validateViewNameAsync(value)]
69
+ rules: [
70
+ ({value}, {visibility}) => {
71
+ return this.parent.validateViewNameAsync(
72
+ value,
73
+ null,
74
+ visibility === 'global'
75
+ );
76
+ }
77
+ ]
70
78
  },
71
79
  {name: 'group'},
72
80
  {name: 'description'},
73
- {name: 'isShared'}
81
+ {name: 'visibility'}
74
82
  ]
75
83
  });
76
84
  }
77
85
 
78
86
  private async doSaveAsAsync() {
79
87
  let {formModel, parent} = this,
80
- {name, group, description, isShared} = formModel.getData(),
81
- isValid = await formModel.validateAsync();
88
+ {typeDisplayName, globalDisplayName} = parent,
89
+ {name, group, description, visibility} = formModel.getData(),
90
+ isValid = await formModel.validateAsync(),
91
+ isGlobal = visibility === 'global',
92
+ isShared = visibility === 'shared';
82
93
 
83
94
  if (!isValid) return;
84
95
 
85
- return parent.saveAsAsync({
96
+ if (isGlobal) {
97
+ const message = [
98
+ p(
99
+ `This ${typeDisplayName} will become a ${globalDisplayName} ${typeDisplayName} visible to all other ${XH.appName} users.`
100
+ ),
101
+ p(strong('Are you sure you want to proceed?'))
102
+ ];
103
+ const confirmed = await XH.confirm({
104
+ message,
105
+ confirmProps: {
106
+ text: `Yes, save ${globalDisplayName} ${typeDisplayName}`,
107
+ outlined: true,
108
+ autoFocus: false,
109
+ intent: 'primary'
110
+ }
111
+ });
112
+ if (!confirmed) return;
113
+ }
114
+
115
+ await parent.saveAsAsync({
86
116
  name: name.trim(),
87
117
  group: group?.trim(),
88
118
  description: description?.trim(),
89
- isPinned: true,
119
+ isGlobal,
90
120
  isShared,
91
121
  value: parent.getValue()
92
122
  });
123
+ this.close();
93
124
  }
94
125
  }
@@ -7,12 +7,44 @@
7
7
 
8
8
  import {ViewManagerModel} from '@xh/hoist/cmp/viewmanager';
9
9
  import {SelectOption} from '@xh/hoist/core';
10
- import {map, uniq} from 'lodash';
10
+ import {pluralize} from '@xh/hoist/utils/js';
11
+ import {capitalize, map, startCase, uniq} from 'lodash';
11
12
 
12
- export function getGroupOptions(model: ViewManagerModel, type: 'owned' | 'global'): SelectOption[] {
13
- const views = type == 'owned' ? model.ownedViews : model.globalViews;
13
+ export function getGroupOptions(vmm: ViewManagerModel, isGlobal: boolean): SelectOption[] {
14
+ const views = isGlobal ? vmm.globalViews : vmm.ownedViews;
14
15
  return uniq(map(views, 'group'))
15
16
  .sort()
16
17
  .filter(g => g != null)
17
18
  .map(g => ({label: g, value: g}));
18
19
  }
20
+
21
+ /**
22
+ * Support for "Visibility" concept used in default view editing/creation.
23
+ * This tri-state selection will translate into boolean `isGlobal` and `isShared`
24
+ * flag settings.
25
+ */
26
+ export type Visibility = 'private' | 'shared' | 'global';
27
+
28
+ export function getVisibilityOptions(vmm: ViewManagerModel): SelectOption[] {
29
+ const ret = [{value: 'private', label: 'Private'}];
30
+ if (vmm.enableSharing) {
31
+ ret.push({value: 'shared', label: 'Shared'});
32
+ }
33
+ if (vmm.enableGlobal && vmm.manageGlobal) {
34
+ ret.push({value: 'global', label: startCase(vmm.globalDisplayName)});
35
+ }
36
+ return ret;
37
+ }
38
+
39
+ export function getVisibilityInfo(vmm: ViewManagerModel, val: Visibility): string {
40
+ switch (val) {
41
+ case 'private':
42
+ return 'Visible to you only.';
43
+ case 'shared':
44
+ return `Visible to all users via the "Manage ${capitalize(pluralize(vmm.typeDisplayName))}" dialog.`;
45
+ case 'global':
46
+ return `Visible to all users and automatically pinned to their menus.`;
47
+ default:
48
+ return '';
49
+ }
50
+ }
@@ -10,13 +10,16 @@ import {div, filler, hbox, hspacer, span, vbox, vframe, vspacer} from '@xh/hoist
10
10
  import {hoistCmp, uses, XH} from '@xh/hoist/core';
11
11
  import {button} from '@xh/hoist/desktop/cmp/button';
12
12
  import {formField} from '@xh/hoist/desktop/cmp/form';
13
- import {select, switchInput, textArea, textInput} from '@xh/hoist/desktop/cmp/input';
13
+ import {select, textArea, textInput} from '@xh/hoist/desktop/cmp/input';
14
14
  import {panel} from '@xh/hoist/desktop/cmp/panel';
15
15
  import {ViewPanelModel} from '@xh/hoist/desktop/cmp/viewmanager/dialog/ViewPanelModel';
16
- import {getGroupOptions} from '@xh/hoist/desktop/cmp/viewmanager/dialog/Utils';
16
+ import {
17
+ getGroupOptions,
18
+ getVisibilityInfo,
19
+ getVisibilityOptions
20
+ } from '@xh/hoist/desktop/cmp/viewmanager/dialog/Utils';
17
21
  import {fmtDateTime} from '@xh/hoist/format';
18
22
  import {Icon} from '@xh/hoist/icon';
19
- import {capitalize, some} from 'lodash';
20
23
 
21
24
  /**
22
25
  * Form to edit or view details on a single saved view within the ViewManager manage dialog.
@@ -24,11 +27,17 @@ import {capitalize, some} from 'lodash';
24
27
  export const viewPanel = hoistCmp.factory({
25
28
  model: uses(ViewPanelModel),
26
29
  render({model}) {
27
- const {view} = model;
30
+ const {view, parent, formModel} = model,
31
+ {viewManagerModel} = parent;
32
+
28
33
  if (!view) return null;
29
34
 
30
- const {isGlobal, lastUpdated, lastUpdatedBy, isEditable} = view,
31
- {enableSharing} = model.parent.viewManagerModel;
35
+ const {lastUpdated, lastUpdatedBy, isEditable} = view,
36
+ visibility = formModel.values.visibility,
37
+ isGlobal = visibility === 'global',
38
+ visOptions = getVisibilityOptions(viewManagerModel),
39
+ visInfo = getVisibilityInfo(viewManagerModel, visibility),
40
+ groupOptions = getGroupOptions(viewManagerModel, isGlobal);
32
41
 
33
42
  return panel({
34
43
  item: form({
@@ -52,10 +61,7 @@ export const viewPanel = hoistCmp.factory({
52
61
  item: select({
53
62
  enableCreate: true,
54
63
  enableClear: true,
55
- options: getGroupOptions(
56
- model.parent.viewManagerModel,
57
- view.isOwned ? 'owned' : 'global'
58
- )
64
+ options: groupOptions
59
65
  }),
60
66
  readonlyRenderer: v =>
61
67
  v || span({item: 'None provided', className: 'xh-text-color-muted'})
@@ -70,22 +76,10 @@ export const viewPanel = hoistCmp.factory({
70
76
  v || span({item: 'None provided', className: 'xh-text-color-muted'})
71
77
  }),
72
78
  formField({
73
- field: 'isShared',
74
- label: 'Shared?',
75
- inline: true,
76
- item: switchInput(),
77
- readonlyRenderer: v => (v ? 'Yes' : 'No'),
78
- omit: !enableSharing || isGlobal || !isEditable
79
- }),
80
- formField({
81
- field: 'isDefaultPinned',
82
- label: null,
83
- inline: true,
84
- item: switchInput({
85
- label: `Pin to everyone's menu by default`,
86
- labelSide: 'left'
87
- }),
88
- omit: !isGlobal || !isEditable
79
+ field: 'visibility',
80
+ omit: !isEditable || visOptions.length === 1,
81
+ item: select({options: visOptions, enableFilter: false}),
82
+ info: visInfo
89
83
  }),
90
84
  vspacer(),
91
85
  formButtons(),
@@ -130,8 +124,6 @@ const formButtons = hoistCmp.factory<ViewPanelModel>({
130
124
  });
131
125
  }
132
126
 
133
- const {enableGlobal, globalDisplayName, manageGlobal, globalViews} =
134
- parent.viewManagerModel;
135
127
  return vbox({
136
128
  style: {gap: 10, alignItems: 'center'},
137
129
  items: [
@@ -145,15 +137,6 @@ const formButtons = hoistCmp.factory<ViewPanelModel>({
145
137
  outlined: true,
146
138
  onClick: () => parent.togglePinned([view])
147
139
  }),
148
- button({
149
- text: `Promote to ${capitalize(globalDisplayName)}`,
150
- icon: Icon.globe(),
151
- width: 200,
152
- outlined: true,
153
- disabled: some(globalViews, {name: view.name}),
154
- omit: readonly || view.isGlobal || !enableGlobal || !manageGlobal,
155
- onClick: () => parent.makeGlobalAsync(view)
156
- }),
157
140
  button({
158
141
  text: 'Delete',
159
142
  icon: Icon.delete(),