mirror of
https://github.com/Eugeny/tabby.git
synced 2025-02-23 14:59:41 +08:00
process completion notifications
This commit is contained in:
parent
822e068bb5
commit
b3f15e27c6
@ -1,6 +1,10 @@
|
||||
import { Observable, Subject } from 'rxjs'
|
||||
import { ViewRef } from '@angular/core'
|
||||
|
||||
export interface BaseTabProcess {
|
||||
name: string
|
||||
}
|
||||
|
||||
export abstract class BaseTabComponent {
|
||||
private static lastTabID = 0
|
||||
id: number
|
||||
@ -14,6 +18,7 @@ export abstract class BaseTabComponent {
|
||||
protected blurred = new Subject<void>()
|
||||
protected progress = new Subject<number>()
|
||||
protected activity = new Subject<boolean>()
|
||||
protected destroyed = new Subject<void>()
|
||||
|
||||
private progressClearTimeout: number
|
||||
|
||||
@ -22,6 +27,7 @@ export abstract class BaseTabComponent {
|
||||
get titleChange$ (): Observable<string> { return this.titleChange }
|
||||
get progress$ (): Observable<number> { return this.progress }
|
||||
get activity$ (): Observable<boolean> { return this.activity }
|
||||
get destroyed$ (): Observable<void> { return this.destroyed }
|
||||
|
||||
constructor () {
|
||||
this.id = BaseTabComponent.lastTabID++
|
||||
@ -66,6 +72,10 @@ export abstract class BaseTabComponent {
|
||||
return null
|
||||
}
|
||||
|
||||
async getCurrentProcess (): Promise<BaseTabProcess> {
|
||||
return null
|
||||
}
|
||||
|
||||
async canClose (): Promise<boolean> {
|
||||
return true
|
||||
}
|
||||
@ -83,5 +93,7 @@ export abstract class BaseTabComponent {
|
||||
this.blurred.complete()
|
||||
this.titleChange.complete()
|
||||
this.progress.complete()
|
||||
this.destroyed.next()
|
||||
this.destroyed.complete()
|
||||
}
|
||||
}
|
||||
|
@ -20,57 +20,16 @@ export class TabHeaderComponent {
|
||||
@Input() progress: number
|
||||
@ViewChild('handle') handle: ElementRef
|
||||
|
||||
private contextMenu: any
|
||||
private completionNotificationEnabled = false
|
||||
|
||||
constructor (
|
||||
zone: NgZone,
|
||||
electron: ElectronService,
|
||||
public app: AppService,
|
||||
private electron: ElectronService,
|
||||
private zone: NgZone,
|
||||
private hostApp: HostAppService,
|
||||
private ngbModal: NgbModal,
|
||||
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 () {
|
||||
if (this.hostApp.platform === Platform.macOS) {
|
||||
@ -90,17 +49,86 @@ export class TabHeaderComponent {
|
||||
}).catch(() => null)
|
||||
}
|
||||
|
||||
@HostListener('auxclick', ['$event']) onAuxClick ($event: MouseEvent): void {
|
||||
@HostListener('auxclick', ['$event']) async onAuxClick ($event: MouseEvent) {
|
||||
if ($event.which === 2) {
|
||||
this.app.closeTab(this.tab, true)
|
||||
}
|
||||
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,
|
||||
y: $event.pageY,
|
||||
async: true,
|
||||
})
|
||||
event.preventDefault()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { Observable, Subject, AsyncSubject } from 'rxjs'
|
||||
import { takeUntil } from 'rxjs/operators'
|
||||
import { Injectable, ComponentFactoryResolver, Injector } from '@angular/core'
|
||||
import { BaseTabComponent } from '../components/baseTab.component'
|
||||
import { Logger, LogService } from './log.service'
|
||||
@ -7,6 +8,33 @@ import { HostAppService } from './hostApp.service'
|
||||
|
||||
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()
|
||||
export class AppService {
|
||||
tabs: BaseTabComponent[] = []
|
||||
@ -20,6 +48,8 @@ export class AppService {
|
||||
private tabClosed = new Subject<BaseTabComponent>()
|
||||
private ready = new AsyncSubject<void>()
|
||||
|
||||
private completionObservers = new Map<BaseTabComponent, CompletionObserver>()
|
||||
|
||||
get activeTabChange$ (): Observable<BaseTabComponent> { return this.activeTabChange }
|
||||
get tabOpened$ (): Observable<BaseTabComponent> { return this.tabOpened }
|
||||
get tabsChanged$ (): Observable<void> { return this.tabsChanged }
|
||||
@ -133,4 +163,19 @@ export class AppService {
|
||||
this.ready.complete()
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { Injectable } from '@angular/core'
|
||||
import { TouchBar, BrowserWindow, Menu } from 'electron'
|
||||
import { TouchBar, BrowserWindow, Menu, MenuItem } from 'electron'
|
||||
|
||||
@Injectable()
|
||||
export class ElectronService {
|
||||
@ -15,6 +15,7 @@ export class ElectronService {
|
||||
TouchBar: typeof TouchBar
|
||||
BrowserWindow: typeof BrowserWindow
|
||||
Menu: typeof Menu
|
||||
MenuItem: typeof MenuItem
|
||||
private electron: any
|
||||
|
||||
constructor () {
|
||||
@ -31,6 +32,7 @@ export class ElectronService {
|
||||
this.TouchBar = this.remote.TouchBar
|
||||
this.BrowserWindow = this.remote.BrowserWindow
|
||||
this.Menu = this.remote.Menu
|
||||
this.MenuItem = this.remote.MenuItem
|
||||
}
|
||||
|
||||
remoteRequire (name: string): any {
|
||||
|
@ -2,7 +2,7 @@ import { Observable, Subject, Subscription } from 'rxjs'
|
||||
import { first } from 'rxjs/operators'
|
||||
import { ToastrService } from 'ngx-toastr'
|
||||
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 { Session, SessionsService } from '../services/sessions.service'
|
||||
@ -347,6 +347,16 @@ export class TerminalTabComponent extends BaseTabComponent {
|
||||
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 () {
|
||||
this.frontend.detach(this.content.nativeElement)
|
||||
this.detachTermContainerHandlers()
|
||||
|
Loading…
Reference in New Issue
Block a user