@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.
Files changed (112) hide show
  1. package/.eslintignore +1 -0
  2. package/.eslintrc.js +16 -0
  3. package/CHANGELOG.md +7 -0
  4. package/LICENSE +201 -0
  5. package/README.md +16 -0
  6. package/dist/app.css +2 -0
  7. package/dist/app.css.map +7 -0
  8. package/dist/app.js +347 -0
  9. package/dist/app.js.map +7 -0
  10. package/esbuild.js +25 -0
  11. package/jest.config.js +20 -0
  12. package/package.json +68 -0
  13. package/src/App.scss +57 -0
  14. package/src/App.tsx +136 -0
  15. package/src/Workarounds.scss +79 -0
  16. package/src/actions.ts +3 -0
  17. package/src/components/AppLogo.module.scss +8 -0
  18. package/src/components/AppLogo.tsx +75 -0
  19. package/src/components/ChangeIndicator.tsx +80 -0
  20. package/src/components/Separator.tsx +32 -0
  21. package/src/components/ThemeSelectorCallout.scss +48 -0
  22. package/src/components/ThemeSelectorCallout.tsx +125 -0
  23. package/src/components/ToolBar.scss +39 -0
  24. package/src/components/ToolBar.tsx +26 -0
  25. package/src/components/index.ts +4 -0
  26. package/src/devices.ts +18 -0
  27. package/src/global.d.ts +4 -0
  28. package/src/i18n/i18n.json +68 -0
  29. package/src/i18n.ts +25 -0
  30. package/src/icons.tsx +198 -0
  31. package/src/index.css +1288 -0
  32. package/src/index.tsx +47 -0
  33. package/src/middleware.ts +54 -0
  34. package/src/panels/LeftPanel.scss +17 -0
  35. package/src/panels/LeftPanel.tsx +48 -0
  36. package/src/panels/changes/ChangeStack.module.scss +3 -0
  37. package/src/panels/changes/ChangeStack.tsx +219 -0
  38. package/src/panels/changes/ChangeStackHeader.tsx +43 -0
  39. package/src/panels/changes/ChangesPanel.module.scss +18 -0
  40. package/src/panels/changes/ChangesPanel.tsx +90 -0
  41. package/src/panels/changes/ControlGroup.module.scss +17 -0
  42. package/src/panels/changes/ControlGroup.tsx +61 -0
  43. package/src/panels/changes/PropertyChange.module.scss +24 -0
  44. package/src/panels/changes/PropertyChange.tsx +159 -0
  45. package/src/panels/changes/UnknownChange.module.scss +46 -0
  46. package/src/panels/changes/UnknownChange.tsx +96 -0
  47. package/src/panels/changes/index.tsx +3 -0
  48. package/src/panels/changes/utils.ts +36 -0
  49. package/src/panels/index.ts +2 -0
  50. package/src/panels/outline/Funnel.tsx +64 -0
  51. package/src/panels/outline/NoControlFound.tsx +45 -0
  52. package/src/panels/outline/OutlinePanel.scss +98 -0
  53. package/src/panels/outline/OutlinePanel.tsx +38 -0
  54. package/src/panels/outline/Tree.tsx +393 -0
  55. package/src/panels/outline/index.ts +1 -0
  56. package/src/panels/outline/utils.ts +154 -0
  57. package/src/panels/properties/Clipboard.tsx +44 -0
  58. package/src/panels/properties/DeviceSelector.tsx +40 -0
  59. package/src/panels/properties/DeviceToggle.tsx +39 -0
  60. package/src/panels/properties/DropdownEditor.tsx +80 -0
  61. package/src/panels/properties/Funnel.tsx +64 -0
  62. package/src/panels/properties/HeaderField.tsx +150 -0
  63. package/src/panels/properties/IconValueHelp.tsx +203 -0
  64. package/src/panels/properties/InputTypeSelector.tsx +20 -0
  65. package/src/panels/properties/InputTypeToggle.module.scss +4 -0
  66. package/src/panels/properties/InputTypeToggle.tsx +79 -0
  67. package/src/panels/properties/InputTypeWrapper.tsx +259 -0
  68. package/src/panels/properties/NoControlSelected.tsx +38 -0
  69. package/src/panels/properties/Properties.scss +102 -0
  70. package/src/panels/properties/PropertiesList.tsx +162 -0
  71. package/src/panels/properties/PropertiesPanel.tsx +30 -0
  72. package/src/panels/properties/PropertyDocumentation.module.scss +81 -0
  73. package/src/panels/properties/PropertyDocumentation.tsx +174 -0
  74. package/src/panels/properties/SapUiIcon.scss +109 -0
  75. package/src/panels/properties/StringEditor.tsx +122 -0
  76. package/src/panels/properties/ViewChanger.module.scss +5 -0
  77. package/src/panels/properties/ViewChanger.tsx +143 -0
  78. package/src/panels/properties/constants.ts +2 -0
  79. package/src/panels/properties/index.tsx +1 -0
  80. package/src/panels/properties/propertyValuesCache.ts +39 -0
  81. package/src/panels/properties/types.ts +49 -0
  82. package/src/slice.ts +216 -0
  83. package/src/store.ts +19 -0
  84. package/src/use-local-storage.ts +40 -0
  85. package/src/use-window-size.ts +39 -0
  86. package/src/variables.scss +2 -0
  87. package/test/unit/App.test.tsx +207 -0
  88. package/test/unit/appIndex.test.ts +23 -0
  89. package/test/unit/components/ChangeIndicator.test.tsx +120 -0
  90. package/test/unit/components/ThemeSelector.test.tsx +41 -0
  91. package/test/unit/middleware.test.ts +116 -0
  92. package/test/unit/panels/changes/ChangesPanel.test.tsx +261 -0
  93. package/test/unit/panels/changes/utils.test.ts +40 -0
  94. package/test/unit/panels/outline/OutlinePanel.test.tsx +353 -0
  95. package/test/unit/panels/outline/__snapshots__/utils.test.ts.snap +36 -0
  96. package/test/unit/panels/outline/utils.test.ts +83 -0
  97. package/test/unit/panels/properties/Clipboard.test.tsx +18 -0
  98. package/test/unit/panels/properties/DropdownEditor.test.tsx +62 -0
  99. package/test/unit/panels/properties/Funnel.test.tsx +34 -0
  100. package/test/unit/panels/properties/HeaderField.test.tsx +36 -0
  101. package/test/unit/panels/properties/IconValueHelp.test.tsx +60 -0
  102. package/test/unit/panels/properties/InputTypeToggle.test.tsx +126 -0
  103. package/test/unit/panels/properties/InputTypeWrapper.test.tsx +430 -0
  104. package/test/unit/panels/properties/PropertyDocumentation.test.tsx +131 -0
  105. package/test/unit/panels/properties/StringEditor.test.tsx +107 -0
  106. package/test/unit/panels/properties/ViewChanger.test.tsx +190 -0
  107. package/test/unit/panels/properties/propertyValuesCache.test.ts +23 -0
  108. package/test/unit/slice.test.ts +268 -0
  109. package/test/unit/utils.tsx +67 -0
  110. package/test/utils/utils.tsx +25 -0
  111. package/tsconfig.eslint.json +4 -0
  112. package/tsconfig.json +39 -0
