@xh/hoist 71.0.0-SNAPSHOT.1734118787755 → 71.0.0-SNAPSHOT.1734552619121
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/ViewManagerModel.d.ts +15 -6
- package/build/types/cmp/viewmanager/ViewToBlobApi.d.ts +1 -2
- package/build/types/desktop/cmp/viewmanager/ViewManagerLocalModel.d.ts +1 -0
- package/build/types/desktop/cmp/viewmanager/dialog/ManageDialog.d.ts +2 -1
- package/build/types/desktop/cmp/viewmanager/dialog/ManageDialogModel.d.ts +0 -4
- package/build/types/desktop/cmp/viewmanager/dialog/ViewMultiPanel.d.ts +2 -1
- package/cmp/viewmanager/ViewManagerModel.ts +44 -15
- package/cmp/viewmanager/ViewToBlobApi.ts +27 -11
- package/desktop/cmp/viewmanager/ViewManager.ts +4 -1
- package/desktop/cmp/viewmanager/ViewManagerLocalModel.ts +5 -0
- package/desktop/cmp/viewmanager/dialog/ManageDialog.ts +4 -4
- package/desktop/cmp/viewmanager/dialog/ManageDialogModel.ts +83 -77
- package/desktop/cmp/viewmanager/dialog/ViewMultiPanel.ts +2 -2
- package/desktop/cmp/viewmanager/dialog/ViewPanel.ts +60 -55
- package/desktop/cmp/viewmanager/dialog/ViewPanelModel.ts +1 -1
- package/package.json +1 -1
- package/tsconfig.tsbuildinfo +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
* Handle delete and update collisions more gracefully.
|
|
10
10
|
* Support for `settleTime`,
|
|
11
11
|
* Improved management UI Dialog.
|
|
12
|
+
* Support for "global" views.
|
|
12
13
|
* New `SessionStorageService` and associated persistence provider provides support for saving
|
|
13
14
|
tab local data across reloads.
|
|
14
15
|
|
|
@@ -2,7 +2,7 @@ import { HoistModel, LoadSpec, PersistOptions, PlainObject, TaskObserver, Thunka
|
|
|
2
2
|
import type { ViewManagerProvider } from '@xh/hoist/core';
|
|
3
3
|
import { ViewInfo } from './ViewInfo';
|
|
4
4
|
import { View } from './View';
|
|
5
|
-
import {
|
|
5
|
+
import { ViewCreateSpec, ViewUpdateSpec } from './ViewToBlobApi';
|
|
6
6
|
export interface ViewManagerConfig {
|
|
7
7
|
/**
|
|
8
8
|
* True (default) to allow user to opt in to automatically saving changes to their current view.
|
|
@@ -14,6 +14,11 @@ export interface ViewManagerConfig {
|
|
|
14
14
|
* in advance, so that there is a clear initial selection for users without any private views.
|
|
15
15
|
*/
|
|
16
16
|
enableDefault?: boolean;
|
|
17
|
+
/**
|
|
18
|
+
* True (default) to enable "global" views - i.e. views that are not owned by a user and are
|
|
19
|
+
* available to all.
|
|
20
|
+
*/
|
|
21
|
+
enableGlobal?: boolean;
|
|
17
22
|
/**
|
|
18
23
|
* True (default) to allow users to share their views with other users.
|
|
19
24
|
*/
|
|
@@ -101,6 +106,7 @@ export declare class ViewManagerModel<T = PlainObject> extends HoistModel {
|
|
|
101
106
|
readonly globalDisplayName: string;
|
|
102
107
|
readonly enableAutoSave: boolean;
|
|
103
108
|
readonly enableDefault: boolean;
|
|
109
|
+
readonly enableGlobal: boolean;
|
|
104
110
|
readonly enableSharing: boolean;
|
|
105
111
|
readonly manageGlobal: boolean;
|
|
106
112
|
readonly settleTime: number;
|
|
@@ -137,11 +143,9 @@ export declare class ViewManagerModel<T = PlainObject> extends HoistModel {
|
|
|
137
143
|
* @internal
|
|
138
144
|
*/
|
|
139
145
|
providers: ViewManagerProvider<any>[];
|
|
140
|
-
/**
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
*/
|
|
144
|
-
api: ViewToBlobApi<T>;
|
|
146
|
+
/** Data access for persisting views. */
|
|
147
|
+
private api;
|
|
148
|
+
/** Last time changes were pushed to linked persistence providers */
|
|
145
149
|
private lastPushed;
|
|
146
150
|
get isValueDirty(): boolean;
|
|
147
151
|
get isViewSavable(): boolean;
|
|
@@ -173,6 +177,11 @@ export declare class ViewManagerModel<T = PlainObject> extends HoistModel {
|
|
|
173
177
|
userUnpin(view: ViewInfo): void;
|
|
174
178
|
isUserPinned(view: ViewInfo): boolean | null;
|
|
175
179
|
validateViewNameAsync(name: string, existing?: ViewInfo): Promise<string>;
|
|
180
|
+
/** Update all aspects of a view's metadata.*/
|
|
181
|
+
updateViewInfoAsync(view: ViewInfo, updates: ViewUpdateSpec): Promise<View<T>>;
|
|
182
|
+
/** Promote a view to global visibility/ownership status. */
|
|
183
|
+
makeViewGlobalAsync(view: ViewInfo): Promise<View<T>>;
|
|
184
|
+
deleteViewsAsync(toDelete: ViewInfo[]): Promise<void>;
|
|
176
185
|
private initAsync;
|
|
177
186
|
private loadViewAsync;
|
|
178
187
|
private maybeAutoSaveAsync;
|
|
@@ -35,8 +35,7 @@ export declare class ViewToBlobApi<T> {
|
|
|
35
35
|
makeViewGlobalAsync(view: ViewInfo): Promise<View<T>>;
|
|
36
36
|
/** Update a view's value. */
|
|
37
37
|
updateViewValueAsync(view: View<T>, value: Partial<T>): Promise<View<T>>;
|
|
38
|
-
|
|
39
|
-
deleteViewAsync(view: ViewInfo): Promise<void>;
|
|
38
|
+
deleteViewsAsync(views: ViewInfo[]): Promise<void>;
|
|
40
39
|
private trackChange;
|
|
41
40
|
private ensureEditable;
|
|
42
41
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { GridModel } from '@xh/hoist/cmp/grid';
|
|
2
|
+
import { ManageDialogModel } from './ManageDialogModel';
|
|
2
3
|
/**
|
|
3
4
|
* Default management dialog for ViewManager.
|
|
4
5
|
*/
|
|
5
|
-
export declare const manageDialog: import("@xh/hoist/core").ElementFactory<import("@xh/hoist/core").DefaultHoistProps<
|
|
6
|
+
export declare const manageDialog: import("@xh/hoist/core").ElementFactory<import("@xh/hoist/core").DefaultHoistProps<ManageDialogModel>>;
|
|
6
7
|
export declare const viewsGrid: import("@xh/hoist/core").ElementFactory<import("@xh/hoist/core").DefaultHoistProps<GridModel>>;
|
|
@@ -21,10 +21,6 @@ export declare class ManageDialogModel extends HoistModel {
|
|
|
21
21
|
get gridModel(): GridModel;
|
|
22
22
|
get selectedView(): ViewInfo;
|
|
23
23
|
get selectedViews(): ViewInfo[];
|
|
24
|
-
get manageGlobal(): boolean;
|
|
25
|
-
get typeDisplayName(): string;
|
|
26
|
-
get globalDisplayName(): string;
|
|
27
|
-
get enableSharing(): boolean;
|
|
28
24
|
constructor(viewManagerModel: ViewManagerModel);
|
|
29
25
|
open(): void;
|
|
30
26
|
close(): void;
|
|
@@ -1 +1,2 @@
|
|
|
1
|
-
|
|
1
|
+
import { ManageDialogModel } from './ManageDialogModel';
|
|
2
|
+
export declare const viewMultiPanel: import("@xh/hoist/core").ElementFactory<import("@xh/hoist/core").DefaultHoistProps<ManageDialogModel>>;
|
|
@@ -21,14 +21,15 @@ import {
|
|
|
21
21
|
import type {ViewManagerProvider} from '@xh/hoist/core';
|
|
22
22
|
import {genDisplayName} from '@xh/hoist/data';
|
|
23
23
|
import {fmtDateTime} from '@xh/hoist/format';
|
|
24
|
-
import {action, bindable, makeObservable, observable,
|
|
24
|
+
import {action, bindable, makeObservable, observable, when} from '@xh/hoist/mobx';
|
|
25
25
|
import {olderThan, SECONDS} from '@xh/hoist/utils/datetime';
|
|
26
26
|
import {executeIfFunction, pluralize, throwIf} from '@xh/hoist/utils/js';
|
|
27
27
|
import {find, isEmpty, isEqual, isNil, isObject, lowerCase, pickBy} from 'lodash';
|
|
28
|
+
import {runInAction} from 'mobx';
|
|
28
29
|
import {ReactNode} from 'react';
|
|
29
30
|
import {ViewInfo} from './ViewInfo';
|
|
30
31
|
import {View} from './View';
|
|
31
|
-
import {ViewToBlobApi, ViewCreateSpec} from './ViewToBlobApi';
|
|
32
|
+
import {ViewToBlobApi, ViewCreateSpec, ViewUpdateSpec} from './ViewToBlobApi';
|
|
32
33
|
|
|
33
34
|
export interface ViewManagerConfig {
|
|
34
35
|
/**
|
|
@@ -43,6 +44,12 @@ export interface ViewManagerConfig {
|
|
|
43
44
|
*/
|
|
44
45
|
enableDefault?: boolean;
|
|
45
46
|
|
|
47
|
+
/**
|
|
48
|
+
* True (default) to enable "global" views - i.e. views that are not owned by a user and are
|
|
49
|
+
* available to all.
|
|
50
|
+
*/
|
|
51
|
+
enableGlobal?: boolean;
|
|
52
|
+
|
|
46
53
|
/**
|
|
47
54
|
* True (default) to allow users to share their views with other users.
|
|
48
55
|
*/
|
|
@@ -145,6 +152,7 @@ export class ViewManagerModel<T = PlainObject> extends HoistModel {
|
|
|
145
152
|
readonly globalDisplayName: string;
|
|
146
153
|
readonly enableAutoSave: boolean;
|
|
147
154
|
readonly enableDefault: boolean;
|
|
155
|
+
readonly enableGlobal: boolean;
|
|
148
156
|
readonly enableSharing: boolean;
|
|
149
157
|
readonly manageGlobal: boolean;
|
|
150
158
|
readonly settleTime: number;
|
|
@@ -193,13 +201,10 @@ export class ViewManagerModel<T = PlainObject> extends HoistModel {
|
|
|
193
201
|
*/
|
|
194
202
|
providers: ViewManagerProvider<any>[] = [];
|
|
195
203
|
|
|
196
|
-
/**
|
|
197
|
-
|
|
198
|
-
* @internal
|
|
199
|
-
*/
|
|
200
|
-
api: ViewToBlobApi<T>;
|
|
204
|
+
/** Data access for persisting views. */
|
|
205
|
+
private api: ViewToBlobApi<T>;
|
|
201
206
|
|
|
202
|
-
|
|
207
|
+
/** Last time changes were pushed to linked persistence providers */
|
|
203
208
|
private lastPushed: number = null;
|
|
204
209
|
|
|
205
210
|
//---------------
|
|
@@ -216,13 +221,7 @@ export class ViewManagerModel<T = PlainObject> extends HoistModel {
|
|
|
216
221
|
|
|
217
222
|
get isViewAutoSavable(): boolean {
|
|
218
223
|
const {enableAutoSave, autoSave, view} = this;
|
|
219
|
-
return
|
|
220
|
-
enableAutoSave &&
|
|
221
|
-
autoSave &&
|
|
222
|
-
!view.isShared &&
|
|
223
|
-
!view.isDefault &&
|
|
224
|
-
!XH.identityService.isImpersonating
|
|
225
|
-
);
|
|
224
|
+
return enableAutoSave && autoSave && view.isOwned && !XH.identityService.isImpersonating;
|
|
226
225
|
}
|
|
227
226
|
|
|
228
227
|
get autoSaveUnavailableReason(): string {
|
|
@@ -272,6 +271,7 @@ export class ViewManagerModel<T = PlainObject> extends HoistModel {
|
|
|
272
271
|
manageGlobal = false,
|
|
273
272
|
enableAutoSave = true,
|
|
274
273
|
enableDefault = true,
|
|
274
|
+
enableGlobal = true,
|
|
275
275
|
enableSharing = true,
|
|
276
276
|
settleTime = 1000,
|
|
277
277
|
initialViewSpec = null
|
|
@@ -290,6 +290,7 @@ export class ViewManagerModel<T = PlainObject> extends HoistModel {
|
|
|
290
290
|
this.persistWith = persistWith;
|
|
291
291
|
this.manageGlobal = executeIfFunction(manageGlobal) ?? false;
|
|
292
292
|
this.enableDefault = enableDefault;
|
|
293
|
+
this.enableGlobal = enableGlobal;
|
|
293
294
|
this.enableSharing = enableSharing;
|
|
294
295
|
this.enableAutoSave = enableAutoSave;
|
|
295
296
|
this.settleTime = settleTime;
|
|
@@ -438,6 +439,34 @@ export class ViewManagerModel<T = PlainObject> extends HoistModel {
|
|
|
438
439
|
return null;
|
|
439
440
|
}
|
|
440
441
|
|
|
442
|
+
/** Update all aspects of a view's metadata.*/
|
|
443
|
+
async updateViewInfoAsync(view: ViewInfo, updates: ViewUpdateSpec): Promise<View<T>> {
|
|
444
|
+
return this.api.updateViewInfoAsync(view, updates);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/** Promote a view to global visibility/ownership status. */
|
|
448
|
+
async makeViewGlobalAsync(view: ViewInfo): Promise<View<T>> {
|
|
449
|
+
return this.api.makeViewGlobalAsync(view);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
async deleteViewsAsync(toDelete: ViewInfo[]): Promise<void> {
|
|
453
|
+
let exception;
|
|
454
|
+
try {
|
|
455
|
+
await this.api.deleteViewsAsync(toDelete);
|
|
456
|
+
} catch (e) {
|
|
457
|
+
exception = e;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
await this.refreshAsync();
|
|
461
|
+
const {views} = this;
|
|
462
|
+
|
|
463
|
+
if (toDelete.some(view => view.isCurrentView) && !views.some(view => view.isCurrentView)) {
|
|
464
|
+
await this.loadViewAsync(this.initialViewSpec?.(views));
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
if (exception) throw exception;
|
|
468
|
+
}
|
|
469
|
+
|
|
441
470
|
//------------------
|
|
442
471
|
// Implementation
|
|
443
472
|
//------------------
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import {PlainObject, XH} from '@xh/hoist/core';
|
|
9
9
|
import {pluralize, throwIf} from '@xh/hoist/utils/js';
|
|
10
|
-
import {omit, pick} from 'lodash';
|
|
10
|
+
import {isEmpty, omit, pick} from 'lodash';
|
|
11
11
|
import {ViewInfo} from './ViewInfo';
|
|
12
12
|
import {View} from './View';
|
|
13
13
|
import {ViewManagerModel} from './ViewManagerModel';
|
|
@@ -50,7 +50,13 @@ export class ViewToBlobApi<T> {
|
|
|
50
50
|
type: model.type,
|
|
51
51
|
includeValue: false
|
|
52
52
|
});
|
|
53
|
-
return blobs
|
|
53
|
+
return blobs
|
|
54
|
+
.map(b => new ViewInfo(b, model))
|
|
55
|
+
.filter(
|
|
56
|
+
view =>
|
|
57
|
+
(model.enableGlobal || !view.isGlobal) &&
|
|
58
|
+
(model.enableSharing || !view.isShared)
|
|
59
|
+
);
|
|
54
60
|
} catch (e) {
|
|
55
61
|
throw XH.exception({
|
|
56
62
|
message: `Unable to fetch ${pluralize(model.typeDisplayName)}`,
|
|
@@ -151,21 +157,31 @@ export class ViewToBlobApi<T> {
|
|
|
151
157
|
}
|
|
152
158
|
}
|
|
153
159
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
160
|
+
async deleteViewsAsync(views: ViewInfo[]) {
|
|
161
|
+
views.forEach(v => this.ensureEditable(v));
|
|
162
|
+
const results = await Promise.allSettled(
|
|
163
|
+
views.map(v => XH.jsonBlobService.archiveAsync(v.token))
|
|
164
|
+
),
|
|
165
|
+
outcome = results.map((result, idx) => ({result, view: views[idx]})),
|
|
166
|
+
failed = outcome.filter(({result}) => result.status === 'rejected') as Array<{
|
|
167
|
+
result: PromiseRejectedResult;
|
|
168
|
+
view: ViewInfo;
|
|
169
|
+
}>;
|
|
170
|
+
|
|
171
|
+
this.trackChange(`Deleted ${pluralize('View', views.length - failed.length, true)}`);
|
|
172
|
+
|
|
173
|
+
if (!isEmpty(failed)) {
|
|
174
|
+
throw XH.exception({
|
|
175
|
+
message: `Failed to delete ${pluralize(this.model.typeDisplayName, failed.length, true)}: ${failed.map(({view}) => view.name).join(', ')}`,
|
|
176
|
+
cause: failed.map(({result}) => result.reason)
|
|
177
|
+
});
|
|
162
178
|
}
|
|
163
179
|
}
|
|
164
180
|
|
|
165
181
|
//------------------
|
|
166
182
|
// Implementation
|
|
167
183
|
//------------------
|
|
168
|
-
private trackChange(message: string, v
|
|
184
|
+
private trackChange(message: string, v?: View | ViewInfo) {
|
|
169
185
|
XH.track({
|
|
170
186
|
message,
|
|
171
187
|
category: 'Views',
|
|
@@ -12,6 +12,7 @@ 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
|
+
import {useOnVisibleChange} from '@xh/hoist/utils/react';
|
|
15
16
|
import {startCase} from 'lodash';
|
|
16
17
|
import {viewMenu} from './ViewMenu';
|
|
17
18
|
import {ViewManagerLocalModel} from './ViewManagerLocalModel';
|
|
@@ -67,6 +68,7 @@ export const [ViewManager, viewManager] = hoistCmp.withFactory<ViewManagerProps>
|
|
|
67
68
|
save = saveButton({model: locModel, mode: showSaveButton, ...saveButtonProps}),
|
|
68
69
|
revert = revertButton({model: locModel, mode: showRevertButton, ...revertButtonProps}),
|
|
69
70
|
menu = popover({
|
|
71
|
+
disabled: !locModel.isVisible, // Prevent orphaned popover menu
|
|
70
72
|
item: menuButton({model: locModel, ...menuButtonProps}),
|
|
71
73
|
content: viewMenu({model: locModel}),
|
|
72
74
|
placement: 'bottom-start',
|
|
@@ -75,7 +77,8 @@ export const [ViewManager, viewManager] = hoistCmp.withFactory<ViewManagerProps>
|
|
|
75
77
|
return fragment(
|
|
76
78
|
hbox({
|
|
77
79
|
className,
|
|
78
|
-
items: buttonSide == 'left' ? [revert, save, menu] : [menu, save, revert]
|
|
80
|
+
items: buttonSide == 'left' ? [revert, save, menu] : [menu, save, revert],
|
|
81
|
+
ref: useOnVisibleChange(isVisible => (locModel.isVisible = isVisible))
|
|
79
82
|
}),
|
|
80
83
|
manageDialog({model: locModel.manageDialogModel}),
|
|
81
84
|
saveAsDialog({model: locModel.saveAsDialogModel})
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import {HoistModel, managed} from '@xh/hoist/core';
|
|
9
|
+
import {bindable, makeObservable} from '@xh/hoist/mobx';
|
|
9
10
|
import {ManageDialogModel} from './dialog/ManageDialogModel';
|
|
10
11
|
import {SaveAsDialogModel} from './dialog/SaveAsDialogModel';
|
|
11
12
|
import {ViewManagerModel} from '@xh/hoist/cmp/viewmanager';
|
|
@@ -19,8 +20,12 @@ export class ViewManagerLocalModel extends HoistModel {
|
|
|
19
20
|
@managed
|
|
20
21
|
readonly saveAsDialogModel: SaveAsDialogModel;
|
|
21
22
|
|
|
23
|
+
@bindable
|
|
24
|
+
isVisible = true;
|
|
25
|
+
|
|
22
26
|
constructor(parent: ViewManagerModel) {
|
|
23
27
|
super();
|
|
28
|
+
makeObservable(this);
|
|
24
29
|
this.parent = parent;
|
|
25
30
|
this.manageDialogModel = new ManageDialogModel(parent);
|
|
26
31
|
this.saveAsDialogModel = new SaveAsDialogModel(parent);
|
|
@@ -24,7 +24,7 @@ import {viewPanel} from './ViewPanel';
|
|
|
24
24
|
/**
|
|
25
25
|
* Default management dialog for ViewManager.
|
|
26
26
|
*/
|
|
27
|
-
export const manageDialog = hoistCmp.factory({
|
|
27
|
+
export const manageDialog = hoistCmp.factory<ManageDialogModel>({
|
|
28
28
|
displayName: 'ManageDialog',
|
|
29
29
|
className: 'xh-view-manager__manage-dialog',
|
|
30
30
|
model: uses(() => ManageDialogModel),
|
|
@@ -32,11 +32,11 @@ export const manageDialog = hoistCmp.factory({
|
|
|
32
32
|
render({model, className}) {
|
|
33
33
|
if (!model.isOpen) return null;
|
|
34
34
|
|
|
35
|
-
const {
|
|
35
|
+
const {updateTask, loadTask, selectedViews, viewManagerModel} = model,
|
|
36
36
|
count = selectedViews.length;
|
|
37
37
|
|
|
38
38
|
return dialog({
|
|
39
|
-
title: `Manage ${capitalize(pluralize(typeDisplayName))}`,
|
|
39
|
+
title: `Manage ${capitalize(pluralize(viewManagerModel.typeDisplayName))}`,
|
|
40
40
|
icon: Icon.gear(),
|
|
41
41
|
className,
|
|
42
42
|
isOpen: true,
|
|
@@ -103,7 +103,7 @@ export const viewsGrid = hoistCmp.factory<GridModel>({
|
|
|
103
103
|
|
|
104
104
|
const placeholderPanel = hoistCmp.factory<ManageDialogModel>({
|
|
105
105
|
render({model}) {
|
|
106
|
-
return placeholder(Icon.gears(), `Select a ${model.typeDisplayName}`);
|
|
106
|
+
return placeholder(Icon.gears(), `Select a ${model.viewManagerModel.typeDisplayName}`);
|
|
107
107
|
}
|
|
108
108
|
});
|
|
109
109
|
|
|
@@ -17,7 +17,7 @@ import {viewsGrid} from '@xh/hoist/desktop/cmp/viewmanager/dialog/ManageDialog';
|
|
|
17
17
|
import {Icon} from '@xh/hoist/icon';
|
|
18
18
|
import {action, bindable, computed, makeObservable, observable, runInAction} from '@xh/hoist/mobx';
|
|
19
19
|
import {pluralize} from '@xh/hoist/utils/js';
|
|
20
|
-
import {capitalize, isEmpty, some, startCase} from 'lodash';
|
|
20
|
+
import {capitalize, compact, isEmpty, some, startCase} from 'lodash';
|
|
21
21
|
import {ReactNode} from 'react';
|
|
22
22
|
import {ViewPanelModel} from './ViewPanelModel';
|
|
23
23
|
|
|
@@ -67,22 +67,6 @@ export class ManageDialogModel extends HoistModel {
|
|
|
67
67
|
return this.gridModel.selectedRecords.map(it => it.data.view) as ViewInfo[];
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
get manageGlobal(): boolean {
|
|
71
|
-
return this.viewManagerModel.manageGlobal;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
get typeDisplayName(): string {
|
|
75
|
-
return this.viewManagerModel.typeDisplayName;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
get globalDisplayName(): string {
|
|
79
|
-
return this.viewManagerModel.globalDisplayName;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
get enableSharing(): boolean {
|
|
83
|
-
return this.viewManagerModel.enableSharing;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
70
|
constructor(viewManagerModel: ViewManagerModel) {
|
|
87
71
|
super();
|
|
88
72
|
makeObservable(this);
|
|
@@ -108,15 +92,22 @@ export class ManageDialogModel extends HoistModel {
|
|
|
108
92
|
|
|
109
93
|
override async doLoadAsync(loadSpec: LoadSpec) {
|
|
110
94
|
const {tabContainerModel} = this,
|
|
111
|
-
{view, ownedViews, globalViews, sharedViews} =
|
|
95
|
+
{enableGlobal, enableSharing, view, ownedViews, globalViews, sharedViews} =
|
|
96
|
+
this.viewManagerModel;
|
|
112
97
|
|
|
113
98
|
runInAction(() => {
|
|
114
99
|
this.ownedGridModel.loadData(ownedViews);
|
|
115
|
-
this.globalGridModel.loadData(globalViews);
|
|
116
|
-
this.sharedGridModel.loadData(sharedViews);
|
|
117
100
|
tabContainerModel.setTabTitle('owned', this.ownedTabTitle);
|
|
118
|
-
|
|
119
|
-
|
|
101
|
+
|
|
102
|
+
if (enableGlobal) {
|
|
103
|
+
this.globalGridModel.loadData(globalViews);
|
|
104
|
+
tabContainerModel.setTabTitle('global', this.globalTabTitle);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (enableSharing) {
|
|
108
|
+
this.sharedGridModel.loadData(sharedViews);
|
|
109
|
+
tabContainerModel.setTabTitle('shared', this.sharedTabTitle);
|
|
110
|
+
}
|
|
120
111
|
});
|
|
121
112
|
if (!loadSpec.isRefresh && !view.isDefault) {
|
|
122
113
|
await this.selectViewAsync(view.info);
|
|
@@ -145,38 +136,51 @@ export class ManageDialogModel extends HoistModel {
|
|
|
145
136
|
// Implementation
|
|
146
137
|
//------------------------
|
|
147
138
|
private init() {
|
|
139
|
+
const {enableGlobal, enableSharing} = this.viewManagerModel;
|
|
140
|
+
|
|
148
141
|
this.ownedGridModel = this.createGridModel('owned');
|
|
149
|
-
this.globalGridModel = this.createGridModel('global');
|
|
150
|
-
this.sharedGridModel = this.createGridModel('shared');
|
|
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
|
+
|
|
151
150
|
this.tabContainerModel = this.createTabContainerModel();
|
|
152
151
|
this.viewPanelModel = new ViewPanelModel(this);
|
|
153
|
-
|
|
152
|
+
|
|
154
153
|
this.addReaction({
|
|
155
154
|
track: () => this.filter,
|
|
156
155
|
run: f => gridModels.forEach(m => m.store.setFilter(f)),
|
|
157
156
|
fireImmediately: true
|
|
158
157
|
});
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
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
|
+
});
|
|
167
170
|
});
|
|
168
|
-
}
|
|
171
|
+
}
|
|
169
172
|
}
|
|
170
173
|
|
|
171
174
|
private async doUpdateAsync(view: ViewInfo, update: ViewUpdateSpec) {
|
|
172
175
|
const {viewManagerModel} = this;
|
|
173
|
-
await viewManagerModel.
|
|
176
|
+
await viewManagerModel.updateViewInfoAsync(view, update);
|
|
174
177
|
await viewManagerModel.refreshAsync();
|
|
175
178
|
await this.refreshAsync();
|
|
176
179
|
}
|
|
177
180
|
|
|
178
181
|
private async doDeleteAsync(views: ViewInfo[]) {
|
|
179
|
-
const {viewManagerModel
|
|
182
|
+
const {viewManagerModel} = this,
|
|
183
|
+
{typeDisplayName} = viewManagerModel,
|
|
180
184
|
count = views.length;
|
|
181
185
|
|
|
182
186
|
if (!count) return;
|
|
@@ -208,12 +212,7 @@ export class ManageDialogModel extends HoistModel {
|
|
|
208
212
|
});
|
|
209
213
|
if (!confirmed) return;
|
|
210
214
|
|
|
211
|
-
|
|
212
|
-
await viewManagerModel.api.deleteViewAsync(view);
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
await viewManagerModel.refreshAsync();
|
|
216
|
-
await this.refreshAsync();
|
|
215
|
+
return viewManagerModel.deleteViewsAsync(views).finally(() => this.refreshAsync());
|
|
217
216
|
}
|
|
218
217
|
|
|
219
218
|
private async doMakeGlobalAsync(view: ViewInfo) {
|
|
@@ -236,7 +235,7 @@ export class ManageDialogModel extends HoistModel {
|
|
|
236
235
|
if (!confirmed) return;
|
|
237
236
|
|
|
238
237
|
const {viewManagerModel} = this;
|
|
239
|
-
const updated = await viewManagerModel.
|
|
238
|
+
const updated = await viewManagerModel.makeViewGlobalAsync(view);
|
|
240
239
|
await viewManagerModel.refreshAsync();
|
|
241
240
|
await this.refreshAsync();
|
|
242
241
|
await this.selectViewAsync(updated.info); // reselect -- will have moved tabs!
|
|
@@ -250,7 +249,7 @@ export class ManageDialogModel extends HoistModel {
|
|
|
250
249
|
}
|
|
251
250
|
|
|
252
251
|
private createGridModel(type: 'owned' | 'global' | 'shared'): GridModel {
|
|
253
|
-
const {typeDisplayName, globalDisplayName} = this;
|
|
252
|
+
const {typeDisplayName, globalDisplayName} = this.viewManagerModel;
|
|
254
253
|
|
|
255
254
|
const modifier =
|
|
256
255
|
type == 'owned' ? `personal` : type == 'global' ? globalDisplayName : 'shared';
|
|
@@ -320,13 +319,12 @@ export class ManageDialogModel extends HoistModel {
|
|
|
320
319
|
}
|
|
321
320
|
|
|
322
321
|
private createTabContainerModel(): TabContainerModel {
|
|
323
|
-
const
|
|
322
|
+
const {enableGlobal, enableSharing, globalDisplayName, typeDisplayName} =
|
|
323
|
+
this.viewManagerModel,
|
|
324
|
+
view = typeDisplayName,
|
|
324
325
|
views = pluralize(view),
|
|
325
|
-
globalViews = `${
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
return new TabContainerModel({
|
|
329
|
-
tabs: [
|
|
326
|
+
globalViews = `${globalDisplayName} ${views}`,
|
|
327
|
+
tabs = [
|
|
330
328
|
{
|
|
331
329
|
id: 'owned',
|
|
332
330
|
title: this.ownedTabTitle,
|
|
@@ -340,50 +338,58 @@ export class ManageDialogModel extends HoistModel {
|
|
|
340
338
|
: ''
|
|
341
339
|
)
|
|
342
340
|
})
|
|
343
|
-
},
|
|
344
|
-
{
|
|
345
|
-
id: 'global',
|
|
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
|
-
})
|
|
365
341
|
}
|
|
366
|
-
]
|
|
367
|
-
|
|
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});
|
|
368
373
|
}
|
|
369
374
|
|
|
370
375
|
private get ownedTabTitle(): ReactNode {
|
|
371
376
|
return hbox(
|
|
372
|
-
`My ${startCase(pluralize(this.typeDisplayName))}`,
|
|
377
|
+
`My ${startCase(pluralize(this.viewManagerModel.typeDisplayName))}`,
|
|
373
378
|
badge(this.ownedGridModel.store.allCount)
|
|
374
379
|
);
|
|
375
380
|
}
|
|
376
381
|
|
|
377
382
|
private get globalTabTitle(): ReactNode {
|
|
383
|
+
const {globalDisplayName, typeDisplayName} = this.viewManagerModel;
|
|
378
384
|
return hbox(
|
|
379
|
-
`${startCase(
|
|
385
|
+
`${startCase(globalDisplayName)} ${startCase(pluralize(typeDisplayName))}`,
|
|
380
386
|
badge(this.globalGridModel.store.allCount)
|
|
381
387
|
);
|
|
382
388
|
}
|
|
383
389
|
|
|
384
390
|
private get sharedTabTitle(): ReactNode {
|
|
385
391
|
return hbox(
|
|
386
|
-
`Shared ${startCase(pluralize(this.typeDisplayName))}`,
|
|
392
|
+
`Shared ${startCase(pluralize(this.viewManagerModel.typeDisplayName))}`,
|
|
387
393
|
badge(this.sharedGridModel.store.allCount)
|
|
388
394
|
);
|
|
389
395
|
}
|
|
@@ -14,7 +14,7 @@ import {pluralize} from '@xh/hoist/utils/js';
|
|
|
14
14
|
import {every, isEmpty, some} from 'lodash';
|
|
15
15
|
import {ManageDialogModel} from './ManageDialogModel';
|
|
16
16
|
|
|
17
|
-
export const viewMultiPanel = hoistCmp.factory({
|
|
17
|
+
export const viewMultiPanel = hoistCmp.factory<ManageDialogModel>({
|
|
18
18
|
model: uses(() => ManageDialogModel),
|
|
19
19
|
render({model}) {
|
|
20
20
|
const views = model.selectedViews;
|
|
@@ -25,7 +25,7 @@ export const viewMultiPanel = hoistCmp.factory({
|
|
|
25
25
|
className: 'xh-view-manager__manage-dialog__form',
|
|
26
26
|
item: placeholder(
|
|
27
27
|
Icon.gears(),
|
|
28
|
-
`${views.length} selected ${pluralize(model.typeDisplayName)}`,
|
|
28
|
+
`${views.length} selected ${pluralize(model.viewManagerModel.typeDisplayName)}`,
|
|
29
29
|
vspacer(),
|
|
30
30
|
buttons()
|
|
31
31
|
)
|