@sap-ux/preview-middleware 0.5.6 → 0.6.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.
@@ -0,0 +1,377 @@
1
+ /** sap.m */
2
+ import Input from 'sap/m/Input';
3
+ import Button from 'sap/m/Button';
4
+ import type Dialog from 'sap/m/Dialog';
5
+ import type ComboBox from 'sap/m/ComboBox';
6
+ import MessageToast from 'sap/m/MessageToast';
7
+
8
+ /** sap.ui.core */
9
+ import { ValueState } from 'sap/ui/core/library';
10
+ import type UI5Element from 'sap/ui/core/Element';
11
+ import Controller from 'sap/ui/core/mvc/Controller';
12
+
13
+ /** sap.ui.base */
14
+ import type Event from 'sap/ui/base/Event';
15
+ import type ManagedObject from 'sap/ui/base/ManagedObject';
16
+ import type ManagedObjectMetadata from 'sap/ui/base/ManagedObjectMetadata';
17
+
18
+ /** sap.ui.model */
19
+ import JSONModel from 'sap/ui/model/json/JSONModel';
20
+
21
+ /** sap.ui.rta */
22
+ import type RuntimeAuthoring from 'sap/ui/rta/RuntimeAuthoring';
23
+
24
+ /** sap.ui.dt */
25
+ import OverlayRegistry from 'sap/ui/dt/OverlayRegistry';
26
+ import type ElementOverlay from 'sap/ui/dt/ElementOverlay';
27
+
28
+ import ControlUtils from '../control-utils';
29
+ import CommandExecutor from '../command-executor';
30
+ import { ManifestAppdescr, getFragments, getManifestAppdescr, writeFragment } from '../api-handler';
31
+
32
+ interface CreateFragmentProps {
33
+ fragmentName: string;
34
+ index: string | number;
35
+ targetAggregation: string;
36
+ }
37
+
38
+ /**
39
+ * @namespace open.ux.preview.client.adp.controllers
40
+ */
41
+ export default class AddFragment extends Controller {
42
+ /**
43
+ * Runtime control managed object
44
+ */
45
+ public runtimeControl: ManagedObject;
46
+ /**
47
+ * JSON Model that has the data
48
+ */
49
+ public model: JSONModel;
50
+ /**
51
+ * Dialog instance
52
+ */
53
+ public dialog: Dialog;
54
+ /**
55
+ * Runtime Authoring
56
+ */
57
+ private rta: RuntimeAuthoring;
58
+ /**
59
+ * Control Overlays
60
+ */
61
+ private overlays: UI5Element;
62
+ /**
63
+ * RTA Command Executor
64
+ */
65
+ private commandExecutor: CommandExecutor;
66
+
67
+ constructor(name: string, overlays: UI5Element, rta: RuntimeAuthoring) {
68
+ super(name);
69
+ this.rta = rta;
70
+ this.overlays = overlays;
71
+ this.commandExecutor = new CommandExecutor(this.rta);
72
+ }
73
+
74
+ /**
75
+ * Initializes controller, fills model with data and opens the dialog
76
+ */
77
+ async onInit() {
78
+ this.model = new JSONModel();
79
+
80
+ this.dialog = this.byId('addNewFragmentDialog') as unknown as Dialog;
81
+
82
+ await this.buildDialogData();
83
+
84
+ this.getView()?.setModel(this.model);
85
+
86
+ this.dialog.open();
87
+ }
88
+
89
+ /**
90
+ * Handles the change in aggregations
91
+ *
92
+ * @param event Event
93
+ */
94
+ onAggregationChanged(event: Event) {
95
+ const source = event.getSource<ComboBox>();
96
+
97
+ const selectedKey = source.getSelectedKey();
98
+ const selectedItem = source.getSelectedItem();
99
+
100
+ let selectedItemText = '';
101
+ if (selectedItem) {
102
+ selectedItemText = selectedItem.getText();
103
+ }
104
+
105
+ this.model.setProperty('/selectedAggregation/key', selectedKey);
106
+ this.model.setProperty('/selectedAggregation/value', selectedItemText);
107
+
108
+ let newSelectedControlChildren: string[] | number[] = Object.keys(
109
+ ControlUtils.getControlAggregationByName(this.runtimeControl, selectedItemText)
110
+ );
111
+
112
+ newSelectedControlChildren = newSelectedControlChildren.map((key) => {
113
+ return parseInt(key);
114
+ });
115
+
116
+ const updatedIndexArray: { key: number; value: number }[] = this.fillIndexArray(newSelectedControlChildren);
117
+
118
+ this.model.setProperty('/index', updatedIndexArray);
119
+ this.model.setProperty('/selectedIndex', updatedIndexArray.length - 1);
120
+ }
121
+
122
+ /**
123
+ * Handles the change in target indexes
124
+ *
125
+ * @param event Event
126
+ */
127
+ onIndexChanged(event: Event) {
128
+ const source = event.getSource<ComboBox>();
129
+ const selectedIndex = source.getSelectedItem()?.getText();
130
+ this.model.setProperty('/selectedIndex', selectedIndex);
131
+ }
132
+
133
+ /**
134
+ * Handles fragment name input change
135
+ *
136
+ * @param event Event
137
+ */
138
+ onFragmentNameInputChange(event: Event) {
139
+ const source = event.getSource<Input>();
140
+
141
+ const fragmentName: string = source.getValue().trim();
142
+ const fragmentList: { fragmentName: string }[] = this.model.getProperty('/fragmentList');
143
+
144
+ if (fragmentName.length <= 0) {
145
+ this.dialog.getBeginButton().setEnabled(false);
146
+ source.setValueState(ValueState.None);
147
+ this.model.setProperty('/newFragmentName', null);
148
+ } else {
149
+ const fileExists = fragmentList.find((f: { fragmentName: string }) => {
150
+ return f.fragmentName === `${fragmentName}.fragment.xml`;
151
+ });
152
+
153
+ const isValidName = /^[a-zA-Z_][a-zA-Z0-9_-]*$/.test(fragmentName);
154
+
155
+ if (fileExists) {
156
+ source.setValueState(ValueState.Error);
157
+ source.setValueStateText(
158
+ 'Enter a different name. The fragment name that you entered already exists in your project.'
159
+ );
160
+ this.dialog.getBeginButton().setEnabled(false);
161
+ } else if (!isValidName) {
162
+ source.setValueState(ValueState.Error);
163
+ source.setValueStateText('A Fragment Name cannot contain white spaces or special characters.');
164
+ this.dialog.getBeginButton().setEnabled(false);
165
+ } else {
166
+ this.dialog.getBeginButton().setEnabled(true);
167
+ source.setValueState(ValueState.None);
168
+ this.model.setProperty('/newFragmentName', fragmentName);
169
+ }
170
+ }
171
+ }
172
+
173
+ /**
174
+ * Handles create button press
175
+ *
176
+ * @param event Event
177
+ */
178
+ async onCreateBtnPress(event: Event) {
179
+ const source = event.getSource<Button>();
180
+ source.setEnabled(false);
181
+
182
+ const fragmentName = this.model.getProperty('/newFragmentName');
183
+ const index = parseInt(this.model.getProperty('/selectedIndex'));
184
+ const targetAggregation = this.model.getProperty('/selectedAggregation/value');
185
+ const fragmentData = {
186
+ index,
187
+ fragmentName,
188
+ targetAggregation
189
+ };
190
+
191
+ await this.createNewFragment(fragmentData);
192
+
193
+ this.handleDialogClose();
194
+ }
195
+
196
+ /**
197
+ * Handles the closing of the dialog
198
+ */
199
+ closeDialog() {
200
+ this.handleDialogClose();
201
+ }
202
+
203
+ /**
204
+ * Handles the dialog closing and destruction of it
205
+ */
206
+ handleDialogClose() {
207
+ this.dialog.close();
208
+ this.getView()?.destroy();
209
+ }
210
+
211
+ /**
212
+ * Builds data that is used in the dialog
213
+ */
214
+ async buildDialogData(): Promise<void> {
215
+ const selectorId = this.overlays.getId();
216
+
217
+ let controlMetadata: ManagedObjectMetadata;
218
+
219
+ const overlayControl = sap.ui.getCore().byId(selectorId) as unknown as ElementOverlay;
220
+ if (overlayControl) {
221
+ this.runtimeControl = ControlUtils.getRuntimeControl(overlayControl);
222
+ controlMetadata = this.runtimeControl.getMetadata();
223
+ } else {
224
+ throw new Error('Cannot get overlay control');
225
+ }
226
+
227
+ const allAggregations = Object.keys(controlMetadata.getAllAggregations());
228
+ const hiddenAggregations = ['customData', 'layoutData', 'dependents'];
229
+ const targetAggregation = allAggregations.filter((item) => {
230
+ if (hiddenAggregations.indexOf(item) === -1) {
231
+ return item;
232
+ }
233
+ return false;
234
+ });
235
+ const defaultAggregation = controlMetadata.getDefaultAggregationName();
236
+ const selectedControlName = controlMetadata.getName();
237
+
238
+ let selectedControlChildren: string[] | number[] = Object.keys(
239
+ ControlUtils.getControlAggregationByName(this.runtimeControl, defaultAggregation)
240
+ );
241
+
242
+ selectedControlChildren = selectedControlChildren.map((key) => {
243
+ return parseInt(key);
244
+ });
245
+
246
+ this.model.setProperty('/selectedControlName', selectedControlName);
247
+ this.model.setProperty('/selectedAggregation', {});
248
+
249
+ const indexArray = this.fillIndexArray(selectedControlChildren);
250
+
251
+ const controlAggregation: { key: string | number; value: string | number }[] = targetAggregation.map(
252
+ (elem, index) => {
253
+ return { key: index, value: elem };
254
+ }
255
+ );
256
+
257
+ if (defaultAggregation !== null) {
258
+ controlAggregation.forEach((obj) => {
259
+ if (obj.value === defaultAggregation) {
260
+ obj.key = 'default';
261
+ this.model.setProperty('/selectedAggregation/key', obj.key);
262
+ this.model.setProperty('/selectedAggregation/value', obj.value);
263
+ }
264
+ });
265
+ } else {
266
+ this.model.setProperty('/selectedAggregation/key', controlAggregation[0].key);
267
+ this.model.setProperty('/selectedAggregation/value', controlAggregation[0].value);
268
+ }
269
+
270
+ try {
271
+ const { fragments } = await getFragments();
272
+
273
+ this.model.setProperty('/fragmentList', fragments);
274
+ } catch (e) {
275
+ throw new Error(e.message);
276
+ }
277
+
278
+ this.model.setProperty('/selectedIndex', indexArray.length - 1);
279
+ this.model.setProperty('/targetAggregation', controlAggregation);
280
+ this.model.setProperty('/index', indexArray);
281
+ }
282
+
283
+ /**
284
+ * Fills indexArray from selected control children
285
+ *
286
+ * @param selectedControlChildren Array of numbers
287
+ * @returns Array of key value pairs
288
+ */
289
+ private fillIndexArray(selectedControlChildren: number[]) {
290
+ let indexArray: { key: number; value: number }[] = [];
291
+ if (selectedControlChildren.length === 0) {
292
+ indexArray.push({ key: 0, value: 0 });
293
+ } else {
294
+ indexArray = selectedControlChildren.map((elem, index) => {
295
+ return { key: index + 1, value: elem + 1 };
296
+ });
297
+ indexArray.unshift({ key: 0, value: 0 });
298
+ indexArray.push({
299
+ key: selectedControlChildren.length + 1,
300
+ value: selectedControlChildren.length + 1
301
+ });
302
+ }
303
+ return indexArray;
304
+ }
305
+
306
+ /**
307
+ * Creates a new fragment for the specified control
308
+ *
309
+ * @param fragmentData Fragment Data
310
+ * @param fragmentData.index Index for XML Fragment placement
311
+ * @param fragmentData.fragmentName Fragment name
312
+ * @param fragmentData.targetAggregation Target aggregation for control
313
+ */
314
+ private async createNewFragment(fragmentData: CreateFragmentProps): Promise<void> {
315
+ const { fragmentName, index, targetAggregation } = fragmentData;
316
+ try {
317
+ await writeFragment<unknown>({ fragmentName });
318
+ MessageToast.show(`Fragment with name '${fragmentName}' was created.`);
319
+ } catch (e) {
320
+ // In case of error when creating a new fragment, we should not create a change file
321
+ MessageToast.show(e.message);
322
+ throw new Error(e.message);
323
+ }
324
+
325
+ await this.createFragmentChange({ fragmentName, index, targetAggregation });
326
+ }
327
+
328
+ /**
329
+ * Creates an addXML fragment command and pushes it to the command stack
330
+ *
331
+ * @param fragmentData Fragment Data
332
+ */
333
+ private async createFragmentChange(fragmentData: CreateFragmentProps) {
334
+ const { fragmentName, index, targetAggregation } = fragmentData;
335
+ let manifest: ManifestAppdescr;
336
+ try {
337
+ manifest = await getManifestAppdescr();
338
+
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
+ };
359
+
360
+ const overlay = OverlayRegistry.getOverlay(this.runtimeControl as UI5Element);
361
+ const designMetadata = overlay.getDesignTimeMetadata();
362
+
363
+ const modifiedValue = {
364
+ fragmentPath: `fragments/${fragmentName}.fragment.xml`,
365
+ index: index ?? 0,
366
+ targetAggregation: targetAggregation ?? 'content'
367
+ };
368
+
369
+ await this.commandExecutor.generateAndExecuteCommand(
370
+ this.runtimeControl,
371
+ 'addXML',
372
+ modifiedValue,
373
+ designMetadata,
374
+ flexSettings
375
+ );
376
+ }
377
+ }
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+
3
+ sap.ui.define(["sap/ui/core/mvc/XMLView", "./controllers/AddFragment.controller"], function (XMLView, __AddFragment) {
4
+ "use strict";
5
+
6
+ function _interopRequireDefault(obj) {
7
+ return obj && obj.__esModule && typeof obj.default !== "undefined" ? obj.default : obj;
8
+ }
9
+ const AddFragment = _interopRequireDefault(__AddFragment);
10
+ var DialogNames = /*#__PURE__*/function (DialogNames) {
11
+ DialogNames["ADD_FRAGMENT"] = "AddFragment";
12
+ return DialogNames;
13
+ }(DialogNames || {});
14
+ /**
15
+ * Adds a new item to the context menu
16
+ *
17
+ * @param rta Runtime Authoring
18
+ */
19
+ const initDialogs = rta => {
20
+ const contextMenu = rta.getDefaultPlugins().contextMenu;
21
+ contextMenu.addMenuItem({
22
+ id: 'ADD_FRAGMENT',
23
+ text: 'Add: Fragment',
24
+ handler: async overlays => await handler(overlays[0], rta, DialogNames.ADD_FRAGMENT),
25
+ icon: 'sap-icon://attachment-html'
26
+ });
27
+ };
28
+
29
+ /**
30
+ * Handler for new context menu entry
31
+ *
32
+ * @param overlays Control overlays
33
+ * @param rta Runtime Authoring
34
+ * @param dialogName Dialog name
35
+ */
36
+ async function handler(overlays, rta, dialogName) {
37
+ const controller = new AddFragment(`open.ux.preview.client.adp.controllers.${dialogName}`, overlays, rta);
38
+ await XMLView.create({
39
+ viewName: `open.ux.preview.client.adp.ui.${dialogName}`,
40
+ controller
41
+ });
42
+ }
43
+ var __exports = {
44
+ __esModule: true
45
+ };
46
+ __exports.DialogNames = DialogNames;
47
+ __exports.initDialogs = initDialogs;
48
+ __exports.handler = handler;
49
+ return __exports;
50
+ });
51
+ //# sourceMappingURL=init-dialogs.js.map
@@ -0,0 +1,43 @@
1
+ /** sap.ui.core */
2
+ import XMLView from 'sap/ui/core/mvc/XMLView';
3
+ import type UI5Element from 'sap/ui/core/Element';
4
+
5
+ /** sap.ui.rta */
6
+ import type RuntimeAuthoring from 'sap/ui/rta/RuntimeAuthoring';
7
+ import AddFragment from './controllers/AddFragment.controller';
8
+
9
+ export const enum DialogNames {
10
+ ADD_FRAGMENT = 'AddFragment'
11
+ }
12
+
13
+ /**
14
+ * Adds a new item to the context menu
15
+ *
16
+ * @param rta Runtime Authoring
17
+ */
18
+ export const initDialogs = (rta: RuntimeAuthoring): void => {
19
+ const contextMenu = rta.getDefaultPlugins().contextMenu;
20
+
21
+ contextMenu.addMenuItem({
22
+ id: 'ADD_FRAGMENT',
23
+ text: 'Add: Fragment',
24
+ handler: async (overlays: UI5Element[]) => await handler(overlays[0], rta, DialogNames.ADD_FRAGMENT),
25
+ icon: 'sap-icon://attachment-html'
26
+ });
27
+ };
28
+
29
+ /**
30
+ * Handler for new context menu entry
31
+ *
32
+ * @param overlays Control overlays
33
+ * @param rta Runtime Authoring
34
+ * @param dialogName Dialog name
35
+ */
36
+ export async function handler(overlays: UI5Element, rta: RuntimeAuthoring, dialogName: DialogNames): Promise<void> {
37
+ const controller = new AddFragment(`open.ux.preview.client.adp.controllers.${dialogName}`, overlays, rta);
38
+
39
+ await XMLView.create({
40
+ viewName: `open.ux.preview.client.adp.ui.${dialogName}`,
41
+ controller
42
+ });
43
+ }
@@ -1,17 +1,19 @@
1
1
  "use strict";
