@ardimedia/angular-portal-azure 0.3.18 → 0.3.27
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.
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { signal, computed, Injectable, inject, makeEnvironmentProviders, APP_INITIALIZER, input, output, Component, ElementRef, Injector,
|
|
2
|
+
import { signal, computed, Injectable, inject, InjectionToken, makeEnvironmentProviders, ENVIRONMENT_INITIALIZER, DestroyRef, effect, APP_INITIALIZER, input, output, Component, ElementRef, Injector, afterNextRender, contentChild } from '@angular/core';
|
|
3
|
+
import { Router, NavigationEnd } from '@angular/router';
|
|
4
|
+
import { filter } from 'rxjs/operators';
|
|
3
5
|
import { DOCUMENT, NgComponentOutlet } from '@angular/common';
|
|
6
|
+
import { NgForm } from '@angular/forms';
|
|
4
7
|
|
|
5
8
|
function clearStatusBar() {
|
|
6
9
|
return { text: '', style: 'none' };
|
|
@@ -15,19 +18,26 @@ function statusBarSuccess(text) {
|
|
|
15
18
|
return { text, style: 'success' };
|
|
16
19
|
}
|
|
17
20
|
|
|
21
|
+
let _nextBladeUid = 0;
|
|
22
|
+
/** @internal */
|
|
23
|
+
function nextBladeUid() { return ++_nextBladeUid; }
|
|
18
24
|
/** Creates a blade definition with sensible defaults.
|
|
19
|
-
* statusBar
|
|
20
|
-
function createBlade(path, title, width = 315) {
|
|
21
|
-
const _statusBar = signal(clearStatusBar(), ...(ngDevMode ? [{ debugName: "_statusBar" }] : []));
|
|
25
|
+
* statusBar and title use getter/setter pairs backed by signals for zoneless change detection. */
|
|
26
|
+
function createBlade(path, title, width = 315, params = {}, uid) {
|
|
27
|
+
const _statusBar = signal(clearStatusBar(), ...(ngDevMode ? [{ debugName: "_statusBar" }] : /* istanbul ignore next */ []));
|
|
28
|
+
const _title = signal(title, ...(ngDevMode ? [{ debugName: "_title" }] : /* istanbul ignore next */ []));
|
|
22
29
|
return {
|
|
30
|
+
uid: uid ?? nextBladeUid(),
|
|
23
31
|
path: path.toLowerCase(),
|
|
24
|
-
title,
|
|
32
|
+
get title() { return _title(); },
|
|
33
|
+
set title(value) { _title.set(value); },
|
|
25
34
|
subtitle: '',
|
|
26
35
|
width,
|
|
27
36
|
isInnerHtml: true,
|
|
28
37
|
commands: [],
|
|
29
38
|
get statusBar() { return _statusBar(); },
|
|
30
39
|
set statusBar(value) { _statusBar.set(value); },
|
|
40
|
+
params,
|
|
31
41
|
};
|
|
32
42
|
}
|
|
33
43
|
|
|
@@ -63,6 +73,7 @@ const LABELS_DE_CH = {
|
|
|
63
73
|
settings: 'Einstellungen',
|
|
64
74
|
language: 'Sprache',
|
|
65
75
|
appearance: 'Darstellung',
|
|
76
|
+
unsavedChangesConfirm: 'Die Änderungen wurden noch nicht gespeichert. Trotzdem verlassen?',
|
|
66
77
|
};
|
|
67
78
|
/** German (Germany) — Swiss spelling rules apply (no ß) */
|
|
68
79
|
const LABELS_DE_DE = { ...LABELS_DE_CH };
|
|
@@ -92,6 +103,7 @@ const LABELS_EN = {
|
|
|
92
103
|
settings: 'Settings',
|
|
93
104
|
language: 'Language',
|
|
94
105
|
appearance: 'Appearance',
|
|
106
|
+
unsavedChangesConfirm: 'You have unsaved changes. Leave anyway?',
|
|
95
107
|
};
|
|
96
108
|
/** French */
|
|
97
109
|
const LABELS_FR = {
|
|
@@ -119,6 +131,7 @@ const LABELS_FR = {
|
|
|
119
131
|
settings: 'Paramètres',
|
|
120
132
|
language: 'Langue',
|
|
121
133
|
appearance: 'Apparence',
|
|
134
|
+
unsavedChangesConfirm: 'Les modifications n\'ont pas été enregistrées. Quitter quand même ?',
|
|
122
135
|
};
|
|
123
136
|
/** Spanish */
|
|
124
137
|
const LABELS_ES = {
|
|
@@ -146,6 +159,7 @@ const LABELS_ES = {
|
|
|
146
159
|
settings: 'Configuración',
|
|
147
160
|
language: 'Idioma',
|
|
148
161
|
appearance: 'Apariencia',
|
|
162
|
+
unsavedChangesConfirm: 'Hay cambios sin guardar. ¿Salir de todos modos?',
|
|
149
163
|
};
|
|
150
164
|
/** Italian */
|
|
151
165
|
const LABELS_IT = {
|
|
@@ -173,6 +187,7 @@ const LABELS_IT = {
|
|
|
173
187
|
settings: 'Impostazioni',
|
|
174
188
|
language: 'Lingua',
|
|
175
189
|
appearance: 'Aspetto',
|
|
190
|
+
unsavedChangesConfirm: 'Le modifiche non sono state salvate. Uscire comunque?',
|
|
176
191
|
};
|
|
177
192
|
// ── Language preset registry ────────────────────────────────────────
|
|
178
193
|
/** Keep DEFAULT_LABELS as alias for backward compatibility */
|
|
@@ -192,16 +207,19 @@ function registerLanguagePreset(preset) {
|
|
|
192
207
|
}
|
|
193
208
|
|
|
194
209
|
/** Creates a data blade definition with sensible defaults.
|
|
195
|
-
* statusBar, item, items use getter/setter pairs backed by signals for zoneless change detection.
|
|
210
|
+
* statusBar, title, item, items use getter/setter pairs backed by signals for zoneless change detection.
|
|
196
211
|
* Note: cannot use ...createBlade() spread here — spread copies getter values, not getter/setter pairs. */
|
|
197
|
-
function createDataBlade(path, title, width = 315) {
|
|
198
|
-
const _statusBar = signal(clearStatusBar(), ...(ngDevMode ? [{ debugName: "_statusBar" }] : []));
|
|
199
|
-
const
|
|
200
|
-
const
|
|
201
|
-
const
|
|
212
|
+
function createDataBlade(path, title, width = 315, params = {}, uid) {
|
|
213
|
+
const _statusBar = signal(clearStatusBar(), ...(ngDevMode ? [{ debugName: "_statusBar" }] : /* istanbul ignore next */ []));
|
|
214
|
+
const _title = signal(title, ...(ngDevMode ? [{ debugName: "_title" }] : /* istanbul ignore next */ []));
|
|
215
|
+
const _item = signal({}, ...(ngDevMode ? [{ debugName: "_item" }] : /* istanbul ignore next */ []));
|
|
216
|
+
const _items = signal([], ...(ngDevMode ? [{ debugName: "_items" }] : /* istanbul ignore next */ []));
|
|
217
|
+
const _loading = signal(false, ...(ngDevMode ? [{ debugName: "_loading" }] : /* istanbul ignore next */ []));
|
|
202
218
|
return {
|
|
219
|
+
uid: uid ?? nextBladeUid(),
|
|
203
220
|
path: path.toLowerCase(),
|
|
204
|
-
title,
|
|
221
|
+
get title() { return _title(); },
|
|
222
|
+
set title(value) { _title.set(value); },
|
|
205
223
|
subtitle: '',
|
|
206
224
|
width,
|
|
207
225
|
isInnerHtml: true,
|
|
@@ -215,6 +233,7 @@ function createDataBlade(path, title, width = 315) {
|
|
|
215
233
|
get loading() { return _loading(); },
|
|
216
234
|
set loading(value) { _loading.set(value); },
|
|
217
235
|
lifecycle: {},
|
|
236
|
+
params,
|
|
218
237
|
};
|
|
219
238
|
}
|
|
220
239
|
/**
|
|
@@ -330,6 +349,10 @@ function getUserDisplayName(account) {
|
|
|
330
349
|
return `${account.firstName || ''} ${account.lastName || ''}`.trim();
|
|
331
350
|
}
|
|
332
351
|
|
|
352
|
+
/**
|
|
353
|
+
* Notification panel definition.
|
|
354
|
+
* Ported from AreaNotification in v0.2.346.
|
|
355
|
+
*/
|
|
333
356
|
function createNotificationPanel() {
|
|
334
357
|
return {
|
|
335
358
|
path: '',
|
|
@@ -426,32 +449,32 @@ function getAllStringValues(obj) {
|
|
|
426
449
|
class PortalService {
|
|
427
450
|
static LANG_STORAGE_KEY = 'apa-language';
|
|
428
451
|
/** Localization labels (defaults to German/de-CH, override via PortalConfig.labels) */
|
|
429
|
-
labels = signal({ ...DEFAULT_LABELS }, ...(ngDevMode ? [{ debugName: "labels" }] : []));
|
|
452
|
+
labels = signal({ ...DEFAULT_LABELS }, ...(ngDevMode ? [{ debugName: "labels" }] : /* istanbul ignore next */ []));
|
|
430
453
|
/** Current language code */
|
|
431
|
-
currentLanguage = signal('de-CH', ...(ngDevMode ? [{ debugName: "currentLanguage" }] : []));
|
|
454
|
+
currentLanguage = signal('de-CH', ...(ngDevMode ? [{ debugName: "currentLanguage" }] : /* istanbul ignore next */ []));
|
|
432
455
|
/** Whether the settings dropdown is open */
|
|
433
|
-
isSettingsOpen = signal(false, ...(ngDevMode ? [{ debugName: "isSettingsOpen" }] : []));
|
|
456
|
+
isSettingsOpen = signal(false, ...(ngDevMode ? [{ debugName: "isSettingsOpen" }] : /* istanbul ignore next */ []));
|
|
434
457
|
/** The blade stack — ordered left-to-right */
|
|
435
|
-
blades = signal([], ...(ngDevMode ? [{ debugName: "blades" }] : []));
|
|
458
|
+
blades = signal([], ...(ngDevMode ? [{ debugName: "blades" }] : /* istanbul ignore next */ []));
|
|
436
459
|
/** Panorama (startboard/dashboard) state */
|
|
437
|
-
panorama = signal(createPanorama(''), ...(ngDevMode ? [{ debugName: "panorama" }] : []));
|
|
460
|
+
panorama = signal(createPanorama(''), ...(ngDevMode ? [{ debugName: "panorama" }] : /* istanbul ignore next */ []));
|
|
438
461
|
/** Notification panel state */
|
|
439
|
-
notification = signal(createNotificationPanel(), ...(ngDevMode ? [{ debugName: "notification" }] : []));
|
|
462
|
+
notification = signal(createNotificationPanel(), ...(ngDevMode ? [{ debugName: "notification" }] : /* istanbul ignore next */ []));
|
|
440
463
|
/** Avatar menu state */
|
|
441
|
-
avatarMenu = signal(createAvatarMenu(), ...(ngDevMode ? [{ debugName: "avatarMenu" }] : []));
|
|
464
|
+
avatarMenu = signal(createAvatarMenu(), ...(ngDevMode ? [{ debugName: "avatarMenu" }] : /* istanbul ignore next */ []));
|
|
442
465
|
/** Shared parameter for passing data between blades */
|
|
443
|
-
parameter = signal({ action: 'none', itemId: 0 }, ...(ngDevMode ? [{ debugName: "parameter" }] : []));
|
|
466
|
+
parameter = signal({ action: 'none', itemId: 0 }, ...(ngDevMode ? [{ debugName: "parameter" }] : /* istanbul ignore next */ []));
|
|
444
467
|
/** Portal theme identifier */
|
|
445
|
-
theme = signal('azure-blue', ...(ngDevMode ? [{ debugName: "theme" }] : []));
|
|
468
|
+
theme = signal('azure-blue', ...(ngDevMode ? [{ debugName: "theme" }] : /* istanbul ignore next */ []));
|
|
446
469
|
/** Whether the panorama is visible (true when no blades are open) */
|
|
447
|
-
isPanoramaVisible = computed(() => this.blades().length === 0, ...(ngDevMode ? [{ debugName: "isPanoramaVisible" }] : []));
|
|
470
|
+
isPanoramaVisible = computed(() => this.blades().length === 0, ...(ngDevMode ? [{ debugName: "isPanoramaVisible" }] : /* istanbul ignore next */ []));
|
|
448
471
|
/** Number of open blades */
|
|
449
|
-
bladeCount = computed(() => this.blades().length, ...(ngDevMode ? [{ debugName: "bladeCount" }] : []));
|
|
472
|
+
bladeCount = computed(() => this.blades().length, ...(ngDevMode ? [{ debugName: "bladeCount" }] : /* istanbul ignore next */ []));
|
|
450
473
|
/** Positioned tiles with layout coordinates */
|
|
451
474
|
positionedTiles = computed(() => {
|
|
452
475
|
const pano = this.panorama();
|
|
453
476
|
return pano.tiles;
|
|
454
|
-
}, ...(ngDevMode ? [{ debugName: "positionedTiles" }] : []));
|
|
477
|
+
}, ...(ngDevMode ? [{ debugName: "positionedTiles" }] : /* istanbul ignore next */ []));
|
|
455
478
|
/** Consumer label overrides from PortalConfig — re-applied on every language switch */
|
|
456
479
|
_configLabelOverrides = {};
|
|
457
480
|
/**
|
|
@@ -559,20 +582,26 @@ class PortalService {
|
|
|
559
582
|
}
|
|
560
583
|
// --- Notification panel ---
|
|
561
584
|
/** Show the notification panel */
|
|
562
|
-
showNotification(path, width = 250) {
|
|
585
|
+
showNotification(path, width = 250, lifecycle) {
|
|
586
|
+
lifecycle?.onShow?.();
|
|
563
587
|
this.notification.update((n) => ({
|
|
564
588
|
...n,
|
|
565
589
|
path,
|
|
566
590
|
width,
|
|
567
591
|
isVisible: true,
|
|
592
|
+
lifecycle,
|
|
568
593
|
}));
|
|
569
594
|
}
|
|
570
|
-
/** Hide the notification panel */
|
|
595
|
+
/** Hide the notification panel. Aborted if onHide() returns false. */
|
|
571
596
|
hideNotification() {
|
|
597
|
+
const current = this.notification();
|
|
598
|
+
if (current.lifecycle?.onHide?.() === false)
|
|
599
|
+
return;
|
|
572
600
|
this.notification.update((n) => ({
|
|
573
601
|
...n,
|
|
574
602
|
path: '',
|
|
575
603
|
isVisible: false,
|
|
604
|
+
lifecycle: undefined,
|
|
576
605
|
}));
|
|
577
606
|
}
|
|
578
607
|
// --- Avatar menu ---
|
|
@@ -591,10 +620,64 @@ class PortalService {
|
|
|
591
620
|
setAvatarMenuItems(items) {
|
|
592
621
|
this.avatarMenu.update((m) => ({ ...m, items }));
|
|
593
622
|
}
|
|
594
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
595
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.
|
|
623
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.3", ngImport: i0, type: PortalService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
624
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.3", ngImport: i0, type: PortalService, providedIn: 'root' });
|
|
625
|
+
}
|
|
626
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.3", ngImport: i0, type: PortalService, decorators: [{
|
|
627
|
+
type: Injectable,
|
|
628
|
+
args: [{ providedIn: 'root' }]
|
|
629
|
+
}] });
|
|
630
|
+
|
|
631
|
+
/**
|
|
632
|
+
* Registry for mapping blade paths to Angular components.
|
|
633
|
+
*
|
|
634
|
+
* Allows consumer apps to register components for blade paths,
|
|
635
|
+
* enabling dynamic rendering in BladeHostComponent without
|
|
636
|
+
* manual @switch blocks.
|
|
637
|
+
*
|
|
638
|
+
* Usage in app bootstrap:
|
|
639
|
+
* ```typescript
|
|
640
|
+
* const registry = inject(BladeRegistry);
|
|
641
|
+
* registry.register('customers', CustomerNavComponent);
|
|
642
|
+
* registry.register('customers/list', CustomerListComponent, { title: 'All Customers', width: 585 });
|
|
643
|
+
* ```
|
|
644
|
+
*
|
|
645
|
+
* Or register multiple at once:
|
|
646
|
+
* ```typescript
|
|
647
|
+
* registry.registerAll({
|
|
648
|
+
* 'customers': CustomerNavComponent,
|
|
649
|
+
* 'customers/list': CustomerListComponent,
|
|
650
|
+
* });
|
|
651
|
+
* ```
|
|
652
|
+
*/
|
|
653
|
+
class BladeRegistry {
|
|
654
|
+
registry = new Map();
|
|
655
|
+
/** Register a component for a blade path with optional metadata (title, width, params) */
|
|
656
|
+
register(path, component, metadata) {
|
|
657
|
+
this.registry.set(path.toLowerCase(), { component, ...metadata });
|
|
658
|
+
}
|
|
659
|
+
/** Register multiple blade path → component mappings */
|
|
660
|
+
registerAll(mappings) {
|
|
661
|
+
for (const [path, component] of Object.entries(mappings)) {
|
|
662
|
+
this.register(path, component);
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
/** Get the component registered for a path, if any */
|
|
666
|
+
get(path) {
|
|
667
|
+
return this.registry.get(path.toLowerCase())?.component;
|
|
668
|
+
}
|
|
669
|
+
/** Get the full registry entry (component + metadata) for a path */
|
|
670
|
+
getEntry(path) {
|
|
671
|
+
return this.registry.get(path.toLowerCase());
|
|
672
|
+
}
|
|
673
|
+
/** Check if a component is registered for a path */
|
|
674
|
+
has(path) {
|
|
675
|
+
return this.registry.has(path.toLowerCase());
|
|
676
|
+
}
|
|
677
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.3", ngImport: i0, type: BladeRegistry, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
678
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.3", ngImport: i0, type: BladeRegistry, providedIn: 'root' });
|
|
596
679
|
}
|
|
597
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
680
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.3", ngImport: i0, type: BladeRegistry, decorators: [{
|
|
598
681
|
type: Injectable,
|
|
599
682
|
args: [{ providedIn: 'root' }]
|
|
600
683
|
}] });
|
|
@@ -605,18 +688,92 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
|
|
|
605
688
|
*
|
|
606
689
|
* Manages the blade stack: adding, removing, cascade-closing,
|
|
607
690
|
* and panorama visibility toggling.
|
|
691
|
+
*
|
|
692
|
+
* When a BladeRegistry is available, metadata (title, width) from the
|
|
693
|
+
* registry is used as defaults — explicit arguments take precedence.
|
|
608
694
|
*/
|
|
609
695
|
class BladeService {
|
|
610
696
|
portal = inject(PortalService);
|
|
697
|
+
registry = inject(BladeRegistry);
|
|
698
|
+
/**
|
|
699
|
+
* Unsaved-changes guard registry: blade path -> predicate returning true when the blade has
|
|
700
|
+
* unsaved edits. Populated by apa-blade-detail (from the projected NgForm). Consulted before a
|
|
701
|
+
* blade is closed/replaced or the page is left, so the user can confirm discarding changes.
|
|
702
|
+
* Message can be localised via PortalLabels.unsavedChangesConfirm; the navigation flow is
|
|
703
|
+
* synchronous, so a synchronous window.confirm is used.
|
|
704
|
+
*/
|
|
705
|
+
dirtyChecks = new Map();
|
|
706
|
+
/** Optional override for the confirmation message (defaults to the de-CH label). */
|
|
707
|
+
unsavedChangesMessage = DEFAULT_LABELS.unsavedChangesConfirm;
|
|
708
|
+
constructor() {
|
|
709
|
+
// Native browser guard when the whole page/tab is closed or reloaded with unsaved edits.
|
|
710
|
+
if (typeof window !== 'undefined') {
|
|
711
|
+
window.addEventListener('beforeunload', (e) => {
|
|
712
|
+
if (this.anyDirty()) {
|
|
713
|
+
e.preventDefault();
|
|
714
|
+
e.returnValue = '';
|
|
715
|
+
}
|
|
716
|
+
});
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
/** Register a blade's unsaved-changes predicate (called by apa-blade-detail). */
|
|
720
|
+
registerDirtyCheck(path, isDirty) {
|
|
721
|
+
this.dirtyChecks.set(path.toLowerCase(), isDirty);
|
|
722
|
+
}
|
|
723
|
+
/** Remove a blade's unsaved-changes predicate (called when the detail is destroyed). */
|
|
724
|
+
unregisterDirtyCheck(path) {
|
|
725
|
+
this.dirtyChecks.delete(path.toLowerCase());
|
|
726
|
+
}
|
|
727
|
+
isPathDirty(path) {
|
|
728
|
+
const check = this.dirtyChecks.get(path.toLowerCase());
|
|
729
|
+
try {
|
|
730
|
+
return check ? check() === true : false;
|
|
731
|
+
}
|
|
732
|
+
catch {
|
|
733
|
+
return false;
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
anyDirty() {
|
|
737
|
+
for (const path of this.dirtyChecks.keys()) {
|
|
738
|
+
if (this.isPathDirty(path))
|
|
739
|
+
return true;
|
|
740
|
+
}
|
|
741
|
+
return false;
|
|
742
|
+
}
|
|
743
|
+
/**
|
|
744
|
+
* Returns true if it's OK to proceed with removing the given blade paths: none of them is dirty,
|
|
745
|
+
* or the user confirmed discarding the changes. Shows one confirmation regardless of how many
|
|
746
|
+
* dirty blades are affected.
|
|
747
|
+
*/
|
|
748
|
+
mayDiscard(removedPaths) {
|
|
749
|
+
const anyDirty = removedPaths.some((p) => this.isPathDirty(p));
|
|
750
|
+
if (!anyDirty)
|
|
751
|
+
return true;
|
|
752
|
+
return typeof window === 'undefined' ? true : window.confirm(this.unsavedChangesMessage);
|
|
753
|
+
}
|
|
754
|
+
/** Drop dirty-check entries for paths that are no longer open. */
|
|
755
|
+
pruneDirtyChecks() {
|
|
756
|
+
const open = new Set(this.portal.blades().map((b) => b.path));
|
|
757
|
+
for (const path of [...this.dirtyChecks.keys()]) {
|
|
758
|
+
if (!open.has(path))
|
|
759
|
+
this.dirtyChecks.delete(path);
|
|
760
|
+
}
|
|
761
|
+
}
|
|
611
762
|
/**
|
|
612
763
|
* Set the first blade (e.g., when opening a top-level item from a tile).
|
|
613
764
|
* Clears all existing blades, hides panorama, and adds the new blade.
|
|
614
765
|
*
|
|
615
766
|
* Ported from AreaBlades.setFirstBlade() in v0.2.346.
|
|
616
767
|
*/
|
|
617
|
-
setFirstBlade(path, title = '', width
|
|
768
|
+
setFirstBlade(path, title = '', width) {
|
|
769
|
+
const existing = this.portal.blades();
|
|
770
|
+
if (existing.length > 0 && !this.mayDiscard(existing.map((b) => b.path))) {
|
|
771
|
+
return existing[0];
|
|
772
|
+
}
|
|
773
|
+
this.dirtyChecks.clear();
|
|
618
774
|
this.portal.blades.set([]);
|
|
619
|
-
const
|
|
775
|
+
const entry = this.registry.getEntry(path);
|
|
776
|
+
const blade = createBlade(path.toLowerCase(), title || entry?.title || path, width ?? entry?.width ?? 315);
|
|
620
777
|
this.portal.blades.set([blade]);
|
|
621
778
|
return blade;
|
|
622
779
|
}
|
|
@@ -626,21 +783,23 @@ class BladeService {
|
|
|
626
783
|
*
|
|
627
784
|
* Ported from AreaBlades.addBlade() in v0.2.346.
|
|
628
785
|
*/
|
|
629
|
-
addBlade(path, senderPath = '', title = '', width
|
|
786
|
+
addBlade(path, senderPath = '', title = '', width, params) {
|
|
630
787
|
if (!path)
|
|
631
788
|
return undefined;
|
|
632
789
|
const normalizedPath = path.toLowerCase();
|
|
633
|
-
|
|
634
|
-
//
|
|
635
|
-
|
|
790
|
+
// Cascade close first: remove blades after the sender
|
|
791
|
+
// This ensures a blade at the same path gets recreated with new params
|
|
792
|
+
if (senderPath) {
|
|
793
|
+
if (!this.clearChild(senderPath))
|
|
794
|
+
return undefined;
|
|
795
|
+
}
|
|
796
|
+
// Check if blade already exists (after cascade close)
|
|
797
|
+
const existing = this.portal.blades().find((b) => b.path === normalizedPath);
|
|
636
798
|
if (existing) {
|
|
637
799
|
return existing;
|
|
638
800
|
}
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
this.clearChild(senderPath);
|
|
642
|
-
}
|
|
643
|
-
const blade = createBlade(normalizedPath, title, width);
|
|
801
|
+
const entry = this.registry.getEntry(normalizedPath);
|
|
802
|
+
const blade = createBlade(normalizedPath, title || entry?.title || normalizedPath, width ?? entry?.width ?? 315, params);
|
|
644
803
|
this.portal.blades.update((b) => [...b, blade]);
|
|
645
804
|
return blade;
|
|
646
805
|
}
|
|
@@ -648,7 +807,7 @@ class BladeService {
|
|
|
648
807
|
* Open a blade from a navigation event (e.g., tile click, nav item click).
|
|
649
808
|
* Wraps addBlade with AddBladeEventArgs for compatibility.
|
|
650
809
|
*/
|
|
651
|
-
openBlade(args, title = '', width
|
|
810
|
+
openBlade(args, title = '', width) {
|
|
652
811
|
return this.addBlade(args.path, args.pathSender, title, width);
|
|
653
812
|
}
|
|
654
813
|
/**
|
|
@@ -656,7 +815,10 @@ class BladeService {
|
|
|
656
815
|
* Ported from AreaBlades.clearAll() in v0.2.346.
|
|
657
816
|
*/
|
|
658
817
|
clearAll() {
|
|
818
|
+
if (!this.mayDiscard(this.portal.blades().map((b) => b.path)))
|
|
819
|
+
return;
|
|
659
820
|
this.portal.blades.set([]);
|
|
821
|
+
this.dirtyChecks.clear();
|
|
660
822
|
}
|
|
661
823
|
/**
|
|
662
824
|
* Remove a specific blade and all blades to its right.
|
|
@@ -669,7 +831,10 @@ class BladeService {
|
|
|
669
831
|
const blades = this.portal.blades();
|
|
670
832
|
const index = blades.findIndex((b) => b.path === normalizedPath);
|
|
671
833
|
if (index >= 0) {
|
|
834
|
+
if (!this.mayDiscard(blades.slice(index).map((b) => b.path)))
|
|
835
|
+
return false;
|
|
672
836
|
this.portal.blades.set(blades.slice(0, index));
|
|
837
|
+
this.pruneDirtyChecks();
|
|
673
838
|
}
|
|
674
839
|
else {
|
|
675
840
|
// Check notification area
|
|
@@ -678,6 +843,7 @@ class BladeService {
|
|
|
678
843
|
this.portal.hideNotification();
|
|
679
844
|
}
|
|
680
845
|
}
|
|
846
|
+
return true;
|
|
681
847
|
}
|
|
682
848
|
/**
|
|
683
849
|
* Remove all blades AFTER a given path (keeps the blade itself).
|
|
@@ -687,13 +853,17 @@ class BladeService {
|
|
|
687
853
|
*/
|
|
688
854
|
clearChild(path) {
|
|
689
855
|
if (!path)
|
|
690
|
-
return;
|
|
856
|
+
return true;
|
|
691
857
|
const normalizedPath = path.toLowerCase();
|
|
692
858
|
const blades = this.portal.blades();
|
|
693
859
|
const index = blades.findIndex((b) => b.path === normalizedPath);
|
|
694
860
|
if (index >= 0) {
|
|
861
|
+
if (!this.mayDiscard(blades.slice(index + 1).map((b) => b.path)))
|
|
862
|
+
return false;
|
|
695
863
|
this.portal.blades.set(blades.slice(0, index + 1));
|
|
864
|
+
this.pruneDirtyChecks();
|
|
696
865
|
}
|
|
866
|
+
return true;
|
|
697
867
|
}
|
|
698
868
|
/**
|
|
699
869
|
* Remove blades at and beyond a specific 1-based level.
|
|
@@ -703,7 +873,10 @@ class BladeService {
|
|
|
703
873
|
const adjustedLevel = level <= 0 ? 1 : level;
|
|
704
874
|
const blades = this.portal.blades();
|
|
705
875
|
if (adjustedLevel <= blades.length) {
|
|
876
|
+
if (!this.mayDiscard(blades.slice(adjustedLevel - 1).map((b) => b.path)))
|
|
877
|
+
return;
|
|
706
878
|
this.portal.blades.set(blades.slice(0, adjustedLevel - 1));
|
|
879
|
+
this.pruneDirtyChecks();
|
|
707
880
|
}
|
|
708
881
|
}
|
|
709
882
|
/**
|
|
@@ -713,7 +886,10 @@ class BladeService {
|
|
|
713
886
|
clearLastLevel() {
|
|
714
887
|
const blades = this.portal.blades();
|
|
715
888
|
if (blades.length > 0) {
|
|
889
|
+
if (!this.mayDiscard([blades[blades.length - 1].path]))
|
|
890
|
+
return;
|
|
716
891
|
this.portal.blades.set(blades.slice(0, -1));
|
|
892
|
+
this.pruneDirtyChecks();
|
|
717
893
|
}
|
|
718
894
|
}
|
|
719
895
|
/**
|
|
@@ -735,63 +911,313 @@ class BladeService {
|
|
|
735
911
|
isBladeOpen(path) {
|
|
736
912
|
return this.portal.blades().some((b) => b.path === path.toLowerCase());
|
|
737
913
|
}
|
|
738
|
-
|
|
739
|
-
|
|
914
|
+
/**
|
|
915
|
+
* Get URL-persisted parameters for a blade by path.
|
|
916
|
+
* Returns empty object if blade not found or has no params.
|
|
917
|
+
*/
|
|
918
|
+
getBladeParams(path) {
|
|
919
|
+
return this.getBlade(path)?.params ?? {};
|
|
920
|
+
}
|
|
921
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.3", ngImport: i0, type: BladeService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
922
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.3", ngImport: i0, type: BladeService, providedIn: 'root' });
|
|
740
923
|
}
|
|
741
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
924
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.3", ngImport: i0, type: BladeService, decorators: [{
|
|
742
925
|
type: Injectable,
|
|
743
926
|
args: [{ providedIn: 'root' }]
|
|
744
|
-
}] });
|
|
927
|
+
}], ctorParameters: () => [] });
|
|
745
928
|
|
|
929
|
+
/** @internal */
|
|
930
|
+
const BLADE_ROUTER_CONFIG = new InjectionToken('BLADE_ROUTER_CONFIG');
|
|
746
931
|
/**
|
|
747
|
-
*
|
|
748
|
-
*
|
|
749
|
-
* Allows consumer apps to register components for blade paths,
|
|
750
|
-
* enabling dynamic rendering in BladeHostComponent without
|
|
751
|
-
* manual @switch blocks.
|
|
932
|
+
* Enables opt-in URL synchronization for the blade stack.
|
|
752
933
|
*
|
|
753
|
-
*
|
|
934
|
+
* Add alongside `provideRouter()` and `providePortalAzure()`:
|
|
754
935
|
* ```typescript
|
|
755
|
-
* const
|
|
756
|
-
*
|
|
757
|
-
*
|
|
936
|
+
* export const appConfig: ApplicationConfig = {
|
|
937
|
+
* providers: [
|
|
938
|
+
* provideRouter(routes),
|
|
939
|
+
* providePortalAzure({ title: 'My Portal', ... }),
|
|
940
|
+
* provideBladeRouter(),
|
|
941
|
+
* ],
|
|
942
|
+
* };
|
|
758
943
|
* ```
|
|
759
944
|
*
|
|
760
|
-
*
|
|
945
|
+
* Optionally pass a config to set a fixed route prefix:
|
|
761
946
|
* ```typescript
|
|
762
|
-
*
|
|
763
|
-
*
|
|
764
|
-
* 'customers/list': CustomerListComponent,
|
|
765
|
-
* });
|
|
947
|
+
* provideBladeRouter({ prefix: 'app' }) // → /app/customers/list
|
|
948
|
+
* provideBladeRouter({ prefix: '' }) // → /customers/list (no prefix)
|
|
766
949
|
* ```
|
|
950
|
+
*
|
|
951
|
+
* Without this provider, blade navigation remains purely in-memory.
|
|
767
952
|
*/
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
953
|
+
function provideBladeRouter(config) {
|
|
954
|
+
return makeEnvironmentProviders([
|
|
955
|
+
BladeRouterService,
|
|
956
|
+
{ provide: BLADE_ROUTER_CONFIG, useValue: config ?? {} },
|
|
957
|
+
{
|
|
958
|
+
provide: ENVIRONMENT_INITIALIZER,
|
|
959
|
+
multi: true,
|
|
960
|
+
useFactory: () => () => inject(BladeRouterService),
|
|
961
|
+
},
|
|
962
|
+
]);
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
/**
|
|
966
|
+
* Optional service that syncs the blade stack with the browser URL.
|
|
967
|
+
*
|
|
968
|
+
* When enabled, blade paths are stored as URL path segments with matrix params:
|
|
969
|
+
* `/crm/customers/list/detail;id=1`
|
|
970
|
+
*
|
|
971
|
+
* This makes blade states bookmarkable, shareable, and back-button navigable.
|
|
972
|
+
*
|
|
973
|
+
* Opt-in: add `provideBladeRouter()` to your app providers alongside `provideRouter()`.
|
|
974
|
+
* Without it, blade navigation remains purely in-memory (no URL changes).
|
|
975
|
+
*/
|
|
976
|
+
class BladeRouterService {
|
|
977
|
+
router = inject(Router);
|
|
978
|
+
portal = inject(PortalService);
|
|
979
|
+
registry = inject(BladeRegistry);
|
|
980
|
+
destroyRef = inject(DestroyRef);
|
|
981
|
+
config = inject(BLADE_ROUTER_CONFIG, { optional: true }) ?? {};
|
|
982
|
+
_syncingFromUrl = false;
|
|
983
|
+
_initialRestoreDone = false;
|
|
984
|
+
constructor() {
|
|
985
|
+
// Sync: blade stack changes → URL path segments
|
|
986
|
+
effect(() => {
|
|
987
|
+
const blades = this.portal.blades();
|
|
988
|
+
if (this._syncingFromUrl)
|
|
989
|
+
return;
|
|
990
|
+
if (blades.length === 0 && !this._initialRestoreDone)
|
|
991
|
+
return;
|
|
992
|
+
const prefix = this.getEffectivePrefix();
|
|
993
|
+
const bladePath = this.encodeBladesToPath(blades);
|
|
994
|
+
const targetUrl = prefix
|
|
995
|
+
? (bladePath ? `/${prefix}/${bladePath}` : `/${prefix}`)
|
|
996
|
+
: (bladePath ? `/${bladePath}` : `/`);
|
|
997
|
+
const currentPath = this.router.url.split('?')[0].split(';')[0];
|
|
998
|
+
// Only navigate if the path actually changed (avoid loops)
|
|
999
|
+
if (this.normalizeUrl(currentPath) !== this.normalizeUrl(targetUrl)) {
|
|
1000
|
+
this.router.navigateByUrl(targetUrl);
|
|
1001
|
+
}
|
|
1002
|
+
});
|
|
1003
|
+
// Sync: URL path segments → blade stack (on NavigationEnd)
|
|
1004
|
+
const sub = this.router.events
|
|
1005
|
+
.pipe(filter((e) => e instanceof NavigationEnd))
|
|
1006
|
+
.subscribe((e) => {
|
|
1007
|
+
this._initialRestoreDone = true;
|
|
1008
|
+
const url = e.urlAfterRedirects;
|
|
1009
|
+
// Backward compat: handle legacy ?blades= query param
|
|
1010
|
+
const legacyParam = this.extractLegacyBladesParam(url);
|
|
1011
|
+
if (legacyParam !== null) {
|
|
1012
|
+
this.handleLegacyUrl(legacyParam);
|
|
1013
|
+
return;
|
|
1014
|
+
}
|
|
1015
|
+
this.restoreFromPath(url);
|
|
1016
|
+
});
|
|
1017
|
+
this.destroyRef.onDestroy(() => sub.unsubscribe());
|
|
773
1018
|
}
|
|
774
|
-
/**
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
1019
|
+
/**
|
|
1020
|
+
* Encode the blade stack into URL path segments.
|
|
1021
|
+
* Each blade contributes its "short name" (suffix after parent prefix).
|
|
1022
|
+
* Blades with params get Angular matrix params appended.
|
|
1023
|
+
*
|
|
1024
|
+
* Example: [customers, customers/list, customers/detail{id:1}]
|
|
1025
|
+
* → "customers/list/detail;id=1"
|
|
1026
|
+
*/
|
|
1027
|
+
encodeBladesToPath(blades) {
|
|
1028
|
+
if (blades.length === 0)
|
|
1029
|
+
return '';
|
|
1030
|
+
const segments = [];
|
|
1031
|
+
const allPaths = [];
|
|
1032
|
+
for (const blade of blades) {
|
|
1033
|
+
// Compute short name: strip the longest matching ancestor prefix
|
|
1034
|
+
let shortName = blade.path;
|
|
1035
|
+
for (let i = allPaths.length - 1; i >= 0; i--) {
|
|
1036
|
+
if (blade.path.startsWith(allPaths[i] + '/')) {
|
|
1037
|
+
shortName = blade.path.substring(allPaths[i].length + 1);
|
|
1038
|
+
break;
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
// Append matrix params if any
|
|
1042
|
+
let segment = shortName;
|
|
1043
|
+
if (blade.params && Object.keys(blade.params).length > 0) {
|
|
1044
|
+
const paramStr = Object.entries(blade.params)
|
|
1045
|
+
.map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`)
|
|
1046
|
+
.join(';');
|
|
1047
|
+
segment += ';' + paramStr;
|
|
1048
|
+
}
|
|
1049
|
+
segments.push(segment);
|
|
1050
|
+
allPaths.push(blade.path);
|
|
778
1051
|
}
|
|
1052
|
+
return segments.join('/');
|
|
779
1053
|
}
|
|
780
|
-
/**
|
|
781
|
-
|
|
782
|
-
|
|
1054
|
+
/**
|
|
1055
|
+
* Decode URL path segments into blade definitions.
|
|
1056
|
+
* Resolves short segment names to full blade paths using the registry.
|
|
1057
|
+
*
|
|
1058
|
+
* Example: "customers/list/detail;id=1" with route prefix "crm"
|
|
1059
|
+
* → [customers, customers/list, customers/detail] with detail.params={id:'1'}
|
|
1060
|
+
*/
|
|
1061
|
+
decodeBladesFromPath(pathAfterPrefix) {
|
|
1062
|
+
if (!pathAfterPrefix)
|
|
1063
|
+
return [];
|
|
1064
|
+
const rawSegments = pathAfterPrefix.split('/').filter(Boolean);
|
|
1065
|
+
const blades = [];
|
|
1066
|
+
const resolvedPaths = [];
|
|
1067
|
+
for (const rawSegment of rawSegments) {
|
|
1068
|
+
// Parse matrix params from segment: "detail;id=1" → name="detail", params={id:'1'}
|
|
1069
|
+
const { name, params } = this.parseSegment(rawSegment);
|
|
1070
|
+
// Resolve short name to full blade path
|
|
1071
|
+
const fullPath = this.resolveSegment(name, resolvedPaths);
|
|
1072
|
+
if (!fullPath) {
|
|
1073
|
+
console.warn(`[BladeRouter] Could not resolve segment "${name}" to a registered blade path`);
|
|
1074
|
+
continue;
|
|
1075
|
+
}
|
|
1076
|
+
const entry = this.registry.getEntry(fullPath);
|
|
1077
|
+
const blade = createBlade(fullPath, entry?.title ?? fullPath, entry?.width ?? 315, params);
|
|
1078
|
+
blades.push(blade);
|
|
1079
|
+
resolvedPaths.push(fullPath);
|
|
1080
|
+
}
|
|
1081
|
+
return blades;
|
|
783
1082
|
}
|
|
784
|
-
/**
|
|
785
|
-
|
|
786
|
-
|
|
1083
|
+
/**
|
|
1084
|
+
* Parse a URL segment into its name and matrix parameters.
|
|
1085
|
+
* "detail;id=1;mode=edit" → { name: "detail", params: { id: "1", mode: "edit" } }
|
|
1086
|
+
*/
|
|
1087
|
+
parseSegment(segment) {
|
|
1088
|
+
const parts = segment.split(';');
|
|
1089
|
+
const name = decodeURIComponent(parts[0]);
|
|
1090
|
+
const params = {};
|
|
1091
|
+
for (let i = 1; i < parts.length; i++) {
|
|
1092
|
+
const eqIndex = parts[i].indexOf('=');
|
|
1093
|
+
if (eqIndex > 0) {
|
|
1094
|
+
const key = decodeURIComponent(parts[i].substring(0, eqIndex));
|
|
1095
|
+
const value = decodeURIComponent(parts[i].substring(eqIndex + 1));
|
|
1096
|
+
params[key] = value;
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
return { name, params };
|
|
787
1100
|
}
|
|
788
|
-
|
|
789
|
-
|
|
1101
|
+
/**
|
|
1102
|
+
* Resolve a short segment name to a full blade path using the registry.
|
|
1103
|
+
* Tries parent/segment first, then walks up ancestors.
|
|
1104
|
+
*/
|
|
1105
|
+
resolveSegment(name, previousPaths) {
|
|
1106
|
+
// Direct match: the segment itself is a registered path
|
|
1107
|
+
if (this.registry.has(name)) {
|
|
1108
|
+
return name.toLowerCase();
|
|
1109
|
+
}
|
|
1110
|
+
// Try prepending each previous blade path (most recent first)
|
|
1111
|
+
for (let i = previousPaths.length - 1; i >= 0; i--) {
|
|
1112
|
+
const candidate = previousPaths[i] + '/' + name;
|
|
1113
|
+
if (this.registry.has(candidate)) {
|
|
1114
|
+
return candidate.toLowerCase();
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
return null;
|
|
1118
|
+
}
|
|
1119
|
+
/** Restore blade stack from a path-based URL */
|
|
1120
|
+
restoreFromPath(url) {
|
|
1121
|
+
const prefix = this.getEffectivePrefix();
|
|
1122
|
+
const path = url.split('?')[0]; // strip query params
|
|
1123
|
+
let pathAfterPrefix;
|
|
1124
|
+
if (prefix) {
|
|
1125
|
+
const prefixPattern = '/' + prefix;
|
|
1126
|
+
if (!path.startsWith(prefixPattern))
|
|
1127
|
+
return;
|
|
1128
|
+
pathAfterPrefix = path.substring(prefixPattern.length + 1); // +1 for trailing /
|
|
1129
|
+
}
|
|
1130
|
+
else {
|
|
1131
|
+
// No prefix: everything after the leading / is blade path
|
|
1132
|
+
pathAfterPrefix = path.substring(1);
|
|
1133
|
+
}
|
|
1134
|
+
const newBlades = this.decodeBladesFromPath(pathAfterPrefix);
|
|
1135
|
+
const currentPaths = this.portal.blades().map((b) => b.path);
|
|
1136
|
+
const newPaths = newBlades.map((b) => b.path);
|
|
1137
|
+
// Skip if blade stack already matches (paths and params)
|
|
1138
|
+
if (newPaths.length === currentPaths.length &&
|
|
1139
|
+
newPaths.every((p, i) => p === currentPaths[i]) &&
|
|
1140
|
+
this.paramsMatch(newBlades, this.portal.blades())) {
|
|
1141
|
+
return;
|
|
1142
|
+
}
|
|
1143
|
+
this._syncingFromUrl = true;
|
|
1144
|
+
this.portal.blades.set(newBlades);
|
|
1145
|
+
// Restore parameter signal for backward compat (from deepest blade with params)
|
|
1146
|
+
const deepestWithParams = [...newBlades].reverse().find((b) => Object.keys(b.params).length > 0);
|
|
1147
|
+
if (deepestWithParams && deepestWithParams.params['id']) {
|
|
1148
|
+
this.portal.setParameter({
|
|
1149
|
+
action: 'edit',
|
|
1150
|
+
itemId: Number(deepestWithParams.params['id']) || 0,
|
|
1151
|
+
});
|
|
1152
|
+
}
|
|
1153
|
+
queueMicrotask(() => (this._syncingFromUrl = false));
|
|
1154
|
+
}
|
|
1155
|
+
/** Check if params match between two blade arrays */
|
|
1156
|
+
paramsMatch(a, b) {
|
|
1157
|
+
if (a.length !== b.length)
|
|
1158
|
+
return false;
|
|
1159
|
+
return a.every((blade, i) => {
|
|
1160
|
+
const aKeys = Object.keys(blade.params);
|
|
1161
|
+
const bKeys = Object.keys(b[i].params);
|
|
1162
|
+
if (aKeys.length !== bKeys.length)
|
|
1163
|
+
return false;
|
|
1164
|
+
return aKeys.every((k) => blade.params[k] === b[i].params[k]);
|
|
1165
|
+
});
|
|
1166
|
+
}
|
|
1167
|
+
/** Get the route prefix from the current URL (e.g., 'crm' from '/crm/...') */
|
|
1168
|
+
getRoutePrefix() {
|
|
1169
|
+
const url = this.router.url.split('?')[0];
|
|
1170
|
+
const firstSegment = url.split('/').filter(Boolean)[0] ?? '';
|
|
1171
|
+
return firstSegment.split(';')[0];
|
|
1172
|
+
}
|
|
1173
|
+
/** Normalize URL for comparison (strip trailing slashes) */
|
|
1174
|
+
normalizeUrl(url) {
|
|
1175
|
+
return url.replace(/\/+$/, '') || '/';
|
|
1176
|
+
}
|
|
1177
|
+
// --- Legacy backward compatibility ---
|
|
1178
|
+
/** Extract legacy `blades` query parameter from URL */
|
|
1179
|
+
extractLegacyBladesParam(url) {
|
|
1180
|
+
const qIndex = url.indexOf('?');
|
|
1181
|
+
if (qIndex === -1)
|
|
1182
|
+
return null;
|
|
1183
|
+
const params = new URLSearchParams(url.substring(qIndex));
|
|
1184
|
+
return params.get('blades');
|
|
1185
|
+
}
|
|
1186
|
+
/** Handle legacy ?blades= URL by redirecting to new path format */
|
|
1187
|
+
handleLegacyUrl(bladesParam) {
|
|
1188
|
+
const paths = bladesParam.split(',').filter(Boolean);
|
|
1189
|
+
if (paths.length === 0)
|
|
1190
|
+
return;
|
|
1191
|
+
// Create temporary blades to encode
|
|
1192
|
+
const blades = paths.map((path) => {
|
|
1193
|
+
const entry = this.registry.getEntry(path);
|
|
1194
|
+
return createBlade(path, entry?.title ?? path, entry?.width ?? 315);
|
|
1195
|
+
});
|
|
1196
|
+
const prefix = this.getEffectivePrefix();
|
|
1197
|
+
const bladePath = this.encodeBladesToPath(blades);
|
|
1198
|
+
const newUrl = prefix
|
|
1199
|
+
? (bladePath ? `/${prefix}/${bladePath}` : `/${prefix}`)
|
|
1200
|
+
: (bladePath ? `/${bladePath}` : `/`);
|
|
1201
|
+
// Redirect to new format
|
|
1202
|
+
this.router.navigateByUrl(newUrl, { replaceUrl: true });
|
|
1203
|
+
}
|
|
1204
|
+
/**
|
|
1205
|
+
* Return the effective route prefix. If a prefix was configured via
|
|
1206
|
+
* `provideBladeRouter({ prefix })`, use it (including empty string).
|
|
1207
|
+
* Otherwise fall back to dynamically reading the first URL segment.
|
|
1208
|
+
*/
|
|
1209
|
+
getEffectivePrefix() {
|
|
1210
|
+
if (this.config.prefix !== undefined) {
|
|
1211
|
+
return this.config.prefix;
|
|
1212
|
+
}
|
|
1213
|
+
return this.getRoutePrefix();
|
|
1214
|
+
}
|
|
1215
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.3", ngImport: i0, type: BladeRouterService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1216
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.3", ngImport: i0, type: BladeRouterService });
|
|
790
1217
|
}
|
|
791
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
792
|
-
type: Injectable
|
|
793
|
-
|
|
794
|
-
}] });
|
|
1218
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.3", ngImport: i0, type: BladeRouterService, decorators: [{
|
|
1219
|
+
type: Injectable
|
|
1220
|
+
}], ctorParameters: () => [] });
|
|
795
1221
|
|
|
796
1222
|
/**
|
|
797
1223
|
* Provide the angular-portal-azure library configuration.
|
|
@@ -834,27 +1260,32 @@ function providePortalAzure(config) {
|
|
|
834
1260
|
* ```
|
|
835
1261
|
*/
|
|
836
1262
|
class TileComponent {
|
|
837
|
-
tile = input.required(...(ngDevMode ? [{ debugName: "tile" }] : []));
|
|
1263
|
+
tile = input.required(...(ngDevMode ? [{ debugName: "tile" }] : /* istanbul ignore next */ []));
|
|
838
1264
|
tileClick = output();
|
|
839
1265
|
onClick() {
|
|
840
|
-
|
|
1266
|
+
if (!this.tile().disabled) {
|
|
1267
|
+
this.tileClick.emit(this.tile());
|
|
1268
|
+
}
|
|
841
1269
|
}
|
|
842
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
843
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.
|
|
1270
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.3", ngImport: i0, type: TileComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1271
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.3", type: TileComponent, isStandalone: true, selector: "apa-tile", inputs: { tile: { classPropertyName: "tile", publicName: "tile", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { tileClick: "tileClick" }, ngImport: i0, template: `
|
|
844
1272
|
<section
|
|
845
1273
|
class="fxs-tile fx-rightClick fxs-flowlayout-element"
|
|
846
1274
|
[class.fxs-tilesize-normal]="tile().size === 'normal'"
|
|
847
1275
|
[class.fxs-tilesize-mini]="tile().size === 'mini'"
|
|
848
1276
|
[class.fxs-tilesize-herowide]="tile().size === 'herowide'"
|
|
849
1277
|
[class.fxs-tilesize-small]="tile().size === 'small'">
|
|
850
|
-
<div class="fxs-part
|
|
1278
|
+
<div class="fxs-part"
|
|
1279
|
+
[class.fxs-part-clickable]="!tile().disabled"
|
|
851
1280
|
role="button"
|
|
852
|
-
tabindex="0"
|
|
1281
|
+
[attr.tabindex]="tile().disabled ? -1 : 0"
|
|
853
1282
|
[attr.aria-label]="tile().title"
|
|
1283
|
+
[attr.aria-disabled]="tile().disabled || null"
|
|
854
1284
|
(click)="onClick()"
|
|
855
1285
|
(keydown.enter)="onClick()"
|
|
856
1286
|
(keydown.space)="onClick(); $event.preventDefault()"
|
|
857
|
-
style="
|
|
1287
|
+
[style.cursor]="tile().disabled ? 'default' : 'pointer'"
|
|
1288
|
+
[style.opacity]="tile().disabled ? 0.45 : 1">
|
|
858
1289
|
<header class="fxs-part-title">
|
|
859
1290
|
<h2 class="msportalfx-tooltip-overflow">{{ tile().title }}</h2>
|
|
860
1291
|
@if (tile().subtitle) {
|
|
@@ -867,7 +1298,7 @@ class TileComponent {
|
|
|
867
1298
|
</section>
|
|
868
1299
|
`, isInline: true, styles: [":host{display:contents}\n"] });
|
|
869
1300
|
}
|
|
870
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
1301
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.3", ngImport: i0, type: TileComponent, decorators: [{
|
|
871
1302
|
type: Component,
|
|
872
1303
|
args: [{ selector: 'apa-tile', standalone: true, template: `
|
|
873
1304
|
<section
|
|
@@ -876,14 +1307,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
|
|
|
876
1307
|
[class.fxs-tilesize-mini]="tile().size === 'mini'"
|
|
877
1308
|
[class.fxs-tilesize-herowide]="tile().size === 'herowide'"
|
|
878
1309
|
[class.fxs-tilesize-small]="tile().size === 'small'">
|
|
879
|
-
<div class="fxs-part
|
|
1310
|
+
<div class="fxs-part"
|
|
1311
|
+
[class.fxs-part-clickable]="!tile().disabled"
|
|
880
1312
|
role="button"
|
|
881
|
-
tabindex="0"
|
|
1313
|
+
[attr.tabindex]="tile().disabled ? -1 : 0"
|
|
882
1314
|
[attr.aria-label]="tile().title"
|
|
1315
|
+
[attr.aria-disabled]="tile().disabled || null"
|
|
883
1316
|
(click)="onClick()"
|
|
884
1317
|
(keydown.enter)="onClick()"
|
|
885
1318
|
(keydown.space)="onClick(); $event.preventDefault()"
|
|
886
|
-
style="
|
|
1319
|
+
[style.cursor]="tile().disabled ? 'default' : 'pointer'"
|
|
1320
|
+
[style.opacity]="tile().disabled ? 0.45 : 1">
|
|
887
1321
|
<header class="fxs-part-title">
|
|
888
1322
|
<h2 class="msportalfx-tooltip-overflow">{{ tile().title }}</h2>
|
|
889
1323
|
@if (tile().subtitle) {
|
|
@@ -904,20 +1338,32 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
|
|
|
904
1338
|
* Displays tiles on the startboard. When a tile is clicked, it opens
|
|
905
1339
|
* the first blade via BladeService.setFirstBlade().
|
|
906
1340
|
*
|
|
1341
|
+
* When `autoNavigate` is true (default), clicking a tile automatically
|
|
1342
|
+
* opens the first blade. Set it to false to handle tile clicks manually
|
|
1343
|
+
* via the `(tileClick)` output.
|
|
1344
|
+
*
|
|
907
1345
|
* Usage:
|
|
908
1346
|
* ```html
|
|
909
1347
|
* <apa-panorama />
|
|
1348
|
+
* <apa-panorama [autoNavigate]="false" (tileClick)="onTileClick($event)" />
|
|
910
1349
|
* ```
|
|
911
1350
|
*/
|
|
912
1351
|
class PanoramaComponent {
|
|
913
1352
|
portal = inject(PortalService);
|
|
914
1353
|
bladeService = inject(BladeService);
|
|
1354
|
+
/** When false, tile clicks only emit tileClick without opening a blade. */
|
|
1355
|
+
autoNavigate = input(true, ...(ngDevMode ? [{ debugName: "autoNavigate" }] : /* istanbul ignore next */ []));
|
|
1356
|
+
/** Emitted when a tile is clicked (always, regardless of autoNavigate). */
|
|
1357
|
+
tileClick = output();
|
|
915
1358
|
panorama = this.portal.panorama;
|
|
916
1359
|
onTileClick(tile) {
|
|
917
|
-
this.
|
|
1360
|
+
this.tileClick.emit(tile);
|
|
1361
|
+
if (this.autoNavigate()) {
|
|
1362
|
+
this.bladeService.setFirstBlade(tile.bladePath, tile.title);
|
|
1363
|
+
}
|
|
918
1364
|
}
|
|
919
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
920
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.
|
|
1365
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.3", ngImport: i0, type: PanoramaComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1366
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.3", type: PanoramaComponent, isStandalone: true, selector: "apa-panorama", inputs: { autoNavigate: { classPropertyName: "autoNavigate", publicName: "autoNavigate", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { tileClick: "tileClick" }, ngImport: i0, template: `
|
|
921
1367
|
@if (portal.isPanoramaVisible()) {
|
|
922
1368
|
<div class="fxs-panorama-homearea" [class.collapsed]="!panorama().showTiles">
|
|
923
1369
|
<div class="fxs-startboard-target fxs-startboard fx-rightClick" [class.collapsed]="!panorama().showTiles">
|
|
@@ -943,7 +1389,7 @@ class PanoramaComponent {
|
|
|
943
1389
|
}
|
|
944
1390
|
`, isInline: true, styles: [":host{display:contents}\n"], dependencies: [{ kind: "component", type: TileComponent, selector: "apa-tile", inputs: ["tile"], outputs: ["tileClick"] }] });
|
|
945
1391
|
}
|
|
946
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
1392
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.3", ngImport: i0, type: PanoramaComponent, decorators: [{
|
|
947
1393
|
type: Component,
|
|
948
1394
|
args: [{ selector: 'apa-panorama', standalone: true, imports: [TileComponent], template: `
|
|
949
1395
|
@if (portal.isPanoramaVisible()) {
|
|
@@ -970,7 +1416,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
|
|
|
970
1416
|
</div>
|
|
971
1417
|
}
|
|
972
1418
|
`, styles: [":host{display:contents}\n"] }]
|
|
973
|
-
}] });
|
|
1419
|
+
}], propDecorators: { autoNavigate: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoNavigate", required: false }] }], tileClick: [{ type: i0.Output, args: ["tileClick"] }] } });
|
|
974
1420
|
|
|
975
1421
|
/**
|
|
976
1422
|
* Root portal shell component.
|
|
@@ -995,7 +1441,7 @@ class PortalLayoutComponent {
|
|
|
995
1441
|
elementRef = inject(ElementRef);
|
|
996
1442
|
injector = inject(Injector);
|
|
997
1443
|
destroyRef = inject(DestroyRef);
|
|
998
|
-
isDark = signal(false, ...(ngDevMode ? [{ debugName: "isDark" }] : []));
|
|
1444
|
+
isDark = signal(false, ...(ngDevMode ? [{ debugName: "isDark" }] : /* istanbul ignore next */ []));
|
|
999
1445
|
/** Available languages from the preset registry */
|
|
1000
1446
|
availableLanguages = Array.from(LANGUAGE_PRESETS.values()).map((p) => ({ code: p.code, displayName: p.displayName }));
|
|
1001
1447
|
constructor() {
|
|
@@ -1110,8 +1556,8 @@ class PortalLayoutComponent {
|
|
|
1110
1556
|
targetScroll = Math.min(targetScroll, currentScroll + viewportWidth);
|
|
1111
1557
|
scrollContainer.scrollTo({ left: targetScroll, behavior: 'smooth' });
|
|
1112
1558
|
}
|
|
1113
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
1114
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.
|
|
1559
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.3", ngImport: i0, type: PortalLayoutComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1560
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.3", type: PortalLayoutComponent, isStandalone: true, selector: "apa-portal-layout", ngImport: i0, template: `
|
|
1115
1561
|
<div class="fxs-portal fxs-theme-blue">
|
|
1116
1562
|
<!-- Top bar -->
|
|
1117
1563
|
<header class="fxs-topbar" role="banner">
|
|
@@ -1198,7 +1644,7 @@ class PortalLayoutComponent {
|
|
|
1198
1644
|
</div>
|
|
1199
1645
|
`, isInline: true });
|
|
1200
1646
|
}
|
|
1201
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
1647
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.3", ngImport: i0, type: PortalLayoutComponent, decorators: [{
|
|
1202
1648
|
type: Component,
|
|
1203
1649
|
args: [{
|
|
1204
1650
|
selector: 'apa-portal-layout',
|
|
@@ -1304,7 +1750,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
|
|
|
1304
1750
|
* ```
|
|
1305
1751
|
*/
|
|
1306
1752
|
class CommandBarComponent {
|
|
1307
|
-
commands = input([], ...(ngDevMode ? [{ debugName: "commands" }] : []));
|
|
1753
|
+
commands = input([], ...(ngDevMode ? [{ debugName: "commands" }] : /* istanbul ignore next */ []));
|
|
1308
1754
|
visibleCommands() {
|
|
1309
1755
|
return this.commands().filter((c) => c.visible);
|
|
1310
1756
|
}
|
|
@@ -1316,8 +1762,8 @@ class CommandBarComponent {
|
|
|
1316
1762
|
}
|
|
1317
1763
|
}
|
|
1318
1764
|
}
|
|
1319
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
1320
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.
|
|
1765
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.3", ngImport: i0, type: CommandBarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1766
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.3", type: CommandBarComponent, isStandalone: true, selector: "apa-command-bar", inputs: { commands: { classPropertyName: "commands", publicName: "commands", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
|
|
1321
1767
|
<nav class="fxs-commandBar" role="toolbar" aria-label="Commands">
|
|
1322
1768
|
<ul class="fxs-commandBar-itemList" role="list">
|
|
1323
1769
|
@for (cmd of visibleCommands(); track cmd.key) {
|
|
@@ -1344,7 +1790,7 @@ class CommandBarComponent {
|
|
|
1344
1790
|
</nav>
|
|
1345
1791
|
`, isInline: true, styles: [":host{display:block}\n"] });
|
|
1346
1792
|
}
|
|
1347
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
1793
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.3", ngImport: i0, type: CommandBarComponent, decorators: [{
|
|
1348
1794
|
type: Component,
|
|
1349
1795
|
args: [{ selector: 'apa-command-bar', standalone: true, template: `
|
|
1350
1796
|
<nav class="fxs-commandBar" role="toolbar" aria-label="Commands">
|
|
@@ -1389,7 +1835,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
|
|
|
1389
1835
|
* ```
|
|
1390
1836
|
*/
|
|
1391
1837
|
class BladeComponent {
|
|
1392
|
-
blade = input.required(...(ngDevMode ? [{ debugName: "blade" }] : []));
|
|
1838
|
+
blade = input.required(...(ngDevMode ? [{ debugName: "blade" }] : /* istanbul ignore next */ []));
|
|
1393
1839
|
bladeClose = output();
|
|
1394
1840
|
portal = inject(PortalService);
|
|
1395
1841
|
bladeService = inject(BladeService);
|
|
@@ -1398,8 +1844,8 @@ class BladeComponent {
|
|
|
1398
1844
|
this.bladeClose.emit(b);
|
|
1399
1845
|
this.bladeService.closeBlade(b);
|
|
1400
1846
|
}
|
|
1401
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
1402
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.
|
|
1847
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.3", ngImport: i0, type: BladeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1848
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.3", type: BladeComponent, isStandalone: true, selector: "apa-blade", inputs: { blade: { classPropertyName: "blade", publicName: "blade", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { bladeClose: "bladeClose" }, ngImport: i0, template: `
|
|
1403
1849
|
<section class="fxs-blade-locked fxs-blade fx-rightClick fxs-bladesize-small"
|
|
1404
1850
|
role="region"
|
|
1405
1851
|
[attr.aria-label]="blade().title"
|
|
@@ -1458,7 +1904,7 @@ class BladeComponent {
|
|
|
1458
1904
|
</section>
|
|
1459
1905
|
`, isInline: true, styles: [":host{display:block;height:100%}\n"], dependencies: [{ kind: "component", type: CommandBarComponent, selector: "apa-command-bar", inputs: ["commands"] }] });
|
|
1460
1906
|
}
|
|
1461
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
1907
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.3", ngImport: i0, type: BladeComponent, decorators: [{
|
|
1462
1908
|
type: Component,
|
|
1463
1909
|
args: [{ selector: 'apa-blade', standalone: true, imports: [CommandBarComponent], template: `
|
|
1464
1910
|
<section class="fxs-blade-locked fxs-blade fx-rightClick fxs-bladesize-small"
|
|
@@ -1533,54 +1979,76 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
|
|
|
1533
1979
|
*
|
|
1534
1980
|
* Usage:
|
|
1535
1981
|
* ```html
|
|
1982
|
+
* <!-- Default: each component is wrapped in <apa-blade> -->
|
|
1536
1983
|
* <apa-blade-host />
|
|
1984
|
+
*
|
|
1985
|
+
* <!-- No wrapper: components render directly (they manage their own blade chrome) -->
|
|
1986
|
+
* <apa-blade-host [wrapBlade]="false" />
|
|
1537
1987
|
* ```
|
|
1538
1988
|
*/
|
|
1539
1989
|
class BladeHostComponent {
|
|
1990
|
+
/** Whether to wrap each component in an `<apa-blade>` element. Default: true. */
|
|
1991
|
+
wrapBlade = input(true, ...(ngDevMode ? [{ debugName: "wrapBlade" }] : /* istanbul ignore next */ []));
|
|
1540
1992
|
portal = inject(PortalService);
|
|
1541
1993
|
registry = inject(BladeRegistry);
|
|
1542
1994
|
getComponent(path) {
|
|
1543
1995
|
return this.registry.get(path) ?? null;
|
|
1544
1996
|
}
|
|
1545
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
1546
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.
|
|
1997
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.3", ngImport: i0, type: BladeHostComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1998
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.3", type: BladeHostComponent, isStandalone: true, selector: "apa-blade-host", inputs: { wrapBlade: { classPropertyName: "wrapBlade", publicName: "wrapBlade", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
|
|
1547
1999
|
<div id="apa-blade-area" class="fxs-journey-target fxs-journey">
|
|
1548
2000
|
<div class="fxs-journey-layout fxs-stacklayout fxs-stacklayout-horizontal">
|
|
1549
|
-
@for (blade of portal.blades(); track blade.
|
|
2001
|
+
@for (blade of portal.blades(); track blade.uid) {
|
|
1550
2002
|
<div class="azureportalblade fxs-stacklayout-child">
|
|
1551
|
-
|
|
2003
|
+
@if (wrapBlade()) {
|
|
2004
|
+
<apa-blade [blade]="blade">
|
|
2005
|
+
@if (getComponent(blade.path); as component) {
|
|
2006
|
+
<ng-container *ngComponentOutlet="component" />
|
|
2007
|
+
} @else {
|
|
2008
|
+
<p style="padding:25px; color:var(--apa-text-secondary);">{{ blade.path }}</p>
|
|
2009
|
+
}
|
|
2010
|
+
</apa-blade>
|
|
2011
|
+
} @else {
|
|
1552
2012
|
@if (getComponent(blade.path); as component) {
|
|
1553
2013
|
<ng-container *ngComponentOutlet="component" />
|
|
1554
2014
|
} @else {
|
|
1555
2015
|
<p style="padding:25px; color:var(--apa-text-secondary);">{{ blade.path }}</p>
|
|
1556
2016
|
}
|
|
1557
|
-
|
|
2017
|
+
}
|
|
1558
2018
|
</div>
|
|
1559
2019
|
}
|
|
1560
2020
|
</div>
|
|
1561
2021
|
</div>
|
|
1562
2022
|
`, isInline: true, styles: [":host{display:block;height:100%}\n"], dependencies: [{ kind: "component", type: BladeComponent, selector: "apa-blade", inputs: ["blade"], outputs: ["bladeClose"] }, { kind: "directive", type: NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule"], exportAs: ["ngComponentOutlet"] }] });
|
|
1563
2023
|
}
|
|
1564
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
2024
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.3", ngImport: i0, type: BladeHostComponent, decorators: [{
|
|
1565
2025
|
type: Component,
|
|
1566
2026
|
args: [{ selector: 'apa-blade-host', standalone: true, imports: [BladeComponent, NgComponentOutlet], template: `
|
|
1567
2027
|
<div id="apa-blade-area" class="fxs-journey-target fxs-journey">
|
|
1568
2028
|
<div class="fxs-journey-layout fxs-stacklayout fxs-stacklayout-horizontal">
|
|
1569
|
-
@for (blade of portal.blades(); track blade.
|
|
2029
|
+
@for (blade of portal.blades(); track blade.uid) {
|
|
1570
2030
|
<div class="azureportalblade fxs-stacklayout-child">
|
|
1571
|
-
|
|
2031
|
+
@if (wrapBlade()) {
|
|
2032
|
+
<apa-blade [blade]="blade">
|
|
2033
|
+
@if (getComponent(blade.path); as component) {
|
|
2034
|
+
<ng-container *ngComponentOutlet="component" />
|
|
2035
|
+
} @else {
|
|
2036
|
+
<p style="padding:25px; color:var(--apa-text-secondary);">{{ blade.path }}</p>
|
|
2037
|
+
}
|
|
2038
|
+
</apa-blade>
|
|
2039
|
+
} @else {
|
|
1572
2040
|
@if (getComponent(blade.path); as component) {
|
|
1573
2041
|
<ng-container *ngComponentOutlet="component" />
|
|
1574
2042
|
} @else {
|
|
1575
2043
|
<p style="padding:25px; color:var(--apa-text-secondary);">{{ blade.path }}</p>
|
|
1576
2044
|
}
|
|
1577
|
-
|
|
2045
|
+
}
|
|
1578
2046
|
</div>
|
|
1579
2047
|
}
|
|
1580
2048
|
</div>
|
|
1581
2049
|
</div>
|
|
1582
2050
|
`, styles: [":host{display:block;height:100%}\n"] }]
|
|
1583
|
-
}] });
|
|
2051
|
+
}], propDecorators: { wrapBlade: [{ type: i0.Input, args: [{ isSignal: true, alias: "wrapBlade", required: false }] }] } });
|
|
1584
2052
|
|
|
1585
2053
|
/**
|
|
1586
2054
|
* Navigation blade content — renders a list of nav items.
|
|
@@ -1597,8 +2065,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
|
|
|
1597
2065
|
* ```
|
|
1598
2066
|
*/
|
|
1599
2067
|
class BladeNavComponent {
|
|
1600
|
-
items = input([], ...(ngDevMode ? [{ debugName: "items" }] : []));
|
|
1601
|
-
senderPath = input('', ...(ngDevMode ? [{ debugName: "senderPath" }] : []));
|
|
2068
|
+
items = input([], ...(ngDevMode ? [{ debugName: "items" }] : /* istanbul ignore next */ []));
|
|
2069
|
+
senderPath = input('', ...(ngDevMode ? [{ debugName: "senderPath" }] : /* istanbul ignore next */ []));
|
|
1602
2070
|
bladeService = inject(BladeService);
|
|
1603
2071
|
visibleItems() {
|
|
1604
2072
|
return this.items().filter((item) => item.isVisible);
|
|
@@ -1613,8 +2081,8 @@ class BladeNavComponent {
|
|
|
1613
2081
|
}
|
|
1614
2082
|
item.callback?.();
|
|
1615
2083
|
}
|
|
1616
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
1617
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.
|
|
2084
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.3", ngImport: i0, type: BladeNavComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2085
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.3", type: BladeNavComponent, isStandalone: true, selector: "apa-blade-nav", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: false, transformFunction: null }, senderPath: { classPropertyName: "senderPath", publicName: "senderPath", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
|
|
1618
2086
|
<table class="azc-grid-full azc-grid-multiselectable" role="grid" aria-label="Navigation">
|
|
1619
2087
|
<colgroup>
|
|
1620
2088
|
<col class="col0" style="width:28px;">
|
|
@@ -1644,7 +2112,7 @@ class BladeNavComponent {
|
|
|
1644
2112
|
</table>
|
|
1645
2113
|
`, isInline: true, styles: [":host{display:flex;flex-direction:column;flex:1}\n"] });
|
|
1646
2114
|
}
|
|
1647
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
2115
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.3", ngImport: i0, type: BladeNavComponent, decorators: [{
|
|
1648
2116
|
type: Component,
|
|
1649
2117
|
args: [{ selector: 'apa-blade-nav', standalone: true, template: `
|
|
1650
2118
|
<table class="azc-grid-full azc-grid-multiselectable" role="grid" aria-label="Navigation">
|
|
@@ -1697,11 +2165,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
|
|
|
1697
2165
|
* ```
|
|
1698
2166
|
*/
|
|
1699
2167
|
class BladeGridComponent {
|
|
1700
|
-
items = input([], ...(ngDevMode ? [{ debugName: "items" }] : []));
|
|
1701
|
-
senderPath = input('', ...(ngDevMode ? [{ debugName: "senderPath" }] : []));
|
|
1702
|
-
displayField = input('title', ...(ngDevMode ? [{ debugName: "displayField" }] : []));
|
|
1703
|
-
bladePathField = input('bladePath', ...(ngDevMode ? [{ debugName: "bladePathField" }] : []));
|
|
1704
|
-
|
|
2168
|
+
items = input([], ...(ngDevMode ? [{ debugName: "items" }] : /* istanbul ignore next */ []));
|
|
2169
|
+
senderPath = input('', ...(ngDevMode ? [{ debugName: "senderPath" }] : /* istanbul ignore next */ []));
|
|
2170
|
+
displayField = input('title', ...(ngDevMode ? [{ debugName: "displayField" }] : /* istanbul ignore next */ []));
|
|
2171
|
+
bladePathField = input('bladePath', ...(ngDevMode ? [{ debugName: "bladePathField" }] : /* istanbul ignore next */ []));
|
|
2172
|
+
idField = input('id', ...(ngDevMode ? [{ debugName: "idField" }] : /* istanbul ignore next */ []));
|
|
2173
|
+
iconClass = input('ti ti-point-filled', ...(ngDevMode ? [{ debugName: "iconClass" }] : /* istanbul ignore next */ []));
|
|
2174
|
+
searchable = input(true, ...(ngDevMode ? [{ debugName: "searchable" }] : /* istanbul ignore next */ []));
|
|
1705
2175
|
itemClick = output();
|
|
1706
2176
|
searchText = '';
|
|
1707
2177
|
bladeService = inject(BladeService);
|
|
@@ -1719,11 +2189,13 @@ class BladeGridComponent {
|
|
|
1719
2189
|
this.itemClick.emit(item);
|
|
1720
2190
|
const bladePath = item[this.bladePathField()];
|
|
1721
2191
|
if (bladePath) {
|
|
1722
|
-
|
|
2192
|
+
const itemId = item[this.idField()];
|
|
2193
|
+
const params = itemId != null ? { id: String(itemId) } : undefined;
|
|
2194
|
+
this.bladeService.addBlade(bladePath, this.senderPath(), '', undefined, params);
|
|
1723
2195
|
}
|
|
1724
2196
|
}
|
|
1725
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
1726
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.
|
|
2197
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.3", ngImport: i0, type: BladeGridComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2198
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.3", type: BladeGridComponent, isStandalone: true, selector: "apa-blade-grid", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: false, transformFunction: null }, senderPath: { classPropertyName: "senderPath", publicName: "senderPath", isSignal: true, isRequired: false, transformFunction: null }, displayField: { classPropertyName: "displayField", publicName: "displayField", isSignal: true, isRequired: false, transformFunction: null }, bladePathField: { classPropertyName: "bladePathField", publicName: "bladePathField", isSignal: true, isRequired: false, transformFunction: null }, idField: { classPropertyName: "idField", publicName: "idField", isSignal: true, isRequired: false, transformFunction: null }, iconClass: { classPropertyName: "iconClass", publicName: "iconClass", isSignal: true, isRequired: false, transformFunction: null }, searchable: { classPropertyName: "searchable", publicName: "searchable", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { itemClick: "itemClick" }, ngImport: i0, template: `
|
|
1727
2199
|
@if (searchable()) {
|
|
1728
2200
|
<div style="padding:0 0 10px 0;">
|
|
1729
2201
|
<input type="search"
|
|
@@ -1746,11 +2218,7 @@ class BladeGridComponent {
|
|
|
1746
2218
|
(click)="onRowClick(item)"
|
|
1747
2219
|
(keydown.enter)="onRowClick(item)">
|
|
1748
2220
|
<td class="msportalfx-gridcolumn-asseticon" role="gridcell" aria-hidden="true">
|
|
1749
|
-
<
|
|
1750
|
-
<rect class="msportalfx-svg-c04" x="19.8" y="39.4" width="10.6" height="3.4"/>
|
|
1751
|
-
<polygon class="msportalfx-svg-c04" points="23.1,50 27,50 30.3,46.5 19.8,46.5"/>
|
|
1752
|
-
<path class="msportalfx-svg-c20" d="M41.2 14.7v-.3c0-7.7-6.6-14.1-14.7-14.2-.2-.3-4.8.1-4.8.1-7.3.9-13 7-13 14.1 0 .2-.8 5.8 4.9 10.5 2.6 2.3 5.3 8.5 5.7 10.3l.3.6h10.6l.3-.6c.4-1.8 3.2-8 5.7-10.2C41.9 20.2 41.2 14.9 41.2 14.7z"/>
|
|
1753
|
-
</svg>
|
|
2221
|
+
<i [class]="iconClass()" style="font-size:20px; color:var(--apa-accent);"></i>
|
|
1754
2222
|
</td>
|
|
1755
2223
|
<td tabindex="0" role="gridcell">
|
|
1756
2224
|
<span>{{ getDisplayValue(item) }}</span>
|
|
@@ -1761,7 +2229,7 @@ class BladeGridComponent {
|
|
|
1761
2229
|
</table>
|
|
1762
2230
|
`, isInline: true, styles: [":host{display:flex;flex-direction:column;flex:1}\n"] });
|
|
1763
2231
|
}
|
|
1764
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
2232
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.3", ngImport: i0, type: BladeGridComponent, decorators: [{
|
|
1765
2233
|
type: Component,
|
|
1766
2234
|
args: [{ selector: 'apa-blade-grid', standalone: true, template: `
|
|
1767
2235
|
@if (searchable()) {
|
|
@@ -1786,11 +2254,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
|
|
|
1786
2254
|
(click)="onRowClick(item)"
|
|
1787
2255
|
(keydown.enter)="onRowClick(item)">
|
|
1788
2256
|
<td class="msportalfx-gridcolumn-asseticon" role="gridcell" aria-hidden="true">
|
|
1789
|
-
<
|
|
1790
|
-
<rect class="msportalfx-svg-c04" x="19.8" y="39.4" width="10.6" height="3.4"/>
|
|
1791
|
-
<polygon class="msportalfx-svg-c04" points="23.1,50 27,50 30.3,46.5 19.8,46.5"/>
|
|
1792
|
-
<path class="msportalfx-svg-c20" d="M41.2 14.7v-.3c0-7.7-6.6-14.1-14.7-14.2-.2-.3-4.8.1-4.8.1-7.3.9-13 7-13 14.1 0 .2-.8 5.8 4.9 10.5 2.6 2.3 5.3 8.5 5.7 10.3l.3.6h10.6l.3-.6c.4-1.8 3.2-8 5.7-10.2C41.9 20.2 41.2 14.9 41.2 14.7z"/>
|
|
1793
|
-
</svg>
|
|
2257
|
+
<i [class]="iconClass()" style="font-size:20px; color:var(--apa-accent);"></i>
|
|
1794
2258
|
</td>
|
|
1795
2259
|
<td tabindex="0" role="gridcell">
|
|
1796
2260
|
<span>{{ getDisplayValue(item) }}</span>
|
|
@@ -1800,7 +2264,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
|
|
|
1800
2264
|
</tbody>
|
|
1801
2265
|
</table>
|
|
1802
2266
|
`, styles: [":host{display:flex;flex-direction:column;flex:1}\n"] }]
|
|
1803
|
-
}], propDecorators: { items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: false }] }], senderPath: [{ type: i0.Input, args: [{ isSignal: true, alias: "senderPath", required: false }] }], displayField: [{ type: i0.Input, args: [{ isSignal: true, alias: "displayField", required: false }] }], bladePathField: [{ type: i0.Input, args: [{ isSignal: true, alias: "bladePathField", required: false }] }], searchable: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchable", required: false }] }], itemClick: [{ type: i0.Output, args: ["itemClick"] }] } });
|
|
2267
|
+
}], propDecorators: { items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: false }] }], senderPath: [{ type: i0.Input, args: [{ isSignal: true, alias: "senderPath", required: false }] }], displayField: [{ type: i0.Input, args: [{ isSignal: true, alias: "displayField", required: false }] }], bladePathField: [{ type: i0.Input, args: [{ isSignal: true, alias: "bladePathField", required: false }] }], idField: [{ type: i0.Input, args: [{ isSignal: true, alias: "idField", required: false }] }], iconClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "iconClass", required: false }] }], searchable: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchable", required: false }] }], itemClick: [{ type: i0.Output, args: ["itemClick"] }] } });
|
|
1804
2268
|
|
|
1805
2269
|
/**
|
|
1806
2270
|
* Detail/edit blade content — renders a form area for editing an item.
|
|
@@ -1824,22 +2288,44 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
|
|
|
1824
2288
|
* ```
|
|
1825
2289
|
*/
|
|
1826
2290
|
class BladeDetailComponent {
|
|
1827
|
-
blade = input.required(...(ngDevMode ? [{ debugName: "blade" }] : []));
|
|
1828
|
-
|
|
1829
|
-
|
|
2291
|
+
blade = input.required(...(ngDevMode ? [{ debugName: "blade" }] : /* istanbul ignore next */ []));
|
|
2292
|
+
bladeService = inject(BladeService);
|
|
2293
|
+
/** The template-driven form projected into this detail, if any (auto-discovered). */
|
|
2294
|
+
form = contentChild(NgForm, { ...(ngDevMode ? { debugName: "form" } : /* istanbul ignore next */ {}), descendants: true });
|
|
2295
|
+
ngAfterContentInit() {
|
|
2296
|
+
const blade = this.blade();
|
|
2297
|
+
const isDirty = () => this.form()?.dirty ?? false;
|
|
2298
|
+
// Expose on the lifecycle (for consumers) and register with the BladeService so navigation
|
|
2299
|
+
// away from a dirty blade prompts for confirmation. No per-detail wiring needed — every
|
|
2300
|
+
// detail that projects a <form> is guarded automatically.
|
|
2301
|
+
blade.lifecycle.isDirty = isDirty;
|
|
2302
|
+
this.bladeService.registerDirtyCheck(blade.path, isDirty);
|
|
2303
|
+
// After a successful save, reset the form to pristine so the guard does not fire on a
|
|
2304
|
+
// saved-but-not-yet-navigated blade. Wrap (don't replace) any existing onSavedItem hook.
|
|
2305
|
+
const originalOnSaved = blade.lifecycle.onSavedItem;
|
|
2306
|
+
blade.lifecycle.onSavedItem = () => {
|
|
2307
|
+
this.form()?.form.markAsPristine();
|
|
2308
|
+
originalOnSaved?.();
|
|
2309
|
+
};
|
|
2310
|
+
}
|
|
2311
|
+
ngOnDestroy() {
|
|
2312
|
+
this.bladeService.unregisterDirtyCheck(this.blade().path);
|
|
2313
|
+
}
|
|
2314
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.3", ngImport: i0, type: BladeDetailComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2315
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "21.2.3", type: BladeDetailComponent, isStandalone: true, selector: "apa-blade-detail", inputs: { blade: { classPropertyName: "blade", publicName: "blade", isSignal: true, isRequired: true, transformFunction: null } }, queries: [{ propertyName: "form", first: true, predicate: NgForm, descendants: true, isSignal: true }], ngImport: i0, template: `
|
|
1830
2316
|
<div class="apa-blade-detail">
|
|
1831
2317
|
<ng-content />
|
|
1832
2318
|
</div>
|
|
1833
2319
|
`, isInline: true, styles: [":host{display:flex;flex-direction:column;flex:1;min-height:0}.apa-blade-detail{flex:1;display:flex;flex-direction:column;min-height:0}\n"] });
|
|
1834
2320
|
}
|
|
1835
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
2321
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.3", ngImport: i0, type: BladeDetailComponent, decorators: [{
|
|
1836
2322
|
type: Component,
|
|
1837
2323
|
args: [{ selector: 'apa-blade-detail', standalone: true, template: `
|
|
1838
2324
|
<div class="apa-blade-detail">
|
|
1839
2325
|
<ng-content />
|
|
1840
2326
|
</div>
|
|
1841
2327
|
`, styles: [":host{display:flex;flex-direction:column;flex:1;min-height:0}.apa-blade-detail{flex:1;display:flex;flex-direction:column;min-height:0}\n"] }]
|
|
1842
|
-
}], propDecorators: { blade: [{ type: i0.Input, args: [{ isSignal: true, alias: "blade", required: true }] }] } });
|
|
2328
|
+
}], propDecorators: { blade: [{ type: i0.Input, args: [{ isSignal: true, alias: "blade", required: true }] }], form: [{ type: i0.ContentChild, args: [i0.forwardRef(() => NgForm), { ...{ descendants: true }, isSignal: true }] }] } });
|
|
1843
2329
|
/**
|
|
1844
2330
|
* Create standard detail blade commands (new, save, delete, cancel).
|
|
1845
2331
|
* Convenience function for setting up typical detail/edit blade commands
|
|
@@ -1875,11 +2361,22 @@ function createDetailCommands(handlers, labels = DEFAULT_LABELS) {
|
|
|
1875
2361
|
*/
|
|
1876
2362
|
class NotificationPanelComponent {
|
|
1877
2363
|
portal = inject(PortalService);
|
|
2364
|
+
wasVisible = false;
|
|
2365
|
+
constructor() {
|
|
2366
|
+
effect(() => {
|
|
2367
|
+
const visible = this.portal.notification().isVisible;
|
|
2368
|
+
if (visible && !this.wasVisible) {
|
|
2369
|
+
const lifecycle = this.portal.notification().lifecycle;
|
|
2370
|
+
queueMicrotask(() => lifecycle?.onShowed?.());
|
|
2371
|
+
}
|
|
2372
|
+
this.wasVisible = visible;
|
|
2373
|
+
});
|
|
2374
|
+
}
|
|
1878
2375
|
onClose() {
|
|
1879
2376
|
this.portal.hideNotification();
|
|
1880
2377
|
}
|
|
1881
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
1882
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.
|
|
2378
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.3", ngImport: i0, type: NotificationPanelComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2379
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.3", type: NotificationPanelComponent, isStandalone: true, selector: "apa-notification-panel", ngImport: i0, template: `
|
|
1883
2380
|
@if (portal.notification().isVisible) {
|
|
1884
2381
|
<div class="apa-notification-panel"
|
|
1885
2382
|
role="complementary"
|
|
@@ -1897,7 +2394,7 @@ class NotificationPanelComponent {
|
|
|
1897
2394
|
}
|
|
1898
2395
|
`, isInline: true, styles: [":host{display:contents}\n"] });
|
|
1899
2396
|
}
|
|
1900
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
2397
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.3", ngImport: i0, type: NotificationPanelComponent, decorators: [{
|
|
1901
2398
|
type: Component,
|
|
1902
2399
|
args: [{ selector: 'apa-notification-panel', standalone: true, template: `
|
|
1903
2400
|
@if (portal.notification().isVisible) {
|
|
@@ -1916,7 +2413,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
|
|
|
1916
2413
|
</div>
|
|
1917
2414
|
}
|
|
1918
2415
|
`, styles: [":host{display:contents}\n"] }]
|
|
1919
|
-
}] });
|
|
2416
|
+
}], ctorParameters: () => [] });
|
|
1920
2417
|
|
|
1921
2418
|
/**
|
|
1922
2419
|
* Avatar menu — user account dropdown in the portal header.
|
|
@@ -1940,8 +2437,8 @@ class AvatarMenuComponent {
|
|
|
1940
2437
|
displayName() {
|
|
1941
2438
|
return getUserDisplayName(this.userAccount());
|
|
1942
2439
|
}
|
|
1943
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
1944
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.
|
|
2440
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.3", ngImport: i0, type: AvatarMenuComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2441
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.3", type: AvatarMenuComponent, isStandalone: true, selector: "apa-avatar-menu", ngImport: i0, template: `
|
|
1945
2442
|
<div class="fxs-avatarmenu">
|
|
1946
2443
|
<a class="fxs-has-hover" (click)="portal.toggleAvatarMenu()">
|
|
1947
2444
|
<div class="fxs-avatarmenu-header">
|
|
@@ -1959,7 +2456,7 @@ class AvatarMenuComponent {
|
|
|
1959
2456
|
</div>
|
|
1960
2457
|
`, isInline: true });
|
|
1961
2458
|
}
|
|
1962
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
2459
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.3", ngImport: i0, type: AvatarMenuComponent, decorators: [{
|
|
1963
2460
|
type: Component,
|
|
1964
2461
|
args: [{
|
|
1965
2462
|
selector: 'apa-avatar-menu',
|
|
@@ -1998,10 +2495,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
|
|
|
1998
2495
|
* ```
|
|
1999
2496
|
*/
|
|
2000
2497
|
class SidebarComponent {
|
|
2001
|
-
items = input([], ...(ngDevMode ? [{ debugName: "items" }] : []));
|
|
2002
|
-
collapsed = input(false, ...(ngDevMode ? [{ debugName: "collapsed" }] : []));
|
|
2003
|
-
width = input(200, ...(ngDevMode ? [{ debugName: "width" }] : []));
|
|
2004
|
-
collapsedWidth = input(50, ...(ngDevMode ? [{ debugName: "collapsedWidth" }] : []));
|
|
2498
|
+
items = input([], ...(ngDevMode ? [{ debugName: "items" }] : /* istanbul ignore next */ []));
|
|
2499
|
+
collapsed = input(false, ...(ngDevMode ? [{ debugName: "collapsed" }] : /* istanbul ignore next */ []));
|
|
2500
|
+
width = input(200, ...(ngDevMode ? [{ debugName: "width" }] : /* istanbul ignore next */ []));
|
|
2501
|
+
collapsedWidth = input(50, ...(ngDevMode ? [{ debugName: "collapsedWidth" }] : /* istanbul ignore next */ []));
|
|
2005
2502
|
bladeService = inject(BladeService);
|
|
2006
2503
|
visibleItems() {
|
|
2007
2504
|
return this.items().filter((item) => item.isVisible);
|
|
@@ -2012,8 +2509,8 @@ class SidebarComponent {
|
|
|
2012
2509
|
}
|
|
2013
2510
|
item.callback?.();
|
|
2014
2511
|
}
|
|
2015
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
2016
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.
|
|
2512
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.3", ngImport: i0, type: SidebarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2513
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.3", type: SidebarComponent, isStandalone: true, selector: "apa-sidebar", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: false, transformFunction: null }, collapsed: { classPropertyName: "collapsed", publicName: "collapsed", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, collapsedWidth: { classPropertyName: "collapsedWidth", publicName: "collapsedWidth", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
|
|
2017
2514
|
<nav class="apa-sidebar" aria-label="Sidebar"
|
|
2018
2515
|
[class.apa-sidebar-collapsed]="collapsed()"
|
|
2019
2516
|
[style.width.px]="collapsed() ? collapsedWidth() : width()">
|
|
@@ -2040,7 +2537,7 @@ class SidebarComponent {
|
|
|
2040
2537
|
</nav>
|
|
2041
2538
|
`, isInline: true, styles: [".apa-sidebar{display:flex;flex-direction:column;background-color:var(--apa-chrome);height:100%;padding-top:10px;transition:width .2s ease}.apa-sidebar-item{display:flex;align-items:center;padding:10px 15px;color:var(--apa-chrome-text);text-decoration:none;font-size:13px;transition:background-color .15s ease}.apa-sidebar-item:hover{background-color:var(--apa-chrome-hover)}.apa-sidebar-icon{margin-right:10px;width:20px;text-align:center}.apa-sidebar-collapsed .apa-sidebar-icon{margin-right:0}.apa-sidebar-label{flex:1}.apa-sidebar-badge{display:inline-flex;align-items:center;justify-content:center;min-width:18px;height:18px;padding:0 5px;border-radius:9px;background-color:var(--apa-accent);color:#fff;font-size:11px;font-weight:600;line-height:1}\n"] });
|
|
2042
2539
|
}
|
|
2043
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
2540
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.3", ngImport: i0, type: SidebarComponent, decorators: [{
|
|
2044
2541
|
type: Component,
|
|
2045
2542
|
args: [{ selector: 'apa-sidebar', standalone: true, template: `
|
|
2046
2543
|
<nav class="apa-sidebar" aria-label="Sidebar"
|
|
@@ -2079,5 +2576,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
|
|
|
2079
2576
|
* Generated bundle index. Do not edit.
|
|
2080
2577
|
*/
|
|
2081
2578
|
|
|
2082
|
-
export { AvatarMenuComponent, BladeComponent, BladeDetailComponent, BladeGridComponent, BladeHostComponent, BladeNavComponent, BladeRegistry, BladeService, CommandBarComponent, DEFAULT_LABELS, LABELS_DE_CH, LABELS_DE_DE, LABELS_EN, LABELS_ES, LABELS_FR, LABELS_IT, LANGUAGE_PRESETS, NotificationPanelComponent, PanoramaComponent, PortalLayoutComponent, PortalService, SidebarComponent, TILE_DIMENSIONS, TileComponent, TileSize, clearStatusBar, createAvatarMenu, createBlade, createCommand, createDataBlade, createDetailCommands, createNavItem, createNotificationPanel, createPanorama, createTile, executeDeleteItem, executeLoadItem, executeLoadItems, executeSaveItem, filterItems, getUserDisplayName, layoutTiles, providePortalAzure, registerLanguagePreset, statusBarError, statusBarInfo, statusBarSuccess };
|
|
2579
|
+
export { AvatarMenuComponent, BLADE_ROUTER_CONFIG, BladeComponent, BladeDetailComponent, BladeGridComponent, BladeHostComponent, BladeNavComponent, BladeRegistry, BladeRouterService, BladeService, CommandBarComponent, DEFAULT_LABELS, LABELS_DE_CH, LABELS_DE_DE, LABELS_EN, LABELS_ES, LABELS_FR, LABELS_IT, LANGUAGE_PRESETS, NotificationPanelComponent, PanoramaComponent, PortalLayoutComponent, PortalService, SidebarComponent, TILE_DIMENSIONS, TileComponent, TileSize, clearStatusBar, createAvatarMenu, createBlade, createCommand, createDataBlade, createDetailCommands, createNavItem, createNotificationPanel, createPanorama, createTile, executeDeleteItem, executeLoadItem, executeLoadItems, executeSaveItem, filterItems, getUserDisplayName, layoutTiles, nextBladeUid, provideBladeRouter, providePortalAzure, registerLanguagePreset, statusBarError, statusBarInfo, statusBarSuccess };
|
|
2083
2580
|
//# sourceMappingURL=ardimedia-angular-portal-azure.mjs.map
|