aril 1.2.1 → 1.2.3
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/esm2022/theme/layout/app/expandableMenu/expandable-menu.component.mjs +1 -1
- package/esm2022/theme/layout/app/site-map/site-map-sidebar.component.mjs +3 -3
- package/esm2022/theme/layout/app/static-sidebar/static-sidebar.component.mjs +1 -1
- package/esm2022/util/sync-active-tab-route/src/sync-active-tab-route.directive.mjs +1 -1
- package/fesm2022/aril-theme-layout.mjs +2 -2
- package/fesm2022/aril-theme-layout.mjs.map +1 -1
- package/fesm2022/aril-util-sync-active-tab-route.mjs.map +1 -1
- package/package.json +154 -153
- package/theme/layout/app/expandableMenu/expandable-menu.component.ts +373 -373
- package/theme/layout/app/site-map/site-map-sidebar.component.html +121 -121
- package/theme/layout/app/static-sidebar/static-sidebar.component.ts +336 -336
- package/theme/styles/theme/base/components/input/_inputtext.scss +4 -0
|
@@ -1,336 +1,336 @@
|
|
|
1
|
-
import { NgClass, NgTemplateOutlet } from '@angular/common';
|
|
2
|
-
import { HttpClient } from '@angular/common/http';
|
|
3
|
-
import { Component, OnDestroy, OnInit, Signal, signal } from '@angular/core';
|
|
4
|
-
import { toSignal } from '@angular/core/rxjs-interop';
|
|
5
|
-
import { FormsModule } from '@angular/forms';
|
|
6
|
-
import { NavigationEnd, Router, RouterLink } from '@angular/router';
|
|
7
|
-
|
|
8
|
-
import { ScrollPanelModule } from 'primeng/scrollpanel';
|
|
9
|
-
import { TooltipModule } from 'primeng/tooltip';
|
|
10
|
-
|
|
11
|
-
import { TranslocoModule, TranslocoService } from '@ngneat/transloco';
|
|
12
|
-
import { Subscription, filter } from 'rxjs';
|
|
13
|
-
|
|
14
|
-
import { bridge } from 'aril/boot/bridge';
|
|
15
|
-
import { MenuItemAction, PluginMenuItem } from 'aril/boot/config/apps';
|
|
16
|
-
import { TranslateJsonPipe } from 'aril/util/pipes';
|
|
17
|
-
|
|
18
|
-
import { LayoutService } from '../../service/app.layout.service';
|
|
19
|
-
import { AppMenuService } from '../../service/app.menu.service';
|
|
20
|
-
|
|
21
|
-
interface MenuNode {
|
|
22
|
-
key: string;
|
|
23
|
-
label: any;
|
|
24
|
-
icon?: string;
|
|
25
|
-
routerLink?: string;
|
|
26
|
-
action?: MenuItemAction;
|
|
27
|
-
children?: MenuNode[];
|
|
28
|
-
expanded: boolean;
|
|
29
|
-
actionLoading?: boolean;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
@Component({
|
|
33
|
-
standalone: true,
|
|
34
|
-
selector: 'app-static-sidebar',
|
|
35
|
-
imports: [RouterLink, NgClass, NgTemplateOutlet, ScrollPanelModule, TooltipModule, FormsModule, TranslocoModule],
|
|
36
|
-
templateUrl: './static-sidebar.component.html',
|
|
37
|
-
styleUrl: './static-sidebar.component.scss',
|
|
38
|
-
providers: [TranslateJsonPipe]
|
|
39
|
-
})
|
|
40
|
-
export class StaticSidebarComponent implements OnInit, OnDestroy {
|
|
41
|
-
activeLang: Signal<string>;
|
|
42
|
-
menuNodes = signal<MenuNode[]>([]);
|
|
43
|
-
filteredNodes = signal<MenuNode[]>([]);
|
|
44
|
-
searchTerm = signal<string>('');
|
|
45
|
-
|
|
46
|
-
private routerSubscription: Subscription | null = null;
|
|
47
|
-
private actionSubscription: Subscription | null = null;
|
|
48
|
-
|
|
49
|
-
private readonly MAX_LENGTH = { level0: 25, level1: 20, level2: 16, level3: 13 };
|
|
50
|
-
|
|
51
|
-
constructor(
|
|
52
|
-
private readonly menuService: AppMenuService,
|
|
53
|
-
private readonly router: Router,
|
|
54
|
-
private readonly layoutService: LayoutService,
|
|
55
|
-
private readonly translateJsonPipe: TranslateJsonPipe,
|
|
56
|
-
private readonly translocoService: TranslocoService,
|
|
57
|
-
private readonly http: HttpClient
|
|
58
|
-
) {
|
|
59
|
-
this.activeLang = toSignal(this.translocoService.langChanges$, { initialValue: 'tr' });
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
get isCollapsed(): boolean {
|
|
63
|
-
return this.layoutService.isStaticSidebarCollapsed();
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
get topLevelNodes(): MenuNode[] {
|
|
67
|
-
return this.menuNodes();
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
ngOnInit() {
|
|
71
|
-
this.buildMenuTree();
|
|
72
|
-
this.routerSubscription = this.router.events
|
|
73
|
-
.pipe(filter((event) => event instanceof NavigationEnd))
|
|
74
|
-
.subscribe(() => this.buildMenuTree());
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
ngOnDestroy() {
|
|
78
|
-
this.routerSubscription?.unsubscribe();
|
|
79
|
-
this.actionSubscription?.unsubscribe();
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
isTextTruncated(label: any, lang: string | null, level = 0): boolean {
|
|
83
|
-
if (!label || !lang || !label[lang]) return false;
|
|
84
|
-
const text = label[lang];
|
|
85
|
-
const len = text.length;
|
|
86
|
-
const limits = [this.MAX_LENGTH.level0, this.MAX_LENGTH.level1, this.MAX_LENGTH.level2, this.MAX_LENGTH.level3];
|
|
87
|
-
return len > limits[Math.min(level, 3)];
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
isMobile(): boolean {
|
|
91
|
-
return this.layoutService.isMobile();
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
buildMenuTree() {
|
|
95
|
-
const menuItems = this.getMenuItems();
|
|
96
|
-
const nodes = this.convertToMenuNodes(menuItems);
|
|
97
|
-
this.menuNodes.set(nodes);
|
|
98
|
-
this.filteredNodes.set([...nodes]);
|
|
99
|
-
this.expandActiveMenu();
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
private expandActiveMenu() {
|
|
103
|
-
const nodes = this.menuNodes();
|
|
104
|
-
this.findAndExpandActiveNode(nodes);
|
|
105
|
-
this.filteredNodes.set([...this.filteredNodes()]);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
private findAndExpandActiveNode(nodes: MenuNode[]): MenuNode | null {
|
|
109
|
-
for (const node of nodes) {
|
|
110
|
-
if (this.isActiveRouteForNode(node)) {
|
|
111
|
-
return node;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
if (node.children && node.children.length > 0) {
|
|
115
|
-
const activeChild = this.findAndExpandActiveNode(node.children);
|
|
116
|
-
if (activeChild) {
|
|
117
|
-
// we find active child and then opened its parent nodes
|
|
118
|
-
node.expanded = true;
|
|
119
|
-
return activeChild;
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
return null;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
convertToMenuNodes(items: PluginMenuItem[], parentKey = ''): MenuNode[] {
|
|
127
|
-
return items
|
|
128
|
-
.filter((item) => !item.separator)
|
|
129
|
-
.map((item, index) => {
|
|
130
|
-
const key = parentKey ? `${parentKey}-${index}` : `${index}`;
|
|
131
|
-
const node: MenuNode = {
|
|
132
|
-
key,
|
|
133
|
-
label: item.label,
|
|
134
|
-
icon: item.icon,
|
|
135
|
-
routerLink: item.action ? undefined : item.routerLink,
|
|
136
|
-
action: item.action,
|
|
137
|
-
expanded: false
|
|
138
|
-
};
|
|
139
|
-
if (item.items && item.items.length > 0) {
|
|
140
|
-
node.children = this.convertToMenuNodes(item.items, key);
|
|
141
|
-
}
|
|
142
|
-
return node;
|
|
143
|
-
});
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
getMenuItems(): PluginMenuItem[] {
|
|
147
|
-
return bridge.isHostMode() ? bridge.hostMenuItems() : this.menuService.menuItems();
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
getLocalText(text: any): string {
|
|
151
|
-
return this.translateJsonPipe.transform(text);
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
isActiveRouteForNode(node: MenuNode): boolean {
|
|
155
|
-
if (!node.routerLink) return false;
|
|
156
|
-
const currentUrl = this.router.url;
|
|
157
|
-
const hashUrl = currentUrl.indexOf('#') > -1 ? currentUrl.split('#')[1] : currentUrl;
|
|
158
|
-
if (hashUrl === node.routerLink || hashUrl === '/' + node.routerLink) return true;
|
|
159
|
-
return (
|
|
160
|
-
node.routerLink !== '/' &&
|
|
161
|
-
(hashUrl.startsWith(node.routerLink + '/') || hashUrl.startsWith('/' + node.routerLink + '/'))
|
|
162
|
-
);
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// use on collapsed mode
|
|
166
|
-
isNodeOrChildActive(node: MenuNode): boolean {
|
|
167
|
-
if (this.isActiveRouteForNode(node)) {
|
|
168
|
-
return true;
|
|
169
|
-
}
|
|
170
|
-
if (node.children && node.children.length > 0) {
|
|
171
|
-
return node.children.some((child) => this.isNodeOrChildActive(child));
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
return false;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
isTopLevelNodeActive(node: MenuNode): boolean {
|
|
178
|
-
return this.isNodeOrChildActive(node);
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
isChildNodeActive(node: MenuNode): boolean {
|
|
182
|
-
return this.isActiveRouteForNode(node);
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
toggleNode(node: MenuNode, level = 0) {
|
|
186
|
-
const wasExpanded = node.expanded;
|
|
187
|
-
if (level === 0) {
|
|
188
|
-
// Diğer ana menüleri kapat
|
|
189
|
-
this.closeOtherTopLevelNodes(node);
|
|
190
|
-
}
|
|
191
|
-
node.expanded = !wasExpanded;
|
|
192
|
-
|
|
193
|
-
this.filteredNodes.set([...this.filteredNodes()]);
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
private closeOtherTopLevelNodes(exceptNode: MenuNode) {
|
|
197
|
-
const nodes = this.menuNodes();
|
|
198
|
-
nodes.forEach((node) => {
|
|
199
|
-
if (node !== exceptNode) {
|
|
200
|
-
node.expanded = false;
|
|
201
|
-
this.closeAllChildNodes(node);
|
|
202
|
-
}
|
|
203
|
-
});
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
private closeAllChildNodes(node: MenuNode) {
|
|
207
|
-
if (node.children) {
|
|
208
|
-
node.children.forEach((child) => {
|
|
209
|
-
child.expanded = false;
|
|
210
|
-
this.closeAllChildNodes(child);
|
|
211
|
-
});
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
trackByKey(index: number, node: MenuNode): any {
|
|
216
|
-
return node.key;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
onMenuItemClick() {
|
|
220
|
-
if (this.isMobile()) {
|
|
221
|
-
this.layoutService.closeMobileMenu();
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
this.searchTerm.set('');
|
|
225
|
-
this.filteredNodes.set(this.menuNodes() || []);
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
onActionItemClick(node: MenuNode, event: Event) {
|
|
229
|
-
event.preventDefault();
|
|
230
|
-
event.stopPropagation();
|
|
231
|
-
|
|
232
|
-
if (!node.action || node.actionLoading) return;
|
|
233
|
-
|
|
234
|
-
// Cancel any previous action request
|
|
235
|
-
this.actionSubscription?.unsubscribe();
|
|
236
|
-
|
|
237
|
-
node.actionLoading = true;
|
|
238
|
-
|
|
239
|
-
const request$ =
|
|
240
|
-
node.action.method === 'GET' ?
|
|
241
|
-
this.http.get(node.action.url)
|
|
242
|
-
: this.http.post(node.action.url, node.action.body ?? {});
|
|
243
|
-
|
|
244
|
-
this.actionSubscription = request$.subscribe({
|
|
245
|
-
next: (response: any) => {
|
|
246
|
-
node.actionLoading = false;
|
|
247
|
-
const route = this.buildSuccessRoute(node.action!, response);
|
|
248
|
-
this.router.navigate([route]);
|
|
249
|
-
this.onMenuItemClick();
|
|
250
|
-
},
|
|
251
|
-
error: () => {
|
|
252
|
-
node.actionLoading = false;
|
|
253
|
-
}
|
|
254
|
-
});
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
private buildSuccessRoute(action: MenuItemAction, response: any): string {
|
|
258
|
-
if (!action.successRouteResponseKey) return action.successRoute;
|
|
259
|
-
|
|
260
|
-
const value = this.getValueByPath(response, action.successRouteResponseKey);
|
|
261
|
-
if (value === undefined || value === null) return action.successRoute;
|
|
262
|
-
|
|
263
|
-
return `${action.successRoute}/${value}`;
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
private getValueByPath(obj: any, path: string): any {
|
|
267
|
-
if (!obj || !path) return undefined;
|
|
268
|
-
|
|
269
|
-
return path.split('.').reduce((current, key) => {
|
|
270
|
-
if (current === undefined || current === null) return undefined;
|
|
271
|
-
|
|
272
|
-
// Handle array access like 'result[0]'
|
|
273
|
-
const arrayMatch = key.match(/^(.+?)\[(\d+)\]$/);
|
|
274
|
-
if (arrayMatch) {
|
|
275
|
-
const arrayKey = arrayMatch[1];
|
|
276
|
-
const index = parseInt(arrayMatch[2], 10);
|
|
277
|
-
return current[arrayKey]?.[index];
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
return current[key];
|
|
281
|
-
}, obj);
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
onCollapsedItemClick(node: MenuNode) {
|
|
285
|
-
if (this.isCollapsed) {
|
|
286
|
-
this.layoutService.onMenuToggle();
|
|
287
|
-
}
|
|
288
|
-
this.closeOtherTopLevelNodes(node);
|
|
289
|
-
if (node.children && node.children.length > 0) {
|
|
290
|
-
node.expanded = true;
|
|
291
|
-
}
|
|
292
|
-
this.filteredNodes.set([...this.filteredNodes()]);
|
|
293
|
-
if (node.routerLink) {
|
|
294
|
-
this.onMenuItemClick();
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
onSearchChange(): void {
|
|
299
|
-
const currentSearchTerm = this.searchTerm()?.toLowerCase()?.trim();
|
|
300
|
-
if (!currentSearchTerm) {
|
|
301
|
-
this.filteredNodes.set(this.menuNodes() || []);
|
|
302
|
-
return;
|
|
303
|
-
}
|
|
304
|
-
this.filteredNodes.set(this.filterNodes(this.menuNodes(), currentSearchTerm));
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
private filterNodes(nodes: MenuNode[], searchTerm: string): MenuNode[] {
|
|
308
|
-
const filtered: MenuNode[] = [];
|
|
309
|
-
const currentLang = this.translocoService.getActiveLang();
|
|
310
|
-
|
|
311
|
-
for (const node of nodes) {
|
|
312
|
-
// Convert label to string for search based on active language
|
|
313
|
-
const labelText =
|
|
314
|
-
typeof node.label === 'string' ?
|
|
315
|
-
node.label
|
|
316
|
-
: (node.label as any)?.[currentLang] || (node.label as any)?.tr || (node.label as any)?.en || '';
|
|
317
|
-
|
|
318
|
-
const matches = labelText.toLowerCase().includes(searchTerm);
|
|
319
|
-
let filteredChildren: MenuNode[] = [];
|
|
320
|
-
|
|
321
|
-
if (node.children) {
|
|
322
|
-
filteredChildren = this.filterNodes(node.children, searchTerm);
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
if (matches || filteredChildren.length > 0) {
|
|
326
|
-
filtered.push({
|
|
327
|
-
...node,
|
|
328
|
-
children: filteredChildren.length > 0 ? filteredChildren : node.children,
|
|
329
|
-
expanded: filteredChildren.length > 0
|
|
330
|
-
});
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
return filtered;
|
|
335
|
-
}
|
|
336
|
-
}
|
|
1
|
+
import { NgClass, NgTemplateOutlet } from '@angular/common';
|
|
2
|
+
import { HttpClient } from '@angular/common/http';
|
|
3
|
+
import { Component, OnDestroy, OnInit, Signal, signal } from '@angular/core';
|
|
4
|
+
import { toSignal } from '@angular/core/rxjs-interop';
|
|
5
|
+
import { FormsModule } from '@angular/forms';
|
|
6
|
+
import { NavigationEnd, Router, RouterLink } from '@angular/router';
|
|
7
|
+
|
|
8
|
+
import { ScrollPanelModule } from 'primeng/scrollpanel';
|
|
9
|
+
import { TooltipModule } from 'primeng/tooltip';
|
|
10
|
+
|
|
11
|
+
import { TranslocoModule, TranslocoService } from '@ngneat/transloco';
|
|
12
|
+
import { Subscription, filter } from 'rxjs';
|
|
13
|
+
|
|
14
|
+
import { bridge } from 'aril/boot/bridge';
|
|
15
|
+
import { MenuItemAction, PluginMenuItem } from 'aril/boot/config/apps';
|
|
16
|
+
import { TranslateJsonPipe } from 'aril/util/pipes';
|
|
17
|
+
|
|
18
|
+
import { LayoutService } from '../../service/app.layout.service';
|
|
19
|
+
import { AppMenuService } from '../../service/app.menu.service';
|
|
20
|
+
|
|
21
|
+
interface MenuNode {
|
|
22
|
+
key: string;
|
|
23
|
+
label: any;
|
|
24
|
+
icon?: string;
|
|
25
|
+
routerLink?: string;
|
|
26
|
+
action?: MenuItemAction;
|
|
27
|
+
children?: MenuNode[];
|
|
28
|
+
expanded: boolean;
|
|
29
|
+
actionLoading?: boolean;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
@Component({
|
|
33
|
+
standalone: true,
|
|
34
|
+
selector: 'app-static-sidebar',
|
|
35
|
+
imports: [RouterLink, NgClass, NgTemplateOutlet, ScrollPanelModule, TooltipModule, FormsModule, TranslocoModule],
|
|
36
|
+
templateUrl: './static-sidebar.component.html',
|
|
37
|
+
styleUrl: './static-sidebar.component.scss',
|
|
38
|
+
providers: [TranslateJsonPipe]
|
|
39
|
+
})
|
|
40
|
+
export class StaticSidebarComponent implements OnInit, OnDestroy {
|
|
41
|
+
activeLang: Signal<string>;
|
|
42
|
+
menuNodes = signal<MenuNode[]>([]);
|
|
43
|
+
filteredNodes = signal<MenuNode[]>([]);
|
|
44
|
+
searchTerm = signal<string>('');
|
|
45
|
+
|
|
46
|
+
private routerSubscription: Subscription | null = null;
|
|
47
|
+
private actionSubscription: Subscription | null = null;
|
|
48
|
+
|
|
49
|
+
private readonly MAX_LENGTH = { level0: 25, level1: 20, level2: 16, level3: 13 };
|
|
50
|
+
|
|
51
|
+
constructor(
|
|
52
|
+
private readonly menuService: AppMenuService,
|
|
53
|
+
private readonly router: Router,
|
|
54
|
+
private readonly layoutService: LayoutService,
|
|
55
|
+
private readonly translateJsonPipe: TranslateJsonPipe,
|
|
56
|
+
private readonly translocoService: TranslocoService,
|
|
57
|
+
private readonly http: HttpClient
|
|
58
|
+
) {
|
|
59
|
+
this.activeLang = toSignal(this.translocoService.langChanges$, { initialValue: 'tr' });
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
get isCollapsed(): boolean {
|
|
63
|
+
return this.layoutService.isStaticSidebarCollapsed();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
get topLevelNodes(): MenuNode[] {
|
|
67
|
+
return this.menuNodes();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
ngOnInit() {
|
|
71
|
+
this.buildMenuTree();
|
|
72
|
+
this.routerSubscription = this.router.events
|
|
73
|
+
.pipe(filter((event) => event instanceof NavigationEnd))
|
|
74
|
+
.subscribe(() => this.buildMenuTree());
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
ngOnDestroy() {
|
|
78
|
+
this.routerSubscription?.unsubscribe();
|
|
79
|
+
this.actionSubscription?.unsubscribe();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
isTextTruncated(label: any, lang: string | null, level = 0): boolean {
|
|
83
|
+
if (!label || !lang || !label[lang]) return false;
|
|
84
|
+
const text = label[lang];
|
|
85
|
+
const len = text.length;
|
|
86
|
+
const limits = [this.MAX_LENGTH.level0, this.MAX_LENGTH.level1, this.MAX_LENGTH.level2, this.MAX_LENGTH.level3];
|
|
87
|
+
return len > limits[Math.min(level, 3)];
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
isMobile(): boolean {
|
|
91
|
+
return this.layoutService.isMobile();
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
buildMenuTree() {
|
|
95
|
+
const menuItems = this.getMenuItems();
|
|
96
|
+
const nodes = this.convertToMenuNodes(menuItems);
|
|
97
|
+
this.menuNodes.set(nodes);
|
|
98
|
+
this.filteredNodes.set([...nodes]);
|
|
99
|
+
this.expandActiveMenu();
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
private expandActiveMenu() {
|
|
103
|
+
const nodes = this.menuNodes();
|
|
104
|
+
this.findAndExpandActiveNode(nodes);
|
|
105
|
+
this.filteredNodes.set([...this.filteredNodes()]);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
private findAndExpandActiveNode(nodes: MenuNode[]): MenuNode | null {
|
|
109
|
+
for (const node of nodes) {
|
|
110
|
+
if (this.isActiveRouteForNode(node)) {
|
|
111
|
+
return node;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (node.children && node.children.length > 0) {
|
|
115
|
+
const activeChild = this.findAndExpandActiveNode(node.children);
|
|
116
|
+
if (activeChild) {
|
|
117
|
+
// we find active child and then opened its parent nodes
|
|
118
|
+
node.expanded = true;
|
|
119
|
+
return activeChild;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
convertToMenuNodes(items: PluginMenuItem[], parentKey = ''): MenuNode[] {
|
|
127
|
+
return items
|
|
128
|
+
.filter((item) => !item.separator)
|
|
129
|
+
.map((item, index) => {
|
|
130
|
+
const key = parentKey ? `${parentKey}-${index}` : `${index}`;
|
|
131
|
+
const node: MenuNode = {
|
|
132
|
+
key,
|
|
133
|
+
label: item.label,
|
|
134
|
+
icon: item.icon,
|
|
135
|
+
routerLink: item.action ? undefined : item.routerLink,
|
|
136
|
+
action: item.action,
|
|
137
|
+
expanded: false
|
|
138
|
+
};
|
|
139
|
+
if (item.items && item.items.length > 0) {
|
|
140
|
+
node.children = this.convertToMenuNodes(item.items, key);
|
|
141
|
+
}
|
|
142
|
+
return node;
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
getMenuItems(): PluginMenuItem[] {
|
|
147
|
+
return bridge.isHostMode() ? bridge.hostMenuItems() : this.menuService.menuItems();
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
getLocalText(text: any): string {
|
|
151
|
+
return this.translateJsonPipe.transform(text);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
isActiveRouteForNode(node: MenuNode): boolean {
|
|
155
|
+
if (!node.routerLink) return false;
|
|
156
|
+
const currentUrl = this.router.url;
|
|
157
|
+
const hashUrl = currentUrl.indexOf('#') > -1 ? currentUrl.split('#')[1] : currentUrl;
|
|
158
|
+
if (hashUrl === node.routerLink || hashUrl === '/' + node.routerLink) return true;
|
|
159
|
+
return (
|
|
160
|
+
node.routerLink !== '/' &&
|
|
161
|
+
(hashUrl.startsWith(node.routerLink + '/') || hashUrl.startsWith('/' + node.routerLink + '/'))
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// use on collapsed mode
|
|
166
|
+
isNodeOrChildActive(node: MenuNode): boolean {
|
|
167
|
+
if (this.isActiveRouteForNode(node)) {
|
|
168
|
+
return true;
|
|
169
|
+
}
|
|
170
|
+
if (node.children && node.children.length > 0) {
|
|
171
|
+
return node.children.some((child) => this.isNodeOrChildActive(child));
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
isTopLevelNodeActive(node: MenuNode): boolean {
|
|
178
|
+
return this.isNodeOrChildActive(node);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
isChildNodeActive(node: MenuNode): boolean {
|
|
182
|
+
return this.isActiveRouteForNode(node);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
toggleNode(node: MenuNode, level = 0) {
|
|
186
|
+
const wasExpanded = node.expanded;
|
|
187
|
+
if (level === 0) {
|
|
188
|
+
// Diğer ana menüleri kapat
|
|
189
|
+
this.closeOtherTopLevelNodes(node);
|
|
190
|
+
}
|
|
191
|
+
node.expanded = !wasExpanded;
|
|
192
|
+
|
|
193
|
+
this.filteredNodes.set([...this.filteredNodes()]);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
private closeOtherTopLevelNodes(exceptNode: MenuNode) {
|
|
197
|
+
const nodes = this.menuNodes();
|
|
198
|
+
nodes.forEach((node) => {
|
|
199
|
+
if (node !== exceptNode) {
|
|
200
|
+
node.expanded = false;
|
|
201
|
+
this.closeAllChildNodes(node);
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
private closeAllChildNodes(node: MenuNode) {
|
|
207
|
+
if (node.children) {
|
|
208
|
+
node.children.forEach((child) => {
|
|
209
|
+
child.expanded = false;
|
|
210
|
+
this.closeAllChildNodes(child);
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
trackByKey(index: number, node: MenuNode): any {
|
|
216
|
+
return node.key;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
onMenuItemClick() {
|
|
220
|
+
if (this.isMobile()) {
|
|
221
|
+
this.layoutService.closeMobileMenu();
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
this.searchTerm.set('');
|
|
225
|
+
this.filteredNodes.set(this.menuNodes() || []);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
onActionItemClick(node: MenuNode, event: Event) {
|
|
229
|
+
event.preventDefault();
|
|
230
|
+
event.stopPropagation();
|
|
231
|
+
|
|
232
|
+
if (!node.action || node.actionLoading) return;
|
|
233
|
+
|
|
234
|
+
// Cancel any previous action request
|
|
235
|
+
this.actionSubscription?.unsubscribe();
|
|
236
|
+
|
|
237
|
+
node.actionLoading = true;
|
|
238
|
+
|
|
239
|
+
const request$ =
|
|
240
|
+
node.action.method === 'GET' ?
|
|
241
|
+
this.http.get(node.action.url)
|
|
242
|
+
: this.http.post(node.action.url, node.action.body ?? {});
|
|
243
|
+
|
|
244
|
+
this.actionSubscription = request$.subscribe({
|
|
245
|
+
next: (response: any) => {
|
|
246
|
+
node.actionLoading = false;
|
|
247
|
+
const route = this.buildSuccessRoute(node.action!, response);
|
|
248
|
+
this.router.navigate([route]);
|
|
249
|
+
this.onMenuItemClick();
|
|
250
|
+
},
|
|
251
|
+
error: () => {
|
|
252
|
+
node.actionLoading = false;
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
private buildSuccessRoute(action: MenuItemAction, response: any): string {
|
|
258
|
+
if (!action.successRouteResponseKey) return action.successRoute;
|
|
259
|
+
|
|
260
|
+
const value = this.getValueByPath(response, action.successRouteResponseKey);
|
|
261
|
+
if (value === undefined || value === null) return action.successRoute;
|
|
262
|
+
|
|
263
|
+
return `${action.successRoute}/${value}`;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
private getValueByPath(obj: any, path: string): any {
|
|
267
|
+
if (!obj || !path) return undefined;
|
|
268
|
+
|
|
269
|
+
return path.split('.').reduce((current, key) => {
|
|
270
|
+
if (current === undefined || current === null) return undefined;
|
|
271
|
+
|
|
272
|
+
// Handle array access like 'result[0]'
|
|
273
|
+
const arrayMatch = key.match(/^(.+?)\[(\d+)\]$/);
|
|
274
|
+
if (arrayMatch) {
|
|
275
|
+
const arrayKey = arrayMatch[1];
|
|
276
|
+
const index = parseInt(arrayMatch[2], 10);
|
|
277
|
+
return current[arrayKey]?.[index];
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return current[key];
|
|
281
|
+
}, obj);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
onCollapsedItemClick(node: MenuNode) {
|
|
285
|
+
if (this.isCollapsed) {
|
|
286
|
+
this.layoutService.onMenuToggle();
|
|
287
|
+
}
|
|
288
|
+
this.closeOtherTopLevelNodes(node);
|
|
289
|
+
if (node.children && node.children.length > 0) {
|
|
290
|
+
node.expanded = true;
|
|
291
|
+
}
|
|
292
|
+
this.filteredNodes.set([...this.filteredNodes()]);
|
|
293
|
+
if (node.routerLink) {
|
|
294
|
+
this.onMenuItemClick();
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
onSearchChange(): void {
|
|
299
|
+
const currentSearchTerm = this.searchTerm()?.toLowerCase()?.trim();
|
|
300
|
+
if (!currentSearchTerm) {
|
|
301
|
+
this.filteredNodes.set(this.menuNodes() || []);
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
this.filteredNodes.set(this.filterNodes(this.menuNodes(), currentSearchTerm));
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
private filterNodes(nodes: MenuNode[], searchTerm: string): MenuNode[] {
|
|
308
|
+
const filtered: MenuNode[] = [];
|
|
309
|
+
const currentLang = this.translocoService.getActiveLang();
|
|
310
|
+
|
|
311
|
+
for (const node of nodes) {
|
|
312
|
+
// Convert label to string for search based on active language
|
|
313
|
+
const labelText =
|
|
314
|
+
typeof node.label === 'string' ?
|
|
315
|
+
node.label
|
|
316
|
+
: (node.label as any)?.[currentLang] || (node.label as any)?.tr || (node.label as any)?.en || '';
|
|
317
|
+
|
|
318
|
+
const matches = labelText.toLowerCase().includes(searchTerm);
|
|
319
|
+
let filteredChildren: MenuNode[] = [];
|
|
320
|
+
|
|
321
|
+
if (node.children) {
|
|
322
|
+
filteredChildren = this.filterNodes(node.children, searchTerm);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
if (matches || filteredChildren.length > 0) {
|
|
326
|
+
filtered.push({
|
|
327
|
+
...node,
|
|
328
|
+
children: filteredChildren.length > 0 ? filteredChildren : node.children,
|
|
329
|
+
expanded: filteredChildren.length > 0
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
return filtered;
|
|
335
|
+
}
|
|
336
|
+
}
|