@xh/hoist 67.0.0-SNAPSHOT.1725047531773 → 67.0.0-SNAPSHOT.1725049253748

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 CHANGED
@@ -21,10 +21,12 @@
21
21
  * New property `FetchOptions.asJson` to instruct `FetchService` to decode an HTTP response as JSON.
22
22
  Note that `FetchService` methods suffixed with `Json` will set this property automatically.
23
23
  * `GridModel` will now accept `contextMenu: false` to omit context menus.
24
- * New bindable `AppContainerModel.intializingLoadMaskMessage` property to allow apps to customize
24
+ * New bindable property `AppContainerModel.intializingLoadMaskMessage` to allow apps to customize
25
25
  the loading mask message shown during app initialization.
26
26
  * Enhanced `select` component with new `emptyValue` prop, allowing for a custom value to be returned
27
27
  when the control is empty (vs default of `null`). Expected usage is `[]` when `enableMulti:true`.
28
+ * New `GroupingChooserModel.setDimensions()` API, to support updating available dimensions on an
29
+ already constructed `GroupingChooserModel`.
28
30
 
29
31
  ### 🐞 Bug Fixes
30
32
 
@@ -35,6 +35,7 @@ export const connPoolMonitorPanel = hoistCmp.factory({
35
35
  omit: readonly,
36
36
  onClick: () => model.takeSnapshotAsync()
37
37
  }),
