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