@xh/hoist 71.0.0-SNAPSHOT.1735310060928 → 71.0.0-SNAPSHOT.1735324763102
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 +6 -0
- package/build/types/cmp/viewmanager/{ViewToBlobApi.d.ts → DataAccess.d.ts} +13 -4
- package/build/types/cmp/viewmanager/ViewManagerModel.d.ts +14 -13
- package/build/types/cmp/viewmanager/index.d.ts +1 -1
- package/build/types/svc/JsonBlobService.d.ts +5 -1
- package/cmp/viewmanager/DataAccess.ts +179 -0
- package/cmp/viewmanager/ViewManagerModel.ts +111 -140
- package/cmp/viewmanager/index.ts +1 -1
- package/mobile/cmp/tab/impl/Tabs.scss +3 -4
- package/package.json +1 -1
- package/svc/JsonBlobService.ts +25 -10
- package/tsconfig.tsbuildinfo +1 -1
- package/cmp/viewmanager/ViewToBlobApi.ts +0 -199
package/CHANGELOG.md
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
* `TreeMap` and `SplitTreeMap` are now cross-platform and can be used in mobile applications.
|
|
15
15
|
Their import paths have changed from `@xh/hoist/desktop/cmp/treemap` to `@xh/hoist/cmp/treemap`.
|
|
16
16
|
* The `RefreshButton` `model` prop has been renamed `target` for clarity and consistency.
|
|
17
|
+
|
|
17
18
|
### 🎁 New Features
|
|
18
19
|
|
|
19
20
|
* Major Improvements to ViewManager component
|
|
@@ -27,6 +28,11 @@
|
|
|
27
28
|
* Added support for `AuthZeroClientConfig.audience` to support improved configuration of Auth0 OAuth
|
|
28
29
|
clients requesting access tokens, covering cases when third-party cookies are blocked.
|
|
29
30
|
|
|
31
|
+
### 🐞 Bug Fixes
|
|
32
|
+
|
|
33
|
+
* Fixed sizing and position of mobile `TabContainer` switcher, particularly when the switcher is
|
|
34
|
+
positioned with `top` orientation.
|
|
35
|
+
|
|
30
36
|
### ⚙️ Technical
|
|
31
37
|
|
|
32
38
|
* Added explicit `devDependencies` and `resolutions` blocks for `@types/react[-dom]` at v18.x.
|
|
@@ -16,15 +16,24 @@ export interface ViewUpdateSpec {
|
|
|
16
16
|
isShared?: boolean;
|
|
17
17
|
isDefaultPinned?: boolean;
|
|
18
18
|
}
|
|
19
|
+
export interface ViewUserState {
|
|
20
|
+
currentView?: string;
|
|
21
|
+
userPinned: Record<string, boolean>;
|
|
22
|
+
autoSave: boolean;
|
|
23
|
+
}
|
|
19
24
|
/**
|
|
20
|
-
*
|
|
25
|
+
* Supporting class for accessing and updating ViewManager and View data.
|
|
26
|
+
*
|
|
21
27
|
* @internal
|
|
22
28
|
*/
|
|
23
|
-
export declare class
|
|
29
|
+
export declare class DataAccess<T> {
|
|
24
30
|
private readonly model;
|
|
25
31
|
constructor(model: ViewManagerModel<T>);
|
|
26
32
|
/** Fetch metadata for all views accessible by current user. */
|
|
27
|
-
|
|
33
|
+
fetchDataAsync(): Promise<{
|
|
34
|
+
views: ViewInfo[];
|
|
35
|
+
state: ViewUserState;
|
|
36
|
+
}>;
|
|
28
37
|
/** Fetch the latest version of a view. */
|
|
29
38
|
fetchViewAsync(info: ViewInfo): Promise<View<T>>;
|
|
30
39
|
/** Create a new view, owned by the current user.*/
|
|
@@ -36,6 +45,6 @@ export declare class ViewToBlobApi<T> {
|
|
|
36
45
|
/** Update a view's value. */
|
|
37
46
|
updateViewValueAsync(view: View<T>, value: Partial<T>): Promise<View<T>>;
|
|
38
47
|
deleteViewsAsync(views: ViewInfo[]): Promise<void>;
|
|
39
|
-
|
|
48
|
+
updateStateAsync(update: Partial<ViewUserState>): Promise<void>;
|
|
40
49
|
private ensureEditable;
|
|
41
50
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { HoistModel, LoadSpec,
|
|
1
|
+
import { HoistModel, LoadSpec, PlainObject, TaskObserver, Thunkable } from '@xh/hoist/core';
|
|
2
2
|
import type { ViewManagerProvider } from '@xh/hoist/core';
|
|
3
3
|
import { ViewInfo } from './ViewInfo';
|
|
4
4
|
import { View } from './View';
|
|
5
|
-
import { ViewCreateSpec, ViewUpdateSpec } from './
|
|
5
|
+
import { ViewCreateSpec, ViewUpdateSpec } from './DataAccess';
|
|
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.
|
|
@@ -44,8 +44,6 @@ export interface ViewManagerConfig {
|
|
|
44
44
|
* commonly set this based on user roles - e.g. `XH.getUser().hasRole('MANAGE_GRID_VIEWS')`.
|
|
45
45
|
*/
|
|
46
46
|
manageGlobal?: Thunkable<boolean>;
|
|
47
|
-
/** Used to persist the user's state. */
|
|
48
|
-
persistWith?: ViewManagerPersistOptions;
|
|
49
47
|
/**
|
|
50
48
|
* Required discriminator for the particular class of views to be loaded and managed by this
|
|
51
49
|
* model. Set to something descriptive and specific enough to be identifiable and allow for
|
|
@@ -53,6 +51,12 @@ export interface ViewManagerConfig {
|
|
|
53
51
|
* `tradeBlotterDashboard`.
|
|
54
52
|
*/
|
|
55
53
|
type: string;
|
|
54
|
+
/**
|
|
55
|
+
* Optional sub-discriminator for the particular location in your app this instance of the
|
|
56
|
+
* view manager appears in. A particular currentView and pendingValue will be maintained by
|
|
57
|
+
* instance, but all other options, and the available library of views will be shared by type.
|
|
58
|
+
*/
|
|
59
|
+
instance?: string;
|
|
56
60
|
/**
|
|
57
61
|
* Optional user-facing display name for the view type, displayed in the ViewManager menu
|
|
58
62
|
* and associated management dialogs and prompts. Defaulted from `type` if not provided.
|
|
@@ -63,12 +67,6 @@ export interface ViewManagerConfig {
|
|
|
63
67
|
*/
|
|
64
68
|
globalDisplayName?: string;
|
|
65
69
|
}
|
|
66
|
-
export interface ViewManagerPersistOptions extends PersistOptions {
|
|
67
|
-
/** True to persist pinning preferences or provide specific PersistOptions. (Default true) */
|
|
68
|
-
persistPinning?: boolean | PersistOptions;
|
|
69
|
-
/** True to include pending value or provide specific PersistOptions. (Default false) */
|
|
70
|
-
persistPendingValue?: boolean | PersistOptions;
|
|
71
|
-
}
|
|
72
70
|
/**
|
|
73
71
|
* ViewManagerModel coordinates the loading, saving, and management of user-defined bundles of
|
|
74
72
|
* {@link Persistable} component/model state.
|
|
@@ -100,8 +98,8 @@ export declare class ViewManagerModel<T = PlainObject> extends HoistModel {
|
|
|
100
98
|
*/
|
|
101
99
|
static createAsync(config: ViewManagerConfig): Promise<ViewManagerModel>;
|
|
102
100
|
/** Immutable configuration for this model. */
|
|
103
|
-
persistWith: ViewManagerPersistOptions;
|
|
104
101
|
readonly type: string;
|
|
102
|
+
readonly instance: string;
|
|
105
103
|
readonly typeDisplayName: string;
|
|
106
104
|
readonly globalDisplayName: string;
|
|
107
105
|
readonly enableAutoSave: boolean;
|
|
@@ -144,7 +142,7 @@ export declare class ViewManagerModel<T = PlainObject> extends HoistModel {
|
|
|
144
142
|
*/
|
|
145
143
|
providers: ViewManagerProvider<any>[];
|
|
146
144
|
/** Data access for persisting views. */
|
|
147
|
-
private
|
|
145
|
+
private dataAccess;
|
|
148
146
|
/** Last time changes were pushed to linked persistence providers */
|
|
149
147
|
private lastPushed;
|
|
150
148
|
get isValueDirty(): boolean;
|
|
@@ -183,16 +181,19 @@ export declare class ViewManagerModel<T = PlainObject> extends HoistModel {
|
|
|
183
181
|
makeViewGlobalAsync(view: ViewInfo): Promise<View<T>>;
|
|
184
182
|
deleteViewsAsync(toDelete: ViewInfo[]): Promise<void>;
|
|
185
183
|
private initAsync;
|
|
184
|
+
private pendingValueReaction;
|
|
185
|
+
private autoSaveReaction;
|
|
186
|
+
private stateReactions;
|
|
186
187
|
private loadViewAsync;
|
|
187
188
|
private maybeAutoSaveAsync;
|
|
188
189
|
private setAsView;
|
|
189
190
|
private handleException;
|
|
190
191
|
private noteSuccess;
|
|
192
|
+
private get pendingValueStorageKey();
|
|
191
193
|
/**
|
|
192
194
|
* Stringify and parse to ensure that any value set here is valid, serializable JSON.
|
|
193
195
|
*/
|
|
194
196
|
private cleanState;
|
|
195
197
|
private confirmDiscardChangesAsync;
|
|
196
198
|
private maybeConfirmSaveAsync;
|
|
197
|
-
private initPersist;
|
|
198
199
|
}
|
|
@@ -50,7 +50,11 @@ export declare class JsonBlobService extends HoistService {
|
|
|
50
50
|
/** Persist a new JSONBlob back to the server. */
|
|
51
51
|
createAsync({ acl, description, type, meta, name, value }: Partial<JsonBlob>): Promise<JsonBlob>;
|
|
52
52
|
/** Modify mutable properties of an existing JSONBlob, as identified by its unique token. */
|
|
53
|
-
updateAsync(token: string,
|
|
53
|
+
updateAsync(token: string, update: Partial<JsonBlob>): Promise<JsonBlob>;
|
|
54
|
+
/** Create or update a blob for a user with the existing type and name. */
|
|
55
|
+
createOrUpdateAsync(type: string, name: string, data: Partial<JsonBlob>): Promise<JsonBlob>;
|
|
56
|
+
/** Find a blob owned by this user with a specific type and name. If none exists, return null. */
|
|
57
|
+
findAsync(type: string, name: string): Promise<JsonBlob>;
|
|
54
58
|
/** Archive (soft-delete) an existing JSONBlob, as identified by its unique token. */
|
|
55
59
|
archiveAsync(token: string): Promise<JsonBlob>;
|
|
56
60
|
}
|
|
@@ -0,0 +1,179 @@
|
|
|
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, throwIf} from '@xh/hoist/utils/js';
|
|
10
|
+
import {map} from 'lodash';
|
|
11
|
+
import {ViewInfo} from './ViewInfo';
|
|
12
|
+
import {View} from './View';
|
|
13
|
+
import {ViewManagerModel} from './ViewManagerModel';
|
|
14
|
+
|
|
15
|
+
export interface ViewCreateSpec {
|
|
16
|
+
name: string;
|
|
17
|
+
group: string;
|
|
18
|
+
description: string;
|
|
19
|
+
isShared: boolean;
|
|
20
|
+
value?: PlainObject;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface ViewUpdateSpec {
|
|
24
|
+
name: string;
|
|
25
|
+
group: string;
|
|
26
|
+
description: string;
|
|
27
|
+
isShared?: boolean;
|
|
28
|
+
isDefaultPinned?: boolean;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface ViewUserState {
|
|
32
|
+
currentView?: string;
|
|
33
|
+
userPinned: Record<string, boolean>;
|
|
34
|
+
autoSave: boolean;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Supporting class for accessing and updating ViewManager and View data.
|
|
39
|
+
*
|
|
40
|
+
* @internal
|
|
41
|
+
*/
|
|
42
|
+
export class DataAccess<T> {
|
|
43
|
+
private readonly model: ViewManagerModel<T>;
|
|
44
|
+
|
|
45
|
+
constructor(model: ViewManagerModel<T>) {
|
|
46
|
+
this.model = model;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
//---------------
|
|
50
|
+
// Load/search.
|
|
51
|
+
//---------------
|
|
52
|
+
/** Fetch metadata for all views accessible by current user. */
|
|
53
|
+
async fetchDataAsync(): Promise<{views: ViewInfo[]; state: ViewUserState}> {
|
|
54
|
+
const {typeDisplayName, type, instance} = this.model;
|
|
55
|
+
try {
|
|
56
|
+
const ret = await XH.fetchJson({
|
|
57
|
+
url: 'xhView/allData',
|
|
58
|
+
params: {type, viewInstance: instance}
|
|
59
|
+
});
|
|
60
|
+
return {
|
|
61
|
+
views: ret.views.map(v => new ViewInfo(v, this.model)),
|
|
62
|
+
state: ret.state
|
|
63
|
+
};
|
|
64
|
+
} catch (e) {
|
|
65
|
+
throw XH.exception({
|
|
66
|
+
message: `Unable to fetch ${pluralize(typeDisplayName)}`,
|
|
67
|
+
cause: e
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Fetch the latest version of a view. */
|
|
73
|
+
async fetchViewAsync(info: ViewInfo): Promise<View<T>> {
|
|
74
|
+
const {model} = this;
|
|
75
|
+
if (!info) return View.createDefault(model);
|
|
76
|
+
try {
|
|
77
|
+
const raw = await XH.fetchJson({url: 'xhView/get', params: {token: info.token}});
|
|
78
|
+
return View.fromBlob(raw, model);
|
|
79
|
+
} catch (e) {
|
|
80
|
+
throw XH.exception({message: `Unable to fetch ${info.typedName}`, cause: e});
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Create a new view, owned by the current user.*/
|
|
85
|
+
async createViewAsync(spec: ViewCreateSpec): Promise<View<T>> {
|
|
86
|
+
const {model} = this;
|
|
87
|
+
try {
|
|
88
|
+
const raw = await XH.postJson({
|
|
89
|
+
url: 'xhView/create',
|
|
90
|
+
body: {type: model.type, ...spec}
|
|
91
|
+
});
|
|
92
|
+
return View.fromBlob(raw, model);
|
|
93
|
+
} catch (e) {
|
|
94
|
+
throw XH.exception({message: `Unable to create ${model.typeDisplayName}`, cause: e});
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Update all aspects of a view's metadata.*/
|
|
99
|
+
async updateViewInfoAsync(view: ViewInfo, updates: ViewUpdateSpec): Promise<View<T>> {
|
|
100
|
+
try {
|
|
101
|
+
this.ensureEditable(view);
|
|
102
|
+
const raw = await XH.postJson({
|
|
103
|
+
url: 'xhView/updateViewInfo',
|
|
104
|
+
params: {token: view.token},
|
|
105
|
+
body: updates
|
|
106
|
+
});
|
|
107
|
+
return View.fromBlob(raw, this.model);
|
|
108
|
+
} catch (e) {
|
|
109
|
+
throw XH.exception({message: `Unable to update ${view.typedName}`, cause: e});
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** Promote a view to global visibility/ownership status. */
|
|
114
|
+
async makeViewGlobalAsync(view: ViewInfo): Promise<View<T>> {
|
|
115
|
+
try {
|
|
116
|
+
this.ensureEditable(view);
|
|
117
|
+
const raw = await XH.fetchJson({url: 'xhView/makeGlobal', params: {token: view.token}});
|
|
118
|
+
return View.fromBlob(raw, this.model);
|
|
119
|
+
} catch (e) {
|
|
120
|
+
throw XH.exception({message: `Unable to update ${view.typedName}`, cause: e});
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** Update a view's value. */
|
|
125
|
+
async updateViewValueAsync(view: View<T>, value: Partial<T>): Promise<View<T>> {
|
|
126
|
+
try {
|
|
127
|
+
this.ensureEditable(view.info);
|
|
128
|
+
const raw = await XH.postJson({
|
|
129
|
+
url: 'xhView/updateValue',
|
|
130
|
+
params: {token: view.token},
|
|
131
|
+
body: value
|
|
132
|
+
});
|
|
133
|
+
return View.fromBlob(raw, this.model);
|
|
134
|
+
} catch (e) {
|
|
135
|
+
throw XH.exception({
|
|
136
|
+
message: `Unable to update value for ${view.typedName}`,
|
|
137
|
+
cause: e
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async deleteViewsAsync(views: ViewInfo[]) {
|
|
143
|
+
views.forEach(v => this.ensureEditable(v));
|
|
144
|
+
try {
|
|
145
|
+
await XH.postJson({
|
|
146
|
+
url: 'xhView/delete',
|
|
147
|
+
params: {tokens: map(views, 'token').join(',')}
|
|
148
|
+
});
|
|
149
|
+
} catch (e) {
|
|
150
|
+
throw XH.exception({
|
|
151
|
+
message: `Failed to delete ${pluralize(this.model.typeDisplayName)}`,
|
|
152
|
+
cause: e
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
//--------------------------
|
|
158
|
+
// State related changes
|
|
159
|
+
//--------------------------
|
|
160
|
+
async updateStateAsync(update: Partial<ViewUserState>) {
|
|
161
|
+
const {type, instance} = this.model;
|
|
162
|
+
await XH.postJson({
|
|
163
|
+
url: 'xhView/updateState',
|
|
164
|
+
params: {type, viewInstance: instance},
|
|
165
|
+
body: update
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
//------------------
|
|
170
|
+
// Implementation
|
|
171
|
+
//------------------
|
|
172
|
+
private ensureEditable(view: ViewInfo) {
|
|
173
|
+
const {model} = this;
|
|
174
|
+
throwIf(
|
|
175
|
+
!view.isEditable,
|
|
176
|
+
`Cannot save changes to ${model.typeDisplayName} - missing required permission.`
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
}
|