@acorex/platform 21.0.0-next.75 → 21.0.0-next.76
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-common-common-settings.provider-Ytey9uhY.mjs → acorex-platform-common-common-settings.provider-ChoT4m4k.mjs} +42 -1
- package/fesm2022/acorex-platform-common-common-settings.provider-ChoT4m4k.mjs.map +1 -0
- package/fesm2022/acorex-platform-common.mjs +1471 -606
- package/fesm2022/acorex-platform-common.mjs.map +1 -1
- package/fesm2022/acorex-platform-contracts.mjs +1330 -1
- package/fesm2022/acorex-platform-contracts.mjs.map +1 -1
- package/fesm2022/acorex-platform-core.mjs +208 -5
- package/fesm2022/acorex-platform-core.mjs.map +1 -1
- package/fesm2022/acorex-platform-layout-components.mjs.map +1 -1
- package/fesm2022/acorex-platform-layout-entity.mjs +595 -439
- package/fesm2022/acorex-platform-layout-entity.mjs.map +1 -1
- package/fesm2022/acorex-platform-layout-views.mjs +372 -171
- package/fesm2022/acorex-platform-layout-views.mjs.map +1 -1
- package/fesm2022/acorex-platform-layout-widget-core.mjs +3 -103
- 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.mjs +11 -6
- package/fesm2022/acorex-platform-layout-widgets.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 +643 -52
- package/fesm2022/acorex-platform-themes-default.mjs.map +1 -1
- package/fesm2022/acorex-platform-workflow.mjs +5 -8
- package/fesm2022/acorex-platform-workflow.mjs.map +1 -1
- package/package.json +1 -1
- package/types/acorex-platform-common.d.ts +337 -1056
- package/types/acorex-platform-contracts.d.ts +1679 -4
- package/types/acorex-platform-core.d.ts +92 -181
- package/types/acorex-platform-layout-components.d.ts +2 -1
- package/types/acorex-platform-layout-entity.d.ts +68 -176
- package/types/acorex-platform-layout-views.d.ts +39 -5
- package/types/acorex-platform-layout-widget-core.d.ts +2 -3
- package/types/acorex-platform-layout-widgets.d.ts +11 -4
- package/types/acorex-platform-themes-default.d.ts +120 -57
- 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-themes-default-entity-master-single-view.component-Br9p5aXT.mjs.map +0 -1
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { InjectionToken, inject, Injectable, Injector, signal, DestroyRef, makeEnvironmentProviders, NgModule, computed, input, output, ViewEncapsulation, ChangeDetectionStrategy, Component, viewChild, ErrorHandler, EventEmitter, Input, Output, Directive, Inject, runInInjectionContext, model, linkedSignal, afterNextRender } from '@angular/core';
|
|
3
|
-
import {
|
|
2
|
+
import { InjectionToken, inject, Injectable, Injector, signal, DestroyRef, makeEnvironmentProviders, NgModule, computed, input, output, ViewEncapsulation, ChangeDetectionStrategy, Component, viewChild, ErrorHandler, EventEmitter, Input, Output, Directive, effect, Inject, runInInjectionContext, model, linkedSignal, afterNextRender } from '@angular/core';
|
|
3
|
+
import { AXPRegionalSetting } from '@acorex/platform/contracts';
|
|
4
|
+
export * from '@acorex/platform/contracts';
|
|
4
5
|
import { Router, NavigationEnd, NavigationStart, ROUTES, RedirectCommand, RouterModule } from '@angular/router';
|
|
5
6
|
import { AXPSessionService, AXPSessionStatus, AXPAuthGuard } from '@acorex/platform/auth';
|
|
6
7
|
import { Subject, distinctUntilChanged, merge as merge$1, first, switchMap, from, map } from 'rxjs';
|
|
7
|
-
import * as
|
|
8
|
-
import { unwrapValueProperty, coerceUnknownToDate, coerceUnknownToFiniteNumber, coerceUnknownToBoolean, coerceUnknownToTrimmedString, AXPPlatformScope, AXPBroadcastEventService, objectKeyValueTransforms, AXPDataGenerator, AXPKeyboardShortcutRegistry, chordToKbdItemKeys, hasForegroundOverlayLayer, AXPKeyboardShortcutPriority, normalizeKeyboardShortcuts, AXPSystemActionType, isLocaleStringMap, AXPExpressionEvaluatorService, AXPModuleManifestModule, AXPComponentSlotModule, AXPAppStartUpProvider, AXP_EXPRESSION_EVALUATOR_SCOPE_PROVIDER, AXPHookService, AXPModuleManifestRegistry } from '@acorex/platform/core';
|
|
8
|
+
import * as i3$1 from '@acorex/platform/core';
|
|
9
|
+
import { unwrapValueProperty, coerceUnknownToDate, coerceUnknownToFiniteNumber, coerceUnknownToBoolean, coerceUnknownToTrimmedString, AXPPlatformScope, AXPBroadcastEventService, objectKeyValueTransforms, AXPDataGenerator, AXPKeyboardShortcutRegistry, chordToKbdItemKeys, hasForegroundOverlayLayer, AXPKeyboardShortcutPriority, normalizeKeyboardShortcuts, AXPSystemActionType, normalizeExpressionTemplate, isLocaleStringMap, AXPExpressionEvaluatorService, AXPContextEvalFactory, AXPDeviceService, AXPModuleManifestModule, AXPComponentSlotModule, AXPAppStartUpProvider, AXP_EXPRESSION_EVALUATOR_SCOPE_PROVIDER, AXPHookService, AXPModuleManifestRegistry } from '@acorex/platform/core';
|
|
10
|
+
import { merge, sortBy, cloneDeep, get, isNumber, isNil, isObject, isString, isPlainObject, omit, defaultTo } from 'lodash-es';
|
|
9
11
|
import { AXCalendarService, AXDateTimeModule } from '@acorex/core/date-time';
|
|
10
12
|
import { AXDialogService } from '@acorex/components/dialog';
|
|
11
13
|
import * as i5 from '@acorex/core/translation';
|
|
12
|
-
import { AXTranslationService, AXTranslationModule, resolveMultiLanguageString } from '@acorex/core/translation';
|
|
14
|
+
import { AXTranslationService, AXTranslationModule, translateSync, resolveMultiLanguageString } from '@acorex/core/translation';
|
|
13
15
|
import * as i4$1 from '@angular/common';
|
|
14
16
|
import { Location, CommonModule, AsyncPipe, DOCUMENT } from '@angular/common';
|
|
15
17
|
import { takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop';
|
|
@@ -24,13 +26,15 @@ import * as i3 from '@acorex/components/progress-bar';
|
|
|
24
26
|
import { AXProgressBarModule } from '@acorex/components/progress-bar';
|
|
25
27
|
import { AXPopupService, AXPopupModule } from '@acorex/components/popup';
|
|
26
28
|
import { AXToastService, AXToastModule } from '@acorex/components/toast';
|
|
27
|
-
import * as
|
|
29
|
+
import * as i2$1 from '@acorex/core/format';
|
|
30
|
+
import { AXFormatService } from '@acorex/core/format';
|
|
31
|
+
import * as i4$2 from '@acorex/platform/workflow';
|
|
28
32
|
import { AXPWorkflowService, createWorkFlowEvent, AXPWorkflowAction, AXPWorkflowError, AXPWorkflowModule } from '@acorex/platform/workflow';
|
|
29
33
|
import { AXKBDComponent, AXKBDItemComponent } from '@acorex/components/kbd';
|
|
30
34
|
import { AXDataSource } from '@acorex/cdk/common';
|
|
31
35
|
import { AXPCommandExecutor, AXPCommandService } from '@acorex/platform/runtime';
|
|
32
36
|
import { signalStore, withState, withMethods, patchState, withHooks } from '@ngrx/signals';
|
|
33
|
-
import {
|
|
37
|
+
import { filter } from 'rxjs/operators';
|
|
34
38
|
import { AXFileService } from '@acorex/core/file';
|
|
35
39
|
import { AXCheckBoxModule } from '@acorex/components/check-box';
|
|
36
40
|
import * as i6 from '@acorex/components/color-box';
|
|
@@ -164,110 +168,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
164
168
|
}]
|
|
165
169
|
}] });
|
|
166
170
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
AXPRelationshipKind[AXPRelationshipKind["Aggregation"] = 2] = "Aggregation";
|
|
172
|
-
})(AXPRelationshipKind || (AXPRelationshipKind = {}));
|
|
173
|
-
var AXPRelationshipCardinality;
|
|
174
|
-
(function (AXPRelationshipCardinality) {
|
|
175
|
-
AXPRelationshipCardinality[AXPRelationshipCardinality["OneToOne"] = 0] = "OneToOne";
|
|
176
|
-
AXPRelationshipCardinality[AXPRelationshipCardinality["OneToMany"] = 1] = "OneToMany";
|
|
177
|
-
AXPRelationshipCardinality[AXPRelationshipCardinality["ManyToMany"] = 2] = "ManyToMany";
|
|
178
|
-
})(AXPRelationshipCardinality || (AXPRelationshipCardinality = {}));
|
|
179
|
-
var AXPEntityCommandScope;
|
|
180
|
-
(function (AXPEntityCommandScope) {
|
|
181
|
-
AXPEntityCommandScope["TypeLevel"] = "typeLevel";
|
|
182
|
-
AXPEntityCommandScope["Selected"] = "selected";
|
|
183
|
-
AXPEntityCommandScope["Individual"] = "individual";
|
|
184
|
-
AXPEntityCommandScope["Section"] = "section";
|
|
185
|
-
})(AXPEntityCommandScope || (AXPEntityCommandScope = {}));
|
|
186
|
-
var AXPEntityQueryType;
|
|
187
|
-
(function (AXPEntityQueryType) {
|
|
188
|
-
AXPEntityQueryType["Single"] = "single";
|
|
189
|
-
AXPEntityQueryType["List"] = "list";
|
|
190
|
-
})(AXPEntityQueryType || (AXPEntityQueryType = {}));
|
|
191
|
-
/** Whether the card field is shown as a header badge (not in the card body). */
|
|
192
|
-
function isCardFieldBadgeDisplay(display) {
|
|
193
|
-
if (display === 'badge') {
|
|
194
|
-
return true;
|
|
195
|
-
}
|
|
196
|
-
return typeof display === 'object' && display !== null && display.type === 'badge';
|
|
197
|
-
}
|
|
198
|
-
/** Resolves static badge color config from {@link AXPCardField.display} and legacy {@link AXPCardField.badgeColor}. */
|
|
199
|
-
function resolveCardFieldBadgeColor(field) {
|
|
200
|
-
if (typeof field.display === 'object' && field.display.type === 'badge') {
|
|
201
|
-
return field.display.options?.color ?? field.badgeColor;
|
|
202
|
-
}
|
|
203
|
-
if (field.display === 'badge') {
|
|
204
|
-
return field.badgeColor;
|
|
205
|
-
}
|
|
206
|
-
return undefined;
|
|
207
|
-
}
|
|
208
|
-
function getEntityInfo(entity) {
|
|
209
|
-
return {
|
|
210
|
-
source: `${entity.module}.${entity.name}`,
|
|
211
|
-
module: {
|
|
212
|
-
route: kebabCase(entity.module),
|
|
213
|
-
},
|
|
214
|
-
entity: {
|
|
215
|
-
route: kebabCase(entity.name),
|
|
216
|
-
},
|
|
217
|
-
};
|
|
218
|
-
}
|
|
219
|
-
function createQueryView(name, title, fixed, params = {
|
|
220
|
-
columns: [],
|
|
221
|
-
conditions: [],
|
|
222
|
-
sorts: [],
|
|
223
|
-
}) {
|
|
224
|
-
params = Object.assign({ columns: [], sorts: [], conditions: [] }, params);
|
|
225
|
-
return {
|
|
226
|
-
name,
|
|
227
|
-
title,
|
|
228
|
-
fixed,
|
|
229
|
-
columns: params.columns ?? [],
|
|
230
|
-
conditions: params.conditions ?? [],
|
|
231
|
-
sorts: params.sorts ?? [],
|
|
232
|
-
};
|
|
233
|
-
}
|
|
234
|
-
function createAllQueryView(params = {
|
|
235
|
-
columns: [],
|
|
236
|
-
sorts: [],
|
|
237
|
-
conditions: [],
|
|
238
|
-
}) {
|
|
239
|
-
params = Object.assign({ columns: [], sorts: [], conditions: [] }, params);
|
|
240
|
-
return createQueryView('all', '@general:terms.interface.selection.all-items', true, {
|
|
241
|
-
columns: params.columns ?? [],
|
|
242
|
-
conditions: params.conditions ?? [],
|
|
243
|
-
sorts: params.sorts ?? [],
|
|
244
|
-
});
|
|
245
|
-
}
|
|
246
|
-
/** Resolves master list layouts that are currently enabled. */
|
|
247
|
-
function resolveEnabledMasterListLayouts(layouts) {
|
|
248
|
-
if (!layouts) {
|
|
249
|
-
return ['table'];
|
|
250
|
-
}
|
|
251
|
-
const enabled = [];
|
|
252
|
-
if (layouts.table?.enabled !== false) {
|
|
253
|
-
enabled.push('table');
|
|
254
|
-
}
|
|
255
|
-
if (layouts.card && layouts.card.enabled !== false) {
|
|
256
|
-
enabled.push('card');
|
|
257
|
-
}
|
|
258
|
-
return enabled.length > 0 ? enabled : ['table'];
|
|
259
|
-
}
|
|
260
|
-
/** First enabled layout; used as the default when no user preference is stored. */
|
|
261
|
-
function resolveDefaultMasterListLayout(layouts) {
|
|
262
|
-
return resolveEnabledMasterListLayouts(layouts)[0];
|
|
263
|
-
}
|
|
264
|
-
// new version
|
|
265
|
-
var AXPEntityType;
|
|
266
|
-
(function (AXPEntityType) {
|
|
267
|
-
AXPEntityType[AXPEntityType["Entity"] = 0] = "Entity";
|
|
268
|
-
AXPEntityType[AXPEntityType["AggregateRoot"] = 1] = "AggregateRoot";
|
|
269
|
-
AXPEntityType[AXPEntityType["ValueObject"] = 2] = "ValueObject";
|
|
270
|
-
})(AXPEntityType || (AXPEntityType = {}));
|
|
171
|
+
/**
|
|
172
|
+
* Entity definition types, query views, and related helpers.
|
|
173
|
+
* Canonical definitions live in @acorex/platform/contracts.
|
|
174
|
+
*/
|
|
271
175
|
|
|
272
176
|
const EQ_OPER = { name: 'equal', title: '@general:terms.operators.equal', hasValue: true };
|
|
273
177
|
const NOT_EQ_OPER = { name: 'notEqual', title: '@general:terms.operators.not-equal', hasValue: true };
|
|
@@ -472,38 +376,6 @@ const AXP_HOME_PAGE_DEFAULT_KEY = new InjectionToken('AXP_HOME_PAGE_DEFAULT_KEY'
|
|
|
472
376
|
factory: () => 'home'
|
|
473
377
|
});
|
|
474
378
|
|
|
475
|
-
/**
|
|
476
|
-
* Regional and locale-related setting keys used by platform widgets and components.
|
|
477
|
-
* Most values use the `LocaleManagement:Setting:Regional.*` prefix; definitions live in the locale-management module where applicable.
|
|
478
|
-
*/
|
|
479
|
-
var AXPRegionalSetting;
|
|
480
|
-
(function (AXPRegionalSetting) {
|
|
481
|
-
AXPRegionalSetting["LocaleProfile"] = "LocaleManagement:Setting:Regional.LocaleProfile";
|
|
482
|
-
AXPRegionalSetting["TimeZone"] = "LocaleManagement:Setting:Regional.TimeZone";
|
|
483
|
-
AXPRegionalSetting["Language"] = "LocaleManagement:Setting:Regional.Language";
|
|
484
|
-
AXPRegionalSetting["Country"] = "LocaleManagement:Setting:Regional.Country";
|
|
485
|
-
AXPRegionalSetting["Calendar"] = "LocaleManagement:Setting:Regional.Calendar";
|
|
486
|
-
/** User-selected holiday calendars shown on the task board and other calendar views. */
|
|
487
|
-
AXPRegionalSetting["CalendarEvents"] = "LocaleManagement:Setting:Regional.CalendarEvents";
|
|
488
|
-
AXPRegionalSetting["FirstDayOfWeek"] = "LocaleManagement:Setting:Regional.FirstDayOfWeek";
|
|
489
|
-
AXPRegionalSetting["WeekendDays"] = "LocaleManagement:Setting:Regional.WeekendDays";
|
|
490
|
-
AXPRegionalSetting["ShortDate"] = "LocaleManagement:Setting:Regional.ShortDate";
|
|
491
|
-
AXPRegionalSetting["MediumDate"] = "LocaleManagement:Setting:Regional.MediumDate";
|
|
492
|
-
AXPRegionalSetting["LongDate"] = "LocaleManagement:Setting:Regional.LongDate";
|
|
493
|
-
AXPRegionalSetting["ShortTime"] = "LocaleManagement:Setting:Regional.ShortTime";
|
|
494
|
-
AXPRegionalSetting["MediumTime"] = "LocaleManagement:Setting:Regional.MediumTime";
|
|
495
|
-
AXPRegionalSetting["LongTime"] = "LocaleManagement:Setting:Regional.LongTime";
|
|
496
|
-
AXPRegionalSetting["MeasurementUnits"] = "LocaleManagement:Setting:Regional.MeasurementUnits";
|
|
497
|
-
AXPRegionalSetting["TemperatureUnits"] = "LocaleManagement:Setting:Regional.TemperatureUnits";
|
|
498
|
-
AXPRegionalSetting["DistanceUnits"] = "LocaleManagement:Setting:Regional.DistanceUnits";
|
|
499
|
-
AXPRegionalSetting["WeightUnits"] = "LocaleManagement:Setting:Regional.WeightUnits";
|
|
500
|
-
AXPRegionalSetting["VolumeUnits"] = "LocaleManagement:Setting:Regional.VolumeUnits";
|
|
501
|
-
AXPRegionalSetting["SpeedUnits"] = "LocaleManagement:Setting:Regional.SpeedUnits";
|
|
502
|
-
AXPRegionalSetting["AreaUnits"] = "LocaleManagement:Setting:Regional.AreaUnits";
|
|
503
|
-
/** Default multi-language behavior for standard text field names; defined in locale-management `AXMRegionalSettingProvider`. */
|
|
504
|
-
AXPRegionalSetting["MultiLanguageSupport"] = "LocaleManagement:Setting:Regional.MultiLanguageSupport";
|
|
505
|
-
})(AXPRegionalSetting || (AXPRegionalSetting = {}));
|
|
506
|
-
|
|
507
379
|
class AXPSettingDefinitionProviderContext {
|
|
508
380
|
constructor() {
|
|
509
381
|
this.rootGroups = [];
|
|
@@ -601,6 +473,7 @@ class AXPSettingDefinitionSectionBuilder {
|
|
|
601
473
|
isRequired: setting.isRequired ?? false,
|
|
602
474
|
isInherited: setting.isInherited ?? false,
|
|
603
475
|
isEncrypted: setting.isEncrypted ?? false,
|
|
476
|
+
requiresPageRefresh: setting.requiresPageRefresh ?? false,
|
|
604
477
|
defaultValue: setting.defaultValue,
|
|
605
478
|
scope: setting.scope ?? 'P',
|
|
606
479
|
layout: setting.widget.layout,
|
|
@@ -735,6 +608,26 @@ class AXPSettingDefinitionProviderService {
|
|
|
735
608
|
collect(this.cache);
|
|
736
609
|
return scopes;
|
|
737
610
|
}
|
|
611
|
+
async requiresPageRefreshByKey() {
|
|
612
|
+
const refreshByKey = {};
|
|
613
|
+
const collect = (groups) => {
|
|
614
|
+
groups.forEach((group) => {
|
|
615
|
+
group.sections.forEach((section) => {
|
|
616
|
+
section.settings.forEach((setting) => {
|
|
617
|
+
if (setting.requiresPageRefresh) {
|
|
618
|
+
refreshByKey[setting.name] = true;
|
|
619
|
+
}
|
|
620
|
+
});
|
|
621
|
+
});
|
|
622
|
+
collect(group.groups);
|
|
623
|
+
});
|
|
624
|
+
};
|
|
625
|
+
if (!this.cache) {
|
|
626
|
+
await this.load();
|
|
627
|
+
}
|
|
628
|
+
collect(this.cache);
|
|
629
|
+
return refreshByKey;
|
|
630
|
+
}
|
|
738
631
|
findGroup(scope, groupName) {
|
|
739
632
|
return this.searchRecursive(this.getList(scope), groupName, []); // Initialize with an empty breadcrumb
|
|
740
633
|
}
|
|
@@ -1425,6 +1318,8 @@ var AXPCommonSettings;
|
|
|
1425
1318
|
AXPCommonSettings["DebugMode"] = "PlatformDevTools:Setting:Developer.DebugMode";
|
|
1426
1319
|
AXPCommonSettings["ShowRowIndexColumn"] = "Common:Setting:Entity.ShowRowIndexColumn";
|
|
1427
1320
|
AXPCommonSettings["EntityListPersistenceMode"] = "Common:Setting:Entity.ListPersistenceMode";
|
|
1321
|
+
AXPCommonSettings["NavigationMode"] = "Common:Setting:Navigation.Mode";
|
|
1322
|
+
AXPCommonSettings["MaxOpenTabs"] = "Common:Setting:Navigation.MaxOpenTabs";
|
|
1428
1323
|
})(AXPCommonSettings || (AXPCommonSettings = {}));
|
|
1429
1324
|
|
|
1430
1325
|
//#endregion
|
|
@@ -1611,9 +1506,176 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
1611
1506
|
}]
|
|
1612
1507
|
}], ctorParameters: () => [] });
|
|
1613
1508
|
|
|
1509
|
+
//#endregion
|
|
1510
|
+
/**
|
|
1511
|
+
* Holds detached Angular route component instances keyed by tab key so tabular navigation can
|
|
1512
|
+
* preserve a page's full state (data-table selection, scroll, unsaved form values, etc.) while it
|
|
1513
|
+
* is in the background, then reattach it without re-rendering.
|
|
1514
|
+
*
|
|
1515
|
+
* The registry has no `Router` dependency on purpose: it is consumed by the {@link RouteReuseStrategy}
|
|
1516
|
+
* implementation, so depending on `Router` here would create a construction-time cycle.
|
|
1517
|
+
*/
|
|
1518
|
+
class AXPTabRouteCacheRegistry {
|
|
1519
|
+
constructor() {
|
|
1520
|
+
//#region ---- State ----
|
|
1521
|
+
this.handles = new Map();
|
|
1522
|
+
this.openKeys = new Set();
|
|
1523
|
+
this.enabled = false;
|
|
1524
|
+
this.deactivateGuardSuspended = false;
|
|
1525
|
+
}
|
|
1526
|
+
//#endregion
|
|
1527
|
+
//#region ---- Deactivate Guard Suspension ----
|
|
1528
|
+
/**
|
|
1529
|
+
* Runs `action` while unsaved-changes `canDeactivate` guards are suspended, so switching between
|
|
1530
|
+
* tabs preserves a dirty page without prompting. Closing a tab does not use this path, so the
|
|
1531
|
+
* prompt still appears there.
|
|
1532
|
+
*/
|
|
1533
|
+
async runWithSuspendedDeactivateGuards(action) {
|
|
1534
|
+
const previous = this.deactivateGuardSuspended;
|
|
1535
|
+
this.deactivateGuardSuspended = true;
|
|
1536
|
+
try {
|
|
1537
|
+
return await action();
|
|
1538
|
+
}
|
|
1539
|
+
finally {
|
|
1540
|
+
this.deactivateGuardSuspended = previous;
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1543
|
+
isDeactivateGuardSuspended() {
|
|
1544
|
+
return this.deactivateGuardSuspended;
|
|
1545
|
+
}
|
|
1546
|
+
//#endregion
|
|
1547
|
+
//#region ---- Mode ----
|
|
1548
|
+
/** Enables caching (tabular mode). Disabling clears and destroys every cached component. */
|
|
1549
|
+
setEnabled(enabled) {
|
|
1550
|
+
if (this.enabled === enabled) {
|
|
1551
|
+
return;
|
|
1552
|
+
}
|
|
1553
|
+
this.enabled = enabled;
|
|
1554
|
+
if (!enabled) {
|
|
1555
|
+
this.clear();
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1558
|
+
isEnabled() {
|
|
1559
|
+
return this.enabled;
|
|
1560
|
+
}
|
|
1561
|
+
//#endregion
|
|
1562
|
+
//#region ---- Open Keys Sync ----
|
|
1563
|
+
/**
|
|
1564
|
+
* Reconciles open scoped tab keys and active tab URLs. Cached handles whose key is not an open
|
|
1565
|
+
* scoped tab key, an active tab URL, or a child path under an open scoped tab are destroyed.
|
|
1566
|
+
*/
|
|
1567
|
+
syncOpenKeys(scopedKeys, activeTabUrls = []) {
|
|
1568
|
+
const scopedSet = new Set(scopedKeys);
|
|
1569
|
+
const activePathSet = new Set(activeTabUrls.map((url) => url.split('?')[0].split('#')[0].replace(/\/+$/, '') || '/'));
|
|
1570
|
+
for (const cacheKey of [...this.handles.keys()]) {
|
|
1571
|
+
const normalizedCacheKey = cacheKey.replace(/\/+$/, '') || '/';
|
|
1572
|
+
const keepsScopedKey = scopedSet.has(normalizedCacheKey);
|
|
1573
|
+
const keepsActiveUrl = activePathSet.has(normalizedCacheKey);
|
|
1574
|
+
const keepsUnderScopedTab = [...scopedSet].some((scopedKey) => normalizedCacheKey === scopedKey || normalizedCacheKey.startsWith(`${scopedKey}/`));
|
|
1575
|
+
if (!keepsScopedKey && !keepsActiveUrl && !keepsUnderScopedTab) {
|
|
1576
|
+
this.destroyHandle(this.handles.get(cacheKey));
|
|
1577
|
+
this.handles.delete(cacheKey);
|
|
1578
|
+
}
|
|
1579
|
+
}
|
|
1580
|
+
this.openKeys = scopedSet;
|
|
1581
|
+
}
|
|
1582
|
+
isOpen(key) {
|
|
1583
|
+
return this.openKeys.has(key);
|
|
1584
|
+
}
|
|
1585
|
+
/**
|
|
1586
|
+
* Returns the live component instance for a cached background tab, when available.
|
|
1587
|
+
* Matches the tab key directly or any cached handle under a scoped tab prefix.
|
|
1588
|
+
*/
|
|
1589
|
+
getComponentInstance(key) {
|
|
1590
|
+
const normalizedKey = key.replace(/\/+$/, '') || '/';
|
|
1591
|
+
const direct = this.handles.get(normalizedKey) ?? this.handles.get(key);
|
|
1592
|
+
if (direct) {
|
|
1593
|
+
return this.readComponentInstance(direct);
|
|
1594
|
+
}
|
|
1595
|
+
for (const [cacheKey, handle] of this.handles) {
|
|
1596
|
+
const normalizedCacheKey = cacheKey.replace(/\/+$/, '') || '/';
|
|
1597
|
+
if (normalizedCacheKey === normalizedKey || normalizedCacheKey.startsWith(`${normalizedKey}/`)) {
|
|
1598
|
+
return this.readComponentInstance(handle);
|
|
1599
|
+
}
|
|
1600
|
+
}
|
|
1601
|
+
return null;
|
|
1602
|
+
}
|
|
1603
|
+
//#endregion
|
|
1604
|
+
//#region ---- Handle Store ----
|
|
1605
|
+
/**
|
|
1606
|
+
* Caches a detached background handle. Overwrites without destroying the previous handle: on a
|
|
1607
|
+
* repeat visit Angular hands back a new handle object wrapping the same live `componentRef`, so
|
|
1608
|
+
* destroying the old one would destroy the live component and corrupt the view (NG0922).
|
|
1609
|
+
*/
|
|
1610
|
+
store(key, handle) {
|
|
1611
|
+
this.handles.set(key, handle);
|
|
1612
|
+
}
|
|
1613
|
+
/**
|
|
1614
|
+
* Returns the cached handle without mutating the map. Angular reads a handle twice per
|
|
1615
|
+
* navigation (once while building the future state, once while attaching), so `retrieve` must be
|
|
1616
|
+
* idempotent; the handle is released afterwards via {@link release} when Angular calls
|
|
1617
|
+
* `store(snapshot, null)`.
|
|
1618
|
+
*/
|
|
1619
|
+
retrieve(key) {
|
|
1620
|
+
return this.handles.get(key) ?? null;
|
|
1621
|
+
}
|
|
1622
|
+
/**
|
|
1623
|
+
* Drops a handle that the router has just reattached (it is now a live foreground view, so it
|
|
1624
|
+
* must not be destroyed). The map keeps only truly backgrounded handles.
|
|
1625
|
+
*/
|
|
1626
|
+
release(key) {
|
|
1627
|
+
this.handles.delete(key);
|
|
1628
|
+
}
|
|
1629
|
+
has(key) {
|
|
1630
|
+
return this.handles.has(key);
|
|
1631
|
+
}
|
|
1632
|
+
/** Destroys and removes a single cached handle (used when a tab closes). */
|
|
1633
|
+
evict(key) {
|
|
1634
|
+
this.destroyHandle(this.handles.get(key));
|
|
1635
|
+
this.handles.delete(key);
|
|
1636
|
+
}
|
|
1637
|
+
/** Destroys and removes every cached handle. */
|
|
1638
|
+
clear() {
|
|
1639
|
+
for (const handle of this.handles.values()) {
|
|
1640
|
+
this.destroyHandle(handle);
|
|
1641
|
+
}
|
|
1642
|
+
this.handles.clear();
|
|
1643
|
+
this.openKeys.clear();
|
|
1644
|
+
}
|
|
1645
|
+
//#endregion
|
|
1646
|
+
//#region ---- Utility Methods ----
|
|
1647
|
+
readComponentInstance(handle) {
|
|
1648
|
+
const componentRef = handle.componentRef;
|
|
1649
|
+
return componentRef?.instance ?? null;
|
|
1650
|
+
}
|
|
1651
|
+
destroyHandle(handle) {
|
|
1652
|
+
if (!handle) {
|
|
1653
|
+
return;
|
|
1654
|
+
}
|
|
1655
|
+
const componentRef = handle.componentRef;
|
|
1656
|
+
componentRef?.destroy?.();
|
|
1657
|
+
}
|
|
1658
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPTabRouteCacheRegistry, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
1659
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPTabRouteCacheRegistry, providedIn: 'root' }); }
|
|
1660
|
+
}
|
|
1661
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPTabRouteCacheRegistry, decorators: [{
|
|
1662
|
+
type: Injectable,
|
|
1663
|
+
args: [{
|
|
1664
|
+
providedIn: 'root',
|
|
1665
|
+
}]
|
|
1666
|
+
}] });
|
|
1667
|
+
|
|
1614
1668
|
//#region ---- Unsaved Changes Can Deactivate Guard Factory ----
|
|
1615
1669
|
function createUnsavedChangesCanDeactivateGuard() {
|
|
1616
|
-
return (component) =>
|
|
1670
|
+
return (component) => {
|
|
1671
|
+
// Tab switches keep the page alive in the background, so skip the prompt; closing a tab does not
|
|
1672
|
+
// suspend guards and still prompts.
|
|
1673
|
+
const tabCache = inject(AXPTabRouteCacheRegistry, { optional: true });
|
|
1674
|
+
if (tabCache?.isDeactivateGuardSuspended()) {
|
|
1675
|
+
return true;
|
|
1676
|
+
}
|
|
1677
|
+
return component.canDeactivate();
|
|
1678
|
+
};
|
|
1617
1679
|
}
|
|
1618
1680
|
//#endregion
|
|
1619
1681
|
|
|
@@ -2991,6 +3053,56 @@ function axpRedirectToUnauthorized(router = inject(Router)) {
|
|
|
2991
3053
|
return new RedirectCommand(router.parseUrl(AXP_UNAUTHORIZED_ROUTE));
|
|
2992
3054
|
}
|
|
2993
3055
|
|
|
3056
|
+
//#endregion
|
|
3057
|
+
/**
|
|
3058
|
+
* Platform formatter for display text: resolves ML objects, i18n keys (`@scope:key`), and primitives.
|
|
3059
|
+
* Register via {@link AXPCommonModule} (auto) or `AXFormatModule.forRoot({ formatters: [AXPTranslateFormatter] })`.
|
|
3060
|
+
*/
|
|
3061
|
+
class AXPTranslateFormatter {
|
|
3062
|
+
constructor() {
|
|
3063
|
+
//#region ---- Services & Dependencies ----
|
|
3064
|
+
this.translationService = inject(AXTranslationService);
|
|
3065
|
+
}
|
|
3066
|
+
//#endregion
|
|
3067
|
+
//#region ---- AXFormatter ----
|
|
3068
|
+
get name() {
|
|
3069
|
+
return 'translate';
|
|
3070
|
+
}
|
|
3071
|
+
format(value, options) {
|
|
3072
|
+
if (isNil(value) || value === '') {
|
|
3073
|
+
return '';
|
|
3074
|
+
}
|
|
3075
|
+
if (isObject(value) && !Array.isArray(value) && !(value instanceof Date)) {
|
|
3076
|
+
const resolved = this.translationService.resolve(value);
|
|
3077
|
+
return resolved?.trim() ?? '';
|
|
3078
|
+
}
|
|
3079
|
+
if (isString(value)) {
|
|
3080
|
+
const text = value.trim();
|
|
3081
|
+
if (!text) {
|
|
3082
|
+
return '';
|
|
3083
|
+
}
|
|
3084
|
+
if (text.startsWith('@')) {
|
|
3085
|
+
const translateOptions = typeof options === 'object' ? options : undefined;
|
|
3086
|
+
const translated = translateSync(text, translateOptions);
|
|
3087
|
+
return translated?.trim() ?? text;
|
|
3088
|
+
}
|
|
3089
|
+
return text;
|
|
3090
|
+
}
|
|
3091
|
+
if (typeof value === 'number' || typeof value === 'boolean') {
|
|
3092
|
+
return String(value);
|
|
3093
|
+
}
|
|
3094
|
+
if (value instanceof Date) {
|
|
3095
|
+
return value.toISOString();
|
|
3096
|
+
}
|
|
3097
|
+
return String(value).trim();
|
|
3098
|
+
}
|
|
3099
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPTranslateFormatter, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
3100
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPTranslateFormatter }); }
|
|
3101
|
+
}
|
|
3102
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPTranslateFormatter, decorators: [{
|
|
3103
|
+
type: Injectable
|
|
3104
|
+
}] });
|
|
3105
|
+
|
|
2994
3106
|
class AXPFileStorageService {
|
|
2995
3107
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPFileStorageService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
2996
3108
|
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPFileStorageService }); }
|
|
@@ -4756,6 +4868,8 @@ class AXPMenuSearchDefinitionProvider {
|
|
|
4756
4868
|
}
|
|
4757
4869
|
}
|
|
4758
4870
|
|
|
4871
|
+
const AXP_SEARCH_DEFINITION_LAZY_RESOLVER = new InjectionToken('AXP_SEARCH_DEFINITION_LAZY_RESOLVER');
|
|
4872
|
+
|
|
4759
4873
|
class AXPSearchDefinitionProviderContext {
|
|
4760
4874
|
constructor() {
|
|
4761
4875
|
this.definitions = [];
|
|
@@ -4830,6 +4944,7 @@ const AXP_SEARCH_DEFINITION_PROVIDER = new InjectionToken('AXP_SEARCH_DEFINITION
|
|
|
4830
4944
|
class AXPSearchDefinitionProviderService {
|
|
4831
4945
|
constructor() {
|
|
4832
4946
|
this.providers = inject(AXP_SEARCH_DEFINITION_PROVIDER, { optional: true });
|
|
4947
|
+
this.lazyResolver = inject(AXP_SEARCH_DEFINITION_LAZY_RESOLVER, { optional: true });
|
|
4833
4948
|
this.cache = null;
|
|
4834
4949
|
}
|
|
4835
4950
|
async load() {
|
|
@@ -4860,12 +4975,29 @@ class AXPSearchDefinitionProviderService {
|
|
|
4860
4975
|
}
|
|
4861
4976
|
}
|
|
4862
4977
|
findDefinition(definitionName) {
|
|
4863
|
-
const definition = this.getList().find((
|
|
4978
|
+
const definition = this.getList().find((item) => item.name === definitionName);
|
|
4864
4979
|
if (!definition) {
|
|
4865
4980
|
throw new Error(`Definition with name ${definitionName} not found`);
|
|
4866
4981
|
}
|
|
4867
4982
|
return definition;
|
|
4868
4983
|
}
|
|
4984
|
+
async findDefinitionAsync(definitionName) {
|
|
4985
|
+
await this.load();
|
|
4986
|
+
const existing = this.getList().find((item) => item.name === definitionName);
|
|
4987
|
+
if (existing) {
|
|
4988
|
+
return existing;
|
|
4989
|
+
}
|
|
4990
|
+
const lazy = this.lazyResolver;
|
|
4991
|
+
if (!lazy) {
|
|
4992
|
+
return null;
|
|
4993
|
+
}
|
|
4994
|
+
const resolved = await lazy.resolve(definitionName);
|
|
4995
|
+
if (!resolved) {
|
|
4996
|
+
return null;
|
|
4997
|
+
}
|
|
4998
|
+
this.cache = [...(this.cache ?? []), resolved];
|
|
4999
|
+
return resolved;
|
|
5000
|
+
}
|
|
4869
5001
|
findDefinitionByGroup(definitionGroup) {
|
|
4870
5002
|
const definition = this.getList().find((definition) => definition.group === definitionGroup);
|
|
4871
5003
|
if (!definition) {
|
|
@@ -4884,17 +5016,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
4884
5016
|
//#region ---- Imports ----
|
|
4885
5017
|
//#endregion
|
|
4886
5018
|
//#region ---- Template normalization ----
|
|
4887
|
-
/**
|
|
4888
|
-
|
|
4889
|
-
* Converts `context.eval('path')`, single braces, and simple dot paths to `context.eval` segments.
|
|
4890
|
-
*/
|
|
4891
|
-
function normalizeEntityDisplayTemplate(template) {
|
|
4892
|
-
let normalized = template.replace(/\{\{\s*context\.eval\(['"]([^'"]+)['"]\)\s*\}\}/g, '{{ context.eval("$1") }}');
|
|
4893
|
-
if (!normalized.includes('{{')) {
|
|
4894
|
-
normalized = normalized.replace(/\{/g, '{{').replace(/\}/g, '}}');
|
|
4895
|
-
}
|
|
4896
|
-
return normalized.replace(/\{\{\s*(?!context\.)([\w]+(?:\.[\w]+)*)\s*\}\}/g, '{{ context.eval("$1") }}');
|
|
4897
|
-
}
|
|
5019
|
+
/** @see normalizeExpressionTemplate */
|
|
5020
|
+
const normalizeEntityDisplayTemplate = normalizeExpressionTemplate;
|
|
4898
5021
|
/** True when formatted text still contains unresolved template markers. */
|
|
4899
5022
|
function isUnresolvedEntityDisplayTemplate(value) {
|
|
4900
5023
|
return /\{\{/.test(value) || /context\.eval\s*\(/.test(value) || value === '[object Object]';
|
|
@@ -5025,17 +5148,55 @@ function resolveEntityRowTitleTemplate(entity) {
|
|
|
5025
5148
|
}
|
|
5026
5149
|
return undefined;
|
|
5027
5150
|
}
|
|
5151
|
+
/**
|
|
5152
|
+
* Record title template for modify popups: `interfaces.master.update.title` → `single.title`.
|
|
5153
|
+
*/
|
|
5154
|
+
function resolveEntityModifyRecordTitleTemplate(entity) {
|
|
5155
|
+
if (!entity) {
|
|
5156
|
+
return undefined;
|
|
5157
|
+
}
|
|
5158
|
+
const updateTitle = entity.interfaces?.master?.update?.title?.trim();
|
|
5159
|
+
if (updateTitle && isRowBoundTemplate(updateTitle)) {
|
|
5160
|
+
return normalizeEntityDisplayTemplate(updateTitle);
|
|
5161
|
+
}
|
|
5162
|
+
const singleTitle = entity.interfaces?.master?.single?.title?.trim();
|
|
5163
|
+
if (singleTitle && isRowBoundTemplate(singleTitle)) {
|
|
5164
|
+
return normalizeEntityDisplayTemplate(singleTitle);
|
|
5165
|
+
}
|
|
5166
|
+
return undefined;
|
|
5167
|
+
}
|
|
5168
|
+
/**
|
|
5169
|
+
* Modify/edit popup chrome: `{action} {individual} ({update.title ?? single.title})` when a record template exists.
|
|
5170
|
+
*/
|
|
5171
|
+
async function resolveEntityModifyPopupTitle(entity, recordData, translationService, formatService, expressionEvaluator, contextEvalFactory, actionTitleKey = '@general:actions.modify.title') {
|
|
5172
|
+
const actionPrefix = translationService.translateSync(actionTitleKey);
|
|
5173
|
+
const entityLabel = translationService.translateSync(entity.formats.individual);
|
|
5174
|
+
const baseTitle = `${actionPrefix} ${entityLabel}`.trim();
|
|
5175
|
+
const recordTemplate = resolveEntityModifyRecordTitleTemplate(entity);
|
|
5176
|
+
if (!recordTemplate) {
|
|
5177
|
+
return baseTitle;
|
|
5178
|
+
}
|
|
5179
|
+
const locale = translationService.getActiveLang();
|
|
5180
|
+
const resolved = await resolveSearchDisplayTemplate(recordTemplate, recordData, locale, translationService, formatService, expressionEvaluator, contextEvalFactory);
|
|
5181
|
+
const recordTitle = sanitizeResolvedEntityDisplayTitle(typeof resolved === 'string'
|
|
5182
|
+
? resolved
|
|
5183
|
+
: resolveSearchableText(resolved, locale, translationService));
|
|
5184
|
+
if (!isMeaningfulEntityDisplayTitle(recordTitle)) {
|
|
5185
|
+
return baseTitle;
|
|
5186
|
+
}
|
|
5187
|
+
return `${baseTitle} (${recordTitle})`;
|
|
5188
|
+
}
|
|
5028
5189
|
//#endregion
|
|
5029
5190
|
//#region ---- Runtime resolution ----
|
|
5030
5191
|
/**
|
|
5031
5192
|
* Resolves an entity row title using entity-driven fallback templates and property paths.
|
|
5032
5193
|
*/
|
|
5033
|
-
async function resolveEntitySearchTitleFallback(format, data, locale, translationService, formatService, expressionEvaluator) {
|
|
5194
|
+
async function resolveEntitySearchTitleFallback(format, data, locale, translationService, formatService, expressionEvaluator, contextEvalFactory) {
|
|
5034
5195
|
if (data == null) {
|
|
5035
5196
|
return '';
|
|
5036
5197
|
}
|
|
5037
5198
|
for (const template of format.titleFallbackTemplates ?? []) {
|
|
5038
|
-
const resolved = await resolveSearchDisplayTemplate(template, data, locale, translationService, formatService, expressionEvaluator);
|
|
5199
|
+
const resolved = await resolveSearchDisplayTemplate(template, data, locale, translationService, formatService, expressionEvaluator, contextEvalFactory);
|
|
5039
5200
|
const text = sanitizeResolvedEntityDisplayTitle(typeof resolved === 'string'
|
|
5040
5201
|
? resolved
|
|
5041
5202
|
: resolveSearchableText(resolved, locale, translationService));
|
|
@@ -5076,13 +5237,8 @@ function resolveSearchableText(value, locale, translationService) {
|
|
|
5076
5237
|
}
|
|
5077
5238
|
return trimmed;
|
|
5078
5239
|
}
|
|
5079
|
-
if (isLocaleStringMap(value)) {
|
|
5080
|
-
return
|
|
5081
|
-
.filter((entry) => (entry ?? '').trim() !== '')
|
|
5082
|
-
.join(' ');
|
|
5083
|
-
}
|
|
5084
|
-
if (translationService.isValidMultiLanguageObject(value)) {
|
|
5085
|
-
return resolveMultiLanguageString(value, locale);
|
|
5240
|
+
if (isLocaleStringMap(value) || translationService.isValidMultiLanguageObject(value)) {
|
|
5241
|
+
return resolveMultiLanguageString(value, locale).trim();
|
|
5086
5242
|
}
|
|
5087
5243
|
return '';
|
|
5088
5244
|
}
|
|
@@ -5094,6 +5250,11 @@ function searchInSearchableValue(value, query, locale, translationService) {
|
|
|
5094
5250
|
if (!q) {
|
|
5095
5251
|
return true;
|
|
5096
5252
|
}
|
|
5253
|
+
if (isLocaleStringMap(value)) {
|
|
5254
|
+
return Object.values(value)
|
|
5255
|
+
.filter((entry) => typeof entry === 'string' && entry.trim() !== '')
|
|
5256
|
+
.some((entry) => entry.toLocaleLowerCase(locale).includes(q));
|
|
5257
|
+
}
|
|
5097
5258
|
const text = resolveSearchableText(value, locale, translationService).toLocaleLowerCase(locale);
|
|
5098
5259
|
return text.includes(q);
|
|
5099
5260
|
}
|
|
@@ -5126,26 +5287,31 @@ function searchInRecordValues(value, query, locale, translationService, depth =
|
|
|
5126
5287
|
function countMustacheExpressions(template) {
|
|
5127
5288
|
return (template.match(/\{\{/g) ?? []).length;
|
|
5128
5289
|
}
|
|
5129
|
-
/** Standard row scope for entity title/search templates. */
|
|
5130
|
-
function createEntityRowExpressionScope(data) {
|
|
5290
|
+
/** Standard display-tier row scope for entity title/search templates. */
|
|
5291
|
+
function createEntityRowExpressionScope(data, contextEvalFactory) {
|
|
5131
5292
|
return {
|
|
5132
|
-
context: {
|
|
5133
|
-
eval: (path) => get(data, path),
|
|
5134
|
-
},
|
|
5293
|
+
context: contextEvalFactory.createScope((data ?? {})),
|
|
5135
5294
|
};
|
|
5136
5295
|
}
|
|
5296
|
+
/**
|
|
5297
|
+
* Coerces an evaluated display segment to plain text.
|
|
5298
|
+
* Piped eval returns strings; unpiped MLS still resolves via compat fallback.
|
|
5299
|
+
*/
|
|
5300
|
+
function formatDisplaySegment(segment, locale, translationService) {
|
|
5301
|
+
if (segment === false || segment == null) {
|
|
5302
|
+
return '';
|
|
5303
|
+
}
|
|
5304
|
+
if (isString(segment)) {
|
|
5305
|
+
return segment.trim();
|
|
5306
|
+
}
|
|
5307
|
+
return resolveSearchableText(segment, locale, translationService);
|
|
5308
|
+
}
|
|
5137
5309
|
/**
|
|
5138
5310
|
* Evaluates a normalized entity display template via {@link AXPExpressionEvaluatorService}.
|
|
5139
5311
|
*/
|
|
5140
|
-
async function evaluateEntityDisplayTemplate(template, data, expressionEvaluator, locale, translationService) {
|
|
5312
|
+
async function evaluateEntityDisplayTemplate(template, data, expressionEvaluator, locale, translationService, contextEvalFactory) {
|
|
5141
5313
|
const normalized = normalizeEntityDisplayTemplate(template);
|
|
5142
|
-
const scope = createEntityRowExpressionScope(data);
|
|
5143
|
-
const formatSegment = (segment) => {
|
|
5144
|
-
if (segment === false || segment == null) {
|
|
5145
|
-
return '';
|
|
5146
|
-
}
|
|
5147
|
-
return resolveSearchableText(segment, locale, translationService);
|
|
5148
|
-
};
|
|
5314
|
+
const scope = createEntityRowExpressionScope(data, contextEvalFactory);
|
|
5149
5315
|
if (!normalized.includes('{{')) {
|
|
5150
5316
|
return normalized;
|
|
5151
5317
|
}
|
|
@@ -5159,7 +5325,7 @@ async function evaluateEntityDisplayTemplate(template, data, expressionEvaluator
|
|
|
5159
5325
|
for (const match of normalized.matchAll(regex)) {
|
|
5160
5326
|
result += normalized.slice(lastIndex, match.index);
|
|
5161
5327
|
const segment = await expressionEvaluator.evaluate(`{{ ${match[1].trim()} }}`, scope);
|
|
5162
|
-
result +=
|
|
5328
|
+
result += formatDisplaySegment(segment, locale, translationService);
|
|
5163
5329
|
lastIndex = (match.index ?? 0) + match[0].length;
|
|
5164
5330
|
}
|
|
5165
5331
|
result += normalized.slice(lastIndex);
|
|
@@ -5206,7 +5372,7 @@ function resolveQueryMatchingDisplayText(value, query, locale, translationServic
|
|
|
5206
5372
|
/**
|
|
5207
5373
|
* Resolves entity search display templates; coerces multi-language field values to readable text.
|
|
5208
5374
|
*/
|
|
5209
|
-
async function resolveSearchDisplayTemplate(template, data, locale, translationService, formatService, expressionEvaluator) {
|
|
5375
|
+
async function resolveSearchDisplayTemplate(template, data, locale, translationService, formatService, expressionEvaluator, contextEvalFactory) {
|
|
5210
5376
|
if (!template) {
|
|
5211
5377
|
return '';
|
|
5212
5378
|
}
|
|
@@ -5217,7 +5383,7 @@ async function resolveSearchDisplayTemplate(template, data, locale, translationS
|
|
|
5217
5383
|
const formatted = formatService.format(template, 'string', data);
|
|
5218
5384
|
return sanitizeResolvedEntityDisplayTitle(typeof formatted === 'string' ? formatted : String(formatted ?? ''));
|
|
5219
5385
|
}
|
|
5220
|
-
const evaluated = await evaluateEntityDisplayTemplate(template, data, expressionEvaluator, locale, translationService);
|
|
5386
|
+
const evaluated = await evaluateEntityDisplayTemplate(template, data, expressionEvaluator, locale, translationService, contextEvalFactory);
|
|
5221
5387
|
return sanitizeResolvedEntityDisplayTitle(resolveSearchableText(evaluated, locale, translationService));
|
|
5222
5388
|
}
|
|
5223
5389
|
//#endregion
|
|
@@ -5242,6 +5408,7 @@ class AXPSearchService {
|
|
|
5242
5408
|
this.formatService = inject(AXFormatService);
|
|
5243
5409
|
this.translationService = inject(AXTranslationService);
|
|
5244
5410
|
this.expressionEvaluator = inject(AXPExpressionEvaluatorService);
|
|
5411
|
+
this.contextEvalFactory = inject(AXPContextEvalFactory);
|
|
5245
5412
|
}
|
|
5246
5413
|
async search(text) {
|
|
5247
5414
|
//TODO better handle this
|
|
@@ -5271,20 +5438,23 @@ class AXPSearchService {
|
|
|
5271
5438
|
for (const resultArray of results) {
|
|
5272
5439
|
for (const result of resultArray) {
|
|
5273
5440
|
try {
|
|
5274
|
-
const definition = this.definitionService.
|
|
5441
|
+
const definition = await this.definitionService.findDefinitionAsync(result.group);
|
|
5442
|
+
if (!definition) {
|
|
5443
|
+
continue;
|
|
5444
|
+
}
|
|
5275
5445
|
const resolvedTitle = result.title ??
|
|
5276
5446
|
(definition.format.title
|
|
5277
|
-
? await resolveSearchDisplayTemplate(definition.format.title, result.data, locale, this.translationService, this.formatService, this.expressionEvaluator)
|
|
5447
|
+
? await resolveSearchDisplayTemplate(definition.format.title, result.data, locale, this.translationService, this.formatService, this.expressionEvaluator, this.contextEvalFactory)
|
|
5278
5448
|
: '');
|
|
5279
5449
|
const resolvedDescription = result.description ??
|
|
5280
5450
|
(definition.format.description
|
|
5281
|
-
? await resolveSearchDisplayTemplate(definition.format.description, result.data, locale, this.translationService, this.formatService, this.expressionEvaluator)
|
|
5451
|
+
? await resolveSearchDisplayTemplate(definition.format.description, result.data, locale, this.translationService, this.formatService, this.expressionEvaluator, this.contextEvalFactory)
|
|
5282
5452
|
: undefined);
|
|
5283
5453
|
let titleText = sanitizeResolvedEntityDisplayTitle(typeof resolvedTitle === 'string'
|
|
5284
5454
|
? resolvedTitle
|
|
5285
5455
|
: resolveSearchableText(resolvedTitle, locale, this.translationService));
|
|
5286
5456
|
if (!isMeaningfulEntityDisplayTitle(titleText)) {
|
|
5287
|
-
titleText = await resolveEntitySearchTitleFallback(definition.format, result.data, locale, this.translationService, this.formatService, this.expressionEvaluator);
|
|
5457
|
+
titleText = await resolveEntitySearchTitleFallback(definition.format, result.data, locale, this.translationService, this.formatService, this.expressionEvaluator, this.contextEvalFactory);
|
|
5288
5458
|
}
|
|
5289
5459
|
if (!isMeaningfulEntityDisplayTitle(titleText)) {
|
|
5290
5460
|
continue;
|
|
@@ -5779,62 +5949,1116 @@ function axpReplaceEntityListRouteQueryParams(router, queryParams) {
|
|
|
5779
5949
|
}
|
|
5780
5950
|
//#endregion
|
|
5781
5951
|
|
|
5782
|
-
|
|
5783
|
-
|
|
5784
|
-
const
|
|
5785
|
-
|
|
5786
|
-
|
|
5787
|
-
|
|
5788
|
-
|
|
5789
|
-
|
|
5952
|
+
//#region ---- Tab Key ----
|
|
5953
|
+
/** Query params ignored when building a tab dedupe key. */
|
|
5954
|
+
const IGNORED_QUERY_KEYS = new Set(['_ts']);
|
|
5955
|
+
/**
|
|
5956
|
+
* Builds a stable tab key from a router URL.
|
|
5957
|
+
* Path segments define identity; query params are excluded so the same entity record
|
|
5958
|
+
* or list reuses one tab even when filters or refresh tokens change.
|
|
5959
|
+
*/
|
|
5960
|
+
function axpBuildTabKey(url) {
|
|
5961
|
+
const normalized = normalizeRouterUrl(url);
|
|
5962
|
+
const pathOnly = normalized.split('?')[0].split('#')[0];
|
|
5963
|
+
return pathOnly;
|
|
5964
|
+
}
|
|
5965
|
+
/** Normalizes a router URL for storage and navigation. */
|
|
5966
|
+
function normalizeRouterUrl(url) {
|
|
5967
|
+
const trimmed = url.trim();
|
|
5968
|
+
if (!trimmed.length) {
|
|
5969
|
+
return '/';
|
|
5970
|
+
}
|
|
5971
|
+
const withLeadingSlash = trimmed.startsWith('/') ? trimmed : `/${trimmed}`;
|
|
5972
|
+
const [pathAndFragment, queryString = ''] = withLeadingSlash.split('?');
|
|
5973
|
+
const [pathPart, fragment = ''] = pathAndFragment.split('#');
|
|
5974
|
+
const normalizedPath = pathPart.replace(/\/+$/, '') || '/';
|
|
5975
|
+
const filteredQuery = filterQueryString(queryString);
|
|
5976
|
+
let result = normalizedPath;
|
|
5977
|
+
if (filteredQuery) {
|
|
5978
|
+
result += `?${filteredQuery}`;
|
|
5979
|
+
}
|
|
5980
|
+
if (fragment) {
|
|
5981
|
+
result += `#${fragment}`;
|
|
5790
5982
|
}
|
|
5791
|
-
|
|
5792
|
-
|
|
5793
|
-
|
|
5794
|
-
|
|
5795
|
-
|
|
5796
|
-
}
|
|
5797
|
-
const path = payload.commands;
|
|
5798
|
-
if (!path) {
|
|
5799
|
-
return;
|
|
5800
|
-
}
|
|
5801
|
-
if (path.toLowerCase().startsWith('http') || payload.target === 'blank') {
|
|
5802
|
-
window.open(path, '_blank');
|
|
5803
|
-
return;
|
|
5804
|
-
}
|
|
5805
|
-
await axpNavigateAppPath(this.router, path, payload.extras);
|
|
5983
|
+
return result;
|
|
5984
|
+
}
|
|
5985
|
+
function filterQueryString(queryString) {
|
|
5986
|
+
if (!queryString) {
|
|
5987
|
+
return '';
|
|
5806
5988
|
}
|
|
5807
|
-
|
|
5808
|
-
|
|
5989
|
+
const params = new URLSearchParams(queryString);
|
|
5990
|
+
const kept = [];
|
|
5991
|
+
params.forEach((value, key) => {
|
|
5992
|
+
if (!IGNORED_QUERY_KEYS.has(key)) {
|
|
5993
|
+
kept.push(`${encodeURIComponent(key)}=${encodeURIComponent(value)}`);
|
|
5994
|
+
}
|
|
5995
|
+
});
|
|
5996
|
+
kept.sort();
|
|
5997
|
+
return kept.join('&');
|
|
5809
5998
|
}
|
|
5810
|
-
|
|
5811
|
-
|
|
5812
|
-
|
|
5813
|
-
|
|
5999
|
+
/** Human-readable fallback title from the URL path. */
|
|
6000
|
+
function axpFallbackTabTitle(url) {
|
|
6001
|
+
const pathOnly = url.split('?')[0].split('#')[0];
|
|
6002
|
+
const segments = pathOnly.split('/').filter(Boolean);
|
|
6003
|
+
const last = defaultTo(segments[segments.length - 1], 'Page');
|
|
6004
|
+
return decodeURIComponent(last);
|
|
6005
|
+
}
|
|
6006
|
+
//#endregion
|
|
6007
|
+
|
|
6008
|
+
//#region ---- Types ----
|
|
6009
|
+
const AXP_HOME_TAB_KEY = 'axp:home';
|
|
6010
|
+
/** Route `data` key for tab-navigation behavior (`data.tab`). */
|
|
6011
|
+
const AXP_TAB_ROUTE_DATA_KEY = 'tab';
|
|
6012
|
+
//#endregion
|
|
6013
|
+
|
|
6014
|
+
//#region ---- Tab Preview Service ----
|
|
6015
|
+
/**
|
|
6016
|
+
* In-memory structured previews for tab hover cards and dirty indicators.
|
|
6017
|
+
* Populated by active/cached pages via {@link AXPPageLayoutBaseComponent}.
|
|
6018
|
+
*/
|
|
6019
|
+
class AXPTabPreviewService {
|
|
5814
6020
|
constructor() {
|
|
5815
|
-
|
|
5816
|
-
this.
|
|
5817
|
-
this.
|
|
5818
|
-
this.defaultAction = 'cancel';
|
|
6021
|
+
//#region ---- State ----
|
|
6022
|
+
this.previewMap = signal({}, ...(ngDevMode ? [{ debugName: "previewMap" }] : /* istanbul ignore next */ []));
|
|
6023
|
+
this.previews = this.previewMap.asReadonly();
|
|
5819
6024
|
}
|
|
5820
|
-
|
|
5821
|
-
|
|
5822
|
-
|
|
5823
|
-
|
|
5824
|
-
|
|
6025
|
+
//#endregion
|
|
6026
|
+
//#region ---- Public API ----
|
|
6027
|
+
setPreview(key, preview) {
|
|
6028
|
+
this.previewMap.update((current) => ({
|
|
6029
|
+
...current,
|
|
6030
|
+
[key]: preview,
|
|
6031
|
+
}));
|
|
6032
|
+
}
|
|
6033
|
+
patchPreview(key, patch) {
|
|
6034
|
+
this.previewMap.update((current) => {
|
|
6035
|
+
const existing = current[key];
|
|
6036
|
+
if (!existing) {
|
|
6037
|
+
if (!patch.url) {
|
|
6038
|
+
return current;
|
|
6039
|
+
}
|
|
6040
|
+
return {
|
|
6041
|
+
...current,
|
|
6042
|
+
[key]: {
|
|
6043
|
+
title: patch.title ?? '',
|
|
6044
|
+
url: patch.url,
|
|
6045
|
+
icon: patch.icon,
|
|
6046
|
+
subtitle: patch.subtitle,
|
|
6047
|
+
isDirty: patch.isDirty ?? false,
|
|
6048
|
+
breadcrumbTrail: patch.breadcrumbTrail ?? [],
|
|
6049
|
+
},
|
|
6050
|
+
};
|
|
6051
|
+
}
|
|
6052
|
+
return {
|
|
6053
|
+
...current,
|
|
6054
|
+
[key]: {
|
|
6055
|
+
...existing,
|
|
6056
|
+
...patch,
|
|
6057
|
+
},
|
|
6058
|
+
};
|
|
6059
|
+
});
|
|
5825
6060
|
}
|
|
5826
|
-
|
|
5827
|
-
|
|
5828
|
-
}
|
|
5829
|
-
|
|
5830
|
-
|
|
5831
|
-
|
|
5832
|
-
|
|
5833
|
-
|
|
5834
|
-
|
|
5835
|
-
|
|
5836
|
-
|
|
5837
|
-
|
|
6061
|
+
getPreview(key) {
|
|
6062
|
+
return this.previewMap()[key];
|
|
6063
|
+
}
|
|
6064
|
+
isTabDirty(key) {
|
|
6065
|
+
return !!this.previewMap()[key]?.isDirty;
|
|
6066
|
+
}
|
|
6067
|
+
clearPreview(key) {
|
|
6068
|
+
this.previewMap.update((current) => {
|
|
6069
|
+
if (!(key in current)) {
|
|
6070
|
+
return current;
|
|
6071
|
+
}
|
|
6072
|
+
const next = { ...current };
|
|
6073
|
+
delete next[key];
|
|
6074
|
+
return next;
|
|
6075
|
+
});
|
|
6076
|
+
}
|
|
6077
|
+
clearPreviews(keys) {
|
|
6078
|
+
const keySet = new Set(keys);
|
|
6079
|
+
if (!keySet.size) {
|
|
6080
|
+
return;
|
|
6081
|
+
}
|
|
6082
|
+
this.previewMap.update((current) => {
|
|
6083
|
+
const next = { ...current };
|
|
6084
|
+
let changed = false;
|
|
6085
|
+
for (const key of keySet) {
|
|
6086
|
+
if (key in next) {
|
|
6087
|
+
delete next[key];
|
|
6088
|
+
changed = true;
|
|
6089
|
+
}
|
|
6090
|
+
}
|
|
6091
|
+
return changed ? next : current;
|
|
6092
|
+
});
|
|
6093
|
+
}
|
|
6094
|
+
clearAll() {
|
|
6095
|
+
this.previewMap.set({});
|
|
6096
|
+
}
|
|
6097
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPTabPreviewService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
6098
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPTabPreviewService, providedIn: 'root' }); }
|
|
6099
|
+
}
|
|
6100
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPTabPreviewService, decorators: [{
|
|
6101
|
+
type: Injectable,
|
|
6102
|
+
args: [{
|
|
6103
|
+
providedIn: 'root',
|
|
6104
|
+
}]
|
|
6105
|
+
}] });
|
|
6106
|
+
|
|
6107
|
+
//#region ---- Tab Route Policy ----
|
|
6108
|
+
const EXCLUDED_PATH_PREFIXES = ['/auth', '/error'];
|
|
6109
|
+
const EXCLUDED_PATH_PATTERNS = [
|
|
6110
|
+
/^\/assessment\/fill\//i,
|
|
6111
|
+
/^\/forms\//i,
|
|
6112
|
+
];
|
|
6113
|
+
/**
|
|
6114
|
+
* Returns true when the URL represents an in-app page that may appear in the tab bar.
|
|
6115
|
+
*/
|
|
6116
|
+
function axpIsTabEligibleUrl(url) {
|
|
6117
|
+
const pathOnly = url.split('?')[0].split('#')[0].trim();
|
|
6118
|
+
if (!pathOnly || pathOnly === '/') {
|
|
6119
|
+
return false;
|
|
6120
|
+
}
|
|
6121
|
+
const normalized = pathOnly.startsWith('/') ? pathOnly : `/${pathOnly}`;
|
|
6122
|
+
if (EXCLUDED_PATH_PREFIXES.some((prefix) => normalized.startsWith(prefix))) {
|
|
6123
|
+
return false;
|
|
6124
|
+
}
|
|
6125
|
+
if (EXCLUDED_PATH_PATTERNS.some((pattern) => pattern.test(normalized))) {
|
|
6126
|
+
return false;
|
|
6127
|
+
}
|
|
6128
|
+
return true;
|
|
6129
|
+
}
|
|
6130
|
+
//#endregion
|
|
6131
|
+
|
|
6132
|
+
//#region ---- Route Data ----
|
|
6133
|
+
/** True when the route declares itself as a scoped tab root (`data.tab = true`). */
|
|
6134
|
+
function axpHasTabScope(data) {
|
|
6135
|
+
return data?.[AXP_TAB_ROUTE_DATA_KEY] === true;
|
|
6136
|
+
}
|
|
6137
|
+
//#endregion
|
|
6138
|
+
//#region ---- Snapshot Resolution ----
|
|
6139
|
+
/** Resolved URL path for a snapshot (no query or fragment). */
|
|
6140
|
+
function axpPathFromSnapshot(snapshot) {
|
|
6141
|
+
const segments = snapshot.pathFromRoot
|
|
6142
|
+
.flatMap((entry) => entry.url.map((segment) => segment.path))
|
|
6143
|
+
.filter(Boolean);
|
|
6144
|
+
return segments.length ? `/${segments.join('/')}` : '/';
|
|
6145
|
+
}
|
|
6146
|
+
/** Returns the leaf snapshot under the router state root. */
|
|
6147
|
+
function axpGetLeafRouteSnapshot(root) {
|
|
6148
|
+
let route = root;
|
|
6149
|
+
while (route.firstChild) {
|
|
6150
|
+
route = route.firstChild;
|
|
6151
|
+
}
|
|
6152
|
+
return route;
|
|
6153
|
+
}
|
|
6154
|
+
/**
|
|
6155
|
+
* Resolves the tab dedupe key from an activated route tree.
|
|
6156
|
+
* Uses the nearest ancestor with `data.tab = true`; otherwise the leaf path.
|
|
6157
|
+
*/
|
|
6158
|
+
function axpResolveTabKeyFromSnapshot(leaf) {
|
|
6159
|
+
const chain = leaf.pathFromRoot;
|
|
6160
|
+
for (let index = chain.length - 1; index >= 0; index--) {
|
|
6161
|
+
const snapshot = chain[index];
|
|
6162
|
+
if (axpHasTabScope(snapshot.data)) {
|
|
6163
|
+
return axpPathFromSnapshot(snapshot);
|
|
6164
|
+
}
|
|
6165
|
+
}
|
|
6166
|
+
return axpPathFromSnapshot(leaf);
|
|
6167
|
+
}
|
|
6168
|
+
//#endregion
|
|
6169
|
+
//#region ---- Router / URL Resolution ----
|
|
6170
|
+
/**
|
|
6171
|
+
* Resolves a tab key for the given URL (or the current router URL).
|
|
6172
|
+
* Prefers the live route snapshot when it matches the target URL; otherwise matches route config.
|
|
6173
|
+
*/
|
|
6174
|
+
function axpResolveTabKey(router, url) {
|
|
6175
|
+
const normalized = normalizeRouterUrl(url ?? router.url);
|
|
6176
|
+
const pathOnly = normalized.split('?')[0].split('#')[0];
|
|
6177
|
+
const currentPath = normalizeRouterUrl(router.url).split('?')[0].split('#')[0];
|
|
6178
|
+
if (!url || pathOnly === currentPath) {
|
|
6179
|
+
const leaf = axpGetLeafRouteSnapshot(router.routerState.snapshot.root);
|
|
6180
|
+
return axpResolveTabKeyFromSnapshot(leaf);
|
|
6181
|
+
}
|
|
6182
|
+
const fromConfig = axpResolveTabKeyFromRouteConfig(router.config, pathOnly);
|
|
6183
|
+
if (fromConfig) {
|
|
6184
|
+
return fromConfig;
|
|
6185
|
+
}
|
|
6186
|
+
return pathOnly.startsWith('/') ? pathOnly : `/${pathOnly}`;
|
|
6187
|
+
}
|
|
6188
|
+
/**
|
|
6189
|
+
* Matches a URL path against registered routes and resolves the tab key from `data.tab`.
|
|
6190
|
+
*/
|
|
6191
|
+
function axpResolveTabKeyFromRouteConfig(routes, pathOnly) {
|
|
6192
|
+
const normalized = pathOnly.startsWith('/') ? pathOnly.replace(/\/+$/, '') || '/' : `/${pathOnly}`.replace(/\/+$/, '') || '/';
|
|
6193
|
+
const segments = normalized === '/' ? [] : normalized.split('/').filter(Boolean);
|
|
6194
|
+
const chain = matchRouteConfigChain(routes, segments, 0, {}, []);
|
|
6195
|
+
if (!chain?.length) {
|
|
6196
|
+
return null;
|
|
6197
|
+
}
|
|
6198
|
+
for (let index = chain.length - 1; index >= 0; index--) {
|
|
6199
|
+
const level = chain[index];
|
|
6200
|
+
if (axpHasTabScope(level.mergedData)) {
|
|
6201
|
+
return level.path;
|
|
6202
|
+
}
|
|
6203
|
+
}
|
|
6204
|
+
return chain[chain.length - 1]?.path ?? null;
|
|
6205
|
+
}
|
|
6206
|
+
function matchRouteConfigChain(routes, segments, startIndex, inheritedData, chain) {
|
|
6207
|
+
let bestMatch = null;
|
|
6208
|
+
for (const route of routes) {
|
|
6209
|
+
if (route.redirectTo) {
|
|
6210
|
+
continue;
|
|
6211
|
+
}
|
|
6212
|
+
const segmentMatch = matchRoutePath(route.path ?? '', segments, startIndex);
|
|
6213
|
+
if (!segmentMatch) {
|
|
6214
|
+
continue;
|
|
6215
|
+
}
|
|
6216
|
+
const pathSegments = [...segments.slice(0, startIndex), ...segments.slice(startIndex, startIndex + segmentMatch.consumed)];
|
|
6217
|
+
const path = pathSegments.length ? `/${pathSegments.join('/')}` : '/';
|
|
6218
|
+
const mergedData = { ...inheritedData, ...(route.data ?? {}) };
|
|
6219
|
+
const level = { mergedData, path };
|
|
6220
|
+
const nextChain = [...chain, level];
|
|
6221
|
+
const nextIndex = startIndex + segmentMatch.consumed;
|
|
6222
|
+
if (route.children?.length) {
|
|
6223
|
+
const childMatch = matchRouteConfigChain(route.children, segments, nextIndex, mergedData, nextChain);
|
|
6224
|
+
if (childMatch && (!bestMatch || childMatch.length > bestMatch.length)) {
|
|
6225
|
+
bestMatch = childMatch;
|
|
6226
|
+
}
|
|
6227
|
+
}
|
|
6228
|
+
if (nextIndex === segments.length) {
|
|
6229
|
+
if (!bestMatch || nextChain.length > bestMatch.length) {
|
|
6230
|
+
bestMatch = nextChain;
|
|
6231
|
+
}
|
|
6232
|
+
}
|
|
6233
|
+
}
|
|
6234
|
+
return bestMatch;
|
|
6235
|
+
}
|
|
6236
|
+
function matchRoutePath(routePath, segments, startIndex) {
|
|
6237
|
+
if (routePath === '**') {
|
|
6238
|
+
return { consumed: Math.max(0, segments.length - startIndex) };
|
|
6239
|
+
}
|
|
6240
|
+
const parts = routePath.split('/').filter(Boolean);
|
|
6241
|
+
if (!parts.length) {
|
|
6242
|
+
return { consumed: 0 };
|
|
6243
|
+
}
|
|
6244
|
+
let consumed = 0;
|
|
6245
|
+
for (const part of parts) {
|
|
6246
|
+
const segment = segments[startIndex + consumed];
|
|
6247
|
+
if (segment === undefined) {
|
|
6248
|
+
return null;
|
|
6249
|
+
}
|
|
6250
|
+
if (part.startsWith(':') || part === segment) {
|
|
6251
|
+
consumed++;
|
|
6252
|
+
continue;
|
|
6253
|
+
}
|
|
6254
|
+
return null;
|
|
6255
|
+
}
|
|
6256
|
+
return { consumed };
|
|
6257
|
+
}
|
|
6258
|
+
//#endregion
|
|
6259
|
+
|
|
6260
|
+
//#region ---- Constants ----
|
|
6261
|
+
const SESSION_STORAGE_PREFIX = 'AXP_TAB_NAV';
|
|
6262
|
+
//#endregion
|
|
6263
|
+
class AXPTabNavigationService {
|
|
6264
|
+
//#endregion
|
|
6265
|
+
//#region ---- Lifecycle ----
|
|
6266
|
+
constructor() {
|
|
6267
|
+
//#region ---- Services & Dependencies ----
|
|
6268
|
+
this.router = inject(Router);
|
|
6269
|
+
this.settingsService = inject(AXPSettingsService);
|
|
6270
|
+
this.deviceService = inject(AXPDeviceService);
|
|
6271
|
+
this.menuService = inject(AXPMenuService);
|
|
6272
|
+
this.homePageService = inject(AXPHomePageService);
|
|
6273
|
+
this.sessionService = inject(AXPSessionService);
|
|
6274
|
+
this.routeCacheRegistry = inject(AXPTabRouteCacheRegistry);
|
|
6275
|
+
this.tabPreviewService = inject(AXPTabPreviewService);
|
|
6276
|
+
this.unsavedChangesConfirm = inject(AXPUnsavedChangesConfirmService);
|
|
6277
|
+
this.destroyRef = inject(DestroyRef);
|
|
6278
|
+
//#endregion
|
|
6279
|
+
//#region ---- State ----
|
|
6280
|
+
this.navigationMode = signal('simple', ...(ngDevMode ? [{ debugName: "navigationMode" }] : /* istanbul ignore next */ []));
|
|
6281
|
+
this.maxOpenTabs = signal(5, ...(ngDevMode ? [{ debugName: "maxOpenTabs" }] : /* istanbul ignore next */ []));
|
|
6282
|
+
this.tabs = signal([], ...(ngDevMode ? [{ debugName: "tabs" }] : /* istanbul ignore next */ []));
|
|
6283
|
+
this.activeTabKey = signal(null, ...(ngDevMode ? [{ debugName: "activeTabKey" }] : /* istanbul ignore next */ []));
|
|
6284
|
+
this.suppressNavigationSync = false;
|
|
6285
|
+
//#endregion
|
|
6286
|
+
//#region ---- Computed ----
|
|
6287
|
+
/** Effective mode: mobile always uses simple navigation. */
|
|
6288
|
+
this.effectiveMode = computed(() => {
|
|
6289
|
+
if (this.deviceService.isSmall()) {
|
|
6290
|
+
return 'simple';
|
|
6291
|
+
}
|
|
6292
|
+
return this.navigationMode();
|
|
6293
|
+
}, ...(ngDevMode ? [{ debugName: "effectiveMode" }] : /* istanbul ignore next */ []));
|
|
6294
|
+
this.isTabBarVisible = computed(() => this.effectiveMode() === 'tabular' && this.tabs().length > 0, ...(ngDevMode ? [{ debugName: "isTabBarVisible" }] : /* istanbul ignore next */ []));
|
|
6295
|
+
this.tabList = this.tabs.asReadonly();
|
|
6296
|
+
this.activeKey = this.activeTabKey.asReadonly();
|
|
6297
|
+
void this.loadSettings();
|
|
6298
|
+
this.bindRouterEvents();
|
|
6299
|
+
this.bindSettingsChanges();
|
|
6300
|
+
this.bindSessionChanges();
|
|
6301
|
+
this.bindRouteCacheSync();
|
|
6302
|
+
}
|
|
6303
|
+
//#endregion
|
|
6304
|
+
//#region ---- Public API ----
|
|
6305
|
+
/** Navigates to a path, opening or activating a tab when tabular mode is active. */
|
|
6306
|
+
async navigate(path, extras) {
|
|
6307
|
+
const normalizedPath = normalizeRouterUrl(path);
|
|
6308
|
+
if (this.effectiveMode() === 'simple' || !axpIsTabEligibleUrl(normalizedPath)) {
|
|
6309
|
+
return axpNavigateAppPath(this.router, normalizedPath, extras);
|
|
6310
|
+
}
|
|
6311
|
+
await this.openOrActivateTab(normalizedPath);
|
|
6312
|
+
return true;
|
|
6313
|
+
}
|
|
6314
|
+
/** Opens a tab or activates an existing tab with the same key. */
|
|
6315
|
+
async openOrActivateTab(url, title) {
|
|
6316
|
+
if (this.effectiveMode() === 'simple') {
|
|
6317
|
+
await axpNavigateAppPath(this.router, url);
|
|
6318
|
+
return;
|
|
6319
|
+
}
|
|
6320
|
+
const normalizedUrl = normalizeRouterUrl(url);
|
|
6321
|
+
if (!axpIsTabEligibleUrl(normalizedUrl)) {
|
|
6322
|
+
await axpNavigateAppPath(this.router, normalizedUrl);
|
|
6323
|
+
return;
|
|
6324
|
+
}
|
|
6325
|
+
await this.ensureHomeTab();
|
|
6326
|
+
if (this.isHomePageUrl(normalizedUrl)) {
|
|
6327
|
+
await this.activateTabKey(AXP_HOME_TAB_KEY, normalizedUrl);
|
|
6328
|
+
return;
|
|
6329
|
+
}
|
|
6330
|
+
const resolvedKey = axpResolveTabKey(this.router, normalizedUrl);
|
|
6331
|
+
const existing = this.findTabForUrl(normalizedUrl);
|
|
6332
|
+
if (existing) {
|
|
6333
|
+
if (existing.key !== resolvedKey) {
|
|
6334
|
+
this.migrateTabKey(existing.key, resolvedKey);
|
|
6335
|
+
}
|
|
6336
|
+
this.updateTabUrl(resolvedKey, normalizedUrl);
|
|
6337
|
+
await this.activateTabKey(resolvedKey, normalizedUrl);
|
|
6338
|
+
return;
|
|
6339
|
+
}
|
|
6340
|
+
// Navigate first so scoped tab keys (`data.tab`) resolve from the live route snapshot.
|
|
6341
|
+
this.suppressNavigationSync = true;
|
|
6342
|
+
try {
|
|
6343
|
+
await axpNavigateAppPath(this.router, normalizedUrl);
|
|
6344
|
+
const key = axpResolveTabKey(this.router);
|
|
6345
|
+
const postNavExisting = this.findTabForUrl(normalizedUrl);
|
|
6346
|
+
if (postNavExisting) {
|
|
6347
|
+
if (postNavExisting.key !== key) {
|
|
6348
|
+
this.migrateTabKey(postNavExisting.key, key);
|
|
6349
|
+
}
|
|
6350
|
+
this.updateTabUrl(key, normalizedUrl);
|
|
6351
|
+
this.touchTab(key);
|
|
6352
|
+
this.activeTabKey.set(key);
|
|
6353
|
+
this.persistSession();
|
|
6354
|
+
return;
|
|
6355
|
+
}
|
|
6356
|
+
this.insertTab(this.createTabDescriptor(normalizedUrl, title, key));
|
|
6357
|
+
this.activeTabKey.set(key);
|
|
6358
|
+
this.persistSession();
|
|
6359
|
+
}
|
|
6360
|
+
finally {
|
|
6361
|
+
this.suppressNavigationSync = false;
|
|
6362
|
+
}
|
|
6363
|
+
}
|
|
6364
|
+
/**
|
|
6365
|
+
* Activates a tab and navigates to its URL. The previously active page is detached and cached by
|
|
6366
|
+
* the reuse strategy, so its state is preserved; this tab's cached instance is reattached when
|
|
6367
|
+
* available instead of being re-rendered.
|
|
6368
|
+
*
|
|
6369
|
+
* @param suspendDeactivateGuard When true (default for tab switches), unsaved-changes prompts are
|
|
6370
|
+
* skipped so switching tabs keeps a dirty page alive. Closing a tab passes false to keep the prompt.
|
|
6371
|
+
*/
|
|
6372
|
+
async activateTabKey(key, urlOverride, suspendDeactivateGuard = true) {
|
|
6373
|
+
const tab = this.tabs().find((item) => item.key === key);
|
|
6374
|
+
if (!tab) {
|
|
6375
|
+
return;
|
|
6376
|
+
}
|
|
6377
|
+
const targetUrl = urlOverride ?? tab.url;
|
|
6378
|
+
this.touchTab(key);
|
|
6379
|
+
this.activeTabKey.set(key);
|
|
6380
|
+
this.persistSession();
|
|
6381
|
+
const navigate = async () => {
|
|
6382
|
+
this.suppressNavigationSync = true;
|
|
6383
|
+
try {
|
|
6384
|
+
await this.router.navigateByUrl(targetUrl);
|
|
6385
|
+
}
|
|
6386
|
+
finally {
|
|
6387
|
+
this.suppressNavigationSync = false;
|
|
6388
|
+
}
|
|
6389
|
+
};
|
|
6390
|
+
if (suspendDeactivateGuard) {
|
|
6391
|
+
await this.routeCacheRegistry.runWithSuspendedDeactivateGuards(navigate);
|
|
6392
|
+
return;
|
|
6393
|
+
}
|
|
6394
|
+
await navigate();
|
|
6395
|
+
}
|
|
6396
|
+
/** Closes a tab and navigates to a neighbor when the active tab is closed. */
|
|
6397
|
+
async closeTab(key) {
|
|
6398
|
+
await this.closeTabs([key]);
|
|
6399
|
+
}
|
|
6400
|
+
/** Closes every closable tab except the given key (pinned tabs are kept). */
|
|
6401
|
+
async closeOtherTabs(keepKey) {
|
|
6402
|
+
const keys = this.tabs()
|
|
6403
|
+
.filter((tab) => tab.closable && !tab.pinned && tab.key !== keepKey)
|
|
6404
|
+
.map((tab) => tab.key);
|
|
6405
|
+
await this.closeTabs(keys);
|
|
6406
|
+
}
|
|
6407
|
+
/** Closes all closable tabs (pinned tabs are kept). */
|
|
6408
|
+
async closeAllTabs() {
|
|
6409
|
+
const keys = this.tabs()
|
|
6410
|
+
.filter((tab) => tab.closable && !tab.pinned)
|
|
6411
|
+
.map((tab) => tab.key);
|
|
6412
|
+
await this.closeTabs(keys);
|
|
6413
|
+
}
|
|
6414
|
+
/** Toggles whether a tab stays pinned in the strip (home tab is always pinned). */
|
|
6415
|
+
toggleTabPinned(key) {
|
|
6416
|
+
if (key === AXP_HOME_TAB_KEY) {
|
|
6417
|
+
return;
|
|
6418
|
+
}
|
|
6419
|
+
this.tabs.update((items) => {
|
|
6420
|
+
const updated = items.map((tab) => {
|
|
6421
|
+
if (tab.key !== key) {
|
|
6422
|
+
return tab;
|
|
6423
|
+
}
|
|
6424
|
+
const pinned = !tab.pinned;
|
|
6425
|
+
return {
|
|
6426
|
+
...tab,
|
|
6427
|
+
pinned,
|
|
6428
|
+
closable: !pinned,
|
|
6429
|
+
};
|
|
6430
|
+
});
|
|
6431
|
+
return this.normalizeTabOrder(updated);
|
|
6432
|
+
});
|
|
6433
|
+
this.persistSession();
|
|
6434
|
+
}
|
|
6435
|
+
/**
|
|
6436
|
+
* Reorders tabs after drag-and-drop. Home stays first; pinned and unpinned tabs cannot cross groups.
|
|
6437
|
+
*/
|
|
6438
|
+
reorderTabs(previousIndex, currentIndex) {
|
|
6439
|
+
if (previousIndex === currentIndex) {
|
|
6440
|
+
return;
|
|
6441
|
+
}
|
|
6442
|
+
const items = this.tabs();
|
|
6443
|
+
if (previousIndex < 0 || currentIndex < 0 || previousIndex >= items.length || currentIndex >= items.length) {
|
|
6444
|
+
return;
|
|
6445
|
+
}
|
|
6446
|
+
const moving = items[previousIndex];
|
|
6447
|
+
if (moving.key === AXP_HOME_TAB_KEY) {
|
|
6448
|
+
return;
|
|
6449
|
+
}
|
|
6450
|
+
const lastPinnedIndex = this.getLastPinnedTabIndex(items);
|
|
6451
|
+
let targetIndex = currentIndex;
|
|
6452
|
+
if (moving.pinned) {
|
|
6453
|
+
if (targetIndex === 0) {
|
|
6454
|
+
return;
|
|
6455
|
+
}
|
|
6456
|
+
targetIndex = Math.max(1, Math.min(targetIndex, lastPinnedIndex));
|
|
6457
|
+
}
|
|
6458
|
+
else {
|
|
6459
|
+
const firstUnpinnedIndex = lastPinnedIndex + 1;
|
|
6460
|
+
if (firstUnpinnedIndex >= items.length) {
|
|
6461
|
+
return;
|
|
6462
|
+
}
|
|
6463
|
+
targetIndex = Math.max(firstUnpinnedIndex, Math.min(targetIndex, items.length - 1));
|
|
6464
|
+
}
|
|
6465
|
+
if (previousIndex === targetIndex) {
|
|
6466
|
+
return;
|
|
6467
|
+
}
|
|
6468
|
+
const next = [...items];
|
|
6469
|
+
const [removed] = next.splice(previousIndex, 1);
|
|
6470
|
+
next.splice(targetIndex, 0, removed);
|
|
6471
|
+
this.tabs.set(next);
|
|
6472
|
+
this.persistSession();
|
|
6473
|
+
}
|
|
6474
|
+
/** Updates a tab title from the current page chrome (e.g. breadcrumb trail). */
|
|
6475
|
+
syncTabTitle(key, title) {
|
|
6476
|
+
if (this.effectiveMode() === 'simple') {
|
|
6477
|
+
return;
|
|
6478
|
+
}
|
|
6479
|
+
if (!this.tabs().some((tab) => tab.key === key)) {
|
|
6480
|
+
return;
|
|
6481
|
+
}
|
|
6482
|
+
const normalized = title.trim();
|
|
6483
|
+
if (!normalized || this.isGenericHomeTitle(normalized)) {
|
|
6484
|
+
return;
|
|
6485
|
+
}
|
|
6486
|
+
this.updateTabTitle(key, normalized);
|
|
6487
|
+
}
|
|
6488
|
+
/** Updates a tab icon from the current page chrome or menu metadata. */
|
|
6489
|
+
syncTabIcon(key, icon) {
|
|
6490
|
+
if (this.effectiveMode() === 'simple') {
|
|
6491
|
+
return;
|
|
6492
|
+
}
|
|
6493
|
+
if (!this.tabs().some((tab) => tab.key === key)) {
|
|
6494
|
+
return;
|
|
6495
|
+
}
|
|
6496
|
+
const normalized = icon.trim();
|
|
6497
|
+
if (!normalized) {
|
|
6498
|
+
return;
|
|
6499
|
+
}
|
|
6500
|
+
this.updateTabIcon(key, normalized);
|
|
6501
|
+
}
|
|
6502
|
+
//#endregion
|
|
6503
|
+
//#region ---- Bulk Tab Close ----
|
|
6504
|
+
/** Closes multiple tabs in one update; navigates once when the active tab is closed. */
|
|
6505
|
+
async closeTabs(keys) {
|
|
6506
|
+
const uniqueKeys = [...new Set(keys)];
|
|
6507
|
+
if (!uniqueKeys.length) {
|
|
6508
|
+
return;
|
|
6509
|
+
}
|
|
6510
|
+
const closableKeySet = new Set(uniqueKeys.filter((key) => {
|
|
6511
|
+
const tab = this.tabs().find((item) => item.key === key);
|
|
6512
|
+
return !!tab?.closable && !tab.pinned;
|
|
6513
|
+
}));
|
|
6514
|
+
if (!closableKeySet.size) {
|
|
6515
|
+
return;
|
|
6516
|
+
}
|
|
6517
|
+
const activeKey = this.activeTabKey();
|
|
6518
|
+
const backgroundKeys = [...closableKeySet].filter((key) => key !== activeKey);
|
|
6519
|
+
for (const key of backgroundKeys) {
|
|
6520
|
+
if (!(await this.confirmBackgroundTabClose(key))) {
|
|
6521
|
+
closableKeySet.delete(key);
|
|
6522
|
+
}
|
|
6523
|
+
}
|
|
6524
|
+
if (!closableKeySet.size) {
|
|
6525
|
+
return;
|
|
6526
|
+
}
|
|
6527
|
+
const currentTabs = this.tabs();
|
|
6528
|
+
const wasActiveClosed = !!activeKey && closableKeySet.has(activeKey);
|
|
6529
|
+
const closedActiveIndex = wasActiveClosed ? currentTabs.findIndex((item) => item.key === activeKey) : -1;
|
|
6530
|
+
const nextTabs = currentTabs.filter((item) => !closableKeySet.has(item.key));
|
|
6531
|
+
if (wasActiveClosed) {
|
|
6532
|
+
const navigated = await this.navigateAwayBeforeClosingActiveTab(closedActiveIndex, nextTabs);
|
|
6533
|
+
if (!navigated) {
|
|
6534
|
+
return;
|
|
6535
|
+
}
|
|
6536
|
+
}
|
|
6537
|
+
this.tabs.set(nextTabs);
|
|
6538
|
+
this.tabPreviewService.clearPreviews(closableKeySet);
|
|
6539
|
+
this.persistSession();
|
|
6540
|
+
}
|
|
6541
|
+
//#endregion
|
|
6542
|
+
//#region ---- Tab Close Helpers ----
|
|
6543
|
+
/**
|
|
6544
|
+
* Prompts before closing a cached background tab. Uses the page's {@link AXPTabCloseConfirm}
|
|
6545
|
+
* hook when available, otherwise falls back to the tab preview dirty flag.
|
|
6546
|
+
*/
|
|
6547
|
+
async confirmBackgroundTabClose(key) {
|
|
6548
|
+
const instance = this.routeCacheRegistry.getComponentInstance(key);
|
|
6549
|
+
if (instance && typeof instance.onBeforeTabClose === 'function') {
|
|
6550
|
+
return instance.onBeforeTabClose();
|
|
6551
|
+
}
|
|
6552
|
+
return this.unsavedChangesConfirm.confirmIfDirty(this.tabPreviewService.isTabDirty(key), {
|
|
6553
|
+
approveLeaveOnConfirm: false,
|
|
6554
|
+
});
|
|
6555
|
+
}
|
|
6556
|
+
//#endregion
|
|
6557
|
+
//#region ---- Tab Close Navigation ----
|
|
6558
|
+
/**
|
|
6559
|
+
* Navigates away from the active tab before it is removed so canDeactivate runs while the tab is still visible.
|
|
6560
|
+
* Returns false when navigation was cancelled (e.g. unsaved-changes confirmation declined).
|
|
6561
|
+
*/
|
|
6562
|
+
async navigateAwayBeforeClosingActiveTab(closedActiveIndex, remainingTabs) {
|
|
6563
|
+
const fallbackIndex = this.resolveFallbackTabIndex(closedActiveIndex, remainingTabs);
|
|
6564
|
+
const fallback = fallbackIndex >= 0 ? remainingTabs[fallbackIndex] : undefined;
|
|
6565
|
+
let targetKey;
|
|
6566
|
+
let targetUrl;
|
|
6567
|
+
if (fallback) {
|
|
6568
|
+
targetKey = fallback.key;
|
|
6569
|
+
targetUrl = fallback.url;
|
|
6570
|
+
}
|
|
6571
|
+
else {
|
|
6572
|
+
await this.ensureHomeTab();
|
|
6573
|
+
const home = this.tabs().find((item) => item.key === AXP_HOME_TAB_KEY);
|
|
6574
|
+
if (!home) {
|
|
6575
|
+
return false;
|
|
6576
|
+
}
|
|
6577
|
+
targetKey = home.key;
|
|
6578
|
+
targetUrl = home.url;
|
|
6579
|
+
}
|
|
6580
|
+
this.suppressNavigationSync = true;
|
|
6581
|
+
try {
|
|
6582
|
+
const navigated = await this.router.navigateByUrl(targetUrl);
|
|
6583
|
+
if (!navigated) {
|
|
6584
|
+
return false;
|
|
6585
|
+
}
|
|
6586
|
+
}
|
|
6587
|
+
finally {
|
|
6588
|
+
this.suppressNavigationSync = false;
|
|
6589
|
+
}
|
|
6590
|
+
this.activeTabKey.set(targetKey);
|
|
6591
|
+
this.touchTab(targetKey);
|
|
6592
|
+
return true;
|
|
6593
|
+
}
|
|
6594
|
+
/** Prefers the tab to the right after removal, then the tab to the left (browser-like). */
|
|
6595
|
+
resolveFallbackTabIndex(closedIndex, remainingTabs) {
|
|
6596
|
+
if (!remainingTabs.length) {
|
|
6597
|
+
return -1;
|
|
6598
|
+
}
|
|
6599
|
+
if (closedIndex < remainingTabs.length) {
|
|
6600
|
+
return closedIndex;
|
|
6601
|
+
}
|
|
6602
|
+
return closedIndex - 1;
|
|
6603
|
+
}
|
|
6604
|
+
//#endregion
|
|
6605
|
+
//#region ---- Route Cache Sync ----
|
|
6606
|
+
/**
|
|
6607
|
+
* Keeps the route cache registry aligned with the current navigation mode and the set of open
|
|
6608
|
+
* tabs. Cached page instances for closed/evicted tabs are destroyed by the registry.
|
|
6609
|
+
*/
|
|
6610
|
+
bindRouteCacheSync() {
|
|
6611
|
+
effect(() => {
|
|
6612
|
+
this.routeCacheRegistry.setEnabled(this.effectiveMode() === 'tabular');
|
|
6613
|
+
this.routeCacheRegistry.syncOpenKeys(this.tabs().map((tab) => tab.key), this.tabs().map((tab) => tab.url));
|
|
6614
|
+
});
|
|
6615
|
+
}
|
|
6616
|
+
//#endregion
|
|
6617
|
+
//#region ---- Router Sync ----
|
|
6618
|
+
bindRouterEvents() {
|
|
6619
|
+
this.router.events
|
|
6620
|
+
.pipe(filter((event) => event instanceof NavigationEnd), takeUntilDestroyed(this.destroyRef))
|
|
6621
|
+
.subscribe((event) => {
|
|
6622
|
+
void this.handleNavigationEnd(event.urlAfterRedirects);
|
|
6623
|
+
});
|
|
6624
|
+
}
|
|
6625
|
+
async handleNavigationEnd(url) {
|
|
6626
|
+
if (this.suppressNavigationSync) {
|
|
6627
|
+
return;
|
|
6628
|
+
}
|
|
6629
|
+
if (this.effectiveMode() === 'simple') {
|
|
6630
|
+
return;
|
|
6631
|
+
}
|
|
6632
|
+
const normalizedUrl = normalizeRouterUrl(url);
|
|
6633
|
+
if (!axpIsTabEligibleUrl(normalizedUrl)) {
|
|
6634
|
+
return;
|
|
6635
|
+
}
|
|
6636
|
+
await this.ensureHomeTab();
|
|
6637
|
+
if (this.isHomePageUrl(normalizedUrl)) {
|
|
6638
|
+
this.activateHomeTabForUrl(normalizedUrl);
|
|
6639
|
+
return;
|
|
6640
|
+
}
|
|
6641
|
+
const key = axpResolveTabKey(this.router, normalizedUrl);
|
|
6642
|
+
const existing = this.findTabForUrl(normalizedUrl);
|
|
6643
|
+
if (existing) {
|
|
6644
|
+
if (existing.key !== key) {
|
|
6645
|
+
this.migrateTabKey(existing.key, key);
|
|
6646
|
+
}
|
|
6647
|
+
this.updateTabUrl(key, normalizedUrl);
|
|
6648
|
+
this.touchTab(key);
|
|
6649
|
+
this.activeTabKey.set(key);
|
|
6650
|
+
this.persistSession();
|
|
6651
|
+
return;
|
|
6652
|
+
}
|
|
6653
|
+
this.insertTab(this.createTabDescriptor(normalizedUrl, undefined, key));
|
|
6654
|
+
this.activeTabKey.set(key);
|
|
6655
|
+
this.persistSession();
|
|
6656
|
+
}
|
|
6657
|
+
//#endregion
|
|
6658
|
+
//#region ---- Settings ----
|
|
6659
|
+
async loadSettings() {
|
|
6660
|
+
const [mode, maxTabs] = await Promise.all([
|
|
6661
|
+
this.settingsService.get(AXPCommonSettings.NavigationMode),
|
|
6662
|
+
this.settingsService.get(AXPCommonSettings.MaxOpenTabs),
|
|
6663
|
+
]);
|
|
6664
|
+
this.navigationMode.set(mode === 'tabular' ? 'tabular' : 'simple');
|
|
6665
|
+
this.maxOpenTabs.set(this.normalizeMaxTabs(maxTabs));
|
|
6666
|
+
await this.restoreSessionIfNeeded();
|
|
6667
|
+
}
|
|
6668
|
+
bindSettingsChanges() {
|
|
6669
|
+
this.settingsService.onChanged.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((event) => {
|
|
6670
|
+
if (event.keys.includes(AXPCommonSettings.NavigationMode)) {
|
|
6671
|
+
const raw = event.values[AXPCommonSettings.NavigationMode];
|
|
6672
|
+
const newMode = raw === 'tabular' ? 'tabular' : 'simple';
|
|
6673
|
+
if (newMode !== this.navigationMode()) {
|
|
6674
|
+
this.navigationMode.set(newMode);
|
|
6675
|
+
if (this.effectiveMode() === 'simple') {
|
|
6676
|
+
this.clearSession();
|
|
6677
|
+
}
|
|
6678
|
+
else {
|
|
6679
|
+
void this.ensureHomeTab().then(() => this.syncCurrentRouteAsTab());
|
|
6680
|
+
}
|
|
6681
|
+
}
|
|
6682
|
+
}
|
|
6683
|
+
if (event.keys.includes(AXPCommonSettings.MaxOpenTabs)) {
|
|
6684
|
+
this.maxOpenTabs.set(this.normalizeMaxTabs(event.values[AXPCommonSettings.MaxOpenTabs]));
|
|
6685
|
+
this.enforceMaxTabs();
|
|
6686
|
+
this.persistSession();
|
|
6687
|
+
}
|
|
6688
|
+
});
|
|
6689
|
+
}
|
|
6690
|
+
bindSessionChanges() {
|
|
6691
|
+
this.sessionService.user$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(() => {
|
|
6692
|
+
void this.restoreSessionIfNeeded();
|
|
6693
|
+
});
|
|
6694
|
+
}
|
|
6695
|
+
//#endregion
|
|
6696
|
+
//#region ---- Tab Management ----
|
|
6697
|
+
async ensureHomeTab() {
|
|
6698
|
+
const homePath = this.resolveHomePath();
|
|
6699
|
+
if (!homePath) {
|
|
6700
|
+
return;
|
|
6701
|
+
}
|
|
6702
|
+
const homeUrl = normalizeRouterUrl(homePath);
|
|
6703
|
+
const homeKey = AXP_HOME_TAB_KEY;
|
|
6704
|
+
const existing = this.tabs().find((tab) => tab.key === homeKey);
|
|
6705
|
+
if (existing) {
|
|
6706
|
+
this.tabs.update((items) => items.map((tab) => tab.key === homeKey
|
|
6707
|
+
? {
|
|
6708
|
+
...tab,
|
|
6709
|
+
url: homeUrl,
|
|
6710
|
+
title: this.isGenericHomeTitle(tab.title) ? this.resolveHomeTitle() : tab.title,
|
|
6711
|
+
icon: this.resolveTabIcon(homeUrl, 'fa-light fa-house'),
|
|
6712
|
+
}
|
|
6713
|
+
: tab));
|
|
6714
|
+
return;
|
|
6715
|
+
}
|
|
6716
|
+
const now = Date.now();
|
|
6717
|
+
const homeTab = {
|
|
6718
|
+
key: homeKey,
|
|
6719
|
+
url: homeUrl,
|
|
6720
|
+
title: this.resolveHomeTitle(),
|
|
6721
|
+
icon: this.resolveTabIcon(homeUrl, 'fa-light fa-house'),
|
|
6722
|
+
pinned: true,
|
|
6723
|
+
closable: false,
|
|
6724
|
+
openedAt: now,
|
|
6725
|
+
lastActivatedAt: now,
|
|
6726
|
+
};
|
|
6727
|
+
this.tabs.update((items) => this.normalizeTabOrder([homeTab, ...items.filter((tab) => tab.key !== homeKey)]));
|
|
6728
|
+
}
|
|
6729
|
+
insertTab(tab) {
|
|
6730
|
+
if (this.isHomePageUrl(tab.url)) {
|
|
6731
|
+
this.activateHomeTabForUrl(tab.url);
|
|
6732
|
+
return;
|
|
6733
|
+
}
|
|
6734
|
+
this.tabs.update((items) => {
|
|
6735
|
+
const withoutDuplicate = items.filter((item) => {
|
|
6736
|
+
if (item.key === tab.key) {
|
|
6737
|
+
return false;
|
|
6738
|
+
}
|
|
6739
|
+
return axpResolveTabKey(this.router, item.url) !== tab.key;
|
|
6740
|
+
});
|
|
6741
|
+
return this.normalizeTabOrder([...withoutDuplicate, tab]);
|
|
6742
|
+
});
|
|
6743
|
+
this.enforceMaxTabs();
|
|
6744
|
+
}
|
|
6745
|
+
/** Home first; preserves session order for the remaining tabs. */
|
|
6746
|
+
ensureHomeTabFirst(items) {
|
|
6747
|
+
const home = items.find((tab) => tab.key === AXP_HOME_TAB_KEY);
|
|
6748
|
+
const others = items.filter((tab) => tab.key !== AXP_HOME_TAB_KEY);
|
|
6749
|
+
return home ? [home, ...others] : items;
|
|
6750
|
+
}
|
|
6751
|
+
getLastPinnedTabIndex(items) {
|
|
6752
|
+
for (let index = items.length - 1; index >= 0; index -= 1) {
|
|
6753
|
+
if (items[index]?.pinned) {
|
|
6754
|
+
return index;
|
|
6755
|
+
}
|
|
6756
|
+
}
|
|
6757
|
+
return 0;
|
|
6758
|
+
}
|
|
6759
|
+
/** Home first, then other pinned tabs, then unpinned tabs (stable by openedAt within each group). */
|
|
6760
|
+
normalizeTabOrder(items) {
|
|
6761
|
+
const home = items.find((tab) => tab.key === AXP_HOME_TAB_KEY);
|
|
6762
|
+
const pinned = items.filter((tab) => tab.key !== AXP_HOME_TAB_KEY && tab.pinned);
|
|
6763
|
+
const unpinned = items.filter((tab) => tab.key !== AXP_HOME_TAB_KEY && !tab.pinned);
|
|
6764
|
+
const byOpenedAt = (a, b) => a.openedAt - b.openedAt;
|
|
6765
|
+
return [...(home ? [home] : []), ...pinned.sort(byOpenedAt), ...unpinned.sort(byOpenedAt)];
|
|
6766
|
+
}
|
|
6767
|
+
enforceMaxTabs() {
|
|
6768
|
+
const limit = this.maxOpenTabs();
|
|
6769
|
+
let current = this.tabs();
|
|
6770
|
+
while (current.length > limit) {
|
|
6771
|
+
const evictionCandidate = [...current]
|
|
6772
|
+
.filter((tab) => tab.closable && !tab.pinned && tab.key !== this.activeTabKey())
|
|
6773
|
+
.filter((tab) => !this.tabPreviewService.isTabDirty(tab.key))
|
|
6774
|
+
.sort((a, b) => a.lastActivatedAt - b.lastActivatedAt)[0];
|
|
6775
|
+
if (!evictionCandidate) {
|
|
6776
|
+
break;
|
|
6777
|
+
}
|
|
6778
|
+
current = current.filter((tab) => tab.key !== evictionCandidate.key);
|
|
6779
|
+
}
|
|
6780
|
+
this.tabs.set(current);
|
|
6781
|
+
}
|
|
6782
|
+
createTabDescriptor(url, title, keyOverride) {
|
|
6783
|
+
const now = Date.now();
|
|
6784
|
+
const key = keyOverride ?? axpResolveTabKey(this.router, url);
|
|
6785
|
+
return {
|
|
6786
|
+
key,
|
|
6787
|
+
url,
|
|
6788
|
+
title: title ?? this.resolveTabTitle(url),
|
|
6789
|
+
icon: this.resolveTabIcon(url),
|
|
6790
|
+
pinned: false,
|
|
6791
|
+
closable: true,
|
|
6792
|
+
openedAt: now,
|
|
6793
|
+
lastActivatedAt: now,
|
|
6794
|
+
};
|
|
6795
|
+
}
|
|
6796
|
+
/** Finds a tab by scoped key or legacy full-path key for the same URL. */
|
|
6797
|
+
findTabForUrl(url) {
|
|
6798
|
+
if (this.isHomePageUrl(url)) {
|
|
6799
|
+
return this.tabs().find((tab) => tab.key === AXP_HOME_TAB_KEY);
|
|
6800
|
+
}
|
|
6801
|
+
const resolvedKey = axpResolveTabKey(this.router, url);
|
|
6802
|
+
return this.tabs().find((tab) => tab.key !== AXP_HOME_TAB_KEY &&
|
|
6803
|
+
(tab.key === resolvedKey || axpResolveTabKey(this.router, tab.url) === resolvedKey));
|
|
6804
|
+
}
|
|
6805
|
+
/** True when the URL is the configured application home page (e.g. dashboard). */
|
|
6806
|
+
isHomePageUrl(url) {
|
|
6807
|
+
const homeUrl = this.resolveHomeUrl();
|
|
6808
|
+
if (!homeUrl) {
|
|
6809
|
+
return false;
|
|
6810
|
+
}
|
|
6811
|
+
return normalizeRouterUrl(url) === homeUrl;
|
|
6812
|
+
}
|
|
6813
|
+
resolveHomeUrl() {
|
|
6814
|
+
const homePath = this.resolveHomePath();
|
|
6815
|
+
if (!homePath) {
|
|
6816
|
+
return null;
|
|
6817
|
+
}
|
|
6818
|
+
return normalizeRouterUrl(homePath);
|
|
6819
|
+
}
|
|
6820
|
+
/** Activates the pinned home tab for a home-page URL instead of opening a duplicate tab. */
|
|
6821
|
+
activateHomeTabForUrl(url) {
|
|
6822
|
+
this.updateTabUrl(AXP_HOME_TAB_KEY, normalizeRouterUrl(url));
|
|
6823
|
+
this.touchTab(AXP_HOME_TAB_KEY);
|
|
6824
|
+
this.activeTabKey.set(AXP_HOME_TAB_KEY);
|
|
6825
|
+
this.persistSession();
|
|
6826
|
+
}
|
|
6827
|
+
/** Drops non-home tabs that duplicate the home page URL after session restore. */
|
|
6828
|
+
removeDuplicateHomeUrlTabs(tabs) {
|
|
6829
|
+
const homeUrl = this.resolveHomeUrl();
|
|
6830
|
+
if (!homeUrl) {
|
|
6831
|
+
return tabs;
|
|
6832
|
+
}
|
|
6833
|
+
return tabs.filter((tab) => tab.key === AXP_HOME_TAB_KEY || normalizeRouterUrl(tab.url) !== homeUrl);
|
|
6834
|
+
}
|
|
6835
|
+
/** Rewrites a tab key when route-scope resolution changes (e.g. profile sub-routes). */
|
|
6836
|
+
migrateTabKey(fromKey, toKey) {
|
|
6837
|
+
if (fromKey === toKey) {
|
|
6838
|
+
return;
|
|
6839
|
+
}
|
|
6840
|
+
if (this.activeTabKey() === fromKey) {
|
|
6841
|
+
this.activeTabKey.set(toKey);
|
|
6842
|
+
}
|
|
6843
|
+
this.tabs.update((items) => {
|
|
6844
|
+
const tab = items.find((item) => item.key === fromKey);
|
|
6845
|
+
if (!tab) {
|
|
6846
|
+
return items;
|
|
6847
|
+
}
|
|
6848
|
+
const without = items.filter((item) => item.key !== fromKey && item.key !== toKey);
|
|
6849
|
+
return this.normalizeTabOrder([...without, { ...tab, key: toKey }]);
|
|
6850
|
+
});
|
|
6851
|
+
}
|
|
6852
|
+
/** Collapses restored tabs that share the same scoped route key. */
|
|
6853
|
+
normalizeRestoredTabKeys(tabs) {
|
|
6854
|
+
const byResolvedKey = new Map();
|
|
6855
|
+
for (const tab of tabs) {
|
|
6856
|
+
if (tab.key === AXP_HOME_TAB_KEY) {
|
|
6857
|
+
byResolvedKey.set(AXP_HOME_TAB_KEY, tab);
|
|
6858
|
+
continue;
|
|
6859
|
+
}
|
|
6860
|
+
const resolvedKey = axpResolveTabKey(this.router, tab.url);
|
|
6861
|
+
const normalized = { ...tab, key: resolvedKey };
|
|
6862
|
+
const existing = byResolvedKey.get(resolvedKey);
|
|
6863
|
+
if (!existing || normalized.lastActivatedAt > existing.lastActivatedAt) {
|
|
6864
|
+
byResolvedKey.set(resolvedKey, normalized);
|
|
6865
|
+
}
|
|
6866
|
+
}
|
|
6867
|
+
return [...byResolvedKey.values()];
|
|
6868
|
+
}
|
|
6869
|
+
updateTabUrl(key, url) {
|
|
6870
|
+
this.tabs.update((items) => items.map((tab) => (tab.key === key ? { ...tab, url } : tab)));
|
|
6871
|
+
}
|
|
6872
|
+
updateTabTitle(key, title) {
|
|
6873
|
+
this.tabs.update((items) => items.map((tab) => (tab.key === key && tab.title !== title ? { ...tab, title } : tab)));
|
|
6874
|
+
this.persistSession();
|
|
6875
|
+
}
|
|
6876
|
+
updateTabIcon(key, icon) {
|
|
6877
|
+
this.tabs.update((items) => items.map((tab) => (tab.key === key && tab.icon !== icon ? { ...tab, icon } : tab)));
|
|
6878
|
+
this.persistSession();
|
|
6879
|
+
}
|
|
6880
|
+
touchTab(key) {
|
|
6881
|
+
const now = Date.now();
|
|
6882
|
+
this.tabs.update((items) => items.map((tab) => (tab.key === key ? { ...tab, lastActivatedAt: now } : tab)));
|
|
6883
|
+
}
|
|
6884
|
+
resolveTabTitle(url, fallback) {
|
|
6885
|
+
const match = axpFindMenuItemByRoute(this.menuService.items(), url);
|
|
6886
|
+
if (match?.item.text) {
|
|
6887
|
+
return match.item.text;
|
|
6888
|
+
}
|
|
6889
|
+
return fallback ?? axpFallbackTabTitle(url);
|
|
6890
|
+
}
|
|
6891
|
+
resolveTabIcon(url, fallback = 'fa-light fa-window') {
|
|
6892
|
+
const match = axpFindMenuItemByRoute(this.menuService.items(), url);
|
|
6893
|
+
if (match?.item.icon?.trim()) {
|
|
6894
|
+
return match.item.icon.trim();
|
|
6895
|
+
}
|
|
6896
|
+
return fallback;
|
|
6897
|
+
}
|
|
6898
|
+
resolveHomePath() {
|
|
6899
|
+
const current = this.homePageService.getCurrent();
|
|
6900
|
+
return current.path ? normalizeRouterUrl(current.path) : '';
|
|
6901
|
+
}
|
|
6902
|
+
resolveHomeTitle() {
|
|
6903
|
+
const current = this.homePageService.getCurrent();
|
|
6904
|
+
return current.title || 'Home';
|
|
6905
|
+
}
|
|
6906
|
+
isGenericHomeTitle(title) {
|
|
6907
|
+
if (title === '@general:terms.interface.navigation.home') {
|
|
6908
|
+
return true;
|
|
6909
|
+
}
|
|
6910
|
+
const homeTitle = this.resolveHomeTitle().trim();
|
|
6911
|
+
return title === homeTitle;
|
|
6912
|
+
}
|
|
6913
|
+
async syncCurrentRouteAsTab() {
|
|
6914
|
+
if (this.effectiveMode() === 'simple') {
|
|
6915
|
+
return;
|
|
6916
|
+
}
|
|
6917
|
+
await this.ensureHomeTab();
|
|
6918
|
+
await this.handleNavigationEnd(this.router.url);
|
|
6919
|
+
}
|
|
6920
|
+
normalizeMaxTabs(value) {
|
|
6921
|
+
const parsed = Number(value);
|
|
6922
|
+
if (!Number.isFinite(parsed)) {
|
|
6923
|
+
return 5;
|
|
6924
|
+
}
|
|
6925
|
+
return Math.min(20, Math.max(2, Math.round(parsed)));
|
|
6926
|
+
}
|
|
6927
|
+
//#endregion
|
|
6928
|
+
//#region ---- Session Persistence ----
|
|
6929
|
+
sessionStorageKey() {
|
|
6930
|
+
const userId = this.sessionService.user?.id;
|
|
6931
|
+
if (!userId) {
|
|
6932
|
+
return null;
|
|
6933
|
+
}
|
|
6934
|
+
return `${SESSION_STORAGE_PREFIX}(${userId})`;
|
|
6935
|
+
}
|
|
6936
|
+
persistSession() {
|
|
6937
|
+
const key = this.sessionStorageKey();
|
|
6938
|
+
if (!key || this.effectiveMode() === 'simple') {
|
|
6939
|
+
return;
|
|
6940
|
+
}
|
|
6941
|
+
const payload = {
|
|
6942
|
+
tabs: cloneDeep(this.tabs()),
|
|
6943
|
+
activeTabKey: this.activeTabKey(),
|
|
6944
|
+
};
|
|
6945
|
+
sessionStorage.setItem(key, JSON.stringify(payload));
|
|
6946
|
+
}
|
|
6947
|
+
clearSession() {
|
|
6948
|
+
const key = this.sessionStorageKey();
|
|
6949
|
+
if (key) {
|
|
6950
|
+
sessionStorage.removeItem(key);
|
|
6951
|
+
}
|
|
6952
|
+
this.tabs.set([]);
|
|
6953
|
+
this.activeTabKey.set(null);
|
|
6954
|
+
this.tabPreviewService.clearAll();
|
|
6955
|
+
}
|
|
6956
|
+
async restoreSessionIfNeeded() {
|
|
6957
|
+
if (this.effectiveMode() === 'simple') {
|
|
6958
|
+
return;
|
|
6959
|
+
}
|
|
6960
|
+
const storageKey = this.sessionStorageKey();
|
|
6961
|
+
if (!storageKey) {
|
|
6962
|
+
await this.ensureHomeTab();
|
|
6963
|
+
await this.syncCurrentRouteAsTab();
|
|
6964
|
+
return;
|
|
6965
|
+
}
|
|
6966
|
+
const raw = sessionStorage.getItem(storageKey);
|
|
6967
|
+
if (!raw) {
|
|
6968
|
+
await this.ensureHomeTab();
|
|
6969
|
+
await this.syncCurrentRouteAsTab();
|
|
6970
|
+
return;
|
|
6971
|
+
}
|
|
6972
|
+
try {
|
|
6973
|
+
const parsed = JSON.parse(raw);
|
|
6974
|
+
const restoredTabs = this.removeDuplicateHomeUrlTabs(this.normalizeRestoredTabKeys(defaultTo(parsed.tabs, []).filter((tab) => tab?.key && tab?.url)));
|
|
6975
|
+
this.tabs.set(this.ensureHomeTabFirst(restoredTabs));
|
|
6976
|
+
await this.ensureHomeTab();
|
|
6977
|
+
const activeKey = parsed.activeTabKey ?? this.tabs()[this.tabs().length - 1]?.key ?? AXP_HOME_TAB_KEY;
|
|
6978
|
+
this.activeTabKey.set(activeKey);
|
|
6979
|
+
const activeTab = this.tabs().find((tab) => tab.key === activeKey);
|
|
6980
|
+
if (activeTab && normalizeRouterUrl(this.router.url) !== normalizeRouterUrl(activeTab.url)) {
|
|
6981
|
+
this.suppressNavigationSync = true;
|
|
6982
|
+
try {
|
|
6983
|
+
await this.router.navigateByUrl(activeTab.url);
|
|
6984
|
+
}
|
|
6985
|
+
finally {
|
|
6986
|
+
this.suppressNavigationSync = false;
|
|
6987
|
+
}
|
|
6988
|
+
}
|
|
6989
|
+
}
|
|
6990
|
+
catch {
|
|
6991
|
+
await this.ensureHomeTab();
|
|
6992
|
+
await this.syncCurrentRouteAsTab();
|
|
6993
|
+
}
|
|
6994
|
+
}
|
|
6995
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPTabNavigationService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
6996
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPTabNavigationService, providedIn: 'root' }); }
|
|
6997
|
+
}
|
|
6998
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPTabNavigationService, decorators: [{
|
|
6999
|
+
type: Injectable,
|
|
7000
|
+
args: [{
|
|
7001
|
+
providedIn: 'root',
|
|
7002
|
+
}]
|
|
7003
|
+
}], ctorParameters: () => [] });
|
|
7004
|
+
|
|
7005
|
+
//TODO Loading, Redirect, Drawer, Show toast
|
|
7006
|
+
const AXPRedirectEvent = createWorkFlowEvent('Redirect Event Fired');
|
|
7007
|
+
const AXPRefreshEvent = createWorkFlowEvent('Refresh Event Fired');
|
|
7008
|
+
const AXPReloadEvent = createWorkFlowEvent('Reload Event Fired');
|
|
7009
|
+
class AXPWorkflowNavigateAction extends AXPWorkflowAction {
|
|
7010
|
+
constructor() {
|
|
7011
|
+
super(...arguments);
|
|
7012
|
+
this.router = inject(Router);
|
|
7013
|
+
this.tabNavigation = inject(AXPTabNavigationService);
|
|
7014
|
+
}
|
|
7015
|
+
async execute(context) {
|
|
7016
|
+
const payload = context.getVariable('payload');
|
|
7017
|
+
if (Array.isArray(payload.commands)) {
|
|
7018
|
+
await this.router.navigate(payload.commands, payload.extras);
|
|
7019
|
+
return;
|
|
7020
|
+
}
|
|
7021
|
+
const path = payload.commands;
|
|
7022
|
+
if (!path) {
|
|
7023
|
+
return;
|
|
7024
|
+
}
|
|
7025
|
+
if (path.toLowerCase().startsWith('http') || payload.target === 'blank') {
|
|
7026
|
+
window.open(path, '_blank');
|
|
7027
|
+
return;
|
|
7028
|
+
}
|
|
7029
|
+
await this.tabNavigation.navigate(path, payload.extras);
|
|
7030
|
+
}
|
|
7031
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPWorkflowNavigateAction, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
7032
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPWorkflowNavigateAction }); }
|
|
7033
|
+
}
|
|
7034
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPWorkflowNavigateAction, decorators: [{
|
|
7035
|
+
type: Injectable
|
|
7036
|
+
}] });
|
|
7037
|
+
class AXPDialogConfirmAction extends AXPWorkflowAction {
|
|
7038
|
+
constructor() {
|
|
7039
|
+
super(...arguments);
|
|
7040
|
+
this.dialogService = inject(AXDialogService);
|
|
7041
|
+
this.translationService = inject(AXTranslationService);
|
|
7042
|
+
this.defaultAction = 'cancel';
|
|
7043
|
+
}
|
|
7044
|
+
async execute(context) {
|
|
7045
|
+
console.log('AXPDialogConfirmAction', this.title, this.message, this.type);
|
|
7046
|
+
context.setOutput('result', false);
|
|
7047
|
+
const dialogResult = await this.dialogService.confirm(await this.translationService.translateAsync(this.title), await this.translationService.translateAsync(this.message), (await this.translationService.translateAsync(this.type)), 'horizontal', false, 'cancel');
|
|
7048
|
+
context.setOutput('result', dialogResult.result);
|
|
7049
|
+
}
|
|
7050
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPDialogConfirmAction, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
7051
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPDialogConfirmAction }); }
|
|
7052
|
+
}
|
|
7053
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPDialogConfirmAction, decorators: [{
|
|
7054
|
+
type: Injectable
|
|
7055
|
+
}] });
|
|
7056
|
+
class AXPToastAction extends AXPWorkflowAction {
|
|
7057
|
+
constructor() {
|
|
7058
|
+
super(...arguments);
|
|
7059
|
+
this.toastService = inject(AXToastService);
|
|
7060
|
+
this.translationService = inject(AXTranslationService);
|
|
7061
|
+
this.settingsService = inject(AXPSettingsService);
|
|
5838
7062
|
}
|
|
5839
7063
|
async execute(context) {
|
|
5840
7064
|
const showResult = await this.settingsService.get(AXPCommonSettings.EnableOperationToasts);
|
|
@@ -5869,7 +7093,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
5869
7093
|
class AXPWorkflowRouterNavigateAction extends AXPWorkflowAction {
|
|
5870
7094
|
constructor() {
|
|
5871
7095
|
super(...arguments);
|
|
5872
|
-
this.
|
|
7096
|
+
this.tabNavigation = inject(AXPTabNavigationService);
|
|
5873
7097
|
}
|
|
5874
7098
|
async execute(context) {
|
|
5875
7099
|
const command = context.getVariable();
|
|
@@ -5882,7 +7106,7 @@ class AXPWorkflowRouterNavigateAction extends AXPWorkflowAction {
|
|
|
5882
7106
|
window.open(path, '_blank');
|
|
5883
7107
|
}
|
|
5884
7108
|
else {
|
|
5885
|
-
await
|
|
7109
|
+
await this.tabNavigation.navigate(path, command.options.extras);
|
|
5886
7110
|
}
|
|
5887
7111
|
}
|
|
5888
7112
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPWorkflowRouterNavigateAction, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
@@ -5956,13 +7180,14 @@ class AXPCommonModule {
|
|
|
5956
7180
|
/**
|
|
5957
7181
|
* @ignore
|
|
5958
7182
|
*/
|
|
5959
|
-
constructor(instances, _keyboardShortcutsHelp) {
|
|
7183
|
+
constructor(instances, _keyboardShortcutsHelp, formatterRegistry) {
|
|
7184
|
+
formatterRegistry.register(AXPTranslateFormatter);
|
|
5960
7185
|
instances.forEach((f) => {
|
|
5961
7186
|
f();
|
|
5962
7187
|
});
|
|
5963
7188
|
}
|
|
5964
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPCommonModule, deps: [{ token: 'AXPCommonModuleFactory' }, { token: AXPKeyboardShortcutsHelpService }], target: i0.ɵɵFactoryTarget.NgModule }); }
|
|
5965
|
-
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.2.9", ngImport: i0, type: AXPCommonModule, imports: [
|
|
7189
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPCommonModule, deps: [{ token: 'AXPCommonModuleFactory' }, { token: AXPKeyboardShortcutsHelpService }, { token: i2$1.AXFormatterRegistryService }], target: i0.ɵɵFactoryTarget.NgModule }); }
|
|
7190
|
+
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.2.9", ngImport: i0, type: AXPCommonModule, imports: [i3$1.AXPComponentSlotModule, i4$2.AXPWorkflowModule, AXPopupModule,
|
|
5966
7191
|
AXDateTimeModule,
|
|
5967
7192
|
AXToastModule,
|
|
5968
7193
|
AXPModuleManifestModule], exports: [RouterModule] }); }
|
|
@@ -6009,7 +7234,7 @@ class AXPCommonModule {
|
|
|
6009
7234
|
provide: AXP_SETTING_DEFINITION_PROVIDER,
|
|
6010
7235
|
useFactory: async () => {
|
|
6011
7236
|
const injector = inject(Injector);
|
|
6012
|
-
const provider = (await import('./acorex-platform-common-common-settings.provider-
|
|
7237
|
+
const provider = (await import('./acorex-platform-common-common-settings.provider-ChoT4m4k.mjs')).AXPCommonSettingProvider;
|
|
6013
7238
|
return new provider(injector);
|
|
6014
7239
|
},
|
|
6015
7240
|
multi: true,
|
|
@@ -6110,7 +7335,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
6110
7335
|
provide: AXP_SETTING_DEFINITION_PROVIDER,
|
|
6111
7336
|
useFactory: async () => {
|
|
6112
7337
|
const injector = inject(Injector);
|
|
6113
|
-
const provider = (await import('./acorex-platform-common-common-settings.provider-
|
|
7338
|
+
const provider = (await import('./acorex-platform-common-common-settings.provider-ChoT4m4k.mjs')).AXPCommonSettingProvider;
|
|
6114
7339
|
return new provider(injector);
|
|
6115
7340
|
},
|
|
6116
7341
|
multi: true,
|
|
@@ -6120,7 +7345,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
6120
7345
|
}], ctorParameters: () => [{ type: undefined, decorators: [{
|
|
6121
7346
|
type: Inject,
|
|
6122
7347
|
args: ['AXPCommonModuleFactory']
|
|
6123
|
-
}] }, { type: AXPKeyboardShortcutsHelpService }] });
|
|
7348
|
+
}] }, { type: AXPKeyboardShortcutsHelpService }, { type: i2$1.AXFormatterRegistryService }] });
|
|
6124
7349
|
|
|
6125
7350
|
const ENVIRONMENT = new InjectionToken('ENVIRONMENT');
|
|
6126
7351
|
const AXP_PLATFORM_CONFIG_TOKEN = new InjectionToken('AXP_PLATFORM_CONFIG_TOKEN', {
|
|
@@ -6335,18 +7560,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
6335
7560
|
type: Injectable
|
|
6336
7561
|
}] });
|
|
6337
7562
|
|
|
6338
|
-
/**
|
|
6339
|
-
* Status Definition Types
|
|
6340
|
-
* Defines the structure for status definitions and their transitions
|
|
6341
|
-
*/
|
|
6342
|
-
/**
|
|
6343
|
-
* Abstract class for status providers
|
|
6344
|
-
* Similar to AXPTaskBadgeProvider pattern
|
|
6345
|
-
*/
|
|
6346
|
-
class AXPStatusProvider {
|
|
6347
|
-
}
|
|
6348
|
-
//#endregion
|
|
6349
|
-
|
|
6350
7563
|
//#region ---- Injection Token ----
|
|
6351
7564
|
/**
|
|
6352
7565
|
* Injection token for status providers
|
|
@@ -6558,356 +7771,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
6558
7771
|
args: [{ providedIn: 'root' }]
|
|
6559
7772
|
}] });
|
|
6560
7773
|
|
|
6561
|
-
|
|
6562
|
-
|
|
6563
|
-
* These are the predefined status values used across the platform
|
|
6564
|
-
*/
|
|
6565
|
-
var AXPSystemStatusType;
|
|
6566
|
-
(function (AXPSystemStatusType) {
|
|
6567
|
-
AXPSystemStatusType["Todo"] = "todo";
|
|
6568
|
-
AXPSystemStatusType["InProgress"] = "in-progress";
|
|
6569
|
-
AXPSystemStatusType["InReview"] = "in-review";
|
|
6570
|
-
AXPSystemStatusType["Open"] = "open";
|
|
6571
|
-
AXPSystemStatusType["Closed"] = "closed";
|
|
6572
|
-
AXPSystemStatusType["Blocked"] = "blocked";
|
|
6573
|
-
AXPSystemStatusType["Done"] = "done";
|
|
6574
|
-
AXPSystemStatusType["Cancelled"] = "cancelled";
|
|
6575
|
-
AXPSystemStatusType["Draft"] = "draft";
|
|
6576
|
-
AXPSystemStatusType["PendingReview"] = "pending-review";
|
|
6577
|
-
AXPSystemStatusType["WaitingSignOff"] = "waiting-sign-off";
|
|
6578
|
-
AXPSystemStatusType["WaitingApproval"] = "waiting-approval";
|
|
6579
|
-
AXPSystemStatusType["Approved"] = "approved";
|
|
6580
|
-
AXPSystemStatusType["Published"] = "published";
|
|
6581
|
-
AXPSystemStatusType["Archived"] = "archived";
|
|
6582
|
-
AXPSystemStatusType["Rejected"] = "rejected";
|
|
6583
|
-
AXPSystemStatusType["Active"] = "active";
|
|
6584
|
-
AXPSystemStatusType["Inactive"] = "inactive";
|
|
6585
|
-
AXPSystemStatusType["Pending"] = "pending";
|
|
6586
|
-
AXPSystemStatusType["Completed"] = "completed";
|
|
6587
|
-
AXPSystemStatusType["Suspended"] = "suspended";
|
|
6588
|
-
AXPSystemStatusType["Failed"] = "failed";
|
|
6589
|
-
AXPSystemStatusType["Review"] = "review";
|
|
6590
|
-
AXPSystemStatusType["Expired"] = "expired";
|
|
6591
|
-
})(AXPSystemStatusType || (AXPSystemStatusType = {}));
|
|
6592
|
-
const i18n = (key) => `@general:statuses.${key}`;
|
|
6593
|
-
/**
|
|
6594
|
-
* Standard system status definitions
|
|
6595
|
-
* Provides consistent status definitions across the platform
|
|
6596
|
-
* Uses AXPStatusDefinition interface from status-definition.types.ts
|
|
6597
|
-
*/
|
|
6598
|
-
const AXPSystemStatuses = Object.freeze({
|
|
6599
|
-
Open: {
|
|
6600
|
-
name: AXPSystemStatusType.Open,
|
|
6601
|
-
title: i18n('open.title'),
|
|
6602
|
-
icon: 'fa-light fa-circle',
|
|
6603
|
-
color: 'secondary',
|
|
6604
|
-
description: i18n('open.description'),
|
|
6605
|
-
order: 1,
|
|
6606
|
-
isInitial: true,
|
|
6607
|
-
isFinal: false,
|
|
6608
|
-
},
|
|
6609
|
-
Closed: {
|
|
6610
|
-
name: AXPSystemStatusType.Closed,
|
|
6611
|
-
title: i18n('closed.title'),
|
|
6612
|
-
icon: 'fa-light fa-circle',
|
|
6613
|
-
color: 'danger',
|
|
6614
|
-
description: i18n('closed.description'),
|
|
6615
|
-
order: 2,
|
|
6616
|
-
isInitial: false,
|
|
6617
|
-
isFinal: true,
|
|
6618
|
-
},
|
|
6619
|
-
Todo: {
|
|
6620
|
-
name: AXPSystemStatusType.Todo,
|
|
6621
|
-
title: i18n('todo.title'),
|
|
6622
|
-
icon: 'fa-light fa-circle',
|
|
6623
|
-
color: 'secondary',
|
|
6624
|
-
description: i18n('todo.description'),
|
|
6625
|
-
order: 1,
|
|
6626
|
-
isInitial: true,
|
|
6627
|
-
isFinal: false,
|
|
6628
|
-
},
|
|
6629
|
-
InProgress: {
|
|
6630
|
-
name: AXPSystemStatusType.InProgress,
|
|
6631
|
-
title: i18n('in-progress.title'),
|
|
6632
|
-
icon: 'fa-light fa-play',
|
|
6633
|
-
color: 'info',
|
|
6634
|
-
description: i18n('in-progress.description'),
|
|
6635
|
-
order: 2,
|
|
6636
|
-
isInitial: false,
|
|
6637
|
-
isFinal: false,
|
|
6638
|
-
},
|
|
6639
|
-
InReview: {
|
|
6640
|
-
name: AXPSystemStatusType.InReview,
|
|
6641
|
-
title: i18n('in-review.title'),
|
|
6642
|
-
icon: 'fa-light fa-eye',
|
|
6643
|
-
color: 'info',
|
|
6644
|
-
description: i18n('in-review.description'),
|
|
6645
|
-
order: 3,
|
|
6646
|
-
isInitial: false,
|
|
6647
|
-
isFinal: false,
|
|
6648
|
-
},
|
|
6649
|
-
Blocked: {
|
|
6650
|
-
name: AXPSystemStatusType.Blocked,
|
|
6651
|
-
title: i18n('blocked.title'),
|
|
6652
|
-
icon: 'fa-light fa-ban',
|
|
6653
|
-
color: 'danger',
|
|
6654
|
-
description: i18n('blocked.description'),
|
|
6655
|
-
order: 4,
|
|
6656
|
-
isInitial: false,
|
|
6657
|
-
isFinal: false,
|
|
6658
|
-
},
|
|
6659
|
-
Done: {
|
|
6660
|
-
name: AXPSystemStatusType.Done,
|
|
6661
|
-
title: i18n('done.title'),
|
|
6662
|
-
icon: 'fa-light fa-check-circle',
|
|
6663
|
-
color: 'success',
|
|
6664
|
-
description: i18n('done.description'),
|
|
6665
|
-
order: 5,
|
|
6666
|
-
isInitial: false,
|
|
6667
|
-
isFinal: true,
|
|
6668
|
-
},
|
|
6669
|
-
Cancelled: {
|
|
6670
|
-
name: AXPSystemStatusType.Cancelled,
|
|
6671
|
-
title: i18n('cancelled.title'),
|
|
6672
|
-
icon: 'fa-light fa-times-circle',
|
|
6673
|
-
color: 'neutral',
|
|
6674
|
-
description: i18n('cancelled.description'),
|
|
6675
|
-
order: 6,
|
|
6676
|
-
isInitial: false,
|
|
6677
|
-
isFinal: true,
|
|
6678
|
-
},
|
|
6679
|
-
Draft: {
|
|
6680
|
-
name: AXPSystemStatusType.Draft,
|
|
6681
|
-
title: i18n('draft.title'),
|
|
6682
|
-
icon: 'fa-light fa-file-pen',
|
|
6683
|
-
color: 'neutral',
|
|
6684
|
-
description: i18n('draft.description'),
|
|
6685
|
-
order: 1,
|
|
6686
|
-
isInitial: true,
|
|
6687
|
-
isFinal: false,
|
|
6688
|
-
},
|
|
6689
|
-
PendingReview: {
|
|
6690
|
-
name: AXPSystemStatusType.PendingReview,
|
|
6691
|
-
title: i18n('pending-review.title'),
|
|
6692
|
-
icon: 'fa-light fa-clock',
|
|
6693
|
-
color: 'warning',
|
|
6694
|
-
description: i18n('pending-review.description'),
|
|
6695
|
-
order: 2,
|
|
6696
|
-
isInitial: false,
|
|
6697
|
-
isFinal: false,
|
|
6698
|
-
},
|
|
6699
|
-
WaitingSignOff: {
|
|
6700
|
-
name: AXPSystemStatusType.WaitingSignOff,
|
|
6701
|
-
title: i18n('waiting-sign-off.title'),
|
|
6702
|
-
icon: 'fa-light fa-clock',
|
|
6703
|
-
color: 'warning',
|
|
6704
|
-
description: i18n('waiting-sign-off.description'),
|
|
6705
|
-
order: 3,
|
|
6706
|
-
isInitial: false,
|
|
6707
|
-
isFinal: false,
|
|
6708
|
-
},
|
|
6709
|
-
WaitingApproval: {
|
|
6710
|
-
name: AXPSystemStatusType.WaitingApproval,
|
|
6711
|
-
title: i18n('waiting-approval.title'),
|
|
6712
|
-
icon: 'fa-light fa-clock',
|
|
6713
|
-
color: 'warning',
|
|
6714
|
-
description: i18n('waiting-approval.description'),
|
|
6715
|
-
order: 1,
|
|
6716
|
-
isInitial: true,
|
|
6717
|
-
isFinal: false,
|
|
6718
|
-
},
|
|
6719
|
-
Approved: {
|
|
6720
|
-
name: AXPSystemStatusType.Approved,
|
|
6721
|
-
title: i18n('approved.title'),
|
|
6722
|
-
icon: 'fa-light fa-check-circle',
|
|
6723
|
-
color: 'success',
|
|
6724
|
-
description: i18n('approved.description'),
|
|
6725
|
-
order: 3,
|
|
6726
|
-
isInitial: false,
|
|
6727
|
-
isFinal: false,
|
|
6728
|
-
},
|
|
6729
|
-
Published: {
|
|
6730
|
-
name: AXPSystemStatusType.Published,
|
|
6731
|
-
title: i18n('published.title'),
|
|
6732
|
-
icon: 'fa-light fa-globe',
|
|
6733
|
-
color: 'primary',
|
|
6734
|
-
description: i18n('published.description'),
|
|
6735
|
-
order: 4,
|
|
6736
|
-
isInitial: false,
|
|
6737
|
-
isFinal: false,
|
|
6738
|
-
},
|
|
6739
|
-
Archived: {
|
|
6740
|
-
name: AXPSystemStatusType.Archived,
|
|
6741
|
-
title: i18n('archived.title'),
|
|
6742
|
-
icon: 'fa-light fa-archive',
|
|
6743
|
-
color: 'danger',
|
|
6744
|
-
description: i18n('archived.description'),
|
|
6745
|
-
order: 5,
|
|
6746
|
-
isInitial: false,
|
|
6747
|
-
isFinal: true,
|
|
6748
|
-
},
|
|
6749
|
-
Rejected: {
|
|
6750
|
-
name: AXPSystemStatusType.Rejected,
|
|
6751
|
-
title: i18n('rejected.title'),
|
|
6752
|
-
icon: 'fa-light fa-times-circle',
|
|
6753
|
-
color: 'danger',
|
|
6754
|
-
description: i18n('rejected.description'),
|
|
6755
|
-
order: 6,
|
|
6756
|
-
isInitial: false,
|
|
6757
|
-
isFinal: true,
|
|
6758
|
-
},
|
|
6759
|
-
Active: {
|
|
6760
|
-
name: AXPSystemStatusType.Active,
|
|
6761
|
-
title: i18n('active.title'),
|
|
6762
|
-
icon: 'fa-light fa-check-circle',
|
|
6763
|
-
color: 'success',
|
|
6764
|
-
description: i18n('active.description'),
|
|
6765
|
-
order: 1,
|
|
6766
|
-
isInitial: false,
|
|
6767
|
-
isFinal: false,
|
|
6768
|
-
},
|
|
6769
|
-
Inactive: {
|
|
6770
|
-
name: AXPSystemStatusType.Inactive,
|
|
6771
|
-
title: i18n('inactive.title'),
|
|
6772
|
-
icon: 'fa-light fa-circle',
|
|
6773
|
-
color: 'secondary',
|
|
6774
|
-
description: i18n('inactive.description'),
|
|
6775
|
-
order: 2,
|
|
6776
|
-
isInitial: false,
|
|
6777
|
-
isFinal: false,
|
|
6778
|
-
},
|
|
6779
|
-
Pending: {
|
|
6780
|
-
name: AXPSystemStatusType.Pending,
|
|
6781
|
-
title: i18n('pending.title'),
|
|
6782
|
-
icon: 'fa-light fa-clock',
|
|
6783
|
-
color: 'warning',
|
|
6784
|
-
description: i18n('pending.description'),
|
|
6785
|
-
order: 1,
|
|
6786
|
-
isInitial: true,
|
|
6787
|
-
isFinal: false,
|
|
6788
|
-
},
|
|
6789
|
-
Completed: {
|
|
6790
|
-
name: AXPSystemStatusType.Completed,
|
|
6791
|
-
title: i18n('completed.title'),
|
|
6792
|
-
icon: 'fa-light fa-check-circle',
|
|
6793
|
-
color: 'success',
|
|
6794
|
-
description: i18n('completed.description'),
|
|
6795
|
-
order: 10,
|
|
6796
|
-
isInitial: false,
|
|
6797
|
-
isFinal: true,
|
|
6798
|
-
},
|
|
6799
|
-
Suspended: {
|
|
6800
|
-
name: AXPSystemStatusType.Suspended,
|
|
6801
|
-
title: i18n('suspended.title'),
|
|
6802
|
-
icon: 'fa-light fa-pause-circle',
|
|
6803
|
-
color: 'neutral',
|
|
6804
|
-
description: i18n('suspended.description'),
|
|
6805
|
-
order: 5,
|
|
6806
|
-
isInitial: false,
|
|
6807
|
-
isFinal: false,
|
|
6808
|
-
},
|
|
6809
|
-
Failed: {
|
|
6810
|
-
name: AXPSystemStatusType.Failed,
|
|
6811
|
-
title: i18n('failed.title'),
|
|
6812
|
-
icon: 'fa-light fa-xmark-circle',
|
|
6813
|
-
color: 'danger',
|
|
6814
|
-
description: i18n('failed.description'),
|
|
6815
|
-
order: 10,
|
|
6816
|
-
isInitial: false,
|
|
6817
|
-
isFinal: true,
|
|
6818
|
-
},
|
|
6819
|
-
Review: {
|
|
6820
|
-
name: AXPSystemStatusType.Review,
|
|
6821
|
-
title: i18n('review.title'),
|
|
6822
|
-
icon: 'fa-light fa-eye',
|
|
6823
|
-
color: 'info',
|
|
6824
|
-
description: i18n('review.description'),
|
|
6825
|
-
order: 3,
|
|
6826
|
-
},
|
|
6827
|
-
Expired: {
|
|
6828
|
-
name: AXPSystemStatusType.Expired,
|
|
6829
|
-
title: i18n('expired.title'),
|
|
6830
|
-
icon: 'fa-light fa-clock',
|
|
6831
|
-
color: 'danger',
|
|
6832
|
-
description: i18n('expired.description'),
|
|
6833
|
-
order: 10,
|
|
6834
|
-
isInitial: false,
|
|
6835
|
-
isFinal: true,
|
|
6836
|
-
},
|
|
6837
|
-
});
|
|
6838
|
-
/**
|
|
6839
|
-
* Get system status definition by type
|
|
6840
|
-
* @param type - Status type
|
|
6841
|
-
* @returns System status definition or undefined
|
|
6842
|
-
*/
|
|
6843
|
-
function getSystemStatus(type) {
|
|
6844
|
-
return Object.values(AXPSystemStatuses).find(status => status.name === type);
|
|
6845
|
-
}
|
|
6846
|
-
/**
|
|
6847
|
-
* Resolves the visual appearance (color and icon) for status
|
|
6848
|
-
* using the system statuses from the core module.
|
|
6849
|
-
* @param statusType - Status type string
|
|
6850
|
-
* @returns Color and icon for the status
|
|
6851
|
-
*/
|
|
6852
|
-
function resolveStatusLook(statusType) {
|
|
6853
|
-
// Try to get system status first
|
|
6854
|
-
const systemStatusType = statusType;
|
|
6855
|
-
if (systemStatusType) {
|
|
6856
|
-
const systemStatus = getSystemStatus(systemStatusType);
|
|
6857
|
-
if (systemStatus) {
|
|
6858
|
-
// Convert hex color to semantic color type if needed
|
|
6859
|
-
// For now, return as-is and let the UI handle it
|
|
6860
|
-
return {
|
|
6861
|
-
color: systemStatus.color,
|
|
6862
|
-
icon: systemStatus.icon || ''
|
|
6863
|
-
};
|
|
6864
|
-
}
|
|
6865
|
-
}
|
|
6866
|
-
// Fallback for unknown statuses
|
|
6867
|
-
return {
|
|
6868
|
-
color: 'secondary',
|
|
6869
|
-
icon: ''
|
|
6870
|
-
};
|
|
6871
|
-
}
|
|
6872
|
-
/**
|
|
6873
|
-
* Resolves the complete status information (title, description, icon, color) for a status
|
|
6874
|
-
* using the system statuses from the core module.
|
|
6875
|
-
* @param statusType - Status type string
|
|
6876
|
-
* @returns Complete status information as AXPStatusDefinition
|
|
6877
|
-
*/
|
|
6878
|
-
function getStatusInfo(statusType) {
|
|
6879
|
-
const systemStatusType = statusType;
|
|
6880
|
-
if (systemStatusType) {
|
|
6881
|
-
const systemStatus = getSystemStatus(systemStatusType);
|
|
6882
|
-
if (systemStatus) {
|
|
6883
|
-
return systemStatus;
|
|
6884
|
-
}
|
|
6885
|
-
}
|
|
6886
|
-
// Fallback for unknown statuses
|
|
6887
|
-
return {
|
|
6888
|
-
name: statusType,
|
|
6889
|
-
title: statusType,
|
|
6890
|
-
description: `Status: ${statusType}`,
|
|
6891
|
-
icon: '',
|
|
6892
|
-
color: 'secondary'
|
|
6893
|
-
};
|
|
6894
|
-
}
|
|
6895
|
-
/**
|
|
6896
|
-
* Get system status definition directly (no conversion needed since it's already AXPStatusDefinition)
|
|
6897
|
-
* @param statusType - Status type
|
|
6898
|
-
* @param overrides - Optional overrides for the status definition
|
|
6899
|
-
* @returns AXPStatusDefinition
|
|
6900
|
-
*/
|
|
6901
|
-
function systemStatusToDefinition(statusType, overrides) {
|
|
6902
|
-
const systemStatus = getSystemStatus(statusType);
|
|
6903
|
-
if (!systemStatus) {
|
|
6904
|
-
throw new Error(`System status ${statusType} not found`);
|
|
6905
|
-
}
|
|
6906
|
-
return {
|
|
6907
|
-
...systemStatus,
|
|
6908
|
-
...overrides,
|
|
6909
|
-
};
|
|
6910
|
-
}
|
|
7774
|
+
//#region ---- Types ----
|
|
7775
|
+
//#endregion
|
|
6911
7776
|
|
|
6912
7777
|
class AXPClipBoardService {
|
|
6913
7778
|
constructor() {
|
|
@@ -7590,5 +8455,5 @@ class AXPVersioningService {
|
|
|
7590
8455
|
* Generated bundle index. Do not edit.
|
|
7591
8456
|
*/
|
|
7592
8457
|
|
|
7593
|
-
export { ALL_DEFAULT_OPERATORS, AXMWorkflowErrorHandler, AXPAppVersionService, AXPBackgroundOperationCompletionService, AXPBackgroundOperationEntityStoreService, AXPBackgroundOperationHandler, AXPBackgroundOperationNotificationService, AXPBackgroundOperationProvider, AXPBackgroundOperationRootConfig, AXPBackgroundOperationService, AXPBackgroundOperationSlotComponent, AXPCleanNestedFilters, AXPClipBoardService, AXPCommonModule, AXPCommonSettings, AXPCustomOperatorService, AXPCustomOperatorServiceImpl, AXPDataProvider, AXPDebugService, AXPDefaultMultiLanguageConfigService, AXPDialogConfirmAction,
|
|
8458
|
+
export { ALL_DEFAULT_OPERATORS, AXMWorkflowErrorHandler, AXPAppVersionService, AXPBackgroundOperationCompletionService, AXPBackgroundOperationEntityStoreService, AXPBackgroundOperationHandler, AXPBackgroundOperationNotificationService, AXPBackgroundOperationProvider, AXPBackgroundOperationRootConfig, AXPBackgroundOperationService, AXPBackgroundOperationSlotComponent, AXPCleanNestedFilters, AXPClipBoardService, AXPCommonModule, AXPCommonSettings, AXPCustomOperatorService, AXPCustomOperatorServiceImpl, AXPDataProvider, AXPDebugService, AXPDefaultMultiLanguageConfigService, AXPDialogConfirmAction, AXPErrorHandlerRegistryService, AXPExportService, AXPFileActionsService, AXPFileStorageEvaluatorScopeProvider, AXPFileStorageService, AXPFileTypeProviderService, AXPFilterOperatorMiddlewareService, AXPFilterOperatorMiddlewareServiceImpl, AXPFooterTextSlotComponent, AXPGlobalErrorHandler, AXPHomePageModule, AXPHomePageService, AXPKeyboardShortcutsHelpComponent, AXPKeyboardShortcutsHelpService, AXPLockService, AXPMenuItemsDataSourceDefinition, AXPMenuKeyboardShortcutService, AXPMenuMiddlewareRegistry, AXPMenuProviderService, AXPMenuRouteAccessService, AXPMenuRouteGuard, AXPMenuSearchDefinitionProvider, AXPMenuSearchProvider, AXPMenuService, AXPMenuVisibilityService, AXPNavBarSlotComponent, AXPNavigateWorkflow, AXPNotFoundCatchAllRoute, AXPNotFoundError, AXPOutputContractTranscriptSegmentService, AXPPlatformCommonDataSourceKeys, AXPPlatformDefaultConfigs, AXPRedirectEvent, AXPRefreshEvent, AXPRegionalCalendarService, AXPReloadAction, AXPReloadEvent, AXPSearchCommandProvider, AXPSearchDefinitionActionBuilder, AXPSearchDefinitionBuilder, AXPSearchDefinitionProviderContext, AXPSearchDefinitionProviderService, AXPSearchService, AXPSettingDefaultValuesAggregatorService, AXPSettingDefinitionGroupBuilder, AXPSettingDefinitionProviderContext, AXPSettingDefinitionProviderService, AXPSettingDefinitionSectionBuilder, AXPSettingsEvaluatorScopeProvider, AXPSettingsService, AXPStatusDefinitionProviderService, AXPStickyDirective, AXPTabNavigationService, AXPTabPreviewService, AXPTabRouteCacheRegistry, AXPToastAction, AXPTokenDefinitionService, AXPTokenEvaluatorScopeProvider, AXPTranslateFormatter, AXPUnsavedChangesConfirmService, AXPUnsavedChangesPopstateService, AXPVersioningService, AXPWorkflowNavigateAction, AXPWorkflowRouterNavigateAction, AXP_AGENT_OUTPUT_CONTRACT_DEFAULT_STRUCTURED_RETRIES, AXP_APP_VERSION_PROVIDER, AXP_BACKGROUND_OPERATION_ENTITY_STORAGE, AXP_BACKGROUND_OPERATION_HANDLERS, AXP_BACKGROUND_OPERATION_PROVIDERS, AXP_FILE_ACTION_PROVIDER, AXP_FILE_TYPE_INFO_PROVIDER, AXP_HOME_PAGES, AXP_HOME_PAGE_DEFAULT_KEY, AXP_HOME_TAB_KEY, AXP_IN_APP_NOTIFICATION_SERVICE, AXP_LOCALE_MANAGEMENT_PORT, AXP_MENU_MIDDLEWARE, AXP_MENU_PROVIDER, AXP_NOT_FOUND_CATCH_ALL_ROUTE, AXP_NOT_FOUND_ROUTE, AXP_OUTPUT_CONTRACT_TRANSCRIPT_SEGMENT_PROVIDER, AXP_PLATFORM_CONFIG_TOKEN, AXP_PROTECTED_ROUTE_GUARDS, AXP_ROOT_CONFIG_TOKEN, AXP_SEARCH_DEFINITION_LAZY_RESOLVER, AXP_SEARCH_DEFINITION_PROVIDER, AXP_SEARCH_PROVIDER, AXP_SETTING_DEFAULT_VALUES_PROVIDERS, AXP_SETTING_DEFINITION_PROVIDER, AXP_SETTING_VALUE_PROVIDER, AXP_STATUS_PROVIDERS, AXP_TAB_ROUTE_DATA_KEY, AXP_TOKEN_DEFINITION_PROVIDER, AXP_UNAUTHORIZED_ROUTE, AXVChangeType, BETWEEN_OPER, BOOLEAN_OPERATORS, CONTAINS_OPER, DATE_OPERATORS, DEFAULT_DATE_FILTER_PRESETS, DEFAULT_MULTILANGUAGE_FIELD_NAMES, ENDS_WITH_OPER, ENTITY_LAYOUT_SINGLE_TITLE_PATH, ENTITY_ROW_POPUP_TITLE_OPTION, ENTITY_ROW_TITLE_TEMPLATE_OPTION, ENVIRONMENT, EQ_OPER, GTE_OPER, GT_OPER, IN_OPER, IS_EMPTY_OPER, IS_NOT_EMPTY_OPER, LTE_OPER, LT_OPER, MULTILANGUAGE_CAPABLE_WIDGET_TYPES, NOT_CONTAINS_OPER, NOT_EQ_OPER, NUMBER_OPERATORS, STARTS_WITH_OPER, STRING_OPERATORS, UploadFromComputerActionProvider, applyDateFilterPreset, axpBackgroundOperationCapabilities, axpBackgroundOperationDisplayTime, axpBackgroundOperationEntityName, axpBackgroundOperationEstimateEndDate, axpBackgroundOperationFailurePolicy, axpBackgroundOperationFromEntityModel, axpBackgroundOperationRemainingMs, axpBackgroundOperationToEntityModel, axpBuildBackgroundOperation, axpBuildTabKey, axpCoerceBackgroundOperationDate, axpCreateBackgroundOperationId, axpFallbackTabTitle, axpFindMenuItemByRoute, axpGetLeafRouteSnapshot, axpHasTabScope, axpIsActiveBackgroundOperationStatus, axpIsCompletedBackgroundOperationStatus, axpIsEntityDetailsPath, axpIsEntityDetailsViewRoute, axpIsEntityListPath, axpIsEntityListRoute, axpIsEntityRecordNotFound, axpIsTabEligibleUrl, axpMenuPathsMatch, axpNavigateAppPath, axpParseAppPath, axpPathFromSnapshot, axpRedirectToNotFound, axpRedirectToUnauthorized, axpReplaceEntityListRouteQueryParams, axpResolveTabKey, axpResolveTabKeyFromRouteConfig, axpResolveTabKeyFromSnapshot, backgroundOperationEntityFactory, buildEntitySearchTitleContext, coerceAXPSettingRawValue, coerceAXPSettingRawValueByKind, configPlatform, createBackgroundOperationStore, createEntityCommandOptions, createEntityRowExpressionScope, createEntitySearchCommand, createMenuContext, createMenuMiddleware, createUnsavedChangesCanDeactivateGuard, downloadGraphicFromElement, evaluateEntityDisplayTemplate, findManualPresetIdByOperation, formatEntityRowDisplayTitle, inferSettingCoercionKind, isMeaningfulEntityDisplayTitle, isUnresolvedEntityDisplayTemplate, normalizeEntityDisplayTemplate, normalizePositiveNumberOption, normalizeRouterUrl, peelSettingValueKey, printGraphicFromElement, provideDynamicHomePage, provideMenuMiddleware, resolveDateFilterPresets, resolveEntityModifyPopupTitle, resolveEntityModifyRecordTitleTemplate, resolveEntityRowDisplayText, resolveEntityRowDisplayTitle, resolveEntityRowDisplayTitleFromColumnContext, resolveEntityRowTitleTemplate, resolveEntitySearchTitleFallback, resolveGraphicElementSize, resolveGraphicPrintContent, resolveQueryMatchingDisplayText, resolveSearchDisplayTemplate, resolveSearchableText, sanitizeResolvedEntityDisplayTitle, searchInRecordValues, searchInSearchableValue, serializeSvgElement, shouldApplyDefaultMultiLanguageToEntityProperty, shouldApplyDefaultMultiLanguageToWidgetNode, shouldApplyDefaultMultiLanguageToWidgetProperty, triggerBrowserDownload, withDefaultMultiLanguageOnWidgetNodeTree, withDefaultMultiLanguageOnWidgetProperty };
|
|
7594
8459
|
//# sourceMappingURL=acorex-platform-common.mjs.map
|