LP#1626157 more accesskeys
authorBill Erickson <berickxx@gmail.com>
Sun, 15 Apr 2018 15:00:44 +0000 (15:00 +0000)
committerBill Erickson <berickxx@gmail.com>
Sun, 15 Apr 2018 15:00:44 +0000 (15:00 +0000)
Signed-off-by: Bill Erickson <berickxx@gmail.com>
Open-ILS/src/eg2/src/app/share/accesskey/accesskey-info.component.html [new file with mode: 0644]
Open-ILS/src/eg2/src/app/share/accesskey/accesskey-info.component.ts [new file with mode: 0644]
Open-ILS/src/eg2/src/app/share/accesskey/accesskey.directive.ts
Open-ILS/src/eg2/src/app/share/accesskey/accesskey.service.ts
Open-ILS/src/eg2/src/app/share/dialog/dialog.component.ts
Open-ILS/src/eg2/src/app/staff/common.module.ts
Open-ILS/src/eg2/src/app/staff/nav.component.html
Open-ILS/src/eg2/src/app/staff/staff.component.html
Open-ILS/src/eg2/src/app/staff/staff.component.ts

diff --git a/Open-ILS/src/eg2/src/app/share/accesskey/accesskey-info.component.html b/Open-ILS/src/eg2/src/app/share/accesskey/accesskey-info.component.html
new file mode 100644 (file)
index 0000000..b68129b
--- /dev/null
@@ -0,0 +1,24 @@
+<ng-template #dialogContent>
+  <div class="modal-header bg-info">
+    <h4 class="modal-title" i18n>Access Key Assignments</h4>
+    <button type="button" class="close" 
+      i18n-aria-label aria-label="Close" 
+      (click)="dismiss('cross_click')">
+      <span aria-hidden="true">&times;</span>
+    </button>
+  </div>
+  <div class="modal-body">
+    <div class="row border-bottom">
+      <div class="col-3 p-1 border-right text-center" i18n>Command</div>
+      <div class="col-9 p-1" i18n>Action</div>
+    </div>
+    <div class="row border-bottom" *ngFor="let a of assignments()">
+      <div class="col-3 p-1 border-right text-center">{{a.key}}</div>
+      <div class="col-9 p-1">{{a.desc}}</div>
+    </div>
+  </div>
+  <div class="modal-footer">
+    <button type="button" class="btn btn-success" 
+      (click)="close(promptValue)" i18n>Close</button>
+  </div>
+</ng-template>
diff --git a/Open-ILS/src/eg2/src/app/share/accesskey/accesskey-info.component.ts b/Open-ILS/src/eg2/src/app/share/accesskey/accesskey-info.component.ts
new file mode 100644 (file)
index 0000000..d74d7a1
--- /dev/null
@@ -0,0 +1,26 @@
+/**
+ */
+import {Component, Input, OnInit} from '@angular/core';
+import {EgAccessKeyService} from '@eg/share/accesskey/accesskey.service';
+import {EgDialogComponent} from '@eg/share/dialog/dialog.component';
+import {NgbModal} from '@ng-bootstrap/ng-bootstrap';
+
+@Component({
+  selector: 'eg-accesskey-info',
+  templateUrl: './accesskey-info.component.html'
+})
+export class EgAccessKeyInfoComponent extends EgDialogComponent {
+
+       constructor(
+        // NgbModal import required for subclassing with a constructor.
+        private modal: NgbModal,
+        private keyService: EgAccessKeyService) {
+        super(modal);
+    }
+
+    assignments(): any[] {
+        return this.keyService.infoIze();
+    }
+}
+
+
index 440b312..db75b4f 100644 (file)
@@ -1,8 +1,13 @@
 /**
- * Allows for in-template hotkey configuration.  Reads hotkey maps and 
- * passes them off to the hotkey service.
+ * Assign access keys from within templates via <a> tags.
+ * Both href and routerLink attributes are supported.
  *
- * Listens and responds to global keyboard events.
+ *   <a 
+ *     routerLink="/staff/splash"                                           
+ *     egAccessKey 
+ *     keySpec="alt+h" i18n-keySpec
+ *     keyDesc="My Description" 18n-keyDesc              
+ *   >
  */
 import {Directive, ElementRef, Input, OnInit} from '@angular/core';
 import {EgAccessKeyService} from '@eg/share/accesskey/accesskey.service';
