refractor: shell (#287)
* Refractor components. * Shift theme picker component to shared module. * Shift progress bar service into its own folder. * Add footer to app shell. * Replace mat-icon by font-awesome icons. * Fix sidenav visibility bug for handsets. * Add comments. Closes #286, Fixes #285
This commit is contained in:
@@ -8,12 +8,11 @@ import { ServiceWorkerModule } from '@angular/service-worker';
|
|||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
import { NgxChartsModule } from '@swimlane/ngx-charts';
|
import { NgxChartsModule } from '@swimlane/ngx-charts';
|
||||||
|
|
||||||
import { environment } from '../environments/environment';
|
import { environment } from 'environments/environment';
|
||||||
|
|
||||||
import { WebAppComponent } from './web-app.component';
|
import { WebAppComponent } from './web-app.component';
|
||||||
|
|
||||||
import { CoreModule } from './core/core.module';
|
import { CoreModule } from './core/core.module';
|
||||||
import { SharedModule } from './shared/shared.module';
|
|
||||||
import { NavigationModule } from './navigation/navigation.module';
|
import { NavigationModule } from './navigation/navigation.module';
|
||||||
import { HomeModule } from './home/home.module';
|
import { HomeModule } from './home/home.module';
|
||||||
import { AboutModule } from './about/about.module';
|
import { AboutModule } from './about/about.module';
|
||||||
@@ -37,7 +36,6 @@ import { SettingsModule } from './settings/settings.module';
|
|||||||
TranslateModule.forRoot(),
|
TranslateModule.forRoot(),
|
||||||
NgxChartsModule,
|
NgxChartsModule,
|
||||||
CoreModule,
|
CoreModule,
|
||||||
SharedModule,
|
|
||||||
HomeModule,
|
HomeModule,
|
||||||
NavigationModule,
|
NavigationModule,
|
||||||
AboutModule,
|
AboutModule,
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { NgModule, Optional, SkipSelf } from '@angular/core';
|
import { NgModule, Optional, SkipSelf } from '@angular/core';
|
||||||
import { CommonModule } from '@angular/common';
|
|
||||||
import { HTTP_INTERCEPTORS, HttpClient, HttpClientModule } from '@angular/common/http';
|
import { HTTP_INTERCEPTORS, HttpClient, HttpClientModule } from '@angular/common/http';
|
||||||
import { RouteReuseStrategy, RouterModule } from '@angular/router';
|
import { RouteReuseStrategy, RouterModule } from '@angular/router';
|
||||||
|
|
||||||
@@ -46,70 +45,32 @@ import { TranslateModule } from '@ngx-translate/core';
|
|||||||
import { RouteReusableStrategy } from './route-reusable-strategy';
|
import { RouteReusableStrategy } from './route-reusable-strategy';
|
||||||
import { AuthenticationService } from './authentication/authentication.service';
|
import { AuthenticationService } from './authentication/authentication.service';
|
||||||
import { AuthenticationGuard } from './authentication/authentication.guard';
|
import { AuthenticationGuard } from './authentication/authentication.guard';
|
||||||
import { I18nService } from './i18n.service';
|
|
||||||
import { HttpService } from './http/http.service';
|
import { HttpService } from './http/http.service';
|
||||||
import { HttpCacheService } from './http/http-cache.service';
|
import { HttpCacheService } from './http/http-cache.service';
|
||||||
import { ProgressBarService } from './progress-bar.service';
|
import { ProgressBarService } from './progress-bar/progress-bar.service';
|
||||||
import { ProgressInterceptor } from './progress.interceptor';
|
import { ProgressInterceptor } from './progress-bar/progress.interceptor';
|
||||||
import { ApiPrefixInterceptor } from './http/api-prefix.interceptor';
|
import { ApiPrefixInterceptor } from './http/api-prefix.interceptor';
|
||||||
import { ErrorHandlerInterceptor } from './http/error-handler.interceptor';
|
import { ErrorHandlerInterceptor } from './http/error-handler.interceptor';
|
||||||
import { CacheInterceptor } from './http/cache.interceptor';
|
import { CacheInterceptor } from './http/cache.interceptor';
|
||||||
|
|
||||||
import { ShellComponent } from './shell/shell.component';
|
import { ShellComponent } from './shell/shell.component';
|
||||||
import { SidenavComponent } from './shell/sidenav/sidenav.component';
|
import { SidenavComponent } from './shell/sidenav/sidenav.component';
|
||||||
import { ThemePickerComponent } from './shell/toolbar/theme-picker/theme-picker.component';
|
|
||||||
import { ToolbarComponent } from './shell/toolbar/toolbar.component';
|
import { ToolbarComponent } from './shell/toolbar/toolbar.component';
|
||||||
import { ContentComponent } from './shell/content/content.component';
|
import { ContentComponent } from './shell/content/content.component';
|
||||||
import { BreadcrumbComponent } from './shell/breadcrumb/breadcrumb.component';
|
import { BreadcrumbComponent } from './shell/breadcrumb/breadcrumb.component';
|
||||||
import { AuthenticationInterceptor } from './authentication/authentication.interceptor';
|
import { AuthenticationInterceptor } from './authentication/authentication.interceptor';
|
||||||
|
import { SharedModule } from '../shared';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
SharedModule,
|
||||||
HttpClientModule,
|
HttpClientModule,
|
||||||
TranslateModule,
|
TranslateModule,
|
||||||
RouterModule,
|
RouterModule
|
||||||
LayoutModule,
|
|
||||||
FlexLayoutModule,
|
|
||||||
MatCheckboxModule,
|
|
||||||
MatCheckboxModule,
|
|
||||||
MatButtonModule,
|
|
||||||
MatInputModule,
|
|
||||||
MatAutocompleteModule,
|
|
||||||
MatDatepickerModule,
|
|
||||||
MatNativeDateModule,
|
|
||||||
MatFormFieldModule,
|
|
||||||
MatRadioModule,
|
|
||||||
MatSelectModule,
|
|
||||||
MatSliderModule,
|
|
||||||
MatSlideToggleModule,
|
|
||||||
MatMenuModule,
|
|
||||||
MatSidenavModule,
|
|
||||||
MatToolbarModule,
|
|
||||||
MatListModule,
|
|
||||||
MatGridListModule,
|
|
||||||
MatCardModule,
|
|
||||||
MatStepperModule,
|
|
||||||
MatTabsModule,
|
|
||||||
MatExpansionModule,
|
|
||||||
MatButtonToggleModule,
|
|
||||||
MatChipsModule,
|
|
||||||
MatIconModule,
|
|
||||||
MatProgressSpinnerModule,
|
|
||||||
MatProgressBarModule,
|
|
||||||
MatDialogModule,
|
|
||||||
MatTooltipModule,
|
|
||||||
MatSnackBarModule,
|
|
||||||
MatTableModule,
|
|
||||||
MatTreeModule,
|
|
||||||
MatSortModule,
|
|
||||||
MatPaginatorModule,
|
|
||||||
MatDividerModule
|
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
ShellComponent,
|
ShellComponent,
|
||||||
SidenavComponent,
|
SidenavComponent,
|
||||||
ThemePickerComponent,
|
|
||||||
ToolbarComponent,
|
ToolbarComponent,
|
||||||
ContentComponent,
|
ContentComponent,
|
||||||
BreadcrumbComponent
|
BreadcrumbComponent
|
||||||
@@ -150,13 +111,11 @@ import { AuthenticationInterceptor } from './authentication/authentication.inter
|
|||||||
MatTreeModule,
|
MatTreeModule,
|
||||||
MatSortModule,
|
MatSortModule,
|
||||||
MatPaginatorModule,
|
MatPaginatorModule,
|
||||||
MatDividerModule,
|
MatDividerModule
|
||||||
ThemePickerComponent
|
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
AuthenticationService,
|
AuthenticationService,
|
||||||
AuthenticationGuard,
|
AuthenticationGuard,
|
||||||
I18nService,
|
|
||||||
HttpCacheService,
|
HttpCacheService,
|
||||||
ProgressBarService,
|
ProgressBarService,
|
||||||
ProgressInterceptor,
|
ProgressInterceptor,
|
||||||
|
|||||||
@@ -20,7 +20,9 @@ export function extract(s: string) {
|
|||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Injectable()
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
export class I18nService {
|
export class I18nService {
|
||||||
|
|
||||||
defaultLanguage: string;
|
defaultLanguage: string;
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
export * from './core.module';
|
export * from './core.module';
|
||||||
export * from './authentication/authentication.service';
|
export * from './authentication/authentication.service';
|
||||||
// export * from './authentication/authentication.service.mock';
|
|
||||||
export * from './authentication/authentication.guard';
|
export * from './authentication/authentication.guard';
|
||||||
export * from './i18n.service';
|
export * from './i18n.service';
|
||||||
export * from './http/http.service';
|
export * from './http/http.service';
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
}
|
}
|
||||||
|
|
||||||
.separator:before {
|
.separator::before {
|
||||||
content: "";
|
content: "";
|
||||||
border: 1.5px solid #000;
|
border: 1.5px solid #000;
|
||||||
margin: 0 0.6rem;
|
margin: 0 0.6rem;
|
||||||
@@ -30,19 +30,17 @@
|
|||||||
a {
|
a {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
color: #0275d8;
|
color: #0275d8;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
a:hover {
|
|
||||||
text-decoration: underline;
|
&::after {
|
||||||
|
content: "/";
|
||||||
|
margin: 0 0.4rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.breadcrumb-link:after {
|
|
||||||
content: "/";
|
|
||||||
margin: 0 0.4rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,45 @@
|
|||||||
|
/** Angular Imports */
|
||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
import { ActivatedRoute, Router, NavigationEnd } from '@angular/router';
|
import { ActivatedRoute, Router, NavigationEnd } from '@angular/router';
|
||||||
|
|
||||||
|
/** rxjs Imports */
|
||||||
import { filter } from 'rxjs/operators';
|
import { filter } from 'rxjs/operators';
|
||||||
|
|
||||||
|
/** Custom Model */
|
||||||
import { Breadcrumb } from './breadcrumb.model';
|
import { Breadcrumb } from './breadcrumb.model';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Route data property to generate breadcrumb using a static string.
|
||||||
|
*
|
||||||
|
* Example- breadcrumb: 'Home'
|
||||||
|
*/
|
||||||
|
const routeDataBreadcrumb = 'breadcrumb';
|
||||||
|
/**
|
||||||
|
* Route data property to generate breadcrumb using given route parameter name.
|
||||||
|
*
|
||||||
|
* Example- routeParamBreadcrumb: 'id'
|
||||||
|
*/
|
||||||
|
const routeParamBreadcrumb = 'routeParamBreadcrumb';
|
||||||
|
/**
|
||||||
|
* Route data property to generate breadcrumb using resolved data property name.
|
||||||
|
*
|
||||||
|
* Use array to specify name for a nested object property.
|
||||||
|
*
|
||||||
|
* Example- routeResolveBreadcrumb: ['user', 'username']
|
||||||
|
*/
|
||||||
|
const routeResolveBreadcrumb = 'routeResolveBreadcrumb';
|
||||||
|
/**
|
||||||
|
* Route data property to specify whether generated breadcrumb should have a link.
|
||||||
|
*
|
||||||
|
* True by default. Specify false if a link is not required.
|
||||||
|
*
|
||||||
|
* Example- addBreadcrumbLink: false
|
||||||
|
*/
|
||||||
|
const routeAddBreadcrumbLink = 'addBreadcrumbLink';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate breadcrumbs dynamically via route configuration.
|
||||||
|
*/
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'mifosx-breadcrumb',
|
selector: 'mifosx-breadcrumb',
|
||||||
templateUrl: './breadcrumb.component.html',
|
templateUrl: './breadcrumb.component.html',
|
||||||
@@ -12,19 +47,26 @@ import { Breadcrumb } from './breadcrumb.model';
|
|||||||
})
|
})
|
||||||
export class BreadcrumbComponent implements OnInit {
|
export class BreadcrumbComponent implements OnInit {
|
||||||
|
|
||||||
|
/** Array of breadcrumbs. */
|
||||||
breadcrumbs: Breadcrumb[];
|
breadcrumbs: Breadcrumb[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates the breadcrumbs.
|
||||||
|
* @param {ActivatedRoute} activatedRoute Activate Route.
|
||||||
|
* @param {Router} router Router for navigation.
|
||||||
|
*/
|
||||||
constructor(private activatedRoute: ActivatedRoute,
|
constructor(private activatedRoute: ActivatedRoute,
|
||||||
private router: Router) {
|
private router: Router) {
|
||||||
this.generateBreadcrumbs();
|
this.generateBreadcrumbs();
|
||||||
}
|
}
|
||||||
|
|
||||||
generateBreadcrumbs() {
|
ngOnInit() {
|
||||||
const routeDataBreadcrumb = 'breadcrumb';
|
}
|
||||||
const routeParamBreadcrumb = 'routeParamBreadcrumb';
|
|
||||||
const routeResolveBreadcrumb = 'routeResolveBreadcrumb';
|
|
||||||
const routeAddBreadcrumbLink = 'addBreadcrumbLink';
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates the array of breadcrumbs for the visited route.
|
||||||
|
*/
|
||||||
|
generateBreadcrumbs() {
|
||||||
const onNavigationEnd = this.router.events.pipe(filter(event => event instanceof NavigationEnd));
|
const onNavigationEnd = this.router.events.pipe(filter(event => event instanceof NavigationEnd));
|
||||||
|
|
||||||
onNavigationEnd.subscribe(() => {
|
onNavigationEnd.subscribe(() => {
|
||||||
@@ -86,7 +128,4 @@ export class BreadcrumbComponent implements OnInit {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
/**
|
||||||
|
* Breadcrumb model.
|
||||||
|
*/
|
||||||
export interface Breadcrumb {
|
export interface Breadcrumb {
|
||||||
label: string;
|
label: string;
|
||||||
url: string;
|
url: string;
|
||||||
|
|||||||
@@ -1 +1,2 @@
|
|||||||
|
<!-- Display the content for the visited route. -->
|
||||||
<router-outlet></router-outlet>
|
<router-outlet></router-outlet>
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
|
/** Angular Imports */
|
||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Content component.
|
||||||
|
*/
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'mifosx-content',
|
selector: 'mifosx-content',
|
||||||
templateUrl: './content.component.html',
|
templateUrl: './content.component.html',
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
|
<!-- Application Shell -->
|
||||||
<mat-sidenav-container id="mifosx-shell-container" autosize>
|
<mat-sidenav-container id="mifosx-shell-container" autosize>
|
||||||
|
|
||||||
|
<!-- Sidenav -->
|
||||||
<mat-sidenav
|
<mat-sidenav
|
||||||
#sidenav
|
#sidenav
|
||||||
class="sidebar-panel"
|
class="sidebar-panel"
|
||||||
@@ -15,13 +17,20 @@
|
|||||||
|
|
||||||
</mat-sidenav>
|
</mat-sidenav>
|
||||||
|
|
||||||
|
<!-- Content -->
|
||||||
<mat-sidenav-content>
|
<mat-sidenav-content>
|
||||||
|
|
||||||
|
<!-- Toolbar -->
|
||||||
<mifosx-toolbar [sidenav]="sidenav" (collapse)="toggleCollapse($event)"></mifosx-toolbar>
|
<mifosx-toolbar [sidenav]="sidenav" (collapse)="toggleCollapse($event)"></mifosx-toolbar>
|
||||||
|
<!-- Progress Bar -->
|
||||||
<mat-progress-bar [mode]="progressBarMode"></mat-progress-bar>
|
<mat-progress-bar [mode]="progressBarMode"></mat-progress-bar>
|
||||||
|
|
||||||
|
<!-- Breadcrumb -->
|
||||||
<mifosx-breadcrumb></mifosx-breadcrumb>
|
<mifosx-breadcrumb></mifosx-breadcrumb>
|
||||||
|
<!-- Content -->
|
||||||
<mifosx-content></mifosx-content>
|
<mifosx-content></mifosx-content>
|
||||||
|
<!-- Footer -->
|
||||||
|
<mifosx-footer></mifosx-footer>
|
||||||
|
|
||||||
</mat-sidenav-content>
|
</mat-sidenav-content>
|
||||||
|
|
||||||
|
|||||||
@@ -3,10 +3,10 @@
|
|||||||
height: 100vh;
|
height: 100vh;
|
||||||
|
|
||||||
.sidebar-panel {
|
.sidebar-panel {
|
||||||
background-color: white; // TODO: Theme Configuration
|
background-color: #fff;
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
overflow: hidden;
|
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);
|
box-shadow: 0 3px 1px -2px rgba(0, 0, 0, 0.2), 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12);
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-full {
|
.sidebar-full {
|
||||||
@@ -16,5 +16,4 @@
|
|||||||
.sidebar-compact {
|
.sidebar-compact {
|
||||||
width: 4rem;
|
width: 4rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,5 @@
|
|||||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
import { RouterTestingModule } from '@angular/router/testing';
|
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
|
||||||
|
|
||||||
import { AuthenticationService } from '../authentication/authentication.service';
|
|
||||||
import { MockAuthenticationService } from '../authentication/authentication.service.mock';
|
|
||||||
import { CoreModule } from '../core.module';
|
|
||||||
import { ShellComponent } from './shell.component';
|
import { ShellComponent } from './shell.component';
|
||||||
|
|
||||||
describe('ShellComponent', () => {
|
describe('ShellComponent', () => {
|
||||||
@@ -13,14 +8,7 @@ describe('ShellComponent', () => {
|
|||||||
|
|
||||||
beforeEach(async(() => {
|
beforeEach(async(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [
|
declarations: [ ShellComponent ]
|
||||||
RouterTestingModule,
|
|
||||||
TranslateModule.forRoot(),
|
|
||||||
CoreModule
|
|
||||||
],
|
|
||||||
providers: [
|
|
||||||
{ provide: AuthenticationService, useClass: MockAuthenticationService }
|
|
||||||
]
|
|
||||||
})
|
})
|
||||||
.compileComponents();
|
.compileComponents();
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -1,10 +1,17 @@
|
|||||||
|
/** Angular Imports */
|
||||||
import { Component, OnInit, ChangeDetectorRef, OnDestroy } from '@angular/core';
|
import { Component, OnInit, ChangeDetectorRef, OnDestroy } from '@angular/core';
|
||||||
import { BreakpointObserver, Breakpoints } from '@angular/cdk/layout';
|
import { BreakpointObserver, Breakpoints } from '@angular/cdk/layout';
|
||||||
|
|
||||||
import { Observable } from 'rxjs';
|
/** rxjs Imports */
|
||||||
|
import { Observable, Subscription } from 'rxjs';
|
||||||
import { map } from 'rxjs/operators';
|
import { map } from 'rxjs/operators';
|
||||||
import { ProgressBarService } from '../progress-bar.service';
|
|
||||||
|
|
||||||
|
/** Custom Services */
|
||||||
|
import { ProgressBarService } from '../progress-bar/progress-bar.service';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shell component.
|
||||||
|
*/
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'mifosx-shell',
|
selector: 'mifosx-shell',
|
||||||
templateUrl: './shell.component.html',
|
templateUrl: './shell.component.html',
|
||||||
@@ -12,30 +19,48 @@ import { ProgressBarService } from '../progress-bar.service';
|
|||||||
})
|
})
|
||||||
export class ShellComponent implements OnInit, OnDestroy {
|
export class ShellComponent implements OnInit, OnDestroy {
|
||||||
|
|
||||||
|
/** Subscription to breakpoint observer for handset. */
|
||||||
isHandset$: Observable<boolean> = this.breakpointObserver.observe(Breakpoints.Handset)
|
isHandset$: Observable<boolean> = this.breakpointObserver.observe(Breakpoints.Handset)
|
||||||
.pipe(
|
.pipe(
|
||||||
map(result => result.matches)
|
map(result => result.matches)
|
||||||
);
|
);
|
||||||
sidenavCollapsed = false;
|
/** Sets the initial state of sidenav as collapsed. Not collapsed if false. */
|
||||||
|
sidenavCollapsed = true;
|
||||||
|
/** Progress bar mode. */
|
||||||
progressBarMode: string;
|
progressBarMode: string;
|
||||||
progressBar$: any;
|
/** Subscription to progress bar. */
|
||||||
|
progressBar$: Subscription;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {BreakpointObserver} breakpointObserver Breakpoint Observer to detect screen size.
|
||||||
|
* @param {ProgressBarService} progressBarService Progress Bar Service.
|
||||||
|
* @param {ChangeDetectorRef} cdr Change Detector Ref.
|
||||||
|
*/
|
||||||
constructor(private breakpointObserver: BreakpointObserver,
|
constructor(private breakpointObserver: BreakpointObserver,
|
||||||
private progressBarService: ProgressBarService,
|
private progressBarService: ProgressBarService,
|
||||||
private cdr: ChangeDetectorRef) { }
|
private cdr: ChangeDetectorRef) { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subscribes to progress bar to update its mode.
|
||||||
|
*/
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.sidenavCollapsed = true;
|
|
||||||
this.progressBar$ = this.progressBarService.updateProgressBar.subscribe((mode: string) => {
|
this.progressBar$ = this.progressBarService.updateProgressBar.subscribe((mode: string) => {
|
||||||
this.progressBarMode = mode;
|
this.progressBarMode = mode;
|
||||||
this.cdr.detectChanges();
|
this.cdr.detectChanges();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggles the current collapsed state of sidenav according to the emitted event.
|
||||||
|
* @param {boolean} event denotes state of sidenav
|
||||||
|
*/
|
||||||
toggleCollapse($event: boolean) {
|
toggleCollapse($event: boolean) {
|
||||||
this.sidenavCollapsed = $event;
|
this.sidenavCollapsed = $event;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unsubscribes from progress bar.
|
||||||
|
*/
|
||||||
ngOnDestroy() {
|
ngOnDestroy() {
|
||||||
this.progressBar$.unsubscribe();
|
this.progressBar$.unsubscribe();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
<div class="navigation-panel" fxLayout="column">
|
<div class="navigation-panel" fxLayout="column">
|
||||||
|
|
||||||
|
<!-- TODO: Allow customization by organization -->
|
||||||
<div class="app-brand">
|
<div class="app-brand">
|
||||||
<img src="assets/images/MifosX_logo.png" alt="app-logo" class="app-logo">
|
<img src="assets/images/MifosX_logo.png" alt="app-logo" class="app-logo">
|
||||||
<span class="app-logo-text text-muted">Mifos</span>
|
<span class="app-logo-text text-muted">Mifos</span>
|
||||||
@@ -13,41 +14,43 @@
|
|||||||
|
|
||||||
<div class="app-user">
|
<div class="app-user">
|
||||||
<div class="app-user-photo">
|
<div class="app-user-photo">
|
||||||
|
<!-- TODO: Decision to be taken on providing this feature -->
|
||||||
<img src="assets/images/user_placeholder.png" alt="app-user-photo">
|
<img src="assets/images/user_placeholder.png" alt="app-user-photo">
|
||||||
</div>
|
</div>
|
||||||
<span class="app-user-name mb-05">
|
<span class="app-user-name mb-05">
|
||||||
Abhay Chawla
|
{{ username }}
|
||||||
</span>
|
</span>
|
||||||
<div class="app-user-controls">
|
<div class="app-user-controls">
|
||||||
<button class="text-muted xs-button" mat-icon-button matTooltip="Settings" routerLink="/settings">
|
<button class="text-muted xs-button" mat-icon-button matTooltip="Settings" [routerLink]="['/settings']">
|
||||||
<mat-icon>settings</mat-icon>
|
<fa-icon icon="cog"></fa-icon>
|
||||||
</button>
|
</button>
|
||||||
<button class="text-muted xs-button" mat-icon-button matTooltip="Sign Out" (click)="logout()">
|
<button class="text-muted xs-button" mat-icon-button matTooltip="Sign Out" (click)="logout()">
|
||||||
<mat-icon>exit_to_app</mat-icon>
|
<fa-icon icon="sign-out-alt"></fa-icon>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="app-sidenav">
|
<div class="app-sidenav">
|
||||||
|
|
||||||
|
<!-- TODO: Generate frequently accessed routes dynamically -->
|
||||||
<div class="icon-menu">
|
<div class="icon-menu">
|
||||||
<div class="mt-1 mb-1 nav-item-sep">
|
<div class="mt-1 mb-1 nav-item-sep">
|
||||||
<mat-divider></mat-divider>
|
<mat-divider></mat-divider>
|
||||||
<span class="text-muted icon-menu-title">Frequently Accessed</span>
|
<span class="text-muted icon-menu-title">Frequently Accessed</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="icon-menu-item">
|
<div class="icon-menu-item">
|
||||||
<button mat-raised-button matTooltip="Home">
|
<button mat-raised-button matTooltip="Home" [routerLink]="['/home']">
|
||||||
<mat-icon>home</mat-icon>
|
<fa-icon icon="home" size="lg"></fa-icon>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="icon-menu-item">
|
<div class="icon-menu-item">
|
||||||
<button mat-raised-button matTooltip="Dashboard">
|
<button mat-raised-button matTooltip="Dashboard" [routerLink]="['/dashboard']">
|
||||||
<mat-icon>dashboard</mat-icon>
|
<fa-icon icon="tachometer-alt" size="lg"></fa-icon>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="icon-menu-item">
|
<div class="icon-menu-item">
|
||||||
<button mat-raised-button matTooltip="Navigation">
|
<button mat-raised-button matTooltip="Navigation" [routerLink]="['/navigation']">
|
||||||
<mat-icon>navigation</mat-icon>
|
<fa-icon icon="location-arrow" size="lg"></fa-icon>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -58,14 +61,36 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<mat-nav-list>
|
<mat-nav-list>
|
||||||
<mat-list-item routerLink="/dashboard">
|
<mat-list-item [routerLink]="['/dashboard']">
|
||||||
<mat-icon matListIcon>dashboard</mat-icon>
|
<mat-icon matListIcon>
|
||||||
|
<fa-icon icon="tachometer-alt" size="sm"></fa-icon>
|
||||||
|
</mat-icon>
|
||||||
<a matLine>Dashboard</a>
|
<a matLine>Dashboard</a>
|
||||||
</mat-list-item>
|
</mat-list-item>
|
||||||
<mat-list-item>
|
<mat-list-item [routerLink]="['/navigation']">
|
||||||
<mat-icon matListIcon>navigation</mat-icon>
|
<mat-icon matListIcon>
|
||||||
|
<fa-icon icon="location-arrow" size="sm"></fa-icon>
|
||||||
|
</mat-icon>
|
||||||
<a matLine>Navigation</a>
|
<a matLine>Navigation</a>
|
||||||
</mat-list-item>
|
</mat-list-item>
|
||||||
|
<mat-list-item [routerLink]="['/accounting/frequent-postings']">
|
||||||
|
<mat-icon matListIcon>
|
||||||
|
<fa-icon icon="sync" size="sm"></fa-icon>
|
||||||
|
</mat-icon>
|
||||||
|
<a matLine>Frequent Postings</a>
|
||||||
|
</mat-list-item>
|
||||||
|
<mat-list-item [routerLink]="['/accounting/journal-entries/create']">
|
||||||
|
<mat-icon matListIcon>
|
||||||
|
<fa-icon icon="plus" size="sm"></fa-icon>
|
||||||
|
</mat-icon>
|
||||||
|
<a matLine>Create Journal Entry</a>
|
||||||
|
</mat-list-item>
|
||||||
|
<mat-list-item [routerLink]="['/accounting/chart-of-accounts']">
|
||||||
|
<mat-icon matListIcon>
|
||||||
|
<fa-icon icon="sitemap" size="sm"></fa-icon>
|
||||||
|
</mat-icon>
|
||||||
|
<a matLine>Chart of Accounts</a>
|
||||||
|
</mat-list-item>
|
||||||
</mat-nav-list>
|
</mat-nav-list>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,117 +1,140 @@
|
|||||||
img {
|
.sidebar-full,
|
||||||
max-width: 100%;
|
.sidebar-compact {
|
||||||
}
|
.navigation-panel {
|
||||||
|
position: absolute;
|
||||||
.navigation-panel {
|
left: 0;
|
||||||
position: absolute;
|
width: 100%;
|
||||||
left: 0;
|
height: 100%;
|
||||||
width: 100%;
|
padding-top: 64px;
|
||||||
height: 100%;
|
background: hsla(0, 0%, 100%, 0.95);
|
||||||
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 {
|
img {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-brand {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
text-align: center;
|
||||||
|
position: fixed;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
max-width: 15rem;
|
||||||
border-radius: 50%;
|
height: 64px;
|
||||||
}
|
line-height: 64px;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 9999;
|
||||||
|
color: #444;
|
||||||
|
|
||||||
.app-user-name {
|
.app-logo {
|
||||||
display: block;
|
width: auto;
|
||||||
font-size: .875rem;
|
height: 42px;
|
||||||
font-weight: 300;
|
margin-right: 8px;
|
||||||
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 {
|
.app-logo-text {
|
||||||
padding-left: 0;
|
width: auto;
|
||||||
margin-left: -8px;
|
height: auto;
|
||||||
|
font-size: 2rem;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-user {
|
||||||
|
text-align: center;
|
||||||
|
width: 100%;
|
||||||
|
padding: 1rem 0 0.4rem;
|
||||||
|
|
||||||
|
.app-user-photo {
|
||||||
|
width: 72px;
|
||||||
|
margin: 0 auto 8px;
|
||||||
|
height: 72px;
|
||||||
|
border: 1px solid hsla(210, 8%, 51%, 0.13);
|
||||||
|
padding: 4px;
|
||||||
|
border-radius: 50%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-menu-item {
|
img {
|
||||||
display: inline-block;
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
button {
|
.app-user-name {
|
||||||
min-width: auto;
|
display: block;
|
||||||
margin: 4px;
|
font-size: 0.875rem;
|
||||||
|
font-weight: 300;
|
||||||
|
color: rgba(0, 0, 0, 0.96);
|
||||||
|
margin-left: -2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-user-controls {
|
||||||
|
.mat-icon-button {
|
||||||
|
.xs-button {
|
||||||
|
height: 20px;
|
||||||
|
width: 20px;
|
||||||
|
line-height: 20px;
|
||||||
|
margin: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-muted {
|
||||||
|
color: rgba(0, 0, 0, 0.54);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-item-sep {
|
.nav-item-sep {
|
||||||
padding: 0;
|
span {
|
||||||
margin: 1rem 0;
|
padding: 16px 0 0 16px;
|
||||||
|
display: block;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mat-nav-list {
|
.app-sidenav {
|
||||||
margin: 0;
|
.icon-menu {
|
||||||
padding: 0;
|
padding: 0 24px;
|
||||||
list-style: none;
|
opacity: 1;
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-item-sep {
|
.mat-divider {
|
||||||
span {
|
margin: 0 -24px;
|
||||||
padding: 16px 0 0 16px;
|
}
|
||||||
display: block;
|
|
||||||
font-size: 12px;
|
.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;
|
||||||
|
|
||||||
|
.mat-list-item {
|
||||||
|
.mat-list-icon {
|
||||||
|
height: 30px;
|
||||||
|
font-size: 22px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,6 +154,12 @@ img {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.app-sidenav {
|
.app-sidenav {
|
||||||
|
.nav-item-sep {
|
||||||
|
span {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.icon-menu {
|
.icon-menu {
|
||||||
padding: 8px 0 0;
|
padding: 8px 0 0;
|
||||||
width: 4rem;
|
width: 4rem;
|
||||||
@@ -150,11 +179,5 @@ img {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-item-sep {
|
|
||||||
span {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,13 @@
|
|||||||
|
/** Angular Imports */
|
||||||
import { Component, OnInit, Input } from '@angular/core';
|
import { Component, OnInit, Input } from '@angular/core';
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
|
|
||||||
|
/** Custom Services */
|
||||||
import { AuthenticationService } from '../../authentication/authentication.service';
|
import { AuthenticationService } from '../../authentication/authentication.service';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sidenav component.
|
||||||
|
*/
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'mifosx-sidenav',
|
selector: 'mifosx-sidenav',
|
||||||
templateUrl: './sidenav.component.html',
|
templateUrl: './sidenav.component.html',
|
||||||
@@ -10,14 +15,29 @@ import { AuthenticationService } from '../../authentication/authentication.servi
|
|||||||
})
|
})
|
||||||
export class SidenavComponent implements OnInit {
|
export class SidenavComponent implements OnInit {
|
||||||
|
|
||||||
|
/** True if sidenav is in collapsed state. */
|
||||||
@Input() sidenavCollapsed: boolean;
|
@Input() sidenavCollapsed: boolean;
|
||||||
|
/** Username of authenticated user. */
|
||||||
|
username: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Router} router Router for navigation.
|
||||||
|
* @param {AuthenticationService} authenticationService Authentication Service.
|
||||||
|
*/
|
||||||
constructor(private router: Router,
|
constructor(private router: Router,
|
||||||
private authenticationService: AuthenticationService) { }
|
private authenticationService: AuthenticationService) { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the username of the authenticated user.
|
||||||
|
*/
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
|
const credentials = this.authenticationService.getCredentials();
|
||||||
|
this.username = credentials.username;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logs out the authenticated user and redirects to login page.
|
||||||
|
*/
|
||||||
logout() {
|
logout() {
|
||||||
this.authenticationService.logout()
|
this.authenticationService.logout()
|
||||||
.subscribe(() => this.router.navigate(['/login'], { replaceUrl: true }));
|
.subscribe(() => this.router.navigate(['/login'], { replaceUrl: true }));
|
||||||
|
|||||||
@@ -1,37 +1,37 @@
|
|||||||
<mat-toolbar fxLayout="row" color="primary" class="toolbar">
|
<mat-toolbar fxLayout="row" color="primary" id="mifosx-toolbar">
|
||||||
|
|
||||||
<button mat-icon-button *ngIf="!sidenavCollapsed" (click)="toggleSidenav()" matTooltip="Toggle Hide/Open">
|
<button mat-icon-button *ngIf="!sidenavCollapsed" (click)="toggleSidenav()" matTooltip="Toggle Hide/Open">
|
||||||
<mat-icon>menu</mat-icon>
|
<fa-icon icon="bars" size="lg"></fa-icon>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button mat-icon-button *ngIf="sidenav.opened" (click)="toggleCollapse()" matTooltip="Toggle Collapse" fxHide.lt-md="true">
|
<button mat-icon-button *ngIf="sidenav.opened" (click)="toggleSidenavCollapse()" matTooltip="Toggle Collapse" fxHide.lt-md="true">
|
||||||
<mat-icon *ngIf="!sidenavCollapsed">chevron_left</mat-icon>
|
<fa-icon *ngIf="!sidenavCollapsed" icon="chevron-left"></fa-icon>
|
||||||
<mat-icon *ngIf="sidenavCollapsed">chevron_right</mat-icon>
|
<fa-icon *ngIf="sidenavCollapsed" icon="chevron-right"></fa-icon>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<nav mat-tab-nav-bar backgroundColor="primary" class="ml-1">
|
<nav mat-tab-nav-bar backgroundColor="primary" class="ml-1">
|
||||||
|
|
||||||
<a mat-tab-link class="tab-link" [matMenuTriggerFor]="institutionMenu" #institutionMenuTrigger="matMenuTrigger">
|
<a mat-tab-link class="tab-link" [matMenuTriggerFor]="institutionMenu" #institutionMenuTrigger="matMenuTrigger">
|
||||||
<mat-icon class="mr-05">business</mat-icon>
|
<fa-icon class="mr-05" icon="university" size="lg"></fa-icon>
|
||||||
Institution
|
Institution
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<span fxHide.lt-lg="true">
|
<span fxHide.lt-lg="true">
|
||||||
|
|
||||||
<a mat-tab-link class="tab-link" [routerLink]="['/accounting']">
|
<a mat-tab-link class="tab-link" [routerLink]="['/accounting']">
|
||||||
<mat-icon class="mr-05">account_balance</mat-icon>
|
<fa-icon class="mr-05" icon="money-bill-alt" size="lg"></fa-icon>
|
||||||
Accounting
|
Accounting
|
||||||
</a>
|
</a>
|
||||||
<a mat-tab-link class="tab-link" [matMenuTriggerFor]="reportsMenu" #reportsMenuTrigger="matMenuTrigger" >
|
<a mat-tab-link class="tab-link" [matMenuTriggerFor]="reportsMenu" #reportsMenuTrigger="matMenuTrigger" >
|
||||||
<mat-icon class="mr-05">insert_chart_outlined</mat-icon>
|
<fa-icon class="mr-05" icon="chart-bar" size="lg"></fa-icon>
|
||||||
Reports
|
Reports
|
||||||
</a>
|
</a>
|
||||||
<a mat-tab-link class="tab-link" [matMenuTriggerFor]="adminMenu" #adminMenuTrigger="matMenuTrigger">
|
<a mat-tab-link class="tab-link" [matMenuTriggerFor]="adminMenu" #adminMenuTrigger="matMenuTrigger">
|
||||||
<mat-icon class="mr-05">security</mat-icon>
|
<fa-icon class="mr-05" icon="shield-alt" size="lg"></fa-icon>
|
||||||
Admin
|
Admin
|
||||||
</a>
|
</a>
|
||||||
<a mat-tab-link class="tab-link" [matMenuTriggerFor]="selfServiceMenu" #selfServiceMenuTrigger="matMenuTrigger">
|
<a mat-tab-link class="tab-link" [matMenuTriggerFor]="selfServiceMenu" #selfServiceMenuTrigger="matMenuTrigger">
|
||||||
<mat-icon class="mr-05">people</mat-icon>
|
<fa-icon class="mr-05" icon="users" size="lg"></fa-icon>
|
||||||
Self Service
|
Self Service
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
@@ -41,73 +41,90 @@
|
|||||||
|
|
||||||
<span fxFlex="auto"></span>
|
<span fxFlex="auto"></span>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
TODO: Search
|
||||||
|
Separate into its own component in shared module for reuse.
|
||||||
|
Implement functionality.
|
||||||
|
-->
|
||||||
<div *ngIf="searchVisible" class="search-bar" fxFlex="auto" fxHide.lt-sm="true" [@fadeInOut]>
|
<div *ngIf="searchVisible" class="search-bar" fxFlex="auto" fxHide.lt-sm="true" [@fadeInOut]>
|
||||||
<mat-form-field class="search">
|
<mat-form-field class="search">
|
||||||
<input matInput type="text" placeholder="Search">
|
<mat-label>Search</mat-label>
|
||||||
|
<input matInput type="text">
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button mat-icon-button class="ml-1" (click)="toggleVisibility()" fxHide.lt-sm="true">
|
<button mat-icon-button class="ml-1" (click)="toggleSearchVisibility()" fxHide.lt-sm="true">
|
||||||
<mat-icon>search</mat-icon>
|
<fa-icon icon="search" size="lg"></fa-icon>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<mat-select placeholder="Language" [(value)]="selectedLanguage" class="ml-1 language" fxHide.lt-md>
|
<mifosx-language-selector class="ml-1 language" fxHide.lt-md></mifosx-language-selector>
|
||||||
<mat-option value="English">English</mat-option>
|
|
||||||
<mat-option value="Hindi">Hindi</mat-option>
|
|
||||||
</mat-select>
|
|
||||||
|
|
||||||
<mifosx-theme-picker class="ml-1" fxHide.lt-md></mifosx-theme-picker>
|
<mifosx-theme-picker fxHide.lt-md></mifosx-theme-picker>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
TODO: Notifications
|
||||||
|
Separate into its own component in shared module for reuse.
|
||||||
|
Implement functionality.
|
||||||
|
-->
|
||||||
<button mat-icon-button class="ml-1" matTooltip="Notifications">
|
<button mat-icon-button class="ml-1" matTooltip="Notifications">
|
||||||
<mat-icon>notifications</mat-icon>
|
<fa-icon icon="bell" size="lg"></fa-icon>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button mat-icon-button class="ml-1 img-button">
|
<button mat-icon-button class="ml-1 img-button" [matMenuTriggerFor]="applicationMenu">
|
||||||
<img src="assets/images/user_placeholder.png">
|
<img src="assets/images/user_placeholder.png">
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
</mat-toolbar>
|
</mat-toolbar>
|
||||||
|
|
||||||
<mat-menu #institutionMenu="matMenu" [overlapTrigger]="false">
|
<mat-menu #institutionMenu="matMenu" [overlapTrigger]="false">
|
||||||
<span>
|
<button mat-menu-item routerLink="/clients">Clients</button>
|
||||||
<button mat-menu-item routerLink="/clients">Clients</button>
|
<button mat-menu-item routerLink="/groups">Groups</button>
|
||||||
<button mat-menu-item routerLink="/groups">Groups</button>
|
<button mat-menu-item routerLink="/centers">Centers</button>
|
||||||
<button mat-menu-item routerLink="/centers">Centers</button>
|
<span fxHide.lg>
|
||||||
<span fxHide.lg>
|
<button mat-menu-item [routerLink]="['/accounting']">Accounting</button>
|
||||||
<button mat-menu-item>Accounting</button>
|
<button mat-menu-item [matMenuTriggerFor]="reportsMenu">Reports</button>
|
||||||
<button mat-menu-item [matMenuTriggerFor]="reportsMenu">Reports</button>
|
<button mat-menu-item [matMenuTriggerFor]="adminMenu">Admin</button>
|
||||||
<button mat-menu-item [matMenuTriggerFor]="adminMenu">Admin</button>
|
<button mat-menu-item [matMenuTriggerFor]="selfServiceMenu">Self Service</button>
|
||||||
<button mat-menu-item [matMenuTriggerFor]="selfServiceMenu">Self Service</button>
|
|
||||||
</span>
|
|
||||||
</span>
|
</span>
|
||||||
</mat-menu>
|
</mat-menu>
|
||||||
|
|
||||||
|
<!-- Toolbar Menus -->
|
||||||
<mat-menu #reportsMenu="matMenu" [overlapTrigger]="false">
|
<mat-menu #reportsMenu="matMenu" [overlapTrigger]="false">
|
||||||
<span (mouseleave)="reportsMenuTrigger.closeMenu()">
|
<button mat-menu-item>All</button>
|
||||||
<button mat-menu-item>All</button>
|
<button mat-menu-item>Clients</button>
|
||||||
<button mat-menu-item>Clients</button>
|
<button mat-menu-item>Loans</button>
|
||||||
<button mat-menu-item>Loans</button>
|
<button mat-menu-item>Savings</button>
|
||||||
<button mat-menu-item>Savings</button>
|
<button mat-menu-item>Funds</button>
|
||||||
<button mat-menu-item>Funds</button>
|
<button mat-menu-item>Accounting</button>
|
||||||
<button mat-menu-item>Accounting</button>
|
<button mat-menu-item>XBRL</button>
|
||||||
<button mat-menu-item>XBRL</button>
|
|
||||||
</span>
|
|
||||||
</mat-menu>
|
</mat-menu>
|
||||||
|
|
||||||
<mat-menu #adminMenu="matMenu" [overlapTrigger]="false">
|
<mat-menu #adminMenu="matMenu" [overlapTrigger]="false">
|
||||||
<span (mouseleave)="adminMenuTrigger.closeMenu()">
|
<button mat-menu-item>Users</button>
|
||||||
<button mat-menu-item>Users</button>
|
<button mat-menu-item>Organisation</button>
|
||||||
<button mat-menu-item>Organisation</button>
|
<button mat-menu-item>System</button>
|
||||||
<button mat-menu-item>System</button>
|
<button mat-menu-item>Products</button>
|
||||||
<button mat-menu-item>Products</button>
|
<button mat-menu-item>Templates</button>
|
||||||
<button mat-menu-item>Templates</button>
|
|
||||||
</span>
|
|
||||||
</mat-menu>
|
</mat-menu>
|
||||||
|
|
||||||
<mat-menu #selfServiceMenu="matMenu" [overlapTrigger]="false">
|
<mat-menu #selfServiceMenu="matMenu" [overlapTrigger]="false">
|
||||||
<span (mouseleave)="selfServiceMenuTrigger.closeMenu()">
|
<button mat-menu-item [routerLink]="['/self-service/users']">User Management</button>
|
||||||
<button mat-menu-item routerLink="/self-service/users">User Management</button>
|
<button mat-menu-item [routerLink]="['/self-service/app-configuration']">App Configuration</button>
|
||||||
<button mat-menu-item routerLink="/self-service/app-configuration">App Configuration</button>
|
<button mat-menu-item>Task Management</button>
|
||||||
<button mat-menu-item>Task Management</button>
|
</mat-menu>
|
||||||
</span>
|
|
||||||
|
<!-- Application User Help Menu -->
|
||||||
|
<mat-menu #applicationMenu="matMenu" [overlapTrigger]="false">
|
||||||
|
<button mat-menu-item [routerLink]="['/settings']">
|
||||||
|
<mat-icon>
|
||||||
|
<fa-icon icon="cog" size="sm"></fa-icon>
|
||||||
|
</mat-icon>
|
||||||
|
<span>Settings</span>
|
||||||
|
</button>
|
||||||
|
<button mat-menu-item (click)="logout()">
|
||||||
|
<mat-icon>
|
||||||
|
<fa-icon icon="sign-out-alt" size="sm"></fa-icon>
|
||||||
|
</mat-icon>
|
||||||
|
<span>Sign Out</span>
|
||||||
|
</button>
|
||||||
</mat-menu>
|
</mat-menu>
|
||||||
|
|||||||
@@ -1,45 +1,40 @@
|
|||||||
mat-form-field {
|
#mifosx-toolbar {
|
||||||
font-size: 1rem;
|
|
||||||
margin-top: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toolbar {
|
|
||||||
position: relative;
|
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);
|
box-shadow: 0 3px 1px -2px rgba(0, 0, 0, 0.2), 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12);
|
||||||
}
|
|
||||||
|
|
||||||
.tab-link {
|
.tab-link {
|
||||||
min-width: auto;
|
min-width: auto;
|
||||||
padding: 0 16px;
|
padding: 0 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-bar {
|
.search-bar {
|
||||||
mat-form-field {
|
.search {
|
||||||
|
max-width: 400px;
|
||||||
|
width: 100%;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.language {
|
||||||
|
min-width: 80px;
|
||||||
|
width: 80px;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
margin-top: 0.375rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.img-button {
|
||||||
|
img {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ml-1 {
|
||||||
|
margin-left: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mr-1 {
|
||||||
margin-right: 1rem;
|
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%;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,20 @@
|
|||||||
|
/** Angular Imports */
|
||||||
import { Component, OnInit, Input, EventEmitter, Output } from '@angular/core';
|
import { Component, OnInit, Input, EventEmitter, Output } from '@angular/core';
|
||||||
import { MatSidenav, MatMenuTrigger } from '@angular/material';
|
import { MatSidenav } from '@angular/material';
|
||||||
import { BreakpointObserver, Breakpoints } from '@angular/cdk/layout';
|
import { BreakpointObserver, Breakpoints } from '@angular/cdk/layout';
|
||||||
import {style, state, animate, transition, trigger} from '@angular/animations';
|
import { style, animate, transition, trigger } from '@angular/animations';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
|
||||||
|
/** rxjs Imports */
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { map } from 'rxjs/operators';
|
import { map } from 'rxjs/operators';
|
||||||
|
|
||||||
|
/** Custom Services */
|
||||||
|
import { AuthenticationService } from '../../authentication/authentication.service';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toolbar component.
|
||||||
|
*/
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'mifosx-toolbar',
|
selector: 'mifosx-toolbar',
|
||||||
templateUrl: './toolbar.component.html',
|
templateUrl: './toolbar.component.html',
|
||||||
@@ -24,45 +33,70 @@ import { map } from 'rxjs/operators';
|
|||||||
})
|
})
|
||||||
export class ToolbarComponent implements OnInit {
|
export class ToolbarComponent implements OnInit {
|
||||||
|
|
||||||
|
/** Subscription to breakpoint observer for handset. */
|
||||||
isHandset$: Observable<boolean> = this.breakpointObserver.observe(Breakpoints.Handset)
|
isHandset$: Observable<boolean> = this.breakpointObserver.observe(Breakpoints.Handset)
|
||||||
.pipe(
|
.pipe(
|
||||||
map(result => result.matches)
|
map(result => result.matches)
|
||||||
);
|
);
|
||||||
|
|
||||||
isHandset: boolean;
|
/** Sets the initial visibility of search input as hidden. Visible if true. */
|
||||||
searchVisible = false;
|
searchVisible = false;
|
||||||
selectedLanguage = 'English';
|
/** Sets the initial state of sidenav as collapsed. Not collapsed if false. */
|
||||||
sidenavCollapsed = false;
|
sidenavCollapsed = true;
|
||||||
|
|
||||||
|
/** Instance of sidenav. */
|
||||||
@Input() sidenav: MatSidenav;
|
@Input() sidenav: MatSidenav;
|
||||||
|
/** Sidenav collapse event. */
|
||||||
@Output() collapse = new EventEmitter<boolean>();
|
@Output() collapse = new EventEmitter<boolean>();
|
||||||
|
|
||||||
constructor(private breakpointObserver: BreakpointObserver) { }
|
/**
|
||||||
|
* @param {BreakpointObserver} breakpointObserver Breakpoint observer to detect screen size.
|
||||||
|
* @param {Router} router Router for navigation.
|
||||||
|
* @param {AuthenticationService} authenticationService Authentication service.
|
||||||
|
*/
|
||||||
|
constructor(private breakpointObserver: BreakpointObserver,
|
||||||
|
private router: Router,
|
||||||
|
private authenticationService: AuthenticationService) { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subscribes to breakpoint for handset.
|
||||||
|
*/
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.sidenavCollapsed = !this.sidenavCollapsed;
|
|
||||||
this.isHandset$.subscribe(isHandset => {
|
this.isHandset$.subscribe(isHandset => {
|
||||||
this.isHandset = isHandset;
|
if (isHandset && this.sidenavCollapsed) {
|
||||||
|
this.toggleSidenavCollapse(false);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggles the current state of sidenav.
|
||||||
|
*/
|
||||||
toggleSidenav() {
|
toggleSidenav() {
|
||||||
this.sidenav.toggle();
|
this.sidenav.toggle();
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleCollapse() {
|
/**
|
||||||
this.sidenavCollapsed = !this.sidenavCollapsed;
|
* Toggles the current collapsed state of sidenav.
|
||||||
|
*/
|
||||||
|
toggleSidenavCollapse(sidenavCollapsed?: boolean) {
|
||||||
|
this.sidenavCollapsed = sidenavCollapsed || !this.sidenavCollapsed;
|
||||||
this.collapse.emit(this.sidenavCollapsed);
|
this.collapse.emit(this.sidenavCollapsed);
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleVisibility() {
|
/**
|
||||||
|
* Toggles the visibility of search input with fadeInOut animation.
|
||||||
|
*/
|
||||||
|
toggleSearchVisibility() {
|
||||||
this.searchVisible = !this.searchVisible;
|
this.searchVisible = !this.searchVisible;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* onMouseEnter(menuTrigger: MatMenuTrigger) {
|
/**
|
||||||
if (!this.isHandset) {
|
* Logs out the authenticated user and redirects to login page.
|
||||||
menuTrigger.openMenu();
|
*/
|
||||||
}
|
logout() {
|
||||||
|
this.authenticationService.logout()
|
||||||
|
.subscribe(() => this.router.navigate(['/login'], { replaceUrl: true }));
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,7 +55,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Contact Information/Resources (hidden on smaller screens) -->
|
<!-- Contact Information/Resources (hidden on smaller screens) -->
|
||||||
<mat-list fxHide.lt-md fxLayout="row" fxFlexAlign="center" fxFlex="3 0 auto" class="information-list">
|
<mat-list fxHide.lt-lg fxLayout="row" fxFlexAlign="center" fxFlex="3 0 auto" class="information-list">
|
||||||
<mat-list-item><button mat-button [matMenuTriggerFor]="resourcesMenu">Resources</button></mat-list-item>
|
<mat-list-item><button mat-button [matMenuTriggerFor]="resourcesMenu">Resources</button></mat-list-item>
|
||||||
<mat-list-item><button mat-button [matMenuTriggerFor]="communityMenu">Community</button></mat-list-item>
|
<mat-list-item><button mat-button [matMenuTriggerFor]="communityMenu">Community</button></mat-list-item>
|
||||||
<mat-list-item><button mat-button [matMenuTriggerFor]="contributeMenu">Contribute</button></mat-list-item>
|
<mat-list-item><button mat-button [matMenuTriggerFor]="contributeMenu">Contribute</button></mat-list-item>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
|||||||
|
|
||||||
import { LoginComponent } from './login.component';
|
import { LoginComponent } from './login.component';
|
||||||
|
|
||||||
describe('LanguageSelectorComponent', () => {
|
describe('LoginComponent', () => {
|
||||||
let component: LoginComponent;
|
let component: LoginComponent;
|
||||||
let fixture: ComponentFixture<LoginComponent>;
|
let fixture: ComponentFixture<LoginComponent>;
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#footer {
|
#footer {
|
||||||
.divider {
|
.divider {
|
||||||
margin: 1rem 1rem 0.5rem;
|
margin: 2.5rem 1rem 0.5rem;
|
||||||
width: 4rem;
|
width: 4rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,25 +5,61 @@ import { NgModule } from '@angular/core';
|
|||||||
import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';
|
import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';
|
||||||
import { library } from '@fortawesome/fontawesome-svg-core';
|
import { library } from '@fortawesome/fontawesome-svg-core';
|
||||||
import {
|
import {
|
||||||
|
faBars,
|
||||||
|
faBell,
|
||||||
|
faChartBar,
|
||||||
faCheckCircle,
|
faCheckCircle,
|
||||||
|
faChevronLeft,
|
||||||
|
faChevronRight,
|
||||||
|
faCog,
|
||||||
faEye,
|
faEye,
|
||||||
faEyeSlash,
|
faEyeSlash,
|
||||||
faFileUpload,
|
faFileUpload,
|
||||||
|
faFillDrip,
|
||||||
|
faHome,
|
||||||
|
faLocationArrow,
|
||||||
faLock,
|
faLock,
|
||||||
|
faMoneyBillAlt,
|
||||||
|
faPlus,
|
||||||
faSearch,
|
faSearch,
|
||||||
|
faShieldAlt,
|
||||||
|
faSignOutAlt,
|
||||||
|
faSitemap,
|
||||||
|
faSync,
|
||||||
|
faTachometerAlt,
|
||||||
|
faUniversity,
|
||||||
faUserCircle,
|
faUserCircle,
|
||||||
|
faUsers,
|
||||||
faUserShield
|
faUserShield
|
||||||
} from '@fortawesome/free-solid-svg-icons';
|
} from '@fortawesome/free-solid-svg-icons';
|
||||||
|
|
||||||
/** Add icons to the library for convenient access in other components. */
|
/** Add icons to the library for convenient access in other components. */
|
||||||
library.add(
|
library.add(
|
||||||
|
faBars,
|
||||||
|
faBell,
|
||||||
|
faChartBar,
|
||||||
faCheckCircle,
|
faCheckCircle,
|
||||||
|
faChevronLeft,
|
||||||
|
faChevronRight,
|
||||||
|
faCog,
|
||||||
faEye,
|
faEye,
|
||||||
faEyeSlash,
|
faEyeSlash,
|
||||||
faFileUpload,
|
faFileUpload,
|
||||||
|
faFillDrip,
|
||||||
|
faHome,
|
||||||
|
faLocationArrow,
|
||||||
faLock,
|
faLock,
|
||||||
|
faMoneyBillAlt,
|
||||||
|
faPlus,
|
||||||
faSearch,
|
faSearch,
|
||||||
|
faShieldAlt,
|
||||||
|
faSignOutAlt,
|
||||||
|
faSitemap,
|
||||||
|
faSync,
|
||||||
|
faTachometerAlt,
|
||||||
|
faUniversity,
|
||||||
faUserCircle,
|
faUserCircle,
|
||||||
|
faUsers,
|
||||||
faUserShield
|
faUserShield
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { Component, OnInit } from '@angular/core';
|
|||||||
import { FormControl } from '@angular/forms';
|
import { FormControl } from '@angular/forms';
|
||||||
|
|
||||||
/** Custom Services */
|
/** Custom Services */
|
||||||
import { I18nService } from 'app/core';
|
import { I18nService } from '../../core/i18n.service';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Language selector component.
|
* Language selector component.
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { DeleteDialogComponent } from './delete-dialog/delete-dialog.component';
|
|||||||
import { FileUploadComponent } from './file-upload/file-upload.component';
|
import { FileUploadComponent } from './file-upload/file-upload.component';
|
||||||
import { FooterComponent } from './footer/footer.component';
|
import { FooterComponent } from './footer/footer.component';
|
||||||
import { LanguageSelectorComponent } from './language-selector/language-selector.component';
|
import { LanguageSelectorComponent } from './language-selector/language-selector.component';
|
||||||
|
import { ThemePickerComponent } from './theme-picker/theme-picker.component';
|
||||||
|
|
||||||
/** Custom Modules */
|
/** Custom Modules */
|
||||||
import { IconsModule } from './icons.module';
|
import { IconsModule } from './icons.module';
|
||||||
@@ -29,12 +30,14 @@ import { MaterialModule } from './material.module';
|
|||||||
DeleteDialogComponent,
|
DeleteDialogComponent,
|
||||||
FileUploadComponent,
|
FileUploadComponent,
|
||||||
FooterComponent,
|
FooterComponent,
|
||||||
LanguageSelectorComponent
|
LanguageSelectorComponent,
|
||||||
|
ThemePickerComponent
|
||||||
],
|
],
|
||||||
exports: [
|
exports: [
|
||||||
FileUploadComponent,
|
FileUploadComponent,
|
||||||
FooterComponent,
|
FooterComponent,
|
||||||
LanguageSelectorComponent,
|
LanguageSelectorComponent,
|
||||||
|
ThemePickerComponent,
|
||||||
CommonModule,
|
CommonModule,
|
||||||
IconsModule,
|
IconsModule,
|
||||||
MaterialModule,
|
MaterialModule,
|
||||||
|
|||||||
+21
@@ -1,18 +1,30 @@
|
|||||||
|
/** Angular Imports */
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Theme manager service.
|
||||||
|
*/
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
})
|
})
|
||||||
export class ThemeManagerService {
|
export class ThemeManagerService {
|
||||||
|
|
||||||
|
/** Stylesheet class for the theme of the application. */
|
||||||
private themeManagerClass = 'theme-manager';
|
private themeManagerClass = 'theme-manager';
|
||||||
|
|
||||||
constructor() { }
|
constructor() { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a new theme for the application.
|
||||||
|
* @param {string} href Link to stylesheet.
|
||||||
|
*/
|
||||||
setTheme(href: string) {
|
setTheme(href: string) {
|
||||||
this.getLinkElement().setAttribute('href', href);
|
this.getLinkElement().setAttribute('href', href);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the current theme of the application.
|
||||||
|
*/
|
||||||
removeTheme() {
|
removeTheme() {
|
||||||
const existingLinkElement = this.getExistingLinkElement();
|
const existingLinkElement = this.getExistingLinkElement();
|
||||||
if (existingLinkElement) {
|
if (existingLinkElement) {
|
||||||
@@ -20,14 +32,23 @@ export class ThemeManagerService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns link element of stylesheet
|
||||||
|
*/
|
||||||
private getLinkElement() {
|
private getLinkElement() {
|
||||||
return this.getExistingLinkElement() || this.createLinkElement();
|
return this.getExistingLinkElement() || this.createLinkElement();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns existing link element of stylesheet with theme manager class
|
||||||
|
*/
|
||||||
private getExistingLinkElement() {
|
private getExistingLinkElement() {
|
||||||
return document.head.querySelector(`link[rel="stylesheet"].${this.themeManagerClass}`);
|
return document.head.querySelector(`link[rel="stylesheet"].${this.themeManagerClass}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns newly created link element of stylesheet with theme manager class
|
||||||
|
*/
|
||||||
private createLinkElement() {
|
private createLinkElement() {
|
||||||
const linkElement = document.createElement('link');
|
const linkElement = document.createElement('link');
|
||||||
linkElement.setAttribute('rel', 'stylesheet');
|
linkElement.setAttribute('rel', 'stylesheet');
|
||||||
+2
-2
@@ -1,5 +1,5 @@
|
|||||||
<button mat-icon-button [matMenuTriggerFor]="themeMenu" matTooltip="Color Schemes">
|
<button mat-icon-button [matMenuTriggerFor]="themeMenu" matTooltip="Color Schemes">
|
||||||
<mat-icon>format_color_fill</mat-icon>
|
<fa-icon icon="fill-drip" size="lg"></fa-icon>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<mat-menu class="mifosx-theme-picker-menu" #themeMenu="matMenu" x-position="before">
|
<mat-menu class="mifosx-theme-picker-menu" #themeMenu="matMenu" x-position="before">
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
<mat-grid-tile *ngFor="let theme of themes">
|
<mat-grid-tile *ngFor="let theme of themes">
|
||||||
<div mat-menu-item (click)="installTheme(theme)">
|
<div mat-menu-item (click)="installTheme(theme)">
|
||||||
<div class="mifosx-theme-picker-swatch">
|
<div class="mifosx-theme-picker-swatch">
|
||||||
<mat-icon class="mifosx-theme-chosen-icon" *ngIf="currentTheme.href === theme.href">check_circle</mat-icon>
|
<fa-icon class="mifosx-theme-chosen-icon" *ngIf="currentTheme.href === theme.href" icon="check-circle" size="lg"></fa-icon>
|
||||||
<div class="mifosx-theme-picker-primary" [style.background]="theme.primary"></div>
|
<div class="mifosx-theme-picker-primary" [style.background]="theme.primary"></div>
|
||||||
<div class="mifosx-theme-picker-accent" [style.background]="theme.accent"></div>
|
<div class="mifosx-theme-picker-accent" [style.background]="theme.accent"></div>
|
||||||
</div>
|
</div>
|
||||||
+4
-3
@@ -26,19 +26,20 @@ $theme-picker-accent-stripe-size: 6px;
|
|||||||
.mifosx-theme-chosen-icon {
|
.mifosx-theme-chosen-icon {
|
||||||
color: white;
|
color: white;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 50%; top: 50%;
|
left: 50%;
|
||||||
|
top: 50%;
|
||||||
transform: translate(-50%, -50%);
|
transform: translate(-50%, -50%);
|
||||||
}
|
}
|
||||||
|
|
||||||
&::after {
|
&::after {
|
||||||
content: '';
|
content: "";
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
border: 1px solid rgba(0,0,0,.2);
|
border: 1px solid rgba(0, 0, 0, 0.2);
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+22
-1
@@ -1,8 +1,17 @@
|
|||||||
|
/** Angular Imports */
|
||||||
import { Component, OnInit, ViewEncapsulation } from '@angular/core';
|
import { Component, OnInit, ViewEncapsulation } from '@angular/core';
|
||||||
|
|
||||||
|
/** Custom Model */
|
||||||
import { Theme } from './theme.model';
|
import { Theme } from './theme.model';
|
||||||
|
|
||||||
|
/** Custom Services */
|
||||||
import { ThemeStorageService } from './theme-storage.service';
|
import { ThemeStorageService } from './theme-storage.service';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Theme picker component.
|
||||||
|
*
|
||||||
|
* TODO: Customization of theme for every component and custom background with darker contrast.
|
||||||
|
*/
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'mifosx-theme-picker',
|
selector: 'mifosx-theme-picker',
|
||||||
templateUrl: './theme-picker.component.html',
|
templateUrl: './theme-picker.component.html',
|
||||||
@@ -11,6 +20,7 @@ import { ThemeStorageService } from './theme-storage.service';
|
|||||||
})
|
})
|
||||||
export class ThemePickerComponent implements OnInit {
|
export class ThemePickerComponent implements OnInit {
|
||||||
|
|
||||||
|
/** Default theme for the application. */
|
||||||
currentTheme: Theme = {
|
currentTheme: Theme = {
|
||||||
href: 'denim-yellowgreen.css',
|
href: 'denim-yellowgreen.css',
|
||||||
primary: '#1074B9',
|
primary: '#1074B9',
|
||||||
@@ -18,6 +28,7 @@ export class ThemePickerComponent implements OnInit {
|
|||||||
isDark: false,
|
isDark: false,
|
||||||
isDefault: true
|
isDefault: true
|
||||||
};
|
};
|
||||||
|
/** Available themes for the application. */
|
||||||
themes = [
|
themes = [
|
||||||
this.currentTheme,
|
this.currentTheme,
|
||||||
{
|
{
|
||||||
@@ -38,7 +49,7 @@ export class ThemePickerComponent implements OnInit {
|
|||||||
accent: '#FFC107',
|
accent: '#FFC107',
|
||||||
isDark: false
|
isDark: false
|
||||||
},
|
},
|
||||||
{ // TODO: Remove Dark Themes
|
{
|
||||||
href: 'pink-bluegrey.css',
|
href: 'pink-bluegrey.css',
|
||||||
primary: '#E91E63',
|
primary: '#E91E63',
|
||||||
accent: '#607D8B',
|
accent: '#607D8B',
|
||||||
@@ -52,8 +63,14 @@ export class ThemePickerComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {ThemeStorageService} themeStorageService Theme Storage Service.
|
||||||
|
*/
|
||||||
constructor(public themeStorageService: ThemeStorageService) { }
|
constructor(public themeStorageService: ThemeStorageService) { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the theme for the application.
|
||||||
|
*/
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
const theme = this.themeStorageService.getTheme();
|
const theme = this.themeStorageService.getTheme();
|
||||||
if (theme) {
|
if (theme) {
|
||||||
@@ -61,6 +78,10 @@ export class ThemePickerComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Installs a new theme for the application.
|
||||||
|
* @param {Theme} theme
|
||||||
|
*/
|
||||||
installTheme(theme: Theme) {
|
installTheme(theme: Theme) {
|
||||||
this.currentTheme = theme;
|
this.currentTheme = theme;
|
||||||
this.themeStorageService.installTheme(theme);
|
this.themeStorageService.installTheme(theme);
|
||||||
+27
@@ -1,33 +1,60 @@
|
|||||||
|
/** Angular Imports */
|
||||||
import {Injectable, EventEmitter} from '@angular/core';
|
import {Injectable, EventEmitter} from '@angular/core';
|
||||||
|
|
||||||
|
/** Custom Model */
|
||||||
import { Theme } from './theme.model';
|
import { Theme } from './theme.model';
|
||||||
|
|
||||||
|
/** Custom Services */
|
||||||
import { ThemeManagerService } from './theme-manager.service';
|
import { ThemeManagerService } from './theme-manager.service';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Theme storage service.
|
||||||
|
*/
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
})
|
})
|
||||||
export class ThemeStorageService {
|
export class ThemeStorageService {
|
||||||
|
|
||||||
|
/** Key to store current theme of application in local storage. */
|
||||||
private themeStorageKey = 'mifosXTheme';
|
private themeStorageKey = 'mifosXTheme';
|
||||||
|
/** Theme update event. */
|
||||||
onThemeUpdate: EventEmitter<Theme>;
|
onThemeUpdate: EventEmitter<Theme>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes theme update event.
|
||||||
|
* @param {ThemeManagerService} themeManagerService
|
||||||
|
*/
|
||||||
constructor(public themeManagerService: ThemeManagerService) {
|
constructor(public themeManagerService: ThemeManagerService) {
|
||||||
this.onThemeUpdate = new EventEmitter<Theme>();
|
this.onThemeUpdate = new EventEmitter<Theme>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores current theme in local storage and emits a theme update event.
|
||||||
|
* @param mifosXTheme
|
||||||
|
*/
|
||||||
storeTheme(mifosXTheme: Theme) {
|
storeTheme(mifosXTheme: Theme) {
|
||||||
localStorage.setItem(this.themeStorageKey, JSON.stringify(mifosXTheme));
|
localStorage.setItem(this.themeStorageKey, JSON.stringify(mifosXTheme));
|
||||||
this.onThemeUpdate.emit(mifosXTheme);
|
this.onThemeUpdate.emit(mifosXTheme);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns current theme from local storage
|
||||||
|
*/
|
||||||
getTheme(): Theme {
|
getTheme(): Theme {
|
||||||
return JSON.parse(localStorage.getItem(this.themeStorageKey));
|
return JSON.parse(localStorage.getItem(this.themeStorageKey));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes current theme from local storage.
|
||||||
|
*/
|
||||||
clearTheme() {
|
clearTheme() {
|
||||||
localStorage.removeItem(this.themeStorageKey);
|
localStorage.removeItem(this.themeStorageKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a new theme for the application and stores in local storage.
|
||||||
|
* @param {Theme} theme
|
||||||
|
*/
|
||||||
installTheme(theme: Theme) {
|
installTheme(theme: Theme) {
|
||||||
if (theme.isDefault) {
|
if (theme.isDefault) {
|
||||||
this.themeManagerService.removeTheme();
|
this.themeManagerService.removeTheme();
|
||||||
+3
@@ -1,3 +1,6 @@
|
|||||||
|
/**
|
||||||
|
* Theme model.
|
||||||
|
*/
|
||||||
export interface Theme {
|
export interface Theme {
|
||||||
href: string;
|
href: string;
|
||||||
primary: string;
|
primary: string;
|
||||||
@@ -7,9 +7,9 @@ import { TranslateService } from '@ngx-translate/core';
|
|||||||
import { merge } from 'rxjs';
|
import { merge } from 'rxjs';
|
||||||
import { filter, map, mergeMap } from 'rxjs/operators';
|
import { filter, map, mergeMap } from 'rxjs/operators';
|
||||||
|
|
||||||
import { environment } from '../environments/environment';
|
import { environment } from 'environments/environment';
|
||||||
import { Logger, I18nService } from './core';
|
import { Logger, I18nService } from './core';
|
||||||
import { ThemeStorageService } from './core/shell/toolbar/theme-picker/theme-storage.service';
|
import { ThemeStorageService } from './shared/theme-picker/theme-storage.service';
|
||||||
import { AlertService } from './core/alert.service';
|
import { AlertService } from './core/alert.service';
|
||||||
|
|
||||||
const log = new Logger('MifosX');
|
const log = new Logger('MifosX');
|
||||||
|
|||||||
@@ -3,56 +3,40 @@
|
|||||||
@import "width-height";
|
@import "width-height";
|
||||||
@import "z-index";
|
@import "z-index";
|
||||||
|
|
||||||
body, html {
|
body,
|
||||||
width: 100%;
|
html {
|
||||||
height: 100%;
|
width: 100%;
|
||||||
position: relative;
|
height: 100%;
|
||||||
overflow: hidden;
|
position: relative;
|
||||||
-webkit-tap-highlight-color: transparent;
|
overflow: hidden;
|
||||||
min-height: 100%;
|
-webkit-tap-highlight-color: transparent;
|
||||||
-webkit-font-smoothing: antialiased;
|
min-height: 100%;
|
||||||
}
|
-webkit-font-smoothing: antialiased;
|
||||||
|
}
|
||||||
|
|
||||||
html {
|
html {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
font-size: .875rem;
|
font-size: .875rem;
|
||||||
color: rgba(0,0,0,.87);
|
color: rgba(0,0,0,.87);
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
font-family: Roboto, Helvetica Neue, sans-serif;
|
font-family: Roboto, Helvetica Neue, sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
div {
|
div {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
max-width: 74rem;
|
max-width: 74rem;
|
||||||
width: 90%;
|
width: 90%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mat-icon-button.xs-button {
|
|
||||||
height: 20px;
|
|
||||||
width: 20px;
|
|
||||||
line-height: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mat-icon-button.xs-button .mat-icon {
|
|
||||||
font-size: 16px;
|
|
||||||
line-height: 20px;
|
|
||||||
height: 20px;
|
|
||||||
width: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-muted {
|
|
||||||
color: rgba(0, 0, 0, .54);
|
|
||||||
}
|
|
||||||
|
|
||||||
.mb-05 {
|
.mb-05 {
|
||||||
margin-bottom: .5rem;
|
margin-bottom: .5rem;
|
||||||
|
|||||||
Reference in New Issue
Block a user