@xh/hoist 71.0.0-SNAPSHOT.1733262000771 → 71.0.0-SNAPSHOT.1733347475493

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 (73) hide show
  1. package/CHANGELOG.md +8 -2
  2. package/appcontainer/AppContainerModel.ts +2 -1
  3. package/build/types/cmp/viewmanager/SaveAsDialogModel.d.ts +23 -0
  4. package/build/types/cmp/viewmanager/View.d.ts +28 -0
  5. package/build/types/cmp/viewmanager/ViewInfo.d.ts +30 -0
  6. package/build/types/cmp/viewmanager/ViewManagerModel.d.ts +185 -0
  7. package/build/types/cmp/viewmanager/index.d.ts +4 -0
  8. package/build/types/core/XH.d.ts +2 -1
  9. package/build/types/core/persist/PersistOptions.d.ts +3 -1
  10. package/build/types/core/persist/index.d.ts +6 -5
  11. package/build/types/core/persist/{CustomProvider.d.ts → provider/CustomProvider.d.ts} +1 -1
  12. package/build/types/core/persist/{DashViewProvider.d.ts → provider/DashViewProvider.d.ts} +2 -2
  13. package/build/types/core/persist/{LocalStorageProvider.d.ts → provider/LocalStorageProvider.d.ts} +1 -1
  14. package/build/types/core/persist/{PrefProvider.d.ts → provider/PrefProvider.d.ts} +1 -2
  15. package/build/types/core/persist/provider/SessionStorageProvider.d.ts +10 -0
  16. package/build/types/core/persist/{viewmanager → provider}/ViewManagerProvider.d.ts +3 -3
  17. package/build/types/desktop/cmp/viewmanager/ViewManager.d.ts +9 -10
  18. package/build/types/desktop/cmp/viewmanager/ViewMenu.d.ts +5 -0
  19. package/build/types/desktop/cmp/viewmanager/dialog/EditForm.d.ts +5 -0
  20. package/build/types/desktop/cmp/viewmanager/dialog/EditFormModel.d.ts +18 -0
  21. package/build/types/desktop/cmp/viewmanager/dialog/ManageDialog.d.ts +5 -0
  22. package/build/types/desktop/cmp/viewmanager/dialog/ManageDialogModel.d.ts +38 -0
  23. package/build/types/desktop/cmp/viewmanager/dialog/SaveAsDialog.d.ts +5 -0
  24. package/build/types/svc/JsonBlobService.d.ts +1 -1
  25. package/build/types/svc/index.d.ts +2 -1
  26. package/build/types/svc/storage/BaseStorageService.d.ts +21 -0
  27. package/build/types/svc/storage/LocalStorageService.d.ts +12 -0
  28. package/build/types/svc/storage/SessionStorageService.d.ts +12 -0
  29. package/cmp/viewmanager/SaveAsDialogModel.ts +97 -0
  30. package/cmp/viewmanager/View.ts +56 -0
  31. package/cmp/viewmanager/ViewInfo.ts +58 -0
  32. package/cmp/viewmanager/ViewManagerModel.ts +710 -0
  33. package/cmp/viewmanager/index.ts +4 -0
  34. package/core/XH.ts +2 -0
  35. package/core/persist/PersistOptions.ts +4 -1
  36. package/core/persist/PersistenceProvider.ts +5 -0
  37. package/core/persist/index.ts +6 -5
  38. package/core/persist/{CustomProvider.ts → provider/CustomProvider.ts} +1 -1
  39. package/core/persist/{DashViewProvider.ts → provider/DashViewProvider.ts} +1 -1
  40. package/core/persist/{LocalStorageProvider.ts → provider/LocalStorageProvider.ts} +1 -1
  41. package/core/persist/{PrefProvider.ts → provider/PrefProvider.ts} +2 -2
  42. package/core/persist/provider/SessionStorageProvider.ts +35 -0
  43. package/core/persist/{viewmanager → provider}/ViewManagerProvider.ts +5 -9
  44. package/desktop/cmp/viewmanager/ViewManager.ts +53 -229
  45. package/desktop/cmp/viewmanager/ViewMenu.ts +201 -0
  46. package/desktop/cmp/viewmanager/dialog/EditForm.ts +126 -0
  47. package/desktop/cmp/viewmanager/dialog/EditFormModel.ts +125 -0
  48. package/desktop/cmp/viewmanager/dialog/ManageDialog.ts +98 -0
  49. package/desktop/cmp/viewmanager/dialog/ManageDialogModel.ts +279 -0
  50. package/desktop/cmp/viewmanager/{impl/SaveDialog.ts → dialog/SaveAsDialog.ts} +20 -12
  51. package/package.json +1 -1
  52. package/svc/JsonBlobService.ts +1 -1
  53. package/svc/index.ts +2 -1
  54. package/svc/{LocalStorageService.ts → storage/BaseStorageService.ts} +13 -23
  55. package/svc/storage/LocalStorageService.ts +23 -0
  56. package/svc/storage/SessionStorageService.ts +23 -0
  57. package/tsconfig.tsbuildinfo +1 -1
  58. package/build/types/core/persist/viewmanager/Types.d.ts +0 -48
  59. package/build/types/core/persist/viewmanager/ViewManagerModel.d.ts +0 -145
  60. package/build/types/core/persist/viewmanager/impl/BuildViewTree.d.ts +0 -8
  61. package/build/types/core/persist/viewmanager/impl/ManageDialogModel.d.ts +0 -30
  62. package/build/types/core/persist/viewmanager/impl/SaveDialogModel.d.ts +0 -23
  63. package/build/types/core/persist/viewmanager/index.d.ts +0 -2
  64. package/build/types/desktop/cmp/viewmanager/impl/ManageDialog.d.ts +0 -6
  65. package/build/types/desktop/cmp/viewmanager/impl/SaveDialog.d.ts +0 -2
  66. package/build/types/svc/LocalStorageService.d.ts +0 -24
  67. package/core/persist/viewmanager/Types.ts +0 -53
  68. package/core/persist/viewmanager/ViewManagerModel.ts +0 -481
  69. package/core/persist/viewmanager/impl/BuildViewTree.ts +0 -68
  70. package/core/persist/viewmanager/impl/ManageDialogModel.ts +0 -276
  71. package/core/persist/viewmanager/impl/SaveDialogModel.ts +0 -112
  72. package/core/persist/viewmanager/index.ts +0 -2
  73. package/desktop/cmp/viewmanager/impl/ManageDialog.ts +0 -197
