Compare commits
62 Commits
v1.0.0-alp
...
v1.0.0-alp
Author | SHA1 | Date | |
---|---|---|---|
![]() |
0a4fadd5ba | ||
![]() |
0b56259a36 | ||
![]() |
0e86894d81 | ||
![]() |
6ee5275981 | ||
![]() |
029e4016af | ||
![]() |
6119d211c2 | ||
![]() |
23e93f0969 | ||
![]() |
9e228a4e93 | ||
![]() |
100a8cacdd | ||
![]() |
abb313d118 | ||
![]() |
aaf6209d9f | ||
![]() |
e0e24878e2 | ||
![]() |
deca9a20b4 | ||
![]() |
b57ff8f37a | ||
![]() |
55a54a1399 | ||
![]() |
538b5c4c28 | ||
![]() |
0419900e1d | ||
![]() |
cc9c66c4a9 | ||
![]() |
7e253d72ea | ||
![]() |
9423ce7c10 | ||
![]() |
ac8bb2de49 | ||
![]() |
ace6446790 | ||
![]() |
259a1d26b0 | ||
![]() |
7c03b62ea3 | ||
![]() |
950f071737 | ||
![]() |
9706c1079e | ||
![]() |
9c6f2747aa | ||
![]() |
1eb2dfd1b6 | ||
![]() |
c0df8f4d41 | ||
![]() |
1e902d734f | ||
![]() |
59a3c9aeb6 | ||
![]() |
21a2fa6da5 | ||
![]() |
42584e1116 | ||
![]() |
7bfc13dae5 | ||
![]() |
7cb6642f1e | ||
![]() |
f011b03fb2 | ||
![]() |
c0d8709a4c | ||
![]() |
5d605a4853 | ||
![]() |
1d69082e6c | ||
![]() |
4d91027b2c | ||
![]() |
86a21c03d2 | ||
![]() |
51950b816f | ||
![]() |
d3a192da58 | ||
![]() |
4b30dfef58 | ||
![]() |
8432e3ef66 | ||
![]() |
cdfd84a7f8 | ||
![]() |
128fe24003 | ||
![]() |
30f221d05e | ||
![]() |
5087224017 | ||
![]() |
9a8bad4851 | ||
![]() |
c3c983daf6 | ||
![]() |
dce8647f55 | ||
![]() |
f947fe3f0f | ||
![]() |
b5f96a59f8 | ||
![]() |
c90a5678cf | ||
![]() |
663da34e6d | ||
![]() |
049f08b8f9 | ||
![]() |
3c3b14bf09 | ||
![]() |
5e07dd5442 | ||
![]() |
8f2d2cbe30 | ||
![]() |
bebde4799d | ||
![]() |
9cedeb3efb |
1
.gitignore
vendored
@@ -16,3 +16,4 @@ npm-debug.log
|
|||||||
|
|
||||||
builtin-plugins
|
builtin-plugins
|
||||||
package-lock.json
|
package-lock.json
|
||||||
|
yarn-error.log
|
||||||
|
2
.pug-lintrc.js
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
module.export = {
|
||||||
|
}
|
14
README.md
@@ -16,18 +16,16 @@
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
**Terminus** is a web technology based terminal heavily inspired by Hyper. It is, however, designed for people who need to get things done.
|
**Terminus** is a terminal heavily inspired by Hyper. It is, however, designed for people who need to get things done.
|
||||||
|
|
||||||
* Runs on Windows, macOS and Linux
|
* Runs on Windows, macOS and Linux
|
||||||
* Theming and color schemes
|
* Theming and color schemes
|
||||||
* Configurable hotkey schemes
|
* Fully configurable shortcuts
|
||||||
* **GNU Screen** style hotkeys available by default
|
|
||||||
* Full Unicode support including double-width characters
|
* Full Unicode support including double-width characters
|
||||||
* Doesn't choke on fast-flowing outputs
|
* Doesn't choke on fast-flowing outputs
|
||||||
|
* Proper shell-like experience on Windows including tab completion (via Clink)
|
||||||
|
* CMD, PowerShell, PowerShell Core, Cygwin, Cmder, Git-Bash and WSL (Bash on Windows) support
|
||||||
* Tab persistence on macOS and Linux
|
* Tab persistence on macOS and Linux
|
||||||
* Proper shell-like experience on Windows including tab completion (thanks, Clink!)
|
|
||||||
* CMD, PowerShell, Cygwin, Git-Bash and Bash on Windows support
|
|
||||||
* Default Linux style hotkeys for copy (`Ctrl`+`Shift`+`C`) and paste (`Ctrl`+`Shift`+`V`)
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -38,12 +36,14 @@ 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
|
||||||
|
* [scrollbar](https://github.com/kbjr/terminus-scrollbar) - adds a scrollbar to terminal tabs
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# Contributing
|
# Contributing
|
||||||
|
|
||||||
Pull requests and plugins are welcome! Publish your plugin on NPM with a `terminus-plugin` keyword to make them appear in the Plugin Manager.
|
Pull requests and plugins are welcome! Publish your plugin on NPM with a `terminus-plugin` keyword to make it appear in the Plugin Manager.
|
||||||
|
|
||||||
See [HACKING.md](https://github.com/Eugeny/terminus/blob/master/HACKING.md) for a very brief plugin development tutorial!
|
See [HACKING.md](https://github.com/Eugeny/terminus/blob/master/HACKING.md) for a very brief plugin development tutorial!
|
||||||
|
|
||||||
|
50
app/bufferizedPTY.js
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
module.exports = function patchPTYModule (path) {
|
||||||
|
const mod = require(path)
|
||||||
|
const oldSpawn = mod.spawn
|
||||||
|
if (mod.patched) {
|
||||||
|
return mod
|
||||||
|
}
|
||||||
|
mod.patched = true
|
||||||
|
mod.spawn = (file, args, opt) => {
|
||||||
|
let terminal = oldSpawn(file, args, opt)
|
||||||
|
let timeout = null
|
||||||
|
let buffer = ''
|
||||||
|
let lastFlush = 0
|
||||||
|
let nextTimeout = 0
|
||||||
|
|
||||||
|
const maxWindow = 250
|
||||||
|
const minWindow = 50
|
||||||
|
|
||||||
|
function flush () {
|
||||||
|
if (buffer) {
|
||||||
|
terminal.emit('data-buffered', buffer)
|
||||||
|
}
|
||||||
|
lastFlush = Date.now()
|
||||||
|
buffer = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
function reschedule () {
|
||||||
|
if (timeout) {
|
||||||
|
clearTimeout(timeout)
|
||||||
|
}
|
||||||
|
nextTimeout = Date.now() + minWindow
|
||||||
|
timeout = setTimeout(() => {
|
||||||
|
timeout = null
|
||||||
|
flush()
|
||||||
|
}, minWindow)
|
||||||
|
}
|
||||||
|
|
||||||
|
terminal.on('data', data => {
|
||||||
|
buffer += data
|
||||||
|
if (Date.now() - lastFlush > maxWindow) {
|
||||||
|
flush()
|
||||||
|
} else {
|
||||||
|
if (Date.now() > nextTimeout - (minWindow / 10)) {
|
||||||
|
reschedule()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return terminal
|
||||||
|
}
|
||||||
|
return mod
|
||||||
|
}
|
@@ -9,7 +9,9 @@ html
|
|||||||
script(src='./preload.js')
|
script(src='./preload.js')
|
||||||
script(src='./bundle.js', defer)
|
script(src='./bundle.js', defer)
|
||||||
style#custom-css
|
style#custom-css
|
||||||
body(style='background: ; min-height: 100vh; overflow: hidden')
|
style.
|
||||||
|
body { transition: 0.5s background; }
|
||||||
|
body
|
||||||
app-root
|
app-root
|
||||||
.preload-logo
|
.preload-logo
|
||||||
div
|
div
|
||||||
|
54
app/main.js
@@ -1,11 +1,15 @@
|
|||||||
if (process.platform == 'win32' && require('electron-squirrel-startup')) process.exit(0)
|
if (process.platform == 'win32' && require('electron-squirrel-startup')) process.exit(0)
|
||||||
|
|
||||||
const electron = require('electron')
|
const electron = require('electron')
|
||||||
|
let electronVibrancy
|
||||||
|
if (process.platform != 'linux') {
|
||||||
|
electronVibrancy = require('electron-vibrancy')
|
||||||
|
}
|
||||||
|
|
||||||
if (process.argv.indexOf('--debug') !== -1) {
|
if (process.argv.indexOf('--debug') !== -1) {
|
||||||
require('electron-debug')({enabled: true, showDevTools: 'undocked'})
|
require('electron-debug')({enabled: true, showDevTools: 'undocked'})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
let app = electron.app
|
let app = electron.app
|
||||||
|
|
||||||
let secondInstance = app.makeSingleInstance((argv, cwd) => {
|
let secondInstance = app.makeSingleInstance((argv, cwd) => {
|
||||||
@@ -29,6 +33,15 @@ if (!process.env.TERMINUS_PLUGINS) {
|
|||||||
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 = () => {
|
setupWindowManagement = () => {
|
||||||
app.window.on('show', () => {
|
app.window.on('show', () => {
|
||||||
app.window.webContents.send('host:window-shown')
|
app.window.webContents.send('host:window-shown')
|
||||||
@@ -44,6 +57,9 @@ setupWindowManagement = () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
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) => {
|
app.window.on('close', (e) => {
|
||||||
windowConfig.set('windowBoundaries', app.window.getBounds())
|
windowConfig.set('windowBoundaries', app.window.getBounds())
|
||||||
})
|
})
|
||||||
@@ -56,14 +72,6 @@ setupWindowManagement = () => {
|
|||||||
app.window.focus()
|
app.window.focus()
|
||||||
})
|
})
|
||||||
|
|
||||||
electron.ipcMain.on('window-toggle-focus', () => {
|
|
||||||
if (app.window.isFocused()) {
|
|
||||||
app.window.minimize()
|
|
||||||
} else {
|
|
||||||
app.window.focus()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
electron.ipcMain.on('window-maximize', () => {
|
electron.ipcMain.on('window-maximize', () => {
|
||||||
app.window.maximize()
|
app.window.maximize()
|
||||||
})
|
})
|
||||||
@@ -91,6 +99,10 @@ setupWindowManagement = () => {
|
|||||||
electron.ipcMain.on('window-set-always-on-top', (event, flag) => {
|
electron.ipcMain.on('window-set-always-on-top', (event, flag) => {
|
||||||
app.window.setAlwaysOnTop(flag)
|
app.window.setAlwaysOnTop(flag)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
electron.ipcMain.on('window-set-vibrancy', (event, enabled) => {
|
||||||
|
setWindowVibrancy(enabled)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -183,7 +195,6 @@ setupMenu = () => {
|
|||||||
{
|
{
|
||||||
role: 'window',
|
role: 'window',
|
||||||
submenu: [
|
submenu: [
|
||||||
{role: 'close'},
|
|
||||||
{role: 'minimize'},
|
{role: 'minimize'},
|
||||||
{role: 'zoom'},
|
{role: 'zoom'},
|
||||||
{type: 'separator'},
|
{type: 'separator'},
|
||||||
@@ -221,11 +232,11 @@ start = () => {
|
|||||||
title: 'Terminus',
|
title: 'Terminus',
|
||||||
minWidth: 400,
|
minWidth: 400,
|
||||||
minHeight: 300,
|
minHeight: 300,
|
||||||
'web-preferences': {'web-security': false},
|
webPreferences: {webSecurity: false},
|
||||||
//- background to avoid the flash of unstyled window
|
//- background to avoid the flash of unstyled window
|
||||||
backgroundColor: '#131d27',
|
backgroundColor: '#131d27',
|
||||||
frame: false,
|
frame: false,
|
||||||
//type: 'toolbar',
|
show: false,
|
||||||
}
|
}
|
||||||
Object.assign(options, windowConfig.get('windowBoundaries'))
|
Object.assign(options, windowConfig.get('windowBoundaries'))
|
||||||
|
|
||||||
@@ -233,22 +244,33 @@ start = () => {
|
|||||||
options.frame = true
|
options.frame = true
|
||||||
} else {
|
} else {
|
||||||
if (process.platform == 'darwin') {
|
if (process.platform == 'darwin') {
|
||||||
options.titleBarStyle = 'hidden-inset'
|
options.titleBarStyle = 'hiddenInset'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (['darwin', 'win32'].includes(process.platform)) {
|
||||||
|
options.transparent = true
|
||||||
|
delete options.backgroundColor
|
||||||
|
}
|
||||||
|
|
||||||
app.commandLine.appendSwitch('disable-http-cache')
|
app.commandLine.appendSwitch('disable-http-cache')
|
||||||
|
|
||||||
app.window = new electron.BrowserWindow(options)
|
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"})
|
app.window.loadURL(`file://${app.getAppPath()}/dist/index.html`, {extraHeaders: "pragma: no-cache\n"})
|
||||||
|
|
||||||
if (process.platform != 'darwin') {
|
if (process.platform != 'darwin') {
|
||||||
app.window.setMenu(null)
|
app.window.setMenu(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
app.window.show()
|
|
||||||
app.window.focus()
|
|
||||||
|
|
||||||
setupWindowManagement()
|
setupWindowManagement()
|
||||||
|
|
||||||
if (process.platform == 'darwin') {
|
if (process.platform == 'darwin') {
|
||||||
|
@@ -12,25 +12,26 @@
|
|||||||
"watch": "webpack --progress --color --watch"
|
"watch": "webpack --progress --color --watch"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@angular/animations": "4.3.0",
|
"@angular/animations": "6.0.2",
|
||||||
"@angular/common": "4.3.0",
|
"@angular/common": "6.0.2",
|
||||||
"@angular/compiler": "4.3.0",
|
"@angular/compiler": "6.0.2",
|
||||||
"@angular/core": "4.3.0",
|
"@angular/core": "6.0.2",
|
||||||
"@angular/forms": "4.3.0",
|
"@angular/forms": "6.0.2",
|
||||||
"@angular/platform-browser": "4.3.0",
|
"@angular/platform-browser": "6.0.2",
|
||||||
"@angular/platform-browser-dynamic": "4.3.0",
|
"@angular/platform-browser-dynamic": "6.0.2",
|
||||||
"@ng-bootstrap/ng-bootstrap": "^1.0.0-beta.2",
|
"@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": "^1.0.1",
|
||||||
"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",
|
||||||
"js-yaml": "3.8.2",
|
"js-yaml": "3.8.2",
|
||||||
"mz": "^2.6.0",
|
"mz": "^2.6.0",
|
||||||
"ngx-toastr": "^8.0.0",
|
"ngx-toastr": "^8.7.3",
|
||||||
"path": "0.12.7",
|
"path": "0.12.7",
|
||||||
"rxjs": "5.3.0",
|
"rxjs": "^6.1.0",
|
||||||
"zone.js": "0.8.12"
|
"zone.js": "~0.8.26"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/mz": "0.0.31"
|
"@types/mz": "0.0.31"
|
||||||
|
@@ -2,6 +2,7 @@ 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 './toastr.scss'
|
||||||
|
|
||||||
// Always land on the start view
|
// Always land on the start view
|
||||||
location.hash = ''
|
location.hash = ''
|
||||||
|
@@ -61,6 +61,7 @@ const builtinModules = [
|
|||||||
'@ng-bootstrap/ng-bootstrap',
|
'@ng-bootstrap/ng-bootstrap',
|
||||||
'ngx-toastr',
|
'ngx-toastr',
|
||||||
'rxjs',
|
'rxjs',
|
||||||
|
'rxjs/operators',
|
||||||
'terminus-core',
|
'terminus-core',
|
||||||
'terminus-settings',
|
'terminus-settings',
|
||||||
'terminus-terminal',
|
'terminus-terminal',
|
||||||
|
@@ -59,6 +59,16 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-dialog {
|
.modal-dialog, .modal-backdrop {
|
||||||
-webkit-app-region: no-drag;
|
-webkit-app-region: no-drag;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[ngbradiogroup] input[type="radio"] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
min-height: 100vh;
|
||||||
|
overflow: hidden;
|
||||||
|
background: rgba(0,0,0,.4);
|
||||||
|
}
|
||||||
|
16
app/src/toastr.scss
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
#toast-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.toast {
|
||||||
|
box-shadow: 0 1px 0 rgba(0,0,0,.25);
|
||||||
|
padding: 10px;
|
||||||
|
background-image: none;
|
||||||
|
width: auto;
|
||||||
|
|
||||||
|
&.toast-info {
|
||||||
|
background-color: #555;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -21,30 +21,36 @@ module.exports = {
|
|||||||
extensions: ['.ts', '.js'],
|
extensions: ['.ts', '.js'],
|
||||||
},
|
},
|
||||||
module: {
|
module: {
|
||||||
loaders: [
|
rules: [
|
||||||
{
|
{
|
||||||
test: /\.ts$/,
|
test: /\.ts$/,
|
||||||
|
use: {
|
||||||
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'] },
|
||||||
{
|
{
|
||||||
test: /\.(png|svg)$/,
|
test: /\.(png|svg)$/,
|
||||||
loader: "file-loader",
|
use: {
|
||||||
|
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])?$/,
|
||||||
loader: "file-loader",
|
use: {
|
||||||
|
loader: 'file-loader',
|
||||||
options: {
|
options: {
|
||||||
name: 'fonts/[name].[ext]'
|
name: 'fonts/[name].[ext]'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
externals: {
|
externals: {
|
||||||
|
109
app/yarn.lock
@@ -2,51 +2,51 @@
|
|||||||
# yarn lockfile v1
|
# yarn lockfile v1
|
||||||
|
|
||||||
|
|
||||||
"@angular/animations@4.3.0":
|
"@angular/animations@6.0.2":
|
||||||
version "4.3.0"
|
version "6.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/@angular/animations/-/animations-4.3.0.tgz#56f34b84649379202ac359929b82eb0b915e9c72"
|
resolved "https://registry.yarnpkg.com/@angular/animations/-/animations-6.0.2.tgz#92063f612c3b33d962eddc9ad538cadd231fbe47"
|
||||||
dependencies:
|
dependencies:
|
||||||
tslib "^1.7.1"
|
tslib "^1.9.0"
|
||||||
|
|
||||||
"@angular/common@4.3.0":
|
"@angular/common@6.0.2":
|
||||||
version "4.3.0"
|
version "6.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/@angular/common/-/common-4.3.0.tgz#13a54a6929dd52f9729b16ae446fad58fe163053"
|
resolved "https://registry.yarnpkg.com/@angular/common/-/common-6.0.2.tgz#e4cbb7d45d8d2f35e918d62f0cbfd8c87e9168f7"
|
||||||
dependencies:
|
dependencies:
|
||||||
tslib "^1.7.1"
|
tslib "^1.9.0"
|
||||||
|
|
||||||
"@angular/compiler@4.3.0":
|
"@angular/compiler@6.0.2":
|
||||||
version "4.3.0"
|
version "6.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/@angular/compiler/-/compiler-4.3.0.tgz#55503bf27a1f062f71b9495393f3311903a8fc43"
|
resolved "https://registry.yarnpkg.com/@angular/compiler/-/compiler-6.0.2.tgz#b9d29b7e032c767179967540f1ed7f8777a615a1"
|
||||||
dependencies:
|
dependencies:
|
||||||
tslib "^1.7.1"
|
tslib "^1.9.0"
|
||||||
|
|
||||||
"@angular/core@4.3.0":
|
"@angular/core@6.0.2":
|
||||||
version "4.3.0"
|
version "6.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/@angular/core/-/core-4.3.0.tgz#bd2249c3de1224a7c6536c4aba728d6565329334"
|
resolved "https://registry.yarnpkg.com/@angular/core/-/core-6.0.2.tgz#d183730d73182a4590a5d71083db45655f210e69"
|
||||||
dependencies:
|
dependencies:
|
||||||
tslib "^1.7.1"
|
tslib "^1.9.0"
|
||||||
|
|
||||||
"@angular/forms@4.3.0":
|
"@angular/forms@6.0.2":
|
||||||
version "4.3.0"
|
version "6.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/@angular/forms/-/forms-4.3.0.tgz#7d0c7a854737e9a30a5fd9665f8d4f56a1b91bd8"
|
resolved "https://registry.yarnpkg.com/@angular/forms/-/forms-6.0.2.tgz#a0647930e8b6e7fbd48f55eb69b399a41bcd091a"
|
||||||
dependencies:
|
dependencies:
|
||||||
tslib "^1.7.1"
|
tslib "^1.9.0"
|
||||||
|
|
||||||
"@angular/platform-browser-dynamic@4.3.0":
|
"@angular/platform-browser-dynamic@6.0.2":
|
||||||
version "4.3.0"
|
version "6.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/@angular/platform-browser-dynamic/-/platform-browser-dynamic-4.3.0.tgz#551fb18851b27ee8f3e4b0ee25aad10bd7b312e3"
|
resolved "https://registry.yarnpkg.com/@angular/platform-browser-dynamic/-/platform-browser-dynamic-6.0.2.tgz#755689df9f02bbcb270c7872a75a2ffe7e0b0c33"
|
||||||
dependencies:
|
dependencies:
|
||||||
tslib "^1.7.1"
|
tslib "^1.9.0"
|
||||||
|
|
||||||
"@angular/platform-browser@4.3.0":
|
"@angular/platform-browser@6.0.2":
|
||||||
version "4.3.0"
|
version "6.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/@angular/platform-browser/-/platform-browser-4.3.0.tgz#02389489185185c3becf06359346100e5479c7e1"
|
resolved "https://registry.yarnpkg.com/@angular/platform-browser/-/platform-browser-6.0.2.tgz#19f56f6efbd0e7af5f35fe2f8cde3557dc8ba689"
|
||||||
dependencies:
|
dependencies:
|
||||||
tslib "^1.7.1"
|
tslib "^1.9.0"
|
||||||
|
|
||||||
"@ng-bootstrap/ng-bootstrap@^1.0.0-beta.2":
|
"@ng-bootstrap/ng-bootstrap@^2.0.0":
|
||||||
version "1.0.0-beta.2"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/@ng-bootstrap/ng-bootstrap/-/ng-bootstrap-1.0.0-beta.2.tgz#3d4b567b0334a9ee631b73c72156cd3a9d3cd29f"
|
resolved "https://registry.yarnpkg.com/@ng-bootstrap/ng-bootstrap/-/ng-bootstrap-2.0.0.tgz#65f78c7dd5a8ac424f44bb2050a9eab247cdeb0c"
|
||||||
|
|
||||||
"@types/mz@0.0.31":
|
"@types/mz@0.0.31":
|
||||||
version "0.0.31"
|
version "0.0.31"
|
||||||
@@ -72,6 +72,10 @@ argparse@^1.0.7:
|
|||||||
dependencies:
|
dependencies:
|
||||||
sprintf-js "~1.0.2"
|
sprintf-js "~1.0.2"
|
||||||
|
|
||||||
|
bindings@^1.2.1:
|
||||||
|
version "1.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.3.0.tgz#b346f6ecf6a95f5a815c5839fc7cdb22502f1ed7"
|
||||||
|
|
||||||
conf@^0.11.1:
|
conf@^0.11.1:
|
||||||
version "0.11.2"
|
version "0.11.2"
|
||||||
resolved "https://registry.yarnpkg.com/conf/-/conf-0.11.2.tgz#879f479267600483e502583462ca4063fc9779b2"
|
resolved "https://registry.yarnpkg.com/conf/-/conf-0.11.2.tgz#879f479267600483e502583462ca4063fc9779b2"
|
||||||
@@ -135,6 +139,13 @@ electron-squirrel-startup@^1.0.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
debug "^2.2.0"
|
debug "^2.2.0"
|
||||||
|
|
||||||
|
electron-vibrancy@^0.1.3:
|
||||||
|
version "0.1.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/electron-vibrancy/-/electron-vibrancy-0.1.3.tgz#04382dd6e030e5ca5e60f8e024033738cb8479e3"
|
||||||
|
dependencies:
|
||||||
|
bindings "^1.2.1"
|
||||||
|
nan "^2.0.5"
|
||||||
|
|
||||||
env-paths@^0.3.0:
|
env-paths@^0.3.0:
|
||||||
version "0.3.1"
|
version "0.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-0.3.1.tgz#c30ccfcbc30c890943dc08a85582517ef00da463"
|
resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-0.3.1.tgz#c30ccfcbc30c890943dc08a85582517ef00da463"
|
||||||
@@ -195,9 +206,15 @@ mz@^2.6.0:
|
|||||||
object-assign "^4.0.1"
|
object-assign "^4.0.1"
|
||||||
thenify-all "^1.0.0"
|
thenify-all "^1.0.0"
|
||||||
|
|
||||||
ngx-toastr@^8.0.0:
|
nan@^2.0.5:
|
||||||
version "8.0.0"
|
version "2.10.0"
|
||||||
resolved "https://registry.yarnpkg.com/ngx-toastr/-/ngx-toastr-8.0.0.tgz#f3bc53146b2f7da3eabf3daa1b1bbdf65cb49697"
|
resolved "https://registry.yarnpkg.com/nan/-/nan-2.10.0.tgz#96d0cd610ebd58d4b4de9cc0c6828cda99c7548f"
|
||||||
|
|
||||||
|
ngx-toastr@^8.7.3:
|
||||||
|
version "8.7.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/ngx-toastr/-/ngx-toastr-8.7.3.tgz#d3b7a8077ba1c860dd8a44779ccad38c5ea15c92"
|
||||||
|
dependencies:
|
||||||
|
tslib "^1.9.0"
|
||||||
|
|
||||||
object-assign@^4.0.1:
|
object-assign@^4.0.1:
|
||||||
version "4.1.1"
|
version "4.1.1"
|
||||||
@@ -236,20 +253,16 @@ process@^0.11.1:
|
|||||||
version "0.11.10"
|
version "0.11.10"
|
||||||
resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182"
|
resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182"
|
||||||
|
|
||||||
rxjs@5.3.0:
|
rxjs@^6.1.0:
|
||||||
version "5.3.0"
|
version "6.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-5.3.0.tgz#d88ccbdd46af290cbdb97d5d8055e52453fabe2d"
|
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.1.0.tgz#833447de4e4f6427b9cec3e5eb9f56415cd28315"
|
||||||
dependencies:
|
dependencies:
|
||||||
symbol-observable "^1.0.1"
|
tslib "^1.9.0"
|
||||||
|
|
||||||
sprintf-js@~1.0.2:
|
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"
|
||||||
|
|
||||||
symbol-observable@^1.0.1:
|
|
||||||
version "1.0.4"
|
|
||||||
resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.0.4.tgz#29bf615d4aa7121bdd898b22d4b3f9bc4e2aa03d"
|
|
||||||
|
|
||||||
thenify-all@^1.0.0:
|
thenify-all@^1.0.0:
|
||||||
version "1.6.0"
|
version "1.6.0"
|
||||||
resolved "https://registry.yarnpkg.com/thenify-all/-/thenify-all-1.6.0.tgz#1a1918d402d8fc3f98fbf234db0bcc8cc10e9726"
|
resolved "https://registry.yarnpkg.com/thenify-all/-/thenify-all-1.6.0.tgz#1a1918d402d8fc3f98fbf234db0bcc8cc10e9726"
|
||||||
@@ -262,9 +275,9 @@ thenify-all@^1.0.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
any-promise "^1.0.0"
|
any-promise "^1.0.0"
|
||||||
|
|
||||||
tslib@^1.7.1:
|
tslib@^1.9.0:
|
||||||
version "1.7.1"
|
version "1.9.1"
|
||||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.7.1.tgz#bc8004164691923a79fe8378bbeb3da2017538ec"
|
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.1.tgz#a5d1f0532a49221c87755cfcc89ca37197242ba7"
|
||||||
|
|
||||||
util@^0.10.3:
|
util@^0.10.3:
|
||||||
version "0.10.3"
|
version "0.10.3"
|
||||||
@@ -272,6 +285,6 @@ util@^0.10.3:
|
|||||||
dependencies:
|
dependencies:
|
||||||
inherits "2.0.1"
|
inherits "2.0.1"
|
||||||
|
|
||||||
zone.js@0.8.12:
|
zone.js@~0.8.26:
|
||||||
version "0.8.12"
|
version "0.8.26"
|
||||||
resolved "https://registry.yarnpkg.com/zone.js/-/zone.js-0.8.12.tgz#86ff5053c98aec291a0bf4bbac501d694a05cfbb"
|
resolved "https://registry.yarnpkg.com/zone.js/-/zone.js-0.8.26.tgz#7bdd72f7668c5a7ad6b118148b4ea39c59d08d2d"
|
||||||
|
Before Width: | Height: | Size: 361 KiB After Width: | Height: | Size: 106 KiB |
92
build/windows/icon.svg
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
width="1024"
|
||||||
|
height="1024"
|
||||||
|
viewBox="0 0 270.93332 270.93333"
|
||||||
|
version="1.1"
|
||||||
|
id="svg8"
|
||||||
|
inkscape:version="0.92.3 (2405546, 2018-03-11)"
|
||||||
|
sodipodi:docname="icon.svg"
|
||||||
|
inkscape:export-filename="D:\Users\Ich\Downloads\64x64.png"
|
||||||
|
inkscape:export-xdpi="6"
|
||||||
|
inkscape:export-ydpi="6">
|
||||||
|
<defs
|
||||||
|
id="defs2" />
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="base"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:zoom="0.35355339"
|
||||||
|
inkscape:cx="-57.249603"
|
||||||
|
inkscape:cy="781.4887"
|
||||||
|
inkscape:document-units="px"
|
||||||
|
inkscape:current-layer="layer1"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:snap-bbox="true"
|
||||||
|
inkscape:window-width="1858"
|
||||||
|
inkscape:window-height="1050"
|
||||||
|
inkscape:window-x="54"
|
||||||
|
inkscape:window-y="1079"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
fit-margin-top="0"
|
||||||
|
fit-margin-left="0"
|
||||||
|
fit-margin-right="0"
|
||||||
|
fit-margin-bottom="0"
|
||||||
|
inkscape:snap-intersection-paths="true"
|
||||||
|
inkscape:object-paths="true"
|
||||||
|
units="px" />
|
||||||
|
<metadata
|
||||||
|
id="metadata5">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work
|
||||||
|
rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
<dc:title></dc:title>
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<g
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1"
|
||||||
|
transform="translate(-47.511065,70.941737)">
|
||||||
|
<path
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path138"
|
||||||
|
style="opacity:0.9;fill:#bfd9f1;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:2.43524027px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1"
|
||||||
|
d="M 64.949149,-45.402272 213.30911,40.153203 168.74736,65.880709 64.949181,2.5692907 Z"
|
||||||
|
sodipodi:nodetypes="ccccc" />
|
||||||
|
<path
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path116"
|
||||||
|
style="opacity:0.9;fill:#6666af;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:2.43524027px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1"
|
||||||
|
d="m 301.0092,42.179959 -0.003,50.177506 -190.42255,107.635545 -0.003,-48.17143 z"
|
||||||
|
sodipodi:nodetypes="ccccc" />
|
||||||
|
<path
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path118"
|
||||||
|
style="opacity:0.9;fill:#bfd9f1;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:2.43524027px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1"
|
||||||
|
d="m 64.948697,125.47711 45.629963,26.34447 0.005,48.17143 -45.637407,-26.50135 z"
|
||||||
|
sodipodi:nodetypes="ccccc" />
|
||||||
|
<path
|
||||||
|
style="opacity:0.9;fill:#9dbef0;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:2.44854069px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1"
|
||||||
|
d="M 105.39355,-70.939125 64.949149,-45.402272 213.30911,40.153203 64.948697,125.47711 110.57866,151.82158 260.4947,65.557719 301.0092,42.179959 Z"
|
||||||
|
id="path134"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
sodipodi:nodetypes="cccccccc" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 3.5 KiB |
39
package.json
@@ -5,44 +5,47 @@
|
|||||||
"@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": "3.1.2",
|
"awesome-typescript-loader": "^5.0.0",
|
||||||
"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": "1.8.4",
|
"electron": "2.0.7",
|
||||||
"electron-builder": "17.1.1",
|
"electron-builder": "^20.27.1",
|
||||||
"electron-builder-squirrel-windows": "17.0.1",
|
"electron-builder-squirrel-windows": "17.0.1",
|
||||||
"electron-rebuild": "1.5.11",
|
"electron-rebuild": "^1.8.2",
|
||||||
"file-loader": "0.9.0",
|
"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",
|
||||||
"html-loader": "0.4.4",
|
"html-loader": "0.4.4",
|
||||||
"json-loader": "0.5.4",
|
"json-loader": "0.5.4",
|
||||||
"less": "2.7.1",
|
"less": "2.7.1",
|
||||||
"less-loader": "2.2.3",
|
"less-loader": "2.2.3",
|
||||||
"node-abi": "2.0.3",
|
"node-abi": "^2.4.1",
|
||||||
"node-gyp": "^3.6.2",
|
"node-gyp": "^3.6.2",
|
||||||
"node-sass": "^4.5.3",
|
"node-sass": "^4.5.3",
|
||||||
"npmlog": "4.1.0",
|
"npmlog": "4.1.0",
|
||||||
"npx": "^9.7.1",
|
"npx": "^9.7.1",
|
||||||
"pug": "2.0.0-beta11",
|
"pug": "^2.0.3",
|
||||||
"pug-html-loader": "1.0.9",
|
"pug-html-loader": "1.0.9",
|
||||||
"pug-loader": "2.3.0",
|
"pug-lint": "^2.5.0",
|
||||||
|
"pug-loader": "^2.4.0",
|
||||||
"pug-static-loader": "0.0.1",
|
"pug-static-loader": "0.0.1",
|
||||||
"raven-js": "3.16.0",
|
"raven-js": "3.16.0",
|
||||||
"raw-loader": "0.5.1",
|
"raw-loader": "0.5.1",
|
||||||
"sass-loader": "6.0.3",
|
"sass-loader": "^7.0.1",
|
||||||
"shelljs": "0.7.7",
|
"shelljs": "0.7.7",
|
||||||
"source-sans-pro": "2.0.10",
|
"source-sans-pro": "2.0.10",
|
||||||
"style-loader": "0.13.1",
|
"style-loader": "0.13.1",
|
||||||
|
"svg-inline-loader": "^0.8.0",
|
||||||
"to-string-loader": "1.1.5",
|
"to-string-loader": "1.1.5",
|
||||||
"tslint": "5.1.0",
|
"tslint": "5.1.0",
|
||||||
"tslint-config-standard": "5.0.2",
|
"tslint-config-standard": "5.0.2",
|
||||||
"tslint-eslint-rules": "4.0.0",
|
"tslint-eslint-rules": "4.0.0",
|
||||||
"typescript": "2.2.2",
|
"typescript": "^2.8.3",
|
||||||
"url-loader": "0.5.7",
|
"url-loader": "0.5.7",
|
||||||
"val-loader": "0.5.0",
|
"val-loader": "0.5.0",
|
||||||
"webpack": "^3.0.0",
|
"webpack": "^4.8.3",
|
||||||
|
"webpack-cli": "^2.1.3",
|
||||||
"yaml-loader": "0.4.0",
|
"yaml-loader": "0.4.0",
|
||||||
"yarn": "^1.3.2"
|
"yarn": "^1.3.2"
|
||||||
},
|
},
|
||||||
@@ -50,6 +53,10 @@
|
|||||||
"appId": "org.terminus",
|
"appId": "org.terminus",
|
||||||
"productName": "Terminus",
|
"productName": "Terminus",
|
||||||
"compression": "normal",
|
"compression": "normal",
|
||||||
|
"files": [
|
||||||
|
"**/*",
|
||||||
|
"dist"
|
||||||
|
],
|
||||||
"extraResources": [
|
"extraResources": [
|
||||||
"builtin-plugins",
|
"builtin-plugins",
|
||||||
"clink"
|
"clink"
|
||||||
@@ -70,7 +77,10 @@
|
|||||||
"identity": null,
|
"identity": null,
|
||||||
"publish": [
|
"publish": [
|
||||||
"github"
|
"github"
|
||||||
]
|
],
|
||||||
|
"extendInfo": {
|
||||||
|
"NSRequiresAquaSystemAppearance": false
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"dmg": {
|
"dmg": {
|
||||||
"artifactName": "terminus-${version}-${os}-${arch}.dmg"
|
"artifactName": "terminus-${version}-${os}-${arch}.dmg"
|
||||||
@@ -91,7 +101,6 @@
|
|||||||
"libappindicator1",
|
"libappindicator1",
|
||||||
"libxtst6",
|
"libxtst6",
|
||||||
"libnss3",
|
"libnss3",
|
||||||
"python-gnomekeyring",
|
|
||||||
"tmux"
|
"tmux"
|
||||||
],
|
],
|
||||||
"artifactName": "terminus-${version}-${os}-${arch}.deb"
|
"artifactName": "terminus-${version}-${os}-${arch}.deb"
|
||||||
@@ -107,8 +116,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.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": "webpack --progress --color --watch",
|
||||||
"start": "cross-env DEV=1 electron --js-flags='--ignition' app --debug",
|
"start": "cross-env DEV=1 electron app --debug",
|
||||||
"prod": "cross-env DEV=1 electron --js-flags='--ignition' 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",
|
||||||
"postinstall": "install-app-deps"
|
"postinstall": "install-app-deps"
|
||||||
},
|
},
|
||||||
|
@@ -5,9 +5,10 @@ const vars = require('./vars')
|
|||||||
builder({
|
builder({
|
||||||
dir: true,
|
dir: true,
|
||||||
linux: ['deb', 'rpm', 'tar.gz'],
|
linux: ['deb', 'rpm', 'tar.gz'],
|
||||||
|
config: {
|
||||||
extraMetadata: {
|
extraMetadata: {
|
||||||
version: vars.version,
|
version: vars.version,
|
||||||
},
|
},
|
||||||
|
},
|
||||||
publish: 'onTag',
|
publish: 'onTag',
|
||||||
draft: false
|
|
||||||
})
|
})
|
||||||
|
@@ -5,9 +5,10 @@ const vars = require('./vars')
|
|||||||
builder({
|
builder({
|
||||||
dir: true,
|
dir: true,
|
||||||
mac: ['dmg'],
|
mac: ['dmg'],
|
||||||
|
config: {
|
||||||
extraMetadata: {
|
extraMetadata: {
|
||||||
version: vars.version,
|
version: vars.version,
|
||||||
},
|
},
|
||||||
|
},
|
||||||
publish: 'onTag',
|
publish: 'onTag',
|
||||||
draft: false
|
|
||||||
})
|
})
|
||||||
|
@@ -3,5 +3,25 @@ const rebuild = require('electron-rebuild').default
|
|||||||
const path = require('path')
|
const path = require('path')
|
||||||
const vars = require('./vars')
|
const vars = require('./vars')
|
||||||
|
|
||||||
rebuild(path.resolve(__dirname, '../terminus-ssh'), vars.electronVersion, process.arch, [], true)
|
lifecycles = []
|
||||||
rebuild(path.resolve(__dirname, '../terminus-terminal'), vars.electronVersion, process.arch, [], true)
|
lifecycles.push(rebuild({
|
||||||
|
buildPath: path.resolve(__dirname, '../app'),
|
||||||
|
electronVersion: vars.electronVersion,
|
||||||
|
force: true,
|
||||||
|
}).lifecycle)
|
||||||
|
lifecycles.push(rebuild({
|
||||||
|
buildPath: path.resolve(__dirname, '../terminus-ssh'),
|
||||||
|
electronVersion: vars.electronVersion,
|
||||||
|
force: true,
|
||||||
|
}).lifecycle)
|
||||||
|
lifecycles.push(rebuild({
|
||||||
|
buildPath: path.resolve(__dirname, '../terminus-terminal'),
|
||||||
|
electronVersion: vars.electronVersion,
|
||||||
|
force: true,
|
||||||
|
}).lifecycle)
|
||||||
|
|
||||||
|
for (let lc of lifecycles) {
|
||||||
|
lc.on('module-found', name => {
|
||||||
|
console.info('Rebuilding', name)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@@ -5,9 +5,10 @@ const vars = require('./vars')
|
|||||||
builder({
|
builder({
|
||||||
dir: true,
|
dir: true,
|
||||||
win: ['squirrel'],
|
win: ['squirrel'],
|
||||||
|
config: {
|
||||||
extraMetadata: {
|
extraMetadata: {
|
||||||
version: vars.version,
|
version: vars.version,
|
||||||
},
|
},
|
||||||
|
},
|
||||||
publish: 'onTag',
|
publish: 'onTag',
|
||||||
draft: false
|
|
||||||
})
|
})
|
||||||
|
@@ -20,3 +20,14 @@ vars.builtinPlugins.forEach(plugin => {
|
|||||||
sh.exec(`${npx} yarn install`)
|
sh.exec(`${npx} yarn install`)
|
||||||
sh.cd('..')
|
sh.cd('..')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (['darwin', 'linux'].includes(process.platform)) {
|
||||||
|
sh.cd('node_modules')
|
||||||
|
for (let x of vars.builtinPlugins) {
|
||||||
|
sh.ln('-fs', '../' + x, x)
|
||||||
|
}
|
||||||
|
for (let x of vars.bundledModules) {
|
||||||
|
sh.ln('-fs', '../app/node_modules/' + x, x)
|
||||||
|
}
|
||||||
|
sh.cd('..')
|
||||||
|
}
|
||||||
|
@@ -16,5 +16,9 @@ exports.builtinPlugins = [
|
|||||||
'terminus-plugin-manager',
|
'terminus-plugin-manager',
|
||||||
'terminus-ssh',
|
'terminus-ssh',
|
||||||
]
|
]
|
||||||
exports.nativeModules = ['node-pty-tmp', 'font-manager', 'xkeychain']
|
exports.bundledModules = [
|
||||||
|
'@angular',
|
||||||
|
'@ng-bootstrap',
|
||||||
|
]
|
||||||
|
exports.nativeModules = ['node-pty-tmp', 'font-manager', 'xkeychain', 'electron-vibrancy']
|
||||||
exports.electronVersion = pkgInfo.devDependencies.electron
|
exports.electronVersion = pkgInfo.devDependencies.electron
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "terminus-community-color-schemes",
|
"name": "terminus-community-color-schemes",
|
||||||
"version": "1.0.0-alpha.36",
|
"version": "1.0.0-alpha.48",
|
||||||
"description": "Community color schemes for Terminus",
|
"description": "Community color schemes for Terminus",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"terminus-builtin-plugin"
|
"terminus-builtin-plugin"
|
||||||
|
36
terminus-community-color-schemes/schemes/Tango
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
! special
|
||||||
|
*.foreground: #babdb6
|
||||||
|
*.background: #000000
|
||||||
|
*.cursorColor: #babdb6
|
||||||
|
|
||||||
|
! black
|
||||||
|
*.color0: #2e3436
|
||||||
|
*.color8: #555753
|
||||||
|
|
||||||
|
! red
|
||||||
|
*.color1: #cc0000
|
||||||
|
*.color9: #ef2929
|
||||||
|
|
||||||
|
! green
|
||||||
|
*.color2: #4e9a06
|
||||||
|
*.color10: #8ae234
|
||||||
|
|
||||||
|
! yellow
|
||||||
|
*.color3: #c4a000
|
||||||
|
*.color11: #fce94f
|
||||||
|
|
||||||
|
! blue
|
||||||
|
*.color4: #3465a4
|
||||||
|
*.color12: #729fcf
|
||||||
|
|
||||||
|
! magenta
|
||||||
|
*.color5: #75507b
|
||||||
|
*.color13: #ad7fa8
|
||||||
|
|
||||||
|
! cyan
|
||||||
|
*.color6: #06989a
|
||||||
|
*.color14: #34e2e2
|
||||||
|
|
||||||
|
! white
|
||||||
|
*.color7: #d3d7cf
|
||||||
|
*.color15: #eeeeec
|
@@ -18,9 +18,10 @@ module.exports = {
|
|||||||
extensions: ['.ts', '.js'],
|
extensions: ['.ts', '.js'],
|
||||||
},
|
},
|
||||||
module: {
|
module: {
|
||||||
loaders: [
|
rules: [
|
||||||
{
|
{
|
||||||
test: /\.ts$/,
|
test: /\.ts$/,
|
||||||
|
use: {
|
||||||
loader: 'awesome-typescript-loader',
|
loader: 'awesome-typescript-loader',
|
||||||
options: {
|
options: {
|
||||||
configFileName: path.resolve(__dirname, 'tsconfig.json'),
|
configFileName: path.resolve(__dirname, 'tsconfig.json'),
|
||||||
@@ -30,8 +31,9 @@ module.exports = {
|
|||||||
"*": [path.resolve(__dirname, '../app/node_modules/*')],
|
"*": [path.resolve(__dirname, '../app/node_modules/*')],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{ test: /[\\\/]schemes[\\\/]/, loader: "raw-loader" },
|
{ test: /[\\\/]schemes[\\\/]/, use: "raw-loader" },
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
externals: [
|
externals: [
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "terminus-core",
|
"name": "terminus-core",
|
||||||
"version": "1.0.0-alpha.36",
|
"version": "1.0.0-alpha.48",
|
||||||
"description": "Terminus core",
|
"description": "Terminus core",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"terminus-builtin-plugin"
|
"terminus-builtin-plugin"
|
||||||
@@ -25,8 +25,8 @@
|
|||||||
"bootstrap": "4.0.0-alpha.6",
|
"bootstrap": "4.0.0-alpha.6",
|
||||||
"core-js": "^2.4.1",
|
"core-js": "^2.4.1",
|
||||||
"electron-updater": "^2.8.9",
|
"electron-updater": "^2.8.9",
|
||||||
"ngx-perfect-scrollbar": "4.0.0",
|
"ng2-dnd": "^5.0.2",
|
||||||
"typescript": "^2.4.1"
|
"ngx-perfect-scrollbar": "^6.0.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@angular/animations": "4.0.1",
|
"@angular/animations": "4.0.1",
|
||||||
|
@@ -1,6 +1,9 @@
|
|||||||
|
import { SafeHtml } from '@angular/platform-browser'
|
||||||
|
|
||||||
export interface IToolbarButton {
|
export interface IToolbarButton {
|
||||||
icon: string
|
icon: SafeHtml
|
||||||
title: string
|
title: string
|
||||||
|
touchBarTitle?: string
|
||||||
weight?: number
|
weight?: number
|
||||||
click: () => void
|
click: () => void
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
title-bar(
|
title-bar(
|
||||||
*ngIf='!hostApp.getWindow().isFullScreen() && config.store.appearance.frame == "full" && config.store.appearance.dock == "off"',
|
*ngIf='!hostApp.isFullScreen && config.store.appearance.frame == "full" && config.store.appearance.dock == "off"',
|
||||||
[class.inset]='hostApp.platform == Platform.macOS'
|
[class.inset]='hostApp.platform == Platform.macOS'
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -7,57 +7,65 @@ title-bar(
|
|||||||
[class.tabs-on-top]='config.store.appearance.tabsLocation == "top"'
|
[class.tabs-on-top]='config.store.appearance.tabsLocation == "top"'
|
||||||
)
|
)
|
||||||
.tab-bar(
|
.tab-bar(
|
||||||
*ngIf='!hostApp.getWindow().isFullScreen()',
|
*ngIf='!hostApp.isFullScreen',
|
||||||
[class.inset]='hostApp.platform == Platform.macOS && config.store.appearance.frame == "thin" && config.store.appearance.tabsLocation == "top"'
|
)
|
||||||
|
.inset.background(*ngIf='hostApp.platform == Platform.macOS && config.store.appearance.frame == "thin" && config.store.appearance.tabsLocation == "top"')
|
||||||
|
.tabs(
|
||||||
|
dnd-sortable-container,
|
||||||
|
[sortableData]='app.tabs',
|
||||||
)
|
)
|
||||||
.tabs
|
|
||||||
tab-header(
|
tab-header(
|
||||||
*ngFor='let tab of app.tabs; let idx = index',
|
*ngFor='let tab of app.tabs; let idx = index',
|
||||||
|
dnd-sortable,
|
||||||
|
[sortableIndex]='idx',
|
||||||
|
(onDragStart)='onTabDragStart()',
|
||||||
|
(onDragEnd)='onTabDragEnd()',
|
||||||
|
|
||||||
[index]='idx',
|
[index]='idx',
|
||||||
[tab]='tab',
|
[tab]='tab',
|
||||||
[active]='tab == app.activeTab',
|
[active]='tab == app.activeTab',
|
||||||
[hasActivity]='tab.hasActivity',
|
[hasActivity]='tab.hasActivity',
|
||||||
[class.drag-region]='hostApp.platform == Platform.macOS',
|
|
||||||
@animateTab,
|
@animateTab,
|
||||||
(click)='app.selectTab(tab)',
|
(click)='app.selectTab(tab)',
|
||||||
|
[class.fully-draggable]='hostApp.platform != Platform.macOS',
|
||||||
|
[class.drag-region]='hostApp.platform == Platform.macOS && !tabsDragging',
|
||||||
)
|
)
|
||||||
|
|
||||||
.btn-group
|
.btn-group.background
|
||||||
button.btn.btn-secondary.btn-tab-bar(
|
button.btn.btn-secondary.btn-tab-bar(
|
||||||
*ngFor='let button of leftToolbarButtons',
|
*ngFor='let button of leftToolbarButtons',
|
||||||
[title]='button.title',
|
[title]='button.title',
|
||||||
(click)='button.click()',
|
(click)='button.click()',
|
||||||
|
[innerHTML]='button.icon',
|
||||||
)
|
)
|
||||||
i.fa([class]='"fa fa-" + button.icon')
|
|
||||||
|
|
||||||
.drag-space([class.persistent]='config.store.appearance.frame == "thin" && hostApp.platform != Platform.macOS')
|
.drag-space.background([class.persistent]='config.store.appearance.frame == "thin" && hostApp.platform != Platform.macOS')
|
||||||
|
|
||||||
.btn-group
|
.btn-group.background
|
||||||
button.btn.btn-secondary.btn-tab-bar(
|
button.btn.btn-secondary.btn-tab-bar(
|
||||||
*ngFor='let button of rightToolbarButtons',
|
*ngFor='let button of rightToolbarButtons',
|
||||||
[title]='button.title',
|
[title]='button.title',
|
||||||
(click)='button.click()',
|
(click)='button.click()',
|
||||||
|
[innerHTML]='button.icon',
|
||||||
)
|
)
|
||||||
i.fa([class]='"fa fa-" + button.icon')
|
|
||||||
button.btn.btn-secondary.btn-tab-bar(
|
button.btn.btn-secondary.btn-tab-bar.btn-update(
|
||||||
*ngIf='appUpdate',
|
*ngIf='appUpdate',
|
||||||
title='Update available',
|
title='Update available',
|
||||||
(click)='updateApp()',
|
(click)='updateApp()',
|
||||||
|
[innerHTML]='updateIcon'
|
||||||
)
|
)
|
||||||
i.fa.fa-arrow-up.text-info
|
|
||||||
span.text-info Update
|
|
||||||
|
|
||||||
window-controls(
|
window-controls.background(
|
||||||
*ngIf='config.store.appearance.frame == "thin" && (hostApp.platform == Platform.Windows || hostApp.platform == Platform.Linux)',
|
*ngIf='config.store.appearance.frame == "thin" && (hostApp.platform == Platform.Windows || hostApp.platform == Platform.Linux)',
|
||||||
)
|
)
|
||||||
|
|
||||||
start-page(*ngIf='ready && app.tabs.length == 0')
|
start-page(*ngIf='ready && app.tabs.length == 0')
|
||||||
|
|
||||||
tab-body(
|
tab-body(
|
||||||
*ngFor='let tab of app.tabs; trackBy: tab?.id',
|
*ngFor='let tab of unsortedTabs',
|
||||||
[active]='tab == app.activeTab',
|
[active]='tab == app.activeTab',
|
||||||
[tab]='tab',
|
[tab]='tab',
|
||||||
[scrollable]='tab.scrollable',
|
|
||||||
)
|
)
|
||||||
|
|
||||||
ng-template(ngbModalContainer)
|
ng-template(ngbModalContainer)
|
||||||
|
@@ -35,6 +35,7 @@ $tab-border-radius: 4px;
|
|||||||
line-height: $tabs-height + 2px;
|
line-height: $tabs-height + 2px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
padding: 0 15px;
|
padding: 0 15px;
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
border-bottom: 2px solid transparent;
|
border-bottom: 2px solid transparent;
|
||||||
@@ -46,7 +47,6 @@ $tab-border-radius: 4px;
|
|||||||
color: #aaa;
|
color: #aaa;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&>.tabs {
|
&>.tabs {
|
||||||
@@ -66,12 +66,13 @@ $tab-border-radius: 4px;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.inset {
|
& > .inset {
|
||||||
padding-left: 85px;
|
width: 85px;
|
||||||
|
flex: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
window-controls {
|
window-controls {
|
||||||
margin-left: 10px;
|
padding-left: 10px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,3 +87,13 @@ hotkey-hint {
|
|||||||
right: 0;
|
right: 0;
|
||||||
max-width: 300px;
|
max-width: 300px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
::ng-deep .btn-tab-bar svg {
|
||||||
|
height: 16px;
|
||||||
|
fill: white;
|
||||||
|
fill-opacity: 0.75;
|
||||||
|
}
|
||||||
|
|
||||||
|
::ng-deep .btn-update svg {
|
||||||
|
fill: cyan;
|
||||||
|
}
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
import { Component, Inject, Input, HostListener } from '@angular/core'
|
import { Component, Inject, Input, HostListener, HostBinding } from '@angular/core'
|
||||||
import { trigger, style, animate, transition, state } from '@angular/animations'
|
import { trigger, style, animate, transition, state } from '@angular/animations'
|
||||||
|
import { DomSanitizer, SafeHtml } from '@angular/platform-browser'
|
||||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
||||||
|
|
||||||
import { ElectronService } from '../services/electron.service'
|
import { ElectronService } from '../services/electron.service'
|
||||||
@@ -11,7 +12,9 @@ import { DockingService } from '../services/docking.service'
|
|||||||
import { TabRecoveryService } from '../services/tabRecovery.service'
|
import { TabRecoveryService } from '../services/tabRecovery.service'
|
||||||
import { ThemesService } from '../services/themes.service'
|
import { ThemesService } from '../services/themes.service'
|
||||||
import { UpdaterService, Update } from '../services/updater.service'
|
import { UpdaterService, Update } from '../services/updater.service'
|
||||||
|
import { TouchbarService } from '../services/touchbar.service'
|
||||||
|
|
||||||
|
import { BaseTabComponent } from './baseTab.component'
|
||||||
import { SafeModeModalComponent } from './safeModeModal.component'
|
import { SafeModeModalComponent } from './safeModeModal.component'
|
||||||
import { AppService, IToolbarButton, ToolbarButtonProvider } from '../api'
|
import { AppService, IToolbarButton, ToolbarButtonProvider } from '../api'
|
||||||
|
|
||||||
@@ -53,6 +56,10 @@ export class AppRootComponent {
|
|||||||
@Input() ready = false
|
@Input() ready = false
|
||||||
@Input() leftToolbarButtons: IToolbarButton[]
|
@Input() leftToolbarButtons: IToolbarButton[]
|
||||||
@Input() rightToolbarButtons: IToolbarButton[]
|
@Input() rightToolbarButtons: IToolbarButton[]
|
||||||
|
@HostBinding('class') hostClass = `platform-${process.platform}`
|
||||||
|
tabsDragging = false
|
||||||
|
unsortedTabs: BaseTabComponent[] = []
|
||||||
|
updateIcon: SafeHtml
|
||||||
private logger: Logger
|
private logger: Logger
|
||||||
private appUpdate: Update
|
private appUpdate: Update
|
||||||
|
|
||||||
@@ -62,12 +69,14 @@ export class AppRootComponent {
|
|||||||
private tabRecovery: TabRecoveryService,
|
private tabRecovery: TabRecoveryService,
|
||||||
private hotkeys: HotkeysService,
|
private hotkeys: HotkeysService,
|
||||||
private updater: UpdaterService,
|
private updater: UpdaterService,
|
||||||
|
private touchbar: TouchbarService,
|
||||||
public hostApp: HostAppService,
|
public hostApp: HostAppService,
|
||||||
public config: ConfigService,
|
public config: ConfigService,
|
||||||
public app: AppService,
|
public app: AppService,
|
||||||
@Inject(ToolbarButtonProvider) private toolbarButtonProviders: ToolbarButtonProvider[],
|
@Inject(ToolbarButtonProvider) private toolbarButtonProviders: ToolbarButtonProvider[],
|
||||||
log: LogService,
|
log: LogService,
|
||||||
ngbModal: NgbModal,
|
ngbModal: NgbModal,
|
||||||
|
domSanitizer: DomSanitizer,
|
||||||
_themes: ThemesService,
|
_themes: ThemesService,
|
||||||
) {
|
) {
|
||||||
this.logger = log.create('main')
|
this.logger = log.create('main')
|
||||||
@@ -76,6 +85,8 @@ export class AppRootComponent {
|
|||||||
this.leftToolbarButtons = this.getToolbarButtons(false)
|
this.leftToolbarButtons = this.getToolbarButtons(false)
|
||||||
this.rightToolbarButtons = this.getToolbarButtons(true)
|
this.rightToolbarButtons = this.getToolbarButtons(true)
|
||||||
|
|
||||||
|
this.updateIcon = domSanitizer.bypassSecurityTrustHtml(require('../icons/gift.svg')),
|
||||||
|
|
||||||
this.hotkeys.matchedHotkey.subscribe((hotkey) => {
|
this.hotkeys.matchedHotkey.subscribe((hotkey) => {
|
||||||
if (hotkey.startsWith('tab-')) {
|
if (hotkey.startsWith('tab-')) {
|
||||||
let index = parseInt(hotkey.split('-')[1])
|
let index = parseInt(hotkey.split('-')[1])
|
||||||
@@ -121,16 +132,30 @@ export class AppRootComponent {
|
|||||||
this.updater.check().then(update => {
|
this.updater.check().then(update => {
|
||||||
this.appUpdate = update
|
this.appUpdate = update
|
||||||
})
|
})
|
||||||
|
|
||||||
|
this.touchbar.update()
|
||||||
|
|
||||||
|
config.changed$.subscribe(() => this.updateVibrancy())
|
||||||
|
this.updateVibrancy()
|
||||||
|
|
||||||
|
this.app.tabOpened$.subscribe(tab => this.unsortedTabs.push(tab))
|
||||||
|
this.app.tabClosed$.subscribe(tab => {
|
||||||
|
this.unsortedTabs = this.unsortedTabs.filter(x => x !== tab)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
onGlobalHotkey () {
|
onGlobalHotkey () {
|
||||||
if (this.electron.app.window.isFocused()) {
|
if (this.electron.app.window.isFocused()) {
|
||||||
// focused
|
// focused
|
||||||
|
this.electron.loseFocus()
|
||||||
|
if (this.hostApp.platform !== Platform.macOS) {
|
||||||
this.electron.app.window.hide()
|
this.electron.app.window.hide()
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if (!this.electron.app.window.isVisible()) {
|
if (!this.electron.app.window.isVisible()) {
|
||||||
// unfocused, invisible
|
// unfocused, invisible
|
||||||
this.electron.app.window.show()
|
this.electron.app.window.show()
|
||||||
|
this.electron.app.window.focus()
|
||||||
} else {
|
} else {
|
||||||
if (this.config.store.appearance.dock === 'off') {
|
if (this.config.store.appearance.dock === 'off') {
|
||||||
// not docked, visible
|
// not docked, visible
|
||||||
@@ -171,6 +196,17 @@ export class AppRootComponent {
|
|||||||
this.electron.shell.openExternal(this.appUpdate.url)
|
this.electron.shell.openExternal(this.appUpdate.url)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onTabDragStart () {
|
||||||
|
this.tabsDragging = true
|
||||||
|
}
|
||||||
|
|
||||||
|
onTabDragEnd () {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.tabsDragging = false
|
||||||
|
this.app.emitTabsChanged()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
private getToolbarButtons (aboveZero: boolean): IToolbarButton[] {
|
private getToolbarButtons (aboveZero: boolean): IToolbarButton[] {
|
||||||
let buttons: IToolbarButton[] = []
|
let buttons: IToolbarButton[] = []
|
||||||
this.config.enabledServices(this.toolbarButtonProviders).forEach(provider => {
|
this.config.enabledServices(this.toolbarButtonProviders).forEach(provider => {
|
||||||
@@ -180,4 +216,9 @@ export class AppRootComponent {
|
|||||||
.filter((button) => (button.weight > 0) === aboveZero)
|
.filter((button) => (button.weight > 0) === aboveZero)
|
||||||
.sort((a: IToolbarButton, b: IToolbarButton) => (a.weight || 0) - (b.weight || 0))
|
.sort((a: IToolbarButton, b: IToolbarButton) => (a.weight || 0) - (b.weight || 0))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private updateVibrancy () {
|
||||||
|
this.hostApp.setVibrancy(this.config.store.appearance.vibrancy)
|
||||||
|
this.hostApp.getWindow().setOpacity(this.config.store.appearance.opacity)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { Subject } from 'rxjs'
|
import { Observable, Subject } from 'rxjs'
|
||||||
import { ViewRef } from '@angular/core'
|
import { ViewRef } from '@angular/core'
|
||||||
|
|
||||||
export abstract class BaseTabComponent {
|
export abstract class BaseTabComponent {
|
||||||
@@ -6,12 +6,16 @@ export abstract class BaseTabComponent {
|
|||||||
id: number
|
id: number
|
||||||
title: string
|
title: string
|
||||||
customTitle: string
|
customTitle: string
|
||||||
scrollable: boolean
|
|
||||||
hasActivity = false
|
hasActivity = false
|
||||||
focused$ = new Subject<void>()
|
|
||||||
blurred$ = new Subject<void>()
|
|
||||||
hasFocus = false
|
hasFocus = false
|
||||||
hostView: ViewRef
|
hostView: ViewRef
|
||||||
|
protected titleChange = new Subject<string>()
|
||||||
|
protected focused = new Subject<void>()
|
||||||
|
protected blurred = new Subject<void>()
|
||||||
|
|
||||||
|
get focused$ (): Observable<void> { return this.focused }
|
||||||
|
get blurred$ (): Observable<void> { return this.blurred }
|
||||||
|
get titleChange$ (): Observable<string> { return this.titleChange }
|
||||||
|
|
||||||
constructor () {
|
constructor () {
|
||||||
this.id = BaseTabComponent.lastTabID++
|
this.id = BaseTabComponent.lastTabID++
|
||||||
@@ -23,6 +27,13 @@ export abstract class BaseTabComponent {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setTitle (title: string) {
|
||||||
|
this.title = title
|
||||||
|
if (!this.customTitle) {
|
||||||
|
this.titleChange.next(title)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
displayActivity (): void {
|
displayActivity (): void {
|
||||||
this.hasActivity = true
|
this.hasActivity = true
|
||||||
}
|
}
|
||||||
@@ -35,8 +46,17 @@ export abstract class BaseTabComponent {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
emitFocused () {
|
||||||
|
this.focused.next()
|
||||||
|
}
|
||||||
|
|
||||||
|
emitBlurred () {
|
||||||
|
this.blurred.next()
|
||||||
|
}
|
||||||
|
|
||||||
destroy (): void {
|
destroy (): void {
|
||||||
this.focused$.complete()
|
this.focused.complete()
|
||||||
this.blurred$.complete()
|
this.blurred.complete()
|
||||||
|
this.titleChange.complete()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
4
terminus-core/src/components/checkbox.component.pug
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
.icon((click)='click()', tabindex='0', [class.active]='model', (keyup.space)='click()')
|
||||||
|
i.fa.fa-square-o.off
|
||||||
|
i.fa.fa-check-square.on
|
||||||
|
.text((click)='click()') {{text}}
|
51
terminus-core/src/components/checkbox.component.scss
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
:host {
|
||||||
|
cursor: pointer;
|
||||||
|
margin: 5px 0;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
background: rgba(255,255,255,.05);
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
background: rgba(255,255,255,.1);
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&[disabled] {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
position: relative;
|
||||||
|
flex: none;
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
|
||||||
|
i {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: -2px;
|
||||||
|
transition: 0.25s opacity;
|
||||||
|
display: block;
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
i.on, &.active i.off {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
i.off, &.active i.on {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.text {
|
||||||
|
flex: auto;
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
|
}
|
45
terminus-core/src/components/checkbox.component.ts
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import { NgZone, Component, Input } from '@angular/core'
|
||||||
|
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'checkbox',
|
||||||
|
template: require('./checkbox.component.pug'),
|
||||||
|
styles: [require('./checkbox.component.scss')],
|
||||||
|
providers: [
|
||||||
|
{ provide: NG_VALUE_ACCESSOR, useExisting: CheckboxComponent, multi: true },
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class CheckboxComponent implements ControlValueAccessor {
|
||||||
|
@Input() model: boolean
|
||||||
|
@Input() disabled: boolean
|
||||||
|
@Input() text: string
|
||||||
|
private changed = new Array<(val: boolean) => void>()
|
||||||
|
|
||||||
|
click () {
|
||||||
|
NgZone.assertInAngularZone()
|
||||||
|
if (this.disabled) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.model = !this.model
|
||||||
|
for (let fx of this.changed) {
|
||||||
|
fx(this.model)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
writeValue (obj: any) {
|
||||||
|
this.model = obj
|
||||||
|
}
|
||||||
|
|
||||||
|
registerOnChange (fn: any): void {
|
||||||
|
this.changed.push(fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
registerOnTouched (fn: any): void {
|
||||||
|
this.changed.push(fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
setDisabledState (isDisabled: boolean) {
|
||||||
|
this.disabled = isDisabled
|
||||||
|
}
|
||||||
|
}
|
@@ -8,7 +8,7 @@ div
|
|||||||
*ngFor='let button of getButtons()',
|
*ngFor='let button of getButtons()',
|
||||||
(click)='button.click()',
|
(click)='button.click()',
|
||||||
)
|
)
|
||||||
i([class]='"fa fa-fw fa-" + button.icon')
|
.d-flex.align-self-center([innerHTML]='button.icon')
|
||||||
span {{button.title}}
|
span {{button.title}}
|
||||||
|
|
||||||
footer
|
footer
|
||||||
|
@@ -27,3 +27,11 @@ footer {
|
|||||||
a, button {
|
a, button {
|
||||||
-webkit-app-region: no-drag;
|
-webkit-app-region: no-drag;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.list-group-item ::ng-deep svg {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
margin-right: 10px;
|
||||||
|
fill: white;
|
||||||
|
fill-opacity: 0.75;
|
||||||
|
}
|
||||||
|
@@ -4,10 +4,6 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
&.scrollable {
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.active {
|
&.active {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
||||||
@@ -15,4 +11,9 @@
|
|||||||
flex: auto;
|
flex: auto;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
> perfect-scrollbar {
|
||||||
|
width: auto;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,29 +1,36 @@
|
|||||||
import { Component, Input, ViewChild, HostBinding, ViewContainerRef } from '@angular/core'
|
import { Component, Input, ViewChild, HostBinding, ViewContainerRef, OnChanges } from '@angular/core'
|
||||||
import { BaseTabComponent } from '../components/baseTab.component'
|
import { BaseTabComponent } from '../components/baseTab.component'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'tab-body',
|
selector: 'tab-body',
|
||||||
template: `
|
template: `
|
||||||
<perfect-scrollbar [config]="{ suppressScrollX: true }" *ngIf="scrollable">
|
<!--perfect-scrollbar [config]="{ suppressScrollX: true }" *ngIf="scrollable">
|
||||||
<ng-template #scrollablePlaceholder></ng-template>
|
<ng-template #scrollablePlaceholder></ng-template>
|
||||||
</perfect-scrollbar>
|
</perfect-scrollbar-->
|
||||||
<template #nonScrollablePlaceholder [ngIf]="!scrollable"></template>
|
<ng-template #placeholder></ng-template>
|
||||||
`,
|
`,
|
||||||
styles: [
|
styles: [
|
||||||
require('./tabBody.component.scss'),
|
require('./tabBody.component.scss'),
|
||||||
require('./tabBody.deep.component.css'),
|
require('./tabBody.deep.component.css'),
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class TabBodyComponent {
|
export class TabBodyComponent implements OnChanges {
|
||||||
@Input() @HostBinding('class.active') active: boolean
|
@Input() @HostBinding('class.active') active: boolean
|
||||||
@Input() tab: BaseTabComponent
|
@Input() tab: BaseTabComponent
|
||||||
@Input() scrollable: boolean
|
@ViewChild('placeholder', {read: ViewContainerRef}) placeholder: ViewContainerRef
|
||||||
@ViewChild('scrollablePlaceholder', {read: ViewContainerRef}) scrollablePlaceholder: ViewContainerRef
|
|
||||||
@ViewChild('nonScrollablePlaceholder', {read: ViewContainerRef}) nonScrollablePlaceholder: ViewContainerRef
|
|
||||||
|
|
||||||
ngAfterViewInit () {
|
ngOnChanges (changes) {
|
||||||
|
if (changes.tab) {
|
||||||
|
if (this.placeholder) {
|
||||||
|
this.placeholder.detach()
|
||||||
|
}
|
||||||
setImmediate(() => {
|
setImmediate(() => {
|
||||||
(this.scrollable ? this.scrollablePlaceholder : this.nonScrollablePlaceholder).insert(this.tab.hostView)
|
this.placeholder.insert(this.tab.hostView)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ngOnDestroy () {
|
||||||
|
this.placeholder.detach()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -1,3 +1,3 @@
|
|||||||
.index {{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)') ×
|
||||||
|
@@ -14,11 +14,11 @@ $tabs-height: 36px;
|
|||||||
|
|
||||||
transition: 0.125s ease-out all;
|
transition: 0.125s ease-out all;
|
||||||
|
|
||||||
border-top: 1px solid transparent;
|
|
||||||
|
|
||||||
.index {
|
.index {
|
||||||
flex: none;
|
flex: none;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
-webkit-app-region: no-drag;
|
||||||
|
cursor: grab;
|
||||||
|
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
width: 20px;
|
width: 20px;
|
||||||
@@ -69,4 +69,8 @@ $tabs-height: 36px;
|
|||||||
&.drag-region {
|
&.drag-region {
|
||||||
-webkit-app-region: drag;
|
-webkit-app-region: drag;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.fully-draggable {
|
||||||
|
cursor: grab;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,9 +1,11 @@
|
|||||||
import { Component, Input, HostBinding, HostListener, NgZone } from '@angular/core'
|
import { Component, Input, HostBinding, HostListener, NgZone, ViewChild, ElementRef } from '@angular/core'
|
||||||
|
import { SortableComponent } from 'ng2-dnd'
|
||||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
||||||
import { BaseTabComponent } from './baseTab.component'
|
import { BaseTabComponent } from './baseTab.component'
|
||||||
import { RenameTabModalComponent } from './renameTabModal.component'
|
import { RenameTabModalComponent } from './renameTabModal.component'
|
||||||
import { ElectronService } from '../services/electron.service'
|
import { ElectronService } from '../services/electron.service'
|
||||||
import { AppService } from '../services/app.service'
|
import { AppService } from '../services/app.service'
|
||||||
|
import { HostAppService, Platform } from '../services/hostApp.service'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'tab-header',
|
selector: 'tab-header',
|
||||||
@@ -15,13 +17,16 @@ 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
|
||||||
|
@ViewChild('handle') handle: ElementRef
|
||||||
private contextMenu: any
|
private contextMenu: any
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
zone: NgZone,
|
zone: NgZone,
|
||||||
electron: ElectronService,
|
electron: ElectronService,
|
||||||
public app: AppService,
|
public app: AppService,
|
||||||
|
private hostApp: HostAppService,
|
||||||
private ngbModal: NgbModal,
|
private ngbModal: NgbModal,
|
||||||
|
private parentDraggable: SortableComponent,
|
||||||
) {
|
) {
|
||||||
this.contextMenu = electron.remote.Menu.buildFromTemplate([
|
this.contextMenu = electron.remote.Menu.buildFromTemplate([
|
||||||
{
|
{
|
||||||
@@ -65,10 +70,17 @@ export class TabHeaderComponent {
|
|||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ngOnInit () {
|
||||||
|
if (this.hostApp.platform !== Platform.macOS) {
|
||||||
|
this.parentDraggable.setDragHandle(this.handle.nativeElement)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@HostListener('dblclick') onDoubleClick (): void {
|
@HostListener('dblclick') onDoubleClick (): void {
|
||||||
let modal = this.ngbModal.open(RenameTabModalComponent)
|
let modal = this.ngbModal.open(RenameTabModalComponent)
|
||||||
modal.componentInstance.value = this.tab.customTitle || this.tab.title
|
modal.componentInstance.value = this.tab.customTitle || this.tab.title
|
||||||
modal.result.then(result => {
|
modal.result.then(result => {
|
||||||
|
this.tab.setTitle(result)
|
||||||
this.tab.customTitle = result
|
this.tab.customTitle = result
|
||||||
}).catch(() => null)
|
}).catch(() => null)
|
||||||
}
|
}
|
||||||
|
@@ -7,3 +7,5 @@ appearance:
|
|||||||
theme: Standard
|
theme: Standard
|
||||||
frame: thin
|
frame: thin
|
||||||
css: '/* * { color: blue !important; } */'
|
css: '/* * { color: blue !important; } */'
|
||||||
|
opacity: 1.0
|
||||||
|
vibrancy: false
|
||||||
|
1
terminus-core/src/icons/gift.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M32 448c0 17.7 14.3 32 32 32h160V320H32v128zm448-288h-42.1c6.2-12.1 10.1-25.5 10.1-40 0-48.5-39.5-88-88-88-41.6 0-68.5 21.3-103 68.3-34.5-47-61.4-68.3-103-68.3-48.5 0-88 39.5-88 88 0 14.5 3.8 27.9 10.1 40H32c-17.7 0-32 14.3-32 32v80c0 8.8 7.2 16 16 16h480c8.8 0 16-7.2 16-16v-80c0-17.7-14.3-32-32-32zm-326.1 0c-22.1 0-40-17.9-40-40s17.9-40 40-40c19.9 0 34.6 3.3 86.1 80h-86.1zm206.1 0h-86.1c51.4-76.5 65.7-80 86.1-80 22.1 0 40 17.9 40 40s-17.9 40-40 40zm-72 320h160c17.7 0 32-14.3 32-32V320H288v160z"/></svg>
|
After Width: | Height: | Size: 579 B |
@@ -3,7 +3,8 @@ import { BrowserModule } from '@angular/platform-browser'
|
|||||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations'
|
import { BrowserAnimationsModule } from '@angular/platform-browser/animations'
|
||||||
import { FormsModule } from '@angular/forms'
|
import { FormsModule } from '@angular/forms'
|
||||||
import { NgbModule } from '@ng-bootstrap/ng-bootstrap'
|
import { NgbModule } from '@ng-bootstrap/ng-bootstrap'
|
||||||
import { PerfectScrollbarModule } from 'ngx-perfect-scrollbar'
|
import { PerfectScrollbarModule, PERFECT_SCROLLBAR_CONFIG } from 'ngx-perfect-scrollbar'
|
||||||
|
import { DndModule } from 'ng2-dnd'
|
||||||
|
|
||||||
import { AppService } from './services/app.service'
|
import { AppService } from './services/app.service'
|
||||||
import { ConfigService } from './services/config.service'
|
import { ConfigService } from './services/config.service'
|
||||||
@@ -14,9 +15,11 @@ 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'
|
||||||
import { ThemesService } from './services/themes.service'
|
import { ThemesService } from './services/themes.service'
|
||||||
|
import { TouchbarService } from './services/touchbar.service'
|
||||||
import { UpdaterService } from './services/updater.service'
|
import { UpdaterService } from './services/updater.service'
|
||||||
|
|
||||||
import { AppRootComponent } from './components/appRoot.component'
|
import { AppRootComponent } from './components/appRoot.component'
|
||||||
|
import { CheckboxComponent } from './components/checkbox.component'
|
||||||
import { TabBodyComponent } from './components/tabBody.component'
|
import { TabBodyComponent } from './components/tabBody.component'
|
||||||
import { SafeModeModalComponent } from './components/safeModeModal.component'
|
import { SafeModeModalComponent } from './components/safeModeModal.component'
|
||||||
import { StartPageComponent } from './components/startPage.component'
|
import { StartPageComponent } from './components/startPage.component'
|
||||||
@@ -32,7 +35,8 @@ import { Theme } from './api/theme'
|
|||||||
import { StandardTheme, StandardCompactTheme } from './theme'
|
import { StandardTheme, StandardCompactTheme } from './theme'
|
||||||
import { CoreConfigProvider } from './config'
|
import { CoreConfigProvider } from './config'
|
||||||
|
|
||||||
import 'perfect-scrollbar/dist/css/perfect-scrollbar.css'
|
import 'perfect-scrollbar/css/perfect-scrollbar.css'
|
||||||
|
import 'ng2-dnd/bundles/style.css'
|
||||||
|
|
||||||
const PROVIDERS = [
|
const PROVIDERS = [
|
||||||
AppService,
|
AppService,
|
||||||
@@ -44,11 +48,13 @@ const PROVIDERS = [
|
|||||||
LogService,
|
LogService,
|
||||||
TabRecoveryService,
|
TabRecoveryService,
|
||||||
ThemesService,
|
ThemesService,
|
||||||
|
TouchbarService,
|
||||||
UpdaterService,
|
UpdaterService,
|
||||||
{ provide: HotkeyProvider, useClass: AppHotkeyProvider, multi: true },
|
{ provide: HotkeyProvider, useClass: AppHotkeyProvider, multi: true },
|
||||||
{ provide: Theme, useClass: StandardTheme, multi: true },
|
{ provide: Theme, useClass: StandardTheme, multi: true },
|
||||||
{ provide: Theme, useClass: StandardCompactTheme, multi: true },
|
{ provide: Theme, useClass: StandardCompactTheme, multi: true },
|
||||||
{ provide: ConfigProvider, useClass: CoreConfigProvider, multi: true },
|
{ provide: ConfigProvider, useClass: CoreConfigProvider, multi: true },
|
||||||
|
{ provide: PERFECT_SCROLLBAR_CONFIG, useValue: { suppressScrollX: true }}
|
||||||
]
|
]
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
@@ -57,12 +63,12 @@ const PROVIDERS = [
|
|||||||
BrowserAnimationsModule,
|
BrowserAnimationsModule,
|
||||||
FormsModule,
|
FormsModule,
|
||||||
NgbModule.forRoot(),
|
NgbModule.forRoot(),
|
||||||
PerfectScrollbarModule.forRoot({
|
PerfectScrollbarModule,
|
||||||
suppressScrollX: true,
|
DndModule.forRoot(),
|
||||||
}),
|
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
AppRootComponent,
|
AppRootComponent,
|
||||||
|
CheckboxComponent,
|
||||||
StartPageComponent,
|
StartPageComponent,
|
||||||
TabBodyComponent,
|
TabBodyComponent,
|
||||||
TabHeaderComponent,
|
TabHeaderComponent,
|
||||||
@@ -74,6 +80,9 @@ const PROVIDERS = [
|
|||||||
entryComponents: [
|
entryComponents: [
|
||||||
RenameTabModalComponent,
|
RenameTabModalComponent,
|
||||||
SafeModeModalComponent,
|
SafeModeModalComponent,
|
||||||
|
],
|
||||||
|
exports: [
|
||||||
|
CheckboxComponent
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export default class AppModule {
|
export default class AppModule {
|
||||||
@@ -85,5 +94,11 @@ export default class AppModule {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PerfectScrollbar fix
|
||||||
|
import { fromEvent } from 'rxjs/internal/observable/fromEvent'
|
||||||
|
import { merge } from 'rxjs/internal/observable/merge'
|
||||||
|
require('rxjs').fromEvent = fromEvent
|
||||||
|
require('rxjs').merge = merge
|
||||||
|
|
||||||
export { AppRootComponent as bootstrap }
|
export { AppRootComponent as bootstrap }
|
||||||
export * from './api'
|
export * from './api'
|
||||||
|
@@ -1,9 +1,9 @@
|
|||||||
import { Subject, AsyncSubject } from 'rxjs'
|
import { Observable, Subject, AsyncSubject } from 'rxjs'
|
||||||
import { Injectable, ComponentFactoryResolver, Injector, Optional } from '@angular/core'
|
import { Injectable, ComponentFactoryResolver, Injector, Optional } from '@angular/core'
|
||||||
import { DefaultTabProvider } from '../api/defaultTabProvider'
|
import { DefaultTabProvider } from '../api/defaultTabProvider'
|
||||||
import { BaseTabComponent } from '../components/baseTab.component'
|
import { BaseTabComponent } from '../components/baseTab.component'
|
||||||
import { Logger, LogService } from '../services/log.service'
|
import { Logger, LogService } from './log.service'
|
||||||
import { ConfigService } from '../services/config.service'
|
import { ConfigService } from './config.service'
|
||||||
|
|
||||||
export declare type TabComponentType = new (...args: any[]) => BaseTabComponent
|
export declare type TabComponentType = new (...args: any[]) => BaseTabComponent
|
||||||
|
|
||||||
@@ -13,8 +13,18 @@ export class AppService {
|
|||||||
activeTab: BaseTabComponent
|
activeTab: BaseTabComponent
|
||||||
lastTabIndex = 0
|
lastTabIndex = 0
|
||||||
logger: Logger
|
logger: Logger
|
||||||
tabsChanged$ = new Subject<void>()
|
|
||||||
ready$ = new AsyncSubject<void>()
|
private activeTabChange = new Subject<BaseTabComponent>()
|
||||||
|
private tabsChanged = new Subject<void>()
|
||||||
|
private tabOpened = new Subject<BaseTabComponent>()
|
||||||
|
private tabClosed = new Subject<BaseTabComponent>()
|
||||||
|
private ready = new AsyncSubject<void>()
|
||||||
|
|
||||||
|
get activeTabChange$ (): Observable<BaseTabComponent> { return this.activeTabChange }
|
||||||
|
get tabOpened$ (): Observable<BaseTabComponent> { return this.tabOpened }
|
||||||
|
get tabsChanged$ (): Observable<void> { return this.tabsChanged }
|
||||||
|
get tabClosed$ (): Observable<BaseTabComponent> { return this.tabClosed }
|
||||||
|
get ready$ (): Observable<void> { return this.ready }
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
private componentFactoryResolver: ComponentFactoryResolver,
|
private componentFactoryResolver: ComponentFactoryResolver,
|
||||||
@@ -34,7 +44,8 @@ export class AppService {
|
|||||||
|
|
||||||
this.tabs.push(componentRef.instance)
|
this.tabs.push(componentRef.instance)
|
||||||
this.selectTab(componentRef.instance)
|
this.selectTab(componentRef.instance)
|
||||||
this.tabsChanged$.next()
|
this.tabsChanged.next()
|
||||||
|
this.tabOpened.next(componentRef.instance)
|
||||||
|
|
||||||
return componentRef.instance
|
return componentRef.instance
|
||||||
}
|
}
|
||||||
@@ -56,11 +67,12 @@ export class AppService {
|
|||||||
}
|
}
|
||||||
if (this.activeTab) {
|
if (this.activeTab) {
|
||||||
this.activeTab.hasActivity = false
|
this.activeTab.hasActivity = false
|
||||||
this.activeTab.blurred$.next()
|
this.activeTab.emitBlurred()
|
||||||
}
|
}
|
||||||
this.activeTab = tab
|
this.activeTab = tab
|
||||||
|
this.activeTabChange.next(tab)
|
||||||
if (this.activeTab) {
|
if (this.activeTab) {
|
||||||
this.activeTab.focused$.next()
|
this.activeTab.emitFocused()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,6 +105,10 @@ export class AppService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
emitTabsChanged () {
|
||||||
|
this.tabsChanged.next()
|
||||||
|
}
|
||||||
|
|
||||||
async closeTab (tab: BaseTabComponent, checkCanClose?: boolean): Promise<void> {
|
async closeTab (tab: BaseTabComponent, checkCanClose?: boolean): Promise<void> {
|
||||||
if (!this.tabs.includes(tab)) {
|
if (!this.tabs.includes(tab)) {
|
||||||
return
|
return
|
||||||
@@ -100,17 +116,18 @@ export class AppService {
|
|||||||
if (checkCanClose && !await tab.canClose()) {
|
if (checkCanClose && !await tab.canClose()) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
let newIndex = Math.max(0, this.tabs.indexOf(tab) - 1)
|
||||||
this.tabs = this.tabs.filter((x) => x !== tab)
|
this.tabs = this.tabs.filter((x) => x !== tab)
|
||||||
tab.destroy()
|
tab.destroy()
|
||||||
let newIndex = Math.max(0, this.tabs.indexOf(tab) - 1)
|
|
||||||
if (tab === this.activeTab) {
|
if (tab === this.activeTab) {
|
||||||
this.selectTab(this.tabs[newIndex])
|
this.selectTab(this.tabs[newIndex])
|
||||||
}
|
}
|
||||||
this.tabsChanged$.next()
|
this.tabsChanged.next()
|
||||||
|
this.tabClosed.next(tab)
|
||||||
}
|
}
|
||||||
|
|
||||||
emitReady () {
|
emitReady () {
|
||||||
this.ready$.next(null)
|
this.ready.next(null)
|
||||||
this.ready$.complete()
|
this.ready.complete()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { Subject } from 'rxjs'
|
import { Observable, Subject } from 'rxjs'
|
||||||
import * as yaml from 'js-yaml'
|
import * as yaml from 'js-yaml'
|
||||||
import * as path from 'path'
|
import * as path from 'path'
|
||||||
import * as fs from 'fs'
|
import * as fs from 'fs'
|
||||||
@@ -6,7 +6,6 @@ import { Injectable, Inject } from '@angular/core'
|
|||||||
import { ConfigProvider } from '../api/configProvider'
|
import { ConfigProvider } from '../api/configProvider'
|
||||||
import { ElectronService } from './electron.service'
|
import { ElectronService } from './electron.service'
|
||||||
import { HostAppService } from './hostApp.service'
|
import { HostAppService } from './hostApp.service'
|
||||||
import * as Reflect from 'core-js/es7/reflect'
|
|
||||||
|
|
||||||
const configMerge = (a, b) => require('deepmerge')(a, b, { arrayMerge: (_d, s) => s })
|
const configMerge = (a, b) => require('deepmerge')(a, b, { arrayMerge: (_d, s) => s })
|
||||||
|
|
||||||
@@ -53,13 +52,15 @@ export class ConfigProxy {
|
|||||||
@Injectable()
|
@Injectable()
|
||||||
export class ConfigService {
|
export class ConfigService {
|
||||||
store: any
|
store: any
|
||||||
changed$ = new Subject<void>()
|
|
||||||
restartRequested: boolean
|
restartRequested: boolean
|
||||||
|
private changed = new Subject<void>()
|
||||||
private _store: any
|
private _store: any
|
||||||
private path: string
|
private path: string
|
||||||
private defaults: any
|
private defaults: any
|
||||||
private servicesCache: { [id: string]: Function[] } = null
|
private servicesCache: { [id: string]: Function[] } = null
|
||||||
|
|
||||||
|
get changed$ (): Observable<void> { return this.changed }
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
electron: ElectronService,
|
electron: ElectronService,
|
||||||
hostApp: HostAppService,
|
hostApp: HostAppService,
|
||||||
@@ -94,7 +95,7 @@ export class ConfigService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
emitChange (): void {
|
emitChange (): void {
|
||||||
this.changed$.next()
|
this.changed.next()
|
||||||
}
|
}
|
||||||
|
|
||||||
requestRestart (): void {
|
requestRestart (): void {
|
||||||
@@ -104,12 +105,11 @@ export class ConfigService {
|
|||||||
enabledServices<T> (services: T[]): T[] {
|
enabledServices<T> (services: T[]): T[] {
|
||||||
if (!this.servicesCache) {
|
if (!this.servicesCache) {
|
||||||
this.servicesCache = {}
|
this.servicesCache = {}
|
||||||
let ngModule = Reflect.getMetadata('annotations', window['rootModule'])[0]
|
let ngModule = window['rootModule'].ngInjectorDef
|
||||||
for (let imp of ngModule.imports) {
|
for (let imp of ngModule.imports) {
|
||||||
let module = imp['module'] || imp
|
let module = (imp['ngModule'] || imp)
|
||||||
let annotations = Reflect.getMetadata('annotations', module)
|
if (module.ngInjectorDef && module.ngInjectorDef.providers) {
|
||||||
if (annotations) {
|
this.servicesCache[module['pluginName']] = module.ngInjectorDef.providers.map(provider => {
|
||||||
this.servicesCache[module['pluginName']] = annotations[0].providers.map(provider => {
|
|
||||||
return provider['useClass'] || provider
|
return provider['useClass'] || provider
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@@ -29,18 +29,19 @@ export class DockingService {
|
|||||||
let dockSide = this.config.store.appearance.dock
|
let dockSide = this.config.store.appearance.dock
|
||||||
let newBounds: Bounds = { x: 0, y: 0, width: 0, height: 0 }
|
let newBounds: Bounds = { x: 0, y: 0, width: 0, height: 0 }
|
||||||
let fill = this.config.store.appearance.dockFill
|
let fill = this.config.store.appearance.dockFill
|
||||||
|
let [minWidth, minHeight] = this.hostApp.getWindow().getMinimumSize()
|
||||||
|
|
||||||
if (dockSide === 'off') {
|
if (dockSide === 'off') {
|
||||||
this.hostApp.setAlwaysOnTop(false)
|
this.hostApp.setAlwaysOnTop(false)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (dockSide === 'left' || dockSide === 'right') {
|
if (dockSide === 'left' || dockSide === 'right') {
|
||||||
newBounds.width = Math.round(fill * display.bounds.width)
|
newBounds.width = Math.max(minWidth, Math.round(fill * display.bounds.width))
|
||||||
newBounds.height = display.bounds.height
|
newBounds.height = display.bounds.height
|
||||||
}
|
}
|
||||||
if (dockSide === 'top' || dockSide === 'bottom') {
|
if (dockSide === 'top' || dockSide === 'bottom') {
|
||||||
newBounds.width = display.bounds.width
|
newBounds.width = display.bounds.width
|
||||||
newBounds.height = Math.round(fill * display.bounds.height)
|
newBounds.height = Math.max(minHeight, Math.round(fill * display.bounds.height))
|
||||||
}
|
}
|
||||||
if (dockSide === 'right') {
|
if (dockSide === 'right') {
|
||||||
newBounds.x = display.bounds.x + display.bounds.width - newBounds.width
|
newBounds.x = display.bounds.x + display.bounds.width - newBounds.width
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
import { Injectable } from '@angular/core'
|
import { Injectable } from '@angular/core'
|
||||||
|
import { TouchBar } from 'electron'
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ElectronService {
|
export class ElectronService {
|
||||||
@@ -10,6 +11,7 @@ export class ElectronService {
|
|||||||
globalShortcut: any
|
globalShortcut: any
|
||||||
screen: any
|
screen: any
|
||||||
remote: any
|
remote: any
|
||||||
|
TouchBar: typeof TouchBar
|
||||||
private electron: any
|
private electron: any
|
||||||
|
|
||||||
constructor () {
|
constructor () {
|
||||||
@@ -22,6 +24,7 @@ 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.TouchBar = this.remote.TouchBar
|
||||||
}
|
}
|
||||||
|
|
||||||
remoteRequire (name: string): any {
|
remoteRequire (name: string): any {
|
||||||
@@ -29,6 +32,16 @@ export class ElectronService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
remoteRequirePluginModule (plugin: string, module: string, globals: any): any {
|
remoteRequirePluginModule (plugin: string, module: string, globals: any): any {
|
||||||
return this.remoteRequire(globals.require.resolve(`${plugin}/node_modules/${module}`))
|
return this.remoteRequire(this.remoteResolvePluginModule(plugin, module, globals))
|
||||||
|
}
|
||||||
|
|
||||||
|
remoteResolvePluginModule (plugin: string, module: string, globals: any): any {
|
||||||
|
return globals.require.resolve(`${plugin}/node_modules/${module}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
loseFocus () {
|
||||||
|
if (process.platform === 'darwin') {
|
||||||
|
this.remote.Menu.sendActionToFirstResponder('hide:')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { 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 '../services/electron.service'
|
||||||
import { Logger, LogService } from '../services/log.service'
|
import { Logger, LogService } from '../services/log.service'
|
||||||
@@ -14,17 +14,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
|
||||||
preferencesMenu$ = new Subject<void>()
|
|
||||||
ready = new EventEmitter<any>()
|
ready = new EventEmitter<any>()
|
||||||
shown = new EventEmitter<any>()
|
shown = new EventEmitter<any>()
|
||||||
secondInstance$ = new Subject<{ argv: string[], cwd: string }>()
|
isFullScreen = false
|
||||||
|
private preferencesMenu = new Subject<void>()
|
||||||
|
private secondInstance = new Subject<SecondInstanceArgs>()
|
||||||
private logger: Logger
|
private logger: Logger
|
||||||
|
|
||||||
|
get preferencesMenu$ (): Observable<void> { return this.preferencesMenu }
|
||||||
|
get secondInstance$ (): Observable<SecondInstanceArgs> { return this.secondInstance }
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
private zone: NgZone,
|
private zone: NgZone,
|
||||||
private electron: ElectronService,
|
private electron: ElectronService,
|
||||||
@@ -38,18 +46,26 @@ export class HostAppService {
|
|||||||
linux: Platform.Linux
|
linux: Platform.Linux
|
||||||
}[this.nodePlatform]
|
}[this.nodePlatform]
|
||||||
|
|
||||||
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)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
electron.ipcRenderer.on('host:window-enter-full-screen', () => this.zone.run(() => {
|
||||||
|
this.isFullScreen = true
|
||||||
|
}))
|
||||||
|
|
||||||
|
electron.ipcRenderer.on('host:window-leave-full-screen', () => this.zone.run(() => {
|
||||||
|
this.isFullScreen = false
|
||||||
|
}))
|
||||||
|
|
||||||
electron.ipcRenderer.on('host:window-shown', () => {
|
electron.ipcRenderer.on('host:window-shown', () => {
|
||||||
this.zone.run(() => this.shown.emit())
|
this.zone.run(() => this.shown.emit())
|
||||||
})
|
})
|
||||||
|
|
||||||
electron.ipcRenderer.on('host:second-instance', ($event, argv: string[], cwd: string) => {
|
electron.ipcRenderer.on('host:second-instance', ($event, argv: string[], cwd: string) => {
|
||||||
this.zone.run(() => this.secondInstance$.next({ argv, cwd }))
|
this.zone.run(() => this.secondInstance.next({ argv, cwd }))
|
||||||
})
|
})
|
||||||
|
|
||||||
this.ready.subscribe(() => {
|
this.ready.subscribe(() => {
|
||||||
@@ -86,10 +102,6 @@ export class HostAppService {
|
|||||||
this.electron.ipcRenderer.send('window-focus')
|
this.electron.ipcRenderer.send('window-focus')
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleWindow () {
|
|
||||||
this.electron.ipcRenderer.send('window-toggle-focus')
|
|
||||||
}
|
|
||||||
|
|
||||||
minimize () {
|
minimize () {
|
||||||
this.electron.ipcRenderer.send('window-minimize')
|
this.electron.ipcRenderer.send('window-minimize')
|
||||||
}
|
}
|
||||||
@@ -114,6 +126,16 @@ export class HostAppService {
|
|||||||
this.electron.ipcRenderer.send('window-set-always-on-top', flag)
|
this.electron.ipcRenderer.send('window-set-always-on-top', flag)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setVibrancy (enable: boolean) {
|
||||||
|
document.body.classList.toggle('vibrant', enable)
|
||||||
|
if (this.platform === Platform.macOS) {
|
||||||
|
this.getWindow().setVibrancy(enable ? 'dark' : null)
|
||||||
|
}
|
||||||
|
if (this.platform === Platform.Windows) {
|
||||||
|
this.electron.ipcRenderer.send('window-set-vibrancy', enable)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
quit () {
|
quit () {
|
||||||
this.logger.info('Quitting')
|
this.logger.info('Quitting')
|
||||||
this.electron.app.quit()
|
this.electron.app.quit()
|
||||||
|
75
terminus-core/src/services/touchbar.service.ts
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
import { Injectable, Inject, NgZone } from '@angular/core'
|
||||||
|
import { TouchBarSegmentedControl, SegmentedControlSegment } from 'electron'
|
||||||
|
import { Subscription } from 'rxjs'
|
||||||
|
import { AppService } from './app.service'
|
||||||
|
import { ConfigService } from './config.service'
|
||||||
|
import { ElectronService } from './electron.service'
|
||||||
|
import { BaseTabComponent } from '../components/baseTab.component'
|
||||||
|
import { IToolbarButton, ToolbarButtonProvider } from '../api'
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class TouchbarService {
|
||||||
|
private titleSubscriptions = new Map<BaseTabComponent, Subscription>()
|
||||||
|
private tabsSegmentedControl: TouchBarSegmentedControl
|
||||||
|
private tabSegments: SegmentedControlSegment[] = []
|
||||||
|
|
||||||
|
constructor (
|
||||||
|
private app: AppService,
|
||||||
|
@Inject(ToolbarButtonProvider) private toolbarButtonProviders: ToolbarButtonProvider[],
|
||||||
|
private config: ConfigService,
|
||||||
|
private electron: ElectronService,
|
||||||
|
private zone: NgZone,
|
||||||
|
) {
|
||||||
|
app.tabsChanged$.subscribe(() => this.update())
|
||||||
|
app.activeTabChange$.subscribe(() => this.update())
|
||||||
|
app.tabOpened$.subscribe(tab => {
|
||||||
|
let sub = tab.titleChange$.subscribe(title => {
|
||||||
|
this.tabSegments[app.tabs.indexOf(tab)].label = this.shortenTitle(title)
|
||||||
|
this.tabsSegmentedControl.segments = this.tabSegments
|
||||||
|
})
|
||||||
|
this.titleSubscriptions.set(tab, sub)
|
||||||
|
})
|
||||||
|
app.tabClosed$.subscribe(tab => {
|
||||||
|
this.titleSubscriptions.get(tab).unsubscribe()
|
||||||
|
this.titleSubscriptions.delete(tab)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
update () {
|
||||||
|
let buttons: IToolbarButton[] = []
|
||||||
|
this.config.enabledServices(this.toolbarButtonProviders).forEach(provider => {
|
||||||
|
buttons = buttons.concat(provider.provide())
|
||||||
|
})
|
||||||
|
buttons.sort((a, b) => (a.weight || 0) - (b.weight || 0))
|
||||||
|
this.tabSegments = this.app.tabs.map(tab => ({
|
||||||
|
label: this.shortenTitle(tab.title),
|
||||||
|
}))
|
||||||
|
this.tabsSegmentedControl = new this.electron.TouchBar.TouchBarSegmentedControl({
|
||||||
|
segments: this.tabSegments,
|
||||||
|
selectedIndex: this.app.tabs.indexOf(this.app.activeTab),
|
||||||
|
change: (selectedIndex) => this.zone.run(() => {
|
||||||
|
this.app.selectTab(this.app.tabs[selectedIndex])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
let touchBar = new this.electron.TouchBar({
|
||||||
|
items: [
|
||||||
|
this.tabsSegmentedControl,
|
||||||
|
new this.electron.TouchBar.TouchBarSpacer({size: 'flexible'}),
|
||||||
|
new this.electron.TouchBar.TouchBarSpacer({size: 'small'}),
|
||||||
|
...buttons.map(button => new this.electron.TouchBar.TouchBarButton({
|
||||||
|
label: this.shortenTitle(button.touchBarTitle || button.title),
|
||||||
|
// backgroundColor: '#0022cc',
|
||||||
|
click: () => this.zone.run(() => button.click()),
|
||||||
|
}))
|
||||||
|
]
|
||||||
|
})
|
||||||
|
this.electron.app.window.setTouchBar(touchBar)
|
||||||
|
}
|
||||||
|
|
||||||
|
private shortenTitle (title: string): string {
|
||||||
|
if (title.length > 15) {
|
||||||
|
title = title.substring(0, 15) + '...'
|
||||||
|
}
|
||||||
|
return title
|
||||||
|
}
|
||||||
|
}
|
@@ -15,9 +15,10 @@ $pink: #ff5b77 !default;
|
|||||||
$purple: #613d7c !default;
|
$purple: #613d7c !default;
|
||||||
|
|
||||||
|
|
||||||
$body-bg: #1D272D;
|
$content-bg: rgba(39, 49, 60, 0.65); //#1D272D;
|
||||||
$body-bg2: #131d27;
|
$content-bg-solid: #1D272D;
|
||||||
$body-bg3: #20333e;
|
$body-bg: #131d27;
|
||||||
|
$body-bg2: #20333e;
|
||||||
|
|
||||||
$body-color: #aaa;
|
$body-color: #aaa;
|
||||||
$font-family-sans-serif: "Source Sans Pro";
|
$font-family-sans-serif: "Source Sans Pro";
|
||||||
@@ -31,10 +32,10 @@ $btn-secondary-border: #444;
|
|||||||
//$btn-warning-bg: rgba($orange, .5);
|
//$btn-warning-bg: rgba($orange, .5);
|
||||||
|
|
||||||
|
|
||||||
$nav-tabs-border-color: $body-bg2;
|
$nav-tabs-border-color: $body-bg;
|
||||||
$nav-tabs-border-width: 1px;
|
$nav-tabs-border-width: 1px;
|
||||||
$nav-tabs-border-radius: 0;
|
$nav-tabs-border-radius: 0;
|
||||||
$nav-tabs-link-hover-border-color: $body-bg2;
|
$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%);
|
||||||
@@ -47,24 +48,25 @@ $input-color-placeholder: #333;
|
|||||||
$input-border-color: #344;
|
$input-border-color: #344;
|
||||||
//$input-box-shadow: inset 0 1px 1px rgba($black,.075);
|
//$input-box-shadow: inset 0 1px 1px rgba($black,.075);
|
||||||
$input-border-radius: 0;
|
$input-border-radius: 0;
|
||||||
|
$custom-select-border-radius: 0;
|
||||||
$input-bg-focus: $input-bg;
|
$input-bg-focus: $input-bg;
|
||||||
//$input-border-focus: lighten($brand-primary, 25%);
|
//$input-border-focus: lighten($brand-primary, 25%);
|
||||||
//$input-box-shadow-focus: $input-box-shadow, rgba($input-border-focus, .6);
|
//$input-box-shadow-focus: $input-box-shadow, rgba($input-border-focus, .6);
|
||||||
$input-color-focus: $input-color;
|
$input-color-focus: $input-color;
|
||||||
$input-group-addon-bg: $body-bg2;
|
$input-group-addon-bg: $body-bg;
|
||||||
$input-group-addon-border-color: $input-border-color;
|
$input-group-addon-border-color: $input-border-color;
|
||||||
|
|
||||||
$modal-content-bg: $body-bg;
|
$modal-content-bg: $content-bg-solid;
|
||||||
$modal-content-border-color: $body-bg2;
|
$modal-content-border-color: $body-bg;
|
||||||
$modal-header-border-color: transparent;
|
$modal-header-border-color: transparent;
|
||||||
$modal-footer-border-color: transparent;
|
$modal-footer-border-color: transparent;
|
||||||
|
|
||||||
$popover-bg: $body-bg2;
|
$popover-bg: $body-bg;
|
||||||
|
|
||||||
$dropdown-bg: $body-bg2;
|
$dropdown-bg: $body-bg;
|
||||||
$dropdown-link-color: $body-color;
|
$dropdown-link-color: $body-color;
|
||||||
$dropdown-link-hover-color: #333;
|
$dropdown-link-hover-color: #333;
|
||||||
$dropdown-link-hover-bg: $body-bg3;
|
$dropdown-link-hover-bg: $body-bg2;
|
||||||
//$dropdown-link-active-color: $component-active-color;
|
//$dropdown-link-active-color: $component-active-color;
|
||||||
//$dropdown-link-active-bg: $component-active-bg;
|
//$dropdown-link-active-bg: $component-active-bg;
|
||||||
$dropdown-link-disabled-color: #333;
|
$dropdown-link-disabled-color: #333;
|
||||||
@@ -79,17 +81,15 @@ $list-group-link-active-bg: rgba(255,255,255,.2);
|
|||||||
$pre-bg: $dropdown-bg;
|
$pre-bg: $dropdown-bg;
|
||||||
$pre-color: $dropdown-link-color;
|
$pre-color: $dropdown-link-color;
|
||||||
|
|
||||||
$alert-danger-bg: $body-bg2;
|
$alert-danger-bg: $body-bg;
|
||||||
$alert-danger-text: $red;
|
$alert-danger-text: $red;
|
||||||
$alert-danger-border: $red;
|
$alert-danger-border: $red;
|
||||||
|
|
||||||
|
$headings-font-weight: lighter;
|
||||||
|
$headings-color: #eee;
|
||||||
|
|
||||||
@import '~bootstrap/scss/bootstrap.scss';
|
@import '~bootstrap/scss/bootstrap.scss';
|
||||||
|
|
||||||
title-bar {
|
|
||||||
background: $body-bg2;
|
|
||||||
}
|
|
||||||
|
|
||||||
window-controls {
|
window-controls {
|
||||||
svg {
|
svg {
|
||||||
transition: 0.25s fill;
|
transition: 0.25s fill;
|
||||||
@@ -105,36 +105,34 @@ window-controls {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$border-color: #141414;
|
$border-color: #111;
|
||||||
|
|
||||||
|
body {
|
||||||
|
background: $body-bg;
|
||||||
|
|
||||||
|
&.vibrant {
|
||||||
|
background: rgba(0,0,0,.4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
app-root {
|
app-root {
|
||||||
&> .content {
|
&> .content {
|
||||||
background: $body-bg2;
|
|
||||||
|
|
||||||
.tab-bar {
|
.tab-bar {
|
||||||
.btn-tab-bar {
|
.btn-tab-bar {
|
||||||
&:not(:hover):not(:active) {
|
&:not(:hover):not(:active) {
|
||||||
background: $body-bg2;
|
background: transparent;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.drag-space {
|
|
||||||
background: $body-bg2;
|
|
||||||
}
|
|
||||||
|
|
||||||
&>.tabs {
|
&>.tabs {
|
||||||
&:empty {
|
|
||||||
background: $body-bg2;
|
|
||||||
}
|
|
||||||
|
|
||||||
tab-header {
|
tab-header {
|
||||||
background: $body-bg2;
|
|
||||||
border-left: 1px solid transparent;
|
border-left: 1px solid transparent;
|
||||||
border-right: 1px solid transparent;
|
border-right: 1px solid transparent;
|
||||||
border-top: 1px solid transparent;
|
|
||||||
|
transition: 0.25s all;
|
||||||
|
|
||||||
.index {
|
.index {
|
||||||
color: #555;
|
color: #888;
|
||||||
}
|
}
|
||||||
|
|
||||||
button {
|
button {
|
||||||
@@ -147,7 +145,8 @@ app-root {
|
|||||||
}
|
}
|
||||||
|
|
||||||
&.active {
|
&.active {
|
||||||
background: $body-bg;
|
color: white;
|
||||||
|
background: $content-bg;
|
||||||
border-left: 1px solid $border-color;
|
border-left: 1px solid $border-color;
|
||||||
border-right: 1px solid $border-color;
|
border-right: 1px solid $border-color;
|
||||||
}
|
}
|
||||||
@@ -156,54 +155,59 @@ app-root {
|
|||||||
}
|
}
|
||||||
|
|
||||||
&.tabs-on-top .tab-bar {
|
&.tabs-on-top .tab-bar {
|
||||||
|
&>.background {
|
||||||
border-bottom: 1px solid $border-color;
|
border-bottom: 1px solid $border-color;
|
||||||
|
}
|
||||||
|
|
||||||
tab-header {
|
tab-header {
|
||||||
border-top: 1px solid transparent;
|
|
||||||
border-bottom: 1px solid $border-color;
|
border-bottom: 1px solid $border-color;
|
||||||
margin-bottom: -1px;
|
|
||||||
|
|
||||||
&.active {
|
&.active {
|
||||||
border-top: 1px solid $teal;
|
|
||||||
border-bottom-color: transparent;
|
border-bottom-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.has-activity:not(.active) {
|
&.has-activity:not(.active) {
|
||||||
border-top: 1px solid $green;
|
background: linear-gradient(to bottom, rgba(208, 0, 0, 0) 95%, #1aa99c 100%);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&:not(.tabs-on-top) .tab-bar {
|
&:not(.tabs-on-top) .tab-bar {
|
||||||
|
&>.background {
|
||||||
border-top: 1px solid $border-color;
|
border-top: 1px solid $border-color;
|
||||||
|
}
|
||||||
|
|
||||||
tab-header {
|
tab-header {
|
||||||
border-bottom: 1px solid transparent;
|
|
||||||
border-top: 1px solid $border-color;
|
border-top: 1px solid $border-color;
|
||||||
margin-top: -1px;
|
|
||||||
|
|
||||||
&.active {
|
&.active {
|
||||||
border-bottom: 1px solid $teal;
|
|
||||||
margin-top: -1px;
|
margin-top: -1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.has-activity:not(.active) {
|
&.has-activity:not(.active) {
|
||||||
border-bottom: 1px solid $green;
|
background: linear-gradient(to top, rgba(208, 0, 0, 0) 95%, #1aa99c 100%);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.platform-win32, &.platform-linux {
|
||||||
|
border: 1px solid #111;
|
||||||
|
&>.content .tab-bar .tabs tab-header:first-child {
|
||||||
|
border-left: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tab-body {
|
tab-body {
|
||||||
background: $body-bg;
|
background: $content-bg;
|
||||||
}
|
}
|
||||||
|
|
||||||
settings-tab > ngb-tabset {
|
settings-tab > ngb-tabset {
|
||||||
border-right: 1px solid $body-bg2;
|
border-right: 1px solid $body-bg;
|
||||||
|
|
||||||
& > .nav {
|
& > .nav {
|
||||||
background: $body-bg3;
|
background: rgba(0, 0, 0, 0.25);
|
||||||
|
|
||||||
& > .nav-item > .nav-link {
|
& > .nav-item > .nav-link {
|
||||||
border-left: none;
|
border-left: none;
|
||||||
@@ -231,7 +235,7 @@ settings-tab > ngb-tabset {
|
|||||||
|
|
||||||
multi-hotkey-input {
|
multi-hotkey-input {
|
||||||
.item {
|
.item {
|
||||||
background: $body-bg3;
|
background: $body-bg2;
|
||||||
border: 1px solid $blue;
|
border: 1px solid $blue;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
margin-right: 5px;
|
margin-right: 5px;
|
||||||
@@ -241,7 +245,7 @@ multi-hotkey-input {
|
|||||||
|
|
||||||
.stroke {
|
.stroke {
|
||||||
padding: 0 6px;
|
padding: 0 6px;
|
||||||
border-right: 1px solid $body-bg;
|
border-right: 1px solid $content-bg;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -256,8 +260,8 @@ multi-hotkey-input {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.add, .item .body, .item .remove {
|
.add, .item .body, .item .remove {
|
||||||
&:hover { background: darken($body-bg3, 5%); }
|
&:hover { background: darken($body-bg2, 5%); }
|
||||||
&:active { background: darken($body-bg3, 15%); }
|
&:active { background: darken($body-bg2, 15%); }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -270,7 +274,7 @@ hotkey-input-modal {
|
|||||||
height: 55px;
|
height: 55px;
|
||||||
|
|
||||||
.stroke {
|
.stroke {
|
||||||
background: $body-bg3;
|
background: $body-bg2;
|
||||||
border: 1px solid $blue;
|
border: 1px solid $blue;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
margin-right: 10px;
|
margin-right: 10px;
|
||||||
@@ -332,3 +336,15 @@ ngb-tabset .tab-content {
|
|||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
select.form-control {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
background-image: url("data:image/svg+xml;utf8,<svg version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' width='24' height='24' viewBox='0 0 24 24'><path fill='#444' d='M7.406 7.828l4.594 4.594 4.594-4.594 1.406 1.406-6 6-6-6z'></path></svg>");
|
||||||
|
background-position: 100% 50%;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
padding-right: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
checkbox i.on {
|
||||||
|
color: $blue;
|
||||||
|
}
|
||||||
|
@@ -6,6 +6,7 @@ module.exports = {
|
|||||||
entry: 'src/index.ts',
|
entry: 'src/index.ts',
|
||||||
devtool: 'source-map',
|
devtool: 'source-map',
|
||||||
context: __dirname,
|
context: __dirname,
|
||||||
|
mode: 'development',
|
||||||
output: {
|
output: {
|
||||||
path: path.resolve(__dirname, 'dist'),
|
path: path.resolve(__dirname, 'dist'),
|
||||||
filename: 'index.js',
|
filename: 'index.js',
|
||||||
@@ -18,9 +19,10 @@ module.exports = {
|
|||||||
extensions: ['.ts', '.js'],
|
extensions: ['.ts', '.js'],
|
||||||
},
|
},
|
||||||
module: {
|
module: {
|
||||||
loaders: [
|
rules: [
|
||||||
{
|
{
|
||||||
test: /\.ts$/,
|
test: /\.ts$/,
|
||||||
|
use: {
|
||||||
loader: 'awesome-typescript-loader',
|
loader: 'awesome-typescript-loader',
|
||||||
options: {
|
options: {
|
||||||
configFileName: path.resolve(__dirname, 'tsconfig.json'),
|
configFileName: path.resolve(__dirname, 'tsconfig.json'),
|
||||||
@@ -30,12 +32,14 @@ module.exports = {
|
|||||||
"*": [path.resolve(__dirname, '../app/node_modules/*')],
|
"*": [path.resolve(__dirname, '../app/node_modules/*')],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{ test: /\.pug$/, use: ['apply-loader', 'pug-loader'] },
|
{ test: /\.pug$/, use: ['apply-loader', 'pug-loader'] },
|
||||||
{ test: /\.scss$/, use: ['to-string-loader', 'css-loader', 'sass-loader'] },
|
{ test: /\.scss$/, use: ['to-string-loader', 'css-loader', 'sass-loader'] },
|
||||||
{ test: /\.css$/, use: ['to-string-loader', 'css-loader'], include: /component\.css/ },
|
{ test: /\.css$/, use: ['to-string-loader', 'css-loader'], include: /component\.css/ },
|
||||||
{ test: /\.css$/, use: ['style-loader', 'css-loader'], exclude: /component\.css/ },
|
{ test: /\.css$/, use: ['style-loader', 'css-loader'], exclude: /component\.css/ },
|
||||||
{ test: /\.yaml$/, use: ['json-loader', 'yaml-loader'] },
|
{ test: /\.yaml$/, use: ['json-loader', 'yaml-loader'] },
|
||||||
|
{ test: /\.svg/, use: ['svg-inline-loader'] },
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
externals: [
|
externals: [
|
||||||
|
@@ -185,15 +185,24 @@ 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"
|
||||||
|
|
||||||
ngx-perfect-scrollbar@4.0.0:
|
ng2-dnd@^5.0.2:
|
||||||
version "4.0.0"
|
version "5.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/ngx-perfect-scrollbar/-/ngx-perfect-scrollbar-4.0.0.tgz#f1e19449fa97d7f16e1ceb1fe1739e4bb646bebe"
|
resolved "https://registry.yarnpkg.com/ng2-dnd/-/ng2-dnd-5.0.2.tgz#862278ac7dedfa14f5783bbf34014d5d73dfefb4"
|
||||||
dependencies:
|
|
||||||
perfect-scrollbar "~0.6.0"
|
|
||||||
|
|
||||||
perfect-scrollbar@~0.6.0:
|
ngx-perfect-scrollbar@^6.0.0:
|
||||||
version "0.6.16"
|
version "6.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/perfect-scrollbar/-/perfect-scrollbar-0.6.16.tgz#b1d61a5245cf3962bb9a8407a3fc669d923212fc"
|
resolved "https://registry.yarnpkg.com/ngx-perfect-scrollbar/-/ngx-perfect-scrollbar-6.0.0.tgz#92b51957c04ed6a6d416beca2707bab005667b68"
|
||||||
|
dependencies:
|
||||||
|
perfect-scrollbar "^1.3.0"
|
||||||
|
resize-observer-polyfill "^1.4.0"
|
||||||
|
|
||||||
|
perfect-scrollbar@^1.3.0:
|
||||||
|
version "1.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/perfect-scrollbar/-/perfect-scrollbar-1.3.0.tgz#61da56f94b58870d8e0a617bce649cee17d1e3b2"
|
||||||
|
|
||||||
|
resize-observer-polyfill@^1.4.0:
|
||||||
|
version "1.5.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.0.tgz#660ff1d9712a2382baa2cad450a4716209f9ca69"
|
||||||
|
|
||||||
sax@^1.2.1:
|
sax@^1.2.1:
|
||||||
version "1.2.4"
|
version "1.2.4"
|
||||||
@@ -225,10 +234,6 @@ tether@^1.4.0:
|
|||||||
version "1.4.0"
|
version "1.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/tether/-/tether-1.4.0.tgz#0f9fa171f75bf58485d8149e94799d7ae74d1c1a"
|
resolved "https://registry.yarnpkg.com/tether/-/tether-1.4.0.tgz#0f9fa171f75bf58485d8149e94799d7ae74d1c1a"
|
||||||
|
|
||||||
typescript@^2.4.1:
|
|
||||||
version "2.5.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.5.2.tgz#038a95f7d9bbb420b1bf35ba31d4c5c1dd3ffe34"
|
|
||||||
|
|
||||||
universalify@^0.1.0:
|
universalify@^0.1.0:
|
||||||
version "0.1.1"
|
version "0.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.1.tgz#fa71badd4437af4c148841e3b3b165f9e9e590b7"
|
resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.1.tgz#fa71badd4437af4c148841e3b3b165f9e9e590b7"
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "terminus-plugin-manager",
|
"name": "terminus-plugin-manager",
|
||||||
"version": "1.0.0-alpha.36",
|
"version": "1.0.0-alpha.48",
|
||||||
"description": "Terminus' plugin manager",
|
"description": "Terminus' plugin manager",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"terminus-builtin-plugin"
|
"terminus-builtin-plugin"
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
import { BehaviorSubject, Observable } from 'rxjs'
|
import { BehaviorSubject, Observable } from 'rxjs'
|
||||||
|
import { debounceTime, distinctUntilChanged, first, tap, flatMap } from 'rxjs/operators'
|
||||||
import * as semver from 'semver'
|
import * as semver from 'semver'
|
||||||
|
|
||||||
import { Component, Input } from '@angular/core'
|
import { Component, Input } from '@angular/core'
|
||||||
@@ -33,15 +34,18 @@ export class PluginsSettingsTabComponent {
|
|||||||
|
|
||||||
ngOnInit () {
|
ngOnInit () {
|
||||||
this.availablePlugins$ = this.availablePluginsQuery$
|
this.availablePlugins$ = this.availablePluginsQuery$
|
||||||
.debounceTime(200)
|
.asObservable()
|
||||||
.distinctUntilChanged()
|
.pipe(
|
||||||
.flatMap(query => {
|
debounceTime(200),
|
||||||
|
distinctUntilChanged(),
|
||||||
|
flatMap(query => {
|
||||||
this.availablePluginsReady = false
|
this.availablePluginsReady = false
|
||||||
return this.pluginManager.listAvailable(query).do(() => {
|
return this.pluginManager.listAvailable(query).pipe(tap(() => {
|
||||||
this.availablePluginsReady = true
|
this.availablePluginsReady = true
|
||||||
|
}))
|
||||||
})
|
})
|
||||||
})
|
)
|
||||||
this.availablePlugins$.first().subscribe(available => {
|
this.availablePlugins$.pipe(first()).subscribe(available => {
|
||||||
for (let plugin of this.pluginManager.installedPlugins) {
|
for (let plugin of this.pluginManager.installedPlugins) {
|
||||||
this.knownUpgrades[plugin.name] = available.find(x => x.name === plugin.name && semver.gt(x.version, plugin.version))
|
this.knownUpgrades[plugin.name] = available.find(x => x.name === plugin.name && semver.gt(x.version, plugin.version))
|
||||||
}
|
}
|
||||||
|
@@ -2,7 +2,8 @@ import * as path from 'path'
|
|||||||
import * as fs from 'mz/fs'
|
import * as fs from 'mz/fs'
|
||||||
import { exec } from 'mz/child_process'
|
import { exec } from 'mz/child_process'
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import { Observable } from 'rxjs'
|
import { Observable, from } from 'rxjs'
|
||||||
|
import { map } from 'rxjs/operators'
|
||||||
import { Injectable } from '@angular/core'
|
import { Injectable } from '@angular/core'
|
||||||
import { Logger, LogService, ConfigService, HostAppService, Platform } from 'terminus-core'
|
import { Logger, LogService, ConfigService, HostAppService, Platform } from 'terminus-core'
|
||||||
|
|
||||||
@@ -29,6 +30,7 @@ export class PluginManagerService {
|
|||||||
userPluginsPath: string = (window as any).userPluginsPath
|
userPluginsPath: string = (window as any).userPluginsPath
|
||||||
installedPlugins: IPluginInfo[] = (window as any).installedPlugins
|
installedPlugins: IPluginInfo[] = (window as any).installedPlugins
|
||||||
npmPath: string
|
npmPath: string
|
||||||
|
private envPath: string
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
log: LogService,
|
log: LogService,
|
||||||
@@ -41,11 +43,13 @@ export class PluginManagerService {
|
|||||||
|
|
||||||
async detectPath () {
|
async detectPath () {
|
||||||
this.npmPath = this.config.store.npm
|
this.npmPath = this.config.store.npm
|
||||||
|
this.envPath = process.env.PATH
|
||||||
if (await fs.exists(this.npmPath)) {
|
if (await fs.exists(this.npmPath)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (this.hostApp.platform !== Platform.Windows) {
|
if (this.hostApp.platform !== Platform.Windows) {
|
||||||
let searchPaths = (await exec('$SHELL -c -i \'echo $PATH\''))[0].toString().trim().split(':')
|
this.envPath = (await exec('$SHELL -c -i \'echo $PATH\''))[0].toString().trim()
|
||||||
|
let searchPaths = this.envPath.split(':')
|
||||||
for (let searchPath of searchPaths) {
|
for (let searchPath of searchPaths) {
|
||||||
if (await fs.exists(path.join(searchPath, 'npm'))) {
|
if (await fs.exists(path.join(searchPath, 'npm'))) {
|
||||||
this.logger.debug('Found npm in', searchPath)
|
this.logger.debug('Found npm in', searchPath)
|
||||||
@@ -59,7 +63,7 @@ export class PluginManagerService {
|
|||||||
async isNPMInstalled (): Promise<boolean> {
|
async isNPMInstalled (): Promise<boolean> {
|
||||||
await this.detectPath()
|
await this.detectPath()
|
||||||
try {
|
try {
|
||||||
await exec(`${this.npmPath} -v`)
|
await exec(`${this.npmPath} -v`, { env: this.getEnv() })
|
||||||
return true
|
return true
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
return false
|
return false
|
||||||
@@ -67,11 +71,10 @@ export class PluginManagerService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
listAvailable (query?: string): Observable<IPluginInfo[]> {
|
listAvailable (query?: string): Observable<IPluginInfo[]> {
|
||||||
return Observable
|
return from(
|
||||||
.fromPromise(
|
axios.get(`https://api.npms.io/v2/search?q=keywords%3A${KEYWORD}+${encodeURIComponent(query || '')}&from=0&size=250`, {})
|
||||||
axios.get(`https://www.npmjs.com/-/search?text=keywords:${KEYWORD}+${encodeURIComponent(query || '')}&from=0&size=1000`)
|
).pipe(
|
||||||
)
|
map(response => response.data.results.map(item => ({
|
||||||
.map(response => response.data.objects.map(item => ({
|
|
||||||
name: item.package.name.substring(NAME_PREFIX.length),
|
name: item.package.name.substring(NAME_PREFIX.length),
|
||||||
packageName: item.package.name,
|
packageName: item.package.name,
|
||||||
description: item.package.description,
|
description: item.package.description,
|
||||||
@@ -79,18 +82,23 @@ export class PluginManagerService {
|
|||||||
homepage: item.package.links.homepage,
|
homepage: item.package.links.homepage,
|
||||||
author: (item.package.author || {}).name,
|
author: (item.package.author || {}).name,
|
||||||
isOfficial: item.package.publisher.username === OFFICIAL_NPM_ACCOUNT,
|
isOfficial: item.package.publisher.username === OFFICIAL_NPM_ACCOUNT,
|
||||||
})))
|
}))),
|
||||||
.map(plugins => plugins.filter(x => x.packageName.startsWith(NAME_PREFIX)))
|
map(plugins => plugins.filter(x => x.packageName.startsWith(NAME_PREFIX))),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
async installPlugin (plugin: IPluginInfo) {
|
async installPlugin (plugin: IPluginInfo) {
|
||||||
await exec(`${this.npmPath} --prefix "${this.userPluginsPath}" install ${plugin.packageName}@${plugin.version}`)
|
await exec(`${this.npmPath} --prefix "${this.userPluginsPath}" install ${plugin.packageName}@${plugin.version}`, { env: this.getEnv() })
|
||||||
this.installedPlugins = this.installedPlugins.filter(x => x.packageName !== plugin.packageName)
|
this.installedPlugins = this.installedPlugins.filter(x => x.packageName !== plugin.packageName)
|
||||||
this.installedPlugins.push(plugin)
|
this.installedPlugins.push(plugin)
|
||||||
}
|
}
|
||||||
|
|
||||||
async uninstallPlugin (plugin: IPluginInfo) {
|
async uninstallPlugin (plugin: IPluginInfo) {
|
||||||
await exec(`${this.npmPath} --prefix "${this.userPluginsPath}" remove ${plugin.packageName}`)
|
await exec(`${this.npmPath} --prefix "${this.userPluginsPath}" remove ${plugin.packageName}`, { env: this.getEnv() })
|
||||||
this.installedPlugins = this.installedPlugins.filter(x => x.packageName !== plugin.packageName)
|
this.installedPlugins = this.installedPlugins.filter(x => x.packageName !== plugin.packageName)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getEnv (): any {
|
||||||
|
return Object.assign(process.env, { PATH: this.envPath })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -18,9 +18,10 @@ module.exports = {
|
|||||||
extensions: ['.ts', '.js'],
|
extensions: ['.ts', '.js'],
|
||||||
},
|
},
|
||||||
module: {
|
module: {
|
||||||
loaders: [
|
rules: [
|
||||||
{
|
{
|
||||||
test: /\.ts$/,
|
test: /\.ts$/,
|
||||||
|
use: {
|
||||||
loader: 'awesome-typescript-loader',
|
loader: 'awesome-typescript-loader',
|
||||||
query: {
|
query: {
|
||||||
configFileName: path.resolve(__dirname, 'tsconfig.json'),
|
configFileName: path.resolve(__dirname, 'tsconfig.json'),
|
||||||
@@ -30,6 +31,7 @@ module.exports = {
|
|||||||
"*": [path.resolve(__dirname, '../app/node_modules/*')],
|
"*": [path.resolve(__dirname, '../app/node_modules/*')],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{ test: /\.pug$/, use: ['apply-loader', 'pug-loader'] },
|
{ test: /\.pug$/, use: ['apply-loader', 'pug-loader'] },
|
||||||
{ test: /\.scss$/, use: ['to-string-loader', 'css-loader', 'sass-loader'] },
|
{ test: /\.scss$/, use: ['to-string-loader', 'css-loader', 'sass-loader'] },
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "terminus-settings",
|
"name": "terminus-settings",
|
||||||
"version": "1.0.0-alpha.36",
|
"version": "1.0.0-alpha.48",
|
||||||
"description": "Terminus terminal settings page",
|
"description": "Terminus terminal settings page",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"terminus-builtin-plugin"
|
"terminus-builtin-plugin"
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
import { Injectable } from '@angular/core'
|
import { Injectable } from '@angular/core'
|
||||||
|
import { DomSanitizer } from '@angular/platform-browser'
|
||||||
import { ToolbarButtonProvider, IToolbarButton, AppService, HostAppService } from 'terminus-core'
|
import { ToolbarButtonProvider, IToolbarButton, AppService, HostAppService } from 'terminus-core'
|
||||||
|
|
||||||
import { SettingsTabComponent } from './components/settingsTab.component'
|
import { SettingsTabComponent } from './components/settingsTab.component'
|
||||||
@@ -8,6 +9,7 @@ export class ButtonProvider extends ToolbarButtonProvider {
|
|||||||
constructor (
|
constructor (
|
||||||
hostApp: HostAppService,
|
hostApp: HostAppService,
|
||||||
private app: AppService,
|
private app: AppService,
|
||||||
|
private domSanitizer: DomSanitizer,
|
||||||
) {
|
) {
|
||||||
super()
|
super()
|
||||||
hostApp.preferencesMenu$.subscribe(() => this.open())
|
hostApp.preferencesMenu$.subscribe(() => this.open())
|
||||||
@@ -15,8 +17,9 @@ export class ButtonProvider extends ToolbarButtonProvider {
|
|||||||
|
|
||||||
provide (): IToolbarButton[] {
|
provide (): IToolbarButton[] {
|
||||||
return [{
|
return [{
|
||||||
icon: 'sliders',
|
icon: this.domSanitizer.bypassSecurityTrustHtml(require('./icons/cog.svg')),
|
||||||
title: 'Settings',
|
title: 'Settings',
|
||||||
|
touchBarTitle: '⚙️',
|
||||||
weight: 10,
|
weight: 10,
|
||||||
click: () => this.open(),
|
click: () => this.open(),
|
||||||
}]
|
}]
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
import { Component, Input, trigger, transition, style, animate } from '@angular/core'
|
import { Component, Input } from '@angular/core'
|
||||||
|
import { trigger, transition, style, animate } from '@angular/animations'
|
||||||
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
|
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
|
||||||
import { Subscription } from 'rxjs'
|
import { Subscription } from 'rxjs'
|
||||||
import { HotkeysService } from 'terminus-core'
|
import { HotkeysService } from 'terminus-core'
|
||||||
|
@@ -1,5 +1,10 @@
|
|||||||
:host {
|
:host {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
|
||||||
|
&:hover .add {
|
||||||
|
display: initial;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.item {
|
.item {
|
||||||
@@ -22,4 +27,9 @@
|
|||||||
|
|
||||||
.add {
|
.add {
|
||||||
flex: auto;
|
flex: auto;
|
||||||
|
display: none;
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -5,6 +5,7 @@ ngb-tabset.vertical(type='tabs', [activeId]='activeTab')
|
|||||||
ng-template(ngbTabTitle)
|
ng-template(ngbTabTitle)
|
||||||
| Application
|
| Application
|
||||||
ng-template(ngbTabContent)
|
ng-template(ngbTabContent)
|
||||||
|
h3.mb-3 Application
|
||||||
.row
|
.row
|
||||||
.col.col-lg-6
|
.col.col-lg-6
|
||||||
.form-group
|
.form-group
|
||||||
@@ -37,6 +38,42 @@ ngb-tabset.vertical(type='tabs', [activeId]='activeTab')
|
|||||||
[value]='"bottom"'
|
[value]='"bottom"'
|
||||||
)
|
)
|
||||||
| At the bottom
|
| At the bottom
|
||||||
|
|
||||||
|
.form-group(*ngIf='hostApp.platform !== Platform.Linux')
|
||||||
|
label Vibrancy
|
||||||
|
br
|
||||||
|
.btn-group(
|
||||||
|
'[(ngModel)]'='config.store.appearance.vibrancy'
|
||||||
|
'(ngModelChange)'='config.save()'
|
||||||
|
ngbRadioGroup
|
||||||
|
)
|
||||||
|
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(
|
||||||
|
type='range',
|
||||||
|
'[(ngModel)]'='config.store.appearance.opacity',
|
||||||
|
'(ngModelChange)'='config.save()',
|
||||||
|
min='0.05',
|
||||||
|
max='1',
|
||||||
|
step='0.01'
|
||||||
|
)
|
||||||
|
|
||||||
.col.col-lg-6
|
.col.col-lg-6
|
||||||
.form-group
|
.form-group
|
||||||
label Window frame
|
label Window frame
|
||||||
@@ -168,6 +205,7 @@ ngb-tabset.vertical(type='tabs', [activeId]='activeTab')
|
|||||||
ng-template(ngbTabTitle)
|
ng-template(ngbTabTitle)
|
||||||
| Hotkeys
|
| Hotkeys
|
||||||
ng-template(ngbTabContent)
|
ng-template(ngbTabContent)
|
||||||
|
h3.mb-3 Hotkeys
|
||||||
input.form-control(type='search', placeholder='Search hotkeys', [(ngModel)]='hotkeyFilter')
|
input.form-control(type='search', placeholder='Search hotkeys', [(ngModel)]='hotkeyFilter')
|
||||||
.form-group
|
.form-group
|
||||||
table.hotkeys-table
|
table.hotkeys-table
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import { Component, Inject, Input } from '@angular/core'
|
import { Component, Inject, Input } from '@angular/core'
|
||||||
import { ElectronService, DockingService, ConfigService, IHotkeyDescription, HotkeyProvider, BaseTabComponent, Theme, HostAppService } from 'terminus-core'
|
import { ElectronService, DockingService, ConfigService, IHotkeyDescription, HotkeyProvider, BaseTabComponent, Theme, HostAppService, Platform } from 'terminus-core'
|
||||||
|
|
||||||
import { SettingsTabProvider } from '../api'
|
import { SettingsTabProvider } from '../api'
|
||||||
|
|
||||||
@@ -14,8 +14,9 @@ import { SettingsTabProvider } from '../api'
|
|||||||
export class SettingsTabComponent extends BaseTabComponent {
|
export class SettingsTabComponent extends BaseTabComponent {
|
||||||
@Input() activeTab: string
|
@Input() activeTab: string
|
||||||
hotkeyFilter = ''
|
hotkeyFilter = ''
|
||||||
private hotkeyDescriptions: IHotkeyDescription[]
|
hotkeyDescriptions: IHotkeyDescription[]
|
||||||
private screens
|
screens: any[]
|
||||||
|
Platform = Platform
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
public config: ConfigService,
|
public config: ConfigService,
|
||||||
@@ -28,8 +29,7 @@ export class SettingsTabComponent extends BaseTabComponent {
|
|||||||
) {
|
) {
|
||||||
super()
|
super()
|
||||||
this.hotkeyDescriptions = config.enabledServices(hotkeyProviders).map(x => x.hotkeys).reduce((a, b) => a.concat(b))
|
this.hotkeyDescriptions = config.enabledServices(hotkeyProviders).map(x => x.hotkeys).reduce((a, b) => a.concat(b))
|
||||||
this.title = 'Settings'
|
this.setTitle('Settings')
|
||||||
this.scrollable = true
|
|
||||||
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)
|
||||||
|
@@ -8,7 +8,7 @@ import { SettingsTabProvider } from '../api'
|
|||||||
export class SettingsTabBodyComponent {
|
export class SettingsTabBodyComponent {
|
||||||
@Input() provider: SettingsTabProvider
|
@Input() provider: SettingsTabProvider
|
||||||
@ViewChild('placeholder', {read: ViewContainerRef}) placeholder: ViewContainerRef
|
@ViewChild('placeholder', {read: ViewContainerRef}) placeholder: ViewContainerRef
|
||||||
private component: ComponentRef<Component>
|
component: ComponentRef<Component>
|
||||||
|
|
||||||
constructor (private componentFactoryResolver: ComponentFactoryResolver) { }
|
constructor (private componentFactoryResolver: ComponentFactoryResolver) { }
|
||||||
|
|
||||||
|
1
terminus-settings/src/icons/cog.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M482.696 299.276l-32.61-18.827a195.168 195.168 0 0 0 0-48.899l32.61-18.827c9.576-5.528 14.195-16.902 11.046-27.501-11.214-37.749-31.175-71.728-57.535-99.595-7.634-8.07-19.817-9.836-29.437-4.282l-32.562 18.798a194.125 194.125 0 0 0-42.339-24.48V38.049c0-11.13-7.652-20.804-18.484-23.367-37.644-8.909-77.118-8.91-114.77 0-10.831 2.563-18.484 12.236-18.484 23.367v37.614a194.101 194.101 0 0 0-42.339 24.48L105.23 81.345c-9.621-5.554-21.804-3.788-29.437 4.282-26.36 27.867-46.321 61.847-57.535 99.595-3.149 10.599 1.47 21.972 11.046 27.501l32.61 18.827a195.168 195.168 0 0 0 0 48.899l-32.61 18.827c-9.576 5.528-14.195 16.902-11.046 27.501 11.214 37.748 31.175 71.728 57.535 99.595 7.634 8.07 19.817 9.836 29.437 4.283l32.562-18.798a194.08 194.08 0 0 0 42.339 24.479v37.614c0 11.13 7.652 20.804 18.484 23.367 37.645 8.909 77.118 8.91 114.77 0 10.831-2.563 18.484-12.236 18.484-23.367v-37.614a194.138 194.138 0 0 0 42.339-24.479l32.562 18.798c9.62 5.554 21.803 3.788 29.437-4.283 26.36-27.867 46.321-61.847 57.535-99.595 3.149-10.599-1.47-21.972-11.046-27.501zm-65.479 100.461l-46.309-26.74c-26.988 23.071-36.559 28.876-71.039 41.059v53.479a217.145 217.145 0 0 1-87.738 0v-53.479c-33.621-11.879-43.355-17.395-71.039-41.059l-46.309 26.74c-19.71-22.09-34.689-47.989-43.929-75.958l46.329-26.74c-6.535-35.417-6.538-46.644 0-82.079l-46.329-26.74c9.24-27.969 24.22-53.869 43.929-75.969l46.309 26.76c27.377-23.434 37.063-29.065 71.039-41.069V44.464a216.79 216.79 0 0 1 87.738 0v53.479c33.978 12.005 43.665 17.637 71.039 41.069l46.309-26.76c19.709 22.099 34.689 47.999 43.929 75.969l-46.329 26.74c6.536 35.426 6.538 46.644 0 82.079l46.329 26.74c-9.24 27.968-24.219 53.868-43.929 75.957zM256 160c-52.935 0-96 43.065-96 96s43.065 96 96 96 96-43.065 96-96-43.065-96-96-96zm0 160c-35.29 0-64-28.71-64-64s28.71-64 64-64 64 28.71 64 64-28.71 64-64 64z"/></svg>
|
After Width: | Height: | Size: 1.9 KiB |
@@ -18,9 +18,10 @@ module.exports = {
|
|||||||
extensions: ['.ts', '.js'],
|
extensions: ['.ts', '.js'],
|
||||||
},
|
},
|
||||||
module: {
|
module: {
|
||||||
loaders: [
|
rules: [
|
||||||
{
|
{
|
||||||
test: /\.ts$/,
|
test: /\.ts$/,
|
||||||
|
use: {
|
||||||
loader: 'awesome-typescript-loader',
|
loader: 'awesome-typescript-loader',
|
||||||
options: {
|
options: {
|
||||||
configFileName: path.resolve(__dirname, 'tsconfig.json'),
|
configFileName: path.resolve(__dirname, 'tsconfig.json'),
|
||||||
@@ -30,10 +31,12 @@ module.exports = {
|
|||||||
"*": [path.resolve(__dirname, '../app/node_modules/*')],
|
"*": [path.resolve(__dirname, '../app/node_modules/*')],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{ test: /\.pug$/, use: ['apply-loader', 'pug-loader'] },
|
{ test: /\.pug$/, use: ['apply-loader', 'pug-loader'] },
|
||||||
{ test: /\.scss$/, use: ['to-string-loader', 'css-loader', 'sass-loader'] },
|
{ test: /\.scss$/, use: ['to-string-loader', 'css-loader', 'sass-loader'] },
|
||||||
{ test: /\.css$/, use: ['to-string-loader', 'css-loader', 'sass-loader'] },
|
{ test: /\.css$/, use: ['to-string-loader', 'css-loader', 'sass-loader'] },
|
||||||
|
{ test: /\.svg/, use: ['svg-inline-loader'] },
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
externals: [
|
externals: [
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "terminus-ssh",
|
"name": "terminus-ssh",
|
||||||
"version": "0.0.1",
|
"version": "1.0.0-alpha.48",
|
||||||
"description": "SSH connection manager for Terminus",
|
"description": "SSH connection manager for Terminus",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"terminus-builtin-plugin"
|
"terminus-builtin-plugin"
|
||||||
@@ -19,12 +19,8 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/ssh2": "^0.5.35",
|
"@types/ssh2": "^0.5.35",
|
||||||
"@types/webpack-env": "^1.13.0",
|
"@types/webpack-env": "^1.13.0",
|
||||||
"apply-loader": "^2.0.0",
|
|
||||||
"awesome-typescript-loader": "^3.1.2",
|
|
||||||
"electron": "^1.6.11",
|
"electron": "^1.6.11",
|
||||||
"ngx-toastr": "^8.0.0",
|
"ngx-toastr": "^8.0.0",
|
||||||
"pug": "^2.0.0-rc.3",
|
|
||||||
"pug-loader": "^2.3.0",
|
|
||||||
"rxjs": "^5.4.0",
|
"rxjs": "^5.4.0",
|
||||||
"typescript": "^2.2.2",
|
"typescript": "^2.2.2",
|
||||||
"webpack": "^2.3.3"
|
"webpack": "^2.3.3"
|
||||||
|
@@ -12,7 +12,9 @@ export interface SSHConnection {
|
|||||||
export class SSHSession extends BaseSession {
|
export class SSHSession extends BaseSession {
|
||||||
constructor (private shell: any) {
|
constructor (private shell: any) {
|
||||||
super()
|
super()
|
||||||
|
}
|
||||||
|
|
||||||
|
start () {
|
||||||
this.open = true
|
this.open = true
|
||||||
|
|
||||||
this.shell.on('data', data => {
|
this.shell.on('data', data => {
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
import { Injectable } from '@angular/core'
|
import { Injectable } from '@angular/core'
|
||||||
|
import { DomSanitizer } from '@angular/platform-browser'
|
||||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
||||||
import { HotkeysService, ToolbarButtonProvider, IToolbarButton } from 'terminus-core'
|
import { HotkeysService, ToolbarButtonProvider, IToolbarButton } from 'terminus-core'
|
||||||
import { SSHModalComponent } from './components/sshModal.component'
|
import { SSHModalComponent } from './components/sshModal.component'
|
||||||
@@ -7,6 +8,7 @@ import { SSHModalComponent } from './components/sshModal.component'
|
|||||||
export class ButtonProvider extends ToolbarButtonProvider {
|
export class ButtonProvider extends ToolbarButtonProvider {
|
||||||
constructor (
|
constructor (
|
||||||
private ngbModal: NgbModal,
|
private ngbModal: NgbModal,
|
||||||
|
private domSanitizer: DomSanitizer,
|
||||||
hotkeys: HotkeysService,
|
hotkeys: HotkeysService,
|
||||||
) {
|
) {
|
||||||
super()
|
super()
|
||||||
@@ -18,17 +20,15 @@ export class ButtonProvider extends ToolbarButtonProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
activate () {
|
activate () {
|
||||||
let modal = this.ngbModal.open(SSHModalComponent)
|
this.ngbModal.open(SSHModalComponent)
|
||||||
modal.result.then(() => {
|
|
||||||
//this.terminal.openTab(shell)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
provide (): IToolbarButton[] {
|
provide (): IToolbarButton[] {
|
||||||
return [{
|
return [{
|
||||||
icon: 'globe',
|
icon: this.domSanitizer.bypassSecurityTrustHtml(require('./icons/globe.svg')),
|
||||||
weight: 5,
|
weight: 5,
|
||||||
title: 'SSH connections',
|
title: 'SSH connections',
|
||||||
|
touchBarTitle: 'SSH',
|
||||||
click: async () => {
|
click: async () => {
|
||||||
this.activate()
|
this.activate()
|
||||||
}
|
}
|
||||||
|
1
terminus-ssh/src/icons/globe.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512"><path d="M248 8C111 8 0 119 0 256s111 248 248 248 248-111 248-248S385 8 248 8zm179.3 160h-67.2c-6.7-36.5-17.5-68.8-31.2-94.7 42.9 19 77.7 52.7 98.4 94.7zM248 56c18.6 0 48.6 41.2 63.2 112H184.8C199.4 97.2 229.4 56 248 56zM48 256c0-13.7 1.4-27.1 4-40h77.7c-1 13.1-1.7 26.3-1.7 40s.7 26.9 1.7 40H52c-2.6-12.9-4-26.3-4-40zm20.7 88h67.2c6.7 36.5 17.5 68.8 31.2 94.7-42.9-19-77.7-52.7-98.4-94.7zm67.2-176H68.7c20.7-42 55.5-75.7 98.4-94.7-13.7 25.9-24.5 58.2-31.2 94.7zM248 456c-18.6 0-48.6-41.2-63.2-112h126.5c-14.7 70.8-44.7 112-63.3 112zm70.1-160H177.9c-1.1-12.8-1.9-26-1.9-40s.8-27.2 1.9-40h140.3c1.1 12.8 1.9 26 1.9 40s-.9 27.2-2 40zm10.8 142.7c13.7-25.9 24.4-58.2 31.2-94.7h67.2c-20.7 42-55.5 75.7-98.4 94.7zM366.3 296c1-13.1 1.7-26.3 1.7-40s-.7-26.9-1.7-40H444c2.6 12.9 4 26.3 4 40s-1.4 27.1-4 40h-77.7z"/></svg>
|
After Width: | Height: | Size: 874 B |
@@ -17,11 +17,12 @@ module.exports = {
|
|||||||
extensions: ['.ts', '.js'],
|
extensions: ['.ts', '.js'],
|
||||||
},
|
},
|
||||||
module: {
|
module: {
|
||||||
loaders: [
|
rules: [
|
||||||
{
|
{
|
||||||
test: /\.ts$/,
|
test: /\.ts$/,
|
||||||
|
use: {
|
||||||
loader: 'awesome-typescript-loader',
|
loader: 'awesome-typescript-loader',
|
||||||
query: {
|
options: {
|
||||||
configFileName: path.resolve(__dirname, 'tsconfig.json'),
|
configFileName: path.resolve(__dirname, 'tsconfig.json'),
|
||||||
typeRoots: [path.resolve(__dirname, 'node_modules/@types')],
|
typeRoots: [path.resolve(__dirname, 'node_modules/@types')],
|
||||||
paths: {
|
paths: {
|
||||||
@@ -29,9 +30,11 @@ module.exports = {
|
|||||||
"*": [path.resolve(__dirname, '../app/node_modules/*')],
|
"*": [path.resolve(__dirname, '../app/node_modules/*')],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{ test: /\.pug$/, use: ['apply-loader', 'pug-loader'] },
|
{ test: /\.pug$/, use: ['apply-loader', 'pug-loader'] },
|
||||||
{ test: /\.scss$/, use: ['to-string-loader', 'css-loader', 'sass-loader'] },
|
{ test: /\.scss$/, use: ['to-string-loader', 'css-loader', 'sass-loader'] },
|
||||||
|
{ test: /\.svg/, use: ['svg-inline-loader'] },
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
externals: [
|
externals: [
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "terminus-terminal",
|
"name": "terminus-terminal",
|
||||||
"version": "1.0.0-alpha.36",
|
"version": "1.0.0-alpha.48",
|
||||||
"description": "Terminus' terminal emulation core",
|
"description": "Terminus' terminal emulation core",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"terminus-builtin-plugin"
|
"terminus-builtin-plugin"
|
||||||
@@ -39,7 +39,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@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.2.2",
|
"font-manager": "0.3.0",
|
||||||
"hterm-umdjs": "1.1.3",
|
"hterm-umdjs": "1.1.3",
|
||||||
"mz": "^2.6.0",
|
"mz": "^2.6.0",
|
||||||
"node-pty-tmp": "0.7.1",
|
"node-pty-tmp": "0.7.1",
|
||||||
|
@@ -11,8 +11,8 @@ export abstract class TerminalDecorator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface ResizeEvent {
|
export interface ResizeEvent {
|
||||||
width: number
|
columns: number
|
||||||
height: number
|
rows: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SessionOptions {
|
export interface SessionOptions {
|
||||||
|
@@ -1,24 +1,23 @@
|
|||||||
import * as fs from 'mz/fs'
|
import * as fs from 'mz/fs'
|
||||||
import * as path from 'path'
|
import * as path from 'path'
|
||||||
|
import { first } from 'rxjs/operators'
|
||||||
import { Injectable } from '@angular/core'
|
import { Injectable } from '@angular/core'
|
||||||
import { HotkeysService, ToolbarButtonProvider, IToolbarButton, ConfigService, HostAppService, ElectronService, Logger, LogService } from 'terminus-core'
|
import { DomSanitizer } from '@angular/platform-browser'
|
||||||
|
import { HotkeysService, ToolbarButtonProvider, IToolbarButton, ConfigService, HostAppService, ElectronService } from 'terminus-core'
|
||||||
|
|
||||||
import { TerminalService } from './services/terminal.service'
|
import { TerminalService } from './services/terminal.service'
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ButtonProvider extends ToolbarButtonProvider {
|
export class ButtonProvider extends ToolbarButtonProvider {
|
||||||
private logger: Logger
|
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
private terminal: TerminalService,
|
private terminal: TerminalService,
|
||||||
|
private domSanitizer: DomSanitizer,
|
||||||
private config: ConfigService,
|
private config: ConfigService,
|
||||||
log: LogService,
|
|
||||||
hostApp: HostAppService,
|
hostApp: HostAppService,
|
||||||
electron: ElectronService,
|
electron: ElectronService,
|
||||||
hotkeys: HotkeysService,
|
hotkeys: HotkeysService,
|
||||||
) {
|
) {
|
||||||
super()
|
super()
|
||||||
this.logger = log.create('newTerminalButton')
|
|
||||||
hotkeys.matchedHotkey.subscribe(async (hotkey) => {
|
hotkeys.matchedHotkey.subscribe(async (hotkey) => {
|
||||||
if (hotkey === 'new-tab') {
|
if (hotkey === 'new-tab') {
|
||||||
this.openNewTab()
|
this.openNewTab()
|
||||||
@@ -47,15 +46,16 @@ export class ButtonProvider extends ToolbarButtonProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async openNewTab (cwd?: string): Promise<void> {
|
async openNewTab (cwd?: string): Promise<void> {
|
||||||
let shells = await this.terminal.shells$.first().toPromise()
|
let shells = await this.terminal.shells$.pipe(first()).toPromise()
|
||||||
let shell = shells.find(x => x.id === this.config.store.terminal.shell)
|
let shell = shells.find(x => x.id === this.config.store.terminal.shell)
|
||||||
this.terminal.openTab(shell, cwd)
|
this.terminal.openTab(shell, cwd)
|
||||||
}
|
}
|
||||||
|
|
||||||
provide (): IToolbarButton[] {
|
provide (): IToolbarButton[] {
|
||||||
return [{
|
return [{
|
||||||
icon: 'plus',
|
icon: this.domSanitizer.bypassSecurityTrustHtml(require('./icons/plus.svg')),
|
||||||
title: 'New terminal',
|
title: 'New terminal',
|
||||||
|
touchBarTitle: 'New',
|
||||||
click: async () => {
|
click: async () => {
|
||||||
this.openNewTab()
|
this.openNewTab()
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,158 @@
|
|||||||
h3.mb-2 Appearance
|
h3.mb-3 Appearance
|
||||||
.row
|
.row
|
||||||
|
.col-md-6
|
||||||
|
.form-group
|
||||||
|
label Font
|
||||||
|
.row
|
||||||
|
.col-8
|
||||||
|
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
|
||||||
|
checkbox(
|
||||||
|
text='Enable font ligatures',
|
||||||
|
[(ngModel)]='config.store.terminal.ligatures',
|
||||||
|
(ngModelChange)='config.save()',
|
||||||
|
)
|
||||||
|
|
||||||
|
.form-group(*ngIf='!editingColorScheme')
|
||||||
|
label Color scheme
|
||||||
|
.input-group
|
||||||
|
select.form-control(
|
||||||
|
[compareWith]='equalComparator',
|
||||||
|
[(ngModel)]='config.store.terminal.colorScheme',
|
||||||
|
(ngModelChange)='config.save()',
|
||||||
|
)
|
||||||
|
option(*ngFor='let scheme of config.store.terminal.customColorSchemes', [ngValue]='scheme') Custom: {{scheme.name}}
|
||||||
|
option(*ngFor='let scheme of colorSchemes', [ngValue]='scheme') {{scheme.name}}
|
||||||
|
.input-group-btn
|
||||||
|
button.btn.btn-secondary((click)='editScheme(config.store.terminal.colorScheme)') Edit
|
||||||
|
.input-group-btn
|
||||||
|
button.btn.btn-outline-danger(
|
||||||
|
(click)='deleteScheme(config.store.terminal.colorScheme)',
|
||||||
|
*ngIf='isCustomScheme(config.store.terminal.colorScheme)'
|
||||||
|
)
|
||||||
|
i.fa.fa-trash-o
|
||||||
|
|
||||||
|
.form-group(*ngIf='editingColorScheme')
|
||||||
|
label Editing
|
||||||
|
.input-group
|
||||||
|
input.form-control(type='text', [(ngModel)]='editingColorScheme.name')
|
||||||
|
.input-group-btn
|
||||||
|
button.btn.btn-secondary((click)='saveScheme()') Save
|
||||||
|
.input-group-btn
|
||||||
|
button.btn.btn-secondary((click)='cancelEditing()') Cancel
|
||||||
|
|
||||||
|
|
||||||
|
.form-group(*ngIf='editingColorScheme')
|
||||||
|
color-picker(
|
||||||
|
'[(model)]'='editingColorScheme.foreground',
|
||||||
|
(modelChange)='config.save(); schemeChanged = true',
|
||||||
|
title='FG',
|
||||||
|
)
|
||||||
|
color-picker(
|
||||||
|
'[(model)]'='editingColorScheme.background',
|
||||||
|
(modelChange)='config.save(); schemeChanged = true',
|
||||||
|
title='BG',
|
||||||
|
)
|
||||||
|
color-picker(
|
||||||
|
'[(model)]'='editingColorScheme.cursor',
|
||||||
|
(modelChange)='config.save(); schemeChanged = true',
|
||||||
|
title='CU',
|
||||||
|
)
|
||||||
|
color-picker(
|
||||||
|
*ngFor='let _ of editingColorScheme.colors; let idx = index; trackBy: colorsTrackBy',
|
||||||
|
'[(model)]'='editingColorScheme.colors[idx]',
|
||||||
|
(modelChange)='config.save(); schemeChanged = true',
|
||||||
|
[title]='idx',
|
||||||
|
)
|
||||||
|
|
||||||
|
.form-group
|
||||||
|
label Terminal background
|
||||||
|
br
|
||||||
|
.btn-group(
|
||||||
|
[(ngModel)]='config.store.terminal.background',
|
||||||
|
(ngModelChange)='config.save()',
|
||||||
|
ngbRadioGroup
|
||||||
|
)
|
||||||
|
label.btn.btn-secondary(ngbButtonLabel)
|
||||||
|
input(
|
||||||
|
type='radio',
|
||||||
|
ngbButton,
|
||||||
|
[value]='"theme"'
|
||||||
|
)
|
||||||
|
| From theme
|
||||||
|
label.btn.btn-secondary(ngbButtonLabel)
|
||||||
|
input(
|
||||||
|
type='radio',
|
||||||
|
ngbButton,
|
||||||
|
[value]='"colorScheme"'
|
||||||
|
)
|
||||||
|
| 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(
|
||||||
@@ -7,6 +160,8 @@ h3.mb-2 Appearance
|
|||||||
[style.font-size]='config.store.terminal.fontSize + "px"',
|
[style.font-size]='config.store.terminal.fontSize + "px"',
|
||||||
[style.background-color]='(config.store.terminal.background == "theme") ? null : config.store.terminal.colorScheme.background',
|
[style.background-color]='(config.store.terminal.background == "theme") ? null : config.store.terminal.colorScheme.background',
|
||||||
[style.color]='config.store.terminal.colorScheme.foreground',
|
[style.color]='config.store.terminal.colorScheme.foreground',
|
||||||
|
[style.font-feature-settings]='\'"liga" \' + config.store.terminal.ligatures ? 1 : 0',
|
||||||
|
[style.font-variant-ligatures]='config.store.terminal.ligatures ? "initial" : "none"',
|
||||||
)
|
)
|
||||||
div
|
div
|
||||||
span([style.background-color]='config.store.terminal.colorScheme.colors[0]')
|
span([style.background-color]='config.store.terminal.colorScheme.colors[0]')
|
||||||
@@ -85,141 +240,13 @@ h3.mb-2 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')
|
||||||
|
|
||||||
|
h3.mt-3.mb-3 Shell
|
||||||
|
|
||||||
.col-md-6
|
|
||||||
.form-group
|
|
||||||
label Font
|
|
||||||
.row
|
|
||||||
.col-8
|
|
||||||
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()',
|
|
||||||
)
|
|
||||||
small.form-text.text-muted Font to be used in the terminal
|
|
||||||
|
|
||||||
.form-group(*ngIf='!editingColorScheme')
|
|
||||||
label Color scheme
|
|
||||||
.input-group
|
|
||||||
select.form-control(
|
|
||||||
[compareWith]='equalComparator',
|
|
||||||
'[(ngModel)]'='config.store.terminal.colorScheme',
|
|
||||||
(ngModelChange)='config.save()',
|
|
||||||
)
|
|
||||||
option(*ngFor='let scheme of config.store.terminal.customColorSchemes', [ngValue]='scheme') Custom: {{scheme.name}}
|
|
||||||
option(*ngFor='let scheme of colorSchemes', [ngValue]='scheme') {{scheme.name}}
|
|
||||||
.input-group-btn
|
|
||||||
button.btn.btn-secondary((click)='editScheme(config.store.terminal.colorScheme)') Edit
|
|
||||||
.input-group-btn
|
|
||||||
button.btn.btn-outline-danger(
|
|
||||||
(click)='deleteScheme(config.store.terminal.colorScheme)',
|
|
||||||
*ngIf='isCustomScheme(config.store.terminal.colorScheme)'
|
|
||||||
)
|
|
||||||
i.fa.fa-trash-o
|
|
||||||
|
|
||||||
.form-group(*ngIf='editingColorScheme')
|
|
||||||
label Editing
|
|
||||||
.input-group
|
|
||||||
input.form-control(type='text', '[(ngModel)]'='editingColorScheme.name')
|
|
||||||
.input-group-btn
|
|
||||||
button.btn.btn-secondary((click)='saveScheme()') Save
|
|
||||||
.input-group-btn
|
|
||||||
button.btn.btn-secondary((click)='cancelEditing()') Cancel
|
|
||||||
|
|
||||||
|
|
||||||
.form-group(*ngIf='editingColorScheme')
|
|
||||||
color-picker(
|
|
||||||
'[(model)]'='editingColorScheme.foreground',
|
|
||||||
(modelChange)='config.save(); schemeChanged = true',
|
|
||||||
title='FG',
|
|
||||||
)
|
|
||||||
color-picker(
|
|
||||||
'[(model)]'='editingColorScheme.background',
|
|
||||||
(modelChange)='config.save(); schemeChanged = true',
|
|
||||||
title='BG',
|
|
||||||
)
|
|
||||||
color-picker(
|
|
||||||
'[(model)]'='editingColorScheme.cursor',
|
|
||||||
(modelChange)='config.save(); schemeChanged = true',
|
|
||||||
title='CU',
|
|
||||||
)
|
|
||||||
color-picker(
|
|
||||||
*ngFor='let _ of editingColorScheme.colors; let idx = index; trackBy: colorsTrackBy',
|
|
||||||
'[(model)]'='editingColorScheme.colors[idx]',
|
|
||||||
(modelChange)='config.save(); schemeChanged = true',
|
|
||||||
[title]='idx',
|
|
||||||
)
|
|
||||||
|
|
||||||
.d-flex
|
|
||||||
.form-group.mr-3
|
|
||||||
label Terminal background
|
|
||||||
br
|
|
||||||
.btn-group(
|
|
||||||
'[(ngModel)]'='config.store.terminal.background',
|
|
||||||
(ngModelChange)='config.save()',
|
|
||||||
ngbRadioGroup
|
|
||||||
)
|
|
||||||
label.btn.btn-secondary(ngbButtonLabel)
|
|
||||||
input(
|
|
||||||
type='radio',
|
|
||||||
ngbButton,
|
|
||||||
[value]='"theme"'
|
|
||||||
)
|
|
||||||
| From theme
|
|
||||||
label.btn.btn-secondary(ngbButtonLabel)
|
|
||||||
input(
|
|
||||||
type='radio',
|
|
||||||
ngbButton,
|
|
||||||
[value]='"colorScheme"'
|
|
||||||
)
|
|
||||||
| From colors
|
|
||||||
|
|
||||||
.form-group
|
|
||||||
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"'
|
|
||||||
)
|
|
||||||
| ▁
|
|
||||||
|
|
||||||
h3.mt-2.mb-2 Behaviour
|
|
||||||
|
|
||||||
.row
|
|
||||||
.col-md-6
|
|
||||||
.d-flex
|
.d-flex
|
||||||
.form-group.mr-3
|
.form-group.mr-3
|
||||||
label Shell
|
label Shell
|
||||||
select.form-control(
|
select.form-control(
|
||||||
'[(ngModel)]'='config.store.terminal.shell',
|
[(ngModel)]='config.store.terminal.shell',
|
||||||
(ngModelChange)='config.save()',
|
(ngModelChange)='config.save()',
|
||||||
)
|
)
|
||||||
option(
|
option(
|
||||||
@@ -227,10 +254,10 @@ h3.mt-2.mb-2 Behaviour
|
|||||||
[ngValue]='shell.id'
|
[ngValue]='shell.id'
|
||||||
) {{shell.name}}
|
) {{shell.name}}
|
||||||
|
|
||||||
.form-group
|
.form-group.mr-3(*ngIf='persistenceProviders.length > 0')
|
||||||
label Session persistence
|
label Session persistence
|
||||||
select.form-control(
|
select.form-control(
|
||||||
'[(ngModel)]'='config.store.terminal.persistence',
|
[(ngModel)]='config.store.terminal.persistence',
|
||||||
(ngModelChange)='config.save()',
|
(ngModelChange)='config.save()',
|
||||||
)
|
)
|
||||||
option([ngValue]='null') Off
|
option([ngValue]='null') Off
|
||||||
@@ -239,53 +266,31 @@ h3.mt-2.mb-2 Behaviour
|
|||||||
[ngValue]='provider.id'
|
[ngValue]='provider.id'
|
||||||
) {{provider.displayName}}
|
) {{provider.displayName}}
|
||||||
|
|
||||||
.form-group(*ngIf='config.store.terminal.shell == "custom"')
|
|
||||||
label Custom shell
|
|
||||||
input.form-control(
|
|
||||||
type='text',
|
|
||||||
'[(ngModel)]'='config.store.terminal.customShell',
|
|
||||||
(ngModelChange)='config.save()',
|
|
||||||
)
|
|
||||||
|
|
||||||
.form-group
|
.form-group
|
||||||
label Working directory
|
label Working directory
|
||||||
input.form-control(
|
input.form-control(
|
||||||
type='text',
|
type='text',
|
||||||
placeholder='Home directory',
|
placeholder='Home directory',
|
||||||
'[(ngModel)]'='config.store.terminal.workingDirectory',
|
[(ngModel)]='config.store.terminal.workingDirectory',
|
||||||
(ngModelChange)='config.save()',
|
(ngModelChange)='config.save()',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
.form-group(*ngIf='config.store.terminal.shell == "custom"')
|
||||||
|
label Custom shell
|
||||||
|
input.form-control(
|
||||||
|
type='text',
|
||||||
|
[(ngModel)]='config.store.terminal.customShell',
|
||||||
|
(ngModelChange)='config.save()',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
h3.mt-3.mb-3 Behaviour
|
||||||
|
|
||||||
.form-group
|
.form-group
|
||||||
label Auto-open a terminal on app start
|
|
||||||
br
|
|
||||||
.btn-group(
|
|
||||||
'[(ngModel)]'='config.store.terminal.autoOpen',
|
|
||||||
(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
|
|
||||||
.d-flex
|
|
||||||
.form-group.mr-3
|
|
||||||
label Terminal bell
|
label Terminal bell
|
||||||
br
|
br
|
||||||
.btn-group(
|
.btn-group(
|
||||||
'[(ngModel)]'='config.store.terminal.bell',
|
[(ngModel)]='config.store.terminal.bell',
|
||||||
(ngModelChange)='config.save()',
|
(ngModelChange)='config.save()',
|
||||||
ngbRadioGroup
|
ngbRadioGroup
|
||||||
)
|
)
|
||||||
@@ -312,57 +317,10 @@ h3.mt-2.mb-2 Behaviour
|
|||||||
| Audible
|
| Audible
|
||||||
|
|
||||||
.form-group
|
.form-group
|
||||||
label Blink cursor
|
label Right click
|
||||||
br
|
br
|
||||||
.btn-group(
|
.btn-group(
|
||||||
'[(ngModel)]'='config.store.terminal.cursorBlink',
|
[(ngModel)]='config.store.terminal.rightClick',
|
||||||
(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
|
|
||||||
|
|
||||||
.d-flex
|
|
||||||
.form-group.mr-3
|
|
||||||
label Copy on select
|
|
||||||
br
|
|
||||||
.btn-group(
|
|
||||||
'[(ngModel)]'='config.store.terminal.copyOnSelect',
|
|
||||||
(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
|
|
||||||
|
|
||||||
.form-group
|
|
||||||
label Right click behaviour
|
|
||||||
br
|
|
||||||
.btn-group(
|
|
||||||
'[(ngModel)]'='config.store.terminal.rightClick',
|
|
||||||
(ngModelChange)='config.save()',
|
(ngModelChange)='config.save()',
|
||||||
ngbRadioGroup
|
ngbRadioGroup
|
||||||
)
|
)
|
||||||
@@ -372,7 +330,7 @@ h3.mt-2.mb-2 Behaviour
|
|||||||
ngbButton,
|
ngbButton,
|
||||||
value='menu'
|
value='menu'
|
||||||
)
|
)
|
||||||
| Menu
|
| Context menu
|
||||||
label.btn.btn-secondary(ngbButtonLabel)
|
label.btn.btn-secondary(ngbButtonLabel)
|
||||||
input(
|
input(
|
||||||
type='radio',
|
type='radio',
|
||||||
@@ -380,3 +338,29 @@ h3.mt-2.mb-2 Behaviour
|
|||||||
value='paste'
|
value='paste'
|
||||||
)
|
)
|
||||||
| Paste
|
| Paste
|
||||||
|
|
||||||
|
|
||||||
|
.form-group
|
||||||
|
checkbox(
|
||||||
|
[(ngModel)]='config.store.terminal.autoOpen',
|
||||||
|
(ngModelChange)='config.save()',
|
||||||
|
text='Auto-open a terminal on app start',
|
||||||
|
)
|
||||||
|
|
||||||
|
checkbox(
|
||||||
|
[(ngModel)]='config.store.terminal.bracketedPaste',
|
||||||
|
(ngModelChange)='config.save()',
|
||||||
|
text='Bracketed paste (requires shell support)',
|
||||||
|
)
|
||||||
|
|
||||||
|
checkbox(
|
||||||
|
[(ngModel)]='config.store.terminal.copyOnSelect',
|
||||||
|
(ngModelChange)='config.save()',
|
||||||
|
text='Copy on select',
|
||||||
|
)
|
||||||
|
|
||||||
|
checkbox(
|
||||||
|
[(ngModel)]='config.store.terminal.altIsMeta',
|
||||||
|
(ngModelChange)='config.save()',
|
||||||
|
text='Use Alt key as the Meta key',
|
||||||
|
)
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
.appearance-preview {
|
.appearance-preview {
|
||||||
padding: 10px 20px;
|
padding: 10px 0;
|
||||||
|
margin-left: 30px;
|
||||||
margin: 0 0 10px;
|
margin: 0 0 10px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
span {
|
span {
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
import { Observable } from 'rxjs'
|
import { Observable } from 'rxjs'
|
||||||
|
import { debounceTime, distinctUntilChanged, map } from 'rxjs/operators'
|
||||||
import { exec } from 'mz/child_process'
|
import { exec } from 'mz/child_process'
|
||||||
const equal = require('deep-equal')
|
const equal = require('deep-equal')
|
||||||
const fontManager = require('font-manager')
|
const fontManager = require('font-manager')
|
||||||
@@ -51,11 +52,12 @@ export class TerminalSettingsTabComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fontAutocomplete = (text$: Observable<string>) => {
|
fontAutocomplete = (text$: Observable<string>) => {
|
||||||
return text$
|
return text$.pipe(
|
||||||
.debounceTime(200)
|
debounceTime(200),
|
||||||
.distinctUntilChanged()
|
distinctUntilChanged(),
|
||||||
.map(query => this.fonts.filter(v => new RegExp(query, 'gi').test(v)))
|
map(query => this.fonts.filter(v => new RegExp(query, 'gi').test(v))),
|
||||||
.map(list => Array.from(new Set(list)))
|
map(list => Array.from(new Set(list))),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
editScheme (scheme: ITerminalColorScheme) {
|
editScheme (scheme: ITerminalColorScheme) {
|
||||||
|
@@ -1,14 +1,17 @@
|
|||||||
import { BehaviorSubject, Subject, Subscription } from 'rxjs'
|
import { Observable, Subject, Subscription } from 'rxjs'
|
||||||
import 'rxjs/add/operator/bufferTime'
|
import { first } from 'rxjs/operators'
|
||||||
|
import { ToastrService } from 'ngx-toastr'
|
||||||
import { Component, NgZone, Inject, Optional, ViewChild, HostBinding, Input } from '@angular/core'
|
import { Component, NgZone, Inject, Optional, ViewChild, HostBinding, Input } from '@angular/core'
|
||||||
import { AppService, ConfigService, BaseTabComponent, ElectronService, ThemesService, HostAppService, HotkeysService, Platform } from 'terminus-core'
|
import { AppService, ConfigService, BaseTabComponent, ElectronService, HostAppService, HotkeysService, Platform } from 'terminus-core'
|
||||||
|
|
||||||
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 { TerminalDecorator, ResizeEvent, SessionOptions } from '../api'
|
import { TerminalDecorator, ResizeEvent, SessionOptions } from '../api'
|
||||||
import { hterm, preferenceManager } from '../hterm'
|
import { TermContainer } from '../terminalContainers/termContainer'
|
||||||
|
import { hterm } from '../hterm'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'terminalTab',
|
selector: 'terminalTab',
|
||||||
@@ -27,73 +30,63 @@ 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
|
||||||
hterm: any
|
termContainer: TermContainer
|
||||||
sessionCloseSubscription: Subscription
|
sessionCloseSubscription: Subscription
|
||||||
hotkeysSubscription: Subscription
|
hotkeysSubscription: Subscription
|
||||||
bell$ = new Subject()
|
|
||||||
size: ResizeEvent
|
|
||||||
resize$ = new Subject<ResizeEvent>()
|
|
||||||
input$ = new Subject<string>()
|
|
||||||
output$ = new Subject<string>()
|
|
||||||
contentUpdated$ = new Subject<void>()
|
|
||||||
alternateScreenActive$ = new BehaviorSubject(false)
|
|
||||||
mouseEvent$ = new Subject<Event>()
|
|
||||||
htermVisible = false
|
htermVisible = false
|
||||||
shell: IShell
|
shell: IShell
|
||||||
|
private output = new Subject<string>()
|
||||||
private bellPlayer: HTMLAudioElement
|
private bellPlayer: HTMLAudioElement
|
||||||
private io: any
|
|
||||||
private contextMenu: any
|
private contextMenu: any
|
||||||
|
private termContainerSubscriptions: Subscription[] = []
|
||||||
|
|
||||||
|
get input$ (): Observable<string> { return this.termContainer.input$ }
|
||||||
|
get output$ (): Observable<string> { return this.output }
|
||||||
|
get resize$ (): Observable<ResizeEvent> { return this.termContainer.resize$ }
|
||||||
|
get alternateScreenActive$ (): Observable<boolean> { return this.termContainer.alternateScreenActive$ }
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
private zone: NgZone,
|
private zone: NgZone,
|
||||||
private app: AppService,
|
private app: AppService,
|
||||||
private themes: ThemesService,
|
|
||||||
private hostApp: HostAppService,
|
private hostApp: HostAppService,
|
||||||
private hotkeys: HotkeysService,
|
private hotkeys: HotkeysService,
|
||||||
private sessions: SessionsService,
|
private sessions: SessionsService,
|
||||||
private electron: ElectronService,
|
private electron: ElectronService,
|
||||||
private terminalService: TerminalService,
|
private terminalService: TerminalService,
|
||||||
|
private terminalContainersService: TerminalContainersService,
|
||||||
public config: ConfigService,
|
public config: ConfigService,
|
||||||
|
private toastr: ToastrService,
|
||||||
@Optional() @Inject(TerminalDecorator) private decorators: TerminalDecorator[],
|
@Optional() @Inject(TerminalDecorator) private decorators: TerminalDecorator[],
|
||||||
) {
|
) {
|
||||||
super()
|
super()
|
||||||
this.decorators = this.decorators || []
|
this.decorators = this.decorators || []
|
||||||
this.title = 'Terminal'
|
this.setTitle('Terminal')
|
||||||
this.resize$.first().subscribe(async (resizeEvent) => {
|
|
||||||
if (!this.session) {
|
|
||||||
this.session = this.sessions.addSession(
|
|
||||||
Object.assign({}, this.sessionOptions, resizeEvent)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
setTimeout(() => {
|
this.session = new Session()
|
||||||
this.session.resize(resizeEvent.width, resizeEvent.height)
|
|
||||||
}, 1000)
|
|
||||||
|
|
||||||
// this.session.output$.bufferTime(10).subscribe((datas) => {
|
|
||||||
this.session.output$.subscribe(data => {
|
|
||||||
this.zone.run(() => {
|
|
||||||
this.output$.next(data)
|
|
||||||
this.write(data)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
this.sessionCloseSubscription = this.session.closed$.subscribe(() => {
|
|
||||||
this.app.closeTab(this)
|
|
||||||
})
|
|
||||||
|
|
||||||
this.session.releaseInitialDataBuffer()
|
|
||||||
})
|
|
||||||
this.hotkeysSubscription = this.hotkeys.matchedHotkey.subscribe(hotkey => {
|
this.hotkeysSubscription = this.hotkeys.matchedHotkey.subscribe(hotkey => {
|
||||||
if (!this.hasFocus) {
|
if (!this.hasFocus) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
switch (hotkey) {
|
switch (hotkey) {
|
||||||
|
case 'ctrl-c':
|
||||||
|
if (this.termContainer.getSelection()) {
|
||||||
|
this.termContainer.copySelection()
|
||||||
|
this.termContainer.clearSelection()
|
||||||
|
this.toastr.info('Copied')
|
||||||
|
} else {
|
||||||
|
this.sendInput('\x03')
|
||||||
|
}
|
||||||
|
break
|
||||||
case 'copy':
|
case 'copy':
|
||||||
this.hterm.copySelectionToClipboard()
|
this.termContainer.copySelection()
|
||||||
|
this.toastr.info('Copied')
|
||||||
|
break
|
||||||
|
case 'paste':
|
||||||
|
this.paste()
|
||||||
break
|
break
|
||||||
case 'clear':
|
case 'clear':
|
||||||
this.clear()
|
this.termContainer.clear()
|
||||||
break
|
break
|
||||||
case 'zoom-in':
|
case 'zoom-in':
|
||||||
this.zoomIn()
|
this.zoomIn()
|
||||||
@@ -128,6 +121,29 @@ export class TerminalTabComponent extends BaseTabComponent {
|
|||||||
this.bellPlayer.src = require<string>('../bell.ogg')
|
this.bellPlayer.src = require<string>('../bell.ogg')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
initializeSession (columns: number, rows: number) {
|
||||||
|
this.sessions.addSession(
|
||||||
|
this.session,
|
||||||
|
Object.assign({}, this.sessionOptions, {
|
||||||
|
width: columns,
|
||||||
|
height: rows,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
// this.session.output$.bufferTime(10).subscribe((datas) => {
|
||||||
|
this.session.output$.subscribe(data => {
|
||||||
|
this.zone.run(() => {
|
||||||
|
this.output.next(data)
|
||||||
|
this.write(data)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
this.sessionCloseSubscription = this.session.closed$.subscribe(() => {
|
||||||
|
this.termContainer.destroy()
|
||||||
|
this.app.closeTab(this)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
getRecoveryToken (): any {
|
getRecoveryToken (): any {
|
||||||
return {
|
return {
|
||||||
type: 'app:terminal',
|
type: 'app:terminal',
|
||||||
@@ -138,46 +154,49 @@ export class TerminalTabComponent extends BaseTabComponent {
|
|||||||
ngOnInit () {
|
ngOnInit () {
|
||||||
this.focused$.subscribe(() => {
|
this.focused$.subscribe(() => {
|
||||||
this.configure()
|
this.configure()
|
||||||
setTimeout(() => {
|
this.termContainer.focus()
|
||||||
this.hterm.scrollPort_.resize()
|
|
||||||
this.hterm.scrollPort_.focus()
|
|
||||||
}, 100)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
this.hterm = new hterm.hterm.Terminal()
|
this.termContainer = this.terminalContainersService.getContainer(this.session)
|
||||||
|
|
||||||
|
this.termContainer.ready$.subscribe(() => {
|
||||||
|
this.htermVisible = true
|
||||||
|
})
|
||||||
|
|
||||||
|
this.termContainer.resize$.pipe(first()).subscribe(async ({columns, rows}) => {
|
||||||
|
if (!this.session.open) {
|
||||||
|
this.initializeSession(columns, rows)
|
||||||
|
}
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
this.session.resize(columns, rows)
|
||||||
|
}, 1000)
|
||||||
|
|
||||||
|
this.session.releaseInitialDataBuffer()
|
||||||
|
})
|
||||||
|
|
||||||
|
this.termContainer.attach(this.content.nativeElement)
|
||||||
|
this.attachTermContainerHandlers()
|
||||||
|
|
||||||
|
this.configure()
|
||||||
|
|
||||||
this.config.enabledServices(this.decorators).forEach((decorator) => {
|
this.config.enabledServices(this.decorators).forEach((decorator) => {
|
||||||
decorator.attach(this)
|
decorator.attach(this)
|
||||||
})
|
})
|
||||||
|
|
||||||
this.attachHTermHandlers(this.hterm)
|
|
||||||
|
|
||||||
this.hterm.onTerminalReady = () => {
|
|
||||||
this.htermVisible = true
|
|
||||||
this.hterm.installKeyboard()
|
|
||||||
this.hterm.scrollPort_.setCtrlVPaste(true)
|
|
||||||
this.io = this.hterm.io.push()
|
|
||||||
this.attachIOHandlers(this.io)
|
|
||||||
}
|
|
||||||
this.hterm.decorate(this.content.nativeElement)
|
|
||||||
this.configure()
|
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.output$.subscribe(() => {
|
this.output.subscribe(() => {
|
||||||
this.displayActivity()
|
this.displayActivity()
|
||||||
})
|
})
|
||||||
}, 1000)
|
}, 1000)
|
||||||
|
|
||||||
this.bell$.subscribe(() => {
|
this.termContainer.bell$.subscribe(() => {
|
||||||
if (this.config.store.terminal.bell === 'visual') {
|
if (this.config.store.terminal.bell === 'visual') {
|
||||||
preferenceManager.set('background-color', 'rgba(128,128,128,.25)')
|
this.termContainer.visualBell()
|
||||||
setTimeout(() => {
|
|
||||||
this.configure()
|
|
||||||
}, 125)
|
|
||||||
}
|
}
|
||||||
if (this.config.store.terminal.bell === 'audible') {
|
if (this.config.store.terminal.bell === 'audible') {
|
||||||
this.bellPlayer.play()
|
this.bellPlayer.play()
|
||||||
}
|
}
|
||||||
// TODO audible
|
|
||||||
})
|
})
|
||||||
|
|
||||||
this.contextMenu = this.electron.remote.Menu.buildFromTemplate([
|
this.contextMenu = this.electron.remote.Menu.buildFromTemplate([
|
||||||
@@ -194,7 +213,8 @@ export class TerminalTabComponent extends BaseTabComponent {
|
|||||||
click: () => {
|
click: () => {
|
||||||
this.zone.run(() => {
|
this.zone.run(() => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.hterm.copySelectionToClipboard()
|
this.termContainer.copySelection()
|
||||||
|
this.toastr.info('Copied')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -210,48 +230,26 @@ export class TerminalTabComponent extends BaseTabComponent {
|
|||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
attachHTermHandlers (hterm: any) {
|
detachTermContainerHandlers () {
|
||||||
hterm.setWindowTitle = (title) => {
|
for (let subscription of this.termContainerSubscriptions) {
|
||||||
this.zone.run(() => {
|
subscription.unsubscribe()
|
||||||
this.title = title
|
}
|
||||||
})
|
this.termContainerSubscriptions = []
|
||||||
}
|
}
|
||||||
|
|
||||||
const _setAlternateMode = hterm.setAlternateMode.bind(hterm)
|
attachTermContainerHandlers () {
|
||||||
hterm.setAlternateMode = (state) => {
|
this.detachTermContainerHandlers()
|
||||||
_setAlternateMode(state)
|
this.termContainerSubscriptions = [
|
||||||
this.alternateScreenActive$.next(state)
|
this.termContainer.title$.subscribe(title => this.zone.run(() => this.setTitle(title))),
|
||||||
}
|
|
||||||
|
|
||||||
hterm.primaryScreen_.syncSelectionCaret = () => null
|
this.focused$.subscribe(() => this.termContainer.enableResizing = true),
|
||||||
hterm.alternateScreen_.syncSelectionCaret = () => null
|
this.blurred$.subscribe(() => this.termContainer.enableResizing = false),
|
||||||
hterm.primaryScreen_.terminal = hterm
|
|
||||||
hterm.alternateScreen_.terminal = hterm
|
|
||||||
|
|
||||||
const _onPaste = hterm.scrollPort_.onPaste_.bind(hterm.scrollPort_)
|
this.termContainer.mouseEvent$.subscribe(event => {
|
||||||
hterm.scrollPort_.onPaste_ = (event) => {
|
|
||||||
hterm.scrollPort_.pasteTarget_.value = event.clipboardData.getData('text/plain').trim()
|
|
||||||
_onPaste()
|
|
||||||
event.preventDefault()
|
|
||||||
}
|
|
||||||
|
|
||||||
const _resize = hterm.scrollPort_.resize.bind(hterm.scrollPort_)
|
|
||||||
hterm.scrollPort_.resize = () => {
|
|
||||||
if (!this.hasFocus) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
_resize()
|
|
||||||
}
|
|
||||||
|
|
||||||
const _onMouse = hterm.onMouse_.bind(hterm)
|
|
||||||
hterm.onMouse_ = (event) => {
|
|
||||||
this.mouseEvent$.next(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.contextMenu.popup({
|
||||||
x: event.pageX + this.content.nativeElement.getBoundingClientRect().left,
|
|
||||||
y: event.pageY + this.content.nativeElement.getBoundingClientRect().top,
|
|
||||||
async: true,
|
async: true,
|
||||||
})
|
})
|
||||||
} else if (this.config.store.terminal.rightClick === 'paste') {
|
} else if (this.config.store.terminal.rightClick === 'paste') {
|
||||||
@@ -264,64 +262,32 @@ export class TerminalTabComponent extends BaseTabComponent {
|
|||||||
}
|
}
|
||||||
if (event.type === 'mousewheel') {
|
if (event.type === 'mousewheel') {
|
||||||
if (event.ctrlKey || event.metaKey) {
|
if (event.ctrlKey || event.metaKey) {
|
||||||
if (event.wheelDeltaY > 0) {
|
if ((event as MouseWheelEvent).wheelDeltaY > 0) {
|
||||||
this.zoomIn()
|
this.zoomIn()
|
||||||
} else {
|
} else {
|
||||||
this.zoomOut()
|
this.zoomOut()
|
||||||
}
|
}
|
||||||
} else if (event.altKey) {
|
} else if (event.altKey) {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
let delta = Math.round(event.wheelDeltaY / 50)
|
let delta = Math.round((event as MouseWheelEvent).wheelDeltaY / 50)
|
||||||
this.sendInput(((delta > 0) ? '\u001bOA' : '\u001bOB').repeat(Math.abs(delta)))
|
this.sendInput(((delta > 0) ? '\u001bOA' : '\u001bOB').repeat(Math.abs(delta)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_onMouse(event)
|
}),
|
||||||
}
|
|
||||||
|
|
||||||
hterm.ringBell = () => {
|
this.termContainer.input$.subscribe(data => {
|
||||||
this.bell$.next()
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let screen of [hterm.primaryScreen_, hterm.alternateScreen_]) {
|
|
||||||
const _insertString = screen.insertString.bind(screen)
|
|
||||||
screen.insertString = (data) => {
|
|
||||||
_insertString(data)
|
|
||||||
this.contentUpdated$.next()
|
|
||||||
}
|
|
||||||
|
|
||||||
const _deleteChars = screen.deleteChars.bind(screen)
|
|
||||||
screen.deleteChars = (count) => {
|
|
||||||
let ret = _deleteChars(count)
|
|
||||||
this.contentUpdated$.next()
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const _measureCharacterSize = hterm.scrollPort_.measureCharacterSize.bind(hterm.scrollPort_)
|
|
||||||
hterm.scrollPort_.measureCharacterSize = () => {
|
|
||||||
let size = _measureCharacterSize()
|
|
||||||
size.height += this.config.store.terminal.linePadding
|
|
||||||
return size
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
attachIOHandlers (io: any) {
|
|
||||||
io.onVTKeystroke = io.sendString = (data) => {
|
|
||||||
this.sendInput(data)
|
this.sendInput(data)
|
||||||
|
}),
|
||||||
|
|
||||||
|
this.termContainer.resize$.subscribe(({columns, rows}) => {
|
||||||
|
console.log(`Resizing to ${columns}x${rows}`)
|
||||||
this.zone.run(() => {
|
this.zone.run(() => {
|
||||||
this.input$.next(data)
|
if (this.session.open) {
|
||||||
})
|
|
||||||
}
|
|
||||||
io.onTerminalResize = (columns, rows) => {
|
|
||||||
// console.log(`Resizing to ${columns}x${rows}`)
|
|
||||||
this.zone.run(() => {
|
|
||||||
this.size = { width: columns, height: rows }
|
|
||||||
if (this.session) {
|
|
||||||
this.session.resize(columns, rows)
|
this.session.resize(columns, rows)
|
||||||
}
|
}
|
||||||
this.resize$.next(this.size)
|
|
||||||
})
|
})
|
||||||
}
|
})
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
sendInput (data: string) {
|
sendInput (data: string) {
|
||||||
@@ -329,103 +295,48 @@ export class TerminalTabComponent extends BaseTabComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
write (data: string) {
|
write (data: string) {
|
||||||
this.io.writeUTF8(data)
|
this.termContainer.write(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
paste () {
|
paste () {
|
||||||
this.sendInput(this.electron.clipboard.readText())
|
let data = this.electron.clipboard.readText()
|
||||||
|
data = hterm.lib.encodeUTF8(data)
|
||||||
|
if (this.config.store.terminal.bracketedPaste) {
|
||||||
|
data = '\x1b[200~' + data + '\x1b[201~'
|
||||||
}
|
}
|
||||||
|
data = data.replace(/\n/g, '\r')
|
||||||
clear () {
|
this.sendInput(data)
|
||||||
this.hterm.wipeContents()
|
|
||||||
this.hterm.onVTKeystroke('\f')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
configure (): void {
|
configure (): void {
|
||||||
let config = this.config.store
|
this.termContainer.configure(this.config.store)
|
||||||
preferenceManager.set('font-family', `"${config.terminal.font}", "monospace-fallback", monospace`)
|
|
||||||
this.setFontSize()
|
|
||||||
preferenceManager.set('enable-bold', true)
|
|
||||||
// preferenceManager.set('audible-bell-sound', '')
|
|
||||||
preferenceManager.set('desktop-notification-bell', config.terminal.bell === 'notification')
|
|
||||||
preferenceManager.set('enable-clipboard-notice', false)
|
|
||||||
preferenceManager.set('receive-encoding', 'raw')
|
|
||||||
preferenceManager.set('send-encoding', 'raw')
|
|
||||||
preferenceManager.set('ctrl-plus-minus-zero-zoom', false)
|
|
||||||
preferenceManager.set('scrollbar-visible', this.hostApp.platform === Platform.macOS)
|
|
||||||
preferenceManager.set('copy-on-select', config.terminal.copyOnSelect)
|
|
||||||
preferenceManager.set('alt-sends-what', 'browser-key')
|
|
||||||
preferenceManager.set('alt-gr-mode', 'ctrl-alt')
|
|
||||||
preferenceManager.set('pass-alt-number', true)
|
|
||||||
preferenceManager.set('cursor-blink', config.terminal.cursorBlink)
|
|
||||||
|
|
||||||
if (config.terminal.colorScheme.foreground) {
|
if (this.config.store.terminal.background === 'colorScheme') {
|
||||||
preferenceManager.set('foreground-color', config.terminal.colorScheme.foreground)
|
if (this.config.store.terminal.colorScheme.background) {
|
||||||
}
|
this.backgroundColor = this.config.store.terminal.colorScheme.background
|
||||||
if (config.terminal.background === 'colorScheme') {
|
|
||||||
if (config.terminal.colorScheme.background) {
|
|
||||||
this.backgroundColor = config.terminal.colorScheme.background
|
|
||||||
preferenceManager.set('background-color', config.terminal.colorScheme.background)
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.backgroundColor = null
|
this.backgroundColor = null
|
||||||
// hterm can't parse "transparent"
|
|
||||||
preferenceManager.set('background-color', this.themes.findCurrentTheme().terminalBackground)
|
|
||||||
}
|
|
||||||
if (config.terminal.colorScheme.colors) {
|
|
||||||
preferenceManager.set('color-palette-overrides', config.terminal.colorScheme.colors)
|
|
||||||
}
|
|
||||||
if (config.terminal.colorScheme.cursor) {
|
|
||||||
preferenceManager.set('cursor-color', config.terminal.colorScheme.cursor)
|
|
||||||
}
|
|
||||||
|
|
||||||
let css = require('../hterm.userCSS.scss')
|
|
||||||
if (!config.terminal.ligatures) {
|
|
||||||
css += `
|
|
||||||
* {
|
|
||||||
font-feature-settings: "liga" 0;
|
|
||||||
font-variant-ligatures: none;
|
|
||||||
}
|
|
||||||
`
|
|
||||||
} else {
|
|
||||||
css += `
|
|
||||||
* {
|
|
||||||
font-feature-settings: "liga" 1;
|
|
||||||
font-variant-ligatures: initial;
|
|
||||||
}
|
|
||||||
`
|
|
||||||
}
|
|
||||||
css += config.appearance.css
|
|
||||||
this.hterm.setCSS(css)
|
|
||||||
this.hterm.setBracketedPaste(config.terminal.bracketedPaste)
|
|
||||||
this.hterm.defaultCursorShape = {
|
|
||||||
block: hterm.hterm.Terminal.cursorShape.BLOCK,
|
|
||||||
underline: hterm.hterm.Terminal.cursorShape.UNDERLINE,
|
|
||||||
beam: hterm.hterm.Terminal.cursorShape.BEAM,
|
|
||||||
}[config.terminal.cursor]
|
|
||||||
this.hterm.applyCursorShape()
|
|
||||||
this.hterm.setCursorBlink(config.terminal.cursorBlink)
|
|
||||||
if (config.terminal.cursorBlink) {
|
|
||||||
this.hterm.onCursorBlink_()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
zoomIn () {
|
zoomIn () {
|
||||||
this.zoom++
|
this.zoom++
|
||||||
this.setFontSize()
|
this.termContainer.setZoom(this.zoom)
|
||||||
}
|
}
|
||||||
|
|
||||||
zoomOut () {
|
zoomOut () {
|
||||||
this.zoom--
|
this.zoom--
|
||||||
this.setFontSize()
|
this.termContainer.setZoom(this.zoom)
|
||||||
}
|
}
|
||||||
|
|
||||||
resetZoom () {
|
resetZoom () {
|
||||||
this.zoom = 0
|
this.zoom = 0
|
||||||
this.setFontSize()
|
this.termContainer.setZoom(this.zoom)
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy () {
|
ngOnDestroy () {
|
||||||
|
this.detachTermContainerHandlers()
|
||||||
this.config.enabledServices(this.decorators).forEach(decorator => {
|
this.config.enabledServices(this.decorators).forEach(decorator => {
|
||||||
decorator.detach(this)
|
decorator.detach(this)
|
||||||
})
|
})
|
||||||
@@ -433,13 +344,7 @@ export class TerminalTabComponent extends BaseTabComponent {
|
|||||||
if (this.sessionCloseSubscription) {
|
if (this.sessionCloseSubscription) {
|
||||||
this.sessionCloseSubscription.unsubscribe()
|
this.sessionCloseSubscription.unsubscribe()
|
||||||
}
|
}
|
||||||
this.resize$.complete()
|
this.output.complete()
|
||||||
this.input$.complete()
|
|
||||||
this.output$.complete()
|
|
||||||
this.contentUpdated$.complete()
|
|
||||||
this.alternateScreenActive$.complete()
|
|
||||||
this.mouseEvent$.complete()
|
|
||||||
this.bell$.complete()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async destroy () {
|
async destroy () {
|
||||||
@@ -459,8 +364,4 @@ export class TerminalTabComponent extends BaseTabComponent {
|
|||||||
}
|
}
|
||||||
return confirm(`"${children[0].command}" is still running. Close?`)
|
return confirm(`"${children[0].command}" is still running. Close?`)
|
||||||
}
|
}
|
||||||
|
|
||||||
private setFontSize () {
|
|
||||||
preferenceManager.set('font-size', this.config.store.terminal.fontSize * Math.pow(1.1, this.zoom))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -16,6 +16,7 @@ export class TerminalConfigProvider extends ConfigProvider {
|
|||||||
rightClick: 'menu',
|
rightClick: 'menu',
|
||||||
copyOnSelect: false,
|
copyOnSelect: false,
|
||||||
workingDirectory: '',
|
workingDirectory: '',
|
||||||
|
altIsMeta: false,
|
||||||
colorScheme: {
|
colorScheme: {
|
||||||
__nonStructural: true,
|
__nonStructural: true,
|
||||||
name: 'Material',
|
name: 'Material',
|
||||||
@@ -53,9 +54,13 @@ export class TerminalConfigProvider extends ConfigProvider {
|
|||||||
persistence: 'screen',
|
persistence: 'screen',
|
||||||
},
|
},
|
||||||
hotkeys: {
|
hotkeys: {
|
||||||
|
'ctrl-c': ['Ctrl-C'],
|
||||||
'copy': [
|
'copy': [
|
||||||
'⌘-C',
|
'⌘-C',
|
||||||
],
|
],
|
||||||
|
'paste': [
|
||||||
|
'⌘-V',
|
||||||
|
],
|
||||||
'clear': [
|
'clear': [
|
||||||
'⌘-K',
|
'⌘-K',
|
||||||
],
|
],
|
||||||
@@ -93,9 +98,13 @@ export class TerminalConfigProvider extends ConfigProvider {
|
|||||||
copyOnSelect: true,
|
copyOnSelect: true,
|
||||||
},
|
},
|
||||||
hotkeys: {
|
hotkeys: {
|
||||||
|
'ctrl-c': ['Ctrl-C'],
|
||||||
'copy': [
|
'copy': [
|
||||||
'Ctrl-Shift-C',
|
'Ctrl-Shift-C',
|
||||||
],
|
],
|
||||||
|
'paste': [
|
||||||
|
'Ctrl-Shift-V',
|
||||||
|
],
|
||||||
'clear': [
|
'clear': [
|
||||||
'Ctrl-L',
|
'Ctrl-L',
|
||||||
],
|
],
|
||||||
@@ -130,9 +139,13 @@ export class TerminalConfigProvider extends ConfigProvider {
|
|||||||
persistence: 'tmux',
|
persistence: 'tmux',
|
||||||
},
|
},
|
||||||
hotkeys: {
|
hotkeys: {
|
||||||
|
'ctrl-c': ['Ctrl-C'],
|
||||||
'copy': [
|
'copy': [
|
||||||
'Ctrl-Shift-C',
|
'Ctrl-Shift-C',
|
||||||
],
|
],
|
||||||
|
'paste': [
|
||||||
|
'Ctrl-Shift-V',
|
||||||
|
],
|
||||||
'clear': [
|
'clear': [
|
||||||
'Ctrl-L',
|
'Ctrl-L',
|
||||||
],
|
],
|
||||||
|
@@ -8,6 +8,10 @@ export class TerminalHotkeyProvider extends HotkeyProvider {
|
|||||||
id: 'copy',
|
id: 'copy',
|
||||||
name: 'Copy to clipboard',
|
name: 'Copy to clipboard',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'paste',
|
||||||
|
name: 'Paste from clipboard',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: 'home',
|
id: 'home',
|
||||||
name: 'Beginning of the line',
|
name: 'Beginning of the line',
|
||||||
@@ -52,5 +56,9 @@ export class TerminalHotkeyProvider extends HotkeyProvider {
|
|||||||
id: 'new-tab',
|
id: 'new-tab',
|
||||||
name: 'New tab',
|
name: 'New tab',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'ctrl-c',
|
||||||
|
name: 'Intelligent Ctrl-C (copy/abort)',
|
||||||
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@@ -22,6 +22,10 @@ x-screen {
|
|||||||
x-row > span {
|
x-row > span {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
height: inherit;
|
height: inherit;
|
||||||
|
|
||||||
|
&.wc-node {
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
|
1
terminus-terminal/src/icons/plus.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><path d="M368 224H224V80c0-8.84-7.16-16-16-16h-32c-8.84 0-16 7.16-16 16v144H16c-8.84 0-16 7.16-16 16v32c0 8.84 7.16 16 16 16h144v144c0 8.84 7.16 16 16 16h32c8.84 0 16-7.16 16-16V288h144c8.84 0 16-7.16 16-16v-32c0-8.84-7.16-16-16-16z"/></svg>
|
After Width: | Height: | Size: 303 B |
@@ -2,6 +2,8 @@ import { NgModule } from '@angular/core'
|
|||||||
import { BrowserModule } from '@angular/platform-browser'
|
import { BrowserModule } from '@angular/platform-browser'
|
||||||
import { FormsModule } from '@angular/forms'
|
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 TerminusCorePlugin from 'terminus-core'
|
||||||
|
|
||||||
import { ToolbarButtonProvider, TabRecoveryProvider, ConfigProvider, HotkeysService, HotkeyProvider, AppService, ConfigService } from 'terminus-core'
|
import { ToolbarButtonProvider, TabRecoveryProvider, ConfigProvider, HotkeysService, HotkeyProvider, AppService, ConfigService } from 'terminus-core'
|
||||||
import { SettingsTabProvider } from 'terminus-settings'
|
import { SettingsTabProvider } from 'terminus-settings'
|
||||||
@@ -12,6 +14,7 @@ import { ColorPickerComponent } from './components/colorPicker.component'
|
|||||||
|
|
||||||
import { SessionsService, BaseSession } from './services/sessions.service'
|
import { SessionsService, BaseSession } from './services/sessions.service'
|
||||||
import { TerminalService } from './services/terminal.service'
|
import { TerminalService } from './services/terminal.service'
|
||||||
|
import { TerminalContainersService } from './services/terminalContainers.service'
|
||||||
|
|
||||||
import { ScreenPersistenceProvider } from './persistence/screen'
|
import { ScreenPersistenceProvider } from './persistence/screen'
|
||||||
import { TMuxPersistenceProvider } from './persistence/tmux'
|
import { TMuxPersistenceProvider } from './persistence/tmux'
|
||||||
@@ -24,6 +27,7 @@ import { TerminalConfigProvider } from './config'
|
|||||||
import { TerminalHotkeyProvider } from './hotkeys'
|
import { TerminalHotkeyProvider } from './hotkeys'
|
||||||
import { HyperColorSchemes } from './colorSchemes'
|
import { HyperColorSchemes } from './colorSchemes'
|
||||||
|
|
||||||
|
import { CmderShellProvider } from './shells/cmder'
|
||||||
import { CustomShellProvider } from './shells/custom'
|
import { CustomShellProvider } from './shells/custom'
|
||||||
import { Cygwin32ShellProvider } from './shells/cygwin32'
|
import { Cygwin32ShellProvider } from './shells/cygwin32'
|
||||||
import { Cygwin64ShellProvider } from './shells/cygwin64'
|
import { Cygwin64ShellProvider } from './shells/cygwin64'
|
||||||
@@ -41,10 +45,13 @@ import { hterm } from './hterm'
|
|||||||
BrowserModule,
|
BrowserModule,
|
||||||
FormsModule,
|
FormsModule,
|
||||||
NgbModule,
|
NgbModule,
|
||||||
|
ToastrModule,
|
||||||
|
TerminusCorePlugin,
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
SessionsService,
|
SessionsService,
|
||||||
TerminalService,
|
TerminalService,
|
||||||
|
TerminalContainersService,
|
||||||
|
|
||||||
{ provide: ToolbarButtonProvider, useClass: ButtonProvider, multi: true },
|
{ provide: ToolbarButtonProvider, useClass: ButtonProvider, multi: true },
|
||||||
{ provide: TabRecoveryProvider, useClass: RecoveryProvider, multi: true },
|
{ provide: TabRecoveryProvider, useClass: RecoveryProvider, multi: true },
|
||||||
@@ -57,6 +64,7 @@ import { hterm } from './hterm'
|
|||||||
{ provide: SessionPersistenceProvider, useClass: ScreenPersistenceProvider, multi: true },
|
{ provide: SessionPersistenceProvider, useClass: ScreenPersistenceProvider, multi: true },
|
||||||
{ provide: SessionPersistenceProvider, useClass: TMuxPersistenceProvider, multi: true },
|
{ provide: SessionPersistenceProvider, useClass: TMuxPersistenceProvider, multi: true },
|
||||||
|
|
||||||
|
{ provide: ShellProvider, useClass: CmderShellProvider, multi: true },
|
||||||
{ provide: ShellProvider, useClass: WindowsStockShellsProvider, multi: true },
|
{ provide: ShellProvider, useClass: WindowsStockShellsProvider, multi: true },
|
||||||
{ provide: ShellProvider, useClass: MacOSDefaultShellProvider, multi: true },
|
{ provide: ShellProvider, useClass: MacOSDefaultShellProvider, multi: true },
|
||||||
{ provide: ShellProvider, useClass: LinuxDefaultShellProvider, multi: true },
|
{ provide: ShellProvider, useClass: LinuxDefaultShellProvider, multi: true },
|
||||||
@@ -117,4 +125,4 @@ export default class TerminalModule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export * from './api'
|
export * from './api'
|
||||||
export { TerminalService, BaseSession, TerminalTabComponent }
|
export { TerminalService, BaseSession, TerminalTabComponent, TerminalContainersService }
|
||||||
|
@@ -1,20 +1,25 @@
|
|||||||
|
import { Subscription } from 'rxjs'
|
||||||
import { Injectable } from '@angular/core'
|
import { Injectable } from '@angular/core'
|
||||||
import { TerminalDecorator } from './api'
|
import { TerminalDecorator } from './api'
|
||||||
import { TerminalTabComponent } from './components/terminalTab.component'
|
import { TerminalTabComponent } from './components/terminalTab.component'
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class PathDropDecorator extends TerminalDecorator {
|
export class PathDropDecorator extends TerminalDecorator {
|
||||||
|
private subscriptions: Subscription[] = []
|
||||||
|
|
||||||
attach (terminal: TerminalTabComponent): void {
|
attach (terminal: TerminalTabComponent): void {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
terminal.hterm.scrollPort_.document_.addEventListener('dragover', (event) => {
|
this.subscriptions = [
|
||||||
|
terminal.termContainer.dragOver$.subscribe(event => {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
})
|
}),
|
||||||
terminal.hterm.scrollPort_.document_.addEventListener('drop', (event) => {
|
terminal.termContainer.drop$.subscribe(event => {
|
||||||
for (let file of event.dataTransfer.files) {
|
for (let file of event.dataTransfer.files as any) {
|
||||||
this.injectPath(terminal, file.path)
|
this.injectPath(terminal, file.path)
|
||||||
}
|
}
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
})
|
}),
|
||||||
|
]
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -25,6 +30,9 @@ export class PathDropDecorator extends TerminalDecorator {
|
|||||||
terminal.sendInput(path + ' ')
|
terminal.sendInput(path + ' ')
|
||||||
}
|
}
|
||||||
|
|
||||||
// tslint:disable-next-line no-empty
|
detach (terminal: TerminalTabComponent): void {
|
||||||
detach (terminal: TerminalTabComponent): void { }
|
for (let s of this.subscriptions) {
|
||||||
|
s.unsubscribe()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -2,6 +2,7 @@ import { Injectable } from '@angular/core'
|
|||||||
import { execFileSync } from 'child_process'
|
import { execFileSync } from 'child_process'
|
||||||
import * as AsyncLock from 'async-lock'
|
import * as AsyncLock from 'async-lock'
|
||||||
import { ConnectableObservable, AsyncSubject, Subject } from 'rxjs'
|
import { ConnectableObservable, AsyncSubject, Subject } from 'rxjs'
|
||||||
|
import { first, publish } from 'rxjs/operators'
|
||||||
import * as childProcess from 'child_process'
|
import * as childProcess from 'child_process'
|
||||||
|
|
||||||
import { Logger } from 'terminus-core'
|
import { Logger } from 'terminus-core'
|
||||||
@@ -102,7 +103,7 @@ export class TMuxCommandProcess {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
this.response$ = this.block$.publish()
|
this.response$ = this.block$.asObservable().pipe(publish()) as ConnectableObservable<TMuxBlock>
|
||||||
this.response$.connect()
|
this.response$.connect()
|
||||||
|
|
||||||
this.block$.subscribe(block => {
|
this.block$.subscribe(block => {
|
||||||
@@ -116,7 +117,7 @@ export class TMuxCommandProcess {
|
|||||||
|
|
||||||
command (command: string): Promise<TMuxBlock> {
|
command (command: string): Promise<TMuxBlock> {
|
||||||
return this.lock.acquire('key', () => {
|
return this.lock.acquire('key', () => {
|
||||||
let p = this.response$.take(1).toPromise()
|
let p = this.response$.pipe(first()).toPromise()
|
||||||
this.logger.debug('command:', command)
|
this.logger.debug('command:', command)
|
||||||
this.process.stdin.write(command + '\n')
|
this.process.stdin.write(command + '\n')
|
||||||
return p
|
return p
|
||||||
|
@@ -1,7 +1,8 @@
|
|||||||
const psNode = require('ps-node')
|
const psNode = require('ps-node')
|
||||||
let nodePTY
|
let nodePTY
|
||||||
import * as fs from 'mz/fs'
|
import * as fs from 'mz/fs'
|
||||||
import { Subject } from 'rxjs'
|
import { Observable, Subject } from 'rxjs'
|
||||||
|
import { first } from 'rxjs/operators'
|
||||||
import { Injectable, Inject } from '@angular/core'
|
import { Injectable, Inject } from '@angular/core'
|
||||||
import { Logger, LogService, ElectronService, ConfigService } from 'terminus-core'
|
import { Logger, LogService, ElectronService, ConfigService } from 'terminus-core'
|
||||||
import { exec } from 'mz/child_process'
|
import { exec } from 'mz/child_process'
|
||||||
@@ -17,28 +18,33 @@ export interface IChildProcess {
|
|||||||
export abstract class BaseSession {
|
export abstract class BaseSession {
|
||||||
open: boolean
|
open: boolean
|
||||||
name: string
|
name: string
|
||||||
output$ = new Subject<string>()
|
|
||||||
closed$ = new Subject<void>()
|
|
||||||
destroyed$ = new Subject<void>()
|
|
||||||
recoveryId: string
|
recoveryId: string
|
||||||
truePID: number
|
truePID: number
|
||||||
|
protected output = new Subject<string>()
|
||||||
|
protected closed = new Subject<void>()
|
||||||
|
protected destroyed = new Subject<void>()
|
||||||
private initialDataBuffer = ''
|
private initialDataBuffer = ''
|
||||||
private initialDataBufferReleased = false
|
private initialDataBufferReleased = false
|
||||||
|
|
||||||
|
get output$ (): Observable<string> { return this.output }
|
||||||
|
get closed$ (): Observable<void> { return this.closed }
|
||||||
|
get destroyed$ (): Observable<void> { return this.destroyed }
|
||||||
|
|
||||||
emitOutput (data: string) {
|
emitOutput (data: string) {
|
||||||
if (!this.initialDataBufferReleased) {
|
if (!this.initialDataBufferReleased) {
|
||||||
this.initialDataBuffer += data
|
this.initialDataBuffer += data
|
||||||
} else {
|
} else {
|
||||||
this.output$.next(data)
|
this.output.next(data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
releaseInitialDataBuffer () {
|
releaseInitialDataBuffer () {
|
||||||
this.initialDataBufferReleased = true
|
this.initialDataBufferReleased = true
|
||||||
this.output$.next(this.initialDataBuffer)
|
this.output.next(this.initialDataBuffer)
|
||||||
this.initialDataBuffer = null
|
this.initialDataBuffer = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
abstract start (options: SessionOptions)
|
||||||
abstract resize (columns, rows)
|
abstract resize (columns, rows)
|
||||||
abstract write (data)
|
abstract write (data)
|
||||||
abstract kill (signal?: string)
|
abstract kill (signal?: string)
|
||||||
@@ -49,9 +55,9 @@ export abstract class BaseSession {
|
|||||||
async destroy (): Promise<void> {
|
async destroy (): Promise<void> {
|
||||||
if (this.open) {
|
if (this.open) {
|
||||||
this.open = false
|
this.open = false
|
||||||
this.closed$.next()
|
this.closed.next()
|
||||||
this.destroyed$.next()
|
this.destroyed.next()
|
||||||
this.output$.complete()
|
this.output.complete()
|
||||||
await this.gracefullyKillProcess()
|
await this.gracefullyKillProcess()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -60,8 +66,7 @@ export abstract class BaseSession {
|
|||||||
export class Session extends BaseSession {
|
export class Session extends BaseSession {
|
||||||
private pty: any
|
private pty: any
|
||||||
|
|
||||||
constructor (options: SessionOptions) {
|
start (options: SessionOptions) {
|
||||||
super()
|
|
||||||
this.name = options.name
|
this.name = options.name
|
||||||
this.recoveryId = options.recoveryId
|
this.recoveryId = options.recoveryId
|
||||||
|
|
||||||
@@ -100,7 +105,7 @@ export class Session extends BaseSession {
|
|||||||
|
|
||||||
this.open = true
|
this.open = true
|
||||||
|
|
||||||
this.pty.on('data', data => {
|
this.pty.on('data-buffered', data => {
|
||||||
this.emitOutput(data)
|
this.emitOutput(data)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -190,7 +195,7 @@ export class Session extends BaseSession {
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class SessionsService {
|
export class SessionsService {
|
||||||
sessions: {[id: string]: Session} = {}
|
sessions: {[id: string]: BaseSession} = {}
|
||||||
logger: Logger
|
logger: Logger
|
||||||
private lastID = 0
|
private lastID = 0
|
||||||
|
|
||||||
@@ -200,7 +205,8 @@ export class SessionsService {
|
|||||||
electron: ElectronService,
|
electron: ElectronService,
|
||||||
log: LogService,
|
log: LogService,
|
||||||
) {
|
) {
|
||||||
nodePTY = electron.remoteRequirePluginModule('terminus-terminal', 'node-pty-tmp', global as any)
|
const nodePTYPath = electron.remoteResolvePluginModule('terminus-terminal', 'node-pty-tmp', global as any)
|
||||||
|
nodePTY = electron.remoteRequire('./bufferizedPTY')(nodePTYPath)
|
||||||
this.logger = log.create('sessions')
|
this.logger = log.create('sessions')
|
||||||
this.persistenceProviders = this.config.enabledServices(this.persistenceProviders).filter(x => x.isAvailable())
|
this.persistenceProviders = this.config.enabledServices(this.persistenceProviders).filter(x => x.isAvailable())
|
||||||
}
|
}
|
||||||
@@ -214,12 +220,12 @@ export class SessionsService {
|
|||||||
return options
|
return options
|
||||||
}
|
}
|
||||||
|
|
||||||
addSession (options: SessionOptions): Session {
|
addSession (session: BaseSession, options: SessionOptions) {
|
||||||
this.lastID++
|
this.lastID++
|
||||||
options.name = `session-${this.lastID}`
|
options.name = `session-${this.lastID}`
|
||||||
let session = new Session(options)
|
session.start(options)
|
||||||
let persistence = this.getPersistence()
|
let persistence = this.getPersistence()
|
||||||
session.destroyed$.first().subscribe(() => {
|
session.destroyed$.pipe(first()).subscribe(() => {
|
||||||
delete this.sessions[session.name]
|
delete this.sessions[session.name]
|
||||||
if (persistence) {
|
if (persistence) {
|
||||||
persistence.terminateSession(session.recoveryId)
|
persistence.terminateSession(session.recoveryId)
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { AsyncSubject } from 'rxjs'
|
import { Observable, AsyncSubject } from 'rxjs'
|
||||||
import { Injectable, Inject } from '@angular/core'
|
import { Injectable, Inject } from '@angular/core'
|
||||||
import { AppService, Logger, LogService, ConfigService } from 'terminus-core'
|
import { AppService, Logger, LogService, ConfigService } from 'terminus-core'
|
||||||
import { IShell, ShellProvider } from '../api'
|
import { IShell, ShellProvider } from '../api'
|
||||||
@@ -7,9 +7,11 @@ import { TerminalTabComponent } from '../components/terminalTab.component'
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class TerminalService {
|
export class TerminalService {
|
||||||
shells$ = new AsyncSubject<IShell[]>()
|
private shells = new AsyncSubject<IShell[]>()
|
||||||
private logger: Logger
|
private logger: Logger
|
||||||
|
|
||||||
|
get shells$ (): Observable<IShell[]> { return this.shells }
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
private app: AppService,
|
private app: AppService,
|
||||||
private sessions: SessionsService,
|
private sessions: SessionsService,
|
||||||
@@ -26,10 +28,10 @@ export class TerminalService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async reloadShells () {
|
async reloadShells () {
|
||||||
this.shells$ = new AsyncSubject<IShell[]>()
|
this.shells = new AsyncSubject<IShell[]>()
|
||||||
let shellLists = await Promise.all(this.config.enabledServices(this.shellProviders).map(x => x.provide()))
|
let shellLists = await Promise.all(this.config.enabledServices(this.shellProviders).map(x => x.provide()))
|
||||||
this.shells$.next(shellLists.reduce((a, b) => a.concat(b)))
|
this.shells.next(shellLists.reduce((a, b) => a.concat(b)))
|
||||||
this.shells$.complete()
|
this.shells.complete()
|
||||||
}
|
}
|
||||||
|
|
||||||
async openTab (shell?: IShell, cwd?: string): Promise<TerminalTabComponent> {
|
async openTab (shell?: IShell, cwd?: string): Promise<TerminalTabComponent> {
|
||||||
|
16
terminus-terminal/src/services/terminalContainers.service.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { Injectable } from '@angular/core'
|
||||||
|
import { TermContainer } from '../terminalContainers/termContainer'
|
||||||
|
import { HTermContainer } from '../terminalContainers/htermContainer'
|
||||||
|
import { BaseSession } from '../services/sessions.service'
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class TerminalContainersService {
|
||||||
|
private containers = new WeakMap<BaseSession, TermContainer>()
|
||||||
|
|
||||||
|
getContainer (session: BaseSession): TermContainer {
|
||||||
|
if (!this.containers.has(session)) {
|
||||||
|
this.containers.set(session, new HTermContainer())
|
||||||
|
}
|
||||||
|
return this.containers.get(session)
|
||||||
|
}
|
||||||
|
}
|
38
terminus-terminal/src/shells/cmder.ts
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import * as path from 'path'
|
||||||
|
import { Injectable } from '@angular/core'
|
||||||
|
import { HostAppService, Platform } from 'terminus-core'
|
||||||
|
|
||||||
|
import { ShellProvider, IShell } from '../api'
|
||||||
|
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class CmderShellProvider extends ShellProvider {
|
||||||
|
constructor (
|
||||||
|
private hostApp: HostAppService,
|
||||||
|
) {
|
||||||
|
super()
|
||||||
|
}
|
||||||
|
|
||||||
|
async provide (): Promise<IShell[]> {
|
||||||
|
if (this.hostApp.platform !== Platform.Windows) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!process.env.CMDER_ROOT) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
return [{
|
||||||
|
id: 'cmder',
|
||||||
|
name: 'Cmder',
|
||||||
|
command: 'cmd.exe',
|
||||||
|
args: [
|
||||||
|
'/k',
|
||||||
|
path.join(process.env.CMDER_ROOT, 'vendor', 'init.bat'),
|
||||||
|
],
|
||||||
|
env: {
|
||||||
|
TERM: 'cygwin',
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
226
terminus-terminal/src/terminalContainers/htermContainer.ts
Normal file
@@ -0,0 +1,226 @@
|
|||||||
|
import { TermContainer } from './termContainer'
|
||||||
|
import { hterm, preferenceManager } from '../hterm'
|
||||||
|
|
||||||
|
export class HTermContainer extends TermContainer {
|
||||||
|
term: any
|
||||||
|
io: any
|
||||||
|
private htermIframe: HTMLElement
|
||||||
|
private initialized = false
|
||||||
|
private configuredFontSize = 0
|
||||||
|
private configuredLinePadding = 0
|
||||||
|
private zoom = 0
|
||||||
|
|
||||||
|
attach (host: HTMLElement) {
|
||||||
|
if (!this.initialized) {
|
||||||
|
this.init()
|
||||||
|
this.initialized = true
|
||||||
|
this.term.decorate(host)
|
||||||
|
this.htermIframe = this.term.scrollPort_.iframe_
|
||||||
|
} else {
|
||||||
|
host.appendChild(this.htermIframe)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getSelection (): string {
|
||||||
|
return this.term.getSelectionText()
|
||||||
|
}
|
||||||
|
|
||||||
|
copySelection () {
|
||||||
|
this.term.copySelectionToClipboard()
|
||||||
|
}
|
||||||
|
|
||||||
|
clearSelection () {
|
||||||
|
this.term.getDocument().getSelection().removeAllRanges()
|
||||||
|
}
|
||||||
|
|
||||||
|
focus () {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.term.scrollPort_.resize()
|
||||||
|
this.term.scrollPort_.focus()
|
||||||
|
}, 100)
|
||||||
|
}
|
||||||
|
|
||||||
|
write (data: string): void {
|
||||||
|
this.io.writeUTF8(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
clear (): void {
|
||||||
|
this.term.wipeContents()
|
||||||
|
this.term.onVTKeystroke('\f')
|
||||||
|
}
|
||||||
|
|
||||||
|
configure (config: any): void {
|
||||||
|
this.configuredFontSize = config.terminal.fontSize
|
||||||
|
this.configuredLinePadding = config.terminal.linePadding
|
||||||
|
this.setFontSize()
|
||||||
|
|
||||||
|
preferenceManager.set('font-family', `"${config.terminal.font}", "monospace-fallback", monospace`)
|
||||||
|
preferenceManager.set('enable-bold', true)
|
||||||
|
// preferenceManager.set('audible-bell-sound', '')
|
||||||
|
preferenceManager.set('desktop-notification-bell', config.terminal.bell === 'notification')
|
||||||
|
preferenceManager.set('enable-clipboard-notice', false)
|
||||||
|
preferenceManager.set('receive-encoding', 'raw')
|
||||||
|
preferenceManager.set('send-encoding', 'raw')
|
||||||
|
preferenceManager.set('ctrl-plus-minus-zero-zoom', false)
|
||||||
|
preferenceManager.set('scrollbar-visible', process.platform === 'darwin')
|
||||||
|
preferenceManager.set('copy-on-select', config.terminal.copyOnSelect)
|
||||||
|
preferenceManager.set('alt-is-meta', config.terminal.altIsMeta)
|
||||||
|
preferenceManager.set('alt-sends-what', 'browser-key')
|
||||||
|
preferenceManager.set('alt-gr-mode', 'ctrl-alt')
|
||||||
|
preferenceManager.set('pass-alt-number', true)
|
||||||
|
preferenceManager.set('cursor-blink', config.terminal.cursorBlink)
|
||||||
|
preferenceManager.set('clear-selection-after-copy', true)
|
||||||
|
|
||||||
|
if (config.terminal.colorScheme.foreground) {
|
||||||
|
preferenceManager.set('foreground-color', config.terminal.colorScheme.foreground)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.terminal.background === 'colorScheme') {
|
||||||
|
if (config.terminal.colorScheme.background) {
|
||||||
|
preferenceManager.set('background-color', config.terminal.colorScheme.background)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// hterm can't parse "transparent"
|
||||||
|
preferenceManager.set('background-color', 'transparent')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.terminal.colorScheme.colors) {
|
||||||
|
preferenceManager.set('color-palette-overrides', config.terminal.colorScheme.colors)
|
||||||
|
}
|
||||||
|
if (config.terminal.colorScheme.cursor) {
|
||||||
|
preferenceManager.set('cursor-color', config.terminal.colorScheme.cursor)
|
||||||
|
}
|
||||||
|
|
||||||
|
let css = require('../hterm.userCSS.scss')
|
||||||
|
if (!config.terminal.ligatures) {
|
||||||
|
css += `
|
||||||
|
* {
|
||||||
|
font-feature-settings: "liga" 0;
|
||||||
|
font-variant-ligatures: none;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
} else {
|
||||||
|
css += `
|
||||||
|
* {
|
||||||
|
font-feature-settings: "liga" 1;
|
||||||
|
font-variant-ligatures: initial;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
}
|
||||||
|
css += config.appearance.css
|
||||||
|
this.term.setCSS(css)
|
||||||
|
this.term.setBracketedPaste(config.terminal.bracketedPaste)
|
||||||
|
this.term.defaultCursorShape = {
|
||||||
|
block: hterm.hterm.Terminal.cursorShape.BLOCK,
|
||||||
|
underline: hterm.hterm.Terminal.cursorShape.UNDERLINE,
|
||||||
|
beam: hterm.hterm.Terminal.cursorShape.BEAM,
|
||||||
|
}[config.terminal.cursor]
|
||||||
|
this.term.applyCursorShape()
|
||||||
|
this.term.setCursorBlink(config.terminal.cursorBlink)
|
||||||
|
if (config.terminal.cursorBlink) {
|
||||||
|
this.term.onCursorBlink_()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setZoom (zoom: number): void {
|
||||||
|
this.zoom = zoom
|
||||||
|
this.setFontSize()
|
||||||
|
}
|
||||||
|
|
||||||
|
visualBell (): void {
|
||||||
|
const color = preferenceManager.get('background-color')
|
||||||
|
preferenceManager.set('background-color', 'rgba(128,128,128,.25)')
|
||||||
|
setTimeout(() => {
|
||||||
|
preferenceManager.set('background-color', color)
|
||||||
|
}, 125)
|
||||||
|
}
|
||||||
|
|
||||||
|
private setFontSize () {
|
||||||
|
preferenceManager.set('font-size', this.configuredFontSize * Math.pow(1.1, this.zoom))
|
||||||
|
}
|
||||||
|
|
||||||
|
private init () {
|
||||||
|
this.term = new hterm.hterm.Terminal()
|
||||||
|
this.term.onTerminalReady = () => {
|
||||||
|
this.term.installKeyboard()
|
||||||
|
this.term.scrollPort_.setCtrlVPaste(true)
|
||||||
|
this.io = this.term.io.push()
|
||||||
|
this.io.onVTKeystroke = this.io.sendString = data => this.input.next(data)
|
||||||
|
this.io.onTerminalResize = (columns, rows) => {
|
||||||
|
console.log('hterm resize')
|
||||||
|
this.resize.next({ columns, rows })
|
||||||
|
}
|
||||||
|
this.ready.next(null)
|
||||||
|
this.ready.complete()
|
||||||
|
|
||||||
|
this.term.scrollPort_.document_.addEventListener('dragOver', event => {
|
||||||
|
this.dragOver.next(event)
|
||||||
|
})
|
||||||
|
|
||||||
|
this.term.scrollPort_.document_.addEventListener('drop', event => {
|
||||||
|
this.drop.next(event)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
this.term.setWindowTitle = title => this.title.next(title)
|
||||||
|
|
||||||
|
const _setAlternateMode = this.term.setAlternateMode.bind(this.term)
|
||||||
|
this.term.setAlternateMode = (state) => {
|
||||||
|
_setAlternateMode(state)
|
||||||
|
this.alternateScreenActive.next(state)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.term.primaryScreen_.syncSelectionCaret = () => null
|
||||||
|
this.term.alternateScreen_.syncSelectionCaret = () => null
|
||||||
|
this.term.primaryScreen_.terminal = this.term
|
||||||
|
this.term.alternateScreen_.terminal = this.term
|
||||||
|
|
||||||
|
this.term.scrollPort_.onPaste_ = (event) => {
|
||||||
|
event.preventDefault()
|
||||||
|
}
|
||||||
|
|
||||||
|
const _resize = this.term.scrollPort_.resize.bind(this.term.scrollPort_)
|
||||||
|
this.term.scrollPort_.resize = () => {
|
||||||
|
if (this.enableResizing) {
|
||||||
|
_resize()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const _onMouse = this.term.onMouse_.bind(this.term)
|
||||||
|
this.term.onMouse_ = (event) => {
|
||||||
|
this.mouseEvent.next(event)
|
||||||
|
if (event.type === 'mousedown' && event.which === 3) {
|
||||||
|
event.preventDefault()
|
||||||
|
event.stopPropagation()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (event.type === 'mousewheel' && event.altKey) {
|
||||||
|
event.preventDefault()
|
||||||
|
}
|
||||||
|
_onMouse(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.term.ringBell = () => this.bell.next()
|
||||||
|
|
||||||
|
for (let screen of [this.term.primaryScreen_, this.term.alternateScreen_]) {
|
||||||
|
const _insertString = screen.insertString.bind(screen)
|
||||||
|
screen.insertString = (data) => {
|
||||||
|
_insertString(data)
|
||||||
|
this.contentUpdated.next()
|
||||||
|
}
|
||||||
|
|
||||||
|
const _deleteChars = screen.deleteChars.bind(screen)
|
||||||
|
screen.deleteChars = (count) => {
|
||||||
|
let ret = _deleteChars(count)
|
||||||
|
this.contentUpdated.next()
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const _measureCharacterSize = this.term.scrollPort_.measureCharacterSize.bind(this.term.scrollPort_)
|
||||||
|
this.term.scrollPort_.measureCharacterSize = () => {
|
||||||
|
let size = _measureCharacterSize()
|
||||||
|
size.height += this.configuredLinePadding
|
||||||
|
return size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
57
terminus-terminal/src/terminalContainers/termContainer.ts
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
import { Observable, Subject, AsyncSubject, ReplaySubject, BehaviorSubject } from 'rxjs'
|
||||||
|
import { ResizeEvent } from '../api'
|
||||||
|
|
||||||
|
export abstract class TermContainer {
|
||||||
|
enableResizing = true
|
||||||
|
protected ready = new AsyncSubject<void>()
|
||||||
|
protected title = new ReplaySubject<string>(1)
|
||||||
|
protected alternateScreenActive = new BehaviorSubject<boolean>(false)
|
||||||
|
protected mouseEvent = new Subject<MouseEvent>()
|
||||||
|
protected bell = new Subject<void>()
|
||||||
|
protected contentUpdated = new Subject<void>()
|
||||||
|
protected input = new Subject<string>()
|
||||||
|
protected resize = new ReplaySubject<ResizeEvent>(1)
|
||||||
|
protected dragOver = new Subject<DragEvent>()
|
||||||
|
protected drop = new Subject<DragEvent>()
|
||||||
|
|
||||||
|
get ready$ (): Observable<void> { return this.ready }
|
||||||
|
get title$ (): Observable<string> { return this.title }
|
||||||
|
get alternateScreenActive$ (): Observable<boolean> { return this.alternateScreenActive }
|
||||||
|
get mouseEvent$ (): Observable<MouseEvent> { return this.mouseEvent }
|
||||||
|
get bell$ (): Observable<void> { return this.bell }
|
||||||
|
get contentUpdated$ (): Observable<void> { return this.contentUpdated }
|
||||||
|
get input$ (): Observable<string> { return this.input }
|
||||||
|
get resize$ (): Observable<ResizeEvent> { return this.resize }
|
||||||
|
get dragOver$ (): Observable<DragEvent> { return this.dragOver }
|
||||||
|
get drop$ (): Observable<DragEvent> { return this.drop }
|
||||||
|
|
||||||
|
abstract attach (host: HTMLElement): void
|
||||||
|
|
||||||
|
destroy (): void {
|
||||||
|
for (let o of [
|
||||||
|
this.ready,
|
||||||
|
this.title,
|
||||||
|
this.alternateScreenActive,
|
||||||
|
this.mouseEvent,
|
||||||
|
this.bell,
|
||||||
|
this.contentUpdated,
|
||||||
|
this.input,
|
||||||
|
this.resize,
|
||||||
|
this.dragOver,
|
||||||
|
this.drop,
|
||||||
|
]) {
|
||||||
|
o.complete()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract getSelection (): string
|
||||||
|
abstract copySelection (): void
|
||||||
|
abstract clearSelection (): void
|
||||||
|
abstract focus (): void
|
||||||
|
abstract write (data: string): void
|
||||||
|
abstract clear (): void
|
||||||
|
abstract visualBell (): void
|
||||||
|
|
||||||
|
abstract configure (configStore: any): void
|
||||||
|
abstract setZoom (zoom: number): void
|
||||||
|
}
|
@@ -18,9 +18,10 @@ module.exports = {
|
|||||||
extensions: ['.ts', '.js'],
|
extensions: ['.ts', '.js'],
|
||||||
},
|
},
|
||||||
module: {
|
module: {
|
||||||
loaders: [
|
rules: [
|
||||||
{
|
{
|
||||||
test: /\.ts$/,
|
test: /\.ts$/,
|
||||||
|
use: {
|
||||||
loader: 'awesome-typescript-loader',
|
loader: 'awesome-typescript-loader',
|
||||||
query: {
|
query: {
|
||||||
configFileName: path.resolve(__dirname, 'tsconfig.json'),
|
configFileName: path.resolve(__dirname, 'tsconfig.json'),
|
||||||
@@ -29,17 +30,21 @@ module.exports = {
|
|||||||
"terminus-*": [path.resolve(__dirname, '../terminus-*')],
|
"terminus-*": [path.resolve(__dirname, '../terminus-*')],
|
||||||
"*": [path.resolve(__dirname, '../app/node_modules/*')],
|
"*": [path.resolve(__dirname, '../app/node_modules/*')],
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{ test: /\.pug$/, use: ['apply-loader', 'pug-loader'] },
|
{ test: /\.pug$/, use: ['apply-loader', 'pug-loader'] },
|
||||||
{ test: /\.scss$/, use: ['to-string-loader', 'css-loader', 'sass-loader'] },
|
{ test: /\.scss$/, use: ['to-string-loader', 'css-loader', 'sass-loader'] },
|
||||||
{ test: /\.css$/, use: ['to-string-loader', 'css-loader'] },
|
{ test: /\.css$/, use: ['to-string-loader', 'css-loader'] },
|
||||||
|
{ test: /\.svg/, use: ['svg-inline-loader'] },
|
||||||
{
|
{
|
||||||
test: /\.(ttf|eot|otf|woff|woff2|ogg)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
|
test: /\.(ttf|eot|otf|woff|woff2|ogg)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
|
||||||
loader: "url-loader",
|
use: {
|
||||||
|
loader: 'url-loader',
|
||||||
options: {
|
options: {
|
||||||
limit: 999999999999,
|
limit: 999999999999,
|
||||||
}
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -55,6 +60,7 @@ module.exports = {
|
|||||||
/^rxjs/,
|
/^rxjs/,
|
||||||
/^@angular/,
|
/^@angular/,
|
||||||
/^@ng-bootstrap/,
|
/^@ng-bootstrap/,
|
||||||
|
'ngx-toastr',
|
||||||
/^terminus-/,
|
/^terminus-/,
|
||||||
],
|
],
|
||||||
plugins: [
|
plugins: [
|
||||||
|
@@ -62,14 +62,14 @@ file-loader@^0.11.2:
|
|||||||
dependencies:
|
dependencies:
|
||||||
loader-utils "^1.0.2"
|
loader-utils "^1.0.2"
|
||||||
|
|
||||||
font-manager@0.2.2:
|
font-manager@0.3.0:
|
||||||
version "0.2.2"
|
version "0.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/font-manager/-/font-manager-0.2.2.tgz#18a1c5b6ec7f91e22a17c71cbbaa0ea4e68e3a44"
|
resolved "https://registry.yarnpkg.com/font-manager/-/font-manager-0.3.0.tgz#9efdc13e521a3d8752e7ab56c3938818043a311f"
|
||||||
dependencies:
|
dependencies:
|
||||||
nan "~2.2.0"
|
nan ">=2.10.0"
|
||||||
|
|
||||||
hterm-umdjs@1.1.3:
|
hterm-umdjs@1.1.3:
|
||||||
version "1.1.3+1.58.sha.15ed490"
|
version "1.1.3"
|
||||||
resolved "https://registry.yarnpkg.com/hterm-umdjs/-/hterm-umdjs-1.1.3.tgz#8b57bcaded5ba9541d6c8e32a82b34abb93e885e"
|
resolved "https://registry.yarnpkg.com/hterm-umdjs/-/hterm-umdjs-1.1.3.tgz#8b57bcaded5ba9541d6c8e32a82b34abb93e885e"
|
||||||
|
|
||||||
json5@^0.5.0:
|
json5@^0.5.0:
|
||||||
@@ -92,14 +92,14 @@ mz@^2.6.0:
|
|||||||
object-assign "^4.0.1"
|
object-assign "^4.0.1"
|
||||||
thenify-all "^1.0.0"
|
thenify-all "^1.0.0"
|
||||||
|
|
||||||
|
nan@>=2.10.0:
|
||||||
|
version "2.10.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/nan/-/nan-2.10.0.tgz#96d0cd610ebd58d4b4de9cc0c6828cda99c7548f"
|
||||||
|
|
||||||
nan@^2.6.2:
|
nan@^2.6.2:
|
||||||
version "2.7.0"
|
version "2.7.0"
|
||||||
resolved "https://registry.yarnpkg.com/nan/-/nan-2.7.0.tgz#d95bf721ec877e08db276ed3fc6eb78f9083ad46"
|
resolved "https://registry.yarnpkg.com/nan/-/nan-2.7.0.tgz#d95bf721ec877e08db276ed3fc6eb78f9083ad46"
|
||||||
|
|
||||||
nan@~2.2.0:
|
|
||||||
version "2.2.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/nan/-/nan-2.2.1.tgz#d68693f6b34bb41d66bc68b3a4f9defc79d7149b"
|
|
||||||
|
|
||||||
node-pty-tmp@0.7.1:
|
node-pty-tmp@0.7.1:
|
||||||
version "0.7.1"
|
version "0.7.1"
|
||||||
resolved "https://registry.yarnpkg.com/node-pty-tmp/-/node-pty-tmp-0.7.1.tgz#0a81179f9087b21f968206c886e543db20650d7a"
|
resolved "https://registry.yarnpkg.com/node-pty-tmp/-/node-pty-tmp-0.7.1.tgz#0a81179f9087b21f968206c886e543db20650d7a"
|
||||||
|
@@ -2,9 +2,9 @@
|
|||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"module": "commonjs",
|
"module": "commonjs",
|
||||||
"target": "es2016",
|
"target": "es2016",
|
||||||
"declaration": false,
|
|
||||||
"noImplicitAny": false,
|
"noImplicitAny": false,
|
||||||
"removeComments": false,
|
"removeComments": false,
|
||||||
|
"emitDeclarationOnly": false,
|
||||||
"emitDecoratorMetadata": true,
|
"emitDecoratorMetadata": true,
|
||||||
"experimentalDecorators": true,
|
"experimentalDecorators": true,
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
|