@acorex/platform 21.0.0-next.70 → 21.0.0-next.72
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-auth.mjs +10 -2
- package/fesm2022/acorex-platform-auth.mjs.map +1 -1
- package/fesm2022/{acorex-platform-common-common-settings.provider-Bi1RYif5.mjs → acorex-platform-common-common-settings.provider-Ytey9uhY.mjs} +15 -1
- package/fesm2022/acorex-platform-common-common-settings.provider-Ytey9uhY.mjs.map +1 -0
- package/fesm2022/acorex-platform-common.mjs +3798 -1674
- package/fesm2022/acorex-platform-common.mjs.map +1 -1
- package/fesm2022/acorex-platform-core.mjs +1362 -97
- package/fesm2022/acorex-platform-core.mjs.map +1 -1
- package/fesm2022/acorex-platform-layout-builder.mjs +446 -44
- package/fesm2022/acorex-platform-layout-builder.mjs.map +1 -1
- package/fesm2022/acorex-platform-layout-components.mjs +149 -109
- package/fesm2022/acorex-platform-layout-components.mjs.map +1 -1
- package/fesm2022/acorex-platform-layout-designer.mjs +199 -126
- package/fesm2022/acorex-platform-layout-designer.mjs.map +1 -1
- package/fesm2022/{acorex-platform-layout-entity-attachments-page.component-D8iQnT-R.mjs → acorex-platform-layout-entity-attachments-page.component-B0EkdqvH.mjs} +6 -1
- package/fesm2022/acorex-platform-layout-entity-attachments-page.component-B0EkdqvH.mjs.map +1 -0
- package/fesm2022/acorex-platform-layout-entity.mjs +823 -594
- package/fesm2022/acorex-platform-layout-entity.mjs.map +1 -1
- package/fesm2022/acorex-platform-layout-views.mjs +845 -218
- package/fesm2022/acorex-platform-layout-views.mjs.map +1 -1
- package/fesm2022/acorex-platform-layout-widget-core.mjs +122 -33
- package/fesm2022/acorex-platform-layout-widget-core.mjs.map +1 -1
- package/fesm2022/{acorex-platform-layout-widgets-tabular-data-edit-popup.component-BcpRkpJp.mjs → acorex-platform-layout-widgets-tabular-data-edit-popup.component-DjpZU6gz.mjs} +2 -2
- package/fesm2022/{acorex-platform-layout-widgets-tabular-data-edit-popup.component-BcpRkpJp.mjs.map → acorex-platform-layout-widgets-tabular-data-edit-popup.component-DjpZU6gz.mjs.map} +1 -1
- package/fesm2022/{acorex-platform-layout-widgets-tabular-data-view-popup.component-DQtK4lxl.mjs → acorex-platform-layout-widgets-tabular-data-view-popup.component-gX-3Kx9I.mjs} +2 -2
- package/fesm2022/{acorex-platform-layout-widgets-tabular-data-view-popup.component-DQtK4lxl.mjs.map → acorex-platform-layout-widgets-tabular-data-view-popup.component-gX-3Kx9I.mjs.map} +1 -1
- package/fesm2022/acorex-platform-layout-widgets.mjs +312 -676
- package/fesm2022/acorex-platform-layout-widgets.mjs.map +1 -1
- package/fesm2022/acorex-platform-themes-default-error-401.component-B1nsdpTY.mjs +48 -0
- package/fesm2022/acorex-platform-themes-default-error-401.component-B1nsdpTY.mjs.map +1 -0
- package/fesm2022/acorex-platform-themes-default-error-404.component-D4UvRe8u.mjs +42 -0
- package/fesm2022/acorex-platform-themes-default-error-404.component-D4UvRe8u.mjs.map +1 -0
- package/fesm2022/acorex-platform-themes-default.mjs +89 -46
- package/fesm2022/acorex-platform-themes-default.mjs.map +1 -1
- package/fesm2022/acorex-platform-themes-shared.mjs +50 -30
- package/fesm2022/acorex-platform-themes-shared.mjs.map +1 -1
- package/package.json +1 -1
- package/types/acorex-platform-auth.d.ts +2 -0
- package/types/acorex-platform-common.d.ts +899 -256
- package/types/acorex-platform-core.d.ts +394 -60
- package/types/acorex-platform-layout-builder.d.ts +78 -13
- package/types/acorex-platform-layout-components.d.ts +30 -24
- package/types/acorex-platform-layout-entity.d.ts +93 -44
- package/types/acorex-platform-layout-views.d.ts +162 -42
- package/types/acorex-platform-layout-widget-core.d.ts +60 -33
- package/types/acorex-platform-layout-widgets.d.ts +48 -20
- package/types/acorex-platform-themes-default.d.ts +38 -8
- package/types/acorex-platform-themes-shared.d.ts +6 -0
- package/types/acorex-platform-workflow.d.ts +1 -1
- package/fesm2022/acorex-platform-common-common-settings.provider-Bi1RYif5.mjs.map +0 -1
- package/fesm2022/acorex-platform-layout-entity-attachments-page.component-D8iQnT-R.mjs.map +0 -1
- package/fesm2022/acorex-platform-themes-default-error-401.component-C7EYJzSr.mjs +0 -31
- package/fesm2022/acorex-platform-themes-default-error-401.component-C7EYJzSr.mjs.map +0 -1
- package/fesm2022/acorex-platform-themes-default-error-404.component-7MVLMwIa.mjs +0 -25
- package/fesm2022/acorex-platform-themes-default-error-404.component-7MVLMwIa.mjs.map +0 -1
|
@@ -2,17 +2,17 @@ import { AXToastService } from '@acorex/components/toast';
|
|
|
2
2
|
import * as i6 from '@acorex/core/translation';
|
|
3
3
|
import { AXTranslationService, AXTranslationModule, resolveMultiLanguageString, translateSync } from '@acorex/core/translation';
|
|
4
4
|
import * as i4$3 from '@acorex/platform/common';
|
|
5
|
-
import { AXPEntityCommandScope, AXPSettingsService, AXPCommonSettings, AXPFilterOperatorMiddlewareService, isCardFieldBadgeDisplay, resolveCardFieldBadgeColor, getEntityInfo, resolveEnabledMasterListLayouts, resolveDefaultMasterListLayout, AXPRefreshEvent, AXPReloadEvent, AXPCleanNestedFilters, AXPFileTypeProviderService, AXPFileStorageService, AXPFileActionsService, AXPDefaultMultiLanguageConfigService, withDefaultMultiLanguageOnWidgetNodeTree, AXPEntityQueryType, AXPWorkflowNavigateAction, AXPToastAction, AXP_SEARCH_DEFINITION_PROVIDER, AXPMenuItemsDataSourceDefinition } from '@acorex/platform/common';
|
|
5
|
+
import { AXPNotFoundError, AXPEntityCommandScope, axpNavigateAppPath, AXPSettingsService, AXPCommonSettings, normalizeEntityDisplayTemplate, isUnresolvedEntityDisplayTemplate, AXPFilterOperatorMiddlewareService, isCardFieldBadgeDisplay, resolveCardFieldBadgeColor, getEntityInfo, resolveEnabledMasterListLayouts, resolveDefaultMasterListLayout, AXPRefreshEvent, AXPReloadEvent, axpRedirectToNotFound, AXPCleanNestedFilters, AXPFileTypeProviderService, AXPFileStorageService, buildEntitySearchTitleContext, resolveEntityRowTitleTemplate, AXPFileActionsService, axpIsEntityRecordNotFound, AXPDefaultMultiLanguageConfigService, withDefaultMultiLanguageOnWidgetNodeTree, AXPEntityQueryType, AXPWorkflowNavigateAction, AXP_NOT_FOUND_ROUTE, AXP_PROTECTED_ROUTE_GUARDS, AXPToastAction, AXP_SEARCH_DEFINITION_PROVIDER, AXPMenuItemsDataSourceDefinition } from '@acorex/platform/common';
|
|
6
6
|
import * as i0 from '@angular/core';
|
|
7
7
|
import { InjectionToken, inject, Injector, runInInjectionContext, Injectable, input, viewChild, signal, computed, ElementRef, ChangeDetectionStrategy, Component, ApplicationRef, EnvironmentInjector, createComponent, NgModule, ChangeDetectorRef, effect, Input, afterNextRender, untracked, ViewEncapsulation, viewChildren, linkedSignal, HostBinding, output, makeEnvironmentProviders } from '@angular/core';
|
|
8
8
|
import { Subject, takeUntil } from 'rxjs';
|
|
9
9
|
import { AXPLayoutBuilderService, LayoutBuilderModule } from '@acorex/platform/layout/builder';
|
|
10
10
|
import * as i3 from '@acorex/platform/layout/widget-core';
|
|
11
11
|
import { AXPWidgetsCatalog, AXPWidgetCoreModule, AXPPageStatus, AXPWidgetRegistryService, AXPColumnWidgetComponent, AXPValueWidgetComponent, AXPWidgetGroupEnum, createBooleanProperty, AXPWidgetRendererDirective, AXPBaseWidgetComponent, AXPWidgetStatus, createStringProperty, createNumberProperty, AXP_WIDGETS_ADVANCE_SUB_MEDIA, AXP_WIDGETS_ADVANCE_CATEGORY, createSelectProperty, AXP_WIDGETS_EDITOR_CATEGORY, AXP_WIDGET_DEFINITION_PROVIDER } from '@acorex/platform/layout/widget-core';
|
|
12
|
-
import { AXPSystemActionType, AXPDeviceService, AXPExpressionEvaluatorService, AXPBroadcastEventService, applyFilterArray, applySortArray, resolveActionLook, AXPDistributedEventListenerService, AXPPlatformScope, AXHighlightService, extractValue, setSmart, getChangedPaths, objectKeyValueTransforms, AXPHookService, AXPDataGenerator, AXPComponentSlotModule, AXPColumnWidthService, AXPModuleManifestRegistry, defaultColumnWidthProvider, AXP_COLUMN_WIDTH_PROVIDER, AXP_DATASOURCE_DEFINITION_PROVIDER, AXPModuleManifestsDataSourceDefinition } from '@acorex/platform/core';
|
|
12
|
+
import { AXPSystemActionType, AXPDeviceService, AXPExpressionEvaluatorService, AXPBroadcastEventService, applyFilterArray, applySortArray, resolveActionLook, AXPDistributedEventListenerService, AXPPlatformScope, AXHighlightService, extractValue, setSmart, getChangedPaths, objectKeyValueTransforms, AXPHookService, AXPDataGenerator, AXPComponentSlotModule, AXPContextStore, AXPColumnWidthService, AXPModuleManifestRegistry, defaultColumnWidthProvider, AXP_COLUMN_WIDTH_PROVIDER, AXP_DATASOURCE_DEFINITION_PROVIDER, AXPModuleManifestsDataSourceDefinition } from '@acorex/platform/core';
|
|
13
13
|
import { cloneDeep, merge, get, castArray, set, orderBy, omit, isNil, isEmpty, isEqual as isEqual$1, isArray, isString } from 'lodash-es';
|
|
14
14
|
import { transform, isEqual } from 'lodash';
|
|
15
|
-
import { AXPSessionService,
|
|
15
|
+
import { AXPSessionService, AXPPermissionDefinitionsDataSourceDefinition } from '@acorex/platform/auth';
|
|
16
16
|
import { Router, ActivatedRoute, RouterModule, ROUTES } from '@angular/router';
|
|
17
17
|
import { defineCommand, AXP_COMMAND_DEFINITION_CATEGORY_ENTITY, AXPCommandService, AXPQueryService, AXPQueryExecutor, AXPCommandExecutor, provideCommandSetups, provideQuerySetups, AXPCommandRegistry, AXPQueryRegistry } from '@acorex/platform/runtime';
|
|
18
18
|
import * as i1 from '@acorex/components/button';
|
|
@@ -23,9 +23,9 @@ import * as i2 from '@acorex/components/popover';
|
|
|
23
23
|
import { AXPopoverModule } from '@acorex/components/popover';
|
|
24
24
|
import { AXFormatService } from '@acorex/core/format';
|
|
25
25
|
import * as i5 from '@angular/common';
|
|
26
|
-
import { CommonModule, AsyncPipe } from '@angular/common';
|
|
26
|
+
import { CommonModule, AsyncPipe, NgClass } from '@angular/common';
|
|
27
27
|
import { AXPThemeLayoutBlockComponent, AXPPreloadFiltersComponent, AXP_PAGE_COMPONENT_PROVIDER, AXPStateMessageComponent, AXPColumnItemListComponent, AXPDataSelectorService, AXPPageComponentRegistryService } from '@acorex/platform/layout/components';
|
|
28
|
-
import { AXPPageLayoutBaseComponent, AXPPageLayoutComponent, AXPPageComponentRendererDirective, AXPPageComponentInstanceRegistryService } from '@acorex/platform/layout/views';
|
|
28
|
+
import { AXPPageLayoutBaseComponent, AXPPageLayoutComponent, AXPPageComponentRendererDirective, AXPPageComponentInstanceRegistryService, axpDetailsViewCanDeactivateGuard } from '@acorex/platform/layout/views';
|
|
29
29
|
import { AXDataSource } from '@acorex/cdk/common';
|
|
30
30
|
import { AXP_ENTITY_CRUD_SETUP } from '@acorex/platform/domain';
|
|
31
31
|
export { AXP_ENTITY_DEFINITION_CRUD_SERVICE } from '@acorex/platform/domain';
|
|
@@ -823,7 +823,7 @@ class AXPEntityDefinitionRegistryService {
|
|
|
823
823
|
throw error; // Rethrow to allow error handling by caller
|
|
824
824
|
}
|
|
825
825
|
if (!config) {
|
|
826
|
-
throw new
|
|
826
|
+
throw new AXPNotFoundError(`Invalid entity name: ${key}`);
|
|
827
827
|
}
|
|
828
828
|
}
|
|
829
829
|
return config;
|
|
@@ -902,6 +902,7 @@ function entityMasterDeleteAction() {
|
|
|
902
902
|
type: AXPSystemActionType.Delete,
|
|
903
903
|
scope: AXPEntityCommandScope.Individual,
|
|
904
904
|
order: 100,
|
|
905
|
+
shortcuts: ['ctrl+shift+delete'],
|
|
905
906
|
};
|
|
906
907
|
}
|
|
907
908
|
function entityMasterCrudActions(options) {
|
|
@@ -1686,13 +1687,14 @@ class PropertyFilter {
|
|
|
1686
1687
|
d.setTitle(title);
|
|
1687
1688
|
d.setSize(this.externalSize ?? (useFormWizard ? 'lg' : calculatedSize));
|
|
1688
1689
|
d.setCloseButton(true);
|
|
1690
|
+
d.confirmCloseWhenDirty();
|
|
1689
1691
|
d.content((layout) => {
|
|
1690
1692
|
const defaultMode = this.kind === 'single' ? 'view' : 'edit';
|
|
1691
1693
|
layout.mode(this.externalMode ?? defaultMode);
|
|
1692
1694
|
if (useFormWizard) {
|
|
1693
1695
|
layout.stepWizard((w) => {
|
|
1694
1696
|
w.name(ENTITY_FORM_STEP_WIZARD_NAME);
|
|
1695
|
-
w.setLook('arrow-minimal');
|
|
1697
|
+
w.setLook('rectangular-arrow-minimal');
|
|
1696
1698
|
w.setShowActions(false);
|
|
1697
1699
|
const mainStepTitle = (entity.formats?.individual ||
|
|
1698
1700
|
entity.title ||
|
|
@@ -2497,8 +2499,7 @@ class AXPOpenEntityDetailsCommand {
|
|
|
2497
2499
|
};
|
|
2498
2500
|
}
|
|
2499
2501
|
const url = `/${this.sessionService.application?.name}/m/${module}/e/${entityName}/${data.id}/view`;
|
|
2500
|
-
|
|
2501
|
-
this.router.navigate([url]);
|
|
2502
|
+
await axpNavigateAppPath(this.router, url);
|
|
2502
2503
|
return {
|
|
2503
2504
|
success: true,
|
|
2504
2505
|
};
|
|
@@ -2554,7 +2555,9 @@ class AXPCreateEntityCommand {
|
|
|
2554
2555
|
const entityRef = await this.entityService.resolve(moduleName, entityName);
|
|
2555
2556
|
let chain = this.entityForm.entity(`${moduleName}.${entityName}`).create(data);
|
|
2556
2557
|
chain.actions((actions) => {
|
|
2557
|
-
actions
|
|
2558
|
+
actions
|
|
2559
|
+
.cancel('@general:actions.cancel.title')
|
|
2560
|
+
.submit('@general:actions.create.title', { shortcuts: ['Enter', 'ctrl+s'] });
|
|
2558
2561
|
});
|
|
2559
2562
|
if (excludeProperties && excludeProperties.length > 0) {
|
|
2560
2563
|
chain = chain.exclude(...excludeProperties);
|
|
@@ -2969,17 +2972,6 @@ var viewEntityDetails_command = /*#__PURE__*/Object.freeze({
|
|
|
2969
2972
|
});
|
|
2970
2973
|
|
|
2971
2974
|
//#region ---- Template resolution ----
|
|
2972
|
-
/**
|
|
2973
|
-
* Normalizes lookup/search display templates for row-based formatting.
|
|
2974
|
-
* Converts `context.eval('path')` expressions to `{{ path }}` and single braces to mustache form.
|
|
2975
|
-
*/
|
|
2976
|
-
function normalizeLookupDisplayTemplate(template) {
|
|
2977
|
-
const withContextEval = template.replace(/\{\{\s*context\.eval\(['"]([^'"]+)['"]\)\s*\}\}/g, '{{ $1 }}');
|
|
2978
|
-
if (withContextEval.includes('{{')) {
|
|
2979
|
-
return withContextEval;
|
|
2980
|
-
}
|
|
2981
|
-
return withContextEval.replace(/\{/g, '{{').replace(/\}/g, '}}');
|
|
2982
|
-
}
|
|
2983
2975
|
/**
|
|
2984
2976
|
* Resolves the display template for a lookup item.
|
|
2985
2977
|
* Priority: explicit `displayFormat` → entity `formats.lookup` (template) → entity `formats.searchResult.title`.
|
|
@@ -2988,18 +2980,18 @@ function normalizeLookupDisplayTemplate(template) {
|
|
|
2988
2980
|
function resolveLookupDisplayTemplate(entity, options) {
|
|
2989
2981
|
const explicit = options.displayFormat?.trim();
|
|
2990
2982
|
if (explicit) {
|
|
2991
|
-
return
|
|
2983
|
+
return normalizeEntityDisplayTemplate(explicit);
|
|
2992
2984
|
}
|
|
2993
2985
|
if (options.textField?.trim()) {
|
|
2994
2986
|
return undefined;
|
|
2995
2987
|
}
|
|
2996
2988
|
const lookupFormat = entity?.formats?.lookup?.trim();
|
|
2997
2989
|
if (lookupFormat && (lookupFormat.includes('{{') || lookupFormat.includes('{'))) {
|
|
2998
|
-
return
|
|
2990
|
+
return normalizeEntityDisplayTemplate(lookupFormat);
|
|
2999
2991
|
}
|
|
3000
2992
|
const searchTitle = entity?.formats?.searchResult?.title?.trim();
|
|
3001
2993
|
if (searchTitle) {
|
|
3002
|
-
return
|
|
2994
|
+
return normalizeEntityDisplayTemplate(searchTitle);
|
|
3003
2995
|
}
|
|
3004
2996
|
return undefined;
|
|
3005
2997
|
}
|
|
@@ -3040,7 +3032,7 @@ function formatLookupItemDisplay(item, entity, options, formatService, resolveMu
|
|
|
3040
3032
|
const formatted = formatService.format(template, 'string', item);
|
|
3041
3033
|
if (formatted) {
|
|
3042
3034
|
const text = String(formatted);
|
|
3043
|
-
if (!
|
|
3035
|
+
if (!isUnresolvedEntityDisplayTemplate(text)) {
|
|
3044
3036
|
return text;
|
|
3045
3037
|
}
|
|
3046
3038
|
}
|
|
@@ -3061,10 +3053,6 @@ function formatLookupItemDisplay(item, entity, options, formatService, resolveMu
|
|
|
3061
3053
|
}
|
|
3062
3054
|
return String(raw);
|
|
3063
3055
|
}
|
|
3064
|
-
/** True when a formatted label still contains unresolved template markers. */
|
|
3065
|
-
function isUnresolvedLookupDisplayTemplate(value) {
|
|
3066
|
-
return /\{\{/.test(value) || /context\.eval\s*\(/.test(value);
|
|
3067
|
-
}
|
|
3068
3056
|
//#endregion
|
|
3069
3057
|
|
|
3070
3058
|
class AXPEntityDetailPopoverComponent {
|
|
@@ -3104,7 +3092,7 @@ class AXPEntityDetailPopoverComponent {
|
|
|
3104
3092
|
}
|
|
3105
3093
|
}
|
|
3106
3094
|
const preset = this.displayTitle()?.trim();
|
|
3107
|
-
if (preset && !
|
|
3095
|
+
if (preset && !isUnresolvedEntityDisplayTemplate(preset)) {
|
|
3108
3096
|
return preset;
|
|
3109
3097
|
}
|
|
3110
3098
|
const fallbackItem = this.item();
|
|
@@ -3203,7 +3191,7 @@ class AXPEntityDetailPopoverComponent {
|
|
|
3203
3191
|
resolveLookupDisplayLabel(item, entityDefinition) {
|
|
3204
3192
|
const label = formatLookupItemDisplay(item, entityDefinition, { textField: this.textField() }, this.formatService, (value) => this.translation.resolve(value));
|
|
3205
3193
|
const resolved = typeof label === 'string' ? label : this.translation.resolve(label);
|
|
3206
|
-
return resolved && !
|
|
3194
|
+
return resolved && !isUnresolvedEntityDisplayTemplate(resolved) ? resolved : '';
|
|
3207
3195
|
}
|
|
3208
3196
|
/**
|
|
3209
3197
|
* Returns true if a value is meaningful for display (non-empty/non-null).
|
|
@@ -3382,11 +3370,11 @@ class AXPEntityDetailPopoverComponent {
|
|
|
3382
3370
|
return prop.name;
|
|
3383
3371
|
}
|
|
3384
3372
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPEntityDetailPopoverComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
3385
|
-
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 } }, viewQueries: [{ propertyName: "detailPopover", first: true, predicate: ["detailPopover"], descendants: true, isSignal: true }], ngImport: i0, template: "<ax-popover [openOn]=\"'manual'\" #detailPopover (openChange)=\"onDetailPopoverOpenChange($event)\">\n <div class=\"ax-lightest-surface ax-p-4 ax-w-[min(600px,calc(100vw-2rem))]\">\n <div class=\"ax-mb-4 ax-border-b ax-pb-2\">\n <h3 class=\"ax-text-base ax-font-semibold ax-text-on-lightest-surface\">\n {{ headerTitle() }}\n </h3>\n @if (breadcrumb()) {\n <div class=\"ax-text-xs ax-text-neutral-500 ax-mt-1\">{{ breadcrumb() }}</div>\n }\n </div>\n @if (isLoadingDetails()) {\n <div class=\"ax-space-y-2 ax-mb-4\">\n @for (row of loadingSkeletonRows; track row) {\n <div class=\"ax-grid ax-grid-cols-[minmax(7rem,35%)_1fr] ax-gap-x-4 ax-items-start\">\n <ax-skeleton [animated]=\"true\" class=\"ax-h-4 ax-w-2/3 ax-rounded\"></ax-skeleton>\n @if (row === 2) {\n <ax-skeleton [animated]=\"true\" class=\"ax-h-16 ax-w-full ax-rounded\"></ax-skeleton>\n } @else {\n <ax-skeleton [animated]=\"true\" class=\"ax-h-4 ax-w-full ax-rounded\"></ax-skeleton>\n }\n </div>\n }\n </div>\n <div class=\"ax-flex ax-justify-end\">\n <ax-skeleton [animated]=\"true\" class=\"ax-h-9 ax-w-28 ax-rounded\"></ax-skeleton>\n </div>\n } @else if (entityDetails()) {\n <div class=\"ax-space-y-3 ax-mb-4\">\n <!-- Important Entity Data -->\n @if (entityDetails()?.entityData) {\n <axp-widgets-container [context]=\"entityDetails()?.entityData\">\n <div class=\"ax-space-y-2\">\n @for (item of entityPropertiesWithWidgets(); track item.name) {\n <div class=\"ax-grid ax-grid-cols-[minmax(7rem,35%)_1fr] ax-gap-x-4 ax-gap-y-1 ax-items-start\">\n <span class=\"ax-text-sm ax-font-medium
|
|
3373
|
+
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 } }, viewQueries: [{ propertyName: "detailPopover", first: true, predicate: ["detailPopover"], descendants: true, isSignal: true }], ngImport: i0, template: "<ax-popover [openOn]=\"'manual'\" #detailPopover (openChange)=\"onDetailPopoverOpenChange($event)\">\n <div class=\"ax-lightest-surface dark:ax-dark-surface ax-p-4 ax-w-[min(600px,calc(100vw-2rem))]\">\n <div class=\"ax-mb-4 ax-border-b ax-pb-2\">\n <h3 class=\"ax-text-base ax-font-semibold ax-text-on-lightest-surface\">\n {{ headerTitle() }}\n </h3>\n @if (breadcrumb()) {\n <div class=\"ax-text-xs ax-text-neutral-500 ax-mt-1\">{{ breadcrumb() }}</div>\n }\n </div>\n @if (isLoadingDetails()) {\n <div class=\"ax-space-y-2 ax-mb-4\">\n @for (row of loadingSkeletonRows; track row) {\n <div class=\"ax-grid ax-grid-cols-[minmax(7rem,35%)_1fr] ax-gap-x-4 ax-items-start\">\n <ax-skeleton [animated]=\"true\" class=\"ax-h-4 ax-w-2/3 ax-rounded\"></ax-skeleton>\n @if (row === 2) {\n <ax-skeleton [animated]=\"true\" class=\"ax-h-16 ax-w-full ax-rounded\"></ax-skeleton>\n } @else {\n <ax-skeleton [animated]=\"true\" class=\"ax-h-4 ax-w-full ax-rounded\"></ax-skeleton>\n }\n </div>\n }\n </div>\n <div class=\"ax-flex ax-justify-end\">\n <ax-skeleton [animated]=\"true\" class=\"ax-h-9 ax-w-28 ax-rounded\"></ax-skeleton>\n </div>\n } @else if (entityDetails()) {\n <div class=\"ax-space-y-3 ax-mb-4\">\n <!-- Important Entity Data -->\n @if (entityDetails()?.entityData) {\n <axp-widgets-container [context]=\"entityDetails()?.entityData\">\n <div class=\"ax-space-y-2\">\n @for (item of entityPropertiesWithWidgets(); track item.name) {\n <div class=\"ax-grid ax-grid-cols-[minmax(7rem,35%)_1fr] ax-gap-x-4 ax-gap-y-1 ax-items-start\">\n <span class=\"ax-text-sm ax-font-medium ax-shrink-0\">{{ item.title | translate | async\n }}:</span>\n <div class=\"ax-min-w-0 ax-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=\"ax-flex ax-gap-2 ax-justify-end ax-sm\">\n <ax-button [color]=\"'primary'\" [look]=\"'solid'\" [text]=\"'@general:actions.open-details.title' | translate | async\"\n (click)=\"navigateToDetails()\">\n </ax-button>\n </div>\n }\n </div>\n</ax-popover>", 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: AXPopoverModule }, { kind: "component", type: i2.AXPopoverComponent, selector: "ax-popover", inputs: ["width", "disablePanelClass", "disabled", "offsetX", "offsetY", "target", "placement", "content", "openOn", "closeOn", "hasBackdrop", "openAfter", "closeAfter", "closeOnScroll", "backdropClass", "panelClass", "adaptivityEnabled"], outputs: ["onOpened", "onClosed"] }, { kind: "ngmodule", type: AXPWidgetCoreModule }, { kind: "component", type: i3.AXPWidgetContainerComponent, selector: "axp-widgets-container", inputs: ["context", "functions"], outputs: ["onContextChanged"] }, { kind: "directive", type: i3.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: i4.AXSkeletonComponent, selector: "ax-skeleton", inputs: ["animated"] }, { kind: "pipe", type: i5.AsyncPipe, name: "async" }, { kind: "pipe", type: i6.AXTranslatorPipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
3386
3374
|
}
|
|
3387
3375
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPEntityDetailPopoverComponent, decorators: [{
|
|
3388
3376
|
type: Component,
|
|
3389
|
-
args: [{ selector: 'axp-entity-detail-popover', changeDetection: ChangeDetectionStrategy.OnPush, imports: [CommonModule, AXButtonModule, AXPopoverModule, AXPWidgetCoreModule, AXTranslationModule, AXSkeletonModule], template: "<ax-popover [openOn]=\"'manual'\" #detailPopover (openChange)=\"onDetailPopoverOpenChange($event)\">\n <div class=\"ax-lightest-surface ax-p-4 ax-w-[min(600px,calc(100vw-2rem))]\">\n <div class=\"ax-mb-4 ax-border-b ax-pb-2\">\n <h3 class=\"ax-text-base ax-font-semibold ax-text-on-lightest-surface\">\n {{ headerTitle() }}\n </h3>\n @if (breadcrumb()) {\n <div class=\"ax-text-xs ax-text-neutral-500 ax-mt-1\">{{ breadcrumb() }}</div>\n }\n </div>\n @if (isLoadingDetails()) {\n <div class=\"ax-space-y-2 ax-mb-4\">\n @for (row of loadingSkeletonRows; track row) {\n <div class=\"ax-grid ax-grid-cols-[minmax(7rem,35%)_1fr] ax-gap-x-4 ax-items-start\">\n <ax-skeleton [animated]=\"true\" class=\"ax-h-4 ax-w-2/3 ax-rounded\"></ax-skeleton>\n @if (row === 2) {\n <ax-skeleton [animated]=\"true\" class=\"ax-h-16 ax-w-full ax-rounded\"></ax-skeleton>\n } @else {\n <ax-skeleton [animated]=\"true\" class=\"ax-h-4 ax-w-full ax-rounded\"></ax-skeleton>\n }\n </div>\n }\n </div>\n <div class=\"ax-flex ax-justify-end\">\n <ax-skeleton [animated]=\"true\" class=\"ax-h-9 ax-w-28 ax-rounded\"></ax-skeleton>\n </div>\n } @else if (entityDetails()) {\n <div class=\"ax-space-y-3 ax-mb-4\">\n <!-- Important Entity Data -->\n @if (entityDetails()?.entityData) {\n <axp-widgets-container [context]=\"entityDetails()?.entityData\">\n <div class=\"ax-space-y-2\">\n @for (item of entityPropertiesWithWidgets(); track item.name) {\n <div class=\"ax-grid ax-grid-cols-[minmax(7rem,35%)_1fr] ax-gap-x-4 ax-gap-y-1 ax-items-start\">\n <span class=\"ax-text-sm ax-font-medium
|
|
3377
|
+
args: [{ selector: 'axp-entity-detail-popover', changeDetection: ChangeDetectionStrategy.OnPush, imports: [CommonModule, AXButtonModule, AXPopoverModule, AXPWidgetCoreModule, AXTranslationModule, AXSkeletonModule], template: "<ax-popover [openOn]=\"'manual'\" #detailPopover (openChange)=\"onDetailPopoverOpenChange($event)\">\n <div class=\"ax-lightest-surface dark:ax-dark-surface ax-p-4 ax-w-[min(600px,calc(100vw-2rem))]\">\n <div class=\"ax-mb-4 ax-border-b ax-pb-2\">\n <h3 class=\"ax-text-base ax-font-semibold ax-text-on-lightest-surface\">\n {{ headerTitle() }}\n </h3>\n @if (breadcrumb()) {\n <div class=\"ax-text-xs ax-text-neutral-500 ax-mt-1\">{{ breadcrumb() }}</div>\n }\n </div>\n @if (isLoadingDetails()) {\n <div class=\"ax-space-y-2 ax-mb-4\">\n @for (row of loadingSkeletonRows; track row) {\n <div class=\"ax-grid ax-grid-cols-[minmax(7rem,35%)_1fr] ax-gap-x-4 ax-items-start\">\n <ax-skeleton [animated]=\"true\" class=\"ax-h-4 ax-w-2/3 ax-rounded\"></ax-skeleton>\n @if (row === 2) {\n <ax-skeleton [animated]=\"true\" class=\"ax-h-16 ax-w-full ax-rounded\"></ax-skeleton>\n } @else {\n <ax-skeleton [animated]=\"true\" class=\"ax-h-4 ax-w-full ax-rounded\"></ax-skeleton>\n }\n </div>\n }\n </div>\n <div class=\"ax-flex ax-justify-end\">\n <ax-skeleton [animated]=\"true\" class=\"ax-h-9 ax-w-28 ax-rounded\"></ax-skeleton>\n </div>\n } @else if (entityDetails()) {\n <div class=\"ax-space-y-3 ax-mb-4\">\n <!-- Important Entity Data -->\n @if (entityDetails()?.entityData) {\n <axp-widgets-container [context]=\"entityDetails()?.entityData\">\n <div class=\"ax-space-y-2\">\n @for (item of entityPropertiesWithWidgets(); track item.name) {\n <div class=\"ax-grid ax-grid-cols-[minmax(7rem,35%)_1fr] ax-gap-x-4 ax-gap-y-1 ax-items-start\">\n <span class=\"ax-text-sm ax-font-medium ax-shrink-0\">{{ item.title | translate | async\n }}:</span>\n <div class=\"ax-min-w-0 ax-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=\"ax-flex ax-gap-2 ax-justify-end ax-sm\">\n <ax-button [color]=\"'primary'\" [look]=\"'solid'\" [text]=\"'@general:actions.open-details.title' | translate | async\"\n (click)=\"navigateToDetails()\">\n </ax-button>\n </div>\n }\n </div>\n</ax-popover>" }]
|
|
3390
3378
|
}], 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 }] }], detailPopover: [{ type: i0.ViewChild, args: ['detailPopover', { isSignal: true }] }] } });
|
|
3391
3379
|
|
|
3392
3380
|
class AXPEntityDetailPopoverService {
|
|
@@ -3624,6 +3612,58 @@ class AXMEntityCrudServiceImpl {
|
|
|
3624
3612
|
async custom(request) { }
|
|
3625
3613
|
}
|
|
3626
3614
|
|
|
3615
|
+
/**
|
|
3616
|
+
* Error type that can be thrown by middlewares to abort the chain with a structured payload.
|
|
3617
|
+
*/
|
|
3618
|
+
class AXPMiddlewareAbortError extends Error {
|
|
3619
|
+
constructor(message, options = {}) {
|
|
3620
|
+
super(message);
|
|
3621
|
+
this.message = message;
|
|
3622
|
+
this.options = options;
|
|
3623
|
+
this.name = 'AXPMiddlewareAbortError';
|
|
3624
|
+
}
|
|
3625
|
+
toResponse() {
|
|
3626
|
+
return {
|
|
3627
|
+
success: false,
|
|
3628
|
+
error: {
|
|
3629
|
+
code: this.options.code,
|
|
3630
|
+
message: this.message,
|
|
3631
|
+
status: this.options.status,
|
|
3632
|
+
details: this.options.details,
|
|
3633
|
+
},
|
|
3634
|
+
};
|
|
3635
|
+
}
|
|
3636
|
+
}
|
|
3637
|
+
/** Type guard for AXPMiddlewareAbortError */
|
|
3638
|
+
function isAXPMiddlewareAbortError(error) {
|
|
3639
|
+
return error instanceof AXPMiddlewareAbortError;
|
|
3640
|
+
}
|
|
3641
|
+
//#endregion
|
|
3642
|
+
|
|
3643
|
+
/**
|
|
3644
|
+
* Maps storage middleware failures to a command result the details view can surface.
|
|
3645
|
+
*/
|
|
3646
|
+
function mapEntityStorageErrorToCommandResult(error, fallback = 'Failed to update entity') {
|
|
3647
|
+
if (isAXPMiddlewareAbortError(error)) {
|
|
3648
|
+
return {
|
|
3649
|
+
success: false,
|
|
3650
|
+
message: {
|
|
3651
|
+
type: 'error',
|
|
3652
|
+
code: error.options.code,
|
|
3653
|
+
text: error.message,
|
|
3654
|
+
details: error.options.details,
|
|
3655
|
+
},
|
|
3656
|
+
};
|
|
3657
|
+
}
|
|
3658
|
+
const text = error instanceof Error ? error.message : typeof error === 'string' ? error : fallback;
|
|
3659
|
+
return {
|
|
3660
|
+
success: false,
|
|
3661
|
+
message: {
|
|
3662
|
+
type: 'error',
|
|
3663
|
+
text,
|
|
3664
|
+
},
|
|
3665
|
+
};
|
|
3666
|
+
}
|
|
3627
3667
|
/**
|
|
3628
3668
|
* Best-effort plain string for thrown {@link Error} messages from command results (no i18n lookup).
|
|
3629
3669
|
*/
|
|
@@ -4301,6 +4341,7 @@ class AXPEntityCommandTriggerViewModel {
|
|
|
4301
4341
|
this.hidden = action.hidden ?? false;
|
|
4302
4342
|
this.disabled = action.disabled ?? false;
|
|
4303
4343
|
this.default = action.default ?? false;
|
|
4344
|
+
this.shortcuts = action.shortcuts;
|
|
4304
4345
|
this.scope = action.scope;
|
|
4305
4346
|
this.order = action.order ?? 0;
|
|
4306
4347
|
this.isChild = isChild;
|
|
@@ -6587,20 +6628,28 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
6587
6628
|
type: Injectable,
|
|
6588
6629
|
args: [{ providedIn: 'root' }]
|
|
6589
6630
|
}] });
|
|
6590
|
-
const AXPEntityListViewModelResolver = async (route, state, service = inject(AXPEntityListViewModelFactory)) => {
|
|
6631
|
+
const AXPEntityListViewModelResolver = async (route, state, service = inject(AXPEntityListViewModelFactory), router = inject(Router)) => {
|
|
6591
6632
|
const moduleName = route.parent?.paramMap.get('module');
|
|
6592
6633
|
const entityName = route.paramMap.get('entity');
|
|
6593
|
-
|
|
6594
|
-
|
|
6595
|
-
|
|
6596
|
-
|
|
6597
|
-
|
|
6598
|
-
|
|
6599
|
-
|
|
6600
|
-
|
|
6634
|
+
try {
|
|
6635
|
+
const vm = await service.create(moduleName, entityName);
|
|
6636
|
+
// Check if filters are provided in query params
|
|
6637
|
+
const filtersParam = route.queryParamMap.get('filters');
|
|
6638
|
+
if (filtersParam) {
|
|
6639
|
+
const applied = vm.applyFiltersFromQueryParams(filtersParam);
|
|
6640
|
+
if (applied) {
|
|
6641
|
+
// Trigger filter and sort application
|
|
6642
|
+
await vm.applyFilterAndSort();
|
|
6643
|
+
}
|
|
6644
|
+
}
|
|
6645
|
+
return vm;
|
|
6646
|
+
}
|
|
6647
|
+
catch (error) {
|
|
6648
|
+
if (error instanceof AXPNotFoundError) {
|
|
6649
|
+
return axpRedirectToNotFound(router);
|
|
6601
6650
|
}
|
|
6651
|
+
throw error;
|
|
6602
6652
|
}
|
|
6603
|
-
return vm;
|
|
6604
6653
|
};
|
|
6605
6654
|
|
|
6606
6655
|
const AXPEntityDeletedEvent = createWorkFlowEvent('[Entity] Deleted');
|
|
@@ -6651,7 +6700,7 @@ class AXPEntityPerformDeleteAction extends AXPWorkflowAction {
|
|
|
6651
6700
|
dialog = this.loadingDialog.show({
|
|
6652
6701
|
title: await this.translationService.translateAsync('@general:workflow.deleting'),
|
|
6653
6702
|
mode: 'determinate',
|
|
6654
|
-
status: '
|
|
6703
|
+
status: await this.translationService.translateAsync('@general:workflow.deleting'),
|
|
6655
6704
|
progressValue: 0,
|
|
6656
6705
|
text: `0/${idLength}`,
|
|
6657
6706
|
buttons: [
|
|
@@ -7412,10 +7461,19 @@ class AXPEntityPreloadFiltersViewModel {
|
|
|
7412
7461
|
const AXPEntityPreloadFiltersViewModelResolver = async (route, state) => {
|
|
7413
7462
|
const injector = inject(Injector);
|
|
7414
7463
|
const entityRegistry = inject(AXPEntityDefinitionRegistryService);
|
|
7464
|
+
const router = inject(Router);
|
|
7415
7465
|
const moduleName = route.parent?.paramMap.get('module');
|
|
7416
7466
|
const entityName = route.paramMap.get('entity');
|
|
7417
|
-
|
|
7418
|
-
|
|
7467
|
+
try {
|
|
7468
|
+
const entity = await entityRegistry.resolve(moduleName, entityName);
|
|
7469
|
+
return new AXPEntityPreloadFiltersViewModel(injector, entity);
|
|
7470
|
+
}
|
|
7471
|
+
catch (error) {
|
|
7472
|
+
if (error instanceof AXPNotFoundError) {
|
|
7473
|
+
return axpRedirectToNotFound(router);
|
|
7474
|
+
}
|
|
7475
|
+
throw error;
|
|
7476
|
+
}
|
|
7419
7477
|
};
|
|
7420
7478
|
//#endregion
|
|
7421
7479
|
|
|
@@ -7980,7 +8038,7 @@ class AXMAttachmentsPageComponentProvider {
|
|
|
7980
8038
|
return [
|
|
7981
8039
|
{
|
|
7982
8040
|
key: ATTACHMENTS_PAGE_COMPONENT_KEY,
|
|
7983
|
-
loader: () => import('./acorex-platform-layout-entity-attachments-page.component-
|
|
8041
|
+
loader: () => import('./acorex-platform-layout-entity-attachments-page.component-B0EkdqvH.mjs').then((m) => m.AXMAttachmentsPageComponent),
|
|
7984
8042
|
},
|
|
7985
8043
|
];
|
|
7986
8044
|
}
|
|
@@ -8299,7 +8357,7 @@ class AXPEntityCategoryTreeSelectorComponent extends AXBasePageComponent {
|
|
|
8299
8357
|
this.noRecordsTitle = signal('', ...(ngDevMode ? [{ debugName: "noRecordsTitle" }] : /* istanbul ignore next */ []));
|
|
8300
8358
|
this.currentSearchTerm = null;
|
|
8301
8359
|
this.isRefreshing = false;
|
|
8302
|
-
this.isUpdatingSelection = false; // Flag to prevent recursive updates during batch operations
|
|
8360
|
+
this.isUpdatingSelection = false; // Flag to prevent recursive updates during batch operations (multiple mode)
|
|
8303
8361
|
/**
|
|
8304
8362
|
* Computed property to check if we should show the "no search results" empty state.
|
|
8305
8363
|
* Returns true when search is active, not searching, and no results found.
|
|
@@ -8366,9 +8424,8 @@ class AXPEntityCategoryTreeSelectorComponent extends AXBasePageComponent {
|
|
|
8366
8424
|
}
|
|
8367
8425
|
});
|
|
8368
8426
|
}
|
|
8369
|
-
// Mark pre-selected nodes
|
|
8370
|
-
|
|
8371
|
-
if (!this.isUpdatingSelection) {
|
|
8427
|
+
// Mark pre-selected leaf nodes on loaded children (multiple mode only; single uses controlledSelection)
|
|
8428
|
+
if (!this.isUpdatingSelection && this.allowMultiple()) {
|
|
8372
8429
|
const selectedIds = this.selectedNodeIds();
|
|
8373
8430
|
childNodes.forEach((node) => {
|
|
8374
8431
|
const nodeId = String(node['id'] ?? '');
|
|
@@ -8376,38 +8433,6 @@ class AXPEntityCategoryTreeSelectorComponent extends AXBasePageComponent {
|
|
|
8376
8433
|
node['selected'] = true;
|
|
8377
8434
|
}
|
|
8378
8435
|
});
|
|
8379
|
-
// After children load, programmatically select pre-selected nodes in tree component
|
|
8380
|
-
// This ensures the tree's internal state matches our selectedNodeIds
|
|
8381
|
-
// Skip this if we're in the middle of a selection update (user is selecting/deselecting)
|
|
8382
|
-
const treeComponent = this.tree();
|
|
8383
|
-
if (treeComponent && selectedIds.length > 0) {
|
|
8384
|
-
// Use setTimeout to ensure nodes are in tree structure before selecting
|
|
8385
|
-
setTimeout(() => {
|
|
8386
|
-
// Double-check we're not in a selection update when timeout fires
|
|
8387
|
-
if (this.isUpdatingSelection) {
|
|
8388
|
-
return;
|
|
8389
|
-
}
|
|
8390
|
-
this.isUpdatingSelection = true;
|
|
8391
|
-
try {
|
|
8392
|
-
// Re-check selectedNodeIds at this point (might have changed)
|
|
8393
|
-
const currentSelectedIds = this.selectedNodeIds();
|
|
8394
|
-
childNodes.forEach((node) => {
|
|
8395
|
-
const nodeId = String(node['id'] ?? '');
|
|
8396
|
-
if (nodeId && currentSelectedIds.includes(nodeId) && nodeId !== 'all') {
|
|
8397
|
-
try {
|
|
8398
|
-
treeComponent.selectNode(nodeId);
|
|
8399
|
-
}
|
|
8400
|
-
catch {
|
|
8401
|
-
// Node might not be in tree yet
|
|
8402
|
-
}
|
|
8403
|
-
}
|
|
8404
|
-
});
|
|
8405
|
-
}
|
|
8406
|
-
finally {
|
|
8407
|
-
this.isUpdatingSelection = false;
|
|
8408
|
-
}
|
|
8409
|
-
}, 10);
|
|
8410
|
-
}
|
|
8411
8436
|
}
|
|
8412
8437
|
return childNodes;
|
|
8413
8438
|
};
|
|
@@ -8433,6 +8458,60 @@ class AXPEntityCategoryTreeSelectorComponent extends AXBasePageComponent {
|
|
|
8433
8458
|
}
|
|
8434
8459
|
//#endregion
|
|
8435
8460
|
//#region ---- Private Methods ----
|
|
8461
|
+
/** Keeps at most one id when {@link allowMultiple} is false. */
|
|
8462
|
+
normalizeSelectedIds(ids) {
|
|
8463
|
+
const filtered = ids.filter((id) => id && id !== 'all');
|
|
8464
|
+
if (this.allowMultiple()) {
|
|
8465
|
+
return [...filtered];
|
|
8466
|
+
}
|
|
8467
|
+
return filtered.length > 0 ? [filtered[filtered.length - 1]] : [];
|
|
8468
|
+
}
|
|
8469
|
+
setSelectedNodeIds(ids) {
|
|
8470
|
+
this.selectedNodeIds.set(this.normalizeSelectedIds(ids));
|
|
8471
|
+
}
|
|
8472
|
+
/** Keeps widget selectedNodeIds in sync when the tree uses controlled single selection. */
|
|
8473
|
+
onControlledSelectedIdsChange(ids) {
|
|
8474
|
+
if (this.allowMultiple()) {
|
|
8475
|
+
return;
|
|
8476
|
+
}
|
|
8477
|
+
this.setSelectedNodeIds(ids);
|
|
8478
|
+
}
|
|
8479
|
+
async waitForTreeComponent(maxWaitMs = 3500) {
|
|
8480
|
+
const deadline = Date.now() + maxWaitMs;
|
|
8481
|
+
while (Date.now() < deadline) {
|
|
8482
|
+
const treeComponent = this.tree();
|
|
8483
|
+
if (treeComponent) {
|
|
8484
|
+
return treeComponent;
|
|
8485
|
+
}
|
|
8486
|
+
await new Promise((resolve) => setTimeout(resolve, 80));
|
|
8487
|
+
}
|
|
8488
|
+
return undefined;
|
|
8489
|
+
}
|
|
8490
|
+
/** Expands ancestor paths and reveals the initial selection via the tree API. */
|
|
8491
|
+
async revealInitialSelection(ids) {
|
|
8492
|
+
const normalizedIds = this.normalizeSelectedIds(ids);
|
|
8493
|
+
if (normalizedIds.length === 0) {
|
|
8494
|
+
return;
|
|
8495
|
+
}
|
|
8496
|
+
const treeComponent = await this.waitForTreeComponent();
|
|
8497
|
+
if (!treeComponent) {
|
|
8498
|
+
return;
|
|
8499
|
+
}
|
|
8500
|
+
try {
|
|
8501
|
+
if (this.allowMultiple()) {
|
|
8502
|
+
await this.restoreMultipleSelectionAfterReload(normalizedIds);
|
|
8503
|
+
}
|
|
8504
|
+
else {
|
|
8505
|
+
const chains = await this.buildAncestorChains(normalizedIds);
|
|
8506
|
+
const ancestorPaths = normalizedIds.map((id) => chains.get(id) ?? []);
|
|
8507
|
+
await treeComponent.selectAndReveal(normalizedIds, { ancestorPaths });
|
|
8508
|
+
}
|
|
8509
|
+
}
|
|
8510
|
+
catch (error) {
|
|
8511
|
+
console.error('Error revealing initial category selection:', error);
|
|
8512
|
+
}
|
|
8513
|
+
this.changeDetectorRef.markForCheck();
|
|
8514
|
+
}
|
|
8436
8515
|
async initializeTree() {
|
|
8437
8516
|
if (!this.entityKey()) {
|
|
8438
8517
|
this.loading.set(false);
|
|
@@ -8457,12 +8536,12 @@ class AXPEntityCategoryTreeSelectorComponent extends AXBasePageComponent {
|
|
|
8457
8536
|
// Resolve initial selected IDs from data (popup) or input binding
|
|
8458
8537
|
const fromData = this.selectedValues().filter((id) => id && id !== 'all');
|
|
8459
8538
|
const fromInput = (this.selectedValuesInput() ?? []).filter((id) => id && id !== 'all');
|
|
8460
|
-
const initialSelectedIds = fromData.length > 0 ? fromData : fromInput;
|
|
8539
|
+
const initialSelectedIds = this.normalizeSelectedIds(fromData.length > 0 ? fromData : fromInput);
|
|
8461
8540
|
if (initialSelectedIds.length > 0) {
|
|
8462
8541
|
await this.loadMissingNodeData(initialSelectedIds);
|
|
8463
8542
|
// Re-fetch any node that has no parent info (batch query may return minimal fields)
|
|
8464
8543
|
await this.ensureParentDataInCache(initialSelectedIds);
|
|
8465
|
-
this.
|
|
8544
|
+
this.setSelectedNodeIds(initialSelectedIds);
|
|
8466
8545
|
}
|
|
8467
8546
|
}
|
|
8468
8547
|
catch (error) {
|
|
@@ -8476,52 +8555,20 @@ class AXPEntityCategoryTreeSelectorComponent extends AXBasePageComponent {
|
|
|
8476
8555
|
const selectedIds = this.selectedNodeIds();
|
|
8477
8556
|
if (selectedIds.length > 0) {
|
|
8478
8557
|
this.initialExpandSyncDone = true;
|
|
8479
|
-
this.
|
|
8558
|
+
void this.revealInitialSelection(selectedIds);
|
|
8480
8559
|
}
|
|
8481
8560
|
}
|
|
8482
8561
|
/**
|
|
8483
8562
|
* Called when popup data arrives after ngOnInit: load data, set selection, expand and sync.
|
|
8484
8563
|
*/
|
|
8485
8564
|
runInitialExpandAndSync(ids) {
|
|
8486
|
-
this.
|
|
8565
|
+
this.setSelectedNodeIds(ids);
|
|
8487
8566
|
Promise.resolve()
|
|
8488
8567
|
.then(() => this.loadMissingNodeData(ids))
|
|
8489
8568
|
.then(() => this.ensureParentDataInCache(ids))
|
|
8490
|
-
.then(() =>
|
|
8491
|
-
this.runExpandAndSyncWhenTreeReady(ids);
|
|
8492
|
-
})
|
|
8569
|
+
.then(() => this.revealInitialSelection(ids))
|
|
8493
8570
|
.catch((err) => console.error('Error in runInitialExpandAndSync:', err));
|
|
8494
8571
|
}
|
|
8495
|
-
/**
|
|
8496
|
-
* Runs expand path + sync selection once the tree viewChild is available.
|
|
8497
|
-
* Uses retry loop so we don't run before the view has rendered the tree.
|
|
8498
|
-
*/
|
|
8499
|
-
runExpandAndSyncWhenTreeReady(selectedIds) {
|
|
8500
|
-
const maxWaitMs = 3500;
|
|
8501
|
-
const pollMs = 80;
|
|
8502
|
-
const start = Date.now();
|
|
8503
|
-
const run = async () => {
|
|
8504
|
-
while (Date.now() - start < maxWaitMs) {
|
|
8505
|
-
const treeComponent = this.tree();
|
|
8506
|
-
if (treeComponent) {
|
|
8507
|
-
try {
|
|
8508
|
-
await new Promise((resolve) => setTimeout(resolve, 320));
|
|
8509
|
-
const ancestorChains = await this.buildAncestorChains(selectedIds);
|
|
8510
|
-
await this.expandAncestorNodesInOrder(ancestorChains);
|
|
8511
|
-
await new Promise((resolve) => setTimeout(resolve, 120));
|
|
8512
|
-
await this.syncSelectionWithTree(selectedIds);
|
|
8513
|
-
}
|
|
8514
|
-
catch (error) {
|
|
8515
|
-
console.error('Error syncing selection after tree render:', error);
|
|
8516
|
-
}
|
|
8517
|
-
this.changeDetectorRef.markForCheck();
|
|
8518
|
-
return;
|
|
8519
|
-
}
|
|
8520
|
-
await new Promise((resolve) => setTimeout(resolve, pollMs));
|
|
8521
|
-
}
|
|
8522
|
-
};
|
|
8523
|
-
run();
|
|
8524
|
-
}
|
|
8525
8572
|
//#endregion
|
|
8526
8573
|
//#region ---- Public Methods ----
|
|
8527
8574
|
/**
|
|
@@ -8926,8 +8973,7 @@ class AXPEntityCategoryTreeSelectorComponent extends AXBasePageComponent {
|
|
|
8926
8973
|
this.nodesExpandedDuringSearch = [];
|
|
8927
8974
|
return;
|
|
8928
8975
|
}
|
|
8929
|
-
|
|
8930
|
-
const selectedIds = this.selectedNodeIds();
|
|
8976
|
+
const expandTargetIds = [...this.selectedNodeIds()];
|
|
8931
8977
|
// Reload tree to show all nodes (no filtering)
|
|
8932
8978
|
await treeComponent.reloadData();
|
|
8933
8979
|
// Collapse nodes that were expanded during search (in reverse order - leaves first)
|
|
@@ -8947,39 +8993,38 @@ class AXPEntityCategoryTreeSelectorComponent extends AXBasePageComponent {
|
|
|
8947
8993
|
// Clear the stored expanded nodes
|
|
8948
8994
|
this.expandedNodesBeforeSearch = [];
|
|
8949
8995
|
// Restore selection state after tree reload
|
|
8950
|
-
if (
|
|
8951
|
-
|
|
8952
|
-
|
|
8953
|
-
|
|
8954
|
-
|
|
8996
|
+
if (expandTargetIds.length > 0) {
|
|
8997
|
+
if (!this.allowMultiple()) {
|
|
8998
|
+
await this.revealInitialSelection(expandTargetIds);
|
|
8999
|
+
}
|
|
9000
|
+
else {
|
|
9001
|
+
await this.restoreMultipleSelectionAfterReload(expandTargetIds);
|
|
9002
|
+
}
|
|
8955
9003
|
}
|
|
8956
9004
|
}
|
|
8957
9005
|
/**
|
|
8958
|
-
* Restores selection
|
|
8959
|
-
* Expands ancestor nodes and selects the previously selected leaf nodes.
|
|
9006
|
+
* Restores multiple-mode leaf selection after search reset.
|
|
8960
9007
|
*/
|
|
8961
|
-
async
|
|
9008
|
+
async restoreMultipleSelectionAfterReload(selectedLeafIds) {
|
|
8962
9009
|
const treeComponent = this.tree();
|
|
8963
|
-
|
|
9010
|
+
const normalizedIds = this.normalizeSelectedIds(selectedLeafIds);
|
|
9011
|
+
if (!treeComponent || normalizedIds.length === 0) {
|
|
8964
9012
|
return;
|
|
8965
9013
|
}
|
|
8966
9014
|
this.isUpdatingSelection = true;
|
|
8967
9015
|
try {
|
|
8968
|
-
|
|
8969
|
-
const ancestorChains = await this.buildAncestorChains(selectedIds);
|
|
8970
|
-
// Expand ancestor nodes to make selected nodes visible
|
|
9016
|
+
const ancestorChains = await this.buildAncestorChains(normalizedIds);
|
|
8971
9017
|
await this.expandAncestorNodesInOrder(ancestorChains);
|
|
8972
|
-
|
|
8973
|
-
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
8974
|
-
// Select the nodes visually in the tree
|
|
8975
|
-
for (const id of selectedIds) {
|
|
9018
|
+
for (const id of normalizedIds) {
|
|
8976
9019
|
try {
|
|
8977
9020
|
treeComponent.selectNode(id);
|
|
8978
9021
|
}
|
|
8979
9022
|
catch {
|
|
8980
|
-
// Node might not be
|
|
9023
|
+
// Node might not be loaded yet
|
|
8981
9024
|
}
|
|
8982
9025
|
}
|
|
9026
|
+
await this.updateParentStatesAfterSelection(normalizedIds);
|
|
9027
|
+
treeComponent.refresh();
|
|
8983
9028
|
}
|
|
8984
9029
|
finally {
|
|
8985
9030
|
this.isUpdatingSelection = false;
|
|
@@ -8996,10 +9041,6 @@ class AXPEntityCategoryTreeSelectorComponent extends AXBasePageComponent {
|
|
|
8996
9041
|
if (!event.isUserInteraction) {
|
|
8997
9042
|
return;
|
|
8998
9043
|
}
|
|
8999
|
-
// Don't process if we're already updating selection
|
|
9000
|
-
if (this.isUpdatingSelection) {
|
|
9001
|
-
return;
|
|
9002
|
-
}
|
|
9003
9044
|
// Cache node data for getSelectedItems (except for 'all' node)
|
|
9004
9045
|
if (nodeId !== 'all') {
|
|
9005
9046
|
const nodeData = node['data'];
|
|
@@ -9007,72 +9048,64 @@ class AXPEntityCategoryTreeSelectorComponent extends AXBasePageComponent {
|
|
|
9007
9048
|
this.nodeDataCache.set(nodeId, nodeData);
|
|
9008
9049
|
}
|
|
9009
9050
|
}
|
|
9010
|
-
//
|
|
9051
|
+
// Single selection is handled by the tree controlledSelection + selectedIds binding.
|
|
9052
|
+
if (!this.allowMultiple()) {
|
|
9053
|
+
return;
|
|
9054
|
+
}
|
|
9011
9055
|
const isSelected = node['selected'] === true;
|
|
9012
|
-
|
|
9013
|
-
|
|
9014
|
-
|
|
9015
|
-
|
|
9016
|
-
|
|
9017
|
-
|
|
9018
|
-
|
|
9019
|
-
|
|
9020
|
-
|
|
9021
|
-
|
|
9022
|
-
|
|
9023
|
-
|
|
9024
|
-
|
|
9025
|
-
|
|
9026
|
-
|
|
9027
|
-
|
|
9028
|
-
|
|
9029
|
-
|
|
9030
|
-
|
|
9056
|
+
const children = node['children'];
|
|
9057
|
+
const childrenCount = node['childrenCount'];
|
|
9058
|
+
// Determine if this node has children (is a parent node)
|
|
9059
|
+
// A node has children if: childrenCount > 0, or has non-empty children array
|
|
9060
|
+
// Note: childrenCount === undefined means "unknown" - we need to check via datasource
|
|
9061
|
+
const hasLoadedChildren = children && children.length > 0;
|
|
9062
|
+
const hasChildrenCount = childrenCount !== undefined && childrenCount > 0;
|
|
9063
|
+
const isDefinitelyLeaf = childrenCount === 0 && (!children || children.length === 0);
|
|
9064
|
+
if (isSelected) {
|
|
9065
|
+
// SELECTION: Only add LEAF nodes to selectedNodeIds
|
|
9066
|
+
this.isUpdatingSelection = true;
|
|
9067
|
+
try {
|
|
9068
|
+
if (nodeId === 'all') {
|
|
9069
|
+
// "All Items" selected - recursively select all leaf descendants
|
|
9070
|
+
await this.selectAllLeafDescendants(nodeId);
|
|
9071
|
+
}
|
|
9072
|
+
else if (isDefinitelyLeaf) {
|
|
9073
|
+
// This is definitely a leaf node - add it directly
|
|
9074
|
+
const currentSelected = new Set(this.selectedNodeIds());
|
|
9075
|
+
currentSelected.add(nodeId);
|
|
9076
|
+
this.setSelectedNodeIds(Array.from(currentSelected));
|
|
9077
|
+
}
|
|
9078
|
+
else if (hasLoadedChildren || hasChildrenCount) {
|
|
9079
|
+
// This node has children - only add its leaf descendants
|
|
9080
|
+
await this.selectAllLeafDescendants(nodeId);
|
|
9081
|
+
}
|
|
9082
|
+
else {
|
|
9083
|
+
// childrenCount is undefined - fetch once and reuse to avoid double datasource call
|
|
9084
|
+
const childNodes = await this.datasource(nodeId);
|
|
9085
|
+
if (!childNodes || childNodes.length === 0) {
|
|
9086
|
+
// No children - this is a leaf node, add it
|
|
9031
9087
|
const currentSelected = new Set(this.selectedNodeIds());
|
|
9032
9088
|
currentSelected.add(nodeId);
|
|
9033
|
-
this.
|
|
9034
|
-
}
|
|
9035
|
-
else if (hasLoadedChildren || hasChildrenCount) {
|
|
9036
|
-
// This node has children - only add its leaf descendants
|
|
9037
|
-
await this.selectAllLeafDescendants(nodeId);
|
|
9089
|
+
this.setSelectedNodeIds(Array.from(currentSelected));
|
|
9038
9090
|
}
|
|
9039
9091
|
else {
|
|
9040
|
-
//
|
|
9041
|
-
|
|
9042
|
-
if (!childNodes || childNodes.length === 0) {
|
|
9043
|
-
// No children - this is a leaf node, add it
|
|
9044
|
-
const currentSelected = new Set(this.selectedNodeIds());
|
|
9045
|
-
currentSelected.add(nodeId);
|
|
9046
|
-
this.selectedNodeIds.set(Array.from(currentSelected));
|
|
9047
|
-
}
|
|
9048
|
-
else {
|
|
9049
|
-
// Has children - pass prefetched childNodes to avoid datasource(nodeId) again
|
|
9050
|
-
await this.selectAllLeafDescendants(nodeId, childNodes);
|
|
9051
|
-
}
|
|
9092
|
+
// Has children - pass prefetched childNodes to avoid datasource(nodeId) again
|
|
9093
|
+
await this.selectAllLeafDescendants(nodeId, childNodes);
|
|
9052
9094
|
}
|
|
9053
9095
|
}
|
|
9054
|
-
finally {
|
|
9055
|
-
this.isUpdatingSelection = false;
|
|
9056
|
-
}
|
|
9057
9096
|
}
|
|
9058
|
-
|
|
9059
|
-
|
|
9060
|
-
this.isUpdatingSelection = true;
|
|
9061
|
-
try {
|
|
9062
|
-
await this.deselectAllLeafDescendants(nodeId);
|
|
9063
|
-
}
|
|
9064
|
-
finally {
|
|
9065
|
-
this.isUpdatingSelection = false;
|
|
9066
|
-
}
|
|
9097
|
+
finally {
|
|
9098
|
+
this.isUpdatingSelection = false;
|
|
9067
9099
|
}
|
|
9068
9100
|
}
|
|
9069
9101
|
else {
|
|
9070
|
-
//
|
|
9071
|
-
|
|
9072
|
-
|
|
9102
|
+
// DESELECTION: Remove node (if leaf) and all leaf descendants from selectedNodeIds
|
|
9103
|
+
this.isUpdatingSelection = true;
|
|
9104
|
+
try {
|
|
9105
|
+
await this.deselectAllLeafDescendants(nodeId);
|
|
9073
9106
|
}
|
|
9074
|
-
|
|
9075
|
-
this.
|
|
9107
|
+
finally {
|
|
9108
|
+
this.isUpdatingSelection = false;
|
|
9076
9109
|
}
|
|
9077
9110
|
}
|
|
9078
9111
|
}
|
|
@@ -9088,7 +9121,7 @@ class AXPEntityCategoryTreeSelectorComponent extends AXBasePageComponent {
|
|
|
9088
9121
|
* Clears all selected items
|
|
9089
9122
|
*/
|
|
9090
9123
|
onClearAll() {
|
|
9091
|
-
this.
|
|
9124
|
+
this.setSelectedNodeIds([]);
|
|
9092
9125
|
const treeComponent = this.tree();
|
|
9093
9126
|
if (treeComponent) {
|
|
9094
9127
|
treeComponent.deselectAll();
|
|
@@ -9139,7 +9172,7 @@ class AXPEntityCategoryTreeSelectorComponent extends AXBasePageComponent {
|
|
|
9139
9172
|
for (const leafId of leafIds) {
|
|
9140
9173
|
currentSelected.add(leafId);
|
|
9141
9174
|
}
|
|
9142
|
-
this.
|
|
9175
|
+
this.setSelectedNodeIds(Array.from(currentSelected));
|
|
9143
9176
|
// Select all leaf nodes visually in the tree
|
|
9144
9177
|
if (treeComponent) {
|
|
9145
9178
|
for (const leafId of leafIds) {
|
|
@@ -9168,7 +9201,7 @@ class AXPEntityCategoryTreeSelectorComponent extends AXBasePageComponent {
|
|
|
9168
9201
|
try {
|
|
9169
9202
|
// Special case: deselecting 'all' clears everything
|
|
9170
9203
|
if (parentId === 'all') {
|
|
9171
|
-
this.
|
|
9204
|
+
this.setSelectedNodeIds([]);
|
|
9172
9205
|
// Use tree's deselectAll() to clear all visual selections
|
|
9173
9206
|
const treeComponent = this.tree();
|
|
9174
9207
|
if (treeComponent) {
|
|
@@ -9189,7 +9222,7 @@ class AXPEntityCategoryTreeSelectorComponent extends AXBasePageComponent {
|
|
|
9189
9222
|
// Only update our internal state - tree handles visual state
|
|
9190
9223
|
const currentSelected = this.selectedNodeIds();
|
|
9191
9224
|
const newSelected = currentSelected.filter((id) => !leafIds.has(id));
|
|
9192
|
-
this.
|
|
9225
|
+
this.setSelectedNodeIds(newSelected);
|
|
9193
9226
|
}
|
|
9194
9227
|
catch (error) {
|
|
9195
9228
|
console.error(`Error deselecting leaf descendants for node ${parentId}:`, error);
|
|
@@ -9439,60 +9472,6 @@ class AXPEntityCategoryTreeSelectorComponent extends AXBasePageComponent {
|
|
|
9439
9472
|
await new Promise((r) => setTimeout(r, pollMs));
|
|
9440
9473
|
}
|
|
9441
9474
|
}
|
|
9442
|
-
/**
|
|
9443
|
-
* Syncs selection state with the tree component.
|
|
9444
|
-
* Selects leaf nodes and manually updates parent states (indeterminate/selected).
|
|
9445
|
-
*/
|
|
9446
|
-
async syncSelectionWithTree(selectedIds) {
|
|
9447
|
-
const treeComponent = this.tree();
|
|
9448
|
-
if (!treeComponent || selectedIds.length === 0) {
|
|
9449
|
-
return;
|
|
9450
|
-
}
|
|
9451
|
-
// Wait for tree to be fully initialized after ancestor expansion
|
|
9452
|
-
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
9453
|
-
// Keep track of successfully synced nodes
|
|
9454
|
-
const syncedNodes = new Set();
|
|
9455
|
-
// Try multiple times to sync selection (nodes might load progressively)
|
|
9456
|
-
for (let attempt = 0; attempt < 5; attempt++) {
|
|
9457
|
-
for (const id of selectedIds) {
|
|
9458
|
-
if (id && id !== 'all' && !syncedNodes.has(id)) {
|
|
9459
|
-
try {
|
|
9460
|
-
const node = treeComponent.findNode(id);
|
|
9461
|
-
if (node) {
|
|
9462
|
-
// Node exists in tree - mark it as selected using both methods
|
|
9463
|
-
// 1. Direct property assignment for immediate visual feedback
|
|
9464
|
-
node['selected'] = true;
|
|
9465
|
-
node['indeterminate'] = false;
|
|
9466
|
-
// 2. Use selectNode API for tree's internal state tracking
|
|
9467
|
-
try {
|
|
9468
|
-
treeComponent.selectNode(id);
|
|
9469
|
-
}
|
|
9470
|
-
catch {
|
|
9471
|
-
// selectNode might fail but direct assignment should work
|
|
9472
|
-
}
|
|
9473
|
-
syncedNodes.add(id);
|
|
9474
|
-
}
|
|
9475
|
-
}
|
|
9476
|
-
catch {
|
|
9477
|
-
// Node not in tree yet
|
|
9478
|
-
}
|
|
9479
|
-
}
|
|
9480
|
-
}
|
|
9481
|
-
// If all nodes are synced, we're done
|
|
9482
|
-
if (syncedNodes.size === selectedIds.length) {
|
|
9483
|
-
break;
|
|
9484
|
-
}
|
|
9485
|
-
// Wait before next attempt
|
|
9486
|
-
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
9487
|
-
}
|
|
9488
|
-
// Now update parent states (indeterminate/selected) based on children
|
|
9489
|
-
// This uses bottom-up traversal with datasource fallback for loading children
|
|
9490
|
-
await this.updateParentStatesAfterSelection(selectedIds);
|
|
9491
|
-
// Refresh tree to apply changes
|
|
9492
|
-
treeComponent.refresh();
|
|
9493
|
-
// Trigger change detection to update UI
|
|
9494
|
-
this.changeDetectorRef.markForCheck();
|
|
9495
|
-
}
|
|
9496
9475
|
/**
|
|
9497
9476
|
* Updates parent node states (selected/indeterminate) based on children selection.
|
|
9498
9477
|
* Called after leaf nodes are selected to properly show parent states.
|
|
@@ -9686,35 +9665,6 @@ class AXPEntityCategoryTreeSelectorComponent extends AXBasePageComponent {
|
|
|
9686
9665
|
const id = String(parentIdValue);
|
|
9687
9666
|
return id && id !== 'all' ? id : null;
|
|
9688
9667
|
}
|
|
9689
|
-
/**
|
|
9690
|
-
* Marks nodes as selected in the tree structure based on selectedNodeIds.
|
|
9691
|
-
* This ensures pre-selected nodes appear selected when the tree is rendered.
|
|
9692
|
-
*/
|
|
9693
|
-
markNodesAsSelected(node) {
|
|
9694
|
-
const selectedIds = this.selectedNodeIds();
|
|
9695
|
-
if (selectedIds.length === 0) {
|
|
9696
|
-
return;
|
|
9697
|
-
}
|
|
9698
|
-
// Mark the node itself if it's selected
|
|
9699
|
-
this.markNodeAsSelectedIfNeeded(node);
|
|
9700
|
-
// Recursively mark children
|
|
9701
|
-
const nodeChildren = node['children'];
|
|
9702
|
-
if (nodeChildren) {
|
|
9703
|
-
nodeChildren.forEach((child) => {
|
|
9704
|
-
this.markNodesAsSelected(child);
|
|
9705
|
-
});
|
|
9706
|
-
}
|
|
9707
|
-
}
|
|
9708
|
-
/**
|
|
9709
|
-
* Marks a single node as selected if it's in the selectedNodeIds list.
|
|
9710
|
-
*/
|
|
9711
|
-
markNodeAsSelectedIfNeeded(node) {
|
|
9712
|
-
const selectedIds = this.selectedNodeIds();
|
|
9713
|
-
const nodeId = String(node['id'] ?? '');
|
|
9714
|
-
if (nodeId && selectedIds.includes(nodeId)) {
|
|
9715
|
-
node['selected'] = true;
|
|
9716
|
-
}
|
|
9717
|
-
}
|
|
9718
9668
|
/**
|
|
9719
9669
|
* Marks a node and its children as disabled if the node ID matches the excluded ID.
|
|
9720
9670
|
*/
|
|
@@ -9723,7 +9673,6 @@ class AXPEntityCategoryTreeSelectorComponent extends AXBasePageComponent {
|
|
|
9723
9673
|
if (nodeId === excludedId) {
|
|
9724
9674
|
node['disabled'] = true;
|
|
9725
9675
|
}
|
|
9726
|
-
// Recursively mark children
|
|
9727
9676
|
const nodeChildren = node['children'];
|
|
9728
9677
|
if (nodeChildren) {
|
|
9729
9678
|
nodeChildren.forEach((child) => {
|
|
@@ -9732,41 +9681,16 @@ class AXPEntityCategoryTreeSelectorComponent extends AXBasePageComponent {
|
|
|
9732
9681
|
}
|
|
9733
9682
|
}
|
|
9734
9683
|
/**
|
|
9735
|
-
* Processes root node: marks excluded as disabled
|
|
9684
|
+
* Processes root node: marks excluded nodes as disabled.
|
|
9736
9685
|
*/
|
|
9737
9686
|
processRootNode(rootNode) {
|
|
9738
9687
|
const excludedId = this.excludedNodeId();
|
|
9739
9688
|
if (excludedId) {
|
|
9740
9689
|
this.markNodeAsDisabled(rootNode, excludedId);
|
|
9741
9690
|
}
|
|
9742
|
-
this.markNodesAsSelected(rootNode);
|
|
9743
|
-
// Sync selection with tree component after a short delay to ensure tree is rendered
|
|
9744
|
-
const treeComponent = this.tree();
|
|
9745
|
-
if (treeComponent) {
|
|
9746
|
-
setTimeout(() => {
|
|
9747
|
-
this.isUpdatingSelection = true;
|
|
9748
|
-
try {
|
|
9749
|
-
const selectedIds = this.selectedNodeIds();
|
|
9750
|
-
selectedIds.forEach((id) => {
|
|
9751
|
-
if (id && id !== 'all') {
|
|
9752
|
-
try {
|
|
9753
|
-
treeComponent.selectNode(id);
|
|
9754
|
-
}
|
|
9755
|
-
catch {
|
|
9756
|
-
// Node might not be in tree yet
|
|
9757
|
-
}
|
|
9758
|
-
}
|
|
9759
|
-
});
|
|
9760
|
-
}
|
|
9761
|
-
finally {
|
|
9762
|
-
this.isUpdatingSelection = false;
|
|
9763
|
-
}
|
|
9764
|
-
}, 0);
|
|
9765
|
-
}
|
|
9766
9691
|
}
|
|
9767
9692
|
/**
|
|
9768
9693
|
* Processes child nodes: marks excluded as disabled
|
|
9769
|
-
* Selection marking is handled in datasource callback ONLY during initial load
|
|
9770
9694
|
*/
|
|
9771
9695
|
processChildNodes(childNodes) {
|
|
9772
9696
|
const excludedId = this.excludedNodeId();
|
|
@@ -9912,8 +9836,7 @@ class AXPEntityCategoryTreeSelectorComponent extends AXBasePageComponent {
|
|
|
9912
9836
|
return this.translationService.resolve(raw);
|
|
9913
9837
|
}
|
|
9914
9838
|
async getSelectedItems() {
|
|
9915
|
-
|
|
9916
|
-
const selectedIds = this.selectedNodeIds();
|
|
9839
|
+
const selectedIds = this.normalizeSelectedIds(this.selectedNodeIds());
|
|
9917
9840
|
if (selectedIds.length === 0) {
|
|
9918
9841
|
return [];
|
|
9919
9842
|
}
|
|
@@ -10006,6 +9929,9 @@ class AXPEntityCategoryTreeSelectorComponent extends AXBasePageComponent {
|
|
|
10006
9929
|
[dragBehavior]="'none'"
|
|
10007
9930
|
[selectMode]="allowMultiple() ? 'multiple' : 'single'"
|
|
10008
9931
|
[selectionBehavior]="allowMultiple() ? 'intermediate-nested' : 'all'"
|
|
9932
|
+
[controlledSelection]="!allowMultiple()"
|
|
9933
|
+
[selectedIds]="selectedNodeIds()"
|
|
9934
|
+
(selectedIdsChange)="onControlledSelectedIdsChange($event)"
|
|
10009
9935
|
[showIcons]="true"
|
|
10010
9936
|
[titleField]="textField()"
|
|
10011
9937
|
[idField]="valueField()"
|
|
@@ -10092,7 +10018,7 @@ class AXPEntityCategoryTreeSelectorComponent extends AXBasePageComponent {
|
|
|
10092
10018
|
></ax-button>
|
|
10093
10019
|
</ax-suffix>
|
|
10094
10020
|
</ax-footer>
|
|
10095
|
-
`, isInline: true, 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: AXBadgeModule }, { kind: "component", type: i2$1.AXBadgeComponent, selector: "ax-badge", inputs: ["color", "look", "text"] }, { kind: "ngmodule", type: AXCheckBoxModule }, { kind: "ngmodule", type: AXDecoratorModule }, { kind: "component", type: i3$1.AXDecoratorIconComponent, selector: "ax-icon", inputs: ["icon"] }, { kind: "component", type: i3$1.AXDecoratorGenericComponent, selector: "ax-footer, ax-header, ax-content, ax-divider, ax-form-hint, ax-prefix, ax-suffix, ax-text, ax-title, ax-subtitle, ax-placeholder, ax-overlay" }, { kind: "ngmodule", type: AXSearchBoxModule }, { kind: "component", type: i4$1.AXSearchBoxComponent, selector: "ax-search-box", inputs: ["disabled", "readonly", "tabIndex", "placeholder", "value", "state", "name", "id", "look", "class", "delayTime", "type", "autoSearch"], outputs: ["valueChange", "stateChange", "onValueChanged", "onBlur", "onFocus", "readonlyChange", "disabledChange", "onKeyDown", "onKeyUp", "onKeyPress"] }, { kind: "ngmodule", type: AXSkeletonModule }, { kind: "component", type: i4.AXSkeletonComponent, selector: "ax-skeleton", inputs: ["animated"] }, { kind: "component", type: AXTreeViewComponent, selector: "ax-tree-view", inputs: ["datasource", "selectMode", "selectionBehavior", "dragArea", "dragBehavior", "showIcons", "showChildrenBadge", "expandedIcon", "collapsedIcon", "indentSize", "look", "nodeTemplate", "idField", "titleField", "tooltipField", "iconField", "expandedField", "selectedField", "indeterminateField", "disabledField", "hiddenField", "childrenField", "childrenCountField", "dataField", "inheritDisabled", "expandOnDoubleClick", "doubleClickDuration", "tooltipDelay"], outputs: ["datasourceChange", "onBeforeDrop", "onNodeToggle", "onNodeSelect", "onNodeDoubleClick", "onNodeClick", "onSelectionChange", "onOrderChange", "onMoveChange", "onItemsChange"] }, { kind: "ngmodule", type: AXTranslationModule }, { kind: "component", type: AXPStateMessageComponent, selector: "axp-state-message", inputs: ["mode", "icon", "title", "description", "look"] }, { kind: "ngmodule", type: FormsModule }, { kind: "pipe", type: i5.AsyncPipe, name: "async" }, { kind: "pipe", type: i6.AXTranslatorPipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
10021
|
+
`, isInline: true, 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: AXBadgeModule }, { kind: "component", type: i2$1.AXBadgeComponent, selector: "ax-badge", inputs: ["color", "look", "text"] }, { kind: "ngmodule", type: AXCheckBoxModule }, { kind: "ngmodule", type: AXDecoratorModule }, { kind: "component", type: i3$1.AXDecoratorIconComponent, selector: "ax-icon", inputs: ["icon"] }, { kind: "component", type: i3$1.AXDecoratorGenericComponent, selector: "ax-footer, ax-header, ax-content, ax-divider, ax-form-hint, ax-prefix, ax-suffix, ax-text, ax-title, ax-subtitle, ax-placeholder, ax-overlay" }, { kind: "ngmodule", type: AXSearchBoxModule }, { kind: "component", type: i4$1.AXSearchBoxComponent, selector: "ax-search-box", inputs: ["disabled", "readonly", "tabIndex", "placeholder", "value", "state", "name", "id", "look", "class", "delayTime", "type", "autoSearch"], outputs: ["valueChange", "stateChange", "onValueChanged", "onBlur", "onFocus", "readonlyChange", "disabledChange", "onKeyDown", "onKeyUp", "onKeyPress"] }, { kind: "ngmodule", type: AXSkeletonModule }, { kind: "component", type: i4.AXSkeletonComponent, selector: "ax-skeleton", inputs: ["animated"] }, { kind: "component", type: AXTreeViewComponent, selector: "ax-tree-view", inputs: ["datasource", "selectMode", "selectionBehavior", "dragArea", "dragBehavior", "showIcons", "showChildrenBadge", "expandedIcon", "collapsedIcon", "indentSize", "look", "nodeTemplate", "idField", "titleField", "tooltipField", "iconField", "expandedField", "selectedField", "indeterminateField", "disabledField", "hiddenField", "childrenField", "childrenCountField", "dataField", "inheritDisabled", "expandOnDoubleClick", "doubleClickDuration", "tooltipDelay", "controlledSelection", "selectedIds"], outputs: ["datasourceChange", "selectedIdsChange", "onBeforeDrop", "onNodeToggle", "onNodeSelect", "onNodeDoubleClick", "onNodeClick", "onSelectionChange", "onOrderChange", "onMoveChange", "onItemsChange"] }, { kind: "ngmodule", type: AXTranslationModule }, { kind: "component", type: AXPStateMessageComponent, selector: "axp-state-message", inputs: ["mode", "icon", "title", "description", "look"] }, { kind: "ngmodule", type: FormsModule }, { kind: "pipe", type: i5.AsyncPipe, name: "async" }, { kind: "pipe", type: i6.AXTranslatorPipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
10096
10022
|
}
|
|
10097
10023
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPEntityCategoryTreeSelectorComponent, decorators: [{
|
|
10098
10024
|
type: Component,
|
|
@@ -10143,6 +10069,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
10143
10069
|
[dragBehavior]="'none'"
|
|
10144
10070
|
[selectMode]="allowMultiple() ? 'multiple' : 'single'"
|
|
10145
10071
|
[selectionBehavior]="allowMultiple() ? 'intermediate-nested' : 'all'"
|
|
10072
|
+
[controlledSelection]="!allowMultiple()"
|
|
10073
|
+
[selectedIds]="selectedNodeIds()"
|
|
10074
|
+
(selectedIdsChange)="onControlledSelectedIdsChange($event)"
|
|
10146
10075
|
[showIcons]="true"
|
|
10147
10076
|
[titleField]="textField()"
|
|
10148
10077
|
[idField]="valueField()"
|
|
@@ -11252,9 +11181,7 @@ class AXPEntityCategoryWidgetEditComponent extends AXPValueWidgetComponent {
|
|
|
11252
11181
|
this.isOpen.set(true);
|
|
11253
11182
|
const currentValue = this.getValue();
|
|
11254
11183
|
const selectedIds = currentValue
|
|
11255
|
-
?
|
|
11256
|
-
.map((v) => String(extractValue(v, this.valueField())))
|
|
11257
|
-
.filter((id) => id && id !== 'all')
|
|
11184
|
+
? this.normalizePopupSelectedIds(currentValue)
|
|
11258
11185
|
: [];
|
|
11259
11186
|
// Get current entity ID from context (if editing an entity)
|
|
11260
11187
|
const currentEntityId = this.contextService.getValue('id');
|
|
@@ -11275,9 +11202,7 @@ class AXPEntityCategoryWidgetEditComponent extends AXPValueWidgetComponent {
|
|
|
11275
11202
|
},
|
|
11276
11203
|
});
|
|
11277
11204
|
if (result?.data?.selected && Array.isArray(result.data.selected)) {
|
|
11278
|
-
|
|
11279
|
-
// This ensures that deselected items are properly removed
|
|
11280
|
-
const newItems = result.data.selected;
|
|
11205
|
+
const newItems = this.multiple() ? result.data.selected : result.data.selected.slice(0, 1);
|
|
11281
11206
|
if (newItems.length === 0) {
|
|
11282
11207
|
// All items were deselected
|
|
11283
11208
|
this.clear();
|
|
@@ -11426,8 +11351,7 @@ class AXPEntityCategoryWidgetEditComponent extends AXPValueWidgetComponent {
|
|
|
11426
11351
|
setSmart(itemToExpose, i.target, this.singleOrMultiple(values));
|
|
11427
11352
|
}
|
|
11428
11353
|
});
|
|
11429
|
-
this.contextService.
|
|
11430
|
-
// Fire triggers
|
|
11354
|
+
this.contextService.applyObjectPaths(itemToExpose, { origin: 'user' });
|
|
11431
11355
|
this.setValue(this.singleOrMultiple(keys));
|
|
11432
11356
|
}
|
|
11433
11357
|
else {
|
|
@@ -11446,6 +11370,12 @@ class AXPEntityCategoryWidgetEditComponent extends AXPValueWidgetComponent {
|
|
|
11446
11370
|
singleOrMultiple(values) {
|
|
11447
11371
|
return this.multiple() ? values : values[0];
|
|
11448
11372
|
}
|
|
11373
|
+
normalizePopupSelectedIds(currentValue) {
|
|
11374
|
+
const ids = (this.multiple() ? castArray(currentValue) : castArray(currentValue).slice(0, 1))
|
|
11375
|
+
.map((v) => String(extractValue(v, this.valueField())))
|
|
11376
|
+
.filter((id) => id && id !== 'all');
|
|
11377
|
+
return this.multiple() ? ids : ids.slice(0, 1);
|
|
11378
|
+
}
|
|
11449
11379
|
getItemLabel(item) {
|
|
11450
11380
|
if (!item) {
|
|
11451
11381
|
return '';
|
|
@@ -12723,11 +12653,10 @@ class AXPEntityListWidgetViewComponent extends AXPValueWidgetComponent {
|
|
|
12723
12653
|
}
|
|
12724
12654
|
untracked(() => void this.applyRelatedFiltersFromContextAndDatasource(specs));
|
|
12725
12655
|
}, ...(ngDevMode ? [{ debugName: "#relatedFilterSyncEffect" }] : /* istanbul ignore next */ []));
|
|
12726
|
-
/** Patches data-list `refresh` so
|
|
12656
|
+
/** Patches data-list `refresh` so reload keeps parent-scoped toolbar filters on the data source. */
|
|
12727
12657
|
this.#patchDataListRefreshEffect = effect(() => {
|
|
12728
12658
|
const inst = this.listWidget()?.instance;
|
|
12729
|
-
|
|
12730
|
-
if (!inst?.refresh || inst.__axpEntityListRefreshPatched || !opts['syncRelatedListFiltersFromDialogContext']) {
|
|
12659
|
+
if (!inst?.refresh || inst.__axpEntityListRefreshPatched) {
|
|
12731
12660
|
return;
|
|
12732
12661
|
}
|
|
12733
12662
|
inst.__axpEntityListRefreshPatched = true;
|
|
@@ -12739,6 +12668,9 @@ class AXPEntityListWidgetViewComponent extends AXPValueWidgetComponent {
|
|
|
12739
12668
|
if (o['syncRelatedListFiltersFromDialogContext'] && specs?.length) {
|
|
12740
12669
|
await this.applyRelatedFiltersFromContextAndDatasource(specs);
|
|
12741
12670
|
}
|
|
12671
|
+
else {
|
|
12672
|
+
await this.reapplyScopedFiltersToDataSource();
|
|
12673
|
+
}
|
|
12742
12674
|
originalRefresh();
|
|
12743
12675
|
})();
|
|
12744
12676
|
};
|
|
@@ -12959,6 +12891,10 @@ class AXPEntityListWidgetViewComponent extends AXPValueWidgetComponent {
|
|
|
12959
12891
|
* Applies merged route + parent-scope filters to the widget value and data source.
|
|
12960
12892
|
*/
|
|
12961
12893
|
applyMergedRouteFiltersToList() {
|
|
12894
|
+
void this.reapplyScopedFiltersToDataSource();
|
|
12895
|
+
}
|
|
12896
|
+
/** Syncs toolbar filters to context and AXDataSource (retries until data-list is mounted). */
|
|
12897
|
+
async reapplyScopedFiltersToDataSource() {
|
|
12962
12898
|
const merged = this.getMergedToolbarFilters();
|
|
12963
12899
|
const current = this.getValue();
|
|
12964
12900
|
if (!isEqual$1(current?.toolbar?.filters, merged)) {
|
|
@@ -12967,7 +12903,26 @@ class AXPEntityListWidgetViewComponent extends AXPValueWidgetComponent {
|
|
|
12967
12903
|
toolbar: { ...(current?.toolbar ?? {}), filters: merged },
|
|
12968
12904
|
});
|
|
12969
12905
|
}
|
|
12906
|
+
const pushed = await this.ensureToolbarFiltersOnDataSource();
|
|
12907
|
+
if (pushed) {
|
|
12908
|
+
this.triggerInitialLoadIfNeeded();
|
|
12909
|
+
}
|
|
12910
|
+
}
|
|
12911
|
+
/**
|
|
12912
|
+
* Entity lists use `fetchDataMode: 'manual'`; after scoped filters reach the data source,
|
|
12913
|
+
* the grid must be refreshed once (initial #effect may run before the data-list is ready).
|
|
12914
|
+
*/
|
|
12915
|
+
triggerInitialLoadIfNeeded() {
|
|
12916
|
+
if (this.isMounted()) {
|
|
12917
|
+
return;
|
|
12918
|
+
}
|
|
12919
|
+
const listInstance = this.listWidget()?.instance;
|
|
12920
|
+
if (!listInstance?.call) {
|
|
12921
|
+
return;
|
|
12922
|
+
}
|
|
12970
12923
|
this.pushToolbarFiltersToDataSource();
|
|
12924
|
+
listInstance.call('refresh');
|
|
12925
|
+
this.isMounted.set(true);
|
|
12971
12926
|
}
|
|
12972
12927
|
/**
|
|
12973
12928
|
* Re-evaluates related-entity list filters from the live dialog form context (e.g. after create saves the main row id).
|
|
@@ -13014,26 +12969,29 @@ class AXPEntityListWidgetViewComponent extends AXPValueWidgetComponent {
|
|
|
13014
12969
|
return true;
|
|
13015
12970
|
}
|
|
13016
12971
|
/**
|
|
13017
|
-
*
|
|
12972
|
+
* Pushes toolbar filters once the embedded data-list exposes its data source
|
|
12973
|
+
* (listNode is created via deferred setTimeout in ngOnInit).
|
|
13018
12974
|
*/
|
|
13019
|
-
async
|
|
13020
|
-
await this.applyRelatedFiltersFromContext(specs);
|
|
12975
|
+
async ensureToolbarFiltersOnDataSource() {
|
|
13021
12976
|
if (this.pushToolbarFiltersToDataSource()) {
|
|
13022
|
-
return;
|
|
13023
|
-
}
|
|
13024
|
-
const opts = this.options();
|
|
13025
|
-
if (!opts['syncRelatedListFiltersFromDialogContext']) {
|
|
13026
|
-
return;
|
|
12977
|
+
return true;
|
|
13027
12978
|
}
|
|
13028
|
-
/** Data-list is created in ngOnInit via deferred listNode.set; retry briefly until instance exposes dataSource. */
|
|
13029
12979
|
const maxAttempts = 40;
|
|
13030
12980
|
const delayMs = 50;
|
|
13031
12981
|
for (let i = 0; i < maxAttempts; i++) {
|
|
13032
12982
|
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
13033
12983
|
if (this.pushToolbarFiltersToDataSource()) {
|
|
13034
|
-
return;
|
|
12984
|
+
return true;
|
|
13035
12985
|
}
|
|
13036
12986
|
}
|
|
12987
|
+
return false;
|
|
12988
|
+
}
|
|
12989
|
+
/**
|
|
12990
|
+
* Writes toolbar filters from specs and pushes them onto the data source so refresh/reload keeps the parent scope.
|
|
12991
|
+
*/
|
|
12992
|
+
async applyRelatedFiltersFromContextAndDatasource(specs) {
|
|
12993
|
+
await this.applyRelatedFiltersFromContext(specs);
|
|
12994
|
+
await this.ensureToolbarFiltersOnDataSource();
|
|
13037
12995
|
}
|
|
13038
12996
|
/**
|
|
13039
12997
|
* Refreshes the embedded data list (toolbar / workflow). In wizard mode, `refresh` is patched to re-apply scoped filters first.
|
|
@@ -13041,7 +12999,7 @@ class AXPEntityListWidgetViewComponent extends AXPValueWidgetComponent {
|
|
|
13041
12999
|
refreshGridWithParentScopedFilters() {
|
|
13042
13000
|
this.listWidget()?.instance?.call('refresh');
|
|
13043
13001
|
}
|
|
13044
|
-
/** Patches data-list `refresh` so
|
|
13002
|
+
/** Patches data-list `refresh` so reload keeps parent-scoped toolbar filters on the data source. */
|
|
13045
13003
|
#patchDataListRefreshEffect;
|
|
13046
13004
|
#effect;
|
|
13047
13005
|
/**
|
|
@@ -13056,7 +13014,7 @@ class AXPEntityListWidgetViewComponent extends AXPValueWidgetComponent {
|
|
|
13056
13014
|
handleQueryChanges(queries, dataSource, listInstance, isMounted) {
|
|
13057
13015
|
const changeTracker = this.analyzeChanges(queries);
|
|
13058
13016
|
this.previousQueries = queries;
|
|
13059
|
-
this.applyDataSourceChanges(dataSource, queries, changeTracker);
|
|
13017
|
+
this.applyDataSourceChanges(dataSource, queries, changeTracker, isMounted);
|
|
13060
13018
|
this.handleListRefresh(listInstance, changeTracker, isMounted);
|
|
13061
13019
|
this.handleColumnChanges(changeTracker);
|
|
13062
13020
|
}
|
|
@@ -13072,30 +13030,40 @@ class AXPEntityListWidgetViewComponent extends AXPValueWidgetComponent {
|
|
|
13072
13030
|
hasAnyChange: changes.length > 0,
|
|
13073
13031
|
};
|
|
13074
13032
|
}
|
|
13033
|
+
/**
|
|
13034
|
+
* Maps toolbar sort queries ({ name, dir }) to AXDataSource sort options ({ field, dir }).
|
|
13035
|
+
*/
|
|
13036
|
+
toDataSourceSortOptions(sorts) {
|
|
13037
|
+
return (sorts ?? [])
|
|
13038
|
+
.filter((s) => s.dir)
|
|
13039
|
+
.map((s) => ({ field: s.name, dir: s.dir }));
|
|
13040
|
+
}
|
|
13075
13041
|
/**
|
|
13076
13042
|
* Applies filter and sort changes to the data source
|
|
13077
13043
|
*/
|
|
13078
|
-
applyDataSourceChanges(dataSource, queries, changeTracker) {
|
|
13079
|
-
|
|
13044
|
+
applyDataSourceChanges(dataSource, queries, changeTracker, isMounted) {
|
|
13045
|
+
const shouldApplyFilters = changeTracker.isFilterChanged || (!isMounted && (queries?.filters?.length ?? 0) > 0);
|
|
13046
|
+
if (shouldApplyFilters) {
|
|
13080
13047
|
dataSource.filter({
|
|
13081
13048
|
filters: queries?.filters,
|
|
13082
13049
|
});
|
|
13083
13050
|
}
|
|
13084
13051
|
if (changeTracker.isSortChanged) {
|
|
13085
|
-
|
|
13052
|
+
const sortOptions = this.toDataSourceSortOptions(queries.sorts);
|
|
13053
|
+
dataSource.sort(...sortOptions);
|
|
13086
13054
|
}
|
|
13087
13055
|
}
|
|
13088
13056
|
/**
|
|
13089
|
-
* Handles list refresh logic based on changes and mount status
|
|
13057
|
+
* Handles list refresh logic based on changes and mount status.
|
|
13058
|
+
* Initial fetch is owned by {@link triggerInitialLoadIfNeeded} (manual fetchDataMode).
|
|
13090
13059
|
*/
|
|
13091
13060
|
handleListRefresh(listInstance, changeTracker, isMounted) {
|
|
13092
|
-
|
|
13093
|
-
|
|
13061
|
+
if (!isMounted) {
|
|
13062
|
+
return;
|
|
13063
|
+
}
|
|
13064
|
+
if (changeTracker.isFilterChanged || changeTracker.isSortChanged) {
|
|
13065
|
+
this.pushToolbarFiltersToDataSource();
|
|
13094
13066
|
listInstance.call('refresh');
|
|
13095
|
-
// Set mounted flag only on initial load (not when filters/sorts change)
|
|
13096
|
-
if (!changeTracker.isFilterChanged && !changeTracker.isSortChanged) {
|
|
13097
|
-
this.isMounted.set(true);
|
|
13098
|
-
}
|
|
13099
13067
|
}
|
|
13100
13068
|
}
|
|
13101
13069
|
/**
|
|
@@ -15534,7 +15502,8 @@ class AXPLookupWidgetEditComponent extends AXPValueWidgetComponent {
|
|
|
15534
15502
|
this.setLoading = (loading) => {
|
|
15535
15503
|
this.isLoading.set(loading);
|
|
15536
15504
|
};
|
|
15537
|
-
|
|
15505
|
+
/** @param syncContext When false, only updates UI (hydration/revert); skips context writes. */
|
|
15506
|
+
this.setItems = (items, syncContext = true) => {
|
|
15538
15507
|
// Ensure items is always an array
|
|
15539
15508
|
items = castArray(items);
|
|
15540
15509
|
// Filter out null/undefined items
|
|
@@ -15550,11 +15519,11 @@ class AXPLookupWidgetEditComponent extends AXPValueWidgetComponent {
|
|
|
15550
15519
|
return item;
|
|
15551
15520
|
});
|
|
15552
15521
|
this.selectedItems.set(items);
|
|
15553
|
-
|
|
15522
|
+
if (!syncContext) {
|
|
15523
|
+
return;
|
|
15524
|
+
}
|
|
15554
15525
|
const keys = items.map((item) => get(item, this.valueField()));
|
|
15555
15526
|
const text = items.map((item) => this.mlsResolver.resolve(get(item, displayField)));
|
|
15556
|
-
//
|
|
15557
|
-
// extract data from valueField and set context by expose path
|
|
15558
15527
|
if (this.expose()) {
|
|
15559
15528
|
this.expoesItems();
|
|
15560
15529
|
}
|
|
@@ -15583,7 +15552,7 @@ class AXPLookupWidgetEditComponent extends AXPValueWidgetComponent {
|
|
|
15583
15552
|
? castArray(this.filterMode() ? rawValue.value : rawValue)
|
|
15584
15553
|
: [rawValue].filter((v) => v != null);
|
|
15585
15554
|
if (!values.length) {
|
|
15586
|
-
this.setItems([]);
|
|
15555
|
+
this.setItems([], false);
|
|
15587
15556
|
this.isLoading.set(false);
|
|
15588
15557
|
return;
|
|
15589
15558
|
}
|
|
@@ -15598,22 +15567,22 @@ class AXPLookupWidgetEditComponent extends AXPValueWidgetComponent {
|
|
|
15598
15567
|
.map((id) => entityDataAccessor.byKey(id)));
|
|
15599
15568
|
// Filter out null/undefined results
|
|
15600
15569
|
const validItems = items.filter((item) => item != null);
|
|
15601
|
-
this.setItems(validItems);
|
|
15570
|
+
this.setItems(validItems, false);
|
|
15602
15571
|
}
|
|
15603
15572
|
else {
|
|
15604
15573
|
const id = extractValue(this.filterMode() ? values[0].value : values[0], this.valueField());
|
|
15605
15574
|
if (id != null) {
|
|
15606
15575
|
const item = await entityDataAccessor.byKey(id);
|
|
15607
|
-
this.setItems(item != null ? [item] : []);
|
|
15576
|
+
this.setItems(item != null ? [item] : [], false);
|
|
15608
15577
|
}
|
|
15609
15578
|
else {
|
|
15610
|
-
this.setItems([]);
|
|
15579
|
+
this.setItems([], false);
|
|
15611
15580
|
}
|
|
15612
15581
|
}
|
|
15613
15582
|
}
|
|
15614
15583
|
catch (error) {
|
|
15615
15584
|
console.error('[LookupWidget] findByValue() error:', error);
|
|
15616
|
-
this.setItems([]);
|
|
15585
|
+
this.setItems([], false);
|
|
15617
15586
|
}
|
|
15618
15587
|
finally {
|
|
15619
15588
|
this.isLoading.set(false);
|
|
@@ -16121,7 +16090,7 @@ class AXPLookupWidgetColumnComponent extends AXPColumnWidgetComponent {
|
|
|
16121
16090
|
if (!resolvedTitle) {
|
|
16122
16091
|
const displayTitle = formatLookupItemDisplay(headerItem, this.entityDef(), this.lookupDisplayOptions(), this.formatService, (value) => this.translation.resolve(value));
|
|
16123
16092
|
resolvedTitle = typeof displayTitle === 'string' ? displayTitle : this.translation.resolve(displayTitle);
|
|
16124
|
-
if (
|
|
16093
|
+
if (isUnresolvedEntityDisplayTemplate(resolvedTitle)) {
|
|
16125
16094
|
resolvedTitle = '';
|
|
16126
16095
|
}
|
|
16127
16096
|
}
|
|
@@ -16516,11 +16485,11 @@ class AXPLookupWidgetColumnComponent extends AXPColumnWidgetComponent {
|
|
|
16516
16485
|
return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value) || /^\d+$/.test(value);
|
|
16517
16486
|
}
|
|
16518
16487
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPLookupWidgetColumnComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
16519
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: AXPLookupWidgetColumnComponent, isStandalone: true, selector: "ng-component", inputs: { rawValue: "rawValue", rowData: "rowData" }, viewQueries: [{ propertyName: "moreButton", first: true, predicate: ["moreButton"], descendants: true, isSignal: true }, { propertyName: "lazyTrigger", first: true, predicate: ["lazyTrigger"], descendants: true, isSignal: true }, { propertyName: "morePopover", first: true, predicate: ["morePopover"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "@if (isHydratedStrategy()) {\n<div class=\"ax-relative ax-flex ax-min-w-0 ax-items-center ax-gap-1\">\n @for (item of visibleItems(); track $index) {\n @let label = getDisplayRaw(item);\n <span
|
|
16488
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: AXPLookupWidgetColumnComponent, isStandalone: true, selector: "ng-component", inputs: { rawValue: "rawValue", rowData: "rowData" }, viewQueries: [{ propertyName: "moreButton", first: true, predicate: ["moreButton"], descendants: true, isSignal: true }, { propertyName: "lazyTrigger", first: true, predicate: ["lazyTrigger"], descendants: true, isSignal: true }, { propertyName: "morePopover", first: true, predicate: ["morePopover"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "@if (isHydratedStrategy()) {\n<div class=\"ax-relative ax-flex ax-min-w-0 ax-items-center ax-gap-1\">\n @for (item of visibleItems(); track $index) {\n @let label = getDisplayRaw(item);\n <span [class.ax-cursor-pointer]=\"detailPopoverEnabled()\" [class.hover:ax-text-primary]=\"detailPopoverEnabled()\"\n [class.hover:ax-underline]=\"detailPopoverEnabled()\" (click)=\"handleItemClick($index)\">\n {{ label }}\n </span>\n @if ($index < visibleItems().length - 1) { <span class=\"ax-text-muted\">\u2022</span>\n }\n } @empty {\n <span class=\"ax-text-muted\">---</span>\n }\n @if (hasMoreItems()) {\n <span\n class=\"ax-absolute ax-end-0 ax-flex ax-h-full ax-cursor-pointer ax-items-center ax-px-1 hover:ax-primary-lighter\"\n (click)=\"showMoreItems(); $event.stopPropagation()\" #moreButton>\n <i class=\"fa-light fa-ellipsis-vertical\"></i>\n </span>\n }\n</div>\n} @else {\n<div class=\"ax-flex ax-min-w-0 ax-items-center ax-gap-1\">\n @if (displayCount() > 0) {\n <span class=\"ax-cursor-pointer ax-text-primary hover:ax-underline\"\n (click)=\"openLazyPopover(); $event.stopPropagation()\" #lazyTrigger>\n {{ summaryLabel() }}\n </span>\n } @else {\n <span class=\"ax-text-muted\">---</span>\n }\n</div>\n}\n\n<ax-popover [openOn]=\"'manual'\" #morePopover (openChange)=\"onPopoverOpenChange($event)\">\n <div class=\"ax-lightest-surface dark:ax-dark-surface ax-min-w-[280px] ax-rounded-lg ax-border ax-p-4 ax-shadow-lg\">\n <div class=\"ax-mb-4 ax-border-b ax-pb-2\">\n <h3 class=\"ax-text-base ax-font-semibold\">{{ popoverHeader() }}</h3>\n </div>\n\n @if (!isHydratedStrategy() && resolveStatus() === 'loading') {\n <div class=\"ax-flex ax-min-h-[120px] ax-items-center ax-justify-center\">\n <ax-loading></ax-loading>\n </div>\n } @else if (!isHydratedStrategy() && resolveStatus() === 'error') {\n <div class=\"ax-text-danger ax-text-sm\">{{ resolveError() }}</div>\n } @else {\n <div class=\"ax-flex ax-max-h-64 ax-flex-col ax-gap-3\">\n @for (item of popoverListItems(); track $index) {\n @let label = getDisplayRaw(item);\n <span [class.ax-cursor-pointer]=\"detailPopoverEnabled()\" [class.hover:ax-text-primary]=\"detailPopoverEnabled()\"\n [class.hover:ax-underline]=\"detailPopoverEnabled()\" (click)=\"handlePopoverItemClick($index)\">\n {{ label }}\n </span>\n } @empty {\n <span class=\"ax-text-muted\">---</span>\n }\n </div>\n }\n </div>\n</ax-popover>", dependencies: [{ kind: "ngmodule", type: AXLoadingModule }, { kind: "component", type: i1$2.AXLoadingComponent, selector: "ax-loading", inputs: ["visible", "type", "context"], outputs: ["visibleChange"] }, { kind: "ngmodule", type: AXPopoverModule }, { kind: "component", type: i2.AXPopoverComponent, selector: "ax-popover", inputs: ["width", "disablePanelClass", "disabled", "offsetX", "offsetY", "target", "placement", "content", "openOn", "closeOn", "hasBackdrop", "openAfter", "closeAfter", "closeOnScroll", "backdropClass", "panelClass", "adaptivityEnabled"], outputs: ["onOpened", "onClosed"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
16520
16489
|
}
|
|
16521
16490
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPLookupWidgetColumnComponent, decorators: [{
|
|
16522
16491
|
type: Component,
|
|
16523
|
-
args: [{ changeDetection: ChangeDetectionStrategy.OnPush, imports: [AXLoadingModule, AXPopoverModule], inputs: ['rawValue', 'rowData'], template: "@if (isHydratedStrategy()) {\n<div class=\"ax-relative ax-flex ax-min-w-0 ax-items-center ax-gap-1\">\n @for (item of visibleItems(); track $index) {\n @let label = getDisplayRaw(item);\n <span
|
|
16492
|
+
args: [{ changeDetection: ChangeDetectionStrategy.OnPush, imports: [AXLoadingModule, AXPopoverModule], inputs: ['rawValue', 'rowData'], template: "@if (isHydratedStrategy()) {\n<div class=\"ax-relative ax-flex ax-min-w-0 ax-items-center ax-gap-1\">\n @for (item of visibleItems(); track $index) {\n @let label = getDisplayRaw(item);\n <span [class.ax-cursor-pointer]=\"detailPopoverEnabled()\" [class.hover:ax-text-primary]=\"detailPopoverEnabled()\"\n [class.hover:ax-underline]=\"detailPopoverEnabled()\" (click)=\"handleItemClick($index)\">\n {{ label }}\n </span>\n @if ($index < visibleItems().length - 1) { <span class=\"ax-text-muted\">\u2022</span>\n }\n } @empty {\n <span class=\"ax-text-muted\">---</span>\n }\n @if (hasMoreItems()) {\n <span\n class=\"ax-absolute ax-end-0 ax-flex ax-h-full ax-cursor-pointer ax-items-center ax-px-1 hover:ax-primary-lighter\"\n (click)=\"showMoreItems(); $event.stopPropagation()\" #moreButton>\n <i class=\"fa-light fa-ellipsis-vertical\"></i>\n </span>\n }\n</div>\n} @else {\n<div class=\"ax-flex ax-min-w-0 ax-items-center ax-gap-1\">\n @if (displayCount() > 0) {\n <span class=\"ax-cursor-pointer ax-text-primary hover:ax-underline\"\n (click)=\"openLazyPopover(); $event.stopPropagation()\" #lazyTrigger>\n {{ summaryLabel() }}\n </span>\n } @else {\n <span class=\"ax-text-muted\">---</span>\n }\n</div>\n}\n\n<ax-popover [openOn]=\"'manual'\" #morePopover (openChange)=\"onPopoverOpenChange($event)\">\n <div class=\"ax-lightest-surface dark:ax-dark-surface ax-min-w-[280px] ax-rounded-lg ax-border ax-p-4 ax-shadow-lg\">\n <div class=\"ax-mb-4 ax-border-b ax-pb-2\">\n <h3 class=\"ax-text-base ax-font-semibold\">{{ popoverHeader() }}</h3>\n </div>\n\n @if (!isHydratedStrategy() && resolveStatus() === 'loading') {\n <div class=\"ax-flex ax-min-h-[120px] ax-items-center ax-justify-center\">\n <ax-loading></ax-loading>\n </div>\n } @else if (!isHydratedStrategy() && resolveStatus() === 'error') {\n <div class=\"ax-text-danger ax-text-sm\">{{ resolveError() }}</div>\n } @else {\n <div class=\"ax-flex ax-max-h-64 ax-flex-col ax-gap-3\">\n @for (item of popoverListItems(); track $index) {\n @let label = getDisplayRaw(item);\n <span [class.ax-cursor-pointer]=\"detailPopoverEnabled()\" [class.hover:ax-text-primary]=\"detailPopoverEnabled()\"\n [class.hover:ax-underline]=\"detailPopoverEnabled()\" (click)=\"handlePopoverItemClick($index)\">\n {{ label }}\n </span>\n } @empty {\n <span class=\"ax-text-muted\">---</span>\n }\n </div>\n }\n </div>\n</ax-popover>" }]
|
|
16524
16493
|
}], ctorParameters: () => [], propDecorators: { moreButton: [{ type: i0.ViewChild, args: ['moreButton', { isSignal: true }] }], lazyTrigger: [{ type: i0.ViewChild, args: ['lazyTrigger', { isSignal: true }] }], morePopover: [{ type: i0.ViewChild, args: ['morePopover', { isSignal: true }] }] } });
|
|
16525
16494
|
|
|
16526
16495
|
var lookupWidgetColumn_component = /*#__PURE__*/Object.freeze({
|
|
@@ -18320,6 +18289,14 @@ class AXPFileListComponent {
|
|
|
18320
18289
|
/** When true, edit dialog shows name, title and description fields. Default false. */
|
|
18321
18290
|
this.enableTitleDescription = input(false, ...(ngDevMode ? [{ debugName: "enableTitleDescription" }] : /* istanbul ignore next */ []));
|
|
18322
18291
|
this.multiple = input(true, ...(ngDevMode ? [{ debugName: "multiple" }] : /* istanbul ignore next */ []));
|
|
18292
|
+
/** `rows`: file-type icons and action buttons; `links`: compact paperclip links (download on click). */
|
|
18293
|
+
this.look = input('rows', ...(ngDevMode ? [{ debugName: "look" }] : /* istanbul ignore next */ []));
|
|
18294
|
+
/** i18n key for links look label (template adds trailing colon). */
|
|
18295
|
+
this.titleKey = input('@general:widgets.file-uploader.attachments.title', ...(ngDevMode ? [{ debugName: "titleKey" }] : /* istanbul ignore next */ []));
|
|
18296
|
+
/** When false, links look omits the label (e.g. popover body). */
|
|
18297
|
+
this.showLabel = input(true, ...(ngDevMode ? [{ debugName: "showLabel" }] : /* istanbul ignore next */ []));
|
|
18298
|
+
/** `inline`: label + divider; `menu`: compact list for popovers/panels. */
|
|
18299
|
+
this.linksLayout = input('inline', ...(ngDevMode ? [{ debugName: "linksLayout" }] : /* istanbul ignore next */ []));
|
|
18323
18300
|
this.files = input([], ...(ngDevMode ? [{ debugName: "files" }] : /* istanbul ignore next */ []));
|
|
18324
18301
|
// Plugin context (passed from parent widget)
|
|
18325
18302
|
this.plugins = input(undefined, ...(ngDevMode ? [{ debugName: "plugins" }] : /* istanbul ignore next */ []));
|
|
@@ -18330,11 +18307,19 @@ class AXPFileListComponent {
|
|
|
18330
18307
|
* All files should be displayed, even those with `deleted` status.
|
|
18331
18308
|
* The template will handle the visual differences based on the status.
|
|
18332
18309
|
*/
|
|
18333
|
-
this.displayFiles = computed(() =>
|
|
18310
|
+
this.displayFiles = computed(() => {
|
|
18311
|
+
const list = this.files();
|
|
18312
|
+
if (this.look() === 'links') {
|
|
18313
|
+
return list.filter((file) => file.status !== 'deleted');
|
|
18314
|
+
}
|
|
18315
|
+
return list;
|
|
18316
|
+
}, ...(ngDevMode ? [{ debugName: "displayFiles" }] : /* istanbul ignore next */ []));
|
|
18334
18317
|
// Cache for per-file actions provided by hooks
|
|
18335
18318
|
this.fileIdToActions = signal({}, ...(ngDevMode ? [{ debugName: "fileIdToActions" }] : /* istanbul ignore next */ []));
|
|
18336
18319
|
// Global actions from FileUploaderWebhookKeys.Actions hook
|
|
18337
18320
|
this.globalActions = signal([], ...(ngDevMode ? [{ debugName: "globalActions" }] : /* istanbul ignore next */ []));
|
|
18321
|
+
/** Suppress duplicate downloads from double-click (two click events). */
|
|
18322
|
+
this.recentDownloadAtByKey = new Map();
|
|
18338
18323
|
// Clear cache when files change to reload actions
|
|
18339
18324
|
this.filesChangeEffect = effect(() => {
|
|
18340
18325
|
// Track files changes to clear cache
|
|
@@ -18342,6 +18327,7 @@ class AXPFileListComponent {
|
|
|
18342
18327
|
this.fileIdToActions.set({});
|
|
18343
18328
|
}, ...(ngDevMode ? [{ debugName: "filesChangeEffect" }] : /* istanbul ignore next */ []));
|
|
18344
18329
|
}
|
|
18330
|
+
static { this.DOWNLOAD_DEBOUNCE_MS = 500; }
|
|
18345
18331
|
// Default actions that can be removed via hooks
|
|
18346
18332
|
getDefaultActions(file) {
|
|
18347
18333
|
const defaultActions = [];
|
|
@@ -18438,6 +18424,10 @@ class AXPFileListComponent {
|
|
|
18438
18424
|
this.fileIdToActions.set(next);
|
|
18439
18425
|
}
|
|
18440
18426
|
async ngOnInit() {
|
|
18427
|
+
if (this.look() === 'links') {
|
|
18428
|
+
this.isLoading.set(false);
|
|
18429
|
+
return;
|
|
18430
|
+
}
|
|
18441
18431
|
this.fileTypes.set(await this.fileTypeService.items());
|
|
18442
18432
|
await this.loadGlobalActions();
|
|
18443
18433
|
this.isLoading.set(false);
|
|
@@ -18457,6 +18447,36 @@ class AXPFileListComponent {
|
|
|
18457
18447
|
this.globalActions.set([]);
|
|
18458
18448
|
}
|
|
18459
18449
|
}
|
|
18450
|
+
shouldSkipDuplicateDownload(file) {
|
|
18451
|
+
const key = this.downloadDedupeKey(file);
|
|
18452
|
+
const now = Date.now();
|
|
18453
|
+
const last = this.recentDownloadAtByKey.get(key);
|
|
18454
|
+
if (last != null && now - last < AXPFileListComponent.DOWNLOAD_DEBOUNCE_MS) {
|
|
18455
|
+
return true;
|
|
18456
|
+
}
|
|
18457
|
+
this.recentDownloadAtByKey.set(key, now);
|
|
18458
|
+
return false;
|
|
18459
|
+
}
|
|
18460
|
+
downloadDedupeKey(file) {
|
|
18461
|
+
if (typeof file.id === 'string' && file.id.trim()) {
|
|
18462
|
+
return file.id;
|
|
18463
|
+
}
|
|
18464
|
+
const src = file.source;
|
|
18465
|
+
if (src?.kind === 'fileId' && typeof src.value === 'string' && src.value.trim()) {
|
|
18466
|
+
return src.value;
|
|
18467
|
+
}
|
|
18468
|
+
return file.name;
|
|
18469
|
+
}
|
|
18470
|
+
/** Ensures `source` is set for links look and legacy rows that only carry storage id on `id`. */
|
|
18471
|
+
resolveFileForDownload(file) {
|
|
18472
|
+
if (file.source) {
|
|
18473
|
+
return file;
|
|
18474
|
+
}
|
|
18475
|
+
if (typeof file.id === 'string' && file.id.trim()) {
|
|
18476
|
+
return { ...file, source: { kind: 'fileId', value: file.id } };
|
|
18477
|
+
}
|
|
18478
|
+
return file;
|
|
18479
|
+
}
|
|
18460
18480
|
getFileInfo(fileName) {
|
|
18461
18481
|
const extension = fileName.split('.').pop()?.toLowerCase() || '';
|
|
18462
18482
|
const extensions = this.fileTypes().flatMap((t) => t.extensions);
|
|
@@ -18466,10 +18486,23 @@ class AXPFileListComponent {
|
|
|
18466
18486
|
type: fileType?.name || 'Unknown',
|
|
18467
18487
|
};
|
|
18468
18488
|
}
|
|
18489
|
+
/**
|
|
18490
|
+
* Double-click on a file row (rows look) or link row triggers download.
|
|
18491
|
+
*/
|
|
18492
|
+
handleFileRowActivate(event, file) {
|
|
18493
|
+
if (file.status === 'deleted') {
|
|
18494
|
+
return;
|
|
18495
|
+
}
|
|
18496
|
+
void this.handleFileDownload(event, file);
|
|
18497
|
+
}
|
|
18469
18498
|
async handleFileDownload(event, file) {
|
|
18470
|
-
event.nativeEvent
|
|
18471
|
-
|
|
18472
|
-
|
|
18499
|
+
const nativeEvent = event instanceof Event ? event : event.nativeEvent;
|
|
18500
|
+
nativeEvent.preventDefault();
|
|
18501
|
+
nativeEvent.stopPropagation();
|
|
18502
|
+
if (this.shouldSkipDuplicateDownload(file)) {
|
|
18503
|
+
return;
|
|
18504
|
+
}
|
|
18505
|
+
try {
|
|
18473
18506
|
await this.hooks.runAsync(FileUploaderWebhookKeys.FileItem.BeforeDownload, {
|
|
18474
18507
|
file,
|
|
18475
18508
|
plugins: this.plugins() ?? [],
|
|
@@ -18478,7 +18511,9 @@ class AXPFileListComponent {
|
|
|
18478
18511
|
});
|
|
18479
18512
|
}
|
|
18480
18513
|
catch { }
|
|
18481
|
-
|
|
18514
|
+
const resolved = this.resolveFileForDownload(file);
|
|
18515
|
+
const source = resolved.source;
|
|
18516
|
+
if (!source) {
|
|
18482
18517
|
console.error('File source is undefined, cannot download.', file);
|
|
18483
18518
|
return;
|
|
18484
18519
|
}
|
|
@@ -18492,37 +18527,37 @@ class AXPFileListComponent {
|
|
|
18492
18527
|
document.body.removeChild(link);
|
|
18493
18528
|
URL.revokeObjectURL(url);
|
|
18494
18529
|
};
|
|
18495
|
-
switch (
|
|
18530
|
+
switch (source.kind) {
|
|
18496
18531
|
case 'blob':
|
|
18497
|
-
if (
|
|
18498
|
-
triggerDownload(
|
|
18532
|
+
if (source.value instanceof Blob) {
|
|
18533
|
+
triggerDownload(source.value, resolved.name ?? 'download');
|
|
18499
18534
|
}
|
|
18500
18535
|
else {
|
|
18501
18536
|
console.error('Source kind is blob, but value is not a Blob.', file);
|
|
18502
18537
|
}
|
|
18503
18538
|
break;
|
|
18504
18539
|
case 'fileId':
|
|
18505
|
-
if (typeof
|
|
18540
|
+
if (typeof source.value === 'string') {
|
|
18506
18541
|
try {
|
|
18507
|
-
const fileInfo = await this.fileStorageService.getInfo(
|
|
18542
|
+
const fileInfo = await this.fileStorageService.getInfo(source.value);
|
|
18508
18543
|
if (fileInfo && fileInfo.url) {
|
|
18509
18544
|
const link = document.createElement('a');
|
|
18510
18545
|
link.href = fileInfo.url;
|
|
18511
|
-
link.download =
|
|
18546
|
+
link.download = resolved.name ?? fileInfo.name ?? 'download';
|
|
18512
18547
|
link.target = '_blank';
|
|
18513
18548
|
document.body.appendChild(link);
|
|
18514
18549
|
link.click();
|
|
18515
18550
|
document.body.removeChild(link);
|
|
18516
18551
|
}
|
|
18517
18552
|
else if (fileInfo && fileInfo.binary instanceof Blob) {
|
|
18518
|
-
triggerDownload(fileInfo.binary,
|
|
18553
|
+
triggerDownload(fileInfo.binary, resolved.name ?? fileInfo.name ?? 'download');
|
|
18519
18554
|
}
|
|
18520
18555
|
else {
|
|
18521
18556
|
console.error('Could not retrieve file for download from fileId:', fileInfo);
|
|
18522
18557
|
}
|
|
18523
18558
|
}
|
|
18524
18559
|
catch (error) {
|
|
18525
|
-
console.error('Error downloading file by fileId:',
|
|
18560
|
+
console.error('Error downloading file by fileId:', source.value, error);
|
|
18526
18561
|
}
|
|
18527
18562
|
}
|
|
18528
18563
|
else {
|
|
@@ -18530,10 +18565,10 @@ class AXPFileListComponent {
|
|
|
18530
18565
|
}
|
|
18531
18566
|
break;
|
|
18532
18567
|
case 'url':
|
|
18533
|
-
if (typeof
|
|
18568
|
+
if (typeof source.value === 'string') {
|
|
18534
18569
|
const link = document.createElement('a');
|
|
18535
|
-
link.href =
|
|
18536
|
-
link.download =
|
|
18570
|
+
link.href = source.value;
|
|
18571
|
+
link.download = resolved.name ?? 'download';
|
|
18537
18572
|
link.target = '_blank';
|
|
18538
18573
|
document.body.appendChild(link);
|
|
18539
18574
|
link.click();
|
|
@@ -18544,14 +18579,14 @@ class AXPFileListComponent {
|
|
|
18544
18579
|
}
|
|
18545
18580
|
break;
|
|
18546
18581
|
case 'reference':
|
|
18547
|
-
if (
|
|
18548
|
-
typeof
|
|
18549
|
-
'id' in
|
|
18550
|
-
'type' in
|
|
18582
|
+
if (source.value &&
|
|
18583
|
+
typeof source.value === 'object' &&
|
|
18584
|
+
'id' in source.value &&
|
|
18585
|
+
'type' in source.value) {
|
|
18551
18586
|
try {
|
|
18552
18587
|
const result = await this.hooks.runAsync(FileUploaderWebhookKeys.FileItem.DownloadReference, {
|
|
18553
|
-
file,
|
|
18554
|
-
reference:
|
|
18588
|
+
file: resolved,
|
|
18589
|
+
reference: source.value,
|
|
18555
18590
|
plugins: this.plugins() ?? [],
|
|
18556
18591
|
excludePlugins: this.excludePlugins() ?? [],
|
|
18557
18592
|
capabilities: this.capabilities(),
|
|
@@ -18561,14 +18596,14 @@ class AXPFileListComponent {
|
|
|
18561
18596
|
if (result.fileInfo.url) {
|
|
18562
18597
|
const link = document.createElement('a');
|
|
18563
18598
|
link.href = result.fileInfo.url;
|
|
18564
|
-
link.download =
|
|
18599
|
+
link.download = resolved.name ?? result.fileInfo.name ?? 'download';
|
|
18565
18600
|
link.target = '_blank';
|
|
18566
18601
|
document.body.appendChild(link);
|
|
18567
18602
|
link.click();
|
|
18568
18603
|
document.body.removeChild(link);
|
|
18569
18604
|
}
|
|
18570
18605
|
else if (result.fileInfo.binary instanceof Blob) {
|
|
18571
|
-
triggerDownload(result.fileInfo.binary,
|
|
18606
|
+
triggerDownload(result.fileInfo.binary, resolved.name ?? result.fileInfo.name ?? 'download');
|
|
18572
18607
|
}
|
|
18573
18608
|
else {
|
|
18574
18609
|
console.error('Could not retrieve file for download from reference:', result.fileInfo);
|
|
@@ -18579,7 +18614,7 @@ class AXPFileListComponent {
|
|
|
18579
18614
|
}
|
|
18580
18615
|
}
|
|
18581
18616
|
catch (error) {
|
|
18582
|
-
console.error('Error downloading file by reference:',
|
|
18617
|
+
console.error('Error downloading file by reference:', source.value, error);
|
|
18583
18618
|
}
|
|
18584
18619
|
}
|
|
18585
18620
|
else {
|
|
@@ -18589,12 +18624,12 @@ class AXPFileListComponent {
|
|
|
18589
18624
|
case 'preview':
|
|
18590
18625
|
case 'none':
|
|
18591
18626
|
default:
|
|
18592
|
-
console.error(`Download not supported for source kind: ${
|
|
18627
|
+
console.error(`Download not supported for source kind: ${source.kind}`, file);
|
|
18593
18628
|
break;
|
|
18594
18629
|
}
|
|
18595
18630
|
try {
|
|
18596
18631
|
await this.hooks.runAsync(FileUploaderWebhookKeys.FileItem.AfterDownload, {
|
|
18597
|
-
file,
|
|
18632
|
+
file: resolved,
|
|
18598
18633
|
plugins: this.plugins() ?? [],
|
|
18599
18634
|
excludePlugins: this.excludePlugins() ?? [],
|
|
18600
18635
|
capabilities: this.capabilities(),
|
|
@@ -18663,7 +18698,9 @@ class AXPFileListComponent {
|
|
|
18663
18698
|
}
|
|
18664
18699
|
catch { }
|
|
18665
18700
|
}
|
|
18666
|
-
ngOnDestroy() {
|
|
18701
|
+
ngOnDestroy() {
|
|
18702
|
+
this.recentDownloadAtByKey.clear();
|
|
18703
|
+
}
|
|
18667
18704
|
async runAction(action) {
|
|
18668
18705
|
try {
|
|
18669
18706
|
await action.run(this.capabilities());
|
|
@@ -18687,19 +18724,24 @@ class AXPFileListComponent {
|
|
|
18687
18724
|
return action.color ?? 'primary';
|
|
18688
18725
|
}
|
|
18689
18726
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPFileListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
18690
|
-
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 }, 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" }, providers: [], ngImport: i0, template: "@for (file of displayFiles(); track $index) {\n <div\n
|
|
18691
|
-
// Angular
|
|
18692
|
-
CommonModule }, { kind: "directive", type: i5.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type:
|
|
18727
|
+
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 ax-flex-shrink-0\"></i>\n <span class=\"ax-min-w-0 ax-break-all\">{{ file.name }}</span>\n </button>\n </li>\n }\n </ul>\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 }} fa-solid\"></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: [":host{display:flex;width:100%;flex-direction:column;gap:.125rem}:host.--look-rows{padding-top:.5rem;padding-bottom:.5rem}:host.--look-links{padding-top:0;padding-bottom:0}:host.--look-links .__links .__links-label{margin-inline-end:.5rem;font-size:.875rem;line-height:1.25rem;font-weight:500;color:rgb(var(--ax-sys-color-on-surface-variant))}:host.--look-links .__links .__links-list{margin:0;margin-top:.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:.75rem;border-radius:.375rem;border-width:0px;background-color:transparent;padding:.625rem .75rem;text-align:start;font-size:.875rem;line-height:1.25rem;text-decoration-line:none;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);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:1rem;border-bottom-width:1px;--tw-border-opacity: 1;border-color:rgba(var(--ax-sys-color-border-surface),var(--tw-border-opacity, 1));padding-bottom:1rem}:host.--look-links:not(.--links-inline) .__links-list{margin-top:0;display:flex;flex-direction:column;gap:.5rem}:host .__item{display:flex;align-items:center;gap:.75rem;border-left-width:4px;border-color:transparent;padding:.5rem}:host .__item.--downloadable{cursor:pointer}:host .__item.--downloadable:hover{background-color:rgb(var(--ax-sys-color-lighter-surface));color:rgb(var(--ax-sys-color-on-lighter-surface));border-color:rgb(var(--ax-sys-color-border-lighter-surface))}:host .__item:not(.--downloadable){cursor:default}:host .__item.--removed{--tw-border-opacity: 1;border-color:rgba(var(--ax-sys-color-danger-500),var(--tw-border-opacity, 1));--tw-bg-opacity: 1;background-color:rgba(var(--ax-sys-color-danger-50),var(--tw-bg-opacity, 1))}:host .__item.--attached{--tw-border-opacity: 1;border-color:rgba(var(--ax-sys-color-success-500),var(--tw-border-opacity, 1));animation:attached-flash 1s ease-out forwards}:host .__item.--item-readonly:not(.--removed){opacity:.85}:host .__item .__icon{width:1.5rem;flex-shrink:0}:host .__item .__name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;text-align:start;font-size:.875rem;line-height:1.25rem;font-weight:500}:host .__item .__content{display:flex;min-width:0px;flex:1 1 0%;flex-direction:column;gap:.125rem}:host .__item .__primary{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;text-align:start;font-size:.875rem;line-height:1.25rem;font-weight:500}:host .__item .__secondary{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;text-align:start;font-size:.75rem;line-height:1rem;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{cursor:pointer;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.2s;animation-duration:.2s}.__empty-state:hover{opacity:.8}\n"], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type:
|
|
18693
18728
|
// ACoreX
|
|
18694
18729
|
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$1.AXDecoratorIconComponent, selector: "ax-icon", inputs: ["icon"] }, { kind: "ngmodule", type: AXTranslationModule }, { kind: "component", type:
|
|
18695
18730
|
// Platform
|
|
18696
|
-
AXPStateMessageComponent, selector: "axp-state-message", inputs: ["mode", "icon", "title", "description", "look"] }
|
|
18731
|
+
AXPStateMessageComponent, selector: "axp-state-message", inputs: ["mode", "icon", "title", "description", "look"] }, { kind: "pipe", type:
|
|
18732
|
+
// Angular
|
|
18733
|
+
AsyncPipe, name: "async" }, { kind: "pipe", type: i6.AXTranslatorPipe, name: "translate" }] }); }
|
|
18697
18734
|
}
|
|
18698
18735
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPFileListComponent, decorators: [{
|
|
18699
18736
|
type: Component,
|
|
18700
|
-
args: [{ selector: 'axp-file-list',
|
|
18737
|
+
args: [{ selector: 'axp-file-list', host: {
|
|
18738
|
+
'[class.--look-rows]': 'look() === "rows"',
|
|
18739
|
+
'[class.--look-links]': 'look() === "links"',
|
|
18740
|
+
'[class.--links-inline]': 'look() === "links" && linksLayout() === "inline"',
|
|
18741
|
+
}, imports: [
|
|
18701
18742
|
// Angular
|
|
18702
|
-
|
|
18743
|
+
AsyncPipe,
|
|
18744
|
+
NgClass,
|
|
18703
18745
|
// ACoreX
|
|
18704
18746
|
AXFormModule,
|
|
18705
18747
|
AXTextBoxModule,
|
|
@@ -18710,8 +18752,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
18710
18752
|
AXTranslationModule,
|
|
18711
18753
|
// Platform
|
|
18712
18754
|
AXPStateMessageComponent,
|
|
18713
|
-
], providers: [], template: "@for (file of displayFiles(); track $index) {\n <div\n
|
|
18714
|
-
}], 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 }] }], 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 }] }] } });
|
|
18755
|
+
], 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 ax-flex-shrink-0\"></i>\n <span class=\"ax-min-w-0 ax-break-all\">{{ file.name }}</span>\n </button>\n </li>\n }\n </ul>\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 }} fa-solid\"></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: [":host{display:flex;width:100%;flex-direction:column;gap:.125rem}:host.--look-rows{padding-top:.5rem;padding-bottom:.5rem}:host.--look-links{padding-top:0;padding-bottom:0}:host.--look-links .__links .__links-label{margin-inline-end:.5rem;font-size:.875rem;line-height:1.25rem;font-weight:500;color:rgb(var(--ax-sys-color-on-surface-variant))}:host.--look-links .__links .__links-list{margin:0;margin-top:.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:.75rem;border-radius:.375rem;border-width:0px;background-color:transparent;padding:.625rem .75rem;text-align:start;font-size:.875rem;line-height:1.25rem;text-decoration-line:none;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);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:1rem;border-bottom-width:1px;--tw-border-opacity: 1;border-color:rgba(var(--ax-sys-color-border-surface),var(--tw-border-opacity, 1));padding-bottom:1rem}:host.--look-links:not(.--links-inline) .__links-list{margin-top:0;display:flex;flex-direction:column;gap:.5rem}:host .__item{display:flex;align-items:center;gap:.75rem;border-left-width:4px;border-color:transparent;padding:.5rem}:host .__item.--downloadable{cursor:pointer}:host .__item.--downloadable:hover{background-color:rgb(var(--ax-sys-color-lighter-surface));color:rgb(var(--ax-sys-color-on-lighter-surface));border-color:rgb(var(--ax-sys-color-border-lighter-surface))}:host .__item:not(.--downloadable){cursor:default}:host .__item.--removed{--tw-border-opacity: 1;border-color:rgba(var(--ax-sys-color-danger-500),var(--tw-border-opacity, 1));--tw-bg-opacity: 1;background-color:rgba(var(--ax-sys-color-danger-50),var(--tw-bg-opacity, 1))}:host .__item.--attached{--tw-border-opacity: 1;border-color:rgba(var(--ax-sys-color-success-500),var(--tw-border-opacity, 1));animation:attached-flash 1s ease-out forwards}:host .__item.--item-readonly:not(.--removed){opacity:.85}:host .__item .__icon{width:1.5rem;flex-shrink:0}:host .__item .__name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;text-align:start;font-size:.875rem;line-height:1.25rem;font-weight:500}:host .__item .__content{display:flex;min-width:0px;flex:1 1 0%;flex-direction:column;gap:.125rem}:host .__item .__primary{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;text-align:start;font-size:.875rem;line-height:1.25rem;font-weight:500}:host .__item .__secondary{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;text-align:start;font-size:.75rem;line-height:1rem;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{cursor:pointer;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.2s;animation-duration:.2s}.__empty-state:hover{opacity:.8}\n"] }]
|
|
18756
|
+
}], 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 }] }] } });
|
|
18715
18757
|
|
|
18716
18758
|
//#region ---- Attachment fingerprint (no blob comparison) ----
|
|
18717
18759
|
/** Stable fingerprint for one file row; ignores Blob binary content. */
|
|
@@ -18968,95 +19010,128 @@ function hasFileUploaderTitleOrDescriptionFields(editDialog) {
|
|
|
18968
19010
|
}
|
|
18969
19011
|
//#endregion
|
|
18970
19012
|
|
|
18971
|
-
|
|
18972
|
-
|
|
18973
|
-
|
|
18974
|
-
|
|
18975
|
-
|
|
18976
|
-
|
|
18977
|
-
|
|
19013
|
+
//#region ---- Imports ----
|
|
19014
|
+
//#endregion
|
|
19015
|
+
//#region ---- Title template resolution ----
|
|
19016
|
+
const INVALID_DISPLAY_TEXT = '[object Object]';
|
|
19017
|
+
/** Resolves a row field or format result to a plain display string for the active locale. */
|
|
19018
|
+
function resolveEntityRowDisplayText(value, activeLang) {
|
|
19019
|
+
if (value == null || value === '') {
|
|
19020
|
+
return '';
|
|
18978
19021
|
}
|
|
18979
|
-
|
|
18980
|
-
|
|
18981
|
-
|
|
18982
|
-
|
|
18983
|
-
let files = options?.files;
|
|
18984
|
-
if (entity) {
|
|
18985
|
-
const filesResult = await this.queryExecutor.fetch('FileUploader:LoadFiles', entity);
|
|
18986
|
-
if (!filesResult) {
|
|
18987
|
-
return undefined;
|
|
18988
|
-
}
|
|
18989
|
-
files = filesResult.files ?? [];
|
|
19022
|
+
if (typeof value === 'string') {
|
|
19023
|
+
const text = value.trim();
|
|
19024
|
+
if (!text || text === INVALID_DISPLAY_TEXT || isUnresolvedEntityDisplayTemplate(text)) {
|
|
19025
|
+
return '';
|
|
18990
19026
|
}
|
|
18991
|
-
|
|
18992
|
-
|
|
18993
|
-
|
|
18994
|
-
|
|
18995
|
-
|
|
18996
|
-
|
|
18997
|
-
|
|
18998
|
-
|
|
18999
|
-
|
|
19000
|
-
editDialog: options?.editDialog,
|
|
19001
|
-
};
|
|
19002
|
-
const component = await import('./acorex-platform-layout-entity-file-list-popup.component-_yrP5SQe.mjs').then((m) => m.AXPFileListPopupComponent);
|
|
19003
|
-
const result = await this.popupService.open(component, {
|
|
19004
|
-
title: await this.translate.translateAsync('@document-management:terms.common.file'),
|
|
19005
|
-
data: {
|
|
19006
|
-
files: signal(resolved.files),
|
|
19007
|
-
isReadonly: signal(resolved.readonly),
|
|
19008
|
-
multiple: signal(resolved.multiple),
|
|
19009
|
-
accept: signal(resolved.accept),
|
|
19010
|
-
maxFileSize: signal(resolved.maxFileSize),
|
|
19011
|
-
fileEditable: signal(resolved.fileEditable),
|
|
19012
|
-
showAddItemButton: signal(resolved.showAddItemButton),
|
|
19013
|
-
plugins: signal(resolved.plugins),
|
|
19014
|
-
editDialog: signal(resolved.editDialog),
|
|
19015
|
-
},
|
|
19016
|
-
});
|
|
19017
|
-
const updatedFiles = result?.data?.data?.files;
|
|
19018
|
-
if (!updatedFiles) {
|
|
19019
|
-
return undefined;
|
|
19027
|
+
return text;
|
|
19028
|
+
}
|
|
19029
|
+
if (typeof value === 'number' || typeof value === 'boolean') {
|
|
19030
|
+
return String(value);
|
|
19031
|
+
}
|
|
19032
|
+
if (typeof value === 'object' && !Array.isArray(value)) {
|
|
19033
|
+
const text = resolveMultiLanguageString(value, activeLang).trim();
|
|
19034
|
+
if (!text || text === INVALID_DISPLAY_TEXT) {
|
|
19035
|
+
return '';
|
|
19020
19036
|
}
|
|
19021
|
-
|
|
19022
|
-
|
|
19037
|
+
return text;
|
|
19038
|
+
}
|
|
19039
|
+
return '';
|
|
19040
|
+
}
|
|
19041
|
+
/**
|
|
19042
|
+
* Formats the entity row title for display using entity-driven templates and property paths.
|
|
19043
|
+
*/
|
|
19044
|
+
function formatEntityRowDisplayTitle(entity, rowData, formatService, activeLang) {
|
|
19045
|
+
if (!rowData) {
|
|
19046
|
+
return '';
|
|
19047
|
+
}
|
|
19048
|
+
const titleContext = buildEntitySearchTitleContext(entity ?? {});
|
|
19049
|
+
for (const path of titleContext.fallbackFields) {
|
|
19050
|
+
const fromField = resolveEntityRowDisplayText(get(rowData, path), activeLang);
|
|
19051
|
+
if (fromField) {
|
|
19052
|
+
return fromField;
|
|
19023
19053
|
}
|
|
19024
|
-
|
|
19025
|
-
|
|
19026
|
-
|
|
19027
|
-
|
|
19028
|
-
|
|
19029
|
-
|
|
19054
|
+
}
|
|
19055
|
+
const templates = [
|
|
19056
|
+
resolveEntityRowTitleTemplate(entity),
|
|
19057
|
+
...titleContext.fallbackTemplates,
|
|
19058
|
+
].filter((template) => !!template?.trim());
|
|
19059
|
+
for (const template of templates) {
|
|
19060
|
+
const formatted = formatService.format(normalizeEntityDisplayTemplate(template), 'string', rowData);
|
|
19061
|
+
const fromTemplate = resolveEntityRowDisplayText(formatted, activeLang);
|
|
19062
|
+
if (fromTemplate) {
|
|
19063
|
+
return fromTemplate;
|
|
19030
19064
|
}
|
|
19031
|
-
return updatedFiles;
|
|
19032
19065
|
}
|
|
19033
|
-
|
|
19034
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPFileUploaderWidgetService, providedIn: 'root' }); }
|
|
19066
|
+
return '';
|
|
19035
19067
|
}
|
|
19036
|
-
|
|
19037
|
-
type: Injectable,
|
|
19038
|
-
args: [{
|
|
19039
|
-
providedIn: 'root',
|
|
19040
|
-
}]
|
|
19041
|
-
}] });
|
|
19068
|
+
//#endregion
|
|
19042
19069
|
|
|
19070
|
+
//#region ---- Imports ----
|
|
19071
|
+
//#endregion
|
|
19072
|
+
//#region ---- Component ----
|
|
19043
19073
|
class AXPFileUploaderWidgetColumnComponent extends AXPColumnWidgetComponent {
|
|
19044
19074
|
//#endregion
|
|
19045
19075
|
//#region ---- Lifecycle ----
|
|
19046
19076
|
constructor() {
|
|
19047
19077
|
super();
|
|
19078
|
+
//#region ---- Services & Dependencies ----
|
|
19079
|
+
this.queryExecutor = inject(AXPQueryExecutor);
|
|
19080
|
+
this.entityResolver = inject(AXPEntityDefinitionRegistryService);
|
|
19081
|
+
this.formatService = inject(AXFormatService);
|
|
19082
|
+
this.translation = inject(AXTranslationService);
|
|
19083
|
+
//#endregion
|
|
19084
|
+
//#region ---- View Children ----
|
|
19085
|
+
this.fileListTrigger = viewChild('fileListTrigger', ...(ngDevMode ? [{ debugName: "fileListTrigger" }] : /* istanbul ignore next */ []));
|
|
19086
|
+
this.fileListPopover = viewChild('fileListPopover', ...(ngDevMode ? [{ debugName: "fileListPopover" }] : /* istanbul ignore next */ []));
|
|
19087
|
+
//#endregion
|
|
19048
19088
|
//#region ---- State ----
|
|
19049
19089
|
this.fileCount = signal(0, ...(ngDevMode ? [{ debugName: "fileCount" }] : /* istanbul ignore next */ []));
|
|
19090
|
+
this.popoverFiles = signal([], ...(ngDevMode ? [{ debugName: "popoverFiles" }] : /* istanbul ignore next */ []));
|
|
19091
|
+
this.loadStatus = signal('idle', ...(ngDevMode ? [{ debugName: "loadStatus" }] : /* istanbul ignore next */ []));
|
|
19092
|
+
this.loadError = signal(null, ...(ngDevMode ? [{ debugName: "loadError" }] : /* istanbul ignore next */ []));
|
|
19093
|
+
this.isPopoverOpen = signal(false, ...(ngDevMode ? [{ debugName: "isPopoverOpen" }] : /* istanbul ignore next */ []));
|
|
19094
|
+
this.entityDef = signal(null, ...(ngDevMode ? [{ debugName: "entityDef" }] : /* istanbul ignore next */ []));
|
|
19050
19095
|
this.loadRequestId = 0;
|
|
19096
|
+
this.popoverLoadRequestId = 0;
|
|
19051
19097
|
//#endregion
|
|
19052
|
-
//#region ----
|
|
19053
|
-
this.
|
|
19054
|
-
this.
|
|
19098
|
+
//#region ---- Computed ----
|
|
19099
|
+
this.entityScope = computed(() => resolveFileUploaderEntityScope(this.options ?? {}, this.rowData, this.path), ...(ngDevMode ? [{ debugName: "entityScope" }] : /* istanbul ignore next */ []));
|
|
19100
|
+
this.popoverTitle = computed(() => {
|
|
19101
|
+
const row = this.rowData;
|
|
19102
|
+
const lang = this.translation.getActiveLang();
|
|
19103
|
+
const titleRaw = get(row, 'title');
|
|
19104
|
+
if (titleRaw != null &&
|
|
19105
|
+
typeof titleRaw === 'object' &&
|
|
19106
|
+
!Array.isArray(titleRaw) &&
|
|
19107
|
+
this.translation.isValidMultiLanguageObject(titleRaw)) {
|
|
19108
|
+
return titleRaw;
|
|
19109
|
+
}
|
|
19110
|
+
const resolved = formatEntityRowDisplayTitle(this.entityDef(), row, this.formatService, lang);
|
|
19111
|
+
if (resolved) {
|
|
19112
|
+
return resolved;
|
|
19113
|
+
}
|
|
19114
|
+
const fallback = this.entityDef()?.formats?.individual;
|
|
19115
|
+
if (fallback != null && fallback !== '') {
|
|
19116
|
+
return resolveEntityRowDisplayText(fallback, lang) || fallback;
|
|
19117
|
+
}
|
|
19118
|
+
return '@document-management:terms.common.file';
|
|
19119
|
+
}, ...(ngDevMode ? [{ debugName: "popoverTitle" }] : /* istanbul ignore next */ []));
|
|
19055
19120
|
effect(() => {
|
|
19056
19121
|
const raw = this.rawValue;
|
|
19057
19122
|
const row = this.rowData;
|
|
19058
19123
|
const opts = this.options ?? {};
|
|
19059
19124
|
const path = this.path;
|
|
19125
|
+
const rowId = row?.['id'] != null ? String(row['id']) : undefined;
|
|
19126
|
+
if (rowId !== this.previousRowId) {
|
|
19127
|
+
this.previousRowId = rowId;
|
|
19128
|
+
untracked(() => {
|
|
19129
|
+
this.popoverFiles.set([]);
|
|
19130
|
+
this.loadStatus.set('idle');
|
|
19131
|
+
this.loadError.set(null);
|
|
19132
|
+
this.popoverLoadRequestId++;
|
|
19133
|
+
});
|
|
19134
|
+
}
|
|
19060
19135
|
const inlineCount = attachmentFieldCount(raw);
|
|
19061
19136
|
if (inlineCount > 0) {
|
|
19062
19137
|
untracked(() => this.fileCount.set(inlineCount));
|
|
@@ -19069,7 +19144,6 @@ class AXPFileUploaderWidgetColumnComponent extends AXPColumnWidgetComponent {
|
|
|
19069
19144
|
}
|
|
19070
19145
|
const requestId = ++this.loadRequestId;
|
|
19071
19146
|
untracked(() => {
|
|
19072
|
-
console.log('loadFiles for count', entity);
|
|
19073
19147
|
void this.queryExecutor
|
|
19074
19148
|
.fetch('FileUploader:LoadFiles', entity)
|
|
19075
19149
|
.then((result) => {
|
|
@@ -19086,51 +19160,102 @@ class AXPFileUploaderWidgetColumnComponent extends AXPColumnWidgetComponent {
|
|
|
19086
19160
|
});
|
|
19087
19161
|
});
|
|
19088
19162
|
});
|
|
19163
|
+
effect(() => {
|
|
19164
|
+
const scope = this.entityScope();
|
|
19165
|
+
if (!scope?.name?.includes('.')) {
|
|
19166
|
+
this.entityDef.set(null);
|
|
19167
|
+
return;
|
|
19168
|
+
}
|
|
19169
|
+
const [module, name] = scope.name.split('.');
|
|
19170
|
+
void this.entityResolver.resolve(module, name).then((def) => this.entityDef.set(def));
|
|
19171
|
+
});
|
|
19089
19172
|
}
|
|
19090
19173
|
//#endregion
|
|
19091
19174
|
//#region ---- UI Handlers ----
|
|
19092
|
-
|
|
19093
|
-
|
|
19094
|
-
|
|
19095
|
-
entity,
|
|
19096
|
-
readonly: true,
|
|
19097
|
-
});
|
|
19175
|
+
openFileListPopover() {
|
|
19176
|
+
this.openPopoverFromRef(this.fileListTrigger());
|
|
19177
|
+
void this.ensurePopoverFilesLoaded();
|
|
19098
19178
|
}
|
|
19099
|
-
|
|
19100
|
-
|
|
19101
|
-
|
|
19102
|
-
|
|
19103
|
-
|
|
19104
|
-
|
|
19105
|
-
|
|
19106
|
-
|
|
19107
|
-
|
|
19108
|
-
|
|
19109
|
-
|
|
19179
|
+
onPopoverOpenChange(event) {
|
|
19180
|
+
const open = this.coercePopoverOpenEvent(event);
|
|
19181
|
+
this.isPopoverOpen.set(open);
|
|
19182
|
+
if (open) {
|
|
19183
|
+
void this.ensurePopoverFilesLoaded();
|
|
19184
|
+
}
|
|
19185
|
+
}
|
|
19186
|
+
//#endregion
|
|
19187
|
+
//#region ---- Private Methods ----
|
|
19188
|
+
openPopoverFromRef(ref) {
|
|
19189
|
+
const popover = this.fileListPopover();
|
|
19190
|
+
if (popover && ref) {
|
|
19191
|
+
popover.target = ref.nativeElement;
|
|
19192
|
+
popover.open();
|
|
19193
|
+
this.isPopoverOpen.set(true);
|
|
19194
|
+
}
|
|
19195
|
+
}
|
|
19196
|
+
async ensurePopoverFilesLoaded() {
|
|
19197
|
+
if (this.loadStatus() === 'loading') {
|
|
19198
|
+
return;
|
|
19199
|
+
}
|
|
19200
|
+
const inline = normalizeEntityFieldToFileList(this.rawValue);
|
|
19201
|
+
if (inline.length > 0) {
|
|
19202
|
+
this.popoverFiles.set(inline);
|
|
19203
|
+
this.loadStatus.set('ready');
|
|
19204
|
+
this.loadError.set(null);
|
|
19205
|
+
return;
|
|
19206
|
+
}
|
|
19207
|
+
const entity = this.entityScope();
|
|
19208
|
+
if (!entity) {
|
|
19209
|
+
this.popoverFiles.set([]);
|
|
19210
|
+
this.loadStatus.set('ready');
|
|
19211
|
+
return;
|
|
19212
|
+
}
|
|
19213
|
+
if (this.loadStatus() === 'ready' && this.popoverFiles().length > 0) {
|
|
19214
|
+
return;
|
|
19215
|
+
}
|
|
19216
|
+
const requestId = ++this.popoverLoadRequestId;
|
|
19217
|
+
this.loadStatus.set('loading');
|
|
19218
|
+
this.loadError.set(null);
|
|
19219
|
+
try {
|
|
19220
|
+
const result = await this.queryExecutor.fetch('FileUploader:LoadFiles', entity);
|
|
19221
|
+
if (requestId !== this.popoverLoadRequestId) {
|
|
19222
|
+
return;
|
|
19223
|
+
}
|
|
19224
|
+
this.popoverFiles.set(result?.files ?? []);
|
|
19225
|
+
this.loadStatus.set('ready');
|
|
19226
|
+
}
|
|
19227
|
+
catch {
|
|
19228
|
+
if (requestId !== this.popoverLoadRequestId) {
|
|
19229
|
+
return;
|
|
19230
|
+
}
|
|
19231
|
+
const msg = await this.translation.translateAsync('@general:widgets.lookup.column.load-failed');
|
|
19232
|
+
this.loadError.set(msg);
|
|
19233
|
+
this.loadStatus.set('error');
|
|
19234
|
+
this.popoverFiles.set([]);
|
|
19235
|
+
}
|
|
19236
|
+
}
|
|
19237
|
+
coercePopoverOpenEvent(event) {
|
|
19238
|
+
if (typeof event === 'boolean') {
|
|
19239
|
+
return event;
|
|
19240
|
+
}
|
|
19241
|
+
if (event && typeof event === 'object') {
|
|
19242
|
+
const o = event;
|
|
19243
|
+
if ('detail' in o) {
|
|
19244
|
+
return Boolean(o['detail']);
|
|
19245
|
+
}
|
|
19246
|
+
if ('open' in o) {
|
|
19247
|
+
return Boolean(o['open']);
|
|
19248
|
+
}
|
|
19249
|
+
}
|
|
19250
|
+
return false;
|
|
19110
19251
|
}
|
|
19111
|
-
|
|
19252
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPFileUploaderWidgetColumnComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
19253
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: AXPFileUploaderWidgetColumnComponent, isStandalone: true, selector: "axp-file-uploader-widget-column", inputs: { rawValue: "rawValue", rowData: "rowData" }, viewQueries: [{ propertyName: "fileListTrigger", first: true, predicate: ["fileListTrigger"], descendants: true, isSignal: true }, { propertyName: "fileListPopover", first: true, predicate: ["fileListPopover"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "@if (fileCount() > 0) {\n<span class=\"ax-cursor-pointer ax-text-primary ax-underline\" (click)=\"openFileListPopover(); $event.stopPropagation()\"\n #fileListTrigger>\n {{\n '@general:widgets.file-uploader.column.files-count'\n | translate: { params: { count: fileCount() } }\n | async\n }}\n</span>\n} @else {\n<span class=\"ax-text-muted\">---</span>\n}\n\n<ax-popover [openOn]=\"'manual'\" #fileListPopover (openChange)=\"onPopoverOpenChange($event)\">\n <div class=\"ax-lightest-surface dark:ax-dark-surface ax-min-w-[17.5rem] ax-max-w-[26rem] ax-overflow-hidden\">\n <div class=\"ax-border-b ax-border-surface ax-px-4 ax-py-3\">\n <h3 class=\"ax-m-0 ax-text-sm ax-font-semibold ax-leading-normal ax-text-on-lightest-surface\">\n {{ popoverTitle() | translate | async }}\n </h3>\n </div>\n <div class=\"ax-max-h-64 ax-overflow-y-auto ax-px-3 ax-py-3\" style=\"max-height: max(30dvh, 12rem)\">\n @if (loadStatus() === 'loading') {\n <div class=\"ax-flex ax-min-h-[120px] ax-items-center ax-justify-center\">\n <ax-loading></ax-loading>\n </div>\n } @else if (loadStatus() === 'error') {\n <div class=\"ax-text-danger ax-text-sm\">{{ loadError() }}</div>\n } @else {\n <axp-file-list [files]=\"popoverFiles()\" look=\"links\" linksLayout=\"menu\" [showLabel]=\"false\" [readonly]=\"true\" />\n }\n </div>\n </div>\n</ax-popover>", dependencies: [{ kind: "ngmodule", type: AXTranslationModule }, { kind: "ngmodule", type: AXPopoverModule }, { kind: "component", type: i2.AXPopoverComponent, selector: "ax-popover", inputs: ["width", "disablePanelClass", "disabled", "offsetX", "offsetY", "target", "placement", "content", "openOn", "closeOn", "hasBackdrop", "openAfter", "closeAfter", "closeOnScroll", "backdropClass", "panelClass", "adaptivityEnabled"], outputs: ["onOpened", "onClosed"] }, { kind: "ngmodule", type: AXLoadingModule }, { kind: "component", type: i1$2.AXLoadingComponent, selector: "ax-loading", inputs: ["visible", "type", "context"], outputs: ["visibleChange"] }, { kind: "component", type: AXPFileListComponent, selector: "axp-file-list", inputs: ["readonly", "fileEditable", "enableTitleDescription", "multiple", "look", "titleKey", "showLabel", "linksLayout", "files", "plugins", "excludePlugins", "capabilities"], outputs: ["onRemove", "onRevert", "onRename"] }, { kind: "pipe", type: i6.AXTranslatorPipe, name: "translate" }, { kind: "pipe", type: AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
19112
19254
|
}
|
|
19113
19255
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPFileUploaderWidgetColumnComponent, decorators: [{
|
|
19114
19256
|
type: Component,
|
|
19115
|
-
args: [{
|
|
19116
|
-
|
|
19117
|
-
template: `
|
|
19118
|
-
@if (fileCount() > 0) {
|
|
19119
|
-
<span
|
|
19120
|
-
class="ax-cursor-pointer ax-text-primary ax-underline"
|
|
19121
|
-
(click)="openFileList()"
|
|
19122
|
-
>
|
|
19123
|
-
{{ fileCount() }} {{ '@document-management:file' | translate | async }}
|
|
19124
|
-
</span>
|
|
19125
|
-
} @else {
|
|
19126
|
-
<span class="ax-text-muted">---</span>
|
|
19127
|
-
}
|
|
19128
|
-
`,
|
|
19129
|
-
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
19130
|
-
imports: [AXTranslationModule, AsyncPipe],
|
|
19131
|
-
inputs: ['rawValue', 'rowData'],
|
|
19132
|
-
}]
|
|
19133
|
-
}], ctorParameters: () => [] });
|
|
19257
|
+
args: [{ selector: 'axp-file-uploader-widget-column', changeDetection: ChangeDetectionStrategy.OnPush, imports: [AXTranslationModule, AsyncPipe, AXPopoverModule, AXLoadingModule, AXPFileListComponent], inputs: ['rawValue', 'rowData'], template: "@if (fileCount() > 0) {\n<span class=\"ax-cursor-pointer ax-text-primary ax-underline\" (click)=\"openFileListPopover(); $event.stopPropagation()\"\n #fileListTrigger>\n {{\n '@general:widgets.file-uploader.column.files-count'\n | translate: { params: { count: fileCount() } }\n | async\n }}\n</span>\n} @else {\n<span class=\"ax-text-muted\">---</span>\n}\n\n<ax-popover [openOn]=\"'manual'\" #fileListPopover (openChange)=\"onPopoverOpenChange($event)\">\n <div class=\"ax-lightest-surface dark:ax-dark-surface ax-min-w-[17.5rem] ax-max-w-[26rem] ax-overflow-hidden\">\n <div class=\"ax-border-b ax-border-surface ax-px-4 ax-py-3\">\n <h3 class=\"ax-m-0 ax-text-sm ax-font-semibold ax-leading-normal ax-text-on-lightest-surface\">\n {{ popoverTitle() | translate | async }}\n </h3>\n </div>\n <div class=\"ax-max-h-64 ax-overflow-y-auto ax-px-3 ax-py-3\" style=\"max-height: max(30dvh, 12rem)\">\n @if (loadStatus() === 'loading') {\n <div class=\"ax-flex ax-min-h-[120px] ax-items-center ax-justify-center\">\n <ax-loading></ax-loading>\n </div>\n } @else if (loadStatus() === 'error') {\n <div class=\"ax-text-danger ax-text-sm\">{{ loadError() }}</div>\n } @else {\n <axp-file-list [files]=\"popoverFiles()\" look=\"links\" linksLayout=\"menu\" [showLabel]=\"false\" [readonly]=\"true\" />\n }\n </div>\n </div>\n</ax-popover>" }]
|
|
19258
|
+
}], ctorParameters: () => [], propDecorators: { fileListTrigger: [{ type: i0.ViewChild, args: ['fileListTrigger', { isSignal: true }] }], fileListPopover: [{ type: i0.ViewChild, args: ['fileListPopover', { isSignal: true }] }] } });
|
|
19134
19259
|
|
|
19135
19260
|
var fileUploaderWidgetColumn_component = /*#__PURE__*/Object.freeze({
|
|
19136
19261
|
__proto__: null,
|
|
@@ -19341,6 +19466,7 @@ class AXPFileUploaderWidgetEditComponent extends AXPValueWidgetComponent {
|
|
|
19341
19466
|
}
|
|
19342
19467
|
this.isDirtySignal.set(dirty);
|
|
19343
19468
|
this.dirtyChangeSubject.next(dirty);
|
|
19469
|
+
this.layoutService.notifyWidgetDirtyChanged();
|
|
19344
19470
|
}
|
|
19345
19471
|
//#endregion
|
|
19346
19472
|
async loadActions() {
|
|
@@ -19574,7 +19700,7 @@ class AXPFileUploaderWidgetEditComponent extends AXPValueWidgetComponent {
|
|
|
19574
19700
|
(onRename)="handleFileRename($event)"
|
|
19575
19701
|
></axp-file-list>
|
|
19576
19702
|
</div>
|
|
19577
|
-
`, isInline: true, styles: [":host{border-color:rgba(var(--ax-comp-editor-border-color))}:host.axp-file-uploader-widget-edit--borderless{border:none!important}.__drag-over{background-color:rgba(var(--ax-sys-color-primary-50),.1);border:2px dashed rgb(var(--ax-sys-color-primary-500));border-radius:.375rem}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { 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: "component", type: i1.AXButtonItemComponent, selector: "ax-button-item", inputs: ["color", "disabled", "text", "selected", "divided", "data", "name"], outputs: ["onClick", "onFocus", "onBlur", "disabledChange"] }, { kind: "component", type: i1.AXButtonItemListComponent, selector: "ax-button-item-list", inputs: ["items", "closeParentOnClick", "lockOnLoading"], outputs: ["onItemClick"] }, { kind: "directive", type: AXUploaderZoneDirective, selector: "[axUploaderZone]", inputs: ["multiple", "accept", "overlayTemplate", "disableBrowse", "disableDragDrop"], outputs: ["fileChange", "onChanged", "dragEnter", "dragLeave", "dragOver", "onFileUploadComplete", "onFilesUploadComplete"] }, { kind: "ngmodule", type: AXDecoratorModule }, { kind: "component", type: i3$1.AXDecoratorIconComponent, selector: "ax-icon", inputs: ["icon"] }, { kind: "component", type: i3$1.AXDecoratorGenericComponent, selector: "ax-footer, ax-header, ax-content, ax-divider, ax-form-hint, ax-prefix, ax-suffix, ax-text, ax-title, ax-subtitle, ax-placeholder, ax-overlay" }, { kind: "ngmodule", type: AXLoadingModule }, { kind: "ngmodule", type: AXDropdownModule }, { kind: "component", type: i3$3.AXDropdownPanelComponent, selector: "ax-dropdown-panel", inputs: ["isOpen", "fitParent", "dropdownWidth", "position", "placement", "_target", "adaptivityEnabled"], outputs: ["onOpened", "onClosed"] }, { kind: "ngmodule", type: AXPComponentSlotModule }, { kind: "ngmodule", type: AXTranslationModule }, { kind: "directive", type: i6.AXTranslatorDirective, selector: "[translate]" }, { kind: "component", type: AXPFileListComponent, selector: "axp-file-list", inputs: ["readonly", "fileEditable", "enableTitleDescription", "multiple", "files", "plugins", "excludePlugins", "capabilities"], outputs: ["onRemove", "onRevert", "onRename"] }, { kind: "pipe", type: i5.AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
19703
|
+
`, isInline: true, styles: [":host{border-color:rgba(var(--ax-comp-editor-border-color))}:host.axp-file-uploader-widget-edit--borderless{border:none!important}.__drag-over{background-color:rgba(var(--ax-sys-color-primary-50),.1);border:2px dashed rgb(var(--ax-sys-color-primary-500));border-radius:.375rem}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { 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: "component", type: i1.AXButtonItemComponent, selector: "ax-button-item", inputs: ["color", "disabled", "text", "selected", "divided", "data", "name"], outputs: ["onClick", "onFocus", "onBlur", "disabledChange"] }, { kind: "component", type: i1.AXButtonItemListComponent, selector: "ax-button-item-list", inputs: ["items", "closeParentOnClick", "lockOnLoading"], outputs: ["onItemClick"] }, { kind: "directive", type: AXUploaderZoneDirective, selector: "[axUploaderZone]", inputs: ["multiple", "accept", "fileType", "overlayTemplate", "disableBrowse", "disableDragDrop"], outputs: ["fileChange", "onChanged", "dragEnter", "dragLeave", "dragOver", "onFileUploadComplete", "onFilesUploadComplete"] }, { kind: "ngmodule", type: AXDecoratorModule }, { kind: "component", type: i3$1.AXDecoratorIconComponent, selector: "ax-icon", inputs: ["icon"] }, { kind: "component", type: i3$1.AXDecoratorGenericComponent, selector: "ax-footer, ax-header, ax-content, ax-divider, ax-form-hint, ax-prefix, ax-suffix, ax-text, ax-title, ax-subtitle, ax-placeholder, ax-overlay" }, { kind: "ngmodule", type: AXLoadingModule }, { kind: "ngmodule", type: AXDropdownModule }, { kind: "component", type: i3$3.AXDropdownPanelComponent, selector: "ax-dropdown-panel", inputs: ["isOpen", "fitParent", "dropdownWidth", "position", "placement", "_target", "adaptivityEnabled"], outputs: ["onOpened", "onClosed"] }, { kind: "ngmodule", type: AXPComponentSlotModule }, { kind: "ngmodule", type: AXTranslationModule }, { kind: "directive", type: i6.AXTranslatorDirective, selector: "[translate]" }, { kind: "component", type: AXPFileListComponent, selector: "axp-file-list", inputs: ["readonly", "fileEditable", "enableTitleDescription", "multiple", "look", "titleKey", "showLabel", "linksLayout", "files", "plugins", "excludePlugins", "capabilities"], outputs: ["onRemove", "onRevert", "onRename"] }, { kind: "pipe", type: i5.AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
19578
19704
|
}
|
|
19579
19705
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPFileUploaderWidgetEditComponent, decorators: [{
|
|
19580
19706
|
type: Component,
|
|
@@ -19666,7 +19792,7 @@ class AXPFileUploaderWidgetViewComponent extends AXPValueWidgetComponent {
|
|
|
19666
19792
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPFileUploaderWidgetViewComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
|
|
19667
19793
|
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.9", type: AXPFileUploaderWidgetViewComponent, isStandalone: true, selector: "axp-file-uploader-widget-view", host: { properties: { "class": "this.__class" } }, usesInheritance: true, ngImport: i0, template: `
|
|
19668
19794
|
<axp-file-list [files]="files()" [readonly]="true"></axp-file-list>
|
|
19669
|
-
`, isInline: true, dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: AXButtonModule }, { kind: "ngmodule", type: AXDecoratorModule }, { kind: "ngmodule", type: AXUploaderModule }, { kind: "ngmodule", type: AXLoadingModule }, { kind: "ngmodule", type: AXTranslationModule }, { kind: "component", type: AXPFileListComponent, selector: "axp-file-list", inputs: ["readonly", "fileEditable", "enableTitleDescription", "multiple", "files", "plugins", "excludePlugins", "capabilities"], outputs: ["onRemove", "onRevert", "onRename"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
19795
|
+
`, isInline: true, dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: AXButtonModule }, { kind: "ngmodule", type: AXDecoratorModule }, { kind: "ngmodule", type: AXUploaderModule }, { kind: "ngmodule", type: AXLoadingModule }, { kind: "ngmodule", type: AXTranslationModule }, { kind: "component", type: AXPFileListComponent, selector: "axp-file-list", inputs: ["readonly", "fileEditable", "enableTitleDescription", "multiple", "look", "titleKey", "showLabel", "linksLayout", "files", "plugins", "excludePlugins", "capabilities"], outputs: ["onRemove", "onRevert", "onRename"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
19670
19796
|
}
|
|
19671
19797
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPFileUploaderWidgetViewComponent, decorators: [{
|
|
19672
19798
|
type: Component,
|
|
@@ -19773,6 +19899,78 @@ const AXPFileUploaderWidget = {
|
|
|
19773
19899
|
},
|
|
19774
19900
|
};
|
|
19775
19901
|
|
|
19902
|
+
class AXPFileUploaderWidgetService {
|
|
19903
|
+
constructor() {
|
|
19904
|
+
//#region ---- Services & Dependencies ----
|
|
19905
|
+
this.popupService = inject(AXPopupService);
|
|
19906
|
+
this.translate = inject(AXTranslationService);
|
|
19907
|
+
this.commandExecutor = inject(AXPCommandExecutor);
|
|
19908
|
+
this.queryExecutor = inject(AXPQueryExecutor);
|
|
19909
|
+
}
|
|
19910
|
+
//#endregion
|
|
19911
|
+
//#region ---- Public API ----
|
|
19912
|
+
async showFileList(options) {
|
|
19913
|
+
const entity = options?.entity;
|
|
19914
|
+
let files = options?.files;
|
|
19915
|
+
if (entity) {
|
|
19916
|
+
const filesResult = await this.queryExecutor.fetch('FileUploader:LoadFiles', entity);
|
|
19917
|
+
if (!filesResult) {
|
|
19918
|
+
return undefined;
|
|
19919
|
+
}
|
|
19920
|
+
files = filesResult.files ?? [];
|
|
19921
|
+
}
|
|
19922
|
+
const resolved = {
|
|
19923
|
+
files: files ?? [],
|
|
19924
|
+
readonly: options?.readonly ?? false,
|
|
19925
|
+
multiple: options?.multiple ?? false,
|
|
19926
|
+
accept: options?.accept ?? '*',
|
|
19927
|
+
fileEditable: options?.fileEditable ?? true,
|
|
19928
|
+
maxFileSize: options?.maxFileSize ?? 1024 * 1024 * 10,
|
|
19929
|
+
showAddItemButton: options?.showAddItemButton ?? true,
|
|
19930
|
+
plugins: options?.plugins ?? [],
|
|
19931
|
+
editDialog: options?.editDialog,
|
|
19932
|
+
};
|
|
19933
|
+
const component = await import('./acorex-platform-layout-entity-file-list-popup.component-_yrP5SQe.mjs').then((m) => m.AXPFileListPopupComponent);
|
|
19934
|
+
const result = await this.popupService.open(component, {
|
|
19935
|
+
title: await this.translate.translateAsync('@document-management:terms.common.file'),
|
|
19936
|
+
data: {
|
|
19937
|
+
files: signal(resolved.files),
|
|
19938
|
+
isReadonly: signal(resolved.readonly),
|
|
19939
|
+
multiple: signal(resolved.multiple),
|
|
19940
|
+
accept: signal(resolved.accept),
|
|
19941
|
+
maxFileSize: signal(resolved.maxFileSize),
|
|
19942
|
+
fileEditable: signal(resolved.fileEditable),
|
|
19943
|
+
showAddItemButton: signal(resolved.showAddItemButton),
|
|
19944
|
+
plugins: signal(resolved.plugins),
|
|
19945
|
+
editDialog: signal(resolved.editDialog),
|
|
19946
|
+
},
|
|
19947
|
+
});
|
|
19948
|
+
const updatedFiles = result?.data?.data?.files;
|
|
19949
|
+
if (!updatedFiles) {
|
|
19950
|
+
return undefined;
|
|
19951
|
+
}
|
|
19952
|
+
if (!entity) {
|
|
19953
|
+
return updatedFiles;
|
|
19954
|
+
}
|
|
19955
|
+
const saveResult = await this.commandExecutor.execute('FileUploader:SaveFiles', {
|
|
19956
|
+
...entity,
|
|
19957
|
+
files: updatedFiles,
|
|
19958
|
+
});
|
|
19959
|
+
if (!saveResult?.success) {
|
|
19960
|
+
return undefined;
|
|
19961
|
+
}
|
|
19962
|
+
return updatedFiles;
|
|
19963
|
+
}
|
|
19964
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPFileUploaderWidgetService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
19965
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPFileUploaderWidgetService, providedIn: 'root' }); }
|
|
19966
|
+
}
|
|
19967
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPFileUploaderWidgetService, decorators: [{
|
|
19968
|
+
type: Injectable,
|
|
19969
|
+
args: [{
|
|
19970
|
+
providedIn: 'root',
|
|
19971
|
+
}]
|
|
19972
|
+
}] });
|
|
19973
|
+
|
|
19776
19974
|
var index = /*#__PURE__*/Object.freeze({
|
|
19777
19975
|
__proto__: null,
|
|
19778
19976
|
AXPEditFileUploaderCommand: AXPEditFileUploaderCommand,
|
|
@@ -21211,7 +21409,7 @@ class AXPLayoutAdapterBuilder {
|
|
|
21211
21409
|
load: this.createLoadFunction(),
|
|
21212
21410
|
pages: this.adapter.pages || [],
|
|
21213
21411
|
exitUrl: this.adapter.exitUrl,
|
|
21214
|
-
|
|
21412
|
+
getPageBadge: (context, isDirty) => this.badgeStatusService.getPageBadge(name, context, isDirty),
|
|
21215
21413
|
getPageStatus: (context, currentPage) => this.badgeStatusService.getPageStatus(name, context, currentPage),
|
|
21216
21414
|
};
|
|
21217
21415
|
}
|
|
@@ -21556,7 +21754,7 @@ class AXPPageComponentConverter {
|
|
|
21556
21754
|
return empty;
|
|
21557
21755
|
}
|
|
21558
21756
|
const tempInjector = Injector.create({
|
|
21559
|
-
providers: [{ provide: ComponentType, useClass: ComponentType }],
|
|
21757
|
+
providers: [AXPContextStore, { provide: ComponentType, useClass: ComponentType }],
|
|
21560
21758
|
parent: this.injector,
|
|
21561
21759
|
});
|
|
21562
21760
|
const componentInstance = tempInjector.get(ComponentType);
|
|
@@ -21754,6 +21952,20 @@ class AXPPageDetailsConverter extends AXPBaseRelatedEntityConverter {
|
|
|
21754
21952
|
if (e.name === 'update-entity') {
|
|
21755
21953
|
const fn = entityDef?.commands?.update?.execute;
|
|
21756
21954
|
const result = await fn(context);
|
|
21955
|
+
if (result && typeof result === 'object' && 'success' in result) {
|
|
21956
|
+
const cmd = result;
|
|
21957
|
+
if (!cmd.success) {
|
|
21958
|
+
const errorText = commandMessageTextForError(cmd.message?.text) || 'Failed to update entity';
|
|
21959
|
+
return {
|
|
21960
|
+
success: false,
|
|
21961
|
+
message: cmd.message ?? { text: errorText },
|
|
21962
|
+
};
|
|
21963
|
+
}
|
|
21964
|
+
return {
|
|
21965
|
+
success: true,
|
|
21966
|
+
data: cmd.data ?? result,
|
|
21967
|
+
};
|
|
21968
|
+
}
|
|
21757
21969
|
return {
|
|
21758
21970
|
success: true,
|
|
21759
21971
|
data: result,
|
|
@@ -21807,12 +22019,7 @@ class AXPPageDetailsConverter extends AXPBaseRelatedEntityConverter {
|
|
|
21807
22019
|
}
|
|
21808
22020
|
}
|
|
21809
22021
|
catch (error) {
|
|
21810
|
-
return
|
|
21811
|
-
success: false,
|
|
21812
|
-
message: {
|
|
21813
|
-
text: error,
|
|
21814
|
-
},
|
|
21815
|
-
};
|
|
22022
|
+
return mapEntityStorageErrorToCommandResult(error);
|
|
21816
22023
|
}
|
|
21817
22024
|
};
|
|
21818
22025
|
}
|
|
@@ -21890,6 +22097,8 @@ class AXPPageListConverter extends AXPBaseRelatedEntityConverter {
|
|
|
21890
22097
|
title: context.rootTitle ?? '',
|
|
21891
22098
|
label: relatedEntity.title,
|
|
21892
22099
|
icon: relatedEntity.icon || entityDef.icon,
|
|
22100
|
+
/** List-only page: scoped filters are system state, not user edits. */
|
|
22101
|
+
isReadonly: true,
|
|
21893
22102
|
actions: this.mergeActions(entityDef, actions)
|
|
21894
22103
|
?.filter((a) => a.priority === 'primary' && !a.isChild)
|
|
21895
22104
|
?.map((a) => {
|
|
@@ -22572,6 +22781,7 @@ class AXPMainEntityContentBuilder {
|
|
|
22572
22781
|
icon: 'fa-light fa-rotate-left',
|
|
22573
22782
|
color: 'default',
|
|
22574
22783
|
visible: '{{context.isDirty()}}',
|
|
22784
|
+
shortcuts: ['escape'],
|
|
22575
22785
|
command: {
|
|
22576
22786
|
name: 'discard',
|
|
22577
22787
|
},
|
|
@@ -22581,6 +22791,7 @@ class AXPMainEntityContentBuilder {
|
|
|
22581
22791
|
icon: 'fa-light fa-floppy-disk',
|
|
22582
22792
|
color: 'primary',
|
|
22583
22793
|
visible: '{{context.isDirty()}}',
|
|
22794
|
+
shortcuts: ['ctrl+s'],
|
|
22584
22795
|
command: {
|
|
22585
22796
|
name: 'update-entity',
|
|
22586
22797
|
},
|
|
@@ -22604,6 +22815,20 @@ class AXPMainEntityContentBuilder {
|
|
|
22604
22815
|
if (e.name == 'update-entity') {
|
|
22605
22816
|
const fn = entity?.commands?.update?.execute;
|
|
22606
22817
|
const result = await fn(context);
|
|
22818
|
+
if (result && typeof result === 'object' && 'success' in result) {
|
|
22819
|
+
const cmd = result;
|
|
22820
|
+
if (!cmd.success) {
|
|
22821
|
+
const errorText = commandMessageTextForError(cmd.message?.text) || 'Failed to update entity';
|
|
22822
|
+
return {
|
|
22823
|
+
success: false,
|
|
22824
|
+
message: cmd.message ?? { text: errorText },
|
|
22825
|
+
};
|
|
22826
|
+
}
|
|
22827
|
+
return {
|
|
22828
|
+
success: true,
|
|
22829
|
+
data: cmd.data ?? result,
|
|
22830
|
+
};
|
|
22831
|
+
}
|
|
22607
22832
|
return {
|
|
22608
22833
|
success: true,
|
|
22609
22834
|
data: result,
|
|
@@ -22657,12 +22882,7 @@ class AXPMainEntityContentBuilder {
|
|
|
22657
22882
|
}
|
|
22658
22883
|
}
|
|
22659
22884
|
catch (error) {
|
|
22660
|
-
return
|
|
22661
|
-
success: false,
|
|
22662
|
-
message: {
|
|
22663
|
-
text: error,
|
|
22664
|
-
},
|
|
22665
|
-
};
|
|
22885
|
+
return mapEntityStorageErrorToCommandResult(error);
|
|
22666
22886
|
}
|
|
22667
22887
|
},
|
|
22668
22888
|
tabs: [...tabDetailTabs, ...tabListTabs, ...nestedTabListTabs],
|
|
@@ -22865,6 +23085,7 @@ class AXPMainEntityContentBuilder {
|
|
|
22865
23085
|
icon: action.icon,
|
|
22866
23086
|
color: action.color,
|
|
22867
23087
|
disabled: disabled || false,
|
|
23088
|
+
shortcuts: action.shortcuts,
|
|
22868
23089
|
zone: 'header',
|
|
22869
23090
|
priority: action.priority,
|
|
22870
23091
|
scope: action.scope === AXPEntityCommandScope.Individual ? AXPEntityCommandScope.TypeLevel : action.scope,
|
|
@@ -22927,17 +23148,25 @@ class AXPLayoutAdapterFactory {
|
|
|
22927
23148
|
async createDetailsViewAdapter(entityResolver, moduleName, entityName, id, dependencies) {
|
|
22928
23149
|
const entity = await entityResolver.resolve(moduleName, entityName);
|
|
22929
23150
|
if (!entity) {
|
|
22930
|
-
throw new
|
|
23151
|
+
throw new AXPNotFoundError(`Entity ${moduleName}.${entityName} not found`);
|
|
22931
23152
|
}
|
|
22932
23153
|
const rootContext = await this.loadRootContext(entity, id);
|
|
22933
|
-
|
|
22934
|
-
|
|
22935
|
-
|
|
22936
|
-
|
|
22937
|
-
const
|
|
22938
|
-
const
|
|
23154
|
+
if (axpIsEntityRecordNotFound(rootContext)) {
|
|
23155
|
+
throw new AXPNotFoundError(`Entity record ${moduleName}.${entityName}#${id} not found`);
|
|
23156
|
+
}
|
|
23157
|
+
// Evaluate hidden expressions for related entities and component pages once, reuse across building and composing
|
|
23158
|
+
const evaluatedRelatedEntities = await this.evaluateHiddenFlags(entity?.relatedEntities ?? [], rootContext, dependencies);
|
|
23159
|
+
const evaluatedPages = await this.evaluateHiddenFlags(entity?.pages ?? [], rootContext, dependencies);
|
|
23160
|
+
// Build main and related pages using evaluated related entities and pages
|
|
23161
|
+
const entityWithEvaluatedLayout = {
|
|
23162
|
+
...entity,
|
|
23163
|
+
relatedEntities: evaluatedRelatedEntities,
|
|
23164
|
+
pages: evaluatedPages,
|
|
23165
|
+
};
|
|
23166
|
+
const mainPage = await this.buildMainPage(entityWithEvaluatedLayout, rootContext, id, dependencies);
|
|
23167
|
+
const relatedPages = await this.buildRelatedPages(entityWithEvaluatedLayout, rootContext, dependencies);
|
|
22939
23168
|
// Compose ordered pages around the primary page
|
|
22940
|
-
const orderedPages = this.composePagesWithPositions(mainPage, relatedPages,
|
|
23169
|
+
const orderedPages = this.composePagesWithPositions(mainPage, relatedPages, entityWithEvaluatedLayout);
|
|
22941
23170
|
const applicationName = dependencies.session.application?.name ?? 'default';
|
|
22942
23171
|
const rootTitle = await this.getRootTitle(entity, rootContext, dependencies);
|
|
22943
23172
|
return this.layoutAdapterBuilder
|
|
@@ -22956,15 +23185,15 @@ class AXPLayoutAdapterFactory {
|
|
|
22956
23185
|
return this.mainEntityContentBuilder.build(entity, rootContext, { ...dependencies, reloadRootContext: () => this.loadRootContext(entity, id) }, await this.getRootTitle(entity, rootContext, dependencies));
|
|
22957
23186
|
}
|
|
22958
23187
|
/**
|
|
22959
|
-
* Evaluates the 'hidden' expression for
|
|
22960
|
-
* Returns a new array
|
|
23188
|
+
* Evaluates the 'hidden' expression for layout items using the expression evaluator.
|
|
23189
|
+
* Returns a new array with evaluated hidden values.
|
|
22961
23190
|
*/
|
|
22962
|
-
async
|
|
22963
|
-
if (!
|
|
22964
|
-
return
|
|
23191
|
+
async evaluateHiddenFlags(items, rootContext, dependencies) {
|
|
23192
|
+
if (!items?.length || !dependencies?.expressionEvaluator) {
|
|
23193
|
+
return items ?? [];
|
|
22965
23194
|
}
|
|
22966
|
-
return Promise.all(
|
|
22967
|
-
let hidden =
|
|
23195
|
+
return Promise.all(items.map(async (item) => {
|
|
23196
|
+
let hidden = item.hidden;
|
|
22968
23197
|
if (hidden && typeof hidden === 'string') {
|
|
22969
23198
|
try {
|
|
22970
23199
|
const scope = {
|
|
@@ -22979,9 +23208,12 @@ class AXPLayoutAdapterFactory {
|
|
|
22979
23208
|
// Keep original hidden value if evaluation fails
|
|
22980
23209
|
}
|
|
22981
23210
|
}
|
|
22982
|
-
return { ...
|
|
23211
|
+
return { ...item, hidden };
|
|
22983
23212
|
}));
|
|
22984
23213
|
}
|
|
23214
|
+
async evaluateRelatedEntitiesHidden(relatedEntities, rootContext, dependencies) {
|
|
23215
|
+
return this.evaluateHiddenFlags(relatedEntities, rootContext, dependencies);
|
|
23216
|
+
}
|
|
22985
23217
|
async buildRelatedPages(entity, rootContext, dependencies) {
|
|
22986
23218
|
const pages = [];
|
|
22987
23219
|
const rootTitle = await this.getRootTitle(entity, rootContext, dependencies);
|
|
@@ -23146,7 +23378,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
23146
23378
|
}]
|
|
23147
23379
|
}], ctorParameters: () => [{ type: AXPRelatedEntityConverterFactory }, { type: AXPMainEntityContentBuilder }, { type: AXPLayoutAdapterBuilder }, { type: i4$3.AXPFilterOperatorMiddlewareService }] });
|
|
23148
23380
|
|
|
23149
|
-
const AXPLayoutDetailsViewRouteResolver = async (route, state, entityResolver = inject(AXPEntityDefinitionRegistryService), expressionEvaluator = inject(AXPExpressionEvaluatorService), session = inject(AXPSessionService), formatService = inject(AXFormatService), workflowService = inject(AXPWorkflowService), commandService = inject(AXPCommandService), layoutAdapterFactory = inject(AXPLayoutAdapterFactory)) => {
|
|
23381
|
+
const AXPLayoutDetailsViewRouteResolver = async (route, state, entityResolver = inject(AXPEntityDefinitionRegistryService), expressionEvaluator = inject(AXPExpressionEvaluatorService), session = inject(AXPSessionService), formatService = inject(AXFormatService), workflowService = inject(AXPWorkflowService), commandService = inject(AXPCommandService), layoutAdapterFactory = inject(AXPLayoutAdapterFactory), router = inject(Router)) => {
|
|
23150
23382
|
const moduleName = route.parent?.paramMap.get('module');
|
|
23151
23383
|
const entityName = route.paramMap.get('entity');
|
|
23152
23384
|
const id = route.paramMap.get('id');
|
|
@@ -23158,8 +23390,15 @@ const AXPLayoutDetailsViewRouteResolver = async (route, state, entityResolver =
|
|
|
23158
23390
|
workflowService,
|
|
23159
23391
|
commandService,
|
|
23160
23392
|
};
|
|
23161
|
-
|
|
23162
|
-
|
|
23393
|
+
try {
|
|
23394
|
+
return await layoutAdapterFactory.createDetailsViewAdapter(entityResolver, moduleName, entityName, id, dependencies);
|
|
23395
|
+
}
|
|
23396
|
+
catch (error) {
|
|
23397
|
+
if (error instanceof AXPNotFoundError) {
|
|
23398
|
+
return axpRedirectToNotFound(router);
|
|
23399
|
+
}
|
|
23400
|
+
throw error;
|
|
23401
|
+
}
|
|
23163
23402
|
};
|
|
23164
23403
|
|
|
23165
23404
|
class AXPEntityPreloadFilterGuard {
|
|
@@ -23218,32 +23457,33 @@ const eventDispatchMiddleware = {
|
|
|
23218
23457
|
const dispatcher = inject(AXPEntityEventDispatcherService);
|
|
23219
23458
|
const workflowEventService = inject(AXPWorkflowEventService);
|
|
23220
23459
|
await next();
|
|
23221
|
-
|
|
23222
|
-
|
|
23223
|
-
|
|
23224
|
-
|
|
23225
|
-
|
|
23226
|
-
|
|
23227
|
-
|
|
23228
|
-
|
|
23229
|
-
|
|
23230
|
-
|
|
23231
|
-
|
|
23232
|
-
|
|
23233
|
-
|
|
23234
|
-
|
|
23235
|
-
|
|
23236
|
-
|
|
23237
|
-
|
|
23238
|
-
}
|
|
23460
|
+
try {
|
|
23461
|
+
if (ctx.op === 'create') {
|
|
23462
|
+
const createdData = ctx.result ? { ...ctx.data, id: ctx.result } : ctx.data;
|
|
23463
|
+
await dispatcher.dispatchInserted(ctx.entityName, { refType: ctx.entityName, data: createdData });
|
|
23464
|
+
workflowEventService.dispatch(AXPRefreshEvent({
|
|
23465
|
+
entity: ctx.entityName,
|
|
23466
|
+
}));
|
|
23467
|
+
}
|
|
23468
|
+
else if (ctx.op === 'update') {
|
|
23469
|
+
await dispatcher.dispatchUpdated(ctx.entityName, {
|
|
23470
|
+
refType: ctx.entityName,
|
|
23471
|
+
data: ctx.result,
|
|
23472
|
+
changes: ctx.locals.get('changes'),
|
|
23473
|
+
});
|
|
23474
|
+
workflowEventService.dispatch(AXPRefreshEvent({
|
|
23475
|
+
entity: ctx.entityName,
|
|
23476
|
+
}));
|
|
23477
|
+
}
|
|
23478
|
+
else if (ctx.op === 'delete') {
|
|
23479
|
+
await dispatcher.dispatchDeleted(ctx.entityName, { refType: ctx.entityName, data: ctx.result ?? ctx.previous });
|
|
23480
|
+
workflowEventService.dispatch(AXPRefreshEvent({
|
|
23481
|
+
entity: ctx.entityName,
|
|
23482
|
+
}));
|
|
23483
|
+
}
|
|
23239
23484
|
}
|
|
23240
|
-
|
|
23241
|
-
//
|
|
23242
|
-
await dispatcher.dispatchDeleted(ctx.entityName, { refType: ctx.entityName, data: ctx.result ?? ctx.previous });
|
|
23243
|
-
// Dispatch workflow refresh event to trigger entity list refresh
|
|
23244
|
-
workflowEventService.dispatch(AXPRefreshEvent({
|
|
23245
|
-
entity: ctx.entityName,
|
|
23246
|
-
}));
|
|
23485
|
+
catch {
|
|
23486
|
+
// Persist already succeeded; event dispatch must not block the write result.
|
|
23247
23487
|
}
|
|
23248
23488
|
},
|
|
23249
23489
|
};
|
|
@@ -23904,8 +24144,12 @@ const AXPCrudModifier = {
|
|
|
23904
24144
|
if (!command?.update) {
|
|
23905
24145
|
command.update = {
|
|
23906
24146
|
execute: async (data) => {
|
|
23907
|
-
|
|
23908
|
-
|
|
24147
|
+
const entityKey = ctx.module.get() + '.' + ctx.name.get();
|
|
24148
|
+
const { id, ...patch } = data ?? {};
|
|
24149
|
+
if (id == null || id === '') {
|
|
24150
|
+
throw new Error(`Update requires entity id (${entityKey})`);
|
|
24151
|
+
}
|
|
24152
|
+
return await dataService.updateOne(id, patch);
|
|
23909
24153
|
},
|
|
23910
24154
|
};
|
|
23911
24155
|
}
|
|
@@ -23928,7 +24172,7 @@ const AXPCrudModifier = {
|
|
|
23928
24172
|
queries.list = {
|
|
23929
24173
|
execute: async (e) => {
|
|
23930
24174
|
const res = await dataService.query(e);
|
|
23931
|
-
console.log('query', res, ctx.module.get() + '.' + ctx.name.get(), e);
|
|
24175
|
+
// console.log('query', res, ctx.module.get() + '.' + ctx.name.get(), e);
|
|
23932
24176
|
return res;
|
|
23933
24177
|
},
|
|
23934
24178
|
type: AXPEntityQueryType.List,
|
|
@@ -23971,6 +24215,8 @@ class AXPEntitySearchDefinitionProvider {
|
|
|
23971
24215
|
this.entityRegister.getAll().forEach((entity) => {
|
|
23972
24216
|
const manifest = this.manifestRegistry.get(entity.module);
|
|
23973
24217
|
const i18nScope = manifest?.i18n ?? moduleNameToI18nScope(entity.module);
|
|
24218
|
+
const searchTitleTemplate = entity.formats.searchResult?.title;
|
|
24219
|
+
const titleContext = buildEntitySearchTitleContext(entity, searchTitleTemplate);
|
|
23974
24220
|
context.addDefinition(`Module.${entity.module}.${entity.name}`, `@${i18nScope}:module.title`, `Module.${entity.module}`, entity.icon ?? 'fa-solid fa-objects-column', 4, {
|
|
23975
24221
|
actions: [
|
|
23976
24222
|
{
|
|
@@ -23980,43 +24226,17 @@ class AXPEntitySearchDefinitionProvider {
|
|
|
23980
24226
|
},
|
|
23981
24227
|
],
|
|
23982
24228
|
format: {
|
|
23983
|
-
title:
|
|
24229
|
+
title: searchTitleTemplate,
|
|
23984
24230
|
description: (entity.formats.searchResult?.description) ?? `${entity.module} / ${entity.title}`,
|
|
23985
24231
|
id: '{{data.id}}',
|
|
24232
|
+
titleFallbackTemplates: titleContext.fallbackTemplates,
|
|
24233
|
+
titleFallbackFields: titleContext.fallbackFields,
|
|
23986
24234
|
},
|
|
23987
24235
|
});
|
|
23988
24236
|
});
|
|
23989
24237
|
}
|
|
23990
24238
|
}
|
|
23991
24239
|
|
|
23992
|
-
/**
|
|
23993
|
-
* Error type that can be thrown by middlewares to abort the chain with a structured payload.
|
|
23994
|
-
*/
|
|
23995
|
-
class AXPMiddlewareAbortError extends Error {
|
|
23996
|
-
constructor(message, options = {}) {
|
|
23997
|
-
super(message);
|
|
23998
|
-
this.message = message;
|
|
23999
|
-
this.options = options;
|
|
24000
|
-
this.name = 'AXPMiddlewareAbortError';
|
|
24001
|
-
}
|
|
24002
|
-
toResponse() {
|
|
24003
|
-
return {
|
|
24004
|
-
success: false,
|
|
24005
|
-
error: {
|
|
24006
|
-
code: this.options.code,
|
|
24007
|
-
message: this.message,
|
|
24008
|
-
status: this.options.status,
|
|
24009
|
-
details: this.options.details,
|
|
24010
|
-
},
|
|
24011
|
-
};
|
|
24012
|
-
}
|
|
24013
|
-
}
|
|
24014
|
-
/** Type guard for AXPMiddlewareAbortError */
|
|
24015
|
-
function isAXPMiddlewareAbortError(error) {
|
|
24016
|
-
return error instanceof AXPMiddlewareAbortError;
|
|
24017
|
-
}
|
|
24018
|
-
//#endregion
|
|
24019
|
-
|
|
24020
24240
|
const AXP_ENTITY_STORAGE_BACKEND = new InjectionToken('AXP_ENTITY_STORAGE_BACKEND');
|
|
24021
24241
|
const AXP_ENTITY_STORAGE_MIDDLEWARE = new InjectionToken('AXP_ENTITY_STORAGE_MIDDLEWARE');
|
|
24022
24242
|
|
|
@@ -24694,7 +24914,7 @@ function routesFacory() {
|
|
|
24694
24914
|
loadComponent: () => {
|
|
24695
24915
|
return config.viewers.root();
|
|
24696
24916
|
},
|
|
24697
|
-
canActivate: [
|
|
24917
|
+
canActivate: [...AXP_PROTECTED_ROUTE_GUARDS],
|
|
24698
24918
|
children: [
|
|
24699
24919
|
{
|
|
24700
24920
|
path: ':module',
|
|
@@ -24732,6 +24952,7 @@ function routesFacory() {
|
|
|
24732
24952
|
loadComponent: () => {
|
|
24733
24953
|
return config.viewers.master.details();
|
|
24734
24954
|
},
|
|
24955
|
+
canDeactivate: [axpDetailsViewCanDeactivateGuard],
|
|
24735
24956
|
runGuardsAndResolvers: (from, to) => {
|
|
24736
24957
|
const entityChanged = from.params['module'] !== to.params['module'] || from.params['entity'] !== to.params['entity'];
|
|
24737
24958
|
const idChanged = from.params['id'] !== to.params['id'];
|
|
@@ -24750,8 +24971,16 @@ function routesFacory() {
|
|
|
24750
24971
|
redirectTo: ':entity/list',
|
|
24751
24972
|
pathMatch: 'full',
|
|
24752
24973
|
},
|
|
24974
|
+
{
|
|
24975
|
+
path: '**',
|
|
24976
|
+
redirectTo: AXP_NOT_FOUND_ROUTE,
|
|
24977
|
+
},
|
|
24753
24978
|
],
|
|
24754
24979
|
},
|
|
24980
|
+
{
|
|
24981
|
+
path: '**',
|
|
24982
|
+
redirectTo: AXP_NOT_FOUND_ROUTE,
|
|
24983
|
+
},
|
|
24755
24984
|
],
|
|
24756
24985
|
},
|
|
24757
24986
|
];
|
|
@@ -25305,5 +25534,5 @@ var getEntityDetails_query = /*#__PURE__*/Object.freeze({
|
|
|
25305
25534
|
* Generated bundle index. Do not edit.
|
|
25306
25535
|
*/
|
|
25307
25536
|
|
|
25308
|
-
export { ATTACHMENTS_PAGE_COMPONENT_KEY, AXMEntityCrudService, AXMEntityCrudServiceImpl, AXPCategoryTreeService, AXPCreateEntityCommand, AXPCreateEntityWorkflow, AXPDataSeederService, AXPDeleteEntityWorkflow, AXPEditFileUploaderCommand, AXPEntitiesListDataSourceDefinition, AXPEntityApplyUpdatesAction, AXPEntityCategoryTreeSelectorComponent, AXPEntityCategoryWidget, AXPEntityCategoryWidgetColumnComponent, AXPEntityCategoryWidgetEditComponent, AXPEntityCategoryWidgetViewComponent, AXPEntityCommandTriggerViewModel, AXPEntityCreateEvent, AXPEntityCreatePopupAction, AXPEntityCreateSubmittedAction, AXPEntityCreateViewElementViewModel, AXPEntityCreateViewModelFactory, AXPEntityCreateViewSectionViewModel, AXPEntityDataProvider, AXPEntityDataProviderImpl, AXPEntityDataSelectorRowActionsService, AXPEntityDataSelectorService, AXPEntityDefinitionProviderWidget, AXPEntityDefinitionProviderWidgetEditComponent, AXPEntityDefinitionRegistryService, AXPEntityDeletedEvent, AXPEntityDetailListViewModel, AXPEntityDetailPopoverComponent, AXPEntityDetailPopoverService, AXPEntityDetailViewModelFactory, AXPEntityDetailViewModelResolver, AXPEntityEventDispatcherService, AXPEntityEventsKeys, AXPEntityFormBuilderService, AXPEntityListPersistenceModeDefault, AXPEntityListTableService, AXPEntityListToolbarService, AXPEntityListViewCardFieldViewModel, AXPEntityListViewColumnViewModel, AXPEntityListViewModelFactory, AXPEntityListViewModelResolver, AXPEntityListWidget, AXPEntityListWidgetViewComponent, AXPEntityMasterCreateViewModel, AXPEntityMasterListCardSelectActionName, AXPEntityMasterListViewModel, AXPEntityMasterListViewQueryViewModel, AXPEntityMasterSingleElementViewModel, AXPEntityMasterSingleViewGroupViewModel, AXPEntityMasterSingleViewModel, AXPEntityMasterUpdateElementViewModel, AXPEntityMasterUpdateViewModel, AXPEntityMasterUpdateViewModelFactory, AXPEntityMiddleware, AXPEntityModifyConfirmedAction, AXPEntityModifyEvent, AXPEntityModifySectionPopupAction, AXPEntityModule, AXPEntityPerformDeleteAction, AXPEntityPreloadFiltersContainerComponent, AXPEntityPreloadFiltersViewModel, AXPEntityPreloadFiltersViewModelResolver, AXPEntityResolver, AXPEntityService, AXPEntityStorageService, AXPEntityUpdateViewSectionViewModel, AXPFileListComponent, AXPFileUploaderLoadFilesQuery, AXPFileUploaderSaveFilesCommand, AXPFileUploaderWidget, AXPFileUploaderWidgetColumnComponent, AXPFileUploaderWidgetEditComponent, AXPFileUploaderWidgetService, AXPFileUploaderWidgetViewComponent, AXPGetEntityDetailsQuery, AXPLayoutOrderingConfigService, AXPLookupWidget, AXPLookupWidgetColumnComponent, AXPLookupWidgetEditComponent, AXPLookupWidgetViewComponent, AXPMiddlewareAbortError, AXPMiddlewareEntityStorageService, AXPModifyEntitySectionWorkflow, AXPMultiSourceDefinitionProviderContext, AXPMultiSourceDefinitionProviderService, AXPMultiSourceFederatedSearchService, AXPMultiSourceSelectorComponent, AXPMultiSourceSelectorService, AXPMultiSourceSelectorWidget, AXPMultiSourceSelectorWidgetColumnComponent, AXPMultiSourceSelectorWidgetEditComponent, AXPMultiSourceSelectorWidgetViewComponent, AXPMultiSourceType, AXPOpenEntityDetailsCommand, AXPQuickEntityModifyPopupAction, AXPQuickModifyEntityWorkflow, AXPRelatedColumnEnrichmentService, AXPRelatedColumnMetadataResolver, AXPSelectorStructureWidget, AXPSelectorStructureWidgetColumnComponent, AXPSelectorStructureWidgetEditComponent, AXPSelectorStructureWidgetViewComponent, AXPShowDetailViewAction, AXPShowDetailsViewWorkflow, AXPShowListViewAction, AXPShowListViewWorkflow, AXPTruncatedBreadcrumbComponent, AXPUpdateEntityCommand, AXPViewEntityDetailsCommand, AXP_CATEGORY_TREE_ROOT_TITLE_I18N_KEY, 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, AXP_RECORD_WORKFLOW_INFO_CORRELATION_ID_FIELD, AXP_RECORD_WORKFLOW_INFO_DEFINITION_ID_FIELD, AXP_RECORD_WORKFLOW_INFO_INSTANCE_ID_FIELD, DEFAULT_COLUMN_ORDER, DEFAULT_PAIR_SPAN_RULES, DEFAULT_PROPERTY_ORDER, DEFAULT_SECTION_ORDER, ENTITY_LIST_ROUTE_CONTEXT_SESSION_KEY, EntityBuilder, EntityDataAccessor, actionExists, applyDataSourcePagingWithoutLoad, attachmentFieldCount, attachmentsPlugin, attachmentsSemanticallyEqual, axpCreateEntityAiToolInputDefaults, axpCreateEntityCommandDefinition, buildAXPRecordWorkflowInfo, canPersistEntityListState, cloneLayoutArrays, collectEntityQuickSearchFieldPaths, collectNestedCreateHiddenProperties, collectNestedFieldPathsFromEntityColumns, collectQuickSearchPathsFromSingleEntityDefinition, columnOrderingMiddleware, columnOrderingMiddlewareProvider, columnWidthMiddleware, columnWidthMiddlewareProvider, committedAttachments, computeEntityAggregates, createColumnOrderingMiddlewareProvider, createLayoutOrderingMiddlewareProvider, createModifierContext, defaultCardLayoutMiddleware, defaultCardLayoutMiddlewareProvider, defaultMultiLanguageMiddleware, defaultMultiLanguageMiddlewareProvider, detectEntityChanges, ensureLayoutPropertyView, ensureLayoutSection, ensureListActions, entityDetailsCreateActions, entityDetailsCreateActionsDeferredParent, entityDetailsCrudActions, entityDetailsEditAction, entityDetailsNewEditAction, entityDetailsReferenceCondition, entityDetailsReferenceCreateActions, entityDetailsSimpleCondition, entityMasterBulkDeleteAction, entityMasterCreateAction, entityMasterCrudActions, entityMasterDeleteAction, entityMasterEditAction, entityMasterRecordActions, entityMasterViewAction, entityOverrideDetailsViewAction, eventDispatchMiddleware, filterSortEntityRows, findEntityListRowDataInTree, fingerprintAttachmentItem, fingerprintAttachments, formatLookupItemDisplay, getDataSourcePageIndex, getEntityListRowId, getMasterInterfacePropertySortKey, getRecordWorkflowCorrelationId, getRecordWorkflowInstanceId, hasFileUploaderTitleOrDescriptionFields, isAXPMiddlewareAbortError, isAttachmentListEntry, isCategoryEntity, isCategoryFilter, isFileListItem, isFileUploaderEditDialogAuto, isLegacyEntityDataSelectorOptions,
|
|
25537
|
+
export { ATTACHMENTS_PAGE_COMPONENT_KEY, AXMEntityCrudService, AXMEntityCrudServiceImpl, AXPCategoryTreeService, AXPCreateEntityCommand, AXPCreateEntityWorkflow, AXPDataSeederService, AXPDeleteEntityWorkflow, AXPEditFileUploaderCommand, AXPEntitiesListDataSourceDefinition, AXPEntityApplyUpdatesAction, AXPEntityCategoryTreeSelectorComponent, AXPEntityCategoryWidget, AXPEntityCategoryWidgetColumnComponent, AXPEntityCategoryWidgetEditComponent, AXPEntityCategoryWidgetViewComponent, AXPEntityCommandTriggerViewModel, AXPEntityCreateEvent, AXPEntityCreatePopupAction, AXPEntityCreateSubmittedAction, AXPEntityCreateViewElementViewModel, AXPEntityCreateViewModelFactory, AXPEntityCreateViewSectionViewModel, AXPEntityDataProvider, AXPEntityDataProviderImpl, AXPEntityDataSelectorRowActionsService, AXPEntityDataSelectorService, AXPEntityDefinitionProviderWidget, AXPEntityDefinitionProviderWidgetEditComponent, AXPEntityDefinitionRegistryService, AXPEntityDeletedEvent, AXPEntityDetailListViewModel, AXPEntityDetailPopoverComponent, AXPEntityDetailPopoverService, AXPEntityDetailViewModelFactory, AXPEntityDetailViewModelResolver, AXPEntityEventDispatcherService, AXPEntityEventsKeys, AXPEntityFormBuilderService, AXPEntityListPersistenceModeDefault, AXPEntityListTableService, AXPEntityListToolbarService, AXPEntityListViewCardFieldViewModel, AXPEntityListViewColumnViewModel, AXPEntityListViewModelFactory, AXPEntityListViewModelResolver, AXPEntityListWidget, AXPEntityListWidgetViewComponent, AXPEntityMasterCreateViewModel, AXPEntityMasterListCardSelectActionName, AXPEntityMasterListViewModel, AXPEntityMasterListViewQueryViewModel, AXPEntityMasterSingleElementViewModel, AXPEntityMasterSingleViewGroupViewModel, AXPEntityMasterSingleViewModel, AXPEntityMasterUpdateElementViewModel, AXPEntityMasterUpdateViewModel, AXPEntityMasterUpdateViewModelFactory, AXPEntityMiddleware, AXPEntityModifyConfirmedAction, AXPEntityModifyEvent, AXPEntityModifySectionPopupAction, AXPEntityModule, AXPEntityPerformDeleteAction, AXPEntityPreloadFiltersContainerComponent, AXPEntityPreloadFiltersViewModel, AXPEntityPreloadFiltersViewModelResolver, AXPEntityResolver, AXPEntityService, AXPEntityStorageService, AXPEntityUpdateViewSectionViewModel, AXPFileListComponent, AXPFileUploaderLoadFilesQuery, AXPFileUploaderSaveFilesCommand, AXPFileUploaderWidget, AXPFileUploaderWidgetColumnComponent, AXPFileUploaderWidgetEditComponent, AXPFileUploaderWidgetService, AXPFileUploaderWidgetViewComponent, AXPGetEntityDetailsQuery, AXPLayoutOrderingConfigService, AXPLookupWidget, AXPLookupWidgetColumnComponent, AXPLookupWidgetEditComponent, AXPLookupWidgetViewComponent, AXPMiddlewareAbortError, AXPMiddlewareEntityStorageService, AXPModifyEntitySectionWorkflow, AXPMultiSourceDefinitionProviderContext, AXPMultiSourceDefinitionProviderService, AXPMultiSourceFederatedSearchService, AXPMultiSourceSelectorComponent, AXPMultiSourceSelectorService, AXPMultiSourceSelectorWidget, AXPMultiSourceSelectorWidgetColumnComponent, AXPMultiSourceSelectorWidgetEditComponent, AXPMultiSourceSelectorWidgetViewComponent, AXPMultiSourceType, AXPOpenEntityDetailsCommand, AXPQuickEntityModifyPopupAction, AXPQuickModifyEntityWorkflow, AXPRelatedColumnEnrichmentService, AXPRelatedColumnMetadataResolver, AXPSelectorStructureWidget, AXPSelectorStructureWidgetColumnComponent, AXPSelectorStructureWidgetEditComponent, AXPSelectorStructureWidgetViewComponent, AXPShowDetailViewAction, AXPShowDetailsViewWorkflow, AXPShowListViewAction, AXPShowListViewWorkflow, AXPTruncatedBreadcrumbComponent, AXPUpdateEntityCommand, AXPViewEntityDetailsCommand, AXP_CATEGORY_TREE_ROOT_TITLE_I18N_KEY, 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, AXP_RECORD_WORKFLOW_INFO_CORRELATION_ID_FIELD, AXP_RECORD_WORKFLOW_INFO_DEFINITION_ID_FIELD, AXP_RECORD_WORKFLOW_INFO_INSTANCE_ID_FIELD, DEFAULT_COLUMN_ORDER, DEFAULT_PAIR_SPAN_RULES, DEFAULT_PROPERTY_ORDER, DEFAULT_SECTION_ORDER, ENTITY_LIST_ROUTE_CONTEXT_SESSION_KEY, EntityBuilder, EntityDataAccessor, actionExists, applyDataSourcePagingWithoutLoad, attachmentFieldCount, attachmentsPlugin, attachmentsSemanticallyEqual, axpCreateEntityAiToolInputDefaults, axpCreateEntityCommandDefinition, buildAXPRecordWorkflowInfo, canPersistEntityListState, cloneLayoutArrays, collectEntityQuickSearchFieldPaths, collectNestedCreateHiddenProperties, collectNestedFieldPathsFromEntityColumns, collectQuickSearchPathsFromSingleEntityDefinition, columnOrderingMiddleware, columnOrderingMiddlewareProvider, columnWidthMiddleware, columnWidthMiddlewareProvider, commandMessageTextForError, committedAttachments, computeEntityAggregates, createColumnOrderingMiddlewareProvider, createLayoutOrderingMiddlewareProvider, createModifierContext, defaultCardLayoutMiddleware, defaultCardLayoutMiddlewareProvider, defaultMultiLanguageMiddleware, defaultMultiLanguageMiddlewareProvider, detectEntityChanges, ensureLayoutPropertyView, ensureLayoutSection, ensureListActions, entityDetailsCreateActions, entityDetailsCreateActionsDeferredParent, entityDetailsCrudActions, entityDetailsEditAction, entityDetailsNewEditAction, entityDetailsReferenceCondition, entityDetailsReferenceCreateActions, entityDetailsSimpleCondition, entityMasterBulkDeleteAction, entityMasterCreateAction, entityMasterCrudActions, entityMasterDeleteAction, entityMasterEditAction, entityMasterRecordActions, entityMasterViewAction, entityOverrideDetailsViewAction, eventDispatchMiddleware, filterSortEntityRows, findEntityListRowDataInTree, fingerprintAttachmentItem, fingerprintAttachments, formatLookupItemDisplay, getDataSourcePageIndex, getEntityListRowId, getMasterInterfacePropertySortKey, getRecordWorkflowCorrelationId, getRecordWorkflowInstanceId, hasFileUploaderTitleOrDescriptionFields, isAXPMiddlewareAbortError, isAttachmentListEntry, isCategoryEntity, isCategoryFilter, isFileListItem, isFileUploaderEditDialogAuto, isLegacyEntityDataSelectorOptions, layoutOrderingMiddlewareFactory, layoutOrderingMiddlewareProvider, mapEntityStorageErrorToCommandResult, mapLegacyEntityDataSelectorOptions, mergeForeignKeyFieldIntoCreateActions, normalizeEntityDataSelectorOptions, normalizeEntityFieldToFileList, normalizeEntityListPersistenceMode, normalizeListPaging, persistedAttachments, provideEntity, resolveEntityPluginDetailPageOrder, resolveFileUploaderEditDialog, resolveFileUploaderEntityScope, resolveLookupDisplayField, resolveLookupDisplayTemplate, restoreEntityListExpandedRows, runEntityQuery, searchResultDescriptionMiddleware, searchResultDescriptionMiddlewareProvider, shouldLoadEntityListStateFromStorage, shouldResetEntityListStateOnRouteEntry };
|
|
25309
25538
|
//# sourceMappingURL=acorex-platform-layout-entity.mjs.map
|