@@ -0,0 +1,201 @@
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 {box, div, filler, fragment, hbox, span} from '@xh/hoist/cmp/layout';
9
+ import {spinner} from '@xh/hoist/cmp/spinner';
10
+ import {hoistCmp} from '@xh/hoist/core';
11
+ import {ViewManagerModel, ViewInfo} from '@xh/hoist/cmp/viewmanager';
12
+ import {switchInput} from '@xh/hoist/desktop/cmp/input';
13
+ import {Icon} from '@xh/hoist/icon';
14
+ import {menu, menuDivider, menuItem} from '@xh/hoist/kit/blueprint';
15
+ import {wait} from '@xh/hoist/promise';
16
+ import {consumeEvent, pluralize} from '@xh/hoist/utils/js';
17
+ import {isEmpty, startCase} from 'lodash';
18
+ import {ReactNode} from 'react';
19
+ import {ViewManagerProps} from './ViewManager';
20
+
21
+ /**
22
+ * Default Menu used by ViewManager.
23
+ */
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.info?.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
+
94
+ return menu({
95
+ 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
+ ]
159
+ });
160
+ }
161
+ });
162
+
163
+ function buildMenuItem(data: ViewInfo, model: ViewManagerModel): ReactNode {
164
+ const selected = data.token === model.view.token,
165
+ icon = selected ? Icon.check() : Icon.placeholder();
166
+
167
+ return menuItem({
168
+ className: 'xh-view-manager__menu-item',
169
+ key: data.token,
170
+ icon,
171
+ text: textAndFaveToggle({info: data}),
172
+ title: data.description,
173
+ onClick: () => model.selectViewAsync(data)
174
+ });
175
+ }
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
+ });
@@ -0,0 +1,126 @@
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 {form} from '@xh/hoist/cmp/form';
9
+ import {div, filler, hbox, hspacer, span, vframe} from '@xh/hoist/cmp/layout';
10
+
11
+ import {hoistCmp, uses, XH} from '@xh/hoist/core';
12
+ import {EditFormModel} from '@xh/hoist/desktop/cmp/viewmanager/dialog/EditFormModel';
13
+ import {button} from '@xh/hoist/desktop/cmp/button';
14
+ import {formField} from '@xh/hoist/desktop/cmp/form';
15
+ import {select, textArea, textInput} from '@xh/hoist/desktop/cmp/input';
16
+ import {panel} from '@xh/hoist/desktop/cmp/panel';
17
+ import {toolbar} from '@xh/hoist/desktop/cmp/toolbar';
18
+ import {fmtCompactDate} from '@xh/hoist/format';
19
+ import {Icon} from '@xh/hoist/icon';
20
+ import {startCase} from 'lodash';
21
+
22
+ /**
23
+ * Default Edit Form for ViewManager
24
+ */
25
+ export const editForm = hoistCmp.factory({
26
+ model: uses(EditFormModel),
27
+ render({model}) {
28
+ const {formModel, view, parent} = model;
29
+ if (!view) return null;
30
+
31
+ const {manageGlobal, globalDisplayName} = parent,
32
+ {lastUpdated, lastUpdatedBy, owner} = view,
33
+ isOwnView = owner === XH.getUsername();
34
+
35
+ return panel({
36
+ item: form({
37
+ fieldDefaults: {
38
+ commitOnChange: true
39
+ },
40
+ item: vframe({
41
+ className: 'xh-view-manager__manage-dialog__form',
42
+ items: [
43
+ formField({
44
+ field: 'name',
45
+ item: textInput()
46
+ }),
47
+ formField({
48
+ field: 'description',
49
+ item: textArea({
50
+ selectOnFocus: true,
51
+ height: 70
52
+ }),
53
+ readonlyRenderer: v =>
54
+ v
55
+ ? v
56
+ : span({
57
+ item: 'None provided',
58
+ className: 'xh-text-color-muted'
59
+ })
60
+ }),
61
+ formField({
62
+ field: 'isGlobal',
63
+ label: 'Visibility',
64
+ item: select({
65
+ options: [
66
+ {value: true, label: startCase(globalDisplayName)},
67
+ {
68
+ value: false,
69
+ label: `Private to ${isOwnView ? 'me' : owner}`
70
+ }
71
+ ],
72
+ enableFilter: false
73
+ }),
74
+ omit: !manageGlobal
75
+ }),
76
+ hbox({
77
+ omit: !model.showSaveButton,
78
+ style: {margin: '10px 20px'},
79
+ items: [
80
+ button({
81
+ text: 'Save Changes',
82
+ icon: Icon.check(),
83
+ intent: 'success',
84
+ minimal: false,
85
+ disabled: !formModel.isValid,
86
+ flex: 1,
87
+ onClick: () => model.saveAsync()
88
+ }),
89
+ hspacer(),
90
+ button({
91
+ icon: Icon.reset(),
92
+ tooltip: 'Revert changes',
93
+ minimal: false,
94
+ onClick: () => formModel.reset()
95
+ })
96
+ ]
97
+ }),
98
+ filler(),
99
+ div({
100
+ className: 'xh-view-manager__manage-dialog__metadata',
101
+ item: `Last Updated: ${fmtCompactDate(lastUpdated)} (${lastUpdatedBy === XH.getUsername() ? 'you' : lastUpdatedBy})`
102
+ })
103
+ ]
104
+ })
105
+ }),
106
+ bbar: bbar()
107
+ });
108
+ }
109
+ });
110
+
111
+ const bbar = hoistCmp.factory<EditFormModel>({
112
+ render({model}) {
113
+ const {parent} = model;
114
+ return toolbar(
115
+ button({
116
+ text: 'Delete',
117
+ icon: Icon.delete(),
118
+ intent: 'danger',
119
+ disabled: !parent.canDelete,
120
+ onClick: () => parent.deleteAsync([model.view])
121
+ }),
122
+ filler(),
123
+ button({text: 'Close', onClick: () => parent.close()})
124
+ );
125
+ }
126
+ });
@@ -0,0 +1,125 @@
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 {FormModel} from '@xh/hoist/cmp/form';
9
+ import {fragment, p, span, strong} from '@xh/hoist/cmp/layout';
10
+ import {HoistModel, managed, TaskObserver, XH} from '@xh/hoist/core';
11
+ import {ManageDialogModel} from './ManageDialogModel';
12
+ import {makeObservable} from '@xh/hoist/mobx';
13
+ import {throwIf} from '@xh/hoist/utils/js';
14
+ import {ViewInfo} from '@xh/hoist/cmp/viewmanager';
15
+ import {action, observable} from 'mobx';
16
+
17
+ /**
18
+ * Backing model for EditForm
19
+ */
20
+ export class EditFormModel extends HoistModel {
21
+ parent: ManageDialogModel;
22
+
23
+ @managed formModel: FormModel;
24
+ @observable.ref view: ViewInfo;
25
+
26
+ @action
27
+ setView(view: ViewInfo) {
28
+ const {formModel, parent} = this;
29
+ this.view = view;
30
+ if (!view) return null;
31
+ formModel.init(view);
32
+ formModel.readonly = view.isGlobal && !parent.manageGlobal;
33
+ }
34
+
35
+ get loadTask(): TaskObserver {
36
+ return this.parent.loadModel;
37
+ }
38
+
39
+ get showSaveButton(): boolean {
40
+ const {formModel, parent} = this;
41
+ return formModel.isDirty && !formModel.readonly && !parent.loadModel.isPending;
42
+ }
43
+
44
+ constructor(parent: ManageDialogModel) {
45
+ super();
46
+ makeObservable(this);
47
+ this.formModel = this.createFormModel();
48
+ this.parent = parent;
49
+ }
50
+
51
+ async saveAsync() {
52
+ const {parent, view, formModel} = this,
53
+ {manageGlobal, typeDisplayName, globalDisplayName} = parent,
54
+ {name, description, isGlobal} = formModel.getData(),
55
+ isValid = await formModel.validateAsync(),
56
+ isDirty = formModel.isDirty;
57
+
58
+ if (!isValid || !isDirty) return;
59
+
60
+ throwIf(
61
+ (view.isGlobal || isGlobal) && !manageGlobal,
62
+ `Cannot save changes to ${globalDisplayName} ${typeDisplayName} - missing required permission.`
63
+ );
64
+
65
+ if (isGlobal != view.isGlobal) {
66
+ const msgs = [];
67
+ if (isGlobal) {
68
+ msgs.push(
69
+ `This ${typeDisplayName} will become visible to all other ${XH.appName} users.`
70
+ );
71
+ } else {
72
+ msgs.push(
73
+ span(
74
+ `The selected ${typeDisplayName} will revert to being private `,
75
+ strong('It will no longer be available to ALL users.')
76
+ )
77
+ );
78
+ if (view.owner != XH.getUsername()) {
79
+ msgs.push(
80
+ `The selected ${typeDisplayName} will revert to being private to its owner (${view.owner}).`,
81
+ `Note that you will no longer have access to this ${typeDisplayName} and will not be able to undo this change.`
82
+ );
83
+ }
84
+ }
85
+
86
+ msgs.push('Are you sure you want to proceed?');
87
+
88
+ const confirmed = await XH.confirm({
89
+ message: fragment(msgs.map(m => p(m))),
90
+ confirmProps: {
91
+ text: 'Yes, update visibility',
92
+ outlined: true,
93
+ autoFocus: false,
94
+ intent: 'primary'
95
+ }
96
+ });
97
+ if (!confirmed) return;
98
+ }
99
+
100
+ await parent.updateAsync(view, name, description, isGlobal);
101
+ }
102
+
103
+ //------------------------
104
+ // Implementation
105
+ //------------------------
106
+ private createFormModel(): FormModel {
107
+ return new FormModel({
108
+ fields: [
109
+ {
110
+ name: 'name',
111
+ rules: [
112
+ async ({value}) => {
113
+ return this.parent.viewManagerModel.validateViewNameAsync(
114
+ value,
115
+ this.view
116
+ );
117
+ }
118
+ ]
119
+ },
120
+ {name: 'description'},
121
+ {name: 'isGlobal', displayName: 'Global'}
122
+ ]
123
+ });
124
+ }
125
+ }
@@ -0,0 +1,98 @@
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 {tabContainer} from '@xh/hoist/cmp/tab';
9
+ import {filler, hframe, placeholder} from '@xh/hoist/cmp/layout';
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';
15
+ import {panel} from '@xh/hoist/desktop/cmp/panel';
16
+ import {toolbar} from '@xh/hoist/desktop/cmp/toolbar';
17
+ import {Icon} from '@xh/hoist/icon';
18
+ import {dialog} from '@xh/hoist/kit/blueprint';
19
+ import {pluralize} from '@xh/hoist/utils/js';
20
+ import {capitalize} from 'lodash';
21
+
22
+ /**
23
+ * Default management dialog for ViewManager
24
+ */
25
+ export const manageDialog = hoistCmp.factory({
26
+ displayName: 'ManageDialog',
27
+ className: 'xh-view-manager__manage-dialog',
28
+ model: creates(ManageDialogModel),
29
+
30
+ render({model, className}) {
31
+ const {typeDisplayName, updateTask, loadTask, selectedViews} = model;
32
+ const count = selectedViews.length;
33
+ return dialog({
34
+ title: `Manage ${capitalize(pluralize(typeDisplayName))}`,
35
+ icon: Icon.gear(),
36
+ className,
37
+ isOpen: true,
38
+ style: {width: '800px', maxWidth: '90vm', minHeight: '430px'},
39
+ canOutsideClickClose: false,
40
+ onClose: () => model.close(),
41
+ item: panel({
42
+ item: hframe(
43
+ viewPanel(),
44
+ count == 0 ? placeholderPanel() : count > 1 ? multiSelectionPanel() : editForm()
45
+ ),
46
+ mask: [updateTask, loadTask]
47
+ })
48
+ });
49
+ }
50
+ });
51
+
52
+ const viewPanel = hoistCmp.factory<ManageDialogModel>({
53
+ render({model}) {
54
+ return panel({
55
+ modelConfig: {defaultSize: 350, side: 'left', collapsible: false},
56
+ item: tabContainer(),
57
+ bbar: [
58
+ storeFilterField({
59
+ autoApply: false,
60
+ includeFields: ['name'],
61
+ onFilterChange: f => (model.filter = f)
62
+ })
63
+ ]
64
+ });
65
+ }
66
+ });
67
+
68
+ const placeholderPanel = hoistCmp.factory<ManageDialogModel>({
69
+ 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
+ });
74
+ }
75
+ });
76
+
77
+ const multiSelectionPanel = hoistCmp.factory<ManageDialogModel>({
78
+ 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
+ });
97
+ }
98
+ });