@sap-ux/preview-middleware 1.1.3 → 1.2.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.
- package/README.md +31 -29
- package/dist/base/config.d.ts +55 -2
- package/dist/base/config.js +158 -26
- package/dist/base/flp.d.ts +24 -0
- package/dist/base/flp.js +116 -18
- package/dist/client/adp/api-handler.js +5 -1
- package/dist/client/adp/api-handler.ts +11 -4
- package/dist/client/adp/controllers/ControllerExtension.controller.js +95 -35
- package/dist/client/adp/controllers/ControllerExtension.controller.ts +121 -42
- package/dist/client/adp/extend-controller.ts +1 -0
- package/dist/client/adp/quick-actions/common/add-controller-to-page.js +2 -2
- package/dist/client/adp/quick-actions/common/add-controller-to-page.ts +2 -2
- package/dist/client/adp/quick-actions/fe-v4/create-page-action.js +3 -2
- package/dist/client/adp/quick-actions/fe-v4/create-page-action.ts +3 -2
- package/dist/client/adp/quick-actions/fe-v4/create-table-action-config-change.js +3 -2
- package/dist/client/adp/quick-actions/fe-v4/create-table-action-config-change.ts +3 -2
- package/dist/client/adp/ui/ControllerExtension.fragment.xml +33 -6
- package/dist/client/adp/utils.js +73 -5
- package/dist/client/adp/utils.ts +84 -5
- package/dist/client/flp/WorkspaceConnector.js +7 -5
- package/dist/client/flp/WorkspaceConnector.ts +7 -5
- package/dist/client/flp/common.js +293 -1
- package/dist/client/flp/common.ts +344 -1
- package/dist/client/flp/sandbox1Init.js +187 -0
- package/dist/client/flp/sandbox1Init.ts +174 -0
- package/dist/client/flp/sandbox2AfterInit.js +66 -0
- package/dist/client/flp/sandbox2AfterInit.ts +63 -0
- package/dist/client/flp/sandbox2BeforeInit.js +67 -0
- package/dist/client/flp/sandbox2BeforeInit.ts +60 -0
- package/dist/client/messagebundle.properties +3 -0
- package/dist/types/index.d.ts +21 -0
- package/package.json +3 -3
- package/templates/flp/cdm.ejs +1 -1
- package/templates/flp/sandbox.ejs +1 -1
- package/templates/flp/sandbox2.ejs +12 -44
- package/dist/client/flp/init.js +0 -465
- package/dist/client/flp/init.ts +0 -498
package/dist/base/flp.js
CHANGED
|
@@ -13,7 +13,7 @@ import { isAppStudio, exposePort } from '@sap-ux/btp-utils';
|
|
|
13
13
|
import { FeatureToggleAccess } from '@sap-ux/feature-toggle';
|
|
14
14
|
import { deleteChange, readChanges, readLocalModulePaths, stripLocalModulesFromLrepResponse, writeChange } from './flex.js';
|
|
15
15
|
import { generateImportList, mergeTestConfigDefaults } from './test.js';
|
|
16
|
-
import { getFlpConfigWithDefaults, createFlpTemplateConfig, PREVIEW_URL, isFlexConnector, createTestTemplateConfig, addApp, getAppName, sanitizeRtaConfig, CARD_GENERATOR_DEFAULT, remapResourcesForPath } from './config.js';
|
|
16
|
+
import { getFlpConfigWithDefaults, createFlpTemplateConfig, PREVIEW_URL, isFlexConnector, createTestTemplateConfig, addApp, getAppName, sanitizeRtaConfig, CARD_GENERATOR_DEFAULT, remapResourcesForPath, generateSandboxAppConfig, qualifiesForNewSandbox } from './config.js';
|
|
17
17
|
import { generateCdm } from './cdm.js';
|
|
18
18
|
import { readFileSync } from 'node:fs';
|
|
19
19
|
import { getIntegrationCard } from './utils/cards.js';
|
|
@@ -89,7 +89,7 @@ export class FlpSandbox {
|
|
|
89
89
|
this.createFlexHandler();
|
|
90
90
|
this.flpConfig.libs ??= await this.hasLocateReuseLibsScript();
|
|
91
91
|
const id = manifest['sap.app']?.id ?? '';
|
|
92
|
-
this.templateConfig = createFlpTemplateConfig(this.flpConfig, manifest, resources, adp !== undefined);
|
|
92
|
+
this.templateConfig = createFlpTemplateConfig(this.flpConfig, manifest, resources, adp !== undefined, this.projectType !== 'EDMXBackend', this.logger);
|
|
93
93
|
this.adp = adp;
|
|
94
94
|
this.manifest = manifest;
|
|
95
95
|
await addApp(this.templateConfig, manifest, {
|
|
@@ -161,14 +161,15 @@ export class FlpSandbox {
|
|
|
161
161
|
* Also deletes the ABAP connector in case of a CAP project.
|
|
162
162
|
* Deletes all connectors if UI5 version is < 1.84 and served from npmjs.
|
|
163
163
|
*
|
|
164
|
+
* @param config - the template config to mutate (must be a per-request clone, not the shared this.templateConfig)
|
|
164
165
|
* @param ui5VersionMajor - the major version of UI5
|
|
165
166
|
* @param ui5VersionMinor - the minor version of UI5
|
|
166
167
|
* @param isCDN - whether the UI5 sources are served from CDN
|
|
167
168
|
* @private
|
|
168
169
|
*/
|
|
169
|
-
checkDeleteConnectors(ui5VersionMajor, ui5VersionMinor, isCDN) {
|
|
170
|
+
checkDeleteConnectors(config, ui5VersionMajor, ui5VersionMinor, isCDN) {
|
|
170
171
|
if (ui5VersionMajor === 1 && ui5VersionMinor < 84) {
|
|
171
|
-
|
|
172
|
+
config.ui5.flex = config.ui5?.flex?.filter((connector) => isFlexConnector(connector));
|
|
172
173
|
this.logger.debug(`The Fiori Tools local connector (WorkspaceConnector) is not being used because the current UI5 version does not support it.${isCDN ? 'The Fiori Tools fake connector (FakeLrepConnector) will be used instead.' : ''} `);
|
|
173
174
|
if (!isCDN) {
|
|
174
175
|
this.logger.warn(`The preview with virtual endpoints does not support flex changes for the current UI5 version ${ui5VersionMajor}.${ui5VersionMinor} from npmjs. Consider using a proxy to load UI5 resources from the CDN (e.g., https://ui5.sap.com), or upgrade the UI5 version in the yaml configuration to at least 1.84.`);
|
|
@@ -177,14 +178,6 @@ export class FlpSandbox {
|
|
|
177
178
|
else {
|
|
178
179
|
this.logger.debug(`The Fiori Tools local connector (WorkspaceConnector) is being used.`);
|
|
179
180
|
}
|
|
180
|
-
if (this.projectType === 'CAPJava' || this.projectType === 'CAPNodejs') {
|
|
181
|
-
this.templateConfig.ui5.flex = this.templateConfig.ui5?.flex?.filter((connector) => !isFlexConnector(connector) ||
|
|
182
|
-
(isFlexConnector(connector) && !connector.url?.startsWith('/sap/bc/lrep')));
|
|
183
|
-
this.logger.debug(`The ABAP connector is not being used because the current project type is '${this.projectType}'.`);
|
|
184
|
-
}
|
|
185
|
-
else {
|
|
186
|
-
this.logger.debug(`The ABAP connector is being used.`);
|
|
187
|
-
}
|
|
188
181
|
}
|
|
189
182
|
/**
|
|
190
183
|
* Generates the FLP sandbox for an editor.
|
|
@@ -201,7 +194,6 @@ export class FlpSandbox {
|
|
|
201
194
|
await this.setApplicationDependencies();
|
|
202
195
|
this.templateConfig.baseUrl = req['ui5-patched-router']?.baseUrl ?? '';
|
|
203
196
|
const ui5Version = await this.getUi5Version(req.protocol, req.headers.host, this.templateConfig.baseUrl);
|
|
204
|
-
this.checkDeleteConnectors(ui5Version.major, ui5Version.minor, ui5Version.isCdn);
|
|
205
197
|
if (ui5Version.major === 1 && ui5Version.minor <= 71) {
|
|
206
198
|
this.removeAsyncHintsRequests();
|
|
207
199
|
}
|
|
@@ -209,6 +201,8 @@ export class FlpSandbox {
|
|
|
209
201
|
this.removeFlexExtensionPointEnabled();
|
|
210
202
|
}
|
|
211
203
|
const config = structuredClone(this.templateConfig);
|
|
204
|
+
config.ui5.versionMajor = ui5Version.major;
|
|
205
|
+
this.checkDeleteConnectors(config, ui5Version.major, ui5Version.minor, ui5Version.isCdn);
|
|
212
206
|
if (!config.ui5.libs.includes('sap.ui.rta')) {
|
|
213
207
|
// sap.ui.rta needs to be added to the list of preload libs for variants management and adaptation projects
|
|
214
208
|
config.ui5.libs += ',sap.ui.rta';
|
|
@@ -226,7 +220,7 @@ export class FlpSandbox {
|
|
|
226
220
|
config.features = FeatureToggleAccess.getAllFeatureToggles();
|
|
227
221
|
const appId = this.manifest['sap.app']?.id ?? '';
|
|
228
222
|
remapResourcesForPath(config, editor.path, appId);
|
|
229
|
-
return render(this.getSandboxTemplate(ui5Version), config);
|
|
223
|
+
return render(await this.getSandboxTemplate(ui5Version), config);
|
|
230
224
|
}
|
|
231
225
|
/**
|
|
232
226
|
* Sets application dependencies in the template configuration.
|
|
@@ -359,15 +353,56 @@ export class FlpSandbox {
|
|
|
359
353
|
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
|
360
354
|
this.templateConfig.baseUrl = ('ui5-patched-router' in req && req['ui5-patched-router']?.baseUrl) || '';
|
|
361
355
|
const ui5Version = await this.getUi5VersionFromRequest(req, this.templateConfig.baseUrl);
|
|
362
|
-
this.
|
|
356
|
+
this.templateConfig.ui5.versionMajor = ui5Version.major;
|
|
357
|
+
this.checkDeleteConnectors(this.templateConfig, ui5Version.major, ui5Version.minor, ui5Version.isCdn);
|
|
363
358
|
if (ui5Version.major === 1 && ui5Version.minor < 120) {
|
|
364
359
|
this.removeFlexExtensionPointEnabled();
|
|
365
360
|
}
|
|
366
361
|
//for consistency reasons, we also add the baseUrl to the HTML here, although it is only used in editor mode
|
|
367
|
-
const html = render(this.getSandboxTemplate(ui5Version), this.templateConfig);
|
|
362
|
+
const html = render(await this.getSandboxTemplate(ui5Version), this.templateConfig);
|
|
368
363
|
this.sendResponse(res, 'text/html', 200, html);
|
|
369
364
|
}
|
|
370
365
|
}
|
|
366
|
+
/**
|
|
367
|
+
* Handler for GET requests to fioriSandboxAppConfig.json (Sandbox 2.0).
|
|
368
|
+
* Serves a virtual config, optionally merged with a user-provided file.
|
|
369
|
+
* If a real HTML file exists at the FLP path, no virtual config is served.
|
|
370
|
+
*
|
|
371
|
+
* @param req incoming request
|
|
372
|
+
* @param res server response
|
|
373
|
+
* @param next next middleware function
|
|
374
|
+
* @param configJsonPath project-relative path to fioriSandboxAppConfig.json
|
|
375
|
+
*/
|
|
376
|
+
async sandboxAppConfigGetHandler(req, res, next, configJsonPath) {
|
|
377
|
+
const baseUrl = (this.templateConfig.baseUrl =
|
|
378
|
+
('ui5-patched-router' in req && req['ui5-patched-router']?.baseUrl) || '');
|
|
379
|
+
const file = await this.project.byPath(`${baseUrl}${this.flpConfig.path}`);
|
|
380
|
+
if (file) {
|
|
381
|
+
this.logger.info(`HTML file returned at '${this.flpConfig.path}'. No virtual 'fioriSandboxAppConfig.json' will be served.`);
|
|
382
|
+
next();
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
// If the legacy fioriSandboxConfig.json exists, sandbox 2 is disabled — no config to serve.
|
|
386
|
+
const legacyFile = await this.project.byPath(`${baseUrl}/appconfig/fioriSandboxConfig.json`);
|
|
387
|
+
if (legacyFile) {
|
|
388
|
+
next();
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
// check for user-provided fioriSandboxAppConfig.json and merge if present
|
|
392
|
+
const userConfigFile = await this.project.byPath(`${baseUrl}${configJsonPath}`);
|
|
393
|
+
let config = generateSandboxAppConfig(this.templateConfig, this.flpConfig, this.adp !== undefined, this.projectType !== 'EDMXBackend', this.logger);
|
|
394
|
+
if (userConfigFile) {
|
|
395
|
+
const userConfig = JSON.parse(await userConfigFile.getString());
|
|
396
|
+
config = {
|
|
397
|
+
...config,
|
|
398
|
+
...userConfig,
|
|
399
|
+
// beforeFlpStart and afterFlpStart must always point to our hooks
|
|
400
|
+
beforeFlpStart: config.beforeFlpStart,
|
|
401
|
+
afterFlpStart: config.afterFlpStart
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
this.sendResponse(res, 'application/json', 200, JSON.stringify(config));
|
|
405
|
+
}
|
|
371
406
|
/**
|
|
372
407
|
* Add routes for html and scripts required for a local FLP.
|
|
373
408
|
*/
|
|
@@ -378,6 +413,35 @@ export class FlpSandbox {
|
|
|
378
413
|
this.router.get(this.flpConfig.path, async (req, res, next) => {
|
|
379
414
|
await this.flpGetHandler(req, res, next);
|
|
380
415
|
});
|
|
416
|
+
// add routes for fioriSandboxAppConfig.json (Sandbox 2.0) — only when sandbox 2 is not explicitly disabled.
|
|
417
|
+
// The config must be served at the directory of every sandbox HTML endpoint,
|
|
418
|
+
// since each endpoint fetches fioriSandboxAppConfig.json relative to its own location.
|
|
419
|
+
if (this.flpConfig.useNewSandbox === true) {
|
|
420
|
+
const sandboxDirs = new Set();
|
|
421
|
+
sandboxDirs.add(dirname(this.flpConfig.path));
|
|
422
|
+
if (this.cardGenerator?.path) {
|
|
423
|
+
const cardGeneratorPath = this.cardGenerator.path.startsWith('/')
|
|
424
|
+
? this.cardGenerator.path
|
|
425
|
+
: `/${this.cardGenerator.path}`;
|
|
426
|
+
sandboxDirs.add(dirname(cardGeneratorPath));
|
|
427
|
+
}
|
|
428
|
+
if (this.rta) {
|
|
429
|
+
for (const editor of this.rta.endpoints) {
|
|
430
|
+
sandboxDirs.add(dirname(editor.path));
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
// Register one route per directory rather than passing an array to router.get()
|
|
434
|
+
// sandboxAppConfigGetHandler needs the concrete configJsonPath to look up the user-provided
|
|
435
|
+
// file via project.byPath(), and connect.IncomingMessage does not expose req.path,
|
|
436
|
+
// so we cannot derive the path from the request inside the handler.
|
|
437
|
+
for (const dir of sandboxDirs) {
|
|
438
|
+
const configJsonPath = `${dir}/fioriSandboxAppConfig.json`;
|
|
439
|
+
this.logger.debug(`Add route for ${configJsonPath}`);
|
|
440
|
+
this.router.get(configJsonPath, async (req, res, next) => {
|
|
441
|
+
await this.sandboxAppConfigGetHandler(req, res, next, configJsonPath);
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
}
|
|
381
445
|
}
|
|
382
446
|
/**
|
|
383
447
|
* Adds a middleware route for the Card Generator in the FLP sandbox.
|
|
@@ -463,6 +527,11 @@ export class FlpSandbox {
|
|
|
463
527
|
this.flpConfig.enhancedHomePage = this.templateConfig.enhancedHomePage = false;
|
|
464
528
|
this.logger.warn(`Feature enhancedHomePage disabled: UI5 version: ${version} not supported.`);
|
|
465
529
|
}
|
|
530
|
+
// enhancedHomePage (CDM) is not supported with Sandbox 2 — fall back to sandbox 1
|
|
531
|
+
if (this.flpConfig.enhancedHomePage && qualifiesForNewSandbox({ major, minor, patch, label, isCdn })) {
|
|
532
|
+
this.flpConfig.useNewSandbox = false;
|
|
533
|
+
this.logger.warn(`New FLP Sandbox disabled: enhancedHomePage is not supported with Sandbox 2.`);
|
|
534
|
+
}
|
|
466
535
|
return {
|
|
467
536
|
major,
|
|
468
537
|
minor,
|
|
@@ -473,13 +542,22 @@ export class FlpSandbox {
|
|
|
473
542
|
}
|
|
474
543
|
/**
|
|
475
544
|
* Read the sandbox template file based on the given UI5 version.
|
|
545
|
+
* Also checks for a legacy 'appconfig/fioriSandboxConfig.json' and falls back to Sandbox 1 if found.
|
|
476
546
|
*
|
|
477
547
|
* @param ui5Version - the UI5 version
|
|
478
548
|
* @returns the template for the sandbox HTML file
|
|
479
549
|
*/
|
|
480
|
-
getSandboxTemplate(ui5Version) {
|
|
550
|
+
async getSandboxTemplate(ui5Version) {
|
|
481
551
|
this.logger.info(`Using sandbox template for UI5 version: ${ui5Version.major}.${ui5Version.minor}.${ui5Version.patch}${ui5Version.label ? `-${ui5Version.label}` : ''}.`);
|
|
482
|
-
const
|
|
552
|
+
const qualifies = qualifiesForNewSandbox(ui5Version);
|
|
553
|
+
const legacyConfigPresent = qualifies
|
|
554
|
+
? await this.hasLegacySandboxConfig(ui5Version, this.templateConfig.baseUrl)
|
|
555
|
+
: false;
|
|
556
|
+
const useNewSandbox = qualifies && this.flpConfig.useNewSandbox === true && !legacyConfigPresent;
|
|
557
|
+
if (qualifies && !useNewSandbox && !this.flpConfig.enhancedHomePage) {
|
|
558
|
+
this.logger.info('New FLP Sandbox disabled in configuration.');
|
|
559
|
+
}
|
|
560
|
+
const filePrefix = useNewSandbox ? '2' : '';
|
|
483
561
|
const template = this.flpConfig.enhancedHomePage ? 'cdm' : 'sandbox';
|
|
484
562
|
return readFileSync(join(__dirname, `../../templates/flp/${template}${filePrefix}.ejs`), 'utf-8');
|
|
485
563
|
}
|
|
@@ -495,6 +573,26 @@ export class FlpSandbox {
|
|
|
495
573
|
}
|
|
496
574
|
}
|
|
497
575
|
}
|
|
576
|
+
/**
|
|
577
|
+
* Checks if a legacy 'appconfig/fioriSandboxConfig.json' file is present when using the new FLP Sandbox.
|
|
578
|
+
* If found, warns the user to migrate and returns true so the caller can fall back to the classic Sandbox
|
|
579
|
+
* for this request.
|
|
580
|
+
*
|
|
581
|
+
* @param ui5Version - the resolved UI5 version
|
|
582
|
+
* @param baseUrl - the base URL of the current request
|
|
583
|
+
* @returns true if the legacy file is present and sandbox 2 should be suppressed
|
|
584
|
+
* @private
|
|
585
|
+
*/
|
|
586
|
+
async hasLegacySandboxConfig(ui5Version, baseUrl) {
|
|
587
|
+
if (qualifiesForNewSandbox(ui5Version) && this.flpConfig.useNewSandbox === true) {
|
|
588
|
+
const legacyFile = await this.project.byPath(`${baseUrl}/appconfig/fioriSandboxConfig.json`);
|
|
589
|
+
if (legacyFile) {
|
|
590
|
+
this.logger.warn(`Found legacy file at 'appconfig/fioriSandboxConfig.json'. Falling back to the classic Sandbox. Please migrate your application configuration first.`);
|
|
591
|
+
return true;
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
return false;
|
|
595
|
+
}
|
|
498
596
|
/**
|
|
499
597
|
* For UI5 versions below 1.120, flexExtensionPointEnabled must be removed from the application
|
|
500
598
|
* dependencies manifest. Older UI5 versions cannot handle this property at bootstrap time.
|
|
@@ -125,12 +125,16 @@ sap.ui.define(["../utils/error"], function (___utils_errorjs) {
|
|
|
125
125
|
* Checks for existing controller in the project's workspace
|
|
126
126
|
*
|
|
127
127
|
* @param controllerName Name of the controller
|
|
128
|
+
* @param viewId ID of the current view, used to detect an existing instance-specific extension
|
|
128
129
|
* @returns {CodeExtResponse} Returns path to existing controller if found
|
|
129
130
|
*/
|
|
130
|
-
async function getExistingController(controllerName) {
|
|
131
|
+
async function getExistingController(controllerName, viewId) {
|
|
131
132
|
const params = new URLSearchParams({
|
|
132
133
|
name: controllerName
|
|
133
134
|
});
|
|
135
|
+
if (viewId) {
|
|
136
|
+
params.append('viewId', viewId);
|
|
137
|
+
}
|
|
134
138
|
const url = `${ApiEndpoints.CODE_EXT}?${params.toString()}`;
|
|
135
139
|
return request(url, RequestMethod.GET);
|
|
136
140
|
}
|
|
@@ -31,9 +31,12 @@ export interface FragmentsResponse {
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
export interface CodeExtResponse {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
baseControllerExists: boolean;
|
|
35
|
+
baseControllerPath: string;
|
|
36
|
+
baseControllerPathFromRoot: string;
|
|
37
|
+
instanceControllerExists: boolean;
|
|
38
|
+
instanceControllerPath: string;
|
|
39
|
+
instanceControllerPathFromRoot: string;
|
|
37
40
|
isRunningInBAS: boolean;
|
|
38
41
|
isTsSupported: boolean;
|
|
39
42
|
}
|
|
@@ -172,10 +175,14 @@ export async function getDataSourceAnnotationFileMap(): Promise<AnnotationDataSo
|
|
|
172
175
|
* Checks for existing controller in the project's workspace
|
|
173
176
|
*
|
|
174
177
|
* @param controllerName Name of the controller
|
|
178
|
+
* @param viewId ID of the current view, used to detect an existing instance-specific extension
|
|
175
179
|
* @returns {CodeExtResponse} Returns path to existing controller if found
|
|
176
180
|
*/
|
|
177
|
-
export async function getExistingController(controllerName: string): Promise<CodeExtResponse> {
|
|
181
|
+
export async function getExistingController(controllerName: string, viewId?: string): Promise<CodeExtResponse> {
|
|
178
182
|
const params = new URLSearchParams({ name: controllerName });
|
|
183
|
+
if (viewId) {
|
|
184
|
+
params.append('viewId', viewId);
|
|
185
|
+
}
|
|
179
186
|
const url = `${ApiEndpoints.CODE_EXT}?${params.toString()}` as ApiEndpoints;
|
|
180
187
|
return request<CodeExtResponse>(url, RequestMethod.GET);
|
|
181
188
|
}
|
|
@@ -25,6 +25,7 @@ sap.ui.define(["sap/ui/core/library", "sap/ui/model/json/JSONModel", "open/ux/pr
|
|
|
25
25
|
const CommandExecutor = _interopRequireDefault(__CommandExecutor);
|
|
26
26
|
const checkForExistingChange = ___utilsjs["checkForExistingChange"];
|
|
27
27
|
const getControllerInfo = ___utilsjs["getControllerInfo"];
|
|
28
|
+
const getPendingCodeExtViewIds = ___utilsjs["getPendingCodeExtViewIds"];
|
|
28
29
|
const BaseDialog = _interopRequireDefault(__BaseDialog);
|
|
29
30
|
/**
|
|
30
31
|
* @namespace open.ux.preview.client.adp.controllers
|
|
@@ -47,6 +48,7 @@ sap.ui.define(["sap/ui/core/library", "sap/ui/model/json/JSONModel", "open/ux/pr
|
|
|
47
48
|
this.setEscapeHandler();
|
|
48
49
|
const resourceModel = await getResourceModel('open.ux.preview.client');
|
|
49
50
|
this.bundle = await getTextBundle();
|
|
51
|
+
this.ui5Version = await getUi5Version();
|
|
50
52
|
await this.buildDialogData();
|
|
51
53
|
this.dialog.setModel(resourceModel, 'i18n');
|
|
52
54
|
this.dialog.setModel(this.model);
|
|
@@ -106,16 +108,18 @@ sap.ui.define(["sap/ui/core/library", "sap/ui/model/json/JSONModel", "open/ux/pr
|
|
|
106
108
|
source.setEnabled(false);
|
|
107
109
|
const controllerName = this.model.getProperty('/newControllerName');
|
|
108
110
|
const viewId = this.model.getProperty('/viewId');
|
|
111
|
+
const isInstanceSpecific = this.model.getProperty('/isInstanceSpecific');
|
|
109
112
|
const controllerRef = {
|
|
110
113
|
codeRef: `coding/${controllerName}.js`,
|
|
111
|
-
viewId
|
|
114
|
+
viewId,
|
|
115
|
+
instanceSpecific: isInstanceSpecific
|
|
112
116
|
};
|
|
113
117
|
if (this.data) {
|
|
114
118
|
this.data.deferred.resolve(controllerRef);
|
|
115
119
|
} else {
|
|
116
120
|
await this.createNewController(controllerName, controllerRef);
|
|
117
121
|
}
|
|
118
|
-
if (this.data &&
|
|
122
|
+
if (this.data && this.isControllerExtensionSupported()) {
|
|
119
123
|
await sendInfoCenterMessage({
|
|
120
124
|
title: {
|
|
121
125
|
key: 'ADP_CREATE_CONTROLLER_EXTENSION_TITLE'
|
|
@@ -127,9 +131,6 @@ sap.ui.define(["sap/ui/core/library", "sap/ui/model/json/JSONModel", "open/ux/pr
|
|
|
127
131
|
type: MessageBarType.info
|
|
128
132
|
});
|
|
129
133
|
}
|
|
130
|
-
} else {
|
|
131
|
-
const controllerPath = this.model.getProperty('/controllerPath');
|
|
132
|
-
window.open(`vscode://file${controllerPath}`);
|
|
133
134
|
}
|
|
134
135
|
this.handleDialogClose();
|
|
135
136
|
},
|
|
@@ -143,41 +144,62 @@ sap.ui.define(["sap/ui/core/library", "sap/ui/model/json/JSONModel", "open/ux/pr
|
|
|
143
144
|
controllerName,
|
|
144
145
|
viewId
|
|
145
146
|
} = getControllerInfo(overlayControl);
|
|
146
|
-
const data = await this.getExistingController(controllerName);
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
147
|
+
const data = await this.getExistingController(controllerName, viewId);
|
|
148
|
+
if (!data) {
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Combine persisted (server) and pending (command stack) changes to determine whether a base
|
|
153
|
+
// page controller and/or an instance-specific controller for this view already exist.
|
|
154
|
+
const pendingViewIds = getPendingCodeExtViewIds(this.rta, controllerName);
|
|
155
|
+
const baseExists = data.baseControllerExists || pendingViewIds.some(id => !id);
|
|
156
|
+
const instanceExists = data.instanceControllerExists || pendingViewIds.includes(viewId);
|
|
157
|
+
const showInstanceSpecificOption = this.isInstanceSpecificSupported();
|
|
158
|
+
if (!showInstanceSpecificOption) {
|
|
159
|
+
if (pendingViewIds.length > 0) {
|
|
150
160
|
this.updateModelForExistingPendingChange();
|
|
151
|
-
} else if (data
|
|
152
|
-
this.updateModelForExistingController(data);
|
|
161
|
+
} else if (data.baseControllerExists) {
|
|
162
|
+
this.updateModelForExistingController(data, true);
|
|
153
163
|
} else {
|
|
154
|
-
this.updateModelForNewController(viewId, data.isTsSupported);
|
|
164
|
+
this.updateModelForNewController(viewId, data.isTsSupported, false, false, false);
|
|
155
165
|
await this.getControllers();
|
|
156
166
|
}
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
if (baseExists && instanceExists) {
|
|
170
|
+
if (data.baseControllerExists || data.instanceControllerExists) {
|
|
171
|
+
this.updateModelForExistingController(data);
|
|
172
|
+
} else {
|
|
173
|
+
this.updateModelForExistingPendingChange();
|
|
174
|
+
}
|
|
175
|
+
return;
|
|
157
176
|
}
|
|
177
|
+
this.updateModelForNewController(viewId, data.isTsSupported, true, baseExists, instanceExists);
|
|
178
|
+
await this.getControllers();
|
|
158
179
|
},
|
|
159
180
|
/**
|
|
160
|
-
* Updates the model properties for
|
|
181
|
+
* Updates the model properties for existing controller(s).
|
|
182
|
+
* Shows all persisted controllers (base and/or instance) in the existing-controller form, each with its own link to open in VS Code.
|
|
161
183
|
*
|
|
162
|
-
* @param
|
|
184
|
+
* @param data - Server response containing existence flags and file paths.
|
|
185
|
+
* @param showVsCodeButton - When true (pre-1.143 single-controller path), shows an "Open in VS Code" begin-button instead of relying on the inline fragment links.
|
|
163
186
|
*/
|
|
164
|
-
updateModelForExistingController: function _updateModelForExistingController(data) {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
this.model.setProperty('/
|
|
172
|
-
this.model.setProperty('/
|
|
173
|
-
this.model.setProperty('/controllerPathFromRoot', controllerPathFromRoot);
|
|
187
|
+
updateModelForExistingController: function _updateModelForExistingController(data, showVsCodeButton = false) {
|
|
188
|
+
this.model.setProperty('/controllerExists', true);
|
|
189
|
+
this.model.setProperty('/baseControllerExists', data.baseControllerExists);
|
|
190
|
+
this.model.setProperty('/baseControllerPath', data.baseControllerPath);
|
|
191
|
+
this.model.setProperty('/baseControllerPathFromRoot', data.baseControllerPathFromRoot);
|
|
192
|
+
this.model.setProperty('/instanceControllerExists', data.instanceControllerExists);
|
|
193
|
+
this.model.setProperty('/instanceControllerPath', data.instanceControllerPath);
|
|
194
|
+
this.model.setProperty('/instanceControllerPathFromRoot', data.instanceControllerPathFromRoot);
|
|
195
|
+
this.model.setProperty('/isRunningInBAS', data.isRunningInBAS);
|
|
174
196
|
this.model.setProperty('/inputFormVisibility', false);
|
|
175
197
|
this.model.setProperty('/pendingChangeFormVisibility', false);
|
|
176
198
|
this.model.setProperty('/existingControllerFormVisibility', true);
|
|
177
|
-
if (isRunningInBAS) {
|
|
178
|
-
this.dialog.getBeginButton().setVisible(false);
|
|
179
|
-
} else {
|
|
199
|
+
if (showVsCodeButton && !data.isRunningInBAS) {
|
|
180
200
|
this.dialog.getBeginButton().setText('Open in VS Code').setEnabled(true);
|
|
201
|
+
} else {
|
|
202
|
+
this.dialog.getBeginButton().setVisible(false);
|
|
181
203
|
}
|
|
182
204
|
this.dialog.getEndButton().setText('Close');
|
|
183
205
|
},
|
|
@@ -196,24 +218,34 @@ sap.ui.define(["sap/ui/core/library", "sap/ui/model/json/JSONModel", "open/ux/pr
|
|
|
196
218
|
*
|
|
197
219
|
* @param {string} viewId - The view ID.
|
|
198
220
|
* @param {boolean} isTsSupported - Whether TypeScript supported for the current project.
|
|
221
|
+
* @param {boolean} showInstanceSpecificOption - Whether to show the instance-specific radio button option.
|
|
222
|
+
* @param {boolean} baseExists - Whether a base page controller extension already exists.
|
|
223
|
+
* @param {boolean} instanceExists - Whether an instance-specific extension already exists for this view.
|
|
199
224
|
*/
|
|
200
|
-
updateModelForNewController: function _updateModelForNewController(viewId, isTsSupported) {
|
|
225
|
+
updateModelForNewController: function _updateModelForNewController(viewId, isTsSupported, showInstanceSpecificOption, baseExists, instanceExists) {
|
|
201
226
|
this.model.setProperty('/viewId', viewId);
|
|
202
227
|
this.model.setProperty('/controllerExtension', isTsSupported ? '.ts' : '.js');
|
|
203
228
|
this.model.setProperty('/existingControllerFormVisibility', false);
|
|
204
229
|
this.model.setProperty('/pendingChangeFormVisibility', false);
|
|
205
230
|
this.model.setProperty('/inputFormVisibility', true);
|
|
231
|
+
this.model.setProperty('/instanceSpecificVisibility', showInstanceSpecificOption);
|
|
232
|
+
this.model.setProperty('/baseControllerEnabled', !baseExists);
|
|
233
|
+
this.model.setProperty('/instanceControllerEnabled', !instanceExists);
|
|
234
|
+
const selectedIndex = baseExists ? 1 : 0;
|
|
235
|
+
this.model.setProperty('/controllerTypeSelectedIndex', selectedIndex);
|
|
236
|
+
this.model.setProperty('/isInstanceSpecific', selectedIndex === 1);
|
|
206
237
|
},
|
|
207
238
|
/**
|
|
208
239
|
* Retrieves existing controller data if found in the project's workspace.
|
|
209
240
|
*
|
|
210
241
|
* @param controllerName Controller name that exists in the view.
|
|
242
|
+
* @param viewId ID of the current view, used to detect an existing instance-specific extension.
|
|
211
243
|
* @returns Returns existing controller data.
|
|
212
244
|
*/
|
|
213
|
-
getExistingController: async function _getExistingController(controllerName) {
|
|
245
|
+
getExistingController: async function _getExistingController(controllerName, viewId) {
|
|
214
246
|
let data;
|
|
215
247
|
try {
|
|
216
|
-
data = await getExistingController(controllerName);
|
|
248
|
+
data = await getExistingController(controllerName, viewId);
|
|
217
249
|
} catch (e) {
|
|
218
250
|
const error = getError(e);
|
|
219
251
|
await sendInfoCenterMessage({
|
|
@@ -255,7 +287,7 @@ sap.ui.define(["sap/ui/core/library", "sap/ui/model/json/JSONModel", "open/ux/pr
|
|
|
255
287
|
* @param controllerRef Controller reference
|
|
256
288
|
*/
|
|
257
289
|
createNewController: async function _createNewController(controllerName, controllerRef) {
|
|
258
|
-
if (
|
|
290
|
+
if (this.isControllerExtensionSupported()) {
|
|
259
291
|
await this.createControllerCommand(controllerName, controllerRef);
|
|
260
292
|
return;
|
|
261
293
|
}
|
|
@@ -264,7 +296,7 @@ sap.ui.define(["sap/ui/core/library", "sap/ui/model/json/JSONModel", "open/ux/pr
|
|
|
264
296
|
controllerName
|
|
265
297
|
});
|
|
266
298
|
const service = await this.rta.getService('controllerExtension');
|
|
267
|
-
const change = await service.add(controllerRef.codeRef, controllerRef.viewId);
|
|
299
|
+
const change = await service.add(controllerRef.codeRef, controllerRef.viewId, controllerRef.instanceSpecific);
|
|
268
300
|
change.creation = new Date().toISOString();
|
|
269
301
|
await writeChange(change);
|
|
270
302
|
await sendInfoCenterMessage({
|
|
@@ -315,9 +347,32 @@ sap.ui.define(["sap/ui/core/library", "sap/ui/model/json/JSONModel", "open/ux/pr
|
|
|
315
347
|
type: MessageBarType.info
|
|
316
348
|
});
|
|
317
349
|
},
|
|
318
|
-
isControllerExtensionSupported:
|
|
319
|
-
|
|
320
|
-
|
|
350
|
+
isControllerExtensionSupported: function _isControllerExtensionSupported() {
|
|
351
|
+
return !isLowerThanMinimalUi5Version(this.ui5Version, ControllerExtension.CONTROLLER_EXT_MIN_UI5_VERSION);
|
|
352
|
+
},
|
|
353
|
+
/**
|
|
354
|
+
* Handles the selection change on the controller type for the radio button group.
|
|
355
|
+
*
|
|
356
|
+
* @param event Event
|
|
357
|
+
*/
|
|
358
|
+
onControllerTypeSelectionChange: function _onControllerTypeSelectionChange(event) {
|
|
359
|
+
const group = event.getSource();
|
|
360
|
+
this.model.setProperty('/isInstanceSpecific', group.getSelectedIndex() === 1);
|
|
361
|
+
},
|
|
362
|
+
/**
|
|
363
|
+
* Opens the base page controller extension file in VS Code.
|
|
364
|
+
*/
|
|
365
|
+
onOpenBaseController: function _onOpenBaseController() {
|
|
366
|
+
window.open(`vscode://file${this.model.getProperty('/baseControllerPath')}`);
|
|
367
|
+
},
|
|
368
|
+
/**
|
|
369
|
+
* Opens the instance-specific controller extension file in VS Code.
|
|
370
|
+
*/
|
|
371
|
+
onOpenInstanceController: function _onOpenInstanceController() {
|
|
372
|
+
window.open(`vscode://file${this.model.getProperty('/instanceControllerPath')}`);
|
|
373
|
+
},
|
|
374
|
+
isInstanceSpecificSupported: function _isInstanceSpecificSupported() {
|
|
375
|
+
return !isLowerThanMinimalUi5Version(this.ui5Version, ControllerExtension.INSTANCE_SPECIFIC_MIN_UI5_VERSION);
|
|
321
376
|
}
|
|
322
377
|
});
|
|
323
378
|
/* The minimum version of UI5 framework which supports controller extensions. */
|
|
@@ -325,6 +380,11 @@ sap.ui.define(["sap/ui/core/library", "sap/ui/model/json/JSONModel", "open/ux/pr
|
|
|
325
380
|
major: 1,
|
|
326
381
|
minor: 135
|
|
327
382
|
};
|
|
383
|
+
/* The minimum version of UI5 framework which supports instance-specific controller extensions. */
|
|
384
|
+
ControllerExtension.INSTANCE_SPECIFIC_MIN_UI5_VERSION = {
|
|
385
|
+
major: 1,
|
|
386
|
+
minor: 143
|
|
387
|
+
};
|
|
328
388
|
return ControllerExtension;
|
|
329
389
|
});
|
|
330
390
|
//# sourceMappingURL=ControllerExtension.controller.js.map
|