mirror of
https://github.com/Eugeny/tabby.git
synced 2025-06-17 01:50:05 +00:00
.
This commit is contained in:
parent
13ec887d66
commit
2acc4f77d4
@ -19,6 +19,8 @@ import { LocalStorageService } from 'angular2-localstorage/LocalStorageEmitter'
|
|||||||
import { AppComponent } from 'components/app'
|
import { AppComponent } from 'components/app'
|
||||||
import { CheckboxComponent } from 'components/checkbox'
|
import { CheckboxComponent } from 'components/checkbox'
|
||||||
import { HotkeyInputComponent } from 'components/hotkeyInput'
|
import { HotkeyInputComponent } from 'components/hotkeyInput'
|
||||||
|
import { HotkeyDisplayComponent } from 'components/hotkeyDisplay'
|
||||||
|
import { HotkeyHintComponent } from 'components/hotkeyHint'
|
||||||
import { HotkeyInputModalComponent } from 'components/hotkeyInputModal'
|
import { HotkeyInputModalComponent } from 'components/hotkeyInputModal'
|
||||||
import { SettingsModalComponent } from 'components/settingsModal'
|
import { SettingsModalComponent } from 'components/settingsModal'
|
||||||
import { TerminalComponent } from 'components/terminal'
|
import { TerminalComponent } from 'components/terminal'
|
||||||
@ -51,6 +53,8 @@ import { TerminalComponent } from 'components/terminal'
|
|||||||
declarations: [
|
declarations: [
|
||||||
AppComponent,
|
AppComponent,
|
||||||
CheckboxComponent,
|
CheckboxComponent,
|
||||||
|
HotkeyDisplayComponent,
|
||||||
|
HotkeyHintComponent,
|
||||||
HotkeyInputComponent,
|
HotkeyInputComponent,
|
||||||
HotkeyInputModalComponent,
|
HotkeyInputModalComponent,
|
||||||
SettingsModalComponent,
|
SettingsModalComponent,
|
||||||
|
@ -189,3 +189,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hotkey-hint {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
max-width: 300px;
|
||||||
|
}
|
||||||
|
@ -29,6 +29,8 @@
|
|||||||
.tab(*ngFor='let tab of tabs; trackBy: tab?.id', [class.active]='tab == activeTab')
|
.tab(*ngFor='let tab of tabs; trackBy: tab?.id', [class.active]='tab == activeTab')
|
||||||
terminal([session]='tab.session', '[(title)]'='tab.name')
|
terminal([session]='tab.session', '[(title)]'='tab.name')
|
||||||
|
|
||||||
|
hotkey-hint
|
||||||
|
|
||||||
toaster-container([toasterconfig]="toasterconfig")
|
toaster-container([toasterconfig]="toasterconfig")
|
||||||
template(ngbModalContainer)
|
template(ngbModalContainer)
|
||||||
|
|
||||||
|
@ -49,6 +49,10 @@ class Tab {
|
|||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class AppComponent {
|
export class AppComponent {
|
||||||
|
toasterConfig: ToasterConfig
|
||||||
|
tabs: Tab[] = []
|
||||||
|
activeTab: Tab
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private modal: ModalService,
|
private modal: ModalService,
|
||||||
private elementRef: ElementRef,
|
private elementRef: ElementRef,
|
||||||
@ -70,6 +74,17 @@ export class AppComponent {
|
|||||||
timeout: 4000,
|
timeout: 4000,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
this.hotkeys.matchedHotkey.subscribe((hotkey) => {
|
||||||
|
if (hotkey == 'new-tab') {
|
||||||
|
this.newTab()
|
||||||
|
}
|
||||||
|
if (hotkey == 'close-tab') {
|
||||||
|
if (this.activeTab) {
|
||||||
|
this.closeTab(this.activeTab)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
this.hotkeys.key.subscribe((key) => {
|
this.hotkeys.key.subscribe((key) => {
|
||||||
if (key.event == 'keydown') {
|
if (key.event == 'keydown') {
|
||||||
if (key.alt && key.key >= '1' && key.key <= '9') {
|
if (key.alt && key.key >= '1' && key.key <= '9') {
|
||||||
@ -83,12 +98,6 @@ export class AppComponent {
|
|||||||
this.selectTab(this.tabs[9])
|
this.selectTab(this.tabs[9])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (key.ctrl && key.shift && key.key == 'W' && this.activeTab) {
|
|
||||||
this.closeTab(this.activeTab)
|
|
||||||
}
|
|
||||||
if (key.ctrl && key.shift && key.key == 'T' && this.activeTab) {
|
|
||||||
this.newTab()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -98,10 +107,6 @@ export class AppComponent {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
toasterConfig: ToasterConfig
|
|
||||||
tabs: Tab[] = []
|
|
||||||
activeTab: Tab
|
|
||||||
|
|
||||||
newTab () {
|
newTab () {
|
||||||
this.addSessionTab(this.sessions.createNewSession({command: 'bash'}))
|
this.addSessionTab(this.sessions.createNewSession({command: 'bash'}))
|
||||||
}
|
}
|
||||||
|
24
app/src/components/hotkeyDisplay.less
Normal file
24
app/src/components/hotkeyDisplay.less
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
:host {
|
||||||
|
display: inline-block;
|
||||||
|
|
||||||
|
.stroke {
|
||||||
|
display: inline-block;
|
||||||
|
margin: 0 5px;
|
||||||
|
|
||||||
|
.key-container {
|
||||||
|
display: inline-block;
|
||||||
|
background: #222;
|
||||||
|
text-shadow: 0 1px 0 rgba(0,0,0,.5);
|
||||||
|
|
||||||
|
.key {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 4px 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plus {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 4px 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
7
app/src/components/hotkeyDisplay.pug
Normal file
7
app/src/components/hotkeyDisplay.pug
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
.stroke(*ngFor='let stroke of model')
|
||||||
|
.key-container(
|
||||||
|
*ngFor='let key of splitKeys(stroke); let isLast = last; trackBy: key',
|
||||||
|
@animateKey
|
||||||
|
)
|
||||||
|
.key {{key}}
|
||||||
|
.plus(*ngIf='!isLast') +
|
37
app/src/components/hotkeyDisplay.ts
Normal file
37
app/src/components/hotkeyDisplay.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import { Component, Input, ChangeDetectionStrategy, trigger, style, animate, transition, state } from '@angular/core'
|
||||||
|
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'hotkey-display',
|
||||||
|
template: require('./hotkeyDisplay.pug'),
|
||||||
|
styles: [require('./hotkeyDisplay.less')],
|
||||||
|
//changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
animations: [
|
||||||
|
trigger('animateKey', [
|
||||||
|
state('in', style({
|
||||||
|
'transform': 'translateX(0)',
|
||||||
|
'opacity': '1',
|
||||||
|
})),
|
||||||
|
transition(':enter', [
|
||||||
|
style({
|
||||||
|
'transform': 'translateX(25px)',
|
||||||
|
'opacity': '0',
|
||||||
|
}),
|
||||||
|
animate('250ms ease-out')
|
||||||
|
]),
|
||||||
|
transition(':leave', [
|
||||||
|
animate('250ms ease-in', style({
|
||||||
|
'transform': 'translateX(25px)',
|
||||||
|
'opacity': '0',
|
||||||
|
}))
|
||||||
|
])
|
||||||
|
])
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class HotkeyDisplayComponent {
|
||||||
|
splitKeys(keys: string): string[] {
|
||||||
|
return keys.split('+').map((x) => x.trim())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Input() model: string[]
|
||||||
|
}
|
8
app/src/components/hotkeyHint.less
Normal file
8
app/src/components/hotkeyHint.less
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
|
||||||
|
.line {
|
||||||
|
background: #333;
|
||||||
|
padding: 3px 10px;
|
||||||
|
}
|
||||||
|
}
|
4
app/src/components/hotkeyHint.pug
Normal file
4
app/src/components/hotkeyHint.pug
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
.body(*ngIf='partialHotkeyMatches?.length > 0')
|
||||||
|
.line(*ngFor='let match of partialHotkeyMatches; trackBy: match?.id', @animateLine)
|
||||||
|
hotkey-display([model]='match.strokes')
|
||||||
|
span {{ hotkeys.getHotkeyDescription(match.id).name }}
|
59
app/src/components/hotkeyHint.ts
Normal file
59
app/src/components/hotkeyHint.ts
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
import { Component, ChangeDetectionStrategy, trigger, style, animate, transition, state } from '@angular/core'
|
||||||
|
import { HotkeysService, PartialHotkeyMatch } from 'services/hotkeys'
|
||||||
|
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'hotkey-hint',
|
||||||
|
template: require('./hotkeyHint.pug'),
|
||||||
|
styles: [require('./hotkeyHint.less')],
|
||||||
|
//changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
animations: [
|
||||||
|
trigger('animateLine', [
|
||||||
|
state('in', style({
|
||||||
|
'transform': 'translateX(0)',
|
||||||
|
'opacity': '1',
|
||||||
|
})),
|
||||||
|
transition(':enter', [
|
||||||
|
style({
|
||||||
|
'transform': 'translateX(25px)',
|
||||||
|
'opacity': '0',
|
||||||
|
}),
|
||||||
|
animate('250ms ease-out')
|
||||||
|
]),
|
||||||
|
transition(':leave', [
|
||||||
|
style({'height': '*'}),
|
||||||
|
animate('250ms ease-in', style({
|
||||||
|
'transform': 'translateX(25px)',
|
||||||
|
'opacity': '0',
|
||||||
|
'height': '0',
|
||||||
|
}))
|
||||||
|
])
|
||||||
|
])
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class HotkeyHintComponent {
|
||||||
|
partialHotkeyMatches: PartialHotkeyMatch[]
|
||||||
|
private keyTimeoutInterval: NodeJS.Timer = null
|
||||||
|
|
||||||
|
constructor (
|
||||||
|
public hotkeys: HotkeysService,
|
||||||
|
) {
|
||||||
|
this.hotkeys.key.subscribe(() => {
|
||||||
|
let partialMatches = this.hotkeys.getCurrentPartiallyMatchedHotkeys()
|
||||||
|
if (partialMatches.length > 0) {
|
||||||
|
console.log('Partial matches:', partialMatches)
|
||||||
|
this.partialHotkeyMatches = partialMatches
|
||||||
|
|
||||||
|
if (this.keyTimeoutInterval == null) {
|
||||||
|
this.keyTimeoutInterval = setInterval(() => {
|
||||||
|
if (this.hotkeys.getCurrentPartiallyMatchedHotkeys().length == 0) {
|
||||||
|
clearInterval(this.keyTimeoutInterval)
|
||||||
|
this.keyTimeoutInterval = null
|
||||||
|
this.partialHotkeyMatches = null
|
||||||
|
}
|
||||||
|
}, 500)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,7 @@
|
|||||||
.button-states() {
|
:host {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 5px 10px;
|
||||||
|
|
||||||
transition: 0.125s all;
|
transition: 0.125s all;
|
||||||
|
|
||||||
&:hover:not(.active) {
|
&:hover:not(.active) {
|
||||||
@ -9,31 +12,3 @@
|
|||||||
background: rgba(0, 0, 0, .1);
|
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();
|
|
||||||
}
|
|
||||||
|
@ -1,4 +0,0 @@
|
|||||||
.stroke(*ngFor='let stroke of model')
|
|
||||||
.key-container(*ngFor='let key of splitKeys(stroke); let isLast = last')
|
|
||||||
.key {{key}}
|
|
||||||
.plus(*ngIf='!isLast') +
|
|
@ -5,7 +5,9 @@ import { HotkeyInputModalComponent } from './hotkeyInputModal'
|
|||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'hotkey-input',
|
selector: 'hotkey-input',
|
||||||
template: require('./hotkeyInput.pug'),
|
template: `
|
||||||
|
<hotkey-display [model]='model'></hotkey-display>
|
||||||
|
`,
|
||||||
styles: [require('./hotkeyInput.less')],
|
styles: [require('./hotkeyInput.less')],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
})
|
})
|
||||||
|
@ -3,42 +3,20 @@
|
|||||||
padding: 30px 20px !important;
|
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 {
|
.input {
|
||||||
background: #111;
|
background: #111;
|
||||||
text-align: center;
|
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
line-height: 24px;
|
line-height: 24px;
|
||||||
height: 50px;
|
height: 50px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.timeout {
|
.timeout {
|
||||||
background: #333;
|
background: #111;
|
||||||
height: 10px;
|
height: 5px;
|
||||||
margin: 15px 0;
|
margin: 0 0 15px;
|
||||||
|
|
||||||
div {
|
div {
|
||||||
height: 10px;
|
height: 5px;
|
||||||
background: #666;
|
background: #666;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,8 @@
|
|||||||
div.modal-body
|
div.modal-body
|
||||||
label Press the key now
|
label Press the key now
|
||||||
.input
|
.input
|
||||||
.stroke(*ngFor='let stroke of value')
|
hotkey-display([model]='value')
|
||||||
.key-container(*ngFor='let key of splitKeys(stroke); let isLast = last')
|
|
||||||
.key {{key}}
|
|
||||||
.plus(*ngIf='!isLast') +
|
|
||||||
|
|
||||||
.timeout
|
.timeout
|
||||||
div([style.width]='timeoutProgress + "%"')
|
div([style.width]='timeoutProgress + "%"')
|
||||||
a.btn.btn-default((click)='close()') Cancel
|
a.btn.btn-default.pull-right((click)='close()') Cancel
|
||||||
|
@ -23,6 +23,7 @@ export class HotkeyInputModalComponent {
|
|||||||
private modalInstance: NgbActiveModal,
|
private modalInstance: NgbActiveModal,
|
||||||
public hotkeys: HotkeysService,
|
public hotkeys: HotkeysService,
|
||||||
) {
|
) {
|
||||||
|
this.hotkeys.clearCurrentKeystrokes()
|
||||||
this.keySubscription = hotkeys.key.subscribe(() => {
|
this.keySubscription = hotkeys.key.subscribe(() => {
|
||||||
this.lastKeyEvent = performance.now()
|
this.lastKeyEvent = performance.now()
|
||||||
this.value = this.hotkeys.getCurrentKeystrokes()
|
this.value = this.hotkeys.getCurrentKeystrokes()
|
||||||
|
@ -1,15 +1,10 @@
|
|||||||
import { Injectable } from '@angular/core'
|
import { Injectable } from '@angular/core'
|
||||||
import { HostAppService, PLATFORM_MAC, PLATFORM_WINDOWS } from 'services/hostApp'
|
|
||||||
const Config = nodeRequire('electron-config')
|
const Config = nodeRequire('electron-config')
|
||||||
const exec = nodeRequire('child-process-promise').exec
|
|
||||||
import * as fs from 'fs'
|
|
||||||
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ConfigService {
|
export class ConfigService {
|
||||||
constructor(
|
constructor() {
|
||||||
private hostApp: HostAppService,
|
|
||||||
) {
|
|
||||||
this.config = new Config({name: 'config'})
|
this.config = new Config({name: 'config'})
|
||||||
this.load()
|
this.load()
|
||||||
}
|
}
|
||||||
@ -17,65 +12,22 @@ export class ConfigService {
|
|||||||
private config: any
|
private config: any
|
||||||
private store: any
|
private store: any
|
||||||
|
|
||||||
migrate() {
|
|
||||||
if (!this.has('migrated')) {
|
|
||||||
if (this.hostApp.platform == PLATFORM_WINDOWS) {
|
|
||||||
let configPath = `${this.hostApp.getPath('documents')}\\.elements.conf`
|
|
||||||
let config = null
|
|
||||||
try {
|
|
||||||
config = JSON.parse(fs.readFileSync(configPath, 'utf-8'))
|
|
||||||
console.log('Migrating configuration:', config)
|
|
||||||
this.set('host', config.Hostname)
|
|
||||||
this.set('username', config.Username)
|
|
||||||
this.set('firstDrive', config.FirstDrive)
|
|
||||||
} catch (err) {
|
|
||||||
console.error('Could not migrate the config:', err)
|
|
||||||
}
|
|
||||||
this.set('migrated', 1)
|
|
||||||
this.save()
|
|
||||||
return Promise.resolve()
|
|
||||||
}
|
|
||||||
if (this.hostApp.platform == PLATFORM_MAC) {
|
|
||||||
return Promise.all([
|
|
||||||
exec('defaults read ~/Library/Preferences/com.syslink.Elements.plist connection_host').then((result) => {
|
|
||||||
this.set('host', result.stdout.trim())
|
|
||||||
}),
|
|
||||||
exec('defaults read ~/Library/Preferences/com.syslink.Elements.plist connection_username').then((result) => {
|
|
||||||
this.set('username', result.stdout.trim())
|
|
||||||
}),
|
|
||||||
]).then(() => {
|
|
||||||
this.set('migrated', 1)
|
|
||||||
this.save()
|
|
||||||
}).catch((err) => {
|
|
||||||
console.error('Could not migrate the config:', err)
|
|
||||||
this.set('migrated', 1)
|
|
||||||
this.save()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Promise.resolve()
|
|
||||||
}
|
|
||||||
|
|
||||||
set(key: string, value: any) {
|
set(key: string, value: any) {
|
||||||
|
this.store.set(key, value)
|
||||||
this.save()
|
this.save()
|
||||||
this.config.set(key, value)
|
|
||||||
this.load()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get(key: string): any {
|
get(key: string): any {
|
||||||
this.save()
|
return this.store[key]
|
||||||
return this.config.get(key)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
has(key: string): boolean {
|
has(key: string): boolean {
|
||||||
this.save()
|
return this.store[key] != undefined
|
||||||
return this.config.has(key)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
delete(key: string) {
|
delete(key: string) {
|
||||||
|
delete this.store[key]
|
||||||
this.save()
|
this.save()
|
||||||
this.config.delete(key)
|
|
||||||
this.load()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
load() {
|
load() {
|
||||||
|
@ -1,9 +1,35 @@
|
|||||||
import { Injectable, NgZone, EventEmitter } from '@angular/core'
|
import { Injectable, NgZone, EventEmitter } from '@angular/core'
|
||||||
import { ElectronService } from 'services/electron'
|
import { ElectronService } from 'services/electron'
|
||||||
|
import { ConfigService } from 'services/config'
|
||||||
import { NativeKeyEvent, stringifyKeySequence } from './hotkeys.util'
|
import { NativeKeyEvent, stringifyKeySequence } from './hotkeys.util'
|
||||||
const hterm = require('hterm-commonjs')
|
const hterm = require('hterm-commonjs')
|
||||||
|
|
||||||
|
export interface HotkeyDescription {
|
||||||
|
id: string,
|
||||||
|
name: string,
|
||||||
|
defaults: string[][],
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PartialHotkeyMatch {
|
||||||
|
id: string,
|
||||||
|
strokes: string[],
|
||||||
|
matchedLength: number,
|
||||||
|
}
|
||||||
|
|
||||||
const KEY_TIMEOUT = 2000
|
const KEY_TIMEOUT = 2000
|
||||||
|
const HOTKEYS: HotkeyDescription[] = [
|
||||||
|
{
|
||||||
|
id: 'new-tab',
|
||||||
|
name: 'New tab',
|
||||||
|
defaults: [['Ctrl+Shift+T'], ['Ctrl+A', 'C']],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'close-tab',
|
||||||
|
name: 'Close tab',
|
||||||
|
defaults: [['Ctrl+Shift+W'], ['Ctrl+A', 'K']],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
interface EventBufferEntry {
|
interface EventBufferEntry {
|
||||||
event: NativeKeyEvent,
|
event: NativeKeyEvent,
|
||||||
@ -13,12 +39,14 @@ interface EventBufferEntry {
|
|||||||
@Injectable()
|
@Injectable()
|
||||||
export class HotkeysService {
|
export class HotkeysService {
|
||||||
key = new EventEmitter<NativeKeyEvent>()
|
key = new EventEmitter<NativeKeyEvent>()
|
||||||
|
matchedHotkey = new EventEmitter<string>()
|
||||||
globalHotkey = new EventEmitter()
|
globalHotkey = new EventEmitter()
|
||||||
private currentKeystrokes: EventBufferEntry[] = []
|
private currentKeystrokes: EventBufferEntry[] = []
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private zone: NgZone,
|
private zone: NgZone,
|
||||||
private electron: ElectronService,
|
private electron: ElectronService,
|
||||||
|
private config: ConfigService,
|
||||||
) {
|
) {
|
||||||
let events = [
|
let events = [
|
||||||
{
|
{
|
||||||
@ -42,6 +70,10 @@ export class HotkeysService {
|
|||||||
oldHandler.bind(this)(nativeEvent)
|
oldHandler.bind(this)(nativeEvent)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (!config.get('hotkeys')) {
|
||||||
|
config.set('hotkeys', {})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
emitNativeEvent (name, nativeEvent) {
|
emitNativeEvent (name, nativeEvent) {
|
||||||
@ -51,10 +83,20 @@ export class HotkeysService {
|
|||||||
this.currentKeystrokes.push({ event: nativeEvent, time: performance.now() })
|
this.currentKeystrokes.push({ event: nativeEvent, time: performance.now() })
|
||||||
|
|
||||||
this.zone.run(() => {
|
this.zone.run(() => {
|
||||||
|
let matched = this.getCurrentFullyMatchedHotkey()
|
||||||
|
if (matched) {
|
||||||
|
console.log('Matched hotkey', matched)
|
||||||
|
this.matchedHotkey.emit(matched)
|
||||||
|
this.clearCurrentKeystrokes()
|
||||||
|
}
|
||||||
this.key.emit(nativeEvent)
|
this.key.emit(nativeEvent)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
clearCurrentKeystrokes () {
|
||||||
|
this.currentKeystrokes = []
|
||||||
|
}
|
||||||
|
|
||||||
getCurrentKeystrokes () : string[] {
|
getCurrentKeystrokes () : string[] {
|
||||||
this.currentKeystrokes = this.currentKeystrokes.filter((x) => performance.now() - x.time < KEY_TIMEOUT )
|
this.currentKeystrokes = this.currentKeystrokes.filter((x) => performance.now() - x.time < KEY_TIMEOUT )
|
||||||
return stringifyKeySequence(this.currentKeystrokes.map((x) => x.event))
|
return stringifyKeySequence(this.currentKeystrokes.map((x) => x.event))
|
||||||
@ -66,4 +108,60 @@ export class HotkeysService {
|
|||||||
this.globalHotkey.emit()
|
this.globalHotkey.emit()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getHotkeysConfig () {
|
||||||
|
let keys = {}
|
||||||
|
for (let key of HOTKEYS) {
|
||||||
|
keys[key.id] = key.defaults
|
||||||
|
}
|
||||||
|
for (let key in this.config.get('hotkeys')) {
|
||||||
|
keys[key] = this.config.get('hotkeys')[key]
|
||||||
|
}
|
||||||
|
return keys
|
||||||
|
}
|
||||||
|
|
||||||
|
getCurrentFullyMatchedHotkey () : string {
|
||||||
|
for (let id in this.getHotkeysConfig()) {
|
||||||
|
for (let sequence of this.getHotkeysConfig()[id]) {
|
||||||
|
let currentStrokes = this.getCurrentKeystrokes()
|
||||||
|
if (currentStrokes.length < sequence.length) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if (sequence.every((x, index) => {
|
||||||
|
return x.toLowerCase() == currentStrokes[currentStrokes.length - sequence.length + index].toLowerCase()
|
||||||
|
})) {
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
getCurrentPartiallyMatchedHotkeys () : PartialHotkeyMatch[] {
|
||||||
|
let result = []
|
||||||
|
for (let id in this.getHotkeysConfig()) {
|
||||||
|
for (let sequence of this.getHotkeysConfig()[id]) {
|
||||||
|
let currentStrokes = this.getCurrentKeystrokes()
|
||||||
|
|
||||||
|
for (let matchLength = Math.min(currentStrokes.length, sequence.length); matchLength > 0; matchLength--) {
|
||||||
|
console.log(sequence, currentStrokes.slice(currentStrokes.length - sequence.length))
|
||||||
|
if (sequence.slice(0, matchLength).every((x, index) => {
|
||||||
|
return x.toLowerCase() == currentStrokes[currentStrokes.length - matchLength + index].toLowerCase()
|
||||||
|
})) {
|
||||||
|
result.push({
|
||||||
|
matchedLength: matchLength,
|
||||||
|
id,
|
||||||
|
strokes: sequence
|
||||||
|
})
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
getHotkeyDescription (id: string) : HotkeyDescription {
|
||||||
|
return HOTKEYS.filter((x) => x.id == id)[0]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user