@xh/hoist 71.0.0-SNAPSHOT.1733266596001 → 71.0.0-SNAPSHOT.1733372979467
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/ViewManagerModel.d.ts +10 -9
- package/build/types/cmp/viewmanager/ViewToBlobApi.d.ts +18 -0
- package/cmp/viewmanager/SaveAsDialogModel.ts +1 -1
- package/cmp/viewmanager/ViewManagerModel.ts +32 -74
- package/cmp/viewmanager/ViewToBlobApi.ts +92 -0
- package/desktop/cmp/viewmanager/ViewManager.ts +8 -2
- package/desktop/cmp/viewmanager/ViewMenu.ts +14 -4
- package/desktop/cmp/viewmanager/dialog/EditForm.ts +2 -2
- package/desktop/cmp/viewmanager/dialog/ManageDialogModel.ts +2 -2
- package/package.json +1 -1
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -3,6 +3,7 @@ import type { ViewManagerProvider } from '@xh/hoist/core';
|
|
|
3
3
|
import { SaveAsDialogModel } from './SaveAsDialogModel';
|
|
4
4
|
import { ViewInfo } from './ViewInfo';
|
|
5
5
|
import { View } from './View';
|
|
6
|
+
import { ViewToBlobApi } from './ViewToBlobApi';
|
|
6
7
|
export interface ViewManagerConfig {
|
|
7
8
|
/**
|
|
8
9
|
* True (default) to allow user to opt in to automatically saving changes to their current view.
|
|
@@ -94,6 +95,7 @@ export declare class ViewManagerModel<T = PlainObject> extends HoistModel {
|
|
|
94
95
|
*/
|
|
95
96
|
static createAsync(config: ViewManagerConfig): Promise<ViewManagerModel>;
|
|
96
97
|
/** Immutable configuration for this model. */
|
|
98
|
+
persistWith: ViewManagerPersistOptions;
|
|
97
99
|
readonly viewType: string;
|
|
98
100
|
readonly typeDisplayName: string;
|
|
99
101
|
readonly globalDisplayName: string;
|
|
@@ -124,18 +126,22 @@ export declare class ViewManagerModel<T = PlainObject> extends HoistModel {
|
|
|
124
126
|
*/
|
|
125
127
|
saveTask: TaskObserver;
|
|
126
128
|
manageDialogOpen: boolean;
|
|
127
|
-
|
|
129
|
+
saveAsDialogModel: SaveAsDialogModel;
|
|
130
|
+
/** Unsaved changes on the current view.*/
|
|
128
131
|
private pendingValue;
|
|
129
|
-
private lastPushed;
|
|
130
132
|
/**
|
|
131
133
|
* Array of {@link ViewManagerProvider} instances bound to this model. Providers will
|
|
132
134
|
* push themselves onto this array when constructed with a reference to this model. Used to
|
|
133
135
|
* proactively push state to the target components when the model's selected `value` changes.
|
|
134
|
-
*
|
|
135
136
|
* @internal
|
|
136
137
|
*/
|
|
137
138
|
providers: ViewManagerProvider<any>[];
|
|
138
|
-
|
|
139
|
+
/**
|
|
140
|
+
* Data access for persisting views
|
|
141
|
+
* @internal
|
|
142
|
+
*/
|
|
143
|
+
api: ViewToBlobApi<T>;
|
|
144
|
+
private lastPushed;
|
|
139
145
|
get isValueDirty(): boolean;
|
|
140
146
|
get isViewSavable(): boolean;
|
|
141
147
|
get isViewAutoSavable(): boolean;
|
|
@@ -165,12 +171,7 @@ export declare class ViewManagerModel<T = PlainObject> extends HoistModel {
|
|
|
165
171
|
openManageDialog(): void;
|
|
166
172
|
closeManageDialog(): void;
|
|
167
173
|
validateViewNameAsync(name: string, existing?: ViewInfo): Promise<string>;
|
|
168
|
-
deleteViewAsync(view: ViewInfo): Promise<void>;
|
|
169
|
-
updateViewAsync(view: ViewInfo, name: string, description: string, isGlobal: boolean): Promise<void>;
|
|
170
|
-
createViewAsync(name: string, description: string, value: PlainObject): Promise<View>;
|
|
171
174
|
private loadViewAsync;
|
|
172
|
-
private fetchViewAsync;
|
|
173
|
-
private fetchViewInfosAsync;
|
|
174
175
|
private maybeAutoSaveAsync;
|
|
175
176
|
private setAsView;
|
|
176
177
|
private handleException;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { PlainObject } from '@xh/hoist/core';
|
|
2
|
+
import { ViewInfo } from './ViewInfo';
|
|
3
|
+
import { View } from './View';
|
|
4
|
+
import { ViewManagerModel } from './ViewManagerModel';
|
|
5
|
+
/**
|
|
6
|
+
* Class for accessing and updating views using JSON Blobs Service.
|
|
7
|
+
*
|
|
8
|
+
* @internal
|
|
9
|
+
*/
|
|
10
|
+
export declare class ViewToBlobApi<T> {
|
|
11
|
+
private owner;
|
|
12
|
+
constructor(owner: ViewManagerModel<T>);
|
|
13
|
+
fetchViewInfosAsync(): Promise<ViewInfo[]>;
|
|
14
|
+
fetchViewAsync(info: ViewInfo): Promise<View<T>>;
|
|
15
|
+
createViewAsync(name: string, description: string, value: PlainObject): Promise<View<T>>;
|
|
16
|
+
updateViewAsync(view: ViewInfo, name: string, description: string, isGlobal: boolean): Promise<void>;
|
|
17
|
+
deleteViewAsync(view: ViewInfo): Promise<void>;
|
|
18
|
+
}
|
|
@@ -81,7 +81,7 @@ export class SaveAsDialogModel extends HoistModel {
|
|
|
81
81
|
if (!isValid) return;
|
|
82
82
|
|
|
83
83
|
try {
|
|
84
|
-
const ret = await this.parent.createViewAsync(name, description, parent.getValue());
|
|
84
|
+
const ret = await this.parent.api.createViewAsync(name, description, parent.getValue());
|
|
85
85
|
this.close();
|
|
86
86
|
this.resolveOpen(ret);
|
|
87
87
|
} catch (e) {
|
|
@@ -30,6 +30,7 @@ import {ReactNode} from 'react';
|
|
|
30
30
|
import {SaveAsDialogModel} from './SaveAsDialogModel';
|
|
31
31
|
import {ViewInfo} from './ViewInfo';
|
|
32
32
|
import {View} from './View';
|
|
33
|
+
import {ViewToBlobApi} from './ViewToBlobApi';
|
|
33
34
|
|
|
34
35
|
export interface ViewManagerConfig {
|
|
35
36
|
/**
|
|
@@ -139,6 +140,7 @@ export class ViewManagerModel<T = PlainObject> extends HoistModel {
|
|
|
139
140
|
}
|
|
140
141
|
|
|
141
142
|
/** Immutable configuration for this model. */
|
|
143
|
+
declare persistWith: ViewManagerPersistOptions;
|
|
142
144
|
readonly viewType: string;
|
|
143
145
|
readonly typeDisplayName: string;
|
|
144
146
|
readonly globalDisplayName: string;
|
|
@@ -174,25 +176,35 @@ export class ViewManagerModel<T = PlainObject> extends HoistModel {
|
|
|
174
176
|
saveTask: TaskObserver;
|
|
175
177
|
|
|
176
178
|
@observable manageDialogOpen = false;
|
|
177
|
-
@managed
|
|
178
|
-
|
|
179
|
-
// Unsaved changes on the current view.
|
|
180
|
-
@observable.ref private pendingValue: PendingValue<T> = null;
|
|
179
|
+
@managed saveAsDialogModel: SaveAsDialogModel;
|
|
181
180
|
|
|
182
|
-
|
|
183
|
-
|
|
181
|
+
//-----------------------
|
|
182
|
+
// Private, internal state.
|
|
183
|
+
//-------------------------
|
|
184
|
+
/** Unsaved changes on the current view.*/
|
|
185
|
+
@observable.ref
|
|
186
|
+
private pendingValue: PendingValue<T> = null;
|
|
184
187
|
|
|
185
188
|
/**
|
|
186
189
|
* Array of {@link ViewManagerProvider} instances bound to this model. Providers will
|
|
187
190
|
* push themselves onto this array when constructed with a reference to this model. Used to
|
|
188
191
|
* proactively push state to the target components when the model's selected `value` changes.
|
|
189
|
-
*
|
|
190
192
|
* @internal
|
|
191
193
|
*/
|
|
192
194
|
providers: ViewManagerProvider<any>[] = [];
|
|
193
195
|
|
|
194
|
-
|
|
196
|
+
/**
|
|
197
|
+
* Data access for persisting views
|
|
198
|
+
* @internal
|
|
199
|
+
*/
|
|
200
|
+
api: ViewToBlobApi<T>;
|
|
201
|
+
|
|
202
|
+
// Last time changes were pushed to linked persistence providers
|
|
203
|
+
private lastPushed: number = null;
|
|
195
204
|
|
|
205
|
+
//---------------
|
|
206
|
+
// Getters
|
|
207
|
+
//---------------
|
|
196
208
|
get isValueDirty(): boolean {
|
|
197
209
|
return !!this.pendingValue;
|
|
198
210
|
}
|
|
@@ -266,7 +278,6 @@ export class ViewManagerModel<T = PlainObject> extends HoistModel {
|
|
|
266
278
|
this.enableAutoSave = enableAutoSave;
|
|
267
279
|
this.enableFavorites = enableFavorites;
|
|
268
280
|
this.settleTime = settleTime;
|
|
269
|
-
this.saveAsDialogModel = new SaveAsDialogModel(this);
|
|
270
281
|
this.initialViewSpec = initialViewSpec;
|
|
271
282
|
|
|
272
283
|
this.selectTask = TaskObserver.trackLast({
|
|
@@ -275,11 +286,14 @@ export class ViewManagerModel<T = PlainObject> extends HoistModel {
|
|
|
275
286
|
this.saveTask = TaskObserver.trackLast({
|
|
276
287
|
message: `Saving ${this.typeDisplayName}...`
|
|
277
288
|
});
|
|
289
|
+
|
|
290
|
+
this.saveAsDialogModel = new SaveAsDialogModel(this);
|
|
291
|
+
this.api = new ViewToBlobApi(this);
|
|
278
292
|
}
|
|
279
293
|
|
|
280
294
|
private async initAsync() {
|
|
281
295
|
try {
|
|
282
|
-
const views = await this.fetchViewInfosAsync();
|
|
296
|
+
const views = await this.api.fetchViewInfosAsync();
|
|
283
297
|
runInAction(() => (this.views = views));
|
|
284
298
|
|
|
285
299
|
if (this.persistWith) {
|
|
@@ -308,7 +322,7 @@ export class ViewManagerModel<T = PlainObject> extends HoistModel {
|
|
|
308
322
|
override async doLoadAsync(loadSpec: LoadSpec) {
|
|
309
323
|
try {
|
|
310
324
|
// 1) Update all view info
|
|
311
|
-
const views = await this.fetchViewInfosAsync();
|
|
325
|
+
const views = await this.api.fetchViewInfosAsync();
|
|
312
326
|
if (loadSpec.isStale) return;
|
|
313
327
|
runInAction(() => (this.views = views));
|
|
314
328
|
|
|
@@ -451,46 +465,15 @@ export class ViewManagerModel<T = PlainObject> extends HoistModel {
|
|
|
451
465
|
return null;
|
|
452
466
|
}
|
|
453
467
|
|
|
454
|
-
async deleteViewAsync(view: ViewInfo) {
|
|
455
|
-
try {
|
|
456
|
-
await XH.jsonBlobService.archiveAsync(view.token);
|
|
457
|
-
this.removeFavorite(view.token);
|
|
458
|
-
} catch (e) {
|
|
459
|
-
throw XH.exception({message: `Unable to delete ${view.typedName}`, cause: e});
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
async updateViewAsync(view: ViewInfo, name: string, description: string, isGlobal: boolean) {
|
|
464
|
-
try {
|
|
465
|
-
await XH.jsonBlobService.updateAsync(view.token, {
|
|
466
|
-
name: name.trim(),
|
|
467
|
-
description: description?.trim(),
|
|
468
|
-
acl: isGlobal ? '*' : null
|
|
469
|
-
});
|
|
470
|
-
} catch (e) {
|
|
471
|
-
throw XH.exception({message: `Unable to update ${view.typedName}`, cause: e});
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
async createViewAsync(name: string, description: string, value: PlainObject): Promise<View> {
|
|
476
|
-
try {
|
|
477
|
-
const blob = await XH.jsonBlobService.createAsync({
|
|
478
|
-
type: this.viewType,
|
|
479
|
-
name: name.trim(),
|
|
480
|
-
description: description?.trim(),
|
|
481
|
-
value
|
|
482
|
-
});
|
|
483
|
-
return View.fromBlob(blob, this);
|
|
484
|
-
} catch (e) {
|
|
485
|
-
throw XH.exception({message: `Unable to create ${this.typeDisplayName}`, cause: e});
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
|
|
489
468
|
//------------------
|
|
490
469
|
// Implementation
|
|
491
470
|
//------------------
|
|
492
|
-
private loadViewAsync(
|
|
493
|
-
|
|
471
|
+
private async loadViewAsync(
|
|
472
|
+
info: ViewInfo,
|
|
473
|
+
pendingValue: PendingValue<T> = null
|
|
474
|
+
): Promise<void> {
|
|
475
|
+
return this.api
|
|
476
|
+
.fetchViewAsync(info)
|
|
494
477
|
.thenAction(latest => {
|
|
495
478
|
this.setAsView(latest, pendingValue?.token == info?.token ? pendingValue : null);
|
|
496
479
|
this.providers.forEach(it => it.pushStateToTarget());
|
|
@@ -499,31 +482,6 @@ export class ViewManagerModel<T = PlainObject> extends HoistModel {
|
|
|
499
482
|
.linkTo(this.selectTask);
|
|
500
483
|
}
|
|
501
484
|
|
|
502
|
-
private async fetchViewAsync(info: ViewInfo): Promise<View<T>> {
|
|
503
|
-
if (!info) return View.createDefault();
|
|
504
|
-
try {
|
|
505
|
-
const blob = await XH.jsonBlobService.getAsync(info.token);
|
|
506
|
-
return View.fromBlob(blob, this);
|
|
507
|
-
} catch (e) {
|
|
508
|
-
throw XH.exception({message: `Unable to fetch ${info.typedName}`, cause: e});
|
|
509
|
-
}
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
private async fetchViewInfosAsync(): Promise<ViewInfo[]> {
|
|
513
|
-
try {
|
|
514
|
-
const blobs = await XH.jsonBlobService.listAsync({
|
|
515
|
-
type: this.viewType,
|
|
516
|
-
includeValue: false
|
|
517
|
-
});
|
|
518
|
-
return blobs.map(b => new ViewInfo(b, this));
|
|
519
|
-
} catch (e) {
|
|
520
|
-
throw XH.exception({
|
|
521
|
-
message: `Unable to fetch ${pluralize(this.typeDisplayName)}`,
|
|
522
|
-
cause: e
|
|
523
|
-
});
|
|
524
|
-
}
|
|
525
|
-
}
|
|
526
|
-
|
|
527
485
|
private async maybeAutoSaveAsync() {
|
|
528
486
|
const {pendingValue, isViewAutoSavable, view} = this;
|
|
529
487
|
if (isViewAutoSavable && pendingValue) {
|
|
@@ -586,7 +544,7 @@ export class ViewManagerModel<T = PlainObject> extends HoistModel {
|
|
|
586
544
|
|
|
587
545
|
private async maybeConfirmSaveAsync(info: ViewInfo, pendingValue: PendingValue<T>) {
|
|
588
546
|
// Get latest from server for reference
|
|
589
|
-
const latest = await this.fetchViewAsync(info),
|
|
547
|
+
const latest = await this.api.fetchViewAsync(info),
|
|
590
548
|
isGlobal = latest.isGlobal,
|
|
591
549
|
isStale = latest.lastUpdated > pendingValue.baseUpdated;
|
|
592
550
|
if (!isStale && !isGlobal) return true;
|
|
@@ -0,0 +1,92 @@
|
|
|
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 {PlainObject, XH} from '@xh/hoist/core';
|
|
9
|
+
import {pluralize} from '@xh/hoist/utils/js';
|
|
10
|
+
import {ViewInfo} from './ViewInfo';
|
|
11
|
+
import {View} from './View';
|
|
12
|
+
import {ViewManagerModel} from './ViewManagerModel';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Class for accessing and updating views using JSON Blobs Service.
|
|
16
|
+
*
|
|
17
|
+
* @internal
|
|
18
|
+
*/
|
|
19
|
+
export class ViewToBlobApi<T> {
|
|
20
|
+
private owner: ViewManagerModel<T>;
|
|
21
|
+
|
|
22
|
+
constructor(owner: ViewManagerModel<T>) {
|
|
23
|
+
this.owner = owner;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
//---------------
|
|
27
|
+
// Load/search.
|
|
28
|
+
//---------------
|
|
29
|
+
async fetchViewInfosAsync(): Promise<ViewInfo[]> {
|
|
30
|
+
const {owner} = this;
|
|
31
|
+
try {
|
|
32
|
+
const blobs = await XH.jsonBlobService.listAsync({
|
|
33
|
+
type: owner.viewType,
|
|
34
|
+
includeValue: false
|
|
35
|
+
});
|
|
36
|
+
return blobs.map(b => new ViewInfo(b, owner));
|
|
37
|
+
} catch (e) {
|
|
38
|
+
throw XH.exception({
|
|
39
|
+
message: `Unable to fetch ${pluralize(owner.typeDisplayName)}`,
|
|
40
|
+
cause: e
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async fetchViewAsync(info: ViewInfo): Promise<View<T>> {
|
|
46
|
+
if (!info) return View.createDefault();
|
|
47
|
+
try {
|
|
48
|
+
const blob = await XH.jsonBlobService.getAsync(info.token);
|
|
49
|
+
return View.fromBlob(blob, this.owner);
|
|
50
|
+
} catch (e) {
|
|
51
|
+
throw XH.exception({message: `Unable to fetch ${info.typedName}`, cause: e});
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
//-----------------
|
|
56
|
+
// Crud
|
|
57
|
+
//-----------------
|
|
58
|
+
async createViewAsync(name: string, description: string, value: PlainObject): Promise<View<T>> {
|
|
59
|
+
const {owner} = this;
|
|
60
|
+
try {
|
|
61
|
+
const blob = await XH.jsonBlobService.createAsync({
|
|
62
|
+
type: owner.viewType,
|
|
63
|
+
name: name.trim(),
|
|
64
|
+
description: description?.trim(),
|
|
65
|
+
value
|
|
66
|
+
});
|
|
67
|
+
return View.fromBlob(blob, owner);
|
|
68
|
+
} catch (e) {
|
|
69
|
+
throw XH.exception({message: `Unable to create ${owner.typeDisplayName}`, cause: e});
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async updateViewAsync(view: ViewInfo, name: string, description: string, isGlobal: boolean) {
|
|
74
|
+
try {
|
|
75
|
+
await XH.jsonBlobService.updateAsync(view.token, {
|
|
76
|
+
name: name.trim(),
|
|
77
|
+
description: description?.trim(),
|
|
78
|
+
acl: isGlobal ? '*' : null
|
|
79
|
+
});
|
|
80
|
+
} catch (e) {
|
|
81
|
+
throw XH.exception({message: `Unable to update ${view.typedName}`, cause: e});
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async deleteViewAsync(view: ViewInfo) {
|
|
86
|
+
try {
|
|
87
|
+
await XH.jsonBlobService.archiveAsync(view.token);
|
|
88
|
+
} catch (e) {
|
|
89
|
+
throw XH.exception({message: `Unable to delete ${view.typedName}`, cause: e});
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
* Copyright © 2024 Extremely Heavy Industries Inc.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import {fragment, hbox} from '@xh/hoist/cmp/layout';
|
|
8
|
+
import {box, fragment, hbox} from '@xh/hoist/cmp/layout';
|
|
9
|
+
import {spinner} from '@xh/hoist/cmp/spinner';
|
|
9
10
|
import {hoistCmp, HoistProps, uses} from '@xh/hoist/core';
|
|
10
11
|
import {ViewManagerModel} from '@xh/hoist/cmp/viewmanager';
|
|
11
12
|
import {button, ButtonProps} from '@xh/hoist/desktop/cmp/button';
|
|
@@ -92,7 +93,12 @@ const menuButton = hoistCmp.factory<ViewManagerModel>({
|
|
|
92
93
|
return button({
|
|
93
94
|
className: 'xh-view-manager__menu-button',
|
|
94
95
|
text: view.info?.name ?? `Default ${startCase(typeDisplayName)}`,
|
|
95
|
-
icon: !isLoading
|
|
96
|
+
icon: !isLoading
|
|
97
|
+
? Icon.bookmark()
|
|
98
|
+
: box({
|
|
99
|
+
item: spinner({width: 13, height: 13, style: {margin: 'auto'}}),
|
|
100
|
+
width: 16.25
|
|
101
|
+
}),
|
|
96
102
|
rightIcon: Icon.chevronDown(),
|
|
97
103
|
outlined: true,
|
|
98
104
|
...rest
|
|
@@ -5,12 +5,14 @@
|
|
|
5
5
|
* Copyright © 2024 Extremely Heavy Industries Inc.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import {div, filler, fragment, hbox, span} from '@xh/hoist/cmp/layout';
|
|
8
|
+
import {box, div, filler, fragment, hbox, span} from '@xh/hoist/cmp/layout';
|
|
9
|
+
import {spinner} from '@xh/hoist/cmp/spinner';
|
|
9
10
|
import {hoistCmp} from '@xh/hoist/core';
|
|
10
11
|
import {ViewManagerModel, ViewInfo} from '@xh/hoist/cmp/viewmanager';
|
|
11
12
|
import {switchInput} from '@xh/hoist/desktop/cmp/input';
|
|
12
13
|
import {Icon} from '@xh/hoist/icon';
|
|
13
14
|
import {menu, menuDivider, menuItem} from '@xh/hoist/kit/blueprint';
|
|
15
|
+
import {wait} from '@xh/hoist/promise';
|
|
14
16
|
import {consumeEvent, pluralize} from '@xh/hoist/utils/js';
|
|
15
17
|
import {isEmpty, startCase} from 'lodash';
|
|
16
18
|
import {ReactNode} from 'react';
|
|
@@ -34,7 +36,8 @@ export const viewMenu = hoistCmp.factory<ViewManagerProps>({
|
|
|
34
36
|
views,
|
|
35
37
|
isValueDirty,
|
|
36
38
|
privateViews,
|
|
37
|
-
globalViews
|
|
39
|
+
globalViews,
|
|
40
|
+
loadModel
|
|
38
41
|
} = model;
|
|
39
42
|
|
|
40
43
|
const pluralName = pluralize(startCase(typeDisplayName)),
|
|
@@ -138,10 +141,17 @@ export const viewMenu = hoistCmp.factory<ViewManagerProps>({
|
|
|
138
141
|
onClick: () => model.openManageDialog()
|
|
139
142
|
}),
|
|
140
143
|
menuItem({
|
|
141
|
-
icon:
|
|
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,
|
|
142
151
|
text: `Refresh ${pluralName}`,
|
|
143
152
|
onClick: e => {
|
|
144
|
-
|
|
153
|
+
// Ensure at least 100ms delay to render spinner
|
|
154
|
+
Promise.all([wait(100), model.refreshAsync()]).linkTo(loadModel);
|
|
145
155
|
consumeEvent(e);
|
|
146
156
|
}
|
|
147
157
|
})
|
|
@@ -15,7 +15,7 @@ import {formField} from '@xh/hoist/desktop/cmp/form';
|
|
|
15
15
|
import {select, textArea, textInput} from '@xh/hoist/desktop/cmp/input';
|
|
16
16
|
import {panel} from '@xh/hoist/desktop/cmp/panel';
|
|
17
17
|
import {toolbar} from '@xh/hoist/desktop/cmp/toolbar';
|
|
18
|
-
import {
|
|
18
|
+
import {fmtDateTime} from '@xh/hoist/format';
|
|
19
19
|
import {Icon} from '@xh/hoist/icon';
|
|
20
20
|
import {startCase} from 'lodash';
|
|
21
21
|
|
|
@@ -98,7 +98,7 @@ export const editForm = hoistCmp.factory({
|
|
|
98
98
|
filler(),
|
|
99
99
|
div({
|
|
100
100
|
className: 'xh-view-manager__manage-dialog__metadata',
|
|
101
|
-
item: `Last Updated: ${
|
|
101
|
+
item: `Last Updated: ${fmtDateTime(lastUpdated)} by ${lastUpdatedBy === XH.getUsername() ? 'you' : lastUpdatedBy}`
|
|
102
102
|
})
|
|
103
103
|
]
|
|
104
104
|
})
|
|
@@ -158,7 +158,7 @@ export class ManageDialogModel extends HoistModel {
|
|
|
158
158
|
) {
|
|
159
159
|
const {viewManagerModel} = this;
|
|
160
160
|
|
|
161
|
-
await viewManagerModel.updateViewAsync(view, name, description, isGlobal);
|
|
161
|
+
await viewManagerModel.api.updateViewAsync(view, name, description, isGlobal);
|
|
162
162
|
await viewManagerModel.refreshAsync();
|
|
163
163
|
await this.refreshAsync();
|
|
164
164
|
|
|
@@ -200,7 +200,7 @@ export class ManageDialogModel extends HoistModel {
|
|
|
200
200
|
if (!confirmed) return;
|
|
201
201
|
|
|
202
202
|
for (const view of views) {
|
|
203
|
-
await viewManagerModel.deleteViewAsync(view);
|
|
203
|
+
await viewManagerModel.api.deleteViewAsync(view);
|
|
204
204
|
}
|
|
205
205
|
|
|
206
206
|
await viewManagerModel.refreshAsync();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xh/hoist",
|
|
3
|
-
"version": "71.0.0-SNAPSHOT.
|
|
3
|
+
"version": "71.0.0-SNAPSHOT.1733372979467",
|
|
4
4
|
"description": "Hoist add-on for building and deploying React Applications.",
|
|
5
5
|
"repository": "github:xh/hoist-react",
|
|
6
6
|
"homepage": "https://xh.io",
|