--- /dev/null
+<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">×</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>
--- /dev/null
+/**
+ */
+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();
+ }
+}
+
+
/**
- * 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';
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()}
+ });
+ }
}
+
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};
+ });
}
}
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();
}
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.
EgDialogComponent,
EgConfirmDialogComponent,
EgPromptDialogComponent,
- EgAccessKeyDirective
+ EgAccessKeyDirective,
+ EgAccessKeyInfoComponent
],
imports: [
EgCommonModule
EgDialogComponent,
EgConfirmDialogComponent,
EgPromptDialogComponent,
- EgAccessKeyDirective
+ EgAccessKeyDirective,
+ EgAccessKeyInfoComponent
]
})
<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>
<!-- 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>
+
-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/';
this.router.events.subscribe(routeEvent => {
if (routeEvent instanceof NavigationEnd) {
//console.debug(`EgStaffComponent routing to ${routeEvent.url}`);
- this.basicAuthChecks(routeEvent.url);
+ this.preventForbiddenNavigation(routeEvent.url);
}
});
this.route.data.subscribe((data: {staffResolver : any}) => {
// Data fetched via EgStaffResolver is available here.
});
+
+
}
/**
*
* 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;
this.keys.fire(evt);
}
+ /*
+ @ViewChild('egAccessKeyInfo')
+ private keyComponent: EgAccessKeyInfoComponent;
+ */
+
}