Skip to content

Commit 888b9b3

Browse files
authored
Add possibility to collapse sidenav (#2004)
* fix nodes list * collapse sidenav * fix test * hide button if sidebar is not visible * add background-color * use View enum
1 parent 5d87c15 commit 888b9b3

19 files changed

+144
-19
lines changed

src/app/core/components/navigation/navigation.component.html

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@
22
<div fxLayout
33
class="km-navbar"
44
fxLayoutAlign=" center">
5+
<button mat-icon-button
6+
fxLayoutAlign="center center"
7+
*ngIf="showCollapseIcon"
8+
(click)="collapseSidenav()">
9+
<i class="km-icon-mask km-icon-collapse-menu"></i>
10+
</button>
511
<a class="km-header-logo"
612
routerLink="projects"></a>
713
<div fxFlex></div>

src/app/core/components/navigation/navigation.component.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ header {
99
}
1010

1111
.km-navbar {
12+
padding-left: 3 * $baseline-grid;
1213
height: $toolbar-height;
1314
position: fixed;
1415
width: 100%;

src/app/core/components/navigation/navigation.component.spec.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,10 @@ import {RouterTestingModule} from '@angular/router/testing';
99
import {SharedModule} from '../../../shared/shared.module';
1010
import {AuthMockService} from '../../../testing/services/auth-mock.service';
1111
import {ProjectMockService} from '../../../testing/services/project-mock.service';
12+
import {SettingsMockService} from '../../../testing/services/settings-mock.service';
1213
import {UserMockService} from '../../../testing/services/user-mock.service';
1314
import {Auth, ProjectService, UserService} from '../../services/index';
15+
import {SettingsService} from '../../services/settings/settings.service';
1416
import {NotificationPanelComponent} from '../notification-panel/notification-panel.component';
1517

1618
import {NavigationComponent} from './navigation.component';
@@ -43,6 +45,7 @@ describe('NavigationComponent', () => {
4345
{provide: UserService, useClass: UserMockService},
4446
{provide: ProjectService, useClass: ProjectMockService},
4547
{provide: Auth, useClass: AuthMockService},
48+
{provide: SettingsService, useClass: SettingsMockService},
4649
],
4750
})
4851
.compileComponents();

src/app/core/components/navigation/navigation.component.ts

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,43 @@
1-
import {Component, OnInit} from '@angular/core';
1+
import {Component, Input, OnInit} from '@angular/core';
22
import {Router} from '@angular/router';
3+
import {Subject} from 'rxjs';
4+
import {switchMap, takeUntil} from 'rxjs/operators';
5+
36
import {MemberEntity} from '../../../shared/entity/MemberEntity';
47
import {Auth, UserService} from '../../services';
8+
import {SettingsService} from '../../services/settings/settings.service';
59

610
@Component({
711
selector: 'kubermatic-navigation',
812
templateUrl: './navigation.component.html',
913
styleUrls: ['./navigation.component.scss'],
1014
})
1115
export class NavigationComponent implements OnInit {
16+
@Input() showCollapseIcon: boolean;
1217
currentUser: MemberEntity;
18+
showSidenav = true;
19+
private _settingsChange = new Subject<void>();
20+
private _unsubscribe: Subject<any> = new Subject();
1321

1422
constructor(
15-
private readonly _auth: Auth, private readonly _router: Router, private readonly _userService: UserService) {}
23+
private readonly _auth: Auth, private readonly _router: Router, private readonly _userService: UserService,
24+
private _settingsService: SettingsService) {}
1625

1726
ngOnInit(): void {
1827
if (this._auth.authenticated()) {
1928
this._userService.loggedInUser.subscribe(user => this.currentUser = user);
2029
}
30+
31+
this._settingsService.userSettings.pipe(takeUntil(this._unsubscribe)).subscribe(settings => {
32+
this.showSidenav = !settings.collapseSidenav;
33+
});
34+
35+
this._settingsChange.pipe(takeUntil(this._unsubscribe))
36+
.pipe(switchMap(() => this._settingsService.patchUserSettings({'collapseSidenav': !this.showSidenav})))
37+
.subscribe(settings => {
38+
this._settingsService.refreshUserSettings();
39+
this.showSidenav = !settings.collapseSidenav;
40+
});
2141
}
2242

2343
isAuthenticated(): boolean {
@@ -49,4 +69,9 @@ export class NavigationComponent implements OnInit {
4969
goToAdminPanel(): void {
5070
this._router.navigate(['settings']);
5171
}
72+
73+
collapseSidenav(): void {
74+
this.showSidenav = !this.showSidenav;
75+
this._settingsChange.next();
76+
}
5277
}

src/app/core/components/sidenav/project/selector.component.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<mat-list-item class="km-project-selector"
22
(click)="openDropdown(select)">
3-
<div class="km-projects-container">
4-
<mat-label>Project</mat-label>
3+
<div [ngClass]="showSidenav ? 'km-projects-container' : 'km-projects-container km-collapse-sidenav'">
4+
<mat-label *ngIf="showSidenav">Project</mat-label>
55
<mat-select #select
66
placeholder="None"
77
class="km-projects-select"

src/app/core/components/sidenav/project/selector.component.scss

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,23 @@ a {
4343
font-size: $font-size-caption;
4444
line-height: $font-size-caption;
4545
}
46+
47+
&.km-collapse-sidenav {
48+
padding: 0 !important;
49+
height: 100%;
50+
51+
mat-select {
52+
height: 100%;
53+
}
54+
55+
i {
56+
height: 100%;
57+
top: 0;
58+
}
59+
60+
.mat-select-placeholder,
61+
.mat-select-value-text {
62+
display: none;
63+
}
64+
}
4665
}

src/app/core/components/sidenav/project/selector.component.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
import {Component, OnDestroy, OnInit} from '@angular/core';
1+
import {Component, Input, OnDestroy, OnInit} from '@angular/core';
22
import {MatSelect, MatSelectChange} from '@angular/material/select';
33
import {Subject} from 'rxjs';
44
import {takeUntil} from 'rxjs/operators';
5+
56
import {ProjectEntity} from '../../../../shared/entity/ProjectEntity';
67
import {ProjectService} from '../../../services';
78

@@ -12,6 +13,7 @@ import {ProjectService} from '../../../services';
1213
})
1314