@@ -11,19 +16,26 @@ import {EgAccessKeyService} from '@eg/share/accesskey/accesskey.service';
   selector: '[egAccessKey]'
 })
 export class EgAccessKeyDirective implements OnInit {
+
+    @Input() keySpec: string;
+    @Input() keyDesc: string;
     
        constructor(
         private elm: ElementRef,
         private keyService: EgAccessKeyService
     ) { }
 
-    @Input() keySpec: string;
-    @Input() keyDesc: string;
 
     ngOnInit() {
-        
-        console.debug(`Assigning access key '${this.keySpec}' => ${this.keyDesc}`);
-    }
+        console.debug(
+            `EgAccessKey assigning '${this.keySpec}' => ${this.keyDesc}`);
 
+        this.keyService.assign({
+            key: this.keySpec, 
+            desc: this.keyDesc,
+            action: () => {this.elm.nativeElement.click()}
+        });
+    }
 }
 
+
index 82f7395..e0e1b0b 100644 (file)
@@ -2,17 +2,62 @@ import {Injectable, EventEmitter, HostListener} from '@angular/core';
 
 export interface EgAccessKeyAssignment {
     key: string,
-    action: () => void
+    desc: string,
+    action: Function
 };
 
 @Injectable()
 export class EgAccessKeyService {
 
-    handlers: {[key: string] : Function} = {};
+    // Assignments stored as an array with most recently assigned
+    // items toward the front.  Most recent items have precedence.
+    assignments: EgAccessKeyAssignment[] = [];
 
     constructor() {}
 
+    assign(assn: EgAccessKeyAssignment): void {
+        this.assignments.unshift(assn);
+    }
+
+    /**
+     * Compress a set of single-fire keyboard events into single 
+     * string.  For example:  Control and 't' becomes 'ctrl+t'.
+     */
+    compressKeys(evt: KeyboardEvent): string {
+
+        let s = '';
+        if (evt.ctrlKey || evt.metaKey) s += 'ctrl+';
+        if (evt.altKey) s += 'alt+';
+        s += String.fromCharCode(evt.keyCode).toLowerCase();
+
+        return s;
+    }
+
+    /**
+     * Checks for a key assignment and fires the assigned action.
+     */
     fire(evt: KeyboardEvent): void {
+        let keySpec = this.compressKeys(evt);
+        for (let i in this.assignments) { // for-loop to exit early
+            if (keySpec == this.assignments[i].key) {
+                let assign = this.assignments[i];
+                console.debug(`EgAccessKey assignment found for ${assign.key}`);
+                assign.action();
+                evt.preventDefault();
+                return;
+            }
+        }
+    }
+
+    /**
+     * Returns a simplified key assignment list containing just
+     * the key spec and the description.  Useful for inspecting 
+     * without exposing the actions.
+     */
+    infoIze(): any[] {
+        return this.assignments.map(a => {
+            return {key: a.key, desc: a.desc};
+        });
     }
 
 }
