@sap-ux/preview-middleware 0.6.3 → 0.7.1

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 (41) hide show
  1. package/dist/base/flp.d.ts +1 -0
  2. package/dist/base/flp.js +49 -8
  3. package/dist/client/adp/controllers/AddFragment.controller.js +1 -27
  4. package/dist/client/adp/controllers/AddFragment.controller.ts +2 -24
  5. package/dist/client/cpe/changes/flex-change.js +46 -0
  6. package/dist/client/cpe/changes/flex-change.ts +49 -0
  7. package/dist/client/cpe/changes/index.js +12 -0
  8. package/dist/client/cpe/changes/index.ts +1 -0
  9. package/dist/client/cpe/changes/service.js +164 -0
  10. package/dist/client/cpe/changes/service.ts +264 -0
  11. package/dist/client/cpe/control-data.js +201 -0
  12. package/dist/client/cpe/control-data.ts +304 -0
  13. package/dist/client/cpe/documentation.js +166 -0
  14. package/dist/client/cpe/documentation.ts +166 -0
  15. package/dist/client/cpe/facade.js +95 -0
  16. package/dist/client/cpe/facade.ts +95 -0
  17. package/dist/client/cpe/init.js +62 -12
  18. package/dist/client/cpe/init.ts +70 -5
  19. package/dist/client/cpe/logger.js +32 -0
  20. package/dist/client/cpe/logger.ts +26 -0
  21. package/dist/client/cpe/outline/index.js +26 -0
  22. package/dist/client/cpe/outline/index.ts +34 -0
  23. package/dist/client/cpe/outline/nodes.js +58 -0
  24. package/dist/client/cpe/outline/nodes.ts +52 -0
  25. package/dist/client/cpe/outline/utils.js +38 -0
  26. package/dist/client/cpe/outline/utils.ts +29 -0
  27. package/dist/client/cpe/selection.js +169 -0
  28. package/dist/client/cpe/selection.ts +237 -0
  29. package/dist/client/cpe/types.js +6 -0
  30. package/dist/client/cpe/types.ts +53 -0
  31. package/dist/client/cpe/utils.js +50 -0
  32. package/dist/client/cpe/utils.ts +62 -0
  33. package/dist/client/flp/WorkspaceConnector.js +65 -0
  34. package/dist/client/flp/WorkspaceConnector.ts +66 -0
  35. package/dist/client/thirdparty/@sap-ux-private/control-property-editor-common.js +272 -0
  36. package/dist/types/index.d.ts +4 -0
  37. package/dist/ui5/middleware.js +2 -1
  38. package/package.json +5 -7
  39. package/templates/flp/editor.html +11 -15
  40. package/templates/flp/sandbox.html +2 -1
  41. package/templates/flp/workspaceConnector.js +0 -76