38
+ '-',
38
39
  button({
39
40
  text: 'Reset Stats',
40
41
  icon: Icon.reset(),
@@ -18,9 +18,9 @@ import {isEmpty} from 'lodash';
18
18
 
19
19
  export class HzObjectModel extends BaseInstanceModel {
20
20
  clearAction: RecordActionSpec = {
21
- icon: Icon.reset(),
22
21
  text: 'Clear Objects',
23
- intent: 'danger',
22
+ icon: Icon.reset(),
23
+ intent: 'warning',
24
24
  actionFn: () => this.clearAsync(),
25
25
  displayFn: ({selectedRecords}) => ({
26
26
  hidden: AppModel.readonly,
@@ -80,8 +80,9 @@ export class HzObjectModel extends BaseInstanceModel {
80
80
  ),
81
81
  confirmProps: {
82
82
  text: 'Clear Objects',
83
+ icon: Icon.reset(),
84
+ intent: 'warning',
83
85
  outlined: true,
84
- intent: 'danger',
85
86
  autoFocus: false
86
87
  }
87
88
  }))
@@ -115,7 +116,8 @@ export class HzObjectModel extends BaseInstanceModel {
115
116
  ),
116
117
  confirmProps: {
117
118
  text: 'Clear Hibernate Caches',
118
- intent: 'danger',
119
+ icon: Icon.reset(),
120
+ intent: 'warning',
119
121
  outlined: true,
120
122
  autoFocus: false
121
123
  }
@@ -25,10 +25,11 @@ export const hzObjectPanel = hoistCmp.factory({
25
25
  selModel: model.gridModel.selModel,
26
26
  actions: [model.clearAction]
27
27
  }),
28
+ '-',
28
29
  button({
29
30
  text: 'Clear Hibernate Caches',
30
31
  icon: Icon.reset(),
31
- intent: 'danger',
32
+ intent: 'warning',
32
33
  tooltip: 'Clear the Hibernate caches using the native Hibernate API',
33
34
  onClick: () => model.clearHibernateCachesAsync()
34
35
  }),
@@ -36,12 +36,14 @@ export const memoryMonitorPanel = hoistCmp.factory({
36
36
  omit: readonly,
37
37
  onClick: () => model.takeSnapshotAsync()
38
38
  }),
39
+ '-',
39
40
  button({
40
41
  text: 'Request GC',
41
42
  icon: Icon.trash(),
42
43
  omit: readonly,
43
44
  onClick: () => model.requestGcAsync()
44
45
  }),
46
+ '-',
45
47
  button({
46
48
  text: 'Dump Heap',
47
49
  icon: Icon.fileArchive(),
@@ -18,9 +18,9 @@ import {isEmpty, lowerFirst} from 'lodash';
18
18
 
19
19
  export class ServiceModel extends BaseInstanceModel {
20
20
  clearCachesAction: RecordActionSpec = {
21
- icon: Icon.reset(),
22
21
  text: 'Clear Caches',
23
- intent: 'danger',
22
+ icon: Icon.reset(),
23
+ intent: 'warning',
24
24
  actionFn: () => this.clearCachesAsync(false),
25
25
  displayFn: () => ({
26
26
  hidden: AppModel.readonly,
@@ -30,9 +30,9 @@ export class ServiceModel extends BaseInstanceModel {
30
30
  };
31
31
 
32
32
  clearClusterCachesAction: RecordActionSpec = {
33
- icon: Icon.reset(),
34
33
  text: 'Clear Caches (entire cluster)',
35
- intent: 'danger',
34
+ icon: Icon.reset(),
35
+ intent: 'warning',
36
36
  actionFn: () => this.clearCachesAsync(true),
37
37
  displayFn: () => ({
38
38
  hidden: AppModel.readonly || !this.parent.isMultiInstance
@@ -91,7 +91,8 @@ export class ServiceModel extends BaseInstanceModel {
91
91
  ),
92
92
  confirmProps: {
93
93
  text: 'Clear Caches',
94
- intent: 'danger',
94
+ icon: Icon.reset(),
95
+ intent: 'warning',
95
96
  outlined: true,
96
97
  autoFocus: false
97
98
  }
@@ -117,8 +117,8 @@ export class WebSocketModel extends BaseInstanceModel {
117
117
  if (isEmpty(selectedRecords)) return;
118
118
 
119
119
  const message = await XH.prompt<string>({
120
- title: 'Force suspend',
121
- icon: Icon.stopCircle(),
120
+ title: 'Please confirm...',
121
+ icon: Icon.warning(),
122
122
  confirmProps: {
123
123
  text: 'Force Suspend',
124
124
  icon: Icon.stopCircle(),
@@ -45,7 +45,12 @@ export const rolePanel = hoistCmp.factory({
45
45
  }),
46
46
  '-',
47
47
  filterChooser({flex: 1}),
48
- switchInput({bind: 'showInGroups', label: 'Show in Groups', labelSide: 'left'})
48
+ '-',
49
+ switchInput({
50
+ bind: 'showInGroups',
51
+ label: 'Group by Category',
52
+ labelSide: 'left'
53
+ })
49
54
  ],
50
55
  item: hframe(vframe(grid(), roleGraph()), detailsPanel())
51
56
  }),
@@ -6,7 +6,7 @@
6
6
  */
7
7
  import {chart} from '@xh/hoist/cmp/chart';
8
8
  import {errorBoundary} from '@xh/hoist/cmp/error';
9
- import {div, hspacer, placeholder} from '@xh/hoist/cmp/layout';
9
+ import {div, filler, placeholder, span} from '@xh/hoist/cmp/layout';
10
10
  import {creates, hoistCmp} from '@xh/hoist/core';
11
11
  import {button} from '@xh/hoist/desktop/cmp/button';
12
12
  import {buttonGroupInput, slider, switchInput} from '@xh/hoist/desktop/cmp/input';
@@ -29,7 +29,7 @@ export const roleGraph = hoistCmp.factory({
29
29
  return panel({
30
30
  compactHeader: true,
31
31
  icon: Icon.treeGraph(),
32
- title: role ? `Relationships - ${role.name} ` : 'Relationships',
32
+ title: role ? `${role.name} Relationships` : 'Relationships',
33
33
  item: div({
34
34
  item: div({
35
35
  style: {margin: 'auto'},
@@ -48,15 +48,31 @@ export const roleGraph = hoistCmp.factory({
48
48
  items: [
49
49
  button({
50
50
  value: 'effective',
51
- text: `Granted to ${role?.effectiveRoles.length} ${pluralize('role', role?.effectiveRoles.length)}`
51
+ text: `Granted to ${pluralize('role', role?.effectiveRoles.length, true)}`
52
52
  }),
53
53
  button({
54
54
  value: 'inherited',
55
- text: `Inheriting from ${role?.inheritedRoles.length} ${pluralize('role', role?.inheritedRoles.length)}`
55
+ text: `Inheriting from ${pluralize('role', role?.inheritedRoles.length, true)}`
56
56
  })
57
57
  ]
58
58
  }),
59
- hspacer(10),
59
+ filler(),
60
+ span('Limit to one level'),
61
+ switchInput({
62
+ bind: 'limitToOneLevel'
63
+ }),
64
+ '-',
65
+ span('Zoom'),
66
+ slider({
67
+ paddingLeft: 2,
68
+ overflow: 'visible',
69
+ bind: 'widthScale',
70
+ min: 0,
71
+ max: 2,
72
+ stepSize: 0.005,
73
+ labelRenderer: false
74
+ }),
75
+ '-',
60
76
  buttonGroupInput({
61
77
  bind: 'inverted',
62
78
  items: [
@@ -69,21 +85,6 @@ export const roleGraph = hoistCmp.factory({
69
85
  icon: Icon.treeGraph({rotation: 270})
70
86
  })
71
87
  ]
72
- }),
73
- hspacer(10),
74
- 'Zoom',
75
- slider({
76
- paddingLeft: 2,
77
- overflow: 'visible',
78
- bind: 'widthScale',
79
- min: 0,
80
- max: 2,
81
- stepSize: 0.005,
82
- labelRenderer: false
83
- }),
84
- 'Limit to one level',
85
- switchInput({
86
- bind: 'limitToOneLevel'
87
88
  })
88
89
  ],
89
90
  omit: !role
@@ -42,8 +42,6 @@ export interface GroupingChooserPersistOptions extends PersistOptions {
42
42
  export declare class GroupingChooserModel extends HoistModel {
43
43
  value: string[];
44
44
  favorites: string[][];
45
- dimensions: Record<string, DimensionSpec>;
46
- dimensionNames: string[];
47
45
  allowEmpty: boolean;
48
46
  maxDepth: number;
49
47
  commitOnChange: boolean;
@@ -54,10 +52,14 @@ export declare class GroupingChooserModel extends HoistModel {
54
52
  editorIsOpen: boolean;
55
53
  favoritesIsOpen: boolean;
56
54
  popoverRef: import("react").RefObject<HTMLElement> & import("react").RefCallback<HTMLElement>;
55
+ private dimensions;
56
+ private dimensionNames;
57
57
  get availableDims(): string[];
58
+ get dimensionSpecs(): DimensionSpec[];
58
59
  get isValid(): boolean;
59
60
  get isAddEnabled(): boolean;
60
61
  constructor({ dimensions, initialValue, initialFavorites, persistWith, allowEmpty, maxDepth, commitOnChange }: GroupingChooserConfig);
62
+ setDimensions(dimensions: Array<DimensionSpec | string>): void;
61
63
  setValue(value: string[]): void;
62
64
  toggleEditor(): void;
63
65
  toggleFavoritesMenu(): void;
@@ -67,13 +69,7 @@ export declare class GroupingChooserModel extends HoistModel {
67
69
  removePendingDimAtIdx(idx: number): void;
68
70
  movePendingDimToIndex(dimName: string, toIdx: number): void;
69
71
  commitPendingValueAndClose(): void;
70
- validateValue(value: any): boolean;
71
- normalizeDimensions(dims: Array<DimensionSpec | string>): Record<string, DimensionSpec>;
72
- createDimension(src: DimensionSpec | string): {
73
- /** Shortname or code (almost always a `CubeField.name`). */
74
- name: string;
75
- displayName: string;
76
- };
72
+ validateValue(value: string[]): boolean;
77
73
  getValueLabel(value: string[]): string;
78
74
  getDimDisplayName(dimName: string): string;
79
75
  onDragEnd(result: any): void;
@@ -86,4 +82,7 @@ export declare class GroupingChooserModel extends HoistModel {
86
82
  removeFavorite(value: string[]): void;
87
83
  isFavorite(value: string[]): boolean;
88
84
  get persistState(): PlainObject;
85
+ private normalizeDimensions;
86
+ private createDimension;
87
+ private removeUnknownDimsFromValue;
89
88
  }
@@ -13,21 +13,11 @@ import {
13
13
  PlainObject,
14
14
  XH
15
15
  } from '@xh/hoist/core';
16
- import {action, computed, observable, makeObservable} from '@xh/hoist/mobx';
17
16
  import {genDisplayName} from '@xh/hoist/data';
18
- import {throwIf} from '@xh/hoist/utils/js';
17
+ import {action, computed, makeObservable, observable} from '@xh/hoist/mobx';
18
+ import {executeIfFunction, throwIf} from '@xh/hoist/utils/js';
19
19
  import {createObservableRef} from '@xh/hoist/utils/react';
20
- import {
21
- cloneDeep,
22
- difference,
23
- isFunction,
24
- isArray,
25
- isEmpty,
26
- isEqual,
27
- isString,
28
- keys,
29
- sortBy
30
- } from 'lodash';
20
+ import {cloneDeep, difference, isArray, isEmpty, isEqual, isString, keys, sortBy} from 'lodash';
31
21
 
32
22
  export interface GroupingChooserConfig {
33
23
  /**
@@ -81,11 +71,8 @@ export interface GroupingChooserPersistOptions extends PersistOptions {
81
71
 
82
72
  export class GroupingChooserModel extends HoistModel {
83
73
  @observable.ref value: string[];
84
-
85
74
  @observable.ref favorites: string[][] = [];
86
75
 
87
- dimensions: Record<string, DimensionSpec>;
88
- dimensionNames: string[];
89
76
  allowEmpty: boolean;
90
77
  maxDepth: number;
91
78
  commitOnChange: boolean;
@@ -98,14 +85,22 @@ export class GroupingChooserModel extends HoistModel {
98
85
  @observable.ref pendingValue: string[] = [];
99
86
  @observable editorIsOpen: boolean = false;
100
87
  @observable favoritesIsOpen: boolean = false;
101
-
102
88
  popoverRef = createObservableRef<HTMLElement>();
103
89
 
90
+ // Internal state
91
+ @observable.ref private dimensions: Record<string, DimensionSpec>;
92
+ @observable.ref private dimensionNames: string[];
93
+
104
94
  @computed
105
95
  get availableDims(): string[] {
106
96
  return difference(this.dimensionNames, this.pendingValue);
107
97
  }
108
98
 
99
+ @computed
100
+ get dimensionSpecs(): DimensionSpec[] {
101
+ return Object.values(this.dimensions);
102
+ }
103
+
109
104
  @computed
110
105
  get isValid(): boolean {
111
106
  return this.validateValue(this.pendingValue);
@@ -132,17 +127,15 @@ export class GroupingChooserModel extends HoistModel {
132
127
  super();
133
128
  makeObservable(this);
134
129
 
135
- this.dimensions = this.normalizeDimensions(dimensions);
136
- this.dimensionNames = keys(this.dimensions);
137
130
  this.allowEmpty = allowEmpty;
138
131
  this.maxDepth = maxDepth;
139
132
  this.commitOnChange = commitOnChange;
140
133
 
141
- throwIf(isEmpty(this.dimensions), 'Must provide valid dimensions available for selection.');
134
+ this.setDimensions(dimensions);
142
135
 
143
136
  // Read and validate value and favorites
144
- let value = isFunction(initialValue) ? initialValue() : initialValue,
145
- favorites = isFunction(initialFavorites) ? initialFavorites() : initialFavorites;
137
+ let value = executeIfFunction(initialValue),
138
+ favorites = executeIfFunction(initialFavorites);
146
139
 
147
140
  throwIf(isEmpty(value) && !this.allowEmpty, 'Initial value cannot be empty.');
148
141
  throwIf(!this.validateValue(value), 'Initial value is invalid.');
@@ -187,6 +180,18 @@ export class GroupingChooserModel extends HoistModel {
187
180
  this.setFavorites(favorites);
188
181
  }
189
182
 
183
+ @action
184
+ setDimensions(dimensions: Array<DimensionSpec | string>) {
185
+ throwIf(
186
+ isEmpty(dimensions) && !this.allowEmpty,
187
+ 'Must provide valid dimensions available for selection.'
188
+ );
189
+
190
+ this.dimensions = this.normalizeDimensions(dimensions);
191
+ this.dimensionNames = keys(this.dimensions);
192
+ this.removeUnknownDimsFromValue();
193
+ }
194
+
190
195
  @action
191
196
  setValue(value: string[]) {
192
197
  if (!this.validateValue(value)) {
@@ -259,37 +264,18 @@ export class GroupingChooserModel extends HoistModel {
259
264
  this.closePopover();
260
265
  }
261
266
 
262
- validateValue(value) {
267
+ validateValue(value: string[]) {
263
268
  if (!isArray(value)) return false;
264
269
  if (isEmpty(value) && !this.allowEmpty) return false;
265
270
  return value.every(dim => this.dimensionNames.includes(dim));
266
271
  }
267
272
 
268
- normalizeDimensions(dims: Array<DimensionSpec | string>): Record<string, DimensionSpec> {
269
- dims = dims ?? [];
270
- const ret = {};
271
- dims.forEach(it => {
272
- const dim = this.createDimension(it);
273
- ret[dim.name] = dim;
274
- });
275
- return ret;
276
- }
277
-
278
- createDimension(src: DimensionSpec | string) {
279
- src = isString(src) ? {name: src} : src;
280
- throwIf(
281
- !src.hasOwnProperty('name'),
282
- "Dimensions provided as Objects must define a 'name' property."
283
- );
284
- return {displayName: genDisplayName(src.name), ...src};
285
- }
286
-
287
- getValueLabel(value: string[]) {
273
+ getValueLabel(value: string[]): string {
288
274
  return value.map(dimName => this.getDimDisplayName(dimName)).join(' › ');
289
275
  }
290
276
 
291
277
  getDimDisplayName(dimName: string) {
292
- return this.dimensions[dimName].displayName;
278
+ return this.dimensions[dimName]?.displayName ?? dimName;
293
279
  }
294
280
 
295
281
  //--------------------
@@ -343,4 +329,41 @@ export class GroupingChooserModel extends HoistModel {
343
329
  if (this.persistFavorites) ret.favorites = this.favorites;
344
330
  return ret;
345
331
  }
332
+
333
+ //------------------------
334
+ // Implementation
335
+ //------------------------
336
+ private normalizeDimensions(
337
+ dims: Array<DimensionSpec | string>
338
+ ): Record<string, DimensionSpec> {
339
+ dims = dims ?? [];
340
+ const ret = {};
341
+ dims.forEach(it => {
342
+ const dim = this.createDimension(it);
343
+ ret[dim.name] = dim;
344
+ });
345
+ return ret;
346
+ }
347
+
348
+ private createDimension(src: DimensionSpec | string) {
349
+ src = isString(src) ? {name: src} : src;
350
+ throwIf(
351
+ !src.hasOwnProperty('name'),
352
+ "Dimensions provided as Objects must define a 'name' property."
353
+ );
354
+ return {displayName: genDisplayName(src.name), ...src};
355
+ }
356
+
357
+ private removeUnknownDimsFromValue() {
358
+ const {value, dimensionNames, allowEmpty} = this,
359
+ cleanValue = value?.filter(dim => dimensionNames.includes(dim));
360
+
361
+ if (isEqual(value, cleanValue)) return;
362
+
363
+ if (isEmpty(cleanValue) && !allowEmpty) {
364
+ cleanValue.push(dimensionNames[0]);
365
+ }
366
+
367
+ this.setValue(cleanValue);
368
+ }
346
369
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xh/hoist",
3
- "version": "67.0.0-SNAPSHOT.1725047531773",
3
+ "version": "67.0.0-SNAPSHOT.1725049253748",
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",