@xh/hoist 71.0.0-SNAPSHOT.1733791818708 → 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/dash/container/DashContainerModel.ts +17 -5
- 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
|
@@ -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
|
});
|
|
@@ -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, 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,30 +46,25 @@ 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
|
+
return this.gridModel.selectedRecords.map(it => it.data.view) as ViewInfo[];
|
|
67
68
|
}
|
|
68
69
|
|
|
69
70
|
get manageGlobal(): boolean {
|
|
@@ -78,60 +79,45 @@ export class ManageDialogModel extends HoistModel {
|
|
|
78
79
|
return this.viewManagerModel.globalDisplayName;
|
|
79
80
|
}
|
|
80
81
|
|
|
81
|
-
get
|
|
82
|
-
return this.viewManagerModel.
|
|
82
|
+
get enableSharing(): boolean {
|
|
83
|
+
return this.viewManagerModel.enableSharing;
|
|
83
84
|
}
|
|
84
85
|
|
|
85
|
-
constructor() {
|
|
86
|
+
constructor(viewManagerModel: ViewManagerModel) {
|
|
86
87
|
super();
|
|
87
88
|
makeObservable(this);
|
|
89
|
+
this.viewManagerModel = viewManagerModel;
|
|
88
90
|
}
|
|
89
91
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
+
@action
|
|
93
|
+
open() {
|
|
94
|
+
if (!this.tabContainerModel) this.init();
|
|
95
|
+
this.loadAsync();
|
|
96
|
+
this.isOpen = true;
|
|
92
97
|
}
|
|
93
98
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
this.globalGridModel = this.createGridModel(this.globalDisplayName);
|
|
99
|
-
this.tabContainerModel = this.createTabContainerModel();
|
|
100
|
-
this.editFormModel = new EditFormModel(this);
|
|
99
|
+
@action
|
|
100
|
+
close() {
|
|
101
|
+
this.isOpen = false;
|
|
102
|
+
}
|
|
101
103
|
|
|
102
|
-
|
|
103
|
-
this.
|
|
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
|
-
}
|
|
127
|
-
}
|
|
128
|
-
);
|
|
104
|
+
activateSelectedViewAndClose() {
|
|
105
|
+
this.viewManagerModel.selectViewAsync(this.selectedView);
|
|
106
|
+
this.close();
|
|
129
107
|
}
|
|
130
108
|
|
|
131
109
|
override async doLoadAsync(loadSpec: LoadSpec) {
|
|
132
|
-
const {
|
|
133
|
-
|
|
134
|
-
|
|
110
|
+
const {tabContainerModel} = this,
|
|
111
|
+
{view, ownedViews, globalViews, sharedViews} = this.viewManagerModel;
|
|
112
|
+
|
|
113
|
+
runInAction(() => {
|
|
114
|
+
this.ownedGridModel.loadData(ownedViews);
|
|
115
|
+
this.globalGridModel.loadData(globalViews);
|
|
116
|
+
this.sharedGridModel.loadData(sharedViews);
|
|
117
|
+
tabContainerModel.setTabTitle('owned', this.ownedTabTitle);
|
|
118
|
+
tabContainerModel.setTabTitle('global', this.globalTabTitle);
|
|
119
|
+
tabContainerModel.setTabTitle('shared', this.sharedTabTitle);
|
|
120
|
+
});
|
|
135
121
|
if (!loadSpec.isRefresh && !view.isDefault) {
|
|
136
122
|
await this.selectViewAsync(view.info);
|
|
137
123
|
}
|
|
@@ -141,51 +127,74 @@ export class ManageDialogModel extends HoistModel {
|
|
|
141
127
|
return this.doDeleteAsync(views).linkTo(this.updateTask).catchDefault();
|
|
142
128
|
}
|
|
143
129
|
|
|
144
|
-
async updateAsync(view: ViewInfo,
|
|
145
|
-
return this.doUpdateAsync(view,
|
|
146
|
-
|
|
147
|
-
|
|
130
|
+
async updateAsync(view: ViewInfo, update: ViewUpdateSpec) {
|
|
131
|
+
return this.doUpdateAsync(view, update).linkTo(this.updateTask).catchDefault();
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async makeGlobalAsync(view: ViewInfo) {
|
|
135
|
+
return this.doMakeGlobalAsync(view).linkTo(this.updateTask).catchDefault();
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
@action
|
|
139
|
+
togglePinned(views: ViewInfo[]) {
|
|
140
|
+
views.forEach(v => this.viewManagerModel.togglePinned(v));
|
|
141
|
+
this.refreshAsync();
|
|
148
142
|
}
|
|
149
143
|
|
|
150
144
|
//------------------------
|
|
151
145
|
// Implementation
|
|
152
146
|
//------------------------
|
|
153
|
-
private
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
const
|
|
147
|
+
private init() {
|
|
148
|
+
this.ownedGridModel = this.createGridModel('owned');
|
|
149
|
+
this.globalGridModel = this.createGridModel('global');
|
|
150
|
+
this.sharedGridModel = this.createGridModel('shared');
|
|
151
|
+
this.tabContainerModel = this.createTabContainerModel();
|
|
152
|
+
this.viewPanelModel = new ViewPanelModel(this);
|
|
153
|
+
const gridModels = [this.ownedGridModel, this.globalGridModel, this.sharedGridModel];
|
|
154
|
+
this.addReaction({
|
|
155
|
+
track: () => this.filter,
|
|
156
|
+
run: f => gridModels.forEach(m => m.store.setFilter(f)),
|
|
157
|
+
fireImmediately: true
|
|
158
|
+
});
|
|
159
|
+
gridModels.forEach(gm => {
|
|
160
|
+
this.addReaction({
|
|
161
|
+
track: () => gm.hasSelection,
|
|
162
|
+
run: hasSelection => {
|
|
163
|
+
gridModels.forEach(it => {
|
|
164
|
+
if (it != gm && hasSelection) it.clearSelection();
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
}
|
|
160
170
|
|
|
161
|
-
|
|
171
|
+
private async doUpdateAsync(view: ViewInfo, update: ViewUpdateSpec) {
|
|
172
|
+
const {viewManagerModel} = this;
|
|
173
|
+
await viewManagerModel.api.updateViewInfoAsync(view, update);
|
|
162
174
|
await viewManagerModel.refreshAsync();
|
|
163
175
|
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
176
|
}
|
|
168
177
|
|
|
169
178
|
private async doDeleteAsync(views: ViewInfo[]) {
|
|
170
179
|
const {viewManagerModel, typeDisplayName} = this,
|
|
171
|
-
{enableDefault} = viewManagerModel,
|
|
172
180
|
count = views.length;
|
|
173
181
|
|
|
174
182
|
if (!count) return;
|
|
175
183
|
|
|
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
184
|
const confirmStr = count > 1 ? pluralize(typeDisplayName, count, true) : views[0].typedName;
|
|
184
185
|
const msgs: ReactNode[] = [`Are you sure you want to delete ${confirmStr}?`];
|
|
185
|
-
if (some(views,
|
|
186
|
+
if (some(views, v => v.isGlobal || v.isShared)) {
|
|
186
187
|
count > 1
|
|
187
|
-
? msgs.push(
|
|
188
|
-
|
|
188
|
+
? msgs.push(
|
|
189
|
+
strong(
|
|
190
|
+
`This includes at least one public ${typeDisplayName}, to be deleted for all users.`
|
|
191
|
+
)
|
|
192
|
+
)
|
|
193
|
+
: msgs.push(
|
|
194
|
+
strong(
|
|
195
|
+
`This is a public ${typeDisplayName} and will be deleted for all users.`
|
|
196
|
+
)
|
|
197
|
+
);
|
|
189
198
|
}
|
|
190
199
|
|
|
191
200
|
const confirmed = await XH.confirm({
|
|
@@ -207,73 +216,175 @@ export class ManageDialogModel extends HoistModel {
|
|
|
207
216
|
await this.refreshAsync();
|
|
208
217
|
}
|
|
209
218
|
|
|
210
|
-
async
|
|
211
|
-
|
|
219
|
+
private async doMakeGlobalAsync(view: ViewInfo) {
|
|
220
|
+
const {globalDisplayName, typeDisplayName} = this.viewManagerModel,
|
|
221
|
+
{typedName} = view,
|
|
222
|
+
msgs = [
|
|
223
|
+
`The ${typedName} will become a ${globalDisplayName} ${typeDisplayName} visible to all other ${XH.appName} users.`,
|
|
224
|
+
strong('Are you sure you want to proceed?')
|
|
225
|
+
];
|
|
226
|
+
|
|
227
|
+
const confirmed = await XH.confirm({
|
|
228
|
+
message: fragment(msgs.map(m => p(m))),
|
|
229
|
+
confirmProps: {
|
|
230
|
+
text: `Yes, change visibility`,
|
|
231
|
+
outlined: true,
|
|
232
|
+
autoFocus: false,
|
|
233
|
+
intent: 'primary'
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
if (!confirmed) return;
|
|
237
|
+
|
|
238
|
+
const {viewManagerModel} = this;
|
|
239
|
+
const updated = await viewManagerModel.api.makeViewGlobalAsync(view);
|
|
240
|
+
await viewManagerModel.refreshAsync();
|
|
241
|
+
await this.refreshAsync();
|
|
242
|
+
await this.selectViewAsync(updated.info); // reselect -- will have moved tabs!
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
private async selectViewAsync(view: ViewInfo) {
|
|
246
|
+
this.tabContainerModel.activateTab(
|
|
247
|
+
view.isOwned ? 'owned' : view.isGlobal ? 'global' : 'shared'
|
|
248
|
+
);
|
|
212
249
|
await this.gridModel.selectAsync(view.token);
|
|
213
250
|
}
|
|
214
251
|
|
|
215
|
-
private createGridModel(
|
|
252
|
+
private createGridModel(type: 'owned' | 'global' | 'shared'): GridModel {
|
|
253
|
+
const {typeDisplayName, globalDisplayName} = this;
|
|
254
|
+
|
|
255
|
+
const modifier =
|
|
256
|
+
type == 'owned' ? `personal` : type == 'global' ? globalDisplayName : 'shared';
|
|
257
|
+
|
|
216
258
|
return new GridModel({
|
|
217
|
-
emptyText: `No ${
|
|
259
|
+
emptyText: `No ${modifier} ${pluralize(typeDisplayName)} found...`,
|
|
218
260
|
sortBy: 'name',
|
|
219
|
-
hideHeaders: true,
|
|
220
261
|
showGroupRowCounts: false,
|
|
262
|
+
groupBy: ['group'],
|
|
263
|
+
groupSortFn: (a, b) => {
|
|
264
|
+
// Place ungrouped items at bottom.
|
|
265
|
+
if (a == '') return 1;
|
|
266
|
+
if (b == '') return -1;
|
|
267
|
+
return a.localeCompare(b);
|
|
268
|
+
},
|
|
221
269
|
selModel: 'multiple',
|
|
222
270
|
contextMenu: null,
|
|
223
271
|
sizingMode: 'standard',
|
|
272
|
+
hideHeaders: true,
|
|
224
273
|
store: {
|
|
225
274
|
idSpec: 'token',
|
|
226
|
-
processRawData: v => ({
|
|
275
|
+
processRawData: v => ({
|
|
276
|
+
name: v.name,
|
|
277
|
+
group: v.isGlobal || v.isOwned ? v.group : v.owner,
|
|
278
|
+
owner: v.owner,
|
|
279
|
+
lastUpdated: v.lastUpdated,
|
|
280
|
+
isPinned: v.isPinned,
|
|
281
|
+
view: v
|
|
282
|
+
}),
|
|
227
283
|
fields: [
|
|
228
284
|
{name: 'name', type: 'string'},
|
|
229
|
-
{name: '
|
|
230
|
-
{name: '
|
|
285
|
+
{name: 'group', type: 'string'},
|
|
286
|
+
{name: 'owner', type: 'string'},
|
|
287
|
+
{name: 'lastUpdated', type: 'date'},
|
|
288
|
+
{name: 'isPinned', type: 'bool'},
|
|
289
|
+
{name: 'view', type: 'auto'}
|
|
231
290
|
]
|
|
232
291
|
},
|
|
233
292
|
autosizeOptions: {mode: GridAutosizeMode.DISABLED},
|
|
234
293
|
columns: [
|
|
235
294
|
{field: 'name', flex: true},
|
|
295
|
+
{field: 'group', hidden: true},
|
|
296
|
+
{field: 'owner', hidden: true},
|
|
297
|
+
{field: 'lastUpdated', ...dateTimeCol},
|
|
236
298
|
{
|
|
237
|
-
|
|
238
|
-
field: 'info',
|
|
239
|
-
omit: !this.enableFavorites,
|
|
299
|
+
field: 'isPinned',
|
|
240
300
|
width: 40,
|
|
241
301
|
align: 'center',
|
|
242
|
-
headerName: Icon.
|
|
243
|
-
|
|
244
|
-
renderer:
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
302
|
+
headerName: Icon.pin(),
|
|
303
|
+
headerTooltip: 'Pin to menu',
|
|
304
|
+
renderer: (isPinned, {record}) => {
|
|
305
|
+
return button({
|
|
306
|
+
icon: Icon.pin({
|
|
307
|
+
prefix: isPinned ? 'fas' : 'fal',
|
|
308
|
+
className: isPinned ? 'xh-yellow' : 'xh-text-color-muted'
|
|
309
|
+
}),
|
|
310
|
+
tooltip: isPinned ? 'Unpin from menu' : 'Pin to menu',
|
|
311
|
+
onClick: () => {
|
|
312
|
+
this.togglePinned([record.data.view]);
|
|
313
|
+
}
|
|
249
314
|
});
|
|
250
315
|
}
|
|
251
316
|
}
|
|
252
317
|
],
|
|
253
|
-
|
|
254
|
-
if (colDef.colId === 'isFavorite') {
|
|
255
|
-
this.viewManagerModel.toggleFavorite(record.id);
|
|
256
|
-
api.redrawRows();
|
|
257
|
-
}
|
|
258
|
-
}
|
|
318
|
+
groupRowRenderer: ({value}) => (isEmpty(value) ? 'Ungrouped' : value)
|
|
259
319
|
});
|
|
260
320
|
}
|
|
261
321
|
|
|
262
322
|
private createTabContainerModel(): TabContainerModel {
|
|
263
|
-
const
|
|
323
|
+
const view = this.typeDisplayName,
|
|
324
|
+
views = pluralize(view),
|
|
325
|
+
globalViews = `${this.globalDisplayName} ${views}`,
|
|
326
|
+
{enableSharing} = this.viewManagerModel;
|
|
327
|
+
|
|
264
328
|
return new TabContainerModel({
|
|
265
329
|
tabs: [
|
|
266
330
|
{
|
|
267
|
-
id: '
|
|
268
|
-
title:
|
|
269
|
-
content:
|
|
331
|
+
id: 'owned',
|
|
332
|
+
title: this.ownedTabTitle,
|
|
333
|
+
content: viewsGrid({
|
|
334
|
+
model: this.ownedGridModel,
|
|
335
|
+
helpText: fragment(
|
|
336
|
+
Icon.user(),
|
|
337
|
+
`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. `,
|
|
338
|
+
enableSharing
|
|
339
|
+
? `Opt-in to sharing any of your ${views} to make them discoverable by other users.`
|
|
340
|
+
: ''
|
|
341
|
+
)
|
|
342
|
+
})
|
|
270
343
|
},
|
|
271
344
|
{
|
|
272
345
|
id: 'global',
|
|
273
|
-
title:
|
|
274
|
-
content:
|
|
346
|
+
title: this.globalTabTitle,
|
|
347
|
+
content: viewsGrid({
|
|
348
|
+
model: this.globalGridModel,
|
|
349
|
+
helpText: fragment(
|
|
350
|
+
Icon.globe(),
|
|
351
|
+
`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.`
|
|
352
|
+
)
|
|
353
|
+
})
|
|
354
|
+
},
|
|
355
|
+
{
|
|
356
|
+
id: 'shared',
|
|
357
|
+
title: this.sharedTabTitle,
|
|
358
|
+
content: viewsGrid({
|
|
359
|
+
model: this.sharedGridModel,
|
|
360
|
+
helpText: fragment(
|
|
361
|
+
Icon.users(),
|
|
362
|
+
`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.`
|
|
363
|
+
)
|
|
364
|
+
})
|
|
275
365
|
}
|
|
276
366
|
]
|
|
277
367
|
});
|
|
278
368
|
}
|
|
369
|
+
|
|
370
|
+
private get ownedTabTitle(): ReactNode {
|
|
371
|
+
return hbox(
|
|
372
|
+
`My ${startCase(pluralize(this.typeDisplayName))}`,
|
|
373
|
+
badge(this.ownedGridModel.store.allCount)
|
|
374
|
+
);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
private get globalTabTitle(): ReactNode {
|
|
378
|
+
return hbox(
|
|
379
|
+
`${startCase(this.globalDisplayName)} ${startCase(pluralize(this.typeDisplayName))}`,
|
|
380
|
+
badge(this.globalGridModel.store.allCount)
|
|
381
|
+
);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
private get sharedTabTitle(): ReactNode {
|
|
385
|
+
return hbox(
|
|
386
|
+
`Shared ${startCase(pluralize(this.typeDisplayName))}`,
|
|
387
|
+
badge(this.sharedGridModel.store.allCount)
|
|
388
|
+
);
|
|
389
|
+
}
|
|
279
390
|
}
|