rxjs cleanup

This commit is contained in:
Eugene Pankov
2018-08-09 12:37:14 -07:00
parent 9e228a4e93
commit 23e93f0969
10 changed files with 105 additions and 70 deletions

View File

@@ -1,17 +1,21 @@
import { Subject } from 'rxjs' import { Observable, Subject } from 'rxjs'
import { ViewRef } from '@angular/core' import { ViewRef } from '@angular/core'
export abstract class BaseTabComponent { export abstract class BaseTabComponent {
private static lastTabID = 0 private static lastTabID = 0
id: number id: number
title: string title: string
titleChange$ = new Subject<string>()
customTitle: string customTitle: string
hasActivity = false hasActivity = false
focused$ = new Subject<void>()
blurred$ = new Subject<void>()
hasFocus = false hasFocus = false
hostView: ViewRef hostView: ViewRef
protected titleChange = new Subject<string>()
protected focused = new Subject<void>()
protected blurred = new Subject<void>()
get focused$ (): Observable<void> { return this.focused }
get blurred$ (): Observable<void> { return this.blurred }
get titleChange$ (): Observable<string> { return this.titleChange }
constructor () { constructor () {
this.id = BaseTabComponent.lastTabID++ this.id = BaseTabComponent.lastTabID++
@@ -26,7 +30,7 @@ export abstract class BaseTabComponent {
setTitle (title: string) { setTitle (title: string) {
this.title = title this.title = title
if (!this.customTitle) { if (!this.customTitle) {
this.titleChange$.next(title) this.titleChange.next(title)
} }
} }
@@ -42,8 +46,17 @@ export abstract class BaseTabComponent {
return true return true
} }
emitFocused () {
this.focused.next()
}
emitBlurred () {
this.blurred.next()
}
destroy (): void { destroy (): void {
this.focused$.complete() this.focused.complete()
this.blurred$.complete() this.blurred.complete()
this.titleChange.complete()
} }
} }

View File

@@ -1,4 +1,4 @@
import { Subject, AsyncSubject } from 'rxjs' import { Observable, Subject, AsyncSubject } from 'rxjs'
import { Injectable, ComponentFactoryResolver, Injector, Optional } from '@angular/core' import { Injectable, ComponentFactoryResolver, Injector, Optional } from '@angular/core'
import { DefaultTabProvider } from '../api/defaultTabProvider' import { DefaultTabProvider } from '../api/defaultTabProvider'
import { BaseTabComponent } from '../components/baseTab.component' import { BaseTabComponent } from '../components/baseTab.component'
@@ -11,13 +11,20 @@ export declare type TabComponentType = new (...args: any[]) => BaseTabComponent
export class AppService { export class AppService {
tabs: BaseTabComponent[] = [] tabs: BaseTabComponent[] = []
activeTab: BaseTabComponent activeTab: BaseTabComponent
activeTabChange$ = new Subject<BaseTabComponent>()
lastTabIndex = 0 lastTabIndex = 0
logger: Logger logger: Logger
tabsChanged$ = new Subject<void>()
tabOpened$ = new Subject<BaseTabComponent>() private activeTabChange = new Subject<BaseTabComponent>()
tabClosed$ = new Subject<BaseTabComponent>() private tabsChanged = new Subject<void>()
ready$ = new AsyncSubject<void>() private tabOpened = new Subject<BaseTabComponent>()
private tabClosed = new Subject<BaseTabComponent>()
private ready = new AsyncSubject<void>()
get activeTabChange$ (): Observable<BaseTabComponent> { return this.activeTabChange }
get tabOpened$ (): Observable<BaseTabComponent> { return this.tabOpened }
get tabsChanged$ (): Observable<void> { return this.tabsChanged }
get tabClosed$ (): Observable<BaseTabComponent> { return this.tabClosed }
get ready$ (): Observable<void> { return this.ready }
constructor ( constructor (
private componentFactoryResolver: ComponentFactoryResolver, private componentFactoryResolver: ComponentFactoryResolver,
@@ -37,8 +44,8 @@ export class AppService {
this.tabs.push(componentRef.instance) this.tabs.push(componentRef.instance)
this.selectTab(componentRef.instance) this.selectTab(componentRef.instance)
this.tabsChanged$.next() this.tabsChanged.next()
this.tabOpened$.next(componentRef.instance) this.tabOpened.next(componentRef.instance)
return componentRef.instance return componentRef.instance
} }
@@ -60,12 +67,12 @@ export class AppService {
} }
if (this.activeTab) { if (this.activeTab) {
this.activeTab.hasActivity = false this.activeTab.hasActivity = false
this.activeTab.blurred$.next() this.activeTab.emitBlurred()
} }
this.activeTab = tab this.activeTab = tab
this.activeTabChange$.next(tab) this.activeTabChange.next(tab)
if (this.activeTab) { if (this.activeTab) {
this.activeTab.focused$.next() this.activeTab.emitFocused()
} }
} }
@@ -99,7 +106,7 @@ export class AppService {
} }
emitTabsChanged () { emitTabsChanged () {
this.tabsChanged$.next() this.tabsChanged.next()
} }
async closeTab (tab: BaseTabComponent, checkCanClose?: boolean): Promise<void> { async closeTab (tab: BaseTabComponent, checkCanClose?: boolean): Promise<void> {
@@ -115,12 +122,12 @@ export class AppService {
if (tab === this.activeTab) { if (tab === this.activeTab) {
this.selectTab(this.tabs[newIndex]) this.selectTab(this.tabs[newIndex])
} }
this.tabsChanged$.next() this.tabsChanged.next()
this.tabClosed$.next(tab) this.tabClosed.next(tab)
} }
emitReady () { emitReady () {
this.ready$.next(null) this.ready.next(null)
this.ready$.complete() this.ready.complete()
} }
} }

