1
1
mirror of https://github.com/Eugeny/tabby.git synced 2024-09-11 13:13:59 +03:00
This commit is contained in:
Eugene Pankov 2016-12-28 17:36:07 +01:00
parent f7af82902f
commit 13ec887d66
14 changed files with 275 additions and 170 deletions

View File

@ -18,6 +18,8 @@ import { LocalStorageService } from 'angular2-localstorage/LocalStorageEmitter'
import { AppComponent } from 'components/app'
import { CheckboxComponent } from 'components/checkbox'
import { HotkeyInputComponent } from 'components/hotkeyInput'
import { HotkeyInputModalComponent } from 'components/hotkeyInputModal'
import { SettingsModalComponent } from 'components/settingsModal'
import { TerminalComponent } from 'components/terminal'
@ -43,11 +45,14 @@ import { TerminalComponent } from 'components/terminal'
LocalStorageService,
],
entryComponents: [
HotkeyInputModalComponent,
SettingsModalComponent,
],
declarations: [
AppComponent,
CheckboxComponent,
HotkeyInputComponent,
HotkeyInputModalComponent,
SettingsModalComponent,
TerminalComponent,
],

View File

@ -66,12 +66,12 @@
display: flex;
flex-direction: row;
.btn-new-tab, .tab {
.btn-settings, .btn-new-tab, .tab {
line-height: @tabs-height - 2px;
cursor: pointer;
}
.btn-new-tab {
.btn-new-tab, .btn-settings {
padding: 0 15px;
flex: none;
flex-grow: 0;

View File

@ -22,6 +22,8 @@
.btn-new-tab((click)='newTab()')
i.fa.fa-plus
span Tab
.btn-settings((click)='showSettings()')
i.fa.fa-cog
.tabs-content
.tab(*ngFor='let tab of tabs; trackBy: tab?.id', [class.active]='tab == activeTab')

View File

@ -0,0 +1,39 @@
.button-states() {
transition: 0.125s all;
&:hover:not(.active) {
background: rgba(255, 255, 255, .033);
}
&:active:not(.active) {
background: rgba(0, 0, 0, .1);
}
}
:host {
display: inline-block;
padding: 5px 10px;
.stroke {
display: inline-block;
margin-right: 5px;
.key-container {
display: inline-block;
.key {
display: inline-block;
padding: 4px 5px;
background: #333;
text-shadow: 0 1px 0 rgba(0,0,0,.5);
}
.plus {
display: inline-block;
margin: 0 5px;
}
}
}
.button-states();
}

View File

@ -0,0 +1,4 @@
.stroke(*ngFor='let stroke of model')
.key-container(*ngFor='let key of splitKeys(stroke); let isLast = last')
.key {{key}}
.plus(*ngIf='!isLast') +

View File

@ -0,0 +1,30 @@
import { Component, Input, Output, EventEmitter, HostListener, ChangeDetectionStrategy } from '@angular/core'
import { ModalService } from 'services/modal'
import { HotkeyInputModalComponent } from './hotkeyInputModal'
@Component({
selector: 'hotkey-input',
template: require('./hotkeyInput.pug'),
styles: [require('./hotkeyInput.less')],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class HotkeyInputComponent {
constructor(
private modal: ModalService,
) { }
@HostListener('click') public click() {
this.modal.open(HotkeyInputModalComponent).result.then((value: string[]) => {
this.model = value
this.modelChange.emit(this.model)
})
}
splitKeys(keys: string): string[] {
return keys.split('+').map((x) => x.trim())
}
@Input() model: string[]
@Output() modelChange = new EventEmitter()
}

View File

@ -0,0 +1,45 @@
:host {
>.modal-body {
padding: 30px 20px !important;
}
.stroke {
display: inline-block;
margin: 8px 5px 0 0;
.key-container {
display: inline-block;
.key {
display: inline-block;
padding: 4px 5px;
background: #333;
text-shadow: 0 1px 0 rgba(0,0,0,.5);
}
.plus {
display: inline-block;
margin: 0 5px;
}
}
}
.input {
background: #111;
text-align: center;
font-size: 24px;
line-height: 24px;
height: 50px;
}
.timeout {
background: #333;
height: 10px;
margin: 15px 0;
div {
height: 10px;
background: #666;
}
}
}

View File

@ -0,0 +1,11 @@
div.modal-body
label Press the key now
.input
.stroke(*ngFor='let stroke of value')
.key-container(*ngFor='let key of splitKeys(stroke); let isLast = last')
.key {{key}}
.plus(*ngIf='!isLast') +
.timeout
div([style.width]='timeoutProgress + "%"')
a.btn.btn-default((click)='close()') Cancel

View File

@ -0,0 +1,55 @@
import { Component, Input } from '@angular/core'
import { HotkeysService } from 'services/hotkeys'
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
import { Subscription } from 'rxjs'
const INPUT_TIMEOUT = 2000
@Component({
selector: 'hotkey-input-modal',
template: require('./hotkeyInputModal.pug'),
styles: [require('./hotkeyInputModal.less')],
})
export class HotkeyInputModalComponent {
private keySubscription: Subscription
private lastKeyEvent: number
private keyTimeoutInterval: NodeJS.Timer
@Input() value: string[] = []
@Input() timeoutProgress = 0
constructor(
private modalInstance: NgbActiveModal,
public hotkeys: HotkeysService,
) {
this.keySubscription = hotkeys.key.subscribe(() => {
this.lastKeyEvent = performance.now()
this.value = this.hotkeys.getCurrentKeystrokes()
})
}
splitKeys (keys: string): string[] {
return keys.split('+').map((x) => x.trim())
}
ngOnInit () {
this.keyTimeoutInterval = setInterval(() => {
if (!this.lastKeyEvent) {
return
}
this.timeoutProgress = (performance.now() - this.lastKeyEvent) * 100 / INPUT_TIMEOUT
if (this.timeoutProgress >= 100) {
this.modalInstance.close(this.value)
}
}, 25)
}
ngOnDestroy () {
clearInterval(this.keyTimeoutInterval)
}
close() {
this.modalInstance.dismiss()
}
}

View File

@ -2,68 +2,4 @@
>.modal-body {
padding: 0 0 20px !important;
}
.form-group {
margin-left: 15px;
}
.version-info {
text-align: center;
font-size: 12px;
color: #aaa;
cursor: pointer;
padding: 10px 0;
-webkit-user-select: text;
transition: .25s all;
&:hover {
color: white;
}
}
.status-line {
display: flex;
padding: 5px 10px;
&.clickable {
&:hover {
background: rgba(0,0,0,.5);
cursor: pointer;
}
}
.icon {
flex: none;
padding: 7px 10px 0 0px;
img {
width: 32px;
height: 32px;
border-radius: 16px;
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.5);
}
i {
width: 32px;
text-align: center;
}
}
.main {
flex: auto;
flex-direction: column;
display: flex;
.title {
flex: none;
font-size: 12px;
}
.value {
flex: auto;
font-size: 16px;
color: #ddd;
}
}
}
}

View File

@ -2,95 +2,18 @@ div.modal-body
ngb-tabset(type='tabs nav-justified')
ngb-tab
template(ngbTabTitle)
i.fa.fa-cog
| General
template(ngbTabContent)
.status-line.clickable(*ngIf='connectionHost', (click)='openWeb()')
.icon
i.fa.fa-rss.fa-2x.fa-live
.main
.title Server
.value {{connectionHost}}
.status-line(*ngIf='!connectionHost')
.icon
i.fa.fa-rss.fa-2x
.main
.title Server
.value Not connected
div.form-group
checkbox(text='Remember connected workspaces', '[(model)]'='config.store.rememberWorkspaces')
ngb-tab
template(ngbTabTitle)
i.fa.fa-wrench
| Advanced
template(ngbTabContent)
div.form-group(*ngIf='isWindows || isLinux')
div.input-group
input.form-control(type='text', placeholder='SNFS projects folder', '[(ngModel)]'='config.store.snfsPath')
div.input-group-btn
button.btn.btn-default((click)='selectSNFSPath()')
i.fa.fa-folder-open
div.form-group(*ngIf='isWindows')
label First drive letter to use
select.form-control('[(ngModel)]'='config.store.firstDrive')
option(*ngFor='let x of drives', value='{{x}}') {{x}}:
div.form-group(*ngIf='isMac')
label Extra NFS options
input.form-control(type='text', '[(ngModel)]'='config.store.extraNFSOptions')
div.form-group(*ngIf='isMac')
label Extra AFP options
input.form-control(type='text', '[(ngModel)]'='config.store.extraAFPOptions')
div.form-group(*ngIf='isMac')
label Extra SMB options
input.form-control(type='text', '[(ngModel)]'='config.store.extraSMBOptions')
div.form-group(*ngIf='isLinux')
label Extra NFS options
input.form-control(type='text', '[(ngModel)]'='config.store.extraLinuxNFSOptions')
div.form-group(*ngIf='isLinux')
label Extra SMB options
input.form-control(type='text', '[(ngModel)]'='config.store.extraLinuxSMBOptions')
ngb-tab(*ngIf="apiServer.authorizedKeysStore.length > 0")
template(ngbTabTitle)
i.fa.fa-plug
| Apps
template(ngbTabContent)
.list-group
.list-group-item(*ngFor="let key of apiServer.authorizedKeysStore")
button.btn.btn-default((click)='apiServer.deauthorizeKey(key)')
i.fa.fa-times
span Disconnect this app
div {{key.name}}
ngb-tab
template(ngbTabTitle)
i.fa.fa-info-circle
| About
i.fa.fa-keyboard-o
| Hotkeys
template(ngbTabContent)
.form-group
h1 ELEMENTS Client
div syslink GmbH © {{year}}
.form-group
label Version
div {{version}}
.form-group
button.btn.btn-default((click)='copyDiagnostics()') Copy diagnostic info
table.table
tr
th Toggle terminal window
td
hotkey-input('[(model)]'='globalHotkey')
div.modal-footer
div.btn-group.btn-group-justified
a.btn.btn-default((click)='logout()', *ngIf='elementsClient.userInfo')
i.fa.fa-fw.fa-arrow-left
br
| Log out
a.btn.btn-default((click)='quit()')
i.fa.fa-fw.fa-power-off
br
| Quit
a.btn.btn-default((click)='close()')
i.fa.fa-fw.fa-check
br

View File

@ -30,6 +30,8 @@ export class SettingsModalComponent {
year: number
version: string
globalHotkey = ['Ctrl+Shift+G']
ngOnDestroy() {
this.config.save()
}

View File

@ -1,21 +1,20 @@
import { Injectable, NgZone, EventEmitter } from '@angular/core'
import { ElectronService } from 'services/electron'
import { NativeKeyEvent, stringifyKeySequence } from './hotkeys.util'
const hterm = require('hterm-commonjs')
const KEY_TIMEOUT = 2000
export interface Key {
event: string,
alt: boolean,
ctrl: boolean,
cmd: boolean,
shift: boolean,
key: string
interface EventBufferEntry {
event: NativeKeyEvent,
time: number,
}
@Injectable()
export class HotkeysService {
key = new EventEmitter<Key>()
key = new EventEmitter<NativeKeyEvent>()
globalHotkey = new EventEmitter()
private currentKeystrokes: EventBufferEntry[] = []
constructor(
private zone: NgZone,
@ -26,10 +25,6 @@ export class HotkeysService {
name: 'keydown',
htermHandler: 'onKeyDown_',
},
{
name: 'keypress',
htermHandler: 'onKeyPress_',
},
{
name: 'keyup',
htermHandler: 'onKeyUp_',
@ -50,18 +45,21 @@ export class HotkeysService {
}
emitNativeEvent (name, nativeEvent) {
nativeEvent.event = name
console.log(nativeEvent)
this.currentKeystrokes.push({ event: nativeEvent, time: performance.now() })
this.zone.run(() => {
this.key.emit({
event: name,
alt: nativeEvent.altKey,
shift: nativeEvent.shiftKey,
cmd: nativeEvent.metaKey,
ctrl: nativeEvent.ctrlKey,
key: nativeEvent.key,
})
this.key.emit(nativeEvent)
})
}
getCurrentKeystrokes () : string[] {
this.currentKeystrokes = this.currentKeystrokes.filter((x) => performance.now() - x.time < KEY_TIMEOUT )
return stringifyKeySequence(this.currentKeystrokes.map((x) => x.event))
}
registerHotkeys () {
this.electron.globalShortcut.unregisterAll()
this.electron.globalShortcut.register('`', () => {

View File

@ -0,0 +1,55 @@
import * as os from 'os'
export const metaKeyName = {
darwin: '⌘',
win32: 'Win',
linux: 'Super',
}[os.platform()]
export const altKeyName = {
darwin: 'Option',
win32: 'Alt',
linux: 'Alt',
}[os.platform()]
export interface NativeKeyEvent {
event?: string,
altKey: boolean,
ctrlKey: boolean,
metaKey: boolean,
shiftKey: boolean,
key: string,
keyCode: string,
}
export function stringifyKeySequence(events: NativeKeyEvent[]): string[] {
let items: string[] = []
let lastEvent: NativeKeyEvent
events = events.slice()
while (events.length > 0) {
let event = events.shift()
if (event.event == 'keyup' && (lastEvent && lastEvent.event == 'keydown')) {
let itemKeys: string[] = []
if (lastEvent.ctrlKey) {
itemKeys.push('Ctrl')
}
if (lastEvent.metaKey) {
itemKeys.push(metaKeyName)
}
if (lastEvent.altKey) {
itemKeys.push(altKeyName)
}
if (lastEvent.shiftKey) {
itemKeys.push('Shift')
}
itemKeys.push(lastEvent.key)
items.push(itemKeys.join('+'))
}
lastEvent = event
}
return items
}