Switching to ng2 side menu (#65)

Closes #63
This commit is contained in:
Nejc Zdovc
2016-07-11 17:14:56 +02:00
committed by József Farkas
parent d20101c981
commit 036c2d0a70
26 changed files with 707 additions and 548 deletions

View File

@@ -2,12 +2,16 @@ import { bootstrap } from '@angular/platform-browser-dynamic';
import { MainAppComponent } from './main-app.component';
import {DataProviderService} from './services/data-provider.service';
import {MapperService} from './services/mapper.service';
import { DataProviderService } from './services/data-provider.service';
import { MapperService } from './services/mapper.service';
import { APP_ROUTER_PROVIDERS } from './main-app.routes';
import { LocationStrategy, HashLocationStrategy } from '@angular/common';
process.stdout = require('browser-stdout')();
bootstrap(MainAppComponent, [
DataProviderService,
MapperService
MapperService,
APP_ROUTER_PROVIDERS,
{ provide: LocationStrategy, useClass: HashLocationStrategy }
]).catch(err => console.error(err));

View File

@@ -0,0 +1,37 @@
<template [ngIf]="keymap">
<div class="row">
<h1 class="col-xs-12 pane-title">
<i class="fa fa-keyboard-o"></i>
<span class="keymap__name pane-title__name" contenteditable="true">{{keymap.name}}</span> keymap
(<span class="keymap__abbrev pane-title__abbrev" contenteditable="true">{{keymap.abbreviation}}</span>)
<i class="fa fa-star-o fa-star keymap__is-default"></i>
<i class="glyphicon glyphicon-trash keymap__remove pull-right" title=""
data-toggle="tooltip" data-placement="left" data-original-title="Remove keymap"></i>
</h1>
</div>
<div class="row uhk--wrapper">
<div class="col-xs-12 text-center">
<span class="uhk__layer-switcher--wrapper" data-title="Layers: ">
<button #baseButton type="button" class="btn btn-default btn-lg btn-primary" (click)="selectLayer(0)">
Base
</button>
<button #modButton type="button" class="btn btn-default btn-lg" (click)="selectLayer(1)">
Mod
</button>
<button #fnButton type="button" class="btn btn-default btn-lg" (click)="selectLayer(2)">
Fn
</button>
<button #mouseButton type="button" class="btn btn-default btn-lg" (click)="selectLayer(3)">
Mouse
</button>
</span>
</div>
<div class="keyboard-slider" *ngIf="layers">
<svg-keyboard-popover *ngFor="let layer of layers.elements"
[moduleConfig]="layer.modules.elements"
(animationend)="onKeyboardAnimationEnd($event)"
hidden>
</svg-keyboard-popover>
</div>
</div>
</template>

View File

@@ -0,0 +1,57 @@
:host {
width: 100%;
height: 100%;
display: block;
}
.uhk--wrapper {
height: calc(100% - 95px);
}
button {
margin: 2px;
}
svg-keyboard-popover {
width: 95%;
max-width: 1400px;
position: absolute;
left: 50%;
transform: translateX(-50%);
animation-duration: 400ms;
animation-timing-function: ease-in-out;
}
.keyboard-slider {
height: calc(100% - 45px);
}
.uhk__layer-switcher--wrapper {
margin-bottom: 2rem;
}
@keyframes animate-center-left {
0% {
transform: translateX(-50%);
left: 50%;
}
100% {
transform: translateX(-100%);
left: 0;
}
}
@keyframes animate-center-right {
0% {
transform: translateX(-50%);
left: 50%;
}
100% {
transform: translateX(0%);
left: 100%;
}
}
[hidden] {
display: none;
}

View File

@@ -0,0 +1,138 @@
import { Component, ViewChildren, QueryList, ElementRef, OnInit, AfterViewInit, Renderer } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { SvgKeyboardPopoverComponent } from '../svg-keyboard-popover.component';
import { Layers } from '../../../config-serializer/config-items/Layers';
import { UhkConfigurationService } from '../../services/uhk-configuration.service';
import { Keymap } from '../../../config-serializer/config-items/Keymap';
import { Subscription } from 'rxjs/Subscription';
@Component({
selector: 'keymap',
template: require('./keymap.component.html'),
styles: [require('./keymap.component.scss')],
directives: [SvgKeyboardPopoverComponent],
providers: [UhkConfigurationService]
})
export class KeymapComponent implements OnInit, AfterViewInit {
@ViewChildren('baseButton,modButton,fnButton,mouseButton')
buttonsQueryList: QueryList<ElementRef>;
@ViewChildren(SvgKeyboardPopoverComponent, { read: ElementRef })
keyboardsQueryList: QueryList<ElementRef>;
private buttons: ElementRef[];
private keyboards: ElementRef[];
private selectedLayerIndex: number;
private keymapId: number = 0;
private layers: Layers;
private keymap: Keymap;
private numAnimationInProgress: number;
private subParams: Subscription;
private subQuery: Subscription;
constructor(
private renderer: Renderer,
private uhkConfigurationService: UhkConfigurationService,
private route: ActivatedRoute
) {
this.buttons = [];
this.keyboards = [];
this.selectedLayerIndex = 0;
this.numAnimationInProgress = 0;
}
ngOnInit() {
this.subParams = this.route.params.subscribe(params => {
let id: number = +params['id'];
if (!isNaN(id)) {
this.keymapId = id;
}
this.keymap = this.uhkConfigurationService.getUhkConfiguration().keymaps.elements[this.keymapId];
this.layers = this.keymap.layers;
});
}
ngAfterViewInit() {
this.buttons = this.buttonsQueryList.toArray();
this.afterView();
this.subQuery = this.keyboardsQueryList.changes.subscribe(() => {
this.afterView();
});
}
ngOnDestroy() {
this.subParams.unsubscribe();
this.subQuery.unsubscribe();
}
private afterView() {
this.keyboards = this.keyboardsQueryList.toArray();
this.renderer.setElementAttribute(this.keyboards[0].nativeElement, 'hidden', undefined);
this.renderer.setElementClass(this.buttons[this.selectedLayerIndex].nativeElement, 'btn-primary', false);
this.renderer.setElementClass(this.buttons[0].nativeElement, 'btn-primary', true);
this.selectedLayerIndex = 0;
}
/* tslint:disable:no-unused-variable */
/* selectLayer is used in the template string */
private selectLayer(index: number): void {
/* tslint:enable:no-unused-variable */
if (this.selectedLayerIndex === index || index > this.keyboards.length - 1 || this.numAnimationInProgress > 0) {
return;
}
this.renderer.setElementClass(this.buttons[this.selectedLayerIndex].nativeElement, 'btn-primary', false);
this.renderer.setElementClass(this.buttons[index].nativeElement, 'btn-primary', true);
if (index > this.selectedLayerIndex) {
this.renderer.setElementStyle(
this.keyboards[this.selectedLayerIndex].nativeElement,
'animation-name',
'animate-center-left'
);
this.renderer.setElementStyle(
this.keyboards[index].nativeElement,
'animation-name',
'animate-center-right'
);
this.renderer.setElementStyle(this.keyboards[index].nativeElement, 'animation-direction', 'reverse');
} else {
this.renderer.setElementStyle(
this.keyboards[this.selectedLayerIndex].nativeElement,
'animation-name',
'animate-center-right'
);
this.renderer.setElementStyle(this.keyboards[index].nativeElement, 'animation-name', 'animate-center-left');
this.renderer.setElementStyle(this.keyboards[index].nativeElement, 'animation-direction', 'reverse');
}
this.numAnimationInProgress += 2;
this.renderer.setElementAttribute(this.keyboards[index].nativeElement, 'hidden', undefined);
this.selectedLayerIndex = index;
}
/* tslint:disable:no-unused-variable */
/* onKeyboardAnimationEnd is used in the template string */
private onKeyboardAnimationEnd(event: AnimationEvent) {
/* tslint:enable:no-unused-variable */
let animationNameTokens: string[] = event.animationName.split('-');
let animationFrom: string = animationNameTokens[1];
let animationTo: string = animationNameTokens[2];
if ((<HTMLElement> event.target).style.animationDirection === 'reverse') {
animationFrom = animationNameTokens[2];
animationTo = animationNameTokens[1];
this.renderer.setElementStyle(event.target, 'animation-direction', undefined);
}
--this.numAnimationInProgress;
this.renderer.setElementStyle(event.target, 'animation-name', undefined);
this.renderer.setElementAttribute(event.target, 'hidden', (animationTo === 'center') ? undefined : '');
}
}

View File

@@ -0,0 +1,18 @@
import { RouterConfig } from '@angular/router';
import { KeymapComponent } from './keymap.component';
export const keymapRoutes: RouterConfig = [
{
path: '',
redirectTo: '/keymap',
pathMatch: 'full'
},
{
path: 'keymap',
component: KeymapComponent
},
{
path: 'keymap/:id',
component: KeymapComponent
}
];

View File

@@ -0,0 +1,9 @@
:host {
height: 100%;
width: 100%;
}
iframe {
height: 100vh;
width: 100%;
}

View File

@@ -0,0 +1,26 @@
import { Component } from '@angular/core';
import { DomSanitizationService, SafeResourceUrl } from '@angular/platform-browser';
import {ActivatedRoute} from '@angular/router';
@Component({
selector: 'legacy',
template:
`
<iframe [src]="safeLink" frameborder="0" scrolling="no"></iframe>
`,
styles: [require('./legacy-loader.component.scss')]
})
export class LegacyLoaderComponent {
private safeLink: SafeResourceUrl;
constructor(private sanitationService: DomSanitizationService, private route: ActivatedRoute) {
}
ngOnInit() {
this.route.params.subscribe(params => {
if (params['id']) {
this.safeLink = this.sanitationService.bypassSecurityTrustResourceUrl(params['id'] + 'Legacy.html');
}
});
}
}

View File

@@ -0,0 +1,9 @@
import { RouterConfig } from '@angular/router';
import {LegacyLoaderComponent} from './legacy-loader.component';
export const legacyRoutes: RouterConfig = [
{
path: 'legacy/:id',
component: LegacyLoaderComponent
}
];

View File

@@ -0,0 +1 @@
Macro

View File

@@ -0,0 +1,12 @@
import { Component } from '@angular/core';
@Component({
selector: 'macro',
template: require('./macro.component.html'),
styles: [require('./macro.component.scss')]
})
export class MacroComponent {
constructor() {
}
}

View File

@@ -0,0 +1,13 @@
import { RouterConfig } from '@angular/router';
import { MacroComponent } from './macro.component';
export const macroRoutes: RouterConfig = [
{
path: 'macro',
component: MacroComponent
},
{
path: 'macro/:id',
component: MacroComponent
}
];

View File

@@ -0,0 +1,87 @@
<ul class="menu--top">
<li class="sidebar__level-1--item">
<div class="sidebar__level-1">
<i class="fa fa-keyboard-o"></i> Keymaps
<a href="#" class="btn btn-default pull-right btn-sm">
<i class="fa fa-plus"></i>
</a>
<i class="fa fa-chevron-up pull-right" (click)="toggleHide($event, keymapElement)"></i>
</div>
<ul #keymapElement>
<li *ngFor="let keymap of keymaps" class="sidebar__level-2--item">
<div class="sidebar__level-2">
<a [routerLink]="['/keymap', keymap.id]">{{keymap.name}}</a>
<i class="fa fa-star pull-right"></i>
</div>
</li>
</ul>
</li>
<li class="sidebar__level-1--item">
<div class="sidebar__level-1">
<i class="fa fa-play"></i> Macros
<a href="#" class="btn btn-default pull-right btn-sm">
<i class="fa fa-plus"></i>
</a>
<i class="fa fa-chevron-up pull-right" (click)="toggleHide($event, macroElement)"></i>
</div>
<ul #macroElement>
<li *ngFor="let macro of macros" class="sidebar__level-2--item">
<div class="sidebar__level-2">
<a [routerLink]="['/macro', macro.id]">{{macro.name}}</a>
<i class="fa fa-star pull-right"></i>
</div>
</li>
</ul>
</li>
<li class="sidebar__level-1--item">
<div class="sidebar__level-1">
<i class="fa fa-puzzle-piece"></i> Add-on modules
<i class="fa fa-chevron-up pull-right" (click)="toggleHide($event, addonElement)"></i>
</div>
<ul #addonElement>
<li class="sidebar__level-2--item" data-name="Key cluster" data-abbrev="">
<div class="sidebar__level-2">
Key cluster
</div>
</li>
<li class="sidebar__level-2--item" data-name="Trackball" data-abbrev="">
<div class="sidebar__level-2">
Trackball
</div>
</li>
<li class="sidebar__level-2--item" data-name="Toucpad" data-abbrev="">
<div class="sidebar__level-2">
Toucpad
</div>
</li>
<li class="sidebar__level-2--item" data-name="Trackpoint" data-abbrev="">
<div class="sidebar__level-2">
Trackpoint
</div>
</li>
</ul>
</li>
<li class="sidebar__level-1--item">
<div class="sidebar__level-1">
<i class="fa fa-exclamation-triangle "></i> Legacy
<i class="fa fa-chevron-up pull-right" (click)="toggleHide($event, legacyElement)"></i>
</div>
<ul #legacyElement>
<li class="sidebar__level-2--item" data-name="Keymap legacy" data-abbrev="QTY">
<div class="sidebar__level-2">
<a [routerLink]="['/legacy', 'keymap']">Keymap HTML</a>
</div>
</li>
<li class="sidebar__level-2--item " data-name="Macro legacy" data-abbrev="DVR">
<div class="sidebar__level-2">
<a [routerLink]="['/legacy', 'macro']">Macro</a>
</div>
</li>
</ul>
</li>
</ul>
<ul class="menu--bottom">
<li class="sidebar__level-1--item">
<div class="sidebar__level-1"><i class="fa fa-gear"></i> Settings</div>
</li>
</ul>

View File

@@ -0,0 +1,23 @@
:host {
background-color: #f5f5f5;
border-right: 1px solid #ccc;
position: fixed;
overflow-y: auto;
width: 250px;
height: 100%;
}
a {
color: #333;
}
ul ul {
max-height: 200px;
transition: max-height 0.5s ease-in;
overflow: hidden;
}
ul ul.slide-up {
max-height: 0;
transition: max-height 0.5s ease-out;
}

View File

@@ -0,0 +1,34 @@
import { Component, OnInit } from '@angular/core';
import { Keymap } from '../../../config-serializer/config-items/Keymap';
import { UhkConfigurationService } from '../../services/uhk-configuration.service';
import { Macro } from '../../../config-serializer/config-items/Macro';
import { ROUTER_DIRECTIVES } from '@angular/router';
@Component({
selector: 'side-menu',
template: require('./side-menu.component.html'),
styles: [require('./side-menu.component.scss')],
providers: [UhkConfigurationService],
directives: [ROUTER_DIRECTIVES]
})
export class SideMenuComponent implements OnInit {
private keymaps: Keymap[];
private macros: Macro[];
constructor(private uhkConfigurationService: UhkConfigurationService) {
}
ngOnInit() {
this.keymaps = this.uhkConfigurationService.getUhkConfiguration().keymaps.elements;
this.macros = this.uhkConfigurationService.getUhkConfiguration().macros.elements;
}
private toggleHide(event: Event, view: Element) {
let header: DOMTokenList = (<Element> event.target).classList;
view.classList.toggle('slide-up');
header.toggle('fa-chevron-up');
header.toggle('fa-chevron-down');
}
}

View File

@@ -0,0 +1,4 @@
<side-menu></side-menu>
<div id="main-content" class="split split-horizontal container-fluid main-content">
<router-outlet></router-outlet>
</div>

View File

@@ -1,58 +1,10 @@
:host {
display: flex;
flex-direction: column;
display: block;
height: 100vh;
width: 100%;
overflow: hidden;
> div:first-child {
display: flex;
flex: 1;
align-items: center;
justify-content: center;
}
}
button {
margin: 2px;
.main-content {
height: 100%;
}
:host > div:last-child {
display: flex;
flex: 5;
position: relative;
}
:host > div:last-child > svg-keyboard-popover {
width: 80%;
position: absolute;
left: 50%;
transform: translateX(-50%);
animation-duration: 400ms;
animation-timing-function: ease-in-out;
}
@keyframes animate-center-left {
0% {
transform: translateX(-50%);
left: 50%;
}
100% {
transform: translateX(-100%);
left: 0%;
}
}
@keyframes animate-center-right {
0% {
transform: translateX(-50%);
left: 50%;
}
100% {
transform: translateX(0%);
left: 100%;
}
}
[hidden] {
display: none;
}

View File

@@ -1,131 +1,16 @@
import { Component, OnInit, AfterViewInit, Renderer, ViewChildren, QueryList, ElementRef} from '@angular/core';
import { Component } from '@angular/core';
import { ROUTER_DIRECTIVES } from '@angular/router';
import {Layers} from '../config-serializer/config-items/Layers';
import {SvgKeyboardPopoverComponent} from './components/svg-keyboard-popover.component';
import {UhkConfigurationService} from './services/uhk-configuration.service';
import { SideMenuComponent } from './components/sidemenu/side-menu.component';
@Component({
moduleId: module.id,
selector: 'main-app',
template:
` <div>
<button #baseButton type="button" class="btn btn-default btn-lg btn-primary" (click)="selectLayer(0)">
Base
</button>
<button #modButton type="button" class="btn btn-default btn-lg" (click)="selectLayer(1)">
Mod
</button>
<button #fnButton type="button" class="btn btn-default btn-lg" (click)="selectLayer(2)">
Fn
</button>
<button #mouseButton type="button" class="btn btn-default btn-lg" (click)="selectLayer(3)">
Mouse
</button>
</div>
<div>
<svg-keyboard-popover *ngFor="let layer of layers.elements"
[moduleConfig]="layer.modules.elements"
(animationend)="onKeyboardAnimationEnd($event)"
hidden>
</svg-keyboard-popover>
</div>
`,
template: require('./main-app.component.html'),
styles: [require('./main-app.component.scss')],
directives: [SvgKeyboardPopoverComponent],
providers: [UhkConfigurationService]
directives: [SideMenuComponent, ROUTER_DIRECTIVES]
})
export class MainAppComponent implements OnInit, AfterViewInit {
@ViewChildren('baseButton,modButton,fnButton,mouseButton')
buttonsQueryList: QueryList<ElementRef>;
@ViewChildren(SvgKeyboardPopoverComponent, { read: ElementRef })
keyboardsQueryList: QueryList<ElementRef>;
private buttons: ElementRef[];
private keyboards: ElementRef[];
private selectedLayerIndex: number;
private layers: Layers;
private numAnimationInProgress: number;
constructor(
private renderer: Renderer,
private uhkConfigurationService: UhkConfigurationService
) {
this.buttons = [];
this.keyboards = [];
this.selectedLayerIndex = -1;
this.numAnimationInProgress = 0;
export class MainAppComponent {
constructor() {
}
ngOnInit() {
this.layers = this.uhkConfigurationService.getUhkConfiguration().keymaps.elements[0].layers;
}
ngAfterViewInit() {
this.buttons = this.buttonsQueryList.toArray();
this.keyboards = this.keyboardsQueryList.toArray();
this.selectedLayerIndex = 0;
this.renderer.setElementAttribute(this.keyboards[0].nativeElement, 'hidden', undefined);
}
/* tslint:disable:no-unused-variable */
/* selectLayer is used in the template string */
private selectLayer(index: number): void {
/* tslint:enable:no-unused-variable */
if (this.selectedLayerIndex === index || index > this.keyboards.length - 1 || this.numAnimationInProgress > 0) {
return;
}
this.renderer.setElementClass(this.buttons[this.selectedLayerIndex].nativeElement, 'btn-primary', false);
this.renderer.setElementClass(this.buttons[index].nativeElement, 'btn-primary', true);
if (index > this.selectedLayerIndex) {
this.renderer.setElementStyle(
this.keyboards[this.selectedLayerIndex].nativeElement,
'animation-name',
'animate-center-left'
);
this.renderer.setElementStyle(
this.keyboards[index].nativeElement,
'animation-name',
'animate-center-right'
);
this.renderer.setElementStyle(this.keyboards[index].nativeElement, 'animation-direction', 'reverse');
} else {
this.renderer.setElementStyle(
this.keyboards[this.selectedLayerIndex].nativeElement,
'animation-name',
'animate-center-right'
);
this.renderer.setElementStyle(this.keyboards[index].nativeElement, 'animation-name', 'animate-center-left');
this.renderer.setElementStyle(this.keyboards[index].nativeElement, 'animation-direction', 'reverse');
}
this.numAnimationInProgress += 2;
this.renderer.setElementAttribute(this.keyboards[index].nativeElement, 'hidden', undefined);
this.selectedLayerIndex = index;
}
/* tslint:disable:no-unused-variable */
/* onKeyboardAnimationEnd is used in the template string */
private onKeyboardAnimationEnd(event: AnimationEvent) {
/* tslint:enable:no-unused-variable */
let animationNameTokens: string[] = event.animationName.split('-');
let animationFrom: string = animationNameTokens[1];
let animationTo: string = animationNameTokens[2];
if ((<HTMLElement> event.target).style.animationDirection === 'reverse') {
animationFrom = animationNameTokens[2];
animationTo = animationNameTokens[1];
this.renderer.setElementStyle(event.target, 'animation-direction', undefined);
}
--this.numAnimationInProgress;
this.renderer.setElementStyle(event.target, 'animation-name', undefined);
this.renderer.setElementAttribute(event.target, 'hidden', (animationTo === 'center') ? undefined : '');
}
}

14
src/main-app.routes.ts Normal file
View File

@@ -0,0 +1,14 @@
import { provideRouter, RouterConfig } from '@angular/router';
import { keymapRoutes } from './components/keymap/keymap.routes';
import { macroRoutes } from './components/macro/macro.routes';
import {legacyRoutes} from './components/legacy/legacy-loader.routes';
export const routes: RouterConfig = [
...keymapRoutes,
...macroRoutes,
...legacyRoutes
];
export const APP_ROUTER_PROVIDERS = [
provideRouter(routes)
];