This commit is contained in:
Eugene Pankov 2017-03-26 22:39:10 +02:00
parent c9ead5e93d
commit 482343e383
14 changed files with 197 additions and 161 deletions

View file

@ -3,7 +3,8 @@ import { BaseTabComponent } from 'components/baseTab'
export declare type ComponentType<T extends Tab> = new (...args: any[]) => BaseTabComponent<T> export declare type ComponentType<T extends Tab> = new (...args: any[]) => BaseTabComponent<T>
export class Tab {
export abstract class Tab {
id: number id: number
title: string title: string
scrollable: boolean scrollable: boolean
@ -20,9 +21,7 @@ export class Tab {
this.hasActivity = true this.hasActivity = true
} }
getComponentType (): ComponentType<Tab> { abstract getComponentType (): ComponentType<Tab>
return null
}
getRecoveryToken (): any { getRecoveryToken (): any {
return null return null

View file

@ -30,6 +30,11 @@ export interface IConfigData {
@Injectable() @Injectable()
export class ConfigService { export class ConfigService {
store: IConfigData
change = new EventEmitter()
restartRequested: boolean
private path: string
constructor ( constructor (
electron: ElectronService electron: ElectronService
) { ) {
@ -37,10 +42,6 @@ export class ConfigService {
this.load() this.load()
} }
private path: string
store: IConfigData
change = new EventEmitter()
load () { load () {
if (fs.existsSync(this.path)) { if (fs.existsSync(this.path)) {
this.store = configMerge(defaultConfigStructure, yaml.safeLoad(fs.readFileSync(this.path, 'utf8'))) this.store = configMerge(defaultConfigStructure, yaml.safeLoad(fs.readFileSync(this.path, 'utf8')))
@ -61,4 +62,8 @@ export class ConfigService {
emitChange () { emitChange () {
this.change.emit() this.change.emit()
} }
requestRestart () {
this.restartRequested = true
}
} }

View file

@ -1,44 +1,21 @@
import { Injectable } from '@angular/core' import { Injectable } from '@angular/core'
interface IPluginEntry { class Plugin {
plugin: any ngModule: any
weight: number name: string
} }
@Injectable() @Injectable()
export class PluginsService { export class PluginsService {
plugins: {[type: string]: IPluginEntry[]} = {} plugins: Plugin[] = []
constructor ( register (plugin: Plugin): void {
) { this.plugins.push(plugin)
} }
register (type: string, plugin: any, weight = 0): void { getModules (): any[] {
if (!this.plugins[type]) { return this.plugins.map((plugin) => plugin.ngModule)
this.plugins[type] = []
}
this.plugins[type].push({ plugin, weight })
}
getAll<T> (type: string): T[] {
let plugins = this.plugins[type] || []
plugins = plugins.sort((a: IPluginEntry, b: IPluginEntry) => {
if (a.weight < b.weight) {
return -1
} else if (a.weight > b.weight) {
return 1
}
return 0
})
return plugins.map((x) => <T>(x.plugin))
}
emit (type: string, event: string, parameters: any[]) {
(this.plugins[type] || []).forEach((entry) => {
if (entry.plugin[event]) {
entry.plugin[event].bind(entry.plugin)(parameters)
}
})
} }
} }

11
app/src/settings/api.ts Normal file
View file

@ -0,0 +1,11 @@
import { Component } from '@angular/core'
export declare type ComponentType = new (...args: any[]) => Component
export abstract class SettingsProvider {
title: string
getComponentType (): ComponentType {
return null
}
}

View file

@ -9,14 +9,4 @@
>.modal-body { >.modal-body {
padding: 0 0 20px !important; padding: 0 0 20px !important;
} }
.appearance-preview {
background: black;
padding: 10px 20px;
margin: 0 0 10px;
.text {
color: white;
}
}
} }

View file

@ -1,6 +1,11 @@
button.btn.btn-outline-warning.btn-block(*ngIf='restartRequested', '(click)'='restartApp()') Restart the app to apply changes button.btn.btn-outline-warning.btn-block(*ngIf='config.restartRequested', '(click)'='restartApp()') Restart the app to apply changes
ngb-tabset(type='tabs') ngb-tabset(type='tabs')
ngb-tab(*ngFor='let provider of settingsProviders')
template(ngbTabTitle)
| {{provider.title}}
template(ngbTabContent)
settings-tab-body([provider]='provider')
ngb-tab ngb-tab
template(ngbTabTitle) template(ngbTabTitle)
| Application | Application
@ -124,71 +129,6 @@ ngb-tabset(type='tabs')
step='0.01' step='0.01'
) )
ngb-tab
template(ngbTabTitle)
| Appearance
template(ngbTabContent)
.row
.col-sm-6
.form-group
label Preview
.appearance-preview(
[style.font-family]='config.store.appearance.font',
[style.font-size]='config.store.appearance.fontSize + "px"',
)
.text john@doe-pc$ ls
.text foo bar
.col-sm-6
.form-group
label Font
input.form-control(
type='text',
[ngbTypeahead]='fontAutocomplete',
'[(ngModel)]'='config.store.appearance.font',
'(ngModelChange)'='config.save()',
)
small.form-text.text-muted Font to be used in the terminal
.form-group
label Font size
input.form-control(
type='number',
'[(ngModel)]'='config.store.appearance.fontSize',
'(ngModelChange)'='config.save()',
)
small.form-text.text-muted Text size to be used in the terminal
ngb-tab
template(ngbTabTitle)
| Terminal
template(ngbTabContent)
.form-group
label Terminal bell
br
div(
'[(ngModel)]'='config.store.terminal.bell'
'(ngModelChange)'='config.save()'
ngbRadioGroup
)
label.btn.btn-secondary
input(
type='radio',
[value]='"off"'
)
| Off
label.btn.btn-secondary
input(
type='radio',
[value]='"sound"'
)
| Sound
label.btn.btn-secondary
input(
type='radio',
[value]='"notification"'
)
| Notification
ngb-tab ngb-tab
template(ngbTabTitle) template(ngbTabTitle)
| Hotkeys | Hotkeys

View file

@ -1,16 +1,11 @@
import { Component } from '@angular/core' import { Component, Inject } from '@angular/core'
import { ElectronService } from 'services/electron' import { ElectronService } from 'services/electron'
import { HostAppService, PLATFORM_WINDOWS, PLATFORM_LINUX, PLATFORM_MAC } from 'services/hostApp'
import { ConfigService } from 'services/config' import { ConfigService } from 'services/config'
import { DockingService } from 'services/docking' import { DockingService } from 'services/docking'
import { Observable } from 'rxjs/Observable'
import 'rxjs/add/operator/map'
import 'rxjs/add/operator/debounceTime'
import 'rxjs/add/operator/distinctUntilChanged'
const childProcessPromise = nodeRequire('child-process-promise')
import { BaseTabComponent } from 'components/baseTab' import { BaseTabComponent } from 'components/baseTab'
import { SettingsTab } from '../tab' import { SettingsTab } from '../tab'
import { SettingsProvider } from '../api'
@Component({ @Component({
@ -19,56 +14,21 @@ import { SettingsTab } from '../tab'
styles: [require('./settingsPane.less')], styles: [require('./settingsPane.less')],
}) })
export class SettingsPaneComponent extends BaseTabComponent<SettingsTab> { export class SettingsPaneComponent extends BaseTabComponent<SettingsTab> {
isWindows: boolean
isMac: boolean
isLinux: boolean
year: number
version: string
fonts: string[] = []
restartRequested: boolean
globalHotkey = ['Ctrl+Shift+G'] globalHotkey = ['Ctrl+Shift+G']
constructor( constructor(
public config: ConfigService, public config: ConfigService,
private electron: ElectronService, private electron: ElectronService,
public docking: DockingService, public docking: DockingService,
hostApp: HostAppService, @Inject(SettingsProvider) public settingsProviders: SettingsProvider[]
) { ) {
super() super()
this.isWindows = hostApp.platform == PLATFORM_WINDOWS
this.isMac = hostApp.platform == PLATFORM_MAC
this.isLinux = hostApp.platform == PLATFORM_LINUX
this.version = electron.app.getVersion()
this.year = new Date().getFullYear()
}
ngOnInit () {
childProcessPromise.exec('fc-list :spacing=mono').then((result) => {
this.fonts = result.stdout
.split('\n')
.filter((x) => !!x)
.map((x) => x.split(':')[1].trim())
.map((x) => x.split(',')[0].trim())
this.fonts.sort()
})
}
fontAutocomplete = (text$: Observable<string>) => {
return text$
.debounceTime(200)
.distinctUntilChanged()
.map(query => this.fonts.filter(v => new RegExp(query, 'gi').test(v)))
.map(list => Array.from(new Set(list)))
} }
ngOnDestroy () { ngOnDestroy () {
this.config.save() this.config.save()
} }
requestRestart () {
this.restartRequested = true
}
restartApp () { restartApp () {
this.electron.app.relaunch() this.electron.app.relaunch()
this.electron.app.exit() this.electron.app.exit()

View file

@ -0,0 +1,25 @@
import { Component, Input, ViewContainerRef, ViewChild, ComponentFactoryResolver, ComponentRef } from '@angular/core'
import { SettingsProvider } from '../api'
@Component({
selector: 'settings-tab-body',
template: '<template #placeholder></template>',
})
export class SettingsTabBodyComponent {
@Input() provider: SettingsProvider
@ViewChild('placeholder', {read: ViewContainerRef}) placeholder: ViewContainerRef
private component: ComponentRef<Component>
constructor (private componentFactoryResolver: ComponentFactoryResolver) { }
ngAfterViewInit () {
// run after the change detection finishes
setImmediate(() => {
this.component = this.placeholder.createComponent(
this.componentFactoryResolver.resolveComponentFactory(
this.provider.getComponentType()
)
)
})
}
}

View file

@ -8,6 +8,7 @@ import { HotkeyDisplayComponent } from './components/hotkeyDisplay'
import { HotkeyHintComponent } from './components/hotkeyHint' import { HotkeyHintComponent } from './components/hotkeyHint'
import { HotkeyInputModalComponent } from './components/hotkeyInputModal' import { HotkeyInputModalComponent } from './components/hotkeyInputModal'
import { SettingsPaneComponent } from './components/settingsPane' import { SettingsPaneComponent } from './components/settingsPane'
import { SettingsTabBodyComponent } from './components/settingsTabBody'
import { ToolbarButtonProvider, TabRecoveryProvider } from 'api' import { ToolbarButtonProvider, TabRecoveryProvider } from 'api'
@ -35,6 +36,7 @@ import { RecoveryProvider } from './recoveryProvider'
HotkeyInputComponent, HotkeyInputComponent,
HotkeyInputModalComponent, HotkeyInputModalComponent,
SettingsPaneComponent, SettingsPaneComponent,
SettingsTabBodyComponent,
], ],
}) })
class SettingsModule { class SettingsModule {

View file

@ -0,0 +1,56 @@
.row
.col-sm-6
.form-group
label Preview
.appearance-preview(
[style.font-family]='config.full().appearance.font',
[style.font-size]='config.full().appearance.fontSize + "px"',
)
.text john@doe-pc$ ls
.text foo bar
.col-sm-6
.form-group
label Font
input.form-control(
type='text',
[ngbTypeahead]='fontAutocomplete',
'[(ngModel)]'='config.store.appearance.font',
(ngModelChange)='config.save()',
)
small.form-text.text-muted Font to be used in the terminal
.form-group
label Font size
input.form-control(
type='number',
'[(ngModel)]'='config.store.appearance.fontSize',
(ngModelChange)='config.save()',
)
small.form-text.text-muted Text size to be used in the terminal
.form-group
label Terminal bell
br
div(
'[(ngModel)]'='config.store.terminal.bell',
(ngModelChange)='config.save()',
ngbRadioGroup
)
label.btn.btn-secondary
input(
type='radio',
[value]='"off"'
)
| Off
label.btn.btn-secondary
input(
type='radio',
[value]='"sound"'
)
| Sound
label.btn.btn-secondary
input(
type='radio',
[value]='"notification"'
)
| Notification

View file

@ -0,0 +1,9 @@
.appearance-preview {
background: black;
padding: 10px 20px;
margin: 0 0 10px;
.text {
color: white;
}
}

View file

@ -0,0 +1,42 @@
import { Observable } from 'rxjs/Observable'
import 'rxjs/add/operator/map'
import 'rxjs/add/operator/debounceTime'
import 'rxjs/add/operator/distinctUntilChanged'
import { Component } from '@angular/core'
const childProcessPromise = nodeRequire('child-process-promise')
import { ConfigService } from 'services/config'
@Component({
template: require('./settings.pug'),
styles: [require('./settings.scss')],
})
export class SettingsComponent {
fonts: string[] = []
constructor(
public config: ConfigService,
) { }
ngOnInit () {
childProcessPromise.exec('fc-list :spacing=mono').then((result) => {
this.fonts = result.stdout
.split('\n')
.filter((x) => !!x)
.map((x) => x.split(':')[1].trim())
.map((x) => x.split(',')[0].trim())
this.fonts.sort()
})
}
fontAutocomplete = (text$: Observable<string>) => {
return text$
.debounceTime(200)
.distinctUntilChanged()
.map(query => this.fonts.filter(v => new RegExp(query, 'gi').test(v)))
.map(list => Array.from(new Set(list)))
}
}

View file

@ -1,33 +1,40 @@
import { NgModule } from '@angular/core' import { NgModule } from '@angular/core'
import { BrowserModule } from '@angular/platform-browser' import { BrowserModule } from '@angular/platform-browser'
import { FormsModule } from '@angular/forms' import { FormsModule } from '@angular/forms'
import { NgbModule } from '@ng-bootstrap/ng-bootstrap'
import { ToolbarButtonProvider, TabRecoveryProvider } from 'api' import { ToolbarButtonProvider, TabRecoveryProvider } from 'api'
import { SettingsProvider } from '../settings/api'
import { TerminalTabComponent } from './components/terminalTab' import { TerminalTabComponent } from './components/terminalTab'
import { SettingsComponent } from './components/settings'
import { SessionsService } from './services/sessions' import { SessionsService } from './services/sessions'
import { ScreenPersistenceProvider } from './persistenceProviders' import { ScreenPersistenceProvider } from './persistenceProviders'
import { ButtonProvider } from './buttonProvider' import { ButtonProvider } from './buttonProvider'
import { RecoveryProvider } from './recoveryProvider' import { RecoveryProvider } from './recoveryProvider'
import { SessionPersistenceProvider } from './api' import { SessionPersistenceProvider } from './api'
import { TerminalSettingsProvider } from './settings'
@NgModule({ @NgModule({
imports: [ imports: [
BrowserModule, BrowserModule,
FormsModule, FormsModule,
NgbModule,
], ],
providers: [ providers: [
{ provide: ToolbarButtonProvider, useClass: ButtonProvider, multi: true }, { provide: ToolbarButtonProvider, useClass: ButtonProvider, multi: true },
{ provide: TabRecoveryProvider, useClass: RecoveryProvider, multi: true }, { provide: TabRecoveryProvider, useClass: RecoveryProvider, multi: true },
SessionsService, SessionsService,
{ provide: SessionPersistenceProvider, useClass: ScreenPersistenceProvider }, { provide: SessionPersistenceProvider, useClass: ScreenPersistenceProvider },
{ provide: SettingsProvider, useClass: TerminalSettingsProvider, multi: true },
], ],
entryComponents: [ entryComponents: [
TerminalTabComponent, TerminalTabComponent,
SettingsComponent,
], ],
declarations: [ declarations: [
TerminalTabComponent, TerminalTabComponent,
SettingsComponent,
], ],
}) })
class TerminalModule { class TerminalModule {

View file

@ -0,0 +1,13 @@
import { Injectable } from '@angular/core'
import { SettingsProvider, ComponentType } from '../settings/api'
import { SettingsComponent } from './components/settings'
@Injectable()
export class TerminalSettingsProvider extends SettingsProvider {
title = 'Terminal'
getComponentType (): ComponentType {
return SettingsComponent
}
}