more platform changes

This commit is contained in:
Eugene Pankov
2021-06-03 19:07:48 +02:00
parent faa9a1269c
commit c0bd008f40
36 changed files with 318 additions and 165 deletions

View File

@@ -28,6 +28,9 @@ jobs:
cd app cd app
../node_modules/.bin/patch-package ../node_modules/.bin/patch-package
cd .. cd ..
cd terminus-terminal
../node_modules/.bin/patch-package
cd ..
- name: Build native deps - name: Build native deps
run: scripts/build-native.js run: scripts/build-native.js

View File

@@ -32,6 +32,9 @@ jobs:
cd app cd app
../node_modules/.bin/patch-package ../node_modules/.bin/patch-package
cd .. cd ..
cd terminus-terminal
../node_modules/.bin/patch-package
cd ..
- name: Build native deps - name: Build native deps
run: scripts/build-native.js run: scripts/build-native.js

View File

@@ -21,6 +21,11 @@ jobs:
npm i -g yarn@1.19.1 npm i -g yarn@1.19.1
yarn yarn
node scripts/build-native.js node scripts/build-native.js
cd terminus-terminal
../node_modules/.bin/patch-package
cd ..
yarn run build yarn run build
node scripts/prepackage-plugins.js node scripts/prepackage-plugins.js

View File

