@xh/hoist 74.1.1 → 75.0.0-SNAPSHOT.1749666833237
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 +5 -35
- package/build/types/cmp/grouping/GroupingChooserModel.d.ts +4 -8
- package/build/types/cmp/viewmanager/ViewInfo.d.ts +3 -7
- package/build/types/core/types/Interfaces.d.ts +1 -1
- package/build/types/desktop/cmp/grouping/GroupingChooser.d.ts +6 -17
- package/build/types/desktop/cmp/panel/Panel.d.ts +1 -1
- package/build/types/desktop/cmp/viewmanager/ViewManager.d.ts +10 -26
- package/build/types/mobile/cmp/grouping/GroupingChooser.d.ts +6 -2
- package/build/types/utils/impl/index.d.ts +0 -1
- package/cmp/grouping/GroupingChooserModel.ts +12 -25
- package/cmp/viewmanager/ViewInfo.ts +3 -7
- package/core/HoistAppModel.ts +0 -1
- package/core/exception/ExceptionHandler.ts +1 -1
- package/core/types/Interfaces.ts +2 -2
- package/desktop/cmp/button/AppMenuButton.ts +46 -3
- package/desktop/cmp/grouping/GroupingChooser.scss +35 -39
- package/desktop/cmp/grouping/GroupingChooser.ts +89 -157
- package/desktop/cmp/panel/Panel.ts +1 -1
- package/desktop/cmp/viewmanager/ViewManager.ts +16 -58
- package/desktop/cmp/viewmanager/ViewMenu.ts +2 -9
- package/mobile/appcontainer/FeedbackDialog.ts +0 -4
- package/mobile/appcontainer/OptionsDialog.ts +1 -3
- package/mobile/cmp/grid/impl/ColChooser.ts +2 -3
- package/mobile/cmp/grouping/GroupingChooser.scss +20 -41
- package/mobile/cmp/grouping/GroupingChooser.ts +89 -60
- package/mobile/cmp/panel/DialogPanel.scss +0 -5
- package/package.json +1 -1
- package/svc/TrackService.ts +3 -4
- package/tsconfig.tsbuildinfo +1 -1
- package/utils/impl/index.ts +0 -1
- package/utils/js/LangUtils.ts +1 -1
- package/build/types/utils/impl/MenuItems.d.ts +0 -13
- package/utils/impl/MenuItems.ts +0 -57
|
@@ -5,66 +5,43 @@
|
|
|
5
5
|
* Copyright © 2025 Extremely Heavy Industries Inc.
|
|
6
6
|
*/
|
|
7
7
|
import {GroupingChooserModel} from '@xh/hoist/cmp/grouping';
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
div,
|
|
11
|
-
filler,
|
|
12
|
-
fragment,
|
|
13
|
-
hbox,
|
|
14
|
-
hframe,
|
|
15
|
-
placeholder,
|
|
16
|
-
vbox,
|
|
17
|
-
vframe
|
|
18
|
-
} from '@xh/hoist/cmp/layout';
|
|
19
|
-
import {hoistCmp, Side, uses} from '@xh/hoist/core';
|
|
8
|
+
import {box, div, filler, fragment, hbox, vbox} from '@xh/hoist/cmp/layout';
|
|
9
|
+
import {hoistCmp, uses} from '@xh/hoist/core';
|
|
20
10
|
import {button, ButtonProps} from '@xh/hoist/desktop/cmp/button';
|
|
21
11
|
import {select} from '@xh/hoist/desktop/cmp/input';
|
|
22
12
|
import {panel} from '@xh/hoist/desktop/cmp/panel';
|
|
23
13
|
import '@xh/hoist/desktop/register';
|
|
24
|
-
import {toolbar} from '@xh/hoist/desktop/cmp/toolbar';
|
|
25
14
|
import {Icon} from '@xh/hoist/icon';
|
|
26
|
-
import {menu, menuItem, popover} from '@xh/hoist/kit/blueprint';
|
|
15
|
+
import {menu, menuDivider, menuItem, popover} from '@xh/hoist/kit/blueprint';
|
|
27
16
|
import {dragDropContext, draggable, droppable} from '@xh/hoist/kit/react-beautiful-dnd';
|
|
28
|
-
import {
|
|
17
|
+
import {elemWithin, getTestId, TEST_ID} from '@xh/hoist/utils/js';
|
|
29
18
|
import {splitLayoutProps} from '@xh/hoist/utils/react';
|
|
19
|
+
import {ReactElement} from 'react';
|
|
30
20
|
import classNames from 'classnames';
|
|
31
|
-
import {compact, isEmpty,
|
|
21
|
+
import {compact, isEmpty, sortBy} from 'lodash';
|
|
32
22
|
import './GroupingChooser.scss';
|
|
33
|
-
import {ReactNode} from 'react';
|
|
34
23
|
|
|
35
24
|
export interface GroupingChooserProps extends ButtonProps<GroupingChooserModel> {
|
|
36
|
-
/** Title for value-editing portion of popover, or null to suppress. */
|
|
37
|
-
editorTitle?: ReactNode;
|
|
38
|
-
|
|
39
25
|
/** Text to represent empty state (i.e. value = null or []) */
|
|
40
26
|
emptyText?: string;
|
|
41
27
|
|
|
42
|
-
/**
|
|
43
|
-
* Side of the popover, relative to the value-editing controls, on which the Favorites list
|
|
44
|
-
* should be rendered, if enabled.
|
|
45
|
-
*/
|
|
46
|
-
favoritesSide?: Side;
|
|
47
|
-
|
|
48
|
-
/** Title for favorites-list portion of popover, or null to suppress. */
|
|
49
|
-
favoritesTitle?: ReactNode;
|
|
50
|
-
|
|
51
28
|
/** Min height in pixels of the popover menu itself. */
|
|
52
29
|
popoverMinHeight?: number;
|
|
53
30
|
|
|
54
31
|
/** Position of popover relative to target button. */
|
|
55
32
|
popoverPosition?: 'bottom' | 'top';
|
|
56
33
|
|
|
57
|
-
/**
|
|
58
|
-
popoverTitle?:
|
|
34
|
+
/** Title for popover (default "GROUP BY") or null to suppress. */
|
|
35
|
+
popoverTitle?: string;
|
|
59
36
|
|
|
60
|
-
/**
|
|
61
|
-
* Width in pixels of the popover menu itself.
|
|
62
|
-
* If unspecified, will default based on favorites enabled status + side.
|
|
63
|
-
*/
|
|
37
|
+
/** Width in pixels of the popover menu itself. */
|
|
64
38
|
popoverWidth?: number;
|
|
65
39
|
|
|
66
40
|
/** True (default) to style target button as an input field - blends better in toolbars. */
|
|
67
41
|
styleButtonAsInput?: boolean;
|
|
42
|
+
|
|
43
|
+
/** Icon clicked to launch favorites menu. Defaults to Icon.favorite() */
|
|
44
|
+
favoritesIcon?: ReactElement;
|
|
68
45
|
}
|
|
69
46
|
|
|
70
47
|
/**
|
|
@@ -82,35 +59,25 @@ export const [GroupingChooser, groupingChooser] = hoistCmp.withFactory<GroupingC
|
|
|
82
59
|
{
|
|
83
60
|
model,
|
|
84
61
|
className,
|
|
85
|
-
editorTitle = 'Group By',
|
|
86
62
|
emptyText = 'Ungrouped',
|
|
87
|
-
|
|
88
|
-
favoritesTitle = 'Favorites',
|
|
89
|
-
popoverWidth,
|
|
63
|
+
popoverWidth = 250,
|
|
90
64
|
popoverMinHeight,
|
|
91
|
-
popoverTitle,
|
|
65
|
+
popoverTitle = 'Group By',
|
|
92
66
|
popoverPosition = 'bottom',
|
|
93
67
|
styleButtonAsInput = true,
|
|
94
68
|
testId,
|
|
69
|
+
favoritesIcon,
|
|
95
70
|
...rest
|
|
96
71
|
},
|
|
97
72
|
ref
|
|
98
73
|
) {
|
|
99
|
-
const {editorIsOpen, value, allowEmpty
|
|
100
|
-
isOpen = editorIsOpen,
|
|
74
|
+
const {editorIsOpen, favoritesIsOpen, persistFavorites, value, allowEmpty} = model,
|
|
75
|
+
isOpen = editorIsOpen || favoritesIsOpen,
|
|
101
76
|
label = isEmpty(value) && allowEmpty ? emptyText : model.getValueLabel(value),
|
|
102
77
|
[layoutProps, buttonProps] = splitLayoutProps(rest),
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
if (!isUndefined(popoverTitle)) {
|
|
107
|
-
apiDeprecated('GroupingChooser.popoverTitle', {
|
|
108
|
-
msg: `Update to use 'editorTitle' instead`
|
|
109
|
-
});
|
|
110
|
-
editorTitle = popoverTitle;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
popoverWidth = popoverWidth || (persistFavorites && !favesTB ? 500 : 250);
|
|
78
|
+
favoritesMenuTestId = getTestId(testId, 'favorites-menu'),
|
|
79
|
+
favoritesIconTestId = getTestId(testId, 'favorites-icon'),
|
|
80
|
+
editorTestId = getTestId(testId, 'editor');
|
|
114
81
|
|
|
115
82
|
return box({
|
|
116
83
|
ref,
|
|
@@ -119,10 +86,11 @@ export const [GroupingChooser, groupingChooser] = hoistCmp.withFactory<GroupingC
|
|
|
119
86
|
item: popover({
|
|
120
87
|
isOpen,
|
|
121
88
|
popoverRef: model.popoverRef,
|
|
122
|
-
popoverClassName:
|
|
123
|
-
//
|
|
124
|
-
|
|
125
|
-
|
|
89
|
+
popoverClassName: 'xh-grouping-chooser-popover xh-popup--framed',
|
|
90
|
+
// right align favorites popover to match star icon
|
|
91
|
+
// left align editor to keep in place when button changing size when commitOnChange: true
|
|
92
|
+
position: favoritesIsOpen ? `${popoverPosition}-right` : `${popoverPosition}-left`,
|
|
93
|
+
minimal: styleButtonAsInput,
|
|
126
94
|
item: fragment(
|
|
127
95
|
button({
|
|
128
96
|
text: label,
|
|
@@ -130,29 +98,34 @@ export const [GroupingChooser, groupingChooser] = hoistCmp.withFactory<GroupingC
|
|
|
130
98
|
tabIndex: -1,
|
|
131
99
|
className: classNames(
|
|
132
100
|
'xh-grouping-chooser-button',
|
|
133
|
-
styleButtonAsInput ? 'xh-grouping-chooser-button--as-input' : null
|
|
101
|
+
styleButtonAsInput ? 'xh-grouping-chooser-button--as-input' : null,
|
|
102
|
+
persistFavorites ? 'xh-grouping-chooser-button--with-favorites' : null
|
|
134
103
|
),
|
|
135
104
|
minimal: styleButtonAsInput,
|
|
136
105
|
...buttonProps,
|
|
137
106
|
onClick: () => model.toggleEditor(),
|
|
138
107
|
testId
|
|
139
|
-
})
|
|
108
|
+
}),
|
|
109
|
+
favoritesIconCmp({testId: favoritesIconTestId, favoritesIcon})
|
|
140
110
|
),
|
|
141
|
-
content:
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
111
|
+
content: favoritesIsOpen
|
|
112
|
+
? favoritesMenu({testId: favoritesMenuTestId})
|
|
113
|
+
: editor({
|
|
114
|
+
popoverWidth,
|
|
115
|
+
popoverMinHeight,
|
|
116
|
+
popoverTitle,
|
|
117
|
+
emptyText,
|
|
118
|
+
testId: editorTestId
|
|
119
|
+
}),
|
|
150
120
|
onInteraction: (nextOpenState, e) => {
|
|
151
121
|
if (
|
|
152
122
|
isOpen &&
|
|
153
123
|
nextOpenState === false &&
|
|
154
124
|
e?.target &&
|
|
155
|
-
!elemWithin(
|
|
125
|
+
!elemWithin(
|
|
126
|
+
e.target as HTMLElement,
|
|
127
|
+
'xh-grouping-chooser-button--with-favorites'
|
|
128
|
+
)
|
|
156
129
|
) {
|
|
157
130
|
model.commitPendingValueAndClose();
|
|
158
131
|
}
|
|
@@ -165,65 +138,18 @@ export const [GroupingChooser, groupingChooser] = hoistCmp.withFactory<GroupingC
|
|
|
165
138
|
//------------------
|
|
166
139
|
// Editor
|
|
167
140
|
//------------------
|
|
168
|
-
const
|
|
169
|
-
render({
|
|
170
|
-
model,
|
|
171
|
-
editorTitle,
|
|
172
|
-
emptyText,
|
|
173
|
-
favoritesSide,
|
|
174
|
-
favoritesTitle,
|
|
175
|
-
popoverWidth,
|
|
176
|
-
popoverMinHeight,
|
|
177
|
-
testId
|
|
178
|
-
}) {
|
|
179
|
-
const favesTB = isTB(favoritesSide),
|
|
180
|
-
isFavesFirst = favoritesSide === 'left' || favoritesSide === 'top',
|
|
181
|
-
items = [
|
|
182
|
-
editor({
|
|
183
|
-
editorTitle,
|
|
184
|
-
emptyText,
|
|
185
|
-
testId: getTestId(testId, 'editor')
|
|
186
|
-
}),
|
|
187
|
-
favoritesChooser({
|
|
188
|
-
// Omit if favorites generally disabled, or if none saved yet AND in top/bottom
|
|
189
|
-
// orientation - the empty state looks clumsy in that case. Show when empty in
|
|
190
|
-
// left/right orientation to avoid large jump in popover width.
|
|
191
|
-
omit: !model.persistFavorites || (!model.hasFavorites && favesTB),
|
|
192
|
-
favoritesSide,
|
|
193
|
-
favoritesTitle,
|
|
194
|
-
testId: getTestId(testId, 'favorites')
|
|
195
|
-
})
|
|
196
|
-
],
|
|
197
|
-
itemsContainer = favesTB ? vframe : hframe;
|
|
198
|
-
|
|
199
|
-
if (isFavesFirst) {
|
|
200
|
-
items.reverse();
|
|
201
|
-
}
|
|
202
|
-
|
|
141
|
+
const editor = hoistCmp.factory<GroupingChooserModel>({
|
|
142
|
+
render({popoverWidth, popoverMinHeight, popoverTitle, emptyText, testId}) {
|
|
203
143
|
return panel({
|
|
204
|
-
className: 'xh-grouping-chooser-popover__inner',
|
|
205
144
|
width: popoverWidth,
|
|
206
145
|
minHeight: popoverMinHeight,
|
|
207
|
-
items: itemsContainer({items}),
|
|
208
|
-
bbar: toolbar({
|
|
209
|
-
compact: true,
|
|
210
|
-
omit: !model.persistFavorites,
|
|
211
|
-
items: [filler(), favoritesAddBtn({testId})]
|
|
212
|
-
})
|
|
213
|
-
});
|
|
214
|
-
}
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
const editor = hoistCmp.factory<GroupingChooserModel>({
|
|
218
|
-
render({editorTitle, emptyText, testId}) {
|
|
219
|
-
return vbox({
|
|
220
|
-
className: 'xh-grouping-chooser__editor',
|
|
221
|
-
testId,
|
|
222
146
|
items: [
|
|
223
|
-
div({className: 'xh-popup__title', item:
|
|
147
|
+
div({className: 'xh-popup__title', item: popoverTitle, omit: !popoverTitle}),
|
|
224
148
|
dimensionList({emptyText}),
|
|
225
|
-
addDimensionControl()
|
|
226
|
-
|
|
149
|
+
addDimensionControl(),
|
|
150
|
+
filler()
|
|
151
|
+
],
|
|
152
|
+
testId
|
|
227
153
|
});
|
|
228
154
|
}
|
|
229
155
|
});
|
|
@@ -357,7 +283,7 @@ const addDimensionControl = hoistCmp.factory<GroupingChooserModel>({
|
|
|
357
283
|
// ensure the Select loses its internal input state.
|
|
358
284
|
key: JSON.stringify(options),
|
|
359
285
|
options,
|
|
360
|
-
placeholder: 'Add
|
|
286
|
+
placeholder: 'Add...',
|
|
361
287
|
flex: 1,
|
|
362
288
|
width: null,
|
|
363
289
|
hideDropdownIndicator: true,
|
|
@@ -394,25 +320,47 @@ function getDimOptions(dims, model) {
|
|
|
394
320
|
//------------------
|
|
395
321
|
// Favorites
|
|
396
322
|
//------------------
|
|
397
|
-
const
|
|
398
|
-
render({model,
|
|
399
|
-
|
|
323
|
+
const favoritesIconCmp = hoistCmp.factory<GroupingChooserModel>({
|
|
324
|
+
render({model, testId, favoritesIcon}) {
|
|
325
|
+
if (!model.persistFavorites) return null;
|
|
326
|
+
return div({
|
|
327
|
+
item: favoritesIcon ?? Icon.favorite(),
|
|
328
|
+
className: 'xh-grouping-chooser__favorite-icon',
|
|
329
|
+
[TEST_ID]: testId,
|
|
330
|
+
onClick: e => {
|
|
331
|
+
model.toggleFavoritesMenu();
|
|
332
|
+
e.stopPropagation();
|
|
333
|
+
}
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
const favoritesMenu = hoistCmp.factory<GroupingChooserModel>({
|
|
339
|
+
render({model, testId}) {
|
|
340
|
+
const options = model.favoritesOptions,
|
|
341
|
+
isFavorite = model.isFavorite(model.value),
|
|
342
|
+
omitAdd = isEmpty(model.value) || isFavorite,
|
|
343
|
+
items = [];
|
|
344
|
+
|
|
345
|
+
if (isEmpty(options)) {
|
|
346
|
+
items.push(menuItem({text: 'No favorites saved...', disabled: true}));
|
|
347
|
+
} else {
|
|
348
|
+
items.push(...options.map(it => favoriteMenuItem(it)));
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
items.push(
|
|
352
|
+
menuDivider({omit: omitAdd}),
|
|
353
|
+
menuItem({
|
|
354
|
+
icon: Icon.add({intent: 'success'}),
|
|
355
|
+
text: 'Add current',
|
|
356
|
+
omit: omitAdd,
|
|
357
|
+
onClick: () => model.addFavorite(model.value)
|
|
358
|
+
})
|
|
359
|
+
);
|
|
400
360
|
|
|
401
361
|
return vbox({
|
|
402
|
-
className: `xh-grouping-chooser__favorites xh-grouping-chooser__favorites--${favoritesSide}`,
|
|
403
362
|
testId,
|
|
404
|
-
items: [
|
|
405
|
-
div({
|
|
406
|
-
className: 'xh-popup__title',
|
|
407
|
-
item: favoritesTitle,
|
|
408
|
-
omit: isNil(favoritesTitle)
|
|
409
|
-
}),
|
|
410
|
-
hasFavorites
|
|
411
|
-
? menu({
|
|
412
|
-
items: options.map(it => favoriteMenuItem(it))
|
|
413
|
-
})
|
|
414
|
-
: placeholder('No favorites saved.')
|
|
415
|
-
]
|
|
363
|
+
items: [div({className: 'xh-popup__title', item: 'Favorites'}), menu({items})]
|
|
416
364
|
});
|
|
417
365
|
}
|
|
418
366
|
});
|
|
@@ -421,7 +369,7 @@ const favoriteMenuItem = hoistCmp.factory<GroupingChooserModel>({
|
|
|
421
369
|
render({model, value, label}) {
|
|
422
370
|
return menuItem({
|
|
423
371
|
text: label,
|
|
424
|
-
className: 'xh-grouping-
|
|
372
|
+
className: 'xh-grouping-chooser__favorite',
|
|
425
373
|
onClick: () => model.setValue(value),
|
|
426
374
|
labelElement: button({
|
|
427
375
|
icon: Icon.delete(),
|
|
@@ -434,19 +382,3 @@ const favoriteMenuItem = hoistCmp.factory<GroupingChooserModel>({
|
|
|
434
382
|
});
|
|
435
383
|
}
|
|
436
384
|
});
|
|
437
|
-
|
|
438
|
-
const favoritesAddBtn = hoistCmp.factory<GroupingChooserModel>({
|
|
439
|
-
render({model, testId}) {
|
|
440
|
-
return button({
|
|
441
|
-
text: 'Save as Favorite',
|
|
442
|
-
icon: Icon.favorite(),
|
|
443
|
-
className: 'xh-grouping-chooser__favorites__add-btn',
|
|
444
|
-
testId: getTestId(testId, 'favorites-add-btn'),
|
|
445
|
-
omit: !model.persistFavorites,
|
|
446
|
-
disabled: !model.isAddFavoriteEnabled,
|
|
447
|
-
onClick: () => model.addPendingAsFavorite()
|
|
448
|
-
});
|
|
449
|
-
}
|
|
450
|
-
});
|
|
451
|
-
|
|
452
|
-
const isTB = (favoritesSide: Side) => favoritesSide === 'top' || favoritesSide === 'bottom';
|
|
@@ -84,7 +84,7 @@ export interface PanelProps extends HoistProps<PanelModel>, Omit<BoxProps, 'titl
|
|
|
84
84
|
tbar?: Some<ReactNode>;
|
|
85
85
|
|
|
86
86
|
/**
|
|
87
|
-
* A toolbar to be docked at the
|
|
87
|
+
* A toolbar to be docked at the top of the panel.
|
|
88
88
|
* If specified as an array, items will be passed as children to a Toolbar component.
|
|
89
89
|
*/
|
|
90
90
|
bbar?: Some<ReactNode>;
|
|
@@ -7,14 +7,13 @@
|
|
|
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,
|
|
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 {useOnVisibleChange} from '@xh/hoist/utils/react';
|
|
16
16
|
import {startCase} from 'lodash';
|
|
17
|
-
import {ReactElement} from 'react';
|
|
18
17
|
import {viewMenu} from './ViewMenu';
|
|
19
18
|
import {ViewManagerLocalModel} from './ViewManagerLocalModel';
|
|
20
19
|
import {manageDialog} from './dialog/ManageDialog';
|
|
@@ -22,44 +21,28 @@ import {saveAsDialog} from './dialog/SaveAsDialog';
|
|
|
22
21
|
|
|
23
22
|
import './ViewManager.scss';
|
|
24
23
|
|
|
24
|
+
/**
|
|
25
|
+
* Visibility options for save/revert button.
|
|
26
|
+
*
|
|
27
|
+
* 'never' to hide button.
|
|
28
|
+
* 'whenDirty' to only show when persistence state is dirty and button is therefore enabled.
|
|
29
|
+
* 'always' will always show button.
|
|
30
|
+
*/
|
|
31
|
+
export type ViewManagerStateButtonMode = 'whenDirty' | 'always' | 'never';
|
|
32
|
+
|
|
25
33
|
export interface ViewManagerProps extends HoistProps<ViewManagerModel> {
|
|
26
34
|
menuButtonProps?: Partial<ButtonProps>;
|
|
27
35
|
saveButtonProps?: Partial<ButtonProps>;
|
|
28
36
|
revertButtonProps?: Partial<ButtonProps>;
|
|
29
37
|
|
|
30
|
-
/** Button icon when on the default (in-code state) view. Default `Icon.bookmark`. */
|
|
31
|
-
defaultViewIcon?: ReactElement;
|
|
32
|
-
/** Button icon when the selected view is owned by the current user. Default `Icon.bookmark`. */
|
|
33
|
-
ownedViewIcon?: ReactElement;
|
|
34
|
-
/** Button icon when the selected view is shared by another user. Default `Icon.users`. */
|
|
35
|
-
sharedViewIcon?: ReactElement;
|
|
36
|
-
/** Button icon when the selected view is globally shared. Default `Icon.globe`. */
|
|
37
|
-
globalViewIcon?: ReactElement;
|
|
38
|
-
|
|
39
38
|
/** Default 'whenDirty' */
|
|
40
39
|
showSaveButton?: ViewManagerStateButtonMode;
|
|
41
40
|
/** Default 'never' */
|
|
42
41
|
showRevertButton?: ViewManagerStateButtonMode;
|
|
43
|
-
/** Side
|
|
42
|
+
/** Side the save and revert buttons should appear on (default 'right') */
|
|
44
43
|
buttonSide?: 'left' | 'right';
|
|
45
|
-
/**
|
|
46
|
-
* Array of extra menu items. Can contain:
|
|
47
|
-
* + `MenuItems` or configs to create them.
|
|
48
|
-
* + `MenuDividers` or the special string token '-'.
|
|
49
|
-
* + React Elements or strings, which will be interpreted as the `text` property for a MenuItem.
|
|
50
|
-
*/
|
|
51
|
-
extraMenuItems?: MenuItemLike[];
|
|
52
44
|
}
|
|
53
45
|
|
|
54
|
-
/**
|
|
55
|
-
* Visibility options for save/revert buttons inlined next to the ViewManager menu:
|
|
56
|
-
* 'never' to always hide - user must save/revert via menu.
|
|
57
|
-
* 'whenDirty' (default) to show only when view state is dirty and the button is enabled.
|
|
58
|
-
* 'always' to always show, including when view not dirty and the button is disabled.
|
|
59
|
-
* Useful to avoid jumpiness in toolbar layouts.
|
|
60
|
-
*/
|
|
61
|
-
export type ViewManagerStateButtonMode = 'whenDirty' | 'always' | 'never';
|
|
62
|
-
|
|
63
46
|
/**
|
|
64
47
|
* Desktop ViewManager component - a button-based menu for saving and swapping between named
|
|
65
48
|
* bundles of persisted component state (e.g. grid views, dashboards, and similar).
|
|
@@ -77,14 +60,9 @@ export const [ViewManager, viewManager] = hoistCmp.withFactory<ViewManagerProps>
|
|
|
77
60
|
menuButtonProps,
|
|
78
61
|
saveButtonProps,
|
|
79
62
|
revertButtonProps,
|
|
80
|
-
defaultViewIcon = Icon.bookmark(),
|
|
81
|
-
ownedViewIcon = Icon.bookmark(),
|
|
82
|
-
sharedViewIcon = Icon.users(),
|
|
83
|
-
globalViewIcon = Icon.globe(),
|
|
84
63
|
showSaveButton = 'whenDirty',
|
|
85
64
|
showRevertButton = 'never',
|
|
86
|
-
buttonSide = 'right'
|
|
87
|
-
extraMenuItems = []
|
|
65
|
+
buttonSide = 'right'
|
|
88
66
|
}: ViewManagerProps) {
|
|
89
67
|
const {loadModel} = model,
|
|
90
68
|
locModel = useLocalModel(() => new ViewManagerLocalModel(model)),
|
|
@@ -92,17 +70,7 @@ export const [ViewManager, viewManager] = hoistCmp.withFactory<ViewManagerProps>
|
|
|
92
70
|
revert = revertButton({model: locModel, mode: showRevertButton, ...revertButtonProps}),
|
|
93
71
|
menu = popover({
|
|
94
72
|
disabled: !locModel.isVisible, // Prevent orphaned popover menu
|
|
95
|
-
item: menuButton({
|
|
96
|
-
model: locModel,
|
|
97
|
-
icon: buttonIcon({
|
|
98
|
-
model: locModel,
|
|
99
|
-
defaultViewIcon,
|
|
100
|
-
ownedViewIcon,
|
|
101
|
-
sharedViewIcon,
|
|
102
|
-
globalViewIcon
|
|
103
|
-
}),
|
|
104
|
-
...menuButtonProps
|
|
105
|
-
}),
|
|
73
|
+
item: menuButton({model: locModel, ...menuButtonProps}),
|
|
106
74
|
content: loadModel.isPending
|
|
107
75
|
? box({
|
|
108
76
|
item: spinner({compact: true}),
|
|
@@ -111,7 +79,7 @@ export const [ViewManager, viewManager] = hoistCmp.withFactory<ViewManagerProps>
|
|
|
111
79
|
height: 30,
|
|
112
80
|
width: 30
|
|
113
81
|
})
|
|
114
|
-
: viewMenu({model: locModel
|
|
82
|
+
: viewMenu({model: locModel}),
|
|
115
83
|
onOpening: () => model.refreshAsync(),
|
|
116
84
|
placement: 'bottom',
|
|
117
85
|
popoverClassName: 'xh-view-manager__popover'
|
|
@@ -129,13 +97,13 @@ export const [ViewManager, viewManager] = hoistCmp.withFactory<ViewManagerProps>
|
|
|
129
97
|
});
|
|
130
98
|
|
|
131
99
|
const menuButton = hoistCmp.factory<ViewManagerLocalModel>({
|
|
132
|
-
render({model,
|
|
100
|
+
render({model, ...rest}) {
|
|
133
101
|
const {view, typeDisplayName, isLoading} = model.parent;
|
|
134
102
|
return button({
|
|
135
103
|
className: 'xh-view-manager__menu-button',
|
|
136
104
|
text: view.isDefault ? `Default ${startCase(typeDisplayName)}` : view.name,
|
|
137
105
|
icon: !isLoading
|
|
138
|
-
?
|
|
106
|
+
? Icon.bookmark()
|
|
139
107
|
: box({
|
|
140
108
|
item: spinner({width: 13, height: 13, style: {margin: 'auto'}}),
|
|
141
109
|
width: 16.25
|
|
@@ -147,16 +115,6 @@ const menuButton = hoistCmp.factory<ViewManagerLocalModel>({
|
|
|
147
115
|
}
|
|
148
116
|
});
|
|
149
117
|
|
|
150
|
-
const buttonIcon = hoistCmp.factory<ViewManagerLocalModel>({
|
|
151
|
-
render({model, ownedViewIcon, sharedViewIcon, globalViewIcon, defaultViewIcon}) {
|
|
152
|
-
const {view} = model.parent;
|
|
153
|
-
if (view.isOwned) return ownedViewIcon;
|
|
154
|
-
if (view.isShared) return sharedViewIcon;
|
|
155
|
-
if (view.isGlobal) return globalViewIcon;
|
|
156
|
-
return defaultViewIcon;
|
|
157
|
-
}
|
|
158
|
-
});
|
|
159
|
-
|
|
160
118
|
const saveButton = hoistCmp.factory<ViewManagerLocalModel>({
|
|
161
119
|
render({model, mode, ...rest}) {
|
|
162
120
|
if (hideStateButton(model, mode)) return null;
|
|
@@ -11,7 +11,6 @@ import {switchInput} from '@xh/hoist/desktop/cmp/input';
|
|
|
11
11
|
import {Icon} from '@xh/hoist/icon';
|
|
12
12
|
import {menu, menuDivider, menuItem} from '@xh/hoist/kit/blueprint';
|
|
13
13
|
import {pluralize} from '@xh/hoist/utils/js';
|
|
14
|
-
import {filterConsecutiveMenuSeparators, parseMenuItems} from '@xh/hoist/utils/impl';
|
|
15
14
|
import {Dictionary} from 'express-serve-static-core';
|
|
16
15
|
import {each, filter, groupBy, isEmpty, isFunction, orderBy, some, startCase} from 'lodash';
|
|
17
16
|
import {ReactNode} from 'react';
|
|
@@ -21,16 +20,10 @@ import {ViewManagerLocalModel} from './ViewManagerLocalModel';
|
|
|
21
20
|
* Default Menu used by ViewManager.
|
|
22
21
|
*/
|
|
23
22
|
export const viewMenu = hoistCmp.factory<ViewManagerLocalModel>({
|
|
24
|
-
render({model
|
|
23
|
+
render({model}) {
|
|
25
24
|
return menu({
|
|
26
25
|
className: 'xh-view-manager__menu',
|
|
27
|
-
items: [
|
|
28
|
-
...getNavMenuItems(model.parent),
|
|
29
|
-
menuDivider(),
|
|
30
|
-
...parseMenuItems(extraMenuItems),
|
|
31
|
-
menuDivider(),
|
|
32
|
-
...getOtherMenuItems(model)
|
|
33
|
-
].filter(filterConsecutiveMenuSeparators())
|
|
26
|
+
items: [...getNavMenuItems(model.parent), menuDivider(), ...getOtherMenuItems(model)]
|
|
34
27
|
});
|
|
35
28
|
}
|
|
36
29
|
});
|
|
@@ -7,7 +7,6 @@
|
|
|
7
7
|
import {FeedbackDialogModel} from '@xh/hoist/appcontainer/FeedbackDialogModel';
|
|
8
8
|
import {filler} from '@xh/hoist/cmp/layout';
|
|
9
9
|
import {hoistCmp, uses} from '@xh/hoist/core';
|
|
10
|
-
import {Icon} from '@xh/hoist/icon';
|
|
11
10
|
import {button} from '@xh/hoist/mobile/cmp/button';
|
|
12
11
|
import {dialog} from '@xh/hoist/mobile/cmp/dialog';
|
|
13
12
|
import {textArea} from '@xh/hoist/mobile/cmp/input';
|
|
@@ -42,9 +41,6 @@ export const feedbackDialog = hoistCmp.factory({
|
|
|
42
41
|
}),
|
|
43
42
|
button({
|
|
44
43
|
text: 'Send',
|
|
45
|
-
icon: Icon.mail(),
|
|
46
|
-
intent: 'primary',
|
|
47
|
-
outlined: true,
|
|
48
44
|
onClick: () => model.submitAsync()
|
|
49
45
|
})
|
|
50
46
|
]
|
|
@@ -57,10 +57,8 @@ export const optionsDialog = hoistCmp.factory({
|
|
|
57
57
|
onClick: () => model.hide()
|
|
58
58
|
}),
|
|
59
59
|
button({
|
|
60
|
-
text: '
|
|
60
|
+
text: 'Save',
|
|
61
61
|
icon: reloadRequired ? Icon.refresh() : Icon.check(),
|
|
62
|
-
intent: 'primary',
|
|
63
|
-
outlined: true,
|
|
64
62
|
disabled: !formModel.isDirty,
|
|
65
63
|
onClick: () => model.saveAsync()
|
|
66
64
|
})
|
|
@@ -60,6 +60,7 @@ export const [ColChooser, colChooser] = hoistCmp.withFactory<ColChooserProps>({
|
|
|
60
60
|
onDragEnd: impl.onDragEnd,
|
|
61
61
|
items: [
|
|
62
62
|
panel({
|
|
63
|
+
title: 'Visible Columns',
|
|
63
64
|
className: 'xh-col-chooser__section',
|
|
64
65
|
scrollable: true,
|
|
65
66
|
items: [
|
|
@@ -118,10 +119,8 @@ export const [ColChooser, colChooser] = hoistCmp.withFactory<ColChooserProps>({
|
|
|
118
119
|
onClick: () => model.close()
|
|
119
120
|
}),
|
|
120
121
|
button({
|
|
121
|
-
text: '
|
|
122
|
+
text: 'Save',
|
|
122
123
|
icon: Icon.check(),
|
|
123
|
-
intent: 'primary',
|
|
124
|
-
outlined: true,
|
|
125
124
|
onClick: () => {
|
|
126
125
|
model.commit();
|
|
127
126
|
model.close();
|