@xh/hoist 74.1.2 → 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 -41
- 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 +40 -45
- package/desktop/cmp/grouping/GroupingChooser.ts +89 -159
- 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,67 +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
|
-
frame,
|
|
14
|
-
hbox,
|
|
15
|
-
hframe,
|
|
16
|
-
placeholder,
|
|
17
|
-
vbox,
|
|
18
|
-
vframe
|
|
19
|
-
} from '@xh/hoist/cmp/layout';
|
|
20
|
-
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';
|
|
21
10
|
import {button, ButtonProps} from '@xh/hoist/desktop/cmp/button';
|
|
22
11
|
import {select} from '@xh/hoist/desktop/cmp/input';
|
|
23
12
|
import {panel} from '@xh/hoist/desktop/cmp/panel';
|
|
24
13
|
import '@xh/hoist/desktop/register';
|
|
25
|
-
import {toolbar} from '@xh/hoist/desktop/cmp/toolbar';
|
|
26
14
|
import {Icon} from '@xh/hoist/icon';
|
|
27
|
-
import {menu, menuItem, popover} from '@xh/hoist/kit/blueprint';
|
|
15
|
+
import {menu, menuDivider, menuItem, popover} from '@xh/hoist/kit/blueprint';
|
|
28
16
|
import {dragDropContext, draggable, droppable} from '@xh/hoist/kit/react-beautiful-dnd';
|
|
29
|
-
import {
|
|
17
|
+
import {elemWithin, getTestId, TEST_ID} from '@xh/hoist/utils/js';
|
|
30
18
|
import {splitLayoutProps} from '@xh/hoist/utils/react';
|
|
19
|
+
import {ReactElement} from 'react';
|
|
31
20
|
import classNames from 'classnames';
|
|
32
|
-
import {compact, isEmpty,
|
|
21
|
+
import {compact, isEmpty, sortBy} from 'lodash';
|
|
33
22
|
import './GroupingChooser.scss';
|
|
34
|
-
import {ReactNode} from 'react';
|
|
35
23
|
|
|
36
24
|
export interface GroupingChooserProps extends ButtonProps<GroupingChooserModel> {
|
|
37
|
-
/** Title for value-editing portion of popover, or null to suppress. */
|
|
38
|
-
editorTitle?: ReactNode;
|
|
39
|
-
|
|
40
25
|
/** Text to represent empty state (i.e. value = null or []) */
|
|
41
26
|
emptyText?: string;
|
|
42
27
|
|
|
43
|
-
/**
|
|
44
|
-
* Side of the popover, relative to the value-editing controls, on which the Favorites list
|
|
45
|
-
* should be rendered, if enabled.
|
|
46
|
-
*/
|
|
47
|
-
favoritesSide?: Side;
|
|
48
|
-
|
|
49
|
-
/** Title for favorites-list portion of popover, or null to suppress. */
|
|
50
|
-
favoritesTitle?: ReactNode;
|
|
51
|
-
|
|
52
28
|
/** Min height in pixels of the popover menu itself. */
|
|
53
29
|
popoverMinHeight?: number;
|
|
54
30
|
|
|
55
31
|
/** Position of popover relative to target button. */
|
|
56
32
|
popoverPosition?: 'bottom' | 'top';
|
|
57
33
|
|
|
58
|
-
/**
|
|
59
|
-
popoverTitle?:
|
|
34
|
+
/** Title for popover (default "GROUP BY") or null to suppress. */
|
|
35
|
+
popoverTitle?: string;
|
|
60
36
|
|
|
61
|
-
/**
|
|
62
|
-
* Width in pixels of the popover menu itself.
|
|
63
|
-
* If unspecified, will default based on favorites enabled status + side.
|
|
64
|
-
*/
|
|
37
|
+
/** Width in pixels of the popover menu itself. */
|
|
65
38
|
popoverWidth?: number;
|
|
66
39
|
|
|
67
40
|
/** True (default) to style target button as an input field - blends better in toolbars. */
|
|
68
41
|
styleButtonAsInput?: boolean;
|
|
42
|
+
|
|
43
|
+
/** Icon clicked to launch favorites menu. Defaults to Icon.favorite() */
|
|
44
|
+
favoritesIcon?: ReactElement;
|
|
69
45
|
}
|
|
70
46
|
|
|
71
47
|
/**
|
|
@@ -83,35 +59,25 @@ export const [GroupingChooser, groupingChooser] = hoistCmp.withFactory<GroupingC
|
|
|
83
59
|
{
|
|
84
60
|
model,
|
|
85
61
|
className,
|
|
86
|
-
editorTitle = 'Group By',
|
|
87
62
|
emptyText = 'Ungrouped',
|
|
88
|
-
|
|
89
|
-
favoritesTitle = 'Favorites',
|
|
90
|
-
popoverWidth,
|
|
63
|
+
popoverWidth = 250,
|
|
91
64
|
popoverMinHeight,
|
|
92
|
-
popoverTitle,
|
|
65
|
+
popoverTitle = 'Group By',
|
|
93
66
|
popoverPosition = 'bottom',
|
|
94
67
|
styleButtonAsInput = true,
|
|
95
68
|
testId,
|
|
69
|
+
favoritesIcon,
|
|
96
70
|
...rest
|
|
97
71
|
},
|
|
98
72
|
ref
|
|
99
73
|
) {
|
|
100
|
-
const {editorIsOpen, value, allowEmpty
|
|
101
|
-
isOpen = editorIsOpen,
|
|
74
|
+
const {editorIsOpen, favoritesIsOpen, persistFavorites, value, allowEmpty} = model,
|
|
75
|
+
isOpen = editorIsOpen || favoritesIsOpen,
|
|
102
76
|
label = isEmpty(value) && allowEmpty ? emptyText : model.getValueLabel(value),
|
|
103
77
|
[layoutProps, buttonProps] = splitLayoutProps(rest),
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
if (!isUndefined(popoverTitle)) {
|
|
108
|
-
apiDeprecated('GroupingChooser.popoverTitle', {
|
|
109
|
-
msg: `Update to use 'editorTitle' instead`
|
|
110
|
-
});
|
|
111
|
-
editorTitle = popoverTitle;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
popoverWidth = popoverWidth || (persistFavorites && !favesTB ? 500 : 250);
|
|
78
|
+
favoritesMenuTestId = getTestId(testId, 'favorites-menu'),
|
|
79
|
+
favoritesIconTestId = getTestId(testId, 'favorites-icon'),
|
|
80
|
+
editorTestId = getTestId(testId, 'editor');
|
|
115
81
|
|
|
116
82
|
return box({
|
|
117
83
|
ref,
|
|
@@ -120,10 +86,11 @@ export const [GroupingChooser, groupingChooser] = hoistCmp.withFactory<GroupingC
|
|
|
120
86
|
item: popover({
|
|
121
87
|
isOpen,
|
|
122
88
|
popoverRef: model.popoverRef,
|
|
123
|
-
popoverClassName:
|
|
124
|
-
//
|
|
125
|
-
|
|
126
|
-
|
|
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,
|
|
127
94
|
item: fragment(
|
|
128
95
|
button({
|
|
129
96
|
text: label,
|
|
@@ -131,29 +98,34 @@ export const [GroupingChooser, groupingChooser] = hoistCmp.withFactory<GroupingC
|
|
|
131
98
|
tabIndex: -1,
|
|
132
99
|
className: classNames(
|
|
133
100
|
'xh-grouping-chooser-button',
|
|
134
|
-
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
|
|
135
103
|
),
|
|
136
104
|
minimal: styleButtonAsInput,
|
|
137
105
|
...buttonProps,
|
|
138
106
|
onClick: () => model.toggleEditor(),
|
|
139
107
|
testId
|
|
140
|
-
})
|
|
108
|
+
}),
|
|
109
|
+
favoritesIconCmp({testId: favoritesIconTestId, favoritesIcon})
|
|
141
110
|
),
|
|
142
|
-
content:
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
111
|
+
content: favoritesIsOpen
|
|
112
|
+
? favoritesMenu({testId: favoritesMenuTestId})
|
|
113
|
+
: editor({
|
|
114
|
+
popoverWidth,
|
|
115
|
+
popoverMinHeight,
|
|
116
|
+
popoverTitle,
|
|
117
|
+
emptyText,
|
|
118
|
+
testId: editorTestId
|
|
119
|
+
}),
|
|
151
120
|
onInteraction: (nextOpenState, e) => {
|
|
152
121
|
if (
|
|
153
122
|
isOpen &&
|
|
154
123
|
nextOpenState === false &&
|
|
155
124
|
e?.target &&
|
|
156
|
-
!elemWithin(
|
|
125
|
+
!elemWithin(
|
|
126
|
+
e.target as HTMLElement,
|
|
127
|
+
'xh-grouping-chooser-button--with-favorites'
|
|
128
|
+
)
|
|
157
129
|
) {
|
|
158
130
|
model.commitPendingValueAndClose();
|
|
159
131
|
}
|
|
@@ -166,66 +138,18 @@ export const [GroupingChooser, groupingChooser] = hoistCmp.withFactory<GroupingC
|
|
|
166
138
|
//------------------
|
|
167
139
|
// Editor
|
|
168
140
|
//------------------
|
|
169
|
-
const
|
|
170
|
-
render({
|
|
171
|
-
model,
|
|
172
|
-
editorTitle,
|
|
173
|
-
emptyText,
|
|
174
|
-
favoritesSide,
|
|
175
|
-
favoritesTitle,
|
|
176
|
-
popoverWidth,
|
|
177
|
-
popoverMinHeight,
|
|
178
|
-
testId
|
|
179
|
-
}) {
|
|
180
|
-
const {persistFavorites} = model,
|
|
181
|
-
favesTB = isTB(favoritesSide),
|
|
182
|
-
isFavesFirst = favoritesSide === 'left' || favoritesSide === 'top',
|
|
183
|
-
items = [
|
|
184
|
-
editor({
|
|
185
|
-
editorTitle,
|
|
186
|
-
emptyText,
|
|
187
|
-
testId: getTestId(testId, 'editor')
|
|
188
|
-
}),
|
|
189
|
-
favoritesChooser({
|
|
190
|
-
// Omit if favorites generally disabled, or if none saved yet AND in top/bottom
|
|
191
|
-
// orientation - the empty state looks clumsy in that case. Show when empty in
|
|
192
|
-
// left/right orientation to avoid large jump in popover width.
|
|
193
|
-
omit: !model.persistFavorites || (!model.hasFavorites && favesTB),
|
|
194
|
-
favoritesSide,
|
|
195
|
-
favoritesTitle,
|
|
196
|
-
testId: getTestId(testId, 'favorites')
|
|
197
|
-
})
|
|
198
|
-
],
|
|
199
|
-
itemsContainer = !persistFavorites ? frame : favesTB ? vframe : hframe;
|
|
200
|
-
|
|
201
|
-
if (isFavesFirst) {
|
|
202
|
-
items.reverse();
|
|
203
|
-
}
|
|
204
|
-
|
|
141
|
+
const editor = hoistCmp.factory<GroupingChooserModel>({
|
|
142
|
+
render({popoverWidth, popoverMinHeight, popoverTitle, emptyText, testId}) {
|
|
205
143
|
return panel({
|
|
206
|
-
className: 'xh-grouping-chooser-popover__inner',
|
|
207
144
|
width: popoverWidth,
|
|
208
145
|
minHeight: popoverMinHeight,
|
|
209
|
-
items: itemsContainer({items}),
|
|
210
|
-
bbar: toolbar({
|
|
211
|
-
compact: true,
|
|
212
|
-
omit: !model.persistFavorites,
|
|
213
|
-
items: [filler(), favoritesAddBtn({testId})]
|
|
214
|
-
})
|
|
215
|
-
});
|
|
216
|
-
}
|
|
217
|
-
});
|
|
218
|
-
|
|
219
|
-
const editor = hoistCmp.factory<GroupingChooserModel>({
|
|
220
|
-
render({editorTitle, emptyText, testId}) {
|
|
221
|
-
return vbox({
|
|
222
|
-
className: 'xh-grouping-chooser__editor',
|
|
223
|
-
testId,
|
|
224
146
|
items: [
|
|
225
|
-
div({className: 'xh-popup__title', item:
|
|
147
|
+
div({className: 'xh-popup__title', item: popoverTitle, omit: !popoverTitle}),
|
|
226
148
|
dimensionList({emptyText}),
|
|
227
|
-
addDimensionControl()
|
|
228
|
-
|
|
149
|
+
addDimensionControl(),
|
|
150
|
+
filler()
|
|
151
|
+
],
|
|
152
|
+
testId
|
|
229
153
|
});
|
|
230
154
|
}
|
|
231
155
|
});
|
|
@@ -359,7 +283,7 @@ const addDimensionControl = hoistCmp.factory<GroupingChooserModel>({
|
|
|
359
283
|
// ensure the Select loses its internal input state.
|
|
360
284
|
key: JSON.stringify(options),
|
|
361
285
|
options,
|
|
362
|
-
placeholder: 'Add
|
|
286
|
+
placeholder: 'Add...',
|
|
363
287
|
flex: 1,
|
|
364
288
|
width: null,
|
|
365
289
|
hideDropdownIndicator: true,
|
|
@@ -396,25 +320,47 @@ function getDimOptions(dims, model) {
|
|
|
396
320
|
//------------------
|
|
397
321
|
// Favorites
|
|
398
322
|
//------------------
|
|
399
|
-
const
|
|
400
|
-
render({model,
|
|
401
|
-
|
|
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
|
+
);
|
|
402
360
|
|
|
403
361
|
return vbox({
|
|
404
|
-
className: `xh-grouping-chooser__favorites xh-grouping-chooser__favorites--${favoritesSide}`,
|
|
405
362
|
testId,
|
|
406
|
-
items: [
|
|
407
|
-
div({
|
|
408
|
-
className: 'xh-popup__title',
|
|
409
|
-
item: favoritesTitle,
|
|
410
|
-
omit: isNil(favoritesTitle)
|
|
411
|
-
}),
|
|
412
|
-
hasFavorites
|
|
413
|
-
? menu({
|
|
414
|
-
items: options.map(it => favoriteMenuItem(it))
|
|
415
|
-
})
|
|
416
|
-
: placeholder('No favorites saved.')
|
|
417
|
-
]
|
|
363
|
+
items: [div({className: 'xh-popup__title', item: 'Favorites'}), menu({items})]
|
|
418
364
|
});
|
|
419
365
|
}
|
|
420
366
|
});
|
|
@@ -423,7 +369,7 @@ const favoriteMenuItem = hoistCmp.factory<GroupingChooserModel>({
|
|
|
423
369
|
render({model, value, label}) {
|
|
424
370
|
return menuItem({
|
|
425
371
|
text: label,
|
|
426
|
-
className: 'xh-grouping-
|
|
372
|
+
className: 'xh-grouping-chooser__favorite',
|
|
427
373
|
onClick: () => model.setValue(value),
|
|
428
374
|
labelElement: button({
|
|
429
375
|
icon: Icon.delete(),
|
|
@@ -436,19 +382,3 @@ const favoriteMenuItem = hoistCmp.factory<GroupingChooserModel>({
|
|
|
436
382
|
});
|
|
437
383
|
}
|
|
438
384
|
});
|
|
439
|
-
|
|
440
|
-
const favoritesAddBtn = hoistCmp.factory<GroupingChooserModel>({
|
|
441
|
-
render({model, testId}) {
|
|
442
|
-
return button({
|
|
443
|
-
text: 'Save as Favorite',
|
|
444
|
-
icon: Icon.favorite(),
|
|
445
|
-
className: 'xh-grouping-chooser__favorites__add-btn',
|
|
446
|
-
testId: getTestId(testId, 'favorites-add-btn'),
|
|
447
|
-
omit: !model.persistFavorites,
|
|
448
|
-
disabled: !model.isAddFavoriteEnabled,
|
|
449
|
-
onClick: () => model.addPendingAsFavorite()
|
|
450
|
-
});
|
|
451
|
-
}
|
|
452
|
-
});
|
|
453
|
-
|
|
454
|
-
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();
|