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