@acorex-platform/framework-client 0.0.14 → 0.0.18
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-framework-client-auth.mjs.map +1 -1
- package/fesm2022/acorex-platform-framework-client-common.mjs +25 -211
- package/fesm2022/acorex-platform-framework-client-common.mjs.map +1 -1
- package/fesm2022/acorex-platform-framework-client-core.mjs +8 -104
- package/fesm2022/acorex-platform-framework-client-core.mjs.map +1 -1
- package/fesm2022/acorex-platform-framework-client-layout-builder.mjs +57 -12
- package/fesm2022/acorex-platform-framework-client-layout-builder.mjs.map +1 -1
- package/fesm2022/acorex-platform-framework-client-layout-components.mjs +494 -561
- package/fesm2022/acorex-platform-framework-client-layout-components.mjs.map +1 -1
- package/fesm2022/acorex-platform-framework-client-layout-designer.mjs +8 -8
- package/fesm2022/acorex-platform-framework-client-layout-designer.mjs.map +1 -1
- package/fesm2022/acorex-platform-framework-client-layout-entity.mjs +1333 -1023
- package/fesm2022/acorex-platform-framework-client-layout-entity.mjs.map +1 -1
- package/fesm2022/acorex-platform-framework-client-layout-views.mjs +51 -9
- package/fesm2022/acorex-platform-framework-client-layout-views.mjs.map +1 -1
- package/fesm2022/acorex-platform-framework-client-layout-widget-core.mjs +29 -9
- package/fesm2022/acorex-platform-framework-client-layout-widget-core.mjs.map +1 -1
- package/fesm2022/acorex-platform-framework-client-layout-widgets.mjs +48 -15
- package/fesm2022/acorex-platform-framework-client-layout-widgets.mjs.map +1 -1
- package/fesm2022/acorex-platform-framework-client-themes-default.mjs +41 -26
- package/fesm2022/acorex-platform-framework-client-themes-default.mjs.map +1 -1
- package/fesm2022/acorex-platform-framework-client-themes-shared.mjs +4 -4
- package/fesm2022/acorex-platform-framework-client-themes-shared.mjs.map +1 -1
- package/fesm2022/acorex-platform-framework-client-workflow.mjs.map +1 -1
- package/package.json +7 -7
- package/types/acorex-platform-framework-client-auth.d.ts +2 -2
- package/types/acorex-platform-framework-client-common.d.ts +12 -125
- package/types/acorex-platform-framework-client-core.d.ts +5 -22
- package/types/acorex-platform-framework-client-layout-builder.d.ts +10 -0
- package/types/acorex-platform-framework-client-layout-components.d.ts +17 -68
- package/types/acorex-platform-framework-client-layout-entity.d.ts +80 -7
- package/types/acorex-platform-framework-client-layout-views.d.ts +11 -6
- package/types/acorex-platform-framework-client-layout-widgets.d.ts +1 -1
- package/types/acorex-platform-framework-client-workflow.d.ts +1 -2
|
@@ -5,7 +5,7 @@ import { AXToastService } from '@acorex/components/toast';
|
|
|
5
5
|
import * as i3$1 from '@acorex/core/translation';
|
|
6
6
|
import { AXTranslationService, AXTranslationModule, resolveMultiLanguageString, translateSync } from '@acorex/core/translation';
|
|
7
7
|
import * as i4$4 from '@acorex-platform/framework-client/common';
|
|
8
|
-
import { AXPNotFoundError, AXP_ENTITY_DELETED_EVENT_TYPE, axpNavigateAppPath, AXPSettingsService, AXPCommonSettings, resolveEntityCreateListBehavior, AXPRefreshEvent, AXPFileTypeProviderService, AXPFileStorageService, resolveFileTypeDisplay, resolveEntityRowDisplayTitle, resolveEntityModifyPopupTitle, resolveSearchableText, evaluateEntityDisplayTemplate,
|
|
8
|
+
import { AXPNotFoundError, AXP_ENTITY_MODIFIED_EVENT_TYPE, AXP_ENTITY_DELETED_EVENT_TYPE, axpNavigateAppPath, AXPSettingsService, AXPCommonSettings, resolveEntityCreateListBehavior, AXPRefreshEvent, AXPFileTypeProviderService, AXPFileStorageService, resolveFileTypeDisplay, resolveEntityRowDisplayTitle, resolveEntityModifyPopupTitle, commandMessageTextForError, resolveSearchableText, evaluateEntityDisplayTemplate, AXPFilterOperatorMiddlewareService, AXPReloadEvent, axpRedirectToNotFound, AXPCleanNestedFilters, axpIsEntityRecordNotFound, coerceAXPSettingRawValueByKind, AXPDefaultMultiLanguageConfigService, withDefaultMultiLanguageOnWidgetNodeTree, axpIsEntityListPath, axpIsEntityDetailsPath, axpParseEntityListRoute, axpParseEntityDetailsRoute, axpSaveEntityListReturnUrl, AXP_NOT_FOUND_ROUTE, AXP_PROTECTED_ROUTE_GUARDS, AXP_SEARCH_DEFINITION_PROVIDER, AXP_SEARCH_DEFINITION_LAZY_RESOLVER, AXPMenuItemsDataSourceDefinition } from '@acorex-platform/framework-client/common';
|
|
9
9
|
export { commandMessageTextForError } from '@acorex-platform/framework-client/common';
|
|
10
10
|
import { createWorkFlowEvent, AXPWorkflowEventService, ofType } from '@acorex-platform/framework-client/workflow';
|
|
11
11
|
import * as i0 from '@angular/core';
|
|
@@ -457,7 +457,9 @@ class AXPEntityMiddleware {
|
|
|
457
457
|
for (const pluginRef of entityPlugins) {
|
|
458
458
|
const contrib = this.providedActionPlugins.find((x) => x.name === pluginRef.name);
|
|
459
459
|
if (contrib) {
|
|
460
|
-
|
|
460
|
+
// Capture this entry's options — re-finding by name breaks multi-instance
|
|
461
|
+
// plugins that share a name (e.g. several `status` plugins on one entity).
|
|
462
|
+
steps.push(this.createPluginStep(contrib, pluginRef.options, context));
|
|
461
463
|
}
|
|
462
464
|
}
|
|
463
465
|
const sorted = steps.sort((a, b) => a.order - b.order);
|
|
@@ -479,15 +481,11 @@ class AXPEntityMiddleware {
|
|
|
479
481
|
run: () => entry.modifier(context),
|
|
480
482
|
};
|
|
481
483
|
}
|
|
482
|
-
createPluginStep(contrib,
|
|
484
|
+
createPluginStep(contrib, options, context) {
|
|
483
485
|
return {
|
|
484
486
|
order: contrib.order ?? 0,
|
|
485
487
|
run: async () => {
|
|
486
|
-
|
|
487
|
-
if (!pluginRef) {
|
|
488
|
-
return;
|
|
489
|
-
}
|
|
490
|
-
await contrib.apply(context, pluginRef.options);
|
|
488
|
+
await contrib.apply(context, options);
|
|
491
489
|
},
|
|
492
490
|
};
|
|
493
491
|
}
|
|
@@ -880,7 +878,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
880
878
|
}] });
|
|
881
879
|
|
|
882
880
|
const AXPEntityCreateEvent = createWorkFlowEvent('[Entity] Created!');
|
|
883
|
-
const AXPEntityModifyEvent = createWorkFlowEvent(
|
|
881
|
+
const AXPEntityModifyEvent = createWorkFlowEvent(AXP_ENTITY_MODIFIED_EVENT_TYPE);
|
|
884
882
|
const AXPEntityDeletedEvent = createWorkFlowEvent(AXP_ENTITY_DELETED_EVENT_TYPE);
|
|
885
883
|
|
|
886
884
|
/**
|
|
@@ -971,6 +969,12 @@ async function resolveRelatedEntityColumns(columns, expressionEvaluator, scope)
|
|
|
971
969
|
}
|
|
972
970
|
//#endregion
|
|
973
971
|
|
|
972
|
+
/**
|
|
973
|
+
* Sentinel used as related-list FK filter value in create wizards before the parent
|
|
974
|
+
* row has a persisted id. Must never be treated as a real parent key.
|
|
975
|
+
*/
|
|
976
|
+
const AXP_RELATED_LIST_UNSCOPED_PARENT_FILTER_VALUE = '__axp_unscoped_parent__';
|
|
977
|
+
|
|
974
978
|
/** Registered widget name for entity create/update step wizard (dialog footer uses `widget:${name}.next`). */
|
|
975
979
|
const ENTITY_FORM_STEP_WIZARD_NAME = 'entityFormStepWizard';
|
|
976
980
|
/** Footer command: validate, persist main entity, merge context, advance wizard. */
|
|
@@ -1592,8 +1596,15 @@ class PropertyFilter {
|
|
|
1592
1596
|
};
|
|
1593
1597
|
const syncRelatedFromRoot = this.kind === 'create';
|
|
1594
1598
|
const foreignKeyField = relatedEntity.persistence?.foreignKeyField;
|
|
1599
|
+
// Create-wizard related steps start with a never-match parent filter so the grid
|
|
1600
|
+
// cannot load other rows before create-and-continue patches the root id.
|
|
1595
1601
|
const filters = syncRelatedFromRoot
|
|
1596
|
-
? []
|
|
1602
|
+
? (relatedEntity.conditions ?? []).map((c) => ({
|
|
1603
|
+
field: c.name,
|
|
1604
|
+
operator: c.operator,
|
|
1605
|
+
value: AXP_RELATED_LIST_UNSCOPED_PARENT_FILTER_VALUE,
|
|
1606
|
+
hidden: true,
|
|
1607
|
+
}))
|
|
1597
1608
|
: await Promise.all(relatedEntity.conditions?.map(async (c) => {
|
|
1598
1609
|
const value = await evaluateExpressions(c.value);
|
|
1599
1610
|
return {
|
|
@@ -2088,6 +2099,7 @@ function configureEntityFormWizardFooterActions(ab, wizardName, firstStepContinu
|
|
|
2088
2099
|
color: 'primary',
|
|
2089
2100
|
position: 'suffix',
|
|
2090
2101
|
disabled: '{{api.getStatus().isFirst}}',
|
|
2102
|
+
predicateApiWidgetName: wizardName,
|
|
2091
2103
|
});
|
|
2092
2104
|
ab.custom({
|
|
2093
2105
|
title: firstStepContinueTitleKey,
|
|
@@ -2345,7 +2357,12 @@ class AXPCreateEntityCommand {
|
|
|
2345
2357
|
};
|
|
2346
2358
|
}
|
|
2347
2359
|
const entityRef = await this.entityService.resolve(moduleName, entityName);
|
|
2348
|
-
|
|
2360
|
+
// Related-list / Assign callers put the parent FK in process.data — keep it in the create seed.
|
|
2361
|
+
const processData = options?.process && typeof options.process === 'object'
|
|
2362
|
+
? options.process.data
|
|
2363
|
+
: undefined;
|
|
2364
|
+
const createSeed = merge({}, data && typeof data === 'object' ? data : {}, processData && typeof processData === 'object' ? processData : {});
|
|
2365
|
+
let chain = this.entityForm.entity(`${moduleName}.${entityName}`).create(createSeed);
|
|
2349
2366
|
chain.actions((actions) => {
|
|
2350
2367
|
actions
|
|
2351
2368
|
.cancel('@general:actions.cancel.title')
|
|
@@ -2378,7 +2395,7 @@ class AXPCreateEntityCommand {
|
|
|
2378
2395
|
}
|
|
2379
2396
|
const createFn = entityRef.commands?.create?.execute;
|
|
2380
2397
|
const updateFn = entityRef.commands?.update?.execute;
|
|
2381
|
-
const context = merge({},
|
|
2398
|
+
const context = merge({}, createSeed, dialogRef.context());
|
|
2382
2399
|
const hasPersistedId = hasPersistedRootId(context);
|
|
2383
2400
|
const persistFn = hasPersistedId ? updateFn : createFn;
|
|
2384
2401
|
const missingPersistHandlerMsg = hasPersistedId
|
|
@@ -3647,7 +3664,7 @@ class AXPFileListComponent {
|
|
|
3647
3664
|
return action.color ?? 'primary';
|
|
3648
3665
|
}
|
|
3649
3666
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPFileListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
3650
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: AXPFileListComponent, isStandalone: true, selector: "axp-file-list", inputs: { readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, fileEditable: { classPropertyName: "fileEditable", publicName: "fileEditable", isSignal: true, isRequired: false, transformFunction: null }, enableTitleDescription: { classPropertyName: "enableTitleDescription", publicName: "enableTitleDescription", isSignal: true, isRequired: false, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, look: { classPropertyName: "look", publicName: "look", isSignal: true, isRequired: false, transformFunction: null }, titleKey: { classPropertyName: "titleKey", publicName: "titleKey", isSignal: true, isRequired: false, transformFunction: null }, showLabel: { classPropertyName: "showLabel", publicName: "showLabel", isSignal: true, isRequired: false, transformFunction: null }, linksLayout: { classPropertyName: "linksLayout", publicName: "linksLayout", isSignal: true, isRequired: false, transformFunction: null }, files: { classPropertyName: "files", publicName: "files", isSignal: true, isRequired: false, transformFunction: null }, plugins: { classPropertyName: "plugins", publicName: "plugins", isSignal: true, isRequired: false, transformFunction: null }, excludePlugins: { classPropertyName: "excludePlugins", publicName: "excludePlugins", isSignal: true, isRequired: false, transformFunction: null }, capabilities: { classPropertyName: "capabilities", publicName: "capabilities", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onRemove: "onRemove", onRevert: "onRevert", onRename: "onRename" }, host: { properties: { "class.--look-rows": "look() === \"rows\"", "class.--look-links": "look() === \"links\"", "class.--links-inline": "look() === \"links\" && linksLayout() === \"inline\"" } }, providers: [], ngImport: i0, template: "@if (look() === 'links') {\n @if (displayFiles().length > 0) {\n <div class=\"__links\">\n @if (showLabel()) {\n <span class=\"__links-label\">{{ titleKey() | translate | async }}:</span>\n }\n <ul class=\"__links-list\">\n @for (file of displayFiles(); track file.id ?? $index) {\n <li>\n <button\n type=\"button\"\n class=\"__link-item\"\n (click)=\"handleFileDownload($event, file)\"\n (dblclick)=\"handleFileRowActivate($event, file)\"\n >\n <i class=\"fa-light fa-paperclip flex-shrink-0\"></i>\n <span class=\"min-w-0 break-all\">{{ file.name }}</span>\n </button>\n </li>\n }\n </ul>\n </div>\n } @else {\n <div class=\"__empty-state __empty-state--links\">\n <axp-state-message\n icon=\"fa-light fa-folder-open\"\n [title]=\"'@general:widgets.file-uploader.empty-state.title'\"\n [description]=\"'@general:widgets.file-uploader.empty-state.description'\"\n look=\"minimal\"\n />\n </div>\n }\n} @else {\n @for (file of displayFiles(); track $index) {\n <div\n class=\"__item\"\n [ngClass]=\"{\n '--removed': file.status === 'deleted',\n '--attached': file.status === 'attached',\n '--with-meta': enableTitleDescription(),\n '--item-readonly': file.readOnly === true,\n '--downloadable': file.status !== 'deleted',\n }\"\n (dblclick)=\"handleFileRowActivate($event, file)\"\n >\n <div class=\"__icon\">\n <i class=\"fa-fw {{ getFileInfo(file.name).icon }}\"></i>\n </div>\n @if (enableTitleDescription()) {\n <div class=\"__content\">\n <div class=\"__primary\">{{ file.name }}{{ file.title?.trim() ? ' - ' + file.title : '' }}</div>\n @if (file.description?.trim()) {\n <div class=\"__secondary\">{{ file.description }}</div>\n }\n </div>\n } @else {\n <div class=\"__name\">{{ file.name }}</div>\n }\n <div class=\"__actions\" (dblclick)=\"$event.stopPropagation()\">\n @if (file.status === 'deleted' && multiple() && !isItemInteractionLocked(file)) {\n <ax-button [look]=\"'blank'\" class=\"ax-sm\" color=\"warning\" (onClick)=\"handleFileRevert($event, file)\">\n <ax-icon class=\"fa-light fa-rotate-left\"></ax-icon>\n </ax-button>\n } @else if (file.status !== 'deleted') {\n @for (action of actionsFor(file, $index); track action.id ?? action.textKey ?? action.text ?? $index) {\n <ax-button\n [look]=\"'blank'\"\n class=\"ax-sm\"\n [color]=\"getActionColor(action)\"\n (onClick)=\"runAction(action)\"\n >\n @if (action.icon) {\n <ax-icon class=\"{{ action.icon }}\"></ax-icon>\n }\n </ax-button>\n }\n }\n </div>\n </div>\n } @empty {\n <div class=\"__empty-state\">\n <axp-state-message\n icon=\"fa-light fa-folder-open\"\n [title]=\"'@general:widgets.file-uploader.empty-state.title'\"\n [description]=\"'@general:widgets.file-uploader.empty-state.description'\"\n >\n </axp-state-message>\n </div>\n }\n}\n", styles: ["@layer properties;:host{display:flex;width:100%;flex-direction:column;gap:calc(var(--spacing, .25rem) * .5)}:host.--look-rows{padding-block:calc(var(--spacing, .25rem) * 2)}:host.--look-links{padding-block:0}:host.--look-links .__links .__links-label{margin-inline-end:calc(var(--spacing, .25rem) * 2);font-size:var(--text-sm, .875rem);line-height:var(--tw-leading, var(--text-sm--line-height, calc(1.25 / .875)));--tw-font-weight: var(--font-weight-medium, 500);font-weight:var(--font-weight-medium, 500);color:rgb(var(--ax-sys-color-on-surface-variant))}:host.--look-links .__links .__links-list{margin:0;margin-top:var(--spacing, .25rem);list-style-type:none;padding:0}:host.--look-links .__links .__links-list li{margin:0;padding:0}:host.--look-links .__links .__links-list .__link-item{display:inline-flex;width:100%;cursor:pointer;align-items:center;gap:calc(var(--spacing, .25rem) * 3);border-radius:var(--radius-md, .375rem);border-style:var(--tw-border-style);border-width:0px;background-color:transparent;padding-inline:calc(var(--spacing, .25rem) * 3);padding-block:calc(var(--spacing, .25rem) * 2.5);text-align:start;font-size:var(--text-sm, .875rem);line-height:var(--tw-leading, var(--text-sm--line-height, calc(1.25 / .875)));text-decoration-line:none;transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease, var(--default-transition-timing-function, cubic-bezier(.4, 0, .2, 1)));transition-duration:var(--tw-duration, var(--default-transition-duration, .15s));color:rgb(var(--ax-sys-color-primary))}:host.--look-links .__links .__links-list .__link-item:hover{background:rgb(var(--ax-sys-color-lighter-surface))}:host.--look-links.--links-inline .__links{margin-bottom:calc(var(--spacing, .25rem) * 4);border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:rgba(var(--ax-sys-color-surface));padding-bottom:calc(var(--spacing, .25rem) * 4)}:host.--look-links:not(.--links-inline) .__links-list{margin-top:0;display:flex;flex-direction:column;gap:calc(var(--spacing, .25rem) * 2)}:host .__item{display:flex;align-items:center;gap:calc(var(--spacing, .25rem) * 3);border-left-style:var(--tw-border-style);border-left-width:4px;border-color:transparent;padding:calc(var(--spacing, .25rem) * 2)}:host .__item.--downloadable{cursor:pointer}:host .__item.--downloadable:hover{background-color:var(--color-lighter);color:var(--color-on-lighter);border-color:var(--color-border-lighter)}:host .__item:not(.--downloadable){cursor:default}:host .__item.--removed{border-color:rgba(var(--ax-sys-color-danger-500));background-color:rgba(var(--ax-sys-color-danger-50))}:host .__item.--attached{border-color:rgba(var(--ax-sys-color-success-500));animation:attached-flash 1s ease-out forwards}:host .__item.--item-readonly:not(.--removed){opacity:.85}:host .__item .__icon{width:calc(var(--spacing, .25rem) * 6);flex-shrink:0}:host .__item .__name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;text-align:start;font-size:var(--text-sm, .875rem);line-height:var(--tw-leading, var(--text-sm--line-height, calc(1.25 / .875)));--tw-font-weight: var(--font-weight-medium, 500);font-weight:var(--font-weight-medium, 500)}:host .__item .__content{display:flex;min-width:0;flex:1;flex-direction:column;gap:calc(var(--spacing, .25rem) * .5)}:host .__item .__primary{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;text-align:start;font-size:var(--text-sm, .875rem);line-height:var(--tw-leading, var(--text-sm--line-height, calc(1.25 / .875)));--tw-font-weight: var(--font-weight-medium, 500);font-weight:var(--font-weight-medium, 500)}:host .__item .__secondary{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;text-align:start;font-size:var(--text-xs, .75rem);line-height:var(--tw-leading, var(--text-xs--line-height, calc(1 / .75)));color:var(--ax-sys-color-text-secondary)}:host .__item.--with-meta .__content{justify-content:center}:host .__item .__actions{margin-left:auto;display:flex;flex-shrink:0}@keyframes attached-flash{0%{background-color:rgb(var(--ax-sys-color-success-50))}to{background-color:transparent}}.__empty-state{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease, var(--default-transition-timing-function, cubic-bezier(.4, 0, .2, 1)));transition-duration:var(--tw-duration, var(--default-transition-duration, .15s));--tw-duration: .2s;transition-duration:.2s}.__empty-state:hover{opacity:80%}.__empty-state--links{padding-block:calc(var(--spacing, .25rem) * 2)}@property --tw-font-weight{syntax: \"*\"; inherits: false;}@property --tw-border-style{syntax: \"*\"; inherits: false; initial-value: solid;}@property --tw-duration{syntax: \"*\"; inherits: false;}@layer properties{@supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-font-weight: initial;--tw-border-style: solid;--tw-duration: initial}}}\n/*! tailwindcss v4.3.2 | MIT License | https://tailwindcss.com */\n"], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type:
|
|
3667
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: AXPFileListComponent, isStandalone: true, selector: "axp-file-list", inputs: { readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, fileEditable: { classPropertyName: "fileEditable", publicName: "fileEditable", isSignal: true, isRequired: false, transformFunction: null }, enableTitleDescription: { classPropertyName: "enableTitleDescription", publicName: "enableTitleDescription", isSignal: true, isRequired: false, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, look: { classPropertyName: "look", publicName: "look", isSignal: true, isRequired: false, transformFunction: null }, titleKey: { classPropertyName: "titleKey", publicName: "titleKey", isSignal: true, isRequired: false, transformFunction: null }, showLabel: { classPropertyName: "showLabel", publicName: "showLabel", isSignal: true, isRequired: false, transformFunction: null }, linksLayout: { classPropertyName: "linksLayout", publicName: "linksLayout", isSignal: true, isRequired: false, transformFunction: null }, files: { classPropertyName: "files", publicName: "files", isSignal: true, isRequired: false, transformFunction: null }, plugins: { classPropertyName: "plugins", publicName: "plugins", isSignal: true, isRequired: false, transformFunction: null }, excludePlugins: { classPropertyName: "excludePlugins", publicName: "excludePlugins", isSignal: true, isRequired: false, transformFunction: null }, capabilities: { classPropertyName: "capabilities", publicName: "capabilities", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onRemove: "onRemove", onRevert: "onRevert", onRename: "onRename" }, host: { properties: { "class.--look-rows": "look() === \"rows\"", "class.--look-links": "look() === \"links\"", "class.--links-inline": "look() === \"links\" && linksLayout() === \"inline\"" } }, providers: [], ngImport: i0, template: "@if (look() === 'links') {\n @if (displayFiles().length > 0) {\n <div class=\"__links\">\n @if (showLabel()) {\n <span class=\"__links-label\">{{ titleKey() | translate | async }}:</span>\n }\n <ul class=\"__links-list\">\n @for (file of displayFiles(); track file.id ?? $index) {\n <li>\n <button\n type=\"button\"\n class=\"__link-item\"\n (click)=\"handleFileDownload($event, file)\"\n (dblclick)=\"handleFileRowActivate($event, file)\"\n >\n <i class=\"fa-light fa-paperclip flex-shrink-0\"></i>\n <span class=\"min-w-0 break-all\">{{ file.name }}</span>\n </button>\n </li>\n }\n </ul>\n </div>\n } @else {\n <div class=\"__empty-state __empty-state--links\">\n <axp-state-message\n icon=\"fa-light fa-folder-open\"\n [title]=\"'@general:widgets.file-uploader.empty-state.title'\"\n [description]=\"'@general:widgets.file-uploader.empty-state.description'\"\n look=\"minimal\"\n />\n </div>\n }\n} @else {\n @for (file of displayFiles(); track $index) {\n <div\n class=\"__item\"\n [ngClass]=\"{\n '--removed': file.status === 'deleted',\n '--attached': file.status === 'attached',\n '--with-meta': enableTitleDescription(),\n '--item-readonly': file.readOnly === true,\n '--downloadable': file.status !== 'deleted',\n }\"\n (dblclick)=\"handleFileRowActivate($event, file)\"\n >\n <div class=\"__icon\">\n <i class=\"fa-fw {{ getFileInfo(file.name).icon }}\"></i>\n </div>\n @if (enableTitleDescription()) {\n <div class=\"__content\">\n <div class=\"__primary\">{{ file.name }}{{ file.title?.trim() ? ' - ' + file.title : '' }}</div>\n @if (file.description?.trim()) {\n <div class=\"__secondary\">{{ file.description }}</div>\n }\n </div>\n } @else {\n <div class=\"__name\">{{ file.name }}</div>\n }\n <div class=\"__actions\" (dblclick)=\"$event.stopPropagation()\">\n @if (file.status === 'deleted' && multiple() && !isItemInteractionLocked(file)) {\n <ax-button [look]=\"'blank'\" class=\"ax-sm\" color=\"warning\" (onClick)=\"handleFileRevert($event, file)\">\n <ax-icon class=\"fa-light fa-rotate-left\"></ax-icon>\n </ax-button>\n } @else if (file.status !== 'deleted') {\n @for (action of actionsFor(file, $index); track action.id ?? action.textKey ?? action.text ?? $index) {\n <ax-button\n [look]=\"'blank'\"\n class=\"ax-sm\"\n [color]=\"getActionColor(action)\"\n (onClick)=\"runAction(action)\"\n >\n @if (action.icon) {\n <ax-icon class=\"{{ action.icon }}\"></ax-icon>\n }\n </ax-button>\n }\n }\n </div>\n </div>\n } @empty {\n <div class=\"__empty-state\">\n <axp-state-message\n icon=\"fa-light fa-folder-open\"\n [title]=\"'@general:widgets.file-uploader.empty-state.title'\"\n [description]=\"'@general:widgets.file-uploader.empty-state.description'\"\n >\n </axp-state-message>\n </div>\n }\n}\n", styles: ["@layer properties;:host{display:flex;width:100%;flex-direction:column;gap:calc(var(--spacing, .25rem) * .5)}:host.--look-rows{padding-block:calc(var(--spacing, .25rem) * 2)}:host.--look-links{padding-block:0}:host.--look-links .__links .__links-label{margin-inline-end:calc(var(--spacing, .25rem) * 2);font-size:var(--text-sm, .875rem);line-height:var(--tw-leading, var(--text-sm--line-height, calc(1.25 / .875)));--tw-font-weight: var(--font-weight-medium, 500);font-weight:var(--font-weight-medium, 500);color:rgb(var(--ax-sys-color-on-surface-variant))}:host.--look-links .__links .__links-list{margin:0;margin-top:var(--spacing, .25rem);list-style-type:none;padding:0}:host.--look-links .__links .__links-list li{margin:0;padding:0}:host.--look-links .__links .__links-list .__link-item{display:inline-flex;width:100%;cursor:pointer;align-items:center;gap:calc(var(--spacing, .25rem) * 3);border-radius:var(--radius-md, .375rem);border-style:var(--tw-border-style);border-width:0px;background-color:transparent;padding-inline:calc(var(--spacing, .25rem) * 3);padding-block:calc(var(--spacing, .25rem) * 2.5);text-align:start;font-size:var(--text-sm, .875rem);line-height:var(--tw-leading, var(--text-sm--line-height, calc(1.25 / .875)));text-decoration-line:none;transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease, var(--default-transition-timing-function, cubic-bezier(.4, 0, .2, 1)));transition-duration:var(--tw-duration, var(--default-transition-duration, .15s));color:rgb(var(--ax-sys-color-primary))}:host.--look-links .__links .__links-list .__link-item:hover{background:rgb(var(--ax-sys-color-lighter-surface))}:host.--look-links.--links-inline .__links{margin-bottom:calc(var(--spacing, .25rem) * 4);border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:rgba(var(--ax-sys-color-surface));padding-bottom:calc(var(--spacing, .25rem) * 4)}:host.--look-links:not(.--links-inline) .__links-list{margin-top:0;display:flex;flex-direction:column;gap:calc(var(--spacing, .25rem) * 2)}:host .__item{display:flex;align-items:center;gap:calc(var(--spacing, .25rem) * 3);border-left-style:var(--tw-border-style);border-left-width:4px;border-color:transparent;padding:calc(var(--spacing, .25rem) * 2)}:host .__item.--downloadable{cursor:pointer}:host .__item.--downloadable:hover{background-color:var(--color-lighter);color:var(--color-on-lighter);border-color:var(--color-border-lighter)}:host .__item:not(.--downloadable){cursor:default}:host .__item.--removed{border-color:rgba(var(--ax-sys-color-danger-500));background-color:rgba(var(--ax-sys-color-danger-50))}:host .__item.--attached{border-color:rgba(var(--ax-sys-color-success-500));animation:attached-flash 1s ease-out forwards}:host .__item.--item-readonly:not(.--removed){opacity:.85}:host .__item .__icon{width:calc(var(--spacing, .25rem) * 6);flex-shrink:0}:host .__item .__name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;text-align:start;font-size:var(--text-sm, .875rem);line-height:var(--tw-leading, var(--text-sm--line-height, calc(1.25 / .875)));--tw-font-weight: var(--font-weight-medium, 500);font-weight:var(--font-weight-medium, 500)}:host .__item .__content{display:flex;min-width:0;flex:1;flex-direction:column;gap:calc(var(--spacing, .25rem) * .5)}:host .__item .__primary{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;text-align:start;font-size:var(--text-sm, .875rem);line-height:var(--tw-leading, var(--text-sm--line-height, calc(1.25 / .875)));--tw-font-weight: var(--font-weight-medium, 500);font-weight:var(--font-weight-medium, 500)}:host .__item .__secondary{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;text-align:start;font-size:var(--text-xs, .75rem);line-height:var(--tw-leading, var(--text-xs--line-height, calc(1 / .75)));color:var(--ax-sys-color-text-secondary)}:host .__item.--with-meta .__content{justify-content:center}:host .__item .__actions{margin-left:auto;display:flex;flex-shrink:0}@keyframes attached-flash{0%{background-color:rgb(var(--ax-sys-color-success-50))}to{background-color:transparent}}.__empty-state{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease, var(--default-transition-timing-function, cubic-bezier(.4, 0, .2, 1)));transition-duration:var(--tw-duration, var(--default-transition-duration, .15s));--tw-duration: .2s;transition-duration:.2s}.__empty-state:hover{opacity:80%}.__empty-state.__empty-state--links{padding-block:calc(var(--spacing, .25rem) * 2)}@property --tw-font-weight{syntax: \"*\"; inherits: false;}@property --tw-border-style{syntax: \"*\"; inherits: false; initial-value: solid;}@property --tw-duration{syntax: \"*\"; inherits: false;}@layer properties{@supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-font-weight: initial;--tw-border-style: solid;--tw-duration: initial}}}\n/*! tailwindcss v4.3.2 | MIT License | https://tailwindcss.com */\n"], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type:
|
|
3651
3668
|
// ACoreX
|
|
3652
3669
|
AXFormModule }, { kind: "ngmodule", type: AXTextBoxModule }, { 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: AXLabelModule }, { kind: "ngmodule", type: AXCheckBoxModule }, { kind: "ngmodule", type: AXDecoratorModule }, { kind: "component", type: i3.AXDecoratorIconComponent, selector: "ax-icon", inputs: ["icon"] }, { kind: "ngmodule", type: AXTranslationModule }, { kind: "component", type:
|
|
3653
3670
|
// Platform
|
|
@@ -3675,7 +3692,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
3675
3692
|
AXTranslationModule,
|
|
3676
3693
|
// Platform
|
|
3677
3694
|
AXPStateMessageComponent,
|
|
3678
|
-
], providers: [], template: "@if (look() === 'links') {\n @if (displayFiles().length > 0) {\n <div class=\"__links\">\n @if (showLabel()) {\n <span class=\"__links-label\">{{ titleKey() | translate | async }}:</span>\n }\n <ul class=\"__links-list\">\n @for (file of displayFiles(); track file.id ?? $index) {\n <li>\n <button\n type=\"button\"\n class=\"__link-item\"\n (click)=\"handleFileDownload($event, file)\"\n (dblclick)=\"handleFileRowActivate($event, file)\"\n >\n <i class=\"fa-light fa-paperclip flex-shrink-0\"></i>\n <span class=\"min-w-0 break-all\">{{ file.name }}</span>\n </button>\n </li>\n }\n </ul>\n </div>\n } @else {\n <div class=\"__empty-state __empty-state--links\">\n <axp-state-message\n icon=\"fa-light fa-folder-open\"\n [title]=\"'@general:widgets.file-uploader.empty-state.title'\"\n [description]=\"'@general:widgets.file-uploader.empty-state.description'\"\n look=\"minimal\"\n />\n </div>\n }\n} @else {\n @for (file of displayFiles(); track $index) {\n <div\n class=\"__item\"\n [ngClass]=\"{\n '--removed': file.status === 'deleted',\n '--attached': file.status === 'attached',\n '--with-meta': enableTitleDescription(),\n '--item-readonly': file.readOnly === true,\n '--downloadable': file.status !== 'deleted',\n }\"\n (dblclick)=\"handleFileRowActivate($event, file)\"\n >\n <div class=\"__icon\">\n <i class=\"fa-fw {{ getFileInfo(file.name).icon }}\"></i>\n </div>\n @if (enableTitleDescription()) {\n <div class=\"__content\">\n <div class=\"__primary\">{{ file.name }}{{ file.title?.trim() ? ' - ' + file.title : '' }}</div>\n @if (file.description?.trim()) {\n <div class=\"__secondary\">{{ file.description }}</div>\n }\n </div>\n } @else {\n <div class=\"__name\">{{ file.name }}</div>\n }\n <div class=\"__actions\" (dblclick)=\"$event.stopPropagation()\">\n @if (file.status === 'deleted' && multiple() && !isItemInteractionLocked(file)) {\n <ax-button [look]=\"'blank'\" class=\"ax-sm\" color=\"warning\" (onClick)=\"handleFileRevert($event, file)\">\n <ax-icon class=\"fa-light fa-rotate-left\"></ax-icon>\n </ax-button>\n } @else if (file.status !== 'deleted') {\n @for (action of actionsFor(file, $index); track action.id ?? action.textKey ?? action.text ?? $index) {\n <ax-button\n [look]=\"'blank'\"\n class=\"ax-sm\"\n [color]=\"getActionColor(action)\"\n (onClick)=\"runAction(action)\"\n >\n @if (action.icon) {\n <ax-icon class=\"{{ action.icon }}\"></ax-icon>\n }\n </ax-button>\n }\n }\n </div>\n </div>\n } @empty {\n <div class=\"__empty-state\">\n <axp-state-message\n icon=\"fa-light fa-folder-open\"\n [title]=\"'@general:widgets.file-uploader.empty-state.title'\"\n [description]=\"'@general:widgets.file-uploader.empty-state.description'\"\n >\n </axp-state-message>\n </div>\n }\n}\n", styles: ["@layer properties;:host{display:flex;width:100%;flex-direction:column;gap:calc(var(--spacing, .25rem) * .5)}:host.--look-rows{padding-block:calc(var(--spacing, .25rem) * 2)}:host.--look-links{padding-block:0}:host.--look-links .__links .__links-label{margin-inline-end:calc(var(--spacing, .25rem) * 2);font-size:var(--text-sm, .875rem);line-height:var(--tw-leading, var(--text-sm--line-height, calc(1.25 / .875)));--tw-font-weight: var(--font-weight-medium, 500);font-weight:var(--font-weight-medium, 500);color:rgb(var(--ax-sys-color-on-surface-variant))}:host.--look-links .__links .__links-list{margin:0;margin-top:var(--spacing, .25rem);list-style-type:none;padding:0}:host.--look-links .__links .__links-list li{margin:0;padding:0}:host.--look-links .__links .__links-list .__link-item{display:inline-flex;width:100%;cursor:pointer;align-items:center;gap:calc(var(--spacing, .25rem) * 3);border-radius:var(--radius-md, .375rem);border-style:var(--tw-border-style);border-width:0px;background-color:transparent;padding-inline:calc(var(--spacing, .25rem) * 3);padding-block:calc(var(--spacing, .25rem) * 2.5);text-align:start;font-size:var(--text-sm, .875rem);line-height:var(--tw-leading, var(--text-sm--line-height, calc(1.25 / .875)));text-decoration-line:none;transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease, var(--default-transition-timing-function, cubic-bezier(.4, 0, .2, 1)));transition-duration:var(--tw-duration, var(--default-transition-duration, .15s));color:rgb(var(--ax-sys-color-primary))}:host.--look-links .__links .__links-list .__link-item:hover{background:rgb(var(--ax-sys-color-lighter-surface))}:host.--look-links.--links-inline .__links{margin-bottom:calc(var(--spacing, .25rem) * 4);border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:rgba(var(--ax-sys-color-surface));padding-bottom:calc(var(--spacing, .25rem) * 4)}:host.--look-links:not(.--links-inline) .__links-list{margin-top:0;display:flex;flex-direction:column;gap:calc(var(--spacing, .25rem) * 2)}:host .__item{display:flex;align-items:center;gap:calc(var(--spacing, .25rem) * 3);border-left-style:var(--tw-border-style);border-left-width:4px;border-color:transparent;padding:calc(var(--spacing, .25rem) * 2)}:host .__item.--downloadable{cursor:pointer}:host .__item.--downloadable:hover{background-color:var(--color-lighter);color:var(--color-on-lighter);border-color:var(--color-border-lighter)}:host .__item:not(.--downloadable){cursor:default}:host .__item.--removed{border-color:rgba(var(--ax-sys-color-danger-500));background-color:rgba(var(--ax-sys-color-danger-50))}:host .__item.--attached{border-color:rgba(var(--ax-sys-color-success-500));animation:attached-flash 1s ease-out forwards}:host .__item.--item-readonly:not(.--removed){opacity:.85}:host .__item .__icon{width:calc(var(--spacing, .25rem) * 6);flex-shrink:0}:host .__item .__name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;text-align:start;font-size:var(--text-sm, .875rem);line-height:var(--tw-leading, var(--text-sm--line-height, calc(1.25 / .875)));--tw-font-weight: var(--font-weight-medium, 500);font-weight:var(--font-weight-medium, 500)}:host .__item .__content{display:flex;min-width:0;flex:1;flex-direction:column;gap:calc(var(--spacing, .25rem) * .5)}:host .__item .__primary{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;text-align:start;font-size:var(--text-sm, .875rem);line-height:var(--tw-leading, var(--text-sm--line-height, calc(1.25 / .875)));--tw-font-weight: var(--font-weight-medium, 500);font-weight:var(--font-weight-medium, 500)}:host .__item .__secondary{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;text-align:start;font-size:var(--text-xs, .75rem);line-height:var(--tw-leading, var(--text-xs--line-height, calc(1 / .75)));color:var(--ax-sys-color-text-secondary)}:host .__item.--with-meta .__content{justify-content:center}:host .__item .__actions{margin-left:auto;display:flex;flex-shrink:0}@keyframes attached-flash{0%{background-color:rgb(var(--ax-sys-color-success-50))}to{background-color:transparent}}.__empty-state{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease, var(--default-transition-timing-function, cubic-bezier(.4, 0, .2, 1)));transition-duration:var(--tw-duration, var(--default-transition-duration, .15s));--tw-duration: .2s;transition-duration:.2s}.__empty-state:hover{opacity:80%}.__empty-state--links{padding-block:calc(var(--spacing, .25rem) * 2)}@property --tw-font-weight{syntax: \"*\"; inherits: false;}@property --tw-border-style{syntax: \"*\"; inherits: false; initial-value: solid;}@property --tw-duration{syntax: \"*\"; inherits: false;}@layer properties{@supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-font-weight: initial;--tw-border-style: solid;--tw-duration: initial}}}\n/*! tailwindcss v4.3.2 | MIT License | https://tailwindcss.com */\n"] }]
|
|
3695
|
+
], providers: [], template: "@if (look() === 'links') {\n @if (displayFiles().length > 0) {\n <div class=\"__links\">\n @if (showLabel()) {\n <span class=\"__links-label\">{{ titleKey() | translate | async }}:</span>\n }\n <ul class=\"__links-list\">\n @for (file of displayFiles(); track file.id ?? $index) {\n <li>\n <button\n type=\"button\"\n class=\"__link-item\"\n (click)=\"handleFileDownload($event, file)\"\n (dblclick)=\"handleFileRowActivate($event, file)\"\n >\n <i class=\"fa-light fa-paperclip flex-shrink-0\"></i>\n <span class=\"min-w-0 break-all\">{{ file.name }}</span>\n </button>\n </li>\n }\n </ul>\n </div>\n } @else {\n <div class=\"__empty-state __empty-state--links\">\n <axp-state-message\n icon=\"fa-light fa-folder-open\"\n [title]=\"'@general:widgets.file-uploader.empty-state.title'\"\n [description]=\"'@general:widgets.file-uploader.empty-state.description'\"\n look=\"minimal\"\n />\n </div>\n }\n} @else {\n @for (file of displayFiles(); track $index) {\n <div\n class=\"__item\"\n [ngClass]=\"{\n '--removed': file.status === 'deleted',\n '--attached': file.status === 'attached',\n '--with-meta': enableTitleDescription(),\n '--item-readonly': file.readOnly === true,\n '--downloadable': file.status !== 'deleted',\n }\"\n (dblclick)=\"handleFileRowActivate($event, file)\"\n >\n <div class=\"__icon\">\n <i class=\"fa-fw {{ getFileInfo(file.name).icon }}\"></i>\n </div>\n @if (enableTitleDescription()) {\n <div class=\"__content\">\n <div class=\"__primary\">{{ file.name }}{{ file.title?.trim() ? ' - ' + file.title : '' }}</div>\n @if (file.description?.trim()) {\n <div class=\"__secondary\">{{ file.description }}</div>\n }\n </div>\n } @else {\n <div class=\"__name\">{{ file.name }}</div>\n }\n <div class=\"__actions\" (dblclick)=\"$event.stopPropagation()\">\n @if (file.status === 'deleted' && multiple() && !isItemInteractionLocked(file)) {\n <ax-button [look]=\"'blank'\" class=\"ax-sm\" color=\"warning\" (onClick)=\"handleFileRevert($event, file)\">\n <ax-icon class=\"fa-light fa-rotate-left\"></ax-icon>\n </ax-button>\n } @else if (file.status !== 'deleted') {\n @for (action of actionsFor(file, $index); track action.id ?? action.textKey ?? action.text ?? $index) {\n <ax-button\n [look]=\"'blank'\"\n class=\"ax-sm\"\n [color]=\"getActionColor(action)\"\n (onClick)=\"runAction(action)\"\n >\n @if (action.icon) {\n <ax-icon class=\"{{ action.icon }}\"></ax-icon>\n }\n </ax-button>\n }\n }\n </div>\n </div>\n } @empty {\n <div class=\"__empty-state\">\n <axp-state-message\n icon=\"fa-light fa-folder-open\"\n [title]=\"'@general:widgets.file-uploader.empty-state.title'\"\n [description]=\"'@general:widgets.file-uploader.empty-state.description'\"\n >\n </axp-state-message>\n </div>\n }\n}\n", styles: ["@layer properties;:host{display:flex;width:100%;flex-direction:column;gap:calc(var(--spacing, .25rem) * .5)}:host.--look-rows{padding-block:calc(var(--spacing, .25rem) * 2)}:host.--look-links{padding-block:0}:host.--look-links .__links .__links-label{margin-inline-end:calc(var(--spacing, .25rem) * 2);font-size:var(--text-sm, .875rem);line-height:var(--tw-leading, var(--text-sm--line-height, calc(1.25 / .875)));--tw-font-weight: var(--font-weight-medium, 500);font-weight:var(--font-weight-medium, 500);color:rgb(var(--ax-sys-color-on-surface-variant))}:host.--look-links .__links .__links-list{margin:0;margin-top:var(--spacing, .25rem);list-style-type:none;padding:0}:host.--look-links .__links .__links-list li{margin:0;padding:0}:host.--look-links .__links .__links-list .__link-item{display:inline-flex;width:100%;cursor:pointer;align-items:center;gap:calc(var(--spacing, .25rem) * 3);border-radius:var(--radius-md, .375rem);border-style:var(--tw-border-style);border-width:0px;background-color:transparent;padding-inline:calc(var(--spacing, .25rem) * 3);padding-block:calc(var(--spacing, .25rem) * 2.5);text-align:start;font-size:var(--text-sm, .875rem);line-height:var(--tw-leading, var(--text-sm--line-height, calc(1.25 / .875)));text-decoration-line:none;transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease, var(--default-transition-timing-function, cubic-bezier(.4, 0, .2, 1)));transition-duration:var(--tw-duration, var(--default-transition-duration, .15s));color:rgb(var(--ax-sys-color-primary))}:host.--look-links .__links .__links-list .__link-item:hover{background:rgb(var(--ax-sys-color-lighter-surface))}:host.--look-links.--links-inline .__links{margin-bottom:calc(var(--spacing, .25rem) * 4);border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:rgba(var(--ax-sys-color-surface));padding-bottom:calc(var(--spacing, .25rem) * 4)}:host.--look-links:not(.--links-inline) .__links-list{margin-top:0;display:flex;flex-direction:column;gap:calc(var(--spacing, .25rem) * 2)}:host .__item{display:flex;align-items:center;gap:calc(var(--spacing, .25rem) * 3);border-left-style:var(--tw-border-style);border-left-width:4px;border-color:transparent;padding:calc(var(--spacing, .25rem) * 2)}:host .__item.--downloadable{cursor:pointer}:host .__item.--downloadable:hover{background-color:var(--color-lighter);color:var(--color-on-lighter);border-color:var(--color-border-lighter)}:host .__item:not(.--downloadable){cursor:default}:host .__item.--removed{border-color:rgba(var(--ax-sys-color-danger-500));background-color:rgba(var(--ax-sys-color-danger-50))}:host .__item.--attached{border-color:rgba(var(--ax-sys-color-success-500));animation:attached-flash 1s ease-out forwards}:host .__item.--item-readonly:not(.--removed){opacity:.85}:host .__item .__icon{width:calc(var(--spacing, .25rem) * 6);flex-shrink:0}:host .__item .__name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;text-align:start;font-size:var(--text-sm, .875rem);line-height:var(--tw-leading, var(--text-sm--line-height, calc(1.25 / .875)));--tw-font-weight: var(--font-weight-medium, 500);font-weight:var(--font-weight-medium, 500)}:host .__item .__content{display:flex;min-width:0;flex:1;flex-direction:column;gap:calc(var(--spacing, .25rem) * .5)}:host .__item .__primary{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;text-align:start;font-size:var(--text-sm, .875rem);line-height:var(--tw-leading, var(--text-sm--line-height, calc(1.25 / .875)));--tw-font-weight: var(--font-weight-medium, 500);font-weight:var(--font-weight-medium, 500)}:host .__item .__secondary{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;text-align:start;font-size:var(--text-xs, .75rem);line-height:var(--tw-leading, var(--text-xs--line-height, calc(1 / .75)));color:var(--ax-sys-color-text-secondary)}:host .__item.--with-meta .__content{justify-content:center}:host .__item .__actions{margin-left:auto;display:flex;flex-shrink:0}@keyframes attached-flash{0%{background-color:rgb(var(--ax-sys-color-success-50))}to{background-color:transparent}}.__empty-state{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease, var(--default-transition-timing-function, cubic-bezier(.4, 0, .2, 1)));transition-duration:var(--tw-duration, var(--default-transition-duration, .15s));--tw-duration: .2s;transition-duration:.2s}.__empty-state:hover{opacity:80%}.__empty-state.__empty-state--links{padding-block:calc(var(--spacing, .25rem) * 2)}@property --tw-font-weight{syntax: \"*\"; inherits: false;}@property --tw-border-style{syntax: \"*\"; inherits: false; initial-value: solid;}@property --tw-duration{syntax: \"*\"; inherits: false;}@layer properties{@supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-font-weight: initial;--tw-border-style: solid;--tw-duration: initial}}}\n/*! tailwindcss v4.3.2 | MIT License | https://tailwindcss.com */\n"] }]
|
|
3679
3696
|
}], propDecorators: { onRemove: [{ type: i0.Output, args: ["onRemove"] }], onRevert: [{ type: i0.Output, args: ["onRevert"] }], onRename: [{ type: i0.Output, args: ["onRename"] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], fileEditable: [{ type: i0.Input, args: [{ isSignal: true, alias: "fileEditable", required: false }] }], enableTitleDescription: [{ type: i0.Input, args: [{ isSignal: true, alias: "enableTitleDescription", required: false }] }], multiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiple", required: false }] }], look: [{ type: i0.Input, args: [{ isSignal: true, alias: "look", required: false }] }], titleKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "titleKey", required: false }] }], showLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "showLabel", required: false }] }], linksLayout: [{ type: i0.Input, args: [{ isSignal: true, alias: "linksLayout", required: false }] }], files: [{ type: i0.Input, args: [{ isSignal: true, alias: "files", required: false }] }], plugins: [{ type: i0.Input, args: [{ isSignal: true, alias: "plugins", required: false }] }], excludePlugins: [{ type: i0.Input, args: [{ isSignal: true, alias: "excludePlugins", required: false }] }], capabilities: [{ type: i0.Input, args: [{ isSignal: true, alias: "capabilities", required: false }] }] } });
|
|
3680
3697
|
|
|
3681
3698
|
//#region ---- Attachment fingerprint (no blob comparison) ----
|
|
@@ -3971,6 +3988,36 @@ function resolveFileUploaderEntityScope(options, rowData, fieldFallback) {
|
|
|
3971
3988
|
}
|
|
3972
3989
|
//#endregion
|
|
3973
3990
|
|
|
3991
|
+
//#region ---- Hierarchical entity list query ----
|
|
3992
|
+
/**
|
|
3993
|
+
* Restricts master-list queries to root rows when the entity uses {@link AXPEntity.parentKey}.
|
|
3994
|
+
* Child rows are loaded lazily on expand (filter parentKey = parent id).
|
|
3995
|
+
*/
|
|
3996
|
+
function applyHierarchicalRootListFilter(request, parentKey) {
|
|
3997
|
+
const key = parentKey.trim();
|
|
3998
|
+
if (!key) {
|
|
3999
|
+
return request;
|
|
4000
|
+
}
|
|
4001
|
+
const filter = request.filter;
|
|
4002
|
+
if (filter?.filters?.length && filter.field !== key) {
|
|
4003
|
+
return request;
|
|
4004
|
+
}
|
|
4005
|
+
if (filter?.field === key) {
|
|
4006
|
+
return request;
|
|
4007
|
+
}
|
|
4008
|
+
return {
|
|
4009
|
+
...request,
|
|
4010
|
+
filter: {
|
|
4011
|
+
value: true,
|
|
4012
|
+
field: key,
|
|
4013
|
+
operator: {
|
|
4014
|
+
type: 'isEmpty',
|
|
4015
|
+
},
|
|
4016
|
+
},
|
|
4017
|
+
};
|
|
4018
|
+
}
|
|
4019
|
+
//#endregion
|
|
4020
|
+
|
|
3974
4021
|
class AXPEntityStorageService {
|
|
3975
4022
|
}
|
|
3976
4023
|
class AXPEntityDataProvider {
|
|
@@ -4048,15 +4095,7 @@ class AXMEntityCrudServiceImpl {
|
|
|
4048
4095
|
try {
|
|
4049
4096
|
const entityRef = await this.entityRegistery.resolve(moduleName, entityName);
|
|
4050
4097
|
if (entityRef.parentKey) {
|
|
4051
|
-
|
|
4052
|
-
request.filter = {
|
|
4053
|
-
value: true,
|
|
4054
|
-
field: entityRef.parentKey,
|
|
4055
|
-
operator: {
|
|
4056
|
-
type: 'isEmpty',
|
|
4057
|
-
},
|
|
4058
|
-
};
|
|
4059
|
-
}
|
|
4098
|
+
request = applyHierarchicalRootListFilter(request, entityRef.parentKey);
|
|
4060
4099
|
}
|
|
4061
4100
|
}
|
|
4062
4101
|
catch (error) {
|
|
@@ -5631,1118 +5670,1276 @@ var updateEntity_command = /*#__PURE__*/Object.freeze({
|
|
|
5631
5670
|
AXPUpdateEntityCommand: AXPUpdateEntityCommand
|
|
5632
5671
|
});
|
|
5633
5672
|
|
|
5634
|
-
|
|
5635
|
-
|
|
5636
|
-
|
|
5637
|
-
|
|
5638
|
-
|
|
5639
|
-
|
|
5640
|
-
this.
|
|
5641
|
-
this.
|
|
5673
|
+
/**
|
|
5674
|
+
* Error type that can be thrown by middlewares to abort the chain with a structured payload.
|
|
5675
|
+
*/
|
|
5676
|
+
class AXPMiddlewareAbortError extends Error {
|
|
5677
|
+
constructor(message, options = {}) {
|
|
5678
|
+
super(message);
|
|
5679
|
+
this.message = message;
|
|
5680
|
+
this.options = options;
|
|
5681
|
+
this.name = 'AXPMiddlewareAbortError';
|
|
5642
5682
|
}
|
|
5643
|
-
|
|
5644
|
-
|
|
5645
|
-
|
|
5646
|
-
|
|
5647
|
-
|
|
5648
|
-
|
|
5649
|
-
|
|
5650
|
-
|
|
5651
|
-
|
|
5652
|
-
|
|
5653
|
-
|
|
5654
|
-
|
|
5655
|
-
|
|
5656
|
-
|
|
5657
|
-
|
|
5683
|
+
toResponse() {
|
|
5684
|
+
return {
|
|
5685
|
+
success: false,
|
|
5686
|
+
error: {
|
|
5687
|
+
code: this.options.code,
|
|
5688
|
+
message: this.message,
|
|
5689
|
+
status: this.options.status,
|
|
5690
|
+
details: this.options.details,
|
|
5691
|
+
},
|
|
5692
|
+
};
|
|
5693
|
+
}
|
|
5694
|
+
}
|
|
5695
|
+
/** Type guard for AXPMiddlewareAbortError */
|
|
5696
|
+
function isAXPMiddlewareAbortError(error) {
|
|
5697
|
+
return error instanceof AXPMiddlewareAbortError;
|
|
5698
|
+
}
|
|
5699
|
+
//#endregion
|
|
5700
|
+
|
|
5701
|
+
/**
|
|
5702
|
+
* Maps storage middleware failures to a command result the details view can surface.
|
|
5703
|
+
*/
|
|
5704
|
+
function mapEntityStorageErrorToCommandResult(error, fallback = 'Failed to update entity') {
|
|
5705
|
+
if (isAXPMiddlewareAbortError(error)) {
|
|
5706
|
+
return {
|
|
5707
|
+
success: false,
|
|
5708
|
+
message: {
|
|
5709
|
+
type: 'error',
|
|
5710
|
+
code: error.options.code,
|
|
5711
|
+
text: error.message,
|
|
5712
|
+
details: error.options.details,
|
|
5713
|
+
},
|
|
5714
|
+
};
|
|
5715
|
+
}
|
|
5716
|
+
const text = error instanceof Error ? error.message : typeof error === 'string' ? error : fallback;
|
|
5717
|
+
return {
|
|
5718
|
+
success: false,
|
|
5719
|
+
message: {
|
|
5720
|
+
type: 'error',
|
|
5721
|
+
text,
|
|
5722
|
+
},
|
|
5723
|
+
};
|
|
5724
|
+
}
|
|
5725
|
+
//#region ---- EntityDataAccessor ----
|
|
5726
|
+
/**
|
|
5727
|
+
* Data accessor for entity operations.
|
|
5728
|
+
* Provides methods to query and execute commands with smart resolution:
|
|
5729
|
+
* 1. Checks entity definition queries/commands first
|
|
5730
|
+
* 2. Falls back to CRUD service if not defined
|
|
5731
|
+
*/
|
|
5732
|
+
class EntityDataAccessor {
|
|
5733
|
+
constructor(module, entity, entityRegistry, queryExecutor, commandService, injector) {
|
|
5734
|
+
this.module = module;
|
|
5735
|
+
this.entity = entity;
|
|
5736
|
+
this.entityRegistry = entityRegistry;
|
|
5737
|
+
this.queryExecutor = queryExecutor;
|
|
5738
|
+
this.commandService = commandService;
|
|
5739
|
+
this.injector = injector;
|
|
5740
|
+
this.crudService = null;
|
|
5741
|
+
this.entityDef = null;
|
|
5742
|
+
this.entityDefPromise = null;
|
|
5743
|
+
}
|
|
5744
|
+
/**
|
|
5745
|
+
* Lazily creates the CRUD fallback inside an injection context so {@link AXMEntityCrudServiceImpl}
|
|
5746
|
+
* can resolve {@link AXPEntityStorageService} via `inject()`.
|
|
5747
|
+
*/
|
|
5748
|
+
ensureCrudService() {
|
|
5749
|
+
if (!this.crudService) {
|
|
5750
|
+
this.crudService = runInInjectionContext(this.injector, () => new AXMEntityCrudServiceImpl(`${this.module}.${this.entity}`));
|
|
5658
5751
|
}
|
|
5659
|
-
|
|
5660
|
-
|
|
5661
|
-
|
|
5662
|
-
|
|
5663
|
-
|
|
5664
|
-
|
|
5665
|
-
|
|
5666
|
-
|
|
5667
|
-
|
|
5668
|
-
};
|
|
5669
|
-
}
|
|
5670
|
-
let chain = this.entityForm.entity(`${moduleName}.${entityName}`).single(data);
|
|
5671
|
-
if (excludeProperties && excludeProperties.length > 0) {
|
|
5672
|
-
chain = chain.exclude(...excludeProperties);
|
|
5673
|
-
}
|
|
5674
|
-
const dialogTitle = await this.resolveDialogTitle(entityDef, data);
|
|
5675
|
-
if (dialogTitle) {
|
|
5676
|
-
chain = chain.title(dialogTitle);
|
|
5677
|
-
}
|
|
5678
|
-
chain = chain.actions((actions) => {
|
|
5679
|
-
actions.submit('@general:actions.ok.title');
|
|
5680
|
-
});
|
|
5681
|
-
dialogRef = await chain.show();
|
|
5682
|
-
dialogRef.close();
|
|
5683
|
-
return {
|
|
5684
|
-
success: true,
|
|
5685
|
-
metadata: {
|
|
5686
|
-
refresh: false,
|
|
5687
|
-
},
|
|
5688
|
-
};
|
|
5752
|
+
return this.crudService;
|
|
5753
|
+
}
|
|
5754
|
+
/**
|
|
5755
|
+
* Resolves the entity definition lazily and caches it.
|
|
5756
|
+
* @returns The resolved entity definition
|
|
5757
|
+
*/
|
|
5758
|
+
async resolveEntityDef() {
|
|
5759
|
+
if (this.entityDef) {
|
|
5760
|
+
return this.entityDef;
|
|
5689
5761
|
}
|
|
5690
|
-
|
|
5691
|
-
|
|
5692
|
-
success: false,
|
|
5693
|
-
message: {
|
|
5694
|
-
text: error instanceof Error
|
|
5695
|
-
? error.message
|
|
5696
|
-
: await this.translationService.translateAsync('@general:messages.entity.view-failed'),
|
|
5697
|
-
},
|
|
5698
|
-
};
|
|
5762
|
+
if (!this.entityDefPromise) {
|
|
5763
|
+
this.entityDefPromise = this.entityRegistry.resolve(this.module, this.entity);
|
|
5699
5764
|
}
|
|
5700
|
-
|
|
5701
|
-
|
|
5702
|
-
|
|
5703
|
-
}
|
|
5765
|
+
this.entityDef = await this.entityDefPromise;
|
|
5766
|
+
if (!this.entityDef) {
|
|
5767
|
+
throw new Error(`Entity "${this.module}.${this.entity}" could not be resolved`);
|
|
5704
5768
|
}
|
|
5769
|
+
return this.entityDef;
|
|
5705
5770
|
}
|
|
5706
|
-
|
|
5707
|
-
|
|
5708
|
-
|
|
5709
|
-
|
|
5710
|
-
|
|
5711
|
-
|
|
5712
|
-
|
|
5713
|
-
|
|
5714
|
-
|
|
5715
|
-
|
|
5716
|
-
const translated = await this.translationService.translateAsync(titleTemplate);
|
|
5717
|
-
return resolveMultiLanguageString(translated, this.translationService.getActiveLang()).trim() || translated;
|
|
5771
|
+
/**
|
|
5772
|
+
* Gets a single entity by key.
|
|
5773
|
+
* @param key - The entity key
|
|
5774
|
+
* @returns The entity detail or null if not found
|
|
5775
|
+
*/
|
|
5776
|
+
async byKey(key) {
|
|
5777
|
+
const entityDef = await this.resolveEntityDef();
|
|
5778
|
+
const execute = entityDef.queries?.byKey?.execute;
|
|
5779
|
+
if (typeof execute === 'function') {
|
|
5780
|
+
return await execute(key);
|
|
5718
5781
|
}
|
|
5719
|
-
|
|
5720
|
-
|
|
5721
|
-
context: this.contextEvalFactory.createScope(recordContext),
|
|
5722
|
-
};
|
|
5723
|
-
const evaluated = await this.expressionEvaluator.evaluate(titleTemplate, scope);
|
|
5724
|
-
const locale = this.translationService.getActiveLang();
|
|
5725
|
-
const text = resolveMultiLanguageString(evaluated, locale).trim() || String(evaluated ?? '').trim();
|
|
5726
|
-
if (text) {
|
|
5727
|
-
return text;
|
|
5782
|
+
else if (typeof execute === 'string') {
|
|
5783
|
+
return await this.queryExecutor.fetch(execute, key);
|
|
5728
5784
|
}
|
|
5729
|
-
|
|
5785
|
+
// Fallback to CRUD service
|
|
5786
|
+
return await this.ensureCrudService().getOne(key);
|
|
5730
5787
|
}
|
|
5731
|
-
|
|
5732
|
-
|
|
5733
|
-
|
|
5734
|
-
|
|
5735
|
-
|
|
5736
|
-
|
|
5737
|
-
|
|
5738
|
-
|
|
5739
|
-
|
|
5740
|
-
|
|
5741
|
-
|
|
5742
|
-
return typeof data === 'object' && data !== null ? data : {};
|
|
5788
|
+
/**
|
|
5789
|
+
* Queries a list of entities.
|
|
5790
|
+
* @param request - The query request (optional)
|
|
5791
|
+
* @returns The paged list result
|
|
5792
|
+
*/
|
|
5793
|
+
async query(request) {
|
|
5794
|
+
const entityDef = await this.resolveEntityDef();
|
|
5795
|
+
const defaultRequest = { skip: 0, take: 10 };
|
|
5796
|
+
let queryRequest = request || defaultRequest;
|
|
5797
|
+
if (entityDef.parentKey) {
|
|
5798
|
+
queryRequest = applyHierarchicalRootListFilter(queryRequest, entityDef.parentKey);
|
|
5743
5799
|
}
|
|
5744
|
-
|
|
5745
|
-
|
|
5746
|
-
|
|
5747
|
-
const byKeyFn = entity?.queries?.byKey?.execute;
|
|
5748
|
-
if (typeof byKeyFn === 'function') {
|
|
5749
|
-
return (await byKeyFn(recordId)) ?? {};
|
|
5750
|
-
}
|
|
5800
|
+
const execute = entityDef.queries?.list?.execute;
|
|
5801
|
+
if (typeof execute === 'function') {
|
|
5802
|
+
return await execute(queryRequest);
|
|
5751
5803
|
}
|
|
5752
|
-
|
|
5753
|
-
|
|
5804
|
+
else if (typeof execute === 'string') {
|
|
5805
|
+
const result = await this.queryExecutor.fetch(execute, queryRequest);
|
|
5806
|
+
if (!result) {
|
|
5807
|
+
throw new Error(`Query "${execute}" returned null`);
|
|
5808
|
+
}
|
|
5809
|
+
return result;
|
|
5754
5810
|
}
|
|
5755
|
-
|
|
5756
|
-
|
|
5757
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPViewEntityDetailsCommand, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
5758
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPViewEntityDetailsCommand, providedIn: 'root' }); }
|
|
5759
|
-
}
|
|
5760
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPViewEntityDetailsCommand, decorators: [{
|
|
5761
|
-
type: Injectable,
|
|
5762
|
-
args: [{
|
|
5763
|
-
providedIn: 'root',
|
|
5764
|
-
}]
|
|
5765
|
-
}] });
|
|
5766
|
-
|
|
5767
|
-
var viewEntityDetails_command = /*#__PURE__*/Object.freeze({
|
|
5768
|
-
__proto__: null,
|
|
5769
|
-
AXPViewEntityDetailsCommand: AXPViewEntityDetailsCommand
|
|
5770
|
-
});
|
|
5771
|
-
|
|
5772
|
-
/** Common keys on denormalized expose stubs (e.g. `{ id, displayName }` from lookup expose). */
|
|
5773
|
-
const LOOKUP_EXPOSE_STUB_DISPLAY_FIELDS = ['displayName', 'title', 'name', 'fullName', 'code'];
|
|
5774
|
-
function resolveLookupItemFieldValue(item, field) {
|
|
5775
|
-
const direct = get(item, field);
|
|
5776
|
-
if (direct != null && direct !== '') {
|
|
5777
|
-
return direct;
|
|
5811
|
+
// Fallback to CRUD service
|
|
5812
|
+
return await this.ensureCrudService().query(queryRequest);
|
|
5778
5813
|
}
|
|
5779
|
-
|
|
5780
|
-
|
|
5814
|
+
/**
|
|
5815
|
+
* Queries category entities linked to this entity (`{module}.{entity}Category`).
|
|
5816
|
+
* Falls back to CRUD category storage (same as {@link AXMEntityCrudServiceImpl.getCategoriesList}).
|
|
5817
|
+
*/
|
|
5818
|
+
async getCategoriesList(request = { skip: 0, take: 1000 }) {
|
|
5819
|
+
return await this.ensureCrudService().getCategoriesList(request);
|
|
5781
5820
|
}
|
|
5782
|
-
|
|
5783
|
-
|
|
5784
|
-
|
|
5785
|
-
|
|
5821
|
+
/**
|
|
5822
|
+
* Creates a new entity.
|
|
5823
|
+
* @param data - The entity data to create
|
|
5824
|
+
* @returns The created entity key
|
|
5825
|
+
*/
|
|
5826
|
+
async create(data) {
|
|
5827
|
+
const entityDef = await this.resolveEntityDef();
|
|
5828
|
+
const execute = entityDef.commands?.create?.execute;
|
|
5829
|
+
if (typeof execute === 'function') {
|
|
5830
|
+
const result = await execute(data);
|
|
5831
|
+
return result?.id || result;
|
|
5786
5832
|
}
|
|
5833
|
+
else if (typeof execute === 'string') {
|
|
5834
|
+
const result = await this.commandService.execute(execute, data);
|
|
5835
|
+
if (!result?.success) {
|
|
5836
|
+
throw new Error(commandMessageTextForError(result?.message?.text) || 'Failed to create entity');
|
|
5837
|
+
}
|
|
5838
|
+
return result.data?.id || result.data;
|
|
5839
|
+
}
|
|
5840
|
+
// Fallback to CRUD service
|
|
5841
|
+
return await this.ensureCrudService().insertOne(data);
|
|
5787
5842
|
}
|
|
5788
|
-
|
|
5789
|
-
|
|
5790
|
-
|
|
5791
|
-
|
|
5792
|
-
|
|
5793
|
-
|
|
5794
|
-
|
|
5795
|
-
|
|
5843
|
+
/**
|
|
5844
|
+
* Updates an existing entity.
|
|
5845
|
+
* @param id - The entity key
|
|
5846
|
+
* @param data - The entity data to update
|
|
5847
|
+
* @returns The updated entity
|
|
5848
|
+
*/
|
|
5849
|
+
async update(id, data) {
|
|
5850
|
+
const entityDef = await this.resolveEntityDef();
|
|
5851
|
+
const execute = entityDef.commands?.update?.execute;
|
|
5852
|
+
const updateData = { id, ...data };
|
|
5853
|
+
if (typeof execute === 'function') {
|
|
5854
|
+
return await execute(updateData);
|
|
5855
|
+
}
|
|
5856
|
+
else if (typeof execute === 'string') {
|
|
5857
|
+
const result = await this.commandService.execute(execute, updateData);
|
|
5858
|
+
if (!result?.success) {
|
|
5859
|
+
throw new Error(commandMessageTextForError(result?.message?.text) || 'Failed to update entity');
|
|
5860
|
+
}
|
|
5861
|
+
return result.data;
|
|
5862
|
+
}
|
|
5863
|
+
// Fallback to CRUD service
|
|
5864
|
+
return await this.ensureCrudService().updateOne(id, data);
|
|
5796
5865
|
}
|
|
5797
|
-
|
|
5798
|
-
|
|
5799
|
-
|
|
5866
|
+
/**
|
|
5867
|
+
* Deletes an entity.
|
|
5868
|
+
* @param id - The entity key
|
|
5869
|
+
*/
|
|
5870
|
+
async delete(id) {
|
|
5871
|
+
const entityDef = await this.resolveEntityDef();
|
|
5872
|
+
const execute = entityDef.commands?.delete?.execute;
|
|
5873
|
+
if (typeof execute === 'function') {
|
|
5874
|
+
await execute(id);
|
|
5875
|
+
return;
|
|
5800
5876
|
}
|
|
5801
|
-
|
|
5877
|
+
else if (typeof execute === 'string') {
|
|
5878
|
+
const result = await this.commandService.execute(execute, id);
|
|
5879
|
+
if (!result?.success) {
|
|
5880
|
+
throw new Error(commandMessageTextForError(result?.message?.text) || 'Failed to delete entity');
|
|
5881
|
+
}
|
|
5882
|
+
return;
|
|
5883
|
+
}
|
|
5884
|
+
// Fallback to CRUD service
|
|
5885
|
+
return await this.ensureCrudService().deleteOne(id);
|
|
5802
5886
|
}
|
|
5803
|
-
return String(raw);
|
|
5804
5887
|
}
|
|
5805
|
-
//#
|
|
5888
|
+
//#endregion
|
|
5889
|
+
//#region ---- EntityBuilder ----
|
|
5806
5890
|
/**
|
|
5807
|
-
*
|
|
5808
|
-
*
|
|
5809
|
-
* Returns undefined when `textField` is set or no template applies (use {@link resolveLookupDisplayField} instead).
|
|
5891
|
+
* Builder class for entity operations.
|
|
5892
|
+
* Provides methods to create paths and access entity data.
|
|
5810
5893
|
*/
|
|
5811
|
-
|
|
5812
|
-
|
|
5813
|
-
|
|
5814
|
-
|
|
5815
|
-
|
|
5816
|
-
|
|
5817
|
-
|
|
5818
|
-
|
|
5819
|
-
|
|
5820
|
-
if (lookupFormat && (lookupFormat.includes('{{') || lookupFormat.includes('{'))) {
|
|
5821
|
-
return normalizeEntityDisplayTemplate(lookupFormat);
|
|
5822
|
-
}
|
|
5823
|
-
const searchTitle = entity?.formats?.searchResult?.title?.trim();
|
|
5824
|
-
if (searchTitle) {
|
|
5825
|
-
return normalizeEntityDisplayTemplate(searchTitle);
|
|
5894
|
+
class EntityBuilder {
|
|
5895
|
+
constructor(module, entity, sessionService, entityRegistry, queryExecutor, commandService, injector) {
|
|
5896
|
+
this.sessionService = sessionService;
|
|
5897
|
+
this.entityRegistry = entityRegistry;
|
|
5898
|
+
this.queryExecutor = queryExecutor;
|
|
5899
|
+
this.commandService = commandService;
|
|
5900
|
+
this.injector = injector;
|
|
5901
|
+
this.module = module;
|
|
5902
|
+
this.entity = entity;
|
|
5826
5903
|
}
|
|
5827
|
-
|
|
5828
|
-
|
|
5829
|
-
|
|
5830
|
-
|
|
5831
|
-
|
|
5832
|
-
|
|
5833
|
-
|
|
5834
|
-
if (options.textField?.trim()) {
|
|
5835
|
-
return options.textField.trim();
|
|
5904
|
+
/**
|
|
5905
|
+
* Creates a path for the entity.
|
|
5906
|
+
* @param path - The path segment (defaults to 'list')
|
|
5907
|
+
* @returns The full path string
|
|
5908
|
+
*/
|
|
5909
|
+
createPath(path = 'list') {
|
|
5910
|
+
return `/${this.sessionService.application?.name}/m/${this.module}/e/${this.entity}/${path}`;
|
|
5836
5911
|
}
|
|
5837
|
-
|
|
5838
|
-
|
|
5839
|
-
|
|
5912
|
+
/**
|
|
5913
|
+
* Gets the data accessor for the entity with generic type support.
|
|
5914
|
+
* Entity definition is resolved lazily on first method call.
|
|
5915
|
+
* @returns An EntityDataAccessor instance
|
|
5916
|
+
*/
|
|
5917
|
+
data() {
|
|
5918
|
+
return new EntityDataAccessor(this.module, this.entity, this.entityRegistry, this.queryExecutor, this.commandService, this.injector);
|
|
5840
5919
|
}
|
|
5841
|
-
const defaults = ['title', 'name', 'code', 'description'];
|
|
5842
|
-
const found = defaults.find((name) => entity?.properties?.some((p) => p.name === name));
|
|
5843
|
-
return found ?? 'title';
|
|
5844
5920
|
}
|
|
5845
|
-
|
|
5846
|
-
|
|
5847
|
-
|
|
5848
|
-
|
|
5849
|
-
|
|
5850
|
-
|
|
5851
|
-
|
|
5852
|
-
|
|
5853
|
-
return String(item);
|
|
5854
|
-
}
|
|
5855
|
-
if (typeof item !== 'object') {
|
|
5856
|
-
return '';
|
|
5921
|
+
//#endregion
|
|
5922
|
+
class AXPEntityService {
|
|
5923
|
+
constructor() {
|
|
5924
|
+
this.sessionService = inject(AXPSessionService);
|
|
5925
|
+
this.entityRegistry = inject(AXPEntityDefinitionRegistryService);
|
|
5926
|
+
this.queryExecutor = inject(AXPQueryExecutor);
|
|
5927
|
+
this.commandService = inject(AXPCommandService);
|
|
5928
|
+
this.injector = inject(Injector);
|
|
5857
5929
|
}
|
|
5858
|
-
|
|
5859
|
-
|
|
5930
|
+
/**
|
|
5931
|
+
* @deprecated Use `withEntity(module, entity).createPath(path)` instead.
|
|
5932
|
+
* This method will be removed in a future version.
|
|
5933
|
+
*/
|
|
5934
|
+
createPath(module, entity, path = 'list') {
|
|
5935
|
+
return `/${this.sessionService.application?.name}/m/${module}/e/${entity}/${path}`;
|
|
5860
5936
|
}
|
|
5861
|
-
|
|
5862
|
-
|
|
5863
|
-
|
|
5864
|
-
|
|
5865
|
-
|
|
5866
|
-
|
|
5867
|
-
|
|
5868
|
-
}
|
|
5937
|
+
withEntity(moduleOrEntityKey, entity) {
|
|
5938
|
+
let module;
|
|
5939
|
+
let entityName;
|
|
5940
|
+
if (entity !== undefined) {
|
|
5941
|
+
// Two parameters: module and entity
|
|
5942
|
+
module = moduleOrEntityKey;
|
|
5943
|
+
entityName = entity;
|
|
5869
5944
|
}
|
|
5870
5945
|
else {
|
|
5871
|
-
|
|
5872
|
-
|
|
5873
|
-
|
|
5874
|
-
|
|
5875
|
-
return text;
|
|
5876
|
-
}
|
|
5946
|
+
// Single parameter: "module.entity" format
|
|
5947
|
+
const dotIndex = moduleOrEntityKey.indexOf('.');
|
|
5948
|
+
if (dotIndex === -1) {
|
|
5949
|
+
throw new Error(`Invalid entity key format: "${moduleOrEntityKey}". Expected format: "module.entity"`);
|
|
5877
5950
|
}
|
|
5951
|
+
module = moduleOrEntityKey.substring(0, dotIndex);
|
|
5952
|
+
entityName = moduleOrEntityKey.substring(dotIndex + 1);
|
|
5878
5953
|
}
|
|
5954
|
+
return new EntityBuilder(module, entityName, this.sessionService, this.entityRegistry, this.queryExecutor, this.commandService, this.injector);
|
|
5879
5955
|
}
|
|
5880
|
-
|
|
5881
|
-
|
|
5882
|
-
return formatLookupFieldValue(raw, displayDeps.locale, displayDeps.translationService, resolveMultiLanguage);
|
|
5956
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPEntityService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
5957
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPEntityService, providedIn: 'root' }); }
|
|
5883
5958
|
}
|
|
5959
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPEntityService, decorators: [{
|
|
5960
|
+
type: Injectable,
|
|
5961
|
+
args: [{ providedIn: 'root' }]
|
|
5962
|
+
}] });
|
|
5963
|
+
|
|
5964
|
+
//#endregion
|
|
5965
|
+
//#region ---- Update entity status command ----
|
|
5884
5966
|
/**
|
|
5885
|
-
*
|
|
5967
|
+
* Default handler for status-chip transitions when no custom transition.command is set.
|
|
5968
|
+
* Patches only the status field via the entity update path (no edit dialog).
|
|
5886
5969
|
*/
|
|
5887
|
-
|
|
5888
|
-
|
|
5889
|
-
|
|
5970
|
+
class AXPUpdateEntityStatusCommand {
|
|
5971
|
+
constructor() {
|
|
5972
|
+
this.entityService = inject(AXPEntityService);
|
|
5973
|
+
this.entityRegistry = inject(AXPEntityDefinitionRegistryService);
|
|
5974
|
+
this.translationService = inject(AXTranslationService);
|
|
5975
|
+
this.workflowEventService = inject(AXPWorkflowEventService);
|
|
5890
5976
|
}
|
|
5891
|
-
|
|
5892
|
-
|
|
5893
|
-
|
|
5894
|
-
|
|
5895
|
-
|
|
5896
|
-
|
|
5897
|
-
|
|
5898
|
-
|
|
5899
|
-
|
|
5900
|
-
|
|
5901
|
-
|
|
5902
|
-
|
|
5903
|
-
if (formatted) {
|
|
5904
|
-
const text = String(formatted);
|
|
5905
|
-
if (!isUnresolvedEntityDisplayTemplate(text)) {
|
|
5906
|
-
return text;
|
|
5907
|
-
}
|
|
5977
|
+
async execute(input) {
|
|
5978
|
+
const entity = input?.entity?.trim();
|
|
5979
|
+
const status = input?.status;
|
|
5980
|
+
const statusField = (input?.statusField ?? 'statusId').trim() || 'statusId';
|
|
5981
|
+
const ids = normalizeIds(input);
|
|
5982
|
+
if (!entity) {
|
|
5983
|
+
return {
|
|
5984
|
+
success: false,
|
|
5985
|
+
message: {
|
|
5986
|
+
text: await this.translationService.translateAsync('@general:messages.entity.invalid-identifier'),
|
|
5987
|
+
},
|
|
5988
|
+
};
|
|
5908
5989
|
}
|
|
5909
|
-
|
|
5910
|
-
|
|
5911
|
-
|
|
5912
|
-
|
|
5913
|
-
|
|
5914
|
-
}
|
|
5915
|
-
if (typeof raw === 'string' || typeof raw === 'number' || typeof raw === 'boolean') {
|
|
5916
|
-
return String(raw);
|
|
5917
|
-
}
|
|
5918
|
-
if (typeof raw === 'object' && !Array.isArray(raw)) {
|
|
5919
|
-
if (resolveMultiLanguage) {
|
|
5920
|
-
return resolveMultiLanguage(raw);
|
|
5990
|
+
if (status == null || String(status).length === 0) {
|
|
5991
|
+
return {
|
|
5992
|
+
success: false,
|
|
5993
|
+
message: { text: 'Status value is required.' },
|
|
5994
|
+
};
|
|
5921
5995
|
}
|
|
5922
|
-
|
|
5923
|
-
|
|
5924
|
-
|
|
5925
|
-
}
|
|
5926
|
-
|
|
5927
|
-
|
|
5928
|
-
|
|
5929
|
-
|
|
5930
|
-
|
|
5931
|
-
|
|
5932
|
-
|
|
5933
|
-
|
|
5934
|
-
|
|
5935
|
-
|
|
5936
|
-
|
|
5937
|
-
|
|
5938
|
-
|
|
5939
|
-
|
|
5940
|
-
|
|
5941
|
-
|
|
5942
|
-
|
|
5943
|
-
|
|
5944
|
-
|
|
5996
|
+
if (ids.length === 0) {
|
|
5997
|
+
return {
|
|
5998
|
+
success: false,
|
|
5999
|
+
message: { text: 'Either id or ids must be provided.' },
|
|
6000
|
+
};
|
|
6001
|
+
}
|
|
6002
|
+
const [moduleName, entityName] = entity.split('.');
|
|
6003
|
+
if (!moduleName || !entityName) {
|
|
6004
|
+
return {
|
|
6005
|
+
success: false,
|
|
6006
|
+
message: {
|
|
6007
|
+
text: await this.translationService.translateAsync('@general:messages.entity.invalid-identifier'),
|
|
6008
|
+
},
|
|
6009
|
+
};
|
|
6010
|
+
}
|
|
6011
|
+
try {
|
|
6012
|
+
const entityRef = await this.entityRegistry.resolve(moduleName, entityName);
|
|
6013
|
+
const data = this.entityService.withEntity(entity).data();
|
|
6014
|
+
const updatedRecords = [];
|
|
6015
|
+
for (const id of ids) {
|
|
6016
|
+
const updated = await data.update(id, { [statusField]: status });
|
|
6017
|
+
const record = updated && typeof updated === 'object'
|
|
6018
|
+
? updated
|
|
6019
|
+
: { id, [statusField]: status };
|
|
6020
|
+
updatedRecords.push(record);
|
|
6021
|
+
dispatchEntityModifiedSideEffects(this.workflowEventService, entity, { id, ...record, [statusField]: status }, entityRef?.parentKey);
|
|
5945
6022
|
}
|
|
6023
|
+
return {
|
|
6024
|
+
success: true,
|
|
6025
|
+
data: ids.length === 1 ? updatedRecords[0] : updatedRecords,
|
|
6026
|
+
};
|
|
6027
|
+
}
|
|
6028
|
+
catch (error) {
|
|
6029
|
+
const text = error instanceof Error
|
|
6030
|
+
? error.message
|
|
6031
|
+
: await this.translationService.translateAsync('@general:messages.entity.update-failed');
|
|
6032
|
+
return {
|
|
6033
|
+
success: false,
|
|
6034
|
+
message: { text },
|
|
6035
|
+
};
|
|
5946
6036
|
}
|
|
5947
6037
|
}
|
|
5948
|
-
|
|
6038
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPUpdateEntityStatusCommand, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
6039
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPUpdateEntityStatusCommand, providedIn: 'root' }); }
|
|
5949
6040
|
}
|
|
5950
|
-
|
|
5951
|
-
|
|
5952
|
-
|
|
5953
|
-
|
|
6041
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPUpdateEntityStatusCommand, decorators: [{
|
|
6042
|
+
type: Injectable,
|
|
6043
|
+
args: [{ providedIn: 'root' }]
|
|
6044
|
+
}] });
|
|
6045
|
+
//#endregion
|
|
6046
|
+
//#region ---- Helpers ----
|
|
6047
|
+
function normalizeIds(input) {
|
|
6048
|
+
if (!input) {
|
|
6049
|
+
return [];
|
|
5954
6050
|
}
|
|
5955
|
-
if (
|
|
5956
|
-
return
|
|
6051
|
+
if (Array.isArray(input.ids) && input.ids.length > 0) {
|
|
6052
|
+
return input.ids.map((id) => String(id)).filter((id) => id.length > 0);
|
|
5957
6053
|
}
|
|
5958
|
-
|
|
5959
|
-
|
|
5960
|
-
return `${idTarget.slice(0, -3)}.type`;
|
|
6054
|
+
if (input.id != null && String(input.id).length > 0) {
|
|
6055
|
+
return [String(input.id)];
|
|
5961
6056
|
}
|
|
5962
|
-
return
|
|
6057
|
+
return [];
|
|
5963
6058
|
}
|
|
5964
|
-
//#endregion
|
|
5965
6059
|
|
|
5966
|
-
|
|
6060
|
+
var updateEntityStatus_command = /*#__PURE__*/Object.freeze({
|
|
6061
|
+
__proto__: null,
|
|
6062
|
+
AXPUpdateEntityStatusCommand: AXPUpdateEntityStatusCommand
|
|
6063
|
+
});
|
|
6064
|
+
|
|
6065
|
+
/**
|
|
6066
|
+
* Build-time only. Do not import from Angular modules.
|
|
6067
|
+
*/
|
|
6068
|
+
const axpUpdateEntityStatusCommandDefinition = defineCommand({
|
|
6069
|
+
key: 'Entity:UpdateStatus',
|
|
6070
|
+
title: 'Update entity status',
|
|
6071
|
+
description: 'AXPUpdateEntityStatusCommand patches a status field on one or more records (entity, status, statusField, id|ids) via the entity update path; used as the default status-chip transition command.',
|
|
6072
|
+
executionMode: 'frontend',
|
|
6073
|
+
inputs: [
|
|
6074
|
+
{
|
|
6075
|
+
name: 'entity',
|
|
6076
|
+
title: 'Entity',
|
|
6077
|
+
description: 'Full entity key (Module.Entity).',
|
|
6078
|
+
interface: { name: 'text-editor' },
|
|
6079
|
+
validations: [{ rule: 'required' }],
|
|
6080
|
+
},
|
|
6081
|
+
{
|
|
6082
|
+
name: 'status',
|
|
6083
|
+
title: 'Status',
|
|
6084
|
+
description: 'Target status name/value to write.',
|
|
6085
|
+
interface: { name: 'text-editor' },
|
|
6086
|
+
validations: [{ rule: 'required' }],
|
|
6087
|
+
},
|
|
6088
|
+
{
|
|
6089
|
+
name: 'statusField',
|
|
6090
|
+
title: 'Status field',
|
|
6091
|
+
description: 'Property path to patch (default statusId).',
|
|
6092
|
+
interface: { name: 'text-editor' },
|
|
6093
|
+
},
|
|
6094
|
+
{
|
|
6095
|
+
name: 'id',
|
|
6096
|
+
title: 'Record id',
|
|
6097
|
+
description: 'Single record id (use ids for bulk).',
|
|
6098
|
+
interface: { name: 'text-editor' },
|
|
6099
|
+
},
|
|
6100
|
+
{
|
|
6101
|
+
name: 'ids',
|
|
6102
|
+
title: 'Record ids',
|
|
6103
|
+
description: 'Bulk record ids.',
|
|
6104
|
+
interface: { name: 'json' },
|
|
6105
|
+
},
|
|
6106
|
+
],
|
|
6107
|
+
outcomes: {
|
|
6108
|
+
mode: 'dynamic',
|
|
6109
|
+
summary: 'success with updated record(s); failure when entity/status/id missing or update throws.',
|
|
6110
|
+
derivesFrom: 'AXPExecuteCommandResult from execute()',
|
|
6111
|
+
},
|
|
6112
|
+
capabilities: ['ai'],
|
|
6113
|
+
ai: {
|
|
6114
|
+
tags: ['entity', 'status', 'update'],
|
|
6115
|
+
},
|
|
6116
|
+
categories: [AXP_COMMAND_DEFINITION_CATEGORY_ENTITY],
|
|
6117
|
+
});
|
|
6118
|
+
|
|
6119
|
+
class AXPViewEntityDetailsCommand {
|
|
5967
6120
|
constructor() {
|
|
5968
|
-
//#region ----
|
|
5969
|
-
this.
|
|
5970
|
-
this.
|
|
5971
|
-
this.
|
|
5972
|
-
this.translation = inject(AXTranslationService);
|
|
6121
|
+
//#region ---- Services & Dependencies ----
|
|
6122
|
+
this.entityForm = inject(AXPEntityFormBuilderService);
|
|
6123
|
+
this.entityService = inject(AXPEntityDefinitionRegistryService);
|
|
6124
|
+
this.translationService = inject(AXTranslationService);
|
|
5973
6125
|
this.expressionEvaluator = inject(AXPExpressionEvaluatorService);
|
|
5974
6126
|
this.contextEvalFactory = inject(AXPContextEvalFactory);
|
|
5975
|
-
this.changeDetectorRef = inject(ChangeDetectorRef);
|
|
5976
|
-
//#endregion
|
|
5977
|
-
//#region ---- Inputs ----
|
|
5978
|
-
this.entity = input.required(...(ngDevMode ? [{ debugName: "entity" }] : /* istanbul ignore next */ []));
|
|
5979
|
-
this.entityId = input.required(...(ngDevMode ? [{ debugName: "entityId" }] : /* istanbul ignore next */ []));
|
|
5980
|
-
this.textField = input('title', ...(ngDevMode ? [{ debugName: "textField" }] : /* istanbul ignore next */ []));
|
|
5981
|
-
this.valueField = input('id', ...(ngDevMode ? [{ debugName: "valueField" }] : /* istanbul ignore next */ []));
|
|
5982
|
-
this.displayTitle = input('', ...(ngDevMode ? [{ debugName: "displayTitle" }] : /* istanbul ignore next */ []));
|
|
5983
|
-
this.item = input(...(ngDevMode ? [undefined, { debugName: "item" }] : /* istanbul ignore next */ []));
|
|
5984
|
-
this.breadcrumb = input(null, ...(ngDevMode ? [{ debugName: "breadcrumb" }] : /* istanbul ignore next */ []));
|
|
5985
|
-
this.requestClose = input(...(ngDevMode ? [undefined, { debugName: "requestClose" }] : /* istanbul ignore next */ []));
|
|
5986
|
-
//#endregion
|
|
5987
|
-
//#region ---- Signals ----
|
|
5988
|
-
this.entityDetails = signal(null, ...(ngDevMode ? [{ debugName: "entityDetails" }] : /* istanbul ignore next */ []));
|
|
5989
|
-
this.isLoadingDetails = signal(false, ...(ngDevMode ? [{ debugName: "isLoadingDetails" }] : /* istanbul ignore next */ []));
|
|
5990
|
-
this.headerTitleVersion = 0;
|
|
5991
|
-
/** Placeholder rows for the loading skeleton (matches typical property count). */
|
|
5992
|
-
this.loadingSkeletonRows = [1, 2, 3, 4, 5];
|
|
5993
|
-
this.headerTitle = signal('', ...(ngDevMode ? [{ debugName: "headerTitle" }] : /* istanbul ignore next */ []));
|
|
5994
|
-
/**
|
|
5995
|
-
* Stable list of property widgets for the template. Must be a signal (computed), not a method:
|
|
5996
|
-
* calling a method from the template rebuilds nodes every CD cycle and can make the widget renderer loop.
|
|
5997
|
-
*/
|
|
5998
|
-
this.entityPropertiesWithWidgets = computed(() => {
|
|
5999
|
-
const details = this.entityDetails();
|
|
6000
|
-
const data = details?.entityData;
|
|
6001
|
-
const entityDefinition = details?.entityDefinition;
|
|
6002
|
-
if (!data || !entityDefinition?.properties)
|
|
6003
|
-
return [];
|
|
6004
|
-
return this.buildEntityPropertiesWithWidgets(data, entityDefinition, this.textField(), this.valueField());
|
|
6005
|
-
}, ...(ngDevMode ? [{ debugName: "entityPropertiesWithWidgets" }] : /* istanbul ignore next */ []));
|
|
6006
|
-
effect(() => {
|
|
6007
|
-
void this.translation.getActiveLang();
|
|
6008
|
-
const details = this.entityDetails();
|
|
6009
|
-
const preset = this.displayTitle();
|
|
6010
|
-
const fallbackItem = this.item();
|
|
6011
|
-
const version = ++this.headerTitleVersion;
|
|
6012
|
-
void this.resolveHeaderTitle(details?.entityDefinition, details?.entityData, preset, fallbackItem).then((title) => {
|
|
6013
|
-
if (version === this.headerTitleVersion) {
|
|
6014
|
-
this.headerTitle.set(title);
|
|
6015
|
-
}
|
|
6016
|
-
});
|
|
6017
|
-
});
|
|
6018
6127
|
}
|
|
6019
6128
|
//#endregion
|
|
6020
|
-
//#region ----
|
|
6021
|
-
|
|
6022
|
-
|
|
6023
|
-
|
|
6024
|
-
|
|
6025
|
-
|
|
6129
|
+
//#region ---- Command Execution ----
|
|
6130
|
+
async execute(input) {
|
|
6131
|
+
const excludeProperties = input['excludeProperties'] ??
|
|
6132
|
+
input?.options?.excludeProperties ??
|
|
6133
|
+
input?.__context__?.options?.excludeProperties;
|
|
6134
|
+
const { entity, entityInfo, data } = input.__context__;
|
|
6135
|
+
const [moduleName, entityName] = (entity || '').split('.');
|
|
6136
|
+
if (!moduleName || !entityName) {
|
|
6137
|
+
return {
|
|
6138
|
+
success: false,
|
|
6139
|
+
message: {
|
|
6140
|
+
text: await this.translationService.translateAsync('@general:messages.entity.invalid-identifier'),
|
|
6141
|
+
},
|
|
6142
|
+
};
|
|
6143
|
+
}
|
|
6144
|
+
const entityDef = await this.entityService.resolve(moduleName, entityName);
|
|
6145
|
+
let dialogRef;
|
|
6026
6146
|
try {
|
|
6027
|
-
|
|
6028
|
-
|
|
6029
|
-
|
|
6030
|
-
|
|
6031
|
-
|
|
6032
|
-
|
|
6147
|
+
if (!data || (typeof data !== 'string' && typeof data !== 'object')) {
|
|
6148
|
+
return {
|
|
6149
|
+
success: false,
|
|
6150
|
+
message: {
|
|
6151
|
+
text: await this.translationService.translateAsync('@general:messages.entity.invalid-data'),
|
|
6152
|
+
},
|
|
6153
|
+
};
|
|
6154
|
+
}
|
|
6155
|
+
let chain = this.entityForm.entity(`${moduleName}.${entityName}`).single(data);
|
|
6156
|
+
if (excludeProperties && excludeProperties.length > 0) {
|
|
6157
|
+
chain = chain.exclude(...excludeProperties);
|
|
6158
|
+
}
|
|
6159
|
+
const dialogTitle = await this.resolveDialogTitle(entityDef, data);
|
|
6160
|
+
if (dialogTitle) {
|
|
6161
|
+
chain = chain.title(dialogTitle);
|
|
6033
6162
|
}
|
|
6163
|
+
chain = chain.actions((actions) => {
|
|
6164
|
+
actions.submit('@general:actions.ok.title');
|
|
6165
|
+
});
|
|
6166
|
+
dialogRef = await chain.show();
|
|
6167
|
+
dialogRef.close();
|
|
6168
|
+
return {
|
|
6169
|
+
success: true,
|
|
6170
|
+
metadata: {
|
|
6171
|
+
refresh: false,
|
|
6172
|
+
},
|
|
6173
|
+
};
|
|
6034
6174
|
}
|
|
6035
6175
|
catch (error) {
|
|
6036
|
-
|
|
6037
|
-
|
|
6038
|
-
|
|
6039
|
-
|
|
6040
|
-
|
|
6176
|
+
return {
|
|
6177
|
+
success: false,
|
|
6178
|
+
message: {
|
|
6179
|
+
text: error instanceof Error
|
|
6180
|
+
? error.message
|
|
6181
|
+
: await this.translationService.translateAsync('@general:messages.entity.view-failed'),
|
|
6182
|
+
},
|
|
6183
|
+
};
|
|
6041
6184
|
}
|
|
6042
|
-
|
|
6043
|
-
|
|
6044
|
-
|
|
6045
|
-
|
|
6046
|
-
entity: this.entity(),
|
|
6047
|
-
data: { id: this.entityId() },
|
|
6048
|
-
});
|
|
6049
|
-
this.requestClose()?.();
|
|
6185
|
+
finally {
|
|
6186
|
+
if (dialogRef && typeof dialogRef === 'object' && dialogRef !== null && 'setLoading' in dialogRef) {
|
|
6187
|
+
dialogRef.setLoading(false);
|
|
6188
|
+
}
|
|
6050
6189
|
}
|
|
6051
6190
|
}
|
|
6052
6191
|
//#endregion
|
|
6053
|
-
//#region ----
|
|
6054
|
-
async
|
|
6055
|
-
|
|
6056
|
-
|
|
6057
|
-
|
|
6058
|
-
|
|
6192
|
+
//#region ---- Title resolution ----
|
|
6193
|
+
async resolveDialogTitle(entityDef, data) {
|
|
6194
|
+
const titleTemplate = entityDef.interfaces?.master?.single?.title ??
|
|
6195
|
+
entityDef.formats?.searchResult?.title ??
|
|
6196
|
+
entityDef.formats?.individual;
|
|
6197
|
+
if (!titleTemplate || typeof titleTemplate !== 'string') {
|
|
6198
|
+
return this.translationService.translateAsync('@general:actions.detail.title');
|
|
6199
|
+
}
|
|
6200
|
+
if (!titleTemplate.includes('{{')) {
|
|
6201
|
+
const translated = await this.translationService.translateAsync(titleTemplate);
|
|
6202
|
+
return resolveMultiLanguageString(translated, this.translationService.getActiveLang()).trim() || translated;
|
|
6203
|
+
}
|
|
6204
|
+
const recordContext = await this.loadRecordContext(`${entityDef.module}.${entityDef.name}`, data);
|
|
6205
|
+
const scope = {
|
|
6206
|
+
context: this.contextEvalFactory.createScope(recordContext),
|
|
6207
|
+
};
|
|
6208
|
+
const evaluated = await this.expressionEvaluator.evaluate(titleTemplate, scope);
|
|
6209
|
+
const locale = this.translationService.getActiveLang();
|
|
6210
|
+
const text = resolveMultiLanguageString(evaluated, locale).trim() || String(evaluated ?? '').trim();
|
|
6211
|
+
if (text) {
|
|
6212
|
+
return text;
|
|
6213
|
+
}
|
|
6214
|
+
return this.translationService.translateAsync('@general:actions.detail.title');
|
|
6215
|
+
}
|
|
6216
|
+
async loadRecordContext(entityFullName, data) {
|
|
6217
|
+
if (typeof data === 'object' && data !== null && !Array.isArray(data)) {
|
|
6218
|
+
const hasNestedFields = get(data, 'subject.title') != null ||
|
|
6219
|
+
get(data, 'recordType.title') != null ||
|
|
6220
|
+
get(data, 'occurredDate') != null;
|
|
6221
|
+
if (hasNestedFields) {
|
|
6222
|
+
return data;
|
|
6059
6223
|
}
|
|
6060
6224
|
}
|
|
6061
|
-
const
|
|
6062
|
-
if (
|
|
6063
|
-
return
|
|
6225
|
+
const recordId = typeof data === 'string' ? data : String(get(data, 'id') ?? '');
|
|
6226
|
+
if (!recordId) {
|
|
6227
|
+
return typeof data === 'object' && data !== null ? data : {};
|
|
6064
6228
|
}
|
|
6065
|
-
|
|
6066
|
-
const
|
|
6067
|
-
|
|
6068
|
-
|
|
6229
|
+
try {
|
|
6230
|
+
const [moduleName, entityName] = entityFullName.split('.');
|
|
6231
|
+
const entity = await this.entityService.resolve(moduleName, entityName);
|
|
6232
|
+
const byKeyFn = entity?.queries?.byKey?.execute;
|
|
6233
|
+
if (typeof byKeyFn === 'function') {
|
|
6234
|
+
return (await byKeyFn(recordId)) ?? {};
|
|
6069
6235
|
}
|
|
6070
6236
|
}
|
|
6071
|
-
|
|
6237
|
+
catch {
|
|
6238
|
+
// Fall back to list row payload below.
|
|
6239
|
+
}
|
|
6240
|
+
return typeof data === 'object' && data !== null ? data : {};
|
|
6072
6241
|
}
|
|
6073
|
-
|
|
6074
|
-
|
|
6075
|
-
|
|
6076
|
-
|
|
6077
|
-
|
|
6078
|
-
|
|
6079
|
-
|
|
6080
|
-
|
|
6081
|
-
|
|
6242
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPViewEntityDetailsCommand, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
6243
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPViewEntityDetailsCommand, providedIn: 'root' }); }
|
|
6244
|
+
}
|
|
6245
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPViewEntityDetailsCommand, decorators: [{
|
|
6246
|
+
type: Injectable,
|
|
6247
|
+
args: [{
|
|
6248
|
+
providedIn: 'root',
|
|
6249
|
+
}]
|
|
6250
|
+
}] });
|
|
6251
|
+
|
|
6252
|
+
var viewEntityDetails_command = /*#__PURE__*/Object.freeze({
|
|
6253
|
+
__proto__: null,
|
|
6254
|
+
AXPViewEntityDetailsCommand: AXPViewEntityDetailsCommand
|
|
6255
|
+
});
|
|
6256
|
+
|
|
6257
|
+
/** Common keys on denormalized expose stubs (e.g. `{ id, displayName }` from lookup expose). */
|
|
6258
|
+
const LOOKUP_EXPOSE_STUB_DISPLAY_FIELDS = ['displayName', 'title', 'name', 'fullName', 'code'];
|
|
6259
|
+
function resolveLookupItemFieldValue(item, field) {
|
|
6260
|
+
const direct = get(item, field);
|
|
6261
|
+
if (direct != null && direct !== '') {
|
|
6262
|
+
return direct;
|
|
6082
6263
|
}
|
|
6083
|
-
|
|
6084
|
-
|
|
6085
|
-
*/
|
|
6086
|
-
hasMeaningfulValue(value) {
|
|
6087
|
-
if (value === null || value === undefined)
|
|
6088
|
-
return false;
|
|
6089
|
-
if (typeof value === 'string')
|
|
6090
|
-
return value.trim().length > 0;
|
|
6091
|
-
if (Array.isArray(value))
|
|
6092
|
-
return value.length > 0;
|
|
6093
|
-
if (typeof value === 'object')
|
|
6094
|
-
return Object.keys(value).length > 0;
|
|
6095
|
-
return true;
|
|
6264
|
+
if (!field.includes('.')) {
|
|
6265
|
+
return direct;
|
|
6096
6266
|
}
|
|
6097
|
-
|
|
6098
|
-
|
|
6099
|
-
|
|
6100
|
-
|
|
6101
|
-
getPropertyImportanceScore(prop, data) {
|
|
6102
|
-
const schema = prop.showAs?.type ?? prop.schema;
|
|
6103
|
-
const importanceMap = {
|
|
6104
|
-
[AXPWidgetsCatalog.text]: 100,
|
|
6105
|
-
[AXPWidgetsCatalog.select]: 95,
|
|
6106
|
-
[AXPWidgetsCatalog.number]: 90,
|
|
6107
|
-
[AXPWidgetsCatalog.dateTime]: 85,
|
|
6108
|
-
[AXPWidgetsCatalog.richText]: 80,
|
|
6109
|
-
[AXPWidgetsCatalog.largeText]: 75,
|
|
6110
|
-
[AXPWidgetsCatalog.contact]: 74,
|
|
6111
|
-
[AXPWidgetsCatalog.toggle]: 65,
|
|
6112
|
-
[AXPWidgetsCatalog.checkbox]: 60,
|
|
6113
|
-
[AXPWidgetsCatalog.color]: 40,
|
|
6114
|
-
};
|
|
6115
|
-
let score = importanceMap[schema] ?? 50;
|
|
6116
|
-
// Prefer fields that actually have a value for the current entity
|
|
6117
|
-
const valuePath = prop.path ?? prop.name;
|
|
6118
|
-
if (this.hasMeaningfulValue(get(data, valuePath))) {
|
|
6119
|
-
score += 20;
|
|
6120
|
-
}
|
|
6121
|
-
// Slight preference for human-friendly titled fields
|
|
6122
|
-
const hasTitle = prop.title && prop.title !== prop.name;
|
|
6123
|
-
if (hasTitle) {
|
|
6124
|
-
score += 5;
|
|
6267
|
+
for (const fallback of LOOKUP_EXPOSE_STUB_DISPLAY_FIELDS) {
|
|
6268
|
+
const candidate = get(item, fallback);
|
|
6269
|
+
if (candidate != null && candidate !== '') {
|
|
6270
|
+
return candidate;
|
|
6125
6271
|
}
|
|
6126
|
-
return score;
|
|
6127
6272
|
}
|
|
6128
|
-
|
|
6129
|
-
|
|
6130
|
-
|
|
6131
|
-
|
|
6132
|
-
|
|
6133
|
-
|
|
6134
|
-
|
|
6135
|
-
|
|
6136
|
-
|
|
6137
|
-
|
|
6138
|
-
|
|
6139
|
-
|
|
6140
|
-
{ keys: ['status', 'state', 'phase', 'stage'], score: 105 },
|
|
6141
|
-
{ keys: ['priority', 'severity', 'rank'], score: 100 },
|
|
6142
|
-
{ keys: ['expirydate', 'expirationdate', 'expiredate', 'duedate', 'deadline', 'enddate'], score: 98 },
|
|
6143
|
-
{ keys: ['startdate', 'createdat', 'createdon', 'updatedat', 'updatedon'], score: 95 },
|
|
6144
|
-
{ keys: ['owner', 'assignee', 'assignedto'], score: 92 },
|
|
6145
|
-
{ keys: ['email', 'phone', 'mobile', 'contact'], score: 90 },
|
|
6146
|
-
{ keys: ['category', 'type', 'label', 'tag', 'note', 'notes'], score: 85 },
|
|
6147
|
-
{ keys: ['primary', 'active', 'active', 'enabled', 'archived', 'deleted', 'locked', 'disabled'], score: -2 }, // send to bottom,
|
|
6148
|
-
];
|
|
6149
|
-
for (const group of priorities) {
|
|
6150
|
-
for (const key of group.keys) {
|
|
6151
|
-
if (collapsed === key || collapsed.endsWith(key)) {
|
|
6152
|
-
return group.score;
|
|
6153
|
-
}
|
|
6154
|
-
}
|
|
6155
|
-
}
|
|
6156
|
-
// Pattern-based priorities (generic forms like validFrom/validTo, start*/end*)
|
|
6157
|
-
if (collapsed.endsWith('from') || collapsed.endsWith('to')) {
|
|
6158
|
-
return 97;
|
|
6159
|
-
}
|
|
6160
|
-
if (collapsed.startsWith('start') || collapsed.startsWith('end')) {
|
|
6161
|
-
return 96;
|
|
6273
|
+
return direct;
|
|
6274
|
+
}
|
|
6275
|
+
function formatLookupFieldValue(raw, locale, translationService, resolveMultiLanguage) {
|
|
6276
|
+
if (raw == null || raw === '') {
|
|
6277
|
+
return '';
|
|
6278
|
+
}
|
|
6279
|
+
if (typeof raw === 'string' || typeof raw === 'number' || typeof raw === 'boolean') {
|
|
6280
|
+
return String(raw);
|
|
6281
|
+
}
|
|
6282
|
+
if (typeof raw === 'object' && !Array.isArray(raw)) {
|
|
6283
|
+
if (resolveMultiLanguage) {
|
|
6284
|
+
return resolveMultiLanguage(raw);
|
|
6162
6285
|
}
|
|
6163
|
-
return
|
|
6286
|
+
return resolveSearchableText(raw, locale, translationService);
|
|
6164
6287
|
}
|
|
6165
|
-
|
|
6166
|
-
|
|
6167
|
-
|
|
6168
|
-
|
|
6169
|
-
|
|
6170
|
-
|
|
6171
|
-
|
|
6172
|
-
|
|
6173
|
-
|
|
6174
|
-
|
|
6175
|
-
|
|
6176
|
-
|
|
6177
|
-
return false;
|
|
6178
|
-
}
|
|
6179
|
-
const schema = prop.schema?.interface?.type;
|
|
6180
|
-
const importantWidgets = [
|
|
6181
|
-
AXPWidgetsCatalog.text,
|
|
6182
|
-
AXPWidgetsCatalog.largeText,
|
|
6183
|
-
AXPWidgetsCatalog.richText,
|
|
6184
|
-
AXPWidgetsCatalog.lookup,
|
|
6185
|
-
AXPWidgetsCatalog.number,
|
|
6186
|
-
AXPWidgetsCatalog.toggle,
|
|
6187
|
-
AXPWidgetsCatalog.checkbox,
|
|
6188
|
-
AXPWidgetsCatalog.dateTime,
|
|
6189
|
-
AXPWidgetsCatalog.color,
|
|
6190
|
-
AXPWidgetsCatalog.contact,
|
|
6191
|
-
];
|
|
6192
|
-
return importantWidgets.includes(schema);
|
|
6193
|
-
})
|
|
6194
|
-
.map((prop) => {
|
|
6195
|
-
const path = this.resolveDataPath(prop, data);
|
|
6196
|
-
const schema = prop.schema?.interface?.type;
|
|
6197
|
-
const options = prop.schema?.interface?.options ?? {};
|
|
6198
|
-
return {
|
|
6199
|
-
...prop,
|
|
6200
|
-
schema,
|
|
6201
|
-
path,
|
|
6202
|
-
options,
|
|
6203
|
-
};
|
|
6204
|
-
})
|
|
6205
|
-
.sort((a, b) => {
|
|
6206
|
-
const nameScoreA = this.getNameImportanceScore(a);
|
|
6207
|
-
const nameScoreB = this.getNameImportanceScore(b);
|
|
6208
|
-
if (nameScoreA !== nameScoreB)
|
|
6209
|
-
return nameScoreB - nameScoreA;
|
|
6210
|
-
const widgetScoreA = this.getPropertyImportanceScore(a, data);
|
|
6211
|
-
const widgetScoreB = this.getPropertyImportanceScore(b, data);
|
|
6212
|
-
if (widgetScoreA !== widgetScoreB)
|
|
6213
|
-
return widgetScoreB - widgetScoreA;
|
|
6214
|
-
const aHasValue = this.hasMeaningfulValue(get(data, a.path));
|
|
6215
|
-
const bHasValue = this.hasMeaningfulValue(get(data, b.path));
|
|
6216
|
-
if (aHasValue !== bHasValue)
|
|
6217
|
-
return aHasValue ? -1 : 1;
|
|
6218
|
-
const aHasTitle = a.title && a.title !== a.name;
|
|
6219
|
-
const bHasTitle = b.title && b.title !== b.name;
|
|
6220
|
-
if (aHasTitle !== bHasTitle)
|
|
6221
|
-
return aHasTitle ? -1 : 1;
|
|
6222
|
-
return a.name.localeCompare(b.name);
|
|
6223
|
-
})
|
|
6224
|
-
.slice(0, 7)
|
|
6225
|
-
.map((prop) => {
|
|
6226
|
-
const widgetNode = {
|
|
6227
|
-
type: prop.schema || AXPWidgetsCatalog.text,
|
|
6228
|
-
path: prop.path,
|
|
6229
|
-
options: { ...prop.options },
|
|
6230
|
-
mode: 'view',
|
|
6231
|
-
};
|
|
6232
|
-
return {
|
|
6233
|
-
name: prop.name,
|
|
6234
|
-
title: prop.title || prop.name,
|
|
6235
|
-
node: widgetNode,
|
|
6236
|
-
};
|
|
6237
|
-
});
|
|
6288
|
+
return String(raw);
|
|
6289
|
+
}
|
|
6290
|
+
//#region ---- Template resolution ----
|
|
6291
|
+
/**
|
|
6292
|
+
* Resolves the display template for a lookup item.
|
|
6293
|
+
* Priority: explicit `displayFormat` → entity `formats.lookup` (template) → entity `formats.searchResult.title`.
|
|
6294
|
+
* Returns undefined when `textField` is set or no template applies (use {@link resolveLookupDisplayField} instead).
|
|
6295
|
+
*/
|
|
6296
|
+
function resolveLookupDisplayTemplate(entity, options) {
|
|
6297
|
+
const explicit = options.displayFormat?.trim();
|
|
6298
|
+
if (explicit) {
|
|
6299
|
+
return normalizeEntityDisplayTemplate(explicit);
|
|
6238
6300
|
}
|
|
6239
|
-
|
|
6240
|
-
|
|
6241
|
-
|
|
6242
|
-
|
|
6243
|
-
|
|
6244
|
-
|
|
6245
|
-
|
|
6246
|
-
|
|
6247
|
-
|
|
6248
|
-
|
|
6249
|
-
|
|
6250
|
-
|
|
6251
|
-
|
|
6252
|
-
|
|
6253
|
-
|
|
6254
|
-
|
|
6255
|
-
|
|
6256
|
-
|
|
6257
|
-
|
|
6258
|
-
|
|
6259
|
-
|
|
6260
|
-
|
|
6261
|
-
|
|
6262
|
-
|
|
6263
|
-
|
|
6301
|
+
if (options.textField?.trim()) {
|
|
6302
|
+
return undefined;
|
|
6303
|
+
}
|
|
6304
|
+
const lookupFormat = entity?.formats?.lookup?.trim();
|
|
6305
|
+
if (lookupFormat && (lookupFormat.includes('{{') || lookupFormat.includes('{'))) {
|
|
6306
|
+
return normalizeEntityDisplayTemplate(lookupFormat);
|
|
6307
|
+
}
|
|
6308
|
+
const searchTitle = entity?.formats?.searchResult?.title?.trim();
|
|
6309
|
+
if (searchTitle) {
|
|
6310
|
+
return normalizeEntityDisplayTemplate(searchTitle);
|
|
6311
|
+
}
|
|
6312
|
+
return undefined;
|
|
6313
|
+
}
|
|
6314
|
+
/**
|
|
6315
|
+
* Resolves the property path used for lookup display when no template applies.
|
|
6316
|
+
* Priority: explicit `textField` → entity `formats.lookup` (simple path) → common property names.
|
|
6317
|
+
*/
|
|
6318
|
+
function resolveLookupDisplayField(entity, options) {
|
|
6319
|
+
if (options.textField?.trim()) {
|
|
6320
|
+
return options.textField.trim();
|
|
6321
|
+
}
|
|
6322
|
+
const lookupFormat = entity?.formats?.lookup?.trim();
|
|
6323
|
+
if (lookupFormat && !lookupFormat.includes('{') && !lookupFormat.includes('{{')) {
|
|
6324
|
+
return lookupFormat;
|
|
6325
|
+
}
|
|
6326
|
+
const defaults = ['title', 'name', 'code', 'description'];
|
|
6327
|
+
const found = defaults.find((name) => entity?.properties?.some((p) => p.name === name));
|
|
6328
|
+
return found ?? 'title';
|
|
6329
|
+
}
|
|
6330
|
+
/**
|
|
6331
|
+
* Formats a lookup row for display using template and/or field resolution (display tier).
|
|
6332
|
+
*/
|
|
6333
|
+
async function formatLookupItemDisplayAsync(item, entity, options, formatService, displayDeps, resolveMultiLanguage) {
|
|
6334
|
+
if (item == null) {
|
|
6335
|
+
return '';
|
|
6336
|
+
}
|
|
6337
|
+
if (typeof item === 'string' || typeof item === 'number' || typeof item === 'boolean') {
|
|
6338
|
+
return String(item);
|
|
6339
|
+
}
|
|
6340
|
+
if (typeof item !== 'object') {
|
|
6341
|
+
return '';
|
|
6342
|
+
}
|
|
6343
|
+
if (options.isMultiLanguage?.(item) && resolveMultiLanguage) {
|
|
6344
|
+
return resolveMultiLanguage(item);
|
|
6345
|
+
}
|
|
6346
|
+
const template = resolveLookupDisplayTemplate(entity, options);
|
|
6347
|
+
if (template) {
|
|
6348
|
+
if (template.includes('{{') || template.includes('context.eval')) {
|
|
6349
|
+
const evaluated = await evaluateEntityDisplayTemplate(template, item, displayDeps.expressionEvaluator, displayDeps.locale, displayDeps.translationService, displayDeps.contextEvalFactory);
|
|
6350
|
+
const text = sanitizeResolvedEntityDisplayTitle(resolveSearchableText(evaluated, displayDeps.locale, displayDeps.translationService));
|
|
6351
|
+
if (text && !isUnresolvedEntityDisplayTemplate(text)) {
|
|
6352
|
+
return text;
|
|
6353
|
+
}
|
|
6354
|
+
}
|
|
6355
|
+
else {
|
|
6356
|
+
const formatted = formatService.format(template, 'string', item);
|
|
6357
|
+
if (formatted) {
|
|
6358
|
+
const text = String(formatted);
|
|
6359
|
+
if (!isUnresolvedEntityDisplayTemplate(text)) {
|
|
6360
|
+
return text;
|
|
6264
6361
|
}
|
|
6265
6362
|
}
|
|
6266
6363
|
}
|
|
6267
|
-
return prop.name;
|
|
6268
6364
|
}
|
|
6269
|
-
|
|
6270
|
-
|
|
6365
|
+
const field = resolveLookupDisplayField(entity, options);
|
|
6366
|
+
const raw = typeof item === 'object' && item != null ? resolveLookupItemFieldValue(item, field) : undefined;
|
|
6367
|
+
return formatLookupFieldValue(raw, displayDeps.locale, displayDeps.translationService, resolveMultiLanguage);
|
|
6271
6368
|
}
|
|
6272
|
-
|
|
6273
|
-
|
|
6274
|
-
|
|
6275
|
-
|
|
6276
|
-
|
|
6277
|
-
|
|
6278
|
-
constructor() {
|
|
6279
|
-
//#region ---- Services & Dependencies ----
|
|
6280
|
-
this.overlayService = inject(AXOverlayService);
|
|
6281
|
-
this.platform = inject(AXPlatform);
|
|
6282
|
-
this.localeService = inject(AXLocaleService);
|
|
6283
|
-
this.document = inject(DOCUMENT);
|
|
6284
|
-
this.router = inject(Router);
|
|
6285
|
-
//#endregion
|
|
6286
|
-
//#region ---- Private Properties ----
|
|
6287
|
-
this.overlayRef = null;
|
|
6288
|
-
this.componentRef = null;
|
|
6289
|
-
this.anchorElement = null;
|
|
6290
|
-
this.outsideClickHandler = null;
|
|
6291
|
-
this.keydownHandler = null;
|
|
6292
|
-
this.routerSubscription = null;
|
|
6369
|
+
/**
|
|
6370
|
+
* @deprecated Prefer {@link formatLookupItemDisplayAsync} for expression templates with `context.eval`.
|
|
6371
|
+
*/
|
|
6372
|
+
function formatLookupItemDisplay(item, entity, options, formatService, resolveMultiLanguage) {
|
|
6373
|
+
if (item == null) {
|
|
6374
|
+
return '';
|
|
6293
6375
|
}
|
|
6294
|
-
|
|
6295
|
-
|
|
6296
|
-
|
|
6297
|
-
|
|
6298
|
-
|
|
6299
|
-
|
|
6300
|
-
|
|
6301
|
-
|
|
6302
|
-
|
|
6303
|
-
|
|
6304
|
-
|
|
6305
|
-
const
|
|
6306
|
-
if (
|
|
6307
|
-
|
|
6308
|
-
|
|
6309
|
-
|
|
6310
|
-
|
|
6311
|
-
};
|
|
6312
|
-
try {
|
|
6313
|
-
const ref = await this.overlayService.create(AXPEntityDetailPopoverComponent, {
|
|
6314
|
-
centered: false,
|
|
6315
|
-
width: 'fit-content',
|
|
6316
|
-
actionSheetStyle: true,
|
|
6317
|
-
panelClass: panelClasses,
|
|
6318
|
-
backdrop: {
|
|
6319
|
-
enabled: false,
|
|
6320
|
-
background: false,
|
|
6321
|
-
closeOnClick: false,
|
|
6322
|
-
},
|
|
6323
|
-
anchorOptions: {
|
|
6324
|
-
anchor: anchorElement,
|
|
6325
|
-
placement: this.resolveOverlayPlacement(anchorElement),
|
|
6326
|
-
offsetX: this.resolveOverlayOffsetX(anchorElement),
|
|
6327
|
-
offsetY: 8,
|
|
6328
|
-
autoFlip: true,
|
|
6329
|
-
},
|
|
6330
|
-
inputs: {
|
|
6331
|
-
entity: options.entity,
|
|
6332
|
-
entityId: options.id,
|
|
6333
|
-
textField: options.textField || 'title',
|
|
6334
|
-
valueField: options.valueField || 'id',
|
|
6335
|
-
displayTitle: options.displayTitle ?? '',
|
|
6336
|
-
breadcrumb: options.breadcrumb ?? null,
|
|
6337
|
-
item: options.item,
|
|
6338
|
-
requestClose,
|
|
6339
|
-
},
|
|
6340
|
-
onDispose: () => {
|
|
6341
|
-
this.handleOverlayDisposed();
|
|
6342
|
-
},
|
|
6343
|
-
});
|
|
6344
|
-
this.overlayRef = ref;
|
|
6345
|
-
this.componentRef = ref.instance;
|
|
6346
|
-
this.anchorElement = anchorElement;
|
|
6347
|
-
setTimeout(() => {
|
|
6348
|
-
ref.updatePosition();
|
|
6349
|
-
this.bindDismissHandlers(ref.overlayElement, anchorElement);
|
|
6350
|
-
}, 0);
|
|
6351
|
-
await this.componentRef.instance.loadDetails();
|
|
6376
|
+
if (typeof item === 'string' || typeof item === 'number' || typeof item === 'boolean') {
|
|
6377
|
+
return String(item);
|
|
6378
|
+
}
|
|
6379
|
+
if (typeof item !== 'object') {
|
|
6380
|
+
return '';
|
|
6381
|
+
}
|
|
6382
|
+
if (options.isMultiLanguage?.(item) && resolveMultiLanguage) {
|
|
6383
|
+
return resolveMultiLanguage(item);
|
|
6384
|
+
}
|
|
6385
|
+
const template = resolveLookupDisplayTemplate(entity, options);
|
|
6386
|
+
if (template && !template.includes('context.eval') && !template.includes('{{')) {
|
|
6387
|
+
const formatted = formatService.format(template, 'string', item);
|
|
6388
|
+
if (formatted) {
|
|
6389
|
+
const text = String(formatted);
|
|
6390
|
+
if (!isUnresolvedEntityDisplayTemplate(text)) {
|
|
6391
|
+
return text;
|
|
6392
|
+
}
|
|
6352
6393
|
}
|
|
6353
|
-
|
|
6354
|
-
|
|
6355
|
-
|
|
6394
|
+
}
|
|
6395
|
+
const field = resolveLookupDisplayField(entity, options);
|
|
6396
|
+
const raw = typeof item === 'object' && item != null ? resolveLookupItemFieldValue(item, field) : undefined;
|
|
6397
|
+
if (raw == null || raw === '') {
|
|
6398
|
+
return '';
|
|
6399
|
+
}
|
|
6400
|
+
if (typeof raw === 'string' || typeof raw === 'number' || typeof raw === 'boolean') {
|
|
6401
|
+
return String(raw);
|
|
6402
|
+
}
|
|
6403
|
+
if (typeof raw === 'object' && !Array.isArray(raw)) {
|
|
6404
|
+
if (resolveMultiLanguage) {
|
|
6405
|
+
return resolveMultiLanguage(raw);
|
|
6356
6406
|
}
|
|
6407
|
+
return raw;
|
|
6357
6408
|
}
|
|
6358
|
-
|
|
6359
|
-
|
|
6360
|
-
|
|
6361
|
-
|
|
6362
|
-
|
|
6409
|
+
return String(raw);
|
|
6410
|
+
}
|
|
6411
|
+
/** Matches a resolved platform entity key such as `BusinessCore.Party`. */
|
|
6412
|
+
const LOOKUP_ENTITY_KEY_PATTERN = /^[A-Za-z][\w-]*\.[A-Za-z][\w-]*$/;
|
|
6413
|
+
/**
|
|
6414
|
+
* Resolves the target entity key for a lookup column.
|
|
6415
|
+
* Widget column options may still contain unevaluated `context.eval(...)` expressions;
|
|
6416
|
+
* in that case the type is read from the row (e.g. `ownerRef.type`, `subjectRef.type`).
|
|
6417
|
+
*/
|
|
6418
|
+
function resolveLookupEntityKey(entityOption, rowData, columnName, expose) {
|
|
6419
|
+
const fromOption = typeof entityOption === 'string' ? entityOption.trim() : '';
|
|
6420
|
+
if (fromOption && LOOKUP_ENTITY_KEY_PATTERN.test(fromOption)) {
|
|
6421
|
+
return fromOption;
|
|
6422
|
+
}
|
|
6423
|
+
const typePath = resolvePolymorphicRefTypePath(columnName, fromOption, expose);
|
|
6424
|
+
if (typePath && rowData) {
|
|
6425
|
+
const typeValue = get(rowData, typePath);
|
|
6426
|
+
if (typeof typeValue === 'string') {
|
|
6427
|
+
const trimmed = typeValue.trim();
|
|
6428
|
+
if (LOOKUP_ENTITY_KEY_PATTERN.test(trimmed)) {
|
|
6429
|
+
return trimmed;
|
|
6430
|
+
}
|
|
6363
6431
|
}
|
|
6364
6432
|
}
|
|
6365
|
-
|
|
6366
|
-
|
|
6367
|
-
|
|
6433
|
+
return fromOption;
|
|
6434
|
+
}
|
|
6435
|
+
function resolvePolymorphicRefTypePath(columnName, entityOption, expose) {
|
|
6436
|
+
const fromExpression = entityOption.match(/context\.eval\(\s*['"]([^'"]+)['"]\s*\)/)?.[1];
|
|
6437
|
+
if (fromExpression) {
|
|
6438
|
+
return fromExpression;
|
|
6439
|
+
}
|
|
6440
|
+
if (columnName?.endsWith('.id')) {
|
|
6441
|
+
return `${columnName.slice(0, -3)}.type`;
|
|
6442
|
+
}
|
|
6443
|
+
const idTarget = expose?.find((entry) => entry.source === 'id')?.target;
|
|
6444
|
+
if (idTarget?.endsWith('.id')) {
|
|
6445
|
+
return `${idTarget.slice(0, -3)}.type`;
|
|
6446
|
+
}
|
|
6447
|
+
return undefined;
|
|
6448
|
+
}
|
|
6449
|
+
//#endregion
|
|
6450
|
+
|
|
6451
|
+
class AXPEntityDetailPopoverComponent {
|
|
6452
|
+
constructor() {
|
|
6453
|
+
//#region ---- Dependencies ----
|
|
6454
|
+
this.commandService = inject(AXPCommandService);
|
|
6455
|
+
this.queryService = inject(AXPQueryService);
|
|
6456
|
+
this.formatService = inject(AXFormatService);
|
|
6457
|
+
this.translation = inject(AXTranslationService);
|
|
6458
|
+
this.expressionEvaluator = inject(AXPExpressionEvaluatorService);
|
|
6459
|
+
this.contextEvalFactory = inject(AXPContextEvalFactory);
|
|
6460
|
+
this.changeDetectorRef = inject(ChangeDetectorRef);
|
|
6461
|
+
//#endregion
|
|
6462
|
+
//#region ---- Inputs ----
|
|
6463
|
+
this.entity = input.required(...(ngDevMode ? [{ debugName: "entity" }] : /* istanbul ignore next */ []));
|
|
6464
|
+
this.entityId = input.required(...(ngDevMode ? [{ debugName: "entityId" }] : /* istanbul ignore next */ []));
|
|
6465
|
+
this.textField = input('title', ...(ngDevMode ? [{ debugName: "textField" }] : /* istanbul ignore next */ []));
|
|
6466
|
+
this.valueField = input('id', ...(ngDevMode ? [{ debugName: "valueField" }] : /* istanbul ignore next */ []));
|
|
6467
|
+
this.displayTitle = input('', ...(ngDevMode ? [{ debugName: "displayTitle" }] : /* istanbul ignore next */ []));
|
|
6468
|
+
this.item = input(...(ngDevMode ? [undefined, { debugName: "item" }] : /* istanbul ignore next */ []));
|
|
6469
|
+
this.breadcrumb = input(null, ...(ngDevMode ? [{ debugName: "breadcrumb" }] : /* istanbul ignore next */ []));
|
|
6470
|
+
this.requestClose = input(...(ngDevMode ? [undefined, { debugName: "requestClose" }] : /* istanbul ignore next */ []));
|
|
6471
|
+
//#endregion
|
|
6472
|
+
//#region ---- Signals ----
|
|
6473
|
+
this.entityDetails = signal(null, ...(ngDevMode ? [{ debugName: "entityDetails" }] : /* istanbul ignore next */ []));
|
|
6474
|
+
this.isLoadingDetails = signal(false, ...(ngDevMode ? [{ debugName: "isLoadingDetails" }] : /* istanbul ignore next */ []));
|
|
6475
|
+
this.headerTitleVersion = 0;
|
|
6476
|
+
/** Placeholder rows for the loading skeleton (matches typical property count). */
|
|
6477
|
+
this.loadingSkeletonRows = [1, 2, 3, 4, 5];
|
|
6478
|
+
this.headerTitle = signal('', ...(ngDevMode ? [{ debugName: "headerTitle" }] : /* istanbul ignore next */ []));
|
|
6479
|
+
/**
|
|
6480
|
+
* Stable list of property widgets for the template. Must be a signal (computed), not a method:
|
|
6481
|
+
* calling a method from the template rebuilds nodes every CD cycle and can make the widget renderer loop.
|
|
6482
|
+
*/
|
|
6483
|
+
this.entityPropertiesWithWidgets = computed(() => {
|
|
6484
|
+
const details = this.entityDetails();
|
|
6485
|
+
const data = details?.entityData;
|
|
6486
|
+
const entityDefinition = details?.entityDefinition;
|
|
6487
|
+
if (!data || !entityDefinition?.properties)
|
|
6488
|
+
return [];
|
|
6489
|
+
return this.buildEntityPropertiesWithWidgets(data, entityDefinition, this.textField(), this.valueField());
|
|
6490
|
+
}, ...(ngDevMode ? [{ debugName: "entityPropertiesWithWidgets" }] : /* istanbul ignore next */ []));
|
|
6491
|
+
effect(() => {
|
|
6492
|
+
void this.translation.getActiveLang();
|
|
6493
|
+
const details = this.entityDetails();
|
|
6494
|
+
const preset = this.displayTitle();
|
|
6495
|
+
const fallbackItem = this.item();
|
|
6496
|
+
const version = ++this.headerTitleVersion;
|
|
6497
|
+
void this.resolveHeaderTitle(details?.entityDefinition, details?.entityData, preset, fallbackItem).then((title) => {
|
|
6498
|
+
if (version === this.headerTitleVersion) {
|
|
6499
|
+
this.headerTitle.set(title);
|
|
6500
|
+
}
|
|
6501
|
+
});
|
|
6502
|
+
});
|
|
6368
6503
|
}
|
|
6369
6504
|
//#endregion
|
|
6370
|
-
//#region ----
|
|
6371
|
-
|
|
6372
|
-
|
|
6373
|
-
|
|
6505
|
+
//#region ---- Public Methods ----
|
|
6506
|
+
/** Loads entity details after the overlay panel is created. */
|
|
6507
|
+
async loadDetails() {
|
|
6508
|
+
this.isLoadingDetails.set(true);
|
|
6509
|
+
this.entityDetails.set(null);
|
|
6510
|
+
this.changeDetectorRef.detectChanges();
|
|
6511
|
+
try {
|
|
6512
|
+
const result = await this.queryService.fetch('Entity:GetDetails', {
|
|
6513
|
+
entity: this.entity(),
|
|
6514
|
+
id: this.entityId(),
|
|
6515
|
+
});
|
|
6516
|
+
if (result?.success) {
|
|
6517
|
+
this.entityDetails.set(result);
|
|
6518
|
+
}
|
|
6374
6519
|
}
|
|
6375
|
-
|
|
6376
|
-
|
|
6520
|
+
catch (error) {
|
|
6521
|
+
console.error('Failed to fetch entity details:', error);
|
|
6377
6522
|
}
|
|
6378
|
-
|
|
6379
|
-
|
|
6380
|
-
|
|
6523
|
+
finally {
|
|
6524
|
+
this.isLoadingDetails.set(false);
|
|
6525
|
+
this.changeDetectorRef.detectChanges();
|
|
6381
6526
|
}
|
|
6382
|
-
return null;
|
|
6383
|
-
}
|
|
6384
|
-
/** Mirror popover placement for RTL so the panel aligns with the anchor's inline-start edge. */
|
|
6385
|
-
resolveOverlayPlacement(anchor) {
|
|
6386
|
-
return this.isRtlContext(anchor) ? 'bottom-end' : 'bottom-start';
|
|
6387
|
-
}
|
|
6388
|
-
resolveOverlayOffsetX(anchor) {
|
|
6389
|
-
return this.isRtlContext(anchor) ? -8 : 8;
|
|
6390
6527
|
}
|
|
6391
|
-
|
|
6392
|
-
if (this.
|
|
6393
|
-
|
|
6528
|
+
async navigateToDetails() {
|
|
6529
|
+
if (this.entityId()) {
|
|
6530
|
+
await this.commandService.execute('Entity:OpenDetails', {
|
|
6531
|
+
entity: this.entity(),
|
|
6532
|
+
data: { id: this.entityId() },
|
|
6533
|
+
});
|
|
6534
|
+
this.requestClose()?.();
|
|
6394
6535
|
}
|
|
6395
|
-
const dir = getComputedStyle(anchor).direction || this.document.documentElement.getAttribute('dir');
|
|
6396
|
-
return dir === 'rtl';
|
|
6397
6536
|
}
|
|
6398
|
-
|
|
6399
|
-
|
|
6400
|
-
|
|
6401
|
-
|
|
6402
|
-
|
|
6403
|
-
|
|
6404
|
-
|
|
6405
|
-
this.document.addEventListener('keydown', this.keydownHandler);
|
|
6406
|
-
this.outsideClickHandler = (event) => {
|
|
6407
|
-
const target = event.target;
|
|
6408
|
-
if (!target || !overlayElement) {
|
|
6409
|
-
return;
|
|
6410
|
-
}
|
|
6411
|
-
if (this.isInsideInteractiveSurface(target, overlayElement, anchor)) {
|
|
6412
|
-
return;
|
|
6537
|
+
//#endregion
|
|
6538
|
+
//#region ---- Helper Methods ----
|
|
6539
|
+
async resolveHeaderTitle(entityDefinition, data, preset, fallbackItem) {
|
|
6540
|
+
if (data && entityDefinition) {
|
|
6541
|
+
const fromDetails = await this.resolveLookupDisplayLabelAsync(data, entityDefinition);
|
|
6542
|
+
if (fromDetails) {
|
|
6543
|
+
return fromDetails;
|
|
6413
6544
|
}
|
|
6414
|
-
this.hide();
|
|
6415
|
-
};
|
|
6416
|
-
this.document.addEventListener('mousedown', this.outsideClickHandler);
|
|
6417
|
-
this.routerSubscription = this.router.events
|
|
6418
|
-
.pipe(filter((event) => event instanceof NavigationStart))
|
|
6419
|
-
.subscribe(() => {
|
|
6420
|
-
this.hide();
|
|
6421
|
-
});
|
|
6422
|
-
}
|
|
6423
|
-
/** Ignore clicks on the anchor, overlay panel, or nested dropdown menus. */
|
|
6424
|
-
isInsideInteractiveSurface(target, overlayElement, anchor) {
|
|
6425
|
-
if (anchor.contains(target) || overlayElement.contains(target)) {
|
|
6426
|
-
return true;
|
|
6427
|
-
}
|
|
6428
|
-
return Boolean(target.closest('ax-dropdown-panel, ax-button-item-list, ax-button-item, ax-dropdown-button, .ax-dropdown-panel'));
|
|
6429
|
-
}
|
|
6430
|
-
removeDismissHandlers() {
|
|
6431
|
-
if (this.keydownHandler) {
|
|
6432
|
-
this.document.removeEventListener('keydown', this.keydownHandler);
|
|
6433
|
-
this.keydownHandler = null;
|
|
6434
6545
|
}
|
|
6435
|
-
|
|
6436
|
-
|
|
6437
|
-
|
|
6546
|
+
const presetTrimmed = preset?.trim();
|
|
6547
|
+
if (presetTrimmed && !isUnresolvedEntityDisplayTemplate(presetTrimmed)) {
|
|
6548
|
+
return presetTrimmed;
|
|
6438
6549
|
}
|
|
6439
|
-
if (
|
|
6440
|
-
this.
|
|
6441
|
-
|
|
6550
|
+
if (fallbackItem) {
|
|
6551
|
+
const fromItem = await this.resolveLookupDisplayLabelAsync(fallbackItem, entityDefinition);
|
|
6552
|
+
if (fromItem) {
|
|
6553
|
+
return fromItem;
|
|
6554
|
+
}
|
|
6442
6555
|
}
|
|
6556
|
+
return presetTrimmed ?? '';
|
|
6443
6557
|
}
|
|
6444
|
-
|
|
6445
|
-
this.
|
|
6446
|
-
this.
|
|
6447
|
-
|
|
6448
|
-
|
|
6449
|
-
|
|
6450
|
-
|
|
6451
|
-
|
|
6452
|
-
|
|
6453
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPEntityDetailPopoverService, decorators: [{
|
|
6454
|
-
type: Injectable,
|
|
6455
|
-
args: [{
|
|
6456
|
-
providedIn: 'root',
|
|
6457
|
-
}]
|
|
6458
|
-
}] });
|
|
6459
|
-
|
|
6460
|
-
/**
|
|
6461
|
-
* Error type that can be thrown by middlewares to abort the chain with a structured payload.
|
|
6462
|
-
*/
|
|
6463
|
-
class AXPMiddlewareAbortError extends Error {
|
|
6464
|
-
constructor(message, options = {}) {
|
|
6465
|
-
super(message);
|
|
6466
|
-
this.message = message;
|
|
6467
|
-
this.options = options;
|
|
6468
|
-
this.name = 'AXPMiddlewareAbortError';
|
|
6469
|
-
}
|
|
6470
|
-
toResponse() {
|
|
6471
|
-
return {
|
|
6472
|
-
success: false,
|
|
6473
|
-
error: {
|
|
6474
|
-
code: this.options.code,
|
|
6475
|
-
message: this.message,
|
|
6476
|
-
status: this.options.status,
|
|
6477
|
-
details: this.options.details,
|
|
6478
|
-
},
|
|
6479
|
-
};
|
|
6480
|
-
}
|
|
6481
|
-
}
|
|
6482
|
-
/** Type guard for AXPMiddlewareAbortError */
|
|
6483
|
-
function isAXPMiddlewareAbortError(error) {
|
|
6484
|
-
return error instanceof AXPMiddlewareAbortError;
|
|
6485
|
-
}
|
|
6486
|
-
//#endregion
|
|
6487
|
-
|
|
6488
|
-
/**
|
|
6489
|
-
* Maps storage middleware failures to a command result the details view can surface.
|
|
6490
|
-
*/
|
|
6491
|
-
function mapEntityStorageErrorToCommandResult(error, fallback = 'Failed to update entity') {
|
|
6492
|
-
if (isAXPMiddlewareAbortError(error)) {
|
|
6493
|
-
return {
|
|
6494
|
-
success: false,
|
|
6495
|
-
message: {
|
|
6496
|
-
type: 'error',
|
|
6497
|
-
code: error.options.code,
|
|
6498
|
-
text: error.message,
|
|
6499
|
-
details: error.options.details,
|
|
6500
|
-
},
|
|
6501
|
-
};
|
|
6502
|
-
}
|
|
6503
|
-
const text = error instanceof Error ? error.message : typeof error === 'string' ? error : fallback;
|
|
6504
|
-
return {
|
|
6505
|
-
success: false,
|
|
6506
|
-
message: {
|
|
6507
|
-
type: 'error',
|
|
6508
|
-
text,
|
|
6509
|
-
},
|
|
6510
|
-
};
|
|
6511
|
-
}
|
|
6512
|
-
//#region ---- EntityDataAccessor ----
|
|
6513
|
-
/**
|
|
6514
|
-
* Data accessor for entity operations.
|
|
6515
|
-
* Provides methods to query and execute commands with smart resolution:
|
|
6516
|
-
* 1. Checks entity definition queries/commands first
|
|
6517
|
-
* 2. Falls back to CRUD service if not defined
|
|
6518
|
-
*/
|
|
6519
|
-
class EntityDataAccessor {
|
|
6520
|
-
constructor(module, entity, entityRegistry, queryExecutor, commandService, injector) {
|
|
6521
|
-
this.module = module;
|
|
6522
|
-
this.entity = entity;
|
|
6523
|
-
this.entityRegistry = entityRegistry;
|
|
6524
|
-
this.queryExecutor = queryExecutor;
|
|
6525
|
-
this.commandService = commandService;
|
|
6526
|
-
this.injector = injector;
|
|
6527
|
-
this.crudService = null;
|
|
6528
|
-
this.entityDef = null;
|
|
6529
|
-
this.entityDefPromise = null;
|
|
6558
|
+
async resolveLookupDisplayLabelAsync(item, entityDefinition) {
|
|
6559
|
+
const locale = this.translation.getActiveLang() ?? 'en-US';
|
|
6560
|
+
const label = await formatLookupItemDisplayAsync(item, entityDefinition ?? null, { textField: this.textField() }, this.formatService, {
|
|
6561
|
+
expressionEvaluator: this.expressionEvaluator,
|
|
6562
|
+
contextEvalFactory: this.contextEvalFactory,
|
|
6563
|
+
translationService: this.translation,
|
|
6564
|
+
locale,
|
|
6565
|
+
}, (value) => this.translation.resolve(value));
|
|
6566
|
+
return label && !isUnresolvedEntityDisplayTemplate(label) ? label : '';
|
|
6530
6567
|
}
|
|
6531
6568
|
/**
|
|
6532
|
-
*
|
|
6533
|
-
* can resolve {@link AXPEntityStorageService} via `inject()`.
|
|
6569
|
+
* Returns true if a value is meaningful for display (non-empty/non-null).
|
|
6534
6570
|
*/
|
|
6535
|
-
|
|
6536
|
-
if (
|
|
6537
|
-
|
|
6538
|
-
|
|
6539
|
-
|
|
6571
|
+
hasMeaningfulValue(value) {
|
|
6572
|
+
if (value === null || value === undefined)
|
|
6573
|
+
return false;
|
|
6574
|
+
if (typeof value === 'string')
|
|
6575
|
+
return value.trim().length > 0;
|
|
6576
|
+
if (Array.isArray(value))
|
|
6577
|
+
return value.length > 0;
|
|
6578
|
+
if (typeof value === 'object')
|
|
6579
|
+
return Object.keys(value).length > 0;
|
|
6580
|
+
return true;
|
|
6540
6581
|
}
|
|
6541
6582
|
/**
|
|
6542
|
-
*
|
|
6543
|
-
*
|
|
6583
|
+
* Calculates an importance score for a property based on its widget type,
|
|
6584
|
+
* whether it currently has a meaningful value, and metadata like title.
|
|
6544
6585
|
*/
|
|
6545
|
-
|
|
6546
|
-
|
|
6547
|
-
|
|
6548
|
-
|
|
6549
|
-
|
|
6550
|
-
|
|
6586
|
+
getPropertyImportanceScore(prop, data) {
|
|
6587
|
+
const schema = prop.showAs?.type ?? prop.schema;
|
|
6588
|
+
const importanceMap = {
|
|
6589
|
+
[AXPWidgetsCatalog.text]: 100,
|
|
6590
|
+
[AXPWidgetsCatalog.select]: 95,
|
|
6591
|
+
[AXPWidgetsCatalog.number]: 90,
|
|
6592
|
+
[AXPWidgetsCatalog.dateTime]: 85,
|
|
6593
|
+
[AXPWidgetsCatalog.richText]: 80,
|
|
6594
|
+
[AXPWidgetsCatalog.largeText]: 75,
|
|
6595
|
+
[AXPWidgetsCatalog.contact]: 74,
|
|
6596
|
+
[AXPWidgetsCatalog.toggle]: 65,
|
|
6597
|
+
[AXPWidgetsCatalog.checkbox]: 60,
|
|
6598
|
+
[AXPWidgetsCatalog.color]: 40,
|
|
6599
|
+
};
|
|
6600
|
+
let score = importanceMap[schema] ?? 50;
|
|
6601
|
+
// Prefer fields that actually have a value for the current entity
|
|
6602
|
+
const valuePath = prop.path ?? prop.name;
|
|
6603
|
+
if (this.hasMeaningfulValue(get(data, valuePath))) {
|
|
6604
|
+
score += 20;
|
|
6551
6605
|
}
|
|
6552
|
-
|
|
6553
|
-
|
|
6554
|
-
|
|
6606
|
+
// Slight preference for human-friendly titled fields
|
|
6607
|
+
const hasTitle = prop.title && prop.title !== prop.name;
|
|
6608
|
+
if (hasTitle) {
|
|
6609
|
+
score += 5;
|
|
6555
6610
|
}
|
|
6556
|
-
return
|
|
6611
|
+
return score;
|
|
6557
6612
|
}
|
|
6558
6613
|
/**
|
|
6559
|
-
*
|
|
6560
|
-
*
|
|
6561
|
-
*
|
|
6614
|
+
* Calculates a name-based importance score to prioritize common key fields
|
|
6615
|
+
* like title, description, status, and important dates.
|
|
6616
|
+
* Name priority MUST come before widget-type priority.
|
|
6562
6617
|
*/
|
|
6563
|
-
|
|
6564
|
-
const
|
|
6565
|
-
const
|
|
6566
|
-
|
|
6567
|
-
|
|
6618
|
+
getNameImportanceScore(prop) {
|
|
6619
|
+
const rawName = (prop.options?.dataPath ?? prop.name ?? '').toString();
|
|
6620
|
+
const normalized = rawName.toLowerCase();
|
|
6621
|
+
const collapsed = normalized.replace(/[^a-z0-9]/g, '');
|
|
6622
|
+
const priorities = [
|
|
6623
|
+
{ keys: ['title', 'name', 'subject', 'code'], score: 120 },
|
|
6624
|
+
{ keys: ['description', 'details', 'summary', 'remark', 'remarks'], score: 110 },
|
|
6625
|
+
{ keys: ['status', 'state', 'phase', 'stage'], score: 105 },
|
|
6626
|
+
{ keys: ['priority', 'severity', 'rank'], score: 100 },
|
|
6627
|
+
{ keys: ['expirydate', 'expirationdate', 'expiredate', 'duedate', 'deadline', 'enddate'], score: 98 },
|
|
6628
|
+
{ keys: ['startdate', 'createdat', 'createdon', 'updatedat', 'updatedon'], score: 95 },
|
|
6629
|
+
{ keys: ['owner', 'assignee', 'assignedto'], score: 92 },
|
|
6630
|
+
{ keys: ['email', 'phone', 'mobile', 'contact'], score: 90 },
|
|
6631
|
+
{ keys: ['category', 'type', 'label', 'tag', 'note', 'notes'], score: 85 },
|
|
6632
|
+
{ keys: ['primary', 'active', 'active', 'enabled', 'archived', 'deleted', 'locked', 'disabled'], score: -2 }, // send to bottom,
|
|
6633
|
+
];
|
|
6634
|
+
for (const group of priorities) {
|
|
6635
|
+
for (const key of group.keys) {
|
|
6636
|
+
if (collapsed === key || collapsed.endsWith(key)) {
|
|
6637
|
+
return group.score;
|
|
6638
|
+
}
|
|
6639
|
+
}
|
|
6568
6640
|
}
|
|
6569
|
-
|
|
6570
|
-
|
|
6641
|
+
// Pattern-based priorities (generic forms like validFrom/validTo, start*/end*)
|
|
6642
|
+
if (collapsed.endsWith('from') || collapsed.endsWith('to')) {
|
|
6643
|
+
return 97;
|
|
6571
6644
|
}
|
|
6572
|
-
|
|
6573
|
-
|
|
6645
|
+
if (collapsed.startsWith('start') || collapsed.startsWith('end')) {
|
|
6646
|
+
return 96;
|
|
6647
|
+
}
|
|
6648
|
+
return 0;
|
|
6649
|
+
}
|
|
6650
|
+
buildEntityPropertiesWithWidgets(data, entityDefinition, textFieldValue, valueFieldValue) {
|
|
6651
|
+
// Use properties (not columns) for correct titles and schema; columns may have dataPath that doesn't match
|
|
6652
|
+
return entityDefinition.properties
|
|
6653
|
+
.filter((prop) => {
|
|
6654
|
+
// Exclude technical fields
|
|
6655
|
+
if (prop.name === 'id' || prop.name === textFieldValue || prop.name === valueFieldValue) {
|
|
6656
|
+
return false;
|
|
6657
|
+
}
|
|
6658
|
+
// Only include properties that have a meaningful value (using get for dotted paths)
|
|
6659
|
+
const path = this.resolveDataPath(prop, data);
|
|
6660
|
+
const value = get(data, path);
|
|
6661
|
+
if (!this.hasMeaningfulValue(value)) {
|
|
6662
|
+
return false;
|
|
6663
|
+
}
|
|
6664
|
+
const schema = prop.schema?.interface?.type;
|
|
6665
|
+
const importantWidgets = [
|
|
6666
|
+
AXPWidgetsCatalog.text,
|
|
6667
|
+
AXPWidgetsCatalog.largeText,
|
|
6668
|
+
AXPWidgetsCatalog.richText,
|
|
6669
|
+
AXPWidgetsCatalog.lookup,
|
|
6670
|
+
AXPWidgetsCatalog.number,
|
|
6671
|
+
AXPWidgetsCatalog.toggle,
|
|
6672
|
+
AXPWidgetsCatalog.checkbox,
|
|
6673
|
+
AXPWidgetsCatalog.dateTime,
|
|
6674
|
+
AXPWidgetsCatalog.color,
|
|
6675
|
+
AXPWidgetsCatalog.contact,
|
|
6676
|
+
];
|
|
6677
|
+
return importantWidgets.includes(schema);
|
|
6678
|
+
})
|
|
6679
|
+
.map((prop) => {
|
|
6680
|
+
const path = this.resolveDataPath(prop, data);
|
|
6681
|
+
const schema = prop.schema?.interface?.type;
|
|
6682
|
+
const options = prop.schema?.interface?.options ?? {};
|
|
6683
|
+
return {
|
|
6684
|
+
...prop,
|
|
6685
|
+
schema,
|
|
6686
|
+
path,
|
|
6687
|
+
options,
|
|
6688
|
+
};
|
|
6689
|
+
})
|
|
6690
|
+
.sort((a, b) => {
|
|
6691
|
+
const nameScoreA = this.getNameImportanceScore(a);
|
|
6692
|
+
const nameScoreB = this.getNameImportanceScore(b);
|
|
6693
|
+
if (nameScoreA !== nameScoreB)
|
|
6694
|
+
return nameScoreB - nameScoreA;
|
|
6695
|
+
const widgetScoreA = this.getPropertyImportanceScore(a, data);
|
|
6696
|
+
const widgetScoreB = this.getPropertyImportanceScore(b, data);
|
|
6697
|
+
if (widgetScoreA !== widgetScoreB)
|
|
6698
|
+
return widgetScoreB - widgetScoreA;
|
|
6699
|
+
const aHasValue = this.hasMeaningfulValue(get(data, a.path));
|
|
6700
|
+
const bHasValue = this.hasMeaningfulValue(get(data, b.path));
|
|
6701
|
+
if (aHasValue !== bHasValue)
|
|
6702
|
+
return aHasValue ? -1 : 1;
|
|
6703
|
+
const aHasTitle = a.title && a.title !== a.name;
|
|
6704
|
+
const bHasTitle = b.title && b.title !== b.name;
|
|
6705
|
+
if (aHasTitle !== bHasTitle)
|
|
6706
|
+
return aHasTitle ? -1 : 1;
|
|
6707
|
+
return a.name.localeCompare(b.name);
|
|
6708
|
+
})
|
|
6709
|
+
.slice(0, 7)
|
|
6710
|
+
.map((prop) => {
|
|
6711
|
+
const widgetNode = {
|
|
6712
|
+
type: prop.schema || AXPWidgetsCatalog.text,
|
|
6713
|
+
path: prop.path,
|
|
6714
|
+
options: { ...prop.options },
|
|
6715
|
+
mode: 'view',
|
|
6716
|
+
};
|
|
6717
|
+
return {
|
|
6718
|
+
name: prop.name,
|
|
6719
|
+
title: prop.title || prop.name,
|
|
6720
|
+
node: widgetNode,
|
|
6721
|
+
};
|
|
6722
|
+
});
|
|
6574
6723
|
}
|
|
6575
6724
|
/**
|
|
6576
|
-
*
|
|
6577
|
-
*
|
|
6578
|
-
* @returns The paged list result
|
|
6725
|
+
* Resolves the data path for a property. For lookups with expose, returns the expanded path
|
|
6726
|
+
* when data has it (e.g. personTitle). Otherwise returns property name (e.g. personTitleId).
|
|
6579
6727
|
*/
|
|
6580
|
-
|
|
6581
|
-
const
|
|
6582
|
-
const
|
|
6583
|
-
const
|
|
6584
|
-
|
|
6585
|
-
|
|
6586
|
-
|
|
6587
|
-
|
|
6588
|
-
|
|
6589
|
-
|
|
6590
|
-
|
|
6591
|
-
|
|
6728
|
+
resolveDataPath(prop, data) {
|
|
6729
|
+
const iface = prop.schema?.interface;
|
|
6730
|
+
const expose = iface?.options?.['expose'];
|
|
6731
|
+
const textField = iface?.options?.['textField']?.trim();
|
|
6732
|
+
if (expose?.length) {
|
|
6733
|
+
const textTarget = expose.find((e) => e.source !== 'id');
|
|
6734
|
+
if (textTarget) {
|
|
6735
|
+
const expandedPath = textTarget.target.split('.')[0];
|
|
6736
|
+
const expandedValue = get(data, expandedPath);
|
|
6737
|
+
if (expandedValue != null && typeof expandedValue === 'object') {
|
|
6738
|
+
if (textField) {
|
|
6739
|
+
if (this.hasMeaningfulValue(get(expandedValue, textField))) {
|
|
6740
|
+
return expandedPath;
|
|
6741
|
+
}
|
|
6742
|
+
}
|
|
6743
|
+
else {
|
|
6744
|
+
const leaf = textTarget.target.split('.').pop();
|
|
6745
|
+
if (leaf && this.hasMeaningfulValue(get(expandedValue, leaf))) {
|
|
6746
|
+
return expandedPath;
|
|
6747
|
+
}
|
|
6748
|
+
}
|
|
6749
|
+
}
|
|
6592
6750
|
}
|
|
6593
|
-
return result;
|
|
6594
6751
|
}
|
|
6595
|
-
|
|
6596
|
-
return await this.ensureCrudService().query(queryRequest);
|
|
6752
|
+
return prop.name;
|
|
6597
6753
|
}
|
|
6598
|
-
|
|
6599
|
-
|
|
6600
|
-
|
|
6601
|
-
|
|
6602
|
-
|
|
6603
|
-
|
|
6754
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPEntityDetailPopoverComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
6755
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: AXPEntityDetailPopoverComponent, isStandalone: true, selector: "axp-entity-detail-popover", inputs: { entity: { classPropertyName: "entity", publicName: "entity", isSignal: true, isRequired: true, transformFunction: null }, entityId: { classPropertyName: "entityId", publicName: "entityId", isSignal: true, isRequired: true, transformFunction: null }, textField: { classPropertyName: "textField", publicName: "textField", isSignal: true, isRequired: false, transformFunction: null }, valueField: { classPropertyName: "valueField", publicName: "valueField", isSignal: true, isRequired: false, transformFunction: null }, displayTitle: { classPropertyName: "displayTitle", publicName: "displayTitle", isSignal: true, isRequired: false, transformFunction: null }, item: { classPropertyName: "item", publicName: "item", isSignal: true, isRequired: false, transformFunction: null }, breadcrumb: { classPropertyName: "breadcrumb", publicName: "breadcrumb", isSignal: true, isRequired: false, transformFunction: null }, requestClose: { classPropertyName: "requestClose", publicName: "requestClose", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<div\n class=\"lightest-surface dark:dark-surface p-4 w-[min(600px,calc(100vw-2rem))] rounded-lg shadow-lg\"\n>\n <div class=\"mb-4 border-b pb-2\">\n <h3 class=\"text-base font-semibold text-on-lightest-surface\">\n {{ headerTitle() }}\n </h3>\n @if (breadcrumb()) {\n <div class=\"text-xs text-neutral-500 mt-1\">{{ breadcrumb() }}</div>\n }\n </div>\n @if (isLoadingDetails()) {\n <div class=\"space-y-2 mb-4\">\n @for (row of loadingSkeletonRows; track row) {\n <div class=\"grid grid-cols-[minmax(7rem,35%)_1fr] gap-x-4 items-start\">\n <ax-skeleton [animated]=\"true\" class=\"h-4 w-2/3 rounded\"></ax-skeleton>\n @if (row === 2) {\n <ax-skeleton [animated]=\"true\" class=\"h-16 w-full rounded\"></ax-skeleton>\n } @else {\n <ax-skeleton [animated]=\"true\" class=\"h-4 w-full rounded\"></ax-skeleton>\n }\n </div>\n }\n </div>\n <div class=\"flex justify-end\">\n <ax-skeleton [animated]=\"true\" class=\"h-9 w-28 rounded\"></ax-skeleton>\n </div>\n } @else if (entityDetails()) {\n <div class=\"space-y-3 mb-4\">\n @if (entityDetails()?.entityData) {\n <axp-widgets-container [context]=\"entityDetails()?.entityData\">\n <div class=\"space-y-2\">\n @for (item of entityPropertiesWithWidgets(); track item.name) {\n <div class=\"grid grid-cols-[minmax(7rem,35%)_1fr] gap-x-4 gap-y-1 items-start\">\n <span class=\"text-sm font-medium shrink-0\">{{ item.title | translate | async }}:</span>\n <div class=\"min-w-0 text-sm\">\n <ng-container axp-widget-renderer [node]=\"item.node\" [mode]=\"'view'\"></ng-container>\n </div>\n </div>\n }\n </div>\n </axp-widgets-container>\n }\n </div>\n <div class=\"flex gap-2 justify-end ax-sm\">\n <ax-button\n [color]=\"'primary'\"\n [look]=\"'solid'\"\n [text]=\"'@general:actions.open-details.title' | translate | async\"\n (click)=\"navigateToDetails()\"\n >\n </ax-button>\n </div>\n }\n</div>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { 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: AXPWidgetCoreModule }, { kind: "component", type: i1$3.AXPWidgetContainerComponent, selector: "axp-widgets-container", inputs: ["context", "functions"], outputs: ["onContextChanged"] }, { kind: "directive", type: i1$3.AXPWidgetRendererDirective, selector: "[axp-widget-renderer]", inputs: ["parentNode", "index", "mode", "node"], outputs: ["onOptionsChanged", "onValueChanged", "onLoad"], exportAs: ["widgetRenderer"] }, { kind: "ngmodule", type: AXTranslationModule }, { kind: "ngmodule", type: AXSkeletonModule }, { kind: "component", type: i3$2.AXSkeletonComponent, selector: "ax-skeleton", inputs: ["animated"] }, { kind: "pipe", type: i4.AsyncPipe, name: "async" }, { kind: "pipe", type: i3$1.AXTranslatorPipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
6756
|
+
}
|
|
6757
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPEntityDetailPopoverComponent, decorators: [{
|
|
6758
|
+
type: Component,
|
|
6759
|
+
args: [{ selector: 'axp-entity-detail-popover', changeDetection: ChangeDetectionStrategy.OnPush, imports: [CommonModule, AXButtonModule, AXPWidgetCoreModule, AXTranslationModule, AXSkeletonModule], template: "<div\n class=\"lightest-surface dark:dark-surface p-4 w-[min(600px,calc(100vw-2rem))] rounded-lg shadow-lg\"\n>\n <div class=\"mb-4 border-b pb-2\">\n <h3 class=\"text-base font-semibold text-on-lightest-surface\">\n {{ headerTitle() }}\n </h3>\n @if (breadcrumb()) {\n <div class=\"text-xs text-neutral-500 mt-1\">{{ breadcrumb() }}</div>\n }\n </div>\n @if (isLoadingDetails()) {\n <div class=\"space-y-2 mb-4\">\n @for (row of loadingSkeletonRows; track row) {\n <div class=\"grid grid-cols-[minmax(7rem,35%)_1fr] gap-x-4 items-start\">\n <ax-skeleton [animated]=\"true\" class=\"h-4 w-2/3 rounded\"></ax-skeleton>\n @if (row === 2) {\n <ax-skeleton [animated]=\"true\" class=\"h-16 w-full rounded\"></ax-skeleton>\n } @else {\n <ax-skeleton [animated]=\"true\" class=\"h-4 w-full rounded\"></ax-skeleton>\n }\n </div>\n }\n </div>\n <div class=\"flex justify-end\">\n <ax-skeleton [animated]=\"true\" class=\"h-9 w-28 rounded\"></ax-skeleton>\n </div>\n } @else if (entityDetails()) {\n <div class=\"space-y-3 mb-4\">\n @if (entityDetails()?.entityData) {\n <axp-widgets-container [context]=\"entityDetails()?.entityData\">\n <div class=\"space-y-2\">\n @for (item of entityPropertiesWithWidgets(); track item.name) {\n <div class=\"grid grid-cols-[minmax(7rem,35%)_1fr] gap-x-4 gap-y-1 items-start\">\n <span class=\"text-sm font-medium shrink-0\">{{ item.title | translate | async }}:</span>\n <div class=\"min-w-0 text-sm\">\n <ng-container axp-widget-renderer [node]=\"item.node\" [mode]=\"'view'\"></ng-container>\n </div>\n </div>\n }\n </div>\n </axp-widgets-container>\n }\n </div>\n <div class=\"flex gap-2 justify-end ax-sm\">\n <ax-button\n [color]=\"'primary'\"\n [look]=\"'solid'\"\n [text]=\"'@general:actions.open-details.title' | translate | async\"\n (click)=\"navigateToDetails()\"\n >\n </ax-button>\n </div>\n }\n</div>\n" }]
|
|
6760
|
+
}], ctorParameters: () => [], propDecorators: { entity: [{ type: i0.Input, args: [{ isSignal: true, alias: "entity", required: true }] }], entityId: [{ type: i0.Input, args: [{ isSignal: true, alias: "entityId", required: true }] }], textField: [{ type: i0.Input, args: [{ isSignal: true, alias: "textField", required: false }] }], valueField: [{ type: i0.Input, args: [{ isSignal: true, alias: "valueField", required: false }] }], displayTitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "displayTitle", required: false }] }], item: [{ type: i0.Input, args: [{ isSignal: true, alias: "item", required: false }] }], breadcrumb: [{ type: i0.Input, args: [{ isSignal: true, alias: "breadcrumb", required: false }] }], requestClose: [{ type: i0.Input, args: [{ isSignal: true, alias: "requestClose", required: false }] }] } });
|
|
6761
|
+
|
|
6762
|
+
class AXPEntityDetailPopoverService {
|
|
6763
|
+
constructor() {
|
|
6764
|
+
//#region ---- Services & Dependencies ----
|
|
6765
|
+
this.overlayService = inject(AXOverlayService);
|
|
6766
|
+
this.platform = inject(AXPlatform);
|
|
6767
|
+
this.localeService = inject(AXLocaleService);
|
|
6768
|
+
this.document = inject(DOCUMENT);
|
|
6769
|
+
this.router = inject(Router);
|
|
6770
|
+
//#endregion
|
|
6771
|
+
//#region ---- Private Properties ----
|
|
6772
|
+
this.overlayRef = null;
|
|
6773
|
+
this.componentRef = null;
|
|
6774
|
+
this.anchorElement = null;
|
|
6775
|
+
this.outsideClickHandler = null;
|
|
6776
|
+
this.keydownHandler = null;
|
|
6777
|
+
this.routerSubscription = null;
|
|
6604
6778
|
}
|
|
6779
|
+
//#endregion
|
|
6780
|
+
//#region ---- Public Methods ----
|
|
6605
6781
|
/**
|
|
6606
|
-
*
|
|
6607
|
-
* @param data - The entity data to create
|
|
6608
|
-
* @returns The created entity key
|
|
6782
|
+
* Show entity detail popover anchored to the target element.
|
|
6609
6783
|
*/
|
|
6610
|
-
async
|
|
6611
|
-
const
|
|
6612
|
-
|
|
6613
|
-
|
|
6614
|
-
|
|
6615
|
-
|
|
6784
|
+
async show(target, options) {
|
|
6785
|
+
const anchorElement = this.resolveAnchorElement(target);
|
|
6786
|
+
if (!anchorElement) {
|
|
6787
|
+
return;
|
|
6788
|
+
}
|
|
6789
|
+
this.hide();
|
|
6790
|
+
const panelClasses = ['animate-fadeIn', 'animate-faster', 'axp-entity-detail-popover-overlay'];
|
|
6791
|
+
if (this.platform.isDark()) {
|
|
6792
|
+
panelClasses.push('ax-dark');
|
|
6793
|
+
}
|
|
6794
|
+
const requestClose = () => {
|
|
6795
|
+
this.hide();
|
|
6796
|
+
};
|
|
6797
|
+
try {
|
|
6798
|
+
const ref = await this.overlayService.create(AXPEntityDetailPopoverComponent, {
|
|
6799
|
+
centered: false,
|
|
6800
|
+
width: 'fit-content',
|
|
6801
|
+
actionSheetStyle: true,
|
|
6802
|
+
panelClass: panelClasses,
|
|
6803
|
+
backdrop: {
|
|
6804
|
+
enabled: false,
|
|
6805
|
+
background: false,
|
|
6806
|
+
closeOnClick: false,
|
|
6807
|
+
},
|
|
6808
|
+
anchorOptions: {
|
|
6809
|
+
anchor: anchorElement,
|
|
6810
|
+
placement: this.resolveOverlayPlacement(anchorElement),
|
|
6811
|
+
offsetX: this.resolveOverlayOffsetX(anchorElement),
|
|
6812
|
+
offsetY: 8,
|
|
6813
|
+
autoFlip: true,
|
|
6814
|
+
},
|
|
6815
|
+
inputs: {
|
|
6816
|
+
entity: options.entity,
|
|
6817
|
+
entityId: options.id,
|
|
6818
|
+
textField: options.textField || 'title',
|
|
6819
|
+
valueField: options.valueField || 'id',
|
|
6820
|
+
displayTitle: options.displayTitle ?? '',
|
|
6821
|
+
breadcrumb: options.breadcrumb ?? null,
|
|
6822
|
+
item: options.item,
|
|
6823
|
+
requestClose,
|
|
6824
|
+
},
|
|
6825
|
+
onDispose: () => {
|
|
6826
|
+
this.handleOverlayDisposed();
|
|
6827
|
+
},
|
|
6828
|
+
});
|
|
6829
|
+
this.overlayRef = ref;
|
|
6830
|
+
this.componentRef = ref.instance;
|
|
6831
|
+
this.anchorElement = anchorElement;
|
|
6832
|
+
setTimeout(() => {
|
|
6833
|
+
ref.updatePosition();
|
|
6834
|
+
this.bindDismissHandlers(ref.overlayElement, anchorElement);
|
|
6835
|
+
}, 0);
|
|
6836
|
+
await this.componentRef.instance.loadDetails();
|
|
6616
6837
|
}
|
|
6617
|
-
|
|
6618
|
-
|
|
6619
|
-
|
|
6620
|
-
throw new Error(commandMessageTextForError(result?.message?.text) || 'Failed to create entity');
|
|
6621
|
-
}
|
|
6622
|
-
return result.data?.id || result.data;
|
|
6838
|
+
catch (error) {
|
|
6839
|
+
console.error('[EntityDetailPopover] Failed to open overlay:', error);
|
|
6840
|
+
this.handleOverlayDisposed();
|
|
6623
6841
|
}
|
|
6624
|
-
// Fallback to CRUD service
|
|
6625
|
-
return await this.ensureCrudService().insertOne(data);
|
|
6626
6842
|
}
|
|
6627
|
-
/**
|
|
6628
|
-
|
|
6629
|
-
|
|
6630
|
-
|
|
6631
|
-
|
|
6632
|
-
*/
|
|
6633
|
-
async update(id, data) {
|
|
6634
|
-
const entityDef = await this.resolveEntityDef();
|
|
6635
|
-
const execute = entityDef.commands?.update?.execute;
|
|
6636
|
-
const updateData = { id, ...data };
|
|
6637
|
-
if (typeof execute === 'function') {
|
|
6638
|
-
return await execute(updateData);
|
|
6639
|
-
}
|
|
6640
|
-
else if (typeof execute === 'string') {
|
|
6641
|
-
const result = await this.commandService.execute(execute, updateData);
|
|
6642
|
-
if (!result?.success) {
|
|
6643
|
-
throw new Error(commandMessageTextForError(result?.message?.text) || 'Failed to update entity');
|
|
6644
|
-
}
|
|
6645
|
-
return result.data;
|
|
6843
|
+
/** Hide the active popover */
|
|
6844
|
+
hide() {
|
|
6845
|
+
this.removeDismissHandlers();
|
|
6846
|
+
if (this.overlayRef) {
|
|
6847
|
+
this.overlayRef.dispose();
|
|
6646
6848
|
}
|
|
6647
|
-
// Fallback to CRUD service
|
|
6648
|
-
return await this.ensureCrudService().updateOne(id, data);
|
|
6649
6849
|
}
|
|
6650
|
-
/**
|
|
6651
|
-
|
|
6652
|
-
|
|
6653
|
-
|
|
6654
|
-
|
|
6655
|
-
|
|
6656
|
-
|
|
6657
|
-
if (
|
|
6658
|
-
|
|
6659
|
-
return;
|
|
6850
|
+
/** Whether a popover is currently active */
|
|
6851
|
+
get isActive() {
|
|
6852
|
+
return this.overlayRef !== null;
|
|
6853
|
+
}
|
|
6854
|
+
//#endregion
|
|
6855
|
+
//#region ---- Utility Methods ----
|
|
6856
|
+
resolveAnchorElement(target) {
|
|
6857
|
+
if (target instanceof ElementRef) {
|
|
6858
|
+
return target.nativeElement;
|
|
6660
6859
|
}
|
|
6661
|
-
|
|
6662
|
-
|
|
6663
|
-
if (!result?.success) {
|
|
6664
|
-
throw new Error(commandMessageTextForError(result?.message?.text) || 'Failed to delete entity');
|
|
6665
|
-
}
|
|
6666
|
-
return;
|
|
6860
|
+
if (target instanceof HTMLElement) {
|
|
6861
|
+
return target;
|
|
6667
6862
|
}
|
|
6668
|
-
|
|
6669
|
-
|
|
6863
|
+
const hostCandidate = target;
|
|
6864
|
+
if (typeof hostCandidate.getHostElement === 'function') {
|
|
6865
|
+
return hostCandidate.getHostElement();
|
|
6866
|
+
}
|
|
6867
|
+
return null;
|
|
6670
6868
|
}
|
|
6671
|
-
|
|
6672
|
-
|
|
6673
|
-
|
|
6674
|
-
/**
|
|
6675
|
-
* Builder class for entity operations.
|
|
6676
|
-
* Provides methods to create paths and access entity data.
|
|
6677
|
-
*/
|
|
6678
|
-
class EntityBuilder {
|
|
6679
|
-
constructor(module, entity, sessionService, entityRegistry, queryExecutor, commandService, injector) {
|
|
6680
|
-
this.sessionService = sessionService;
|
|
6681
|
-
this.entityRegistry = entityRegistry;
|
|
6682
|
-
this.queryExecutor = queryExecutor;
|
|
6683
|
-
this.commandService = commandService;
|
|
6684
|
-
this.injector = injector;
|
|
6685
|
-
this.module = module;
|
|
6686
|
-
this.entity = entity;
|
|
6869
|
+
/** Mirror popover placement for RTL so the panel aligns with the anchor's inline-start edge. */
|
|
6870
|
+
resolveOverlayPlacement(anchor) {
|
|
6871
|
+
return this.isRtlContext(anchor) ? 'bottom-end' : 'bottom-start';
|
|
6687
6872
|
}
|
|
6688
|
-
|
|
6689
|
-
|
|
6690
|
-
* @param path - The path segment (defaults to 'list')
|
|
6691
|
-
* @returns The full path string
|
|
6692
|
-
*/
|
|
6693
|
-
createPath(path = 'list') {
|
|
6694
|
-
return `/${this.sessionService.application?.name}/m/${this.module}/e/${this.entity}/${path}`;
|
|
6873
|
+
resolveOverlayOffsetX(anchor) {
|
|
6874
|
+
return this.isRtlContext(anchor) ? -8 : 8;
|
|
6695
6875
|
}
|
|
6696
|
-
|
|
6697
|
-
|
|
6698
|
-
|
|
6699
|
-
|
|
6700
|
-
|
|
6701
|
-
|
|
6702
|
-
return new EntityDataAccessor(this.module, this.entity, this.entityRegistry, this.queryExecutor, this.commandService, this.injector);
|
|
6876
|
+
isRtlContext(anchor) {
|
|
6877
|
+
if (this.localeService.activeProfile()?.i18nMeta?.rtl === true) {
|
|
6878
|
+
return true;
|
|
6879
|
+
}
|
|
6880
|
+
const dir = getComputedStyle(anchor).direction || this.document.documentElement.getAttribute('dir');
|
|
6881
|
+
return dir === 'rtl';
|
|
6703
6882
|
}
|
|
6704
|
-
|
|
6705
|
-
|
|
6706
|
-
|
|
6707
|
-
|
|
6708
|
-
|
|
6709
|
-
|
|
6710
|
-
|
|
6711
|
-
this.
|
|
6712
|
-
this.
|
|
6883
|
+
bindDismissHandlers(overlayElement, anchor) {
|
|
6884
|
+
this.removeDismissHandlers();
|
|
6885
|
+
this.keydownHandler = (event) => {
|
|
6886
|
+
if (event.key === 'Escape') {
|
|
6887
|
+
this.hide();
|
|
6888
|
+
}
|
|
6889
|
+
};
|
|
6890
|
+
this.document.addEventListener('keydown', this.keydownHandler);
|
|
6891
|
+
this.outsideClickHandler = (event) => {
|
|
6892
|
+
const target = event.target;
|
|
6893
|
+
if (!target || !overlayElement) {
|
|
6894
|
+
return;
|
|
6895
|
+
}
|
|
6896
|
+
if (this.isInsideInteractiveSurface(target, overlayElement, anchor)) {
|
|
6897
|
+
return;
|
|
6898
|
+
}
|
|
6899
|
+
this.hide();
|
|
6900
|
+
};
|
|
6901
|
+
this.document.addEventListener('mousedown', this.outsideClickHandler);
|
|
6902
|
+
this.routerSubscription = this.router.events
|
|
6903
|
+
.pipe(filter((event) => event instanceof NavigationStart))
|
|
6904
|
+
.subscribe(() => {
|
|
6905
|
+
this.hide();
|
|
6906
|
+
});
|
|
6713
6907
|
}
|
|
6714
|
-
/**
|
|
6715
|
-
|
|
6716
|
-
|
|
6717
|
-
|
|
6718
|
-
|
|
6719
|
-
return
|
|
6908
|
+
/** Ignore clicks on the anchor, overlay panel, or nested dropdown menus. */
|
|
6909
|
+
isInsideInteractiveSurface(target, overlayElement, anchor) {
|
|
6910
|
+
if (anchor.contains(target) || overlayElement.contains(target)) {
|
|
6911
|
+
return true;
|
|
6912
|
+
}
|
|
6913
|
+
return Boolean(target.closest('ax-dropdown-panel, ax-button-item-list, ax-button-item, ax-dropdown-button, .ax-dropdown-panel'));
|
|
6720
6914
|
}
|
|
6721
|
-
|
|
6722
|
-
|
|
6723
|
-
|
|
6724
|
-
|
|
6725
|
-
// Two parameters: module and entity
|
|
6726
|
-
module = moduleOrEntityKey;
|
|
6727
|
-
entityName = entity;
|
|
6915
|
+
removeDismissHandlers() {
|
|
6916
|
+
if (this.keydownHandler) {
|
|
6917
|
+
this.document.removeEventListener('keydown', this.keydownHandler);
|
|
6918
|
+
this.keydownHandler = null;
|
|
6728
6919
|
}
|
|
6729
|
-
|
|
6730
|
-
|
|
6731
|
-
|
|
6732
|
-
|
|
6733
|
-
|
|
6734
|
-
|
|
6735
|
-
|
|
6736
|
-
entityName = moduleOrEntityKey.substring(dotIndex + 1);
|
|
6920
|
+
if (this.outsideClickHandler) {
|
|
6921
|
+
this.document.removeEventListener('mousedown', this.outsideClickHandler);
|
|
6922
|
+
this.outsideClickHandler = null;
|
|
6923
|
+
}
|
|
6924
|
+
if (this.routerSubscription) {
|
|
6925
|
+
this.routerSubscription.unsubscribe();
|
|
6926
|
+
this.routerSubscription = null;
|
|
6737
6927
|
}
|
|
6738
|
-
return new EntityBuilder(module, entityName, this.sessionService, this.entityRegistry, this.queryExecutor, this.commandService, this.injector);
|
|
6739
6928
|
}
|
|
6740
|
-
|
|
6741
|
-
|
|
6929
|
+
handleOverlayDisposed() {
|
|
6930
|
+
this.removeDismissHandlers();
|
|
6931
|
+
this.overlayRef = null;
|
|
6932
|
+
this.componentRef = null;
|
|
6933
|
+
this.anchorElement = null;
|
|
6934
|
+
}
|
|
6935
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPEntityDetailPopoverService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
6936
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPEntityDetailPopoverService, providedIn: 'root' }); }
|
|
6742
6937
|
}
|
|
6743
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type:
|
|
6938
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPEntityDetailPopoverService, decorators: [{
|
|
6744
6939
|
type: Injectable,
|
|
6745
|
-
args: [{
|
|
6940
|
+
args: [{
|
|
6941
|
+
providedIn: 'root',
|
|
6942
|
+
}]
|
|
6746
6943
|
}] });
|
|
6747
6944
|
|
|
6748
6945
|
class AXPEntityPreloadFiltersContainerComponent extends AXPPageLayoutBaseComponent {
|
|
@@ -8444,12 +8641,15 @@ class AXPEntityMasterListViewModel {
|
|
|
8444
8641
|
return null;
|
|
8445
8642
|
},
|
|
8446
8643
|
load: async (e) => {
|
|
8644
|
+
const listRequest = this.entityDef.parentKey != null
|
|
8645
|
+
? applyHierarchicalRootListFilter(e, this.entityDef.parentKey)
|
|
8646
|
+
: e;
|
|
8447
8647
|
const execute = this.entityDef.queries?.list?.execute;
|
|
8448
8648
|
if (typeof execute === 'function') {
|
|
8449
|
-
return await execute(
|
|
8649
|
+
return await execute(listRequest);
|
|
8450
8650
|
}
|
|
8451
8651
|
else if (typeof execute === 'string') {
|
|
8452
|
-
return await this.queryExecutor.fetch(execute,
|
|
8652
|
+
return await this.queryExecutor.fetch(execute, listRequest);
|
|
8453
8653
|
}
|
|
8454
8654
|
return null;
|
|
8455
8655
|
},
|
|
@@ -15510,12 +15710,18 @@ class AXPEntityListWidgetViewComponent extends AXPValueWidgetComponent {
|
|
|
15510
15710
|
if (!specs?.length) {
|
|
15511
15711
|
return;
|
|
15512
15712
|
}
|
|
15713
|
+
// Track both parent context and widget path — path is often empty on the first run.
|
|
15513
15714
|
this.contextService.data();
|
|
15514
|
-
|
|
15715
|
+
const path = this.fullPath();
|
|
15716
|
+
if (!path) {
|
|
15515
15717
|
return;
|
|
15516
15718
|
}
|
|
15517
|
-
untracked(() =>
|
|
15719
|
+
untracked(() => {
|
|
15720
|
+
void this.syncRelatedFiltersFromContextAndReload(specs);
|
|
15721
|
+
});
|
|
15518
15722
|
}, ...(ngDevMode ? [{ debugName: "#relatedFilterSyncEffect" }] : /* istanbul ignore next */ []));
|
|
15723
|
+
this.#relatedFilterReloadInProgress = false;
|
|
15724
|
+
this.#relatedFilterReloadQueued = false;
|
|
15519
15725
|
/** Patches data-list `refresh` so reload keeps parent-scoped toolbar filters on the data source. */
|
|
15520
15726
|
this.#patchDataListRefreshEffect = effect(() => {
|
|
15521
15727
|
const inst = this.listWidget()?.instance;
|
|
@@ -15526,15 +15732,29 @@ class AXPEntityListWidgetViewComponent extends AXPValueWidgetComponent {
|
|
|
15526
15732
|
const originalRefresh = inst.refresh.bind(inst);
|
|
15527
15733
|
inst.refresh = () => {
|
|
15528
15734
|
void (async () => {
|
|
15529
|
-
|
|
15530
|
-
|
|
15531
|
-
|
|
15532
|
-
|
|
15735
|
+
if (this.#relatedFilterReloadInProgress) {
|
|
15736
|
+
// Filters are being (re)applied by the in-progress sync — only re-fetch.
|
|
15737
|
+
originalRefresh();
|
|
15738
|
+
return;
|
|
15533
15739
|
}
|
|
15534
|
-
|
|
15535
|
-
|
|
15740
|
+
this.#relatedFilterReloadInProgress = true;
|
|
15741
|
+
try {
|
|
15742
|
+
do {
|
|
15743
|
+
this.#relatedFilterReloadQueued = false;
|
|
15744
|
+
const o = this.options();
|
|
15745
|
+
const specs = o['relatedFilterConditionSpecs'];
|
|
15746
|
+
if (o['syncRelatedListFiltersFromDialogContext'] && specs?.length) {
|
|
15747
|
+
await this.applyRelatedFiltersFromContextAndDatasource(specs);
|
|
15748
|
+
}
|
|
15749
|
+
else {
|
|
15750
|
+
await this.reapplyScopedFiltersToDataSource();
|
|
15751
|
+
}
|
|
15752
|
+
originalRefresh();
|
|
15753
|
+
} while (this.#relatedFilterReloadQueued);
|
|
15754
|
+
}
|
|
15755
|
+
finally {
|
|
15756
|
+
this.#relatedFilterReloadInProgress = false;
|
|
15536
15757
|
}
|
|
15537
|
-
originalRefresh();
|
|
15538
15758
|
})();
|
|
15539
15759
|
};
|
|
15540
15760
|
}, ...(ngDevMode ? [{ debugName: "#patchDataListRefreshEffect" }] : /* istanbul ignore next */ []));
|
|
@@ -15579,14 +15799,19 @@ class AXPEntityListWidgetViewComponent extends AXPValueWidgetComponent {
|
|
|
15579
15799
|
async execute(commandName, _data) {
|
|
15580
15800
|
const action = this.findToolbarAction(commandName);
|
|
15581
15801
|
const command = commandName.split('&')[0];
|
|
15582
|
-
const
|
|
15802
|
+
const selectedRows = action?.scope == AXPEntityCommandScope.Selected
|
|
15583
15803
|
? this.selectedItems()
|
|
15584
|
-
:
|
|
15585
|
-
|
|
15804
|
+
: null;
|
|
15805
|
+
// Evaluate first (same as page-list) so process.data expressions resolve against dialog context
|
|
15806
|
+
// — critical for create-wizard related steps where actions are kept raw until click time.
|
|
15807
|
+
let options = await this.evaluateToolbarExpressions(action?.options, selectedRows);
|
|
15586
15808
|
const listOpts = this.options();
|
|
15587
15809
|
const foreignKeyFieldName = listOpts['foreignKeyField'];
|
|
15588
15810
|
const createExcludes = listOpts['nestedCreateHiddenProperties'] ?? [];
|
|
15589
|
-
|
|
15811
|
+
// Inject parent FK for Entity:Create and TypeLevel create-style commands (e.g. Assign).
|
|
15812
|
+
if (foreignKeyFieldName &&
|
|
15813
|
+
(command === 'Entity:Create' ||
|
|
15814
|
+
action?.scope === AXPEntityCommandScope.TypeLevel)) {
|
|
15590
15815
|
const root = this.contextService.snapshot();
|
|
15591
15816
|
const idVal = get(root, 'id');
|
|
15592
15817
|
options = merge({}, options);
|
|
@@ -15616,6 +15841,9 @@ class AXPEntityListWidgetViewComponent extends AXPValueWidgetComponent {
|
|
|
15616
15841
|
...new Set([...existing, ...exclusionsFromList]),
|
|
15617
15842
|
];
|
|
15618
15843
|
}
|
|
15844
|
+
const commandData = selectedRows ??
|
|
15845
|
+
options['process']?.data ??
|
|
15846
|
+
null;
|
|
15619
15847
|
await this.commandService.execute(command, {
|
|
15620
15848
|
__context__: {
|
|
15621
15849
|
entity: this.entitySource(),
|
|
@@ -15788,33 +16016,72 @@ class AXPEntityListWidgetViewComponent extends AXPValueWidgetComponent {
|
|
|
15788
16016
|
listInstance.call('refresh');
|
|
15789
16017
|
this.isMounted.set(true);
|
|
15790
16018
|
}
|
|
16019
|
+
/**
|
|
16020
|
+
* Parent-scoped related filters must have a real PK. Empty/undefined values would otherwise
|
|
16021
|
+
* load unscoped rows (other employees' assignments) in create wizards.
|
|
16022
|
+
*/
|
|
16023
|
+
isUsableRelatedFilterValue(value) {
|
|
16024
|
+
if (value === undefined || value === null) {
|
|
16025
|
+
return false;
|
|
16026
|
+
}
|
|
16027
|
+
if (typeof value === 'string') {
|
|
16028
|
+
const trimmed = value.trim();
|
|
16029
|
+
return (trimmed.length > 0 &&
|
|
16030
|
+
!trimmed.includes('{{') &&
|
|
16031
|
+
trimmed !== AXP_RELATED_LIST_UNSCOPED_PARENT_FILTER_VALUE);
|
|
16032
|
+
}
|
|
16033
|
+
return true;
|
|
16034
|
+
}
|
|
15791
16035
|
/**
|
|
15792
16036
|
* Re-evaluates related-entity list filters from the live dialog form context (e.g. after create saves the main row id).
|
|
16037
|
+
* When the parent id is not ready yet, installs a never-match filter so the grid cannot leak other rows.
|
|
15793
16038
|
*/
|
|
15794
16039
|
async applyRelatedFiltersFromContext(specs) {
|
|
15795
16040
|
const root = this.contextService.snapshot();
|
|
16041
|
+
const foreignKeyField = this.options()['foreignKeyField'];
|
|
16042
|
+
const parentId = root['id'] ?? root['_id'];
|
|
15796
16043
|
const scope = {
|
|
15797
16044
|
context: {
|
|
15798
16045
|
eval: (path) => get(root, path),
|
|
15799
16046
|
},
|
|
15800
16047
|
};
|
|
15801
|
-
const
|
|
15802
|
-
|
|
15803
|
-
|
|
15804
|
-
|
|
15805
|
-
|
|
15806
|
-
|
|
16048
|
+
const evaluated = await Promise.all(specs.map(async (c) => {
|
|
16049
|
+
let value = await this.expressionEvaluator.evaluate(c.value, scope);
|
|
16050
|
+
// Prefer live root id for the FK field — expression eval can lag right after create-and-continue.
|
|
16051
|
+
if (foreignKeyField &&
|
|
16052
|
+
c.name === foreignKeyField &&
|
|
16053
|
+
this.isUsableRelatedFilterValue(parentId)) {
|
|
16054
|
+
value = parentId;
|
|
16055
|
+
}
|
|
16056
|
+
return {
|
|
16057
|
+
field: c.name,
|
|
16058
|
+
operator: c.operator,
|
|
16059
|
+
value,
|
|
16060
|
+
hidden: true,
|
|
16061
|
+
};
|
|
16062
|
+
}));
|
|
16063
|
+
const usable = evaluated.every((f) => this.isUsableRelatedFilterValue(f.value));
|
|
16064
|
+
// Never-match sentinel keeps fetchDataMode loads empty until the parent id exists.
|
|
16065
|
+
const filters = usable
|
|
16066
|
+
? evaluated
|
|
16067
|
+
: evaluated.map((f) => ({
|
|
16068
|
+
...f,
|
|
16069
|
+
value: this.isUsableRelatedFilterValue(f.value)
|
|
16070
|
+
? f.value
|
|
16071
|
+
: AXP_RELATED_LIST_UNSCOPED_PARENT_FILTER_VALUE,
|
|
16072
|
+
}));
|
|
15807
16073
|
if (!this.fullPath()) {
|
|
15808
|
-
return;
|
|
16074
|
+
return usable;
|
|
15809
16075
|
}
|
|
15810
16076
|
const current = this.getValue();
|
|
15811
16077
|
if (isEqual(current?.toolbar?.filters, filters)) {
|
|
15812
|
-
return;
|
|
16078
|
+
return usable;
|
|
15813
16079
|
}
|
|
15814
16080
|
this.setValue({
|
|
15815
16081
|
...current,
|
|
15816
16082
|
toolbar: { ...(current?.toolbar ?? {}), filters },
|
|
15817
16083
|
});
|
|
16084
|
+
return usable;
|
|
15818
16085
|
}
|
|
15819
16086
|
/** Keeps related-list filters in sync with dialog context for create wizards (main id appears after first-step save). */
|
|
15820
16087
|
#relatedFilterSyncEffect;
|
|
@@ -15854,8 +16121,39 @@ class AXPEntityListWidgetViewComponent extends AXPValueWidgetComponent {
|
|
|
15854
16121
|
* Writes toolbar filters from specs and pushes them onto the data source so refresh/reload keeps the parent scope.
|
|
15855
16122
|
*/
|
|
15856
16123
|
async applyRelatedFiltersFromContextAndDatasource(specs) {
|
|
15857
|
-
await this.applyRelatedFiltersFromContext(specs);
|
|
16124
|
+
const usable = await this.applyRelatedFiltersFromContext(specs);
|
|
15858
16125
|
await this.ensureToolbarFiltersOnDataSource();
|
|
16126
|
+
return usable;
|
|
16127
|
+
}
|
|
16128
|
+
/**
|
|
16129
|
+
* Applies parent-scoped filters then loads/refreshes the grid.
|
|
16130
|
+
* Used when dialog context gains a persisted root id (create-and-continue).
|
|
16131
|
+
*/
|
|
16132
|
+
async syncRelatedFiltersFromContextAndReload(specs) {
|
|
16133
|
+
if (this.#relatedFilterReloadInProgress) {
|
|
16134
|
+
this.#relatedFilterReloadQueued = true;
|
|
16135
|
+
return;
|
|
16136
|
+
}
|
|
16137
|
+
this.#relatedFilterReloadInProgress = true;
|
|
16138
|
+
try {
|
|
16139
|
+
do {
|
|
16140
|
+
this.#relatedFilterReloadQueued = false;
|
|
16141
|
+
await this.applyRelatedFiltersFromContextAndDatasource(specs);
|
|
16142
|
+
if (!this.pushToolbarFiltersToDataSource()) {
|
|
16143
|
+
continue;
|
|
16144
|
+
}
|
|
16145
|
+
if (!this.isMounted()) {
|
|
16146
|
+
this.triggerInitialLoadIfNeeded();
|
|
16147
|
+
continue;
|
|
16148
|
+
}
|
|
16149
|
+
const listInstance = this.listWidget()?.instance;
|
|
16150
|
+
// Flag is set so the patched refresh skips re-apply and only re-fetches.
|
|
16151
|
+
listInstance?.call?.('refresh');
|
|
16152
|
+
} while (this.#relatedFilterReloadQueued);
|
|
16153
|
+
}
|
|
16154
|
+
finally {
|
|
16155
|
+
this.#relatedFilterReloadInProgress = false;
|
|
16156
|
+
}
|
|
15859
16157
|
}
|
|
15860
16158
|
/**
|
|
15861
16159
|
* Refreshes the embedded data list (toolbar / commands). In wizard mode, `refresh` is patched to re-apply scoped filters first.
|
|
@@ -15863,6 +16161,8 @@ class AXPEntityListWidgetViewComponent extends AXPValueWidgetComponent {
|
|
|
15863
16161
|
refreshGridWithParentScopedFilters() {
|
|
15864
16162
|
this.listWidget()?.instance?.call('refresh');
|
|
15865
16163
|
}
|
|
16164
|
+
#relatedFilterReloadInProgress;
|
|
16165
|
+
#relatedFilterReloadQueued;
|
|
15866
16166
|
/** Patches data-list `refresh` so reload keeps parent-scoped toolbar filters on the data source. */
|
|
15867
16167
|
#patchDataListRefreshEffect;
|
|
15868
16168
|
#effect;
|
|
@@ -25676,7 +25976,9 @@ const AXPCrudModifier = {
|
|
|
25676
25976
|
if (!queries?.list) {
|
|
25677
25977
|
queries.list = {
|
|
25678
25978
|
execute: async (e) => {
|
|
25679
|
-
|
|
25979
|
+
const parentKey = ctx.parentKey.get();
|
|
25980
|
+
const request = parentKey != null ? applyHierarchicalRootListFilter(e, parentKey) : e;
|
|
25981
|
+
return await queryService.fetch(queryKey, request);
|
|
25680
25982
|
},
|
|
25681
25983
|
type: AXPEntityQueryType.List,
|
|
25682
25984
|
};
|
|
@@ -26341,6 +26643,10 @@ class AXPEntityModule {
|
|
|
26341
26643
|
key: 'Entity:Update',
|
|
26342
26644
|
command: () => Promise.resolve().then(function () { return updateEntity_command; }).then((c) => c.AXPUpdateEntityCommand),
|
|
26343
26645
|
},
|
|
26646
|
+
{
|
|
26647
|
+
key: 'Entity:UpdateStatus',
|
|
26648
|
+
command: () => Promise.resolve().then(function () { return updateEntityStatus_command; }).then((c) => c.AXPUpdateEntityStatusCommand),
|
|
26649
|
+
},
|
|
26344
26650
|
{
|
|
26345
26651
|
key: 'Entity:View',
|
|
26346
26652
|
command: () => Promise.resolve().then(function () { return viewEntityDetails_command; }).then((c) => c.AXPViewEntityDetailsCommand),
|
|
@@ -26504,6 +26810,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
26504
26810
|
key: 'Entity:Update',
|
|
26505
26811
|
command: () => Promise.resolve().then(function () { return updateEntity_command; }).then((c) => c.AXPUpdateEntityCommand),
|
|
26506
26812
|
},
|
|
26813
|
+
{
|
|
26814
|
+
key: 'Entity:UpdateStatus',
|
|
26815
|
+
command: () => Promise.resolve().then(function () { return updateEntityStatus_command; }).then((c) => c.AXPUpdateEntityStatusCommand),
|
|
26816
|
+
},
|
|
26507
26817
|
{
|
|
26508
26818
|
key: 'Entity:View',
|
|
26509
26819
|
command: () => Promise.resolve().then(function () { return viewEntityDetails_command; }).then((c) => c.AXPViewEntityDetailsCommand),
|
|
@@ -26780,5 +27090,5 @@ function detectEntityChanges(oldObj, newObj) {
|
|
|
26780
27090
|
* Generated bundle index. Do not edit.
|
|
26781
27091
|
*/
|
|
26782
27092
|
|
|
26783
|
-
export { ATTACHMENTS_PAGE_COMPONENT_KEY, AXMEntityCrudService, AXMEntityCrudServiceImpl, AXPCategoryTreeService, AXPCreateEntityCommand, AXPDataSeederService, AXPDeleteEntityCommand, AXPEditFileUploaderCommand, AXPEntitiesListDataSourceDefinition, AXPEntityCategoryTreeSelectorComponent, AXPEntityCategoryWidget, AXPEntityCategoryWidgetColumnComponent, AXPEntityCategoryWidgetEditComponent, AXPEntityCategoryWidgetViewComponent, AXPEntityCommandTriggerViewModel, AXPEntityCreateEvent, AXPEntityCreateViewElementViewModel, AXPEntityCreateViewModelFactory, AXPEntityCreateViewSectionViewModel, AXPEntityDataProvider, AXPEntityDataProviderImpl, AXPEntityDataSelectorRowActionsService, AXPEntityDataSelectorService, AXPEntityDefinitionProviderWidget, AXPEntityDefinitionProviderWidgetEditComponent, AXPEntityDefinitionRegistryService, AXPEntityDeletedEvent, AXPEntityDetailListViewModel, AXPEntityDetailPopoverComponent, AXPEntityDetailPopoverService, AXPEntityDetailViewModelFactory, AXPEntityDetailViewModelResolver, AXPEntityEventDispatcherService, AXPEntityFormBuilderService, AXPEntityLifecycleHookKeys, AXPEntityListPersistenceModeDefault, AXPEntityListTableService, AXPEntityListToolbarService, AXPEntityListViewCardFieldViewModel, AXPEntityListViewColumnViewModel, AXPEntityListViewModelFactory, AXPEntityListViewModelResolver, AXPEntityListWidget, AXPEntityListWidgetViewComponent, AXPEntityMasterCreateViewModel, AXPEntityMasterListCardSelectActionName, AXPEntityMasterListViewModel, AXPEntityMasterListViewQueryViewModel, AXPEntityMasterSingleElementViewModel, AXPEntityMasterSingleViewGroupViewModel, AXPEntityMasterSingleViewModel, AXPEntityMasterUpdateElementViewModel, AXPEntityMasterUpdateViewModel, AXPEntityMasterUpdateViewModelFactory, AXPEntityMiddleware, AXPEntityModifyEvent, AXPEntityModule, AXPEntityPreloadFiltersContainerComponent, AXPEntityPreloadFiltersViewModel, AXPEntityPreloadFiltersViewModelResolver, AXPEntityResolver, AXPEntityService, AXPEntityStorageService, AXPEntityUpdateViewSectionViewModel, AXPFileListComponent, AXPFileUploaderLoadFilesQuery, AXPFileUploaderSaveFilesCommand, AXPFileUploaderWidget, AXPFileUploaderWidgetColumnComponent, AXPFileUploaderWidgetEditComponent, AXPFileUploaderWidgetService, AXPFileUploaderWidgetViewComponent, AXPGetEntityDetailsQuery, AXPLayoutOrderingConfigService, AXPLookupWidget, AXPLookupWidgetColumnComponent, AXPLookupWidgetEditComponent, AXPLookupWidgetViewComponent, AXPMiddlewareAbortError, AXPMiddlewareEntityStorageService, AXPMultiSourceDefinitionProviderContext, AXPMultiSourceDefinitionProviderService, AXPMultiSourceFederatedSearchService, AXPMultiSourceSelectorComponent, AXPMultiSourceSelectorService, AXPMultiSourceSelectorWidget, AXPMultiSourceSelectorWidgetColumnComponent, AXPMultiSourceSelectorWidgetEditComponent, AXPMultiSourceSelectorWidgetViewComponent, AXPMultiSourceType, AXPOpenEntityDetailsCommand, AXPOpenEntityListCommand, AXPRelatedColumnEnrichmentService, AXPRelatedColumnMetadataResolver, AXPSelectorStructureWidget, AXPSelectorStructureWidgetColumnComponent, AXPSelectorStructureWidgetEditComponent, AXPSelectorStructureWidgetViewComponent, AXPShowFileUploaderPopupCommand, AXPTruncatedBreadcrumbComponent, AXPUpdateEntityCommand, AXPViewEntityDetailsCommand, AXP_APPEARANCE_SECTION_ORDER, AXP_CATEGORY_TREE_ROOT_TITLE_I18N_KEY, AXP_CLASSIFICATION_SECTION_ORDER, 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_COLUMN_ORDER, DEFAULT_PAIR_SPAN_RULES, DEFAULT_PROPERTY_ORDER, DEFAULT_SECTION_ORDER, ENTITY_LIST_ROUTE_CONTEXT_SESSION_KEY, ENTITY_MASTER_LIST_TRACE_NS, EntityBuilder, EntityDataAccessor, GALLERY_PAGE_COMPONENT_KEY, actionExists, applyDataSourcePagingWithoutLoad, attachmentFieldCount, attachmentNeedsHydration, attachmentsPlugin, attachmentsSemanticallyEqual, axpCreateEntityAiToolInputDefaults, axpCreateEntityCommandDefinition, axpDeleteEntityCommandDefinition, axpOpenEntityListCommandDefinition, axpShowFileUploaderPopupCommandDefinition, buildDataSourceTraceSnapshot, canPersistEntityListState, cloneLayoutArrays, columnOrderingMiddleware, columnOrderingMiddlewareProvider, columnWidthMiddleware, columnWidthMiddlewareProvider, committedAttachments, computeEntityAggregates, createColumnOrderingMiddlewareProvider, createLayoutOrderingMiddlewareProvider, createModifierContext, defaultCardLayoutMiddleware, defaultCardLayoutMiddlewareProvider, defaultMultiLanguageMiddleware, defaultMultiLanguageMiddlewareProvider, detectEntityChanges, ensureLayoutPropertyView, ensureLayoutSection, ensureListActions, eventDispatchMiddleware, filterSortEntityRows, findEntityListRowDataInTree, fingerprintAttachmentItem, fingerprintAttachments, fireEntityLifecycleHook, formatLookupItemDisplay, formatLookupItemDisplayAsync, galleryPlugin, getDataSourcePageIndex, getEntityListRowId, getMasterInterfacePropertySortKey, hasFileUploaderTitleOrDescriptionFields, hydrateAttachmentFieldToFileList, isAXPMiddlewareAbortError, isAttachmentFieldPresentOnRow, isAttachmentListEntry, isAttachmentStorageRef, isCategoryEntity, isCategoryFilter, isFileListItem, isFileUploaderEditDialogAuto, isLegacyEntityDataSelectorOptions, layoutOrderingMiddlewareFactory, layoutOrderingMiddlewareProvider, mapEntityStorageErrorToCommandResult, mapLegacyEntityDataSelectorOptions, normalizeEntityDataSelectorOptions, normalizeEntityFieldToFileList, normalizeEntityListPersistenceMode, normalizeListPaging, persistedAttachments, provideEntity, resolveAttachmentFieldPath, resolveEntityPluginDetailPageOrder, resolveFileUploaderEditDialog, resolveFileUploaderEntityScope, resolveLookupDisplayField, resolveLookupDisplayTemplate, resolveLookupEntityKey, restoreEntityListExpandedRows, runEntityQuery, searchResultDescriptionMiddleware, searchResultDescriptionMiddlewareProvider, shouldLoadEntityListStateFromStorage, shouldResetEntityListStateOnRouteEntry, traceEntityMasterList };
|
|
27093
|
+
export { ATTACHMENTS_PAGE_COMPONENT_KEY, AXMEntityCrudService, AXMEntityCrudServiceImpl, AXPCategoryTreeService, AXPCreateEntityCommand, AXPDataSeederService, AXPDeleteEntityCommand, AXPEditFileUploaderCommand, AXPEntitiesListDataSourceDefinition, AXPEntityCategoryTreeSelectorComponent, AXPEntityCategoryWidget, AXPEntityCategoryWidgetColumnComponent, AXPEntityCategoryWidgetEditComponent, AXPEntityCategoryWidgetViewComponent, AXPEntityCommandTriggerViewModel, AXPEntityCreateEvent, AXPEntityCreateViewElementViewModel, AXPEntityCreateViewModelFactory, AXPEntityCreateViewSectionViewModel, AXPEntityDataProvider, AXPEntityDataProviderImpl, AXPEntityDataSelectorRowActionsService, AXPEntityDataSelectorService, AXPEntityDefinitionProviderWidget, AXPEntityDefinitionProviderWidgetEditComponent, AXPEntityDefinitionRegistryService, AXPEntityDeletedEvent, AXPEntityDetailListViewModel, AXPEntityDetailPopoverComponent, AXPEntityDetailPopoverService, AXPEntityDetailViewModelFactory, AXPEntityDetailViewModelResolver, AXPEntityEventDispatcherService, AXPEntityFormBuilderService, AXPEntityLifecycleHookKeys, AXPEntityListPersistenceModeDefault, AXPEntityListTableService, AXPEntityListToolbarService, AXPEntityListViewCardFieldViewModel, AXPEntityListViewColumnViewModel, AXPEntityListViewModelFactory, AXPEntityListViewModelResolver, AXPEntityListWidget, AXPEntityListWidgetViewComponent, AXPEntityMasterCreateViewModel, AXPEntityMasterListCardSelectActionName, AXPEntityMasterListViewModel, AXPEntityMasterListViewQueryViewModel, AXPEntityMasterSingleElementViewModel, AXPEntityMasterSingleViewGroupViewModel, AXPEntityMasterSingleViewModel, AXPEntityMasterUpdateElementViewModel, AXPEntityMasterUpdateViewModel, AXPEntityMasterUpdateViewModelFactory, AXPEntityMiddleware, AXPEntityModifyEvent, AXPEntityModule, AXPEntityPreloadFiltersContainerComponent, AXPEntityPreloadFiltersViewModel, AXPEntityPreloadFiltersViewModelResolver, AXPEntityResolver, AXPEntityService, AXPEntityStorageService, AXPEntityUpdateViewSectionViewModel, AXPFileListComponent, AXPFileUploaderLoadFilesQuery, AXPFileUploaderSaveFilesCommand, AXPFileUploaderWidget, AXPFileUploaderWidgetColumnComponent, AXPFileUploaderWidgetEditComponent, AXPFileUploaderWidgetService, AXPFileUploaderWidgetViewComponent, AXPGetEntityDetailsQuery, AXPLayoutOrderingConfigService, AXPLookupWidget, AXPLookupWidgetColumnComponent, AXPLookupWidgetEditComponent, AXPLookupWidgetViewComponent, AXPMiddlewareAbortError, AXPMiddlewareEntityStorageService, AXPMultiSourceDefinitionProviderContext, AXPMultiSourceDefinitionProviderService, AXPMultiSourceFederatedSearchService, AXPMultiSourceSelectorComponent, AXPMultiSourceSelectorService, AXPMultiSourceSelectorWidget, AXPMultiSourceSelectorWidgetColumnComponent, AXPMultiSourceSelectorWidgetEditComponent, AXPMultiSourceSelectorWidgetViewComponent, AXPMultiSourceType, AXPOpenEntityDetailsCommand, AXPOpenEntityListCommand, AXPRelatedColumnEnrichmentService, AXPRelatedColumnMetadataResolver, AXPSelectorStructureWidget, AXPSelectorStructureWidgetColumnComponent, AXPSelectorStructureWidgetEditComponent, AXPSelectorStructureWidgetViewComponent, AXPShowFileUploaderPopupCommand, AXPTruncatedBreadcrumbComponent, AXPUpdateEntityCommand, AXPUpdateEntityStatusCommand, AXPViewEntityDetailsCommand, AXP_APPEARANCE_SECTION_ORDER, AXP_CATEGORY_TREE_ROOT_TITLE_I18N_KEY, AXP_CLASSIFICATION_SECTION_ORDER, 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_COLUMN_ORDER, DEFAULT_PAIR_SPAN_RULES, DEFAULT_PROPERTY_ORDER, DEFAULT_SECTION_ORDER, ENTITY_LIST_ROUTE_CONTEXT_SESSION_KEY, ENTITY_MASTER_LIST_TRACE_NS, EntityBuilder, EntityDataAccessor, GALLERY_PAGE_COMPONENT_KEY, actionExists, applyDataSourcePagingWithoutLoad, attachmentFieldCount, attachmentNeedsHydration, attachmentsPlugin, attachmentsSemanticallyEqual, axpCreateEntityAiToolInputDefaults, axpCreateEntityCommandDefinition, axpDeleteEntityCommandDefinition, axpOpenEntityListCommandDefinition, axpShowFileUploaderPopupCommandDefinition, axpUpdateEntityStatusCommandDefinition, buildDataSourceTraceSnapshot, canPersistEntityListState, cloneLayoutArrays, columnOrderingMiddleware, columnOrderingMiddlewareProvider, columnWidthMiddleware, columnWidthMiddlewareProvider, committedAttachments, computeEntityAggregates, createColumnOrderingMiddlewareProvider, createLayoutOrderingMiddlewareProvider, createModifierContext, defaultCardLayoutMiddleware, defaultCardLayoutMiddlewareProvider, defaultMultiLanguageMiddleware, defaultMultiLanguageMiddlewareProvider, detectEntityChanges, ensureLayoutPropertyView, ensureLayoutSection, ensureListActions, eventDispatchMiddleware, filterSortEntityRows, findEntityListRowDataInTree, fingerprintAttachmentItem, fingerprintAttachments, fireEntityLifecycleHook, formatLookupItemDisplay, formatLookupItemDisplayAsync, galleryPlugin, getDataSourcePageIndex, getEntityListRowId, getMasterInterfacePropertySortKey, hasFileUploaderTitleOrDescriptionFields, hydrateAttachmentFieldToFileList, isAXPMiddlewareAbortError, isAttachmentFieldPresentOnRow, isAttachmentListEntry, isAttachmentStorageRef, isCategoryEntity, isCategoryFilter, isFileListItem, isFileUploaderEditDialogAuto, isLegacyEntityDataSelectorOptions, layoutOrderingMiddlewareFactory, layoutOrderingMiddlewareProvider, mapEntityStorageErrorToCommandResult, mapLegacyEntityDataSelectorOptions, normalizeEntityDataSelectorOptions, normalizeEntityFieldToFileList, normalizeEntityListPersistenceMode, normalizeListPaging, persistedAttachments, provideEntity, resolveAttachmentFieldPath, resolveEntityPluginDetailPageOrder, resolveFileUploaderEditDialog, resolveFileUploaderEntityScope, resolveLookupDisplayField, resolveLookupDisplayTemplate, resolveLookupEntityKey, restoreEntityListExpandedRows, runEntityQuery, searchResultDescriptionMiddleware, searchResultDescriptionMiddlewareProvider, shouldLoadEntityListStateFromStorage, shouldResetEntityListStateOnRouteEntry, traceEntityMasterList };
|
|
26784
27094
|
//# sourceMappingURL=acorex-platform-framework-client-layout-entity.mjs.map
|