@xh/hoist 76.0.0-SNAPSHOT.1755706862648 → 76.0.0-SNAPSHOT.1755780982882

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
@@ -2,17 +2,24 @@
2
2
 
3
3
  ## 76.0.0-SNAPSHOT - unreleased
4
4
 
5
+ ### 💥 Breaking Changes (upgrade difficulty: 🟢 LOW - requires hoist-core v31.2)
6
+
5
7
  ### 🎁 New Features
6
8
 
7
9
  * Added new `extraConfirmText`, `extraConfirmLabel` properties to `MessageOptions`. Use this option
8
10
  to require the specified text to be re-typed by a user when confirming a potentially destructive
9
11
  or disruptive action.
12
+ * Updated grid column filters to apply on `Enter` / dismiss on `Esc` and tweaked the filter popup
13
+ toolbar for clarity.
10
14
 
11
15
  ### 🐞 Bug Fixes
12
16
 
13
17
  * Handled an edge-case `ViewManager` bug where `enableDefault` changed to `false` after some user
14
18
  state had already been persisted w/users pointed at in-code default view. The manager now calls
15
19
  its configured `initialViewSpec` function as expected in this case.
20
+
21
+ * `XH.restoreDefaultsAsync` will now clear basic view state. Views themselves will be preserved.
22
+ Requires hoist-core v31.2
16
23
 
17
24
  ### ⚙️ Technical
18
25
 
@@ -51,12 +51,15 @@ export declare class HoistAppModel extends HoistModel {
51
51
  /**
52
52
  * Resets user preferences and any persistent local application state.
53
53
  *
54
- * The default implementation for this method will clear *all* preferences and local storage.
54
+ * The default implementation for this method will clear all preferences, local storage, and
55
+ * transient view state such as current, and pinned views. Views themselves are preserved.
55
56
  *
56
- * Applications may wish to override this method to do a more targeted clearing of state.
57
+ * Applications may wish to override this method to perform a more targeted clearing of state.
57
58
  * This is important for complex applications with smaller sub-applications, and/or device
58
59
  * specific applications. These applications will typically want to perform a custom clearing
59
60
  * that is more targeted, and includes any additional app-specific state.
61
+ *
62
+ * Not typically called directly by applications. Call XH.restoreDefaultsAsync() instead.
60
63
  */
61
64
  restoreDefaultsAsync(): Promise<void>;
62
65
  }
@@ -11,7 +11,7 @@ import { ToastModel } from '../appcontainer/ToastModel';
11
11
  import '../styles/XH.scss';
12
12
  import { AppSpec, AppState, AppSuspendData, BannerSpec, ExceptionHandler, ExceptionHandlerOptions, HoistAppModel, HoistException, HoistService, HoistServiceClass, HoistUser, MessageSpec, PageState, PlainObject, ReloadAppOptions, SizingMode, TaskObserver, Theme, ToastSpec, TrackOptions } from './';
13
13
  import { HoistModel, ModelSelector, RefreshContextModel } from './model';
14
- export declare const MIN_HOIST_CORE_VERSION = "30.1";
14
+ export declare const MIN_HOIST_CORE_VERSION = "31.2";
15
15
  /**
16
16
  * Top-level Singleton model for Hoist. This is the main entry point for the API.
17
17
  *
@@ -396,7 +396,9 @@ export declare class XHApi {
396
396
  /** All Stores registered with this application. */
397
397
  getStores(): Store[];
398
398
  /**
399
- * Reset user preferences and any persistent local application state, then reload the app.
399
+ * Reset user state and then reload the app.
400
+ *
401
+ * @see HoistAppModel.restoreDefaultsAsync()
400
402
  */
401
403
  restoreDefaultsAsync(): Promise<void>;
