提交 b3f15e27 编写于 作者: E Eugene Pankov

process completion notifications

上级 822e068b
import { Observable, Subject } from 'rxjs' import { Observable, Subject } from 'rxjs'
import { ViewRef } from '@angular/core' import { ViewRef } from '@angular/core'
export interface BaseTabProcess {
name: string
}
export abstract class BaseTabComponent { export abstract class BaseTabComponent {
private static lastTabID = 0 private static lastTabID = 0
id: number id: number
...@@ -14,6 +18,7 @@ export abstract class BaseTabComponent { ...@@ -14,6 +18,7 @@ export abstract class BaseTabComponent {
protected blurred = new Subject<void>() protected blurred = new Subject<void>()
protected progress = new Subject<number>() protected progress = new Subject<number>()
protected activity = new Subject<boolean>() protected activity = new Subject<boolean>()
protected destroyed = new Subject<void>()
private progressClearTimeout: number private progressClearTimeout: number
...@@ -22,6 +27,7 @@ export abstract class BaseTabComponent { ...@@ -22,6 +27,7 @@ export abstract class BaseTabComponent {
get titleChange$ (): Observable<string> { return this.titleChange } get titleChange$ (): Observable<string> { return this.titleChange }
get progress$ (): Observable<number> { return this.progress } get progress$ (): Observable<number> { return this.progress }
get activity$ (): Observable<boolean> { return this.activity } get activity$ (): Observable<boolean> { return this.activity }
get destroyed$ (): Observable<void> { return this.destroyed }
constructor () { constructor () {
this.id = BaseTabComponent.lastTabID++ this.id = BaseTabComponent.lastTabID++
...@@ -66,6 +72,10 @@ export abstract class BaseTabComponent { ...@@ -66,6 +72,10 @@ export abstract class BaseTabComponent {
return null return null
} }
async getCurrentProcess (): Promise<BaseTabProcess> {
return null
}
async canClose (): Promise<boolean> { async canClose (): Promise<boolean> {
return true return true
} }
...@@ -83,5 +93,7 @@ export abstract class BaseTabComponent { ...@@ -83,5 +93,7 @@ export abstract class BaseTabComponent {
this.blurred.complete() this.blurred.complete()
this.titleChange.complete() this.titleChange.complete()
this.progress.complete() this.progress.complete()
this.destroyed.next()
this.destroyed.complete()
} }
} }
...@@ -20,57 +20,16 @@ export class TabHeaderComponent { ...@@ -20,57 +20,16 @@ export class TabHeaderComponent {
@Input() progress: number @Input() progress: number
@ViewChild('handle') handle: ElementRef @ViewChild('handle') handle: ElementRef
private contextMenu: any private completionNotificationEnabled = false
constructor ( constructor (
zone: NgZone,
electron: ElectronService,
public app: AppService, public app: AppService,
private electron: ElectronService,
private zone: NgZone,
private hostApp: HostAppService, private hostApp: HostAppService,
private ngbModal: NgbModal, private ngbModal: NgbModal,
private parentDraggable: SortableComponent, private parentDraggable: SortableComponent,
) { ) { }
this.contextMenu = electron.remote.Menu.buildFromTemplate([
{
label: 'Close',
click: () => {
zone.run(() => {
app.closeTab(this.tab, true)
})
}
},
{
label: 'Close other tabs',
click: () => {
zone.run(() => {
for (let tab of app.tabs.filter(x => x !== this.tab)) {
app.closeTab(tab, true)
}
})
}
},
{
label: 'Close tabs to the right',
click: () => {
zone.run(() => {
for (let tab of app.tabs.slice(app.tabs.indexOf(this.tab) + 1)) {
app.closeTab(tab, true)
}
})
}
},
{
label: 'Close tabs to the left',
click: () => {
zone.run(() => {
for (let tab of app.tabs.slice(0, app.tabs.indexOf(this.tab))) {
app.closeTab(tab, true)
}
})
}
},
])
}
ngOnInit () { ngOnInit () {
if (this.hostApp.platform === Platform.macOS) { if (this.hostApp.platform === Platform.macOS) {
...@@ -90,17 +49,86 @@ export class TabHeaderComponent { ...@@ -90,17 +49,86 @@ export class TabHeaderComponent {
}).catch(() => null) }).catch(() => null)
} }
@HostListener('auxclick', ['$event']) onAuxClick ($event: MouseEvent): void { @HostListener('auxclick', ['$event']) async onAuxClick ($event: MouseEvent) {
if ($event.which === 2) { if ($event.which === 2) {
this.app.closeTab(this.tab, true) this.app.closeTab(this.tab, true)
} }
if ($event.which === 3) { if ($event.which === 3) {
this.contextMenu.popup({ event.preventDefault()
let contextMenu = this.electron.remote.Menu.buildFromTemplate([
{
label: 'Close',
click: () => this.zone.run(() => {
this.app.closeTab(this.tab, true)
})
},
{
label: 'Close other tabs',
click: () => this.zone.run(() => {
for (let tab of this.app.tabs.filter(x => x !== this.tab)) {
this.app.closeTab(tab, true)
}
})
},
{
label: 'Close tabs to the right',
click: () => this.zone.run(() => {
for (let tab of this.app.tabs.slice(this.app.tabs.indexOf(this.tab) + 1)) {
this.app.closeTab(tab, true)
}
})
},
{
label: 'Close tabs to the left',
click: () => this.zone.run(() => {
for (let tab of this.app.tabs.slice(0, this.app.tabs.indexOf(this.tab))) {
this.app.closeTab(tab, true)
}
})
},
])
let process = await this.tab.getCurrentProcess()
if (process) {
contextMenu.append(new this.electron.MenuItem({
id: 'sep',
type: 'separator',
}))
contextMenu.append(new this.electron.MenuItem({
id: 'process-name',
enabled: false,
label: 'Current process: ' + process.name,
}))
contextMenu.append(new this.electron.MenuItem({
id: 'completion',
label: 'Notify when done',
type: 'checkbox',
checked: this.completionNotificationEnabled,
click: () => this.zone.run(() => {
this.completionNotificationEnabled = !this.completionNotificationEnabled
if (this.completionNotificationEnabled) {
this.app.observeTabCompletion(this.tab).subscribe(() => {
new Notification('Process completed', {
body: process.name,
}).addEventListener('click', () => {
this.app.selectTab(this.tab)
})
this.completionNotificationEnabled = false
})
} else {
this.app.stopObservingTabCompletion(this.tab)
}
})
}))
}
contextMenu.popup({
x: $event.pageX, x: $event.pageX,
y: $event.pageY, y: $event.pageY,
async: true, async: true,
}) })
event.preventDefault()
} }
} }
} }
import { Observable, Subject, AsyncSubject } from 'rxjs' import { Observable, Subject, AsyncSubject } from 'rxjs'
import { takeUntil } from 'rxjs/operators'
import { Injectable, ComponentFactoryResolver, Injector } from '@angular/core' import { Injectable, ComponentFactoryResolver, Injector } from '@angular/core'
import { BaseTabComponent } from '../components/baseTab.component' import { BaseTabComponent } from '../components/baseTab.component'
import { Logger, LogService } from './log.service' import { Logger, LogService } from './log.service'
...@@ -7,6 +8,33 @@ import { HostAppService } from './hostApp.service' ...@@ -7,6 +8,33 @@ import { HostAppService } from './hostApp.service'
export declare type TabComponentType = new (...args: any[]) => BaseTabComponent export declare type TabComponentType = new (...args: any[]) => BaseTabComponent
class CompletionObserver {
get done$ (): Observable<void> { return this.done }
get destroyed$ (): Observable<void> { return this.destroyed }
private done = new AsyncSubject<void>()
private destroyed = new AsyncSubject<void>()
private interval: number
constructor (private tab: BaseTabComponent) {
this.interval = setInterval(() => this.tick(), 1000)
this.tab.destroyed$.pipe(takeUntil(this.destroyed$)).subscribe(() => this.stop())
}
async tick () {
if (!(await this.tab.getCurrentProcess())) {
this.done.next(null)
this.stop()
}
}
stop () {
clearInterval(this.interval)
this.destroyed.next(null)
this.destroyed.complete()
this.done.complete()
}
}
@Injectable() @Injectable()
export class AppService { export class AppService {
tabs: BaseTabComponent[] = [] tabs: BaseTabComponent[] = []
...@@ -20,6 +48,8 @@ export class AppService { ...@@ -20,6 +48,8 @@ export class AppService {
private tabClosed = new Subject<BaseTabComponent>() private tabClosed = new Subject<BaseTabComponent>()
private ready = new AsyncSubject<void>() private ready = new AsyncSubject<void>()
private completionObservers = new Map<BaseTabComponent, CompletionObserver>()
get activeTabChange$ (): Observable<BaseTabComponent> { return this.activeTabChange } get activeTabChange$ (): Observable<BaseTabComponent> { return this.activeTabChange }
get tabOpened$ (): Observable<BaseTabComponent> { return this.tabOpened } get tabOpened$ (): Observable<BaseTabComponent> { return this.tabOpened }
get tabsChanged$ (): Observable<void> { return this.tabsChanged } get tabsChanged$ (): Observable<void> { return this.tabsChanged }
...@@ -133,4 +163,19 @@ export class AppService { ...@@ -133,4 +163,19 @@ export class AppService {
this.ready.complete() this.ready.complete()
this.hostApp.emitReady() this.hostApp.emitReady()
} }
observeTabCompletion (tab: BaseTabComponent): Observable<void> {
if (!this.completionObservers.has(tab)) {
let observer = new CompletionObserver(tab)
observer.destroyed$.subscribe(() => {
this.stopObservingTabCompletion(tab)
})
this.completionObservers.set(tab, observer)
}
return this.completionObservers.get(tab).done$
}
stopObservingTabCompletion (tab: BaseTabComponent) {
this.completionObservers.delete(tab)
}
} }
import { Injectable } from '@angular/core' import { Injectable } from '@angular/core'
import { TouchBar, BrowserWindow, Menu } from 'electron' import { TouchBar, BrowserWindow, Menu, MenuItem } from 'electron'
@Injectable() @Injectable()
export class ElectronService { export class ElectronService {
...@@ -15,6 +15,7 @@ export class ElectronService { ...@@ -15,6 +15,7 @@ export class ElectronService {
TouchBar: typeof TouchBar TouchBar: typeof TouchBar
BrowserWindow: typeof BrowserWindow BrowserWindow: typeof BrowserWindow
Menu: typeof Menu Menu: typeof Menu
MenuItem: typeof MenuItem
private electron: any private electron: any
constructor () { constructor () {
...@@ -31,6 +32,7 @@ export class ElectronService { ...@@ -31,6 +32,7 @@ export class ElectronService {
this.TouchBar = this.remote.TouchBar this.TouchBar = this.remote.TouchBar
this.BrowserWindow = this.remote.BrowserWindow this.BrowserWindow = this.remote.BrowserWindow
this.Menu = this.remote.Menu this.Menu = this.remote.Menu
this.MenuItem = this.remote.MenuItem
} }
remoteRequire (name: string): any { remoteRequire (name: string): any {
......
...@@ -2,7 +2,7 @@ import { Observable, Subject, Subscription } from 'rxjs' ...@@ -2,7 +2,7 @@ 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'
import { AppService, ConfigService, BaseTabComponent, ElectronService, HostAppService, HotkeysService, Platform } from 'terminus-core' import { AppService, ConfigService, BaseTabComponent, BaseTabProcess, ElectronService, HostAppService, HotkeysService, Platform } from 'terminus-core'
import { IShell } from '../api' import { IShell } from '../api'
import { Session, SessionsService } from '../services/sessions.service' import { Session, SessionsService } from '../services/sessions.service'
...@@ -347,6 +347,16 @@ export class TerminalTabComponent extends BaseTabComponent { ...@@ -347,6 +347,16 @@ export class TerminalTabComponent extends BaseTabComponent {
this.frontend.setZoom(this.zoom) this.frontend.setZoom(this.zoom)
} }
async getCurrentProcess (): Promise<BaseTabProcess> {
let children = await this.session.getChildProcesses()
if (!children.length) {
return null
}
return {
name: children[0].command
}
}
ngOnDestroy () { ngOnDestroy () {
this.frontend.detach(this.content.nativeElement) this.frontend.detach(this.content.nativeElement)
this.detachTermContainerHandlers() this.detachTermContainerHandlers()
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册