@xh/hoist 59.2.0 → 59.3.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 +63 -14
- package/admin/AppComponent.ts +1 -1
- package/admin/tabs/activity/ActivityTab.ts +1 -1
- package/admin/tabs/general/GeneralTab.ts +2 -2
- package/admin/tabs/general/config/ConfigPanel.ts +1 -0
- package/admin/tabs/monitor/MonitorTab.ts +1 -1
- package/admin/tabs/server/ServerTab.ts +1 -1
- package/admin/tabs/userData/UserDataTab.ts +1 -1
- package/cmp/ag-grid/AgGrid.scss +51 -25
- package/cmp/ag-grid/AgGrid.ts +8 -2
- package/cmp/badge/Badge.ts +18 -5
- package/cmp/chart/Chart.ts +13 -11
- package/cmp/clock/Clock.ts +6 -5
- package/cmp/dataview/DataView.ts +5 -3
- package/cmp/form/Form.ts +25 -6
- package/cmp/grid/Grid.ts +41 -25
- package/cmp/grid/GridModel.ts +2 -2
- package/cmp/grid/Types.ts +1 -1
- package/cmp/grid/columns/Column.ts +45 -2
- package/cmp/grid/impl/GridHScrollbar.ts +140 -0
- package/cmp/grid/renderers/MultiFieldRenderer.ts +1 -1
- package/cmp/input/HoistInputModel.ts +4 -4
- package/cmp/input/HoistInputProps.ts +3 -1
- package/cmp/layout/Box.ts +4 -2
- package/cmp/relativetimestamp/RelativeTimestamp.ts +106 -40
- package/cmp/store/StoreFilterField.ts +2 -2
- package/cmp/tab/TabContainer.ts +1 -1
- package/cmp/zoneGrid/Types.ts +47 -0
- package/cmp/zoneGrid/ZoneGrid.ts +62 -0
- package/cmp/zoneGrid/ZoneGridModel.ts +666 -0
- package/cmp/zoneGrid/impl/ZoneGridPersistenceModel.ts +143 -0
- package/cmp/zoneGrid/impl/ZoneMapperModel.ts +335 -0
- package/cmp/zoneGrid/index.ts +3 -0
- package/core/HoistComponent.ts +23 -10
- package/core/HoistProps.ts +25 -6
- package/core/XH.ts +49 -27
- package/core/elem.ts +11 -3
- package/core/impl/InstanceManager.ts +24 -1
- package/core/model/HoistModel.ts +4 -4
- package/data/RecordAction.ts +7 -4
- package/data/StoreRecord.ts +8 -1
- package/desktop/appcontainer/AppContainer.ts +2 -0
- package/desktop/cmp/appbar/AppBar.ts +8 -6
- package/desktop/cmp/button/Button.ts +14 -3
- package/desktop/cmp/button/ButtonGroup.ts +14 -3
- package/desktop/cmp/button/ZoneMapperButton.ts +82 -0
- package/desktop/cmp/button/index.ts +1 -0
- package/desktop/cmp/dash/canvas/DashCanvas.ts +14 -4
- package/desktop/cmp/dash/container/DashContainer.ts +11 -4
- package/desktop/cmp/error/ErrorMessage.ts +9 -8
- package/desktop/cmp/form/FormField.ts +34 -10
- package/desktop/cmp/grid/columns/Actions.ts +2 -1
- package/desktop/cmp/grid/impl/colchooser/ColChooser.ts +3 -2
- package/desktop/cmp/grouping/GroupingChooser.ts +29 -29
- package/desktop/cmp/input/ButtonGroupInput.ts +1 -1
- package/desktop/cmp/input/Checkbox.ts +3 -3
- package/desktop/cmp/input/CodeInput.ts +2 -1
- package/desktop/cmp/input/DateInput.ts +128 -123
- package/desktop/cmp/input/JsonInput.ts +1 -1
- package/desktop/cmp/input/NumberInput.ts +3 -2
- package/desktop/cmp/input/RadioInput.ts +3 -1
- package/desktop/cmp/input/Select.ts +31 -4
- package/desktop/cmp/input/SwitchInput.ts +2 -1
- package/desktop/cmp/input/TextArea.ts +3 -3
- package/desktop/cmp/input/TextInput.ts +51 -47
- package/desktop/cmp/panel/Panel.ts +21 -19
- package/desktop/cmp/panel/impl/ResizeContainer.ts +3 -2
- package/desktop/cmp/pinpad/impl/PinPad.ts +4 -3
- package/desktop/cmp/record/RecordActionBar.ts +12 -3
- package/desktop/cmp/record/impl/RecordActionButton.ts +1 -0
- package/desktop/cmp/rest/Actions.ts +10 -5
- package/desktop/cmp/rest/RestGrid.ts +20 -6
- package/desktop/cmp/rest/impl/RestForm.ts +5 -4
- package/desktop/cmp/rest/impl/RestGridToolbar.ts +3 -2
- package/desktop/cmp/tab/TabSwitcher.ts +8 -3
- package/desktop/cmp/tab/impl/Tab.ts +2 -1
- package/desktop/cmp/tab/impl/TabContainer.ts +18 -15
- package/desktop/cmp/toolbar/Toolbar.ts +3 -1
- package/desktop/cmp/treemap/SplitTreeMap.ts +2 -1
- package/desktop/cmp/treemap/TreeMap.ts +5 -3
- package/desktop/cmp/zoneGrid/impl/ZoneMapper.scss +71 -0
- package/desktop/cmp/zoneGrid/impl/ZoneMapper.ts +232 -0
- package/desktop/cmp/zoneGrid/impl/ZoneMapperDialog.ts +35 -0
- package/dynamics/desktop.ts +2 -0
- package/dynamics/mobile.ts +2 -0
- package/inspector/instances/InstancesModel.ts +2 -2
- package/mobile/appcontainer/AppContainer.ts +2 -0
- package/mobile/cmp/button/ZoneMapperButton.ts +41 -0
- package/mobile/cmp/button/index.ts +1 -0
- package/mobile/cmp/error/ErrorMessage.ts +4 -4
- package/mobile/cmp/input/Select.scss +1 -0
- package/mobile/cmp/input/Select.ts +7 -0
- package/mobile/cmp/input/TextInput.ts +1 -0
- package/mobile/cmp/panel/DialogPanel.scss +18 -6
- package/mobile/cmp/panel/DialogPanel.ts +3 -1
- package/mobile/cmp/zoneGrid/impl/ZoneMapper.scss +67 -0
- package/mobile/cmp/zoneGrid/impl/ZoneMapper.ts +236 -0
- package/package.json +4 -3
- package/styles/vars.scss +3 -3
- package/svc/InspectorService.ts +1 -1
- package/utils/js/DomUtils.ts +10 -0
- package/utils/js/LangUtils.ts +10 -0
- package/utils/js/TestUtils.ts +9 -0
- package/utils/js/index.ts +1 -0
package/core/XH.ts
CHANGED
|
@@ -5,27 +5,7 @@
|
|
|
5
5
|
* Copyright © 2023 Extremely Heavy Industries Inc.
|
|
6
6
|
*/
|
|
7
7
|
import {RouterModel} from '@xh/hoist/appcontainer/RouterModel';
|
|
8
|
-
import {Router, State} from 'router5';
|
|
9
|
-
import {
|
|
10
|
-
HoistService,
|
|
11
|
-
AppSpec,
|
|
12
|
-
AppState,
|
|
13
|
-
Exception,
|
|
14
|
-
ExceptionHandlerOptions,
|
|
15
|
-
ExceptionHandler,
|
|
16
|
-
TrackOptions,
|
|
17
|
-
SizingMode,
|
|
18
|
-
HoistServiceClass,
|
|
19
|
-
Theme,
|
|
20
|
-
PlainObject,
|
|
21
|
-
HoistException,
|
|
22
|
-
PageState,
|
|
23
|
-
AppSuspendData,
|
|
24
|
-
FetchResponse
|
|
25
|
-
} from './';
|
|
26
8
|
import {Store} from '@xh/hoist/data';
|
|
27
|
-
import {instanceManager} from './impl/InstanceManager';
|
|
28
|
-
import {installServicesAsync} from './impl/InstallServices';
|
|
29
9
|
import {Icon} from '@xh/hoist/icon';
|
|
30
10
|
import {action} from '@xh/hoist/mobx';
|
|
31
11
|
import {never} from '@xh/hoist/promise';
|
|
@@ -35,6 +15,7 @@ import {
|
|
|
35
15
|
ChangelogService,
|
|
36
16
|
ConfigService,
|
|
37
17
|
EnvironmentService,
|
|
18
|
+
FetchOptions,
|
|
38
19
|
FetchService,
|
|
39
20
|
GridAutosizeService,
|
|
40
21
|
GridExportService,
|
|
@@ -45,17 +26,41 @@ import {
|
|
|
45
26
|
LocalStorageService,
|
|
46
27
|
PrefService,
|
|
47
28
|
TrackService,
|
|
48
|
-
WebSocketService
|
|
49
|
-
FetchOptions
|
|
29
|
+
WebSocketService
|
|
50
30
|
} from '@xh/hoist/svc';
|
|
51
31
|
import {camelCase, flatten, isString, uniqueId} from 'lodash';
|
|
32
|
+
import {Router, State} from 'router5';
|
|
33
|
+
import {CancelFn} from 'router5/types/types/base';
|
|
52
34
|
import {AppContainerModel} from '../appcontainer/AppContainerModel';
|
|
53
|
-
import {ToastModel} from '../appcontainer/ToastModel';
|
|
54
35
|
import {BannerModel} from '../appcontainer/BannerModel';
|
|
36
|
+
import {ToastModel} from '../appcontainer/ToastModel';
|
|
55
37
|
import '../styles/XH.scss';
|
|
56
|
-
import {
|
|
57
|
-
|
|
58
|
-
|
|
38
|
+
import {
|
|
39
|
+
AppSpec,
|
|
40
|
+
AppState,
|
|
41
|
+
AppSuspendData,
|
|
42
|
+
BannerSpec,
|
|
43
|
+
Exception,
|
|
44
|
+
ExceptionHandler,
|
|
45
|
+
ExceptionHandlerOptions,
|
|
46
|
+
FetchResponse,
|
|
47
|
+
HoistAppModel,
|
|
48
|
+
HoistException,
|
|
49
|
+
HoistService,
|
|
50
|
+
HoistServiceClass,
|
|
51
|
+
HoistUser,
|
|
52
|
+
MessageSpec,
|
|
53
|
+
PageState,
|
|
54
|
+
PlainObject,
|
|
55
|
+
SizingMode,
|
|
56
|
+
TaskObserver,
|
|
57
|
+
Theme,
|
|
58
|
+
ToastSpec,
|
|
59
|
+
TrackOptions
|
|
60
|
+
} from './';
|
|
61
|
+
import {installServicesAsync} from './impl/InstallServices';
|
|
62
|
+
import {instanceManager} from './impl/InstanceManager';
|
|
63
|
+
import {HoistModel, ModelSelector, RefreshContextModel} from './model';
|
|
59
64
|
import {apiDeprecated} from '@xh/hoist/utils/js';
|
|
60
65
|
|
|
61
66
|
export const MIN_HOIST_CORE_VERSION = '16.0';
|
|
@@ -659,7 +664,7 @@ export class XHApi {
|
|
|
659
664
|
* Get a collection of Models currently 'active' in the app, returned in creation-time order.
|
|
660
665
|
* This will include all models that have not yet had `destroy()` called on them.
|
|
661
666
|
*/
|
|
662
|
-
|
|
667
|
+
getModels<T extends HoistModel>(selector: ModelSelector = '*'): T[] {
|
|
663
668
|
const ret = [];
|
|
664
669
|
instanceManager.models.forEach(m => {
|
|
665
670
|
if (m.matchesSelector(selector, true)) ret.push(m);
|
|
@@ -667,6 +672,23 @@ export class XHApi {
|
|
|
667
672
|
return ret;
|
|
668
673
|
}
|
|
669
674
|
|
|
675
|
+
/** Get the first active model that matches the given selector, or null if none found. */
|
|
676
|
+
getModel<T extends HoistModel>(selector: ModelSelector = '*'): T {
|
|
677
|
+
instanceManager.models.forEach(m => {
|
|
678
|
+
if (m.matchesSelector(selector, true)) return m;
|
|
679
|
+
});
|
|
680
|
+
return null;
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
/**
|
|
684
|
+
* Get the first active model that has been assigned the given testId, or null if none found.
|
|
685
|
+
* Note that a small subset of models are automatically assigned the testId of their component.
|
|
686
|
+
* @see InstanceManager.testSupportedModels
|
|
687
|
+
*/
|
|
688
|
+
getModelByTestId<T extends HoistModel>(testId: string): T {
|
|
689
|
+
return instanceManager.getModelByTestId(testId) as T;
|
|
690
|
+
}
|
|
691
|
+
|
|
670
692
|
/** All services registered with this application. */
|
|
671
693
|
getServices(): HoistService[] {
|
|
672
694
|
return Array.from(instanceManager.services);
|
package/core/elem.ts
CHANGED
|
@@ -4,14 +4,15 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Copyright © 2023 Extremely Heavy Industries Inc.
|
|
6
6
|
*/
|
|
7
|
+
import {TEST_ID} from '@xh/hoist/utils/js';
|
|
7
8
|
import {castArray, isFunction, isNil, isPlainObject} from 'lodash';
|
|
8
9
|
import {
|
|
9
10
|
createElement as reactCreateElement,
|
|
11
|
+
ForwardedRef,
|
|
10
12
|
isValidElement,
|
|
11
|
-
|
|
13
|
+
Key,
|
|
12
14
|
ReactElement,
|
|
13
|
-
|
|
14
|
-
Key
|
|
15
|
+
ReactNode
|
|
15
16
|
} from 'react';
|
|
16
17
|
import {PlainObject, Some, Thunkable} from './types/Types';
|
|
17
18
|
|
|
@@ -60,6 +61,13 @@ export type ElementSpec<P extends PlainObject> = P & {
|
|
|
60
61
|
/** React key for this component. */
|
|
61
62
|
key?: Key;
|
|
62
63
|
|
|
64
|
+
/**
|
|
65
|
+
* Supports passing a "data-testid" prop to built-in tags (e.g. `div`), to be rendered as an
|
|
66
|
+
* HTML attribute. See {@link TestSupportProps} for the higher-level `testId` prop that most
|
|
67
|
+
* Hoist components accept and should use.
|
|
68
|
+
*/
|
|
69
|
+
[TEST_ID]?: string;
|
|
70
|
+
|
|
63
71
|
//----------------------------
|
|
64
72
|
// Technical -- Escape support
|
|
65
73
|
//----------------------------
|
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
* Copyright © 2023 Extremely Heavy Industries Inc.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import {HoistService, HoistModel} from '
|
|
8
|
+
import {HoistService, HoistModel} from '../';
|
|
9
|
+
import {isNil} from 'lodash';
|
|
9
10
|
import {Store} from '@xh/hoist/data';
|
|
10
11
|
import {observable, makeObservable} from '@xh/hoist/mobx';
|
|
11
12
|
import {wait} from '@xh/hoist/promise';
|
|
@@ -24,6 +25,9 @@ class InstanceManager {
|
|
|
24
25
|
@observable.shallow
|
|
25
26
|
stores: Set<Store> = new Set();
|
|
26
27
|
|
|
28
|
+
private modelsByTestId: Map<string, HoistModel> = new Map();
|
|
29
|
+
private testSupportedModels = new Set(['GridModel', 'DataViewModel', 'FormModel', 'TabModel']);
|
|
30
|
+
|
|
27
31
|
registerModel(m: HoistModel) {
|
|
28
32
|
wait().thenAction(() => this.models.add(m));
|
|
29
33
|
}
|
|
@@ -44,6 +48,25 @@ class InstanceManager {
|
|
|
44
48
|
wait().thenAction(() => this.stores.delete(s));
|
|
45
49
|
}
|
|
46
50
|
|
|
51
|
+
registerModelWithTestId(testId: string, m: HoistModel) {
|
|
52
|
+
if (
|
|
53
|
+
isNil(testId) ||
|
|
54
|
+
!m.isHoistModel ||
|
|
55
|
+
!this.testSupportedModels.has(m.constructor.name) ||
|
|
56
|
+
this.modelsByTestId.has(testId)
|
|
57
|
+
)
|
|
58
|
+
return;
|
|
59
|
+
this.modelsByTestId.set(testId, m);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
unregisterModelWithTestId(testId: string) {
|
|
63
|
+
this.modelsByTestId.delete(testId);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
getModelByTestId(testId: string): HoistModel {
|
|
67
|
+
return this.modelsByTestId.get(testId);
|
|
68
|
+
}
|
|
69
|
+
|
|
47
70
|
constructor() {
|
|
48
71
|
makeObservable(this);
|
|
49
72
|
}
|
package/core/model/HoistModel.ts
CHANGED
|
@@ -4,13 +4,13 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Copyright © 2023 Extremely Heavy Industries Inc.
|
|
6
6
|
*/
|
|
7
|
-
import {
|
|
7
|
+
import {action, makeObservable, observable} from '@xh/hoist/mobx';
|
|
8
8
|
import {warnIf} from '@xh/hoist/utils/js';
|
|
9
|
+
import {forOwn, has, isFunction} from 'lodash';
|
|
9
10
|
import {DefaultHoistProps, HoistBase, managed, PlainObject} from '../';
|
|
10
|
-
import {ModelSelector} from './';
|
|
11
|
-
import {LoadSupport, LoadSpec, Loadable} from '../load';
|
|
12
|
-
import {observable, action, makeObservable} from '@xh/hoist/mobx';
|
|
13
11
|
import {instanceManager} from '../impl/InstanceManager';
|
|
12
|
+
import {Loadable, LoadSpec, LoadSupport} from '../load';
|
|
13
|
+
import {ModelSelector} from './';
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* Core superclass for stateful Models in Hoist. Models are used throughout the toolkit and
|
package/data/RecordAction.ts
CHANGED
|
@@ -7,11 +7,11 @@
|
|
|
7
7
|
|
|
8
8
|
import {isBoolean, isEmpty, isNil, isNumber, isString} from 'lodash';
|
|
9
9
|
import {ReactElement} from 'react';
|
|
10
|
-
import {Intent, PlainObject} from '../core';
|
|
10
|
+
import {Intent, PlainObject, TestSupportProps} from '../core';
|
|
11
11
|
import {StoreRecord} from './StoreRecord';
|
|
12
|
-
import {
|
|
12
|
+
import {Column, GridModel} from '../cmp/grid';
|
|
13
13
|
|
|
14
|
-
export interface RecordActionSpec {
|
|
14
|
+
export interface RecordActionSpec extends TestSupportProps {
|
|
15
15
|
/** Label to be displayed. */
|
|
16
16
|
text?: string;
|
|
17
17
|
|
|
@@ -112,6 +112,7 @@ export class RecordAction {
|
|
|
112
112
|
disabled: boolean;
|
|
113
113
|
hidden: boolean;
|
|
114
114
|
recordsRequired: boolean | number;
|
|
115
|
+
testId: string;
|
|
115
116
|
|
|
116
117
|
constructor({
|
|
117
118
|
text,
|
|
@@ -125,7 +126,8 @@ export class RecordAction {
|
|
|
125
126
|
disabled = false,
|
|
126
127
|
hidden = false,
|
|
127
128
|
displayFn = null,
|
|
128
|
-
recordsRequired = false
|
|
129
|
+
recordsRequired = false,
|
|
130
|
+
testId = null
|
|
129
131
|
}: RecordActionSpec) {
|
|
130
132
|
this.text = text;
|
|
131
133
|
this.secondaryText = secondaryText;
|
|
@@ -138,6 +140,7 @@ export class RecordAction {
|
|
|
138
140
|
this.hidden = hidden;
|
|
139
141
|
this.displayFn = displayFn;
|
|
140
142
|
this.recordsRequired = recordsRequired;
|
|
143
|
+
this.testId = testId;
|
|
141
144
|
|
|
142
145
|
this.items = items?.map(it => {
|
|
143
146
|
if (isString(it)) return it;
|
package/data/StoreRecord.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import {PlainObject} from '@xh/hoist/core';
|
|
8
8
|
import {throwIf} from '@xh/hoist/utils/js';
|
|
9
|
-
import {isNil, flatMap} from 'lodash';
|
|
9
|
+
import {isNil, flatMap, isMatch} from 'lodash';
|
|
10
10
|
import {Store} from './Store';
|
|
11
11
|
import {ValidationState} from './validation/ValidationState';
|
|
12
12
|
import {RecordValidator} from './impl/RecordValidator';
|
|
@@ -243,6 +243,13 @@ export class StoreRecord {
|
|
|
243
243
|
this.store.getAncestorsById(this.id, fromFiltered).forEach(fn);
|
|
244
244
|
}
|
|
245
245
|
|
|
246
|
+
/**
|
|
247
|
+
* Tests to see if this Record's data matches the given partial data object.
|
|
248
|
+
*/
|
|
249
|
+
matchesData(partialData: PlainObject): boolean {
|
|
250
|
+
return isMatch(this.data, partialData);
|
|
251
|
+
}
|
|
252
|
+
|
|
246
253
|
// --------------------------
|
|
247
254
|
// Protected methods
|
|
248
255
|
// --------------------------
|
|
@@ -13,6 +13,7 @@ import {suspendPanel} from '@xh/hoist/desktop/appcontainer/SuspendPanel';
|
|
|
13
13
|
import {dockContainerImpl} from '@xh/hoist/desktop/cmp/dock/impl/DockContainer';
|
|
14
14
|
import {colChooserDialog as colChooser} from '@xh/hoist/desktop/cmp/grid/impl/colchooser/ColChooserDialog';
|
|
15
15
|
import {ColChooserModel} from '@xh/hoist/desktop/cmp/grid/impl/colchooser/ColChooserModel';
|
|
16
|
+
import {zoneMapperDialog as zoneMapper} from '@xh/hoist/desktop/cmp/zoneGrid/impl/ZoneMapperDialog';
|
|
16
17
|
import {columnHeaderFilter} from '@xh/hoist/desktop/cmp/grid/impl/filter/ColumnHeaderFilter';
|
|
17
18
|
import {ColumnHeaderFilterModel} from '@xh/hoist/desktop/cmp/grid/impl/filter/ColumnHeaderFilterModel';
|
|
18
19
|
import {gridFilterDialog} from '@xh/hoist/desktop/cmp/grid/impl/filter/GridFilterDialog';
|
|
@@ -48,6 +49,7 @@ installDesktopImpls({
|
|
|
48
49
|
storeFilterFieldImpl,
|
|
49
50
|
pinPadImpl,
|
|
50
51
|
colChooser,
|
|
52
|
+
zoneMapper,
|
|
51
53
|
columnHeaderFilter,
|
|
52
54
|
gridFilterDialog,
|
|
53
55
|
ColChooserModel,
|
|
@@ -6,18 +6,18 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import {span} from '@xh/hoist/cmp/layout';
|
|
9
|
-
import {hoistCmp, HoistProps, HSide, XH} from '@xh/hoist/core';
|
|
9
|
+
import {hoistCmp, HoistProps, HSide, TestSupportProps, XH} from '@xh/hoist/core';
|
|
10
10
|
import {appBarSeparator} from '@xh/hoist/desktop/cmp/appbar';
|
|
11
11
|
import {appMenuButton, AppMenuButtonProps, refreshButton} from '@xh/hoist/desktop/cmp/button';
|
|
12
12
|
import {whatsNewButton} from '@xh/hoist/desktop/cmp/button/WhatsNewButton';
|
|
13
13
|
import '@xh/hoist/desktop/register';
|
|
14
14
|
import {navbar, navbarGroup} from '@xh/hoist/kit/blueprint';
|
|
15
|
-
import {withDefault} from '@xh/hoist/utils/js';
|
|
15
|
+
import {TEST_ID, withDefault} from '@xh/hoist/utils/js';
|
|
16
16
|
import {isEmpty} from 'lodash';
|
|
17
|
-
import {
|
|
17
|
+
import {ReactElement, ReactNode} from 'react';
|
|
18
18
|
import './AppBar.scss';
|
|
19
19
|
|
|
20
|
-
export interface AppBarProps extends HoistProps {
|
|
20
|
+
export interface AppBarProps extends HoistProps, TestSupportProps {
|
|
21
21
|
/** Position of the AppMenuButton. */
|
|
22
22
|
appMenuButtonPosition?: HSide;
|
|
23
23
|
|
|
@@ -69,7 +69,8 @@ export const [AppBar, appBar] = hoistCmp.withFactory<AppBarProps>({
|
|
|
69
69
|
hideAppMenuButton,
|
|
70
70
|
className,
|
|
71
71
|
appMenuButtonProps = {},
|
|
72
|
-
appMenuButtonPosition = 'right'
|
|
72
|
+
appMenuButtonPosition = 'right',
|
|
73
|
+
testId
|
|
73
74
|
} = props;
|
|
74
75
|
|
|
75
76
|
const title = withDefault(props.title, XH.clientAppName);
|
|
@@ -102,7 +103,8 @@ export const [AppBar, appBar] = hoistCmp.withFactory<AppBarProps>({
|
|
|
102
103
|
})
|
|
103
104
|
]
|
|
104
105
|
})
|
|
105
|
-
]
|
|
106
|
+
],
|
|
107
|
+
[TEST_ID]: testId
|
|
106
108
|
});
|
|
107
109
|
}
|
|
108
110
|
});
|
|
@@ -6,19 +6,28 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import {ButtonProps as BpButtonProps} from '@blueprintjs/core';
|
|
8
8
|
import composeRefs from '@seznam/compose-react-refs';
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
hoistCmp,
|
|
11
|
+
HoistModel,
|
|
12
|
+
HoistProps,
|
|
13
|
+
Intent,
|
|
14
|
+
LayoutProps,
|
|
15
|
+
StyleProps,
|
|
16
|
+
TestSupportProps
|
|
17
|
+
} from '@xh/hoist/core';
|
|
10
18
|
import '@xh/hoist/desktop/register';
|
|
11
19
|
import {button as bpButton} from '@xh/hoist/kit/blueprint';
|
|
12
|
-
import {withDefault} from '@xh/hoist/utils/js';
|
|
20
|
+
import {TEST_ID, withDefault} from '@xh/hoist/utils/js';
|
|
13
21
|
import {splitLayoutProps} from '@xh/hoist/utils/react';
|
|
14
22
|
import classNames from 'classnames';
|
|
15
|
-
import {
|
|
23
|
+
import {ReactElement, ReactNode} from 'react';
|
|
16
24
|
import './Button.scss';
|
|
17
25
|
|
|
18
26
|
export interface ButtonProps<M extends HoistModel = null>
|
|
19
27
|
extends HoistProps<M>,
|
|
20
28
|
StyleProps,
|
|
21
29
|
LayoutProps,
|
|
30
|
+
TestSupportProps,
|
|
22
31
|
BpButtonProps {
|
|
23
32
|
active?: boolean;
|
|
24
33
|
autoFocus?: boolean;
|
|
@@ -69,6 +78,7 @@ export const [Button, button] = hoistCmp.withFactory<ButtonProps>({
|
|
|
69
78
|
tooltip,
|
|
70
79
|
active,
|
|
71
80
|
elementRef,
|
|
81
|
+
testId,
|
|
72
82
|
...rest
|
|
73
83
|
} = nonLayoutProps;
|
|
74
84
|
|
|
@@ -96,6 +106,7 @@ export const [Button, button] = hoistCmp.withFactory<ButtonProps>({
|
|
|
96
106
|
autoFocus,
|
|
97
107
|
className: classNames(className, classes),
|
|
98
108
|
elementRef: composeRefs(ref, elementRef),
|
|
109
|
+
[TEST_ID]: testId,
|
|
99
110
|
disabled,
|
|
100
111
|
icon,
|
|
101
112
|
intent,
|
|
@@ -4,17 +4,26 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Copyright © 2023 Extremely Heavy Industries Inc.
|
|
6
6
|
*/
|
|
7
|
-
import {SetOptional} from 'type-fest';
|
|
8
7
|
import {ButtonGroupProps as BpButtonGroupProps} from '@blueprintjs/core';
|
|
9
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
hoistCmp,
|
|
10
|
+
HoistModel,
|
|
11
|
+
HoistProps,
|
|
12
|
+
LayoutProps,
|
|
13
|
+
StyleProps,
|
|
14
|
+
TestSupportProps
|
|
15
|
+
} from '@xh/hoist/core';
|
|
10
16
|
import '@xh/hoist/desktop/register';
|
|
11
17
|
import {buttonGroup as bpButtonGroup} from '@xh/hoist/kit/blueprint';
|
|
18
|
+
import {TEST_ID} from '@xh/hoist/utils/js';
|
|
12
19
|
import {splitLayoutProps} from '@xh/hoist/utils/react';
|
|
20
|
+
import {SetOptional} from 'type-fest';
|
|
13
21
|
|
|
14
22
|
export interface ButtonGroupProps<M extends HoistModel = null>
|
|
15
23
|
extends HoistProps<M>,
|
|
16
24
|
LayoutProps,
|
|
17
25
|
StyleProps,
|
|
26
|
+
TestSupportProps,
|
|
18
27
|
SetOptional<BpButtonGroupProps, 'children'> {
|
|
19
28
|
/** True to have all buttons fill available width equally. */
|
|
20
29
|
fill?: boolean;
|
|
@@ -35,11 +44,13 @@ export const [ButtonGroup, buttonGroup] = hoistCmp.withFactory<ButtonGroupProps>
|
|
|
35
44
|
className: 'xh-button-group',
|
|
36
45
|
|
|
37
46
|
render(props, ref) {
|
|
38
|
-
const [layoutProps, {fill, minimal, vertical, style, ...rest}] =
|
|
47
|
+
const [layoutProps, {fill, minimal, vertical, style, testId, ...rest}] =
|
|
48
|
+
splitLayoutProps(props);
|
|
39
49
|
return bpButtonGroup({
|
|
40
50
|
fill,
|
|
41
51
|
minimal,
|
|
42
52
|
vertical,
|
|
53
|
+
[TEST_ID]: testId,
|
|
43
54
|
style: {
|
|
44
55
|
...style,
|
|
45
56
|
...layoutProps
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This file belongs to Hoist, an application development toolkit
|
|
3
|
+
* developed by Extremely Heavy Industries (www.xh.io | info@xh.io)
|
|
4
|
+
*
|
|
5
|
+
* Copyright © 2023 Extremely Heavy Industries Inc.
|
|
6
|
+
*/
|
|
7
|
+
import '@xh/hoist/desktop/register';
|
|
8
|
+
import {hoistCmp, useContextModel} from '@xh/hoist/core';
|
|
9
|
+
import {div, vbox} from '@xh/hoist/cmp/layout';
|
|
10
|
+
import {ZoneGridModel} from '@xh/hoist/cmp/zoneGrid';
|
|
11
|
+
import {ZoneMapperModel} from '@xh/hoist/cmp/zoneGrid/impl/ZoneMapperModel';
|
|
12
|
+
import {zoneMapper} from '@xh/hoist/desktop/cmp/zoneGrid/impl/ZoneMapper';
|
|
13
|
+
import {Icon} from '@xh/hoist/icon';
|
|
14
|
+
import {popover, Position} from '@xh/hoist/kit/blueprint';
|
|
15
|
+
import {stopPropagation, withDefault} from '@xh/hoist/utils/js';
|
|
16
|
+
import {button, ButtonProps} from './Button';
|
|
17
|
+
|
|
18
|
+
export interface ZoneMapperButtonProps extends ButtonProps {
|
|
19
|
+
/** ZoneGridModel of the grid for which this button should show a chooser. */
|
|
20
|
+
zoneGridModel?: ZoneGridModel;
|
|
21
|
+
|
|
22
|
+
/** Position for chooser popover, as per Blueprint docs. */
|
|
23
|
+
popoverPosition?: Position;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* A convenience button to trigger the display of a ZoneMapper UI for ZoneGrid configuration.
|
|
28
|
+
*
|
|
29
|
+
* Requires a `ZoneGridModel.zoneMapperModel` config option, set to true for default implementation.
|
|
30
|
+
*/
|
|
31
|
+
export const [ZoneMapperButton, zoneMapperButton] = hoistCmp.withFactory<ZoneMapperButtonProps>({
|
|
32
|
+
displayName: 'ZoneMapperButton',
|
|
33
|
+
model: false,
|
|
34
|
+
render({icon, title, zoneGridModel, popoverPosition, disabled, ...rest}, ref) {
|
|
35
|
+
zoneGridModel = withDefault(zoneGridModel, useContextModel(ZoneGridModel));
|
|
36
|
+
|
|
37
|
+
const mapperModel = zoneGridModel?.mapperModel as ZoneMapperModel;
|
|
38
|
+
|
|
39
|
+
if (!zoneGridModel) {
|
|
40
|
+
console.error(
|
|
41
|
+
"No ZoneGridModel available to ZoneMapperButton. Provide via a 'zoneGridModel' prop, or context."
|
|
42
|
+
);
|
|
43
|
+
disabled = true;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (!mapperModel) {
|
|
47
|
+
console.error(
|
|
48
|
+
'No ZoneMapperModel available on bound ZoneGridModel - enable via ZoneGridModel.zoneMapperModel config.'
|
|
49
|
+
);
|
|
50
|
+
disabled = true;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const isOpen = mapperModel?.isPopoverOpen;
|
|
54
|
+
return popover({
|
|
55
|
+
isOpen,
|
|
56
|
+
popoverClassName: 'xh-zone-mapper-popover xh-popup--framed',
|
|
57
|
+
position: withDefault(popoverPosition, 'auto'),
|
|
58
|
+
target: button({
|
|
59
|
+
icon: withDefault(icon, Icon.gridLarge()),
|
|
60
|
+
title: withDefault(title, 'Customize fields...'),
|
|
61
|
+
disabled,
|
|
62
|
+
...rest
|
|
63
|
+
}),
|
|
64
|
+
disabled,
|
|
65
|
+
content: vbox({
|
|
66
|
+
onClick: stopPropagation,
|
|
67
|
+
onDoubleClick: stopPropagation,
|
|
68
|
+
items: [
|
|
69
|
+
div({ref, className: 'xh-popup__title', item: 'Customize Fields'}),
|
|
70
|
+
zoneMapper({model: mapperModel})
|
|
71
|
+
]
|
|
72
|
+
}),
|
|
73
|
+
onInteraction: (nextOpenState, e) => {
|
|
74
|
+
if (nextOpenState) {
|
|
75
|
+
mapperModel.openPopover();
|
|
76
|
+
} else {
|
|
77
|
+
mapperModel.close();
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
});
|
|
@@ -7,10 +7,19 @@
|
|
|
7
7
|
import {ContextMenu} from '@blueprintjs/core';
|
|
8
8
|
import composeRefs from '@seznam/compose-react-refs';
|
|
9
9
|
import {div, vbox, vspacer} from '@xh/hoist/cmp/layout';
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
elementFactory,
|
|
12
|
+
hoistCmp,
|
|
13
|
+
HoistProps,
|
|
14
|
+
refreshContextView,
|
|
15
|
+
TestSupportProps,
|
|
16
|
+
uses,
|
|
17
|
+
XH
|
|
18
|
+
} from '@xh/hoist/core';
|
|
11
19
|
import {dashCanvasAddViewButton} from '@xh/hoist/desktop/cmp/button/DashCanvasAddViewButton';
|
|
12
20
|
import '@xh/hoist/desktop/register';
|
|
13
21
|
import {Classes, overlay} from '@xh/hoist/kit/blueprint';
|
|
22
|
+
import {TEST_ID} from '@xh/hoist/utils/js';
|
|
14
23
|
import {useOnVisibleChange} from '@xh/hoist/utils/react';
|
|
15
24
|
import classNames from 'classnames';
|
|
16
25
|
import ReactGridLayout, {WidthProvider} from 'react-grid-layout';
|
|
@@ -20,7 +29,7 @@ import {DashCanvasModel} from './DashCanvasModel';
|
|
|
20
29
|
import {dashCanvasContextMenu} from './impl/DashCanvasContextMenu';
|
|
21
30
|
import {dashCanvasView} from './impl/DashCanvasView';
|
|
22
31
|
|
|
23
|
-
export type DashCanvasProps = HoistProps<DashCanvasModel
|
|
32
|
+
export type DashCanvasProps = HoistProps<DashCanvasModel> & TestSupportProps;
|
|
24
33
|
|
|
25
34
|
/**
|
|
26
35
|
* Dashboard-style container that allows users to drag-and-drop child widgets into flexible layouts.
|
|
@@ -38,7 +47,7 @@ export const [DashCanvas, dashCanvas] = hoistCmp.withFactory<DashCanvasProps>({
|
|
|
38
47
|
className: 'xh-dash-canvas',
|
|
39
48
|
model: uses(DashCanvasModel),
|
|
40
49
|
|
|
41
|
-
render({className, model}, ref) {
|
|
50
|
+
render({className, model, testId}, ref) {
|
|
42
51
|
const isDraggable = !model.layoutLocked,
|
|
43
52
|
isResizable = !model.layoutLocked;
|
|
44
53
|
|
|
@@ -85,7 +94,8 @@ export const [DashCanvas, dashCanvas] = hoistCmp.withFactory<DashCanvasProps>({
|
|
|
85
94
|
)
|
|
86
95
|
}),
|
|
87
96
|
emptyContainerOverlay()
|
|
88
|
-
]
|
|
97
|
+
],
|
|
98
|
+
[TEST_ID]: testId
|
|
89
99
|
})
|
|
90
100
|
});
|
|
91
101
|
}
|
|
@@ -6,7 +6,14 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import composeRefs from '@seznam/compose-react-refs';
|
|
8
8
|
import {div, frame, vbox, vspacer} from '@xh/hoist/cmp/layout';
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
hoistCmp,
|
|
11
|
+
HoistProps,
|
|
12
|
+
ModelLookupContext,
|
|
13
|
+
refreshContextView,
|
|
14
|
+
TestSupportProps,
|
|
15
|
+
uses
|
|
16
|
+
} from '@xh/hoist/core';
|
|
10
17
|
import {mask} from '@xh/hoist/desktop/cmp/mask';
|
|
11
18
|
import {Classes, overlay} from '@xh/hoist/kit/blueprint';
|
|
12
19
|
import {useOnMount, useOnResize} from '@xh/hoist/utils/react';
|
|
@@ -15,7 +22,7 @@ import './DashContainer.scss';
|
|
|
15
22
|
import {DashContainerModel} from './DashContainerModel';
|
|
16
23
|
import {dashContainerAddViewButton} from './impl/DashContainerContextMenu';
|
|
17
24
|
|
|
18
|
-
export type DashContainerProps = HoistProps<DashContainerModel
|
|
25
|
+
export type DashContainerProps = HoistProps<DashContainerModel> & TestSupportProps;
|
|
19
26
|
|
|
20
27
|
/**
|
|
21
28
|
* Display a set of child components in accordance with a DashContainerModel.
|
|
@@ -25,7 +32,7 @@ export const [DashContainer, dashContainer] = hoistCmp.withFactory<DashContainer
|
|
|
25
32
|
model: uses(DashContainerModel),
|
|
26
33
|
className: 'xh-dash-container',
|
|
27
34
|
|
|
28
|
-
render({model, className}, ref) {
|
|
35
|
+
render({model, className, testId}, ref) {
|
|
29
36
|
// Store current ModelLookupContext in model, to be applied in views later
|
|
30
37
|
const context = useContext(ModelLookupContext);
|
|
31
38
|
useOnMount(() => (model.modelLookupContext = context));
|
|
@@ -39,7 +46,7 @@ export const [DashContainer, dashContainer] = hoistCmp.withFactory<DashContainer
|
|
|
39
46
|
return refreshContextView({
|
|
40
47
|
model: model.refreshContextModel,
|
|
41
48
|
item: frame(
|
|
42
|
-
frame({className, ref}),
|
|
49
|
+
frame({className, ref, testId}),
|
|
43
50
|
mask({spinner: true, bind: model.loadingStateTask}),
|
|
44
51
|
emptyContainerOverlay()
|
|
45
52
|
)
|
|
@@ -5,16 +5,15 @@
|
|
|
5
5
|
* Copyright © 2023 Extremely Heavy Industries Inc.
|
|
6
6
|
*/
|
|
7
7
|
import {div, filler, frame, hbox, p} from '@xh/hoist/cmp/layout';
|
|
8
|
-
import {hoistCmp, HoistProps} from '@xh/hoist/core';
|
|
8
|
+
import {BoxProps, hoistCmp, HoistProps} from '@xh/hoist/core';
|
|
9
9
|
import {button, ButtonProps} from '@xh/hoist/desktop/cmp/button';
|
|
10
10
|
import '@xh/hoist/desktop/register';
|
|
11
11
|
import {isNil, isString} from 'lodash';
|
|
12
12
|
import {isValidElement, ReactNode} from 'react';
|
|
13
|
-
|
|
14
13
|
import './ErrorMessage.scss';
|
|
15
14
|
import {Icon} from '@xh/hoist/icon';
|
|
16
15
|
|
|
17
|
-
export interface ErrorMessageProps extends HoistProps {
|
|
16
|
+
export interface ErrorMessageProps extends HoistProps, Omit<BoxProps, 'title'> {
|
|
18
17
|
/**
|
|
19
18
|
* If provided, will render a "Retry" button that calls this function.
|
|
20
19
|
* Use `actionButtonProps` for further control over this button.
|
|
@@ -69,7 +68,8 @@ export const [ErrorMessage, errorMessage] = hoistCmp.withFactory<ErrorMessagePro
|
|
|
69
68
|
actionFn,
|
|
70
69
|
actionButtonProps,
|
|
71
70
|
detailsFn,
|
|
72
|
-
detailsButtonProps
|
|
71
|
+
detailsButtonProps,
|
|
72
|
+
...rest
|
|
73
73
|
} = props;
|
|
74
74
|
|
|
75
75
|
if (isNil(error)) return null;
|
|
@@ -77,17 +77,17 @@ export const [ErrorMessage, errorMessage] = hoistCmp.withFactory<ErrorMessagePro
|
|
|
77
77
|
if (!message) {
|
|
78
78
|
if (isString(error)) {
|
|
79
79
|
message = error;
|
|
80
|
-
} else
|
|
81
|
-
message = error.message;
|
|
80
|
+
} else {
|
|
81
|
+
message = error.message || error.name || 'Unknown Error';
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
84
|
|
|
85
85
|
if (actionFn) {
|
|
86
|
-
actionButtonProps = {...actionButtonProps, onClick:
|
|
86
|
+
actionButtonProps = {...actionButtonProps, onClick: () => actionFn(error)};
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
if (detailsFn) {
|
|
90
|
-
detailsButtonProps = {...detailsButtonProps, onClick:
|
|
90
|
+
detailsButtonProps = {...detailsButtonProps, onClick: () => detailsFn(error)};
|
|
91
91
|
}
|
|
92
92
|
|
|
93
93
|
let buttons = [],
|
|
@@ -103,6 +103,7 @@ export const [ErrorMessage, errorMessage] = hoistCmp.withFactory<ErrorMessagePro
|
|
|
103
103
|
return frame({
|
|
104
104
|
ref,
|
|
105
105
|
className,
|
|
106
|
+
...rest,
|
|
106
107
|
item: div({
|
|
107
108
|
className: 'xh-error-message__inner',
|
|
108
109
|
items: [titleCmp({title}), messageCmp({message, error}), buttonBar]
|