402
404
  /**
@@ -1,6 +1,7 @@
1
1
  import { TabContainerModel } from '@xh/hoist/cmp/tab';
2
2
  import { HoistModel } from '@xh/hoist/core';
3
- import { GridFilterFieldSpec } from '@xh/hoist/cmp/grid';
3
+ import { CompoundFilter, FieldFilter, FieldType, Filter, FilterLike, Store } from '@xh/hoist/data';
4
+ import { GridFilterFieldSpec, GridFilterModel } from '@xh/hoist/cmp/grid';
4
5
  import { CustomTabModel } from './custom/CustomTabModel';
5
6
  import { ValuesTabModel } from './values/ValuesTabModel';
6
7
  import { ColumnHeaderFilterModel } from '../ColumnHeaderFilterModel';
@@ -11,15 +12,17 @@ export declare class HeaderFilterModel extends HoistModel {
11
12
  tabContainerModel: TabContainerModel;
12
13
  valuesTabModel: ValuesTabModel;
13
14
  customTabModel: CustomTabModel;
14
- get filterModel(): import("@xh/hoist/cmp/grid").GridFilterModel;
15
+ get filterModel(): GridFilterModel;
15
16
  get field(): string;
16
- get store(): import("../../../../../../data").Store;
17
- get fieldType(): import("../../../../../../data").FieldType;
18
- get currentGridFilter(): import("../../../../../../data").Filter;
19
- get columnFilters(): import("../../../../../../data").FieldFilter[];
20
- get columnCompoundFilter(): import("../../../../../../data").CompoundFilter;
17
+ get store(): Store;
18
+ get fieldType(): FieldType;
19
+ get currentGridFilter(): Filter;
20
+ get columnFilters(): FieldFilter[];
21
+ get columnCompoundFilter(): CompoundFilter;
21
22
  get hasFilter(): boolean;
23
+ get pendingFilter(): FilterLike;
22
24
  get hasPendingFilter(): boolean;
25
+ get isDirty(): boolean;
23
26
  get isCustomFilter(): boolean;
24
27
  get commitOnChange(): boolean;
25
28
  onLinked(): void;
@@ -56,11 +56,6 @@ export declare class PrefService extends HoistService {
56
56
  * before making another call that relies on its updated value being read on the server.
57
57
  */
58
58
  pushAsync(key: string, value: any): Promise<void>;
59
- /**
60
- * Reset *all* preferences, reverting their effective values back to defaults.
61
- * @returns a Promise that resolves when preferences have been cleared and defaults reloaded.
62
- */
63
- clearAllAsync(): Promise<void>;
64
59
  /**
65
60
  * Push any pending buffered updates to persist newly set values to server.
66
61
  * Called automatically by this app on page unload to avoid dropping changes when e.g. a user
@@ -88,16 +88,22 @@ export class HoistAppModel extends HoistModel {
88
88
  /**
89
89
  * Resets user preferences and any persistent local application state.
90
90
  *
91
- * The default implementation for this method will clear *all* preferences and local storage.
91
+ * The default implementation for this method will clear all preferences, local storage, and
92
+ * transient view state such as current, and pinned views. Views themselves are preserved.
92
93
  *
93
- * Applications may wish to override this method to do a more targeted clearing of state.
94
+ * Applications may wish to override this method to perform a more targeted clearing of state.
94
95
  * This is important for complex applications with smaller sub-applications, and/or device
95
96
  * specific applications. These applications will typically want to perform a custom clearing
96
97
  * that is more targeted, and includes any additional app-specific state.
98
+ *
99
+ * Not typically called directly by applications. Call XH.restoreDefaultsAsync() instead.
97
100
  */
98
101
  async restoreDefaultsAsync() {
99
102
  const XH = window['XH'];
100
- await XH.prefService.clearAllAsync();
103
+ await XH.fetchJson({
104
+ url: 'xh/clearUserState',
105
+ params: {clientUsername: XH.getUsername()}
106
+ });
101
107
  XH.localStorageService.clear();
102
108
  XH.sessionStorageService.clear();
103
109
  }
package/core/XH.ts CHANGED
@@ -67,7 +67,7 @@ import {instanceManager} from './impl/InstanceManager';
67
67
  import {HoistModel, ModelSelector, RefreshContextModel} from './model';
68
68
  import ShortUniqueId from 'short-unique-id';
69
69
 
70
- export const MIN_HOIST_CORE_VERSION = '30.1';
70
+ export const MIN_HOIST_CORE_VERSION = '31.2';
71
71
 
72
72
  declare const xhAppCode: string;
73
73
  declare const xhAppName: string;
@@ -772,11 +772,21 @@ export class XHApi {
772
772
  }
773
773
 
774
774
  /**
775
- * Reset user preferences and any persistent local application state, then reload the app.
775
+ * Reset user state and then reload the app.
776
+ *
777
+ * @see HoistAppModel.restoreDefaultsAsync()
776
778
  */
777
779
  async restoreDefaultsAsync() {
778
- await this.appModel.restoreDefaultsAsync();
779
- this.reloadApp();
780
+ try {
781
+ await this.appModel.restoreDefaultsAsync();
782
+ XH.trackService.track({category: 'App', message: 'Restored app defaults'});
783
+ this.reloadApp();
784
+ } catch (e) {
785
+ XH.handleException(e, {
786
+ message: 'Failed to restore app defaults',
787
+ requireReload: true
788
+ });
789
+ }
780
790
  }
781
791
 
782
792
  /**
@@ -12,6 +12,7 @@ import {button, buttonGroup} from '@xh/hoist/desktop/cmp/button';
12
12
  import {panel} from '@xh/hoist/desktop/cmp/panel';
13
13
  import {toolbar} from '@xh/hoist/desktop/cmp/toolbar';
14
14
  import {Icon} from '@xh/hoist/icon';
15
+ import {wait} from '@xh/hoist/promise';
15
16
  import {stopPropagation} from '@xh/hoist/utils/js';
16
17
  import {HeaderFilterModel} from './HeaderFilterModel';
17
18
 
@@ -22,7 +23,7 @@ import {HeaderFilterModel} from './HeaderFilterModel';
22
23
  */
