@sap-ux/preview-middleware 0.16.92 → 0.16.94

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/README.md CHANGED
@@ -12,7 +12,7 @@ When this middleware is used together with the `reload-middleware`, then the ord
12
12
  afterMiddleware: reload-middleware
13
13
  ```
14
14
 
15
- ## Configuration Options
15
+ ## [Configuration Options](#configuration-options)
16
16
  | Option | Type | Default Value | Description |
17
17
  | ---------------------- | --------- | ---------------- |-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
18
18
  | `flp` | | | Optional configuration object for the local Fiori launchpad |
@@ -32,7 +32,7 @@ When this middleware is used together with the `reload-middleware`, then the ord
32
32
  | `test` | `array` | `undefined` | Optional list of configurations for automated testing. |
33
33
  | `debug` | `boolean` | `false` | Enables debug output |
34
34
 
35
- ### `flp.apps`
35
+ ### [`flp.apps`](#configuration-option-flpapps)
36
36
  Array of additional application configurations:
37
37
  | Option | Type | Default Value | Description |
38
38
  | ------------------------ | -------- | -------------- | ------------------------------------------------------------------------------------------------------------- |
@@ -42,7 +42,7 @@ Array of additional application configurations:
42
42
  | `intent.object` | `string` | `(calculated)` | Optional intent object, if it is not provided then it will be calculated based on the application id |
43
43
  | `intent.action` | `string` | `preview` | Optional intent action |
44
44
 
45
- ### `adp.target`
45
+ ### [`adp.target`](#configuration-option-adptarget)
46
46
  | Option | Type | Description |
47
47
  | ------------- | ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------- |
48
48
  | `url` | `string` mandatory (local) | Mandatory URL pointing to the backend system. *Not required if destination is provided and the proxy is running SAP Business Application Studio |
@@ -50,13 +50,13 @@ Array of additional application configurations:
50
50
  | `client` | `string` optional | sap-client parameter |
51
51
  | `scp` | `boolean` optional | If set to true the proxy will execute the required OAuth routine for the ABAP environment on SAP BTP |
52
52
 
53
- ### `rta.editors`
53
+ ### [`rta.editors`](#configuration-option-rtaeditors)
54
54
  | Option | Type | Description |
55
55
  | --------------- | -------------------| -----------------------------------------------------------------------------------------------|
56
56
  | `path` | `string` mandatory | The mount point to be used for the editor. |
57
57
  | `developerMode` | `boolean` optional | Enables/disables the runtime adaptation developer mode (only supported for adaptation projects) |
58
58
 
59
- ### `test`
59
+ ### [`test`](#configuration-option-test)
60
60
  | Option | Type | Description |
61
61
  | --------------- | -------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
62
62
  | `framework` | `string` mandatory | Currently `OPA5`, `QUnit` (only QUnit 2.3.2 provided as third-party module via [OpenUI5](https://github.com/SAP/openui5/blob/master/THIRDPARTY.txt)/SAPUI5) and `Testsuite` are supported. `Testsuite` will generate a testsuite for all configured frameworks that can be be used with a test runner (like e.g. karma) |
@@ -65,11 +65,11 @@ Array of additional application configurations:
65
65
  | `pattern` | `string` optional | Optional glob pattern to find the tests. By default `/test/**/*Journey.*` is used for `OPA5` and `/test/**/*Test.*` is used for `QUnit` (n.a. for `Testsuite`) |
66
66
 
67
67
 
68
- ## Usage
68
+ ## [Usage](#usage)
69
69
  The middleware can be used without configuration. However, since the middleware intercepts a few requests that might otherwise be handled by a different middleware, it is strongly recommended to run other file serving middlewares after the `preview-middleware` e.g. `backend-proxy-middleware` and `ui5-proxy-middleware` (and the corresponding middlewares in the `@sap/ux-ui5-tooling`).
70
70
  Example: [./test/fixtures/simple-app/ui5.yaml](./test/fixtures/simple-app/ui5.yaml)
71
71
 
72
- ### Minimal Configuration
72
+ ### [Minimal Configuration](#minimal-configuration)
73
73
  With no configuration provided, the local FLP will be available at `/test/flp.html` and the log level is `info`.
74
74
  ```Yaml
75
75
  server:
@@ -78,7 +78,7 @@ server:
78
78
  afterMiddleware: compression
79
79
  ```
80
80
 
81
- ### Different Path and Debugging enabled
81
+ ### [Different Path and Debugging enabled](#different-path-and-debugging-enabled)
82
82
  With this configuration, the local FLP will be available at `/test/myFLP.html` and the log level is `debug`.
83
83
  ```Yaml
84
84
  server:
@@ -91,7 +91,7 @@ server:
91
91
  debug: true
92
92
  ```
93
93
 
94
- ### Additional Applications
94
+ ### [Additional Applications](#additional-applications)
95
95
  If you want to test cross application navigation, then you can add additional applications into the local FLP.
96
96
  With this configuration, an application that is locally available in `../local-folder` will be available at `/apps/other-app` and will also be added as tile to the local FLP as well as one of the UI5 sample apps will be bound to the intent `TheOther-preview`.
97
97
  ```Yaml
@@ -113,7 +113,7 @@ server:
113
113
  action: preview
114
114
  ```
115
115
 
116
- ### Runtime Adaptation Support
116
+ ### [Runtime Adaptation Support](#runtime-adaptation-support)
117
117
  If you want to create variants as part of your application, then you can create an additional mount point allowing to create and edit variants.
118
118
  ```Yaml
119
119
  server:
@@ -131,7 +131,7 @@ This mount path can be used with a run script that looks as follows.
131
131
  "start-variants-management": "ui5 serve --open \"test/variant-editor.html.html#app-preview\""
132
132
  ```
133
133
 
134
- ### Test Suites
134
+ ### [Test Suites](#test-suites)
135
135
  If you want to also generate generic test suites and test runners for QUnit or OPA5 tests then you can use the following minimum configurations
136
136
  ```Yaml
137
137
  server:
@@ -145,7 +145,7 @@ server:
145
145
  - framework: OPA5
146
146
  ```
147
147
 
148
- ### Adaptation Project
148
+ ### [Adaptation Project](#adaptation-project)
149
149
  If you want to use the middleware in an adaption project, the additional `adp` object needs to be configured. This example would preview a local adaptation project merged with its reference application from the target system at `http://sap.example` and it will ignore certification validation errors. For adaptation projects, it is also recommended to add the `rta` configuration allowing to edit the project.
