mirror of
https://github.com/Eugeny/tabby
synced 2024-11-14 08:57:21 +00:00
.
This commit is contained in:
parent
c9ead5e93d
commit
482343e383
14 changed files with 197 additions and 161 deletions
|
@ -3,7 +3,8 @@ import { BaseTabComponent } from 'components/baseTab'
|
|||
|
||||
export declare type ComponentType<T extends Tab> = new (...args: any[]) => BaseTabComponent<T>
|
||||
|
||||
export class Tab {
|
||||
|
||||
export abstract class Tab {
|
||||
id: number
|
||||
title: string
|
||||
scrollable: boolean
|
||||
|
@ -20,9 +21,7 @@ export class Tab {
|
|||
this.hasActivity = true
|
||||
}
|
||||
|
||||
getComponentType (): ComponentType<Tab> {
|
||||
return null
|
||||
}
|
||||
abstract getComponentType (): ComponentType<Tab>
|
||||
|
||||
getRecoveryToken (): any {
|
||||
return null
|
||||
|
|
|
@ -30,6 +30,11 @@ export interface IConfigData {
|
|||
|
||||
@Injectable()
|
||||
export class ConfigService {
|
||||
store: IConfigData
|
||||
change = new EventEmitter()
|
||||
restartRequested: boolean
|
||||
private path: string
|
||||
|
||||
constructor (
|
||||
electron: ElectronService
|
||||
) {
|
||||
|
@ -37,10 +42,6 @@ export class ConfigService {
|
|||
this.load()
|
||||
}
|
||||
|
||||
private path: string
|
||||
store: IConfigData
|
||||
change = new EventEmitter()
|
||||
|
||||
load () {
|
||||
if (fs.existsSync(this.path)) {
|
||||
this.store = configMerge(defaultConfigStructure, yaml.safeLoad(fs.readFileSync(this.path, 'utf8')))
|
||||
|
@ -61,4 +62,8 @@ export class ConfigService {
|
|||
emitChange () {
|
||||
this.change.emit()
|
||||
}
|
||||
|
||||
requestRestart () {
|
||||
this.restartRequested = true
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,44 +1,21 @@
|
|||
import { Injectable } from '@angular/core'
|
||||
|
||||
|
||||
interface IPluginEntry {
|
||||
plugin: any
|
||||
weight: number
|
||||
class Plugin {
|
||||
ngModule: any
|
||||
name: string
|
||||
}
|
||||
|
||||
|
||||
@Injectable()
|
||||
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 {
|
||||
if (!this.plugins[type]) {
|
||||
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)
|
||||
}
|
||||
})
|
||||
getModules (): any[] {
|
||||
return this.plugins.map((plugin) => plugin.ngModule)
|
||||
}
|
||||
}
|
||||
|
|
11
app/src/settings/api.ts
Normal file
11
app/src/settings/api.ts
Normal 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
|
||||
}
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
:host {
|
||||
flex: auto;
|
||||
margin: 15px;
|
||||
|
||||
|
||||
>.btn-block {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
@ -9,14 +9,4 @@
|
|||
>.modal-body {
|
||||
padding: 0 0 20px !important;
|
||||
}
|
||||
|
||||
.appearance-preview {
|
||||
background: black;
|
||||
padding: 10px 20px;
|
||||
margin: 0 0 10px;
|
||||
|
||||
.text {
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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-tab(*ngFor='let provider of settingsProviders')
|
||||
template(ngbTabTitle)
|
||||
| {{provider.title}}
|
||||
template(ngbTabContent)
|
||||
settings-tab-body([provider]='provider')
|
||||
ngb-tab
|
||||
template(ngbTabTitle)
|
||||
| Application
|
||||
|
@ -124,71 +129,6 @@ ngb-tabset(type='tabs')
|
|||
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
|
||||
template(ngbTabTitle)
|
||||
| Hotkeys
|
||||
|
|
|
@ -1,16 +1,11 @@
|
|||
import { Component } from '@angular/core'
|
||||
import { Component, Inject } from '@angular/core'
|
||||
import { ElectronService } from 'services/electron'
|
||||
import { HostAppService, PLATFORM_WINDOWS, PLATFORM_LINUX, PLATFORM_MAC } from 'services/hostApp'
|
||||
import { ConfigService } from 'services/config'
|
||||
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 { SettingsTab } from '../tab'
|
||||
import { SettingsProvider } from '../api'
|
||||
|
||||
|
||||
@Component({
|
||||
|
@ -19,56 +14,21 @@ import { SettingsTab } from '../tab'
|
|||
styles: [require('./settingsPane.less')],
|
||||
})
|
||||
export class SettingsPaneComponent extends BaseTabComponent<SettingsTab> {
|
||||
isWindows: boolean
|
||||
isMac: boolean
|
||||
isLinux: boolean
|
||||
year: number
|
||||
version: string
|
||||
fonts: string[] = []
|
||||
restartRequested: boolean
|
||||
globalHotkey = ['Ctrl+Shift+G']
|
||||
|
||||
constructor(
|
||||
public config: ConfigService,
|
||||
private electron: ElectronService,
|
||||
public docking: DockingService,
|
||||
hostApp: HostAppService,
|
||||
@Inject(SettingsProvider) public settingsProviders: SettingsProvider[]
|
||||
) {
|
||||
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 () {
|
||||
this.config.save()
|
||||
}
|
||||
|
||||
requestRestart () {
|
||||
this.restartRequested = true
|
||||
}
|
||||
|
||||
restartApp () {
|
||||
this.electron.app.relaunch()
|
||||
this.electron.app.exit()
|
||||
|
|
25
app/src/settings/components/settingsTabBody.ts
Normal file
25
app/src/settings/components/settingsTabBody.ts
Normal 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()
|
||||
)
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
|
@ -8,6 +8,7 @@ import { HotkeyDisplayComponent } from './components/hotkeyDisplay'
|
|||
import { HotkeyHintComponent } from './components/hotkeyHint'
|
||||
import { HotkeyInputModalComponent } from './components/hotkeyInputModal'
|
||||
import { SettingsPaneComponent } from './components/settingsPane'
|
||||
import { SettingsTabBodyComponent } from './components/settingsTabBody'
|
||||
|
||||
import { ToolbarButtonProvider, TabRecoveryProvider } from 'api'
|
||||
|
||||
|
@ -35,6 +36,7 @@ import { RecoveryProvider } from './recoveryProvider'
|
|||
HotkeyInputComponent,
|
||||
HotkeyInputModalComponent,
|
||||
SettingsPaneComponent,
|
||||
SettingsTabBodyComponent,
|
||||
],
|
||||
})
|
||||
class SettingsModule {
|
||||
|
|
56
app/src/terminal/components/settings.pug
Normal file
56
app/src/terminal/components/settings.pug
Normal 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
|
9
app/src/terminal/components/settings.scss
Normal file
9
app/src/terminal/components/settings.scss
Normal file
|
@ -0,0 +1,9 @@
|
|||
.appearance-preview {
|
||||
background: black;
|
||||
padding: 10px 20px;
|
||||
margin: 0 0 10px;
|
||||
|
||||
.text {
|
||||
color: white;
|
||||
}
|
||||
}
|
42
app/src/terminal/components/settings.ts
Normal file
42
app/src/terminal/components/settings.ts
Normal 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)))
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -1,33 +1,40 @@
|
|||
import { NgModule } from '@angular/core'
|
||||
import { BrowserModule } from '@angular/platform-browser'
|
||||
import { FormsModule } from '@angular/forms'
|
||||
import { NgbModule } from '@ng-bootstrap/ng-bootstrap'
|
||||
|
||||
import { ToolbarButtonProvider, TabRecoveryProvider } from 'api'
|
||||
import { SettingsProvider } from '../settings/api'
|
||||
|
||||
import { TerminalTabComponent } from './components/terminalTab'
|
||||
import { SettingsComponent } from './components/settings'
|
||||
import { SessionsService } from './services/sessions'
|
||||
import { ScreenPersistenceProvider } from './persistenceProviders'
|
||||
import { ButtonProvider } from './buttonProvider'
|
||||
import { RecoveryProvider } from './recoveryProvider'
|
||||
import { SessionPersistenceProvider } from './api'
|
||||
|
||||
import { TerminalSettingsProvider } from './settings'
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
BrowserModule,
|
||||
FormsModule,
|
||||
NgbModule,
|
||||
],
|
||||
providers: [
|
||||
{ provide: ToolbarButtonProvider, useClass: ButtonProvider, multi: true },
|
||||
{ provide: TabRecoveryProvider, useClass: RecoveryProvider, multi: true },
|
||||
SessionsService,
|
||||
{ provide: SessionPersistenceProvider, useClass: ScreenPersistenceProvider },
|
||||
{ provide: SettingsProvider, useClass: TerminalSettingsProvider, multi: true },
|
||||
],
|
||||
entryComponents: [
|
||||
TerminalTabComponent,
|
||||
SettingsComponent,
|
||||
],
|
||||
declarations: [
|
||||
TerminalTabComponent,
|
||||
SettingsComponent,
|
||||
],
|
||||
})
|
||||
class TerminalModule {
|
||||
|
|
13
app/src/terminal/settings.ts
Normal file
13
app/src/terminal/settings.ts
Normal 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
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue