@sap-ux/control-property-editor 0.2.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/.eslintignore +1 -0
- package/.eslintrc.js +16 -0
- package/CHANGELOG.md +7 -0
- package/LICENSE +201 -0
- package/README.md +16 -0
- package/dist/app.css +2 -0
- package/dist/app.css.map +7 -0
- package/dist/app.js +347 -0
- package/dist/app.js.map +7 -0
- package/esbuild.js +25 -0
- package/jest.config.js +20 -0
- package/package.json +68 -0
- package/src/App.scss +57 -0
- package/src/App.tsx +136 -0
- package/src/Workarounds.scss +79 -0
- package/src/actions.ts +3 -0
- package/src/components/AppLogo.module.scss +8 -0
- package/src/components/AppLogo.tsx +75 -0
- package/src/components/ChangeIndicator.tsx +80 -0
- package/src/components/Separator.tsx +32 -0
- package/src/components/ThemeSelectorCallout.scss +48 -0
- package/src/components/ThemeSelectorCallout.tsx +125 -0
- package/src/components/ToolBar.scss +39 -0
- package/src/components/ToolBar.tsx +26 -0
- package/src/components/index.ts +4 -0
- package/src/devices.ts +18 -0
- package/src/global.d.ts +4 -0
- package/src/i18n/i18n.json +68 -0
- package/src/i18n.ts +25 -0
- package/src/icons.tsx +198 -0
- package/src/index.css +1288 -0
- package/src/index.tsx +47 -0
- package/src/middleware.ts +54 -0
- package/src/panels/LeftPanel.scss +17 -0
- package/src/panels/LeftPanel.tsx +48 -0
- package/src/panels/changes/ChangeStack.module.scss +3 -0
- package/src/panels/changes/ChangeStack.tsx +219 -0
- package/src/panels/changes/ChangeStackHeader.tsx +43 -0
- package/src/panels/changes/ChangesPanel.module.scss +18 -0
- package/src/panels/changes/ChangesPanel.tsx +90 -0
- package/src/panels/changes/ControlGroup.module.scss +17 -0
- package/src/panels/changes/ControlGroup.tsx +61 -0
- package/src/panels/changes/PropertyChange.module.scss +24 -0
- package/src/panels/changes/PropertyChange.tsx +159 -0
- package/src/panels/changes/UnknownChange.module.scss +46 -0
- package/src/panels/changes/UnknownChange.tsx +96 -0
- package/src/panels/changes/index.tsx +3 -0
- package/src/panels/changes/utils.ts +36 -0
- package/src/panels/index.ts +2 -0
- package/src/panels/outline/Funnel.tsx +64 -0
- package/src/panels/outline/NoControlFound.tsx +45 -0
- package/src/panels/outline/OutlinePanel.scss +98 -0
- package/src/panels/outline/OutlinePanel.tsx +38 -0
- package/src/panels/outline/Tree.tsx +393 -0
- package/src/panels/outline/index.ts +1 -0
- package/src/panels/outline/utils.ts +154 -0
- package/src/panels/properties/Clipboard.tsx +44 -0
- package/src/panels/properties/DeviceSelector.tsx +40 -0
- package/src/panels/properties/DeviceToggle.tsx +39 -0
- package/src/panels/properties/DropdownEditor.tsx +80 -0
- package/src/panels/properties/Funnel.tsx +64 -0
- package/src/panels/properties/HeaderField.tsx +150 -0
- package/src/panels/properties/IconValueHelp.tsx +203 -0
- package/src/panels/properties/InputTypeSelector.tsx +20 -0
- package/src/panels/properties/InputTypeToggle.module.scss +4 -0
- package/src/panels/properties/InputTypeToggle.tsx +79 -0
- package/src/panels/properties/InputTypeWrapper.tsx +259 -0
- package/src/panels/properties/NoControlSelected.tsx +38 -0
- package/src/panels/properties/Properties.scss +102 -0
- package/src/panels/properties/PropertiesList.tsx +162 -0
- package/src/panels/properties/PropertiesPanel.tsx +30 -0
- package/src/panels/properties/PropertyDocumentation.module.scss +81 -0
- package/src/panels/properties/PropertyDocumentation.tsx +174 -0
- package/src/panels/properties/SapUiIcon.scss +109 -0
- package/src/panels/properties/StringEditor.tsx +122 -0
- package/src/panels/properties/ViewChanger.module.scss +5 -0
- package/src/panels/properties/ViewChanger.tsx +143 -0
- package/src/panels/properties/constants.ts +2 -0
- package/src/panels/properties/index.tsx +1 -0
- package/src/panels/properties/propertyValuesCache.ts +39 -0
- package/src/panels/properties/types.ts +49 -0
- package/src/slice.ts +216 -0
- package/src/store.ts +19 -0
- package/src/use-local-storage.ts +40 -0
- package/src/use-window-size.ts +39 -0
- package/src/variables.scss +2 -0
- package/test/unit/App.test.tsx +207 -0
- package/test/unit/appIndex.test.ts +23 -0
- package/test/unit/components/ChangeIndicator.test.tsx +120 -0
- package/test/unit/components/ThemeSelector.test.tsx +41 -0
- package/test/unit/middleware.test.ts +116 -0
- package/test/unit/panels/changes/ChangesPanel.test.tsx +261 -0
- package/test/unit/panels/changes/utils.test.ts +40 -0
- package/test/unit/panels/outline/OutlinePanel.test.tsx +353 -0
- package/test/unit/panels/outline/__snapshots__/utils.test.ts.snap +36 -0
- package/test/unit/panels/outline/utils.test.ts +83 -0
- package/test/unit/panels/properties/Clipboard.test.tsx +18 -0
- package/test/unit/panels/properties/DropdownEditor.test.tsx +62 -0
- package/test/unit/panels/properties/Funnel.test.tsx +34 -0
- package/test/unit/panels/properties/HeaderField.test.tsx +36 -0
- package/test/unit/panels/properties/IconValueHelp.test.tsx +60 -0
- package/test/unit/panels/properties/InputTypeToggle.test.tsx +126 -0
- package/test/unit/panels/properties/InputTypeWrapper.test.tsx +430 -0
- package/test/unit/panels/properties/PropertyDocumentation.test.tsx +131 -0
- package/test/unit/panels/properties/StringEditor.test.tsx +107 -0
- package/test/unit/panels/properties/ViewChanger.test.tsx +190 -0
- package/test/unit/panels/properties/propertyValuesCache.test.ts +23 -0
- package/test/unit/slice.test.ts +268 -0
- package/test/unit/utils.tsx +67 -0
- package/test/utils/utils.tsx +25 -0
- package/tsconfig.eslint.json +4 -0
- package/tsconfig.json +39 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type { ReactElement } from 'react';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { useDispatch } from 'react-redux';
|
|
4
|
+
|
|
5
|
+
import type { UIComboBoxOption, UIComboBoxRef } from '@sap-ux/ui-components';
|
|
6
|
+
import { UIComboBox } from '@sap-ux/ui-components';
|
|
7
|
+
|
|
8
|
+
import type { PropertyChange, StringControlPropertyWithOptions } from '@sap-ux-private/control-property-editor-common';
|
|
9
|
+
import { changeProperty } from '../../slice';
|
|
10
|
+
|
|
11
|
+
import { setCachedValue } from './propertyValuesCache';
|
|
12
|
+
import type { PropertyInputProps } from './types';
|
|
13
|
+
import { InputType } from './types';
|
|
14
|
+
|
|
15
|
+
import './Properties.scss';
|
|
16
|
+
import { debounce, reportTelemetry } from '@sap-ux-private/control-property-editor-common';
|
|
17
|
+
|
|
18
|
+
// exported to make it testable without events
|
|
19
|
+
export const valueChanged = (
|
|
20
|
+
controlId: string,
|
|
21
|
+
name: string,
|
|
22
|
+
newValue: string | number,
|
|
23
|
+
controlName: string
|
|
24
|
+
): { payload: PropertyChange<string | number | boolean>; type: string } => {
|
|
25
|
+
setCachedValue(controlId, name, InputType.enumMember, newValue);
|
|
26
|
+
return changeProperty({ controlId, propertyName: name, value: newValue, controlName });
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* React element for dropdown editor.
|
|
31
|
+
*
|
|
32
|
+
* @param propertyInputProps PropertyInputProps<StringControlPropertyWithOptions>
|
|
33
|
+
* @returns ReactElement
|
|
34
|
+
*/
|
|
35
|
+
export function DropdownEditor(propertyInputProps: PropertyInputProps<StringControlPropertyWithOptions>): ReactElement {
|
|
36
|
+
const {
|
|
37
|
+
property: { name, value, options, isEnabled, errorMessage },
|
|
38
|
+
controlId,
|
|
39
|
+
controlName
|
|
40
|
+
} = propertyInputProps;
|
|
41
|
+
const dispatch = useDispatch();
|
|
42
|
+
const selectedOption = options.find((enumValue) => enumValue.key === value);
|
|
43
|
+
const text = !selectedOption && value ? value : undefined;
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<UIComboBox
|
|
47
|
+
className="dropdownEditor"
|
|
48
|
+
key={name}
|
|
49
|
+
data-testid={`${name}--DropdownEditor`}
|
|
50
|
+
selectedKey={value}
|
|
51
|
+
disabled={!isEnabled}
|
|
52
|
+
autoComplete="on"
|
|
53
|
+
allowFreeform={true}
|
|
54
|
+
text={text}
|
|
55
|
+
errorMessage={errorMessage}
|
|
56
|
+
options={options}
|
|
57
|
+
useComboBoxAsMenuWidth={true}
|
|
58
|
+
onChange={(
|
|
59
|
+
event: React.FormEvent<UIComboBoxRef>,
|
|
60
|
+
option?: UIComboBoxOption,
|
|
61
|
+
index?: number,
|
|
62
|
+
value?: string
|
|
63
|
+
): void => {
|
|
64
|
+
const newValue = option?.key ?? value ?? '';
|
|
65
|
+
reportTelemetry({ category: 'Property Change', propertyName: name }).catch((error) => {
|
|
66
|
+
console.error(`Error in reporting telemetry`, error);
|
|
67
|
+
});
|
|
68
|
+
dispatch(valueChanged(controlId, name, newValue, controlName));
|
|
69
|
+
}}
|
|
70
|
+
onPendingValueChanged={debounce((option?: UIComboBoxOption, index?: number, value?: string): void => {
|
|
71
|
+
if (value) {
|
|
72
|
+
reportTelemetry({ category: 'Property Change', propertyName: name }).catch((error) => {
|
|
73
|
+
console.error(`Error in reporting telemetry`, error);
|
|
74
|
+
});
|
|
75
|
+
dispatch(valueChanged(controlId, name, value, controlName));
|
|
76
|
+
}
|
|
77
|
+
}, 500)}
|
|
78
|
+
/>
|
|
79
|
+
);
|
|
80
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { ReactElement } from 'react';
|
|
2
|
+
import React, { useState } from 'react';
|
|
3
|
+
import { useTranslation } from 'react-i18next';
|
|
4
|
+
import { UICheckbox, UIIconButton, UICallout, UICalloutContentPadding } from '@sap-ux/ui-components';
|
|
5
|
+
import { useDispatch, useSelector } from 'react-redux';
|
|
6
|
+
import { IconName } from '../../icons';
|
|
7
|
+
import type { FilterOptions } from '../../slice';
|
|
8
|
+
import { FilterName, filterNodes } from '../../slice';
|
|
9
|
+
import type { RootState } from '../../store';
|
|
10
|
+
import './Properties.scss';
|
|
11
|
+
|
|
12
|
+
const TARGET = 'control-property-editor-property-search-funnel-callout-target-id';
|
|
13
|
+
|
|
14
|
+
const Funnel = (): ReactElement => {
|
|
15
|
+
const { t } = useTranslation();
|
|
16
|
+
const filterQuery = useSelector<RootState, FilterOptions[]>((state) => state.filterQuery);
|
|
17
|
+
const dispatch = useDispatch();
|
|
18
|
+
const [isVisible, setIsVisible] = useState(false);
|
|
19
|
+
const showCallout = () => {
|
|
20
|
+
setIsVisible(true);
|
|
21
|
+
};
|
|
22
|
+
const onChange = (name: FilterName, isChecked = false) => {
|
|
23
|
+
const action = filterNodes([{ name, value: isChecked }]);
|
|
24
|
+
dispatch(action);
|
|
25
|
+
};
|
|
26
|
+
const showEditablePropertiesChecked = filterQuery.filter(
|
|
27
|
+
(item) => item.name === FilterName.showEditableProperties
|
|
28
|
+
)[0].value as boolean;
|
|
29
|
+
const checked = showEditablePropertiesChecked;
|
|
30
|
+
return (
|
|
31
|
+
<>
|
|
32
|
+
<UIIconButton
|
|
33
|
+
id={TARGET}
|
|
34
|
+
className={`funnel-properties-icon`}
|
|
35
|
+
iconProps={{ iconName: IconName.funnel }}
|
|
36
|
+
checked={checked}
|
|
37
|
+
onClick={showCallout}></UIIconButton>
|
|
38
|
+
{isVisible && (
|
|
39
|
+
<UICallout
|
|
40
|
+
styles={{ calloutMain: { minWidth: 196, minHeight: 35 } }}
|
|
41
|
+
target={`#${TARGET}`}
|
|
42
|
+
isBeakVisible={true}
|
|
43
|
+
gapSpace={5}
|
|
44
|
+
beakWidth={5}
|
|
45
|
+
directionalHint={4}
|
|
46
|
+
onDismiss={() => setIsVisible(false)}
|
|
47
|
+
contentPadding={UICalloutContentPadding.Standard}>
|
|
48
|
+
<UICheckbox
|
|
49
|
+
id={'editable-properties-checkbox'}
|
|
50
|
+
styles={{
|
|
51
|
+
checkbox: { width: 20, height: 20 }
|
|
52
|
+
}}
|
|
53
|
+
label={t('SHOW_EDITABLE_PROPERTIES')}
|
|
54
|
+
checked={showEditablePropertiesChecked}
|
|
55
|
+
onChange={(ev?: React.FormEvent<HTMLElement | HTMLInputElement>, isChecked?: boolean) => {
|
|
56
|
+
onChange(FilterName.showEditableProperties, isChecked);
|
|
57
|
+
}}></UICheckbox>
|
|
58
|
+
</UICallout>
|
|
59
|
+
)}
|
|
60
|
+
</>
|
|
61
|
+
);
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export { Funnel };
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import type { ReactElement } from 'react';
|
|
2
|
+
import React, { useCallback, useState } from 'react';
|
|
3
|
+
|
|
4
|
+
import { TextField, Label, Stack } from '@fluentui/react';
|
|
5
|
+
import { UIIconButton, UiIcons, UITooltip, UITooltipUtils, UIDirectionalHint } from '@sap-ux/ui-components';
|
|
6
|
+
|
|
7
|
+
import './Properties.scss';
|
|
8
|
+
import { defaultFontSize, sectionHeaderFontSize } from './constants';
|
|
9
|
+
import { PropertyDocumentation } from './PropertyDocumentation';
|
|
10
|
+
import type { PropertiesInfo } from '@sap-ux-private/control-property-editor-common';
|
|
11
|
+
import { Clipboard } from './Clipboard';
|
|
12
|
+
|
|
13
|
+
export interface HeaderFieldProps {
|
|
14
|
+
label: string;
|
|
15
|
+
/**
|
|
16
|
+
* There will be an icon on the screen that allows to copy this value to clipboard
|
|
17
|
+
*/
|
|
18
|
+
value: string;
|
|
19
|
+
|
|
20
|
+
documentation?: PropertiesInfo;
|
|
21
|
+
|
|
22
|
+
hidden?: boolean;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* React element for HeaderField.
|
|
27
|
+
*
|
|
28
|
+
* @param headerFieldProps HeaderFieldProps
|
|
29
|
+
* @returns ReactElement
|
|
30
|
+
*/
|
|
31
|
+
export function HeaderField(headerFieldProps: HeaderFieldProps): ReactElement {
|
|
32
|
+
const { label, value, documentation, hidden = true } = headerFieldProps;
|
|
33
|
+
const [isCopyMessageBoxVisible, setMessageBoxVisibility] = useState(false);
|
|
34
|
+
const documentationContent = documentation && (
|
|
35
|
+
<PropertyDocumentation
|
|
36
|
+
defaultValue={documentation.defaultValue}
|
|
37
|
+
description={documentation.description}
|
|
38
|
+
propertyName={documentation.propertyName}
|
|
39
|
+
propertyType={documentation.propertyType}
|
|
40
|
+
/>
|
|
41
|
+
);
|
|
42
|
+
const onCopy = useCallback(
|
|
43
|
+
() => (
|
|
44
|
+
<CopyButton
|
|
45
|
+
label={label}
|
|
46
|
+
onClick={(): void => {
|
|
47
|
+
copyToClipboard(value).catch((reason) => console.error(reason));
|
|
48
|
+
setMessageBoxVisibility(!isCopyMessageBoxVisible);
|
|
49
|
+
setTimeout(() => setMessageBoxVisibility(false), 3000);
|
|
50
|
+
}}
|
|
51
|
+
/>
|
|
52
|
+
),
|
|
53
|
+
[]
|
|
54
|
+
);
|
|
55
|
+
return (
|
|
56
|
+
<>
|
|
57
|
+
<Stack horizontal={false} verticalAlign={'space-between'} style={{ marginBottom: 4 }}>
|
|
58
|
+
<UITooltip
|
|
59
|
+
hidden={hidden}
|
|
60
|
+
calloutProps={{ gapSpace: 5 }}
|
|
61
|
+
delay={2}
|
|
62
|
+
directionalHint={UIDirectionalHint.leftCenter}
|
|
63
|
+
tooltipProps={UITooltipUtils.renderContent(documentationContent ?? '')}>
|
|
64
|
+
<Label
|
|
65
|
+
htmlFor={label}
|
|
66
|
+
data-aria-label={label}
|
|
67
|
+
data-testid={`${label}--Label`}
|
|
68
|
+
style={{
|
|
69
|
+
color: 'var(--vscode-foreground)',
|
|
70
|
+
fontSize: sectionHeaderFontSize,
|
|
71
|
+
fontWeight: 'bold',
|
|
72
|
+
padding: 0,
|
|
73
|
+
width: '190px',
|
|
74
|
+
textOverflow: 'ellipsis',
|
|
75
|
+
whiteSpace: 'nowrap',
|
|
76
|
+
overflowX: 'hidden',
|
|
77
|
+
marginTop: 2,
|
|
78
|
+
marginBottom: 4
|
|
79
|
+
}}>
|
|
80
|
+
{label}
|
|
81
|
+
</Label>
|
|
82
|
+
</UITooltip>
|
|
83
|
+
<TextField
|
|
84
|
+
id={label}
|
|
85
|
+
value={value}
|
|
86
|
+
readOnly={true}
|
|
87
|
+
borderless
|
|
88
|
+
styles={{
|
|
89
|
+
field: {
|
|
90
|
+
color: 'var(--vscode-input-foreground)',
|
|
91
|
+
fontSize: defaultFontSize,
|
|
92
|
+
backgroundColor: 'var(--vscode-sideBar-background)'
|
|
93
|
+
},
|
|
94
|
+
fieldGroup: {
|
|
95
|
+
color: 'var(--vscode-input-foreground)',
|
|
96
|
+
backgroundColor: 'var(--vscode-sideBar-background)',
|
|
97
|
+
alignItems: 'center'
|
|
98
|
+
},
|
|
99
|
+
suffix: {
|
|
100
|
+
color: 'var(--vscode-input-foreground)',
|
|
101
|
+
backgroundColor: 'var(--vscode-sideBar-background)'
|
|
102
|
+
},
|
|
103
|
+
subComponentStyles: {
|
|
104
|
+
label: {
|
|
105
|
+
root: {
|
|
106
|
+
fontSize: sectionHeaderFontSize,
|
|
107
|
+
fontWeight: 'bold !important',
|
|
108
|
+
color: 'var(--vscode-foreground)'
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}}
|
|
113
|
+
onRenderSuffix={onCopy}
|
|
114
|
+
/>
|
|
115
|
+
{isCopyMessageBoxVisible && <Clipboard label={label} />}
|
|
116
|
+
</Stack>
|
|
117
|
+
</>
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Copy given string to clipboard, show toast success message.
|
|
123
|
+
*
|
|
124
|
+
* @param text {string to copy to clipboard}
|
|
125
|
+
*/
|
|
126
|
+
async function copyToClipboard(text: string): Promise<void> {
|
|
127
|
+
await navigator.clipboard.writeText(text);
|
|
128
|
+
}
|
|
129
|
+
interface CopyButtonProps {
|
|
130
|
+
label: string;
|
|
131
|
+
onClick?: React.MouseEventHandler<HTMLAnchorElement | HTMLButtonElement | HTMLDivElement | HTMLSpanElement>;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Copy button component.
|
|
136
|
+
*
|
|
137
|
+
* @param props {CopyButtonProps}
|
|
138
|
+
* @returns ReactElement
|
|
139
|
+
*/
|
|
140
|
+
function CopyButton(props: CopyButtonProps): ReactElement {
|
|
141
|
+
const { label, onClick } = props;
|
|
142
|
+
|
|
143
|
+
return (
|
|
144
|
+
<UIIconButton
|
|
145
|
+
id={`${label.replace(/\s/g, '')}--copy`}
|
|
146
|
+
iconProps={{ iconName: UiIcons.Copy }}
|
|
147
|
+
onClick={onClick}
|
|
148
|
+
/>
|
|
149
|
+
);
|
|
150
|
+
}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import type { UIColumn } from '@sap-ux/ui-components';
|
|
2
|
+
import { UIDialog, UIIconButton, UiIcons, UISearchBox, UITable, SelectionMode } from '@sap-ux/ui-components';
|
|
3
|
+
import type { CSSProperties, ReactElement } from 'react';
|
|
4
|
+
import React, { useState } from 'react';
|
|
5
|
+
import { useTranslation } from 'react-i18next';
|
|
6
|
+
|
|
7
|
+
import './SapUiIcon.scss';
|
|
8
|
+
import { changeProperty } from '../../slice';
|
|
9
|
+
import { setCachedValue } from './propertyValuesCache';
|
|
10
|
+
import { InputType } from './types';
|
|
11
|
+
import { useDispatch } from 'react-redux';
|
|
12
|
+
|
|
13
|
+
export interface IconValueHelpProps {
|
|
14
|
+
isIcon: boolean | undefined;
|
|
15
|
+
icons:
|
|
16
|
+
| {
|
|
17
|
+
name: string;
|
|
18
|
+
content: string;
|
|
19
|
+
fontFamily: string;
|
|
20
|
+
}[]
|
|
21
|
+
| [];
|
|
22
|
+
value: string;
|
|
23
|
+
controlId: string;
|
|
24
|
+
propertyName: string;
|
|
25
|
+
disabled: boolean;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* React element for showing ui5 icon values.
|
|
30
|
+
*
|
|
31
|
+
* @param iconValueHelpProps IconValueHelpProps
|
|
32
|
+
* @returns ReactElement
|
|
33
|
+
*/
|
|
34
|
+
export function IconValueHelp(iconValueHelpProps: IconValueHelpProps): ReactElement {
|
|
35
|
+
const { icons, value, propertyName, controlId, disabled } = iconValueHelpProps;
|
|
36
|
+
const dispatch = useDispatch();
|
|
37
|
+
const [newValue, setNewValue] = useState(value || '');
|
|
38
|
+
const { t } = useTranslation();
|
|
39
|
+
const [items, setItems] = useState(icons);
|
|
40
|
+
const [isDialogVisible, setDialogVisibility] = useState(false);
|
|
41
|
+
|
|
42
|
+
const onValueHelpButtonClick = (): void => {
|
|
43
|
+
setDialogVisibility(true);
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const onSelectionChange = (rows: number[]): void => {
|
|
47
|
+
if (items && rows.length > 0) {
|
|
48
|
+
setNewValue('sap-icon://' + items[rows[0]].name);
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const onFilterChange = (
|
|
53
|
+
event?: React.ChangeEvent<HTMLInputElement> | undefined,
|
|
54
|
+
filterValue?: string | undefined
|
|
55
|
+
): void => {
|
|
56
|
+
if (filterValue && icons) {
|
|
57
|
+
setItems(
|
|
58
|
+
icons.filter((icon) => {
|
|
59
|
+
return icon.name.toLowerCase().includes(filterValue.toLowerCase());
|
|
60
|
+
})
|
|
61
|
+
);
|
|
62
|
+
} else {
|
|
63
|
+
setItems(icons);
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
const onIconColumnRender = (item: IconColumnProps) => (
|
|
67
|
+
<IconColumn content={item.content} fontFamily={item.fontFamily} />
|
|
68
|
+
);
|
|
69
|
+
const onLabelColumnRender = (item: LabelColumnProps) => (
|
|
70
|
+
<LabelColumn fontFamily={item.fontFamily} name={item.name} />
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
const col1: UIColumn = {
|
|
74
|
+
key: t('ICON'),
|
|
75
|
+
name: t('ICON'),
|
|
76
|
+
fieldName: t('ICON'),
|
|
77
|
+
minWidth: 68,
|
|
78
|
+
flexGrow: 1,
|
|
79
|
+
isResizable: true,
|
|
80
|
+
onRender: onIconColumnRender
|
|
81
|
+
};
|
|
82
|
+
const col2: UIColumn = {
|
|
83
|
+
key: t('ICON_NAME'),
|
|
84
|
+
name: t('ICON_NAME'),
|
|
85
|
+
fieldName: t('ICON_NAME'),
|
|
86
|
+
minWidth: 238,
|
|
87
|
+
flexGrow: 4,
|
|
88
|
+
isResizable: true,
|
|
89
|
+
onRender: onLabelColumnRender
|
|
90
|
+
};
|
|
91
|
+
const columns: UIColumn[] = [col1, col2];
|
|
92
|
+
const propertyNamePascalCase = propertyName[0].toUpperCase() + propertyName.substring(1);
|
|
93
|
+
return (
|
|
94
|
+
<>
|
|
95
|
+
<UIIconButton
|
|
96
|
+
id={`SelectIconFor${propertyNamePascalCase}`}
|
|
97
|
+
className="valueHelp-button"
|
|
98
|
+
title={t('SELECT_ICON')}
|
|
99
|
+
iconProps={{ iconName: UiIcons.ValueHelp }}
|
|
100
|
+
onClick={onValueHelpButtonClick}
|
|
101
|
+
disabled={disabled}
|
|
102
|
+
/>
|
|
103
|
+
<UIDialog
|
|
104
|
+
hidden={!isDialogVisible}
|
|
105
|
+
modalProps={{
|
|
106
|
+
className: 'icon-dialog'
|
|
107
|
+
}}
|
|
108
|
+
dialogContentProps={{
|
|
109
|
+
title: t('SELECT_ICON')
|
|
110
|
+
}}
|
|
111
|
+
closeButtonAriaLabel={t('CLOSE')}
|
|
112
|
+
acceptButtonText={t('OK')}
|
|
113
|
+
cancelButtonText={t('CANCEL')}
|
|
114
|
+
onAccept={() => {
|
|
115
|
+
setDialogVisibility(false);
|
|
116
|
+
setItems(icons);
|
|
117
|
+
setCachedValue(controlId, propertyName, InputType.string, newValue);
|
|
118
|
+
const action = changeProperty({
|
|
119
|
+
controlId,
|
|
120
|
+
propertyName,
|
|
121
|
+
value: newValue
|
|
122
|
+
});
|
|
123
|
+
dispatch(action);
|
|
124
|
+
}}
|
|
125
|
+
onCancel={() => {
|
|
126
|
+
setDialogVisibility(false);
|
|
127
|
+
setItems(icons);
|
|
128
|
+
}}>
|
|
129
|
+
<div className="filter-icon-div" style={{ width: '100%', display: 'flex', alignItems: 'center' }}>
|
|
130
|
+
<UISearchBox
|
|
131
|
+
className="filter-icons"
|
|
132
|
+
autoFocus={false}
|
|
133
|
+
disableAnimation={false}
|
|
134
|
+
placeholder="Filter Icons"
|
|
135
|
+
onChange={onFilterChange}
|
|
136
|
+
/>
|
|
137
|
+
</div>
|
|
138
|
+
<div className="icon-table-div" style={{ height: '100%', position: 'relative', marginTop: '10px' }}>
|
|
139
|
+
<UITable
|
|
140
|
+
className="icon-table space"
|
|
141
|
+
scrollablePaneProps={{
|
|
142
|
+
className: 'icon-table'
|
|
143
|
+
}}
|
|
144
|
+
selectionMode={SelectionMode.single}
|
|
145
|
+
dataSetKey={'datasetkey'}
|
|
146
|
+
items={items}
|
|
147
|
+
columns={columns}
|
|
148
|
+
onSelectionChange={onSelectionChange}
|
|
149
|
+
ariaLabelForSelectionColumn="Toggle selection"
|
|
150
|
+
ariaLabelForSelectAllCheckbox="Toggle selection for all items"
|
|
151
|
+
checkButtonAriaLabel="select row"
|
|
152
|
+
layoutMode={1}
|
|
153
|
+
isHeaderVisible={true}
|
|
154
|
+
/>
|
|
155
|
+
</div>
|
|
156
|
+
</UIDialog>
|
|
157
|
+
</>
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
interface IconColumnProps {
|
|
161
|
+
fontFamily: string;
|
|
162
|
+
content: string;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* React element for showing ui5 icon column.
|
|
167
|
+
*
|
|
168
|
+
* @param props IconColumnProps
|
|
169
|
+
* @returns ReactElement
|
|
170
|
+
*/
|
|
171
|
+
function IconColumn(props: IconColumnProps): React.JSX.Element {
|
|
172
|
+
const { content, fontFamily } = props;
|
|
173
|
+
const style: CSSProperties = {
|
|
174
|
+
fontFamily: fontFamily,
|
|
175
|
+
fontSize: '1rem',
|
|
176
|
+
fontStyle: 'normal',
|
|
177
|
+
display: 'inline-block',
|
|
178
|
+
lineHeight: 0,
|
|
179
|
+
verticalAlign: 'baseLine',
|
|
180
|
+
textAlign: 'center',
|
|
181
|
+
marginTop: 8
|
|
182
|
+
};
|
|
183
|
+
return <span className="sapUiIcon icon-span" data-sap-ui-icon-content={content} style={style}></span>;
|
|
184
|
+
}
|
|
185
|
+
interface LabelColumnProps {
|
|
186
|
+
name: string;
|
|
187
|
+
fontFamily: string;
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* React element for showing ui5 label column.
|
|
191
|
+
*
|
|
192
|
+
* @param props { name: string; fontFamily: string }
|
|
193
|
+
* @returns ReactElement
|
|
194
|
+
*/
|
|
195
|
+
function LabelColumn(props: LabelColumnProps): React.JSX.Element {
|
|
196
|
+
const { name, fontFamily } = props;
|
|
197
|
+
const style: CSSProperties = {
|
|
198
|
+
fontFamily: fontFamily,
|
|
199
|
+
fontSize: '13px',
|
|
200
|
+
fontStyle: 'normal'
|
|
201
|
+
};
|
|
202
|
+
return <span style={style}>{name}</span>;
|
|
203
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { ReactElement } from 'react';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { InputTypeToggle } from './InputTypeToggle';
|
|
4
|
+
import type { InputTypeWrapperProps } from './types';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Input type selector.
|
|
8
|
+
*
|
|
9
|
+
* @param props InputTypeWrapperProps
|
|
10
|
+
* @returns ReactElement
|
|
11
|
+
*/
|
|
12
|
+
export function InputTypeSelector(props: InputTypeWrapperProps): ReactElement {
|
|
13
|
+
return (
|
|
14
|
+
<>
|
|
15
|
+
{props.toggleOptions.map((optionProps) => (
|
|
16
|
+
<InputTypeToggle key={optionProps.inputType} inputTypeProps={optionProps} {...props} />
|
|
17
|
+
))}
|
|
18
|
+
</>
|
|
19
|
+
);
|
|
20
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { UIIconButton } from '@sap-ux/ui-components';
|
|
2
|
+
import type { ReactElement } from 'react';
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import { useDispatch } from 'react-redux';
|
|
5
|
+
import type { ControlProperty, StringControlPropertyWithOptions } from '@sap-ux-private/control-property-editor-common';
|
|
6
|
+
import { changeProperty } from '../../slice';
|
|
7
|
+
import type { CacheValue } from './propertyValuesCache';
|
|
8
|
+
import { getCachedValue } from './propertyValuesCache';
|
|
9
|
+
import styles from './InputTypeToggle.module.scss';
|
|
10
|
+
import type { InputTypeToggleProps } from './types';
|
|
11
|
+
import { InputType } from './types';
|
|
12
|
+
import { reportTelemetry, STRING_VALUE_TYPE } from '@sap-ux-private/control-property-editor-common';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Get value for input type.
|
|
16
|
+
*
|
|
17
|
+
* @param controlId string
|
|
18
|
+
* @param property ControlProperty
|
|
19
|
+
* @param inputType InputType
|
|
20
|
+
* @returns CacheValue
|
|
21
|
+
*/
|
|
22
|
+
export function getValueForInputType(controlId: string, property: ControlProperty, inputType: InputType): CacheValue {
|
|
23
|
+
if (inputType === InputType.booleanTrue) {
|
|
24
|
+
return true;
|
|
25
|
+
} else if (inputType === InputType.booleanFalse) {
|
|
26
|
+
return false;
|
|
27
|
+
} else {
|
|
28
|
+
const cachedValue = getCachedValue(controlId, property.name, inputType);
|
|
29
|
+
if (cachedValue) {
|
|
30
|
+
return cachedValue;
|
|
31
|
+
} else if (inputType === InputType.expression) {
|
|
32
|
+
return '{expression}';
|
|
33
|
+
} else if (inputType === InputType.enumMember) {
|
|
34
|
+
return (property as StringControlPropertyWithOptions).options[0]?.key ?? '';
|
|
35
|
+
} else {
|
|
36
|
+
return property.type === STRING_VALUE_TYPE ? '' : 0;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* React element for input type toggle.
|
|
43
|
+
*
|
|
44
|
+
* @param inputTypeToggleProps InputTypeToggleProps
|
|
45
|
+
* @returns ReactElement
|
|
46
|
+
*/
|
|
47
|
+
export function InputTypeToggle(inputTypeToggleProps: InputTypeToggleProps): ReactElement {
|
|
48
|
+
const { inputTypeProps, controlId, property, controlName } = inputTypeToggleProps;
|
|
49
|
+
const { tooltip, iconName, selected } = inputTypeProps;
|
|
50
|
+
const dispatch = useDispatch();
|
|
51
|
+
return (
|
|
52
|
+
<UIIconButton
|
|
53
|
+
className={selected ? styles.selectedTypeButton : ''}
|
|
54
|
+
data-testid={`${property.name}--InputTypeToggle--${inputTypeProps.inputType}`}
|
|
55
|
+
iconProps={{ iconName: iconName }}
|
|
56
|
+
title={tooltip}
|
|
57
|
+
disabled={!property.isEnabled}
|
|
58
|
+
toggle={true}
|
|
59
|
+
checked={selected}
|
|
60
|
+
style={{ margin: 1, padding: 1 }}
|
|
61
|
+
onClick={(): void => {
|
|
62
|
+
if (inputTypeProps.selected) {
|
|
63
|
+
return;
|
|
64
|
+
} // click on already selected button should have no effect
|
|
65
|
+
const newValue = getValueForInputType(controlId, property, inputTypeProps.inputType);
|
|
66
|
+
reportTelemetry({ category: 'Property Change', propertyName: property.name }).catch((error) => {
|
|
67
|
+
console.error(`Error in reporting telemetry`, error);
|
|
68
|
+
});
|
|
69
|
+
const action = changeProperty({
|
|
70
|
+
controlId,
|
|
71
|
+
propertyName: property.name,
|
|
72
|
+
value: newValue,
|
|
73
|
+
controlName
|
|
74
|
+
});
|
|
75
|
+
dispatch(action);
|
|
76
|
+
}}
|
|
77
|
+
/>
|
|
78
|
+
);
|
|
79
|
+
}
|