@@ -309,20 +309,6 @@ export class Window {
this.window.focus() this.window.focus()
}) })
ipcMain.on('window-maximize', event => {
if (!this.window || event.sender !== this.window.webContents) {
return
}
this.window.maximize()
})
ipcMain.on('window-unmaximize', event => {
if (!this.window || event.sender !== this.window.webContents) {
return
}
this.window.unmaximize()
})
ipcMain.on('window-toggle-maximize', event => { ipcMain.on('window-toggle-maximize', event => {
if (!this.window || event.sender !== this.window.webContents) { if (!this.window || event.sender !== this.window.webContents) {
return return

View File

@@ -0,0 +1,12 @@
import { Observable } from 'rxjs'
export abstract class HostWindowService {
abstract readonly closeRequest$: Observable<void>
abstract readonly isFullscreen: boolean
abstract reload (): void
abstract setTitle (title?: string): void
abstract toggleFullscreen (): void
abstract minimize (): void
abstract toggleMaximize (): void
abstract close (): void
}

View File

@@ -10,9 +10,10 @@ export { Theme } from './theme'
export { TabContextMenuItemProvider } from './tabContextMenuProvider' export { TabContextMenuItemProvider } from './tabContextMenuProvider'
export { SelectorOption } from './selector' export { SelectorOption } from './selector'
export { CLIHandler, CLIEvent } from './cli' export { CLIHandler, CLIEvent } from './cli'
export { PlatformService, ClipboardContent } from './platform' export { PlatformService, ClipboardContent, MessageBoxResult, MessageBoxOptions } from './platform'
export { MenuItemOptions } from './menu' export { MenuItemOptions } from './menu'
export { BootstrapData, BOOTSTRAP_DATA } from './mainProcess' export { BootstrapData, BOOTSTRAP_DATA } from './mainProcess'
export { HostWindowService } from './hostWindow'
export { AppService } from '../services/app.service' export { AppService } from '../services/app.service'
export { ConfigService } from '../services/config.service' export { ConfigService } from '../services/config.service'

View File

@@ -6,9 +6,22 @@ export interface ClipboardContent {
html?: string html?: string
} }
export interface MessageBoxOptions {
type: 'warning'|'error'
message: string
detail?: string
buttons: string[]
defaultId?: number
}
export interface MessageBoxResult {
response: number
}
export abstract class PlatformService { export abstract class PlatformService {
supportsWindowControls = false supportsWindowControls = false
abstract readClipboard (): string
abstract setClipboard (content: ClipboardContent): void abstract setClipboard (content: ClipboardContent): void
abstract loadConfig (): Promise<string> abstract loadConfig (): Promise<string>
abstract saveConfig (content: string): Promise<void> abstract saveConfig (content: string): Promise<void>
@@ -66,4 +79,5 @@ export abstract class PlatformService {
abstract openExternal (url: string): void abstract openExternal (url: string): void
abstract listFonts (): Promise<string[]> abstract listFonts (): Promise<string[]>
abstract popupContextMenu (menu: MenuItemOptions[], event?: MouseEvent): void abstract popupContextMenu (menu: MenuItemOptions[], event?: MouseEvent): void
abstract showMessageBox (options: MessageBoxOptions): Promise<MessageBoxResult>
} }

View File

@@ -1,15 +1,16 @@
title-bar( title-bar(
*ngIf='!hostApp.isFullScreen && config.store.appearance.frame == "full" && config.store.appearance.dock == "off"', *ngIf='ready && !hostWindow.isFullScreen && config.store.appearance.frame == "full" && config.store.appearance.dock == "off"',
[class.inset]='hostApp.platform == Platform.macOS && !hostApp.isFullScreen' [class.inset]='hostApp.platform == Platform.macOS && !hostWindow.isFullScreen'
) )
.content( .content(
*ngIf='ready',
[class.tabs-on-top]='config.store.appearance.tabsLocation == "top" || config.store.appearance.tabsLocation == "left"', [class.tabs-on-top]='config.store.appearance.tabsLocation == "top" || config.store.appearance.tabsLocation == "left"',
[class.tabs-on-side]='hasVerticalTabs()', [class.tabs-on-side]='hasVerticalTabs()',
) )
.tab-bar .tab-bar
.inset.background(*ngIf='hostApp.platform == Platform.macOS \ .inset.background(*ngIf='hostApp.platform == Platform.macOS \
&& !hostApp.isFullScreen \ && !hostWindow.isFullScreen \
&& config.store.appearance.frame == "thin" \ && config.store.appearance.frame == "thin" \
&& (config.store.appearance.tabsLocation == "top" || config.store.appearance.tabsLocation == "left")') && (config.store.appearance.tabsLocation == "top" || config.store.appearance.tabsLocation == "left")')
.tabs( .tabs(

View File

@@ -12,7 +12,7 @@ import { UpdaterService } from '../services/updater.service'
import { BaseTabComponent } from './baseTab.component' import { BaseTabComponent } from './baseTab.component'
import { SafeModeModalComponent } from './safeModeModal.component' import { SafeModeModalComponent } from './safeModeModal.component'
import { AppService, PlatformService, ToolbarButton, ToolbarButtonProvider } from '../api' import { AppService, HostWindowService, PlatformService, ToolbarButton, ToolbarButtonProvider } from '../api'
/** @hidden */ /** @hidden */
@Component({ @Component({
@@ -66,6 +66,7 @@ export class AppRootComponent {
private constructor ( private constructor (
private hotkeys: HotkeysService, private hotkeys: HotkeysService,
private updater: UpdaterService, private updater: UpdaterService,
public hostWindow: HostWindowService,
public hostApp: HostAppService, public hostApp: HostAppService,
public config: ConfigService, public config: ConfigService,
public app: AppService, public app: AppService,
@@ -78,9 +79,6 @@ export class AppRootComponent {
this.logger = log.create('main') this.logger = log.create('main')
this.logger.info('v', platform.getAppVersion()) this.logger.info('v', platform.getAppVersion())
this.leftToolbarButtons = this.getToolbarButtons(false)
this.rightToolbarButtons = this.getToolbarButtons(true)
this.updateIcon = require('../icons/gift.svg') this.updateIcon = require('../icons/gift.svg')
this.hotkeys.matchedHotkey.subscribe((hotkey: string) => { this.hotkeys.matchedHotkey.subscribe((hotkey: string) => {
@@ -114,7 +112,7 @@ export class AppRootComponent {
} }
} }
if (hotkey === 'toggle-fullscreen') { if (hotkey === 'toggle-fullscreen') {
this.hostApp.toggleFullscreen() hostWindow.toggleFullscreen()
} }
}) })
@@ -126,14 +124,6 @@ export class AppRootComponent {
ngbModal.open(SafeModeModalComponent) ngbModal.open(SafeModeModalComponent)
} }
setInterval(() => {
if (this.config.store.enableAutomaticUpdates) {
this.updater.check().then(available => {
this.updatesAvailable = available
})
}
}, 3600 * 12 * 1000)
this.app.tabOpened$.subscribe(tab => { this.app.tabOpened$.subscribe(tab => {
this.unsortedTabs.push(tab) this.unsortedTabs.push(tab)
this.noTabs = false this.noTabs = false
@@ -143,12 +133,26 @@ export class AppRootComponent {
this.unsortedTabs = this.unsortedTabs.filter(x => x !== tab) this.unsortedTabs = this.unsortedTabs.filter(x => x !== tab)
this.noTabs = app.tabs.length === 0 this.noTabs = app.tabs.length === 0
}) })
config.ready$.toPromise().then(() => {
this.leftToolbarButtons = this.getToolbarButtons(false)
this.rightToolbarButtons = this.getToolbarButtons(true)
setInterval(() => {
if (this.config.store.enableAutomaticUpdates) {
this.updater.check().then(available => {
this.updatesAvailable = available
})
}
}, 3600 * 12 * 1000)
})
} }
async ngOnInit () { async ngOnInit () {
this.ready = true this.config.ready$.toPromise().then(() => {
this.ready = true
this.app.emitReady() this.app.emitReady()
})
} }
@HostListener('dragover') @HostListener('dragover')

View File

@@ -2,7 +2,7 @@
import { Component } from '@angular/core' import { Component } from '@angular/core'
import { BaseTabComponent } from './baseTab.component' import { BaseTabComponent } from './baseTab.component'
import { ConfigService } from '../services/config.service' import { ConfigService } from '../services/config.service'
import { HostAppService } from '../services/hostApp.service' import { HostWindowService } from '../api/hostWindow'
/** @hidden */ /** @hidden */
@Component({ @Component({
@@ -16,7 +16,7 @@ export class WelcomeTabComponent extends BaseTabComponent {
enableGlobalHotkey = true enableGlobalHotkey = true
constructor ( constructor (
private hostApp: HostAppService, private hostWindow: HostWindowService,
public config: ConfigService, public config: ConfigService,
) { ) {
super() super()
@@ -38,6 +38,6 @@ export class WelcomeTabComponent extends BaseTabComponent {
this.config.store.hotkeys['toggle-window'] = [] this.config.store.hotkeys['toggle-window'] = []
} }
this.config.save() this.config.save()
this.hostApp.getWindow().reload() this.hostWindow.reload()
} }
} }

View File

@@ -1,4 +1,4 @@
import { NgModule, ModuleWithProviders, APP_INITIALIZER } from '@angular/core' import { NgModule, ModuleWithProviders } from '@angular/core'
import { BrowserModule } from '@angular/platform-browser' import { BrowserModule } from '@angular/platform-browser'
import { BrowserAnimationsModule } from '@angular/platform-browser/animations' import { BrowserAnimationsModule } from '@angular/platform-browser/animations'
import { FormsModule } from '@angular/forms' import { FormsModule } from '@angular/forms'
@@ -38,10 +38,6 @@ import { LastCLIHandler } from './cli'
import 'perfect-scrollbar/css/perfect-scrollbar.css' import 'perfect-scrollbar/css/perfect-scrollbar.css'
import 'ng2-dnd/bundles/style.css' import 'ng2-dnd/bundles/style.css'
function initialize (config: ConfigService) {
return () => config.ready$.toPromise()
}
const PROVIDERS = [ const PROVIDERS = [
{ provide: HotkeyProvider, useClass: AppHotkeyProvider, multi: true }, { provide: HotkeyProvider, useClass: AppHotkeyProvider, multi: true },
{ provide: Theme, useClass: StandardTheme, multi: true }, { provide: Theme, useClass: StandardTheme, multi: true },
@@ -54,7 +50,6 @@ const PROVIDERS = [
{ provide: TabRecoveryProvider, useClass: SplitTabRecoveryProvider, multi: true }, { provide: TabRecoveryProvider, useClass: SplitTabRecoveryProvider, multi: true },
{ provide: CLIHandler, useClass: LastCLIHandler, multi: true }, { provide: CLIHandler, useClass: LastCLIHandler, multi: true },
{ provide: PERFECT_SCROLLBAR_CONFIG, useValue: { suppressScrollX: true } }, { provide: PERFECT_SCROLLBAR_CONFIG, useValue: { suppressScrollX: true } },
{ provide: APP_INITIALIZER, useFactory: initialize, deps: [ConfigService], multi: true },
] ]
/** @hidden */ /** @hidden */

View File

@@ -10,6 +10,7 @@ import { SelectorModalComponent } from '../components/selectorModal.component'
import { SelectorOption } from '../api/selector' import { SelectorOption } from '../api/selector'
import { RecoveryToken } from '../api/tabRecovery' import { RecoveryToken } from '../api/tabRecovery'
import { BootstrapData, BOOTSTRAP_DATA } from '../api/mainProcess' import { BootstrapData, BOOTSTRAP_DATA } from '../api/mainProcess'
import { HostWindowService } from '../api/hostWindow'
import { ConfigService } from './config.service' import { ConfigService } from './config.service'
import { HostAppService } from './hostApp.service' import { HostAppService } from './hostApp.service'
@@ -73,6 +74,7 @@ export class AppService {
private constructor ( private constructor (
private config: ConfigService, private config: ConfigService,
private hostApp: HostAppService, private hostApp: HostAppService,
private hostWindow: HostWindowService,
private tabRecovery: TabRecoveryService, private tabRecovery: TabRecoveryService,
private tabsService: TabsService, private tabsService: TabsService,
private ngbModal: NgbModal, private ngbModal: NgbModal,
@@ -127,7 +129,7 @@ export class AppService {
tab.titleChange$.subscribe(title => { tab.titleChange$.subscribe(title => {
if (tab === this._activeTab) { if (tab === this._activeTab) {
this.hostApp.setTitle(title) this.hostWindow.setTitle(title)
} }
}) })
@@ -205,7 +207,7 @@ export class AppService {
setImmediate(() => { setImmediate(() => {
this._activeTab?.emitFocused() this._activeTab?.emitFocused()
}) })
this.hostApp.setTitle(this._activeTab?.title) this.hostWindow.setTitle(this._activeTab?.title)
} }
getParentTab (tab: BaseTabComponent): SplitTabComponent|null { getParentTab (tab: BaseTabComponent): SplitTabComponent|null {
@@ -332,7 +334,7 @@ export class AppService {
this.tabRecovery.enabled = false this.tabRecovery.enabled = false
await this.tabRecovery.saveTabs(this.tabs) await this.tabRecovery.saveTabs(this.tabs)
if (await this.closeAllTabs()) { if (await this.closeAllTabs()) {
this.hostApp.closeWindow() this.hostWindow.close()
} else { } else {
this.tabRecovery.enabled = true this.tabRecovery.enabled = true
} }

View File

@@ -89,9 +89,9 @@ export class ConfigService {
restartRequested: boolean restartRequested: boolean
/** Fires once when the config is loaded */ /** Fires once when the config is loaded */
get ready$ (): Observable<void> { return this.ready } get ready$ (): Observable<boolean> { return this.ready }
private ready = new AsyncSubject<void>() private ready = new AsyncSubject<boolean>()
private changed = new Subject<void>() private changed = new Subject<void>()
private _store: any private _store: any
private defaults: any private defaults: any
@@ -213,7 +213,7 @@ export class ConfigService {
private async init () { private async init () {
await this.load() await this.load()
this.ready.next() this.ready.next(true)
this.ready.complete() this.ready.complete()
this.hostApp.configChangeBroadcast$.subscribe(() => { this.hostApp.configChangeBroadcast$.subscribe(() => {

View File

@@ -1,5 +1,5 @@
import { Injectable } from '@angular/core' import { Injectable } from '@angular/core'
import { App, IpcRenderer, Shell, Dialog, Clipboard, GlobalShortcut, Screen, Remote, AutoUpdater, TouchBar, BrowserWindow, Menu, MenuItem, NativeImage, MessageBoxOptions } from 'electron' import { App, IpcRenderer, Shell, Dialog, Clipboard, GlobalShortcut, Screen, Remote, AutoUpdater, TouchBar, BrowserWindow, Menu, MenuItem, NativeImage } from 'electron'
import * as remote from '@electron/remote' import * as remote from '@electron/remote'
export interface MessageBoxResponse { export interface MessageBoxResponse {
@@ -44,11 +44,4 @@ export class ElectronService {
this.Menu = remote.Menu this.Menu = remote.Menu
this.MenuItem = remote.MenuItem this.MenuItem = remote.MenuItem
} }
async showMessageBox (
browserWindow: BrowserWindow,
options: MessageBoxOptions
): Promise<MessageBoxResponse> {
return this.dialog.showMessageBox(browserWindow, options)
}
} }

View File

@@ -33,7 +33,6 @@ export class HostAppService {
* Fired once the window is visible * Fired once the window is visible
*/ */
shown = new EventEmitter<any>() shown = new EventEmitter<any>()
isFullScreen = false
isPortable = !!process.env.PORTABLE_EXECUTABLE_FILE isPortable = !!process.env.PORTABLE_EXECUTABLE_FILE
private preferencesMenu = new Subject<void>() private preferencesMenu = new Subject<void>()
@@ -92,14 +91,6 @@ export class HostAppService {
this.logger.error('Unhandled exception:', err) this.logger.error('Unhandled exception:', err)
}) })
electron.ipcRenderer.on('host:window-enter-full-screen', () => this.zone.run(() => {
this.isFullScreen = true
}))
electron.ipcRenderer.on('host:window-leave-full-screen', () => this.zone.run(() => {
this.isFullScreen = false
}))
electron.ipcRenderer.on('host:window-shown', () => { electron.ipcRenderer.on('host:window-shown', () => {
this.zone.run(() => this.shown.emit()) this.zone.run(() => this.shown.emit())
}) })
@@ -163,11 +154,6 @@ export class HostAppService {
this.electron.ipcRenderer.send('app:new-window') this.electron.ipcRenderer.send('app:new-window')
} }
toggleFullscreen (): void {
const window = this.getWindow()
window.setFullScreen(!this.isFullScreen)
}
openDevTools (): void { openDevTools (): void {
this.getWindow().webContents.openDevTools({ mode: 'undocked' }) this.getWindow().webContents.openDevTools({ mode: 'undocked' })
} }
@@ -176,22 +162,6 @@ export class HostAppService {
this.electron.ipcRenderer.send('window-focus') this.electron.ipcRenderer.send('window-focus')
} }
minimize (): void {
this.electron.ipcRenderer.send('window-minimize')
}
maximize (): void {
this.electron.ipcRenderer.send('window-maximize')
}
unmaximize (): void {
this.electron.ipcRenderer.send('window-unmaximize')
}
toggleMaximize (): void {
this.electron.ipcRenderer.send('window-toggle-maximize')
}
setBounds (bounds: Bounds): void { setBounds (bounds: Bounds): void {
this.electron.ipcRenderer.send('window-set-bounds', bounds) this.electron.ipcRenderer.send('window-set-bounds', bounds)
} }
@@ -200,10 +170,6 @@ export class HostAppService {
this.electron.ipcRenderer.send('window-set-always-on-top', flag) this.electron.ipcRenderer.send('window-set-always-on-top', flag)
} }
setTitle (title?: string): void {
this.electron.ipcRenderer.send('window-set-title', title ?? 'Terminus')
}
setTouchBar (touchBar: TouchBar): void { setTouchBar (touchBar: TouchBar): void {
this.getWindow().setTouchBar(touchBar) this.getWindow().setTouchBar(touchBar)
} }
@@ -223,10 +189,6 @@ export class HostAppService {
this.electron.ipcRenderer.send('window-bring-to-front') this.electron.ipcRenderer.send('window-bring-to-front')
} }
closeWindow (): void {
this.electron.ipcRenderer.send('window-close')
}
registerGlobalHotkey (specs: string[]): void { registerGlobalHotkey (specs: string[]): void {
this.electron.ipcRenderer.send('app:register-global-hotkey', specs) this.electron.ipcRenderer.send('app:register-global-hotkey', specs)
} }

View File

@@ -1,18 +1,20 @@
import { NgModule } from '@angular/core' import { NgModule } from '@angular/core'
import { PlatformService, LogService, UpdaterService, DockingService, HostAppService, ThemesService, Platform, AppService, ConfigService, ElectronService, WIN_BUILD_FLUENT_BG_SUPPORTED, isWindowsBuild } from 'terminus-core' import { PlatformService, LogService, UpdaterService, DockingService, HostAppService, ThemesService, Platform, AppService, ConfigService, ElectronService, WIN_BUILD_FLUENT_BG_SUPPORTED, isWindowsBuild, HostWindowService } from 'terminus-core'
import { TerminalColorSchemeProvider } from 'terminus-terminal' import { TerminalColorSchemeProvider } from 'terminus-terminal'
import { HyperColorSchemes } from './colorSchemes' import { HyperColorSchemes } from './colorSchemes'
import { ElectronPlatformService } from './services/platform' import { ElectronPlatformService } from './services/platform.service'
import { ElectronLogService } from './services/log.service' import { ElectronLogService } from './services/log.service'
import { ElectronUpdaterService } from './services/updater.service' import { ElectronUpdaterService } from './services/updater.service'
import { TouchbarService } from './services/touchbar.service' import { TouchbarService } from './services/touchbar.service'
import { ElectronDockingService } from './services/docking.service' import { ElectronDockingService } from './services/docking.service'
import { ElectronHostWindow } from './services/hostWindow.service'
@NgModule({ @NgModule({
providers: [ providers: [
{ provide: TerminalColorSchemeProvider, useClass: HyperColorSchemes, multi: true }, { provide: TerminalColorSchemeProvider, useClass: HyperColorSchemes, multi: true },
{ provide: PlatformService, useClass: ElectronPlatformService }, { provide: PlatformService, useClass: ElectronPlatformService },
{ provide: HostWindowService, useClass: ElectronHostWindow },
{ provide: LogService, useClass: ElectronLogService }, { provide: LogService, useClass: ElectronLogService },
{ provide: UpdaterService, useClass: ElectronUpdaterService }, { provide: UpdaterService, useClass: ElectronUpdaterService },
{ provide: DockingService, useClass: ElectronDockingService }, { provide: DockingService, useClass: ElectronDockingService },

View File

@@ -0,0 +1,51 @@
import { Injectable, NgZone } from '@angular/core'
import { Observable, Subject } from 'rxjs'
import { ElectronService, HostAppService, HostWindowService } from 'terminus-core'
@Injectable({ providedIn: 'root' })
export class ElectronHostWindow extends HostWindowService {
get closeRequest$ (): Observable<void> { return this.closeRequest }
get isFullscreen (): boolean { return this._isFullScreen}
private closeRequest = new Subject<void>()
private _isFullScreen = false
constructor (
private electron: ElectronService,
private hostApp: HostAppService,
zone: NgZone,
) {
super()
electron.ipcRenderer.on('host:window-enter-full-screen', () => zone.run(() => {
this._isFullScreen = true
}))
electron.ipcRenderer.on('host:window-leave-full-screen', () => zone.run(() => {
this._isFullScreen = false
}))
}
reload (): void {
this.hostApp.getWindow().reload()
}
setTitle (title?: string): void {
this.electron.ipcRenderer.send('window-set-title', title ?? 'Terminus')
}
toggleFullscreen (): void {
this.hostApp.getWindow().setFullScreen(!this._isFullScreen)
}
minimize (): void {
this.electron.ipcRenderer.send('window-minimize')
}
toggleMaximize (): void {
this.electron.ipcRenderer.send('window-toggle-maximize')
}
close (): void {
this.electron.ipcRenderer.send('window-close')
}
}

View File

@@ -4,7 +4,7 @@ import * as os from 'os'
import promiseIpc from 'electron-promise-ipc' import promiseIpc from 'electron-promise-ipc'
import { execFile } from 'mz/child_process' import { execFile } from 'mz/child_process'
import { Injectable } from '@angular/core' import { Injectable } from '@angular/core'
import { PlatformService, ClipboardContent, HostAppService, Platform, ElectronService, MenuItemOptions } from 'terminus-core' import { PlatformService, ClipboardContent, HostAppService, Platform, ElectronService, MenuItemOptions, MessageBoxOptions, MessageBoxResult } from 'terminus-core'
const fontManager = require('fontmanager-redux') // eslint-disable-line const fontManager = require('fontmanager-redux') // eslint-disable-line
/* eslint-disable block-scoped-var */ /* eslint-disable block-scoped-var */
@@ -30,6 +30,10 @@ export class ElectronPlatformService extends PlatformService {
this.configPath = path.join(electron.app.getPath('userData'), 'config.yaml') this.configPath = path.join(electron.app.getPath('userData'), 'config.yaml')
} }
readClipboard (): string {
return this.electron.clipboard.readText()
}
setClipboard (content: ClipboardContent): void { setClipboard (content: ClipboardContent): void {
require('@electron/remote').clipboard.write(content) require('@electron/remote').clipboard.write(content)
} }
@@ -86,7 +90,7 @@ export class ElectronPlatformService extends PlatformService {
async loadConfig (): Promise<string> { async loadConfig (): Promise<string> {
if (await fs.exists(this.configPath)) { if (await fs.exists(this.configPath)) {
return fs.readFileSync(this.configPath, 'utf8') return fs.readFile(this.configPath, 'utf8')
} else { } else {
return '' return ''
} }
@@ -141,6 +145,12 @@ export class ElectronPlatformService extends PlatformService {
} }
popupContextMenu (menu: MenuItemOptions[], _event?: MouseEvent): void { popupContextMenu (menu: MenuItemOptions[], _event?: MouseEvent): void {
this.electron.Menu.buildFromTemplate(menu).popup({}) this.electron.Menu.buildFromTemplate(menu.map(item => ({
...item,
}))).popup({})
}
async showMessageBox (options: MessageBoxOptions): Promise<MessageBoxResult> {
return this.electron.dialog.showMessageBox(this.hostApp.getWindow(), options)
} }
} }

View File

@@ -2,8 +2,7 @@ import * as path from 'path'
import * as fs from 'mz/fs' import * as fs from 'mz/fs'
import { exec } from 'mz/child_process' import { exec } from 'mz/child_process'
import { Injectable } from '@angular/core' import { Injectable } from '@angular/core'
import { ElectronService } from '../../../terminus-core/src/services/electron.service' import { ElectronService, HostAppService, Platform } from 'terminus-core'
import { HostAppService, Platform } from '../../../terminus-core/src/services/hostApp.service'
/* eslint-disable block-scoped-var */ /* eslint-disable block-scoped-var */

View File

@@ -1,7 +1,7 @@
import { Injectable } from '@angular/core' import { Injectable } from '@angular/core'
import axios from 'axios' import axios from 'axios'
import { Logger, LogService, ElectronService, ConfigService, HostAppService, UpdaterService } from 'terminus-core' import { Logger, LogService, ElectronService, ConfigService, UpdaterService, PlatformService } from 'terminus-core'
const UPDATES_URL = 'https://api.github.com/repos/eugeny/terminus/releases/latest' const UPDATES_URL = 'https://api.github.com/repos/eugeny/terminus/releases/latest'
@@ -15,8 +15,8 @@ export class ElectronUpdaterService extends UpdaterService {
constructor ( constructor (
log: LogService, log: LogService,
config: ConfigService, config: ConfigService,
private platform: PlatformService,
private electron: ElectronService, private electron: ElectronService,
private hostApp: HostAppService,
) { ) {
super() super()
this.logger = log.create('updater') this.logger = log.create('updater')
@@ -42,18 +42,21 @@ export class ElectronUpdaterService extends UpdaterService {
electron.autoUpdater.once('update-downloaded', () => resolve(true)) electron.autoUpdater.once('update-downloaded', () => resolve(true))
}) })
if (config.store.enableAutomaticUpdates && this.electronUpdaterAvailable && !process.env.TERMINUS_DEV) {
this.logger.debug('Checking for updates') config.ready$.toPromise().then(() => {
try { if (config.store.enableAutomaticUpdates && this.electronUpdaterAvailable && !process.env.TERMINUS_DEV) {
electron.autoUpdater.setFeedURL({ this.logger.debug('Checking for updates')
url: `https://update.electronjs.org/eugeny/terminus/${process.platform}-${process.arch}/${electron.app.getVersion()}`, try {
}) electron.autoUpdater.setFeedURL({
electron.autoUpdater.checkForUpdates() url: `https://update.electronjs.org/eugeny/terminus/${process.platform}-${process.arch}/${electron.app.getVersion()}`,
} catch (e) { })
this.electronUpdaterAvailable = false electron.autoUpdater.checkForUpdates()
this.logger.info('Electron updater unavailable, falling back', e) } catch (e) {
this.electronUpdaterAvailable = false
this.logger.info('Electron updater unavailable, falling back', e)
}
} }
} })
} }
async check (): Promise<boolean> { async check (): Promise<boolean> {
@@ -117,8 +120,7 @@ export class ElectronUpdaterService extends UpdaterService {
if (!this.electronUpdaterAvailable) { if (!this.electronUpdaterAvailable) {
this.electron.shell.openExternal(this.updateURL) this.electron.shell.openExternal(this.updateURL)
} else { } else {
if ((await this.electron.showMessageBox( if ((await this.platform.showMessageBox(
this.hostApp.getWindow(),
{ {
type: 'warning', type: 'warning',
message: 'Installing the update will close all tabs and restart Terminus.', message: 'Installing the update will close all tabs and restart Terminus.',

View File

@@ -90,8 +90,7 @@ export class TerminalTabComponent extends BaseTerminalTabComponent {
if (!children?.length) { if (!children?.length) {
return true return true
} }
return (await this.electron.showMessageBox( return (await this.platform.showMessageBox(
this.hostApp.getWindow(),
{ {
type: 'warning', type: 'warning',
message: `"${children[0].command}" is still running. Close?`, message: `"${children[0].command}" is still running. Close?`,

View File

@@ -4,7 +4,7 @@ import { TerminalService } from './services/terminal.service'
/** @hidden */ /** @hidden */
@Injectable() @Injectable()
export class TerminalHotkeyProvider extends HotkeyProvider { export class LocalTerminalHotkeyProvider extends HotkeyProvider {
hotkeys: HotkeyDescription[] = [ hotkeys: HotkeyDescription[] = [
{ {
id: 'new-tab', id: 'new-tab',

View File

@@ -21,7 +21,7 @@ import { RecoveryProvider } from './recoveryProvider'
import { ShellProvider } from './api' import { ShellProvider } from './api'
import { ShellSettingsTabProvider } from './settings' import { ShellSettingsTabProvider } from './settings'
import { TerminalConfigProvider } from './config' import { TerminalConfigProvider } from './config'
import { TerminalHotkeyProvider } from './hotkeys' import { LocalTerminalHotkeyProvider } from './hotkeys'
import { NewTabContextMenu, SaveAsProfileContextMenu } from './tabContextMenu' import { NewTabContextMenu, SaveAsProfileContextMenu } from './tabContextMenu'
import { CmderShellProvider } from './shells/cmder' import { CmderShellProvider } from './shells/cmder'
@@ -55,7 +55,7 @@ import { AutoOpenTabCLIHandler, OpenPathCLIHandler, TerminalCLIHandler } from '.
{ provide: ToolbarButtonProvider, useClass: ButtonProvider, multi: true }, { provide: ToolbarButtonProvider, useClass: ButtonProvider, multi: true },
{ provide: TabRecoveryProvider, useClass: RecoveryProvider, multi: true }, { provide: TabRecoveryProvider, useClass: RecoveryProvider, multi: true },
{ provide: ConfigProvider, useClass: TerminalConfigProvider, multi: true }, { provide: ConfigProvider, useClass: TerminalConfigProvider, multi: true },
{ provide: HotkeyProvider, useClass: TerminalHotkeyProvider, multi: true }, { provide: HotkeyProvider, useClass: LocalTerminalHotkeyProvider, multi: true },
{ provide: ShellProvider, useClass: WindowsDefaultShellProvider, multi: true }, { provide: ShellProvider, useClass: WindowsDefaultShellProvider, multi: true },
{ provide: ShellProvider, useClass: MacOSDefaultShellProvider, multi: true }, { provide: ShellProvider, useClass: MacOSDefaultShellProvider, multi: true },

View File

@@ -2,7 +2,7 @@
import { Component } from '@angular/core' import { Component } from '@angular/core'
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap' import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
import { debounceTime, distinctUntilChanged, map } from 'rxjs/operators' import { debounceTime, distinctUntilChanged, map } from 'rxjs/operators'
import { ElectronService, HostAppService } from 'terminus-core' import { PlatformService } from 'terminus-core'
import { SerialConnection, LoginScript, SerialPortInfo, BAUD_RATES } from '../api' import { SerialConnection, LoginScript, SerialPortInfo, BAUD_RATES } from '../api'
import { SerialService } from '../services/serial.service' import { SerialService } from '../services/serial.service'
@@ -32,8 +32,7 @@ export class EditConnectionModalComponent {
constructor ( constructor (
private modalInstance: NgbActiveModal, private modalInstance: NgbActiveModal,
private electron: ElectronService, private platform: PlatformService,
private hostApp: HostAppService,
private serial: SerialService, private serial: SerialService,
) { ) {
} }
@@ -100,8 +99,7 @@ export class EditConnectionModalComponent {
} }
async deleteScript (script: LoginScript) { async deleteScript (script: LoginScript) {
if (this.connection.scripts && (await this.electron.showMessageBox( if (this.connection.scripts && (await this.platform.showMessageBox(
this.hostApp.getWindow(),
{ {
type: 'warning', type: 'warning',
message: 'Delete this script?', message: 'Delete this script?',

View File

@@ -1,7 +1,7 @@
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ /* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { Component } from '@angular/core' import { Component } from '@angular/core'
import { NgbModal } from '@ng-bootstrap/ng-bootstrap' import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
import { ConfigService, ElectronService, HostAppService } from 'terminus-core' import { ConfigService, PlatformService } from 'terminus-core'
import { SerialConnection } from '../api' import { SerialConnection } from '../api'
import { EditConnectionModalComponent } from './editConnectionModal.component' import { EditConnectionModalComponent } from './editConnectionModal.component'
@@ -14,8 +14,7 @@ export class SerialSettingsTabComponent {
constructor ( constructor (
public config: ConfigService, public config: ConfigService,
private electron: ElectronService, private platform: PlatformService,
private hostApp: HostAppService,
private ngbModal: NgbModal, private ngbModal: NgbModal,
) { ) {
this.connections = this.config.store.serial.connections this.connections = this.config.store.serial.connections
@@ -62,8 +61,7 @@ export class SerialSettingsTabComponent {
} }
async deleteConnection (connection: SerialConnection) { async deleteConnection (connection: SerialConnection) {
if ((await this.electron.showMessageBox( if ((await this.platform.showMessageBox(
this.hostApp.getWindow(),
{ {
type: 'warning', type: 'warning',
message: `Delete "${connection.name}"?`, message: `Delete "${connection.name}"?`,

View File

@@ -4,7 +4,7 @@ import { NgbModal, NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
import { Observable } from 'rxjs' import { Observable } from 'rxjs'
import { debounceTime, distinctUntilChanged, map } from 'rxjs/operators' import { debounceTime, distinctUntilChanged, map } from 'rxjs/operators'
import { ElectronService, HostAppService, ConfigService } from 'terminus-core' import { ElectronService, HostAppService, ConfigService, PlatformService } from 'terminus-core'
import { PasswordStorageService } from '../services/passwordStorage.service' import { PasswordStorageService } from '../services/passwordStorage.service'
import { SSHConnection, LoginScript, ForwardedPortConfig, SSHAlgorithmType, ALGORITHM_BLACKLIST } from '../api' import { SSHConnection, LoginScript, ForwardedPortConfig, SSHAlgorithmType, ALGORITHM_BLACKLIST } from '../api'
import { PromptModalComponent } from './promptModal.component' import { PromptModalComponent } from './promptModal.component'
@@ -29,6 +29,7 @@ export class EditConnectionModalComponent {
public config: ConfigService, public config: ConfigService,
private modalInstance: NgbActiveModal, private modalInstance: NgbActiveModal,
private electron: ElectronService, private electron: ElectronService,
private platform: PlatformService,
private hostApp: HostAppService, private hostApp: HostAppService,
private passwordStorage: PasswordStorageService, private passwordStorage: PasswordStorageService,
private ngbModal: NgbModal, private ngbModal: NgbModal,
@@ -153,8 +154,7 @@ export class EditConnectionModalComponent {
} }
async deleteScript (script: LoginScript) { async deleteScript (script: LoginScript) {
if (this.connection.scripts && (await this.electron.showMessageBox( if (this.connection.scripts && (await this.platform.showMessageBox(
this.hostApp.getWindow(),
{ {
type: 'warning', type: 'warning',
message: 'Delete this script?', message: 'Delete this script?',

View File

@@ -2,7 +2,7 @@
import deepClone from 'clone-deep' import deepClone from 'clone-deep'
import { Component } from '@angular/core' import { Component } from '@angular/core'
import { NgbModal } from '@ng-bootstrap/ng-bootstrap' import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
import { ConfigService, ElectronService, HostAppService, Platform } from 'terminus-core' import { ConfigService, HostAppService, Platform, PlatformService } from 'terminus-core'
import { PasswordStorageService } from '../services/passwordStorage.service' import { PasswordStorageService } from '../services/passwordStorage.service'
import { SSHConnection } from '../api' import { SSHConnection } from '../api'
import { EditConnectionModalComponent } from './editConnectionModal.component' import { EditConnectionModalComponent } from './editConnectionModal.component'
@@ -28,7 +28,7 @@ export class SSHSettingsTabComponent {
constructor ( constructor (
public config: ConfigService, public config: ConfigService,
public hostApp: HostAppService, public hostApp: HostAppService,
private electron: ElectronService, private platform: PlatformService,
private ngbModal: NgbModal, private ngbModal: NgbModal,
private passwordStorage: PasswordStorageService, private passwordStorage: PasswordStorageService,
) { ) {
@@ -81,8 +81,7 @@ export class SSHSettingsTabComponent {
} }
async deleteConnection (connection: SSHConnection) { async deleteConnection (connection: SSHConnection) {
if ((await this.electron.showMessageBox( if ((await this.platform.showMessageBox(
this.hostApp.getWindow(),
{ {
type: 'warning', type: 'warning',
message: `Delete "${connection.name}"?`, message: `Delete "${connection.name}"?`,
@@ -115,8 +114,7 @@ export class SSHSettingsTabComponent {
} }
async deleteGroup (group: SSHConnectionGroup) { async deleteGroup (group: SSHConnectionGroup) {
if ((await this.electron.showMessageBox( if ((await this.platform.showMessageBox(
this.hostApp.getWindow(),
{ {
type: 'warning', type: 'warning',
message: `Delete "${group.name}"?`, message: `Delete "${group.name}"?`,

View File

@@ -210,8 +210,7 @@ export class SSHTabComponent extends BaseTerminalTabComponent {
if (!(this.connection?.warnOnClose ?? this.config.store.ssh.warnOnClose)) { if (!(this.connection?.warnOnClose ?? this.config.store.ssh.warnOnClose)) {
return true return true
} }
return (await this.electron.showMessageBox( return (await this.platform.showMessageBox(
this.hostApp.getWindow(),
{ {
type: 'warning', type: 'warning',
message: `Disconnect from ${this.connection?.host}?`, message: `Disconnect from ${this.connection?.host}?`,

View File

@@ -3,7 +3,7 @@ import { first } from 'rxjs/operators'
import colors from 'ansi-colors' import colors from 'ansi-colors'
import { NgZone, OnInit, OnDestroy, Injector, ViewChild, HostBinding, Input, ElementRef, InjectFlags } from '@angular/core' import { NgZone, OnInit, OnDestroy, Injector, ViewChild, HostBinding, Input, ElementRef, InjectFlags } from '@angular/core'
import { trigger, transition, style, animate, AnimationTriggerMetadata } from '@angular/animations' import { trigger, transition, style, animate, AnimationTriggerMetadata } from '@angular/animations'
import { AppService, ConfigService, BaseTabComponent, ElectronService, HostAppService, HotkeysService, NotificationsService, Platform, LogService, Logger, TabContextMenuItemProvider, SplitTabComponent, SubscriptionContainer, MenuItemOptions, PlatformService } from 'terminus-core' import { AppService, ConfigService, BaseTabComponent, HostAppService, HotkeysService, NotificationsService, Platform, LogService, Logger, TabContextMenuItemProvider, SplitTabComponent, SubscriptionContainer, MenuItemOptions, PlatformService } from 'terminus-core'
import { BaseSession } from '../session' import { BaseSession } from '../session'
import { TerminalFrontendService } from '../services/terminalFrontend.service' import { TerminalFrontendService } from '../services/terminalFrontend.service'
@@ -82,7 +82,6 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
protected app: AppService protected app: AppService
protected hostApp: HostAppService protected hostApp: HostAppService
protected hotkeys: HotkeysService protected hotkeys: HotkeysService
protected electron: ElectronService
protected platform: PlatformService protected platform: PlatformService
protected terminalContainersService: TerminalFrontendService protected terminalContainersService: TerminalFrontendService
protected notifications: NotificationsService protected notifications: NotificationsService
@@ -135,7 +134,6 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
this.app = injector.get(AppService) this.app = injector.get(AppService)
this.hostApp = injector.get(HostAppService) this.hostApp = injector.get(HostAppService)
this.hotkeys = injector.get(HotkeysService) this.hotkeys = injector.get(HotkeysService)
this.electron = injector.get(ElectronService)
this.platform = injector.get(PlatformService) this.platform = injector.get(PlatformService)
this.terminalContainersService = injector.get(TerminalFrontendService) this.terminalContainersService = injector.get(TerminalFrontendService)
this.notifications = injector.get(NotificationsService) this.notifications = injector.get(NotificationsService)
@@ -359,7 +357,7 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
} }
async paste (): Promise<void> { async paste (): Promise<void> {
let data = this.electron.clipboard.readText() let data = this.platform.readClipboard()
if (this.config.store.terminal.bracketedPaste) { if (this.config.store.terminal.bracketedPaste) {
data = `\x1b[200~${data}\x1b[201~` data = `\x1b[200~${data}\x1b[201~`
} }
@@ -374,15 +372,13 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
if (data.includes('\r') && this.config.store.terminal.warnOnMultilinePaste) { if (data.includes('\r') && this.config.store.terminal.warnOnMultilinePaste) {
const buttons = ['Paste', 'Cancel'] const buttons = ['Paste', 'Cancel']
const result = (await this.electron.showMessageBox( const result = (await this.platform.showMessageBox(
this.hostApp.getWindow(),
{ {
type: 'warning', type: 'warning',
detail: data, detail: data,
message: `Paste multiple lines?`, message: `Paste multiple lines?`,
buttons, buttons,
defaultId: 0, defaultId: 0,
cancelId: 1,
} }
)).response )).response
if (result === 1) { if (result === 1) {
@@ -463,7 +459,7 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
cwd = await this.session.getWorkingDirectory() cwd = await this.session.getWorkingDirectory()
} }
if (cwd) { if (cwd) {
this.electron.clipboard.writeText(cwd) this.platform.setClipboard({ text: cwd })
this.notifications.notice('Copied') this.notifications.notice('Copied')
} else { } else {
this.notifications.error('Shell does not support current path detection') this.notifications.error('Shell does not support current path detection')

View File

@@ -2,7 +2,7 @@
import deepEqual from 'deep-equal' import deepEqual from 'deep-equal'
import { Component, Inject, Input, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core' import { Component, Inject, Input, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core'
import { ConfigService, HostAppService, ElectronService } from 'terminus-core' import { ConfigService, PlatformService } from 'terminus-core'
import { TerminalColorSchemeProvider } from '../api/colorSchemeProvider' import { TerminalColorSchemeProvider } from '../api/colorSchemeProvider'
import { TerminalColorScheme } from '../api/interfaces' import { TerminalColorScheme } from '../api/interfaces'
@@ -26,8 +26,7 @@ export class ColorSchemeSettingsTabComponent {
constructor ( constructor (
@Inject(TerminalColorSchemeProvider) private colorSchemeProviders: TerminalColorSchemeProvider[], @Inject(TerminalColorSchemeProvider) private colorSchemeProviders: TerminalColorSchemeProvider[],
private changeDetector: ChangeDetectorRef, private changeDetector: ChangeDetectorRef,
private hostApp: HostAppService, private platform: PlatformService,
private electron: ElectronService,
public config: ConfigService, public config: ConfigService,
) { } ) { }
@@ -76,8 +75,7 @@ export class ColorSchemeSettingsTabComponent {
} }
async deleteScheme (scheme: TerminalColorScheme) { async deleteScheme (scheme: TerminalColorScheme) {
if ((await this.electron.showMessageBox( if ((await this.platform.showMessageBox(
this.hostApp.getWindow(),
{ {
type: 'warning', type: 'warning',
message: `Delete "${scheme.name}"?`, message: `Delete "${scheme.name}"?`,

View File

@@ -2,13 +2,14 @@ import * as fs from 'fs'
import { Injectable } from '@angular/core' import { Injectable } from '@angular/core'
import { TerminalDecorator } from '../api/decorator' import { TerminalDecorator } from '../api/decorator'
import { BaseTerminalTabComponent } from '../api/baseTerminalTab.component' import { BaseTerminalTabComponent } from '../api/baseTerminalTab.component'
import { ElectronService, HostAppService } from 'terminus-core' import { ElectronService, HostAppService, PlatformService } from 'terminus-core'
/** @hidden */ /** @hidden */
@Injectable() @Injectable()
export class DebugDecorator extends TerminalDecorator { export class DebugDecorator extends TerminalDecorator {
constructor ( constructor (
private electron: ElectronService, private electron: ElectronService,
private platform: PlatformService,
private hostApp: HostAppService, private hostApp: HostAppService,
) { ) {
super() super()
@@ -93,7 +94,7 @@ export class DebugDecorator extends TerminalDecorator {
private async doCopyState (terminal: BaseTerminalTabComponent) { private async doCopyState (terminal: BaseTerminalTabComponent) {
const data = '```' + JSON.stringify(terminal.frontend!.saveState()) + '```' const data = '```' + JSON.stringify(terminal.frontend!.saveState()) + '```'
this.electron.clipboard.writeText(data) this.platform.setClipboard({ text: data })
} }
private async doLoadState (terminal: BaseTerminalTabComponent) { private async doLoadState (terminal: BaseTerminalTabComponent) {
@@ -104,7 +105,7 @@ export class DebugDecorator extends TerminalDecorator {
} }
private async doPasteState (terminal: BaseTerminalTabComponent) { private async doPasteState (terminal: BaseTerminalTabComponent) {
let data = this.electron.clipboard.readText() let data = this.platform.readClipboard()
if (data) { if (data) {
if (data.startsWith('`')) { if (data.startsWith('`')) {
data = data.substring(3, data.length - 3) data = data.substring(3, data.length - 3)
@@ -119,7 +120,7 @@ export class DebugDecorator extends TerminalDecorator {
private async doCopyOutput (buffer: string) { private async doCopyOutput (buffer: string) {
const data = '```' + JSON.stringify(buffer) + '```' const data = '```' + JSON.stringify(buffer) + '```'
this.electron.clipboard.writeText(data) this.platform.setClipboard({ text: data })
} }
private async doLoadOutput (terminal: BaseTerminalTabComponent) { private async doLoadOutput (terminal: BaseTerminalTabComponent) {
@@ -130,7 +131,7 @@ export class DebugDecorator extends TerminalDecorator {
} }
private async doPasteOutput (terminal: BaseTerminalTabComponent) { private async doPasteOutput (terminal: BaseTerminalTabComponent) {
let data = this.electron.clipboard.readText() let data = this.platform.readClipboard()
if (data) { if (data) {
if (data.startsWith('`')) { if (data.startsWith('`')) {
data = data.substring(3, data.length - 3) data = data.substring(3, data.length - 3)

View File

@@ -0,0 +1,13 @@
.modal-body
div {{options.message}}
small {{options.detail}}
.modal-footer
.ml-auto
button.btn(
*ngFor='let button of options.buttons; index as i',
[autofocus]='i === options.defaultId',
[class.btn-primary]='i === options.defaultId',
[class.btn-secondary]='i !== options.defaultId',
(click)='onButton(i)',
) {{button}}

View File

@@ -0,0 +1,34 @@
import { Component, Input, ElementRef, } from '@angular/core'
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
import { BaseComponent, HotkeysService, MessageBoxOptions } from 'terminus-core'
/** @hidden */
@Component({
template: require('./messageBoxModal.component.pug'),
})
export class MessageBoxModalComponent extends BaseComponent {
@Input() options: MessageBoxOptions
constructor (
hotkeys: HotkeysService,
private element: ElementRef,
private modalInstance: NgbActiveModal,
) {
super()
this.subscribeUntilDestroyed(hotkeys.key, (event: KeyboardEvent) => {
if (event.type === 'keydown') {
if (event.key === 'Enter' && this.options.defaultId !== undefined) {
this.modalInstance.close(this.options.defaultId)
}
}
})
}
ngAfterViewInit (): void {
this.element.nativeElement.querySelector('button[autofocus]').focus()
}
onButton (index: number): void {
this.modalInstance.close(index)
}
}

View File

@@ -1,17 +1,30 @@
import { NgModule } from '@angular/core' import { NgModule } from '@angular/core'
import { LogService, PlatformService, UpdaterService } from 'terminus-core' import { CommonModule } from '@angular/common'
import { HostWindowService, LogService, PlatformService, UpdaterService } from 'terminus-core'
import { WebPlatformService } from './platform' import { WebPlatformService } from './platform'
import { ConsoleLogService } from './services/log.service' import { ConsoleLogService } from './services/log.service'
import { NullUpdaterService } from './services/updater.service' import { NullUpdaterService } from './services/updater.service'
import { WebHostWindow } from './services/hostWindow.service'
import { MessageBoxModalComponent } from './components/messageBoxModal.component'
import './styles.scss' import './styles.scss'
@NgModule({ @NgModule({
imports: [
CommonModule,
],
providers: [ providers: [
{ provide: PlatformService, useClass: WebPlatformService }, { provide: PlatformService, useClass: WebPlatformService },
{ provide: LogService, useClass: ConsoleLogService }, { provide: LogService, useClass: ConsoleLogService },
{ provide: UpdaterService, useClass: NullUpdaterService }, { provide: UpdaterService, useClass: NullUpdaterService },
{ provide: HostWindowService, useClass: WebHostWindow },
],
declarations: [
MessageBoxModalComponent,
],
entryComponents: [
MessageBoxModalComponent,
], ],
}) })
export default class WebModule { } // eslint-disable-line @typescript-eslint/no-extraneous-class export default class WebModule { } // eslint-disable-line @typescript-eslint/no-extraneous-class

View File

@@ -1,11 +1,13 @@
import '@vaadin/vaadin-context-menu/vaadin-context-menu.js' import '@vaadin/vaadin-context-menu/vaadin-context-menu.js'
import copyToClipboard from 'copy-text-to-clipboard' import copyToClipboard from 'copy-text-to-clipboard'
import { Injectable } from '@angular/core' import { Injectable } from '@angular/core'
import { PlatformService, ClipboardContent, MenuItemOptions } from 'terminus-core' import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
import { PlatformService, ClipboardContent, MenuItemOptions, MessageBoxOptions, MessageBoxResult } from 'terminus-core'
// eslint-disable-next-line no-duplicate-imports // eslint-disable-next-line no-duplicate-imports
import type { ContextMenuElement, ContextMenuItem } from '@vaadin/vaadin-context-menu/vaadin-context-menu.js' import type { ContextMenuElement, ContextMenuItem } from '@vaadin/vaadin-context-menu/vaadin-context-menu.js'
import { MessageBoxModalComponent } from './components/messageBoxModal.component'
import './styles.scss' import './styles.scss'
@Injectable() @Injectable()
@@ -13,14 +15,19 @@ export class WebPlatformService extends PlatformService {
private menu: ContextMenuElement private menu: ContextMenuElement
private contextMenuHandlers = new Map<ContextMenuItem, () => void>() private contextMenuHandlers = new Map<ContextMenuItem, () => void>()
constructor () { constructor (
private ngbModal: NgbModal,
) {
super() super()
this.menu = window.document.createElement('vaadin-context-menu') this.menu = window.document.createElement('vaadin-context-menu')
this.menu.addEventListener('item-selected', e => { this.menu.addEventListener('item-selected', e => {
this.contextMenuHandlers.get(e.detail.value)?.() this.contextMenuHandlers.get(e.detail.value)?.()
}) })
document.body.appendChild(this.menu) document.body.appendChild(this.menu)
console.log(require('./styles.scss')) }
readClipboard (): string {
return ''
} }
setClipboard (content: ClipboardContent): void { setClipboard (content: ClipboardContent): void {
@@ -73,4 +80,19 @@ export class WebPlatformService extends PlatformService {
} }
return cmi return cmi
} }
async showMessageBox (options: MessageBoxOptions): Promise<MessageBoxResult> {
console.log(options)
const modal = this.ngbModal.open(MessageBoxModalComponent, {
backdrop: 'static',
})
const instance: MessageBoxModalComponent = modal.componentInstance
instance.options = options
try {
const response = await modal.result
return { response }
} catch {
return { response: 0 }
}
}
} }

View File

@@ -0,0 +1,39 @@
import { Injectable } from '@angular/core'
import { Observable, Subject } from 'rxjs'
import { HostWindowService } from 'terminus-core'
@Injectable({ providedIn: 'root' })
export class WebHostWindow extends HostWindowService {
get closeRequest$ (): Observable<void> { return this.closeRequest }
get isFullscreen (): boolean { return !!document.fullscreenElement }
private closeRequest = new Subject<void>()
reload (): void {
location.reload()
}
setTitle (title?: string): void {
document.title = title ?? 'Terminus'
}
toggleFullscreen (): void {
if (this.isFullscreen) {
document.exitFullscreen()
} else {
document.body.requestFullscreen({ navigationUI: 'hide' })
}
}
minimize (): void {
throw new Error('Unavailable')
}
toggleMaximize (): void {
throw new Error('Unavailable')
}
close (): void {
window.close()
}
}