@xh/hoist 71.0.0-SNAPSHOT.1733854822950 → 71.0.0-SNAPSHOT.1734551243081
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 +1 -0
- 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 +41 -31
- package/build/types/cmp/viewmanager/ViewToBlobApi.d.ts +28 -7
- 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 +11 -0
- package/build/types/desktop/cmp/viewmanager/ViewMenu.d.ts +2 -2
- package/build/types/desktop/cmp/viewmanager/dialog/ManageDialog.d.ts +3 -1
- package/build/types/desktop/cmp/viewmanager/dialog/ManageDialogModel.d.ts +18 -13
- 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 +2 -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 +109 -82
- package/cmp/viewmanager/ViewToBlobApi.ts +114 -42
- package/cmp/viewmanager/index.ts +1 -1
- package/desktop/cmp/viewmanager/ViewManager.scss +25 -28
- package/desktop/cmp/viewmanager/ViewManager.ts +32 -27
- package/desktop/cmp/viewmanager/ViewManagerLocalModel.ts +33 -0
- package/desktop/cmp/viewmanager/ViewMenu.ts +162 -169
- package/desktop/cmp/viewmanager/dialog/ManageDialog.ts +69 -42
- package/desktop/cmp/viewmanager/dialog/ManageDialogModel.ts +267 -150
- 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 +166 -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
|
@@ -5,33 +5,39 @@
|
|
|
5
5
|
* Copyright © 2024 Extremely Heavy Industries Inc.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
8
|
+
import {badge} from '@xh/hoist/cmp/badge';
|
|
9
|
+
import {dateTimeCol, GridAutosizeMode, GridModel} from '@xh/hoist/cmp/grid';
|
|
10
|
+
import {fragment, hbox, p, strong} from '@xh/hoist/cmp/layout';
|
|
10
11
|
import {TabContainerModel} from '@xh/hoist/cmp/tab';
|
|
11
|
-
import {
|
|
12
|
+
import {ViewInfo, ViewManagerModel, ViewUpdateSpec} from '@xh/hoist/cmp/viewmanager';
|
|
13
|
+
import {HoistModel, LoadSpec, managed, TaskObserver, XH} from '@xh/hoist/core';
|
|
12
14
|
import {FilterTestFn} from '@xh/hoist/data';
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {EditFormModel} from './EditFormModel';
|
|
15
|
+
import {button} from '@xh/hoist/desktop/cmp/button';
|
|
16
|
+
import {viewsGrid} from '@xh/hoist/desktop/cmp/viewmanager/dialog/ManageDialog';
|
|
16
17
|
import {Icon} from '@xh/hoist/icon';
|
|
17
|
-
import {bindable, makeObservable} from '@xh/hoist/mobx';
|
|
18
|
+
import {action, bindable, computed, makeObservable, observable, runInAction} from '@xh/hoist/mobx';
|
|
18
19
|
import {pluralize} from '@xh/hoist/utils/js';
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
20
|
+
import {capitalize, compact, isEmpty, some, startCase} from 'lodash';
|
|
21
|
+
import {ReactNode} from 'react';
|
|
22
|
+
import {ViewPanelModel} from './ViewPanelModel';
|
|
21
23
|
|
|
22
24
|
/**
|
|
23
25
|
* Backing model for ManageDialog
|
|
24
26
|
*/
|
|
25
27
|
export class ManageDialogModel extends HoistModel {
|
|
26
|
-
@lookup(() => ViewManagerModel)
|
|
27
28
|
viewManagerModel: ViewManagerModel;
|
|
28
29
|
|
|
29
|
-
@
|
|
30
|
+
@observable isOpen: boolean = false;
|
|
31
|
+
|
|
32
|
+
@managed ownedGridModel: GridModel;
|
|
30
33
|
@managed globalGridModel: GridModel;
|
|
31
|
-
@managed
|
|
34
|
+
@managed sharedGridModel: GridModel;
|
|
35
|
+
|
|
36
|
+
@managed viewPanelModel: ViewPanelModel;
|
|
37
|
+
|
|
32
38
|
@managed tabContainerModel: TabContainerModel;
|
|
33
39
|
|
|
34
|
-
@bindable filter: FilterTestFn;
|
|
40
|
+
@bindable.ref filter: FilterTestFn;
|
|
35
41
|
|
|
36
42
|
readonly updateTask = TaskObserver.trackLast();
|
|
37
43
|
|
|
@@ -40,98 +46,69 @@ export class ManageDialogModel extends HoistModel {
|
|
|
40
46
|
}
|
|
41
47
|
|
|
42
48
|
get gridModel(): GridModel {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
49
|
+
switch (this.tabContainerModel.activeTabId) {
|
|
50
|
+
case 'global':
|
|
51
|
+
return this.globalGridModel;
|
|
52
|
+
case 'shared':
|
|
53
|
+
return this.sharedGridModel;
|
|
54
|
+
case 'owned':
|
|
55
|
+
default:
|
|
56
|
+
return this.ownedGridModel;
|
|
57
|
+
}
|
|
46
58
|
}
|
|
47
59
|
|
|
48
60
|
@computed
|
|
49
61
|
get selectedView(): ViewInfo {
|
|
50
|
-
return this.gridModel.selectedRecord?.data.
|
|
62
|
+
return this.gridModel.selectedRecord?.data.view;
|
|
51
63
|
}
|
|
52
64
|
|
|
53
65
|
@computed
|
|
54
66
|
get selectedViews(): ViewInfo[] {
|
|
55
|
-
return this.gridModel.selectedRecords.map(it => it.data.
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
get canDelete(): boolean {
|
|
59
|
-
const {viewManagerModel, manageGlobal, selectedViews} = this,
|
|
60
|
-
{views, enableDefault} = viewManagerModel;
|
|
61
|
-
|
|
62
|
-
// Can't delete global views without role.
|
|
63
|
-
if (!manageGlobal && selectedViews.some(v => v.isGlobal)) return false;
|
|
64
|
-
|
|
65
|
-
// Can't delete all the views, unless default mode is enabled.
|
|
66
|
-
return enableDefault || views.length - selectedViews.length > 0;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
get manageGlobal(): boolean {
|
|
70
|
-
return this.viewManagerModel.manageGlobal;
|
|
67
|
+
return this.gridModel.selectedRecords.map(it => it.data.view) as ViewInfo[];
|
|
71
68
|
}
|
|
72
69
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
get globalDisplayName(): string {
|
|
78
|
-
return this.viewManagerModel.globalDisplayName;
|
|
70
|
+
constructor(viewManagerModel: ViewManagerModel) {
|
|
71
|
+
super();
|
|
72
|
+
makeObservable(this);
|
|
73
|
+
this.viewManagerModel = viewManagerModel;
|
|
79
74
|
}
|
|
80
75
|
|
|
81
|
-
|
|
82
|
-
|
|
76
|
+
@action
|
|
77
|
+
open() {
|
|
78
|
+
if (!this.tabContainerModel) this.init();
|
|
79
|
+
this.loadAsync();
|
|
80
|
+
this.isOpen = true;
|
|
83
81
|
}
|
|
84
82
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
83
|
+
@action
|
|
84
|
+
close() {
|
|
85
|
+
this.isOpen = false;
|
|
88
86
|
}
|
|
89
87
|
|
|
90
|
-
|
|
91
|
-
this.viewManagerModel.
|
|
88
|
+
activateSelectedViewAndClose() {
|
|
89
|
+
this.viewManagerModel.selectViewAsync(this.selectedView);
|
|
90
|
+
this.close();
|
|
92
91
|
}
|
|
93
92
|
|
|
94
|
-
override
|
|
95
|
-
|
|
93
|
+
override async doLoadAsync(loadSpec: LoadSpec) {
|
|
94
|
+
const {tabContainerModel} = this,
|
|
95
|
+
{enableGlobal, enableSharing, view, ownedViews, globalViews, sharedViews} =
|
|
96
|
+
this.viewManagerModel;
|
|
96
97
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
this.editFormModel = new EditFormModel(this);
|
|
98
|
+
runInAction(() => {
|
|
99
|
+
this.ownedGridModel.loadData(ownedViews);
|
|
100
|
+
tabContainerModel.setTabTitle('owned', this.ownedTabTitle);
|
|
101
101
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
track: () => this.selectedView,
|
|
106
|
-
run: r => editFormModel.setView(r)
|
|
107
|
-
},
|
|
108
|
-
{
|
|
109
|
-
track: () => this.filter,
|
|
110
|
-
run: f => {
|
|
111
|
-
privateGridModel.store.setFilter(f);
|
|
112
|
-
globalGridModel.store.setFilter(f);
|
|
113
|
-
},
|
|
114
|
-
fireImmediately: true
|
|
115
|
-
},
|
|
116
|
-
{
|
|
117
|
-
track: () => privateGridModel.selectedRecords,
|
|
118
|
-
run: recs => {
|
|
119
|
-
if (recs.length) globalGridModel.clearSelection();
|
|
120
|
-
}
|
|
121
|
-
},
|
|
122
|
-
{
|
|
123
|
-
track: () => globalGridModel.selectedRecords,
|
|
124
|
-
run: recs => {
|
|
125
|
-
if (recs.length) privateGridModel.clearSelection();
|
|
126
|
-
}
|
|
102
|
+
if (enableGlobal) {
|
|
103
|
+
this.globalGridModel.loadData(globalViews);
|
|
104
|
+
tabContainerModel.setTabTitle('global', this.globalTabTitle);
|
|
127
105
|
}
|
|
128
|
-
);
|
|
129
|
-
}
|
|
130
106
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
107
|
+
if (enableSharing) {
|
|
108
|
+
this.sharedGridModel.loadData(sharedViews);
|
|
109
|
+
tabContainerModel.setTabTitle('shared', this.sharedTabTitle);
|
|
110
|
+
}
|
|
111
|
+
});
|
|
135
112
|
if (!loadSpec.isRefresh && !view.isDefault) {
|
|
136
113
|
await this.selectViewAsync(view.info);
|
|
137
114
|
}
|
|
@@ -141,51 +118,87 @@ export class ManageDialogModel extends HoistModel {
|
|
|
141
118
|
return this.doDeleteAsync(views).linkTo(this.updateTask).catchDefault();
|
|
142
119
|
}
|
|
143
120
|
|
|
144
|
-
async updateAsync(view: ViewInfo,
|
|
145
|
-
return this.doUpdateAsync(view,
|
|
146
|
-
|
|
147
|
-
|
|
121
|
+
async updateAsync(view: ViewInfo, update: ViewUpdateSpec) {
|
|
122
|
+
return this.doUpdateAsync(view, update).linkTo(this.updateTask).catchDefault();
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async makeGlobalAsync(view: ViewInfo) {
|
|
126
|
+
return this.doMakeGlobalAsync(view).linkTo(this.updateTask).catchDefault();
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
@action
|
|
130
|
+
togglePinned(views: ViewInfo[]) {
|
|
131
|
+
views.forEach(v => this.viewManagerModel.togglePinned(v));
|
|
132
|
+
this.refreshAsync();
|
|
148
133
|
}
|
|
149
134
|
|
|
150
135
|
//------------------------
|
|
151
136
|
// Implementation
|
|
152
137
|
//------------------------
|
|
153
|
-
private
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
const
|
|
138
|
+
private init() {
|
|
139
|
+
const {enableGlobal, enableSharing} = this.viewManagerModel;
|
|
140
|
+
|
|
141
|
+
this.ownedGridModel = this.createGridModel('owned');
|
|
142
|
+
if (enableGlobal) this.globalGridModel = this.createGridModel('global');
|
|
143
|
+
if (enableSharing) this.sharedGridModel = this.createGridModel('shared');
|
|
144
|
+
const gridModels = compact([
|
|
145
|
+
this.ownedGridModel,
|
|
146
|
+
this.globalGridModel,
|
|
147
|
+
this.sharedGridModel
|
|
148
|
+
]);
|
|
149
|
+
|
|
150
|
+
this.tabContainerModel = this.createTabContainerModel();
|
|
151
|
+
this.viewPanelModel = new ViewPanelModel(this);
|
|
152
|
+
|
|
153
|
+
this.addReaction({
|
|
154
|
+
track: () => this.filter,
|
|
155
|
+
run: f => gridModels.forEach(m => m.store.setFilter(f)),
|
|
156
|
+
fireImmediately: true
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
// Only allow one selection at a time across all grids
|
|
160
|
+
if (gridModels.length > 1) {
|
|
161
|
+
gridModels.forEach(gm => {
|
|
162
|
+
this.addReaction({
|
|
163
|
+
track: () => gm.hasSelection,
|
|
164
|
+
run: hasSelection => {
|
|
165
|
+
gridModels.forEach(it => {
|
|
166
|
+
if (it != gm && hasSelection) it.clearSelection();
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
}
|
|
160
173
|
|
|
161
|
-
|
|
174
|
+
private async doUpdateAsync(view: ViewInfo, update: ViewUpdateSpec) {
|
|
175
|
+
const {viewManagerModel} = this;
|
|
176
|
+
await viewManagerModel.api.updateViewInfoAsync(view, update);
|
|
162
177
|
await viewManagerModel.refreshAsync();
|
|
163
178
|
await this.refreshAsync();
|
|
164
|
-
|
|
165
|
-
// reselect the updated copy of this view -- it may have moved.
|
|
166
|
-
await this.selectViewAsync(find(viewManagerModel.views, {token: view.token}));
|
|
167
179
|
}
|
|
168
180
|
|
|
169
181
|
private async doDeleteAsync(views: ViewInfo[]) {
|
|
170
|
-
const {viewManagerModel
|
|
171
|
-
{
|
|
182
|
+
const {viewManagerModel} = this,
|
|
183
|
+
{typeDisplayName} = viewManagerModel,
|
|
172
184
|
count = views.length;
|
|
173
185
|
|
|
174
186
|
if (!count) return;
|
|
175
187
|
|
|
176
|
-
if (viewManagerModel.views.length === count && !enableDefault) {
|
|
177
|
-
throw XH.exception({
|
|
178
|
-
message: `You cannot delete all ${pluralize(typeDisplayName)}.`,
|
|
179
|
-
isRoutine: true
|
|
180
|
-
});
|
|
181
|
-
}
|
|
182
|
-
|
|
183
188
|
const confirmStr = count > 1 ? pluralize(typeDisplayName, count, true) : views[0].typedName;
|
|
184
189
|
const msgs: ReactNode[] = [`Are you sure you want to delete ${confirmStr}?`];
|
|
185
|
-
if (some(views,
|
|
190
|
+
if (some(views, v => v.isGlobal || v.isShared)) {
|
|
186
191
|
count > 1
|
|
187
|
-
? msgs.push(
|
|
188
|
-
|
|
192
|
+
? msgs.push(
|
|
193
|
+
strong(
|
|
194
|
+
`This includes at least one public ${typeDisplayName}, to be deleted for all users.`
|
|
195
|
+
)
|
|
196
|
+
)
|
|
197
|
+
: msgs.push(
|
|
198
|
+
strong(
|
|
199
|
+
`This is a public ${typeDisplayName} and will be deleted for all users.`
|
|
200
|
+
)
|
|
201
|
+
);
|
|
189
202
|
}
|
|
190
203
|
|
|
191
204
|
const confirmed = await XH.confirm({
|
|
@@ -199,81 +212,185 @@ export class ManageDialogModel extends HoistModel {
|
|
|
199
212
|
});
|
|
200
213
|
if (!confirmed) return;
|
|
201
214
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
}
|
|
215
|
+
return viewManagerModel.deleteViewsAsync(views).finally(() => this.refreshAsync());
|
|
216
|
+
}
|
|
205
217
|
|
|
218
|
+
private async doMakeGlobalAsync(view: ViewInfo) {
|
|
219
|
+
const {globalDisplayName, typeDisplayName} = this.viewManagerModel,
|
|
220
|
+
{typedName} = view,
|
|
221
|
+
msgs = [
|
|
222
|
+
`The ${typedName} will become a ${globalDisplayName} ${typeDisplayName} visible to all other ${XH.appName} users.`,
|
|
223
|
+
strong('Are you sure you want to proceed?')
|
|
224
|
+
];
|
|
225
|
+
|
|
226
|
+
const confirmed = await XH.confirm({
|
|
227
|
+
message: fragment(msgs.map(m => p(m))),
|
|
228
|
+
confirmProps: {
|
|
229
|
+
text: `Yes, change visibility`,
|
|
230
|
+
outlined: true,
|
|
231
|
+
autoFocus: false,
|
|
232
|
+
intent: 'primary'
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
if (!confirmed) return;
|
|
236
|
+
|
|
237
|
+
const {viewManagerModel} = this;
|
|
238
|
+
const updated = await viewManagerModel.api.makeViewGlobalAsync(view);
|
|
206
239
|
await viewManagerModel.refreshAsync();
|
|
207
240
|
await this.refreshAsync();
|
|
241
|
+
await this.selectViewAsync(updated.info); // reselect -- will have moved tabs!
|
|
208
242
|
}
|
|
209
243
|
|
|
210
|
-
async selectViewAsync(view: ViewInfo) {
|
|
211
|
-
this.tabContainerModel.activateTab(
|
|
244
|
+
private async selectViewAsync(view: ViewInfo) {
|
|
245
|
+
this.tabContainerModel.activateTab(
|
|
246
|
+
view.isOwned ? 'owned' : view.isGlobal ? 'global' : 'shared'
|
|
247
|
+
);
|
|
212
248
|
await this.gridModel.selectAsync(view.token);
|
|
213
249
|
}
|
|
214
250
|
|
|
215
|
-
private createGridModel(
|
|
251
|
+
private createGridModel(type: 'owned' | 'global' | 'shared'): GridModel {
|
|
252
|
+
const {typeDisplayName, globalDisplayName} = this.viewManagerModel;
|
|
253
|
+
|
|
254
|
+
const modifier =
|
|
255
|
+
type == 'owned' ? `personal` : type == 'global' ? globalDisplayName : 'shared';
|
|
256
|
+
|
|
216
257
|
return new GridModel({
|
|
217
|
-
emptyText: `No ${
|
|
258
|
+
emptyText: `No ${modifier} ${pluralize(typeDisplayName)} found...`,
|
|
218
259
|
sortBy: 'name',
|
|
219
|
-
hideHeaders: true,
|
|
220
260
|
showGroupRowCounts: false,
|
|
261
|
+
groupBy: ['group'],
|
|
262
|
+
groupSortFn: (a, b) => {
|
|
263
|
+
// Place ungrouped items at bottom.
|
|
264
|
+
if (a == '') return 1;
|
|
265
|
+
if (b == '') return -1;
|
|
266
|
+
return a.localeCompare(b);
|
|
267
|
+
},
|
|
221
268
|
selModel: 'multiple',
|
|
222
269
|
contextMenu: null,
|
|
223
270
|
sizingMode: 'standard',
|
|
271
|
+
hideHeaders: true,
|
|
224
272
|
store: {
|
|
225
273
|
idSpec: 'token',
|
|
226
|
-
processRawData: v => ({
|
|
274
|
+
processRawData: v => ({
|
|
275
|
+
name: v.name,
|
|
276
|
+
group: v.isGlobal || v.isOwned ? v.group : v.owner,
|
|
277
|
+
owner: v.owner,
|
|
278
|
+
lastUpdated: v.lastUpdated,
|
|
279
|
+
isPinned: v.isPinned,
|
|
280
|
+
view: v
|
|
281
|
+
}),
|
|
227
282
|
fields: [
|
|
228
283
|
{name: 'name', type: 'string'},
|
|
229
|
-
{name: '
|
|
230
|
-
{name: '
|
|
284
|
+
{name: 'group', type: 'string'},
|
|
285
|
+
{name: 'owner', type: 'string'},
|
|
286
|
+
{name: 'lastUpdated', type: 'date'},
|
|
287
|
+
{name: 'isPinned', type: 'bool'},
|
|
288
|
+
{name: 'view', type: 'auto'}
|
|
231
289
|
]
|
|
232
290
|
},
|
|
233
291
|
autosizeOptions: {mode: GridAutosizeMode.DISABLED},
|
|
234
292
|
columns: [
|
|
235
293
|
{field: 'name', flex: true},
|
|
294
|
+
{field: 'group', hidden: true},
|
|
295
|
+
{field: 'owner', hidden: true},
|
|
296
|
+
{field: 'lastUpdated', ...dateTimeCol},
|
|
236
297
|
{
|
|
237
|
-
|
|
238
|
-
field: 'info',
|
|
239
|
-
omit: !this.enableFavorites,
|
|
298
|
+
field: 'isPinned',
|
|
240
299
|
width: 40,
|
|
241
300
|
align: 'center',
|
|
242
|
-
headerName: Icon.
|
|
243
|
-
|
|
244
|
-
renderer:
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
301
|
+
headerName: Icon.pin(),
|
|
302
|
+
headerTooltip: 'Pin to menu',
|
|
303
|
+
renderer: (isPinned, {record}) => {
|
|
304
|
+
return button({
|
|
305
|
+
icon: Icon.pin({
|
|
306
|
+
prefix: isPinned ? 'fas' : 'fal',
|
|
307
|
+
className: isPinned ? 'xh-yellow' : 'xh-text-color-muted'
|
|
308
|
+
}),
|
|
309
|
+
tooltip: isPinned ? 'Unpin from menu' : 'Pin to menu',
|
|
310
|
+
onClick: () => {
|
|
311
|
+
this.togglePinned([record.data.view]);
|
|
312
|
+
}
|
|
249
313
|
});
|
|
250
314
|
}
|
|
251
315
|
}
|
|
252
316
|
],
|
|
253
|
-
|
|
254
|
-
if (colDef.colId === 'isFavorite') {
|
|
255
|
-
this.viewManagerModel.toggleFavorite(record.id);
|
|
256
|
-
api.redrawRows();
|
|
257
|
-
}
|
|
258
|
-
}
|
|
317
|
+
groupRowRenderer: ({value}) => (isEmpty(value) ? 'Ungrouped' : value)
|
|
259
318
|
});
|
|
260
319
|
}
|
|
261
320
|
|
|
262
321
|
private createTabContainerModel(): TabContainerModel {
|
|
263
|
-
const
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
content: grid({model: this.privateGridModel})
|
|
270
|
-
},
|
|
322
|
+
const {enableGlobal, enableSharing, globalDisplayName, typeDisplayName} =
|
|
323
|
+
this.viewManagerModel,
|
|
324
|
+
view = typeDisplayName,
|
|
325
|
+
views = pluralize(view),
|
|
326
|
+
globalViews = `${globalDisplayName} ${views}`,
|
|
327
|
+
tabs = [
|
|
271
328
|
{
|
|
272
|
-
id: '
|
|
273
|
-
title:
|
|
274
|
-
content:
|
|
329
|
+
id: 'owned',
|
|
330
|
+
title: this.ownedTabTitle,
|
|
331
|
+
content: viewsGrid({
|
|
332
|
+
model: this.ownedGridModel,
|
|
333
|
+
helpText: fragment(
|
|
334
|
+
Icon.user(),
|
|
335
|
+
`This tab shows ${views} you have created. Pinned ${views} are shown in your menu for quick access. Set a group on ${views} to show them together in a sub-menu. `,
|
|
336
|
+
enableSharing
|
|
337
|
+
? `Opt-in to sharing any of your ${views} to make them discoverable by other users.`
|
|
338
|
+
: ''
|
|
339
|
+
)
|
|
340
|
+
})
|
|
275
341
|
}
|
|
276
|
-
]
|
|
277
|
-
|
|
342
|
+
];
|
|
343
|
+
|
|
344
|
+
if (enableGlobal) {
|
|
345
|
+
tabs.push({
|
|
346
|
+
id: 'global',
|
|
347
|
+
title: this.globalTabTitle,
|
|
348
|
+
content: viewsGrid({
|
|
349
|
+
model: this.globalGridModel,
|
|
350
|
+
helpText: fragment(
|
|
351
|
+
Icon.globe(),
|
|
352
|
+
`This tab shows ${globalViews} available to everyone. ${capitalize(globalViews)} can be pinned by default so they appear automatically in everyone's menu, but you can choose which ${views} you would like to see by pinning/unpinning them at any time.`
|
|
353
|
+
)
|
|
354
|
+
})
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
if (enableSharing) {
|
|
359
|
+
tabs.push({
|
|
360
|
+
id: 'shared',
|
|
361
|
+
title: this.sharedTabTitle,
|
|
362
|
+
content: viewsGrid({
|
|
363
|
+
model: this.sharedGridModel,
|
|
364
|
+
helpText: fragment(
|
|
365
|
+
Icon.users(),
|
|
366
|
+
`This tab shows ${views} shared by other ${XH.appName} users. You can pin these ${views} to add them to your menu and access them directly. Only the owner will be able to save changes to a shared ${view}, but you can save as a copy to make it your own.`
|
|
367
|
+
)
|
|
368
|
+
})
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
return new TabContainerModel({tabs});
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
private get ownedTabTitle(): ReactNode {
|
|
376
|
+
return hbox(
|
|
377
|
+
`My ${startCase(pluralize(this.viewManagerModel.typeDisplayName))}`,
|
|
378
|
+
badge(this.ownedGridModel.store.allCount)
|
|
379
|
+
);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
private get globalTabTitle(): ReactNode {
|
|
383
|
+
const {globalDisplayName, typeDisplayName} = this.viewManagerModel;
|
|
384
|
+
return hbox(
|
|
385
|
+
`${startCase(globalDisplayName)} ${startCase(pluralize(typeDisplayName))}`,
|
|
386
|
+
badge(this.globalGridModel.store.allCount)
|
|
387
|
+
);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
private get sharedTabTitle(): ReactNode {
|
|
391
|
+
return hbox(
|
|
392
|
+
`Shared ${startCase(pluralize(this.viewManagerModel.typeDisplayName))}`,
|
|
393
|
+
badge(this.sharedGridModel.store.allCount)
|
|
394
|
+
);
|
|
278
395
|
}
|
|
279
396
|
}
|
|
@@ -6,16 +6,17 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import {form} from '@xh/hoist/cmp/form';
|
|
9
|
-
import {filler, vframe} from '@xh/hoist/cmp/layout';
|
|
9
|
+
import {filler, hbox, vframe} from '@xh/hoist/cmp/layout';
|
|
10
10
|
import {hoistCmp, uses} from '@xh/hoist/core';
|
|
11
|
-
import {SaveAsDialogModel} from '@xh/hoist/cmp/viewmanager/';
|
|
12
11
|
import {button} from '@xh/hoist/desktop/cmp/button';
|
|
13
12
|
import {formField} from '@xh/hoist/desktop/cmp/form';
|
|
14
|
-
import {textArea, textInput} from '@xh/hoist/desktop/cmp/input';
|
|
13
|
+
import {select, switchInput, textArea, textInput} from '@xh/hoist/desktop/cmp/input';
|
|
15
14
|
import {panel} from '@xh/hoist/desktop/cmp/panel';
|
|
16
15
|
import {toolbar} from '@xh/hoist/desktop/cmp/toolbar';
|
|
17
16
|
import {dialog} from '@xh/hoist/kit/blueprint';
|
|
18
17
|
import {startCase} from 'lodash';
|
|
18
|
+
import {SaveAsDialogModel} from './SaveAsDialogModel';
|
|
19
|
+
import {getGroupOptions} from './Utils';
|
|
19
20
|
|
|
20
21
|
/**
|
|
21
22
|
* Default Save As dialog used by ViewManager.
|
|
@@ -34,7 +35,7 @@ export const saveAsDialog = hoistCmp.factory<SaveAsDialogModel>({
|
|
|
34
35
|
isOpen: true,
|
|
35
36
|
style: {width: 500},
|
|
36
37
|
canOutsideClickClose: false,
|
|
37
|
-
onClose: () => model.
|
|
38
|
+
onClose: () => model.close(),
|
|
38
39
|
item: formPanel()
|
|
39
40
|
});
|
|
40
41
|
}
|
|
@@ -45,7 +46,9 @@ const formPanel = hoistCmp.factory<SaveAsDialogModel>({
|
|
|
45
46
|
return panel({
|
|
46
47
|
item: form({
|
|
47
48
|
fieldDefaults: {
|
|
48
|
-
commitOnChange: true
|
|
49
|
+
commitOnChange: true,
|
|
50
|
+
inline: true,
|
|
51
|
+
minimal: true
|
|
49
52
|
},
|
|
50
53
|
item: vframe({
|
|
51
54
|
className: 'xh-view-manager__save-dialog__form',
|
|
@@ -60,13 +63,31 @@ const formPanel = hoistCmp.factory<SaveAsDialogModel>({
|
|
|
60
63
|
}
|
|
61
64
|
})
|
|
62
65
|
}),
|
|
66
|
+
formField({
|
|
67
|
+
field: 'group',
|
|
68
|
+
item: select({
|
|
69
|
+
enableCreate: true,
|
|
70
|
+
enableClear: true,
|
|
71
|
+
placeholder: 'Select optional group....',
|
|
72
|
+
options: getGroupOptions(model.parent, 'owned')
|
|
73
|
+
})
|
|
74
|
+
}),
|
|
63
75
|
formField({
|
|
64
76
|
field: 'description',
|
|
65
77
|
item: textArea({
|
|
66
78
|
selectOnFocus: true,
|
|
67
|
-
height:
|
|
79
|
+
height: 70
|
|
80
|
+
})
|
|
81
|
+
}),
|
|
82
|
+
hbox(
|
|
83
|
+
formField({
|
|
84
|
+
field: 'isShared',
|
|
85
|
+
label: 'Share?',
|
|
86
|
+
labelTextAlign: 'left',
|
|
87
|
+
omit: !model.parent.enableSharing,
|
|
88
|
+
item: switchInput()
|
|
68
89
|
})
|
|
69
|
-
|
|
90
|
+
)
|
|
70
91
|
]
|
|
71
92
|
})
|
|
72
93
|
}),
|
|
@@ -78,12 +99,12 @@ const formPanel = hoistCmp.factory<SaveAsDialogModel>({
|
|
|
78
99
|
|
|
79
100
|
const bbar = hoistCmp.factory<SaveAsDialogModel>({
|
|
80
101
|
render({model}) {
|
|
81
|
-
const {typeDisplayName} = model;
|
|
102
|
+
const {typeDisplayName} = model.parent;
|
|
82
103
|
return toolbar(
|
|
83
104
|
filler(),
|
|
84
105
|
button({
|
|
85
106
|
text: 'Cancel',
|
|
86
|
-
onClick: () => model.
|
|
107
|
+
onClick: () => model.close()
|
|
87
108
|
}),
|
|
88
109
|
button({
|
|
89
110
|
text: `Save as new ${startCase(typeDisplayName)}`,
|