@acorex/platform 20.7.20 → 20.7.22
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/common/index.d.ts +4 -1
- package/fesm2022/{acorex-platform-common-common-settings.provider-gyb6ohAE.mjs → acorex-platform-common-common-settings.provider-73370m-b.mjs} +43 -1
- package/fesm2022/acorex-platform-common-common-settings.provider-73370m-b.mjs.map +1 -0
- package/fesm2022/acorex-platform-common.mjs +5 -2
- package/fesm2022/acorex-platform-common.mjs.map +1 -1
- package/fesm2022/acorex-platform-layout-builder.mjs +16 -8
- package/fesm2022/acorex-platform-layout-builder.mjs.map +1 -1
- package/fesm2022/acorex-platform-layout-entity.mjs +369 -151
- package/fesm2022/acorex-platform-layout-entity.mjs.map +1 -1
- package/fesm2022/acorex-platform-layout-views.mjs +19 -66
- package/fesm2022/acorex-platform-layout-views.mjs.map +1 -1
- package/fesm2022/acorex-platform-layout-widgets.mjs +31 -6
- package/fesm2022/acorex-platform-layout-widgets.mjs.map +1 -1
- package/fesm2022/{acorex-platform-themes-default-entity-master-list-view.component-DDd7YryZ.mjs → acorex-platform-themes-default-entity-master-list-view.component-3djSN0h5.mjs} +1 -2
- package/fesm2022/acorex-platform-themes-default-entity-master-list-view.component-3djSN0h5.mjs.map +1 -0
- package/fesm2022/acorex-platform-themes-default.mjs +2 -2
- package/layout/entity/index.d.ts +31 -1
- package/layout/views/index.d.ts +6 -1
- package/layout/widgets/index.d.ts +14 -1
- package/package.json +9 -9
- package/fesm2022/acorex-platform-common-common-settings.provider-gyb6ohAE.mjs.map +0 -1
- package/fesm2022/acorex-platform-themes-default-entity-master-list-view.component-DDd7YryZ.mjs.map +0 -1
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { AXToastService } from '@acorex/components/toast';
|
|
2
2
|
import * as i6 from '@acorex/core/translation';
|
|
3
3
|
import { AXTranslationService, AXTranslationModule } from '@acorex/core/translation';
|
|
4
|
+
import * as i4$1 from '@acorex/platform/common';
|
|
5
|
+
import { AXPSettingsService, AXPCommonSettings, AXPFilterOperatorMiddlewareService, AXPEntityCommandScope, getEntityInfo, AXPRefreshEvent, AXPReloadEvent, AXPEntityQueryType, AXPCleanNestedFilters, AXPWorkflowNavigateAction, AXPToastAction, AXP_SEARCH_DEFINITION_PROVIDER } from '@acorex/platform/common';
|
|
4
6
|
import * as i0 from '@angular/core';
|
|
5
7
|
import { InjectionToken, inject, Injector, runInInjectionContext, Injectable, input, viewChild, signal, ElementRef, ChangeDetectionStrategy, Component, ApplicationRef, EnvironmentInjector, createComponent, computed, ChangeDetectorRef, effect, Input, afterNextRender, untracked, ViewEncapsulation, viewChildren, linkedSignal, HostBinding, output, NgModule } from '@angular/core';
|
|
6
8
|
import { Subject, takeUntil } from 'rxjs';
|
|
7
9
|
import { AXPLayoutBuilderService } from '@acorex/platform/layout/builder';
|
|
8
10
|
import { AXPDeviceService, AXPBroadcastEventService, applyFilterArray, applySortArray, resolveActionLook, AXPExpressionEvaluatorService, AXPDistributedEventListenerService, AXPPlatformScope, AXPColumnWidthService, AXHighlightService, extractValue, setSmart, getChangedPaths, defaultColumnWidthProvider, AXP_COLUMN_WIDTH_PROVIDER, AXP_DATASOURCE_DEFINITION_PROVIDER, AXPSystemActionType } from '@acorex/platform/core';
|
|
9
11
|
import { merge, castArray, get, cloneDeep, set, orderBy, isNil, isEmpty, isEqual } from 'lodash-es';
|
|
10
|
-
import * as i4$1 from '@acorex/platform/common';
|
|
11
|
-
import { AXPSettingsService, AXPFilterOperatorMiddlewareService, AXPEntityCommandScope, getEntityInfo, AXPRefreshEvent, AXPReloadEvent, AXPCommonSettings, AXPEntityQueryType, AXPCleanNestedFilters, AXPWorkflowNavigateAction, AXPToastAction, AXP_SEARCH_DEFINITION_PROVIDER } from '@acorex/platform/common';
|
|
12
12
|
import { AXPSessionService, AXPAuthGuard } from '@acorex/platform/auth';
|
|
13
13
|
import { Router, RouterModule, ROUTES } from '@angular/router';
|
|
14
14
|
import * as i3 from '@acorex/components/button';
|
|
@@ -53,7 +53,7 @@ import { AXFormModule } from '@acorex/components/form';
|
|
|
53
53
|
import * as i6$1 from '@acorex/components/tag-box';
|
|
54
54
|
import { AXTagBoxModule } from '@acorex/components/tag-box';
|
|
55
55
|
import { AXValidationModule } from '@acorex/core/validation';
|
|
56
|
-
import { AXP_DISABLED_PROPERTY, AXP_ALLOW_CLEAR_PROPERTY, AXP_DATA_PATH_PROPERTY, AXP_DATA_PROPERTY_GROUP, AXP_ALLOW_MULTIPLE_PROPERTY, AXP_NAME_PROPERTY, AXPFileUploaderWidgetService } from '@acorex/platform/layout/widgets';
|
|
56
|
+
import { AXP_DISABLED_PROPERTY, AXP_ALLOW_CLEAR_PROPERTY, AXP_DATA_PATH_PROPERTY, AXP_DATA_PROPERTY_GROUP, AXP_ALLOW_MULTIPLE_PROPERTY, AXP_ROW_EXPR_PREFIX, AXP_NAME_PROPERTY, AXPFileUploaderWidgetService } from '@acorex/platform/layout/widgets';
|
|
57
57
|
import * as i4$3 from '@acorex/components/dropdown';
|
|
58
58
|
import { AXDropdownModule } from '@acorex/components/dropdown';
|
|
59
59
|
import * as i4$4 from '@acorex/components/select-box';
|
|
@@ -380,7 +380,6 @@ class AXPEntityMiddleware {
|
|
|
380
380
|
}
|
|
381
381
|
// Apply all matching modifiers in order
|
|
382
382
|
for (const modifier of modifiers) {
|
|
383
|
-
//TODO Check side EFFECTS
|
|
384
383
|
runInInjectionContext(this.injector, () => modifier(context));
|
|
385
384
|
}
|
|
386
385
|
return context.toEntity();
|
|
@@ -1365,12 +1364,80 @@ function sortMergedSections(mainSections, extraSections, mainSectionIds) {
|
|
|
1365
1364
|
return [...beforeSections, ...sortedMiddleBlock, ...afterSections];
|
|
1366
1365
|
}
|
|
1367
1366
|
|
|
1367
|
+
//#endregion
|
|
1368
|
+
//#region ---- Entity Open Details Command ----
|
|
1369
|
+
/**
|
|
1370
|
+
* Generic command to open entity details view
|
|
1371
|
+
* Can be used by any entity to navigate to its detail page
|
|
1372
|
+
*/
|
|
1373
|
+
class AXPOpenEntityDetailsCommand {
|
|
1374
|
+
constructor() {
|
|
1375
|
+
//#endregion
|
|
1376
|
+
//#region ---- Services & Dependencies ----
|
|
1377
|
+
this.router = inject(Router);
|
|
1378
|
+
this.sessionService = inject(AXPSessionService);
|
|
1379
|
+
}
|
|
1380
|
+
//#endregion
|
|
1381
|
+
//#region ---- Command Execution ----
|
|
1382
|
+
/**
|
|
1383
|
+
* Execute the command to navigate to entity details
|
|
1384
|
+
* @param input - Command input containing entity and data information
|
|
1385
|
+
*/
|
|
1386
|
+
async execute(input) {
|
|
1387
|
+
const { entity, data } = input;
|
|
1388
|
+
if (!entity) {
|
|
1389
|
+
return {
|
|
1390
|
+
success: false,
|
|
1391
|
+
message: {
|
|
1392
|
+
text: 'Entity name is required for opening details view',
|
|
1393
|
+
},
|
|
1394
|
+
};
|
|
1395
|
+
}
|
|
1396
|
+
if (!data?.id) {
|
|
1397
|
+
return {
|
|
1398
|
+
success: false,
|
|
1399
|
+
message: {
|
|
1400
|
+
text: 'Entity ID is required for opening details view',
|
|
1401
|
+
},
|
|
1402
|
+
};
|
|
1403
|
+
}
|
|
1404
|
+
const [module, entityName] = entity.split('.');
|
|
1405
|
+
if (!module || !entityName) {
|
|
1406
|
+
return {
|
|
1407
|
+
success: false,
|
|
1408
|
+
message: {
|
|
1409
|
+
text: 'Entity must be in format "ModuleName.EntityName"',
|
|
1410
|
+
},
|
|
1411
|
+
};
|
|
1412
|
+
}
|
|
1413
|
+
const url = `/${this.sessionService.application?.name}/m/${module}/e/${entityName}/${data.id}/view`;
|
|
1414
|
+
// Navigate to the entity details page
|
|
1415
|
+
this.router.navigate([url]);
|
|
1416
|
+
return {
|
|
1417
|
+
success: true,
|
|
1418
|
+
};
|
|
1419
|
+
}
|
|
1420
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AXPOpenEntityDetailsCommand, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
1421
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AXPOpenEntityDetailsCommand, providedIn: 'root' }); }
|
|
1422
|
+
}
|
|
1423
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AXPOpenEntityDetailsCommand, decorators: [{
|
|
1424
|
+
type: Injectable,
|
|
1425
|
+
args: [{ providedIn: 'root' }]
|
|
1426
|
+
}] });
|
|
1427
|
+
|
|
1428
|
+
var openEntityDetails_command = /*#__PURE__*/Object.freeze({
|
|
1429
|
+
__proto__: null,
|
|
1430
|
+
AXPOpenEntityDetailsCommand: AXPOpenEntityDetailsCommand
|
|
1431
|
+
});
|
|
1432
|
+
|
|
1368
1433
|
class AXPCreateEntityCommand {
|
|
1369
1434
|
constructor() {
|
|
1370
1435
|
this.entityForm = inject(AXPEntityFormBuilderService);
|
|
1371
1436
|
this.entityService = inject(AXPEntityDefinitionRegistryService);
|
|
1372
1437
|
this.toastService = inject(AXToastService);
|
|
1373
1438
|
this.translationService = inject(AXTranslationService);
|
|
1439
|
+
this.settingsService = inject(AXPSettingsService);
|
|
1440
|
+
this.openEntityDetailsCommand = inject(AXPOpenEntityDetailsCommand);
|
|
1374
1441
|
this.context = {};
|
|
1375
1442
|
}
|
|
1376
1443
|
async execute(input) {
|
|
@@ -1469,7 +1536,22 @@ class AXPCreateEntityCommand {
|
|
|
1469
1536
|
}
|
|
1470
1537
|
})
|
|
1471
1538
|
.show();
|
|
1472
|
-
|
|
1539
|
+
const commandResult = result;
|
|
1540
|
+
const redirectOption = options?.process?.redirect;
|
|
1541
|
+
const shouldRedirect = redirectOption !== undefined
|
|
1542
|
+
? redirectOption
|
|
1543
|
+
: await this.settingsService.get(AXPCommonSettings.RedirectToDetailsAfterCreate);
|
|
1544
|
+
if (commandResult.success && shouldRedirect && commandResult.data) {
|
|
1545
|
+
const createdItem = commandResult.data?.item ?? commandResult.data;
|
|
1546
|
+
const createdId = createdItem?.id;
|
|
1547
|
+
if (createdId != null) {
|
|
1548
|
+
await this.openEntityDetailsCommand.execute({
|
|
1549
|
+
entity: `${moduleName}.${entityName}`,
|
|
1550
|
+
data: { id: String(createdId) },
|
|
1551
|
+
});
|
|
1552
|
+
}
|
|
1553
|
+
}
|
|
1554
|
+
return commandResult;
|
|
1473
1555
|
}
|
|
1474
1556
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AXPCreateEntityCommand, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
1475
1557
|
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AXPCreateEntityCommand, providedIn: 'root' }); }
|
|
@@ -1710,72 +1792,6 @@ var viewEntityDetails_command = /*#__PURE__*/Object.freeze({
|
|
|
1710
1792
|
AXPViewEntityDetailsCommand: AXPViewEntityDetailsCommand
|
|
1711
1793
|
});
|
|
1712
1794
|
|
|
1713
|
-
//#endregion
|
|
1714
|
-
//#region ---- Entity Open Details Command ----
|
|
1715
|
-
/**
|
|
1716
|
-
* Generic command to open entity details view
|
|
1717
|
-
* Can be used by any entity to navigate to its detail page
|
|
1718
|
-
*/
|
|
1719
|
-
class AXPOpenEntityDetailsCommand {
|
|
1720
|
-
constructor() {
|
|
1721
|
-
//#endregion
|
|
1722
|
-
//#region ---- Services & Dependencies ----
|
|
1723
|
-
this.router = inject(Router);
|
|
1724
|
-
this.sessionService = inject(AXPSessionService);
|
|
1725
|
-
}
|
|
1726
|
-
//#endregion
|
|
1727
|
-
//#region ---- Command Execution ----
|
|
1728
|
-
/**
|
|
1729
|
-
* Execute the command to navigate to entity details
|
|
1730
|
-
* @param input - Command input containing entity and data information
|
|
1731
|
-
*/
|
|
1732
|
-
async execute(input) {
|
|
1733
|
-
const { entity, data } = input;
|
|
1734
|
-
if (!entity) {
|
|
1735
|
-
return {
|
|
1736
|
-
success: false,
|
|
1737
|
-
message: {
|
|
1738
|
-
text: 'Entity name is required for opening details view',
|
|
1739
|
-
},
|
|
1740
|
-
};
|
|
1741
|
-
}
|
|
1742
|
-
if (!data?.id) {
|
|
1743
|
-
return {
|
|
1744
|
-
success: false,
|
|
1745
|
-
message: {
|
|
1746
|
-
text: 'Entity ID is required for opening details view',
|
|
1747
|
-
},
|
|
1748
|
-
};
|
|
1749
|
-
}
|
|
1750
|
-
const [module, entityName] = entity.split('.');
|
|
1751
|
-
if (!module || !entityName) {
|
|
1752
|
-
return {
|
|
1753
|
-
success: false,
|
|
1754
|
-
message: {
|
|
1755
|
-
text: 'Entity must be in format "ModuleName.EntityName"',
|
|
1756
|
-
},
|
|
1757
|
-
};
|
|
1758
|
-
}
|
|
1759
|
-
const url = `/${this.sessionService.application?.name}/m/${module}/e/${entityName}/${data.id}/view`;
|
|
1760
|
-
// Navigate to the entity details page
|
|
1761
|
-
this.router.navigate([url]);
|
|
1762
|
-
return {
|
|
1763
|
-
success: true,
|
|
1764
|
-
};
|
|
1765
|
-
}
|
|
1766
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AXPOpenEntityDetailsCommand, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
1767
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AXPOpenEntityDetailsCommand, providedIn: 'root' }); }
|
|
1768
|
-
}
|
|
1769
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AXPOpenEntityDetailsCommand, decorators: [{
|
|
1770
|
-
type: Injectable,
|
|
1771
|
-
args: [{ providedIn: 'root' }]
|
|
1772
|
-
}] });
|
|
1773
|
-
|
|
1774
|
-
var openEntityDetails_command = /*#__PURE__*/Object.freeze({
|
|
1775
|
-
__proto__: null,
|
|
1776
|
-
AXPOpenEntityDetailsCommand: AXPOpenEntityDetailsCommand
|
|
1777
|
-
});
|
|
1778
|
-
|
|
1779
1795
|
class AXPEntityDetailPopoverComponent {
|
|
1780
1796
|
constructor() {
|
|
1781
1797
|
//#region ---- Dependencies ----
|
|
@@ -4532,8 +4548,82 @@ const AXPEntityDetailViewModelResolver = (route, state, service = inject(AXPEnti
|
|
|
4532
4548
|
return service.create(moduleName, entityName, id);
|
|
4533
4549
|
};
|
|
4534
4550
|
|
|
4551
|
+
class AXPDetailsViewBadgeStatusService {
|
|
4552
|
+
constructor() {
|
|
4553
|
+
this.entityRegistry = inject(AXPEntityDefinitionRegistryService);
|
|
4554
|
+
this.translateService = inject(AXTranslationService);
|
|
4555
|
+
}
|
|
4556
|
+
async getPageBadge(entityName, _context, isDirty) {
|
|
4557
|
+
if (!isDirty) {
|
|
4558
|
+
return null;
|
|
4559
|
+
}
|
|
4560
|
+
return {
|
|
4561
|
+
title: await this.translateService.translateAsync('@general:terms.interface.save-status.not-saved.title'),
|
|
4562
|
+
color: 'warning',
|
|
4563
|
+
description: await this.translateService.translateAsync('@general:terms.interface.save-status.not-saved.description'),
|
|
4564
|
+
};
|
|
4565
|
+
}
|
|
4566
|
+
async getPageStatus(entityName, context, currentPage) {
|
|
4567
|
+
if (!entityName) {
|
|
4568
|
+
return null;
|
|
4569
|
+
}
|
|
4570
|
+
const [moduleName, entityNamePart] = entityName.split('.');
|
|
4571
|
+
if (!moduleName || !entityNamePart) {
|
|
4572
|
+
return null;
|
|
4573
|
+
}
|
|
4574
|
+
try {
|
|
4575
|
+
const entityDefinition = await this.entityRegistry.resolve(moduleName, entityNamePart);
|
|
4576
|
+
if (!entityDefinition) {
|
|
4577
|
+
return null;
|
|
4578
|
+
}
|
|
4579
|
+
const statusPlugin = entityDefinition.plugins?.find((p) => p.name === 'status');
|
|
4580
|
+
if (!statusPlugin?.options) {
|
|
4581
|
+
return null;
|
|
4582
|
+
}
|
|
4583
|
+
const statusOptions = statusPlugin.options;
|
|
4584
|
+
const statusField = statusOptions.field ?? 'statusId';
|
|
4585
|
+
const statusDefinitionKey = statusOptions.definition;
|
|
4586
|
+
let definitionKey = typeof statusDefinitionKey === 'string' ? statusDefinitionKey : undefined;
|
|
4587
|
+
if (!definitionKey) {
|
|
4588
|
+
const statusProperty = entityDefinition.properties?.find((p) => p.name === statusField);
|
|
4589
|
+
const widgetOptions = statusProperty?.schema?.interface?.options;
|
|
4590
|
+
if (widgetOptions?.definitionKey) {
|
|
4591
|
+
definitionKey = widgetOptions.definitionKey;
|
|
4592
|
+
}
|
|
4593
|
+
}
|
|
4594
|
+
if (!definitionKey) {
|
|
4595
|
+
return null;
|
|
4596
|
+
}
|
|
4597
|
+
const statusValue = context?.[statusField];
|
|
4598
|
+
if (statusValue == null) {
|
|
4599
|
+
return null;
|
|
4600
|
+
}
|
|
4601
|
+
const value = typeof statusValue === 'string' ? statusValue : statusValue?.name ?? statusValue;
|
|
4602
|
+
return {
|
|
4603
|
+
definitionKey,
|
|
4604
|
+
value: String(value),
|
|
4605
|
+
dataPath: statusField,
|
|
4606
|
+
readonly: currentPage?.isReadonly ?? false,
|
|
4607
|
+
};
|
|
4608
|
+
}
|
|
4609
|
+
catch (error) {
|
|
4610
|
+
console.warn('Failed to get page status:', error);
|
|
4611
|
+
return null;
|
|
4612
|
+
}
|
|
4613
|
+
}
|
|
4614
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AXPDetailsViewBadgeStatusService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
4615
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AXPDetailsViewBadgeStatusService, providedIn: 'root' }); }
|
|
4616
|
+
}
|
|
4617
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AXPDetailsViewBadgeStatusService, decorators: [{
|
|
4618
|
+
type: Injectable,
|
|
4619
|
+
args: [{
|
|
4620
|
+
providedIn: 'root',
|
|
4621
|
+
}]
|
|
4622
|
+
}] });
|
|
4623
|
+
|
|
4535
4624
|
class AXPLayoutAdapterBuilder {
|
|
4536
4625
|
constructor() {
|
|
4626
|
+
this.badgeStatusService = inject(AXPDetailsViewBadgeStatusService);
|
|
4537
4627
|
this.adapter = {};
|
|
4538
4628
|
}
|
|
4539
4629
|
setEntity(entity, rootContext) {
|
|
@@ -4562,8 +4652,9 @@ class AXPLayoutAdapterBuilder {
|
|
|
4562
4652
|
return this;
|
|
4563
4653
|
}
|
|
4564
4654
|
build() {
|
|
4655
|
+
const name = `${this.entity?.module}.${this.entity?.name}`;
|
|
4565
4656
|
return {
|
|
4566
|
-
name
|
|
4657
|
+
name,
|
|
4567
4658
|
title: `${this.entity?.interfaces?.master?.single?.title}`,
|
|
4568
4659
|
label: this.entity?.formats.plural,
|
|
4569
4660
|
actions: [],
|
|
@@ -4572,6 +4663,8 @@ class AXPLayoutAdapterBuilder {
|
|
|
4572
4663
|
load: this.createLoadFunction(),
|
|
4573
4664
|
pages: this.adapter.pages || [],
|
|
4574
4665
|
exitUrl: this.adapter.exitUrl,
|
|
4666
|
+
getPageBadge: (context, isDirty) => this.badgeStatusService.getPageBadge(name, context, isDirty),
|
|
4667
|
+
getPageStatus: (context, currentPage) => this.badgeStatusService.getPageStatus(name, context, currentPage),
|
|
4575
4668
|
};
|
|
4576
4669
|
}
|
|
4577
4670
|
createBreadcrumbs() {
|
|
@@ -5069,7 +5162,8 @@ class AXPPageListConverter extends AXPBaseRelatedEntityConverter {
|
|
|
5069
5162
|
return {
|
|
5070
5163
|
...a,
|
|
5071
5164
|
zone: 'header',
|
|
5072
|
-
visible: !a.hidden,
|
|
5165
|
+
// visible: !a.hidden,
|
|
5166
|
+
hidden: a.hidden,
|
|
5073
5167
|
priority: 'primary',
|
|
5074
5168
|
name: a.name,
|
|
5075
5169
|
title: a.title,
|
|
@@ -5217,7 +5311,7 @@ class AXPPageListConverter extends AXPBaseRelatedEntityConverter {
|
|
|
5217
5311
|
return !usedOverrideActions.has(actionKey);
|
|
5218
5312
|
})
|
|
5219
5313
|
.map((action) => new AXPEntityCommandTriggerViewModel(entityDef, action));
|
|
5220
|
-
return [...additionalActions, ...mergedActions]
|
|
5314
|
+
return [...additionalActions, ...mergedActions];
|
|
5221
5315
|
}
|
|
5222
5316
|
}
|
|
5223
5317
|
|
|
@@ -5989,11 +6083,14 @@ class AXPLayoutAdapterFactory {
|
|
|
5989
6083
|
throw new Error(`Entity ${moduleName}.${entityName} not found`);
|
|
5990
6084
|
}
|
|
5991
6085
|
const rootContext = await this.loadRootContext(entity, id);
|
|
5992
|
-
//
|
|
5993
|
-
const
|
|
5994
|
-
|
|
6086
|
+
// Evaluate hidden expressions for related entities once, reuse across building and composing
|
|
6087
|
+
const evaluatedRelatedEntities = await this.evaluateRelatedEntitiesHidden(entity?.relatedEntities ?? [], rootContext, dependencies);
|
|
6088
|
+
// Build main and related pages using evaluated related entities
|
|
6089
|
+
const entityWithEvaluatedRelated = { ...entity, relatedEntities: evaluatedRelatedEntities };
|
|
6090
|
+
const mainPage = await this.buildMainPage(entityWithEvaluatedRelated, rootContext, id, dependencies);
|
|
6091
|
+
const relatedPages = await this.buildRelatedPages(entityWithEvaluatedRelated, rootContext, dependencies);
|
|
5995
6092
|
// Compose ordered pages around the primary page
|
|
5996
|
-
const orderedPages = this.composePagesWithPositions(mainPage, relatedPages,
|
|
6093
|
+
const orderedPages = this.composePagesWithPositions(mainPage, relatedPages, entityWithEvaluatedRelated);
|
|
5997
6094
|
const applicationName = dependencies.session.application?.name ?? 'default';
|
|
5998
6095
|
return this.layoutAdapterBuilder
|
|
5999
6096
|
.setEntity(entity, rootContext)
|
|
@@ -6009,11 +6106,40 @@ class AXPLayoutAdapterFactory {
|
|
|
6009
6106
|
async buildMainPage(entity, rootContext, id, dependencies) {
|
|
6010
6107
|
return this.mainEntityContentBuilder.build(entity, rootContext, { ...dependencies, reloadRootContext: () => this.loadRootContext(entity, id) }, await this.getRootTitle(entity, rootContext, dependencies));
|
|
6011
6108
|
}
|
|
6109
|
+
/**
|
|
6110
|
+
* Evaluates the 'hidden' expression for each related entity using the expression evaluator.
|
|
6111
|
+
* Returns a new array of related entities with the evaluated hidden values.
|
|
6112
|
+
*/
|
|
6113
|
+
async evaluateRelatedEntitiesHidden(relatedEntities, rootContext, dependencies) {
|
|
6114
|
+
if (!relatedEntities?.length || !dependencies?.expressionEvaluator) {
|
|
6115
|
+
return relatedEntities ?? [];
|
|
6116
|
+
}
|
|
6117
|
+
return Promise.all(relatedEntities.map(async (re) => {
|
|
6118
|
+
let hidden = re.hidden;
|
|
6119
|
+
if (hidden && typeof hidden === 'string') {
|
|
6120
|
+
try {
|
|
6121
|
+
const scope = {
|
|
6122
|
+
context: {
|
|
6123
|
+
eval: (path) => get(rootContext, path),
|
|
6124
|
+
},
|
|
6125
|
+
};
|
|
6126
|
+
const result = await dependencies.expressionEvaluator.evaluate({ hidden }, scope);
|
|
6127
|
+
hidden = result.hidden;
|
|
6128
|
+
}
|
|
6129
|
+
catch {
|
|
6130
|
+
// Keep original hidden value if evaluation fails
|
|
6131
|
+
}
|
|
6132
|
+
}
|
|
6133
|
+
return { ...re, hidden };
|
|
6134
|
+
}));
|
|
6135
|
+
}
|
|
6012
6136
|
async buildRelatedPages(entity, rootContext, dependencies) {
|
|
6013
6137
|
const pages = [];
|
|
6014
6138
|
const rootTitle = await this.getRootTitle(entity, rootContext, dependencies);
|
|
6139
|
+
// Evaluate hidden expressions before filtering related entities
|
|
6140
|
+
const evaluatedRelatedEntities = await this.evaluateRelatedEntitiesHidden(entity?.relatedEntities ?? [], rootContext, dependencies);
|
|
6015
6141
|
// Page Details
|
|
6016
|
-
const pageDetailEntities =
|
|
6142
|
+
const pageDetailEntities = evaluatedRelatedEntities.filter((re) => !re.hidden && re.layout?.type === 'page-detail');
|
|
6017
6143
|
for (const relatedEntity of pageDetailEntities || []) {
|
|
6018
6144
|
const converter = this.relatedEntityConverterFactory.createPageDetailsConverter();
|
|
6019
6145
|
pages.push(await converter.convert(relatedEntity, {
|
|
@@ -6023,7 +6149,7 @@ class AXPLayoutAdapterFactory {
|
|
|
6023
6149
|
}));
|
|
6024
6150
|
}
|
|
6025
6151
|
// Page Lists
|
|
6026
|
-
const pageListEntities =
|
|
6152
|
+
const pageListEntities = evaluatedRelatedEntities.filter((re) => !re.hidden && re.layout?.type === 'page-list');
|
|
6027
6153
|
for (const relatedEntity of pageListEntities || []) {
|
|
6028
6154
|
const converter = this.relatedEntityConverterFactory.createPageListConverter();
|
|
6029
6155
|
pages.push(await converter.convert(relatedEntity, {
|
|
@@ -6034,7 +6160,7 @@ class AXPLayoutAdapterFactory {
|
|
|
6034
6160
|
}));
|
|
6035
6161
|
}
|
|
6036
6162
|
// Include nested page-related entities from merge-detail related entities
|
|
6037
|
-
const mergeDetailEntities =
|
|
6163
|
+
const mergeDetailEntities = evaluatedRelatedEntities.filter((re) => !re.hidden && re.layout?.type === 'merge-detail');
|
|
6038
6164
|
for (const mergeRelated of mergeDetailEntities || []) {
|
|
6039
6165
|
try {
|
|
6040
6166
|
const [moduleName, childEntityName] = (mergeRelated.entity || '').split('.');
|
|
@@ -6044,7 +6170,9 @@ class AXPLayoutAdapterFactory {
|
|
|
6044
6170
|
}
|
|
6045
6171
|
const nestedDataPath = mergeRelated?.persistence?.dataPath || childEntityName;
|
|
6046
6172
|
const nestedContext = get(rootContext, nestedDataPath) ?? rootContext;
|
|
6047
|
-
|
|
6173
|
+
// Evaluate hidden expressions for nested related entities
|
|
6174
|
+
const evaluatedNestedEntities = await this.evaluateRelatedEntitiesHidden(childEntity?.relatedEntities ?? [], nestedContext, dependencies);
|
|
6175
|
+
const nestedPageDetailEntities = evaluatedNestedEntities.filter((re) => !re.hidden && re.layout?.type === 'page-detail');
|
|
6048
6176
|
for (const nestedRelated of nestedPageDetailEntities || []) {
|
|
6049
6177
|
const converter = this.relatedEntityConverterFactory.createPageDetailsConverter();
|
|
6050
6178
|
pages.push(await converter.convert(nestedRelated, {
|
|
@@ -6053,7 +6181,7 @@ class AXPLayoutAdapterFactory {
|
|
|
6053
6181
|
rootTitle: rootTitle,
|
|
6054
6182
|
}));
|
|
6055
6183
|
}
|
|
6056
|
-
const nestedPageListEntities =
|
|
6184
|
+
const nestedPageListEntities = evaluatedNestedEntities.filter((re) => !re.hidden && re.layout?.type === 'page-list');
|
|
6057
6185
|
for (const nestedRelated of nestedPageListEntities || []) {
|
|
6058
6186
|
const converter = this.relatedEntityConverterFactory.createPageListConverter();
|
|
6059
6187
|
pages.push(await converter.convert(nestedRelated, {
|
|
@@ -6219,6 +6347,47 @@ const columnWidthMiddlewareProvider = {
|
|
|
6219
6347
|
};
|
|
6220
6348
|
//#endregion
|
|
6221
6349
|
|
|
6350
|
+
const DEFAULT_APPLY_ORDERING = true;
|
|
6351
|
+
/**
|
|
6352
|
+
* Provides synchronous access to the ApplyLayoutOrdering setting.
|
|
6353
|
+
* Used by layout ordering middleware to avoid async in the modifier and prevent startup deadlocks.
|
|
6354
|
+
* Value is updated on onLoaded/onChanged; first read can trigger a one-time background get (no await).
|
|
6355
|
+
*/
|
|
6356
|
+
class AXPLayoutOrderingConfigService {
|
|
6357
|
+
constructor() {
|
|
6358
|
+
this.settingsService = inject(AXPSettingsService);
|
|
6359
|
+
this._applyOrdering = signal(DEFAULT_APPLY_ORDERING, ...(ngDevMode ? [{ debugName: "_applyOrdering" }] : []));
|
|
6360
|
+
this.syncScheduled = false;
|
|
6361
|
+
this.settingsService.onLoaded.subscribe(() => this.syncFromSettings());
|
|
6362
|
+
this.settingsService.onChanged.subscribe(() => this.syncFromSettings());
|
|
6363
|
+
}
|
|
6364
|
+
getApplyOrdering() {
|
|
6365
|
+
if (!this.syncScheduled) {
|
|
6366
|
+
this.syncScheduled = true;
|
|
6367
|
+
this.settingsService
|
|
6368
|
+
.get(AXPCommonSettings.ApplyLayoutOrdering)
|
|
6369
|
+
.then((value) => this._applyOrdering.set(value ?? DEFAULT_APPLY_ORDERING))
|
|
6370
|
+
.catch(() => this._applyOrdering.set(DEFAULT_APPLY_ORDERING));
|
|
6371
|
+
}
|
|
6372
|
+
return this._applyOrdering();
|
|
6373
|
+
}
|
|
6374
|
+
async syncFromSettings() {
|
|
6375
|
+
try {
|
|
6376
|
+
const value = await this.settingsService.get(AXPCommonSettings.ApplyLayoutOrdering);
|
|
6377
|
+
this._applyOrdering.set(value ?? DEFAULT_APPLY_ORDERING);
|
|
6378
|
+
}
|
|
6379
|
+
catch {
|
|
6380
|
+
this._applyOrdering.set(DEFAULT_APPLY_ORDERING);
|
|
6381
|
+
}
|
|
6382
|
+
}
|
|
6383
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AXPLayoutOrderingConfigService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
6384
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AXPLayoutOrderingConfigService, providedIn: 'root' }); }
|
|
6385
|
+
}
|
|
6386
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AXPLayoutOrderingConfigService, decorators: [{
|
|
6387
|
+
type: Injectable,
|
|
6388
|
+
args: [{ providedIn: 'root' }]
|
|
6389
|
+
}], ctorParameters: () => [] });
|
|
6390
|
+
|
|
6222
6391
|
/**
|
|
6223
6392
|
* Default order for common sections
|
|
6224
6393
|
*/
|
|
@@ -6289,6 +6458,10 @@ const getPropertyOrder = (sectionId, propertyName, orderConfig) => {
|
|
|
6289
6458
|
*/
|
|
6290
6459
|
const layoutOrderingMiddlewareFactory = (config) => {
|
|
6291
6460
|
return (context) => {
|
|
6461
|
+
const configService = inject(AXPLayoutOrderingConfigService);
|
|
6462
|
+
if (!configService.getApplyOrdering()) {
|
|
6463
|
+
return;
|
|
6464
|
+
}
|
|
6292
6465
|
const { sections: sectionOrder, properties: propertyOrder } = config;
|
|
6293
6466
|
//#region ---- Order Sections ----
|
|
6294
6467
|
const orderSections = (layout) => {
|
|
@@ -6438,7 +6611,9 @@ const AXPCrudModifier = {
|
|
|
6438
6611
|
if (!queries?.byKey) {
|
|
6439
6612
|
queries.byKey = {
|
|
6440
6613
|
execute: async (id) => {
|
|
6441
|
-
|
|
6614
|
+
const data = await dataService.getOne(id);
|
|
6615
|
+
// debugger;
|
|
6616
|
+
return data;
|
|
6442
6617
|
},
|
|
6443
6618
|
type: AXPEntityQueryType.Single,
|
|
6444
6619
|
};
|
|
@@ -7683,7 +7858,7 @@ class AXPEntityCategoryTreeSelectorComponent extends AXBasePageComponent {
|
|
|
7683
7858
|
await this.selectAllLeafDescendants(nodeId);
|
|
7684
7859
|
}
|
|
7685
7860
|
else {
|
|
7686
|
-
// childrenCount is undefined -
|
|
7861
|
+
// childrenCount is undefined - fetch once and reuse to avoid double datasource call
|
|
7687
7862
|
const childNodes = await this.datasource(nodeId);
|
|
7688
7863
|
if (!childNodes || childNodes.length === 0) {
|
|
7689
7864
|
// No children - this is a leaf node, add it
|
|
@@ -7692,8 +7867,8 @@ class AXPEntityCategoryTreeSelectorComponent extends AXBasePageComponent {
|
|
|
7692
7867
|
this.selectedNodeIds.set(Array.from(currentSelected));
|
|
7693
7868
|
}
|
|
7694
7869
|
else {
|
|
7695
|
-
// Has children -
|
|
7696
|
-
await this.selectAllLeafDescendants(nodeId);
|
|
7870
|
+
// Has children - pass prefetched childNodes to avoid datasource(nodeId) again
|
|
7871
|
+
await this.selectAllLeafDescendants(nodeId, childNodes);
|
|
7697
7872
|
}
|
|
7698
7873
|
}
|
|
7699
7874
|
}
|
|
@@ -7759,16 +7934,17 @@ class AXPEntityCategoryTreeSelectorComponent extends AXBasePageComponent {
|
|
|
7759
7934
|
/**
|
|
7760
7935
|
* Expands parent nodes, collects all LEAF descendants, and selects them visually.
|
|
7761
7936
|
* If the parent itself is a leaf (no children), it will be added.
|
|
7937
|
+
* When prefetchedChildren is provided, avoids an extra datasource(parentId) call.
|
|
7762
7938
|
*/
|
|
7763
|
-
async selectAllLeafDescendants(parentId) {
|
|
7939
|
+
async selectAllLeafDescendants(parentId, prefetchedChildren) {
|
|
7764
7940
|
if (!this.treeData || !this.treeConfig) {
|
|
7765
7941
|
return;
|
|
7766
7942
|
}
|
|
7767
7943
|
const treeComponent = this.tree();
|
|
7768
7944
|
try {
|
|
7769
7945
|
const leafIds = new Set();
|
|
7770
|
-
// Expand and collect leaf nodes
|
|
7771
|
-
await this.collectLeafNodes(parentId, leafIds, treeComponent);
|
|
7946
|
+
// Expand and collect leaf nodes; pass prefetchedChildren to avoid duplicate API call
|
|
7947
|
+
await this.collectLeafNodes(parentId, leafIds, treeComponent, prefetchedChildren);
|
|
7772
7948
|
// Also check if the parent itself is a leaf (has no children)
|
|
7773
7949
|
if (parentId !== 'all') {
|
|
7774
7950
|
const isLeaf = await this.isLeafNodeCheck(parentId);
|
|
@@ -7843,14 +8019,19 @@ class AXPEntityCategoryTreeSelectorComponent extends AXBasePageComponent {
|
|
|
7843
8019
|
/**
|
|
7844
8020
|
* Recursively expands parent nodes and collects LEAF node IDs.
|
|
7845
8021
|
* When treeComponent is provided, expands each parent node before loading children.
|
|
8022
|
+
* When prefetchedChildren is provided, uses it instead of calling datasource(parentId) (one less API call).
|
|
8023
|
+
* Fetches each node's children at most once by passing grandchildren when recursing.
|
|
7846
8024
|
*/
|
|
7847
|
-
async collectLeafNodes(parentId, collection, treeComponent) {
|
|
8025
|
+
async collectLeafNodes(parentId, collection, treeComponent, prefetchedChildren) {
|
|
7848
8026
|
if (!this.treeData || !this.treeConfig) {
|
|
7849
8027
|
return;
|
|
7850
8028
|
}
|
|
7851
8029
|
try {
|
|
7852
8030
|
let childNodes;
|
|
7853
|
-
if (
|
|
8031
|
+
if (prefetchedChildren !== undefined) {
|
|
8032
|
+
childNodes = prefetchedChildren;
|
|
8033
|
+
}
|
|
8034
|
+
else if (parentId === 'all') {
|
|
7854
8035
|
// Expand 'all' node if tree component provided
|
|
7855
8036
|
if (treeComponent) {
|
|
7856
8037
|
try {
|
|
@@ -7860,7 +8041,7 @@ class AXPEntityCategoryTreeSelectorComponent extends AXBasePageComponent {
|
|
|
7860
8041
|
// Ignore expand errors
|
|
7861
8042
|
}
|
|
7862
8043
|
}
|
|
7863
|
-
// Get root node's children
|
|
8044
|
+
// Get root node's children (single datasource call)
|
|
7864
8045
|
const rootNodes = await this.datasource();
|
|
7865
8046
|
if (!rootNodes || rootNodes.length === 0) {
|
|
7866
8047
|
return;
|
|
@@ -7878,13 +8059,13 @@ class AXPEntityCategoryTreeSelectorComponent extends AXBasePageComponent {
|
|
|
7878
8059
|
// Ignore expand errors
|
|
7879
8060
|
}
|
|
7880
8061
|
}
|
|
7881
|
-
// Load children via datasource
|
|
8062
|
+
// Load children via datasource (single call per parent)
|
|
7882
8063
|
childNodes = await this.datasource(parentId);
|
|
7883
8064
|
}
|
|
7884
8065
|
if (!childNodes || childNodes.length === 0) {
|
|
7885
8066
|
return;
|
|
7886
8067
|
}
|
|
7887
|
-
// Process each child
|
|
8068
|
+
// Process each child: fetch grandchildren once, then recurse with them to avoid double datasource call
|
|
7888
8069
|
for (const childNode of childNodes) {
|
|
7889
8070
|
const childId = String(childNode['id'] ?? '');
|
|
7890
8071
|
if (!childId || childId === 'all' || collection.has(childId)) {
|
|
@@ -7892,15 +8073,13 @@ class AXPEntityCategoryTreeSelectorComponent extends AXBasePageComponent {
|
|
|
7892
8073
|
}
|
|
7893
8074
|
// Cache node data
|
|
7894
8075
|
this.cacheNodeFromTreeNode(childNode);
|
|
7895
|
-
//
|
|
7896
|
-
const
|
|
7897
|
-
if (!
|
|
7898
|
-
// This is a LEAF node - add it
|
|
8076
|
+
// Fetch children once; use result to decide leaf vs recurse (avoids nodeHasChildren -> extra datasource)
|
|
8077
|
+
const grandchildNodes = await this.datasource(childId);
|
|
8078
|
+
if (!grandchildNodes || grandchildNodes.length === 0) {
|
|
7899
8079
|
collection.add(childId);
|
|
7900
8080
|
}
|
|
7901
8081
|
else {
|
|
7902
|
-
|
|
7903
|
-
await this.collectLeafNodes(childId, collection, treeComponent);
|
|
8082
|
+
await this.collectLeafNodes(childId, collection, treeComponent, grandchildNodes);
|
|
7904
8083
|
}
|
|
7905
8084
|
}
|
|
7906
8085
|
}
|
|
@@ -7961,6 +8140,7 @@ class AXPEntityCategoryTreeSelectorComponent extends AXBasePageComponent {
|
|
|
7961
8140
|
/**
|
|
7962
8141
|
* Builds complete ancestor chains for all selected node IDs.
|
|
7963
8142
|
* Returns a Map where key is the selected node ID and value is array of ancestor IDs from root to parent.
|
|
8143
|
+
* Batch-fetches missing node and ancestor data in parallel to minimize API calls.
|
|
7964
8144
|
*/
|
|
7965
8145
|
async buildAncestorChains(selectedIds) {
|
|
7966
8146
|
const ancestorChains = new Map();
|
|
@@ -7968,18 +8148,44 @@ class AXPEntityCategoryTreeSelectorComponent extends AXBasePageComponent {
|
|
|
7968
8148
|
return ancestorChains;
|
|
7969
8149
|
if (!this.treeData.categoryEntityDef?.parentKey)
|
|
7970
8150
|
return ancestorChains;
|
|
8151
|
+
// Collect and batch-fetch all missing IDs (selected nodes without parent + ancestors) in rounds
|
|
8152
|
+
let toFetch = new Set();
|
|
8153
|
+
for (const nodeId of selectedIds) {
|
|
8154
|
+
const cached = this.nodeDataCache.get(nodeId);
|
|
8155
|
+
if (!cached || this.getParentIdFromNodeData(cached) === null) {
|
|
8156
|
+
toFetch.add(nodeId);
|
|
8157
|
+
}
|
|
8158
|
+
}
|
|
8159
|
+
while (toFetch.size > 0) {
|
|
8160
|
+
const results = await Promise.all(Array.from(toFetch).map((id) => this.fetchItemById(id)));
|
|
8161
|
+
const idsArr = Array.from(toFetch);
|
|
8162
|
+
results.forEach((item, i) => {
|
|
8163
|
+
if (item)
|
|
8164
|
+
this.nodeDataCache.set(idsArr[i], item);
|
|
8165
|
+
});
|
|
8166
|
+
toFetch = new Set();
|
|
8167
|
+
for (const nodeId of selectedIds) {
|
|
8168
|
+
let currentData = this.nodeDataCache.get(nodeId) ?? null;
|
|
8169
|
+
const visited = new Set();
|
|
8170
|
+
while (currentData) {
|
|
8171
|
+
const parentId = this.getParentIdFromNodeData(currentData);
|
|
8172
|
+
if (!parentId || visited.has(parentId))
|
|
8173
|
+
break;
|
|
8174
|
+
visited.add(parentId);
|
|
8175
|
+
if (!this.nodeDataCache.has(parentId)) {
|
|
8176
|
+
toFetch.add(parentId);
|
|
8177
|
+
break;
|
|
8178
|
+
}
|
|
8179
|
+
currentData = this.nodeDataCache.get(parentId) ?? null;
|
|
8180
|
+
}
|
|
8181
|
+
}
|
|
8182
|
+
}
|
|
8183
|
+
// Build chains from cache (no further API calls)
|
|
7971
8184
|
for (const nodeId of selectedIds) {
|
|
7972
8185
|
const chain = [];
|
|
7973
8186
|
let currentData = this.nodeDataCache.get(nodeId) ?? null;
|
|
7974
8187
|
if (!currentData)
|
|
7975
8188
|
continue;
|
|
7976
|
-
if (!this.getParentIdFromNodeData(currentData)) {
|
|
7977
|
-
const refetched = await this.fetchItemById(nodeId);
|
|
7978
|
-
if (refetched) {
|
|
7979
|
-
currentData = refetched;
|
|
7980
|
-
this.nodeDataCache.set(nodeId, refetched);
|
|
7981
|
-
}
|
|
7982
|
-
}
|
|
7983
8189
|
const visited = new Set();
|
|
7984
8190
|
while (currentData) {
|
|
7985
8191
|
const parentId = this.getParentIdFromNodeData(currentData);
|
|
@@ -7987,19 +8193,7 @@ class AXPEntityCategoryTreeSelectorComponent extends AXBasePageComponent {
|
|
|
7987
8193
|
break;
|
|
7988
8194
|
visited.add(parentId);
|
|
7989
8195
|
chain.unshift(parentId);
|
|
7990
|
-
|
|
7991
|
-
const parentData = await this.fetchItemById(parentId);
|
|
7992
|
-
if (parentData) {
|
|
7993
|
-
this.nodeDataCache.set(parentId, parentData);
|
|
7994
|
-
currentData = parentData;
|
|
7995
|
-
}
|
|
7996
|
-
else {
|
|
7997
|
-
break;
|
|
7998
|
-
}
|
|
7999
|
-
}
|
|
8000
|
-
else {
|
|
8001
|
-
currentData = this.nodeDataCache.get(parentId) ?? null;
|
|
8002
|
-
}
|
|
8196
|
+
currentData = this.nodeDataCache.get(parentId) ?? null;
|
|
8003
8197
|
}
|
|
8004
8198
|
ancestorChains.set(nodeId, chain);
|
|
8005
8199
|
}
|
|
@@ -8163,27 +8357,34 @@ class AXPEntityCategoryTreeSelectorComponent extends AXBasePageComponent {
|
|
|
8163
8357
|
// Sort parents by depth (deepest first so we update bottom-up)
|
|
8164
8358
|
const sortedParents = await this.sortParentsByDepth(Array.from(parentsToUpdate));
|
|
8165
8359
|
const reversedParents = [...sortedParents].reverse(); // Deepest first
|
|
8360
|
+
// Batch-load children for parents that don't have them in tree (parallel instead of sequential)
|
|
8361
|
+
const parentIdsNeedingLoad = reversedParents.filter((parentId) => {
|
|
8362
|
+
const parentNode = treeComponent.findNode(parentId);
|
|
8363
|
+
const treeChildren = parentNode?.['children'];
|
|
8364
|
+
return !treeChildren || treeChildren.length === 0;
|
|
8365
|
+
});
|
|
8366
|
+
const childrenByParentId = new Map();
|
|
8367
|
+
if (parentIdsNeedingLoad.length > 0) {
|
|
8368
|
+
const results = await Promise.all(parentIdsNeedingLoad.map((id) => this.datasource(id)));
|
|
8369
|
+
results.forEach((dsChildren, index) => {
|
|
8370
|
+
if (dsChildren && dsChildren.length > 0) {
|
|
8371
|
+
childrenByParentId.set(parentIdsNeedingLoad[index], dsChildren.map((c) => ({ id: String(c['id'] ?? '') })));
|
|
8372
|
+
}
|
|
8373
|
+
});
|
|
8374
|
+
}
|
|
8166
8375
|
for (const parentId of reversedParents) {
|
|
8167
8376
|
try {
|
|
8168
8377
|
const parentNode = treeComponent.findNode(parentId);
|
|
8169
|
-
// Get all direct children
|
|
8170
|
-
// First try from tree node, then fall back to datasource
|
|
8378
|
+
// Get all direct children: from tree node, or from batch-loaded map
|
|
8171
8379
|
let childrenList = [];
|
|
8172
8380
|
const treeChildren = parentNode?.['children'];
|
|
8173
8381
|
if (treeChildren && treeChildren.length > 0) {
|
|
8174
8382
|
childrenList = treeChildren.map((c) => ({ id: String(c['id'] ?? '') }));
|
|
8175
8383
|
}
|
|
8176
8384
|
else {
|
|
8177
|
-
|
|
8178
|
-
|
|
8179
|
-
|
|
8180
|
-
if (dsChildren && dsChildren.length > 0) {
|
|
8181
|
-
childrenList = dsChildren.map((c) => ({ id: String(c['id'] ?? '') }));
|
|
8182
|
-
}
|
|
8183
|
-
}
|
|
8184
|
-
catch {
|
|
8185
|
-
// Datasource failed, skip this parent
|
|
8186
|
-
continue;
|
|
8385
|
+
const loaded = childrenByParentId.get(parentId);
|
|
8386
|
+
if (loaded && loaded.length > 0) {
|
|
8387
|
+
childrenList = loaded;
|
|
8187
8388
|
}
|
|
8188
8389
|
}
|
|
8189
8390
|
if (childrenList.length === 0) {
|
|
@@ -8244,6 +8445,7 @@ class AXPEntityCategoryTreeSelectorComponent extends AXBasePageComponent {
|
|
|
8244
8445
|
/**
|
|
8245
8446
|
* Loads node data for IDs that are selected but not yet in the cache.
|
|
8246
8447
|
* This is critical for pre-selected values in collapsed branches.
|
|
8448
|
+
* Fetches all missing IDs in parallel to minimize API calls and latency.
|
|
8247
8449
|
*/
|
|
8248
8450
|
async loadMissingNodeData(selectedIds) {
|
|
8249
8451
|
if (!this.treeData || !this.treeConfig || selectedIds.length === 0) {
|
|
@@ -8253,13 +8455,14 @@ class AXPEntityCategoryTreeSelectorComponent extends AXBasePageComponent {
|
|
|
8253
8455
|
if (missingIds.length === 0)
|
|
8254
8456
|
return;
|
|
8255
8457
|
try {
|
|
8256
|
-
// Prefer fetching each id via service (uses byKey when available = full item with parent)
|
|
8257
8458
|
const valueField = this.treeConfig.valueField || 'id';
|
|
8258
|
-
|
|
8259
|
-
|
|
8260
|
-
if (item)
|
|
8459
|
+
const results = await Promise.all(missingIds.map((id) => this.fetchItemById(id)));
|
|
8460
|
+
results.forEach((item, index) => {
|
|
8461
|
+
if (item) {
|
|
8462
|
+
const id = missingIds[index];
|
|
8261
8463
|
this.nodeDataCache.set(String(item[valueField] ?? id), item);
|
|
8262
|
-
|
|
8464
|
+
}
|
|
8465
|
+
});
|
|
8263
8466
|
}
|
|
8264
8467
|
catch (error) {
|
|
8265
8468
|
console.error('Error loading missing node data:', error);
|
|
@@ -8268,18 +8471,22 @@ class AXPEntityCategoryTreeSelectorComponent extends AXBasePageComponent {
|
|
|
8268
8471
|
/**
|
|
8269
8472
|
* For each selected id, if cached item has no parent (e.g. batch query returned minimal fields),
|
|
8270
8473
|
* re-fetch by id so we have parent/parentId for building ancestor chains.
|
|
8474
|
+
* Fetches all needing refresh in parallel.
|
|
8271
8475
|
*/
|
|
8272
8476
|
async ensureParentDataInCache(selectedIds) {
|
|
8273
8477
|
if (!this.treeConfig)
|
|
8274
8478
|
return;
|
|
8275
|
-
|
|
8479
|
+
const idsNeedingFetch = selectedIds.filter((id) => {
|
|
8276
8480
|
const cached = this.nodeDataCache.get(id);
|
|
8277
|
-
|
|
8278
|
-
|
|
8279
|
-
|
|
8280
|
-
|
|
8281
|
-
|
|
8282
|
-
|
|
8481
|
+
return cached && this.getParentIdFromNodeData(cached) === null;
|
|
8482
|
+
});
|
|
8483
|
+
if (idsNeedingFetch.length === 0)
|
|
8484
|
+
return;
|
|
8485
|
+
const results = await Promise.all(idsNeedingFetch.map((id) => this.fetchItemById(id)));
|
|
8486
|
+
results.forEach((full, index) => {
|
|
8487
|
+
if (full)
|
|
8488
|
+
this.nodeDataCache.set(idsNeedingFetch[index], full);
|
|
8489
|
+
});
|
|
8283
8490
|
}
|
|
8284
8491
|
/**
|
|
8285
8492
|
* Resolves parent ID from node data: supports nested `parent` object or flat parentId/parentKey.
|
|
@@ -10612,6 +10819,7 @@ class AXPEntityListTableService {
|
|
|
10612
10819
|
// 🎪 Events
|
|
10613
10820
|
...this.createDefaultEvents(entity, allActions),
|
|
10614
10821
|
};
|
|
10822
|
+
console.log('listOptions', listOptions);
|
|
10615
10823
|
return listOptions;
|
|
10616
10824
|
}
|
|
10617
10825
|
//#endregion
|
|
@@ -10660,18 +10868,28 @@ class AXPEntityListTableService {
|
|
|
10660
10868
|
createRowCommands(actions, priority) {
|
|
10661
10869
|
return actions
|
|
10662
10870
|
.filter((action) => action.scope === AXPEntityCommandScope.Individual && // Only individual actions
|
|
10663
|
-
action.priority === priority
|
|
10664
|
-
!action.hidden)
|
|
10871
|
+
action.priority === priority)
|
|
10665
10872
|
.map((action) => ({
|
|
10666
10873
|
name: action.name,
|
|
10667
10874
|
text: action.title,
|
|
10668
10875
|
icon: action.icon,
|
|
10669
10876
|
color: action.color,
|
|
10670
10877
|
look: 'outline',
|
|
10671
|
-
|
|
10672
|
-
disabled: action.disabled,
|
|
10878
|
+
hidden: priority === 'secondary' ? this.wrapRowExpr(action.hidden) : action.hidden,
|
|
10879
|
+
disabled: priority === 'secondary' ? this.wrapRowExpr(action.disabled) : action.disabled,
|
|
10673
10880
|
}));
|
|
10674
10881
|
}
|
|
10882
|
+
/**
|
|
10883
|
+
* Wraps string values with AXP_ROW_EXPR_PREFIX so widget-renderer does not
|
|
10884
|
+
* evaluate them (it only treats {{ ... }} as expressions). Data-list will
|
|
10885
|
+
* unwrap and evaluate per row with row data. Only used for secondary commands.
|
|
10886
|
+
*/
|
|
10887
|
+
wrapRowExpr(value) {
|
|
10888
|
+
if (value === undefined || value === null || typeof value !== 'string') {
|
|
10889
|
+
return value;
|
|
10890
|
+
}
|
|
10891
|
+
return `${AXP_ROW_EXPR_PREFIX}${value}`;
|
|
10892
|
+
}
|
|
10675
10893
|
/**
|
|
10676
10894
|
* Check if entity has Selected Scope Actions
|
|
10677
10895
|
*/
|
|
@@ -10918,7 +11136,7 @@ class AXPEntityListWidgetViewComponent extends AXPValueWidgetComponent {
|
|
|
10918
11136
|
return !usedOverrideActions.has(actionKey);
|
|
10919
11137
|
})
|
|
10920
11138
|
.map((action) => new AXPEntityCommandTriggerViewModel(this.entity(), action));
|
|
10921
|
-
return [...additionalActions, ...mergedActions]
|
|
11139
|
+
return [...additionalActions, ...mergedActions];
|
|
10922
11140
|
}, ...(ngDevMode ? [{ debugName: "allActions" }] : []));
|
|
10923
11141
|
this.primaryActions = computed(() => {
|
|
10924
11142
|
const actions = this.allActions()
|
|
@@ -15332,5 +15550,5 @@ function detectEntityChanges(oldObj, newObj) {
|
|
|
15332
15550
|
* Generated bundle index. Do not edit.
|
|
15333
15551
|
*/
|
|
15334
15552
|
|
|
15335
|
-
export { AXMEntityCrudService, AXMEntityCrudServiceImpl, AXPCategoryTreeService, AXPCreateEntityCommand, AXPCreateEntityWorkflow, AXPDataSeederService, AXPDeleteEntityWorkflow, AXPEntitiesListDataSourceDefinition, AXPEntityApplyUpdatesAction, AXPEntityCategoryTreeSelectorComponent, AXPEntityCategoryWidget, AXPEntityCategoryWidgetColumnComponent, AXPEntityCategoryWidgetEditComponent, AXPEntityCategoryWidgetViewComponent, AXPEntityCommandTriggerViewModel, AXPEntityCreateEvent, AXPEntityCreatePopupAction, AXPEntityCreateSubmittedAction, AXPEntityCreateViewElementViewModel, AXPEntityCreateViewModelFactory, AXPEntityCreateViewSectionViewModel, AXPEntityDataProvider, AXPEntityDataProviderImpl, AXPEntityDataSelectorService, AXPEntityDefinitionRegistryService, AXPEntityDeletedEvent, AXPEntityDetailListViewModel, AXPEntityDetailPopoverComponent, AXPEntityDetailPopoverService, AXPEntityDetailViewModelFactory, AXPEntityDetailViewModelResolver, AXPEntityEventDispatcherService, AXPEntityEventsKeys, AXPEntityFormBuilderService, AXPEntityListTableService, AXPEntityListViewColumnViewModel, AXPEntityListViewModelFactory, AXPEntityListViewModelResolver, AXPEntityListWidget, AXPEntityListWidgetViewComponent, AXPEntityMasterCreateViewModel, AXPEntityMasterListViewModel, AXPEntityMasterListViewQueryViewModel, AXPEntityMasterSingleElementViewModel, AXPEntityMasterSingleViewGroupViewModel, AXPEntityMasterSingleViewModel, AXPEntityMasterUpdateElementViewModel, AXPEntityMasterUpdateViewModel, AXPEntityMasterUpdateViewModelFactory, AXPEntityMiddleware, AXPEntityModifyConfirmedAction, AXPEntityModifyEvent, AXPEntityModifySectionPopupAction, AXPEntityModule, AXPEntityPerformDeleteAction, AXPEntityResolver, AXPEntityService, AXPEntityStorageService, AXPEntityUpdateViewSectionViewModel, AXPGetEntityDetailsQuery, AXPLookupWidget, AXPLookupWidgetColumnComponent, AXPLookupWidgetEditComponent, AXPLookupWidgetViewComponent, AXPMiddlewareAbortError, AXPMiddlewareEntityStorageService, AXPModifyEntitySectionWorkflow, AXPMultiSourceDefinitionProviderContext, AXPMultiSourceDefinitionProviderService, AXPMultiSourceFederatedSearchService, AXPMultiSourceSelectorComponent, AXPMultiSourceSelectorService, AXPMultiSourceSelectorWidget, AXPMultiSourceSelectorWidgetColumnComponent, AXPMultiSourceSelectorWidgetEditComponent, AXPMultiSourceSelectorWidgetViewComponent, AXPMultiSourceType, AXPOpenEntityDetailsCommand, AXPQuickEntityModifyPopupAction, AXPQuickModifyEntityWorkflow, AXPShowDetailViewAction, AXPShowDetailsViewWorkflow, AXPShowListViewAction, AXPShowListViewWorkflow, AXPTruncatedBreadcrumbComponent, AXPUpdateEntityCommand, AXPViewEntityDetailsCommand, AXP_DATA_SEEDER_TOKEN, AXP_ENTITY_ACTION_PLUGIN, AXP_ENTITY_CONFIG_TOKEN, AXP_ENTITY_DEFINITION_LOADER, AXP_ENTITY_MODIFIER, AXP_ENTITY_STORAGE_BACKEND, AXP_ENTITY_STORAGE_MIDDLEWARE, AXP_MULTI_SOURCE_DEFINITION_PROVIDER, DEFAULT_PROPERTY_ORDER, DEFAULT_SECTION_ORDER, EntityBuilder, EntityDataAccessor, actionExists, cloneLayoutArrays, columnWidthMiddleware, columnWidthMiddlewareProvider, createLayoutOrderingMiddlewareProvider, createModifierContext, detectEntityChanges, ensureLayoutPropertyView, ensureLayoutSection, ensureListActions, entityDetailsCreateActions, entityDetailsCrudActions, entityDetailsEditAction, entityDetailsNewEditAction, entityDetailsReferenceCondition, entityDetailsReferenceCreateActions, entityDetailsSimpleCondition, entityMasterBulkDeleteAction, entityMasterCreateAction, entityMasterCrudActions, entityMasterDeleteAction, entityMasterEditAction, entityMasterRecordActions, entityMasterViewAction, entityOverrideDetailsViewAction, eventDispatchMiddleware, isAXPMiddlewareAbortError, layoutOrderingMiddlewareFactory, layoutOrderingMiddlewareProvider };
|
|
15553
|
+
export { AXMEntityCrudService, AXMEntityCrudServiceImpl, AXPCategoryTreeService, AXPCreateEntityCommand, AXPCreateEntityWorkflow, AXPDataSeederService, AXPDeleteEntityWorkflow, AXPEntitiesListDataSourceDefinition, AXPEntityApplyUpdatesAction, AXPEntityCategoryTreeSelectorComponent, AXPEntityCategoryWidget, AXPEntityCategoryWidgetColumnComponent, AXPEntityCategoryWidgetEditComponent, AXPEntityCategoryWidgetViewComponent, AXPEntityCommandTriggerViewModel, AXPEntityCreateEvent, AXPEntityCreatePopupAction, AXPEntityCreateSubmittedAction, AXPEntityCreateViewElementViewModel, AXPEntityCreateViewModelFactory, AXPEntityCreateViewSectionViewModel, AXPEntityDataProvider, AXPEntityDataProviderImpl, AXPEntityDataSelectorService, AXPEntityDefinitionRegistryService, AXPEntityDeletedEvent, AXPEntityDetailListViewModel, AXPEntityDetailPopoverComponent, AXPEntityDetailPopoverService, AXPEntityDetailViewModelFactory, AXPEntityDetailViewModelResolver, AXPEntityEventDispatcherService, AXPEntityEventsKeys, AXPEntityFormBuilderService, AXPEntityListTableService, AXPEntityListViewColumnViewModel, AXPEntityListViewModelFactory, AXPEntityListViewModelResolver, AXPEntityListWidget, AXPEntityListWidgetViewComponent, AXPEntityMasterCreateViewModel, AXPEntityMasterListViewModel, AXPEntityMasterListViewQueryViewModel, AXPEntityMasterSingleElementViewModel, AXPEntityMasterSingleViewGroupViewModel, AXPEntityMasterSingleViewModel, AXPEntityMasterUpdateElementViewModel, AXPEntityMasterUpdateViewModel, AXPEntityMasterUpdateViewModelFactory, AXPEntityMiddleware, AXPEntityModifyConfirmedAction, AXPEntityModifyEvent, AXPEntityModifySectionPopupAction, AXPEntityModule, AXPEntityPerformDeleteAction, AXPEntityResolver, AXPEntityService, AXPEntityStorageService, AXPEntityUpdateViewSectionViewModel, AXPGetEntityDetailsQuery, AXPLayoutOrderingConfigService, AXPLookupWidget, AXPLookupWidgetColumnComponent, AXPLookupWidgetEditComponent, AXPLookupWidgetViewComponent, AXPMiddlewareAbortError, AXPMiddlewareEntityStorageService, AXPModifyEntitySectionWorkflow, AXPMultiSourceDefinitionProviderContext, AXPMultiSourceDefinitionProviderService, AXPMultiSourceFederatedSearchService, AXPMultiSourceSelectorComponent, AXPMultiSourceSelectorService, AXPMultiSourceSelectorWidget, AXPMultiSourceSelectorWidgetColumnComponent, AXPMultiSourceSelectorWidgetEditComponent, AXPMultiSourceSelectorWidgetViewComponent, AXPMultiSourceType, AXPOpenEntityDetailsCommand, AXPQuickEntityModifyPopupAction, AXPQuickModifyEntityWorkflow, AXPShowDetailViewAction, AXPShowDetailsViewWorkflow, AXPShowListViewAction, AXPShowListViewWorkflow, AXPTruncatedBreadcrumbComponent, AXPUpdateEntityCommand, AXPViewEntityDetailsCommand, AXP_DATA_SEEDER_TOKEN, AXP_ENTITY_ACTION_PLUGIN, AXP_ENTITY_CONFIG_TOKEN, AXP_ENTITY_DEFINITION_LOADER, AXP_ENTITY_MODIFIER, AXP_ENTITY_STORAGE_BACKEND, AXP_ENTITY_STORAGE_MIDDLEWARE, AXP_MULTI_SOURCE_DEFINITION_PROVIDER, DEFAULT_PROPERTY_ORDER, DEFAULT_SECTION_ORDER, EntityBuilder, EntityDataAccessor, actionExists, cloneLayoutArrays, columnWidthMiddleware, columnWidthMiddlewareProvider, createLayoutOrderingMiddlewareProvider, createModifierContext, detectEntityChanges, ensureLayoutPropertyView, ensureLayoutSection, ensureListActions, entityDetailsCreateActions, entityDetailsCrudActions, entityDetailsEditAction, entityDetailsNewEditAction, entityDetailsReferenceCondition, entityDetailsReferenceCreateActions, entityDetailsSimpleCondition, entityMasterBulkDeleteAction, entityMasterCreateAction, entityMasterCrudActions, entityMasterDeleteAction, entityMasterEditAction, entityMasterRecordActions, entityMasterViewAction, entityOverrideDetailsViewAction, eventDispatchMiddleware, isAXPMiddlewareAbortError, layoutOrderingMiddlewareFactory, layoutOrderingMiddlewareProvider };
|
|
15336
15554
|
//# sourceMappingURL=acorex-platform-layout-entity.mjs.map
|