1415
export class ProjectSelectorComponent implements OnInit, OnDestroy {
16+
@Input() showSidenav: boolean;
1517
projects: ProjectEntity[];
1618
selectedProject: ProjectEntity;
1719

src/app/core/components/sidenav/sidenav.component.html

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
<div class="km-sidenav"
2-
fxLayout="column">
2+
fxLayout="column"
3+
[ngClass]="!showSidenav ? 'km-collapse-sidenav' : ''">
34
<mat-nav-list>
45
<div class="km-sidenav-top">
5-
<km-project-selector></km-project-selector>
6+
<km-project-selector [showSidenav]="showSidenav"></km-project-selector>
67

78
<div [matTooltip]="getTooltip('clusters')">
89
<mat-list-item [ngClass]="getMenuItemClass('clusters')">
@@ -11,7 +12,9 @@
1112
fxLayoutAlign=" center"
1213
[routerLink]="getRouterLink('clusters')"
1314
[ngClass]="getLinkClass('clusters')">
14-
<i class="km-icon-cluster"></i> Clusters</a>
15+
<i class="km-icon-cluster"></i>
16+
<span *ngIf="showSidenav"> Clusters</span>
17+
</a>
1518
</mat-list-item>
1619
</div>
1720

@@ -22,7 +25,9 @@
2225
fxLayoutAlign=" center"
2326
[routerLink]="getRouterLink('sshkeys')"
2427
[ngClass]="getLinkClass('sshkeys')">
25-
<i class="km-icon-key"></i> SSH Keys</a>
28+
<i class="km-icon-key"></i>
29+
<span *ngIf="showSidenav"> SSH Keys</span>
30+
</a>
2631
</mat-list-item>
2732
</div>
2833

@@ -33,7 +38,9 @@
3338
fxLayoutAlign=" center"
3439
[routerLink]="getRouterLink('members')"
3540
[ngClass]="getLinkClass('members')">
36-
<i class="km-icon-member"></i> Members</a>
41+
<i class="km-icon-member"></i>
42+
<span *ngIf="showSidenav"> Members</span>
43+
</a>
3744
</mat-list-item>
3845
</div>
3946

@@ -44,7 +51,8 @@
4451
fxLayoutAlign=" center"
4552
[routerLink]="getRouterLink('serviceaccounts')"
4653
[ngClass]="getLinkClass('serviceaccounts')">
47-
<i class="km-icon-serviceaccount"></i> Service Accounts</a>
54+
<i class="km-icon-serviceaccount"></i>
55+
<span *ngIf="showSidenav"> Service Accounts</span></a>
4856
</mat-list-item>
4957
</div>
5058

@@ -61,7 +69,7 @@
6169
target="_blank"
6270
rel="noopener">
6371
<i [ngStyle]="getCustomLinkIconStyle(link)"></i>
64-
<span>{{link.label}}</span>
72+
<span *ngIf="showSidenav">{{link.label}}</span>
6573
</a>
6674
</mat-list-item>
6775
</mat-nav-list>
@@ -71,13 +79,13 @@
7179

7280
<div class="km-sidenav-bottom">
7381
<mat-nav-list>
74-
<mat-list-item>
82+
<mat-list-item [matTooltip]="getTooltip('projects')">
7583
<a id="km-nav-item-projects"
7684
mat-line
7785
fxLayoutAlign="center center"
7886
routerLink="/projects">
7987
<i class="km-icon-arrow-left"></i>
80-
<span>back to projects</span>
88+
<span *ngIf="showSidenav">back to projects</span>
8189
</a>
8290
</mat-list-item>
8391
</mat-nav-list>

src/app/core/components/sidenav/sidenav.component.scss

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,4 +69,18 @@
6969
height: 2 * $baseline-grid;
7070
}
7171
}
72+
73+
&.km-collapse-sidenav {
74+
width: .3 * $sidenav-width;
75+
overflow-x: hidden;
76+
77+
.mat-nav-list a {
78+
place-content: center !important;
79+
padding: 0 !important;
80+
81+
i {
82+
margin-right: 0;
83+
}
84+
}
85+
}
7286
}

