mirror of
https://github.com/Eugeny/tabby.git
synced 2025-09-01 22:21:50 +00:00
Compare commits
67 Commits
v1.0.0-alp
...
v1.0.0-alp
Author | SHA1 | Date | |
---|---|---|---|
![]() |
62bf681598 | ||
![]() |
e403a423b7 | ||
![]() |
6b49cdc974 | ||
![]() |
5fe71b8169 | ||
![]() |
6bc2d18f3c | ||
![]() |
6fa5ab5eb2 | ||
![]() |
2411713501 | ||
![]() |
947e0f8b66 | ||
![]() |
f8bc94fe78 | ||
![]() |
5a32a45b19 | ||
![]() |
62222e67fb | ||
![]() |
6aab782326 | ||
![]() |
cec349d021 | ||
![]() |
c8b40647a9 | ||
![]() |
67ed830e97 | ||
![]() |
975d4d62ef | ||
![]() |
275791517b | ||
![]() |
9db452f489 | ||
![]() |
5094262e68 | ||
![]() |
17cf0f59c9 | ||
![]() |
aa805b912b | ||
![]() |
4b3cbc5639 | ||
![]() |
6a59db1a36 | ||
![]() |
2d43e29bcd | ||
![]() |
bf5e460bca | ||
![]() |
d574f634c9 | ||
![]() |
4ceb8ff897 | ||
![]() |
0f8c27e536 | ||
![]() |
d6fb71dca2 | ||
![]() |
38e450f70a | ||
![]() |
18c0a585a2 | ||
![]() |
8a1c33b82a | ||
![]() |
f81bda1686 | ||
![]() |
c285b89b6c | ||
![]() |
6dc46bb970 | ||
![]() |
7d25816751 | ||
![]() |
d6f163b048 | ||
![]() |
f357dab8f0 | ||
![]() |
0eaf857b02 | ||
![]() |
f367ea6c74 | ||
![]() |
dc4b984ed0 | ||
![]() |
4b7b692ace | ||
![]() |
0749096d9f | ||
![]() |
5b76947d70 | ||
![]() |
98a7801803 | ||
![]() |
ce2c72393b | ||
![]() |
2ea2c02845 | ||
![]() |
4b1ba7863f | ||
![]() |
2e558e2aa2 | ||
![]() |
a98f2ce12d | ||
![]() |
046ef239db | ||
![]() |
6cc20c3719 | ||
![]() |
3f8f87a141 | ||
![]() |
5f337e6dbe | ||
![]() |
7af14c5699 | ||
![]() |
d3a5c7be8d | ||
![]() |
8aff33d59c | ||
![]() |
7f45bb57fc | ||
![]() |
06d14f9bb2 | ||
![]() |
64f670bd86 | ||
![]() |
c9dde2e29c | ||
![]() |
46d8533fee | ||
![]() |
627d7402ca | ||
![]() |
a10c6e6251 | ||
![]() |
594597e93d | ||
![]() |
2dded5ddb6 | ||
![]() |
c4415577fa |
55
.eslintrc.yml
Normal file
55
.eslintrc.yml
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
parser: babel-eslint
|
||||||
|
extends: standard
|
||||||
|
env:
|
||||||
|
node: true
|
||||||
|
commonjs: true
|
||||||
|
rules:
|
||||||
|
no-duplicate-imports: error
|
||||||
|
import/no-duplicates: 0
|
||||||
|
array-bracket-spacing:
|
||||||
|
- error
|
||||||
|
- never
|
||||||
|
block-scoped-var: error
|
||||||
|
brace-style:
|
||||||
|
- error
|
||||||
|
- 1tbs
|
||||||
|
computed-property-spacing:
|
||||||
|
- error
|
||||||
|
- never
|
||||||
|
comma-dangle:
|
||||||
|
- error
|
||||||
|
- always-multiline
|
||||||
|
curly: error
|
||||||
|
eol-last: error
|
||||||
|
eqeqeq:
|
||||||
|
- error
|
||||||
|
- smart
|
||||||
|
max-depth:
|
||||||
|
- 1
|
||||||
|
- 5
|
||||||
|
max-statements:
|
||||||
|
- 1
|
||||||
|
- 80
|
||||||
|
no-multiple-empty-lines: error
|
||||||
|
no-mixed-spaces-and-tabs: error
|
||||||
|
no-trailing-spaces: error
|
||||||
|
no-unused-vars:
|
||||||
|
- error
|
||||||
|
- vars: all
|
||||||
|
args: after-used
|
||||||
|
argsIgnorePattern: ^_
|
||||||
|
no-undef: error
|
||||||
|
no-use-before-define:
|
||||||
|
- error
|
||||||
|
- nofunc
|
||||||
|
no-var: error
|
||||||
|
object-curly-spacing:
|
||||||
|
- error
|
||||||
|
- always
|
||||||
|
quote-props:
|
||||||
|
- warn
|
||||||
|
- as-needed
|
||||||
|
- keywords: true
|
||||||
|
numbers: true
|
||||||
|
strict:
|
||||||
|
- error
|
18
.github/stale.yml
vendored
Normal file
18
.github/stale.yml
vendored
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# Number of days of inactivity before an issue becomes stale
|
||||||
|
daysUntilStale: 60
|
||||||
|
# Number of days of inactivity before a stale issue is closed
|
||||||
|
daysUntilClose: 14
|
||||||
|
# Issues with these labels will never be considered stale
|
||||||
|
exemptLabels:
|
||||||
|
- "T: Enhancement"
|
||||||
|
- "S: Triaged"
|
||||||
|
# Label to use when marking an issue as stale
|
||||||
|
staleLabel: "S: Stale"
|
||||||
|
# Comment to post when marking an issue as stale. Set to `false` to disable
|
||||||
|
markComment: >
|
||||||
|
This issue has been automatically marked as stale because it has not had
|
||||||
|
recent activity. It will be closed in two weeks unless you comment.
|
||||||
|
|
||||||
|
Thank you for your contributions.
|
||||||
|
# Comment to post when closing a stale issue. Set to `false` to disable
|
||||||
|
closeComment: false
|
@@ -6,7 +6,7 @@ matrix:
|
|||||||
env: BUILD_FOR=macos
|
env: BUILD_FOR=macos
|
||||||
|
|
||||||
language: node_js
|
language: node_js
|
||||||
node_js: 7
|
node_js: 8
|
||||||
|
|
||||||
cache:
|
cache:
|
||||||
directories:
|
directories:
|
||||||
|
@@ -26,8 +26,9 @@ Plugins can be installed directly from the Settings view inside Terminus.
|
|||||||
* [clickable-links](https://github.com/Eugeny/terminus-clickable-links) - makes paths and URLs in the terminal clickable
|
* [clickable-links](https://github.com/Eugeny/terminus-clickable-links) - makes paths and URLs in the terminal clickable
|
||||||
* [theme-hype](https://github.com/Eugeny/terminus-theme-hype) - a Hyper inspired theme
|
* [theme-hype](https://github.com/Eugeny/terminus-theme-hype) - a Hyper inspired theme
|
||||||
* [shell-selector](https://github.com/Eugeny/terminus-shell-selector) - a quick shell selector pane
|
* [shell-selector](https://github.com/Eugeny/terminus-shell-selector) - a quick shell selector pane
|
||||||
* [title-control](https://github.com/kbjr/terminus-scrollbar) - allows modifying the title of the terminal tabs by providing a prefix, suffix, and/or strings to be removed
|
* [title-control](https://github.com/kbjr/terminus-title-control) - allows modifying the title of the terminal tabs by providing a prefix, suffix, and/or strings to be removed
|
||||||
* [scrollbar](https://github.com/kbjr/terminus-scrollbar) - adds a scrollbar to terminal tabs
|
* [scrollbar](https://github.com/kbjr/terminus-scrollbar) - adds a scrollbar to terminal tabs
|
||||||
|
* [quick-cmds](https://github.com/Domain/terminus-quick-cmds) - quicklky send commands to one or all terminal tabs
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
172
app/lib/app.ts
Normal file
172
app/lib/app.ts
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
import { app, ipcMain, Menu, Tray, shell } from 'electron'
|
||||||
|
import { Window } from './window'
|
||||||
|
|
||||||
|
export class Application {
|
||||||
|
private tray: Tray
|
||||||
|
private windows: Window[] = []
|
||||||
|
|
||||||
|
constructor () {
|
||||||
|
ipcMain.on('app:config-change', () => {
|
||||||
|
this.broadcast('host:config-change')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async newWindow (): Promise<Window> {
|
||||||
|
let window = new Window()
|
||||||
|
this.windows.push(window)
|
||||||
|
window.visible$.subscribe(visible => {
|
||||||
|
if (visible) {
|
||||||
|
this.disableTray()
|
||||||
|
} else {
|
||||||
|
this.enableTray()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (process.platform === 'darwin') {
|
||||||
|
this.setupMenu()
|
||||||
|
}
|
||||||
|
await window.ready
|
||||||
|
return window
|
||||||
|
}
|
||||||
|
|
||||||
|
broadcast (event, ...args) {
|
||||||
|
for (let window of this.windows) {
|
||||||
|
window.send(event, ...args)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async send (event, ...args) {
|
||||||
|
if (!this.hasWindows()) {
|
||||||
|
await this.newWindow()
|
||||||
|
}
|
||||||
|
this.windows[0].send(event, ...args)
|
||||||
|
}
|
||||||
|
|
||||||
|
enableTray () {
|
||||||
|
if (this.tray) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (process.platform === 'darwin') {
|
||||||
|
this.tray = new Tray(`${app.getAppPath()}/assets/tray-darwinTemplate.png`)
|
||||||
|
this.tray.setPressedImage(`${app.getAppPath()}/assets/tray-darwinHighlightTemplate.png`)
|
||||||
|
} else {
|
||||||
|
this.tray = new Tray(`${app.getAppPath()}/assets/tray.png`)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.tray.on('click', () => this.focus())
|
||||||
|
|
||||||
|
const contextMenu = Menu.buildFromTemplate([{
|
||||||
|
label: 'Show',
|
||||||
|
click: () => this.focus(),
|
||||||
|
}])
|
||||||
|
|
||||||
|
if (process.platform !== 'darwin') {
|
||||||
|
this.tray.setContextMenu(contextMenu)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.tray.setToolTip(`Terminus ${app.getVersion()}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
disableTray () {
|
||||||
|
if (this.tray) {
|
||||||
|
this.tray.destroy()
|
||||||
|
this.tray = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hasWindows () {
|
||||||
|
return !!this.windows.length
|
||||||
|
}
|
||||||
|
|
||||||
|
focus () {
|
||||||
|
for (let window of this.windows) {
|
||||||
|
window.show()
|
||||||
|
window.focus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private setupMenu () {
|
||||||
|
let template: Electron.MenuItemConstructorOptions[] = [
|
||||||
|
{
|
||||||
|
label: 'Application',
|
||||||
|
submenu: [
|
||||||
|
{ role: 'about', label: 'About Terminus' },
|
||||||
|
{ type: 'separator' },
|
||||||
|
{
|
||||||
|
label: 'Preferences',
|
||||||
|
accelerator: 'Cmd+,',
|
||||||
|
async click () {
|
||||||
|
if (!this.hasWindows()) {
|
||||||
|
await this.newWindow()
|
||||||
|
}
|
||||||
|
this.windows[0].send('host:preferences-menu')
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ type: 'separator' },
|
||||||
|
{ role: 'services', submenu: [] },
|
||||||
|
{ type: 'separator' },
|
||||||
|
{ role: 'hide' },
|
||||||
|
{ role: 'hideothers' },
|
||||||
|
{ role: 'unhide' },
|
||||||
|
{ type: 'separator' },
|
||||||
|
{
|
||||||
|
label: 'Quit',
|
||||||
|
accelerator: 'Cmd+Q',
|
||||||
|
click () {
|
||||||
|
app.quit()
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Edit',
|
||||||
|
submenu: [
|
||||||
|
{ role: 'undo' },
|
||||||
|
{ role: 'redo' },
|
||||||
|
{ type: 'separator' },
|
||||||
|
{ role: 'cut' },
|
||||||
|
{ role: 'copy' },
|
||||||
|
{ role: 'paste' },
|
||||||
|
{ role: 'pasteandmatchstyle' },
|
||||||
|
{ role: 'delete' },
|
||||||
|
{ role: 'selectall' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'View',
|
||||||
|
submenu: [
|
||||||
|
{ role: 'reload' },
|
||||||
|
{ role: 'forcereload' },
|
||||||
|
{ role: 'toggledevtools' },
|
||||||
|
{ type: 'separator' },
|
||||||
|
{ role: 'resetzoom' },
|
||||||
|
{ role: 'zoomin' },
|
||||||
|
{ role: 'zoomout' },
|
||||||
|
{ type: 'separator' },
|
||||||
|
{ role: 'togglefullscreen' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'window',
|
||||||
|
submenu: [
|
||||||
|
{ role: 'minimize' },
|
||||||
|
{ role: 'zoom' },
|
||||||
|
{ type: 'separator' },
|
||||||
|
{ role: 'front' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'help',
|
||||||
|
submenu: [
|
||||||
|
{
|
||||||
|
label: 'Website',
|
||||||
|
click () {
|
||||||
|
shell.openExternal('https://eugeny.github.io/terminus')
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
Menu.setApplicationMenu(Menu.buildFromTemplate(template))
|
||||||
|
}
|
||||||
|
}
|
23
app/lib/cli.ts
Normal file
23
app/lib/cli.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { app } from 'electron'
|
||||||
|
|
||||||
|
export function parseArgs (argv, cwd) {
|
||||||
|
if (argv[0].includes('node')) {
|
||||||
|
argv = argv.slice(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return require('yargs')
|
||||||
|
.usage('terminus [command] [arguments]')
|
||||||
|
.command('open [directory]', 'open a shell in a directory', {
|
||||||
|
directory: { type: 'string', 'default': cwd },
|
||||||
|
})
|
||||||
|
.command('run [command...]', 'run a command in the terminal', {
|
||||||
|
command: { type: 'string' },
|
||||||
|
})
|
||||||
|
.version('v', 'Show version and exit', app.getVersion())
|
||||||
|
.alias('d', 'debug')
|
||||||
|
.describe('d', 'Show DevTools on start')
|
||||||
|
.alias('h', 'help')
|
||||||
|
.help('h')
|
||||||
|
.strict()
|
||||||
|
.parse(argv.slice(1))
|
||||||
|
}
|
65
app/lib/index.ts
Normal file
65
app/lib/index.ts
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
import './lru'
|
||||||
|
import { app, ipcMain, Menu } from 'electron'
|
||||||
|
import electronDebug = require('electron-debug')
|
||||||
|
import { parseArgs } from './cli'
|
||||||
|
import { Application } from './app'
|
||||||
|
if (process.platform === 'win32' && require('electron-squirrel-startup')) process.exit(0)
|
||||||
|
|
||||||
|
if (!process.env.TERMINUS_PLUGINS) {
|
||||||
|
process.env.TERMINUS_PLUGINS = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const application = new Application()
|
||||||
|
|
||||||
|
app.commandLine.appendSwitch('disable-http-cache')
|
||||||
|
|
||||||
|
ipcMain.on('app:new-window', () => {
|
||||||
|
console.log('new-window')
|
||||||
|
application.newWindow()
|
||||||
|
})
|
||||||
|
|
||||||
|
app.on('activate', () => {
|
||||||
|
if (!application.hasWindows()) {
|
||||||
|
application.newWindow()
|
||||||
|
} else {
|
||||||
|
application.focus()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
app.on('window-all-closed', () => {
|
||||||
|
app.quit()
|
||||||
|
})
|
||||||
|
|
||||||
|
process.on('uncaughtException' as any, err => {
|
||||||
|
console.log(err)
|
||||||
|
application.broadcast('uncaughtException', err)
|
||||||
|
})
|
||||||
|
|
||||||
|
app.on('second-instance', (_event, argv, cwd) => {
|
||||||
|
application.send('host:second-instance', parseArgs(argv, cwd), cwd)
|
||||||
|
})
|
||||||
|
|
||||||
|
const argv = parseArgs(process.argv, process.cwd())
|
||||||
|
|
||||||
|
if (!app.requestSingleInstanceLock()) {
|
||||||
|
app.quit()
|
||||||
|
process.exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (argv.d) {
|
||||||
|
electronDebug({ enabled: true, showDevTools: 'undocked' })
|
||||||
|
}
|
||||||
|
|
||||||
|
app.on('ready', () => {
|
||||||
|
if (process.platform === 'darwin') {
|
||||||
|
app.dock.setMenu(Menu.buildFromTemplate([
|
||||||
|
{
|
||||||
|
label: 'New window',
|
||||||
|
click () {
|
||||||
|
this.app.newWindow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]))
|
||||||
|
}
|
||||||
|
application.newWindow()
|
||||||
|
})
|
15
app/lib/lru.ts
Normal file
15
app/lib/lru.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
let lru = require('lru-cache')({ max: 256, maxAge: 250 })
|
||||||
|
|
||||||
|
let fs = require('fs')
|
||||||
|
let origLstat = fs.realpathSync.bind(fs)
|
||||||
|
|
||||||
|
// NB: The biggest offender of thrashing realpathSync is the node module system
|
||||||
|
// itself, which we can't get into via any sane means.
|
||||||
|
require('fs').realpathSync = function (p) {
|
||||||
|
let r = lru.get(p)
|
||||||
|
if (r) return r
|
||||||
|
|
||||||
|
r = origLstat(p)
|
||||||
|
lru.set(p, r)
|
||||||
|
return r
|
||||||
|
}
|
201
app/lib/window.ts
Normal file
201
app/lib/window.ts
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
import { Subject, Observable } from 'rxjs'
|
||||||
|
import { BrowserWindow, app, ipcMain, Rectangle, Menu } from 'electron'
|
||||||
|
import ElectronConfig = require('electron-config')
|
||||||
|
import * as yaml from 'js-yaml'
|
||||||
|
import * as fs from 'fs'
|
||||||
|
import * as path from 'path'
|
||||||
|
|
||||||
|
let electronVibrancy: any
|
||||||
|
if (process.platform !== 'linux') {
|
||||||
|
electronVibrancy = require('electron-vibrancy')
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Window {
|
||||||
|
ready: Promise<void>
|
||||||
|
private visible = new Subject<boolean>()
|
||||||
|
private window: BrowserWindow
|
||||||
|
private vibrancyViewID: number
|
||||||
|
private windowConfig: ElectronConfig
|
||||||
|
private windowBounds: Rectangle
|
||||||
|
|
||||||
|
get visible$ (): Observable<boolean> { return this.visible }
|
||||||
|
|
||||||
|
constructor () {
|
||||||
|
let configPath = path.join(app.getPath('userData'), 'config.yaml')
|
||||||
|
let configData
|
||||||
|
if (fs.existsSync(configPath)) {
|
||||||
|
configData = yaml.safeLoad(fs.readFileSync(configPath, 'utf8'))
|
||||||
|
} else {
|
||||||
|
configData = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.windowConfig = new ElectronConfig({ name: 'window' })
|
||||||
|
this.windowBounds = this.windowConfig.get('windowBoundaries')
|
||||||
|
|
||||||
|
let maximized = this.windowConfig.get('maximized')
|
||||||
|
let options: Electron.BrowserWindowConstructorOptions = {
|
||||||
|
width: 800,
|
||||||
|
height: 600,
|
||||||
|
title: 'Terminus',
|
||||||
|
minWidth: 400,
|
||||||
|
minHeight: 300,
|
||||||
|
webPreferences: { webSecurity: false },
|
||||||
|
frame: false,
|
||||||
|
show: false,
|
||||||
|
}
|
||||||
|
Object.assign(options, this.windowBounds)
|
||||||
|
|
||||||
|
if ((configData.appearance || {}).frame === 'native') {
|
||||||
|
options.frame = true
|
||||||
|
} else {
|
||||||
|
if (process.platform === 'darwin') {
|
||||||
|
options.titleBarStyle = 'hiddenInset'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (process.platform === 'win32' && (configData.appearance || {}).vibrancy) {
|
||||||
|
options.transparent = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (process.platform === 'linux') {
|
||||||
|
options.backgroundColor = '#131d27'
|
||||||
|
}
|
||||||
|
|
||||||
|
this.window = new BrowserWindow(options)
|
||||||
|
this.window.once('ready-to-show', () => {
|
||||||
|
if (process.platform === 'darwin') {
|
||||||
|
this.window.setVibrancy('dark')
|
||||||
|
} else if (process.platform === 'win32' && (configData.appearance || {}).vibrancy) {
|
||||||
|
this.setVibrancy(true)
|
||||||
|
}
|
||||||
|
if (maximized) {
|
||||||
|
this.window.maximize()
|
||||||
|
} else {
|
||||||
|
this.window.show()
|
||||||
|
}
|
||||||
|
this.window.focus()
|
||||||
|
})
|
||||||
|
this.window.loadURL(`file://${app.getAppPath()}/dist/index.html?${this.window.id}`, { extraHeaders: 'pragma: no-cache\n' })
|
||||||
|
|
||||||
|
if (process.platform !== 'darwin') {
|
||||||
|
this.window.setMenu(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setupWindowManagement()
|
||||||
|
|
||||||
|
this.ready = new Promise(resolve => {
|
||||||
|
const listener = event => {
|
||||||
|
if (event.sender === this.window.webContents) {
|
||||||
|
ipcMain.removeListener('app:ready', listener)
|
||||||
|
resolve()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ipcMain.on('app:ready', listener)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
setVibrancy (enabled: boolean) {
|
||||||
|
if (enabled && !this.vibrancyViewID) {
|
||||||
|
this.vibrancyViewID = electronVibrancy.SetVibrancy(this.window, 0)
|
||||||
|
} else if (!enabled && this.vibrancyViewID) {
|
||||||
|
electronVibrancy.RemoveView(this.window, this.vibrancyViewID)
|
||||||
|
this.vibrancyViewID = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
show () {
|
||||||
|
this.window.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
focus () {
|
||||||
|
this.window.focus()
|
||||||
|
}
|
||||||
|
|
||||||
|
send (event, ...args) {
|
||||||
|
this.window.webContents.send(event, ...args)
|
||||||
|
}
|
||||||
|
|
||||||
|
private setupWindowManagement () {
|
||||||
|
this.window.on('show', () => {
|
||||||
|
this.visible.next(true)
|
||||||
|
this.window.webContents.send('host:window-shown')
|
||||||
|
})
|
||||||
|
|
||||||
|
this.window.on('hide', () => {
|
||||||
|
this.visible.next(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
this.window.on('enter-full-screen', () => this.window.webContents.send('host:window-enter-full-screen'))
|
||||||
|
this.window.on('leave-full-screen', () => this.window.webContents.send('host:window-leave-full-screen'))
|
||||||
|
|
||||||
|
this.window.on('close', () => {
|
||||||
|
this.windowConfig.set('windowBoundaries', this.windowBounds)
|
||||||
|
this.windowConfig.set('maximized', this.window.isMaximized())
|
||||||
|
})
|
||||||
|
|
||||||
|
this.window.on('closed', () => {
|
||||||
|
this.destroy()
|
||||||
|
})
|
||||||
|
|
||||||
|
this.window.on('resize', () => {
|
||||||
|
if (!this.window.isMaximized()) {
|
||||||
|
this.windowBounds = this.window.getBounds()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
this.window.on('move', () => {
|
||||||
|
if (!this.window.isMaximized()) {
|
||||||
|
this.windowBounds = this.window.getBounds()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
ipcMain.on('window-focus', () => {
|
||||||
|
this.window.focus()
|
||||||
|
})
|
||||||
|
|
||||||
|
ipcMain.on('window-maximize', () => {
|
||||||
|
this.window.maximize()
|
||||||
|
})
|
||||||
|
|
||||||
|
ipcMain.on('window-unmaximize', () => {
|
||||||
|
this.window.unmaximize()
|
||||||
|
})
|
||||||
|
|
||||||
|
ipcMain.on('window-toggle-maximize', () => {
|
||||||
|
if (this.window.isMaximized()) {
|
||||||
|
this.window.unmaximize()
|
||||||
|
} else {
|
||||||
|
this.window.maximize()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
ipcMain.on('window-minimize', () => {
|
||||||
|
this.window.minimize()
|
||||||
|
})
|
||||||
|
|
||||||
|
ipcMain.on('window-set-bounds', (_event, bounds) => {
|
||||||
|
this.window.setBounds(bounds)
|
||||||
|
})
|
||||||
|
|
||||||
|
ipcMain.on('window-set-always-on-top', (_event, flag) => {
|
||||||
|
this.window.setAlwaysOnTop(flag)
|
||||||
|
})
|
||||||
|
|
||||||
|
ipcMain.on('window-set-vibrancy', (_event, enabled) => {
|
||||||
|
this.setVibrancy(enabled)
|
||||||
|
})
|
||||||
|
|
||||||
|
ipcMain.on('window-set-title', (_event, title) => {
|
||||||
|
this.window.setTitle(title)
|
||||||
|
})
|
||||||
|
|
||||||
|
ipcMain.on('window-popup-context-menu', (_event, menuDefinition) => {
|
||||||
|
Menu.buildFromTemplate(menuDefinition).popup({ window: this.window })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private destroy () {
|
||||||
|
this.window = null
|
||||||
|
this.visible.complete()
|
||||||
|
}
|
||||||
|
}
|
312
app/main.js
312
app/main.js
@@ -1,312 +0,0 @@
|
|||||||
if (process.platform == 'win32' && require('electron-squirrel-startup')) process.exit(0)
|
|
||||||
|
|
||||||
const electron = require('electron')
|
|
||||||
let electronVibrancy
|
|
||||||
if (process.platform != 'linux') {
|
|
||||||
electronVibrancy = require('electron-vibrancy')
|
|
||||||
}
|
|
||||||
|
|
||||||
let app = electron.app
|
|
||||||
|
|
||||||
|
|
||||||
const yaml = require('js-yaml')
|
|
||||||
const path = require('path')
|
|
||||||
const fs = require('fs')
|
|
||||||
const Config = require('electron-config')
|
|
||||||
let windowConfig = new Config({name: 'window'})
|
|
||||||
|
|
||||||
|
|
||||||
if (!process.env.TERMINUS_PLUGINS) {
|
|
||||||
process.env.TERMINUS_PLUGINS = ''
|
|
||||||
}
|
|
||||||
|
|
||||||
setWindowVibrancy = (enabled) => {
|
|
||||||
if (enabled && !app.window.vibrancyViewID) {
|
|
||||||
app.window.vibrancyViewID = electronVibrancy.SetVibrancy(app.window, 0)
|
|
||||||
} else if (!enabled && app.window.vibrancyViewID) {
|
|
||||||
electronVibrancy.RemoveView(app.window, app.window.vibrancyViewID)
|
|
||||||
app.window.vibrancyViewID = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setupWindowManagement = () => {
|
|
||||||
app.window.on('show', () => {
|
|
||||||
app.window.webContents.send('host:window-shown')
|
|
||||||
if (app.tray) {
|
|
||||||
app.tray.destroy()
|
|
||||||
app.tray = null
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
app.window.on('hide', (e) => {
|
|
||||||
if (!app.tray) {
|
|
||||||
setupTray()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
app.window.on('enter-full-screen', () => app.window.webContents.send('host:window-enter-full-screen'))
|
|
||||||
app.window.on('leave-full-screen', () => app.window.webContents.send('host:window-leave-full-screen'))
|
|
||||||
|
|
||||||
app.window.on('close', (e) => {
|
|
||||||
windowConfig.set('windowBoundaries', app.window.getBounds())
|
|
||||||
})
|
|
||||||
|
|
||||||
app.window.on('closed', () => {
|
|
||||||
app.window = null
|
|
||||||
})
|
|
||||||
|
|
||||||
electron.ipcMain.on('window-focus', () => {
|
|
||||||
app.window.focus()
|
|
||||||
})
|
|
||||||
|
|
||||||
electron.ipcMain.on('window-maximize', () => {
|
|
||||||
app.window.maximize()
|
|
||||||
})
|
|
||||||
|
|
||||||
electron.ipcMain.on('window-unmaximize', () => {
|
|
||||||
app.window.unmaximize()
|
|
||||||
})
|
|
||||||
|
|
||||||
electron.ipcMain.on('window-toggle-maximize', () => {
|
|
||||||
if (app.window.isMaximized()) {
|
|
||||||
app.window.unmaximize()
|
|
||||||
} else {
|
|
||||||
app.window.maximize()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
electron.ipcMain.on('window-minimize', () => {
|
|
||||||
app.window.minimize()
|
|
||||||
})
|
|
||||||
|
|
||||||
electron.ipcMain.on('window-set-bounds', (event, bounds) => {
|
|
||||||
app.window.setBounds(bounds)
|
|
||||||
})
|
|
||||||
|
|
||||||
electron.ipcMain.on('window-set-always-on-top', (event, flag) => {
|
|
||||||
app.window.setAlwaysOnTop(flag)
|
|
||||||
})
|
|
||||||
|
|
||||||
electron.ipcMain.on('window-set-vibrancy', (event, enabled) => {
|
|
||||||
setWindowVibrancy(enabled)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
setupTray = () => {
|
|
||||||
if (process.platform == 'darwin') {
|
|
||||||
app.tray = new electron.Tray(`${app.getAppPath()}/assets/tray-darwinTemplate.png`)
|
|
||||||
app.tray.setPressedImage(`${app.getAppPath()}/assets/tray-darwinHighlightTemplate.png`)
|
|
||||||
} else {
|
|
||||||
app.tray = new electron.Tray(`${app.getAppPath()}/assets/tray.png`)
|
|
||||||
}
|
|
||||||
|
|
||||||
app.tray.on('click', () => {
|
|
||||||
app.window.show()
|
|
||||||
app.window.focus()
|
|
||||||
})
|
|
||||||
|
|
||||||
const contextMenu = electron.Menu.buildFromTemplate([{
|
|
||||||
label: 'Show',
|
|
||||||
click () {
|
|
||||||
app.window.show()
|
|
||||||
app.window.focus()
|
|
||||||
}
|
|
||||||
}])
|
|
||||||
|
|
||||||
if (process.platform != 'darwin') {
|
|
||||||
app.tray.setContextMenu(contextMenu)
|
|
||||||
}
|
|
||||||
|
|
||||||
app.tray.setToolTip(`Terminus ${app.getVersion()}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
setupMenu = () => {
|
|
||||||
let template = [{
|
|
||||||
label: "Application",
|
|
||||||
submenu: [
|
|
||||||
{ role: 'about', label: 'About Terminus' },
|
|
||||||
{ type: 'separator' },
|
|
||||||
{
|
|
||||||
label: 'Preferences',
|
|
||||||
accelerator: 'Cmd+,',
|
|
||||||
click () {
|
|
||||||
app.window.webContents.send('host:preferences-menu')
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ type: 'separator' },
|
|
||||||
{ role: 'services', submenu: [] },
|
|
||||||
{ type: 'separator' },
|
|
||||||
{ role: 'hide' },
|
|
||||||
{ role: 'hideothers' },
|
|
||||||
{ role: 'unhide' },
|
|
||||||
{ type: 'separator' },
|
|
||||||
{
|
|
||||||
label: 'Quit',
|
|
||||||
accelerator: 'Cmd+Q',
|
|
||||||
click () {
|
|
||||||
app.quit()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Edit",
|
|
||||||
submenu: [
|
|
||||||
{role: 'undo'},
|
|
||||||
{role: 'redo'},
|
|
||||||
{type: 'separator'},
|
|
||||||
{role: 'cut'},
|
|
||||||
{role: 'copy'},
|
|
||||||
{role: 'paste'},
|
|
||||||
{role: 'pasteandmatchstyle'},
|
|
||||||
{role: 'delete'},
|
|
||||||
{role: 'selectall'}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'View',
|
|
||||||
submenu: [
|
|
||||||
{role: 'reload'},
|
|
||||||
{role: 'forcereload'},
|
|
||||||
{role: 'toggledevtools'},
|
|
||||||
{type: 'separator'},
|
|
||||||
{role: 'resetzoom'},
|
|
||||||
{role: 'zoomin'},
|
|
||||||
{role: 'zoomout'},
|
|
||||||
{type: 'separator'},
|
|
||||||
{role: 'togglefullscreen'}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
role: 'window',
|
|
||||||
submenu: [
|
|
||||||
{role: 'minimize'},
|
|
||||||
{role: 'zoom'},
|
|
||||||
{type: 'separator'},
|
|
||||||
{role: 'front'}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
role: 'help',
|
|
||||||
submenu: [
|
|
||||||
{
|
|
||||||
label: 'Website',
|
|
||||||
click () { electron.shell.openExternal('https://eugeny.github.io/terminus') }
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}]
|
|
||||||
|
|
||||||
electron.Menu.setApplicationMenu(electron.Menu.buildFromTemplate(template))
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
start = () => {
|
|
||||||
let t0 = Date.now()
|
|
||||||
|
|
||||||
let configPath = path.join(electron.app.getPath('userData'), 'config.yaml')
|
|
||||||
let configData
|
|
||||||
if (fs.existsSync(configPath)) {
|
|
||||||
configData = yaml.safeLoad(fs.readFileSync(configPath, 'utf8'))
|
|
||||||
} else {
|
|
||||||
configData = {}
|
|
||||||
}
|
|
||||||
|
|
||||||
let options = {
|
|
||||||
width: 800,
|
|
||||||
height: 600,
|
|
||||||
title: 'Terminus',
|
|
||||||
minWidth: 400,
|
|
||||||
minHeight: 300,
|
|
||||||
webPreferences: {webSecurity: false},
|
|
||||||
frame: false,
|
|
||||||
show: false,
|
|
||||||
}
|
|
||||||
Object.assign(options, windowConfig.get('windowBoundaries'))
|
|
||||||
|
|
||||||
if ((configData.appearance || {}).frame == 'native') {
|
|
||||||
options.frame = true
|
|
||||||
} else {
|
|
||||||
if (process.platform == 'darwin') {
|
|
||||||
options.titleBarStyle = 'hiddenInset'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (process.platform == 'linux') {
|
|
||||||
options.backgroundColor = '#131d27'
|
|
||||||
}
|
|
||||||
|
|
||||||
app.commandLine.appendSwitch('disable-http-cache')
|
|
||||||
|
|
||||||
app.window = new electron.BrowserWindow(options)
|
|
||||||
app.window.once('ready-to-show', () => {
|
|
||||||
if (process.platform == 'darwin') {
|
|
||||||
app.window.setVibrancy('dark')
|
|
||||||
} else if (process.platform == 'windows') {
|
|
||||||
setWindowVibrancy(true)
|
|
||||||
}
|
|
||||||
app.window.show()
|
|
||||||
app.window.focus()
|
|
||||||
})
|
|
||||||
app.window.loadURL(`file://${app.getAppPath()}/dist/index.html`, {extraHeaders: "pragma: no-cache\n"})
|
|
||||||
|
|
||||||
if (process.platform != 'darwin') {
|
|
||||||
app.window.setMenu(null)
|
|
||||||
}
|
|
||||||
|
|
||||||
setupWindowManagement()
|
|
||||||
|
|
||||||
if (process.platform == 'darwin') {
|
|
||||||
setupMenu()
|
|
||||||
} else {
|
|
||||||
app.window.setMenu(null)
|
|
||||||
}
|
|
||||||
|
|
||||||
console.info(`Host startup: ${Date.now() - t0}ms`)
|
|
||||||
t0 = Date.now()
|
|
||||||
electron.ipcMain.on('app:ready', () => {
|
|
||||||
console.info(`App startup: ${Date.now() - t0}ms`)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
app.on('activate', () => {
|
|
||||||
if (!app.window)
|
|
||||||
start()
|
|
||||||
else {
|
|
||||||
app.window.show()
|
|
||||||
app.window.focus()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
process.on('uncaughtException', function(err) {
|
|
||||||
console.log(err)
|
|
||||||
app.window.webContents.send('uncaughtException', err)
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const argv = require('yargs')
|
|
||||||
.usage('terminus [command] [arguments]')
|
|
||||||
.version('v', 'Show version and exit', app.getVersion())
|
|
||||||
.alias('d', 'debug')
|
|
||||||
.describe('d', 'Show DevTools on start')
|
|
||||||
.alias('h', 'help')
|
|
||||||
.help('h')
|
|
||||||
.strict()
|
|
||||||
.argv
|
|
||||||
|
|
||||||
app.on('second-instance', (argv, cwd) => {
|
|
||||||
app.window.webContents.send('host:second-instance', argv, cwd)
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!app.requestSingleInstanceLock()) {
|
|
||||||
app.quit()
|
|
||||||
process.exit(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (argv.d) {
|
|
||||||
require('electron-debug')({enabled: true, showDevTools: 'undocked'})
|
|
||||||
}
|
|
||||||
|
|
||||||
app.on('ready', start)
|
|
@@ -5,7 +5,7 @@
|
|||||||
"name": "Eugene Pankov",
|
"name": "Eugene Pankov",
|
||||||
"email": "e@ajenti.org"
|
"email": "e@ajenti.org"
|
||||||
},
|
},
|
||||||
"main": "main.js",
|
"main": "dist/main.js",
|
||||||
"version": "1.0.0-alpha.1",
|
"version": "1.0.0-alpha.1",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "webpack --progress --color --display-modules",
|
"build": "webpack --progress --color --display-modules",
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
"@ng-bootstrap/ng-bootstrap": "^2.0.0",
|
"@ng-bootstrap/ng-bootstrap": "^2.0.0",
|
||||||
"devtron": "1.4.0",
|
"devtron": "1.4.0",
|
||||||
"electron-config": "0.2.1",
|
"electron-config": "0.2.1",
|
||||||
"electron-debug": "^1.0.1",
|
"electron-debug": "^2.0.0",
|
||||||
"electron-is-dev": "0.1.2",
|
"electron-is-dev": "0.1.2",
|
||||||
"electron-squirrel-startup": "^1.0.0",
|
"electron-squirrel-startup": "^1.0.0",
|
||||||
"electron-vibrancy": "^0.1.3",
|
"electron-vibrancy": "^0.1.3",
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
import '../lib/lru'
|
||||||
import 'source-sans-pro'
|
import 'source-sans-pro'
|
||||||
import 'font-awesome/css/font-awesome.css'
|
import 'font-awesome/css/font-awesome.css'
|
||||||
import 'ngx-toastr/toastr.css'
|
import 'ngx-toastr/toastr.css'
|
||||||
@@ -28,8 +29,8 @@ Raven.config(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
process.on('uncaughtException', (err) => {
|
process.on('uncaughtException' as any, (err) => {
|
||||||
Raven.captureException(err)
|
Raven.captureException(err as any)
|
||||||
console.error(err)
|
console.error(err)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@@ -2,6 +2,8 @@ import 'zone.js'
|
|||||||
import 'core-js/es7/reflect'
|
import 'core-js/es7/reflect'
|
||||||
import 'core-js/core/delay'
|
import 'core-js/core/delay'
|
||||||
import 'rxjs'
|
import 'rxjs'
|
||||||
|
|
||||||
|
import './global.scss'
|
||||||
import './toastr.scss'
|
import './toastr.scss'
|
||||||
|
|
||||||
// Always land on the start view
|
// Always land on the start view
|
||||||
|
91
app/src/global.scss
Normal file
91
app/src/global.scss
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
body {
|
||||||
|
min-height: 100vh;
|
||||||
|
overflow: hidden;
|
||||||
|
background: #1D272D;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-dialog, .modal-backdrop, .no-drag {
|
||||||
|
-webkit-app-region: no-drag;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selectable {
|
||||||
|
user-select: text;
|
||||||
|
}
|
||||||
|
|
||||||
|
[ngbradiogroup] input[type="radio"] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-line {
|
||||||
|
display: flex;
|
||||||
|
border-top: 1px solid rgba(0, 0, 0, 0.2);
|
||||||
|
align-items: center;
|
||||||
|
padding: 10px 0;
|
||||||
|
margin: 0;
|
||||||
|
min-height: 64px;
|
||||||
|
|
||||||
|
.header {
|
||||||
|
margin-right: auto;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
}
|
||||||
|
|
||||||
|
.description {
|
||||||
|
font-size: 13px;
|
||||||
|
opacity: .5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&>.form-control, &>.input-group {
|
||||||
|
width: 33%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=range] {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
background: transparent;
|
||||||
|
outline: none;
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
border-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin thumb() {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
display: block;
|
||||||
|
height: 12px;
|
||||||
|
width: 12px;
|
||||||
|
background: #aaa;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
margin-top: -4px;
|
||||||
|
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.95);
|
||||||
|
transition: 0.25s background;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #777;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
background: #666;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-slider-thumb { @include thumb(); }
|
||||||
|
&::-moz-range-thumb { @include thumb(); }
|
||||||
|
&::-ms-thumb { @include thumb(); }
|
||||||
|
&::thumb { @include thumb(); }
|
||||||
|
|
||||||
|
@mixin track() {
|
||||||
|
height: 4px;
|
||||||
|
background: #111;
|
||||||
|
margin: 3px 0 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-slider-runnable-track { @include track(); }
|
||||||
|
&:focus::-webkit-slider-runnable-track { @include track(); }
|
||||||
|
&::-moz-range-track { @include track(); }
|
||||||
|
&::-ms-track { @include track(); }
|
||||||
|
}
|
@@ -58,17 +58,3 @@
|
|||||||
color: #842fe0;
|
color: #842fe0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-dialog, .modal-backdrop {
|
|
||||||
-webkit-app-region: no-drag;
|
|
||||||
}
|
|
||||||
|
|
||||||
[ngbradiogroup] input[type="radio"] {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
min-height: 100vh;
|
|
||||||
overflow: hidden;
|
|
||||||
background: rgba(0,0,0,.4);
|
|
||||||
}
|
|
||||||
|
@@ -17,7 +17,7 @@
|
|||||||
"lib": [
|
"lib": [
|
||||||
"dom",
|
"dom",
|
||||||
"es2015",
|
"es2015",
|
||||||
"es2015.iterable.ts",
|
"es2015.iterable",
|
||||||
"es2017",
|
"es2017",
|
||||||
"es7"
|
"es7"
|
||||||
]
|
]
|
||||||
|
31
app/tsconfig.main.json
Normal file
31
app/tsconfig.main.json
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"baseUrl": "./lib",
|
||||||
|
"module": "commonjs",
|
||||||
|
"target": "es2017",
|
||||||
|
"declaration": false,
|
||||||
|
"noImplicitAny": false,
|
||||||
|
"removeComments": false,
|
||||||
|
"emitDecoratorMetadata": true,
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"sourceMap": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"noImplicitReturns": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"lib": [
|
||||||
|
"dom",
|
||||||
|
"es2015",
|
||||||
|
"es2015.iterable",
|
||||||
|
"es2017",
|
||||||
|
"es7"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"compileOnSave": false,
|
||||||
|
"exclude": [
|
||||||
|
"dist",
|
||||||
|
"node_modules",
|
||||||
|
"*/node_modules"
|
||||||
|
]
|
||||||
|
}
|
@@ -6,15 +6,16 @@ module.exports = {
|
|||||||
target: 'node',
|
target: 'node',
|
||||||
entry: {
|
entry: {
|
||||||
'index.ignore': 'file-loader?name=index.html!val-loader!pug-html-loader!' + path.resolve(__dirname, './index.pug'),
|
'index.ignore': 'file-loader?name=index.html!val-loader!pug-html-loader!' + path.resolve(__dirname, './index.pug'),
|
||||||
'preload': path.resolve(__dirname, 'src/entry.preload.ts'),
|
preload: path.resolve(__dirname, 'src/entry.preload.ts'),
|
||||||
'bundle': path.resolve(__dirname, 'src/entry.ts'),
|
bundle: path.resolve(__dirname, 'src/entry.ts'),
|
||||||
},
|
},
|
||||||
|
mode: process.env.DEV ? 'development' : 'production',
|
||||||
context: __dirname,
|
context: __dirname,
|
||||||
devtool: 'source-map',
|
devtool: 'source-map',
|
||||||
output: {
|
output: {
|
||||||
path: path.join(__dirname, 'dist'),
|
path: path.join(__dirname, 'dist'),
|
||||||
pathinfo: true,
|
pathinfo: true,
|
||||||
filename: '[name].js'
|
filename: '[name].js',
|
||||||
},
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
modules: ['src/', 'node_modules', '../node_modules', 'assets/'].map(x => path.join(__dirname, x)),
|
modules: ['src/', 'node_modules', '../node_modules', 'assets/'].map(x => path.join(__dirname, x)),
|
||||||
@@ -28,8 +29,8 @@ module.exports = {
|
|||||||
loader: 'awesome-typescript-loader',
|
loader: 'awesome-typescript-loader',
|
||||||
options: {
|
options: {
|
||||||
configFileName: path.resolve(__dirname, 'tsconfig.json'),
|
configFileName: path.resolve(__dirname, 'tsconfig.json'),
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{ test: /\.scss$/, use: ['style-loader', 'css-loader', 'sass-loader'] },
|
{ test: /\.scss$/, use: ['style-loader', 'css-loader', 'sass-loader'] },
|
||||||
{ test: /\.css$/, use: ['style-loader', 'css-loader', 'sass-loader'] },
|
{ test: /\.css$/, use: ['style-loader', 'css-loader', 'sass-loader'] },
|
||||||
@@ -38,20 +39,20 @@ module.exports = {
|
|||||||
use: {
|
use: {
|
||||||
loader: 'file-loader',
|
loader: 'file-loader',
|
||||||
options: {
|
options: {
|
||||||
name: 'images/[name].[ext]'
|
name: 'images/[name].[ext]',
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.(ttf|eot|otf|woff|woff2)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
|
test: /\.(ttf|eot|otf|woff|woff2)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
|
||||||
use: {
|
use: {
|
||||||
loader: 'file-loader',
|
loader: 'file-loader',
|
||||||
options: {
|
options: {
|
||||||
name: 'fonts/[name].[ext]'
|
name: 'fonts/[name].[ext]',
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
},
|
},
|
||||||
externals: {
|
externals: {
|
||||||
'@angular/core': 'commonjs @angular/core',
|
'@angular/core': 'commonjs @angular/core',
|
||||||
@@ -61,14 +62,15 @@ module.exports = {
|
|||||||
'@angular/forms': 'commonjs @angular/forms',
|
'@angular/forms': 'commonjs @angular/forms',
|
||||||
'@angular/common': 'commonjs @angular/common',
|
'@angular/common': 'commonjs @angular/common',
|
||||||
'@ng-bootstrap/ng-bootstrap': 'commonjs @ng-bootstrap/ng-bootstrap',
|
'@ng-bootstrap/ng-bootstrap': 'commonjs @ng-bootstrap/ng-bootstrap',
|
||||||
'child_process': 'commonjs child_process',
|
child_process: 'commonjs child_process',
|
||||||
'electron': 'commonjs electron',
|
electron: 'commonjs electron',
|
||||||
'electron-is-dev': 'commonjs electron-is-dev',
|
'electron-is-dev': 'commonjs electron-is-dev',
|
||||||
|
fs: 'commonjs fs',
|
||||||
'ngx-toastr': 'commonjs ngx-toastr',
|
'ngx-toastr': 'commonjs ngx-toastr',
|
||||||
'module': 'commonjs module',
|
module: 'commonjs module',
|
||||||
'mz': 'commonjs mz',
|
mz: 'commonjs mz',
|
||||||
'path': 'commonjs path',
|
path: 'commonjs path',
|
||||||
'rxjs': 'commonjs rxjs',
|
rxjs: 'commonjs rxjs',
|
||||||
'zone.js': 'commonjs zone.js/dist/zone.js',
|
'zone.js': 'commonjs zone.js/dist/zone.js',
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
|
48
app/webpack.main.config.js
Normal file
48
app/webpack.main.config.js
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
const path = require('path')
|
||||||
|
const webpack = require('webpack')
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
name: 'terminus-main',
|
||||||
|
target: 'node',
|
||||||
|
entry: {
|
||||||
|
main: path.resolve(__dirname, 'lib/index.ts'),
|
||||||
|
},
|
||||||
|
mode: process.env.DEV ? 'development' : 'production',
|
||||||
|
context: __dirname,
|
||||||
|
devtool: 'source-map',
|
||||||
|
output: {
|
||||||
|
path: path.join(__dirname, 'dist'),
|
||||||
|
pathinfo: true,
|
||||||
|
filename: '[name].js',
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
modules: ['lib/', 'node_modules', '../node_modules'].map(x => path.join(__dirname, x)),
|
||||||
|
extensions: ['.ts', '.js'],
|
||||||
|
},
|
||||||
|
module: {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
test: /\.ts$/,
|
||||||
|
use: {
|
||||||
|
loader: 'awesome-typescript-loader',
|
||||||
|
options: {
|
||||||
|
configFileName: path.resolve(__dirname, 'tsconfig.main.json'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
externals: {
|
||||||
|
electron: 'commonjs electron',
|
||||||
|
'electron-config': 'commonjs electron-config',
|
||||||
|
'electron-vibrancy': 'commonjs electron-vibrancy',
|
||||||
|
'electron-squirrel-startup': 'commonjs electron-squirrel-startup',
|
||||||
|
fs: 'commonjs fs',
|
||||||
|
mz: 'commonjs mz',
|
||||||
|
path: 'commonjs path',
|
||||||
|
yargs: 'commonjs yargs',
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
new webpack.optimize.ModuleConcatenationPlugin(),
|
||||||
|
],
|
||||||
|
}
|
@@ -149,27 +149,33 @@ electron-config@0.2.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
conf "^0.11.1"
|
conf "^0.11.1"
|
||||||
|
|
||||||
electron-debug@^1.0.1:
|
electron-debug@^2.0.0:
|
||||||
version "1.2.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/electron-debug/-/electron-debug-1.2.0.tgz#22e51a73e1bf095d0bb51a6c3d97a203364c4222"
|
resolved "https://registry.yarnpkg.com/electron-debug/-/electron-debug-2.0.0.tgz#3059a6557acbfb091f138d83875f57bac80cea6d"
|
||||||
dependencies:
|
dependencies:
|
||||||
electron-is-dev "^0.1.0"
|
electron-is-dev "^0.3.0"
|
||||||
electron-localshortcut "^2.0.0"
|
electron-localshortcut "^3.0.0"
|
||||||
|
|
||||||
electron-is-accelerator@^0.1.0:
|
electron-is-accelerator@^0.1.0:
|
||||||
version "0.1.2"
|
version "0.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/electron-is-accelerator/-/electron-is-accelerator-0.1.2.tgz#509e510c26a56b55e17f863a4b04e111846ab27b"
|
resolved "https://registry.yarnpkg.com/electron-is-accelerator/-/electron-is-accelerator-0.1.2.tgz#509e510c26a56b55e17f863a4b04e111846ab27b"
|
||||||
|
|
||||||
electron-is-dev@0.1.2, electron-is-dev@^0.1.0:
|
electron-is-dev@0.1.2:
|
||||||
version "0.1.2"
|
version "0.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/electron-is-dev/-/electron-is-dev-0.1.2.tgz#8a1043e32b3a1da1c3f553dce28ce764246167e3"
|
resolved "https://registry.yarnpkg.com/electron-is-dev/-/electron-is-dev-0.1.2.tgz#8a1043e32b3a1da1c3f553dce28ce764246167e3"
|
||||||
|
|
||||||
electron-localshortcut@^2.0.0:
|
electron-is-dev@^0.3.0:
|
||||||
version "2.0.2"
|
version "0.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/electron-localshortcut/-/electron-localshortcut-2.0.2.tgz#6a1adcd6514c957328ec7912f5ccb5e1c10706db"
|
resolved "https://registry.yarnpkg.com/electron-is-dev/-/electron-is-dev-0.3.0.tgz#14e6fda5c68e9e4ecbeff9ccf037cbd7c05c5afe"
|
||||||
|
|
||||||
|
electron-localshortcut@^3.0.0:
|
||||||
|
version "3.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/electron-localshortcut/-/electron-localshortcut-3.1.0.tgz#10c1ffd537b8d39170aaf6e1551341f7780dd2ce"
|
||||||
dependencies:
|
dependencies:
|
||||||
debug "^2.6.8"
|
debug "^2.6.8"
|
||||||
electron-is-accelerator "^0.1.0"
|
electron-is-accelerator "^0.1.0"
|
||||||
|
keyboardevent-from-electron-accelerator "^1.1.0"
|
||||||
|
keyboardevents-areequal "^0.2.1"
|
||||||
|
|
||||||
electron-squirrel-startup@^1.0.0:
|
electron-squirrel-startup@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
@@ -270,6 +276,14 @@ js-yaml@3.8.2:
|
|||||||
argparse "^1.0.7"
|
argparse "^1.0.7"
|
||||||
esprima "^3.1.1"
|
esprima "^3.1.1"
|
||||||
|
|
||||||
|
keyboardevent-from-electron-accelerator@^1.1.0:
|
||||||
|
version "1.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/keyboardevent-from-electron-accelerator/-/keyboardevent-from-electron-accelerator-1.1.0.tgz#324614f6e33490c37ffc5be5876b3e85fe223c84"
|
||||||
|
|
||||||
|
keyboardevents-areequal@^0.2.1:
|
||||||
|
version "0.2.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/keyboardevents-areequal/-/keyboardevents-areequal-0.2.2.tgz#88191ec738ce9f7591c25e9056de928b40277194"
|
||||||
|
|
||||||
lcid@^1.0.0:
|
lcid@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/lcid/-/lcid-1.0.0.tgz#308accafa0bc483a3867b4b6f2b9506251d1b835"
|
resolved "https://registry.yarnpkg.com/lcid/-/lcid-1.0.0.tgz#308accafa0bc483a3867b4b6f2b9506251d1b835"
|
||||||
|
@@ -1,6 +1,10 @@
|
|||||||
|
|
||||||
### Changes
|
### Changes
|
||||||
|
|
||||||
|
##### v0.4.9
|
||||||
|
|
||||||
|
- Fixed broken Doskey on Win10 (#438, #451)
|
||||||
|
|
||||||
##### v0.4.8
|
##### v0.4.8
|
||||||
|
|
||||||
- Environment variable 'clink_profile' overrides Clink's profile path (#390).
|
- Environment variable 'clink_profile' overrides Clink's profile path (#390).
|
||||||
|
@@ -136,7 +136,7 @@ h6 { font-size: 1.00em; }
|
|||||||
<!-- ----------------------------------------------------- -->
|
<!-- ----------------------------------------------------- -->
|
||||||
<body onload="go();">
|
<body onload="go();">
|
||||||
<div id="header">
|
<div id="header">
|
||||||
<div id="title">Clink v0.4.8</div>
|
<div id="title">Clink v0.4.9</div>
|
||||||
<div id="url">
|
<div id="url">
|
||||||
<a href="http://github.com/mridgers/clink">
|
<a href="http://github.com/mridgers/clink">
|
||||||
http://github.com/mridgers/clink
|
http://github.com/mridgers/clink
|
||||||
@@ -697,6 +697,10 @@ The current cursor position within the line buffer.
|
|||||||
|
|
||||||
### Changes
|
### Changes
|
||||||
|
|
||||||
|
##### v0.4.9
|
||||||
|
|
||||||
|
- Fixed broken Doskey on Win10 (#438, #451)
|
||||||
|
|
||||||
##### v0.4.8
|
##### v0.4.8
|
||||||
|
|
||||||
- Environment variable 'clink_profile' overrides Clink's profile path (#390).
|
- Environment variable 'clink_profile' overrides Clink's profile path (#390).
|
||||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
19
package.json
19
package.json
@@ -1,19 +1,32 @@
|
|||||||
{
|
{
|
||||||
"name": "term",
|
"name": "term",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/electron-config": "^0.2.1",
|
||||||
|
"@types/electron-debug": "^1.1.0",
|
||||||
"@types/fs-promise": "1.0.1",
|
"@types/fs-promise": "1.0.1",
|
||||||
|
"@types/js-yaml": "^3.11.2",
|
||||||
"@types/node": "7.0.5",
|
"@types/node": "7.0.5",
|
||||||
"@types/webpack-env": "1.13.0",
|
"@types/webpack-env": "1.13.0",
|
||||||
"apply-loader": "0.1.0",
|
"apply-loader": "0.1.0",
|
||||||
"awesome-typescript-loader": "^5.0.0",
|
"awesome-typescript-loader": "^5.0.0",
|
||||||
|
"babel-core": "^6.26.3",
|
||||||
|
"babel-eslint": "^8.2.6",
|
||||||
|
"babel-loader": "^7.1.5",
|
||||||
|
"babel-preset-es2015": "^6.24.1",
|
||||||
"core-js": "2.4.1",
|
"core-js": "2.4.1",
|
||||||
"cross-env": "4.0.0",
|
"cross-env": "4.0.0",
|
||||||
"css-loader": "0.28.0",
|
"css-loader": "0.28.0",
|
||||||
"electron": "3.0.0-beta.5",
|
"electron": "3.0.0-beta.9",
|
||||||
"electron-builder": "^20.27.1",
|
"electron-builder": "^20.27.1",
|
||||||
"electron-builder-squirrel-windows": "17.0.1",
|
"electron-builder-squirrel-windows": "17.0.1",
|
||||||
"electron-installer-snap": "^3.0.0",
|
"electron-installer-snap": "^3.0.0",
|
||||||
"electron-rebuild": "^1.8.2",
|
"electron-rebuild": "^1.8.2",
|
||||||
|
"eslint": "^5.4.0",
|
||||||
|
"eslint-config-standard": "^11.0.0",
|
||||||
|
"eslint-plugin-import": "^2.14.0",
|
||||||
|
"eslint-plugin-node": "^7.0.1",
|
||||||
|
"eslint-plugin-promise": "^4.0.0",
|
||||||
|
"eslint-plugin-standard": "^3.1.0",
|
||||||
"file-loader": "^1.1.11",
|
"file-loader": "^1.1.11",
|
||||||
"font-awesome": "4.7.0",
|
"font-awesome": "4.7.0",
|
||||||
"graceful-fs": "^4.1.11",
|
"graceful-fs": "^4.1.11",
|
||||||
@@ -114,8 +127,8 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "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-settings/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",
|
"build": "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-settings/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",
|
||||||
"watch": "webpack --progress --color --watch",
|
"watch": "cross-env DEV=1 webpack --progress --color --watch",
|
||||||
"start": "cross-env DEV=1 electron app --debug",
|
"start": "cross-env DEV=1 electron app --debug",
|
||||||
"prod": "cross-env DEV=1 electron app",
|
"prod": "cross-env DEV=1 electron app",
|
||||||
"lint": "tslint -c tslint.json -t stylish terminus-*/src/**/*.ts terminus-*/src/*.ts app/src/*.ts",
|
"lint": "tslint -c tslint.json -t stylish terminus-*/src/**/*.ts terminus-*/src/*.ts app/src/*.ts",
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "terminus-community-color-schemes",
|
"name": "terminus-community-color-schemes",
|
||||||
"version": "1.0.0-alpha.48",
|
"version": "1.0.0-alpha.55",
|
||||||
"description": "Community color schemes for Terminus",
|
"description": "Community color schemes for Terminus",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"terminus-builtin-plugin"
|
"terminus-builtin-plugin"
|
||||||
|
@@ -13,6 +13,7 @@ module.exports = {
|
|||||||
libraryTarget: 'umd',
|
libraryTarget: 'umd',
|
||||||
devtoolModuleFilenameTemplate: 'webpack-terminus-community-color-schemes:///[resource-path]',
|
devtoolModuleFilenameTemplate: 'webpack-terminus-community-color-schemes:///[resource-path]',
|
||||||
},
|
},
|
||||||
|
mode: process.env.DEV ? 'development' : 'production',
|
||||||
resolve: {
|
resolve: {
|
||||||
modules: ['.', 'src', 'node_modules', '../app/node_modules'].map(x => path.join(__dirname, x)),
|
modules: ['.', 'src', 'node_modules', '../app/node_modules'].map(x => path.join(__dirname, x)),
|
||||||
extensions: ['.ts', '.js'],
|
extensions: ['.ts', '.js'],
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "terminus-core",
|
"name": "terminus-core",
|
||||||
"version": "1.0.0-alpha.48",
|
"version": "1.0.0-alpha.55",
|
||||||
"description": "Terminus core",
|
"description": "Terminus core",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"terminus-builtin-plugin"
|
"terminus-builtin-plugin"
|
||||||
@@ -22,11 +22,12 @@
|
|||||||
"@types/webpack-env": "^1.13.0",
|
"@types/webpack-env": "^1.13.0",
|
||||||
"@types/winston": "^2.3.6",
|
"@types/winston": "^2.3.6",
|
||||||
"axios": "0.16.2",
|
"axios": "0.16.2",
|
||||||
"bootstrap": "4.0.0-alpha.6",
|
"bootstrap": "^4.1.3",
|
||||||
"core-js": "^2.4.1",
|
"core-js": "^2.4.1",
|
||||||
"electron-updater": "^2.8.9",
|
"electron-updater": "^2.8.9",
|
||||||
"ng2-dnd": "^5.0.2",
|
"ng2-dnd": "^5.0.2",
|
||||||
"ngx-perfect-scrollbar": "^6.0.0"
|
"ngx-perfect-scrollbar": "^6.0.0",
|
||||||
|
"universal-analytics": "^0.4.17"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@angular/animations": "4.0.1",
|
"@angular/animations": "4.0.1",
|
||||||
|
@@ -1,3 +0,0 @@
|
|||||||
export abstract class DefaultTabProvider {
|
|
||||||
abstract async openNewTab (): Promise<void>
|
|
||||||
}
|
|
@@ -3,7 +3,6 @@ export { TabRecoveryProvider, RecoveredTab } from './tabRecovery'
|
|||||||
export { ToolbarButtonProvider, IToolbarButton } from './toolbarButtonProvider'
|
export { ToolbarButtonProvider, IToolbarButton } from './toolbarButtonProvider'
|
||||||
export { ConfigProvider } from './configProvider'
|
export { ConfigProvider } from './configProvider'
|
||||||
export { HotkeyProvider, IHotkeyDescription } from './hotkeyProvider'
|
export { HotkeyProvider, IHotkeyDescription } from './hotkeyProvider'
|
||||||
export { DefaultTabProvider } from './defaultTabProvider'
|
|
||||||
export { Theme } from './theme'
|
export { Theme } from './theme'
|
||||||
|
|
||||||
export { AppService } from '../services/app.service'
|
export { AppService } from '../services/app.service'
|
||||||
@@ -11,6 +10,7 @@ export { ConfigService } from '../services/config.service'
|
|||||||
export { DockingService } from '../services/docking.service'
|
export { DockingService } from '../services/docking.service'
|
||||||
export { ElectronService } from '../services/electron.service'
|
export { ElectronService } from '../services/electron.service'
|
||||||
export { Logger, LogService } from '../services/log.service'
|
export { Logger, LogService } from '../services/log.service'
|
||||||
|
export { HomeBaseService } from '../services/homeBase.service'
|
||||||
export { HotkeysService } from '../services/hotkeys.service'
|
export { HotkeysService } from '../services/hotkeys.service'
|
||||||
export { HostAppService, Platform } from '../services/hostApp.service'
|
export { HostAppService, Platform } from '../services/hostApp.service'
|
||||||
export { ThemesService } from '../services/themes.service'
|
export { ThemesService } from '../services/themes.service'
|
||||||
|
@@ -2,6 +2,7 @@ import { SafeHtml } from '@angular/platform-browser'
|
|||||||
|
|
||||||
export interface IToolbarButton {
|
export interface IToolbarButton {
|
||||||
icon: SafeHtml
|
icon: SafeHtml
|
||||||
|
touchBarNSImage?: string
|
||||||
title: string
|
title: string
|
||||||
touchBarTitle?: string
|
touchBarTitle?: string
|
||||||
weight?: number
|
weight?: number
|
||||||
|
@@ -24,7 +24,7 @@ title-bar(
|
|||||||
[index]='idx',
|
[index]='idx',
|
||||||
[tab]='tab',
|
[tab]='tab',
|
||||||
[active]='tab == app.activeTab',
|
[active]='tab == app.activeTab',
|
||||||
[hasActivity]='tab.hasActivity',
|
[hasActivity]='tab.activity$|async',
|
||||||
@animateTab,
|
@animateTab,
|
||||||
(click)='app.selectTab(tab)',
|
(click)='app.selectTab(tab)',
|
||||||
[class.fully-draggable]='hostApp.platform != Platform.macOS',
|
[class.fully-draggable]='hostApp.platform != Platform.macOS',
|
||||||
|
@@ -4,7 +4,7 @@
|
|||||||
height: 100vh;
|
height: 100vh;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
-webkit-user-select: none;
|
user-select: none;
|
||||||
-webkit-user-drag: none;
|
-webkit-user-drag: none;
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
will-change: transform;
|
will-change: transform;
|
||||||
|
@@ -138,33 +138,42 @@ export class AppRootComponent {
|
|||||||
config.changed$.subscribe(() => this.updateVibrancy())
|
config.changed$.subscribe(() => this.updateVibrancy())
|
||||||
this.updateVibrancy()
|
this.updateVibrancy()
|
||||||
|
|
||||||
this.app.tabOpened$.subscribe(tab => this.unsortedTabs.push(tab))
|
this.app.tabOpened$.subscribe(tab => {
|
||||||
|
this.unsortedTabs.push(tab)
|
||||||
|
tab.progress$.subscribe(progress => {
|
||||||
|
if (progress !== null) {
|
||||||
|
this.hostApp.getWindow().setProgressBar(progress / 100.0, { mode: 'normal' })
|
||||||
|
} else {
|
||||||
|
this.hostApp.getWindow().setProgressBar(-1, { mode: 'none' })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
this.app.tabClosed$.subscribe(tab => {
|
this.app.tabClosed$.subscribe(tab => {
|
||||||
this.unsortedTabs = this.unsortedTabs.filter(x => x !== tab)
|
this.unsortedTabs = this.unsortedTabs.filter(x => x !== tab)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
onGlobalHotkey () {
|
onGlobalHotkey () {
|
||||||
if (this.electron.app.window.isFocused()) {
|
if (this.hostApp.getWindow().isFocused()) {
|
||||||
// focused
|
// focused
|
||||||
this.electron.loseFocus()
|
this.electron.loseFocus()
|
||||||
if (this.hostApp.platform !== Platform.macOS) {
|
if (this.hostApp.platform !== Platform.macOS) {
|
||||||
this.electron.app.window.hide()
|
this.hostApp.getWindow().hide()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (!this.electron.app.window.isVisible()) {
|
if (!this.hostApp.getWindow().isVisible()) {
|
||||||
// unfocused, invisible
|
// unfocused, invisible
|
||||||
this.electron.app.window.show()
|
this.hostApp.getWindow().show()
|
||||||
this.electron.app.window.focus()
|
this.hostApp.getWindow().focus()
|
||||||
} else {
|
} else {
|
||||||
if (this.config.store.appearance.dock === 'off') {
|
if (this.config.store.appearance.dock === 'off') {
|
||||||
// not docked, visible
|
// not docked, visible
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.electron.app.window.focus()
|
this.hostApp.getWindow().focus()
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
// docked, visible
|
// docked, visible
|
||||||
this.electron.app.window.hide()
|
this.hostApp.getWindow().hide()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -175,10 +184,6 @@ export class AppRootComponent {
|
|||||||
this.ready = true
|
this.ready = true
|
||||||
this.tabRecovery.saveTabs(this.app.tabs)
|
this.tabRecovery.saveTabs(this.app.tabs)
|
||||||
|
|
||||||
if (this.app.tabs.length === 0) {
|
|
||||||
this.app.openDefaultTab()
|
|
||||||
}
|
|
||||||
|
|
||||||
this.app.emitReady()
|
this.app.emitReady()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -218,7 +223,7 @@ export class AppRootComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private updateVibrancy () {
|
private updateVibrancy () {
|
||||||
this.hostApp.setVibrancy(this.config.store.appearance.vibrancy)
|
this.hostApp.setVibrancy(this.config.store.appearance.vibrancy)
|
||||||
this.hostApp.getWindow().setOpacity(this.config.store.appearance.opacity)
|
this.hostApp.getWindow().setOpacity(this.config.store.appearance.opacity)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -6,16 +6,22 @@ export abstract class BaseTabComponent {
|
|||||||
id: number
|
id: number
|
||||||
title: string
|
title: string
|
||||||
customTitle: string
|
customTitle: string
|
||||||
hasActivity = false
|
|
||||||
hasFocus = false
|
hasFocus = false
|
||||||
|
hasActivity = false
|
||||||
hostView: ViewRef
|
hostView: ViewRef
|
||||||
protected titleChange = new Subject<string>()
|
protected titleChange = new Subject<string>()
|
||||||
protected focused = new Subject<void>()
|
protected focused = new Subject<void>()
|
||||||
protected blurred = new Subject<void>()
|
protected blurred = new Subject<void>()
|
||||||
|
protected progress = new Subject<number>()
|
||||||
|
protected activity = new Subject<boolean>()
|
||||||
|
|
||||||
|
private progressClearTimeout: number
|
||||||
|
|
||||||
get focused$ (): Observable<void> { return this.focused }
|
get focused$ (): Observable<void> { return this.focused }
|
||||||
get blurred$ (): Observable<void> { return this.blurred }
|
get blurred$ (): Observable<void> { return this.blurred }
|
||||||
get titleChange$ (): Observable<string> { return this.titleChange }
|
get titleChange$ (): Observable<string> { return this.titleChange }
|
||||||
|
get progress$ (): Observable<number> { return this.progress }
|
||||||
|
get activity$ (): Observable<boolean> { return this.activity }
|
||||||
|
|
||||||
constructor () {
|
constructor () {
|
||||||
this.id = BaseTabComponent.lastTabID++
|
this.id = BaseTabComponent.lastTabID++
|
||||||
@@ -34,8 +40,26 @@ export abstract class BaseTabComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setProgress (progress: number) {
|
||||||
|
this.progress.next(progress)
|
||||||
|
if (progress) {
|
||||||
|
if (this.progressClearTimeout) {
|
||||||
|
clearTimeout(this.progressClearTimeout)
|
||||||
|
}
|
||||||
|
this.progressClearTimeout = setTimeout(() => {
|
||||||
|
this.setProgress(null)
|
||||||
|
}, 5000)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
displayActivity (): void {
|
displayActivity (): void {
|
||||||
this.hasActivity = true
|
this.hasActivity = true
|
||||||
|
this.activity.next(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
clearActivity (): void {
|
||||||
|
this.hasActivity = false
|
||||||
|
this.activity.next(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
getRecoveryToken (): any {
|
getRecoveryToken (): any {
|
||||||
@@ -58,5 +82,6 @@ export abstract class BaseTabComponent {
|
|||||||
this.focused.complete()
|
this.focused.complete()
|
||||||
this.blurred.complete()
|
this.blurred.complete()
|
||||||
this.titleChange.complete()
|
this.titleChange.complete()
|
||||||
|
this.progress.complete()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
.icon((click)='click()', tabindex='0', [class.active]='model', (keyup.space)='click()')
|
.icon(tabindex='0', [class.active]='model', (keyup.space)='click()')
|
||||||
i.fa.fa-square-o.off
|
i.fa.fa-square-o.off
|
||||||
i.fa.fa-check-square.on
|
i.fa.fa-check-square.on
|
||||||
.text((click)='click()') {{text}}
|
.text {{text}}
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { NgZone, Component, Input } from '@angular/core'
|
import { NgZone, Component, Input, HostBinding, HostListener } from '@angular/core'
|
||||||
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'
|
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@@ -10,12 +10,12 @@ import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'
|
|||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class CheckboxComponent implements ControlValueAccessor {
|
export class CheckboxComponent implements ControlValueAccessor {
|
||||||
@Input() model: boolean
|
@HostBinding('class.active') @Input() model: boolean
|
||||||
@Input() disabled: boolean
|
@Input() disabled: boolean
|
||||||
@Input() text: string
|
@Input() text: string
|
||||||
private changed = new Array<(val: boolean) => void>()
|
private changed = new Array<(val: boolean) => void>()
|
||||||
|
|
||||||
click () {
|
@HostListener('click') click () {
|
||||||
NgZone.assertInAngularZone()
|
NgZone.assertInAngularZone()
|
||||||
if (this.disabled) {
|
if (this.disabled) {
|
||||||
return
|
return
|
||||||
|
@@ -4,21 +4,20 @@ div
|
|||||||
sup α
|
sup α
|
||||||
|
|
||||||
.list-group
|
.list-group
|
||||||
a.list-group-item.list-group-item-action(
|
a.list-group-item.list-group-item-action.d-flex(
|
||||||
*ngFor='let button of getButtons()',
|
*ngFor='let button of getButtons()',
|
||||||
(click)='button.click()',
|
(click)='button.click()',
|
||||||
)
|
)
|
||||||
.d-flex.align-self-center([innerHTML]='button.icon')
|
.d-flex.align-self-center([innerHTML]='button.icon')
|
||||||
span {{button.title}}
|
span {{button.title}}
|
||||||
|
|
||||||
footer
|
footer.d-flex.align-items-center
|
||||||
.pull-right
|
.btn-group.mr-auto
|
||||||
.form-control-static Version: {{version}}
|
button.btn.btn-secondary((click)='homeBase.openGitHub()')
|
||||||
|
|
||||||
.btn-group
|
|
||||||
button.btn.btn-secondary((click)='openGitHub()')
|
|
||||||
i.fa.fa-github
|
i.fa.fa-github
|
||||||
span GitHub
|
span GitHub
|
||||||
button.btn.btn-secondary((click)='reportBug()')
|
button.btn.btn-secondary((click)='homeBase.reportBug()')
|
||||||
i.fa.fa-bug
|
i.fa.fa-bug
|
||||||
span Report a problem
|
span Report a problem
|
||||||
|
|
||||||
|
.form-control-static.selectable.no-drag Version: {{homeBase.appVersion}}
|
||||||
|
@@ -3,6 +3,7 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
flex: auto;
|
flex: auto;
|
||||||
-webkit-app-region: drag;
|
-webkit-app-region: drag;
|
||||||
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
:host > div {
|
:host > div {
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
import * as os from 'os'
|
|
||||||
import { Component, Inject } from '@angular/core'
|
import { Component, Inject } from '@angular/core'
|
||||||
import { ElectronService } from '../services/electron.service'
|
|
||||||
import { ConfigService } from '../services/config.service'
|
import { ConfigService } from '../services/config.service'
|
||||||
|
import { HomeBaseService } from '../services/homeBase.service'
|
||||||
import { IToolbarButton, ToolbarButtonProvider } from '../api'
|
import { IToolbarButton, ToolbarButtonProvider } from '../api'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@@ -13,11 +12,10 @@ export class StartPageComponent {
|
|||||||
version: string
|
version: string
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
private electron: ElectronService,
|
|
||||||
private config: ConfigService,
|
private config: ConfigService,
|
||||||
|
public homeBase: HomeBaseService,
|
||||||
@Inject(ToolbarButtonProvider) private toolbarButtonProviders: ToolbarButtonProvider[],
|
@Inject(ToolbarButtonProvider) private toolbarButtonProviders: ToolbarButtonProvider[],
|
||||||
) {
|
) {
|
||||||
this.version = electron.app.getVersion()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getButtons (): IToolbarButton[] {
|
getButtons (): IToolbarButton[] {
|
||||||
@@ -26,19 +24,4 @@ export class StartPageComponent {
|
|||||||
.reduce((a, b) => a.concat(b))
|
.reduce((a, b) => a.concat(b))
|
||||||
.sort((a: IToolbarButton, b: IToolbarButton) => (a.weight || 0) - (b.weight || 0))
|
.sort((a: IToolbarButton, b: IToolbarButton) => (a.weight || 0) - (b.weight || 0))
|
||||||
}
|
}
|
||||||
|
|
||||||
openGitHub () {
|
|
||||||
this.electron.shell.openExternal('https://github.com/eugeny/terminus')
|
|
||||||
}
|
|
||||||
|
|
||||||
reportBug () {
|
|
||||||
let body = `Version: ${this.version}\n`
|
|
||||||
body += `Platform: ${os.platform()} ${os.release()}\n\n`
|
|
||||||
let label = {
|
|
||||||
darwin: 'macOS',
|
|
||||||
windows: 'Windows',
|
|
||||||
linux: 'Linux',
|
|
||||||
}[os.platform()]
|
|
||||||
this.electron.shell.openExternal(`https://github.com/eugeny/terminus/issues/new?body=${encodeURIComponent(body)}&labels=${label}`)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
.progressbar([style.width]='progress + "%"', *ngIf='progress != null')
|
||||||
.index(#handle) {{index + 1}}
|
.index(#handle) {{index + 1}}
|
||||||
.name([title]='tab.customTitle || tab.title') {{tab.customTitle || tab.title}}
|
.name([title]='tab.customTitle || tab.title') {{tab.customTitle || tab.title}}
|
||||||
button((click)='app.closeTab(tab, true)') ×
|
button((click)='app.closeTab(tab, true)') ×
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
$tabs-height: 36px;
|
$tabs-height: 36px;
|
||||||
|
|
||||||
:host {
|
:host {
|
||||||
|
position: relative;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
flex: 1000 1 200px;
|
flex: 1000 1 200px;
|
||||||
@@ -12,8 +13,6 @@ $tabs-height: 36px;
|
|||||||
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
transition: 0.125s ease-out all;
|
|
||||||
|
|
||||||
.index {
|
.index {
|
||||||
flex: none;
|
flex: none;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
@@ -43,12 +42,13 @@ $tabs-height: 36px;
|
|||||||
flex: none;
|
flex: none;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
|
-webkit-app-region: no-drag;
|
||||||
|
|
||||||
$button-size: 23px;
|
$button-size: 26px;
|
||||||
width: $button-size;
|
width: $button-size;
|
||||||
height: $button-size;
|
height: $button-size;
|
||||||
border-radius: $button-size / 2;
|
border-radius: $button-size / 2;
|
||||||
line-height: $button-size * 0.87;
|
line-height: $button-size * 0.9;
|
||||||
align-self: center;
|
align-self: center;
|
||||||
margin-right: 10px;
|
margin-right: 10px;
|
||||||
|
|
||||||
@@ -73,4 +73,13 @@ $tabs-height: 36px;
|
|||||||
&.fully-draggable {
|
&.fully-draggable {
|
||||||
cursor: -webkit-grab;
|
cursor: -webkit-grab;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.progressbar {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
height: 5px;
|
||||||
|
z-index: -1;
|
||||||
|
transition: 0.25s width;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -17,7 +17,9 @@ export class TabHeaderComponent {
|
|||||||
@Input() @HostBinding('class.active') active: boolean
|
@Input() @HostBinding('class.active') active: boolean
|
||||||
@Input() @HostBinding('class.has-activity') hasActivity: boolean
|
@Input() @HostBinding('class.has-activity') hasActivity: boolean
|
||||||
@Input() tab: BaseTabComponent
|
@Input() tab: BaseTabComponent
|
||||||
|
@Input() progress: number
|
||||||
@ViewChild('handle') handle: ElementRef
|
@ViewChild('handle') handle: ElementRef
|
||||||
|
|
||||||
private contextMenu: any
|
private contextMenu: any
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
@@ -74,6 +76,9 @@ export class TabHeaderComponent {
|
|||||||
if (this.hostApp.platform === Platform.macOS) {
|
if (this.hostApp.platform === Platform.macOS) {
|
||||||
this.parentDraggable.setDragHandle(this.handle.nativeElement)
|
this.parentDraggable.setDragHandle(this.handle.nativeElement)
|
||||||
}
|
}
|
||||||
|
this.tab.progress$.subscribe(progress => {
|
||||||
|
this.progress = progress
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@HostListener('dblclick') onDoubleClick (): void {
|
@HostListener('dblclick') onDoubleClick (): void {
|
||||||
|
70
terminus-core/src/components/toggle.component.scss
Normal file
70
terminus-core/src/components/toggle.component.scss
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
:host {
|
||||||
|
flex: none;
|
||||||
|
$toggle-size: 18px;
|
||||||
|
$height: 30px;
|
||||||
|
$padding: 2px;
|
||||||
|
cursor: pointer;
|
||||||
|
display: inline-flex;
|
||||||
|
overflow: visible;
|
||||||
|
border-radius: 3px;
|
||||||
|
line-height: $height;
|
||||||
|
height: $height;
|
||||||
|
transition: 0.25s opacity;
|
||||||
|
align-items: center;
|
||||||
|
overflow: hidden;
|
||||||
|
padding-right: 10px;
|
||||||
|
padding-left: 10px;
|
||||||
|
margin-left: -10px;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
background: rgba(255,255,255,.05);
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&[disabled] {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.body {
|
||||||
|
$border-width: 2px;
|
||||||
|
border-radius: 5px;
|
||||||
|
border: $border-width solid rgba(255, 255, 255, .2);
|
||||||
|
padding: $padding;
|
||||||
|
height: $toggle-size + $border-width * 2 + $padding * 2;
|
||||||
|
width: $toggle-size * 2 + $border-width * 2 + $padding * 2;
|
||||||
|
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.toggle {
|
||||||
|
position: absolute;
|
||||||
|
border-radius: 2px;
|
||||||
|
width: $toggle-size;
|
||||||
|
height: $toggle-size;
|
||||||
|
background: #475158;
|
||||||
|
top: $padding;
|
||||||
|
left: $padding;
|
||||||
|
transition: 0.25s left;
|
||||||
|
line-height: 19px;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 10px;
|
||||||
|
|
||||||
|
i {
|
||||||
|
opacity: 0;
|
||||||
|
transition: 0.25s opacity;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active .body .toggle {
|
||||||
|
left: $toggle-size + $padding;
|
||||||
|
|
||||||
|
i {
|
||||||
|
color: white;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
background: rgba(255,255,255,.1);
|
||||||
|
}
|
||||||
|
}
|
22
terminus-core/src/components/toggle.component.ts
Normal file
22
terminus-core/src/components/toggle.component.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { Component } from '@angular/core'
|
||||||
|
import { NG_VALUE_ACCESSOR } from '@angular/forms'
|
||||||
|
import { CheckboxComponent } from './checkbox.component'
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'toggle',
|
||||||
|
template: `
|
||||||
|
<div class="switch">
|
||||||
|
<div class="body">
|
||||||
|
<div class="toggle" [class.bg-primary]='model'>
|
||||||
|
<i class="fa fa-check"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
styles: [require('./toggle.component.scss')],
|
||||||
|
providers: [
|
||||||
|
{ provide: NG_VALUE_ACCESSOR, useExisting: ToggleComponent, multi: true },
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class ToggleComponent extends CheckboxComponent {
|
||||||
|
}
|
@@ -9,7 +9,7 @@ button.btn.btn-secondary.btn-maximize(
|
|||||||
svg(version='1.1', width='10', height='10')
|
svg(version='1.1', width='10', height='10')
|
||||||
path(d='M 0,0 0,10 10,10 10,0 Z M 1,1 9,1 9,9 1,9 Z')
|
path(d='M 0,0 0,10 10,10 10,0 Z M 1,1 9,1 9,9 1,9 Z')
|
||||||
button.btn.btn-secondary.btn-close(
|
button.btn.btn-secondary.btn-close(
|
||||||
(click)='hostApp.quit()',
|
(click)='hostApp.getWindow().close()',
|
||||||
)
|
)
|
||||||
svg(version='1.1', width='10', height='10')
|
svg(version='1.1', width='10', height='10')
|
||||||
path(d='M 0,0 0,0.7 4.3,5 0,9.3 0,10 0.7,10 5,5.7 9.3,10 10,10 10,9.3 5.7,5 10,0.7 10,0 9.3,0 5,4.3 0.7,0 Z')
|
path(d='M 0,0 0,0.7 4.3,5 0,9.3 0,10 0.7,10 5,5.7 9.3,10 10,10 10,9.3 5.7,5 10,0.7 10,0 9.3,0 5,4.3 0.7,0 Z')
|
||||||
|
@@ -1,4 +1,6 @@
|
|||||||
hotkeys:
|
hotkeys:
|
||||||
|
new-window:
|
||||||
|
- 'Ctrl-Shift-N'
|
||||||
toggle-window:
|
toggle-window:
|
||||||
- 'Ctrl+Space'
|
- 'Ctrl+Space'
|
||||||
toggle-fullscreen:
|
toggle-fullscreen:
|
||||||
|
@@ -1,4 +1,6 @@
|
|||||||
hotkeys:
|
hotkeys:
|
||||||
|
new-window:
|
||||||
|
- '⌘-N'
|
||||||
toggle-window:
|
toggle-window:
|
||||||
- 'Ctrl+Space'
|
- 'Ctrl+Space'
|
||||||
toggle-fullscreen:
|
toggle-fullscreen:
|
||||||
|
@@ -1,4 +1,6 @@
|
|||||||
hotkeys:
|
hotkeys:
|
||||||
|
new-window:
|
||||||
|
- 'Ctrl-Shift-N'
|
||||||
toggle-window:
|
toggle-window:
|
||||||
- 'Ctrl+Space'
|
- 'Ctrl+Space'
|
||||||
toggle-fullscreen:
|
toggle-fullscreen:
|
||||||
|
@@ -9,3 +9,4 @@ appearance:
|
|||||||
css: '/* * { color: blue !important; } */'
|
css: '/* * { color: blue !important; } */'
|
||||||
opacity: 1.0
|
opacity: 1.0
|
||||||
vibrancy: false
|
vibrancy: false
|
||||||
|
enableAnalytics: true
|
||||||
|
15
terminus-core/src/directives/autofocus.directive.ts
Normal file
15
terminus-core/src/directives/autofocus.directive.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { Directive, AfterViewInit, ElementRef } from '@angular/core'
|
||||||
|
|
||||||
|
@Directive({
|
||||||
|
selector: '[autofocus]'
|
||||||
|
})
|
||||||
|
export class AutofocusDirective implements AfterViewInit {
|
||||||
|
constructor (private el: ElementRef) { }
|
||||||
|
|
||||||
|
ngAfterViewInit () {
|
||||||
|
this.el.nativeElement.blur()
|
||||||
|
setTimeout(() => {
|
||||||
|
this.el.nativeElement.focus()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@@ -11,6 +11,7 @@ import { ConfigService } from './services/config.service'
|
|||||||
import { ElectronService } from './services/electron.service'
|
import { ElectronService } from './services/electron.service'
|
||||||
import { HostAppService } from './services/hostApp.service'
|
import { HostAppService } from './services/hostApp.service'
|
||||||
import { LogService } from './services/log.service'
|
import { LogService } from './services/log.service'
|
||||||
|
import { HomeBaseService } from './services/homeBase.service'
|
||||||
import { HotkeysService, AppHotkeyProvider } from './services/hotkeys.service'
|
import { HotkeysService, AppHotkeyProvider } from './services/hotkeys.service'
|
||||||
import { DockingService } from './services/docking.service'
|
import { DockingService } from './services/docking.service'
|
||||||
import { TabRecoveryService } from './services/tabRecovery.service'
|
import { TabRecoveryService } from './services/tabRecovery.service'
|
||||||
@@ -25,9 +26,12 @@ import { SafeModeModalComponent } from './components/safeModeModal.component'
|
|||||||
import { StartPageComponent } from './components/startPage.component'
|
import { StartPageComponent } from './components/startPage.component'
|
||||||
import { TabHeaderComponent } from './components/tabHeader.component'
|
import { TabHeaderComponent } from './components/tabHeader.component'
|
||||||
import { TitleBarComponent } from './components/titleBar.component'
|
import { TitleBarComponent } from './components/titleBar.component'
|
||||||
|
import { ToggleComponent } from './components/toggle.component'
|
||||||
import { WindowControlsComponent } from './components/windowControls.component'
|
import { WindowControlsComponent } from './components/windowControls.component'
|
||||||
import { RenameTabModalComponent } from './components/renameTabModal.component'
|
import { RenameTabModalComponent } from './components/renameTabModal.component'
|
||||||
|
|
||||||
|
import { AutofocusDirective } from './directives/autofocus.directive'
|
||||||
|
|
||||||
import { HotkeyProvider } from './api/hotkeyProvider'
|
import { HotkeyProvider } from './api/hotkeyProvider'
|
||||||
import { ConfigProvider } from './api/configProvider'
|
import { ConfigProvider } from './api/configProvider'
|
||||||
import { Theme } from './api/theme'
|
import { Theme } from './api/theme'
|
||||||
@@ -43,6 +47,7 @@ const PROVIDERS = [
|
|||||||
ConfigService,
|
ConfigService,
|
||||||
DockingService,
|
DockingService,
|
||||||
ElectronService,
|
ElectronService,
|
||||||
|
HomeBaseService,
|
||||||
HostAppService,
|
HostAppService,
|
||||||
HotkeysService,
|
HotkeysService,
|
||||||
LogService,
|
LogService,
|
||||||
@@ -73,16 +78,20 @@ const PROVIDERS = [
|
|||||||
TabBodyComponent,
|
TabBodyComponent,
|
||||||
TabHeaderComponent,
|
TabHeaderComponent,
|
||||||
TitleBarComponent,
|
TitleBarComponent,
|
||||||
|
ToggleComponent,
|
||||||
WindowControlsComponent,
|
WindowControlsComponent,
|
||||||
RenameTabModalComponent,
|
RenameTabModalComponent,
|
||||||
SafeModeModalComponent,
|
SafeModeModalComponent,
|
||||||
|
AutofocusDirective,
|
||||||
],
|
],
|
||||||
entryComponents: [
|
entryComponents: [
|
||||||
RenameTabModalComponent,
|
RenameTabModalComponent,
|
||||||
SafeModeModalComponent,
|
SafeModeModalComponent,
|
||||||
],
|
],
|
||||||
exports: [
|
exports: [
|
||||||
CheckboxComponent
|
CheckboxComponent,
|
||||||
|
ToggleComponent,
|
||||||
|
AutofocusDirective,
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export default class AppModule {
|
export default class AppModule {
|
||||||
|
@@ -1,9 +1,9 @@
|
|||||||
import { Observable, Subject, AsyncSubject } from 'rxjs'
|
import { Observable, Subject, AsyncSubject } from 'rxjs'
|
||||||
import { Injectable, ComponentFactoryResolver, Injector, Optional } from '@angular/core'
|
import { Injectable, ComponentFactoryResolver, Injector } from '@angular/core'
|
||||||
import { DefaultTabProvider } from '../api/defaultTabProvider'
|
|
||||||
import { BaseTabComponent } from '../components/baseTab.component'
|
import { BaseTabComponent } from '../components/baseTab.component'
|
||||||
import { Logger, LogService } from './log.service'
|
import { Logger, LogService } from './log.service'
|
||||||
import { ConfigService } from './config.service'
|
import { ConfigService } from './config.service'
|
||||||
|
import { HostAppService } from './hostApp.service'
|
||||||
|
|
||||||
export declare type TabComponentType = new (...args: any[]) => BaseTabComponent
|
export declare type TabComponentType = new (...args: any[]) => BaseTabComponent
|
||||||
|
|
||||||
@@ -28,8 +28,8 @@ export class AppService {
|
|||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
private componentFactoryResolver: ComponentFactoryResolver,
|
private componentFactoryResolver: ComponentFactoryResolver,
|
||||||
@Optional() private defaultTabProvider: DefaultTabProvider,
|
|
||||||
private config: ConfigService,
|
private config: ConfigService,
|
||||||
|
private hostApp: HostAppService,
|
||||||
private injector: Injector,
|
private injector: Injector,
|
||||||
log: LogService,
|
log: LogService,
|
||||||
) {
|
) {
|
||||||
@@ -39,25 +39,26 @@ export class AppService {
|
|||||||
openNewTab (type: TabComponentType, inputs?: any): BaseTabComponent {
|
openNewTab (type: TabComponentType, inputs?: any): BaseTabComponent {
|
||||||
let componentFactory = this.componentFactoryResolver.resolveComponentFactory(type)
|
let componentFactory = this.componentFactoryResolver.resolveComponentFactory(type)
|
||||||
let componentRef = componentFactory.create(this.injector)
|
let componentRef = componentFactory.create(this.injector)
|
||||||
componentRef.instance.hostView = componentRef.hostView
|
let tab = componentRef.instance
|
||||||
Object.assign(componentRef.instance, inputs || {})
|
tab.hostView = componentRef.hostView
|
||||||
|
Object.assign(tab, inputs || {})
|
||||||
|
|
||||||
this.tabs.push(componentRef.instance)
|
this.tabs.push(tab)
|
||||||
this.selectTab(componentRef.instance)
|
this.selectTab(tab)
|
||||||
this.tabsChanged.next()
|
this.tabsChanged.next()
|
||||||
this.tabOpened.next(componentRef.instance)
|
this.tabOpened.next(tab)
|
||||||
|
|
||||||
return componentRef.instance
|
tab.titleChange$.subscribe(title => {
|
||||||
}
|
if (tab === this.activeTab) {
|
||||||
|
this.hostApp.setTitle(title)
|
||||||
openDefaultTab (): void {
|
}
|
||||||
if (this.defaultTabProvider) {
|
})
|
||||||
this.defaultTabProvider.openNewTab()
|
return tab
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
selectTab (tab: BaseTabComponent) {
|
selectTab (tab: BaseTabComponent) {
|
||||||
if (this.activeTab === tab) {
|
if (this.activeTab === tab) {
|
||||||
|
this.activeTab.emitFocused()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (this.tabs.includes(this.activeTab)) {
|
if (this.tabs.includes(this.activeTab)) {
|
||||||
@@ -66,7 +67,7 @@ export class AppService {
|
|||||||
this.lastTabIndex = null
|
this.lastTabIndex = null
|
||||||
}
|
}
|
||||||
if (this.activeTab) {
|
if (this.activeTab) {
|
||||||
this.activeTab.hasActivity = false
|
this.activeTab.clearActivity()
|
||||||
this.activeTab.emitBlurred()
|
this.activeTab.emitBlurred()
|
||||||
}
|
}
|
||||||
this.activeTab = tab
|
this.activeTab = tab
|
||||||
@@ -74,6 +75,7 @@ export class AppService {
|
|||||||
if (this.activeTab) {
|
if (this.activeTab) {
|
||||||
this.activeTab.emitFocused()
|
this.activeTab.emitFocused()
|
||||||
}
|
}
|
||||||
|
this.hostApp.setTitle(this.activeTab.title)
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleLastTab () {
|
toggleLastTab () {
|
||||||
@@ -129,5 +131,6 @@ export class AppService {
|
|||||||
emitReady () {
|
emitReady () {
|
||||||
this.ready.next(null)
|
this.ready.next(null)
|
||||||
this.ready.complete()
|
this.ready.complete()
|
||||||
|
this.hostApp.emitReady()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -63,7 +63,7 @@ export class ConfigService {
|
|||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
electron: ElectronService,
|
electron: ElectronService,
|
||||||
hostApp: HostAppService,
|
private hostApp: HostAppService,
|
||||||
@Inject(ConfigProvider) configProviders: ConfigProvider[],
|
@Inject(ConfigProvider) configProviders: ConfigProvider[],
|
||||||
) {
|
) {
|
||||||
this.path = path.join(electron.app.getPath('userData'), 'config.yaml')
|
this.path = path.join(electron.app.getPath('userData'), 'config.yaml')
|
||||||
@@ -78,6 +78,15 @@ export class ConfigService {
|
|||||||
return defaults
|
return defaults
|
||||||
}).reduce(configMerge)
|
}).reduce(configMerge)
|
||||||
this.load()
|
this.load()
|
||||||
|
|
||||||
|
hostApp.configChangeBroadcast$.subscribe(() => {
|
||||||
|
this.load()
|
||||||
|
this.emitChange()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
getDefaults () {
|
||||||
|
return this.defaults
|
||||||
}
|
}
|
||||||
|
|
||||||
load (): void {
|
load (): void {
|
||||||
@@ -92,6 +101,18 @@ export class ConfigService {
|
|||||||
save (): void {
|
save (): void {
|
||||||
fs.writeFileSync(this.path, yaml.safeDump(this._store), 'utf8')
|
fs.writeFileSync(this.path, yaml.safeDump(this._store), 'utf8')
|
||||||
this.emitChange()
|
this.emitChange()
|
||||||
|
this.hostApp.broadcastConfigChange()
|
||||||
|
}
|
||||||
|
|
||||||
|
readRaw (): string {
|
||||||
|
return yaml.safeDump(this._store)
|
||||||
|
}
|
||||||
|
|
||||||
|
writeRaw (data: string): void {
|
||||||
|
this._store = yaml.safeLoad(data)
|
||||||
|
this.save()
|
||||||
|
this.load()
|
||||||
|
this.emitChange()
|
||||||
}
|
}
|
||||||
|
|
||||||
emitChange (): void {
|
emitChange (): void {
|
||||||
|
@@ -76,12 +76,8 @@ export class DockingService {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
getWindow () {
|
|
||||||
return this.electron.app.window
|
|
||||||
}
|
|
||||||
|
|
||||||
repositionWindow () {
|
repositionWindow () {
|
||||||
let [x, y] = this.getWindow().getPosition()
|
let [x, y] = this.hostApp.getWindow().getPosition()
|
||||||
for (let screen of this.electron.screen.getAllDisplays()) {
|
for (let screen of this.electron.screen.getAllDisplays()) {
|
||||||
let bounds = screen.bounds
|
let bounds = screen.bounds
|
||||||
if (x >= bounds.x && x <= bounds.x + bounds.width && y >= bounds.y && y <= bounds.y + bounds.height) {
|
if (x >= bounds.x && x <= bounds.x + bounds.width && y >= bounds.y && y <= bounds.y + bounds.height) {
|
||||||
@@ -89,6 +85,6 @@ export class DockingService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
let screen = this.electron.screen.getPrimaryDisplay()
|
let screen = this.electron.screen.getPrimaryDisplay()
|
||||||
this.getWindow().setPosition(screen.bounds.x, screen.bounds.y)
|
this.hostApp.getWindow().setPosition(screen.bounds.x, screen.bounds.y)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import { Injectable } from '@angular/core'
|
import { Injectable } from '@angular/core'
|
||||||
import { TouchBar } from 'electron'
|
import { TouchBar, BrowserWindow } from 'electron'
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ElectronService {
|
export class ElectronService {
|
||||||
@@ -9,9 +9,11 @@ export class ElectronService {
|
|||||||
dialog: any
|
dialog: any
|
||||||
clipboard: any
|
clipboard: any
|
||||||
globalShortcut: any
|
globalShortcut: any
|
||||||
|
nativeImage: any
|
||||||
screen: any
|
screen: any
|
||||||
remote: any
|
remote: any
|
||||||
TouchBar: typeof TouchBar
|
TouchBar: typeof TouchBar
|
||||||
|
BrowserWindow: typeof BrowserWindow
|
||||||
private electron: any
|
private electron: any
|
||||||
|
|
||||||
constructor () {
|
constructor () {
|
||||||
@@ -24,7 +26,9 @@ export class ElectronService {
|
|||||||
this.clipboard = this.electron.clipboard
|
this.clipboard = this.electron.clipboard
|
||||||
this.ipcRenderer = this.electron.ipcRenderer
|
this.ipcRenderer = this.electron.ipcRenderer
|
||||||
this.globalShortcut = this.remote.globalShortcut
|
this.globalShortcut = this.remote.globalShortcut
|
||||||
|
this.nativeImage = this.remote.nativeImage
|
||||||
this.TouchBar = this.remote.TouchBar
|
this.TouchBar = this.remote.TouchBar
|
||||||
|
this.BrowserWindow = this.remote.BrowserWindow
|
||||||
}
|
}
|
||||||
|
|
||||||
remoteRequire (name: string): any {
|
remoteRequire (name: string): any {
|
||||||
|
45
terminus-core/src/services/homeBase.service.ts
Normal file
45
terminus-core/src/services/homeBase.service.ts
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import * as os from 'os'
|
||||||
|
import { Injectable } from '@angular/core'
|
||||||
|
import { ElectronService } from './electron.service'
|
||||||
|
import { ConfigService } from './config.service'
|
||||||
|
import ua = require('universal-analytics')
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class HomeBaseService {
|
||||||
|
appVersion: string
|
||||||
|
|
||||||
|
constructor (
|
||||||
|
private electron: ElectronService,
|
||||||
|
private config: ConfigService,
|
||||||
|
) {
|
||||||
|
this.appVersion = electron.app.getVersion()
|
||||||
|
|
||||||
|
if (this.config.store.enableAnalytics) {
|
||||||
|
this.enableAnalytics()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
openGitHub () {
|
||||||
|
this.electron.shell.openExternal('https://github.com/eugeny/terminus')
|
||||||
|
}
|
||||||
|
|
||||||
|
reportBug () {
|
||||||
|
let body = `Version: ${this.appVersion}\n`
|
||||||
|
body += `Platform: ${os.platform()} ${os.release()}\n`
|
||||||
|
let label = {
|
||||||
|
darwin: 'OS: macOS',
|
||||||
|
windows: 'OS: Windows',
|
||||||
|
linux: 'OS: Linux',
|
||||||
|
}[os.platform()]
|
||||||
|
let plugins = (window as any).installedPlugins.filter(x => !x.isBuiltin).map(x => x.name)
|
||||||
|
body += `Plugins: ${plugins.join(', ')}\n\n`
|
||||||
|
this.electron.shell.openExternal(`https://github.com/eugeny/terminus/issues/new?body=${encodeURIComponent(body)}&labels=${label}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
enableAnalytics () {
|
||||||
|
const session = ua('UA-3278102-20')
|
||||||
|
session.set('cd1', this.appVersion)
|
||||||
|
session.set('cd2', process.platform)
|
||||||
|
session.pageview('/').send()
|
||||||
|
}
|
||||||
|
}
|
@@ -1,7 +1,8 @@
|
|||||||
|
import * as path from 'path'
|
||||||
import { Observable, Subject } from 'rxjs'
|
import { Observable, Subject } from 'rxjs'
|
||||||
import { Injectable, NgZone, EventEmitter } from '@angular/core'
|
import { Injectable, NgZone, EventEmitter } from '@angular/core'
|
||||||
import { ElectronService } from '../services/electron.service'
|
import { ElectronService } from './electron.service'
|
||||||
import { Logger, LogService } from '../services/log.service'
|
import { Logger, LogService } from './log.service'
|
||||||
|
|
||||||
export enum Platform {
|
export enum Platform {
|
||||||
Linux, macOS, Windows,
|
Linux, macOS, Windows,
|
||||||
@@ -14,24 +15,25 @@ export interface Bounds {
|
|||||||
height: number
|
height: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SecondInstanceArgs {
|
|
||||||
argv: string[],
|
|
||||||
cwd: string
|
|
||||||
}
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class HostAppService {
|
export class HostAppService {
|
||||||
platform: Platform
|
platform: Platform
|
||||||
nodePlatform: string
|
nodePlatform: string
|
||||||
ready = new EventEmitter<any>()
|
|
||||||
shown = new EventEmitter<any>()
|
shown = new EventEmitter<any>()
|
||||||
isFullScreen = false
|
isFullScreen = false
|
||||||
private preferencesMenu = new Subject<void>()
|
private preferencesMenu = new Subject<void>()
|
||||||
private secondInstance = new Subject<SecondInstanceArgs>()
|
private secondInstance = new Subject<void>()
|
||||||
|
private cliOpenDirectory = new Subject<string>()
|
||||||
|
private cliRunCommand = new Subject<string[]>()
|
||||||
|
private configChangeBroadcast = new Subject<void>()
|
||||||
private logger: Logger
|
private logger: Logger
|
||||||
|
private windowId: number
|
||||||
|
|
||||||
get preferencesMenu$ (): Observable<void> { return this.preferencesMenu }
|
get preferencesMenu$ (): Observable<void> { return this.preferencesMenu }
|
||||||
get secondInstance$ (): Observable<SecondInstanceArgs> { return this.secondInstance }
|
get secondInstance$ (): Observable<void> { return this.secondInstance }
|
||||||
|
get cliOpenDirectory$ (): Observable<string> { return this.cliOpenDirectory }
|
||||||
|
get cliRunCommand$ (): Observable<string[]> { return this.cliRunCommand }
|
||||||
|
get configChangeBroadcast$ (): Observable<void> { return this.configChangeBroadcast }
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
private zone: NgZone,
|
private zone: NgZone,
|
||||||
@@ -46,9 +48,12 @@ export class HostAppService {
|
|||||||
linux: Platform.Linux
|
linux: Platform.Linux
|
||||||
}[this.nodePlatform]
|
}[this.nodePlatform]
|
||||||
|
|
||||||
|
this.windowId = parseInt(location.search.substring(1))
|
||||||
|
this.logger.info('Window ID:', this.windowId)
|
||||||
|
|
||||||
electron.ipcRenderer.on('host:preferences-menu', () => this.zone.run(() => this.preferencesMenu.next()))
|
electron.ipcRenderer.on('host:preferences-menu', () => this.zone.run(() => this.preferencesMenu.next()))
|
||||||
|
|
||||||
electron.ipcRenderer.on('uncaughtException', ($event, err) => {
|
electron.ipcRenderer.on('uncaughtException', (_$event, err) => {
|
||||||
this.logger.error('Unhandled exception:', err)
|
this.logger.error('Unhandled exception:', err)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -64,17 +69,27 @@ export class HostAppService {
|
|||||||
this.zone.run(() => this.shown.emit())
|
this.zone.run(() => this.shown.emit())
|
||||||
})
|
})
|
||||||
|
|
||||||
electron.ipcRenderer.on('host:second-instance', ($event, argv: string[], cwd: string) => {
|
electron.ipcRenderer.on('host:second-instance', (_$event, argv: any, cwd: string) => this.zone.run(() => {
|
||||||
this.zone.run(() => this.secondInstance.next({ argv, cwd }))
|
this.logger.info('Second instance', argv)
|
||||||
})
|
const op = argv._[0]
|
||||||
|
if (op === 'open') {
|
||||||
|
this.cliOpenDirectory.next(path.resolve(cwd, argv.directory))
|
||||||
|
} else if (op === 'run') {
|
||||||
|
this.cliRunCommand.next(argv.command)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
this.ready.subscribe(() => {
|
electron.ipcRenderer.on('host:config-change', () => this.zone.run(() => {
|
||||||
electron.ipcRenderer.send('app:ready')
|
this.configChangeBroadcast.next()
|
||||||
})
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
getWindow () {
|
getWindow () {
|
||||||
return this.electron.app.window
|
return this.electron.BrowserWindow.fromId(this.windowId)
|
||||||
|
}
|
||||||
|
|
||||||
|
newWindow () {
|
||||||
|
this.electron.ipcRenderer.send('app:new-window')
|
||||||
}
|
}
|
||||||
|
|
||||||
getShell () {
|
getShell () {
|
||||||
@@ -136,6 +151,26 @@ export class HostAppService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setTitle (title: string) {
|
||||||
|
this.electron.ipcRenderer.send('window-set-title', title)
|
||||||
|
}
|
||||||
|
|
||||||
|
setTouchBar (touchBar: Electron.TouchBar) {
|
||||||
|
this.getWindow().setTouchBar(touchBar)
|
||||||
|
}
|
||||||
|
|
||||||
|
popupContextMenu (menuDefinition: Electron.MenuItemConstructorOptions[]) {
|
||||||
|
this.electron.ipcRenderer.send('window-popup-context-menu', menuDefinition)
|
||||||
|
}
|
||||||
|
|
||||||
|
broadcastConfigChange () {
|
||||||
|
this.electron.ipcRenderer.send('app:config-change')
|
||||||
|
}
|
||||||
|
|
||||||
|
emitReady () {
|
||||||
|
this.electron.ipcRenderer.send('app:ready')
|
||||||
|
}
|
||||||
|
|
||||||
quit () {
|
quit () {
|
||||||
this.logger.info('Quitting')
|
this.logger.info('Quitting')
|
||||||
this.electron.app.quit()
|
this.electron.app.quit()
|
||||||
|
@@ -174,6 +174,10 @@ export class HotkeysService {
|
|||||||
@Injectable()
|
@Injectable()
|
||||||
export class AppHotkeyProvider extends HotkeyProvider {
|
export class AppHotkeyProvider extends HotkeyProvider {
|
||||||
hotkeys: IHotkeyDescription[] = [
|
hotkeys: IHotkeyDescription[] = [
|
||||||
|
{
|
||||||
|
id: 'new-window',
|
||||||
|
name: 'New window',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: 'toggle-window',
|
id: 'toggle-window',
|
||||||
name: 'Toggle terminal window',
|
name: 'Toggle terminal window',
|
||||||
|
@@ -1,20 +1,20 @@
|
|||||||
import { Injectable, Inject, NgZone } from '@angular/core'
|
import { Injectable, Inject, NgZone } from '@angular/core'
|
||||||
import { TouchBarSegmentedControl, SegmentedControlSegment } from 'electron'
|
import { TouchBarSegmentedControl, SegmentedControlSegment } from 'electron'
|
||||||
import { Subscription } from 'rxjs'
|
|
||||||
import { AppService } from './app.service'
|
import { AppService } from './app.service'
|
||||||
import { ConfigService } from './config.service'
|
import { ConfigService } from './config.service'
|
||||||
import { ElectronService } from './electron.service'
|
import { ElectronService } from './electron.service'
|
||||||
import { BaseTabComponent } from '../components/baseTab.component'
|
import { HostAppService } from './hostApp.service'
|
||||||
import { IToolbarButton, ToolbarButtonProvider } from '../api'
|
import { IToolbarButton, ToolbarButtonProvider } from '../api'
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class TouchbarService {
|
export class TouchbarService {
|
||||||
private titleSubscriptions = new Map<BaseTabComponent, Subscription>()
|
|
||||||
private tabsSegmentedControl: TouchBarSegmentedControl
|
private tabsSegmentedControl: TouchBarSegmentedControl
|
||||||
private tabSegments: SegmentedControlSegment[] = []
|
private tabSegments: SegmentedControlSegment[] = []
|
||||||
|
private nsImageCache: {[id: string]: Electron.NativeImage} = {}
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
private app: AppService,
|
private app: AppService,
|
||||||
|
private hostApp: HostAppService,
|
||||||
@Inject(ToolbarButtonProvider) private toolbarButtonProviders: ToolbarButtonProvider[],
|
@Inject(ToolbarButtonProvider) private toolbarButtonProviders: ToolbarButtonProvider[],
|
||||||
private config: ConfigService,
|
private config: ConfigService,
|
||||||
private electron: ElectronService,
|
private electron: ElectronService,
|
||||||
@@ -23,15 +23,10 @@ export class TouchbarService {
|
|||||||
app.tabsChanged$.subscribe(() => this.update())
|
app.tabsChanged$.subscribe(() => this.update())
|
||||||
app.activeTabChange$.subscribe(() => this.update())
|
app.activeTabChange$.subscribe(() => this.update())
|
||||||
app.tabOpened$.subscribe(tab => {
|
app.tabOpened$.subscribe(tab => {
|
||||||
let sub = tab.titleChange$.subscribe(title => {
|
tab.titleChange$.subscribe(title => {
|
||||||
this.tabSegments[app.tabs.indexOf(tab)].label = this.shortenTitle(title)
|
this.tabSegments[app.tabs.indexOf(tab)].label = this.shortenTitle(title)
|
||||||
this.tabsSegmentedControl.segments = this.tabSegments
|
this.tabsSegmentedControl.segments = this.tabSegments
|
||||||
})
|
})
|
||||||
this.titleSubscriptions.set(tab, sub)
|
|
||||||
})
|
|
||||||
app.tabClosed$.subscribe(tab => {
|
|
||||||
this.titleSubscriptions.get(tab).unsubscribe()
|
|
||||||
this.titleSubscriptions.delete(tab)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,14 +51,25 @@ export class TouchbarService {
|
|||||||
this.tabsSegmentedControl,
|
this.tabsSegmentedControl,
|
||||||
new this.electron.TouchBar.TouchBarSpacer({size: 'flexible'}),
|
new this.electron.TouchBar.TouchBarSpacer({size: 'flexible'}),
|
||||||
new this.electron.TouchBar.TouchBarSpacer({size: 'small'}),
|
new this.electron.TouchBar.TouchBarSpacer({size: 'small'}),
|
||||||
...buttons.map(button => new this.electron.TouchBar.TouchBarButton({
|
...buttons.map(button => this.getButton(button))
|
||||||
label: this.shortenTitle(button.touchBarTitle || button.title),
|
|
||||||
// backgroundColor: '#0022cc',
|
|
||||||
click: () => this.zone.run(() => button.click()),
|
|
||||||
}))
|
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
this.electron.app.window.setTouchBar(touchBar)
|
this.hostApp.setTouchBar(touchBar)
|
||||||
|
}
|
||||||
|
|
||||||
|
private getButton (button: IToolbarButton): Electron.TouchBarButton {
|
||||||
|
return new this.electron.TouchBar.TouchBarButton({
|
||||||
|
label: button.touchBarNSImage ? null : this.shortenTitle(button.touchBarTitle || button.title),
|
||||||
|
icon: button.touchBarNSImage ? this.getCachedNSImage(button.touchBarNSImage) : null,
|
||||||
|
click: () => this.zone.run(() => button.click()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private getCachedNSImage (name: string) {
|
||||||
|
if (!this.nsImageCache[name]) {
|
||||||
|
this.nsImageCache[name] = this.electron.nativeImage.createFromNamedImage(name, [0, 0, 1])
|
||||||
|
}
|
||||||
|
return this.nsImageCache[name]
|
||||||
}
|
}
|
||||||
|
|
||||||
private shortenTitle (title: string): string {
|
private shortenTitle (title: string): string {
|
||||||
|
@@ -14,13 +14,17 @@ $teal: #5bc0de !default;
|
|||||||
$pink: #ff5b77 !default;
|
$pink: #ff5b77 !default;
|
||||||
$purple: #613d7c !default;
|
$purple: #613d7c !default;
|
||||||
|
|
||||||
|
$theme-colors: (
|
||||||
|
"primary": $blue,
|
||||||
|
"secondary": #394b5d
|
||||||
|
);
|
||||||
|
|
||||||
$content-bg: rgba(39, 49, 60, 0.65); //#1D272D;
|
$content-bg: rgba(39, 49, 60, 0.65); //#1D272D;
|
||||||
$content-bg-solid: #1D272D;
|
$content-bg-solid: #1D272D;
|
||||||
$body-bg: #131d27;
|
$body-bg: #131d27;
|
||||||
$body-bg2: #20333e;
|
$body-bg2: #20333e;
|
||||||
|
|
||||||
$body-color: #aaa;
|
$body-color: #ccc;
|
||||||
$font-family-sans-serif: "Source Sans Pro";
|
$font-family-sans-serif: "Source Sans Pro";
|
||||||
$font-size-base: 14rem / 16;
|
$font-size-base: 14rem / 16;
|
||||||
|
|
||||||
@@ -32,16 +36,16 @@ $btn-secondary-border: #444;
|
|||||||
//$btn-warning-bg: rgba($orange, .5);
|
//$btn-warning-bg: rgba($orange, .5);
|
||||||
|
|
||||||
|
|
||||||
$nav-tabs-border-color: $body-bg;
|
$nav-tabs-border-width: 0;
|
||||||
$nav-tabs-border-width: 1px;
|
|
||||||
$nav-tabs-border-radius: 0;
|
$nav-tabs-border-radius: 0;
|
||||||
$nav-tabs-link-hover-border-color: $body-bg;
|
$nav-tabs-link-hover-border-color: $body-bg;
|
||||||
$nav-tabs-active-link-hover-color: $white;
|
$nav-tabs-active-link-hover-color: $white;
|
||||||
$nav-tabs-active-link-hover-bg: $blue;
|
$nav-tabs-active-link-hover-bg: $blue;
|
||||||
$nav-tabs-active-link-hover-border-color: darken($blue, 30%);
|
$nav-tabs-active-link-hover-border-color: darken($blue, 30%);
|
||||||
|
$nav-pills-border-radius: 0;
|
||||||
|
|
||||||
$input-bg: #111;
|
$input-bg: #111;
|
||||||
$input-bg-disabled: #333;
|
$input-disabled-bg: #333;
|
||||||
|
|
||||||
$input-color: $body-color;
|
$input-color: $body-color;
|
||||||
$input-color-placeholder: #333;
|
$input-color-placeholder: #333;
|
||||||
@@ -78,6 +82,10 @@ $list-group-border-color: rgba(255,255,255,.1);
|
|||||||
$list-group-hover-bg: rgba(255,255,255,.1);
|
$list-group-hover-bg: rgba(255,255,255,.1);
|
||||||
$list-group-link-active-bg: rgba(255,255,255,.2);
|
$list-group-link-active-bg: rgba(255,255,255,.2);
|
||||||
|
|
||||||
|
$list-group-action-color: $body-color;
|
||||||
|
$list-group-action-bg: rgba(255,255,255,.05);
|
||||||
|
$list-group-action-active-bg: $list-group-link-active-bg;
|
||||||
|
|
||||||
$pre-bg: $dropdown-bg;
|
$pre-bg: $dropdown-bg;
|
||||||
$pre-color: $dropdown-link-color;
|
$pre-color: $dropdown-link-color;
|
||||||
|
|
||||||
@@ -119,9 +127,9 @@ app-root {
|
|||||||
&> .content {
|
&> .content {
|
||||||
.tab-bar {
|
.tab-bar {
|
||||||
.btn-tab-bar {
|
.btn-tab-bar {
|
||||||
&:not(:hover):not(:active) {
|
background: transparent;
|
||||||
background: transparent;
|
&:hover { background: rgba(0, 0, 0, .25) !important; }
|
||||||
}
|
&:active { background: rgba(0, 0, 0, .5) !important; }
|
||||||
}
|
}
|
||||||
|
|
||||||
&>.tabs {
|
&>.tabs {
|
||||||
@@ -129,10 +137,10 @@ app-root {
|
|||||||
border-left: 1px solid transparent;
|
border-left: 1px solid transparent;
|
||||||
border-right: 1px solid transparent;
|
border-right: 1px solid transparent;
|
||||||
|
|
||||||
transition: 0.25s all;
|
transition: 0.125s ease-out width;
|
||||||
|
|
||||||
.index {
|
.index {
|
||||||
color: #888;
|
color: rgba(255, 255, 255, 0.4);
|
||||||
}
|
}
|
||||||
|
|
||||||
button {
|
button {
|
||||||
@@ -144,6 +152,10 @@ app-root {
|
|||||||
&:active { background: $button-active-bg !important; }
|
&:active { background: $button-active-bg !important; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.progressbar {
|
||||||
|
background: $green;
|
||||||
|
}
|
||||||
|
|
||||||
&.active {
|
&.active {
|
||||||
color: white;
|
color: white;
|
||||||
background: $content-bg;
|
background: $content-bg;
|
||||||
@@ -210,10 +222,7 @@ settings-tab > ngb-tabset {
|
|||||||
background: rgba(0, 0, 0, 0.25);
|
background: rgba(0, 0, 0, 0.25);
|
||||||
|
|
||||||
& > .nav-item > .nav-link {
|
& > .nav-item > .nav-link {
|
||||||
border-left: none;
|
border: none;
|
||||||
border-right: none;
|
|
||||||
border-top: 1px solid transparent;
|
|
||||||
border-bottom: 1px solid transparent;
|
|
||||||
padding: 10px 50px 10px 20px;
|
padding: 10px 50px 10px 20px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
|
|
||||||
@@ -224,11 +233,6 @@ settings-tab > ngb-tabset {
|
|||||||
color: $white;
|
color: $white;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.active {
|
|
||||||
border-top-color: $nav-tabs-active-link-hover-border-color;
|
|
||||||
border-bottom-color: $nav-tabs-active-link-hover-border-color;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -346,5 +350,9 @@ select.form-control {
|
|||||||
}
|
}
|
||||||
|
|
||||||
checkbox i.on {
|
checkbox i.on {
|
||||||
color: $blue;
|
color: $blue;
|
||||||
|
}
|
||||||
|
|
||||||
|
toggle.active .body .toggle {
|
||||||
|
background: $blue;
|
||||||
}
|
}
|
||||||
|
@@ -14,6 +14,7 @@ module.exports = {
|
|||||||
libraryTarget: 'umd',
|
libraryTarget: 'umd',
|
||||||
devtoolModuleFilenameTemplate: 'webpack-terminus-core:///[resource-path]',
|
devtoolModuleFilenameTemplate: 'webpack-terminus-core:///[resource-path]',
|
||||||
},
|
},
|
||||||
|
mode: process.env.DEV ? 'development' : 'production',
|
||||||
resolve: {
|
resolve: {
|
||||||
modules: ['.', 'src', 'node_modules', '../app/node_modules'].map(x => path.join(__dirname, x)),
|
modules: ['.', 'src', 'node_modules', '../app/node_modules'].map(x => path.join(__dirname, x)),
|
||||||
extensions: ['.ts', '.js'],
|
extensions: ['.ts', '.js'],
|
||||||
|
@@ -20,16 +20,47 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@types/node" "*"
|
"@types/node" "*"
|
||||||
|
|
||||||
|
ajv@^5.1.0:
|
||||||
|
version "5.5.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.5.2.tgz#73b5eeca3fab653e3d3f9422b341ad42205dc965"
|
||||||
|
dependencies:
|
||||||
|
co "^4.6.0"
|
||||||
|
fast-deep-equal "^1.0.0"
|
||||||
|
fast-json-stable-stringify "^2.0.0"
|
||||||
|
json-schema-traverse "^0.3.0"
|
||||||
|
|
||||||
argparse@^1.0.7:
|
argparse@^1.0.7:
|
||||||
version "1.0.9"
|
version "1.0.9"
|
||||||
resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.9.tgz#73d83bc263f86e97f8cc4f6bae1b0e90a7d22c86"
|
resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.9.tgz#73d83bc263f86e97f8cc4f6bae1b0e90a7d22c86"
|
||||||
dependencies:
|
dependencies:
|
||||||
sprintf-js "~1.0.2"
|
sprintf-js "~1.0.2"
|
||||||
|
|
||||||
|
asn1@~0.2.3:
|
||||||
|
version "0.2.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136"
|
||||||
|
dependencies:
|
||||||
|
safer-buffer "~2.1.0"
|
||||||
|
|
||||||
|
assert-plus@1.0.0, assert-plus@^1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525"
|
||||||
|
|
||||||
async@~1.0.0:
|
async@~1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/async/-/async-1.0.0.tgz#f8fc04ca3a13784ade9e1641af98578cfbd647a9"
|
resolved "https://registry.yarnpkg.com/async/-/async-1.0.0.tgz#f8fc04ca3a13784ade9e1641af98578cfbd647a9"
|
||||||
|
|
||||||
|
asynckit@^0.4.0:
|
||||||
|
version "0.4.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
|
||||||
|
|
||||||
|
aws-sign2@~0.7.0:
|
||||||
|
version "0.7.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8"
|
||||||
|
|
||||||
|
aws4@^1.6.0:
|
||||||
|
version "1.8.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f"
|
||||||
|
|
||||||
axios@0.16.2:
|
axios@0.16.2:
|
||||||
version "0.16.2"
|
version "0.16.2"
|
||||||
resolved "https://registry.yarnpkg.com/axios/-/axios-0.16.2.tgz#ba4f92f17167dfbab40983785454b9ac149c3c6d"
|
resolved "https://registry.yarnpkg.com/axios/-/axios-0.16.2.tgz#ba4f92f17167dfbab40983785454b9ac149c3c6d"
|
||||||
@@ -37,6 +68,12 @@ axios@0.16.2:
|
|||||||
follow-redirects "^1.2.3"
|
follow-redirects "^1.2.3"
|
||||||
is-buffer "^1.1.5"
|
is-buffer "^1.1.5"
|
||||||
|
|
||||||
|
bcrypt-pbkdf@^1.0.0:
|
||||||
|
version "1.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e"
|
||||||
|
dependencies:
|
||||||
|
tweetnacl "^0.14.3"
|
||||||
|
|
||||||
bluebird-lst@^1.0.2, bluebird-lst@^1.0.3:
|
bluebird-lst@^1.0.2, bluebird-lst@^1.0.3:
|
||||||
version "1.0.3"
|
version "1.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/bluebird-lst/-/bluebird-lst-1.0.3.tgz#cc56c18660eff0a0b86e2c33d1659618f7005158"
|
resolved "https://registry.yarnpkg.com/bluebird-lst/-/bluebird-lst-1.0.3.tgz#cc56c18660eff0a0b86e2c33d1659618f7005158"
|
||||||
@@ -47,31 +84,76 @@ bluebird@^3.5.0:
|
|||||||
version "3.5.0"
|
version "3.5.0"
|
||||||
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.0.tgz#791420d7f551eea2897453a8a77653f96606d67c"
|
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.0.tgz#791420d7f551eea2897453a8a77653f96606d67c"
|
||||||
|
|
||||||
bootstrap@4.0.0-alpha.6:
|
boom@4.x.x:
|
||||||
version "4.0.0-alpha.6"
|
version "4.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-4.0.0-alpha.6.tgz#4f54dd33ac0deac3b28407bc2df7ec608869c9c8"
|
resolved "https://registry.yarnpkg.com/boom/-/boom-4.3.1.tgz#4f8a3005cb4a7e3889f749030fd25b96e01d2e31"
|
||||||
dependencies:
|
dependencies:
|
||||||
jquery ">=1.9.1"
|
hoek "4.x.x"
|
||||||
tether "^1.4.0"
|
|
||||||
|
boom@5.x.x:
|
||||||
|
version "5.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/boom/-/boom-5.2.0.tgz#5dd9da6ee3a5f302077436290cb717d3f4a54e02"
|
||||||
|
dependencies:
|
||||||
|
hoek "4.x.x"
|
||||||
|
|
||||||
|
bootstrap@^4.1.3:
|
||||||
|
version "4.1.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-4.1.3.tgz#0eb371af2c8448e8c210411d0cb824a6409a12be"
|
||||||
|
|
||||||
|
caseless@~0.12.0:
|
||||||
|
version "0.12.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
|
||||||
|
|
||||||
|
co@^4.6.0:
|
||||||
|
version "4.6.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184"
|
||||||
|
|
||||||
colors@1.0.x:
|
colors@1.0.x:
|
||||||
version "1.0.3"
|
version "1.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/colors/-/colors-1.0.3.tgz#0433f44d809680fdeb60ed260f1b0c262e82a40b"
|
resolved "https://registry.yarnpkg.com/colors/-/colors-1.0.3.tgz#0433f44d809680fdeb60ed260f1b0c262e82a40b"
|
||||||
|
|
||||||
|
combined-stream@1.0.6, combined-stream@~1.0.5:
|
||||||
|
version "1.0.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.6.tgz#723e7df6e801ac5613113a7e445a9b69cb632818"
|
||||||
|
dependencies:
|
||||||
|
delayed-stream "~1.0.0"
|
||||||
|
|
||||||
core-js@^2.4.1:
|
core-js@^2.4.1:
|
||||||
version "2.5.1"
|
version "2.5.1"
|
||||||
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.1.tgz#ae6874dc66937789b80754ff5428df66819ca50b"
|
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.1.tgz#ae6874dc66937789b80754ff5428df66819ca50b"
|
||||||
|
|
||||||
|
core-util-is@1.0.2:
|
||||||
|
version "1.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
|
||||||
|
|
||||||
|
cryptiles@3.x.x:
|
||||||
|
version "3.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-3.1.2.tgz#a89fbb220f5ce25ec56e8c4aa8a4fd7b5b0d29fe"
|
||||||
|
dependencies:
|
||||||
|
boom "5.x.x"
|
||||||
|
|
||||||
cycle@1.0.x:
|
cycle@1.0.x:
|
||||||
version "1.0.3"
|
version "1.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/cycle/-/cycle-1.0.3.tgz#21e80b2be8580f98b468f379430662b046c34ad2"
|
resolved "https://registry.yarnpkg.com/cycle/-/cycle-1.0.3.tgz#21e80b2be8580f98b468f379430662b046c34ad2"
|
||||||
|
|
||||||
|
dashdash@^1.12.0:
|
||||||
|
version "1.14.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0"
|
||||||
|
dependencies:
|
||||||
|
assert-plus "^1.0.0"
|
||||||
|
|
||||||
debug@^2.4.5:
|
debug@^2.4.5:
|
||||||
version "2.6.8"
|
version "2.6.8"
|
||||||
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.8.tgz#e731531ca2ede27d188222427da17821d68ff4fc"
|
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.8.tgz#e731531ca2ede27d188222427da17821d68ff4fc"
|
||||||
dependencies:
|
dependencies:
|
||||||
ms "2.0.0"
|
ms "2.0.0"
|
||||||
|
|
||||||
|
debug@^3.0.0:
|
||||||
|
version "3.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
|
||||||
|
dependencies:
|
||||||
|
ms "2.0.0"
|
||||||
|
|
||||||
debug@^3.0.1:
|
debug@^3.0.1:
|
||||||
version "3.0.1"
|
version "3.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/debug/-/debug-3.0.1.tgz#0564c612b521dc92d9f2988f0549e34f9c98db64"
|
resolved "https://registry.yarnpkg.com/debug/-/debug-3.0.1.tgz#0564c612b521dc92d9f2988f0549e34f9c98db64"
|
||||||
@@ -82,6 +164,17 @@ deepmerge@^1.5.0:
|
|||||||
version "1.5.1"
|
version "1.5.1"
|
||||||
resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-1.5.1.tgz#c053bf06fd7276f1994f70c09a0760cb61a56237"
|
resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-1.5.1.tgz#c053bf06fd7276f1994f70c09a0760cb61a56237"
|
||||||
|
|
||||||
|
delayed-stream@~1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
|
||||||
|
|
||||||
|
ecc-jsbn@~0.1.1:
|
||||||
|
version "0.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9"
|
||||||
|
dependencies:
|
||||||
|
jsbn "~0.1.0"
|
||||||
|
safer-buffer "^2.1.0"
|
||||||
|
|
||||||
electron-builder-http@~19.27.5:
|
electron-builder-http@~19.27.5:
|
||||||
version "19.27.5"
|
version "19.27.5"
|
||||||
resolved "https://registry.yarnpkg.com/electron-builder-http/-/electron-builder-http-19.27.5.tgz#800865df2e618ffab9e5b3b895c15b4ce7fd7f17"
|
resolved "https://registry.yarnpkg.com/electron-builder-http/-/electron-builder-http-19.27.5.tgz#800865df2e618ffab9e5b3b895c15b4ce7fd7f17"
|
||||||
@@ -115,16 +208,48 @@ esprima@^4.0.0:
|
|||||||
version "4.0.0"
|
version "4.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.0.tgz#4499eddcd1110e0b218bacf2fa7f7f59f55ca804"
|
resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.0.tgz#4499eddcd1110e0b218bacf2fa7f7f59f55ca804"
|
||||||
|
|
||||||
|
extend@~3.0.1:
|
||||||
|
version "3.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa"
|
||||||
|
|
||||||
|
extsprintf@1.3.0:
|
||||||
|
version "1.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05"
|
||||||
|
|
||||||
|
extsprintf@^1.2.0:
|
||||||
|
version "1.4.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f"
|
||||||
|
|
||||||
eyes@0.1.x:
|
eyes@0.1.x:
|
||||||
version "0.1.8"
|
version "0.1.8"
|
||||||
resolved "https://registry.yarnpkg.com/eyes/-/eyes-0.1.8.tgz#62cf120234c683785d902348a800ef3e0cc20bc0"
|
resolved "https://registry.yarnpkg.com/eyes/-/eyes-0.1.8.tgz#62cf120234c683785d902348a800ef3e0cc20bc0"
|
||||||
|
|
||||||
|
fast-deep-equal@^1.0.0:
|
||||||
|
version "1.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz#c053477817c86b51daa853c81e059b733d023614"
|
||||||
|
|
||||||
|
fast-json-stable-stringify@^2.0.0:
|
||||||
|
version "2.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2"
|
||||||
|
|
||||||
follow-redirects@^1.2.3:
|
follow-redirects@^1.2.3:
|
||||||
version "1.2.4"
|
version "1.2.4"
|
||||||
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.2.4.tgz#355e8f4d16876b43f577b0d5ce2668b9723214ea"
|
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.2.4.tgz#355e8f4d16876b43f577b0d5ce2668b9723214ea"
|
||||||
dependencies:
|
dependencies:
|
||||||
debug "^2.4.5"
|
debug "^2.4.5"
|
||||||
|
|
||||||
|
forever-agent@~0.6.1:
|
||||||
|
version "0.6.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91"
|
||||||
|
|
||||||
|
form-data@~2.3.1:
|
||||||
|
version "2.3.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.2.tgz#4970498be604c20c005d4f5c23aecd21d6b49099"
|
||||||
|
dependencies:
|
||||||
|
asynckit "^0.4.0"
|
||||||
|
combined-stream "1.0.6"
|
||||||
|
mime-types "^2.1.12"
|
||||||
|
|
||||||
fs-extra-p@^4.4.0:
|
fs-extra-p@^4.4.0:
|
||||||
version "4.4.0"
|
version "4.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/fs-extra-p/-/fs-extra-p-4.4.0.tgz#729c601c4f4c701328921adc7cfe9b236f100660"
|
resolved "https://registry.yarnpkg.com/fs-extra-p/-/fs-extra-p-4.4.0.tgz#729c601c4f4c701328921adc7cfe9b236f100660"
|
||||||
@@ -140,22 +265,60 @@ fs-extra@^4.0.0:
|
|||||||
jsonfile "^3.0.0"
|
jsonfile "^3.0.0"
|
||||||
universalify "^0.1.0"
|
universalify "^0.1.0"
|
||||||
|
|
||||||
|
getpass@^0.1.1:
|
||||||
|
version "0.1.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa"
|
||||||
|
dependencies:
|
||||||
|
assert-plus "^1.0.0"
|
||||||
|
|
||||||
graceful-fs@^4.1.2, graceful-fs@^4.1.6:
|
graceful-fs@^4.1.2, graceful-fs@^4.1.6:
|
||||||
version "4.1.11"
|
version "4.1.11"
|
||||||
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658"
|
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658"
|
||||||
|
|
||||||
|
har-schema@^2.0.0:
|
||||||
|
version "2.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92"
|
||||||
|
|
||||||
|
har-validator@~5.0.3:
|
||||||
|
version "5.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.0.3.tgz#ba402c266194f15956ef15e0fcf242993f6a7dfd"
|
||||||
|
dependencies:
|
||||||
|
ajv "^5.1.0"
|
||||||
|
har-schema "^2.0.0"
|
||||||
|
|
||||||
|
hawk@~6.0.2:
|
||||||
|
version "6.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/hawk/-/hawk-6.0.2.tgz#af4d914eb065f9b5ce4d9d11c1cb2126eecc3038"
|
||||||
|
dependencies:
|
||||||
|
boom "4.x.x"
|
||||||
|
cryptiles "3.x.x"
|
||||||
|
hoek "4.x.x"
|
||||||
|
sntp "2.x.x"
|
||||||
|
|
||||||
|
hoek@4.x.x:
|
||||||
|
version "4.2.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/hoek/-/hoek-4.2.1.tgz#9634502aa12c445dd5a7c5734b572bb8738aacbb"
|
||||||
|
|
||||||
|
http-signature@~1.2.0:
|
||||||
|
version "1.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1"
|
||||||
|
dependencies:
|
||||||
|
assert-plus "^1.0.0"
|
||||||
|
jsprim "^1.2.2"
|
||||||
|
sshpk "^1.7.0"
|
||||||
|
|
||||||
is-buffer@^1.1.5:
|
is-buffer@^1.1.5:
|
||||||
version "1.1.5"
|
version "1.1.5"
|
||||||
resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.5.tgz#1f3b26ef613b214b88cbca23cc6c01d87961eecc"
|
resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.5.tgz#1f3b26ef613b214b88cbca23cc6c01d87961eecc"
|
||||||
|
|
||||||
isstream@0.1.x:
|
is-typedarray@~1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
|
||||||
|
|
||||||
|
isstream@0.1.x, isstream@~0.1.2:
|
||||||
version "0.1.2"
|
version "0.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
|
resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
|
||||||
|
|
||||||
jquery@>=1.9.1:
|
|
||||||
version "3.2.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.2.1.tgz#5c4d9de652af6cd0a770154a631bba12b015c787"
|
|
||||||
|
|
||||||
js-yaml@^3.9.0, js-yaml@^3.9.1:
|
js-yaml@^3.9.0, js-yaml@^3.9.1:
|
||||||
version "3.9.1"
|
version "3.9.1"
|
||||||
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.9.1.tgz#08775cebdfdd359209f0d2acd383c8f86a6904a0"
|
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.9.1.tgz#08775cebdfdd359209f0d2acd383c8f86a6904a0"
|
||||||
@@ -163,12 +326,37 @@ js-yaml@^3.9.0, js-yaml@^3.9.1:
|
|||||||
argparse "^1.0.7"
|
argparse "^1.0.7"
|
||||||
esprima "^4.0.0"
|
esprima "^4.0.0"
|
||||||
|
|
||||||
|
jsbn@~0.1.0:
|
||||||
|
version "0.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513"
|
||||||
|
|
||||||
|
json-schema-traverse@^0.3.0:
|
||||||
|
version "0.3.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz#349a6d44c53a51de89b40805c5d5e59b417d3340"
|
||||||
|
|
||||||
|
json-schema@0.2.3:
|
||||||
|
version "0.2.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13"
|
||||||
|
|
||||||
|
json-stringify-safe@~5.0.1:
|
||||||
|
version "5.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb"
|
||||||
|
|
||||||
jsonfile@^3.0.0:
|
jsonfile@^3.0.0:
|
||||||
version "3.0.1"
|
version "3.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-3.0.1.tgz#a5ecc6f65f53f662c4415c7675a0331d0992ec66"
|
resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-3.0.1.tgz#a5ecc6f65f53f662c4415c7675a0331d0992ec66"
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
graceful-fs "^4.1.6"
|
graceful-fs "^4.1.6"
|
||||||
|
|
||||||
|
jsprim@^1.2.2:
|
||||||
|
version "1.4.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2"
|
||||||
|
dependencies:
|
||||||
|
assert-plus "1.0.0"
|
||||||
|
extsprintf "1.3.0"
|
||||||
|
json-schema "0.2.3"
|
||||||
|
verror "1.10.0"
|
||||||
|
|
||||||
lazy-val@^1.0.2:
|
lazy-val@^1.0.2:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/lazy-val/-/lazy-val-1.0.2.tgz#d9b07fb1fce54cbc99b3c611de431b83249369b6"
|
resolved "https://registry.yarnpkg.com/lazy-val/-/lazy-val-1.0.2.tgz#d9b07fb1fce54cbc99b3c611de431b83249369b6"
|
||||||
@@ -181,6 +369,16 @@ macaddress@^0.2.7:
|
|||||||
version "0.2.8"
|
version "0.2.8"
|
||||||
resolved "https://registry.yarnpkg.com/macaddress/-/macaddress-0.2.8.tgz#5904dc537c39ec6dbefeae902327135fa8511f12"
|
resolved "https://registry.yarnpkg.com/macaddress/-/macaddress-0.2.8.tgz#5904dc537c39ec6dbefeae902327135fa8511f12"
|
||||||
|
|
||||||
|
mime-db@~1.35.0:
|
||||||
|
version "1.35.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.35.0.tgz#0569d657466491283709663ad379a99b90d9ab47"
|
||||||
|
|
||||||
|
mime-types@^2.1.12, mime-types@~2.1.17:
|
||||||
|
version "2.1.19"
|
||||||
|
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.19.tgz#71e464537a7ef81c15f2db9d97e913fc0ff606f0"
|
||||||
|
dependencies:
|
||||||
|
mime-db "~1.35.0"
|
||||||
|
|
||||||
ms@2.0.0:
|
ms@2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
|
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
|
||||||
@@ -196,14 +394,64 @@ ngx-perfect-scrollbar@^6.0.0:
|
|||||||
perfect-scrollbar "^1.3.0"
|
perfect-scrollbar "^1.3.0"
|
||||||
resize-observer-polyfill "^1.4.0"
|
resize-observer-polyfill "^1.4.0"
|
||||||
|
|
||||||
|
oauth-sign@~0.8.2:
|
||||||
|
version "0.8.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43"
|
||||||
|
|
||||||
perfect-scrollbar@^1.3.0:
|
perfect-scrollbar@^1.3.0:
|
||||||
version "1.3.0"
|
version "1.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/perfect-scrollbar/-/perfect-scrollbar-1.3.0.tgz#61da56f94b58870d8e0a617bce649cee17d1e3b2"
|
resolved "https://registry.yarnpkg.com/perfect-scrollbar/-/perfect-scrollbar-1.3.0.tgz#61da56f94b58870d8e0a617bce649cee17d1e3b2"
|
||||||
|
|
||||||
|
performance-now@^2.1.0:
|
||||||
|
version "2.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b"
|
||||||
|
|
||||||
|
punycode@^1.4.1:
|
||||||
|
version "1.4.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e"
|
||||||
|
|
||||||
|
qs@~6.5.1:
|
||||||
|
version "6.5.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"
|
||||||
|
|
||||||
|
request@2.86.0:
|
||||||
|
version "2.86.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/request/-/request-2.86.0.tgz#2b9497f449b0a32654c081a5cf426bbfb5bf5b69"
|
||||||
|
dependencies:
|
||||||
|
aws-sign2 "~0.7.0"
|
||||||
|
aws4 "^1.6.0"
|
||||||
|
caseless "~0.12.0"
|
||||||
|
combined-stream "~1.0.5"
|
||||||
|
extend "~3.0.1"
|
||||||
|
forever-agent "~0.6.1"
|
||||||
|
form-data "~2.3.1"
|
||||||
|
har-validator "~5.0.3"
|
||||||
|
hawk "~6.0.2"
|
||||||
|
http-signature "~1.2.0"
|
||||||
|
is-typedarray "~1.0.0"
|
||||||
|
isstream "~0.1.2"
|
||||||
|
json-stringify-safe "~5.0.1"
|
||||||
|
mime-types "~2.1.17"
|
||||||
|
oauth-sign "~0.8.2"
|
||||||
|
performance-now "^2.1.0"
|
||||||
|
qs "~6.5.1"
|
||||||
|
safe-buffer "^5.1.1"
|
||||||
|
tough-cookie "~2.3.3"
|
||||||
|
tunnel-agent "^0.6.0"
|
||||||
|
uuid "^3.1.0"
|
||||||
|
|
||||||
resize-observer-polyfill@^1.4.0:
|
resize-observer-polyfill@^1.4.0:
|
||||||
version "1.5.0"
|
version "1.5.0"
|
||||||
resolved "https://registry.yarnpkg.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.0.tgz#660ff1d9712a2382baa2cad450a4716209f9ca69"
|
resolved "https://registry.yarnpkg.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.0.tgz#660ff1d9712a2382baa2cad450a4716209f9ca69"
|
||||||
|
|
||||||
|
safe-buffer@^5.0.1, safe-buffer@^5.1.1:
|
||||||
|
version "5.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
|
||||||
|
|
||||||
|
safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0:
|
||||||
|
version "2.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
|
||||||
|
|
||||||
sax@^1.2.1:
|
sax@^1.2.1:
|
||||||
version "1.2.4"
|
version "1.2.4"
|
||||||
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
|
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
|
||||||
@@ -212,6 +460,12 @@ semver@^5.4.1:
|
|||||||
version "5.4.1"
|
version "5.4.1"
|
||||||
resolved "https://registry.yarnpkg.com/semver/-/semver-5.4.1.tgz#e059c09d8571f0540823733433505d3a2f00b18e"
|
resolved "https://registry.yarnpkg.com/semver/-/semver-5.4.1.tgz#e059c09d8571f0540823733433505d3a2f00b18e"
|
||||||
|
|
||||||
|
sntp@2.x.x:
|
||||||
|
version "2.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/sntp/-/sntp-2.1.0.tgz#2c6cec14fedc2222739caf9b5c3d85d1cc5a2cc8"
|
||||||
|
dependencies:
|
||||||
|
hoek "4.x.x"
|
||||||
|
|
||||||
source-map-support@^0.4.16:
|
source-map-support@^0.4.16:
|
||||||
version "0.4.17"
|
version "0.4.17"
|
||||||
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.17.tgz#6f2150553e6375375d0ccb3180502b78c18ba430"
|
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.17.tgz#6f2150553e6375375d0ccb3180502b78c18ba430"
|
||||||
@@ -226,13 +480,48 @@ sprintf-js@~1.0.2:
|
|||||||
version "1.0.3"
|
version "1.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
|
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
|
||||||
|
|
||||||
|
sshpk@^1.7.0:
|
||||||
|
version "1.14.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.14.2.tgz#c6fc61648a3d9c4e764fd3fcdf4ea105e492ba98"
|
||||||
|
dependencies:
|
||||||
|
asn1 "~0.2.3"
|
||||||
|
assert-plus "^1.0.0"
|
||||||
|
dashdash "^1.12.0"
|
||||||
|
getpass "^0.1.1"
|
||||||
|
safer-buffer "^2.0.2"
|
||||||
|
optionalDependencies:
|
||||||
|
bcrypt-pbkdf "^1.0.0"
|
||||||
|
ecc-jsbn "~0.1.1"
|
||||||
|
jsbn "~0.1.0"
|
||||||
|
tweetnacl "~0.14.0"
|
||||||
|
|
||||||
stack-trace@0.0.x:
|
stack-trace@0.0.x:
|
||||||
version "0.0.10"
|
version "0.0.10"
|
||||||
resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0"
|
resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0"
|
||||||
|
|
||||||
tether@^1.4.0:
|
tough-cookie@~2.3.3:
|
||||||
version "1.4.0"
|
version "2.3.4"
|
||||||
resolved "https://registry.yarnpkg.com/tether/-/tether-1.4.0.tgz#0f9fa171f75bf58485d8149e94799d7ae74d1c1a"
|
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.4.tgz#ec60cee38ac675063ffc97a5c18970578ee83655"
|
||||||
|
dependencies:
|
||||||
|
punycode "^1.4.1"
|
||||||
|
|
||||||
|
tunnel-agent@^0.6.0:
|
||||||
|
version "0.6.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd"
|
||||||
|
dependencies:
|
||||||
|
safe-buffer "^5.0.1"
|
||||||
|
|
||||||
|
tweetnacl@^0.14.3, tweetnacl@~0.14.0:
|
||||||
|
version "0.14.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64"
|
||||||
|
|
||||||
|
universal-analytics@^0.4.17:
|
||||||
|
version "0.4.17"
|
||||||
|
resolved "https://registry.yarnpkg.com/universal-analytics/-/universal-analytics-0.4.17.tgz#b57a07e37446ebe4f32872b2152a44cbc5cc4b73"
|
||||||
|
dependencies:
|
||||||
|
debug "^3.0.0"
|
||||||
|
request "2.86.0"
|
||||||
|
uuid "^3.0.0"
|
||||||
|
|
||||||
universalify@^0.1.0:
|
universalify@^0.1.0:
|
||||||
version "0.1.1"
|
version "0.1.1"
|
||||||
@@ -244,6 +533,18 @@ uuid-1345@^0.99.6:
|
|||||||
dependencies:
|
dependencies:
|
||||||
macaddress "^0.2.7"
|
macaddress "^0.2.7"
|
||||||
|
|
||||||
|
uuid@^3.0.0, uuid@^3.1.0:
|
||||||
|
version "3.3.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131"
|
||||||
|
|
||||||
|
verror@1.10.0:
|
||||||
|
version "1.10.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400"
|
||||||
|
dependencies:
|
||||||
|
assert-plus "^1.0.0"
|
||||||
|
core-util-is "1.0.2"
|
||||||
|
extsprintf "^1.2.0"
|
||||||
|
|
||||||
winston@^2.4.0:
|
winston@^2.4.0:
|
||||||
version "2.4.0"
|
version "2.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/winston/-/winston-2.4.0.tgz#808050b93d52661ed9fb6c26b3f0c826708b0aee"
|
resolved "https://registry.yarnpkg.com/winston/-/winston-2.4.0.tgz#808050b93d52661ed9fb6c26b3f0c826708b0aee"
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "terminus-plugin-manager",
|
"name": "terminus-plugin-manager",
|
||||||
"version": "1.0.0-alpha.48",
|
"version": "1.0.0-alpha.55",
|
||||||
"description": "Terminus' plugin manager",
|
"description": "Terminus' plugin manager",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"terminus-builtin-plugin"
|
"terminus-builtin-plugin"
|
||||||
|
@@ -19,7 +19,7 @@ h3 Installed
|
|||||||
.d-flex.flex-column.align-items-end.mr-3
|
.d-flex.flex-column.align-items-end.mr-3
|
||||||
div {{plugin.version}}
|
div {{plugin.version}}
|
||||||
small.text-muted {{plugin.author}}
|
small.text-muted {{plugin.author}}
|
||||||
button.btn.btn-outline-primary(
|
button.btn.btn-secondary.ml-2(
|
||||||
*ngIf='npmInstalled && knownUpgrades[plugin.name]',
|
*ngIf='npmInstalled && knownUpgrades[plugin.name]',
|
||||||
(click)='upgradePlugin(plugin)',
|
(click)='upgradePlugin(plugin)',
|
||||||
[disabled]='busy[plugin.name] != undefined'
|
[disabled]='busy[plugin.name] != undefined'
|
||||||
@@ -27,19 +27,19 @@ h3 Installed
|
|||||||
i.fa.fa-fw.fa-arrow-up(*ngIf='busy[plugin.name] != BusyState.Installing')
|
i.fa.fa-fw.fa-arrow-up(*ngIf='busy[plugin.name] != BusyState.Installing')
|
||||||
i.fa.fa-fw.fa-circle-o-notch.fa-spin(*ngIf='busy[plugin.name] == BusyState.Installing')
|
i.fa.fa-fw.fa-circle-o-notch.fa-spin(*ngIf='busy[plugin.name] == BusyState.Installing')
|
||||||
span Upgrade ({{knownUpgrades[plugin.name].version}})
|
span Upgrade ({{knownUpgrades[plugin.name].version}})
|
||||||
button.btn.btn-outline-danger(
|
button.btn.btn-secondary.ml-2(
|
||||||
(click)='uninstallPlugin(plugin)',
|
(click)='uninstallPlugin(plugin)',
|
||||||
*ngIf='!plugin.isBuiltin && npmInstalled',
|
*ngIf='!plugin.isBuiltin && npmInstalled',
|
||||||
[disabled]='busy[plugin.name] != undefined'
|
[disabled]='busy[plugin.name] != undefined'
|
||||||
)
|
)
|
||||||
i.fa.fa-fw.fa-trash-o(*ngIf='busy[plugin.name] != BusyState.Uninstalling')
|
i.fa.fa-fw.fa-trash-o(*ngIf='busy[plugin.name] != BusyState.Uninstalling')
|
||||||
i.fa.fa-fw.fa-circle-o-notch.fa-spin(*ngIf='busy[plugin.name] == BusyState.Uninstalling')
|
i.fa.fa-fw.fa-circle-o-notch.fa-spin(*ngIf='busy[plugin.name] == BusyState.Uninstalling')
|
||||||
button.btn.btn-outline-danger(
|
button.btn.btn-secondary.ml-2(
|
||||||
*ngIf='config.store.pluginBlacklist.includes(plugin.name)',
|
*ngIf='config.store.pluginBlacklist.includes(plugin.name)',
|
||||||
(click)='enablePlugin(plugin)'
|
(click)='enablePlugin(plugin)'
|
||||||
)
|
)
|
||||||
i.fa.fa-fw.fa-play
|
i.fa.fa-fw.fa-play
|
||||||
button.btn.btn-outline-primary(
|
button.btn.btn-secondary.ml-2(
|
||||||
*ngIf='!config.store.pluginBlacklist.includes(plugin.name)',
|
*ngIf='!config.store.pluginBlacklist.includes(plugin.name)',
|
||||||
(click)='disablePlugin(plugin)'
|
(click)='disablePlugin(plugin)'
|
||||||
)
|
)
|
||||||
@@ -60,9 +60,10 @@ div(*ngIf='npmInstalled')
|
|||||||
h3.mt-4 Available
|
h3.mt-4 Available
|
||||||
|
|
||||||
.input-group.mb-4
|
.input-group.mb-4
|
||||||
.input-group-addon
|
.input-group-prepend
|
||||||
i.fa.fa-fw.fa-circle-o-notch.fa-spin(*ngIf='!availablePluginsReady')
|
.input-group-text
|
||||||
i.fa.fa-fw.fa-search(*ngIf='availablePluginsReady')
|
i.fa.fa-fw.fa-circle-o-notch.fa-spin(*ngIf='!availablePluginsReady')
|
||||||
|
i.fa.fa-fw.fa-search(*ngIf='availablePluginsReady')
|
||||||
input.form-control(
|
input.form-control(
|
||||||
type='text',
|
type='text',
|
||||||
'[(ngModel)]'='_1',
|
'[(ngModel)]'='_1',
|
||||||
@@ -83,7 +84,7 @@ div(*ngIf='npmInstalled')
|
|||||||
div {{plugin.version}}
|
div {{plugin.version}}
|
||||||
small.text-muted {{plugin.author}}
|
small.text-muted {{plugin.author}}
|
||||||
i.fa.fa-check.text-success.ml-1(*ngIf='plugin.isOfficial', title='Official')
|
i.fa.fa-check.text-success.ml-1(*ngIf='plugin.isOfficial', title='Official')
|
||||||
button.btn.btn-outline-primary(
|
button.btn.btn-primary(
|
||||||
(click)='installPlugin(plugin)',
|
(click)='installPlugin(plugin)',
|
||||||
[disabled]='busy[plugin.name] != undefined'
|
[disabled]='busy[plugin.name] != undefined'
|
||||||
)
|
)
|
||||||
|
@@ -13,6 +13,7 @@ module.exports = {
|
|||||||
libraryTarget: 'umd',
|
libraryTarget: 'umd',
|
||||||
devtoolModuleFilenameTemplate: 'webpack-terminus-plugin-manager:///[resource-path]',
|
devtoolModuleFilenameTemplate: 'webpack-terminus-plugin-manager:///[resource-path]',
|
||||||
},
|
},
|
||||||
|
mode: process.env.DEV ? 'development' : 'production',
|
||||||
resolve: {
|
resolve: {
|
||||||
modules: ['.', 'src', 'node_modules', '../app/node_modules'].map(x => path.join(__dirname, x)),
|
modules: ['.', 'src', 'node_modules', '../app/node_modules'].map(x => path.join(__dirname, x)),
|
||||||
extensions: ['.ts', '.js'],
|
extensions: ['.ts', '.js'],
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "terminus-settings",
|
"name": "terminus-settings",
|
||||||
"version": "1.0.0-alpha.48",
|
"version": "1.0.0-alpha.55",
|
||||||
"description": "Terminus terminal settings page",
|
"description": "Terminus terminal settings page",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"terminus-builtin-plugin"
|
"terminus-builtin-plugin"
|
||||||
|
@@ -19,7 +19,7 @@ export class ButtonProvider extends ToolbarButtonProvider {
|
|||||||
return [{
|
return [{
|
||||||
icon: this.domSanitizer.bypassSecurityTrustHtml(require('./icons/cog.svg')),
|
icon: this.domSanitizer.bypassSecurityTrustHtml(require('./icons/cog.svg')),
|
||||||
title: 'Settings',
|
title: 'Settings',
|
||||||
touchBarTitle: '⚙️',
|
touchBarNSImage: 'NSTouchBarComposeTemplate',
|
||||||
weight: 10,
|
weight: 10,
|
||||||
click: () => this.open(),
|
click: () => this.open(),
|
||||||
}]
|
}]
|
||||||
|
@@ -1,204 +1,231 @@
|
|||||||
button.btn.btn-outline-warning.btn-block(*ngIf='config.restartRequested', '(click)'='restartApp()') Restart the app to apply changes
|
button.btn.btn-outline-warning.btn-block(*ngIf='config.restartRequested', '(click)'='restartApp()') Restart the app to apply changes
|
||||||
|
|
||||||
ngb-tabset.vertical(type='tabs', [activeId]='activeTab')
|
ngb-tabset.vertical(type='pills', [activeId]='activeTab')
|
||||||
ngb-tab(id='application')
|
ngb-tab(id='application')
|
||||||
ng-template(ngbTabTitle)
|
ng-template(ngbTabTitle)
|
||||||
| Application
|
| Application
|
||||||
ng-template(ngbTabContent)
|
ng-template(ngbTabContent)
|
||||||
h3.mb-3 Application
|
.d-flex.align-items-center.mb-4
|
||||||
.row
|
h1.terminus-title.mb-2.mr-2 Terminus
|
||||||
.col.col-lg-6
|
sup α
|
||||||
.form-group
|
|
||||||
label Theme
|
.text-muted.mr-auto {{homeBase.appVersion}}
|
||||||
select.form-control(
|
|
||||||
'[(ngModel)]'='config.store.appearance.theme',
|
button.btn.btn-secondary.mr-3((click)='homeBase.openGitHub()')
|
||||||
(ngModelChange)='config.save()',
|
i.fa.fa-github
|
||||||
)
|
span GitHub
|
||||||
option(*ngFor='let theme of themes', [ngValue]='theme.name') {{theme.name}}
|
|
||||||
|
button.btn.btn-secondary((click)='homeBase.reportBug()')
|
||||||
|
i.fa.fa-bug
|
||||||
|
span Report a problem
|
||||||
|
|
||||||
.form-group
|
.form-line
|
||||||
label Show tabs
|
.header
|
||||||
br
|
.title Theme
|
||||||
.btn-group(
|
select.form-control(
|
||||||
'[(ngModel)]'='config.store.appearance.tabsLocation',
|
[(ngModel)]='config.store.appearance.theme',
|
||||||
(ngModelChange)='config.save()',
|
(ngModelChange)='config.save()',
|
||||||
ngbRadioGroup
|
)
|
||||||
)
|
option(*ngFor='let theme of themes', [ngValue]='theme.name') {{theme.name}}
|
||||||
label.btn.btn-secondary(ngbButtonLabel)
|
|
||||||
input(
|
|
||||||
type='radio',
|
|
||||||
ngbButton,
|
|
||||||
[value]='"top"'
|
|
||||||
)
|
|
||||||
| On the top
|
|
||||||
label.btn.btn-secondary(ngbButtonLabel)
|
|
||||||
input(
|
|
||||||
type='radio',
|
|
||||||
ngbButton,
|
|
||||||
[value]='"bottom"'
|
|
||||||
)
|
|
||||||
| At the bottom
|
|
||||||
|
|
||||||
.form-group(*ngIf='hostApp.platform !== Platform.Linux')
|
.form-line
|
||||||
label Vibrancy
|
.header
|
||||||
br
|
.title Tabs location
|
||||||
.btn-group(
|
.btn-group(
|
||||||
'[(ngModel)]'='config.store.appearance.vibrancy'
|
[(ngModel)]='config.store.appearance.tabsLocation',
|
||||||
'(ngModelChange)'='config.save()'
|
(ngModelChange)='config.save()',
|
||||||
ngbRadioGroup
|
ngbRadioGroup
|
||||||
)
|
)
|
||||||
label.btn.btn-secondary(ngbButtonLabel)
|
label.btn.btn-secondary(ngbButtonLabel)
|
||||||
input(
|
|
||||||
type='radio',
|
|
||||||
ngbButton,
|
|
||||||
[value]='true'
|
|
||||||
)
|
|
||||||
| Enable
|
|
||||||
label.btn.btn-secondary(ngbButtonLabel)
|
|
||||||
input(
|
|
||||||
type='radio',
|
|
||||||
ngbButton,
|
|
||||||
[value]='false'
|
|
||||||
)
|
|
||||||
| Disable
|
|
||||||
|
|
||||||
.form-group(*ngIf='hostApp.platform !== Platform.Linux')
|
|
||||||
label Opacity
|
|
||||||
br
|
|
||||||
input(
|
input(
|
||||||
type='range',
|
type='radio',
|
||||||
'[(ngModel)]'='config.store.appearance.opacity',
|
ngbButton,
|
||||||
'(ngModelChange)'='config.save()',
|
[value]='"top"'
|
||||||
min='0.05',
|
|
||||||
max='1',
|
|
||||||
step='0.01'
|
|
||||||
)
|
)
|
||||||
|
| On the top
|
||||||
.col.col-lg-6
|
label.btn.btn-secondary(ngbButtonLabel)
|
||||||
.form-group
|
input(
|
||||||
label Window frame
|
type='radio',
|
||||||
br
|
ngbButton,
|
||||||
.btn-group(
|
[value]='"bottom"'
|
||||||
'[(ngModel)]'='config.store.appearance.frame'
|
|
||||||
'(ngModelChange)'='config.save(); config.requestRestart()'
|
|
||||||
ngbRadioGroup
|
|
||||||
)
|
)
|
||||||
label.btn.btn-secondary(ngbButtonLabel)
|
| At the bottom
|
||||||
input(
|
|
||||||
type='radio',
|
|
||||||
ngbButton,
|
|
||||||
[value]='"native"'
|
|
||||||
)
|
|
||||||
| Native
|
|
||||||
label.btn.btn-secondary(ngbButtonLabel)
|
|
||||||
input(
|
|
||||||
type='radio',
|
|
||||||
ngbButton,
|
|
||||||
[value]='"thin"'
|
|
||||||
)
|
|
||||||
| Thin
|
|
||||||
label.btn.btn-secondary(ngbButtonLabel)
|
|
||||||
input(
|
|
||||||
type='radio',
|
|
||||||
ngbButton,
|
|
||||||
[value]='"full"'
|
|
||||||
)
|
|
||||||
| Full
|
|
||||||
small.form-text.text-muted Whether a custom window or an OS native window should be used
|
|
||||||
|
|
||||||
.row
|
.form-line(*ngIf='hostApp.platform !== Platform.Linux')
|
||||||
.col.col-auto
|
.header
|
||||||
.form-group
|
.title Vibrancy
|
||||||
label Dock the terminal
|
.description Gives the window a blurred transparent background
|
||||||
br
|
|
||||||
.btn-group(
|
|
||||||
'[(ngModel)]'='config.store.appearance.dock'
|
|
||||||
'(ngModelChange)'='config.save(); docking.dock()'
|
|
||||||
ngbRadioGroup
|
|
||||||
)
|
|
||||||
label.btn.btn-secondary(ngbButtonLabel)
|
|
||||||
input(
|
|
||||||
type='radio',
|
|
||||||
ngbButton,
|
|
||||||
[value]='"off"'
|
|
||||||
)
|
|
||||||
| Off
|
|
||||||
label.btn.btn-secondary(ngbButtonLabel)
|
|
||||||
input(
|
|
||||||
type='radio',
|
|
||||||
ngbButton,
|
|
||||||
[value]='"top"'
|
|
||||||
)
|
|
||||||
| Top
|
|
||||||
label.btn.btn-secondary(ngbButtonLabel)
|
|
||||||
input(
|
|
||||||
type='radio',
|
|
||||||
ngbButton,
|
|
||||||
[value]='"left"'
|
|
||||||
)
|
|
||||||
| Left
|
|
||||||
label.btn.btn-secondary(ngbButtonLabel)
|
|
||||||
input(
|
|
||||||
type='radio',
|
|
||||||
ngbButton,
|
|
||||||
[value]='"right"'
|
|
||||||
)
|
|
||||||
| Right
|
|
||||||
label.btn.btn-secondary(ngbButtonLabel)
|
|
||||||
input(
|
|
||||||
type='radio',
|
|
||||||
ngbButton,
|
|
||||||
[value]='"bottom"'
|
|
||||||
)
|
|
||||||
| Bottom
|
|
||||||
|
|
||||||
.form-group(*ngIf='config.store.appearance.dock != "off"')
|
.btn-group(
|
||||||
label Display on
|
[(ngModel)]='config.store.appearance.vibrancy',
|
||||||
br
|
(ngModelChange)='config.save(); (hostApp.platform === Platform.Windows && config.requestRestart())',
|
||||||
div(
|
ngbRadioGroup
|
||||||
[(ngModel)]='config.store.appearance.dockScreen',
|
)
|
||||||
(ngModelChange)='config.save(); docking.dock()',
|
label.btn.btn-secondary(ngbButtonLabel)
|
||||||
ngbRadioGroup
|
input(
|
||||||
)
|
type='radio',
|
||||||
label.btn.btn-secondary(ngbButtonLabel)
|
ngbButton,
|
||||||
input(
|
[value]='true'
|
||||||
type='radio',
|
)
|
||||||
ngbButton,
|
| Enable
|
||||||
value='current'
|
label.btn.btn-secondary(ngbButtonLabel)
|
||||||
)
|
input(
|
||||||
| Current
|
type='radio',
|
||||||
label.btn.btn-secondary(*ngFor='let screen of screens', ngbButtonLabel)
|
ngbButton,
|
||||||
input(
|
[value]='false'
|
||||||
type='radio',
|
)
|
||||||
ngbButton,
|
| Disable
|
||||||
[value]='screen.id'
|
|
||||||
)
|
|
||||||
| {{screen.name}}
|
|
||||||
.col.col-auto
|
|
||||||
.form-group(*ngIf='config.store.appearance.dock != "off"')
|
|
||||||
label Docked terminal size
|
|
||||||
br
|
|
||||||
input(
|
|
||||||
type='range',
|
|
||||||
'[(ngModel)]'='config.store.appearance.dockFill',
|
|
||||||
'(mouseup)'='config.save(); docking.dock()',
|
|
||||||
min='0.05',
|
|
||||||
max='1',
|
|
||||||
step='0.01'
|
|
||||||
)
|
|
||||||
|
|
||||||
.form-group
|
.form-line(*ngIf='hostApp.platform !== Platform.Linux')
|
||||||
label Debugging
|
.header
|
||||||
div
|
.title Window opacity
|
||||||
button.btn.btn-secondary((click)='hostApp.openDevTools()')
|
input(
|
||||||
i.fa.fa-bug
|
type='range',
|
||||||
span Open DevTools
|
[(ngModel)]='config.store.appearance.opacity',
|
||||||
|
(ngModelChange)='config.save()',
|
||||||
|
min='0.05',
|
||||||
|
max='1',
|
||||||
|
step='0.01'
|
||||||
|
)
|
||||||
|
|
||||||
.form-group
|
.form-line
|
||||||
label Custom CSS
|
.header
|
||||||
|
.title Window frame
|
||||||
|
.description Whether a custom window or an OS native window should be used
|
||||||
|
|
||||||
|
.btn-group(
|
||||||
|
[(ngModel)]='config.store.appearance.frame',
|
||||||
|
(ngModelChange)='config.save(); config.requestRestart()',
|
||||||
|
ngbRadioGroup
|
||||||
|
)
|
||||||
|
label.btn.btn-secondary(ngbButtonLabel)
|
||||||
|
input(
|
||||||
|
type='radio',
|
||||||
|
ngbButton,
|
||||||
|
[value]='"native"'
|
||||||
|
)
|
||||||
|
| Native
|
||||||
|
label.btn.btn-secondary(ngbButtonLabel)
|
||||||
|
input(
|
||||||
|
type='radio',
|
||||||
|
ngbButton,
|
||||||
|
[value]='"thin"'
|
||||||
|
)
|
||||||
|
| Thin
|
||||||
|
label.btn.btn-secondary(ngbButtonLabel)
|
||||||
|
input(
|
||||||
|
type='radio',
|
||||||
|
ngbButton,
|
||||||
|
[value]='"full"'
|
||||||
|
)
|
||||||
|
| Full
|
||||||
|
|
||||||
|
.form-line
|
||||||
|
.header
|
||||||
|
.title Dock the terminal
|
||||||
|
.description Snaps the window to a side of the screen
|
||||||
|
|
||||||
|
.btn-group(
|
||||||
|
[(ngModel)]='config.store.appearance.dock',
|
||||||
|
(ngModelChange)='config.save(); docking.dock()',
|
||||||
|
ngbRadioGroup
|
||||||
|
)
|
||||||
|
label.btn.btn-secondary(ngbButtonLabel)
|
||||||
|
input(
|
||||||
|
type='radio',
|
||||||
|
ngbButton,
|
||||||
|
[value]='"off"'
|
||||||
|
)
|
||||||
|
| Off
|
||||||
|
label.btn.btn-secondary(ngbButtonLabel)
|
||||||
|
input(
|
||||||
|
type='radio',
|
||||||
|
ngbButton,
|
||||||
|
[value]='"top"'
|
||||||
|
)
|
||||||
|
| Top
|
||||||
|
label.btn.btn-secondary(ngbButtonLabel)
|
||||||
|
input(
|
||||||
|
type='radio',
|
||||||
|
ngbButton,
|
||||||
|
[value]='"left"'
|
||||||
|
)
|
||||||
|
| Left
|
||||||
|
label.btn.btn-secondary(ngbButtonLabel)
|
||||||
|
input(
|
||||||
|
type='radio',
|
||||||
|
ngbButton,
|
||||||
|
[value]='"right"'
|
||||||
|
)
|
||||||
|
| Right
|
||||||
|
label.btn.btn-secondary(ngbButtonLabel)
|
||||||
|
input(
|
||||||
|
type='radio',
|
||||||
|
ngbButton,
|
||||||
|
[value]='"bottom"'
|
||||||
|
)
|
||||||
|
| Bottom
|
||||||
|
|
||||||
|
.form-line(*ngIf='config.store.appearance.dock != "off"')
|
||||||
|
.header
|
||||||
|
.title Display on
|
||||||
|
.description Snaps the window to a side of the screen
|
||||||
|
|
||||||
|
div(
|
||||||
|
[(ngModel)]='config.store.appearance.dockScreen',
|
||||||
|
(ngModelChange)='config.save(); docking.dock()',
|
||||||
|
ngbRadioGroup
|
||||||
|
)
|
||||||
|
label.btn.btn-secondary(ngbButtonLabel)
|
||||||
|
input(
|
||||||
|
type='radio',
|
||||||
|
ngbButton,
|
||||||
|
value='current'
|
||||||
|
)
|
||||||
|
| Current
|
||||||
|
label.btn.btn-secondary(*ngFor='let screen of screens', ngbButtonLabel)
|
||||||
|
input(
|
||||||
|
type='radio',
|
||||||
|
ngbButton,
|
||||||
|
[value]='screen.id'
|
||||||
|
)
|
||||||
|
| {{screen.name}}
|
||||||
|
|
||||||
|
.form-line(*ngIf='config.store.appearance.dock != "off"')
|
||||||
|
.header
|
||||||
|
.title Docked terminal size
|
||||||
|
input(
|
||||||
|
type='range',
|
||||||
|
[(ngModel)]='config.store.appearance.dockFill',
|
||||||
|
(mouseup)='config.save(); docking.dock()',
|
||||||
|
min='0.05',
|
||||||
|
max='1',
|
||||||
|
step='0.01'
|
||||||
|
)
|
||||||
|
|
||||||
|
.form-line
|
||||||
|
.header
|
||||||
|
.title Debugging
|
||||||
|
|
||||||
|
button.btn.btn-secondary((click)='hostApp.openDevTools()')
|
||||||
|
i.fa.fa-bug
|
||||||
|
span Open DevTools
|
||||||
|
|
||||||
|
.form-line
|
||||||
|
.header
|
||||||
|
.title Enable analytics
|
||||||
|
.description We use Google Analytics
|
||||||
|
toggle(
|
||||||
|
[(ngModel)]='config.store.enableAnalytics',
|
||||||
|
(ngModelChange)='config.save(); config.requestRestart()',
|
||||||
|
)
|
||||||
|
|
||||||
|
.form-line
|
||||||
|
.header
|
||||||
|
.title Custom CSS
|
||||||
textarea.form-control(
|
textarea.form-control(
|
||||||
[(ngModel)]='config.store.appearance.css',
|
[(ngModel)]='config.store.appearance.css',
|
||||||
'(ngModelChange)'='config.save()',
|
(ngModelChange)='config.save()',
|
||||||
)
|
)
|
||||||
|
|
||||||
ngb-tab(id='hotkeys')
|
ngb-tab(id='hotkeys')
|
||||||
@@ -206,7 +233,13 @@ ngb-tabset.vertical(type='tabs', [activeId]='activeTab')
|
|||||||
| Hotkeys
|
| Hotkeys
|
||||||
ng-template(ngbTabContent)
|
ng-template(ngbTabContent)
|
||||||
h3.mb-3 Hotkeys
|
h3.mb-3 Hotkeys
|
||||||
input.form-control(type='search', placeholder='Search hotkeys', [(ngModel)]='hotkeyFilter')
|
|
||||||
|
.input-group.mb-4
|
||||||
|
.input-group-prepend
|
||||||
|
.input-group-text
|
||||||
|
i.fa.fa-fw.fa-search
|
||||||
|
input.form-control(type='search', placeholder='Search hotkeys', [(ngModel)]='hotkeyFilter')
|
||||||
|
|
||||||
.form-group
|
.form-group
|
||||||
table.hotkeys-table
|
table.hotkeys-table
|
||||||
tr
|
tr
|
||||||
@@ -227,3 +260,29 @@ ngb-tabset.vertical(type='tabs', [activeId]='activeTab')
|
|||||||
| {{provider.title}}
|
| {{provider.title}}
|
||||||
ng-template(ngbTabContent)
|
ng-template(ngbTabContent)
|
||||||
settings-tab-body([provider]='provider')
|
settings-tab-body([provider]='provider')
|
||||||
|
|
||||||
|
|
||||||
|
ngb-tab(id='config-file')
|
||||||
|
ng-template(ngbTabTitle)
|
||||||
|
| Config file
|
||||||
|
ng-template.test(ngbTabContent)
|
||||||
|
.d-flex.flex-column.w-100.h-100
|
||||||
|
.h-100.d-flex
|
||||||
|
.w-100.d-flex.flex-column
|
||||||
|
h3 Config file
|
||||||
|
textarea.form-control.h-100(
|
||||||
|
[(ngModel)]='configFile'
|
||||||
|
)
|
||||||
|
.w-100.d-flex.flex-column
|
||||||
|
h3 Defaults
|
||||||
|
textarea.form-control.h-100(
|
||||||
|
[(ngModel)]='configDefaults',
|
||||||
|
readonly
|
||||||
|
)
|
||||||
|
.mt-3
|
||||||
|
button.btn.btn-primary((click)='saveConfigFile()', *ngIf='isConfigFileValid()')
|
||||||
|
i.fa.fa-check.mr-2
|
||||||
|
| Save and apply
|
||||||
|
button.btn.btn-primary(disabled, *ngIf='!isConfigFileValid()')
|
||||||
|
i.fa.fa-warning.mr-2
|
||||||
|
| Invalid syntax
|
||||||
|
@@ -20,5 +20,5 @@
|
|||||||
|
|
||||||
textarea {
|
textarea {
|
||||||
font-family: 'Source Sans Mono', monospace;
|
font-family: 'Source Sans Mono', monospace;
|
||||||
height: 120px;
|
min-height: 120px;
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,7 @@
|
|||||||
|
import * as yaml from 'js-yaml'
|
||||||
|
import { Subscription } from 'rxjs'
|
||||||
import { Component, Inject, Input } from '@angular/core'
|
import { Component, Inject, Input } from '@angular/core'
|
||||||
import { ElectronService, DockingService, ConfigService, IHotkeyDescription, HotkeyProvider, BaseTabComponent, Theme, HostAppService, Platform } from 'terminus-core'
|
import { ElectronService, DockingService, ConfigService, IHotkeyDescription, HotkeyProvider, BaseTabComponent, Theme, HostAppService, Platform, HomeBaseService } from 'terminus-core'
|
||||||
|
|
||||||
import { SettingsTabProvider } from '../api'
|
import { SettingsTabProvider } from '../api'
|
||||||
|
|
||||||
@@ -17,12 +19,16 @@ export class SettingsTabComponent extends BaseTabComponent {
|
|||||||
hotkeyDescriptions: IHotkeyDescription[]
|
hotkeyDescriptions: IHotkeyDescription[]
|
||||||
screens: any[]
|
screens: any[]
|
||||||
Platform = Platform
|
Platform = Platform
|
||||||
|
configDefaults: any
|
||||||
|
configFile: string
|
||||||
|
private configSubscription: Subscription
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
public config: ConfigService,
|
public config: ConfigService,
|
||||||
private electron: ElectronService,
|
private electron: ElectronService,
|
||||||
public docking: DockingService,
|
public docking: DockingService,
|
||||||
public hostApp: HostAppService,
|
public hostApp: HostAppService,
|
||||||
|
public homeBase: HomeBaseService,
|
||||||
@Inject(HotkeyProvider) hotkeyProviders: HotkeyProvider[],
|
@Inject(HotkeyProvider) hotkeyProviders: HotkeyProvider[],
|
||||||
@Inject(SettingsTabProvider) public settingsProviders: SettingsTabProvider[],
|
@Inject(SettingsTabProvider) public settingsProviders: SettingsTabProvider[],
|
||||||
@Inject(Theme) public themes: Theme[],
|
@Inject(Theme) public themes: Theme[],
|
||||||
@@ -33,6 +39,12 @@ export class SettingsTabComponent extends BaseTabComponent {
|
|||||||
this.screens = this.docking.getScreens()
|
this.screens = this.docking.getScreens()
|
||||||
this.settingsProviders = config.enabledServices(this.settingsProviders)
|
this.settingsProviders = config.enabledServices(this.settingsProviders)
|
||||||
this.themes = config.enabledServices(this.themes)
|
this.themes = config.enabledServices(this.themes)
|
||||||
|
|
||||||
|
this.configDefaults = yaml.safeDump(config.getDefaults())
|
||||||
|
this.configFile = config.readRaw()
|
||||||
|
this.configSubscription = config.changed$.subscribe(() => {
|
||||||
|
this.configFile = config.readRaw()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
getRecoveryToken (): any {
|
getRecoveryToken (): any {
|
||||||
@@ -40,6 +52,7 @@ export class SettingsTabComponent extends BaseTabComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy () {
|
ngOnDestroy () {
|
||||||
|
this.configSubscription.unsubscribe()
|
||||||
this.config.save()
|
this.config.save()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,4 +60,19 @@ export class SettingsTabComponent extends BaseTabComponent {
|
|||||||
this.electron.app.relaunch()
|
this.electron.app.relaunch()
|
||||||
this.electron.app.exit()
|
this.electron.app.exit()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
saveConfigFile () {
|
||||||
|
if (this.isConfigFileValid()) {
|
||||||
|
this.config.writeRaw(this.configFile)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isConfigFileValid () {
|
||||||
|
try {
|
||||||
|
yaml.safeLoad(this.configFile)
|
||||||
|
return true
|
||||||
|
} catch (_) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -12,6 +12,12 @@
|
|||||||
:host /deep/ ngb-tabset > .tab-content {
|
:host /deep/ ngb-tabset > .tab-content {
|
||||||
padding: 15px 30px;
|
padding: 15px 30px;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
flex: auto;
|
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
flex: auto;
|
||||||
|
display: flex;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
:host /deep/ ngb-tabset > .tab-content > .tab-pane {
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
@@ -5,6 +5,7 @@ import { NgbModule } from '@ng-bootstrap/ng-bootstrap'
|
|||||||
import { NgPipesModule } from 'ngx-pipes'
|
import { NgPipesModule } from 'ngx-pipes'
|
||||||
|
|
||||||
import { ToolbarButtonProvider, TabRecoveryProvider } from 'terminus-core'
|
import { ToolbarButtonProvider, TabRecoveryProvider } from 'terminus-core'
|
||||||
|
import TerminusCorePlugin from 'terminus-core'
|
||||||
|
|
||||||
import { HotkeyInputModalComponent } from './components/hotkeyInputModal.component'
|
import { HotkeyInputModalComponent } from './components/hotkeyInputModal.component'
|
||||||
import { MultiHotkeyInputComponent } from './components/multiHotkeyInput.component'
|
import { MultiHotkeyInputComponent } from './components/multiHotkeyInput.component'
|
||||||
@@ -20,6 +21,7 @@ import { RecoveryProvider } from './recoveryProvider'
|
|||||||
FormsModule,
|
FormsModule,
|
||||||
NgbModule,
|
NgbModule,
|
||||||
NgPipesModule,
|
NgPipesModule,
|
||||||
|
TerminusCorePlugin,
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
{ provide: ToolbarButtonProvider, useClass: ButtonProvider, multi: true },
|
{ provide: ToolbarButtonProvider, useClass: ButtonProvider, multi: true },
|
||||||
|
@@ -13,6 +13,7 @@ module.exports = {
|
|||||||
libraryTarget: 'umd',
|
libraryTarget: 'umd',
|
||||||
devtoolModuleFilenameTemplate: 'webpack-terminus-settings:///[resource-path]',
|
devtoolModuleFilenameTemplate: 'webpack-terminus-settings:///[resource-path]',
|
||||||
},
|
},
|
||||||
|
mode: process.env.DEV ? 'development' : 'production',
|
||||||
resolve: {
|
resolve: {
|
||||||
modules: ['.', 'src', 'node_modules', '../app/node_modules'].map(x => path.join(__dirname, x)),
|
modules: ['.', 'src', 'node_modules', '../app/node_modules'].map(x => path.join(__dirname, x)),
|
||||||
extensions: ['.ts', '.js'],
|
extensions: ['.ts', '.js'],
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "terminus-ssh",
|
"name": "terminus-ssh",
|
||||||
"version": "1.0.0-alpha.48",
|
"version": "1.0.0-alpha.55",
|
||||||
"description": "SSH connection manager for Terminus",
|
"description": "SSH connection manager for Terminus",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"terminus-builtin-plugin"
|
"terminus-builtin-plugin"
|
||||||
|
@@ -1,5 +1,10 @@
|
|||||||
import { BaseSession } from 'terminus-terminal'
|
import { BaseSession } from 'terminus-terminal'
|
||||||
|
|
||||||
|
export interface LoginScript {
|
||||||
|
expect?: string
|
||||||
|
send: string
|
||||||
|
}
|
||||||
|
|
||||||
export interface SSHConnection {
|
export interface SSHConnection {
|
||||||
name?: string
|
name?: string
|
||||||
host: string
|
host: string
|
||||||
@@ -7,18 +12,45 @@ export interface SSHConnection {
|
|||||||
user: string
|
user: string
|
||||||
password?: string
|
password?: string
|
||||||
privateKey?: string
|
privateKey?: string
|
||||||
|
group?: string
|
||||||
|
scripts?: LoginScript[]
|
||||||
|
keepaliveInterval?: number
|
||||||
|
keepaliveCountMax?: number
|
||||||
|
readyTimeout?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export class SSHSession extends BaseSession {
|
export class SSHSession extends BaseSession {
|
||||||
constructor (private shell: any) {
|
scripts?: LoginScript[]
|
||||||
|
|
||||||
|
constructor (private shell: any, conn: SSHConnection) {
|
||||||
super()
|
super()
|
||||||
|
this.scripts = conn.scripts || []
|
||||||
}
|
}
|
||||||
|
|
||||||
start () {
|
start () {
|
||||||
this.open = true
|
this.open = true
|
||||||
|
|
||||||
this.shell.on('data', data => {
|
this.shell.on('data', data => {
|
||||||
this.emitOutput(data.toString())
|
let dataString = data.toString()
|
||||||
|
this.emitOutput(dataString)
|
||||||
|
|
||||||
|
if (this.scripts) {
|
||||||
|
let found = false
|
||||||
|
for (let script of this.scripts) {
|
||||||
|
if (dataString.includes(script.expect)) {
|
||||||
|
console.log('Executing script:', script.send)
|
||||||
|
this.shell.write(script.send + '\n')
|
||||||
|
this.scripts = this.scripts.filter(x => x !== script)
|
||||||
|
found = true
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (found) {
|
||||||
|
this.executeUnconditionalScripts()
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
this.shell.on('end', () => {
|
this.shell.on('end', () => {
|
||||||
@@ -26,6 +58,8 @@ export class SSHSession extends BaseSession {
|
|||||||
this.destroy()
|
this.destroy()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
this.executeUnconditionalScripts()
|
||||||
}
|
}
|
||||||
|
|
||||||
resize (columns, rows) {
|
resize (columns, rows) {
|
||||||
@@ -51,4 +85,23 @@ export class SSHSession extends BaseSession {
|
|||||||
async getWorkingDirectory (): Promise<string> {
|
async getWorkingDirectory (): Promise<string> {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private executeUnconditionalScripts () {
|
||||||
|
if (this.scripts) {
|
||||||
|
for (let script of this.scripts) {
|
||||||
|
if (!script.expect) {
|
||||||
|
console.log('Executing script:', script.send)
|
||||||
|
this.shell.write(script.send + '\n')
|
||||||
|
this.scripts = this.scripts.filter(x => x !== script)
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ISSHConnectionGroup {
|
||||||
|
name: string
|
||||||
|
connections: SSHConnection[]
|
||||||
}
|
}
|
||||||
|
@@ -28,7 +28,7 @@ export class ButtonProvider extends ToolbarButtonProvider {
|
|||||||
icon: this.domSanitizer.bypassSecurityTrustHtml(require('./icons/globe.svg')),
|
icon: this.domSanitizer.bypassSecurityTrustHtml(require('./icons/globe.svg')),
|
||||||
weight: 5,
|
weight: 5,
|
||||||
title: 'SSH connections',
|
title: 'SSH connections',
|
||||||
touchBarTitle: 'SSH',
|
touchBarNSImage: 'NSTouchBarOpenInBrowserTemplate',
|
||||||
click: async () => {
|
click: async () => {
|
||||||
this.activate()
|
this.activate()
|
||||||
}
|
}
|
||||||
|
@@ -1,45 +1,139 @@
|
|||||||
.modal-body
|
.modal-body
|
||||||
.form-group
|
ngb-tabset(type='pills', [activeId]='basic')
|
||||||
label Name
|
ngb-tab(id='basic')
|
||||||
input.form-control(
|
ng-template(ngbTabTitle)
|
||||||
type='text',
|
| Basic Setting
|
||||||
[(ngModel)]='connection.name',
|
ng-template(ngbTabContent)
|
||||||
)
|
.form-group
|
||||||
|
label Name
|
||||||
|
input.form-control(
|
||||||
|
type='text',
|
||||||
|
autofocus,
|
||||||
|
[(ngModel)]='connection.name',
|
||||||
|
)
|
||||||
|
|
||||||
.form-group
|
.form-group
|
||||||
label Host
|
label Group
|
||||||
input.form-control(
|
input.form-control(
|
||||||
type='text',
|
type='text',
|
||||||
[(ngModel)]='connection.host',
|
placeholder='Ungrouped',
|
||||||
)
|
[(ngModel)]='connection.group',
|
||||||
|
)
|
||||||
|
|
||||||
.form-group
|
.form-group
|
||||||
label Port
|
label Host
|
||||||
input.form-control(
|
input.form-control(
|
||||||
type='number',
|
type='text',
|
||||||
placeholder='22',
|
[(ngModel)]='connection.host',
|
||||||
[(ngModel)]='connection.port',
|
)
|
||||||
)
|
|
||||||
|
|
||||||
.form-group
|
.form-group
|
||||||
label Username
|
label Port
|
||||||
input.form-control(
|
input.form-control(
|
||||||
type='text',
|
type='number',
|
||||||
[(ngModel)]='connection.user',
|
placeholder='22',
|
||||||
)
|
[(ngModel)]='connection.port',
|
||||||
|
)
|
||||||
|
|
||||||
.form-group
|
.form-group
|
||||||
label Private key
|
label Username
|
||||||
.input-group
|
input.form-control(
|
||||||
input.form-control(
|
type='text',
|
||||||
type='text',
|
[(ngModel)]='connection.user',
|
||||||
placeholder='Key file path',
|
)
|
||||||
[(ngModel)]='connection.privateKey'
|
|
||||||
)
|
.alert.alert-info.d-flex.bg-transparent.text-white.align-items-center(*ngIf='hasSavedPassword')
|
||||||
.input-group-btn
|
.mr-auto There is a saved password for this connection
|
||||||
button.btn.btn-secondary((click)='selectPrivateKey()')
|
button.btn.btn-danger.ml-4((click)='clearSavedPassword()') Forget
|
||||||
i.fa.fa-folder-open
|
|
||||||
|
.form-group
|
||||||
|
label Private key
|
||||||
|
.input-group
|
||||||
|
input.form-control(
|
||||||
|
type='text',
|
||||||
|
placeholder='Key file path',
|
||||||
|
[(ngModel)]='connection.privateKey'
|
||||||
|
)
|
||||||
|
.input-group-btn
|
||||||
|
button.btn.btn-secondary((click)='selectPrivateKey()')
|
||||||
|
i.fa.fa-folder-open
|
||||||
|
|
||||||
|
ngb-tab(id='advanced')
|
||||||
|
ng-template(ngbTabTitle)
|
||||||
|
| Advanced Setting
|
||||||
|
ng-template(ngbTabContent)
|
||||||
|
.form-group
|
||||||
|
label Keep Alive Interval (Milliseconds)
|
||||||
|
input.form-control(
|
||||||
|
type='number',
|
||||||
|
placeholder='0',
|
||||||
|
[(ngModel)]='connection.keepaliveInterval',
|
||||||
|
)
|
||||||
|
|
||||||
|
.form-group
|
||||||
|
label Max Keep Alive Count
|
||||||
|
input.form-control(
|
||||||
|
type='number',
|
||||||
|
placeholder='3',
|
||||||
|
[(ngModel)]='connection.keepaliveCountMax',
|
||||||
|
)
|
||||||
|
|
||||||
|
.form-group
|
||||||
|
label Ready Timeout (Milliseconds)
|
||||||
|
input.form-control(
|
||||||
|
type='number',
|
||||||
|
placeholder='20000',
|
||||||
|
[(ngModel)]='connection.readyTimeout',
|
||||||
|
)
|
||||||
|
|
||||||
|
ngb-tab(id='scripts')
|
||||||
|
ng-template(ngbTabTitle)
|
||||||
|
| Login Scripts
|
||||||
|
ng-template(ngbTabContent)
|
||||||
|
table
|
||||||
|
tr
|
||||||
|
th String to expect
|
||||||
|
th String to be sent
|
||||||
|
th Actions
|
||||||
|
tr(*ngFor='let script of connection.scripts')
|
||||||
|
td
|
||||||
|
input.form-control(
|
||||||
|
type='text',
|
||||||
|
[(ngModel)]='script.expect'
|
||||||
|
)
|
||||||
|
td
|
||||||
|
input.form-control(
|
||||||
|
type='text',
|
||||||
|
[(ngModel)]='script.send'
|
||||||
|
)
|
||||||
|
td
|
||||||
|
.input-group.flex-nowrap
|
||||||
|
button.btn.btn-outline-info.ml-0((click)='moveScriptUp(script)')
|
||||||
|
i.fa.fa-arrow-up
|
||||||
|
button.btn.btn-outline-info.ml-0((click)='moveScriptDown(script)')
|
||||||
|
i.fa.fa-arrow-down
|
||||||
|
button.btn.btn-outline-danger.ml-0((click)='deleteScript(script)')
|
||||||
|
i.fa.fa-trash-o
|
||||||
|
tr
|
||||||
|
td
|
||||||
|
input.form-control(
|
||||||
|
type='text',
|
||||||
|
placeholder='Enter a string to expect',
|
||||||
|
[(ngModel)]='newScript.expect'
|
||||||
|
)
|
||||||
|
td
|
||||||
|
input.form-control(
|
||||||
|
type='text',
|
||||||
|
placeholder='Enter a string to be sent',
|
||||||
|
[(ngModel)]='newScript.send'
|
||||||
|
)
|
||||||
|
td
|
||||||
|
.input-group.flex-nowrap
|
||||||
|
button.btn.btn-outline-info.ml-0((click)='addScript()')
|
||||||
|
i.fa.fa-check
|
||||||
|
button.btn.btn-outline-danger.ml-0((click)='clearScript()')
|
||||||
|
i.fa.fa-trash-o
|
||||||
|
|
||||||
.modal-footer
|
.modal-footer
|
||||||
button.btn.btn-outline-primary((click)='save()') Save
|
button.btn.btn-outline-primary((click)='save()') Save
|
||||||
button.btn.btn-outline-danger((click)='cancel()') Cancel
|
button.btn.btn-outline-danger((click)='cancel()') Cancel
|
||||||
|
@@ -1,19 +1,34 @@
|
|||||||
import { Component } from '@angular/core'
|
import { Component } from '@angular/core'
|
||||||
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
|
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
|
||||||
import { ElectronService, HostAppService } from 'terminus-core'
|
import { ElectronService, HostAppService } from 'terminus-core'
|
||||||
import { SSHConnection } from '../api'
|
import { PasswordStorageService } from '../services/passwordStorage.service'
|
||||||
|
import { SSHConnection, LoginScript } from '../api'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
template: require('./editConnectionModal.component.pug'),
|
template: require('./editConnectionModal.component.pug'),
|
||||||
})
|
})
|
||||||
export class EditConnectionModalComponent {
|
export class EditConnectionModalComponent {
|
||||||
connection: SSHConnection
|
connection: SSHConnection
|
||||||
|
newScript: LoginScript
|
||||||
|
hasSavedPassword: boolean
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
private modalInstance: NgbActiveModal,
|
private modalInstance: NgbActiveModal,
|
||||||
private electron: ElectronService,
|
private electron: ElectronService,
|
||||||
private hostApp: HostAppService,
|
private hostApp: HostAppService,
|
||||||
) { }
|
private passwordStorage: PasswordStorageService,
|
||||||
|
) {
|
||||||
|
this.newScript = { expect: '', send: '' }
|
||||||
|
}
|
||||||
|
|
||||||
|
async ngOnInit () {
|
||||||
|
this.hasSavedPassword = !!(await this.passwordStorage.loadPassword(this.connection))
|
||||||
|
}
|
||||||
|
|
||||||
|
clearSavedPassword () {
|
||||||
|
this.hasSavedPassword = false
|
||||||
|
this.passwordStorage.deletePassword(this.connection)
|
||||||
|
}
|
||||||
|
|
||||||
selectPrivateKey () {
|
selectPrivateKey () {
|
||||||
let path = this.electron.dialog.showOpenDialog(
|
let path = this.electron.dialog.showOpenDialog(
|
||||||
@@ -34,4 +49,39 @@ export class EditConnectionModalComponent {
|
|||||||
cancel () {
|
cancel () {
|
||||||
this.modalInstance.dismiss()
|
this.modalInstance.dismiss()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
moveScriptUp (script: LoginScript) {
|
||||||
|
let index = this.connection.scripts.indexOf(script)
|
||||||
|
if (index > 0) {
|
||||||
|
this.connection.scripts.splice(index, 1)
|
||||||
|
this.connection.scripts.splice(index - 1, 0, script)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
moveScriptDown (script: LoginScript) {
|
||||||
|
let index = this.connection.scripts.indexOf(script)
|
||||||
|
if (index >= 0 && index < this.connection.scripts.length - 1) {
|
||||||
|
this.connection.scripts.splice(index, 1)
|
||||||
|
this.connection.scripts.splice(index + 1, 0, script)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteScript (script: LoginScript) {
|
||||||
|
if (confirm(`Delete?`)) {
|
||||||
|
this.connection.scripts = this.connection.scripts.filter(x => x !== script)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addScript () {
|
||||||
|
if (!this.connection.scripts) {
|
||||||
|
this.connection.scripts = []
|
||||||
|
}
|
||||||
|
this.connection.scripts.push({...this.newScript})
|
||||||
|
this.clearScript()
|
||||||
|
}
|
||||||
|
|
||||||
|
clearScript () {
|
||||||
|
this.newScript.expect = ''
|
||||||
|
this.newScript.send = ''
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
.modal-body
|
.modal-body
|
||||||
input.form-control(
|
input.form-control(
|
||||||
[type]='password ? "password" : "text"',
|
[type]='password ? "password" : "text"',
|
||||||
|
autofocus,
|
||||||
[(ngModel)]='value',
|
[(ngModel)]='value',
|
||||||
#input,
|
#input,
|
||||||
[placeholder]='prompt',
|
[placeholder]='prompt',
|
||||||
|
@@ -4,6 +4,7 @@
|
|||||||
[(ngModel)]='quickTarget',
|
[(ngModel)]='quickTarget',
|
||||||
autofocus,
|
autofocus,
|
||||||
placeholder='Quick connect: [user@]host[:port]',
|
placeholder='Quick connect: [user@]host[:port]',
|
||||||
|
(ngModelChange)='refresh()',
|
||||||
(keyup.enter)='quickConnect()'
|
(keyup.enter)='quickConnect()'
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -12,13 +13,16 @@
|
|||||||
i.fa.fa-fw.fa-history
|
i.fa.fa-fw.fa-history
|
||||||
span {{lastConnection.name}}
|
span {{lastConnection.name}}
|
||||||
|
|
||||||
.list-group.mt-3
|
.list-group.mt-3.connections-list
|
||||||
a.list-group-item.list-group-item-action(*ngFor='let connection of connections', (click)='connect(connection)')
|
ng-container(*ngFor='let group of childGroups')
|
||||||
i.fa.fa-fw.fa-globe
|
.list-group-item.list-group-item-action.d-flex.align-items-center(
|
||||||
span {{connection.name}}
|
(click)='groupCollapsed[group.name] = !groupCollapsed[group.name]'
|
||||||
a.list-group-item.list-group-item-action((click)='manageConnections()')
|
)
|
||||||
i.fa.fa-fw.fa-wrench
|
.fa.fa-fw.fa-chevron-right(*ngIf='groupCollapsed[group.name]')
|
||||||
span Manage connections
|
.fa.fa-fw.fa-chevron-down(*ngIf='!groupCollapsed[group.name]')
|
||||||
|
.ml-2 {{group.name || "Ungrouped"}}
|
||||||
//.modal-footer
|
ng-container(*ngIf='!groupCollapsed[group.name]')
|
||||||
button.btn.btn-outline-primary((click)='close()') Cancel
|
.list-group-item.list-group-item-action.pl-5.d-flex.align-items-center(
|
||||||
|
*ngFor='let connection of group.connections',
|
||||||
|
(click)='connect(connection)'
|
||||||
|
) {{connection.name}}
|
||||||
|
5
terminus-ssh/src/components/sshModal.component.scss
Normal file
5
terminus-ssh/src/components/sshModal.component.scss
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
.list-group.connections-list {
|
||||||
|
display: block;
|
||||||
|
max-height: 70vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
@@ -4,16 +4,19 @@ import { ToastrService } from 'ngx-toastr'
|
|||||||
import { ConfigService, AppService } from 'terminus-core'
|
import { ConfigService, AppService } from 'terminus-core'
|
||||||
import { SettingsTabComponent } from 'terminus-settings'
|
import { SettingsTabComponent } from 'terminus-settings'
|
||||||
import { SSHService } from '../services/ssh.service'
|
import { SSHService } from '../services/ssh.service'
|
||||||
import { SSHConnection } from '../api'
|
import { SSHConnection, ISSHConnectionGroup } from '../api'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
template: require('./sshModal.component.pug'),
|
template: require('./sshModal.component.pug'),
|
||||||
//styles: [require('./sshModal.component.scss')],
|
styles: [require('./sshModal.component.scss')],
|
||||||
})
|
})
|
||||||
export class SSHModalComponent {
|
export class SSHModalComponent {
|
||||||
connections: SSHConnection[]
|
connections: SSHConnection[]
|
||||||
|
childFolders: ISSHConnectionGroup[]
|
||||||
quickTarget: string
|
quickTarget: string
|
||||||
lastConnection: SSHConnection
|
lastConnection: SSHConnection
|
||||||
|
childGroups: ISSHConnectionGroup[]
|
||||||
|
groupCollapsed: {[id: string]: boolean} = {}
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
public modalInstance: NgbActiveModal,
|
public modalInstance: NgbActiveModal,
|
||||||
@@ -28,6 +31,7 @@ export class SSHModalComponent {
|
|||||||
if (window.localStorage.lastConnection) {
|
if (window.localStorage.lastConnection) {
|
||||||
this.lastConnection = JSON.parse(window.localStorage.lastConnection)
|
this.lastConnection = JSON.parse(window.localStorage.lastConnection)
|
||||||
}
|
}
|
||||||
|
this.refresh()
|
||||||
}
|
}
|
||||||
|
|
||||||
quickConnect () {
|
quickConnect () {
|
||||||
@@ -65,4 +69,26 @@ export class SSHModalComponent {
|
|||||||
close () {
|
close () {
|
||||||
this.modalInstance.close()
|
this.modalInstance.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
refresh () {
|
||||||
|
this.childGroups = []
|
||||||
|
|
||||||
|
let connections = this.connections
|
||||||
|
if (this.quickTarget) {
|
||||||
|
connections = connections.filter(connection => (connection.name + connection.group).toLowerCase().includes(this.quickTarget))
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let connection of connections) {
|
||||||
|
connection.group = connection.group || null
|
||||||
|
let group = this.childGroups.find(x => x.name === connection.group)
|
||||||
|
if (!group) {
|
||||||
|
group = {
|
||||||
|
name: connection.group,
|
||||||
|
connections: [],
|
||||||
|
}
|
||||||
|
this.childGroups.push(group)
|
||||||
|
}
|
||||||
|
group.connections.push(connection)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,15 +1,25 @@
|
|||||||
h3 Connections
|
h3 Connections
|
||||||
|
|
||||||
.list-group.mt-3.mb-3
|
.list-group.mt-3.mb-3
|
||||||
.list-group-item(*ngFor='let connection of connections')
|
ng-container(*ngFor='let group of childGroups')
|
||||||
.d-flex.w-100
|
.list-group-item.list-group-item-action.d-flex.align-items-center((click)='groupCollapsed[group.name] = !groupCollapsed[group.name]')
|
||||||
.mr-auto
|
.fa.fa-fw.fa-chevron-right(*ngIf='groupCollapsed[group.name]')
|
||||||
div
|
.fa.fa-fw.fa-chevron-down(*ngIf='!groupCollapsed[group.name]')
|
||||||
span {{connection.name}}
|
span.ml-3.mr-auto {{group.name || "Ungrouped"}}
|
||||||
.text-muted {{connection.host}}
|
button.btn.btn-outline-info.ml-2((click)='editGroup(group)')
|
||||||
button.btn.btn-outline-info.ml-2((click)='editConnection(connection)')
|
|
||||||
i.fa.fa-pencil
|
i.fa.fa-pencil
|
||||||
button.btn.btn-outline-danger.ml-1((click)='deleteConnection(connection)')
|
button.btn.btn-outline-danger.ml-1((click)='deleteGroup(group)')
|
||||||
i.fa.fa-trash-o
|
i.fa.fa-trash-o
|
||||||
|
ng-container(*ngIf='!groupCollapsed[group.name]')
|
||||||
|
.list-group-item.pl-5.d-flex.align-items-center(*ngFor='let connection of group.connections')
|
||||||
|
.mr-auto
|
||||||
|
div {{connection.name}}
|
||||||
|
.text-muted {{connection.host}}
|
||||||
|
button.btn.btn-outline-info.ml-2((click)='editConnection(connection)')
|
||||||
|
i.fa.fa-pencil
|
||||||
|
button.btn.btn-outline-danger.ml-1((click)='deleteConnection(connection)')
|
||||||
|
i.fa.fa-trash-o
|
||||||
|
|
||||||
button.btn.btn-outline-primary((click)='createConnection()') Add connection
|
button.btn.btn-outline-primary((click)='createConnection()')
|
||||||
|
div.fa.fa-fw.fa-globe
|
||||||
|
span.ml-2 Add connection
|
||||||
|
@@ -1,20 +1,24 @@
|
|||||||
import { Component } from '@angular/core'
|
import { Component } from '@angular/core'
|
||||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
||||||
import { ConfigService } from 'terminus-core'
|
import { ConfigService } from 'terminus-core'
|
||||||
import { SSHConnection } from '../api'
|
import { SSHConnection, ISSHConnectionGroup } from '../api'
|
||||||
import { EditConnectionModalComponent } from './editConnectionModal.component'
|
import { EditConnectionModalComponent } from './editConnectionModal.component'
|
||||||
|
import { PromptModalComponent } from './promptModal.component'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
template: require('./sshSettingsTab.component.pug'),
|
template: require('./sshSettingsTab.component.pug'),
|
||||||
})
|
})
|
||||||
export class SSHSettingsTabComponent {
|
export class SSHSettingsTabComponent {
|
||||||
connections: SSHConnection[]
|
connections: SSHConnection[]
|
||||||
|
childGroups: ISSHConnectionGroup[]
|
||||||
|
groupCollapsed: {[id: string]: boolean} = {}
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
public config: ConfigService,
|
public config: ConfigService,
|
||||||
private ngbModal: NgbModal,
|
private ngbModal: NgbModal,
|
||||||
) {
|
) {
|
||||||
this.connections = this.config.store.ssh.connections
|
this.connections = this.config.store.ssh.connections
|
||||||
|
this.refresh()
|
||||||
}
|
}
|
||||||
|
|
||||||
createConnection () {
|
createConnection () {
|
||||||
@@ -24,12 +28,14 @@ export class SSHSettingsTabComponent {
|
|||||||
port: 22,
|
port: 22,
|
||||||
user: 'root',
|
user: 'root',
|
||||||
}
|
}
|
||||||
|
|
||||||
let modal = this.ngbModal.open(EditConnectionModalComponent)
|
let modal = this.ngbModal.open(EditConnectionModalComponent)
|
||||||
modal.componentInstance.connection = connection
|
modal.componentInstance.connection = connection
|
||||||
modal.result.then(result => {
|
modal.result.then(result => {
|
||||||
this.connections.push(result)
|
this.connections.push(result)
|
||||||
this.config.store.ssh.connections = this.connections
|
this.config.store.ssh.connections = this.connections
|
||||||
this.config.save()
|
this.config.save()
|
||||||
|
this.refresh()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,6 +45,7 @@ export class SSHSettingsTabComponent {
|
|||||||
modal.result.then(result => {
|
modal.result.then(result => {
|
||||||
Object.assign(connection, result)
|
Object.assign(connection, result)
|
||||||
this.config.save()
|
this.config.save()
|
||||||
|
this.refresh()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,6 +54,49 @@ export class SSHSettingsTabComponent {
|
|||||||
this.connections = this.connections.filter(x => x !== connection)
|
this.connections = this.connections.filter(x => x !== connection)
|
||||||
this.config.store.ssh.connections = this.connections
|
this.config.store.ssh.connections = this.connections
|
||||||
this.config.save()
|
this.config.save()
|
||||||
|
this.refresh()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
editGroup (group: ISSHConnectionGroup) {
|
||||||
|
let modal = this.ngbModal.open(PromptModalComponent)
|
||||||
|
modal.componentInstance.prompt = 'New group name'
|
||||||
|
modal.componentInstance.value = group.name
|
||||||
|
modal.result.then(result => {
|
||||||
|
if (result) {
|
||||||
|
for (let connection of this.connections.filter(x => x.group === group.name)) {
|
||||||
|
connection.group = result
|
||||||
|
}
|
||||||
|
this.config.save()
|
||||||
|
this.refresh()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteGroup (group: ISSHConnectionGroup) {
|
||||||
|
if (confirm(`Delete "${group}"?`)) {
|
||||||
|
for (let connection of this.connections.filter(x => x.group === group.name)) {
|
||||||
|
connection.group = null
|
||||||
|
}
|
||||||
|
this.config.save()
|
||||||
|
this.refresh()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
refresh () {
|
||||||
|
this.childGroups = []
|
||||||
|
|
||||||
|
for (let connection of this.connections) {
|
||||||
|
connection.group = connection.group || null
|
||||||
|
let group = this.childGroups.find(x => x.name === connection.group)
|
||||||
|
if (!group) {
|
||||||
|
group = {
|
||||||
|
name: connection.group,
|
||||||
|
connections: [],
|
||||||
|
}
|
||||||
|
this.childGroups.push(group)
|
||||||
|
}
|
||||||
|
group.connections.push(connection)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -4,6 +4,7 @@ import { FormsModule } from '@angular/forms'
|
|||||||
import { NgbModule } from '@ng-bootstrap/ng-bootstrap'
|
import { NgbModule } from '@ng-bootstrap/ng-bootstrap'
|
||||||
import { ToastrModule } from 'ngx-toastr'
|
import { ToastrModule } from 'ngx-toastr'
|
||||||
import { ToolbarButtonProvider, ConfigProvider } from 'terminus-core'
|
import { ToolbarButtonProvider, ConfigProvider } from 'terminus-core'
|
||||||
|
import TerminusCoreModule from 'terminus-core'
|
||||||
import { SettingsTabProvider } from 'terminus-settings'
|
import { SettingsTabProvider } from 'terminus-settings'
|
||||||
|
|
||||||
import { EditConnectionModalComponent } from './components/editConnectionModal.component'
|
import { EditConnectionModalComponent } from './components/editConnectionModal.component'
|
||||||
@@ -23,6 +24,7 @@ import { SSHSettingsTabProvider } from './settings'
|
|||||||
CommonModule,
|
CommonModule,
|
||||||
FormsModule,
|
FormsModule,
|
||||||
ToastrModule,
|
ToastrModule,
|
||||||
|
TerminusCoreModule,
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
PasswordStorageService,
|
PasswordStorageService,
|
||||||
|
@@ -55,7 +55,7 @@ export class SSHService {
|
|||||||
modal.componentInstance.password = true
|
modal.componentInstance.password = true
|
||||||
try {
|
try {
|
||||||
privateKeyPassphrase = await modal.result
|
privateKeyPassphrase = await modal.result
|
||||||
} catch (_err) { }
|
} catch (_err) { } // tslint:disable-line
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -110,6 +110,9 @@ export class SSHService {
|
|||||||
tryKeyboard: true,
|
tryKeyboard: true,
|
||||||
agent,
|
agent,
|
||||||
agentForward: !!agent,
|
agentForward: !!agent,
|
||||||
|
keepaliveInterval: connection.keepaliveInterval,
|
||||||
|
keepaliveCountMax: connection.keepaliveCountMax,
|
||||||
|
readyTimeout: connection.readyTimeout,
|
||||||
})
|
})
|
||||||
|
|
||||||
let keychainPasswordUsed = false
|
let keychainPasswordUsed = false
|
||||||
@@ -148,7 +151,7 @@ export class SSHService {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
let session = new SSHSession(shell)
|
let session = new SSHSession(shell, connection)
|
||||||
|
|
||||||
return this.zone.run(() => this.app.openNewTab(
|
return this.zone.run(() => this.app.openNewTab(
|
||||||
TerminalTabComponent,
|
TerminalTabComponent,
|
||||||
|
@@ -12,6 +12,7 @@ module.exports = {
|
|||||||
libraryTarget: 'umd',
|
libraryTarget: 'umd',
|
||||||
devtoolModuleFilenameTemplate: 'webpack-terminus-ssh:///[resource-path]',
|
devtoolModuleFilenameTemplate: 'webpack-terminus-ssh:///[resource-path]',
|
||||||
},
|
},
|
||||||
|
mode: process.env.DEV ? 'development' : 'production',
|
||||||
resolve: {
|
resolve: {
|
||||||
modules: ['.', 'src', 'node_modules', '../app/node_modules'].map(x => path.join(__dirname, x)),
|
modules: ['.', 'src', 'node_modules', '../app/node_modules'].map(x => path.join(__dirname, x)),
|
||||||
extensions: ['.ts', '.js'],
|
extensions: ['.ts', '.js'],
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "terminus-terminal",
|
"name": "terminus-terminal",
|
||||||
"version": "1.0.0-alpha.48",
|
"version": "1.0.0-alpha.55",
|
||||||
"description": "Terminus' terminal emulation core",
|
"description": "Terminus' terminal emulation core",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"terminus-builtin-plugin"
|
"terminus-builtin-plugin"
|
||||||
@@ -24,7 +24,8 @@
|
|||||||
"@types/winreg": "^1.2.30",
|
"@types/winreg": "^1.2.30",
|
||||||
"dataurl": "0.1.0",
|
"dataurl": "0.1.0",
|
||||||
"deep-equal": "1.0.1",
|
"deep-equal": "1.0.1",
|
||||||
"file-loader": "^0.11.2"
|
"file-loader": "^0.11.2",
|
||||||
|
"xterm": "^3.6.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@angular/common": "4.0.1",
|
"@angular/common": "4.0.1",
|
||||||
@@ -40,7 +41,7 @@
|
|||||||
"@types/async-lock": "0.0.19",
|
"@types/async-lock": "0.0.19",
|
||||||
"async-lock": "^1.0.0",
|
"async-lock": "^1.0.0",
|
||||||
"font-manager": "0.3.0",
|
"font-manager": "0.3.0",
|
||||||
"hterm-umdjs": "1.1.3",
|
"hterm-umdjs": "1.4.1",
|
||||||
"mz": "^2.6.0",
|
"mz": "^2.6.0",
|
||||||
"node-pty-tmp": "0.7.2",
|
"node-pty-tmp": "0.7.2",
|
||||||
"ps-node": "^0.1.6",
|
"ps-node": "^0.1.6",
|
||||||
|
@@ -25,6 +25,7 @@ export interface SessionOptions {
|
|||||||
height?: number
|
height?: number
|
||||||
recoveryId?: string
|
recoveryId?: string
|
||||||
recoveredTruePID$?: Observable<number>
|
recoveredTruePID$?: Observable<number>
|
||||||
|
pauseAfterExit?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export abstract class SessionPersistenceProvider {
|
export abstract class SessionPersistenceProvider {
|
||||||
@@ -51,7 +52,7 @@ export abstract class TerminalColorSchemeProvider {
|
|||||||
|
|
||||||
export interface IShell {
|
export interface IShell {
|
||||||
id: string
|
id: string
|
||||||
name: string
|
name?: string
|
||||||
command: string
|
command: string
|
||||||
args?: string[]
|
args?: string[]
|
||||||
env?: any
|
env?: any
|
||||||
|
@@ -1,9 +1,7 @@
|
|||||||
import * as fs from 'mz/fs'
|
import * as fs from 'mz/fs'
|
||||||
import * as path from 'path'
|
|
||||||
import { first } from 'rxjs/operators'
|
|
||||||
import { Injectable } from '@angular/core'
|
import { Injectable } from '@angular/core'
|
||||||
import { DomSanitizer } from '@angular/platform-browser'
|
import { DomSanitizer } from '@angular/platform-browser'
|
||||||
import { HotkeysService, ToolbarButtonProvider, IToolbarButton, ConfigService, HostAppService, ElectronService } from 'terminus-core'
|
import { HotkeysService, ToolbarButtonProvider, IToolbarButton, HostAppService, ElectronService } from 'terminus-core'
|
||||||
|
|
||||||
import { TerminalService } from './services/terminal.service'
|
import { TerminalService } from './services/terminal.service'
|
||||||
|
|
||||||
@@ -12,7 +10,6 @@ export class ButtonProvider extends ToolbarButtonProvider {
|
|||||||
constructor (
|
constructor (
|
||||||
private terminal: TerminalService,
|
private terminal: TerminalService,
|
||||||
private domSanitizer: DomSanitizer,
|
private domSanitizer: DomSanitizer,
|
||||||
private config: ConfigService,
|
|
||||||
hostApp: HostAppService,
|
hostApp: HostAppService,
|
||||||
electron: ElectronService,
|
electron: ElectronService,
|
||||||
hotkeys: HotkeysService,
|
hotkeys: HotkeysService,
|
||||||
@@ -20,24 +17,35 @@ export class ButtonProvider extends ToolbarButtonProvider {
|
|||||||
super()
|
super()
|
||||||
hotkeys.matchedHotkey.subscribe(async (hotkey) => {
|
hotkeys.matchedHotkey.subscribe(async (hotkey) => {
|
||||||
if (hotkey === 'new-tab') {
|
if (hotkey === 'new-tab') {
|
||||||
this.openNewTab()
|
terminal.openTab()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
hostApp.secondInstance$.subscribe(async ({argv, cwd}) => {
|
hotkeys.matchedHotkey.subscribe(async (hotkey) => {
|
||||||
if (argv.length === 2) {
|
if (hotkey === 'new-window') {
|
||||||
let arg = path.resolve(cwd, argv[1])
|
hostApp.newWindow()
|
||||||
if (await fs.exists(arg)) {
|
}
|
||||||
this.openNewTab(arg)
|
})
|
||||||
|
hostApp.cliOpenDirectory$.subscribe(async directory => {
|
||||||
|
if (await fs.exists(directory)) {
|
||||||
|
if ((await fs.stat(directory)).isDirectory()) {
|
||||||
|
this.terminal.openTab(null, directory)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
hostApp.cliRunCommand$.subscribe(async command => {
|
||||||
|
this.terminal.openTab({
|
||||||
|
id: '',
|
||||||
|
command: command[0],
|
||||||
|
args: command.slice(1),
|
||||||
|
}, null, true)
|
||||||
|
})
|
||||||
if (!electron.remote.process.env.DEV) {
|
if (!electron.remote.process.env.DEV) {
|
||||||
setImmediate(async () => {
|
setImmediate(async () => {
|
||||||
let argv: string[] = electron.remote.process.argv
|
let argv: string[] = electron.remote.process.argv
|
||||||
for (let arg of argv.slice(1).concat([electron.remote.process.argv0])) {
|
for (let arg of argv.slice(1).concat([electron.remote.process.argv0])) {
|
||||||
if (await fs.exists(arg)) {
|
if (await fs.exists(arg)) {
|
||||||
if ((await fs.stat(arg)).isDirectory()) {
|
if ((await fs.stat(arg)).isDirectory()) {
|
||||||
this.openNewTab(arg)
|
this.terminal.openTab(null, arg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -45,19 +53,13 @@ export class ButtonProvider extends ToolbarButtonProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async openNewTab (cwd?: string): Promise<void> {
|
|
||||||
let shells = await this.terminal.shells$.pipe(first()).toPromise()
|
|
||||||
let shell = shells.find(x => x.id === this.config.store.terminal.shell)
|
|
||||||
this.terminal.openTab(shell, cwd)
|
|
||||||
}
|
|
||||||
|
|
||||||
provide (): IToolbarButton[] {
|
provide (): IToolbarButton[] {
|
||||||
return [{
|
return [{
|
||||||
icon: this.domSanitizer.bypassSecurityTrustHtml(require('./icons/plus.svg')),
|
icon: this.domSanitizer.bypassSecurityTrustHtml(require('./icons/plus.svg')),
|
||||||
title: 'New terminal',
|
title: 'New terminal',
|
||||||
touchBarTitle: 'New',
|
touchBarNSImage: 'NSTouchBarAddDetailTemplate',
|
||||||
click: async () => {
|
click: async () => {
|
||||||
this.openNewTab()
|
this.terminal.openTab()
|
||||||
}
|
}
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
template(#content)
|
ng-template(#content)
|
||||||
.preview(
|
.preview(
|
||||||
[style.width]='"100%"',
|
[style.width]='"100%"',
|
||||||
[style.background]='model',
|
[style.background]='model',
|
||||||
|
@@ -1,33 +1,48 @@
|
|||||||
h3.mb-3 Appearance
|
h3.mb-3 Appearance
|
||||||
.row
|
.row
|
||||||
.col-md-6
|
.col-md-6
|
||||||
.form-group
|
.form-line
|
||||||
label Font
|
.header
|
||||||
.row
|
.title Frontend
|
||||||
.col-8
|
.description Switches terminal frontend implementation (experimental)
|
||||||
input.form-control(
|
|
||||||
type='text',
|
|
||||||
[ngbTypeahead]='fontAutocomplete',
|
|
||||||
[(ngModel)]='config.store.terminal.font',
|
|
||||||
(ngModelChange)='config.save()',
|
|
||||||
)
|
|
||||||
.col-4
|
|
||||||
input.form-control(
|
|
||||||
type='number',
|
|
||||||
[(ngModel)]='config.store.terminal.fontSize',
|
|
||||||
(ngModelChange)='config.save()',
|
|
||||||
)
|
|
||||||
|
|
||||||
div
|
select.form-control(
|
||||||
checkbox(
|
[(ngModel)]='config.store.terminal.frontend',
|
||||||
text='Enable font ligatures',
|
(ngModelChange)='config.save()',
|
||||||
[(ngModel)]='config.store.terminal.ligatures',
|
)
|
||||||
|
option(value='hterm') hterm
|
||||||
|
option(value='xterm') xterm
|
||||||
|
|
||||||
|
.form-line
|
||||||
|
.header
|
||||||
|
.title Font
|
||||||
|
|
||||||
|
.d-flex.w-75
|
||||||
|
input.form-control.w-75(
|
||||||
|
type='text',
|
||||||
|
[ngbTypeahead]='fontAutocomplete',
|
||||||
|
[(ngModel)]='config.store.terminal.font',
|
||||||
|
(ngModelChange)='config.save()',
|
||||||
|
)
|
||||||
|
input.form-control.w-25(
|
||||||
|
type='number',
|
||||||
|
[(ngModel)]='config.store.terminal.fontSize',
|
||||||
(ngModelChange)='config.save()',
|
(ngModelChange)='config.save()',
|
||||||
)
|
)
|
||||||
|
|
||||||
.form-group(*ngIf='!editingColorScheme')
|
.form-line
|
||||||
label Color scheme
|
.header
|
||||||
.input-group
|
.title Enable font ligatures
|
||||||
|
toggle(
|
||||||
|
[(ngModel)]='config.store.terminal.ligatures',
|
||||||
|
(ngModelChange)='config.save()',
|
||||||
|
)
|
||||||
|
|
||||||
|
.form-line(*ngIf='!editingColorScheme')
|
||||||
|
.header
|
||||||
|
.title Color scheme
|
||||||
|
|
||||||
|
.input-group.w-50
|
||||||
select.form-control(
|
select.form-control(
|
||||||
[compareWith]='equalComparator',
|
[compareWith]='equalComparator',
|
||||||
[(ngModel)]='config.store.terminal.colorScheme',
|
[(ngModel)]='config.store.terminal.colorScheme',
|
||||||
@@ -53,7 +68,6 @@ h3.mb-3 Appearance
|
|||||||
.input-group-btn
|
.input-group-btn
|
||||||
button.btn.btn-secondary((click)='cancelEditing()') Cancel
|
button.btn.btn-secondary((click)='cancelEditing()') Cancel
|
||||||
|
|
||||||
|
|
||||||
.form-group(*ngIf='editingColorScheme')
|
.form-group(*ngIf='editingColorScheme')
|
||||||
color-picker(
|
color-picker(
|
||||||
'[(model)]'='editingColorScheme.foreground',
|
'[(model)]'='editingColorScheme.foreground',
|
||||||
@@ -77,9 +91,10 @@ h3.mb-3 Appearance
|
|||||||
[title]='idx',
|
[title]='idx',
|
||||||
)
|
)
|
||||||
|
|
||||||
.form-group
|
.form-line
|
||||||
label Terminal background
|
.header
|
||||||
br
|
.title Terminal background
|
||||||
|
|
||||||
.btn-group(
|
.btn-group(
|
||||||
[(ngModel)]='config.store.terminal.background',
|
[(ngModel)]='config.store.terminal.background',
|
||||||
(ngModelChange)='config.save()',
|
(ngModelChange)='config.save()',
|
||||||
@@ -100,59 +115,6 @@ h3.mb-3 Appearance
|
|||||||
)
|
)
|
||||||
| From colors
|
| From colors
|
||||||
|
|
||||||
.d-flex
|
|
||||||
.form-group.mr-3
|
|
||||||
label Cursor shape
|
|
||||||
br
|
|
||||||
.btn-group(
|
|
||||||
[(ngModel)]='config.store.terminal.cursor',
|
|
||||||
(ngModelChange)='config.save()',
|
|
||||||
ngbRadioGroup
|
|
||||||
)
|
|
||||||
label.btn.btn-secondary(ngbButtonLabel)
|
|
||||||
input(
|
|
||||||
type='radio',
|
|
||||||
ngbButton,
|
|
||||||
[value]='"block"'
|
|
||||||
)
|
|
||||||
| █
|
|
||||||
label.btn.btn-secondary(ngbButtonLabel)
|
|
||||||
input(
|
|
||||||
type='radio',
|
|
||||||
ngbButton,
|
|
||||||
[value]='"beam"'
|
|
||||||
)
|
|
||||||
| |
|
|
||||||
label.btn.btn-secondary(ngbButtonLabel)
|
|
||||||
input(
|
|
||||||
type='radio',
|
|
||||||
ngbButton,
|
|
||||||
[value]='"underline"'
|
|
||||||
)
|
|
||||||
| ▁
|
|
||||||
|
|
||||||
.form-group
|
|
||||||
label Blink cursor
|
|
||||||
br
|
|
||||||
.btn-group(
|
|
||||||
[(ngModel)]='config.store.terminal.cursorBlink',
|
|
||||||
(ngModelChange)='config.save()',
|
|
||||||
ngbRadioGroup
|
|
||||||
)
|
|
||||||
label.btn.btn-secondary(ngbButtonLabel)
|
|
||||||
input(
|
|
||||||
type='radio',
|
|
||||||
ngbButton,
|
|
||||||
[value]='false'
|
|
||||||
)
|
|
||||||
| Off
|
|
||||||
label.btn.btn-secondary(ngbButtonLabel)
|
|
||||||
input(
|
|
||||||
type='radio',
|
|
||||||
ngbButton,
|
|
||||||
[value]='true'
|
|
||||||
)
|
|
||||||
| On
|
|
||||||
.col-md-6
|
.col-md-6
|
||||||
.form-group
|
.form-group
|
||||||
.appearance-preview(
|
.appearance-preview(
|
||||||
@@ -240,55 +202,101 @@ h3.mb-3 Appearance
|
|||||||
span rm -rf /
|
span rm -rf /
|
||||||
span([style.background-color]='config.store.terminal.colorScheme.cursor')
|
span([style.background-color]='config.store.terminal.colorScheme.cursor')
|
||||||
|
|
||||||
|
.form-line
|
||||||
|
.header
|
||||||
|
.title Cursor shape
|
||||||
|
|
||||||
|
.btn-group(
|
||||||
|
[(ngModel)]='config.store.terminal.cursor',
|
||||||
|
(ngModelChange)='config.save()',
|
||||||
|
ngbRadioGroup
|
||||||
|
)
|
||||||
|
label.btn.btn-secondary(ngbButtonLabel)
|
||||||
|
input(
|
||||||
|
type='radio',
|
||||||
|
ngbButton,
|
||||||
|
[value]='"block"'
|
||||||
|
)
|
||||||
|
| █
|
||||||
|
label.btn.btn-secondary(ngbButtonLabel)
|
||||||
|
input(
|
||||||
|
type='radio',
|
||||||
|
ngbButton,
|
||||||
|
[value]='"beam"'
|
||||||
|
)
|
||||||
|
| |
|
||||||
|
label.btn.btn-secondary(ngbButtonLabel)
|
||||||
|
input(
|
||||||
|
type='radio',
|
||||||
|
ngbButton,
|
||||||
|
[value]='"underline"'
|
||||||
|
)
|
||||||
|
| ▁
|
||||||
|
|
||||||
|
.form-line
|
||||||
|
.header
|
||||||
|
.title Blink cursor
|
||||||
|
|
||||||
|
toggle(
|
||||||
|
[(ngModel)]='config.store.terminal.cursorBlink',
|
||||||
|
(ngModelChange)='config.save()',
|
||||||
|
)
|
||||||
|
|
||||||
h3.mt-3.mb-3 Shell
|
h3.mt-3.mb-3 Shell
|
||||||
|
|
||||||
.d-flex
|
.form-line
|
||||||
.form-group.mr-3
|
.header
|
||||||
label Shell
|
.title Shell
|
||||||
select.form-control(
|
.description Default shell for new tabs
|
||||||
[(ngModel)]='config.store.terminal.shell',
|
|
||||||
(ngModelChange)='config.save()',
|
select.form-control(
|
||||||
)
|
[(ngModel)]='config.store.terminal.shell',
|
||||||
option(
|
(ngModelChange)='config.save()',
|
||||||
*ngFor='let shell of shells',
|
)
|
||||||
[ngValue]='shell.id'
|
option(
|
||||||
) {{shell.name}}
|
*ngFor='let shell of shells',
|
||||||
|
[ngValue]='shell.id'
|
||||||
|
) {{shell.name}}
|
||||||
|
|
||||||
|
.form-line(*ngIf='config.store.terminal.shell == "custom"')
|
||||||
|
.header
|
||||||
|
.title Custom shell
|
||||||
|
|
||||||
.form-group.mr-3(*ngIf='persistenceProviders.length > 0')
|
|
||||||
label Session persistence
|
|
||||||
select.form-control(
|
|
||||||
[(ngModel)]='config.store.terminal.persistence',
|
|
||||||
(ngModelChange)='config.save()',
|
|
||||||
)
|
|
||||||
option([ngValue]='null') Off
|
|
||||||
option(
|
|
||||||
*ngFor='let provider of persistenceProviders',
|
|
||||||
[ngValue]='provider.id'
|
|
||||||
) {{provider.displayName}}
|
|
||||||
|
|
||||||
.form-group
|
|
||||||
label Working directory
|
|
||||||
input.form-control(
|
|
||||||
type='text',
|
|
||||||
placeholder='Home directory',
|
|
||||||
[(ngModel)]='config.store.terminal.workingDirectory',
|
|
||||||
(ngModelChange)='config.save()',
|
|
||||||
)
|
|
||||||
|
|
||||||
.form-group(*ngIf='config.store.terminal.shell == "custom"')
|
|
||||||
label Custom shell
|
|
||||||
input.form-control(
|
input.form-control(
|
||||||
type='text',
|
type='text',
|
||||||
[(ngModel)]='config.store.terminal.customShell',
|
[(ngModel)]='config.store.terminal.customShell',
|
||||||
(ngModelChange)='config.save()',
|
(ngModelChange)='config.save()',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
.form-line(*ngIf='persistenceProviders.length > 0')
|
||||||
|
.header
|
||||||
|
.title Session persistence
|
||||||
|
.description Restores tabs when Terminus is restarted
|
||||||
|
select.form-control(
|
||||||
|
[(ngModel)]='config.store.terminal.persistence',
|
||||||
|
(ngModelChange)='config.save()',
|
||||||
|
)
|
||||||
|
option([ngValue]='null') Off
|
||||||
|
option(
|
||||||
|
*ngFor='let provider of persistenceProviders',
|
||||||
|
[ngValue]='provider.id'
|
||||||
|
) {{provider.displayName}}
|
||||||
|
|
||||||
|
.form-line
|
||||||
|
.header
|
||||||
|
.title Working directory
|
||||||
|
input.form-control(
|
||||||
|
type='text',
|
||||||
|
placeholder='Home directory',
|
||||||
|
[(ngModel)]='config.store.terminal.workingDirectory',
|
||||||
|
(ngModelChange)='config.save()',
|
||||||
|
)
|
||||||
|
|
||||||
h3.mt-3.mb-3 Behaviour
|
h3.mt-3.mb-3 Behaviour
|
||||||
|
|
||||||
.form-group
|
.form-line
|
||||||
label Terminal bell
|
.header
|
||||||
br
|
.title Terminal bell
|
||||||
.btn-group(
|
.btn-group(
|
||||||
[(ngModel)]='config.store.terminal.bell',
|
[(ngModel)]='config.store.terminal.bell',
|
||||||
(ngModelChange)='config.save()',
|
(ngModelChange)='config.save()',
|
||||||
@@ -315,10 +323,10 @@ h3.mt-3.mb-3 Behaviour
|
|||||||
[value]='"audible"'
|
[value]='"audible"'
|
||||||
)
|
)
|
||||||
| Audible
|
| Audible
|
||||||
|
|
||||||
.form-group
|
.form-line
|
||||||
label Right click
|
.header
|
||||||
br
|
.title Right click
|
||||||
.btn-group(
|
.btn-group(
|
||||||
[(ngModel)]='config.store.terminal.rightClick',
|
[(ngModel)]='config.store.terminal.rightClick',
|
||||||
(ngModelChange)='config.save()',
|
(ngModelChange)='config.save()',
|
||||||
@@ -339,28 +347,37 @@ h3.mt-3.mb-3 Behaviour
|
|||||||
)
|
)
|
||||||
| Paste
|
| Paste
|
||||||
|
|
||||||
|
.form-line
|
||||||
|
.header
|
||||||
|
.title Auto-open a terminal on app start
|
||||||
|
|
||||||
.form-group
|
toggle(
|
||||||
checkbox(
|
|
||||||
[(ngModel)]='config.store.terminal.autoOpen',
|
[(ngModel)]='config.store.terminal.autoOpen',
|
||||||
(ngModelChange)='config.save()',
|
(ngModelChange)='config.save()',
|
||||||
text='Auto-open a terminal on app start',
|
|
||||||
)
|
)
|
||||||
|
|
||||||
checkbox(
|
.form-line
|
||||||
|
.header
|
||||||
|
.title Bracketed paste (requires shell support)
|
||||||
|
.description Prevents accidental execution of pasted commands
|
||||||
|
toggle(
|
||||||
[(ngModel)]='config.store.terminal.bracketedPaste',
|
[(ngModel)]='config.store.terminal.bracketedPaste',
|
||||||
(ngModelChange)='config.save()',
|
(ngModelChange)='config.save()',
|
||||||
text='Bracketed paste (requires shell support)',
|
|
||||||
)
|
)
|
||||||
|
|
||||||
checkbox(
|
.form-line
|
||||||
|
.header
|
||||||
|
.title Copy on select
|
||||||
|
toggle(
|
||||||
[(ngModel)]='config.store.terminal.copyOnSelect',
|
[(ngModel)]='config.store.terminal.copyOnSelect',
|
||||||
(ngModelChange)='config.save()',
|
(ngModelChange)='config.save()',
|
||||||
text='Copy on select',
|
|
||||||
)
|
)
|
||||||
|
|
||||||
checkbox(
|
.form-line
|
||||||
|
.header
|
||||||
|
.title Use Alt key as the Meta key
|
||||||
|
.description Lets the shell handle Meta key instead of OS
|
||||||
|
toggle(
|
||||||
[(ngModel)]='config.store.terminal.altIsMeta',
|
[(ngModel)]='config.store.terminal.altIsMeta',
|
||||||
(ngModelChange)='config.save()',
|
(ngModelChange)='config.save()',
|
||||||
text='Use Alt key as the Meta key',
|
|
||||||
)
|
)
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import { Observable } from 'rxjs'
|
import { Observable } from 'rxjs'
|
||||||
import { debounceTime, distinctUntilChanged, map } from 'rxjs/operators'
|
import { debounceTime, distinctUntilChanged, map } from 'rxjs/operators'
|
||||||
import { exec } from 'mz/child_process'
|
import { exec } from 'mz/child_process'
|
||||||
const equal = require('deep-equal')
|
import deepEqual = require('deep-equal')
|
||||||
const fontManager = require('font-manager')
|
const fontManager = require('font-manager')
|
||||||
|
|
||||||
import { Component, Inject } from '@angular/core'
|
import { Component, Inject } from '@angular/core'
|
||||||
@@ -17,7 +17,7 @@ export class TerminalSettingsTabComponent {
|
|||||||
shells: IShell[] = []
|
shells: IShell[] = []
|
||||||
persistenceProviders: SessionPersistenceProvider[]
|
persistenceProviders: SessionPersistenceProvider[]
|
||||||
colorSchemes: ITerminalColorScheme[] = []
|
colorSchemes: ITerminalColorScheme[] = []
|
||||||
equalComparator = equal
|
equalComparator = deepEqual
|
||||||
editingColorScheme: ITerminalColorScheme
|
editingColorScheme: ITerminalColorScheme
|
||||||
schemeChanged = false
|
schemeChanged = false
|
||||||
|
|
||||||
@@ -88,7 +88,7 @@ export class TerminalSettingsTabComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
isCustomScheme (scheme: ITerminalColorScheme) {
|
isCustomScheme (scheme: ITerminalColorScheme) {
|
||||||
return this.config.store.terminal.customColorSchemes.some(x => equal(x, scheme))
|
return this.config.store.terminal.customColorSchemes.some(x => deepEqual(x, scheme))
|
||||||
}
|
}
|
||||||
|
|
||||||
colorsTrackBy (index) {
|
colorsTrackBy (index) {
|
||||||
|
@@ -7,11 +7,10 @@ import { AppService, ConfigService, BaseTabComponent, ElectronService, HostAppSe
|
|||||||
import { IShell } from '../api'
|
import { IShell } from '../api'
|
||||||
import { Session, SessionsService } from '../services/sessions.service'
|
import { Session, SessionsService } from '../services/sessions.service'
|
||||||
import { TerminalService } from '../services/terminal.service'
|
import { TerminalService } from '../services/terminal.service'
|
||||||
import { TerminalContainersService } from '../services/terminalContainers.service'
|
import { TerminalFrontendService } from '../services/terminalFrontend.service'
|
||||||
|
|
||||||
import { TerminalDecorator, ResizeEvent, SessionOptions } from '../api'
|
import { TerminalDecorator, ResizeEvent, SessionOptions } from '../api'
|
||||||
import { TermContainer } from '../terminalContainers/termContainer'
|
import { Frontend } from '../frontends/frontend'
|
||||||
import { hterm } from '../hterm'
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'terminalTab',
|
selector: 'terminalTab',
|
||||||
@@ -30,7 +29,7 @@ export class TerminalTabComponent extends BaseTabComponent {
|
|||||||
@Input() zoom = 0
|
@Input() zoom = 0
|
||||||
@ViewChild('content') content
|
@ViewChild('content') content
|
||||||
@HostBinding('style.background-color') backgroundColor: string
|
@HostBinding('style.background-color') backgroundColor: string
|
||||||
termContainer: TermContainer
|
frontend: Frontend
|
||||||
sessionCloseSubscription: Subscription
|
sessionCloseSubscription: Subscription
|
||||||
hotkeysSubscription: Subscription
|
hotkeysSubscription: Subscription
|
||||||
htermVisible = false
|
htermVisible = false
|
||||||
@@ -40,10 +39,10 @@ export class TerminalTabComponent extends BaseTabComponent {
|
|||||||
private contextMenu: any
|
private contextMenu: any
|
||||||
private termContainerSubscriptions: Subscription[] = []
|
private termContainerSubscriptions: Subscription[] = []
|
||||||
|
|
||||||
get input$ (): Observable<string> { return this.termContainer.input$ }
|
get input$ (): Observable<string> { return this.frontend.input$ }
|
||||||
get output$ (): Observable<string> { return this.output }
|
get output$ (): Observable<string> { return this.output }
|
||||||
get resize$ (): Observable<ResizeEvent> { return this.termContainer.resize$ }
|
get resize$ (): Observable<ResizeEvent> { return this.frontend.resize$ }
|
||||||
get alternateScreenActive$ (): Observable<boolean> { return this.termContainer.alternateScreenActive$ }
|
get alternateScreenActive$ (): Observable<boolean> { return this.frontend.alternateScreenActive$ }
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
private zone: NgZone,
|
private zone: NgZone,
|
||||||
@@ -53,7 +52,7 @@ export class TerminalTabComponent extends BaseTabComponent {
|
|||||||
private sessions: SessionsService,
|
private sessions: SessionsService,
|
||||||
private electron: ElectronService,
|
private electron: ElectronService,
|
||||||
private terminalService: TerminalService,
|
private terminalService: TerminalService,
|
||||||
private terminalContainersService: TerminalContainersService,
|
private terminalContainersService: TerminalFrontendService,
|
||||||
public config: ConfigService,
|
public config: ConfigService,
|
||||||
private toastr: ToastrService,
|
private toastr: ToastrService,
|
||||||
@Optional() @Inject(TerminalDecorator) private decorators: TerminalDecorator[],
|
@Optional() @Inject(TerminalDecorator) private decorators: TerminalDecorator[],
|
||||||
@@ -70,23 +69,23 @@ export class TerminalTabComponent extends BaseTabComponent {
|
|||||||
}
|
}
|
||||||
switch (hotkey) {
|
switch (hotkey) {
|
||||||
case 'ctrl-c':
|
case 'ctrl-c':
|
||||||
if (this.termContainer.getSelection()) {
|
if (this.frontend.getSelection()) {
|
||||||
this.termContainer.copySelection()
|
this.frontend.copySelection()
|
||||||
this.termContainer.clearSelection()
|
this.frontend.clearSelection()
|
||||||
this.toastr.info('Copied')
|
this.toastr.info('Copied')
|
||||||
} else {
|
} else {
|
||||||
this.sendInput('\x03')
|
this.sendInput('\x03')
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
case 'copy':
|
case 'copy':
|
||||||
this.termContainer.copySelection()
|
this.frontend.copySelection()
|
||||||
this.toastr.info('Copied')
|
this.toastr.info('Copied')
|
||||||
break
|
break
|
||||||
case 'paste':
|
case 'paste':
|
||||||
this.paste()
|
this.paste()
|
||||||
break
|
break
|
||||||
case 'clear':
|
case 'clear':
|
||||||
this.termContainer.clear()
|
this.frontend.clear()
|
||||||
break
|
break
|
||||||
case 'zoom-in':
|
case 'zoom-in':
|
||||||
this.zoomIn()
|
this.zoomIn()
|
||||||
@@ -139,7 +138,7 @@ export class TerminalTabComponent extends BaseTabComponent {
|
|||||||
})
|
})
|
||||||
|
|
||||||
this.sessionCloseSubscription = this.session.closed$.subscribe(() => {
|
this.sessionCloseSubscription = this.session.closed$.subscribe(() => {
|
||||||
this.termContainer.destroy()
|
this.frontend.destroy()
|
||||||
this.app.closeTab(this)
|
this.app.closeTab(this)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -154,16 +153,16 @@ export class TerminalTabComponent extends BaseTabComponent {
|
|||||||
ngOnInit () {
|
ngOnInit () {
|
||||||
this.focused$.subscribe(() => {
|
this.focused$.subscribe(() => {
|
||||||
this.configure()
|
this.configure()
|
||||||
this.termContainer.focus()
|
this.frontend.focus()
|
||||||
})
|
})
|
||||||
|
|
||||||
this.termContainer = this.terminalContainersService.getContainer(this.session)
|
this.frontend = this.terminalContainersService.getFrontend(this.session)
|
||||||
|
|
||||||
this.termContainer.ready$.subscribe(() => {
|
this.frontend.ready$.subscribe(() => {
|
||||||
this.htermVisible = true
|
this.htermVisible = true
|
||||||
})
|
})
|
||||||
|
|
||||||
this.termContainer.resize$.pipe(first()).subscribe(async ({columns, rows}) => {
|
this.frontend.resize$.pipe(first()).subscribe(async ({columns, rows}) => {
|
||||||
if (!this.session.open) {
|
if (!this.session.open) {
|
||||||
this.initializeSession(columns, rows)
|
this.initializeSession(columns, rows)
|
||||||
}
|
}
|
||||||
@@ -175,7 +174,8 @@ export class TerminalTabComponent extends BaseTabComponent {
|
|||||||
this.session.releaseInitialDataBuffer()
|
this.session.releaseInitialDataBuffer()
|
||||||
})
|
})
|
||||||
|
|
||||||
this.termContainer.attach(this.content.nativeElement)
|
this.frontend.configure(this.config.store)
|
||||||
|
this.frontend.attach(this.content.nativeElement)
|
||||||
this.attachTermContainerHandlers()
|
this.attachTermContainerHandlers()
|
||||||
|
|
||||||
this.configure()
|
this.configure()
|
||||||
@@ -190,16 +190,16 @@ export class TerminalTabComponent extends BaseTabComponent {
|
|||||||
})
|
})
|
||||||
}, 1000)
|
}, 1000)
|
||||||
|
|
||||||
this.termContainer.bell$.subscribe(() => {
|
this.frontend.bell$.subscribe(() => {
|
||||||
if (this.config.store.terminal.bell === 'visual') {
|
if (this.config.store.terminal.bell === 'visual') {
|
||||||
this.termContainer.visualBell()
|
this.frontend.visualBell()
|
||||||
}
|
}
|
||||||
if (this.config.store.terminal.bell === 'audible') {
|
if (this.config.store.terminal.bell === 'audible') {
|
||||||
this.bellPlayer.play()
|
this.bellPlayer.play()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
this.contextMenu = this.electron.remote.Menu.buildFromTemplate([
|
this.contextMenu = [
|
||||||
{
|
{
|
||||||
label: 'New terminal',
|
label: 'New terminal',
|
||||||
click: () => {
|
click: () => {
|
||||||
@@ -213,7 +213,7 @@ export class TerminalTabComponent extends BaseTabComponent {
|
|||||||
click: () => {
|
click: () => {
|
||||||
this.zone.run(() => {
|
this.zone.run(() => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.termContainer.copySelection()
|
this.frontend.copySelection()
|
||||||
this.toastr.info('Copied')
|
this.toastr.info('Copied')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -227,7 +227,7 @@ export class TerminalTabComponent extends BaseTabComponent {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
])
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
detachTermContainerHandlers () {
|
detachTermContainerHandlers () {
|
||||||
@@ -240,18 +240,16 @@ export class TerminalTabComponent extends BaseTabComponent {
|
|||||||
attachTermContainerHandlers () {
|
attachTermContainerHandlers () {
|
||||||
this.detachTermContainerHandlers()
|
this.detachTermContainerHandlers()
|
||||||
this.termContainerSubscriptions = [
|
this.termContainerSubscriptions = [
|
||||||
this.termContainer.title$.subscribe(title => this.zone.run(() => this.setTitle(title))),
|
this.frontend.title$.subscribe(title => this.zone.run(() => this.setTitle(title))),
|
||||||
|
|
||||||
this.focused$.subscribe(() => this.termContainer.enableResizing = true),
|
this.focused$.subscribe(() => this.frontend.enableResizing = true),
|
||||||
this.blurred$.subscribe(() => this.termContainer.enableResizing = false),
|
this.blurred$.subscribe(() => this.frontend.enableResizing = false),
|
||||||
|
|
||||||
this.termContainer.mouseEvent$.subscribe(event => {
|
this.frontend.mouseEvent$.subscribe(event => {
|
||||||
if (event.type === 'mousedown') {
|
if (event.type === 'mousedown') {
|
||||||
if (event.which === 3) {
|
if (event.which === 3) {
|
||||||
if (this.config.store.terminal.rightClick === 'menu') {
|
if (this.config.store.terminal.rightClick === 'menu') {
|
||||||
this.contextMenu.popup({
|
this.hostApp.popupContextMenu(this.contextMenu)
|
||||||
async: true,
|
|
||||||
})
|
|
||||||
} else if (this.config.store.terminal.rightClick === 'paste') {
|
} else if (this.config.store.terminal.rightClick === 'paste') {
|
||||||
this.paste()
|
this.paste()
|
||||||
}
|
}
|
||||||
@@ -275,11 +273,11 @@ export class TerminalTabComponent extends BaseTabComponent {
|
|||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
|
||||||
this.termContainer.input$.subscribe(data => {
|
this.frontend.input$.subscribe(data => {
|
||||||
this.sendInput(data)
|
this.sendInput(data)
|
||||||
}),
|
}),
|
||||||
|
|
||||||
this.termContainer.resize$.subscribe(({columns, rows}) => {
|
this.frontend.resize$.subscribe(({columns, rows}) => {
|
||||||
console.log(`Resizing to ${columns}x${rows}`)
|
console.log(`Resizing to ${columns}x${rows}`)
|
||||||
this.zone.run(() => {
|
this.zone.run(() => {
|
||||||
if (this.session.open) {
|
if (this.session.open) {
|
||||||
@@ -292,24 +290,38 @@ export class TerminalTabComponent extends BaseTabComponent {
|
|||||||
|
|
||||||
sendInput (data: string) {
|
sendInput (data: string) {
|
||||||
this.session.write(data)
|
this.session.write(data)
|
||||||
|
this.frontend.scrollToBottom()
|
||||||
}
|
}
|
||||||
|
|
||||||
write (data: string) {
|
write (data: string) {
|
||||||
this.termContainer.write(data)
|
let percentageMatch = /(^|[^\d])(\d+(\.\d+)?)%([^\d]|$)/.exec(data)
|
||||||
|
if (percentageMatch) {
|
||||||
|
let percentage = percentageMatch[3] ? parseFloat(percentageMatch[2]) : parseInt(percentageMatch[2])
|
||||||
|
if (percentage > 0 && percentage <= 100) {
|
||||||
|
this.setProgress(percentage)
|
||||||
|
console.log('Detected progress:', percentage)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.setProgress(null)
|
||||||
|
}
|
||||||
|
this.frontend.write(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
paste () {
|
paste () {
|
||||||
let data = this.electron.clipboard.readText()
|
let data = this.electron.clipboard.readText()
|
||||||
data = hterm.lib.encodeUTF8(data)
|
|
||||||
if (this.config.store.terminal.bracketedPaste) {
|
if (this.config.store.terminal.bracketedPaste) {
|
||||||
data = '\x1b[200~' + data + '\x1b[201~'
|
data = '\x1b[200~' + data + '\x1b[201~'
|
||||||
}
|
}
|
||||||
data = data.replace(/\n/g, '\r')
|
if (this.hostApp.platform === Platform.Windows) {
|
||||||
|
data = data.replace(/\r\n/g, '\r')
|
||||||
|
} else {
|
||||||
|
data = data.replace(/\n/g, '\r')
|
||||||
|
}
|
||||||
this.sendInput(data)
|
this.sendInput(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
configure (): void {
|
configure (): void {
|
||||||
this.termContainer.configure(this.config.store)
|
this.frontend.configure(this.config.store)
|
||||||
|
|
||||||
if (this.config.store.terminal.background === 'colorScheme') {
|
if (this.config.store.terminal.background === 'colorScheme') {
|
||||||
if (this.config.store.terminal.colorScheme.background) {
|
if (this.config.store.terminal.colorScheme.background) {
|
||||||
@@ -322,20 +334,21 @@ export class TerminalTabComponent extends BaseTabComponent {
|
|||||||
|
|
||||||
zoomIn () {
|
zoomIn () {
|
||||||
this.zoom++
|
this.zoom++
|
||||||
this.termContainer.setZoom(this.zoom)
|
this.frontend.setZoom(this.zoom)
|
||||||
}
|
}
|
||||||
|
|
||||||
zoomOut () {
|
zoomOut () {
|
||||||
this.zoom--
|
this.zoom--
|
||||||
this.termContainer.setZoom(this.zoom)
|
this.frontend.setZoom(this.zoom)
|
||||||
}
|
}
|
||||||
|
|
||||||
resetZoom () {
|
resetZoom () {
|
||||||
this.zoom = 0
|
this.zoom = 0
|
||||||
this.termContainer.setZoom(this.zoom)
|
this.frontend.setZoom(this.zoom)
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy () {
|
ngOnDestroy () {
|
||||||
|
this.frontend.detach(this.content.nativeElement)
|
||||||
this.detachTermContainerHandlers()
|
this.detachTermContainerHandlers()
|
||||||
this.config.enabledServices(this.decorators).forEach(decorator => {
|
this.config.enabledServices(this.decorators).forEach(decorator => {
|
||||||
decorator.detach(this)
|
decorator.detach(this)
|
||||||
|
@@ -3,6 +3,7 @@ import { ConfigProvider, Platform } from 'terminus-core'
|
|||||||
export class TerminalConfigProvider extends ConfigProvider {
|
export class TerminalConfigProvider extends ConfigProvider {
|
||||||
defaults = {
|
defaults = {
|
||||||
terminal: {
|
terminal: {
|
||||||
|
frontend: 'hterm',
|
||||||
autoOpen: false,
|
autoOpen: false,
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
linePadding: 0,
|
linePadding: 0,
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import { Observable, Subject, AsyncSubject, ReplaySubject, BehaviorSubject } from 'rxjs'
|
import { Observable, Subject, AsyncSubject, ReplaySubject, BehaviorSubject } from 'rxjs'
|
||||||
import { ResizeEvent } from '../api'
|
import { ResizeEvent } from '../api'
|
||||||
|
|
||||||
export abstract class TermContainer {
|
export abstract class Frontend {
|
||||||
enableResizing = true
|
enableResizing = true
|
||||||
protected ready = new AsyncSubject<void>()
|
protected ready = new AsyncSubject<void>()
|
||||||
protected title = new ReplaySubject<string>(1)
|
protected title = new ReplaySubject<string>(1)
|
||||||
@@ -26,6 +26,7 @@ export abstract class TermContainer {
|
|||||||
get drop$ (): Observable<DragEvent> { return this.drop }
|
get drop$ (): Observable<DragEvent> { return this.drop }
|
||||||
|
|
||||||
abstract attach (host: HTMLElement): void
|
abstract attach (host: HTMLElement): void
|
||||||
|
detach (host: HTMLElement): void { } // tslint:disable-line
|
||||||
|
|
||||||
destroy (): void {
|
destroy (): void {
|
||||||
for (let o of [
|
for (let o of [
|
||||||
@@ -51,6 +52,7 @@ export abstract class TermContainer {
|
|||||||
abstract write (data: string): void
|
abstract write (data: string): void
|
||||||
abstract clear (): void
|
abstract clear (): void
|
||||||
abstract visualBell (): void
|
abstract visualBell (): void
|
||||||
|
abstract scrollToBottom (): void
|
||||||
|
|
||||||
abstract configure (configStore: any): void
|
abstract configure (configStore: any): void
|
||||||
abstract setZoom (zoom: number): void
|
abstract setZoom (zoom: number): void
|
@@ -1,13 +1,14 @@
|
|||||||
import { TermContainer } from './termContainer'
|
import { Frontend } from './frontend'
|
||||||
import { hterm, preferenceManager } from '../hterm'
|
import { hterm, preferenceManager } from '../hterm'
|
||||||
|
|
||||||
export class HTermContainer extends TermContainer {
|
export class HTermFrontend extends Frontend {
|
||||||
term: any
|
term: any
|
||||||
io: any
|
io: any
|
||||||
private htermIframe: HTMLElement
|
private htermIframe: HTMLElement
|
||||||
private initialized = false
|
private initialized = false
|
||||||
private configuredFontSize = 0
|
private configuredFontSize = 0
|
||||||
private configuredLinePadding = 0
|
private configuredLinePadding = 0
|
||||||
|
private configuredBackgroundColor = 'transparent'
|
||||||
private zoom = 0
|
private zoom = 0
|
||||||
|
|
||||||
attach (host: HTMLElement) {
|
attach (host: HTMLElement) {
|
||||||
@@ -50,6 +51,9 @@ export class HTermContainer extends TermContainer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
configure (config: any): void {
|
configure (config: any): void {
|
||||||
|
if (!this.term) {
|
||||||
|
return
|
||||||
|
}
|
||||||
this.configuredFontSize = config.terminal.fontSize
|
this.configuredFontSize = config.terminal.fontSize
|
||||||
this.configuredLinePadding = config.terminal.linePadding
|
this.configuredLinePadding = config.terminal.linePadding
|
||||||
this.setFontSize()
|
this.setFontSize()
|
||||||
@@ -84,8 +88,13 @@ export class HTermContainer extends TermContainer {
|
|||||||
preferenceManager.set('background-color', 'transparent')
|
preferenceManager.set('background-color', 'transparent')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.configuredBackgroundColor = preferenceManager.get('background-color')
|
||||||
|
|
||||||
if (config.terminal.colorScheme.colors) {
|
if (config.terminal.colorScheme.colors) {
|
||||||
preferenceManager.set('color-palette-overrides', config.terminal.colorScheme.colors)
|
preferenceManager.set(
|
||||||
|
'color-palette-overrides',
|
||||||
|
Object.assign([], config.terminal.colorScheme.colors, this.term.colorPaletteOverrides)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
if (config.terminal.colorScheme.cursor) {
|
if (config.terminal.colorScheme.cursor) {
|
||||||
preferenceManager.set('cursor-color', config.terminal.colorScheme.cursor)
|
preferenceManager.set('cursor-color', config.terminal.colorScheme.cursor)
|
||||||
@@ -128,19 +137,23 @@ export class HTermContainer extends TermContainer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
visualBell (): void {
|
visualBell (): void {
|
||||||
const color = preferenceManager.get('background-color')
|
|
||||||
preferenceManager.set('background-color', 'rgba(128,128,128,.25)')
|
preferenceManager.set('background-color', 'rgba(128,128,128,.25)')
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
preferenceManager.set('background-color', color)
|
preferenceManager.set('background-color', this.configuredBackgroundColor)
|
||||||
}, 125)
|
}, 125)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
scrollToBottom (): void {
|
||||||
|
this.term.scrollEnd()
|
||||||
|
}
|
||||||
|
|
||||||
private setFontSize () {
|
private setFontSize () {
|
||||||
preferenceManager.set('font-size', this.configuredFontSize * Math.pow(1.1, this.zoom))
|
preferenceManager.set('font-size', this.configuredFontSize * Math.pow(1.1, this.zoom))
|
||||||
}
|
}
|
||||||
|
|
||||||
private init () {
|
private init () {
|
||||||
this.term = new hterm.hterm.Terminal()
|
this.term = new hterm.hterm.Terminal()
|
||||||
|
this.term.colorPaletteOverrides = []
|
||||||
this.term.onTerminalReady = () => {
|
this.term.onTerminalReady = () => {
|
||||||
this.term.installKeyboard()
|
this.term.installKeyboard()
|
||||||
this.term.scrollPort_.setCtrlVPaste(true)
|
this.term.scrollPort_.setCtrlVPaste(true)
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user