@xh/hoist 59.3.2 → 59.4.0
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 +25 -3
- package/admin/differ/DifferModel.ts +5 -7
- package/appcontainer/AppContainerModel.ts +8 -10
- package/appcontainer/AppStateModel.ts +3 -2
- package/appcontainer/PageStateModel.ts +1 -2
- package/appcontainer/SizingModeModel.ts +2 -2
- package/cmp/ag-grid/AgGrid.ts +4 -3
- package/cmp/ag-grid/AgGridModel.ts +8 -9
- package/cmp/chart/Chart.ts +4 -3
- package/cmp/dataview/DataViewModel.ts +2 -2
- package/cmp/filter/FilterChooserModel.ts +5 -5
- package/cmp/grid/Grid.ts +2 -2
- package/cmp/grid/GridContextMenu.ts +2 -2
- package/cmp/grid/GridModel.ts +13 -21
- package/cmp/grid/Types.ts +6 -5
- package/cmp/grid/columns/Column.ts +12 -12
- package/cmp/grid/columns/ColumnGroup.ts +17 -6
- package/cmp/grid/helpers/GridCountLabel.ts +5 -4
- package/cmp/grid/impl/ColumnWidthCalculator.ts +3 -3
- package/cmp/grid/impl/GridPersistenceModel.ts +11 -4
- package/cmp/grid/renderers/MultiFieldRenderer.ts +28 -22
- package/cmp/grouping/GroupingChooserModel.ts +2 -2
- package/cmp/tab/TabContainerModel.ts +2 -2
- package/cmp/zoneGrid/impl/ZoneGridPersistenceModel.ts +10 -4
- package/core/HoistBase.ts +44 -5
- package/core/elem.ts +2 -2
- package/core/impl/InstallServices.ts +2 -8
- package/core/load/LoadSupport.ts +3 -3
- package/core/model/HoistModel.ts +1 -1
- package/data/Store.ts +1 -1
- package/data/UrlStore.ts +3 -3
- package/data/filter/CompoundFilter.ts +5 -3
- package/data/filter/FieldFilter.ts +4 -3
- package/data/filter/Filter.ts +2 -3
- package/data/filter/FunctionFilter.ts +2 -1
- package/data/impl/RecordSet.ts +5 -5
- package/desktop/appcontainer/ToastSource.ts +1 -1
- package/desktop/cmp/rest/Actions.ts +15 -9
- package/desktop/cmp/treemap/TreeMap.ts +4 -8
- package/package.json +1 -1
- package/svc/AutoRefreshService.ts +3 -3
- package/svc/FetchService.ts +5 -5
- package/svc/GridAutosizeService.ts +4 -7
- package/svc/TrackService.ts +6 -6
- package/svc/WebSocketService.ts +14 -15
- package/utils/async/AsyncUtils.ts +3 -2
- package/utils/async/Timer.ts +4 -3
- package/utils/js/BrowserUtils.ts +8 -8
- package/utils/js/LangUtils.ts +10 -9
- package/utils/js/LogUtils.ts +66 -26
- package/utils/react/LayoutPropUtils.ts +3 -3
|
@@ -4,11 +4,11 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Copyright © 2023 Extremely Heavy Industries Inc.
|
|
6
6
|
*/
|
|
7
|
-
import {GridModel} from '../GridModel';
|
|
8
7
|
import {box} from '@xh/hoist/cmp/layout';
|
|
9
8
|
import {BoxProps, hoistCmp, HoistProps, useContextModel} from '@xh/hoist/core';
|
|
10
9
|
import {fmtNumber} from '@xh/hoist/format';
|
|
11
|
-
import {pluralize, singularize, withDefault} from '@xh/hoist/utils/js';
|
|
10
|
+
import {logError, pluralize, singularize, withDefault} from '@xh/hoist/utils/js';
|
|
11
|
+
import {GridModel} from '../GridModel';
|
|
12
12
|
|
|
13
13
|
export interface GridCountLabelProps extends HoistProps, BoxProps {
|
|
14
14
|
/** GridModel to which this component should bind. */
|
|
@@ -49,8 +49,9 @@ export const [GridCountLabel, gridCountLabel] = hoistCmp.withFactory<GridCountLa
|
|
|
49
49
|
gridModel = withDefault(gridModel, useContextModel(GridModel));
|
|
50
50
|
|
|
51
51
|
if (!gridModel) {
|
|
52
|
-
|
|
53
|
-
|
|
52
|
+
logError(
|
|
53
|
+
`GridModel not found - provide via 'gridModel' prop or context.`,
|
|
54
|
+
GridCountLabel
|
|
54
55
|
);
|
|
55
56
|
return '';
|
|
56
57
|
}
|
|
@@ -9,7 +9,7 @@ import {GridAutosizeOptions} from '@xh/hoist/cmp/grid/GridAutosizeOptions';
|
|
|
9
9
|
import {XH} from '@xh/hoist/core';
|
|
10
10
|
import {CompoundFilter, FieldFilter, Filter, StoreRecord} from '@xh/hoist/data';
|
|
11
11
|
import {forEachAsync} from '@xh/hoist/utils/async';
|
|
12
|
-
import {stripTags} from '@xh/hoist/utils/js';
|
|
12
|
+
import {logWarn, stripTags} from '@xh/hoist/utils/js';
|
|
13
13
|
import {
|
|
14
14
|
forOwn,
|
|
15
15
|
groupBy,
|
|
@@ -74,7 +74,7 @@ export class ColumnWidthCalculator {
|
|
|
74
74
|
try {
|
|
75
75
|
return this.getHeaderWidth(gridModel, column, autosizeIncludeHeaderIcons, bufferPx);
|
|
76
76
|
} catch (e) {
|
|
77
|
-
|
|
77
|
+
logWarn([`Error calculating max header width for colId '${column.colId}'.`, e], this);
|
|
78
78
|
} finally {
|
|
79
79
|
this.resetHeaderClassNames();
|
|
80
80
|
}
|
|
@@ -104,7 +104,7 @@ export class ColumnWidthCalculator {
|
|
|
104
104
|
return await this.calcLevelWidthAsync(gridModel, records, column, options);
|
|
105
105
|
}
|
|
106
106
|
} catch (e) {
|
|
107
|
-
|
|
107
|
+
logWarn([`Error calculating max data width for colId '${column.colId}'.`, e], this);
|
|
108
108
|
} finally {
|
|
109
109
|
this.resetClassNames();
|
|
110
110
|
}
|
|
@@ -4,11 +4,12 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Copyright © 2023 Extremely Heavy Industries Inc.
|
|
6
6
|
*/
|
|
7
|
-
import {
|
|
7
|
+
import {GridSorterLike} from '@xh/hoist/cmp/grid';
|
|
8
|
+
import {HoistModel, managed, PersistenceProvider, Some, XH} from '@xh/hoist/core';
|
|
8
9
|
import {action, makeObservable, observable} from '@xh/hoist/mobx';
|
|
9
10
|
import {isUndefined} from 'lodash';
|
|
10
11
|
import {GridModel} from '../GridModel';
|
|
11
|
-
import {GridModelPersistOptions} from '../Types';
|
|
12
|
+
import {ColumnState, GridModelPersistOptions} from '../Types';
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* Model to manage persisting state from GridModel.
|
|
@@ -22,7 +23,13 @@ export class GridPersistenceModel extends HoistModel {
|
|
|
22
23
|
gridModel: GridModel;
|
|
23
24
|
|
|
24
25
|
@observable.ref
|
|
25
|
-
state:
|
|
26
|
+
state: {
|
|
27
|
+
columns?: Partial<ColumnState>[];
|
|
28
|
+
sortBy?: Some<GridSorterLike>;
|
|
29
|
+
groupBy?: Some<string>;
|
|
30
|
+
version?: number;
|
|
31
|
+
autosize?: any;
|
|
32
|
+
};
|
|
26
33
|
|
|
27
34
|
@managed
|
|
28
35
|
provider: PersistenceProvider;
|
|
@@ -51,7 +58,7 @@ export class GridPersistenceModel extends HoistModel {
|
|
|
51
58
|
run: state => this.provider.write(state)
|
|
52
59
|
});
|
|
53
60
|
} catch (e) {
|
|
54
|
-
|
|
61
|
+
this.logError(e);
|
|
55
62
|
this.state = {version: this.VERSION};
|
|
56
63
|
}
|
|
57
64
|
|
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import {ColumnRenderer} from '@xh/hoist/cmp/grid';
|
|
8
8
|
import {div, span} from '@xh/hoist/cmp/layout';
|
|
9
|
-
import {throwIf, warnIf} from '@xh/hoist/utils/js';
|
|
10
|
-
import {isString, partition} from 'lodash';
|
|
9
|
+
import {throwIf, warnIf, intersperse} from '@xh/hoist/utils/js';
|
|
10
|
+
import {isNil, isString, partition, pull} from 'lodash';
|
|
11
11
|
import {ReactNode} from 'react';
|
|
12
12
|
|
|
13
13
|
/**
|
|
@@ -30,24 +30,24 @@ export function multiFieldRenderer(value, context): ReactNode {
|
|
|
30
30
|
);
|
|
31
31
|
|
|
32
32
|
const {mainRenderer, delimiter, subFields = []} = multiFieldConfig,
|
|
33
|
-
[topFields, bottomFields] = partition(subFields, it => it.position === 'top')
|
|
34
|
-
topRowItems = [],
|
|
35
|
-
bottomRowItems = [];
|
|
33
|
+
[topFields, bottomFields] = partition(subFields, it => it.position === 'top');
|
|
36
34
|
|
|
37
|
-
// Render main field to top row
|
|
38
|
-
topRowItems
|
|
35
|
+
// Render main field and subfields to top row
|
|
36
|
+
let topRowItems: ReactNode[] = [
|
|
37
|
+
renderMainField(value, mainRenderer, context),
|
|
38
|
+
...topFields.map(it => renderSubField(it, context))
|
|
39
|
+
];
|
|
40
|
+
pull(topRowItems, null);
|
|
39
41
|
|
|
40
|
-
// Render
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
topRowItems.push(renderSubField(it, context));
|
|
44
|
-
});
|
|
42
|
+
// Render subfield to bottom row
|
|
43
|
+
let bottomRowItems: ReactNode[] = bottomFields.map(it => renderSubField(it, context));
|
|
44
|
+
pull(bottomRowItems, null);
|
|
45
45
|
|
|
46
|
-
//
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
bottomRowItems
|
|
50
|
-
}
|
|
46
|
+
// Insert delimiter if applicable
|
|
47
|
+
if (delimiter) {
|
|
48
|
+
topRowItems = intersperse(topRowItems, renderDelimiter(delimiter));
|
|
49
|
+
bottomRowItems = intersperse(bottomRowItems, renderDelimiter(delimiter));
|
|
50
|
+
}
|
|
51
51
|
|
|
52
52
|
return div({
|
|
53
53
|
className: 'xh-multifield-renderer',
|
|
@@ -107,14 +107,20 @@ function renderSubField({colId, label}, context) {
|
|
|
107
107
|
|
|
108
108
|
if (label && !isString(label)) label = headerName;
|
|
109
109
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
110
|
+
const renderedVal = renderValue(value, renderer, column, context),
|
|
111
|
+
renderedValIsEmpty = renderedVal === '' || isNil(renderedVal);
|
|
112
|
+
|
|
113
|
+
return renderedValIsEmpty
|
|
114
|
+
? null
|
|
115
|
+
: div({
|
|
116
|
+
className: 'xh-multifield-renderer-field',
|
|
117
|
+
items: [label ? `${label}: ` : null, renderedVal]
|
|
118
|
+
});
|
|
114
119
|
}
|
|
115
120
|
|
|
116
121
|
function renderValue(value, renderer, column, context) {
|
|
117
|
-
|
|
122
|
+
const ret = renderer ? renderer(value, {...context, column}) : value;
|
|
123
|
+
return isNil(ret) ? null : ret;
|
|
118
124
|
}
|
|
119
125
|
|
|
120
126
|
function renderDelimiter(delimiter) {
|
|
@@ -170,7 +170,7 @@ export class GroupingChooserModel extends HoistModel {
|
|
|
170
170
|
run: state => this.provider.write(state)
|
|
171
171
|
});
|
|
172
172
|
} catch (e) {
|
|
173
|
-
|
|
173
|
+
this.logError(e);
|
|
174
174
|
XH.safeDestroy(this.provider);
|
|
175
175
|
this.provider = null;
|
|
176
176
|
}
|
|
@@ -190,7 +190,7 @@ export class GroupingChooserModel extends HoistModel {
|
|
|
190
190
|
@action
|
|
191
191
|
setValue(value: string[]) {
|
|
192
192
|
if (!this.validateValue(value)) {
|
|
193
|
-
|
|
193
|
+
this.logWarn('Attempted to set invalid value', value);
|
|
194
194
|
return;
|
|
195
195
|
}
|
|
196
196
|
this.value = value;
|
|
@@ -144,7 +144,7 @@ export class TabContainerModel extends HoistModel {
|
|
|
144
144
|
|
|
145
145
|
if (route) {
|
|
146
146
|
if (XH.isMobileApp) {
|
|
147
|
-
|
|
147
|
+
this.logWarn('TabContainer routing is not supported for mobile applications.');
|
|
148
148
|
return;
|
|
149
149
|
}
|
|
150
150
|
|
|
@@ -384,7 +384,7 @@ export class TabContainerModel extends HoistModel {
|
|
|
384
384
|
this.provider = PersistenceProvider.create({path: 'tabContainer', ...persistWith});
|
|
385
385
|
state = this.provider.read() || null;
|
|
386
386
|
} catch (e) {
|
|
387
|
-
|
|
387
|
+
this.logError(e);
|
|
388
388
|
XH.safeDestroy(this.provider);
|
|
389
389
|
this.provider = null;
|
|
390
390
|
}
|
|
@@ -4,11 +4,12 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Copyright © 2023 Extremely Heavy Industries Inc.
|
|
6
6
|
*/
|
|
7
|
-
import {
|
|
7
|
+
import {GridSorterLike} from '@xh/hoist/cmp/grid';
|
|
8
|
+
import {HoistModel, managed, PersistenceProvider, Some} from '@xh/hoist/core';
|
|
8
9
|
import {action, makeObservable, observable} from '@xh/hoist/mobx';
|
|
9
10
|
import {isUndefined} from 'lodash';
|
|
10
11
|
import {ZoneGridModel} from '../ZoneGridModel';
|
|
11
|
-
import {ZoneGridModelPersistOptions} from '../Types';
|
|
12
|
+
import {Zone, ZoneGridModelPersistOptions, ZoneMapping} from '../Types';
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* Model to manage persisting state from ZoneGridModel.
|
|
@@ -22,7 +23,12 @@ export class ZoneGridPersistenceModel extends HoistModel {
|
|
|
22
23
|
zoneGridModel: ZoneGridModel;
|
|
23
24
|
|
|
24
25
|
@observable.ref
|
|
25
|
-
state:
|
|
26
|
+
state: {
|
|
27
|
+
sortBy?: GridSorterLike;
|
|
28
|
+
groupBy?: Some<string>;
|
|
29
|
+
version?: number;
|
|
30
|
+
mappings?: Record<Zone, Some<string | ZoneMapping>>;
|
|
31
|
+
};
|
|
26
32
|
|
|
27
33
|
@managed
|
|
28
34
|
provider: PersistenceProvider;
|
|
@@ -51,7 +57,7 @@ export class ZoneGridPersistenceModel extends HoistModel {
|
|
|
51
57
|
run: state => this.provider.write(state)
|
|
52
58
|
});
|
|
53
59
|
} catch (e) {
|
|
54
|
-
|
|
60
|
+
this.logError(e);
|
|
55
61
|
this.state = {version: this.VERSION};
|
|
56
62
|
}
|
|
57
63
|
|
package/core/HoistBase.ts
CHANGED
|
@@ -4,8 +4,17 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Copyright © 2023 Extremely Heavy Industries Inc.
|
|
6
6
|
*/
|
|
7
|
-
import {XH, PersistenceProvider, PersistOptions, DebounceSpec} from './';
|
|
8
|
-
import {
|
|
7
|
+
import {XH, PersistenceProvider, PersistOptions, DebounceSpec, Some} from './';
|
|
8
|
+
import {
|
|
9
|
+
throwIf,
|
|
10
|
+
getOrCreate,
|
|
11
|
+
logInfo,
|
|
12
|
+
logDebug,
|
|
13
|
+
logError,
|
|
14
|
+
logWarn,
|
|
15
|
+
withDebug,
|
|
16
|
+
withInfo
|
|
17
|
+
} from '@xh/hoist/utils/js';
|
|
9
18
|
import {
|
|
10
19
|
cloneDeep,
|
|
11
20
|
debounce as lodashDebounce,
|
|
@@ -26,7 +35,7 @@ import {
|
|
|
26
35
|
when as mobxWhen
|
|
27
36
|
} from '@xh/hoist/mobx';
|
|
28
37
|
import {IAutorunOptions, IReactionOptions} from 'mobx/dist/api/autorun';
|
|
29
|
-
import {IReactionDisposer} from 'mobx/dist/internal';
|
|
38
|
+
import {IReactionDisposer, IEqualsComparer} from 'mobx/dist/internal';
|
|
30
39
|
|
|
31
40
|
export interface HoistBaseClass {
|
|
32
41
|
new (...args: any[]): HoistBase;
|
|
@@ -69,6 +78,33 @@ export abstract class HoistBase {
|
|
|
69
78
|
/** Default persistence options for this object. */
|
|
70
79
|
persistWith: PersistOptions = null;
|
|
71
80
|
|
|
81
|
+
//--------------------------------------------------
|
|
82
|
+
// Logging Delegates
|
|
83
|
+
//--------------------------------------------------
|
|
84
|
+
logInfo(...messages: unknown[]) {
|
|
85
|
+
logInfo(messages, this);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
logWarn(...messages: unknown[]) {
|
|
89
|
+
logWarn(messages, this);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
logError(...messages: unknown[]) {
|
|
93
|
+
logError(messages, this);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
logDebug(...messages: unknown[]) {
|
|
97
|
+
logDebug(messages, this);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
withInfo<T>(messages: Some<unknown>, fn: () => T): T {
|
|
101
|
+
return withInfo<T>(messages, fn, this);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
withDebug<T>(messages: Some<unknown>, fn: () => T): T {
|
|
105
|
+
return withDebug<T>(messages, fn, this);
|
|
106
|
+
}
|
|
107
|
+
|
|
72
108
|
/**
|
|
73
109
|
* Add and start one or more managed reactions.
|
|
74
110
|
*
|
|
@@ -257,7 +293,7 @@ export abstract class HoistBase {
|
|
|
257
293
|
/**
|
|
258
294
|
* Object containing options accepted by MobX 'reaction' API as well as arguments below.
|
|
259
295
|
*/
|
|
260
|
-
export
|
|
296
|
+
export type ReactionSpec<T = any> = IReactionOptions<T, any> & {
|
|
261
297
|
/**
|
|
262
298
|
* Function returning data to observe - first arg to the underlying reaction() call.
|
|
263
299
|
* Specify this or `when`.
|
|
@@ -275,7 +311,10 @@ export interface ReactionSpec<T = any> extends IReactionOptions<T, any> {
|
|
|
275
311
|
|
|
276
312
|
/** Specify to debounce run function */
|
|
277
313
|
debounce?: DebounceSpec;
|
|
278
|
-
|
|
314
|
+
|
|
315
|
+
/** Specify a default from {@link comparer} or a custom comparer function. */
|
|
316
|
+
equals?: keyof typeof comparer | IEqualsComparer<T>;
|
|
317
|
+
};
|
|
279
318
|
|
|
280
319
|
/**
|
|
281
320
|
* Object containing options accepted by MobX 'autorun' API as well as arguments below.
|
package/core/elem.ts
CHANGED
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
ReactElement,
|
|
15
15
|
ReactNode
|
|
16
16
|
} from 'react';
|
|
17
|
-
import {
|
|
17
|
+
import {Some, Thunkable} from './types/Types';
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
20
|
* Alternative format for specifying React Elements in render functions. This type is designed to
|
|
@@ -39,7 +39,7 @@ import {PlainObject, Some, Thunkable} from './types/Types';
|
|
|
39
39
|
* with this API. The '$' will be stripped from the prop name before passing it along to the
|
|
40
40
|
* underlying component.
|
|
41
41
|
*/
|
|
42
|
-
export type ElementSpec<P
|
|
42
|
+
export type ElementSpec<P> = P & {
|
|
43
43
|
//---------------------------------------------
|
|
44
44
|
// Enhanced attributes to support element factory
|
|
45
45
|
//---------------------------------------------
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import {HoistService, HoistServiceClass, Some, XH} from '@xh/hoist/core';
|
|
8
8
|
import {instanceManager} from '@xh/hoist/core/impl/InstanceManager';
|
|
9
|
-
import {throwIf
|
|
9
|
+
import {throwIf} from '@xh/hoist/utils/js';
|
|
10
10
|
import {camelCase, castArray} from 'lodash';
|
|
11
11
|
|
|
12
12
|
/**
|
|
@@ -50,13 +50,7 @@ export async function installServicesAsync(serviceClasses: Some<HoistServiceClas
|
|
|
50
50
|
|
|
51
51
|
async function initServicesInternalAsync(svcs: HoistService[]) {
|
|
52
52
|
const promises = svcs.map(it => {
|
|
53
|
-
return withDebug(
|
|
54
|
-
`Initializing ${it.constructor.name}`,
|
|
55
|
-
() => {
|
|
56
|
-
return it.initAsync();
|
|
57
|
-
},
|
|
58
|
-
'XH'
|
|
59
|
-
);
|
|
53
|
+
return it.withDebug(`Initializing`, () => it.initAsync());
|
|
60
54
|
});
|
|
61
55
|
|
|
62
56
|
const results: any[] = await Promise.allSettled(promises),
|
package/core/load/LoadSupport.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Copyright © 2023 Extremely Heavy Industries Inc.
|
|
6
6
|
*/
|
|
7
|
-
import {HoistBase, managed, RefreshContextModel, TaskObserver} from '../';
|
|
7
|
+
import {HoistBase, managed, PlainObject, RefreshContextModel, TaskObserver} from '../';
|
|
8
8
|
import {LoadSpec, Loadable} from './';
|
|
9
9
|
import {makeObservable, observable, runInAction} from '@xh/hoist/mobx';
|
|
10
10
|
import {throwIf} from '@xh/hoist/utils/js';
|
|
@@ -55,11 +55,11 @@ export class LoadSupport extends HoistBase implements Loadable {
|
|
|
55
55
|
return this.doLoadAsync(newSpec);
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
async refreshAsync(meta?:
|
|
58
|
+
async refreshAsync(meta?: PlainObject) {
|
|
59
59
|
return this.loadAsync({meta, isRefresh: true});
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
async autoRefreshAsync(meta?:
|
|
62
|
+
async autoRefreshAsync(meta?: PlainObject) {
|
|
63
63
|
return this.loadAsync({meta, isAutoRefresh: true});
|
|
64
64
|
}
|
|
65
65
|
|
package/core/model/HoistModel.ts
CHANGED
package/data/Store.ts
CHANGED
package/data/UrlStore.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Copyright © 2023 Extremely Heavy Industries Inc.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import {XH, managed, LoadSupport, LoadSpec, Loadable} from '@xh/hoist/core';
|
|
8
|
+
import {XH, managed, LoadSupport, LoadSpec, Loadable, PlainObject} from '@xh/hoist/core';
|
|
9
9
|
|
|
10
10
|
import {Store, StoreConfig} from './Store';
|
|
11
11
|
|
|
@@ -45,10 +45,10 @@ export class UrlStore extends Store implements Loadable {
|
|
|
45
45
|
get lastLoadException() {
|
|
46
46
|
return this.loadSupport.lastLoadException;
|
|
47
47
|
}
|
|
48
|
-
async refreshAsync(meta?:
|
|
48
|
+
async refreshAsync(meta?: PlainObject) {
|
|
49
49
|
return this.loadSupport.refreshAsync(meta);
|
|
50
50
|
}
|
|
51
|
-
async autoRefreshAsync(meta?:
|
|
51
|
+
async autoRefreshAsync(meta?: PlainObject) {
|
|
52
52
|
return this.loadSupport.autoRefreshAsync(meta);
|
|
53
53
|
}
|
|
54
54
|
async loadAsync(loadSpec?: LoadSpec | Partial<LoadSpec>) {
|
|
@@ -13,7 +13,7 @@ import {Store} from '../Store';
|
|
|
13
13
|
import {CompoundFilterSpec, CompoundFilterOperator, FilterTestFn} from './Types';
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
|
-
* Combines multiple filters (including other nested CompoundFilters) via an AND or OR operator.
|
|
16
|
+
* Combines multiple filters (including other nested CompoundFilters) via an `AND` or `OR` operator.
|
|
17
17
|
* Immutable.
|
|
18
18
|
*/
|
|
19
19
|
export class CompoundFilter extends Filter {
|
|
@@ -24,7 +24,8 @@ export class CompoundFilter extends Filter {
|
|
|
24
24
|
readonly filters: Filter[];
|
|
25
25
|
readonly op: CompoundFilterOperator;
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
/** @returns the singular field this filter operates on, if consistent across all clauses. */
|
|
28
|
+
get field(): string {
|
|
28
29
|
if (isEmpty(this.filters)) return null;
|
|
29
30
|
const {field} = this.filters[0] as any;
|
|
30
31
|
if (field && every(this.filters, {field})) return field;
|
|
@@ -32,7 +33,8 @@ export class CompoundFilter extends Filter {
|
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
/**
|
|
35
|
-
* Constructor - not typically called by apps - create
|
|
36
|
+
* Constructor - not typically called by apps - create via {@link parseFilter} instead.
|
|
37
|
+
* @internal
|
|
36
38
|
*/
|
|
37
39
|
constructor({filters, op = 'AND'}: CompoundFilterSpec) {
|
|
38
40
|
super();
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
difference,
|
|
13
13
|
escapeRegExp,
|
|
14
14
|
isArray,
|
|
15
|
+
isEqual,
|
|
15
16
|
isNil,
|
|
16
17
|
isString,
|
|
17
18
|
isUndefined,
|
|
@@ -27,7 +28,7 @@ import {FieldFilterOperator, FieldFilterSpec, FilterTestFn} from './Types';
|
|
|
27
28
|
* Filters by comparing the value of a given field to one or more given candidate values using one
|
|
28
29
|
* of several supported operators.
|
|
29
30
|
*
|
|
30
|
-
* Note that the comparison operators `[<,<=,>,>=]` always return false for null
|
|
31
|
+
* Note that the comparison operators `[<,<=,>,>=]` always return false for null/undefined values,
|
|
31
32
|
* favoring the behavior of Excel over Javascript's implicit conversion of nullish values to 0.
|
|
32
33
|
*
|
|
33
34
|
* Immutable.
|
|
@@ -122,13 +123,13 @@ export class FieldFilter extends Filter {
|
|
|
122
123
|
case '=':
|
|
123
124
|
opFn = v => {
|
|
124
125
|
if (isNil(v) || v === '') v = null;
|
|
125
|
-
return value.
|
|
126
|
+
return value.some(it => isEqual(v, it));
|
|
126
127
|
};
|
|
127
128
|
break;
|
|
128
129
|
case '!=':
|
|
129
130
|
opFn = v => {
|
|
130
131
|
if (isNil(v) || v === '') v = null;
|
|
131
|
-
return !value.
|
|
132
|
+
return !value.some(it => isEqual(v, it));
|
|
132
133
|
};
|
|
133
134
|
break;
|
|
134
135
|
case '>':
|
package/data/filter/Filter.ts
CHANGED
|
@@ -25,13 +25,12 @@ export abstract class Filter {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
/**
|
|
28
|
-
*
|
|
29
|
-
*
|
|
28
|
+
* @returns a function that can be used to test a record or object.
|
|
30
29
|
* @param store - if provided, return will be appropriate for testing records of this store.
|
|
31
30
|
* Otherwise, return will be appropriate for testing anonymous objects.
|
|
32
31
|
*/
|
|
33
32
|
abstract getTestFn(store?: Store): FilterTestFn;
|
|
34
33
|
|
|
35
|
-
/**
|
|
34
|
+
/** @returns true if the provided other Filter is equivalent to this instance.*/
|
|
36
35
|
abstract equals(other: Filter): boolean;
|
|
37
36
|
}
|
|
@@ -26,7 +26,8 @@ export class FunctionFilter extends Filter {
|
|
|
26
26
|
readonly testFn: FilterTestFn;
|
|
27
27
|
|
|
28
28
|
/**
|
|
29
|
-
* Constructor - not typically called by apps - create
|
|
29
|
+
* Constructor - not typically called by apps - create via {@link parseFilter} instead.
|
|
30
|
+
* @internal
|
|
30
31
|
*/
|
|
31
32
|
constructor({key, testFn}: FunctionFilterSpec) {
|
|
32
33
|
super();
|
package/data/impl/RecordSet.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import equal from 'fast-deep-equal';
|
|
9
|
-
import {
|
|
9
|
+
import {logWarn, throwIf} from '@xh/hoist/utils/js';
|
|
10
10
|
import {maxBy, isNil} from 'lodash';
|
|
11
11
|
import {StoreRecord, StoreRecordId} from '../StoreRecord';
|
|
12
12
|
import {Store} from '../Store';
|
|
@@ -196,7 +196,7 @@ export class RecordSet {
|
|
|
196
196
|
remove.forEach(id => {
|
|
197
197
|
if (!newRecords.has(id)) {
|
|
198
198
|
missingRemoves++;
|
|
199
|
-
logDebug(`Attempted to remove non-existent record: ${id}
|
|
199
|
+
this.store.logDebug(`Attempted to remove non-existent record: ${id}`);
|
|
200
200
|
return;
|
|
201
201
|
}
|
|
202
202
|
this.gatherDescendantIds(id, allRemoves);
|
|
@@ -211,7 +211,7 @@ export class RecordSet {
|
|
|
211
211
|
existing = newRecords.get(id);
|
|
212
212
|
if (!existing) {
|
|
213
213
|
missingUpdates++;
|
|
214
|
-
logDebug(`Attempted to update non-existent record: ${id}
|
|
214
|
+
this.store.logDebug(`Attempted to update non-existent record: ${id}`);
|
|
215
215
|
return;
|
|
216
216
|
}
|
|
217
217
|
newRecords.set(id, rec);
|
|
@@ -230,9 +230,9 @@ export class RecordSet {
|
|
|
230
230
|
}
|
|
231
231
|
|
|
232
232
|
if (missingRemoves > 0)
|
|
233
|
-
|
|
233
|
+
logWarn(`Failed to remove ${missingRemoves} records not found by id`, this);
|
|
234
234
|
if (missingUpdates > 0)
|
|
235
|
-
|
|
235
|
+
logWarn(`Failed to update ${missingUpdates} records not found by id`, this);
|
|
236
236
|
|
|
237
237
|
return new RecordSet(this.store, newRecords);
|
|
238
238
|
}
|
|
@@ -95,7 +95,7 @@ class ToastSourceLocalModel extends HoistModel {
|
|
|
95
95
|
*/
|
|
96
96
|
async getToasterAsync(position: ToasterPosition, container: HTMLElement) {
|
|
97
97
|
if (container && !isElement(container)) {
|
|
98
|
-
|
|
98
|
+
this.logWarn('Ignoring invalid containerRef for Toast - must be a DOM element');
|
|
99
99
|
container = null;
|
|
100
100
|
}
|
|
101
101
|
const className = `xh-toast-container ${container ? 'xh-toast-container--anchored' : ''}`;
|
|
@@ -6,15 +6,17 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import '@xh/hoist/desktop/register';
|
|
9
|
+
import {GridModel} from '@xh/hoist/cmp/grid';
|
|
9
10
|
import {RecordActionSpec} from '@xh/hoist/data';
|
|
11
|
+
import {RestGridModel} from '@xh/hoist/desktop/cmp/rest/RestGridModel';
|
|
10
12
|
import {Icon} from '@xh/hoist/icon/Icon';
|
|
11
13
|
|
|
12
14
|
export const addAction: RecordActionSpec = {
|
|
13
15
|
text: 'Add',
|
|
14
16
|
icon: Icon.add(),
|
|
15
17
|
intent: 'success',
|
|
16
|
-
actionFn: ({gridModel}) => gridModel.
|
|
17
|
-
displayFn: ({gridModel}) => ({hidden: gridModel.
|
|
18
|
+
actionFn: ({gridModel}) => getRGM(gridModel).addRecord(),
|
|
19
|
+
displayFn: ({gridModel}) => ({hidden: getRGM(gridModel).readonly}),
|
|
18
20
|
testId: 'add-action-button'
|
|
19
21
|
};
|
|
20
22
|
|
|
@@ -23,8 +25,8 @@ export const editAction: RecordActionSpec = {
|
|
|
23
25
|
icon: Icon.edit(),
|
|
24
26
|
intent: 'primary',
|
|
25
27
|
recordsRequired: 1,
|
|
26
|
-
actionFn: ({record, gridModel}) => gridModel.
|
|
27
|
-
displayFn: ({gridModel}) => ({hidden: gridModel.
|
|
28
|
+
actionFn: ({record, gridModel}) => getRGM(gridModel).editRecord(record),
|
|
29
|
+
displayFn: ({gridModel}) => ({hidden: getRGM(gridModel).readonly}),
|
|
28
30
|
testId: 'edit-action-button'
|
|
29
31
|
};
|
|
30
32
|
|
|
@@ -32,7 +34,7 @@ export const viewAction: RecordActionSpec = {
|
|
|
32
34
|
text: 'View',
|
|
33
35
|
icon: Icon.search(),
|
|
34
36
|
recordsRequired: 1,
|
|
35
|
-
actionFn: ({record, gridModel}) => gridModel.
|
|
37
|
+
actionFn: ({record, gridModel}) => getRGM(gridModel).viewRecord(record),
|
|
36
38
|
testId: 'view-action-button'
|
|
37
39
|
};
|
|
38
40
|
|
|
@@ -40,8 +42,8 @@ export const cloneAction: RecordActionSpec = {
|
|
|
40
42
|
text: 'Clone',
|
|
41
43
|
icon: Icon.copy(),
|
|
42
44
|
recordsRequired: 1,
|
|
43
|
-
actionFn: ({record, gridModel}) => gridModel.
|
|
44
|
-
displayFn: ({gridModel}) => ({hidden: gridModel.
|
|
45
|
+
actionFn: ({record, gridModel}) => getRGM(gridModel).cloneRecord(record),
|
|
46
|
+
displayFn: ({gridModel}) => ({hidden: getRGM(gridModel).readonly}),
|
|
45
47
|
testId: 'clone-action-button'
|
|
46
48
|
};
|
|
47
49
|
|
|
@@ -51,8 +53,12 @@ export const deleteAction: RecordActionSpec = {
|
|
|
51
53
|
intent: 'danger',
|
|
52
54
|
recordsRequired: true,
|
|
53
55
|
displayFn: ({gridModel, record}) => ({
|
|
54
|
-
hidden: (record && record.id === null) || gridModel.
|
|
56
|
+
hidden: (record && record.id === null) || getRGM(gridModel).readonly // Hide this action if we are acting on a "new" record
|
|
55
57
|
}),
|
|
56
|
-
actionFn: ({gridModel}) => gridModel.
|
|
58
|
+
actionFn: ({gridModel}) => getRGM(gridModel).confirmDeleteRecords(),
|
|
57
59
|
testId: 'delete-action-button'
|
|
58
60
|
};
|
|
61
|
+
|
|
62
|
+
function getRGM(gridModel: GridModel): RestGridModel {
|
|
63
|
+
return gridModel.appData.restGridModel as RestGridModel;
|
|
64
|
+
}
|