feat: add keyboard shortcuts (#1191)

- implement keyboard shortcuts
- designed user interface to view them

Fixes: #257
This commit is contained in:
Muskan Khedia
2020-08-10 20:04:33 +05:30
committed by GitHub
parent bc57a061c4
commit 75b1d605f4
15 changed files with 373 additions and 10 deletions
@@ -68,7 +68,6 @@ export class ListStandingInstructionsComponent implements OnInit {
this.standingIntructionsTemplateData = data.standingIntructionsTemplate;
if (data.standingIntructionsTemplate.fromClient) {
this.clientName = this.standingIntructionsTemplateData.fromClient.displayName;
console.log('here');
this.getStandingInstructions();
}
this.setParams();
@@ -97,6 +97,12 @@
</mat-icon>
<a matLine>Chart of Accounts</a>
</mat-list-item>
<mat-list-item>
<mat-icon matListIcon (click)="help()">
<fa-icon icon="question-circle" size="sm"></fa-icon>
</mat-icon>
<a matLine>Help</a>
</mat-list-item>
</mat-nav-list>
</div>
@@ -43,4 +43,8 @@ export class SidenavComponent implements OnInit {
.subscribe(() => this.router.navigate(['/login'], { replaceUrl: true }));
}
help() {
window.open('https://mifosforge.jira.com/wiki/spaces/docs/pages/52035622/User+Manual', '_blank');
}
}
@@ -49,6 +49,10 @@
<mifosx-notifications-tray fxHide.lt-md></mifosx-notifications-tray>
<button mat-icon-button class="ml-1 img-button" matTooltip="Keyboard Shortcuts" (click)="showKeyboardShortcuts()">
<img src="assets/images/keyboard_placeholder.png">
</button>
<button mat-icon-button class="ml-1 img-button" [matMenuTriggerFor]="applicationMenu">
<img src="assets/images/user_placeholder.png">
</button>
@@ -100,7 +104,7 @@
</mat-icon>
<span>Settings</span>
</button>
<button mat-menu-item (click)="logout()">
<button mat-menu-item (click)="logout()" id="logout">
<mat-icon>
<fa-icon icon="sign-out-alt" size="sm"></fa-icon>
</mat-icon>
@@ -1,6 +1,6 @@
/** Angular Imports */
import { Component, OnInit, Input, EventEmitter, Output } from '@angular/core';
import { MatSidenav } from '@angular/material';
import { MatSidenav, MatDialog } from '@angular/material';
import { BreakpointObserver, Breakpoints } from '@angular/cdk/layout';
import { Router } from '@angular/router';
@@ -10,6 +10,7 @@ import { map } from 'rxjs/operators';
/** Custom Services */
import { AuthenticationService } from '../../authentication/authentication.service';
import { KeyboardShortcutsDialogComponent } from 'app/shared/keyboard-shortcuts-dialog/keyboard-shortcuts-dialog.component';
/**
* Toolbar component.
@@ -42,7 +43,8 @@ export class ToolbarComponent implements OnInit {
*/
constructor(private breakpointObserver: BreakpointObserver,
private router: Router,
private authenticationService: AuthenticationService) { }
private authenticationService: AuthenticationService,
private dialog: MatDialog) { }
/**
* Subscribes to breakpoint for handset.
@@ -78,4 +80,9 @@ export class ToolbarComponent implements OnInit {
.subscribe(() => this.router.navigate(['/login'], { replaceUrl: true }));
}
showKeyboardShortcuts() {
const dialogRef = this.dialog.open(KeyboardShortcutsDialogComponent);
dialogRef.afterClosed().subscribe((response: any) => {});
}
}
+203
View File
@@ -0,0 +1,203 @@
/** Keyboard Shortcuts Button Configuration */
export class KeyboardShortcutsConfiguration {
buttonsArray: {
title: string,
ctrlKey: boolean,
shiftKey: boolean,
altKey: boolean,
key: string,
route: string,
id?: string
}[];
constructor() {
this.makeCombination();
}
makeCombination() {
this.buttonsArray = [
// Navigation Page - alt + n
{
title: 'Navigation Page',
ctrlKey: false,
shiftKey: false,
altKey: true,
key: 'n',
route: 'navigation'
},
// Run Report - alt + r
{
title: 'Run Report',
ctrlKey: false,
shiftKey: false,
altKey: true,
key: 'r',
route: '',
id: 'runReport'
},
// Checker Inbox & Pending Tasks - ctrl + alt + i
// {
// title: 'Checker Inbox & Pending Tasks',
// ctrlKey: true,
// shiftKey: false,
// altKey: true,
// key: 'i,
// route: ''
// },
// Collection Sheet- ctrl + alt + o
// {
// title: 'Collection Sheet',
// ctrlKey: true,
// shiftKey: false,
// altKey: true,
// key: 'o,
// route: ''
// },
// Create Client - ctrl + alt + c
{
title: 'Create Client',
ctrlKey: true,
shiftKey: false,
altKey: true,
key: 'c',
route: 'clients/create'
},
// Create Group - ctrl + alt + g
{
title: 'Create Group',
ctrlKey: true,
shiftKey: false,
altKey: true,
key: 'g',
route: 'groups/create'
},
// Create Center - ctrl + alt + q
{
title: 'Create Center',
ctrlKey: true,
shiftKey: false,
altKey: true,
key: 'q',
route: 'centers/create'
},
// Frequent Posting - ctrl + alt + f
{
title: 'Frequent Posting',
ctrlKey: true,
shiftKey: false,
altKey: true,
key: 'f',
route: 'accounting/journal-entries/frequent-postings'
},
// Closure Entries - ctrl + alt + e
{
title: 'Closure Entries',
ctrlKey: true,
shiftKey: false,
altKey: true,
key: 'e',
route: 'accounting/closing-entries'
},
// Journal Entry - ctrl + alt + j
{
title: 'Journal Entry',
ctrlKey: true,
shiftKey: false,
altKey: true,
key: 'j',
route: 'accounting/journal-entries/create'
},
// Reports - ctrl + alt + r
{
title: 'Reports',
ctrlKey: true,
shiftKey: false,
altKey: true,
key: 'r',
route: 'reports'
},
// Accounting - ctrl + alt + a
{
title: 'Accounting',
ctrlKey: true,
shiftKey: false,
altKey: true,
key: 'a',
route: 'accounting'
},
// Save/Submit Forms - ctrl + alt + s
{
title: 'Save/Submit Forms',
ctrlKey: true,
shiftKey: false,
altKey: true,
key: 's',
route: '',
id: 'submit'
},
// Cancel - ctrl + alt + x
{
title: 'Cancel',
ctrlKey: true,
shiftKey: false,
altKey: true,
key: 'x',
route: '',
id: 'cancel'
},
// Search - alt + x
// {
// title: 'Search'
// ctrlKey: true,
// shiftKey: false,
// altKey: true,
// key: 88,
// route: ''
// },
// Help - ctrl + alt + h
{
title: 'Help',
ctrlKey: true,
shiftKey: false,
altKey: true,
key: 'h',
route: '',
id: 'help'
},
// Pagination: Next - ctrl + n
// {
// title: 'Pagination: Next'
// ctrlKey: true,
// shiftKey: false,
// altKey: true,
// key: 'n,
// route: ''
// },
// Pagination: Previous - ctrl + p
// {
// title: 'Pagination: Previous'
// ctrlKey: true,
// shiftKey: false,
// altKey: true,
// key: 'p,
// route: ''
// },
// Logout - ctrl + shift + l
{
title: 'Logout',
ctrlKey: true,
shiftKey: true,
altKey: false,
key: 'l',
route: '',
id: 'logout'
}
];
}
get buttonCombinations() {
return this.buttonsArray;
}
}
@@ -80,7 +80,7 @@
<mat-card-actions fxLayout="row" fxLayout.xs="column" fxLayoutAlign="center" fxLayoutGap="5px">
<button type="button" mat-raised-button [routerLink]="['/reports']">Cancel</button>
<button mat-raised-button color="primary" [disabled]="!ReportForm.valid">
<button mat-raised-button color="primary" [disabled]="!ReportForm.valid" id="runReport">
<fa-icon icon="cogs"></fa-icon>
&nbsp; Run Report
</button>
@@ -24,7 +24,7 @@
<mat-card-actions fxLayout="row" fxLayout.xs="column" fxLayoutAlign="center" fxLayoutGap="5px">
<button type="button" mat-raised-button [routerLink]="['../']">Cancel</button>
<button mat-raised-button color="primary">Run Report</button>
<button mat-raised-button color="primary" id="runReport">Run Report</button>
</mat-card-actions>
</form>
@@ -0,0 +1,18 @@
<h1 mat-dialog-title>Keyboard Shortcuts</h1>
<div mat-dialog-content>
<div fxLayout="column" fxLayoutGap="2%">
<ng-container *ngFor="let item of buttonConfig.buttonCombinations">
<div fxFlexFill fxFlex="50%">
<span fxFlex="50%">
<span *ngIf="item.ctrlKey"> <span class="modifierKey">Ctrl</span> + </span>
<span *ngIf="item.shiftKey"> <span class="modifierKey">Shift</span> + </span>
<span *ngIf="item.altKey"> <span class="modifierKey">Alt</span> + </span>
<span *ngIf="item.key"> <span class="modifierKey">{{ item.key }}</span></span>
</span>
<span fxFlex="50%">{{ item.title }}</span>
</div>
<mat-divider [inset]="true"></mat-divider>
</ng-container>
</div>
</div>
@@ -0,0 +1,11 @@
.modifierKey {
border: 1px solid #0000004d;
padding: 0.3rem;
background: #f8f8f8;
font-weight: bold;
border-radius: 0.3rem;
}
.mat-divider.mat-divider-inset {
margin-left: 0px;
}
@@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { KeyboardShortcutsDialogComponent } from './keyboard-shortcuts-dialog.component';
describe('KeyboardShortcutsDialogComponent', () => {
let component: KeyboardShortcutsDialogComponent;
let fixture: ComponentFixture<KeyboardShortcutsDialogComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ KeyboardShortcutsDialogComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(KeyboardShortcutsDialogComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,28 @@
/** Angular Imports */
import { Component, OnInit, Inject } from '@angular/core';
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material';
import { KeyboardShortcutsConfiguration } from '../../keyboards-shortcut-config';
/**
* Delete dialog component.
*/
@Component({
selector: 'mifosx-keyboard-shortcuts-dialog',
templateUrl: './keyboard-shortcuts-dialog.component.html',
styleUrls: ['./keyboard-shortcuts-dialog.component.scss']
})
export class KeyboardShortcutsDialogComponent implements OnInit {
buttonConfig: KeyboardShortcutsConfiguration;
/**
* @param {MatDialogRef} dialogRef Component reference to dialog.
* @param {any} data Provides a deleteContext.
*/
constructor(public dialogRef: MatDialogRef<KeyboardShortcutsDialogComponent>) { }
ngOnInit() {
this.dialogRef.updateSize(`800px`);
this.buttonConfig = new KeyboardShortcutsConfiguration();
}
}
+5 -2
View File
@@ -15,6 +15,7 @@ import { ChangePasswordDialogComponent } from './change-password-dialog/change-p
import { EnableDialogComponent } from './enable-dialog/enable-dialog.component';
import { DisableDialogComponent } from './disable-dialog/disable-dialog.component';
import { ConfirmationDialogComponent } from './confirmation-dialog/confirmation-dialog.component';
import { KeyboardShortcutsDialogComponent } from './keyboard-shortcuts-dialog/keyboard-shortcuts-dialog.component';
/** Custom Modules */
import { IconsModule } from './icons.module';
@@ -43,7 +44,8 @@ import { MaterialModule } from './material.module';
ChangePasswordDialogComponent,
EnableDialogComponent,
DisableDialogComponent,
ConfirmationDialogComponent
ConfirmationDialogComponent,
KeyboardShortcutsDialogComponent
],
exports: [
FileUploadComponent,
@@ -62,7 +64,8 @@ import { MaterialModule } from './material.module';
ChangePasswordDialogComponent,
EnableDialogComponent,
DisableDialogComponent,
ConfirmationDialogComponent
ConfirmationDialogComponent,
KeyboardShortcutsDialogComponent
]
})
export class SharedModule { }
+57 -2
View File
@@ -1,5 +1,5 @@
/** Angular Imports */
import { Component, OnInit } from '@angular/core';
import { Component, OnInit, HostListener } from '@angular/core';
import { Router, NavigationEnd, ActivatedRoute } from '@angular/router';
import { Title } from '@angular/platform-browser';
import { MatSnackBar } from '@angular/material';
@@ -19,9 +19,11 @@ import { Logger } from './core/logger/logger.service';
import { I18nService } from './core/i18n/i18n.service';
import { ThemeStorageService } from './shared/theme-picker/theme-storage.service';
import { AlertService } from './core/alert/alert.service';
import { KeyboardShortcutsConfiguration } from './keyboards-shortcut-config';
/** Custom Models */
import { Alert } from './core/alert/alert.model';
import { AuthenticationService } from './core/authentication/authentication.service';
/** Initialize Logger */
const log = new Logger('MifosX');
@@ -36,6 +38,8 @@ const log = new Logger('MifosX');
})
export class WebAppComponent implements OnInit {
buttonConfig: KeyboardShortcutsConfiguration;
/**
* @param {Router} router Router for navigation.
* @param {ActivatedRoute} activatedRoute Activated Route.
@@ -45,6 +49,7 @@ export class WebAppComponent implements OnInit {
* @param {ThemeStorageService} themeStorageService Theme Storage Service.
* @param {MatSnackBar} snackBar Material Snackbar for notifications.
* @param {AlertService} alertService Alert Service.
* @param {AuthenticationService} authenticationService Authentication service.
*/
constructor(private router: Router,
private activatedRoute: ActivatedRoute,
@@ -53,7 +58,8 @@ export class WebAppComponent implements OnInit {
private i18nService: I18nService,
private themeStorageService: ThemeStorageService,
public snackBar: MatSnackBar,
private alertService: AlertService) { }
private alertService: AlertService,
private authenticationService: AuthenticationService) { }
/**
* Initial Setup:
@@ -126,6 +132,55 @@ export class WebAppComponent implements OnInit {
verticalPosition: 'top'
});
});
this.buttonConfig = new KeyboardShortcutsConfiguration();
}
logout() {
this.authenticationService.logout()
.subscribe(() => this.router.navigate(['/login'], { replaceUrl: true }));
}
help() {
window.open('https://mifosforge.jira.com/wiki/spaces/docs/pages/52035622/User+Manual', '_blank');
}
// Monitor all keyboard events and excute keyboard shortcuts
@HostListener('window:keydown', ['$event'])
onKeydownHandler(event: KeyboardEvent) {
const routeD = this.buttonConfig.buttonCombinations.find(x => (x.ctrlKey === event.ctrlKey && x.shiftKey === event.shiftKey && x.altKey === event.altKey && x.key === event.key));
if (!(routeD === undefined)) {
switch (routeD.id) {
case 'logout':
this.logout();
break;
case 'help':
this.help();
break;
case 'runReport':
document.getElementById('runReport').click();
break;
case 'cancel':
const cancelButtons = document.querySelectorAll('button');
const filteredcancelButtons = Array.prototype.filter.call(cancelButtons, function (el: any) {
return el.textContent.trim() === 'Cancel';
});
if (filteredcancelButtons.length > 0) {
filteredcancelButtons[0].click();
}
break;
case 'submit':
const submitButton = document.querySelectorAll('button');
const filteredSubmitButton = Array.prototype.filter.call(submitButton, function (el: any) {
return el.textContent.trim() === 'Submit';
});
if (filteredSubmitButton.length > 0) {
filteredSubmitButton[0].click();
}
break;
default:
this.router.navigate([routeD.route], { relativeTo: this.activatedRoute });
}
}
}
}