View File

@@ -1,4 +1,4 @@
import { Subject } from 'rxjs' import { Observable, Subject } from 'rxjs'
import * as yaml from 'js-yaml' import * as yaml from 'js-yaml'
import * as path from 'path' import * as path from 'path'
import * as fs from 'fs' import * as fs from 'fs'
@@ -52,13 +52,15 @@ export class ConfigProxy {
@Injectable() @Injectable()
export class ConfigService { export class ConfigService {
store: any store: any
changed$ = new Subject<void>()
restartRequested: boolean restartRequested: boolean
private changed = new Subject<void>()
private _store: any private _store: any
private path: string private path: string
private defaults: any private defaults: any
private servicesCache: { [id: string]: Function[] } = null private servicesCache: { [id: string]: Function[] } = null
get changed$ (): Observable<void> { return this.changed }
constructor ( constructor (
electron: ElectronService, electron: ElectronService,
hostApp: HostAppService, hostApp: HostAppService,
@@ -93,7 +95,7 @@ export class ConfigService {
} }
emitChange (): void { emitChange (): void {
this.changed$.next() this.changed.next()
} }
requestRestart (): void { requestRestart (): void {

View File

@@ -14,17 +14,25 @@ export interface Bounds {
height: number height: number
} }
export interface SecondInstanceArgs {
argv: string[],
cwd: string
}
@Injectable() @Injectable()
export class HostAppService { export class HostAppService {
platform: Platform platform: Platform
nodePlatform: string nodePlatform: string
preferencesMenu$ = new Subject<void>()
ready = new EventEmitter<any>() ready = new EventEmitter<any>()
shown = new EventEmitter<any>() shown = new EventEmitter<any>()
secondInstance$ = new Subject<{ argv: string[], cwd: string }>()
isFullScreen = false isFullScreen = false
private preferencesMenu = new Subject<void>()
private secondInstance = new Subject<SecondInstanceArgs>()
private logger: Logger private logger: Logger
get preferencesMenu$ (): Observable<void> { return this.preferencesMenu }
get secondInstance$ (): Observable<SecondInstanceArgs> { return this.secondInstance }
constructor ( constructor (
private zone: NgZone, private zone: NgZone,
private electron: ElectronService, private electron: ElectronService,
@@ -38,7 +46,7 @@ export class HostAppService {
linux: Platform.Linux linux: Platform.Linux
}[this.nodePlatform] }[this.nodePlatform]
electron.ipcRenderer.on('host:preferences-menu', () => this.zone.run(() => this.preferencesMenu$.next())) electron.ipcRenderer.on('host:preferences-menu', () => this.zone.run(() => this.preferencesMenu.next()))
electron.ipcRenderer.on('uncaughtException', ($event, err) => { electron.ipcRenderer.on('uncaughtException', ($event, err) => {
this.logger.error('Unhandled exception:', err) this.logger.error('Unhandled exception:', err)
@@ -57,7 +65,7 @@ export class HostAppService {
}) })
electron.ipcRenderer.on('host:second-instance', ($event, argv: string[], cwd: string) => { electron.ipcRenderer.on('host:second-instance', ($event, argv: string[], cwd: string) => {
this.zone.run(() => this.secondInstance$.next({ argv, cwd })) this.zone.run(() => this.secondInstance.next({ argv, cwd }))
}) })
this.ready.subscribe(() => { this.ready.subscribe(() => {
@@ -119,13 +127,13 @@ export class HostAppService {
} }
setVibrancy (enable: boolean) { setVibrancy (enable: boolean) {
document.body.classList.toggle('vibrant', enable) document.body.classList.toggle('vibrant', enable)
if (this.platform === Platform.macOS) { if (this.platform === Platform.macOS) {
this.hostApp.getWindow().setVibrancy(enable ? 'dark' : null) this.getWindow().setVibrancy(enable ? 'dark' : null)
} }
if (this.platform === Platform.Windows) { if (this.platform === Platform.Windows) {
this.electron.ipcRenderer.send('window-set-vibrancy', enable) this.electron.ipcRenderer.send('window-set-vibrancy', enable)
} }
} }
quit () { quit () {

View File

@@ -1,6 +1,6 @@
import { Injectable, Inject, NgZone } from '@angular/core' import { Injectable, Inject, NgZone } from '@angular/core'
import { TouchBarSegmentedControl, SegmentedControlSegment } from 'electron' import { TouchBarSegmentedControl, SegmentedControlSegment } from 'electron'
import { Subject, Subscription } from 'rxjs' import { Subscription } from 'rxjs'
import { AppService } from './app.service' import { AppService } from './app.service'
import { ConfigService } from './config.service' import { ConfigService } from './config.service'
import { ElectronService } from './electron.service' import { ElectronService } from './electron.service'
@@ -9,7 +9,6 @@ import { IToolbarButton, ToolbarButtonProvider } from '../api'
@Injectable() @Injectable()
export class TouchbarService { export class TouchbarService {
tabSelected$ = new Subject<number>()
private titleSubscriptions = new Map<BaseTabComponent, Subscription>() private titleSubscriptions = new Map<BaseTabComponent, Subscription>()
private tabsSegmentedControl: TouchBarSegmentedControl private tabsSegmentedControl: TouchBarSegmentedControl
private tabSegments: SegmentedControlSegment[] = [] private tabSegments: SegmentedControlSegment[] = []

View File

@@ -11,8 +11,8 @@ export abstract class TerminalDecorator {
} }
export interface ResizeEvent { export interface ResizeEvent {
width: number columns: number
height: number rows: number
} }
export interface SessionOptions { export interface SessionOptions {

View File

@@ -1,4 +1,4 @@
import { Subject, Subscription } from 'rxjs' import { Observable, Subject, Subscription } from 'rxjs'
import { first } from 'rxjs/operators' import { first } from 'rxjs/operators'
import { ToastrService } from 'ngx-toastr' import { ToastrService } from 'ngx-toastr'
import { Component, NgZone, Inject, Optional, ViewChild, HostBinding, Input } from '@angular/core' import { Component, NgZone, Inject, Optional, ViewChild, HostBinding, Input } from '@angular/core'
@@ -33,14 +33,18 @@ export class TerminalTabComponent extends BaseTabComponent {
termContainer: TermContainer termContainer: TermContainer
sessionCloseSubscription: Subscription sessionCloseSubscription: Subscription
hotkeysSubscription: Subscription hotkeysSubscription: Subscription
size: ResizeEvent
output$ = new Subject<string>()
htermVisible = false htermVisible = false
shell: IShell shell: IShell
private output = new Subject<string>()
private bellPlayer: HTMLAudioElement private bellPlayer: HTMLAudioElement
private contextMenu: any private contextMenu: any
private termContainerSubscriptions: Subscription[] = [] private termContainerSubscriptions: Subscription[] = []
get input$ (): Observable<string> { return this.termContainer.input$ }
get output$ (): Observable<string> { return this.output }
get resize$ (): Observable<ResizeEvent> { return this.termContainer.resize$ }
get alternateScreenActive$ (): Observable<boolean> { return this.termContainer.alternateScreenActive$ }
constructor ( constructor (
private zone: NgZone, private zone: NgZone,
private app: AppService, private app: AppService,
@@ -129,7 +133,7 @@ export class TerminalTabComponent extends BaseTabComponent {
// this.session.output$.bufferTime(10).subscribe((datas) => { // this.session.output$.bufferTime(10).subscribe((datas) => {
this.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)
}) })
}) })
@@ -181,7 +185,7 @@ export class TerminalTabComponent extends BaseTabComponent {
}) })
setTimeout(() => { setTimeout(() => {
this.output$.subscribe(() => { this.output.subscribe(() => {
this.displayActivity() this.displayActivity()
}) })
}, 1000) }, 1000)
@@ -278,7 +282,6 @@ export class TerminalTabComponent extends BaseTabComponent {
this.termContainer.resize$.subscribe(({columns, rows}) => { this.termContainer.resize$.subscribe(({columns, rows}) => {
console.log(`Resizing to ${columns}x${rows}`) console.log(`Resizing to ${columns}x${rows}`)
this.zone.run(() => { this.zone.run(() => {
this.size = { width: columns, height: rows }
if (this.session.open) { if (this.session.open) {
this.session.resize(columns, rows) this.session.resize(columns, rows)
} }
@@ -341,7 +344,7 @@ export class TerminalTabComponent extends BaseTabComponent {
if (this.sessionCloseSubscription) { if (this.sessionCloseSubscription) {
this.sessionCloseSubscription.unsubscribe() this.sessionCloseSubscription.unsubscribe()
} }
this.output$.complete() this.output.complete()
} }
async destroy () { async destroy () {

View File

@@ -20,32 +20,33 @@ export abstract class BaseSession {
name: string name: string
recoveryId: string recoveryId: string
truePID: number truePID: number
output$: Observable<string> protected output = new Subject<string>()
closed$: Observable<void> protected closed = new Subject<void>()
destroyed$: Observable<void> protected destroyed = new Subject<void>()
protected output_ = new Subject<string>()
protected closed_ = new Subject<void>()
protected destroyed_ = new Subject<void>()
private initialDataBuffer = '' private initialDataBuffer = ''
private initialDataBufferReleased = false private initialDataBufferReleased = false
get output$ (): Observable<string> { return this.output }
get closed$ (): Observable<void> { return this.closed }
get destroyed$ (): Observable<void> { return this.destroyed }
constructor () { constructor () {
this.output$ = this.output_.asObservable() this.output$ = this.output.asObservable()
this.closed$ = this.closed_.asObservable() this.closed$ = this.closed.asObservable()
this.destroyed$ = this.destroyed_.asObservable() this.destroyed$ = this.destroyed.asObservable()
} }
emitOutput (data: string) { emitOutput (data: string) {
if (!this.initialDataBufferReleased) { if (!this.initialDataBufferReleased) {
this.initialDataBuffer += data this.initialDataBuffer += data
} else { } else {
this.output_.next(data) this.output.next(data)
} }
} }
releaseInitialDataBuffer () { releaseInitialDataBuffer () {
this.initialDataBufferReleased = true this.initialDataBufferReleased = true
this.output_.next(this.initialDataBuffer) this.output.next(this.initialDataBuffer)
this.initialDataBuffer = null this.initialDataBuffer = null
} }
@@ -60,9 +61,9 @@ export abstract class BaseSession {
async destroy (): Promise<void> { async destroy (): Promise<void> {
if (this.open) { if (this.open) {
this.open = false this.open = false
this.closed_.next() this.closed.next()
this.destroyed_.next() this.destroyed.next()
this.output_.complete() this.output.complete()
await this.gracefullyKillProcess() await this.gracefullyKillProcess()
} }
} }

View File

@@ -7,10 +7,11 @@ import { TerminalTabComponent } from '../components/terminalTab.component'
@Injectable() @Injectable()
export class TerminalService { export class TerminalService {
shells$: Observable<IShell[]> private shells = new AsyncSubject<IShell[]>()
private shells_ = new AsyncSubject<IShell[]>()
private logger: Logger private logger: Logger
get shells$ (): Observable<IShell[]> { return this.shells }
constructor ( constructor (
private app: AppService, private app: AppService,
private sessions: SessionsService, private sessions: SessionsService,
@@ -27,11 +28,11 @@ export class TerminalService {
} }
async reloadShells () { async reloadShells () {
this.shells_ = new AsyncSubject<IShell[]>() this.shells = new AsyncSubject<IShell[]>()
this.shells$ = this.shells_.asObservable() this.shells$ = this.shells.asObservable()
let shellLists = await Promise.all(this.config.enabledServices(this.shellProviders).map(x => x.provide())) let shellLists = await Promise.all(this.config.enabledServices(this.shellProviders).map(x => x.provide()))
this.shells_.next(shellLists.reduce((a, b) => a.concat(b))) this.shells.next(shellLists.reduce((a, b) => a.concat(b)))
this.shells_.complete() this.shells.complete()
} }
async openTab (shell?: IShell, cwd?: string): Promise<TerminalTabComponent> { async openTab (shell?: IShell, cwd?: string): Promise<TerminalTabComponent> {

View File

@@ -1,4 +1,5 @@
import { Observable, Subject, AsyncSubject, ReplaySubject, BehaviorSubject } from 'rxjs' import { Observable, Subject, AsyncSubject, ReplaySubject, BehaviorSubject } from 'rxjs'
import { ResizeEvent } from '../api'
export abstract class TermContainer { export abstract class TermContainer {
enableResizing = true enableResizing = true
@@ -9,7 +10,7 @@ export abstract class TermContainer {
protected bell = new Subject<void>() protected bell = new Subject<void>()
protected contentUpdated = new Subject<void>() protected contentUpdated = new Subject<void>()
protected input = new Subject<string>() protected input = new Subject<string>()
protected resize = new ReplaySubject<{columns: number, rows: number}>(1) protected resize = new ReplaySubject<ResizeEvent>(1)
protected dragOver = new Subject<DragEvent>() protected dragOver = new Subject<DragEvent>()
protected drop = new Subject<DragEvent>() protected drop = new Subject<DragEvent>()
@@ -20,7 +21,7 @@ export abstract class TermContainer {
get bell$ (): Observable<void> { return this.bell } get bell$ (): Observable<void> { return this.bell }
get contentUpdated$ (): Observable<void> { return this.contentUpdated } get contentUpdated$ (): Observable<void> { return this.contentUpdated }
get input$ (): Observable<string> { return this.input } get input$ (): Observable<string> { return this.input }
get resize$ (): Observable<{columns: number, rows: number}> { return this.resize } get resize$ (): Observable<ResizeEvent> { return this.resize }
get dragOver$ (): Observable<DragEvent> { return this.dragOver } get dragOver$ (): Observable<DragEvent> { return this.dragOver }
get drop$ (): Observable<DragEvent> { return this.drop } get drop$ (): Observable<DragEvent> { return this.drop }