2
2
 
3
- sap.ui.define(["../cpe/init", "sap/base/Log"], function (__init, log) {
3
+ sap.ui.define(["sap/base/Log", "../cpe/init", "./init-dialogs"], function (log, __init, ___init_dialogs) {
4
4
  "use strict";
5
5
 
6
6
  function _interopRequireDefault(obj) {
7
7
  return obj && obj.__esModule && typeof obj.default !== "undefined" ? obj.default : obj;
8
8
  }
9
9
  const init = _interopRequireDefault(__init);
10
+ const initDialogs = ___init_dialogs["initDialogs"];
10
11
  var __exports = function (rta) {
11
- // custom adaptation project plugin code goes here
12
- log.debug('ADP init executed.');
12
+ // initialize fragment content menu entry
13
+ initDialogs(rta);
13
14
  // also initialize the editor
14
15
  init(rta);
16
+ log.debug('ADP init executed.');
15
17
  };
16
18
  return __exports;
17
19
  });
@@ -1,10 +1,12 @@
1
+ import log from 'sap/base/Log';
1
2
  import type RuntimeAuthoring from 'sap/ui/rta/RuntimeAuthoring';
2
3
  import init from '../cpe/init';
3
- import log from 'sap/base/Log';
4
+ import { initDialogs } from './init-dialogs';
4
5
 
5
- export default function(rta: RuntimeAuthoring) {
6
- // custom adaptation project plugin code goes here
7
- log.debug('ADP init executed.');
6
+ export default function (rta: RuntimeAuthoring) {
7
+ // initialize fragment content menu entry
8
+ initDialogs(rta);
8
9
  // also initialize the editor
9
10
  init(rta);
10
- }
11
+ log.debug('ADP init executed.');
12
+ }
@@ -0,0 +1,59 @@
1
+ <mvc:View xmlns:mvc="sap.ui.core.mvc"
2
+ xmlns="sap.m"
3
+ xmlns:core="sap.ui.core"
4
+ xmlns:f="sap.ui.layout.form">
5
+ <Dialog id="addNewFragmentDialog" title="Add XML Fragment" contentWidth="500px" class="sapUiRTABorder">
6
+ <content>
7
+ <f:SimpleForm id="addNewFragmentDialog_Form"
8
+ editable="true"
9
+ layout="ResponsiveGridLayout"
10
+ labelSpanS="4"
11
+ singleContainerFullSize="false">
12
+ <f:content>
13
+ <Label text="Control Type" />
14
+ <Text text="{/selectedControlName}" />
15
+ <Label text="Target Aggregation" />
16
+ <ComboBox
17
+ change="onAggregationChanged"
18
+ selectedKey="{/selectedAggregation/key}"
19
+ items="{
20
+ path: '/targetAggregation',
21
+ sorter: { path: 'text' }
22
+ }">
23
+ <core:Item key="{key}" text="{value}" />
24
+ </ComboBox>
25
+ <Label text="Index" />
26
+ <ComboBox
27
+ selectedKey='{/selectedIndex}'
28
+ items="{
29
+ path: '/index',
30
+ sorter: { path: 'text' }
31
+ }">
32
+ <core:Item key="{key}" text="{value}" />
33
+ </ComboBox>
34
+ <Label text="Fragment Name" />
35
+ <Input
36
+ description=".fragment.xml"
37
+ value="{/newFragmentName}"
38
+ liveChange="onFragmentNameInputChange">
39
+ </Input>
40
+ </f:content>
41
+ </f:SimpleForm>
42
+ </content>
43
+ <beginButton>
44
+ <Button
45
+ id="createDialogBtn"
46
+ text="Create"
47
+ press="onCreateBtnPress"
48
+ enabled="false"
49
+ type="Emphasized" />
50
+ </beginButton>
51
+ <endButton>
52
+ <Button
53
+ id="closeDialogBtn"
54
+ text="Cancel"
55
+ press="closeDialog"
56
+ type="Reject" />
57
+ </endButton>
58
+ </Dialog>
59
+ </mvc:View>