From c25d00181602de2283af3b034401770c6d48903e Mon Sep 17 00:00:00 2001 From: "mathias.chouet" <mathias.chouet@irstea.fr> Date: Fri, 23 Aug 2019 17:23:45 +0200 Subject: [PATCH] Fix #192 - add keyboard shortcuts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Alt + S save current session Alt + O open session file Alt + Q empty current session Alt + N add new module Alt + ↵ trigger calculation Alt + D duplicate current module Alt + W close current module --- package-lock.json | 19 +++++++ package.json | 3 +- src/app/app.component.html | 2 +- src/app/app.component.ts | 56 +++++++++++-------- src/app/app.module.ts | 4 +- .../app-setup/app-setup.component.html | 5 ++ .../app-setup/app-setup.component.ts | 13 +++++ .../calculator.component.ts | 11 +++- src/app/config.json | 1 + .../services/app-setup/app-setup.service.ts | 8 +++ src/locale/messages.en.json | 1 + src/locale/messages.fr.json | 1 + 12 files changed, 96 insertions(+), 28 deletions(-) diff --git a/package-lock.json b/package-lock.json index 46e495dd6..4c9a9bbd2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1087,6 +1087,11 @@ "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==", "dev": true }, + "@types/mousetrap": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/@types/mousetrap/-/mousetrap-1.6.3.tgz", + "integrity": "sha512-13gmo3M2qVvjQrWNseqM3+cR6S2Ss3grbR2NZltgMq94wOwqJYQdgn8qzwDshzgXqMlSUtyPZjysImmktu22ew==" + }, "@types/node": { "version": "12.6.8", "resolved": "https://registry.npmjs.org/@types/node/-/node-12.6.8.tgz", @@ -1456,6 +1461,15 @@ "chart.js": "^2.3.0" } }, + "angular2-hotkeys": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/angular2-hotkeys/-/angular2-hotkeys-2.1.4.tgz", + "integrity": "sha512-/KzgsrFjodoeZosXqsx1IvUo3rWBalSJ3QyVz2EALj1C0Woz84iNtXPZnlzuPNHrCmHcfOu28BNvIGBa+9Ving==", + "requires": { + "@types/mousetrap": "^1.6.0", + "mousetrap": "^1.6.0" + } + }, "ansi": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/ansi/-/ansi-0.3.1.tgz", @@ -9713,6 +9727,11 @@ "on-headers": "~1.0.1" } }, + "mousetrap": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/mousetrap/-/mousetrap-1.6.3.tgz", + "integrity": "sha512-bd+nzwhhs9ifsUrC2tWaSgm24/oo2c83zaRyZQF06hYA6sANfsXHtnZ19AbbbDXCDzeH5nZBSQ4NvCjgD62tJA==" + }, "move-concurrently": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", diff --git a/package.json b/package.json index 54d37af7c..2fd54de46 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ "@types/pako": "^1.0.1", "@types/sprintf-js": "^1.1.2", "angular2-chartjs": "^0.5.1", + "angular2-hotkeys": "^2.1.4", "chartjs-plugin-zoom": "^0.7.3", "cordova-android": "^8.0.0", "cordova-plugin-device": "^2.0.3", @@ -104,4 +105,4 @@ "android" ] } -} \ No newline at end of file +} diff --git a/src/app/app.component.html b/src/app/app.component.html index b735b02bf..64b3c9ae9 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -130,7 +130,7 @@ <mat-sidenav-content class="sidenav-content" fxFlexFill> <div id="app-content"> - <router-outlet (activate)="onRouterOutletActivated($event)"></router-outlet> + <router-outlet></router-outlet> </div> </mat-sidenav-content> diff --git a/src/app/app.component.ts b/src/app/app.component.ts index b0dd4500e..9121bbd12 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,10 +1,8 @@ import { Component, ApplicationRef, OnInit, OnDestroy, HostListener, ViewChild, ComponentRef } from "@angular/core"; import { Router, Event, NavigationEnd, ActivationEnd, NavigationStart, NavigationCancel, NavigationError } from "@angular/router"; import { MatDialog } from "@angular/material/dialog"; -import { MatIconRegistry } from "@angular/material/icon"; import { MatSidenav } from "@angular/material/sidenav"; import { MatToolbar } from "@angular/material/toolbar"; -import { DomSanitizer } from "@angular/platform-browser"; import { CdkDragDrop, moveItemInArray } from "@angular/cdk/drag-drop"; import { Observer, jalhydDateRev, jalhydVersion, CalculatorType, Session } from "jalhyd"; @@ -25,6 +23,8 @@ import { DialogLoadSessionComponent } from "./components/dialog-load-session/dia import { DialogSaveSessionComponent } from "./components/dialog-save-session/dialog-save-session.component"; import { NotificationsService } from "./services/notifications/notifications.service"; +import { HotkeysService, Hotkey } from "angular2-hotkeys"; + import * as pako from "pako"; @Component({ @@ -70,15 +70,9 @@ export class AppComponent implements OnInit, OnDestroy, Observer { private _innerWidth: number; - /** - * composant actuellement affiché par l'élément <router-outlet> - */ - private _routerCurrentComponent: Component; - constructor( private intlService: I18nService, private appSetupService: ApplicationSetupService, - private appRef: ApplicationRef, private errorService: ErrorService, private router: Router, private formulaireService: FormulaireService, @@ -87,9 +81,8 @@ export class AppComponent implements OnInit, OnDestroy, Observer { private confirmEmptySessionDialog: MatDialog, private saveSessionDialog: MatDialog, private loadSessionDialog: MatDialog, - private matIconRegistry: MatIconRegistry, - private domSanitizer: DomSanitizer, - private confirmCloseCalcDialog: MatDialog + private confirmCloseCalcDialog: MatDialog, + private hotkeysService: HotkeysService ) { ServiceFactory.instance.httpService = httpService; ServiceFactory.instance.applicationSetupService = appSetupService; @@ -128,6 +121,28 @@ export class AppComponent implements OnInit, OnDestroy, Observer { this.showLoading(false); } }); + + // hotkeys listeners + this.hotkeysService.add(new Hotkey("alt+s", AppComponent.onHotkey(this.saveForm, this))); + this.hotkeysService.add(new Hotkey("alt+o", AppComponent.onHotkey(this.loadSession, this))); + this.hotkeysService.add(new Hotkey("alt+q", AppComponent.onHotkey(this.emptySession, this))); + this.hotkeysService.add(new Hotkey("alt+n", AppComponent.onHotkey(this.toList, this))); + } + + /** + * Wrapper for hotkeys triggers, that executes given function only if + * hotkeys are enabled in app preferences + * @param func function to execute when hotkey is entered + */ + public static onHotkey(func: any, that: any) { + return (event: KeyboardEvent): boolean => { + if (ServiceFactory.instance.applicationSetupService.enableHotkeys) { + func.call(that); + return false; // Prevent bubbling + } else { + console.log("Hotkeys are disabled in app preferences"); + } + }; } /** @@ -226,12 +241,12 @@ export class AppComponent implements OnInit, OnDestroy, Observer { }, disableClose: true } - ); - dialogRef.afterClosed().subscribe(result => { - if (result) { - this.formulaireService.requestCloseForm(uid); - } - }); + ); + dialogRef.afterClosed().subscribe(result => { + if (result) { + this.formulaireService.requestCloseForm(uid); + } + }); } } @@ -426,13 +441,6 @@ export class AppComponent implements OnInit, OnDestroy, Observer { this.setActiveCalc(id); } - /** - * récupération du composant affiché par le routeur - */ - public onRouterOutletActivated(a) { - this._routerCurrentComponent = a; - } - /** * restarts a fresh session by closing all calculators */ diff --git a/src/app/app.module.ts b/src/app/app.module.ts index e1fb55a8b..26158718f 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -46,6 +46,7 @@ import { ChartModule } from "angular2-chartjs"; import { RouterModule, Routes } from "@angular/router"; import { NgxMdModule } from "ngx-md"; import { StorageServiceModule } from "ngx-webstorage-service"; +import { HotkeyModule } from "angular2-hotkeys"; import { FormulaireService } from "./services/formulaire/formulaire.service"; import { I18nService } from "./services/internationalisation/internationalisation.service"; @@ -121,8 +122,9 @@ const appRoutes: Routes = [ BrowserModule, ChartModule, DragDropModule, - HttpClientModule, FlexLayoutModule, + HotkeyModule.forRoot(), + HttpClientModule, MatBadgeModule, MatButtonModule, MatButtonToggleModule, diff --git a/src/app/components/app-setup/app-setup.component.html b/src/app/components/app-setup/app-setup.component.html index a8bef4b4c..d7f7a0ae6 100644 --- a/src/app/components/app-setup/app-setup.component.html +++ b/src/app/components/app-setup/app-setup.component.html @@ -72,6 +72,11 @@ {{ uitextEnableNotifications }} </mat-checkbox> + <!-- hotkeys --> + <mat-checkbox name="hotkeys" [(ngModel)]="enableHotkeys" [ngModelOptions]="{standalone: true}"> + {{ uitextEnableHotkeys }} + </mat-checkbox> + <!-- langue --> <mat-form-field> <mat-select [placeholder]="uitextLanguage" [(value)]="currentLanguageCode" data-testid="language-select"> diff --git a/src/app/components/app-setup/app-setup.component.ts b/src/app/components/app-setup/app-setup.component.ts index c1eafaed8..dff9caef6 100644 --- a/src/app/components/app-setup/app-setup.component.ts +++ b/src/app/components/app-setup/app-setup.component.ts @@ -61,6 +61,15 @@ export class ApplicationSetupComponent extends BaseComponent implements Observer this.appSetupService.enableNotifications = v; } + /** hotkeys (keyboard shortcuts) */ + public get enableHotkeys(): boolean { + return this.appSetupService.enableHotkeys; + } + + public set enableHotkeys(v: boolean) { + this.appSetupService.enableHotkeys = v; + } + public get uitextTitle(): string { return this.intlService.localizeText("INFO_SETUP_TITLE"); } @@ -85,6 +94,10 @@ export class ApplicationSetupComponent extends BaseComponent implements Observer return this.intlService.localizeText("INFO_SETUP_ENABLE_NOTIFICATIONS"); } + public get uitextEnableHotkeys(): string { + return this.intlService.localizeText("INFO_SETUP_ENABLE_HOTKEYS"); + } + public get uitextMustBeANumber(): string { return this.intlService.localizeText("ERROR_PARAM_MUST_BE_A_NUMBER"); } diff --git a/src/app/components/generic-calculator/calculator.component.ts b/src/app/components/generic-calculator/calculator.component.ts index ef034b6f0..7b4ae6960 100644 --- a/src/app/components/generic-calculator/calculator.component.ts +++ b/src/app/components/generic-calculator/calculator.component.ts @@ -4,6 +4,7 @@ import { ActivatedRoute, Router } from "@angular/router"; import { Observer, Session, Cloisons, Pab, ParamValueMode, CalculatorType } from "jalhyd"; +import { AppComponent } from "../../app.component"; import { FormulaireService } from "../../services/formulaire/formulaire.service"; import { I18nService } from "../../services/internationalisation/internationalisation.service"; import { FieldSet } from "../../formulaire/fieldset"; @@ -23,6 +24,8 @@ import { DialogConfirmCloseCalcComponent } from "../dialog-confirm-close-calc/di import { DialogGeneratePABComponent } from "../dialog-generate-pab/dialog-generate-pab.component"; import { PabTable } from "../../formulaire/pab-table"; +import { HotkeysService, Hotkey } from "angular2-hotkeys"; + @Component({ selector: "hydrocalc", templateUrl: "./calculator.component.html", @@ -110,11 +113,17 @@ export class GenericCalculatorComponent extends BaseComponent implements OnInit, private router: Router, private confirmCloseCalcDialog: MatDialog, private generatePABDialog: MatDialog, - private _elementRef: ElementRef + private _elementRef: ElementRef, + private hotkeysService: HotkeysService ) { super(); this.intlService = ServiceFactory.instance.i18nService; this.formulaireService = ServiceFactory.instance.formulaireService; + + // hotkeys listeners + this.hotkeysService.add(new Hotkey("alt+w", AppComponent.onHotkey(this.closeCalculator, this))); + this.hotkeysService.add(new Hotkey("alt+d", AppComponent.onHotkey(this.cloneCalculator, this))); + this.hotkeysService.add(new Hotkey("alt+enter", AppComponent.onHotkey(this.doCompute, this))); } public get formElements(): FormulaireElement[] { diff --git a/src/app/config.json b/src/app/config.json index 0eb401ba2..5160a729c 100644 --- a/src/app/config.json +++ b/src/app/config.json @@ -4,6 +4,7 @@ "computePrecision": 0.0001, "newtonMaxIterations": 50, "enableNotifications": true, + "enableHotkeys": false, "language": "fr" }, "themes": [ diff --git a/src/app/services/app-setup/app-setup.service.ts b/src/app/services/app-setup/app-setup.service.ts index fc2a72ede..8ac7a2ee7 100644 --- a/src/app/services/app-setup/app-setup.service.ts +++ b/src/app/services/app-setup/app-setup.service.ts @@ -20,6 +20,7 @@ export class ApplicationSetupService extends Observable { public computePrecision = 0.0001; public newtonMaxIterations = 50; public enableNotifications = true; + public enableHotkeys = false; /** * just stores the current language preference, does not transmit it to I18nService, that is @@ -85,6 +86,7 @@ export class ApplicationSetupService extends Observable { this.storage.set(this.LOCAL_STORAGE_PREFIX + "computePrecision", this.computePrecision); this.storage.set(this.LOCAL_STORAGE_PREFIX + "newtonMaxIterations", this.newtonMaxIterations); this.storage.set(this.LOCAL_STORAGE_PREFIX + "enableNotifications", this.enableNotifications); + this.storage.set(this.LOCAL_STORAGE_PREFIX + "enableHotkeys", this.enableHotkeys); this.storage.set(this.LOCAL_STORAGE_PREFIX + "language", this.language); } @@ -127,6 +129,11 @@ export class ApplicationSetupService extends Observable { this.enableNotifications = enableNotifications; loadedKeys.push("enableNotifications"); } + const enableHotkeys = this.storage.get(this.LOCAL_STORAGE_PREFIX + "enableHotkeys"); + if (enableHotkeys !== undefined) { + this.enableHotkeys = enableHotkeys; + loadedKeys.push("enableHotkeys"); + } const language = this.storage.get(this.LOCAL_STORAGE_PREFIX + "language"); if (language !== undefined) { this.language = language; @@ -145,6 +152,7 @@ export class ApplicationSetupService extends Observable { this.computePrecision = data.params.computePrecision; this.newtonMaxIterations = data.params.newtonMaxIterations; this.enableNotifications = data.params.enableNotifications; + this.enableHotkeys = data.params.enableHotkeys; this.language = data.params.language; // load themes for calculators list page this.themes = data.themes; diff --git a/src/locale/messages.en.json b/src/locale/messages.en.json index c107256cb..08c4f8864 100644 --- a/src/locale/messages.en.json +++ b/src/locale/messages.en.json @@ -371,6 +371,7 @@ "INFO_RESULTS_EXPORT_AS_SPREADSHEET": "Export as XLSX", "INFO_SECTIONPARAMETREE_TITRE_COURT": "Param. section", "INFO_SECTIONPARAMETREE_TITRE": "Parametric section", + "INFO_SETUP_ENABLE_HOTKEYS": "Enable keyboard shortcuts", "INFO_SETUP_ENABLE_NOTIFICATIONS": "Enable on-screen notifications", "INFO_SETUP_LANGUAGE": "Language", "INFO_SETUP_NEWTON_MAX_ITER": "Newton iteration limit", diff --git a/src/locale/messages.fr.json b/src/locale/messages.fr.json index 478772b1b..de9d932ff 100644 --- a/src/locale/messages.fr.json +++ b/src/locale/messages.fr.json @@ -370,6 +370,7 @@ "INFO_RESULTS_EXPORT_AS_SPREADSHEET": "Exporter en XLSX", "INFO_SECTIONPARAMETREE_TITRE_COURT": "Sec. param.", "INFO_SECTIONPARAMETREE_TITRE": "Section paramétrée", + "INFO_SETUP_ENABLE_HOTKEYS": "Activer les raccourcis clavier", "INFO_SETUP_ENABLE_NOTIFICATIONS": "Activer les notifications à l'écran", "INFO_SETUP_LANGUAGE": "Langue", "INFO_SETUP_NEWTON_MAX_ITER": "Newton : nombre d'itérations maximum", -- GitLab