@xh/hoist 70.0.0-SNAPSHOT.1731083521069 → 70.0.0-SNAPSHOT.1731623470295

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 (95) hide show
  1. package/CHANGELOG.md +57 -0
  2. package/build/types/cmp/filter/FilterChooserModel.d.ts +17 -12
  3. package/build/types/cmp/grid/GridModel.d.ts +5 -9
  4. package/build/types/cmp/grid/Types.d.ts +7 -19
  5. package/build/types/cmp/grid/columns/Column.d.ts +0 -1
  6. package/build/types/cmp/grid/impl/InitPersist.d.ts +7 -0
  7. package/build/types/cmp/grouping/GroupingChooserModel.d.ts +6 -8
  8. package/build/types/cmp/tab/TabContainerModel.d.ts +10 -4
  9. package/build/types/cmp/zoneGrid/Types.d.ts +6 -6
  10. package/build/types/cmp/zoneGrid/ZoneGridModel.d.ts +0 -2
  11. package/build/types/cmp/zoneGrid/impl/InitPersist.d.ts +7 -0
  12. package/build/types/core/HoistBase.d.ts +1 -1
  13. package/build/types/core/persist/CustomProvider.d.ts +5 -6
  14. package/build/types/core/persist/DashViewProvider.d.ts +6 -6
  15. package/build/types/core/persist/LocalStorageProvider.d.ts +4 -5
  16. package/build/types/core/persist/PersistOptions.d.ts +5 -4
  17. package/build/types/core/persist/Persistable.d.ts +14 -0
  18. package/build/types/core/persist/PersistenceProvider.d.ts +47 -34
  19. package/build/types/core/persist/PrefProvider.d.ts +5 -5
  20. package/build/types/core/persist/index.d.ts +2 -0
  21. package/build/types/core/persist/viewmanager/Types.d.ts +46 -0
  22. package/build/types/core/persist/viewmanager/ViewManagerModel.d.ts +149 -0
  23. package/build/types/core/persist/viewmanager/ViewManagerProvider.d.ts +10 -0
  24. package/build/types/core/persist/viewmanager/impl/ManageDialogModel.d.ts +30 -0
  25. package/build/types/core/persist/viewmanager/impl/SaveDialogModel.d.ts +23 -0
  26. package/build/types/core/persist/viewmanager/index.d.ts +2 -0
  27. package/build/types/desktop/cmp/button/ColAutosizeButton.d.ts +1 -1
  28. package/build/types/desktop/cmp/dash/DashConfig.d.ts +3 -1
  29. package/build/types/desktop/cmp/dash/DashModel.d.ts +1 -2
  30. package/build/types/desktop/cmp/dash/DashViewSpec.d.ts +1 -1
  31. package/build/types/desktop/cmp/dash/canvas/DashCanvasModel.d.ts +10 -2
  32. package/build/types/desktop/cmp/dash/container/DashContainerModel.d.ts +26 -10
  33. package/build/types/desktop/cmp/dash/container/impl/DashContainerUtils.d.ts +4 -2
  34. package/build/types/desktop/cmp/panel/PanelModel.d.ts +8 -4
  35. package/build/types/desktop/cmp/viewmanager/ViewManager.d.ts +22 -0
  36. package/build/types/desktop/cmp/viewmanager/cmp/ManageDialog.d.ts +6 -0
  37. package/build/types/desktop/cmp/viewmanager/cmp/SaveDialog.d.ts +2 -0
  38. package/build/types/desktop/cmp/viewmanager/index.d.ts +3 -0
  39. package/build/types/kit/blueprint/Wrappers.d.ts +1 -1
  40. package/build/types/mobile/cmp/button/ColAutosizeButton.d.ts +1 -1
  41. package/build/types/promise/Promise.d.ts +6 -5
  42. package/build/types/svc/GridAutosizeService.d.ts +2 -5
  43. package/build/types/svc/JsonBlobService.d.ts +45 -24
  44. package/cmp/filter/FilterChooserModel.ts +142 -125
  45. package/cmp/grid/Grid.ts +2 -10
  46. package/cmp/grid/GridModel.ts +18 -31
  47. package/cmp/grid/Types.ts +7 -21
  48. package/cmp/grid/columns/Column.ts +0 -1
  49. package/cmp/grid/impl/InitPersist.ts +71 -0
  50. package/cmp/grouping/GroupingChooserModel.ts +48 -57
  51. package/cmp/tab/TabContainerModel.ts +22 -36
  52. package/cmp/zoneGrid/Types.ts +6 -6
  53. package/cmp/zoneGrid/ZoneGridModel.ts +2 -7
  54. package/cmp/zoneGrid/impl/InitPersist.ts +70 -0
  55. package/core/HoistBase.ts +14 -22
  56. package/core/HoistBaseDecorators.ts +26 -28
  57. package/core/persist/CustomProvider.ts +7 -10
  58. package/core/persist/DashViewProvider.ts +8 -10
  59. package/core/persist/LocalStorageProvider.ts +9 -12
  60. package/core/persist/PersistOptions.ts +6 -4
  61. package/core/persist/Persistable.ts +23 -0
  62. package/core/persist/PersistenceProvider.ts +159 -79
  63. package/core/persist/PrefProvider.ts +9 -12
  64. package/core/persist/index.ts +2 -0
  65. package/core/persist/viewmanager/Types.ts +51 -0
  66. package/core/persist/viewmanager/ViewManagerModel.ts +515 -0
  67. package/core/persist/viewmanager/ViewManagerProvider.ts +51 -0
  68. package/core/persist/viewmanager/impl/ManageDialogModel.ts +274 -0
  69. package/core/persist/viewmanager/impl/SaveDialogModel.ts +112 -0
  70. package/core/persist/viewmanager/index.ts +2 -0
  71. package/desktop/cmp/button/ColAutosizeButton.ts +1 -1
  72. package/desktop/cmp/dash/DashConfig.ts +3 -1
  73. package/desktop/cmp/dash/DashModel.ts +1 -2
  74. package/desktop/cmp/dash/DashViewSpec.ts +1 -1
  75. package/desktop/cmp/dash/canvas/DashCanvasModel.ts +31 -30
  76. package/desktop/cmp/dash/container/DashContainerModel.ts +68 -43
  77. package/desktop/cmp/dash/container/impl/DashContainerUtils.ts +13 -4
  78. package/desktop/cmp/leftrightchooser/LeftRightChooserFilter.ts +1 -1
  79. package/desktop/cmp/panel/PanelModel.ts +33 -53
  80. package/desktop/cmp/store/impl/StoreFilterField.ts +1 -1
  81. package/desktop/cmp/viewmanager/ViewManager.scss +58 -0
  82. package/desktop/cmp/viewmanager/ViewManager.ts +274 -0
  83. package/desktop/cmp/viewmanager/cmp/ManageDialog.ts +197 -0
  84. package/desktop/cmp/viewmanager/cmp/SaveDialog.ts +89 -0
  85. package/desktop/cmp/viewmanager/index.ts +3 -0
  86. package/mobile/cmp/button/ColAutosizeButton.ts +1 -1
  87. package/package.json +1 -1
  88. package/promise/Promise.ts +6 -7
  89. package/svc/GridAutosizeService.ts +73 -36
  90. package/svc/JsonBlobService.ts +64 -31
  91. package/tsconfig.tsbuildinfo +1 -1
  92. package/build/types/cmp/grid/impl/GridPersistenceModel.d.ts +0 -41
  93. package/build/types/cmp/zoneGrid/impl/ZoneGridPersistenceModel.d.ts +0 -39
  94. package/cmp/grid/impl/GridPersistenceModel.ts +0 -174
  95. package/cmp/zoneGrid/impl/ZoneGridPersistenceModel.ts +0 -149
