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

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 (41) hide show
  1. package/build/types/cmp/viewmanager/View.d.ts +5 -0
  2. package/build/types/cmp/viewmanager/ViewInfo.d.ts +32 -7
  3. package/build/types/cmp/viewmanager/ViewManagerModel.d.ts +34 -31
  4. package/build/types/cmp/viewmanager/ViewToBlobApi.d.ts +28 -6
  5. package/build/types/cmp/viewmanager/index.d.ts +1 -1
  6. package/build/types/desktop/cmp/viewmanager/ViewManager.d.ts +0 -4
  7. package/build/types/desktop/cmp/viewmanager/ViewManagerLocalModel.d.ts +10 -0
  8. package/build/types/desktop/cmp/viewmanager/ViewMenu.d.ts +2 -2
  9. package/build/types/desktop/cmp/viewmanager/dialog/ManageDialog.d.ts +4 -3
  10. package/build/types/desktop/cmp/viewmanager/dialog/ManageDialogModel.d.ts +19 -10
  11. package/build/types/desktop/cmp/viewmanager/dialog/SaveAsDialog.d.ts +1 -1
  12. package/build/types/{cmp/viewmanager → desktop/cmp/viewmanager/dialog}/SaveAsDialogModel.d.ts +3 -9
  13. package/build/types/desktop/cmp/viewmanager/dialog/Utils.d.ts +3 -0
  14. package/build/types/desktop/cmp/viewmanager/dialog/ViewMultiPanel.d.ts +1 -0
  15. package/build/types/desktop/cmp/viewmanager/dialog/ViewPanel.d.ts +5 -0
  16. package/build/types/desktop/cmp/viewmanager/dialog/{EditFormModel.d.ts → ViewPanelModel.d.ts} +2 -4
  17. package/build/types/svc/JsonBlobService.d.ts +1 -1
  18. package/cmp/viewmanager/View.ts +21 -1
  19. package/cmp/viewmanager/ViewInfo.ts +58 -11
  20. package/cmp/viewmanager/ViewManagerModel.ts +86 -81
  21. package/cmp/viewmanager/ViewToBlobApi.ts +91 -35
  22. package/cmp/viewmanager/index.ts +1 -1
  23. package/desktop/cmp/viewmanager/ViewManager.scss +25 -28
  24. package/desktop/cmp/viewmanager/ViewManager.ts +28 -26
  25. package/desktop/cmp/viewmanager/ViewManagerLocalModel.ts +28 -0
  26. package/desktop/cmp/viewmanager/ViewMenu.ts +162 -169
  27. package/desktop/cmp/viewmanager/dialog/ManageDialog.ts +67 -40
  28. package/desktop/cmp/viewmanager/dialog/ManageDialogModel.ts +238 -127
  29. package/desktop/cmp/viewmanager/dialog/SaveAsDialog.ts +30 -9
  30. package/{cmp/viewmanager → desktop/cmp/viewmanager/dialog}/SaveAsDialogModel.ts +35 -40
  31. package/desktop/cmp/viewmanager/dialog/Utils.ts +18 -0
  32. package/desktop/cmp/viewmanager/dialog/ViewMultiPanel.ts +70 -0
  33. package/desktop/cmp/viewmanager/dialog/ViewPanel.ts +161 -0
  34. package/desktop/cmp/viewmanager/dialog/ViewPanelModel.ts +116 -0
  35. package/package.json +1 -1
  36. package/svc/JsonBlobService.ts +3 -3
  37. package/svc/storage/BaseStorageService.ts +1 -1
  38. package/tsconfig.tsbuildinfo +1 -1
  39. package/build/types/desktop/cmp/viewmanager/dialog/EditForm.d.ts +0 -5
  40. package/desktop/cmp/viewmanager/dialog/EditForm.ts +0 -126
  41. package/desktop/cmp/viewmanager/dialog/EditFormModel.ts +0 -125
@@ -7,13 +7,14 @@
7
7
 
8
8
  import {box, fragment, hbox} from '@xh/hoist/cmp/layout';
9
9
  import {spinner} from '@xh/hoist/cmp/spinner';
