@xh/hoist 71.0.0-SNAPSHOT.1735311286067 → 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.
@@ -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
- * Class for accessing and updating views using {@link JsonBlobService}.
25
+ * Supporting class for accessing and updating ViewManager and View data.
26
+ *
21
27
  * @internal
22
28
  */
23
- export declare class ViewToBlobApi<T> {
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
- fetchViewInfosAsync(): Promise<ViewInfo[]>;
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
- private trackChange;
48
+ updateStateAsync(update: Partial<ViewUserState>): Promise<void>;
40
49
  private ensureEditable;
41
50
  }
@@ -1,8 +1,8 @@
1
- import { HoistModel, LoadSpec, PersistOptions, PlainObject, TaskObserver, Thunkable } from '@xh/hoist/core';
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 './ViewToBlobApi';
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 api;
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
  }
@@ -1,4 +1,4 @@
1
1
  export * from './ViewManagerModel';
2
2
  export * from './ViewInfo';
3
3
  export * from './View';
4
- export * from './ViewToBlobApi';
4
+ export * from './DataAccess';
@@ -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, { acl, description, meta, name, owner, value }: Partial<JsonBlob>): Promise<JsonBlob>;
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
+ }