@acorex/platform 21.0.0-next.85 → 21.0.0-next.88
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fesm2022/acorex-platform-auth.mjs +1 -1
- package/fesm2022/acorex-platform-auth.mjs.map +1 -1
- package/fesm2022/{acorex-platform-common-common-settings.provider-Ytey9uhY.mjs → acorex-platform-common-common-settings.provider-VfmZct9H.mjs} +43 -3
- package/fesm2022/acorex-platform-common-common-settings.provider-VfmZct9H.mjs.map +1 -0
- package/fesm2022/acorex-platform-common.mjs +1709 -870
- package/fesm2022/acorex-platform-common.mjs.map +1 -1
- package/fesm2022/acorex-platform-contracts.mjs +1160 -9
- package/fesm2022/acorex-platform-contracts.mjs.map +1 -1
- package/fesm2022/acorex-platform-core.mjs +210 -131
- package/fesm2022/acorex-platform-core.mjs.map +1 -1
- package/fesm2022/acorex-platform-domain-contracts.mjs +58 -0
- package/fesm2022/acorex-platform-domain-contracts.mjs.map +1 -0
- package/fesm2022/acorex-platform-domain.mjs +2 -50
- package/fesm2022/acorex-platform-domain.mjs.map +1 -1
- package/fesm2022/acorex-platform-layout-builder.mjs.map +1 -1
- package/fesm2022/acorex-platform-layout-components.mjs +59 -23
- package/fesm2022/acorex-platform-layout-components.mjs.map +1 -1
- package/fesm2022/acorex-platform-layout-designer.mjs +3 -2
- package/fesm2022/acorex-platform-layout-designer.mjs.map +1 -1
- package/fesm2022/acorex-platform-layout-entity-attachments-page.component-CwG6n_Yb.mjs.map +1 -1
- package/fesm2022/acorex-platform-layout-entity-contracts.mjs +1114 -0
- package/fesm2022/acorex-platform-layout-entity-contracts.mjs.map +1 -0
- package/fesm2022/acorex-platform-layout-entity-file-list-popup.component--IeMZLIr.mjs.map +1 -1
- package/fesm2022/acorex-platform-layout-entity.mjs +661 -742
- package/fesm2022/acorex-platform-layout-entity.mjs.map +1 -1
- package/fesm2022/acorex-platform-layout-views.mjs +333 -119
- package/fesm2022/acorex-platform-layout-views.mjs.map +1 -1
- package/fesm2022/acorex-platform-layout-widget-core.mjs +11 -105
- package/fesm2022/acorex-platform-layout-widget-core.mjs.map +1 -1
- package/fesm2022/acorex-platform-layout-widgets-repeater-widget-column.component-CFyfix31.mjs +513 -0
- package/fesm2022/acorex-platform-layout-widgets-repeater-widget-column.component-CFyfix31.mjs.map +1 -0
- package/fesm2022/{acorex-platform-layout-widgets-tabular-data-edit-popup.component-DjpZU6gz.mjs → acorex-platform-layout-widgets-tabular-data-edit-popup.component-D4MOHEpI.mjs} +4 -4
- package/fesm2022/acorex-platform-layout-widgets-tabular-data-edit-popup.component-D4MOHEpI.mjs.map +1 -0
- package/fesm2022/acorex-platform-layout-widgets.mjs +868 -57
- package/fesm2022/acorex-platform-layout-widgets.mjs.map +1 -1
- package/fesm2022/acorex-platform-runtime.mjs.map +1 -1
- package/fesm2022/{acorex-platform-themes-default-entity-master-single-view.component-Br9p5aXT.mjs → acorex-platform-themes-default-entity-master-single-view.component-DrUwqKKA.mjs} +2 -3
- package/fesm2022/acorex-platform-themes-default-entity-master-single-view.component-DrUwqKKA.mjs.map +1 -0
- package/fesm2022/acorex-platform-themes-default.mjs +740 -96
- package/fesm2022/acorex-platform-themes-default.mjs.map +1 -1
- package/fesm2022/{acorex-platform-themes-shared-settings.provider-BjuzSe0T.mjs → acorex-platform-themes-shared-settings.provider-BUwPqwUS.mjs} +3 -3
- package/fesm2022/acorex-platform-themes-shared-settings.provider-BUwPqwUS.mjs.map +1 -0
- package/fesm2022/acorex-platform-themes-shared.mjs +4 -3
- package/fesm2022/acorex-platform-themes-shared.mjs.map +1 -1
- package/fesm2022/acorex-platform-workflow.mjs +6 -8
- package/fesm2022/acorex-platform-workflow.mjs.map +1 -1
- package/package.json +9 -5
- package/types/acorex-platform-auth.d.ts +2 -1
- package/types/acorex-platform-common.d.ts +419 -1083
- package/types/acorex-platform-contracts.d.ts +1791 -260
- package/types/acorex-platform-core.d.ts +2153 -2806
- package/types/acorex-platform-domain-contracts.d.ts +416 -0
- package/types/acorex-platform-domain.d.ts +4 -415
- package/types/acorex-platform-layout-builder.d.ts +2 -1
- package/types/acorex-platform-layout-components.d.ts +15 -4
- package/types/acorex-platform-layout-designer.d.ts +2 -1
- package/types/acorex-platform-layout-entity-contracts.d.ts +864 -0
- package/types/acorex-platform-layout-entity.d.ts +88 -269
- package/types/acorex-platform-layout-views.d.ts +48 -14
- package/types/acorex-platform-layout-widget-core.d.ts +9 -5
- package/types/acorex-platform-layout-widgets.d.ts +89 -13
- package/types/acorex-platform-runtime.d.ts +1 -1
- package/types/acorex-platform-themes-default.d.ts +173 -70
- package/types/acorex-platform-workflow.d.ts +5 -18
- package/fesm2022/acorex-platform-common-common-settings.provider-Ytey9uhY.mjs.map +0 -1
- package/fesm2022/acorex-platform-layout-widgets-repeater-widget-column.component-J0zcGKBX.mjs +0 -116
- package/fesm2022/acorex-platform-layout-widgets-repeater-widget-column.component-J0zcGKBX.mjs.map +0 -1
- package/fesm2022/acorex-platform-layout-widgets-tabular-data-edit-popup.component-DjpZU6gz.mjs.map +0 -1
- package/fesm2022/acorex-platform-native.mjs +0 -155
- package/fesm2022/acorex-platform-native.mjs.map +0 -1
- package/fesm2022/acorex-platform-themes-default-entity-master-single-view.component-Br9p5aXT.mjs.map +0 -1
- package/fesm2022/acorex-platform-themes-shared-settings.provider-BjuzSe0T.mjs.map +0 -1
- package/native/README.md +0 -4
- package/types/acorex-platform-native.d.ts +0 -28
|
@@ -1,19 +1,22 @@
|
|
|
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 { normalizeEntityDisplayTemplate, sanitizeResolvedEntityDisplayTitle, resolveEntityModifyRecordTitleTemplate, isMeaningfulEntityDisplayTitle, isUnresolvedEntityDisplayTemplate, ENTITY_ROW_POPUP_TITLE_OPTION, ENTITY_ROW_TITLE_TEMPLATE_OPTION, ENTITY_LAYOUT_SINGLE_TITLE_PATH, buildEntitySearchTitleContext, resolveEntityRowTitleTemplate } from '@acorex/platform/layout/entity-contracts';
|
|
4
|
+
export * from '@acorex/platform/layout/entity-contracts';
|
|
5
|
+
export { ENTITY_LAYOUT_SINGLE_TITLE_PATH, ENTITY_ROW_POPUP_TITLE_OPTION, ENTITY_ROW_TITLE_TEMPLATE_OPTION, buildEntitySearchTitleContext, isMeaningfulEntityDisplayTitle, isUnresolvedEntityDisplayTemplate, normalizeEntityDisplayTemplate, resolveEntityModifyRecordTitleTemplate, resolveEntityRowTitleTemplate, sanitizeResolvedEntityDisplayTitle } from '@acorex/platform/layout/entity-contracts';
|
|
4
6
|
import { Router, NavigationEnd, NavigationStart, ROUTES, RedirectCommand, RouterModule } from '@angular/router';
|
|
5
7
|
import { AXPSessionService, AXPSessionStatus, AXPAuthGuard } from '@acorex/platform/auth';
|
|
6
8
|
import { Subject, distinctUntilChanged, merge as merge$1, first, switchMap, from, map } from 'rxjs';
|
|
7
|
-
import
|
|
8
|
-
import
|
|
9
|
+
import { AXPPlatformScope, AXPRegionalSetting, AXPWidgetsCatalog, AXPDataGenerator, AXPSystemActionType } from '@acorex/platform/contracts';
|
|
10
|
+
import * as i3$1 from '@acorex/platform/core';
|
|
11
|
+
import { unwrapValueProperty, coerceUnknownToDate, coerceUnknownToFiniteNumber, coerceUnknownToBoolean, coerceUnknownToTrimmedString, AXPBroadcastEventService, objectKeyValueTransforms, AXPKeyboardShortcutRegistry, chordToKbdItemKeys, hasForegroundOverlayLayer, AXPKeyboardShortcutPriority, normalizeKeyboardShortcuts, isLocaleStringMap, AXPExpressionEvaluatorService, AXPContextEvalFactory, AXPDeviceService, AXPModuleManifestModule, AXPComponentSlotModule, AXPAppStartUpProvider, AXP_EXPRESSION_EVALUATOR_SCOPE_PROVIDER, AXPHookService, AXPModuleManifestRegistry } from '@acorex/platform/core';
|
|
12
|
+
import { merge, sortBy, cloneDeep, get, isNumber, isNil, isObject, isString, isPlainObject, omit, defaultTo } from 'lodash-es';
|
|
9
13
|
import { AXCalendarService, AXDateTimeModule } from '@acorex/core/date-time';
|
|
10
14
|
import { AXDialogService } from '@acorex/components/dialog';
|
|
11
15
|
import * as i5 from '@acorex/core/translation';
|
|
12
|
-
import { AXTranslationService, AXTranslationModule, resolveMultiLanguageString } from '@acorex/core/translation';
|
|
16
|
+
import { AXTranslationService, AXTranslationModule, translateSync, resolveMultiLanguageString } from '@acorex/core/translation';
|
|
13
17
|
import * as i4$1 from '@angular/common';
|
|
14
18
|
import { Location, CommonModule, AsyncPipe, DOCUMENT } from '@angular/common';
|
|
15
19
|
import { takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop';
|
|
16
|
-
import { AXPWidgetsCatalog } from '@acorex/platform/layout/widget-core';
|
|
17
20
|
import * as i4 from '@acorex/components/decorators';
|
|
18
21
|
import { AXDecoratorModule } from '@acorex/components/decorators';
|
|
19
22
|
import * as i2 from '@acorex/components/popover';
|
|
@@ -24,13 +27,15 @@ import * as i3 from '@acorex/components/progress-bar';
|
|
|
24
27
|
import { AXProgressBarModule } from '@acorex/components/progress-bar';
|
|
25
28
|
import { AXPopupService, AXPopupModule } from '@acorex/components/popup';
|
|
26
29
|
import { AXToastService, AXToastModule } from '@acorex/components/toast';
|
|
27
|
-
import * as
|
|
30
|
+
import * as i2$1 from '@acorex/core/format';
|
|
31
|
+
import { AXFormatService } from '@acorex/core/format';
|
|
32
|
+
import * as i4$2 from '@acorex/platform/workflow';
|
|
28
33
|
import { AXPWorkflowService, createWorkFlowEvent, AXPWorkflowAction, AXPWorkflowError, AXPWorkflowModule } from '@acorex/platform/workflow';
|
|
29
34
|
import { AXKBDComponent, AXKBDItemComponent } from '@acorex/components/kbd';
|
|
30
35
|
import { AXDataSource } from '@acorex/cdk/common';
|
|
31
36
|
import { AXPCommandExecutor, AXPCommandService } from '@acorex/platform/runtime';
|
|
32
37
|
import { signalStore, withState, withMethods, patchState, withHooks } from '@ngrx/signals';
|
|
33
|
-
import {
|
|
38
|
+
import { filter } from 'rxjs/operators';
|
|
34
39
|
import { AXFileService } from '@acorex/core/file';
|
|
35
40
|
import { AXCheckBoxModule } from '@acorex/components/check-box';
|
|
36
41
|
import * as i6 from '@acorex/components/color-box';
|
|
@@ -164,110 +169,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
164
169
|
}]
|
|
165
170
|
}] });
|
|
166
171
|
|
|
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 = {}));
|
|
172
|
+
/**
|
|
173
|
+
* Entity definition types, query views, and related helpers.
|
|
174
|
+
* Canonical definitions live in @acorex/platform/layout/entity-contracts.
|
|
175
|
+
*/
|
|
271
176
|
|
|
272
177
|
const EQ_OPER = { name: 'equal', title: '@general:terms.operators.equal', hasValue: true };
|
|
273
178
|
const NOT_EQ_OPER = { name: 'notEqual', title: '@general:terms.operators.not-equal', hasValue: true };
|
|
@@ -472,38 +377,6 @@ const AXP_HOME_PAGE_DEFAULT_KEY = new InjectionToken('AXP_HOME_PAGE_DEFAULT_KEY'
|
|
|
472
377
|
factory: () => 'home'
|
|
473
378
|
});
|
|
474
379
|
|
|
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
380
|
class AXPSettingDefinitionProviderContext {
|
|
508
381
|
constructor() {
|
|
509
382
|
this.rootGroups = [];
|
|
@@ -601,6 +474,7 @@ class AXPSettingDefinitionSectionBuilder {
|
|
|
601
474
|
isRequired: setting.isRequired ?? false,
|
|
602
475
|
isInherited: setting.isInherited ?? false,
|
|
603
476
|
isEncrypted: setting.isEncrypted ?? false,
|
|
477
|
+
requiresPageRefresh: setting.requiresPageRefresh ?? false,
|
|
604
478
|
defaultValue: setting.defaultValue,
|
|
605
479
|
scope: setting.scope ?? 'P',
|
|
606
480
|
layout: setting.widget.layout,
|
|
@@ -735,6 +609,26 @@ class AXPSettingDefinitionProviderService {
|
|
|
735
609
|
collect(this.cache);
|
|
736
610
|
return scopes;
|
|
737
611
|
}
|
|
612
|
+
async requiresPageRefreshByKey() {
|
|
613
|
+
const refreshByKey = {};
|
|
614
|
+
const collect = (groups) => {
|
|
615
|
+
groups.forEach((group) => {
|
|
616
|
+
group.sections.forEach((section) => {
|
|
617
|
+
section.settings.forEach((setting) => {
|
|
618
|
+
if (setting.requiresPageRefresh) {
|
|
619
|
+
refreshByKey[setting.name] = true;
|
|
620
|
+
}
|
|
621
|
+
});
|
|
622
|
+
});
|
|
623
|
+
collect(group.groups);
|
|
624
|
+
});
|
|
625
|
+
};
|
|
626
|
+
if (!this.cache) {
|
|
627
|
+
await this.load();
|
|
628
|
+
}
|
|
629
|
+
collect(this.cache);
|
|
630
|
+
return refreshByKey;
|
|
631
|
+
}
|
|
738
632
|
findGroup(scope, groupName) {
|
|
739
633
|
return this.searchRecursive(this.getList(scope), groupName, []); // Initialize with an empty breadcrumb
|
|
740
634
|
}
|
|
@@ -1425,6 +1319,8 @@ var AXPCommonSettings;
|
|
|
1425
1319
|
AXPCommonSettings["DebugMode"] = "PlatformDevTools:Setting:Developer.DebugMode";
|
|
1426
1320
|
AXPCommonSettings["ShowRowIndexColumn"] = "Common:Setting:Entity.ShowRowIndexColumn";
|
|
1427
1321
|
AXPCommonSettings["EntityListPersistenceMode"] = "Common:Setting:Entity.ListPersistenceMode";
|
|
1322
|
+
AXPCommonSettings["NavigationMode"] = "Common:Setting:Navigation.Mode";
|
|
1323
|
+
AXPCommonSettings["MaxOpenTabs"] = "Common:Setting:Navigation.MaxOpenTabs";
|
|
1428
1324
|
})(AXPCommonSettings || (AXPCommonSettings = {}));
|
|
1429
1325
|
|
|
1430
1326
|
//#endregion
|
|
@@ -1611,9 +1507,176 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
1611
1507
|
}]
|
|
1612
1508
|
}], ctorParameters: () => [] });
|
|
1613
1509
|
|
|
1510
|
+
//#endregion
|
|
1511
|
+
/**
|
|
1512
|
+
* Holds detached Angular route component instances keyed by tab key so tabular navigation can
|
|
1513
|
+
* preserve a page's full state (data-table selection, scroll, unsaved form values, etc.) while it
|
|
1514
|
+
* is in the background, then reattach it without re-rendering.
|
|
1515
|
+
*
|
|
1516
|
+
* The registry has no `Router` dependency on purpose: it is consumed by the {@link RouteReuseStrategy}
|
|
1517
|
+
* implementation, so depending on `Router` here would create a construction-time cycle.
|
|
1518
|
+
*/
|
|
1519
|
+
class AXPTabRouteCacheRegistry {
|
|
1520
|
+
constructor() {
|
|
1521
|
+
//#region ---- State ----
|
|
1522
|
+
this.handles = new Map();
|
|
1523
|
+
this.openKeys = new Set();
|
|
1524
|
+
this.enabled = false;
|
|
1525
|
+
this.deactivateGuardSuspended = false;
|
|
1526
|
+
}
|
|
1527
|
+
//#endregion
|
|
1528
|
+
//#region ---- Deactivate Guard Suspension ----
|
|
1529
|
+
/**
|
|
1530
|
+
* Runs `action` while unsaved-changes `canDeactivate` guards are suspended, so switching between
|
|
1531
|
+
* tabs preserves a dirty page without prompting. Closing a tab does not use this path, so the
|
|
1532
|
+
* prompt still appears there.
|
|
1533
|
+
*/
|
|
1534
|
+
async runWithSuspendedDeactivateGuards(action) {
|
|
1535
|
+
const previous = this.deactivateGuardSuspended;
|
|
1536
|
+
this.deactivateGuardSuspended = true;
|
|
1537
|
+
try {
|
|
1538
|
+
return await action();
|
|
1539
|
+
}
|
|
1540
|
+
finally {
|
|
1541
|
+
this.deactivateGuardSuspended = previous;
|
|
1542
|
+
}
|
|
1543
|
+
}
|
|
1544
|
+
isDeactivateGuardSuspended() {
|
|
1545
|
+
return this.deactivateGuardSuspended;
|
|
1546
|
+
}
|
|
1547
|
+
//#endregion
|
|
1548
|
+
//#region ---- Mode ----
|
|
1549
|
+
/** Enables caching (tabular mode). Disabling clears and destroys every cached component. */
|
|
1550
|
+
setEnabled(enabled) {
|
|
1551
|
+
if (this.enabled === enabled) {
|
|
1552
|
+
return;
|
|
1553
|
+
}
|
|
1554
|
+
this.enabled = enabled;
|
|
1555
|
+
if (!enabled) {
|
|
1556
|
+
this.clear();
|
|
1557
|
+
}
|
|
1558
|
+
}
|
|
1559
|
+
isEnabled() {
|
|
1560
|
+
return this.enabled;
|
|
1561
|
+
}
|
|
1562
|
+
//#endregion
|
|
1563
|
+
//#region ---- Open Keys Sync ----
|
|
1564
|
+
/**
|
|
1565
|
+
* Reconciles open scoped tab keys and active tab URLs. Cached handles whose key is not an open
|
|
1566
|
+
* scoped tab key, an active tab URL, or a child path under an open scoped tab are destroyed.
|
|
1567
|
+
*/
|
|
1568
|
+
syncOpenKeys(scopedKeys, activeTabUrls = []) {
|
|
1569
|
+
const scopedSet = new Set(scopedKeys);
|
|
1570
|
+
const activePathSet = new Set(activeTabUrls.map((url) => url.split('?')[0].split('#')[0].replace(/\/+$/, '') || '/'));
|
|
1571
|
+
for (const cacheKey of [...this.handles.keys()]) {
|
|
1572
|
+
const normalizedCacheKey = cacheKey.replace(/\/+$/, '') || '/';
|
|
1573
|
+
const keepsScopedKey = scopedSet.has(normalizedCacheKey);
|
|
1574
|
+
const keepsActiveUrl = activePathSet.has(normalizedCacheKey);
|
|
1575
|
+
const keepsUnderScopedTab = [...scopedSet].some((scopedKey) => normalizedCacheKey === scopedKey || normalizedCacheKey.startsWith(`${scopedKey}/`));
|
|
1576
|
+
if (!keepsScopedKey && !keepsActiveUrl && !keepsUnderScopedTab) {
|
|
1577
|
+
this.destroyHandle(this.handles.get(cacheKey));
|
|
1578
|
+
this.handles.delete(cacheKey);
|
|
1579
|
+
}
|
|
1580
|
+
}
|
|
1581
|
+
this.openKeys = scopedSet;
|
|
1582
|
+
}
|
|
1583
|
+
isOpen(key) {
|
|
1584
|
+
return this.openKeys.has(key);
|
|
1585
|
+
}
|
|
1586
|
+
/**
|
|
1587
|
+
* Returns the live component instance for a cached background tab, when available.
|
|
1588
|
+
* Matches the tab key directly or any cached handle under a scoped tab prefix.
|
|
1589
|
+
*/
|
|
1590
|
+
getComponentInstance(key) {
|
|
1591
|
+
const normalizedKey = key.replace(/\/+$/, '') || '/';
|
|
1592
|
+
const direct = this.handles.get(normalizedKey) ?? this.handles.get(key);
|
|
1593
|
+
if (direct) {
|
|
1594
|
+
return this.readComponentInstance(direct);
|
|
1595
|
+
}
|
|
1596
|
+
for (const [cacheKey, handle] of this.handles) {
|
|
1597
|
+
const normalizedCacheKey = cacheKey.replace(/\/+$/, '') || '/';
|
|
1598
|
+
if (normalizedCacheKey === normalizedKey || normalizedCacheKey.startsWith(`${normalizedKey}/`)) {
|
|
1599
|
+
return this.readComponentInstance(handle);
|
|
1600
|
+
}
|
|
1601
|
+
}
|
|
1602
|
+
return null;
|
|
1603
|
+
}
|
|
1604
|
+
//#endregion
|
|
1605
|
+
//#region ---- Handle Store ----
|
|
1606
|
+
/**
|
|
1607
|
+
* Caches a detached background handle. Overwrites without destroying the previous handle: on a
|
|
1608
|
+
* repeat visit Angular hands back a new handle object wrapping the same live `componentRef`, so
|
|
1609
|
+
* destroying the old one would destroy the live component and corrupt the view (NG0922).
|
|
1610
|
+
*/
|
|
1611
|
+
store(key, handle) {
|
|
1612
|
+
this.handles.set(key, handle);
|
|
1613
|
+
}
|
|
1614
|
+
/**
|
|
1615
|
+
* Returns the cached handle without mutating the map. Angular reads a handle twice per
|
|
1616
|
+
* navigation (once while building the future state, once while attaching), so `retrieve` must be
|
|
1617
|
+
* idempotent; the handle is released afterwards via {@link release} when Angular calls
|
|
1618
|
+
* `store(snapshot, null)`.
|
|
1619
|
+
*/
|
|
1620
|
+
retrieve(key) {
|
|
1621
|
+
return this.handles.get(key) ?? null;
|
|
1622
|
+
}
|
|
1623
|
+
/**
|
|
1624
|
+
* Drops a handle that the router has just reattached (it is now a live foreground view, so it
|
|
1625
|
+
* must not be destroyed). The map keeps only truly backgrounded handles.
|
|
1626
|
+
*/
|
|
1627
|
+
release(key) {
|
|
1628
|
+
this.handles.delete(key);
|
|
1629
|
+
}
|
|
1630
|
+
has(key) {
|
|
1631
|
+
return this.handles.has(key);
|
|
1632
|
+
}
|
|
1633
|
+
/** Destroys and removes a single cached handle (used when a tab closes). */
|
|
1634
|
+
evict(key) {
|
|
1635
|
+
this.destroyHandle(this.handles.get(key));
|
|
1636
|
+
this.handles.delete(key);
|
|
1637
|
+
}
|
|
1638
|
+
/** Destroys and removes every cached handle. */
|
|
1639
|
+
clear() {
|
|
1640
|
+
for (const handle of this.handles.values()) {
|
|
1641
|
+
this.destroyHandle(handle);
|
|
1642
|
+
}
|
|
1643
|
+
this.handles.clear();
|
|
1644
|
+
this.openKeys.clear();
|
|
1645
|
+
}
|
|
1646
|
+
//#endregion
|
|
1647
|
+
//#region ---- Utility Methods ----
|
|
1648
|
+
readComponentInstance(handle) {
|
|
1649
|
+
const componentRef = handle.componentRef;
|
|
1650
|
+
return componentRef?.instance ?? null;
|
|
1651
|
+
}
|
|
1652
|
+
destroyHandle(handle) {
|
|
1653
|
+
if (!handle) {
|
|
1654
|
+
return;
|
|
1655
|
+
}
|
|
1656
|
+
const componentRef = handle.componentRef;
|
|
1657
|
+
componentRef?.destroy?.();
|
|
1658
|
+
}
|
|
1659
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPTabRouteCacheRegistry, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
1660
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPTabRouteCacheRegistry, providedIn: 'root' }); }
|
|
1661
|
+
}
|
|
1662
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPTabRouteCacheRegistry, decorators: [{
|
|
1663
|
+
type: Injectable,
|
|
1664
|
+
args: [{
|
|
1665
|
+
providedIn: 'root',
|
|
1666
|
+
}]
|
|
1667
|
+
}] });
|
|
1668
|
+
|
|
1614
1669
|
//#region ---- Unsaved Changes Can Deactivate Guard Factory ----
|
|
1615
1670
|
function createUnsavedChangesCanDeactivateGuard() {
|
|
1616
|
-
return (component) =>
|
|
1671
|
+
return (component) => {
|
|
1672
|
+
// Tab switches keep the page alive in the background, so skip the prompt; closing a tab does not
|
|
1673
|
+
// suspend guards and still prompts.
|
|
1674
|
+
const tabCache = inject(AXPTabRouteCacheRegistry, { optional: true });
|
|
1675
|
+
if (tabCache?.isDeactivateGuardSuspended()) {
|
|
1676
|
+
return true;
|
|
1677
|
+
}
|
|
1678
|
+
return component.canDeactivate();
|
|
1679
|
+
};
|
|
1617
1680
|
}
|
|
1618
1681
|
//#endregion
|
|
1619
1682
|
|
|
@@ -1670,7 +1733,7 @@ class AXPHomePageService {
|
|
|
1670
1733
|
return [
|
|
1671
1734
|
{ path: '', redirectTo: fullPath, pathMatch: 'full' },
|
|
1672
1735
|
{ path: ':app', redirectTo: fullPath, pathMatch: 'full' },
|
|
1673
|
-
{ ...home?.route,
|
|
1736
|
+
{ ...home?.route, data: { ...home?.route?.data, key: home?.key, title: home?.title, path: fullPath } },
|
|
1674
1737
|
];
|
|
1675
1738
|
}
|
|
1676
1739
|
return [{ path: '', redirectTo: this.loginPath, pathMatch: 'full' }];
|
|
@@ -2991,6 +3054,56 @@ function axpRedirectToUnauthorized(router = inject(Router)) {
|
|
|
2991
3054
|
return new RedirectCommand(router.parseUrl(AXP_UNAUTHORIZED_ROUTE));
|
|
2992
3055
|
}
|
|
2993
3056
|
|
|
3057
|
+
//#endregion
|
|
3058
|
+
/**
|
|
3059
|
+
* Platform formatter for display text: resolves ML objects, i18n keys (`@scope:key`), and primitives.
|
|
3060
|
+
* Register via {@link AXPCommonModule} (auto) or `AXFormatModule.forRoot({ formatters: [AXPTranslateFormatter] })`.
|
|
3061
|
+
*/
|
|
3062
|
+
class AXPTranslateFormatter {
|
|
3063
|
+
constructor() {
|
|
3064
|
+
//#region ---- Services & Dependencies ----
|
|
3065
|
+
this.translationService = inject(AXTranslationService);
|
|
3066
|
+
}
|
|
3067
|
+
//#endregion
|
|
3068
|
+
//#region ---- AXFormatter ----
|
|
3069
|
+
get name() {
|
|
3070
|
+
return 'translate';
|
|
3071
|
+
}
|
|
3072
|
+
format(value, options) {
|
|
3073
|
+
if (isNil(value) || value === '') {
|
|
3074
|
+
return '';
|
|
3075
|
+
}
|
|
3076
|
+
if (isObject(value) && !Array.isArray(value) && !(value instanceof Date)) {
|
|
3077
|
+
const resolved = this.translationService.resolve(value);
|
|
3078
|
+
return resolved?.trim() ?? '';
|
|
3079
|
+
}
|
|
3080
|
+
if (isString(value)) {
|
|
3081
|
+
const text = value.trim();
|
|
3082
|
+
if (!text) {
|
|
3083
|
+
return '';
|
|
3084
|
+
}
|
|
3085
|
+
if (text.startsWith('@')) {
|
|
3086
|
+
const translateOptions = typeof options === 'object' ? options : undefined;
|
|
3087
|
+
const translated = translateSync(text, translateOptions);
|
|
3088
|
+
return translated?.trim() ?? text;
|
|
3089
|
+
}
|
|
3090
|
+
return text;
|
|
3091
|
+
}
|
|
3092
|
+
if (typeof value === 'number' || typeof value === 'boolean') {
|
|
3093
|
+
return String(value);
|
|
3094
|
+
}
|
|
3095
|
+
if (value instanceof Date) {
|
|
3096
|
+
return value.toISOString();
|
|
3097
|
+
}
|
|
3098
|
+
return String(value).trim();
|
|
3099
|
+
}
|
|
3100
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPTranslateFormatter, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
3101
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPTranslateFormatter }); }
|
|
3102
|
+
}
|
|
3103
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPTranslateFormatter, decorators: [{
|
|
3104
|
+
type: Injectable
|
|
3105
|
+
}] });
|
|
3106
|
+
|
|
2994
3107
|
class AXPFileStorageService {
|
|
2995
3108
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPFileStorageService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
2996
3109
|
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPFileStorageService }); }
|
|
@@ -4429,7 +4542,115 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
4429
4542
|
}]
|
|
4430
4543
|
}] });
|
|
4431
4544
|
|
|
4545
|
+
//#endregion
|
|
4546
|
+
//#region ---- App Path Navigation ----
|
|
4547
|
+
/**
|
|
4548
|
+
* Splits an absolute app path (`/app/m/module/e/Entity/id/view`) into router command segments.
|
|
4549
|
+
* Prefer this over `navigate([fullPath])` or ambiguous single-segment navigation so browser history stays correct.
|
|
4550
|
+
*/
|
|
4551
|
+
function axpParseAppPath(path) {
|
|
4552
|
+
const trimmed = path.trim();
|
|
4553
|
+
const withoutLeadingSlash = trimmed.startsWith('/') ? trimmed.slice(1) : trimmed;
|
|
4554
|
+
const [pathAndFragment, queryString] = withoutLeadingSlash.split('?');
|
|
4555
|
+
const [pathPart, fragment] = pathAndFragment.split('#');
|
|
4556
|
+
const segments = pathPart.split('/').filter(Boolean);
|
|
4557
|
+
const queryParams = queryString
|
|
4558
|
+
? queryString.split('&').reduce((params, pair) => {
|
|
4559
|
+
const [key, value = ''] = pair.split('=');
|
|
4560
|
+
if (key) {
|
|
4561
|
+
params[decodeURIComponent(key)] = decodeURIComponent(value);
|
|
4562
|
+
}
|
|
4563
|
+
return params;
|
|
4564
|
+
}, {})
|
|
4565
|
+
: undefined;
|
|
4566
|
+
return {
|
|
4567
|
+
segments,
|
|
4568
|
+
queryParams,
|
|
4569
|
+
fragment: fragment || undefined,
|
|
4570
|
+
};
|
|
4571
|
+
}
|
|
4572
|
+
/** Navigates using explicit path segments (same pattern as entity preload filters). */
|
|
4573
|
+
function axpNavigateAppPath(router, path, extras) {
|
|
4574
|
+
const { segments, queryParams, fragment } = axpParseAppPath(path);
|
|
4575
|
+
return router.navigate(segments, {
|
|
4576
|
+
...extras,
|
|
4577
|
+
queryParams,
|
|
4578
|
+
fragment,
|
|
4579
|
+
});
|
|
4580
|
+
}
|
|
4581
|
+
/** True when a URL path is an entity master list route (`.../e/{Entity}/list`). */
|
|
4582
|
+
function axpIsEntityListPath(url) {
|
|
4583
|
+
const path = url.split('?')[0].split('#')[0];
|
|
4584
|
+
return /\/e\/[^/]+\/list$/.test(path);
|
|
4585
|
+
}
|
|
4586
|
+
/** True when the current browser URL is an entity master list route (`.../e/{Entity}/list`). */
|
|
4587
|
+
function axpIsEntityListRoute(router) {
|
|
4588
|
+
return axpIsEntityListPath(router.url);
|
|
4589
|
+
}
|
|
4590
|
+
/** True when a URL path is an entity details view route (`.../e/{Entity}/{id}/view`). */
|
|
4591
|
+
function axpIsEntityDetailsPath(url) {
|
|
4592
|
+
const path = url.split('?')[0].split('#')[0];
|
|
4593
|
+
return /\/e\/[^/]+\/[^/]+\/view$/.test(path);
|
|
4594
|
+
}
|
|
4595
|
+
/** True when the current browser URL is an entity details view route (`.../e/{Entity}/{id}/view`). */
|
|
4596
|
+
function axpIsEntityDetailsViewRoute(router) {
|
|
4597
|
+
return axpIsEntityDetailsPath(router.url);
|
|
4598
|
+
}
|
|
4599
|
+
/**
|
|
4600
|
+
* Merges query params on the current entity list route without pushing history.
|
|
4601
|
+
* Uses absolute path segments so a stale `relativeTo` cannot rewrite the active URL after navigating away.
|
|
4602
|
+
*/
|
|
4603
|
+
function axpReplaceEntityListRouteQueryParams(router, queryParams) {
|
|
4604
|
+
const url = router.url;
|
|
4605
|
+
if (!axpIsEntityListPath(url)) {
|
|
4606
|
+
return;
|
|
4607
|
+
}
|
|
4608
|
+
const pathOnly = url.split('?')[0].split('#')[0];
|
|
4609
|
+
const { segments } = axpParseAppPath(pathOnly);
|
|
4610
|
+
void router.navigate(segments, {
|
|
4611
|
+
queryParams,
|
|
4612
|
+
queryParamsHandling: 'merge',
|
|
4613
|
+
replaceUrl: true,
|
|
4614
|
+
});
|
|
4615
|
+
}
|
|
4616
|
+
//#endregion
|
|
4617
|
+
|
|
4432
4618
|
const DEFAULT_TRAILING_SEGMENTS = ['list', 'view', 'edit', 'create'];
|
|
4619
|
+
//#region ---- Entity Route Helpers ----
|
|
4620
|
+
/**
|
|
4621
|
+
* Parses `/{app}/m/{module}/e/{entity}/...` routes into a stable entity key.
|
|
4622
|
+
*/
|
|
4623
|
+
function axpParseEntityRouteKey(segments) {
|
|
4624
|
+
const mIndex = segments.indexOf('m');
|
|
4625
|
+
const eIndex = segments.indexOf('e');
|
|
4626
|
+
if (mIndex < 1 || eIndex <= mIndex + 1 || eIndex + 1 >= segments.length) {
|
|
4627
|
+
return null;
|
|
4628
|
+
}
|
|
4629
|
+
return {
|
|
4630
|
+
app: segments[mIndex - 1],
|
|
4631
|
+
module: segments[mIndex + 1],
|
|
4632
|
+
entity: segments[eIndex + 1],
|
|
4633
|
+
entityEndIndex: eIndex + 2,
|
|
4634
|
+
};
|
|
4635
|
+
}
|
|
4636
|
+
function axpEntityRouteKeysEqual(left, right) {
|
|
4637
|
+
return left.app === right.app && left.module === right.module && left.entity === right.entity;
|
|
4638
|
+
}
|
|
4639
|
+
/**
|
|
4640
|
+
* Matches entity list menu entries against list/detail/create/edit routes for the same entity.
|
|
4641
|
+
*/
|
|
4642
|
+
function axpTryMatchEntityMenuRoute(itemSegments, pathSegments, currentBase) {
|
|
4643
|
+
const menuEntity = axpParseEntityRouteKey(itemSegments);
|
|
4644
|
+
const currentEntity = axpParseEntityRouteKey(pathSegments);
|
|
4645
|
+
if (!menuEntity || !currentEntity || !axpEntityRouteKeysEqual(menuEntity, currentEntity)) {
|
|
4646
|
+
return null;
|
|
4647
|
+
}
|
|
4648
|
+
const isPartial = pathSegments.length > menuEntity.entityEndIndex ||
|
|
4649
|
+
axpIsEntityDetailsPath(currentBase) ||
|
|
4650
|
+
!axpIsEntityListPath(currentBase);
|
|
4651
|
+
return { isMatch: true, isPartial };
|
|
4652
|
+
}
|
|
4653
|
+
//#endregion
|
|
4433
4654
|
/**
|
|
4434
4655
|
* Compares a menu item path with the current browser path.
|
|
4435
4656
|
* Mirrors the matching rules used when selecting the active menu item.
|
|
@@ -4438,9 +4659,13 @@ function axpMenuPathsMatch(itemPath, currentPath, trailingSegmentsToIgnore = DEF
|
|
|
4438
4659
|
const itemNoHash = itemPath.split('#')[0];
|
|
4439
4660
|
const currentNoHash = currentPath.split('#')[0];
|
|
4440
4661
|
const [itemBase, itemQueryString = ''] = itemNoHash.split('?');
|
|
4441
|
-
const [currentBase
|
|
4662
|
+
const [currentBase] = currentNoHash.split('?');
|
|
4442
4663
|
const itemSegments = itemBase.split('/').filter((segment) => segment.length > 0);
|
|
4443
4664
|
const pathSegments = currentBase.split('/').filter((segment) => segment.length > 0);
|
|
4665
|
+
const entityMatch = axpTryMatchEntityMenuRoute(itemSegments, pathSegments, `/${currentBase}`);
|
|
4666
|
+
if (entityMatch) {
|
|
4667
|
+
return entityMatch;
|
|
4668
|
+
}
|
|
4444
4669
|
const menuLength = itemSegments.length;
|
|
4445
4670
|
const pathLength = pathSegments.length;
|
|
4446
4671
|
let effectiveMenuLength = menuLength;
|
|
@@ -4463,7 +4688,7 @@ function axpMenuPathsMatch(itemPath, currentPath, trailingSegmentsToIgnore = DEF
|
|
|
4463
4688
|
const isPartialMatch = effectiveMenuLength < pathLength;
|
|
4464
4689
|
const isExactMatch = effectiveMenuLength === pathLength;
|
|
4465
4690
|
const itemParams = new URLSearchParams(itemQueryString);
|
|
4466
|
-
const currentParams = new URLSearchParams(
|
|
4691
|
+
const currentParams = new URLSearchParams(currentNoHash.split('?')[1] ?? '');
|
|
4467
4692
|
let queryMatches = true;
|
|
4468
4693
|
const itemParamPairs = [];
|
|
4469
4694
|
itemParams.forEach((value, key) => {
|
|
@@ -4508,6 +4733,10 @@ function axpFindMenuItemByRoute(items, currentPath, trailingSegmentsToIgnore = D
|
|
|
4508
4733
|
trailingSegmentsToIgnore.includes(itemSegments[itemSegments.length - 1])) {
|
|
4509
4734
|
effectiveMenuLength--;
|
|
4510
4735
|
}
|
|
4736
|
+
const menuEntity = axpParseEntityRouteKey(itemSegments);
|
|
4737
|
+
if (menuEntity) {
|
|
4738
|
+
effectiveMenuLength = menuEntity.entityEndIndex;
|
|
4739
|
+
}
|
|
4511
4740
|
if (effectiveMenuLength > bestEffectiveLength) {
|
|
4512
4741
|
bestEffectiveLength = effectiveMenuLength;
|
|
4513
4742
|
bestMatch = { item, ancestors, isPartialMatch: matchResult.isPartial };
|
|
@@ -4536,29 +4765,13 @@ withMethods((store, router = inject(Router), workflow = inject(AXPWorkflowServic
|
|
|
4536
4765
|
patchState(store, { items: items });
|
|
4537
4766
|
},
|
|
4538
4767
|
selectMenuItemByRoute(path) {
|
|
4539
|
-
const
|
|
4540
|
-
|
|
4541
|
-
|
|
4542
|
-
|
|
4543
|
-
|
|
4544
|
-
|
|
4545
|
-
|
|
4546
|
-
return foundChild;
|
|
4547
|
-
}
|
|
4548
|
-
}
|
|
4549
|
-
if (item.path) {
|
|
4550
|
-
// Check if the item path matches the current path (including query param subset if provided on item)
|
|
4551
|
-
const matchResult = axpMenuPathsMatch(item.path, currentRawPath, ['list', 'view', 'edit', 'create']);
|
|
4552
|
-
if (matchResult.isMatch) {
|
|
4553
|
-
return { item, isPartialMatch: matchResult.isPartial };
|
|
4554
|
-
}
|
|
4555
|
-
}
|
|
4556
|
-
}
|
|
4557
|
-
return { item: null, isPartialMatch: false };
|
|
4558
|
-
};
|
|
4559
|
-
const items = store.items();
|
|
4560
|
-
const { item, isPartialMatch } = findItem(items);
|
|
4561
|
-
patchState(store, { selectedMenuItem: { item, isFullMatch: !isPartialMatch } });
|
|
4768
|
+
const match = axpFindMenuItemByRoute(store.items(), path, ['list', 'view', 'edit', 'create']);
|
|
4769
|
+
patchState(store, {
|
|
4770
|
+
selectedMenuItem: {
|
|
4771
|
+
item: match?.item ?? null,
|
|
4772
|
+
isFullMatch: match ? !match.isPartialMatch : false,
|
|
4773
|
+
},
|
|
4774
|
+
});
|
|
4562
4775
|
},
|
|
4563
4776
|
executeCommand(item) {
|
|
4564
4777
|
if (item.path) {
|
|
@@ -4579,24 +4792,28 @@ withMethods((store, router = inject(Router), workflow = inject(AXPWorkflowServic
|
|
|
4579
4792
|
}
|
|
4580
4793
|
},
|
|
4581
4794
|
isItemOpen(item) {
|
|
4582
|
-
const selectedItem = store.selectedMenuItem();
|
|
4583
|
-
if (!selectedItem
|
|
4795
|
+
const selectedItem = store.selectedMenuItem().item;
|
|
4796
|
+
if (!selectedItem) {
|
|
4584
4797
|
return false;
|
|
4585
4798
|
}
|
|
4799
|
+
if (item === selectedItem || (item.name && item.name === selectedItem.name)) {
|
|
4800
|
+
return true;
|
|
4801
|
+
}
|
|
4586
4802
|
const findParent = (currentItem, targetItem) => {
|
|
4587
|
-
if (currentItem.children?.
|
|
4803
|
+
if (currentItem.children?.some((child) => child === targetItem || (child.name && child.name === targetItem.name))) {
|
|
4588
4804
|
return true;
|
|
4589
4805
|
}
|
|
4590
4806
|
return currentItem.children?.some((child) => findParent(child, targetItem)) ?? false;
|
|
4591
4807
|
};
|
|
4592
|
-
return
|
|
4808
|
+
return findParent(item, selectedItem);
|
|
4593
4809
|
},
|
|
4594
4810
|
};
|
|
4595
|
-
}), withHooks((store, menuProviderService = inject(AXPMenuProviderService), menuKeyboardShortcuts = inject(AXPMenuKeyboardShortcutService)) => {
|
|
4811
|
+
}), withHooks((store, menuProviderService = inject(AXPMenuProviderService), menuKeyboardShortcuts = inject(AXPMenuKeyboardShortcutService), router = inject(Router)) => {
|
|
4596
4812
|
let reloadSubscription;
|
|
4597
4813
|
const applyMenuItems = (items) => {
|
|
4598
4814
|
patchState(store, { items });
|
|
4599
4815
|
menuKeyboardShortcuts.sync(items);
|
|
4816
|
+
store.selectMenuItemByRoute(router.url);
|
|
4600
4817
|
};
|
|
4601
4818
|
return {
|
|
4602
4819
|
onInit() {
|
|
@@ -4756,6 +4973,8 @@ class AXPMenuSearchDefinitionProvider {
|
|
|
4756
4973
|
}
|
|
4757
4974
|
}
|
|
4758
4975
|
|
|
4976
|
+
const AXP_SEARCH_DEFINITION_LAZY_RESOLVER = new InjectionToken('AXP_SEARCH_DEFINITION_LAZY_RESOLVER');
|
|
4977
|
+
|
|
4759
4978
|
class AXPSearchDefinitionProviderContext {
|
|
4760
4979
|
constructor() {
|
|
4761
4980
|
this.definitions = [];
|
|
@@ -4830,6 +5049,7 @@ const AXP_SEARCH_DEFINITION_PROVIDER = new InjectionToken('AXP_SEARCH_DEFINITION
|
|
|
4830
5049
|
class AXPSearchDefinitionProviderService {
|
|
4831
5050
|
constructor() {
|
|
4832
5051
|
this.providers = inject(AXP_SEARCH_DEFINITION_PROVIDER, { optional: true });
|
|
5052
|
+
this.lazyResolver = inject(AXP_SEARCH_DEFINITION_LAZY_RESOLVER, { optional: true });
|
|
4833
5053
|
this.cache = null;
|
|
4834
5054
|
}
|
|
4835
5055
|
async load() {
|
|
@@ -4860,12 +5080,29 @@ class AXPSearchDefinitionProviderService {
|
|
|
4860
5080
|
}
|
|
4861
5081
|
}
|
|
4862
5082
|
findDefinition(definitionName) {
|
|
4863
|
-
const definition = this.getList().find((
|
|
5083
|
+
const definition = this.getList().find((item) => item.name === definitionName);
|
|
4864
5084
|
if (!definition) {
|
|
4865
5085
|
throw new Error(`Definition with name ${definitionName} not found`);
|
|
4866
5086
|
}
|
|
4867
5087
|
return definition;
|
|
4868
5088
|
}
|
|
5089
|
+
async findDefinitionAsync(definitionName) {
|
|
5090
|
+
await this.load();
|
|
5091
|
+
const existing = this.getList().find((item) => item.name === definitionName);
|
|
5092
|
+
if (existing) {
|
|
5093
|
+
return existing;
|
|
5094
|
+
}
|
|
5095
|
+
const lazy = this.lazyResolver;
|
|
5096
|
+
if (!lazy) {
|
|
5097
|
+
return null;
|
|
5098
|
+
}
|
|
5099
|
+
const resolved = await lazy.resolve(definitionName);
|
|
5100
|
+
if (!resolved) {
|
|
5101
|
+
return null;
|
|
5102
|
+
}
|
|
5103
|
+
this.cache = [...(this.cache ?? []), resolved];
|
|
5104
|
+
return resolved;
|
|
5105
|
+
}
|
|
4869
5106
|
findDefinitionByGroup(definitionGroup) {
|
|
4870
5107
|
const definition = this.getList().find((definition) => definition.group === definitionGroup);
|
|
4871
5108
|
if (!definition) {
|
|
@@ -4883,188 +5120,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
4883
5120
|
|
|
4884
5121
|
//#region ---- Imports ----
|
|
4885
5122
|
//#endregion
|
|
4886
|
-
//#region ----
|
|
4887
|
-
/**
|
|
4888
|
-
* Normalizes entity display templates for {@link AXPExpressionEvaluatorService}.
|
|
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
|
-
}
|
|
4898
|
-
/** True when formatted text still contains unresolved template markers. */
|
|
4899
|
-
function isUnresolvedEntityDisplayTemplate(value) {
|
|
4900
|
-
return /\{\{/.test(value) || /context\.eval\s*\(/.test(value) || value === '[object Object]';
|
|
4901
|
-
}
|
|
5123
|
+
//#region ---- Searchable text resolution ----
|
|
4902
5124
|
/**
|
|
4903
|
-
*
|
|
5125
|
+
* Resolves a value to plain text for global-search matching (i18n keys, locale maps, scalars).
|
|
4904
5126
|
*/
|
|
4905
|
-
function
|
|
4906
|
-
|
|
4907
|
-
if (!trimmed) {
|
|
5127
|
+
function resolveSearchableText(value, locale, translationService) {
|
|
5128
|
+
if (value == null || value === false) {
|
|
4908
5129
|
return '';
|
|
4909
5130
|
}
|
|
4910
|
-
|
|
4911
|
-
|
|
4912
|
-
.replace(/\bnull\b/gi, ' ')
|
|
4913
|
-
.replace(/\s+/g, ' ')
|
|
4914
|
-
.trim();
|
|
4915
|
-
return withoutLiterals
|
|
4916
|
-
.replace(/^[\s\-–—·|/,()+]+|[\s\-–—·|/,()+]+$/g, '')
|
|
4917
|
-
.trim();
|
|
4918
|
-
}
|
|
4919
|
-
/** True when a resolved title is non-empty and safe to show in search or entity chrome. */
|
|
4920
|
-
function isMeaningfulEntityDisplayTitle(value) {
|
|
4921
|
-
const sanitized = sanitizeResolvedEntityDisplayTitle(value);
|
|
4922
|
-
return sanitized.length > 0 && !isUnresolvedEntityDisplayTemplate(sanitized);
|
|
4923
|
-
}
|
|
4924
|
-
function isRowBoundTemplate(template) {
|
|
4925
|
-
const trimmed = template.trim();
|
|
4926
|
-
if (!trimmed || trimmed.startsWith('@')) {
|
|
4927
|
-
return false;
|
|
4928
|
-
}
|
|
4929
|
-
return trimmed.includes('{{') || trimmed.includes('{');
|
|
4930
|
-
}
|
|
4931
|
-
//#endregion
|
|
4932
|
-
//#region ---- Entity metadata ----
|
|
4933
|
-
const DEFAULT_DISPLAY_FIELD_CANDIDATES = [
|
|
4934
|
-
'title',
|
|
4935
|
-
'name',
|
|
4936
|
-
'code',
|
|
4937
|
-
'fullName',
|
|
4938
|
-
'displayName',
|
|
4939
|
-
'subject',
|
|
4940
|
-
'description',
|
|
4941
|
-
];
|
|
4942
|
-
function entityHasProperty(entity, path) {
|
|
4943
|
-
const root = path.split('.')[0];
|
|
4944
|
-
return entity.properties?.some((property) => property.name === root) ?? false;
|
|
4945
|
-
}
|
|
4946
|
-
function resolveEntityDisplayPropertyPaths(entity) {
|
|
4947
|
-
const paths = [];
|
|
4948
|
-
const add = (path) => {
|
|
4949
|
-
const trimmed = path?.trim();
|
|
4950
|
-
if (!trimmed || trimmed.includes('{') || paths.includes(trimmed)) {
|
|
4951
|
-
return;
|
|
4952
|
-
}
|
|
4953
|
-
if (entityHasProperty(entity, trimmed)) {
|
|
4954
|
-
paths.push(trimmed);
|
|
4955
|
-
}
|
|
4956
|
-
};
|
|
4957
|
-
const lookupFormat = entity.formats?.lookup?.trim();
|
|
4958
|
-
if (lookupFormat && !lookupFormat.includes('{')) {
|
|
4959
|
-
add(lookupFormat);
|
|
4960
|
-
}
|
|
4961
|
-
for (const candidate of DEFAULT_DISPLAY_FIELD_CANDIDATES) {
|
|
4962
|
-
add(candidate);
|
|
4963
|
-
}
|
|
4964
|
-
const identifierPlugin = entity.plugins?.find((plugin) => plugin.name === 'identifier');
|
|
4965
|
-
const identifierField = identifierPlugin?.options?.['field'];
|
|
4966
|
-
if (typeof identifierField === 'string') {
|
|
4967
|
-
add(identifierField);
|
|
4968
|
-
}
|
|
4969
|
-
if (paths.length === 0) {
|
|
4970
|
-
const firstStringProperty = entity.properties?.find((property) => property.schema?.dataType === 'string' && property.name !== 'id');
|
|
4971
|
-
add(firstStringProperty?.name);
|
|
4972
|
-
}
|
|
4973
|
-
return paths;
|
|
4974
|
-
}
|
|
4975
|
-
/**
|
|
4976
|
-
* Builds entity-scoped fallback metadata for global search (registered on each entity search definition).
|
|
4977
|
-
*/
|
|
4978
|
-
function buildEntitySearchTitleContext(entity, primaryTemplate) {
|
|
4979
|
-
const fallbackTemplates = [];
|
|
4980
|
-
const normalizedPrimary = primaryTemplate?.trim()
|
|
4981
|
-
? normalizeEntityDisplayTemplate(primaryTemplate.trim())
|
|
4982
|
-
: undefined;
|
|
4983
|
-
const addTemplate = (raw) => {
|
|
4984
|
-
const trimmed = raw?.trim();
|
|
4985
|
-
if (!trimmed || !isRowBoundTemplate(trimmed)) {
|
|
4986
|
-
return;
|
|
4987
|
-
}
|
|
4988
|
-
const normalized = normalizeEntityDisplayTemplate(trimmed);
|
|
4989
|
-
if (!normalized || normalized === normalizedPrimary || fallbackTemplates.includes(normalized)) {
|
|
4990
|
-
return;
|
|
4991
|
-
}
|
|
4992
|
-
fallbackTemplates.push(normalized);
|
|
4993
|
-
};
|
|
4994
|
-
addTemplate(entity.interfaces?.master?.single?.title);
|
|
4995
|
-
addTemplate(entity.formats?.lookup);
|
|
4996
|
-
addTemplate(entity.formats?.individual);
|
|
4997
|
-
return {
|
|
4998
|
-
fallbackTemplates,
|
|
4999
|
-
fallbackFields: resolveEntityDisplayPropertyPaths(entity),
|
|
5000
|
-
};
|
|
5001
|
-
}
|
|
5002
|
-
/**
|
|
5003
|
-
* Row title template chain aligned with entity layouts.
|
|
5004
|
-
* Priority: `interfaces.master.single.title` → `formats.searchResult.title` → `formats.individual`.
|
|
5005
|
-
*/
|
|
5006
|
-
function resolveEntityRowTitleTemplate(entity) {
|
|
5007
|
-
if (!entity) {
|
|
5008
|
-
return undefined;
|
|
5009
|
-
}
|
|
5010
|
-
const singleTitle = entity.interfaces?.master?.single?.title?.trim();
|
|
5011
|
-
if (singleTitle && isRowBoundTemplate(singleTitle)) {
|
|
5012
|
-
return normalizeEntityDisplayTemplate(singleTitle);
|
|
5013
|
-
}
|
|
5014
|
-
const searchTitle = entity.formats?.searchResult?.title?.trim();
|
|
5015
|
-
if (searchTitle && isRowBoundTemplate(searchTitle)) {
|
|
5016
|
-
return normalizeEntityDisplayTemplate(searchTitle);
|
|
5017
|
-
}
|
|
5018
|
-
const lookupFormat = entity.formats?.lookup?.trim();
|
|
5019
|
-
if (lookupFormat && isRowBoundTemplate(lookupFormat)) {
|
|
5020
|
-
return normalizeEntityDisplayTemplate(lookupFormat);
|
|
5021
|
-
}
|
|
5022
|
-
const individual = entity.formats?.individual?.trim();
|
|
5023
|
-
if (individual && isRowBoundTemplate(individual)) {
|
|
5024
|
-
return normalizeEntityDisplayTemplate(individual);
|
|
5025
|
-
}
|
|
5026
|
-
return undefined;
|
|
5027
|
-
}
|
|
5028
|
-
//#endregion
|
|
5029
|
-
//#region ---- Runtime resolution ----
|
|
5030
|
-
/**
|
|
5031
|
-
* Resolves an entity row title using entity-driven fallback templates and property paths.
|
|
5032
|
-
*/
|
|
5033
|
-
async function resolveEntitySearchTitleFallback(format, data, locale, translationService, formatService, expressionEvaluator) {
|
|
5034
|
-
if (data == null) {
|
|
5035
|
-
return '';
|
|
5036
|
-
}
|
|
5037
|
-
for (const template of format.titleFallbackTemplates ?? []) {
|
|
5038
|
-
const resolved = await resolveSearchDisplayTemplate(template, data, locale, translationService, formatService, expressionEvaluator);
|
|
5039
|
-
const text = sanitizeResolvedEntityDisplayTitle(typeof resolved === 'string'
|
|
5040
|
-
? resolved
|
|
5041
|
-
: resolveSearchableText(resolved, locale, translationService));
|
|
5042
|
-
if (isMeaningfulEntityDisplayTitle(text)) {
|
|
5043
|
-
return text;
|
|
5044
|
-
}
|
|
5045
|
-
}
|
|
5046
|
-
for (const path of format.titleFallbackFields ?? []) {
|
|
5047
|
-
const text = resolveSearchableText(get(data, path), locale, translationService).trim();
|
|
5048
|
-
if (text) {
|
|
5049
|
-
return text;
|
|
5050
|
-
}
|
|
5051
|
-
}
|
|
5052
|
-
return '';
|
|
5053
|
-
}
|
|
5054
|
-
//#endregion
|
|
5055
|
-
|
|
5056
|
-
//#region ---- Imports ----
|
|
5057
|
-
//#endregion
|
|
5058
|
-
//#region ---- Searchable text resolution ----
|
|
5059
|
-
/**
|
|
5060
|
-
* Resolves a value to plain text for global-search matching (i18n keys, locale maps, scalars).
|
|
5061
|
-
*/
|
|
5062
|
-
function resolveSearchableText(value, locale, translationService) {
|
|
5063
|
-
if (value == null || value === false) {
|
|
5064
|
-
return '';
|
|
5065
|
-
}
|
|
5066
|
-
if (typeof value === 'number' || typeof value === 'boolean') {
|
|
5067
|
-
return String(value);
|
|
5131
|
+
if (typeof value === 'number' || typeof value === 'boolean') {
|
|
5132
|
+
return String(value);
|
|
5068
5133
|
}
|
|
5069
5134
|
if (typeof value === 'string') {
|
|
5070
5135
|
const trimmed = value.trim();
|
|
@@ -5076,13 +5141,8 @@ function resolveSearchableText(value, locale, translationService) {
|
|
|
5076
5141
|
}
|
|
5077
5142
|
return trimmed;
|
|
5078
5143
|
}
|
|
5079
|
-
if (isLocaleStringMap(value)) {
|
|
5080
|
-
return
|
|
5081
|
-
.filter((entry) => (entry ?? '').trim() !== '')
|
|
5082
|
-
.join(' ');
|
|
5083
|
-
}
|
|
5084
|
-
if (translationService.isValidMultiLanguageObject(value)) {
|
|
5085
|
-
return resolveMultiLanguageString(value, locale);
|
|
5144
|
+
if (isLocaleStringMap(value) || translationService.isValidMultiLanguageObject(value)) {
|
|
5145
|
+
return resolveMultiLanguageString(value, locale).trim();
|
|
5086
5146
|
}
|
|
5087
5147
|
return '';
|
|
5088
5148
|
}
|
|
@@ -5094,6 +5154,11 @@ function searchInSearchableValue(value, query, locale, translationService) {
|
|
|
5094
5154
|
if (!q) {
|
|
5095
5155
|
return true;
|
|
5096
5156
|
}
|
|
5157
|
+
if (isLocaleStringMap(value)) {
|
|
5158
|
+
return Object.values(value)
|
|
5159
|
+
.filter((entry) => typeof entry === 'string' && entry.trim() !== '')
|
|
5160
|
+
.some((entry) => entry.toLocaleLowerCase(locale).includes(q));
|
|
5161
|
+
}
|
|
5097
5162
|
const text = resolveSearchableText(value, locale, translationService).toLocaleLowerCase(locale);
|
|
5098
5163
|
return text.includes(q);
|
|
5099
5164
|
}
|
|
@@ -5126,26 +5191,31 @@ function searchInRecordValues(value, query, locale, translationService, depth =
|
|
|
5126
5191
|
function countMustacheExpressions(template) {
|
|
5127
5192
|
return (template.match(/\{\{/g) ?? []).length;
|
|
5128
5193
|
}
|
|
5129
|
-
/** Standard row scope for entity title/search templates. */
|
|
5130
|
-
function createEntityRowExpressionScope(data) {
|
|
5194
|
+
/** Standard display-tier row scope for entity title/search templates. */
|
|
5195
|
+
function createEntityRowExpressionScope(data, contextEvalFactory) {
|
|
5131
5196
|
return {
|
|
5132
|
-
context: {
|
|
5133
|
-
eval: (path) => get(data, path),
|
|
5134
|
-
},
|
|
5197
|
+
context: contextEvalFactory.createScope((data ?? {})),
|
|
5135
5198
|
};
|
|
5136
5199
|
}
|
|
5200
|
+
/**
|
|
5201
|
+
* Coerces an evaluated display segment to plain text.
|
|
5202
|
+
* Piped eval returns strings; unpiped MLS still resolves via compat fallback.
|
|
5203
|
+
*/
|
|
5204
|
+
function formatDisplaySegment(segment, locale, translationService) {
|
|
5205
|
+
if (segment === false || segment == null) {
|
|
5206
|
+
return '';
|
|
5207
|
+
}
|
|
5208
|
+
if (isString(segment)) {
|
|
5209
|
+
return segment.trim();
|
|
5210
|
+
}
|
|
5211
|
+
return resolveSearchableText(segment, locale, translationService);
|
|
5212
|
+
}
|
|
5137
5213
|
/**
|
|
5138
5214
|
* Evaluates a normalized entity display template via {@link AXPExpressionEvaluatorService}.
|
|
5139
5215
|
*/
|
|
5140
|
-
async function evaluateEntityDisplayTemplate(template, data, expressionEvaluator, locale, translationService) {
|
|
5216
|
+
async function evaluateEntityDisplayTemplate(template, data, expressionEvaluator, locale, translationService, contextEvalFactory) {
|
|
5141
5217
|
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
|
-
};
|
|
5218
|
+
const scope = createEntityRowExpressionScope(data, contextEvalFactory);
|
|
5149
5219
|
if (!normalized.includes('{{')) {
|
|
5150
5220
|
return normalized;
|
|
5151
5221
|
}
|
|
@@ -5159,7 +5229,7 @@ async function evaluateEntityDisplayTemplate(template, data, expressionEvaluator
|
|
|
5159
5229
|
for (const match of normalized.matchAll(regex)) {
|
|
5160
5230
|
result += normalized.slice(lastIndex, match.index);
|
|
5161
5231
|
const segment = await expressionEvaluator.evaluate(`{{ ${match[1].trim()} }}`, scope);
|
|
5162
|
-
result +=
|
|
5232
|
+
result += formatDisplaySegment(segment, locale, translationService);
|
|
5163
5233
|
lastIndex = (match.index ?? 0) + match[0].length;
|
|
5164
5234
|
}
|
|
5165
5235
|
result += normalized.slice(lastIndex);
|
|
@@ -5206,7 +5276,7 @@ function resolveQueryMatchingDisplayText(value, query, locale, translationServic
|
|
|
5206
5276
|
/**
|
|
5207
5277
|
* Resolves entity search display templates; coerces multi-language field values to readable text.
|
|
5208
5278
|
*/
|
|
5209
|
-
async function resolveSearchDisplayTemplate(template, data, locale, translationService, formatService, expressionEvaluator) {
|
|
5279
|
+
async function resolveSearchDisplayTemplate(template, data, locale, translationService, formatService, expressionEvaluator, contextEvalFactory) {
|
|
5210
5280
|
if (!template) {
|
|
5211
5281
|
return '';
|
|
5212
5282
|
}
|
|
@@ -5217,7 +5287,7 @@ async function resolveSearchDisplayTemplate(template, data, locale, translationS
|
|
|
5217
5287
|
const formatted = formatService.format(template, 'string', data);
|
|
5218
5288
|
return sanitizeResolvedEntityDisplayTitle(typeof formatted === 'string' ? formatted : String(formatted ?? ''));
|
|
5219
5289
|
}
|
|
5220
|
-
const evaluated = await evaluateEntityDisplayTemplate(template, data, expressionEvaluator, locale, translationService);
|
|
5290
|
+
const evaluated = await evaluateEntityDisplayTemplate(template, data, expressionEvaluator, locale, translationService, contextEvalFactory);
|
|
5221
5291
|
return sanitizeResolvedEntityDisplayTitle(resolveSearchableText(evaluated, locale, translationService));
|
|
5222
5292
|
}
|
|
5223
5293
|
//#endregion
|
|
@@ -5233,6 +5303,56 @@ class AXPSearchCommandProvider {
|
|
|
5233
5303
|
}
|
|
5234
5304
|
}
|
|
5235
5305
|
|
|
5306
|
+
//#region ---- Imports ----
|
|
5307
|
+
//#endregion
|
|
5308
|
+
//#region ---- Runtime resolution ----
|
|
5309
|
+
/**
|
|
5310
|
+
* Modify/edit popup chrome: `{action} {individual} ({update.title ?? single.title})` when a record template exists.
|
|
5311
|
+
*/
|
|
5312
|
+
async function resolveEntityModifyPopupTitle(entity, recordData, translationService, formatService, expressionEvaluator, contextEvalFactory, actionTitleKey = '@general:actions.modify.title') {
|
|
5313
|
+
const actionPrefix = translationService.translateSync(actionTitleKey);
|
|
5314
|
+
const entityLabel = translationService.translateSync(entity.formats.individual);
|
|
5315
|
+
const baseTitle = `${actionPrefix} ${entityLabel}`.trim();
|
|
5316
|
+
const recordTemplate = resolveEntityModifyRecordTitleTemplate(entity);
|
|
5317
|
+
if (!recordTemplate) {
|
|
5318
|
+
return baseTitle;
|
|
5319
|
+
}
|
|
5320
|
+
const locale = translationService.getActiveLang();
|
|
5321
|
+
const resolved = await resolveSearchDisplayTemplate(recordTemplate, recordData, locale, translationService, formatService, expressionEvaluator, contextEvalFactory);
|
|
5322
|
+
const recordTitle = sanitizeResolvedEntityDisplayTitle(typeof resolved === 'string'
|
|
5323
|
+
? resolved
|
|
5324
|
+
: resolveSearchableText(resolved, locale, translationService));
|
|
5325
|
+
if (!isMeaningfulEntityDisplayTitle(recordTitle)) {
|
|
5326
|
+
return baseTitle;
|
|
5327
|
+
}
|
|
5328
|
+
return `${baseTitle} (${recordTitle})`;
|
|
5329
|
+
}
|
|
5330
|
+
/**
|
|
5331
|
+
* Resolves an entity row title using entity-driven fallback templates and property paths.
|
|
5332
|
+
*/
|
|
5333
|
+
async function resolveEntitySearchTitleFallback(format, data, locale, translationService, formatService, expressionEvaluator, contextEvalFactory) {
|
|
5334
|
+
if (data == null) {
|
|
5335
|
+
return '';
|
|
5336
|
+
}
|
|
5337
|
+
for (const template of format.titleFallbackTemplates ?? []) {
|
|
5338
|
+
const resolved = await resolveSearchDisplayTemplate(template, data, locale, translationService, formatService, expressionEvaluator, contextEvalFactory);
|
|
5339
|
+
const text = sanitizeResolvedEntityDisplayTitle(typeof resolved === 'string'
|
|
5340
|
+
? resolved
|
|
5341
|
+
: resolveSearchableText(resolved, locale, translationService));
|
|
5342
|
+
if (isMeaningfulEntityDisplayTitle(text)) {
|
|
5343
|
+
return text;
|
|
5344
|
+
}
|
|
5345
|
+
}
|
|
5346
|
+
for (const path of format.titleFallbackFields ?? []) {
|
|
5347
|
+
const text = resolveSearchableText(get(data, path), locale, translationService).trim();
|
|
5348
|
+
if (text) {
|
|
5349
|
+
return text;
|
|
5350
|
+
}
|
|
5351
|
+
}
|
|
5352
|
+
return '';
|
|
5353
|
+
}
|
|
5354
|
+
//#endregion
|
|
5355
|
+
|
|
5236
5356
|
// Injection token for setting providers
|
|
5237
5357
|
const AXP_SEARCH_PROVIDER = new InjectionToken('AXP_SEARCH_PROVIDER');
|
|
5238
5358
|
class AXPSearchService {
|
|
@@ -5242,6 +5362,7 @@ class AXPSearchService {
|
|
|
5242
5362
|
this.formatService = inject(AXFormatService);
|
|
5243
5363
|
this.translationService = inject(AXTranslationService);
|
|
5244
5364
|
this.expressionEvaluator = inject(AXPExpressionEvaluatorService);
|
|
5365
|
+
this.contextEvalFactory = inject(AXPContextEvalFactory);
|
|
5245
5366
|
}
|
|
5246
5367
|
async search(text) {
|
|
5247
5368
|
//TODO better handle this
|
|
@@ -5271,20 +5392,23 @@ class AXPSearchService {
|
|
|
5271
5392
|
for (const resultArray of results) {
|
|
5272
5393
|
for (const result of resultArray) {
|
|
5273
5394
|
try {
|
|
5274
|
-
const definition = this.definitionService.
|
|
5395
|
+
const definition = await this.definitionService.findDefinitionAsync(result.group);
|
|
5396
|
+
if (!definition) {
|
|
5397
|
+
continue;
|
|
5398
|
+
}
|
|
5275
5399
|
const resolvedTitle = result.title ??
|
|
5276
5400
|
(definition.format.title
|
|
5277
|
-
? await resolveSearchDisplayTemplate(definition.format.title, result.data, locale, this.translationService, this.formatService, this.expressionEvaluator)
|
|
5401
|
+
? await resolveSearchDisplayTemplate(definition.format.title, result.data, locale, this.translationService, this.formatService, this.expressionEvaluator, this.contextEvalFactory)
|
|
5278
5402
|
: '');
|
|
5279
5403
|
const resolvedDescription = result.description ??
|
|
5280
5404
|
(definition.format.description
|
|
5281
|
-
? await resolveSearchDisplayTemplate(definition.format.description, result.data, locale, this.translationService, this.formatService, this.expressionEvaluator)
|
|
5405
|
+
? await resolveSearchDisplayTemplate(definition.format.description, result.data, locale, this.translationService, this.formatService, this.expressionEvaluator, this.contextEvalFactory)
|
|
5282
5406
|
: undefined);
|
|
5283
5407
|
let titleText = sanitizeResolvedEntityDisplayTitle(typeof resolvedTitle === 'string'
|
|
5284
5408
|
? resolvedTitle
|
|
5285
5409
|
: resolveSearchableText(resolvedTitle, locale, this.translationService));
|
|
5286
5410
|
if (!isMeaningfulEntityDisplayTitle(titleText)) {
|
|
5287
|
-
titleText = await resolveEntitySearchTitleFallback(definition.format, result.data, locale, this.translationService, this.formatService, this.expressionEvaluator);
|
|
5411
|
+
titleText = await resolveEntitySearchTitleFallback(definition.format, result.data, locale, this.translationService, this.formatService, this.expressionEvaluator, this.contextEvalFactory);
|
|
5288
5412
|
}
|
|
5289
5413
|
if (!isMeaningfulEntityDisplayTitle(titleText)) {
|
|
5290
5414
|
continue;
|
|
@@ -5349,12 +5473,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
5349
5473
|
//#region ---- Constants ----
|
|
5350
5474
|
const INVALID_DISPLAY_TEXT = '[object Object]';
|
|
5351
5475
|
const DEFAULT_ROW_TITLE_FALLBACK_PATHS = ['title', 'name', 'code'];
|
|
5352
|
-
/** Row path used when context carries a nested entity layout title. */
|
|
5353
|
-
const ENTITY_LAYOUT_SINGLE_TITLE_PATH = 'entity.layout.single.title';
|
|
5354
|
-
/** Widget option for a pre-evaluated or template entity row title (entity list column mappers). */
|
|
5355
|
-
const ENTITY_ROW_POPUP_TITLE_OPTION = 'popupTitle';
|
|
5356
|
-
/** Widget option alias for entity row title templates in view/edit forms. */
|
|
5357
|
-
const ENTITY_ROW_TITLE_TEMPLATE_OPTION = 'titleTemplate';
|
|
5358
5476
|
//#endregion
|
|
5359
5477
|
//#region ---- Value normalization ----
|
|
5360
5478
|
/** Resolves a row field or format result to a plain display string for the active locale. */
|
|
@@ -5658,126 +5776,1106 @@ class AXPTokenDefinitionService {
|
|
|
5658
5776
|
flattenedProviders.push(provider);
|
|
5659
5777
|
}
|
|
5660
5778
|
}
|
|
5661
|
-
const match = flattenedProviders.find(def => def?.name === name);
|
|
5662
|
-
if (match) {
|
|
5663
|
-
this.definitionsByName.set(name, match);
|
|
5664
|
-
return match;
|
|
5779
|
+
const match = flattenedProviders.find(def => def?.name === name);
|
|
5780
|
+
if (match) {
|
|
5781
|
+
this.definitionsByName.set(name, match);
|
|
5782
|
+
return match;
|
|
5783
|
+
}
|
|
5784
|
+
return undefined;
|
|
5785
|
+
}
|
|
5786
|
+
/**
|
|
5787
|
+
* Gets the resolved value of a token by its name.
|
|
5788
|
+
* @param name The token name.
|
|
5789
|
+
* @returns The resolved token value, or undefined if the token is not found.
|
|
5790
|
+
*/
|
|
5791
|
+
async getValue(name) {
|
|
5792
|
+
const definition = await this.getDefinition(name);
|
|
5793
|
+
if (definition) {
|
|
5794
|
+
return await definition.execute();
|
|
5795
|
+
}
|
|
5796
|
+
return undefined;
|
|
5797
|
+
}
|
|
5798
|
+
//#endregion
|
|
5799
|
+
//#region ---- Cache Management ----
|
|
5800
|
+
/** Clears the definitions by name cache. */
|
|
5801
|
+
clearDefinitionsCache() {
|
|
5802
|
+
this.definitionsByName.clear();
|
|
5803
|
+
}
|
|
5804
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPTokenDefinitionService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
5805
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPTokenDefinitionService, providedIn: 'root' }); }
|
|
5806
|
+
}
|
|
5807
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPTokenDefinitionService, decorators: [{
|
|
5808
|
+
type: Injectable,
|
|
5809
|
+
args: [{
|
|
5810
|
+
providedIn: 'root',
|
|
5811
|
+
}]
|
|
5812
|
+
}] });
|
|
5813
|
+
|
|
5814
|
+
class AXPTokenEvaluatorScopeProvider {
|
|
5815
|
+
constructor() {
|
|
5816
|
+
this.tokenService = inject(AXPTokenDefinitionService);
|
|
5817
|
+
}
|
|
5818
|
+
async provide(context) {
|
|
5819
|
+
context.addScope('tokens', {
|
|
5820
|
+
get: async (name) => {
|
|
5821
|
+
return await this.tokenService.getValue(name);
|
|
5822
|
+
}
|
|
5823
|
+
});
|
|
5824
|
+
}
|
|
5825
|
+
}
|
|
5826
|
+
|
|
5827
|
+
//#region ---- Tab Key ----
|
|
5828
|
+
/** Query params ignored when building a tab dedupe key. */
|
|
5829
|
+
const IGNORED_QUERY_KEYS = new Set(['_ts']);
|
|
5830
|
+
/**
|
|
5831
|
+
* Builds a stable tab key from a router URL.
|
|
5832
|
+
* Path segments define identity; query params are excluded so the same entity record
|
|
5833
|
+
* or list reuses one tab even when filters or refresh tokens change.
|
|
5834
|
+
*/
|
|
5835
|
+
function axpBuildTabKey(url) {
|
|
5836
|
+
const normalized = normalizeRouterUrl(url);
|
|
5837
|
+
const pathOnly = normalized.split('?')[0].split('#')[0];
|
|
5838
|
+
return pathOnly;
|
|
5839
|
+
}
|
|
5840
|
+
/** Normalizes a router URL for storage and navigation. */
|
|
5841
|
+
function normalizeRouterUrl(url) {
|
|
5842
|
+
const trimmed = url.trim();
|
|
5843
|
+
if (!trimmed.length) {
|
|
5844
|
+
return '/';
|
|
5845
|
+
}
|
|
5846
|
+
const withLeadingSlash = trimmed.startsWith('/') ? trimmed : `/${trimmed}`;
|
|
5847
|
+
const [pathAndFragment, queryString = ''] = withLeadingSlash.split('?');
|
|
5848
|
+
const [pathPart, fragment = ''] = pathAndFragment.split('#');
|
|
5849
|
+
const normalizedPath = pathPart.replace(/\/+$/, '') || '/';
|
|
5850
|
+
const filteredQuery = filterQueryString(queryString);
|
|
5851
|
+
let result = normalizedPath;
|
|
5852
|
+
if (filteredQuery) {
|
|
5853
|
+
result += `?${filteredQuery}`;
|
|
5854
|
+
}
|
|
5855
|
+
if (fragment) {
|
|
5856
|
+
result += `#${fragment}`;
|
|
5857
|
+
}
|
|
5858
|
+
return result;
|
|
5859
|
+
}
|
|
5860
|
+
function filterQueryString(queryString) {
|
|
5861
|
+
if (!queryString) {
|
|
5862
|
+
return '';
|
|
5863
|
+
}
|
|
5864
|
+
const params = new URLSearchParams(queryString);
|
|
5865
|
+
const kept = [];
|
|
5866
|
+
params.forEach((value, key) => {
|
|
5867
|
+
if (!IGNORED_QUERY_KEYS.has(key)) {
|
|
5868
|
+
kept.push(`${encodeURIComponent(key)}=${encodeURIComponent(value)}`);
|
|
5869
|
+
}
|
|
5870
|
+
});
|
|
5871
|
+
kept.sort();
|
|
5872
|
+
return kept.join('&');
|
|
5873
|
+
}
|
|
5874
|
+
/** Human-readable fallback title from the URL path. */
|
|
5875
|
+
function axpFallbackTabTitle(url) {
|
|
5876
|
+
const pathOnly = url.split('?')[0].split('#')[0];
|
|
5877
|
+
const segments = pathOnly.split('/').filter(Boolean);
|
|
5878
|
+
const last = defaultTo(segments[segments.length - 1], 'Page');
|
|
5879
|
+
return decodeURIComponent(last);
|
|
5880
|
+
}
|
|
5881
|
+
//#endregion
|
|
5882
|
+
|
|
5883
|
+
//#region ---- Types ----
|
|
5884
|
+
const AXP_HOME_TAB_KEY = 'axp:home';
|
|
5885
|
+
/** Route `data` key for tab-navigation behavior (`data.tab`). */
|
|
5886
|
+
const AXP_TAB_ROUTE_DATA_KEY = 'tab';
|
|
5887
|
+
//#endregion
|
|
5888
|
+
|
|
5889
|
+
//#region ---- Tab Preview Service ----
|
|
5890
|
+
/**
|
|
5891
|
+
* In-memory structured previews for tab hover cards and dirty indicators.
|
|
5892
|
+
* Populated by active/cached pages via {@link AXPPageLayoutBaseComponent}.
|
|
5893
|
+
*/
|
|
5894
|
+
class AXPTabPreviewService {
|
|
5895
|
+
constructor() {
|
|
5896
|
+
//#region ---- State ----
|
|
5897
|
+
this.previewMap = signal({}, ...(ngDevMode ? [{ debugName: "previewMap" }] : /* istanbul ignore next */ []));
|
|
5898
|
+
this.previews = this.previewMap.asReadonly();
|
|
5899
|
+
}
|
|
5900
|
+
//#endregion
|
|
5901
|
+
//#region ---- Public API ----
|
|
5902
|
+
setPreview(key, preview) {
|
|
5903
|
+
this.previewMap.update((current) => ({
|
|
5904
|
+
...current,
|
|
5905
|
+
[key]: preview,
|
|
5906
|
+
}));
|
|
5907
|
+
}
|
|
5908
|
+
patchPreview(key, patch) {
|
|
5909
|
+
this.previewMap.update((current) => {
|
|
5910
|
+
const existing = current[key];
|
|
5911
|
+
if (!existing) {
|
|
5912
|
+
if (!patch.url) {
|
|
5913
|
+
return current;
|
|
5914
|
+
}
|
|
5915
|
+
return {
|
|
5916
|
+
...current,
|
|
5917
|
+
[key]: {
|
|
5918
|
+
title: patch.title ?? '',
|
|
5919
|
+
url: patch.url,
|
|
5920
|
+
icon: patch.icon,
|
|
5921
|
+
subtitle: patch.subtitle,
|
|
5922
|
+
isDirty: patch.isDirty ?? false,
|
|
5923
|
+
breadcrumbTrail: patch.breadcrumbTrail ?? [],
|
|
5924
|
+
},
|
|
5925
|
+
};
|
|
5926
|
+
}
|
|
5927
|
+
return {
|
|
5928
|
+
...current,
|
|
5929
|
+
[key]: {
|
|
5930
|
+
...existing,
|
|
5931
|
+
...patch,
|
|
5932
|
+
},
|
|
5933
|
+
};
|
|
5934
|
+
});
|
|
5935
|
+
}
|
|
5936
|
+
getPreview(key) {
|
|
5937
|
+
return this.previewMap()[key];
|
|
5938
|
+
}
|
|
5939
|
+
isTabDirty(key) {
|
|
5940
|
+
return !!this.previewMap()[key]?.isDirty;
|
|
5941
|
+
}
|
|
5942
|
+
clearPreview(key) {
|
|
5943
|
+
this.previewMap.update((current) => {
|
|
5944
|
+
if (!(key in current)) {
|
|
5945
|
+
return current;
|
|
5946
|
+
}
|
|
5947
|
+
const next = { ...current };
|
|
5948
|
+
delete next[key];
|
|
5949
|
+
return next;
|
|
5950
|
+
});
|
|
5951
|
+
}
|
|
5952
|
+
clearPreviews(keys) {
|
|
5953
|
+
const keySet = new Set(keys);
|
|
5954
|
+
if (!keySet.size) {
|
|
5955
|
+
return;
|
|
5956
|
+
}
|
|
5957
|
+
this.previewMap.update((current) => {
|
|
5958
|
+
const next = { ...current };
|
|
5959
|
+
let changed = false;
|
|
5960
|
+
for (const key of keySet) {
|
|
5961
|
+
if (key in next) {
|
|
5962
|
+
delete next[key];
|
|
5963
|
+
changed = true;
|
|
5964
|
+
}
|
|
5965
|
+
}
|
|
5966
|
+
return changed ? next : current;
|
|
5967
|
+
});
|
|
5968
|
+
}
|
|
5969
|
+
clearAll() {
|
|
5970
|
+
this.previewMap.set({});
|
|
5971
|
+
}
|
|
5972
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPTabPreviewService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
5973
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPTabPreviewService, providedIn: 'root' }); }
|
|
5974
|
+
}
|
|
5975
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPTabPreviewService, decorators: [{
|
|
5976
|
+
type: Injectable,
|
|
5977
|
+
args: [{
|
|
5978
|
+
providedIn: 'root',
|
|
5979
|
+
}]
|
|
5980
|
+
}] });
|
|
5981
|
+
|
|
5982
|
+
//#region ---- Tab Route Policy ----
|
|
5983
|
+
const EXCLUDED_PATH_PREFIXES = ['/auth', '/error'];
|
|
5984
|
+
const EXCLUDED_PATH_PATTERNS = [
|
|
5985
|
+
/^\/assessment\/fill\//i,
|
|
5986
|
+
/^\/forms\//i,
|
|
5987
|
+
];
|
|
5988
|
+
/**
|
|
5989
|
+
* Returns true when the URL represents an in-app page that may appear in the tab bar.
|
|
5990
|
+
*/
|
|
5991
|
+
function axpIsTabEligibleUrl(url) {
|
|
5992
|
+
const pathOnly = url.split('?')[0].split('#')[0].trim();
|
|
5993
|
+
if (!pathOnly || pathOnly === '/') {
|
|
5994
|
+
return false;
|
|
5995
|
+
}
|
|
5996
|
+
const normalized = pathOnly.startsWith('/') ? pathOnly : `/${pathOnly}`;
|
|
5997
|
+
if (EXCLUDED_PATH_PREFIXES.some((prefix) => normalized.startsWith(prefix))) {
|
|
5998
|
+
return false;
|
|
5999
|
+
}
|
|
6000
|
+
if (EXCLUDED_PATH_PATTERNS.some((pattern) => pattern.test(normalized))) {
|
|
6001
|
+
return false;
|
|
6002
|
+
}
|
|
6003
|
+
return true;
|
|
6004
|
+
}
|
|
6005
|
+
//#endregion
|
|
6006
|
+
|
|
6007
|
+
//#region ---- Route Data ----
|
|
6008
|
+
/** True when the route declares itself as a scoped tab root (`data.tab = true`). */
|
|
6009
|
+
function axpHasTabScope(data) {
|
|
6010
|
+
return data?.[AXP_TAB_ROUTE_DATA_KEY] === true;
|
|
6011
|
+
}
|
|
6012
|
+
//#endregion
|
|
6013
|
+
//#region ---- Snapshot Resolution ----
|
|
6014
|
+
/** Resolved URL path for a snapshot (no query or fragment). */
|
|
6015
|
+
function axpPathFromSnapshot(snapshot) {
|
|
6016
|
+
const segments = snapshot.pathFromRoot
|
|
6017
|
+
.flatMap((entry) => entry.url.map((segment) => segment.path))
|
|
6018
|
+
.filter(Boolean);
|
|
6019
|
+
return segments.length ? `/${segments.join('/')}` : '/';
|
|
6020
|
+
}
|
|
6021
|
+
/** Returns the leaf snapshot under the router state root. */
|
|
6022
|
+
function axpGetLeafRouteSnapshot(root) {
|
|
6023
|
+
let route = root;
|
|
6024
|
+
while (route.firstChild) {
|
|
6025
|
+
route = route.firstChild;
|
|
6026
|
+
}
|
|
6027
|
+
return route;
|
|
6028
|
+
}
|
|
6029
|
+
/**
|
|
6030
|
+
* Resolves the tab dedupe key from an activated route tree.
|
|
6031
|
+
* Uses the nearest ancestor with `data.tab = true`; otherwise the leaf path.
|
|
6032
|
+
*/
|
|
6033
|
+
function axpResolveTabKeyFromSnapshot(leaf) {
|
|
6034
|
+
const chain = leaf.pathFromRoot;
|
|
6035
|
+
for (let index = chain.length - 1; index >= 0; index--) {
|
|
6036
|
+
const snapshot = chain[index];
|
|
6037
|
+
if (axpHasTabScope(snapshot.data)) {
|
|
6038
|
+
return axpPathFromSnapshot(snapshot);
|
|
6039
|
+
}
|
|
6040
|
+
}
|
|
6041
|
+
return axpPathFromSnapshot(leaf);
|
|
6042
|
+
}
|
|
6043
|
+
//#endregion
|
|
6044
|
+
//#region ---- Router / URL Resolution ----
|
|
6045
|
+
/**
|
|
6046
|
+
* Resolves a tab key for the given URL (or the current router URL).
|
|
6047
|
+
* Prefers the live route snapshot when it matches the target URL; otherwise matches route config.
|
|
6048
|
+
*/
|
|
6049
|
+
function axpResolveTabKey(router, url) {
|
|
6050
|
+
const normalized = normalizeRouterUrl(url ?? router.url);
|
|
6051
|
+
const pathOnly = normalized.split('?')[0].split('#')[0];
|
|
6052
|
+
const currentPath = normalizeRouterUrl(router.url).split('?')[0].split('#')[0];
|
|
6053
|
+
if (!url || pathOnly === currentPath) {
|
|
6054
|
+
const leaf = axpGetLeafRouteSnapshot(router.routerState.snapshot.root);
|
|
6055
|
+
return axpResolveTabKeyFromSnapshot(leaf);
|
|
6056
|
+
}
|
|
6057
|
+
const fromConfig = axpResolveTabKeyFromRouteConfig(router.config, pathOnly);
|
|
6058
|
+
if (fromConfig) {
|
|
6059
|
+
return fromConfig;
|
|
6060
|
+
}
|
|
6061
|
+
return pathOnly.startsWith('/') ? pathOnly : `/${pathOnly}`;
|
|
6062
|
+
}
|
|
6063
|
+
/**
|
|
6064
|
+
* Matches a URL path against registered routes and resolves the tab key from `data.tab`.
|
|
6065
|
+
*/
|
|
6066
|
+
function axpResolveTabKeyFromRouteConfig(routes, pathOnly) {
|
|
6067
|
+
const normalized = pathOnly.startsWith('/') ? pathOnly.replace(/\/+$/, '') || '/' : `/${pathOnly}`.replace(/\/+$/, '') || '/';
|
|
6068
|
+
const segments = normalized === '/' ? [] : normalized.split('/').filter(Boolean);
|
|
6069
|
+
const chain = matchRouteConfigChain(routes, segments, 0, {}, []);
|
|
6070
|
+
if (!chain?.length) {
|
|
6071
|
+
return null;
|
|
6072
|
+
}
|
|
6073
|
+
for (let index = chain.length - 1; index >= 0; index--) {
|
|
6074
|
+
const level = chain[index];
|
|
6075
|
+
if (axpHasTabScope(level.mergedData)) {
|
|
6076
|
+
return level.path;
|
|
6077
|
+
}
|
|
6078
|
+
}
|
|
6079
|
+
return chain[chain.length - 1]?.path ?? null;
|
|
6080
|
+
}
|
|
6081
|
+
function matchRouteConfigChain(routes, segments, startIndex, inheritedData, chain) {
|
|
6082
|
+
let bestMatch = null;
|
|
6083
|
+
for (const route of routes) {
|
|
6084
|
+
if (route.redirectTo) {
|
|
6085
|
+
continue;
|
|
6086
|
+
}
|
|
6087
|
+
const segmentMatch = matchRoutePath(route.path ?? '', segments, startIndex);
|
|
6088
|
+
if (!segmentMatch) {
|
|
6089
|
+
continue;
|
|
6090
|
+
}
|
|
6091
|
+
const pathSegments = [...segments.slice(0, startIndex), ...segments.slice(startIndex, startIndex + segmentMatch.consumed)];
|
|
6092
|
+
const path = pathSegments.length ? `/${pathSegments.join('/')}` : '/';
|
|
6093
|
+
const mergedData = { ...inheritedData, ...(route.data ?? {}) };
|
|
6094
|
+
const level = { mergedData, path };
|
|
6095
|
+
const nextChain = [...chain, level];
|
|
6096
|
+
const nextIndex = startIndex + segmentMatch.consumed;
|
|
6097
|
+
if (route.children?.length) {
|
|
6098
|
+
const childMatch = matchRouteConfigChain(route.children, segments, nextIndex, mergedData, nextChain);
|
|
6099
|
+
if (childMatch && (!bestMatch || childMatch.length > bestMatch.length)) {
|
|
6100
|
+
bestMatch = childMatch;
|
|
6101
|
+
}
|
|
6102
|
+
}
|
|
6103
|
+
if (nextIndex === segments.length) {
|
|
6104
|
+
if (!bestMatch || nextChain.length > bestMatch.length) {
|
|
6105
|
+
bestMatch = nextChain;
|
|
6106
|
+
}
|
|
6107
|
+
}
|
|
6108
|
+
}
|
|
6109
|
+
return bestMatch;
|
|
6110
|
+
}
|
|
6111
|
+
function matchRoutePath(routePath, segments, startIndex) {
|
|
6112
|
+
if (routePath === '**') {
|
|
6113
|
+
return { consumed: Math.max(0, segments.length - startIndex) };
|
|
6114
|
+
}
|
|
6115
|
+
const parts = routePath.split('/').filter(Boolean);
|
|
6116
|
+
if (!parts.length) {
|
|
6117
|
+
return { consumed: 0 };
|
|
6118
|
+
}
|
|
6119
|
+
let consumed = 0;
|
|
6120
|
+
for (const part of parts) {
|
|
6121
|
+
const segment = segments[startIndex + consumed];
|
|
6122
|
+
if (segment === undefined) {
|
|
6123
|
+
return null;
|
|
6124
|
+
}
|
|
6125
|
+
if (part.startsWith(':') || part === segment) {
|
|
6126
|
+
consumed++;
|
|
6127
|
+
continue;
|
|
6128
|
+
}
|
|
6129
|
+
return null;
|
|
6130
|
+
}
|
|
6131
|
+
return { consumed };
|
|
6132
|
+
}
|
|
6133
|
+
//#endregion
|
|
6134
|
+
|
|
6135
|
+
//#region ---- Constants ----
|
|
6136
|
+
const SESSION_STORAGE_PREFIX = 'AXP_TAB_NAV';
|
|
6137
|
+
//#endregion
|
|
6138
|
+
class AXPTabNavigationService {
|
|
6139
|
+
//#endregion
|
|
6140
|
+
//#region ---- Lifecycle ----
|
|
6141
|
+
constructor() {
|
|
6142
|
+
//#region ---- Services & Dependencies ----
|
|
6143
|
+
this.router = inject(Router);
|
|
6144
|
+
this.settingsService = inject(AXPSettingsService);
|
|
6145
|
+
this.deviceService = inject(AXPDeviceService);
|
|
6146
|
+
this.menuService = inject(AXPMenuService);
|
|
6147
|
+
this.homePageService = inject(AXPHomePageService);
|
|
6148
|
+
this.sessionService = inject(AXPSessionService);
|
|
6149
|
+
this.routeCacheRegistry = inject(AXPTabRouteCacheRegistry);
|
|
6150
|
+
this.tabPreviewService = inject(AXPTabPreviewService);
|
|
6151
|
+
this.unsavedChangesConfirm = inject(AXPUnsavedChangesConfirmService);
|
|
6152
|
+
this.destroyRef = inject(DestroyRef);
|
|
6153
|
+
//#endregion
|
|
6154
|
+
//#region ---- State ----
|
|
6155
|
+
this.navigationMode = signal('simple', ...(ngDevMode ? [{ debugName: "navigationMode" }] : /* istanbul ignore next */ []));
|
|
6156
|
+
this.maxOpenTabs = signal(5, ...(ngDevMode ? [{ debugName: "maxOpenTabs" }] : /* istanbul ignore next */ []));
|
|
6157
|
+
this.tabs = signal([], ...(ngDevMode ? [{ debugName: "tabs" }] : /* istanbul ignore next */ []));
|
|
6158
|
+
this.activeTabKey = signal(null, ...(ngDevMode ? [{ debugName: "activeTabKey" }] : /* istanbul ignore next */ []));
|
|
6159
|
+
this.suppressNavigationSync = false;
|
|
6160
|
+
//#endregion
|
|
6161
|
+
//#region ---- Computed ----
|
|
6162
|
+
/** Effective mode: mobile always uses simple navigation. */
|
|
6163
|
+
this.effectiveMode = computed(() => {
|
|
6164
|
+
if (this.deviceService.isSmall()) {
|
|
6165
|
+
return 'simple';
|
|
6166
|
+
}
|
|
6167
|
+
return this.navigationMode();
|
|
6168
|
+
}, ...(ngDevMode ? [{ debugName: "effectiveMode" }] : /* istanbul ignore next */ []));
|
|
6169
|
+
this.isTabBarVisible = computed(() => this.effectiveMode() === 'tabular' && this.tabs().length > 0, ...(ngDevMode ? [{ debugName: "isTabBarVisible" }] : /* istanbul ignore next */ []));
|
|
6170
|
+
this.tabList = this.tabs.asReadonly();
|
|
6171
|
+
this.activeKey = this.activeTabKey.asReadonly();
|
|
6172
|
+
void this.loadSettings();
|
|
6173
|
+
this.bindRouterEvents();
|
|
6174
|
+
this.bindSettingsChanges();
|
|
6175
|
+
this.bindSessionChanges();
|
|
6176
|
+
this.bindRouteCacheSync();
|
|
6177
|
+
}
|
|
6178
|
+
//#endregion
|
|
6179
|
+
//#region ---- Public API ----
|
|
6180
|
+
/** Navigates to a path, opening or activating a tab when tabular mode is active. */
|
|
6181
|
+
async navigate(path, extras) {
|
|
6182
|
+
const normalizedPath = normalizeRouterUrl(path);
|
|
6183
|
+
if (this.effectiveMode() === 'simple' || !axpIsTabEligibleUrl(normalizedPath)) {
|
|
6184
|
+
return axpNavigateAppPath(this.router, normalizedPath, extras);
|
|
6185
|
+
}
|
|
6186
|
+
await this.openOrActivateTab(normalizedPath);
|
|
6187
|
+
return true;
|
|
6188
|
+
}
|
|
6189
|
+
/** Opens a tab or activates an existing tab with the same key. */
|
|
6190
|
+
async openOrActivateTab(url, title) {
|
|
6191
|
+
if (this.effectiveMode() === 'simple') {
|
|
6192
|
+
await axpNavigateAppPath(this.router, url);
|
|
6193
|
+
return;
|
|
6194
|
+
}
|
|
6195
|
+
const normalizedUrl = normalizeRouterUrl(url);
|
|
6196
|
+
if (!axpIsTabEligibleUrl(normalizedUrl)) {
|
|
6197
|
+
await axpNavigateAppPath(this.router, normalizedUrl);
|
|
6198
|
+
return;
|
|
6199
|
+
}
|
|
6200
|
+
await this.ensureHomeTab();
|
|
6201
|
+
if (this.isHomePageUrl(normalizedUrl)) {
|
|
6202
|
+
await this.activateTabKey(AXP_HOME_TAB_KEY, normalizedUrl);
|
|
6203
|
+
return;
|
|
6204
|
+
}
|
|
6205
|
+
const resolvedKey = axpResolveTabKey(this.router, normalizedUrl);
|
|
6206
|
+
const existing = this.findTabForUrl(normalizedUrl);
|
|
6207
|
+
if (existing) {
|
|
6208
|
+
if (existing.key !== resolvedKey) {
|
|
6209
|
+
this.migrateTabKey(existing.key, resolvedKey);
|
|
6210
|
+
}
|
|
6211
|
+
this.updateTabUrl(resolvedKey, normalizedUrl);
|
|
6212
|
+
await this.activateTabKey(resolvedKey, normalizedUrl);
|
|
6213
|
+
return;
|
|
6214
|
+
}
|
|
6215
|
+
// Navigate first so scoped tab keys (`data.tab`) resolve from the live route snapshot.
|
|
6216
|
+
this.suppressNavigationSync = true;
|
|
6217
|
+
try {
|
|
6218
|
+
await axpNavigateAppPath(this.router, normalizedUrl);
|
|
6219
|
+
const key = axpResolveTabKey(this.router);
|
|
6220
|
+
const postNavExisting = this.findTabForUrl(normalizedUrl);
|
|
6221
|
+
if (postNavExisting) {
|
|
6222
|
+
if (postNavExisting.key !== key) {
|
|
6223
|
+
this.migrateTabKey(postNavExisting.key, key);
|
|
6224
|
+
}
|
|
6225
|
+
this.updateTabUrl(key, normalizedUrl);
|
|
6226
|
+
this.touchTab(key);
|
|
6227
|
+
this.activeTabKey.set(key);
|
|
6228
|
+
this.persistSession();
|
|
6229
|
+
return;
|
|
6230
|
+
}
|
|
6231
|
+
this.insertTab(this.createTabDescriptor(normalizedUrl, title, key));
|
|
6232
|
+
this.activeTabKey.set(key);
|
|
6233
|
+
this.persistSession();
|
|
6234
|
+
}
|
|
6235
|
+
finally {
|
|
6236
|
+
this.suppressNavigationSync = false;
|
|
6237
|
+
}
|
|
6238
|
+
}
|
|
6239
|
+
/**
|
|
6240
|
+
* Activates a tab and navigates to its URL. The previously active page is detached and cached by
|
|
6241
|
+
* the reuse strategy, so its state is preserved; this tab's cached instance is reattached when
|
|
6242
|
+
* available instead of being re-rendered.
|
|
6243
|
+
*
|
|
6244
|
+
* @param suspendDeactivateGuard When true (default for tab switches), unsaved-changes prompts are
|
|
6245
|
+
* skipped so switching tabs keeps a dirty page alive. Closing a tab passes false to keep the prompt.
|
|
6246
|
+
*/
|
|
6247
|
+
async activateTabKey(key, urlOverride, suspendDeactivateGuard = true) {
|
|
6248
|
+
const tab = this.tabs().find((item) => item.key === key);
|
|
6249
|
+
if (!tab) {
|
|
6250
|
+
return;
|
|
6251
|
+
}
|
|
6252
|
+
const targetUrl = urlOverride ?? tab.url;
|
|
6253
|
+
this.touchTab(key);
|
|
6254
|
+
this.activeTabKey.set(key);
|
|
6255
|
+
this.persistSession();
|
|
6256
|
+
const navigate = async () => {
|
|
6257
|
+
this.suppressNavigationSync = true;
|
|
6258
|
+
try {
|
|
6259
|
+
await this.router.navigateByUrl(targetUrl);
|
|
6260
|
+
}
|
|
6261
|
+
finally {
|
|
6262
|
+
this.suppressNavigationSync = false;
|
|
6263
|
+
}
|
|
6264
|
+
};
|
|
6265
|
+
if (suspendDeactivateGuard) {
|
|
6266
|
+
await this.routeCacheRegistry.runWithSuspendedDeactivateGuards(navigate);
|
|
6267
|
+
return;
|
|
6268
|
+
}
|
|
6269
|
+
await navigate();
|
|
6270
|
+
}
|
|
6271
|
+
/** Closes a tab and navigates to a neighbor when the active tab is closed. */
|
|
6272
|
+
async closeTab(key) {
|
|
6273
|
+
await this.closeTabs([key]);
|
|
6274
|
+
}
|
|
6275
|
+
/** Closes every closable tab except the given key (pinned tabs are kept). */
|
|
6276
|
+
async closeOtherTabs(keepKey) {
|
|
6277
|
+
const keys = this.tabs()
|
|
6278
|
+
.filter((tab) => tab.closable && !tab.pinned && tab.key !== keepKey)
|
|
6279
|
+
.map((tab) => tab.key);
|
|
6280
|
+
await this.closeTabs(keys);
|
|
6281
|
+
}
|
|
6282
|
+
/** Closes all closable tabs (pinned tabs are kept). */
|
|
6283
|
+
async closeAllTabs() {
|
|
6284
|
+
const keys = this.tabs()
|
|
6285
|
+
.filter((tab) => tab.closable && !tab.pinned)
|
|
6286
|
+
.map((tab) => tab.key);
|
|
6287
|
+
await this.closeTabs(keys);
|
|
6288
|
+
}
|
|
6289
|
+
/** Toggles whether a tab stays pinned in the strip (home tab is always pinned). */
|
|
6290
|
+
toggleTabPinned(key) {
|
|
6291
|
+
if (key === AXP_HOME_TAB_KEY) {
|
|
6292
|
+
return;
|
|
6293
|
+
}
|
|
6294
|
+
this.tabs.update((items) => {
|
|
6295
|
+
const updated = items.map((tab) => {
|
|
6296
|
+
if (tab.key !== key) {
|
|
6297
|
+
return tab;
|
|
6298
|
+
}
|
|
6299
|
+
const pinned = !tab.pinned;
|
|
6300
|
+
return {
|
|
6301
|
+
...tab,
|
|
6302
|
+
pinned,
|
|
6303
|
+
closable: !pinned,
|
|
6304
|
+
};
|
|
6305
|
+
});
|
|
6306
|
+
return this.normalizeTabOrder(updated);
|
|
6307
|
+
});
|
|
6308
|
+
this.persistSession();
|
|
6309
|
+
}
|
|
6310
|
+
/**
|
|
6311
|
+
* Reorders tabs after drag-and-drop. Home stays first; pinned and unpinned tabs cannot cross groups.
|
|
6312
|
+
*/
|
|
6313
|
+
reorderTabs(previousIndex, currentIndex) {
|
|
6314
|
+
if (previousIndex === currentIndex) {
|
|
6315
|
+
return;
|
|
6316
|
+
}
|
|
6317
|
+
const items = this.tabs();
|
|
6318
|
+
if (previousIndex < 0 || currentIndex < 0 || previousIndex >= items.length || currentIndex >= items.length) {
|
|
6319
|
+
return;
|
|
6320
|
+
}
|
|
6321
|
+
const moving = items[previousIndex];
|
|
6322
|
+
if (moving.key === AXP_HOME_TAB_KEY) {
|
|
6323
|
+
return;
|
|
6324
|
+
}
|
|
6325
|
+
const lastPinnedIndex = this.getLastPinnedTabIndex(items);
|
|
6326
|
+
let targetIndex = currentIndex;
|
|
6327
|
+
if (moving.pinned) {
|
|
6328
|
+
if (targetIndex === 0) {
|
|
6329
|
+
return;
|
|
6330
|
+
}
|
|
6331
|
+
targetIndex = Math.max(1, Math.min(targetIndex, lastPinnedIndex));
|
|
6332
|
+
}
|
|
6333
|
+
else {
|
|
6334
|
+
const firstUnpinnedIndex = lastPinnedIndex + 1;
|
|
6335
|
+
if (firstUnpinnedIndex >= items.length) {
|
|
6336
|
+
return;
|
|
6337
|
+
}
|
|
6338
|
+
targetIndex = Math.max(firstUnpinnedIndex, Math.min(targetIndex, items.length - 1));
|
|
6339
|
+
}
|
|
6340
|
+
if (previousIndex === targetIndex) {
|
|
6341
|
+
return;
|
|
6342
|
+
}
|
|
6343
|
+
const next = [...items];
|
|
6344
|
+
const [removed] = next.splice(previousIndex, 1);
|
|
6345
|
+
next.splice(targetIndex, 0, removed);
|
|
6346
|
+
this.tabs.set(next);
|
|
6347
|
+
this.persistSession();
|
|
6348
|
+
}
|
|
6349
|
+
/** Updates a tab title from the current page chrome (e.g. breadcrumb trail). */
|
|
6350
|
+
syncTabTitle(key, title) {
|
|
6351
|
+
if (this.effectiveMode() === 'simple') {
|
|
6352
|
+
return;
|
|
6353
|
+
}
|
|
6354
|
+
if (!this.tabs().some((tab) => tab.key === key)) {
|
|
6355
|
+
return;
|
|
6356
|
+
}
|
|
6357
|
+
const normalized = title.trim();
|
|
6358
|
+
if (!normalized || this.isGenericHomeTitle(normalized)) {
|
|
6359
|
+
return;
|
|
6360
|
+
}
|
|
6361
|
+
this.updateTabTitle(key, normalized);
|
|
6362
|
+
}
|
|
6363
|
+
/** Updates a tab icon from the current page chrome or menu metadata. */
|
|
6364
|
+
syncTabIcon(key, icon) {
|
|
6365
|
+
if (this.effectiveMode() === 'simple') {
|
|
6366
|
+
return;
|
|
6367
|
+
}
|
|
6368
|
+
if (!this.tabs().some((tab) => tab.key === key)) {
|
|
6369
|
+
return;
|
|
6370
|
+
}
|
|
6371
|
+
const normalized = icon.trim();
|
|
6372
|
+
if (!normalized) {
|
|
6373
|
+
return;
|
|
6374
|
+
}
|
|
6375
|
+
this.updateTabIcon(key, normalized);
|
|
6376
|
+
}
|
|
6377
|
+
//#endregion
|
|
6378
|
+
//#region ---- Bulk Tab Close ----
|
|
6379
|
+
/** Closes multiple tabs in one update; navigates once when the active tab is closed. */
|
|
6380
|
+
async closeTabs(keys) {
|
|
6381
|
+
const uniqueKeys = [...new Set(keys)];
|
|
6382
|
+
if (!uniqueKeys.length) {
|
|
6383
|
+
return;
|
|
6384
|
+
}
|
|
6385
|
+
const closableKeySet = new Set(uniqueKeys.filter((key) => {
|
|
6386
|
+
const tab = this.tabs().find((item) => item.key === key);
|
|
6387
|
+
return !!tab?.closable && !tab.pinned;
|
|
6388
|
+
}));
|
|
6389
|
+
if (!closableKeySet.size) {
|
|
6390
|
+
return;
|
|
6391
|
+
}
|
|
6392
|
+
const activeKey = this.activeTabKey();
|
|
6393
|
+
const backgroundKeys = [...closableKeySet].filter((key) => key !== activeKey);
|
|
6394
|
+
for (const key of backgroundKeys) {
|
|
6395
|
+
if (!(await this.confirmBackgroundTabClose(key))) {
|
|
6396
|
+
closableKeySet.delete(key);
|
|
6397
|
+
}
|
|
6398
|
+
}
|
|
6399
|
+
if (!closableKeySet.size) {
|
|
6400
|
+
return;
|
|
6401
|
+
}
|
|
6402
|
+
const currentTabs = this.tabs();
|
|
6403
|
+
const wasActiveClosed = !!activeKey && closableKeySet.has(activeKey);
|
|
6404
|
+
const closedActiveIndex = wasActiveClosed ? currentTabs.findIndex((item) => item.key === activeKey) : -1;
|
|
6405
|
+
const nextTabs = currentTabs.filter((item) => !closableKeySet.has(item.key));
|
|
6406
|
+
if (wasActiveClosed) {
|
|
6407
|
+
const navigated = await this.navigateAwayBeforeClosingActiveTab(closedActiveIndex, nextTabs);
|
|
6408
|
+
if (!navigated) {
|
|
6409
|
+
return;
|
|
6410
|
+
}
|
|
6411
|
+
}
|
|
6412
|
+
this.tabs.set(nextTabs);
|
|
6413
|
+
this.tabPreviewService.clearPreviews(closableKeySet);
|
|
6414
|
+
this.persistSession();
|
|
6415
|
+
}
|
|
6416
|
+
//#endregion
|
|
6417
|
+
//#region ---- Tab Close Helpers ----
|
|
6418
|
+
/**
|
|
6419
|
+
* Prompts before closing a cached background tab. Uses the page's {@link AXPTabCloseConfirm}
|
|
6420
|
+
* hook when available, otherwise falls back to the tab preview dirty flag.
|
|
6421
|
+
*/
|
|
6422
|
+
async confirmBackgroundTabClose(key) {
|
|
6423
|
+
const instance = this.routeCacheRegistry.getComponentInstance(key);
|
|
6424
|
+
if (instance && typeof instance.onBeforeTabClose === 'function') {
|
|
6425
|
+
return instance.onBeforeTabClose();
|
|
6426
|
+
}
|
|
6427
|
+
return this.unsavedChangesConfirm.confirmIfDirty(this.tabPreviewService.isTabDirty(key), {
|
|
6428
|
+
approveLeaveOnConfirm: false,
|
|
6429
|
+
});
|
|
6430
|
+
}
|
|
6431
|
+
//#endregion
|
|
6432
|
+
//#region ---- Tab Close Navigation ----
|
|
6433
|
+
/**
|
|
6434
|
+
* Navigates away from the active tab before it is removed so canDeactivate runs while the tab is still visible.
|
|
6435
|
+
* Returns false when navigation was cancelled (e.g. unsaved-changes confirmation declined).
|
|
6436
|
+
*/
|
|
6437
|
+
async navigateAwayBeforeClosingActiveTab(closedActiveIndex, remainingTabs) {
|
|
6438
|
+
const fallbackIndex = this.resolveFallbackTabIndex(closedActiveIndex, remainingTabs);
|
|
6439
|
+
const fallback = fallbackIndex >= 0 ? remainingTabs[fallbackIndex] : undefined;
|
|
6440
|
+
let targetKey;
|
|
6441
|
+
let targetUrl;
|
|
6442
|
+
if (fallback) {
|
|
6443
|
+
targetKey = fallback.key;
|
|
6444
|
+
targetUrl = fallback.url;
|
|
6445
|
+
}
|
|
6446
|
+
else {
|
|
6447
|
+
await this.ensureHomeTab();
|
|
6448
|
+
const home = this.tabs().find((item) => item.key === AXP_HOME_TAB_KEY);
|
|
6449
|
+
if (!home) {
|
|
6450
|
+
return false;
|
|
6451
|
+
}
|
|
6452
|
+
targetKey = home.key;
|
|
6453
|
+
targetUrl = home.url;
|
|
6454
|
+
}
|
|
6455
|
+
this.suppressNavigationSync = true;
|
|
6456
|
+
try {
|
|
6457
|
+
const navigated = await this.router.navigateByUrl(targetUrl);
|
|
6458
|
+
if (!navigated) {
|
|
6459
|
+
return false;
|
|
6460
|
+
}
|
|
6461
|
+
}
|
|
6462
|
+
finally {
|
|
6463
|
+
this.suppressNavigationSync = false;
|
|
6464
|
+
}
|
|
6465
|
+
this.activeTabKey.set(targetKey);
|
|
6466
|
+
this.touchTab(targetKey);
|
|
6467
|
+
return true;
|
|
6468
|
+
}
|
|
6469
|
+
/** Prefers the tab to the right after removal, then the tab to the left (browser-like). */
|
|
6470
|
+
resolveFallbackTabIndex(closedIndex, remainingTabs) {
|
|
6471
|
+
if (!remainingTabs.length) {
|
|
6472
|
+
return -1;
|
|
6473
|
+
}
|
|
6474
|
+
if (closedIndex < remainingTabs.length) {
|
|
6475
|
+
return closedIndex;
|
|
6476
|
+
}
|
|
6477
|
+
return closedIndex - 1;
|
|
6478
|
+
}
|
|
6479
|
+
//#endregion
|
|
6480
|
+
//#region ---- Route Cache Sync ----
|
|
6481
|
+
/**
|
|
6482
|
+
* Keeps the route cache registry aligned with the current navigation mode and the set of open
|
|
6483
|
+
* tabs. Cached page instances for closed/evicted tabs are destroyed by the registry.
|
|
6484
|
+
*/
|
|
6485
|
+
bindRouteCacheSync() {
|
|
6486
|
+
effect(() => {
|
|
6487
|
+
this.routeCacheRegistry.setEnabled(this.effectiveMode() === 'tabular');
|
|
6488
|
+
this.routeCacheRegistry.syncOpenKeys(this.tabs().map((tab) => tab.key), this.tabs().map((tab) => tab.url));
|
|
6489
|
+
});
|
|
6490
|
+
}
|
|
6491
|
+
//#endregion
|
|
6492
|
+
//#region ---- Router Sync ----
|
|
6493
|
+
bindRouterEvents() {
|
|
6494
|
+
this.router.events
|
|
6495
|
+
.pipe(filter((event) => event instanceof NavigationEnd), takeUntilDestroyed(this.destroyRef))
|
|
6496
|
+
.subscribe((event) => {
|
|
6497
|
+
void this.handleNavigationEnd(event.urlAfterRedirects);
|
|
6498
|
+
});
|
|
6499
|
+
}
|
|
6500
|
+
async handleNavigationEnd(url) {
|
|
6501
|
+
if (this.suppressNavigationSync) {
|
|
6502
|
+
return;
|
|
6503
|
+
}
|
|
6504
|
+
if (this.effectiveMode() === 'simple') {
|
|
6505
|
+
return;
|
|
6506
|
+
}
|
|
6507
|
+
const normalizedUrl = normalizeRouterUrl(url);
|
|
6508
|
+
if (!axpIsTabEligibleUrl(normalizedUrl)) {
|
|
6509
|
+
return;
|
|
6510
|
+
}
|
|
6511
|
+
await this.ensureHomeTab();
|
|
6512
|
+
if (this.isHomePageUrl(normalizedUrl)) {
|
|
6513
|
+
this.activateHomeTabForUrl(normalizedUrl);
|
|
6514
|
+
return;
|
|
6515
|
+
}
|
|
6516
|
+
const key = axpResolveTabKey(this.router, normalizedUrl);
|
|
6517
|
+
const existing = this.findTabForUrl(normalizedUrl);
|
|
6518
|
+
if (existing) {
|
|
6519
|
+
if (existing.key !== key) {
|
|
6520
|
+
this.migrateTabKey(existing.key, key);
|
|
6521
|
+
}
|
|
6522
|
+
this.updateTabUrl(key, normalizedUrl);
|
|
6523
|
+
this.touchTab(key);
|
|
6524
|
+
this.activeTabKey.set(key);
|
|
6525
|
+
this.persistSession();
|
|
6526
|
+
return;
|
|
6527
|
+
}
|
|
6528
|
+
this.insertTab(this.createTabDescriptor(normalizedUrl, undefined, key));
|
|
6529
|
+
this.activeTabKey.set(key);
|
|
6530
|
+
this.persistSession();
|
|
6531
|
+
}
|
|
6532
|
+
//#endregion
|
|
6533
|
+
//#region ---- Settings ----
|
|
6534
|
+
async loadSettings() {
|
|
6535
|
+
const [mode, maxTabs] = await Promise.all([
|
|
6536
|
+
this.settingsService.get(AXPCommonSettings.NavigationMode),
|
|
6537
|
+
this.settingsService.get(AXPCommonSettings.MaxOpenTabs),
|
|
6538
|
+
]);
|
|
6539
|
+
this.navigationMode.set(mode === 'tabular' ? 'tabular' : 'simple');
|
|
6540
|
+
this.maxOpenTabs.set(this.normalizeMaxTabs(maxTabs));
|
|
6541
|
+
await this.restoreSessionIfNeeded();
|
|
6542
|
+
}
|
|
6543
|
+
bindSettingsChanges() {
|
|
6544
|
+
this.settingsService.onChanged.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((event) => {
|
|
6545
|
+
if (event.keys.includes(AXPCommonSettings.NavigationMode)) {
|
|
6546
|
+
const raw = event.values[AXPCommonSettings.NavigationMode];
|
|
6547
|
+
const newMode = raw === 'tabular' ? 'tabular' : 'simple';
|
|
6548
|
+
if (newMode !== this.navigationMode()) {
|
|
6549
|
+
this.navigationMode.set(newMode);
|
|
6550
|
+
if (this.effectiveMode() === 'simple') {
|
|
6551
|
+
this.clearSession();
|
|
6552
|
+
}
|
|
6553
|
+
else {
|
|
6554
|
+
void this.ensureHomeTab().then(() => this.syncCurrentRouteAsTab());
|
|
6555
|
+
}
|
|
6556
|
+
}
|
|
6557
|
+
}
|
|
6558
|
+
if (event.keys.includes(AXPCommonSettings.MaxOpenTabs)) {
|
|
6559
|
+
this.maxOpenTabs.set(this.normalizeMaxTabs(event.values[AXPCommonSettings.MaxOpenTabs]));
|
|
6560
|
+
this.enforceMaxTabs();
|
|
6561
|
+
this.persistSession();
|
|
6562
|
+
}
|
|
6563
|
+
});
|
|
6564
|
+
}
|
|
6565
|
+
bindSessionChanges() {
|
|
6566
|
+
this.sessionService.user$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(() => {
|
|
6567
|
+
void this.restoreSessionIfNeeded();
|
|
6568
|
+
});
|
|
6569
|
+
}
|
|
6570
|
+
//#endregion
|
|
6571
|
+
//#region ---- Tab Management ----
|
|
6572
|
+
async ensureHomeTab() {
|
|
6573
|
+
const homePath = this.resolveHomePath();
|
|
6574
|
+
if (!homePath) {
|
|
6575
|
+
return;
|
|
6576
|
+
}
|
|
6577
|
+
const homeUrl = normalizeRouterUrl(homePath);
|
|
6578
|
+
const homeKey = AXP_HOME_TAB_KEY;
|
|
6579
|
+
const existing = this.tabs().find((tab) => tab.key === homeKey);
|
|
6580
|
+
if (existing) {
|
|
6581
|
+
this.tabs.update((items) => items.map((tab) => tab.key === homeKey
|
|
6582
|
+
? {
|
|
6583
|
+
...tab,
|
|
6584
|
+
url: homeUrl,
|
|
6585
|
+
title: this.isGenericHomeTitle(tab.title) ? this.resolveHomeTitle() : tab.title,
|
|
6586
|
+
icon: this.resolveTabIcon(homeUrl, 'fa-light fa-house'),
|
|
6587
|
+
}
|
|
6588
|
+
: tab));
|
|
6589
|
+
return;
|
|
6590
|
+
}
|
|
6591
|
+
const now = Date.now();
|
|
6592
|
+
const homeTab = {
|
|
6593
|
+
key: homeKey,
|
|
6594
|
+
url: homeUrl,
|
|
6595
|
+
title: this.resolveHomeTitle(),
|
|
6596
|
+
icon: this.resolveTabIcon(homeUrl, 'fa-light fa-house'),
|
|
6597
|
+
pinned: true,
|
|
6598
|
+
closable: false,
|
|
6599
|
+
openedAt: now,
|
|
6600
|
+
lastActivatedAt: now,
|
|
6601
|
+
};
|
|
6602
|
+
this.tabs.update((items) => this.normalizeTabOrder([homeTab, ...items.filter((tab) => tab.key !== homeKey)]));
|
|
6603
|
+
}
|
|
6604
|
+
insertTab(tab) {
|
|
6605
|
+
if (this.isHomePageUrl(tab.url)) {
|
|
6606
|
+
this.activateHomeTabForUrl(tab.url);
|
|
6607
|
+
return;
|
|
6608
|
+
}
|
|
6609
|
+
this.tabs.update((items) => {
|
|
6610
|
+
const withoutDuplicate = items.filter((item) => {
|
|
6611
|
+
if (item.key === tab.key) {
|
|
6612
|
+
return false;
|
|
6613
|
+
}
|
|
6614
|
+
return axpResolveTabKey(this.router, item.url) !== tab.key;
|
|
6615
|
+
});
|
|
6616
|
+
return this.normalizeTabOrder([...withoutDuplicate, tab]);
|
|
6617
|
+
});
|
|
6618
|
+
this.enforceMaxTabs();
|
|
6619
|
+
}
|
|
6620
|
+
/** Home first; preserves session order for the remaining tabs. */
|
|
6621
|
+
ensureHomeTabFirst(items) {
|
|
6622
|
+
const home = items.find((tab) => tab.key === AXP_HOME_TAB_KEY);
|
|
6623
|
+
const others = items.filter((tab) => tab.key !== AXP_HOME_TAB_KEY);
|
|
6624
|
+
return home ? [home, ...others] : items;
|
|
6625
|
+
}
|
|
6626
|
+
getLastPinnedTabIndex(items) {
|
|
6627
|
+
for (let index = items.length - 1; index >= 0; index -= 1) {
|
|
6628
|
+
if (items[index]?.pinned) {
|
|
6629
|
+
return index;
|
|
6630
|
+
}
|
|
6631
|
+
}
|
|
6632
|
+
return 0;
|
|
6633
|
+
}
|
|
6634
|
+
/** Home first, then other pinned tabs, then unpinned tabs (stable by openedAt within each group). */
|
|
6635
|
+
normalizeTabOrder(items) {
|
|
6636
|
+
const home = items.find((tab) => tab.key === AXP_HOME_TAB_KEY);
|
|
6637
|
+
const pinned = items.filter((tab) => tab.key !== AXP_HOME_TAB_KEY && tab.pinned);
|
|
6638
|
+
const unpinned = items.filter((tab) => tab.key !== AXP_HOME_TAB_KEY && !tab.pinned);
|
|
6639
|
+
const byOpenedAt = (a, b) => a.openedAt - b.openedAt;
|
|
6640
|
+
return [...(home ? [home] : []), ...pinned.sort(byOpenedAt), ...unpinned.sort(byOpenedAt)];
|
|
6641
|
+
}
|
|
6642
|
+
enforceMaxTabs() {
|
|
6643
|
+
const limit = this.maxOpenTabs();
|
|
6644
|
+
let current = this.tabs();
|
|
6645
|
+
while (current.length > limit) {
|
|
6646
|
+
const evictionCandidate = [...current]
|
|
6647
|
+
.filter((tab) => tab.closable && !tab.pinned && tab.key !== this.activeTabKey())
|
|
6648
|
+
.filter((tab) => !this.tabPreviewService.isTabDirty(tab.key))
|
|
6649
|
+
.sort((a, b) => a.lastActivatedAt - b.lastActivatedAt)[0];
|
|
6650
|
+
if (!evictionCandidate) {
|
|
6651
|
+
break;
|
|
6652
|
+
}
|
|
6653
|
+
current = current.filter((tab) => tab.key !== evictionCandidate.key);
|
|
6654
|
+
}
|
|
6655
|
+
this.tabs.set(current);
|
|
6656
|
+
}
|
|
6657
|
+
createTabDescriptor(url, title, keyOverride) {
|
|
6658
|
+
const now = Date.now();
|
|
6659
|
+
const key = keyOverride ?? axpResolveTabKey(this.router, url);
|
|
6660
|
+
return {
|
|
6661
|
+
key,
|
|
6662
|
+
url,
|
|
6663
|
+
title: title ?? this.resolveTabTitle(url),
|
|
6664
|
+
icon: this.resolveTabIcon(url),
|
|
6665
|
+
pinned: false,
|
|
6666
|
+
closable: true,
|
|
6667
|
+
openedAt: now,
|
|
6668
|
+
lastActivatedAt: now,
|
|
6669
|
+
};
|
|
6670
|
+
}
|
|
6671
|
+
/** Finds a tab by scoped key or legacy full-path key for the same URL. */
|
|
6672
|
+
findTabForUrl(url) {
|
|
6673
|
+
if (this.isHomePageUrl(url)) {
|
|
6674
|
+
return this.tabs().find((tab) => tab.key === AXP_HOME_TAB_KEY);
|
|
6675
|
+
}
|
|
6676
|
+
const resolvedKey = axpResolveTabKey(this.router, url);
|
|
6677
|
+
return this.tabs().find((tab) => tab.key !== AXP_HOME_TAB_KEY &&
|
|
6678
|
+
(tab.key === resolvedKey || axpResolveTabKey(this.router, tab.url) === resolvedKey));
|
|
6679
|
+
}
|
|
6680
|
+
/** True when the URL is the configured application home page (e.g. dashboard). */
|
|
6681
|
+
isHomePageUrl(url) {
|
|
6682
|
+
const homeUrl = this.resolveHomeUrl();
|
|
6683
|
+
if (!homeUrl) {
|
|
6684
|
+
return false;
|
|
6685
|
+
}
|
|
6686
|
+
return normalizeRouterUrl(url) === homeUrl;
|
|
6687
|
+
}
|
|
6688
|
+
resolveHomeUrl() {
|
|
6689
|
+
const homePath = this.resolveHomePath();
|
|
6690
|
+
if (!homePath) {
|
|
6691
|
+
return null;
|
|
6692
|
+
}
|
|
6693
|
+
return normalizeRouterUrl(homePath);
|
|
6694
|
+
}
|
|
6695
|
+
/** Activates the pinned home tab for a home-page URL instead of opening a duplicate tab. */
|
|
6696
|
+
activateHomeTabForUrl(url) {
|
|
6697
|
+
this.updateTabUrl(AXP_HOME_TAB_KEY, normalizeRouterUrl(url));
|
|
6698
|
+
this.touchTab(AXP_HOME_TAB_KEY);
|
|
6699
|
+
this.activeTabKey.set(AXP_HOME_TAB_KEY);
|
|
6700
|
+
this.persistSession();
|
|
6701
|
+
}
|
|
6702
|
+
/** Drops non-home tabs that duplicate the home page URL after session restore. */
|
|
6703
|
+
removeDuplicateHomeUrlTabs(tabs) {
|
|
6704
|
+
const homeUrl = this.resolveHomeUrl();
|
|
6705
|
+
if (!homeUrl) {
|
|
6706
|
+
return tabs;
|
|
6707
|
+
}
|
|
6708
|
+
return tabs.filter((tab) => tab.key === AXP_HOME_TAB_KEY || normalizeRouterUrl(tab.url) !== homeUrl);
|
|
6709
|
+
}
|
|
6710
|
+
/** Rewrites a tab key when route-scope resolution changes (e.g. profile sub-routes). */
|
|
6711
|
+
migrateTabKey(fromKey, toKey) {
|
|
6712
|
+
if (fromKey === toKey) {
|
|
6713
|
+
return;
|
|
6714
|
+
}
|
|
6715
|
+
if (this.activeTabKey() === fromKey) {
|
|
6716
|
+
this.activeTabKey.set(toKey);
|
|
6717
|
+
}
|
|
6718
|
+
this.tabs.update((items) => {
|
|
6719
|
+
const tab = items.find((item) => item.key === fromKey);
|
|
6720
|
+
if (!tab) {
|
|
6721
|
+
return items;
|
|
6722
|
+
}
|
|
6723
|
+
const without = items.filter((item) => item.key !== fromKey && item.key !== toKey);
|
|
6724
|
+
return this.normalizeTabOrder([...without, { ...tab, key: toKey }]);
|
|
6725
|
+
});
|
|
6726
|
+
}
|
|
6727
|
+
/** Collapses restored tabs that share the same scoped route key. */
|
|
6728
|
+
normalizeRestoredTabKeys(tabs) {
|
|
6729
|
+
const byResolvedKey = new Map();
|
|
6730
|
+
for (const tab of tabs) {
|
|
6731
|
+
if (tab.key === AXP_HOME_TAB_KEY) {
|
|
6732
|
+
byResolvedKey.set(AXP_HOME_TAB_KEY, tab);
|
|
6733
|
+
continue;
|
|
6734
|
+
}
|
|
6735
|
+
const resolvedKey = axpResolveTabKey(this.router, tab.url);
|
|
6736
|
+
const normalized = { ...tab, key: resolvedKey };
|
|
6737
|
+
const existing = byResolvedKey.get(resolvedKey);
|
|
6738
|
+
if (!existing || normalized.lastActivatedAt > existing.lastActivatedAt) {
|
|
6739
|
+
byResolvedKey.set(resolvedKey, normalized);
|
|
6740
|
+
}
|
|
6741
|
+
}
|
|
6742
|
+
return [...byResolvedKey.values()];
|
|
6743
|
+
}
|
|
6744
|
+
updateTabUrl(key, url) {
|
|
6745
|
+
this.tabs.update((items) => items.map((tab) => (tab.key === key ? { ...tab, url } : tab)));
|
|
6746
|
+
}
|
|
6747
|
+
updateTabTitle(key, title) {
|
|
6748
|
+
this.tabs.update((items) => items.map((tab) => (tab.key === key && tab.title !== title ? { ...tab, title } : tab)));
|
|
6749
|
+
this.persistSession();
|
|
6750
|
+
}
|
|
6751
|
+
updateTabIcon(key, icon) {
|
|
6752
|
+
this.tabs.update((items) => items.map((tab) => (tab.key === key && tab.icon !== icon ? { ...tab, icon } : tab)));
|
|
6753
|
+
this.persistSession();
|
|
6754
|
+
}
|
|
6755
|
+
touchTab(key) {
|
|
6756
|
+
const now = Date.now();
|
|
6757
|
+
this.tabs.update((items) => items.map((tab) => (tab.key === key ? { ...tab, lastActivatedAt: now } : tab)));
|
|
6758
|
+
}
|
|
6759
|
+
resolveTabTitle(url, fallback) {
|
|
6760
|
+
const match = axpFindMenuItemByRoute(this.menuService.items(), url);
|
|
6761
|
+
if (match?.item.text) {
|
|
6762
|
+
return match.item.text;
|
|
6763
|
+
}
|
|
6764
|
+
return fallback ?? axpFallbackTabTitle(url);
|
|
6765
|
+
}
|
|
6766
|
+
resolveTabIcon(url, fallback = 'fa-light fa-window') {
|
|
6767
|
+
const match = axpFindMenuItemByRoute(this.menuService.items(), url);
|
|
6768
|
+
if (match?.item.icon?.trim()) {
|
|
6769
|
+
return match.item.icon.trim();
|
|
6770
|
+
}
|
|
6771
|
+
return fallback;
|
|
6772
|
+
}
|
|
6773
|
+
resolveHomePath() {
|
|
6774
|
+
const current = this.homePageService.getCurrent();
|
|
6775
|
+
return current.path ? normalizeRouterUrl(current.path) : '';
|
|
6776
|
+
}
|
|
6777
|
+
resolveHomeTitle() {
|
|
6778
|
+
const current = this.homePageService.getCurrent();
|
|
6779
|
+
return current.title || 'Home';
|
|
6780
|
+
}
|
|
6781
|
+
isGenericHomeTitle(title) {
|
|
6782
|
+
if (title === '@general:terms.interface.navigation.home') {
|
|
6783
|
+
return true;
|
|
6784
|
+
}
|
|
6785
|
+
const homeTitle = this.resolveHomeTitle().trim();
|
|
6786
|
+
return title === homeTitle;
|
|
6787
|
+
}
|
|
6788
|
+
async syncCurrentRouteAsTab() {
|
|
6789
|
+
if (this.effectiveMode() === 'simple') {
|
|
6790
|
+
return;
|
|
6791
|
+
}
|
|
6792
|
+
await this.ensureHomeTab();
|
|
6793
|
+
await this.handleNavigationEnd(this.router.url);
|
|
6794
|
+
}
|
|
6795
|
+
normalizeMaxTabs(value) {
|
|
6796
|
+
const parsed = Number(value);
|
|
6797
|
+
if (!Number.isFinite(parsed)) {
|
|
6798
|
+
return 5;
|
|
6799
|
+
}
|
|
6800
|
+
return Math.min(20, Math.max(2, Math.round(parsed)));
|
|
6801
|
+
}
|
|
6802
|
+
//#endregion
|
|
6803
|
+
//#region ---- Session Persistence ----
|
|
6804
|
+
sessionStorageKey() {
|
|
6805
|
+
const userId = this.sessionService.user?.id;
|
|
6806
|
+
if (!userId) {
|
|
6807
|
+
return null;
|
|
6808
|
+
}
|
|
6809
|
+
return `${SESSION_STORAGE_PREFIX}(${userId})`;
|
|
6810
|
+
}
|
|
6811
|
+
persistSession() {
|
|
6812
|
+
const key = this.sessionStorageKey();
|
|
6813
|
+
if (!key || this.effectiveMode() === 'simple') {
|
|
6814
|
+
return;
|
|
5665
6815
|
}
|
|
5666
|
-
|
|
6816
|
+
const payload = {
|
|
6817
|
+
tabs: cloneDeep(this.tabs()),
|
|
6818
|
+
activeTabKey: this.activeTabKey(),
|
|
6819
|
+
};
|
|
6820
|
+
sessionStorage.setItem(key, JSON.stringify(payload));
|
|
5667
6821
|
}
|
|
5668
|
-
|
|
5669
|
-
|
|
5670
|
-
|
|
5671
|
-
|
|
5672
|
-
*/
|
|
5673
|
-
async getValue(name) {
|
|
5674
|
-
const definition = await this.getDefinition(name);
|
|
5675
|
-
if (definition) {
|
|
5676
|
-
return await definition.execute();
|
|
6822
|
+
clearSession() {
|
|
6823
|
+
const key = this.sessionStorageKey();
|
|
6824
|
+
if (key) {
|
|
6825
|
+
sessionStorage.removeItem(key);
|
|
5677
6826
|
}
|
|
5678
|
-
|
|
6827
|
+
this.tabs.set([]);
|
|
6828
|
+
this.activeTabKey.set(null);
|
|
6829
|
+
this.tabPreviewService.clearAll();
|
|
5679
6830
|
}
|
|
5680
|
-
|
|
5681
|
-
|
|
5682
|
-
|
|
5683
|
-
|
|
5684
|
-
this.
|
|
6831
|
+
async restoreSessionIfNeeded() {
|
|
6832
|
+
if (this.effectiveMode() === 'simple') {
|
|
6833
|
+
return;
|
|
6834
|
+
}
|
|
6835
|
+
const storageKey = this.sessionStorageKey();
|
|
6836
|
+
if (!storageKey) {
|
|
6837
|
+
await this.ensureHomeTab();
|
|
6838
|
+
await this.syncCurrentRouteAsTab();
|
|
6839
|
+
return;
|
|
6840
|
+
}
|
|
6841
|
+
const raw = sessionStorage.getItem(storageKey);
|
|
6842
|
+
if (!raw) {
|
|
6843
|
+
await this.ensureHomeTab();
|
|
6844
|
+
await this.syncCurrentRouteAsTab();
|
|
6845
|
+
return;
|
|
6846
|
+
}
|
|
6847
|
+
try {
|
|
6848
|
+
const parsed = JSON.parse(raw);
|
|
6849
|
+
const restoredTabs = this.removeDuplicateHomeUrlTabs(this.normalizeRestoredTabKeys(defaultTo(parsed.tabs, []).filter((tab) => tab?.key && tab?.url)));
|
|
6850
|
+
this.tabs.set(this.ensureHomeTabFirst(restoredTabs));
|
|
6851
|
+
await this.ensureHomeTab();
|
|
6852
|
+
const activeKey = parsed.activeTabKey ?? this.tabs()[this.tabs().length - 1]?.key ?? AXP_HOME_TAB_KEY;
|
|
6853
|
+
this.activeTabKey.set(activeKey);
|
|
6854
|
+
const activeTab = this.tabs().find((tab) => tab.key === activeKey);
|
|
6855
|
+
if (activeTab && normalizeRouterUrl(this.router.url) !== normalizeRouterUrl(activeTab.url)) {
|
|
6856
|
+
this.suppressNavigationSync = true;
|
|
6857
|
+
try {
|
|
6858
|
+
await this.router.navigateByUrl(activeTab.url);
|
|
6859
|
+
}
|
|
6860
|
+
finally {
|
|
6861
|
+
this.suppressNavigationSync = false;
|
|
6862
|
+
}
|
|
6863
|
+
}
|
|
6864
|
+
}
|
|
6865
|
+
catch {
|
|
6866
|
+
await this.ensureHomeTab();
|
|
6867
|
+
await this.syncCurrentRouteAsTab();
|
|
6868
|
+
}
|
|
5685
6869
|
}
|
|
5686
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type:
|
|
5687
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type:
|
|
6870
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPTabNavigationService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
6871
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPTabNavigationService, providedIn: 'root' }); }
|
|
5688
6872
|
}
|
|
5689
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type:
|
|
6873
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPTabNavigationService, decorators: [{
|
|
5690
6874
|
type: Injectable,
|
|
5691
6875
|
args: [{
|
|
5692
6876
|
providedIn: 'root',
|
|
5693
6877
|
}]
|
|
5694
|
-
}] });
|
|
5695
|
-
|
|
5696
|
-
class AXPTokenEvaluatorScopeProvider {
|
|
5697
|
-
constructor() {
|
|
5698
|
-
this.tokenService = inject(AXPTokenDefinitionService);
|
|
5699
|
-
}
|
|
5700
|
-
async provide(context) {
|
|
5701
|
-
context.addScope('tokens', {
|
|
5702
|
-
get: async (name) => {
|
|
5703
|
-
return await this.tokenService.getValue(name);
|
|
5704
|
-
}
|
|
5705
|
-
});
|
|
5706
|
-
}
|
|
5707
|
-
}
|
|
5708
|
-
|
|
5709
|
-
//#endregion
|
|
5710
|
-
//#region ---- App Path Navigation ----
|
|
5711
|
-
/**
|
|
5712
|
-
* Splits an absolute app path (`/app/m/module/e/Entity/id/view`) into router command segments.
|
|
5713
|
-
* Prefer this over `navigate([fullPath])` or ambiguous single-segment navigation so browser history stays correct.
|
|
5714
|
-
*/
|
|
5715
|
-
function axpParseAppPath(path) {
|
|
5716
|
-
const trimmed = path.trim();
|
|
5717
|
-
const withoutLeadingSlash = trimmed.startsWith('/') ? trimmed.slice(1) : trimmed;
|
|
5718
|
-
const [pathAndFragment, queryString] = withoutLeadingSlash.split('?');
|
|
5719
|
-
const [pathPart, fragment] = pathAndFragment.split('#');
|
|
5720
|
-
const segments = pathPart.split('/').filter(Boolean);
|
|
5721
|
-
const queryParams = queryString
|
|
5722
|
-
? queryString.split('&').reduce((params, pair) => {
|
|
5723
|
-
const [key, value = ''] = pair.split('=');
|
|
5724
|
-
if (key) {
|
|
5725
|
-
params[decodeURIComponent(key)] = decodeURIComponent(value);
|
|
5726
|
-
}
|
|
5727
|
-
return params;
|
|
5728
|
-
}, {})
|
|
5729
|
-
: undefined;
|
|
5730
|
-
return {
|
|
5731
|
-
segments,
|
|
5732
|
-
queryParams,
|
|
5733
|
-
fragment: fragment || undefined,
|
|
5734
|
-
};
|
|
5735
|
-
}
|
|
5736
|
-
/** Navigates using explicit path segments (same pattern as entity preload filters). */
|
|
5737
|
-
function axpNavigateAppPath(router, path, extras) {
|
|
5738
|
-
const { segments, queryParams, fragment } = axpParseAppPath(path);
|
|
5739
|
-
return router.navigate(segments, {
|
|
5740
|
-
...extras,
|
|
5741
|
-
queryParams,
|
|
5742
|
-
fragment,
|
|
5743
|
-
});
|
|
5744
|
-
}
|
|
5745
|
-
/** True when a URL path is an entity master list route (`.../e/{Entity}/list`). */
|
|
5746
|
-
function axpIsEntityListPath(url) {
|
|
5747
|
-
const path = url.split('?')[0].split('#')[0];
|
|
5748
|
-
return /\/e\/[^/]+\/list$/.test(path);
|
|
5749
|
-
}
|
|
5750
|
-
/** True when the current browser URL is an entity master list route (`.../e/{Entity}/list`). */
|
|
5751
|
-
function axpIsEntityListRoute(router) {
|
|
5752
|
-
return axpIsEntityListPath(router.url);
|
|
5753
|
-
}
|
|
5754
|
-
/** True when a URL path is an entity details view route (`.../e/{Entity}/{id}/view`). */
|
|
5755
|
-
function axpIsEntityDetailsPath(url) {
|
|
5756
|
-
const path = url.split('?')[0].split('#')[0];
|
|
5757
|
-
return /\/e\/[^/]+\/[^/]+\/view$/.test(path);
|
|
5758
|
-
}
|
|
5759
|
-
/** True when the current browser URL is an entity details view route (`.../e/{Entity}/{id}/view`). */
|
|
5760
|
-
function axpIsEntityDetailsViewRoute(router) {
|
|
5761
|
-
return axpIsEntityDetailsPath(router.url);
|
|
5762
|
-
}
|
|
5763
|
-
/**
|
|
5764
|
-
* Merges query params on the current entity list route without pushing history.
|
|
5765
|
-
* Uses absolute path segments so a stale `relativeTo` cannot rewrite the active URL after navigating away.
|
|
5766
|
-
*/
|
|
5767
|
-
function axpReplaceEntityListRouteQueryParams(router, queryParams) {
|
|
5768
|
-
const url = router.url;
|
|
5769
|
-
if (!axpIsEntityListPath(url)) {
|
|
5770
|
-
return;
|
|
5771
|
-
}
|
|
5772
|
-
const pathOnly = url.split('?')[0].split('#')[0];
|
|
5773
|
-
const { segments } = axpParseAppPath(pathOnly);
|
|
5774
|
-
void router.navigate(segments, {
|
|
5775
|
-
queryParams,
|
|
5776
|
-
queryParamsHandling: 'merge',
|
|
5777
|
-
replaceUrl: true,
|
|
5778
|
-
});
|
|
5779
|
-
}
|
|
5780
|
-
//#endregion
|
|
6878
|
+
}], ctorParameters: () => [] });
|
|
5781
6879
|
|
|
5782
6880
|
//TODO Loading, Redirect, Drawer, Show toast
|
|
5783
6881
|
const AXPRedirectEvent = createWorkFlowEvent('Redirect Event Fired');
|
|
@@ -5787,6 +6885,7 @@ class AXPWorkflowNavigateAction extends AXPWorkflowAction {
|
|
|
5787
6885
|
constructor() {
|
|
5788
6886
|
super(...arguments);
|
|
5789
6887
|
this.router = inject(Router);
|
|
6888
|
+
this.tabNavigation = inject(AXPTabNavigationService);
|
|
5790
6889
|
}
|
|
5791
6890
|
async execute(context) {
|
|
5792
6891
|
const payload = context.getVariable('payload');
|
|
@@ -5802,7 +6901,7 @@ class AXPWorkflowNavigateAction extends AXPWorkflowAction {
|
|
|
5802
6901
|
window.open(path, '_blank');
|
|
5803
6902
|
return;
|
|
5804
6903
|
}
|
|
5805
|
-
await
|
|
6904
|
+
await this.tabNavigation.navigate(path, payload.extras);
|
|
5806
6905
|
}
|
|
5807
6906
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPWorkflowNavigateAction, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
5808
6907
|
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPWorkflowNavigateAction }); }
|
|
@@ -5869,7 +6968,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
5869
6968
|
class AXPWorkflowRouterNavigateAction extends AXPWorkflowAction {
|
|
5870
6969
|
constructor() {
|
|
5871
6970
|
super(...arguments);
|
|
5872
|
-
this.
|
|
6971
|
+
this.tabNavigation = inject(AXPTabNavigationService);
|
|
5873
6972
|
}
|
|
5874
6973
|
async execute(context) {
|
|
5875
6974
|
const command = context.getVariable();
|
|
@@ -5882,7 +6981,7 @@ class AXPWorkflowRouterNavigateAction extends AXPWorkflowAction {
|
|
|
5882
6981
|
window.open(path, '_blank');
|
|
5883
6982
|
}
|
|
5884
6983
|
else {
|
|
5885
|
-
await
|
|
6984
|
+
await this.tabNavigation.navigate(path, command.options.extras);
|
|
5886
6985
|
}
|
|
5887
6986
|
}
|
|
5888
6987
|
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 +7055,14 @@ class AXPCommonModule {
|
|
|
5956
7055
|
/**
|
|
5957
7056
|
* @ignore
|
|
5958
7057
|
*/
|
|
5959
|
-
constructor(instances, _keyboardShortcutsHelp) {
|
|
7058
|
+
constructor(instances, _keyboardShortcutsHelp, formatterRegistry) {
|
|
7059
|
+
formatterRegistry.register(AXPTranslateFormatter);
|
|
5960
7060
|
instances.forEach((f) => {
|
|
5961
7061
|
f();
|
|
5962
7062
|
});
|
|
5963
7063
|
}
|
|
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: [
|
|
7064
|
+
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 }); }
|
|
7065
|
+
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
7066
|
AXDateTimeModule,
|
|
5967
7067
|
AXToastModule,
|
|
5968
7068
|
AXPModuleManifestModule], exports: [RouterModule] }); }
|
|
@@ -6009,7 +7109,7 @@ class AXPCommonModule {
|
|
|
6009
7109
|
provide: AXP_SETTING_DEFINITION_PROVIDER,
|
|
6010
7110
|
useFactory: async () => {
|
|
6011
7111
|
const injector = inject(Injector);
|
|
6012
|
-
const provider = (await import('./acorex-platform-common-common-settings.provider-
|
|
7112
|
+
const provider = (await import('./acorex-platform-common-common-settings.provider-VfmZct9H.mjs')).AXPCommonSettingProvider;
|
|
6013
7113
|
return new provider(injector);
|
|
6014
7114
|
},
|
|
6015
7115
|
multi: true,
|
|
@@ -6110,7 +7210,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
6110
7210
|
provide: AXP_SETTING_DEFINITION_PROVIDER,
|
|
6111
7211
|
useFactory: async () => {
|
|
6112
7212
|
const injector = inject(Injector);
|
|
6113
|
-
const provider = (await import('./acorex-platform-common-common-settings.provider-
|
|
7213
|
+
const provider = (await import('./acorex-platform-common-common-settings.provider-VfmZct9H.mjs')).AXPCommonSettingProvider;
|
|
6114
7214
|
return new provider(injector);
|
|
6115
7215
|
},
|
|
6116
7216
|
multi: true,
|
|
@@ -6120,7 +7220,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
6120
7220
|
}], ctorParameters: () => [{ type: undefined, decorators: [{
|
|
6121
7221
|
type: Inject,
|
|
6122
7222
|
args: ['AXPCommonModuleFactory']
|
|
6123
|
-
}] }, { type: AXPKeyboardShortcutsHelpService }] });
|
|
7223
|
+
}] }, { type: AXPKeyboardShortcutsHelpService }, { type: i2$1.AXFormatterRegistryService }] });
|
|
6124
7224
|
|
|
6125
7225
|
const ENVIRONMENT = new InjectionToken('ENVIRONMENT');
|
|
6126
7226
|
const AXP_PLATFORM_CONFIG_TOKEN = new InjectionToken('AXP_PLATFORM_CONFIG_TOKEN', {
|
|
@@ -6327,6 +7427,105 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
6327
7427
|
args: [{ providedIn: 'root' }]
|
|
6328
7428
|
}] });
|
|
6329
7429
|
|
|
7430
|
+
const AXP_INTEGRATION_PROVIDER = new InjectionToken('AXP_INTEGRATION_PROVIDER', {
|
|
7431
|
+
providedIn: 'root',
|
|
7432
|
+
factory: () => [],
|
|
7433
|
+
});
|
|
7434
|
+
const AXP_INTEGRATION_RUNTIME_PROVIDER = new InjectionToken('AXP_INTEGRATION_RUNTIME_PROVIDER', {
|
|
7435
|
+
providedIn: 'root',
|
|
7436
|
+
factory: () => [],
|
|
7437
|
+
});
|
|
7438
|
+
//#endregion
|
|
7439
|
+
|
|
7440
|
+
//#region ---- Integration provider context ----
|
|
7441
|
+
class AXPIntegrationProviderContext {
|
|
7442
|
+
constructor() {
|
|
7443
|
+
//#region ---- State ----
|
|
7444
|
+
this.definitions = new Map();
|
|
7445
|
+
//#endregion
|
|
7446
|
+
}
|
|
7447
|
+
//#endregion
|
|
7448
|
+
//#region ---- Builder API ----
|
|
7449
|
+
/**
|
|
7450
|
+
* Registers a provider definition. Throws when duplicate ids are registered.
|
|
7451
|
+
*/
|
|
7452
|
+
register(definition) {
|
|
7453
|
+
if (this.definitions.has(definition.id)) {
|
|
7454
|
+
throw new Error(`[AXPIntegrationProviderContext] Duplicate integration provider id '${definition.id}'.`);
|
|
7455
|
+
}
|
|
7456
|
+
this.definitions.set(definition.id, definition);
|
|
7457
|
+
}
|
|
7458
|
+
//#endregion
|
|
7459
|
+
//#region ---- Accessors ----
|
|
7460
|
+
getDefinitions() {
|
|
7461
|
+
return this.definitions;
|
|
7462
|
+
}
|
|
7463
|
+
}
|
|
7464
|
+
//#endregion
|
|
7465
|
+
|
|
7466
|
+
//#region ---- Integration provider registry ----
|
|
7467
|
+
class AXPIntegrationProviderRegistryService {
|
|
7468
|
+
constructor() {
|
|
7469
|
+
//#region ---- Dependencies ----
|
|
7470
|
+
this.providers = inject(AXP_INTEGRATION_PROVIDER, { optional: true });
|
|
7471
|
+
//#endregion
|
|
7472
|
+
//#region ---- State ----
|
|
7473
|
+
this.cache = null;
|
|
7474
|
+
}
|
|
7475
|
+
//#endregion
|
|
7476
|
+
//#region ---- Public API ----
|
|
7477
|
+
async list() {
|
|
7478
|
+
const map = await this.load();
|
|
7479
|
+
return Array.from(map.values());
|
|
7480
|
+
}
|
|
7481
|
+
async get(id) {
|
|
7482
|
+
const map = await this.load();
|
|
7483
|
+
return map.get(id) ?? null;
|
|
7484
|
+
}
|
|
7485
|
+
async getOrThrow(id) {
|
|
7486
|
+
const definition = await this.get(id);
|
|
7487
|
+
if (!definition) {
|
|
7488
|
+
throw new Error(`[AXPIntegrationProviderRegistry] Unknown integration provider '${id}'.`);
|
|
7489
|
+
}
|
|
7490
|
+
return definition;
|
|
7491
|
+
}
|
|
7492
|
+
clearCache() {
|
|
7493
|
+
this.cache = null;
|
|
7494
|
+
}
|
|
7495
|
+
//#endregion
|
|
7496
|
+
//#region ---- Private Methods ----
|
|
7497
|
+
async load() {
|
|
7498
|
+
if (this.cache) {
|
|
7499
|
+
return this.cache;
|
|
7500
|
+
}
|
|
7501
|
+
const context = new AXPIntegrationProviderContext();
|
|
7502
|
+
const providerList = this.providers ?? [];
|
|
7503
|
+
for (const provider of providerList) {
|
|
7504
|
+
const resolved = provider instanceof Promise ? await provider : provider;
|
|
7505
|
+
await resolved.provide(context);
|
|
7506
|
+
}
|
|
7507
|
+
this.cache = new Map(context.getDefinitions());
|
|
7508
|
+
return this.cache;
|
|
7509
|
+
}
|
|
7510
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPIntegrationProviderRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
7511
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPIntegrationProviderRegistryService, providedIn: 'root' }); }
|
|
7512
|
+
}
|
|
7513
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPIntegrationProviderRegistryService, decorators: [{
|
|
7514
|
+
type: Injectable,
|
|
7515
|
+
args: [{ providedIn: 'root' }]
|
|
7516
|
+
}] });
|
|
7517
|
+
|
|
7518
|
+
const AXP_INTEGRATION_SECRET_STORE = new InjectionToken('AXP_INTEGRATION_SECRET_STORE');
|
|
7519
|
+
class AXPIntegrationRuntimeService {
|
|
7520
|
+
}
|
|
7521
|
+
class AXPIntegrationOAuthService {
|
|
7522
|
+
}
|
|
7523
|
+
const AXP_INTEGRATION_OAUTH_SERVICE = new InjectionToken('AXP_INTEGRATION_OAUTH_SERVICE');
|
|
7524
|
+
//#endregion
|
|
7525
|
+
|
|
7526
|
+
//#region ---- Integration barrel ----
|
|
7527
|
+
//#endregion
|
|
7528
|
+
|
|
6330
7529
|
class AXPLockService {
|
|
6331
7530
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPLockService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
6332
7531
|
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPLockService }); }
|
|
@@ -6335,18 +7534,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
6335
7534
|
type: Injectable
|
|
6336
7535
|
}] });
|
|
6337
7536
|
|
|
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
7537
|
//#region ---- Injection Token ----
|
|
6351
7538
|
/**
|
|
6352
7539
|
* Injection token for status providers
|
|
@@ -6558,356 +7745,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
6558
7745
|
args: [{ providedIn: 'root' }]
|
|
6559
7746
|
}] });
|
|
6560
7747
|
|
|
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
|
-
}
|
|
7748
|
+
//#region ---- Types ----
|
|
7749
|
+
//#endregion
|
|
6911
7750
|
|
|
6912
7751
|
class AXPClipBoardService {
|
|
6913
7752
|
constructor() {
|
|
@@ -7590,5 +8429,5 @@ class AXPVersioningService {
|
|
|
7590
8429
|
* Generated bundle index. Do not edit.
|
|
7591
8430
|
*/
|
|
7592
8431
|
|
|
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,
|
|
8432
|
+
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, AXPIntegrationOAuthService, AXPIntegrationProviderContext, AXPIntegrationProviderRegistryService, AXPIntegrationRuntimeService, 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_INTEGRATION_OAUTH_SERVICE, AXP_INTEGRATION_PROVIDER, AXP_INTEGRATION_RUNTIME_PROVIDER, AXP_INTEGRATION_SECRET_STORE, 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, 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, coerceAXPSettingRawValue, coerceAXPSettingRawValueByKind, configPlatform, createBackgroundOperationStore, createEntityCommandOptions, createEntityRowExpressionScope, createEntitySearchCommand, createMenuContext, createMenuMiddleware, createUnsavedChangesCanDeactivateGuard, downloadGraphicFromElement, evaluateEntityDisplayTemplate, findManualPresetIdByOperation, formatEntityRowDisplayTitle, inferSettingCoercionKind, normalizePositiveNumberOption, normalizeRouterUrl, peelSettingValueKey, printGraphicFromElement, provideDynamicHomePage, provideMenuMiddleware, resolveDateFilterPresets, resolveEntityModifyPopupTitle, resolveEntityRowDisplayText, resolveEntityRowDisplayTitle, resolveEntityRowDisplayTitleFromColumnContext, resolveEntitySearchTitleFallback, resolveGraphicElementSize, resolveGraphicPrintContent, resolveQueryMatchingDisplayText, resolveSearchDisplayTemplate, resolveSearchableText, searchInRecordValues, searchInSearchableValue, serializeSvgElement, shouldApplyDefaultMultiLanguageToEntityProperty, shouldApplyDefaultMultiLanguageToWidgetNode, shouldApplyDefaultMultiLanguageToWidgetProperty, triggerBrowserDownload, withDefaultMultiLanguageOnWidgetNodeTree, withDefaultMultiLanguageOnWidgetProperty };
|
|
7594
8433
|
//# sourceMappingURL=acorex-platform-common.mjs.map
|