feat: add app shell (angular-material)

Add app shell built using angular-material and flex-layout. Refractor
code due to configuration changes.

Fixes #52
This commit is contained in:
abhay_1806
2018-06-23 20:06:32 +05:30
committed by Abhay Chawla
parent b1c2d6d98e
commit fc7ae1d0ba
98 changed files with 1098 additions and 532 deletions
@@ -0,0 +1,2 @@
<!-- TODO: Breadcrumb Links -->
<router-outlet></router-outlet>
@@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ContentComponent } from './content.component';
describe('ContentComponent', () => {
let component: ContentComponent;
let fixture: ComponentFixture<ContentComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ ContentComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ContentComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,15 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'mifosx-content',
templateUrl: './content.component.html',
styleUrls: ['./content.component.scss']
})
export class ContentComponent implements OnInit {
constructor() { }
ngOnInit() {
}
}
@@ -1,76 +0,0 @@
<!-- <header>
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<a class="navbar-brand" href="https://github.com/ngx-rocket" translate>APP_NAME</a>
<button class="navbar-toggler" type="button" aria-controls="navbar-menu"
aria-label="Toggle navigation" (click)="toggleMenu()" [attr.aria-expanded]="!menuHidden">
<span class="navbar-toggler-icon"></span>
</button>
<div id="navbar-menu" class="collapse navbar-collapse float-xs-none" [ngbCollapse]="menuHidden">
<div class="navbar-nav">
<a class="nav-item nav-link text-uppercase" routerLink="/home" routerLinkActive="active">
<i class="fas fa-home"></i>
<span translate>Home</span>
</a>
<a class="nav-item nav-link text-uppercase" routerLink="/about" routerLinkActive="active">
<i class="fas fa-question-circle"></i>
<span translate>About</span>
</a>
</div>
<div class="navbar-nav ml-auto">
<div class="nav-item" ngbDropdown placement="bottom-right">
<a id="language-dropdown" class="nav-link" ngbDropdownToggle>{{currentLanguage}}</a>
<div ngbDropdownMenu aria-labelledby="language-dropdown">
<button class="dropdown-item" *ngFor="let language of languages" (click)="setLanguage(language)">
{{language}}
</button>
</div>
</div>
<div class="nav-item" ngbDropdown placement="bottom-right">
<a id="user-dropdown" class="nav-link" ngbDropdownToggle>
<i class="fas fa-user-circle"></i>
</a>
<div ngbDropdownMenu aria-labelledby="user-dropdown">
<h6 class="dropdown-header">
<span translate>Logged in as</span> <b>{{username}}</b></h6>
<div class="dropdown-divider"></div>
<button class="dropdown-item" (click)="logout()" translate>Logout</button>
</div>
</div>
</div>
</div>
</nav>
</header> -->
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<a class="navbar-brand" href="#"><h2>MifosX</h2></a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarColor02" aria-controls="navbarColor02" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarColor02">
<ul class="navbar-nav mr-auto">
<li class="nav-item active">
<a class="nav-link" href="#"><h5 class="font-weight-normal">Home</h5> <span class="sr-only">(current)</span></a>
</li>
<li class="nav-item">
<a class="nav-link" routerLink="/clients" routerLinkActive="active"><h5 class="font-weight-normal">Clients</h5></a>
</li>
<li class="nav-item">
<a class="nav-link" href="#"><h5 class="font-weight-normal">Groups</h5></a>
</li>
<li class="nav-item dropdown" ngbDropdown>
<a class="nav-link" id="self-service-dropdown">
<h5 class="font-weight-normal" ngbDropdownToggle>Self Service</h5>
</a>
<div class="dropdown-menu" ngbDropdownMenu arial-labelledby="self-service-dropdown">
<a class="dropdown-item" routerLink="/self-service/users" routerLinkActive="active">User Management</a>
</div>
</li>
</ul>
<form class="form-inline my-2 my-lg-0">
<input class="form-control mr-sm-2" placeholder="Search" type="text">
<!-- <button class="btn btn-secondary my-2 mr-sm-2 my-sm-0" type="submit">Search</button> -->
</form>
<button class="btn btn-secondary my-2 mr-sm-2 my-sm-0" (click)="logout()" type="submit">Logout</button>
</div>
</nav>
@@ -1,10 +0,0 @@
/* @import "src/theme/theme-variables";
.navbar {
margin-bottom: $spacer;
}
.nav-link.dropdown-toggle {
cursor: pointer;
}
*/
@@ -1,40 +0,0 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { TranslateModule } from '@ngx-translate/core';
import { RouterTestingModule } from '@angular/router/testing';
import { AuthenticationService } from '../../authentication/authentication.service';
import { MockAuthenticationService } from '../../authentication/authentication.service.mock';
import { I18nService } from '../../i18n.service';
import { HeaderComponent } from './header.component';
describe('HeaderComponent', () => {
let component: HeaderComponent;
let fixture: ComponentFixture<HeaderComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
RouterTestingModule,
NgbModule.forRoot(),
TranslateModule.forRoot()
],
declarations: [HeaderComponent],
providers: [
{ provide: AuthenticationService, useClass: MockAuthenticationService },
I18nService
]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(HeaderComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -1,48 +0,0 @@
import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { AuthenticationService } from '../../authentication/authentication.service';
import { I18nService } from '../../i18n.service';
@Component({
selector: 'app-header',
templateUrl: './header.component.html',
styleUrls: ['./header.component.scss']
})
export class HeaderComponent implements OnInit {
menuHidden = true;
constructor(private router: Router,
private authenticationService: AuthenticationService,
private i18nService: I18nService) { }
ngOnInit() { }
toggleMenu() {
this.menuHidden = !this.menuHidden;
}
setLanguage(language: string) {
this.i18nService.language = language;
}
logout() {
this.authenticationService.logout()
.subscribe(() => this.router.navigate(['/login'], { replaceUrl: true }));
}
get currentLanguage(): string {
return this.i18nService.language;
}
get languages(): string[] {
return this.i18nService.supportedLanguages;
}
get username(): string | null {
const credentials = this.authenticationService.credentials;
return credentials ? credentials.username : null;
}
}
+26 -2
View File
@@ -1,2 +1,26 @@
<app-header></app-header>
<router-outlet></router-outlet>
<mat-sidenav-container id="mifosx-shell-container" autosize>
<mat-sidenav
#sidenav
class="sidebar-panel"
[ngClass]="{
'sidebar-full': !sidenavCollapsed,
'sidebar-compact': sidenavCollapsed
}"
[attr.role]="(isHandset$ | async) ? 'dialog' : 'navigation'"
[mode]="(isHandset$ | async) ? 'over' : 'side'"
[opened]="!(isHandset$ | async)">
<mifosx-sidenav [sidenavCollapsed]="sidenavCollapsed"></mifosx-sidenav>
</mat-sidenav>
<mat-sidenav-content>
<mifosx-toolbar [sidenav]="sidenav" (collapse)="toggleCollapse($event)"></mifosx-toolbar>
<mifosx-content></mifosx-content>
</mat-sidenav-content>
</mat-sidenav-container>
+20
View File
@@ -0,0 +1,20 @@
#mifosx-shell-container {
max-width: 100%;
height: 100vh;
.sidebar-panel {
background-color: white; // TODO: Theme Configuration
min-height: 100vh;
overflow: hidden;
box-shadow: 0 3px 1px -2px rgba(0,0,0,.2), 0 2px 2px 0 rgba(0,0,0,.14), 0 1px 5px 0 rgba(0,0,0,.12);
}
.sidebar-full {
width: 15rem;
}
.sidebar-compact {
width: 4rem;
}
}
@@ -1,7 +1,6 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { TranslateModule } from '@ngx-translate/core';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { AuthenticationService } from '../authentication/authentication.service';
import { MockAuthenticationService } from '../authentication/authentication.service.mock';
@@ -17,7 +16,6 @@ describe('ShellComponent', () => {
imports: [
RouterTestingModule,
TranslateModule.forRoot(),
NgbModule.forRoot(),
CoreModule
],
providers: [
+18 -3
View File
@@ -1,14 +1,29 @@
import { Component, OnInit } from '@angular/core';
import { BreakpointObserver, Breakpoints } from '@angular/cdk/layout';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
@Component({
selector: 'app-shell',
selector: 'mifosx-shell',
templateUrl: './shell.component.html',
styleUrls: ['./shell.component.scss']
})
export class ShellComponent implements OnInit {
constructor() { }
isHandset$: Observable<boolean> = this.breakpointObserver.observe(Breakpoints.Handset)
.pipe(
map(result => result.matches)
);
sidenavCollapsed = false;
ngOnInit() { }
constructor(private breakpointObserver: BreakpointObserver) { }
ngOnInit() {
}
toggleCollapse($event: boolean) {
this.sidenavCollapsed = $event;
}
}
@@ -0,0 +1,75 @@
<div
[ngClass]="{
'sidebar-full': !sidenavCollapsed,
'sidebar-compact': sidenavCollapsed
}">
<div class="navigation-panel" fxLayout="column">
<div class="app-brand">
<img src="assets/images/MifosX_logo.png" alt="app-logo" class="app-logo">
<span class="app-logo-text text-muted">Mifos</span>
</div>
<div class="app-user">
<div class="app-user-photo">
<img src="assets/images/user_placeholder.png" alt="app-user-photo">
</div>
<span class="app-user-name mb-05">
Abhay Chawla
</span>
<div class="app-user-controls">
<button class="text-muted xs-button" mat-icon-button>
<mat-icon>settings</mat-icon>
</button>
<button class="text-muted xs-button" mat-icon-button matTooltip="Sign Out" (click)="logout()">
<mat-icon>exit_to_app</mat-icon>
</button>
</div>
</div>
<div class="app-sidenav">
<div class="icon-menu">
<div class="mt-1 mb-1 nav-item-sep">
<mat-divider></mat-divider>
<span class="text-muted icon-menu-title">Frequently Accessed</span>
</div>
<div class="icon-menu-item">
<button mat-raised-button matTooltip="Home">
<mat-icon>home</mat-icon>
</button>
</div>
<div class="icon-menu-item">
<button mat-raised-button matTooltip="Dashboard">
<mat-icon>dashboard</mat-icon>
</button>
</div>
<div class="icon-menu-item">
<button mat-raised-button matTooltip="Navigation">
<mat-icon>navigation</mat-icon>
</button>
</div>
</div>
<div class="nav-item-sep">
<mat-divider></mat-divider>
<span class="text-muted">Main Items</span>
</div>
<mat-nav-list>
<mat-list-item routerLink="/dashboard">
<mat-icon matListIcon>dashboard</mat-icon>
<a matLine>Dashboard</a>
</mat-list-item>
<mat-list-item>
<mat-icon matListIcon>navigation</mat-icon>
<a matLine>Navigation</a>
</mat-list-item>
</mat-nav-list>
</div>
</div>
</div>
@@ -0,0 +1,160 @@
img {
max-width: 100%;
}
.navigation-panel {
position: absolute;
left: 0;
width: 100%;
height: 100%;
padding-top: 64px;
background: hsla(0,0%,100%,.95);
}
.app-brand {
display: flex;
-webkit-box-align: center;
align-items: center;
-webkit-box-pack: center;
justify-content: center;
text-align: center;
position: fixed;
width: 100%;
max-width: 15rem;
height: 64px;
line-height: 64px;
top: 0;
left: 0;
z-index: 9999;
color: #444;
.app-logo {
width: auto;
height: 42px;
margin-right: 8px;
}
.app-logo-text {
width: auto;
height: auto;
font-size: 2rem;
font-weight: normal;
}
}
.app-user {
text-align: center;
width: 100%;
padding: 1rem 0 .4rem;
.app-user-photo {
width: 72px;
margin: 0 auto 8px;
height: 72px;
border: 1px solid hsla(210,8%,51%,.13);
padding: 4px;
border-radius: 50%;
}
img {
width: 100%;
height: 100%;
border-radius: 50%;
}
.app-user-name {
display: block;
font-size: .875rem;
font-weight: 300;
color: rgba(0,0,0,.96);
margin-left: -2px;
}
}
.app-sidenav {
.icon-menu {
padding: 0 24px;
opacity: 1;
.mat-divider {
margin: 0 -24px;
}
.icon-menu-title {
padding-left: 0;
margin-left: -8px;
}
.icon-menu-item {
display: inline-block;
button {
min-width: auto;
margin: 4px;
}
}
}
.nav-item-sep {
padding: 0;
margin: 1rem 0;
}
mat-nav-list {
margin: 0;
padding: 0;
list-style: none;
}
}
.nav-item-sep {
span {
padding: 16px 0 0 16px;
display: block;
font-size: 12px;
}
}
.sidebar-compact {
.app-brand {
.app-logo {
margin: auto;
}
.app-logo-text {
display: none;
}
}
.app-user {
display: none;
}
.app-sidenav {
.icon-menu {
padding: 8px 0 0;
width: 4rem;
.nav-item-sep {
display: none;
}
span {
display: none;
}
.icon-menu-item {
button {
margin: 0 0 4px 0;
padding: 0 20px;
}
}
}
.nav-item-sep {
span {
display: none;
}
}
}
}
@@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { SidenavComponent } from './sidenav.component';
describe('SidenavComponent', () => {
let component: SidenavComponent;
let fixture: ComponentFixture<SidenavComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ SidenavComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(SidenavComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,26 @@
import { Component, OnInit, Input } from '@angular/core';
import { Router } from '@angular/router';
import { AuthenticationService } from '../../authentication/authentication.service';
@Component({
selector: 'mifosx-sidenav',
templateUrl: './sidenav.component.html',
styleUrls: ['./sidenav.component.scss']
})
export class SidenavComponent implements OnInit {
@Input() sidenavCollapsed: boolean;
constructor(private router: Router,
private authenticationService: AuthenticationService) { }
ngOnInit() {
}
logout() {
this.authenticationService.logout()
.subscribe(() => this.router.navigate(['/login'], { replaceUrl: true }));
}
}
@@ -0,0 +1,115 @@
<mat-toolbar fxLayout="row" color="primary" class="toolbar">
<button mat-icon-button *ngIf="!sidenavCollapsed" (click)="toggleSidenav()" matTooltip="Toggle Hide/Open">
<mat-icon>menu</mat-icon>
</button>
<button mat-icon-button *ngIf="sidenav.opened" (click)="toggleCollapse()" matTooltip="Toggle Collapse" fxHide.lt-md="true">
<mat-icon *ngIf="!sidenavCollapsed">chevron_left</mat-icon>
<mat-icon *ngIf="sidenavCollapsed">chevron_right</mat-icon>
</button>
<nav mat-tab-nav-bar backgroundColor="primary" class="ml-1">
<a mat-tab-link [active]="true" class="tab-link" [matMenuTriggerFor]="institutionMenu" #institutionMenuTrigger="matMenuTrigger" (mouseenter)="onMouseEnter(institutionMenuTrigger)">
<mat-icon class="mr-05">business</mat-icon>
Institution
</a>
<span fxHide.lt-lg="true">
<a mat-tab-link class="tab-link">
<mat-icon class="mr-05">account_balance</mat-icon>
Accounting
</a>
<a mat-tab-link class="tab-link" [matMenuTriggerFor]="reportsMenu" #reportsMenuTrigger="matMenuTrigger" (mouseenter)="reportsMenuTrigger.openMenu()">
<mat-icon class="mr-05">insert_chart_outlined</mat-icon>
Reports
</a>
<a mat-tab-link class="tab-link" [matMenuTriggerFor]="adminMenu" #adminMenuTrigger="matMenuTrigger" (mouseenter)="adminMenuTrigger.openMenu()">
<mat-icon class="mr-05">security</mat-icon>
Admin
</a>
<a mat-tab-link class="tab-link" [matMenuTriggerFor]="selfServiceMenu" #selfServiceMenuTrigger="matMenuTrigger" (mouseenter)="selfServiceMenuTrigger.openMenu()">
<mat-icon class="mr-05">people</mat-icon>
Self Service
</a>
</span>
</nav>
<span fxFlex="auto"></span>
<div *ngIf="searchVisible" class="search-bar" fxFlex="auto" fxHide.lt-sm="true" [@fadeInOut]>
<mat-form-field class="search">
<input matInput type="text" placeholder="Search">
</mat-form-field>
</div>
<button mat-icon-button class="ml-1" (click)="toggleVisibility()" fxHide.lt-sm="true">
<mat-icon>search</mat-icon>
</button>
<mat-select placeholder="Language" [(value)]="selectedLanguage" class="ml-1 language" fxHide.lt-md>
<mat-option value="English">English</mat-option>
<mat-option value="Hindi">Hindi</mat-option>
</mat-select>
<button mat-icon-button class="ml-1" matTooltip="Color Schemes" fxHide.lt-md>
<mat-icon>format_color_fill</mat-icon>
</button>
<button mat-icon-button class="ml-1" matTooltip="Notifications">
<mat-icon>notifications</mat-icon>
</button>
<button mat-icon-button class="ml-1 img-button">
<img src="assets/images/user_placeholder.png">
</button>
</mat-toolbar>
<mat-menu #institutionMenu="matMenu" [overlapTrigger]="false">
<span (mouseleave)="institutionMenuTrigger.closeMenu()">
<button mat-menu-item>Clients</button>
<button mat-menu-item>Groups</button>
<button mat-menu-item>Centers</button>
<span fxHide.lg>
<button mat-menu-item>Accounting</button>
<button mat-menu-item [matMenuTriggerFor]="reportsMenu">Reports</button>
<button mat-menu-item [matMenuTriggerFor]="adminMenu">Admin</button>
<button mat-menu-item [matMenuTriggerFor]="selfServiceMenu">Self Service</button>
</span>
</span>
</mat-menu>
<mat-menu #reportsMenu="matMenu" [overlapTrigger]="false">
<span (mouseleave)="reportsMenuTrigger.closeMenu()">
<button mat-menu-item>All</button>
<button mat-menu-item>Clients</button>
<button mat-menu-item>Loans</button>
<button mat-menu-item>Savings</button>
<button mat-menu-item>Funds</button>
<button mat-menu-item>Accounting</button>
<button mat-menu-item>XBRL</button>
</span>
</mat-menu>
<mat-menu #adminMenu="matMenu" [overlapTrigger]="false">
<span (mouseleave)="adminMenuTrigger.closeMenu()">
<button mat-menu-item>Users</button>
<button mat-menu-item>Organisation</button>
<button mat-menu-item>System</button>
<button mat-menu-item>Products</button>
<button mat-menu-item>Templates</button>
</span>
</mat-menu>
<mat-menu #selfServiceMenu="matMenu" [overlapTrigger]="false">
<span (mouseleave)="selfServiceMenuTrigger.closeMenu()">
<button mat-menu-item>User Management</button>
<button mat-menu-item>App Configuration</button>
<button mat-menu-item>Task Management</button>
</span>
</mat-menu>
@@ -0,0 +1,45 @@
mat-form-field {
font-size: 1rem;
margin-top: 6px;
}
.toolbar {
position: relative;
box-shadow: 0 3px 1px -2px rgba(0,0,0,.2), 0 2px 2px 0 rgba(0,0,0,.14), 0 1px 5px 0 rgba(0,0,0,.12);
}
.tab-link {
min-width: auto;
padding: 0 16px;
}
.search-bar {
mat-form-field {
margin-right: 1rem;
max-width: 400px;
width: 100%;
margin-left: 20px;
}
}
.language {
min-width: 80px;
width: 80px;
font-size: .875rem;
}
.ml-1 {
margin-left: 1rem;
}
.mr-1 {
margin-right: 1rem;
}
.img-button {
img {
width: 32px;
height: 32px;
border-radius: 50%;
}
}
@@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ToolbarComponent } from './toolbar.component';
describe('ToolbarComponent', () => {
let component: ToolbarComponent;
let fixture: ComponentFixture<ToolbarComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ ToolbarComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ToolbarComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,67 @@
import { Component, OnInit, Input, EventEmitter, Output } from '@angular/core';
import { MatSidenav, MatMenuTrigger } from '@angular/material';
import { BreakpointObserver, Breakpoints } from '@angular/cdk/layout';
import {style, state, animate, transition, trigger} from '@angular/animations';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
@Component({
selector: 'mifosx-toolbar',
templateUrl: './toolbar.component.html',
styleUrls: ['./toolbar.component.scss'],
animations: [
trigger('fadeInOut', [
transition(':enter', [
style({ opacity: 0 }),
animate(500, style({ opacity: 1 }))
]),
transition(':leave', [
animate(500, style({ opacity: 0 }))
])
])
]
})
export class ToolbarComponent implements OnInit {
isHandset$: Observable<boolean> = this.breakpointObserver.observe(Breakpoints.Handset)
.pipe(
map(result => result.matches)
);
isHandset: boolean;
searchVisible = false;
selectedLanguage = 'English';
sidenavCollapsed = false;
@Input() sidenav: MatSidenav;
@Output() collapse = new EventEmitter<boolean>();
constructor(private breakpointObserver: BreakpointObserver) { }
ngOnInit() {
this.isHandset$.subscribe(isHandset => {
this.isHandset = isHandset;
});
}
toggleSidenav() {
this.sidenav.toggle();
}
toggleCollapse() {
this.sidenavCollapsed = !this.sidenavCollapsed;
this.collapse.emit(this.sidenavCollapsed);
}
toggleVisibility() {
this.searchVisible = !this.searchVisible;
}
onMouseEnter(menuTrigger: MatMenuTrigger) {
if (!this.isHandset) {
menuTrigger.openMenu();
}
}
}