10
- import {hoistCmp, HoistProps, uses} from '@xh/hoist/core';
10
+ import {hoistCmp, HoistProps, useLocalModel, uses} from '@xh/hoist/core';
11
11
  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
15
  import {startCase} from 'lodash';
16
16
  import {viewMenu} from './ViewMenu';
17
+ import {ViewManagerLocalModel} from './ViewManagerLocalModel';
17
18
  import {manageDialog} from './dialog/ManageDialog';
18
19
  import {saveAsDialog} from './dialog/SaveAsDialog';
19
20
 
@@ -39,10 +40,6 @@ export interface ViewManagerProps extends HoistProps<ViewManagerModel> {
39
40
  showRevertButton?: ViewManagerStateButtonMode;
40
41
  /** Side the save and revert buttons should appear on (default 'right') */
41
42
  buttonSide?: 'left' | 'right';
42
- /** True to render private views in sub-menu (Default false) */
43
- showPrivateViewsInSubMenu?: boolean;
44
- /** True to render global views in sub-menu (Default false) */
45
- showGlobalViewsInSubMenu?: boolean;
46
43
  }
47
44
 
48
45
  /**
@@ -64,15 +61,14 @@ export const [ViewManager, viewManager] = hoistCmp.withFactory<ViewManagerProps>
64
61
  revertButtonProps,
65
62
  showSaveButton = 'whenDirty',
66
63
  showRevertButton = 'never',
67
- buttonSide = 'right',
68
- showPrivateViewsInSubMenu = false,
69
- showGlobalViewsInSubMenu = false
64
+ buttonSide = 'right'
70
65
  }: ViewManagerProps) {
71
- const save = saveButton({mode: showSaveButton, ...saveButtonProps}),
72
- revert = revertButton({mode: showRevertButton, ...revertButtonProps}),
66
+ const locModel = useLocalModel(() => new ViewManagerLocalModel(model)),
67
+ save = saveButton({model: locModel, mode: showSaveButton, ...saveButtonProps}),
68
+ revert = revertButton({model: locModel, mode: showRevertButton, ...revertButtonProps}),
73
69
  menu = popover({
74
- item: menuButton(menuButtonProps),
75
- content: viewMenu({showPrivateViewsInSubMenu, showGlobalViewsInSubMenu}),
70
+ item: menuButton({model: locModel, ...menuButtonProps}),
71
+ content: viewMenu({model: locModel}),
76
72
  placement: 'bottom-start',
77
73
  popoverClassName: 'xh-view-manager__popover'
78
74
  });
@@ -81,15 +77,15 @@ export const [ViewManager, viewManager] = hoistCmp.withFactory<ViewManagerProps>
81
77
  className,
82
78
  items: buttonSide == 'left' ? [revert, save, menu] : [menu, save, revert]
83
79
  }),
84
- manageDialog({omit: !model.manageDialogOpen}),
85
- saveAsDialog()
80
+ manageDialog({model: locModel.manageDialogModel}),
81
+ saveAsDialog({model: locModel.saveAsDialogModel})
86
82
  );
87
83
  }
88
84
  });
89
85
 
90
- const menuButton = hoistCmp.factory<ViewManagerModel>({
86
+ const menuButton = hoistCmp.factory<ViewManagerLocalModel>({
91
87
  render({model, ...rest}) {
92
- const {view, typeDisplayName, isLoading} = model;
88
+ const {view, typeDisplayName, isLoading} = model.parent;
93
89
  return button({
94
90
  className: 'xh-view-manager__menu-button',
95
91
  text: view.isDefault ? `Default ${startCase(typeDisplayName)}` : view.name,
@@ -106,40 +102,46 @@ const menuButton = hoistCmp.factory<ViewManagerModel>({
106
102
  }
107
103
  });
108
104
 
109
- const saveButton = hoistCmp.factory<ViewManagerModel>({
105
+ const saveButton = hoistCmp.factory<ViewManagerLocalModel>({
110
106
  render({model, mode, ...rest}) {
111
107
  if (hideStateButton(model, mode)) return null;
108
+ const {parent, saveAsDialogModel} = model,
109
+ {typeDisplayName, isLoading, isValueDirty} = parent;
112
110
  return button({
113
111
  className: 'xh-view-manager__save-button',
114
112
  icon: Icon.save(),
115
- tooltip: `Save changes to this ${model.typeDisplayName}`,
113
+ tooltip: `Save changes to this ${typeDisplayName}`,
116
114
  intent: 'primary',
117
- disabled: !model.isValueDirty || model.isLoading,
115
+ disabled: !isValueDirty || isLoading,
118
116
  onClick: () => {
119
- model.isViewSavable ? model.saveAsync() : model.saveAsAsync();
117
+ parent.isViewSavable ? parent.saveAsync() : saveAsDialogModel.open();
120
118
  },
121
119
  ...rest
122
120
  });
123
121
  }
124
122
  });
125
123
 
126
- const revertButton = hoistCmp.factory<ViewManagerModel>({
124
+ const revertButton = hoistCmp.factory<ViewManagerLocalModel>({
127
125
  render({model, mode, ...rest}) {
128
126
  if (hideStateButton(model, mode)) return null;
127
+ const {typeDisplayName, isLoading, isValueDirty} = model.parent;
129
128
  return button({
130
129
  className: 'xh-view-manager__revert-button',
131
130
  icon: Icon.reset(),
132
- tooltip: `Revert changes to this ${model.typeDisplayName}`,
131
+ tooltip: `Revert changes to this ${typeDisplayName}`,
133
132
  intent: 'danger',
134
- disabled: !model.isValueDirty || model.isLoading,
135
- onClick: () => model.resetAsync(),
133
+ disabled: !isValueDirty || isLoading,
134
+ onClick: () => model.parent.resetAsync(),
136
135
  ...rest
137
136
  });
138
137
  }
139
138
  });
140
139
 
141
- function hideStateButton(model: ViewManagerModel, mode: ViewManagerStateButtonMode): boolean {
140
+ function hideStateButton(model: ViewManagerLocalModel, mode: ViewManagerStateButtonMode): boolean {
141
+ const {parent} = model;
142
142
  return (
143
- mode === 'never' || (mode === 'whenDirty' && !model.isValueDirty) || model.isViewAutoSavable
143
+ mode === 'never' ||
144
+ (mode === 'whenDirty' && !parent.isValueDirty) ||
145
+ parent.isViewAutoSavable
144
146
  );
145
147
  }
@@ -0,0 +1,28 @@
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
+
8
+ import {HoistModel, managed} from '@xh/hoist/core';
9
+ import {ManageDialogModel} from './dialog/ManageDialogModel';
10
+ import {SaveAsDialogModel} from './dialog/SaveAsDialogModel';
11
+ import {ViewManagerModel} from '@xh/hoist/cmp/viewmanager';
12
+
13
+ export class ViewManagerLocalModel extends HoistModel {
14
+ readonly parent: ViewManagerModel;
15
+
16
+ @managed
17
+ readonly manageDialogModel: ManageDialogModel;
18
+
19
+ @managed
20
+ readonly saveAsDialogModel: SaveAsDialogModel;
21
+
22
+ constructor(parent: ViewManagerModel) {
23
+ super();
24
+ this.parent = parent;
25
+ this.manageDialogModel = new ManageDialogModel(parent);
26
+ this.saveAsDialogModel = new SaveAsDialogModel(parent);
27
+ }
28
+ }
@@ -5,7 +5,7 @@
5
5
  * Copyright © 2024 Extremely Heavy Industries Inc.
6
6
  */