@@ -0,0 +1,274 @@
1
+ import {div, filler, fragment, hbox, span} from '@xh/hoist/cmp/layout';
2
+ import {hoistCmp, HoistProps, uses} from '@xh/hoist/core';
3
+ import './ViewManager.scss';
4
+ import {ViewTree} from '@xh/hoist/core/persist/viewmanager';
5
+ import {ViewManagerModel} from '@xh/hoist/core/persist/viewmanager/ViewManagerModel';
6
+ import {button, ButtonProps} from '@xh/hoist/desktop/cmp/button';
7
+ import {switchInput} from '@xh/hoist/desktop/cmp/input';
8
+ import {manageDialog} from '@xh/hoist/desktop/cmp/viewmanager/cmp/ManageDialog';
9
+ import {saveDialog} from '@xh/hoist/desktop/cmp/viewmanager/cmp/SaveDialog';
10
+ import {Icon} from '@xh/hoist/icon';
11
+ import {menu, menuDivider, menuItem, popover} from '@xh/hoist/kit/blueprint';
12
+ import {consumeEvent, pluralize} from '@xh/hoist/utils/js';
13
+ import {isEmpty} from 'lodash';
14
+ import {ReactNode} from 'react';
15
+
16
+ export interface ViewManagerProps extends HoistProps<ViewManagerModel> {
17
+ menuButtonProps?: Partial<ButtonProps>;
18
+ saveButtonProps?: Partial<ButtonProps>;
19
+ /** 'whenDirty' to only show saveButton when persistence state is dirty. (Default 'whenDirty') */
20
+ showSaveButton?: 'whenDirty' | 'always' | 'never';
21
+ /** True to render private views in sub-menu (Default false)*/
22
+ showPrivateViewsInSubMenu?: boolean;
23
+ /** True to render shared views in sub-menu (Default false)*/
24
+ showSharedViewsInSubMenu?: boolean;
25
+ }
26
+
27
+ /**
28
+ * Desktop ViewManager component - a button-based menu for saving and swapping between named
29
+ * bundles of persisted component state (eg grid views, dashboards, and similar).
30
+ *
31
+ * See {@link ViewManagerModel} for additional details and configuration options.
32
+ */
33
+ export const [ViewManager, viewManager] = hoistCmp.withFactory<ViewManagerProps>({
34
+ displayName: 'ViewManager',
35
+ className: 'xh-view-manager',
36
+ model: uses(ViewManagerModel),
37
+
38
+ render({
39
+ model,
40
+ className,
41
+ menuButtonProps,
42
+ saveButtonProps,
43
+ showSaveButton = 'whenDirty',
44
+ showPrivateViewsInSubMenu = false,
45
+ showSharedViewsInSubMenu = false
46
+ }: ViewManagerProps) {
47
+ return fragment(
48
+ hbox({
49
+ className,
50
+ items: [
51
+ popover({
52
+ item: menuButton(menuButtonProps),
53
+ content: viewMenu({showPrivateViewsInSubMenu, showSharedViewsInSubMenu}),
54
+ placement: 'bottom-start',
55
+ popoverClassName: 'xh-view-manager__popover'
56
+ }),
57
+ saveButton({
58
+ showSaveButton,
59
+ ...saveButtonProps
60
+ })
61
+ ]
62
+ }),
63
+ manageDialog({
64
+ omit: !model.manageDialogOpen,
65
+ onClose: () => model.closeManageDialog()
66
+ }),
67
+ saveDialog()
68
+ );
69
+ }
70
+ });
71
+
72
+ const menuButton = hoistCmp.factory<ViewManagerModel>({
73
+ render({model, ...rest}) {
74
+ const {selectedView, DisplayName} = model;
75
+ return button({
76
+ className: 'xh-view-manager__menu-button',
77
+ text: model.getHierarchyDisplayName(selectedView?.name) ?? `Default ${DisplayName}`,
78
+ icon: Icon.bookmark(),
79
+ rightIcon: Icon.chevronDown(),
80
+ outlined: true,
81
+ ...rest
82
+ });
83
+ }
84
+ });
85
+
86
+ const saveButton = hoistCmp.factory<ViewManagerModel>({
87
+ render({model, showSaveButton, ...rest}) {
88
+ if (
89
+ !model.canShowSaveButton ||
90
+ showSaveButton === 'never' ||
91
+ (showSaveButton === 'whenDirty' && !model.isDirty)
92
+ ) {
93
+ return null;
94
+ }
95
+
96
+ return button({
97
+ className: 'xh-view-manager__save-button',
98
+ icon: Icon.save(),
99
+ tooltip: `Save changes to this ${model.displayName}`,
100
+ intent: 'primary',
101
+ disabled: !model.canSave,
102
+ onClick: () => model.saveAsync(false).linkTo(model.loadModel),
103
+ ...rest
104
+ });
105
+ }
106
+ });
107
+
108
+ const viewMenu = hoistCmp.factory<ViewManagerProps>({
109
+ render({model, showPrivateViewsInSubMenu, showSharedViewsInSubMenu}) {
110
+ const {DisplayName} = model,
111
+ pluralDisp = pluralize(DisplayName),
112
+ items = [];
113
+
114
+ if (!isEmpty(model.favoriteViews)) {
115
+ items.push(
116
+ menuDivider({title: 'Favorites'}),
117
+ ...model.favoriteViews.map(it => {
118
+ return menuItem({
119
+ key: `${it.token}-favorite`,
120
+ icon: model.selectedToken === it.token ? Icon.check() : Icon.placeholder(),
121
+ text: menuItemTextAndFaveToggle({
122
+ view: {...it, text: model.getHierarchyDisplayName(it.name)}
123
+ }),
124
+ onClick: () => model.selectViewAsync(it.token).linkTo(model.loadModel),
125
+ title: it.description
126
+ });
127
+ })
128
+ );
129
+ }
130
+
131
+ if (!isEmpty(model.privateViewTree)) {
132
+ if (showPrivateViewsInSubMenu) {
133
+ items.push(
134
+ menuDivider({omit: isEmpty(items)}),
135
+ menuItem({
136
+ text: `My ${pluralDisp}`,
137
+ shouldDismissPopover: false,
138
+ children: model.privateViewTree.map(it => {
139
+ return buildMenuItem(it, model);
140
+ })
141
+ })
142
+ );
143
+ } else {
144
+ items.push(
145
+ menuDivider({title: `My ${pluralDisp}`}),
146
+ ...model.privateViewTree.map(it => buildMenuItem(it, model))
147
+ );
148
+ }
149
+ }
150
+
151
+ if (!isEmpty(model.sharedViewTree)) {
152
+ if (showSharedViewsInSubMenu) {
153
+ items.push(
154
+ menuDivider({omit: isEmpty(items)}),
155
+ menuItem({
156
+ text: `Shared ${pluralDisp}`,
157
+ shouldDismissPopover: false,
158
+ children: model.sharedViewTree.map(it => {
159
+ return buildMenuItem(it, model);
160
+ })
161
+ })
162
+ );
163
+ } else {
164
+ items.push(
165
+ menuDivider({title: `Shared ${pluralDisp}`}),
166
+ ...model.sharedViewTree.map(it => buildMenuItem(it, model))
167
+ );
168
+ }
169
+ }
170
+
171
+ return menu({
172
+ className: 'xh-view-manager__menu',
173
+ items: [
174
+ ...items,
175
+ menuDivider({omit: !model.enableDefault || isEmpty(items)}),
176
+ menuItem({
177
+ icon: model.selectedToken ? Icon.placeholder() : Icon.check(),
178
+ text: `Default ${DisplayName}`,
179
+ omit: !model.enableDefault,
180
+ onClick: () => model.selectViewAsync(null)
181
+ }),
182
+ menuDivider(),
183
+ menuItem({
184
+ icon: Icon.save(),
185
+ text: 'Save',
186
+ disabled: !model.canSave,
187
+ onClick: () => model.saveAsync(false)
188
+ }),
189
+ menuItem({
190
+ icon: Icon.copy(),
191
+ text: 'Save as...',
192
+ onClick: () => model.saveAsAsync()
193
+ }),
194
+ menuItem({
195
+ icon: Icon.reset(),
196
+ text: `Revert ${DisplayName}`,
197
+ disabled: !model.isDirty,
198
+ onClick: () => model.resetAsync()
199
+ }),
200
+ menuDivider({omit: !model.enableAutoSave}),
201
+ menuItem({
202
+ omit: !model.enableAutoSave,
203
+ text: switchInput({
204
+ label: 'Auto Save',
205
+ bind: 'autoSaveActive',
206
+ inline: true,
207
+ disabled: !model.enableAutoSaveToggle
208
+ }),
209
+ title: model.disabledAutoSaveReason,
210
+ shouldDismissPopover: false
211
+ }),
212
+ menuDivider(),
213
+ menuItem({
214
+ icon: Icon.gear(),
215
+ disabled: isEmpty(model.views),
216
+ text: `Manage ${pluralDisp}...`,
217
+ onClick: () => model.openManageDialog()
218
+ })
219
+ ]
220
+ });
221
+ }
222
+ });
223
+
224
+ function buildMenuItem(viewOrFolder: ViewTree, model: ViewManagerModel): ReactNode {
225
+ const {type, text, selected} = viewOrFolder,
226
+ icon = selected ? Icon.check() : Icon.placeholder();
227
+
228
+ switch (type) {
229
+ case 'folder':
230
+ return menuItem({
231
+ text,
232
+ icon,
233
+ shouldDismissPopover: false,
234
+ children: viewOrFolder.items
235
+ ? viewOrFolder.items.map(child => buildMenuItem(child, model))
236
+ : []
237
+ });
238
+ case 'view':
239
+ return menuItem({
240
+ className: 'xh-view-manager__menu-item',
241
+ key: viewOrFolder.token,
242
+ icon,
243
+ text: menuItemTextAndFaveToggle({model, view: viewOrFolder}),
244
+ title: viewOrFolder.description,
245
+ onClick: () => model.selectViewAsync(viewOrFolder.token).linkTo(model.loadModel)
246
+ });
247
+ }
248
+ }
249
+
250
+ const menuItemTextAndFaveToggle = hoistCmp.factory<ViewManagerModel>({
251
+ render({model, view}) {
252
+ const isFavorite = model.isFavorite(view.token);
253
+ return hbox({
254
+ alignItems: 'center',
255
+ items: [
256
+ span({style: {paddingRight: 5}, item: view.text}),
257
+ fragment({
258
+ omit: !model.enableFavorites,
259
+ items: [
260
+ filler(),
261
+ div({
262
+ className: `xh-view-manager__menu-item__fave-toggle ${isFavorite ? 'xh-view-manager__menu-item__fave-toggle--active' : ''}`,
263
+ item: Icon.favorite({prefix: isFavorite ? 'fas' : 'far'}),
264
+ onClick: e => {
265
+ consumeEvent(e);
266
+ model.toggleFavorite(view.token);
267
+ }
268
+ })
269
+ ]
270
+ })
271
+ ]
272
+ });
273
+ }
274
+ });
@@ -0,0 +1,197 @@
1
+ import {form} from '@xh/hoist/cmp/form';
2
+ import {grid} from '@xh/hoist/cmp/grid';
3
+ import {div, filler, hbox, hframe, hspacer, placeholder, span, vframe} from '@xh/hoist/cmp/layout';
4
+ import {storeFilterField} from '@xh/hoist/cmp/store';
5
+ import {creates, hoistCmp, HoistProps, XH} from '@xh/hoist/core';
6
+ import {ManageDialogModel} from '@xh/hoist/core/persist/viewmanager/impl/ManageDialogModel';
7
+ import {button} from '@xh/hoist/desktop/cmp/button';
8
+ import {formField} from '@xh/hoist/desktop/cmp/form';
9
+ import {select, textArea, textInput} from '@xh/hoist/desktop/cmp/input';
10
+ import {panel} from '@xh/hoist/desktop/cmp/panel';
11
+ import {toolbar} from '@xh/hoist/desktop/cmp/toolbar';
12
+ import {fmtCompactDate} from '@xh/hoist/format';
13
+ import {Icon} from '@xh/hoist/icon';
14
+ import {dialog} from '@xh/hoist/kit/blueprint';
15
+ import {pluralize} from '@xh/hoist/utils/js';
16
+ import {capitalize} from 'lodash';
17
+
18
+ export interface ManageDialogProps extends HoistProps<ManageDialogModel> {
19
+ onClose: () => void;
20
+ }
21
+
22
+ export const manageDialog = hoistCmp.factory<ManageDialogProps>({
23
+ displayName: 'ManageDialog',
24
+ className: 'xh-view-manager__manage-dialog',
25
+ model: creates(ManageDialogModel),
26
+
27
+ render({model, className, onClose}) {
28
+ const {displayName, saveTask, deleteTask} = model;
29
+ return dialog({
30
+ title: `Manage ${capitalize(pluralize(displayName))}`,
31
+ icon: Icon.gear(),
32
+ className,
33
+ isOpen: true,
34
+ style: {width: '800px', maxWidth: '90vm', minHeight: '430px'},
35
+ canOutsideClickClose: false,
36
+ onClose,
37
+ item: panel({
38
+ item: hframe(gridPanel(), formPanel({onClose})),
39
+ mask: [saveTask, deleteTask]
40
+ })
41
+ });
42
+ }
43
+ });
44
+
45
+ const gridPanel = hoistCmp.factory({
46
+ render() {
47
+ return panel({
48
+ modelConfig: {defaultSize: 350, side: 'left', collapsible: false},
49
+ item: grid(),
50
+ bbar: [storeFilterField()]
51
+ });
52
+ }
53
+ });
54
+
55
+ const formPanel = hoistCmp.factory<ManageDialogProps>({
56
+ render({model, onClose}) {
57
+ const {displayName, formModel} = model,
58
+ {values} = formModel,
59
+ isOwnView = values.owner === XH.getUsername();
60
+
61
+ if (model.hasMultiSelection) {
62
+ return multiSelectionPanel();
63
+ }
64
+
65
+ if (!model.selectedId)
66
+ return panel({
67
+ item: placeholder(Icon.gears(), `Select a ${displayName}`),
68
+ bbar: bbar({onClose})
69
+ });
70
+
71
+ return panel({
72
+ item: form({
73
+ fieldDefaults: {
74
+ commitOnChange: true
75
+ },
76
+ item: vframe({
77
+ className: 'xh-view-manager__manage-dialog__form',
78
+ items: [
79
+ formField({
80
+ field: 'name',
81
+ item: textInput(),
82
+ info: model.canEdit
83
+ ? `Organize your ${pluralize(displayName)} into folders by including the "\\" character in their names - e.g. "My folder\\My ${displayName}".`
84
+ : null
85
+ }),
86
+ formField({
87
+ field: 'description',
88
+ item: textArea({
89
+ selectOnFocus: true,
90
+ height: 70
91
+ }),
92
+ readonlyRenderer: v =>
93
+ v
94
+ ? v
95
+ : span({
96
+ item: 'None provided',
97
+ className: 'xh-text-color-muted'
98
+ })
99
+ }),
100
+ formField({
101
+ field: 'isShared',
102
+ label: 'Visibility',
103
+ item: select({
104
+ options: [
105
+ {value: true, label: 'Shared with all users'},
106
+ {
107
+ value: false,
108
+ label: `Private to ${isOwnView ? 'me' : values.owner}`
109
+ }
110
+ ],
111
+ enableFilter: false
112
+ }),
113
+ omit: !model.enableSharing
114
+ }),
115
+ hbox({
116
+ omit: !model.showSaveButton,
117
+ style: {margin: '10px 20px'},
118
+ items: [
119
+ button({
120
+ text: 'Save Changes',
121
+ icon: Icon.check(),
122
+ intent: 'success',
123
+ minimal: false,
124
+ flex: 1,
125
+ onClick: () => model.saveAsync()
126
+ }),
127
+ hspacer(),
128
+ button({
129
+ icon: Icon.reset(),
130
+ tooltip: 'Revert changes',
131
+ minimal: false,
132
+ onClick: () => formModel.reset()
133
+ })
134
+ ]
135
+ }),
136
+ filler(),
137
+ div({
138
+ className: 'xh-view-manager__manage-dialog__metadata',
139
+ items: [
140
+ `Created ${fmtCompactDate(values.dateCreated)} by ${
141
+ values.owner === XH.getUsername() ? 'you' : values.owner
142
+ }. `,
143
+ `Updated ${fmtCompactDate(values.lastUpdated)} by ${
144
+ values.lastUpdatedBy === XH.getUsername()
145
+ ? 'you'
146
+ : values.lastUpdatedBy
147
+ }.`
148
+ ]
149
+ })
150
+ ]
151
+ })
152
+ }),
153
+ bbar: bbar({onClose})
154
+ });
155
+ }
156
+ });
157
+
158
+ const multiSelectionPanel = hoistCmp.factory<ManageDialogProps>({
159
+ render({model, onClose}) {
160
+ const {selectedIds} = model;
161
+ return panel({
162
+ item: vframe({
163
+ alignItems: 'center',
164
+ justifyContent: 'center',
165
+ item: button({
166
+ text: `Delete ${selectedIds.length} ${pluralize(model.displayName)}`,
167
+ icon: Icon.delete(),
168
+ intent: 'danger',
169
+ outlined: true,
170
+ disabled: !model.canDelete,
171
+ onClick: () => model.deleteAsync()
172
+ })
173
+ }),
174
+ bbar: bbar({onClose})
175
+ });
176
+ }
177
+ });
178
+
179
+ const bbar = hoistCmp.factory<ManageDialogProps>({
180
+ render({model, onClose}) {
181
+ return toolbar(
182
+ button({
183
+ text: 'Delete',
184
+ icon: Icon.delete(),
185
+ intent: 'danger',
186
+ disabled: !model.canDelete,
187
+ omit: model.hasMultiSelection,
188
+ onClick: () => model.deleteAsync()
189
+ }),
190
+ filler(),
191
+ button({
192
+ text: 'Close',
193
+ onClick: onClose
194
+ })
195
+ );
196
+ }
197
+ });
@@ -0,0 +1,89 @@
1
+ import {form} from '@xh/hoist/cmp/form';
2
+ import {filler, vframe} from '@xh/hoist/cmp/layout';
3
+ import {hoistCmp, uses} from '@xh/hoist/core';
4
+ import {SaveDialogModel} from '@xh/hoist/core/persist/viewmanager/impl/SaveDialogModel';
5
+ import {button} from '@xh/hoist/desktop/cmp/button';
6
+ import {formField} from '@xh/hoist/desktop/cmp/form';
7
+ import {textArea, textInput} from '@xh/hoist/desktop/cmp/input';
8
+ import {panel} from '@xh/hoist/desktop/cmp/panel';
9
+ import {toolbar} from '@xh/hoist/desktop/cmp/toolbar';
10
+ import {Icon} from '@xh/hoist/icon';
11
+ import {dialog} from '@xh/hoist/kit/blueprint';
12
+
13
+ export const saveDialog = hoistCmp.factory<SaveDialogModel>({
14
+ displayName: 'SaveDialog',
15
+ className: 'xh-view-manager__save-dialog',
16
+ model: uses(SaveDialogModel),
17
+
18
+ render({model, className}) {
19
+ if (!model.isOpen) return null;
20
+
21
+ return dialog({
22
+ title: `Save as...`,
23
+ icon: Icon.copy(),
24
+ className,
25
+ isOpen: true,
26
+ style: {width: 500},
27
+ canOutsideClickClose: false,
28
+ onClose: () => model.cancel(),
29
+ item: formPanel()
30
+ });
31
+ }
32
+ });
33
+
34
+ const formPanel = hoistCmp.factory<SaveDialogModel>({
35
+ render({model}) {
36
+ return panel({
37
+ item: form({
38
+ fieldDefaults: {
39
+ commitOnChange: true
40
+ },
41
+ item: vframe({
42
+ className: 'xh-view-manager__save-dialog__form',
43
+ items: [
44
+ formField({
45
+ field: 'name',
46
+ item: textInput({
47
+ autoFocus: true,
48
+ selectOnFocus: true,
49
+ onKeyDown: e => {
50
+ if (e.key === 'Enter') model.saveAsAsync();
51
+ }
52
+ })
53
+ }),
54
+ formField({
55
+ field: 'description',
56
+ item: textArea({
57
+ selectOnFocus: true,
58
+ height: 90
59
+ })
60
+ })
61
+ ]
62
+ })
63
+ }),
64
+ bbar: bbar(),
65
+ mask: model.saveTask
66
+ });
67
+ }
68
+ });
69
+
70
+ const bbar = hoistCmp.factory<SaveDialogModel>({
71
+ render({model}) {
72
+ const {formModel, DisplayName} = model;
73
+ return toolbar(
74
+ filler(),
75
+ button({
76
+ text: 'Cancel',
77
+ onClick: () => model.cancel()
78
+ }),
79
+ button({
80
+ text: `Save as new ${DisplayName}`,
81
+ icon: Icon.copy(),
82
+ outlined: true,
83
+ intent: 'success',
84
+ disabled: !formModel.isValid,
85
+ onClick: () => model.saveAsAsync()
86
+ })
87
+ );
88
+ }
89
+ });
@@ -0,0 +1,3 @@
1
+ export * from './ViewManager';
2
+ export * from './cmp/ManageDialog';
3
+ export * from './cmp/SaveDialog';
@@ -17,7 +17,7 @@ export interface ColAutosizeButtonProps extends ButtonProps {
17
17
  gridModel?: GridModel;
18
18
 
19
19
  /** Options for the grid autosize. */
20
- autosizeOptions?: GridAutosizeOptions;
20
+ autosizeOptions?: Omit<GridAutosizeOptions, 'mode'>;
21
21
  }