src/app/core/components/sidenav/sidenav.component.ts

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,12 @@ import {Subject} from 'rxjs';
66
import {switchMap, takeUntil} from 'rxjs/operators';
77

88
import {environment} from '../../../../environments/environment';
9+
import {UserSettings} from '../../../shared/entity/MemberEntity';
910
import {ProjectEntity} from '../../../shared/entity/ProjectEntity';
1011
import {GroupConfig} from '../../../shared/model/Config';
1112
import {CustomLink, CustomLinkLocation, filterCustomLinks} from '../../../shared/utils/custom-link-utils/custom-link';
1213
import {ProjectService, UserService} from '../../services';
14+
import {View} from '../../services/auth/auth.guard';
1315
import {SettingsService} from '../../services/settings/settings.service';
1416

1517
@Component({
@@ -20,6 +22,8 @@ import {SettingsService} from '../../services/settings/settings.service';
2022
export class SidenavComponent implements OnInit, OnDestroy {
2123
environment: any = environment;
2224
customLinks: CustomLink[] = [];
25+
settings: UserSettings;
26+
showSidenav = true;
2327
private _selectedProject = {} as ProjectEntity;
2428
private _currentGroupConfig: GroupConfig;
2529
private _unsubscribe = new Subject<void>();
@@ -36,6 +40,11 @@ export class SidenavComponent implements OnInit, OnDestroy {
3640
}
3741
});
3842

43+
this._settingsService.userSettings.pipe(takeUntil(this._unsubscribe)).subscribe(settings => {
44+
this.showSidenav = !settings.collapseSidenav;
45+
this.settings = settings;
46+
});
47+
3948
this._projectService.selectedProject.pipe(takeUntil(this._unsubscribe))
4049
.pipe(switchMap(project => {
4150
this._selectedProject = project;
@@ -66,7 +75,28 @@ export class SidenavComponent implements OnInit, OnDestroy {
6675
}
6776

6877
getTooltip(viewName: string): string {
69-
let tooltip: string;
78+
let tooltip = '';
79+
80+
if (!this.showSidenav) {
81+
switch (viewName) {
82+
case View.Clusters:
83+
tooltip += 'Clusters';
84+
break;
85+
case View.SSHKeys:
86+
tooltip += 'SSH Keys';
87+
break;
88+
case View.Members:
89+
tooltip += 'Members';
90+
break;
91+
case View.ServiceAccounts:
92+
tooltip += 'Service Accounts';
93+
break;
94+
case View.Projects:
95+
tooltip += 'Projects';
96+
break;
97+
}
98+
}
99+
70100
if (!this._hasViewPermissions(viewName)) {
71101
tooltip = 'Cannot enter this view.';
72102
if (this._selectedProject.status !== 'Active') {

0 commit comments

Comments
 (0)