experimental support for multiple windows (fixes #212, fixes #170)

This commit is contained in:
Eugene Pankov
2018-08-31 15:41:28 +02:00
parent 0749096d9f
commit 4b7b692ace
25 changed files with 577 additions and 395 deletions

View File

@@ -142,9 +142,9 @@ export class AppRootComponent {
this.unsortedTabs.push(tab)
tab.progress$.subscribe(progress => {
if (progress !== null) {
this.hostApp.getWindow().setProgressBar(progress / 100.0, 'normal')
this.hostApp.getWindow().setProgressBar(progress / 100.0, { mode: 'normal' })
} else {
this.hostApp.getWindow().setProgressBar(-1, 'none')
this.hostApp.getWindow().setProgressBar(-1, { mode: 'none' })
}
})
})
@@ -154,26 +154,26 @@ export class AppRootComponent {
}
onGlobalHotkey () {
if (this.electron.app.window.isFocused()) {
if (this.hostApp.getWindow().isFocused()) {
// focused
this.electron.loseFocus()
if (this.hostApp.platform !== Platform.macOS) {
this.electron.app.window.hide()
this.hostApp.getWindow().hide()
}
} else {
if (!this.electron.app.window.isVisible()) {
if (!this.hostApp.getWindow().isVisible()) {
// unfocused, invisible
this.electron.app.window.show()
this.electron.app.window.focus()
this.hostApp.getWindow().show()
this.hostApp.getWindow().focus()
} else {
if (this.config.store.appearance.dock === 'off') {
// not docked, visible
setTimeout(() => {
this.electron.app.window.focus()
this.hostApp.getWindow().focus()
})
} else {
// docked, visible
this.electron.app.window.hide()
this.hostApp.getWindow().hide()
}
}
}
@@ -223,7 +223,7 @@ export class AppRootComponent {
}
private updateVibrancy () {
this.hostApp.setVibrancy(this.config.store.appearance.vibrancy)
this.hostApp.getWindow().setOpacity(this.config.store.appearance.opacity)
this.hostApp.setVibrancy(this.config.store.appearance.vibrancy)
this.hostApp.getWindow().setOpacity(this.config.store.appearance.opacity)
}
}

View File

@@ -1,4 +1,6 @@
hotkeys:
new-window:
- 'Ctrl-Shift-N'
toggle-window:
- 'Ctrl+Space'
toggle-fullscreen:

View File

@@ -1,4 +1,6 @@
hotkeys:
new-window:
- '⌘-N'
toggle-window:
- 'Ctrl+Space'
toggle-fullscreen:

View File

@@ -1,4 +1,6 @@
hotkeys:
new-window:
- 'Ctrl-Shift-N'
toggle-window:
- 'Ctrl+Space'
toggle-fullscreen:

View File

@@ -3,6 +3,7 @@ import { Injectable, ComponentFactoryResolver, Injector } from '@angular/core'
import { BaseTabComponent } from '../components/baseTab.component'
import { Logger, LogService } from './log.service'
import { ConfigService } from './config.service'
import { HostAppService } from './hostApp.service'
export declare type TabComponentType = new (...args: any[]) => BaseTabComponent
@@ -28,6 +29,7 @@ export class AppService {
constructor (
private componentFactoryResolver: ComponentFactoryResolver,
private config: ConfigService,
private hostApp: HostAppService,
private injector: Injector,
log: LogService,
) {
@@ -37,15 +39,21 @@ export class AppService {
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 || {})
let tab = componentRef.instance
tab.hostView = componentRef.hostView
Object.assign(tab, inputs || {})
this.tabs.push(componentRef.instance)
this.selectTab(componentRef.instance)
this.tabs.push(tab)
this.selectTab(tab)
this.tabsChanged.next()
this.tabOpened.next(componentRef.instance)
this.tabOpened.next(tab)
return componentRef.instance
tab.titleChange$.subscribe(title => {
if (tab === this.activeTab) {
this.hostApp.getWindow().setTitle(title)
}
})
return tab
}
selectTab (tab: BaseTabComponent) {
@@ -67,6 +75,7 @@ export class AppService {
if (this.activeTab) {
this.activeTab.emitFocused()
}
this.hostApp.getWindow().setTitle(this.activeTab.title)
}
toggleLastTab () {
@@ -122,5 +131,6 @@ export class AppService {
emitReady () {
this.ready.next(null)
this.ready.complete()
this.hostApp.emitReady()
}
}

View File

@@ -63,7 +63,7 @@ export class ConfigService {
constructor (
electron: ElectronService,
hostApp: HostAppService,
private hostApp: HostAppService,
@Inject(ConfigProvider) configProviders: ConfigProvider[],
) {
this.path = path.join(electron.app.getPath('userData'), 'config.yaml')
@@ -78,6 +78,11 @@ export class ConfigService {
return defaults
}).reduce(configMerge)
this.load()
hostApp.configChangeBroadcast$.subscribe(() => {
this.load()
this.emitChange()
})
}
getDefaults () {
@@ -96,6 +101,7 @@ export class ConfigService {
save (): void {
fs.writeFileSync(this.path, yaml.safeDump(this._store), 'utf8')
this.emitChange()
this.hostApp.broadcastConfigChange()
}
readRaw (): string {

View File

@@ -76,12 +76,8 @@ export class DockingService {
})
}
getWindow () {
return this.electron.app.window
}
repositionWindow () {
let [x, y] = this.getWindow().getPosition()
let [x, y] = this.hostApp.getWindow().getPosition()
for (let screen of this.electron.screen.getAllDisplays()) {
let bounds = screen.bounds
if (x >= bounds.x && x <= bounds.x + bounds.width && y >= bounds.y && y <= bounds.y + bounds.height) {
@@ -89,6 +85,6 @@ export class DockingService {
}
}
let screen = this.electron.screen.getPrimaryDisplay()
this.getWindow().setPosition(screen.bounds.x, screen.bounds.y)
this.hostApp.getWindow().setPosition(screen.bounds.x, screen.bounds.y)
}
}

View File

@@ -1,5 +1,5 @@
import { Injectable } from '@angular/core'
import { TouchBar } from 'electron'
import { TouchBar, BrowserWindow } from 'electron'
@Injectable()
export class ElectronService {
@@ -13,6 +13,7 @@ export class ElectronService {
screen: any
remote: any
TouchBar: typeof TouchBar
BrowserWindow: typeof BrowserWindow
private electron: any
constructor () {
@@ -27,6 +28,7 @@ export class ElectronService {
this.globalShortcut = this.remote.globalShortcut
this.nativeImage = this.remote.nativeImage
this.TouchBar = this.remote.TouchBar
this.BrowserWindow = this.remote.BrowserWindow
}
remoteRequire (name: string): any {

View File

@@ -1,8 +1,8 @@
import * as path from 'path'
import { Observable, Subject } from 'rxjs'
import { Injectable, NgZone, EventEmitter } from '@angular/core'
import { ElectronService } from '../services/electron.service'
import { Logger, LogService } from '../services/log.service'
import { ElectronService } from './electron.service'
import { Logger, LogService } from './log.service'
export enum Platform {
Linux, macOS, Windows,
@@ -19,19 +19,21 @@ export interface Bounds {
export class HostAppService {
platform: Platform
nodePlatform: string
ready = new EventEmitter<any>()
shown = new EventEmitter<any>()
isFullScreen = false
private preferencesMenu = new Subject<void>()
private secondInstance = new Subject<void>()
private cliOpenDirectory = new Subject<string>()
private cliRunCommand = new Subject<string[]>()
private configChangeBroadcast = new Subject<void>()
private logger: Logger
private windowId: number
get preferencesMenu$ (): Observable<void> { return this.preferencesMenu }
get secondInstance$ (): Observable<void> { return this.secondInstance }
get cliOpenDirectory$ (): Observable<string> { return this.cliOpenDirectory }
get cliRunCommand$ (): Observable<string[]> { return this.cliRunCommand }
get configChangeBroadcast$ (): Observable<void> { return this.configChangeBroadcast }
constructor (
private zone: NgZone,
@@ -46,9 +48,12 @@ export class HostAppService {
linux: Platform.Linux
}[this.nodePlatform]
this.windowId = parseInt(location.search.substring(1))
this.logger.info('Window ID:', this.windowId)
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)
})
@@ -64,7 +69,7 @@ export class HostAppService {
this.zone.run(() => this.shown.emit())
})
electron.ipcRenderer.on('host:second-instance', ($event, argv: any, cwd: string) => this.zone.run(() => {
electron.ipcRenderer.on('host:second-instance', (_$event, argv: any, cwd: string) => this.zone.run(() => {
this.logger.info('Second instance', argv)
const op = argv._[0]
if (op === 'open') {
@@ -74,13 +79,17 @@ export class HostAppService {
}
}))
this.ready.subscribe(() => {
electron.ipcRenderer.send('app:ready')
})
electron.ipcRenderer.on('host:config-change', () => this.zone.run(() => {
this.configChangeBroadcast.next()
}))
}
getWindow () {
return this.electron.app.window
return this.electron.BrowserWindow.fromId(this.windowId)
}
newWindow () {
this.electron.ipcRenderer.send('app:new-window')
}
getShell () {
@@ -142,6 +151,14 @@ export class HostAppService {
}
}
broadcastConfigChange () {
this.electron.ipcRenderer.send('app:config-change')
}
emitReady () {
this.electron.ipcRenderer.send('app:ready')
}
quit () {
this.logger.info('Quitting')
this.electron.app.quit()

View File

@@ -174,6 +174,10 @@ export class HotkeysService {
@Injectable()
export class AppHotkeyProvider extends HotkeyProvider {
hotkeys: IHotkeyDescription[] = [
{
id: 'new-window',
name: 'New window',
},
{
id: 'toggle-window',
name: 'Toggle terminal window',

View File

@@ -3,6 +3,7 @@ import { TouchBarSegmentedControl, SegmentedControlSegment } from 'electron'
import { AppService } from './app.service'
import { ConfigService } from './config.service'
import { ElectronService } from './electron.service'
import { HostAppService } from './hostApp.service'
import { IToolbarButton, ToolbarButtonProvider } from '../api'
@Injectable()
@@ -12,6 +13,7 @@ export class TouchbarService {
constructor (
private app: AppService,
private hostApp: HostAppService,
@Inject(ToolbarButtonProvider) private toolbarButtonProviders: ToolbarButtonProvider[],
private config: ConfigService,
private electron: ElectronService,
@@ -51,7 +53,7 @@ export class TouchbarService {
...buttons.map(button => this.getButton(button))
]
})
this.electron.app.window.setTouchBar(touchBar)
this.hostApp.getWindow().setTouchBar(touchBar)
}
private getButton (button: IToolbarButton): Electron.TouchBarButton {