@acorex/platform 21.0.0-next.83 → 21.0.0-next.88
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 +1 -1
- package/fesm2022/acorex-platform-auth.mjs.map +1 -1
- package/fesm2022/{acorex-platform-common-common-settings.provider-Ytey9uhY.mjs → acorex-platform-common-common-settings.provider-VfmZct9H.mjs} +43 -3
- package/fesm2022/acorex-platform-common-common-settings.provider-VfmZct9H.mjs.map +1 -0
- package/fesm2022/acorex-platform-common.mjs +1709 -870
- package/fesm2022/acorex-platform-common.mjs.map +1 -1
- package/fesm2022/acorex-platform-contracts.mjs +1160 -9
- package/fesm2022/acorex-platform-contracts.mjs.map +1 -1
- package/fesm2022/acorex-platform-core.mjs +210 -131
- package/fesm2022/acorex-platform-core.mjs.map +1 -1
- package/fesm2022/acorex-platform-domain-contracts.mjs +58 -0
- package/fesm2022/acorex-platform-domain-contracts.mjs.map +1 -0
- package/fesm2022/acorex-platform-domain.mjs +2 -50
- package/fesm2022/acorex-platform-domain.mjs.map +1 -1
- package/fesm2022/acorex-platform-layout-builder.mjs.map +1 -1
- package/fesm2022/acorex-platform-layout-components.mjs +61 -23
- package/fesm2022/acorex-platform-layout-components.mjs.map +1 -1
- package/fesm2022/acorex-platform-layout-designer.mjs +3 -2
- package/fesm2022/acorex-platform-layout-designer.mjs.map +1 -1
- package/fesm2022/acorex-platform-layout-entity-attachments-page.component-CwG6n_Yb.mjs.map +1 -1
- package/fesm2022/acorex-platform-layout-entity-contracts.mjs +1114 -0
- package/fesm2022/acorex-platform-layout-entity-contracts.mjs.map +1 -0
- package/fesm2022/acorex-platform-layout-entity-file-list-popup.component--IeMZLIr.mjs.map +1 -1
- package/fesm2022/acorex-platform-layout-entity.mjs +661 -742
- package/fesm2022/acorex-platform-layout-entity.mjs.map +1 -1
- package/fesm2022/acorex-platform-layout-views.mjs +376 -176
- package/fesm2022/acorex-platform-layout-views.mjs.map +1 -1
- package/fesm2022/acorex-platform-layout-widget-core.mjs +11 -105
- package/fesm2022/acorex-platform-layout-widget-core.mjs.map +1 -1
- package/fesm2022/acorex-platform-layout-widgets-repeater-widget-column.component-CFyfix31.mjs +513 -0
- package/fesm2022/acorex-platform-layout-widgets-repeater-widget-column.component-CFyfix31.mjs.map +1 -0
- package/fesm2022/{acorex-platform-layout-widgets-tabular-data-edit-popup.component-DjpZU6gz.mjs → acorex-platform-layout-widgets-tabular-data-edit-popup.component-D4MOHEpI.mjs} +4 -4
- package/fesm2022/acorex-platform-layout-widgets-tabular-data-edit-popup.component-D4MOHEpI.mjs.map +1 -0
- package/fesm2022/acorex-platform-layout-widgets.mjs +871 -59
- package/fesm2022/acorex-platform-layout-widgets.mjs.map +1 -1
- package/fesm2022/acorex-platform-runtime.mjs.map +1 -1
- package/fesm2022/{acorex-platform-themes-default-entity-master-single-view.component-Br9p5aXT.mjs → acorex-platform-themes-default-entity-master-single-view.component-DrUwqKKA.mjs} +2 -3
- package/fesm2022/acorex-platform-themes-default-entity-master-single-view.component-DrUwqKKA.mjs.map +1 -0
- package/fesm2022/acorex-platform-themes-default.mjs +740 -96
- package/fesm2022/acorex-platform-themes-default.mjs.map +1 -1
- package/fesm2022/{acorex-platform-themes-shared-settings.provider-BjuzSe0T.mjs → acorex-platform-themes-shared-settings.provider-BUwPqwUS.mjs} +3 -3
- package/fesm2022/acorex-platform-themes-shared-settings.provider-BUwPqwUS.mjs.map +1 -0
- package/fesm2022/acorex-platform-themes-shared.mjs +4 -3
- package/fesm2022/acorex-platform-themes-shared.mjs.map +1 -1
- package/fesm2022/acorex-platform-workflow.mjs +6 -8
- package/fesm2022/acorex-platform-workflow.mjs.map +1 -1
- package/package.json +9 -5
- package/types/acorex-platform-auth.d.ts +2 -1
- package/types/acorex-platform-common.d.ts +419 -1083
- package/types/acorex-platform-contracts.d.ts +1791 -260
- package/types/acorex-platform-core.d.ts +2153 -2806
- package/types/acorex-platform-domain-contracts.d.ts +416 -0
- package/types/acorex-platform-domain.d.ts +4 -415
- package/types/acorex-platform-layout-builder.d.ts +2 -1
- package/types/acorex-platform-layout-components.d.ts +15 -4
- package/types/acorex-platform-layout-designer.d.ts +2 -1
- package/types/acorex-platform-layout-entity-contracts.d.ts +864 -0
- package/types/acorex-platform-layout-entity.d.ts +88 -269
- package/types/acorex-platform-layout-views.d.ts +48 -14
- package/types/acorex-platform-layout-widget-core.d.ts +9 -5
- package/types/acorex-platform-layout-widgets.d.ts +89 -13
- package/types/acorex-platform-runtime.d.ts +1 -1
- package/types/acorex-platform-themes-default.d.ts +173 -70
- package/types/acorex-platform-workflow.d.ts +5 -18
- package/fesm2022/acorex-platform-common-common-settings.provider-Ytey9uhY.mjs.map +0 -1
- package/fesm2022/acorex-platform-layout-widgets-repeater-widget-column.component-J0zcGKBX.mjs +0 -116
- package/fesm2022/acorex-platform-layout-widgets-repeater-widget-column.component-J0zcGKBX.mjs.map +0 -1
- package/fesm2022/acorex-platform-layout-widgets-tabular-data-edit-popup.component-DjpZU6gz.mjs.map +0 -1
- package/fesm2022/acorex-platform-native.mjs +0 -155
- package/fesm2022/acorex-platform-native.mjs.map +0 -1
- package/fesm2022/acorex-platform-themes-default-entity-master-single-view.component-Br9p5aXT.mjs.map +0 -1
- package/fesm2022/acorex-platform-themes-shared-settings.provider-BjuzSe0T.mjs.map +0 -1
- package/native/README.md +0 -4
- package/types/acorex-platform-native.d.ts +0 -28
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as i2 from '@angular/common';
|
|
2
2
|
import { CommonModule } from '@angular/common';
|
|
3
3
|
import * as i0 from '@angular/core';
|
|
4
|
-
import { inject, viewChild, afterNextRender, effect, ChangeDetectionStrategy, ViewEncapsulation, Component, signal, computed, Injectable, input, output, ViewContainerRef, Directive, DestroyRef, ElementRef
|
|
4
|
+
import { inject, viewChild, afterNextRender, effect, ChangeDetectionStrategy, ViewEncapsulation, Component, signal, untracked, computed, Injectable, input, output, ViewContainerRef, Directive, DestroyRef, ElementRef } from '@angular/core';
|
|
5
5
|
import { AXCommonModule } from '@acorex/cdk/common';
|
|
6
6
|
import * as i4 from '@acorex/components/breadcrumbs';
|
|
7
7
|
import { AXBreadcrumbsModule } from '@acorex/components/breadcrumbs';
|
|
@@ -12,9 +12,9 @@ import { AXDecoratorModule } from '@acorex/components/decorators';
|
|
|
12
12
|
import * as i5 from '@acorex/components/dropdown';
|
|
13
13
|
import { AXDropdownModule } from '@acorex/components/dropdown';
|
|
14
14
|
import { AXDropdownButtonModule } from '@acorex/components/dropdown-button';
|
|
15
|
-
import { AXPStickyDirective, AXPCommonSettings, AXPHomePageService, AXPSettingsService,
|
|
15
|
+
import { AXPTabNavigationService, AXPStickyDirective, AXPCommonSettings, AXPHomePageService, AXPSettingsService, AXPTabPreviewService, AXPUnsavedChangesConfirmService, normalizeRouterUrl, AXP_HOME_TAB_KEY, axpResolveTabKey, axpIsEntityDetailsViewRoute, axpNavigateAppPath, AXPEntityCommandScope, AXPUnsavedChangesPopstateService, createUnsavedChangesCanDeactivateGuard } from '@acorex/platform/common';
|
|
16
16
|
import * as i8 from '@acorex/platform/core';
|
|
17
|
-
import { AXPDeviceService, AXPComponentSlotModule, getSmart, AXPExpressionEvaluatorService,
|
|
17
|
+
import { AXPDeviceService, AXPComponentSlotModule, getSmart, AXPExpressionEvaluatorService, AXPContextEvalFactory, normalizeKeyboardShortcuts, AXPContextStore, AXPBroadcastEventService, AXPKeyboardShortcutRegistry, AXPKeyboardShortcutPriority } from '@acorex/platform/core';
|
|
18
18
|
import * as i1 from '@acorex/cdk/drawer';
|
|
19
19
|
import { AXDrawerDirectiveModule, AXDrawerContainerDirective } from '@acorex/cdk/drawer';
|
|
20
20
|
import { AXDrawerModule } from '@acorex/components/drawer';
|
|
@@ -28,22 +28,23 @@ import { AXPWorkflowService } from '@acorex/platform/workflow';
|
|
|
28
28
|
import { AXToastService } from '@acorex/components/toast';
|
|
29
29
|
import { AXFormatService, AXFormatModule } from '@acorex/core/format';
|
|
30
30
|
import { AXPSessionService } from '@acorex/platform/auth';
|
|
31
|
+
import { Router, ActivatedRoute } from '@angular/router';
|
|
31
32
|
import { isEmpty, cloneDeep, get } from 'lodash-es';
|
|
32
33
|
import { AXBasePageComponent } from '@acorex/components/page';
|
|
33
|
-
import * as
|
|
34
|
+
import * as i7 from '@acorex/platform/layout/widget-core';
|
|
34
35
|
import { AXPPageStatus, AXPWidgetContainerComponent, AXPWidgetCoreModule } from '@acorex/platform/layout/widget-core';
|
|
35
|
-
import { Router, ActivatedRoute } from '@angular/router';
|
|
36
36
|
import { signalStore, withState, withComputed, withMethods, patchState } from '@ngrx/signals';
|
|
37
37
|
import { AXBadgeModule } from '@acorex/components/badge';
|
|
38
38
|
import { AXButtonGroupModule } from '@acorex/components/button-group';
|
|
39
|
+
import * as i5$1 from '@acorex/components/loading';
|
|
39
40
|
import { AXLoadingModule } from '@acorex/components/loading';
|
|
40
41
|
import { AXMenuModule } from '@acorex/components/menu';
|
|
41
42
|
import { AXSearchBoxModule } from '@acorex/components/search-box';
|
|
42
43
|
import { AXDateTimeModule } from '@acorex/core/date-time';
|
|
43
44
|
import { AXFileModule } from '@acorex/core/file';
|
|
44
|
-
import * as
|
|
45
|
+
import * as i8$1 from '@acorex/components/form';
|
|
45
46
|
import { AXFormModule } from '@acorex/components/form';
|
|
46
|
-
import * as
|
|
47
|
+
import * as i6$1 from '@acorex/components/tabs';
|
|
47
48
|
import { AXTabsModule } from '@acorex/components/tabs';
|
|
48
49
|
import { Subject, takeUntil } from 'rxjs';
|
|
49
50
|
|
|
@@ -55,6 +56,7 @@ class AXPPageLayoutComponent {
|
|
|
55
56
|
this.page = inject(AXPPageLayoutBase, { optional: true });
|
|
56
57
|
this.layoutService = inject(AXPLayoutThemeService);
|
|
57
58
|
this.deviceService = inject(AXPDeviceService);
|
|
59
|
+
this.tabNavigation = inject(AXPTabNavigationService);
|
|
58
60
|
this.startSideDrawer = viewChild('startSideDrawer', ...(ngDevMode ? [{ debugName: "startSideDrawer" }] : /* istanbul ignore next */ []));
|
|
59
61
|
this.workflow = inject(AXPWorkflowService);
|
|
60
62
|
this.commandService = inject(AXPCommandService);
|
|
@@ -218,11 +220,55 @@ class AXPPageLayoutBaseComponent {
|
|
|
218
220
|
this.homePageService = inject(AXPHomePageService);
|
|
219
221
|
this.policyService = inject(AXPPolicyEngineService);
|
|
220
222
|
this.settingsService = inject(AXPSettingsService);
|
|
223
|
+
this.tabNavigation = inject(AXPTabNavigationService);
|
|
224
|
+
this.tabPreviewService = inject(AXPTabPreviewService);
|
|
225
|
+
this.unsavedChangesConfirm = inject(AXPUnsavedChangesConfirmService);
|
|
226
|
+
this.router = inject(Router);
|
|
221
227
|
//#endregion
|
|
222
228
|
//#region ---------------- Signal to force recomputation ----------------
|
|
223
229
|
this._updateTrigger = signal(0, ...(ngDevMode ? [{ debugName: "_updateTrigger" }] : /* istanbul ignore next */ []));
|
|
230
|
+
/** Tab key captured at page init — stable while the route instance is cached. */
|
|
231
|
+
this.pageTabKey = signal(null, ...(ngDevMode ? [{ debugName: "pageTabKey" }] : /* istanbul ignore next */ []));
|
|
224
232
|
this.updateTrigger = this._updateTrigger.asReadonly();
|
|
225
233
|
//#endregion
|
|
234
|
+
//#region ---------------- Tab Title Sync ----------------
|
|
235
|
+
/** Refreshes tab title when this cached page becomes the active tab again. */
|
|
236
|
+
this.#syncTabTitleOnActivate = effect(() => {
|
|
237
|
+
const activeKey = this.tabNavigation.activeKey();
|
|
238
|
+
const pageKey = this.pageTabKey();
|
|
239
|
+
if (!activeKey || !pageKey || activeKey !== pageKey) {
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
if (this.tabNavigation.effectiveMode() !== 'tabular') {
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
untracked(() => {
|
|
246
|
+
this.recompute();
|
|
247
|
+
});
|
|
248
|
+
}, ...(ngDevMode ? [{ debugName: "#syncTabTitleOnActivate" }] : /* istanbul ignore next */ []));
|
|
249
|
+
/** Publishes structured tab preview (hover card + dirty badge) for this cached page. */
|
|
250
|
+
this.#syncTabStructuredPreview = effect(() => {
|
|
251
|
+
this.updateTrigger();
|
|
252
|
+
const tabKey = this.pageTabKey();
|
|
253
|
+
const titleValue = this.title();
|
|
254
|
+
const descriptionValue = this.description();
|
|
255
|
+
const icon = this.titleIcon();
|
|
256
|
+
const breadcrumbItems = this.breadcrumbs();
|
|
257
|
+
if (!tabKey || this.tabNavigation.effectiveMode() !== 'tabular') {
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
const isDirty = this.hasUnsavedChanges();
|
|
261
|
+
untracked(() => {
|
|
262
|
+
void this.publishTabStructuredPreview(tabKey, {
|
|
263
|
+
titleValue,
|
|
264
|
+
descriptionValue,
|
|
265
|
+
icon,
|
|
266
|
+
breadcrumbItems,
|
|
267
|
+
isDirty,
|
|
268
|
+
});
|
|
269
|
+
});
|
|
270
|
+
}, ...(ngDevMode ? [{ debugName: "#syncTabStructuredPreview" }] : /* istanbul ignore next */ []));
|
|
271
|
+
//#endregion
|
|
226
272
|
//#region ---------------- Title ----------------
|
|
227
273
|
this.title = signal('', ...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
|
|
228
274
|
this.titleIcon = signal(null, ...(ngDevMode ? [{ debugName: "titleIcon" }] : /* istanbul ignore next */ []));
|
|
@@ -231,6 +277,12 @@ class AXPPageLayoutBaseComponent {
|
|
|
231
277
|
this.title.set(await this.getPageTitle());
|
|
232
278
|
const icon = await this.getPageIcon();
|
|
233
279
|
this.titleIcon.set(icon);
|
|
280
|
+
if (this.tabNavigation.effectiveMode() === 'tabular') {
|
|
281
|
+
const tabKey = this.pageTabKey();
|
|
282
|
+
if (tabKey && icon) {
|
|
283
|
+
this.tabNavigation.syncTabIcon(tabKey, icon);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
234
286
|
}, ...(ngDevMode ? [{ debugName: "#titleEffect" }] : /* istanbul ignore next */ []));
|
|
235
287
|
this.hasTitle = computed(() => {
|
|
236
288
|
const value = this.title();
|
|
@@ -284,9 +336,20 @@ class AXPPageLayoutBaseComponent {
|
|
|
284
336
|
this.#breadcrumbsEffect = effect(async () => {
|
|
285
337
|
this.updateTrigger();
|
|
286
338
|
const breadCrumbs = await this.getPageBreadcrumbs();
|
|
287
|
-
|
|
339
|
+
const trail = [...this.defaultBreadCrumbs, ...breadCrumbs];
|
|
340
|
+
this.breadcrumbs.set(trail);
|
|
341
|
+
if (this.tabNavigation.effectiveMode() === 'tabular') {
|
|
342
|
+
const tabKey = this.pageTabKey();
|
|
343
|
+
let tabTitle = await this.resolveBreadcrumbTabTitle(breadCrumbs);
|
|
344
|
+
if (!tabTitle) {
|
|
345
|
+
tabTitle = await this.resolvePageTitleForTab();
|
|
346
|
+
}
|
|
347
|
+
if (tabKey && tabTitle) {
|
|
348
|
+
this.tabNavigation.syncTabTitle(tabKey, tabTitle);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
288
351
|
}, ...(ngDevMode ? [{ debugName: "#breadcrumbsEffect" }] : /* istanbul ignore next */ []));
|
|
289
|
-
this.hasBreadcrumbs = computed(() => this.breadcrumbs().length > 1, ...(ngDevMode ? [{ debugName: "hasBreadcrumbs" }] : /* istanbul ignore next */ []));
|
|
352
|
+
this.hasBreadcrumbs = computed(() => this.breadcrumbs().length > 1 && this.tabNavigation.effectiveMode() !== 'tabular', ...(ngDevMode ? [{ debugName: "hasBreadcrumbs" }] : /* istanbul ignore next */ []));
|
|
290
353
|
//#endregion
|
|
291
354
|
//#region ---------------- Badge ----------------
|
|
292
355
|
this.badge = signal(null, ...(ngDevMode ? [{ debugName: "badge" }] : /* istanbul ignore next */ []));
|
|
@@ -342,10 +405,75 @@ class AXPPageLayoutBaseComponent {
|
|
|
342
405
|
//#endregion
|
|
343
406
|
//#region ---------------- Lifecycle hooks ----------------
|
|
344
407
|
async ngOnInit() {
|
|
408
|
+
this.pageTabKey.set(this.resolvePageTabKey());
|
|
345
409
|
this.translateService.langChanges$.subscribe(() => {
|
|
346
410
|
this.recompute();
|
|
347
411
|
});
|
|
348
412
|
}
|
|
413
|
+
/** Maps the home-page route to the pinned home tab key instead of a path-based key. */
|
|
414
|
+
resolvePageTabKey() {
|
|
415
|
+
const homePath = this.homePageService.getCurrent().path;
|
|
416
|
+
if (homePath && normalizeRouterUrl(homePath) === normalizeRouterUrl(this.router.url)) {
|
|
417
|
+
return AXP_HOME_TAB_KEY;
|
|
418
|
+
}
|
|
419
|
+
return axpResolveTabKey(this.router);
|
|
420
|
+
}
|
|
421
|
+
//#endregion
|
|
422
|
+
//#region ---------------- Tab Title Sync ----------------
|
|
423
|
+
/** Refreshes tab title when this cached page becomes the active tab again. */
|
|
424
|
+
#syncTabTitleOnActivate;
|
|
425
|
+
/** Publishes structured tab preview (hover card + dirty badge) for this cached page. */
|
|
426
|
+
#syncTabStructuredPreview;
|
|
427
|
+
async publishTabStructuredPreview(tabKey, input) {
|
|
428
|
+
const tab = this.tabNavigation.tabList().find((item) => item.key === tabKey);
|
|
429
|
+
const breadcrumbTrail = [];
|
|
430
|
+
for (const item of input.breadcrumbItems.slice(1)) {
|
|
431
|
+
const segment = await this.resolveBreadcrumbItemTitle(item);
|
|
432
|
+
if (segment && segment !== breadcrumbTrail[breadcrumbTrail.length - 1]) {
|
|
433
|
+
breadcrumbTrail.push(segment);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
const fallbackTitle = await this.translateTextIfKey(breadcrumbTrail.join(' - ') || tab?.title || '');
|
|
437
|
+
const title = await this.resolveMultiLanguageText(input.titleValue, fallbackTitle);
|
|
438
|
+
const subtitle = await this.resolveMultiLanguageText(input.descriptionValue, '');
|
|
439
|
+
this.tabPreviewService.setPreview(tabKey, {
|
|
440
|
+
title,
|
|
441
|
+
subtitle: subtitle || undefined,
|
|
442
|
+
url: tab?.url ?? '',
|
|
443
|
+
icon: input.icon ?? tab?.icon,
|
|
444
|
+
isDirty: input.isDirty,
|
|
445
|
+
breadcrumbTrail,
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
async resolveMultiLanguageText(value, fallback) {
|
|
449
|
+
const resolvedFallback = await this.translateTextIfKey(fallback);
|
|
450
|
+
if (value == null || value === '') {
|
|
451
|
+
return resolvedFallback;
|
|
452
|
+
}
|
|
453
|
+
if (typeof value === 'string') {
|
|
454
|
+
const trimmed = value.trim();
|
|
455
|
+
if (!trimmed) {
|
|
456
|
+
return resolvedFallback;
|
|
457
|
+
}
|
|
458
|
+
if (trimmed.startsWith('@')) {
|
|
459
|
+
return (await this.translateService.translateAsync(trimmed)) || resolvedFallback;
|
|
460
|
+
}
|
|
461
|
+
return trimmed;
|
|
462
|
+
}
|
|
463
|
+
const resolved = this.translateService.resolve(value);
|
|
464
|
+
return resolved || resolvedFallback;
|
|
465
|
+
}
|
|
466
|
+
/** Resolves i18n keys (`@scope:key`); returns plain text unchanged. */
|
|
467
|
+
async translateTextIfKey(text) {
|
|
468
|
+
const trimmed = text.trim();
|
|
469
|
+
if (!trimmed) {
|
|
470
|
+
return '';
|
|
471
|
+
}
|
|
472
|
+
if (trimmed.startsWith('@')) {
|
|
473
|
+
return (await this.translateService.translateAsync(trimmed)) || trimmed;
|
|
474
|
+
}
|
|
475
|
+
return trimmed;
|
|
476
|
+
}
|
|
349
477
|
#titleEffect;
|
|
350
478
|
getPageTitle() {
|
|
351
479
|
return '';
|
|
@@ -361,10 +489,78 @@ class AXPPageLayoutBaseComponent {
|
|
|
361
489
|
getPageBreadcrumbs() {
|
|
362
490
|
return [];
|
|
363
491
|
}
|
|
492
|
+
/** Joins all page breadcrumbs (Home is excluded upstream) as the tab label. */
|
|
493
|
+
async resolveBreadcrumbTabTitle(pageBreadcrumbs) {
|
|
494
|
+
const segments = [];
|
|
495
|
+
for (const item of pageBreadcrumbs) {
|
|
496
|
+
const title = await this.resolveBreadcrumbItemTitle(item);
|
|
497
|
+
if (title && title !== segments[segments.length - 1]) {
|
|
498
|
+
segments.push(title);
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
if (!segments.length) {
|
|
502
|
+
return null;
|
|
503
|
+
}
|
|
504
|
+
return segments.join(' - ');
|
|
505
|
+
}
|
|
506
|
+
/** Resolves a breadcrumb label the same way as page chrome (`t(item.title)`). */
|
|
507
|
+
async resolveBreadcrumbItemTitle(item) {
|
|
508
|
+
const raw = item?.title;
|
|
509
|
+
if (raw == null || raw === '') {
|
|
510
|
+
return null;
|
|
511
|
+
}
|
|
512
|
+
if (typeof raw === 'string') {
|
|
513
|
+
const trimmed = raw.trim();
|
|
514
|
+
if (!trimmed) {
|
|
515
|
+
return null;
|
|
516
|
+
}
|
|
517
|
+
if (trimmed.startsWith('@')) {
|
|
518
|
+
return (await this.translateService.translateAsync(trimmed)) || trimmed;
|
|
519
|
+
}
|
|
520
|
+
return trimmed;
|
|
521
|
+
}
|
|
522
|
+
if (typeof raw === 'object' && !Array.isArray(raw)) {
|
|
523
|
+
if (this.translateService.isValidMultiLanguageObject(raw)) {
|
|
524
|
+
const resolved = this.translateService.resolve(raw);
|
|
525
|
+
return resolved || null;
|
|
526
|
+
}
|
|
527
|
+
const resolved = this.translateService.resolve(raw);
|
|
528
|
+
return resolved || null;
|
|
529
|
+
}
|
|
530
|
+
return String(raw);
|
|
531
|
+
}
|
|
532
|
+
/** Fallback tab label from the page title when breadcrumbs are not ready yet. */
|
|
533
|
+
async resolvePageTitleForTab() {
|
|
534
|
+
const raw = await Promise.resolve(this.getPageTitle());
|
|
535
|
+
if (raw == null || raw === '') {
|
|
536
|
+
return null;
|
|
537
|
+
}
|
|
538
|
+
if (typeof raw === 'string') {
|
|
539
|
+
const trimmed = raw.trim();
|
|
540
|
+
if (trimmed.startsWith('@')) {
|
|
541
|
+
return (await this.translateService.translateAsync(trimmed)) || trimmed;
|
|
542
|
+
}
|
|
543
|
+
return trimmed;
|
|
544
|
+
}
|
|
545
|
+
if (typeof raw === 'object' && !Array.isArray(raw)) {
|
|
546
|
+
const resolved = this.translateService.resolve(raw);
|
|
547
|
+
return resolved || null;
|
|
548
|
+
}
|
|
549
|
+
return null;
|
|
550
|
+
}
|
|
364
551
|
#badgeEffect;
|
|
365
552
|
hasUnsavedChanges() {
|
|
366
553
|
return false;
|
|
367
554
|
}
|
|
555
|
+
/**
|
|
556
|
+
* Confirms closing this page as a background tab (no router navigation).
|
|
557
|
+
* Override when dirty detection differs from {@link hasUnsavedChanges}.
|
|
558
|
+
*/
|
|
559
|
+
onBeforeTabClose() {
|
|
560
|
+
return this.unsavedChangesConfirm.confirmIfDirty(this.hasUnsavedChanges(), {
|
|
561
|
+
approveLeaveOnConfirm: false,
|
|
562
|
+
});
|
|
563
|
+
}
|
|
368
564
|
async getPageBadge() {
|
|
369
565
|
if (!this.hasUnsavedChanges()) {
|
|
370
566
|
return null;
|
|
@@ -675,14 +871,61 @@ const AXPLayoutDetailsViewViewModel = signalStore(withState(() => {
|
|
|
675
871
|
const evaluatedByTab = store.evaluatedTabContentsById();
|
|
676
872
|
return evaluatedByTab[tab.id] ?? [];
|
|
677
873
|
}),
|
|
678
|
-
})), withMethods((store, evaluatorService = inject(AXPExpressionEvaluatorService), router = inject(Router), route = inject(ActivatedRoute), deviceService = inject(AXPDeviceService), formatService = inject(AXFormatService), toastService = inject(AXToastService), translateService = inject(AXTranslationService), settingsService = inject(AXPSettingsService)) => {
|
|
679
|
-
const
|
|
874
|
+
})), withMethods((store, evaluatorService = inject(AXPExpressionEvaluatorService), contextEvalFactory = inject(AXPContextEvalFactory), router = inject(Router), route = inject(ActivatedRoute), deviceService = inject(AXPDeviceService), formatService = inject(AXFormatService), toastService = inject(AXToastService), translateService = inject(AXTranslationService), settingsService = inject(AXPSettingsService)) => {
|
|
875
|
+
const resolvePageFromId = (adapter, pageId) => {
|
|
876
|
+
if (pageId) {
|
|
877
|
+
return adapter.pages.find((p) => p.id === pageId) ?? null;
|
|
878
|
+
}
|
|
879
|
+
if (deviceService.isLarge() || adapter.pages.length === 1) {
|
|
880
|
+
return adapter.pages.find((p) => p.isPrimary) ?? adapter.pages[0] ?? null;
|
|
881
|
+
}
|
|
882
|
+
return null;
|
|
883
|
+
};
|
|
884
|
+
/** Loads page data, context, and evaluated widgets for the target sidebar page. */
|
|
885
|
+
const activatePageById = async (pageId, forceRefresh) => {
|
|
886
|
+
const adapter = store.adapter();
|
|
887
|
+
if (!adapter) {
|
|
888
|
+
return;
|
|
889
|
+
}
|
|
890
|
+
const queryParams = router.routerState.snapshot.root.queryParams;
|
|
891
|
+
const targetPageId = pageId ?? queryParams['page'];
|
|
892
|
+
const currentPage = resolvePageFromId(adapter, targetPageId);
|
|
893
|
+
if (!forceRefresh && currentPage?.id === store.currentPage()?.id && store.status() === AXPPageStatus.Rendered) {
|
|
894
|
+
return;
|
|
895
|
+
}
|
|
896
|
+
patchState(store, {
|
|
897
|
+
status: AXPPageStatus.Processing,
|
|
898
|
+
});
|
|
899
|
+
let context = {};
|
|
900
|
+
if (currentPage) {
|
|
901
|
+
const currentPageIndex = adapter.pages.findIndex((p) => p.id === currentPage.id);
|
|
902
|
+
const loadResult = await adapter.pages[currentPageIndex]?.load?.({ forceRefresh });
|
|
903
|
+
context = loadResult?.data ?? {};
|
|
904
|
+
}
|
|
905
|
+
const currentTab = currentPage?.tabs?.[0] ?? null;
|
|
680
906
|
const rootCtx = store.rootContext();
|
|
681
|
-
const { evaluatedPageContent, evaluatedTabContentsById } = await buildEvaluatedPageContentsForRootContext(
|
|
907
|
+
const { evaluatedPageContent, evaluatedTabContentsById } = await buildEvaluatedPageContentsForRootContext(currentPage, rootCtx, evaluatorService);
|
|
682
908
|
patchState(store, {
|
|
909
|
+
previousContext: cloneDeep(context),
|
|
910
|
+
context: cloneDeep(context),
|
|
911
|
+
status: AXPPageStatus.Rendered,
|
|
912
|
+
currentPage,
|
|
913
|
+
currentTab,
|
|
683
914
|
evaluatedPageContent,
|
|
684
915
|
evaluatedTabContentsById,
|
|
916
|
+
formDirty: false,
|
|
685
917
|
});
|
|
918
|
+
if (currentPage && axpIsEntityDetailsViewRoute(router)) {
|
|
919
|
+
const pageParam = route.snapshot.queryParamMap.get('page');
|
|
920
|
+
if (pageParam !== currentPage.id) {
|
|
921
|
+
router.navigate([], {
|
|
922
|
+
relativeTo: route,
|
|
923
|
+
queryParams: { page: currentPage.id },
|
|
924
|
+
queryParamsHandling: 'merge',
|
|
925
|
+
replaceUrl: true,
|
|
926
|
+
});
|
|
927
|
+
}
|
|
928
|
+
}
|
|
686
929
|
};
|
|
687
930
|
// Sync sidebar page from route query params
|
|
688
931
|
effect((onCleanup) => {
|
|
@@ -690,30 +933,20 @@ const AXPLayoutDetailsViewViewModel = signalStore(withState(() => {
|
|
|
690
933
|
return;
|
|
691
934
|
}
|
|
692
935
|
const subscription = route.queryParams.subscribe((params) => {
|
|
693
|
-
const pageId = params['page'];
|
|
694
936
|
const adapter = store.adapter();
|
|
695
937
|
if (!adapter) {
|
|
696
938
|
return;
|
|
697
939
|
}
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
else if (deviceService.isLarge() || adapter.pages.length === 1) {
|
|
703
|
-
currentPage = adapter.pages.find((p) => p.isPrimary) ?? adapter.pages[0];
|
|
940
|
+
const pageId = params['page'];
|
|
941
|
+
const targetPage = resolvePageFromId(adapter, pageId);
|
|
942
|
+
if (store.currentPage()?.id === targetPage?.id) {
|
|
943
|
+
return;
|
|
704
944
|
}
|
|
705
|
-
|
|
706
|
-
if (store.currentPage()
|
|
707
|
-
|
|
708
|
-
if (store.currentPage() == null && currentPage == null) {
|
|
709
|
-
return;
|
|
710
|
-
}
|
|
711
|
-
patchState(store, {
|
|
712
|
-
currentPage,
|
|
713
|
-
currentTab,
|
|
714
|
-
});
|
|
715
|
-
void refreshEvaluatedPageContents(currentPage);
|
|
945
|
+
// `load()` → `activatePageById` owns initial hydration when both sides are still null.
|
|
946
|
+
if (store.currentPage() == null && targetPage == null) {
|
|
947
|
+
return;
|
|
716
948
|
}
|
|
949
|
+
void activatePageById(targetPage?.id);
|
|
717
950
|
});
|
|
718
951
|
onCleanup(() => subscription.unsubscribe());
|
|
719
952
|
});
|
|
@@ -774,7 +1007,12 @@ const AXPLayoutDetailsViewViewModel = signalStore(withState(() => {
|
|
|
774
1007
|
evaluated = await evaluatorService.evaluate(raw, evalScope);
|
|
775
1008
|
}
|
|
776
1009
|
else if (typeof sourceAfterPreLocale === 'string') {
|
|
777
|
-
|
|
1010
|
+
if (sourceAfterPreLocale.trim().startsWith('@')) {
|
|
1011
|
+
evaluated = await translateService.translateAsync(sourceAfterPreLocale);
|
|
1012
|
+
}
|
|
1013
|
+
else {
|
|
1014
|
+
evaluated = await formatService.format(sourceAfterPreLocale, 'string', formatContext);
|
|
1015
|
+
}
|
|
778
1016
|
}
|
|
779
1017
|
else {
|
|
780
1018
|
evaluated = await formatService.format(raw, 'string', formatContext);
|
|
@@ -808,71 +1046,12 @@ const AXPLayoutDetailsViewViewModel = signalStore(withState(() => {
|
|
|
808
1046
|
});
|
|
809
1047
|
},
|
|
810
1048
|
async loadPage(pageId, forceRefresh) {
|
|
811
|
-
|
|
812
|
-
if (!adapter) {
|
|
813
|
-
throw new Error('Adapter must be loaded before loading page');
|
|
814
|
-
}
|
|
815
|
-
patchState(store, {
|
|
816
|
-
status: AXPPageStatus.Processing,
|
|
817
|
-
});
|
|
818
|
-
// Get page ID from parameter or query params
|
|
819
|
-
const queryParams = router.routerState.snapshot.root.queryParams;
|
|
820
|
-
const targetPageId = pageId || queryParams['page'];
|
|
821
|
-
// Find current page from page ID or conditionally default to first page
|
|
822
|
-
let currentPage = null;
|
|
823
|
-
if (targetPageId) {
|
|
824
|
-
const foundPage = adapter.pages.find((p) => p.id === targetPageId);
|
|
825
|
-
if (foundPage) {
|
|
826
|
-
currentPage = foundPage;
|
|
827
|
-
}
|
|
828
|
-
}
|
|
829
|
-
else {
|
|
830
|
-
// Only auto-select primary page if layout is large
|
|
831
|
-
if (deviceService.isLarge() || adapter.pages.length == 1) {
|
|
832
|
-
const primaryPage = adapter.pages.find((p) => p.isPrimary) ?? adapter.pages[0];
|
|
833
|
-
currentPage = primaryPage;
|
|
834
|
-
}
|
|
835
|
-
}
|
|
836
|
-
// Load page data if current page exists
|
|
837
|
-
let context = {};
|
|
838
|
-
if (currentPage) {
|
|
839
|
-
const currentPageIndex = adapter.pages.findIndex((p) => p.id === currentPage.id);
|
|
840
|
-
const loadResult = await adapter.pages[currentPageIndex]?.load?.({ forceRefresh });
|
|
841
|
-
context = loadResult?.data ?? {};
|
|
842
|
-
}
|
|
843
|
-
// Set current tab to first tab of the current page (internal state only)
|
|
844
|
-
let currentTab = currentPage?.tabs?.[0] ?? null;
|
|
845
|
-
const rootCtx = store.rootContext();
|
|
846
|
-
const { evaluatedPageContent, evaluatedTabContentsById } = await buildEvaluatedPageContentsForRootContext(currentPage, rootCtx, evaluatorService);
|
|
847
|
-
patchState(store, {
|
|
848
|
-
previousContext: cloneDeep(context),
|
|
849
|
-
context: cloneDeep(context),
|
|
850
|
-
status: AXPPageStatus.Rendered,
|
|
851
|
-
currentPage,
|
|
852
|
-
currentTab,
|
|
853
|
-
evaluatedPageContent,
|
|
854
|
-
evaluatedTabContentsById,
|
|
855
|
-
formDirty: false,
|
|
856
|
-
});
|
|
857
|
-
if (currentPage && axpIsEntityDetailsViewRoute(router)) {
|
|
858
|
-
const pageParam = route.snapshot.queryParamMap.get('page');
|
|
859
|
-
if (pageParam !== currentPage.id) {
|
|
860
|
-
router.navigate([], {
|
|
861
|
-
relativeTo: route,
|
|
862
|
-
queryParams: { page: currentPage.id },
|
|
863
|
-
queryParamsHandling: 'merge',
|
|
864
|
-
replaceUrl: true,
|
|
865
|
-
});
|
|
866
|
-
}
|
|
867
|
-
}
|
|
1049
|
+
await activatePageById(pageId, forceRefresh);
|
|
868
1050
|
},
|
|
869
1051
|
async load(adapter) {
|
|
870
|
-
// Setup expression evaluator scope
|
|
871
1052
|
const scope = {
|
|
872
1053
|
context: {
|
|
873
|
-
|
|
874
|
-
return getSmart(store.context(), path);
|
|
875
|
-
},
|
|
1054
|
+
...contextEvalFactory.createLiveScope(() => store.context()),
|
|
876
1055
|
isDirty: () => store.isDirty(),
|
|
877
1056
|
},
|
|
878
1057
|
};
|
|
@@ -951,14 +1130,8 @@ const AXPLayoutDetailsViewViewModel = signalStore(withState(() => {
|
|
|
951
1130
|
setCurrentPage(page) {
|
|
952
1131
|
const previousPage = store.currentPage();
|
|
953
1132
|
if (page) {
|
|
954
|
-
const firstTab = page.tabs?.[0] ?? null;
|
|
955
1133
|
const pageParam = route.snapshot.queryParamMap.get('page');
|
|
956
1134
|
const isSamePage = previousPage?.id === page.id && pageParam === page.id;
|
|
957
|
-
patchState(store, {
|
|
958
|
-
currentPage: page,
|
|
959
|
-
currentTab: firstTab,
|
|
960
|
-
...(page.isReadonly ? { formDirty: false } : {}),
|
|
961
|
-
});
|
|
962
1135
|
if (!isSamePage && axpIsEntityDetailsViewRoute(router)) {
|
|
963
1136
|
const queryParams = { page: page.id };
|
|
964
1137
|
if (previousPage?.id && previousPage.id !== page.id) {
|
|
@@ -972,7 +1145,7 @@ const AXPLayoutDetailsViewViewModel = signalStore(withState(() => {
|
|
|
972
1145
|
replaceUrl: false,
|
|
973
1146
|
});
|
|
974
1147
|
}
|
|
975
|
-
void
|
|
1148
|
+
void activatePageById(page.id);
|
|
976
1149
|
}
|
|
977
1150
|
else {
|
|
978
1151
|
patchState(store, {
|
|
@@ -1107,6 +1280,7 @@ const AXPLayoutDetailsViewViewModel = signalStore(withState(() => {
|
|
|
1107
1280
|
},
|
|
1108
1281
|
async discard() {
|
|
1109
1282
|
patchState(store, {
|
|
1283
|
+
context: cloneDeep(store.previousContext()),
|
|
1110
1284
|
formDirty: false,
|
|
1111
1285
|
});
|
|
1112
1286
|
},
|
|
@@ -1207,6 +1381,66 @@ const AXPLayoutDetailsViewViewModel = signalStore(withState(() => {
|
|
|
1207
1381
|
};
|
|
1208
1382
|
}));
|
|
1209
1383
|
|
|
1384
|
+
//#region ---- Details View Action Shortcut Utilities ----
|
|
1385
|
+
/** Platform defaults for known entity detail commands when `shortcuts` is omitted on the action. */
|
|
1386
|
+
const DEFAULT_ENTITY_DETAIL_ACTION_SHORTCUTS = {
|
|
1387
|
+
'delete-entity': ['ctrl+shift+delete'],
|
|
1388
|
+
};
|
|
1389
|
+
const DEFAULT_FOOTER_COMMAND_SHORTCUTS = {
|
|
1390
|
+
'update-entity': ['ctrl+s'],
|
|
1391
|
+
discard: ['escape'],
|
|
1392
|
+
};
|
|
1393
|
+
/**
|
|
1394
|
+
* Returns the base workflow/command name from a composite entity action command id (`cmd&actionName`).
|
|
1395
|
+
*/
|
|
1396
|
+
function getDetailsViewActionCommandBaseName(commandName) {
|
|
1397
|
+
if (!commandName) {
|
|
1398
|
+
return '';
|
|
1399
|
+
}
|
|
1400
|
+
return commandName.split('&')[0];
|
|
1401
|
+
}
|
|
1402
|
+
/**
|
|
1403
|
+
* Resolves keyboard shortcuts for a header entity action.
|
|
1404
|
+
* Explicit `shortcuts` wins; pass `[]` to disable. Otherwise known commands use platform defaults.
|
|
1405
|
+
*/
|
|
1406
|
+
function resolveDetailsViewActionShortcuts(commandBaseName, explicit) {
|
|
1407
|
+
if (explicit !== undefined) {
|
|
1408
|
+
const chords = flattenDetailsViewActionShortcuts(explicit);
|
|
1409
|
+
return chords.length ? chords : undefined;
|
|
1410
|
+
}
|
|
1411
|
+
const defaults = DEFAULT_ENTITY_DETAIL_ACTION_SHORTCUTS[commandBaseName];
|
|
1412
|
+
return defaults?.length ? [...defaults] : undefined;
|
|
1413
|
+
}
|
|
1414
|
+
/**
|
|
1415
|
+
* Resolves save/discard footer command shortcuts from page settings.
|
|
1416
|
+
*/
|
|
1417
|
+
function resolveDetailsViewFooterCommandShortcuts(commandName, explicit) {
|
|
1418
|
+
if (explicit !== undefined) {
|
|
1419
|
+
const chords = flattenDetailsViewActionShortcuts(explicit);
|
|
1420
|
+
return chords.length ? chords : undefined;
|
|
1421
|
+
}
|
|
1422
|
+
const defaults = DEFAULT_FOOTER_COMMAND_SHORTCUTS[commandName];
|
|
1423
|
+
return defaults?.length ? [...defaults] : undefined;
|
|
1424
|
+
}
|
|
1425
|
+
function flattenDetailsViewActionShortcuts(shortcuts) {
|
|
1426
|
+
const chords = normalizeKeyboardShortcuts(shortcuts).flatMap((shortcut) => shortcut.keys);
|
|
1427
|
+
return dedupeDetailsViewActionShortcuts(chords);
|
|
1428
|
+
}
|
|
1429
|
+
function dedupeDetailsViewActionShortcuts(shortcuts) {
|
|
1430
|
+
const seen = new Set();
|
|
1431
|
+
const result = [];
|
|
1432
|
+
for (const shortcut of shortcuts) {
|
|
1433
|
+
const token = shortcut.trim().toLowerCase();
|
|
1434
|
+
if (!token || seen.has(token)) {
|
|
1435
|
+
continue;
|
|
1436
|
+
}
|
|
1437
|
+
seen.add(token);
|
|
1438
|
+
result.push(shortcut.trim());
|
|
1439
|
+
}
|
|
1440
|
+
return result;
|
|
1441
|
+
}
|
|
1442
|
+
//#endregion
|
|
1443
|
+
|
|
1210
1444
|
/**
|
|
1211
1445
|
* Registry of rendered page component instances by component key.
|
|
1212
1446
|
* Used so page execute can delegate to the actual component instance in the view.
|
|
@@ -1345,77 +1579,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
1345
1579
|
}]
|
|
1346
1580
|
}], propDecorators: { componentKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "componentKey", required: true }] }], rootContext: [{ type: i0.Input, args: [{ isSignal: true, alias: "rootContext", required: false }] }], pageConfig: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageConfig", required: false }] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], componentReady: [{ type: i0.Output, args: ["componentReady"] }] } });
|
|
1347
1581
|
|
|
1348
|
-
//#region ---- Details View Action Shortcut Utilities ----
|
|
1349
|
-
/** Platform defaults for known entity detail commands when `shortcuts` is omitted on the action. */
|
|
1350
|
-
const DEFAULT_ENTITY_DETAIL_ACTION_SHORTCUTS = {
|
|
1351
|
-
'delete-entity': ['ctrl+shift+delete'],
|
|
1352
|
-
};
|
|
1353
|
-
const DEFAULT_FOOTER_COMMAND_SHORTCUTS = {
|
|
1354
|
-
'update-entity': ['ctrl+s'],
|
|
1355
|
-
discard: ['escape'],
|
|
1356
|
-
};
|
|
1357
|
-
/**
|
|
1358
|
-
* Returns the base workflow/command name from a composite entity action command id (`cmd&actionName`).
|
|
1359
|
-
*/
|
|
1360
|
-
function getDetailsViewActionCommandBaseName(commandName) {
|
|
1361
|
-
if (!commandName) {
|
|
1362
|
-
return '';
|
|
1363
|
-
}
|
|
1364
|
-
return commandName.split('&')[0];
|
|
1365
|
-
}
|
|
1366
|
-
/**
|
|
1367
|
-
* Resolves keyboard shortcuts for a header entity action.
|
|
1368
|
-
* Explicit `shortcuts` wins; pass `[]` to disable. Otherwise known commands use platform defaults.
|
|
1369
|
-
*/
|
|
1370
|
-
function resolveDetailsViewActionShortcuts(commandBaseName, explicit) {
|
|
1371
|
-
if (explicit !== undefined) {
|
|
1372
|
-
const chords = flattenDetailsViewActionShortcuts(explicit);
|
|
1373
|
-
return chords.length ? chords : undefined;
|
|
1374
|
-
}
|
|
1375
|
-
const defaults = DEFAULT_ENTITY_DETAIL_ACTION_SHORTCUTS[commandBaseName];
|
|
1376
|
-
return defaults?.length ? [...defaults] : undefined;
|
|
1377
|
-
}
|
|
1378
|
-
/**
|
|
1379
|
-
* Resolves save/discard footer command shortcuts from page settings.
|
|
1380
|
-
*/
|
|
1381
|
-
function resolveDetailsViewFooterCommandShortcuts(commandName, explicit) {
|
|
1382
|
-
if (explicit !== undefined) {
|
|
1383
|
-
const chords = flattenDetailsViewActionShortcuts(explicit);
|
|
1384
|
-
return chords.length ? chords : undefined;
|
|
1385
|
-
}
|
|
1386
|
-
const defaults = DEFAULT_FOOTER_COMMAND_SHORTCUTS[commandName];
|
|
1387
|
-
return defaults?.length ? [...defaults] : undefined;
|
|
1388
|
-
}
|
|
1389
|
-
function flattenDetailsViewActionShortcuts(shortcuts) {
|
|
1390
|
-
const chords = normalizeKeyboardShortcuts(shortcuts).flatMap((shortcut) => shortcut.keys);
|
|
1391
|
-
return dedupeDetailsViewActionShortcuts(chords);
|
|
1392
|
-
}
|
|
1393
|
-
function dedupeDetailsViewActionShortcuts(shortcuts) {
|
|
1394
|
-
const seen = new Set();
|
|
1395
|
-
const result = [];
|
|
1396
|
-
for (const shortcut of shortcuts) {
|
|
1397
|
-
const token = shortcut.trim().toLowerCase();
|
|
1398
|
-
if (!token || seen.has(token)) {
|
|
1399
|
-
continue;
|
|
1400
|
-
}
|
|
1401
|
-
seen.add(token);
|
|
1402
|
-
result.push(shortcut.trim());
|
|
1403
|
-
}
|
|
1404
|
-
return result;
|
|
1405
|
-
}
|
|
1406
|
-
//#endregion
|
|
1407
|
-
|
|
1408
1582
|
class AXPLayoutDetailsViewComponent extends AXPPageLayoutBaseComponent {
|
|
1409
1583
|
constructor() {
|
|
1410
1584
|
super(...arguments);
|
|
1411
1585
|
this.adapter = input.required(...(ngDevMode ? [{ debugName: "adapter" }] : /* istanbul ignore next */ []));
|
|
1412
1586
|
//
|
|
1413
1587
|
this.vm = inject(AXPLayoutDetailsViewViewModel);
|
|
1414
|
-
this.router = inject(Router);
|
|
1415
1588
|
this.route = inject(ActivatedRoute);
|
|
1416
1589
|
this.eventService = inject(AXPBroadcastEventService);
|
|
1417
1590
|
this.deviceService = inject(AXPDeviceService);
|
|
1418
|
-
this.unsavedChangesConfirm = inject(AXPUnsavedChangesConfirmService);
|
|
1419
1591
|
this.unsavedChangesPopstate = inject(AXPUnsavedChangesPopstateService);
|
|
1420
1592
|
this.pageComponentInstanceRegistry = inject(AXPPageComponentInstanceRegistryService);
|
|
1421
1593
|
this.shortcutRegistry = inject(AXPKeyboardShortcutRegistry);
|
|
@@ -1664,11 +1836,17 @@ class AXPLayoutDetailsViewComponent extends AXPPageLayoutBaseComponent {
|
|
|
1664
1836
|
}
|
|
1665
1837
|
finalizeDiscard() {
|
|
1666
1838
|
const container = this.widgetContainer();
|
|
1667
|
-
if (
|
|
1839
|
+
if (container) {
|
|
1840
|
+
container.revertAndSettle();
|
|
1841
|
+
this.vm.applySavedState(container.contextService.data());
|
|
1668
1842
|
return;
|
|
1669
1843
|
}
|
|
1670
|
-
|
|
1671
|
-
|
|
1844
|
+
void this.vm.discard();
|
|
1845
|
+
}
|
|
1846
|
+
/** Reverts widget form state and view-model context to the last saved baseline. */
|
|
1847
|
+
async discardUnsavedChanges() {
|
|
1848
|
+
this.finalizeDiscard();
|
|
1849
|
+
this.recompute();
|
|
1672
1850
|
}
|
|
1673
1851
|
/**
|
|
1674
1852
|
* `ax-tabs` selects on click before we can confirm. Revert synchronously, then confirm + switch.
|
|
@@ -1764,6 +1942,15 @@ class AXPLayoutDetailsViewComponent extends AXPPageLayoutBaseComponent {
|
|
|
1764
1942
|
async getBackButton() {
|
|
1765
1943
|
return this.vm.getBackButton();
|
|
1766
1944
|
}
|
|
1945
|
+
hasUnsavedChanges() {
|
|
1946
|
+
if (this.vm.currentPage()?.isReadonly) {
|
|
1947
|
+
return false;
|
|
1948
|
+
}
|
|
1949
|
+
if (this.isActivePageComponentDirty()) {
|
|
1950
|
+
return true;
|
|
1951
|
+
}
|
|
1952
|
+
return this.liveFormIsDirty();
|
|
1953
|
+
}
|
|
1767
1954
|
async getPageBadge() {
|
|
1768
1955
|
if (this.vm.currentPage()?.isReadonly) {
|
|
1769
1956
|
return null;
|
|
@@ -1808,6 +1995,12 @@ class AXPLayoutDetailsViewComponent extends AXPPageLayoutBaseComponent {
|
|
|
1808
1995
|
}
|
|
1809
1996
|
return this.confirmLeaveIfDirty();
|
|
1810
1997
|
}
|
|
1998
|
+
/** Background tab close — confirm without discarding (the cached instance is destroyed). */
|
|
1999
|
+
onBeforeTabClose() {
|
|
2000
|
+
return this.unsavedChangesConfirm.confirmIfDirty(this.shouldPromptLeave(), {
|
|
2001
|
+
approveLeaveOnConfirm: false,
|
|
2002
|
+
});
|
|
2003
|
+
}
|
|
1811
2004
|
/** Active entity component page instance, when the current details sub-page is component-based. */
|
|
1812
2005
|
getActivePageComponentInstance() {
|
|
1813
2006
|
const componentKey = this.getActivePageComponentKey();
|
|
@@ -1832,6 +2025,12 @@ class AXPLayoutDetailsViewComponent extends AXPPageLayoutBaseComponent {
|
|
|
1832
2025
|
static isComponentPageContent(item) {
|
|
1833
2026
|
return 'type' in item && item.type === 'component';
|
|
1834
2027
|
}
|
|
2028
|
+
pageContentTrackKey(item, index) {
|
|
2029
|
+
if (AXPLayoutDetailsViewComponent.isComponentPageContent(item)) {
|
|
2030
|
+
return `component:${item.componentKey}`;
|
|
2031
|
+
}
|
|
2032
|
+
return item.name ?? `widget:${index}`;
|
|
2033
|
+
}
|
|
1835
2034
|
/** Whether the active component-based entity page has unsaved edits. */
|
|
1836
2035
|
isActivePageComponentDirty() {
|
|
1837
2036
|
return this.getActivePageComponentInstance()?.hasUnsavedChanges() ?? false;
|
|
@@ -1847,8 +2046,13 @@ class AXPLayoutDetailsViewComponent extends AXPPageLayoutBaseComponent {
|
|
|
1847
2046
|
return this.liveFormIsDirty();
|
|
1848
2047
|
}
|
|
1849
2048
|
/** Single gate for leaving the page or switching details sub-pages when dirty. */
|
|
1850
|
-
confirmLeaveIfDirty() {
|
|
1851
|
-
|
|
2049
|
+
async confirmLeaveIfDirty() {
|
|
2050
|
+
const shouldPrompt = this.shouldPromptLeave();
|
|
2051
|
+
const confirmed = await this.unsavedChangesConfirm.confirmIfDirty(shouldPrompt);
|
|
2052
|
+
if (confirmed && shouldPrompt) {
|
|
2053
|
+
await this.discardUnsavedChanges();
|
|
2054
|
+
}
|
|
2055
|
+
return confirmed;
|
|
1852
2056
|
}
|
|
1853
2057
|
//#endregion
|
|
1854
2058
|
//#region ---- Command Execution ----
|
|
@@ -1867,9 +2071,7 @@ class AXPLayoutDetailsViewComponent extends AXPPageLayoutBaseComponent {
|
|
|
1867
2071
|
return;
|
|
1868
2072
|
}
|
|
1869
2073
|
if (command.name == rejectCommand) {
|
|
1870
|
-
await this.
|
|
1871
|
-
this.finalizeDiscard();
|
|
1872
|
-
this.recompute();
|
|
2074
|
+
await this.discardUnsavedChanges();
|
|
1873
2075
|
return;
|
|
1874
2076
|
}
|
|
1875
2077
|
// Handle status update command
|
|
@@ -1932,9 +2134,7 @@ class AXPLayoutDetailsViewComponent extends AXPPageLayoutBaseComponent {
|
|
|
1932
2134
|
return;
|
|
1933
2135
|
}
|
|
1934
2136
|
if (isDiscardCommand(command.name, rejectCommand)) {
|
|
1935
|
-
await this.
|
|
1936
|
-
this.finalizeDiscard();
|
|
1937
|
-
this.recompute();
|
|
2137
|
+
await this.discardUnsavedChanges();
|
|
1938
2138
|
return;
|
|
1939
2139
|
}
|
|
1940
2140
|
if (!this.isActivePageComponentDirty()) {
|
|
@@ -2143,13 +2343,13 @@ class AXPLayoutDetailsViewComponent extends AXPPageLayoutBaseComponent {
|
|
|
2143
2343
|
useExisting: AXPLayoutDetailsViewComponent,
|
|
2144
2344
|
},
|
|
2145
2345
|
AXPLayoutDetailsViewViewModel,
|
|
2146
|
-
], viewQueries: [{ propertyName: "form", first: true, predicate: ["form"], descendants: true, isSignal: true }, { propertyName: "widgetContainer", first: true, predicate: AXPWidgetContainerComponent, descendants: true, isSignal: true }, { propertyName: "sidebarTabs", first: true, predicate: ["sidebarTabs"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<axp-page-layout *translate=\"let t\">\n @if (vm.adapter()?.pages?.length! > 1 && vm.isLarge()) {\n <axp-layout-start-side>\n <axp-layout-header>\n <axp-layout-title>{{\n (formatService.format(vm.showPages() ? (vm.adapter()?.title ?? '') : (vm.adapter()?.label ?? ''), 'string', vm.rootContext())\n | translate\n | async)\n }}</axp-layout-title>\n <axp-layout-description>{{\n (formatService.format(vm.adapter()?.description ?? '', 'string', vm.rootContext()) | translate | async)\n }}</axp-layout-description>\n </axp-layout-header>\n <axp-layout-content>\n <ax-tabs\n #sidebarTabs\n class=\"axp-vertical-tabs\"\n [look]=\"'with-line-color'\"\n [location]=\"'end'\"\n [fitParent]=\"true\"\n (onActiveTabChanged)=\"handleSidebarTabChanged($event)\"\n >\n @for (item of vm.adapter()?.pages; track item.id) {\n <ax-tab-item\n [key]=\"item.id\"\n [text]=\"(formatService.format(item.label, 'string', vm.rootContext()) | translate | async)!\"\n [active]=\"vm.currentPage()?.id === item.id\"\n >\n <ax-prefix>\n <ax-icon icon=\"far ${{ item.icon }}\"></ax-icon>\n </ax-prefix>\n </ax-tab-item>\n }\n </ax-tabs>\n </axp-layout-content>\n </axp-layout-start-side>\n }\n <!-- Content Section -->\n <axp-page-content class=\"ax-flex ax-flex-row ax-gap-4\">\n @if (vm.showPages()) {\n <axp-layout-list class=\"ax-w-full\">\n @for (item of vm.adapter()?.pages; track $index) {\n <axp-layout-list-item (click)=\"handleSelectPage(item)\">\n <axp-layout-prefix>\n <ax-icon icon=\"far ${{ item.icon }}\" class=\"ax-text-gray-500\"></ax-icon>\n </axp-layout-prefix>\n <axp-layout-content>\n <a class=\"ax-font-semibold\">{{\n formatService.format(item.label, 'string', vm.rootContext()) | translate | async\n }}</a>\n </axp-layout-content>\n <axp-layout-suffix>\n <ax-icon icon=\" far fa-chevron-right\" class=\"ax-text-gray-400\"></ax-icon>\n </axp-layout-suffix>\n </axp-layout-list-item>\n }\n </axp-layout-list>\n } @else {\n <ax-form class=\"ax-h-full ax-w-full\" #form>\n <axp-widgets-container\n #widgetsContainerRef\n [context]=\"vm.context()\"\n (onContextChanged)=\"handleOnContextChanged($event)\"\n >\n <div class=\"ax-flex ax-flex-col ax-w-full ax-gap-4 ax-h-full\">\n @for (item of vm.currentPageContent(); track $index) {\n @if ('type' in item && item.type === 'component') {\n <!-- Component-based content -->\n <div\n axp-page-component-renderer\n [componentKey]=\"$any(item).componentKey\"\n [rootContext]=\"vm.rootContext()\"\n [pageConfig]=\"$any(item).pageConfig\"\n [options]=\"$any(item).options\"\n (componentReady)=\"recompute()\"\n ></div>\n } @else {\n <!-- Widget-based content -->\n <ng-container axp-widget-renderer [node]=\"$any(item)\" [mode]=\"$any(item).mode ?? 'view'\"></ng-container>\n }\n }\n @if (vm.currentPage()?.tabs?.length) {\n <div class=\"axp-horizontal-tabs\">\n <ax-tabs [location]=\"'bottom'\" [fitParent]=\"false\" look=\"classic\">\n @for (tab of vm.currentPageTabs(); track $index) {\n <ax-tab-item\n (onClick)=\"vm.setCurrentTab(tab)\"\n [text]=\"(tab.title | translate | async)!\"\n [active]=\"vm.currentTab()?.id === tab.id\"\n >\n <ax-prefix>\n <ax-icon icon=\"far ${{ tab.icon }}\"></ax-icon>\n </ax-prefix>\n </ax-tab-item>\n }\n </ax-tabs>\n <div class=\"content\">\n @if (vm.currentTab()) {\n @for (content of vm.activeTabContent(); track content.name) {\n <ng-container axp-widget-renderer [node]=\"content\" [mode]=\"content.mode ?? 'view'\">\n </ng-container>\n }\n }\n </div>\n </div>\n }\n </div>\n </axp-widgets-container>\n </ax-form>\n }\n </axp-page-content>\n\n <!-- Footer Section -->\n @if (hasFooter()) {\n <axp-page-footer class=\"--animated\">\n <axp-layout-suffix>\n <!-- secondary footer actions -->\n @if (hasVisibleFooterSecondaryActions()) {\n <ax-button\n [class.ax-sm]=\"deviceService.isSmall()\"\n [iconOnly]=\"deviceService.isSmall()\"\n [text]=\"'@general:terms.interface.actions' | translate | async\"\n [look]=\"deviceService.isSmall() ? 'blank' : 'solid'\"\n [color]=\"'default'\"\n >\n <ax-prefix>\n <i class=\"fa-solid fa-ellipsis-vertical\"></i>\n </ax-prefix>\n <ax-dropdown-panel #panel>\n <ax-button-item-list>\n @for (action of footerSecondaryActions(); track $index) {\n @if (isFooterActionVisible(action)) {\n <ax-button-item\n [text]=\"(t(action.title) | async)!\"\n [color]=\"action.color\"\n [disabled]=\"isFooterActionDisabled(action)\"\n (onClick)=\"execute(action.command!)\"\n >\n <ax-prefix>\n <ax-icon icon=\"fa-light {{ action.icon }}\"></ax-icon>\n </ax-prefix>\n </ax-button-item>\n @if (action.break) {\n <ax-divider></ax-divider>\n }\n }\n }\n </ax-button-item-list>\n </ax-dropdown-panel>\n </ax-button>\n }\n\n @if (hasVisibleFooterPrimaryActions()) {\n @if (showSaveDiscardFooter()) {\n @if (rejectFooterCommand(); as reject) {\n <ax-button\n [class.ax-sm]=\"deviceService.isSmall()\"\n [disabled]=\"vm.isSaving()\"\n [text]=\"(t(reject.title) | async)!\"\n [look]=\"'solid'\"\n [color]=\"reject.color\"\n (onClick)=\"execute(reject.command)\"\n >\n <ax-prefix>\n <i class=\"{{ reject.icon }}\"></i>\n </ax-prefix>\n </ax-button>\n }\n @if (acceptFooterCommand(); as accept) {\n <ax-button\n [class.ax-sm]=\"deviceService.isSmall()\"\n [disabled]=\"vm.isSaving()\"\n [text]=\"(t(accept.title) | async)!\"\n [look]=\"'solid'\"\n [color]=\"accept.color\"\n (onClick)=\"execute(accept.command)\"\n >\n <ax-prefix>\n <i class=\"{{ accept.icon }}\"></i>\n </ax-prefix>\n </ax-button>\n }\n }\n @for (action of footerPrimaryActions(); track $index) {\n @if (isFooterActionVisible(action)) {\n <ax-button\n [class.ax-sm]=\"deviceService.isSmall()\"\n [disabled]=\"isFooterActionDisabled(action)\"\n [text]=\"(t(action.title) | async)!\"\n [look]=\"'solid'\"\n [color]=\"action.color\"\n (onClick)=\"execute(action.command!)\"\n >\n <ax-prefix>\n <i class=\"{{ action.icon }}\"></i>\n </ax-prefix>\n @if (action?.items) {\n <ax-dropdown-panel #panel>\n <ax-button-item-list>\n @for (sub of action?.items; track $index) {\n @if (isFooterActionVisible(sub)) {\n <ax-button-item\n [text]=\"(t(sub.title) | async)!\"\n [color]=\"sub.color\"\n [disabled]=\"isFooterActionDisabled(sub)\"\n (onClick)=\"execute(sub.command!)\"\n >\n <ax-prefix>\n <ax-icon icon=\"fa-light {{ sub.icon }}\"></ax-icon>\n </ax-prefix>\n </ax-button-item>\n @if (sub.break) {\n <ax-divider></ax-divider>\n }\n }\n }\n </ax-button-item-list>\n </ax-dropdown-panel>\n }\n </ax-button>\n }\n }\n }\n </axp-layout-suffix>\n </axp-page-footer>\n }\n</axp-page-layout>\n", styles: ["axp-layout-details-view .axp-vertical-tabs{--ax-comp-tabs-default-border-radius: 0}axp-layout-details-view .axp-vertical-tabs ax-tab-item{margin-top:0!important;margin-bottom:0!important;padding-top:.75rem!important;padding-bottom:.75rem!important;font-weight:600!important}axp-layout-details-view .axp-vertical-tabs ax-tab-item>div{display:flex!important;align-items:center!important}axp-layout-details-view .axp-vertical-tabs ax-tab-item>div .ax-tab-item-text{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}axp-layout-details-view .axp-horizontal-tabs{display:flex;width:100%;flex-direction:column}axp-layout-details-view .axp-horizontal-tabs .content{margin-top:1rem;margin-bottom:1rem;display:block;width:100%}axp-layout-details-view axp-layout-section axp-layout-content{display:flex;width:100%;flex-direction:column}axp-layout-details-view axp-layout-section axp-layout-content>:not([hidden])~:not([hidden]){--tw-divide-y-reverse: 0;border-top-width:calc(1px * calc(1 - var(--tw-divide-y-reverse)));border-bottom-width:calc(1px * var(--tw-divide-y-reverse));border-style:dashed;--tw-divide-opacity: 1;border-color:rgba(var(--ax-sys-color-border-lightest-surface),var(--tw-divide-opacity, 1))}axp-layout-details-view axp-layout-section axp-layout-content .--item-container{align-items:center;gap:1rem;padding:1rem}@media(min-width:1024px){axp-layout-details-view axp-layout-section axp-layout-content .--item-container{padding-left:1.5rem;padding-right:1.5rem}}@media(min-width:1280px){axp-layout-details-view axp-layout-section axp-layout-content .--item-container{padding-left:2rem;padding-right:2rem}}@media(min-width:1536px){axp-layout-details-view axp-layout-section axp-layout-content .--item-container{padding-left:2.5rem;padding-right:2.5rem}}axp-layout-details-view axp-layout-section axp-layout-content .--item-container{display:grid;grid-template-columns:repeat(12,minmax(0,1fr))}axp-layout-details-view axp-layout-section axp-layout-content .--item-container axp-layout-description{margin-top:.5rem!important;font-size:.75rem!important;line-height:1rem!important}axp-layout-details-view axp-page-content ax-form form{height:100%}axp-layout-details-view axp-layout-start-side{border-inline-end-width:1px}\n"], dependencies: [{ kind: "ngmodule", type:
|
|
2346
|
+
], viewQueries: [{ propertyName: "form", first: true, predicate: ["form"], descendants: true, isSignal: true }, { propertyName: "widgetContainer", first: true, predicate: AXPWidgetContainerComponent, descendants: true, isSignal: true }, { propertyName: "sidebarTabs", first: true, predicate: ["sidebarTabs"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<axp-page-layout *translate=\"let t\">\n @if (vm.adapter()?.pages?.length! > 1 && vm.isLarge()) {\n <axp-layout-start-side>\n <axp-layout-header>\n <axp-layout-title>{{\n formatService.format(\n vm.showPages() ? (vm.adapter()?.title ?? '') : (vm.adapter()?.label ?? ''),\n 'string',\n vm.rootContext()\n )\n | translate\n | async\n }}</axp-layout-title>\n <axp-layout-description>{{\n formatService.format(vm.adapter()?.description ?? '', 'string', vm.rootContext()) | translate | async\n }}</axp-layout-description>\n </axp-layout-header>\n <axp-layout-content>\n <ax-tabs\n #sidebarTabs\n class=\"axp-vertical-tabs\"\n [look]=\"'with-line-color'\"\n [location]=\"'end'\"\n [fitParent]=\"true\"\n (onActiveTabChanged)=\"handleSidebarTabChanged($event)\"\n >\n @for (item of vm.adapter()?.pages; track item.id) {\n <ax-tab-item\n [key]=\"item.id\"\n [text]=\"(formatService.format(item.label, 'string', vm.rootContext()) | translate | async)!\"\n [active]=\"vm.currentPage()?.id === item.id\"\n >\n <ax-prefix>\n <ax-icon icon=\"far ${{ item.icon }}\"></ax-icon>\n </ax-prefix>\n </ax-tab-item>\n }\n </ax-tabs>\n </axp-layout-content>\n </axp-layout-start-side>\n }\n <!-- Content Section -->\n <axp-page-content class=\"ax-flex ax-flex-row ax-gap-4\">\n @if (vm.showPages()) {\n <axp-layout-list class=\"ax-w-full\">\n @for (item of vm.adapter()?.pages; track $index) {\n <axp-layout-list-item (click)=\"handleSelectPage(item)\">\n <axp-layout-prefix>\n <ax-icon icon=\"far ${{ item.icon }}\" class=\"ax-text-gray-500\"></ax-icon>\n </axp-layout-prefix>\n <axp-layout-content>\n <a class=\"ax-font-semibold\">{{\n formatService.format(item.label, 'string', vm.rootContext()) | translate | async\n }}</a>\n </axp-layout-content>\n <axp-layout-suffix>\n <ax-icon icon=\" far fa-chevron-right\" class=\"ax-text-gray-400\"></ax-icon>\n </axp-layout-suffix>\n </axp-layout-list-item>\n }\n </axp-layout-list>\n } @else {\n @if (vm.isBusy()) {\n <div class=\"ax-flex ax-h-full ax-w-full ax-items-center ax-justify-center\">\n <ax-loading></ax-loading>\n </div>\n } @else {\n <ax-form class=\"ax-h-full ax-w-full\" #form>\n <axp-widgets-container\n #widgetsContainerRef\n [context]=\"vm.context()\"\n (onContextChanged)=\"handleOnContextChanged($event)\"\n >\n <div class=\"ax-flex ax-flex-col ax-w-full ax-gap-4 ax-h-full\">\n @for (item of vm.currentPageContent(); track pageContentTrackKey($any(item), $index)) {\n @if ('type' in item && item.type === 'component') {\n <!-- Component-based content -->\n <div\n axp-page-component-renderer\n [componentKey]=\"$any(item).componentKey\"\n [rootContext]=\"vm.rootContext()\"\n [pageConfig]=\"$any(item).pageConfig\"\n [options]=\"$any(item).options\"\n (componentReady)=\"recompute()\"\n ></div>\n } @else {\n <!-- Widget-based content -->\n <ng-container axp-widget-renderer [node]=\"$any(item)\" [mode]=\"$any(item).mode ?? 'view'\"></ng-container>\n }\n }\n @if (vm.currentPage()?.tabs?.length) {\n <div class=\"axp-horizontal-tabs\">\n <ax-tabs [location]=\"'bottom'\" [fitParent]=\"false\" look=\"classic\">\n @for (tab of vm.currentPageTabs(); track $index) {\n <ax-tab-item\n (onClick)=\"vm.setCurrentTab(tab)\"\n [text]=\"(tab.title | translate | async)!\"\n [active]=\"vm.currentTab()?.id === tab.id\"\n >\n <ax-prefix>\n <ax-icon icon=\"far ${{ tab.icon }}\"></ax-icon>\n </ax-prefix>\n </ax-tab-item>\n }\n </ax-tabs>\n <div class=\"content\">\n @if (vm.currentTab()) {\n @for (content of vm.activeTabContent(); track content.name) {\n <ng-container axp-widget-renderer [node]=\"content\" [mode]=\"content.mode ?? 'view'\">\n </ng-container>\n }\n }\n </div>\n </div>\n }\n </div>\n </axp-widgets-container>\n </ax-form>\n }\n }\n </axp-page-content>\n\n <!-- Footer Section -->\n @if (hasFooter()) {\n <axp-page-footer class=\"--animated\">\n <axp-layout-suffix>\n <!-- secondary footer actions -->\n @if (hasVisibleFooterSecondaryActions()) {\n <ax-button\n [class.ax-sm]=\"deviceService.isSmall()\"\n [iconOnly]=\"deviceService.isSmall()\"\n [text]=\"'@general:terms.interface.actions' | translate | async\"\n [look]=\"deviceService.isSmall() ? 'blank' : 'solid'\"\n [color]=\"'default'\"\n >\n <ax-prefix>\n <i class=\"fa-solid fa-ellipsis-vertical\"></i>\n </ax-prefix>\n <ax-dropdown-panel #panel>\n <ax-button-item-list>\n @for (action of footerSecondaryActions(); track $index) {\n @if (isFooterActionVisible(action)) {\n <ax-button-item\n [text]=\"(t(action.title) | async)!\"\n [color]=\"action.color\"\n [disabled]=\"isFooterActionDisabled(action)\"\n (onClick)=\"execute(action.command!)\"\n >\n <ax-prefix>\n <ax-icon icon=\"fa-light {{ action.icon }}\"></ax-icon>\n </ax-prefix>\n </ax-button-item>\n @if (action.break) {\n <ax-divider></ax-divider>\n }\n }\n }\n </ax-button-item-list>\n </ax-dropdown-panel>\n </ax-button>\n }\n\n @if (hasVisibleFooterPrimaryActions()) {\n @if (showSaveDiscardFooter()) {\n @if (rejectFooterCommand(); as reject) {\n <ax-button\n [class.ax-sm]=\"deviceService.isSmall()\"\n [disabled]=\"vm.isSaving()\"\n [text]=\"(t(reject.title) | async)!\"\n [look]=\"'solid'\"\n [color]=\"reject.color\"\n (onClick)=\"execute(reject.command)\"\n >\n <ax-prefix>\n <i class=\"{{ reject.icon }}\"></i>\n </ax-prefix>\n </ax-button>\n }\n @if (acceptFooterCommand(); as accept) {\n <ax-button\n [class.ax-sm]=\"deviceService.isSmall()\"\n [disabled]=\"vm.isSaving()\"\n [text]=\"(t(accept.title) | async)!\"\n [look]=\"'solid'\"\n [color]=\"accept.color\"\n (onClick)=\"execute(accept.command)\"\n >\n <ax-prefix>\n <i class=\"{{ accept.icon }}\"></i>\n </ax-prefix>\n </ax-button>\n }\n }\n @for (action of footerPrimaryActions(); track $index) {\n @if (isFooterActionVisible(action)) {\n <ax-button\n [class.ax-sm]=\"deviceService.isSmall()\"\n [disabled]=\"isFooterActionDisabled(action)\"\n [text]=\"(t(action.title) | async)!\"\n [look]=\"'solid'\"\n [color]=\"action.color\"\n (onClick)=\"execute(action.command!)\"\n >\n <ax-prefix>\n <i class=\"{{ action.icon }}\"></i>\n </ax-prefix>\n @if (action?.items) {\n <ax-dropdown-panel #panel>\n <ax-button-item-list>\n @for (sub of action?.items; track $index) {\n @if (isFooterActionVisible(sub)) {\n <ax-button-item\n [text]=\"(t(sub.title) | async)!\"\n [color]=\"sub.color\"\n [disabled]=\"isFooterActionDisabled(sub)\"\n (onClick)=\"execute(sub.command!)\"\n >\n <ax-prefix>\n <ax-icon icon=\"fa-light {{ sub.icon }}\"></ax-icon>\n </ax-prefix>\n </ax-button-item>\n @if (sub.break) {\n <ax-divider></ax-divider>\n }\n }\n }\n </ax-button-item-list>\n </ax-dropdown-panel>\n }\n </ax-button>\n }\n }\n }\n </axp-layout-suffix>\n </axp-page-footer>\n }\n</axp-page-layout>\n", styles: ["axp-layout-details-view .axp-vertical-tabs{--ax-comp-tabs-default-border-radius: 0}axp-layout-details-view .axp-vertical-tabs ax-tab-item{margin-top:0!important;margin-bottom:0!important;padding-top:.75rem!important;padding-bottom:.75rem!important;font-weight:600!important}axp-layout-details-view .axp-vertical-tabs ax-tab-item>div{display:flex!important;align-items:center!important}axp-layout-details-view .axp-vertical-tabs ax-tab-item>div .ax-tab-item-text{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}axp-layout-details-view .axp-horizontal-tabs{display:flex;width:100%;flex-direction:column}axp-layout-details-view .axp-horizontal-tabs .content{margin-top:1rem;margin-bottom:1rem;display:block;width:100%}axp-layout-details-view axp-layout-section axp-layout-content{display:flex;width:100%;flex-direction:column}axp-layout-details-view axp-layout-section axp-layout-content>:not([hidden])~:not([hidden]){--tw-divide-y-reverse: 0;border-top-width:calc(1px * calc(1 - var(--tw-divide-y-reverse)));border-bottom-width:calc(1px * var(--tw-divide-y-reverse));border-style:dashed;--tw-divide-opacity: 1;border-color:rgba(var(--ax-sys-color-border-lightest-surface),var(--tw-divide-opacity, 1))}axp-layout-details-view axp-layout-section axp-layout-content .--item-container{align-items:center;gap:1rem;padding:1rem}@media(min-width:1024px){axp-layout-details-view axp-layout-section axp-layout-content .--item-container{padding-left:1.5rem;padding-right:1.5rem}}@media(min-width:1280px){axp-layout-details-view axp-layout-section axp-layout-content .--item-container{padding-left:2rem;padding-right:2rem}}@media(min-width:1536px){axp-layout-details-view axp-layout-section axp-layout-content .--item-container{padding-left:2.5rem;padding-right:2.5rem}}axp-layout-details-view axp-layout-section axp-layout-content .--item-container{display:grid;grid-template-columns:repeat(12,minmax(0,1fr))}axp-layout-details-view axp-layout-section axp-layout-content .--item-container axp-layout-description{margin-top:.5rem!important;font-size:.75rem!important;line-height:1rem!important}axp-layout-details-view axp-page-content ax-form form{height:100%}axp-layout-details-view axp-layout-start-side{border-inline-end-width:1px}\n"], dependencies: [{ kind: "ngmodule", type:
|
|
2147
2347
|
// Angular
|
|
2148
2348
|
CommonModule }, { kind: "ngmodule", type:
|
|
2149
2349
|
// ACoreX
|
|
2150
|
-
AXMenuModule }, { kind: "ngmodule", type: AXButtonModule }, { kind: "component", type: i6.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: i6.AXButtonItemComponent, selector: "ax-button-item", inputs: ["color", "disabled", "text", "selected", "divided", "data", "name"], outputs: ["onClick", "onFocus", "onBlur", "disabledChange"] }, { kind: "component", type: i6.AXButtonItemListComponent, selector: "ax-button-item-list", inputs: ["items", "closeParentOnClick", "lockOnLoading"], outputs: ["onItemClick"] }, { kind: "ngmodule", type: AXButtonGroupModule }, { kind: "ngmodule", type: AXDropdownButtonModule }, { kind: "ngmodule", type: AXDropdownModule }, { kind: "component", type: i5.AXDropdownPanelComponent, selector: "ax-dropdown-panel", inputs: ["isOpen", "fitParent", "dropdownWidth", "position", "placement", "_target", "adaptivityEnabled"], outputs: ["onOpened", "onClosed"] }, { kind: "ngmodule", type: AXTranslationModule }, { kind: "directive", type: i3$1.AXTranslatorDirective, selector: "[translate]" }, { kind: "ngmodule", type: AXDecoratorModule }, { kind: "component", type: i3.AXDecoratorIconComponent, selector: "ax-icon", inputs: ["icon"] }, { kind: "component", type: i3.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: AXBreadcrumbsModule }, { kind: "ngmodule", type: AXBadgeModule }, { kind: "ngmodule", type: AXSearchBoxModule }, { kind: "ngmodule", type: AXFormatModule }, { kind: "ngmodule", type: AXFileModule }, { kind: "ngmodule", type: AXDateTimeModule }, { kind: "ngmodule", type: AXTabsModule }, { kind: "component", type:
|
|
2350
|
+
AXMenuModule }, { kind: "ngmodule", type: AXButtonModule }, { kind: "component", type: i6.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: i6.AXButtonItemComponent, selector: "ax-button-item", inputs: ["color", "disabled", "text", "selected", "divided", "data", "name"], outputs: ["onClick", "onFocus", "onBlur", "disabledChange"] }, { kind: "component", type: i6.AXButtonItemListComponent, selector: "ax-button-item-list", inputs: ["items", "closeParentOnClick", "lockOnLoading"], outputs: ["onItemClick"] }, { kind: "ngmodule", type: AXButtonGroupModule }, { kind: "ngmodule", type: AXDropdownButtonModule }, { kind: "ngmodule", type: AXDropdownModule }, { kind: "component", type: i5.AXDropdownPanelComponent, selector: "ax-dropdown-panel", inputs: ["isOpen", "fitParent", "dropdownWidth", "position", "placement", "_target", "adaptivityEnabled"], outputs: ["onOpened", "onClosed"] }, { kind: "ngmodule", type: AXTranslationModule }, { kind: "directive", type: i3$1.AXTranslatorDirective, selector: "[translate]" }, { kind: "ngmodule", type: AXDecoratorModule }, { kind: "component", type: i3.AXDecoratorIconComponent, selector: "ax-icon", inputs: ["icon"] }, { kind: "component", type: i3.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: "component", type: i5$1.AXLoadingComponent, selector: "ax-loading", inputs: ["visible", "type", "context"], outputs: ["visibleChange"] }, { kind: "ngmodule", type: AXBreadcrumbsModule }, { kind: "ngmodule", type: AXBadgeModule }, { kind: "ngmodule", type: AXSearchBoxModule }, { kind: "ngmodule", type: AXFormatModule }, { kind: "ngmodule", type: AXFileModule }, { kind: "ngmodule", type: AXDateTimeModule }, { kind: "ngmodule", type: AXTabsModule }, { kind: "component", type: i6$1.AXTabsComponent, selector: "ax-tabs", inputs: ["look", "location", "fitParent", "minWidth", "content"], outputs: ["onActiveTabChanged"] }, { kind: "component", type: i6$1.AXTabItemComponent, selector: "ax-tab-item", inputs: ["disabled", "text", "key", "headerTemplate", "active"], outputs: ["disabledChange", "onClick", "onBlur", "onFocus", "activeChange"] }, { kind: "component", type:
|
|
2151
2351
|
//
|
|
2152
|
-
AXPThemeLayoutListComponent, selector: "axp-layout-list" }, { kind: "component", type: AXPThemeLayoutListItemComponent, selector: "axp-layout-list-item" }, { kind: "component", type: AXPPageLayoutComponent, selector: "axp-page-layout" }, { kind: "component", type: AXPThemeLayoutBlockComponent, selector: " axp-page-content, axp-page-footer-container, axp-page-footer, axp-page-header, axp-page-header-container, axp-page-toolbar, axp-layout-content, axp-layout-page-content, axp-layout-sections, axp-layout-body, axp-layout-page-body, axp-layout-prefix, axp-layout-suffix, axp-layout-title-bar, axp-layout-title, axp-layout-title-actions, axp-layout-nav-button, axp-layout-description, axp-layout-breadcrumbs, axp-layout-list-action, " }, { kind: "ngmodule", type: AXPWidgetCoreModule }, { kind: "component", type:
|
|
2352
|
+
AXPThemeLayoutListComponent, selector: "axp-layout-list" }, { kind: "component", type: AXPThemeLayoutListItemComponent, selector: "axp-layout-list-item" }, { kind: "component", type: AXPPageLayoutComponent, selector: "axp-page-layout" }, { kind: "component", type: AXPThemeLayoutBlockComponent, selector: " axp-page-content, axp-page-footer-container, axp-page-footer, axp-page-header, axp-page-header-container, axp-page-toolbar, axp-layout-content, axp-layout-page-content, axp-layout-sections, axp-layout-body, axp-layout-page-body, axp-layout-prefix, axp-layout-suffix, axp-layout-title-bar, axp-layout-title, axp-layout-title-actions, axp-layout-nav-button, axp-layout-description, axp-layout-breadcrumbs, axp-layout-list-action, " }, { kind: "ngmodule", type: AXPWidgetCoreModule }, { kind: "component", type: i7.AXPWidgetContainerComponent, selector: "axp-widgets-container", inputs: ["context", "functions"], outputs: ["onContextChanged"] }, { kind: "directive", type: i7.AXPWidgetRendererDirective, selector: "[axp-widget-renderer]", inputs: ["parentNode", "index", "mode", "node"], outputs: ["onOptionsChanged", "onValueChanged", "onLoad"], exportAs: ["widgetRenderer"] }, { kind: "component", type: AXPThemeLayoutStartSideComponent, selector: "axp-layout-page-start-side, axp-layout-start-side" }, { kind: "component", type: AXPThemeLayoutHeaderComponent, selector: "axp-layout-header" }, { kind: "ngmodule", type: AXFormModule }, { kind: "component", type: i8$1.AXFormComponent, selector: "ax-form", inputs: ["disabled", "readonly", "labelMode", "look", "messageStyle", "updateOn", "inUserInteractionActive"], outputs: ["onValidate", "updateOnChange"] }, { kind: "directive", type: AXPPageComponentRendererDirective, selector: "[axp-page-component-renderer]", inputs: ["componentKey", "rootContext", "pageConfig", "options"], outputs: ["componentReady"] }, { kind: "pipe", type: i2.AsyncPipe, name: "async" }, { kind: "pipe", type: i3$1.AXTranslatorPipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
|
|
2153
2353
|
}
|
|
2154
2354
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPLayoutDetailsViewComponent, decorators: [{
|
|
2155
2355
|
type: Component,
|
|
@@ -2189,7 +2389,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
2189
2389
|
useExisting: AXPLayoutDetailsViewComponent,
|
|
2190
2390
|
},
|
|
2191
2391
|
AXPLayoutDetailsViewViewModel,
|
|
2192
|
-
], template: "<axp-page-layout *translate=\"let t\">\n @if (vm.adapter()?.pages?.length! > 1 && vm.isLarge()) {\n <axp-layout-start-side>\n <axp-layout-header>\n <axp-layout-title>{{\n (formatService.format(vm.showPages() ? (vm.adapter()?.title ?? '') : (vm.adapter()?.label ?? ''), 'string', vm.rootContext())\n | translate\n | async)\n }}</axp-layout-title>\n <axp-layout-description>{{\n (formatService.format(vm.adapter()?.description ?? '', 'string', vm.rootContext()) | translate | async)\n }}</axp-layout-description>\n </axp-layout-header>\n <axp-layout-content>\n <ax-tabs\n #sidebarTabs\n class=\"axp-vertical-tabs\"\n [look]=\"'with-line-color'\"\n [location]=\"'end'\"\n [fitParent]=\"true\"\n (onActiveTabChanged)=\"handleSidebarTabChanged($event)\"\n >\n @for (item of vm.adapter()?.pages; track item.id) {\n <ax-tab-item\n [key]=\"item.id\"\n [text]=\"(formatService.format(item.label, 'string', vm.rootContext()) | translate | async)!\"\n [active]=\"vm.currentPage()?.id === item.id\"\n >\n <ax-prefix>\n <ax-icon icon=\"far ${{ item.icon }}\"></ax-icon>\n </ax-prefix>\n </ax-tab-item>\n }\n </ax-tabs>\n </axp-layout-content>\n </axp-layout-start-side>\n }\n <!-- Content Section -->\n <axp-page-content class=\"ax-flex ax-flex-row ax-gap-4\">\n @if (vm.showPages()) {\n <axp-layout-list class=\"ax-w-full\">\n @for (item of vm.adapter()?.pages; track $index) {\n <axp-layout-list-item (click)=\"handleSelectPage(item)\">\n <axp-layout-prefix>\n <ax-icon icon=\"far ${{ item.icon }}\" class=\"ax-text-gray-500\"></ax-icon>\n </axp-layout-prefix>\n <axp-layout-content>\n <a class=\"ax-font-semibold\">{{\n formatService.format(item.label, 'string', vm.rootContext()) | translate | async\n }}</a>\n </axp-layout-content>\n <axp-layout-suffix>\n <ax-icon icon=\" far fa-chevron-right\" class=\"ax-text-gray-400\"></ax-icon>\n </axp-layout-suffix>\n </axp-layout-list-item>\n }\n </axp-layout-list>\n } @else {\n <ax-form class=\"ax-h-full ax-w-full\" #form>\n <axp-widgets-container\n #widgetsContainerRef\n [context]=\"vm.context()\"\n (onContextChanged)=\"handleOnContextChanged($event)\"\n >\n <div class=\"ax-flex ax-flex-col ax-w-full ax-gap-4 ax-h-full\">\n @for (item of vm.currentPageContent(); track $index) {\n @if ('type' in item && item.type === 'component') {\n <!-- Component-based content -->\n <div\n axp-page-component-renderer\n [componentKey]=\"$any(item).componentKey\"\n [rootContext]=\"vm.rootContext()\"\n [pageConfig]=\"$any(item).pageConfig\"\n [options]=\"$any(item).options\"\n (componentReady)=\"recompute()\"\n ></div>\n } @else {\n <!-- Widget-based content -->\n <ng-container axp-widget-renderer [node]=\"$any(item)\" [mode]=\"$any(item).mode ?? 'view'\"></ng-container>\n }\n }\n @if (vm.currentPage()?.tabs?.length) {\n <div class=\"axp-horizontal-tabs\">\n <ax-tabs [location]=\"'bottom'\" [fitParent]=\"false\" look=\"classic\">\n @for (tab of vm.currentPageTabs(); track $index) {\n <ax-tab-item\n (onClick)=\"vm.setCurrentTab(tab)\"\n [text]=\"(tab.title | translate | async)!\"\n [active]=\"vm.currentTab()?.id === tab.id\"\n >\n <ax-prefix>\n <ax-icon icon=\"far ${{ tab.icon }}\"></ax-icon>\n </ax-prefix>\n </ax-tab-item>\n }\n </ax-tabs>\n <div class=\"content\">\n @if (vm.currentTab()) {\n @for (content of vm.activeTabContent(); track content.name) {\n <ng-container axp-widget-renderer [node]=\"content\" [mode]=\"content.mode ?? 'view'\">\n </ng-container>\n }\n }\n </div>\n </div>\n }\n </div>\n </axp-widgets-container>\n </ax-form>\n }\n </axp-page-content>\n\n <!-- Footer Section -->\n @if (hasFooter()) {\n <axp-page-footer class=\"--animated\">\n <axp-layout-suffix>\n <!-- secondary footer actions -->\n @if (hasVisibleFooterSecondaryActions()) {\n <ax-button\n [class.ax-sm]=\"deviceService.isSmall()\"\n [iconOnly]=\"deviceService.isSmall()\"\n [text]=\"'@general:terms.interface.actions' | translate | async\"\n [look]=\"deviceService.isSmall() ? 'blank' : 'solid'\"\n [color]=\"'default'\"\n >\n <ax-prefix>\n <i class=\"fa-solid fa-ellipsis-vertical\"></i>\n </ax-prefix>\n <ax-dropdown-panel #panel>\n <ax-button-item-list>\n @for (action of footerSecondaryActions(); track $index) {\n @if (isFooterActionVisible(action)) {\n <ax-button-item\n [text]=\"(t(action.title) | async)!\"\n [color]=\"action.color\"\n [disabled]=\"isFooterActionDisabled(action)\"\n (onClick)=\"execute(action.command!)\"\n >\n <ax-prefix>\n <ax-icon icon=\"fa-light {{ action.icon }}\"></ax-icon>\n </ax-prefix>\n </ax-button-item>\n @if (action.break) {\n <ax-divider></ax-divider>\n }\n }\n }\n </ax-button-item-list>\n </ax-dropdown-panel>\n </ax-button>\n }\n\n @if (hasVisibleFooterPrimaryActions()) {\n @if (showSaveDiscardFooter()) {\n @if (rejectFooterCommand(); as reject) {\n <ax-button\n [class.ax-sm]=\"deviceService.isSmall()\"\n [disabled]=\"vm.isSaving()\"\n [text]=\"(t(reject.title) | async)!\"\n [look]=\"'solid'\"\n [color]=\"reject.color\"\n (onClick)=\"execute(reject.command)\"\n >\n <ax-prefix>\n <i class=\"{{ reject.icon }}\"></i>\n </ax-prefix>\n </ax-button>\n }\n @if (acceptFooterCommand(); as accept) {\n <ax-button\n [class.ax-sm]=\"deviceService.isSmall()\"\n [disabled]=\"vm.isSaving()\"\n [text]=\"(t(accept.title) | async)!\"\n [look]=\"'solid'\"\n [color]=\"accept.color\"\n (onClick)=\"execute(accept.command)\"\n >\n <ax-prefix>\n <i class=\"{{ accept.icon }}\"></i>\n </ax-prefix>\n </ax-button>\n }\n }\n @for (action of footerPrimaryActions(); track $index) {\n @if (isFooterActionVisible(action)) {\n <ax-button\n [class.ax-sm]=\"deviceService.isSmall()\"\n [disabled]=\"isFooterActionDisabled(action)\"\n [text]=\"(t(action.title) | async)!\"\n [look]=\"'solid'\"\n [color]=\"action.color\"\n (onClick)=\"execute(action.command!)\"\n >\n <ax-prefix>\n <i class=\"{{ action.icon }}\"></i>\n </ax-prefix>\n @if (action?.items) {\n <ax-dropdown-panel #panel>\n <ax-button-item-list>\n @for (sub of action?.items; track $index) {\n @if (isFooterActionVisible(sub)) {\n <ax-button-item\n [text]=\"(t(sub.title) | async)!\"\n [color]=\"sub.color\"\n [disabled]=\"isFooterActionDisabled(sub)\"\n (onClick)=\"execute(sub.command!)\"\n >\n <ax-prefix>\n <ax-icon icon=\"fa-light {{ sub.icon }}\"></ax-icon>\n </ax-prefix>\n </ax-button-item>\n @if (sub.break) {\n <ax-divider></ax-divider>\n }\n }\n }\n </ax-button-item-list>\n </ax-dropdown-panel>\n }\n </ax-button>\n }\n }\n }\n </axp-layout-suffix>\n </axp-page-footer>\n }\n</axp-page-layout>\n", styles: ["axp-layout-details-view .axp-vertical-tabs{--ax-comp-tabs-default-border-radius: 0}axp-layout-details-view .axp-vertical-tabs ax-tab-item{margin-top:0!important;margin-bottom:0!important;padding-top:.75rem!important;padding-bottom:.75rem!important;font-weight:600!important}axp-layout-details-view .axp-vertical-tabs ax-tab-item>div{display:flex!important;align-items:center!important}axp-layout-details-view .axp-vertical-tabs ax-tab-item>div .ax-tab-item-text{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}axp-layout-details-view .axp-horizontal-tabs{display:flex;width:100%;flex-direction:column}axp-layout-details-view .axp-horizontal-tabs .content{margin-top:1rem;margin-bottom:1rem;display:block;width:100%}axp-layout-details-view axp-layout-section axp-layout-content{display:flex;width:100%;flex-direction:column}axp-layout-details-view axp-layout-section axp-layout-content>:not([hidden])~:not([hidden]){--tw-divide-y-reverse: 0;border-top-width:calc(1px * calc(1 - var(--tw-divide-y-reverse)));border-bottom-width:calc(1px * var(--tw-divide-y-reverse));border-style:dashed;--tw-divide-opacity: 1;border-color:rgba(var(--ax-sys-color-border-lightest-surface),var(--tw-divide-opacity, 1))}axp-layout-details-view axp-layout-section axp-layout-content .--item-container{align-items:center;gap:1rem;padding:1rem}@media(min-width:1024px){axp-layout-details-view axp-layout-section axp-layout-content .--item-container{padding-left:1.5rem;padding-right:1.5rem}}@media(min-width:1280px){axp-layout-details-view axp-layout-section axp-layout-content .--item-container{padding-left:2rem;padding-right:2rem}}@media(min-width:1536px){axp-layout-details-view axp-layout-section axp-layout-content .--item-container{padding-left:2.5rem;padding-right:2.5rem}}axp-layout-details-view axp-layout-section axp-layout-content .--item-container{display:grid;grid-template-columns:repeat(12,minmax(0,1fr))}axp-layout-details-view axp-layout-section axp-layout-content .--item-container axp-layout-description{margin-top:.5rem!important;font-size:.75rem!important;line-height:1rem!important}axp-layout-details-view axp-page-content ax-form form{height:100%}axp-layout-details-view axp-layout-start-side{border-inline-end-width:1px}\n"] }]
|
|
2392
|
+
], template: "<axp-page-layout *translate=\"let t\">\n @if (vm.adapter()?.pages?.length! > 1 && vm.isLarge()) {\n <axp-layout-start-side>\n <axp-layout-header>\n <axp-layout-title>{{\n formatService.format(\n vm.showPages() ? (vm.adapter()?.title ?? '') : (vm.adapter()?.label ?? ''),\n 'string',\n vm.rootContext()\n )\n | translate\n | async\n }}</axp-layout-title>\n <axp-layout-description>{{\n formatService.format(vm.adapter()?.description ?? '', 'string', vm.rootContext()) | translate | async\n }}</axp-layout-description>\n </axp-layout-header>\n <axp-layout-content>\n <ax-tabs\n #sidebarTabs\n class=\"axp-vertical-tabs\"\n [look]=\"'with-line-color'\"\n [location]=\"'end'\"\n [fitParent]=\"true\"\n (onActiveTabChanged)=\"handleSidebarTabChanged($event)\"\n >\n @for (item of vm.adapter()?.pages; track item.id) {\n <ax-tab-item\n [key]=\"item.id\"\n [text]=\"(formatService.format(item.label, 'string', vm.rootContext()) | translate | async)!\"\n [active]=\"vm.currentPage()?.id === item.id\"\n >\n <ax-prefix>\n <ax-icon icon=\"far ${{ item.icon }}\"></ax-icon>\n </ax-prefix>\n </ax-tab-item>\n }\n </ax-tabs>\n </axp-layout-content>\n </axp-layout-start-side>\n }\n <!-- Content Section -->\n <axp-page-content class=\"ax-flex ax-flex-row ax-gap-4\">\n @if (vm.showPages()) {\n <axp-layout-list class=\"ax-w-full\">\n @for (item of vm.adapter()?.pages; track $index) {\n <axp-layout-list-item (click)=\"handleSelectPage(item)\">\n <axp-layout-prefix>\n <ax-icon icon=\"far ${{ item.icon }}\" class=\"ax-text-gray-500\"></ax-icon>\n </axp-layout-prefix>\n <axp-layout-content>\n <a class=\"ax-font-semibold\">{{\n formatService.format(item.label, 'string', vm.rootContext()) | translate | async\n }}</a>\n </axp-layout-content>\n <axp-layout-suffix>\n <ax-icon icon=\" far fa-chevron-right\" class=\"ax-text-gray-400\"></ax-icon>\n </axp-layout-suffix>\n </axp-layout-list-item>\n }\n </axp-layout-list>\n } @else {\n @if (vm.isBusy()) {\n <div class=\"ax-flex ax-h-full ax-w-full ax-items-center ax-justify-center\">\n <ax-loading></ax-loading>\n </div>\n } @else {\n <ax-form class=\"ax-h-full ax-w-full\" #form>\n <axp-widgets-container\n #widgetsContainerRef\n [context]=\"vm.context()\"\n (onContextChanged)=\"handleOnContextChanged($event)\"\n >\n <div class=\"ax-flex ax-flex-col ax-w-full ax-gap-4 ax-h-full\">\n @for (item of vm.currentPageContent(); track pageContentTrackKey($any(item), $index)) {\n @if ('type' in item && item.type === 'component') {\n <!-- Component-based content -->\n <div\n axp-page-component-renderer\n [componentKey]=\"$any(item).componentKey\"\n [rootContext]=\"vm.rootContext()\"\n [pageConfig]=\"$any(item).pageConfig\"\n [options]=\"$any(item).options\"\n (componentReady)=\"recompute()\"\n ></div>\n } @else {\n <!-- Widget-based content -->\n <ng-container axp-widget-renderer [node]=\"$any(item)\" [mode]=\"$any(item).mode ?? 'view'\"></ng-container>\n }\n }\n @if (vm.currentPage()?.tabs?.length) {\n <div class=\"axp-horizontal-tabs\">\n <ax-tabs [location]=\"'bottom'\" [fitParent]=\"false\" look=\"classic\">\n @for (tab of vm.currentPageTabs(); track $index) {\n <ax-tab-item\n (onClick)=\"vm.setCurrentTab(tab)\"\n [text]=\"(tab.title | translate | async)!\"\n [active]=\"vm.currentTab()?.id === tab.id\"\n >\n <ax-prefix>\n <ax-icon icon=\"far ${{ tab.icon }}\"></ax-icon>\n </ax-prefix>\n </ax-tab-item>\n }\n </ax-tabs>\n <div class=\"content\">\n @if (vm.currentTab()) {\n @for (content of vm.activeTabContent(); track content.name) {\n <ng-container axp-widget-renderer [node]=\"content\" [mode]=\"content.mode ?? 'view'\">\n </ng-container>\n }\n }\n </div>\n </div>\n }\n </div>\n </axp-widgets-container>\n </ax-form>\n }\n }\n </axp-page-content>\n\n <!-- Footer Section -->\n @if (hasFooter()) {\n <axp-page-footer class=\"--animated\">\n <axp-layout-suffix>\n <!-- secondary footer actions -->\n @if (hasVisibleFooterSecondaryActions()) {\n <ax-button\n [class.ax-sm]=\"deviceService.isSmall()\"\n [iconOnly]=\"deviceService.isSmall()\"\n [text]=\"'@general:terms.interface.actions' | translate | async\"\n [look]=\"deviceService.isSmall() ? 'blank' : 'solid'\"\n [color]=\"'default'\"\n >\n <ax-prefix>\n <i class=\"fa-solid fa-ellipsis-vertical\"></i>\n </ax-prefix>\n <ax-dropdown-panel #panel>\n <ax-button-item-list>\n @for (action of footerSecondaryActions(); track $index) {\n @if (isFooterActionVisible(action)) {\n <ax-button-item\n [text]=\"(t(action.title) | async)!\"\n [color]=\"action.color\"\n [disabled]=\"isFooterActionDisabled(action)\"\n (onClick)=\"execute(action.command!)\"\n >\n <ax-prefix>\n <ax-icon icon=\"fa-light {{ action.icon }}\"></ax-icon>\n </ax-prefix>\n </ax-button-item>\n @if (action.break) {\n <ax-divider></ax-divider>\n }\n }\n }\n </ax-button-item-list>\n </ax-dropdown-panel>\n </ax-button>\n }\n\n @if (hasVisibleFooterPrimaryActions()) {\n @if (showSaveDiscardFooter()) {\n @if (rejectFooterCommand(); as reject) {\n <ax-button\n [class.ax-sm]=\"deviceService.isSmall()\"\n [disabled]=\"vm.isSaving()\"\n [text]=\"(t(reject.title) | async)!\"\n [look]=\"'solid'\"\n [color]=\"reject.color\"\n (onClick)=\"execute(reject.command)\"\n >\n <ax-prefix>\n <i class=\"{{ reject.icon }}\"></i>\n </ax-prefix>\n </ax-button>\n }\n @if (acceptFooterCommand(); as accept) {\n <ax-button\n [class.ax-sm]=\"deviceService.isSmall()\"\n [disabled]=\"vm.isSaving()\"\n [text]=\"(t(accept.title) | async)!\"\n [look]=\"'solid'\"\n [color]=\"accept.color\"\n (onClick)=\"execute(accept.command)\"\n >\n <ax-prefix>\n <i class=\"{{ accept.icon }}\"></i>\n </ax-prefix>\n </ax-button>\n }\n }\n @for (action of footerPrimaryActions(); track $index) {\n @if (isFooterActionVisible(action)) {\n <ax-button\n [class.ax-sm]=\"deviceService.isSmall()\"\n [disabled]=\"isFooterActionDisabled(action)\"\n [text]=\"(t(action.title) | async)!\"\n [look]=\"'solid'\"\n [color]=\"action.color\"\n (onClick)=\"execute(action.command!)\"\n >\n <ax-prefix>\n <i class=\"{{ action.icon }}\"></i>\n </ax-prefix>\n @if (action?.items) {\n <ax-dropdown-panel #panel>\n <ax-button-item-list>\n @for (sub of action?.items; track $index) {\n @if (isFooterActionVisible(sub)) {\n <ax-button-item\n [text]=\"(t(sub.title) | async)!\"\n [color]=\"sub.color\"\n [disabled]=\"isFooterActionDisabled(sub)\"\n (onClick)=\"execute(sub.command!)\"\n >\n <ax-prefix>\n <ax-icon icon=\"fa-light {{ sub.icon }}\"></ax-icon>\n </ax-prefix>\n </ax-button-item>\n @if (sub.break) {\n <ax-divider></ax-divider>\n }\n }\n }\n </ax-button-item-list>\n </ax-dropdown-panel>\n }\n </ax-button>\n }\n }\n }\n </axp-layout-suffix>\n </axp-page-footer>\n }\n</axp-page-layout>\n", styles: ["axp-layout-details-view .axp-vertical-tabs{--ax-comp-tabs-default-border-radius: 0}axp-layout-details-view .axp-vertical-tabs ax-tab-item{margin-top:0!important;margin-bottom:0!important;padding-top:.75rem!important;padding-bottom:.75rem!important;font-weight:600!important}axp-layout-details-view .axp-vertical-tabs ax-tab-item>div{display:flex!important;align-items:center!important}axp-layout-details-view .axp-vertical-tabs ax-tab-item>div .ax-tab-item-text{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}axp-layout-details-view .axp-horizontal-tabs{display:flex;width:100%;flex-direction:column}axp-layout-details-view .axp-horizontal-tabs .content{margin-top:1rem;margin-bottom:1rem;display:block;width:100%}axp-layout-details-view axp-layout-section axp-layout-content{display:flex;width:100%;flex-direction:column}axp-layout-details-view axp-layout-section axp-layout-content>:not([hidden])~:not([hidden]){--tw-divide-y-reverse: 0;border-top-width:calc(1px * calc(1 - var(--tw-divide-y-reverse)));border-bottom-width:calc(1px * var(--tw-divide-y-reverse));border-style:dashed;--tw-divide-opacity: 1;border-color:rgba(var(--ax-sys-color-border-lightest-surface),var(--tw-divide-opacity, 1))}axp-layout-details-view axp-layout-section axp-layout-content .--item-container{align-items:center;gap:1rem;padding:1rem}@media(min-width:1024px){axp-layout-details-view axp-layout-section axp-layout-content .--item-container{padding-left:1.5rem;padding-right:1.5rem}}@media(min-width:1280px){axp-layout-details-view axp-layout-section axp-layout-content .--item-container{padding-left:2rem;padding-right:2rem}}@media(min-width:1536px){axp-layout-details-view axp-layout-section axp-layout-content .--item-container{padding-left:2.5rem;padding-right:2.5rem}}axp-layout-details-view axp-layout-section axp-layout-content .--item-container{display:grid;grid-template-columns:repeat(12,minmax(0,1fr))}axp-layout-details-view axp-layout-section axp-layout-content .--item-container axp-layout-description{margin-top:.5rem!important;font-size:.75rem!important;line-height:1rem!important}axp-layout-details-view axp-page-content ax-form form{height:100%}axp-layout-details-view axp-layout-start-side{border-inline-end-width:1px}\n"] }]
|
|
2193
2393
|
}], propDecorators: { adapter: [{ type: i0.Input, args: [{ isSignal: true, alias: "adapter", required: true }] }], form: [{ type: i0.ViewChild, args: ['form', { isSignal: true }] }], widgetContainer: [{ type: i0.ViewChild, args: [i0.forwardRef(() => AXPWidgetContainerComponent), { isSignal: true }] }], sidebarTabs: [{ type: i0.ViewChild, args: ['sidebarTabs', { isSignal: true }] }] } });
|
|
2194
2394
|
|
|
2195
2395
|
//#region ---- Details View Can Deactivate Guard ----
|