22
22
 
23
23
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xh/hoist",
3
- "version": "70.0.0-SNAPSHOT.1731083521069",
3
+ "version": "70.0.0-SNAPSHOT.1731623470295",
4
4
  "description": "Hoist add-on for building and deploying React Applications.",
5
5
  "repository": "github:xh/hoist-react",
6
6
  "homepage": "https://xh.io",
@@ -121,21 +121,20 @@ export function wait<T>(interval: number = 0): Promise<T> {
121
121
  }
122
122
 
123
123
  /**
124
- * Return a promise that will resolve after a condition has been met, polling at the specified
125
- * interval.
126
- *
127
- * @param condition - function that should return true when condition is met
124
+ * Return a promise that will resolve after a condition has been met, or reject if timed out.
125
+ * @param condition - function returning true when expected condition is met.
128
126
  * @param interval - milliseconds to wait between checks (default 50). Note that the actual time
129
127
  * will be subject to the minimum delay for `setTimeout()` in the browser.
130
128
  * @param timeout - milliseconds after which the Promise should be rejected (default 5000).
131
129
  */
132
130
  export function waitFor(
133
131
  condition: () => boolean,
134
- interval: number = 50,
135
- timeout: number = 5 * SECONDS
132
+ {interval = 50, timeout = 5 * SECONDS}: {interval?: number; timeout?: number} = {}
136
133
  ): Promise<void> {
137
- const startTime = Date.now();
134
+ if (!isNumber(interval) || interval <= 0) throw new Error('Invalid interval');
135
+ if (!isNumber(timeout) || timeout <= 0) throw new Error('Invalid timeout');
138
136
 
137
+ const startTime = Date.now();
139
138
  return new Promise((resolve, reject) => {
140
139
  const resolveOnMet = () => {
141
140
  if (condition()) {