index 397a6db..4389eb6 100644 (file)
@@ -28,7 +28,9 @@ export class EgDialogComponent {
     open(): Promise<any> {
 
         if (this.modalRef !== null) {
-            console.error('Dismissing existing dialog!');
+            //This can happen when a dialog is dismissed by clicking
+            //outside of the modal.  Avoid treating it like an error.
+            //console.debug('Dismissing existing dialog!');
             this.dismiss();
         }
 
index cddd063..82529f4 100644 (file)
@@ -7,6 +7,7 @@ import {EgConfirmDialogComponent} from '@eg/share/dialog/confirm.component';
 import {EgPromptDialogComponent} from '@eg/share/dialog/prompt.component';
 import {EgAccessKeyDirective} from '@eg/share/accesskey/accesskey.directive';
 import {EgAccessKeyService} from '@eg/share/accesskey/accesskey.service';
+import {EgAccessKeyInfoComponent} from '@eg/share/accesskey/accesskey-info.component';
 
 /**
  * Imports the EG common modules and adds modules common to all staff UI's.
@@ -19,7 +20,8 @@ import {EgAccessKeyService} from '@eg/share/accesskey/accesskey.service';
     EgDialogComponent,
     EgConfirmDialogComponent,
     EgPromptDialogComponent,
-    EgAccessKeyDirective
+    EgAccessKeyDirective,
+    EgAccessKeyInfoComponent
   ],
   imports: [
     EgCommonModule
@@ -31,7 +33,8 @@ import {EgAccessKeyService} from '@eg/share/accesskey/accesskey.service';
     EgDialogComponent,
     EgConfirmDialogComponent,
     EgPromptDialogComponent,
-    EgAccessKeyDirective
+    EgAccessKeyDirective,
+    EgAccessKeyInfoComponent
   ]
 })
 
index 803e173..9b5d9c2 100644 (file)
@@ -2,7 +2,11 @@
   <div class="collapse navbar-collapse">
     <div class="navbar-nav">
       <div class="nav-item">
-        <a i18n class="nav-link with-material-icon" routerLink="/staff/splash">
+        <a i18n class="nav-link with-material-icon" 
+          routerLink="/staff/splash"
+          egAccessKey keySpec="alt+h" keyDesc="My Description" 
+          i18n-keySpec i18n-keyDesc>
+
           <span class="material-icons">home</span>
         </a>
       </div>
index 5f92ef0..0e94fa6 100644 (file)
@@ -1,15 +1,17 @@
 <!-- top navigation bar -->
 <eg-staff-nav-bar></eg-staff-nav-bar>
 
-
 <div id='staff-content-container'>
   <!-- page content -->
   <router-outlet></router-outlet>
 </div>
 
-<div>
-    <b>testing</b>
-    <div egAccessKey keySpec="alt+s" keyDesc="My Description" i18n-keySpec i18n-keyDesc></div>
-    <div egAccessKey keySpec="alt+t" keyDesc="My Description 2"></div>
-</div>
+<!-- EgAccessKey Info Panel -->
+<eg-accesskey-info #egAccessKeyInfo></eg-accesskey-info>
+<a egAccessKey 
+    keySpec="ctrl+h" i18n-keySpec
+    keyDesc="Show Info Dialog" i18n-keyDesc
+    (click)="egAccessKeyInfo.open()">
+</a>
+
 
index 505b21d..c8daf73 100644 (file)
@@ -1,8 +1,10 @@
-import {Component, OnInit, NgZone, HostListener} from '@angular/core';
+import {Component, OnInit, NgZone, HostListener/*, ViewChild*/} from '@angular/core';
 import {Router, ActivatedRoute, NavigationEnd} from '@angular/router';
 import {EgAuthService, EgAuthWsState} from '@eg/core/auth.service';
 import {EgNetService} from '@eg/core/net.service';
 import {EgAccessKeyService} from '@eg/share/accesskey/accesskey.service';
+import {EgAccessKeyInfoComponent} 
+    from '@eg/share/accesskey/accesskey-info.component';
 
 const LOGIN_PATH = '/staff/login';
 const WS_BASE_PATH = '/staff/admin/workstation/workstations/';
@@ -32,7 +34,7 @@ export class EgStaffComponent implements OnInit {
         this.router.events.subscribe(routeEvent => {
             if (routeEvent instanceof NavigationEnd) {
                 //console.debug(`EgStaffComponent routing to ${routeEvent.url}`);
-                this.basicAuthChecks(routeEvent.url);
+                this.preventForbiddenNavigation(routeEvent.url);
             }
         });
 
@@ -60,6 +62,8 @@ export class EgStaffComponent implements OnInit {
         this.route.data.subscribe((data: {staffResolver : any}) => {
             // Data fetched via EgStaffResolver is available here.
         });
+
+
     }
 
     /**
@@ -71,9 +75,10 @@ export class EgStaffComponent implements OnInit {
      *
      * This does not verify auth tokens with the server on every route, 
      * because that would be overkill.  This is only here to keep
-     * people boxed in.
+     * people boxed in with their authenication state was already
+     * known to be less then 100%.
      */
-    basicAuthChecks(url: string): void {
+    preventForbiddenNavigation(url: string): void {
 
         // No auth checks needed for login page.
         if (url.startsWith(LOGIN_PATH)) return;
@@ -97,6 +102,11 @@ export class EgStaffComponent implements OnInit {
         this.keys.fire(evt);
     }
 
+    /*
+    @ViewChild('egAccessKeyInfo') 
+    private keyComponent: EgAccessKeyInfoComponent;
+    */
+
 }