Skip to content

Commit 6fc47a5

Browse files
committed
fix: open application in the new tab
* On a ctrl (command) + left mouse click is now opened the application in the new tab and routing is stopped in the parent tab. * This is now implemented also for side menu, tables and inner page menu in user-profile (authentication and settings).
1 parent 93fdcbd commit 6fc47a5

File tree

9 files changed

+80
-37
lines changed

9 files changed

+80
-37
lines changed

apps/admin-gui/src/app/shared/side-menu/side-menu-item.service.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ export class SideMenuItemService {
133133
{
134134
label: 'MENU_ITEMS.USER.SETTINGS',
135135
url: [`/myProfile/settings`],
136-
activatedRegex: `^/myProfile/settings$`,
136+
activatedRegex: `^/myProfile/settings$`, // this route is never activated (expandable submenu)
137137
children: [
138138
{
139139
label: 'MENU_ITEMS.USER.PASSWORD_RESET',
@@ -609,7 +609,7 @@ export class SideMenuItemService {
609609
links.push({
610610
label: 'MENU_ITEMS.VO.SETTINGS',
611611
url: [`/organizations/${vo.id}/settings`],
612-
activatedRegex: '/organizations/\\d+/settings$',
612+
activatedRegex: '/organizations/\\d+/settings$', // this route is never activated (expandable submenu)
613613
children: children,
614614
showChildren: 'settings',
615615
});
@@ -900,7 +900,7 @@ export class SideMenuItemService {
900900
links.push({
901901
label: 'MENU_ITEMS.FACILITY.SETTINGS',
902902
url: ['/facilities', facility.id.toString(), 'settings'],
903-
activatedRegex: '/facilities/\\d+/settings$',
903+
activatedRegex: '/facilities/\\d+/settings$', // this route is never activated (expandable submenu)
904904
children: children,
905905
showChildren: 'settings',
906906
});
@@ -1062,7 +1062,7 @@ export class SideMenuItemService {
10621062
links.push({
10631063
label: 'MENU_ITEMS.GROUP.SETTINGS',
10641064
url: [`/organizations/${group.voId}/groups/${group.id}/settings`],
1065-
activatedRegex: '/organizations/\\d+/groups/\\d+/settings$',
1065+
activatedRegex: '/organizations/\\d+/groups/\\d+/settings$', // this route is never activated (expandable submenu)
10661066
children: settingsChildrenLinks,
10671067
showChildren: 'settings',
10681068
});
@@ -1144,7 +1144,7 @@ export class SideMenuItemService {
11441144
links.push({
11451145
label: 'MENU_ITEMS.RESOURCE.SETTINGS',
11461146
url: [baseUrl, `settings`],
1147-
activatedRegex: `${regexStart}/\\d+/resources/\\d+/settings$`,
1147+
activatedRegex: `${regexStart}/\\d+/resources/\\d+/settings$`, // this route is never activated (expandable submenu)
11481148
children: children,
11491149
showChildren: `settings`,
11501150
});

apps/admin-gui/src/app/shared/side-menu/side-menu-item/side-menu-item.component.html

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
<div class="w-100">
22
<div
3-
[routerLink]="item.baseLink"
4-
[perunWebAppsMiddleClickRouterLink]="item.baseLink"
53
[ngStyle]="{'background': item.backgroundColorCss, 'border-top': !root ? dividerStyle: ''}"
64
[matRippleColor]="rippleColor"
75
[class.header-activated]="(currentUrl | activeSideMenuItem: item.baseColorClassRegex) && root"
86
class="side-menu-item"
97
matRipple>
108
<div class="{{item.labelClass}}" matRipple>
11-
<a [ngStyle]="{'color': item.textColorCss}" class="side-menu-item-label clickable">
9+
<a
10+
class="side-menu-item-label clickable"
11+
[ngStyle]="{'color': item.textColorCss}"
12+
(auxclick)="$event.preventDefault()"
13+
[routerLink]="item.baseLink"
14+
[perunWebAppsMiddleClickRouterLink]="item.baseLink">
1215
<mat-icon
1316
[ngStyle]="{'color': 'currentColor'}"
1417
[svgIcon]="item.icon"
@@ -26,8 +29,9 @@
2629
@rollInOut>
2730
<div *ngFor="let link of item.links">
2831
<a
29-
(click)="linkClicked(link)"
30-
[perunWebAppsMiddleClickRouterLink]="link.url"
32+
(click)="navigateOrExpandSideMenu(link, $event)"
33+
(auxclick)="$event.preventDefault()"
34+
[perunWebAppsMiddleClickRouterLink]="link.children ? null : link.url"
3135
[ngClass]="(currentUrl | activeSideMenuItem: link.activatedRegex) && item.activatedClass"
3236
[ngStyle]="{'color': linkTextColor}"
3337
class="clickable d-flex"
@@ -42,6 +46,7 @@
4246
<div *ngIf="expandSections.get(link.showChildren)" @rollInOut class="overflow-hidden">
4347
<a
4448
*ngFor="let child of link?.children"
49+
(auxclick)="$event.preventDefault()"
4550
[routerLink]="child.url"
4651
[perunWebAppsMiddleClickRouterLink]="child.url"
4752
[matRippleColor]="rippleColor"

apps/admin-gui/src/app/shared/side-menu/side-menu-item/side-menu-item.component.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,18 @@ export class SideMenuItemComponent {
4343
});
4444
}
4545

46-
linkClicked(link: SideMenuLink): void {
47-
// Navigate or expand children based on item
48-
if (!link.children) {
49-
void this.router.navigate(link.url);
50-
} else {
51-
this.expandedTilesStore.setItem(link.showChildren);
46+
/**
47+
* Navigate or expand children based on item if ctrl (metaKey) is not pressed
48+
* @param link SideMenuLink with url and list of children
49+
* @param event mouse event used for detect ctrlKey (support for Windows/Linux keyboards) or metaKey (support for Mac keyboards)
50+
*/
51+
navigateOrExpandSideMenu(link: SideMenuLink, event: MouseEvent): void {
52+
if (!event.ctrlKey && !event.metaKey) {
53+
if (!link.children) {
54+
void this.router.navigate(link.url);
55+
} else {
56+
this.expandedTilesStore.setItem(link.showChildren);
57+
}
5258
}
5359
}
5460
}
Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,21 @@
11
<mat-nav-list class="pt-0">
2-
<mat-list-item
2+
<a
3+
mat-list-item
34
(click)="item.external ? goToURL(item.link):shouldHideMenu()"
45
*ngFor="let item of items"
56
[class.activated]="isActive(item.activatedRegex)"
67
mat-ripple
78
[matRippleColor]="'rgba(255, 255, 255, 0.1)'"
89
class="side-menu-item-height"
910
routerLink="{{item.external ? null: item.link}}"
11+
[perunWebAppsMiddleClickRouterLink]="[item.external ? null: item.link]"
12+
(auxclick)="$event.preventDefault()"
1013
queryParamsHandling="merge">
1114
<div [ngStyle]="{'color': textColor}" class="d-flex flex-row">
1215
<mat-icon>{{item.icon}}</mat-icon>
1316
<span class="ms-4">
1417
{{item.external ? (item | localisedText: lang:'label') : item.label | customTranslate: lang | translate}}
1518
</span>
1619
</div>
17-
</mat-list-item>
20+
</a>
1821
</mat-nav-list>
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
11
<div [hidden]="loading">
2-
<mat-list>
3-
<mat-list-item
2+
<mat-nav-list>
3+
<a
4+
mat-list-item
45
*ngFor="let item of items"
56
matRipple
67
[routerLink]="item.url"
8+
[perunWebAppsMiddleClickRouterLink]="[item.url]"
9+
(auxclick)="$event.preventDefault()"
710
queryParamsHandling="merge">
811
<div class="d-flex flex-row">
912
<mat-icon>{{item.icon}}</mat-icon>
1013
<p class="ms-2 mt-auto mb-auto">{{item.label | customTranslate | translate}}</p>
1114
</div>
12-
</mat-list-item>
13-
</mat-list>
15+
</a>
16+
</mat-nav-list>
1417
</div>
1518
<mat-spinner class="ms-auto me-auto" *ngIf="loading"></mat-spinner>

apps/user-profile/src/app/pages/authentication-page/authentication-overview/authentication-overview.component.scss

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
color: grey;
44
}
55

6-
mat-list-item:hover {
6+
mat-nav-list a:hover {
77
background: var(--side-hover) !important;
88
color: var(--side-text-hover) !important;
99
cursor: pointer;
Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
1-
<mat-list>
2-
<mat-list-item
1+
<mat-nav-list>
2+
<a
3+
mat-list-item
34
*ngFor="let item of items"
45
matRipple
56
[routerLink]="item.url"
7+
[perunWebAppsMiddleClickRouterLink]="[item.url]"
8+
(auxclick)="$event.preventDefault()"
69
queryParamsHandling="merge">
710
<div class="d-flex flex-row">
811
<mat-icon>{{item.icon}}</mat-icon>
912
<p class="ms-2 mt-auto mb-auto">{{item.label | customTranslate | translate}}</p>
1013
</div>
11-
</mat-list-item>
12-
</mat-list>
14+
</a>
15+
</mat-nav-list>

apps/user-profile/src/app/pages/settings-page/settings-overview/settings-overview.component.scss

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
mat-list-item:hover {
1+
mat-nav-list a:hover {
22
background: var(--side-hover) !important;
33
color: var(--side-text-hover) !important;
44
cursor: pointer;
Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,43 @@
1-
import { Directive, HostListener, Input } from '@angular/core';
1+
import { Directive, ElementRef, HostListener, Input, Renderer2 } from '@angular/core';
22

33
@Directive({
44
selector: '[perunWebAppsMiddleClickRouterLink]',
55
})
66
export class MiddleClickRouterLinkDirective {
77
@Input() perunWebAppsMiddleClickRouterLink: string[];
88

9+
constructor(private ref: ElementRef, private renderer: Renderer2) {
10+
const htmlElement: HTMLElement = this.ref.nativeElement as HTMLElement;
11+
this.renderer.listen(htmlElement, 'click', (event: MouseEvent) => {
12+
// ctrl (command) + left mouse click will stop routing and open router link in the new tab
13+
if (
14+
// ctrlKey = support for Windows/Linux keyboards
15+
// metaKey = support for Mac keyboards
16+
(event.ctrlKey || event.metaKey) &&
17+
event.button === 0 &&
18+
this.perunWebAppsMiddleClickRouterLink
19+
) {
20+
if (htmlElement.tagName.toLowerCase() === 'tr') {
21+
// handle correct behavior for table 'tr' tag (with this directive)
22+
event.stopImmediatePropagation();
23+
} else {
24+
// handle correct behavior primarily for 'a' tag in our apps (with this directive)
25+
event.preventDefault();
26+
}
27+
window.open(this.getUrlWithParams());
28+
}
29+
});
30+
}
31+
932
@HostListener('mouseup', ['$event']) onClick(event: MouseEvent): void {
10-
if (
11-
event.button === 1 &&
12-
this.perunWebAppsMiddleClickRouterLink !== null &&
13-
this.perunWebAppsMiddleClickRouterLink !== undefined
14-
) {
15-
const fullUrl = this.perunWebAppsMiddleClickRouterLink.join('/');
16-
const queryParams = location.search;
17-
window.open(fullUrl + queryParams);
33+
if (event.button === 1 && this.perunWebAppsMiddleClickRouterLink) {
34+
window.open(this.getUrlWithParams());
1835
}
1936
}
37+
38+
private getUrlWithParams(): string {
39+
const fullUrl = this.perunWebAppsMiddleClickRouterLink.join('/');
40+
const queryParams = location.search;
41+
return fullUrl + queryParams;
42+
}
2043
}

0 commit comments

Comments
 (0)