@acorex/platform 20.9.38 → 20.9.40
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/fesm2022/acorex-platform-layout-entity-contracts.mjs +3 -2
- package/fesm2022/acorex-platform-layout-entity-contracts.mjs.map +1 -1
- package/fesm2022/acorex-platform-layout-entity.mjs +349 -31
- package/fesm2022/acorex-platform-layout-entity.mjs.map +1 -1
- package/fesm2022/acorex-platform-layout-widgets.mjs +65 -8
- package/fesm2022/acorex-platform-layout-widgets.mjs.map +1 -1
- package/fesm2022/acorex-platform-themes-default.mjs +1 -1
- package/fesm2022/acorex-platform-themes-default.mjs.map +1 -1
- package/package.json +6 -6
- package/types/acorex-platform-layout-entity-contracts.d.ts +12 -3
- package/types/acorex-platform-layout-entity.d.ts +14 -2
- package/types/acorex-platform-layout-widgets.d.ts +8 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { mergeForeignKeyFieldIntoCreateActions, collectNestedCreateHiddenProperties, AXPEntityCommandScope, normalizeEntityDisplayTemplate, sanitizeResolvedEntityDisplayTitle, isUnresolvedEntityDisplayTemplate, resolveEntityListFilterContextField, resolveEntityListFilterQueryField, AXPEntityDataSourceKeys, isCardFieldBadgeDisplay, resolveCardFieldBadgeColor, isEntityActionPermitted, getEntityInfo, resolveEnabledMasterListLayouts, resolveDefaultMasterListLayout, AXPEntityEventsKeys, collectEntityListSortableFields, collectEntityListFilterDefinitions, parseDottedFieldPath, ENTITY_ROW_POPUP_TITLE_OPTION,
|
|
1
|
+
import { mergeForeignKeyFieldIntoCreateActions, collectNestedCreateHiddenProperties, AXPEntityCommandScope, normalizeEntityDisplayTemplate, sanitizeResolvedEntityDisplayTitle, isUnresolvedEntityDisplayTemplate, resolveEntityListFilterContextField, resolveEntityListFilterQueryField, AXPEntityDataSourceKeys, isCardFieldBadgeDisplay, resolveCardFieldBadgeColor, isEntityActionPermitted, getEntityInfo, resolveEnabledMasterListLayouts, resolveDefaultMasterListLayout, AXPEntityEventsKeys, collectEntityListSortableFields, collectEntityListFilterDefinitions, parseDottedFieldPath, ENTITY_ROW_POPUP_TITLE_OPTION, buildPageListWidgetName, resolveLookupWidgetCreateOptions, AXPEntityQueryType, buildEntitySearchTitleContext } from '@acorex/platform/layout/entity-contracts';
|
|
2
2
|
export * from '@acorex/platform/layout/entity-contracts';
|
|
3
3
|
export { collectNestedCreateHiddenProperties, entityDetailsCreateActions, entityDetailsCreateActionsDeferredParent, entityDetailsCrudActions, entityDetailsEditAction, entityDetailsNewEditAction, entityDetailsReferenceCondition, entityDetailsReferenceCreateActions, entityDetailsSimpleCondition, entityMasterBulkDeleteAction, entityMasterCreateAction, entityMasterCrudActions, entityMasterDeleteAction, entityMasterEditAction, entityMasterRecordActions, entityMasterViewAction, entityOverrideDetailsViewAction, mergeForeignKeyFieldIntoCreateActions } from '@acorex/platform/layout/entity-contracts';
|
|
4
4
|
import { AXToastService } from '@acorex/components/toast';
|
|
@@ -24,7 +24,7 @@ import { AXButtonModule } from '@acorex/components/button';
|
|
|
24
24
|
import * as i3 from '@acorex/components/skeleton';
|
|
25
25
|
import { AXSkeletonModule } from '@acorex/components/skeleton';
|
|
26
26
|
import * as i2 from '@acorex/platform/layout/widget-core';
|
|
27
|
-
import { AXPWidgetCoreModule, AXPWidgetRegistryService, AXPColumnWidgetComponent, AXPValueWidgetComponent, AXPWidgetRendererDirective, AXPBaseWidgetComponent, WIDGET_CASCADE_TRACE_NS, AXP_WIDGETS_ADVANCE_SUB_MEDIA, AXP_WIDGETS_ADVANCE_CATEGORY,
|
|
27
|
+
import { AXPWidgetCoreModule, AXPWidgetRegistryService, AXPColumnWidgetComponent, AXPValueWidgetComponent, AXPWidgetRendererDirective, AXPBaseWidgetComponent, WIDGET_CASCADE_TRACE_NS, AXP_WIDGETS_EDITOR_CATEGORY, AXP_WIDGETS_ADVANCE_SUB_MEDIA, AXP_WIDGETS_ADVANCE_CATEGORY, AXP_WIDGET_DEFINITION_PROVIDER } from '@acorex/platform/layout/widget-core';
|
|
28
28
|
import { AXPCommandService, AXPQueryService, AXPQueryExecutor, AXPCommandExecutor, provideCommandSetups, provideQuerySetups, AXPCommandRegistry, AXPQueryRegistry } from '@acorex/platform/runtime';
|
|
29
29
|
import * as i5 from '@angular/common';
|
|
30
30
|
import { CommonModule, DOCUMENT, AsyncPipe, NgClass } from '@angular/common';
|
|
@@ -4552,12 +4552,14 @@ class AXPEntityListViewColumnViewModel {
|
|
|
4552
4552
|
this.width = this.column?.options?.width;
|
|
4553
4553
|
this.node = computed(() => {
|
|
4554
4554
|
const widget = this.property.schema.interface;
|
|
4555
|
+
// Column `visible` is resolved by the list viewmodel; do not forward expressions into widget options.
|
|
4556
|
+
const { visible: _columnVisible, ...columnOptions } = this.column?.options ?? {};
|
|
4555
4557
|
return {
|
|
4556
4558
|
path: this.column.options?.dataPath ?? this.name,
|
|
4557
4559
|
type: widget.type,
|
|
4558
4560
|
options: {
|
|
4559
4561
|
...widget?.options,
|
|
4560
|
-
...
|
|
4562
|
+
...columnOptions,
|
|
4561
4563
|
columnName: this.name,
|
|
4562
4564
|
// Include children from widget interface so column components can access them
|
|
4563
4565
|
...(widget.children ? { children: widget.children } : {}),
|
|
@@ -5587,7 +5589,7 @@ class AXPEntityMasterListViewModel {
|
|
|
5587
5589
|
if (shouldApplyViewState) {
|
|
5588
5590
|
this.view.set(nextView);
|
|
5589
5591
|
this.applyViewSorts();
|
|
5590
|
-
this.applyViewColumns();
|
|
5592
|
+
await this.applyViewColumns();
|
|
5591
5593
|
this.applyCardFields();
|
|
5592
5594
|
this.applyViewFilters();
|
|
5593
5595
|
this.resolvedListPaging.set(null);
|
|
@@ -5971,7 +5973,12 @@ class AXPEntityMasterListViewModel {
|
|
|
5971
5973
|
for (const column of next) {
|
|
5972
5974
|
const columnKey = column.column?.options?.dataPath ?? column.name;
|
|
5973
5975
|
column.width = defaultTo(columnWidthsMap.get(columnKey), column.column?.options?.width);
|
|
5974
|
-
|
|
5976
|
+
// Definition visibility (view.columns whitelist + options.visible) always wins for "hide".
|
|
5977
|
+
// Persisted settings may only further hide an already-allowed column.
|
|
5978
|
+
const definitionVisible = column.visible === true;
|
|
5979
|
+
const persistedVisible = columnVisibilityMap.get(columnKey);
|
|
5980
|
+
column.visible =
|
|
5981
|
+
definitionVisible && (persistedVisible === undefined ? true : persistedVisible !== false);
|
|
5975
5982
|
}
|
|
5976
5983
|
return next;
|
|
5977
5984
|
});
|
|
@@ -6107,7 +6114,7 @@ class AXPEntityMasterListViewModel {
|
|
|
6107
6114
|
const existingColumns = prev?.list?.views?.[this.view().name]?.columns;
|
|
6108
6115
|
const baseColumns = Array.isArray(existingColumns) && existingColumns.length
|
|
6109
6116
|
? existingColumns
|
|
6110
|
-
: this.
|
|
6117
|
+
: this.columns().map((c) => ({
|
|
6111
6118
|
name: c.column?.options?.dataPath ?? c.name,
|
|
6112
6119
|
visible: c.visible,
|
|
6113
6120
|
width: c.width,
|
|
@@ -6116,11 +6123,6 @@ class AXPEntityMasterListViewModel {
|
|
|
6116
6123
|
...c,
|
|
6117
6124
|
width: c.name === field ? columnSizeData.width : c.width,
|
|
6118
6125
|
}));
|
|
6119
|
-
const x = this.allAvailableColumns().map((c) => ({
|
|
6120
|
-
name: c.column?.options?.dataPath ?? c.name,
|
|
6121
|
-
visible: c.visible,
|
|
6122
|
-
width: c.width,
|
|
6123
|
-
}));
|
|
6124
6126
|
set(newSettings, `list.views.${this.view().name}.columns`, updatedColumns);
|
|
6125
6127
|
return newSettings;
|
|
6126
6128
|
});
|
|
@@ -6638,22 +6640,59 @@ class AXPEntityMasterListViewModel {
|
|
|
6638
6640
|
const relatedDef = this.getRegisteredEntity(related.entity);
|
|
6639
6641
|
return relatedDef?.properties?.find((property) => property.name === propertyName) ?? null;
|
|
6640
6642
|
}
|
|
6641
|
-
resetColumns() {
|
|
6642
|
-
this.applyViewColumns();
|
|
6643
|
+
async resetColumns() {
|
|
6644
|
+
await this.applyViewColumns();
|
|
6643
6645
|
}
|
|
6644
6646
|
async onColumnsChanged(columns) {
|
|
6645
6647
|
this.saveSettings('columnOrders', columns);
|
|
6646
6648
|
}
|
|
6647
|
-
|
|
6648
|
-
|
|
6649
|
-
|
|
6649
|
+
/**
|
|
6650
|
+
* Builds list columns for the active view:
|
|
6651
|
+
* - `view.columns` is a whitelist (empty = all columns)
|
|
6652
|
+
* - string `options.visible` is evaluated against the master-list scope
|
|
6653
|
+
*/
|
|
6654
|
+
async applyViewColumns() {
|
|
6655
|
+
const viewColumnNames = this.view().columns ?? [];
|
|
6656
|
+
const scope = this.createLogicExpressionScope(null);
|
|
6657
|
+
const cloned = await Promise.all(this.allAvailableColumns().map(async (c) => {
|
|
6650
6658
|
const column = this.entityDef.columns?.find((cc) => cc.name == c.name);
|
|
6651
6659
|
const col = new AXPEntityListViewColumnViewModel(c.property, column);
|
|
6652
|
-
|
|
6660
|
+
const expressionVisible = await this.resolveColumnVisibleExpression(col.visible, scope);
|
|
6661
|
+
const includedInView = viewColumnNames.length === 0 || viewColumnNames.some((name) => name === col.name);
|
|
6662
|
+
col.visible = includedInView && expressionVisible;
|
|
6653
6663
|
return col;
|
|
6654
|
-
});
|
|
6664
|
+
}));
|
|
6665
|
+
if (viewColumnNames.length > 0) {
|
|
6666
|
+
cloned.sort((a, b) => {
|
|
6667
|
+
const aIndex = viewColumnNames.indexOf(a.name);
|
|
6668
|
+
const bIndex = viewColumnNames.indexOf(b.name);
|
|
6669
|
+
const aOrder = aIndex === -1 ? Number.MAX_SAFE_INTEGER : aIndex;
|
|
6670
|
+
const bOrder = bIndex === -1 ? Number.MAX_SAFE_INTEGER : bIndex;
|
|
6671
|
+
return aOrder - bOrder;
|
|
6672
|
+
});
|
|
6673
|
+
}
|
|
6655
6674
|
this.columns.set(cloned);
|
|
6656
6675
|
}
|
|
6676
|
+
/**
|
|
6677
|
+
* Resolves column `options.visible` (boolean or expression) to a boolean for the current list scope.
|
|
6678
|
+
*/
|
|
6679
|
+
async resolveColumnVisibleExpression(visible, scope) {
|
|
6680
|
+
if (visible === false) {
|
|
6681
|
+
return false;
|
|
6682
|
+
}
|
|
6683
|
+
if (visible === true || visible == null || visible === '') {
|
|
6684
|
+
return true;
|
|
6685
|
+
}
|
|
6686
|
+
if (typeof visible === 'string') {
|
|
6687
|
+
if (!visible.includes('{{')) {
|
|
6688
|
+
const normalized = visible.trim().toLowerCase();
|
|
6689
|
+
return normalized !== 'false' && normalized !== '0';
|
|
6690
|
+
}
|
|
6691
|
+
const result = await this.expressionEvaluator.evaluate(visible, scope);
|
|
6692
|
+
return result !== false;
|
|
6693
|
+
}
|
|
6694
|
+
return Boolean(visible);
|
|
6695
|
+
}
|
|
6657
6696
|
onColumnsOrderChanged(event) {
|
|
6658
6697
|
const columns = [...this.columns()];
|
|
6659
6698
|
moveItemInArray(columns, event.previousIndex - 1, event.currentIndex - 1);
|
|
@@ -12144,6 +12183,8 @@ class AXPEntityDefinitionProviderWidgetEditComponent extends AXPProviderSelectWi
|
|
|
12144
12183
|
super(...arguments);
|
|
12145
12184
|
//#region ---- Services & Dependencies ----
|
|
12146
12185
|
this.entityRegistry = inject(AXPEntityDefinitionRegistryService);
|
|
12186
|
+
this.entityResolver = inject(AXPEntityResolver);
|
|
12187
|
+
this.translationService = inject(AXTranslationService);
|
|
12147
12188
|
/**
|
|
12148
12189
|
* Last applied module filter (from context) to avoid redundant reloads.
|
|
12149
12190
|
*/
|
|
@@ -12182,18 +12223,26 @@ class AXPEntityDefinitionProviderWidgetEditComponent extends AXPProviderSelectWi
|
|
|
12182
12223
|
* - `valueFormat`: `fullKey` (`module.name`, default) or `entityName` (short name only, for menus that store `entityListRef.entity`).
|
|
12183
12224
|
*/
|
|
12184
12225
|
async loadDataSourceRows() {
|
|
12185
|
-
const
|
|
12226
|
+
const entries = await this.entityResolver.listAll();
|
|
12186
12227
|
const filterModuleField = this.options()['filterModuleField'];
|
|
12187
12228
|
const valueFormat = this.options()['valueFormat'] ?? 'fullKey';
|
|
12188
|
-
let list =
|
|
12229
|
+
let list = entries;
|
|
12189
12230
|
if (filterModuleField) {
|
|
12190
12231
|
const mod = this.readModuleFromContext(filterModuleField);
|
|
12191
|
-
list = mod ?
|
|
12232
|
+
list = mod ? entries.filter((e) => e.module === mod) : [];
|
|
12192
12233
|
}
|
|
12193
|
-
|
|
12194
|
-
|
|
12195
|
-
|
|
12196
|
-
|
|
12234
|
+
const locale = this.translationService.getActiveLang();
|
|
12235
|
+
const registeredByKey = new Map(this.entityRegistry.getAll().map((e) => [`${e.module}.${e.name}`, e]));
|
|
12236
|
+
return list.map((e) => {
|
|
12237
|
+
const cached = registeredByKey.get(e.fullName);
|
|
12238
|
+
const resolvedTitle = cached
|
|
12239
|
+
? resolveSearchableText(cached.title, locale, this.translationService).trim() || e.name
|
|
12240
|
+
: e.name;
|
|
12241
|
+
return {
|
|
12242
|
+
value: valueFormat === 'entityName' ? e.name : e.fullName,
|
|
12243
|
+
title: `${resolvedTitle} (${e.fullName})`,
|
|
12244
|
+
};
|
|
12245
|
+
});
|
|
12197
12246
|
}
|
|
12198
12247
|
//#endregion
|
|
12199
12248
|
//#region ---- Refresh (e.g. when sibling module field changes) ----
|
|
@@ -13131,6 +13180,7 @@ class AXPEntityListWidgetViewComponent extends AXPValueWidgetComponent {
|
|
|
13131
13180
|
/**
|
|
13132
13181
|
* Route `filters` apply only when the active details `page` matches this list's entity
|
|
13133
13182
|
* (e.g. WorkOrder filters must not affect FailureRegister on the same asset layout).
|
|
13183
|
+
* page-list sidebar pages use `page-list-{module.entity}` as their page id.
|
|
13134
13184
|
*/
|
|
13135
13185
|
shouldApplyRouteFilters() {
|
|
13136
13186
|
const pageId = this.route.snapshot.queryParamMap.get('page');
|
|
@@ -13142,7 +13192,7 @@ class AXPEntityListWidgetViewComponent extends AXPValueWidgetComponent {
|
|
|
13142
13192
|
return false;
|
|
13143
13193
|
}
|
|
13144
13194
|
const [, entityName] = entitySource.split('.');
|
|
13145
|
-
return !!entityName && pageId === entityName;
|
|
13195
|
+
return ((!!entityName && pageId === entityName) || pageId === buildPageListWidgetName(entitySource));
|
|
13146
13196
|
}
|
|
13147
13197
|
/**
|
|
13148
13198
|
* Merges route filters into related-entity toolbar filters (route wins per field).
|
|
@@ -17364,7 +17414,7 @@ const AXPLookupWidget = {
|
|
|
17364
17414
|
name: 'lookup-editor',
|
|
17365
17415
|
title: 'Lookup',
|
|
17366
17416
|
categories: [],
|
|
17367
|
-
groups: [AXPWidgetGroupEnum.EntityWidget],
|
|
17417
|
+
groups: [AXPWidgetGroupEnum.EntityWidget, AXPWidgetGroupEnum.FormElement],
|
|
17368
17418
|
defaultFilterWidgetName: 'lookup-filter',
|
|
17369
17419
|
type: 'editor',
|
|
17370
17420
|
icon: 'fa-light fa-list-radio',
|
|
@@ -17395,7 +17445,25 @@ const AXPLookupWidget = {
|
|
|
17395
17445
|
interface: {
|
|
17396
17446
|
name: 'entity',
|
|
17397
17447
|
path: 'options.entity',
|
|
17398
|
-
type: AXPWidgetsCatalog.
|
|
17448
|
+
type: AXPWidgetsCatalog.entityDefinitionProvider,
|
|
17449
|
+
options: {
|
|
17450
|
+
hasClearButton: true,
|
|
17451
|
+
allowSearch: true,
|
|
17452
|
+
},
|
|
17453
|
+
},
|
|
17454
|
+
},
|
|
17455
|
+
visible: true,
|
|
17456
|
+
},
|
|
17457
|
+
{
|
|
17458
|
+
name: 'filter',
|
|
17459
|
+
title: 'Filters',
|
|
17460
|
+
group: AXP_DATA_PROPERTY_GROUP,
|
|
17461
|
+
schema: {
|
|
17462
|
+
dataType: 'object',
|
|
17463
|
+
interface: {
|
|
17464
|
+
name: 'filter',
|
|
17465
|
+
path: 'options.filter',
|
|
17466
|
+
type: 'lookup-filter-options',
|
|
17399
17467
|
},
|
|
17400
17468
|
},
|
|
17401
17469
|
visible: true,
|
|
@@ -17533,6 +17601,250 @@ const AXPLookupWidget = {
|
|
|
17533
17601
|
},
|
|
17534
17602
|
};
|
|
17535
17603
|
|
|
17604
|
+
//#region ---- Imports ----
|
|
17605
|
+
//#endregion
|
|
17606
|
+
//#region ---- Widget Config ----
|
|
17607
|
+
const AXPLookupFilterOptionsWidget = {
|
|
17608
|
+
name: 'lookup-filter-options',
|
|
17609
|
+
title: 'Lookup Filter Options',
|
|
17610
|
+
description: 'Configure lookup entity filter conditions',
|
|
17611
|
+
icon: 'fa-light fa-filter',
|
|
17612
|
+
categories: AXP_WIDGETS_EDITOR_CATEGORY,
|
|
17613
|
+
groups: [AXPWidgetGroupEnum.EntityWidget],
|
|
17614
|
+
type: 'editor',
|
|
17615
|
+
properties: [AXP_NAME_PROPERTY, AXP_DATA_PATH_PROPERTY],
|
|
17616
|
+
components: {
|
|
17617
|
+
edit: {
|
|
17618
|
+
component: () => Promise.resolve().then(function () { return lookupFilterOptionsWidgetEditor_component; }).then((c) => c.AXPLookupFilterOptionsWidgetEditComponent),
|
|
17619
|
+
},
|
|
17620
|
+
},
|
|
17621
|
+
};
|
|
17622
|
+
//#endregion
|
|
17623
|
+
|
|
17624
|
+
//#endregion
|
|
17625
|
+
//#region ---- Constants ----
|
|
17626
|
+
const LOOKUP_FILTER_OPTIONS_I18N = {
|
|
17627
|
+
filters: '@general:widgets.data-source-options.filters',
|
|
17628
|
+
filtersButtonEmpty: '@general:widgets.data-source-options.filters-button-empty',
|
|
17629
|
+
filtersButtonWithCount: '@general:widgets.data-source-options.filters-button-with-count',
|
|
17630
|
+
filtersModalTitle: '@general:widgets.data-source-options.filters-modal-title',
|
|
17631
|
+
selectEntityFirst: '@general:widgets.lookup-filter-options.select-entity-first',
|
|
17632
|
+
noFilterableFields: '@general:widgets.lookup-filter-options.no-filterable-fields',
|
|
17633
|
+
};
|
|
17634
|
+
//#endregion
|
|
17635
|
+
//#region ---- Helpers ----
|
|
17636
|
+
function getFilterKey(filter, index) {
|
|
17637
|
+
return filter.id ?? filter.field ?? slugFilterTitle(filter.title ?? '') ?? `filter_${index}`;
|
|
17638
|
+
}
|
|
17639
|
+
function slugFilterTitle(title) {
|
|
17640
|
+
return (title
|
|
17641
|
+
.toLowerCase()
|
|
17642
|
+
.replace(/[^a-z0-9\s]/g, '')
|
|
17643
|
+
.replace(/\s+/g, '_')
|
|
17644
|
+
.substring(0, 50) || 'filter');
|
|
17645
|
+
}
|
|
17646
|
+
function flattenFilterLeaves(filter) {
|
|
17647
|
+
if (!filter) {
|
|
17648
|
+
return [];
|
|
17649
|
+
}
|
|
17650
|
+
if (filter.filters?.length) {
|
|
17651
|
+
return filter.filters.flatMap((child) => flattenFilterLeaves(child));
|
|
17652
|
+
}
|
|
17653
|
+
if (filter.field) {
|
|
17654
|
+
return [filter];
|
|
17655
|
+
}
|
|
17656
|
+
return [];
|
|
17657
|
+
}
|
|
17658
|
+
function filterToExpressionMap(filter, definitions) {
|
|
17659
|
+
const map = {};
|
|
17660
|
+
definitions.forEach((definition, index) => {
|
|
17661
|
+
map[getFilterKey(definition, index)] = '';
|
|
17662
|
+
});
|
|
17663
|
+
for (const leaf of flattenFilterLeaves(filter)) {
|
|
17664
|
+
const definitionIndex = definitions.findIndex((definition) => definition.field === leaf.field);
|
|
17665
|
+
if (definitionIndex < 0) {
|
|
17666
|
+
continue;
|
|
17667
|
+
}
|
|
17668
|
+
const key = getFilterKey(definitions[definitionIndex], definitionIndex);
|
|
17669
|
+
const value = leaf.value;
|
|
17670
|
+
map[key] = typeof value === 'string' ? value : value != null ? String(value) : '';
|
|
17671
|
+
}
|
|
17672
|
+
return map;
|
|
17673
|
+
}
|
|
17674
|
+
function expressionsToFilter(expressions, definitions) {
|
|
17675
|
+
const clauses = definitions
|
|
17676
|
+
.map((definition, index) => {
|
|
17677
|
+
const key = getFilterKey(definition, index);
|
|
17678
|
+
const expression = expressions[key]?.trim();
|
|
17679
|
+
if (!expression) {
|
|
17680
|
+
return null;
|
|
17681
|
+
}
|
|
17682
|
+
return {
|
|
17683
|
+
field: definition.field,
|
|
17684
|
+
operator: definition.operator ?? { type: 'equal' },
|
|
17685
|
+
value: expression,
|
|
17686
|
+
};
|
|
17687
|
+
})
|
|
17688
|
+
.filter((clause) => clause != null);
|
|
17689
|
+
if (!clauses.length) {
|
|
17690
|
+
return undefined;
|
|
17691
|
+
}
|
|
17692
|
+
return {
|
|
17693
|
+
logic: 'and',
|
|
17694
|
+
filters: clauses,
|
|
17695
|
+
};
|
|
17696
|
+
}
|
|
17697
|
+
function countConfiguredExpressions(filter) {
|
|
17698
|
+
return flattenFilterLeaves(filter).filter((leaf) => {
|
|
17699
|
+
const value = leaf.value;
|
|
17700
|
+
const text = typeof value === 'string' ? value : value != null ? String(value) : '';
|
|
17701
|
+
return text.trim().length > 0;
|
|
17702
|
+
}).length;
|
|
17703
|
+
}
|
|
17704
|
+
//#endregion
|
|
17705
|
+
//#region ---- Component ----
|
|
17706
|
+
class AXPLookupFilterOptionsWidgetEditComponent extends AXPValueWidgetComponent {
|
|
17707
|
+
constructor() {
|
|
17708
|
+
super(...arguments);
|
|
17709
|
+
//#region ---- Services & Dependencies ----
|
|
17710
|
+
this.entityRegistry = inject(AXPEntityDefinitionRegistryService);
|
|
17711
|
+
this.widgetRegistry = inject(AXPWidgetRegistryService);
|
|
17712
|
+
this.layoutBuilder = inject(AXPLayoutBuilderService);
|
|
17713
|
+
this.translationService = inject(AXTranslationService);
|
|
17714
|
+
//#endregion
|
|
17715
|
+
//#region ---- State ----
|
|
17716
|
+
this.I18N = LOOKUP_FILTER_OPTIONS_I18N;
|
|
17717
|
+
this.filterDefinitions = signal([], ...(ngDevMode ? [{ debugName: "filterDefinitions" }] : /* istanbul ignore next */ []));
|
|
17718
|
+
this.entityTitle = signal('', ...(ngDevMode ? [{ debugName: "entityTitle" }] : /* istanbul ignore next */ []));
|
|
17719
|
+
this.loadedEntityKey = signal(null, ...(ngDevMode ? [{ debugName: "loadedEntityKey" }] : /* istanbul ignore next */ []));
|
|
17720
|
+
//#endregion
|
|
17721
|
+
//#region ---- Computed ----
|
|
17722
|
+
this.entityKey = computed(() => {
|
|
17723
|
+
this.contextService.data();
|
|
17724
|
+
const key = this.contextService.getValue('options.entity');
|
|
17725
|
+
const trimmed = key?.trim();
|
|
17726
|
+
return trimmed && trimmed.length > 0 ? trimmed : null;
|
|
17727
|
+
}, ...(ngDevMode ? [{ debugName: "entityKey" }] : /* istanbul ignore next */ []));
|
|
17728
|
+
this.filterCount = computed(() => countConfiguredExpressions(this.getValue()), ...(ngDevMode ? [{ debugName: "filterCount" }] : /* istanbul ignore next */ []));
|
|
17729
|
+
//#endregion
|
|
17730
|
+
//#region ---- Effects ----
|
|
17731
|
+
this.loadFilterDefinitionsEffect = effect(() => {
|
|
17732
|
+
const key = this.entityKey();
|
|
17733
|
+
untracked(() => {
|
|
17734
|
+
void this.loadFilterDefinitions(key);
|
|
17735
|
+
});
|
|
17736
|
+
}, ...(ngDevMode ? [{ debugName: "loadFilterDefinitionsEffect" }] : /* istanbul ignore next */ []));
|
|
17737
|
+
}
|
|
17738
|
+
//#endregion
|
|
17739
|
+
//#region ---- UI Handlers ----
|
|
17740
|
+
async openFilterEditor() {
|
|
17741
|
+
const definitions = this.filterDefinitions();
|
|
17742
|
+
if (!definitions.length) {
|
|
17743
|
+
return;
|
|
17744
|
+
}
|
|
17745
|
+
const initialFilters = filterToExpressionMap(this.getValue(), definitions);
|
|
17746
|
+
const filtersModalTitle = await this.translationService.translateAsync(LOOKUP_FILTER_OPTIONS_I18N.filtersModalTitle);
|
|
17747
|
+
const ref = await this.layoutBuilder
|
|
17748
|
+
.create()
|
|
17749
|
+
.dialog((dialog) => {
|
|
17750
|
+
dialog
|
|
17751
|
+
.setTitle(filtersModalTitle)
|
|
17752
|
+
.setMessage(this.entityTitle())
|
|
17753
|
+
.setSize('md')
|
|
17754
|
+
.setContext({ filters: initialFilters })
|
|
17755
|
+
.content((layoutBuilder) => {
|
|
17756
|
+
layoutBuilder.flex((flex) => {
|
|
17757
|
+
flex
|
|
17758
|
+
.mode('edit')
|
|
17759
|
+
.setOptions({
|
|
17760
|
+
flexDirection: 'column',
|
|
17761
|
+
maxHeight: '60vh',
|
|
17762
|
+
overflowY: 'auto',
|
|
17763
|
+
});
|
|
17764
|
+
definitions.forEach((filter, index) => {
|
|
17765
|
+
const key = getFilterKey(filter, index);
|
|
17766
|
+
flex.formField(filter.title, (field) => {
|
|
17767
|
+
field.path(`filters.${key}`).customWidget('code-editor', {
|
|
17768
|
+
language: 'javascript',
|
|
17769
|
+
lineNumbers: true,
|
|
17770
|
+
height: 'min-h-48',
|
|
17771
|
+
});
|
|
17772
|
+
});
|
|
17773
|
+
});
|
|
17774
|
+
});
|
|
17775
|
+
})
|
|
17776
|
+
.setActions((actions) => {
|
|
17777
|
+
actions.cancel('@general:actions.cancel.title');
|
|
17778
|
+
actions.submit('@general:actions.save.title');
|
|
17779
|
+
});
|
|
17780
|
+
})
|
|
17781
|
+
.show();
|
|
17782
|
+
if (ref.action() === 'submit') {
|
|
17783
|
+
const context = ref.context();
|
|
17784
|
+
const expressions = context?.filters ?? {};
|
|
17785
|
+
this.setValue(expressionsToFilter(expressions, definitions));
|
|
17786
|
+
ref.close();
|
|
17787
|
+
return;
|
|
17788
|
+
}
|
|
17789
|
+
ref.close();
|
|
17790
|
+
}
|
|
17791
|
+
//#endregion
|
|
17792
|
+
//#region ---- Private Methods ----
|
|
17793
|
+
async loadFilterDefinitions(entityKey) {
|
|
17794
|
+
if (!entityKey) {
|
|
17795
|
+
this.filterDefinitions.set([]);
|
|
17796
|
+
this.entityTitle.set('');
|
|
17797
|
+
if (this.loadedEntityKey()) {
|
|
17798
|
+
this.setValue(undefined);
|
|
17799
|
+
}
|
|
17800
|
+
this.loadedEntityKey.set(null);
|
|
17801
|
+
return;
|
|
17802
|
+
}
|
|
17803
|
+
const [module, entity] = entityKey.split('.');
|
|
17804
|
+
if (!module || !entity) {
|
|
17805
|
+
this.filterDefinitions.set([]);
|
|
17806
|
+
this.entityTitle.set('');
|
|
17807
|
+
if (this.loadedEntityKey()) {
|
|
17808
|
+
this.setValue(undefined);
|
|
17809
|
+
}
|
|
17810
|
+
this.loadedEntityKey.set(null);
|
|
17811
|
+
return;
|
|
17812
|
+
}
|
|
17813
|
+
const previousKey = this.loadedEntityKey();
|
|
17814
|
+
if (previousKey && previousKey !== entityKey) {
|
|
17815
|
+
this.setValue(undefined);
|
|
17816
|
+
}
|
|
17817
|
+
const entityDef = await this.entityRegistry.resolve(module, entity).catch(() => null);
|
|
17818
|
+
if (!entityDef) {
|
|
17819
|
+
this.filterDefinitions.set([]);
|
|
17820
|
+
this.entityTitle.set('');
|
|
17821
|
+
this.loadedEntityKey.set(null);
|
|
17822
|
+
return;
|
|
17823
|
+
}
|
|
17824
|
+
this.entityTitle.set(typeof entityDef.title === 'string' ? entityDef.title : entityKey);
|
|
17825
|
+
const definitions = await collectEntityListFilterDefinitions(entityDef, (moduleName, entityName) => this.entityRegistry.resolve(moduleName, entityName).catch(() => null), {
|
|
17826
|
+
resolveWidget: (type) => this.widgetRegistry.resolve(type),
|
|
17827
|
+
});
|
|
17828
|
+
this.filterDefinitions.set(definitions);
|
|
17829
|
+
this.loadedEntityKey.set(entityKey);
|
|
17830
|
+
const current = this.getValue();
|
|
17831
|
+
if (current && !isEmpty(current) && countConfiguredExpressions(current) === 0) {
|
|
17832
|
+
this.setValue(undefined);
|
|
17833
|
+
}
|
|
17834
|
+
}
|
|
17835
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPLookupFilterOptionsWidgetEditComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
|
|
17836
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: AXPLookupFilterOptionsWidgetEditComponent, isStandalone: true, selector: "axp-lookup-filter-options-widget-editor", usesInheritance: true, ngImport: i0, template: "<div class=\"axp-lookup-filter-options ax-flex ax-flex-col ax-gap-2 ax-py-2\">\n @if (!entityKey()) {\n <span class=\"ax-text-sm ax-opacity-70\">{{ I18N.selectEntityFirst | translate | async }}</span>\n } @else if (filterDefinitions().length === 0) {\n <span class=\"ax-text-sm ax-opacity-70\">{{ I18N.noFilterableFields | translate | async }}</span>\n } @else {\n @if (filterCount() === 0) {\n <ax-button\n look=\"twotone\"\n color=\"default\"\n size=\"sm\"\n [text]=\"(I18N.filtersButtonEmpty | translate | async) ?? ''\"\n (onClick)=\"openFilterEditor()\"\n class=\"ax-w-full\"\n >\n <ax-prefix>\n <ax-icon icon=\"fa-light fa-filter\"></ax-icon>\n </ax-prefix>\n </ax-button>\n } @else {\n <ax-button\n look=\"twotone\"\n color=\"default\"\n size=\"sm\"\n [text]=\"((I18N.filtersButtonWithCount | translate: { params: { count: filterCount() } }) | async) ?? ''\"\n (onClick)=\"openFilterEditor()\"\n class=\"ax-w-full\"\n >\n <ax-prefix>\n <ax-icon icon=\"fa-light fa-filter\"></ax-icon>\n </ax-prefix>\n </ax-button>\n }\n }\n</div>\n", styles: [".axp-lookup-filter-options{min-width:0}\n"], dependencies: [{ kind: "ngmodule", type: AXButtonModule }, { kind: "component", type: i1.AXButtonComponent, selector: "ax-button", inputs: ["disabled", "size", "tabIndex", "color", "look", "text", "toggleable", "selected", "iconOnly", "type", "loadingText"], outputs: ["onBlur", "onFocus", "onClick", "selectedChange", "toggleableChange", "lookChange", "colorChange", "disabledChange", "loadingTextChange"] }, { kind: "ngmodule", type: AXDecoratorModule }, { kind: "component", type: i3$2.AXDecoratorIconComponent, selector: "ax-icon", inputs: ["icon"] }, { kind: "component", type: i3$2.AXDecoratorGenericComponent, selector: "ax-footer, ax-header, ax-content, ax-divider, ax-form-hint, ax-prefix, ax-suffix, ax-text, ax-title, ax-subtitle, ax-placeholder, ax-overlay" }, { kind: "ngmodule", type: AXTranslationModule }, { kind: "pipe", type: AsyncPipe, name: "async" }, { kind: "pipe", type: i3$1.AXTranslatorPipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
17837
|
+
}
|
|
17838
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPLookupFilterOptionsWidgetEditComponent, decorators: [{
|
|
17839
|
+
type: Component,
|
|
17840
|
+
args: [{ selector: 'axp-lookup-filter-options-widget-editor', changeDetection: ChangeDetectionStrategy.OnPush, imports: [AsyncPipe, AXButtonModule, AXDecoratorModule, AXTranslationModule], template: "<div class=\"axp-lookup-filter-options ax-flex ax-flex-col ax-gap-2 ax-py-2\">\n @if (!entityKey()) {\n <span class=\"ax-text-sm ax-opacity-70\">{{ I18N.selectEntityFirst | translate | async }}</span>\n } @else if (filterDefinitions().length === 0) {\n <span class=\"ax-text-sm ax-opacity-70\">{{ I18N.noFilterableFields | translate | async }}</span>\n } @else {\n @if (filterCount() === 0) {\n <ax-button\n look=\"twotone\"\n color=\"default\"\n size=\"sm\"\n [text]=\"(I18N.filtersButtonEmpty | translate | async) ?? ''\"\n (onClick)=\"openFilterEditor()\"\n class=\"ax-w-full\"\n >\n <ax-prefix>\n <ax-icon icon=\"fa-light fa-filter\"></ax-icon>\n </ax-prefix>\n </ax-button>\n } @else {\n <ax-button\n look=\"twotone\"\n color=\"default\"\n size=\"sm\"\n [text]=\"((I18N.filtersButtonWithCount | translate: { params: { count: filterCount() } }) | async) ?? ''\"\n (onClick)=\"openFilterEditor()\"\n class=\"ax-w-full\"\n >\n <ax-prefix>\n <ax-icon icon=\"fa-light fa-filter\"></ax-icon>\n </ax-prefix>\n </ax-button>\n }\n }\n</div>\n", styles: [".axp-lookup-filter-options{min-width:0}\n"] }]
|
|
17841
|
+
}] });
|
|
17842
|
+
|
|
17843
|
+
var lookupFilterOptionsWidgetEditor_component = /*#__PURE__*/Object.freeze({
|
|
17844
|
+
__proto__: null,
|
|
17845
|
+
AXPLookupFilterOptionsWidgetEditComponent: AXPLookupFilterOptionsWidgetEditComponent
|
|
17846
|
+
});
|
|
17847
|
+
|
|
17536
17848
|
/**
|
|
17537
17849
|
* Source type
|
|
17538
17850
|
*/
|
|
@@ -22486,6 +22798,7 @@ const AXPSelectorStructureWidget = {
|
|
|
22486
22798
|
//#region ---- Entity Widgets Provider ----
|
|
22487
22799
|
const ENTITY_WIDGETS = [
|
|
22488
22800
|
AXPLookupWidget,
|
|
22801
|
+
AXPLookupFilterOptionsWidget,
|
|
22489
22802
|
AXPSelectorStructureWidget,
|
|
22490
22803
|
AXPEntityListWidget,
|
|
22491
22804
|
AXPPageComponentWidget,
|
|
@@ -23406,8 +23719,10 @@ class AXPPageListConverter extends AXPBaseRelatedEntityConverter {
|
|
|
23406
23719
|
hidden: true,
|
|
23407
23720
|
};
|
|
23408
23721
|
}) ?? []);
|
|
23722
|
+
// Unique from host overview page id (`entity.name`) so self-referential related lists navigate correctly.
|
|
23723
|
+
const pageId = buildPageListWidgetName(relatedEntity.entity);
|
|
23409
23724
|
return {
|
|
23410
|
-
id:
|
|
23725
|
+
id: pageId,
|
|
23411
23726
|
title: context.rootTitle ?? '',
|
|
23412
23727
|
label: relatedEntity.title,
|
|
23413
23728
|
icon: relatedEntity.icon || entityDef.icon,
|
|
@@ -24638,11 +24953,14 @@ class AXPLayoutAdapterFactory {
|
|
|
24638
24953
|
composePagesWithPositions(mainPage, relatedPages, entity) {
|
|
24639
24954
|
// Only consider related entities with layout types page-detail or page-list
|
|
24640
24955
|
const pageEntities = (entity?.relatedEntities || []).filter((re) => !re.hidden && (re.layout?.type === 'page-detail' || re.layout?.type === 'page-list'));
|
|
24641
|
-
// Build a map from
|
|
24956
|
+
// Build a map from page id to its layout config (order/position).
|
|
24957
|
+
// page-list ids use `page-list-{module.entity}` so they never collide with the host overview page.
|
|
24642
24958
|
const layoutConfigByEntity = {};
|
|
24643
24959
|
for (const re of pageEntities) {
|
|
24644
24960
|
const [, entityName] = (re.entity || '').split('.');
|
|
24645
|
-
const key =
|
|
24961
|
+
const key = re.layout?.type === 'page-list'
|
|
24962
|
+
? buildPageListWidgetName(re.entity)
|
|
24963
|
+
: entityName || re.entity;
|
|
24646
24964
|
layoutConfigByEntity[key] = { order: re.layout?.order, position: re.layout?.position };
|
|
24647
24965
|
}
|
|
24648
24966
|
// Nested pages from merge-detail (e.g. PartyContact from Party) have no layout config.
|