From 012986dc7eda45424f5ac41a17e004f24f8237b5 Mon Sep 17 00:00:00 2001 From: Eugene Pankov Date: Mon, 24 May 2021 17:48:12 +0200 Subject: [PATCH] started separating terminus-electron and terminus-web --- .github/dependabot.yml | 12 + HACKING.md | 2 + app/common.ts | 4 - app/index.pug | 2 +- app/lib/app.ts | 17 + app/lib/window.ts | 4 +- app/package.json | 1 + app/src/app.module.ts | 6 + app/src/entry-web.ts | 42 ++ app/src/entry.ts | 12 +- app/webpack.config.js | 6 +- app/webpack.main.config.js | 1 + package.json | 2 +- scripts/prepackage-plugins.js | 3 + scripts/vars.js | 2 + terminus-core/package.json | 6 +- terminus-core/src/api/index.ts | 11 +- terminus-core/src/api/main-process.ts | 4 - terminus-core/src/api/mainProcess.ts | 4 + terminus-core/src/api/menu.ts | 9 + terminus-core/src/api/platform.ts | 69 +++ .../src/api/tabContextMenuProvider.ts | 4 +- .../src/components/appRoot.component.ts | 40 +- .../src/components/tabHeader.component.ts | 24 +- terminus-core/src/config.ts | 1 + terminus-core/src/index.ts | 13 +- terminus-core/src/services/app.service.ts | 19 +- terminus-core/src/services/config.service.ts | 58 ++- terminus-core/src/services/docking.service.ts | 22 +- .../src/services/homeBase.service.ts | 19 +- terminus-core/src/services/hostApp.service.ts | 55 +- terminus-core/src/services/hotkeys.service.ts | 11 +- terminus-core/src/services/log.service.ts | 62 +-- terminus-core/src/services/themes.service.ts | 19 +- terminus-core/src/services/updater.service.ts | 139 +---- terminus-core/src/tabContextMenu.ts | 19 +- terminus-core/yarn.lock | 230 +-------- terminus-electron/.gitignore | 1 + terminus-electron/package.json | 27 + .../src/colorSchemes.ts | 3 +- terminus-electron/src/index.ts | 77 +++ .../src/services/docking.service.ts | 97 ++++ terminus-electron/src/services/log.service.ts | 54 ++ terminus-electron/src/services/platform.ts | 146 ++++++ .../src/services/shellIntegration.service.ts | 4 +- .../src/services/touchbar.service.ts | 4 +- .../src/services/updater.service.ts | 134 +++++ terminus-electron/tsconfig.json | 7 + terminus-electron/tsconfig.typings.json | 14 + terminus-electron/webpack.config.js | 5 + terminus-electron/yarn.lock | 477 ++++++++++++++++++ .../src/components/terminalTab.component.ts | 2 +- terminus-local/src/index.ts | 7 +- .../src/services/terminal.service.ts | 6 +- terminus-local/src/session.ts | 27 +- terminus-local/src/tabContextMenu.ts | 11 +- terminus-plugin-manager/package.json | 1 - .../pluginsSettingsTab.component.ts | 8 +- .../src/services/pluginManager.service.ts | 8 +- terminus-plugin-manager/yarn.lock | 175 ------- .../components/hotkeyInputModal.component.ts | 4 +- .../src/components/settingsTab.component.pug | 7 +- .../src/components/settingsTab.component.ts | 16 +- .../windowSettingsTab.component.pug | 18 +- .../components/windowSettingsTab.component.ts | 20 +- .../components/sshSettingsTab.component.pug | 2 +- .../components/sshSettingsTab.component.ts | 5 +- terminus-ssh/src/services/ssh.service.ts | 20 +- terminus-ssh/src/winSCPIntegration.ts | 23 +- .../src/api/baseTerminalTab.component.ts | 27 +- .../src/api/contextMenuProvider.ts | 4 +- .../appearanceSettingsTab.component.ts | 22 +- .../terminalSettingsTab.component.pug | 4 +- .../terminalSettingsTab.component.ts | 9 +- terminus-terminal/src/frontends/frontend.ts | 8 +- .../src/frontends/htermFrontend.ts | 12 +- .../src/frontends/xtermFrontend.ts | 33 +- terminus-terminal/src/index.ts | 2 - .../src/services/terminalFrontend.service.ts | 12 +- terminus-terminal/src/tabContextMenu.ts | 9 +- terminus-web/.gitignore | 1 + terminus-web/package.json | 27 + terminus-web/src/index.ts | 17 + terminus-web/src/platform.ts | 76 +++ terminus-web/src/services/log.service.ts | 9 + terminus-web/src/services/updater.service.ts | 10 + terminus-web/src/styles.scss | 11 + terminus-web/tsconfig.json | 7 + terminus-web/tsconfig.typings.json | 14 + terminus-web/webpack.config.js | 5 + terminus-web/yarn.lock | 178 +++++++ tsconfig.json | 3 +- webpack.config.js | 2 + webpack.plugin.config.js | 5 +- 94 files changed, 1899 insertions(+), 972 deletions(-) delete mode 100644 app/common.ts create mode 100644 app/src/entry-web.ts delete mode 100644 terminus-core/src/api/main-process.ts create mode 100644 terminus-core/src/api/menu.ts create mode 100644 terminus-core/src/api/platform.ts create mode 100644 terminus-electron/.gitignore create mode 100644 terminus-electron/package.json rename {terminus-terminal => terminus-electron}/src/colorSchemes.ts (95%) create mode 100644 terminus-electron/src/index.ts create mode 100644 terminus-electron/src/services/docking.service.ts create mode 100644 terminus-electron/src/services/log.service.ts create mode 100644 terminus-electron/src/services/platform.ts rename {terminus-core => terminus-electron}/src/services/shellIntegration.service.ts (95%) rename {terminus-core => terminus-electron}/src/services/touchbar.service.ts (94%) create mode 100644 terminus-electron/src/services/updater.service.ts create mode 100644 terminus-electron/tsconfig.json create mode 100644 terminus-electron/tsconfig.typings.json create mode 100644 terminus-electron/webpack.config.js create mode 100644 terminus-electron/yarn.lock create mode 100644 terminus-web/.gitignore create mode 100644 terminus-web/package.json create mode 100644 terminus-web/src/index.ts create mode 100644 terminus-web/src/platform.ts create mode 100644 terminus-web/src/services/log.service.ts create mode 100644 terminus-web/src/services/updater.service.ts create mode 100644 terminus-web/src/styles.scss create mode 100644 terminus-web/tsconfig.json create mode 100644 terminus-web/tsconfig.typings.json create mode 100644 terminus-web/webpack.config.js create mode 100644 terminus-web/yarn.lock diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 1c378f2b..dafb3fe8 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -42,6 +42,18 @@ updates: interval: daily time: "04:00" open-pull-requests-limit: 20 +- package-ecosystem: npm + directory: "/terminus-electron" + schedule: + interval: daily + time: "04:00" + open-pull-requests-limit: 20 +- package-ecosystem: npm + directory: "/terminus-web" + schedule: + interval: daily + time: "04:00" + open-pull-requests-limit: 20 - package-ecosystem: npm directory: "/terminus-plugin-manager" schedule: diff --git a/HACKING.md b/HACKING.md index 06dee86f..5edc1f69 100644 --- a/HACKING.md +++ b/HACKING.md @@ -42,10 +42,12 @@ terminus ├─ scripts # Maintenance scripts ├─ terminus-community-color-schemes # Plugin that provides color schemes ├─ terminus-core # Plugin that provides base UI and tab management +├─ terminus-electron # Plugin that provides Electron-specific functions └─ terminus-local # Plugin that provides local shells and profiles ├─ terminus-plugin-manager # Plugin that installs other plugins ├─ terminus-settings # Plugin that provides the settings tab └─ terminus-terminal # Plugin that provides terminal tabs +├─ terminus-web # Plugin that provides web-specific functions ``` # Plugin layout diff --git a/app/common.ts b/app/common.ts deleted file mode 100644 index 01c86f13..00000000 --- a/app/common.ts +++ /dev/null @@ -1,4 +0,0 @@ -export interface BootstrapData { - config: Record - executable: string -} diff --git a/app/index.pug b/app/index.pug index 4a689daf..1a88818d 100644 --- a/app/index.pug +++ b/app/index.pug @@ -1,5 +1,5 @@ doctype html -html +html.terminus head meta(charset='UTF-8') base(href='index.html') diff --git a/app/lib/app.ts b/app/lib/app.ts index e3a39ce7..98b455b2 100644 --- a/app/lib/app.ts +++ b/app/lib/app.ts @@ -7,6 +7,12 @@ import { Window, WindowOptions } from './window' import { pluginManager } from './pluginManager' import { PTYManager } from './pty' +/* eslint-disable block-scoped-var */ + +try { + var wnr = require('windows-native-registry') // eslint-disable-line @typescript-eslint/no-var-requires, no-var +} catch (_) { } + export class Application { private tray?: Tray private ptyManager = new PTYManager() @@ -14,6 +20,7 @@ export class Application { constructor () { remote.initialize() + this.useBuiltinGraphics() this.ptyManager.init(this) ipcMain.on('app:config-change', (_event, config) => { @@ -161,6 +168,16 @@ export class Application { this.windows[this.windows.length - 1].passCliArguments(argv, cwd, true) } + private useBuiltinGraphics (): void { + if (process.platform === 'win32') { + const keyPath = 'SOFTWARE\\Microsoft\\DirectX\\UserGpuPreferences' + const valueName = app.getPath('exe') + if (!wnr.getRegistryValue(wnr.HK.CU, keyPath, valueName)) { + wnr.setRegistryValue(wnr.HK.CU, keyPath, valueName, wnr.REG.SZ, 'GpuPreference=1;') + } + } + } + private setupMenu () { const template: MenuItemConstructorOptions[] = [ { diff --git a/app/lib/window.ts b/app/lib/window.ts index d1962bef..1955abc9 100644 --- a/app/lib/window.ts +++ b/app/lib/window.ts @@ -122,7 +122,7 @@ export class Window { } }) - this.window.loadURL(`file://${app.getAppPath()}/dist/index.html?${this.window.id}`, { extraHeaders: 'pragma: no-cache\n' }) + this.window.loadURL(`file://${app.getAppPath()}/dist/index.html`, { extraHeaders: 'pragma: no-cache\n' }) this.window.webContents.setVisualZoomLevelLimits(1, 1) this.window.webContents.setZoomFactor(1) @@ -297,6 +297,8 @@ export class Window { this.window.webContents.send('start', { config: this.configStore, executable: app.getPath('exe'), + windowID: this.window.id, + isFirstWindow: this.window.id === 1, }) }) diff --git a/app/package.json b/app/package.json index 362b561c..e711ec0d 100644 --- a/app/package.json +++ b/app/package.json @@ -57,6 +57,7 @@ "peerDependencies": { "terminus-community-color-schemes": "*", "terminus-core": "*", + "terminus-electron": "*", "terminus-local": "*", "terminus-plugin-manager": "*", "terminus-serial": "*", diff --git a/app/src/app.module.ts b/app/src/app.module.ts index 7a7c9095..3f3ade00 100644 --- a/app/src/app.module.ts +++ b/app/src/app.module.ts @@ -16,10 +16,15 @@ export function getRootModule (plugins: any[]) { extendedTimeOut: 1000, }), ] + const bootstrap = [ ...plugins.filter(x => x.bootstrap).map(x => x.bootstrap), ] + const providers = [ + ...plugins.filter(x => x.providers).map(x => x.providers), + ].flat() + if (bootstrap.length === 0) { throw new Error('Did not find any bootstrap components. Are there any plugins installed?') } @@ -27,6 +32,7 @@ export function getRootModule (plugins: any[]) { @NgModule({ imports, bootstrap, + providers, }) class RootModule { } // eslint-disable-line @typescript-eslint/no-extraneous-class return RootModule diff --git a/app/src/entry-web.ts b/app/src/entry-web.ts new file mode 100644 index 00000000..f5446d92 --- /dev/null +++ b/app/src/entry-web.ts @@ -0,0 +1,42 @@ +import 'zone.js' +import 'core-js/proposals/reflect-metadata' +import 'core-js/features/array/flat' +import 'rxjs' + +import './global.scss' +import './toastr.scss' + +import { enableProdMode, NgModuleRef, ApplicationRef } from '@angular/core' +import { enableDebugTools } from '@angular/platform-browser' +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic' + +import { getRootModule } from './app.module' +import { BootstrapData, BOOTSTRAP_DATA } from '../../terminus-core/src/api/mainProcess' + + +window['bootstrapTerminus'] = async function bootstrap (packageModules: any[], bootstrapData: BootstrapData, debugMode = false): Promise> { + const pluginModules = [] + for (const packageModule of packageModules) { + const pluginModule = packageModule.default.forRoot ? packageModule.default.forRoot() : packageModule.default + pluginModule.pluginName = packageModule.pluginName + pluginModule.bootstrap = packageModule.bootstrap + pluginModules.push(pluginModule) + } + + if (!debugMode) { + enableProdMode() + } + + const module = getRootModule(pluginModules) + window['rootModule'] = module + + const moduleRef = await platformBrowserDynamic([ + { provide: BOOTSTRAP_DATA, useValue: bootstrapData }, + ]).bootstrapModule(module) + if (debugMode) { + const applicationRef = moduleRef.injector.get(ApplicationRef) + const componentRef = applicationRef.components[0] + enableDebugTools(componentRef) + } + return moduleRef +} diff --git a/app/src/entry.ts b/app/src/entry.ts index a5bdf2e2..745af861 100644 --- a/app/src/entry.ts +++ b/app/src/entry.ts @@ -12,7 +12,7 @@ import { ipcRenderer } from 'electron' import { getRootModule } from './app.module' import { findPlugins, loadPlugins, PluginInfo } from './plugins' -import { BootstrapData } from '../common' +import { BootstrapData, BOOTSTRAP_DATA } from '../../terminus-core/src/api/mainProcess' // Always land on the start view location.hash = '' @@ -39,11 +39,9 @@ async function bootstrap (plugins: PluginInfo[], bootstrapData: BootstrapData, s }) const module = getRootModule(pluginModules) window['rootModule'] = module - const moduleRef = await platformBrowserDynamic().bootstrapModule(module, { - providers: [ - { provide: 'bootstrapData', useValue: bootstrapData }, - ], - }) + const moduleRef = await platformBrowserDynamic([ + { provide: BOOTSTRAP_DATA, useValue: bootstrapData }, + ]).bootstrapModule(module) if (process.env.TERMINUS_DEV) { const applicationRef = moduleRef.injector.get(ApplicationRef) const componentRef = applicationRef.components[0] @@ -54,12 +52,12 @@ async function bootstrap (plugins: PluginInfo[], bootstrapData: BootstrapData, s ipcRenderer.once('start', async (_$event, bootstrapData: BootstrapData) => { console.log('Window bootstrap data:', bootstrapData) - ;(window as any).bootstrapData = bootstrapData let plugins = await findPlugins() if (bootstrapData.config.pluginBlacklist) { plugins = plugins.filter(x => !bootstrapData.config.pluginBlacklist.includes(x.name)) } + plugins = plugins.filter(x => x.name !== 'web') console.log('Starting with plugins:', plugins) try { await bootstrap(plugins, bootstrapData) diff --git a/app/webpack.config.js b/app/webpack.config.js index 0c814904..dbaa1dee 100644 --- a/app/webpack.config.js +++ b/app/webpack.config.js @@ -9,6 +9,7 @@ module.exports = { sentry: path.resolve(__dirname, 'lib/sentry.ts'), preload: path.resolve(__dirname, 'src/entry.preload.ts'), bundle: path.resolve(__dirname, 'src/entry.ts'), + 'bundle-web': path.resolve(__dirname, 'src/entry-web.ts'), }, mode: process.env.TERMINUS_DEV ? 'development' : 'production', optimization:{ @@ -41,9 +42,9 @@ module.exports = { { test: /\.(png|svg)$/, use: { - loader: 'file-loader', + loader: 'url-loader', options: { - name: 'images/[name].[ext]', + limit: 999999, }, }, }, @@ -66,6 +67,7 @@ module.exports = { '@angular/forms': 'commonjs @angular/forms', '@angular/common': 'commonjs @angular/common', '@ng-bootstrap/ng-bootstrap': 'commonjs @ng-bootstrap/ng-bootstrap', + '@electron/remote': 'commonjs @electron/remote', child_process: 'commonjs child_process', electron: 'commonjs electron', fs: 'commonjs fs', diff --git a/app/webpack.main.config.js b/app/webpack.main.config.js index 1d8b83e9..e037996e 100644 --- a/app/webpack.main.config.js +++ b/app/webpack.main.config.js @@ -51,6 +51,7 @@ module.exports = { util: 'commonjs util', 'source-map-support': 'commonjs source-map-support', 'windows-swca': 'commonjs windows-swca', + 'windows-native-registry': 'commonjs windows-native-registry', 'windows-blurbehind': 'commonjs windows-blurbehind', 'yargs/yargs': 'commonjs yargs/yargs', }, diff --git a/package.json b/package.json index 7e85ce53..64f50eb6 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,7 @@ "**/graceful-fs": "^4.2.4" }, "scripts": { - "build": "npm run build:typings && webpack --color --config app/webpack.main.config.js && webpack --color --config app/webpack.config.js && webpack --color --config terminus-core/webpack.config.js && webpack --color --config terminus-settings/webpack.config.js && webpack --color --config terminus-terminal/webpack.config.js && webpack --color --config terminus-local/webpack.config.js && webpack --color --config terminus-plugin-manager/webpack.config.js && webpack --color --config terminus-community-color-schemes/webpack.config.js && webpack --color --config terminus-ssh/webpack.config.js && webpack --color --config terminus-serial/webpack.config.js", + "build": "npm run build:typings && webpack --color --config app/webpack.main.config.js && webpack --color --config app/webpack.config.js && webpack --color --config terminus-core/webpack.config.js && webpack --color --config terminus-settings/webpack.config.js && webpack --color --config terminus-terminal/webpack.config.js && webpack --color --config terminus-local/webpack.config.js && webpack --color --config terminus-plugin-manager/webpack.config.js && webpack --color --config terminus-community-color-schemes/webpack.config.js && webpack --color --config terminus-ssh/webpack.config.js && webpack --color --config terminus-serial/webpack.config.js && webpack --color --config terminus-electron/webpack.config.js && webpack --color --config terminus-web/webpack.config.js", "build:typings": "node scripts/build-typings.js", "watch": "cross-env TERMINUS_DEV=1 webpack --progress --color --watch", "start": "cross-env TERMINUS_DEV=1 electron app --debug --inspect", diff --git a/scripts/prepackage-plugins.js b/scripts/prepackage-plugins.js index 4a105749..d7108b89 100755 --- a/scripts/prepackage-plugins.js +++ b/scripts/prepackage-plugins.js @@ -11,6 +11,9 @@ sh.mkdir('-p', target) fs.writeFileSync(path.join(target, 'package.json'), '{}') sh.cd(target) vars.builtinPlugins.forEach(plugin => { + if (plugin === 'terminus-web') { + continue + } log.info('install', plugin) sh.cp('-r', path.join('..', plugin), '.') sh.rm('-rf', path.join(plugin, 'node_modules')) diff --git a/scripts/vars.js b/scripts/vars.js index 4dc42b9e..c02093ab 100755 --- a/scripts/vars.js +++ b/scripts/vars.js @@ -19,7 +19,9 @@ exports.builtinPlugins = [ 'terminus-settings', 'terminus-terminal', 'terminus-local', + 'terminus-web', 'terminus-community-color-schemes', + 'terminus-electron', 'terminus-plugin-manager', 'terminus-ssh', 'terminus-serial', diff --git a/terminus-core/package.json b/terminus-core/package.json index 4beedb41..bdea9941 100644 --- a/terminus-core/package.json +++ b/terminus-core/package.json @@ -17,10 +17,7 @@ "author": "Eugene Pankov", "license": "MIT", "devDependencies": { - "@electron/remote": "1.1.0", "@types/js-yaml": "^4.0.0", - "@types/winston": "^2.3.6", - "axios": "^0.21.1", "bootstrap": "^4.1.3", "clone-deep": "^4.0.1", "core-js": "^3.1.2", @@ -31,8 +28,7 @@ "ng2-dnd": "^5.0.2", "ngx-perfect-scrollbar": "^10.1.0", "readable-stream": "3.6.0", - "uuid": "^8.0.0", - "winston": "^3.3.3" + "uuid": "^8.0.0" }, "peerDependencies": { "@angular/animations": "^9.1.9", diff --git a/terminus-core/src/api/index.ts b/terminus-core/src/api/index.ts index eff38c10..ea2ae531 100644 --- a/terminus-core/src/api/index.ts +++ b/terminus-core/src/api/index.ts @@ -10,18 +10,19 @@ export { Theme } from './theme' export { TabContextMenuItemProvider } from './tabContextMenuProvider' export { SelectorOption } from './selector' export { CLIHandler, CLIEvent } from './cli' -export { BootstrapData } from './main-process' +export { PlatformService, ClipboardContent } from './platform' +export { MenuItemOptions } from './menu' +export { BootstrapData, BOOTSTRAP_DATA } from './mainProcess' export { AppService } from '../services/app.service' export { ConfigService } from '../services/config.service' -export { DockingService } from '../services/docking.service' +export { DockingService, Screen } from '../services/docking.service' export { ElectronService } from '../services/electron.service' -export { Logger, LogService } from '../services/log.service' +export { Logger, ConsoleLogger, LogService } from '../services/log.service' export { HomeBaseService } from '../services/homeBase.service' export { HotkeysService } from '../services/hotkeys.service' -export { HostAppService, Platform } from '../services/hostApp.service' +export { HostAppService, Platform, Bounds } from '../services/hostApp.service' export { NotificationsService } from '../services/notifications.service' -export { ShellIntegrationService } from '../services/shellIntegration.service' export { ThemesService } from '../services/themes.service' export { TabsService } from '../services/tabs.service' export { UpdaterService } from '../services/updater.service' diff --git a/terminus-core/src/api/main-process.ts b/terminus-core/src/api/main-process.ts deleted file mode 100644 index 01c86f13..00000000 --- a/terminus-core/src/api/main-process.ts +++ /dev/null @@ -1,4 +0,0 @@ -export interface BootstrapData { - config: Record - executable: string -} diff --git a/terminus-core/src/api/mainProcess.ts b/terminus-core/src/api/mainProcess.ts index 01c86f13..1fbdd017 100644 --- a/terminus-core/src/api/mainProcess.ts +++ b/terminus-core/src/api/mainProcess.ts @@ -1,4 +1,8 @@ +export const BOOTSTRAP_DATA = 'BOOTSTRAP_DATA' + export interface BootstrapData { config: Record executable: string + isFirstWindow: boolean + windowID: number } diff --git a/terminus-core/src/api/menu.ts b/terminus-core/src/api/menu.ts new file mode 100644 index 00000000..2641eb62 --- /dev/null +++ b/terminus-core/src/api/menu.ts @@ -0,0 +1,9 @@ +export interface MenuItemOptions { + type?: ('normal' | 'separator' | 'submenu' | 'checkbox' | 'radio') + label?: string + sublabel?: string + enabled?: boolean + checked?: boolean + submenu?: MenuItemOptions[] + click?: () => void +} diff --git a/terminus-core/src/api/platform.ts b/terminus-core/src/api/platform.ts new file mode 100644 index 00000000..b8ee0cb7 --- /dev/null +++ b/terminus-core/src/api/platform.ts @@ -0,0 +1,69 @@ +import { MenuItemOptions } from './menu' + +/* eslint-disable @typescript-eslint/no-unused-vars */ +export interface ClipboardContent { + text: string + html?: string +} + +export abstract class PlatformService { + supportsWindowControls = false + + abstract setClipboard (content: ClipboardContent): void + abstract loadConfig (): Promise + abstract saveConfig (content: string): Promise + + getConfigPath (): string|null { + return null + } + + showItemInFolder (path: string): void { + throw new Error('Not implemented') + } + + async isProcessRunning (name: string): Promise { + return false + } + + async installPlugin (name: string, version: string): Promise { + throw new Error('Not implemented') + } + + async uninstallPlugin (name: string): Promise { + throw new Error('Not implemented') + } + + getWinSCPPath (): string|null { + throw new Error('Not implemented') + } + + exec (app: string, argv: string[]): void { + throw new Error('Not implemented') + } + + isShellIntegrationSupported (): boolean { + return false + } + + async isShellIntegrationInstalled (): Promise { + return false + } + + async installShellIntegration (): Promise { + throw new Error('Not implemented') + } + + async uninstallShellIntegration (): Promise { + throw new Error('Not implemented') + } + + openPath (path: string): void { + throw new Error('Not implemented') + } + + abstract getOSRelease (): string + abstract getAppVersion (): string + abstract openExternal (url: string): void + abstract listFonts (): Promise + abstract popupContextMenu (menu: MenuItemOptions[], event?: MouseEvent): void +} diff --git a/terminus-core/src/api/tabContextMenuProvider.ts b/terminus-core/src/api/tabContextMenuProvider.ts index 2b008e1f..baad789b 100644 --- a/terminus-core/src/api/tabContextMenuProvider.ts +++ b/terminus-core/src/api/tabContextMenuProvider.ts @@ -1,6 +1,6 @@ -import type { MenuItemConstructorOptions } from 'electron' import { BaseTabComponent } from '../components/baseTab.component' import { TabHeaderComponent } from '../components/tabHeader.component' +import { MenuItemOptions } from './menu' /** * Extend to add items to the tab header's context menu @@ -8,5 +8,5 @@ import { TabHeaderComponent } from '../components/tabHeader.component' export abstract class TabContextMenuItemProvider { weight = 0 - abstract getItems (tab: BaseTabComponent, tabHeader?: TabHeaderComponent): Promise + abstract getItems (tab: BaseTabComponent, tabHeader?: TabHeaderComponent): Promise } diff --git a/terminus-core/src/components/appRoot.component.ts b/terminus-core/src/components/appRoot.component.ts index 4f9b74d8..902aa907 100644 --- a/terminus-core/src/components/appRoot.component.ts +++ b/terminus-core/src/components/appRoot.component.ts @@ -3,19 +3,16 @@ import { Component, Inject, Input, HostListener, HostBinding } from '@angular/co import { trigger, style, animate, transition, state } from '@angular/animations' import { NgbModal } from '@ng-bootstrap/ng-bootstrap' -import { ElectronService } from '../services/electron.service' import { HostAppService, Platform } from '../services/hostApp.service' import { HotkeysService } from '../services/hotkeys.service' import { Logger, LogService } from '../services/log.service' import { ConfigService } from '../services/config.service' -import { DockingService } from '../services/docking.service' import { ThemesService } from '../services/themes.service' import { UpdaterService } from '../services/updater.service' -import { TouchbarService } from '../services/touchbar.service' import { BaseTabComponent } from './baseTab.component' import { SafeModeModalComponent } from './safeModeModal.component' -import { AppService, ToolbarButton, ToolbarButtonProvider } from '../api' +import { AppService, PlatformService, ToolbarButton, ToolbarButtonProvider } from '../api' /** @hidden */ @Component({ @@ -67,21 +64,19 @@ export class AppRootComponent { private logger: Logger private constructor ( - private docking: DockingService, private hotkeys: HotkeysService, private updater: UpdaterService, - private touchbar: TouchbarService, public hostApp: HostAppService, public config: ConfigService, public app: AppService, @Inject(ToolbarButtonProvider) private toolbarButtonProviders: ToolbarButtonProvider[], - electron: ElectronService, + platform: PlatformService, log: LogService, ngbModal: NgbModal, _themes: ThemesService, ) { this.logger = log.create('main') - this.logger.info('v', electron.app.getVersion()) + this.logger.info('v', platform.getAppVersion()) this.leftToolbarButtons = this.getToolbarButtons(false) this.rightToolbarButtons = this.getToolbarButtons(true) @@ -123,11 +118,6 @@ export class AppRootComponent { } }) - this.docking.dock() - this.hostApp.shown.subscribe(() => { - this.docking.dock() - }) - this.hostApp.windowCloseRequest$.subscribe(async () => { this.app.closeWindow() }) @@ -144,27 +134,8 @@ export class AppRootComponent { } }, 3600 * 12 * 1000) - this.touchbar.update() - - this.hostApp.useBuiltinGraphics() - - config.changed$.subscribe(() => this.updateVibrancy()) - this.updateVibrancy() - - let lastProgress: number|null = null this.app.tabOpened$.subscribe(tab => { this.unsortedTabs.push(tab) - tab.progress$.subscribe(progress => { - if (lastProgress === progress) { - return - } - if (progress !== null) { - this.hostApp.getWindow().setProgressBar(progress / 100.0, { mode: 'normal' }) - } else { - this.hostApp.getWindow().setProgressBar(-1, { mode: 'none' }) - } - lastProgress = progress - }) this.noTabs = false }) @@ -224,9 +195,4 @@ export class AppRootComponent { .filter(button => (button.weight ?? 0) > 0 === aboveZero) .sort((a: ToolbarButton, b: ToolbarButton) => (a.weight ?? 0) - (b.weight ?? 0)) } - - private updateVibrancy () { - this.hostApp.setVibrancy(this.config.store.appearance.vibrancy, this.config.store.appearance.vibrancyType) - this.hostApp.getWindow().setOpacity(this.config.store.appearance.opacity) - } } diff --git a/terminus-core/src/components/tabHeader.component.ts b/terminus-core/src/components/tabHeader.component.ts index 625f090f..bf1fdb41 100644 --- a/terminus-core/src/components/tabHeader.component.ts +++ b/terminus-core/src/components/tabHeader.component.ts @@ -1,5 +1,4 @@ /* eslint-disable @typescript-eslint/explicit-module-boundary-types */ -import type { MenuItemConstructorOptions } from 'electron' import { Component, Input, Optional, Inject, HostBinding, HostListener, ViewChild, ElementRef, NgZone } from '@angular/core' import { SortableComponent } from 'ng2-dnd' import { NgbModal } from '@ng-bootstrap/ng-bootstrap' @@ -7,11 +6,12 @@ import { TabContextMenuItemProvider } from '../api/tabContextMenuProvider' import { BaseTabComponent } from './baseTab.component' import { RenameTabModalComponent } from './renameTabModal.component' import { HotkeysService } from '../services/hotkeys.service' -import { ElectronService } from '../services/electron.service' import { AppService } from '../services/app.service' import { HostAppService, Platform } from '../services/hostApp.service' import { ConfigService } from '../services/config.service' import { BaseComponent } from './base.component' +import { MenuItemOptions } from '../api/menu' +import { PlatformService } from '../api/platform' /** @hidden */ export interface SortableComponentProxy { @@ -34,10 +34,10 @@ export class TabHeaderComponent extends BaseComponent { private constructor ( public app: AppService, public config: ConfigService, - private electron: ElectronService, private hostApp: HostAppService, private ngbModal: NgbModal, private hotkeys: HotkeysService, + private platform: PlatformService, private zone: NgZone, @Inject(SortableComponent) private parentDraggable: SortableComponentProxy, @Optional() @Inject(TabContextMenuItemProvider) protected contextMenuProviders: TabContextMenuItemProvider[], @@ -76,8 +76,8 @@ export class TabHeaderComponent extends BaseComponent { }).catch(() => null) } - async buildContextMenu (): Promise { - let items: MenuItemConstructorOptions[] = [] + async buildContextMenu (): Promise { + let items: MenuItemOptions[] = [] for (const section of await Promise.all(this.contextMenuProviders.map(x => x.getItems(this.tab, this)))) { items.push({ type: 'separator' }) items = items.concat(section) @@ -105,16 +105,8 @@ export class TabHeaderComponent extends BaseComponent { } } - @HostListener('auxclick', ['$event']) async onAuxClick ($event: MouseEvent) { - if ($event.which === 3) { - $event.preventDefault() - - const contextMenu = this.electron.Menu.buildFromTemplate(await this.buildContextMenu()) - - contextMenu.popup({ - x: $event.pageX, - y: $event.pageY, - }) - } + @HostListener('contextmenu', ['$event']) async onContextMenu ($event: MouseEvent) { + $event.preventDefault() + this.platform.popupContextMenu(await this.buildContextMenu(), $event) } } diff --git a/terminus-core/src/config.ts b/terminus-core/src/config.ts index a81ff957..07b38859 100644 --- a/terminus-core/src/config.ts +++ b/terminus-core/src/config.ts @@ -7,6 +7,7 @@ export class CoreConfigProvider extends ConfigProvider { [Platform.macOS]: require('./configDefaults.macos.yaml'), [Platform.Windows]: require('./configDefaults.windows.yaml'), [Platform.Linux]: require('./configDefaults.linux.yaml'), + [Platform.Web]: require('./configDefaults.windows.yaml'), } defaults = require('./configDefaults.yaml') } diff --git a/terminus-core/src/index.ts b/terminus-core/src/index.ts index a0c99f0e..92e53807 100644 --- a/terminus-core/src/index.ts +++ b/terminus-core/src/index.ts @@ -1,4 +1,4 @@ -import { NgModule, ModuleWithProviders } from '@angular/core' +import { NgModule, ModuleWithProviders, APP_INITIALIZER } from '@angular/core' import { BrowserModule } from '@angular/platform-browser' import { BrowserAnimationsModule } from '@angular/platform-browser/animations' import { FormsModule } from '@angular/forms' @@ -24,7 +24,7 @@ import { WelcomeTabComponent } from './components/welcomeTab.component' import { AutofocusDirective } from './directives/autofocus.directive' import { FastHtmlBindDirective } from './directives/fastHtmlBind.directive' -import { Theme, CLIHandler, BootstrapData, TabContextMenuItemProvider, TabRecoveryProvider, HotkeyProvider, ConfigProvider } from './api' +import { Theme, CLIHandler, TabContextMenuItemProvider, TabRecoveryProvider, HotkeyProvider, ConfigProvider } from './api' import { AppService } from './services/app.service' import { ConfigService } from './services/config.service' @@ -38,6 +38,10 @@ 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 }, @@ -50,6 +54,7 @@ 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 */ @@ -110,10 +115,6 @@ export default class AppModule { // eslint-disable-line @typescript-eslint/no-ex } } -export function getBootstrapData (): BootstrapData { - return (window as any).bootstrapData -} - export { AppRootComponent as bootstrap } export * from './api' diff --git a/terminus-core/src/services/app.service.ts b/terminus-core/src/services/app.service.ts index c0beed64..eade55ee 100644 --- a/terminus-core/src/services/app.service.ts +++ b/terminus-core/src/services/app.service.ts @@ -1,7 +1,7 @@ import { Observable, Subject, AsyncSubject } from 'rxjs' import { takeUntil } from 'rxjs/operators' -import { Injectable } from '@angular/core' +import { Injectable, Inject } from '@angular/core' import { NgbModal } from '@ng-bootstrap/ng-bootstrap' import { BaseTabComponent } from '../components/baseTab.component' @@ -9,6 +9,7 @@ import { SplitTabComponent } from '../components/splitTab.component' import { SelectorModalComponent } from '../components/selectorModal.component' import { SelectorOption } from '../api/selector' import { RecoveryToken } from '../api/tabRecovery' +import { BootstrapData, BOOTSTRAP_DATA } from '../api/mainProcess' import { ConfigService } from './config.service' import { HostAppService } from './hostApp.service' @@ -75,6 +76,7 @@ export class AppService { private tabRecovery: TabRecoveryService, private tabsService: TabsService, private ngbModal: NgbModal, + @Inject(BOOTSTRAP_DATA) private bootstrapData: BootstrapData, ) { this.tabsChanged$.subscribe(() => { this.tabRecovery.saveTabs(this.tabs) @@ -83,19 +85,18 @@ export class AppService { this.tabRecovery.saveTabs(this.tabs) }, 30000) - if (hostApp.getWindow().id === 1) { - if (config.store.terminal.recoverTabs) { - this.tabRecovery.recoverTabs().then(tabs => { + config.ready$.toPromise().then(async () => { + if (this.bootstrapData.isFirstWindow) { + if (config.store.terminal.recoverTabs) { + const tabs = await this.tabRecovery.recoverTabs() for (const tab of tabs) { this.openNewTabRaw(tab.type, tab.options) } - this.tabRecovery.enabled = true - }) - } else { + } /** Continue to store the tabs even if the setting is currently off */ this.tabRecovery.enabled = true } - } + }) hostApp.windowFocused$.subscribe(() => this._activeTab?.emitFocused()) @@ -118,7 +119,7 @@ export class AppService { this.tabsChanged.next() this.tabOpened.next(tab) - if (this.hostApp.getWindow().id === 1) { + if (this.bootstrapData.isFirstWindow) { tab.recoveryStateChangedHint$.subscribe(() => { this.tabRecovery.saveTabs(this.tabs) }) diff --git a/terminus-core/src/services/config.service.ts b/terminus-core/src/services/config.service.ts index 382442ee..d1e42e67 100644 --- a/terminus-core/src/services/config.service.ts +++ b/terminus-core/src/services/config.service.ts @@ -1,13 +1,12 @@ -import { Observable, Subject } from 'rxjs' +import { Observable, Subject, AsyncSubject } from 'rxjs' import * as yaml from 'js-yaml' -import * as path from 'path' -import * as fs from 'fs' import { Injectable, Inject } from '@angular/core' import { ConfigProvider } from '../api/configProvider' -import { ElectronService } from './electron.service' +import { PlatformService } from '../api/platform' import { HostAppService } from './hostApp.service' +const deepmerge = require('deepmerge') -const configMerge = (a, b) => require('deepmerge')(a, b, { arrayMerge: (_d, s) => s }) // eslint-disable-line @typescript-eslint/no-var-requires +const configMerge = (a, b) => deepmerge(a, b, { arrayMerge: (_d, s) => s }) // eslint-disable-line @typescript-eslint/no-var-requires function isStructuralMember (v) { return v instanceof Object && !(v instanceof Array) && @@ -89,11 +88,10 @@ export class ConfigService { */ restartRequested: boolean - /** - * Full config file path - */ - path: string + /** Fires once when the config is loaded */ + get ready$ (): Observable { return this.ready } + private ready = new AsyncSubject() private changed = new Subject() private _store: any private defaults: any @@ -103,26 +101,24 @@ export class ConfigService { /** @hidden */ private constructor ( - electron: ElectronService, private hostApp: HostAppService, + private platform: PlatformService, @Inject(ConfigProvider) private configProviders: ConfigProvider[], ) { - this.path = path.join(electron.app.getPath('userData'), 'config.yaml') this.defaults = this.mergeDefaults() - this.load() - - hostApp.configChangeBroadcast$.subscribe(() => { - this.load() - this.emitChange() - }) + this.init() } mergeDefaults (): unknown { const providers = this.configProviders return providers.map(provider => { - let defaults = provider.platformDefaults[this.hostApp.platform] || {} + let defaults = provider.platformDefaults[this.hostApp.configPlatform] ?? {} + defaults = configMerge( + defaults, + provider.platformDefaults[this.hostApp.platform] ?? {}, + ) if (provider.defaults) { - defaults = configMerge(defaults, provider.defaults) + defaults = configMerge(provider.defaults, defaults) } return defaults }).reduce(configMerge) @@ -147,19 +143,20 @@ export class ConfigService { return cleanup(this.defaults) } - load (): void { - if (fs.existsSync(this.path)) { - this._store = yaml.load(fs.readFileSync(this.path, 'utf8')) + async load (): Promise { + const content = await this.platform.loadConfig() + if (content) { + this._store = yaml.load(content) } else { this._store = {} } this.store = new ConfigProxy(this._store, this.defaults) } - save (): void { + async save (): Promise { // Scrub undefined values - this._store = JSON.parse(JSON.stringify(this._store)) - fs.writeFileSync(this.path, yaml.dump(this._store), 'utf8') + const cleanStore = JSON.parse(JSON.stringify(this._store)) + await this.platform.saveConfig(yaml.dump(cleanStore)) this.emitChange() this.hostApp.broadcastConfigChange(JSON.parse(JSON.stringify(this.store))) } @@ -214,6 +211,17 @@ export class ConfigService { }) } + private async init () { + await this.load() + this.ready.next() + this.ready.complete() + + this.hostApp.configChangeBroadcast$.subscribe(() => { + this.load() + this.emitChange() + }) + } + private emitChange (): void { this.changed.next() } diff --git a/terminus-core/src/services/docking.service.ts b/terminus-core/src/services/docking.service.ts index b42eadee..8e06730a 100644 --- a/terminus-core/src/services/docking.service.ts +++ b/terminus-core/src/services/docking.service.ts @@ -1,11 +1,19 @@ import type { Display } from 'electron' -import { Injectable } from '@angular/core' import { ConfigService } from '../services/config.service' import { ElectronService } from '../services/electron.service' import { HostAppService, Bounds } from '../services/hostApp.service' -@Injectable({ providedIn: 'root' }) -export class DockingService { +export abstract class Screen { + id: number + name?: string +} + +export abstract class DockingService { + abstract dock (): void + abstract getScreens (): Screen[] +} + +export class ElectronDockingService { /** @hidden */ private constructor ( private electron: ElectronService, @@ -68,10 +76,6 @@ export class DockingService { }) } - getCurrentScreen (): Display { - return this.electron.screen.getDisplayNearestPoint(this.electron.screen.getCursorScreenPoint()) - } - getScreens (): Display[] { const primaryDisplayID = this.electron.screen.getPrimaryDisplay().id return this.electron.screen.getAllDisplays().sort((a, b) => @@ -85,6 +89,10 @@ export class DockingService { }) } + private getCurrentScreen (): Display { + return this.electron.screen.getDisplayNearestPoint(this.electron.screen.getCursorScreenPoint()) + } + private repositionWindow () { const [x, y] = this.hostApp.getWindow().getPosition() for (const screen of this.electron.screen.getAllDisplays()) { diff --git a/terminus-core/src/services/homeBase.service.ts b/terminus-core/src/services/homeBase.service.ts index cca3f3a2..35ecf28e 100644 --- a/terminus-core/src/services/homeBase.service.ts +++ b/terminus-core/src/services/homeBase.service.ts @@ -1,9 +1,8 @@ -import * as os from 'os' import { Injectable } from '@angular/core' -import { ElectronService } from './electron.service' -import { ConfigService } from './config.service' import * as mixpanel from 'mixpanel' import { v4 as uuidv4 } from 'uuid' +import { ConfigService } from './config.service' +import { PlatformService } from '../api' @Injectable({ providedIn: 'root' }) export class HomeBaseService { @@ -12,10 +11,10 @@ export class HomeBaseService { /** @hidden */ private constructor ( - private electron: ElectronService, private config: ConfigService, + private platform: PlatformService, ) { - this.appVersion = electron.app.getVersion() + this.appVersion = platform.getAppVersion() if (this.config.store.enableAnalytics && !this.config.store.enableWelcomeTab) { this.enableAnalytics() @@ -23,12 +22,12 @@ export class HomeBaseService { } openGitHub (): void { - this.electron.shell.openExternal('https://github.com/eugeny/terminus') + this.platform.openExternal('https://github.com/eugeny/terminus') } reportBug (): void { let body = `Version: ${this.appVersion}\n` - body += `Platform: ${os.platform()} ${os.release()}\n` + body += `Platform: ${process.platform} ${this.platform.getOSRelease()}\n` const label = { aix: 'OS: IBM AIX', android: 'OS: Android', @@ -38,10 +37,10 @@ export class HomeBaseService { openbsd: 'OS: OpenBSD', sunos: 'OS: Solaris', win32: 'OS: Windows', - }[os.platform()] + }[process.platform] const plugins = (window as any).installedPlugins.filter(x => !x.isBuiltin).map(x => x.name) body += `Plugins: ${plugins.join(', ') || 'none'}\n\n` - this.electron.shell.openExternal(`https://github.com/eugeny/terminus/issues/new?body=${encodeURIComponent(body)}&labels=${label}`) + this.platform.openExternal(`https://github.com/eugeny/terminus/issues/new?body=${encodeURIComponent(body)}&labels=${label}`) } enableAnalytics (): void { @@ -60,7 +59,7 @@ export class HomeBaseService { return { distinct_id: window.localStorage.analyticsUserID, platform: process.platform, - os: os.release(), + os: this.platform.getOSRelease(), version: this.appVersion, } } diff --git a/terminus-core/src/services/hostApp.service.ts b/terminus-core/src/services/hostApp.service.ts index 8b9d19fb..1d8a05db 100644 --- a/terminus-core/src/services/hostApp.service.ts +++ b/terminus-core/src/services/hostApp.service.ts @@ -1,21 +1,17 @@ -import type { BrowserWindow, TouchBar, MenuItemConstructorOptions } from 'electron' +import type { BrowserWindow, TouchBar } from 'electron' import { Observable, Subject } from 'rxjs' -import { Injectable, NgZone, EventEmitter, Injector } from '@angular/core' +import { Injectable, NgZone, EventEmitter, Injector, Inject } from '@angular/core' import { ElectronService } from './electron.service' import { Logger, LogService } from './log.service' import { CLIHandler } from '../api/cli' +import { BootstrapData, BOOTSTRAP_DATA } from '../api/mainProcess' import { isWindowsBuild, WIN_BUILD_FLUENT_BG_SUPPORTED } from '../utils' -/* eslint-disable block-scoped-var */ - -try { - var wnr = require('windows-native-registry') // eslint-disable-line @typescript-eslint/no-var-requires, no-var -} catch (_) { } - export enum Platform { Linux = 'Linux', macOS = 'macOS', Windows = 'Windows', + Web = 'Web', } export interface Bounds { @@ -31,6 +27,7 @@ export interface Bounds { @Injectable({ providedIn: 'root' }) export class HostAppService { platform: Platform + configPlatform: Platform /** * Fired once the window is visible @@ -47,7 +44,6 @@ export class HostAppService { private displayMetricsChanged = new Subject() private displaysChanged = new Subject() private logger: Logger - private windowId: number /** * Fired when Preferences is selected in the macOS menu @@ -75,18 +71,20 @@ export class HostAppService { private constructor ( private zone: NgZone, private electron: ElectronService, + @Inject(BOOTSTRAP_DATA) private bootstrapData: BootstrapData, injector: Injector, log: LogService, ) { this.logger = log.create('hostApp') - this.platform = { + this.configPlatform = this.platform = { win32: Platform.Windows, darwin: Platform.macOS, linux: Platform.Linux, }[process.platform] - this.windowId = parseInt(location.search.substring(1)) - this.logger.info('Window ID:', this.windowId) + if (process.env.XWEB) { + this.platform = Platform.Web + } electron.ipcRenderer.on('host:preferences-menu', () => this.zone.run(() => this.preferencesMenu.next())) @@ -158,7 +156,7 @@ export class HostAppService { * Returns the current remote [[BrowserWindow]] */ getWindow (): BrowserWindow { - return this.electron.BrowserWindow.fromId(this.windowId)! + return this.electron.BrowserWindow.fromId(this.bootstrapData.windowID)! } newWindow (): void { @@ -202,19 +200,6 @@ export class HostAppService { this.electron.ipcRenderer.send('window-set-always-on-top', flag) } - /** - * Sets window vibrancy mode (Windows, macOS) - * - * @param type `null`, or `fluent` when supported (Windowd only) - */ - setVibrancy (enable: boolean, type: string|null): void { - if (this.platform === Platform.Windows && !isWindowsBuild(WIN_BUILD_FLUENT_BG_SUPPORTED)) { - type = null - } - document.body.classList.toggle('vibrant', enable) - this.electron.ipcRenderer.send('window-set-vibrancy', enable, type) - } - setTitle (title?: string): void { this.electron.ipcRenderer.send('window-set-title', title ?? 'Terminus') } @@ -223,10 +208,6 @@ export class HostAppService { this.getWindow().setTouchBar(touchBar) } - popupContextMenu (menuDefinition: MenuItemConstructorOptions[]): void { - this.electron.Menu.buildFromTemplate(menuDefinition).popup({}) - } - /** * Notifies other windows of config file changes */ @@ -250,20 +231,6 @@ export class HostAppService { this.electron.ipcRenderer.send('app:register-global-hotkey', specs) } - useBuiltinGraphics (): void { - const keyPath = 'SOFTWARE\\Microsoft\\DirectX\\UserGpuPreferences' - const valueName = this.electron.app.getPath('exe') - if (this.platform === Platform.Windows) { - if (!wnr.getRegistryValue(wnr.HK.CU, keyPath, valueName)) { - wnr.setRegistryValue(wnr.HK.CU, keyPath, valueName, wnr.REG.SZ, 'GpuPreference=1;') - } - } - } - - setTrafficLightInset (x: number, y: number): void { - this.getWindow().setTrafficLightPosition({ x, y }) - } - relaunch (): void { if (this.isPortable) { this.electron.app.relaunch({ execPath: process.env.PORTABLE_EXECUTABLE_FILE }) diff --git a/terminus-core/src/services/hotkeys.service.ts b/terminus-core/src/services/hotkeys.service.ts index 5d90a950..0b7b59b2 100644 --- a/terminus-core/src/services/hotkeys.service.ts +++ b/terminus-core/src/services/hotkeys.service.ts @@ -3,7 +3,6 @@ import { Observable, Subject } from 'rxjs' import { HotkeyDescription, HotkeyProvider } from '../api/hotkeyProvider' import { stringifyKeySequence, EventData } from './hotkeys.util' import { ConfigService } from './config.service' -import { ElectronService } from './electron.service' import { HostAppService } from './hostApp.service' export interface PartialHotkeyMatch { @@ -35,7 +34,6 @@ export class HotkeysService { private constructor ( private zone: NgZone, private hostApp: HostAppService, - private electron: ElectronService, private config: ConfigService, @Inject(HotkeyProvider) private hotkeyProviders: HotkeyProvider[], ) { @@ -52,9 +50,11 @@ export class HotkeysService { this.config.changed$.subscribe(() => { this.registerGlobalHotkey() }) - this.registerGlobalHotkey() - this.getHotkeyDescriptions().then(hotkeys => { - this.hotkeyDescriptions = hotkeys + this.config.ready$.toPromise().then(() => { + this.registerGlobalHotkey() + this.getHotkeyDescriptions().then(hotkeys => { + this.hotkeyDescriptions = hotkeys + }) }) // deprecated @@ -183,7 +183,6 @@ export class HotkeysService { } private registerGlobalHotkey () { - this.electron.globalShortcut.unregisterAll() let value = this.config.store.hotkeys['toggle-window'] || [] if (typeof value === 'string') { value = [value] diff --git a/terminus-core/src/services/log.service.ts b/terminus-core/src/services/log.service.ts index 25ad3cd1..f04f2f64 100644 --- a/terminus-core/src/services/log.service.ts +++ b/terminus-core/src/services/log.service.ts @@ -1,38 +1,5 @@ -import { Injectable } from '@angular/core' -import { ElectronService } from './electron.service' -import type * as winston from 'winston' -import * as fs from 'fs' -import * as path from 'path' - -const initializeWinston = (electron: ElectronService) => { - const logDirectory = electron.app.getPath('userData') - // eslint-disable-next-line - const winston = require('winston') - - if (!fs.existsSync(logDirectory)) { - fs.mkdirSync(logDirectory) - } - - return winston.createLogger({ - transports: [ - new winston.transports.File({ - level: 'debug', - filename: path.join(logDirectory, 'log.txt'), - format: winston.format.simple(), - handleExceptions: false, - maxsize: 5242880, - maxFiles: 5, - }), - ], - exitOnError: false, - }) -} - -export class Logger { - constructor ( - private winstonLogger: winston.Logger, - private name: string, - ) {} +export abstract class Logger { + constructor (protected name: string) { } debug (...args: any[]): void { this.doLog('debug', ...args) @@ -54,26 +21,15 @@ export class Logger { this.doLog('log', ...args) } - private doLog (level: string, ...args: any[]): void { + protected abstract doLog (level: string, ...args: any[]): void +} + +export class ConsoleLogger extends Logger { + protected doLog (level: string, ...args: any[]): void { console[level](`%c[${this.name}]`, 'color: #aaa', ...args) - this.winstonLogger[level](...args) } } -@Injectable({ providedIn: 'root' }) -export class LogService { - private log: winston.Logger - - /** @hidden */ - private constructor (electron: ElectronService) { - if (!process.env.XWEB) { - this.log = initializeWinston(electron) - } else { - this.log = console as any - } - } - - create (name: string): Logger { - return new Logger(this.log, name) - } +export abstract class LogService { + abstract create (name: string): Logger } diff --git a/terminus-core/src/services/themes.service.ts b/terminus-core/src/services/themes.service.ts index 343d4868..4b1c6c7b 100644 --- a/terminus-core/src/services/themes.service.ts +++ b/terminus-core/src/services/themes.service.ts @@ -1,21 +1,25 @@ import { Inject, Injectable } from '@angular/core' +import { Subject, Observable } from 'rxjs' import { ConfigService } from '../services/config.service' import { Theme } from '../api/theme' -import { HostAppService, Platform } from './hostApp.service' @Injectable({ providedIn: 'root' }) export class ThemesService { + get themeChanged$ (): Observable { return this.themeChanged } + private themeChanged = new Subject() + private styleElement: HTMLElement|null = null /** @hidden */ private constructor ( private config: ConfigService, - private hostApp: HostAppService, @Inject(Theme) private themes: Theme[], ) { - this.applyCurrentTheme() - config.changed$.subscribe(() => { + config.ready$.toPromise().then(() => { this.applyCurrentTheme() + config.changed$.subscribe(() => { + this.applyCurrentTheme() + }) }) } @@ -35,12 +39,7 @@ export class ThemesService { } this.styleElement.textContent = theme.css document.querySelector('style#custom-css')!.innerHTML = this.config.store.appearance.css - if (this.hostApp.platform === Platform.macOS) { - this.hostApp.setTrafficLightInset( - theme.macOSWindowButtonsInsetX ?? 14, - theme.macOSWindowButtonsInsetY ?? 22, - ) - } + this.themeChanged.next(theme) } private applyCurrentTheme (): void { diff --git a/terminus-core/src/services/updater.service.ts b/terminus-core/src/services/updater.service.ts index d9bfa606..d2cf428f 100644 --- a/terminus-core/src/services/updater.service.ts +++ b/terminus-core/src/services/updater.service.ts @@ -1,137 +1,4 @@ -import axios from 'axios' - -import { Injectable } from '@angular/core' -import { Logger, LogService } from './log.service' -import { ElectronService } from './electron.service' -import { ConfigService } from './config.service' -import { HostAppService } from './hostApp.service' - -const UPDATES_URL = 'https://api.github.com/repos/eugeny/terminus/releases/latest' - -/** @hidden */ -@Injectable({ providedIn: 'root' }) -export class UpdaterService { - private logger: Logger - private downloaded: Promise - private electronUpdaterAvailable = true - private updateURL: string - - private constructor ( - log: LogService, - config: ConfigService, - private electron: ElectronService, - private hostApp: HostAppService, - ) { - this.logger = log.create('updater') - - if (process.platform === 'linux') { - this.electronUpdaterAvailable = false - return - } - - electron.autoUpdater.on('update-available', () => { - this.logger.info('Update available') - }) - - electron.autoUpdater.once('update-not-available', () => { - this.logger.info('No updates') - }) - - electron.autoUpdater.once('error', err => { - this.logger.error(err) - }) - - this.downloaded = new Promise(resolve => { - 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) - } - } - } - - async check (): Promise { - if (this.electronUpdaterAvailable) { - return new Promise((resolve, reject) => { - // eslint-disable-next-line @typescript-eslint/init-declarations, prefer-const - let cancel - const onNoUpdate = () => { - cancel() - resolve(false) - } - const onUpdate = () => { - cancel() - resolve(this.downloaded) - } - const onError = (err) => { - cancel() - reject(err) - } - cancel = () => { - this.electron.autoUpdater.off('error', onError) - this.electron.autoUpdater.off('update-not-available', onNoUpdate) - this.electron.autoUpdater.off('update-available', onUpdate) - } - this.electron.autoUpdater.on('error', onError) - this.electron.autoUpdater.on('update-not-available', onNoUpdate) - this.electron.autoUpdater.on('update-available', onUpdate) - try { - this.electron.autoUpdater.checkForUpdates() - } catch (e) { - this.electronUpdaterAvailable = false - this.logger.info('Electron updater unavailable, falling back', e) - } - }) - - this.electron.autoUpdater.on('update-available', () => { - this.logger.info('Update available') - }) - - this.electron.autoUpdater.once('update-not-available', () => { - this.logger.info('No updates') - }) - - } else { - this.logger.debug('Checking for updates through fallback method.') - const response = await axios.get(UPDATES_URL) - const data = response.data - const version = data.tag_name.substring(1) - if (this.electron.app.getVersion() !== version) { - this.logger.info('Update available') - this.updateURL = data.html_url - return true - } - this.logger.info('No updates') - return false - } - return this.downloaded - } - - async update (): Promise { - if (!this.electronUpdaterAvailable) { - this.electron.shell.openExternal(this.updateURL) - } else { - if ((await this.electron.showMessageBox( - this.hostApp.getWindow(), - { - type: 'warning', - message: 'Installing the update will close all tabs and restart Terminus.', - buttons: ['Cancel', 'Update'], - defaultId: 1, - } - )).response === 1) { - await this.downloaded - this.electron.autoUpdater.quitAndInstall() - } - } - } +export abstract class UpdaterService { + abstract check (): Promise + abstract update (): Promise } diff --git a/terminus-core/src/tabContextMenu.ts b/terminus-core/src/tabContextMenu.ts index ba9f9f38..5e68079b 100644 --- a/terminus-core/src/tabContextMenu.ts +++ b/terminus-core/src/tabContextMenu.ts @@ -1,5 +1,4 @@ /* eslint-disable @typescript-eslint/explicit-module-boundary-types */ -import type { MenuItemConstructorOptions } from 'electron' import { Injectable, NgZone } from '@angular/core' import { Subscription } from 'rxjs' import { AppService } from './services/app.service' @@ -7,6 +6,7 @@ import { BaseTabComponent } from './components/baseTab.component' import { TabHeaderComponent } from './components/tabHeader.component' import { SplitTabComponent, SplitDirection } from './components/splitTab.component' import { TabContextMenuItemProvider } from './api/tabContextMenuProvider' +import { MenuItemOptions } from './api/menu' /** @hidden */ @Injectable() @@ -20,8 +20,8 @@ export class TabManagementContextMenu extends TabContextMenuItemProvider { super() } - async getItems (tab: BaseTabComponent, tabHeader?: TabHeaderComponent): Promise { - let items: MenuItemConstructorOptions[] = [ + async getItems (tab: BaseTabComponent, tabHeader?: TabHeaderComponent): Promise { + let items: MenuItemOptions[] = [ { label: 'Close', click: () => this.zone.run(() => { @@ -76,7 +76,7 @@ export class TabManagementContextMenu extends TabContextMenuItemProvider { click: () => this.zone.run(() => { (tab.parent as SplitTabComponent).splitTab(tab, dir) }), - })) as MenuItemConstructorOptions[], + })) as MenuItemOptions[], }) } } @@ -106,8 +106,8 @@ export class CommonOptionsContextMenu extends TabContextMenuItemProvider { super() } - async getItems (tab: BaseTabComponent, tabHeader?: TabHeaderComponent): Promise { - let items: MenuItemConstructorOptions[] = [] + async getItems (tab: BaseTabComponent, tabHeader?: TabHeaderComponent): Promise { + let items: MenuItemOptions[] = [] if (tabHeader) { items = [ ...items, @@ -129,7 +129,7 @@ export class CommonOptionsContextMenu extends TabContextMenuItemProvider { click: () => this.zone.run(() => { tab.color = color.value }), - })) as MenuItemConstructorOptions[], + })) as MenuItemOptions[], }, ] } @@ -147,15 +147,14 @@ export class TaskCompletionContextMenu extends TabContextMenuItemProvider { super() } - async getItems (tab: BaseTabComponent): Promise { + async getItems (tab: BaseTabComponent): Promise { const process = await tab.getCurrentProcess() - const items: MenuItemConstructorOptions[] = [] + const items: MenuItemOptions[] = [] const extTab: (BaseTabComponent & { __completionNotificationEnabled?: boolean, __outputNotificationSubscription?: Subscription|null }) = tab if (process) { items.push({ - id: 'process-name', enabled: false, label: 'Current process: ' + process.name, }) diff --git a/terminus-core/yarn.lock b/terminus-core/yarn.lock index 78472649..cf3cedbe 100644 --- a/terminus-core/yarn.lock +++ b/terminus-core/yarn.lock @@ -2,20 +2,6 @@ # yarn lockfile v1 -"@dabh/diagnostics@^2.0.2": - version "2.0.2" - resolved "https://registry.yarnpkg.com/@dabh/diagnostics/-/diagnostics-2.0.2.tgz#290d08f7b381b8f94607dc8f471a12c675f9db31" - integrity sha512-+A1YivoVDNNVCdfozHSR8v/jyuuLTMXwjWuxPFlFlUapXoGc+Gj9mDlTDDfrwl7rXCl2tNZ0kE8sIBO6YOn96Q== - dependencies: - colorspace "1.1.x" - enabled "2.0.x" - kuler "^2.0.0" - -"@electron/remote@1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@electron/remote/-/remote-1.1.0.tgz#167d119c7c03c7778b556fdc4f1f38a44b23f1c2" - integrity sha512-yr8gZTkIgJYKbFqExI4QZqMSjn1kL/us9Dl46+TH1EZdhgRtsJ6HDfdsIxu0QEc6Hv+DMAXs69rgquH+8FDk4w== - "@types/js-yaml@^4.0.0": version "4.0.1" resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-4.0.1.tgz#5544730b65a480b18ace6b6ce914e519cec2d43b" @@ -26,13 +12,6 @@ resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.3.5.tgz#74deebbbcb1e86634dbf10a5b5e8798626f5a597" integrity sha512-iotVxtCCsPLRAvxMFFgxL8HD2l4mAZ2Oin7/VJ2ooWO0VOK4EGOGmZWZn1uCq7RofR3I/1IOSjCHlFT71eVK0Q== -"@types/winston@^2.3.6": - version "2.4.4" - resolved "https://registry.yarnpkg.com/@types/winston/-/winston-2.4.4.tgz#48cc744b7b42fad74b9a2e8490e0112bd9a3d08d" - integrity sha512-BVGCztsypW8EYwJ+Hq+QNYiT/MUyCif0ouBH+flrY66O5W+KIXAMML6E/0fJpm7VjIzgangahl5S03bJJQGrZw== - dependencies: - winston "*" - agent-base@6: version "6.0.2" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" @@ -45,18 +24,6 @@ argparse@^2.0.1: resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== -async@^3.1.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/async/-/async-3.2.0.tgz#b3a2685c5ebb641d3de02d161002c60fc9f85720" - integrity sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw== - -axios@^0.21.1: - version "0.21.1" - resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.1.tgz#22563481962f4d6bde9a76d516ef0e5d3c09b2b8" - integrity sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA== - dependencies: - follow-redirects "^1.10.0" - bootstrap@^4.1.3: version "4.5.3" resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-4.5.3.tgz#c6a72b355aaf323920be800246a6e4ef30997fe6" @@ -79,62 +46,11 @@ clone-deep@^4.0.1: kind-of "^6.0.2" shallow-clone "^3.0.0" -color-convert@^1.9.1: - version "1.9.3" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" - integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== - dependencies: - color-name "1.1.3" - -color-name@1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" - integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= - -color-name@^1.0.0: - version "1.1.4" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" - integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== - -color-string@^1.5.2: - version "1.5.4" - resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.5.4.tgz#dd51cd25cfee953d138fe4002372cc3d0e504cb6" - integrity sha512-57yF5yt8Xa3czSEW1jfQDE79Idk0+AkN/4KWad6tbdxUmAs3MvjxlWSWD4deYytcRfoZ9nhKyFl1kj5tBvidbw== - dependencies: - color-name "^1.0.0" - simple-swizzle "^0.2.2" - -color@3.0.x: - version "3.0.0" - resolved "https://registry.yarnpkg.com/color/-/color-3.0.0.tgz#d920b4328d534a3ac8295d68f7bd4ba6c427be9a" - integrity sha512-jCpd5+s0s0t7p3pHQKpnJ0TpQKKdleP71LWcA0aqiljpiuAkOSUFN/dyH8ZwF0hRmFlrIuRhufds1QyEP9EB+w== - dependencies: - color-convert "^1.9.1" - color-string "^1.5.2" - -colors@^1.2.1: - version "1.4.0" - resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78" - integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA== - -colorspace@1.1.x: - version "1.1.2" - resolved "https://registry.yarnpkg.com/colorspace/-/colorspace-1.1.2.tgz#e0128950d082b86a2168580796a0aa5d6c68d8c5" - integrity sha512-vt+OoIP2d76xLhjwbBaucYlNSpPsrJWPlBTtwCpQKIu6/CSMutyzX93O/Do0qzpH3YoHEes8YEFXyZ797rEhzQ== - dependencies: - color "3.0.x" - text-hex "1.0.x" - core-js@^3.1.2: version "3.12.1" resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.12.1.tgz#6b5af4ff55616c08a44d386f1f510917ff204112" integrity sha512-Ne9DKPHTObRuB09Dru5AjwKjY4cJHVGu+y5f7coGn1E9Grkc3p2iBwE9AI/nJzsE29mQF7oq+mhYYRqOMFN1Bw== -core-util-is@~1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" - integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= - debug@4: version "4.3.1" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee" @@ -168,31 +84,6 @@ electron-updater@^4.0.6: lodash.isequal "^4.5.0" semver "^7.3.5" -enabled@2.0.x: - version "2.0.0" - resolved "https://registry.yarnpkg.com/enabled/-/enabled-2.0.0.tgz#f9dd92ec2d6f4bbc0d5d1e64e21d61cd4665e7c2" - integrity sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ== - -fast-safe-stringify@^2.0.4: - version "2.0.7" - resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz#124aa885899261f68aedb42a7c080de9da608743" - integrity sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA== - -fecha@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/fecha/-/fecha-4.2.0.tgz#3ffb6395453e3f3efff850404f0a59b6747f5f41" - integrity sha512-aN3pcx/DSmtyoovUudctc8+6Hl4T+hI9GBBHLjA76jdZl7+b1sgh5g4k+u/GL3dTy1/pnYzKp69FpJ0OicE3Wg== - -fn.name@1.x.x: - version "1.1.0" - resolved "https://registry.yarnpkg.com/fn.name/-/fn.name-1.1.0.tgz#26cad8017967aea8731bc42961d04a3d5988accc" - integrity sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw== - -follow-redirects@^1.10.0: - version "1.13.1" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.1.tgz#5f69b813376cee4fd0474a3aba835df04ab763b7" - integrity sha512-SSG5xmZh1mkPGyKzjZP8zLjltIfpW32Y5QpdNJyjcfGxK3qo3NDDkZOZSFiGn1A6SclQxY9GzEwAHQ3dmYRWpg== - fs-extra@^10.0.0: version "10.0.0" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.0.0.tgz#9ff61b655dde53fb34a82df84bb214ce802e17c1" @@ -215,16 +106,11 @@ https-proxy-agent@5.0.0: agent-base "6" debug "4" -inherits@^2.0.3, inherits@~2.0.3: +inherits@^2.0.3: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== -is-arrayish@^0.3.1: - version "0.3.2" - resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03" - integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== - is-plain-object@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" @@ -232,16 +118,6 @@ is-plain-object@^2.0.4: dependencies: isobject "^3.0.1" -is-stream@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.0.tgz#bde9c32680d6fae04129d6ac9d921ce7815f78e3" - integrity sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw== - -isarray@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" - integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= - isobject@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" @@ -268,11 +144,6 @@ kind-of@^6.0.2: resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== -kuler@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/kuler/-/kuler-2.0.0.tgz#e2c570a3800388fb44407e851531c1d670b061b3" - integrity sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A== - lazy-val@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/lazy-val/-/lazy-val-1.0.4.tgz#882636a7245c2cfe6e0a4e3ba6c5d68a137e5c65" @@ -288,17 +159,6 @@ lodash.isequal@^4.5.0: resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" integrity sha1-QVxEePK8wwEgwizhDtMib30+GOA= -logform@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/logform/-/logform-2.2.0.tgz#40f036d19161fc76b68ab50fdc7fe495544492f2" - integrity sha512-N0qPlqfypFx7UHNn4B3lzS/b0uLqt2hmuoa+PpuXNYgozdJYAyauF5Ky0BWVjrxDlMWiT3qN4zPq3vVAfZy7Yg== - dependencies: - colors "^1.2.1" - fast-safe-stringify "^2.0.4" - fecha "^4.2.0" - ms "^2.1.1" - triple-beam "^1.3.0" - lru-cache@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" @@ -313,7 +173,7 @@ mixpanel@^0.13.0: dependencies: https-proxy-agent "5.0.0" -ms@2.1.2, ms@^2.1.1: +ms@2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== @@ -332,24 +192,12 @@ ngx-perfect-scrollbar@^10.1.0: resize-observer-polyfill "^1.5.0" tslib "^2.0.0" -one-time@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/one-time/-/one-time-1.0.0.tgz#e06bc174aed214ed58edede573b433bbf827cb45" - integrity sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g== - dependencies: - fn.name "1.x.x" - perfect-scrollbar@1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/perfect-scrollbar/-/perfect-scrollbar-1.5.0.tgz#821d224ed8ff61990c23f26db63048cdc75b6b83" integrity sha512-NrNHJn5mUGupSiheBTy6x+6SXCFbLlm8fVZh9moIzw/LgqElN5q4ncR4pbCBCYuCJ8Kcl9mYM0NgDxvW+b4LxA== -process-nextick-args@~2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" - integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== - -readable-stream@3.6.0, readable-stream@^3.4.0: +readable-stream@3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== @@ -358,29 +206,11 @@ readable-stream@3.6.0, readable-stream@^3.4.0: string_decoder "^1.1.1" util-deprecate "^1.0.1" -readable-stream@^2.3.7: - version "2.3.7" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" - integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.3" - isarray "~1.0.0" - process-nextick-args "~2.0.0" - safe-buffer "~5.1.1" - string_decoder "~1.1.1" - util-deprecate "~1.0.1" - resize-observer-polyfill@^1.5.0: version "1.5.1" resolved "https://registry.yarnpkg.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz#0e9020dd3d21024458d4ebd27e23e40269810464" integrity sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg== -safe-buffer@~5.1.0, safe-buffer@~5.1.1: - version "5.1.2" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" - integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== - safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" @@ -405,18 +235,6 @@ shallow-clone@^3.0.0: dependencies: kind-of "^6.0.2" -simple-swizzle@^0.2.2: - version "0.2.2" - resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a" - integrity sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo= - dependencies: - is-arrayish "^0.3.1" - -stack-trace@0.0.x: - version "0.0.10" - resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0" - integrity sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA= - string_decoder@^1.1.1: version "1.3.0" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" @@ -424,23 +242,6 @@ string_decoder@^1.1.1: dependencies: safe-buffer "~5.2.0" -string_decoder@~1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" - integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== - dependencies: - safe-buffer "~5.1.0" - -text-hex@1.0.x: - version "1.0.0" - resolved "https://registry.yarnpkg.com/text-hex/-/text-hex-1.0.0.tgz#69dc9c1b17446ee79a92bf5b884bb4b9127506f5" - integrity sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg== - -triple-beam@^1.2.0, triple-beam@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/triple-beam/-/triple-beam-1.3.0.tgz#a595214c7298db8339eeeee083e4d10bd8cb8dd9" - integrity sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw== - tslib@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.1.0.tgz#da60860f1c2ecaa5703ab7d39bc05b6bf988b97a" @@ -451,7 +252,7 @@ universalify@^2.0.0: resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717" integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ== -util-deprecate@^1.0.1, util-deprecate@~1.0.1: +util-deprecate@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= @@ -461,29 +262,6 @@ uuid@^8.0.0: resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== -winston-transport@^4.4.0: - version "4.4.0" - resolved "https://registry.yarnpkg.com/winston-transport/-/winston-transport-4.4.0.tgz#17af518daa690d5b2ecccaa7acf7b20ca7925e59" - integrity sha512-Lc7/p3GtqtqPBYYtS6KCN3c77/2QCev51DvcJKbkFPQNoj1sinkGwLGFDxkXY9J6p9+EPnYs+D90uwbnaiURTw== - dependencies: - readable-stream "^2.3.7" - triple-beam "^1.2.0" - -winston@*, winston@^3.3.3: - version "3.3.3" - resolved "https://registry.yarnpkg.com/winston/-/winston-3.3.3.tgz#ae6172042cafb29786afa3d09c8ff833ab7c9170" - integrity sha512-oEXTISQnC8VlSAKf1KYSSd7J6IWuRPQqDdo8eoRNaYKLvwSb5+79Z3Yi1lrl6KDpU6/VWaxpakDAtb1oQ4n9aw== - dependencies: - "@dabh/diagnostics" "^2.0.2" - async "^3.1.0" - is-stream "^2.0.0" - logform "^2.2.0" - one-time "^1.0.0" - readable-stream "^3.4.0" - stack-trace "0.0.x" - triple-beam "^1.3.0" - winston-transport "^4.4.0" - yallist@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" diff --git a/terminus-electron/.gitignore b/terminus-electron/.gitignore new file mode 100644 index 00000000..1521c8b7 --- /dev/null +++ b/terminus-electron/.gitignore @@ -0,0 +1 @@ +dist diff --git a/terminus-electron/package.json b/terminus-electron/package.json new file mode 100644 index 00000000..0e7b9253 --- /dev/null +++ b/terminus-electron/package.json @@ -0,0 +1,27 @@ +{ + "name": "terminus-electron", + "version": "1.0.135-nightly.0", + "description": "Electron-specific bindings", + "keywords": [ + "terminus-builtin-plugin" + ], + "main": "dist/index.js", + "typings": "typings/index.d.ts", + "scripts": { + "build": "webpack --progress --color", + "watch": "webpack --progress --color --watch" + }, + "files": [ + "dist" + ], + "author": "Eugene Pankov", + "license": "MIT", + "peerDependencies": { + "@angular/core": "^9.1.9" + }, + "devDependencies": { + "axios": "^0.21.1", + "winston": "^3.3.3", + "electron-promise-ipc": "^2.2.4" + } +} diff --git a/terminus-terminal/src/colorSchemes.ts b/terminus-electron/src/colorSchemes.ts similarity index 95% rename from terminus-terminal/src/colorSchemes.ts rename to terminus-electron/src/colorSchemes.ts index fa64658d..23f1bcb2 100644 --- a/terminus-terminal/src/colorSchemes.ts +++ b/terminus-electron/src/colorSchemes.ts @@ -1,8 +1,7 @@ import * as fs from 'mz/fs' import * as path from 'path' import { Injectable } from '@angular/core' -import { TerminalColorSchemeProvider } from './api/colorSchemeProvider' -import { TerminalColorScheme } from './api/interfaces' +import { TerminalColorSchemeProvider, TerminalColorScheme } from 'terminus-terminal' /** @hidden */ @Injectable() diff --git a/terminus-electron/src/index.ts b/terminus-electron/src/index.ts new file mode 100644 index 00000000..8ab73b32 --- /dev/null +++ b/terminus-electron/src/index.ts @@ -0,0 +1,77 @@ +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 { TerminalColorSchemeProvider } from 'terminus-terminal' + +import { HyperColorSchemes } from './colorSchemes' +import { ElectronPlatformService } from './services/platform' +import { ElectronLogService } from './services/log.service' +import { ElectronUpdaterService } from './services/updater.service' +import { TouchbarService } from './services/touchbar.service' +import { ElectronDockingService } from './services/docking.service' + +@NgModule({ + providers: [ + { provide: TerminalColorSchemeProvider, useClass: HyperColorSchemes, multi: true }, + { provide: PlatformService, useClass: ElectronPlatformService }, + { provide: LogService, useClass: ElectronLogService }, + { provide: UpdaterService, useClass: ElectronUpdaterService }, + { provide: DockingService, useClass: ElectronDockingService }, + ], +}) +export default class ElectronModule { + constructor ( + private config: ConfigService, + private hostApp: HostAppService, + private electron: ElectronService, + touchbar: TouchbarService, + docking: DockingService, + themeService: ThemesService, + app: AppService + ) { + config.ready$.toPromise().then(() => { + touchbar.update() + docking.dock() + hostApp.shown.subscribe(() => { + docking.dock() + }) + this.updateVibrancy() + }) + + themeService.themeChanged$.subscribe(theme => { + if (hostApp.platform === Platform.macOS) { + hostApp.getWindow().setTrafficLightPosition({ + x: theme.macOSWindowButtonsInsetX ?? 14, + y: theme.macOSWindowButtonsInsetY ?? 22, + }) + } + }) + + let lastProgress: number|null = null + app.tabOpened$.subscribe(tab => { + tab.progress$.subscribe(progress => { + if (lastProgress === progress) { + return + } + if (progress !== null) { + hostApp.getWindow().setProgressBar(progress / 100.0, { mode: 'normal' }) + } else { + hostApp.getWindow().setProgressBar(-1, { mode: 'none' }) + } + lastProgress = progress + }) + }) + + config.changed$.subscribe(() => this.updateVibrancy()) + } + + private updateVibrancy () { + let vibrancyType = this.config.store.appearance.vibrancyType + if (this.hostApp.platform === Platform.Windows && !isWindowsBuild(WIN_BUILD_FLUENT_BG_SUPPORTED)) { + vibrancyType = null + } + document.body.classList.toggle('vibrant', this.config.store.appearance.vibrancy) + this.electron.ipcRenderer.send('window-set-vibrancy', this.config.store.appearance.vibrancy, vibrancyType) + + this.hostApp.getWindow().setOpacity(this.config.store.appearance.opacity) + } +} diff --git a/terminus-electron/src/services/docking.service.ts b/terminus-electron/src/services/docking.service.ts new file mode 100644 index 00000000..0aefe8b5 --- /dev/null +++ b/terminus-electron/src/services/docking.service.ts @@ -0,0 +1,97 @@ +import { Injectable } from '@angular/core' +import type { Display } from 'electron' +import { ConfigService, ElectronService, HostAppService, Bounds, DockingService, Screen } from 'terminus-core' + +@Injectable() +export class ElectronDockingService extends DockingService { + constructor ( + private electron: ElectronService, + private config: ConfigService, + private hostApp: HostAppService, + ) { + super() + hostApp.displaysChanged$.subscribe(() => this.repositionWindow()) + hostApp.displayMetricsChanged$.subscribe(() => this.repositionWindow()) + } + + dock (): void { + const dockSide = this.config.store.appearance.dock + + if (dockSide === 'off') { + this.hostApp.setAlwaysOnTop(false) + return + } + + let display = this.electron.screen.getAllDisplays() + .filter(x => x.id === this.config.store.appearance.dockScreen)[0] + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + if (!display) { + display = this.getCurrentScreen() + } + + const newBounds: Bounds = { x: 0, y: 0, width: 0, height: 0 } + + const fill = this.config.store.appearance.dockFill <= 1 ? this.config.store.appearance.dockFill : 1 + const space = this.config.store.appearance.dockSpace <= 1 ? this.config.store.appearance.dockSpace : 1 + const [minWidth, minHeight] = this.hostApp.getWindow().getMinimumSize() + + if (dockSide === 'left' || dockSide === 'right') { + newBounds.width = Math.max(minWidth, Math.round(fill * display.bounds.width)) + newBounds.height = Math.round(display.bounds.height * space) + } + if (dockSide === 'top' || dockSide === 'bottom') { + newBounds.width = Math.round(display.bounds.width * space) + newBounds.height = Math.max(minHeight, Math.round(fill * display.bounds.height)) + } + if (dockSide === 'right') { + newBounds.x = display.bounds.x + display.bounds.width - newBounds.width + } else if (dockSide === 'left') { + newBounds.x = display.bounds.x + } else { + newBounds.x = display.bounds.x + Math.round(display.bounds.width / 2 * (1 - space)) + } + if (dockSide === 'bottom') { + newBounds.y = display.bounds.y + display.bounds.height - newBounds.height + } else if (dockSide === 'top') { + newBounds.y = display.bounds.y + } else { + newBounds.y = display.bounds.y + Math.round(display.bounds.height / 2 * (1 - space)) + } + + const alwaysOnTop = this.config.store.appearance.dockAlwaysOnTop + + this.hostApp.setAlwaysOnTop(alwaysOnTop) + setImmediate(() => { + this.hostApp.setBounds(newBounds) + }) + } + + getScreens (): Screen[] { + const primaryDisplayID = this.electron.screen.getPrimaryDisplay().id + return this.electron.screen.getAllDisplays().sort((a, b) => + a.bounds.x === b.bounds.x ? a.bounds.y - b.bounds.y : a.bounds.x - b.bounds.x + ).map((display, index) => { + return { + ...display, + id: display.id, + name: display.id === primaryDisplayID ? 'Primary Display' : `Display ${index + 1}`, + } + }) + } + + private getCurrentScreen (): Display { + return this.electron.screen.getDisplayNearestPoint(this.electron.screen.getCursorScreenPoint()) + } + + private repositionWindow () { + const [x, y] = this.hostApp.getWindow().getPosition() + for (const screen of this.electron.screen.getAllDisplays()) { + const bounds = screen.bounds + if (x >= bounds.x && x <= bounds.x + bounds.width && y >= bounds.y && y <= bounds.y + bounds.height) { + return + } + } + const screen = this.electron.screen.getPrimaryDisplay() + this.hostApp.getWindow().setPosition(screen.bounds.x, screen.bounds.y) + } +} diff --git a/terminus-electron/src/services/log.service.ts b/terminus-electron/src/services/log.service.ts new file mode 100644 index 00000000..57cfbe06 --- /dev/null +++ b/terminus-electron/src/services/log.service.ts @@ -0,0 +1,54 @@ +import * as fs from 'fs' +import * as path from 'path' +import * as winston from 'winston' +import { Injectable } from '@angular/core' +import { ConsoleLogger, Logger, ElectronService } from 'terminus-core' + +const initializeWinston = (electron: ElectronService) => { + const logDirectory = electron.app.getPath('userData') + // eslint-disable-next-line + const winston = require('winston') + + if (!fs.existsSync(logDirectory)) { + fs.mkdirSync(logDirectory) + } + + return winston.createLogger({ + transports: [ + new winston.transports.File({ + level: 'debug', + filename: path.join(logDirectory, 'log.txt'), + format: winston.format.simple(), + handleExceptions: false, + maxsize: 5242880, + maxFiles: 5, + }), + ], + exitOnError: false, + }) +} + +export class WinstonAndConsoleLogger extends ConsoleLogger { + constructor (private winstonLogger: winston.Logger, name: string) { + super(name) + } + + protected doLog (level: string, ...args: any[]): void { + super.doLog(level, ...args) + this.winstonLogger[level](...args) + } +} + +@Injectable({ providedIn: 'root' }) +export class ElectronLogService { + private log: winston.Logger + + /** @hidden */ + constructor (electron: ElectronService) { + this.log = initializeWinston(electron) + } + + create (name: string): Logger { + return new WinstonAndConsoleLogger(this.log, name) + } +} diff --git a/terminus-electron/src/services/platform.ts b/terminus-electron/src/services/platform.ts new file mode 100644 index 00000000..acbba4f9 --- /dev/null +++ b/terminus-electron/src/services/platform.ts @@ -0,0 +1,146 @@ +import * as path from 'path' +import * as fs from 'mz/fs' +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' +const fontManager = require('fontmanager-redux') // eslint-disable-line + +/* eslint-disable block-scoped-var */ + +try { + // eslint-disable-next-line no-var + var windowsProcessTreeNative = require('windows-process-tree/build/Release/windows_process_tree.node') + // eslint-disable-next-line no-var + var wnr = require('windows-native-registry') +} catch { } + +@Injectable() +export class ElectronPlatformService extends PlatformService { + supportsWindowControls = true + private userPluginsPath: string = (window as any).userPluginsPath + private configPath: string + + constructor ( + private hostApp: HostAppService, + private electron: ElectronService, + ) { + super() + this.configPath = path.join(electron.app.getPath('userData'), 'config.yaml') + } + + setClipboard (content: ClipboardContent): void { + require('@electron/remote').clipboard.write(content) + } + + async installPlugin (name: string, version: string): Promise { + await (promiseIpc as any).send('plugin-manager:install', this.userPluginsPath, name, version) + } + + async uninstallPlugin (name: string): Promise { + await (promiseIpc as any).send('plugin-manager:uninstall', this.userPluginsPath, name) + } + + async isProcessRunning (name: string): Promise { + if (this.hostApp.platform === Platform.Windows) { + return new Promise(resolve => { + windowsProcessTreeNative.getProcessList(list => { // eslint-disable-line block-scoped-var + resolve(list.some(x => x.name === name)) + }, 0) + }) + } else { + throw new Error('Not supported') + } + } + + getWinSCPPath (): string|null { + const key = wnr.getRegistryKey(wnr.HK.CR, 'WinSCP.Url\\DefaultIcon') + if (key?.['']) { + let detectedPath = key[''].value?.split(',')[0] + detectedPath = detectedPath?.substring(1, detectedPath.length - 1) + return detectedPath + } + return null + } + + exec (app: string, argv: string[]): void { + execFile(app, argv) + } + + isShellIntegrationSupported (): boolean { + return this.hostApp.platform !== Platform.Linux + } + + async isShellIntegrationInstalled (): Promise { + return false + } + + async installShellIntegration (): Promise { + throw new Error('Not implemented') + } + + async uninstallShellIntegration (): Promise { + throw new Error('Not implemented') + } + + async loadConfig (): Promise { + if (await fs.exists(this.configPath)) { + return fs.readFileSync(this.configPath, 'utf8') + } else { + return '' + } + } + + async saveConfig (content: string): Promise { + await fs.writeFile(this.configPath, content, 'utf8') + } + + getConfigPath (): string|null { + return this.configPath + } + + showItemInFolder (p: string): void { + this.electron.shell.showItemInFolder(p) + } + + openExternal (url: string): void { + this.electron.shell.openExternal(url) + } + + openPath (p: string): void { + this.electron.shell.openPath(p) + } + + getOSRelease (): string { + return os.release() + } + + getAppVersion (): string { + return this.electron.app.getVersion() + } + + async listFonts (): Promise { + if (this.hostApp.platform === Platform.Windows || this.hostApp.platform === Platform.macOS) { + let fonts = await new Promise((resolve) => fontManager.findFonts({ monospace: true }, resolve)) + fonts = fonts.map(x => x.family.trim()) + return fonts + } + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + if (this.hostApp.platform === Platform.Linux) { + const stdout = (await execFile('fc-list', [':spacing=mono']))[0] + const fonts = stdout.toString() + .split('\n') + .filter(x => !!x) + .map(x => x.split(':')[1].trim()) + .map(x => x.split(',')[0].trim()) + fonts.sort() + return fonts + } + return [] + } + + popupContextMenu (menu: MenuItemOptions[], _event?: MouseEvent): void { + this.electron.Menu.buildFromTemplate(menu).popup({}) + } +} diff --git a/terminus-core/src/services/shellIntegration.service.ts b/terminus-electron/src/services/shellIntegration.service.ts similarity index 95% rename from terminus-core/src/services/shellIntegration.service.ts rename to terminus-electron/src/services/shellIntegration.service.ts index e3a906b3..39013367 100644 --- a/terminus-core/src/services/shellIntegration.service.ts +++ b/terminus-electron/src/services/shellIntegration.service.ts @@ -2,8 +2,8 @@ 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 './electron.service' -import { HostAppService, Platform } from './hostApp.service' +import { ElectronService } from '../../../terminus-core/src/services/electron.service' +import { HostAppService, Platform } from '../../../terminus-core/src/services/hostApp.service' /* eslint-disable block-scoped-var */ diff --git a/terminus-core/src/services/touchbar.service.ts b/terminus-electron/src/services/touchbar.service.ts similarity index 94% rename from terminus-core/src/services/touchbar.service.ts rename to terminus-electron/src/services/touchbar.service.ts index 5b79bcbe..7cb7209b 100644 --- a/terminus-core/src/services/touchbar.service.ts +++ b/terminus-electron/src/services/touchbar.service.ts @@ -1,8 +1,6 @@ import { SegmentedControlSegment, TouchBarSegmentedControl } from 'electron' import { Injectable, NgZone } from '@angular/core' -import { AppService } from './app.service' -import { ElectronService } from './electron.service' -import { HostAppService, Platform } from './hostApp.service' +import { AppService, HostAppService, Platform, ElectronService } from 'terminus-core' /** @hidden */ @Injectable({ providedIn: 'root' }) diff --git a/terminus-electron/src/services/updater.service.ts b/terminus-electron/src/services/updater.service.ts new file mode 100644 index 00000000..f6d16250 --- /dev/null +++ b/terminus-electron/src/services/updater.service.ts @@ -0,0 +1,134 @@ +import { Injectable } from '@angular/core' +import axios from 'axios' + +import { Logger, LogService, ElectronService, ConfigService, HostAppService, UpdaterService } from 'terminus-core' + +const UPDATES_URL = 'https://api.github.com/repos/eugeny/terminus/releases/latest' + +@Injectable() +export class ElectronUpdaterService extends UpdaterService { + private logger: Logger + private downloaded: Promise + private electronUpdaterAvailable = true + private updateURL: string + + constructor ( + log: LogService, + config: ConfigService, + private electron: ElectronService, + private hostApp: HostAppService, + ) { + super() + this.logger = log.create('updater') + + if (process.platform === 'linux') { + this.electronUpdaterAvailable = false + return + } + + electron.autoUpdater.on('update-available', () => { + this.logger.info('Update available') + }) + + electron.autoUpdater.once('update-not-available', () => { + this.logger.info('No updates') + }) + + electron.autoUpdater.once('error', err => { + this.logger.error(err) + }) + + this.downloaded = new Promise(resolve => { + 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) + } + } + } + + async check (): Promise { + if (this.electronUpdaterAvailable) { + return new Promise((resolve, reject) => { + // eslint-disable-next-line @typescript-eslint/init-declarations, prefer-const + let cancel + const onNoUpdate = () => { + cancel() + resolve(false) + } + const onUpdate = () => { + cancel() + resolve(this.downloaded) + } + const onError = (err) => { + cancel() + reject(err) + } + cancel = () => { + this.electron.autoUpdater.off('error', onError) + this.electron.autoUpdater.off('update-not-available', onNoUpdate) + this.electron.autoUpdater.off('update-available', onUpdate) + } + this.electron.autoUpdater.on('error', onError) + this.electron.autoUpdater.on('update-not-available', onNoUpdate) + this.electron.autoUpdater.on('update-available', onUpdate) + try { + this.electron.autoUpdater.checkForUpdates() + } catch (e) { + this.electronUpdaterAvailable = false + this.logger.info('Electron updater unavailable, falling back', e) + } + }) + + this.electron.autoUpdater.on('update-available', () => { + this.logger.info('Update available') + }) + + this.electron.autoUpdater.once('update-not-available', () => { + this.logger.info('No updates') + }) + + } else { + this.logger.debug('Checking for updates through fallback method.') + const response = await axios.get(UPDATES_URL) + const data = response.data + const version = data.tag_name.substring(1) + if (this.electron.app.getVersion() !== version) { + this.logger.info('Update available') + this.updateURL = data.html_url + return true + } + this.logger.info('No updates') + return false + } + return this.downloaded + } + + async update (): Promise { + if (!this.electronUpdaterAvailable) { + this.electron.shell.openExternal(this.updateURL) + } else { + if ((await this.electron.showMessageBox( + this.hostApp.getWindow(), + { + type: 'warning', + message: 'Installing the update will close all tabs and restart Terminus.', + buttons: ['Cancel', 'Update'], + defaultId: 1, + } + )).response === 1) { + await this.downloaded + this.electron.autoUpdater.quitAndInstall() + } + } + } +} diff --git a/terminus-electron/tsconfig.json b/terminus-electron/tsconfig.json new file mode 100644 index 00000000..286cc9cd --- /dev/null +++ b/terminus-electron/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../tsconfig.json", + "exclude": ["node_modules", "dist"], + "compilerOptions": { + "baseUrl": "src" + } +} diff --git a/terminus-electron/tsconfig.typings.json b/terminus-electron/tsconfig.typings.json new file mode 100644 index 00000000..c0d2273c --- /dev/null +++ b/terminus-electron/tsconfig.typings.json @@ -0,0 +1,14 @@ +{ + "extends": "../tsconfig.json", + "exclude": ["node_modules", "dist", "typings"], + "compilerOptions": { + "baseUrl": "src", + "emitDeclarationOnly": true, + "declaration": true, + "declarationDir": "./typings", + "paths": { + "terminus-*": ["../../terminus-*"], + "*": ["../../app/node_modules/*"] + } + } +} diff --git a/terminus-electron/webpack.config.js b/terminus-electron/webpack.config.js new file mode 100644 index 00000000..432b0e9d --- /dev/null +++ b/terminus-electron/webpack.config.js @@ -0,0 +1,5 @@ +const config = require('../webpack.plugin.config') +module.exports = config({ + name: 'electron', + dirname: __dirname, +}) diff --git a/terminus-electron/yarn.lock b/terminus-electron/yarn.lock new file mode 100644 index 00000000..8d272afa --- /dev/null +++ b/terminus-electron/yarn.lock @@ -0,0 +1,477 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@dabh/diagnostics@^2.0.2": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@dabh/diagnostics/-/diagnostics-2.0.2.tgz#290d08f7b381b8f94607dc8f471a12c675f9db31" + integrity sha512-+A1YivoVDNNVCdfozHSR8v/jyuuLTMXwjWuxPFlFlUapXoGc+Gj9mDlTDDfrwl7rXCl2tNZ0kE8sIBO6YOn96Q== + dependencies: + colorspace "1.1.x" + enabled "2.0.x" + kuler "^2.0.0" + +async@^3.1.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/async/-/async-3.2.0.tgz#b3a2685c5ebb641d3de02d161002c60fc9f85720" + integrity sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw== + +axios@^0.21.1: + version "0.21.1" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.1.tgz#22563481962f4d6bde9a76d516ef0e5d3c09b2b8" + integrity sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA== + dependencies: + follow-redirects "^1.10.0" + +call-bind@^1.0.0, call-bind@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" + integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== + dependencies: + function-bind "^1.1.1" + get-intrinsic "^1.0.2" + +color-convert@^1.9.1: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= + +color-name@^1.0.0: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +color-string@^1.5.2: + version "1.5.5" + resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.5.5.tgz#65474a8f0e7439625f3d27a6a19d89fc45223014" + integrity sha512-jgIoum0OfQfq9Whcfc2z/VhCNcmQjWbey6qBX0vqt7YICflUmBCh9E9CiQD5GSJ+Uehixm3NUwHVhqUAWRivZg== + dependencies: + color-name "^1.0.0" + simple-swizzle "^0.2.2" + +color@3.0.x: + version "3.0.0" + resolved "https://registry.yarnpkg.com/color/-/color-3.0.0.tgz#d920b4328d534a3ac8295d68f7bd4ba6c427be9a" + integrity sha512-jCpd5+s0s0t7p3pHQKpnJ0TpQKKdleP71LWcA0aqiljpiuAkOSUFN/dyH8ZwF0hRmFlrIuRhufds1QyEP9EB+w== + dependencies: + color-convert "^1.9.1" + color-string "^1.5.2" + +colors@^1.2.1: + version "1.4.0" + resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78" + integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA== + +colorspace@1.1.x: + version "1.1.2" + resolved "https://registry.yarnpkg.com/colorspace/-/colorspace-1.1.2.tgz#e0128950d082b86a2168580796a0aa5d6c68d8c5" + integrity sha512-vt+OoIP2d76xLhjwbBaucYlNSpPsrJWPlBTtwCpQKIu6/CSMutyzX93O/Do0qzpH3YoHEes8YEFXyZ797rEhzQ== + dependencies: + color "3.0.x" + text-hex "1.0.x" + +core-util-is@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" + integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= + +define-properties@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" + integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== + dependencies: + object-keys "^1.0.12" + +electron-promise-ipc@^2.2.4: + version "2.2.4" + resolved "https://registry.yarnpkg.com/electron-promise-ipc/-/electron-promise-ipc-2.2.4.tgz#b82daf86ca6d0f0b8655937fdbe9a554590deeea" + integrity sha512-xCkFEeuru9l7H/+m1gpK4F1utexvTT7+n1PTquP2MVTpmBmpgFBlLqSXC7TqwpROkHRm9wGpaCJEx0djonnSEg== + dependencies: + is-electron-renderer "^2.0.1" + object.entries "^1.1.3" + serialize-error "^5.0.0" + uuid "^3.0.1" + +enabled@2.0.x: + version "2.0.0" + resolved "https://registry.yarnpkg.com/enabled/-/enabled-2.0.0.tgz#f9dd92ec2d6f4bbc0d5d1e64e21d61cd4665e7c2" + integrity sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ== + +es-abstract@^1.18.0-next.1: + version "1.18.0" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.18.0.tgz#ab80b359eecb7ede4c298000390bc5ac3ec7b5a4" + integrity sha512-LJzK7MrQa8TS0ja2w3YNLzUgJCGPdPOV1yVvezjNnS89D+VR08+Szt2mz3YB2Dck/+w5tfIq/RoUAFqJJGM2yw== + dependencies: + call-bind "^1.0.2" + es-to-primitive "^1.2.1" + function-bind "^1.1.1" + get-intrinsic "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.2" + is-callable "^1.2.3" + is-negative-zero "^2.0.1" + is-regex "^1.1.2" + is-string "^1.0.5" + object-inspect "^1.9.0" + object-keys "^1.1.1" + object.assign "^4.1.2" + string.prototype.trimend "^1.0.4" + string.prototype.trimstart "^1.0.4" + unbox-primitive "^1.0.0" + +es-to-primitive@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" + integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== + dependencies: + is-callable "^1.1.4" + is-date-object "^1.0.1" + is-symbol "^1.0.2" + +fast-safe-stringify@^2.0.4: + version "2.0.7" + resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz#124aa885899261f68aedb42a7c080de9da608743" + integrity sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA== + +fecha@^4.2.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/fecha/-/fecha-4.2.1.tgz#0a83ad8f86ef62a091e22bb5a039cd03d23eecce" + integrity sha512-MMMQ0ludy/nBs1/o0zVOiKTpG7qMbonKUzjJgQFEuvq6INZ1OraKPRAWkBq5vlKLOUMpmNYG1JoN3oDPUQ9m3Q== + +fn.name@1.x.x: + version "1.1.0" + resolved "https://registry.yarnpkg.com/fn.name/-/fn.name-1.1.0.tgz#26cad8017967aea8731bc42961d04a3d5988accc" + integrity sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw== + +follow-redirects@^1.10.0: + version "1.14.1" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.1.tgz#d9114ded0a1cfdd334e164e6662ad02bfd91ff43" + integrity sha512-HWqDgT7ZEkqRzBvc2s64vSZ/hfOceEol3ac/7tKwzuvEyWx3/4UegXh5oBOIotkGsObyk3xznnSRVADBgWSQVg== + +function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== + +get-intrinsic@^1.0.2, get-intrinsic@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.1.tgz#15f59f376f855c446963948f0d24cd3637b4abc6" + integrity sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q== + dependencies: + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.1" + +has-bigints@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.1.tgz#64fe6acb020673e3b78db035a5af69aa9d07b113" + integrity sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA== + +has-symbols@^1.0.1, has-symbols@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.2.tgz#165d3070c00309752a1236a479331e3ac56f1423" + integrity sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw== + +has@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" + integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== + dependencies: + function-bind "^1.1.1" + +inherits@^2.0.3, inherits@~2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +is-arrayish@^0.3.1: + version "0.3.2" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03" + integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== + +is-bigint@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.2.tgz#ffb381442503235ad245ea89e45b3dbff040ee5a" + integrity sha512-0JV5+SOCQkIdzjBK9buARcV804Ddu7A0Qet6sHi3FimE9ne6m4BGQZfRn+NZiXbBk4F4XmHfDZIipLj9pX8dSA== + +is-boolean-object@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.1.tgz#3c0878f035cb821228d350d2e1e36719716a3de8" + integrity sha512-bXdQWkECBUIAcCkeH1unwJLIpZYaa5VvuygSyS/c2lf719mTKZDU5UdDRlpd01UjADgmW8RfqaP+mRaVPdr/Ng== + dependencies: + call-bind "^1.0.2" + +is-callable@^1.1.4, is-callable@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.3.tgz#8b1e0500b73a1d76c70487636f368e519de8db8e" + integrity sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ== + +is-date-object@^1.0.1: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.4.tgz#550cfcc03afada05eea3dd30981c7b09551f73e5" + integrity sha512-/b4ZVsG7Z5XVtIxs/h9W8nvfLgSAyKYdtGWQLbqy6jA1icmgjf8WCoTKgeS4wy5tYaPePouzFMANbnj94c2Z+A== + +is-electron-renderer@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-electron-renderer/-/is-electron-renderer-2.0.1.tgz#a469d056f975697c58c98c6023eb0aa79af895a2" + integrity sha1-pGnQVvl1aXxYyYxgI+sKp5r4laI= + +is-negative-zero@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.1.tgz#3de746c18dda2319241a53675908d8f766f11c24" + integrity sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w== + +is-number-object@^1.0.4: + version "1.0.5" + resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.5.tgz#6edfaeed7950cff19afedce9fbfca9ee6dd289eb" + integrity sha512-RU0lI/n95pMoUKu9v1BZP5MBcZuNSVJkMkAG2dJqC4z2GlkGUNeH68SuHuBKBD/XFe+LHZ+f9BKkLET60Niedw== + +is-regex@^1.1.2: + version "1.1.3" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.3.tgz#d029f9aff6448b93ebbe3f33dac71511fdcbef9f" + integrity sha512-qSVXFz28HM7y+IWX6vLCsexdlvzT1PJNFSBuaQLQ5o0IEw8UDYW6/2+eCMVyIsbM8CNLX2a/QWmSpyxYEHY7CQ== + dependencies: + call-bind "^1.0.2" + has-symbols "^1.0.2" + +is-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.0.tgz#bde9c32680d6fae04129d6ac9d921ce7815f78e3" + integrity sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw== + +is-string@^1.0.5: + version "1.0.6" + resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.6.tgz#3fe5d5992fb0d93404f32584d4b0179a71b54a5f" + integrity sha512-2gdzbKUuqtQ3lYNrUTQYoClPhm7oQu4UdpSZMp1/DGgkHBT8E2Z1l0yMdb6D4zNAxwDiMv8MdulKROJGNl0Q0w== + +is-symbol@^1.0.2, is-symbol@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.4.tgz#a6dac93b635b063ca6872236de88910a57af139c" + integrity sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg== + dependencies: + has-symbols "^1.0.2" + +isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= + +kuler@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/kuler/-/kuler-2.0.0.tgz#e2c570a3800388fb44407e851531c1d670b061b3" + integrity sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A== + +logform@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/logform/-/logform-2.2.0.tgz#40f036d19161fc76b68ab50fdc7fe495544492f2" + integrity sha512-N0qPlqfypFx7UHNn4B3lzS/b0uLqt2hmuoa+PpuXNYgozdJYAyauF5Ky0BWVjrxDlMWiT3qN4zPq3vVAfZy7Yg== + dependencies: + colors "^1.2.1" + fast-safe-stringify "^2.0.4" + fecha "^4.2.0" + ms "^2.1.1" + triple-beam "^1.3.0" + +ms@^2.1.1: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +object-inspect@^1.9.0: + version "1.10.3" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.10.3.tgz#c2aa7d2d09f50c99375704f7a0adf24c5782d369" + integrity sha512-e5mCJlSH7poANfC8z8S9s9S2IN5/4Zb3aZ33f5s8YqoazCFzNLloLU8r5VCG+G7WoqLvAAZoVMcy3tp/3X0Plw== + +object-keys@^1.0.12, object-keys@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" + integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== + +object.assign@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.2.tgz#0ed54a342eceb37b38ff76eb831a0e788cb63940" + integrity sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ== + dependencies: + call-bind "^1.0.0" + define-properties "^1.1.3" + has-symbols "^1.0.1" + object-keys "^1.1.1" + +object.entries@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.3.tgz#c601c7f168b62374541a07ddbd3e2d5e4f7711a6" + integrity sha512-ym7h7OZebNS96hn5IJeyUmaWhaSM4SVtAPPfNLQEI2MYWCO2egsITb9nab2+i/Pwibx+R0mtn+ltKJXRSeTMGg== + dependencies: + call-bind "^1.0.0" + define-properties "^1.1.3" + es-abstract "^1.18.0-next.1" + has "^1.0.3" + +one-time@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/one-time/-/one-time-1.0.0.tgz#e06bc174aed214ed58edede573b433bbf827cb45" + integrity sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g== + dependencies: + fn.name "1.x.x" + +process-nextick-args@~2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" + integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== + +readable-stream@^2.3.7: + version "2.3.7" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" + integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~2.0.0" + safe-buffer "~5.1.1" + string_decoder "~1.1.1" + util-deprecate "~1.0.1" + +readable-stream@^3.4.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" + integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + +safe-buffer@~5.1.0, safe-buffer@~5.1.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + +safe-buffer@~5.2.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +serialize-error@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/serialize-error/-/serialize-error-5.0.0.tgz#a7ebbcdb03a5d71a6ed8461ffe0fc1a1afed62ac" + integrity sha512-/VtpuyzYf82mHYTtI4QKtwHa79vAdU5OQpNPAmE/0UDdlGT0ZxHwC+J6gXkw29wwoVI8fMPsfcVHOwXtUQYYQA== + dependencies: + type-fest "^0.8.0" + +simple-swizzle@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a" + integrity sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo= + dependencies: + is-arrayish "^0.3.1" + +stack-trace@0.0.x: + version "0.0.10" + resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0" + integrity sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA= + +string.prototype.trimend@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz#e75ae90c2942c63504686c18b287b4a0b1a45f80" + integrity sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + +string.prototype.trimstart@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz#b36399af4ab2999b4c9c648bd7a3fb2bb26feeed" + integrity sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + +string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + +string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== + dependencies: + safe-buffer "~5.1.0" + +text-hex@1.0.x: + version "1.0.0" + resolved "https://registry.yarnpkg.com/text-hex/-/text-hex-1.0.0.tgz#69dc9c1b17446ee79a92bf5b884bb4b9127506f5" + integrity sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg== + +triple-beam@^1.2.0, triple-beam@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/triple-beam/-/triple-beam-1.3.0.tgz#a595214c7298db8339eeeee083e4d10bd8cb8dd9" + integrity sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw== + +type-fest@^0.8.0: + version "0.8.1" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" + integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== + +unbox-primitive@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.1.tgz#085e215625ec3162574dc8859abee78a59b14471" + integrity sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw== + dependencies: + function-bind "^1.1.1" + has-bigints "^1.0.1" + has-symbols "^1.0.2" + which-boxed-primitive "^1.0.2" + +util-deprecate@^1.0.1, util-deprecate@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= + +uuid@^3.0.1: + version "3.4.0" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" + integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== + +which-boxed-primitive@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" + integrity sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg== + dependencies: + is-bigint "^1.0.1" + is-boolean-object "^1.1.0" + is-number-object "^1.0.4" + is-string "^1.0.5" + is-symbol "^1.0.3" + +winston-transport@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/winston-transport/-/winston-transport-4.4.0.tgz#17af518daa690d5b2ecccaa7acf7b20ca7925e59" + integrity sha512-Lc7/p3GtqtqPBYYtS6KCN3c77/2QCev51DvcJKbkFPQNoj1sinkGwLGFDxkXY9J6p9+EPnYs+D90uwbnaiURTw== + dependencies: + readable-stream "^2.3.7" + triple-beam "^1.2.0" + +winston@^3.3.3: + version "3.3.3" + resolved "https://registry.yarnpkg.com/winston/-/winston-3.3.3.tgz#ae6172042cafb29786afa3d09c8ff833ab7c9170" + integrity sha512-oEXTISQnC8VlSAKf1KYSSd7J6IWuRPQqDdo8eoRNaYKLvwSb5+79Z3Yi1lrl6KDpU6/VWaxpakDAtb1oQ4n9aw== + dependencies: + "@dabh/diagnostics" "^2.0.2" + async "^3.1.0" + is-stream "^2.0.0" + logform "^2.2.0" + one-time "^1.0.0" + readable-stream "^3.4.0" + stack-trace "0.0.x" + triple-beam "^1.3.0" + winston-transport "^4.4.0" diff --git a/terminus-local/src/components/terminalTab.component.ts b/terminus-local/src/components/terminalTab.component.ts index a35da2cb..64a65674 100644 --- a/terminus-local/src/components/terminalTab.component.ts +++ b/terminus-local/src/components/terminalTab.component.ts @@ -24,7 +24,7 @@ export class TerminalTabComponent extends BaseTerminalTabComponent { ngOnInit (): void { this.logger = this.log.create('terminalTab') - this.session = new Session(this.config) + this.session = new Session(this.injector) const isConPTY = isWindowsBuild(WIN_BUILD_CONPTY_SUPPORTED) && this.config.store.terminal.useConPTY diff --git a/terminus-local/src/index.ts b/terminus-local/src/index.ts index f1344a4e..72c11d4b 100644 --- a/terminus-local/src/index.ts +++ b/terminus-local/src/index.ts @@ -4,7 +4,7 @@ import { FormsModule } from '@angular/forms' import { NgbModule } from '@ng-bootstrap/ng-bootstrap' import { ToastrModule } from 'ngx-toastr' -import TerminusCorePlugin, { HostAppService, ToolbarButtonProvider, TabRecoveryProvider, ConfigProvider, HotkeysService, HotkeyProvider, TabContextMenuItemProvider, CLIHandler } from 'terminus-core' +import TerminusCorePlugin, { HostAppService, ToolbarButtonProvider, TabRecoveryProvider, ConfigProvider, HotkeysService, HotkeyProvider, TabContextMenuItemProvider, CLIHandler, ConfigService } from 'terminus-core' import TerminusTerminalModule from 'terminus-terminal' import { SettingsTabProvider } from 'terminus-settings' @@ -104,6 +104,7 @@ export default class LocalTerminalModule { // eslint-disable-line @typescript-es terminal: TerminalService, hostApp: HostAppService, dockMenu: DockMenuService, + config: ConfigService, ) { hotkeys.matchedHotkey.subscribe(async (hotkey) => { if (hotkey === 'new-tab') { @@ -120,7 +121,9 @@ export default class LocalTerminalModule { // eslint-disable-line @typescript-es } }) - dockMenu.update() + config.ready$.toPromise().then(() => { + dockMenu.update() + }) } } diff --git a/terminus-local/src/services/terminal.service.ts b/terminus-local/src/services/terminal.service.ts index 4c0b8e6e..e71b9c27 100644 --- a/terminus-local/src/services/terminal.service.ts +++ b/terminus-local/src/services/terminal.service.ts @@ -26,10 +26,12 @@ export class TerminalService { log: LogService, ) { this.logger = log.create('terminal') - this.reloadShells() - config.changed$.subscribe(() => { + config.ready$.toPromise().then(() => { this.reloadShells() + config.changed$.subscribe(() => { + this.reloadShells() + }) }) } diff --git a/terminus-local/src/session.ts b/terminus-local/src/session.ts index c350d751..06593fef 100644 --- a/terminus-local/src/session.ts +++ b/terminus-local/src/session.ts @@ -1,7 +1,8 @@ import * as psNode from 'ps-node' import * as fs from 'mz/fs' import * as os from 'os' -import { ConfigService, WIN_BUILD_CONPTY_SUPPORTED, isWindowsBuild, getBootstrapData } from 'terminus-core' +import { Injector } from '@angular/core' +import { HostAppService, ConfigService, WIN_BUILD_CONPTY_SUPPORTED, isWindowsBuild, Platform, BootstrapData, BOOTSTRAP_DATA } from 'terminus-core' import { BaseSession } from 'terminus-terminal' import { ipcRenderer } from 'electron' import { getWorkingDirectoryFromPID } from 'native-process-working-directory' @@ -91,9 +92,15 @@ export class Session extends BaseSession { private guessedCWD: string|null = null private reportedCWD: string private initialCWD: string|null = null + private config: ConfigService + private hostApp: HostAppService + private bootstrapData: BootstrapData - constructor (private config: ConfigService) { + constructor (injector: Injector) { super() + this.config = injector.get(ConfigService) + this.hostApp = injector.get(HostAppService) + this.bootstrapData = injector.get(BOOTSTRAP_DATA) as BootstrapData } start (options: SessionOptions): void { @@ -115,13 +122,13 @@ export class Session extends BaseSession { ...this.config.store.terminal.environment || {}, } - if (process.platform === 'win32') { - env.COMSPEC = getBootstrapData().executable + if (this.hostApp.platform === Platform.Windows) { + env.COMSPEC = this.bootstrapData.executable } delete env[''] - if (process.platform === 'darwin' && !process.env.LC_ALL) { + if (this.hostApp.platform === Platform.macOS && !process.env.LC_ALL) { const locale = process.env.LC_CTYPE ?? 'en_US.UTF-8' Object.assign(env, { LANG: locale, @@ -177,7 +184,7 @@ export class Session extends BaseSession { let data = Buffer.from(array) data = this.processOSC1337(data) this.emitOutput(data) - if (process.platform === 'win32') { + if (this.hostApp.platform === Platform.Windows) { this.guessWindowsCWD(data.toString()) } }) @@ -229,7 +236,7 @@ export class Session extends BaseSession { if (!this.truePID) { return [] } - if (process.platform === 'darwin') { + if (this.hostApp.platform === Platform.macOS) { const processes = await macOSNativeProcessList.getProcessList() return processes.filter(x => x.ppid === this.truePID).map(p => ({ pid: p.pid, @@ -237,7 +244,7 @@ export class Session extends BaseSession { command: p.name, })) } - if (process.platform === 'win32') { + if (this.hostApp.platform === Platform.Windows) { return new Promise(resolve => { windowsProcessTree.getProcessTree(this.truePID, tree => { resolve(tree ? tree.children.map(child => ({ @@ -259,7 +266,7 @@ export class Session extends BaseSession { } async gracefullyKillProcess (): Promise { - if (process.platform === 'win32') { + if (this.hostApp.platform === Platform.Windows) { this.kill() } else { await new Promise((resolve) => { @@ -302,7 +309,7 @@ export class Session extends BaseSession { cwd = await fs.realpath(cwd) } catch {} - if (process.platform === 'win32' && (cwd === this.initialCWD || cwd === process.env.WINDIR)) { + if (this.hostApp.platform === Platform.Windows && (cwd === this.initialCWD || cwd === process.env.WINDIR)) { // shell doesn't truly change its process' CWD cwd = null } diff --git a/terminus-local/src/tabContextMenu.ts b/terminus-local/src/tabContextMenu.ts index 22a01185..df0cb57b 100644 --- a/terminus-local/src/tabContextMenu.ts +++ b/terminus-local/src/tabContextMenu.ts @@ -1,6 +1,5 @@ -import { MenuItemConstructorOptions } from 'electron' import { Injectable, NgZone } from '@angular/core' -import { ConfigService, BaseTabComponent, TabContextMenuItemProvider, TabHeaderComponent, SplitTabComponent, NotificationsService } from 'terminus-core' +import { ConfigService, BaseTabComponent, TabContextMenuItemProvider, TabHeaderComponent, SplitTabComponent, NotificationsService, MenuItemOptions } from 'terminus-core' import { TerminalTabComponent } from './components/terminalTab.component' import { UACService } from './services/uac.service' import { TerminalService } from './services/terminal.service' @@ -16,11 +15,11 @@ export class SaveAsProfileContextMenu extends TabContextMenuItemProvider { super() } - async getItems (tab: BaseTabComponent, _tabHeader?: TabHeaderComponent): Promise { + async getItems (tab: BaseTabComponent, _tabHeader?: TabHeaderComponent): Promise { if (!(tab instanceof TerminalTabComponent)) { return [] } - const items: MenuItemConstructorOptions[] = [ + const items: MenuItemOptions[] = [ { label: 'Save as profile', click: () => this.zone.run(async () => { @@ -59,10 +58,10 @@ export class NewTabContextMenu extends TabContextMenuItemProvider { super() } - async getItems (tab: BaseTabComponent, tabHeader?: TabHeaderComponent): Promise { + async getItems (tab: BaseTabComponent, tabHeader?: TabHeaderComponent): Promise { const profiles = await this.terminalService.getProfiles() - const items: MenuItemConstructorOptions[] = [ + const items: MenuItemOptions[] = [ { label: 'New terminal', click: () => this.zone.run(() => { diff --git a/terminus-plugin-manager/package.json b/terminus-plugin-manager/package.json index 93fbb32d..5727f0e7 100644 --- a/terminus-plugin-manager/package.json +++ b/terminus-plugin-manager/package.json @@ -19,7 +19,6 @@ "devDependencies": { "@types/semver": "^7.1.0", "axios": "^0.21.1", - "electron-promise-ipc": "^2.2.4", "mz": "^2.6.0", "semver": "^7.1.1" }, diff --git a/terminus-plugin-manager/src/components/pluginsSettingsTab.component.ts b/terminus-plugin-manager/src/components/pluginsSettingsTab.component.ts index d56162c0..caab9b38 100644 --- a/terminus-plugin-manager/src/components/pluginsSettingsTab.component.ts +++ b/terminus-plugin-manager/src/components/pluginsSettingsTab.component.ts @@ -4,7 +4,7 @@ import { debounceTime, distinctUntilChanged, first, tap, flatMap, map } from 'rx import semverGt from 'semver/functions/gt' import { Component, Input } from '@angular/core' -import { ConfigService, ElectronService } from 'terminus-core' +import { ConfigService, PlatformService } from 'terminus-core' import { PluginInfo, PluginManagerService } from '../services/pluginManager.service' enum BusyState { Installing = 'Installing', Uninstalling = 'Uninstalling' } @@ -27,8 +27,8 @@ export class PluginsSettingsTabComponent { @Input() errorMessage: string constructor ( - private electron: ElectronService, private config: ConfigService, + private platform: PlatformService, public pluginManager: PluginManagerService ) { } @@ -57,7 +57,7 @@ export class PluginsSettingsTabComponent { } openPluginsFolder (): void { - this.electron.shell.openPath(this.pluginManager.userPluginsPath) + this.platform.openPath(this.pluginManager.userPluginsPath) } searchAvailable (query: string) { @@ -101,7 +101,7 @@ export class PluginsSettingsTabComponent { } showPluginInfo (plugin: PluginInfo) { - this.electron.shell.openExternal('https://www.npmjs.com/package/' + plugin.packageName) + this.platform.openExternal('https://www.npmjs.com/package/' + plugin.packageName) } isPluginEnabled (plugin: PluginInfo) { diff --git a/terminus-plugin-manager/src/services/pluginManager.service.ts b/terminus-plugin-manager/src/services/pluginManager.service.ts index 7803f003..3717b5b7 100644 --- a/terminus-plugin-manager/src/services/pluginManager.service.ts +++ b/terminus-plugin-manager/src/services/pluginManager.service.ts @@ -1,9 +1,8 @@ import axios from 'axios' -import promiseIpc from 'electron-promise-ipc' import { Observable, from } from 'rxjs' import { map } from 'rxjs/operators' import { Injectable } from '@angular/core' -import { Logger, LogService } from 'terminus-core' +import { Logger, LogService, PlatformService } from 'terminus-core' const NAME_PREFIX = 'terminus-' const KEYWORD = 'terminus-plugin' @@ -34,6 +33,7 @@ export class PluginManagerService { private constructor ( log: LogService, + private platform: PlatformService, ) { this.logger = log.create('pluginManager') } @@ -63,7 +63,7 @@ export class PluginManagerService { async installPlugin (plugin: PluginInfo): Promise { try { - await (promiseIpc as any).send('plugin-manager:install', this.userPluginsPath, plugin.packageName, plugin.version) + await this.platform.installPlugin(plugin.packageName, plugin.version) this.installedPlugins = this.installedPlugins.filter(x => x.packageName !== plugin.packageName) this.installedPlugins.push(plugin) } catch (err) { @@ -74,7 +74,7 @@ export class PluginManagerService { async uninstallPlugin (plugin: PluginInfo): Promise { try { - await (promiseIpc as any).send('plugin-manager:uninstall', this.userPluginsPath, plugin.packageName) + await this.platform.uninstallPlugin(plugin.packageName) this.installedPlugins = this.installedPlugins.filter(x => x.packageName !== plugin.packageName) } catch (err) { this.logger.error(err) diff --git a/terminus-plugin-manager/yarn.lock b/terminus-plugin-manager/yarn.lock index 55ff23ca..0eb56dd4 100644 --- a/terminus-plugin-manager/yarn.lock +++ b/terminus-plugin-manager/yarn.lock @@ -19,123 +19,11 @@ axios@^0.21.1: dependencies: follow-redirects "^1.10.0" -call-bind@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.0.tgz#24127054bb3f9bdcb4b1fb82418186072f77b8ce" - integrity sha512-AEXsYIyyDY3MCzbwdhzG3Jx1R0J2wetQyUynn6dYHAO+bg8l1k7jwZtRv4ryryFs7EP+NDlikJlVe59jr0cM2w== - dependencies: - function-bind "^1.1.1" - get-intrinsic "^1.0.0" - -define-properties@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" - integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== - dependencies: - object-keys "^1.0.12" - -electron-promise-ipc@^2.2.4: - version "2.2.4" - resolved "https://registry.yarnpkg.com/electron-promise-ipc/-/electron-promise-ipc-2.2.4.tgz#b82daf86ca6d0f0b8655937fdbe9a554590deeea" - integrity sha512-xCkFEeuru9l7H/+m1gpK4F1utexvTT7+n1PTquP2MVTpmBmpgFBlLqSXC7TqwpROkHRm9wGpaCJEx0djonnSEg== - dependencies: - is-electron-renderer "^2.0.1" - object.entries "^1.1.3" - serialize-error "^5.0.0" - uuid "^3.0.1" - -es-abstract@^1.18.0-next.1: - version "1.18.0-next.1" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.18.0-next.1.tgz#6e3a0a4bda717e5023ab3b8e90bec36108d22c68" - integrity sha512-I4UGspA0wpZXWENrdA0uHbnhte683t3qT/1VFH9aX2dA5PPSf6QW5HHXf5HImaqPmjXaVeVk4RGWnaylmV7uAA== - dependencies: - es-to-primitive "^1.2.1" - function-bind "^1.1.1" - has "^1.0.3" - has-symbols "^1.0.1" - is-callable "^1.2.2" - is-negative-zero "^2.0.0" - is-regex "^1.1.1" - object-inspect "^1.8.0" - object-keys "^1.1.1" - object.assign "^4.1.1" - string.prototype.trimend "^1.0.1" - string.prototype.trimstart "^1.0.1" - -es-to-primitive@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" - integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== - dependencies: - is-callable "^1.1.4" - is-date-object "^1.0.1" - is-symbol "^1.0.2" - follow-redirects@^1.10.0: version "1.13.1" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.1.tgz#5f69b813376cee4fd0474a3aba835df04ab763b7" integrity sha512-SSG5xmZh1mkPGyKzjZP8zLjltIfpW32Y5QpdNJyjcfGxK3qo3NDDkZOZSFiGn1A6SclQxY9GzEwAHQ3dmYRWpg== -function-bind@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" - integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== - -get-intrinsic@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.0.2.tgz#6820da226e50b24894e08859469dc68361545d49" - integrity sha512-aeX0vrFm21ILl3+JpFFRNe9aUvp6VFZb2/CTbgLb8j75kOhvoNYjt9d8KA/tJG4gSo8nzEDedRl0h7vDmBYRVg== - dependencies: - function-bind "^1.1.1" - has "^1.0.3" - has-symbols "^1.0.1" - -has-symbols@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8" - integrity sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg== - -has@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" - integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== - dependencies: - function-bind "^1.1.1" - -is-callable@^1.1.4, is-callable@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.2.tgz#c7c6715cd22d4ddb48d3e19970223aceabb080d9" - integrity sha512-dnMqspv5nU3LoewK2N/y7KLtxtakvTuaCsU9FU50/QDmdbHNy/4/JuRtMHqRU22o3q+W89YQndQEeCVwK+3qrA== - -is-date-object@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.2.tgz#bda736f2cd8fd06d32844e7743bfa7494c3bfd7e" - integrity sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g== - -is-electron-renderer@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-electron-renderer/-/is-electron-renderer-2.0.1.tgz#a469d056f975697c58c98c6023eb0aa79af895a2" - integrity sha1-pGnQVvl1aXxYyYxgI+sKp5r4laI= - -is-negative-zero@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.1.tgz#3de746c18dda2319241a53675908d8f766f11c24" - integrity sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w== - -is-regex@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.1.tgz#c6f98aacc546f6cec5468a07b7b153ab564a57b9" - integrity sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg== - dependencies: - has-symbols "^1.0.1" - -is-symbol@^1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.3.tgz#38e1014b9e6329be0de9d24a414fd7441ec61937" - integrity sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ== - dependencies: - has-symbols "^1.0.1" - lru-cache@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" @@ -157,36 +45,6 @@ object-assign@^4.0.1: resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= -object-inspect@^1.8.0: - version "1.9.0" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.9.0.tgz#c90521d74e1127b67266ded3394ad6116986533a" - integrity sha512-i3Bp9iTqwhaLZBxGkRfo5ZbE07BQRT7MGu8+nNgwW9ItGp1TzCTw2DLEoWwjClxBjOFI/hWljTAmYGCEwmtnOw== - -object-keys@^1.0.12, object-keys@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" - integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== - -object.assign@^4.1.1: - version "4.1.2" - resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.2.tgz#0ed54a342eceb37b38ff76eb831a0e788cb63940" - integrity sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ== - dependencies: - call-bind "^1.0.0" - define-properties "^1.1.3" - has-symbols "^1.0.1" - object-keys "^1.1.1" - -object.entries@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.3.tgz#c601c7f168b62374541a07ddbd3e2d5e4f7711a6" - integrity sha512-ym7h7OZebNS96hn5IJeyUmaWhaSM4SVtAPPfNLQEI2MYWCO2egsITb9nab2+i/Pwibx+R0mtn+ltKJXRSeTMGg== - dependencies: - call-bind "^1.0.0" - define-properties "^1.1.3" - es-abstract "^1.18.0-next.1" - has "^1.0.3" - semver@^7.1.1: version "7.3.5" resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7" @@ -194,29 +52,6 @@ semver@^7.1.1: dependencies: lru-cache "^6.0.0" -serialize-error@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/serialize-error/-/serialize-error-5.0.0.tgz#a7ebbcdb03a5d71a6ed8461ffe0fc1a1afed62ac" - integrity sha512-/VtpuyzYf82mHYTtI4QKtwHa79vAdU5OQpNPAmE/0UDdlGT0ZxHwC+J6gXkw29wwoVI8fMPsfcVHOwXtUQYYQA== - dependencies: - type-fest "^0.8.0" - -string.prototype.trimend@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.3.tgz#a22bd53cca5c7cf44d7c9d5c732118873d6cd18b" - integrity sha512-ayH0pB+uf0U28CtjlLvL7NaohvR1amUvVZk+y3DYb0Ey2PUV5zPkkKy9+U1ndVEIXO8hNg18eIv9Jntbii+dKw== - dependencies: - call-bind "^1.0.0" - define-properties "^1.1.3" - -string.prototype.trimstart@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.3.tgz#9b4cb590e123bb36564401d59824298de50fd5aa" - integrity sha512-oBIBUy5lea5tt0ovtOFiEQaBkoBBkyJhZXzJYrSmDo5IUUqbOPvVezuRs/agBIdZ2p2Eo1FD6bD9USyBLfl3xg== - dependencies: - call-bind "^1.0.0" - define-properties "^1.1.3" - thenify-all@^1.0.0: version "1.6.0" resolved "https://registry.yarnpkg.com/thenify-all/-/thenify-all-1.6.0.tgz#1a1918d402d8fc3f98fbf234db0bcc8cc10e9726" @@ -231,16 +66,6 @@ thenify-all@^1.0.0: dependencies: any-promise "^1.0.0" -type-fest@^0.8.0: - version "0.8.1" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" - integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== - -uuid@^3.0.1: - version "3.4.0" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" - integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== - yallist@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" diff --git a/terminus-settings/src/components/hotkeyInputModal.component.ts b/terminus-settings/src/components/hotkeyInputModal.component.ts index 6cc9991b..62d7755d 100644 --- a/terminus-settings/src/components/hotkeyInputModal.component.ts +++ b/terminus-settings/src/components/hotkeyInputModal.component.ts @@ -67,6 +67,7 @@ export class HotkeyInputModalComponent extends BaseComponent { } this.timeoutProgress = Math.min(100, (performance.now() - this.lastKeyEvent) * 100 / INPUT_TIMEOUT) if (this.timeoutProgress === 100) { + clearInterval(this.keyTimeoutInterval!) this.modalInstance.close(this.value) } }, 25) @@ -74,13 +75,14 @@ export class HotkeyInputModalComponent extends BaseComponent { } ngOnDestroy (): void { + clearInterval(this.keyTimeoutInterval!) this.hotkeys.clearCurrentKeystrokes() this.hotkeys.enable() - clearInterval(this.keyTimeoutInterval!) super.ngOnDestroy() } close (): void { + clearInterval(this.keyTimeoutInterval!) this.modalInstance.dismiss() } } diff --git a/terminus-settings/src/components/settingsTab.component.pug b/terminus-settings/src/components/settingsTab.component.pug index 47c45546..4acfd705 100644 --- a/terminus-settings/src/components/settingsTab.component.pug +++ b/terminus-settings/src/components/settingsTab.component.pug @@ -40,7 +40,7 @@ button.btn.btn-outline-warning.btn-block(*ngIf='config.restartRequested', '(clic i.fas.fa-sync span Update - .form-line(*ngIf='hostApp.platform !== Platform.Linux') + .form-line(*ngIf='platform.isShellIntegrationSupported()') .header .title Shell integration .description Allows quickly opening a terminal in the selected folder @@ -101,7 +101,10 @@ button.btn.btn-outline-warning.btn-block(*ngIf='config.restartRequested', '(clic button.btn.btn-primary(disabled, *ngIf='!isConfigFileValid()') i.fas.fa-exclamation-triangle.mr-2 | Invalid syntax - button.btn.btn-secondary.ml-auto((click)='showConfigFile()') + button.btn.btn-secondary.ml-auto( + *ngIf='platform.getConfigPath()', + (click)='showConfigFile()' + ) i.fas.fa-external-link-square-alt.mr-2 | Show config file diff --git a/terminus-settings/src/components/settingsTab.component.ts b/terminus-settings/src/components/settingsTab.component.ts index 532ea380..07371109 100644 --- a/terminus-settings/src/components/settingsTab.component.ts +++ b/terminus-settings/src/components/settingsTab.component.ts @@ -3,14 +3,13 @@ import * as yaml from 'js-yaml' import { debounce } from 'utils-decorators/dist/cjs' import { Component, Inject, Input, HostBinding, NgZone } from '@angular/core' import { - ElectronService, ConfigService, BaseTabComponent, HostAppService, Platform, HomeBaseService, - ShellIntegrationService, UpdaterService, + PlatformService, } from 'terminus-core' import { SettingsTabProvider } from '../api' @@ -35,10 +34,9 @@ export class SettingsTabComponent extends BaseTabComponent { constructor ( public config: ConfigService, - private electron: ElectronService, public hostApp: HostAppService, public homeBase: HomeBaseService, - public shellIntegration: ShellIntegrationService, + public platform: PlatformService, public zone: NgZone, private updater: UpdaterService, @Inject(SettingsTabProvider) public settingsProviders: SettingsTabProvider[], @@ -61,16 +59,16 @@ export class SettingsTabComponent extends BaseTabComponent { } async ngOnInit () { - this.isShellIntegrationInstalled = await this.shellIntegration.isInstalled() + this.isShellIntegrationInstalled = await this.platform.isShellIntegrationInstalled() } async toggleShellIntegration () { if (!this.isShellIntegrationInstalled) { - await this.shellIntegration.install() + await this.platform.installShellIntegration() } else { - await this.shellIntegration.remove() + await this.platform.uninstallShellIntegration() } - this.isShellIntegrationInstalled = await this.shellIntegration.isInstalled() + this.isShellIntegrationInstalled = await this.platform.isShellIntegrationInstalled() } ngOnDestroy () { @@ -96,7 +94,7 @@ export class SettingsTabComponent extends BaseTabComponent { } showConfigFile () { - this.electron.shell.showItemInFolder(this.config.path) + this.platform.showItemInFolder(this.platform.getConfigPath()!) } isConfigFileValid () { diff --git a/terminus-settings/src/components/windowSettingsTab.component.pug b/terminus-settings/src/components/windowSettingsTab.component.pug index d23455fe..394305f4 100644 --- a/terminus-settings/src/components/windowSettingsTab.component.pug +++ b/terminus-settings/src/components/windowSettingsTab.component.pug @@ -9,7 +9,7 @@ h3.mb-3 Window ) option(*ngFor='let theme of themes', [ngValue]='theme.name') {{theme.name}} -.form-line +.form-line(*ngIf='platform.supportsWindowControls') .header .title(*ngIf='hostApp.platform !== Platform.macOS') Acrylic background .title(*ngIf='hostApp.platform === Platform.macOS') Vibrancy @@ -43,7 +43,7 @@ h3.mb-3 Window ) | Fluent -.form-line +.form-line(*ngIf='platform.supportsWindowControls') .header .title Opacity input( @@ -55,7 +55,7 @@ h3.mb-3 Window step='0.01' ) -.form-line +.form-line(*ngIf='platform.supportsWindowControls') .header .title Window frame .description Whether a custom window or an OS native window should be used @@ -87,7 +87,7 @@ h3.mb-3 Window ) | Full -.form-line +.form-line(*ngIf='docking') .header .title Dock the terminal .description Snaps the window to a side of the screen @@ -133,7 +133,7 @@ h3.mb-3 Window ) | Bottom -.ml-5.form-line(*ngIf='config.store.appearance.dock != "off"') +.ml-5.form-line(*ngIf='docking && config.store.appearance.dock != "off"') .header .title Display on .description Snaps the window to a side of the screen @@ -158,7 +158,7 @@ h3.mb-3 Window ) | {{screen.name}} -.ml-5.form-line(*ngIf='config.store.appearance.dock != "off"') +.ml-5.form-line(*ngIf='docking && config.store.appearance.dock != "off"') .header .title Dock always on top .description Keep docked terminal always on top @@ -167,7 +167,7 @@ h3.mb-3 Window (ngModelChange)='saveConfiguration(); docking.dock()', ) -.ml-5.form-line(*ngIf='config.store.appearance.dock != "off"') +.ml-5.form-line(*ngIf='docking && config.store.appearance.dock != "off"') .header .title Docked terminal size input( @@ -179,7 +179,7 @@ h3.mb-3 Window step='0.01' ) -.ml-5.form-line(*ngIf='config.store.appearance.dock != "off"') +.ml-5.form-line(*ngIf='docking && config.store.appearance.dock != "off"') .header .title Docked terminal space input( @@ -191,7 +191,7 @@ h3.mb-3 Window step='0.01' ) -.ml-5.form-line(*ngIf='config.store.appearance.dock != "off"') +.ml-5.form-line(*ngIf='docking && config.store.appearance.dock != "off"') .header .title Hide dock on blur .description Hides the docked terminal when you click away. diff --git a/terminus-settings/src/components/windowSettingsTab.component.ts b/terminus-settings/src/components/windowSettingsTab.component.ts index c009390c..dea83c2a 100644 --- a/terminus-settings/src/components/windowSettingsTab.component.ts +++ b/terminus-settings/src/components/windowSettingsTab.component.ts @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/explicit-module-boundary-types */ import { debounce } from 'utils-decorators/dist/cjs' -import { Component, Inject, NgZone } from '@angular/core' +import { Component, Inject, NgZone, Optional } from '@angular/core' import { DockingService, ConfigService, @@ -10,6 +10,8 @@ import { isWindowsBuild, WIN_BUILD_FLUENT_BG_SUPPORTED, BaseComponent, + Screen, + PlatformService, } from 'terminus-core' @@ -19,24 +21,28 @@ import { template: require('./windowSettingsTab.component.pug'), }) export class WindowSettingsTabComponent extends BaseComponent { - screens: any[] + screens: Screen[] Platform = Platform isFluentVibrancySupported = false constructor ( public config: ConfigService, - public docking: DockingService, public hostApp: HostAppService, + public platform: PlatformService, public zone: NgZone, @Inject(Theme) public themes: Theme[], + @Optional() public docking?: DockingService, ) { super() - this.screens = this.docking.getScreens() + this.themes = config.enabledServices(this.themes) - this.subscribeUntilDestroyed(hostApp.displaysChanged$, () => { - this.zone.run(() => this.screens = this.docking.getScreens()) - }) + if (this.docking) { + this.subscribeUntilDestroyed(hostApp.displaysChanged$, () => { + this.zone.run(() => this.screens = this.docking!.getScreens()) + }) + this.screens = this.docking.getScreens() + } this.isFluentVibrancySupported = isWindowsBuild(WIN_BUILD_FLUENT_BG_SUPPORTED) } diff --git a/terminus-ssh/src/components/sshSettingsTab.component.pug b/terminus-ssh/src/components/sshSettingsTab.component.pug index 84988b6f..d68b1100 100644 --- a/terminus-ssh/src/components/sshSettingsTab.component.pug +++ b/terminus-ssh/src/components/sshSettingsTab.component.pug @@ -61,7 +61,7 @@ h3.mt-5 Options (ngModelChange)='config.save()', ) -.form-line +.form-line(*ngIf='hostApp.platform === Platform.Windows') .header .title WinSCP path .descriptions When WinSCP is detected, you can launch an SCP session from the context menu. diff --git a/terminus-ssh/src/components/sshSettingsTab.component.ts b/terminus-ssh/src/components/sshSettingsTab.component.ts index eea85b26..1f714b41 100644 --- a/terminus-ssh/src/components/sshSettingsTab.component.ts +++ b/terminus-ssh/src/components/sshSettingsTab.component.ts @@ -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 } from 'terminus-core' +import { ConfigService, ElectronService, HostAppService, Platform } from 'terminus-core' import { PasswordStorageService } from '../services/passwordStorage.service' import { SSHConnection } from '../api' import { EditConnectionModalComponent } from './editConnectionModal.component' @@ -23,11 +23,12 @@ export class SSHSettingsTabComponent { childGroups: SSHConnectionGroup[] groupCollapsed: Record = {} filter = '' + Platform = Platform constructor ( public config: ConfigService, + public hostApp: HostAppService, private electron: ElectronService, - private hostApp: HostAppService, private ngbModal: NgbModal, private passwordStorage: PasswordStorageService, ) { diff --git a/terminus-ssh/src/services/ssh.service.ts b/terminus-ssh/src/services/ssh.service.ts index 79c19b91..361ef69d 100644 --- a/terminus-ssh/src/services/ssh.service.ts +++ b/terminus-ssh/src/services/ssh.service.ts @@ -10,7 +10,7 @@ import { exec } from 'child_process' import * as path from 'path' import * as sshpk from 'sshpk' import { Subject, Observable } from 'rxjs' -import { HostAppService, Platform, Logger, LogService, AppService, SelectorOption, ConfigService, NotificationsService } from 'terminus-core' +import { HostAppService, Platform, Logger, LogService, AppService, SelectorOption, ConfigService, NotificationsService, PlatformService } from 'terminus-core' import { SettingsTabComponent } from 'terminus-settings' import { ALGORITHM_BLACKLIST, ForwardedPort, SSHConnection, SSHSession } from '../api' import { PromptModalComponent } from '../components/promptModal.component' @@ -20,15 +20,6 @@ import { ChildProcess } from 'node:child_process' const WINDOWS_OPENSSH_AGENT_PIPE = '\\\\.\\pipe\\openssh-ssh-agent' -try { - var windowsProcessTreeNative = require('windows-process-tree/build/Release/windows_process_tree.node') // eslint-disable-line @typescript-eslint/no-var-requires, no-var -} catch { } - - -// eslint-disable-next-line @typescript-eslint/no-type-alias -export type SSHLogCallback = (message: string) => void - - @Injectable({ providedIn: 'root' }) export class SSHService { private logger: Logger @@ -42,6 +33,7 @@ export class SSHService { private notifications: NotificationsService, private app: AppService, private config: ConfigService, + private platform: PlatformService, ) { this.logger = log.create('ssh') } @@ -197,13 +189,7 @@ export class SSHService { if (await fs.exists(WINDOWS_OPENSSH_AGENT_PIPE)) { agent = WINDOWS_OPENSSH_AGENT_PIPE } else { - // eslint-disable-next-line @typescript-eslint/no-shadow - const pageantRunning = await new Promise(resolve => { - windowsProcessTreeNative.getProcessList(list => { // eslint-disable-line block-scoped-var - resolve(list.some(x => x.name === 'pageant.exe')) - }, 0) - }) - if (pageantRunning) { + if (await this.platform.isProcessRunning('pageant.exe')) { agent = 'pageant' } } diff --git a/terminus-ssh/src/winSCPIntegration.ts b/terminus-ssh/src/winSCPIntegration.ts index 006f4ab1..2f57f27f 100644 --- a/terminus-ssh/src/winSCPIntegration.ts +++ b/terminus-ssh/src/winSCPIntegration.ts @@ -1,27 +1,20 @@ -import type { MenuItemConstructorOptions } from 'electron' -import { execFile } from 'child_process' import { Injectable } from '@angular/core' -import { ConfigService, BaseTabComponent, TabContextMenuItemProvider, TabHeaderComponent, HostAppService, Platform } from 'terminus-core' +import { ConfigService, BaseTabComponent, TabContextMenuItemProvider, TabHeaderComponent, HostAppService, Platform, PlatformService, MenuItemOptions } from 'terminus-core' import { SSHTabComponent } from './components/sshTab.component' import { PasswordStorageService } from './services/passwordStorage.service' import { SSHConnection } from './api' -/* eslint-disable block-scoped-var */ -try { - var wnr = require('windows-native-registry') // eslint-disable-line @typescript-eslint/no-var-requires, no-var -} catch { } - - /** @hidden */ @Injectable() export class WinSCPContextMenu extends TabContextMenuItemProvider { weight = 10 - private detectedPath?: string + private detectedPath: string | null constructor ( private hostApp: HostAppService, private config: ConfigService, + private platform: PlatformService, private passwordStorage: PasswordStorageService, ) { super() @@ -30,14 +23,10 @@ export class WinSCPContextMenu extends TabContextMenuItemProvider { return } - const key = wnr.getRegistryKey(wnr.HK.CR, 'WinSCP.Url\\DefaultIcon') - if (key?.['']) { - this.detectedPath = key[''].value?.split(',')[0] - this.detectedPath = this.detectedPath?.substring(1, this.detectedPath.length - 1) - } + this.detectedPath = platform.getWinSCPPath() } - async getItems (tab: BaseTabComponent, tabHeader?: TabHeaderComponent): Promise { + async getItems (tab: BaseTabComponent, tabHeader?: TabHeaderComponent): Promise { if (this.hostApp.platform !== Platform.Windows || tabHeader) { return [] } @@ -81,6 +70,6 @@ export class WinSCPContextMenu extends TabContextMenuItemProvider { args.push('/privatekey') args.push(connection.privateKey) } - execFile(path, args) + this.platform.exec(path, args) } } diff --git a/terminus-terminal/src/api/baseTerminalTab.component.ts b/terminus-terminal/src/api/baseTerminalTab.component.ts index ace6c63d..f9534e10 100644 --- a/terminus-terminal/src/api/baseTerminalTab.component.ts +++ b/terminus-terminal/src/api/baseTerminalTab.component.ts @@ -1,10 +1,9 @@ -import type { MenuItemConstructorOptions } from 'electron' import { Observable, Subject, Subscription } from 'rxjs' 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 } from 'terminus-core' +import { AppService, ConfigService, BaseTabComponent, ElectronService, HostAppService, HotkeysService, NotificationsService, Platform, LogService, Logger, TabContextMenuItemProvider, SplitTabComponent, SubscriptionContainer, MenuItemOptions, PlatformService } from 'terminus-core' import { BaseSession } from '../session' import { TerminalFrontendService } from '../services/terminalFrontend.service' @@ -84,6 +83,7 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit protected hostApp: HostAppService protected hotkeys: HotkeysService protected electron: ElectronService + protected platform: PlatformService protected terminalContainersService: TerminalFrontendService protected notifications: NotificationsService protected log: LogService @@ -136,6 +136,7 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit 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) this.log = injector.get(LogService) @@ -312,8 +313,8 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit } } - async buildContextMenu (): Promise { - let items: MenuItemConstructorOptions[] = [] + async buildContextMenu (): Promise { + let items: MenuItemOptions[] = [] for (const section of await Promise.all(this.contextMenuProviders.map(x => x.getItems(this)))) { items = items.concat(section) items.push({ type: 'separator' }) @@ -498,6 +499,16 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit this.termContainerSubscriptions.cancelAll() } + protected async handleRightClick (event: MouseEvent): Promise { + event.preventDefault() + event.stopPropagation() + if (this.config.store.terminal.rightClick === 'menu') { + this.platform.popupContextMenu(await this.buildContextMenu(), event) + } else if (this.config.store.terminal.rightClick === 'paste') { + this.paste() + } + } + protected attachTermContainerHandlers (): void { this.detachTermContainerHandlers() @@ -531,13 +542,7 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit return } if (event.which === 3 || event.which === 1 && event.ctrlKey) { - if (this.config.store.terminal.rightClick === 'menu') { - this.hostApp.popupContextMenu(await this.buildContextMenu()) - } else if (this.config.store.terminal.rightClick === 'paste') { - this.paste() - } - event.preventDefault() - event.stopPropagation() + this.handleRightClick(event) return } } diff --git a/terminus-terminal/src/api/contextMenuProvider.ts b/terminus-terminal/src/api/contextMenuProvider.ts index f524b2db..df26ac89 100644 --- a/terminus-terminal/src/api/contextMenuProvider.ts +++ b/terminus-terminal/src/api/contextMenuProvider.ts @@ -1,4 +1,4 @@ -import type { MenuItemConstructorOptions } from 'electron' +import type { MenuItemOptions } from 'terminus-core' import { BaseTerminalTabComponent } from './baseTerminalTab.component' /** @@ -8,5 +8,5 @@ import { BaseTerminalTabComponent } from './baseTerminalTab.component' export abstract class TerminalContextMenuItemProvider { weight: number - abstract getItems (tab: BaseTerminalTabComponent): Promise + abstract getItems (tab: BaseTerminalTabComponent): Promise } diff --git a/terminus-terminal/src/components/appearanceSettingsTab.component.ts b/terminus-terminal/src/components/appearanceSettingsTab.component.ts index b54acc53..9c961a8e 100644 --- a/terminus-terminal/src/components/appearanceSettingsTab.component.ts +++ b/terminus-terminal/src/components/appearanceSettingsTab.component.ts @@ -2,11 +2,9 @@ import { Observable } from 'rxjs' import { debounce } from 'utils-decorators/dist/cjs' import { debounceTime, distinctUntilChanged, map } from 'rxjs/operators' -import { exec } from 'mz/child_process' -const fontManager = require('fontmanager-redux') // eslint-disable-line import { Component } from '@angular/core' -import { ConfigService, HostAppService, Platform, getCSSFontFamily } from 'terminus-core' +import { ConfigService, getCSSFontFamily, PlatformService } from 'terminus-core' /** @hidden */ @Component({ @@ -17,26 +15,12 @@ export class AppearanceSettingsTabComponent { fonts: string[] = [] constructor ( - private hostApp: HostAppService, public config: ConfigService, + private platform: PlatformService, ) { } async ngOnInit () { - if (this.hostApp.platform === Platform.Windows || this.hostApp.platform === Platform.macOS) { - const fonts = await new Promise((resolve) => fontManager.findFonts({ monospace: true }, resolve)) - this.fonts = fonts.map(x => x.family.trim()) - this.fonts.sort() - } - if (this.hostApp.platform === Platform.Linux) { - exec('fc-list :spacing=mono').then(([stdout, _]) => { - this.fonts = stdout.toString() - .split('\n') - .filter(x => !!x) - .map(x => x.split(':')[1].trim()) - .map(x => x.split(',')[0].trim()) - this.fonts.sort() - }) - } + this.fonts = await this.platform.listFonts() } fontAutocomplete = (text$: Observable) => { diff --git a/terminus-terminal/src/components/terminalSettingsTab.component.pug b/terminus-terminal/src/components/terminalSettingsTab.component.pug index 2609d01c..0a965a5c 100644 --- a/terminus-terminal/src/components/terminalSettingsTab.component.pug +++ b/terminus-terminal/src/components/terminalSettingsTab.component.pug @@ -1,6 +1,6 @@ h3.mb-3 Terminal -.form-line +.form-line(*ngIf='hostApp.platform !== Platform.Web') .header .title Frontend .description Switches terminal frontend implementation (experimental) @@ -86,7 +86,7 @@ h3.mb-3 Terminal (ngModelChange)='config.save()', ) -.form-line +.form-line(*ngIf='hostApp.platform !== Platform.Web') .header .title Auto-open a terminal on app start diff --git a/terminus-terminal/src/components/terminalSettingsTab.component.ts b/terminus-terminal/src/components/terminalSettingsTab.component.ts index e8a6d234..f0ce9f1e 100644 --- a/terminus-terminal/src/components/terminalSettingsTab.component.ts +++ b/terminus-terminal/src/components/terminalSettingsTab.component.ts @@ -1,19 +1,22 @@ import { execFile } from 'mz/child_process' import { Component } from '@angular/core' -import { ConfigService, ElectronService } from 'terminus-core' +import { ConfigService, HostAppService, Platform, PlatformService } from 'terminus-core' /** @hidden */ @Component({ template: require('./terminalSettingsTab.component.pug'), }) export class TerminalSettingsTabComponent { + Platform = Platform + constructor ( public config: ConfigService, - private electron: ElectronService, + public hostApp: HostAppService, + private platform: PlatformService, ) { } openWSLVolumeMixer (): void { - this.electron.shell.openPath('sndvol.exe') + this.platform.openPath('sndvol.exe') execFile('wsl.exe', ['tput', 'bel']) } } diff --git a/terminus-terminal/src/frontends/frontend.ts b/terminus-terminal/src/frontends/frontend.ts index 712e666d..31495097 100644 --- a/terminus-terminal/src/frontends/frontend.ts +++ b/terminus-terminal/src/frontends/frontend.ts @@ -1,6 +1,6 @@ +import { Injector } from '@angular/core' import { Observable, Subject, AsyncSubject, ReplaySubject, BehaviorSubject } from 'rxjs' import { ResizeEvent } from '../api/interfaces' -import { ConfigService, ThemesService, HotkeysService } from 'terminus-core' export interface SearchOptions { regex?: boolean @@ -13,10 +13,6 @@ export interface SearchOptions { * Extend to add support for a different VT frontend implementation */ export abstract class Frontend { - configService: ConfigService - themesService: ThemesService - hotkeysService: HotkeysService - enableResizing = true protected ready = new AsyncSubject() protected title = new ReplaySubject(1) @@ -40,6 +36,8 @@ export abstract class Frontend { get dragOver$ (): Observable { return this.dragOver } get drop$ (): Observable { return this.drop } + constructor (protected injector: Injector) { } + destroy (): void { for (const o of [ this.ready, diff --git a/terminus-terminal/src/frontends/htermFrontend.ts b/terminus-terminal/src/frontends/htermFrontend.ts index efd77a96..3c9ce4ca 100644 --- a/terminus-terminal/src/frontends/htermFrontend.ts +++ b/terminus-terminal/src/frontends/htermFrontend.ts @@ -1,6 +1,7 @@ +import { Injector } from '@angular/core' +import { ConfigService, getCSSFontFamily, ThemesService } from 'terminus-core' import { Frontend, SearchOptions } from './frontend' import { hterm, preferenceManager } from './hterm' -import { getCSSFontFamily } from 'terminus-core' /** @hidden */ export class HTermFrontend extends Frontend { @@ -13,6 +14,15 @@ export class HTermFrontend extends Frontend { private configuredBackgroundColor = 'transparent' private zoom = 0 + private configService: ConfigService + private themesService: ThemesService + + constructor (injector: Injector) { + super(injector) + this.configService = injector.get(ConfigService) + this.themesService = injector.get(ThemesService) + } + async attach (host: HTMLElement): Promise { if (!this.initialized) { this.init() diff --git a/terminus-terminal/src/frontends/xtermFrontend.ts b/terminus-terminal/src/frontends/xtermFrontend.ts index 44a1fef7..10270a0d 100644 --- a/terminus-terminal/src/frontends/xtermFrontend.ts +++ b/terminus-terminal/src/frontends/xtermFrontend.ts @@ -1,4 +1,5 @@ -import { getCSSFontFamily } from 'terminus-core' +import { Injector } from '@angular/core' +import { ConfigService, getCSSFontFamily, HostAppService, HotkeysService, Platform, PlatformService } from 'terminus-core' import { Frontend, SearchOptions } from './frontend' import { Terminal, ITheme } from 'xterm' import { FitAddon } from 'xterm-addon-fit' @@ -39,8 +40,18 @@ export class XTermFrontend extends Frontend { private opened = false private resizeObserver?: any - constructor () { - super() + private configService: ConfigService + private hotkeysService: HotkeysService + private platformService: PlatformService + private hostApp: HostAppService + + constructor (injector: Injector) { + super(injector) + this.configService = injector.get(ConfigService) + this.hotkeysService = injector.get(HotkeysService) + this.platformService = injector.get(PlatformService) + this.hostApp = injector.get(HostAppService) + this.xterm = new Terminal({ allowTransparency: true, windowsMode: process.platform === 'win32', @@ -88,9 +99,11 @@ export class XTermFrontend extends Frontend { } this.xterm.attachCustomKeyEventHandler((event: KeyboardEvent) => { - if (event.getModifierState('Meta') && event.key.toLowerCase() === 'v') { - event.preventDefault() - return false + if (this.hostApp.platform !== Platform.Web) { + if (event.getModifierState('Meta') && event.key.toLowerCase() === 'v') { + event.preventDefault() + return false + } } if (event.getModifierState('Meta') && event.key.startsWith('Arrow')) { return false @@ -167,6 +180,10 @@ export class XTermFrontend extends Frontend { host.addEventListener('mousedown', event => this.mouseEvent.next(event)) host.addEventListener('mouseup', event => this.mouseEvent.next(event)) host.addEventListener('mousewheel', event => this.mouseEvent.next(event as MouseEvent)) + host.addEventListener('contextmenu', event => { + event.preventDefault() + event.stopPropagation() + }) this.resizeObserver = new window['ResizeObserver'](() => setTimeout(() => this.resizeHandler())) this.resizeObserver.observe(host) @@ -190,12 +207,12 @@ export class XTermFrontend extends Frontend { copySelection (): void { const text = this.getSelection() if (text.length < 1024 * 32) { - require('@electron/remote').clipboard.write({ + this.platformService.setClipboard({ text: this.getSelection(), html: this.getSelectionAsHTML(), }) } else { - require('@electron/remote').clipboard.write({ + this.platformService.setClipboard({ text: this.getSelection(), }) } diff --git a/terminus-terminal/src/index.ts b/terminus-terminal/src/index.ts index 78155ece..4d6b9fa5 100644 --- a/terminus-terminal/src/index.ts +++ b/terminus-terminal/src/index.ts @@ -25,7 +25,6 @@ import { PathDropDecorator } from './features/pathDrop' import { ZModemDecorator } from './features/zmodem' import { TerminalConfigProvider } from './config' import { TerminalHotkeyProvider } from './hotkeys' -import { HyperColorSchemes } from './colorSchemes' import { CopyPasteContextMenu, LegacyContextMenu } from './tabContextMenu' import { hterm } from './frontends/hterm' @@ -50,7 +49,6 @@ import { TerminalCLIHandler } from './cli' { provide: ConfigProvider, useClass: TerminalConfigProvider, multi: true }, { provide: HotkeyProvider, useClass: TerminalHotkeyProvider, multi: true }, - { provide: TerminalColorSchemeProvider, useClass: HyperColorSchemes, multi: true }, { provide: TerminalDecorator, useClass: PathDropDecorator, multi: true }, { provide: TerminalDecorator, useClass: ZModemDecorator, multi: true }, { provide: TerminalDecorator, useClass: DebugDecorator, multi: true }, diff --git a/terminus-terminal/src/services/terminalFrontend.service.ts b/terminus-terminal/src/services/terminalFrontend.service.ts index 38d172b9..6bed5c21 100644 --- a/terminus-terminal/src/services/terminalFrontend.service.ts +++ b/terminus-terminal/src/services/terminalFrontend.service.ts @@ -1,5 +1,5 @@ -import { Injectable } from '@angular/core' -import { ConfigService, ThemesService, HotkeysService } from 'terminus-core' +import { Injectable, Injector } from '@angular/core' +import { ConfigService } from 'terminus-core' import { Frontend } from '../frontends/frontend' import { HTermFrontend } from '../frontends/htermFrontend' import { XTermFrontend, XTermWebGLFrontend } from '../frontends/xtermFrontend' @@ -12,8 +12,7 @@ export class TerminalFrontendService { /** @hidden */ private constructor ( private config: ConfigService, - private themes: ThemesService, - private hotkeys: HotkeysService, + private injector: Injector, ) { } getFrontend (session?: BaseSession|null): Frontend { @@ -22,10 +21,7 @@ export class TerminalFrontendService { xterm: XTermFrontend, 'xterm-webgl': XTermWebGLFrontend, hterm: HTermFrontend, - }[this.config.store.terminal.frontend]() - frontend.configService = this.config - frontend.themesService = this.themes - frontend.hotkeysService = this.hotkeys + }[this.config.store.terminal.frontend](this.injector) return frontend } if (!this.containers.has(session)) { diff --git a/terminus-terminal/src/tabContextMenu.ts b/terminus-terminal/src/tabContextMenu.ts index f2076a6e..2896cc7d 100644 --- a/terminus-terminal/src/tabContextMenu.ts +++ b/terminus-terminal/src/tabContextMenu.ts @@ -1,6 +1,5 @@ -import { MenuItemConstructorOptions } from 'electron' import { Injectable, NgZone, Optional, Inject } from '@angular/core' -import { BaseTabComponent, TabContextMenuItemProvider, TabHeaderComponent, NotificationsService } from 'terminus-core' +import { BaseTabComponent, TabContextMenuItemProvider, TabHeaderComponent, NotificationsService, MenuItemOptions } from 'terminus-core' import { BaseTerminalTabComponent } from './api/baseTerminalTab.component' import { TerminalContextMenuItemProvider } from './api/contextMenuProvider' @@ -16,7 +15,7 @@ export class CopyPasteContextMenu extends TabContextMenuItemProvider { super() } - async getItems (tab: BaseTabComponent, tabHeader?: TabHeaderComponent): Promise { + async getItems (tab: BaseTabComponent, tabHeader?: TabHeaderComponent): Promise { if (tabHeader) { return [] } @@ -56,12 +55,12 @@ export class LegacyContextMenu extends TabContextMenuItemProvider { super() } - async getItems (tab: BaseTabComponent, _tabHeader?: TabHeaderComponent): Promise { + async getItems (tab: BaseTabComponent, _tabHeader?: TabHeaderComponent): Promise { if (!this.contextMenuProviders) { return [] } if (tab instanceof BaseTerminalTabComponent) { - let items: MenuItemConstructorOptions[] = [] + let items: MenuItemOptions[] = [] for (const p of this.contextMenuProviders) { items = items.concat(await p.getItems(tab)) } diff --git a/terminus-web/.gitignore b/terminus-web/.gitignore new file mode 100644 index 00000000..1521c8b7 --- /dev/null +++ b/terminus-web/.gitignore @@ -0,0 +1 @@ +dist diff --git a/terminus-web/package.json b/terminus-web/package.json new file mode 100644 index 00000000..a81a7098 --- /dev/null +++ b/terminus-web/package.json @@ -0,0 +1,27 @@ +{ + "name": "terminus-web", + "version": "1.0.135-nightly.0", + "description": "Web-specific bindings", + "keywords": [ + "terminus-builtin-plugin" + ], + "main": "dist/index.js", + "typings": "typings/index.d.ts", + "scripts": { + "build": "webpack --progress --color", + "watch": "webpack --progress --color --watch" + }, + "files": [ + "dist" + ], + "author": "Eugene Pankov", + "license": "MIT", + "peerDependencies": { + "@angular/core": "^9.1.9" + }, + "devDependencies": { + "@vaadin/vaadin-context-menu": "^5.0.0", + "bootstrap": "^4.1.3", + "copy-text-to-clipboard": "^3.0.1" + } +} diff --git a/terminus-web/src/index.ts b/terminus-web/src/index.ts new file mode 100644 index 00000000..10676b7c --- /dev/null +++ b/terminus-web/src/index.ts @@ -0,0 +1,17 @@ +import { NgModule } from '@angular/core' +import { LogService, PlatformService, UpdaterService } from 'terminus-core' + +import { WebPlatformService } from './platform' +import { ConsoleLogService } from './services/log.service' +import { NullUpdaterService } from './services/updater.service' + +import './styles.scss' + +@NgModule({ + providers: [ + { provide: PlatformService, useClass: WebPlatformService }, + { provide: LogService, useClass: ConsoleLogService }, + { provide: UpdaterService, useClass: NullUpdaterService }, + ], +}) +export default class WebModule { } // eslint-disable-line @typescript-eslint/no-extraneous-class diff --git a/terminus-web/src/platform.ts b/terminus-web/src/platform.ts new file mode 100644 index 00000000..aae00d84 --- /dev/null +++ b/terminus-web/src/platform.ts @@ -0,0 +1,76 @@ +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' + +// eslint-disable-next-line no-duplicate-imports +import type { ContextMenuElement, ContextMenuItem } from '@vaadin/vaadin-context-menu/vaadin-context-menu.js' + +import './styles.scss' + +@Injectable() +export class WebPlatformService extends PlatformService { + private menu: ContextMenuElement + private contextMenuHandlers = new Map void>() + + constructor () { + 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')) + } + + setClipboard (content: ClipboardContent): void { + copyToClipboard(content.text) + } + + async loadConfig (): Promise { + return window['terminusConfig'] + } + + async saveConfig (content: string): Promise { + console.log('config save', content) + } + + getOSRelease (): string { + return '1.0' + } + + openExternal (url: string): void { + window.open(url) + } + + getAppVersion (): string { + return '1.0-web' + } + + async listFonts (): Promise { + return [] + } + + popupContextMenu (menu: MenuItemOptions[], event?: MouseEvent): void { + this.contextMenuHandlers.clear() + this.menu.items = menu + .filter(x => x.type !== 'separator') + .map(x => this.remapMenuItem(x)) + setTimeout(() => { + this.menu.open(event) + }, 10) + } + + private remapMenuItem (item: MenuItemOptions): ContextMenuItem { + const cmi = { + text: item.label, + disabled: !(item.enabled ?? true), + checked: item.checked, + children: item.submenu?.map(i => this.remapMenuItem(i)), + } + if (item.click) { + this.contextMenuHandlers.set(cmi, item.click) + } + return cmi + } +} diff --git a/terminus-web/src/services/log.service.ts b/terminus-web/src/services/log.service.ts new file mode 100644 index 00000000..1bd76b23 --- /dev/null +++ b/terminus-web/src/services/log.service.ts @@ -0,0 +1,9 @@ +import { Injectable } from '@angular/core' +import { ConsoleLogger, Logger } from 'terminus-core' + +@Injectable({ providedIn: 'root' }) +export class ConsoleLogService { + create (name: string): Logger { + return new ConsoleLogger(name) + } +} diff --git a/terminus-web/src/services/updater.service.ts b/terminus-web/src/services/updater.service.ts new file mode 100644 index 00000000..3dcc2a26 --- /dev/null +++ b/terminus-web/src/services/updater.service.ts @@ -0,0 +1,10 @@ +import { UpdaterService } from 'terminus-core' + +export class NullUpdaterService extends UpdaterService { + async check (): Promise { + return false + } + + // eslint-disable-next-line @typescript-eslint/no-empty-function + async update (): Promise { } +} diff --git a/terminus-web/src/styles.scss b/terminus-web/src/styles.scss new file mode 100644 index 00000000..a85d8aef --- /dev/null +++ b/terminus-web/src/styles.scss @@ -0,0 +1,11 @@ +@import "../../terminus-core/src/theme.vars.scss"; + +html.terminus { + --lumo-primary-text-color: #{$body-color}; + --lumo-base-color: #{$body-bg}; + --lumo-body-text-color: #{$body-color}; + --lumo-tint-5pct: #{$body-bg}; + --lumo-font-family: #{$font-family-sans-serif}; + --lumo-font-size-m: #{$font-size-base}; + --lumo-box-shadow-m: #{$dropdown-box-shadow}; +} diff --git a/terminus-web/tsconfig.json b/terminus-web/tsconfig.json new file mode 100644 index 00000000..286cc9cd --- /dev/null +++ b/terminus-web/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../tsconfig.json", + "exclude": ["node_modules", "dist"], + "compilerOptions": { + "baseUrl": "src" + } +} diff --git a/terminus-web/tsconfig.typings.json b/terminus-web/tsconfig.typings.json new file mode 100644 index 00000000..c0d2273c --- /dev/null +++ b/terminus-web/tsconfig.typings.json @@ -0,0 +1,14 @@ +{ + "extends": "../tsconfig.json", + "exclude": ["node_modules", "dist", "typings"], + "compilerOptions": { + "baseUrl": "src", + "emitDeclarationOnly": true, + "declaration": true, + "declarationDir": "./typings", + "paths": { + "terminus-*": ["../../terminus-*"], + "*": ["../../app/node_modules/*"] + } + } +} diff --git a/terminus-web/webpack.config.js b/terminus-web/webpack.config.js new file mode 100644 index 00000000..23ecfa61 --- /dev/null +++ b/terminus-web/webpack.config.js @@ -0,0 +1,5 @@ +const config = require('../webpack.plugin.config') +module.exports = config({ + name: 'web', + dirname: __dirname, +}) diff --git a/terminus-web/yarn.lock b/terminus-web/yarn.lock new file mode 100644 index 00000000..e22b3d46 --- /dev/null +++ b/terminus-web/yarn.lock @@ -0,0 +1,178 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@polymer/iron-flex-layout@^3.0.0-pre.26": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@polymer/iron-flex-layout/-/iron-flex-layout-3.0.1.tgz#36f9e1a8eb792d279b2bc75d362628721ad37f0c" + integrity sha512-7gB869czArF+HZcPTVSgvA7tXYFze9EKckvM95NB7SqYF+NnsQyhoXgKnpFwGyo95lUjUW9TFDLUwDXnCYFtkw== + dependencies: + "@polymer/polymer" "^3.0.0" + +"@polymer/iron-icon@^3.0.0": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@polymer/iron-icon/-/iron-icon-3.0.1.tgz#93211c39d8825fe4965a68419566036c1df291eb" + integrity sha512-QLPwirk+UPZNaLnMew9VludXA4CWUCenRewgEcGYwdzVgDPCDbXxy6vRJjmweZobMQv/oVLppT2JZtJFnPxX6g== + dependencies: + "@polymer/iron-flex-layout" "^3.0.0-pre.26" + "@polymer/iron-meta" "^3.0.0-pre.26" + "@polymer/polymer" "^3.0.0" + +"@polymer/iron-iconset-svg@^3.0.0": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@polymer/iron-iconset-svg/-/iron-iconset-svg-3.0.1.tgz#568d6e7dbc120299dae63be3600aeba0d30ddbea" + integrity sha512-XNwURbNHRw6u2fJe05O5fMYye6GSgDlDqCO+q6K1zAnKIrpgZwf2vTkBd5uCcZwsN0FyCB3mvNZx4jkh85dRDw== + dependencies: + "@polymer/iron-meta" "^3.0.0-pre.26" + "@polymer/polymer" "^3.0.0" + +"@polymer/iron-media-query@^3.0.0": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@polymer/iron-media-query/-/iron-media-query-3.0.1.tgz#5cd8a1c1e8c9b8bafd3dd5da14e0f8d2cfa76d83" + integrity sha512-czUX1pm1zfmfcZtq5J57XFkcobBv08Y50exp0/3v8Bos5VL/jv2tU0RwiTfDBxUMhjicGbgwEBFQPY2V5DMzyw== + dependencies: + "@polymer/polymer" "^3.0.0" + +"@polymer/iron-meta@^3.0.0-pre.26": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@polymer/iron-meta/-/iron-meta-3.0.1.tgz#7f140628d127b0a284f882f1bb323a261bc125f5" + integrity sha512-pWguPugiLYmWFV9UWxLWzZ6gm4wBwQdDy4VULKwdHCqR7OP7u98h+XDdGZsSlDPv6qoryV/e3tGHlTIT0mbzJA== + dependencies: + "@polymer/polymer" "^3.0.0" + +"@polymer/polymer@^3.0.0": + version "3.4.1" + resolved "https://registry.yarnpkg.com/@polymer/polymer/-/polymer-3.4.1.tgz#333bef25711f8411bb5624fb3eba8212ef8bee96" + integrity sha512-KPWnhDZibtqKrUz7enIPOiO4ZQoJNOuLwqrhV2MXzIt3VVnUVJVG5ORz4Z2sgO+UZ+/UZnPD0jqY+jmw/+a9mQ== + dependencies: + "@webcomponents/shadycss" "^1.9.1" + +"@vaadin/vaadin-context-menu@^5.0.0": + version "5.0.0" + resolved "https://registry.yarnpkg.com/@vaadin/vaadin-context-menu/-/vaadin-context-menu-5.0.0.tgz#c8ef7a78f107c9824ef90c9331159d5f2818fdac" + integrity sha512-+OIFseHPRy1QraQFLUT/jxCKlvsOVg/NaaHhfonTZdwrO31CTpKGZFCDB0Gvos2W9WdXa6WI12DRJLZF7Wcr0g== + dependencies: + "@polymer/iron-media-query" "^3.0.0" + "@polymer/polymer" "^3.0.0" + "@vaadin/vaadin-element-mixin" "^2.4.1" + "@vaadin/vaadin-item" "^3.0.0" + "@vaadin/vaadin-list-box" "^2.0.0" + "@vaadin/vaadin-lumo-styles" "^1.6.1" + "@vaadin/vaadin-material-styles" "^1.3.2" + "@vaadin/vaadin-overlay" "^3.5.0" + "@vaadin/vaadin-themable-mixin" "^1.6.2" + +"@vaadin/vaadin-development-mode-detector@^2.0.0": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@vaadin/vaadin-development-mode-detector/-/vaadin-development-mode-detector-2.0.4.tgz#f49c8009856bead92d248377c36b295b5aae78e5" + integrity sha512-S+PaFrZpK8uBIOnIHxjntTrgumd5ztuCnZww96ydGKXgo9whXfZsbMwDuD/102a/IuPUMyF+dh/n3PbWzJ6igA== + +"@vaadin/vaadin-element-mixin@^2.4.0", "@vaadin/vaadin-element-mixin@^2.4.1": + version "2.4.2" + resolved "https://registry.yarnpkg.com/@vaadin/vaadin-element-mixin/-/vaadin-element-mixin-2.4.2.tgz#3c8040a8e756bc274b7777723b1fba2b9895cd41" + integrity sha512-VSDVK0XUsFe/RohpwSzQwgqb2Pwpok6sDNhIDS4CARr3HPhq2voMzT/FowFbkEy0J1hFtN/ZfC7tkv3kdEKKIQ== + dependencies: + "@polymer/polymer" "^3.0.0" + "@vaadin/vaadin-development-mode-detector" "^2.0.0" + "@vaadin/vaadin-usage-statistics" "^2.1.0" + +"@vaadin/vaadin-item@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@vaadin/vaadin-item/-/vaadin-item-3.0.0.tgz#abbeadd752dd46351217b94351c05bf93d6fad1c" + integrity sha512-AcSqaOd2LJr51JWT3j7GcdbU54oBHAE8xlfeN0O5OdCcsAQJLekkNJ3uxt8Kr3ZP99nnEFTZ1WKcQtEufSAVhA== + dependencies: + "@polymer/polymer" "^3.0.0" + "@vaadin/vaadin-element-mixin" "^2.4.1" + "@vaadin/vaadin-lumo-styles" "^1.6.1" + "@vaadin/vaadin-material-styles" "^1.3.2" + "@vaadin/vaadin-themable-mixin" "^1.6.2" + +"@vaadin/vaadin-list-box@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@vaadin/vaadin-list-box/-/vaadin-list-box-2.0.0.tgz#783e1abf1dd50609a7a00a6de2acd2394a1d808e" + integrity sha512-3WU7oU3cgrp7jPet1aAjAIJSQqdVbKAqIPxOH3LsLX7QQAYnWvUwQY+UApPHiJIjpnKF0PfYiIZe1o6adqKivg== + dependencies: + "@polymer/polymer" "^3.0.0" + "@vaadin/vaadin-element-mixin" "^2.4.1" + "@vaadin/vaadin-item" "^3.0.0" + "@vaadin/vaadin-list-mixin" "^2.5.0" + "@vaadin/vaadin-lumo-styles" "^1.6.1" + "@vaadin/vaadin-material-styles" "^1.3.2" + "@vaadin/vaadin-themable-mixin" "^1.6.1" + +"@vaadin/vaadin-list-mixin@^2.5.0": + version "2.5.1" + resolved "https://registry.yarnpkg.com/@vaadin/vaadin-list-mixin/-/vaadin-list-mixin-2.5.1.tgz#f6ab60cc658900d3eb7bfff18cf42d769374b659" + integrity sha512-XcMzQ0hJnK/AAiV+bW95nwJgmMIrXUBiSDwM+uvfurcBKqPyM4pm3sj8imh8zXSTfpN4HSjMnrLWU1ZfR330vg== + dependencies: + "@polymer/polymer" "^3.0.0" + "@vaadin/vaadin-element-mixin" "^2.4.1" + +"@vaadin/vaadin-lumo-styles@^1.3.0", "@vaadin/vaadin-lumo-styles@^1.6.1": + version "1.6.1" + resolved "https://registry.yarnpkg.com/@vaadin/vaadin-lumo-styles/-/vaadin-lumo-styles-1.6.1.tgz#2099227b0f646ead16f7289e704b6a793594bf5c" + integrity sha512-Yh9ZcekpY7byXP1QJnfx94rVvK71xHBEspsVV7LL7YMvqXU4EAYuzQGYsljryV4PGS9PFPD6sqbGqhEkIhHPnQ== + dependencies: + "@polymer/iron-icon" "^3.0.0" + "@polymer/iron-iconset-svg" "^3.0.0" + "@polymer/polymer" "^3.0.0" + +"@vaadin/vaadin-material-styles@^1.2.0", "@vaadin/vaadin-material-styles@^1.3.2": + version "1.3.2" + resolved "https://registry.yarnpkg.com/@vaadin/vaadin-material-styles/-/vaadin-material-styles-1.3.2.tgz#d2c1bd290db16721152ae672dbe052c381686696" + integrity sha512-EFrvGScoxhLNrPnWtT2Ia77whjF2TD4jrcyeh1jv9joCA2n5SUba+4XJciVSGmopqqQato6lwRnZSvMLJX7cyw== + dependencies: + "@polymer/polymer" "^3.0.0" + +"@vaadin/vaadin-overlay@^3.5.0": + version "3.5.1" + resolved "https://registry.yarnpkg.com/@vaadin/vaadin-overlay/-/vaadin-overlay-3.5.1.tgz#c4391b3c6c1f7a512b0a6f0dd96f11480feed402" + integrity sha512-0g+poK/BXF92L2lSKrHMY5rcKzUxCBZNzP/NDwgi4a86nbjL7CAKKZdno7Yl+j8UsTR76nOEw4fAYTFi86B0qg== + dependencies: + "@polymer/polymer" "^3.0.0" + "@vaadin/vaadin-element-mixin" "^2.4.0" + "@vaadin/vaadin-lumo-styles" "^1.3.0" + "@vaadin/vaadin-material-styles" "^1.2.0" + "@vaadin/vaadin-themable-mixin" "^1.6.1" + +"@vaadin/vaadin-themable-mixin@^1.6.1", "@vaadin/vaadin-themable-mixin@^1.6.2": + version "1.6.2" + resolved "https://registry.yarnpkg.com/@vaadin/vaadin-themable-mixin/-/vaadin-themable-mixin-1.6.2.tgz#8d619722819ba850af777579a550ff8b1d2b960f" + integrity sha512-PZZOZnke3KUlZsDrRVbWxAGEeFBPRyRayNRCvip0XnQK+Zs3cLuRgdgbdro3Ir9LZ3Izsw6HqA6XNMKffEP67A== + dependencies: + "@polymer/polymer" "^3.0.0" + lit-element "^2.0.0" + +"@vaadin/vaadin-usage-statistics@^2.1.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@vaadin/vaadin-usage-statistics/-/vaadin-usage-statistics-2.1.0.tgz#9c0fd71dded80f401bcdfbcb3f45b5640fc4256d" + integrity sha512-e81nbqY5zsaYhLJuOVkJkB/Um1pGK5POIqIlTNhUfjeoyGaJ63tiX8+D5n6F+GgVxUTLUarsKa6SKRcQel0AzA== + dependencies: + "@vaadin/vaadin-development-mode-detector" "^2.0.0" + +"@webcomponents/shadycss@^1.9.1": + version "1.10.2" + resolved "https://registry.yarnpkg.com/@webcomponents/shadycss/-/shadycss-1.10.2.tgz#40e03cab6dc5e12f199949ba2b79e02f183d1e7b" + integrity sha512-9Iseu8bRtecb0klvv+WXZOVZatsRkbaH7M97Z+f+Pt909R4lDfgUODAnra23DOZTpeMTAkVpf4m/FZztN7Ox1A== + +bootstrap@^4.1.3: + version "4.6.0" + resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-4.6.0.tgz#97b9f29ac98f98dfa43bf7468262d84392552fd7" + integrity sha512-Io55IuQY3kydzHtbGvQya3H+KorS/M9rSNyfCGCg9WZ4pyT/lCxIlpJgG1GXW/PswzC84Tr2fBYi+7+jFVQQBw== + +copy-text-to-clipboard@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/copy-text-to-clipboard/-/copy-text-to-clipboard-3.0.1.tgz#8cbf8f90e0a47f12e4a24743736265d157bce69c" + integrity sha512-rvVsHrpFcL4F2P8ihsoLdFHmd404+CMg71S756oRSeQgqk51U3kicGdnvfkrxva0xXH92SjGS62B0XIJsbh+9Q== + +lit-element@^2.0.0: + version "2.5.1" + resolved "https://registry.yarnpkg.com/lit-element/-/lit-element-2.5.1.tgz#3fa74b121a6cd22902409ae3859b7847d01aa6b6" + integrity sha512-ogu7PiJTA33bEK0xGu1dmaX5vhcRjBXCFexPja0e7P7jqLhTpNKYRPmE+GmiCaRVAbiQKGkUgkh/i6+bh++dPQ== + dependencies: + lit-html "^1.1.1" + +lit-html@^1.1.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/lit-html/-/lit-html-1.4.1.tgz#0c6f3ee4ad4eb610a49831787f0478ad8e9ae5e0" + integrity sha512-B9btcSgPYb1q4oSOb/PrOT6Z/H+r6xuNzfH4lFli/AWhYwdtrgQkQWBbIc6mdnf6E2IL3gDXdkkqNktpU0OZQA== diff --git a/tsconfig.json b/tsconfig.json index ce8c742c..6adbaf95 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -23,7 +23,8 @@ "es6", "es7", "es2015", - "es2017" + "es2017", + "es2019" ], "paths": { "terminus-*": ["../../terminus-*/src"] diff --git a/webpack.config.js b/webpack.config.js index cd5f868f..0dc065c3 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -2,6 +2,8 @@ module.exports = [ require('./app/webpack.config.js'), require('./app/webpack.main.config.js'), require('./terminus-core/webpack.config.js'), + require('./terminus-electron/webpack.config.js'), + require('./terminus-web/webpack.config.js'), require('./terminus-settings/webpack.config.js'), require('./terminus-terminal/webpack.config.js'), require('./terminus-local/webpack.config.js'), diff --git a/webpack.plugin.config.js b/webpack.plugin.config.js index d4f0f68a..2fc85b41 100644 --- a/webpack.plugin.config.js +++ b/webpack.plugin.config.js @@ -43,6 +43,7 @@ module.exports = options => { }, module: { rules: [ + ...options.rules ?? [], { test: /\.ts$/, use: { @@ -64,7 +65,8 @@ module.exports = options => { }, }, { test: /\.pug$/, use: ['apply-loader', 'pug-loader'] }, - { test: /\.scss$/, use: ['@terminus-term/to-string-loader', 'css-loader', 'sass-loader'] }, + { test: /\.scss$/, use: ['@terminus-term/to-string-loader', 'css-loader', 'sass-loader'], include: /(theme.*|component)\.scss/ }, + { test: /\.scss$/, use: ['style-loader', 'css-loader', 'sass-loader'], exclude: /(theme.*|component)\.scss/ }, { test: /\.css$/, use: ['@terminus-term/to-string-loader', 'css-loader'], include: /component\.css/ }, { test: /\.css$/, use: ['style-loader', 'css-loader'], exclude: /component\.css/ }, { test: /\.yaml$/, use: ['json-loader', 'yaml-loader'] }, @@ -81,6 +83,7 @@ module.exports = options => { ], }, externals: [ + '@electron/remote', 'any-promise', 'child_process', 'electron-promise-ipc',