7
7
 
8
- import {box, div, filler, fragment, hbox, span} from '@xh/hoist/cmp/layout';
8
+ import {box} from '@xh/hoist/cmp/layout';
9
9
  import {spinner} from '@xh/hoist/cmp/spinner';
10
10
  import {hoistCmp} from '@xh/hoist/core';
11
11
  import {ViewManagerModel, ViewInfo} from '@xh/hoist/cmp/viewmanager';
@@ -14,188 +14,181 @@ import {Icon} from '@xh/hoist/icon';
14
14
  import {menu, menuDivider, menuItem} from '@xh/hoist/kit/blueprint';
15
15
  import {wait} from '@xh/hoist/promise';
16
16
  import {consumeEvent, pluralize} from '@xh/hoist/utils/js';
17
- import {isEmpty, startCase} from 'lodash';
17
+ import {Dictionary} from 'express-serve-static-core';
18
+ import {each, filter, groupBy, isEmpty, orderBy, some, startCase} from 'lodash';
18
19
  import {ReactNode} from 'react';
19
- import {ViewManagerProps} from './ViewManager';
20
+ import {ViewManagerLocalModel} from './ViewManagerLocalModel';
20
21
 
21
22
  /**
22
23
  * Default Menu used by ViewManager.
23
24
  */
