1
1
mirror of https://github.com/Eugeny/tabby.git synced 2024-12-22 18:11:43 +03:00
This commit is contained in:
Eugene Pankov 2017-03-24 22:24:12 +01:00
parent 3b47047052
commit 739750e8f0
15 changed files with 281 additions and 192 deletions

View File

@ -9,4 +9,4 @@ html
script(src='./preload.js')
script(src='./bundle.js', defer)
body(style='background: ; min-height: 100vh')
app
app-root

20
app/src/api.ts Normal file
View File

@ -0,0 +1,20 @@
export { AppService } from 'services/app'
export { PluginsService } from 'services/plugins'
export { Tab } from 'models/tab'
export interface IPlugin {
}
export interface IToolbarButton {
icon: string
title: string
weight?: number
click: () => void
}
export interface IToolbarButtonProvider {
provide (): IToolbarButton[]
}
export const ToolbarButtonProviderType = 'app:toolbar-button-provider'

View File

@ -5,6 +5,7 @@ import { FormsModule } from '@angular/forms'
import { ToasterModule } from 'angular2-toaster'
import { NgbModule } from '@ng-bootstrap/ng-bootstrap'
import { AppService } from 'services/app'
import { ConfigService } from 'services/config'
import { ElectronService } from 'services/electron'
import { HostAppService } from 'services/hostApp'
@ -12,11 +13,11 @@ import { LogService } from 'services/log'
import { HotkeysService } from 'services/hotkeys'
import { ModalService } from 'services/modal'
import { NotifyService } from 'services/notify'
import { PluginDispatcherService } from 'services/pluginDispatcher'
import { PluginsService } from 'services/plugins'
import { QuitterService } from 'services/quitter'
import { DockingService } from 'services/docking'
import { AppComponent } from 'components/app'
import { AppRootComponent } from 'components/appRoot'
import { CheckboxComponent } from 'components/checkbox'
import { TabBodyComponent } from 'components/tabBody'
import { TabHeaderComponent } from 'components/tabHeader'
@ -37,6 +38,7 @@ let plugins = [
NgbModule.forRoot(),
].concat(plugins),
providers: [
AppService,
ConfigService,
DockingService,
ElectronService,
@ -45,24 +47,24 @@ let plugins = [
LogService,
ModalService,
NotifyService,
PluginDispatcherService,
PluginsService,
QuitterService,
],
entryComponents: [
],
declarations: [
AppComponent,
AppRootComponent,
CheckboxComponent,
TabBodyComponent,
TabHeaderComponent,
TitleBarComponent,
],
bootstrap: [
AppComponent,
AppRootComponent,
]
})
export class AppModule {
constructor (pluginDispatcher: PluginDispatcherService) {
pluginDispatcher.register(require('./plugin.hyperlinks').default)
constructor () {
//pluginDispatcher.register(require('./plugin.hyperlinks').default)
}
}

View File

@ -1,35 +0,0 @@
title-bar(*ngIf='!config.store.appearance.useNativeFrame && config.store.appearance.dock == "off"')
.spacer
.tabs(class='active-tab-{{tabs.indexOf(activeTab)}}')
button.btn.btn-secondary.btn-new-tab((click)='newTab()')
i.fa.fa-plus
tab-header(
*ngFor='let tab of tabs; let idx = index; trackBy: tab?.id',
[index]='idx',
[model]='tab',
[active]='tab == activeTab',
[hasActivity]='tab.hasActivity',
@animateTab,
(click)='selectTab(tab)',
(closeClicked)='closeTab(tab)',
)
button.btn.btn-secondary.btn-settings((click)='showSettings()')
i.fa.fa-cog
.tabs-content
tab-body(
*ngFor='let tab of tabs; trackBy: tab?.id',
[active]='tab == activeTab',
[model]='tab',
[class.scrollable]='tab.scrollable',
)
// TODO
//hotkey-hint
toaster-container([toasterconfig]="toasterconfig")
template(ngbModalContainer)
div.window-resizer.window-resizer-tl

View File

@ -0,0 +1,43 @@
title-bar(*ngIf='!config.store.appearance.useNativeFrame && config.store.appearance.dock == "off"')
.spacer
.tabs(class='active-tab-{{app.tabs.indexOf(app.activeTab)}}')
button.btn.btn-secondary(
*ngFor='let button of getToolbarButtons(false)',
[title]='button.title',
(click)='button.click()',
)
i.fa([class]='"fa fa-" + button.icon')
tab-header(
*ngFor='let tab of app.tabs; let idx = index; trackBy: tab?.id',
[index]='idx',
[model]='tab',
[active]='tab == app.activeTab',
[hasActivity]='tab.hasActivity',
@animateTab,
(click)='app.selectTab(tab)',
(closeClicked)='app.closeTab(tab)',
)
button.btn.btn-secondary(
*ngFor='let button of getToolbarButtons(true)',
[title]='button.title',
(click)='button.click()',
)
i.fa([class]='"fa fa-" + button.icon')
.tabs-content
tab-body(
*ngFor='let tab of app.tabs; trackBy: tab?.id',
[active]='tab == app.activeTab',
[model]='tab',
[class.scrollable]='tab.scrollable',
)
// TODO
//hotkey-hint
toaster-container([toasterconfig]="toasterconfig")
template(ngbModalContainer)
div.window-resizer.window-resizer-tl

View File

@ -1,4 +1,4 @@
import { Component, Input, trigger, style, animate, transition, state } from '@angular/core'
import { Component, trigger, style, animate, transition, state } from '@angular/core'
import { ToasterConfig } from 'angular2-toaster'
import { ElectronService } from 'services/electron'
@ -8,10 +8,9 @@ import { LogService } from 'services/log'
import { QuitterService } from 'services/quitter'
import { ConfigService } from 'services/config'
import { DockingService } from 'services/docking'
// import { SessionsService } from 'services/sessions'
import { PluginDispatcherService } from 'services/pluginDispatcher'
import { PluginsService } from 'services/plugins'
import { Tab } from 'models/tab'
import { AppService, IToolbarButton, IToolbarButtonProvider, ToolbarButtonProviderType } from 'api'
import 'angular2-toaster/lib/toaster.css'
import 'global.less'
@ -19,9 +18,9 @@ import 'theme.scss'
@Component({
selector: 'app',
template: require('./app.pug'),
styles: [require('./app.less')],
selector: 'app-root',
template: require('./appRoot.pug'),
styles: [require('./appRoot.less')],
animations: [
trigger('animateTab', [
state('in', style({
@ -41,27 +40,24 @@ import 'theme.scss'
])
]
})
export class AppComponent {
export class AppRootComponent {
toasterConfig: ToasterConfig
@Input() tabs: Tab[] = []
@Input() activeTab: Tab
lastTabIndex = 0
constructor(
// private sessions: SessionsService,
private docking: DockingService,
private electron: ElectronService,
public hostApp: HostAppService,
public hotkeys: HotkeysService,
public config: ConfigService,
private pluginDispatcher: PluginDispatcherService,
private plugins: PluginsService,
public app: AppService,
log: LogService,
_quitter: QuitterService,
) {
console.timeStamp('AppComponent ctor')
let logger = log.create('main')
logger.info('ELEMENTS client', electron.app.getVersion())
logger.info('v', electron.app.getVersion())
this.toasterConfig = new ToasterConfig({
mouseoverTimerStop: true,
@ -71,42 +67,26 @@ export class AppComponent {
this.hotkeys.matchedHotkey.subscribe((hotkey) => {
if (hotkey == 'new-tab') {
this.newTab()
// TODO this.newTab()
}
if (hotkey.startsWith('tab-')) {
let index = parseInt(hotkey.split('-')[1])
if (index <= this.tabs.length) {
this.selectTab(this.tabs[index - 1])
if (index <= this.app.tabs.length) {
this.app.selectTab(this.app.tabs[index - 1])
}
}
if (this.activeTab) {
if (this.app.activeTab) {
if (hotkey == 'close-tab') {
this.closeTab(this.activeTab)
this.app.closeTab(this.app.activeTab)
}
if (hotkey == 'toggle-last-tab') {
this.toggleLastTab()
this.app.toggleLastTab()
}
if (hotkey == 'next-tab') {
this.nextTab()
this.app.nextTab()
}
if (hotkey == 'previous-tab') {
this.previousTab()
}
}
})
this.hotkeys.key.subscribe((key) => {
if (key.event == 'keydown') {
if (key.alt && key.key >= '1' && key.key <= '9') {
let index = key.key.charCodeAt(0) - '0'.charCodeAt(0) - 1
if (index < this.tabs.length) {
this.selectTab(this.tabs[index])
}
}
if (key.alt && key.key == '0') {
if (this.tabs.length >= 10) {
this.selectTab(this.tabs[9])
}
this.app.previousTab()
}
}
})
@ -145,57 +125,15 @@ export class AppComponent {
})
}
newTab () {
const tab = this.pluginDispatcher.temp2('zsh')
this.tabs.push(tab)
this.selectTab(tab)
}
selectTab (tab) {
if (this.tabs.includes(this.activeTab)) {
this.lastTabIndex = this.tabs.indexOf(this.activeTab)
} else {
this.lastTabIndex = null
}
if (this.activeTab) {
this.activeTab.hasActivity = false
this.activeTab.blurred.emit()
}
this.activeTab = tab
this.activeTab.focused.emit()
}
toggleLastTab () {
if (!this.lastTabIndex || this.lastTabIndex >= this.tabs.length) {
this.lastTabIndex = 0
}
this.selectTab(this.tabs[this.lastTabIndex])
}
nextTab () {
let tabIndex = this.tabs.indexOf(this.activeTab)
if (tabIndex < this.tabs.length - 1) {
this.selectTab(this.tabs[tabIndex + 1])
}
}
previousTab () {
let tabIndex = this.tabs.indexOf(this.activeTab)
if (tabIndex > 0) {
this.selectTab(this.tabs[tabIndex - 1])
}
}
closeTab (tab) {
tab.destroy()
/* if (tab.session) {
this.sessions.destroySession(tab.session)
} */
let newIndex = Math.max(0, this.tabs.indexOf(tab) - 1)
this.tabs = this.tabs.filter((x) => x != tab)
if (tab == this.activeTab) {
this.selectTab(this.tabs[newIndex])
}
getToolbarButtons (aboveZero: boolean): IToolbarButton[] {
let buttons: IToolbarButton[] = []
this.plugins.getAll<IToolbarButtonProvider>(ToolbarButtonProviderType)
.forEach((provider) => {
buttons = buttons.concat(provider.provide())
})
return buttons
.filter((button) => (button.weight > 0) === aboveZero)
.sort((a: IToolbarButton, b: IToolbarButton) => (a.weight || 0) - (b.weight || 0))
}
ngOnInit () {
@ -212,14 +150,4 @@ export class AppComponent {
})
*/
}
showSettings() {
const SettingsTab = this.pluginDispatcher.temp
let settingsTab = this.tabs.find((x) => x instanceof SettingsTab)
if (!settingsTab) {
settingsTab = new SettingsTab()
this.tabs.push(settingsTab)
}
this.selectTab(settingsTab)
}
}

66
app/src/services/app.ts Normal file
View File

@ -0,0 +1,66 @@
import { EventEmitter, Injectable } from '@angular/core'
import { Tab } from 'models/tab'
@Injectable()
export class AppService {
tabs: Tab[] = []
activeTab: Tab
lastTabIndex = 0
constructor () {
}
openTab (tab: Tab): void {
this.tabs.push(tab)
this.selectTab(tab)
}
selectTab (tab) {
if (this.tabs.includes(this.activeTab)) {
this.lastTabIndex = this.tabs.indexOf(this.activeTab)
} else {
this.lastTabIndex = null
}
if (this.activeTab) {
this.activeTab.hasActivity = false
this.activeTab.blurred.emit()
}
this.activeTab = tab
this.activeTab.focused.emit()
}
toggleLastTab () {
if (!this.lastTabIndex || this.lastTabIndex >= this.tabs.length) {
this.lastTabIndex = 0
}
this.selectTab(this.tabs[this.lastTabIndex])
}
nextTab () {
let tabIndex = this.tabs.indexOf(this.activeTab)
if (tabIndex < this.tabs.length - 1) {
this.selectTab(this.tabs[tabIndex + 1])
}
}
previousTab () {
let tabIndex = this.tabs.indexOf(this.activeTab)
if (tabIndex > 0) {
this.selectTab(this.tabs[tabIndex - 1])
}
}
closeTab (tab) {
tab.destroy()
/* if (tab.session) {
this.sessions.destroySession(tab.session)
} */
let newIndex = Math.max(0, this.tabs.indexOf(tab) - 1)
this.tabs = this.tabs.filter((x) => x != tab)
if (tab == this.activeTab) {
this.selectTab(this.tabs[newIndex])
}
}
}

View File

@ -1,34 +0,0 @@
import { Injectable } from '@angular/core'
import { ConfigService } from 'services/config'
import { ElectronService } from 'services/electron'
@Injectable()
export class PluginDispatcherService {
plugins = []
temp: any
temp2: any
constructor (
private config: ConfigService,
private electron: ElectronService,
) {
}
register (plugin) {
if (!this.plugins.includes(plugin)) {
this.plugins.push(new plugin({
config: this.config,
electron: this.electron,
}))
}
}
emit (event: string, parameters: any) {
this.plugins.forEach((plugin) => {
if (plugin[event]) {
plugin[event].bind(plugin)(parameters)
}
})
}
}

View File

@ -0,0 +1,45 @@
import { IPlugin } from 'api'
import { Injectable } from '@angular/core'
interface IPluginEntry {
plugin: IPlugin
weight: number
}
@Injectable()
export class PluginsService {
plugins: {[type: string]: IPluginEntry[]} = {}
constructor (
) {
}
register (type: string, plugin: IPlugin, weight = 0): void {
if (!this.plugins[type]) {
this.plugins[type] = []
}
this.plugins[type].push({ plugin, weight })
}
getAll<T extends IPlugin> (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)
}
})
}
}

View File

@ -0,0 +1,27 @@
import { Injectable } from '@angular/core'
import { IToolbarButtonProvider, IToolbarButton, AppService } from 'api'
import { SettingsTab } from './tab'
@Injectable()
export class ButtonProvider implements IToolbarButtonProvider {
constructor (
private app: AppService,
) { }
provide (): IToolbarButton[] {
return [{
icon: 'cog',
title: 'Settings',
weight: 10,
click: () => {
let settingsTab = this.app.tabs.find((tab) => tab instanceof SettingsTab)
if (settingsTab) {
this.app.selectTab(settingsTab)
} else {
this.app.openTab(new SettingsTab())
}
}
}]
}
}

View File

@ -8,9 +8,11 @@ import { HotkeyDisplayComponent } from './components/hotkeyDisplay'
import { HotkeyHintComponent } from './components/hotkeyHint'
import { HotkeyInputModalComponent } from './components/hotkeyInputModal'
import { SettingsPaneComponent } from './components/settingsPane'
import { PluginDispatcherService } from 'services/pluginDispatcher'
import { SettingsTab } from './tab'
import { PluginsService, ToolbarButtonProviderType } from 'api'
import { ButtonProvider } from './buttonProvider'
@NgModule({
imports: [
@ -19,6 +21,7 @@ import { SettingsTab } from './tab'
NgbModule,
],
providers: [
ButtonProvider,
],
entryComponents: [
HotkeyInputModalComponent,
@ -33,8 +36,8 @@ import { SettingsTab } from './tab'
],
})
class SettingsModule {
constructor (pluginDispatcher: PluginDispatcherService) {
pluginDispatcher.temp = SettingsTab
constructor (plugins: PluginsService, buttonProvider: ButtonProvider) {
plugins.register(ToolbarButtonProviderType, buttonProvider, 1)
}
}

View File

@ -0,0 +1,26 @@
import { Injectable } from '@angular/core'
import { IToolbarButtonProvider, IToolbarButton, AppService } from 'api'
import { SessionsService } from './services/sessions'
import { TerminalTab } from './tab'
@Injectable()
export class ButtonProvider implements IToolbarButtonProvider {
constructor (
private app: AppService,
private sessions: SessionsService,
) {
}
provide (): IToolbarButton[] {
return [{
icon: 'plus',
title: 'New terminal',
click: () => {
let session = this.sessions.createNewSession({ command: 'zsh' })
this.app.openTab(new TerminalTab(session))
}
}]
}
}

View File

@ -2,7 +2,7 @@ import { Subscription } from 'rxjs'
import { Component, NgZone, Output, EventEmitter, ElementRef } from '@angular/core'
import { ConfigService } from 'services/config'
import { PluginDispatcherService } from 'services/pluginDispatcher'
import { PluginsService } from 'services/plugins'
import { BaseTabComponent } from 'components/baseTab'
import { TerminalTab } from '../tab'
@ -27,7 +27,7 @@ export class TerminalTabComponent extends BaseTabComponent<TerminalTab> {
private zone: NgZone,
private elementRef: ElementRef,
public config: ConfigService,
private pluginDispatcher: PluginDispatcherService,
private plugins: PluginsService,
) {
super()
this.startupTime = performance.now()
@ -42,7 +42,7 @@ export class TerminalTabComponent extends BaseTabComponent<TerminalTab> {
})
this.terminal = new hterm.hterm.Terminal()
this.pluginDispatcher.emit('preTerminalInit', { terminal: this.terminal })
//this.pluginDispatcher.emit('preTerminalInit', { terminal: this.terminal })
this.terminal.setWindowTitle = (title) => {
this.zone.run(() => {
this.title = title
@ -77,7 +77,7 @@ export class TerminalTabComponent extends BaseTabComponent<TerminalTab> {
}
this.terminal.decorate(this.elementRef.nativeElement)
this.configure()
this.pluginDispatcher.emit('postTerminalInit', { terminal: this.terminal })
//this.pluginDispatcher.emit('postTerminalInit', { terminal: this.terminal })
}
configure () {

View File

@ -2,11 +2,11 @@ import { BrowserModule } from '@angular/platform-browser'
import { NgModule } from '@angular/core'
import { FormsModule } from '@angular/forms'
import { PluginsService, ToolbarButtonProviderType } from 'api'
import { TerminalTabComponent } from './components/terminalTab'
import { SessionsService } from './services/sessions'
import { TerminalTab } from './tab'
import { PluginDispatcherService } from 'services/pluginDispatcher'
import { ButtonProvider } from './buttonProvider'
@NgModule({
@ -15,6 +15,7 @@ import { PluginDispatcherService } from 'services/pluginDispatcher'
FormsModule,
],
providers: [
ButtonProvider,
SessionsService,
],
entryComponents: [
@ -25,11 +26,8 @@ import { PluginDispatcherService } from 'services/pluginDispatcher'
],
})
class TerminalModule {
constructor (pluginDispatcher: PluginDispatcherService, sessions: SessionsService) {
pluginDispatcher.temp2 = (command) => {
let session = sessions.createNewSession({ command }))
return new TerminalTab(session)
}
constructor (plugins: PluginsService, buttonProvider: ButtonProvider) {
plugins.register(ToolbarButtonProviderType, buttonProvider)
}
}