@@ -0,0 +1,264 @@
1
+ import type {
2
+ ExternalAction,
3
+ SavedPropertyChange,
4
+ PendingPropertyChange,
5
+ UnknownSavedChange
6
+ } from '@sap-ux-private/control-property-editor-common';
7
+ import {
8
+ changeProperty,
9
+ changeStackModified,
10
+ deletePropertyChanges,
11
+ propertyChangeFailed,
12
+ FlexChangesEndPoints
13
+ } from '@sap-ux-private/control-property-editor-common';
14
+ import { applyChange } from './flex-change';
15
+ import type { SelectionService } from '../selection';
16
+
17
+ import type { ActionSenderFunction, SubscribeFunction, UI5AdaptationOptions, UI5Facade } from '../types';
18
+ import type Event from 'sap/ui/base/Event';
19
+ import type BaseCommand from 'sap/ui/rta/command/BaseCommand';
20
+ import Log from 'sap/base/Log';
21
+
22
+ interface ChangeContent {
23
+ property: string;
24
+ newValue: string;
25
+ newBinding: string;
26
+ }
27
+
28
+ interface ChangeSelector {
29
+ id: string;
30
+ type: string;
31
+ }
32
+
33
+ interface Change {
34
+ fileName: string;
35
+ controlId: string;
36
+ propertyName: string;
37
+ value: string;
38
+ timestamp: string;
39
+ selector: ChangeSelector;
40
+ content: ChangeContent;
41
+ creation: string;
42
+ }
43
+
44
+ type Properties<T extends object> = { [K in keyof T]-?: K extends string ? K : never }[keyof T];
45
+ /**
46
+ * Assert change for its validity. Throws error if no value found in saved changes.
47
+ *
48
+ * @param properties array of property name
49
+ * @param target object which will be checked
50
+ */
51
+ function assertProperties<T extends object>(properties: Properties<T>[], target: T): void {
52
+ for (const property of properties) {
53
+ const value = target[property];
54
+ if (value === null || value === undefined) {
55
+ throw new Error(`Invalid change, missing ${property} in the change file`);
56
+ }
57
+ }
58
+ }
59
+
60
+ /**
61
+ * Assert change for its validity. Throws error if no value found in saved changes.
62
+ *
63
+ * @param change Change object
64
+ */
65
+ function assertChange(change: Change): void {
66
+ assertProperties(['fileName', 'selector', 'content', 'creation'], change);
67
+ assertProperties(['id'], change.selector);
68
+ assertProperties(['property'], change.content);
69
+ }
70
+
71
+ /**
72
+ * Modify rta message.
73
+ *
74
+ * @param errorMessage error message to be replaced
75
+ * @param id - control id
76
+ * @param type - control type
77
+ * @returns string
78
+ */
79
+ function modifyRTAErrorMessage(errorMessage: string, id: string, type: string): string {
80
+ return errorMessage.replace('Error: Applying property changes failed:', '').replace(`${type}#${id}`, '');
81
+ }
82
+
83
+ /**
84
+ * A Class of ChangeService
85
+ */
86
+ export class ChangeService {
87
+ private savedChanges: SavedPropertyChange[] = [];
88
+ /**
89
+ *
90
+ * @param options ui5 adaptation options.
91
+ * @param ui5 facade for ui5 framework methods.
92
+ * @param selectionService selection service instance.
93
+ */
94
+ constructor(
95
+ private readonly options: UI5AdaptationOptions,
96
+ private readonly ui5: UI5Facade,
97
+ private readonly selectionService: SelectionService
98
+ ) {}
99
+
100
+ /**
101
+ *
102
+ * @param sendAction action sender function
103
+ * @param subscribe subscriber function
104
+ */
105
+ public async init(sendAction: ActionSenderFunction, subscribe: SubscribeFunction): Promise<void> {
106
+ subscribe(async (action): Promise<void> => {
107
+ if (changeProperty.match(action)) {
108
+ try {
109
+ this.selectionService.applyControlPropertyChange(
110
+ action.payload.controlId,
111
+ action.payload.propertyName
112
+ );
113
+ await applyChange(this.options, action.payload);
114
+ } catch (exception) {
115
+ // send error information
116
+ let name = '';
117
+ const id = action.payload.controlId || '';
118
+ const control = this.ui5.getControlById(id);
119
+ if (control) {
120
+ name = control.getMetadata().getName();
121
+ }
122
+ const modifiedMessage = modifyRTAErrorMessage(exception?.toString(), id, name);
123
+ const errorMessage =
124
+ modifiedMessage || `RTA Exception applying expression "${action.payload.value}"`;
125
+ const propertyChangeFailedAction = propertyChangeFailed({ ...action.payload, errorMessage });
126
+ sendAction(propertyChangeFailedAction);
127
+ }
128
+ } else if (deletePropertyChanges.match(action)) {
129
+ await this.deleteChange(action.payload.controlId, action.payload.propertyName, action.payload.fileName);
130
+ }
131
+ });
132
+
133
+ const savedChangesResponse = await fetch(FlexChangesEndPoints.changes + `?_=${Date.now()}`);
134
+ const savedChanges = await savedChangesResponse.json();
135
+ const changes = (
136
+ Object.keys(savedChanges ?? {})
137
+ .map((key): SavedPropertyChange | UnknownSavedChange | undefined => {
138
+ const change: Change = savedChanges[key];
139
+ try {
140
+ assertChange(change);
141
+ if (
142
+ [change.content.newValue, change.content.newBinding].every(
143
+ (item) => item === undefined || item === null
144
+ )
145
+ ) {
146
+ throw new Error('Invalid change, missing new value in the change file');
147
+ }
148
+ return {
149
+ type: 'saved',
150
+ kind: 'valid',
151
+ fileName: change.fileName,
152
+ controlId: change.selector.id,
153
+ propertyName: change.content.property,
154
+ value: change.content.newValue ?? change.content.newBinding,
155
+ timestamp: new Date(change.creation).getTime(),
156
+ controlName: change.selector.type.split('.').pop()
157
+ };
158
+ } catch (error) {
159
+ // Gracefully handle change files with invalid content
160
+ if (change.fileName) {
161
+ const unknownChange: UnknownSavedChange = {
162
+ type: 'saved',
163
+ kind: 'unknown',
164
+ fileName: change.fileName
165
+ };
166
+ if (change.creation) {
167
+ unknownChange.timestamp = new Date(change.creation).getTime();
168
+ }
169
+ return unknownChange;
170
+ }
171
+ return undefined;
172
+ }
173
+ })
174
+ .filter((change) => !!change) as SavedPropertyChange[]
175
+ ).sort((a, b) => b.timestamp - a.timestamp);
176
+ this.savedChanges = changes;
177
+ sendAction(
178
+ changeStackModified({
179
+ saved: changes,
180
+ pending: []
181
+ })
182
+ );
183
+ this.options.rta.attachUndoRedoStackModified(this.createOnStackChangeHandler(sendAction));
184
+ }
185
+
186
+ /**
187
+ *
188
+ * @param controlId unique identifier for a control
189
+ * @param propertyName name of the property change to be deleted
190
+ * @param fileName name of the file.
191
+ */
192
+ public async deleteChange(controlId: string, propertyName: string, fileName?: string): Promise<void> {
193
+ const filesToDelete = this.savedChanges
194
+ .filter((change) =>
195
+ fileName
196
+ ? fileName === change.fileName
197
+ : change.controlId === controlId && change.propertyName === propertyName
198
+ )
199
+ .map((change) =>
200
+ fetch(FlexChangesEndPoints.changes, {
201
+ method: 'DELETE',
202
+ headers: {
203
+ 'Content-Type': 'application/json'
204
+ },
205
+ body: JSON.stringify({ fileName: change.fileName })
206
+ })
207
+ );
208
+
209
+ await Promise.all(filesToDelete).catch((error) => Log.error(error));
210
+ }
211
+
212
+ /**
213
+ * Handler for undo/redo stack change.
214
+ *
215
+ * @param sendAction send action method
216
+ * @returns (event: sap.ui.base.Event) => void
217
+ */
218
+ private createOnStackChangeHandler(sendAction: (action: ExternalAction) => void): (event: Event) => void {
219
+ return (): void => {
220
+ const stack = this.options.rta.getCommandStack();
221
+ const allCommands = stack.getCommands();
222
+ const executedCommands = stack.getAllExecutedCommands();
223
+ const inactiveCommandCount = allCommands.length - executedCommands.length;
224
+
225
+ const activeChanges = allCommands
226
+ .map((command: BaseCommand, i): PendingPropertyChange | undefined => {
227
+ let result: PendingPropertyChange | undefined;
228
+ try {
229
+ const selector = command.getProperty('selector');
230
+ const changeType = command.getProperty('changeType');
231
+ let value = '';
232
+ switch (changeType) {
233
+ case 'propertyChange':
234
+ value = command.getProperty('newValue');
235
+ break;
236
+ case 'propertyBindingChange':
237
+ value = command.getProperty('newBinding');
238
+ break;
239
+ default:
240
+ throw new Error(`Invalid changeType ${changeType}`);
241
+ }
242
+ result = {
243
+ type: 'pending',
244
+ controlId: selector.id,
245
+ propertyName: command.getProperty('propertyName'),
246
+ isActive: i >= inactiveCommandCount,
247
+ value,
248
+ controlName: command.getElement().getMetadata().getName().split('.').pop() ?? ''
249
+ };
250
+ } catch (error) {
251
+ Log.error('Failed: ', error);
252
+ }
253
+ return result;
254
+ })
255
+ .filter((change): boolean => !!change) as PendingPropertyChange[];
256
+ sendAction(
257
+ changeStackModified({
258
+ saved: this.savedChanges,
259
+ pending: activeChanges
260
+ })
261
+ );
262
+ };
263
+ }
264
+ }
@@ -0,0 +1,201 @@
1
+ 'use strict';
2
+ sap.ui.define([
3
+ 'open/ux/preview/client/thirdparty/@sap-ux-private/control-property-editor-common',
4
+ 'sap/ui/fl/Utils',
5
+ 'sap/ui/base/DataType'
6
+ ], function (___sap_ux_private_control_property_editor_common, Utils, DataType) {
7
+ 'use strict';
8
+ const BOOLEAN_VALUE_TYPE = ___sap_ux_private_control_property_editor_common['BOOLEAN_VALUE_TYPE'];
9
+ const CHECKBOX_EDITOR_TYPE = ___sap_ux_private_control_property_editor_common['CHECKBOX_EDITOR_TYPE'];
10
+ const DROPDOWN_EDITOR_TYPE = ___sap_ux_private_control_property_editor_common['DROPDOWN_EDITOR_TYPE'];
11
+ const FLOAT_VALUE_TYPE = ___sap_ux_private_control_property_editor_common['FLOAT_VALUE_TYPE'];
12
+ const INPUT_EDITOR_TYPE = ___sap_ux_private_control_property_editor_common['INPUT_EDITOR_TYPE'];
13
+ const INTEGER_VALUE_TYPE = ___sap_ux_private_control_property_editor_common['INTEGER_VALUE_TYPE'];
14
+ const STRING_VALUE_TYPE = ___sap_ux_private_control_property_editor_common['STRING_VALUE_TYPE'];
15
+ const convertCamelCaseToPascalCase = ___sap_ux_private_control_property_editor_common['convertCamelCaseToPascalCase'];
16
+ function isPropertyEnabled(analyzedType) {
17
+ return !(analyzedType.isArray || analyzedType.primitiveType === 'any');
18
+ }
19
+ function isControlEnabled(analyzedType, hasStableId, ignore, controlOverlay) {
20
+ return (controlOverlay?.isSelectable() ?? false) && isPropertyEnabled(analyzedType) && hasStableId && !ignore;
21
+ }
22
+ function testIconPattern(name) {
23
+ const nameLc = (name || '').toLowerCase();
24
+ return nameLc.indexOf('src') >= 0 || nameLc.startsWith('icon') || nameLc.endsWith('icon');
25
+ }
26
+ function analyzePropertyType(property) {
27
+ const analyzedType = {
28
+ primitiveType: 'any',
29
+ ui5Type: null,
30
+ enumValues: null,
31
+ isArray: false
32
+ };
33
+ const propertyType = property?.getType();
34
+ const typeName = propertyType?.getName();
35
+ if (!property || !propertyType || !typeName) {
36
+ return undefined;
37
+ }
38
+ if (typeName.indexOf('[]') > 0) {
39
+ analyzedType.primitiveType = typeName.substring(0, typeName.indexOf('[]'));
40
+ analyzedType.isArray = true;
41
+ } else if ([
42
+ 'object',
43
+ 'void',
44
+ 'any'
45
+ ].includes(typeName)) {
46
+ analyzedType.primitiveType = typeName;
47
+ } else if ([
48
+ 'boolean',
49
+ 'string',
50
+ 'int',
51
+ 'float'
52
+ ].includes(typeName)) {
53
+ analyzedType.primitiveType = typeName;
54
+ } else {
55
+ const propertyDataType = DataType.getType(typeName);
56
+ if (propertyDataType && !(propertyDataType instanceof DataType)) {
57
+ return analyzedType;
58
+ }
59
+ const name = Object.getPrototypeOf(propertyDataType).getName();
60
+ if (!name) {
61
+ analyzedType.primitiveType = 'enum';
62
+ } else {
63
+ analyzedType.primitiveType = name;
64
+ }
65
+ analyzedType.ui5Type = typeName;
66
+ if (analyzedType.primitiveType === 'enum') {
67
+ analyzedType.enumValues = sap.ui.require(analyzedType.ui5Type.split('.').join('/'));
68
+ }
69
+ }
70
+ return analyzedType;
71
+ }
72
+ function normalizeObjectPropertyValue(rawValue) {
73
+ if (typeof rawValue === 'object' && rawValue instanceof Object && !Array.isArray(rawValue)) {
74
+ try {
75
+ return JSON.stringify(rawValue);
76
+ } catch (e) {
77
+ if (e instanceof Error && e.message.toLowerCase().includes('converting circular structure to json')) {
78
+ return '<Circular JSON cannot be displayed>';
79
+ }
80
+ return rawValue;
81
+ }
82
+ } else if (typeof rawValue === 'function') {
83
+ return '';
84
+ } else {
85
+ return rawValue;
86
+ }
87
+ }
88
+ function buildControlData(control, controlOverlay) {
89
+ const controlMetadata = control.getMetadata();
90
+ const selectedControlName = controlMetadata.getName();
91
+ const hasStableId = Utils.checkControlId(control);
92
+ const controlProperties = controlOverlay ? controlOverlay.getDesignTimeMetadata().getData().properties : undefined;
93
+ const allProperties = controlMetadata.getAllProperties();
94
+ const propertyNames = Object.keys(allProperties);
95
+ const properties = [];
96
+ for (const propertyName of propertyNames) {
97
+ const property = allProperties[propertyName];
98
+ const analyzedType = analyzePropertyType(property);
99
+ if (!analyzedType) {
100
+ continue;
101
+ }
102
+ let ignore = false;
103
+ if (controlProperties?.[property.name]) {
104
+ ignore = controlProperties[property.name].ignore;
105
+ }
106
+ const controlNewData = {
107
+ id: control.getId(),
108
+ name: property.name,
109
+ newValue: control.getProperty(property.name)
110
+ };
111
+ const bindingInfo = control.getBindingInfo(controlNewData.name);
112
+ if (bindingInfo?.bindingString !== undefined) {
113
+ controlNewData.newValue = bindingInfo.bindingString;
114
+ }
115
+ const isEnabled = isControlEnabled(analyzedType, hasStableId, ignore, controlOverlay);
116
+ const value = normalizeObjectPropertyValue(controlNewData.newValue);
117
+ const isIcon = testIconPattern(property.name) && selectedControlName !== 'sap.m.Image' && analyzedType.ui5Type === 'sap.ui.core.URI';
118
+ const ui5Type = analyzedType.ui5Type || undefined;
119
+ const readableName = convertCamelCaseToPascalCase(property.name);
120
+ switch (analyzedType.primitiveType) {
121
+ case 'enum': {
122
+ const values = analyzedType.enumValues ?? {};
123
+ const options = Object.keys(values).map(key => ({
124
+ key,
125
+ text: values[key]
126
+ }));
127
+ properties.push({
128
+ type: STRING_VALUE_TYPE,
129
+ editor: DROPDOWN_EDITOR_TYPE,
130
+ name: property.name,
131
+ readableName,
132
+ value: value,
133
+ isEnabled,
134
+ ui5Type,
135
+ options
136
+ });
137
+ break;
138
+ }
139
+ case 'string': {
140
+ properties.push({
141
+ type: STRING_VALUE_TYPE,
142
+ editor: INPUT_EDITOR_TYPE,
143
+ name: property.name,
144
+ readableName,
145
+ value: value,
146
+ isEnabled,
147
+ isIcon,
148
+ ui5Type
149
+ });
150
+ break;
151
+ }
152
+ case 'int': {
153
+ properties.push({
154
+ type: INTEGER_VALUE_TYPE,
155
+ editor: INPUT_EDITOR_TYPE,
156
+ name: property.name,
157
+ readableName,
158
+ value: value,
159
+ isEnabled,
160
+ ui5Type
161
+ });
162
+ break;
163
+ }
164
+ case 'float': {
165
+ properties.push({
166
+ type: FLOAT_VALUE_TYPE,
167
+ editor: INPUT_EDITOR_TYPE,
168
+ name: property.name,
169
+ readableName,
170
+ value: value,
171
+ isEnabled,
172
+ ui5Type
173
+ });
174
+ break;
175
+ }
176
+ case 'boolean': {
177
+ properties.push({
178
+ type: BOOLEAN_VALUE_TYPE,
179
+ editor: CHECKBOX_EDITOR_TYPE,
180
+ name: property.name,
181
+ readableName,
182
+ value: value,
183
+ isEnabled,
184
+ ui5Type
185
+ });
186
+ break;
187
+ }
188
+ default:
189
+ }
190
+ }
191
+ return {
192
+ id: control.getId(),
193
+ type: selectedControlName,
194
+ properties: [...properties].sort((a, b) => a.name > b.name ? 1 : -1),
195
+ name: selectedControlName
196
+ };
197
+ }
198
+ var __exports = { __esModule: true };
199
+ __exports.buildControlData = buildControlData;
200
+ return __exports;
201
+ });