@@ -0,0 +1,259 @@
1
+ import type { ReactElement } from 'react';
2
+ import React, { useState } from 'react';
3
+ import { useDispatch } from 'react-redux';
4
+ import type { TFunction } from 'i18next';
5
+ import { Label, Stack } from '@fluentui/react';
6
+
7
+ import {
8
+ UIFocusZone,
9
+ UITooltip,
10
+ UITooltipUtils,
11
+ UIDirectionalHint,
12
+ getCalloutStyle,
13
+ UIDialog
14
+ } from '@sap-ux/ui-components';
15
+
16
+ import type { ControlProperty, PropertyChangeDeletionDetails } from '@sap-ux-private/control-property-editor-common';
17
+ import {
18
+ CHECKBOX_EDITOR_TYPE,
19
+ deletePropertyChanges,
20
+ DROPDOWN_EDITOR_TYPE,
21
+ INPUT_EDITOR_TYPE,
22
+ INTEGER_VALUE_TYPE,
23
+ STRING_VALUE_TYPE
24
+ } from '@sap-ux-private/control-property-editor-common';
25
+
26
+ import { IconName } from '../../icons';
27
+ import { ChangeIndicator } from '../../components/ChangeIndicator';
28
+
29
+ import type { CacheValue } from './propertyValuesCache';
30
+ import type { InputTypeToggleOptionProps, InputTypeWrapperProps } from './types';
31
+ import { InputType, isExpression } from './types';
32
+ import { PropertyDocumentation } from './PropertyDocumentation';
33
+ import { defaultFontSize } from './constants';
34
+ import { InputTypeSelector } from './InputTypeSelector';
35
+ import { useTranslation } from 'react-i18next';
36
+
37
+ export const getDefaultInputType = (editor: string, type: string, value: CacheValue): InputType => {
38
+ let defaultInputType: InputType = InputType.expression;
39
+ switch (editor) {
40
+ case DROPDOWN_EDITOR_TYPE:
41
+ defaultInputType = InputType.enumMember;
42
+ break;
43
+ case INPUT_EDITOR_TYPE:
44
+ defaultInputType = type === STRING_VALUE_TYPE ? InputType.string : InputType.number;
45
+ break;
46
+ case CHECKBOX_EDITOR_TYPE:
47
+ if (typeof value === 'boolean') {
48
+ defaultInputType = value ? InputType.booleanTrue : InputType.booleanFalse;
49
+ }
50
+ break;
51
+ default:
52
+ }
53
+ return defaultInputType;
54
+ };
55
+
56
+ /**
57
+ * Gets translated tooltip, if translation function exists.
58
+ *
59
+ * @param value string
60
+ * @param t TFunction
61
+ * @returns string
62
+ */
63
+ function getToolTip(value: string, t?: TFunction): string {
64
+ if (t) {
65
+ return t(value);
66
+ } else {
67
+ return value;
68
+ }
69
+ }
70
+
71
+ export const getInputTypeToggleOptions = (property: ControlProperty, t?: TFunction): InputTypeToggleOptionProps[] => {
72
+ const { value, editor, type } = property;
73
+ const inputTypeToggleOptions: InputTypeToggleOptionProps[] = [];
74
+ switch (editor) {
75
+ case CHECKBOX_EDITOR_TYPE:
76
+ inputTypeToggleOptions.push({
77
+ inputType: InputType.booleanTrue,
78
+ tooltip: getToolTip('BOOLEAN_TYPE_TRUE', t),
79
+ iconName: IconName.boolTrue,
80
+ selected: typeof value === 'boolean' && value === true
81
+ });
82
+ inputTypeToggleOptions.push({
83
+ inputType: InputType.booleanFalse,
84
+ tooltip: getToolTip('BOOLEAN_TYPE_FALSE', t),
85
+ iconName: IconName.boolFalse,
86
+ selected: typeof value === 'boolean' && value === false
87
+ });
88
+ break;
89
+ case DROPDOWN_EDITOR_TYPE:
90
+ inputTypeToggleOptions.push({
91
+ inputType: InputType.enumMember,
92
+ tooltip: getToolTip('ENUM_TYPE', t),
93
+ iconName: IconName.dropdown,
94
+ selected: !!property.options.find((option) => option.key === value)
95
+ });
96
+ break;
97
+ case INPUT_EDITOR_TYPE:
98
+ if (type === STRING_VALUE_TYPE) {
99
+ inputTypeToggleOptions.push({
100
+ inputType: InputType.string,
101
+ tooltip: getToolTip('STRING_TYPE', t),
102
+ iconName: IconName.string,
103
+ selected: typeof value !== 'string' || !isExpression(value)
104
+ });
105
+ } else {
106
+ const textKey = type === INTEGER_VALUE_TYPE ? 'INTEGER_TYPE' : 'FLOAT_TYPE';
107
+ inputTypeToggleOptions.push({
108
+ inputType: InputType.number,
109
+ tooltip: getToolTip(textKey, t),
110
+ iconName: IconName.number,
111
+ selected: typeof value !== 'string' || !isExpression(value)
112
+ });
113
+ }
114
+ break;
115
+ default:
116
+ }
117
+
118
+ inputTypeToggleOptions.push({
119
+ inputType: InputType.expression,
120
+ tooltip: getToolTip('EXPRESSION_TYPE', t),
121
+ iconName: IconName.expression,
122
+ selected: typeof value === 'string' && isExpression(value)
123
+ });
124
+
125
+ return inputTypeToggleOptions;
126
+ };
127
+
128
+ /**
129
+ * React element for input type wrapper.
130
+ *
131
+ * @param props InputTypeWrapperProps
132
+ * @returns ReactElement
133
+ */
134
+ export function InputTypeWrapper(props: InputTypeWrapperProps): ReactElement {
135
+ const { property, changes } = props;
136
+ const { name, isEnabled, documentation } = property;
137
+
138
+ const { t } = useTranslation();
139
+ const dispatch = useDispatch();
140
+
141
+ const [dialogState, setDialogState] = useState<PropertyChangeDeletionDetails | undefined>(undefined);
142
+ const documentationContent = documentation && (
143
+ <PropertyDocumentation
144
+ title={property.readableName}
145
+ defaultValue={documentation?.defaultValue}
146
+ description={documentation?.description}
147
+ propertyName={documentation?.propertyName}
148
+ propertyType={documentation?.propertyType}
149
+ onDelete={showDeleteConfirmation}
150
+ />
151
+ );
152
+
153
+ /**
154
+ *
155
+ * @param controlId string
156
+ * @param propertyName string
157
+ */
158
+ function showDeleteConfirmation(controlId: string, propertyName: string): void {
159
+ setDialogState({
160
+ controlId,
161
+ propertyName
162
+ });
163
+ }
164
+
165
+ function onConfirmDelete(): void {
166
+ if (dialogState) {
167
+ dispatch(deletePropertyChanges(dialogState));
168
+ setDialogState(undefined);
169
+ }
170
+ }
171
+
172
+ function onCancelDelete(): void {
173
+ setDialogState(undefined);
174
+ }
175
+
176
+ const indicator = changes ? (
177
+ <Stack.Item>
178
+ <ChangeIndicator id={`${name}--ChangeIndicator`} {...changes} />
179
+ </Stack.Item>
180
+ ) : (
181
+ <></>
182
+ );
183
+ return (
184
+ <UIFocusZone
185
+ isCircularNavigation={true}
186
+ data-testid={`${name}--InputTypeWrapper`}
187
+ style={{
188
+ marginBottom: 10,
189
+ marginTop: 10
190
+ }}>
191
+ <Stack horizontal horizontalAlign="space-between">
192
+ <UITooltip
193
+ calloutProps={{
194
+ gapSpace: 5,
195
+ styles: getCalloutStyle({
196
+ styles: {
197
+ root: {
198
+ padding: 'none'
199
+ },
200
+ calloutMain: {
201
+ padding: 'none'
202
+ }
203
+ }
204
+ })
205
+ }}
206
+ delay={2}
207
+ maxWidth={400}
208
+ directionalHint={UIDirectionalHint.leftCenter}
209
+ id={`${name}--PropertyTooltip`}
210
+ tooltipProps={UITooltipUtils.renderContent(documentationContent ?? '')}>
211
+ <Stack
212
+ horizontal
213
+ horizontalAlign="space-between"
214
+ tokens={{
215
+ childrenGap: '5px'
216
+ }}>
217
+ {indicator}
218
+ <Stack.Item>
219
+ <Label
220
+ data-aria-label={name}
221
+ data-testid={`${name}--Label`}
222
+ disabled={!isEnabled}
223
+ style={{
224
+ color: 'var(--vscode-foreground)',
225
+ opacity: isEnabled ? 1 : 0.4,
226
+ fontSize: defaultFontSize,
227
+ fontWeight: 'bold',
228
+ padding: 0,
229
+ width: '190px',
230
+ textOverflow: 'ellipsis',
231
+ whiteSpace: 'nowrap',
232
+ overflowX: 'hidden',
233
+ marginTop: 2
234
+ }}>
235
+ {property.readableName}
236
+ </Label>
237
+ </Stack.Item>
238
+ </Stack>
239
+ </UITooltip>
240
+ <Stack horizontal horizontalAlign="end">
241
+ <InputTypeSelector {...props} />
242
+ </Stack>
243
+ </Stack>
244
+ {props.children}
245
+ {dialogState && (
246
+ <UIDialog
247
+ hidden={dialogState === undefined}
248
+ onAccept={onConfirmDelete}
249
+ acceptButtonText={t('CONFIRM_DELETE')}
250
+ cancelButtonText={t('CANCEL_DELETE')}
251
+ onCancel={onCancelDelete}
252
+ dialogContentProps={{
253
+ title: t('CONFIRM_DELETE_TITLE'),
254
+ subText: t('CONFIRM_DELETE_SUBTEXT')
255
+ }}></UIDialog>
256
+ )}
257
+ </UIFocusZone>
258
+ );
259
+ }
@@ -0,0 +1,38 @@
1
+ import React from 'react';
2
+ import { Text } from '@fluentui/react';
3
+ import { useTranslation } from 'react-i18next';
4
+
5
+ const NoControlSelected = () => {
6
+ const { t } = useTranslation();
7
+ return (
8
+ <>
9
+ <Text className="properties-no-control-selected-text">{t('NO_CONTROL_SELECTED')}</Text>
10
+ <Text className="properties-modify-text">{t('PROPERTIES_MODIFY_TEXT')}</Text>
11
+ <div className="properties-control-select-icon">
12
+ {
13
+ <svg
14
+ data-testid="Control-Property-Editor-No-Control-Selected"
15
+ width="80"
16
+ height="37"
17
+ viewBox="0 0 80 37"
18
+ fill="none"
19
+ xmlns="http://www.w3.org/2000/svg">
20
+ <path
21
+ fillRule="evenodd"
22
+ clipRule="evenodd"
23
+ d="M0 2.75V0H3.16666V2H2V2.75H0ZM15.8333 0H9.5V2H15.8333V0ZM22.1667 0V2H28.5V0H22.1667ZM34.8333 0V2H41.1667V0H34.8333ZM47.5 0V2H53.8333V0H47.5ZM60.1667 0V2H66.5V0H60.1667ZM72.8333 0V2H74V2.75H76V0H72.8333ZM76 8.25H74V13.75H76V8.25ZM76 19.25H74V24.75H76V19.25ZM76 30.25H74V31H72.8333V33H76V30.25ZM66.5 33V31H60.1667V33H66.5ZM53.8333 33V31H47.5V33H53.8333ZM41.1667 33V31H34.8333V33H41.1667ZM28.5 33V31H22.1667V33H28.5ZM15.8333 33V31H9.5V33H15.8333ZM3.16667 33V31H2V30.25H0V33H3.16667ZM0 24.75H2V19.25H0V24.75ZM0 13.75H2V8.25H0V13.75Z"
24
+ fill="var(--vscode-focusBorder)"
25
+ />
26
+ <path d="M66 12H69V20H66V12Z" fill="var(--vscode-icon-foreground)" />
27
+ <path d="M72 23H80V26H72V23Z" fill="var(--vscode-icon-foreground)" />
28
+ <path d="M63 23H55V26H63V23Z" fill="var(--vscode-icon-foreground)" />
29
+ <path d="M66 29H69V37H66V29Z" fill="var(--vscode-icon-foreground)" />
30
+ <path d="M69 23H66V26H69V23Z" fill="var(--vscode-icon-foreground)" />
31
+ </svg>
32
+ }
33
+ </div>
34
+ </>
35
+ );
36
+ };
37
+
38
+ export { NoControlSelected };
@@ -0,0 +1,102 @@
1
+ @import './../../variables.scss';
2
+
3
+ .property-content {
4
+ padding: 20px 15px;
5
+ }
6
+
7
+ .space {
8
+ margin-bottom: 10px;
9
+ &:last-child {
10
+ margin-bottom: 0;
11
+ }
12
+ }
13
+
14
+ .space-toggle {
15
+ label {
16
+ font-size: $defaultFontSize;
17
+ font-weight: bold;
18
+ text-overflow: ellipsis;
19
+ overflow: hidden;
20
+ white-space: nowrap;
21
+ }
22
+ margin-bottom: 12px;
23
+ }
24
+ .space-toggle-disabled {
25
+ label {
26
+ opacity: 0.55;
27
+ }
28
+ }
29
+
30
+ .dropdownEditor {
31
+ label {
32
+ font-weight: bold;
33
+ padding-bottom: 2px;
34
+ height: auto;
35
+ }
36
+ color: var(--vscode-foreground);
37
+ font-size: $defaultFontSize;
38
+ margin-bottom: 12px;
39
+ }
40
+
41
+ .stringEditor {
42
+ font-weight: bold;
43
+ font-size: $defaultFontSize;
44
+ div {
45
+ label {
46
+ font-weight: bold !important;
47
+ margin-bottom: 0px !important;
48
+ }
49
+ }
50
+ margin-bottom: 12px;
51
+ }
52
+
53
+ .stringEditorDisabled {
54
+ div {
55
+ div {
56
+ opacity: 0.4;
57
+ }
58
+ }
59
+ }
60
+
61
+ .properties-no-control-selected-text {
62
+ font-style: normal;
63
+ font-weight: bold;
64
+ font-size: 24px;
65
+ line-height: 29px;
66
+ text-align: center;
67
+ padding-top: 30px;
68
+ color: var(--vscode-foreground);
69
+ }
70
+
71
+ .properties-modify-text {
72
+ font-style: normal;
73
+ font-weight: normal;
74
+ font-size: 13px;
75
+ line-height: 18px;
76
+ text-align: center;
77
+ margin: 10%;
78
+ color: var(--vscode-foreground);
79
+ }
80
+
81
+ .properties-control-select-icon {
82
+ text-align: center;
83
+ }
84
+ .filter-properties {
85
+ display: flex;
86
+ margin: 0px 15px 10px 15px;
87
+ flex-direction: row;
88
+ align-items: center;
89
+ }
90
+
91
+ .funnel-properties-icon {
92
+ margin-left: 16px;
93
+ i {
94
+ line-height: 20px;
95
+ }
96
+ }
97
+
98
+ label[for='editable-properties-checkbox'] {
99
+ .ms-Checkbox-text {
100
+ font-size: 13px !important;
101
+ }
102
+ }
@@ -0,0 +1,162 @@
1
+ import type { ReactElement } from 'react';
2
+ import React, { useState } from 'react';
3
+ import { useTranslation } from 'react-i18next';
4
+ import type { Control } from '@sap-ux-private/control-property-editor-common';
5
+ import {
6
+ CHECKBOX_EDITOR_TYPE,
7
+ DROPDOWN_EDITOR_TYPE,
8
+ INPUT_EDITOR_TYPE
9
+ } from '@sap-ux-private/control-property-editor-common';
10
+ import { Separator } from '../../components/Separator';
11
+ import type { RootState } from '../../store';
12
+ import { DropdownEditor } from './DropdownEditor';
13
+ import { HeaderField } from './HeaderField';
14
+ import { getDefaultInputType, getInputTypeToggleOptions, InputTypeWrapper } from './InputTypeWrapper';
15
+ import { setCachedValue } from './propertyValuesCache';
16
+ import { StringEditor } from './StringEditor';
17
+ import type { InputTypeWrapperProps } from './types';
18
+ import { isExpression } from './types';
19
+ import { sectionHeaderFontSize } from './constants';
20
+ import { useSelector } from 'react-redux';
21
+ import { UISearchBox } from '@sap-ux/ui-components';
22
+ import { NoControlSelected } from './NoControlSelected';
23
+ import { Label } from '@fluentui/react';
24
+ import './Properties.scss';
25
+ import { Funnel } from './Funnel';
26
+ import type { ControlChangeStats, FilterOptions } from '../../slice';
27
+ import { FilterName } from '../../slice';
28
+
29
+ /**
30
+ * React element for all properties including id & type and property editors.
31
+ *
32
+ * @returns ReactElement
33
+ */
34
+ export function PropertiesList(): ReactElement {
35
+ const { t } = useTranslation();
36
+ const control = useSelector<RootState, Control | undefined>((state) => state.selectedControl);
37
+ const controlChanges = useSelector<RootState, ControlChangeStats | undefined>(
38
+ (state) => state.changes.controls[control?.id ?? '']
39
+ );
40
+ const isEditableOnly = useSelector<RootState, FilterOptions[]>((state) => state.filterQuery).filter(
41
+ (item) => item.name === FilterName.showEditableProperties
42
+ )[0].value as boolean;
43
+ const [filterValue, setFilterValue] = useState('');
44
+ const doc = {
45
+ defaultValue: '-',
46
+ description: 'The unique identifier within a page, either configured or automatically generated.',
47
+ propertyName: 'id',
48
+ type: 'sap.ui.core.ID',
49
+ propertyType: 'ID'
50
+ };
51
+ if (!control) {
52
+ // Nothing selected, show message
53
+ return <NoControlSelected />;
54
+ }
55
+ const { id, type, properties, name } = control;
56
+ const onFilterChange = (
57
+ event?: React.ChangeEvent<HTMLInputElement> | undefined,
58
+ filterValue?: string | undefined
59
+ ): void => {
60
+ setFilterValue(filterValue?.toLowerCase() ?? '');
61
+ };
62
+ const editors = (
63
+ <>
64
+ {properties
65
+ .filter(
66
+ (property) =>
67
+ !filterValue ||
68
+ property.name.toLowerCase().includes(filterValue) ||
69
+ property.readableName.toLowerCase().includes(filterValue)
70
+ )
71
+ .filter((property) => (isEditableOnly && property.isEnabled) || !isEditableOnly)
72
+ .map((property) => {
73
+ const props: InputTypeWrapperProps = {
74
+ controlId: id,
75
+ controlName: name,
76
+ property,
77
+ toggleOptions: getInputTypeToggleOptions(property, t),
78
+ changes: controlChanges?.properties[property.name]
79
+ };
80
+ const defaultInputType = getDefaultInputType(property.editor, property.type, property.value);
81
+ setCachedValue(id, property.name, defaultInputType, property.value);
82
+ let result: ReactElement | undefined;
83
+ switch (property.editor) {
84
+ case CHECKBOX_EDITOR_TYPE:
85
+ if (typeof property.value !== 'boolean') {
86
+ result = <StringEditor {...props} />;
87
+ }
88
+ break;
89
+ case DROPDOWN_EDITOR_TYPE:
90
+ if (isExpression(property.value)) {
91
+ result = <StringEditor {...props} />;
92
+ } else {
93
+ result = (
94
+ <DropdownEditor
95
+ {...{
96
+ key: property.name,
97
+ controlId: id,
98
+ property,
99
+ controlName: name
100
+ }}
101
+ />
102
+ );
103
+ }
104
+ break;
105
+ case INPUT_EDITOR_TYPE:
106
+ result = <StringEditor {...props} />;
107
+ break;
108
+ default: {
109
+ console.warn(
110
+ `No property editor for '${JSON.stringify(property)}' found. Fallback to string editor`
111
+ );
112
+ result = <StringEditor {...props} />;
113
+ break;
114
+ }
115
+ }
116
+ return (
117
+ <InputTypeWrapper key={property.name} {...props}>
118
+ {result}
119
+ </InputTypeWrapper>
120
+ );
121
+ })}
122
+ </>
123
+ );
124
+ return (
125
+ <>
126
+ <div className="property-content">
127
+ <HeaderField label={t('CONTROL_ID_LABEL')} value={id} documentation={doc} hidden={false} />
128
+ <HeaderField label={t('CONTROL_TYPE_LABEL')} value={type} />
129
+ </div>
130
+ <Separator
131
+ style={{
132
+ marginTop: '0px',
133
+ marginBottom: '20px'
134
+ }}
135
+ />
136
+ <div className="filter-properties">
137
+ <UISearchBox
138
+ id="SearchProperties"
139
+ autoFocus={false}
140
+ disableAnimation={false}
141
+ placeholder={t('SEARCH_PROPERTIES')}
142
+ onChange={onFilterChange}
143
+ />
144
+ <Funnel />
145
+ </div>
146
+ <div className={`property-content app-panel-scroller`}>
147
+ <Label
148
+ data-aria-label={t('PROPERTIES')}
149
+ style={{
150
+ color: 'var(--vscode-foreground)',
151
+ fontSize: sectionHeaderFontSize,
152
+ fontWeight: 'bold',
153
+ padding: 0,
154
+ marginBottom: '10px'
155
+ }}>
156
+ {t('PROPERTIES')}
157
+ </Label>
158
+ {editors}
159
+ </div>
160
+ </>
161
+ );
162
+ }
@@ -0,0 +1,30 @@
1
+ import type { ReactElement } from 'react';
2
+ import React from 'react';
3
+
4
+ import { PropertiesList } from './PropertiesList';
5
+ import { Separator, ThemeSelectorCallout, Toolbar } from '../../components';
6
+ import { ViewChanger } from './ViewChanger';
7
+ import { DeviceSelector } from './DeviceSelector';
8
+
9
+ /**
10
+ * Main function to render the properties panel.
11
+ *
12
+ * @returns Properties panel as ReactElement
13
+ */
14
+ export function PropertiesPanel(): ReactElement {
15
+ return (
16
+ <>
17
+ <Toolbar
18
+ left={
19
+ <>
20
+ <ThemeSelectorCallout />
21
+ <Separator direction="vertical" style={{ marginLeft: '10px', marginRight: '10px' }} />
22
+ <ViewChanger />
23
+ </>
24
+ }
25
+ right={<DeviceSelector />}
26
+ />
27
+ <PropertiesList />
28
+ </>
29
+ );
30
+ }
@@ -0,0 +1,81 @@
1
+ .container {
2
+ padding: 10px;
3
+ }
4
+
5
+ .header {
6
+ height: 46px;
7
+ padding: 13px 10px 13px 10px;
8
+ border-bottom: 1px solid var(--vscode-contrastBorder, var(--vscode-editorIndentGuide-background));
9
+ }
10
+
11
+ .savedChanges {
12
+ color: var(--vscode-terminal-ansiGreen);
13
+ font-size: 11px;
14
+ line-height: 15px;
15
+ }
16
+
17
+ .unsavedChanges {
18
+ color: var(--vscode-foreground);
19
+ font-size: 11px;
20
+ line-height: 15px;
21
+ }
22
+
23
+ .title {
24
+ color: var(--vscode-foreground);
25
+ font-size: 14px;
26
+ font-weight: bold;
27
+ line-height: 16px;
28
+ }
29
+
30
+ .savedChangeRow {
31
+ width: 100%;
32
+ }
33
+
34
+ .bold,
35
+ .propertyName {
36
+ color: var(--vscode-foreground);
37
+ font-weight: bold;
38
+ font-size: 13px;
39
+ line-height: 18px;
40
+ }
41
+
42
+ .propertyName {
43
+ grid-column-start: 1;
44
+ }
45
+
46
+ .value {
47
+ color: var(--vscode-foreground);
48
+ font-size: 13px;
49
+ line-height: 18px;
50
+ }
51
+
52
+ .infoIcon {
53
+ padding: 3px;
54
+ }
55
+
56
+ .description {
57
+ color: var(--vscode-foreground);
58
+ font-size: 11px;
59
+ line-height: 15px;
60
+ }
61
+
62
+ .grid {
63
+ display: grid;
64
+ grid-template-columns: 125px 1fr 22px;
65
+ grid-template-rows: repeat(auto-fill, 22px);
66
+ min-height: 82px;
67
+ row-gap: 8px;
68
+ column-gap: 10px;
69
+ justify-items: start;
70
+ }
71
+ .grid span {
72
+ display: inline-flex;
73
+ align-items: center;
74
+ overflow: hidden;
75
+ max-width: 100%;
76
+ }
77
+
78
+ .propertyWithNoActions {
79
+ grid-column-start: 2;
80
+ grid-column-end: span 2;
81
+ }