@sap-ux/preview-middleware 0.6.3 → 0.7.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 (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 +4 -3
  39. package/templates/flp/editor.html +11 -15
  40. package/templates/flp/sandbox.html +2 -1
  41. package/templates/flp/workspaceConnector.js +0 -76
@@ -30,6 +30,7 @@ export interface TemplateConfig {
30
30
  writeConnector: string;
31
31
  custom: boolean;
32
32
  }[];
33
+ bootstrapOptions: string;
33
34
  resources: Record<string, string>;
34
35
  };
35
36
  flex?: {
package/dist/base/flp.js CHANGED
@@ -15,6 +15,15 @@ const fs_1 = require("fs");
15
15
  const path_1 = require("path");
16
16
  const express_1 = require("express");
17
17
  const flex_1 = require("./flex");
18
+ const DEVELOPER_MODE_CONFIG = new Map([
19
+ // Run application in design time mode
20
+ // Adds bindingString to BindingInfo objects. Required to create and read PropertyBinding changes
21
+ ['xx-designMode', 'true'],
22
+ // In design mode, the controller code will not be executed by default, which is not desired in our case, so we suppress the deactivation
23
+ ['xx-suppressDeactivationOfControllerCode', 'true'],
24
+ // Make sure that XML preprocessing results are correctly invalidated
25
+ ['xx-viewCache', 'false']
26
+ ]);
18
27
  /**
19
28
  * Default theme
20
29
  */
@@ -93,7 +102,8 @@ class FlpSandbox {
93
102
  libs: Object.keys((_g = (_f = (_e = manifest['sap.ui5']) === null || _e === void 0 ? void 0 : _e.dependencies) === null || _f === void 0 ? void 0 : _f.libs) !== null && _g !== void 0 ? _g : {}).join(','),
94
103
  theme: ui5Theme,
95
104
  flex,
96
- resources: Object.assign(Object.assign({}, resources), { [PREVIEW_URL.client.ns]: PREVIEW_URL.client.url })
105
+ resources: Object.assign(Object.assign({}, resources), { [PREVIEW_URL.client.ns]: PREVIEW_URL.client.url }),
106
+ bootstrapOptions: ''
97
107
  },
98
108
  locateReuseLibsScript: (_h = this.config.libs) !== null && _h !== void 0 ? _h : (yield this.hasLocateReuseLibsScript())
99
109
  };
