diff --git a/package-lock.json b/package-lock.json index 46e495dd604459530f0e9698bd6115dd787f010b..4c9a9bbd2201aa56273ba5685f7118157eb44560 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 54d37af7ceda3e80925603771d7b7ec53dec98dd..2fd54de46848af8a57f2a43cb6b07ea46290eb5d 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 b735b02bff351e9a70bf8dd7823f1523b5239d31..64b3c9ae9aed625e98b3ccd1b95de173c97fc380 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 b0dd4500e69ddfec6d41b0fd65d297e527dee56c..9121bbd12ec12a6cd440596e64871d6a1f0d4f26 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 e1fb55a8b585cc70faab8dd41fb6521887f4fa76..26158718f4ca443d0d91d523784d5c6df4e0f9f7 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 a8bef4b4c5aee90cc5e2896a7693006d47ae78df..d7f7a0ae6f1e78929fe624215257e40142ae4bf1 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 c1eafaed8925e114a095a6a6f36b0a8d870e14b7..dff9caef6e82d7e94a7d98d0e8f3df92ad38c512 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 ef034b6f0f572c0b6706935c258280156eacc91b..7b4ae6960f26c69bb61a6254a2383ef717742489 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 0eb401ba2100bdf2a98343f9a05da72500c6ca74..5160a729c5b169892b9d6c0eacebdbbdaf6e813e 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 fc2a72ede92919a67479ed8afc196f3c4a1cde3f..8ac7a2ee71028fa71f02f2d9fd75a43aff9e4a5f 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 c107256cbfd39792eba921b46a626fec44fcb58b..08c4f88647b1887523977625fd2337dab092ed5a 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 478772b1b2adb3016778d987eadedc9793d8c7f5..de9d932ff3245b7da61fe157b704906b91aeb4ca 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",