@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
|
@@ -26,7 +26,7 @@ export const tab = hoistCmp.factory({
|
|
|
26
26
|
className: 'xh-tab',
|
|
27
27
|
model: uses(TabModel, {publishMode: 'limited'}),
|
|
28
28
|
|
|
29
|
-
render({model, className}) {
|
|
29
|
+
render({model, className, testId}) {
|
|
30
30
|
let {content, isActive, renderMode, refreshContextModel} = model,
|
|
31
31
|
wasActivated = useRef(false);
|
|
32
32
|
|
|
@@ -42,6 +42,7 @@ export const tab = hoistCmp.factory({
|
|
|
42
42
|
return frame({
|
|
43
43
|
display: isActive ? 'flex' : 'none',
|
|
44
44
|
className,
|
|
45
|
+
testId,
|
|
45
46
|
item: refreshContextView({
|
|
46
47
|
model: refreshContextModel,
|
|
47
48
|
item: errorBoundary(elementFromContent(content, {flex: 1}))
|
|
@@ -5,18 +5,19 @@
|
|
|
5
5
|
* Copyright © 2023 Extremely Heavy Industries Inc.
|
|
6
6
|
*/
|
|
7
7
|
import {div, hbox, placeholder, vbox} from '@xh/hoist/cmp/layout';
|
|
8
|
+
import {TabContainerModel, TabContainerProps} from '@xh/hoist/cmp/tab';
|
|
9
|
+
import {getTestId} from '@xh/hoist/utils/js';
|
|
8
10
|
import {getLayoutProps} from '@xh/hoist/utils/react';
|
|
9
11
|
import {isEmpty} from 'lodash';
|
|
10
12
|
import '../Tabs.scss';
|
|
11
13
|
import {tabSwitcher} from '../TabSwitcher';
|
|
12
14
|
import {tab} from './Tab';
|
|
13
|
-
import {TabContainerModel, TabContainerProps} from '@xh/hoist/cmp/tab';
|
|
14
15
|
|
|
15
16
|
/**
|
|
16
17
|
* Desktop implementation of TabContainer.
|
|
17
18
|
* @internal
|
|
18
19
|
*/
|
|
19
|
-
export function tabContainerImpl({model, className, ...props}: TabContainerProps) {
|
|
20
|
+
export function tabContainerImpl({model, className, testId, ...props}: TabContainerProps) {
|
|
20
21
|
const layoutProps = getLayoutProps(props),
|
|
21
22
|
vertical = ['left', 'right'].includes(model.switcher?.orientation),
|
|
22
23
|
container = vertical ? hbox : vbox;
|
|
@@ -29,24 +30,26 @@ export function tabContainerImpl({model, className, ...props}: TabContainerProps
|
|
|
29
30
|
return container({
|
|
30
31
|
...layoutProps,
|
|
31
32
|
className,
|
|
32
|
-
|
|
33
|
+
testId,
|
|
34
|
+
item: getChildren(model, testId)
|
|
33
35
|
});
|
|
34
36
|
}
|
|
35
37
|
|
|
36
|
-
function getChildren(model: TabContainerModel) {
|
|
37
|
-
const {tabs
|
|
38
|
-
switcherBefore = ['left', 'top'].includes(switcher?.orientation),
|
|
39
|
-
switcherAfter = ['right', 'bottom'].includes(switcher?.orientation);
|
|
40
|
-
|
|
38
|
+
function getChildren(model: TabContainerModel, testId: string) {
|
|
39
|
+
const {tabs} = model;
|
|
41
40
|
if (isEmpty(tabs)) {
|
|
42
|
-
return div({
|
|
43
|
-
className: 'xh-tab-wrapper',
|
|
44
|
-
item: placeholder(model.emptyText)
|
|
45
|
-
});
|
|
41
|
+
return div({className: 'xh-tab-wrapper', item: placeholder(model.emptyText)});
|
|
46
42
|
}
|
|
47
43
|
|
|
44
|
+
const {activeTabId, switcher} = model,
|
|
45
|
+
switcherBefore = ['left', 'top'].includes(switcher?.orientation),
|
|
46
|
+
switcherAfter = ['right', 'bottom'].includes(switcher?.orientation),
|
|
47
|
+
switcherCmp = switcher
|
|
48
|
+
? tabSwitcher({key: 'switcher', testId: getTestId(testId, 'switcher'), ...switcher})
|
|
49
|
+
: null;
|
|
50
|
+
|
|
48
51
|
return [
|
|
49
|
-
switcherBefore ?
|
|
52
|
+
switcherBefore ? switcherCmp : null,
|
|
50
53
|
...tabs.map(tabModel => {
|
|
51
54
|
const tabId = tabModel.id,
|
|
52
55
|
style = activeTabId !== tabId ? hideStyle : undefined;
|
|
@@ -55,10 +58,10 @@ function getChildren(model: TabContainerModel) {
|
|
|
55
58
|
className: 'xh-tab-wrapper',
|
|
56
59
|
style,
|
|
57
60
|
key: tabId,
|
|
58
|
-
item: tab({model: tabModel})
|
|
61
|
+
item: tab({model: tabModel, testId: getTestId(testId, tabId)})
|
|
59
62
|
});
|
|
60
63
|
}),
|
|
61
|
-
switcherAfter ?
|
|
64
|
+
switcherAfter ? switcherCmp : null
|
|
62
65
|
];
|
|
63
66
|
}
|
|
64
67
|
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Copyright © 2023 Extremely Heavy Industries Inc.
|
|
6
6
|
*/
|
|
7
7
|
import {filler, fragment, hbox, vbox} from '@xh/hoist/cmp/layout';
|
|
8
|
-
import {
|
|
8
|
+
import {BoxProps, hoistCmp, HoistProps} from '@xh/hoist/core';
|
|
9
9
|
import {button} from '@xh/hoist/desktop/cmp/button';
|
|
10
10
|
import '@xh/hoist/desktop/register';
|
|
11
11
|
import {Icon} from '@xh/hoist/icon';
|
|
@@ -65,6 +65,7 @@ export const [Toolbar, toolbar] = hoistCmp.withFactory<ToolbarProps>({
|
|
|
65
65
|
enableOverflowMenu = false,
|
|
66
66
|
collapseFrom = 'end',
|
|
67
67
|
minVisibleItems,
|
|
68
|
+
testId,
|
|
68
69
|
...rest
|
|
69
70
|
},
|
|
70
71
|
ref
|
|
@@ -83,6 +84,7 @@ export const [Toolbar, toolbar] = hoistCmp.withFactory<ToolbarProps>({
|
|
|
83
84
|
|
|
84
85
|
return container({
|
|
85
86
|
ref,
|
|
87
|
+
testId,
|
|
86
88
|
...rest,
|
|
87
89
|
className: classNames(
|
|
88
90
|
className,
|
|
@@ -28,12 +28,13 @@ export const [SplitTreeMap, splitTreeMap] = hoistCmp.withFactory<SplitTreeMapPro
|
|
|
28
28
|
model: uses(SplitTreeMapModel),
|
|
29
29
|
className: 'xh-split-treemap',
|
|
30
30
|
|
|
31
|
-
render({model, className, ...props}, ref) {
|
|
31
|
+
render({model, className, testId, ...props}, ref) {
|
|
32
32
|
const {primaryMapModel, secondaryMapModel, orientation} = model,
|
|
33
33
|
errors = uniq(compact([primaryMapModel.error, secondaryMapModel.error])),
|
|
34
34
|
container = orientation === 'horizontal' ? hframe : vframe;
|
|
35
35
|
|
|
36
36
|
return container({
|
|
37
|
+
testId,
|
|
37
38
|
ref,
|
|
38
39
|
className,
|
|
39
40
|
items: errors.length ? errorPanel({errors}) : childMaps(),
|
|
@@ -9,9 +9,10 @@ import {box, div, placeholder} from '@xh/hoist/cmp/layout';
|
|
|
9
9
|
import {
|
|
10
10
|
hoistCmp,
|
|
11
11
|
HoistModel,
|
|
12
|
-
LayoutProps,
|
|
13
12
|
HoistProps,
|
|
13
|
+
LayoutProps,
|
|
14
14
|
lookup,
|
|
15
|
+
TestSupportProps,
|
|
15
16
|
useLocalModel,
|
|
16
17
|
uses,
|
|
17
18
|
XH
|
|
@@ -35,7 +36,7 @@ import {assign, cloneDeep, debounce, isFunction, merge, omit} from 'lodash';
|
|
|
35
36
|
import './TreeMap.scss';
|
|
36
37
|
import {TreeMapModel} from './TreeMapModel';
|
|
37
38
|
|
|
38
|
-
export interface TreeMapProps extends HoistProps<TreeMapModel>, LayoutProps {}
|
|
39
|
+
export interface TreeMapProps extends HoistProps<TreeMapModel>, LayoutProps, TestSupportProps {}
|
|
39
40
|
|
|
40
41
|
/**
|
|
41
42
|
* Component for rendering a TreeMap.
|
|
@@ -50,7 +51,7 @@ export const [TreeMap, treeMap] = hoistCmp.withFactory<TreeMapProps>({
|
|
|
50
51
|
model: uses(TreeMapModel),
|
|
51
52
|
className: 'xh-treemap',
|
|
52
53
|
|
|
53
|
-
render({model, className, ...props}, ref) {
|
|
54
|
+
render({model, className, testId, ...props}, ref) {
|
|
54
55
|
if (!Highcharts) {
|
|
55
56
|
console.error(
|
|
56
57
|
'Highcharts has not been imported in to this application. Please import and ' +
|
|
@@ -99,6 +100,7 @@ export const [TreeMap, treeMap] = hoistCmp.withFactory<TreeMapProps>({
|
|
|
99
100
|
...layoutProps,
|
|
100
101
|
className: classNames(className, `xh-treemap--${impl.theme}`),
|
|
101
102
|
ref,
|
|
103
|
+
testId,
|
|
102
104
|
items
|
|
103
105
|
});
|
|
104
106
|
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
.xh-zone-mapper {
|
|
2
|
+
width: 350px;
|
|
3
|
+
height: 500px;
|
|
4
|
+
|
|
5
|
+
&__zone-picker {
|
|
6
|
+
padding: var(--xh-pad-px);
|
|
7
|
+
background: var(--xh-bg-alt);
|
|
8
|
+
border-bottom: var(--xh-border-solid);
|
|
9
|
+
|
|
10
|
+
&__zone-cell {
|
|
11
|
+
display: flex;
|
|
12
|
+
align-items: center;
|
|
13
|
+
overflow: hidden;
|
|
14
|
+
flex: 1;
|
|
15
|
+
min-height: 30px;
|
|
16
|
+
white-space: nowrap;
|
|
17
|
+
padding: var(--xh-pad-half-px);
|
|
18
|
+
background: var(--xh-bg);
|
|
19
|
+
border: var(--xh-border-solid);
|
|
20
|
+
cursor: pointer;
|
|
21
|
+
|
|
22
|
+
& > span:not(:last-child) {
|
|
23
|
+
margin-right: 3px;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
&--selected {
|
|
27
|
+
box-shadow: var(--xh-form-field-focused-box-shadow);
|
|
28
|
+
background: var(--xh-grid-tree-group-bg);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
&.tl {
|
|
32
|
+
font-size: var(--xh-grid-multifield-top-font-size-px);
|
|
33
|
+
border-radius: var(--xh-border-radius-px) 0 0 0;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
&.tr {
|
|
37
|
+
justify-content: flex-end;
|
|
38
|
+
margin: 0 0 0 -1px;
|
|
39
|
+
font-size: var(--xh-grid-multifield-top-font-size-px);
|
|
40
|
+
border-radius: 0 var(--xh-border-radius-px) 0 0;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
&.bl {
|
|
44
|
+
margin: -1px 0 0 0;
|
|
45
|
+
font-size: var(--xh-grid-multifield-bottom-font-size-px);
|
|
46
|
+
border-radius: 0 0 0 var(--xh-border-radius-px);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
&.br {
|
|
50
|
+
justify-content: flex-end;
|
|
51
|
+
margin: -1px 0 0 -1px;
|
|
52
|
+
font-size: var(--xh-grid-multifield-bottom-font-size-px);
|
|
53
|
+
border-radius: 0 0 var(--xh-border-radius-px);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
&__sort-picker {
|
|
59
|
+
flex: none !important;
|
|
60
|
+
border-top: var(--xh-border-solid);
|
|
61
|
+
|
|
62
|
+
.xh-panel__content .xh-hframe {
|
|
63
|
+
padding: var(--xh-pad-px);
|
|
64
|
+
align-items: center;
|
|
65
|
+
|
|
66
|
+
& > *:not(:last-child) {
|
|
67
|
+
margin-right: var(--xh-pad-px);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,232 @@
|
|
|
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, HoistModel, lookup, managed, useLocalModel, uses} from '@xh/hoist/core';
|
|
9
|
+
import {div, filler, hbox, hframe, span, vbox} from '@xh/hoist/cmp/layout';
|
|
10
|
+
import {panel} from '@xh/hoist/desktop/cmp/panel';
|
|
11
|
+
import {grid, GridModel} from '@xh/hoist/cmp/grid';
|
|
12
|
+
import {checkbox} from '@xh/hoist/desktop/cmp/input';
|
|
13
|
+
import {button} from '@xh/hoist/desktop/cmp/button';
|
|
14
|
+
import {select} from '@xh/hoist/desktop/cmp/input';
|
|
15
|
+
import {Icon} from '@xh/hoist/icon';
|
|
16
|
+
import {intersperse} from '@xh/hoist/utils/js';
|
|
17
|
+
import {isEmpty} from 'lodash';
|
|
18
|
+
import classNames from 'classnames';
|
|
19
|
+
import './ZoneMapper.scss';
|
|
20
|
+
import {ZoneMapperModel} from '@xh/hoist/cmp/zoneGrid/impl/ZoneMapperModel';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Hoist UI for user selection and discovery of available ZoneGrid columns, enabled via the
|
|
24
|
+
* `ZoneGridModel.zoneMapperModel` config option.
|
|
25
|
+
*
|
|
26
|
+
* This component displays an example of each of the four zones, with the available columns for
|
|
27
|
+
* the currently selected zone displayed in a list below. Users can toggle column visibility
|
|
28
|
+
* and labels for each zone to construct a custom layout for the grid rows.
|
|
29
|
+
*
|
|
30
|
+
* It is not necessary to manually create instances of this component within an application.
|
|
31
|
+
*
|
|
32
|
+
* @internal
|
|
33
|
+
*/
|
|
34
|
+
export const [ZoneMapper, zoneMapper] = hoistCmp.withFactory<ZoneMapperModel>({
|
|
35
|
+
displayName: 'ZoneMapper',
|
|
36
|
+
model: uses(ZoneMapperModel),
|
|
37
|
+
className: 'xh-zone-mapper',
|
|
38
|
+
render({model, className}) {
|
|
39
|
+
const {showRestoreDefaults, isDirty} = model,
|
|
40
|
+
impl = useLocalModel(ZoneMapperLocalModel);
|
|
41
|
+
|
|
42
|
+
return panel({
|
|
43
|
+
className,
|
|
44
|
+
items: [zonePicker(), grid({model: impl.gridModel}), sortPicker()],
|
|
45
|
+
bbar: [
|
|
46
|
+
button({
|
|
47
|
+
omit: !showRestoreDefaults,
|
|
48
|
+
text: 'Restore Defaults',
|
|
49
|
+
icon: Icon.undo({className: 'xh-red'}),
|
|
50
|
+
onClick: () => model.restoreDefaultsAsync()
|
|
51
|
+
}),
|
|
52
|
+
filler(),
|
|
53
|
+
button({
|
|
54
|
+
text: 'Cancel',
|
|
55
|
+
onClick: () => model.close()
|
|
56
|
+
}),
|
|
57
|
+
button({
|
|
58
|
+
text: 'Save',
|
|
59
|
+
icon: Icon.check(),
|
|
60
|
+
intent: 'success',
|
|
61
|
+
disabled: !isDirty,
|
|
62
|
+
onClick: () => {
|
|
63
|
+
model.commit();
|
|
64
|
+
model.close();
|
|
65
|
+
}
|
|
66
|
+
})
|
|
67
|
+
]
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
const zonePicker = hoistCmp.factory<ZoneMapperModel>({
|
|
73
|
+
render({model}) {
|
|
74
|
+
const {leftFlex, rightFlex} = model,
|
|
75
|
+
className = 'xh-zone-mapper__zone-picker';
|
|
76
|
+
|
|
77
|
+
return vbox({
|
|
78
|
+
className,
|
|
79
|
+
items: [
|
|
80
|
+
hbox({
|
|
81
|
+
className: `${className}__top`,
|
|
82
|
+
items: [
|
|
83
|
+
zoneCell({zone: 'tl', flex: leftFlex}),
|
|
84
|
+
zoneCell({zone: 'tr', flex: rightFlex})
|
|
85
|
+
]
|
|
86
|
+
}),
|
|
87
|
+
hbox({
|
|
88
|
+
className: `${className}__bottom`,
|
|
89
|
+
items: [
|
|
90
|
+
zoneCell({zone: 'bl', flex: leftFlex}),
|
|
91
|
+
zoneCell({zone: 'br', flex: rightFlex})
|
|
92
|
+
]
|
|
93
|
+
})
|
|
94
|
+
]
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
const zoneCell = hoistCmp.factory<ZoneMapperModel>({
|
|
100
|
+
render({model, zone, flex}) {
|
|
101
|
+
const {selectedZone, delimiter} = model,
|
|
102
|
+
className = 'xh-zone-mapper__zone-picker__zone-cell',
|
|
103
|
+
samples = model.getSamplesForZone(zone);
|
|
104
|
+
|
|
105
|
+
return div({
|
|
106
|
+
className: classNames(
|
|
107
|
+
className,
|
|
108
|
+
zone,
|
|
109
|
+
selectedZone === zone ? `${className}--selected` : null
|
|
110
|
+
),
|
|
111
|
+
style: {flex},
|
|
112
|
+
onClick: () => (model.selectedZone = zone),
|
|
113
|
+
items: intersperse(samples, span(delimiter))
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
const sortPicker = hoistCmp.factory<ZoneMapperModel>({
|
|
119
|
+
render({model}) {
|
|
120
|
+
return panel({
|
|
121
|
+
className: 'xh-zone-mapper__sort-picker',
|
|
122
|
+
title: 'Sorting',
|
|
123
|
+
icon: Icon.list(),
|
|
124
|
+
compactHeader: true,
|
|
125
|
+
items: hframe(
|
|
126
|
+
select({
|
|
127
|
+
bind: 'sortByColId',
|
|
128
|
+
enableFilter: true,
|
|
129
|
+
flex: 1,
|
|
130
|
+
options: model.sortByOptions
|
|
131
|
+
}),
|
|
132
|
+
button({
|
|
133
|
+
text: model.getSortLabel(),
|
|
134
|
+
icon: model.getSortIcon(),
|
|
135
|
+
width: 80,
|
|
136
|
+
minimal: false,
|
|
137
|
+
onClick: () => model.setNextSortBy()
|
|
138
|
+
})
|
|
139
|
+
)
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
class ZoneMapperLocalModel extends HoistModel {
|
|
145
|
+
override xhImpl = true;
|
|
146
|
+
@lookup(ZoneMapperModel) model: ZoneMapperModel;
|
|
147
|
+
|
|
148
|
+
@managed
|
|
149
|
+
gridModel: GridModel;
|
|
150
|
+
|
|
151
|
+
override onLinked() {
|
|
152
|
+
super.onLinked();
|
|
153
|
+
|
|
154
|
+
this.gridModel = this.createGridModel();
|
|
155
|
+
|
|
156
|
+
this.addReaction({
|
|
157
|
+
track: () => [
|
|
158
|
+
this.model.isOpen,
|
|
159
|
+
this.model.isPopoverOpen,
|
|
160
|
+
this.model.mappings,
|
|
161
|
+
this.model.selectedZone
|
|
162
|
+
],
|
|
163
|
+
run: () => this.syncGrid(),
|
|
164
|
+
fireImmediately: true
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
private createGridModel(): GridModel {
|
|
169
|
+
const {model} = this,
|
|
170
|
+
{groupColumns, fields} = model,
|
|
171
|
+
hasGrouping = groupColumns && fields.some(it => it.chooserGroup);
|
|
172
|
+
|
|
173
|
+
return new GridModel({
|
|
174
|
+
store: {idSpec: 'field'},
|
|
175
|
+
groupBy: hasGrouping ? 'chooserGroup' : null,
|
|
176
|
+
colDefaults: {movable: false, resizable: false, sortable: false},
|
|
177
|
+
selModel: 'disabled',
|
|
178
|
+
columns: [
|
|
179
|
+
{
|
|
180
|
+
field: 'displayName',
|
|
181
|
+
headerName: 'Field',
|
|
182
|
+
flex: 1
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
field: 'show',
|
|
186
|
+
align: 'center',
|
|
187
|
+
renderer: (value, {record}) => {
|
|
188
|
+
const {field} = record.data;
|
|
189
|
+
return checkbox({value, onChange: () => model.toggleShown(field)});
|
|
190
|
+
}
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
field: 'showLabel',
|
|
194
|
+
headerName: 'Label',
|
|
195
|
+
align: 'center',
|
|
196
|
+
renderer: (value, {record}) => {
|
|
197
|
+
const {label, field} = record.data;
|
|
198
|
+
if (!label) return null;
|
|
199
|
+
return checkbox({value, onChange: () => model.toggleShowLabel(field)});
|
|
200
|
+
}
|
|
201
|
+
},
|
|
202
|
+
// Hidden
|
|
203
|
+
{field: 'field', hidden: true},
|
|
204
|
+
{field: 'label', hidden: true},
|
|
205
|
+
{field: 'chooserGroup', hidden: true}
|
|
206
|
+
]
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
private syncGrid() {
|
|
211
|
+
const {fields, mappings, limits, selectedZone} = this.model,
|
|
212
|
+
mapping = mappings[selectedZone],
|
|
213
|
+
limit = limits?.[selectedZone],
|
|
214
|
+
data = [];
|
|
215
|
+
|
|
216
|
+
// 1) Determine which fields are shown and labeled for the zone
|
|
217
|
+
const allowedFields = !isEmpty(limit?.only)
|
|
218
|
+
? fields.filter(it => limit.only.includes(it.field))
|
|
219
|
+
: fields;
|
|
220
|
+
|
|
221
|
+
allowedFields.forEach(f => {
|
|
222
|
+
const fieldMapping = mapping.find(it => f.field === it.field),
|
|
223
|
+
show = !!fieldMapping,
|
|
224
|
+
showLabel = fieldMapping?.showLabel ?? false;
|
|
225
|
+
|
|
226
|
+
data.push({...f, show, showLabel});
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
// 2) Load into display grid
|
|
230
|
+
this.gridModel.loadData(data);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
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 {hoistCmp, uses} from '@xh/hoist/core';
|
|
8
|
+
import {Icon} from '@xh/hoist/icon';
|
|
9
|
+
import {dialog} from '@xh/hoist/kit/blueprint';
|
|
10
|
+
import {ZoneMapperModel} from '@xh/hoist/cmp/zoneGrid/impl/ZoneMapperModel';
|
|
11
|
+
import {zoneMapper} from './ZoneMapper';
|
|
12
|
+
|
|
13
|
+
export const zoneMapperDialog = hoistCmp.factory({
|
|
14
|
+
model: uses(ZoneMapperModel),
|
|
15
|
+
className: 'xh-zone-mapper-dialog',
|
|
16
|
+
|
|
17
|
+
render({model, className}) {
|
|
18
|
+
const {isOpen} = model;
|
|
19
|
+
if (!isOpen) return null;
|
|
20
|
+
|
|
21
|
+
return dialog({
|
|
22
|
+
className,
|
|
23
|
+
icon: Icon.gridLarge(),
|
|
24
|
+
title: 'Customize Fields',
|
|
25
|
+
isOpen: true,
|
|
26
|
+
onClose: () => model.close(),
|
|
27
|
+
item: zoneMapper({model}),
|
|
28
|
+
// Size determined by inner component
|
|
29
|
+
style: {
|
|
30
|
+
width: 'unset',
|
|
31
|
+
height: 'unset'
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
});
|
package/dynamics/desktop.ts
CHANGED
|
@@ -19,6 +19,7 @@ export let ColChooserModel = null;
|
|
|
19
19
|
export let ColumnHeaderFilterModel = null;
|
|
20
20
|
export let ModalSupportModel = null;
|
|
21
21
|
export let colChooser = null;
|
|
22
|
+
export let zoneMapper = null;
|
|
22
23
|
export let columnHeaderFilter = null;
|
|
23
24
|
export let dockContainerImpl = null;
|
|
24
25
|
export let errorMessage = null;
|
|
@@ -38,6 +39,7 @@ export function installDesktopImpls(impls) {
|
|
|
38
39
|
ColumnHeaderFilterModel = impls.ColumnHeaderFilterModel;
|
|
39
40
|
ModalSupportModel = impls.ModalSupportModel;
|
|
40
41
|
colChooser = impls.colChooser;
|
|
42
|
+
zoneMapper = impls.zoneMapper;
|
|
41
43
|
columnHeaderFilter = impls.columnHeaderFilter;
|
|
42
44
|
dockContainerImpl = impls.dockContainerImpl;
|
|
43
45
|
errorMessage = impls.errorMessage;
|
package/dynamics/mobile.ts
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
*/
|
|
18
18
|
export let ColChooserModel = null;
|
|
19
19
|
export let colChooser = null;
|
|
20
|
+
export let zoneMapper = null;
|
|
20
21
|
export let errorMessage = null;
|
|
21
22
|
export let pinPadImpl = null;
|
|
22
23
|
export let storeFilterFieldImpl = null;
|
|
@@ -30,6 +31,7 @@ export let tabContainerImpl = null;
|
|
|
30
31
|
export function installMobileImpls(impls) {
|
|
31
32
|
ColChooserModel = impls.ColChooserModel;
|
|
32
33
|
colChooser = impls.colChooser;
|
|
34
|
+
zoneMapper = impls.zoneMapper;
|
|
33
35
|
errorMessage = impls.errorMessage;
|
|
34
36
|
pinPadImpl = impls.pinPadImpl;
|
|
35
37
|
storeFilterFieldImpl = impls.storeFilterFieldImpl;
|
|
@@ -32,7 +32,7 @@ export class InstancesModel extends HoistModel {
|
|
|
32
32
|
instancesPanelModel: PanelModel;
|
|
33
33
|
|
|
34
34
|
get statsModel(): StatsModel {
|
|
35
|
-
return XH.
|
|
35
|
+
return XH.getModels(StatsModel)[0] as StatsModel;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
get selectedSyncRun() {
|
|
@@ -170,7 +170,7 @@ export class InstancesModel extends HoistModel {
|
|
|
170
170
|
getInstance(xhId: string): HoistBase {
|
|
171
171
|
if (!xhId) return null;
|
|
172
172
|
return (
|
|
173
|
-
head(XH.
|
|
173
|
+
head(XH.getModels(it => it.xhId === xhId)) ??
|
|
174
174
|
XH.getServices().find(it => it.xhId === xhId) ??
|
|
175
175
|
XH.getStores().find(it => it.xhId === xhId)
|
|
176
176
|
);
|
|
@@ -11,6 +11,7 @@ import {createElement, hoistCmp, refreshContextView, uses, XH} from '@xh/hoist/c
|
|
|
11
11
|
import {installMobileImpls} from '@xh/hoist/dynamics/mobile';
|
|
12
12
|
import {colChooser} from '@xh/hoist/mobile/cmp/grid/impl/ColChooser';
|
|
13
13
|
import {ColChooserModel} from '@xh/hoist/mobile/cmp/grid/impl/ColChooserModel';
|
|
14
|
+
import {zoneMapper} from '@xh/hoist/mobile/cmp/zoneGrid/impl/ZoneMapper';
|
|
14
15
|
import {mask} from '@xh/hoist/mobile/cmp/mask';
|
|
15
16
|
import {storeFilterFieldImpl} from '@xh/hoist/mobile/cmp/store/impl/StoreFilterField';
|
|
16
17
|
import {tabContainerImpl} from '@xh/hoist/mobile/cmp/tab/impl/TabContainer';
|
|
@@ -38,6 +39,7 @@ installMobileImpls({
|
|
|
38
39
|
pinPadImpl,
|
|
39
40
|
colChooser,
|
|
40
41
|
ColChooserModel,
|
|
42
|
+
zoneMapper,
|
|
41
43
|
errorMessage
|
|
42
44
|
});
|
|
43
45
|
|
|
@@ -0,0 +1,41 @@
|
|
|
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 {hoistCmp, useContextModel} from '@xh/hoist/core';
|
|
8
|
+
import {ZoneGridModel} from '../../../cmp/zoneGrid';
|
|
9
|
+
import {button, ButtonProps} from '@xh/hoist/mobile/cmp/button';
|
|
10
|
+
import {Icon} from '@xh/hoist/icon';
|
|
11
|
+
import {withDefault} from '@xh/hoist/utils/js';
|
|
12
|
+
import '@xh/hoist/mobile/register';
|
|
13
|
+
|
|
14
|
+
export interface ZoneMapperButtonProps extends ButtonProps {
|
|
15
|
+
/** ZoneGridModel of the grid for which this button should show a chooser. */
|
|
16
|
+
zoneGridModel?: ZoneGridModel;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* A convenience button to trigger the display of a ZoneMapper UI for ZoneGrid configuration.
|
|
21
|
+
*
|
|
22
|
+
* Requires a `ZoneGridModel.zoneMapperModel` config option, set to true for default implementation.
|
|
23
|
+
*/
|
|
24
|
+
export const [ZoneMapperButton, zoneMapperButton] = hoistCmp.withFactory<ZoneMapperButtonProps>({
|
|
25
|
+
displayName: 'ZoneMapperButton',
|
|
26
|
+
model: false,
|
|
27
|
+
render({zoneGridModel, icon = Icon.gridLarge(), onClick, ...props}) {
|
|
28
|
+
zoneGridModel = withDefault(zoneGridModel, useContextModel(ZoneGridModel));
|
|
29
|
+
|
|
30
|
+
if (!zoneGridModel) {
|
|
31
|
+
console.error(
|
|
32
|
+
"No ZoneGridModel available to ZoneMapperButton. Provide via a 'zoneGridModel' prop, or context."
|
|
33
|
+
);
|
|
34
|
+
return button({icon, disabled: true, ...props});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
onClick = onClick ?? (() => zoneGridModel.showMapper());
|
|
38
|
+
|
|
39
|
+
return button({icon, onClick, ...props});
|
|
40
|
+
}
|
|
41
|
+
});
|
|
@@ -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 = [],
|
|
@@ -57,6 +57,12 @@ export interface SelectProps extends HoistProps, HoistInputProps, LayoutProps {
|
|
|
57
57
|
*/
|
|
58
58
|
enableFullscreen?: boolean;
|
|
59
59
|
|
|
60
|
+
/**
|
|
61
|
+
* Optional override for fullscreen z-index. Useful for enabling fullscreen from
|
|
62
|
+
* within components that have a higher z-index.
|
|
63
|
+
*/
|
|
64
|
+
fullScreenZIndex?: number;
|
|
65
|
+
|
|
60
66
|
/**
|
|
61
67
|
* Function called to filter available options for a given query string input.
|
|
62
68
|
* Used for filtering of options provided by `options` prop when `enableFilter` is true.
|
|
@@ -531,6 +537,7 @@ class SelectInputModel extends HoistInputModel {
|
|
|
531
537
|
portal.id = FULLSCREEN_PORTAL_ID;
|
|
532
538
|
document.body.appendChild(portal);
|
|
533
539
|
}
|
|
540
|
+
portal.style.zIndex = withDefault(this.componentProps.fullScreenZIndex, null);
|
|
534
541
|
return portal;
|
|
535
542
|
}
|
|
536
543
|
|