2
0
mirror of https://github.com/Eugeny/tabby.git synced 2025-03-25 15:40:32 +08:00

more platform changes

This commit is contained in:
Eugene Pankov 2021-06-03 19:07:48 +02:00
parent faa9a1269c
commit c0bd008f40
No known key found for this signature in database
GPG Key ID: 5896FCBBDD1CF4F4
36 changed files with 318 additions and 165 deletions

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

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

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

@ -309,20 +309,6 @@ export class Window {
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 => {
if (!this.window || event.sender !== this.window.webContents) {
return

@ -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
}

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

@ -6,9 +6,22 @@ export interface ClipboardContent {
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 {
supportsWindowControls = false
abstract readClipboard (): string
abstract setClipboard (content: ClipboardContent): void
abstract loadConfig (): Promise<string>
abstract saveConfig (content: string): Promise<void>
@ -66,4 +79,5 @@ export abstract class PlatformService {
abstract openExternal (url: string): void
abstract listFonts (): Promise<string[]>
abstract popupContextMenu (menu: MenuItemOptions[], event?: MouseEvent): void
abstract showMessageBox (options: MessageBoxOptions): Promise<MessageBoxResult>
}

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

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

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

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

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

@ -89,9 +89,9 @@ export class ConfigService {
restartRequested: boolean
/** 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 _store: any
private defaults: any
@ -213,7 +213,7 @@ export class ConfigService {
private async init () {
await this.load()
this.ready.next()
this.ready.next(true)
this.ready.complete()
this.hostApp.configChangeBroadcast$.subscribe(() => {

@ -1,5 +1,5 @@
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'
export interface MessageBoxResponse {
@ -44,11 +44,4 @@ export class ElectronService {
this.Menu = remote.Menu
this.MenuItem = remote.MenuItem
}
async showMessageBox (
browserWindow: BrowserWindow,
options: MessageBoxOptions
): Promise<MessageBoxResponse> {
return this.dialog.showMessageBox(browserWindow, options)
}
}

@ -33,7 +33,6 @@ export class HostAppService {
* Fired once the window is visible
*/
shown = new EventEmitter<any>()
isFullScreen = false
isPortable = !!process.env.PORTABLE_EXECUTABLE_FILE
private preferencesMenu = new Subject<void>()
@ -92,14 +91,6 @@ export class HostAppService {
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', () => {
this.zone.run(() => this.shown.emit())
})
@ -163,11 +154,6 @@ export class HostAppService {
this.electron.ipcRenderer.send('app:new-window')
}
toggleFullscreen (): void {
const window = this.getWindow()
window.setFullScreen(!this.isFullScreen)
}
openDevTools (): void {
this.getWindow().webContents.openDevTools({ mode: 'undocked' })
}
@ -176,22 +162,6 @@ export class HostAppService {
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 {
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)
}
setTitle (title?: string): void {
this.electron.ipcRenderer.send('window-set-title', title ?? 'Terminus')
}
setTouchBar (touchBar: TouchBar): void {
this.getWindow().setTouchBar(touchBar)
}
@ -223,10 +189,6 @@ export class HostAppService {
this.electron.ipcRenderer.send('window-bring-to-front')
}
closeWindow (): void {
this.electron.ipcRenderer.send('window-close')
}
registerGlobalHotkey (specs: string[]): void {
this.electron.ipcRenderer.send('app:register-global-hotkey', specs)
}

@ -1,18 +1,20 @@
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 { HyperColorSchemes } from './colorSchemes'
import { ElectronPlatformService } from './services/platform'
import { ElectronPlatformService } from './services/platform.service'
import { ElectronLogService } from './services/log.service'
import { ElectronUpdaterService } from './services/updater.service'
import { TouchbarService } from './services/touchbar.service'
import { ElectronDockingService } from './services/docking.service'
import { ElectronHostWindow } from './services/hostWindow.service'
@NgModule({
providers: [
{ provide: TerminalColorSchemeProvider, useClass: HyperColorSchemes, multi: true },
{ provide: PlatformService, useClass: ElectronPlatformService },
{ provide: HostWindowService, useClass: ElectronHostWindow },
{ provide: LogService, useClass: ElectronLogService },
{ provide: UpdaterService, useClass: ElectronUpdaterService },
{ provide: DockingService, useClass: ElectronDockingService },

@ -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')
}
}

@ -4,7 +4,7 @@ import * as os from 'os'
import promiseIpc from 'electron-promise-ipc'
import { execFile } from 'mz/child_process'
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
/* eslint-disable block-scoped-var */
@ -30,6 +30,10 @@ export class ElectronPlatformService extends PlatformService {
this.configPath = path.join(electron.app.getPath('userData'), 'config.yaml')
}
readClipboard (): string {
return this.electron.clipboard.readText()
}
setClipboard (content: ClipboardContent): void {
require('@electron/remote').clipboard.write(content)
}
@ -86,7 +90,7 @@ export class ElectronPlatformService extends PlatformService {
async loadConfig (): Promise<string> {
if (await fs.exists(this.configPath)) {
return fs.readFileSync(this.configPath, 'utf8')
return fs.readFile(this.configPath, 'utf8')
} else {
return ''
}
@ -141,6 +145,12 @@ export class ElectronPlatformService extends PlatformService {
}
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)
}
}

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

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

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

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

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

@ -2,7 +2,7 @@
import { Component } from '@angular/core'
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
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 { SerialService } from '../services/serial.service'
@ -32,8 +32,7 @@ export class EditConnectionModalComponent {
constructor (
private modalInstance: NgbActiveModal,
private electron: ElectronService,
private hostApp: HostAppService,
private platform: PlatformService,
private serial: SerialService,
) {
}
@ -100,8 +99,7 @@ export class EditConnectionModalComponent {
}
async deleteScript (script: LoginScript) {
if (this.connection.scripts && (await this.electron.showMessageBox(
this.hostApp.getWindow(),
if (this.connection.scripts && (await this.platform.showMessageBox(
{
type: 'warning',
message: 'Delete this script?',

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

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

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

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

@ -3,7 +3,7 @@ import { first } from 'rxjs/operators'
import colors from 'ansi-colors'
import { NgZone, OnInit, OnDestroy, Injector, ViewChild, HostBinding, Input, ElementRef, InjectFlags } from '@angular/core'
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 { TerminalFrontendService } from '../services/terminalFrontend.service'
@ -82,7 +82,6 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
protected app: AppService
protected hostApp: HostAppService
protected hotkeys: HotkeysService
protected electron: ElectronService
protected platform: PlatformService
protected terminalContainersService: TerminalFrontendService
protected notifications: NotificationsService
@ -135,7 +134,6 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
this.app = injector.get(AppService)
this.hostApp = injector.get(HostAppService)
this.hotkeys = injector.get(HotkeysService)
this.electron = injector.get(ElectronService)
this.platform = injector.get(PlatformService)
this.terminalContainersService = injector.get(TerminalFrontendService)
this.notifications = injector.get(NotificationsService)
@ -359,7 +357,7 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
}
async paste (): Promise<void> {
let data = this.electron.clipboard.readText()
let data = this.platform.readClipboard()
if (this.config.store.terminal.bracketedPaste) {
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) {
const buttons = ['Paste', 'Cancel']
const result = (await this.electron.showMessageBox(
this.hostApp.getWindow(),
const result = (await this.platform.showMessageBox(
{
type: 'warning',
detail: data,
message: `Paste multiple lines?`,
buttons,
defaultId: 0,
cancelId: 1,
}
)).response
if (result === 1) {
@ -463,7 +459,7 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
cwd = await this.session.getWorkingDirectory()
}
if (cwd) {
this.electron.clipboard.writeText(cwd)
this.platform.setClipboard({ text: cwd })
this.notifications.notice('Copied')
} else {
this.notifications.error('Shell does not support current path detection')

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

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

@ -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}}

@ -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)
}
}

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

@ -1,11 +1,13 @@
import '@vaadin/vaadin-context-menu/vaadin-context-menu.js'
import copyToClipboard from 'copy-text-to-clipboard'
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
import type { ContextMenuElement, ContextMenuItem } from '@vaadin/vaadin-context-menu/vaadin-context-menu.js'
import { MessageBoxModalComponent } from './components/messageBoxModal.component'
import './styles.scss'
@Injectable()
@ -13,14 +15,19 @@ export class WebPlatformService extends PlatformService {
private menu: ContextMenuElement
private contextMenuHandlers = new Map<ContextMenuItem, () => void>()
constructor () {
constructor (
private ngbModal: NgbModal,
) {
super()
this.menu = window.document.createElement('vaadin-context-menu')
this.menu.addEventListener('item-selected', e => {
this.contextMenuHandlers.get(e.detail.value)?.()
})
document.body.appendChild(this.menu)
console.log(require('./styles.scss'))
}
readClipboard (): string {
return ''
}
setClipboard (content: ClipboardContent): void {
@ -73,4 +80,19 @@ export class WebPlatformService extends PlatformService {
}
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 }
}
}
}

@ -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()
}
}