@acorex-platform/framework-client 2.0.0-next.3 → 2.0.0-next.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fesm2022/acorex-platform-framework-client-auth.mjs +8 -9
- package/fesm2022/acorex-platform-framework-client-auth.mjs.map +1 -1
- package/fesm2022/{acorex-platform-framework-client-common-common-settings.provider-ChU2tvyt.mjs → acorex-platform-framework-client-common-common-settings.provider-Biu0_ryw.mjs} +35 -2
- package/fesm2022/acorex-platform-framework-client-common-common-settings.provider-Biu0_ryw.mjs.map +1 -0
- package/fesm2022/acorex-platform-framework-client-common.mjs +1387 -538
- package/fesm2022/acorex-platform-framework-client-common.mjs.map +1 -1
- package/fesm2022/acorex-platform-framework-client-core.mjs +7 -2
- package/fesm2022/acorex-platform-framework-client-core.mjs.map +1 -1
- package/fesm2022/acorex-platform-framework-client-layout-builder.mjs +20 -0
- package/fesm2022/acorex-platform-framework-client-layout-builder.mjs.map +1 -1
- package/fesm2022/acorex-platform-framework-client-layout-components.mjs +520 -142
- package/fesm2022/acorex-platform-framework-client-layout-components.mjs.map +1 -1
- package/fesm2022/{acorex-platform-framework-client-layout-entity-acorex-platform-framework-client-layout-entity-DaOvPoNI.mjs → acorex-platform-framework-client-layout-entity-acorex-platform-framework-client-layout-entity-DmkFxSuJ.mjs} +1084 -223
- package/fesm2022/acorex-platform-framework-client-layout-entity-acorex-platform-framework-client-layout-entity-DmkFxSuJ.mjs.map +1 -0
- package/fesm2022/{acorex-platform-framework-client-layout-entity-attachments-page.component-BU7KEfyF.mjs → acorex-platform-framework-client-layout-entity-attachments-page.component-BV5WLQVo.mjs} +2 -2
- package/fesm2022/{acorex-platform-framework-client-layout-entity-attachments-page.component-BU7KEfyF.mjs.map → acorex-platform-framework-client-layout-entity-attachments-page.component-BV5WLQVo.mjs.map} +1 -1
- package/fesm2022/{acorex-platform-framework-client-layout-entity-bulk-entity.command-Boq0MUFK.mjs → acorex-platform-framework-client-layout-entity-bulk-entity.command-O8zdiHRH.mjs} +2 -2
- package/fesm2022/{acorex-platform-framework-client-layout-entity-bulk-entity.command-Boq0MUFK.mjs.map → acorex-platform-framework-client-layout-entity-bulk-entity.command-O8zdiHRH.mjs.map} +1 -1
- package/fesm2022/{acorex-platform-framework-client-layout-entity-file-list-popup.component-CMoFkzhf.mjs → acorex-platform-framework-client-layout-entity-file-list-popup.component-B3-0gJTQ.mjs} +4 -4
- package/fesm2022/{acorex-platform-framework-client-layout-entity-file-list-popup.component-CMoFkzhf.mjs.map → acorex-platform-framework-client-layout-entity-file-list-popup.component-B3-0gJTQ.mjs.map} +1 -1
- package/fesm2022/{acorex-platform-framework-client-layout-entity-gallery-page.component-Bre8friL.mjs → acorex-platform-framework-client-layout-entity-gallery-page.component-CjfLUBPZ.mjs} +2 -2
- package/fesm2022/{acorex-platform-framework-client-layout-entity-gallery-page.component-Bre8friL.mjs.map → acorex-platform-framework-client-layout-entity-gallery-page.component-CjfLUBPZ.mjs.map} +1 -1
- package/fesm2022/acorex-platform-framework-client-layout-entity.mjs +1 -1
- package/fesm2022/acorex-platform-framework-client-layout-views.mjs +94 -17
- package/fesm2022/acorex-platform-framework-client-layout-views.mjs.map +1 -1
- package/fesm2022/acorex-platform-framework-client-layout-widget-core.mjs +285 -184
- package/fesm2022/acorex-platform-framework-client-layout-widget-core.mjs.map +1 -1
- package/fesm2022/{acorex-platform-framework-client-layout-widgets-acorex-platform-framework-client-layout-widgets-1UJh0Qj4.mjs → acorex-platform-framework-client-layout-widgets-acorex-platform-framework-client-layout-widgets-CpmGqDUE.mjs} +1167 -265
- package/fesm2022/acorex-platform-framework-client-layout-widgets-acorex-platform-framework-client-layout-widgets-CpmGqDUE.mjs.map +1 -0
- package/fesm2022/acorex-platform-framework-client-layout-widgets-filter-default-operators-widget-edit.component-DOEn58--.mjs +103 -0
- package/fesm2022/acorex-platform-framework-client-layout-widgets-filter-default-operators-widget-edit.component-DOEn58--.mjs.map +1 -0
- package/fesm2022/{acorex-platform-framework-client-layout-widgets-repeater-widget-column.component-SHCgo4UC.mjs → acorex-platform-framework-client-layout-widgets-repeater-widget-column.component-XgjDH9Oc.mjs} +3 -3
- package/fesm2022/{acorex-platform-framework-client-layout-widgets-repeater-widget-column.component-SHCgo4UC.mjs.map → acorex-platform-framework-client-layout-widgets-repeater-widget-column.component-XgjDH9Oc.mjs.map} +1 -1
- package/fesm2022/{acorex-platform-framework-client-layout-widgets-text-block-widget-designer.component-BYsrp8LT.mjs → acorex-platform-framework-client-layout-widgets-text-block-widget-designer.component-DDczHmAA.mjs} +2 -2
- package/fesm2022/{acorex-platform-framework-client-layout-widgets-text-block-widget-designer.component-BYsrp8LT.mjs.map → acorex-platform-framework-client-layout-widgets-text-block-widget-designer.component-DDczHmAA.mjs.map} +1 -1
- package/fesm2022/acorex-platform-framework-client-layout-widgets.mjs +1 -1
- package/fesm2022/acorex-platform-framework-client-realtime.mjs +49 -7
- package/fesm2022/acorex-platform-framework-client-realtime.mjs.map +1 -1
- package/fesm2022/{acorex-platform-framework-client-themes-default-entity-master-create-view.component-DwCoz2Du.mjs → acorex-platform-framework-client-themes-default-entity-master-create-view.component-DPU6jTDk.mjs} +8 -2
- package/fesm2022/acorex-platform-framework-client-themes-default-entity-master-create-view.component-DPU6jTDk.mjs.map +1 -0
- package/fesm2022/{acorex-platform-framework-client-themes-default-entity-master-modify-view.component-BCyFK60f.mjs → acorex-platform-framework-client-themes-default-entity-master-modify-view.component-CLdQJ8e8.mjs} +8 -2
- package/fesm2022/acorex-platform-framework-client-themes-default-entity-master-modify-view.component-CLdQJ8e8.mjs.map +1 -0
- package/fesm2022/{acorex-platform-framework-client-themes-default-error-401.component-1lfBpY_d.mjs → acorex-platform-framework-client-themes-default-error-401.component-DszlyIaw.mjs} +5 -11
- package/fesm2022/acorex-platform-framework-client-themes-default-error-401.component-DszlyIaw.mjs.map +1 -0
- package/fesm2022/{acorex-platform-framework-client-themes-default-error-404.component-BeWyRCJr.mjs → acorex-platform-framework-client-themes-default-error-404.component-BusKNAit.mjs} +5 -11
- package/fesm2022/acorex-platform-framework-client-themes-default-error-404.component-BusKNAit.mjs.map +1 -0
- package/fesm2022/{acorex-platform-framework-client-themes-default-error-offline.component-PvveUj5c.mjs → acorex-platform-framework-client-themes-default-error-offline.component-Abf14p44.mjs} +15 -4
- package/fesm2022/acorex-platform-framework-client-themes-default-error-offline.component-Abf14p44.mjs.map +1 -0
- package/fesm2022/acorex-platform-framework-client-themes-default-error-page-go-home-B9VmB9Vt.mjs +19 -0
- package/fesm2022/acorex-platform-framework-client-themes-default-error-page-go-home-B9VmB9Vt.mjs.map +1 -0
- package/fesm2022/acorex-platform-framework-client-themes-default.mjs +306 -129
- package/fesm2022/acorex-platform-framework-client-themes-default.mjs.map +1 -1
- package/fesm2022/acorex-platform-framework-client-themes-shared.mjs +11 -5
- package/fesm2022/acorex-platform-framework-client-themes-shared.mjs.map +1 -1
- package/i18n/en-AU/general.json +32 -4
- package/i18n/en-AU/layout.json +2 -1
- package/i18n/en-AU/platform-layout-widgets.json +7 -1
- package/i18n/en-US/general.json +32 -4
- package/i18n/en-US/layout.json +2 -1
- package/i18n/en-US/platform-layout-widgets.json +7 -1
- package/i18n/fa-IR/general.json +32 -4
- package/i18n/fa-IR/layout.json +2 -1
- package/i18n/fa-IR/platform-layout-widgets.json +7 -1
- package/package.json +6 -6
- package/types/acorex-platform-framework-client-auth.d.ts +21 -11
- package/types/acorex-platform-framework-client-common.d.ts +230 -14
- package/types/acorex-platform-framework-client-layout-components.d.ts +47 -6
- package/types/acorex-platform-framework-client-layout-entity.d.ts +187 -35
- package/types/acorex-platform-framework-client-layout-views.d.ts +15 -0
- package/types/acorex-platform-framework-client-layout-widget-core.d.ts +64 -14
- package/types/acorex-platform-framework-client-layout-widgets.d.ts +145 -9
- package/types/acorex-platform-framework-client-realtime.d.ts +5 -0
- package/types/acorex-platform-framework-client-themes-default.d.ts +16 -5
- package/fesm2022/acorex-platform-framework-client-common-common-settings.provider-ChU2tvyt.mjs.map +0 -1
- package/fesm2022/acorex-platform-framework-client-layout-entity-acorex-platform-framework-client-layout-entity-DaOvPoNI.mjs.map +0 -1
- package/fesm2022/acorex-platform-framework-client-layout-widgets-acorex-platform-framework-client-layout-widgets-1UJh0Qj4.mjs.map +0 -1
- package/fesm2022/acorex-platform-framework-client-themes-default-entity-master-create-view.component-DwCoz2Du.mjs.map +0 -1
- package/fesm2022/acorex-platform-framework-client-themes-default-entity-master-modify-view.component-BCyFK60f.mjs.map +0 -1
- package/fesm2022/acorex-platform-framework-client-themes-default-error-401.component-1lfBpY_d.mjs.map +0 -1
- package/fesm2022/acorex-platform-framework-client-themes-default-error-404.component-BeWyRCJr.mjs.map +0 -1
- package/fesm2022/acorex-platform-framework-client-themes-default-error-offline.component-PvveUj5c.mjs.map +0 -1
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
2
|
import { InjectionToken, inject, Injectable, Injector, signal, ApplicationRef, DestroyRef, makeEnvironmentProviders, NgModule, computed, input, ChangeDetectionStrategy, ViewEncapsulation, Component, output, effect, viewChild, afterNextRender, untracked, ErrorHandler, EventEmitter, Input, Output, Directive, Inject, runInInjectionContext, model, linkedSignal } from '@angular/core';
|
|
3
|
-
import * as i5 from '@angular/router';
|
|
4
|
-
import { Router, NavigationEnd, NavigationStart, ROUTES, RedirectCommand, UrlTree, RouterModule } from '@angular/router';
|
|
5
3
|
import { AXPSessionService, AXPSessionStatus, AXPUnauthenticatedError, AXPAuthGuard, commandMessageTextForError } from '@acorex-platform/framework-client/auth';
|
|
6
4
|
export { commandMessageTextForError } from '@acorex-platform/framework-client/auth';
|
|
7
|
-
import { combineLatest, map, distinctUntilChanged, Subject, ReplaySubject, firstValueFrom, filter, take, timer, timeout, switchMap, from, of } from 'rxjs';
|
|
8
|
-
import { unwrapValueProperty, isLocaleStringMap, coerceUnknownToDate, coerceUnknownToFiniteNumber, coerceUnknownToBoolean, coerceUnknownToTrimmedString, AXPPlatformScope, runtimeQueryPayloadData, runtimeCommandPayloadData, AXPRegionalSetting, AXPWidgetsCatalog, axpResolveUserNotificationDelivery, resolveMultiLanguageString, coerceUnknownToMultiLanguageString, AXPKeyboardShortcutPriority, AXP_PLATFORM_ERROR_CODES, AXPRequestError, mapUnknownToAXPErrorBody, AXPSystemActionType, runtimeQueryCollectionData, createRandomUuid, runtimeCommandFailure, runtimeCommandSuccessObject, runtimeQuerySuccessObject } from '@acorex-platform/framework-shared/core';
|
|
9
5
|
import * as i4$2 from '@acorex-platform/framework-client/core';
|
|
10
6
|
import { AXPDebugService, AXPBroadcastEventService, AXP_SETTINGS_DEFINITION_DIALOG_OPENER, objectKeyValueTransforms, AXPDataGenerator, AXPKeyboardShortcutRegistry, normalizeKeyboardShortcuts, AXPDeviceService, provideAppStartUpTask, AXP_APP_STARTUP_PRIORITY, chordToKbdItemKeys, hasForegroundOverlayLayer, AXPExpressionEvaluatorService, AXPContextEvalFactory, AXPModuleManifestModule, AXPComponentSlotModule, AXP_EXPRESSION_EVALUATOR_SCOPE_PROVIDER, AXP_LOGO_IMAGE_SRC_RESOLVER, AXPHookService, AXPPageContextRegistry } from '@acorex-platform/framework-client/core';
|
|
11
7
|
export { AXPDebugService } from '@acorex-platform/framework-client/core';
|
|
8
|
+
import { unwrapValueProperty, isLocaleStringMap, coerceUnknownToDate, coerceUnknownToFiniteNumber, coerceUnknownToBoolean, coerceUnknownToTrimmedString, AXPPlatformScope, runtimeQueryPayloadData, runtimeCommandPayloadData, AXPRegionalSetting, AXPWidgetsCatalog, axpResolveUserNotificationDelivery, resolveMultiLanguageString, coerceUnknownToMultiLanguageString, AXPKeyboardShortcutPriority, AXP_PLATFORM_ERROR_CODES, AXPRequestError, mapUnknownToAXPErrorBody, AXPSystemActionType, runtimeQueryCollectionData, createRandomUuid, runtimeCommandFailure, runtimeCommandSuccessObject, runtimeQuerySuccessObject } from '@acorex-platform/framework-shared/core';
|
|
12
9
|
import { merge, sortBy, isString, toString, isPlainObject, mergeWith, defaultTo, isEmpty, isEqual, cloneDeep, get, isNumber, isNil, isObject, omit, uniq } from 'lodash-es';
|
|
10
|
+
import { combineLatest, map, distinctUntilChanged, Subject, ReplaySubject, firstValueFrom, filter, take, timer, timeout, switchMap, from, of } from 'rxjs';
|
|
13
11
|
import { AXPQueryService, AXPCommandService, provideCommandSetups, provideQuerySetups } from '@acorex-platform/framework-client/runtime';
|
|
14
12
|
import Dexie from 'dexie';
|
|
13
|
+
import * as i5 from '@angular/router';
|
|
14
|
+
import { Router, NavigationEnd, NavigationStart, ROUTES, RedirectCommand, UrlTree, RouterModule } from '@angular/router';
|
|
15
15
|
import { AXDialogService } from '@acorex/components/dialog';
|
|
16
16
|
import * as i6 from '@acorex/core/translation';
|
|
17
17
|
import { AXTranslationService, AXTranslationModule, translateSync, resolveMultiLanguageString as resolveMultiLanguageString$1 } from '@acorex/core/translation';
|
|
@@ -34,7 +34,7 @@ import { AXProgressBarModule } from '@acorex/components/progress-bar';
|
|
|
34
34
|
import * as i3$1 from '@acorex/core/format';
|
|
35
35
|
import { AXFormatModule, AXFormatService } from '@acorex/core/format';
|
|
36
36
|
import { AXNotificationService, AXNotificationModule } from '@acorex/components/notification';
|
|
37
|
-
import { filter as filter$1 } from 'rxjs/operators';
|
|
37
|
+
import { filter as filter$1, map as map$1, distinctUntilChanged as distinctUntilChanged$1 } from 'rxjs/operators';
|
|
38
38
|
import { signalStore, withState, withMethods, patchState, withHooks } from '@ngrx/signals';
|
|
39
39
|
import { AXPopupService, AXPopupModule } from '@acorex/components/popup';
|
|
40
40
|
import { AXToastModule, AXToastService } from '@acorex/components/toast';
|
|
@@ -238,162 +238,109 @@ const ALL_DEFAULT_OPERATORS = [
|
|
|
238
238
|
BETWEEN_OPER,
|
|
239
239
|
];
|
|
240
240
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
241
|
+
//#endregion
|
|
242
|
+
//#region ----- Catalog -----
|
|
243
|
+
const I18N$1 = '@general:settings.experience.entity.filter-default-operators.widgets';
|
|
244
|
+
/** Single-value choice filters. `in` stays on the multiple-selection path. */
|
|
245
|
+
const EQUALITY_OPERATORS = [EQ_OPER, NOT_EQ_OPER];
|
|
246
|
+
function toChoices(operators) {
|
|
247
|
+
return operators.map((operator) => ({ name: operator.name, titleKey: operator.title }));
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* One row per filter widget. `codeDefault` matches the widget config constant
|
|
251
|
+
* (`AXP_*_FILTER_DEFAULT_OPERATOR`). Stored settings keep only values that differ from it.
|
|
252
|
+
*/
|
|
253
|
+
const AXP_FILTER_DEFAULT_OPERATOR_WIDGETS = [
|
|
254
|
+
{
|
|
255
|
+
widgetName: 'string-filter',
|
|
256
|
+
titleKey: `${I18N$1}.string-filter`,
|
|
257
|
+
codeDefault: 'contains',
|
|
258
|
+
operators: toChoices(STRING_OPERATORS),
|
|
259
|
+
},
|
|
260
|
+
{
|
|
261
|
+
widgetName: 'number-filter',
|
|
262
|
+
titleKey: `${I18N$1}.number-filter`,
|
|
263
|
+
codeDefault: 'equal',
|
|
264
|
+
operators: toChoices(NUMBER_OPERATORS),
|
|
265
|
+
},
|
|
266
|
+
{
|
|
267
|
+
widgetName: 'datetime-filter',
|
|
268
|
+
titleKey: `${I18N$1}.datetime-filter`,
|
|
269
|
+
codeDefault: 'equal',
|
|
270
|
+
operators: toChoices(DATE_OPERATORS),
|
|
271
|
+
},
|
|
272
|
+
{
|
|
273
|
+
widgetName: 'boolean-filter',
|
|
274
|
+
titleKey: `${I18N$1}.boolean-filter`,
|
|
275
|
+
codeDefault: 'equal',
|
|
276
|
+
operators: toChoices(BOOLEAN_OPERATORS),
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
widgetName: 'select-filter',
|
|
280
|
+
titleKey: `${I18N$1}.select-filter`,
|
|
281
|
+
codeDefault: 'equal',
|
|
282
|
+
operators: toChoices(EQUALITY_OPERATORS),
|
|
283
|
+
},
|
|
284
|
+
{
|
|
285
|
+
widgetName: 'lookup-filter',
|
|
286
|
+
titleKey: `${I18N$1}.lookup-filter`,
|
|
287
|
+
codeDefault: 'equal',
|
|
288
|
+
operators: toChoices(EQUALITY_OPERATORS),
|
|
289
|
+
},
|
|
290
|
+
{
|
|
291
|
+
widgetName: 'status-filter',
|
|
292
|
+
titleKey: `${I18N$1}.status-filter`,
|
|
293
|
+
codeDefault: 'equal',
|
|
294
|
+
operators: toChoices(EQUALITY_OPERATORS),
|
|
295
|
+
},
|
|
296
|
+
{
|
|
297
|
+
widgetName: 'time-duration-filter',
|
|
298
|
+
titleKey: `${I18N$1}.time-duration-filter`,
|
|
299
|
+
codeDefault: 'equal',
|
|
300
|
+
operators: toChoices(NUMBER_OPERATORS),
|
|
301
|
+
},
|
|
254
302
|
];
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
303
|
+
const AXP_FILTER_DEFAULT_OPERATORS_WIDGET_NAME = 'filter-default-operators';
|
|
304
|
+
//#endregion
|
|
305
|
+
//#region ----- Map helpers -----
|
|
306
|
+
function readFilterDefaultOperatorMap(value) {
|
|
307
|
+
if (value == null || typeof value !== 'object' || Array.isArray(value)) {
|
|
308
|
+
return {};
|
|
259
309
|
}
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
const calendar = calendarService.calendar;
|
|
265
|
-
switch (presetId) {
|
|
266
|
-
case 'today':
|
|
267
|
-
return {
|
|
268
|
-
operation: { type: 'between' },
|
|
269
|
-
value: {
|
|
270
|
-
from: calendar.startOf(currentDate, 'day').date.toISOString(),
|
|
271
|
-
to: calendar.endOf(currentDate, 'day').date.toISOString(),
|
|
272
|
-
},
|
|
273
|
-
};
|
|
274
|
-
case 'this-week':
|
|
275
|
-
return {
|
|
276
|
-
operation: { type: 'between' },
|
|
277
|
-
value: {
|
|
278
|
-
from: calendar.startOf(currentDate, 'week').date.toISOString(),
|
|
279
|
-
to: calendar.endOf(currentDate, 'week').date.toISOString(),
|
|
280
|
-
},
|
|
281
|
-
};
|
|
282
|
-
case 'this-month':
|
|
283
|
-
return {
|
|
284
|
-
operation: { type: 'between' },
|
|
285
|
-
value: {
|
|
286
|
-
from: calendar.startOf(currentDate, 'month').date.toISOString(),
|
|
287
|
-
to: calendar.endOf(currentDate, 'month').date.toISOString(),
|
|
288
|
-
},
|
|
289
|
-
};
|
|
290
|
-
case 'yesterday': {
|
|
291
|
-
const yesterday = calendar.add(currentDate, 'day', -1);
|
|
292
|
-
return {
|
|
293
|
-
operation: { type: 'between' },
|
|
294
|
-
value: {
|
|
295
|
-
from: yesterday.startOf('day').date.toISOString(),
|
|
296
|
-
to: yesterday.endOf('day').date.toISOString(),
|
|
297
|
-
},
|
|
298
|
-
};
|
|
299
|
-
}
|
|
300
|
-
case 'last-week': {
|
|
301
|
-
const lastWeek = calendar.add(currentDate, 'week', -1);
|
|
302
|
-
return {
|
|
303
|
-
operation: { type: 'between' },
|
|
304
|
-
value: {
|
|
305
|
-
from: lastWeek.startOf('week').date.toISOString(),
|
|
306
|
-
to: lastWeek.endOf('week').date.toISOString(),
|
|
307
|
-
},
|
|
308
|
-
};
|
|
309
|
-
}
|
|
310
|
-
case 'last-month': {
|
|
311
|
-
const lastMonth = calendar.add(currentDate, 'month', -1);
|
|
312
|
-
return {
|
|
313
|
-
operation: { type: 'between' },
|
|
314
|
-
value: {
|
|
315
|
-
from: lastMonth.startOf('month').date.toISOString(),
|
|
316
|
-
to: lastMonth.endOf('month').date.toISOString(),
|
|
317
|
-
},
|
|
318
|
-
};
|
|
319
|
-
}
|
|
320
|
-
case 'tomorrow': {
|
|
321
|
-
const tomorrow = calendar.add(currentDate, 'day', 1);
|
|
322
|
-
return {
|
|
323
|
-
operation: { type: 'between' },
|
|
324
|
-
value: {
|
|
325
|
-
from: tomorrow.startOf('day').date.toISOString(),
|
|
326
|
-
to: tomorrow.endOf('day').date.toISOString(),
|
|
327
|
-
},
|
|
328
|
-
};
|
|
329
|
-
}
|
|
330
|
-
case 'next-week': {
|
|
331
|
-
const nextWeek = calendar.add(currentDate, 'week', 1);
|
|
332
|
-
return {
|
|
333
|
-
operation: { type: 'between' },
|
|
334
|
-
value: {
|
|
335
|
-
from: nextWeek.startOf('week').date.toISOString(),
|
|
336
|
-
to: nextWeek.endOf('week').date.toISOString(),
|
|
337
|
-
},
|
|
338
|
-
};
|
|
339
|
-
}
|
|
340
|
-
case 'next-month': {
|
|
341
|
-
const nextMonth = calendar.add(currentDate, 'month', 1);
|
|
342
|
-
return {
|
|
343
|
-
operation: { type: 'between' },
|
|
344
|
-
value: {
|
|
345
|
-
from: nextMonth.startOf('month').date.toISOString(),
|
|
346
|
-
to: nextMonth.endOf('month').date.toISOString(),
|
|
347
|
-
},
|
|
348
|
-
};
|
|
310
|
+
const result = {};
|
|
311
|
+
for (const [widgetName, operatorName] of Object.entries(value)) {
|
|
312
|
+
if (typeof operatorName === 'string' && operatorName.length > 0) {
|
|
313
|
+
result[widgetName] = operatorName;
|
|
349
314
|
}
|
|
350
|
-
case 'between':
|
|
351
|
-
return { operation: { type: 'between' } };
|
|
352
|
-
case 'greater-than':
|
|
353
|
-
return { operation: { type: 'greaterThan' } };
|
|
354
|
-
case 'less-than':
|
|
355
|
-
return { operation: { type: 'lessThan' } };
|
|
356
|
-
default:
|
|
357
|
-
return {
|
|
358
|
-
operation: { type: 'between' },
|
|
359
|
-
value: { from: '', to: '' },
|
|
360
|
-
};
|
|
361
315
|
}
|
|
316
|
+
return result;
|
|
362
317
|
}
|
|
363
|
-
function
|
|
364
|
-
|
|
365
|
-
return undefined;
|
|
366
|
-
}
|
|
367
|
-
return presets.find((p) => p.kind === 'manual' && p.operationType === operationType)?.id;
|
|
318
|
+
function findFilterDefaultOperatorWidget(widgetName) {
|
|
319
|
+
return AXP_FILTER_DEFAULT_OPERATOR_WIDGETS.find((widget) => widget.widgetName === widgetName);
|
|
368
320
|
}
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
const
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
/** Strict descendants allowed to override a setting owned at `scope`. */
|
|
376
|
-
const AXP_SETTING_SCOPE_DESCENDANTS = {
|
|
377
|
-
P: ['T', 'U'],
|
|
378
|
-
T: ['U'],
|
|
379
|
-
U: [],
|
|
380
|
-
};
|
|
381
|
-
function normalizeOverridableAt(ownerScope, overridableAt) {
|
|
382
|
-
const scopes = overridableAt ?? [];
|
|
383
|
-
const allowed = AXP_SETTING_SCOPE_DESCENDANTS[ownerScope];
|
|
384
|
-
for (const scope of scopes) {
|
|
385
|
-
if (!allowed.includes(scope)) {
|
|
386
|
-
throw new Error(`Setting at scope "${ownerScope}" cannot be overridable at "${scope}". Allowed: [${allowed.join(', ')}].`);
|
|
387
|
-
}
|
|
321
|
+
/** Stored operator when it belongs to that widget; otherwise the code default. */
|
|
322
|
+
function resolveStoredFilterDefaultOperator(widgetName, stored, codeDefault) {
|
|
323
|
+
const selected = readFilterDefaultOperatorMap(stored)[widgetName];
|
|
324
|
+
if (!selected) {
|
|
325
|
+
return codeDefault;
|
|
388
326
|
}
|
|
389
|
-
|
|
327
|
+
const widget = findFilterDefaultOperatorWidget(widgetName);
|
|
328
|
+
if (widget && !widget.operators.some((operator) => operator.name === selected)) {
|
|
329
|
+
return codeDefault;
|
|
330
|
+
}
|
|
331
|
+
return selected;
|
|
390
332
|
}
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
333
|
+
/** Persist a change only when it differs from the widget's code default. */
|
|
334
|
+
function writeFilterDefaultOperatorOverride(current, widgetName, operatorName, codeDefault) {
|
|
335
|
+
const next = { ...readFilterDefaultOperatorMap(current) };
|
|
336
|
+
if (!operatorName || operatorName === codeDefault) {
|
|
337
|
+
delete next[widgetName];
|
|
338
|
+
return next;
|
|
394
339
|
}
|
|
395
|
-
|
|
340
|
+
next[widgetName] = operatorName;
|
|
341
|
+
return next;
|
|
396
342
|
}
|
|
343
|
+
//#endregion
|
|
397
344
|
|
|
398
345
|
var AXPCommonSettings;
|
|
399
346
|
(function (AXPCommonSettings) {
|
|
@@ -413,6 +360,11 @@ var AXPCommonSettings;
|
|
|
413
360
|
* IndexedDB platform-settings cache. When false, bootstrap redirects to /auth/unavailable.
|
|
414
361
|
*/
|
|
415
362
|
AXPCommonSettings["AllowWorkingOffline"] = "Common:Setting:Offline.AllowWorkingOffline";
|
|
363
|
+
/**
|
|
364
|
+
* Platform (P): when false, hide user availability (account menu, avatar dot, user card).
|
|
365
|
+
* Products without a status surface turn this off.
|
|
366
|
+
*/
|
|
367
|
+
AXPCommonSettings["UserStatusEnabled"] = "Common:Setting:Presence.UserStatusEnabled";
|
|
416
368
|
AXPCommonSettings["ShowRowIndexColumn"] = "Common:Setting:Entity.ShowRowIndexColumn";
|
|
417
369
|
/** When true, the entity master-list table derives header bands from entity sections (property groups). */
|
|
418
370
|
AXPCommonSettings["EntityMultiLevelHeaders"] = "Common:Setting:Entity.MultiLevelHeaders";
|
|
@@ -423,6 +375,8 @@ var AXPCommonSettings;
|
|
|
423
375
|
AXPCommonSettings["MaxOpenTabs"] = "Common:Setting:Navigation.MaxOpenTabs";
|
|
424
376
|
/** When true with entity list quick-search input, filters emit {@link AXP_QUERY_QUICK_SEARCH_FIELD} instead of inline OR. */
|
|
425
377
|
AXPCommonSettings["QuickSearchEnabled"] = "Common:Setting:Entity.QuickSearchEnabled";
|
|
378
|
+
/** Platform map of filter widget name → operator. Only overrides are stored. */
|
|
379
|
+
AXPCommonSettings["FilterDefaultOperators"] = "Common:Setting:Entity.FilterDefaultOperators";
|
|
426
380
|
})(AXPCommonSettings || (AXPCommonSettings = {}));
|
|
427
381
|
/**
|
|
428
382
|
* File-chooser keys live here so `common` can register Experience settings
|
|
@@ -445,6 +399,31 @@ const AXP_RUNTIME_QUERY_CACHE_DEFAULTS = {
|
|
|
445
399
|
};
|
|
446
400
|
/** Default entity list presentation when the user has not chosen a layout. */
|
|
447
401
|
const AXP_ENTITY_LIST_DISPLAY_MODE_DEFAULT = 'table';
|
|
402
|
+
/** User status stays visible until a platform setting turns it off. */
|
|
403
|
+
const AXP_USER_STATUS_ENABLED_DEFAULT = true;
|
|
404
|
+
|
|
405
|
+
/** Strict descendants allowed to override a setting owned at `scope`. */
|
|
406
|
+
const AXP_SETTING_SCOPE_DESCENDANTS = {
|
|
407
|
+
P: ['T', 'U'],
|
|
408
|
+
T: ['U'],
|
|
409
|
+
U: [],
|
|
410
|
+
};
|
|
411
|
+
function normalizeOverridableAt(ownerScope, overridableAt) {
|
|
412
|
+
const scopes = overridableAt ?? [];
|
|
413
|
+
const allowed = AXP_SETTING_SCOPE_DESCENDANTS[ownerScope];
|
|
414
|
+
for (const scope of scopes) {
|
|
415
|
+
if (!allowed.includes(scope)) {
|
|
416
|
+
throw new Error(`Setting at scope "${ownerScope}" cannot be overridable at "${scope}". Allowed: [${allowed.join(', ')}].`);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
return scopes;
|
|
420
|
+
}
|
|
421
|
+
function isSettingVisibleOnScope(setting, pageScope) {
|
|
422
|
+
if (setting.scope === pageScope) {
|
|
423
|
+
return true;
|
|
424
|
+
}
|
|
425
|
+
return (setting.overridableAt ?? []).includes(pageScope);
|
|
426
|
+
}
|
|
448
427
|
|
|
449
428
|
class AXPSettingDefinitionProviderContext {
|
|
450
429
|
constructor() {
|
|
@@ -584,24 +563,64 @@ const AXP_SETTING_DEFINITION_PROVIDER = new InjectionToken('AXP_SETTING_DEFINITI
|
|
|
584
563
|
class AXPSettingDefinitionProviderService {
|
|
585
564
|
constructor() {
|
|
586
565
|
this.providers = inject(AXP_SETTING_DEFINITION_PROVIDER, { optional: true });
|
|
566
|
+
this.sessionService = inject(AXPSessionService);
|
|
587
567
|
this.cache = null;
|
|
568
|
+
/**
|
|
569
|
+
* Tenant, application, and enabled edition features.
|
|
570
|
+
* Providers such as Telegram gate sections on `isModuleEnabled` at build time,
|
|
571
|
+
* so a catalog built before login must be rebuilt once features arrive.
|
|
572
|
+
*/
|
|
573
|
+
this.cacheAvailabilityKey = null;
|
|
588
574
|
this.loadPromise = null;
|
|
589
575
|
}
|
|
576
|
+
/** Same identity permissions use when deciding the definition cache is still valid. */
|
|
577
|
+
getAvailabilityKey() {
|
|
578
|
+
return this.buildAvailabilityKey();
|
|
579
|
+
}
|
|
580
|
+
buildAvailabilityKey() {
|
|
581
|
+
const enabled = this.sessionService.features
|
|
582
|
+
.filter((feature) => feature.value === true && !!feature.name)
|
|
583
|
+
.map((feature) => feature.name)
|
|
584
|
+
.sort();
|
|
585
|
+
const applicationId = this.sessionService.application?.id ?? '';
|
|
586
|
+
const tenantId = this.sessionService.tenant?.id ?? '';
|
|
587
|
+
return `${tenantId}|${applicationId}|${enabled.join(',')}`;
|
|
588
|
+
}
|
|
590
589
|
async load() {
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
590
|
+
while (true) {
|
|
591
|
+
const availabilityKey = this.buildAvailabilityKey();
|
|
592
|
+
if (this.cache && this.cacheAvailabilityKey === availabilityKey) {
|
|
593
|
+
return;
|
|
594
|
+
}
|
|
595
|
+
if (this.loadPromise) {
|
|
596
|
+
await this.loadPromise;
|
|
597
|
+
continue;
|
|
598
|
+
}
|
|
599
|
+
const keyAtStart = availabilityKey;
|
|
600
|
+
this.cache = null;
|
|
601
|
+
this.cacheAvailabilityKey = null;
|
|
602
|
+
let resolveLoad;
|
|
603
|
+
this.loadPromise = new Promise((resolve) => {
|
|
604
|
+
resolveLoad = resolve;
|
|
597
605
|
});
|
|
606
|
+
try {
|
|
607
|
+
await this.loadDefinitions();
|
|
608
|
+
if (this.buildAvailabilityKey() === keyAtStart) {
|
|
609
|
+
this.cacheAvailabilityKey = keyAtStart;
|
|
610
|
+
}
|
|
611
|
+
else {
|
|
612
|
+
// Features changed while providers ran. Drop the catalog and rebuild.
|
|
613
|
+
this.cache = null;
|
|
614
|
+
this.cacheAvailabilityKey = null;
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
finally {
|
|
618
|
+
this.loadPromise = null;
|
|
619
|
+
resolveLoad();
|
|
620
|
+
}
|
|
598
621
|
}
|
|
599
|
-
await this.loadPromise;
|
|
600
622
|
}
|
|
601
623
|
async loadDefinitions() {
|
|
602
|
-
if (this.cache) {
|
|
603
|
-
return;
|
|
604
|
-
}
|
|
605
624
|
const context = new AXPSettingDefinitionProviderContext();
|
|
606
625
|
// Load providers from DI tokens — isolate failures so one provider cannot block bootstrap.
|
|
607
626
|
const providerList = this.normalizeProviders(this.providers);
|
|
@@ -629,6 +648,12 @@ class AXPSettingDefinitionProviderService {
|
|
|
629
648
|
}
|
|
630
649
|
async reload() {
|
|
631
650
|
this.cache = null;
|
|
651
|
+
this.cacheAvailabilityKey = null;
|
|
652
|
+
if (this.loadPromise) {
|
|
653
|
+
await this.loadPromise;
|
|
654
|
+
this.cache = null;
|
|
655
|
+
this.cacheAvailabilityKey = null;
|
|
656
|
+
}
|
|
632
657
|
await this.load();
|
|
633
658
|
}
|
|
634
659
|
async getListAsync(scope) {
|
|
@@ -2781,36 +2806,217 @@ class ScopedSettingService {
|
|
|
2781
2806
|
}
|
|
2782
2807
|
}
|
|
2783
2808
|
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
* modifiers and startup deadlocks.
|
|
2789
|
-
*/
|
|
2790
|
-
class AXPDefaultMultiLanguageConfigService {
|
|
2809
|
+
//#region ----- Service -----
|
|
2810
|
+
class AXPFilterDefaultOperatorService {
|
|
2811
|
+
//#endregion
|
|
2812
|
+
//#region ----- Lifecycle -----
|
|
2791
2813
|
constructor() {
|
|
2792
|
-
|
|
2793
|
-
this.
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
this.
|
|
2797
|
-
|
|
2814
|
+
//#region ----- Services & Dependencies -----
|
|
2815
|
+
this.settings = inject(AXPSettingsService);
|
|
2816
|
+
//#endregion
|
|
2817
|
+
//#region ----- State Signals -----
|
|
2818
|
+
this.overrides = signal({}, /* @ts-ignore */
|
|
2819
|
+
...(ngDevMode ? [{ debugName: "overrides" }] : /* istanbul ignore next */ []));
|
|
2820
|
+
this.refresh();
|
|
2821
|
+
this.settings.onChanged.subscribe(() => {
|
|
2822
|
+
this.refresh();
|
|
2823
|
+
});
|
|
2798
2824
|
}
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
.then((value) => this._applyDefault.set(value ?? DEFAULT_APPLY_DEFAULT_MULTILANGUAGE))
|
|
2805
|
-
.catch(() => this._applyDefault.set(DEFAULT_APPLY_DEFAULT_MULTILANGUAGE));
|
|
2806
|
-
}
|
|
2807
|
-
return this._applyDefault();
|
|
2825
|
+
//#endregion
|
|
2826
|
+
//#region ----- Resolve -----
|
|
2827
|
+
/** Platform override for this filter widget, or `codeDefault` when unset. */
|
|
2828
|
+
resolve(widgetName, codeDefault) {
|
|
2829
|
+
return resolveStoredFilterDefaultOperator(widgetName, this.overrides(), codeDefault);
|
|
2808
2830
|
}
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2831
|
+
//#endregion
|
|
2832
|
+
//#region ----- Cache -----
|
|
2833
|
+
refresh() {
|
|
2834
|
+
this.overrides.set(readFilterDefaultOperatorMap(this.settings.peekCached(AXPCommonSettings.FilterDefaultOperators)));
|
|
2835
|
+
}
|
|
2836
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPFilterDefaultOperatorService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
2837
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPFilterDefaultOperatorService, providedIn: 'root' }); }
|
|
2838
|
+
}
|
|
2839
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPFilterDefaultOperatorService, decorators: [{
|
|
2840
|
+
type: Injectable,
|
|
2841
|
+
args: [{ providedIn: 'root' }]
|
|
2842
|
+
}], ctorParameters: () => [] });
|
|
2843
|
+
//#endregion
|
|
2844
|
+
//#region ----- Widget lookup overlay -----
|
|
2845
|
+
function withFilterDefaultOperator(service, widgetName, widget) {
|
|
2846
|
+
if (!widget?.defaultFilterOperator) {
|
|
2847
|
+
return widget;
|
|
2848
|
+
}
|
|
2849
|
+
const resolved = service.resolve(widgetName, widget.defaultFilterOperator);
|
|
2850
|
+
if (resolved === widget.defaultFilterOperator) {
|
|
2851
|
+
return widget;
|
|
2852
|
+
}
|
|
2853
|
+
return { ...widget, defaultFilterOperator: resolved };
|
|
2854
|
+
}
|
|
2855
|
+
|
|
2856
|
+
const DEFAULT_DATE_FILTER_PRESETS = [
|
|
2857
|
+
{ id: 'today', title: '@general:terms.date-presets.today', kind: 'computed' },
|
|
2858
|
+
{ id: 'this-week', title: '@general:terms.date-presets.this-week', kind: 'computed' },
|
|
2859
|
+
{ id: 'this-month', title: '@general:terms.date-presets.this-month', kind: 'computed' },
|
|
2860
|
+
{ id: 'yesterday', title: '@general:terms.date-presets.yesterday', kind: 'computed' },
|
|
2861
|
+
{ id: 'last-week', title: '@general:terms.date-presets.last-week', kind: 'computed' },
|
|
2862
|
+
{ id: 'last-month', title: '@general:terms.date-presets.last-month', kind: 'computed' },
|
|
2863
|
+
{ id: 'tomorrow', title: '@general:terms.date-presets.tomorrow', kind: 'computed' },
|
|
2864
|
+
{ id: 'next-week', title: '@general:terms.date-presets.next-week', kind: 'computed' },
|
|
2865
|
+
{ id: 'next-month', title: '@general:terms.date-presets.next-month', kind: 'computed' },
|
|
2866
|
+
{ id: 'between', title: BETWEEN_OPER.title, kind: 'manual', operationType: 'between' },
|
|
2867
|
+
{ id: 'greater-than', title: '@general:terms.operators.greater-than', kind: 'manual', operationType: 'greaterThan' },
|
|
2868
|
+
{ id: 'less-than', title: '@general:terms.operators.less-than', kind: 'manual', operationType: 'lessThan' },
|
|
2869
|
+
];
|
|
2870
|
+
function resolveDateFilterPresets(customPresets, presetIds) {
|
|
2871
|
+
let presets = customPresets?.length ? customPresets : DEFAULT_DATE_FILTER_PRESETS;
|
|
2872
|
+
if (presetIds?.length) {
|
|
2873
|
+
presets = presets.filter((p) => presetIds.includes(p.id));
|
|
2874
|
+
}
|
|
2875
|
+
return presets;
|
|
2876
|
+
}
|
|
2877
|
+
function applyDateFilterPreset(presetId, calendarService) {
|
|
2878
|
+
const currentDate = calendarService.now().date;
|
|
2879
|
+
const calendar = calendarService.calendar;
|
|
2880
|
+
switch (presetId) {
|
|
2881
|
+
case 'today':
|
|
2882
|
+
return {
|
|
2883
|
+
operation: { type: 'between' },
|
|
2884
|
+
value: {
|
|
2885
|
+
from: calendar.startOf(currentDate, 'day').date.toISOString(),
|
|
2886
|
+
to: calendar.endOf(currentDate, 'day').date.toISOString(),
|
|
2887
|
+
},
|
|
2888
|
+
};
|
|
2889
|
+
case 'this-week':
|
|
2890
|
+
return {
|
|
2891
|
+
operation: { type: 'between' },
|
|
2892
|
+
value: {
|
|
2893
|
+
from: calendar.startOf(currentDate, 'week').date.toISOString(),
|
|
2894
|
+
to: calendar.endOf(currentDate, 'week').date.toISOString(),
|
|
2895
|
+
},
|
|
2896
|
+
};
|
|
2897
|
+
case 'this-month':
|
|
2898
|
+
return {
|
|
2899
|
+
operation: { type: 'between' },
|
|
2900
|
+
value: {
|
|
2901
|
+
from: calendar.startOf(currentDate, 'month').date.toISOString(),
|
|
2902
|
+
to: calendar.endOf(currentDate, 'month').date.toISOString(),
|
|
2903
|
+
},
|
|
2904
|
+
};
|
|
2905
|
+
case 'yesterday': {
|
|
2906
|
+
const yesterday = calendar.add(currentDate, 'day', -1);
|
|
2907
|
+
return {
|
|
2908
|
+
operation: { type: 'between' },
|
|
2909
|
+
value: {
|
|
2910
|
+
from: yesterday.startOf('day').date.toISOString(),
|
|
2911
|
+
to: yesterday.endOf('day').date.toISOString(),
|
|
2912
|
+
},
|
|
2913
|
+
};
|
|
2914
|
+
}
|
|
2915
|
+
case 'last-week': {
|
|
2916
|
+
const lastWeek = calendar.add(currentDate, 'week', -1);
|
|
2917
|
+
return {
|
|
2918
|
+
operation: { type: 'between' },
|
|
2919
|
+
value: {
|
|
2920
|
+
from: lastWeek.startOf('week').date.toISOString(),
|
|
2921
|
+
to: lastWeek.endOf('week').date.toISOString(),
|
|
2922
|
+
},
|
|
2923
|
+
};
|
|
2924
|
+
}
|
|
2925
|
+
case 'last-month': {
|
|
2926
|
+
const lastMonth = calendar.add(currentDate, 'month', -1);
|
|
2927
|
+
return {
|
|
2928
|
+
operation: { type: 'between' },
|
|
2929
|
+
value: {
|
|
2930
|
+
from: lastMonth.startOf('month').date.toISOString(),
|
|
2931
|
+
to: lastMonth.endOf('month').date.toISOString(),
|
|
2932
|
+
},
|
|
2933
|
+
};
|
|
2934
|
+
}
|
|
2935
|
+
case 'tomorrow': {
|
|
2936
|
+
const tomorrow = calendar.add(currentDate, 'day', 1);
|
|
2937
|
+
return {
|
|
2938
|
+
operation: { type: 'between' },
|
|
2939
|
+
value: {
|
|
2940
|
+
from: tomorrow.startOf('day').date.toISOString(),
|
|
2941
|
+
to: tomorrow.endOf('day').date.toISOString(),
|
|
2942
|
+
},
|
|
2943
|
+
};
|
|
2944
|
+
}
|
|
2945
|
+
case 'next-week': {
|
|
2946
|
+
const nextWeek = calendar.add(currentDate, 'week', 1);
|
|
2947
|
+
return {
|
|
2948
|
+
operation: { type: 'between' },
|
|
2949
|
+
value: {
|
|
2950
|
+
from: nextWeek.startOf('week').date.toISOString(),
|
|
2951
|
+
to: nextWeek.endOf('week').date.toISOString(),
|
|
2952
|
+
},
|
|
2953
|
+
};
|
|
2954
|
+
}
|
|
2955
|
+
case 'next-month': {
|
|
2956
|
+
const nextMonth = calendar.add(currentDate, 'month', 1);
|
|
2957
|
+
return {
|
|
2958
|
+
operation: { type: 'between' },
|
|
2959
|
+
value: {
|
|
2960
|
+
from: nextMonth.startOf('month').date.toISOString(),
|
|
2961
|
+
to: nextMonth.endOf('month').date.toISOString(),
|
|
2962
|
+
},
|
|
2963
|
+
};
|
|
2964
|
+
}
|
|
2965
|
+
case 'between':
|
|
2966
|
+
return { operation: { type: 'between' } };
|
|
2967
|
+
case 'greater-than':
|
|
2968
|
+
return { operation: { type: 'greaterThan' } };
|
|
2969
|
+
case 'less-than':
|
|
2970
|
+
return { operation: { type: 'lessThan' } };
|
|
2971
|
+
default:
|
|
2972
|
+
return {
|
|
2973
|
+
operation: { type: 'between' },
|
|
2974
|
+
value: { from: '', to: '' },
|
|
2975
|
+
};
|
|
2976
|
+
}
|
|
2977
|
+
}
|
|
2978
|
+
function findManualPresetIdByOperation(operationType, presets) {
|
|
2979
|
+
if (!operationType) {
|
|
2980
|
+
return undefined;
|
|
2981
|
+
}
|
|
2982
|
+
return presets.find((p) => p.kind === 'manual' && p.operationType === operationType)?.id;
|
|
2983
|
+
}
|
|
2984
|
+
|
|
2985
|
+
const AXP_HOME_PAGES = new InjectionToken('AXP_HOME_PAGES');
|
|
2986
|
+
const AXP_HOME_PAGE_DEFAULT_KEY = new InjectionToken('AXP_HOME_PAGE_DEFAULT_KEY', {
|
|
2987
|
+
factory: () => 'home'
|
|
2988
|
+
});
|
|
2989
|
+
|
|
2990
|
+
const DEFAULT_APPLY_DEFAULT_MULTILANGUAGE = true;
|
|
2991
|
+
/**
|
|
2992
|
+
* Synchronous access to default multi-language behavior for named entity fields and widget
|
|
2993
|
+
* property bindings. Mirrors {@link AXPLayoutOrderingConfigService}: avoids async in entity
|
|
2994
|
+
* modifiers and startup deadlocks.
|
|
2995
|
+
*/
|
|
2996
|
+
class AXPDefaultMultiLanguageConfigService {
|
|
2997
|
+
constructor() {
|
|
2998
|
+
this.settingsService = inject(AXPSettingsService);
|
|
2999
|
+
this._applyDefault = signal(DEFAULT_APPLY_DEFAULT_MULTILANGUAGE, /* @ts-ignore */
|
|
3000
|
+
...(ngDevMode ? [{ debugName: "_applyDefault" }] : /* istanbul ignore next */ []));
|
|
3001
|
+
this.syncScheduled = false;
|
|
3002
|
+
this.settingsService.onLoaded.subscribe(() => this.syncFromSettings());
|
|
3003
|
+
this.settingsService.onChanged.subscribe(() => this.syncFromSettings());
|
|
3004
|
+
}
|
|
3005
|
+
getApplyDefaultMultiLanguage() {
|
|
3006
|
+
if (!this.syncScheduled) {
|
|
3007
|
+
this.syncScheduled = true;
|
|
3008
|
+
this.settingsService
|
|
3009
|
+
.get(AXPRegionalSetting.MultiLanguageSupport)
|
|
3010
|
+
.then((value) => this._applyDefault.set(value ?? DEFAULT_APPLY_DEFAULT_MULTILANGUAGE))
|
|
3011
|
+
.catch(() => this._applyDefault.set(DEFAULT_APPLY_DEFAULT_MULTILANGUAGE));
|
|
3012
|
+
}
|
|
3013
|
+
return this._applyDefault();
|
|
3014
|
+
}
|
|
3015
|
+
async syncFromSettings() {
|
|
3016
|
+
try {
|
|
3017
|
+
const value = await this.settingsService.get(AXPRegionalSetting.MultiLanguageSupport);
|
|
3018
|
+
this._applyDefault.set(value ?? DEFAULT_APPLY_DEFAULT_MULTILANGUAGE);
|
|
3019
|
+
}
|
|
2814
3020
|
catch {
|
|
2815
3021
|
this._applyDefault.set(DEFAULT_APPLY_DEFAULT_MULTILANGUAGE);
|
|
2816
3022
|
}
|
|
@@ -2977,6 +3183,52 @@ function withDefaultMultiLanguageOnWidgetProperty(prop, applyDefault) {
|
|
|
2977
3183
|
}
|
|
2978
3184
|
//#endregion
|
|
2979
3185
|
|
|
3186
|
+
//#region ----- User status feature -----
|
|
3187
|
+
/**
|
|
3188
|
+
* Platform switch for showing user availability.
|
|
3189
|
+
* Stays on until {@link AXPCommonSettings.UserStatusEnabled} is false.
|
|
3190
|
+
*/
|
|
3191
|
+
class AXPUserStatusFeatureService {
|
|
3192
|
+
//#endregion
|
|
3193
|
+
//#region ----- Lifecycle -----
|
|
3194
|
+
constructor() {
|
|
3195
|
+
//#region ----- Services & Dependencies -----
|
|
3196
|
+
this.settingsService = inject(AXPSettingsService);
|
|
3197
|
+
//#endregion
|
|
3198
|
+
//#region ----- State Signals -----
|
|
3199
|
+
this.enabledSignal = signal(AXP_USER_STATUS_ENABLED_DEFAULT, /* @ts-ignore */
|
|
3200
|
+
...(ngDevMode ? [{ debugName: "enabledSignal" }] : /* istanbul ignore next */ []));
|
|
3201
|
+
/** Live platform flag. True until settings say otherwise. */
|
|
3202
|
+
this.enabled = this.enabledSignal.asReadonly();
|
|
3203
|
+
this.settingsService.onLoaded.subscribe(() => {
|
|
3204
|
+
void this.refresh();
|
|
3205
|
+
});
|
|
3206
|
+
this.settingsService.onChanged.subscribe((event) => {
|
|
3207
|
+
if (!event.keys.includes(AXPCommonSettings.UserStatusEnabled)) {
|
|
3208
|
+
return;
|
|
3209
|
+
}
|
|
3210
|
+
void this.refresh();
|
|
3211
|
+
});
|
|
3212
|
+
}
|
|
3213
|
+
//#endregion
|
|
3214
|
+
//#region ----- Public API -----
|
|
3215
|
+
async isEnabled() {
|
|
3216
|
+
return this.refresh();
|
|
3217
|
+
}
|
|
3218
|
+
async refresh() {
|
|
3219
|
+
const value = await this.settingsService.get(AXPCommonSettings.UserStatusEnabled);
|
|
3220
|
+
const enabled = value !== false;
|
|
3221
|
+
this.enabledSignal.set(enabled);
|
|
3222
|
+
return enabled;
|
|
3223
|
+
}
|
|
3224
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPUserStatusFeatureService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
3225
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPUserStatusFeatureService, providedIn: 'root' }); }
|
|
3226
|
+
}
|
|
3227
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPUserStatusFeatureService, decorators: [{
|
|
3228
|
+
type: Injectable,
|
|
3229
|
+
args: [{ providedIn: 'root' }]
|
|
3230
|
+
}], ctorParameters: () => [] });
|
|
3231
|
+
|
|
2980
3232
|
//#region ----- Platform settings load state -----
|
|
2981
3233
|
/**
|
|
2982
3234
|
* Tracks fatal failure / offline-continue of the public platform (P) settings bootstrap.
|
|
@@ -3382,65 +3634,273 @@ class AXPUnsavedChangesPopstateService {
|
|
|
3382
3634
|
}
|
|
3383
3635
|
});
|
|
3384
3636
|
}
|
|
3385
|
-
//#endregion
|
|
3386
|
-
//#region ---- Registry ----
|
|
3387
|
-
registerSource(source) {
|
|
3388
|
-
this.sources = this.sources.filter((entry) => entry.id !== source.id);
|
|
3389
|
-
this.sources.push(source);
|
|
3637
|
+
//#endregion
|
|
3638
|
+
//#region ---- Registry ----
|
|
3639
|
+
registerSource(source) {
|
|
3640
|
+
this.sources = this.sources.filter((entry) => entry.id !== source.id);
|
|
3641
|
+
this.sources.push(source);
|
|
3642
|
+
}
|
|
3643
|
+
unregisterSource(id) {
|
|
3644
|
+
this.sources = this.sources.filter((entry) => entry.id !== id);
|
|
3645
|
+
}
|
|
3646
|
+
//#endregion
|
|
3647
|
+
//#region ---- Popstate Handler ----
|
|
3648
|
+
/** True while a popstate back/forward is being intercepted or confirmed. */
|
|
3649
|
+
isHandlingPopstate() {
|
|
3650
|
+
return this.handling;
|
|
3651
|
+
}
|
|
3652
|
+
async handlePopstateNavigation(start) {
|
|
3653
|
+
if (this.handling) {
|
|
3654
|
+
return;
|
|
3655
|
+
}
|
|
3656
|
+
if (this.confirm.hasLeaveConfirmed()) {
|
|
3657
|
+
return;
|
|
3658
|
+
}
|
|
3659
|
+
const dirty = this.isDirty();
|
|
3660
|
+
if (!dirty) {
|
|
3661
|
+
return;
|
|
3662
|
+
}
|
|
3663
|
+
const stayUrl = this.committedUrl;
|
|
3664
|
+
const targetUrl = start.url;
|
|
3665
|
+
if (!stayUrl || stayUrl === targetUrl) {
|
|
3666
|
+
return;
|
|
3667
|
+
}
|
|
3668
|
+
this.handling = true;
|
|
3669
|
+
try {
|
|
3670
|
+
this.location.go(stayUrl);
|
|
3671
|
+
const allowed = await this.confirm.confirmIfDirty(true);
|
|
3672
|
+
if (!allowed) {
|
|
3673
|
+
await this.router.navigateByUrl(stayUrl, { replaceUrl: true });
|
|
3674
|
+
return;
|
|
3675
|
+
}
|
|
3676
|
+
window.history.back();
|
|
3677
|
+
}
|
|
3678
|
+
finally {
|
|
3679
|
+
this.handling = false;
|
|
3680
|
+
}
|
|
3681
|
+
}
|
|
3682
|
+
//#endregion
|
|
3683
|
+
//#region ---- Utility Methods ----
|
|
3684
|
+
isDirty() {
|
|
3685
|
+
return this.sources.some((source) => source.isDirty());
|
|
3686
|
+
}
|
|
3687
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPUnsavedChangesPopstateService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
3688
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPUnsavedChangesPopstateService, providedIn: 'root' }); }
|
|
3689
|
+
}
|
|
3690
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPUnsavedChangesPopstateService, decorators: [{
|
|
3691
|
+
type: Injectable,
|
|
3692
|
+
args: [{
|
|
3693
|
+
providedIn: 'root',
|
|
3694
|
+
}]
|
|
3695
|
+
}], ctorParameters: () => [] });
|
|
3696
|
+
|
|
3697
|
+
//#endregion
|
|
3698
|
+
//#region ---- App Path Navigation ----
|
|
3699
|
+
/**
|
|
3700
|
+
* Splits an absolute app path (`/app/m/module/e/Entity/id/view`) into router command segments.
|
|
3701
|
+
* Prefer this over `navigate([fullPath])` or ambiguous single-segment navigation so browser history stays correct.
|
|
3702
|
+
*/
|
|
3703
|
+
function axpParseAppPath(path) {
|
|
3704
|
+
const trimmed = path.trim();
|
|
3705
|
+
const withoutLeadingSlash = trimmed.startsWith('/') ? trimmed.slice(1) : trimmed;
|
|
3706
|
+
const [pathAndFragment, queryString] = withoutLeadingSlash.split('?');
|
|
3707
|
+
const [pathPart, fragment] = pathAndFragment.split('#');
|
|
3708
|
+
const segments = pathPart.split('/').filter(Boolean);
|
|
3709
|
+
const queryParams = queryString
|
|
3710
|
+
? queryString.split('&').reduce((params, pair) => {
|
|
3711
|
+
const [key, value = ''] = pair.split('=');
|
|
3712
|
+
if (key) {
|
|
3713
|
+
params[decodeURIComponent(key)] = decodeURIComponent(value);
|
|
3714
|
+
}
|
|
3715
|
+
return params;
|
|
3716
|
+
}, {})
|
|
3717
|
+
: undefined;
|
|
3718
|
+
return {
|
|
3719
|
+
segments,
|
|
3720
|
+
queryParams,
|
|
3721
|
+
fragment: fragment || undefined,
|
|
3722
|
+
};
|
|
3723
|
+
}
|
|
3724
|
+
/** Navigates using explicit path segments (same pattern as entity preload filters). */
|
|
3725
|
+
function axpNavigateAppPath(router, path, extras) {
|
|
3726
|
+
const { segments, queryParams, fragment } = axpParseAppPath(path);
|
|
3727
|
+
return router.navigate(segments, {
|
|
3728
|
+
...extras,
|
|
3729
|
+
queryParams,
|
|
3730
|
+
fragment,
|
|
3731
|
+
});
|
|
3732
|
+
}
|
|
3733
|
+
/** True when a URL path is an entity master list route (`.../e/{Entity}/list`). */
|
|
3734
|
+
function axpIsEntityListPath(url) {
|
|
3735
|
+
const path = url.split('?')[0].split('#')[0];
|
|
3736
|
+
return /\/e\/[^/]+\/list$/.test(path);
|
|
3737
|
+
}
|
|
3738
|
+
/** True when the current browser URL is an entity master list route (`.../e/{Entity}/list`). */
|
|
3739
|
+
function axpIsEntityListRoute(router) {
|
|
3740
|
+
return axpIsEntityListPath(router.url);
|
|
3741
|
+
}
|
|
3742
|
+
/** True when a URL path is an entity details view route (`.../e/{Entity}/{id}/view`). */
|
|
3743
|
+
function axpIsEntityDetailsPath(url) {
|
|
3744
|
+
const path = url.split('?')[0].split('#')[0];
|
|
3745
|
+
return /\/e\/[^/]+\/[^/]+\/view$/.test(path);
|
|
3746
|
+
}
|
|
3747
|
+
/**
|
|
3748
|
+
* Entity key (`module.entity`) for `/{app}/m/{module}/e/{entity}/{id}/view`.
|
|
3749
|
+
* Query and hash are ignored. Returns null for every other path.
|
|
3750
|
+
*/
|
|
3751
|
+
function axpEntityKeyFromDetailsPath(url) {
|
|
3752
|
+
const path = url.split('?')[0]?.split('#')[0] ?? '';
|
|
3753
|
+
const match = /\/m\/([^/]+)\/e\/([^/]+)\/[^/]+\/view$/.exec(path);
|
|
3754
|
+
const moduleName = match?.[1];
|
|
3755
|
+
const entityName = match?.[2];
|
|
3756
|
+
if (!moduleName || !entityName) {
|
|
3757
|
+
return null;
|
|
3758
|
+
}
|
|
3759
|
+
return `${moduleName}.${entityName}`;
|
|
3760
|
+
}
|
|
3761
|
+
/** True when the current browser URL is an entity details view route (`.../e/{Entity}/{id}/view`). */
|
|
3762
|
+
function axpIsEntityDetailsViewRoute(router) {
|
|
3763
|
+
return axpIsEntityDetailsPath(router.url);
|
|
3764
|
+
}
|
|
3765
|
+
/**
|
|
3766
|
+
* Merges query params on the current entity list route without pushing history.
|
|
3767
|
+
* Uses absolute path segments so a stale `relativeTo` cannot rewrite the active URL after navigating away.
|
|
3768
|
+
*/
|
|
3769
|
+
function axpReplaceEntityListRouteQueryParams(router, queryParams) {
|
|
3770
|
+
const url = router.url;
|
|
3771
|
+
if (!axpIsEntityListPath(url)) {
|
|
3772
|
+
return;
|
|
3773
|
+
}
|
|
3774
|
+
const parsed = router.parseUrl(url);
|
|
3775
|
+
const pathOnly = url.split('?')[0].split('#')[0];
|
|
3776
|
+
const { segments } = axpParseAppPath(pathOnly);
|
|
3777
|
+
const mergedQueryParams = { ...parsed.queryParams, ...queryParams };
|
|
3778
|
+
void router.navigate(segments, {
|
|
3779
|
+
queryParams: mergedQueryParams,
|
|
3780
|
+
replaceUrl: true,
|
|
3781
|
+
});
|
|
3782
|
+
}
|
|
3783
|
+
//#endregion
|
|
3784
|
+
|
|
3785
|
+
//#region ----- Constants -----
|
|
3786
|
+
/** Query param that distinguishes two menus pointing at the same entity list. */
|
|
3787
|
+
const ENTITY_LIST_INSTANCE_QUERY_PARAM = 'list';
|
|
3788
|
+
/** Used when the list is opened without a menu (command, deep link). */
|
|
3789
|
+
const ENTITY_LIST_DEFAULT_INSTANCE_ID = 'default';
|
|
3790
|
+
//#endregion
|
|
3791
|
+
//#region ----- Instance id -----
|
|
3792
|
+
function axpNormalizeEntityListInstanceId(value) {
|
|
3793
|
+
if (typeof value !== 'string') {
|
|
3794
|
+
return ENTITY_LIST_DEFAULT_INSTANCE_ID;
|
|
3795
|
+
}
|
|
3796
|
+
const trimmed = value.trim();
|
|
3797
|
+
return trimmed.length > 0 ? trimmed : ENTITY_LIST_DEFAULT_INSTANCE_ID;
|
|
3798
|
+
}
|
|
3799
|
+
function axpGetEntityListInstanceIdFromQueryString(queryString) {
|
|
3800
|
+
const params = new URLSearchParams(queryString.startsWith('?') ? queryString.slice(1) : queryString);
|
|
3801
|
+
return axpNormalizeEntityListInstanceId(params.get(ENTITY_LIST_INSTANCE_QUERY_PARAM));
|
|
3802
|
+
}
|
|
3803
|
+
function axpGetEntityListInstanceIdFromParamMap(queryParamMap) {
|
|
3804
|
+
return axpNormalizeEntityListInstanceId(queryParamMap.get(ENTITY_LIST_INSTANCE_QUERY_PARAM));
|
|
3805
|
+
}
|
|
3806
|
+
function axpGetEntityListInstanceIdFromPath(path) {
|
|
3807
|
+
const queryString = path.split('#')[0].split('?')[1] ?? '';
|
|
3808
|
+
return axpGetEntityListInstanceIdFromQueryString(queryString);
|
|
3809
|
+
}
|
|
3810
|
+
//#endregion
|
|
3811
|
+
//#region ----- Menu / URL stamping -----
|
|
3812
|
+
/**
|
|
3813
|
+
* Appends `list={instanceId}` to an entity-list path when missing.
|
|
3814
|
+
* Named menu entries get a stable identity so Angular reuses the list route
|
|
3815
|
+
* while filter persistence can still isolate each menu.
|
|
3816
|
+
*/
|
|
3817
|
+
function axpEnsureEntityListInstanceQueryParam(path, instanceId) {
|
|
3818
|
+
if (!path || !instanceId || !axpIsEntityListPath(path)) {
|
|
3819
|
+
return path;
|
|
3820
|
+
}
|
|
3821
|
+
const normalized = axpNormalizeEntityListInstanceId(instanceId);
|
|
3822
|
+
if (normalized === ENTITY_LIST_DEFAULT_INSTANCE_ID) {
|
|
3823
|
+
return path;
|
|
3390
3824
|
}
|
|
3391
|
-
|
|
3392
|
-
|
|
3825
|
+
const { segments, queryParams, fragment } = axpParseAppPath(path);
|
|
3826
|
+
if (queryParams?.[ENTITY_LIST_INSTANCE_QUERY_PARAM]) {
|
|
3827
|
+
return path;
|
|
3393
3828
|
}
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3829
|
+
const nextQuery = new URLSearchParams({
|
|
3830
|
+
...(queryParams ?? {}),
|
|
3831
|
+
[ENTITY_LIST_INSTANCE_QUERY_PARAM]: normalized,
|
|
3832
|
+
});
|
|
3833
|
+
const base = `/${segments.join('/')}`;
|
|
3834
|
+
const withQuery = `${base}?${nextQuery.toString()}`;
|
|
3835
|
+
return fragment ? `${withQuery}#${fragment}` : withQuery;
|
|
3836
|
+
}
|
|
3837
|
+
/**
|
|
3838
|
+
* Query keys the list page rewrites while the user stays on the same menu
|
|
3839
|
+
* (`view`, `filters`, layout, refresh). They must not open another tab.
|
|
3840
|
+
*/
|
|
3841
|
+
const ENTITY_LIST_PAGE_STATE_QUERY_KEYS = new Set(['view', 'layout', 'filters', '_ts']);
|
|
3842
|
+
function pushEntityListNavigationPair(pairs, key, raw) {
|
|
3843
|
+
if (!key || ENTITY_LIST_PAGE_STATE_QUERY_KEYS.has(key)) {
|
|
3844
|
+
return;
|
|
3399
3845
|
}
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
const dirty = this.isDirty();
|
|
3408
|
-
if (!dirty) {
|
|
3409
|
-
return;
|
|
3410
|
-
}
|
|
3411
|
-
const stayUrl = this.committedUrl;
|
|
3412
|
-
const targetUrl = start.url;
|
|
3413
|
-
if (!stayUrl || stayUrl === targetUrl) {
|
|
3846
|
+
const value = Array.isArray(raw) ? raw[0] : raw;
|
|
3847
|
+
if (typeof value !== 'string') {
|
|
3848
|
+
return;
|
|
3849
|
+
}
|
|
3850
|
+
if (key === ENTITY_LIST_INSTANCE_QUERY_PARAM) {
|
|
3851
|
+
const normalized = axpNormalizeEntityListInstanceId(value);
|
|
3852
|
+
if (normalized === ENTITY_LIST_DEFAULT_INSTANCE_ID) {
|
|
3414
3853
|
return;
|
|
3415
3854
|
}
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
this.location.go(stayUrl);
|
|
3419
|
-
const allowed = await this.confirm.confirmIfDirty(true);
|
|
3420
|
-
if (!allowed) {
|
|
3421
|
-
await this.router.navigateByUrl(stayUrl, { replaceUrl: true });
|
|
3422
|
-
return;
|
|
3423
|
-
}
|
|
3424
|
-
window.history.back();
|
|
3425
|
-
}
|
|
3426
|
-
finally {
|
|
3427
|
-
this.handling = false;
|
|
3428
|
-
}
|
|
3855
|
+
pairs.push([key, normalized]);
|
|
3856
|
+
return;
|
|
3429
3857
|
}
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
|
|
3858
|
+
pairs.push([key, value]);
|
|
3859
|
+
}
|
|
3860
|
+
function readEntityListNavigationPairs(query) {
|
|
3861
|
+
const pairs = [];
|
|
3862
|
+
if (!query) {
|
|
3863
|
+
return pairs;
|
|
3434
3864
|
}
|
|
3435
|
-
|
|
3436
|
-
|
|
3865
|
+
if (typeof query === 'string') {
|
|
3866
|
+
const hashless = query.split('#')[0];
|
|
3867
|
+
const queryMarker = hashless.indexOf('?');
|
|
3868
|
+
// A path with no "?" is not a query. Parsing it makes the path itself a key and opens a second tab.
|
|
3869
|
+
const queryString = queryMarker >= 0 ? hashless.slice(queryMarker + 1) : hashless.startsWith('/') ? '' : hashless;
|
|
3870
|
+
const params = new URLSearchParams(queryString);
|
|
3871
|
+
params.forEach((value, key) => pushEntityListNavigationPair(pairs, key, value));
|
|
3872
|
+
return pairs;
|
|
3873
|
+
}
|
|
3874
|
+
if (typeof URLSearchParams !== 'undefined' && query instanceof URLSearchParams) {
|
|
3875
|
+
query.forEach((value, key) => pushEntityListNavigationPair(pairs, key, value));
|
|
3876
|
+
return pairs;
|
|
3877
|
+
}
|
|
3878
|
+
for (const [key, value] of Object.entries(query)) {
|
|
3879
|
+
pushEntityListNavigationPair(pairs, key, value);
|
|
3880
|
+
}
|
|
3881
|
+
return pairs;
|
|
3437
3882
|
}
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
|
|
3441
|
-
|
|
3442
|
-
|
|
3443
|
-
|
|
3883
|
+
/**
|
|
3884
|
+
* Tab and route-cache identity for an entity list.
|
|
3885
|
+
* Navigation query params (`mode=cm`, `mode=pm`, non-default `list`, …) keep each menu distinct,
|
|
3886
|
+
* the same way simple navigation does. Page-state params stay on the URL and do not split the tab.
|
|
3887
|
+
*/
|
|
3888
|
+
function axpEntityListNavigationTabKey(path, query) {
|
|
3889
|
+
const base = path.split('?')[0].split('#')[0].replace(/\/+$/, '') || '/';
|
|
3890
|
+
if (!axpIsEntityListPath(base)) {
|
|
3891
|
+
return base;
|
|
3892
|
+
}
|
|
3893
|
+
const pairs = readEntityListNavigationPairs(query);
|
|
3894
|
+
if (!pairs.length) {
|
|
3895
|
+
return base;
|
|
3896
|
+
}
|
|
3897
|
+
pairs.sort((left, right) => left[0].localeCompare(right[0]) || left[1].localeCompare(right[1]));
|
|
3898
|
+
const queryString = pairs
|
|
3899
|
+
.map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
|
|
3900
|
+
.join('&');
|
|
3901
|
+
return `${base}?${queryString}`;
|
|
3902
|
+
}
|
|
3903
|
+
//#endregion
|
|
3444
3904
|
|
|
3445
3905
|
//#endregion
|
|
3446
3906
|
/**
|
|
@@ -3458,6 +3918,10 @@ class AXPTabRouteCacheRegistry {
|
|
|
3458
3918
|
this.openKeys = new Set();
|
|
3459
3919
|
this.enabled = false;
|
|
3460
3920
|
this.deactivateGuardSuspended = false;
|
|
3921
|
+
/** While true, cached pages stay stored but must not be attached to a navigation. */
|
|
3922
|
+
this.attachSuspended = false;
|
|
3923
|
+
/** Session that owns cached pages. Null means signed out: never keep another user's components. */
|
|
3924
|
+
this.ownerKey = null;
|
|
3461
3925
|
}
|
|
3462
3926
|
//#endregion
|
|
3463
3927
|
//#region ---- Deactivate Guard Suspension ----
|
|
@@ -3481,6 +3945,35 @@ class AXPTabRouteCacheRegistry {
|
|
|
3481
3945
|
}
|
|
3482
3946
|
//#endregion
|
|
3483
3947
|
//#region ---- Mode ----
|
|
3948
|
+
/** True while a signed-in session may keep detached pages. */
|
|
3949
|
+
canCache() {
|
|
3950
|
+
return this.ownerKey !== null;
|
|
3951
|
+
}
|
|
3952
|
+
/** True while a session transition must not reattach a cached page. */
|
|
3953
|
+
isAttachSuspended() {
|
|
3954
|
+
return this.attachSuspended;
|
|
3955
|
+
}
|
|
3956
|
+
/**
|
|
3957
|
+
* Keeps cached pages for a failed switch, but blocks attaching them until the transition settles.
|
|
3958
|
+
*/
|
|
3959
|
+
suspendAttach() {
|
|
3960
|
+
this.attachSuspended = true;
|
|
3961
|
+
}
|
|
3962
|
+
/** Allows attach again. Does not destroy the stored pages. */
|
|
3963
|
+
resumeAttach() {
|
|
3964
|
+
this.attachSuspended = false;
|
|
3965
|
+
}
|
|
3966
|
+
/**
|
|
3967
|
+
* Binds cached pages to one session. A sign-out or a different user destroys every cached
|
|
3968
|
+
* component so the next session cannot reattach the previous user's data.
|
|
3969
|
+
*/
|
|
3970
|
+
bindOwner(ownerKey) {
|
|
3971
|
+
if (this.ownerKey === ownerKey) {
|
|
3972
|
+
return;
|
|
3973
|
+
}
|
|
3974
|
+
this.ownerKey = ownerKey;
|
|
3975
|
+
this.clear();
|
|
3976
|
+
}
|
|
3484
3977
|
/** Enables caching (tabular mode). Disabling clears and destroys every cached component. */
|
|
3485
3978
|
setEnabled(enabled) {
|
|
3486
3979
|
if (this.enabled === enabled) {
|
|
@@ -3502,7 +3995,11 @@ class AXPTabRouteCacheRegistry {
|
|
|
3502
3995
|
*/
|
|
3503
3996
|
syncOpenKeys(scopedKeys, activeTabUrls = []) {
|
|
3504
3997
|
const scopedSet = new Set(scopedKeys);
|
|
3505
|
-
const activePathSet = new Set(activeTabUrls.
|
|
3998
|
+
const activePathSet = new Set(activeTabUrls.flatMap((url) => {
|
|
3999
|
+
const pathOnly = url.split('?')[0].split('#')[0].replace(/\/+$/, '') || '/';
|
|
4000
|
+
const withNavigation = axpEntityListNavigationTabKey(pathOnly, url);
|
|
4001
|
+
return withNavigation === pathOnly ? [pathOnly] : [pathOnly, withNavigation];
|
|
4002
|
+
}));
|
|
3506
4003
|
for (const cacheKey of [...this.handles.keys()]) {
|
|
3507
4004
|
const normalizedCacheKey = cacheKey.replace(/\/+$/, '') || '/';
|
|
3508
4005
|
const keepsScopedKey = scopedSet.has(normalizedCacheKey);
|
|
@@ -3544,6 +4041,10 @@ class AXPTabRouteCacheRegistry {
|
|
|
3544
4041
|
* destroying the old one would destroy the live component and corrupt the view (NG0922).
|
|
3545
4042
|
*/
|
|
3546
4043
|
store(key, handle) {
|
|
4044
|
+
if (!this.ownerKey) {
|
|
4045
|
+
this.destroyHandle(handle);
|
|
4046
|
+
return;
|
|
4047
|
+
}
|
|
3547
4048
|
this.handles.set(key, handle);
|
|
3548
4049
|
}
|
|
3549
4050
|
/**
|
|
@@ -3553,6 +4054,9 @@ class AXPTabRouteCacheRegistry {
|
|
|
3553
4054
|
* `store(snapshot, null)`.
|
|
3554
4055
|
*/
|
|
3555
4056
|
retrieve(key) {
|
|
4057
|
+
if (this.attachSuspended) {
|
|
4058
|
+
return null;
|
|
4059
|
+
}
|
|
3556
4060
|
return this.handles.get(key) ?? null;
|
|
3557
4061
|
}
|
|
3558
4062
|
/**
|
|
@@ -3832,6 +4336,7 @@ const AXP_EXPERIENCE_SETTINGS_SECTIONS = {
|
|
|
3832
4336
|
Performance: 'performance',
|
|
3833
4337
|
Entity: 'entity',
|
|
3834
4338
|
Offline: 'offline',
|
|
4339
|
+
Presence: 'presence',
|
|
3835
4340
|
FileChooser: 'file-chooser',
|
|
3836
4341
|
};
|
|
3837
4342
|
//#endregion
|
|
@@ -5329,7 +5834,7 @@ class AXPBackgroundOperationSlotComponent {
|
|
|
5329
5834
|
this.popover()?.close();
|
|
5330
5835
|
}
|
|
5331
5836
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPBackgroundOperationSlotComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
5332
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: AXPBackgroundOperationSlotComponent, isStandalone: true, selector: "axp-background-operation-slot", viewQueries: [{ propertyName: "popover", first: true, predicate: AXPopoverComponent, descendants: true, isSignal: true }], ngImport: i0, template: "@if (showSlot()) {\n <!-- ----- Footer tray trigger ----- -->\n <button type=\"button\" class=\"operation-slot__trigger\" #operationTrigger (click)=\"onTriggerClick(panel)\">\n @if (badgeDisplay()) {\n <!-- ----- Count badge ----- -->\n <ax-badge class=\"operation-slot__badge\" [class.--xs]=\"badgeDisplay().length > 2\" color=\"danger\" [text]=\"badgeDisplay()\"></ax-badge>\n }\n @if (slotShowsFailureOnly()) {\n <!-- ----- Failed tasks ----- -->\n <ax-icon class=\"operation-slot__icon\">\n <i class=\"fa-light fa-circle-exclamation\"></i>\n </ax-icon>\n }\n <!--->\n <!-- ----- In-progress spinner (no icon for completed) ----- -->\n @else if (!slotShowsRetainedOnly()) {\n <ax-icon class=\"operation-slot__icon\">\n <i class=\"fa-light fa-spinner fa-spin\"></i>\n </ax-icon>\n }\n <span class=\"operation-slot__text\">\n {{ slotLabelKey() | translate | async }}\n </span>\n </button>\n\n <ax-popover\n [target]=\"operationTrigger\"\n [openOn]=\"'toggle'\"\n [closeOn]=\"'clickOut'\"\n [adaptivityEnabled]=\"true\"\n (onOpened)=\"onPopoverOpened(panel)\"\n >\n <axp-background-operation-panel\n #panel\n (onClosePopover)=\"closePopover()\"\n ></axp-background-operation-panel>\n </ax-popover>\n}\n", styles: ["@layer properties;axp-background-operation-slot{display:inline-flex}.operation-slot__trigger{display:inline-flex;cursor:pointer;align-items:center;gap:calc(var(--spacing, .25rem) * 1.5);border-radius:var(--radius-md, .375rem);border-style:var(--tw-border-style);border-width:0px;background-color:transparent;padding-inline:calc(var(--spacing, .25rem) * 2);padding-block:var(--spacing, .25rem);color:var(--color-text-default);--ax-sys-size: ax-sm;--ax-sys-size-base: calc(var(--ax-sys-size-md) - var(--ax-sys-size-step));--spacing: calc(var(--ax-sys-spacing-base) - .25 * var(--ax-sys-padding-step));--ax-sys-border-radius: var(--ax-sys-border-radius-base);--text-xs: calc(var(--ax-sys-text-xs-base) * 11 / 12);--text-sm: calc(var(--ax-sys-text-sm-base) * 6 / 7);--text-base: calc(var(--ax-sys-body-font-size) * .875);--text-lg: calc(var(--ax-sys-text-lg-base) * 8 / 9);--text-xl: calc(var(--ax-sys-text-xl-base) * .9);--leading-3: calc(var(--ax-sys-leading-3-base) * 7 / 6);--leading-4: var(--ax-sys-leading-4-base);--leading-5: calc(var(--ax-sys-leading-5-base) * .9);--leading-6: calc(var(--ax-sys-leading-6-base) * 11 / 12)}@media(hover:hover){.operation-slot__trigger:hover{background-color:rgba(var(--ax-sys-color-light-surface))}}.operation-slot__icon{font-size:var(--text-sm, .875rem);line-height:var(--tw-leading, var(--text-sm--line-height, calc(1.25 / .875)))}.operation-slot__text{font-size:var(--text-sm, .875rem);line-height:var(--tw-leading, var(--text-sm--line-height, calc(1.25 / .875)));--tw-font-weight: var(--font-weight-medium, 500);font-weight:var(--font-weight-medium, 500)}.operation-slot__badge{flex-shrink:0}@property --tw-border-style{syntax: \"*\"; inherits: false; initial-value: solid;}@property --tw-font-weight{syntax: \"*\"; inherits: false;}@layer properties{@supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-border-style: solid;--tw-font-weight: initial}}}\n/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: AXBadgeModule }, { kind: "component", type: i4$1.AXBadgeComponent, selector: "ax-badge", inputs: ["color", "look", "text"] }, { kind: "ngmodule", type: AXDecoratorModule }, { kind: "component", type: i2.AXDecoratorIconComponent, selector: "ax-icon", inputs: ["icon"] }, { kind: "ngmodule", type: AXTranslationModule }, { kind: "ngmodule", type: AXPopoverModule }, { kind: "component", type: i3$2.AXPopoverComponent, selector: "ax-popover", inputs: ["width", "disablePanelClass", "visualContextScope", "disabled", "offsetX", "offsetY", "target", "placement", "content", "openOn", "closeOn", "hasBackdrop", "openAfter", "closeAfter", "closeOnScroll", "closeOnNavigation", "backdropClass", "panelClass", "adaptivityEnabled"], outputs: ["onOpened", "onClosed"] }, { kind: "component", type: AXPBackgroundOperationPanelComponent, selector: "axp-background-operation-panel", outputs: ["onActiveCountChange", "onClosePopover"] }, { kind: "pipe", type: i4.AsyncPipe, name: "async" }, { kind: "pipe", type: i6.AXTranslatorPipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
|
|
5837
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: AXPBackgroundOperationSlotComponent, isStandalone: true, selector: "axp-background-operation-slot", viewQueries: [{ propertyName: "popover", first: true, predicate: AXPopoverComponent, descendants: true, isSignal: true }], ngImport: i0, template: "@if (showSlot()) {\n <!-- ----- Footer tray trigger ----- -->\n <button type=\"button\" class=\"operation-slot__trigger\" #operationTrigger (click)=\"onTriggerClick(panel)\">\n @if (badgeDisplay()) {\n <!-- ----- Count badge ----- -->\n <ax-badge class=\"operation-slot__badge\" [class.--xs]=\"badgeDisplay().length > 2\" color=\"danger\" [text]=\"badgeDisplay()\"></ax-badge>\n }\n @if (slotShowsFailureOnly()) {\n <!-- ----- Failed tasks ----- -->\n <ax-icon class=\"operation-slot__icon\">\n <i class=\"fa-light fa-circle-exclamation\"></i>\n </ax-icon>\n }\n <!--->\n <!-- ----- In-progress spinner (no icon for completed) ----- -->\n @else if (!slotShowsRetainedOnly()) {\n <ax-icon class=\"operation-slot__icon\">\n <i class=\"fa-light fa-spinner fa-spin\"></i>\n </ax-icon>\n }\n <span class=\"operation-slot__text\">\n {{ slotLabelKey() | translate | async }}\n </span>\n </button>\n\n <ax-popover\n [target]=\"operationTrigger\"\n [openOn]=\"'toggle'\"\n [closeOn]=\"'clickOut'\"\n [adaptivityEnabled]=\"true\"\n (onOpened)=\"onPopoverOpened(panel)\"\n >\n <axp-background-operation-panel\n #panel\n (onClosePopover)=\"closePopover()\"\n ></axp-background-operation-panel>\n </ax-popover>\n}\n", styles: ["@layer properties;axp-background-operation-slot{display:inline-flex}.operation-slot__trigger{display:inline-flex;cursor:pointer;align-items:center;gap:calc(var(--spacing, .25rem) * 1.5);border-radius:var(--radius-md, .375rem);border-style:var(--tw-border-style);border-width:0px;background-color:transparent;padding-inline:calc(var(--spacing, .25rem) * 2);padding-block:var(--spacing, .25rem);color:var(--color-text-default);--ax-sys-size: ax-sm;--ax-sys-size-base: calc(var(--ax-sys-size-md) - var(--ax-sys-size-step));--spacing: calc(var(--ax-sys-spacing-base) - .25 * var(--ax-sys-padding-step));--ax-sys-border-radius: var(--ax-sys-border-radius-base);--text-xs: calc(var(--ax-sys-text-xs-base) * 11 / 12);--text-sm: calc(var(--ax-sys-text-sm-base) * 6 / 7);--text-base: calc(var(--ax-sys-body-font-size) * .875);--text-lg: calc(var(--ax-sys-text-lg-base) * 8 / 9);--text-xl: calc(var(--ax-sys-text-xl-base) * .9);--leading-3: calc(var(--ax-sys-leading-3-base) * 7 / 6);--leading-4: var(--ax-sys-leading-4-base);--leading-5: calc(var(--ax-sys-leading-5-base) * .9);--leading-6: calc(var(--ax-sys-leading-6-base) * 11 / 12)}@media(hover:hover){.operation-slot__trigger:hover{background-color:rgba(var(--ax-sys-color-light-surface))}}.operation-slot__icon{font-size:var(--text-sm, .875rem);line-height:var(--tw-leading, var(--text-sm--line-height, calc(1.25 / .875)))}.operation-slot__text{font-size:var(--text-sm, .875rem);line-height:var(--tw-leading, var(--text-sm--line-height, calc(1.25 / .875)));--tw-font-weight: var(--font-weight-medium, 500);font-weight:var(--font-weight-medium, 500)}.operation-slot__badge{flex-shrink:0}@property --tw-border-style{syntax: \"*\"; inherits: false; initial-value: solid;}@property --tw-font-weight{syntax: \"*\"; inherits: false;}@layer properties{@supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-border-style: solid;--tw-font-weight: initial}}}\n/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: AXBadgeModule }, { kind: "component", type: i4$1.AXBadgeComponent, selector: "ax-badge", inputs: ["color", "look", "text"] }, { kind: "ngmodule", type: AXDecoratorModule }, { kind: "component", type: i2.AXDecoratorIconComponent, selector: "ax-icon", inputs: ["icon"] }, { kind: "ngmodule", type: AXTranslationModule }, { kind: "ngmodule", type: AXPopoverModule }, { kind: "component", type: i3$2.AXPopoverComponent, selector: "ax-popover", inputs: ["width", "disablePanelClass", "visualContextScope", "disabled", "offsetX", "offsetY", "target", "placement", "content", "openOn", "closeOn", "hasBackdrop", "restoreFocusOnClose", "openAfter", "closeAfter", "closeOnScroll", "closeOnNavigation", "backdropClass", "panelClass", "adaptivityEnabled"], outputs: ["onOpened", "onClosed"] }, { kind: "component", type: AXPBackgroundOperationPanelComponent, selector: "axp-background-operation-panel", outputs: ["onActiveCountChange", "onClosePopover"] }, { kind: "pipe", type: i4.AsyncPipe, name: "async" }, { kind: "pipe", type: i6.AXTranslatorPipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
|
|
5333
5838
|
}
|
|
5334
5839
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPBackgroundOperationSlotComponent, decorators: [{
|
|
5335
5840
|
type: Component,
|
|
@@ -5600,148 +6105,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
|
|
|
5600
6105
|
args: [{ providedIn: 'root' }]
|
|
5601
6106
|
}] });
|
|
5602
6107
|
|
|
5603
|
-
//#endregion
|
|
5604
|
-
//#region ---- App Path Navigation ----
|
|
5605
|
-
/**
|
|
5606
|
-
* Splits an absolute app path (`/app/m/module/e/Entity/id/view`) into router command segments.
|
|
5607
|
-
* Prefer this over `navigate([fullPath])` or ambiguous single-segment navigation so browser history stays correct.
|
|
5608
|
-
*/
|
|
5609
|
-
function axpParseAppPath(path) {
|
|
5610
|
-
const trimmed = path.trim();
|
|
5611
|
-
const withoutLeadingSlash = trimmed.startsWith('/') ? trimmed.slice(1) : trimmed;
|
|
5612
|
-
const [pathAndFragment, queryString] = withoutLeadingSlash.split('?');
|
|
5613
|
-
const [pathPart, fragment] = pathAndFragment.split('#');
|
|
5614
|
-
const segments = pathPart.split('/').filter(Boolean);
|
|
5615
|
-
const queryParams = queryString
|
|
5616
|
-
? queryString.split('&').reduce((params, pair) => {
|
|
5617
|
-
const [key, value = ''] = pair.split('=');
|
|
5618
|
-
if (key) {
|
|
5619
|
-
params[decodeURIComponent(key)] = decodeURIComponent(value);
|
|
5620
|
-
}
|
|
5621
|
-
return params;
|
|
5622
|
-
}, {})
|
|
5623
|
-
: undefined;
|
|
5624
|
-
return {
|
|
5625
|
-
segments,
|
|
5626
|
-
queryParams,
|
|
5627
|
-
fragment: fragment || undefined,
|
|
5628
|
-
};
|
|
5629
|
-
}
|
|
5630
|
-
/** Navigates using explicit path segments (same pattern as entity preload filters). */
|
|
5631
|
-
function axpNavigateAppPath(router, path, extras) {
|
|
5632
|
-
const { segments, queryParams, fragment } = axpParseAppPath(path);
|
|
5633
|
-
return router.navigate(segments, {
|
|
5634
|
-
...extras,
|
|
5635
|
-
queryParams,
|
|
5636
|
-
fragment,
|
|
5637
|
-
});
|
|
5638
|
-
}
|
|
5639
|
-
/** True when a URL path is an entity master list route (`.../e/{Entity}/list`). */
|
|
5640
|
-
function axpIsEntityListPath(url) {
|
|
5641
|
-
const path = url.split('?')[0].split('#')[0];
|
|
5642
|
-
return /\/e\/[^/]+\/list$/.test(path);
|
|
5643
|
-
}
|
|
5644
|
-
/** True when the current browser URL is an entity master list route (`.../e/{Entity}/list`). */
|
|
5645
|
-
function axpIsEntityListRoute(router) {
|
|
5646
|
-
return axpIsEntityListPath(router.url);
|
|
5647
|
-
}
|
|
5648
|
-
/** True when a URL path is an entity details view route (`.../e/{Entity}/{id}/view`). */
|
|
5649
|
-
function axpIsEntityDetailsPath(url) {
|
|
5650
|
-
const path = url.split('?')[0].split('#')[0];
|
|
5651
|
-
return /\/e\/[^/]+\/[^/]+\/view$/.test(path);
|
|
5652
|
-
}
|
|
5653
|
-
/**
|
|
5654
|
-
* Entity key (`module.entity`) for `/{app}/m/{module}/e/{entity}/{id}/view`.
|
|
5655
|
-
* Query and hash are ignored. Returns null for every other path.
|
|
5656
|
-
*/
|
|
5657
|
-
function axpEntityKeyFromDetailsPath(url) {
|
|
5658
|
-
const path = url.split('?')[0]?.split('#')[0] ?? '';
|
|
5659
|
-
const match = /\/m\/([^/]+)\/e\/([^/]+)\/[^/]+\/view$/.exec(path);
|
|
5660
|
-
const moduleName = match?.[1];
|
|
5661
|
-
const entityName = match?.[2];
|
|
5662
|
-
if (!moduleName || !entityName) {
|
|
5663
|
-
return null;
|
|
5664
|
-
}
|
|
5665
|
-
return `${moduleName}.${entityName}`;
|
|
5666
|
-
}
|
|
5667
|
-
/** True when the current browser URL is an entity details view route (`.../e/{Entity}/{id}/view`). */
|
|
5668
|
-
function axpIsEntityDetailsViewRoute(router) {
|
|
5669
|
-
return axpIsEntityDetailsPath(router.url);
|
|
5670
|
-
}
|
|
5671
|
-
/**
|
|
5672
|
-
* Merges query params on the current entity list route without pushing history.
|
|
5673
|
-
* Uses absolute path segments so a stale `relativeTo` cannot rewrite the active URL after navigating away.
|
|
5674
|
-
*/
|
|
5675
|
-
function axpReplaceEntityListRouteQueryParams(router, queryParams) {
|
|
5676
|
-
const url = router.url;
|
|
5677
|
-
if (!axpIsEntityListPath(url)) {
|
|
5678
|
-
return;
|
|
5679
|
-
}
|
|
5680
|
-
const parsed = router.parseUrl(url);
|
|
5681
|
-
const pathOnly = url.split('?')[0].split('#')[0];
|
|
5682
|
-
const { segments } = axpParseAppPath(pathOnly);
|
|
5683
|
-
const mergedQueryParams = { ...parsed.queryParams, ...queryParams };
|
|
5684
|
-
void router.navigate(segments, {
|
|
5685
|
-
queryParams: mergedQueryParams,
|
|
5686
|
-
replaceUrl: true,
|
|
5687
|
-
});
|
|
5688
|
-
}
|
|
5689
|
-
//#endregion
|
|
5690
|
-
|
|
5691
|
-
//#region ----- Constants -----
|
|
5692
|
-
/** Query param that distinguishes two menus pointing at the same entity list. */
|
|
5693
|
-
const ENTITY_LIST_INSTANCE_QUERY_PARAM = 'list';
|
|
5694
|
-
/** Used when the list is opened without a menu (command, deep link). */
|
|
5695
|
-
const ENTITY_LIST_DEFAULT_INSTANCE_ID = 'default';
|
|
5696
|
-
//#endregion
|
|
5697
|
-
//#region ----- Instance id -----
|
|
5698
|
-
function axpNormalizeEntityListInstanceId(value) {
|
|
5699
|
-
if (typeof value !== 'string') {
|
|
5700
|
-
return ENTITY_LIST_DEFAULT_INSTANCE_ID;
|
|
5701
|
-
}
|
|
5702
|
-
const trimmed = value.trim();
|
|
5703
|
-
return trimmed.length > 0 ? trimmed : ENTITY_LIST_DEFAULT_INSTANCE_ID;
|
|
5704
|
-
}
|
|
5705
|
-
function axpGetEntityListInstanceIdFromQueryString(queryString) {
|
|
5706
|
-
const params = new URLSearchParams(queryString.startsWith('?') ? queryString.slice(1) : queryString);
|
|
5707
|
-
return axpNormalizeEntityListInstanceId(params.get(ENTITY_LIST_INSTANCE_QUERY_PARAM));
|
|
5708
|
-
}
|
|
5709
|
-
function axpGetEntityListInstanceIdFromParamMap(queryParamMap) {
|
|
5710
|
-
return axpNormalizeEntityListInstanceId(queryParamMap.get(ENTITY_LIST_INSTANCE_QUERY_PARAM));
|
|
5711
|
-
}
|
|
5712
|
-
function axpGetEntityListInstanceIdFromPath(path) {
|
|
5713
|
-
const queryString = path.split('#')[0].split('?')[1] ?? '';
|
|
5714
|
-
return axpGetEntityListInstanceIdFromQueryString(queryString);
|
|
5715
|
-
}
|
|
5716
|
-
//#endregion
|
|
5717
|
-
//#region ----- Menu / URL stamping -----
|
|
5718
|
-
/**
|
|
5719
|
-
* Appends `list={instanceId}` to an entity-list path when missing.
|
|
5720
|
-
* Named menu entries get a stable identity so Angular reuses the list route
|
|
5721
|
-
* while filter persistence can still isolate each menu.
|
|
5722
|
-
*/
|
|
5723
|
-
function axpEnsureEntityListInstanceQueryParam(path, instanceId) {
|
|
5724
|
-
if (!path || !instanceId || !axpIsEntityListPath(path)) {
|
|
5725
|
-
return path;
|
|
5726
|
-
}
|
|
5727
|
-
const normalized = axpNormalizeEntityListInstanceId(instanceId);
|
|
5728
|
-
if (normalized === ENTITY_LIST_DEFAULT_INSTANCE_ID) {
|
|
5729
|
-
return path;
|
|
5730
|
-
}
|
|
5731
|
-
const { segments, queryParams, fragment } = axpParseAppPath(path);
|
|
5732
|
-
if (queryParams?.[ENTITY_LIST_INSTANCE_QUERY_PARAM]) {
|
|
5733
|
-
return path;
|
|
5734
|
-
}
|
|
5735
|
-
const nextQuery = new URLSearchParams({
|
|
5736
|
-
...(queryParams ?? {}),
|
|
5737
|
-
[ENTITY_LIST_INSTANCE_QUERY_PARAM]: normalized,
|
|
5738
|
-
});
|
|
5739
|
-
const base = `/${segments.join('/')}`;
|
|
5740
|
-
const withQuery = `${base}?${nextQuery.toString()}`;
|
|
5741
|
-
return fragment ? `${withQuery}#${fragment}` : withQuery;
|
|
5742
|
-
}
|
|
5743
|
-
//#endregion
|
|
5744
|
-
|
|
5745
6108
|
//#region ----- Menu instance identity -----
|
|
5746
6109
|
/** Prefers the stamped `list` query param, then the menu item name. */
|
|
5747
6110
|
function axpMenuItemListInstanceId(item) {
|
|
@@ -7051,6 +7414,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
|
|
|
7051
7414
|
function axpMenuItemNeedsUpdate(item, updates) {
|
|
7052
7415
|
return Object.keys(updates).some((key) => item[key] !== updates[key]);
|
|
7053
7416
|
}
|
|
7417
|
+
/** True when the menu command itself navigates. A path on the same item must not navigate again. */
|
|
7418
|
+
function axpMenuCommandIsNavigation(command) {
|
|
7419
|
+
if (!command || typeof command !== 'object') {
|
|
7420
|
+
return false;
|
|
7421
|
+
}
|
|
7422
|
+
if ('type' in command && command.type === 'router') {
|
|
7423
|
+
return true;
|
|
7424
|
+
}
|
|
7425
|
+
return 'name' in command && command.name === 'navigate';
|
|
7426
|
+
}
|
|
7054
7427
|
/** Immutably patches the first menu item with the given name. */
|
|
7055
7428
|
function axpUpdateMenuItemByName(items, name, updates) {
|
|
7056
7429
|
let changed = false;
|
|
@@ -7124,18 +7497,22 @@ withMethods((store, router = inject(Router), commandService = inject(AXPCommandS
|
|
|
7124
7497
|
});
|
|
7125
7498
|
},
|
|
7126
7499
|
executeCommand(item) {
|
|
7127
|
-
|
|
7128
|
-
|
|
7500
|
+
const path = item.path
|
|
7501
|
+
? item.name
|
|
7129
7502
|
? axpEnsureEntityListInstanceQueryParam(item.path, item.name)
|
|
7130
|
-
: item.path
|
|
7503
|
+
: item.path
|
|
7504
|
+
: undefined;
|
|
7505
|
+
if (path) {
|
|
7131
7506
|
void commandService.execute('navigate', { path });
|
|
7132
7507
|
patchState(store, { selectedMenuItem: { item, isFullMatch: true } });
|
|
7133
7508
|
}
|
|
7134
|
-
|
|
7135
|
-
|
|
7136
|
-
|
|
7137
|
-
|
|
7138
|
-
|
|
7509
|
+
const command = item.command;
|
|
7510
|
+
if (!command || (path && axpMenuCommandIsNavigation(command))) {
|
|
7511
|
+
return;
|
|
7512
|
+
}
|
|
7513
|
+
const executeCommand = command;
|
|
7514
|
+
if (executeCommand.name && commandService.exists(executeCommand.name)) {
|
|
7515
|
+
void commandService.execute(executeCommand.name, executeCommand.options ?? {});
|
|
7139
7516
|
}
|
|
7140
7517
|
},
|
|
7141
7518
|
isItemOpen(item) {
|
|
@@ -7248,13 +7625,14 @@ withMethods((store, router = inject(Router), commandService = inject(AXPCommandS
|
|
|
7248
7625
|
const IGNORED_QUERY_KEYS = new Set(['_ts']);
|
|
7249
7626
|
/**
|
|
7250
7627
|
* Builds a stable tab key from a router URL.
|
|
7251
|
-
* Path segments define
|
|
7252
|
-
*
|
|
7628
|
+
* Path segments define the page. Entity-list navigation query params (`mode`, `list`, …)
|
|
7629
|
+
* stay in the key so two menus of the same entity stay distinct, matching simple navigation.
|
|
7630
|
+
* Page-state params (`view`, `filters`, `_ts`) stay on the URL and do not open another tab.
|
|
7253
7631
|
*/
|
|
7254
7632
|
function axpBuildTabKey(url) {
|
|
7255
7633
|
const normalized = normalizeRouterUrl(url);
|
|
7256
7634
|
const pathOnly = normalized.split('?')[0].split('#')[0];
|
|
7257
|
-
return pathOnly;
|
|
7635
|
+
return axpEntityListNavigationTabKey(pathOnly, normalized);
|
|
7258
7636
|
}
|
|
7259
7637
|
/** Normalizes a router URL for storage and navigation. */
|
|
7260
7638
|
function normalizeRouterUrl(url) {
|
|
@@ -7299,6 +7677,73 @@ function axpFallbackTabTitle(url) {
|
|
|
7299
7677
|
}
|
|
7300
7678
|
//#endregion
|
|
7301
7679
|
|
|
7680
|
+
//#region ---- Constants ----
|
|
7681
|
+
/** Query param that keeps the failed route as the tab identity. */
|
|
7682
|
+
const AXP_IN_TAB_ERROR_FROM_PARAM = 'from';
|
|
7683
|
+
const IN_TAB_ERROR_PATHS = new Set(['/error/401', '/error/404', '/error/offline', '/401', '/404']);
|
|
7684
|
+
//#endregion
|
|
7685
|
+
//#region ---- URL Helpers ----
|
|
7686
|
+
/**
|
|
7687
|
+
* Original page URL carried on an in-shell error route.
|
|
7688
|
+
* Null when the URL is not an error page or `from` is missing.
|
|
7689
|
+
*/
|
|
7690
|
+
function axpInTabErrorSourceUrl(url) {
|
|
7691
|
+
const normalized = normalizeRouterUrl(url);
|
|
7692
|
+
const pathOnly = normalized.split('?')[0].split('#')[0];
|
|
7693
|
+
if (!IN_TAB_ERROR_PATHS.has(pathOnly)) {
|
|
7694
|
+
return null;
|
|
7695
|
+
}
|
|
7696
|
+
const params = new URLSearchParams(normalized.split('?')[1] ?? '');
|
|
7697
|
+
const from = params.get(AXP_IN_TAB_ERROR_FROM_PARAM);
|
|
7698
|
+
if (!from?.startsWith('/')) {
|
|
7699
|
+
return null;
|
|
7700
|
+
}
|
|
7701
|
+
const fromPath = from.split('?')[0].split('#')[0];
|
|
7702
|
+
if (IN_TAB_ERROR_PATHS.has(fromPath)) {
|
|
7703
|
+
return null;
|
|
7704
|
+
}
|
|
7705
|
+
return normalizeRouterUrl(from);
|
|
7706
|
+
}
|
|
7707
|
+
/** URL a tab should open again. An in-tab error page retries its `from` route. */
|
|
7708
|
+
function axpInTabErrorRetryUrl(url) {
|
|
7709
|
+
return axpInTabErrorSourceUrl(url) ?? normalizeRouterUrl(url);
|
|
7710
|
+
}
|
|
7711
|
+
/** In-shell error URL whose tab identity is `sourceUrl`, not a single app-wide error route. */
|
|
7712
|
+
function axpInTabErrorLocation(kind, sourceUrl) {
|
|
7713
|
+
const path = inTabErrorPath(kind);
|
|
7714
|
+
const source = axpInTabErrorSourceUrl(sourceUrl) ?? sourceUrl;
|
|
7715
|
+
const params = new URLSearchParams();
|
|
7716
|
+
params.set(AXP_IN_TAB_ERROR_FROM_PARAM, source);
|
|
7717
|
+
return `${path}?${params.toString()}`;
|
|
7718
|
+
}
|
|
7719
|
+
/** UrlTree for guards and resolvers. The shell stays mounted; the tab key stays the source route. */
|
|
7720
|
+
function axpInTabErrorUrlTree(router, kind, sourceUrl) {
|
|
7721
|
+
return router.parseUrl(axpInTabErrorLocation(kind, sourceUrl));
|
|
7722
|
+
}
|
|
7723
|
+
//#endregion
|
|
7724
|
+
//#region ---- Unknown Route Guard ----
|
|
7725
|
+
/**
|
|
7726
|
+
* Sends an unmatched URL to the in-shell 404 without dropping the root layout.
|
|
7727
|
+
* `from` keeps that URL as its own tab.
|
|
7728
|
+
*/
|
|
7729
|
+
const axpRedirectUnknownToNotFound = (_route, state) => {
|
|
7730
|
+
const router = inject(Router);
|
|
7731
|
+
return axpInTabErrorUrlTree(router, '404', state.url);
|
|
7732
|
+
};
|
|
7733
|
+
//#endregion
|
|
7734
|
+
//#region ---- Helpers ----
|
|
7735
|
+
function inTabErrorPath(kind) {
|
|
7736
|
+
switch (kind) {
|
|
7737
|
+
case '401':
|
|
7738
|
+
return '/error/401';
|
|
7739
|
+
case '404':
|
|
7740
|
+
return '/error/404';
|
|
7741
|
+
case 'offline':
|
|
7742
|
+
return '/error/offline';
|
|
7743
|
+
}
|
|
7744
|
+
}
|
|
7745
|
+
//#endregion
|
|
7746
|
+
|
|
7302
7747
|
//#region ---- Types ----
|
|
7303
7748
|
const AXP_HOME_TAB_KEY = 'axp:home';
|
|
7304
7749
|
/** Route `data` key for tab-navigation behavior (`data.tab`). */
|
|
@@ -7306,9 +7751,20 @@ const AXP_TAB_ROUTE_DATA_KEY = 'tab';
|
|
|
7306
7751
|
//#endregion
|
|
7307
7752
|
|
|
7308
7753
|
//#region ---- Route Data ----
|
|
7309
|
-
/**
|
|
7754
|
+
/**
|
|
7755
|
+
* True when this route node itself sets `data.tab = true`.
|
|
7756
|
+
* Pass the route's own config data. Activated snapshots merge parent `data`, so `snapshot.data`
|
|
7757
|
+
* is not a declaration — a child would look like its own tab root.
|
|
7758
|
+
*/
|
|
7310
7759
|
function axpHasTabScope(data) {
|
|
7311
|
-
|
|
7760
|
+
if (!data) {
|
|
7761
|
+
return false;
|
|
7762
|
+
}
|
|
7763
|
+
return data[AXP_TAB_ROUTE_DATA_KEY] === true;
|
|
7764
|
+
}
|
|
7765
|
+
/** True when this snapshot's route config declares `data.tab`, ignoring data inherited from parents. */
|
|
7766
|
+
function axpSnapshotDeclaresTabScope(snapshot) {
|
|
7767
|
+
return axpHasTabScope(snapshot.routeConfig?.data);
|
|
7312
7768
|
}
|
|
7313
7769
|
//#endregion
|
|
7314
7770
|
//#region ---- Snapshot Resolution ----
|
|
@@ -7328,19 +7784,27 @@ function axpGetLeafRouteSnapshot(root) {
|
|
|
7328
7784
|
return route;
|
|
7329
7785
|
}
|
|
7330
7786
|
/**
|
|
7331
|
-
*
|
|
7332
|
-
* Uses the nearest ancestor
|
|
7787
|
+
* Path identity from an activated route tree, without query params.
|
|
7788
|
+
* Uses the nearest ancestor whose own route config sets `data.tab = true`; otherwise the leaf path.
|
|
7789
|
+
* Child navigations under that boundary (settings groups, profile sections) stay on the same tab.
|
|
7333
7790
|
*/
|
|
7334
|
-
function
|
|
7791
|
+
function axpTabScopePathFromSnapshot(leaf) {
|
|
7335
7792
|
const chain = leaf.pathFromRoot;
|
|
7336
7793
|
for (let index = chain.length - 1; index >= 0; index--) {
|
|
7337
7794
|
const snapshot = chain[index];
|
|
7338
|
-
if (
|
|
7795
|
+
if (axpSnapshotDeclaresTabScope(snapshot)) {
|
|
7339
7796
|
return axpPathFromSnapshot(snapshot);
|
|
7340
7797
|
}
|
|
7341
7798
|
}
|
|
7342
7799
|
return axpPathFromSnapshot(leaf);
|
|
7343
7800
|
}
|
|
7801
|
+
/**
|
|
7802
|
+
* Resolves the tab dedupe key from an activated route tree.
|
|
7803
|
+
* Entity list routes also include navigation query params (`mode`, `list`, …), same as simple navigation.
|
|
7804
|
+
*/
|
|
7805
|
+
function axpResolveTabKeyFromSnapshot(leaf) {
|
|
7806
|
+
return axpEntityListNavigationTabKey(axpTabScopePathFromSnapshot(leaf), leaf.queryParams);
|
|
7807
|
+
}
|
|
7344
7808
|
//#endregion
|
|
7345
7809
|
//#region ---- Router / URL Resolution ----
|
|
7346
7810
|
/**
|
|
@@ -7349,17 +7813,29 @@ function axpResolveTabKeyFromSnapshot(leaf) {
|
|
|
7349
7813
|
*/
|
|
7350
7814
|
function axpResolveTabKey(router, url) {
|
|
7351
7815
|
const normalized = normalizeRouterUrl(url ?? router.url);
|
|
7816
|
+
const errorSource = axpInTabErrorSourceUrl(normalized);
|
|
7817
|
+
if (errorSource) {
|
|
7818
|
+
return axpResolveTabKey(router, errorSource);
|
|
7819
|
+
}
|
|
7352
7820
|
const pathOnly = normalized.split('?')[0].split('#')[0];
|
|
7821
|
+
// Entity lists key off the URL itself. Route-config matching and the live snapshot can
|
|
7822
|
+
// disagree until navigation finishes, and that split was opening a second tab for one click.
|
|
7823
|
+
// `mode` and `list` stay in the key; `view`, `layout`, `filters`, and `_ts` do not.
|
|
7824
|
+
if (axpIsEntityListPath(pathOnly)) {
|
|
7825
|
+
return axpEntityListNavigationTabKey(pathOnly, normalized);
|
|
7826
|
+
}
|
|
7353
7827
|
const currentPath = normalizeRouterUrl(router.url).split('?')[0].split('#')[0];
|
|
7828
|
+
let basePath;
|
|
7354
7829
|
if (!url || pathOnly === currentPath) {
|
|
7355
7830
|
const leaf = axpGetLeafRouteSnapshot(router.routerState.snapshot.root);
|
|
7356
|
-
|
|
7831
|
+
basePath = axpTabScopePathFromSnapshot(leaf);
|
|
7357
7832
|
}
|
|
7358
|
-
|
|
7359
|
-
|
|
7360
|
-
|
|
7833
|
+
else {
|
|
7834
|
+
basePath =
|
|
7835
|
+
axpResolveTabKeyFromRouteConfig(router.config, pathOnly) ??
|
|
7836
|
+
(pathOnly.startsWith('/') ? pathOnly : `/${pathOnly}`);
|
|
7361
7837
|
}
|
|
7362
|
-
return
|
|
7838
|
+
return axpEntityListNavigationTabKey(basePath, normalized);
|
|
7363
7839
|
}
|
|
7364
7840
|
/**
|
|
7365
7841
|
* Matches a URL path against registered routes and resolves the tab key from `data.tab`.
|
|
@@ -7367,19 +7843,19 @@ function axpResolveTabKey(router, url) {
|
|
|
7367
7843
|
function axpResolveTabKeyFromRouteConfig(routes, pathOnly) {
|
|
7368
7844
|
const normalized = pathOnly.startsWith('/') ? pathOnly.replace(/\/+$/, '') || '/' : `/${pathOnly}`.replace(/\/+$/, '') || '/';
|
|
7369
7845
|
const segments = normalized === '/' ? [] : normalized.split('/').filter(Boolean);
|
|
7370
|
-
const chain = matchRouteConfigChain(routes, segments, 0,
|
|
7846
|
+
const chain = matchRouteConfigChain(routes, segments, 0, []);
|
|
7371
7847
|
if (!chain?.length) {
|
|
7372
7848
|
return null;
|
|
7373
7849
|
}
|
|
7374
7850
|
for (let index = chain.length - 1; index >= 0; index--) {
|
|
7375
7851
|
const level = chain[index];
|
|
7376
|
-
if (axpHasTabScope(level.
|
|
7852
|
+
if (axpHasTabScope(level.ownData)) {
|
|
7377
7853
|
return level.path;
|
|
7378
7854
|
}
|
|
7379
7855
|
}
|
|
7380
7856
|
return chain[chain.length - 1]?.path ?? null;
|
|
7381
7857
|
}
|
|
7382
|
-
function matchRouteConfigChain(routes, segments, startIndex,
|
|
7858
|
+
function matchRouteConfigChain(routes, segments, startIndex, chain) {
|
|
7383
7859
|
let bestMatch = null;
|
|
7384
7860
|
for (const route of routes) {
|
|
7385
7861
|
if (route.redirectTo) {
|
|
@@ -7391,12 +7867,12 @@ function matchRouteConfigChain(routes, segments, startIndex, inheritedData, chai
|
|
|
7391
7867
|
}
|
|
7392
7868
|
const pathSegments = [...segments.slice(0, startIndex), ...segments.slice(startIndex, startIndex + segmentMatch.consumed)];
|
|
7393
7869
|
const path = pathSegments.length ? `/${pathSegments.join('/')}` : '/';
|
|
7394
|
-
const
|
|
7395
|
-
const level = {
|
|
7870
|
+
const ownData = { ...(route.data ?? {}) };
|
|
7871
|
+
const level = { ownData, path };
|
|
7396
7872
|
const nextChain = [...chain, level];
|
|
7397
7873
|
const nextIndex = startIndex + segmentMatch.consumed;
|
|
7398
7874
|
if (route.children?.length) {
|
|
7399
|
-
const childMatch = matchRouteConfigChain(route.children, segments, nextIndex,
|
|
7875
|
+
const childMatch = matchRouteConfigChain(route.children, segments, nextIndex, nextChain);
|
|
7400
7876
|
if (childMatch && (!bestMatch || childMatch.length > bestMatch.length)) {
|
|
7401
7877
|
bestMatch = childMatch;
|
|
7402
7878
|
}
|
|
@@ -7552,6 +8028,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
|
|
|
7552
8028
|
//#region ----- Command sub-flow navigation (session) -----
|
|
7553
8029
|
const SUPPRESS_TAB_RESTORE_KEY = 'axp:command-subflow-suppress-tab-restore';
|
|
7554
8030
|
const RETURN_URL_KEY = 'axp:command-subflow-return-url';
|
|
8031
|
+
const subFlowEndedListeners = new Set();
|
|
8032
|
+
/** Fires after a command sub-flow releases tab restore. Returns an unsubscribe function. */
|
|
8033
|
+
function axpOnCommandSubFlowNavigationEnded(listener) {
|
|
8034
|
+
subFlowEndedListeners.add(listener);
|
|
8035
|
+
return () => {
|
|
8036
|
+
subFlowEndedListeners.delete(listener);
|
|
8037
|
+
};
|
|
8038
|
+
}
|
|
7555
8039
|
/** Marks an in-flight command sub-flow (file picker, crop, dialog) so tab restore must not hijack the route. */
|
|
7556
8040
|
function axpBeginCommandSubFlowNavigation(returnUrl) {
|
|
7557
8041
|
const trimmed = returnUrl.trim();
|
|
@@ -7591,11 +8075,14 @@ function axpEndCommandSubFlowNavigation() {
|
|
|
7591
8075
|
catch {
|
|
7592
8076
|
// sessionStorage may be unavailable
|
|
7593
8077
|
}
|
|
8078
|
+
for (const listener of subFlowEndedListeners) {
|
|
8079
|
+
listener();
|
|
8080
|
+
}
|
|
7594
8081
|
}
|
|
7595
8082
|
//#endregion
|
|
7596
8083
|
|
|
7597
8084
|
//#region ---- Tab Route Policy ----
|
|
7598
|
-
const EXCLUDED_PATH_PREFIXES = ['/auth'
|
|
8085
|
+
const EXCLUDED_PATH_PREFIXES = ['/auth'];
|
|
7599
8086
|
const EXCLUDED_PATH_PATTERNS = [
|
|
7600
8087
|
/^\/assessment\/fill\//i,
|
|
7601
8088
|
/^\/forms\//i,
|
|
@@ -7647,7 +8134,28 @@ class AXPTabNavigationService {
|
|
|
7647
8134
|
...(ngDevMode ? [{ debugName: "tabs" }] : /* istanbul ignore next */ []));
|
|
7648
8135
|
this.activeTabKey = signal(null, /* @ts-ignore */
|
|
7649
8136
|
...(ngDevMode ? [{ debugName: "activeTabKey" }] : /* istanbul ignore next */ []));
|
|
7650
|
-
|
|
8137
|
+
/** Nested navigations (menu + route `replaceUrl`) must not clear each other's sync guard. */
|
|
8138
|
+
this.navigationSyncSuppressDepth = 0;
|
|
8139
|
+
this.navigationSettingsChain = Promise.resolve();
|
|
8140
|
+
/** `tenantId:applicationId:userId` while the tab strip belongs to that session. Null after sign-out. */
|
|
8141
|
+
this.tabsOwnerStamp = null;
|
|
8142
|
+
/** False until the first session emission. A later stamp change must not restore saved tabs. */
|
|
8143
|
+
this.sessionTabsBound = false;
|
|
8144
|
+
/** Set when the user actually signs out, so the next sign-in does not reopen that list. */
|
|
8145
|
+
this.closedTabsForSessionChange = false;
|
|
8146
|
+
/** Storage key of the session that owned the strip. Survives sign-out, when the user id is already gone. */
|
|
8147
|
+
this.ownedStorageKey = null;
|
|
8148
|
+
/**
|
|
8149
|
+
* Stamp captured when readiness dropped but the user was still signed in.
|
|
8150
|
+
* A failed tenant/application switch returns to this stamp and must keep the saved list.
|
|
8151
|
+
*/
|
|
8152
|
+
this.pendingReadinessGap = null;
|
|
8153
|
+
/** False until the saved list has been read, so an earlier route sync cannot overwrite it. */
|
|
8154
|
+
this.sessionTabsHydrated = false;
|
|
8155
|
+
/** Restore was skipped because a command sub-flow was open. Resumed when that sub-flow ends. */
|
|
8156
|
+
this.restoreDeferred = false;
|
|
8157
|
+
/** Incremented on every session emission. A queued task stops when a newer emission exists. */
|
|
8158
|
+
this.sessionSyncGeneration = 0;
|
|
7651
8159
|
//#endregion
|
|
7652
8160
|
//#region ---- Computed ----
|
|
7653
8161
|
/** Effective mode: mobile always uses simple navigation. */
|
|
@@ -7667,6 +8175,10 @@ class AXPTabNavigationService {
|
|
|
7667
8175
|
this.bindSettingsChanges();
|
|
7668
8176
|
this.bindSessionChanges();
|
|
7669
8177
|
this.bindRouteCacheSync();
|
|
8178
|
+
const stopSubFlowWatch = axpOnCommandSubFlowNavigationEnded(() => {
|
|
8179
|
+
void this.enqueueNavigationSettingsSync(() => this.resumeDeferredTabRestore());
|
|
8180
|
+
});
|
|
8181
|
+
this.destroyRef.onDestroy(stopSubFlowWatch);
|
|
7670
8182
|
}
|
|
7671
8183
|
//#endregion
|
|
7672
8184
|
//#region ---- Public API ----
|
|
@@ -7706,10 +8218,9 @@ class AXPTabNavigationService {
|
|
|
7706
8218
|
return;
|
|
7707
8219
|
}
|
|
7708
8220
|
// Navigate first so scoped tab keys (`data.tab`) resolve from the live route snapshot.
|
|
7709
|
-
this.
|
|
7710
|
-
try {
|
|
8221
|
+
await this.runWithSuppressedNavigationSync(async () => {
|
|
7711
8222
|
await axpNavigateAppPath(this.router, normalizedUrl);
|
|
7712
|
-
const key = axpResolveTabKey(this.router);
|
|
8223
|
+
const key = axpResolveTabKey(this.router, normalizedUrl);
|
|
7713
8224
|
const postNavExisting = this.findTabForUrl(normalizedUrl);
|
|
7714
8225
|
if (postNavExisting) {
|
|
7715
8226
|
if (postNavExisting.key !== key) {
|
|
@@ -7724,10 +8235,7 @@ class AXPTabNavigationService {
|
|
|
7724
8235
|
this.insertTab(this.createTabDescriptor(normalizedUrl, title, key));
|
|
7725
8236
|
this.activeTabKey.set(key);
|
|
7726
8237
|
this.persistSession();
|
|
7727
|
-
}
|
|
7728
|
-
finally {
|
|
7729
|
-
this.suppressNavigationSync = false;
|
|
7730
|
-
}
|
|
8238
|
+
});
|
|
7731
8239
|
}
|
|
7732
8240
|
/**
|
|
7733
8241
|
* Activates a tab and navigates to its URL. The previously active page is detached and cached by
|
|
@@ -7742,18 +8250,15 @@ class AXPTabNavigationService {
|
|
|
7742
8250
|
if (!tab) {
|
|
7743
8251
|
return;
|
|
7744
8252
|
}
|
|
7745
|
-
const targetUrl = urlOverride ?? tab.url;
|
|
8253
|
+
const targetUrl = axpInTabErrorRetryUrl(urlOverride ?? tab.url);
|
|
8254
|
+
if (tab.url !== targetUrl) {
|
|
8255
|
+
this.updateTabUrl(key, targetUrl);
|
|
8256
|
+
}
|
|
7746
8257
|
this.touchTab(key);
|
|
7747
8258
|
this.activeTabKey.set(key);
|
|
7748
8259
|
this.persistSession();
|
|
7749
8260
|
const navigate = async () => {
|
|
7750
|
-
this.
|
|
7751
|
-
try {
|
|
7752
|
-
await this.router.navigateByUrl(targetUrl);
|
|
7753
|
-
}
|
|
7754
|
-
finally {
|
|
7755
|
-
this.suppressNavigationSync = false;
|
|
7756
|
-
}
|
|
8261
|
+
await this.runWithSuppressedNavigationSync(() => this.router.navigateByUrl(targetUrl));
|
|
7757
8262
|
};
|
|
7758
8263
|
if (suspendDeactivateGuard) {
|
|
7759
8264
|
await this.routeCacheRegistry.runWithSuspendedDeactivateGuards(navigate);
|
|
@@ -7765,6 +8270,24 @@ class AXPTabNavigationService {
|
|
|
7765
8270
|
async closeTab(key) {
|
|
7766
8271
|
await this.closeTabs([key]);
|
|
7767
8272
|
}
|
|
8273
|
+
/**
|
|
8274
|
+
* Drops the active error tab without opening a neighbor.
|
|
8275
|
+
* Home stays. The caller navigates to the real home page afterwards.
|
|
8276
|
+
*/
|
|
8277
|
+
closeActiveErrorTab() {
|
|
8278
|
+
if (this.effectiveMode() !== 'tabular') {
|
|
8279
|
+
return;
|
|
8280
|
+
}
|
|
8281
|
+
const activeKey = this.activeTabKey();
|
|
8282
|
+
const tab = this.tabs().find((item) => item.key === activeKey);
|
|
8283
|
+
if (!tab || tab.key === AXP_HOME_TAB_KEY) {
|
|
8284
|
+
return;
|
|
8285
|
+
}
|
|
8286
|
+
this.tabs.update((items) => items.filter((item) => item.key !== tab.key));
|
|
8287
|
+
this.tabPreviewService.clearPreviews(new Set([tab.key]));
|
|
8288
|
+
this.activeTabKey.set(AXP_HOME_TAB_KEY);
|
|
8289
|
+
this.persistSession();
|
|
8290
|
+
}
|
|
7768
8291
|
/** Closes every closable tab except the given key (pinned tabs are kept). */
|
|
7769
8292
|
async closeOtherTabs(keepKey) {
|
|
7770
8293
|
const keys = this.tabs()
|
|
@@ -7946,15 +8469,9 @@ class AXPTabNavigationService {
|
|
|
7946
8469
|
targetKey = home.key;
|
|
7947
8470
|
targetUrl = home.url;
|
|
7948
8471
|
}
|
|
7949
|
-
this.
|
|
7950
|
-
|
|
7951
|
-
|
|
7952
|
-
if (!navigated) {
|
|
7953
|
-
return false;
|
|
7954
|
-
}
|
|
7955
|
-
}
|
|
7956
|
-
finally {
|
|
7957
|
-
this.suppressNavigationSync = false;
|
|
8472
|
+
const navigated = await this.runWithSuppressedNavigationSync(() => this.router.navigateByUrl(targetUrl));
|
|
8473
|
+
if (!navigated) {
|
|
8474
|
+
return false;
|
|
7958
8475
|
}
|
|
7959
8476
|
this.activeTabKey.set(targetKey);
|
|
7960
8477
|
this.touchTab(targetKey);
|
|
@@ -7984,6 +8501,16 @@ class AXPTabNavigationService {
|
|
|
7984
8501
|
}
|
|
7985
8502
|
//#endregion
|
|
7986
8503
|
//#region ---- Router Sync ----
|
|
8504
|
+
/** Holds route-sync until every nested navigation in this turn has finished. */
|
|
8505
|
+
async runWithSuppressedNavigationSync(action) {
|
|
8506
|
+
this.navigationSyncSuppressDepth += 1;
|
|
8507
|
+
try {
|
|
8508
|
+
return await action();
|
|
8509
|
+
}
|
|
8510
|
+
finally {
|
|
8511
|
+
this.navigationSyncSuppressDepth = Math.max(0, this.navigationSyncSuppressDepth - 1);
|
|
8512
|
+
}
|
|
8513
|
+
}
|
|
7987
8514
|
bindRouterEvents() {
|
|
7988
8515
|
this.router.events
|
|
7989
8516
|
.pipe(filter$1((event) => event instanceof NavigationEnd), takeUntilDestroyed(this.destroyRef))
|
|
@@ -7992,7 +8519,7 @@ class AXPTabNavigationService {
|
|
|
7992
8519
|
});
|
|
7993
8520
|
}
|
|
7994
8521
|
async handleNavigationEnd(url) {
|
|
7995
|
-
if (this.
|
|
8522
|
+
if (this.navigationSyncSuppressDepth > 0) {
|
|
7996
8523
|
return;
|
|
7997
8524
|
}
|
|
7998
8525
|
if (this.effectiveMode() === 'simple') {
|
|
@@ -8003,67 +8530,292 @@ class AXPTabNavigationService {
|
|
|
8003
8530
|
return;
|
|
8004
8531
|
}
|
|
8005
8532
|
await this.ensureHomeTab();
|
|
8006
|
-
|
|
8007
|
-
|
|
8533
|
+
const errorSource = axpInTabErrorSourceUrl(normalizedUrl);
|
|
8534
|
+
const storedUrl = errorSource ?? normalizedUrl;
|
|
8535
|
+
if (this.isHomePageUrl(storedUrl)) {
|
|
8536
|
+
this.activateHomeTabForUrl(storedUrl);
|
|
8008
8537
|
return;
|
|
8009
8538
|
}
|
|
8010
|
-
const key = axpResolveTabKey(this.router,
|
|
8011
|
-
const existing = this.findTabForUrl(
|
|
8539
|
+
const key = axpResolveTabKey(this.router, storedUrl);
|
|
8540
|
+
const existing = this.findTabForUrl(storedUrl);
|
|
8012
8541
|
if (existing) {
|
|
8013
8542
|
if (existing.key !== key) {
|
|
8014
8543
|
this.migrateTabKey(existing.key, key);
|
|
8015
8544
|
}
|
|
8016
|
-
this.updateTabUrl(key,
|
|
8545
|
+
this.updateTabUrl(key, storedUrl);
|
|
8017
8546
|
this.touchTab(key);
|
|
8018
8547
|
this.activeTabKey.set(key);
|
|
8019
8548
|
this.persistSession();
|
|
8020
8549
|
return;
|
|
8021
8550
|
}
|
|
8022
|
-
this.insertTab(this.createTabDescriptor(
|
|
8551
|
+
this.insertTab(this.createTabDescriptor(storedUrl, errorSource ? this.resolveTabTitle(errorSource) : undefined, key));
|
|
8023
8552
|
this.activeTabKey.set(key);
|
|
8024
8553
|
this.persistSession();
|
|
8025
8554
|
}
|
|
8026
8555
|
//#endregion
|
|
8027
8556
|
//#region ---- Settings ----
|
|
8028
8557
|
async loadSettings() {
|
|
8029
|
-
|
|
8030
|
-
this.settingsService.get(AXPCommonSettings.
|
|
8031
|
-
this.
|
|
8032
|
-
|
|
8033
|
-
|
|
8034
|
-
this.maxOpenTabs.set(this.normalizeMaxTabs(maxTabs));
|
|
8035
|
-
await this.restoreSessionIfNeeded();
|
|
8558
|
+
await this.enqueueNavigationSettingsSync(async () => {
|
|
8559
|
+
const maxTabs = await this.settingsService.get(AXPCommonSettings.MaxOpenTabs);
|
|
8560
|
+
this.maxOpenTabs.set(this.normalizeMaxTabs(maxTabs));
|
|
8561
|
+
await this.applyEffectiveNavigationMode();
|
|
8562
|
+
});
|
|
8036
8563
|
}
|
|
8037
8564
|
bindSettingsChanges() {
|
|
8565
|
+
this.settingsService.onLoaded.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(() => {
|
|
8566
|
+
void this.enqueueNavigationSettingsSync(() => this.applyEffectiveNavigationMode());
|
|
8567
|
+
});
|
|
8038
8568
|
this.settingsService.onChanged.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((event) => {
|
|
8039
|
-
|
|
8040
|
-
|
|
8041
|
-
|
|
8042
|
-
|
|
8043
|
-
this.navigationMode.set(newMode);
|
|
8044
|
-
if (this.effectiveMode() === 'simple') {
|
|
8045
|
-
this.clearSession();
|
|
8046
|
-
}
|
|
8047
|
-
else {
|
|
8048
|
-
void this.ensureHomeTab().then(() => this.syncCurrentRouteAsTab());
|
|
8049
|
-
}
|
|
8050
|
-
}
|
|
8051
|
-
}
|
|
8052
|
-
if (event.keys.includes(AXPCommonSettings.MaxOpenTabs)) {
|
|
8053
|
-
this.maxOpenTabs.set(this.normalizeMaxTabs(event.values[AXPCommonSettings.MaxOpenTabs]));
|
|
8054
|
-
this.enforceMaxTabs();
|
|
8055
|
-
this.persistSession();
|
|
8569
|
+
const modeChanged = event.keys.includes(AXPCommonSettings.NavigationMode);
|
|
8570
|
+
const maxChanged = event.keys.includes(AXPCommonSettings.MaxOpenTabs);
|
|
8571
|
+
if (!modeChanged && !maxChanged) {
|
|
8572
|
+
return;
|
|
8056
8573
|
}
|
|
8574
|
+
void this.enqueueNavigationSettingsSync(async () => {
|
|
8575
|
+
if (modeChanged) {
|
|
8576
|
+
await this.applyEffectiveNavigationMode();
|
|
8577
|
+
}
|
|
8578
|
+
if (maxChanged) {
|
|
8579
|
+
const maxTabs = await this.settingsService.get(AXPCommonSettings.MaxOpenTabs);
|
|
8580
|
+
this.maxOpenTabs.set(this.normalizeMaxTabs(maxTabs));
|
|
8581
|
+
this.enforceMaxTabs();
|
|
8582
|
+
this.persistSession();
|
|
8583
|
+
}
|
|
8584
|
+
});
|
|
8057
8585
|
});
|
|
8058
8586
|
}
|
|
8059
8587
|
bindSessionChanges() {
|
|
8060
|
-
|
|
8061
|
-
|
|
8062
|
-
|
|
8588
|
+
combineLatest([
|
|
8589
|
+
this.sessionService.user$.pipe(map$1((user) => user?.id ?? null)),
|
|
8590
|
+
this.sessionService.tenant$.pipe(map$1((tenant) => tenant?.id ?? null)),
|
|
8591
|
+
this.sessionService.application$.pipe(map$1((application) => application?.id ?? null)),
|
|
8592
|
+
this.sessionService.isReadyForAppScopedApi$,
|
|
8593
|
+
])
|
|
8594
|
+
.pipe(distinctUntilChanged$1((previous, next) => previous[0] === next[0] &&
|
|
8595
|
+
previous[1] === next[1] &&
|
|
8596
|
+
previous[2] === next[2] &&
|
|
8597
|
+
previous[3] === next[3]), takeUntilDestroyed(this.destroyRef))
|
|
8598
|
+
.subscribe(() => {
|
|
8599
|
+
const context = this.captureSessionSyncContext();
|
|
8600
|
+
if (this.tabsOwnerStamp != null && (context.sessionEnded || context.stamp !== this.tabsOwnerStamp)) {
|
|
8601
|
+
this.routeCacheRegistry.suspendAttach();
|
|
8063
8602
|
}
|
|
8064
|
-
void this.
|
|
8603
|
+
void this.enqueueNavigationSettingsSync(() => this.syncTabsToSession(context));
|
|
8065
8604
|
});
|
|
8066
8605
|
}
|
|
8606
|
+
/** Reads the session once, so a later user cannot replace this event's storage key. */
|
|
8607
|
+
captureSessionSyncContext() {
|
|
8608
|
+
return {
|
|
8609
|
+
generation: ++this.sessionSyncGeneration,
|
|
8610
|
+
stamp: this.sessionStamp(),
|
|
8611
|
+
sessionEnded: !this.sessionService.user?.id || !this.sessionService.hasSessionCredentials(),
|
|
8612
|
+
storageKey: this.sessionStorageKey(),
|
|
8613
|
+
url: normalizeRouterUrl(this.router.url),
|
|
8614
|
+
};
|
|
8615
|
+
}
|
|
8616
|
+
isCurrentSessionSync(context) {
|
|
8617
|
+
return context.generation === this.sessionSyncGeneration;
|
|
8618
|
+
}
|
|
8619
|
+
/**
|
|
8620
|
+
* Closes every tab when the signed-in user, tenant, or application actually changes, including sign-out.
|
|
8621
|
+
* The saved list is deleted only for that real change, so a failed switch can restore it.
|
|
8622
|
+
* The context is the session at enqueue time. A newer emission stops this task, including after an await.
|
|
8623
|
+
*/
|
|
8624
|
+
async syncTabsToSession(context) {
|
|
8625
|
+
// Only a bound signed-in owner is a real sign-out. Startup publishes the user
|
|
8626
|
+
// before Authenticated; treating that as sign-out deletes the saved tab list.
|
|
8627
|
+
// A queued sign-out still deletes its captured key after a newer sign-in.
|
|
8628
|
+
if (context.sessionEnded) {
|
|
8629
|
+
if (this.tabsOwnerStamp != null) {
|
|
8630
|
+
this.applyCapturedSignOut(context);
|
|
8631
|
+
}
|
|
8632
|
+
return;
|
|
8633
|
+
}
|
|
8634
|
+
if (!this.isCurrentSessionSync(context)) {
|
|
8635
|
+
return;
|
|
8636
|
+
}
|
|
8637
|
+
if (!this.sessionTabsBound) {
|
|
8638
|
+
this.sessionTabsBound = true;
|
|
8639
|
+
this.tabsOwnerStamp = context.stamp;
|
|
8640
|
+
this.ownedStorageKey = context.storageKey;
|
|
8641
|
+
if (!context.stamp) {
|
|
8642
|
+
return;
|
|
8643
|
+
}
|
|
8644
|
+
this.routeCacheRegistry.bindOwner(context.stamp);
|
|
8645
|
+
await this.applyEffectiveNavigationMode();
|
|
8646
|
+
if (!this.isCurrentSessionSync(context)) {
|
|
8647
|
+
return;
|
|
8648
|
+
}
|
|
8649
|
+
await this.restoreSessionIfNeeded(context);
|
|
8650
|
+
return;
|
|
8651
|
+
}
|
|
8652
|
+
if (context.stamp === this.tabsOwnerStamp &&
|
|
8653
|
+
this.pendingReadinessGap == null &&
|
|
8654
|
+
!this.closedTabsForSessionChange) {
|
|
8655
|
+
this.routeCacheRegistry.resumeAttach();
|
|
8656
|
+
return;
|
|
8657
|
+
}
|
|
8658
|
+
if (context.stamp == null) {
|
|
8659
|
+
if (this.tabsOwnerStamp != null) {
|
|
8660
|
+
this.pendingReadinessGap = this.tabsOwnerStamp;
|
|
8661
|
+
}
|
|
8662
|
+
return;
|
|
8663
|
+
}
|
|
8664
|
+
const gap = this.pendingReadinessGap;
|
|
8665
|
+
this.pendingReadinessGap = null;
|
|
8666
|
+
if (gap != null && gap === context.stamp && !this.closedTabsForSessionChange) {
|
|
8667
|
+
this.tabsOwnerStamp = context.stamp;
|
|
8668
|
+
this.routeCacheRegistry.resumeAttach();
|
|
8669
|
+
return;
|
|
8670
|
+
}
|
|
8671
|
+
const previous = gap ?? this.tabsOwnerStamp;
|
|
8672
|
+
const identityChanged = previous != null && previous !== context.stamp;
|
|
8673
|
+
if (identityChanged || this.closedTabsForSessionChange) {
|
|
8674
|
+
this.closedTabsForSessionChange = false;
|
|
8675
|
+
this.restoreDeferred = false;
|
|
8676
|
+
this.forgetPersistedTabs(this.ownedStorageKey);
|
|
8677
|
+
this.tabsOwnerStamp = context.stamp;
|
|
8678
|
+
this.ownedStorageKey = context.storageKey;
|
|
8679
|
+
this.sessionTabsHydrated = false;
|
|
8680
|
+
this.discardRuntimeTabs();
|
|
8681
|
+
this.routeCacheRegistry.bindOwner(context.stamp);
|
|
8682
|
+
this.routeCacheRegistry.resumeAttach();
|
|
8683
|
+
await this.applyEffectiveNavigationMode();
|
|
8684
|
+
if (!this.isCurrentSessionSync(context)) {
|
|
8685
|
+
return;
|
|
8686
|
+
}
|
|
8687
|
+
this.sessionTabsHydrated = true;
|
|
8688
|
+
await this.syncSettledDestination(context);
|
|
8689
|
+
return;
|
|
8690
|
+
}
|
|
8691
|
+
if (this.tabsOwnerStamp == null && context.stamp != null) {
|
|
8692
|
+
this.tabsOwnerStamp = context.stamp;
|
|
8693
|
+
this.ownedStorageKey = context.storageKey;
|
|
8694
|
+
this.routeCacheRegistry.bindOwner(context.stamp);
|
|
8695
|
+
this.routeCacheRegistry.resumeAttach();
|
|
8696
|
+
await this.applyEffectiveNavigationMode();
|
|
8697
|
+
if (!this.isCurrentSessionSync(context)) {
|
|
8698
|
+
return;
|
|
8699
|
+
}
|
|
8700
|
+
await this.restoreSessionIfNeeded(context);
|
|
8701
|
+
}
|
|
8702
|
+
}
|
|
8703
|
+
/**
|
|
8704
|
+
* Sign-out cleanup uses the captured storage key and still runs when a newer sign-in
|
|
8705
|
+
* has already superseded this event. It does not read the live session.
|
|
8706
|
+
*/
|
|
8707
|
+
applyCapturedSignOut(context) {
|
|
8708
|
+
this.pendingReadinessGap = null;
|
|
8709
|
+
this.restoreDeferred = false;
|
|
8710
|
+
this.forgetPersistedTabs(context.storageKey ?? this.ownedStorageKey);
|
|
8711
|
+
this.tabsOwnerStamp = null;
|
|
8712
|
+
this.discardRuntimeTabs();
|
|
8713
|
+
this.routeCacheRegistry.resumeAttach();
|
|
8714
|
+
this.closedTabsForSessionChange = true;
|
|
8715
|
+
this.sessionTabsHydrated = false;
|
|
8716
|
+
}
|
|
8717
|
+
/**
|
|
8718
|
+
* Opens Home for the settled session. The current route is added only after the router
|
|
8719
|
+
* has left the URL captured with the event, so the previous context is not reopened.
|
|
8720
|
+
* Home is created even when that URL is unchanged, including when it is already Home.
|
|
8721
|
+
*/
|
|
8722
|
+
async syncSettledDestination(context) {
|
|
8723
|
+
if (!this.isCurrentSessionSync(context) || this.effectiveMode() !== 'tabular') {
|
|
8724
|
+
return;
|
|
8725
|
+
}
|
|
8726
|
+
await this.ensureHomeTab();
|
|
8727
|
+
if (!this.isCurrentSessionSync(context)) {
|
|
8728
|
+
return;
|
|
8729
|
+
}
|
|
8730
|
+
const destination = normalizeRouterUrl(this.router.url);
|
|
8731
|
+
const routeBelongsToSettledSession = !!destination && destination !== context.url && axpIsTabEligibleUrl(destination);
|
|
8732
|
+
if (routeBelongsToSettledSession || (!!destination && this.isHomePageUrl(destination))) {
|
|
8733
|
+
await this.syncCurrentRouteAsTab();
|
|
8734
|
+
return;
|
|
8735
|
+
}
|
|
8736
|
+
if (this.tabs().some((tab) => tab.key === AXP_HOME_TAB_KEY)) {
|
|
8737
|
+
this.activeTabKey.set(AXP_HOME_TAB_KEY);
|
|
8738
|
+
}
|
|
8739
|
+
this.persistSession();
|
|
8740
|
+
}
|
|
8741
|
+
/** Continues a restore that was skipped while a command sub-flow held the router. */
|
|
8742
|
+
async resumeDeferredTabRestore() {
|
|
8743
|
+
if (!this.restoreDeferred || axpIsCommandSubFlowNavigationSuspended()) {
|
|
8744
|
+
return;
|
|
8745
|
+
}
|
|
8746
|
+
const context = {
|
|
8747
|
+
generation: this.sessionSyncGeneration,
|
|
8748
|
+
stamp: this.tabsOwnerStamp,
|
|
8749
|
+
sessionEnded: false,
|
|
8750
|
+
storageKey: this.ownedStorageKey,
|
|
8751
|
+
url: normalizeRouterUrl(this.router.url),
|
|
8752
|
+
};
|
|
8753
|
+
this.restoreDeferred = false;
|
|
8754
|
+
if (!context.stamp) {
|
|
8755
|
+
return;
|
|
8756
|
+
}
|
|
8757
|
+
await this.restoreSessionIfNeeded(context);
|
|
8758
|
+
}
|
|
8759
|
+
/** Identity of the session that may own open tabs. Null when the app session is not ready. */
|
|
8760
|
+
sessionStamp() {
|
|
8761
|
+
if (!this.sessionService.isReadyForAppScopedApi) {
|
|
8762
|
+
return null;
|
|
8763
|
+
}
|
|
8764
|
+
const userId = this.sessionService.user?.id;
|
|
8765
|
+
const tenantId = this.sessionService.tenant?.id;
|
|
8766
|
+
const applicationId = this.sessionService.application?.id;
|
|
8767
|
+
if (!userId || !tenantId || !applicationId) {
|
|
8768
|
+
return null;
|
|
8769
|
+
}
|
|
8770
|
+
return `${tenantId}:${applicationId}:${userId}`;
|
|
8771
|
+
}
|
|
8772
|
+
/** Deletes one captured tab list. A newer user's key is left untouched. */
|
|
8773
|
+
forgetPersistedTabs(storageKey) {
|
|
8774
|
+
if (!storageKey) {
|
|
8775
|
+
return;
|
|
8776
|
+
}
|
|
8777
|
+
sessionStorage.removeItem(storageKey);
|
|
8778
|
+
if (this.ownedStorageKey === storageKey) {
|
|
8779
|
+
this.ownedStorageKey = null;
|
|
8780
|
+
}
|
|
8781
|
+
}
|
|
8782
|
+
/** Closes every tab and destroys cached page components. */
|
|
8783
|
+
discardRuntimeTabs() {
|
|
8784
|
+
this.tabs.set([]);
|
|
8785
|
+
this.activeTabKey.set(null);
|
|
8786
|
+
this.tabPreviewService.clearAll();
|
|
8787
|
+
this.routeCacheRegistry.bindOwner(this.tabsOwnerStamp);
|
|
8788
|
+
}
|
|
8789
|
+
/**
|
|
8790
|
+
* Reads the effective NavigationMode (User, then Tenant, then Platform, then the definition default).
|
|
8791
|
+
* Scope reloads update the settings cache without `onChanged`, so callers must use this instead of
|
|
8792
|
+
* treating a partial event value as "switch to simple".
|
|
8793
|
+
*/
|
|
8794
|
+
async applyEffectiveNavigationMode() {
|
|
8795
|
+
if (!this.sessionService.isReadyForAppScopedApi) {
|
|
8796
|
+
return;
|
|
8797
|
+
}
|
|
8798
|
+
const mode = await this.settingsService.get(AXPCommonSettings.NavigationMode);
|
|
8799
|
+
const next = mode === 'tabular' ? 'tabular' : 'simple';
|
|
8800
|
+
const previous = this.navigationMode();
|
|
8801
|
+
if (next === previous) {
|
|
8802
|
+
return;
|
|
8803
|
+
}
|
|
8804
|
+
this.navigationMode.set(next);
|
|
8805
|
+
if (previous === 'tabular' && next === 'simple') {
|
|
8806
|
+
this.clearSession();
|
|
8807
|
+
return;
|
|
8808
|
+
}
|
|
8809
|
+
if (next === 'tabular' && this.effectiveMode() === 'tabular' && this.sessionTabsHydrated) {
|
|
8810
|
+
await this.ensureHomeTab();
|
|
8811
|
+
await this.syncCurrentRouteAsTab();
|
|
8812
|
+
}
|
|
8813
|
+
}
|
|
8814
|
+
enqueueNavigationSettingsSync(task) {
|
|
8815
|
+
const run = this.navigationSettingsChain.then(task, task);
|
|
8816
|
+
this.navigationSettingsChain = run.then(() => undefined, () => undefined);
|
|
8817
|
+
return run;
|
|
8818
|
+
}
|
|
8067
8819
|
//#endregion
|
|
8068
8820
|
//#region ---- Tab Management ----
|
|
8069
8821
|
async ensureHomeTab() {
|
|
@@ -8105,7 +8857,7 @@ class AXPTabNavigationService {
|
|
|
8105
8857
|
}
|
|
8106
8858
|
this.tabs.update((items) => {
|
|
8107
8859
|
const withoutDuplicate = items.filter((item) => {
|
|
8108
|
-
if (item.key === tab.key) {
|
|
8860
|
+
if (item.key === tab.key || this.isSameEntityListTab(item, tab)) {
|
|
8109
8861
|
return false;
|
|
8110
8862
|
}
|
|
8111
8863
|
return axpResolveTabKey(this.router, item.url) !== tab.key;
|
|
@@ -8165,14 +8917,29 @@ class AXPTabNavigationService {
|
|
|
8165
8917
|
lastActivatedAt: now,
|
|
8166
8918
|
};
|
|
8167
8919
|
}
|
|
8168
|
-
/** Finds a tab by scoped key or
|
|
8920
|
+
/** Finds a tab by scoped key, or by entity-list identity when an older key drifted. */
|
|
8169
8921
|
findTabForUrl(url) {
|
|
8170
8922
|
if (this.isHomePageUrl(url)) {
|
|
8171
8923
|
return this.tabs().find((tab) => tab.key === AXP_HOME_TAB_KEY);
|
|
8172
8924
|
}
|
|
8173
8925
|
const resolvedKey = axpResolveTabKey(this.router, url);
|
|
8174
|
-
|
|
8926
|
+
const byKey = this.tabs().find((tab) => tab.key !== AXP_HOME_TAB_KEY &&
|
|
8175
8927
|
(tab.key === resolvedKey || axpResolveTabKey(this.router, tab.url) === resolvedKey));
|
|
8928
|
+
if (byKey) {
|
|
8929
|
+
return byKey;
|
|
8930
|
+
}
|
|
8931
|
+
if (!axpIsEntityListPath(url)) {
|
|
8932
|
+
return undefined;
|
|
8933
|
+
}
|
|
8934
|
+
const identity = axpBuildTabKey(normalizeRouterUrl(url));
|
|
8935
|
+
return this.tabs().find((tab) => tab.key !== AXP_HOME_TAB_KEY && axpIsEntityListPath(tab.url) && axpBuildTabKey(tab.url) === identity);
|
|
8936
|
+
}
|
|
8937
|
+
/** Same list menu (`mode`, `list`, …). Page-state query params do not make a new tab. */
|
|
8938
|
+
isSameEntityListTab(left, right) {
|
|
8939
|
+
if (!axpIsEntityListPath(left.url) || !axpIsEntityListPath(right.url)) {
|
|
8940
|
+
return false;
|
|
8941
|
+
}
|
|
8942
|
+
return axpBuildTabKey(left.url) === axpBuildTabKey(right.url);
|
|
8176
8943
|
}
|
|
8177
8944
|
/** True when the URL is the configured application home page (e.g. dashboard). */
|
|
8178
8945
|
isHomePageUrl(url) {
|
|
@@ -8317,8 +9084,12 @@ class AXPTabNavigationService {
|
|
|
8317
9084
|
return `${SESSION_STORAGE_PREFIX}(${userId})`;
|
|
8318
9085
|
}
|
|
8319
9086
|
persistSession() {
|
|
8320
|
-
const key = this.
|
|
8321
|
-
if (!key ||
|
|
9087
|
+
const key = this.ownedStorageKey;
|
|
9088
|
+
if (!key ||
|
|
9089
|
+
key !== this.sessionStorageKey() ||
|
|
9090
|
+
this.tabsOwnerStamp !== this.sessionStamp() ||
|
|
9091
|
+
!this.sessionTabsHydrated ||
|
|
9092
|
+
this.effectiveMode() === 'simple') {
|
|
8322
9093
|
return;
|
|
8323
9094
|
}
|
|
8324
9095
|
const payload = {
|
|
@@ -8335,31 +9106,54 @@ class AXPTabNavigationService {
|
|
|
8335
9106
|
this.tabs.set([]);
|
|
8336
9107
|
this.activeTabKey.set(null);
|
|
8337
9108
|
this.tabPreviewService.clearAll();
|
|
9109
|
+
this.routeCacheRegistry.clear();
|
|
8338
9110
|
}
|
|
8339
|
-
async restoreSessionIfNeeded() {
|
|
9111
|
+
async restoreSessionIfNeeded(context) {
|
|
9112
|
+
if (!this.isCurrentSessionSync(context)) {
|
|
9113
|
+
return;
|
|
9114
|
+
}
|
|
8340
9115
|
if (this.effectiveMode() === 'simple') {
|
|
9116
|
+
this.sessionTabsHydrated = true;
|
|
8341
9117
|
return;
|
|
8342
9118
|
}
|
|
8343
9119
|
if (axpIsCommandSubFlowNavigationSuspended()) {
|
|
9120
|
+
this.restoreDeferred = true;
|
|
8344
9121
|
return;
|
|
8345
9122
|
}
|
|
8346
|
-
const storageKey =
|
|
9123
|
+
const storageKey = context.storageKey;
|
|
8347
9124
|
if (!storageKey) {
|
|
8348
|
-
|
|
8349
|
-
|
|
9125
|
+
this.tabs.set([]);
|
|
9126
|
+
this.activeTabKey.set(null);
|
|
9127
|
+
this.tabPreviewService.clearAll();
|
|
9128
|
+
this.sessionTabsHydrated = true;
|
|
8350
9129
|
return;
|
|
8351
9130
|
}
|
|
8352
9131
|
const raw = sessionStorage.getItem(storageKey);
|
|
8353
9132
|
if (!raw) {
|
|
9133
|
+
this.tabs.set([]);
|
|
9134
|
+
this.activeTabKey.set(null);
|
|
9135
|
+
this.sessionTabsHydrated = true;
|
|
8354
9136
|
await this.ensureHomeTab();
|
|
9137
|
+
if (!this.isCurrentSessionSync(context)) {
|
|
9138
|
+
return;
|
|
9139
|
+
}
|
|
8355
9140
|
await this.syncCurrentRouteAsTab();
|
|
8356
9141
|
return;
|
|
8357
9142
|
}
|
|
8358
9143
|
try {
|
|
8359
9144
|
const parsed = JSON.parse(raw);
|
|
8360
|
-
const restoredTabs = this.removeDuplicateHomeUrlTabs(this.normalizeRestoredTabKeys(defaultTo(parsed.tabs, [])
|
|
9145
|
+
const restoredTabs = this.removeDuplicateHomeUrlTabs(this.normalizeRestoredTabKeys(defaultTo(parsed.tabs, [])
|
|
9146
|
+
.filter((tab) => tab?.key && tab?.url)
|
|
9147
|
+
.map((tab) => ({ ...tab, url: axpInTabErrorRetryUrl(tab.url) }))));
|
|
9148
|
+
if (!this.isCurrentSessionSync(context)) {
|
|
9149
|
+
return;
|
|
9150
|
+
}
|
|
9151
|
+
this.sessionTabsHydrated = true;
|
|
8361
9152
|
this.tabs.set(this.ensureHomeTabFirst(restoredTabs));
|
|
8362
9153
|
await this.ensureHomeTab();
|
|
9154
|
+
if (!this.isCurrentSessionSync(context)) {
|
|
9155
|
+
return;
|
|
9156
|
+
}
|
|
8363
9157
|
const activeKey = parsed.activeTabKey ?? this.tabs()[this.tabs().length - 1]?.key ?? AXP_HOME_TAB_KEY;
|
|
8364
9158
|
this.activeTabKey.set(activeKey);
|
|
8365
9159
|
const activeTab = this.tabs().find((tab) => tab.key === activeKey);
|
|
@@ -8367,17 +9161,18 @@ class AXPTabNavigationService {
|
|
|
8367
9161
|
if (activeTab &&
|
|
8368
9162
|
currentUrl !== normalizeRouterUrl(activeTab.url) &&
|
|
8369
9163
|
axpIsTabEligibleUrl(currentUrl)) {
|
|
8370
|
-
this.
|
|
8371
|
-
try {
|
|
8372
|
-
await this.router.navigateByUrl(activeTab.url);
|
|
8373
|
-
}
|
|
8374
|
-
finally {
|
|
8375
|
-
this.suppressNavigationSync = false;
|
|
8376
|
-
}
|
|
9164
|
+
await this.runWithSuppressedNavigationSync(() => this.router.navigateByUrl(activeTab.url));
|
|
8377
9165
|
}
|
|
8378
9166
|
}
|
|
8379
9167
|
catch {
|
|
9168
|
+
if (!this.isCurrentSessionSync(context)) {
|
|
9169
|
+
return;
|
|
9170
|
+
}
|
|
9171
|
+
this.sessionTabsHydrated = true;
|
|
8380
9172
|
await this.ensureHomeTab();
|
|
9173
|
+
if (!this.isCurrentSessionSync(context)) {
|
|
9174
|
+
return;
|
|
9175
|
+
}
|
|
8381
9176
|
await this.syncCurrentRouteAsTab();
|
|
8382
9177
|
}
|
|
8383
9178
|
}
|
|
@@ -8627,7 +9422,12 @@ class AXPLocalEntityOutboxService {
|
|
|
8627
9422
|
this.handlers.delete(entityKey);
|
|
8628
9423
|
}
|
|
8629
9424
|
pendingFor(entityKey) {
|
|
8630
|
-
|
|
9425
|
+
const userKey = this.resolveUserKey();
|
|
9426
|
+
return this.pendingOps().filter((op) => op.entityKey === entityKey && op.userKey === userKey);
|
|
9427
|
+
}
|
|
9428
|
+
/** Await durable operations before a feature overlays pending rows after reload. */
|
|
9429
|
+
hydrate() {
|
|
9430
|
+
return this.refreshPending();
|
|
8631
9431
|
}
|
|
8632
9432
|
async enqueue(input) {
|
|
8633
9433
|
const userKey = this.resolveUserKey();
|
|
@@ -8638,10 +9438,7 @@ class AXPLocalEntityOutboxService {
|
|
|
8638
9438
|
}
|
|
8639
9439
|
const recordIds = [...new Set((input.recordIds ?? []).map((id) => id?.trim()).filter(Boolean))];
|
|
8640
9440
|
const existing = await this.loadUserOps(userKey);
|
|
8641
|
-
const duplicate = existing.find((op) => op.entityKey === entityKey &&
|
|
8642
|
-
op.action === action &&
|
|
8643
|
-
sameIdSet(op.recordIds, recordIds) &&
|
|
8644
|
-
stablePayload(op.payload) === stablePayload(input.payload));
|
|
9441
|
+
const duplicate = existing.find((op) => op.entityKey === entityKey && op.action === action && sameIdSet(op.recordIds, recordIds) && stablePayload(op.payload) === stablePayload(input.payload));
|
|
8645
9442
|
if (duplicate) {
|
|
8646
9443
|
return;
|
|
8647
9444
|
}
|
|
@@ -8652,7 +9449,8 @@ class AXPLocalEntityOutboxService {
|
|
|
8652
9449
|
action,
|
|
8653
9450
|
recordIds,
|
|
8654
9451
|
payload: input.payload,
|
|
8655
|
-
|
|
9452
|
+
// Keep dependent operations ordered even if send and edit enqueue in the same millisecond.
|
|
9453
|
+
queuedAt: new Date(Math.max(Date.now(), ...existing.map((op) => Date.parse(op.queuedAt) + 1))).toISOString(),
|
|
8656
9454
|
attempts: 0,
|
|
8657
9455
|
lastError: null,
|
|
8658
9456
|
};
|
|
@@ -8690,6 +9488,10 @@ class AXPLocalEntityOutboxService {
|
|
|
8690
9488
|
}
|
|
8691
9489
|
const ops = (await this.loadUserOps(userKey)).sort((left, right) => left.queuedAt.localeCompare(right.queuedAt));
|
|
8692
9490
|
for (const op of ops) {
|
|
9491
|
+
// A queued batch must never continue under another account after an awaited HTTP call.
|
|
9492
|
+
if (userKey !== this.resolveUserKey() || !this.sessionService.isReadyForAppScopedApi) {
|
|
9493
|
+
break;
|
|
9494
|
+
}
|
|
8693
9495
|
const handler = this.handlers.get(op.entityKey);
|
|
8694
9496
|
if (!handler) {
|
|
8695
9497
|
continue;
|
|
@@ -8745,6 +9547,8 @@ class AXPLocalEntityOutboxService {
|
|
|
8745
9547
|
return;
|
|
8746
9548
|
}
|
|
8747
9549
|
const ops = await this.loadUserOps(userKey);
|
|
9550
|
+
if (userKey !== this.resolveUserKey())
|
|
9551
|
+
return;
|
|
8748
9552
|
ops.sort((left, right) => left.queuedAt.localeCompare(right.queuedAt));
|
|
8749
9553
|
this.pendingOps.set(ops);
|
|
8750
9554
|
}
|
|
@@ -9268,6 +10072,23 @@ class AXPNotFoundError extends Error {
|
|
|
9268
10072
|
}
|
|
9269
10073
|
}
|
|
9270
10074
|
|
|
10075
|
+
/**
|
|
10076
|
+
* Placeholder for a wildcard route whose guard always redirects into the shell.
|
|
10077
|
+
* The component is not shown.
|
|
10078
|
+
*/
|
|
10079
|
+
class AXPUnreachableRouteComponent {
|
|
10080
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPUnreachableRouteComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
10081
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.0.8", type: AXPUnreachableRouteComponent, isStandalone: true, selector: "axp-unreachable-route", ngImport: i0, template: '', isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
10082
|
+
}
|
|
10083
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPUnreachableRouteComponent, decorators: [{
|
|
10084
|
+
type: Component,
|
|
10085
|
+
args: [{
|
|
10086
|
+
selector: 'axp-unreachable-route',
|
|
10087
|
+
template: '',
|
|
10088
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
10089
|
+
}]
|
|
10090
|
+
}] });
|
|
10091
|
+
|
|
9271
10092
|
/** Canonical route path for the platform 404 page. */
|
|
9272
10093
|
const AXP_NOT_FOUND_ROUTE = '/error/404';
|
|
9273
10094
|
/**
|
|
@@ -9276,7 +10097,8 @@ const AXP_NOT_FOUND_ROUTE = '/error/404';
|
|
|
9276
10097
|
*/
|
|
9277
10098
|
const AXP_NOT_FOUND_CATCH_ALL_ROUTE = {
|
|
9278
10099
|
path: '**',
|
|
9279
|
-
|
|
10100
|
+
canActivate: [axpRedirectUnknownToNotFound],
|
|
10101
|
+
component: AXPUnreachableRouteComponent,
|
|
9280
10102
|
};
|
|
9281
10103
|
/**
|
|
9282
10104
|
* Registers the platform catch-all 404 route.
|
|
@@ -9300,8 +10122,8 @@ function axpIsEntityRecordNotFound(data) {
|
|
|
9300
10122
|
/**
|
|
9301
10123
|
* Creates an Angular redirect command to the platform 404 page.
|
|
9302
10124
|
*/
|
|
9303
|
-
function axpRedirectToNotFound(router = inject(Router)) {
|
|
9304
|
-
return new RedirectCommand(router.
|
|
10125
|
+
function axpRedirectToNotFound(router = inject(Router), sourceUrl) {
|
|
10126
|
+
return new RedirectCommand(axpInTabErrorUrlTree(router, '404', sourceUrl ?? router.url));
|
|
9305
10127
|
}
|
|
9306
10128
|
|
|
9307
10129
|
/** Canonical route path for the platform 401 page. */
|
|
@@ -9309,8 +10131,8 @@ const AXP_UNAUTHORIZED_ROUTE = '/error/401';
|
|
|
9309
10131
|
/**
|
|
9310
10132
|
* Creates an Angular redirect command to the platform 401 page.
|
|
9311
10133
|
*/
|
|
9312
|
-
function axpRedirectToUnauthorized(router = inject(Router)) {
|
|
9313
|
-
return new RedirectCommand(router.
|
|
10134
|
+
function axpRedirectToUnauthorized(router = inject(Router), sourceUrl) {
|
|
10135
|
+
return new RedirectCommand(axpInTabErrorUrlTree(router, '401', sourceUrl ?? router.url));
|
|
9314
10136
|
}
|
|
9315
10137
|
|
|
9316
10138
|
//#endregion
|
|
@@ -10078,15 +10900,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
|
|
|
10078
10900
|
const AXP_ENTITY_VIEW_ACCESS = new InjectionToken('AXP_ENTITY_VIEW_ACCESS');
|
|
10079
10901
|
|
|
10080
10902
|
const APP_CHOOSER_ROUTE = '/auth/account/app-chooser';
|
|
10081
|
-
/** In-app navigations (e.g. Open Details) stay put instead of redirecting to 401. */
|
|
10082
|
-
function axpShouldStayOnCurrentPage(router, targetUrl) {
|
|
10083
|
-
const currentPath = router.url.split('#')[0]?.split('?')[0] ?? '';
|
|
10084
|
-
const targetPath = targetUrl.split('#')[0]?.split('?')[0] ?? '';
|
|
10085
|
-
if (!currentPath || currentPath === '/' || currentPath === targetPath) {
|
|
10086
|
-
return false;
|
|
10087
|
-
}
|
|
10088
|
-
return !currentPath.startsWith('/auth') && !currentPath.startsWith('/error');
|
|
10089
|
-
}
|
|
10090
10903
|
/**
|
|
10091
10904
|
|
|
10092
10905
|
* Blocks navigation when the target route is not accessible for the current user.
|
|
@@ -10114,10 +10927,7 @@ const AXPMenuRouteGuard = (route, state) => {
|
|
|
10114
10927
|
if (isAllowed instanceof UrlTree) {
|
|
10115
10928
|
return isAllowed;
|
|
10116
10929
|
}
|
|
10117
|
-
|
|
10118
|
-
return false;
|
|
10119
|
-
}
|
|
10120
|
-
return router.parseUrl(AXP_UNAUTHORIZED_ROUTE);
|
|
10930
|
+
return axpInTabErrorUrlTree(router, '401', state.url);
|
|
10121
10931
|
}));
|
|
10122
10932
|
};
|
|
10123
10933
|
|
|
@@ -11175,7 +11985,7 @@ class AXPCommonModule {
|
|
|
11175
11985
|
provide: AXP_SETTING_DEFINITION_PROVIDER,
|
|
11176
11986
|
useFactory: async () => {
|
|
11177
11987
|
const injector = inject(Injector);
|
|
11178
|
-
const provider = (await import('./acorex-platform-framework-client-common-common-settings.provider-
|
|
11988
|
+
const provider = (await import('./acorex-platform-framework-client-common-common-settings.provider-Biu0_ryw.mjs')).AXPCommonSettingProvider;
|
|
11179
11989
|
return new provider(injector);
|
|
11180
11990
|
},
|
|
11181
11991
|
multi: true,
|
|
@@ -11298,7 +12108,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
|
|
|
11298
12108
|
provide: AXP_SETTING_DEFINITION_PROVIDER,
|
|
11299
12109
|
useFactory: async () => {
|
|
11300
12110
|
const injector = inject(Injector);
|
|
11301
|
-
const provider = (await import('./acorex-platform-framework-client-common-common-settings.provider-
|
|
12111
|
+
const provider = (await import('./acorex-platform-framework-client-common-common-settings.provider-Biu0_ryw.mjs')).AXPCommonSettingProvider;
|
|
11302
12112
|
return new provider(injector);
|
|
11303
12113
|
},
|
|
11304
12114
|
multi: true,
|
|
@@ -11343,20 +12153,38 @@ const AXP_ROOT_CONFIG_TOKEN = new InjectionToken('AXP_ROOT_CONFIG_TOKEN');
|
|
|
11343
12153
|
|
|
11344
12154
|
/**
|
|
11345
12155
|
* Opens the native file picker and always settles — including when the user cancels.
|
|
11346
|
-
* `AXFileService.choose()`
|
|
12156
|
+
* `AXFileService.choose()` only listens for `change`, so dismissing the dialog leaves that promise pending.
|
|
11347
12157
|
*/
|
|
11348
12158
|
function chooseSystemFiles(options = {}) {
|
|
11349
|
-
|
|
12159
|
+
return openSystemFileDialog(inject(DOCUMENT), options);
|
|
12160
|
+
}
|
|
12161
|
+
/**
|
|
12162
|
+
* Same picker as {@link chooseSystemFiles}, with the document passed in so callers can `await` before opening it.
|
|
12163
|
+
*/
|
|
12164
|
+
function openSystemFileDialog(doc, options = {}) {
|
|
12165
|
+
const view = doc.defaultView;
|
|
12166
|
+
if (!view) {
|
|
12167
|
+
return Promise.resolve([]);
|
|
12168
|
+
}
|
|
11350
12169
|
const accept = options.accept ?? '';
|
|
11351
12170
|
const multiple = options.multiple ?? false;
|
|
11352
12171
|
return new Promise((resolve) => {
|
|
11353
|
-
const input =
|
|
12172
|
+
const input = doc.createElement('input');
|
|
11354
12173
|
input.type = 'file';
|
|
11355
12174
|
input.accept = accept;
|
|
11356
12175
|
input.multiple = multiple;
|
|
11357
12176
|
input.style.display = 'none';
|
|
11358
|
-
|
|
12177
|
+
doc.body.appendChild(input);
|
|
11359
12178
|
let settled = false;
|
|
12179
|
+
let dialogOpened = false;
|
|
12180
|
+
let watchFocus = false;
|
|
12181
|
+
const cleanup = () => {
|
|
12182
|
+
input.removeEventListener('change', onChange);
|
|
12183
|
+
input.removeEventListener('cancel', onCancel);
|
|
12184
|
+
view.removeEventListener('blur', onBlur);
|
|
12185
|
+
view.removeEventListener('focus', onFocus);
|
|
12186
|
+
input.remove();
|
|
12187
|
+
};
|
|
11360
12188
|
const settle = (files) => {
|
|
11361
12189
|
if (settled) {
|
|
11362
12190
|
return;
|
|
@@ -11365,21 +12193,37 @@ function chooseSystemFiles(options = {}) {
|
|
|
11365
12193
|
cleanup();
|
|
11366
12194
|
resolve(files);
|
|
11367
12195
|
};
|
|
11368
|
-
const cleanup = () => {
|
|
11369
|
-
input.removeEventListener('change', onChange);
|
|
11370
|
-
window.removeEventListener('focus', onWindowFocus);
|
|
11371
|
-
input.remove();
|
|
11372
|
-
};
|
|
11373
12196
|
const onChange = () => {
|
|
11374
12197
|
settle(Array.from(input.files ?? []));
|
|
11375
12198
|
};
|
|
11376
|
-
const
|
|
11377
|
-
|
|
11378
|
-
|
|
12199
|
+
const onCancel = () => {
|
|
12200
|
+
settle([]);
|
|
12201
|
+
};
|
|
12202
|
+
const onBlur = () => {
|
|
12203
|
+
if (watchFocus) {
|
|
12204
|
+
dialogOpened = true;
|
|
12205
|
+
}
|
|
12206
|
+
};
|
|
12207
|
+
const onFocus = () => {
|
|
12208
|
+
if (!watchFocus || !dialogOpened) {
|
|
12209
|
+
return;
|
|
12210
|
+
}
|
|
12211
|
+
// `change` fires before this timeout when the user confirms a file.
|
|
12212
|
+
view.setTimeout(() => {
|
|
12213
|
+
if (!settled) {
|
|
12214
|
+
settle(Array.from(input.files ?? []));
|
|
12215
|
+
}
|
|
12216
|
+
}, 300);
|
|
11379
12217
|
};
|
|
11380
12218
|
input.addEventListener('change', onChange);
|
|
11381
|
-
|
|
12219
|
+
input.addEventListener('cancel', onCancel);
|
|
12220
|
+
view.addEventListener('blur', onBlur);
|
|
12221
|
+
view.addEventListener('focus', onFocus);
|
|
11382
12222
|
input.click();
|
|
12223
|
+
// Ignore blur/focus caused by opening the dialog itself. Cancel still settles via the `cancel` event.
|
|
12224
|
+
view.setTimeout(() => {
|
|
12225
|
+
watchFocus = true;
|
|
12226
|
+
}, 0);
|
|
11383
12227
|
});
|
|
11384
12228
|
}
|
|
11385
12229
|
//#endregion
|
|
@@ -12213,8 +13057,11 @@ class AXPLocalEntityEngine {
|
|
|
12213
13057
|
return bucket.syncInFlight;
|
|
12214
13058
|
}
|
|
12215
13059
|
bucket.syncInFlight = (async () => {
|
|
13060
|
+
const userKey = this.resolveUserKey();
|
|
12216
13061
|
const result = await fetchHot();
|
|
12217
|
-
|
|
13062
|
+
if (userKey === this.resolveUserKey()) {
|
|
13063
|
+
await this.replaceAll(entityKey, result.items ?? []);
|
|
13064
|
+
}
|
|
12218
13065
|
})().finally(() => {
|
|
12219
13066
|
bucket.syncInFlight = null;
|
|
12220
13067
|
});
|
|
@@ -12289,6 +13136,8 @@ class AXPLocalEntityEngine {
|
|
|
12289
13136
|
await axpLocalEntityIndexedDb.transaction('rw', axpLocalEntityIndexedDb.items, async () => {
|
|
12290
13137
|
await axpLocalEntityIndexedDb.items.bulkDelete(storageIds);
|
|
12291
13138
|
});
|
|
13139
|
+
if (userKey !== this.resolveUserKey())
|
|
13140
|
+
return;
|
|
12292
13141
|
bucket.items.set(next);
|
|
12293
13142
|
bucket.hydrated.set(true);
|
|
12294
13143
|
this.publish({ type: 'store-changed', entityKey, userKey, originId: this.originId });
|
|
@@ -12347,10 +13196,9 @@ class AXPLocalEntityEngine {
|
|
|
12347
13196
|
bucket.hydrated.set(true);
|
|
12348
13197
|
return;
|
|
12349
13198
|
}
|
|
12350
|
-
const stored = await axpLocalEntityIndexedDb.items
|
|
12351
|
-
|
|
12352
|
-
|
|
12353
|
-
.toArray();
|
|
13199
|
+
const stored = await axpLocalEntityIndexedDb.items.where('[entityKey+userKey]').equals([entityKey, userKey]).toArray();
|
|
13200
|
+
if (userKey !== this.resolveUserKey())
|
|
13201
|
+
return;
|
|
12354
13202
|
const items = this.prune(entityKey, stored.map((row) => row.payload));
|
|
12355
13203
|
this.userKeySignal.set(userKey);
|
|
12356
13204
|
bucket.items.set(items);
|
|
@@ -12373,10 +13221,7 @@ class AXPLocalEntityEngine {
|
|
|
12373
13221
|
const keepIds = new Set(stored.map((row) => row.storageId));
|
|
12374
13222
|
const metaId = buildLocalEntityMetaId(entityKey, userKey);
|
|
12375
13223
|
await axpLocalEntityIndexedDb.transaction('rw', axpLocalEntityIndexedDb.items, axpLocalEntityIndexedDb.meta, async () => {
|
|
12376
|
-
const existing = await axpLocalEntityIndexedDb.items
|
|
12377
|
-
.where('[entityKey+userKey]')
|
|
12378
|
-
.equals([entityKey, userKey])
|
|
12379
|
-
.primaryKeys();
|
|
13224
|
+
const existing = await axpLocalEntityIndexedDb.items.where('[entityKey+userKey]').equals([entityKey, userKey]).primaryKeys();
|
|
12380
13225
|
const staleIds = existing.filter((id) => !keepIds.has(String(id)));
|
|
12381
13226
|
if (staleIds.length > 0) {
|
|
12382
13227
|
await axpLocalEntityIndexedDb.items.bulkDelete(staleIds);
|
|
@@ -12391,6 +13236,8 @@ class AXPLocalEntityEngine {
|
|
|
12391
13236
|
lastSyncedAt: new Date().toISOString(),
|
|
12392
13237
|
});
|
|
12393
13238
|
});
|
|
13239
|
+
if (userKey !== this.resolveUserKey())
|
|
13240
|
+
return;
|
|
12394
13241
|
this.userKeySignal.set(userKey);
|
|
12395
13242
|
bucket.items.set(items);
|
|
12396
13243
|
bucket.hydrated.set(true);
|
|
@@ -12412,6 +13259,8 @@ class AXPLocalEntityEngine {
|
|
|
12412
13259
|
}
|
|
12413
13260
|
this.userKeySignal.set(userKey);
|
|
12414
13261
|
for (const [entityKey, bucket] of this.buckets) {
|
|
13262
|
+
bucket.items.set([]);
|
|
13263
|
+
bucket.hydrated.set(false);
|
|
12415
13264
|
bucket.bootstrapPromise = null;
|
|
12416
13265
|
bucket.hydratePromise = null;
|
|
12417
13266
|
bucket.syncInFlight = null;
|
|
@@ -14054,5 +14903,5 @@ const axpPageGetContextQueryDefinition = defineQuery({
|
|
|
14054
14903
|
* Generated bundle index. Do not edit.
|
|
14055
14904
|
*/
|
|
14056
14905
|
|
|
14057
|
-
export { ALL_DEFAULT_OPERATORS, AXPAcorexFileTypeBridgeProvider, AXPAcorexFileTypeModule, AXPAppVersionService, AXPBackgroundOperationCompletionService, AXPBackgroundOperationEntityStoreService, AXPBackgroundOperationHandler, AXPBackgroundOperationNotificationService, AXPBackgroundOperationProvider, AXPBackgroundOperationRootConfig, AXPBackgroundOperationService, AXPBackgroundOperationSlotComponent, AXPBrowserTitleService, AXPCleanNestedFilters, AXPClipBoardService, AXPCommonModule, AXPCommonSettings, AXPCustomOperatorService, AXPCustomOperatorServiceImpl, AXPDataProvider, AXPDefaultMultiLanguageConfigService, AXPEntityDetailsNavTrailService, AXPErrorHandlerRegistryService, AXPExportService, AXPFileActionsService, AXPFileChooserSettings, AXPFileStorageEvaluatorScopeProvider, AXPFileStorageService, AXPFileTypeProviderService, AXPFilterOperatorMiddlewareService, AXPFilterOperatorMiddlewareServiceImpl, AXPFooterTextSlotComponent, AXPGlobalErrorHandler, AXPHomePageModule, AXPHomePageService, AXPIndexedDbSettingValueProvider, AXPIntegrationDomainProviderContext, AXPIntegrationDomainRegistryFacade, AXPIntegrationDomainRegistryService, AXPIntegrationOAuthService, AXPIntegrationProviderContext, AXPIntegrationProviderRegistryService, AXPIntegrationRuntimeService, AXPKeyboardShortcutsHelpComponent, AXPKeyboardShortcutsHelpService, AXPLocalEntityEngine, AXPLocalEntityIndexedDb, AXPLocalEntityOfflineRuntime, AXPLocalEntityOutboxService, AXPLocalEntityRealtimeBridge, AXPLockService, AXPLogoImageSrcResolverService, AXPMenuKeyboardShortcutService, AXPMenuMiddlewareRegistry, AXPMenuProviderService, AXPMenuRouteAccessService, AXPMenuRouteGuard, AXPMenuSearchDefinitionProvider, AXPMenuSearchProvider, AXPMenuService, AXPMenuVisibilityService, AXPNavBarSlotComponent, AXPNavigateCommand, AXPNetworkConnectivityService, AXPNotFoundCatchAllRoute, AXPNotFoundError, AXPOutputContractTranscriptSegmentService, AXPPageGetContextQuery, AXPPageRunActionCommand, AXPPlatformBrandingService, AXPPlatformDefaultConfigs, AXPPlatformSettingsLoadState, AXPPlatformSettingsOfflineBootstrapService, AXPQueryEvaluatorScopeProvider, AXPQueryParamsEvaluatorScopeProvider, AXPRegionalCalendarService, AXPSearchCommandProvider, AXPSearchDefinitionActionBuilder, AXPSearchDefinitionBuilder, AXPSearchDefinitionProviderContext, AXPSearchDefinitionProviderService, AXPSearchService, AXPSettingDefaultValuesAggregatorService, AXPSettingDefinitionGroupBuilder, AXPSettingDefinitionProviderContext, AXPSettingDefinitionProviderService, AXPSettingDefinitionSectionBuilder, AXPSettingsBulkSyncService, AXPSettingsEvaluatorScopeProvider, AXPSettingsIndexedDb, AXPSettingsService, AXPStateMessageComponent, AXPStatusDefinitionProviderService, AXPStickyDirective, AXPTabNavigationService, AXPTabPreviewService, AXPTabRouteCacheRegistry, AXPTokenDefinitionService, AXPTokenEvaluatorScopeProvider, AXPTranslateFormatter, AXPUnsavedChangesConfirmService, AXPUnsavedChangesPopstateService, AXPUserNotificationService, AXPVersioningService, 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_BREADCRUMB_COLLAPSE_HEAD_COUNT, AXP_BREADCRUMB_COLLAPSE_TAIL_COUNT, AXP_DOCUMENT_FILE_TYPE_PURPOSES, AXP_ENTITY_LIST_DISPLAY_MODE_DEFAULT, AXP_ENTITY_VIEW_ACCESS, AXP_EXPERIENCE_SETTINGS_GROUP, AXP_EXPERIENCE_SETTINGS_SECTIONS, AXP_FILE_ACTION_PROVIDER, AXP_FILE_TYPE_INFO_PROVIDER, AXP_HOME_PAGES, AXP_HOME_PAGE_DEFAULT_KEY, AXP_HOME_TAB_KEY, AXP_INTEGRATION_CONNECTION_ESTABLISHED, AXP_INTEGRATION_CONNECTION_LIFECYCLE, AXP_INTEGRATION_DOMAIN_PROVIDER, 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_LOCAL_ENTITY_BROADCAST_CHANNEL, AXP_LOCAL_ENTITY_DEFAULT_MAX_ITEMS, AXP_LOCAL_ENTITY_OUTBOX_LOCK, AXP_MENU_MIDDLEWARE, AXP_MENU_PROVIDER, AXP_NOT_FOUND_CATCH_ALL_ROUTE, AXP_NOT_FOUND_ROUTE, AXP_OUTPUT_CONTRACT_TRANSCRIPT_SEGMENT_PROVIDER, AXP_PLATFORM_BRANDING_SETTING_KEYS, AXP_PLATFORM_CONFIG_TOKEN, AXP_PROTECTED_ROUTE_GUARDS, AXP_ROOT_CONFIG_TOKEN, AXP_RUNTIME_QUERY_CACHE_DEFAULTS, AXP_SEARCH_DEFINITION_LAZY_RESOLVER, AXP_SEARCH_DEFINITION_PROVIDER, AXP_SEARCH_PROVIDER, AXP_SETTINGS_CACHE_SYNC, AXP_SETTINGS_SYNC_DEBOUNCE_MS, AXP_SETTINGS_SYNC_KEYS, AXP_SETTINGS_SYNC_RETRY_BACKOFF_MS, AXP_SETTINGS_SYNC_TTL_MS, AXP_SETTING_DEFAULT_VALUES_PROVIDERS, AXP_SETTING_DEFINITION_PROVIDER, AXP_SETTING_SCOPE_DESCENDANTS, AXP_SETTING_VALUE_PROVIDER, AXP_STATUS_PROVIDERS, AXP_TAB_BREADCRUMB_JOIN_SEPARATOR, AXP_TAB_ROUTE_DATA_KEY, AXP_TOKEN_DEFINITION_PROVIDER, AXP_UNAUTHORIZED_ROUTE, AXP_USER_NOTIFICATION_SERVICE, AXVChangeType, AX_FILE_TYPE_METADATA_SUPPORT_BY_FILE_VIEWER, BETWEEN_OPER, BOOLEAN_OPERATORS, CONTAINS_OPER, DATE_OPERATORS, DEFAULT_DATE_FILTER_PRESETS, DEFAULT_MULTILANGUAGE_FIELD_NAMES, ENDS_WITH_OPER, ENTITY_LIST_DEFAULT_INSTANCE_ID, ENTITY_LIST_INSTANCE_QUERY_PARAM, ENVIRONMENT, EQ_OPER, FILE_VIEWER_PREVIEW_EXTENSIONS, 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, SETTINGS_TRACE_NS, SETTINGS_TRACE_TITLE, STARTS_WITH_OPER, STRING_OPERATORS, UploadFromComputerActionProvider, applyDateFilterPreset, axpBackgroundOperationCapabilities, axpBackgroundOperationDisplayTime, axpBackgroundOperationDurationMs, axpBackgroundOperationEntityName, axpBackgroundOperationEstimateEndDate, axpBackgroundOperationFailurePolicy, axpBackgroundOperationFromEntityModel, axpBackgroundOperationRemainingMs, axpBackgroundOperationStatusTextParams, axpBackgroundOperationToEntityModel, axpBeginCommandSubFlowNavigation, axpBuildBackgroundOperation, axpBuildTabKey, axpClearEntityDetailsNavTrail, axpCloneBreadcrumbItems, axpCoerceBackgroundOperationDate, axpComposeEntityDetailsNavTrail, axpCreateBackgroundOperationId, axpEndCommandSubFlowNavigation, axpEnsureEntityListInstanceQueryParam, axpEntityKeyFromDetailsPath, axpFallbackTabTitle, axpFindMenuItemByRoute, axpFormatBackgroundOperationDurationMs, axpGetBreadcrumbNavigatePath, axpGetEntityDetailsNavTrail, axpGetEntityListInstanceIdFromParamMap, axpGetEntityListInstanceIdFromPath, axpGetEntityListInstanceIdFromQueryString, axpGetEntityListReturnUrl, axpGetLeafRouteSnapshot, axpHasTabScope, axpInterpolateNavigatePath, axpIsActiveBackgroundOperationStatus, axpIsBackgroundOperationI18nText, axpIsCommandSubFlowNavigationSuspended, axpIsCompletedBackgroundOperationStatus, axpIsCompletedPanelBackgroundOperation, axpIsEntityDetailsPath, axpIsEntityDetailsViewRoute, axpIsEntityListPath, axpIsEntityListRoute, axpIsEntityRecordNotFound, axpIsInProgressPanelBackgroundOperation, axpIsLikelyNetworkFailure, axpIsSameEntityDetailsRecord, axpIsTabEligibleUrl, axpIsTrayVisibleBackgroundOperation, axpJoinTabTitleSegments, axpLocalEntityCanServeListLocally, axpLocalEntityHotListRequest, axpLocalEntityIndexedDb, axpLocalEntityRecordId, axpLocalEntitySortAt, axpLocalEntitySupportsOfflineWrite, axpLocalEntityToPayload, axpMenuItemListInstanceId, axpMenuPathsMatch, axpNavigateAppPath, axpNavigateCommandDefinition, axpNormalizeEntityListInstanceId, axpPageGetContextQueryDefinition, axpPageRunActionCommandDefinition, axpParseAppPath, axpParseEntityDetailsRoute, axpParseEntityListRoute, axpPathFromSnapshot, axpPeekCommandSubFlowReturnUrl, axpPlatformSettingsAvailableGuard, axpPrepareOriginNavTrail, axpRedirectToNotFound, axpRedirectToUnauthorized, axpReplaceEntityListRouteQueryParams, axpResolveNavigatePath, axpResolveTabKey, axpResolveTabKeyFromRouteConfig, axpResolveTabKeyFromSnapshot, axpResolveTabTitleParts, axpSaveEntityDetailsNavTrail, axpSaveEntityListReturnUrl, axpSetEntityDetailsPageQuery, axpSettingsIndexedDb, axpSplitCollapsedBreadcrumbs, axpStampEntityListMenuInstancePaths, axpStripHomeBreadcrumb, axpStripTrailingCommandlessCrumbs, axpTrimNavTrailBeforeTarget, backgroundOperationEntityFactory, buildFileStorageDownloadUrl, buildLocalEntityMetaId, buildLocalEntityStorageId, buildScopedSettingsSnapshotId, chooseSystemFiles, coerceAXPSettingRawValue, coerceAXPSettingRawValueByKind, configPlatform, createBackgroundOperationStore, createDefaultSettingValueProviders, createEmptyScopedSettingsSnapshot, createEntityCommandOptions, createEntityRowExpressionScope, createEntitySearchCommand, createFileTypeOpenContext, createIndexedDbSettingValueProviders, createMenuContext, createMenuMiddleware, createUnsavedChangesCanDeactivateGuard, downloadGraphicFromElement, evaluateEntityDisplayTemplate, extractFileExtension, extractHttpRequestErrorInfo, extractRequestErrorInfo, findManualPresetIdByOperation, formatEntityRowDisplayTitle, formatRequestError, hasFileTypeOpenHandler, hydrateSettingsValuesFromIndexedDb, inferSettingCoercionKind, isFileNameShownInViewer, isFileTypeShownInViewer, isFileViewerPreviewExtension, isOneWaySettingsIndexedDbCache, isPendingCascadeFilter, isSettingKeyCacheable, isSettingVisibleOnScope, isStoredImageFileId, normalizeAvailableLanguageCodes, normalizeDocumentFileTypePurposes, normalizeDocumentToFileListItem, normalizeOverridableAt, normalizePositiveNumberOption, normalizeRouterUrl, normalizeStoredImageRef, peelSettingValueKey, platformErrorI18nKey, primaryLanguageSubtag, printGraphicFromElement, projectSettingsValuesForIndexedDb, provideAXPUserNotificationService, provideDynamicHomePage, provideLocalEntityOfflineRuntime, provideMenuMiddleware, purgeLegacyTenantScopedSettingsSnapshots, resolveDateFilterPresets, resolveEffectiveAvailableLanguageCodes, resolveEntityCreateListBehavior, resolveEntityDetailsRouteAccess, resolveEntityModifyPopupTitle, resolveEntityRowDisplayText, resolveEntityRowDisplayTitle, resolveEntityRowDisplayTitleFromColumnContext, resolveEntitySearchTitleFallback, resolveExplorerFileDisplayName, resolveFileTypeDisplay, resolveFileViewerType, resolveGraphicElementSize, resolveGraphicPrintContent, resolveQueryMatchingDisplayText, resolveSearchDisplayTemplate, resolveSearchableText, resolveStoredImageDisplayUrl, searchInRecordValues, searchInSearchableValue, serializeSvgElement, shouldApplyDefaultMultiLanguageToEntityProperty, shouldApplyDefaultMultiLanguageToWidgetNode, shouldApplyDefaultMultiLanguageToWidgetProperty, shouldPersistSettingsInIndexedDb, stripAccidentalNestedSettingParents, throwIfCommandFailed, toAXPRequestError, toExecuteCommandFailureResult, traceSettingsAppStartupLoadComplete, traceSettingsAppStartupLoadStarted, traceSettingsClientTool, traceSettingsFlush, traceSettingsLocalPersist, traceSettingsPageLoadComplete, traceSettingsPageLoadStarted, traceSettingsPageRouteLoad, traceSettingsRetryScheduled, traceSettingsRevalidateFailed, traceSettingsServerApiCall, traceSettingsServiceLoadComplete, traceSettingsServiceLoadFailed, traceSettingsServiceLoadStarted, traceSettingsServiceReload, traceSettingsSyncFailed, triggerBrowserDownload, tryOpenFileByCatalogType, withDefaultMultiLanguageOnWidgetNodeTree, withDefaultMultiLanguageOnWidgetProperty };
|
|
14906
|
+
export { ALL_DEFAULT_OPERATORS, AXPAcorexFileTypeBridgeProvider, AXPAcorexFileTypeModule, AXPAppVersionService, AXPBackgroundOperationCompletionService, AXPBackgroundOperationEntityStoreService, AXPBackgroundOperationHandler, AXPBackgroundOperationNotificationService, AXPBackgroundOperationProvider, AXPBackgroundOperationRootConfig, AXPBackgroundOperationService, AXPBackgroundOperationSlotComponent, AXPBrowserTitleService, AXPCleanNestedFilters, AXPClipBoardService, AXPCommonModule, AXPCommonSettings, AXPCustomOperatorService, AXPCustomOperatorServiceImpl, AXPDataProvider, AXPDefaultMultiLanguageConfigService, AXPEntityDetailsNavTrailService, AXPErrorHandlerRegistryService, AXPExportService, AXPFileActionsService, AXPFileChooserSettings, AXPFileStorageEvaluatorScopeProvider, AXPFileStorageService, AXPFileTypeProviderService, AXPFilterDefaultOperatorService, AXPFilterOperatorMiddlewareService, AXPFilterOperatorMiddlewareServiceImpl, AXPFooterTextSlotComponent, AXPGlobalErrorHandler, AXPHomePageModule, AXPHomePageService, AXPIndexedDbSettingValueProvider, AXPIntegrationDomainProviderContext, AXPIntegrationDomainRegistryFacade, AXPIntegrationDomainRegistryService, AXPIntegrationOAuthService, AXPIntegrationProviderContext, AXPIntegrationProviderRegistryService, AXPIntegrationRuntimeService, AXPKeyboardShortcutsHelpComponent, AXPKeyboardShortcutsHelpService, AXPLocalEntityEngine, AXPLocalEntityIndexedDb, AXPLocalEntityOfflineRuntime, AXPLocalEntityOutboxService, AXPLocalEntityRealtimeBridge, AXPLockService, AXPLogoImageSrcResolverService, AXPMenuKeyboardShortcutService, AXPMenuMiddlewareRegistry, AXPMenuProviderService, AXPMenuRouteAccessService, AXPMenuRouteGuard, AXPMenuSearchDefinitionProvider, AXPMenuSearchProvider, AXPMenuService, AXPMenuVisibilityService, AXPNavBarSlotComponent, AXPNavigateCommand, AXPNetworkConnectivityService, AXPNotFoundCatchAllRoute, AXPNotFoundError, AXPOutputContractTranscriptSegmentService, AXPPageGetContextQuery, AXPPageRunActionCommand, AXPPlatformBrandingService, AXPPlatformDefaultConfigs, AXPPlatformSettingsLoadState, AXPPlatformSettingsOfflineBootstrapService, AXPQueryEvaluatorScopeProvider, AXPQueryParamsEvaluatorScopeProvider, AXPRegionalCalendarService, AXPSearchCommandProvider, AXPSearchDefinitionActionBuilder, AXPSearchDefinitionBuilder, AXPSearchDefinitionProviderContext, AXPSearchDefinitionProviderService, AXPSearchService, AXPSettingDefaultValuesAggregatorService, AXPSettingDefinitionGroupBuilder, AXPSettingDefinitionProviderContext, AXPSettingDefinitionProviderService, AXPSettingDefinitionSectionBuilder, AXPSettingsBulkSyncService, AXPSettingsEvaluatorScopeProvider, AXPSettingsIndexedDb, AXPSettingsService, AXPStateMessageComponent, AXPStatusDefinitionProviderService, AXPStickyDirective, AXPTabNavigationService, AXPTabPreviewService, AXPTabRouteCacheRegistry, AXPTokenDefinitionService, AXPTokenEvaluatorScopeProvider, AXPTranslateFormatter, AXPUnreachableRouteComponent, AXPUnsavedChangesConfirmService, AXPUnsavedChangesPopstateService, AXPUserNotificationService, AXPUserStatusFeatureService, AXPVersioningService, 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_BREADCRUMB_COLLAPSE_HEAD_COUNT, AXP_BREADCRUMB_COLLAPSE_TAIL_COUNT, AXP_DOCUMENT_FILE_TYPE_PURPOSES, AXP_ENTITY_LIST_DISPLAY_MODE_DEFAULT, AXP_ENTITY_VIEW_ACCESS, AXP_EXPERIENCE_SETTINGS_GROUP, AXP_EXPERIENCE_SETTINGS_SECTIONS, AXP_FILE_ACTION_PROVIDER, AXP_FILE_TYPE_INFO_PROVIDER, AXP_FILTER_DEFAULT_OPERATORS_WIDGET_NAME, AXP_FILTER_DEFAULT_OPERATOR_WIDGETS, AXP_HOME_PAGES, AXP_HOME_PAGE_DEFAULT_KEY, AXP_HOME_TAB_KEY, AXP_INTEGRATION_CONNECTION_ESTABLISHED, AXP_INTEGRATION_CONNECTION_LIFECYCLE, AXP_INTEGRATION_DOMAIN_PROVIDER, AXP_INTEGRATION_OAUTH_SERVICE, AXP_INTEGRATION_PROVIDER, AXP_INTEGRATION_RUNTIME_PROVIDER, AXP_INTEGRATION_SECRET_STORE, AXP_IN_APP_NOTIFICATION_SERVICE, AXP_IN_TAB_ERROR_FROM_PARAM, AXP_LOCALE_MANAGEMENT_PORT, AXP_LOCAL_ENTITY_BROADCAST_CHANNEL, AXP_LOCAL_ENTITY_DEFAULT_MAX_ITEMS, AXP_LOCAL_ENTITY_OUTBOX_LOCK, AXP_MENU_MIDDLEWARE, AXP_MENU_PROVIDER, AXP_NOT_FOUND_CATCH_ALL_ROUTE, AXP_NOT_FOUND_ROUTE, AXP_OUTPUT_CONTRACT_TRANSCRIPT_SEGMENT_PROVIDER, AXP_PLATFORM_BRANDING_SETTING_KEYS, AXP_PLATFORM_CONFIG_TOKEN, AXP_PROTECTED_ROUTE_GUARDS, AXP_ROOT_CONFIG_TOKEN, AXP_RUNTIME_QUERY_CACHE_DEFAULTS, AXP_SEARCH_DEFINITION_LAZY_RESOLVER, AXP_SEARCH_DEFINITION_PROVIDER, AXP_SEARCH_PROVIDER, AXP_SETTINGS_CACHE_SYNC, AXP_SETTINGS_SYNC_DEBOUNCE_MS, AXP_SETTINGS_SYNC_KEYS, AXP_SETTINGS_SYNC_RETRY_BACKOFF_MS, AXP_SETTINGS_SYNC_TTL_MS, AXP_SETTING_DEFAULT_VALUES_PROVIDERS, AXP_SETTING_DEFINITION_PROVIDER, AXP_SETTING_SCOPE_DESCENDANTS, AXP_SETTING_VALUE_PROVIDER, AXP_STATUS_PROVIDERS, AXP_TAB_BREADCRUMB_JOIN_SEPARATOR, AXP_TAB_ROUTE_DATA_KEY, AXP_TOKEN_DEFINITION_PROVIDER, AXP_UNAUTHORIZED_ROUTE, AXP_USER_NOTIFICATION_SERVICE, AXP_USER_STATUS_ENABLED_DEFAULT, AXVChangeType, AX_FILE_TYPE_METADATA_SUPPORT_BY_FILE_VIEWER, BETWEEN_OPER, BOOLEAN_OPERATORS, CONTAINS_OPER, DATE_OPERATORS, DEFAULT_DATE_FILTER_PRESETS, DEFAULT_MULTILANGUAGE_FIELD_NAMES, ENDS_WITH_OPER, ENTITY_LIST_DEFAULT_INSTANCE_ID, ENTITY_LIST_INSTANCE_QUERY_PARAM, ENVIRONMENT, EQ_OPER, FILE_VIEWER_PREVIEW_EXTENSIONS, 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, SETTINGS_TRACE_NS, SETTINGS_TRACE_TITLE, STARTS_WITH_OPER, STRING_OPERATORS, UploadFromComputerActionProvider, applyDateFilterPreset, axpBackgroundOperationCapabilities, axpBackgroundOperationDisplayTime, axpBackgroundOperationDurationMs, axpBackgroundOperationEntityName, axpBackgroundOperationEstimateEndDate, axpBackgroundOperationFailurePolicy, axpBackgroundOperationFromEntityModel, axpBackgroundOperationRemainingMs, axpBackgroundOperationStatusTextParams, axpBackgroundOperationToEntityModel, axpBeginCommandSubFlowNavigation, axpBuildBackgroundOperation, axpBuildTabKey, axpClearEntityDetailsNavTrail, axpCloneBreadcrumbItems, axpCoerceBackgroundOperationDate, axpComposeEntityDetailsNavTrail, axpCreateBackgroundOperationId, axpEndCommandSubFlowNavigation, axpEnsureEntityListInstanceQueryParam, axpEntityKeyFromDetailsPath, axpEntityListNavigationTabKey, axpFallbackTabTitle, axpFindMenuItemByRoute, axpFormatBackgroundOperationDurationMs, axpGetBreadcrumbNavigatePath, axpGetEntityDetailsNavTrail, axpGetEntityListInstanceIdFromParamMap, axpGetEntityListInstanceIdFromPath, axpGetEntityListInstanceIdFromQueryString, axpGetEntityListReturnUrl, axpGetLeafRouteSnapshot, axpHasTabScope, axpInTabErrorLocation, axpInTabErrorRetryUrl, axpInTabErrorSourceUrl, axpInTabErrorUrlTree, axpInterpolateNavigatePath, axpIsActiveBackgroundOperationStatus, axpIsBackgroundOperationI18nText, axpIsCommandSubFlowNavigationSuspended, axpIsCompletedBackgroundOperationStatus, axpIsCompletedPanelBackgroundOperation, axpIsEntityDetailsPath, axpIsEntityDetailsViewRoute, axpIsEntityListPath, axpIsEntityListRoute, axpIsEntityRecordNotFound, axpIsInProgressPanelBackgroundOperation, axpIsLikelyNetworkFailure, axpIsSameEntityDetailsRecord, axpIsTabEligibleUrl, axpIsTrayVisibleBackgroundOperation, axpJoinTabTitleSegments, axpLocalEntityCanServeListLocally, axpLocalEntityHotListRequest, axpLocalEntityIndexedDb, axpLocalEntityRecordId, axpLocalEntitySortAt, axpLocalEntitySupportsOfflineWrite, axpLocalEntityToPayload, axpMenuItemListInstanceId, axpMenuPathsMatch, axpNavigateAppPath, axpNavigateCommandDefinition, axpNormalizeEntityListInstanceId, axpOnCommandSubFlowNavigationEnded, axpPageGetContextQueryDefinition, axpPageRunActionCommandDefinition, axpParseAppPath, axpParseEntityDetailsRoute, axpParseEntityListRoute, axpPathFromSnapshot, axpPeekCommandSubFlowReturnUrl, axpPlatformSettingsAvailableGuard, axpPrepareOriginNavTrail, axpRedirectToNotFound, axpRedirectToUnauthorized, axpRedirectUnknownToNotFound, axpReplaceEntityListRouteQueryParams, axpResolveNavigatePath, axpResolveTabKey, axpResolveTabKeyFromRouteConfig, axpResolveTabKeyFromSnapshot, axpResolveTabTitleParts, axpSaveEntityDetailsNavTrail, axpSaveEntityListReturnUrl, axpSetEntityDetailsPageQuery, axpSettingsIndexedDb, axpSnapshotDeclaresTabScope, axpSplitCollapsedBreadcrumbs, axpStampEntityListMenuInstancePaths, axpStripHomeBreadcrumb, axpStripTrailingCommandlessCrumbs, axpTabScopePathFromSnapshot, axpTrimNavTrailBeforeTarget, backgroundOperationEntityFactory, buildFileStorageDownloadUrl, buildLocalEntityMetaId, buildLocalEntityStorageId, buildScopedSettingsSnapshotId, chooseSystemFiles, coerceAXPSettingRawValue, coerceAXPSettingRawValueByKind, configPlatform, createBackgroundOperationStore, createDefaultSettingValueProviders, createEmptyScopedSettingsSnapshot, createEntityCommandOptions, createEntityRowExpressionScope, createEntitySearchCommand, createFileTypeOpenContext, createIndexedDbSettingValueProviders, createMenuContext, createMenuMiddleware, createUnsavedChangesCanDeactivateGuard, downloadGraphicFromElement, evaluateEntityDisplayTemplate, extractFileExtension, extractHttpRequestErrorInfo, extractRequestErrorInfo, findFilterDefaultOperatorWidget, findManualPresetIdByOperation, formatEntityRowDisplayTitle, formatRequestError, hasFileTypeOpenHandler, hydrateSettingsValuesFromIndexedDb, inferSettingCoercionKind, isFileNameShownInViewer, isFileTypeShownInViewer, isFileViewerPreviewExtension, isOneWaySettingsIndexedDbCache, isPendingCascadeFilter, isSettingKeyCacheable, isSettingVisibleOnScope, isStoredImageFileId, normalizeAvailableLanguageCodes, normalizeDocumentFileTypePurposes, normalizeDocumentToFileListItem, normalizeOverridableAt, normalizePositiveNumberOption, normalizeRouterUrl, normalizeStoredImageRef, openSystemFileDialog, peelSettingValueKey, platformErrorI18nKey, primaryLanguageSubtag, printGraphicFromElement, projectSettingsValuesForIndexedDb, provideAXPUserNotificationService, provideDynamicHomePage, provideLocalEntityOfflineRuntime, provideMenuMiddleware, purgeLegacyTenantScopedSettingsSnapshots, readFilterDefaultOperatorMap, resolveDateFilterPresets, resolveEffectiveAvailableLanguageCodes, resolveEntityCreateListBehavior, resolveEntityDetailsRouteAccess, resolveEntityModifyPopupTitle, resolveEntityRowDisplayText, resolveEntityRowDisplayTitle, resolveEntityRowDisplayTitleFromColumnContext, resolveEntitySearchTitleFallback, resolveExplorerFileDisplayName, resolveFileTypeDisplay, resolveFileViewerType, resolveGraphicElementSize, resolveGraphicPrintContent, resolveQueryMatchingDisplayText, resolveSearchDisplayTemplate, resolveSearchableText, resolveStoredFilterDefaultOperator, resolveStoredImageDisplayUrl, searchInRecordValues, searchInSearchableValue, serializeSvgElement, shouldApplyDefaultMultiLanguageToEntityProperty, shouldApplyDefaultMultiLanguageToWidgetNode, shouldApplyDefaultMultiLanguageToWidgetProperty, shouldPersistSettingsInIndexedDb, stripAccidentalNestedSettingParents, throwIfCommandFailed, toAXPRequestError, toExecuteCommandFailureResult, traceSettingsAppStartupLoadComplete, traceSettingsAppStartupLoadStarted, traceSettingsClientTool, traceSettingsFlush, traceSettingsLocalPersist, traceSettingsPageLoadComplete, traceSettingsPageLoadStarted, traceSettingsPageRouteLoad, traceSettingsRetryScheduled, traceSettingsRevalidateFailed, traceSettingsServerApiCall, traceSettingsServiceLoadComplete, traceSettingsServiceLoadFailed, traceSettingsServiceLoadStarted, traceSettingsServiceReload, traceSettingsSyncFailed, triggerBrowserDownload, tryOpenFileByCatalogType, withDefaultMultiLanguageOnWidgetNodeTree, withDefaultMultiLanguageOnWidgetProperty, withFilterDefaultOperator, writeFilterDefaultOperatorOverride };
|
|
14058
14907
|
//# sourceMappingURL=acorex-platform-framework-client-common.mjs.map
|