23
24
  export const headerFilter = hoistCmp.factory({
24
25
  model: creates(HeaderFilterModel),
25
- render() {
26
+ render({model}) {
26
27
  return panel({
27
28
  title: `Filter`,
28
29
  className: 'xh-column-header-filter',
@@ -31,31 +32,49 @@ export const headerFilter = hoistCmp.factory({
31
32
  onDoubleClick: stopPropagation,
32
33
  headerItems: [switcher()],
33
34
  item: tabContainer(),
34
- bbar: bbar()
35
+ bbar: bbar(),
36
+ hotkeys: [
37
+ {
38
+ allowInInput: true,
39
+ combo: 'enter',
40
+ label: 'Apply',
41
+ group: 'Column Filter',
42
+ onKeyDown: () =>
43
+ // Wait for debounced reaction in `ValuesTabModel` to run before committing
44
+ wait(400).then(() => {
45
+ if (model.isDirty) model.commit();
46
+ })
47
+ }
48
+ ]
35
49
  });
36
50
  }
37
51
  });
38
52
 
39
53
  const bbar = hoistCmp.factory<HeaderFilterModel>({
40
54
  render({model}) {
41
- const {commitOnChange} = model;
55
+ const {commitOnChange, hasFilter, isDirty} = model;
42
56
  return toolbar({
43
57
  compact: true,
44
58
  items: [
45
- filler(),
46
59
  button({
47
60
  icon: Icon.delete(),
48
- text: 'Clear Filter',
61
+ text: 'Clear',
49
62
  intent: 'danger',
50
- disabled: !model.hasFilter,
63
+ disabled: !hasFilter,
51
64
  onClick: () => model.clear()
52
65
  }),
66
+ filler(),
67
+ button({
68
+ omit: commitOnChange,
69
+ text: 'Cancel',
70
+ onClick: () => model.parent.close()
71
+ }),
53
72
  button({
54
73
  omit: commitOnChange,
55
- icon: Icon.check(),
56
- text: 'Apply Filter',
57
- intent: 'success',
58
- disabled: !model.hasFilter && !model.hasPendingFilter,
74
+ text: 'Apply',
75
+ disabled: !isDirty,
76
+ intent: 'primary',
77
+ minimal: false,
59
78
  onClick: () => model.commit()
60
79
  })
61
80
  ]
@@ -7,10 +7,19 @@
7
7
 
8
8
  import {TabContainerModel} from '@xh/hoist/cmp/tab';
9
9
  import {HoistModel, managed, lookup} from '@xh/hoist/core';
10
+ import {
11
+ CompoundFilter,
12
+ FieldFilter,
13
+ FieldType,
14
+ Filter,
15
+ FilterLike,
16
+ parseFilter,
17
+ Store
18
+ } from '@xh/hoist/data';
10
19
  import {action, computed} from '@xh/hoist/mobx';
11
20
  import {wait} from '@xh/hoist/promise';
12
21
  import {isEmpty} from 'lodash';
13
- import {GridFilterFieldSpec} from '@xh/hoist/cmp/grid';
22
+ import {GridFilterFieldSpec, GridFilterModel} from '@xh/hoist/cmp/grid';
14
23
  import {customTab} from './custom/CustomTab';
15
24
  import {CustomTabModel} from './custom/CustomTabModel';
16
25
  import {valuesTab} from './values/ValuesTab';
@@ -29,43 +38,54 @@ export class HeaderFilterModel extends HoistModel {
29
38
  @managed valuesTabModel: ValuesTabModel;
30
39
  @managed customTabModel: CustomTabModel;
31
40
 
32
- get filterModel() {
41
+ get filterModel(): GridFilterModel {
33
42
  return this.parent.filterModel;
34
43
  }
35
44
 
36
- get field() {
45
+ get field(): string {
37
46
  return this.fieldSpec.field;
38
47
  }
39
48
 
40
- get store() {
49
+ get store(): Store {
41
50
  return this.filterModel.gridModel.store;
42
51
  }
43
52
 
44
- get fieldType() {
53
+ get fieldType(): FieldType {
45
54
  return this.store.getField(this.field).type;
46
55
  }
47
56
 
48
- get currentGridFilter() {
57
+ get currentGridFilter(): Filter {
49
58
  return this.filterModel.filter;
50
59
  }
51
60
 
52
- get columnFilters() {
61
+ get columnFilters(): FieldFilter[] {
53
62
  return this.filterModel.getColumnFilters(this.field);
54
63
  }
55
64
 
56
- get columnCompoundFilter() {
65
+ get columnCompoundFilter(): CompoundFilter {
57
66
  return this.filterModel.getColumnCompoundFilter(this.field);
58
67
  }
59
68
 
60
- get hasFilter() {
69
+ get hasFilter(): boolean {
61
70
  return !isEmpty(this.columnFilters);
62
71
  }
63
72
 
64
- get hasPendingFilter() {
73
+ get pendingFilter(): FilterLike {
65
74
  const {activeTabId} = this.tabContainerModel;
66
75
  return activeTabId === 'valuesFilter'
67
- ? !!this.valuesTabModel.filter
68
- : !!this.customTabModel.filter;
76
+ ? this.valuesTabModel.filter
77
+ : this.customTabModel.filter;
78
+ }
79
+
80
+ get hasPendingFilter(): boolean {
81
+ return !!this.pendingFilter;
82
+ }
83
+
84
+ @computed
85
+ get isDirty(): boolean {
86
+ const current = parseFilter(this.columnFilters),
87
+ pending = parseFilter(this.pendingFilter);
88
+ return current ? !current.equals(pending) : !!pending;
69
89
  }
70
90
 
71
91
  @computed
@@ -96,6 +96,8 @@ export class ValuesTabModel extends HoistModel {
96
96
  {
97
97
  track: () => this.filterText,
98
98
  run: () => this.onFilterTextChange(),
99
+ // Must be longer than the `filterBuffer` on `storeFilterField` since this Grid's
100
+ // filtered RecordSet must be current before `onFilterTextChange` can run.
99
101
  debounce: 300
100
102
  },
101
103
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xh/hoist",
3
- "version": "76.0.0-SNAPSHOT.1755706862648",
3
+ "version": "76.0.0-SNAPSHOT.1755780982882",
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",
@@ -110,18 +110,6 @@ export class PrefService extends HoistService {
110
110
  return this.pushPendingAsync();
111
111
  }
112
112
 
113
- /**
114
- * Reset *all* preferences, reverting their effective values back to defaults.
115
- * @returns a Promise that resolves when preferences have been cleared and defaults reloaded.
116
- */
117
- async clearAllAsync() {
118
- await XH.fetchJson({
119
- url: 'xh/clearPrefs',
120
- params: {clientUsername: XH.getUsername()}
121
- });
122
- return this.loadPrefsAsync();
123
- }
124
-
125
113
  /**
126
114
  * Push any pending buffered updates to persist newly set values to server.
127
115
  * Called automatically by this app on page unload to avoid dropping changes when e.g. a user