150
150
  ```Yaml
151
151
  server:
@@ -170,7 +170,7 @@ When the middleware is used in an adaptation project together with a middleware
170
170
  afterMiddleware: preview-middleware
171
171
  ```
172
172
 
173
- ### Programmatic Usage
173
+ ### [Programmatic Usage](#programmatic-usage)
174
174
  Alternatively you can use the underlying middleware fuction programmatically, e.g. for the case when you want to incorporate the `preview-middleware` functionality in your own middleware.
175
175
 
176
176
  ```typescript
@@ -4,11 +4,26 @@ sap.ui.define([
4
4
  './flex-change',
5
5
  'sap/base/Log',
6
6
  '../rta-service',
7
+ 'sap/ui/core/util/reflection/JsControlTreeModifier',
7
8
  '../../utils/error',
9
+ '../../utils/version',
8
10
  'sap/m/MessageToast',
9
11
  '../../i18n'
10
- ], function (___sap_ux_private_control_property_editor_common, ___flex_change, Log, ___rta_service, ____utils_error, MessageToast, ____i18n) {
12
+ ], function (___sap_ux_private_control_property_editor_common, ___flex_change, Log, ___rta_service, JsControlTreeModifier, ____utils_error, ____utils_version, MessageToast, ____i18n) {
11
13
  'use strict';
14
+ function __ui5_require_async(path) {
15
+ return new Promise(function (resolve, reject) {
16
+ sap.ui.require([path], function (module) {
17
+ if (!(module && module.__esModule)) {
18
+ module = module === null || !(typeof module === 'object' && path.endsWith('/library')) ? { default: module } : module;
19
+ Object.defineProperty(module, '__esModule', { value: true });
20
+ }
21
+ resolve(module);
22
+ }, function (err) {
23
+ reject(err);
24
+ });
25
+ });
26
+ }
12
27
  const changeProperty = ___sap_ux_private_control_property_editor_common['changeProperty'];
13
28
  const changeStackModified = ___sap_ux_private_control_property_editor_common['changeStackModified'];
14
29
  const deletePropertyChanges = ___sap_ux_private_control_property_editor_common['deletePropertyChanges'];
@@ -20,6 +35,8 @@ sap.ui.define([
20
35
  const applyChange = ___flex_change['applyChange'];
21
36
  const modeAndStackChangeHandler = ___rta_service['modeAndStackChangeHandler'];
22
37
  const getError = ____utils_error['getError'];
38
+ const isLowerThanMinimalUi5Version = ____utils_version['isLowerThanMinimalUi5Version'];
39
+ const getUi5Version = ____utils_version['getUi5Version'];
23
40
  const getTextBundle = ____i18n['getTextBundle'];
24
41
  function assertProperties(properties, target) {
25
42
  for (const property of properties) {
@@ -45,6 +62,9 @@ sap.ui.define([
45
62
  class ChangeService {
46
63
  savedChanges = [];
47
64
  changesRequiringReload = 0;
65
+ pendingChanges = [];
66
+ changedFiles = {};
67
+ eventStack = [];
48
68
  constructor(options, selectionService) {
49
69
  this.options = options;
50
70
  this.selectionService = selectionService;
@@ -86,18 +106,21 @@ sap.ui.define([
86
106
  this.options.rta.attachUndoRedoStackModified(this.createOnStackChangeHandler());
87
107
  }
88
108
  updateStack() {
89
- let pendingChanges = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
90
109
  this.sendAction(changeStackModified({
91
- saved: this.savedChanges,
92
- pending: pendingChanges
110
+ saved: this.savedChanges ?? [],
111
+ pending: this.pendingChanges ?? []
93
112
  }));
94
113
  }
95
114
  async fetchSavedChanges() {
115
+ this.changedFiles = {};
96
116
  const savedChangesResponse = await fetch(FlexChangesEndPoints.changes + `?_=${ Date.now() }`);
97
117
  const savedChanges = await savedChangesResponse.json();
98
- const changes = Object.keys(savedChanges ?? {}).map(key => {
118
+ const changes = (await Promise.all(Object.keys(savedChanges ?? {}).map(async key => {
99
119
  const change = savedChanges[key];
120
+ let selectorId;
100
121
  try {
122
+ const flexObject = await this.getFlexObject(change);
123
+ selectorId = await this.getControlIdByChange(flexObject);
101
124
  assertChange(change);
102
125
  if ([
103
126
  change.content.newValue,
@@ -108,11 +131,12 @@ sap.ui.define([
108
131
  if (change.changeType !== 'propertyChange' && change.changeType !== 'propertyBindingChange') {
109
132
  throw new Error('Unknown Change Type');
110
133
  }
134
+ this.changedFiles[change.fileName] = change;
111
135
  return {
112
136
  type: 'saved',
113
137
  kind: 'property',
114
138
  fileName: change.fileName,
115
- controlId: change.selector.id,
139
+ controlId: selectorId,
116
140
  propertyName: change.content.property,
117
141
  value: change.content.newValue ?? change.content.newBinding,
118
142
  timestamp: new Date(change.creation).getTime(),
@@ -121,25 +145,44 @@ sap.ui.define([
121
145
  };
122
146
  } catch (error) {
123
147
  if (change.fileName) {
148
+ this.changedFiles[change.fileName] = change;
124
149
  const unknownChange = {
125
150
  type: 'saved',
126
151
  kind: 'unknown',
127
152
  changeType: change.changeType,
128
153
  fileName: change.fileName,
129
- controlId: change.selector?.id
154
+ timestamp: new Date(change.creation).getTime()
130
155
  };
131
156
  if (change.creation) {
132
157
  unknownChange.timestamp = new Date(change.creation).getTime();
133
158
  }
159
+ if (selectorId) {
160
+ const controlChange = {
161
+ ...unknownChange,
162
+ kind: 'control',
163
+ controlId: selectorId
164
+ };
165
+ return controlChange;
166
+ }
134
167
  return unknownChange;
135
168
  }
136
169
  return undefined;
137
170
  }
138
- }).filter(change => !!change).sort((a, b) => b.timestamp - a.timestamp);
171
+ }))).filter(change => !!change).sort((a, b) => b.timestamp - a.timestamp);
139
172
  this.savedChanges = changes;
140
173
  }
141
174
  async deleteChange(controlId, propertyName, fileName) {
142
- const filesToDelete = this.savedChanges.filter(change => fileName ? fileName === change.fileName : change.controlId === controlId && change.propertyName === propertyName).map(change => fetch(FlexChangesEndPoints.changes, {
175
+ const filesToDelete = this.savedChanges.filter(change => {
176
+ if (fileName) {
177
+ return fileName === change.fileName;
178
+ }
179
+ if (change.kind === 'property') {
180
+ return change.controlId === controlId && change.propertyName === propertyName;
181
+ }
182
+ if (change.kind === 'control') {
183
+ return change.controlId === controlId;
184
+ }
185
+ }).map(change => fetch(FlexChangesEndPoints.changes, {
143
186
  method: 'DELETE',
144
187
  headers: { 'Content-Type': 'application/json' },
145
188
  body: JSON.stringify({ fileName: change.fileName })
@@ -150,53 +193,61 @@ sap.ui.define([
150
193
  }
151
194
  createOnStackChangeHandler() {
152
195
  const handleStackChange = modeAndStackChangeHandler(this.sendAction, this.options.rta);
153
- return async () => {
196
+ return async event => {
197
+ const pendingChanges = [];
198
+ this.eventStack.push(event);
154
199
  const stack = this.options.rta.getCommandStack();
155
200
  const allCommands = stack.getCommands();
156
201
  const executedCommands = stack.getAllExecutedCommands();
157
202
  const inactiveCommandCount = allCommands.length - executedCommands.length;
158
- let activeChanges = [];
159
- allCommands.forEach((command, i) => {
203
+ let i, command;
204
+ for ([i, command] of allCommands.entries()) {
160
205
  try {
161
206
  if (typeof command.getCommands === 'function') {
162
207
  const subCommands = command.getCommands();
163
- subCommands.forEach(subCommand => {
164
- const pendingChange = this.prepareChangeType(subCommand, inactiveCommandCount, i);
165
- if (pendingChange) {
166
- activeChanges.push(pendingChange);
167
- }
168
- });
169
- } else {
170
- const pendingChange = this.prepareChangeType(command, inactiveCommandCount, i);
171
- if (pendingChange) {
172
- activeChanges.push(pendingChange);
208
+ for (const subCommand of subCommands) {
209
+ await this.handleCommand(subCommand, inactiveCommandCount, i, pendingChanges);
173
210
  }
211
+ } else {
212
+ await this.handleCommand(command, inactiveCommandCount, i, pendingChanges);
174
213
  }
175
214
  } catch (error) {
176
215
  Log.error('CPE: Change creation Failed', getError(error));
177
216
  }
178
- });
179
- activeChanges = activeChanges.filter(change => !!change);
180
- const changesRequiringReload = activeChanges.reduce((sum, change) => change.changeType === 'appdescr_fe_changePageConfiguration' ? sum + 1 : sum, 0);
181
- if (changesRequiringReload > this.changesRequiringReload) {
182
- const resourceBundle = await getTextBundle();
183
- MessageToast.show(resourceBundle.getText('CPE_CHANGES_VISIBLE_AFTER_SAVE_AND_RELOAD_MESSAGE'), { duration: 8000 });
184
- this.sendAction(setApplicationRequiresReload(changesRequiringReload > 0));
185
217
  }
186
- this.changesRequiringReload = changesRequiringReload;
218
+ const resourceBundle = await getTextBundle();
219
+ const eventIndex = this.eventStack.indexOf(event);
220
+ if (this.eventStack.length - 1 === eventIndex) {
221
+ this.pendingChanges = pendingChanges.filter(change => !!change);
222
+ const changesRequiringReload = this.pendingChanges.reduce((sum, change) => change.changeType === 'appdescr_fe_changePageConfiguration' ? sum + 1 : sum, 0);
223
+ if (changesRequiringReload > this.changesRequiringReload) {
224
+ MessageToast.show(resourceBundle.getText('CPE_CHANGES_VISIBLE_AFTER_SAVE_AND_RELOAD_MESSAGE'), { duration: 8000 });
225
+ this.sendAction(setApplicationRequiresReload(changesRequiringReload > 0));
226
+ }
227
+ this.changesRequiringReload = changesRequiringReload;
228
+ }
229
+ this.eventStack.splice(eventIndex, 1);
187
230
  if (Array.isArray(allCommands) && allCommands.length === 0) {
231
+ this.pendingChanges = [];
188
232
  await this.fetchSavedChanges();
189
233
  }
190
- this.updateStack(activeChanges);
234
+ this.updateStack();
191
235
  handleStackChange();
192
236
  };
193
237
  }
194
- prepareChangeType(command, inactiveCommandCount, index) {
238
+ async handleCommand(command, inactiveCommandCount, index, pendingChanges) {
239
+ const pendingChange = await this.prepareChangeType(command, inactiveCommandCount, index);
240
+ if (pendingChange) {
241
+ pendingChanges.push(pendingChange);
242
+ }
243
+ }
244
+ async prepareChangeType(command, inactiveCommandCount, index) {
195
245
  let result;
196
246
  let value = '';
197
- const selectorId = this.getCommandSelectorId(command);
247
+ const change = command.getPreparedChange();
248
+ const selectorId = typeof change.getSelector === 'function' ? await this.getControlIdByChange(change) : this.getCommandSelectorId(command);
198
249
  const changeType = this.getCommandChangeType(command);
199
- if (!selectorId || !changeType) {
250
+ if (!changeType) {
200
251
  return undefined;
201
252
  }
202
253
  switch (changeType) {
@@ -207,8 +258,8 @@ sap.ui.define([
207
258
  value = command.getProperty('newBinding');
208
259
  break;
209
260
  }
210
- const {fileName} = command.getPreparedChange().getDefinition();
211
- if (changeType === 'propertyChange' || changeType === 'propertyBindingChange') {
261
+ const {fileName} = change.getDefinition();
262
+ if ((changeType === 'propertyChange' || changeType === 'propertyBindingChange') && selectorId) {
212
263
  result = {
213
264
  type: 'pending',
214
265
  kind: 'property',
@@ -224,12 +275,17 @@ sap.ui.define([
224
275
  result = {
225
276
  type: 'pending',
226
277
  kind: 'unknown',
227
- controlId: selectorId,
228
278
  changeType,
229
279
  isActive: index >= inactiveCommandCount,
230
- controlName: changeType === 'addXMLAtExtensionPoint' ? command.getSelector().name ?? '' : command.getElement().getMetadata().getName().split('.').pop() ?? '',
231
280
  fileName
232
281
  };
282
+ if (selectorId) {
283
+ result = {
284
+ ...result,
285
+ kind: 'control',
286
+ controlId: selectorId
287
+ };
288
+ }
233
289
  }
234
290
  return result;
235
291
  }
@@ -262,6 +318,56 @@ sap.ui.define([
262
318
  () => command.getParent()?.getElement().getId()
263
319
  ]);
264
320
  }
321
+ async getControlIdByChange(change) {
322
+ const appComponent = this.options.rta.getRootControlInstance();
323
+ const selector = typeof change.getSelector === 'function' ? change.getSelector() : undefined;
324
+ const changeType = change.getChangeType();
325
+ const layer = change.getLayer();
326
+ if (!selector?.id) {
327
+ return;
328
+ }
329
+ try {
330
+ let control = JsControlTreeModifier.bySelector(selector, appComponent);
331
+ if (!control) {
332
+ return selector.id;
333
+ }
334
+ const changeHandlerAPI = (await __ui5_require_async('sap/ui/fl/write/api/ChangesWriteAPI')).default;
335
+ const changeHandler = await changeHandlerAPI.getChangeHandler({
336
+ changeType,
337
+ element: control,
338
+ modifier: JsControlTreeModifier,
339
+ layer
340
+ });
341
+ if (changeHandler && typeof changeHandler.getChangeVisualizationInfo === 'function') {
342
+ const result = await changeHandler.getChangeVisualizationInfo(change, appComponent);
343
+ return JsControlTreeModifier.getControlIdBySelector(result?.affectedControls?.[0] ?? selector, appComponent);
344
+ }
345
+ return JsControlTreeModifier.getControlIdBySelector(selector, appComponent);
346
+ } catch (error) {
347
+ Log.error('Getting element ID from change has failed:', getError(error));
348
+ return selector.id;
349
+ }
350
+ }
351
+ async syncOutlineChanges() {
352
+ for (const change of this.savedChanges) {
353
+ if (change.kind !== 'unknown') {
354
+ const flexObject = await this.getFlexObject(this.changedFiles[change.fileName]);
355
+ change.controlId = await this.getControlIdByChange(flexObject) ?? '';
356
+ }
357
+ }
358
+ this.updateStack();
359
+ }
360
+ async getFlexObject(change) {
361
+ if (isLowerThanMinimalUi5Version(await getUi5Version(), {
362
+ major: 1,
363
+ minor: 109
364
+ })) {
365
+ const Change = (await __ui5_require_async('sap/ui/fl/Change')).default;
366
+ return new Change(change);
367
+ }
368
+ const FlexObjectFactory = (await __ui5_require_async('sap/ui/fl/apply/_internal/flexObjects/FlexObjectFactory')).default;
369
+ return FlexObjectFactory.createFromFileContent(change);
370
+ }
265
371
  }
266
372
  var __exports = { __esModule: true };
267
373
  __exports.ChangeService = ChangeService;
@@ -2,7 +2,8 @@ import type {
2
2
  ExternalAction,
3
3
  PendingChange,
4
4
  SavedPropertyChange,
5
- UnknownSavedChange
5
+ UnknownSavedChange,
6
+ SavedControlChange
6
7
  } from '@sap-ux-private/control-property-editor-common';
7
8
  import {
8
9
  changeProperty,
@@ -16,13 +17,15 @@ import {
16
17
  } from '@sap-ux-private/control-property-editor-common';
17
18
  import { applyChange } from './flex-change';
18
19
  import type { SelectionService } from '../selection';
19
-
20
20
  import type { ActionSenderFunction, SubscribeFunction, UI5AdaptationOptions } from '../types';
21
21
  import type Event from 'sap/ui/base/Event';
22
22
  import type FlexCommand from 'sap/ui/rta/command/FlexCommand';
23
23
  import Log from 'sap/base/Log';
24
24
  import { modeAndStackChangeHandler } from '../rta-service';
25
+ import JsControlTreeModifier from 'sap/ui/core/util/reflection/JsControlTreeModifier';
26
+ import FlexChange from 'sap/ui/fl/Change';
25
27
  import { getError } from '../../utils/error';
28
+ import { isLowerThanMinimalUi5Version, getUi5Version } from '../../utils/version';
26
29
  import MessageToast from 'sap/m/MessageToast';
27
30
  import { getTextBundle } from '../../i18n';
28
31
 
@@ -94,9 +97,12 @@ function modifyRTAErrorMessage(errorMessage: string, id: string, type: string):
94
97
  * A Class of ChangeService
95
98
  */
96
99
  export class ChangeService {
97
- private savedChanges: SavedPropertyChange[] = [];
100
+ private savedChanges: SavedPropertyChange[] | UnknownSavedChange[] | SavedControlChange[] = [];
98
101
  private changesRequiringReload = 0;
99
102
  private sendAction: (action: ExternalAction) => void;
103
+ private pendingChanges: PendingChange[] = [];
104
+ private changedFiles: Record<string, object> = {};
105
+ private readonly eventStack: object[] = [];
100
106
  /**
101
107
  *
102
108
  * @param options ui5 adaptation options.
@@ -159,11 +165,11 @@ export class ChangeService {
159
165
  *
160
166
  * @param pendingChanges Changes that are waiting to be saved
161
167
  */
162
- private updateStack(pendingChanges: PendingChange[] = []) {
168
+ private updateStack() {
163
169
  this.sendAction(
164
170
  changeStackModified({
165
- saved: this.savedChanges,
166
- pending: pendingChanges
171
+ saved: this.savedChanges ?? [],
172
+ pending: this.pendingChanges ?? []
167
173
  })
168
174
  );
169
175
  }
@@ -172,54 +178,80 @@ export class ChangeService {
172
178
  * Fetches saved changes from the workspace and sorts them.
173
179
  */
174
180
  private async fetchSavedChanges(): Promise<void> {
181
+ this.changedFiles = {};
175
182
  const savedChangesResponse = await fetch(FlexChangesEndPoints.changes + `?_=${Date.now()}`);
176
183
  const savedChanges = (await savedChangesResponse.json()) as SavedChangesResponse;
177
184
  const changes = (
178
- Object.keys(savedChanges ?? {})
179
- .map((key): SavedPropertyChange | UnknownSavedChange | undefined => {
180
- const change: Change = savedChanges[key];
181
- try {
182
- assertChange(change);
183
- if (
184
- [change.content.newValue, change.content.newBinding].every(
185
- (item) => item === undefined || item === null
186
- )
187
- ) {
188
- throw new Error('Invalid change, missing new value in the change file');
189
- }
190
- if (change.changeType !== 'propertyChange' && change.changeType !== 'propertyBindingChange') {
191
- throw new Error('Unknown Change Type');
192
- }
193
- return {
194
- type: 'saved',
195
- kind: 'property',
196
- fileName: change.fileName,
197
- controlId: change.selector.id,
198
- propertyName: change.content.property,
199
- value: change.content.newValue ?? change.content.newBinding,
200
- timestamp: new Date(change.creation).getTime(),
201
- controlName: change.selector.type ? (change.selector.type.split('.').pop() as string) : '',
202
- changeType: change.changeType
203
- };
204
- } catch (error) {
205
- // Gracefully handle change files with invalid content
206
- if (change.fileName) {
207
- const unknownChange: UnknownSavedChange = {
208
- type: 'saved',
209
- kind: 'unknown',
210
- changeType: change.changeType,
211
- fileName: change.fileName,
212
- controlId: change.selector?.id // some changes may not have selector
213
- };
214
- if (change.creation) {
215
- unknownChange.timestamp = new Date(change.creation).getTime();
185
+ (
186
+ await Promise.all(
187
+ Object.keys(savedChanges ?? {}).map(
188
+ async (
189
+ key
190
+ ): Promise<SavedPropertyChange | UnknownSavedChange | SavedControlChange | undefined> => {
191
+ const change: Change = savedChanges[key];
192
+ let selectorId;
193
+ try {
194
+ const flexObject = await this.getFlexObject(change);
195
+ selectorId = await this.getControlIdByChange(flexObject);
196
+ assertChange(change);
197
+ if (
198
+ [change.content.newValue, change.content.newBinding].every(
199
+ (item) => item === undefined || item === null
200
+ )
201
+ ) {
202
+ throw new Error('Invalid change, missing new value in the change file');
203
+ }
204
+ if (
205
+ change.changeType !== 'propertyChange' &&
206
+ change.changeType !== 'propertyBindingChange'
207
+ ) {
208
+ throw new Error('Unknown Change Type');
209
+ }
210
+ this.changedFiles[change.fileName] = change;
211
+ return {
212
+ type: 'saved',
213
+ kind: 'property',
214
+ fileName: change.fileName,
215
+ controlId: selectorId,
216
+ propertyName: change.content.property,
217
+ value: change.content.newValue ?? change.content.newBinding,
218
+ timestamp: new Date(change.creation).getTime(),
219
+ controlName: change.selector.type
220
+ ? (change.selector.type.split('.').pop() as string)
221
+ : '',
222
+ changeType: change.changeType
223
+ } as SavedPropertyChange;
224
+ } catch (error) {
225
+ // Gracefully handle change files with invalid content
226
+ if (change.fileName) {
227
+ this.changedFiles[change.fileName] = change;
228
+ const unknownChange: UnknownSavedChange = {
229
+ type: 'saved',
230
+ kind: 'unknown',
231
+ changeType: change.changeType,
232
+ fileName: change.fileName,
233
+ timestamp: new Date(change.creation).getTime()
234
+ };
235
+ if (change.creation) {
236
+ unknownChange.timestamp = new Date(change.creation).getTime();
237
+ }
238
+ if (selectorId) {
239
+ const controlChange: SavedControlChange = {
240
+ ...unknownChange,
241
+ kind: 'control',
242
+ controlId: selectorId
243
+ };
244
+
245
+ return controlChange;
246
+ }
247
+ return unknownChange;
248
+ }
249
+ return undefined;
216
250
  }
217
- return unknownChange;
218
251
  }
219
- return undefined;
220
- }
221
- })
222
- .filter((change) => !!change) as SavedPropertyChange[]
252
+ )
253
+ )
254
+ ).filter((change) => !!change) as SavedPropertyChange[]
223
255
  ).sort((a, b) => b.timestamp - a.timestamp);
224
256
  this.savedChanges = changes;
225
257
  }
@@ -232,11 +264,19 @@ export class ChangeService {
232
264
  */
233
265
  public async deleteChange(controlId: string, propertyName: string, fileName?: string): Promise<void> {
234
266
  const filesToDelete = this.savedChanges
235
- .filter((change) =>
236
- fileName
237
- ? fileName === change.fileName
238
- : change.controlId === controlId && change.propertyName === propertyName
239
- )
267
+ .filter((change) => {
268
+ if (fileName) {
269
+ return fileName === change.fileName;
270
+ }
271
+
272
+ if (change.kind === 'property') {
273
+ return change.controlId === controlId && change.propertyName === propertyName;
274
+ }
275
+
276
+ if (change.kind === 'control') {
277
+ return change.controlId === controlId;
278
+ }
279
+ })
240
280
  .map((change) =>
241
281
  fetch(FlexChangesEndPoints.changes, {
242
282
  method: 'DELETE',
@@ -261,67 +301,101 @@ export class ChangeService {
261
301
  */
262
302
  private createOnStackChangeHandler(): (event: Event) => Promise<void> {
263
303
  const handleStackChange = modeAndStackChangeHandler(this.sendAction, this.options.rta);
264
- return async (): Promise<void> => {
304
+ return async (event): Promise<void> => {
305
+ const pendingChanges: PendingChange[] = [];
306
+ this.eventStack.push(event);
265
307
  const stack = this.options.rta.getCommandStack();
266
308
  const allCommands = stack.getCommands();
267
309
  const executedCommands = stack.getAllExecutedCommands();
268
310
  const inactiveCommandCount = allCommands.length - executedCommands.length;
269
- let activeChanges: PendingChange[] = [];
270
- allCommands.forEach((command: FlexCommand, i): void => {
311
+ let i: number, command: FlexCommand;
312
+ for ([i, command] of allCommands.entries()) {
271
313
  try {
272
314
  if (typeof command.getCommands === 'function') {
273
315
  const subCommands = command.getCommands();
274
- subCommands.forEach((subCommand) => {
275
- const pendingChange = this.prepareChangeType(subCommand, inactiveCommandCount, i);
276
- if (pendingChange) {
277
- activeChanges.push(pendingChange);
278
- }
279
- });
280
- } else {
281
- const pendingChange = this.prepareChangeType(command, inactiveCommandCount, i);
282
- if (pendingChange) {
283
- activeChanges.push(pendingChange);
316
+ for (const subCommand of subCommands) {
317
+ await this.handleCommand(subCommand, inactiveCommandCount, i, pendingChanges);
284
318
  }
319
+ } else {
320
+ await this.handleCommand(command, inactiveCommandCount, i, pendingChanges);
285
321
  }
286
322
  } catch (error) {
287
323
  Log.error('CPE: Change creation Failed', getError(error));
288
324
  }
289
- });
290
-
291
- activeChanges = activeChanges.filter((change): boolean => !!change);
292
- const changesRequiringReload = activeChanges.reduce(
293
- (sum, change) => (change.changeType === 'appdescr_fe_changePageConfiguration' ? sum + 1 : sum),
294
- 0
295
- );
296
- if (changesRequiringReload > this.changesRequiringReload) {
297
- const resourceBundle = await getTextBundle();
298
- MessageToast.show(resourceBundle.getText('CPE_CHANGES_VISIBLE_AFTER_SAVE_AND_RELOAD_MESSAGE'), {
299
- duration: 8000
300
- });
301
- this.sendAction(setApplicationRequiresReload(changesRequiringReload > 0));
302
325
  }
303
- this.changesRequiringReload = changesRequiringReload;
304
-
326
+ const resourceBundle = await getTextBundle();
327
+ const eventIndex = this.eventStack.indexOf(event);
328
+ if (this.eventStack.length - 1 === eventIndex) {
329
+ this.pendingChanges = pendingChanges.filter((change): boolean => !!change);
330
+ const changesRequiringReload = this.pendingChanges.reduce(
331
+ (sum, change) => (change.changeType === 'appdescr_fe_changePageConfiguration' ? sum + 1 : sum),
332
+ 0
333
+ );
334
+ if (changesRequiringReload > this.changesRequiringReload) {
335
+ MessageToast.show(resourceBundle.getText('CPE_CHANGES_VISIBLE_AFTER_SAVE_AND_RELOAD_MESSAGE'), {
336
+ duration: 8000
337
+ });
338
+ this.sendAction(setApplicationRequiresReload(changesRequiringReload > 0));
339
+ }
340
+ this.changesRequiringReload = changesRequiringReload;
341
+ }
342
+ this.eventStack.splice(eventIndex, 1);
305
343
  if (Array.isArray(allCommands) && allCommands.length === 0) {
344
+ this.pendingChanges = [];
306
345
  await this.fetchSavedChanges();
307
346
  }
308
-
309
- this.updateStack(activeChanges);
347
+ this.updateStack();
310
348
  handleStackChange();
311
349
  };
312
350
  }
313
351
 
314
- private prepareChangeType(
352
+ /**
353
+ * Handles a command by preparing a pending change and adding it to the list of pending changes.
354
+ *
355
+ * @param {FlexCommand} command - The command to process.
356
+ * @param {number} inactiveCommandCount - The number of inactive commands.
357
+ * @param {number} index - The index of the current command being processed.
358
+ * @param {PendingChange[]} pendingChanges - The list of pending changes to update.
359
+ * @returns {Promise<void>} A promise that resolves when the command is handled.
360
+ */
361
+ private async handleCommand(
362
+ command: FlexCommand,
363
+ inactiveCommandCount: number,
364
+ index: number,
365
+ pendingChanges: PendingChange[]
366
+ ): Promise<void> {
367
+ const pendingChange = await this.prepareChangeType(command, inactiveCommandCount, index);
368
+ if (pendingChange) {
369
+ pendingChanges.push(pendingChange);
370
+ }
371
+ }
372
+
373
+ /**
374
+ * Prepares the type of change based on the command and other parameters.
375
+ *
376
+ * @param {FlexCommand} command - The command to process.
377
+ * @param {number} inactiveCommandCount - The number of inactive commands.
378
+ * @param {number} index - The index of the current command being processed.
379
+ * @returns {Promise<PendingChange | undefined>} - A promise that resolves to a `PendingChange` or `undefined`.
380
+ */
381
+ private async prepareChangeType(
315
382
  command: FlexCommand,
316
383
  inactiveCommandCount: number,
317
384
  index: number
318
- ): PendingChange | undefined {
385
+ ): Promise<PendingChange | undefined> {
319
386
  let result: PendingChange;
320
387
  let value = '';
321
- const selectorId = this.getCommandSelectorId(command);
388
+
389
+ const change = command.getPreparedChange();
390
+
391
+ const selectorId =
392
+ typeof change.getSelector === 'function'
393
+ ? await this.getControlIdByChange(change)
394
+ : this.getCommandSelectorId(command);
395
+
322
396
  const changeType = this.getCommandChangeType(command);
323
397
 
324
- if (!selectorId || !changeType) {
398
+ if (!changeType) {
325
399
  return undefined;
326
400
  }
327
401
 
@@ -333,8 +407,9 @@ export class ChangeService {
333
407
  value = command.getProperty('newBinding') as string;
334
408
  break;
335
409
  }
336
- const { fileName } = command.getPreparedChange().getDefinition();
337
- if (changeType === 'propertyChange' || changeType === 'propertyBindingChange') {
410
+
411
+ const { fileName } = change.getDefinition();
412
+ if ((changeType === 'propertyChange' || changeType === 'propertyBindingChange') && selectorId) {
338
413
  result = {
339
414
  type: 'pending',
340
415
  kind: 'property',
@@ -350,15 +425,18 @@ export class ChangeService {
350
425
  result = {
351
426
  type: 'pending',
352
427
  kind: 'unknown',
353
- controlId: selectorId,
354
428
  changeType,
355
429
  isActive: index >= inactiveCommandCount,
356
- controlName:
357
- changeType === 'addXMLAtExtensionPoint'
358
- ? command.getSelector().name ?? ''
359
- : command.getElement().getMetadata().getName().split('.').pop() ?? '',
360
430
  fileName
361
431
  };
432
+
433
+ if (selectorId) {
434
+ result = {
435
+ ...result,
436
+ kind: 'control',
437
+ controlId: selectorId
438
+ };
439
+ }
362
440
  }
363
441
 
364
442
  return result;
@@ -414,4 +492,84 @@ export class ChangeService {
414
492
  () => command.getParent()?.getElement().getId()
415
493
  ]) as string | undefined;
416
494
  }
495
+
496
+ /**
497
+ * Get element id by change.
498
+ *
499
+ * @param change to be executed for creating change
500
+ * @returns element id or empty string
501
+ */
502
+ private async getControlIdByChange(change: FlexChange<ChangeContent>): Promise<string | undefined> {
503
+ const appComponent = this.options.rta.getRootControlInstance();
504
+ const selector = typeof change.getSelector === 'function' ? change.getSelector() : undefined;
505
+ const changeType = change.getChangeType();
506
+ const layer = change.getLayer();
507
+
508
+ if (!selector?.id) {
509
+ return;
510
+ }
511
+
512
+ try {
513
+ let control = JsControlTreeModifier.bySelector(selector, appComponent);
514
+ if (!control) {
515
+ return selector.id;
516
+ }
517
+
518
+ const changeHandlerAPI = (await import('sap/ui/fl/write/api/ChangesWriteAPI')).default;
519
+
520
+ const changeHandler = await changeHandlerAPI.getChangeHandler({
521
+ changeType,
522
+ element: control,
523
+ modifier: JsControlTreeModifier,
524
+ layer
525
+ });
526
+
527
+ if (changeHandler && typeof changeHandler.getChangeVisualizationInfo === 'function') {
528
+ const result: { affectedControls?: [string] } = await changeHandler.getChangeVisualizationInfo(
529
+ change,
530
+ appComponent
531
+ );
532
+ return JsControlTreeModifier.getControlIdBySelector(
533
+ result?.affectedControls?.[0] ?? selector,
534
+ appComponent
535
+ );
536
+ }
537
+
538
+ return JsControlTreeModifier.getControlIdBySelector(selector, appComponent);
539
+ } catch (error) {
540
+ Log.error('Getting element ID from change has failed:', getError(error));
541
+ return selector.id;
542
+ }
543
+ }
544
+
545
+ /**
546
+ * Sync outline changes to place modification markers when outline is changed.
547
+ *
548
+ * @returns void
549
+ */
550
+ public async syncOutlineChanges(): Promise<void> {
551
+ for (const change of this.savedChanges) {
552
+ if (change.kind !== 'unknown') {
553
+ const flexObject = await this.getFlexObject(this.changedFiles[change.fileName]);
554
+ change.controlId = (await this.getControlIdByChange(flexObject)) ?? '';
555
+ }
556
+ }
557
+ this.updateStack();
558
+ }
559
+
560
+ /**
561
+ * Get FlexObject from change object based on UI5 version.
562
+ *
563
+ * @param change change object
564
+ * @returns FlexChange
565
+ */
566
+ private async getFlexObject(change: object): Promise<FlexChange<ChangeContent>> {
567
+ if (isLowerThanMinimalUi5Version(await getUi5Version(), { major: 1, minor: 109 })) {
568
+ const Change = (await import('sap/ui/fl/Change')).default;
569
+ return new Change(change);
570
+ }
571
+
572
+ const FlexObjectFactory = (await import('sap/ui/fl/apply/_internal/flexObjects/FlexObjectFactory')).default;
573
+ return FlexObjectFactory.createFromFileContent(change) as FlexChange<ChangeContent>;
574
+ }
417
575
  }
@@ -41,7 +41,7 @@ sap.ui.define([
41
41
  const changesService = new ChangeService({ rta }, selectionService);
42
42
  const connectorService = new WorkspaceConnectorService();
43
43
  const rtaService = new RtaService(rta);
44
- const outlineService = new OutlineService(rta);
44
+ const outlineService = new OutlineService(rta, changesService);
45
45
  const quickActionService = new QuickActionService(rta, outlineService, registries);
46
46
  const services = [
47
47
  connectorService,
@@ -45,7 +45,7 @@ export default function init(
45
45
  const changesService = new ChangeService({ rta }, selectionService);
46
46
  const connectorService = new WorkspaceConnectorService();
47
47
  const rtaService = new RtaService(rta);
48
- const outlineService = new OutlineService(rta);
48
+ const outlineService = new OutlineService(rta, changesService);
49
49
  const quickActionService = new QuickActionService(rta, outlineService, registries);
50
50
  const services: Service[] = [
51
51
  connectorService,
@@ -15,9 +15,10 @@ sap.ui.define([
15
15
  const transformNodes = ___nodes['transformNodes'];
16
16
  const OUTLINE_CHANGE_EVENT = 'OUTLINE_CHANGED';
17
17
  class OutlineService extends EventTarget {
18
- constructor(rta) {
18
+ constructor(rta, changeService) {
19
19
  super();
20
20
  this.rta = rta;
21
+ this.changeService = changeService;
21
22
  }
22
23
  async init(sendAction) {
23
24
  const outline = await this.rta.getService('outline');
@@ -32,6 +33,7 @@ sap.ui.define([
32
33
  const viewNodes = await outline.get();
33
34
  const controlIndex = {};
34
35
  const outlineNodes = await transformNodes(viewNodes, scenario, reuseComponentsIds, controlIndex);
36
+ await this.changeService.syncOutlineChanges();
35
37
  const event = new CustomEvent(OUTLINE_CHANGE_EVENT, { detail: { controlIndex } });
36
38
  this.dispatchEvent(event);
37
39
  sendAction(outlineChanged(outlineNodes));
@@ -1,6 +1,7 @@
1
1
  import Log from 'sap/base/Log';
2
2
  import type RuntimeAuthoring from 'sap/ui/rta/RuntimeAuthoring';
3
3
  import type RTAOutlineService from 'sap/ui/rta/command/OutlineService';
4
+ import type { ChangeService } from '../changes/service';
4
5
 
5
6
  import type { ExternalAction } from '@sap-ux-private/control-property-editor-common';
6
7
  import { outlineChanged, SCENARIO, showMessage } from '@sap-ux-private/control-property-editor-common';
@@ -19,7 +20,7 @@ export interface OutlineChangedEventDetail {
19
20
  * A Class of WorkspaceConnectorService
20
21
  */
21
22
  export class OutlineService extends EventTarget {
22
- constructor(private rta: RuntimeAuthoring) {
23
+ constructor(private readonly rta: RuntimeAuthoring, private readonly changeService: ChangeService) {
23
24
  super();
24
25
  }
25
26
 
@@ -41,6 +42,7 @@ export class OutlineService extends EventTarget {
41
42
  const viewNodes = await outline.get();
42
43
  const controlIndex: ControlTreeIndex = {};
43
44
  const outlineNodes = await transformNodes(viewNodes, scenario, reuseComponentsIds, controlIndex);
45
+ await this.changeService.syncOutlineChanges();
44
46
 
45
47
  const event = new CustomEvent(OUTLINE_CHANGE_EVENT, {
46
48
  detail: {
@@ -8,7 +8,7 @@ sap.ui.define((function () { 'use strict';
8
8
 
9
9
  (function (exports) {
10
10
  Object.defineProperty(exports, "__esModule", { value: true });
11
- exports.setApplicationRequiresReload = exports.executeQuickAction = exports.updateQuickAction = exports.quickActionListChanged = exports.save = exports.redo = exports.undo = exports.appLoaded = exports.setSaveEnablement = exports.setUndoRedoEnablement = exports.applicationModeChanged = exports.setAppMode = exports.storageFileChanged = exports.reloadApplication = exports.showMessage = exports.changeStackModified = exports.propertyChangeFailed = exports.propertyChanged = exports.changeProperty = exports.outlineChanged = exports.deletePropertyChanges = exports.addExtensionPoint = exports.selectControl = exports.controlSelected = exports.iconsLoaded = exports.EXTERNAL_ACTION_PREFIX = exports.NESTED_QUICK_ACTION_KIND = exports.SIMPLE_QUICK_ACTION_KIND = exports.UNKNOWN_CHANGE_KIND = exports.PROPERTY_CHANGE_KIND = exports.SAVED_CHANGE_TYPE = exports.PENDING_CHANGE_TYPE = exports.SCENARIO = exports.CHECKBOX_EDITOR_TYPE = exports.DROPDOWN_EDITOR_TYPE = exports.INPUT_EDITOR_TYPE = exports.STRING_VALUE_TYPE = exports.FLOAT_VALUE_TYPE = exports.INTEGER_VALUE_TYPE = exports.BOOLEAN_VALUE_TYPE = void 0;
11
+ exports.setApplicationRequiresReload = exports.executeQuickAction = exports.updateQuickAction = exports.quickActionListChanged = exports.save = exports.redo = exports.undo = exports.appLoaded = exports.setSaveEnablement = exports.setUndoRedoEnablement = exports.applicationModeChanged = exports.setAppMode = exports.storageFileChanged = exports.reloadApplication = exports.showMessage = exports.changeStackModified = exports.propertyChangeFailed = exports.propertyChanged = exports.changeProperty = exports.outlineChanged = exports.deletePropertyChanges = exports.addExtensionPoint = exports.selectControl = exports.controlSelected = exports.iconsLoaded = exports.EXTERNAL_ACTION_PREFIX = exports.NESTED_QUICK_ACTION_KIND = exports.SIMPLE_QUICK_ACTION_KIND = exports.CONTROL_CHANGE_KIND = exports.UNKNOWN_CHANGE_KIND = exports.PROPERTY_CHANGE_KIND = exports.SAVED_CHANGE_TYPE = exports.PENDING_CHANGE_TYPE = exports.SCENARIO = exports.CHECKBOX_EDITOR_TYPE = exports.DROPDOWN_EDITOR_TYPE = exports.INPUT_EDITOR_TYPE = exports.STRING_VALUE_TYPE = exports.FLOAT_VALUE_TYPE = exports.INTEGER_VALUE_TYPE = exports.BOOLEAN_VALUE_TYPE = void 0;
12
12
  exports.BOOLEAN_VALUE_TYPE = 'boolean';
13
13
  exports.INTEGER_VALUE_TYPE = 'integer';
14
14
  exports.FLOAT_VALUE_TYPE = 'float';
@@ -27,6 +27,7 @@ sap.ui.define((function () { 'use strict';
27
27
  exports.SAVED_CHANGE_TYPE = 'saved';
28
28
  exports.PROPERTY_CHANGE_KIND = 'property';
29
29
  exports.UNKNOWN_CHANGE_KIND = 'unknown';
30
+ exports.CONTROL_CHANGE_KIND = 'control';
30
31
  exports.SIMPLE_QUICK_ACTION_KIND = 'simple';
31
32
  exports.NESTED_QUICK_ACTION_KIND = 'nested';
32
33
  /**
package/package.json CHANGED
@@ -9,7 +9,7 @@
9
9
  "bugs": {
10
10
  "url": "https://github.com/SAP/open-ux-tools/issues?q=is%3Aopen+is%3Aissue+label%3Abug+label%3Apreview-middleware"
11
11
  },
12
- "version": "0.16.92",
12
+ "version": "0.16.94",
13
13
  "license": "Apache-2.0",
14
14
  "author": "@SAP/ux-tools-team",
15
15
  "main": "dist/index.js",
@@ -29,7 +29,7 @@
29
29
  "@sap-ux/feature-toggle": "0.2.2",
30
30
  "@sap-ux/btp-utils": "0.15.2",
31
31
  "@sap-ux/adp-tooling": "0.12.62",
32
- "@sap-ux/control-property-editor-sources": "npm:@sap-ux/control-property-editor@0.5.21",
32
+ "@sap-ux/control-property-editor-sources": "npm:@sap-ux/control-property-editor@0.5.22",
33
33
  "@sap-ux/project-access": "1.28.2"
34
34
  },
35
35
  "devDependencies": {
@@ -46,7 +46,7 @@
46
46
  "supertest": "6.3.3",
47
47
  "@sap-ux-private/playwright": "0.1.0",
48
48
  "dotenv": "16.3.1",
49
- "@private/preview-middleware-client": "npm:@sap-ux-private/preview-middleware-client@0.11.17",
49
+ "@private/preview-middleware-client": "npm:@sap-ux-private/preview-middleware-client@0.11.18",
50
50
  "@sap-ux/axios-extension": "1.16.7",
51
51
  "@sap-ux/store": "0.9.2",
52
52
  "@sap-ux/ui5-info": "0.8.1",