feat: add keyboard shortcuts (#1191)
- implement keyboard shortcuts - designed user interface to view them Fixes: #257
This commit is contained in:
-1
@@ -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) => {});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 { }
|
||||
|
||||
@@ -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 });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user