diff --git a/package.json b/package.json index 47806f1..b61168a 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,7 @@ "reflect-metadata": "0.1.2", "rimraf": "^2.5.1", "tslint": "^3.4.0", - "typescript": "^1.7.3", + "typescript": "^1.8.7", "typings": "^0.6.6", "zone.js": "0.5.15" }, diff --git a/src/index.ts b/src/index.ts index 64eaacc..4c63c94 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,2 +1,3 @@ export * from './store'; export {LogMonitor} from './monitors/log-monitor'; +export * from './monitors/dock-monitor'; diff --git a/src/monitors/dock-monitor/actions.ts b/src/monitors/dock-monitor/actions.ts new file mode 100644 index 0000000..2505675 --- /dev/null +++ b/src/monitors/dock-monitor/actions.ts @@ -0,0 +1,26 @@ +import {Injectable} from 'angular2/core'; +import {Action} from '@ngrx/store'; + + +@Injectable() +export class DockActions{ + static TOGGLE_VISIBILITY = '@@redux-devtools-log-monitor/TOGGLE_VISIBILITY'; + toggleVisibility(): Action { + return { type: DockActions.TOGGLE_VISIBILITY }; + } + + static CHANGE_POSITION = '@@redux-devtools-log-monitor/CHANGE_POSITION'; + changePosition(): Action { + return { type: DockActions.CHANGE_POSITION }; + } + + static CHANGE_SIZE = '@@redux-devtools-log-monitor/CHANGE_SIZE'; + changeSize(size: number): Action { + return { type: DockActions.CHANGE_SIZE, payload: size }; + } + + static CHANGE_MONITOR = '@@redux-devtools-log-monitor/CHANGE_MONITOR'; + changeMonitor(): Action { + return { type: DockActions.CHANGE_MONITOR }; + } +} diff --git a/src/monitors/dock-monitor/commander.ts b/src/monitors/dock-monitor/commander.ts new file mode 100644 index 0000000..c0c2d08 --- /dev/null +++ b/src/monitors/dock-monitor/commander.ts @@ -0,0 +1,94 @@ +import 'rxjs/add/operator/filter'; +import 'rxjs/add/operator/map'; +import {Component, Input, Output, Injectable, Renderer} from 'angular2/core'; +import {Observable} from 'rxjs/Observable'; +import {Subject} from 'rxjs/Subject'; +import {Subscriber} from 'rxjs/Subscriber'; + +import {keycodes} from './keycodes'; + +export interface ParsedCommand{ + name?: string; + ctrl: boolean; + meta: boolean; + shift: boolean; + alt: boolean; + sequence?: string; +} + +@Component({ + selector: 'ngrx-commander', + template: '', + styles: [':host{ display: none }'], + host: { + '(document.keydown)': 'keydown$.next($event)' + } +}) +export class Commander{ + private keydown$ = new Subject(); + private _ignoreTags = ['INPUT', 'SELECT', 'TEXTAREA']; + + constructor(private _renderer: Renderer){ } + + @Input() command: string; + @Output() press = this.keydown$ + .filter(e => this._ignoreTags.indexOf((e.target as HTMLElement).tagName) < 0) + .filter(e => !((e.target as HTMLElement).isContentEditable)) + .filter(e => { + const command = this.parseCommand(this.command); + + if (!command) { + return false; + } + + const charCode = e.keyCode || e.which; + const char = String.fromCharCode(charCode); + return command.name.toUpperCase() === char.toUpperCase() && + command.alt === e.altKey && + command.ctrl === e.ctrlKey && + command.meta === e.metaKey && + command.shift === e.shiftKey; + }) + .map(e => { + e.preventDefault(); + + return { command: this.command }; + }); + + parseCommand(s: string): ParsedCommand { + var keyString = s.trim().toLowerCase(); + + if ( !/^(ctrl-|shift-|alt-|meta-){0,4}\w+$/.test(keyString) ){ + throw new Error('The string to parse needs to be of the format "c", "ctrl-c", "shift-ctrl-c".'); + } + + const parts = keyString.split('-'); + const key: ParsedCommand = { + ctrl: false, + meta: false, + shift: false, + alt: false + }; + + let c; + + key.name = parts.pop(); + + while((c = parts.pop())) { + key[c] = true; + } + + if(key.ctrl) { + key.sequence = keycodes.ctrl[key.name] || key.name; + } + else { + key.sequence = keycodes.nomod[key.name] || key.name; + } + + if (key.shift && key.sequence && key.sequence.length === 1) { + key.sequence = key.sequence.toUpperCase(); + } + + return key; + } +} diff --git a/src/monitors/dock-monitor/dock-monitor.ts b/src/monitors/dock-monitor/dock-monitor.ts new file mode 100644 index 0000000..7e5f643 --- /dev/null +++ b/src/monitors/dock-monitor/dock-monitor.ts @@ -0,0 +1,59 @@ +import 'rxjs/add/observable/merge'; +import 'rxjs/add/operator/filter'; +import 'rxjs/add/operator/map'; +import 'rxjs/add/operator/distinctUntilChanged'; +import {StoreDevtools} from '../../store/devtools'; +import {Component, ChangeDetectionStrategy} from 'angular2/core'; +import {Subject} from 'rxjs/Subject'; +import {Observable} from 'rxjs/Observable'; +import {Subscription} from 'rxjs/Subscription'; + +import {DockState} from './reducer'; +import {Commander} from './commander'; +import {Dock} from './dock'; +import {DockActions} from './actions'; + + +@Component({ + selector: 'dock-monitor', + changeDetection: ChangeDetectionStrategy.OnPush, + directives: [ Dock, Commander ], + providers: [ DockActions ], + template: ` + + + + + + + ` +}) +export class DockMonitor { + constructor( + private tools: StoreDevtools, + private actions: DockActions, + private commander: Commander + ){ + Observable + .merge(this.toggleAction$, this.positionAction$) + .subscribe(this.tools); + } + + private state$ = this.tools.liftedState.map(s => s.monitorState.dock); + private visible$ = this.state$.map(s => s.visible).distinctUntilChanged(); + private position$ = this.state$.map(s => s.position).distinctUntilChanged(); + private size$ = this.state$.map(s => s.size).distinctUntilChanged(); + + private toggleCommand = 'ctrl-h'; + private toggle$ = new Subject(); + private toggleAction$ = this.toggle$ + .map(() => this.actions.toggleVisibility()); + + private positionCommand = 'ctrl-m'; + private changePosition$ = new Subject(); + private positionAction$ = this.changePosition$ + .map(() => this.actions.changePosition()); +} diff --git a/src/monitors/dock-monitor/dock.ts b/src/monitors/dock-monitor/dock.ts new file mode 100644 index 0000000..9c57a1a --- /dev/null +++ b/src/monitors/dock-monitor/dock.ts @@ -0,0 +1,92 @@ +import {Component, HostListener, HostBinding, Input} from 'angular2/core'; +import {PositionsType} from './reducer'; + + +@Component({ + selector: 'ngrx-dock', + template: ` +
+
+ +
+
+ `, + styles: [` + :host { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + transition: all 0.3s; + z-index: 9999; + } + + .dock { + position: absolute; + z-index: 1; + box-shadow: 0 0 4px rgba(0, 0, 0, 0.3); + background-color: white; + left: 0; + top: 0; + width: 100%; + height: 100%; + } + + .dock-content { + width: 100%; + height: 100%; + overflow: auto; + } + `] +}) +export class Dock{ + @Input() position: PositionsType = 'right'; + @Input() size: number = 0.3; + @Input() visible: boolean = true; + + get absoluteSize(){ + return `${100 * this.size}%`; + } + + get restSize(){ + return `${100 - (100 * this.size)}%`; + } + + @HostBinding('style.left') get leftPosition(){ + return this.calculatePosition('left', 'right'); + } + + @HostBinding('style.right') get rightPosition(){ + return this.calculatePosition('right', 'left'); + } + + @HostBinding('style.top') get topPosition(){ + return this.calculatePosition('top', 'bottom'); + } + + @HostBinding('style.bottom') get bottomPosition(){ + return this.calculatePosition('bottom', 'top'); + } + + calculatePosition(primary: PositionsType, secondary: PositionsType) { + if(this.visible){ + switch (this.position) { + case secondary: + return this.restSize; + default: + return '0%'; + } + } + else { + switch (this.position) { + case primary: + return `-${this.absoluteSize}`; + case secondary: + return '100%'; + default: + return '0%'; + } + } + } +} diff --git a/src/monitors/dock-monitor/keycodes.ts b/src/monitors/dock-monitor/keycodes.ts new file mode 100644 index 0000000..1d31d7d --- /dev/null +++ b/src/monitors/dock-monitor/keycodes.ts @@ -0,0 +1,43 @@ +// Most of these are according to this table: http://www.ssicom.org/js/x171166.htm +// However where nodejs readline diverges, they are adjusted to conform to it +export const keycodes = { + nomod: { + escape: '\u001b', + space: ' ' // actually '\u0020' + }, + ctrl: { + ' ': '\u0000', + 'a': '\u0001', + 'b': '\u0002', + 'c': '\u0003', + 'd': '\u0004', + 'e': '\u0005', + 'f': '\u0006', + 'g': '\u0007', + 'h': '\u0008', + 'i': '\u0009', + 'j': '\u000a', + 'k': '\u000b', + 'm': '\u000c', + 'n': '\u000d', + 'l': '\u000e', + 'o': '\u000f', + 'p': '\u0010', + 'q': '\u0011', + 'r': '\u0012', + 's': '\u0013', + 't': '\u0014', + 'u': '\u0015', + 'v': '\u0016', + 'w': '\u0017', + 'x': '\u0018', + 'y': '\u0019', + 'z': '\u001a', + '[': '\u001b', + '\\':'\u001c', + ']': '\u001d', + '^': '\u001e', + '_': '\u001f', + 'space': '\u0000' + } +}; diff --git a/src/monitors/dock-monitor/reducer.ts b/src/monitors/dock-monitor/reducer.ts new file mode 100644 index 0000000..0bb3c11 --- /dev/null +++ b/src/monitors/dock-monitor/reducer.ts @@ -0,0 +1,35 @@ +import {combineReducers, Reducer} from '@ngrx/store'; +import {DockActions} from './actions'; + +export const POSITIONS = ['left', 'top', 'right', 'bottom']; +export type PositionsType = 'left' | 'top' | 'right' | 'bottom'; + +function position(state: PositionsType = 'right', action) { + return (action.type === DockActions.CHANGE_POSITION) ? + POSITIONS[(POSITIONS.indexOf(state) + 1) % POSITIONS.length] : + state; +} + +function size(state: number = 0.3, action) { + return (action.type === DockActions.CHANGE_SIZE) ? + action.size : + state; +} + +function visible(state: boolean = true, action) { + return (action.type === DockActions.TOGGLE_VISIBILITY) ? + !state : + state; +} + +export interface DockState{ + position?: PositionsType; + visible?: boolean; + size?: number; +} + +export const dockReducer = combineReducers({ + position, + visible, + size +}); diff --git a/src/monitors/log-monitor/index.ts b/src/monitors/log-monitor/index.ts index 5d71843..d38cbe1 100644 --- a/src/monitors/log-monitor/index.ts +++ b/src/monitors/log-monitor/index.ts @@ -17,13 +17,11 @@ import {LogMonitorButton} from './log-monitor-button'; display: block; background-color: #2A2F3A; font-family: 'monaco', 'Consolas', 'Lucida Console', monospace; - position: absolute; - top: 0; - right: 0; + position: relative; overflow-y: hidden; width: 100%; height: 100%; - max-width: 300px; + min-width: 300px; direction: ltr; } diff --git a/tsconfig.json b/tsconfig.json index cc033e1..2b0ecb9 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -18,7 +18,14 @@ "src/monitors/log-monitor/index.ts", "src/monitors/log-monitor/log-entry-item.ts", "src/monitors/log-monitor/log-monitor-button.ts", - "src/monitors/log-monitor/log-monitor-entry.ts" + "src/monitors/log-monitor/log-monitor-entry.ts", + "src/monitors/dock-monitor/actions.ts", + "src/monitors/dock-monitor/commander.ts", + "src/monitors/dock-monitor/dock.ts", + "src/monitors/dock-monitor/index.ts", + "src/monitors/dock-monitor/monitor.ts", + "src/monitors/dock-monitor/parse-key.ts", + "src/monitors/dock-monitor/reducer.ts" ], "exclude": [ "node_modules"