@acorex-platform/framework-client 2.0.0-next.3 → 2.0.0-next.30
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 +1382 -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,272 @@ 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 queryString = query.includes('?')
|
|
3867
|
+
? (query.split('#')[0].split('?')[1] ?? '')
|
|
3868
|
+
: query.replace(/^\?/, '');
|
|
3869
|
+
const params = new URLSearchParams(queryString);
|
|
3870
|
+
params.forEach((value, key) => pushEntityListNavigationPair(pairs, key, value));
|
|
3871
|
+
return pairs;
|
|
3872
|
+
}
|
|
3873
|
+
if (typeof URLSearchParams !== 'undefined' && query instanceof URLSearchParams) {
|
|
3874
|
+
query.forEach((value, key) => pushEntityListNavigationPair(pairs, key, value));
|
|
3875
|
+
return pairs;
|
|
3876
|
+
}
|
|
3877
|
+
for (const [key, value] of Object.entries(query)) {
|
|
3878
|
+
pushEntityListNavigationPair(pairs, key, value);
|
|
3879
|
+
}
|
|
3880
|
+
return pairs;
|
|
3437
3881
|
}
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
|
|
3441
|
-
|
|
3442
|
-
|
|
3443
|
-
|
|
3882
|
+
/**
|
|
3883
|
+
* Tab and route-cache identity for an entity list.
|
|
3884
|
+
* Navigation query params (`mode=cm`, `mode=pm`, non-default `list`, …) keep each menu distinct,
|
|
3885
|
+
* the same way simple navigation does. Page-state params stay on the URL and do not split the tab.
|
|
3886
|
+
*/
|
|
3887
|
+
function axpEntityListNavigationTabKey(path, query) {
|
|
3888
|
+
const base = path.split('?')[0].split('#')[0].replace(/\/+$/, '') || '/';
|
|
3889
|
+
if (!axpIsEntityListPath(base)) {
|
|
3890
|
+
return base;
|
|
3891
|
+
}
|
|
3892
|
+
const pairs = readEntityListNavigationPairs(query);
|
|
3893
|
+
if (!pairs.length) {
|
|
3894
|
+
return base;
|
|
3895
|
+
}
|
|
3896
|
+
pairs.sort((left, right) => left[0].localeCompare(right[0]) || left[1].localeCompare(right[1]));
|
|
3897
|
+
const queryString = pairs
|
|
3898
|
+
.map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
|
|
3899
|
+
.join('&');
|
|
3900
|
+
return `${base}?${queryString}`;
|
|
3901
|
+
}
|
|
3902
|
+
//#endregion
|
|
3444
3903
|
|
|
3445
3904
|
//#endregion
|
|
3446
3905
|
/**
|
|
@@ -3458,6 +3917,10 @@ class AXPTabRouteCacheRegistry {
|
|
|
3458
3917
|
this.openKeys = new Set();
|
|
3459
3918
|
this.enabled = false;
|
|
3460
3919
|
this.deactivateGuardSuspended = false;
|
|
3920
|
+
/** While true, cached pages stay stored but must not be attached to a navigation. */
|
|
3921
|
+
this.attachSuspended = false;
|
|
3922
|
+
/** Session that owns cached pages. Null means signed out: never keep another user's components. */
|
|
3923
|
+
this.ownerKey = null;
|
|
3461
3924
|
}
|
|
3462
3925
|
//#endregion
|
|
3463
3926
|
//#region ---- Deactivate Guard Suspension ----
|
|
@@ -3481,6 +3944,35 @@ class AXPTabRouteCacheRegistry {
|
|
|
3481
3944
|
}
|
|
3482
3945
|
//#endregion
|
|
3483
3946
|
//#region ---- Mode ----
|
|
3947
|
+
/** True while a signed-in session may keep detached pages. */
|
|
3948
|
+
canCache() {
|
|
3949
|
+
return this.ownerKey !== null;
|
|
3950
|
+
}
|
|
3951
|
+
/** True while a session transition must not reattach a cached page. */
|
|
3952
|
+
isAttachSuspended() {
|
|
3953
|
+
return this.attachSuspended;
|
|
3954
|
+
}
|
|
3955
|
+
/**
|
|
3956
|
+
* Keeps cached pages for a failed switch, but blocks attaching them until the transition settles.
|
|
3957
|
+
*/
|
|
3958
|
+
suspendAttach() {
|
|
3959
|
+
this.attachSuspended = true;
|
|
3960
|
+
}
|
|
3961
|
+
/** Allows attach again. Does not destroy the stored pages. */
|
|
3962
|
+
resumeAttach() {
|
|
3963
|
+
this.attachSuspended = false;
|
|
3964
|
+
}
|
|
3965
|
+
/**
|
|
3966
|
+
* Binds cached pages to one session. A sign-out or a different user destroys every cached
|
|
3967
|
+
* component so the next session cannot reattach the previous user's data.
|
|
3968
|
+
*/
|
|
3969
|
+
bindOwner(ownerKey) {
|
|
3970
|
+
if (this.ownerKey === ownerKey) {
|
|
3971
|
+
return;
|
|
3972
|
+
}
|
|
3973
|
+
this.ownerKey = ownerKey;
|
|
3974
|
+
this.clear();
|
|
3975
|
+
}
|
|
3484
3976
|
/** Enables caching (tabular mode). Disabling clears and destroys every cached component. */
|
|
3485
3977
|
setEnabled(enabled) {
|
|
3486
3978
|
if (this.enabled === enabled) {
|
|
@@ -3502,7 +3994,11 @@ class AXPTabRouteCacheRegistry {
|
|
|
3502
3994
|
*/
|
|
3503
3995
|
syncOpenKeys(scopedKeys, activeTabUrls = []) {
|
|
3504
3996
|
const scopedSet = new Set(scopedKeys);
|
|
3505
|
-
const activePathSet = new Set(activeTabUrls.
|
|
3997
|
+
const activePathSet = new Set(activeTabUrls.flatMap((url) => {
|
|
3998
|
+
const pathOnly = url.split('?')[0].split('#')[0].replace(/\/+$/, '') || '/';
|
|
3999
|
+
const withNavigation = axpEntityListNavigationTabKey(pathOnly, url);
|
|
4000
|
+
return withNavigation === pathOnly ? [pathOnly] : [pathOnly, withNavigation];
|
|
4001
|
+
}));
|
|
3506
4002
|
for (const cacheKey of [...this.handles.keys()]) {
|
|
3507
4003
|
const normalizedCacheKey = cacheKey.replace(/\/+$/, '') || '/';
|
|
3508
4004
|
const keepsScopedKey = scopedSet.has(normalizedCacheKey);
|
|
@@ -3544,6 +4040,10 @@ class AXPTabRouteCacheRegistry {
|
|
|
3544
4040
|
* destroying the old one would destroy the live component and corrupt the view (NG0922).
|
|
3545
4041
|
*/
|
|
3546
4042
|
store(key, handle) {
|
|
4043
|
+
if (!this.ownerKey) {
|
|
4044
|
+
this.destroyHandle(handle);
|
|
4045
|
+
return;
|
|
4046
|
+
}
|
|
3547
4047
|
this.handles.set(key, handle);
|
|
3548
4048
|
}
|
|
3549
4049
|
/**
|
|
@@ -3553,6 +4053,9 @@ class AXPTabRouteCacheRegistry {
|
|
|
3553
4053
|
* `store(snapshot, null)`.
|
|
3554
4054
|
*/
|
|
3555
4055
|
retrieve(key) {
|
|
4056
|
+
if (this.attachSuspended) {
|
|
4057
|
+
return null;
|
|
4058
|
+
}
|
|
3556
4059
|
return this.handles.get(key) ?? null;
|
|
3557
4060
|
}
|
|
3558
4061
|
/**
|
|
@@ -3832,6 +4335,7 @@ const AXP_EXPERIENCE_SETTINGS_SECTIONS = {
|
|
|
3832
4335
|
Performance: 'performance',
|
|
3833
4336
|
Entity: 'entity',
|
|
3834
4337
|
Offline: 'offline',
|
|
4338
|
+
Presence: 'presence',
|
|
3835
4339
|
FileChooser: 'file-chooser',
|
|
3836
4340
|
};
|
|
3837
4341
|
//#endregion
|
|
@@ -5329,7 +5833,7 @@ class AXPBackgroundOperationSlotComponent {
|
|
|
5329
5833
|
this.popover()?.close();
|
|
5330
5834
|
}
|
|
5331
5835
|
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 }); }
|
|
5836
|
+
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
5837
|
}
|
|
5334
5838
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPBackgroundOperationSlotComponent, decorators: [{
|
|
5335
5839
|
type: Component,
|
|
@@ -5600,148 +6104,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
|
|
|
5600
6104
|
args: [{ providedIn: 'root' }]
|
|
5601
6105
|
}] });
|
|
5602
6106
|
|
|
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
6107
|
//#region ----- Menu instance identity -----
|
|
5746
6108
|
/** Prefers the stamped `list` query param, then the menu item name. */
|
|
5747
6109
|
function axpMenuItemListInstanceId(item) {
|
|
@@ -7051,6 +7413,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
|
|
|
7051
7413
|
function axpMenuItemNeedsUpdate(item, updates) {
|
|
7052
7414
|
return Object.keys(updates).some((key) => item[key] !== updates[key]);
|
|
7053
7415
|
}
|
|
7416
|
+
/** True when the menu command itself navigates. A path on the same item must not navigate again. */
|
|
7417
|
+
function axpMenuCommandIsNavigation(command) {
|
|
7418
|
+
if (!command || typeof command !== 'object') {
|
|
7419
|
+
return false;
|
|
7420
|
+
}
|
|
7421
|
+
if ('type' in command && command.type === 'router') {
|
|
7422
|
+
return true;
|
|
7423
|
+
}
|
|
7424
|
+
return 'name' in command && command.name === 'navigate';
|
|
7425
|
+
}
|
|
7054
7426
|
/** Immutably patches the first menu item with the given name. */
|
|
7055
7427
|
function axpUpdateMenuItemByName(items, name, updates) {
|
|
7056
7428
|
let changed = false;
|
|
@@ -7124,18 +7496,22 @@ withMethods((store, router = inject(Router), commandService = inject(AXPCommandS
|
|
|
7124
7496
|
});
|
|
7125
7497
|
},
|
|
7126
7498
|
executeCommand(item) {
|
|
7127
|
-
|
|
7128
|
-
|
|
7499
|
+
const path = item.path
|
|
7500
|
+
? item.name
|
|
7129
7501
|
? axpEnsureEntityListInstanceQueryParam(item.path, item.name)
|
|
7130
|
-
: item.path
|
|
7502
|
+
: item.path
|
|
7503
|
+
: undefined;
|
|
7504
|
+
if (path) {
|
|
7131
7505
|
void commandService.execute('navigate', { path });
|
|
7132
7506
|
patchState(store, { selectedMenuItem: { item, isFullMatch: true } });
|
|
7133
7507
|
}
|
|
7134
|
-
|
|
7135
|
-
|
|
7136
|
-
|
|
7137
|
-
|
|
7138
|
-
|
|
7508
|
+
const command = item.command;
|
|
7509
|
+
if (!command || (path && axpMenuCommandIsNavigation(command))) {
|
|
7510
|
+
return;
|
|
7511
|
+
}
|
|
7512
|
+
const executeCommand = command;
|
|
7513
|
+
if (executeCommand.name && commandService.exists(executeCommand.name)) {
|
|
7514
|
+
void commandService.execute(executeCommand.name, executeCommand.options ?? {});
|
|
7139
7515
|
}
|
|
7140
7516
|
},
|
|
7141
7517
|
isItemOpen(item) {
|
|
@@ -7248,13 +7624,14 @@ withMethods((store, router = inject(Router), commandService = inject(AXPCommandS
|
|
|
7248
7624
|
const IGNORED_QUERY_KEYS = new Set(['_ts']);
|
|
7249
7625
|
/**
|
|
7250
7626
|
* Builds a stable tab key from a router URL.
|
|
7251
|
-
* Path segments define
|
|
7252
|
-
*
|
|
7627
|
+
* Path segments define the page. Entity-list navigation query params (`mode`, `list`, …)
|
|
7628
|
+
* stay in the key so two menus of the same entity stay distinct, matching simple navigation.
|
|
7629
|
+
* Page-state params (`view`, `filters`, `_ts`) stay on the URL and do not open another tab.
|
|
7253
7630
|
*/
|
|
7254
7631
|
function axpBuildTabKey(url) {
|
|
7255
7632
|
const normalized = normalizeRouterUrl(url);
|
|
7256
7633
|
const pathOnly = normalized.split('?')[0].split('#')[0];
|
|
7257
|
-
return pathOnly;
|
|
7634
|
+
return axpEntityListNavigationTabKey(pathOnly, normalized);
|
|
7258
7635
|
}
|
|
7259
7636
|
/** Normalizes a router URL for storage and navigation. */
|
|
7260
7637
|
function normalizeRouterUrl(url) {
|
|
@@ -7299,6 +7676,73 @@ function axpFallbackTabTitle(url) {
|
|
|
7299
7676
|
}
|
|
7300
7677
|
//#endregion
|
|
7301
7678
|
|
|
7679
|
+
//#region ---- Constants ----
|
|
7680
|
+
/** Query param that keeps the failed route as the tab identity. */
|
|
7681
|
+
const AXP_IN_TAB_ERROR_FROM_PARAM = 'from';
|
|
7682
|
+
const IN_TAB_ERROR_PATHS = new Set(['/error/401', '/error/404', '/error/offline', '/401', '/404']);
|
|
7683
|
+
//#endregion
|
|
7684
|
+
//#region ---- URL Helpers ----
|
|
7685
|
+
/**
|
|
7686
|
+
* Original page URL carried on an in-shell error route.
|
|
7687
|
+
* Null when the URL is not an error page or `from` is missing.
|
|
7688
|
+
*/
|
|
7689
|
+
function axpInTabErrorSourceUrl(url) {
|
|
7690
|
+
const normalized = normalizeRouterUrl(url);
|
|
7691
|
+
const pathOnly = normalized.split('?')[0].split('#')[0];
|
|
7692
|
+
if (!IN_TAB_ERROR_PATHS.has(pathOnly)) {
|
|
7693
|
+
return null;
|
|
7694
|
+
}
|
|
7695
|
+
const params = new URLSearchParams(normalized.split('?')[1] ?? '');
|
|
7696
|
+
const from = params.get(AXP_IN_TAB_ERROR_FROM_PARAM);
|
|
7697
|
+
if (!from?.startsWith('/')) {
|
|
7698
|
+
return null;
|
|
7699
|
+
}
|
|
7700
|
+
const fromPath = from.split('?')[0].split('#')[0];
|
|
7701
|
+
if (IN_TAB_ERROR_PATHS.has(fromPath)) {
|
|
7702
|
+
return null;
|
|
7703
|
+
}
|
|
7704
|
+
return normalizeRouterUrl(from);
|
|
7705
|
+
}
|
|
7706
|
+
/** URL a tab should open again. An in-tab error page retries its `from` route. */
|
|
7707
|
+
function axpInTabErrorRetryUrl(url) {
|
|
7708
|
+
return axpInTabErrorSourceUrl(url) ?? normalizeRouterUrl(url);
|
|
7709
|
+
}
|
|
7710
|
+
/** In-shell error URL whose tab identity is `sourceUrl`, not a single app-wide error route. */
|
|
7711
|
+
function axpInTabErrorLocation(kind, sourceUrl) {
|
|
7712
|
+
const path = inTabErrorPath(kind);
|
|
7713
|
+
const source = axpInTabErrorSourceUrl(sourceUrl) ?? sourceUrl;
|
|
7714
|
+
const params = new URLSearchParams();
|
|
7715
|
+
params.set(AXP_IN_TAB_ERROR_FROM_PARAM, source);
|
|
7716
|
+
return `${path}?${params.toString()}`;
|
|
7717
|
+
}
|
|
7718
|
+
/** UrlTree for guards and resolvers. The shell stays mounted; the tab key stays the source route. */
|
|
7719
|
+
function axpInTabErrorUrlTree(router, kind, sourceUrl) {
|
|
7720
|
+
return router.parseUrl(axpInTabErrorLocation(kind, sourceUrl));
|
|
7721
|
+
}
|
|
7722
|
+
//#endregion
|
|
7723
|
+
//#region ---- Unknown Route Guard ----
|
|
7724
|
+
/**
|
|
7725
|
+
* Sends an unmatched URL to the in-shell 404 without dropping the root layout.
|
|
7726
|
+
* `from` keeps that URL as its own tab.
|
|
7727
|
+
*/
|
|
7728
|
+
const axpRedirectUnknownToNotFound = (_route, state) => {
|
|
7729
|
+
const router = inject(Router);
|
|
7730
|
+
return axpInTabErrorUrlTree(router, '404', state.url);
|
|
7731
|
+
};
|
|
7732
|
+
//#endregion
|
|
7733
|
+
//#region ---- Helpers ----
|
|
7734
|
+
function inTabErrorPath(kind) {
|
|
7735
|
+
switch (kind) {
|
|
7736
|
+
case '401':
|
|
7737
|
+
return '/error/401';
|
|
7738
|
+
case '404':
|
|
7739
|
+
return '/error/404';
|
|
7740
|
+
case 'offline':
|
|
7741
|
+
return '/error/offline';
|
|
7742
|
+
}
|
|
7743
|
+
}
|
|
7744
|
+
//#endregion
|
|
7745
|
+
|
|
7302
7746
|
//#region ---- Types ----
|
|
7303
7747
|
const AXP_HOME_TAB_KEY = 'axp:home';
|
|
7304
7748
|
/** Route `data` key for tab-navigation behavior (`data.tab`). */
|
|
@@ -7306,9 +7750,20 @@ const AXP_TAB_ROUTE_DATA_KEY = 'tab';
|
|
|
7306
7750
|
//#endregion
|
|
7307
7751
|
|
|
7308
7752
|
//#region ---- Route Data ----
|
|
7309
|
-
/**
|
|
7753
|
+
/**
|
|
7754
|
+
* True when this route node itself sets `data.tab = true`.
|
|
7755
|
+
* Pass the route's own config data. Activated snapshots merge parent `data`, so `snapshot.data`
|
|
7756
|
+
* is not a declaration — a child would look like its own tab root.
|
|
7757
|
+
*/
|
|
7310
7758
|
function axpHasTabScope(data) {
|
|
7311
|
-
|
|
7759
|
+
if (!data) {
|
|
7760
|
+
return false;
|
|
7761
|
+
}
|
|
7762
|
+
return data[AXP_TAB_ROUTE_DATA_KEY] === true;
|
|
7763
|
+
}
|
|
7764
|
+
/** True when this snapshot's route config declares `data.tab`, ignoring data inherited from parents. */
|
|
7765
|
+
function axpSnapshotDeclaresTabScope(snapshot) {
|
|
7766
|
+
return axpHasTabScope(snapshot.routeConfig?.data);
|
|
7312
7767
|
}
|
|
7313
7768
|
//#endregion
|
|
7314
7769
|
//#region ---- Snapshot Resolution ----
|
|
@@ -7328,19 +7783,27 @@ function axpGetLeafRouteSnapshot(root) {
|
|
|
7328
7783
|
return route;
|
|
7329
7784
|
}
|
|
7330
7785
|
/**
|
|
7331
|
-
*
|
|
7332
|
-
* Uses the nearest ancestor
|
|
7786
|
+
* Path identity from an activated route tree, without query params.
|
|
7787
|
+
* Uses the nearest ancestor whose own route config sets `data.tab = true`; otherwise the leaf path.
|
|
7788
|
+
* Child navigations under that boundary (settings groups, profile sections) stay on the same tab.
|
|
7333
7789
|
*/
|
|
7334
|
-
function
|
|
7790
|
+
function axpTabScopePathFromSnapshot(leaf) {
|
|
7335
7791
|
const chain = leaf.pathFromRoot;
|
|
7336
7792
|
for (let index = chain.length - 1; index >= 0; index--) {
|
|
7337
7793
|
const snapshot = chain[index];
|
|
7338
|
-
if (
|
|
7794
|
+
if (axpSnapshotDeclaresTabScope(snapshot)) {
|
|
7339
7795
|
return axpPathFromSnapshot(snapshot);
|
|
7340
7796
|
}
|
|
7341
7797
|
}
|
|
7342
7798
|
return axpPathFromSnapshot(leaf);
|
|
7343
7799
|
}
|
|
7800
|
+
/**
|
|
7801
|
+
* Resolves the tab dedupe key from an activated route tree.
|
|
7802
|
+
* Entity list routes also include navigation query params (`mode`, `list`, …), same as simple navigation.
|
|
7803
|
+
*/
|
|
7804
|
+
function axpResolveTabKeyFromSnapshot(leaf) {
|
|
7805
|
+
return axpEntityListNavigationTabKey(axpTabScopePathFromSnapshot(leaf), leaf.queryParams);
|
|
7806
|
+
}
|
|
7344
7807
|
//#endregion
|
|
7345
7808
|
//#region ---- Router / URL Resolution ----
|
|
7346
7809
|
/**
|
|
@@ -7349,17 +7812,29 @@ function axpResolveTabKeyFromSnapshot(leaf) {
|
|
|
7349
7812
|
*/
|
|
7350
7813
|
function axpResolveTabKey(router, url) {
|
|
7351
7814
|
const normalized = normalizeRouterUrl(url ?? router.url);
|
|
7815
|
+
const errorSource = axpInTabErrorSourceUrl(normalized);
|
|
7816
|
+
if (errorSource) {
|
|
7817
|
+
return axpResolveTabKey(router, errorSource);
|
|
7818
|
+
}
|
|
7352
7819
|
const pathOnly = normalized.split('?')[0].split('#')[0];
|
|
7820
|
+
// Entity lists key off the URL itself. Route-config matching and the live snapshot can
|
|
7821
|
+
// disagree until navigation finishes, and that split was opening a second tab for one click.
|
|
7822
|
+
// `mode` and `list` stay in the key; `view`, `layout`, `filters`, and `_ts` do not.
|
|
7823
|
+
if (axpIsEntityListPath(pathOnly)) {
|
|
7824
|
+
return axpEntityListNavigationTabKey(pathOnly, normalized);
|
|
7825
|
+
}
|
|
7353
7826
|
const currentPath = normalizeRouterUrl(router.url).split('?')[0].split('#')[0];
|
|
7827
|
+
let basePath;
|
|
7354
7828
|
if (!url || pathOnly === currentPath) {
|
|
7355
7829
|
const leaf = axpGetLeafRouteSnapshot(router.routerState.snapshot.root);
|
|
7356
|
-
|
|
7830
|
+
basePath = axpTabScopePathFromSnapshot(leaf);
|
|
7357
7831
|
}
|
|
7358
|
-
|
|
7359
|
-
|
|
7360
|
-
|
|
7832
|
+
else {
|
|
7833
|
+
basePath =
|
|
7834
|
+
axpResolveTabKeyFromRouteConfig(router.config, pathOnly) ??
|
|
7835
|
+
(pathOnly.startsWith('/') ? pathOnly : `/${pathOnly}`);
|
|
7361
7836
|
}
|
|
7362
|
-
return
|
|
7837
|
+
return axpEntityListNavigationTabKey(basePath, normalized);
|
|
7363
7838
|
}
|
|
7364
7839
|
/**
|
|
7365
7840
|
* Matches a URL path against registered routes and resolves the tab key from `data.tab`.
|
|
@@ -7367,19 +7842,19 @@ function axpResolveTabKey(router, url) {
|
|
|
7367
7842
|
function axpResolveTabKeyFromRouteConfig(routes, pathOnly) {
|
|
7368
7843
|
const normalized = pathOnly.startsWith('/') ? pathOnly.replace(/\/+$/, '') || '/' : `/${pathOnly}`.replace(/\/+$/, '') || '/';
|
|
7369
7844
|
const segments = normalized === '/' ? [] : normalized.split('/').filter(Boolean);
|
|
7370
|
-
const chain = matchRouteConfigChain(routes, segments, 0,
|
|
7845
|
+
const chain = matchRouteConfigChain(routes, segments, 0, []);
|
|
7371
7846
|
if (!chain?.length) {
|
|
7372
7847
|
return null;
|
|
7373
7848
|
}
|
|
7374
7849
|
for (let index = chain.length - 1; index >= 0; index--) {
|
|
7375
7850
|
const level = chain[index];
|
|
7376
|
-
if (axpHasTabScope(level.
|
|
7851
|
+
if (axpHasTabScope(level.ownData)) {
|
|
7377
7852
|
return level.path;
|
|
7378
7853
|
}
|
|
7379
7854
|
}
|
|
7380
7855
|
return chain[chain.length - 1]?.path ?? null;
|
|
7381
7856
|
}
|
|
7382
|
-
function matchRouteConfigChain(routes, segments, startIndex,
|
|
7857
|
+
function matchRouteConfigChain(routes, segments, startIndex, chain) {
|
|
7383
7858
|
let bestMatch = null;
|
|
7384
7859
|
for (const route of routes) {
|
|
7385
7860
|
if (route.redirectTo) {
|
|
@@ -7391,12 +7866,12 @@ function matchRouteConfigChain(routes, segments, startIndex, inheritedData, chai
|
|
|
7391
7866
|
}
|
|
7392
7867
|
const pathSegments = [...segments.slice(0, startIndex), ...segments.slice(startIndex, startIndex + segmentMatch.consumed)];
|
|
7393
7868
|
const path = pathSegments.length ? `/${pathSegments.join('/')}` : '/';
|
|
7394
|
-
const
|
|
7395
|
-
const level = {
|
|
7869
|
+
const ownData = { ...(route.data ?? {}) };
|
|
7870
|
+
const level = { ownData, path };
|
|
7396
7871
|
const nextChain = [...chain, level];
|
|
7397
7872
|
const nextIndex = startIndex + segmentMatch.consumed;
|
|
7398
7873
|
if (route.children?.length) {
|
|
7399
|
-
const childMatch = matchRouteConfigChain(route.children, segments, nextIndex,
|
|
7874
|
+
const childMatch = matchRouteConfigChain(route.children, segments, nextIndex, nextChain);
|
|
7400
7875
|
if (childMatch && (!bestMatch || childMatch.length > bestMatch.length)) {
|
|
7401
7876
|
bestMatch = childMatch;
|
|
7402
7877
|
}
|
|
@@ -7552,6 +8027,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
|
|
|
7552
8027
|
//#region ----- Command sub-flow navigation (session) -----
|
|
7553
8028
|
const SUPPRESS_TAB_RESTORE_KEY = 'axp:command-subflow-suppress-tab-restore';
|
|
7554
8029
|
const RETURN_URL_KEY = 'axp:command-subflow-return-url';
|
|
8030
|
+
const subFlowEndedListeners = new Set();
|
|
8031
|
+
/** Fires after a command sub-flow releases tab restore. Returns an unsubscribe function. */
|
|
8032
|
+
function axpOnCommandSubFlowNavigationEnded(listener) {
|
|
8033
|
+
subFlowEndedListeners.add(listener);
|
|
8034
|
+
return () => {
|
|
8035
|
+
subFlowEndedListeners.delete(listener);
|
|
8036
|
+
};
|
|
8037
|
+
}
|
|
7555
8038
|
/** Marks an in-flight command sub-flow (file picker, crop, dialog) so tab restore must not hijack the route. */
|
|
7556
8039
|
function axpBeginCommandSubFlowNavigation(returnUrl) {
|
|
7557
8040
|
const trimmed = returnUrl.trim();
|
|
@@ -7591,11 +8074,14 @@ function axpEndCommandSubFlowNavigation() {
|
|
|
7591
8074
|
catch {
|
|
7592
8075
|
// sessionStorage may be unavailable
|
|
7593
8076
|
}
|
|
8077
|
+
for (const listener of subFlowEndedListeners) {
|
|
8078
|
+
listener();
|
|
8079
|
+
}
|
|
7594
8080
|
}
|
|
7595
8081
|
//#endregion
|
|
7596
8082
|
|
|
7597
8083
|
//#region ---- Tab Route Policy ----
|
|
7598
|
-
const EXCLUDED_PATH_PREFIXES = ['/auth'
|
|
8084
|
+
const EXCLUDED_PATH_PREFIXES = ['/auth'];
|
|
7599
8085
|
const EXCLUDED_PATH_PATTERNS = [
|
|
7600
8086
|
/^\/assessment\/fill\//i,
|
|
7601
8087
|
/^\/forms\//i,
|
|
@@ -7647,7 +8133,28 @@ class AXPTabNavigationService {
|
|
|
7647
8133
|
...(ngDevMode ? [{ debugName: "tabs" }] : /* istanbul ignore next */ []));
|
|
7648
8134
|
this.activeTabKey = signal(null, /* @ts-ignore */
|
|
7649
8135
|
...(ngDevMode ? [{ debugName: "activeTabKey" }] : /* istanbul ignore next */ []));
|
|
7650
|
-
|
|
8136
|
+
/** Nested navigations (menu + route `replaceUrl`) must not clear each other's sync guard. */
|
|
8137
|
+
this.navigationSyncSuppressDepth = 0;
|
|
8138
|
+
this.navigationSettingsChain = Promise.resolve();
|
|
8139
|
+
/** `tenantId:applicationId:userId` while the tab strip belongs to that session. Null after sign-out. */
|
|
8140
|
+
this.tabsOwnerStamp = null;
|
|
8141
|
+
/** False until the first session emission. A later stamp change must not restore saved tabs. */
|
|
8142
|
+
this.sessionTabsBound = false;
|
|
8143
|
+
/** Set when the user actually signs out, so the next sign-in does not reopen that list. */
|
|
8144
|
+
this.closedTabsForSessionChange = false;
|
|
8145
|
+
/** Storage key of the session that owned the strip. Survives sign-out, when the user id is already gone. */
|
|
8146
|
+
this.ownedStorageKey = null;
|
|
8147
|
+
/**
|
|
8148
|
+
* Stamp captured when readiness dropped but the user was still signed in.
|
|
8149
|
+
* A failed tenant/application switch returns to this stamp and must keep the saved list.
|
|
8150
|
+
*/
|
|
8151
|
+
this.pendingReadinessGap = null;
|
|
8152
|
+
/** False until the saved list has been read, so an earlier route sync cannot overwrite it. */
|
|
8153
|
+
this.sessionTabsHydrated = false;
|
|
8154
|
+
/** Restore was skipped because a command sub-flow was open. Resumed when that sub-flow ends. */
|
|
8155
|
+
this.restoreDeferred = false;
|
|
8156
|
+
/** Incremented on every session emission. A queued task stops when a newer emission exists. */
|
|
8157
|
+
this.sessionSyncGeneration = 0;
|
|
7651
8158
|
//#endregion
|
|
7652
8159
|
//#region ---- Computed ----
|
|
7653
8160
|
/** Effective mode: mobile always uses simple navigation. */
|
|
@@ -7667,6 +8174,10 @@ class AXPTabNavigationService {
|
|
|
7667
8174
|
this.bindSettingsChanges();
|
|
7668
8175
|
this.bindSessionChanges();
|
|
7669
8176
|
this.bindRouteCacheSync();
|
|
8177
|
+
const stopSubFlowWatch = axpOnCommandSubFlowNavigationEnded(() => {
|
|
8178
|
+
void this.enqueueNavigationSettingsSync(() => this.resumeDeferredTabRestore());
|
|
8179
|
+
});
|
|
8180
|
+
this.destroyRef.onDestroy(stopSubFlowWatch);
|
|
7670
8181
|
}
|
|
7671
8182
|
//#endregion
|
|
7672
8183
|
//#region ---- Public API ----
|
|
@@ -7706,10 +8217,9 @@ class AXPTabNavigationService {
|
|
|
7706
8217
|
return;
|
|
7707
8218
|
}
|
|
7708
8219
|
// Navigate first so scoped tab keys (`data.tab`) resolve from the live route snapshot.
|
|
7709
|
-
this.
|
|
7710
|
-
try {
|
|
8220
|
+
await this.runWithSuppressedNavigationSync(async () => {
|
|
7711
8221
|
await axpNavigateAppPath(this.router, normalizedUrl);
|
|
7712
|
-
const key = axpResolveTabKey(this.router);
|
|
8222
|
+
const key = axpResolveTabKey(this.router, normalizedUrl);
|
|
7713
8223
|
const postNavExisting = this.findTabForUrl(normalizedUrl);
|
|
7714
8224
|
if (postNavExisting) {
|
|
7715
8225
|
if (postNavExisting.key !== key) {
|
|
@@ -7724,10 +8234,7 @@ class AXPTabNavigationService {
|
|
|
7724
8234
|
this.insertTab(this.createTabDescriptor(normalizedUrl, title, key));
|
|
7725
8235
|
this.activeTabKey.set(key);
|
|
7726
8236
|
this.persistSession();
|
|
7727
|
-
}
|
|
7728
|
-
finally {
|
|
7729
|
-
this.suppressNavigationSync = false;
|
|
7730
|
-
}
|
|
8237
|
+
});
|
|
7731
8238
|
}
|
|
7732
8239
|
/**
|
|
7733
8240
|
* Activates a tab and navigates to its URL. The previously active page is detached and cached by
|
|
@@ -7742,18 +8249,15 @@ class AXPTabNavigationService {
|
|
|
7742
8249
|
if (!tab) {
|
|
7743
8250
|
return;
|
|
7744
8251
|
}
|
|
7745
|
-
const targetUrl = urlOverride ?? tab.url;
|
|
8252
|
+
const targetUrl = axpInTabErrorRetryUrl(urlOverride ?? tab.url);
|
|
8253
|
+
if (tab.url !== targetUrl) {
|
|
8254
|
+
this.updateTabUrl(key, targetUrl);
|
|
8255
|
+
}
|
|
7746
8256
|
this.touchTab(key);
|
|
7747
8257
|
this.activeTabKey.set(key);
|
|
7748
8258
|
this.persistSession();
|
|
7749
8259
|
const navigate = async () => {
|
|
7750
|
-
this.
|
|
7751
|
-
try {
|
|
7752
|
-
await this.router.navigateByUrl(targetUrl);
|
|
7753
|
-
}
|
|
7754
|
-
finally {
|
|
7755
|
-
this.suppressNavigationSync = false;
|
|
7756
|
-
}
|
|
8260
|
+
await this.runWithSuppressedNavigationSync(() => this.router.navigateByUrl(targetUrl));
|
|
7757
8261
|
};
|
|
7758
8262
|
if (suspendDeactivateGuard) {
|
|
7759
8263
|
await this.routeCacheRegistry.runWithSuspendedDeactivateGuards(navigate);
|
|
@@ -7765,6 +8269,24 @@ class AXPTabNavigationService {
|
|
|
7765
8269
|
async closeTab(key) {
|
|
7766
8270
|
await this.closeTabs([key]);
|
|
7767
8271
|
}
|
|
8272
|
+
/**
|
|
8273
|
+
* Drops the active error tab without opening a neighbor.
|
|
8274
|
+
* Home stays. The caller navigates to the real home page afterwards.
|
|
8275
|
+
*/
|
|
8276
|
+
closeActiveErrorTab() {
|
|
8277
|
+
if (this.effectiveMode() !== 'tabular') {
|
|
8278
|
+
return;
|
|
8279
|
+
}
|
|
8280
|
+
const activeKey = this.activeTabKey();
|
|
8281
|
+
const tab = this.tabs().find((item) => item.key === activeKey);
|
|
8282
|
+
if (!tab || tab.key === AXP_HOME_TAB_KEY) {
|
|
8283
|
+
return;
|
|
8284
|
+
}
|
|
8285
|
+
this.tabs.update((items) => items.filter((item) => item.key !== tab.key));
|
|
8286
|
+
this.tabPreviewService.clearPreviews(new Set([tab.key]));
|
|
8287
|
+
this.activeTabKey.set(AXP_HOME_TAB_KEY);
|
|
8288
|
+
this.persistSession();
|
|
8289
|
+
}
|
|
7768
8290
|
/** Closes every closable tab except the given key (pinned tabs are kept). */
|
|
7769
8291
|
async closeOtherTabs(keepKey) {
|
|
7770
8292
|
const keys = this.tabs()
|
|
@@ -7946,15 +8468,9 @@ class AXPTabNavigationService {
|
|
|
7946
8468
|
targetKey = home.key;
|
|
7947
8469
|
targetUrl = home.url;
|
|
7948
8470
|
}
|
|
7949
|
-
this.
|
|
7950
|
-
|
|
7951
|
-
|
|
7952
|
-
if (!navigated) {
|
|
7953
|
-
return false;
|
|
7954
|
-
}
|
|
7955
|
-
}
|
|
7956
|
-
finally {
|
|
7957
|
-
this.suppressNavigationSync = false;
|
|
8471
|
+
const navigated = await this.runWithSuppressedNavigationSync(() => this.router.navigateByUrl(targetUrl));
|
|
8472
|
+
if (!navigated) {
|
|
8473
|
+
return false;
|
|
7958
8474
|
}
|
|
7959
8475
|
this.activeTabKey.set(targetKey);
|
|
7960
8476
|
this.touchTab(targetKey);
|
|
@@ -7984,6 +8500,16 @@ class AXPTabNavigationService {
|
|
|
7984
8500
|
}
|
|
7985
8501
|
//#endregion
|
|
7986
8502
|
//#region ---- Router Sync ----
|
|
8503
|
+
/** Holds route-sync until every nested navigation in this turn has finished. */
|
|
8504
|
+
async runWithSuppressedNavigationSync(action) {
|
|
8505
|
+
this.navigationSyncSuppressDepth += 1;
|
|
8506
|
+
try {
|
|
8507
|
+
return await action();
|
|
8508
|
+
}
|
|
8509
|
+
finally {
|
|
8510
|
+
this.navigationSyncSuppressDepth = Math.max(0, this.navigationSyncSuppressDepth - 1);
|
|
8511
|
+
}
|
|
8512
|
+
}
|
|
7987
8513
|
bindRouterEvents() {
|
|
7988
8514
|
this.router.events
|
|
7989
8515
|
.pipe(filter$1((event) => event instanceof NavigationEnd), takeUntilDestroyed(this.destroyRef))
|
|
@@ -7992,7 +8518,7 @@ class AXPTabNavigationService {
|
|
|
7992
8518
|
});
|
|
7993
8519
|
}
|
|
7994
8520
|
async handleNavigationEnd(url) {
|
|
7995
|
-
if (this.
|
|
8521
|
+
if (this.navigationSyncSuppressDepth > 0) {
|
|
7996
8522
|
return;
|
|
7997
8523
|
}
|
|
7998
8524
|
if (this.effectiveMode() === 'simple') {
|
|
@@ -8003,67 +8529,288 @@ class AXPTabNavigationService {
|
|
|
8003
8529
|
return;
|
|
8004
8530
|
}
|
|
8005
8531
|
await this.ensureHomeTab();
|
|
8006
|
-
|
|
8007
|
-
|
|
8532
|
+
const errorSource = axpInTabErrorSourceUrl(normalizedUrl);
|
|
8533
|
+
const storedUrl = errorSource ?? normalizedUrl;
|
|
8534
|
+
if (this.isHomePageUrl(storedUrl)) {
|
|
8535
|
+
this.activateHomeTabForUrl(storedUrl);
|
|
8008
8536
|
return;
|
|
8009
8537
|
}
|
|
8010
|
-
const key = axpResolveTabKey(this.router,
|
|
8011
|
-
const existing = this.findTabForUrl(
|
|
8538
|
+
const key = axpResolveTabKey(this.router, storedUrl);
|
|
8539
|
+
const existing = this.findTabForUrl(storedUrl);
|
|
8012
8540
|
if (existing) {
|
|
8013
8541
|
if (existing.key !== key) {
|
|
8014
8542
|
this.migrateTabKey(existing.key, key);
|
|
8015
8543
|
}
|
|
8016
|
-
this.updateTabUrl(key,
|
|
8544
|
+
this.updateTabUrl(key, storedUrl);
|
|
8017
8545
|
this.touchTab(key);
|
|
8018
8546
|
this.activeTabKey.set(key);
|
|
8019
8547
|
this.persistSession();
|
|
8020
8548
|
return;
|
|
8021
8549
|
}
|
|
8022
|
-
this.insertTab(this.createTabDescriptor(
|
|
8550
|
+
this.insertTab(this.createTabDescriptor(storedUrl, errorSource ? this.resolveTabTitle(errorSource) : undefined, key));
|
|
8023
8551
|
this.activeTabKey.set(key);
|
|
8024
8552
|
this.persistSession();
|
|
8025
8553
|
}
|
|
8026
8554
|
//#endregion
|
|
8027
8555
|
//#region ---- Settings ----
|
|
8028
8556
|
async loadSettings() {
|
|
8029
|
-
|
|
8030
|
-
this.settingsService.get(AXPCommonSettings.
|
|
8031
|
-
this.
|
|
8032
|
-
|
|
8033
|
-
|
|
8034
|
-
this.maxOpenTabs.set(this.normalizeMaxTabs(maxTabs));
|
|
8035
|
-
await this.restoreSessionIfNeeded();
|
|
8557
|
+
await this.enqueueNavigationSettingsSync(async () => {
|
|
8558
|
+
const maxTabs = await this.settingsService.get(AXPCommonSettings.MaxOpenTabs);
|
|
8559
|
+
this.maxOpenTabs.set(this.normalizeMaxTabs(maxTabs));
|
|
8560
|
+
await this.applyEffectiveNavigationMode();
|
|
8561
|
+
});
|
|
8036
8562
|
}
|
|
8037
8563
|
bindSettingsChanges() {
|
|
8564
|
+
this.settingsService.onLoaded.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(() => {
|
|
8565
|
+
void this.enqueueNavigationSettingsSync(() => this.applyEffectiveNavigationMode());
|
|
8566
|
+
});
|
|
8038
8567
|
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();
|
|
8568
|
+
const modeChanged = event.keys.includes(AXPCommonSettings.NavigationMode);
|
|
8569
|
+
const maxChanged = event.keys.includes(AXPCommonSettings.MaxOpenTabs);
|
|
8570
|
+
if (!modeChanged && !maxChanged) {
|
|
8571
|
+
return;
|
|
8056
8572
|
}
|
|
8573
|
+
void this.enqueueNavigationSettingsSync(async () => {
|
|
8574
|
+
if (modeChanged) {
|
|
8575
|
+
await this.applyEffectiveNavigationMode();
|
|
8576
|
+
}
|
|
8577
|
+
if (maxChanged) {
|
|
8578
|
+
const maxTabs = await this.settingsService.get(AXPCommonSettings.MaxOpenTabs);
|
|
8579
|
+
this.maxOpenTabs.set(this.normalizeMaxTabs(maxTabs));
|
|
8580
|
+
this.enforceMaxTabs();
|
|
8581
|
+
this.persistSession();
|
|
8582
|
+
}
|
|
8583
|
+
});
|
|
8057
8584
|
});
|
|
8058
8585
|
}
|
|
8059
8586
|
bindSessionChanges() {
|
|
8060
|
-
|
|
8061
|
-
|
|
8062
|
-
|
|
8587
|
+
combineLatest([
|
|
8588
|
+
this.sessionService.user$.pipe(map$1((user) => user?.id ?? null)),
|
|
8589
|
+
this.sessionService.tenant$.pipe(map$1((tenant) => tenant?.id ?? null)),
|
|
8590
|
+
this.sessionService.application$.pipe(map$1((application) => application?.id ?? null)),
|
|
8591
|
+
this.sessionService.isReadyForAppScopedApi$,
|
|
8592
|
+
])
|
|
8593
|
+
.pipe(distinctUntilChanged$1((previous, next) => previous[0] === next[0] &&
|
|
8594
|
+
previous[1] === next[1] &&
|
|
8595
|
+
previous[2] === next[2] &&
|
|
8596
|
+
previous[3] === next[3]), takeUntilDestroyed(this.destroyRef))
|
|
8597
|
+
.subscribe(() => {
|
|
8598
|
+
const context = this.captureSessionSyncContext();
|
|
8599
|
+
if (this.tabsOwnerStamp != null && (context.sessionEnded || context.stamp !== this.tabsOwnerStamp)) {
|
|
8600
|
+
this.routeCacheRegistry.suspendAttach();
|
|
8063
8601
|
}
|
|
8064
|
-
void this.
|
|
8602
|
+
void this.enqueueNavigationSettingsSync(() => this.syncTabsToSession(context));
|
|
8065
8603
|
});
|
|
8066
8604
|
}
|
|
8605
|
+
/** Reads the session once, so a later user cannot replace this event's storage key. */
|
|
8606
|
+
captureSessionSyncContext() {
|
|
8607
|
+
return {
|
|
8608
|
+
generation: ++this.sessionSyncGeneration,
|
|
8609
|
+
stamp: this.sessionStamp(),
|
|
8610
|
+
sessionEnded: !this.sessionService.user?.id || !this.sessionService.hasSessionCredentials(),
|
|
8611
|
+
storageKey: this.sessionStorageKey(),
|
|
8612
|
+
url: normalizeRouterUrl(this.router.url),
|
|
8613
|
+
};
|
|
8614
|
+
}
|
|
8615
|
+
isCurrentSessionSync(context) {
|
|
8616
|
+
return context.generation === this.sessionSyncGeneration;
|
|
8617
|
+
}
|
|
8618
|
+
/**
|
|
8619
|
+
* Closes every tab when the signed-in user, tenant, or application actually changes, including sign-out.
|
|
8620
|
+
* The saved list is deleted only for that real change, so a failed switch can restore it.
|
|
8621
|
+
* The context is the session at enqueue time. A newer emission stops this task, including after an await.
|
|
8622
|
+
*/
|
|
8623
|
+
async syncTabsToSession(context) {
|
|
8624
|
+
// Sign-out still deletes its own captured list when a later sign-in made this event stale.
|
|
8625
|
+
if (context.sessionEnded) {
|
|
8626
|
+
this.applyCapturedSignOut(context);
|
|
8627
|
+
return;
|
|
8628
|
+
}
|
|
8629
|
+
if (!this.isCurrentSessionSync(context)) {
|
|
8630
|
+
return;
|
|
8631
|
+
}
|
|
8632
|
+
if (!this.sessionTabsBound) {
|
|
8633
|
+
this.sessionTabsBound = true;
|
|
8634
|
+
this.tabsOwnerStamp = context.stamp;
|
|
8635
|
+
this.ownedStorageKey = context.storageKey;
|
|
8636
|
+
if (!context.stamp) {
|
|
8637
|
+
return;
|
|
8638
|
+
}
|
|
8639
|
+
this.routeCacheRegistry.bindOwner(context.stamp);
|
|
8640
|
+
await this.applyEffectiveNavigationMode();
|
|
8641
|
+
if (!this.isCurrentSessionSync(context)) {
|
|
8642
|
+
return;
|
|
8643
|
+
}
|
|
8644
|
+
await this.restoreSessionIfNeeded(context);
|
|
8645
|
+
return;
|
|
8646
|
+
}
|
|
8647
|
+
if (context.stamp === this.tabsOwnerStamp &&
|
|
8648
|
+
this.pendingReadinessGap == null &&
|
|
8649
|
+
!this.closedTabsForSessionChange) {
|
|
8650
|
+
this.routeCacheRegistry.resumeAttach();
|
|
8651
|
+
return;
|
|
8652
|
+
}
|
|
8653
|
+
if (context.stamp == null) {
|
|
8654
|
+
if (this.tabsOwnerStamp != null) {
|
|
8655
|
+
this.pendingReadinessGap = this.tabsOwnerStamp;
|
|
8656
|
+
}
|
|
8657
|
+
return;
|
|
8658
|
+
}
|
|
8659
|
+
const gap = this.pendingReadinessGap;
|
|
8660
|
+
this.pendingReadinessGap = null;
|
|
8661
|
+
if (gap != null && gap === context.stamp && !this.closedTabsForSessionChange) {
|
|
8662
|
+
this.tabsOwnerStamp = context.stamp;
|
|
8663
|
+
this.routeCacheRegistry.resumeAttach();
|
|
8664
|
+
return;
|
|
8665
|
+
}
|
|
8666
|
+
const previous = gap ?? this.tabsOwnerStamp;
|
|
8667
|
+
const identityChanged = previous != null && previous !== context.stamp;
|
|
8668
|
+
if (identityChanged || this.closedTabsForSessionChange) {
|
|
8669
|
+
this.closedTabsForSessionChange = false;
|
|
8670
|
+
this.restoreDeferred = false;
|
|
8671
|
+
this.forgetPersistedTabs(this.ownedStorageKey);
|
|
8672
|
+
this.tabsOwnerStamp = context.stamp;
|
|
8673
|
+
this.ownedStorageKey = context.storageKey;
|
|
8674
|
+
this.sessionTabsHydrated = false;
|
|
8675
|
+
this.discardRuntimeTabs();
|
|
8676
|
+
this.routeCacheRegistry.bindOwner(context.stamp);
|
|
8677
|
+
this.routeCacheRegistry.resumeAttach();
|
|
8678
|
+
await this.applyEffectiveNavigationMode();
|
|
8679
|
+
if (!this.isCurrentSessionSync(context)) {
|
|
8680
|
+
return;
|
|
8681
|
+
}
|
|
8682
|
+
this.sessionTabsHydrated = true;
|
|
8683
|
+
await this.syncSettledDestination(context);
|
|
8684
|
+
return;
|
|
8685
|
+
}
|
|
8686
|
+
if (this.tabsOwnerStamp == null && context.stamp != null) {
|
|
8687
|
+
this.tabsOwnerStamp = context.stamp;
|
|
8688
|
+
this.ownedStorageKey = context.storageKey;
|
|
8689
|
+
this.routeCacheRegistry.bindOwner(context.stamp);
|
|
8690
|
+
this.routeCacheRegistry.resumeAttach();
|
|
8691
|
+
await this.applyEffectiveNavigationMode();
|
|
8692
|
+
if (!this.isCurrentSessionSync(context)) {
|
|
8693
|
+
return;
|
|
8694
|
+
}
|
|
8695
|
+
await this.restoreSessionIfNeeded(context);
|
|
8696
|
+
}
|
|
8697
|
+
}
|
|
8698
|
+
/**
|
|
8699
|
+
* Sign-out cleanup uses the captured storage key and still runs when a newer sign-in
|
|
8700
|
+
* has already superseded this event. It does not read the live session.
|
|
8701
|
+
*/
|
|
8702
|
+
applyCapturedSignOut(context) {
|
|
8703
|
+
this.pendingReadinessGap = null;
|
|
8704
|
+
this.restoreDeferred = false;
|
|
8705
|
+
this.forgetPersistedTabs(context.storageKey ?? this.ownedStorageKey);
|
|
8706
|
+
this.tabsOwnerStamp = null;
|
|
8707
|
+
this.discardRuntimeTabs();
|
|
8708
|
+
this.routeCacheRegistry.resumeAttach();
|
|
8709
|
+
this.closedTabsForSessionChange = true;
|
|
8710
|
+
this.sessionTabsHydrated = false;
|
|
8711
|
+
}
|
|
8712
|
+
/**
|
|
8713
|
+
* Opens Home for the settled session. The current route is added only after the router
|
|
8714
|
+
* has left the URL captured with the event, so the previous context is not reopened.
|
|
8715
|
+
* Home is created even when that URL is unchanged, including when it is already Home.
|
|
8716
|
+
*/
|
|
8717
|
+
async syncSettledDestination(context) {
|
|
8718
|
+
if (!this.isCurrentSessionSync(context) || this.effectiveMode() !== 'tabular') {
|
|
8719
|
+
return;
|
|
8720
|
+
}
|
|
8721
|
+
await this.ensureHomeTab();
|
|
8722
|
+
if (!this.isCurrentSessionSync(context)) {
|
|
8723
|
+
return;
|
|
8724
|
+
}
|
|
8725
|
+
const destination = normalizeRouterUrl(this.router.url);
|
|
8726
|
+
const routeBelongsToSettledSession = !!destination && destination !== context.url && axpIsTabEligibleUrl(destination);
|
|
8727
|
+
if (routeBelongsToSettledSession || (!!destination && this.isHomePageUrl(destination))) {
|
|
8728
|
+
await this.syncCurrentRouteAsTab();
|
|
8729
|
+
return;
|
|
8730
|
+
}
|
|
8731
|
+
if (this.tabs().some((tab) => tab.key === AXP_HOME_TAB_KEY)) {
|
|
8732
|
+
this.activeTabKey.set(AXP_HOME_TAB_KEY);
|
|
8733
|
+
}
|
|
8734
|
+
this.persistSession();
|
|
8735
|
+
}
|
|
8736
|
+
/** Continues a restore that was skipped while a command sub-flow held the router. */
|
|
8737
|
+
async resumeDeferredTabRestore() {
|
|
8738
|
+
if (!this.restoreDeferred || axpIsCommandSubFlowNavigationSuspended()) {
|
|
8739
|
+
return;
|
|
8740
|
+
}
|
|
8741
|
+
const context = {
|
|
8742
|
+
generation: this.sessionSyncGeneration,
|
|
8743
|
+
stamp: this.tabsOwnerStamp,
|
|
8744
|
+
sessionEnded: false,
|
|
8745
|
+
storageKey: this.ownedStorageKey,
|
|
8746
|
+
url: normalizeRouterUrl(this.router.url),
|
|
8747
|
+
};
|
|
8748
|
+
this.restoreDeferred = false;
|
|
8749
|
+
if (!context.stamp) {
|
|
8750
|
+
return;
|
|
8751
|
+
}
|
|
8752
|
+
await this.restoreSessionIfNeeded(context);
|
|
8753
|
+
}
|
|
8754
|
+
/** Identity of the session that may own open tabs. Null when the app session is not ready. */
|
|
8755
|
+
sessionStamp() {
|
|
8756
|
+
if (!this.sessionService.isReadyForAppScopedApi) {
|
|
8757
|
+
return null;
|
|
8758
|
+
}
|
|
8759
|
+
const userId = this.sessionService.user?.id;
|
|
8760
|
+
const tenantId = this.sessionService.tenant?.id;
|
|
8761
|
+
const applicationId = this.sessionService.application?.id;
|
|
8762
|
+
if (!userId || !tenantId || !applicationId) {
|
|
8763
|
+
return null;
|
|
8764
|
+
}
|
|
8765
|
+
return `${tenantId}:${applicationId}:${userId}`;
|
|
8766
|
+
}
|
|
8767
|
+
/** Deletes one captured tab list. A newer user's key is left untouched. */
|
|
8768
|
+
forgetPersistedTabs(storageKey) {
|
|
8769
|
+
if (!storageKey) {
|
|
8770
|
+
return;
|
|
8771
|
+
}
|
|
8772
|
+
sessionStorage.removeItem(storageKey);
|
|
8773
|
+
if (this.ownedStorageKey === storageKey) {
|
|
8774
|
+
this.ownedStorageKey = null;
|
|
8775
|
+
}
|
|
8776
|
+
}
|
|
8777
|
+
/** Closes every tab and destroys cached page components. */
|
|
8778
|
+
discardRuntimeTabs() {
|
|
8779
|
+
this.tabs.set([]);
|
|
8780
|
+
this.activeTabKey.set(null);
|
|
8781
|
+
this.tabPreviewService.clearAll();
|
|
8782
|
+
this.routeCacheRegistry.bindOwner(this.tabsOwnerStamp);
|
|
8783
|
+
}
|
|
8784
|
+
/**
|
|
8785
|
+
* Reads the effective NavigationMode (User, then Tenant, then Platform, then the definition default).
|
|
8786
|
+
* Scope reloads update the settings cache without `onChanged`, so callers must use this instead of
|
|
8787
|
+
* treating a partial event value as "switch to simple".
|
|
8788
|
+
*/
|
|
8789
|
+
async applyEffectiveNavigationMode() {
|
|
8790
|
+
if (!this.sessionService.isReadyForAppScopedApi) {
|
|
8791
|
+
return;
|
|
8792
|
+
}
|
|
8793
|
+
const mode = await this.settingsService.get(AXPCommonSettings.NavigationMode);
|
|
8794
|
+
const next = mode === 'tabular' ? 'tabular' : 'simple';
|
|
8795
|
+
const previous = this.navigationMode();
|
|
8796
|
+
if (next === previous) {
|
|
8797
|
+
return;
|
|
8798
|
+
}
|
|
8799
|
+
this.navigationMode.set(next);
|
|
8800
|
+
if (previous === 'tabular' && next === 'simple') {
|
|
8801
|
+
this.clearSession();
|
|
8802
|
+
return;
|
|
8803
|
+
}
|
|
8804
|
+
if (next === 'tabular' && this.effectiveMode() === 'tabular' && this.sessionTabsHydrated) {
|
|
8805
|
+
await this.ensureHomeTab();
|
|
8806
|
+
await this.syncCurrentRouteAsTab();
|
|
8807
|
+
}
|
|
8808
|
+
}
|
|
8809
|
+
enqueueNavigationSettingsSync(task) {
|
|
8810
|
+
const run = this.navigationSettingsChain.then(task, task);
|
|
8811
|
+
this.navigationSettingsChain = run.then(() => undefined, () => undefined);
|
|
8812
|
+
return run;
|
|
8813
|
+
}
|
|
8067
8814
|
//#endregion
|
|
8068
8815
|
//#region ---- Tab Management ----
|
|
8069
8816
|
async ensureHomeTab() {
|
|
@@ -8105,7 +8852,7 @@ class AXPTabNavigationService {
|
|
|
8105
8852
|
}
|
|
8106
8853
|
this.tabs.update((items) => {
|
|
8107
8854
|
const withoutDuplicate = items.filter((item) => {
|
|
8108
|
-
if (item.key === tab.key) {
|
|
8855
|
+
if (item.key === tab.key || this.isSameEntityListTab(item, tab)) {
|
|
8109
8856
|
return false;
|
|
8110
8857
|
}
|
|
8111
8858
|
return axpResolveTabKey(this.router, item.url) !== tab.key;
|
|
@@ -8165,14 +8912,29 @@ class AXPTabNavigationService {
|
|
|
8165
8912
|
lastActivatedAt: now,
|
|
8166
8913
|
};
|
|
8167
8914
|
}
|
|
8168
|
-
/** Finds a tab by scoped key or
|
|
8915
|
+
/** Finds a tab by scoped key, or by entity-list identity when an older key drifted. */
|
|
8169
8916
|
findTabForUrl(url) {
|
|
8170
8917
|
if (this.isHomePageUrl(url)) {
|
|
8171
8918
|
return this.tabs().find((tab) => tab.key === AXP_HOME_TAB_KEY);
|
|
8172
8919
|
}
|
|
8173
8920
|
const resolvedKey = axpResolveTabKey(this.router, url);
|
|
8174
|
-
|
|
8921
|
+
const byKey = this.tabs().find((tab) => tab.key !== AXP_HOME_TAB_KEY &&
|
|
8175
8922
|
(tab.key === resolvedKey || axpResolveTabKey(this.router, tab.url) === resolvedKey));
|
|
8923
|
+
if (byKey) {
|
|
8924
|
+
return byKey;
|
|
8925
|
+
}
|
|
8926
|
+
if (!axpIsEntityListPath(url)) {
|
|
8927
|
+
return undefined;
|
|
8928
|
+
}
|
|
8929
|
+
const identity = axpBuildTabKey(normalizeRouterUrl(url));
|
|
8930
|
+
return this.tabs().find((tab) => tab.key !== AXP_HOME_TAB_KEY && axpIsEntityListPath(tab.url) && axpBuildTabKey(tab.url) === identity);
|
|
8931
|
+
}
|
|
8932
|
+
/** Same list menu (`mode`, `list`, …). Page-state query params do not make a new tab. */
|
|
8933
|
+
isSameEntityListTab(left, right) {
|
|
8934
|
+
if (!axpIsEntityListPath(left.url) || !axpIsEntityListPath(right.url)) {
|
|
8935
|
+
return false;
|
|
8936
|
+
}
|
|
8937
|
+
return axpBuildTabKey(left.url) === axpBuildTabKey(right.url);
|
|
8176
8938
|
}
|
|
8177
8939
|
/** True when the URL is the configured application home page (e.g. dashboard). */
|
|
8178
8940
|
isHomePageUrl(url) {
|
|
@@ -8317,8 +9079,12 @@ class AXPTabNavigationService {
|
|
|
8317
9079
|
return `${SESSION_STORAGE_PREFIX}(${userId})`;
|
|
8318
9080
|
}
|
|
8319
9081
|
persistSession() {
|
|
8320
|
-
const key = this.
|
|
8321
|
-
if (!key ||
|
|
9082
|
+
const key = this.ownedStorageKey;
|
|
9083
|
+
if (!key ||
|
|
9084
|
+
key !== this.sessionStorageKey() ||
|
|
9085
|
+
this.tabsOwnerStamp !== this.sessionStamp() ||
|
|
9086
|
+
!this.sessionTabsHydrated ||
|
|
9087
|
+
this.effectiveMode() === 'simple') {
|
|
8322
9088
|
return;
|
|
8323
9089
|
}
|
|
8324
9090
|
const payload = {
|
|
@@ -8335,31 +9101,54 @@ class AXPTabNavigationService {
|
|
|
8335
9101
|
this.tabs.set([]);
|
|
8336
9102
|
this.activeTabKey.set(null);
|
|
8337
9103
|
this.tabPreviewService.clearAll();
|
|
9104
|
+
this.routeCacheRegistry.clear();
|
|
8338
9105
|
}
|
|
8339
|
-
async restoreSessionIfNeeded() {
|
|
9106
|
+
async restoreSessionIfNeeded(context) {
|
|
9107
|
+
if (!this.isCurrentSessionSync(context)) {
|
|
9108
|
+
return;
|
|
9109
|
+
}
|
|
8340
9110
|
if (this.effectiveMode() === 'simple') {
|
|
9111
|
+
this.sessionTabsHydrated = true;
|
|
8341
9112
|
return;
|
|
8342
9113
|
}
|
|
8343
9114
|
if (axpIsCommandSubFlowNavigationSuspended()) {
|
|
9115
|
+
this.restoreDeferred = true;
|
|
8344
9116
|
return;
|
|
8345
9117
|
}
|
|
8346
|
-
const storageKey =
|
|
9118
|
+
const storageKey = context.storageKey;
|
|
8347
9119
|
if (!storageKey) {
|
|
8348
|
-
|
|
8349
|
-
|
|
9120
|
+
this.tabs.set([]);
|
|
9121
|
+
this.activeTabKey.set(null);
|
|
9122
|
+
this.tabPreviewService.clearAll();
|
|
9123
|
+
this.sessionTabsHydrated = true;
|
|
8350
9124
|
return;
|
|
8351
9125
|
}
|
|
8352
9126
|
const raw = sessionStorage.getItem(storageKey);
|
|
8353
9127
|
if (!raw) {
|
|
9128
|
+
this.tabs.set([]);
|
|
9129
|
+
this.activeTabKey.set(null);
|
|
9130
|
+
this.sessionTabsHydrated = true;
|
|
8354
9131
|
await this.ensureHomeTab();
|
|
9132
|
+
if (!this.isCurrentSessionSync(context)) {
|
|
9133
|
+
return;
|
|
9134
|
+
}
|
|
8355
9135
|
await this.syncCurrentRouteAsTab();
|
|
8356
9136
|
return;
|
|
8357
9137
|
}
|
|
8358
9138
|
try {
|
|
8359
9139
|
const parsed = JSON.parse(raw);
|
|
8360
|
-
const restoredTabs = this.removeDuplicateHomeUrlTabs(this.normalizeRestoredTabKeys(defaultTo(parsed.tabs, [])
|
|
9140
|
+
const restoredTabs = this.removeDuplicateHomeUrlTabs(this.normalizeRestoredTabKeys(defaultTo(parsed.tabs, [])
|
|
9141
|
+
.filter((tab) => tab?.key && tab?.url)
|
|
9142
|
+
.map((tab) => ({ ...tab, url: axpInTabErrorRetryUrl(tab.url) }))));
|
|
9143
|
+
if (!this.isCurrentSessionSync(context)) {
|
|
9144
|
+
return;
|
|
9145
|
+
}
|
|
9146
|
+
this.sessionTabsHydrated = true;
|
|
8361
9147
|
this.tabs.set(this.ensureHomeTabFirst(restoredTabs));
|
|
8362
9148
|
await this.ensureHomeTab();
|
|
9149
|
+
if (!this.isCurrentSessionSync(context)) {
|
|
9150
|
+
return;
|
|
9151
|
+
}
|
|
8363
9152
|
const activeKey = parsed.activeTabKey ?? this.tabs()[this.tabs().length - 1]?.key ?? AXP_HOME_TAB_KEY;
|
|
8364
9153
|
this.activeTabKey.set(activeKey);
|
|
8365
9154
|
const activeTab = this.tabs().find((tab) => tab.key === activeKey);
|
|
@@ -8367,17 +9156,18 @@ class AXPTabNavigationService {
|
|
|
8367
9156
|
if (activeTab &&
|
|
8368
9157
|
currentUrl !== normalizeRouterUrl(activeTab.url) &&
|
|
8369
9158
|
axpIsTabEligibleUrl(currentUrl)) {
|
|
8370
|
-
this.
|
|
8371
|
-
try {
|
|
8372
|
-
await this.router.navigateByUrl(activeTab.url);
|
|
8373
|
-
}
|
|
8374
|
-
finally {
|
|
8375
|
-
this.suppressNavigationSync = false;
|
|
8376
|
-
}
|
|
9159
|
+
await this.runWithSuppressedNavigationSync(() => this.router.navigateByUrl(activeTab.url));
|
|
8377
9160
|
}
|
|
8378
9161
|
}
|
|
8379
9162
|
catch {
|
|
9163
|
+
if (!this.isCurrentSessionSync(context)) {
|
|
9164
|
+
return;
|
|
9165
|
+
}
|
|
9166
|
+
this.sessionTabsHydrated = true;
|
|
8380
9167
|
await this.ensureHomeTab();
|
|
9168
|
+
if (!this.isCurrentSessionSync(context)) {
|
|
9169
|
+
return;
|
|
9170
|
+
}
|
|
8381
9171
|
await this.syncCurrentRouteAsTab();
|
|
8382
9172
|
}
|
|
8383
9173
|
}
|
|
@@ -8627,7 +9417,12 @@ class AXPLocalEntityOutboxService {
|
|
|
8627
9417
|
this.handlers.delete(entityKey);
|
|
8628
9418
|
}
|
|
8629
9419
|
pendingFor(entityKey) {
|
|
8630
|
-
|
|
9420
|
+
const userKey = this.resolveUserKey();
|
|
9421
|
+
return this.pendingOps().filter((op) => op.entityKey === entityKey && op.userKey === userKey);
|
|
9422
|
+
}
|
|
9423
|
+
/** Await durable operations before a feature overlays pending rows after reload. */
|
|
9424
|
+
hydrate() {
|
|
9425
|
+
return this.refreshPending();
|
|
8631
9426
|
}
|
|
8632
9427
|
async enqueue(input) {
|
|
8633
9428
|
const userKey = this.resolveUserKey();
|
|
@@ -8638,10 +9433,7 @@ class AXPLocalEntityOutboxService {
|
|
|
8638
9433
|
}
|
|
8639
9434
|
const recordIds = [...new Set((input.recordIds ?? []).map((id) => id?.trim()).filter(Boolean))];
|
|
8640
9435
|
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));
|
|
9436
|
+
const duplicate = existing.find((op) => op.entityKey === entityKey && op.action === action && sameIdSet(op.recordIds, recordIds) && stablePayload(op.payload) === stablePayload(input.payload));
|
|
8645
9437
|
if (duplicate) {
|
|
8646
9438
|
return;
|
|
8647
9439
|
}
|
|
@@ -8652,7 +9444,8 @@ class AXPLocalEntityOutboxService {
|
|
|
8652
9444
|
action,
|
|
8653
9445
|
recordIds,
|
|
8654
9446
|
payload: input.payload,
|
|
8655
|
-
|
|
9447
|
+
// Keep dependent operations ordered even if send and edit enqueue in the same millisecond.
|
|
9448
|
+
queuedAt: new Date(Math.max(Date.now(), ...existing.map((op) => Date.parse(op.queuedAt) + 1))).toISOString(),
|
|
8656
9449
|
attempts: 0,
|
|
8657
9450
|
lastError: null,
|
|
8658
9451
|
};
|
|
@@ -8690,6 +9483,10 @@ class AXPLocalEntityOutboxService {
|
|
|
8690
9483
|
}
|
|
8691
9484
|
const ops = (await this.loadUserOps(userKey)).sort((left, right) => left.queuedAt.localeCompare(right.queuedAt));
|
|
8692
9485
|
for (const op of ops) {
|
|
9486
|
+
// A queued batch must never continue under another account after an awaited HTTP call.
|
|
9487
|
+
if (userKey !== this.resolveUserKey() || !this.sessionService.isReadyForAppScopedApi) {
|
|
9488
|
+
break;
|
|
9489
|
+
}
|
|
8693
9490
|
const handler = this.handlers.get(op.entityKey);
|
|
8694
9491
|
if (!handler) {
|
|
8695
9492
|
continue;
|
|
@@ -8745,6 +9542,8 @@ class AXPLocalEntityOutboxService {
|
|
|
8745
9542
|
return;
|
|
8746
9543
|
}
|
|
8747
9544
|
const ops = await this.loadUserOps(userKey);
|
|
9545
|
+
if (userKey !== this.resolveUserKey())
|
|
9546
|
+
return;
|
|
8748
9547
|
ops.sort((left, right) => left.queuedAt.localeCompare(right.queuedAt));
|
|
8749
9548
|
this.pendingOps.set(ops);
|
|
8750
9549
|
}
|
|
@@ -9268,6 +10067,23 @@ class AXPNotFoundError extends Error {
|
|
|
9268
10067
|
}
|
|
9269
10068
|
}
|
|
9270
10069
|
|
|
10070
|
+
/**
|
|
10071
|
+
* Placeholder for a wildcard route whose guard always redirects into the shell.
|
|
10072
|
+
* The component is not shown.
|
|
10073
|
+
*/
|
|
10074
|
+
class AXPUnreachableRouteComponent {
|
|
10075
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPUnreachableRouteComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
10076
|
+
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 }); }
|
|
10077
|
+
}
|
|
10078
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPUnreachableRouteComponent, decorators: [{
|
|
10079
|
+
type: Component,
|
|
10080
|
+
args: [{
|
|
10081
|
+
selector: 'axp-unreachable-route',
|
|
10082
|
+
template: '',
|
|
10083
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
10084
|
+
}]
|
|
10085
|
+
}] });
|
|
10086
|
+
|
|
9271
10087
|
/** Canonical route path for the platform 404 page. */
|
|
9272
10088
|
const AXP_NOT_FOUND_ROUTE = '/error/404';
|
|
9273
10089
|
/**
|
|
@@ -9276,7 +10092,8 @@ const AXP_NOT_FOUND_ROUTE = '/error/404';
|
|
|
9276
10092
|
*/
|
|
9277
10093
|
const AXP_NOT_FOUND_CATCH_ALL_ROUTE = {
|
|
9278
10094
|
path: '**',
|
|
9279
|
-
|
|
10095
|
+
canActivate: [axpRedirectUnknownToNotFound],
|
|
10096
|
+
component: AXPUnreachableRouteComponent,
|
|
9280
10097
|
};
|
|
9281
10098
|
/**
|
|
9282
10099
|
* Registers the platform catch-all 404 route.
|
|
@@ -9300,8 +10117,8 @@ function axpIsEntityRecordNotFound(data) {
|
|
|
9300
10117
|
/**
|
|
9301
10118
|
* Creates an Angular redirect command to the platform 404 page.
|
|
9302
10119
|
*/
|
|
9303
|
-
function axpRedirectToNotFound(router = inject(Router)) {
|
|
9304
|
-
return new RedirectCommand(router.
|
|
10120
|
+
function axpRedirectToNotFound(router = inject(Router), sourceUrl) {
|
|
10121
|
+
return new RedirectCommand(axpInTabErrorUrlTree(router, '404', sourceUrl ?? router.url));
|
|
9305
10122
|
}
|
|
9306
10123
|
|
|
9307
10124
|
/** Canonical route path for the platform 401 page. */
|
|
@@ -9309,8 +10126,8 @@ const AXP_UNAUTHORIZED_ROUTE = '/error/401';
|
|
|
9309
10126
|
/**
|
|
9310
10127
|
* Creates an Angular redirect command to the platform 401 page.
|
|
9311
10128
|
*/
|
|
9312
|
-
function axpRedirectToUnauthorized(router = inject(Router)) {
|
|
9313
|
-
return new RedirectCommand(router.
|
|
10129
|
+
function axpRedirectToUnauthorized(router = inject(Router), sourceUrl) {
|
|
10130
|
+
return new RedirectCommand(axpInTabErrorUrlTree(router, '401', sourceUrl ?? router.url));
|
|
9314
10131
|
}
|
|
9315
10132
|
|
|
9316
10133
|
//#endregion
|
|
@@ -10078,15 +10895,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
|
|
|
10078
10895
|
const AXP_ENTITY_VIEW_ACCESS = new InjectionToken('AXP_ENTITY_VIEW_ACCESS');
|
|
10079
10896
|
|
|
10080
10897
|
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
10898
|
/**
|
|
10091
10899
|
|
|
10092
10900
|
* Blocks navigation when the target route is not accessible for the current user.
|
|
@@ -10114,10 +10922,7 @@ const AXPMenuRouteGuard = (route, state) => {
|
|
|
10114
10922
|
if (isAllowed instanceof UrlTree) {
|
|
10115
10923
|
return isAllowed;
|
|
10116
10924
|
}
|
|
10117
|
-
|
|
10118
|
-
return false;
|
|
10119
|
-
}
|
|
10120
|
-
return router.parseUrl(AXP_UNAUTHORIZED_ROUTE);
|
|
10925
|
+
return axpInTabErrorUrlTree(router, '401', state.url);
|
|
10121
10926
|
}));
|
|
10122
10927
|
};
|
|
10123
10928
|
|
|
@@ -11175,7 +11980,7 @@ class AXPCommonModule {
|
|
|
11175
11980
|
provide: AXP_SETTING_DEFINITION_PROVIDER,
|
|
11176
11981
|
useFactory: async () => {
|
|
11177
11982
|
const injector = inject(Injector);
|
|
11178
|
-
const provider = (await import('./acorex-platform-framework-client-common-common-settings.provider-
|
|
11983
|
+
const provider = (await import('./acorex-platform-framework-client-common-common-settings.provider-Biu0_ryw.mjs')).AXPCommonSettingProvider;
|
|
11179
11984
|
return new provider(injector);
|
|
11180
11985
|
},
|
|
11181
11986
|
multi: true,
|
|
@@ -11298,7 +12103,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
|
|
|
11298
12103
|
provide: AXP_SETTING_DEFINITION_PROVIDER,
|
|
11299
12104
|
useFactory: async () => {
|
|
11300
12105
|
const injector = inject(Injector);
|
|
11301
|
-
const provider = (await import('./acorex-platform-framework-client-common-common-settings.provider-
|
|
12106
|
+
const provider = (await import('./acorex-platform-framework-client-common-common-settings.provider-Biu0_ryw.mjs')).AXPCommonSettingProvider;
|
|
11302
12107
|
return new provider(injector);
|
|
11303
12108
|
},
|
|
11304
12109
|
multi: true,
|
|
@@ -11343,20 +12148,38 @@ const AXP_ROOT_CONFIG_TOKEN = new InjectionToken('AXP_ROOT_CONFIG_TOKEN');
|
|
|
11343
12148
|
|
|
11344
12149
|
/**
|
|
11345
12150
|
* Opens the native file picker and always settles — including when the user cancels.
|
|
11346
|
-
* `AXFileService.choose()`
|
|
12151
|
+
* `AXFileService.choose()` only listens for `change`, so dismissing the dialog leaves that promise pending.
|
|
11347
12152
|
*/
|
|
11348
12153
|
function chooseSystemFiles(options = {}) {
|
|
11349
|
-
|
|
12154
|
+
return openSystemFileDialog(inject(DOCUMENT), options);
|
|
12155
|
+
}
|
|
12156
|
+
/**
|
|
12157
|
+
* Same picker as {@link chooseSystemFiles}, with the document passed in so callers can `await` before opening it.
|
|
12158
|
+
*/
|
|
12159
|
+
function openSystemFileDialog(doc, options = {}) {
|
|
12160
|
+
const view = doc.defaultView;
|
|
12161
|
+
if (!view) {
|
|
12162
|
+
return Promise.resolve([]);
|
|
12163
|
+
}
|
|
11350
12164
|
const accept = options.accept ?? '';
|
|
11351
12165
|
const multiple = options.multiple ?? false;
|
|
11352
12166
|
return new Promise((resolve) => {
|
|
11353
|
-
const input =
|
|
12167
|
+
const input = doc.createElement('input');
|
|
11354
12168
|
input.type = 'file';
|
|
11355
12169
|
input.accept = accept;
|
|
11356
12170
|
input.multiple = multiple;
|
|
11357
12171
|
input.style.display = 'none';
|
|
11358
|
-
|
|
12172
|
+
doc.body.appendChild(input);
|
|
11359
12173
|
let settled = false;
|
|
12174
|
+
let dialogOpened = false;
|
|
12175
|
+
let watchFocus = false;
|
|
12176
|
+
const cleanup = () => {
|
|
12177
|
+
input.removeEventListener('change', onChange);
|
|
12178
|
+
input.removeEventListener('cancel', onCancel);
|
|
12179
|
+
view.removeEventListener('blur', onBlur);
|
|
12180
|
+
view.removeEventListener('focus', onFocus);
|
|
12181
|
+
input.remove();
|
|
12182
|
+
};
|
|
11360
12183
|
const settle = (files) => {
|
|
11361
12184
|
if (settled) {
|
|
11362
12185
|
return;
|
|
@@ -11365,21 +12188,37 @@ function chooseSystemFiles(options = {}) {
|
|
|
11365
12188
|
cleanup();
|
|
11366
12189
|
resolve(files);
|
|
11367
12190
|
};
|
|
11368
|
-
const cleanup = () => {
|
|
11369
|
-
input.removeEventListener('change', onChange);
|
|
11370
|
-
window.removeEventListener('focus', onWindowFocus);
|
|
11371
|
-
input.remove();
|
|
11372
|
-
};
|
|
11373
12191
|
const onChange = () => {
|
|
11374
12192
|
settle(Array.from(input.files ?? []));
|
|
11375
12193
|
};
|
|
11376
|
-
const
|
|
11377
|
-
|
|
11378
|
-
|
|
12194
|
+
const onCancel = () => {
|
|
12195
|
+
settle([]);
|
|
12196
|
+
};
|
|
12197
|
+
const onBlur = () => {
|
|
12198
|
+
if (watchFocus) {
|
|
12199
|
+
dialogOpened = true;
|
|
12200
|
+
}
|
|
12201
|
+
};
|
|
12202
|
+
const onFocus = () => {
|
|
12203
|
+
if (!watchFocus || !dialogOpened) {
|
|
12204
|
+
return;
|
|
12205
|
+
}
|
|
12206
|
+
// `change` fires before this timeout when the user confirms a file.
|
|
12207
|
+
view.setTimeout(() => {
|
|
12208
|
+
if (!settled) {
|
|
12209
|
+
settle(Array.from(input.files ?? []));
|
|
12210
|
+
}
|
|
12211
|
+
}, 300);
|
|
11379
12212
|
};
|
|
11380
12213
|
input.addEventListener('change', onChange);
|
|
11381
|
-
|
|
12214
|
+
input.addEventListener('cancel', onCancel);
|
|
12215
|
+
view.addEventListener('blur', onBlur);
|
|
12216
|
+
view.addEventListener('focus', onFocus);
|
|
11382
12217
|
input.click();
|
|
12218
|
+
// Ignore blur/focus caused by opening the dialog itself. Cancel still settles via the `cancel` event.
|
|
12219
|
+
view.setTimeout(() => {
|
|
12220
|
+
watchFocus = true;
|
|
12221
|
+
}, 0);
|
|
11383
12222
|
});
|
|
11384
12223
|
}
|
|
11385
12224
|
//#endregion
|
|
@@ -12213,8 +13052,11 @@ class AXPLocalEntityEngine {
|
|
|
12213
13052
|
return bucket.syncInFlight;
|
|
12214
13053
|
}
|
|
12215
13054
|
bucket.syncInFlight = (async () => {
|
|
13055
|
+
const userKey = this.resolveUserKey();
|
|
12216
13056
|
const result = await fetchHot();
|
|
12217
|
-
|
|
13057
|
+
if (userKey === this.resolveUserKey()) {
|
|
13058
|
+
await this.replaceAll(entityKey, result.items ?? []);
|
|
13059
|
+
}
|
|
12218
13060
|
})().finally(() => {
|
|
12219
13061
|
bucket.syncInFlight = null;
|
|
12220
13062
|
});
|
|
@@ -12289,6 +13131,8 @@ class AXPLocalEntityEngine {
|
|
|
12289
13131
|
await axpLocalEntityIndexedDb.transaction('rw', axpLocalEntityIndexedDb.items, async () => {
|
|
12290
13132
|
await axpLocalEntityIndexedDb.items.bulkDelete(storageIds);
|
|
12291
13133
|
});
|
|
13134
|
+
if (userKey !== this.resolveUserKey())
|
|
13135
|
+
return;
|
|
12292
13136
|
bucket.items.set(next);
|
|
12293
13137
|
bucket.hydrated.set(true);
|
|
12294
13138
|
this.publish({ type: 'store-changed', entityKey, userKey, originId: this.originId });
|
|
@@ -12347,10 +13191,9 @@ class AXPLocalEntityEngine {
|
|
|
12347
13191
|
bucket.hydrated.set(true);
|
|
12348
13192
|
return;
|
|
12349
13193
|
}
|
|
12350
|
-
const stored = await axpLocalEntityIndexedDb.items
|
|
12351
|
-
|
|
12352
|
-
|
|
12353
|
-
.toArray();
|
|
13194
|
+
const stored = await axpLocalEntityIndexedDb.items.where('[entityKey+userKey]').equals([entityKey, userKey]).toArray();
|
|
13195
|
+
if (userKey !== this.resolveUserKey())
|
|
13196
|
+
return;
|
|
12354
13197
|
const items = this.prune(entityKey, stored.map((row) => row.payload));
|
|
12355
13198
|
this.userKeySignal.set(userKey);
|
|
12356
13199
|
bucket.items.set(items);
|
|
@@ -12373,10 +13216,7 @@ class AXPLocalEntityEngine {
|
|
|
12373
13216
|
const keepIds = new Set(stored.map((row) => row.storageId));
|
|
12374
13217
|
const metaId = buildLocalEntityMetaId(entityKey, userKey);
|
|
12375
13218
|
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();
|
|
13219
|
+
const existing = await axpLocalEntityIndexedDb.items.where('[entityKey+userKey]').equals([entityKey, userKey]).primaryKeys();
|
|
12380
13220
|
const staleIds = existing.filter((id) => !keepIds.has(String(id)));
|
|
12381
13221
|
if (staleIds.length > 0) {
|
|
12382
13222
|
await axpLocalEntityIndexedDb.items.bulkDelete(staleIds);
|
|
@@ -12391,6 +13231,8 @@ class AXPLocalEntityEngine {
|
|
|
12391
13231
|
lastSyncedAt: new Date().toISOString(),
|
|
12392
13232
|
});
|
|
12393
13233
|
});
|
|
13234
|
+
if (userKey !== this.resolveUserKey())
|
|
13235
|
+
return;
|
|
12394
13236
|
this.userKeySignal.set(userKey);
|
|
12395
13237
|
bucket.items.set(items);
|
|
12396
13238
|
bucket.hydrated.set(true);
|
|
@@ -12412,6 +13254,8 @@ class AXPLocalEntityEngine {
|
|
|
12412
13254
|
}
|
|
12413
13255
|
this.userKeySignal.set(userKey);
|
|
12414
13256
|
for (const [entityKey, bucket] of this.buckets) {
|
|
13257
|
+
bucket.items.set([]);
|
|
13258
|
+
bucket.hydrated.set(false);
|
|
12415
13259
|
bucket.bootstrapPromise = null;
|
|
12416
13260
|
bucket.hydratePromise = null;
|
|
12417
13261
|
bucket.syncInFlight = null;
|
|
@@ -14054,5 +14898,5 @@ const axpPageGetContextQueryDefinition = defineQuery({
|
|
|
14054
14898
|
* Generated bundle index. Do not edit.
|
|
14055
14899
|
*/
|
|
14056
14900
|
|
|
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 };
|
|
14901
|
+
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
14902
|
//# sourceMappingURL=acorex-platform-framework-client-common.mjs.map
|