24
- export const viewMenu = hoistCmp.factory<ViewManagerProps>({
25
- render({model, showPrivateViewsInSubMenu, showGlobalViewsInSubMenu}) {
26
- const {
27
- enableAutoSave,
28
- autoSaveUnavailableReason,
29
- autoSave,
30
- enableDefault,
31
- isViewSavable,
32
- view,
33
- typeDisplayName,
34
- globalDisplayName,
35
- favoriteViews,
36
- views,
37
- isValueDirty,
38
- privateViews,
39
- globalViews,
40
- loadModel
41
- } = model;
42
-
43
- const pluralName = pluralize(startCase(typeDisplayName)),
44
- myPluralName = `My ${pluralName}`,
45
- globalPluralName = `${startCase(globalDisplayName)} ${pluralName}`,
46
- items = [];
47
- if (!isEmpty(favoriteViews)) {
48
- items.push(
49
- menuDivider({title: 'Favorites'}),
50
- ...favoriteViews.map(info => {
51
- return menuItem({
52
- key: `${info.token}-favorite`,
53
- icon: view.token === info.token ? Icon.check() : Icon.placeholder(),
54
- text: textAndFaveToggle({info}),
55
- onClick: () => model.selectViewAsync(info),
56
- title: info.description
57
- });
58
- })
59
- );
60
- }
61
-
62
- if (!isEmpty(privateViews)) {
63
- const privateItems = privateViews.map(it => buildMenuItem(it, model));
64
- if (showPrivateViewsInSubMenu) {
65
- items.push(
66
- menuDivider({omit: isEmpty(items)}),
67
- menuItem({
68
- text: myPluralName,
69
- shouldDismissPopover: false,
70
- items: privateItems
71
- })
72
- );
73
- } else {
74
- items.push(menuDivider({title: myPluralName}), ...privateItems);
75
- }
76
- }
77
-
78
- if (!isEmpty(globalViews)) {
79
- const globalItems = globalViews.map(it => buildMenuItem(it, model));
80
- if (showGlobalViewsInSubMenu) {
81
- items.push(
82
- menuDivider({omit: isEmpty(items)}),
83
- menuItem({
84
- text: globalPluralName,
85
- shouldDismissPopover: false,
86
- items: globalItems
87
- })
88
- );
89
- } else {
90
- items.push(menuDivider({title: globalPluralName}), ...globalItems);
91
- }
92
- }
93
-
25
+ export const viewMenu = hoistCmp.factory<ViewManagerLocalModel>({
26
+ render({model}) {
94
27
  return menu({
95
28
  className: 'xh-view-manager__menu',
96
- items: [
97
- ...items,
98
- menuDivider({omit: !enableDefault || isEmpty(items)}),
99
- menuItem({
100
- icon: view.isDefault ? Icon.check() : Icon.placeholder(),
101
- text: `Default ${startCase(typeDisplayName)}`,
102
- omit: !enableDefault,
103
- onClick: () => model.selectViewAsync(null)
104
- }),
105
- menuDivider(),
106
- menuItem({
107
- icon: Icon.save(),
108
- text: 'Save',
109
- disabled: !isViewSavable || !isValueDirty,
110
- onClick: () => model.saveAsync()
111
- }),
112
- menuItem({
113
- icon: Icon.placeholder(),
114
- text: 'Save As...',
115
- onClick: () => model.saveAsAsync()
116
- }),
117
- menuItem({
118
- icon: Icon.reset(),
119
- text: `Revert`,
120
- disabled: !isValueDirty,
121
- onClick: () => model.resetAsync()
122
- }),
123
- menuDivider({omit: !enableAutoSave}),
124
- menuItem({
125
- omit: !enableAutoSave,
126
- text: switchInput({
127
- label: 'Auto Save',
128
- value: !autoSaveUnavailableReason && autoSave,
129
- disabled: !!autoSaveUnavailableReason,
130
- onChange: v => (model.autoSave = v),
131
- inline: true
132
- }),
133
- title: autoSaveUnavailableReason,
134
- shouldDismissPopover: false
135
- }),
136
- menuDivider(),
137
- menuItem({
138
- icon: Icon.gear(),
139
- disabled: isEmpty(views),
140
- text: `Manage ${pluralName}...`,
141
- onClick: () => model.openManageDialog()
142
- }),
143
- menuItem({
144
- icon: !loadModel.isPending
145
- ? Icon.refresh()
146
- : box({
147
- height: 20,
148
- item: spinner({width: 16.25, height: 16.25})
149
- }),
150
- disabled: loadModel.isPending,
151
- text: `Refresh ${pluralName}`,
152
- onClick: e => {
153
- // Ensure at least 100ms delay to render spinner
154
- Promise.all([wait(100), model.refreshAsync()]).linkTo(loadModel);
155
- consumeEvent(e);
156
- }
157
- })
158
- ]
29
+ items: [...getNavMenuItems(model.parent), menuDivider(), ...getOtherMenuItems(model)]
159
30
  });
160
31
  }
161
32
  });
162
33
 
163
- function buildMenuItem(data: ViewInfo, model: ViewManagerModel): ReactNode {
164
- const selected = data.token === model.view.token,
165
- icon = selected ? Icon.check() : Icon.placeholder();
34
+ function getNavMenuItems(model: ViewManagerModel): ReactNode[] {
35
+ const {enableDefault, view, typeDisplayName, globalDisplayName} = model,
36
+ ownedViews = groupBy(filter(model.ownedViews, 'isPinned'), 'group'),
37
+ globalViews = groupBy(filter(model.globalViews, 'isPinned'), 'group'),
38
+ sharedViews = groupBy(filter(model.sharedViews, 'isPinned'), 'owner'),
39
+ pluralName = pluralize(startCase(typeDisplayName)),
40
+ ret = [];
41
+
42
+ // Main Views items by type
43
+ if (!isEmpty(ownedViews)) {
44
+ ret.push(
45
+ menuDivider({title: `My ${pluralName}`}),
46
+ ...getGroupedMenuItems(ownedViews, model)
47
+ );
48
+ }
49
+ if (!isEmpty(globalViews)) {
50
+ ret.push(
51
+ menuDivider({title: `${startCase(globalDisplayName)} ${pluralName}`}),
52
+ ...getGroupedMenuItems(globalViews, model)
53
+ );
54
+ }
55
+ if (!isEmpty(sharedViews)) {
56
+ ret.push(
57
+ menuDivider({title: `Shared ${pluralName}`}),
58
+ ...getGroupedMenuItems(sharedViews, model)
59
+ );
60
+ }
61
+
62
+ if (enableDefault) {
63
+ ret.push(
64
+ menuDivider({omit: isEmpty(ret)}),
65
+ menuItem({
66
+ className: 'xh-view-manager__menu-item',
67
+ icon: view.isDefault ? Icon.check() : Icon.placeholder(),
68
+ text: `Default ${startCase(typeDisplayName)}`,
69
+ onClick: () => model.selectViewAsync(null)
70
+ })
71
+ );
72
+ }
73
+
74
+ return ret;
75
+ }
76
+
77
+ function getOtherMenuItems(model: ViewManagerLocalModel): ReactNode[] {
78
+ const {parent} = model;
79
+ const {
80
+ enableAutoSave,
81
+ autoSaveUnavailableReason,
82
+ autoSave,
83
+ isViewSavable,
84
+ views,
85
+ isValueDirty,
86
+ loadModel,
87
+ typeDisplayName
88
+ } = parent;
89
+
90
+ const pluralName = pluralize(startCase(typeDisplayName));
91
+
92
+ return [
93
+ menuItem({
94
+ icon: Icon.save(),
95
+ text: 'Save',
96
+ disabled: !isViewSavable || !isValueDirty,
97
+ onClick: () => parent.saveAsync()
98
+ }),
99
+ menuItem({
100
+ icon: Icon.placeholder(),
101
+ text: 'Save As...',
102
+ onClick: () => model.saveAsDialogModel.open()
103
+ }),
104
+ menuItem({
105
+ icon: Icon.reset(),
106
+ text: `Revert`,
107
+ disabled: !isValueDirty,
108
+ onClick: () => parent.resetAsync()
109
+ }),
110
+ menuDivider({omit: !enableAutoSave}),
111
+ menuItem({
112
+ omit: !enableAutoSave,
113
+ text: switchInput({
114
+ label: 'Auto Save',
115
+ value: !autoSaveUnavailableReason && autoSave,
116
+ disabled: !!autoSaveUnavailableReason,
117
+ onChange: v => (parent.autoSave = v),
118
+ inline: true
119
+ }),
120
+ title: autoSaveUnavailableReason,
121
+ shouldDismissPopover: false
122
+ }),
123
+ menuDivider(),
124
+ menuItem({
125
+ icon: Icon.gear(),
126
+ disabled: isEmpty(views),
127
+ text: `Manage ${pluralName}...`,
128
+ onClick: () => model.manageDialogModel.open()
129
+ }),
130
+ menuItem({
131
+ icon: !loadModel.isPending
132
+ ? Icon.refresh()
133
+ : box({
134
+ height: 20,
135
+ item: spinner({width: 16.25, height: 16.25})
136
+ }),
137
+ disabled: loadModel.isPending,
138
+ text: `Refresh ${pluralName}`,
139
+ onClick: e => {
140
+ // Ensure at least 100ms delay to render spinner
141
+ Promise.all([wait(100), parent.refreshAsync()]).linkTo(loadModel);
142
+ consumeEvent(e);
143
+ }
144
+ })
145
+ ];
146
+ }
147
+
148
+ function getGroupedMenuItems(
149
+ byGroup: Dictionary<ViewInfo[]>,
150
+ model: ViewManagerModel
151
+ ): ReactNode[] {
152
+ // Create grouped tree...
153
+ let nodes: (ViewInfo | {name: string; groupViews: ViewInfo[]; isSelected: boolean})[] = [],
154
+ selectedToken = model.view.token;
155
+
156
+ each(byGroup, (groupViews, name) => {
157
+ if (name != 'null') {
158
+ nodes.push({name, groupViews, isSelected: some(groupViews, {token: selectedToken})});
159
+ } else {
160
+ nodes.push(...groupViews);
161
+ }
162
+ });
163
+
164
+ // ...sort groups first, then alpha by name. But could easily intersperse instead
165
+ nodes = orderBy(nodes, [v => v instanceof ViewInfo, 'name']);
166
+
167
+ return nodes.map(n => {
168
+ return n instanceof ViewInfo
169
+ ? viewMenuItem(n, model)
170
+ : menuItem({
171
+ text: n.name,
172
+ icon: n.isSelected ? Icon.check() : Icon.placeholder(),
173
+ shouldDismissPopover: false,
174
+ items: n.groupViews.map(v => viewMenuItem(v, model))
175
+ });
176
+ });
177
+ }
178
+
179
+ function viewMenuItem(view: ViewInfo, model: ViewManagerModel): ReactNode {
180
+ const icon = view.isCurrentView ? Icon.check() : Icon.placeholder(),
181
+ title = [];
182
+
183
+ if (!view.isOwned && view.owner) title.push(view.owner);
184
+ if (view.description) title.push(view.description);
166
185
 
167
186
  return menuItem({
168
187
  className: 'xh-view-manager__menu-item',
169
- key: data.token,
188
+ key: view.token,
189
+ text: view.name,
190
+ title: title.join(' | '),
170
191
  icon,
171
- text: textAndFaveToggle({info: data}),
172
- title: data.description,
173
- onClick: () => model.selectViewAsync(data)
192
+ onClick: () => model.selectViewAsync(view)
174
193
  });
175
194
  }
176
-
177
- const textAndFaveToggle = hoistCmp.factory<ViewManagerModel>({
178
- render({model, info}) {
179
- const {isFavorite, name} = info;
180
- return hbox({
181
- alignItems: 'center',
182
- items: [
183
- span({style: {paddingRight: 5}, item: name}),
184
- fragment({
185
- omit: !model.enableFavorites,
186
- items: [
187
- filler(),
188
- div({
189
- className: `xh-view-manager__menu-item__fave-toggle ${isFavorite ? 'xh-view-manager__menu-item__fave-toggle--active' : ''}`,
190
- item: Icon.favorite({prefix: isFavorite ? 'fas' : 'far'}),
191
- onClick: e => {
192
- consumeEvent(e);
193
- model.toggleFavorite(info.token);
194
- }
195
- })
196
- ]
197
- })
198
- ]
199
- });
200
- }
201
- });
@@ -5,43 +5,56 @@
5
5
  * Copyright © 2024 Extremely Heavy Industries Inc.
6
6
  */
7
7
 
8
- import {tabContainer} from '@xh/hoist/cmp/tab';
9
- import {filler, hframe, placeholder} from '@xh/hoist/cmp/layout';
8
+ import {grid, GridModel} from '@xh/hoist/cmp/grid';
9
+ import {div, filler, hframe, placeholder, vframe} from '@xh/hoist/cmp/layout';
10
10
  import {storeFilterField} from '@xh/hoist/cmp/store';
11
- import {creates, hoistCmp} from '@xh/hoist/core';
12
- import {editForm} from './EditForm';
13
- import {ManageDialogModel} from './ManageDialogModel';
14
- import {button} from '@xh/hoist/desktop/cmp/button';
11
+ import {tabContainer} from '@xh/hoist/cmp/tab';
12
+ import {hoistCmp, uses} from '@xh/hoist/core';
13
+ import {button, refreshButton} from '@xh/hoist/desktop/cmp/button';
15
14
  import {panel} from '@xh/hoist/desktop/cmp/panel';
16
- import {toolbar} from '@xh/hoist/desktop/cmp/toolbar';
15
+ import {toolbar, toolbarSep} from '@xh/hoist/desktop/cmp/toolbar';
17
16
  import {Icon} from '@xh/hoist/icon';
18
17
  import {dialog} from '@xh/hoist/kit/blueprint';
19
18
  import {pluralize} from '@xh/hoist/utils/js';
20
19
  import {capitalize} from 'lodash';
20
+ import {ManageDialogModel} from './ManageDialogModel';
21
+ import {viewMultiPanel} from './ViewMultiPanel';
22
+ import {viewPanel} from './ViewPanel';
21
23
 
22
24
  /**
23
- * Default management dialog for ViewManager
25
+ * Default management dialog for ViewManager.
24
26
  */
25
27
  export const manageDialog = hoistCmp.factory({
26
28
  displayName: 'ManageDialog',
27
29
  className: 'xh-view-manager__manage-dialog',
28
- model: creates(ManageDialogModel),
30
+ model: uses(() => ManageDialogModel),
29
31
 
30
32
  render({model, className}) {
31
- const {typeDisplayName, updateTask, loadTask, selectedViews} = model;
32
- const count = selectedViews.length;
33
+ if (!model.isOpen) return null;
34
+
35
+ const {typeDisplayName, updateTask, loadTask, selectedViews} = model,
36
+ count = selectedViews.length;
37
+
33
38
  return dialog({
34
39
  title: `Manage ${capitalize(pluralize(typeDisplayName))}`,
35
40
  icon: Icon.gear(),
36
41
  className,
37
42
  isOpen: true,
38
- style: {width: '800px', maxWidth: '90vm', minHeight: '430px'},
43
+ style: {width: '1000px', maxWidth: '90vw', minHeight: '550px'},
39
44
  canOutsideClickClose: false,
40
45
  onClose: () => model.close(),
41
46
  item: panel({
42
47
  item: hframe(
43
- viewPanel(),
44
- count == 0 ? placeholderPanel() : count > 1 ? multiSelectionPanel() : editForm()
48
+ selectorPanel(),
49
+ panel({
50
+ item:
51
+ count == 0
52
+ ? placeholderPanel()
53
+ : count > 1
54
+ ? viewMultiPanel()
55
+ : viewPanel(),
56
+ bbar: bbar()
57
+ })
45
58
  ),
46
59
  mask: [updateTask, loadTask]
47
60
  })
@@ -49,16 +62,39 @@ export const manageDialog = hoistCmp.factory({
49
62
  }
50
63
  });
51
64
 
52
- const viewPanel = hoistCmp.factory<ManageDialogModel>({
65
+ const selectorPanel = hoistCmp.factory<ManageDialogModel>({
53
66
  render({model}) {
54
67
  return panel({
55
- modelConfig: {defaultSize: 350, side: 'left', collapsible: false},
68
+ modelConfig: {defaultSize: 650, side: 'left', collapsible: false},
56
69
  item: tabContainer(),
57
70
  bbar: [
58
71
  storeFilterField({
59
72
  autoApply: false,
60
- includeFields: ['name'],
73
+ includeFields: ['name', 'group'],
61
74
  onFilterChange: f => (model.filter = f)
75
+ }),
76
+ filler(),
77
+ refreshButton({model})
78
+ ]
79
+ });
80
+ }
81
+ });
82
+
83
+ export const viewsGrid = hoistCmp.factory<GridModel>({
84
+ render({model, helpText}) {
85
+ return vframe({
86
+ paddingTop: 5,
87
+ items: [
88
+ grid({
89
+ model,
90
+ agOptions: {
91
+ suppressMakeColumnVisibleAfterUnGroup: true
92
+ }
93
+ }),
94
+ div({
95
+ item: helpText,
96
+ omit: !helpText,
97
+ className: 'xh-view-manager__manage-dialog__help-text'
62
98
  })
63
99
  ]
64
100
  });
@@ -67,32 +103,23 @@ const viewPanel = hoistCmp.factory<ManageDialogModel>({
67
103
 
68
104
  const placeholderPanel = hoistCmp.factory<ManageDialogModel>({
69
105
  render({model}) {
70
- return panel({
71
- item: placeholder(Icon.gears(), `Select a ${model.typeDisplayName}`),
72
- bbar: toolbar(filler(), button({text: 'Close', onClick: () => model.close()}))
73
- });
106
+ return placeholder(Icon.gears(), `Select a ${model.typeDisplayName}`);
74
107
  }
75
108
  });
76
109
 
77
- const multiSelectionPanel = hoistCmp.factory<ManageDialogModel>({
110
+ const bbar = hoistCmp.factory<ManageDialogModel>({
78
111
  render({model}) {
79
- const {selectedViews} = model;
80
- return panel({
81
- item: placeholder(
82
- Icon.gears(),
83
- `${selectedViews.length} selected ${pluralize(model.typeDisplayName)}`
84
- ),
85
- bbar: toolbar(
86
- button({
87
- text: 'Delete',
88
- icon: Icon.delete(),
89
- intent: 'danger',
90
- disabled: !model.canDelete,
91
- onClick: () => model.deleteAsync(selectedViews)
92
- }),
93
- filler(),
94
- button({text: 'Close', onClick: () => model.close()})
95
- )
96
- });
112
+ const {selectedView} = model;
113
+ return toolbar(
114
+ filler(),
115
+ button({
116
+ text: selectedView?.isCurrentView ? 'Currently Active' : 'Activate + Close',
117
+ onClick: () => model.activateSelectedViewAndClose(),
118
+ disabled: selectedView?.isCurrentView,
119
+ omit: !selectedView
120
+ }),
121
+ toolbarSep({omit: !selectedView}),
122
+ button({text: 'Close', onClick: () => model.close()})
123
+ );
97
124
  }
98
125
  });