@@ -107,6 +117,7 @@ class FlpSandbox {
107
117
  if (this.rta) {
108
118
  (_k = (_l = this.rta).options) !== null && _k !== void 0 ? _k : (_l.options = {});
109
119
  this.rta.options.baseId = componentId !== null && componentId !== void 0 ? componentId : manifest['sap.app'].id;
120
+ this.rta.options.generator = 'changeUtils: SAPFioriTools-propertyEditor';
110
121
  this.addEditorRoutes(this.rta);
111
122
  }
112
123
  this.addRoutesForAdditionalApps();
@@ -121,22 +132,33 @@ class FlpSandbox {
121
132
  */
122
133
  addEditorRoutes(rta) {
123
134
  var _a;
135
+ const cpe = (0, path_1.dirname)(require.resolve('@sap-ux/control-property-editor-sources'));
124
136
  for (const editor of rta.editors) {
125
137
  let previewUrl = editor.path;
126
138
  if (editor.developerMode) {
127
139
  previewUrl = `${previewUrl}.inner.html`;
128
140
  (_a = editor.pluginScript) !== null && _a !== void 0 ? _a : (editor.pluginScript = 'open/ux/preview/client/cpe/init');
129
141
  this.router.get(editor.path, ((_req, res) => __awaiter(this, void 0, void 0, function* () {
142
+ var _b, _c;
130
143
  const template = (0, fs_1.readFileSync)((0, path_1.join)(__dirname, '../../templates/flp/editor.html'), 'utf-8');
131
144
  const html = (0, ejs_1.render)(template, {
132
- previewUrl: `${previewUrl}?sap-ui-xx-viewCache=false&fiori-tools-rta-mode=forAdaptation&sap-ui-rta-skip-flex-validation=true&sap-ui-xx-condense-changes=true#${this.config.intent.object}-${this.config.intent.action}`
145
+ previewUrl: `${previewUrl}?sap-ui-xx-viewCache=false&fiori-tools-rta-mode=forAdaptation&sap-ui-rta-skip-flex-validation=true&sap-ui-xx-condense-changes=true#${this.config.intent.object}-${this.config.intent.action}`,
146
+ telemetry: (_c = (_b = rta.options) === null || _b === void 0 ? void 0 : _b.telemetry) !== null && _c !== void 0 ? _c : false
133
147
  });
134
148
  res.status(200).contentType('html').send(html);
135
149
  })));
150
+ let path = (0, path_1.dirname)(editor.path);
151
+ if (!path.endsWith('/')) {
152
+ path = `${path}/`;
153
+ }
154
+ this.router.use(`${path}editor`, (0, express_1.static)(cpe));
136
155
  }
137
156
  this.router.get(previewUrl, ((_req, res) => __awaiter(this, void 0, void 0, function* () {
138
157
  const config = Object.assign({}, this.templateConfig);
139
158
  config.flex = Object.assign(Object.assign({ layer: rta.layer }, rta.options), { developerMode: editor.developerMode === true, pluginScript: editor.pluginScript });
159
+ if (editor.developerMode === true) {
160
+ config.ui5.bootstrapOptions = serializeUi5Configuration(DEVELOPER_MODE_CONFIG);
161
+ }
140
162
  const template = (0, fs_1.readFileSync)((0, path_1.join)(__dirname, '../../templates/flp/sandbox.html'), 'utf-8');
141
163
  const html = (0, ejs_1.render)(template, config);
142
164
  res.status(200).contentType('html').send(html);
@@ -198,12 +220,7 @@ class FlpSandbox {
198
220
  * @returns template configuration for flex.
199
221
  */
200
222
  createFlexHandler() {
201
- const workspaceConnectorPath = '/preview/WorkspaceConnector';
202
- this.router.get(`/resources${workspaceConnectorPath}.js`, (_req, res) => {
203
- res.status(200)
204
- .contentType('text/javascript')
205
- .send((0, fs_1.readFileSync)((0, path_1.join)(__dirname, '../../templates/flp/workspaceConnector.js'), 'utf-8'));
206
- });
223
+ const workspaceConnectorPath = 'open/ux/preview/client/flp/WorkspaceConnector';
207
224
  const api = `${PREVIEW_URL.api}/changes`;
208
225
  this.router.use(api, (0, express_1.json)());
209
226
  this.router.get(api, ((_req, res) => __awaiter(this, void 0, void 0, function* () {
@@ -271,4 +288,28 @@ class FlpSandbox {
271
288
  }
272
289
  }
273
290
  exports.FlpSandbox = FlpSandbox;
291
+ /**
292
+ * Creates an attribute string that can be added to an HTML element.
293
+ *
294
+ * @param attributes map with attributes and their values
295
+ * @param indent indentation thats inserted before each attribute
296
+ * @param prefix value that should be added at the start of to all attribute names
297
+ * @returns attribute string
298
+ */
299
+ function serializeDataAttributes(attributes, indent = '', prefix = 'data') {
300
+ return [...attributes.entries()]
301
+ .map(([name, value]) => {
302
+ return `${indent}${prefix}-${name}="${value}"`;
303
+ })
304
+ .join('\n');
305
+ }
306
+ /**
307
+ * Creates an attribute string that can be added to bootstrap script in a HTML file.
308
+ *
309
+ * @param config ui5 configuration options
310
+ * @returns attribute string
311
+ */
312
+ function serializeUi5Configuration(config) {
313
+ return '\n' + serializeDataAttributes(config, ' ', 'data-sap-ui');
314
+ }
274
315
  //# sourceMappingURL=flp.js.map
@@ -11,7 +11,6 @@ sap.ui.define(["sap/m/MessageToast", "sap/ui/core/library", "sap/ui/core/mvc/Con
11
11
  const ControlUtils = _interopRequireDefault(__ControlUtils);
12
12
  const CommandExecutor = _interopRequireDefault(__CommandExecutor);
13
13
  const getFragments = ___api_handler["getFragments"];
14
- const getManifestAppdescr = ___api_handler["getManifestAppdescr"];
15
14
  const writeFragment = ___api_handler["writeFragment"];
16
15
  /**
17
16
  * @namespace open.ux.preview.client.adp.controllers
@@ -212,32 +211,7 @@ sap.ui.define(["sap/m/MessageToast", "sap/ui/core/library", "sap/ui/core/mvc/Con
212
211
  index,
213
212
  targetAggregation
214
213
  } = fragmentData;
215
- let manifest;
216
- try {
217
- manifest = await getManifestAppdescr();
218
- if (!manifest) {
219
- // Highly unlikely since adaptation projects are required to have manifest.appdescr_variant
220
- throw new Error('Could not retrieve manifest');
221
- }
222
- } catch (e) {
223
- MessageToast.show(e.message);
224
- throw new Error(e.message);
225
- }
226
- const {
227
- id,
228
- reference,
229
- namespace,
230
- layer
231
- } = manifest;
232
- const flexSettings = {
233
- baseId: reference,
234
- developerMode: true,
235
- layer: layer,
236
- namespace: namespace,
237
- projectId: id,
238
- rootNamespace: namespace.split('/').slice(0, 2).join('/'),
239
- scenario: undefined
240
- };
214
+ const flexSettings = this.rta.getFlexSettings();
241
215
  const overlay = OverlayRegistry.getOverlay(this.runtimeControl);
242
216
  const designMetadata = overlay.getDesignTimeMetadata();
243
217
  const modifiedValue = {
@@ -27,7 +27,7 @@ import type ElementOverlay from 'sap/ui/dt/ElementOverlay';
27
27
 
28
28
  import ControlUtils from '../control-utils';
29
29
  import CommandExecutor from '../command-executor';
30
- import { ManifestAppdescr, getFragments, getManifestAppdescr, writeFragment } from '../api-handler';
30
+ import { getFragments, writeFragment } from '../api-handler';
31
31
 
32
32
  interface CreateFragmentProps {
33
33
  fragmentName: string;
@@ -332,30 +332,8 @@ export default class AddFragment extends Controller {
332
332
  */
333
333
  private async createFragmentChange(fragmentData: CreateFragmentProps) {
334
334
  const { fragmentName, index, targetAggregation } = fragmentData;
335
- let manifest: ManifestAppdescr;
336
- try {
337
- manifest = await getManifestAppdescr();
338
335
 
339
- if (!manifest) {
340
- // Highly unlikely since adaptation projects are required to have manifest.appdescr_variant
341
- throw new Error('Could not retrieve manifest');
342
- }
343
- } catch (e) {
344
- MessageToast.show(e.message);
345
- throw new Error(e.message);
346
- }
347
-
348
- const { id, reference, namespace, layer } = manifest;
349
-
350
- const flexSettings = {
351
- baseId: reference,
352
- developerMode: true,
353
- layer: layer,
354
- namespace: namespace,
355
- projectId: id,
356
- rootNamespace: namespace.split('/').slice(0, 2).join('/'),
357
- scenario: undefined
358
- };
336
+ const flexSettings = this.rta.getFlexSettings();
359
337
 
360
338
  const overlay = OverlayRegistry.getOverlay(this.runtimeControl as UI5Element);
361
339
  const designMetadata = overlay.getDesignTimeMetadata();
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+
3
+ sap.ui.define(["sap/ui/rta/command/CommandFactory"], function (CommandFactory) {
4
+ "use strict";
5
+
6
+ /**
7
+ * Function to check a give value is a binding expression.
8
+ *
9
+ * @param value value to be checked.
10
+ * @returns boolean
11
+ */
12
+ function isBindingExpression(value) {
13
+ return value.includes('{') && value.includes('}');
14
+ }
15
+
16
+ /**
17
+ *
18
+ * @param options UI5 adaptation options
19
+ * @param change changed property of a control
20
+ */
21
+ async function applyChange(options, change) {
22
+ const {
23
+ rta
24
+ } = options;
25
+ const modifiedControl = sap.ui.getCore().byId(change.controlId);
26
+ if (!modifiedControl) {
27
+ return;
28
+ }
29
+ const flexSettings = rta.getFlexSettings();
30
+ const changeType = typeof change.value === 'string' && isBindingExpression(change.value) ? 'BindProperty' : 'Property';
31
+ const property = typeof change.value === 'string' && isBindingExpression(change.value) ? 'newBinding' : 'newValue';
32
+ const modifiedValue = {
33
+ generator: flexSettings.generator,
34
+ propertyName: change.propertyName,
35
+ [property]: change.value
36
+ };
37
+ const command = await CommandFactory.getCommandFor(modifiedControl, changeType, modifiedValue, null, flexSettings);
38
+ await rta.getCommandStack().pushAndExecute(command);
39
+ }
40
+ var __exports = {
41
+ __esModule: true
42
+ };
43
+ __exports.applyChange = applyChange;
44
+ return __exports;
45
+ });
46
+ //# sourceMappingURL=flex-change.js.map
@@ -0,0 +1,49 @@
1
+ import type FlexCommand from 'sap/ui/rta/command/FlexCommand';
2
+ import CommandFactory from 'sap/ui/rta/command/CommandFactory';
3
+ import type { PropertyChange } from '@sap-ux-private/control-property-editor-common';
4
+ import type { UI5AdaptationOptions } from '../types';
5
+
6
+ /**
7
+ * Function to check a give value is a binding expression.
8
+ *
9
+ * @param value value to be checked.
10
+ * @returns boolean
11
+ */
12
+ function isBindingExpression(value: string): boolean {
13
+ return value.includes('{') && value.includes('}');
14
+ }
15
+
16
+ /**
17
+ *
18
+ * @param options UI5 adaptation options
19
+ * @param change changed property of a control
20
+ */
21
+ export async function applyChange(options: UI5AdaptationOptions, change: PropertyChange): Promise<void> {
22
+ const { rta } = options;
23
+ const modifiedControl = sap.ui.getCore().byId(change.controlId);
24
+ if (!modifiedControl) {
25
+ return;
26
+ }
27
+
28
+ const flexSettings = rta.getFlexSettings();
29
+
30
+ const changeType =
31
+ typeof change.value === 'string' && isBindingExpression(change.value) ? 'BindProperty' : 'Property';
32
+
33
+ const property = typeof change.value === 'string' && isBindingExpression(change.value) ? 'newBinding' : 'newValue';
34
+ const modifiedValue = {
35
+ generator: flexSettings.generator,
36
+ propertyName: change.propertyName,
37
+ [property]: change.value
38
+ };
39
+
40
+ const command = await CommandFactory.getCommandFor<FlexCommand>(
41
+ modifiedControl,
42
+ changeType,
43
+ modifiedValue,
44
+ null,
45
+ flexSettings
46
+ );
47
+
48
+ await rta.getCommandStack().pushAndExecute(command);
49
+ }
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+
3
+ sap.ui.define(["./service"], function (___service) {
4
+ "use strict";
5
+
6
+ var __exports = {
7
+ __esModule: true
8
+ };
9
+ __exports.ChangeService = ___service.ChangeService;
10
+ return __exports;
11
+ });
12
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ export { ChangeService } from './service';
@@ -0,0 +1,164 @@
1
+ 'use strict';
2
+ sap.ui.define([
3
+ 'open/ux/preview/client/thirdparty/@sap-ux-private/control-property-editor-common',
4
+ './flex-change',
5
+ 'sap/base/Log'
6
+ ], function (___sap_ux_private_control_property_editor_common, ___flex_change, Log) {
7
+ 'use strict';
8
+ const changeProperty = ___sap_ux_private_control_property_editor_common['changeProperty'];
9
+ const changeStackModified = ___sap_ux_private_control_property_editor_common['changeStackModified'];
10
+ const deletePropertyChanges = ___sap_ux_private_control_property_editor_common['deletePropertyChanges'];
11
+ const propertyChangeFailed = ___sap_ux_private_control_property_editor_common['propertyChangeFailed'];
12
+ const FlexChangesEndPoints = ___sap_ux_private_control_property_editor_common['FlexChangesEndPoints'];
13
+ const applyChange = ___flex_change['applyChange'];
14
+ function assertProperties(properties, target) {
15
+ for (const property of properties) {
16
+ const value = target[property];
17
+ if (value === null || value === undefined) {
18
+ throw new Error(`Invalid change, missing ${ property } in the change file`);
19
+ }
20
+ }
21
+ }
22
+ function assertChange(change) {
23
+ assertProperties([
24
+ 'fileName',
25
+ 'selector',
26
+ 'content',
27
+ 'creation'
28
+ ], change);
29
+ assertProperties(['id'], change.selector);
30
+ assertProperties(['property'], change.content);
31
+ }
32
+ function modifyRTAErrorMessage(errorMessage, id, type) {
33
+ return errorMessage.replace('Error: Applying property changes failed:', '').replace(`${ type }#${ id }`, '');
34
+ }
35
+ class ChangeService {
36
+ savedChanges = [];
37
+ constructor(options, ui5, selectionService) {
38
+ this.options = options;
39
+ this.ui5 = ui5;
40
+ this.selectionService = selectionService;
41
+ }
42
+ async init(sendAction, subscribe) {
43
+ subscribe(async action => {
44
+ if (changeProperty.match(action)) {
45
+ try {
46
+ this.selectionService.applyControlPropertyChange(action.payload.controlId, action.payload.propertyName);
47
+ await applyChange(this.options, action.payload);
48
+ } catch (exception) {
49
+ let name = '';
50
+ const id = action.payload.controlId || '';
51
+ const control = this.ui5.getControlById(id);
52
+ if (control) {
53
+ name = control.getMetadata().getName();
54
+ }
55
+ const modifiedMessage = modifyRTAErrorMessage(exception?.toString(), id, name);
56
+ const errorMessage = modifiedMessage || `RTA Exception applying expression "${ action.payload.value }"`;
57
+ const propertyChangeFailedAction = propertyChangeFailed({
58
+ ...action.payload,
59
+ errorMessage
60
+ });
61
+ sendAction(propertyChangeFailedAction);
62
+ }
63
+ } else if (deletePropertyChanges.match(action)) {
64
+ await this.deleteChange(action.payload.controlId, action.payload.propertyName, action.payload.fileName);
65
+ }
66
+ });
67
+ const savedChangesResponse = await fetch(FlexChangesEndPoints.changes + `?_=${ Date.now() }`);
68
+ const savedChanges = await savedChangesResponse.json();
69
+ const changes = Object.keys(savedChanges ?? {}).map(key => {
70
+ const change = savedChanges[key];
71
+ try {
72
+ assertChange(change);
73
+ if ([
74
+ change.content.newValue,
75
+ change.content.newBinding
76
+ ].every(item => item === undefined || item === null)) {
77
+ throw new Error('Invalid change, missing new value in the change file');
78
+ }
79
+ return {
80
+ type: 'saved',
81
+ kind: 'valid',
82
+ fileName: change.fileName,
83
+ controlId: change.selector.id,
84
+ propertyName: change.content.property,
85
+ value: change.content.newValue ?? change.content.newBinding,
86
+ timestamp: new Date(change.creation).getTime(),
87
+ controlName: change.selector.type.split('.').pop()
88
+ };
89
+ } catch (error) {
90
+ if (change.fileName) {
91
+ const unknownChange = {
92
+ type: 'saved',
93
+ kind: 'unknown',
94
+ fileName: change.fileName
95
+ };
96
+ if (change.creation) {
97
+ unknownChange.timestamp = new Date(change.creation).getTime();
98
+ }
99
+ return unknownChange;
100
+ }
101
+ return undefined;
102
+ }
103
+ }).filter(change => !!change).sort((a, b) => b.timestamp - a.timestamp);
104
+ this.savedChanges = changes;
105
+ sendAction(changeStackModified({
106
+ saved: changes,
107
+ pending: []
108
+ }));
109
+ this.options.rta.attachUndoRedoStackModified(this.createOnStackChangeHandler(sendAction));
110
+ }
111
+ async deleteChange(controlId, propertyName, fileName) {
112
+ const filesToDelete = this.savedChanges.filter(change => fileName ? fileName === change.fileName : change.controlId === controlId && change.propertyName === propertyName).map(change => fetch(FlexChangesEndPoints.changes, {
113
+ method: 'DELETE',
114
+ headers: { 'Content-Type': 'application/json' },
115
+ body: JSON.stringify({ fileName: change.fileName })
116
+ }));
117
+ await Promise.all(filesToDelete).catch(error => Log.error(error));
118
+ }
119
+ createOnStackChangeHandler(sendAction) {
120
+ return () => {
121
+ const stack = this.options.rta.getCommandStack();
122
+ const allCommands = stack.getCommands();
123
+ const executedCommands = stack.getAllExecutedCommands();
124
+ const inactiveCommandCount = allCommands.length - executedCommands.length;
125
+ const activeChanges = allCommands.map((command, i) => {
126
+ let result;
127
+ try {
128
+ const selector = command.getProperty('selector');
129
+ const changeType = command.getProperty('changeType');
130
+ let value = '';
131
+ switch (changeType) {
132
+ case 'propertyChange':
133
+ value = command.getProperty('newValue');
134
+ break;
135
+ case 'propertyBindingChange':
136
+ value = command.getProperty('newBinding');
137
+ break;
138
+ default:
139
+ throw new Error(`Invalid changeType ${ changeType }`);
140
+ }
141
+ result = {
142
+ type: 'pending',
143
+ controlId: selector.id,
144
+ propertyName: command.getProperty('propertyName'),
145
+ isActive: i >= inactiveCommandCount,
146
+ value,
147
+ controlName: command.getElement().getMetadata().getName().split('.').pop() ?? ''
148
+ };
149
+ } catch (error) {
150
+ Log.error('Failed: ', error);
151
+ }
152
+ return result;
153
+ }).filter(change => !!change);
154
+ sendAction(changeStackModified({
155
+ saved: this.savedChanges,
156
+ pending: activeChanges
157
+ }));
158
+ };
159
+ }
160
+ }
161
+ var __exports = { __esModule: true };
162
+ __exports.ChangeService = ChangeService;
163
+ return __exports;
164
+ });