@xh/hoist 70.0.0-SNAPSHOT.1731083521069 → 70.0.0-SNAPSHOT.1731374612473
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 +54 -0
- package/build/types/cmp/filter/FilterChooserModel.d.ts +17 -12
- package/build/types/cmp/grid/GridModel.d.ts +5 -9
- package/build/types/cmp/grid/Types.d.ts +7 -19
- package/build/types/cmp/grid/columns/Column.d.ts +0 -1
- package/build/types/cmp/grid/impl/InitPersist.d.ts +7 -0
- package/build/types/cmp/grouping/GroupingChooserModel.d.ts +6 -8
- package/build/types/cmp/tab/TabContainerModel.d.ts +10 -4
- package/build/types/cmp/zoneGrid/Types.d.ts +6 -6
- package/build/types/cmp/zoneGrid/ZoneGridModel.d.ts +0 -2
- package/build/types/cmp/zoneGrid/impl/InitPersist.d.ts +7 -0
- package/build/types/core/HoistBase.d.ts +1 -1
- package/build/types/core/persist/CustomProvider.d.ts +5 -6
- package/build/types/core/persist/DashViewProvider.d.ts +6 -6
- package/build/types/core/persist/LocalStorageProvider.d.ts +4 -5
- package/build/types/core/persist/PersistOptions.d.ts +5 -4
- package/build/types/core/persist/Persistable.d.ts +14 -0
- package/build/types/core/persist/PersistenceProvider.d.ts +47 -34
- package/build/types/core/persist/PrefProvider.d.ts +5 -5
- package/build/types/core/persist/index.d.ts +2 -0
- package/build/types/core/persist/viewmanager/Types.d.ts +46 -0
- package/build/types/core/persist/viewmanager/ViewManagerModel.d.ts +149 -0
- package/build/types/core/persist/viewmanager/ViewManagerProvider.d.ts +10 -0
- package/build/types/core/persist/viewmanager/impl/ManageDialogModel.d.ts +30 -0
- package/build/types/core/persist/viewmanager/impl/SaveDialogModel.d.ts +23 -0
- package/build/types/core/persist/viewmanager/index.d.ts +2 -0
- package/build/types/desktop/cmp/button/ColAutosizeButton.d.ts +1 -1
- package/build/types/desktop/cmp/dash/DashConfig.d.ts +3 -1
- package/build/types/desktop/cmp/dash/DashModel.d.ts +1 -2
- package/build/types/desktop/cmp/dash/DashViewSpec.d.ts +1 -1
- package/build/types/desktop/cmp/dash/canvas/DashCanvasModel.d.ts +10 -2
- package/build/types/desktop/cmp/dash/container/DashContainerModel.d.ts +26 -10
- package/build/types/desktop/cmp/dash/container/impl/DashContainerUtils.d.ts +4 -2
- package/build/types/desktop/cmp/panel/PanelModel.d.ts +8 -4
- package/build/types/desktop/cmp/viewmanager/ViewManager.d.ts +22 -0
- package/build/types/desktop/cmp/viewmanager/cmp/ManageDialog.d.ts +6 -0
- package/build/types/desktop/cmp/viewmanager/cmp/SaveDialog.d.ts +2 -0
- package/build/types/desktop/cmp/viewmanager/index.d.ts +3 -0
- package/build/types/kit/blueprint/Wrappers.d.ts +1 -1
- package/build/types/mobile/cmp/button/ColAutosizeButton.d.ts +1 -1
- package/build/types/svc/GridAutosizeService.d.ts +2 -5
- package/build/types/svc/JsonBlobService.d.ts +45 -24
- package/cmp/filter/FilterChooserModel.ts +142 -125
- package/cmp/grid/Grid.ts +2 -10
- package/cmp/grid/GridModel.ts +18 -31
- package/cmp/grid/Types.ts +7 -21
- package/cmp/grid/columns/Column.ts +0 -1
- package/cmp/grid/impl/InitPersist.ts +71 -0
- package/cmp/grouping/GroupingChooserModel.ts +48 -57
- package/cmp/tab/TabContainerModel.ts +22 -36
- package/cmp/zoneGrid/Types.ts +6 -6
- package/cmp/zoneGrid/ZoneGridModel.ts +2 -7
- package/cmp/zoneGrid/impl/InitPersist.ts +70 -0
- package/core/HoistBase.ts +14 -22
- package/core/HoistBaseDecorators.ts +26 -28
- package/core/persist/CustomProvider.ts +7 -10
- package/core/persist/DashViewProvider.ts +8 -10
- package/core/persist/LocalStorageProvider.ts +9 -12
- package/core/persist/PersistOptions.ts +6 -4
- package/core/persist/Persistable.ts +23 -0
- package/core/persist/PersistenceProvider.ts +159 -79
- package/core/persist/PrefProvider.ts +9 -12
- package/core/persist/index.ts +2 -0
- package/core/persist/viewmanager/Types.ts +51 -0
- package/core/persist/viewmanager/ViewManagerModel.ts +515 -0
- package/core/persist/viewmanager/ViewManagerProvider.ts +51 -0
- package/core/persist/viewmanager/impl/ManageDialogModel.ts +274 -0
- package/core/persist/viewmanager/impl/SaveDialogModel.ts +112 -0
- package/core/persist/viewmanager/index.ts +2 -0
- package/desktop/cmp/button/ColAutosizeButton.ts +1 -1
- package/desktop/cmp/dash/DashConfig.ts +3 -1
- package/desktop/cmp/dash/DashModel.ts +1 -2
- package/desktop/cmp/dash/DashViewSpec.ts +1 -1
- package/desktop/cmp/dash/canvas/DashCanvasModel.ts +31 -30
- package/desktop/cmp/dash/container/DashContainerModel.ts +68 -43
- package/desktop/cmp/dash/container/impl/DashContainerUtils.ts +13 -4
- package/desktop/cmp/leftrightchooser/LeftRightChooserFilter.ts +1 -1
- package/desktop/cmp/panel/PanelModel.ts +33 -53
- package/desktop/cmp/store/impl/StoreFilterField.ts +1 -1
- package/desktop/cmp/viewmanager/ViewManager.scss +58 -0
- package/desktop/cmp/viewmanager/ViewManager.ts +274 -0
- package/desktop/cmp/viewmanager/cmp/ManageDialog.ts +197 -0
- package/desktop/cmp/viewmanager/cmp/SaveDialog.ts +89 -0
- package/desktop/cmp/viewmanager/index.ts +3 -0
- package/mobile/cmp/button/ColAutosizeButton.ts +1 -1
- package/package.json +1 -1
- package/svc/GridAutosizeService.ts +73 -36
- package/svc/JsonBlobService.ts +64 -31
- package/tsconfig.tsbuildinfo +1 -1
- package/build/types/cmp/grid/impl/GridPersistenceModel.d.ts +0 -41
- package/build/types/cmp/zoneGrid/impl/ZoneGridPersistenceModel.d.ts +0 -39
- package/cmp/grid/impl/GridPersistenceModel.ts +0 -174
- package/cmp/zoneGrid/impl/ZoneGridPersistenceModel.ts +0 -149
|
@@ -1,35 +1,56 @@
|
|
|
1
|
-
import { HoistService } from '@xh/hoist/core';
|
|
1
|
+
import { HoistService, LoadSpec, PlainObject } from '@xh/hoist/core';
|
|
2
|
+
export interface JsonBlob {
|
|
3
|
+
/** Either null for private blobs or special token "*" for globally shared blobs. */
|
|
4
|
+
acl: string;
|
|
5
|
+
/** True if this blob has been archived (soft-deleted). */
|
|
6
|
+
archived: boolean;
|
|
7
|
+
/** Timestamp indicating when this blob was archived, or special value `0` if not archived. */
|
|
8
|
+
archivedDate: number;
|
|
9
|
+
dateCreated: number;
|
|
10
|
+
description: string;
|
|
11
|
+
/** @internal database ID for this blob. Favor token instead. */
|
|
12
|
+
id: number;
|
|
13
|
+
lastUpdated: number;
|
|
14
|
+
lastUpdatedBy: string;
|
|
15
|
+
/** Optional application-specific metadata. */
|
|
16
|
+
meta: PlainObject | unknown[];
|
|
17
|
+
name: string;
|
|
18
|
+
/** Username of the blob's creator / owner. */
|
|
19
|
+
owner: string;
|
|
20
|
+
/** Primary unique identifier for getting, updating, and archiving blobs. */
|
|
21
|
+
token: string;
|
|
22
|
+
/**
|
|
23
|
+
* Application defined type for this blob. Used as a discriminator when querying blobs related
|
|
24
|
+
* to a particular use-case within an application.
|
|
25
|
+
*/
|
|
26
|
+
type: string;
|
|
27
|
+
/**
|
|
28
|
+
* Current JSON value of this blob - its contents or data. Will be undefined for blob stubs
|
|
29
|
+
* returned by `listAsync` if `includeValue` was false.
|
|
30
|
+
*/
|
|
31
|
+
value?: any;
|
|
32
|
+
}
|
|
2
33
|
/**
|
|
3
34
|
* Service to read and set chunks of user-specific JSON persisted via Hoist Core's JSONBlob class.
|
|
35
|
+
*
|
|
36
|
+
* This service is intended as a general, lightweight utility for persisting small bundles of
|
|
37
|
+
* unstructured data that might not warrant a full-blown domain object, but which still need to be
|
|
38
|
+
* persisted back to the database.
|
|
4
39
|
*/
|
|
5
40
|
export declare class JsonBlobService extends HoistService {
|
|
6
41
|
static instance: JsonBlobService;
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
* @param type - reference key for which type of data to list.
|
|
12
|
-
* @param includeValue - true to include the full value string for each blob.
|
|
13
|
-
*/
|
|
14
|
-
listAsync({ type, includeValue }: {
|
|
42
|
+
/** Retrieve a single JSONBlob by its unique token. */
|
|
43
|
+
getAsync(token: string): Promise<JsonBlob>;
|
|
44
|
+
/** Retrieve all blobs of a particular type that are visible to the current user. */
|
|
45
|
+
listAsync({ type, includeValue, loadSpec }: {
|
|
15
46
|
type: string;
|
|
16
47
|
includeValue?: boolean;
|
|
48
|
+
loadSpec?: LoadSpec;
|
|
17
49
|
}): Promise<any>;
|
|
18
50
|
/** Persist a new JSONBlob back to the server. */
|
|
19
|
-
createAsync({
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
description?: string;
|
|
23
|
-
value: any;
|
|
24
|
-
meta?: any;
|
|
25
|
-
}): Promise<any>;
|
|
26
|
-
/** Modify an existing JSONBlob, as identified by its unique token. */
|
|
27
|
-
updateAsync(token: string, { name, value, meta, description }: {
|
|
28
|
-
name?: string;
|
|
29
|
-
value?: any;
|
|
30
|
-
meta?: any;
|
|
31
|
-
description?: string;
|
|
32
|
-
}): Promise<any>;
|
|
51
|
+
createAsync({ acl, description, type, meta, name, value }: Partial<JsonBlob>): Promise<JsonBlob>;
|
|
52
|
+
/** Modify mutable properties of an existing JSONBlob, as identified by its unique token. */
|
|
53
|
+
updateAsync(token: string, { acl, description, meta, name, value }: Partial<JsonBlob>): Promise<JsonBlob>;
|
|
33
54
|
/** Archive (soft-delete) an existing JSONBlob, as identified by its unique token. */
|
|
34
|
-
archiveAsync(token: string): Promise<
|
|
55
|
+
archiveAsync(token: string): Promise<JsonBlob>;
|
|
35
56
|
}
|
|
@@ -7,10 +7,11 @@
|
|
|
7
7
|
import {
|
|
8
8
|
HoistModel,
|
|
9
9
|
managed,
|
|
10
|
+
PersistableState,
|
|
10
11
|
PersistenceProvider,
|
|
11
12
|
PersistOptions,
|
|
12
|
-
PlainObject,
|
|
13
13
|
TaskObserver,
|
|
14
|
+
Thunkable,
|
|
14
15
|
XH
|
|
15
16
|
} from '@xh/hoist/core';
|
|
16
17
|
import {
|
|
@@ -26,9 +27,8 @@ import {
|
|
|
26
27
|
import {CompoundFilterSpec, FieldFilterSpec, FilterLike} from '@xh/hoist/data/filter/Types';
|
|
27
28
|
import {action, makeObservable, observable} from '@xh/hoist/mobx';
|
|
28
29
|
import {wait} from '@xh/hoist/promise';
|
|
29
|
-
import {throwIf, withDefault} from '@xh/hoist/utils/js';
|
|
30
|
+
import {executeIfFunction, throwIf, withDefault} from '@xh/hoist/utils/js';
|
|
30
31
|
import {createObservableRef} from '@xh/hoist/utils/react';
|
|
31
|
-
import {ReactNode} from 'react';
|
|
32
32
|
import {
|
|
33
33
|
cloneDeep,
|
|
34
34
|
compact,
|
|
@@ -39,13 +39,14 @@ import {
|
|
|
39
39
|
isArray,
|
|
40
40
|
isEmpty,
|
|
41
41
|
isFinite,
|
|
42
|
-
|
|
42
|
+
isObject,
|
|
43
43
|
isString,
|
|
44
44
|
partition,
|
|
45
45
|
sortBy,
|
|
46
46
|
uniq,
|
|
47
47
|
uniqBy
|
|
48
48
|
} from 'lodash';
|
|
49
|
+
import {ReactNode} from 'react';
|
|
49
50
|
|
|
50
51
|
import {FilterChooserFieldSpec, FilterChooserFieldSpecConfig} from './FilterChooserFieldSpec';
|
|
51
52
|
import {compoundFilterOption, fieldFilterOption, FilterChooserOption} from './impl/Option';
|
|
@@ -80,12 +81,12 @@ export interface FilterChooserConfig {
|
|
|
80
81
|
* to produce the same. Note that FilterChooser currently can only edit and create a flat collection
|
|
81
82
|
* of FieldFilters, to be 'AND'ed together.
|
|
82
83
|
*/
|
|
83
|
-
initialValue?: FilterChooserFilterLike
|
|
84
|
+
initialValue?: Thunkable<FilterChooserFilterLike>;
|
|
84
85
|
|
|
85
86
|
/**
|
|
86
87
|
* Initial favorites as an array of filter configurations, or a function to produce such an array.
|
|
87
88
|
*/
|
|
88
|
-
initialFavorites?: FilterChooserFilterLike[]
|
|
89
|
+
initialFavorites?: Thunkable<FilterChooserFilterLike[]>;
|
|
89
90
|
|
|
90
91
|
/**
|
|
91
92
|
* true to offer all field suggestions when the control is focused with an empty query,
|
|
@@ -131,9 +132,6 @@ export class FilterChooserModel extends HoistModel {
|
|
|
131
132
|
maxTags: number;
|
|
132
133
|
maxResults: number;
|
|
133
134
|
introHelpText: ReactNode;
|
|
134
|
-
|
|
135
|
-
@managed provider: PersistenceProvider;
|
|
136
|
-
persistValue: boolean = false;
|
|
137
135
|
persistFavorites: boolean = false;
|
|
138
136
|
|
|
139
137
|
/** Tracks execution of filtering operation on bound object.*/
|
|
@@ -174,39 +172,12 @@ export class FilterChooserModel extends HoistModel {
|
|
|
174
172
|
this.introHelpText = withDefault(introHelpText, this.getDefaultIntroHelpText());
|
|
175
173
|
this.queryEngine = new QueryEngine(this);
|
|
176
174
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
f => parseFilter(f)
|
|
180
|
-
);
|
|
181
|
-
|
|
182
|
-
// Read state from provider -- fail gently
|
|
183
|
-
if (persistWith) {
|
|
184
|
-
try {
|
|
185
|
-
this.provider = PersistenceProvider.create({path: 'filterChooser', ...persistWith});
|
|
186
|
-
this.persistValue = persistWith.persistValue ?? true;
|
|
187
|
-
this.persistFavorites = persistWith.persistFavorites ?? true;
|
|
188
|
-
|
|
189
|
-
const state = this.provider.read();
|
|
190
|
-
if (this.persistValue && state?.value) {
|
|
191
|
-
value = state.value;
|
|
192
|
-
}
|
|
193
|
-
if (this.persistFavorites && state?.favorites) {
|
|
194
|
-
favorites = state.favorites.map(f => parseFilter(f));
|
|
195
|
-
}
|
|
175
|
+
this.setValueInternal(executeIfFunction(initialValue), false);
|
|
176
|
+
this.setFavorites(executeIfFunction(initialFavorites));
|
|
196
177
|
|
|
197
|
-
|
|
198
|
-
track: () => this.persistState,
|
|
199
|
-
run: state => this.provider.write(state)
|
|
200
|
-
});
|
|
201
|
-
} catch (e) {
|
|
202
|
-
this.logError(e);
|
|
203
|
-
XH.safeDestroy(this.provider);
|
|
204
|
-
this.provider = null;
|
|
205
|
-
}
|
|
206
|
-
}
|
|
178
|
+
if (persistWith) this.initPersist(persistWith);
|
|
207
179
|
|
|
208
|
-
this.
|
|
209
|
-
this.setFavorites(favorites);
|
|
180
|
+
this.updateSelectValueAndBind();
|
|
210
181
|
|
|
211
182
|
if (bind) {
|
|
212
183
|
this.addReaction({
|
|
@@ -231,73 +202,8 @@ export class FilterChooserModel extends HoistModel {
|
|
|
231
202
|
*
|
|
232
203
|
* Any other Filter is unsupported and will cause the control to show a placeholder error.
|
|
233
204
|
*/
|
|
234
|
-
@action
|
|
235
205
|
setValue(rawValue: FilterLike) {
|
|
236
|
-
|
|
237
|
-
try {
|
|
238
|
-
const value = parseFilter(rawValue);
|
|
239
|
-
if (this.value?.equals(value)) return;
|
|
240
|
-
|
|
241
|
-
// 1) Ensure FilterChooser can handle the requested value.
|
|
242
|
-
const isValid = this.validateFilter(value),
|
|
243
|
-
displayFilters = isValid ? this.toDisplayFilters(value) : null;
|
|
244
|
-
|
|
245
|
-
this.unsupportedFilter = !isValid || (maxTags && displayFilters.length > maxTags);
|
|
246
|
-
if (this.unsupportedFilter) {
|
|
247
|
-
this.value = null;
|
|
248
|
-
this.selectOptions = null;
|
|
249
|
-
this.selectValue = null;
|
|
250
|
-
return;
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
// 2) Main path - filter has been validated as supported, set internal value.
|
|
254
|
-
this.logDebug('Setting value', value);
|
|
255
|
-
this.value = value as FilterChooserFilter;
|
|
256
|
-
|
|
257
|
-
// 3) Set props on select input needed to display
|
|
258
|
-
// Build list of options, used for displaying tags. We combine the needed
|
|
259
|
-
// options for the current filter tags with any previous ones to ensure
|
|
260
|
-
// tags are rendered correctly throughout the transition.
|
|
261
|
-
const newOptions = displayFilters.map(f => this.createFilterOption(f)),
|
|
262
|
-
previousOptions = this.selectOptions ?? [],
|
|
263
|
-
options = uniqBy([...newOptions, ...previousOptions], 'value');
|
|
264
|
-
|
|
265
|
-
this.selectOptions = !isEmpty(options) ? options : null;
|
|
266
|
-
|
|
267
|
-
// 4) Do the next steps asynchronously for UI responsiveness and to ensure the component
|
|
268
|
-
// is ready to render the tags correctly (after selectOptions set above).
|
|
269
|
-
wait()
|
|
270
|
-
.thenAction(() => {
|
|
271
|
-
// No-op if we've already re-entered this method by the time this async routine runs.
|
|
272
|
-
if (this.value !== value) {
|
|
273
|
-
return;
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
this.selectValue = sortBy(
|
|
277
|
-
displayFilters.map(f => JSON.stringify(f)),
|
|
278
|
-
f => {
|
|
279
|
-
const idx = this.selectValue?.indexOf(f);
|
|
280
|
-
return isFinite(idx) && idx > -1 ? idx : displayFilters.length;
|
|
281
|
-
}
|
|
282
|
-
);
|
|
283
|
-
|
|
284
|
-
// 5) Round-trip value to bound filter
|
|
285
|
-
if (bind) {
|
|
286
|
-
const filter = withFilterByTypes(bind.filter, value, [
|
|
287
|
-
'FieldFilter',
|
|
288
|
-
'CompoundFilter'
|
|
289
|
-
]);
|
|
290
|
-
bind.setFilter(filter);
|
|
291
|
-
}
|
|
292
|
-
})
|
|
293
|
-
.linkTo(this.filterTask);
|
|
294
|
-
} catch (e) {
|
|
295
|
-
this.logError('Failed to set value', e);
|
|
296
|
-
this.value = null;
|
|
297
|
-
this.selectOptions = null;
|
|
298
|
-
this.selectValue = null;
|
|
299
|
-
this.unsupportedFilter = true;
|
|
300
|
-
}
|
|
206
|
+
this.setValueInternal(rawValue, true);
|
|
301
207
|
}
|
|
302
208
|
|
|
303
209
|
//---------------------------
|
|
@@ -422,8 +328,8 @@ export class FilterChooserModel extends HoistModel {
|
|
|
422
328
|
}
|
|
423
329
|
|
|
424
330
|
@action
|
|
425
|
-
setFavorites(favorites:
|
|
426
|
-
this.favorites = favorites.filter(this.validateFilter.bind(this));
|
|
331
|
+
setFavorites(favorites: FilterChooserFilterLike[]) {
|
|
332
|
+
this.favorites = favorites.map(parseFilter).filter(this.validateFilter.bind(this));
|
|
427
333
|
}
|
|
428
334
|
|
|
429
335
|
@action
|
|
@@ -445,16 +351,6 @@ export class FilterChooserModel extends HoistModel {
|
|
|
445
351
|
return !!this.findFavorite(filter);
|
|
446
352
|
}
|
|
447
353
|
|
|
448
|
-
//-------------------------
|
|
449
|
-
// Persistence handling
|
|
450
|
-
//-------------------------
|
|
451
|
-
get persistState() {
|
|
452
|
-
const ret: PlainObject = {};
|
|
453
|
-
if (this.persistValue) ret.value = this.value;
|
|
454
|
-
if (this.persistFavorites) ret.favorites = this.favorites;
|
|
455
|
-
return ret;
|
|
456
|
-
}
|
|
457
|
-
|
|
458
354
|
//--------------------------------
|
|
459
355
|
// FilterChooserFieldSpec handling
|
|
460
356
|
//--------------------------------
|
|
@@ -507,22 +403,143 @@ export class FilterChooserModel extends HoistModel {
|
|
|
507
403
|
getDefaultIntroHelpText(): string {
|
|
508
404
|
return 'Select or enter a field name (below) or begin typing to match available field values.';
|
|
509
405
|
}
|
|
406
|
+
|
|
407
|
+
// -------------------------------
|
|
408
|
+
// Implementation
|
|
409
|
+
// -------------------------------
|
|
410
|
+
private initPersist({
|
|
411
|
+
persistValue = true,
|
|
412
|
+
persistFavorites = true,
|
|
413
|
+
path = 'filterChooser',
|
|
414
|
+
...rootPersistWith
|
|
415
|
+
}: FilterChooserPersistOptions) {
|
|
416
|
+
if (persistValue) {
|
|
417
|
+
const status = {initialized: false},
|
|
418
|
+
persistWith = isObject(persistValue) ? persistValue : rootPersistWith;
|
|
419
|
+
PersistenceProvider.create({
|
|
420
|
+
persistOptions: {
|
|
421
|
+
path: `${path}.value`,
|
|
422
|
+
...persistWith
|
|
423
|
+
},
|
|
424
|
+
target: {
|
|
425
|
+
getPersistableState: () => new PersistableState(this.value?.toJSON() ?? null),
|
|
426
|
+
setPersistableState: ({value}) =>
|
|
427
|
+
this.setValueInternal(value, status.initialized)
|
|
428
|
+
},
|
|
429
|
+
owner: this
|
|
430
|
+
});
|
|
431
|
+
status.initialized = true;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
if (persistFavorites) {
|
|
435
|
+
const persistWith = isObject(persistFavorites) ? persistFavorites : rootPersistWith,
|
|
436
|
+
provider = PersistenceProvider.create({
|
|
437
|
+
persistOptions: {
|
|
438
|
+
path: `${path}.favorites`,
|
|
439
|
+
...persistWith
|
|
440
|
+
},
|
|
441
|
+
target: {
|
|
442
|
+
getPersistableState: () =>
|
|
443
|
+
new PersistableState(this.favorites.map(f => f.toJSON())),
|
|
444
|
+
setPersistableState: ({value}) => this.setFavorites(value)
|
|
445
|
+
},
|
|
446
|
+
owner: this
|
|
447
|
+
});
|
|
448
|
+
if (provider) this.persistFavorites = true;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
@action
|
|
453
|
+
private setValueInternal(rawValue: FilterLike, updateSelectValueAndBind: boolean) {
|
|
454
|
+
const {maxTags} = this;
|
|
455
|
+
try {
|
|
456
|
+
const value = parseFilter(rawValue);
|
|
457
|
+
if (this.value?.equals(value)) return;
|
|
458
|
+
|
|
459
|
+
// 1) Ensure FilterChooser can handle the requested value.
|
|
460
|
+
const isValid = this.validateFilter(value),
|
|
461
|
+
displayFilters = isValid ? this.toDisplayFilters(value) : null;
|
|
462
|
+
|
|
463
|
+
this.unsupportedFilter = !isValid || (maxTags && displayFilters.length > maxTags);
|
|
464
|
+
if (this.unsupportedFilter) {
|
|
465
|
+
this.value = null;
|
|
466
|
+
this.selectOptions = null;
|
|
467
|
+
this.selectValue = null;
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// 2) Main path - filter has been validated as supported, set internal value.
|
|
472
|
+
this.logDebug('Setting value', value);
|
|
473
|
+
this.value = value as FilterChooserFilter;
|
|
474
|
+
|
|
475
|
+
// 3) Set props on select input needed to display
|
|
476
|
+
// Build list of options, used for displaying tags. We combine the needed
|
|
477
|
+
// options for the current filter tags with any previous ones to ensure
|
|
478
|
+
// tags are rendered correctly throughout the transition.
|
|
479
|
+
const newOptions = displayFilters.map(f => this.createFilterOption(f)),
|
|
480
|
+
previousOptions = this.selectOptions ?? [],
|
|
481
|
+
options = uniqBy([...newOptions, ...previousOptions], 'value');
|
|
482
|
+
|
|
483
|
+
this.selectOptions = !isEmpty(options) ? options : null;
|
|
484
|
+
|
|
485
|
+
if (updateSelectValueAndBind) this.updateSelectValueAndBind(displayFilters);
|
|
486
|
+
} catch (e) {
|
|
487
|
+
this.logError('Failed to set value', e);
|
|
488
|
+
this.value = null;
|
|
489
|
+
this.selectOptions = null;
|
|
490
|
+
this.selectValue = null;
|
|
491
|
+
this.unsupportedFilter = true;
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
/**
|
|
496
|
+
* Update the select value and bind the filter to the bound model. Runs asynchronously after
|
|
497
|
+
* selectOptions are set to ensure the component is ready to render the tags correctly.
|
|
498
|
+
*/
|
|
499
|
+
private updateSelectValueAndBind(displayFilters = this.toDisplayFilters(this.value)) {
|
|
500
|
+
const {bind, value} = this;
|
|
501
|
+
wait()
|
|
502
|
+
.thenAction(() => {
|
|
503
|
+
// No-op if we've already re-entered this method by the time this async routine runs.
|
|
504
|
+
if (this.value !== value) {
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
this.selectValue = sortBy(
|
|
509
|
+
displayFilters.map(f => JSON.stringify(f)),
|
|
510
|
+
f => {
|
|
511
|
+
const idx = this.selectValue?.indexOf(f);
|
|
512
|
+
return isFinite(idx) && idx > -1 ? idx : displayFilters.length;
|
|
513
|
+
}
|
|
514
|
+
);
|
|
515
|
+
|
|
516
|
+
// Round-trip value to bound filter
|
|
517
|
+
if (bind) {
|
|
518
|
+
const filter = withFilterByTypes(bind.filter, value, [
|
|
519
|
+
'FieldFilter',
|
|
520
|
+
'CompoundFilter'
|
|
521
|
+
]);
|
|
522
|
+
bind.setFilter(filter);
|
|
523
|
+
}
|
|
524
|
+
})
|
|
525
|
+
.linkTo(this.filterTask);
|
|
526
|
+
}
|
|
510
527
|
}
|
|
511
528
|
|
|
512
529
|
interface FilterChooserPersistOptions extends PersistOptions {
|
|
513
|
-
/** True (default) to
|
|
514
|
-
persistValue?: boolean;
|
|
530
|
+
/** True (default) to include value or provide value-specific PersistOptions. */
|
|
531
|
+
persistValue?: boolean | PersistOptions;
|
|
515
532
|
|
|
516
|
-
/** True (default) to include favorites. */
|
|
517
|
-
persistFavorites?: boolean;
|
|
533
|
+
/** True (default) to include favorites or provide favorites-specific PersistOptions. */
|
|
534
|
+
persistFavorites?: boolean | PersistOptions;
|
|
518
535
|
}
|
|
519
536
|
|
|
520
537
|
/** A variant of {@link Filter} that excludes FunctionFilter (unsupported by FilterChooser). */
|
|
521
538
|
export type FilterChooserFilter = CompoundFilter | FieldFilter;
|
|
539
|
+
export type FilterChooserFilterSpec = CompoundFilterSpec | FieldFilterSpec;
|
|
522
540
|
|
|
523
541
|
/** A variant of {@link FilterLike} that excludes FunctionFilters and FilterTestFn. */
|
|
524
542
|
export type FilterChooserFilterLike =
|
|
525
|
-
|
|
|
526
|
-
|
|
|
527
|
-
| FieldFilterSpec
|
|
543
|
+
| FilterChooserFilter
|
|
544
|
+
| FilterChooserFilterSpec
|
|
528
545
|
| FilterChooserFilterLike[];
|
package/cmp/grid/Grid.ts
CHANGED
|
@@ -681,16 +681,8 @@ export class GridLocalModel extends HoistModel {
|
|
|
681
681
|
}
|
|
682
682
|
|
|
683
683
|
if (model.autosizeOptions.mode === 'managed') {
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
model.autosizeAsync();
|
|
687
|
-
} else {
|
|
688
|
-
// ...otherwise, only autosize columns that are not manually sized
|
|
689
|
-
const columns = model.columnState
|
|
690
|
-
.filter(it => !it.manuallySized)
|
|
691
|
-
.map(it => it.colId);
|
|
692
|
-
model.autosizeAsync({columns});
|
|
693
|
-
}
|
|
684
|
+
const columns = model.columnState.filter(it => !it.manuallySized).map(it => it.colId);
|
|
685
|
+
model.autosizeAsync({columns});
|
|
694
686
|
}
|
|
695
687
|
|
|
696
688
|
model.noteAgExpandStateChange();
|
package/cmp/grid/GridModel.ts
CHANGED
|
@@ -95,10 +95,9 @@ import {ReactNode} from 'react';
|
|
|
95
95
|
import {GridAutosizeOptions} from './GridAutosizeOptions';
|
|
96
96
|
import {GridContextMenuSpec} from './GridContextMenu';
|
|
97
97
|
import {GridSorter, GridSorterLike} from './GridSorter';
|
|
98
|
-
import {
|
|
98
|
+
import {initPersist} from './impl/InitPersist';
|
|
99
99
|
import {managedRenderer} from './impl/Utils';
|
|
100
100
|
import {
|
|
101
|
-
AutosizeState,
|
|
102
101
|
ColChooserConfig,
|
|
103
102
|
ColumnState,
|
|
104
103
|
GridModelPersistOptions,
|
|
@@ -405,10 +404,13 @@ export class GridModel extends HoistModel {
|
|
|
405
404
|
@observable.ref columns: Array<ColumnGroup | Column> = [];
|
|
406
405
|
@observable.ref columnState: ColumnState[] = [];
|
|
407
406
|
@observable.ref expandState: any = {};
|
|
408
|
-
@observable.ref autosizeState: AutosizeState = {};
|
|
409
407
|
@observable.ref sortBy: GridSorter[] = [];
|
|
410
408
|
@observable.ref groupBy: string[] = null;
|
|
411
409
|
|
|
410
|
+
get persistableColumnState(): ColumnState[] {
|
|
411
|
+
return this.cleanColumnState(this.columnState);
|
|
412
|
+
}
|
|
413
|
+
|
|
412
414
|
@bindable showSummary: boolean | VSide = false;
|
|
413
415
|
@bindable.ref emptyText: ReactNode;
|
|
414
416
|
@bindable treeStyle: TreeStyle;
|
|
@@ -446,7 +448,6 @@ export class GridModel extends HoistModel {
|
|
|
446
448
|
];
|
|
447
449
|
|
|
448
450
|
private _defaultState; // initial state provided to ctor - powers restoreDefaults().
|
|
449
|
-
@managed persistenceModel: GridPersistenceModel;
|
|
450
451
|
|
|
451
452
|
/**
|
|
452
453
|
* Is autosizing enabled on this grid?
|
|
@@ -599,7 +600,7 @@ export class GridModel extends HoistModel {
|
|
|
599
600
|
this.colChooserModel = this.parseChooserModel(colChooserModel);
|
|
600
601
|
this.selModel = this.parseSelModel(selModel);
|
|
601
602
|
this.filterModel = this.parseFilterModel(filterModel);
|
|
602
|
-
|
|
603
|
+
if (persistWith) initPersist(this, persistWith);
|
|
603
604
|
this.experimental = this.parseExperimental(experimental);
|
|
604
605
|
this.onKeyDown = onKeyDown;
|
|
605
606
|
this.onRowClicked = onRowClicked;
|
|
@@ -649,7 +650,6 @@ export class GridModel extends HoistModel {
|
|
|
649
650
|
this.setGroupBy(groupBy);
|
|
650
651
|
|
|
651
652
|
this.filterModel?.clear();
|
|
652
|
-
this.persistenceModel?.clear();
|
|
653
653
|
|
|
654
654
|
if (this.autosizeOptions.mode === 'managed') {
|
|
655
655
|
await this.autosizeAsync();
|
|
@@ -1112,13 +1112,6 @@ export class GridModel extends HoistModel {
|
|
|
1112
1112
|
}
|
|
1113
1113
|
}
|
|
1114
1114
|
|
|
1115
|
-
@action
|
|
1116
|
-
setAutosizeState(autosizeState) {
|
|
1117
|
-
if (!equal(this.autosizeState, autosizeState)) {
|
|
1118
|
-
this.autosizeState = deepFreeze(autosizeState);
|
|
1119
|
-
}
|
|
1120
|
-
}
|
|
1121
|
-
|
|
1122
1115
|
noteColumnManuallySized(colId, width) {
|
|
1123
1116
|
const col = this.findColumn(this.columns, colId);
|
|
1124
1117
|
if (!width || !col || col.flex) return;
|
|
@@ -1126,12 +1119,6 @@ export class GridModel extends HoistModel {
|
|
|
1126
1119
|
this.applyColumnStateChanges(colStateChanges);
|
|
1127
1120
|
}
|
|
1128
1121
|
|
|
1129
|
-
noteColumnsAutosized(colIds) {
|
|
1130
|
-
const colStateChanges = castArray(colIds).map(colId => ({colId, manuallySized: false}));
|
|
1131
|
-
this.applyColumnStateChanges(colStateChanges);
|
|
1132
|
-
this.setAutosizeState({sizingMode: this.sizingMode});
|
|
1133
|
-
}
|
|
1134
|
-
|
|
1135
1122
|
/**
|
|
1136
1123
|
* This method will update the current column definition if it has changed.
|
|
1137
1124
|
* Throws an exception if any of the columns provided in colStateChanges are not
|
|
@@ -1291,11 +1278,11 @@ export class GridModel extends HoistModel {
|
|
|
1291
1278
|
* columns are also ignored unless {@link GridAutosizeOptions.includeHiddenColumns} has been
|
|
1292
1279
|
* set to true.
|
|
1293
1280
|
*
|
|
1294
|
-
* @param
|
|
1281
|
+
* @param overrideOpts - optional overrides of this model's {@link GridAutosizeOptions}.
|
|
1295
1282
|
*/
|
|
1296
1283
|
@logWithDebug
|
|
1297
|
-
async autosizeAsync(
|
|
1298
|
-
options = {...this.autosizeOptions, ...
|
|
1284
|
+
async autosizeAsync(overrideOpts: Omit<GridAutosizeOptions, 'mode'> = {}) {
|
|
1285
|
+
const options = {...this.autosizeOptions, ...overrideOpts};
|
|
1299
1286
|
|
|
1300
1287
|
if (options.mode === 'disabled') {
|
|
1301
1288
|
return;
|
|
@@ -1305,16 +1292,16 @@ export class GridModel extends HoistModel {
|
|
|
1305
1292
|
const {columns} = options;
|
|
1306
1293
|
if (columns) options.fillMode = 'none'; // Fill makes sense only for the entire set.
|
|
1307
1294
|
|
|
1308
|
-
let colIds,
|
|
1309
|
-
includeColFn =
|
|
1295
|
+
let colIds: string[],
|
|
1296
|
+
includeColFn = (_: Column) => true;
|
|
1310
1297
|
if (isFunction(columns)) {
|
|
1311
|
-
includeColFn = columns as (col) => boolean;
|
|
1298
|
+
includeColFn = columns as (col: Column) => boolean;
|
|
1312
1299
|
colIds = this.columnState.map(it => it.colId);
|
|
1313
1300
|
} else {
|
|
1314
|
-
colIds = columns
|
|
1301
|
+
colIds = columns ? castArray(columns) : this.columnState.map(it => it.colId);
|
|
1315
1302
|
}
|
|
1316
1303
|
|
|
1317
|
-
colIds =
|
|
1304
|
+
colIds = colIds.filter(id => {
|
|
1318
1305
|
if (!options.includeHiddenColumns && !this.isColumnVisible(id)) return false;
|
|
1319
1306
|
const col = this.getColumn(id);
|
|
1320
1307
|
return col && col.autosizable && !col.flex && includeColFn(col);
|
|
@@ -1327,7 +1314,7 @@ export class GridModel extends HoistModel {
|
|
|
1327
1314
|
|
|
1328
1315
|
/**
|
|
1329
1316
|
* Begin an inline editing session.
|
|
1330
|
-
* @param
|
|
1317
|
+
* @param record - StoreRecord/ID to edit. If unspecified, the first selected StoreRecord
|
|
1331
1318
|
* will be used, if any, or the first overall StoreRecord in the grid.
|
|
1332
1319
|
* @param colId - ID of column on which to start editing. If unspecified, the first
|
|
1333
1320
|
* editable column will be used.
|
|
@@ -1472,7 +1459,7 @@ export class GridModel extends HoistModel {
|
|
|
1472
1459
|
return new Column(config, this);
|
|
1473
1460
|
}
|
|
1474
1461
|
|
|
1475
|
-
private async autosizeColsInternalAsync(colIds, options) {
|
|
1462
|
+
private async autosizeColsInternalAsync(colIds: string[], options: GridAutosizeOptions) {
|
|
1476
1463
|
await this.whenReadyAsync();
|
|
1477
1464
|
if (!this.isReady) return;
|
|
1478
1465
|
|
|
@@ -1485,7 +1472,6 @@ export class GridModel extends HoistModel {
|
|
|
1485
1472
|
|
|
1486
1473
|
try {
|
|
1487
1474
|
await XH.gridAutosizeService.autosizeAsync(this, colIds, options);
|
|
1488
|
-
this.noteColumnsAutosized(colIds);
|
|
1489
1475
|
} finally {
|
|
1490
1476
|
if (showMask) {
|
|
1491
1477
|
await wait();
|
|
@@ -1616,7 +1602,8 @@ export class GridModel extends HoistModel {
|
|
|
1616
1602
|
// Remove the width from any non-resizable column - we don't want to track those widths as
|
|
1617
1603
|
// they are set programmatically (e.g. fixed / action columns), and saved state should not
|
|
1618
1604
|
// conflict with any code-level updates to their widths.
|
|
1619
|
-
if (!col.resizable) state = omit(state, 'width');
|
|
1605
|
+
if (!col.resizable || !state.manuallySized) state = omit(state, 'width');
|
|
1606
|
+
state = {...state, manuallySized: state.manuallySized ?? false};
|
|
1620
1607
|
|
|
1621
1608
|
// Remove all metadata other than the id and the hidden state from hidden columns, to save
|
|
1622
1609
|
// on space when storing user configs with large amounts of hidden fields.
|
package/cmp/grid/Types.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import {IRowNode} from '@xh/hoist/kit/ag-grid';
|
|
9
9
|
import {GridFilterFieldSpecConfig} from '@xh/hoist/cmp/grid/filter/GridFilterFieldSpec';
|
|
10
|
-
import {HSide, PersistOptions,
|
|
10
|
+
import {HSide, PersistOptions, Some} from '@xh/hoist/core';
|
|
11
11
|
import {Store, StoreRecord, View} from '@xh/hoist/data';
|
|
12
12
|
import {ReactElement, ReactNode} from 'react';
|
|
13
13
|
import {Column} from './columns/Column';
|
|
@@ -35,11 +35,6 @@ export interface ColumnState {
|
|
|
35
35
|
pinned?: HSide;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
export interface AutosizeState {
|
|
39
|
-
/** Sizing mode used last time the columns were autosized. */
|
|
40
|
-
sizingMode?: SizingMode;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
38
|
/**
|
|
44
39
|
* Comparator for custom grid group sorting, provided to GridModel.
|
|
45
40
|
* @param groupAVal - first group value to be compared.
|
|
@@ -79,21 +74,12 @@ export type RowClassFn = (record: StoreRecord) => Some<string>;
|
|
|
79
74
|
export type RowClassRuleFn = (agParams: RowClassParams) => boolean;
|
|
80
75
|
|
|
81
76
|
export interface GridModelPersistOptions extends PersistOptions {
|
|
82
|
-
/** True to include column
|
|
83
|
-
persistColumns?: boolean;
|
|
84
|
-
/** True to include grouping
|
|
85
|
-
persistGrouping?: boolean;
|
|
86
|
-
/** True to include
|
|
87
|
-
persistSort?: boolean;
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Key to be used to identify location of legacy grid state from LocalStorage.
|
|
91
|
-
* This key will identify the pre-v35 location for grid state, and will be used
|
|
92
|
-
* as an initial source of grid state after an upgrade to v35.0.0 or greater.
|
|
93
|
-
* Defaults to the new value of 'key'. If no legacy state is available at this
|
|
94
|
-
* location, the key is ignored.
|
|
95
|
-
*/
|
|
96
|
-
legacyStateKey?: string;
|
|
77
|
+
/** True (default) to include column state or provide column-specific PersistOptions. */
|
|
78
|
+
persistColumns?: boolean | PersistOptions;
|
|
79
|
+
/** True (default) to include grouping state or provide grouping-specific PersistOptions. */
|
|
80
|
+
persistGrouping?: boolean | PersistOptions;
|
|
81
|
+
/** True (default) to include sort state or provide sort-specific PersistOptions. */
|
|
82
|
+
persistSort?: boolean | PersistOptions;
|
|
97
83
|
}
|
|
98
84
|
|
|
99
85
|
export interface GridFilterModelConfig {
|