mirror of
https://github.com/Eugeny/tabby
synced 2024-11-14 08:57:21 +00:00
proper tab classes
This commit is contained in:
parent
2cca57e0fb
commit
79cd2a3bbb
27 changed files with 208 additions and 227 deletions
|
@ -1,3 +1,3 @@
|
|||
export abstract class DefaultTabProvider {
|
||||
abstract open (): void
|
||||
abstract async openNewTab (): Promise<void>
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
export { Tab } from './tab'
|
||||
export { BaseTabComponent } from '../components/baseTab'
|
||||
export { TabRecoveryProvider } from './tabRecovery'
|
||||
export { ToolbarButtonProvider, IToolbarButton } from './toolbarButtonProvider'
|
||||
export { ConfigProvider } from './configProvider'
|
||||
|
|
|
@ -1,32 +0,0 @@
|
|||
import { EventEmitter } from '@angular/core'
|
||||
import { BaseTabComponent } from 'components/baseTab'
|
||||
|
||||
export declare type ComponentType<T extends Tab> = new (...args: any[]) => BaseTabComponent<T>
|
||||
|
||||
|
||||
export abstract class Tab {
|
||||
id: number
|
||||
title: string
|
||||
scrollable: boolean
|
||||
hasActivity = false
|
||||
focused = new EventEmitter<any>()
|
||||
blurred = new EventEmitter<any>()
|
||||
static lastTabID = 0
|
||||
|
||||
constructor () {
|
||||
this.id = Tab.lastTabID++
|
||||
}
|
||||
|
||||
displayActivity (): void {
|
||||
this.hasActivity = true
|
||||
}
|
||||
|
||||
abstract getComponentType (): ComponentType<Tab>
|
||||
|
||||
getRecoveryToken (): any {
|
||||
return null
|
||||
}
|
||||
|
||||
destroy (): void {
|
||||
}
|
||||
}
|
|
@ -1,5 +1,3 @@
|
|||
import { Tab } from './tab'
|
||||
|
||||
export abstract class TabRecoveryProvider {
|
||||
abstract async recover (recoveryToken: any): Promise<Tab>
|
||||
abstract async recover (recoveryToken: any): Promise<void>
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ import { NotifyService } from 'services/notify'
|
|||
import { PluginsService } from 'services/plugins'
|
||||
import { QuitterService } from 'services/quitter'
|
||||
import { DockingService } from 'services/docking'
|
||||
import { TabRecoveryService } from 'services/tabRecovery'
|
||||
|
||||
import { AppRootComponent } from 'components/appRoot'
|
||||
import { CheckboxComponent } from 'components/checkbox'
|
||||
|
@ -53,6 +54,7 @@ let plugins = [
|
|||
ModalService,
|
||||
NotifyService,
|
||||
PluginsService,
|
||||
TabRecoveryService,
|
||||
QuitterService,
|
||||
{ provide: HotkeyProvider, useClass: AppHotkeyProvider, multi: true },
|
||||
],
|
||||
|
|
|
@ -17,7 +17,7 @@ title-bar(*ngIf='!config.full().appearance.useNativeFrame && config.store.appear
|
|||
[class.pre-selected]='idx == app.tabs.indexOf(app.activeTab) - 1',
|
||||
[class.post-selected]='idx == app.tabs.indexOf(app.activeTab) + 1',
|
||||
[index]='idx',
|
||||
[model]='tab',
|
||||
[tab]='tab',
|
||||
[active]='tab == app.activeTab',
|
||||
[hasActivity]='tab.hasActivity',
|
||||
@animateTab,
|
||||
|
@ -36,7 +36,7 @@ title-bar(*ngIf='!config.full().appearance.useNativeFrame && config.store.appear
|
|||
tab-body(
|
||||
*ngFor='let tab of app.tabs; trackBy: tab?.id',
|
||||
[active]='tab == app.activeTab',
|
||||
[model]='tab',
|
||||
[tab]='tab',
|
||||
[class.scrollable]='tab.scrollable',
|
||||
)
|
||||
|
||||
|
|
|
@ -5,10 +5,11 @@ import { ToasterConfig } from 'angular2-toaster'
|
|||
import { ElectronService } from 'services/electron'
|
||||
import { HostAppService } from 'services/hostApp'
|
||||
import { HotkeysService } from 'services/hotkeys'
|
||||
import { LogService } from 'services/log'
|
||||
import { Logger, LogService } from 'services/log'
|
||||
import { QuitterService } from 'services/quitter'
|
||||
import { ConfigService } from 'services/config'
|
||||
import { DockingService } from 'services/docking'
|
||||
import { TabRecoveryService } from 'services/tabRecovery'
|
||||
|
||||
import { AppService, IToolbarButton, ToolbarButtonProvider } from 'api'
|
||||
|
||||
|
@ -43,10 +44,12 @@ import 'theme.scss'
|
|||
})
|
||||
export class AppRootComponent {
|
||||
toasterConfig: ToasterConfig
|
||||
logger: Logger
|
||||
|
||||
constructor(
|
||||
private docking: DockingService,
|
||||
private electron: ElectronService,
|
||||
private tabRecovery: TabRecoveryService,
|
||||
public hostApp: HostAppService,
|
||||
public hotkeys: HotkeysService,
|
||||
public config: ConfigService,
|
||||
|
@ -57,8 +60,8 @@ export class AppRootComponent {
|
|||
) {
|
||||
console.timeStamp('AppComponent ctor')
|
||||
|
||||
let logger = log.create('main')
|
||||
logger.info('v', electron.app.getVersion())
|
||||
this.logger = log.create('main')
|
||||
this.logger.info('v', electron.app.getVersion())
|
||||
|
||||
this.toasterConfig = new ToasterConfig({
|
||||
mouseoverTimerStop: true,
|
||||
|
@ -131,7 +134,9 @@ export class AppRootComponent {
|
|||
getRightToolbarButtons (): IToolbarButton[] { return this.getToolbarButtons(true) }
|
||||
|
||||
async ngOnInit () {
|
||||
await this.app.restoreTabs()
|
||||
await this.tabRecovery.recoverTabs()
|
||||
this.tabRecovery.saveTabs(this.app.tabs)
|
||||
|
||||
if (this.app.tabs.length == 0) {
|
||||
this.app.openDefaultTab()
|
||||
}
|
||||
|
|
|
@ -1,12 +1,29 @@
|
|||
import { Tab } from 'api/tab'
|
||||
import { BehaviorSubject } from 'rxjs'
|
||||
import { EventEmitter, ViewRef } from '@angular/core'
|
||||
|
||||
export class BaseTabComponent<T extends Tab> {
|
||||
protected model: T
|
||||
|
||||
initModel (model: T) {
|
||||
this.model = model
|
||||
this.initTab()
|
||||
export abstract class BaseTabComponent {
|
||||
id: number
|
||||
title$ = new BehaviorSubject<string>(null)
|
||||
scrollable: boolean
|
||||
hasActivity = false
|
||||
focused = new EventEmitter<any>()
|
||||
blurred = new EventEmitter<any>()
|
||||
hostView: ViewRef
|
||||
private static lastTabID = 0
|
||||
|
||||
constructor () {
|
||||
this.id = BaseTabComponent.lastTabID++
|
||||
}
|
||||
|
||||
initTab () { }
|
||||
displayActivity (): void {
|
||||
this.hasActivity = true
|
||||
}
|
||||
|
||||
getRecoveryToken (): any {
|
||||
return null
|
||||
}
|
||||
|
||||
destroy (): void {
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { Component, Input, ViewContainerRef, ViewChild, HostBinding, ComponentFactoryResolver, ComponentRef } from '@angular/core'
|
||||
import { Tab } from 'api/tab'
|
||||
import { Component, Input, ViewChild, HostBinding, ViewContainerRef } from '@angular/core'
|
||||
import { BaseTabComponent } from 'components/baseTab'
|
||||
|
||||
@Component({
|
||||
|
@ -9,21 +8,12 @@ import { BaseTabComponent } from 'components/baseTab'
|
|||
})
|
||||
export class TabBodyComponent {
|
||||
@Input() @HostBinding('class.active') active: boolean
|
||||
@Input() model: Tab
|
||||
@Input() tab: BaseTabComponent
|
||||
@ViewChild('placeholder', {read: ViewContainerRef}) placeholder: ViewContainerRef
|
||||
private component: ComponentRef<BaseTabComponent<Tab>>
|
||||
|
||||
constructor (private componentFactoryResolver: ComponentFactoryResolver) {
|
||||
}
|
||||
|
||||
ngAfterViewInit () {
|
||||
// run after the change detection finishes
|
||||
setImmediate(() => {
|
||||
let componentFactory = this.componentFactoryResolver.resolveComponentFactory(this.model.getComponentType())
|
||||
this.component = this.placeholder.createComponent(componentFactory)
|
||||
setImmediate(() => {
|
||||
this.component.instance.initModel(this.model)
|
||||
})
|
||||
this.placeholder.insert(this.tab.hostView)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
.content-wrapper
|
||||
.index {{index + 1}}
|
||||
.name {{model.title || "Terminal"}}
|
||||
.name {{(tab.title$ || "Terminal") | async}}
|
||||
button((click)='closeClicked.emit()') ×
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { Component, Input, Output, EventEmitter, HostBinding } from '@angular/core'
|
||||
import { Tab } from 'api/tab'
|
||||
import { BaseTabComponent } from 'components/baseTab'
|
||||
|
||||
import './tabHeader.scss'
|
||||
|
||||
|
@ -12,6 +12,6 @@ export class TabHeaderComponent {
|
|||
@Input() index: number
|
||||
@Input() @HostBinding('class.active') active: boolean
|
||||
@Input() @HostBinding('class.has-activity') hasActivity: boolean
|
||||
@Input() model: Tab
|
||||
@Input() tab: BaseTabComponent
|
||||
@Output() closeClicked = new EventEmitter()
|
||||
}
|
||||
|
|
|
@ -1,36 +1,49 @@
|
|||
import { Inject, Injectable } from '@angular/core'
|
||||
import { Subject } from 'rxjs'
|
||||
import { Injectable, ComponentFactoryResolver, Injector, Optional } from '@angular/core'
|
||||
import { Logger, LogService } from 'services/log'
|
||||
import { Tab } from 'api/tab'
|
||||
import { TabRecoveryProvider } from 'api/tabRecovery'
|
||||
import { DefaultTabProvider } from 'api/defaultTabProvider'
|
||||
import { BaseTabComponent } from 'components/baseTab'
|
||||
|
||||
export declare type TabComponentType = new (...args: any[]) => BaseTabComponent
|
||||
|
||||
|
||||
@Injectable()
|
||||
export class AppService {
|
||||
tabs: Tab[] = []
|
||||
activeTab: Tab
|
||||
tabs: BaseTabComponent[] = []
|
||||
activeTab: BaseTabComponent
|
||||
lastTabIndex = 0
|
||||
logger: Logger
|
||||
tabsChanged$ = new Subject()
|
||||
|
||||
constructor (
|
||||
@Inject(TabRecoveryProvider) private tabRecoveryProviders: TabRecoveryProvider[],
|
||||
private defaultTabProvider: DefaultTabProvider,
|
||||
private componentFactoryResolver: ComponentFactoryResolver,
|
||||
@Optional() private defaultTabProvider: DefaultTabProvider,
|
||||
private injector: Injector,
|
||||
log: LogService,
|
||||
) {
|
||||
this.logger = log.create('app')
|
||||
}
|
||||
|
||||
openTab (tab: Tab): void {
|
||||
this.tabs.push(tab)
|
||||
this.selectTab(tab)
|
||||
this.saveTabs()
|
||||
openNewTab (type: TabComponentType, inputs?: any): BaseTabComponent {
|
||||
let componentFactory = this.componentFactoryResolver.resolveComponentFactory(type)
|
||||
let componentRef = componentFactory.create(this.injector)
|
||||
componentRef.instance.hostView = componentRef.hostView
|
||||
Object.assign(componentRef.instance, inputs || {})
|
||||
|
||||
this.tabs.push(componentRef.instance)
|
||||
this.selectTab(componentRef.instance)
|
||||
this.tabsChanged$.next()
|
||||
|
||||
return componentRef.instance
|
||||
}
|
||||
|
||||
openDefaultTab (): void {
|
||||
|
||||
if (this.defaultTabProvider) {
|
||||
this.defaultTabProvider.openNewTab()
|
||||
}
|
||||
}
|
||||
|
||||
selectTab (tab) {
|
||||
selectTab (tab: BaseTabComponent) {
|
||||
if (this.tabs.includes(this.activeTab)) {
|
||||
this.lastTabIndex = this.tabs.indexOf(this.activeTab)
|
||||
} else {
|
||||
|
@ -41,7 +54,9 @@ export class AppService {
|
|||
this.activeTab.blurred.emit()
|
||||
}
|
||||
this.activeTab = tab
|
||||
this.activeTab.focused.emit()
|
||||
if (this.activeTab) {
|
||||
this.activeTab.focused.emit()
|
||||
}
|
||||
}
|
||||
|
||||
toggleLastTab () {
|
||||
|
@ -65,7 +80,7 @@ export class AppService {
|
|||
}
|
||||
}
|
||||
|
||||
closeTab (tab) {
|
||||
closeTab (tab: BaseTabComponent) {
|
||||
tab.destroy()
|
||||
/* if (tab.session) {
|
||||
this.sessions.destroySession(tab.session)
|
||||
|
@ -75,38 +90,6 @@ export class AppService {
|
|||
if (tab == this.activeTab) {
|
||||
this.selectTab(this.tabs[newIndex])
|
||||
}
|
||||
this.saveTabs()
|
||||
}
|
||||
|
||||
saveTabs () {
|
||||
window.localStorage.tabsRecovery = JSON.stringify(
|
||||
this.tabs
|
||||
.map((tab) => tab.getRecoveryToken())
|
||||
.filter((token) => !!token)
|
||||
)
|
||||
}
|
||||
|
||||
async restoreTabs (): Promise<void> {
|
||||
if (window.localStorage.tabsRecovery) {
|
||||
for (let token of JSON.parse(window.localStorage.tabsRecovery)) {
|
||||
let tab: Tab
|
||||
for (let provider of this.tabRecoveryProviders) {
|
||||
try {
|
||||
tab = await provider.recover(token)
|
||||
if (tab) {
|
||||
break
|
||||
}
|
||||
} catch (error) {
|
||||
this.logger.warn('Tab recovery crashed:', token, provider, error)
|
||||
}
|
||||
}
|
||||
if (tab) {
|
||||
this.openTab(tab)
|
||||
} else {
|
||||
this.logger.warn('Cannot restore tab from the token:', token)
|
||||
}
|
||||
}
|
||||
this.saveTabs()
|
||||
}
|
||||
this.tabsChanged$.next()
|
||||
}
|
||||
}
|
||||
|
|
45
app/src/services/tabRecovery.ts
Normal file
45
app/src/services/tabRecovery.ts
Normal file
|
@ -0,0 +1,45 @@
|
|||
import { Injectable, Inject } from '@angular/core'
|
||||
import { Logger, LogService } from 'services/log'
|
||||
import { BaseTabComponent } from 'components/baseTab'
|
||||
import { TabRecoveryProvider } from 'api/tabRecovery'
|
||||
import { AppService } from 'services/app'
|
||||
|
||||
|
||||
@Injectable()
|
||||
export class TabRecoveryService {
|
||||
logger: Logger
|
||||
|
||||
constructor(
|
||||
@Inject(TabRecoveryProvider) private tabRecoveryProviders: TabRecoveryProvider[],
|
||||
app: AppService,
|
||||
log: LogService
|
||||
) {
|
||||
this.logger = log.create('tabRecovery')
|
||||
app.tabsChanged$.subscribe(() => {
|
||||
this.saveTabs(app.tabs)
|
||||
})
|
||||
}
|
||||
|
||||
saveTabs (tabs: BaseTabComponent[]) {
|
||||
window.localStorage.tabsRecovery = JSON.stringify(
|
||||
tabs
|
||||
.map((tab) => tab.getRecoveryToken())
|
||||
.filter((token) => !!token)
|
||||
)
|
||||
}
|
||||
|
||||
async recoverTabs (): Promise<void> {
|
||||
if (window.localStorage.tabsRecovery) {
|
||||
for (let token of JSON.parse(window.localStorage.tabsRecovery)) {
|
||||
for (let provider of this.tabRecoveryProviders) {
|
||||
try {
|
||||
await provider.recover(token)
|
||||
} catch (error) {
|
||||
this.logger.warn('Tab recovery crashed:', token, provider, error)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
import { Injectable } from '@angular/core'
|
||||
import { ToolbarButtonProvider, IToolbarButton, AppService } from 'api'
|
||||
import { SettingsTab } from './tab'
|
||||
import { SettingsTabComponent } from './components/settingsTab'
|
||||
|
||||
|
||||
@Injectable()
|
||||
|
@ -17,11 +17,11 @@ export class ButtonProvider extends ToolbarButtonProvider {
|
|||
title: 'Settings',
|
||||
weight: 10,
|
||||
click: () => {
|
||||
let settingsTab = this.app.tabs.find((tab) => tab instanceof SettingsTab)
|
||||
let settingsTab = this.app.tabs.find((tab) => tab instanceof SettingsTabComponent)
|
||||
if (settingsTab) {
|
||||
this.app.selectTab(settingsTab)
|
||||
} else {
|
||||
this.app.openTab(new SettingsTab())
|
||||
this.app.openNewTab(SettingsTabComponent)
|
||||
}
|
||||
}
|
||||
}]
|
||||
|
|
|
@ -2,22 +2,20 @@ import { Component, Inject } from '@angular/core'
|
|||
import { ElectronService } from 'services/electron'
|
||||
import { ConfigService } from 'services/config'
|
||||
import { DockingService } from 'services/docking'
|
||||
import { IHotkeyDescription, HotkeyProvider } from 'api/hotkeyProvider'
|
||||
import { IHotkeyDescription, HotkeyProvider, BaseTabComponent } from 'api'
|
||||
|
||||
import { BaseTabComponent } from 'components/baseTab'
|
||||
import { SettingsTab } from '../tab'
|
||||
import { SettingsTabProvider } from '../api'
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'settings-pane',
|
||||
template: require('./settingsPane.pug'),
|
||||
selector: 'settings-tab',
|
||||
template: require('./settingsTab.pug'),
|
||||
styles: [
|
||||
require('./settingsPane.scss'),
|
||||
require('./settingsPane.deep.css'),
|
||||
require('./settingsTab.scss'),
|
||||
require('./settingsTab.deep.css'),
|
||||
],
|
||||
})
|
||||
export class SettingsPaneComponent extends BaseTabComponent<SettingsTab> {
|
||||
export class SettingsTabComponent extends BaseTabComponent {
|
||||
globalHotkey = ['Ctrl+Shift+G']
|
||||
private hotkeyDescriptions: IHotkeyDescription[]
|
||||
|
||||
|
@ -30,6 +28,12 @@ export class SettingsPaneComponent extends BaseTabComponent<SettingsTab> {
|
|||
) {
|
||||
super()
|
||||
this.hotkeyDescriptions = hotkeyProviders.map(x => x.hotkeys).reduce((a, b) => a.concat(b))
|
||||
this.title$.next('Settings')
|
||||
this.scrollable = true
|
||||
}
|
||||
|
||||
getRecoveryToken (): any {
|
||||
return { type: 'app:settings' }
|
||||
}
|
||||
|
||||
ngOnDestroy () {
|
|
@ -7,7 +7,7 @@ import { HotkeyInputComponent } from './components/hotkeyInput'
|
|||
import { HotkeyDisplayComponent } from './components/hotkeyDisplay'
|
||||
import { HotkeyInputModalComponent } from './components/hotkeyInputModal'
|
||||
import { MultiHotkeyInputComponent } from './components/multiHotkeyInput'
|
||||
import { SettingsPaneComponent } from './components/settingsPane'
|
||||
import { SettingsTabComponent } from './components/settingsTab'
|
||||
import { SettingsTabBodyComponent } from './components/settingsTabBody'
|
||||
|
||||
import { ToolbarButtonProvider, TabRecoveryProvider } from 'api'
|
||||
|
@ -28,14 +28,14 @@ import { RecoveryProvider } from './recoveryProvider'
|
|||
],
|
||||
entryComponents: [
|
||||
HotkeyInputModalComponent,
|
||||
SettingsPaneComponent,
|
||||
SettingsTabComponent,
|
||||
],
|
||||
declarations: [
|
||||
HotkeyDisplayComponent,
|
||||
HotkeyInputComponent,
|
||||
HotkeyInputModalComponent,
|
||||
MultiHotkeyInputComponent,
|
||||
SettingsPaneComponent,
|
||||
SettingsTabComponent,
|
||||
SettingsTabBodyComponent,
|
||||
],
|
||||
})
|
||||
|
|
|
@ -1,14 +1,19 @@
|
|||
import { Injectable } from '@angular/core'
|
||||
import { Tab, TabRecoveryProvider } from 'api'
|
||||
import { SettingsTab } from './tab'
|
||||
import { TabRecoveryProvider, AppService } from 'api'
|
||||
import { SettingsTabComponent } from './components/settingsTab'
|
||||
|
||||
|
||||
@Injectable()
|
||||
export class RecoveryProvider extends TabRecoveryProvider {
|
||||
async recover (recoveryToken: any): Promise<Tab> {
|
||||
constructor(
|
||||
private app: AppService
|
||||
) {
|
||||
super()
|
||||
}
|
||||
|
||||
async recover (recoveryToken: any): Promise<void> {
|
||||
if (recoveryToken.type == 'app:settings') {
|
||||
return new SettingsTab()
|
||||
this.app.openNewTab(SettingsTabComponent)
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,20 +0,0 @@
|
|||
import { Tab, ComponentType } from 'api/tab'
|
||||
import { SettingsPaneComponent } from './components/settingsPane'
|
||||
|
||||
export class SettingsTab extends Tab {
|
||||
constructor () {
|
||||
super()
|
||||
this.title = 'Settings'
|
||||
this.scrollable = true
|
||||
}
|
||||
|
||||
getComponentType (): ComponentType<SettingsTab> {
|
||||
return SettingsPaneComponent
|
||||
}
|
||||
|
||||
getRecoveryToken (): any {
|
||||
return {
|
||||
type: 'app:settings',
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
import { Injectable } from '@angular/core'
|
||||
import { HotkeysService, ToolbarButtonProvider, IToolbarButton, AppService } from 'api'
|
||||
import { SessionsService } from './services/sessions'
|
||||
import { TerminalTab } from './tab'
|
||||
import { TerminalTabComponent } from './components/terminalTab'
|
||||
|
||||
|
||||
@Injectable()
|
||||
|
@ -14,17 +14,20 @@ export class ButtonProvider extends ToolbarButtonProvider {
|
|||
super()
|
||||
hotkeys.matchedHotkey.subscribe(async (hotkey) => {
|
||||
if (hotkey == 'new-tab') {
|
||||
this.app.openTab(await this.getNewTab())
|
||||
this.openNewTab()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async getNewTab (): Promise<TerminalTab> {
|
||||
async openNewTab (): Promise<void> {
|
||||
let cwd = null
|
||||
if (this.app.activeTab instanceof TerminalTab) {
|
||||
if (this.app.activeTab instanceof TerminalTabComponent) {
|
||||
cwd = await this.app.activeTab.session.getWorkingDirectory()
|
||||
}
|
||||
return new TerminalTab(await this.sessions.createNewSession({ command: 'zsh', cwd }))
|
||||
this.app.openNewTab(
|
||||
TerminalTabComponent,
|
||||
{ session: await this.sessions.createNewSession({ command: 'zsh', cwd }) }
|
||||
)
|
||||
}
|
||||
|
||||
provide (): IToolbarButton[] {
|
||||
|
@ -32,7 +35,7 @@ export class ButtonProvider extends ToolbarButtonProvider {
|
|||
icon: 'plus',
|
||||
title: 'New terminal',
|
||||
click: async () => {
|
||||
this.app.openTab(await this.getNewTab())
|
||||
this.openNewTab()
|
||||
}
|
||||
}]
|
||||
}
|
||||
|
|
|
@ -48,22 +48,21 @@
|
|||
.col-lg-6
|
||||
.form-group
|
||||
label Font
|
||||
input.form-control(
|
||||
type='text',
|
||||
[ngbTypeahead]='fontAutocomplete',
|
||||
'[(ngModel)]'='config.store.terminal.font',
|
||||
(ngModelChange)='config.save()',
|
||||
)
|
||||
.row
|
||||
.col-8
|
||||
input.form-control(
|
||||
type='text',
|
||||
[ngbTypeahead]='fontAutocomplete',
|
||||
'[(ngModel)]'='config.store.terminal.font',
|
||||
(ngModelChange)='config.save()',
|
||||
)
|
||||
.col-4
|
||||
input.form-control(
|
||||
type='number',
|
||||
'[(ngModel)]'='config.store.terminal.fontSize',
|
||||
(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.terminal.fontSize',
|
||||
(ngModelChange)='config.save()',
|
||||
)
|
||||
small.form-text.text-muted Text size to be used in the terminal
|
||||
|
||||
.form-group
|
||||
label Color scheme
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
import { BehaviorSubject, ReplaySubject, Subject, Subscription } from 'rxjs'
|
||||
import { Component, NgZone, Inject, ViewChild, HostBinding } from '@angular/core'
|
||||
import { Component, NgZone, Inject, ViewChild, HostBinding, Input } from '@angular/core'
|
||||
|
||||
import { BaseTabComponent } from 'components/baseTab'
|
||||
import { TerminalTab } from '../tab'
|
||||
import { TerminalDecorator, ResizeEvent } from '../api'
|
||||
import { AppService, ConfigService } from 'api'
|
||||
import { Session } from '../services/sessions'
|
||||
|
||||
import { AppService, ConfigService, BaseTabComponent } from 'api'
|
||||
import { hterm, preferenceManager } from '../hterm'
|
||||
|
||||
|
||||
|
@ -14,17 +13,17 @@ import { hterm, preferenceManager } from '../hterm'
|
|||
template: '<div #content class="content"></div>',
|
||||
styles: [require('./terminalTab.scss')],
|
||||
})
|
||||
export class TerminalTabComponent extends BaseTabComponent<TerminalTab> {
|
||||
export class TerminalTabComponent extends BaseTabComponent {
|
||||
hterm: any
|
||||
configSubscription: Subscription
|
||||
focusedSubscription: Subscription
|
||||
title$ = new BehaviorSubject('')
|
||||
size$ = new ReplaySubject<ResizeEvent>(1)
|
||||
input$ = new Subject<string>()
|
||||
output$ = new Subject<string>()
|
||||
contentUpdated$ = new Subject<void>()
|
||||
alternateScreenActive$ = new BehaviorSubject(false)
|
||||
mouseEvent$ = new Subject<Event>()
|
||||
@Input() session: Session
|
||||
@ViewChild('content') content
|
||||
@HostBinding('style.background-color') backgroundColor: string
|
||||
private io: any
|
||||
|
@ -41,8 +40,15 @@ export class TerminalTabComponent extends BaseTabComponent<TerminalTab> {
|
|||
})
|
||||
}
|
||||
|
||||
initTab () {
|
||||
this.focusedSubscription = this.model.focused.subscribe(() => {
|
||||
getRecoveryToken (): any {
|
||||
return {
|
||||
type: 'app:terminal',
|
||||
recoveryId: this.session.recoveryId,
|
||||
}
|
||||
}
|
||||
|
||||
ngOnInit () {
|
||||
this.focusedSubscription = this.focused.subscribe(() => {
|
||||
this.hterm.scrollPort_.focus()
|
||||
})
|
||||
|
||||
|
@ -57,24 +63,24 @@ export class TerminalTabComponent extends BaseTabComponent<TerminalTab> {
|
|||
this.hterm.installKeyboard()
|
||||
this.io = this.hterm.io.push()
|
||||
this.attachIOHandlers(this.io)
|
||||
this.model.session.output$.subscribe((data) => {
|
||||
this.session.output$.subscribe((data) => {
|
||||
this.zone.run(() => {
|
||||
this.output$.next(data)
|
||||
})
|
||||
this.write(data)
|
||||
})
|
||||
this.model.session.closed$.first().subscribe(() => {
|
||||
this.app.closeTab(this.model)
|
||||
this.session.closed$.first().subscribe(() => {
|
||||
this.app.closeTab(this)
|
||||
})
|
||||
|
||||
this.model.session.releaseInitialDataBuffer()
|
||||
this.session.releaseInitialDataBuffer()
|
||||
}
|
||||
this.hterm.decorate(this.content.nativeElement)
|
||||
this.configure()
|
||||
|
||||
setTimeout(() => {
|
||||
this.output$.subscribe(() => {
|
||||
this.model.displayActivity()
|
||||
this.displayActivity()
|
||||
})
|
||||
}, 1000)
|
||||
}
|
||||
|
@ -82,7 +88,6 @@ export class TerminalTabComponent extends BaseTabComponent<TerminalTab> {
|
|||
attachHTermHandlers (hterm: any) {
|
||||
hterm.setWindowTitle = (title) => {
|
||||
this.zone.run(() => {
|
||||
this.model.title = title
|
||||
this.title$.next(title)
|
||||
})
|
||||
}
|
||||
|
@ -142,14 +147,14 @@ export class TerminalTabComponent extends BaseTabComponent<TerminalTab> {
|
|||
io.onTerminalResize = (columns, rows) => {
|
||||
// console.log(`Resizing to ${columns}x${rows}`)
|
||||
this.zone.run(() => {
|
||||
this.model.session.resize(columns, rows)
|
||||
this.session.resize(columns, rows)
|
||||
this.size$.next({ width: columns, height: rows })
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
sendInput (data: string) {
|
||||
this.model.session.write(data)
|
||||
this.session.write(data)
|
||||
}
|
||||
|
||||
write (data: string) {
|
||||
|
@ -198,5 +203,7 @@ export class TerminalTabComponent extends BaseTabComponent<TerminalTab> {
|
|||
this.contentUpdated$.complete()
|
||||
this.alternateScreenActive$.complete()
|
||||
this.mouseEvent$.complete()
|
||||
|
||||
this.session.gracefullyDestroy()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,23 +1,25 @@
|
|||
import { Injectable } from '@angular/core'
|
||||
import { Tab, TabRecoveryProvider } from 'api'
|
||||
import { TerminalTab } from './tab'
|
||||
import { TabRecoveryProvider, AppService } from 'api'
|
||||
import { SessionsService } from './services/sessions'
|
||||
import { TerminalTabComponent } from './components/terminalTab'
|
||||
|
||||
|
||||
@Injectable()
|
||||
export class RecoveryProvider extends TabRecoveryProvider {
|
||||
constructor (private sessions: SessionsService) {
|
||||
constructor (
|
||||
private sessions: SessionsService,
|
||||
private app: AppService,
|
||||
) {
|
||||
super()
|
||||
}
|
||||
|
||||
async recover (recoveryToken: any): Promise<Tab> {
|
||||
async recover (recoveryToken: any): Promise<void> {
|
||||
if (recoveryToken.type == 'app:terminal') {
|
||||
let session = await this.sessions.recover(recoveryToken.recoveryId)
|
||||
if (!session) {
|
||||
return null
|
||||
return
|
||||
}
|
||||
return new TerminalTab(session)
|
||||
this.app.openNewTab(TerminalTabComponent, { session })
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,27 +0,0 @@
|
|||
import { Tab, ComponentType } from 'api/tab'
|
||||
import { TerminalTabComponent } from './components/terminalTab'
|
||||
import { Session } from './services/sessions'
|
||||
|
||||
|
||||
export class TerminalTab extends Tab {
|
||||
static recoveryId = 'app:terminal'
|
||||
|
||||
constructor (public session: Session) {
|
||||
super()
|
||||
}
|
||||
|
||||
getComponentType (): ComponentType<TerminalTab> {
|
||||
return TerminalTabComponent
|
||||
}
|
||||
|
||||
getRecoveryToken (): any {
|
||||
return {
|
||||
type: 'app:terminal',
|
||||
recoveryId: this.session.recoveryId,
|
||||
}
|
||||
}
|
||||
|
||||
destroy (): void {
|
||||
this.session.gracefullyDestroy()
|
||||
}
|
||||
}
|
|
@ -135,7 +135,7 @@ app-root > .content {
|
|||
margin-top: 3px;
|
||||
|
||||
tab-header {
|
||||
&.pre-selected, &:nth-last-child(1) {
|
||||
&.pre-selected {
|
||||
.content-wrapper {
|
||||
border-bottom-right-radius: $tab-border-radius;
|
||||
}
|
||||
|
@ -167,7 +167,7 @@ app-root > .content {
|
|||
margin-bottom: 3px;
|
||||
|
||||
tab-header {
|
||||
&.pre-selected, &:nth-last-child(1) {
|
||||
&.pre-selected {
|
||||
.content-wrapper {
|
||||
border-top-right-radius: $tab-border-radius;
|
||||
}
|
||||
|
@ -200,7 +200,7 @@ tab-body {
|
|||
background: $body-bg;
|
||||
}
|
||||
|
||||
settings-pane > ngb-tabset {
|
||||
settings-tab > ngb-tabset {
|
||||
border-right: 1px solid $body-bg2;
|
||||
|
||||
& > .nav {
|
||||
|
|
Loading…
Reference in a new issue