@sap/ux-ui5-tooling 1.24.0 → 1.25.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,128 @@
1
+ "use strict";
2
+
3
+ sap.ui.define(["sap/base/Log", "open/ux/preview/client/thirdparty/@sap-ux-private/control-property-editor-common", "../utils/info-center-message"], function (log, ___sap_ux_private_control_property_editor_common, ___utils_info_center_message) {
4
+ "use strict";
5
+
6
+ const MessageBarType = ___sap_ux_private_control_property_editor_common["MessageBarType"];
7
+ const sendInfoCenterMessage = ___utils_info_center_message["sendInfoCenterMessage"];
8
+ const CHANGE_TYPE = {
9
+ addXML: 'addXML',
10
+ codeExt: 'codeExt'
11
+ };
12
+ /**
13
+ * Type guard for changes that reference a fragment or controller extension file.
14
+ *
15
+ * @param change flex change object
16
+ * @returns true if the change is an addXML with fragmentPath or a codeExt with codeRef
17
+ */
18
+ function isFragmentOrCodeExtChange(change) {
19
+ return !!change.reference && (change.changeType === CHANGE_TYPE.addXML && !!change.content?.fragmentPath || change.changeType === CHANGE_TYPE.codeExt && !!change.content?.codeRef);
20
+ }
21
+
22
+ /**
23
+ * Builds a lookup map from module name patterns to change metadata
24
+ * for addXML and codeExt changes.
25
+ *
26
+ * @param changes record of change objects keyed by flex key
27
+ * @returns map from module name substring to orphaned change entry
28
+ */
29
+ function buildModuleNameMap(changes) {
30
+ const map = new Map();
31
+ for (const change of Object.values(changes)) {
32
+ if (!isFragmentOrCodeExtChange(change)) {
33
+ continue;
34
+ }
35
+ const prefix = change.reference.replaceAll('.', '/');
36
+ const path = change.changeType === CHANGE_TYPE.addXML ? change.content?.fragmentPath ?? '' : change.content?.codeRef ?? '';
37
+ const changeFileName = `${change.fileName}.${change.fileType ?? 'change'}`;
38
+ const key = change.moduleName ?? `${prefix}/changes/${path}`;
39
+ map.set(key, {
40
+ changeFileName,
41
+ filePath: path,
42
+ changeType: change.changeType
43
+ });
44
+ }
45
+ return map;
46
+ }
47
+
48
+ /**
49
+ * Creates an error handler that matches error messages against known module names
50
+ * and sends InfoCenter errors for orphaned change files.
51
+ *
52
+ * @param moduleNameMap map from module name substring to orphaned change entry
53
+ * @returns error handler function
54
+ */
55
+ function createErrorHandler(moduleNameMap, restoreConsole) {
56
+ return message => {
57
+ for (const [moduleName, entry] of moduleNameMap) {
58
+ if (message.includes(moduleName)) {
59
+ sendInfoCenterMessage({
60
+ title: {
61
+ key: 'ADP_ORPHANED_CHANGE_ERROR_TITLE'
62
+ },
63
+ description: {
64
+ key: 'ADP_ORPHANED_FILE_DESCRIPTION',
65
+ params: [entry.filePath, entry.changeFileName]
66
+ },
67
+ type: MessageBarType.error
68
+ }).catch(error => {
69
+ log.error('Failed to send orphaned change InfoCenter message', error);
70
+ });
71
+ moduleNameMap.delete(moduleName);
72
+ if (moduleNameMap.size === 0) {
73
+ restoreConsole();
74
+ }
75
+ break;
76
+ }
77
+ }
78
+ };
79
+ }
80
+
81
+ /**
82
+ * Initializes orphaned change file detection.
83
+ *
84
+ * Fetches loaded flex changes, builds a lookup map of module names for addXML and codeExt changes,
85
+ * and wraps console.error to intercept UI5 flex change application errors. When UI5 fails to load
86
+ * a fragment or controller extension referenced by a change file, the error is intercepted and an
87
+ * actionable message is shown in the InfoCenter advising the user to delete the orphaned change file.
88
+ */
89
+ async function initOrphanedChangeDetection() {
90
+ const baseUrl = document.getElementById('sap-ui-bootstrap')?.dataset.openUxPreviewBaseUrl ?? '';
91
+ const response = await fetch(`${baseUrl}/preview/api/changes`, {
92
+ method: 'GET',
93
+ headers: {
94
+ 'content-type': 'application/json'
95
+ }
96
+ });
97
+ if (!response.ok) {
98
+ log.error(`Failed to fetch changes for orphaned change detection: ${response.status}`);
99
+ return;
100
+ }
101
+ const changes = await response.json();
102
+ const moduleNameMap = buildModuleNameMap(changes);
103
+ if (moduleNameMap.size === 0) {
104
+ return;
105
+ }
106
+ const consoleRef = globalThis.console;
107
+ const originalConsoleError = consoleRef.error;
108
+ const restore = () => {
109
+ consoleRef.error = originalConsoleError;
110
+ };
111
+ const handler = createErrorHandler(moduleNameMap, restore);
112
+ const safetyTimeout = setTimeout(restore, 60_000);
113
+ consoleRef.error = (...args) => {
114
+ originalConsoleError.apply(consoleRef, args);
115
+ const message = args.filter(arg => typeof arg === 'string').join('');
116
+ handler(message);
117
+ if (moduleNameMap.size === 0) {
118
+ clearTimeout(safetyTimeout);
119
+ }
120
+ };
121
+ }
122
+ var __exports = {
123
+ __esModule: true
124
+ };
125
+ __exports.initOrphanedChangeDetection = initOrphanedChangeDetection;
126
+ return __exports;
127
+ });
128
+ //# sourceMappingURL=change-file-validator.js.map
@@ -0,0 +1,158 @@
1
+ import log from 'sap/base/Log';
2
+ import { MessageBarType } from '@sap-ux-private/control-property-editor-common';
3
+
4
+ import { sendInfoCenterMessage } from '../utils/info-center-message';
5
+
6
+ const CHANGE_TYPE = {
7
+ addXML: 'addXML',
8
+ codeExt: 'codeExt'
9
+ };
10
+
11
+ type FlexChangeType = (typeof CHANGE_TYPE)[keyof typeof CHANGE_TYPE];
12
+
13
+ interface ChangeContent {
14
+ fragmentPath?: string;
15
+ codeRef?: string;
16
+ }
17
+
18
+ interface Change {
19
+ changeType: string;
20
+ fileName: string;
21
+ fileType?: string;
22
+ reference: string;
23
+ moduleName?: string;
24
+ content?: ChangeContent;
25
+ }
26
+
27
+ interface OrphanedChangeEntry {
28
+ changeFileName: string;
29
+ filePath: string;
30
+ changeType: FlexChangeType;
31
+ }
32
+
33
+ interface RelevantChange extends Change {
34
+ changeType: FlexChangeType;
35
+ reference: string;
36
+ }
37
+
38
+ /**
39
+ * Type guard for changes that reference a fragment or controller extension file.
40
+ *
41
+ * @param change flex change object
42
+ * @returns true if the change is an addXML with fragmentPath or a codeExt with codeRef
43
+ */
44
+ function isFragmentOrCodeExtChange(change: Change): change is RelevantChange {
45
+ return (
46
+ !!change.reference &&
47
+ ((change.changeType === CHANGE_TYPE.addXML && !!change.content?.fragmentPath) ||
48
+ (change.changeType === CHANGE_TYPE.codeExt && !!change.content?.codeRef))
49
+ );
50
+ }
51
+
52
+ /**
53
+ * Builds a lookup map from module name patterns to change metadata
54
+ * for addXML and codeExt changes.
55
+ *
56
+ * @param changes record of change objects keyed by flex key
57
+ * @returns map from module name substring to orphaned change entry
58
+ */
59
+ function buildModuleNameMap(changes: Record<string, Change>): Map<string, OrphanedChangeEntry> {
60
+ const map = new Map<string, OrphanedChangeEntry>();
61
+
62
+ for (const change of Object.values(changes)) {
63
+ if (!isFragmentOrCodeExtChange(change)) {
64
+ continue;
65
+ }
66
+
67
+ const prefix = change.reference.replaceAll('.', '/');
68
+ const path = change.changeType === CHANGE_TYPE.addXML ? change.content?.fragmentPath ?? '' : change.content?.codeRef ?? '';
69
+ const changeFileName = `${change.fileName}.${change.fileType ?? 'change'}`;
70
+ const key = change.moduleName ?? `${prefix}/changes/${path}`;
71
+
72
+ map.set(key, { changeFileName, filePath: path, changeType: change.changeType });
73
+ }
74
+
75
+ return map;
76
+ }
77
+
78
+ /**
79
+ * Creates an error handler that matches error messages against known module names
80
+ * and sends InfoCenter errors for orphaned change files.
81
+ *
82
+ * @param moduleNameMap map from module name substring to orphaned change entry
83
+ * @returns error handler function
84
+ */
85
+ function createErrorHandler(
86
+ moduleNameMap: Map<string, OrphanedChangeEntry>,
87
+ restoreConsole: () => void
88
+ ): (message: string) => void {
89
+ return (message: string) => {
90
+ for (const [moduleName, entry] of moduleNameMap) {
91
+ if (message.includes(moduleName)) {
92
+ sendInfoCenterMessage({
93
+ title: { key: 'ADP_ORPHANED_CHANGE_ERROR_TITLE' },
94
+ description: {
95
+ key: 'ADP_ORPHANED_FILE_DESCRIPTION',
96
+ params: [entry.filePath, entry.changeFileName]
97
+ },
98
+ type: MessageBarType.error
99
+ }).catch((error) => {
100
+ log.error('Failed to send orphaned change InfoCenter message', error);
101
+ });
102
+ moduleNameMap.delete(moduleName);
103
+ if (moduleNameMap.size === 0) {
104
+ restoreConsole();
105
+ }
106
+ break;
107
+ }
108
+ }
109
+ };
110
+ }
111
+
112
+ /**
113
+ * Initializes orphaned change file detection.
114
+ *
115
+ * Fetches loaded flex changes, builds a lookup map of module names for addXML and codeExt changes,
116
+ * and wraps console.error to intercept UI5 flex change application errors. When UI5 fails to load
117
+ * a fragment or controller extension referenced by a change file, the error is intercepted and an
118
+ * actionable message is shown in the InfoCenter advising the user to delete the orphaned change file.
119
+ */
120
+ export async function initOrphanedChangeDetection(): Promise<void> {
121
+ const baseUrl = document.getElementById('sap-ui-bootstrap')?.dataset.openUxPreviewBaseUrl ?? '';
122
+ const response = await fetch(`${baseUrl}/preview/api/changes`, {
123
+ method: 'GET',
124
+ headers: { 'content-type': 'application/json' }
125
+ });
126
+
127
+ if (!response.ok) {
128
+ log.error(`Failed to fetch changes for orphaned change detection: ${response.status}`);
129
+ return;
130
+ }
131
+
132
+ const changes = (await response.json()) as Record<string, Change>;
133
+ const moduleNameMap = buildModuleNameMap(changes);
134
+
135
+ if (moduleNameMap.size === 0) {
136
+ return;
137
+ }
138
+
139
+ const consoleRef = globalThis.console;
140
+ const originalConsoleError = consoleRef.error;
141
+
142
+ const restore = (): void => {
143
+ consoleRef.error = originalConsoleError;
144
+ };
145
+
146
+ const handler = createErrorHandler(moduleNameMap, restore);
147
+
148
+ const safetyTimeout = setTimeout(restore, 60_000);
149
+
150
+ consoleRef.error = (...args: unknown[]) => {
151
+ originalConsoleError.apply(consoleRef, args);
152
+ const message = args.filter((arg): arg is string => typeof arg === 'string').join('');
153
+ handler(message);
154
+ if (moduleNameMap.size === 0) {
155
+ clearTimeout(safetyTimeout);
156
+ }
157
+ };
158
+ }
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
 
