From 79cd2a3bbbb695efe154245e5be47de361e3ea3c Mon Sep 17 00:00:00 2001 From: Eugene Pankov Date: Sat, 8 Apr 2017 14:50:10 +0200 Subject: [PATCH] proper tab classes --- app/src/api/defaultTabProvider.ts | 2 +- app/src/api/index.ts | 2 +- app/src/api/tab.ts | 32 -------- app/src/api/tabRecovery.ts | 4 +- app/src/app.module.ts | 2 + app/src/components/appRoot.pug | 4 +- app/src/components/appRoot.ts | 13 ++- app/src/components/baseTab.ts | 31 ++++++-- app/src/components/tabBody.ts | 16 +--- app/src/components/tabHeader.pug | 2 +- app/src/components/tabHeader.ts | 4 +- app/src/services/app.ts | 79 ++++++++----------- app/src/services/tabRecovery.ts | 45 +++++++++++ app/src/settings/buttonProvider.ts | 6 +- ...ingsPane.deep.css => settingsTab.deep.css} | 0 .../{settingsPane.pug => settingsTab.pug} | 0 .../{settingsPane.scss => settingsTab.scss} | 0 .../{settingsPane.ts => settingsTab.ts} | 20 +++-- app/src/settings/index.ts | 6 +- app/src/settings/recoveryProvider.ts | 15 ++-- app/src/settings/tab.ts | 20 ----- app/src/terminal/buttonProvider.ts | 15 ++-- app/src/terminal/components/settings.pug | 29 ++++--- app/src/terminal/components/terminalTab.ts | 39 +++++---- app/src/terminal/recoveryProvider.ts | 16 ++-- app/src/terminal/tab.ts | 27 ------- app/src/theme.scss | 6 +- 27 files changed, 208 insertions(+), 227 deletions(-) delete mode 100644 app/src/api/tab.ts create mode 100644 app/src/services/tabRecovery.ts rename app/src/settings/components/{settingsPane.deep.css => settingsTab.deep.css} (100%) rename app/src/settings/components/{settingsPane.pug => settingsTab.pug} (100%) rename app/src/settings/components/{settingsPane.scss => settingsTab.scss} (100%) rename app/src/settings/components/{settingsPane.ts => settingsTab.ts} (68%) delete mode 100644 app/src/settings/tab.ts delete mode 100644 app/src/terminal/tab.ts diff --git a/app/src/api/defaultTabProvider.ts b/app/src/api/defaultTabProvider.ts index c496517c..7d584e15 100644 --- a/app/src/api/defaultTabProvider.ts +++ b/app/src/api/defaultTabProvider.ts @@ -1,3 +1,3 @@ export abstract class DefaultTabProvider { - abstract open (): void + abstract async openNewTab (): Promise } diff --git a/app/src/api/index.ts b/app/src/api/index.ts index 9f85c4b5..d12ca063 100644 --- a/app/src/api/index.ts +++ b/app/src/api/index.ts @@ -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' diff --git a/app/src/api/tab.ts b/app/src/api/tab.ts deleted file mode 100644 index 02a47570..00000000 --- a/app/src/api/tab.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { EventEmitter } from '@angular/core' -import { BaseTabComponent } from 'components/baseTab' - -export declare type ComponentType = new (...args: any[]) => BaseTabComponent - - -export abstract class Tab { - id: number - title: string - scrollable: boolean - hasActivity = false - focused = new EventEmitter() - blurred = new EventEmitter() - static lastTabID = 0 - - constructor () { - this.id = Tab.lastTabID++ - } - - displayActivity (): void { - this.hasActivity = true - } - - abstract getComponentType (): ComponentType - - getRecoveryToken (): any { - return null - } - - destroy (): void { - } -} diff --git a/app/src/api/tabRecovery.ts b/app/src/api/tabRecovery.ts index 8b2b612a..d5f23220 100644 --- a/app/src/api/tabRecovery.ts +++ b/app/src/api/tabRecovery.ts @@ -1,5 +1,3 @@ -import { Tab } from './tab' - export abstract class TabRecoveryProvider { - abstract async recover (recoveryToken: any): Promise + abstract async recover (recoveryToken: any): Promise } diff --git a/app/src/app.module.ts b/app/src/app.module.ts index eaa52338..a2dc0d1e 100644 --- a/app/src/app.module.ts +++ b/app/src/app.module.ts @@ -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 }, ], diff --git a/app/src/components/appRoot.pug b/app/src/components/appRoot.pug index 9766062c..a0858364 100644 --- a/app/src/components/appRoot.pug +++ b/app/src/components/appRoot.pug @@ -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', ) diff --git a/app/src/components/appRoot.ts b/app/src/components/appRoot.ts index e7947baa..7b3592cb 100644 --- a/app/src/components/appRoot.ts +++ b/app/src/components/appRoot.ts @@ -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() } diff --git a/app/src/components/baseTab.ts b/app/src/components/baseTab.ts index 8becc96f..edf10872 100644 --- a/app/src/components/baseTab.ts +++ b/app/src/components/baseTab.ts @@ -1,12 +1,29 @@ -import { Tab } from 'api/tab' +import { BehaviorSubject } from 'rxjs' +import { EventEmitter, ViewRef } from '@angular/core' -export class BaseTabComponent { - protected model: T - initModel (model: T) { - this.model = model - this.initTab() +export abstract class BaseTabComponent { + id: number + title$ = new BehaviorSubject(null) + scrollable: boolean + hasActivity = false + focused = new EventEmitter() + blurred = new EventEmitter() + hostView: ViewRef + private static lastTabID = 0 + + constructor () { + this.id = BaseTabComponent.lastTabID++ } - initTab () { } + displayActivity (): void { + this.hasActivity = true + } + + getRecoveryToken (): any { + return null + } + + destroy (): void { + } } diff --git a/app/src/components/tabBody.ts b/app/src/components/tabBody.ts index 361545fa..334e030b 100644 --- a/app/src/components/tabBody.ts +++ b/app/src/components/tabBody.ts @@ -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> - - 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) }) } } diff --git a/app/src/components/tabHeader.pug b/app/src/components/tabHeader.pug index cabab87f..0a250190 100644 --- a/app/src/components/tabHeader.pug +++ b/app/src/components/tabHeader.pug @@ -1,4 +1,4 @@ .content-wrapper .index {{index + 1}} - .name {{model.title || "Terminal"}} + .name {{(tab.title$ || "Terminal") | async}} button((click)='closeClicked.emit()') × diff --git a/app/src/components/tabHeader.ts b/app/src/components/tabHeader.ts index 69c3f63b..c28152a7 100644 --- a/app/src/components/tabHeader.ts +++ b/app/src/components/tabHeader.ts @@ -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() } diff --git a/app/src/services/app.ts b/app/src/services/app.ts index f92bed3b..a021e2a9 100644 --- a/app/src/services/app.ts +++ b/app/src/services/app.ts @@ -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 { - 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() } } diff --git a/app/src/services/tabRecovery.ts b/app/src/services/tabRecovery.ts new file mode 100644 index 00000000..0c42ab0f --- /dev/null +++ b/app/src/services/tabRecovery.ts @@ -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 { + 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) + } + } + } + } + } + +} diff --git a/app/src/settings/buttonProvider.ts b/app/src/settings/buttonProvider.ts index 6133f1ef..65ab805d 100644 --- a/app/src/settings/buttonProvider.ts +++ b/app/src/settings/buttonProvider.ts @@ -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) } } }] diff --git a/app/src/settings/components/settingsPane.deep.css b/app/src/settings/components/settingsTab.deep.css similarity index 100% rename from app/src/settings/components/settingsPane.deep.css rename to app/src/settings/components/settingsTab.deep.css diff --git a/app/src/settings/components/settingsPane.pug b/app/src/settings/components/settingsTab.pug similarity index 100% rename from app/src/settings/components/settingsPane.pug rename to app/src/settings/components/settingsTab.pug diff --git a/app/src/settings/components/settingsPane.scss b/app/src/settings/components/settingsTab.scss similarity index 100% rename from app/src/settings/components/settingsPane.scss rename to app/src/settings/components/settingsTab.scss diff --git a/app/src/settings/components/settingsPane.ts b/app/src/settings/components/settingsTab.ts similarity index 68% rename from app/src/settings/components/settingsPane.ts rename to app/src/settings/components/settingsTab.ts index 2426a58f..412ff763 100644 --- a/app/src/settings/components/settingsPane.ts +++ b/app/src/settings/components/settingsTab.ts @@ -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 { +export class SettingsTabComponent extends BaseTabComponent { globalHotkey = ['Ctrl+Shift+G'] private hotkeyDescriptions: IHotkeyDescription[] @@ -30,6 +28,12 @@ export class SettingsPaneComponent extends BaseTabComponent { ) { 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 () { diff --git a/app/src/settings/index.ts b/app/src/settings/index.ts index 6632f825..1276a8b8 100644 --- a/app/src/settings/index.ts +++ b/app/src/settings/index.ts @@ -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, ], }) diff --git a/app/src/settings/recoveryProvider.ts b/app/src/settings/recoveryProvider.ts index 5e7271e7..f2763a4d 100644 --- a/app/src/settings/recoveryProvider.ts +++ b/app/src/settings/recoveryProvider.ts @@ -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 { + constructor( + private app: AppService + ) { + super() + } + + async recover (recoveryToken: any): Promise { if (recoveryToken.type == 'app:settings') { - return new SettingsTab() + this.app.openNewTab(SettingsTabComponent) } - return null } } diff --git a/app/src/settings/tab.ts b/app/src/settings/tab.ts deleted file mode 100644 index 090c69b1..00000000 --- a/app/src/settings/tab.ts +++ /dev/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 { - return SettingsPaneComponent - } - - getRecoveryToken (): any { - return { - type: 'app:settings', - } - } -} diff --git a/app/src/terminal/buttonProvider.ts b/app/src/terminal/buttonProvider.ts index 6b67f692..3b141e6f 100644 --- a/app/src/terminal/buttonProvider.ts +++ b/app/src/terminal/buttonProvider.ts @@ -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 { + async openNewTab (): Promise { 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() } }] } diff --git a/app/src/terminal/components/settings.pug b/app/src/terminal/components/settings.pug index 4ec4f9f2..905a3e1e 100644 --- a/app/src/terminal/components/settings.pug +++ b/app/src/terminal/components/settings.pug @@ -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 diff --git a/app/src/terminal/components/terminalTab.ts b/app/src/terminal/components/terminalTab.ts index ee60261f..5b0a3370 100644 --- a/app/src/terminal/components/terminalTab.ts +++ b/app/src/terminal/components/terminalTab.ts @@ -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: '
', styles: [require('./terminalTab.scss')], }) -export class TerminalTabComponent extends BaseTabComponent { +export class TerminalTabComponent extends BaseTabComponent { hterm: any configSubscription: Subscription focusedSubscription: Subscription - title$ = new BehaviorSubject('') size$ = new ReplaySubject(1) input$ = new Subject() output$ = new Subject() contentUpdated$ = new Subject() alternateScreenActive$ = new BehaviorSubject(false) mouseEvent$ = new Subject() + @Input() session: Session @ViewChild('content') content @HostBinding('style.background-color') backgroundColor: string private io: any @@ -41,8 +40,15 @@ export class TerminalTabComponent extends BaseTabComponent { }) } - 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 { 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 { 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 { 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 { this.contentUpdated$.complete() this.alternateScreenActive$.complete() this.mouseEvent$.complete() + + this.session.gracefullyDestroy() } } diff --git a/app/src/terminal/recoveryProvider.ts b/app/src/terminal/recoveryProvider.ts index c569eeba..4c41ae28 100644 --- a/app/src/terminal/recoveryProvider.ts +++ b/app/src/terminal/recoveryProvider.ts @@ -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 { + async recover (recoveryToken: any): Promise { 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 } } diff --git a/app/src/terminal/tab.ts b/app/src/terminal/tab.ts deleted file mode 100644 index c34c3a3c..00000000 --- a/app/src/terminal/tab.ts +++ /dev/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 { - return TerminalTabComponent - } - - getRecoveryToken (): any { - return { - type: 'app:terminal', - recoveryId: this.session.recoveryId, - } - } - - destroy (): void { - this.session.gracefullyDestroy() - } -} diff --git a/app/src/theme.scss b/app/src/theme.scss index 5aef2d1e..ef56c575 100644 --- a/app/src/theme.scss +++ b/app/src/theme.scss @@ -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 {