aril 1.2.18 → 2.0.1-dev.0
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/boot/bridge/src/mfe-bridge.d.ts +42 -2
- package/boot/config/apps/index.d.ts +7 -1
- package/boot/config/apps/src/custom-reuse-outlet.component.d.ts +37 -0
- package/boot/config/apps/src/custom-route-reuse-strategy.class.d.ts +156 -0
- package/boot/config/apps/src/nav-link-context-menu.service.d.ts +33 -0
- package/boot/config/apps/src/nav-link.directive.d.ts +29 -0
- package/boot/config/apps/src/nav.service.d.ts +198 -0
- package/boot/config/apps/src/route-close.service.d.ts +9 -0
- package/boot/config/apps/src/safe-navigate.d.ts +17 -0
- package/boot/config/apps/src/tab-aware-url-serializer.d.ts +22 -0
- package/boot/config/plugins/src/getNgZone.d.ts +9 -1
- package/boot/mfe/src/app.component.d.ts +15 -4
- package/boot/mfe/src/isolated-location-strategy.d.ts +57 -0
- package/esm2022/boot/bridge/src/mfe-bridge.mjs +36 -5
- package/esm2022/boot/config/api/src/api.service.mjs +12 -3
- package/esm2022/boot/config/apps/index.mjs +8 -2
- package/esm2022/boot/config/apps/src/apps.service.mjs +14 -6
- package/esm2022/boot/config/apps/src/custom-reuse-outlet.component.mjs +207 -0
- package/esm2022/boot/config/apps/src/custom-route-reuse-strategy.class.mjs +540 -0
- package/esm2022/boot/config/apps/src/nav-link-context-menu.service.mjs +105 -0
- package/esm2022/boot/config/apps/src/nav-link.directive.mjs +45 -0
- package/esm2022/boot/config/apps/src/nav.service.mjs +675 -0
- package/esm2022/boot/config/apps/src/route-close.service.mjs +19 -0
- package/esm2022/boot/config/apps/src/safe-navigate.mjs +50 -0
- package/esm2022/boot/config/apps/src/tab-aware-url-serializer.mjs +50 -0
- package/esm2022/boot/config/plugins/src/getNgZone.mjs +13 -5
- package/esm2022/boot/host/src/app.component.mjs +1 -2
- package/esm2022/boot/host/src/bootstrap.mjs +22 -7
- package/esm2022/boot/mfe/src/app.component.mjs +143 -39
- package/esm2022/boot/mfe/src/bootstrap.mjs +197 -20
- package/esm2022/boot/mfe/src/isolated-location-strategy.mjs +142 -0
- package/esm2022/keycloak/src/auth.interceptor.mjs +17 -2
- package/esm2022/provider/src/prodiveHost.mjs +3 -5
- package/esm2022/provider/src/prodiveHostRouter.mjs +88 -9
- package/esm2022/theme/layout/app/expandableMenu/expandable-menu.component.mjs +81 -19
- package/esm2022/theme/layout/app/favorite-pages/favorite-pages-sidebar.component.mjs +6 -4
- package/esm2022/theme/layout/app/general-search/general-search.component.mjs +4 -4
- package/esm2022/theme/layout/app/history/history-sidebar.component.mjs +6 -4
- package/esm2022/theme/layout/app/layout/app.layout.component.mjs +422 -20
- package/esm2022/theme/layout/app/layout/mfe.layout.component.mjs +24 -35
- package/esm2022/theme/layout/app/site-map/site-map-sidebar.component.mjs +6 -4
- package/esm2022/theme/layout/app/static-sidebar/static-sidebar.component.mjs +85 -27
- package/esm2022/theme/layout/app/topbar/app.topbar.component.mjs +3 -3
- package/esm2022/theme/layout/service/breadcrumb-publisher.service.mjs +86 -0
- package/esm2022/theme/layout/service/tab-session.service.mjs +126 -0
- package/esm2022/ui-business/ref-value/src/ref-value.component.mjs +15 -7
- package/esm2022/util/sync-active-tab-route/src/sync-active-tab-route.directive.mjs +29 -9
- package/fesm2022/aril-app.component-s14ruALV.mjs +183 -0
- package/fesm2022/aril-app.component-s14ruALV.mjs.map +1 -0
- package/fesm2022/aril-boot-bridge.mjs +35 -4
- package/fesm2022/aril-boot-bridge.mjs.map +1 -1
- package/fesm2022/aril-boot-config-api.mjs +11 -2
- package/fesm2022/aril-boot-config-api.mjs.map +1 -1
- package/fesm2022/aril-boot-config-apps.mjs +1678 -10
- package/fesm2022/aril-boot-config-apps.mjs.map +1 -1
- package/fesm2022/aril-boot-config-plugins.mjs +12 -4
- package/fesm2022/aril-boot-config-plugins.mjs.map +1 -1
- package/fesm2022/aril-boot-host.mjs +21 -7
- package/fesm2022/aril-boot-host.mjs.map +1 -1
- package/fesm2022/aril-boot-mfe-app.component-a34GeuUv.mjs +183 -0
- package/fesm2022/aril-boot-mfe-app.component-a34GeuUv.mjs.map +1 -0
- package/fesm2022/aril-boot-mfe-aril-boot-mfe-KFO_X7yR.mjs +631 -0
- package/fesm2022/aril-boot-mfe-aril-boot-mfe-KFO_X7yR.mjs.map +1 -0
- package/fesm2022/aril-boot-mfe.mjs +5 -3
- package/fesm2022/aril-boot-mfe.mjs.map +1 -1
- package/fesm2022/aril-keycloak.mjs +16 -1
- package/fesm2022/aril-keycloak.mjs.map +1 -1
- package/fesm2022/aril-provider.mjs +90 -12
- package/fesm2022/aril-provider.mjs.map +1 -1
- package/fesm2022/aril-theme-layout.mjs +2630 -2017
- package/fesm2022/aril-theme-layout.mjs.map +1 -1
- package/fesm2022/aril-ui-business-ref-value.mjs +14 -6
- package/fesm2022/aril-ui-business-ref-value.mjs.map +1 -1
- package/fesm2022/aril-util-sync-active-tab-route.mjs +28 -8
- package/fesm2022/aril-util-sync-active-tab-route.mjs.map +1 -1
- package/fesm2022/aril.mjs +354 -25
- package/fesm2022/aril.mjs.map +1 -1
- package/keycloak/src/auth.interceptor.d.ts +7 -0
- package/package.json +216 -216
- package/provider/src/prodiveHost.d.ts +1 -0
- package/theme/layout/app/expandableMenu/expandable-menu.component.d.ts +21 -4
- package/theme/layout/app/expandableMenu/expandable-menu.component.html +19 -5
- package/theme/layout/app/expandableMenu/expandable-menu.component.ts +69 -9
- package/theme/layout/app/favorite-pages/favorite-pages-sidebar.component.html +1 -0
- package/theme/layout/app/favorite-pages/favorite-pages-sidebar.component.ts +3 -1
- package/theme/layout/app/general-search/general-search.component.html +2 -1
- package/theme/layout/app/general-search/general-search.component.ts +2 -2
- package/theme/layout/app/history/history-sidebar.component.html +3 -1
- package/theme/layout/app/history/history-sidebar.component.ts +3 -1
- package/theme/layout/app/layout/app.layout.component.d.ts +105 -5
- package/theme/layout/app/layout/app.layout.component.html +102 -1
- package/theme/layout/app/layout/app.layout.component.scss +372 -0
- package/theme/layout/app/layout/app.layout.component.ts +452 -13
- package/theme/layout/app/layout/mfe.layout.component.d.ts +7 -5
- package/theme/layout/app/layout/mfe.layout.component.ts +13 -39
- package/theme/layout/app/site-map/site-map-sidebar.component.html +1 -0
- package/theme/layout/app/site-map/site-map-sidebar.component.ts +3 -1
- package/theme/layout/app/static-sidebar/static-sidebar.component.d.ts +26 -5
- package/theme/layout/app/static-sidebar/static-sidebar.component.html +11 -5
- package/theme/layout/app/static-sidebar/static-sidebar.component.ts +68 -13
- package/theme/layout/app/topbar/app.topbar.component.html +0 -1
- package/theme/layout/app/topbar/app.topbar.component.scss +1 -1
- package/theme/layout/service/breadcrumb-publisher.service.d.ts +24 -0
- package/theme/layout/service/breadcrumb-publisher.service.ts +95 -0
- package/theme/layout/service/tab-session.service.d.ts +52 -0
- package/theme/layout/service/tab-session.service.ts +138 -0
- package/theme/styles/layout/_breadcrumb.scss +95 -0
- package/theme/styles/layout/_content.scss +2 -2
- package/ui-business/ref-value/src/ref-value.component.d.ts +4 -2
- package/util/sync-active-tab-route/src/sync-active-tab-route.directive.d.ts +15 -2
- package/boot/config/apps/src/reuse-strategy.d.ts +0 -4
- package/esm2022/boot/config/apps/src/reuse-strategy.mjs +0 -9
- package/esm2022/theme/layout/app/breadcrumb/app.breadcrumb.component.mjs +0 -107
- package/fesm2022/aril-app.component-wxP3y8dg.mjs +0 -81
- package/fesm2022/aril-app.component-wxP3y8dg.mjs.map +0 -1
- package/fesm2022/aril-boot-mfe-app.component-7IjAmjz0.mjs +0 -80
- package/fesm2022/aril-boot-mfe-app.component-7IjAmjz0.mjs.map +0 -1
- package/fesm2022/aril-boot-mfe-aril-boot-mfe-KXDpUyv7.mjs +0 -315
- package/fesm2022/aril-boot-mfe-aril-boot-mfe-KXDpUyv7.mjs.map +0 -1
- package/theme/layout/app/breadcrumb/app.breadcrumb.component.d.ts +0 -25
- package/theme/layout/app/breadcrumb/app.breadcrumb.component.html +0 -8
- package/theme/layout/app/breadcrumb/app.breadcrumb.component.ts +0 -127
|
@@ -2,10 +2,9 @@ import { HttpClient } from '@angular/common/http';
|
|
|
2
2
|
import { OnDestroy, OnInit, Signal } from '@angular/core';
|
|
3
3
|
import { Router } from '@angular/router';
|
|
4
4
|
import { TranslocoService } from '@ngneat/transloco';
|
|
5
|
-
import { MenuItemAction, PluginMenuItem } from 'aril/boot/config/apps';
|
|
5
|
+
import { MenuItemAction, NavService, PluginMenuItem } from 'aril/boot/config/apps';
|
|
6
6
|
import { TranslateJsonPipe } from 'aril/util/pipes';
|
|
7
7
|
import { LayoutService } from '../../service/app.layout.service';
|
|
8
|
-
import { AppMenuService } from '../../service/app.menu.service';
|
|
9
8
|
import * as i0 from "@angular/core";
|
|
10
9
|
interface MenuNode {
|
|
11
10
|
key: string;
|
|
@@ -18,7 +17,6 @@ interface MenuNode {
|
|
|
18
17
|
actionLoading?: boolean;
|
|
19
18
|
}
|
|
20
19
|
export declare class StaticSidebarComponent implements OnInit, OnDestroy {
|
|
21
|
-
private readonly menuService;
|
|
22
20
|
private readonly router;
|
|
23
21
|
private readonly layoutService;
|
|
24
22
|
private readonly translateJsonPipe;
|
|
@@ -28,10 +26,11 @@ export declare class StaticSidebarComponent implements OnInit, OnDestroy {
|
|
|
28
26
|
menuNodes: import("@angular/core").WritableSignal<MenuNode[]>;
|
|
29
27
|
filteredNodes: import("@angular/core").WritableSignal<MenuNode[]>;
|
|
30
28
|
searchTerm: import("@angular/core").WritableSignal<string>;
|
|
29
|
+
navService: NavService;
|
|
31
30
|
private routerSubscription;
|
|
32
31
|
private actionSubscription;
|
|
33
32
|
private readonly MAX_LENGTH;
|
|
34
|
-
constructor(
|
|
33
|
+
constructor(router: Router, layoutService: LayoutService, translateJsonPipe: TranslateJsonPipe, translocoService: TranslocoService, http: HttpClient);
|
|
35
34
|
get isCollapsed(): boolean;
|
|
36
35
|
get topLevelNodes(): MenuNode[];
|
|
37
36
|
ngOnInit(): void;
|
|
@@ -44,6 +43,12 @@ export declare class StaticSidebarComponent implements OnInit, OnDestroy {
|
|
|
44
43
|
convertToMenuNodes(items: PluginMenuItem[], parentKey?: string): MenuNode[];
|
|
45
44
|
getMenuItems(): PluginMenuItem[];
|
|
46
45
|
getLocalText(text: any): string;
|
|
46
|
+
/**
|
|
47
|
+
* Hash strategy için routerLink'i `#`-prefix'li href'e çevirir. routerLink config'te
|
|
48
|
+
* leading slash içerebilir (örn. anasayfa için `'/'`); naive `'#/' + routerLink`
|
|
49
|
+
* concatenation `#//` üretirdi — sağ tık "linki kopyala" UX'inde bu çirkin görünür.
|
|
50
|
+
*/
|
|
51
|
+
getHashHref(routerLink: string | undefined | null): string;
|
|
47
52
|
isActiveRouteForNode(node: MenuNode): boolean;
|
|
48
53
|
isNodeOrChildActive(node: MenuNode): boolean;
|
|
49
54
|
isTopLevelNodeActive(node: MenuNode): boolean;
|
|
@@ -51,10 +56,26 @@ export declare class StaticSidebarComponent implements OnInit, OnDestroy {
|
|
|
51
56
|
toggleNode(node: MenuNode, level?: number): void;
|
|
52
57
|
private closeOtherTopLevelNodes;
|
|
53
58
|
private closeAllChildNodes;
|
|
54
|
-
|
|
59
|
+
/**
|
|
60
|
+
* Chrome-like menü click davranışı:
|
|
61
|
+
* - Normal click → aktif tab'da navigate (yeni tab açma)
|
|
62
|
+
* - Ctrl/Cmd + Click → arka planda yeni tab
|
|
63
|
+
* - Ctrl/Cmd + Shift + Click → ön planda yeni tab (yeni tab aç + oraya geç)
|
|
64
|
+
*/
|
|
65
|
+
onMenuClick(event: MouseEvent, node: MenuNode): void;
|
|
66
|
+
/** Middle click (mouse button 1) → arka planda yeni tab. */
|
|
67
|
+
onMenuAuxClick(event: MouseEvent, node: MenuNode): void;
|
|
68
|
+
/** Middle click default scroll davranışını engelle. */
|
|
69
|
+
onMenuMousedown(event: MouseEvent): void;
|
|
70
|
+
private afterMenuClick;
|
|
55
71
|
onActionItemClick(node: MenuNode, event: Event): void;
|
|
56
72
|
private buildSuccessRoute;
|
|
57
73
|
private getValueByPath;
|
|
74
|
+
/**
|
|
75
|
+
* Collapsed-mode UI-only handler: sidebar'ı aç, kardeş toplevel'ları kapat, children varsa expand.
|
|
76
|
+
* Navigation YAPMAZ — routerLink'li node'larda `(click)` template'te `onMenuClick` ile zincirleniyor;
|
|
77
|
+
* `afterMenuClick` da orada bir kez çağrılır.
|
|
78
|
+
*/
|
|
58
79
|
onCollapsedItemClick(node: MenuNode): void;
|
|
59
80
|
onSearchChange(): void;
|
|
60
81
|
private filterNodes;
|
|
@@ -46,9 +46,12 @@
|
|
|
46
46
|
class="collapsed-item"
|
|
47
47
|
[pTooltip]="getLocalText(node.label)"
|
|
48
48
|
tooltipPosition="right"
|
|
49
|
-
[
|
|
49
|
+
[attr.href]="getHashHref(node.routerLink)"
|
|
50
50
|
[class.active]="isTopLevelNodeActive(node)"
|
|
51
|
-
|
|
51
|
+
[arilNavLink]="{ tabId: '', navLink: node.routerLink, navName: getLocalText(node.label), icon: node.icon }"
|
|
52
|
+
(click)="onCollapsedItemClick(node); onMenuClick($event, node)"
|
|
53
|
+
(auxclick)="onMenuAuxClick($event, node)"
|
|
54
|
+
(mousedown)="onMenuMousedown($event)">
|
|
52
55
|
@if (node.icon) {
|
|
53
56
|
<i class="collapsed-icon" [class]="node.icon"></i>
|
|
54
57
|
} @else {
|
|
@@ -103,14 +106,17 @@
|
|
|
103
106
|
</div>
|
|
104
107
|
} @else if (node.routerLink) {
|
|
105
108
|
<a
|
|
106
|
-
[
|
|
109
|
+
[attr.href]="getHashHref(node.routerLink)"
|
|
110
|
+
[arilNavLink]="{ tabId: '', navLink: node.routerLink, navName: getLocalText(node.label), icon: node.icon }"
|
|
111
|
+
(click)="onMenuClick($event, node)"
|
|
112
|
+
(auxclick)="onMenuAuxClick($event, node)"
|
|
113
|
+
(mousedown)="onMenuMousedown($event)"
|
|
107
114
|
class="node-item"
|
|
108
115
|
[class.has-children]="node.children && node.children.length > 0"
|
|
109
116
|
[class.is-page]="node.routerLink"
|
|
110
117
|
[class.active]="isChildNodeActive(node)"
|
|
111
118
|
[pTooltip]="!isMobile() && isTextTruncated(node.label, activeLang(), level) ? getLocalText(node.label) : ''"
|
|
112
|
-
tooltipPosition="right"
|
|
113
|
-
(click)="onMenuItemClick()">
|
|
119
|
+
tooltipPosition="right">
|
|
114
120
|
<div class="node-content">
|
|
115
121
|
@if (node.children && node.children.length > 0) {
|
|
116
122
|
<div class="toggle-btn" (click)="$event.stopPropagation(); toggleNode(node, level)">
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { NgClass, NgTemplateOutlet } from '@angular/common';
|
|
2
2
|
import { HttpClient } from '@angular/common/http';
|
|
3
|
-
import { Component, OnDestroy, OnInit, Signal, signal } from '@angular/core';
|
|
3
|
+
import { Component, inject, OnDestroy, OnInit, Signal, signal } from '@angular/core';
|
|
4
4
|
import { toSignal } from '@angular/core/rxjs-interop';
|
|
5
5
|
import { FormsModule } from '@angular/forms';
|
|
6
|
-
import { NavigationEnd, Router
|
|
6
|
+
import { NavigationEnd, Router } from '@angular/router';
|
|
7
7
|
|
|
8
8
|
import { InputTextModule } from 'primeng/inputtext';
|
|
9
9
|
import { ScrollPanelModule } from 'primeng/scrollpanel';
|
|
@@ -13,11 +13,10 @@ import { TranslocoModule, TranslocoService } from '@ngneat/transloco';
|
|
|
13
13
|
import { Subscription, filter } from 'rxjs';
|
|
14
14
|
|
|
15
15
|
import { bridge } from 'aril/boot/bridge';
|
|
16
|
-
import { MenuItemAction, PluginMenuItem } from 'aril/boot/config/apps';
|
|
16
|
+
import { MenuItemAction, NavItem, NavLinkDirective, NavService, PluginMenuItem } from 'aril/boot/config/apps';
|
|
17
17
|
import { TranslateJsonPipe } from 'aril/util/pipes';
|
|
18
18
|
|
|
19
19
|
import { LayoutService } from '../../service/app.layout.service';
|
|
20
|
-
import { AppMenuService } from '../../service/app.menu.service';
|
|
21
20
|
|
|
22
21
|
interface MenuNode {
|
|
23
22
|
key: string;
|
|
@@ -34,14 +33,14 @@ interface MenuNode {
|
|
|
34
33
|
standalone: true,
|
|
35
34
|
selector: 'app-static-sidebar',
|
|
36
35
|
imports: [
|
|
37
|
-
RouterLink,
|
|
38
36
|
NgClass,
|
|
39
37
|
NgTemplateOutlet,
|
|
40
38
|
ScrollPanelModule,
|
|
41
39
|
TooltipModule,
|
|
42
40
|
FormsModule,
|
|
43
41
|
TranslocoModule,
|
|
44
|
-
InputTextModule
|
|
42
|
+
InputTextModule,
|
|
43
|
+
NavLinkDirective
|
|
45
44
|
],
|
|
46
45
|
templateUrl: './static-sidebar.component.html',
|
|
47
46
|
styleUrl: './static-sidebar.component.scss',
|
|
@@ -52,6 +51,7 @@ export class StaticSidebarComponent implements OnInit, OnDestroy {
|
|
|
52
51
|
menuNodes = signal<MenuNode[]>([]);
|
|
53
52
|
filteredNodes = signal<MenuNode[]>([]);
|
|
54
53
|
searchTerm = signal<string>('');
|
|
54
|
+
navService: NavService = inject(NavService);
|
|
55
55
|
|
|
56
56
|
private routerSubscription: Subscription | null = null;
|
|
57
57
|
private actionSubscription: Subscription | null = null;
|
|
@@ -59,7 +59,6 @@ export class StaticSidebarComponent implements OnInit, OnDestroy {
|
|
|
59
59
|
private readonly MAX_LENGTH = { level0: 25, level1: 20, level2: 16, level3: 13 };
|
|
60
60
|
|
|
61
61
|
constructor(
|
|
62
|
-
private readonly menuService: AppMenuService,
|
|
63
62
|
private readonly router: Router,
|
|
64
63
|
private readonly layoutService: LayoutService,
|
|
65
64
|
private readonly translateJsonPipe: TranslateJsonPipe,
|
|
@@ -154,13 +153,23 @@ export class StaticSidebarComponent implements OnInit, OnDestroy {
|
|
|
154
153
|
}
|
|
155
154
|
|
|
156
155
|
getMenuItems(): PluginMenuItem[] {
|
|
157
|
-
return bridge.
|
|
156
|
+
return bridge.hostMenuItems();
|
|
158
157
|
}
|
|
159
158
|
|
|
160
159
|
getLocalText(text: any): string {
|
|
161
160
|
return this.translateJsonPipe.transform(text);
|
|
162
161
|
}
|
|
163
162
|
|
|
163
|
+
/**
|
|
164
|
+
* Hash strategy için routerLink'i `#`-prefix'li href'e çevirir. routerLink config'te
|
|
165
|
+
* leading slash içerebilir (örn. anasayfa için `'/'`); naive `'#/' + routerLink`
|
|
166
|
+
* concatenation `#//` üretirdi — sağ tık "linki kopyala" UX'inde bu çirkin görünür.
|
|
167
|
+
*/
|
|
168
|
+
getHashHref(routerLink: string | undefined | null): string {
|
|
169
|
+
if (!routerLink) return '#';
|
|
170
|
+
return routerLink.startsWith('/') ? '#' + routerLink : '#/' + routerLink;
|
|
171
|
+
}
|
|
172
|
+
|
|
164
173
|
isActiveRouteForNode(node: MenuNode): boolean {
|
|
165
174
|
if (!node.routerLink) return false;
|
|
166
175
|
const currentUrl = this.router.url;
|
|
@@ -222,7 +231,51 @@ export class StaticSidebarComponent implements OnInit, OnDestroy {
|
|
|
222
231
|
}
|
|
223
232
|
}
|
|
224
233
|
|
|
225
|
-
|
|
234
|
+
/**
|
|
235
|
+
* Chrome-like menü click davranışı:
|
|
236
|
+
* - Normal click → aktif tab'da navigate (yeni tab açma)
|
|
237
|
+
* - Ctrl/Cmd + Click → arka planda yeni tab
|
|
238
|
+
* - Ctrl/Cmd + Shift + Click → ön planda yeni tab (yeni tab aç + oraya geç)
|
|
239
|
+
*/
|
|
240
|
+
onMenuClick(event: MouseEvent, node: MenuNode): void {
|
|
241
|
+
if (!node.routerLink) return;
|
|
242
|
+
event.preventDefault();
|
|
243
|
+
const navItem: NavItem = {
|
|
244
|
+
tabId: '',
|
|
245
|
+
navLink: node.routerLink,
|
|
246
|
+
navName: this.getLocalText(node.label),
|
|
247
|
+
icon: node.icon
|
|
248
|
+
};
|
|
249
|
+
if (event.ctrlKey || event.metaKey) {
|
|
250
|
+
if (event.shiftKey) {
|
|
251
|
+
this.navService.openInNewTabAndFocus(navItem);
|
|
252
|
+
} else {
|
|
253
|
+
this.navService.openInBackgroundTab(navItem);
|
|
254
|
+
}
|
|
255
|
+
} else {
|
|
256
|
+
this.navService.navigateInCurrentTab(navItem);
|
|
257
|
+
}
|
|
258
|
+
this.afterMenuClick();
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/** Middle click (mouse button 1) → arka planda yeni tab. */
|
|
262
|
+
onMenuAuxClick(event: MouseEvent, node: MenuNode): void {
|
|
263
|
+
if (event.button !== 1 || !node.routerLink) return;
|
|
264
|
+
event.preventDefault();
|
|
265
|
+
this.navService.openInBackgroundTab({
|
|
266
|
+
tabId: '',
|
|
267
|
+
navLink: node.routerLink,
|
|
268
|
+
navName: this.getLocalText(node.label),
|
|
269
|
+
icon: node.icon
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/** Middle click default scroll davranışını engelle. */
|
|
274
|
+
onMenuMousedown(event: MouseEvent): void {
|
|
275
|
+
if (event.button === 1) event.preventDefault();
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
private afterMenuClick(): void {
|
|
226
279
|
if (this.isMobile()) {
|
|
227
280
|
this.layoutService.closeMobileMenu();
|
|
228
281
|
}
|
|
@@ -252,7 +305,7 @@ export class StaticSidebarComponent implements OnInit, OnDestroy {
|
|
|
252
305
|
node.actionLoading = false;
|
|
253
306
|
const route = this.buildSuccessRoute(node.action!, response);
|
|
254
307
|
this.router.navigate([route]);
|
|
255
|
-
this.
|
|
308
|
+
this.afterMenuClick();
|
|
256
309
|
},
|
|
257
310
|
error: () => {
|
|
258
311
|
node.actionLoading = false;
|
|
@@ -287,6 +340,11 @@ export class StaticSidebarComponent implements OnInit, OnDestroy {
|
|
|
287
340
|
}, obj);
|
|
288
341
|
}
|
|
289
342
|
|
|
343
|
+
/**
|
|
344
|
+
* Collapsed-mode UI-only handler: sidebar'ı aç, kardeş toplevel'ları kapat, children varsa expand.
|
|
345
|
+
* Navigation YAPMAZ — routerLink'li node'larda `(click)` template'te `onMenuClick` ile zincirleniyor;
|
|
346
|
+
* `afterMenuClick` da orada bir kez çağrılır.
|
|
347
|
+
*/
|
|
290
348
|
onCollapsedItemClick(node: MenuNode) {
|
|
291
349
|
if (this.isCollapsed) {
|
|
292
350
|
this.layoutService.onMenuToggle();
|
|
@@ -296,9 +354,6 @@ export class StaticSidebarComponent implements OnInit, OnDestroy {
|
|
|
296
354
|
node.expanded = true;
|
|
297
355
|
}
|
|
298
356
|
this.filteredNodes.set([...this.filteredNodes()]);
|
|
299
|
-
if (node.routerLink) {
|
|
300
|
-
this.onMenuItemClick();
|
|
301
|
-
}
|
|
302
357
|
}
|
|
303
358
|
|
|
304
359
|
onSearchChange(): void {
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { OnDestroy } from '@angular/core';
|
|
2
|
+
import * as i0 from "@angular/core";
|
|
3
|
+
/**
|
|
4
|
+
* MFE-side route ağacını dolaşıp aktif route zincirinin breadcrumb listesini
|
|
5
|
+
* `bridge.breadcrumbs` signal'ine yazar. Shell layout bu signal'i okuyup
|
|
6
|
+
* breadcrumb şeridini render eder; presenter shell'de, source-of-truth MFE'de.
|
|
7
|
+
*
|
|
8
|
+
* Eski `<app-breadcrumb>` component'inin logic'i bu service'e taşınmıştır.
|
|
9
|
+
* Geriye dönük uyumluluk için `breadcrumbUpdated` PubSub event'i hâlâ yayınlanır
|
|
10
|
+
* (history.service abone).
|
|
11
|
+
*/
|
|
12
|
+
export declare class BreadcrumbPublisherService implements OnDestroy {
|
|
13
|
+
private readonly router;
|
|
14
|
+
private readonly breadcrumbService;
|
|
15
|
+
private readonly pubSubService;
|
|
16
|
+
private routerSubscription;
|
|
17
|
+
constructor();
|
|
18
|
+
ngOnDestroy(): void;
|
|
19
|
+
private publishFromCurrentRoute;
|
|
20
|
+
private collect;
|
|
21
|
+
private publish;
|
|
22
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<BreadcrumbPublisherService, never>;
|
|
23
|
+
static ɵprov: i0.ɵɵInjectableDeclaration<BreadcrumbPublisherService>;
|
|
24
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { Injectable, OnDestroy, effect, inject } from '@angular/core';
|
|
2
|
+
import { ActivatedRouteSnapshot, NavigationEnd, Router } from '@angular/router';
|
|
3
|
+
|
|
4
|
+
import { Subscription, filter } from 'rxjs';
|
|
5
|
+
|
|
6
|
+
import { Breadcrumb, bridge } from 'aril/boot/bridge';
|
|
7
|
+
import { PubSubService } from 'aril/util/pub-sub';
|
|
8
|
+
|
|
9
|
+
import { BreadcrumbService } from './breadcrumb.service';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* MFE-side route ağacını dolaşıp aktif route zincirinin breadcrumb listesini
|
|
13
|
+
* `bridge.breadcrumbs` signal'ine yazar. Shell layout bu signal'i okuyup
|
|
14
|
+
* breadcrumb şeridini render eder; presenter shell'de, source-of-truth MFE'de.
|
|
15
|
+
*
|
|
16
|
+
* Eski `<app-breadcrumb>` component'inin logic'i bu service'e taşınmıştır.
|
|
17
|
+
* Geriye dönük uyumluluk için `breadcrumbUpdated` PubSub event'i hâlâ yayınlanır
|
|
18
|
+
* (history.service abone).
|
|
19
|
+
*/
|
|
20
|
+
@Injectable({ providedIn: 'root' })
|
|
21
|
+
export class BreadcrumbPublisherService implements OnDestroy {
|
|
22
|
+
private readonly router = inject(Router);
|
|
23
|
+
private readonly breadcrumbService = inject(BreadcrumbService);
|
|
24
|
+
private readonly pubSubService = inject(PubSubService);
|
|
25
|
+
|
|
26
|
+
private routerSubscription: Subscription;
|
|
27
|
+
|
|
28
|
+
constructor() {
|
|
29
|
+
this.routerSubscription = this.router.events
|
|
30
|
+
.pipe(filter((event) => event instanceof NavigationEnd))
|
|
31
|
+
.subscribe(() => this.publishFromCurrentRoute());
|
|
32
|
+
|
|
33
|
+
effect(() => {
|
|
34
|
+
// keyValues sinyalini dinle; dinamik breadcrumb (örn. "{customerId}")
|
|
35
|
+
// değerleri sonradan geldikçe listeyi yeniden yayınla.
|
|
36
|
+
this.breadcrumbService.keyValues();
|
|
37
|
+
this.publishFromCurrentRoute();
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
ngOnDestroy(): void {
|
|
42
|
+
this.routerSubscription?.unsubscribe();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
private publishFromCurrentRoute(): void {
|
|
46
|
+
const root = this.router.routerState.snapshot.root;
|
|
47
|
+
const breadcrumbs: Breadcrumb[] = [];
|
|
48
|
+
this.collect(root, [], breadcrumbs);
|
|
49
|
+
this.publish(breadcrumbs);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
private collect(route: ActivatedRouteSnapshot, parentUrl: string[], breadcrumbs: Breadcrumb[]): void {
|
|
53
|
+
const routeUrl = parentUrl.concat(route.url.map((url) => url.path));
|
|
54
|
+
const breadcrumb = route.data['breadcrumb'];
|
|
55
|
+
const parentBreadcrumb = route.parent && route.parent.data ? route.parent.data['breadcrumb'] : null;
|
|
56
|
+
|
|
57
|
+
if (breadcrumb && breadcrumb !== parentBreadcrumb) {
|
|
58
|
+
const template = breadcrumb as string;
|
|
59
|
+
// Tüm `{key}` placeholder'larını sırayla resolve et — global regex.
|
|
60
|
+
// Eskiden tek placeholder regex'i (`exec`) ile yalnız ilk key alınıyordu;
|
|
61
|
+
// `'Customer {customerId} - Service {servicePointId}'` formatında `servicePointId`
|
|
62
|
+
// literal kalıyordu. Resolve olmayan key için `'...'` placeholder döner; statik
|
|
63
|
+
// kısımlar korunur (önceden tüm label `'...'`'a düşüyordu).
|
|
64
|
+
const label = template.replace(/\{([^}]+)\}/g, (_, key: string) => {
|
|
65
|
+
const value = this.breadcrumbService.getValue(key);
|
|
66
|
+
return value !== undefined && value !== null && value !== '' ? String(value) : '...';
|
|
67
|
+
});
|
|
68
|
+
breadcrumbs.push({ label, url: '/' + routeUrl.join('/') });
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (route.firstChild) {
|
|
72
|
+
this.collect(route.firstChild, routeUrl, breadcrumbs);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
private publish(breadcrumbs: Breadcrumb[]): void {
|
|
77
|
+
bridge.breadcrumbs.set(breadcrumbs);
|
|
78
|
+
|
|
79
|
+
const lastLabel = breadcrumbs[breadcrumbs.length - 1]?.label ?? '';
|
|
80
|
+
if (bridge.pageTitle() || lastLabel) {
|
|
81
|
+
bridge.pageTitle.set(lastLabel);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
this.pubSubService.publish({
|
|
85
|
+
name: 'breadcrumbUpdated',
|
|
86
|
+
data: {
|
|
87
|
+
path: this.router.url,
|
|
88
|
+
custom: {
|
|
89
|
+
breadcrumbs,
|
|
90
|
+
pageTitle: lastLabel
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import * as i0 from "@angular/core";
|
|
2
|
+
/**
|
|
3
|
+
* Sabitlenmiş (pinned) tab'ları kullanıcı bazlı `localStorage` anahtarında kalıcı
|
|
4
|
+
* kılar. F5 / yeniden login / yeni sekme açılışında pinned tab'lar geri yüklenir;
|
|
5
|
+
* non-pinned tab'lar saklanmaz.
|
|
6
|
+
*
|
|
7
|
+
* **Akış**: `AppLayoutComponent` constructor'da (menü/home provider'ları set
|
|
8
|
+
* edildikten SONRA) `restore()` SENKRON çağrılır → pinned'ler `NavService.activeRoutes`'a
|
|
9
|
+
* eklenir → save effect armlanır. Save effect `restore()` ÖNCESİ kurulmaz; aksi
|
|
10
|
+
* halde Angular effect'in ilk (eager) tick'i boş `activeRoutes`'u yazıp saklı
|
|
11
|
+
* pinned'leri silerdi.
|
|
12
|
+
*
|
|
13
|
+
* **Duplicate önlemi**: Aktif tab pinned'di ise restore SAKLI `tabId` ile ekler;
|
|
14
|
+
* F5 sonrası korunan `history.state._tab` bununla eşleşir → handler türetmez, sadece
|
|
15
|
+
* focus eder. Aktif tab pinned değildiyse eşleşme olmaz → handler gelen URL'i yeni
|
|
16
|
+
* aktif tab olarak ekler (deep-link gereksinimi: "kayıtlıları yükle + URL'i ekle").
|
|
17
|
+
*/
|
|
18
|
+
export declare class TabSessionService {
|
|
19
|
+
private readonly navService;
|
|
20
|
+
private readonly keycloak;
|
|
21
|
+
private readonly injector;
|
|
22
|
+
private readonly STORAGE_PREFIX;
|
|
23
|
+
/** `NavService.MAX_TABS` ile aynı tavan — restore ve save iki tarafta da sınırlanır. */
|
|
24
|
+
private readonly MAX_PERSISTED;
|
|
25
|
+
private restored;
|
|
26
|
+
/** Son yazılan payload — değişmediyse gereksiz `localStorage` write'ı atlanır. */
|
|
27
|
+
private lastWritten;
|
|
28
|
+
/**
|
|
29
|
+
* Kullanıcı bazlı storage anahtarı. Mevcut kod (`app.profilesidebar.component.ts`)
|
|
30
|
+
* `idTokenParsed.sub` kullanıyor; her iki token şeklini de kapsamak için fallback
|
|
31
|
+
* zinciri — kullanıcı çözülemezse `anonymous`.
|
|
32
|
+
*/
|
|
33
|
+
private storageKey;
|
|
34
|
+
/**
|
|
35
|
+
* SENKRON restore. `AppLayoutComponent` constructor'dan, ilk `NavigationEnd`
|
|
36
|
+
* işlenmeden ÖNCE çağrılmalı — böylece `NavService.activeRoute` handler'ının
|
|
37
|
+
* `knownTab` guard'ı restore edilen tabId'leri görür. Idempotent: ikinci çağrı no-op.
|
|
38
|
+
*/
|
|
39
|
+
restore(): void;
|
|
40
|
+
/**
|
|
41
|
+
* Save effect'ini `restore()` SONRASI kurar. İlk çalıştığında `activeRoutes` zaten
|
|
42
|
+
* restore edilmiş pinned'leri içerdiği için yazım idempotent kalır. Effect, service'in
|
|
43
|
+
* kendi `Injector`'ına bağlanır (field initializer / constructor dışında kurulduğu için
|
|
44
|
+
* explicit injector zorunlu).
|
|
45
|
+
*/
|
|
46
|
+
private armSave;
|
|
47
|
+
private save;
|
|
48
|
+
private load;
|
|
49
|
+
private isValidPersistedTab;
|
|
50
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<TabSessionService, never>;
|
|
51
|
+
static ɵprov: i0.ɵɵInjectableDeclaration<TabSessionService>;
|
|
52
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { Injectable, Injector, effect, inject } from '@angular/core';
|
|
2
|
+
|
|
3
|
+
import { KeycloakService } from 'keycloak-angular';
|
|
4
|
+
|
|
5
|
+
import { NavItem, NavService } from 'aril/boot/config/apps';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* localStorage'a serialize edilen pinned tab kaydı. `pinned` alanı SAKLANMAZ
|
|
9
|
+
* (yüklenen her kayıt zaten pinned'dir). `tabId` SAKLANIR — F5 sonrası
|
|
10
|
+
* `window.history.state._tab` ile eşleşip `NavService.activeRoute` handler'ının
|
|
11
|
+
* aynı tab'ı tekrar türetmesini (duplicate) engeller.
|
|
12
|
+
*/
|
|
13
|
+
interface PersistedTab {
|
|
14
|
+
tabId: string;
|
|
15
|
+
navLink: string;
|
|
16
|
+
navName: string;
|
|
17
|
+
icon?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Sabitlenmiş (pinned) tab'ları kullanıcı bazlı `localStorage` anahtarında kalıcı
|
|
22
|
+
* kılar. F5 / yeniden login / yeni sekme açılışında pinned tab'lar geri yüklenir;
|
|
23
|
+
* non-pinned tab'lar saklanmaz.
|
|
24
|
+
*
|
|
25
|
+
* **Akış**: `AppLayoutComponent` constructor'da (menü/home provider'ları set
|
|
26
|
+
* edildikten SONRA) `restore()` SENKRON çağrılır → pinned'ler `NavService.activeRoutes`'a
|
|
27
|
+
* eklenir → save effect armlanır. Save effect `restore()` ÖNCESİ kurulmaz; aksi
|
|
28
|
+
* halde Angular effect'in ilk (eager) tick'i boş `activeRoutes`'u yazıp saklı
|
|
29
|
+
* pinned'leri silerdi.
|
|
30
|
+
*
|
|
31
|
+
* **Duplicate önlemi**: Aktif tab pinned'di ise restore SAKLI `tabId` ile ekler;
|
|
32
|
+
* F5 sonrası korunan `history.state._tab` bununla eşleşir → handler türetmez, sadece
|
|
33
|
+
* focus eder. Aktif tab pinned değildiyse eşleşme olmaz → handler gelen URL'i yeni
|
|
34
|
+
* aktif tab olarak ekler (deep-link gereksinimi: "kayıtlıları yükle + URL'i ekle").
|
|
35
|
+
*/
|
|
36
|
+
@Injectable({ providedIn: 'root' })
|
|
37
|
+
export class TabSessionService {
|
|
38
|
+
private readonly navService = inject(NavService);
|
|
39
|
+
private readonly keycloak = inject(KeycloakService);
|
|
40
|
+
private readonly injector = inject(Injector);
|
|
41
|
+
|
|
42
|
+
private readonly STORAGE_PREFIX = 'tab-session:';
|
|
43
|
+
/** `NavService.MAX_TABS` ile aynı tavan — restore ve save iki tarafta da sınırlanır. */
|
|
44
|
+
private readonly MAX_PERSISTED = 20;
|
|
45
|
+
|
|
46
|
+
private restored = false;
|
|
47
|
+
/** Son yazılan payload — değişmediyse gereksiz `localStorage` write'ı atlanır. */
|
|
48
|
+
private lastWritten = '';
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Kullanıcı bazlı storage anahtarı. Mevcut kod (`app.profilesidebar.component.ts`)
|
|
52
|
+
* `idTokenParsed.sub` kullanıyor; her iki token şeklini de kapsamak için fallback
|
|
53
|
+
* zinciri — kullanıcı çözülemezse `anonymous`.
|
|
54
|
+
*/
|
|
55
|
+
private storageKey(): string {
|
|
56
|
+
const kc = this.keycloak.getKeycloakInstance();
|
|
57
|
+
const sub = kc?.tokenParsed?.sub ?? kc?.idTokenParsed?.sub ?? 'anonymous';
|
|
58
|
+
return this.STORAGE_PREFIX + sub;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* SENKRON restore. `AppLayoutComponent` constructor'dan, ilk `NavigationEnd`
|
|
63
|
+
* işlenmeden ÖNCE çağrılmalı — böylece `NavService.activeRoute` handler'ının
|
|
64
|
+
* `knownTab` guard'ı restore edilen tabId'leri görür. Idempotent: ikinci çağrı no-op.
|
|
65
|
+
*/
|
|
66
|
+
restore(): void {
|
|
67
|
+
if (this.restored) return;
|
|
68
|
+
const persisted = this.load();
|
|
69
|
+
const existingIds = new Set(this.navService.activeRoutes().map((t) => t.tabId));
|
|
70
|
+
for (const item of persisted) {
|
|
71
|
+
// Aynı tabId zaten ekliyse atla (idempotency / olası çift çağrı koruması).
|
|
72
|
+
if (existingIds.has(item.tabId)) continue;
|
|
73
|
+
this.navService.addToActiveRoutes({
|
|
74
|
+
tabId: item.tabId,
|
|
75
|
+
navLink: item.navLink,
|
|
76
|
+
navName: item.navName,
|
|
77
|
+
icon: item.icon,
|
|
78
|
+
pinned: true
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
this.restored = true;
|
|
82
|
+
this.armSave();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Save effect'ini `restore()` SONRASI kurar. İlk çalıştığında `activeRoutes` zaten
|
|
87
|
+
* restore edilmiş pinned'leri içerdiği için yazım idempotent kalır. Effect, service'in
|
|
88
|
+
* kendi `Injector`'ına bağlanır (field initializer / constructor dışında kurulduğu için
|
|
89
|
+
* explicit injector zorunlu).
|
|
90
|
+
*/
|
|
91
|
+
private armSave(): void {
|
|
92
|
+
effect(
|
|
93
|
+
() => {
|
|
94
|
+
const pinned = this.navService.activeRoutes().filter((t) => t.pinned);
|
|
95
|
+
this.save(pinned);
|
|
96
|
+
},
|
|
97
|
+
{ injector: this.injector }
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
private save(pinned: NavItem[]): void {
|
|
102
|
+
try {
|
|
103
|
+
const payload: PersistedTab[] = pinned
|
|
104
|
+
.slice(0, this.MAX_PERSISTED)
|
|
105
|
+
.map((t) => ({ tabId: t.tabId, navLink: t.navLink, navName: t.navName, icon: t.icon }));
|
|
106
|
+
const serialized = JSON.stringify(payload);
|
|
107
|
+
if (serialized === this.lastWritten) return;
|
|
108
|
+
this.lastWritten = serialized;
|
|
109
|
+
localStorage.setItem(this.storageKey(), serialized);
|
|
110
|
+
} catch {
|
|
111
|
+
/* quota / serialize hatası: kritik değil, persist sessizce atlanır */
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
private load(): PersistedTab[] {
|
|
116
|
+
try {
|
|
117
|
+
const raw = localStorage.getItem(this.storageKey());
|
|
118
|
+
if (!raw) return [];
|
|
119
|
+
const parsed: unknown = JSON.parse(raw);
|
|
120
|
+
if (!Array.isArray(parsed)) return [];
|
|
121
|
+
return parsed.filter((x): x is PersistedTab => this.isValidPersistedTab(x));
|
|
122
|
+
} catch {
|
|
123
|
+
return []; // bozuk JSON → yok say, uygulama normal açılır
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
private isValidPersistedTab(x: unknown): x is PersistedTab {
|
|
128
|
+
if (!x || typeof x !== 'object') return false;
|
|
129
|
+
const t = x as Record<string, unknown>;
|
|
130
|
+
return (
|
|
131
|
+
typeof t['tabId'] === 'string' &&
|
|
132
|
+
t['tabId'].length > 0 &&
|
|
133
|
+
typeof t['navLink'] === 'string' &&
|
|
134
|
+
t['navLink'].length > 0 &&
|
|
135
|
+
typeof t['navName'] === 'string'
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
}
|