3
- sap.ui.define(["sap/base/Log", "open/ux/preview/client/thirdparty/@sap-ux-private/control-property-editor-common", "../utils/version", "../cpe/init", "./sync-views-utils", "../utils/application", "./quick-actions/load", "./init-dialogs", "../utils/info-center-message", "../cpe/communication-service"], function (log, ___sap_ux_private_control_property_editor_common, ___utils_version, __init, ___sync_views_utils, ___utils_application, ___quick_actions_load, ___init_dialogs, ___utils_info_center_message, ___cpe_communication_service) {
3
+ sap.ui.define(["sap/base/Log", "open/ux/preview/client/thirdparty/@sap-ux-private/control-property-editor-common", "../utils/version", "../cpe/init", "./sync-views-utils", "../utils/application", "./quick-actions/load", "./init-dialogs", "../utils/info-center-message", "../cpe/communication-service", "./change-file-validator"], function (log, ___sap_ux_private_control_property_editor_common, ___utils_version, __init, ___sync_views_utils, ___utils_application, ___quick_actions_load, ___init_dialogs, ___utils_info_center_message, ___cpe_communication_service, ___change_file_validator) {
4
4
  "use strict";
5
5
 
6
6
  function _interopRequireDefault(obj) {
@@ -38,6 +38,7 @@ sap.ui.define(["sap/base/Log", "open/ux/preview/client/thirdparty/@sap-ux-privat
38
38
  const initDialogs = ___init_dialogs["initDialogs"];
39
39
  const sendInfoCenterMessage = ___utils_info_center_message["sendInfoCenterMessage"];
40
40
  const CommunicationService = ___cpe_communication_service["CommunicationService"];
41
+ const initOrphanedChangeDetection = ___change_file_validator["initOrphanedChangeDetection"];
41
42
  var __exports = async function (rta) {
42
43
  const flexSettings = rta.getFlexSettings();
43
44
  if (flexSettings.telemetry === true) {
@@ -104,6 +105,9 @@ sap.ui.define(["sap/base/Log", "open/ux/preview/client/thirdparty/@sap-ux-privat
104
105
  CommunicationService.sendAction(toggleAppPreviewVisibility(false));
105
106
  return;
106
107
  }
108
+ initOrphanedChangeDetection().catch(error => {
109
+ log.error('Failed to run orphaned change detection', error);
110
+ });
107
111
  log.debug('ADP init executed.');
108
112
  };
109
113
  return __exports;
@@ -23,6 +23,7 @@ import { loadDefinitions } from './quick-actions/load';
23
23
  import { initDialogs } from './init-dialogs';
24
24
  import { sendInfoCenterMessage } from '../utils/info-center-message';
25
25
  import { CommunicationService } from '../cpe/communication-service';
26
+ import { initOrphanedChangeDetection } from './change-file-validator';
26
27
 
27
28
  export default async function (rta: RuntimeAuthoring) {
28
29
  const flexSettings = rta.getFlexSettings();
@@ -88,5 +89,9 @@ export default async function (rta: RuntimeAuthoring) {
88
89
  return;
89
90
  }
90
91
 
92
+ initOrphanedChangeDetection().catch((error) => {
93
+ log.error('Failed to run orphaned change detection', error);
94
+ });
95
+
91
96
  log.debug('ADP init executed.');
92
97
  }
@@ -370,6 +370,88 @@ sap.ui.define(["open/ux/preview/client/thirdparty/@sap-ux-private/control-proper
370
370
  }
371
371
  }
372
372
 
373
+ /**
374
+ * Build a PendingGenericChange and track config change path if applicable.
375
+ *
376
+ * @param changeDefinition - change definition object
377
+ * @param changeType - the change type string
378
+ * @param handler - the generic change handler
379
+ * @param isActive - whether the change is currently active
380
+ * @param fileName - file name of the change
381
+ * @param textBundle - i18n text bundle
382
+ * @returns Promise resolving to PendingGenericChange
383
+ */
384
+ async buildGenericChange(changeDefinition, changeType, handler, isActive, fileName, textBundle) {
385
+ const {
386
+ properties,
387
+ changeTitle,
388
+ controlId,
389
+ changeType: type,
390
+ subtitle
391
+ } = await handler(changeDefinition, {
392
+ textBundle,
393
+ appComponent: this.options.rta.getRootControlInstance(),
394
+ configPropertyControlIdMap: this.configPropertyControlIdMap
395
+ });
396
+ const genericChange = {
397
+ kind: GENERIC_CHANGE_KIND,
398
+ type: 'pending',
399
+ changeType: type ?? changeType,
400
+ ...(subtitle && {
401
+ subtitle
402
+ }),
403
+ isActive,
404
+ title: textBundle.getText(changeTitle),
405
+ fileName,
406
+ ...(controlId && {
407
+ controlId
408
+ }),
409
+ properties
410
+ };
411
+ if (changeType === 'appdescr_fe_changePageConfiguration') {
412
+ const configChangePath = changeDefinition.content.entityPropertyChange.propertyPath;
413
+ if (genericChange.isActive) {
414
+ this.configPropertyPath.add(configChangePath);
415
+ } else {
416
+ // remove value from set if change is undone
417
+ this.configPropertyPath.delete(configChangePath);
418
+ }
419
+ this.trackPendingConfigChanges(genericChange);
420
+ }
421
+ return genericChange;
422
+ }
423
+
424
+ /**
425
+ * Build a fallback PendingChange for changes without a registered handler.
426
+ *
427
+ * @param changeType - the change type string
428
+ * @param selectorId - optional selector/control ID
429
+ * @param isActive - whether the change is currently active
430
+ * @param fileName - file name of the change
431
+ * @returns PendingChange
432
+ */
433
+ buildFallbackChange(changeType, selectorId, isActive, fileName) {
434
+ const title = TITLE_MAP[changeType] ?? '';
435
+ let result = {
436
+ type: PENDING_CHANGE_TYPE,
437
+ kind: UNKNOWN_CHANGE_KIND,
438
+ ...(title && {
439
+ title
440
+ }),
441
+ changeType,
442
+ isActive,
443
+ fileName
444
+ };
445
+ if (selectorId) {
446
+ result = {
447
+ ...result,
448
+ kind: 'control',
449
+ controlId: selectorId
450
+ };
451
+ }
452
+ return result;
453
+ }
454
+
373
455
  /**
374
456
  * Prepares the type of change based on the command and other parameters.
375
457
  *
@@ -391,65 +473,11 @@ sap.ui.define(["open/ux/preview/client/thirdparty/@sap-ux-private/control-proper
391
473
  fileName
392
474
  } = changeDefinition;
393
475
  const handler = GENERIC_CHANGE_HANDLER[changeType];
476
+ const isActive = index >= inactiveCommandCount;
394
477
  if (handler) {
395
- const {
396
- properties,
397
- changeTitle,
398
- controlId,
399
- changeType: type,
400
- subtitle
401
- } = await handler(changeDefinition, {
402
- textBundle,
403
- appComponent: this.options.rta.getRootControlInstance(),
404
- configPropertyControlIdMap: this.configPropertyControlIdMap
405
- });
406
- const genericChange = {
407
- kind: GENERIC_CHANGE_KIND,
408
- type: 'pending',
409
- changeType: type ?? changeType,
410
- ...(subtitle && {
411
- subtitle
412
- }),
413
- isActive: index >= inactiveCommandCount,
414
- title: textBundle.getText(changeTitle),
415
- fileName,
416
- ...(controlId && {
417
- controlId
418
- }),
419
- properties
420
- };
421
- if (changeType === 'appdescr_fe_changePageConfiguration') {
422
- const configChangePath = changeDefinition.content.entityPropertyChange.propertyPath;
423
- if (genericChange.isActive) {
424
- this.configPropertyPath.add(configChangePath);
425
- } else {
426
- // remove value from set if change is undone
427
- this.configPropertyPath.delete(configChangePath);
428
- }
429
- this.trackPendingConfigChanges(genericChange);
430
- }
431
- return genericChange;
432
- } else {
433
- const title = TITLE_MAP[changeType] ?? '';
434
- let result = {
435
- type: PENDING_CHANGE_TYPE,
436
- kind: UNKNOWN_CHANGE_KIND,
437
- ...(title && {
438
- title
439
- }),
440
- changeType,
441
- isActive: index >= inactiveCommandCount,
442
- fileName
443
- };
444
- if (selectorId) {
445
- result = {
446
- ...result,
447
- kind: 'control',
448
- controlId: selectorId
449
- };
450
- }
451
- return result;
478
+ return this.buildGenericChange(changeDefinition, changeType, handler, isActive, fileName, textBundle);
452
479
  }
480
+ return this.buildFallbackChange(changeType, selectorId, isActive, fileName);
453
481
  }
454
482
 
455
483
  /**
@@ -433,6 +433,94 @@ export class ChangeService extends EventTarget {
433
433
  }
434
434
  }
435
435
 
436
+ /**
437
+ * Build a PendingGenericChange and track config change path if applicable.
438
+ *
439
+ * @param changeDefinition - change definition object
440
+ * @param changeType - the change type string
441
+ * @param handler - the generic change handler
442
+ * @param isActive - whether the change is currently active
443
+ * @param fileName - file name of the change
444
+ * @param textBundle - i18n text bundle
445
+ * @returns Promise resolving to PendingGenericChange
446
+ */
447
+ private async buildGenericChange(
448
+ changeDefinition: ChangeDefinition,
449
+ changeType: string,
450
+ handler: ChangeHandler<GenericChange>,
451
+ isActive: boolean,
452
+ fileName: string,
453
+ textBundle: Awaited<ReturnType<typeof getTextBundle>>
454
+ ): Promise<PendingGenericChange> {
455
+ const {
456
+ properties,
457
+ changeTitle,
458
+ controlId,
459
+ changeType: type,
460
+ subtitle
461
+ } = await handler(changeDefinition as unknown as GenericChange, {
462
+ textBundle,
463
+ appComponent: this.options.rta.getRootControlInstance(),
464
+ configPropertyControlIdMap: this.configPropertyControlIdMap
465
+ });
466
+ const genericChange: PendingGenericChange = {
467
+ kind: GENERIC_CHANGE_KIND,
468
+ type: 'pending',
469
+ changeType: type ?? changeType,
470
+ ...(subtitle && { subtitle }),
471
+ isActive,
472
+ title: textBundle.getText(changeTitle),
473
+ fileName,
474
+ ...(controlId && { controlId }),
475
+ properties
476
+ };
477
+ if (changeType === 'appdescr_fe_changePageConfiguration') {
478
+ const configChangePath = (changeDefinition as ConfigChange).content.entityPropertyChange.propertyPath;
479
+ if (genericChange.isActive) {
480
+ this.configPropertyPath.add(configChangePath);
481
+ } else {
482
+ // remove value from set if change is undone
483
+ this.configPropertyPath.delete(configChangePath);
484
+ }
485
+ this.trackPendingConfigChanges(genericChange);
486
+ }
487
+ return genericChange;
488
+ }
489
+
490
+ /**
491
+ * Build a fallback PendingChange for changes without a registered handler.
492
+ *
493
+ * @param changeType - the change type string
494
+ * @param selectorId - optional selector/control ID
495
+ * @param isActive - whether the change is currently active
496
+ * @param fileName - file name of the change
497
+ * @returns PendingChange
498
+ */
499
+ private buildFallbackChange(
500
+ changeType: string,
501
+ selectorId: string | undefined,
502
+ isActive: boolean,
503
+ fileName: string
504
+ ): PendingChange {
505
+ const title = TITLE_MAP[changeType] ?? '';
506
+ let result: PendingChange = {
507
+ type: PENDING_CHANGE_TYPE,
508
+ kind: UNKNOWN_CHANGE_KIND,
509
+ ...(title && { title }),
510
+ changeType,
511
+ isActive,
512
+ fileName
513
+ };
514
+ if (selectorId) {
515
+ result = {
516
+ ...result,
517
+ kind: 'control',
518
+ controlId: selectorId
519
+ };
520
+ }
521
+ return result;
522
+ }
523
+
436
524
  /**
437
525
  * Prepares the type of change based on the command and other parameters.
438
526
  *
@@ -462,60 +550,11 @@ export class ChangeService extends EventTarget {
462
550
  const changeDefinition = change.getDefinition ? change.getDefinition() : (change.getJson() as ChangeDefinition);
463
551
  const { fileName } = changeDefinition;
464
552
  const handler = GENERIC_CHANGE_HANDLER[changeType as ChangeType] as unknown as ChangeHandler<GenericChange>;
553
+ const isActive = index >= inactiveCommandCount;
465
554
  if (handler) {
466
- const {
467
- properties,
468
- changeTitle,
469
- controlId,
470
- changeType: type,
471
- subtitle
472
- } = await handler(changeDefinition as unknown as GenericChange, {
473
- textBundle,
474
- appComponent: this.options.rta.getRootControlInstance(),
475
- configPropertyControlIdMap: this.configPropertyControlIdMap
476
- });
477
- const genericChange: PendingGenericChange = {
478
- kind: GENERIC_CHANGE_KIND,
479
- type: 'pending',
480
- changeType: type ?? changeType,
481
- ...(subtitle && { subtitle }),
482
- isActive: index >= inactiveCommandCount,
483
- title: textBundle.getText(changeTitle),
484
- fileName,
485
- ...(controlId && { controlId }),
486
- properties
487
- };
488
- if (changeType === 'appdescr_fe_changePageConfiguration') {
489
- const configChangePath = (changeDefinition as ConfigChange).content.entityPropertyChange.propertyPath;
490
- if (genericChange.isActive) {
491
- this.configPropertyPath.add(configChangePath);
492
- } else {
493
- // remove value from set if change is undone
494
- this.configPropertyPath.delete(configChangePath);
495
- }
496
- this.trackPendingConfigChanges(genericChange);
497
- }
498
- return genericChange;
499
- } else {
500
- const title = TITLE_MAP[changeType] ?? '';
501
- let result: PendingChange = {
502
- type: PENDING_CHANGE_TYPE,
503
- kind: UNKNOWN_CHANGE_KIND,
504
- ...(title && { title }),
505
- changeType,
506
- isActive: index >= inactiveCommandCount,
507
- fileName
508
- };
509
-
510
- if (selectorId) {
511
- result = {
512
- ...result,
513
- kind: 'control',
514
- controlId: selectorId
515
- };
516
- }
517
- return result;
555
+ return this.buildGenericChange(changeDefinition, changeType, handler, isActive, fileName, textBundle);
518
556
  }
557
+ return this.buildFallbackChange(changeType, selectorId, isActive, fileName);
519
558
  }
520
559
 
521
560
  /**