Compare commits
113 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
78f8f4005e | ||
![]() |
38cfb3f036 | ||
![]() |
4e4d8a0e91 | ||
![]() |
21cfd14f1c | ||
![]() |
a64bbe145c | ||
![]() |
6a5dc79c5d | ||
![]() |
b799128427 | ||
![]() |
8b64a819e7 | ||
![]() |
5b78a5c1ed | ||
![]() |
91b318853f | ||
![]() |
ce3610c2da | ||
![]() |
d03430fb2e | ||
![]() |
caacc01aea | ||
![]() |
bcb6963c35 | ||
![]() |
deb99b0865 | ||
![]() |
2101c18657 | ||
![]() |
1a258f32b0 | ||
![]() |
3aaf490f57 | ||
![]() |
9faa346699 | ||
![]() |
d5b6a686f8 | ||
![]() |
492d006f64 | ||
![]() |
d999320c24 | ||
![]() |
5142d12e7e | ||
![]() |
453c613571 | ||
![]() |
ccc34ae4d9 | ||
![]() |
4362b5c50b | ||
![]() |
2d6023446c | ||
![]() |
dcd43dc019 | ||
![]() |
d8e70f9693 | ||
![]() |
7a26e8bd65 | ||
![]() |
d56287587c | ||
![]() |
8793613117 | ||
![]() |
92afec75e7 | ||
![]() |
ca71ec24f8 | ||
![]() |
524550f6e3 | ||
![]() |
fe31131fc1 | ||
![]() |
a7c1fe3425 | ||
![]() |
d7b305bf29 | ||
![]() |
0bd0c850da | ||
![]() |
88bb40f94b | ||
![]() |
120e2a2cd5 | ||
![]() |
cbb6821814 | ||
![]() |
75bf374a8f | ||
![]() |
bf995981d3 | ||
![]() |
a6fdabcd2f | ||
![]() |
0e6886d00a | ||
![]() |
459d6aadd9 | ||
![]() |
21d533c7cf | ||
![]() |
211566488d | ||
![]() |
282aab2e55 | ||
![]() |
6f41865474 | ||
![]() |
e4bcfd8f39 | ||
![]() |
504cfcf8ff | ||
![]() |
6e13914712 | ||
![]() |
9aaf670092 | ||
![]() |
c204f6d5a4 | ||
![]() |
91bba042b5 | ||
![]() |
2ca6135c06 | ||
![]() |
9ef3cbc177 | ||
![]() |
8a3906687a | ||
![]() |
3192a14c9d | ||
![]() |
b510a86f4d | ||
![]() |
fcf14eaa8b | ||
![]() |
137dd0bbe8 | ||
![]() |
4b5b75a57a | ||
![]() |
68c497e5fc | ||
![]() |
1da7c85973 | ||
![]() |
fe75aab724 | ||
![]() |
85bcac1fb7 | ||
![]() |
72287cc7cb | ||
![]() |
1f1d212c1d | ||
![]() |
cded1284de | ||
![]() |
df97e7ebb5 | ||
![]() |
d80c9a27d3 | ||
![]() |
3469ec9b6b | ||
![]() |
d4db8f4b18 | ||
![]() |
384744ec44 | ||
![]() |
76633db25e | ||
![]() |
6b823d0fa0 | ||
![]() |
798dda5236 | ||
![]() |
2b90a17d5e | ||
![]() |
6387539980 | ||
![]() |
cb17fd0866 | ||
![]() |
17bac5a904 | ||
![]() |
c34123ffe3 | ||
![]() |
c755885bbb | ||
![]() |
f49e3f0664 | ||
![]() |
7852ac2071 | ||
![]() |
60358e7ac4 | ||
![]() |
f32bdbdeac | ||
![]() |
c58c629d0e | ||
![]() |
a091f46100 | ||
![]() |
76e8652492 | ||
![]() |
2606b910f1 | ||
![]() |
9440d687d3 | ||
![]() |
216f5c2213 | ||
![]() |
dbadef1c4e | ||
![]() |
1baf80cfe6 | ||
![]() |
2e50bfccf4 | ||
![]() |
07a3d88397 | ||
![]() |
6e5ce8e0b1 | ||
![]() |
c1d1ddd3b7 | ||
![]() |
bd5f274cf3 | ||
![]() |
38045165d4 | ||
![]() |
9e7721d2a9 | ||
![]() |
1d593e0495 | ||
![]() |
9b263c7237 | ||
![]() |
ca05c1b819 | ||
![]() |
2107ed7ab7 | ||
![]() |
9fd69f668a | ||
![]() |
8800614bff | ||
![]() |
867ddb4809 | ||
![]() |
5cf31d3351 |
1
.gitignore
vendored
@@ -5,6 +5,7 @@ node_modules
|
|||||||
|
|
||||||
build/files.wxs
|
build/files.wxs
|
||||||
dist
|
dist
|
||||||
|
*/dist
|
||||||
|
|
||||||
*.xcworkspacedata
|
*.xcworkspacedata
|
||||||
*.xcuserstate
|
*.xcuserstate
|
||||||
|
@@ -6,7 +6,7 @@ matrix:
|
|||||||
env: BUILD_FOR=macos
|
env: BUILD_FOR=macos
|
||||||
|
|
||||||
language: node_js
|
language: node_js
|
||||||
node_js: 8
|
node_js: 10
|
||||||
|
|
||||||
cache:
|
cache:
|
||||||
directories:
|
directories:
|
||||||
@@ -14,8 +14,7 @@ cache:
|
|||||||
- app/node_modules
|
- app/node_modules
|
||||||
|
|
||||||
before_install:
|
before_install:
|
||||||
- yarn install
|
- yarn
|
||||||
- scripts/install-deps.js
|
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- scripts/build-native.js
|
- scripts/build-native.js
|
||||||
|
@@ -37,7 +37,7 @@ Plugins can be installed directly from the Settings view inside Terminus.
|
|||||||
* [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-title-control) - allows modifying the title of the terminal tabs by providing a prefix, suffix, and/or strings to be removed
|
* [title-control](https://github.com/kbjr/terminus-title-control) - allows modifying the title of the terminal tabs by providing a prefix, suffix, and/or strings to be removed
|
||||||
* [scrollbar](https://github.com/kbjr/terminus-scrollbar) - adds a scrollbar to terminal tabs
|
* [scrollbar](https://github.com/kbjr/terminus-scrollbar) - adds a scrollbar to terminal tabs
|
||||||
* [quick-cmds](https://github.com/Domain/terminus-quick-cmds) - quicklky send commands to one or all terminal tabs
|
* [quick-cmds](https://github.com/Domain/terminus-quick-cmds) - quickly send commands to one or all terminal tabs
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@@ -1,91 +1,55 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
<!-- Generator: Adobe Illustrator 23.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
<svg
|
viewBox="0 0 1024 1024" style="enable-background:new 0 0 1024 1024;" xml:space="preserve">
|
||||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
<style type="text/css">
|
||||||
xmlns:cc="http://creativecommons.org/ns#"
|
.st0{fill:url(#SVGID_1_);}
|
||||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
.st1{opacity:0.16;fill:url(#SVGID_2_);}
|
||||||
xmlns:svg="http://www.w3.org/2000/svg"
|
.st2{fill:url(#SVGID_3_);}
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
.st3{opacity:0.16;fill:url(#SVGID_4_);}
|
||||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
.st4{fill:url(#SVGID_5_);}
|
||||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
.st5{opacity:0.15;fill:url(#SVGID_6_);}
|
||||||
width="150mm"
|
.st6{fill:url(#SVGID_7_);}
|
||||||
height="150mm"
|
</style>
|
||||||
viewBox="0 0 150 150"
|
<g>
|
||||||
version="1.1"
|
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="260.9675" y1="871.1813" x2="919.1845" y2="491.1596">
|
||||||
id="svg8"
|
<stop offset="0" style="stop-color:#669ABD"/>
|
||||||
inkscape:version="0.92.2 (5c3e80d, 2017-08-06)"
|
<stop offset="1" style="stop-color:#77DBDB"/>
|
||||||
sodipodi:docname="logo.svg"
|
</linearGradient>
|
||||||
inkscape:export-filename="/home/eugene/Work/term/build/icons/512x512.png"
|
<polygon class="st0" points="297.54,934.52 882.6,596.72 882.61,427.82 297.54,765.65 "/>
|
||||||
inkscape:export-xdpi="86.699997"
|
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="553.5051" y1="617.8278" x2="626.647" y2="744.5132">
|
||||||
inkscape:export-ydpi="86.699997">
|
<stop offset="0.5588" style="stop-color:#000000;stop-opacity:0"/>
|
||||||
<defs
|
<stop offset="1" style="stop-color:#000000"/>
|
||||||
id="defs2" />
|
</linearGradient>
|
||||||
<sodipodi:namedview
|
<polygon class="st1" points="297.54,934.52 882.6,596.72 882.61,427.82 297.54,765.65 "/>
|
||||||
id="base"
|
</g>
|
||||||
pagecolor="#ffffff"
|
<g>
|
||||||
bordercolor="#666666"
|
<linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="114.6631" y1="744.5275" x2="334.0905" y2="871.2141">
|
||||||
borderopacity="1.0"
|
<stop offset="0" style="stop-color:#6A8FAD"/>
|
||||||
inkscape:pageopacity="0.0"
|
<stop offset="1" style="stop-color:#669ABD"/>
|
||||||
inkscape:pageshadow="2"
|
</linearGradient>
|
||||||
inkscape:zoom="0.49497475"
|
<polygon class="st2" points="151.23,681.18 151.22,850.09 297.54,934.52 297.54,765.65 "/>
|
||||||
inkscape:cx="85.897128"
|
<linearGradient id="SVGID_4_" gradientUnits="userSpaceOnUse" x1="260.9478" y1="744.5281" x2="187.8059" y2="871.2135">
|
||||||
inkscape:cy="375.72042"
|
<stop offset="0.5588" style="stop-color:#000000;stop-opacity:0"/>
|
||||||
inkscape:document-units="mm"
|
<stop offset="1" style="stop-color:#000000"/>
|
||||||
inkscape:current-layer="layer1"
|
</linearGradient>
|
||||||
showgrid="false"
|
<polygon class="st3" points="151.23,681.18 151.22,850.09 297.54,934.52 297.54,765.65 "/>
|
||||||
inkscape:snap-bbox="true"
|
</g>
|
||||||
inkscape:window-width="1366"
|
<g>
|
||||||
inkscape:window-height="692"
|
<linearGradient id="SVGID_5_" gradientUnits="userSpaceOnUse" x1="114.663" y1="237.793" x2="553.5026" y2="491.1571">
|
||||||
inkscape:window-x="0"
|
<stop offset="0" style="stop-color:#6A8FAD"/>
|
||||||
inkscape:window-y="0"
|
<stop offset="1" style="stop-color:#669ABD"/>
|
||||||
inkscape:window-maximized="1"
|
</linearGradient>
|
||||||
fit-margin-top="0"
|
<polygon class="st4" points="151.23,174.45 151.21,343.36 443.79,512.27 590.08,427.81 "/>
|
||||||
fit-margin-left="0"
|
<linearGradient id="SVGID_6_" gradientUnits="userSpaceOnUse" x1="370.6562" y1="301.1281" x2="297.5094" y2="427.8221">
|
||||||
fit-margin-right="0"
|
<stop offset="0.5588" style="stop-color:#000000;stop-opacity:0"/>
|
||||||
fit-margin-bottom="0"
|
<stop offset="1" style="stop-color:#000000"/>
|
||||||
inkscape:snap-intersection-paths="true"
|
</linearGradient>
|
||||||
inkscape:object-paths="true" />
|
<polygon class="st5" points="151.23,174.45 151.21,343.36 443.79,512.27 590.08,427.81 "/>
|
||||||
<metadata
|
</g>
|
||||||
id="metadata5">
|
<linearGradient id="SVGID_7_" gradientUnits="userSpaceOnUse" x1="78.0912" y1="554.4979" x2="736.3375" y2="174.4593">
|
||||||
<rdf:RDF>
|
<stop offset="0" style="stop-color:#CCECFF"/>
|
||||||
<cc:Work
|
<stop offset="1" style="stop-color:#9FECED"/>
|
||||||
rdf:about="">
|
</linearGradient>
|
||||||
<dc:format>image/svg+xml</dc:format>
|
<polygon class="st6" points="297.51,765.64 151.23,681.18 590.08,427.81 151.23,174.45 297.5,90 882.61,427.82 "/>
|
||||||
<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(-10.356544,-82.309525)">
|
|
||||||
<path
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
id="path138"
|
|
||||||
style="opacity:0.9;fill:#bfd9f1;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.12037313px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1"
|
|
||||||
d="M 33.048081,103.66303 101.30357,143.02426 80.80219,154.86063 33.048089,125.73315 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:1.12037313px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1"
|
|
||||||
d="m 141.59934,143.95811 0.051,23.16109 -87.420905,49.42651 -0.0034,-22.16232 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:1.12037313px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1"
|
|
||||||
d="m 33.233182,182.28294 20.992812,12.1202 0.0034,22.16208 -20.996251,-12.19239 z"
|
|
||||||
sodipodi:nodetypes="ccccc" />
|
|
||||||
<path
|
|
||||||
style="opacity:0.9;fill:#9dbef0;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.12649226px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1"
|
|
||||||
d="m 52.236336,92.196079 -19.484508,11.249681 68.551742,39.5785 -68.366041,39.4708 21.107487,12.18633 68.366044,-39.4708 19.48451,-11.24968 z"
|
|
||||||
id="path134"
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
sodipodi:nodetypes="cccccccc" />
|
|
||||||
</g>
|
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 2.9 KiB |
@@ -103,7 +103,7 @@ export class Application {
|
|||||||
{
|
{
|
||||||
label: 'Preferences',
|
label: 'Preferences',
|
||||||
accelerator: 'Cmd+,',
|
accelerator: 'Cmd+,',
|
||||||
async click () {
|
click: async () => {
|
||||||
if (!this.hasWindows()) {
|
if (!this.hasWindows()) {
|
||||||
await this.newWindow()
|
await this.newWindow()
|
||||||
}
|
}
|
||||||
|
@@ -13,6 +13,9 @@ export function parseArgs (argv, cwd) {
|
|||||||
.command('run [command...]', 'run a command in the terminal', {
|
.command('run [command...]', 'run a command in the terminal', {
|
||||||
command: { type: 'string' },
|
command: { type: 'string' },
|
||||||
})
|
})
|
||||||
|
.command('profile [profileName]', 'open a tab with specified profile', {
|
||||||
|
profileName: { type: 'string' },
|
||||||
|
})
|
||||||
.command('paste [text]', 'paste stdin into the active tab', yargs => {
|
.command('paste [text]', 'paste stdin into the active tab', yargs => {
|
||||||
return yargs.option('escape', {
|
return yargs.option('escape', {
|
||||||
alias: 'e',
|
alias: 'e',
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
import { Subject, Observable } from 'rxjs'
|
import { Subject, Observable } from 'rxjs'
|
||||||
|
import { debounceTime } from 'rxjs/operators'
|
||||||
import { BrowserWindow, app, ipcMain, Rectangle } from 'electron'
|
import { BrowserWindow, app, ipcMain, Rectangle } from 'electron'
|
||||||
import ElectronConfig = require('electron-config')
|
import ElectronConfig = require('electron-config')
|
||||||
import * as os from 'os'
|
import * as os from 'os'
|
||||||
@@ -143,6 +144,16 @@ export class Window {
|
|||||||
this.visible.next(false)
|
this.visible.next(false)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
let moveSubscription = new Observable<void>(observer => {
|
||||||
|
this.window.on('move', () => observer.next())
|
||||||
|
}).pipe(debounceTime(250)).subscribe(() => {
|
||||||
|
this.window.webContents.send('host:window-moved')
|
||||||
|
})
|
||||||
|
|
||||||
|
this.window.on('closed', () => {
|
||||||
|
moveSubscription.unsubscribe()
|
||||||
|
})
|
||||||
|
|
||||||
this.window.on('enter-full-screen', () => this.window.webContents.send('host:window-enter-full-screen'))
|
this.window.on('enter-full-screen', () => this.window.webContents.send('host:window-enter-full-screen'))
|
||||||
this.window.on('leave-full-screen', () => this.window.webContents.send('host:window-leave-full-screen'))
|
this.window.on('leave-full-screen', () => this.window.webContents.send('host:window-leave-full-screen'))
|
||||||
|
|
||||||
@@ -250,7 +261,10 @@ export class Window {
|
|||||||
this.window.moveTop()
|
this.window.moveTop()
|
||||||
})
|
})
|
||||||
|
|
||||||
ipcMain.on('window-close', () => {
|
ipcMain.on('window-close', event => {
|
||||||
|
if (event.sender !== this.window.webContents) {
|
||||||
|
return
|
||||||
|
}
|
||||||
this.closing = true
|
this.closing = true
|
||||||
this.window.close()
|
this.window.close()
|
||||||
})
|
})
|
||||||
|
@@ -13,13 +13,13 @@
|
|||||||
"watch": "webpack --progress --color --watch"
|
"watch": "webpack --progress --color --watch"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@angular/animations": "7.0.0",
|
"@angular/animations": "7.2.0-rc.0",
|
||||||
"@angular/common": "7.0.0",
|
"@angular/common": "7.2.0-rc.0",
|
||||||
"@angular/compiler": "7.0.0",
|
"@angular/compiler": "7.2.0-rc.0",
|
||||||
"@angular/core": "7.0.0",
|
"@angular/core": "7.2.0-rc.0",
|
||||||
"@angular/forms": "7.0.0",
|
"@angular/forms": "7.2.0-rc.0",
|
||||||
"@angular/platform-browser": "7.0.0",
|
"@angular/platform-browser": "7.2.0-rc.0",
|
||||||
"@angular/platform-browser-dynamic": "7.0.0",
|
"@angular/platform-browser-dynamic": "7.2.0-rc.0",
|
||||||
"@ng-bootstrap/ng-bootstrap": "^3.3.1",
|
"@ng-bootstrap/ng-bootstrap": "^3.3.1",
|
||||||
"devtron": "1.4.0",
|
"devtron": "1.4.0",
|
||||||
"electron-config": "0.2.1",
|
"electron-config": "0.2.1",
|
||||||
|
@@ -1,7 +1,9 @@
|
|||||||
import '../lib/lru'
|
import '../lib/lru'
|
||||||
import 'source-sans-pro'
|
import 'source-sans-pro'
|
||||||
import 'source-code-pro/source-code-pro.css'
|
import 'source-code-pro/source-code-pro.css'
|
||||||
import 'font-awesome/css/font-awesome.css'
|
import '@fortawesome/fontawesome-free/css/solid.css'
|
||||||
|
import '@fortawesome/fontawesome-free/css/brands.css'
|
||||||
|
import '@fortawesome/fontawesome-free/css/fontawesome.css'
|
||||||
import 'ngx-toastr/toastr.css'
|
import 'ngx-toastr/toastr.css'
|
||||||
import './preload.scss'
|
import './preload.scss'
|
||||||
|
|
||||||
|
@@ -15,6 +15,8 @@ import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'
|
|||||||
import { getRootModule } from './app.module'
|
import { getRootModule } from './app.module'
|
||||||
import { findPlugins, loadPlugins, IPluginInfo } from './plugins'
|
import { findPlugins, loadPlugins, IPluginInfo } from './plugins'
|
||||||
|
|
||||||
|
;(process as any).enablePromiseAPI = true
|
||||||
|
|
||||||
if (process.platform === 'win32') {
|
if (process.platform === 'win32') {
|
||||||
process.env.HOME = process.env.HOMEDRIVE + process.env.HOMEPATH
|
process.env.HOME = process.env.HOMEDRIVE + process.env.HOMEPATH
|
||||||
}
|
}
|
||||||
@@ -34,7 +36,7 @@ async function bootstrap (plugins: IPluginInfo[], safeMode = false): Promise<NgM
|
|||||||
})
|
})
|
||||||
let module = getRootModule(pluginsModules)
|
let module = getRootModule(pluginsModules)
|
||||||
window['rootModule'] = module
|
window['rootModule'] = module
|
||||||
return await platformBrowserDynamic().bootstrapModule(module)
|
return platformBrowserDynamic().bootstrapModule(module)
|
||||||
}
|
}
|
||||||
|
|
||||||
findPlugins().then(async plugins => {
|
findPlugins().then(async plugins => {
|
||||||
|
@@ -118,7 +118,7 @@ export async function findPlugins (): Promise<IPluginInfo[]> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let info = JSON.parse(await fs.readFile(infoPath, {encoding: 'utf-8'}))
|
let info = JSON.parse(await fs.readFile(infoPath, { encoding: 'utf-8' }))
|
||||||
if (!info.keywords || !(info.keywords.includes('terminus-plugin') || info.keywords.includes('terminus-builtin-plugin'))) {
|
if (!info.keywords || !(info.keywords.includes('terminus-plugin') || info.keywords.includes('terminus-builtin-plugin'))) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@@ -2,52 +2,52 @@
|
|||||||
# yarn lockfile v1
|
# yarn lockfile v1
|
||||||
|
|
||||||
|
|
||||||
"@angular/animations@7.0.0":
|
"@angular/animations@7.2.0-rc.0":
|
||||||
version "7.0.0"
|
version "7.2.0-rc.0"
|
||||||
resolved "https://registry.yarnpkg.com/@angular/animations/-/animations-7.0.0.tgz#5c9e1683063c29df10253b7dc5bb9b13694ee396"
|
resolved "https://registry.yarnpkg.com/@angular/animations/-/animations-7.2.0-rc.0.tgz#12849f8ab104d309ec99c0ceb170a895c15d3d44"
|
||||||
integrity sha512-IYdryQXdYfPvhJpExLSAr0o9rlUeyVS++a6h/sjqN1dkUt/yJBHLRreuHx8Udvlj2nH70raHJgevk8FwhAkTdA==
|
integrity sha512-CRQNQ6QVTuf4nCHVLVpKQx7YPpNPfnTF79KVWzHefkkyS3URRuEgvE4jCED4oTJ4BEsmkjXyt51VeDV0FgqQFg==
|
||||||
dependencies:
|
dependencies:
|
||||||
tslib "^1.9.0"
|
tslib "^1.9.0"
|
||||||
|
|
||||||
"@angular/common@7.0.0":
|
"@angular/common@7.2.0-rc.0":
|
||||||
version "7.0.0"
|
version "7.2.0-rc.0"
|
||||||
resolved "https://registry.yarnpkg.com/@angular/common/-/common-7.0.0.tgz#29206614d2b8dc79e5207b7dc6f9fc559e9a24f2"
|
resolved "https://registry.yarnpkg.com/@angular/common/-/common-7.2.0-rc.0.tgz#60d3540c6cdcf3440f67e2c15cf8f1c7b1160d9d"
|
||||||
integrity sha512-jp6MA6EOq/a1m+F0c1aZC345pAYYYFpN1m7GMM91JlqkjzJMhyYVk+Bod9xQOEWadcpY+RFudG+jRsPCMO8bvQ==
|
integrity sha512-Xv60KEP1kpF74kpN1xtps0W++PUXLUMK/0tDblUZH7tBWvS0XwEwtuK5B6wcs+I5nqZkPgvlvOyiVZvOLraWOg==
|
||||||
dependencies:
|
dependencies:
|
||||||
tslib "^1.9.0"
|
tslib "^1.9.0"
|
||||||
|
|
||||||
"@angular/compiler@7.0.0":
|
"@angular/compiler@7.2.0-rc.0":
|
||||||
version "7.0.0"
|
version "7.2.0-rc.0"
|
||||||
resolved "https://registry.yarnpkg.com/@angular/compiler/-/compiler-7.0.0.tgz#f953a213a01e4736e94fe1a370b07e13e2393b71"
|
resolved "https://registry.yarnpkg.com/@angular/compiler/-/compiler-7.2.0-rc.0.tgz#603dbec25d6c2beea08a293c68c39b40e2ea81e2"
|
||||||
integrity sha512-4fkohfGyG1BEpeYenOartuJmduyZ/R3XQx46hDDiR/9A8/Go4qLGkgr9Bd/JL/gPIR1XAHH9D5ii2sh+28ZEmA==
|
integrity sha512-tvgGJx0urSz/qn6upmcjX3N3dyWQ9m5mQOwJxmN4qekxjOtSRml5yt2KtlaUTkGsjkEmEVfSHel+X1TwzBdhYw==
|
||||||
dependencies:
|
dependencies:
|
||||||
tslib "^1.9.0"
|
tslib "^1.9.0"
|
||||||
|
|
||||||
"@angular/core@7.0.0":
|
"@angular/core@7.2.0-rc.0":
|
||||||
version "7.0.0"
|
version "7.2.0-rc.0"
|
||||||
resolved "https://registry.yarnpkg.com/@angular/core/-/core-7.0.0.tgz#01e9db9074a1db1c47a32f745b787d1c86f5d61a"
|
resolved "https://registry.yarnpkg.com/@angular/core/-/core-7.2.0-rc.0.tgz#57c0e26130288c3b58466f079828c028bdf6221f"
|
||||||
integrity sha512-DjVyWNGBWKEeBvxeXy8FGBNlnr/W/tNygOZEd6/uCktcXTG4DNyNQrWuNZUKEpr7RuIT3YVMj+UNwgTq0jB/9g==
|
integrity sha512-2u11TNlLorw3JhuczCPwl8UmxE+ja2Q/ghBl8iYi4SIBWiBO1K0wVT13Ts7eojk63yZcg60lyYYCegXBmHLTuw==
|
||||||
dependencies:
|
dependencies:
|
||||||
tslib "^1.9.0"
|
tslib "^1.9.0"
|
||||||
|
|
||||||
"@angular/forms@7.0.0":
|
"@angular/forms@7.2.0-rc.0":
|
||||||
version "7.0.0"
|
version "7.2.0-rc.0"
|
||||||
resolved "https://registry.yarnpkg.com/@angular/forms/-/forms-7.0.0.tgz#672c306b13e94a20b72c096214642a326c43699a"
|
resolved "https://registry.yarnpkg.com/@angular/forms/-/forms-7.2.0-rc.0.tgz#4eb473018084bb81be3e2e1ae8afa8d2b2117a6c"
|
||||||
integrity sha512-rTg1UHq9gHR6zY3Kkip1KCm/YTck/rlR8CvVFIMwF0bdQxUCT51SXVn58nXts9yDaieABcGaQHNkQn1mARslgw==
|
integrity sha512-OWP1zzYQiuqtoltdlhkcVjHxg78exbt7z1lr8RSjybr/Snc5zSFhnZF6byasd/4lzVySuujsMXkTK7D8x6hedA==
|
||||||
dependencies:
|
dependencies:
|
||||||
tslib "^1.9.0"
|
tslib "^1.9.0"
|
||||||
|
|
||||||
"@angular/platform-browser-dynamic@7.0.0":
|
"@angular/platform-browser-dynamic@7.2.0-rc.0":
|
||||||
version "7.0.0"
|
version "7.2.0-rc.0"
|
||||||
resolved "https://registry.yarnpkg.com/@angular/platform-browser-dynamic/-/platform-browser-dynamic-7.0.0.tgz#2b2a50b5a8176bee257f90ee47b1d873502f7182"
|
resolved "https://registry.yarnpkg.com/@angular/platform-browser-dynamic/-/platform-browser-dynamic-7.2.0-rc.0.tgz#5ea47d094c53a0ba34ecbb0dfdcef452fa05dc9a"
|
||||||
integrity sha512-lH2KuH+Em1y/mTOE6yTJmsOxYkMbYKzKLP9gYzc9vZu3er1df6Jx6jxefeBmAr9v+kNCLnpnHWHz2y4GzAesJA==
|
integrity sha512-uqT27oh9m58L6MUjgvT+7NpAFbigOnnTUWMsCLijNUKd7i37T6UxTVKPvuqNHlaLXsmDRxVHN3INI0IrWZ3R+w==
|
||||||
dependencies:
|
dependencies:
|
||||||
tslib "^1.9.0"
|
tslib "^1.9.0"
|
||||||
|
|
||||||
"@angular/platform-browser@7.0.0":
|
"@angular/platform-browser@7.2.0-rc.0":
|
||||||
version "7.0.0"
|
version "7.2.0-rc.0"
|
||||||
resolved "https://registry.yarnpkg.com/@angular/platform-browser/-/platform-browser-7.0.0.tgz#8c13a6380cf465b3628e5b576a1313e9b4976093"
|
resolved "https://registry.yarnpkg.com/@angular/platform-browser/-/platform-browser-7.2.0-rc.0.tgz#c6d1f0b2328b1d81649bea70c23edc33de729015"
|
||||||
integrity sha512-XyvL30d6meJ+SXlOmdR+sxoLdSvkQdmVNvpdvUzAHC/EqwA/byg4V3bTe5lpZmypclgFCjkGoTsz6uOnnwlQhw==
|
integrity sha512-r0ak7SVLWrivd4S0MXWmqNLeF6NNOBAopnjrhUu2j5I00u7/QfLrX0E5zRlJ8JkARVjer6Wm+D1ztlOWw5jHag==
|
||||||
dependencies:
|
dependencies:
|
||||||
tslib "^1.9.0"
|
tslib "^1.9.0"
|
||||||
|
|
||||||
|
@@ -7,18 +7,18 @@ environment:
|
|||||||
nodejs_version: "10"
|
nodejs_version: "10"
|
||||||
|
|
||||||
cache:
|
cache:
|
||||||
- '%USERPROFILE%\.electron'
|
- "%USERPROFILE%\\.electron"
|
||||||
|
- "%LOCALAPPDATA%\\Yarn"
|
||||||
|
|
||||||
version: "{build}"
|
version: "{build}"
|
||||||
|
|
||||||
install:
|
install:
|
||||||
- ps: Install-Product node $env:nodejs_version $env:platform
|
- ps: Install-Product node $env:nodejs_version $env:platform
|
||||||
- npm install
|
- yarn
|
||||||
- node scripts/install-deps.js
|
|
||||||
- node scripts/build-native.js
|
- node scripts/build-native.js
|
||||||
|
|
||||||
build_script:
|
build_script:
|
||||||
- npm run build
|
- yarn run build
|
||||||
- node scripts/prepackage-plugins.js
|
- node scripts/prepackage-plugins.js
|
||||||
- node scripts/build-windows.js
|
- node scripts/build-windows.js
|
||||||
|
|
||||||
|
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 9.8 KiB |
Before Width: | Height: | Size: 644 B After Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 8.3 KiB After Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 3.0 KiB |
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 36 KiB |
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 5.2 KiB |
@@ -1,124 +1,57 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
<!-- Generator: Adobe Illustrator 23.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
<svg
|
viewBox="0 0 1024 1024" style="enable-background:new 0 0 1024 1024;" xml:space="preserve">
|
||||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
<style type="text/css">
|
||||||
xmlns:cc="http://creativecommons.org/ns#"
|
.st0{opacity:0.8;fill:#00232E;}
|
||||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
.st1{fill:url(#SVGID_1_);}
|
||||||
xmlns:svg="http://www.w3.org/2000/svg"
|
.st2{opacity:0.16;fill:url(#SVGID_2_);}
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
.st3{fill:url(#SVGID_3_);}
|
||||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
.st4{opacity:0.16;fill:url(#SVGID_4_);}
|
||||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
.st5{fill:url(#SVGID_5_);}
|
||||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
.st6{opacity:0.15;fill:url(#SVGID_6_);}
|
||||||
width="150mm"
|
.st7{fill:url(#SVGID_7_);}
|
||||||
height="150mm"
|
</style>
|
||||||
viewBox="0 0 150 150"
|
<polygon class="st0" points="449.5,645.47 407.51,621.23 533.47,548.5 407.51,475.77 449.5,451.53 617.45,548.5 "/>
|
||||||
version="1.1"
|
<g>
|
||||||
id="svg8"
|
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="439.0065" y1="603.0394" x2="627.9464" y2="493.9549">
|
||||||
inkscape:version="0.92.2 (5c3e80d, 2017-08-06)"
|
<stop offset="0" style="stop-color:#669ABD"/>
|
||||||
sodipodi:docname="icon.svg"
|
<stop offset="1" style="stop-color:#77DBDB"/>
|
||||||
inkscape:export-filename="/home/eugene/Work/term/build/icons/512x512.png"
|
</linearGradient>
|
||||||
inkscape:export-xdpi="86.699997"
|
<polygon class="st1" points="449.5,621.22 617.45,524.25 617.45,475.77 449.5,572.75 "/>
|
||||||
inkscape:export-ydpi="86.699997">
|
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="522.9788" y1="530.3148" x2="543.9741" y2="566.6795">
|
||||||
<defs
|
<stop offset="0.5588" style="stop-color:#000000;stop-opacity:0"/>
|
||||||
id="defs2">
|
<stop offset="1" style="stop-color:#000000"/>
|
||||||
<linearGradient
|
</linearGradient>
|
||||||
inkscape:collect="always"
|
<polygon class="st2" points="449.5,621.22 617.45,524.25 617.45,475.77 449.5,572.75 "/>
|
||||||
id="linearGradient4649">
|
</g>
|
||||||
<stop
|
<g>
|
||||||
style="stop-color:#000316;stop-opacity:1"
|
<linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="397.0101" y1="566.6837" x2="459.9963" y2="603.0487">
|
||||||
offset="0"
|
<stop offset="0" style="stop-color:#6A8FAD"/>
|
||||||
id="stop4645" />
|
<stop offset="1" style="stop-color:#669ABD"/>
|
||||||
<stop
|
</linearGradient>
|
||||||
style="stop-color:#190065;stop-opacity:1"
|
<polygon class="st3" points="407.51,548.5 407.5,596.99 449.5,621.22 449.5,572.75 "/>
|
||||||
offset="1"
|
<linearGradient id="SVGID_4_" gradientUnits="userSpaceOnUse" x1="439.0009" y1="566.6838" x2="418.0056" y2="603.0486">
|
||||||
id="stop4647" />
|
<stop offset="0.5588" style="stop-color:#000000;stop-opacity:0"/>
|
||||||
</linearGradient>
|
<stop offset="1" style="stop-color:#000000"/>
|
||||||
<linearGradient
|
</linearGradient>
|
||||||
inkscape:collect="always"
|
<polygon class="st4" points="407.51,548.5 407.5,596.99 449.5,621.22 449.5,572.75 "/>
|
||||||
xlink:href="#linearGradient4649"
|
</g>
|
||||||
id="linearGradient4651"
|
<g>
|
||||||
x1="89.26284"
|
<linearGradient id="SVGID_5_" gradientUnits="userSpaceOnUse" x1="397.0101" y1="421.2265" x2="522.9781" y2="493.9542">
|
||||||
y1="85.146751"
|
<stop offset="0" style="stop-color:#6A8FAD"/>
|
||||||
x2="89.26284"
|
<stop offset="1" style="stop-color:#669ABD"/>
|
||||||
y2="229.47229"
|
</linearGradient>
|
||||||
gradientUnits="userSpaceOnUse"
|
<polygon class="st5" points="407.51,403.04 407.5,451.53 491.49,500.01 533.48,475.77 "/>
|
||||||
gradientTransform="matrix(0.82182032,0,0,0.82182032,15.208802,28.029361)" />
|
<linearGradient id="SVGID_6_" gradientUnits="userSpaceOnUse" x1="470.4924" y1="439.4067" x2="449.4958" y2="475.774">
|
||||||
</defs>
|
<stop offset="0.5588" style="stop-color:#000000;stop-opacity:0"/>
|
||||||
<sodipodi:namedview
|
<stop offset="1" style="stop-color:#000000"/>
|
||||||
id="base"
|
</linearGradient>
|
||||||
pagecolor="#ffffff"
|
<polygon class="st6" points="407.51,403.04 407.5,451.53 491.49,500.01 533.48,475.77 "/>
|
||||||
bordercolor="#666666"
|
</g>
|
||||||
borderopacity="1.0"
|
<linearGradient id="SVGID_7_" gradientUnits="userSpaceOnUse" x1="386.5123" y1="512.136" x2="575.4605" y2="403.0467">
|
||||||
inkscape:pageopacity="0.0"
|
<stop offset="0" style="stop-color:#CCECFF"/>
|
||||||
inkscape:pageshadow="2"
|
<stop offset="1" style="stop-color:#9FECED"/>
|
||||||
inkscape:zoom="0.49497475"
|
</linearGradient>
|
||||||
inkscape:cx="85.897128"
|
<polygon class="st7" points="449.5,572.74 407.51,548.5 533.48,475.77 407.51,403.04 449.49,378.8 617.45,475.77 "/>
|
||||||
inkscape:cy="375.72042"
|
|
||||||
inkscape:document-units="mm"
|
|
||||||
inkscape:current-layer="layer1"
|
|
||||||
showgrid="false"
|
|
||||||
inkscape:snap-bbox="true"
|
|
||||||
inkscape:window-width="1366"
|
|
||||||
inkscape:window-height="692"
|
|
||||||
inkscape:window-x="0"
|
|
||||||
inkscape:window-y="0"
|
|
||||||
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" />
|
|
||||||
<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(-10.356544,-82.309525)">
|
|
||||||
<rect
|
|
||||||
id="rect168"
|
|
||||||
width="123.27305"
|
|
||||||
height="123.27305"
|
|
||||||
x="23.72002"
|
|
||||||
y="95.673004"
|
|
||||||
style="fill:url(#linearGradient4651);fill-opacity:1;stroke-width:0.21743995"
|
|
||||||
rx="8.2182035"
|
|
||||||
ry="8.2182035" />
|
|
||||||
<path
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
id="path138"
|
|
||||||
style="opacity:0.9;fill:#bfd9f1;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.82182032px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1"
|
|
||||||
d="m 47.511243,117.17807 50.067023,28.8724 -15.038249,8.68226 -35.028768,-21.3657 z"
|
|
||||||
sodipodi:nodetypes="ccccc" />
|
|
||||||
<path
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
id="path116"
|
|
||||||
style="opacity:0.9;fill:#6666af;fill-rule:evenodd;stroke:none;stroke-width:0.82182032px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1;fill-opacity:1"
|
|
||||||
d="m 127.13617,146.73547 0.0374,16.98921 -64.125308,36.25552 -0.0025,-16.25659 z"
|
|
||||||
sodipodi:nodetypes="ccccc" />
|
|
||||||
<path
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
id="path118"
|
|
||||||
style="opacity:0.9;fill:#bfd9f1;fill-rule:evenodd;stroke:none;stroke-width:0.82182032px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1;fill-opacity:1"
|
|
||||||
d="m 47.647019,174.84764 15.398727,8.89046 0.0025,16.25641 -15.401249,-8.94341 z"
|
|
||||||
sodipodi:nodetypes="ccccc" />
|
|
||||||
<path
|
|
||||||
style="opacity:0.9;fill:#9dbef0;fill-rule:evenodd;stroke:none;stroke-width:0.82630885px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1;fill-opacity:1"
|
|
||||||
d="m 61.586284,108.76679 -14.292349,8.25191 50.284331,29.03177 -50.148115,28.95277 15.482843,8.93896 50.148116,-28.95277 14.29235,-8.25191 z"
|
|
||||||
id="path134"
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
sodipodi:nodetypes="cccccccc" />
|
|
||||||
</g>
|
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 112 KiB After Width: | Height: | Size: 361 KiB |
@@ -1,16 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
||||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
|
||||||
<svg width="100%" height="100%" viewBox="0 0 426 426" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421;">
|
|
||||||
<g id="Layer-1" serif:id="Layer 1" transform="matrix(1,0,0,1,-29.3571,-233.318)">
|
|
||||||
<g>
|
|
||||||
<path id="path138" d="M93.68,293.848L287.16,405.423L229.046,438.975L93.68,356.409L93.68,293.848Z" style="fill:url(#_Linear1);"/>
|
|
||||||
<path id="path118" d="M94.204,516.708L153.711,551.064L153.721,613.886L94.204,579.325L94.204,516.708Z" style="fill:url(#_Linear2);"/>
|
|
||||||
</g>
|
|
||||||
<path id="path116" d="M401.384,408.07L401.529,473.724L153.721,613.83L153.712,551.008L401.384,408.07Z" style="fill:rgb(0,94,167);fill-opacity:0.9;"/>
|
|
||||||
<path id="path134" d="M148.072,261.343L92.84,293.232L287.16,405.423L93.366,517.309L153.198,551.853L346.992,439.967L402.224,408.078L148.072,261.343Z" style="fill:rgb(7,147,255);fill-opacity:0.9;"/>
|
|
||||||
</g>
|
|
||||||
<defs>
|
|
||||||
<linearGradient id="_Linear1" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(193.48,0,0,320.038,93.6796,453.867)"><stop offset="0" style="stop-color:rgb(0,121,215);stop-opacity:0.9"/><stop offset="1" style="stop-color:rgb(40,97,156);stop-opacity:0.9"/></linearGradient>
|
|
||||||
<linearGradient id="_Linear2" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(193.48,0,0,320.038,93.6796,453.867)"><stop offset="0" style="stop-color:rgb(0,121,215);stop-opacity:0.9"/><stop offset="1" style="stop-color:rgb(40,97,156);stop-opacity:0.9"/></linearGradient>
|
|
||||||
</defs>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 1.8 KiB |
BIN
build/windows/squirrel.gif
Normal file
After Width: | Height: | Size: 9.2 KiB |
BIN
extras/UAC.exe
Normal file
25
package.json
@@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "term",
|
"name": "term",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@fortawesome/fontawesome-free": "^5.6.3",
|
||||||
"@types/electron-config": "^0.2.1",
|
"@types/electron-config": "^0.2.1",
|
||||||
"@types/electron-debug": "^1.1.0",
|
"@types/electron-debug": "^1.1.0",
|
||||||
"@types/fs-promise": "1.0.1",
|
"@types/fs-promise": "1.0.1",
|
||||||
@@ -13,20 +14,17 @@
|
|||||||
"core-js": "2.4.1",
|
"core-js": "2.4.1",
|
||||||
"cross-env": "4.0.0",
|
"cross-env": "4.0.0",
|
||||||
"css-loader": "0.28.0",
|
"css-loader": "0.28.0",
|
||||||
"electron": "3.0.8",
|
"electron": "4.0.0",
|
||||||
"electron-builder": "^20.27.1",
|
"electron-builder": "^20.38.4",
|
||||||
"electron-builder-squirrel-windows": "^20.28.3",
|
"electron-builder-squirrel-windows": "^20.28.3",
|
||||||
"electron-installer-snap": "^3.0.0",
|
"electron-installer-snap": "^3.0.0",
|
||||||
"electron-rebuild": "^1.8.2",
|
"electron-rebuild": "^1.8.2",
|
||||||
"file-loader": "^1.1.11",
|
"file-loader": "^1.1.11",
|
||||||
"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-loader": "2.2.3",
|
|
||||||
"node-abi": "^2.4.4",
|
"node-abi": "^2.4.4",
|
||||||
"node-gyp": "^3.6.2",
|
"node-gyp": "^3.8.0",
|
||||||
"node-sass": "^4.5.3",
|
"node-sass": "^4.5.3",
|
||||||
"npmlog": "4.1.0",
|
"npmlog": "4.1.0",
|
||||||
"npx": "^10.2.0",
|
"npx": "^10.2.0",
|
||||||
@@ -44,17 +42,20 @@
|
|||||||
"style-loader": "0.13.1",
|
"style-loader": "0.13.1",
|
||||||
"svg-inline-loader": "^0.8.0",
|
"svg-inline-loader": "^0.8.0",
|
||||||
"to-string-loader": "1.1.5",
|
"to-string-loader": "1.1.5",
|
||||||
"tslint": "5.1.0",
|
"tslint": "^5.12.0",
|
||||||
"tslint-config-standard": "5.0.2",
|
"tslint-config-standard": "^8.0.1",
|
||||||
"tslint-eslint-rules": "4.0.0",
|
"tslint-eslint-rules": "^5.4.0",
|
||||||
"typescript": "^3.1.3",
|
"typescript": "^3.1.3",
|
||||||
"url-loader": "^1.1.1",
|
"url-loader": "^1.1.1",
|
||||||
"val-loader": "0.5.0",
|
"val-loader": "0.5.0",
|
||||||
"webpack": "^4.22.0",
|
"webpack": "^4.27.1",
|
||||||
"webpack-cli": "^3.1.2",
|
"webpack-cli": "^3.1.2",
|
||||||
"yaml-loader": "0.4.0",
|
"yaml-loader": "0.4.0",
|
||||||
"yarn": "^1.10.1"
|
"yarn": "^1.10.1"
|
||||||
},
|
},
|
||||||
|
"resolutions": {
|
||||||
|
"*/node-abi": "^2.5.0"
|
||||||
|
},
|
||||||
"build": {
|
"build": {
|
||||||
"appId": "org.terminus",
|
"appId": "org.terminus",
|
||||||
"productName": "Terminus",
|
"productName": "Terminus",
|
||||||
@@ -76,6 +77,7 @@
|
|||||||
},
|
},
|
||||||
"squirrelWindows": {
|
"squirrelWindows": {
|
||||||
"iconUrl": "https://github.com/Eugeny/terminus/raw/master/build/windows/icon.ico",
|
"iconUrl": "https://github.com/Eugeny/terminus/raw/master/build/windows/icon.ico",
|
||||||
|
"loadingGif": "./build/windows/squirrel.gif",
|
||||||
"artifactName": "terminus-${version}-setup.exe"
|
"artifactName": "terminus-${version}-setup.exe"
|
||||||
},
|
},
|
||||||
"portable": {
|
"portable": {
|
||||||
@@ -84,6 +86,7 @@
|
|||||||
"mac": {
|
"mac": {
|
||||||
"category": "public.app-category.video",
|
"category": "public.app-category.video",
|
||||||
"icon": "./build/mac/icon.icns",
|
"icon": "./build/mac/icon.icns",
|
||||||
|
"artifactName": "terminus-${version}-macos.${ext}",
|
||||||
"publish": [
|
"publish": [
|
||||||
"github"
|
"github"
|
||||||
],
|
],
|
||||||
@@ -128,7 +131,7 @@
|
|||||||
"start": "cross-env DEV=1 electron app --debug",
|
"start": "cross-env DEV=1 electron app --debug",
|
||||||
"prod": "cross-env DEV=1 electron app",
|
"prod": "cross-env DEV=1 electron app",
|
||||||
"lint": "tslint -c tslint.json -t stylish terminus-*/src/**/*.ts terminus-*/src/*.ts app/src/*.ts",
|
"lint": "tslint -c tslint.json -t stylish terminus-*/src/**/*.ts terminus-*/src/*.ts app/src/*.ts",
|
||||||
"postinstall": "install-app-deps"
|
"postinstall": "node ./scripts/install-deps.js"
|
||||||
},
|
},
|
||||||
"repository": "eugeny/terminus"
|
"repository": "eugeny/terminus"
|
||||||
}
|
}
|
||||||
|
@@ -4,24 +4,18 @@ const path = require('path')
|
|||||||
const vars = require('./vars')
|
const vars = require('./vars')
|
||||||
|
|
||||||
lifecycles = []
|
lifecycles = []
|
||||||
lifecycles.push(rebuild({
|
for (let dir of ['app', 'terminus-ssh', 'terminus-terminal']) {
|
||||||
buildPath: path.resolve(__dirname, '../app'),
|
lifecycles.push([rebuild({
|
||||||
electronVersion: vars.electronVersion,
|
buildPath: path.resolve(__dirname, '../' + dir),
|
||||||
force: true,
|
electronVersion: vars.electronVersion,
|
||||||
}).lifecycle)
|
force: true,
|
||||||
lifecycles.push(rebuild({
|
}).lifecycle, dir])
|
||||||
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) {
|
console.info('Building against Electron', vars.electronVersion)
|
||||||
|
|
||||||
|
for (let [lc, dir] of lifecycles) {
|
||||||
lc.on('module-found', name => {
|
lc.on('module-found', name => {
|
||||||
console.info('Rebuilding', name)
|
console.info('Rebuilding', dir + '/' + name)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@@ -8,7 +8,6 @@ const localBinPath = path.resolve(__dirname, '../node_modules/.bin');
|
|||||||
const npx = `${localBinPath}/npx`;
|
const npx = `${localBinPath}/npx`;
|
||||||
|
|
||||||
log.info('deps', 'app')
|
log.info('deps', 'app')
|
||||||
sh.exec(`${npx} yarn install`)
|
|
||||||
|
|
||||||
sh.cd('app')
|
sh.cd('app')
|
||||||
sh.exec(`${npx} yarn install`)
|
sh.exec(`${npx} yarn install`)
|
||||||
|
@@ -3,10 +3,11 @@ const fs = require('fs')
|
|||||||
const childProcess = require('child_process')
|
const childProcess = require('child_process')
|
||||||
|
|
||||||
const appInfo = JSON.parse(fs.readFileSync(path.resolve(__dirname, '../app/package.json')))
|
const appInfo = JSON.parse(fs.readFileSync(path.resolve(__dirname, '../app/package.json')))
|
||||||
const pkgInfo = JSON.parse(fs.readFileSync(path.resolve(__dirname, '../package.json')))
|
const electronInfo = JSON.parse(fs.readFileSync(path.resolve(__dirname, '../node_modules/electron/package.json')))
|
||||||
|
|
||||||
exports.version = childProcess.execSync('git describe --tags', {encoding:'utf-8'})
|
exports.version = childProcess.execSync('git describe --tags', {encoding:'utf-8'})
|
||||||
exports.version = exports.version.substring(1, exports.version.length - 1)
|
exports.version = exports.version.substring(1).trim()
|
||||||
|
exports.version = exports.version.replace('-', '-c')
|
||||||
|
|
||||||
exports.builtinPlugins = [
|
exports.builtinPlugins = [
|
||||||
'terminus-core',
|
'terminus-core',
|
||||||
@@ -20,4 +21,4 @@ exports.bundledModules = [
|
|||||||
'@angular',
|
'@angular',
|
||||||
'@ng-bootstrap',
|
'@ng-bootstrap',
|
||||||
]
|
]
|
||||||
exports.electronVersion = pkgInfo.devDependencies.electron
|
exports.electronVersion = electronInfo.version
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "terminus-community-color-schemes",
|
"name": "terminus-community-color-schemes",
|
||||||
"version": "1.0.0-alpha.55",
|
"version": "1.0.68-c17-g8b64a81",
|
||||||
"description": "Community color schemes for Terminus",
|
"description": "Community color schemes for Terminus",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"terminus-builtin-plugin"
|
"terminus-builtin-plugin"
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "terminus-core",
|
"name": "terminus-core",
|
||||||
"version": "1.0.0-alpha.55",
|
"version": "1.0.68-c17-g8b64a81",
|
||||||
"description": "Terminus core",
|
"description": "Terminus core",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"terminus-builtin-plugin"
|
"terminus-builtin-plugin"
|
||||||
@@ -8,8 +8,8 @@
|
|||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"typings": "dist/index.d.ts",
|
"typings": "dist/index.d.ts",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "rm -rf dist && webpack --progress --color --display-modules",
|
"build": "webpack --progress --color --display-modules",
|
||||||
"watch": "rm -rf dist && webpack --progress --color --watch"
|
"watch": "webpack --progress --color --watch"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"dist"
|
"dist"
|
||||||
@@ -21,12 +21,13 @@
|
|||||||
"@types/node": "^7.0.37",
|
"@types/node": "^7.0.37",
|
||||||
"@types/webpack-env": "^1.13.0",
|
"@types/webpack-env": "^1.13.0",
|
||||||
"@types/winston": "^2.3.6",
|
"@types/winston": "^2.3.6",
|
||||||
|
"axios": "^0.18.0",
|
||||||
"bootstrap": "^4.1.3",
|
"bootstrap": "^4.1.3",
|
||||||
"core-js": "^2.4.1",
|
"core-js": "^2.4.1",
|
||||||
"electron-updater": "^2.8.9",
|
"electron-updater": "^2.8.9",
|
||||||
"ng2-dnd": "^5.0.2",
|
"ng2-dnd": "^5.0.2",
|
||||||
"ngx-perfect-scrollbar": "^6.0.0",
|
"ngx-perfect-scrollbar": "^6.0.0",
|
||||||
"rage-edit-tmp": "^1.1.0",
|
"rage-edit": "^1.2.0",
|
||||||
"shell-escape": "^0.2.0",
|
"shell-escape": "^0.2.0",
|
||||||
"universal-analytics": "^0.4.17"
|
"universal-analytics": "^0.4.17"
|
||||||
},
|
},
|
||||||
@@ -44,6 +45,5 @@
|
|||||||
"deepmerge": "^1.5.0",
|
"deepmerge": "^1.5.0",
|
||||||
"js-yaml": "^3.9.0",
|
"js-yaml": "^3.9.0",
|
||||||
"winston": "^2.4.0"
|
"winston": "^2.4.0"
|
||||||
},
|
}
|
||||||
"false": {}
|
|
||||||
}
|
}
|
||||||
|
@@ -4,6 +4,7 @@ export { ToolbarButtonProvider, IToolbarButton } from './toolbarButtonProvider'
|
|||||||
export { ConfigProvider } from './configProvider'
|
export { ConfigProvider } from './configProvider'
|
||||||
export { HotkeyProvider, IHotkeyDescription } from './hotkeyProvider'
|
export { HotkeyProvider, IHotkeyDescription } from './hotkeyProvider'
|
||||||
export { Theme } from './theme'
|
export { Theme } from './theme'
|
||||||
|
export { TabContextMenuItemProvider } from './tabContextMenuProvider'
|
||||||
|
|
||||||
export { AppService } from '../services/app.service'
|
export { AppService } from '../services/app.service'
|
||||||
export { ConfigService } from '../services/config.service'
|
export { ConfigService } from '../services/config.service'
|
||||||
|
8
terminus-core/src/api/tabContextMenuProvider.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import { BaseTabComponent } from '../components/baseTab.component'
|
||||||
|
import { TabHeaderComponent } from '../components/tabHeader.component'
|
||||||
|
|
||||||
|
export abstract class TabContextMenuItemProvider {
|
||||||
|
weight = 0
|
||||||
|
|
||||||
|
abstract async getItems (tab: BaseTabComponent, tabHeader?: TabHeaderComponent): Promise<Electron.MenuItemConstructorOptions[]>
|
||||||
|
}
|
@@ -6,5 +6,5 @@ export interface RecoveredTab {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export abstract class TabRecoveryProvider {
|
export abstract class TabRecoveryProvider {
|
||||||
abstract async recover (recoveryToken: any): Promise<RecoveredTab|null>
|
abstract async recover (recoveryToken: any): Promise<RecoveredTab | null>
|
||||||
}
|
}
|
||||||
|
@@ -132,8 +132,8 @@ export class AppRootComponent {
|
|||||||
ngbModal.open(SafeModeModalComponent)
|
ngbModal.open(SafeModeModalComponent)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.updater.check().then(() => {
|
this.updater.check().then(available => {
|
||||||
this.updatesAvailable = true
|
this.updatesAvailable = available
|
||||||
})
|
})
|
||||||
|
|
||||||
this.touchbar.update()
|
this.touchbar.update()
|
||||||
|
@@ -13,6 +13,7 @@ export abstract class BaseTabComponent {
|
|||||||
hasFocus = false
|
hasFocus = false
|
||||||
hasActivity = false
|
hasActivity = false
|
||||||
hostView: ViewRef
|
hostView: ViewRef
|
||||||
|
color: string = null
|
||||||
protected titleChange = new Subject<string>()
|
protected titleChange = new Subject<string>()
|
||||||
protected focused = new Subject<void>()
|
protected focused = new Subject<void>()
|
||||||
protected blurred = new Subject<void>()
|
protected blurred = new Subject<void>()
|
||||||
@@ -68,7 +69,7 @@ export abstract class BaseTabComponent {
|
|||||||
this.activity.next(false)
|
this.activity.next(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
getRecoveryToken (): any {
|
async getRecoveryToken (): Promise<any> {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
.icon(tabindex='0', [class.active]='model', (keyup.space)='click()')
|
.icon(tabindex='0', [class.active]='model', (keyup.space)='click()')
|
||||||
i.fa.fa-square-o.off
|
i.fas.fa-square.off
|
||||||
i.fa.fa-check-square.on
|
i.fas.fa-check-square.on
|
||||||
.text {{text}}
|
.text {{text}}
|
||||||
|
@@ -16,6 +16,7 @@ export class RenameTabModalComponent {
|
|||||||
ngOnInit () {
|
ngOnInit () {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.input.nativeElement.focus()
|
this.input.nativeElement.focus()
|
||||||
|
this.input.nativeElement.select()
|
||||||
}, 250)
|
}, 250)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -14,10 +14,10 @@ div
|
|||||||
footer.d-flex.align-items-center
|
footer.d-flex.align-items-center
|
||||||
.btn-group.mr-auto
|
.btn-group.mr-auto
|
||||||
button.btn.btn-secondary((click)='homeBase.openGitHub()')
|
button.btn.btn-secondary((click)='homeBase.openGitHub()')
|
||||||
i.fa.fa-github
|
i.fab.fa-github
|
||||||
span GitHub
|
span GitHub
|
||||||
button.btn.btn-secondary((click)='homeBase.reportBug()')
|
button.btn.btn-secondary((click)='homeBase.reportBug()')
|
||||||
i.fa.fa-bug
|
i.fas.fa-bug
|
||||||
span Report a problem
|
span Report a problem
|
||||||
|
|
||||||
.form-control-static.selectable.no-drag Version: {{homeBase.appVersion}}
|
.form-control-static.selectable.no-drag Version: {{homeBase.appVersion}}
|
||||||
|
@@ -17,7 +17,7 @@ import { BaseTabComponent } from '../components/baseTab.component'
|
|||||||
export class TabBodyComponent implements OnChanges {
|
export class TabBodyComponent implements OnChanges {
|
||||||
@Input() @HostBinding('class.active') active: boolean
|
@Input() @HostBinding('class.active') active: boolean
|
||||||
@Input() tab: BaseTabComponent
|
@Input() tab: BaseTabComponent
|
||||||
@ViewChild('placeholder', {read: ViewContainerRef}) placeholder: ViewContainerRef
|
@ViewChild('placeholder', { read: ViewContainerRef }) placeholder: ViewContainerRef
|
||||||
|
|
||||||
ngOnChanges (changes) {
|
ngOnChanges (changes) {
|
||||||
if (changes.tab) {
|
if (changes.tab) {
|
||||||
|
@@ -1,4 +1,7 @@
|
|||||||
.progressbar([style.width]='progress + "%"', *ngIf='progress != null')
|
.progressbar([style.width]='progress + "%"', *ngIf='progress != null')
|
||||||
.index(#handle) {{index + 1}}
|
.index(
|
||||||
|
#handle,
|
||||||
|
[style.background-color]='tab.color',
|
||||||
|
) {{index + 1}}
|
||||||
.name([title]='tab.customTitle || tab.title') {{tab.customTitle || tab.title}}
|
.name([title]='tab.customTitle || tab.title') {{tab.customTitle || tab.title}}
|
||||||
button((click)='app.closeTab(tab, true)') ×
|
button((click)='app.closeTab(tab, true)') ×
|
||||||
|
@@ -1,8 +1,10 @@
|
|||||||
import { Component, Input, HostBinding, HostListener, NgZone, ViewChild, ElementRef } from '@angular/core'
|
import { Component, Input, Optional, Inject, HostBinding, HostListener, ViewChild, ElementRef } from '@angular/core'
|
||||||
import { SortableComponent } from 'ng2-dnd'
|
import { SortableComponent } from 'ng2-dnd'
|
||||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
||||||
|
import { TabContextMenuItemProvider } from '../api/tabContextMenuProvider'
|
||||||
import { BaseTabComponent } from './baseTab.component'
|
import { BaseTabComponent } from './baseTab.component'
|
||||||
import { RenameTabModalComponent } from './renameTabModal.component'
|
import { RenameTabModalComponent } from './renameTabModal.component'
|
||||||
|
import { HotkeysService } from '../services/hotkeys.service'
|
||||||
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'
|
import { HostAppService, Platform } from '../services/hostApp.service'
|
||||||
@@ -20,16 +22,24 @@ export class TabHeaderComponent {
|
|||||||
@Input() progress: number
|
@Input() progress: number
|
||||||
@ViewChild('handle') handle: ElementRef
|
@ViewChild('handle') handle: ElementRef
|
||||||
|
|
||||||
private completionNotificationEnabled = false
|
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
public app: AppService,
|
public app: AppService,
|
||||||
private electron: ElectronService,
|
private electron: ElectronService,
|
||||||
private zone: NgZone,
|
|
||||||
private hostApp: HostAppService,
|
private hostApp: HostAppService,
|
||||||
private ngbModal: NgbModal,
|
private ngbModal: NgbModal,
|
||||||
|
private hotkeys: HotkeysService,
|
||||||
private parentDraggable: SortableComponent,
|
private parentDraggable: SortableComponent,
|
||||||
) { }
|
@Optional() @Inject(TabContextMenuItemProvider) protected contextMenuProviders: TabContextMenuItemProvider[],
|
||||||
|
) {
|
||||||
|
this.hotkeys.matchedHotkey.subscribe((hotkey) => {
|
||||||
|
if (this.app.activeTab === this.tab) {
|
||||||
|
if (hotkey === 'rename-tab') {
|
||||||
|
this.showRenameTabModal()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
this.contextMenuProviders.sort((a, b) => a.weight - b.weight)
|
||||||
|
}
|
||||||
|
|
||||||
ngOnInit () {
|
ngOnInit () {
|
||||||
if (this.hostApp.platform === Platform.macOS) {
|
if (this.hostApp.platform === Platform.macOS) {
|
||||||
@@ -40,7 +50,7 @@ export class TabHeaderComponent {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@HostListener('dblclick') onDoubleClick (): void {
|
showRenameTabModal (): 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 => {
|
||||||
@@ -49,6 +59,19 @@ export class TabHeaderComponent {
|
|||||||
}).catch(() => null)
|
}).catch(() => null)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async buildContextMenu (): Promise<Electron.MenuItemConstructorOptions[]> {
|
||||||
|
let items: Electron.MenuItemConstructorOptions[] = []
|
||||||
|
for (let section of await Promise.all(this.contextMenuProviders.map(x => x.getItems(this.tab, this)))) {
|
||||||
|
items.push({ type: 'separator' })
|
||||||
|
items = items.concat(section)
|
||||||
|
}
|
||||||
|
return items.slice(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
@HostListener('dblclick') onDoubleClick (): void {
|
||||||
|
this.showRenameTabModal()
|
||||||
|
}
|
||||||
|
|
||||||
@HostListener('auxclick', ['$event']) async onAuxClick ($event: MouseEvent) {
|
@HostListener('auxclick', ['$event']) async onAuxClick ($event: MouseEvent) {
|
||||||
if ($event.which === 2) {
|
if ($event.which === 2) {
|
||||||
this.app.closeTab(this.tab, true)
|
this.app.closeTab(this.tab, true)
|
||||||
@@ -56,73 +79,7 @@ export class TabHeaderComponent {
|
|||||||
if ($event.which === 3) {
|
if ($event.which === 3) {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
|
|
||||||
let contextMenu = this.electron.remote.Menu.buildFromTemplate([
|
const contextMenu = this.electron.remote.Menu.buildFromTemplate(await this.buildContextMenu())
|
||||||
{
|
|
||||||
label: 'Close',
|
|
||||||
click: () => this.zone.run(() => {
|
|
||||||
this.app.closeTab(this.tab, true)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Close other tabs',
|
|
||||||
click: () => this.zone.run(() => {
|
|
||||||
for (let tab of this.app.tabs.filter(x => x !== this.tab)) {
|
|
||||||
this.app.closeTab(tab, true)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Close tabs to the right',
|
|
||||||
click: () => this.zone.run(() => {
|
|
||||||
for (let tab of this.app.tabs.slice(this.app.tabs.indexOf(this.tab) + 1)) {
|
|
||||||
this.app.closeTab(tab, true)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Close tabs to the left',
|
|
||||||
click: () => this.zone.run(() => {
|
|
||||||
for (let tab of this.app.tabs.slice(0, this.app.tabs.indexOf(this.tab))) {
|
|
||||||
this.app.closeTab(tab, true)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
])
|
|
||||||
|
|
||||||
let process = await this.tab.getCurrentProcess()
|
|
||||||
if (process) {
|
|
||||||
contextMenu.append(new this.electron.MenuItem({
|
|
||||||
id: 'sep',
|
|
||||||
type: 'separator',
|
|
||||||
}))
|
|
||||||
contextMenu.append(new this.electron.MenuItem({
|
|
||||||
id: 'process-name',
|
|
||||||
enabled: false,
|
|
||||||
label: 'Current process: ' + process.name,
|
|
||||||
}))
|
|
||||||
contextMenu.append(new this.electron.MenuItem({
|
|
||||||
id: 'completion',
|
|
||||||
label: 'Notify when done',
|
|
||||||
type: 'checkbox',
|
|
||||||
checked: this.completionNotificationEnabled,
|
|
||||||
click: () => this.zone.run(() => {
|
|
||||||
this.completionNotificationEnabled = !this.completionNotificationEnabled
|
|
||||||
|
|
||||||
if (this.completionNotificationEnabled) {
|
|
||||||
this.app.observeTabCompletion(this.tab).subscribe(() => {
|
|
||||||
new Notification('Process completed', {
|
|
||||||
body: process.name,
|
|
||||||
}).addEventListener('click', () => {
|
|
||||||
this.app.selectTab(this.tab)
|
|
||||||
})
|
|
||||||
this.completionNotificationEnabled = false
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
this.app.stopObservingTabCompletion(this.tab)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
contextMenu.popup({
|
contextMenu.popup({
|
||||||
x: $event.pageX,
|
x: $event.pageX,
|
||||||
|
@@ -7,46 +7,33 @@ hotkeys:
|
|||||||
- 'F11'
|
- 'F11'
|
||||||
close-tab:
|
close-tab:
|
||||||
- 'Ctrl-Shift-W'
|
- 'Ctrl-Shift-W'
|
||||||
- ['Ctrl-A', 'K']
|
toggle-last-tab: []
|
||||||
toggle-last-tab:
|
rename-tab:
|
||||||
- ['Ctrl-A', 'A']
|
- 'Ctrl-Shift-R'
|
||||||
- ['Ctrl-A', 'Ctrl-A']
|
|
||||||
next-tab:
|
next-tab:
|
||||||
- 'Ctrl-Shift-ArrowRight'
|
- 'Ctrl-Shift-ArrowRight'
|
||||||
- ['Ctrl-A', 'N']
|
|
||||||
- 'Ctrl-Tab'
|
- 'Ctrl-Tab'
|
||||||
previous-tab:
|
previous-tab:
|
||||||
- 'Ctrl-Shift-ArrowLeft'
|
- 'Ctrl-Shift-ArrowLeft'
|
||||||
- ['Ctrl-A', 'P']
|
|
||||||
- 'Ctrl-Shift-Tab'
|
- 'Ctrl-Shift-Tab'
|
||||||
tab-1:
|
tab-1:
|
||||||
- 'Alt-1'
|
- 'Alt-1'
|
||||||
- ['Ctrl-A', '1']
|
|
||||||
tab-2:
|
tab-2:
|
||||||
- 'Alt-2'
|
- 'Alt-2'
|
||||||
- ['Ctrl-A', '2']
|
|
||||||
tab-3:
|
tab-3:
|
||||||
- 'Alt-3'
|
- 'Alt-3'
|
||||||
- ['Ctrl-A', '3']
|
|
||||||
tab-4:
|
tab-4:
|
||||||
- 'Alt-4'
|
- 'Alt-4'
|
||||||
- ['Ctrl-A', '4']
|
|
||||||
tab-5:
|
tab-5:
|
||||||
- 'Alt-5'
|
- 'Alt-5'
|
||||||
- ['Ctrl-A', '5']
|
|
||||||
tab-6:
|
tab-6:
|
||||||
- 'Alt-6'
|
- 'Alt-6'
|
||||||
- ['Ctrl-A', '6']
|
|
||||||
tab-7:
|
tab-7:
|
||||||
- 'Alt-7'
|
- 'Alt-7'
|
||||||
- ['Ctrl-A', '7']
|
|
||||||
tab-8:
|
tab-8:
|
||||||
- 'Alt-8'
|
- 'Alt-8'
|
||||||
- ['Ctrl-A', '8']
|
|
||||||
tab-9:
|
tab-9:
|
||||||
- 'Alt-9'
|
- 'Alt-9'
|
||||||
- ['Ctrl-A', '9']
|
|
||||||
tab-10:
|
tab-10:
|
||||||
- 'Alt-0'
|
- 'Alt-0'
|
||||||
- ['Ctrl-A', '0']
|
|
||||||
pluginBlacklist: ['ssh']
|
pluginBlacklist: ['ssh']
|
||||||
|
@@ -8,6 +8,8 @@ hotkeys:
|
|||||||
close-tab:
|
close-tab:
|
||||||
- '⌘-W'
|
- '⌘-W'
|
||||||
toggle-last-tab: []
|
toggle-last-tab: []
|
||||||
|
rename-tab:
|
||||||
|
- '⌘-R'
|
||||||
next-tab:
|
next-tab:
|
||||||
- 'Ctrl-Tab'
|
- 'Ctrl-Tab'
|
||||||
previous-tab:
|
previous-tab:
|
||||||
|
@@ -7,46 +7,33 @@ hotkeys:
|
|||||||
- 'F11'
|
- 'F11'
|
||||||
close-tab:
|
close-tab:
|
||||||
- 'Ctrl-Shift-W'
|
- 'Ctrl-Shift-W'
|
||||||
- ['Ctrl-A', 'K']
|
toggle-last-tab: []
|
||||||
toggle-last-tab:
|
rename-tab:
|
||||||
- ['Ctrl-A', 'A']
|
- 'Ctrl-Shift-R'
|
||||||
- ['Ctrl-A', 'Ctrl-A']
|
|
||||||
next-tab:
|
next-tab:
|
||||||
- 'Ctrl-Shift-ArrowRight'
|
- 'Ctrl-Shift-ArrowRight'
|
||||||
- ['Ctrl-A', 'N']
|
|
||||||
- 'Ctrl-Tab'
|
- 'Ctrl-Tab'
|
||||||
previous-tab:
|
previous-tab:
|
||||||
- 'Ctrl-Shift-ArrowLeft'
|
- 'Ctrl-Shift-ArrowLeft'
|
||||||
- ['Ctrl-A', 'P']
|
|
||||||
- 'Ctrl-Shift-Tab'
|
- 'Ctrl-Shift-Tab'
|
||||||
tab-1:
|
tab-1:
|
||||||
- 'Alt-1'
|
- 'Alt-1'
|
||||||
- ['Ctrl-A', '1']
|
|
||||||
tab-2:
|
tab-2:
|
||||||
- 'Alt-2'
|
- 'Alt-2'
|
||||||
- ['Ctrl-A', '2']
|
|
||||||
tab-3:
|
tab-3:
|
||||||
- 'Alt-3'
|
- 'Alt-3'
|
||||||
- ['Ctrl-A', '3']
|
|
||||||
tab-4:
|
tab-4:
|
||||||
- 'Alt-4'
|
- 'Alt-4'
|
||||||
- ['Ctrl-A', '4']
|
|
||||||
tab-5:
|
tab-5:
|
||||||
- 'Alt-5'
|
- 'Alt-5'
|
||||||
- ['Ctrl-A', '5']
|
|
||||||
tab-6:
|
tab-6:
|
||||||
- 'Alt-6'
|
- 'Alt-6'
|
||||||
- ['Ctrl-A', '6']
|
|
||||||
tab-7:
|
tab-7:
|
||||||
- 'Alt-7'
|
- 'Alt-7'
|
||||||
- ['Ctrl-A', '7']
|
|
||||||
tab-8:
|
tab-8:
|
||||||
- 'Alt-8'
|
- 'Alt-8'
|
||||||
- ['Ctrl-A', '8']
|
|
||||||
tab-9:
|
tab-9:
|
||||||
- 'Alt-9'
|
- 'Alt-9'
|
||||||
- ['Ctrl-A', '9']
|
|
||||||
tab-10:
|
tab-10:
|
||||||
- 'Alt-0'
|
- 'Alt-0'
|
||||||
- ['Ctrl-A', '0']
|
|
||||||
pluginBlacklist: []
|
pluginBlacklist: []
|
||||||
|
@@ -6,19 +6,7 @@ import { NgbModule } from '@ng-bootstrap/ng-bootstrap'
|
|||||||
import { PerfectScrollbarModule, PERFECT_SCROLLBAR_CONFIG } from 'ngx-perfect-scrollbar'
|
import { PerfectScrollbarModule, PERFECT_SCROLLBAR_CONFIG } from 'ngx-perfect-scrollbar'
|
||||||
import { DndModule } from 'ng2-dnd'
|
import { DndModule } from 'ng2-dnd'
|
||||||
|
|
||||||
import { AppService } from './services/app.service'
|
import { AppHotkeyProvider } from './services/hotkeys.service'
|
||||||
import { ConfigService } from './services/config.service'
|
|
||||||
import { ElectronService } from './services/electron.service'
|
|
||||||
import { HostAppService } from './services/hostApp.service'
|
|
||||||
import { LogService } from './services/log.service'
|
|
||||||
import { HomeBaseService } from './services/homeBase.service'
|
|
||||||
import { HotkeysService, AppHotkeyProvider } from './services/hotkeys.service'
|
|
||||||
import { DockingService } from './services/docking.service'
|
|
||||||
import { ShellIntegrationService } from './services/shellIntegration.service'
|
|
||||||
import { TabRecoveryService } from './services/tabRecovery.service'
|
|
||||||
import { ThemesService } from './services/themes.service'
|
|
||||||
import { TouchbarService } from './services/touchbar.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 { CheckboxComponent } from './components/checkbox.component'
|
||||||
@@ -36,33 +24,25 @@ import { AutofocusDirective } from './directives/autofocus.directive'
|
|||||||
import { HotkeyProvider } from './api/hotkeyProvider'
|
import { HotkeyProvider } from './api/hotkeyProvider'
|
||||||
import { ConfigProvider } from './api/configProvider'
|
import { ConfigProvider } from './api/configProvider'
|
||||||
import { Theme } from './api/theme'
|
import { Theme } from './api/theme'
|
||||||
|
import { TabContextMenuItemProvider } from './api/tabContextMenuProvider'
|
||||||
|
|
||||||
import { StandardTheme, StandardCompactTheme, PaperTheme } from './theme'
|
import { StandardTheme, StandardCompactTheme, PaperTheme } from './theme'
|
||||||
import { CoreConfigProvider } from './config'
|
import { CoreConfigProvider } from './config'
|
||||||
|
import { TaskCompletionContextMenu, CommonOptionsContextMenu, CloseContextMenu } from './tabContextMenu'
|
||||||
|
|
||||||
import 'perfect-scrollbar/css/perfect-scrollbar.css'
|
import 'perfect-scrollbar/css/perfect-scrollbar.css'
|
||||||
import 'ng2-dnd/bundles/style.css'
|
import 'ng2-dnd/bundles/style.css'
|
||||||
|
|
||||||
const PROVIDERS = [
|
const PROVIDERS = [
|
||||||
AppService,
|
|
||||||
ConfigService,
|
|
||||||
DockingService,
|
|
||||||
ElectronService,
|
|
||||||
HomeBaseService,
|
|
||||||
HostAppService,
|
|
||||||
HotkeysService,
|
|
||||||
LogService,
|
|
||||||
ShellIntegrationService,
|
|
||||||
TabRecoveryService,
|
|
||||||
ThemesService,
|
|
||||||
TouchbarService,
|
|
||||||
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: Theme, useClass: PaperTheme, multi: true },
|
{ provide: Theme, useClass: PaperTheme, multi: true },
|
||||||
{ provide: ConfigProvider, useClass: CoreConfigProvider, multi: true },
|
{ provide: ConfigProvider, useClass: CoreConfigProvider, multi: true },
|
||||||
{ provide: PERFECT_SCROLLBAR_CONFIG, useValue: { suppressScrollX: true }}
|
{ provide: TabContextMenuItemProvider, useClass: CommonOptionsContextMenu, multi: true },
|
||||||
|
{ provide: TabContextMenuItemProvider, useClass: CloseContextMenu, multi: true },
|
||||||
|
{ provide: TabContextMenuItemProvider, useClass: TaskCompletionContextMenu, multi: true },
|
||||||
|
{ provide: PERFECT_SCROLLBAR_CONFIG, useValue: { suppressScrollX: true } }
|
||||||
]
|
]
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
|
@@ -35,7 +35,7 @@ class CompletionObserver {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Injectable()
|
@Injectable({ providedIn: 'root' })
|
||||||
export class AppService {
|
export class AppService {
|
||||||
tabs: BaseTabComponent[] = []
|
tabs: BaseTabComponent[] = []
|
||||||
activeTab: BaseTabComponent
|
activeTab: BaseTabComponent
|
||||||
|
@@ -56,7 +56,7 @@ export class ConfigProxy {
|
|||||||
return real[key]
|
return real[key]
|
||||||
} else {
|
} else {
|
||||||
if (isNonStructuralObjectMember(defaults[key])) {
|
if (isNonStructuralObjectMember(defaults[key])) {
|
||||||
real[key] = {...defaults[key]}
|
real[key] = { ...defaults[key] }
|
||||||
delete real[key].__nonStructural
|
delete real[key].__nonStructural
|
||||||
return real[key]
|
return real[key]
|
||||||
} else {
|
} else {
|
||||||
@@ -74,7 +74,7 @@ export class ConfigProxy {
|
|||||||
setValue (key: string, value: any) { } // tslint:disable-line
|
setValue (key: string, value: any) { } // tslint:disable-line
|
||||||
}
|
}
|
||||||
|
|
||||||
@Injectable()
|
@Injectable({ providedIn: 'root' })
|
||||||
export class ConfigService {
|
export class ConfigService {
|
||||||
store: any
|
store: any
|
||||||
restartRequested: boolean
|
restartRequested: boolean
|
||||||
|
@@ -8,7 +8,7 @@ export interface IScreen {
|
|||||||
name: string
|
name: string
|
||||||
}
|
}
|
||||||
|
|
||||||
@Injectable()
|
@Injectable({ providedIn: 'root' })
|
||||||
export class DockingService {
|
export class DockingService {
|
||||||
constructor (
|
constructor (
|
||||||
private electron: ElectronService,
|
private electron: ElectronService,
|
||||||
|
@@ -1,7 +1,12 @@
|
|||||||
import { Injectable } from '@angular/core'
|
import { Injectable } from '@angular/core'
|
||||||
import { TouchBar, BrowserWindow, Menu, MenuItem } from 'electron'
|
import { TouchBar, BrowserWindow, Menu, MenuItem } from 'electron'
|
||||||
|
|
||||||
@Injectable()
|
export interface MessageBoxResponse {
|
||||||
|
response: number
|
||||||
|
checkboxChecked?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
@Injectable({ providedIn: 'root' })
|
||||||
export class ElectronService {
|
export class ElectronService {
|
||||||
app: any
|
app: any
|
||||||
ipcRenderer: any
|
ipcRenderer: any
|
||||||
@@ -54,4 +59,15 @@ export class ElectronService {
|
|||||||
this.remote.Menu.sendActionToFirstResponder('hide:')
|
this.remote.Menu.sendActionToFirstResponder('hide:')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
showMessageBox (
|
||||||
|
browserWindow: Electron.BrowserWindow,
|
||||||
|
options: Electron.MessageBoxOptions
|
||||||
|
): Promise<MessageBoxResponse> {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
this.dialog.showMessageBox(browserWindow, options, (response, checkboxChecked) => {
|
||||||
|
resolve({ response, checkboxChecked })
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -5,7 +5,7 @@ import { ConfigService } from './config.service'
|
|||||||
import ua = require('universal-analytics')
|
import ua = require('universal-analytics')
|
||||||
import uuidv4 = require('uuid/v4')
|
import uuidv4 = require('uuid/v4')
|
||||||
|
|
||||||
@Injectable()
|
@Injectable({ providedIn: 'root' })
|
||||||
export class HomeBaseService {
|
export class HomeBaseService {
|
||||||
appVersion: string
|
appVersion: string
|
||||||
|
|
||||||
@@ -33,7 +33,7 @@ export class HomeBaseService {
|
|||||||
linux: 'OS: Linux',
|
linux: 'OS: Linux',
|
||||||
}[os.platform()]
|
}[os.platform()]
|
||||||
let plugins = (window as any).installedPlugins.filter(x => !x.isBuiltin).map(x => x.name)
|
let plugins = (window as any).installedPlugins.filter(x => !x.isBuiltin).map(x => x.name)
|
||||||
body += `Plugins: ${plugins.join(', ')}\n\n`
|
body += `Plugins: ${plugins.join(', ') || 'none'}\n\n`
|
||||||
this.electron.shell.openExternal(`https://github.com/eugeny/terminus/issues/new?body=${encodeURIComponent(body)}&labels=${label}`)
|
this.electron.shell.openExternal(`https://github.com/eugeny/terminus/issues/new?body=${encodeURIComponent(body)}&labels=${label}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -16,7 +16,7 @@ export interface Bounds {
|
|||||||
height: number
|
height: number
|
||||||
}
|
}
|
||||||
|
|
||||||
@Injectable()
|
@Injectable({ providedIn: 'root' })
|
||||||
export class HostAppService {
|
export class HostAppService {
|
||||||
platform: Platform
|
platform: Platform
|
||||||
nodePlatform: string
|
nodePlatform: string
|
||||||
@@ -27,8 +27,10 @@ export class HostAppService {
|
|||||||
private cliOpenDirectory = new Subject<string>()
|
private cliOpenDirectory = new Subject<string>()
|
||||||
private cliRunCommand = new Subject<string[]>()
|
private cliRunCommand = new Subject<string[]>()
|
||||||
private cliPaste = new Subject<string>()
|
private cliPaste = new Subject<string>()
|
||||||
|
private cliOpenProfile = new Subject<string>()
|
||||||
private configChangeBroadcast = new Subject<void>()
|
private configChangeBroadcast = new Subject<void>()
|
||||||
private windowCloseRequest = new Subject<void>()
|
private windowCloseRequest = new Subject<void>()
|
||||||
|
private windowMoved = new Subject<void>()
|
||||||
private logger: Logger
|
private logger: Logger
|
||||||
private windowId: number
|
private windowId: number
|
||||||
|
|
||||||
@@ -37,8 +39,10 @@ export class HostAppService {
|
|||||||
get cliOpenDirectory$ (): Observable<string> { return this.cliOpenDirectory }
|
get cliOpenDirectory$ (): Observable<string> { return this.cliOpenDirectory }
|
||||||
get cliRunCommand$ (): Observable<string[]> { return this.cliRunCommand }
|
get cliRunCommand$ (): Observable<string[]> { return this.cliRunCommand }
|
||||||
get cliPaste$ (): Observable<string> { return this.cliPaste }
|
get cliPaste$ (): Observable<string> { return this.cliPaste }
|
||||||
|
get cliOpenProfile$ (): Observable<string> { return this.cliOpenProfile }
|
||||||
get configChangeBroadcast$ (): Observable<void> { return this.configChangeBroadcast }
|
get configChangeBroadcast$ (): Observable<void> { return this.configChangeBroadcast }
|
||||||
get windowCloseRequest$ (): Observable<void> { return this.windowCloseRequest }
|
get windowCloseRequest$ (): Observable<void> { return this.windowCloseRequest }
|
||||||
|
get windowMoved$ (): Observable<void> { return this.windowMoved }
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
private zone: NgZone,
|
private zone: NgZone,
|
||||||
@@ -78,6 +82,10 @@ export class HostAppService {
|
|||||||
this.zone.run(() => this.windowCloseRequest.next())
|
this.zone.run(() => this.windowCloseRequest.next())
|
||||||
})
|
})
|
||||||
|
|
||||||
|
electron.ipcRenderer.on('host:window-moved', () => {
|
||||||
|
this.zone.run(() => this.windowMoved.next())
|
||||||
|
})
|
||||||
|
|
||||||
electron.ipcRenderer.on('host:second-instance', (_$event, argv: any, cwd: string) => this.zone.run(() => {
|
electron.ipcRenderer.on('host:second-instance', (_$event, argv: any, cwd: string) => this.zone.run(() => {
|
||||||
this.logger.info('Second instance', argv)
|
this.logger.info('Second instance', argv)
|
||||||
const op = argv._[0]
|
const op = argv._[0]
|
||||||
@@ -91,6 +99,8 @@ export class HostAppService {
|
|||||||
text = shellEscape([text])
|
text = shellEscape([text])
|
||||||
}
|
}
|
||||||
this.cliPaste.next(text)
|
this.cliPaste.next(text)
|
||||||
|
} else if (op === 'profile') {
|
||||||
|
this.cliOpenProfile.next(argv.profileName)
|
||||||
} else {
|
} else {
|
||||||
this.secondInstance.next()
|
this.secondInstance.next()
|
||||||
}
|
}
|
||||||
|
@@ -17,7 +17,7 @@ interface EventBufferEntry {
|
|||||||
time: number,
|
time: number,
|
||||||
}
|
}
|
||||||
|
|
||||||
@Injectable()
|
@Injectable({ providedIn: 'root' })
|
||||||
export class HotkeysService {
|
export class HotkeysService {
|
||||||
key = new EventEmitter<NativeKeyEvent>()
|
key = new EventEmitter<NativeKeyEvent>()
|
||||||
matchedHotkey = new EventEmitter<string>()
|
matchedHotkey = new EventEmitter<string>()
|
||||||
@@ -80,13 +80,13 @@ export class HotkeysService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getCurrentKeystrokes (): string[] {
|
getCurrentKeystrokes (): string[] {
|
||||||
this.currentKeystrokes = this.currentKeystrokes.filter((x) => performance.now() - x.time < KEY_TIMEOUT )
|
this.currentKeystrokes = this.currentKeystrokes.filter(x => performance.now() - x.time < KEY_TIMEOUT)
|
||||||
return stringifyKeySequence(this.currentKeystrokes.map((x) => x.event))
|
return stringifyKeySequence(this.currentKeystrokes.map(x => x.event))
|
||||||
}
|
}
|
||||||
|
|
||||||
registerGlobalHotkey () {
|
registerGlobalHotkey () {
|
||||||
this.electron.globalShortcut.unregisterAll()
|
this.electron.globalShortcut.unregisterAll()
|
||||||
let value = this.config.store.hotkeys['toggle-window']
|
let value = this.config.store.hotkeys['toggle-window'] || []
|
||||||
if (typeof value === 'string') {
|
if (typeof value === 'string') {
|
||||||
value = [value]
|
value = [value]
|
||||||
}
|
}
|
||||||
@@ -120,8 +120,10 @@ export class HotkeysService {
|
|||||||
if (typeof value === 'string') {
|
if (typeof value === 'string') {
|
||||||
value = [value]
|
value = [value]
|
||||||
}
|
}
|
||||||
value = value.map((item) => (typeof item === 'string') ? [item] : item)
|
if (value) {
|
||||||
keys[key] = value
|
value = value.map((item) => (typeof item === 'string') ? [item] : item)
|
||||||
|
keys[key] = value
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return keys
|
return keys
|
||||||
@@ -213,6 +215,10 @@ export class AppHotkeyProvider extends HotkeyProvider {
|
|||||||
id: 'toggle-fullscreen',
|
id: 'toggle-fullscreen',
|
||||||
name: 'Toggle fullscreen mode',
|
name: 'Toggle fullscreen mode',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'rename-tab',
|
||||||
|
name: 'Rename Tab',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: 'close-tab',
|
id: 'close-tab',
|
||||||
name: 'Close tab',
|
name: 'Close tab',
|
||||||
|
@@ -53,7 +53,7 @@ export class Logger {
|
|||||||
log (...args: any[]) { this.doLog('log', ...args) }
|
log (...args: any[]) { this.doLog('log', ...args) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Injectable()
|
@Injectable({ providedIn: 'root' })
|
||||||
export class LogService {
|
export class LogService {
|
||||||
private log: any
|
private log: any
|
||||||
|
|
||||||
|
@@ -1,12 +1,12 @@
|
|||||||
import * as path from 'path'
|
import * as path from 'path'
|
||||||
import * as fs from 'mz/fs'
|
import * as fs from 'mz/fs'
|
||||||
import { Registry } from 'rage-edit-tmp'
|
import { Registry } from 'rage-edit'
|
||||||
import { exec } from 'mz/child_process'
|
import { exec } from 'mz/child_process'
|
||||||
import { Injectable } from '@angular/core'
|
import { Injectable } from '@angular/core'
|
||||||
import { ElectronService } from './electron.service'
|
import { ElectronService } from './electron.service'
|
||||||
import { HostAppService, Platform } from './hostApp.service'
|
import { HostAppService, Platform } from './hostApp.service'
|
||||||
|
|
||||||
@Injectable()
|
@Injectable({ providedIn: 'root' })
|
||||||
export class ShellIntegrationService {
|
export class ShellIntegrationService {
|
||||||
private automatorWorkflows = ['Open Terminus here.workflow', 'Paste path into Terminus.workflow']
|
private automatorWorkflows = ['Open Terminus here.workflow', 'Paste path into Terminus.workflow']
|
||||||
private automatorWorkflowsLocation: string
|
private automatorWorkflowsLocation: string
|
||||||
@@ -48,9 +48,9 @@ export class ShellIntegrationService {
|
|||||||
|
|
||||||
async isInstalled (): Promise<boolean> {
|
async isInstalled (): Promise<boolean> {
|
||||||
if (this.hostApp.platform === Platform.macOS) {
|
if (this.hostApp.platform === Platform.macOS) {
|
||||||
return await fs.exists(path.join(this.automatorWorkflowsDestination, this.automatorWorkflows[0]))
|
return fs.exists(path.join(this.automatorWorkflowsDestination, this.automatorWorkflows[0]))
|
||||||
} else if (this.hostApp.platform === Platform.Windows) {
|
} else if (this.hostApp.platform === Platform.Windows) {
|
||||||
return await Registry.has(this.registryKeys[0].path)
|
return Registry.has(this.registryKeys[0].path)
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@@ -5,7 +5,7 @@ import { Logger, LogService } from '../services/log.service'
|
|||||||
import { AppService } from '../services/app.service'
|
import { AppService } from '../services/app.service'
|
||||||
import { ConfigService } from '../services/config.service'
|
import { ConfigService } from '../services/config.service'
|
||||||
|
|
||||||
@Injectable()
|
@Injectable({ providedIn: 'root' })
|
||||||
export class TabRecoveryService {
|
export class TabRecoveryService {
|
||||||
logger: Logger
|
logger: Logger
|
||||||
|
|
||||||
@@ -19,13 +19,18 @@ export class TabRecoveryService {
|
|||||||
app.tabsChanged$.subscribe(() => {
|
app.tabsChanged$.subscribe(() => {
|
||||||
this.saveTabs(app.tabs)
|
this.saveTabs(app.tabs)
|
||||||
})
|
})
|
||||||
|
setInterval(() => {
|
||||||
|
this.saveTabs(app.tabs)
|
||||||
|
}, 30000)
|
||||||
}
|
}
|
||||||
|
|
||||||
saveTabs (tabs: BaseTabComponent[]) {
|
async saveTabs (tabs: BaseTabComponent[]) {
|
||||||
window.localStorage.tabsRecovery = JSON.stringify(
|
window.localStorage.tabsRecovery = JSON.stringify(
|
||||||
tabs
|
await Promise.all(
|
||||||
.map((tab) => tab.getRecoveryToken())
|
tabs
|
||||||
.filter((token) => !!token)
|
.map(tab => tab.getRecoveryToken())
|
||||||
|
.filter(token => !!token)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -2,7 +2,7 @@ import { Inject, Injectable } from '@angular/core'
|
|||||||
import { ConfigService } from '../services/config.service'
|
import { ConfigService } from '../services/config.service'
|
||||||
import { Theme } from '../api/theme'
|
import { Theme } from '../api/theme'
|
||||||
|
|
||||||
@Injectable()
|
@Injectable({ providedIn: 'root' })
|
||||||
export class ThemesService {
|
export class ThemesService {
|
||||||
private styleElement: HTMLElement = null
|
private styleElement: HTMLElement = null
|
||||||
|
|
||||||
|
@@ -6,7 +6,7 @@ import { ElectronService } from './electron.service'
|
|||||||
import { HostAppService } from './hostApp.service'
|
import { HostAppService } from './hostApp.service'
|
||||||
import { IToolbarButton, ToolbarButtonProvider } from '../api'
|
import { IToolbarButton, ToolbarButtonProvider } from '../api'
|
||||||
|
|
||||||
@Injectable()
|
@Injectable({ providedIn: 'root' })
|
||||||
export class TouchbarService {
|
export class TouchbarService {
|
||||||
private tabsSegmentedControl: TouchBarSegmentedControl
|
private tabsSegmentedControl: TouchBarSegmentedControl
|
||||||
private tabSegments: SegmentedControlSegment[] = []
|
private tabSegments: SegmentedControlSegment[] = []
|
||||||
@@ -49,8 +49,8 @@ export class TouchbarService {
|
|||||||
let touchBar = new this.electron.TouchBar({
|
let touchBar = new this.electron.TouchBar({
|
||||||
items: [
|
items: [
|
||||||
this.tabsSegmentedControl,
|
this.tabsSegmentedControl,
|
||||||
new this.electron.TouchBar.TouchBarSpacer({size: 'flexible'}),
|
new this.electron.TouchBar.TouchBarSpacer({ size: 'flexible' }),
|
||||||
new this.electron.TouchBar.TouchBarSpacer({size: 'small'}),
|
new this.electron.TouchBar.TouchBarSpacer({ size: 'small' }),
|
||||||
...buttons.map(button => this.getButton(button))
|
...buttons.map(button => this.getButton(button))
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
@@ -1,19 +1,30 @@
|
|||||||
|
import axios from 'axios'
|
||||||
import * as os from 'os'
|
import * as os from 'os'
|
||||||
import { Injectable } from '@angular/core'
|
import { Injectable } from '@angular/core'
|
||||||
import { Logger, LogService } from './log.service'
|
import { Logger, LogService } from './log.service'
|
||||||
import { ElectronService } from './electron.service'
|
import { ElectronService } from './electron.service'
|
||||||
|
|
||||||
@Injectable()
|
const UPDATES_URL = 'https://api.github.com/repos/eugeny/terminus/releases/latest'
|
||||||
|
|
||||||
|
@Injectable({ providedIn: 'root' })
|
||||||
export class UpdaterService {
|
export class UpdaterService {
|
||||||
private logger: Logger
|
private logger: Logger
|
||||||
private downloaded: Promise<void>
|
private downloaded: Promise<boolean>
|
||||||
|
private isSquirrel = true
|
||||||
|
private updateURL: string
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
log: LogService,
|
log: LogService,
|
||||||
private electron: ElectronService,
|
private electron: ElectronService,
|
||||||
) {
|
) {
|
||||||
this.logger = log.create('updater')
|
this.logger = log.create('updater')
|
||||||
electron.autoUpdater.setFeedURL(`https://terminus-updates.herokuapp.com/update/${os.platform()}/${electron.app.getVersion()}`)
|
|
||||||
|
try {
|
||||||
|
electron.autoUpdater.setFeedURL(`https://terminus-updates.herokuapp.com/update/${os.platform()}/${electron.app.getVersion()}`)
|
||||||
|
} catch (e) {
|
||||||
|
this.isSquirrel = false
|
||||||
|
this.logger.info('Squirrel updater unavailable, falling back')
|
||||||
|
}
|
||||||
|
|
||||||
this.electron.autoUpdater.on('update-available', () => {
|
this.electron.autoUpdater.on('update-available', () => {
|
||||||
this.logger.info('Update available')
|
this.logger.info('Update available')
|
||||||
@@ -22,20 +33,45 @@ export class UpdaterService {
|
|||||||
this.logger.info('No updates')
|
this.logger.info('No updates')
|
||||||
})
|
})
|
||||||
|
|
||||||
this.downloaded = new Promise<void>(resolve => {
|
this.downloaded = new Promise<boolean>(resolve => {
|
||||||
this.electron.autoUpdater.once('update-downloaded', resolve)
|
this.electron.autoUpdater.once('update-downloaded', () => resolve(true))
|
||||||
})
|
})
|
||||||
|
|
||||||
this.logger.debug('Checking for updates')
|
this.logger.debug('Checking for updates')
|
||||||
this.electron.autoUpdater.checkForUpdates()
|
|
||||||
|
if (this.isSquirrel) {
|
||||||
|
try {
|
||||||
|
this.electron.autoUpdater.checkForUpdates()
|
||||||
|
} catch (e) {
|
||||||
|
this.isSquirrel = false
|
||||||
|
this.logger.info('Squirrel updater unavailable, falling back')
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
check (): Promise<void> {
|
async check (): Promise<boolean> {
|
||||||
|
if (!this.isSquirrel) {
|
||||||
|
this.logger.debug('Checking for updates')
|
||||||
|
let response = await axios.get(UPDATES_URL)
|
||||||
|
let data = response.data
|
||||||
|
let version = data.tag_name.substring(1)
|
||||||
|
if (this.electron.app.getVersion() !== version) {
|
||||||
|
this.logger.info('Update available')
|
||||||
|
this.updateURL = data.html_url
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
this.logger.info('No updates')
|
||||||
|
return false
|
||||||
|
}
|
||||||
return this.downloaded
|
return this.downloaded
|
||||||
}
|
}
|
||||||
|
|
||||||
async update () {
|
async update () {
|
||||||
await this.downloaded
|
if (!this.isSquirrel) {
|
||||||
this.electron.autoUpdater.quitAndInstall()
|
this.electron.shell.openExternal(this.updateURL)
|
||||||
|
} else {
|
||||||
|
await this.downloaded
|
||||||
|
this.electron.autoUpdater.quitAndInstall()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
139
terminus-core/src/tabContextMenu.ts
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
import { Injectable, NgZone } from '@angular/core'
|
||||||
|
import { AppService } from './services/app.service'
|
||||||
|
import { BaseTabComponent } from './components/baseTab.component'
|
||||||
|
import { TabHeaderComponent } from './components/tabHeader.component'
|
||||||
|
import { TabContextMenuItemProvider } from './api/tabContextMenuProvider'
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class CloseContextMenu extends TabContextMenuItemProvider {
|
||||||
|
weight = -5
|
||||||
|
|
||||||
|
constructor (
|
||||||
|
private app: AppService,
|
||||||
|
private zone: NgZone,
|
||||||
|
) {
|
||||||
|
super()
|
||||||
|
}
|
||||||
|
|
||||||
|
async getItems (tab: BaseTabComponent): Promise<Electron.MenuItemConstructorOptions[]> {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
label: 'Close',
|
||||||
|
click: () => this.zone.run(() => {
|
||||||
|
this.app.closeTab(tab, true)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Close other tabs',
|
||||||
|
click: () => this.zone.run(() => {
|
||||||
|
for (let t of this.app.tabs.filter(x => x !== tab)) {
|
||||||
|
this.app.closeTab(t, true)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Close tabs to the right',
|
||||||
|
click: () => this.zone.run(() => {
|
||||||
|
for (let t of this.app.tabs.slice(this.app.tabs.indexOf(tab) + 1)) {
|
||||||
|
this.app.closeTab(t, true)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Close tabs to the left',
|
||||||
|
click: () => this.zone.run(() => {
|
||||||
|
for (let t of this.app.tabs.slice(0, this.app.tabs.indexOf(tab))) {
|
||||||
|
this.app.closeTab(t, true)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const COLORS = [
|
||||||
|
{ name: 'No color', value: null },
|
||||||
|
{ name: 'Blue', value: '#0275d8' },
|
||||||
|
{ name: 'Green', value: '#5cb85c' },
|
||||||
|
{ name: 'Orange', value: '#f0ad4e' },
|
||||||
|
{ name: 'Purple', value: '#613d7c' },
|
||||||
|
{ name: 'Red', value: '#d9534f' },
|
||||||
|
{ name: 'Yellow', value: '#ffd500' },
|
||||||
|
]
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class CommonOptionsContextMenu extends TabContextMenuItemProvider {
|
||||||
|
weight = -1
|
||||||
|
|
||||||
|
constructor (
|
||||||
|
private zone: NgZone,
|
||||||
|
) {
|
||||||
|
super()
|
||||||
|
}
|
||||||
|
|
||||||
|
async getItems (tab: BaseTabComponent, tabHeader?: TabHeaderComponent): Promise<Electron.MenuItemConstructorOptions[]> {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
label: 'Rename',
|
||||||
|
click: () => this.zone.run(() => tabHeader.showRenameTabModal())
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Color',
|
||||||
|
sublabel: COLORS.find(x => x.value === tab.color).name,
|
||||||
|
submenu: COLORS.map(color => ({
|
||||||
|
label: color.name,
|
||||||
|
type: 'radio',
|
||||||
|
checked: tab.color === color.value,
|
||||||
|
click: () => this.zone.run(() => {
|
||||||
|
tab.color = color.value
|
||||||
|
}),
|
||||||
|
})) as Electron.MenuItemConstructorOptions[],
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class TaskCompletionContextMenu extends TabContextMenuItemProvider {
|
||||||
|
constructor (
|
||||||
|
private app: AppService,
|
||||||
|
private zone: NgZone,
|
||||||
|
) {
|
||||||
|
super()
|
||||||
|
}
|
||||||
|
|
||||||
|
async getItems (tab: BaseTabComponent): Promise<Electron.MenuItemConstructorOptions[]> {
|
||||||
|
let process = await tab.getCurrentProcess()
|
||||||
|
if (process) {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
id: 'process-name',
|
||||||
|
enabled: false,
|
||||||
|
label: 'Current process: ' + process.name,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Notify when done',
|
||||||
|
type: 'checkbox',
|
||||||
|
checked: (tab as any).__completionNotificationEnabled,
|
||||||
|
click: () => this.zone.run(() => {
|
||||||
|
;(tab as any).__completionNotificationEnabled = !(tab as any).__completionNotificationEnabled
|
||||||
|
|
||||||
|
if ((tab as any).__completionNotificationEnabled) {
|
||||||
|
this.app.observeTabCompletion(tab).subscribe(() => {
|
||||||
|
new Notification('Process completed', {
|
||||||
|
body: process.name,
|
||||||
|
}).addEventListener('click', () => {
|
||||||
|
this.app.selectTab(tab)
|
||||||
|
})
|
||||||
|
;(tab as any).__completionNotificationEnabled = false
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
this.app.stopObservingTabCompletion(tab)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
@@ -51,6 +51,7 @@ $input-disabled-bg: #333;
|
|||||||
$input-color: $body-color;
|
$input-color: $body-color;
|
||||||
$input-color-placeholder: #333;
|
$input-color-placeholder: #333;
|
||||||
$input-border-color: #344;
|
$input-border-color: #344;
|
||||||
|
$input-border-width: 0;
|
||||||
//$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;
|
$custom-select-border-radius: 0;
|
||||||
@@ -70,7 +71,7 @@ $popover-bg: $body-bg;
|
|||||||
|
|
||||||
$dropdown-bg: $body-bg;
|
$dropdown-bg: $body-bg;
|
||||||
$dropdown-link-color: $body-color;
|
$dropdown-link-color: $body-color;
|
||||||
$dropdown-link-hover-color: #333;
|
$dropdown-link-hover-color: white;
|
||||||
$dropdown-link-hover-bg: $body-bg2;
|
$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;
|
||||||
@@ -126,7 +127,7 @@ body {
|
|||||||
|
|
||||||
app-root {
|
app-root {
|
||||||
&.no-tabs {
|
&.no-tabs {
|
||||||
background: rgba(0,0,0,.5);
|
background: rgba(0,0,0,.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
&> .content {
|
&> .content {
|
||||||
@@ -346,6 +347,15 @@ ngb-tabset .tab-content {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.list-group.list-group-flush .list-group-item:not(.list-group-item-action) {
|
||||||
|
background: transparent;
|
||||||
|
border-color: rgba(0, 0, 0, 0.2);
|
||||||
|
|
||||||
|
&:not(:last-child) {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
select.form-control {
|
select.form-control {
|
||||||
-webkit-appearance: none;
|
-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-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>");
|
||||||
@@ -362,7 +372,31 @@ toggle.active .body .toggle {
|
|||||||
background: $blue;
|
background: $blue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.modal .modal-footer {
|
||||||
|
background: rgba(0, 0, 0, .25);
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
font-weight: bold;
|
||||||
|
padding: 0.375rem 1.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.list-group-item svg {
|
.list-group-item svg {
|
||||||
fill: white;
|
fill: white;
|
||||||
fill-opacity: 0.75;
|
fill-opacity: 0.75;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
*::-webkit-scrollbar {
|
||||||
|
background: rgba(0, 0, 0, .125);
|
||||||
|
width: 10px;
|
||||||
|
margin: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
*::-webkit-scrollbar-thumb {
|
||||||
|
background: rgba(255, 255, 255, .25);
|
||||||
|
}
|
||||||
|
|
||||||
|
*::-webkit-scrollbar-corner,
|
||||||
|
*::-webkit-resizer {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
@@ -5,19 +5,19 @@ import { Theme } from './api'
|
|||||||
export class StandardTheme extends Theme {
|
export class StandardTheme extends Theme {
|
||||||
name = 'Standard'
|
name = 'Standard'
|
||||||
css = require('./theme.scss')
|
css = require('./theme.scss')
|
||||||
terminalBackground = '#1D272D'
|
terminalBackground = '#222a33'
|
||||||
}
|
}
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class StandardCompactTheme extends Theme {
|
export class StandardCompactTheme extends Theme {
|
||||||
name = 'Compact'
|
name = 'Compact'
|
||||||
css = require('./theme.compact.scss')
|
css = require('./theme.compact.scss')
|
||||||
terminalBackground = '#1D272D'
|
terminalBackground = '#222a33'
|
||||||
}
|
}
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class PaperTheme extends Theme {
|
export class PaperTheme extends Theme {
|
||||||
name = 'Paper'
|
name = 'Paper'
|
||||||
css = require('./theme.paper.scss')
|
css = require('./theme.paper.scss')
|
||||||
terminalBackground = '#1D272D'
|
terminalBackground = '#f7f1e0'
|
||||||
}
|
}
|
||||||
|
@@ -73,6 +73,14 @@ aws4@^1.6.0:
|
|||||||
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f"
|
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f"
|
||||||
integrity sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==
|
integrity sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==
|
||||||
|
|
||||||
|
axios@^0.18.0:
|
||||||
|
version "0.18.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/axios/-/axios-0.18.0.tgz#32d53e4851efdc0a11993b6cd000789d70c05102"
|
||||||
|
integrity sha1-MtU+SFHv3AoRmTts0AB4nXDAUQI=
|
||||||
|
dependencies:
|
||||||
|
follow-redirects "^1.3.0"
|
||||||
|
is-buffer "^1.1.5"
|
||||||
|
|
||||||
bcrypt-pbkdf@^1.0.0:
|
bcrypt-pbkdf@^1.0.0:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e"
|
resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e"
|
||||||
@@ -162,7 +170,7 @@ dashdash@^1.12.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
assert-plus "^1.0.0"
|
assert-plus "^1.0.0"
|
||||||
|
|
||||||
debug@^3.0.0:
|
debug@=3.1.0, debug@^3.0.0:
|
||||||
version "3.1.0"
|
version "3.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
|
resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
|
||||||
integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==
|
integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==
|
||||||
@@ -261,6 +269,13 @@ fast-json-stable-stringify@^2.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2"
|
resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2"
|
||||||
integrity sha1-1RQsDK7msRifh9OnYREGT4bIu/I=
|
integrity sha1-1RQsDK7msRifh9OnYREGT4bIu/I=
|
||||||
|
|
||||||
|
follow-redirects@^1.3.0:
|
||||||
|
version "1.5.10"
|
||||||
|
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.10.tgz#7b7a9f9aea2fdff36786a94ff643ed07f4ff5e2a"
|
||||||
|
integrity sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==
|
||||||
|
dependencies:
|
||||||
|
debug "=3.1.0"
|
||||||
|
|
||||||
forever-agent@~0.6.1:
|
forever-agent@~0.6.1:
|
||||||
version "0.6.1"
|
version "0.6.1"
|
||||||
resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91"
|
resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91"
|
||||||
@@ -341,6 +356,11 @@ http-signature@~1.2.0:
|
|||||||
jsprim "^1.2.2"
|
jsprim "^1.2.2"
|
||||||
sshpk "^1.7.0"
|
sshpk "^1.7.0"
|
||||||
|
|
||||||
|
is-buffer@^1.1.5:
|
||||||
|
version "1.1.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be"
|
||||||
|
integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==
|
||||||
|
|
||||||
is-typedarray@~1.0.0:
|
is-typedarray@~1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
|
resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
|
||||||
@@ -466,10 +486,10 @@ qs@~6.5.1:
|
|||||||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"
|
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"
|
||||||
integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==
|
integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==
|
||||||
|
|
||||||
rage-edit-tmp@^1.1.0:
|
rage-edit@^1.2.0:
|
||||||
version "1.1.0"
|
version "1.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/rage-edit-tmp/-/rage-edit-tmp-1.1.0.tgz#fc5d76716d2fe2cf97dcafbf3e26753e3a08e3b2"
|
resolved "https://registry.yarnpkg.com/rage-edit/-/rage-edit-1.2.0.tgz#991860a60fef934d8a6d0f057e55786b02f94a2b"
|
||||||
integrity sha512-lR97QHY5WSf9orInMJhPqUbenkdiy7QbXUoRMI+wBZGyAPkxNwgo7h6ojq634QrBf/kQo3mVXYjuD3ZYraNaZQ==
|
integrity sha512-0RspBRc2s6We4g7hRCvT5mu7YPEnfjvQK8Tt354a2uUNJCMC7MKLvo/1mLvHUCQ/zbP6siQyp5VRZN7UCpMFZg==
|
||||||
|
|
||||||
request@2.86.0:
|
request@2.86.0:
|
||||||
version "2.86.0"
|
version "2.86.0"
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "terminus-plugin-manager",
|
"name": "terminus-plugin-manager",
|
||||||
"version": "1.0.0-alpha.55",
|
"version": "1.0.68-c17-g8b64a81",
|
||||||
"description": "Terminus' plugin manager",
|
"description": "Terminus' plugin manager",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"terminus-builtin-plugin"
|
"terminus-builtin-plugin"
|
||||||
|
@@ -2,57 +2,61 @@
|
|||||||
strong Error in {{erroredPlugin}}:
|
strong Error in {{erroredPlugin}}:
|
||||||
pre {{errorMessage}}
|
pre {{errorMessage}}
|
||||||
|
|
||||||
button.btn.btn-outline-info.btn-sm.pull-right((click)='openPluginsFolder()')
|
|
||||||
i.fa.fa-folder
|
.d-flex
|
||||||
span Plugins folder
|
h3.mb-1 Installed
|
||||||
|
button.btn.btn-outline-info.btn-sm.ml-auto((click)='openPluginsFolder()')
|
||||||
|
i.fas.fa-folder
|
||||||
|
span Plugins folder
|
||||||
|
|
||||||
h3.mb-1 Installed
|
.list-group.list-group-flush.mt-2
|
||||||
|
.list-group-item.d-flex.align-items-center(*ngFor='let plugin of pluginManager.installedPlugins|orderBy:"name"')
|
||||||
|
.mr-auto.d-flex.flex-column
|
||||||
|
div
|
||||||
|
strong {{plugin.name}}
|
||||||
|
small.text-muted.ml-1(*ngIf='!plugin.isBuiltin') {{plugin.version}} / {{plugin.author}}
|
||||||
|
small.text-warning.ml-1(*ngIf='config.store.pluginBlacklist.includes(plugin.name)') Disabled
|
||||||
|
a.text-muted.mb-0((click)='showPluginInfo(plugin)')
|
||||||
|
small {{plugin.description}}
|
||||||
|
|
||||||
.mb-3.d-flex.w-100.align-items-center(*ngFor='let plugin of pluginManager.installedPlugins|orderBy:"name"')
|
button.btn.btn-primary.ml-2(
|
||||||
button.btn.btn-outline-danger.active.mr-2(
|
*ngIf='npmInstalled && knownUpgrades[plugin.name]',
|
||||||
*ngIf='config.store.pluginBlacklist.includes(plugin.name)',
|
(click)='upgradePlugin(plugin)',
|
||||||
(click)='enablePlugin(plugin)'
|
[disabled]='busy[plugin.name] != undefined'
|
||||||
)
|
)
|
||||||
i.fa.fa-fw.fa-pause
|
i.fas.fa-fw.fa-arrow-up(*ngIf='busy[plugin.name] != BusyState.Installing')
|
||||||
button.btn.btn-outline-secondary.mr-2(
|
i.fas.fa-fw.fa-circle-notch.fa-spin(*ngIf='busy[plugin.name] == BusyState.Installing')
|
||||||
*ngIf='!config.store.pluginBlacklist.includes(plugin.name)',
|
span Upgrade ({{knownUpgrades[plugin.name].version}})
|
||||||
(click)='disablePlugin(plugin)'
|
|
||||||
)
|
|
||||||
i.fa.fa-fw.fa-check
|
|
||||||
|
|
||||||
.mr-auto.d-flex.flex-column
|
button.btn.btn-primary.ml-2(
|
||||||
div
|
*ngIf='config.store.pluginBlacklist.includes(plugin.name)',
|
||||||
strong {{plugin.name}}
|
(click)='enablePlugin(plugin)'
|
||||||
small.text-muted.ml-1 {{plugin.version}} / {{plugin.author}}
|
)
|
||||||
a.text-muted.mb-0((click)='showPluginInfo(plugin)')
|
i.fas.fa-fw.fa-play
|
||||||
small {{plugin.description}}
|
|
||||||
|
button.btn.btn-secondary.ml-2(
|
||||||
button.btn.btn-primary.ml-2(
|
*ngIf='!config.store.pluginBlacklist.includes(plugin.name)',
|
||||||
*ngIf='npmInstalled && knownUpgrades[plugin.name]',
|
(click)='disablePlugin(plugin)'
|
||||||
(click)='upgradePlugin(plugin)',
|
)
|
||||||
[disabled]='busy[plugin.name] != undefined'
|
i.fas.fa-fw.fa-pause
|
||||||
)
|
|
||||||
i.fa.fa-fw.fa-arrow-up(*ngIf='busy[plugin.name] != BusyState.Installing')
|
button.btn.btn-danger.ml-2(
|
||||||
i.fa.fa-fw.fa-circle-o-notch.fa-spin(*ngIf='busy[plugin.name] == BusyState.Installing')
|
(click)='uninstallPlugin(plugin)',
|
||||||
span Upgrade ({{knownUpgrades[plugin.name].version}})
|
*ngIf='!plugin.isBuiltin && npmInstalled',
|
||||||
|
[disabled]='busy[plugin.name] != undefined'
|
||||||
button.btn.btn-outline-danger.ml-2(
|
)
|
||||||
(click)='uninstallPlugin(plugin)',
|
i.fas.fa-fw.fa-trash(*ngIf='busy[plugin.name] != BusyState.Uninstalling')
|
||||||
*ngIf='!plugin.isBuiltin && npmInstalled',
|
i.fas.fa-fw.fa-circle-notch.fa-spin(*ngIf='busy[plugin.name] == BusyState.Uninstalling')
|
||||||
[disabled]='busy[plugin.name] != undefined'
|
|
||||||
)
|
|
||||||
i.fa.fa-fw.fa-trash-o(*ngIf='busy[plugin.name] != BusyState.Uninstalling')
|
|
||||||
i.fa.fa-fw.fa-circle-o-notch.fa-spin(*ngIf='busy[plugin.name] == BusyState.Uninstalling')
|
|
||||||
|
|
||||||
.text-center.mt-5(*ngIf='npmMissing')
|
.text-center.mt-5(*ngIf='npmMissing')
|
||||||
h4 npm not installed
|
h4 npm not installed
|
||||||
p.mb-2 npm is required to install Terminus plugins.
|
p.mb-2 npm is required to install Terminus plugins.
|
||||||
.btn-group
|
.btn-group
|
||||||
button.btn.btn-outline-primary((click)='downloadNPM()')
|
button.btn.btn-outline-primary((click)='downloadNPM()')
|
||||||
i.fa.fa-download
|
i.fas.fa-download
|
||||||
span Get npm
|
span Get npm
|
||||||
button.btn.btn-outline-info((click)='checkNPM()')
|
button.btn.btn-outline-info((click)='checkNPM()')
|
||||||
i.fa.fa-refresh
|
i.fas.fa-refresh
|
||||||
span Try again
|
span Try again
|
||||||
|
|
||||||
div(*ngIf='npmInstalled')
|
div(*ngIf='npmInstalled')
|
||||||
@@ -61,8 +65,8 @@ div(*ngIf='npmInstalled')
|
|||||||
.input-group.mb-3
|
.input-group.mb-3
|
||||||
.input-group-prepend
|
.input-group-prepend
|
||||||
.input-group-text
|
.input-group-text
|
||||||
i.fa.fa-fw.fa-circle-o-notch.fa-spin(*ngIf='!availablePluginsReady')
|
i.fas.fa-fw.fa-circle-o-notch.fa-spin(*ngIf='!availablePluginsReady')
|
||||||
i.fa.fa-fw.fa-search(*ngIf='availablePluginsReady')
|
i.fas.fa-fw.fa-search(*ngIf='availablePluginsReady')
|
||||||
input.form-control(
|
input.form-control(
|
||||||
type='text',
|
type='text',
|
||||||
[(ngModel)]='_1',
|
[(ngModel)]='_1',
|
||||||
@@ -71,19 +75,19 @@ div(*ngIf='npmInstalled')
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
.mb-4(*ngIf='availablePlugins$')
|
.list-group.list-group-flush.mb-4(*ngIf='availablePlugins$')
|
||||||
ng-container(*ngFor='let plugin of (availablePlugins$|async|orderBy:"name")')
|
ng-container(*ngFor='let plugin of (availablePlugins$|async|orderBy:"name")')
|
||||||
.d-flex.w-100.align-items-center.mb-3(*ngIf='!isAlreadyInstalled(plugin)')
|
.list-group-item.d-flex.align-items-center(*ngIf='!isAlreadyInstalled(plugin)')
|
||||||
button.btn.btn-primary.mr-2(
|
button.btn.btn-primary.mr-3(
|
||||||
(click)='installPlugin(plugin)',
|
(click)='installPlugin(plugin)',
|
||||||
[disabled]='busy[plugin.name] != undefined'
|
[disabled]='busy[plugin.name] != undefined'
|
||||||
)
|
)
|
||||||
i.fa.fa-fw.fa-download(*ngIf='busy[plugin.name] != BusyState.Installing')
|
i.fas.fa-fw.fa-download(*ngIf='busy[plugin.name] != BusyState.Installing')
|
||||||
i.fa.fa-fw.fa-circle-o-notch.fa-spin(*ngIf='busy[plugin.name] == BusyState.Installing')
|
i.fas.fa-fw.fa-circle-notch.fa-spin(*ngIf='busy[plugin.name] == BusyState.Installing')
|
||||||
|
|
||||||
div((click)='showPluginInfo(plugin)')
|
div((click)='showPluginInfo(plugin)')
|
||||||
div
|
div
|
||||||
strong {{plugin.name}}
|
strong {{plugin.name}}
|
||||||
small.text-muted.ml-1 {{plugin.version}} / {{plugin.author}}
|
small.text-muted.ml-1 {{plugin.version}} / {{plugin.author}}
|
||||||
i.fa.fa-check.text-success.ml-1(*ngIf='plugin.isOfficial', title='Official')
|
i.fas.fa-check.text-success.ml-1(*ngIf='plugin.isOfficial', title='Official')
|
||||||
small.text-muted {{plugin.description}}
|
small.text-muted {{plugin.description}}
|
||||||
|
@@ -117,7 +117,7 @@ export class PluginsSettingsTabComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
disablePlugin (plugin: IPluginInfo) {
|
disablePlugin (plugin: IPluginInfo) {
|
||||||
this.config.store.pluginBlacklist.push(plugin.name)
|
this.config.store.pluginBlacklist = [...this.config.store.pluginBlacklist, plugin.name]
|
||||||
this.config.save()
|
this.config.save()
|
||||||
this.config.requestRestart()
|
this.config.requestRestart()
|
||||||
}
|
}
|
||||||
|
@@ -22,7 +22,6 @@ import { PluginsSettingsTabProvider } from './settings'
|
|||||||
providers: [
|
providers: [
|
||||||
{ provide: SettingsTabProvider, useClass: PluginsSettingsTabProvider, multi: true },
|
{ provide: SettingsTabProvider, useClass: PluginsSettingsTabProvider, multi: true },
|
||||||
{ provide: ConfigProvider, useClass: PluginsConfigProvider, multi: true },
|
{ provide: ConfigProvider, useClass: PluginsConfigProvider, multi: true },
|
||||||
PluginManagerService,
|
|
||||||
],
|
],
|
||||||
entryComponents: [
|
entryComponents: [
|
||||||
PluginsSettingsTabComponent,
|
PluginsSettingsTabComponent,
|
||||||
|
@@ -23,7 +23,7 @@ export interface IPluginInfo {
|
|||||||
path?: string
|
path?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
@Injectable()
|
@Injectable({ providedIn: 'root' })
|
||||||
export class PluginManagerService {
|
export class PluginManagerService {
|
||||||
logger: Logger
|
logger: Logger
|
||||||
builtinPluginsPath: string = (window as any).builtinPluginsPath
|
builtinPluginsPath: string = (window as any).builtinPluginsPath
|
||||||
@@ -48,7 +48,7 @@ export class PluginManagerService {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (this.hostApp.platform !== Platform.Windows) {
|
if (this.hostApp.platform !== Platform.Windows) {
|
||||||
this.envPath = (await exec('$SHELL -c -i \'echo $PATH\''))[0].toString().trim()
|
this.envPath = (await exec('$SHELL -i -c \'echo $PATH\''))[0].toString().trim()
|
||||||
let searchPaths = this.envPath.split(':')
|
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'))) {
|
||||||
|
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "terminus-settings",
|
"name": "terminus-settings",
|
||||||
"version": "1.0.0-alpha.55",
|
"version": "1.0.68-c17-g8b64a81",
|
||||||
"description": "Terminus terminal settings page",
|
"description": "Terminus terminal settings page",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"terminus-builtin-plugin"
|
"terminus-builtin-plugin"
|
||||||
],
|
],
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"typings": "dist/index.d.ts",
|
"typings": "dist/src/index.d.ts",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "webpack --progress --color --display-modules",
|
"build": "webpack --progress --color --display-modules",
|
||||||
"watch": "webpack --progress --color --watch"
|
"watch": "webpack --progress --color --watch"
|
||||||
@@ -30,6 +30,5 @@
|
|||||||
"@ng-bootstrap/ng-bootstrap": "1.0.0-alpha.22",
|
"@ng-bootstrap/ng-bootstrap": "1.0.0-alpha.22",
|
||||||
"terminus-core": "*",
|
"terminus-core": "*",
|
||||||
"rxjs": "5.3.0"
|
"rxjs": "5.3.0"
|
||||||
},
|
}
|
||||||
"false": {}
|
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
export abstract class SettingsTabProvider {
|
export abstract class SettingsTabProvider {
|
||||||
id: string
|
id: string
|
||||||
|
icon: string
|
||||||
title: string
|
title: string
|
||||||
|
|
||||||
getComponentType (): any {
|
getComponentType (): any {
|
||||||
|
@@ -3,6 +3,7 @@ button.btn.btn-outline-warning.btn-block(*ngIf='config.restartRequested', '(clic
|
|||||||
ngb-tabset.vertical(type='pills', [activeId]='activeTab')
|
ngb-tabset.vertical(type='pills', [activeId]='activeTab')
|
||||||
ngb-tab(id='application')
|
ngb-tab(id='application')
|
||||||
ng-template(ngbTabTitle)
|
ng-template(ngbTabTitle)
|
||||||
|
i.fas.fa-fw.fa-window-maximize.mr-2
|
||||||
| Application
|
| Application
|
||||||
ng-template(ngbTabContent)
|
ng-template(ngbTabContent)
|
||||||
.d-flex.align-items-center.mb-4
|
.d-flex.align-items-center.mb-4
|
||||||
@@ -12,11 +13,11 @@ ngb-tabset.vertical(type='pills', [activeId]='activeTab')
|
|||||||
.text-muted.mr-auto {{homeBase.appVersion}}
|
.text-muted.mr-auto {{homeBase.appVersion}}
|
||||||
|
|
||||||
button.btn.btn-secondary.mr-3((click)='homeBase.openGitHub()')
|
button.btn.btn-secondary.mr-3((click)='homeBase.openGitHub()')
|
||||||
i.fa.fa-github
|
i.fab.fa-github
|
||||||
span GitHub
|
span GitHub
|
||||||
|
|
||||||
button.btn.btn-secondary((click)='homeBase.reportBug()')
|
button.btn.btn-secondary((click)='homeBase.reportBug()')
|
||||||
i.fa.fa-bug
|
i.fas.fa-bug
|
||||||
span Report a problem
|
span Report a problem
|
||||||
|
|
||||||
.form-line(*ngIf='!isShellIntegrationInstalled')
|
.form-line(*ngIf='!isShellIntegrationInstalled')
|
||||||
@@ -24,7 +25,7 @@ ngb-tabset.vertical(type='pills', [activeId]='activeTab')
|
|||||||
.title Shell integration
|
.title Shell integration
|
||||||
.description Allows quickly opening a terminal in the selected folder
|
.description Allows quickly opening a terminal in the selected folder
|
||||||
button.btn.btn-primary((click)='installShellIntegration()')
|
button.btn.btn-primary((click)='installShellIntegration()')
|
||||||
i.fa.fa-check
|
i.fas.fa-check
|
||||||
span Install
|
span Install
|
||||||
|
|
||||||
.form-line
|
.form-line
|
||||||
@@ -225,7 +226,7 @@ ngb-tabset.vertical(type='pills', [activeId]='activeTab')
|
|||||||
.title Debugging
|
.title Debugging
|
||||||
|
|
||||||
button.btn.btn-secondary((click)='hostApp.openDevTools()')
|
button.btn.btn-secondary((click)='hostApp.openDevTools()')
|
||||||
i.fa.fa-bug
|
i.fas.fa-bug
|
||||||
span Open DevTools
|
span Open DevTools
|
||||||
|
|
||||||
.form-line
|
.form-line
|
||||||
@@ -247,6 +248,7 @@ ngb-tabset.vertical(type='pills', [activeId]='activeTab')
|
|||||||
|
|
||||||
ngb-tab(id='hotkeys')
|
ngb-tab(id='hotkeys')
|
||||||
ng-template(ngbTabTitle)
|
ng-template(ngbTabTitle)
|
||||||
|
i.fas.fa-fw.fa-keyboard.mr-2
|
||||||
| Hotkeys
|
| Hotkeys
|
||||||
ng-template(ngbTabContent)
|
ng-template(ngbTabContent)
|
||||||
h3.mb-3 Hotkeys
|
h3.mb-3 Hotkeys
|
||||||
@@ -254,7 +256,7 @@ ngb-tabset.vertical(type='pills', [activeId]='activeTab')
|
|||||||
.input-group.mb-4
|
.input-group.mb-4
|
||||||
.input-group-prepend
|
.input-group-prepend
|
||||||
.input-group-text
|
.input-group-text
|
||||||
i.fa.fa-fw.fa-search
|
i.fas.fa-fw.fa-search
|
||||||
input.form-control(type='search', placeholder='Search hotkeys', [(ngModel)]='hotkeyFilter')
|
input.form-control(type='search', placeholder='Search hotkeys', [(ngModel)]='hotkeyFilter')
|
||||||
|
|
||||||
.form-group
|
.form-group
|
||||||
@@ -274,6 +276,7 @@ ngb-tabset.vertical(type='pills', [activeId]='activeTab')
|
|||||||
|
|
||||||
ngb-tab(*ngFor='let provider of settingsProviders', [id]='provider.id')
|
ngb-tab(*ngFor='let provider of settingsProviders', [id]='provider.id')
|
||||||
ng-template(ngbTabTitle)
|
ng-template(ngbTabTitle)
|
||||||
|
i(class='fas fa-fw mr-2 fa-{{provider.icon || "puzzle-piece"}}')
|
||||||
| {{provider.title}}
|
| {{provider.title}}
|
||||||
ng-template(ngbTabContent)
|
ng-template(ngbTabContent)
|
||||||
settings-tab-body([provider]='provider')
|
settings-tab-body([provider]='provider')
|
||||||
@@ -281,6 +284,7 @@ ngb-tabset.vertical(type='pills', [activeId]='activeTab')
|
|||||||
|
|
||||||
ngb-tab(id='config-file')
|
ngb-tab(id='config-file')
|
||||||
ng-template(ngbTabTitle)
|
ng-template(ngbTabTitle)
|
||||||
|
i.fas.fa-fw.fa-code.mr-2
|
||||||
| Config file
|
| Config file
|
||||||
ng-template.test(ngbTabContent)
|
ng-template.test(ngbTabContent)
|
||||||
.d-flex.flex-column.w-100.h-100
|
.d-flex.flex-column.w-100.h-100
|
||||||
@@ -298,8 +302,8 @@ ngb-tabset.vertical(type='pills', [activeId]='activeTab')
|
|||||||
)
|
)
|
||||||
.mt-3
|
.mt-3
|
||||||
button.btn.btn-primary((click)='saveConfigFile()', *ngIf='isConfigFileValid()')
|
button.btn.btn-primary((click)='saveConfigFile()', *ngIf='isConfigFileValid()')
|
||||||
i.fa.fa-check.mr-2
|
i.fas.fa-check.mr-2
|
||||||
| Save and apply
|
| Save and apply
|
||||||
button.btn.btn-primary(disabled, *ngIf='!isConfigFileValid()')
|
button.btn.btn-primary(disabled, *ngIf='!isConfigFileValid()')
|
||||||
i.fa.fa-warning.mr-2
|
i.fas.fa-exclamation-triangle.mr-2
|
||||||
| Invalid syntax
|
| Invalid syntax
|
||||||
|
@@ -1,14 +1,13 @@
|
|||||||
import * as yaml from 'js-yaml'
|
import * as yaml from 'js-yaml'
|
||||||
import * as os from 'os'
|
import * as os from 'os'
|
||||||
import { Subscription } from 'rxjs'
|
import { Subscription } from 'rxjs'
|
||||||
import { Component, Inject, Input } from '@angular/core'
|
import { Component, Inject, Input, HostBinding } from '@angular/core'
|
||||||
import { HotkeysService } from 'terminus-core'
|
|
||||||
import {
|
import {
|
||||||
ElectronService,
|
ElectronService,
|
||||||
DockingService,
|
DockingService,
|
||||||
ConfigService,
|
ConfigService,
|
||||||
IHotkeyDescription,
|
IHotkeyDescription,
|
||||||
HotkeyProvider,
|
HotkeysService,
|
||||||
BaseTabComponent,
|
BaseTabComponent,
|
||||||
Theme,
|
Theme,
|
||||||
HostAppService,
|
HostAppService,
|
||||||
@@ -37,6 +36,7 @@ export class SettingsTabComponent extends BaseTabComponent {
|
|||||||
configFile: string
|
configFile: string
|
||||||
isShellIntegrationInstalled = false
|
isShellIntegrationInstalled = false
|
||||||
isFluentVibrancySupported = false
|
isFluentVibrancySupported = false
|
||||||
|
@HostBinding('class.pad-window-controls') padWindowControls = false
|
||||||
private configSubscription: Subscription
|
private configSubscription: Subscription
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
@@ -47,7 +47,6 @@ export class SettingsTabComponent extends BaseTabComponent {
|
|||||||
public homeBase: HomeBaseService,
|
public homeBase: HomeBaseService,
|
||||||
public shellIntegration: ShellIntegrationService,
|
public shellIntegration: ShellIntegrationService,
|
||||||
hotkeys: HotkeysService,
|
hotkeys: HotkeysService,
|
||||||
@Inject(HotkeyProvider) hotkeyProviders: HotkeyProvider[],
|
|
||||||
@Inject(SettingsTabProvider) public settingsProviders: SettingsTabProvider[],
|
@Inject(SettingsTabProvider) public settingsProviders: SettingsTabProvider[],
|
||||||
@Inject(Theme) public themes: Theme[],
|
@Inject(Theme) public themes: Theme[],
|
||||||
) {
|
) {
|
||||||
@@ -58,16 +57,21 @@ export class SettingsTabComponent extends BaseTabComponent {
|
|||||||
this.themes = config.enabledServices(this.themes)
|
this.themes = config.enabledServices(this.themes)
|
||||||
|
|
||||||
this.configDefaults = yaml.safeDump(config.getDefaults())
|
this.configDefaults = yaml.safeDump(config.getDefaults())
|
||||||
this.configFile = config.readRaw()
|
|
||||||
this.configSubscription = config.changed$.subscribe(() => {
|
const onConfigChange = () => {
|
||||||
this.configFile = config.readRaw()
|
this.configFile = config.readRaw()
|
||||||
})
|
this.padWindowControls = hostApp.platform === Platform.macOS
|
||||||
|
&& config.store.appearance.tabsLocation === 'bottom'
|
||||||
|
}
|
||||||
|
|
||||||
|
this.configSubscription = config.changed$.subscribe(onConfigChange)
|
||||||
|
onConfigChange()
|
||||||
|
|
||||||
hotkeys.getHotkeyDescriptions().then(descriptions => {
|
hotkeys.getHotkeyDescriptions().then(descriptions => {
|
||||||
this.hotkeyDescriptions = descriptions
|
this.hotkeyDescriptions = descriptions
|
||||||
})
|
})
|
||||||
|
|
||||||
this.isFluentVibrancySupported = process.platform === 'win32'
|
this.isFluentVibrancySupported = hostApp.platform === Platform.Windows
|
||||||
&& parseFloat(os.release()) >= 10
|
&& parseFloat(os.release()) >= 10
|
||||||
&& parseInt(os.release().split('.')[2]) >= 17063
|
&& parseInt(os.release().split('.')[2]) >= 17063
|
||||||
}
|
}
|
||||||
@@ -76,7 +80,7 @@ export class SettingsTabComponent extends BaseTabComponent {
|
|||||||
this.isShellIntegrationInstalled = await this.shellIntegration.isInstalled()
|
this.isShellIntegrationInstalled = await this.shellIntegration.isInstalled()
|
||||||
}
|
}
|
||||||
|
|
||||||
getRecoveryToken (): any {
|
async getRecoveryToken (): Promise<any> {
|
||||||
return { type: 'app:settings' }
|
return { type: 'app:settings' }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -21,3 +21,7 @@
|
|||||||
:host /deep/ ngb-tabset > .tab-content > .tab-pane {
|
:host /deep/ ngb-tabset > .tab-content > .tab-pane {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:host.pad-window-controls /deep/ ngb-tabset > .nav {
|
||||||
|
padding-top: 40px;
|
||||||
|
}
|
||||||
|
@@ -7,7 +7,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
|
||||||
component: ComponentRef<Component>
|
component: ComponentRef<Component>
|
||||||
|
|
||||||
constructor (private componentFactoryResolver: ComponentFactoryResolver) { }
|
constructor (private componentFactoryResolver: ComponentFactoryResolver) { }
|
||||||
|
@@ -4,8 +4,7 @@ import { FormsModule } from '@angular/forms'
|
|||||||
import { NgbModule } from '@ng-bootstrap/ng-bootstrap'
|
import { NgbModule } from '@ng-bootstrap/ng-bootstrap'
|
||||||
import { NgPipesModule } from 'ngx-pipes'
|
import { NgPipesModule } from 'ngx-pipes'
|
||||||
|
|
||||||
import { ToolbarButtonProvider, TabRecoveryProvider, HotkeyProvider, ConfigProvider } from 'terminus-core'
|
import TerminusCorePlugin, { ToolbarButtonProvider, TabRecoveryProvider, HotkeyProvider, ConfigProvider } from 'terminus-core'
|
||||||
import TerminusCorePlugin from 'terminus-core'
|
|
||||||
|
|
||||||
import { HotkeyInputModalComponent } from './components/hotkeyInputModal.component'
|
import { HotkeyInputModalComponent } from './components/hotkeyInputModal.component'
|
||||||
import { MultiHotkeyInputComponent } from './components/multiHotkeyInput.component'
|
import { MultiHotkeyInputComponent } from './components/multiHotkeyInput.component'
|
||||||
|
@@ -6,7 +6,7 @@ import { SettingsTabComponent } from './components/settingsTab.component'
|
|||||||
@Injectable()
|
@Injectable()
|
||||||
export class RecoveryProvider extends TabRecoveryProvider {
|
export class RecoveryProvider extends TabRecoveryProvider {
|
||||||
async recover (recoveryToken: any): Promise<RecoveredTab> {
|
async recover (recoveryToken: any): Promise<RecoveredTab> {
|
||||||
if (recoveryToken.type === 'app:settings') {
|
if (recoveryToken && recoveryToken.type === 'app:settings') {
|
||||||
return { type: SettingsTabComponent }
|
return { type: SettingsTabComponent }
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
|
@@ -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',
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "terminus-ssh",
|
"name": "terminus-ssh",
|
||||||
"version": "1.0.0-alpha.55",
|
"version": "1.0.68-c17-g8b64a81",
|
||||||
"description": "SSH connection manager for Terminus",
|
"description": "SSH connection manager for Terminus",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"terminus-builtin-plugin"
|
"terminus-builtin-plugin"
|
||||||
@@ -36,7 +36,8 @@
|
|||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"wincredmgr": "^2.0.0",
|
"wincredmgr": "^2.0.0",
|
||||||
"xkeychain": "^0.0.6"
|
"xkeychain": "^0.0.6",
|
||||||
|
"windows-process-tree": "^0.2.3"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ssh2": "^0.5.5"
|
"ssh2": "^0.5.5"
|
||||||
|
@@ -23,10 +23,11 @@ export interface SSHConnection {
|
|||||||
|
|
||||||
export class SSHSession extends BaseSession {
|
export class SSHSession extends BaseSession {
|
||||||
scripts?: LoginScript[]
|
scripts?: LoginScript[]
|
||||||
|
shell: any
|
||||||
|
|
||||||
constructor (private shell: any, conn: SSHConnection) {
|
constructor (public connection: SSHConnection) {
|
||||||
super()
|
super()
|
||||||
this.scripts = conn.scripts || []
|
this.scripts = connection.scripts || []
|
||||||
}
|
}
|
||||||
|
|
||||||
start () {
|
start () {
|
||||||
@@ -87,15 +88,21 @@ export class SSHSession extends BaseSession {
|
|||||||
}
|
}
|
||||||
|
|
||||||
resize (columns, rows) {
|
resize (columns, rows) {
|
||||||
this.shell.setWindow(rows, columns)
|
if (this.shell) {
|
||||||
|
this.shell.setWindow(rows, columns)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
write (data) {
|
write (data) {
|
||||||
this.shell.write(data)
|
if (this.shell) {
|
||||||
|
this.shell.write(data)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
kill (signal?: string) {
|
kill (signal?: string) {
|
||||||
this.shell.signal(signal || 'TERM')
|
if (this.shell) {
|
||||||
|
this.shell.signal(signal || 'TERM')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async getChildProcesses (): Promise<any[]> {
|
async getChildProcesses (): Promise<any[]> {
|
||||||
|
@@ -56,7 +56,7 @@
|
|||||||
)
|
)
|
||||||
.input-group-btn
|
.input-group-btn
|
||||||
button.btn.btn-secondary((click)='selectPrivateKey()')
|
button.btn.btn-secondary((click)='selectPrivateKey()')
|
||||||
i.fa.fa-folder-open
|
i.fas.fa-folder-open
|
||||||
|
|
||||||
ngb-tab(id='advanced')
|
ngb-tab(id='advanced')
|
||||||
ng-template(ngbTabTitle)
|
ng-template(ngbTabTitle)
|
||||||
@@ -119,11 +119,11 @@
|
|||||||
td
|
td
|
||||||
.input-group.flex-nowrap
|
.input-group.flex-nowrap
|
||||||
button.btn.btn-outline-info.ml-0((click)='moveScriptUp(script)')
|
button.btn.btn-outline-info.ml-0((click)='moveScriptUp(script)')
|
||||||
i.fa.fa-arrow-up
|
i.fas.fa-arrow-up
|
||||||
button.btn.btn-outline-info.ml-0((click)='moveScriptDown(script)')
|
button.btn.btn-outline-info.ml-0((click)='moveScriptDown(script)')
|
||||||
i.fa.fa-arrow-down
|
i.fas.fa-arrow-down
|
||||||
button.btn.btn-outline-danger.ml-0((click)='deleteScript(script)')
|
button.btn.btn-outline-danger.ml-0((click)='deleteScript(script)')
|
||||||
i.fa.fa-trash-o
|
i.fas.fa-trash
|
||||||
tr
|
tr
|
||||||
td
|
td
|
||||||
input.form-control(
|
input.form-control(
|
||||||
@@ -148,9 +148,9 @@
|
|||||||
td
|
td
|
||||||
.input-group.flex-nowrap
|
.input-group.flex-nowrap
|
||||||
button.btn.btn-outline-info.ml-0((click)='addScript()')
|
button.btn.btn-outline-info.ml-0((click)='addScript()')
|
||||||
i.fa.fa-check
|
i.fas.fa-check
|
||||||
button.btn.btn-outline-danger.ml-0((click)='clearScript()')
|
button.btn.btn-outline-danger.ml-0((click)='clearScript()')
|
||||||
i.fa.fa-trash-o
|
i.fas.fa-trash
|
||||||
|
|
||||||
.modal-footer
|
.modal-footer
|
||||||
button.btn.btn-outline-primary((click)='save()') Save
|
button.btn.btn-outline-primary((click)='save()') Save
|
||||||
|
@@ -66,8 +66,17 @@ export class EditConnectionModalComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteScript (script: LoginScript) {
|
async deleteScript (script: LoginScript) {
|
||||||
if (confirm(`Delete?`)) {
|
if ((await this.electron.showMessageBox(
|
||||||
|
this.hostApp.getWindow(),
|
||||||
|
{
|
||||||
|
type: 'warning',
|
||||||
|
message: 'Delete this script?',
|
||||||
|
detail: script.expect,
|
||||||
|
buttons: ['Keep', 'Delete'],
|
||||||
|
defaultId: 1,
|
||||||
|
}
|
||||||
|
)).response === 1) {
|
||||||
this.connection.scripts = this.connection.scripts.filter(x => x !== script)
|
this.connection.scripts = this.connection.scripts.filter(x => x !== script)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -76,7 +85,7 @@ export class EditConnectionModalComponent {
|
|||||||
if (!this.connection.scripts) {
|
if (!this.connection.scripts) {
|
||||||
this.connection.scripts = []
|
this.connection.scripts = []
|
||||||
}
|
}
|
||||||
this.connection.scripts.push({...this.newScript})
|
this.connection.scripts.push({ ...this.newScript })
|
||||||
this.clearScript()
|
this.clearScript()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -10,10 +10,10 @@
|
|||||||
|
|
||||||
.list-group.mt-3(*ngIf='lastConnection')
|
.list-group.mt-3(*ngIf='lastConnection')
|
||||||
a.list-group-item.list-group-item-action.d-flex.align-items-center((click)='connect(lastConnection)')
|
a.list-group-item.list-group-item-action.d-flex.align-items-center((click)='connect(lastConnection)')
|
||||||
i.fa.fa-fw.fa-history
|
i.fas.fa-fw.fa-history
|
||||||
.mr-auto {{lastConnection.name}}
|
.mr-auto {{lastConnection.name}}
|
||||||
button.btn.btn-outline-danger.btn-sm((click)='clearLastConnection(); $event.stopPropagation()')
|
button.btn.btn-outline-danger.btn-sm((click)='clearLastConnection(); $event.stopPropagation()')
|
||||||
i.fa.fa-trash-o
|
i.fas.fa-trash
|
||||||
|
|
||||||
.list-group.mt-3.connections-list(*ngIf='childGroups.length')
|
.list-group.mt-3.connections-list(*ngIf='childGroups.length')
|
||||||
ng-container(*ngFor='let group of childGroups')
|
ng-container(*ngFor='let group of childGroups')
|
||||||
@@ -27,4 +27,6 @@
|
|||||||
.list-group-item.list-group-item-action.pl-5.d-flex.align-items-center(
|
.list-group-item.list-group-item-action.pl-5.d-flex.align-items-center(
|
||||||
*ngFor='let connection of group.connections',
|
*ngFor='let connection of group.connections',
|
||||||
(click)='connect(connection)'
|
(click)='connect(connection)'
|
||||||
) {{connection.name}}
|
)
|
||||||
|
.mr-2 {{connection.name}}
|
||||||
|
.text-muted {{connection.host}}
|
||||||
|
@@ -61,7 +61,7 @@ export class SSHModalComponent {
|
|||||||
|
|
||||||
connect (connection: SSHConnection) {
|
connect (connection: SSHConnection) {
|
||||||
this.close()
|
this.close()
|
||||||
this.ssh.connect(connection).catch(error => {
|
this.ssh.openTab(connection).catch(error => {
|
||||||
this.toastr.error(`Could not connect: ${error}`)
|
this.toastr.error(`Could not connect: ${error}`)
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
@@ -1,25 +1,28 @@
|
|||||||
h3 Connections
|
h3 Connections
|
||||||
|
|
||||||
.list-group.mt-3.mb-3
|
.list-group.list-group-flush.mt-3.mb-3
|
||||||
ng-container(*ngFor='let group of childGroups')
|
ng-container(*ngFor='let group of childGroups')
|
||||||
.list-group-item.list-group-item-action.d-flex.align-items-center((click)='groupCollapsed[group.name] = !groupCollapsed[group.name]')
|
.list-group-item.list-group-item-action.d-flex.align-items-center(
|
||||||
|
(click)='groupCollapsed[group.name] = !groupCollapsed[group.name]'
|
||||||
|
)
|
||||||
.fa.fa-fw.fa-chevron-right(*ngIf='groupCollapsed[group.name]')
|
.fa.fa-fw.fa-chevron-right(*ngIf='groupCollapsed[group.name]')
|
||||||
.fa.fa-fw.fa-chevron-down(*ngIf='!groupCollapsed[group.name]')
|
.fa.fa-fw.fa-chevron-down(*ngIf='!groupCollapsed[group.name]')
|
||||||
span.ml-3.mr-auto {{group.name || "Ungrouped"}}
|
span.ml-3.mr-auto {{group.name || "Ungrouped"}}
|
||||||
button.btn.btn-outline-info.ml-2((click)='editGroup(group)')
|
button.btn.btn-outline-info.ml-2((click)='editGroup(group)')
|
||||||
i.fa.fa-pencil
|
i.fas.fa-edit
|
||||||
button.btn.btn-outline-danger.ml-1((click)='deleteGroup(group)')
|
button.btn.btn-outline-danger.ml-1((click)='deleteGroup(group)')
|
||||||
i.fa.fa-trash-o
|
i.fas.fa-trash
|
||||||
ng-container(*ngIf='!groupCollapsed[group.name]')
|
ng-container(*ngIf='!groupCollapsed[group.name]')
|
||||||
.list-group-item.pl-5.d-flex.align-items-center(*ngFor='let connection of group.connections')
|
.list-group-item.list-group-item-action.pl-5.d-flex.align-items-center(
|
||||||
|
*ngFor='let connection of group.connections',
|
||||||
|
(click)='editConnection(connection)'
|
||||||
|
)
|
||||||
.mr-auto
|
.mr-auto
|
||||||
div {{connection.name}}
|
div {{connection.name}}
|
||||||
.text-muted {{connection.host}}
|
.text-muted {{connection.host}}
|
||||||
button.btn.btn-outline-info.ml-2((click)='editConnection(connection)')
|
button.btn.btn-outline-danger.ml-1((click)='$event.stopPropagation(); deleteConnection(connection)')
|
||||||
i.fa.fa-pencil
|
i.fas.fa-trash
|
||||||
button.btn.btn-outline-danger.ml-1((click)='deleteConnection(connection)')
|
|
||||||
i.fa.fa-trash-o
|
|
||||||
|
|
||||||
button.btn.btn-outline-primary((click)='createConnection()')
|
button.btn.btn-primary((click)='createConnection()')
|
||||||
div.fa.fa-fw.fa-globe
|
i.fas.fa-fw.fa-plus
|
||||||
span.ml-2 Add connection
|
span.ml-2 Add connection
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import { Component } from '@angular/core'
|
import { Component } from '@angular/core'
|
||||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
||||||
import { ConfigService } from 'terminus-core'
|
import { ConfigService, ElectronService, HostAppService } from 'terminus-core'
|
||||||
import { SSHConnection, ISSHConnectionGroup } from '../api'
|
import { SSHConnection, ISSHConnectionGroup } from '../api'
|
||||||
import { EditConnectionModalComponent } from './editConnectionModal.component'
|
import { EditConnectionModalComponent } from './editConnectionModal.component'
|
||||||
import { PromptModalComponent } from './promptModal.component'
|
import { PromptModalComponent } from './promptModal.component'
|
||||||
@@ -15,6 +15,8 @@ export class SSHSettingsTabComponent {
|
|||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
public config: ConfigService,
|
public config: ConfigService,
|
||||||
|
private electron: ElectronService,
|
||||||
|
private hostApp: HostAppService,
|
||||||
private ngbModal: NgbModal,
|
private ngbModal: NgbModal,
|
||||||
) {
|
) {
|
||||||
this.connections = this.config.store.ssh.connections
|
this.connections = this.config.store.ssh.connections
|
||||||
@@ -44,13 +46,22 @@ export class SSHSettingsTabComponent {
|
|||||||
modal.componentInstance.connection = Object.assign({}, connection)
|
modal.componentInstance.connection = Object.assign({}, connection)
|
||||||
modal.result.then(result => {
|
modal.result.then(result => {
|
||||||
Object.assign(connection, result)
|
Object.assign(connection, result)
|
||||||
|
this.config.store.ssh.connections = this.connections
|
||||||
this.config.save()
|
this.config.save()
|
||||||
this.refresh()
|
this.refresh()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteConnection (connection: SSHConnection) {
|
async deleteConnection (connection: SSHConnection) {
|
||||||
if (confirm(`Delete "${connection.name}"?`)) {
|
if ((await this.electron.showMessageBox(
|
||||||
|
this.hostApp.getWindow(),
|
||||||
|
{
|
||||||
|
type: 'warning',
|
||||||
|
message: `Delete "${connection.name}"?`,
|
||||||
|
buttons: ['Keep', 'Delete'],
|
||||||
|
defaultId: 1,
|
||||||
|
}
|
||||||
|
)).response === 1) {
|
||||||
this.connections = this.connections.filter(x => x !== connection)
|
this.connections = this.connections.filter(x => x !== connection)
|
||||||
this.config.store.ssh.connections = this.connections
|
this.config.store.ssh.connections = this.connections
|
||||||
this.config.save()
|
this.config.save()
|
||||||
@@ -67,14 +78,23 @@ export class SSHSettingsTabComponent {
|
|||||||
for (let connection of this.connections.filter(x => x.group === group.name)) {
|
for (let connection of this.connections.filter(x => x.group === group.name)) {
|
||||||
connection.group = result
|
connection.group = result
|
||||||
}
|
}
|
||||||
|
this.config.store.ssh.connections = this.connections
|
||||||
this.config.save()
|
this.config.save()
|
||||||
this.refresh()
|
this.refresh()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteGroup (group: ISSHConnectionGroup) {
|
async deleteGroup (group: ISSHConnectionGroup) {
|
||||||
if (confirm(`Delete "${group}"?`)) {
|
if ((await this.electron.showMessageBox(
|
||||||
|
this.hostApp.getWindow(),
|
||||||
|
{
|
||||||
|
type: 'warning',
|
||||||
|
message: `Delete "${group}"?`,
|
||||||
|
buttons: ['Keep', 'Delete'],
|
||||||
|
defaultId: 1,
|
||||||
|
}
|
||||||
|
)).response === 1) {
|
||||||
for (let connection of this.connections.filter(x => x.group === group.name)) {
|
for (let connection of this.connections.filter(x => x.group === group.name)) {
|
||||||
connection.group = null
|
connection.group = null
|
||||||
}
|
}
|
||||||
@@ -84,6 +104,7 @@ export class SSHSettingsTabComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
refresh () {
|
refresh () {
|
||||||
|
this.connections = this.config.store.ssh.connections
|
||||||
this.childGroups = []
|
this.childGroups = []
|
||||||
|
|
||||||
for (let connection of this.connections) {
|
for (let connection of this.connections) {
|
||||||
|
13
terminus-ssh/src/components/sshTab.component.scss
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
:host {
|
||||||
|
flex: auto;
|
||||||
|
display: flex;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
&> .content {
|
||||||
|
flex: auto;
|
||||||
|
position: relative;
|
||||||
|
display: block;
|
||||||
|
overflow: hidden;
|
||||||
|
margin: 15px;
|
||||||
|
}
|
||||||
|
}
|
63
terminus-ssh/src/components/sshTab.component.ts
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
import { Component } from '@angular/core'
|
||||||
|
import { first } from 'rxjs/operators'
|
||||||
|
import { BaseTerminalTabComponent } from 'terminus-terminal'
|
||||||
|
import { SSHService } from '../services/ssh.service'
|
||||||
|
import { SSHConnection, SSHSession } from '../api'
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
template: `
|
||||||
|
<div
|
||||||
|
#content
|
||||||
|
class="content"
|
||||||
|
></div>
|
||||||
|
`,
|
||||||
|
styles: [require('./sshTab.component.scss')],
|
||||||
|
})
|
||||||
|
export class SSHTabComponent extends BaseTerminalTabComponent {
|
||||||
|
connection: SSHConnection
|
||||||
|
ssh: SSHService
|
||||||
|
session: SSHSession
|
||||||
|
|
||||||
|
ngOnInit () {
|
||||||
|
this.logger = this.log.create('terminalTab')
|
||||||
|
this.ssh = this.injector.get(SSHService)
|
||||||
|
this.frontendReady$.pipe(first()).subscribe(() => {
|
||||||
|
this.initializeSession()
|
||||||
|
})
|
||||||
|
|
||||||
|
super.ngOnInit()
|
||||||
|
}
|
||||||
|
|
||||||
|
async initializeSession () {
|
||||||
|
if (!this.connection) {
|
||||||
|
this.logger.error('No SSH connection info supplied')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.session = new SSHSession(this.connection)
|
||||||
|
this.attachSessionHandlers()
|
||||||
|
this.write(`Connecting to ${this.connection.host}`)
|
||||||
|
let interval = setInterval(() => this.write('.'), 500)
|
||||||
|
try {
|
||||||
|
await this.ssh.connectSession(this.session, message => {
|
||||||
|
this.write('\r\n' + message)
|
||||||
|
})
|
||||||
|
} catch (e) {
|
||||||
|
this.write('\r\n')
|
||||||
|
this.write(e.message)
|
||||||
|
return
|
||||||
|
} finally {
|
||||||
|
clearInterval(interval)
|
||||||
|
this.write('\r\n')
|
||||||
|
}
|
||||||
|
this.session.resize(this.size.columns, this.size.rows)
|
||||||
|
this.session.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
async getRecoveryToken (): Promise<any> {
|
||||||
|
return {
|
||||||
|
type: 'app:ssh-tab',
|
||||||
|
connection: this.connection,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -3,20 +3,19 @@ import { CommonModule } from '@angular/common'
|
|||||||
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 { ToastrModule } from 'ngx-toastr'
|
||||||
import { ToolbarButtonProvider, ConfigProvider } from 'terminus-core'
|
import TerminusCoreModule, { ToolbarButtonProvider, ConfigProvider, TabRecoveryProvider } from 'terminus-core'
|
||||||
import TerminusCoreModule from 'terminus-core'
|
|
||||||
import { SettingsTabProvider } from 'terminus-settings'
|
import { SettingsTabProvider } from 'terminus-settings'
|
||||||
|
|
||||||
import { EditConnectionModalComponent } from './components/editConnectionModal.component'
|
import { EditConnectionModalComponent } from './components/editConnectionModal.component'
|
||||||
import { SSHModalComponent } from './components/sshModal.component'
|
import { SSHModalComponent } from './components/sshModal.component'
|
||||||
import { PromptModalComponent } from './components/promptModal.component'
|
import { PromptModalComponent } from './components/promptModal.component'
|
||||||
import { SSHSettingsTabComponent } from './components/sshSettingsTab.component'
|
import { SSHSettingsTabComponent } from './components/sshSettingsTab.component'
|
||||||
import { SSHService } from './services/ssh.service'
|
import { SSHTabComponent } from './components/sshTab.component'
|
||||||
import { PasswordStorageService } from './services/passwordStorage.service'
|
|
||||||
|
|
||||||
import { ButtonProvider } from './buttonProvider'
|
import { ButtonProvider } from './buttonProvider'
|
||||||
import { SSHConfigProvider } from './config'
|
import { SSHConfigProvider } from './config'
|
||||||
import { SSHSettingsTabProvider } from './settings'
|
import { SSHSettingsTabProvider } from './settings'
|
||||||
|
import { RecoveryProvider } from './recoveryProvider'
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -27,23 +26,24 @@ import { SSHSettingsTabProvider } from './settings'
|
|||||||
TerminusCoreModule,
|
TerminusCoreModule,
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
PasswordStorageService,
|
|
||||||
SSHService,
|
|
||||||
{ provide: ToolbarButtonProvider, useClass: ButtonProvider, multi: true },
|
{ provide: ToolbarButtonProvider, useClass: ButtonProvider, multi: true },
|
||||||
{ provide: ConfigProvider, useClass: SSHConfigProvider, multi: true },
|
{ provide: ConfigProvider, useClass: SSHConfigProvider, multi: true },
|
||||||
{ provide: SettingsTabProvider, useClass: SSHSettingsTabProvider, multi: true },
|
{ provide: SettingsTabProvider, useClass: SSHSettingsTabProvider, multi: true },
|
||||||
|
{ provide: TabRecoveryProvider, useClass: RecoveryProvider, multi: true },
|
||||||
],
|
],
|
||||||
entryComponents: [
|
entryComponents: [
|
||||||
EditConnectionModalComponent,
|
EditConnectionModalComponent,
|
||||||
PromptModalComponent,
|
PromptModalComponent,
|
||||||
SSHModalComponent,
|
SSHModalComponent,
|
||||||
SSHSettingsTabComponent,
|
SSHSettingsTabComponent,
|
||||||
|
SSHTabComponent,
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
EditConnectionModalComponent,
|
EditConnectionModalComponent,
|
||||||
PromptModalComponent,
|
PromptModalComponent,
|
||||||
SSHModalComponent,
|
SSHModalComponent,
|
||||||
SSHSettingsTabComponent,
|
SSHSettingsTabComponent,
|
||||||
|
SSHTabComponent,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export default class SSHModule { }
|
export default class SSHModule { }
|
||||||
|
17
terminus-ssh/src/recoveryProvider.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { Injectable } from '@angular/core'
|
||||||
|
import { TabRecoveryProvider, RecoveredTab } from 'terminus-core'
|
||||||
|
|
||||||
|
import { SSHTabComponent } from './components/sshTab.component'
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class RecoveryProvider extends TabRecoveryProvider {
|
||||||
|
async recover (recoveryToken: any): Promise<RecoveredTab> {
|
||||||
|
if (recoveryToken && recoveryToken.type === 'app:ssh-tab') {
|
||||||
|
return {
|
||||||
|
type: SSHTabComponent,
|
||||||
|
options: { connection: recoveryToken.connection },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
@@ -13,7 +13,7 @@ try {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Injectable()
|
@Injectable({ providedIn: 'root' })
|
||||||
export class PasswordStorageService {
|
export class PasswordStorageService {
|
||||||
constructor (
|
constructor (
|
||||||
private zone: NgZone,
|
private zone: NgZone,
|
||||||
|
@@ -5,13 +5,19 @@ import * as fs from 'mz/fs'
|
|||||||
import * as path from 'path'
|
import * as path from 'path'
|
||||||
import { ToastrService } from 'ngx-toastr'
|
import { ToastrService } from 'ngx-toastr'
|
||||||
import { AppService, HostAppService, Platform, Logger, LogService } from 'terminus-core'
|
import { AppService, HostAppService, Platform, Logger, LogService } from 'terminus-core'
|
||||||
import { TerminalTabComponent } from 'terminus-terminal'
|
|
||||||
import { SSHConnection, SSHSession } from '../api'
|
import { SSHConnection, SSHSession } from '../api'
|
||||||
import { PromptModalComponent } from '../components/promptModal.component'
|
import { PromptModalComponent } from '../components/promptModal.component'
|
||||||
|
import { SSHTabComponent } from '../components/sshTab.component'
|
||||||
import { PasswordStorageService } from './passwordStorage.service'
|
import { PasswordStorageService } from './passwordStorage.service'
|
||||||
const { SSH2Stream } = require('ssh2-streams')
|
const { SSH2Stream } = require('ssh2-streams')
|
||||||
|
|
||||||
@Injectable()
|
let windowsProcessTree
|
||||||
|
try {
|
||||||
|
windowsProcessTree = require('windows-process-tree/build/Release/windows_process_tree.node')
|
||||||
|
} catch (e) {
|
||||||
|
} // tslint:disable-line
|
||||||
|
|
||||||
|
@Injectable({ providedIn: 'root' })
|
||||||
export class SSHService {
|
export class SSHService {
|
||||||
private logger: Logger
|
private logger: Logger
|
||||||
|
|
||||||
@@ -27,14 +33,31 @@ export class SSHService {
|
|||||||
this.logger = log.create('ssh')
|
this.logger = log.create('ssh')
|
||||||
}
|
}
|
||||||
|
|
||||||
async connect (connection: SSHConnection): Promise<TerminalTabComponent> {
|
async openTab (connection: SSHConnection): Promise<SSHTabComponent> {
|
||||||
|
return this.zone.run(() => this.app.openNewTab(
|
||||||
|
SSHTabComponent,
|
||||||
|
{ connection }
|
||||||
|
) as SSHTabComponent)
|
||||||
|
}
|
||||||
|
|
||||||
|
async connectSession (session: SSHSession, logCallback?: (s: string) => void): Promise<void> {
|
||||||
let privateKey: string = null
|
let privateKey: string = null
|
||||||
let privateKeyPassphrase: string = null
|
let privateKeyPassphrase: string = null
|
||||||
let privateKeyPath = connection.privateKey
|
let privateKeyPath = session.connection.privateKey
|
||||||
|
|
||||||
|
if (!logCallback) {
|
||||||
|
logCallback = (s) => null
|
||||||
|
}
|
||||||
|
|
||||||
|
const log = s => {
|
||||||
|
logCallback(s)
|
||||||
|
this.logger.info(s)
|
||||||
|
}
|
||||||
|
|
||||||
if (!privateKeyPath) {
|
if (!privateKeyPath) {
|
||||||
let userKeyPath = path.join(process.env.HOME, '.ssh', 'id_rsa')
|
let userKeyPath = path.join(process.env.HOME, '.ssh', 'id_rsa')
|
||||||
if (await fs.exists(userKeyPath)) {
|
if (await fs.exists(userKeyPath)) {
|
||||||
this.logger.info('Using user\'s default private key:', userKeyPath)
|
log(`Using user's default private key: ${userKeyPath}`)
|
||||||
privateKeyPath = userKeyPath
|
privateKeyPath = userKeyPath
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -43,11 +66,12 @@ export class SSHService {
|
|||||||
try {
|
try {
|
||||||
privateKey = (await fs.readFile(privateKeyPath)).toString()
|
privateKey = (await fs.readFile(privateKeyPath)).toString()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
log('Could not read the private key file')
|
||||||
this.toastr.warning('Could not read the private key file')
|
this.toastr.warning('Could not read the private key file')
|
||||||
}
|
}
|
||||||
|
|
||||||
if (privateKey) {
|
if (privateKey) {
|
||||||
this.logger.info('Loaded private key from', privateKeyPath)
|
log(`Loading private key from ${privateKeyPath}`)
|
||||||
|
|
||||||
let encrypted = privateKey.includes('ENCRYPTED')
|
let encrypted = privateKey.includes('ENCRYPTED')
|
||||||
if (privateKeyPath.toLowerCase().endsWith('.ppk')) {
|
if (privateKeyPath.toLowerCase().endsWith('.ppk')) {
|
||||||
@@ -55,6 +79,7 @@ export class SSHService {
|
|||||||
}
|
}
|
||||||
if (encrypted) {
|
if (encrypted) {
|
||||||
let modal = this.ngbModal.open(PromptModalComponent)
|
let modal = this.ngbModal.open(PromptModalComponent)
|
||||||
|
log('Key requires passphrase')
|
||||||
modal.componentInstance.prompt = 'Private key passphrase'
|
modal.componentInstance.prompt = 'Private key passphrase'
|
||||||
modal.componentInstance.password = true
|
modal.componentInstance.password = true
|
||||||
try {
|
try {
|
||||||
@@ -67,16 +92,16 @@ export class SSHService {
|
|||||||
let ssh = new Client()
|
let ssh = new Client()
|
||||||
let connected = false
|
let connected = false
|
||||||
let savedPassword: string = null
|
let savedPassword: string = null
|
||||||
await new Promise((resolve, reject) => {
|
await new Promise(async (resolve, reject) => {
|
||||||
ssh.on('ready', () => {
|
ssh.on('ready', () => {
|
||||||
connected = true
|
connected = true
|
||||||
if (savedPassword) {
|
if (savedPassword) {
|
||||||
this.passwordStorage.savePassword(connection, savedPassword)
|
this.passwordStorage.savePassword(session.connection, savedPassword)
|
||||||
}
|
}
|
||||||
this.zone.run(resolve)
|
this.zone.run(resolve)
|
||||||
})
|
})
|
||||||
ssh.on('error', error => {
|
ssh.on('error', error => {
|
||||||
this.passwordStorage.deletePassword(connection)
|
this.passwordStorage.deletePassword(session.connection)
|
||||||
this.zone.run(() => {
|
this.zone.run(() => {
|
||||||
if (connected) {
|
if (connected) {
|
||||||
this.toastr.error(error.toString())
|
this.toastr.error(error.toString())
|
||||||
@@ -86,7 +111,8 @@ export class SSHService {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
ssh.on('keyboard-interactive', (name, instructions, instructionsLang, prompts, finish) => this.zone.run(async () => {
|
ssh.on('keyboard-interactive', (name, instructions, instructionsLang, prompts, finish) => this.zone.run(async () => {
|
||||||
console.log(name, instructions, instructionsLang)
|
log(`Keyboard-interactive auth requested: ${name}`)
|
||||||
|
this.logger.info('Keyboard-interactive auth:', name, instructions, instructionsLang)
|
||||||
let results = []
|
let results = []
|
||||||
for (let prompt of prompts) {
|
for (let prompt of prompts) {
|
||||||
let modal = this.ngbModal.open(PromptModalComponent)
|
let modal = this.ngbModal.open(PromptModalComponent)
|
||||||
@@ -97,55 +123,84 @@ export class SSHService {
|
|||||||
finish(results)
|
finish(results)
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
ssh.on('greeting', greeting => {
|
||||||
|
log('Greeting: ' + greeting)
|
||||||
|
})
|
||||||
|
|
||||||
|
ssh.on('banner', banner => {
|
||||||
|
log('Banner: ' + banner)
|
||||||
|
})
|
||||||
|
|
||||||
let agent: string = null
|
let agent: string = null
|
||||||
if (this.hostApp.platform === Platform.Windows) {
|
if (this.hostApp.platform === Platform.Windows) {
|
||||||
agent = 'pageant'
|
let pageantRunning = new Promise<boolean>(resolve => {
|
||||||
|
windowsProcessTree.getProcessList(list => {
|
||||||
|
resolve(list.some(x => x.name === 'pageant.exe'))
|
||||||
|
}, 0)
|
||||||
|
})
|
||||||
|
if (await pageantRunning) {
|
||||||
|
agent = 'pageant'
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
agent = process.env.SSH_AUTH_SOCK
|
agent = process.env.SSH_AUTH_SOCK
|
||||||
}
|
}
|
||||||
|
|
||||||
ssh.connect({
|
try {
|
||||||
host: connection.host,
|
ssh.connect({
|
||||||
port: connection.port || 22,
|
host: session.connection.host,
|
||||||
username: connection.user,
|
port: session.connection.port || 22,
|
||||||
password: connection.privateKey ? undefined : '',
|
username: session.connection.user,
|
||||||
privateKey,
|
password: session.connection.privateKey ? undefined : '',
|
||||||
passphrase: privateKeyPassphrase,
|
privateKey,
|
||||||
tryKeyboard: true,
|
passphrase: privateKeyPassphrase,
|
||||||
agent,
|
tryKeyboard: true,
|
||||||
agentForward: !!agent,
|
agent,
|
||||||
keepaliveInterval: connection.keepaliveInterval,
|
agentForward: !!agent,
|
||||||
keepaliveCountMax: connection.keepaliveCountMax,
|
keepaliveInterval: session.connection.keepaliveInterval,
|
||||||
readyTimeout: connection.readyTimeout,
|
keepaliveCountMax: session.connection.keepaliveCountMax,
|
||||||
})
|
readyTimeout: session.connection.readyTimeout,
|
||||||
|
hostVerifier: digest => {
|
||||||
|
log('SHA256 fingerprint: ' + digest)
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
hostHash: 'sha256' as any,
|
||||||
|
})
|
||||||
|
} catch (e) {
|
||||||
|
this.toastr.error(e.message)
|
||||||
|
reject(e)
|
||||||
|
}
|
||||||
|
|
||||||
let keychainPasswordUsed = false
|
let keychainPasswordUsed = false
|
||||||
|
|
||||||
;(ssh as any).config.password = () => this.zone.run(async () => {
|
;(ssh as any).config.password = () => this.zone.run(async () => {
|
||||||
if (connection.password) {
|
if (session.connection.password) {
|
||||||
this.logger.info('Using preset password')
|
log('Using preset password')
|
||||||
return connection.password
|
return session.connection.password
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!keychainPasswordUsed) {
|
if (!keychainPasswordUsed) {
|
||||||
let password = await this.passwordStorage.loadPassword(connection)
|
let password = await this.passwordStorage.loadPassword(session.connection)
|
||||||
if (password) {
|
if (password) {
|
||||||
this.logger.info('Using saved password')
|
log('Trying saved password')
|
||||||
keychainPasswordUsed = true
|
keychainPasswordUsed = true
|
||||||
return password
|
return password
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let modal = this.ngbModal.open(PromptModalComponent)
|
let modal = this.ngbModal.open(PromptModalComponent)
|
||||||
modal.componentInstance.prompt = `Password for ${connection.user}@${connection.host}`
|
modal.componentInstance.prompt = `Password for ${session.connection.user}@${session.connection.host}`
|
||||||
modal.componentInstance.password = true
|
modal.componentInstance.password = true
|
||||||
savedPassword = await modal.result
|
try {
|
||||||
|
savedPassword = await modal.result
|
||||||
|
} catch (_) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
return savedPassword
|
return savedPassword
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let shell = await new Promise((resolve, reject) => {
|
let shell: any = await new Promise<any>((resolve, reject) => {
|
||||||
ssh.shell({ term: 'xterm-256color' }, (err, shell) => {
|
ssh.shell({ term: 'xterm-256color' }, (err, shell) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
reject(err)
|
reject(err)
|
||||||
@@ -155,14 +210,17 @@ export class SSHService {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
let session = new SSHSession(shell, connection)
|
session.shell = shell
|
||||||
|
|
||||||
return this.zone.run(() => this.app.openNewTab(
|
shell.on('greeting', greeting => {
|
||||||
TerminalTabComponent,
|
log('Shell Greeting: ' + greeting)
|
||||||
{ session, sessionOptions: {} }
|
})
|
||||||
) as TerminalTabComponent)
|
|
||||||
|
shell.on('banner', banner => {
|
||||||
|
log('Shell Banner: ' + banner)
|
||||||
|
})
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error)
|
this.toastr.error(error.message)
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -6,6 +6,7 @@ import { SSHSettingsTabComponent } from './components/sshSettingsTab.component'
|
|||||||
@Injectable()
|
@Injectable()
|
||||||
export class SSHSettingsTabProvider extends SettingsTabProvider {
|
export class SSHSettingsTabProvider extends SettingsTabProvider {
|
||||||
id = 'ssh'
|
id = 'ssh'
|
||||||
|
icon = 'globe'
|
||||||
title = 'SSH'
|
title = 'SSH'
|
||||||
|
|
||||||
getComponentType (): any {
|
getComponentType (): any {
|
||||||
|
@@ -48,6 +48,7 @@ module.exports = {
|
|||||||
'wincredmgr',
|
'wincredmgr',
|
||||||
'path',
|
'path',
|
||||||
'ngx-toastr',
|
'ngx-toastr',
|
||||||
|
'windows-process-tree/build/Release/windows_process_tree.node',
|
||||||
/^rxjs/,
|
/^rxjs/,
|
||||||
/^@angular/,
|
/^@angular/,
|
||||||
/^@ng-bootstrap/,
|
/^@ng-bootstrap/,
|
||||||
|
@@ -1507,6 +1507,11 @@ ms@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"
|
||||||
integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=
|
integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=
|
||||||
|
|
||||||
|
nan@^2.10.0:
|
||||||
|
version "2.11.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/nan/-/nan-2.11.1.tgz#90e22bccb8ca57ea4cd37cc83d3819b52eea6766"
|
||||||
|
integrity sha512-iji6k87OSXa0CcrLl9z+ZiYSuR2o+c0bGuNmXdrhTQTakxytAFsC56SArGYoiHlJlFoHSnvmhpceZJaXkVuOtA==
|
||||||
|
|
||||||
nan@^2.3.0:
|
nan@^2.3.0:
|
||||||
version "2.8.0"
|
version "2.8.0"
|
||||||
resolved "https://registry.yarnpkg.com/nan/-/nan-2.8.0.tgz#ed715f3fe9de02b57a5e6252d90a96675e1f085a"
|
resolved "https://registry.yarnpkg.com/nan/-/nan-2.8.0.tgz#ed715f3fe9de02b57a5e6252d90a96675e1f085a"
|
||||||
@@ -2520,6 +2525,13 @@ window-size@0.1.0:
|
|||||||
resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.0.tgz#5438cd2ea93b202efa3a19fe8887aee7c94f9c9d"
|
resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.0.tgz#5438cd2ea93b202efa3a19fe8887aee7c94f9c9d"
|
||||||
integrity sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=
|
integrity sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=
|
||||||
|
|
||||||
|
windows-process-tree@^0.2.3:
|
||||||
|
version "0.2.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/windows-process-tree/-/windows-process-tree-0.2.3.tgz#6b781f0a320e8a0d6434c9399add4389c709cf6e"
|
||||||
|
integrity sha512-SzPJSubVVsToz1g5lr2P+4mQT70gvJ9u/nlnpfkOeQcAhOuhKz5DiO1TARgR0OnVsv21LPzxbA2m/4JQkGh1wA==
|
||||||
|
dependencies:
|
||||||
|
nan "^2.10.0"
|
||||||
|
|
||||||
wordwrap@0.0.2:
|
wordwrap@0.0.2:
|
||||||
version "0.0.2"
|
version "0.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.2.tgz#b79669bb42ecb409f83d583cad52ca17eaa1643f"
|
resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.2.tgz#b79669bb42ecb409f83d583cad52ca17eaa1643f"
|
||||||
|
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "terminus-terminal",
|
"name": "terminus-terminal",
|
||||||
"version": "1.0.0-alpha.55",
|
"version": "1.0.68-c17-g8b64a81",
|
||||||
"description": "Terminus' terminal emulation core",
|
"description": "Terminus' terminal emulation core",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"terminus-builtin-plugin"
|
"terminus-builtin-plugin"
|
||||||
],
|
],
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"typings": "dist/index.d.ts",
|
"typings": "dist/src/index.d.ts",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "webpack --progress --color --display-modules",
|
"build": "webpack --progress --color --display-modules",
|
||||||
"watch": "webpack --progress --color --watch"
|
"watch": "webpack --progress --color --watch"
|
||||||
@@ -24,9 +24,9 @@
|
|||||||
"dataurl": "0.1.0",
|
"dataurl": "0.1.0",
|
||||||
"deep-equal": "1.0.1",
|
"deep-equal": "1.0.1",
|
||||||
"file-loader": "^0.11.2",
|
"file-loader": "^0.11.2",
|
||||||
"rage-edit-tmp": "^1.1.0",
|
"rage-edit": "1.2.0",
|
||||||
"uuid": "^3.3.2",
|
"uuid": "^3.3.2",
|
||||||
"xterm": "^3.8.0",
|
"xterm": "3.10.1",
|
||||||
"xterm-addon-ligatures-tmp": "^0.1.0-beta-1"
|
"xterm-addon-ligatures-tmp": "^0.1.0-beta-1"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
@@ -40,12 +40,10 @@
|
|||||||
"terminus-settings": "*"
|
"terminus-settings": "*"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/async-lock": "0.0.19",
|
|
||||||
"async-lock": "^1.0.0",
|
|
||||||
"font-manager": "0.3.0",
|
"font-manager": "0.3.0",
|
||||||
"hterm-umdjs": "1.4.1",
|
"hterm-umdjs": "1.4.1",
|
||||||
"mz": "^2.6.0",
|
"mz": "^2.6.0",
|
||||||
"node-pty-tmp": "0.7.2",
|
"node-pty": "^0.8.0",
|
||||||
"ps-node": "^0.1.6",
|
"ps-node": "^0.1.6",
|
||||||
"runes": "^0.4.2"
|
"runes": "^0.4.2"
|
||||||
},
|
},
|
||||||
|
@@ -1,11 +1,10 @@
|
|||||||
import { Observable } from 'rxjs'
|
import { BaseTerminalTabComponent } from './components/baseTerminalTab.component'
|
||||||
import { TerminalTabComponent } from './components/terminalTab.component'
|
|
||||||
|
|
||||||
export abstract class TerminalDecorator {
|
export abstract class TerminalDecorator {
|
||||||
// tslint:disable-next-line no-empty
|
// tslint:disable-next-line no-empty
|
||||||
attach (_terminal: TerminalTabComponent): void { }
|
attach (_terminal: BaseTerminalTabComponent): void { }
|
||||||
// tslint:disable-next-line no-empty
|
// tslint:disable-next-line no-empty
|
||||||
detach (_terminal: TerminalTabComponent): void { }
|
detach (_terminal: BaseTerminalTabComponent): void { }
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ResizeEvent {
|
export interface ResizeEvent {
|
||||||
@@ -15,25 +14,19 @@ export interface ResizeEvent {
|
|||||||
|
|
||||||
export interface SessionOptions {
|
export interface SessionOptions {
|
||||||
name?: string
|
name?: string
|
||||||
command?: string
|
command: string
|
||||||
args?: string[]
|
args: string[]
|
||||||
cwd?: string
|
cwd?: string
|
||||||
env?: any
|
env?: any
|
||||||
width?: number
|
width?: number
|
||||||
height?: number
|
height?: number
|
||||||
recoveryId?: string
|
|
||||||
recoveredTruePID$?: Observable<number>
|
|
||||||
pauseAfterExit?: boolean
|
pauseAfterExit?: boolean
|
||||||
|
runAsAdministrator?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export abstract class SessionPersistenceProvider {
|
export interface Profile {
|
||||||
abstract id: string
|
name: string,
|
||||||
abstract displayName: string
|
sessionOptions: SessionOptions,
|
||||||
|
|
||||||
abstract isAvailable (): boolean
|
|
||||||
abstract async attachSession (recoveryId: any): Promise<SessionOptions>
|
|
||||||
abstract async startSession (options: SessionOptions): Promise<any>
|
|
||||||
abstract async terminateSession (recoveryId: string): Promise<void>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ITerminalColorScheme {
|
export interface ITerminalColorScheme {
|
||||||
@@ -48,6 +41,12 @@ export abstract class TerminalColorSchemeProvider {
|
|||||||
abstract async getSchemes (): Promise<ITerminalColorScheme[]>
|
abstract async getSchemes (): Promise<ITerminalColorScheme[]>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export abstract class TerminalContextMenuItemProvider {
|
||||||
|
weight: number
|
||||||
|
|
||||||
|
abstract async getItems (tab: BaseTerminalTabComponent): Promise<Electron.MenuItemConstructorOptions[]>
|
||||||
|
}
|
||||||
|
|
||||||
export interface IShell {
|
export interface IShell {
|
||||||
id: string
|
id: string
|
||||||
name?: string
|
name?: string
|
||||||
|
@@ -57,7 +57,7 @@ h3.mb-3 Appearance
|
|||||||
(click)='deleteScheme(config.store.terminal.colorScheme)',
|
(click)='deleteScheme(config.store.terminal.colorScheme)',
|
||||||
*ngIf='isCustomScheme(config.store.terminal.colorScheme)'
|
*ngIf='isCustomScheme(config.store.terminal.colorScheme)'
|
||||||
)
|
)
|
||||||
i.fa.fa-trash-o
|
i.fas.fa-trash
|
||||||
|
|
||||||
.form-group(*ngIf='editingColorScheme')
|
.form-group(*ngIf='editingColorScheme')
|
||||||
label Editing
|
label Editing
|
||||||
|
@@ -5,7 +5,7 @@ import deepEqual = require('deep-equal')
|
|||||||
const fontManager = require('font-manager')
|
const fontManager = require('font-manager')
|
||||||
|
|
||||||
import { Component, Inject } from '@angular/core'
|
import { Component, Inject } from '@angular/core'
|
||||||
import { ConfigService, HostAppService, Platform } from 'terminus-core'
|
import { ConfigService, HostAppService, Platform, ElectronService } from 'terminus-core'
|
||||||
import { TerminalColorSchemeProvider, ITerminalColorScheme } from '../api'
|
import { TerminalColorSchemeProvider, ITerminalColorScheme } from '../api'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@@ -22,6 +22,7 @@ export class AppearanceSettingsTabComponent {
|
|||||||
constructor (
|
constructor (
|
||||||
@Inject(TerminalColorSchemeProvider) private colorSchemeProviders: TerminalColorSchemeProvider[],
|
@Inject(TerminalColorSchemeProvider) private colorSchemeProviders: TerminalColorSchemeProvider[],
|
||||||
private hostApp: HostAppService,
|
private hostApp: HostAppService,
|
||||||
|
private electron: ElectronService,
|
||||||
public config: ConfigService,
|
public config: ConfigService,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
@@ -71,8 +72,16 @@ export class AppearanceSettingsTabComponent {
|
|||||||
this.editingColorScheme = null
|
this.editingColorScheme = null
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteScheme (scheme: ITerminalColorScheme) {
|
async deleteScheme (scheme: ITerminalColorScheme) {
|
||||||
if (confirm(`Delete "${scheme.name}"?`)) {
|
if ((await this.electron.showMessageBox(
|
||||||
|
this.hostApp.getWindow(),
|
||||||
|
{
|
||||||
|
type: 'warning',
|
||||||
|
message: `Delete "${scheme.name}"?`,
|
||||||
|
buttons: ['Keep', 'Delete'],
|
||||||
|
defaultId: 1,
|
||||||
|
}
|
||||||
|
)).response === 1) {
|
||||||
let schemes = this.config.store.terminal.customColorSchemes
|
let schemes = this.config.store.terminal.customColorSchemes
|
||||||
schemes = schemes.filter(x => x !== scheme)
|
schemes = schemes.filter(x => x !== scheme)
|
||||||
this.config.store.terminal.customColorSchemes = schemes
|
this.config.store.terminal.customColorSchemes = schemes
|
||||||
|
357
terminus-terminal/src/components/baseTerminalTab.component.ts
Normal file
@@ -0,0 +1,357 @@
|
|||||||
|
import { Observable, Subject, Subscription } from 'rxjs'
|
||||||
|
import { first } from 'rxjs/operators'
|
||||||
|
import { ToastrService } from 'ngx-toastr'
|
||||||
|
import { NgZone, OnInit, OnDestroy, Inject, Injector, Optional, ViewChild, HostBinding, Input, ElementRef } from '@angular/core'
|
||||||
|
import { AppService, ConfigService, BaseTabComponent, ElectronService, HostAppService, HotkeysService, Platform, LogService, Logger } from 'terminus-core'
|
||||||
|
|
||||||
|
import { BaseSession, SessionsService } from '../services/sessions.service'
|
||||||
|
import { TerminalFrontendService } from '../services/terminalFrontend.service'
|
||||||
|
|
||||||
|
import { TerminalDecorator, ResizeEvent, TerminalContextMenuItemProvider } from '../api'
|
||||||
|
import { Frontend } from '../frontends/frontend'
|
||||||
|
|
||||||
|
export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit, OnDestroy {
|
||||||
|
static template = `
|
||||||
|
<div
|
||||||
|
#content
|
||||||
|
class="content"
|
||||||
|
[style.opacity]="htermVisible ? 1 : 0"
|
||||||
|
></div>
|
||||||
|
`
|
||||||
|
static styles = [require('./terminalTab.component.scss')]
|
||||||
|
|
||||||
|
session: BaseSession
|
||||||
|
@Input() zoom = 0
|
||||||
|
@ViewChild('content') content
|
||||||
|
@HostBinding('style.background-color') backgroundColor: string
|
||||||
|
frontend: Frontend
|
||||||
|
sessionCloseSubscription: Subscription
|
||||||
|
hotkeysSubscription: Subscription
|
||||||
|
htermVisible = false
|
||||||
|
frontendReady = new Subject<void>()
|
||||||
|
size: ResizeEvent
|
||||||
|
protected logger: Logger
|
||||||
|
protected output = new Subject<string>()
|
||||||
|
private bellPlayer: HTMLAudioElement
|
||||||
|
private termContainerSubscriptions: Subscription[] = []
|
||||||
|
|
||||||
|
get input$ (): Observable<string> { return this.frontend.input$ }
|
||||||
|
get output$ (): Observable<string> { return this.output }
|
||||||
|
get resize$ (): Observable<ResizeEvent> { return this.frontend.resize$ }
|
||||||
|
get alternateScreenActive$ (): Observable<boolean> { return this.frontend.alternateScreenActive$ }
|
||||||
|
get frontendReady$ (): Observable<void> { return this.frontendReady }
|
||||||
|
|
||||||
|
constructor (
|
||||||
|
public config: ConfigService,
|
||||||
|
public element: ElementRef,
|
||||||
|
protected injector: Injector,
|
||||||
|
protected zone: NgZone,
|
||||||
|
protected app: AppService,
|
||||||
|
protected hostApp: HostAppService,
|
||||||
|
protected hotkeys: HotkeysService,
|
||||||
|
protected sessions: SessionsService,
|
||||||
|
protected electron: ElectronService,
|
||||||
|
protected terminalContainersService: TerminalFrontendService,
|
||||||
|
protected toastr: ToastrService,
|
||||||
|
protected log: LogService,
|
||||||
|
@Optional() @Inject(TerminalDecorator) protected decorators: TerminalDecorator[],
|
||||||
|
@Optional() @Inject(TerminalContextMenuItemProvider) protected contextMenuProviders: TerminalContextMenuItemProvider[],
|
||||||
|
) {
|
||||||
|
super()
|
||||||
|
this.logger = log.create('baseTerminalTab')
|
||||||
|
this.decorators = this.decorators || []
|
||||||
|
this.setTitle('Terminal')
|
||||||
|
|
||||||
|
this.hotkeysSubscription = this.hotkeys.matchedHotkey.subscribe(hotkey => {
|
||||||
|
if (!this.hasFocus) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch (hotkey) {
|
||||||
|
case 'ctrl-c':
|
||||||
|
if (this.frontend.getSelection()) {
|
||||||
|
this.frontend.copySelection()
|
||||||
|
this.frontend.clearSelection()
|
||||||
|
this.toastr.info('Copied')
|
||||||
|
} else {
|
||||||
|
this.sendInput('\x03')
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case 'copy':
|
||||||
|
this.frontend.copySelection()
|
||||||
|
this.toastr.info('Copied')
|
||||||
|
break
|
||||||
|
case 'paste':
|
||||||
|
this.paste()
|
||||||
|
break
|
||||||
|
case 'clear':
|
||||||
|
this.frontend.clear()
|
||||||
|
break
|
||||||
|
case 'zoom-in':
|
||||||
|
this.zoomIn()
|
||||||
|
break
|
||||||
|
case 'zoom-out':
|
||||||
|
this.zoomOut()
|
||||||
|
break
|
||||||
|
case 'reset-zoom':
|
||||||
|
this.resetZoom()
|
||||||
|
break
|
||||||
|
case 'home':
|
||||||
|
this.sendInput('\x1bOH')
|
||||||
|
break
|
||||||
|
case 'end':
|
||||||
|
this.sendInput('\x1bOF')
|
||||||
|
break
|
||||||
|
case 'previous-word':
|
||||||
|
this.sendInput('\x1bb')
|
||||||
|
break
|
||||||
|
case 'next-word':
|
||||||
|
this.sendInput('\x1bf')
|
||||||
|
break
|
||||||
|
case 'delete-previous-word':
|
||||||
|
this.sendInput('\x1b\x7f')
|
||||||
|
break
|
||||||
|
case 'delete-next-word':
|
||||||
|
this.sendInput('\x1bd')
|
||||||
|
break
|
||||||
|
}
|
||||||
|
})
|
||||||
|
this.bellPlayer = document.createElement('audio')
|
||||||
|
this.bellPlayer.src = require<string>('../bell.ogg')
|
||||||
|
|
||||||
|
this.contextMenuProviders.sort((a, b) => a.weight - b.weight)
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit () {
|
||||||
|
this.focused$.subscribe(() => {
|
||||||
|
this.configure()
|
||||||
|
this.frontend.focus()
|
||||||
|
})
|
||||||
|
|
||||||
|
this.frontend = this.terminalContainersService.getFrontend(this.session)
|
||||||
|
|
||||||
|
this.frontend.ready$.subscribe(() => {
|
||||||
|
this.htermVisible = true
|
||||||
|
})
|
||||||
|
|
||||||
|
this.frontend.resize$.pipe(first()).subscribe(async ({ columns, rows }) => {
|
||||||
|
this.size = { columns, rows }
|
||||||
|
this.frontendReady.next()
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
this.session.resize(columns, rows)
|
||||||
|
}, 1000)
|
||||||
|
|
||||||
|
this.session.releaseInitialDataBuffer()
|
||||||
|
})
|
||||||
|
|
||||||
|
this.frontend.configure()
|
||||||
|
this.frontend.attach(this.content.nativeElement)
|
||||||
|
this.attachTermContainerHandlers()
|
||||||
|
|
||||||
|
this.configure()
|
||||||
|
|
||||||
|
this.config.enabledServices(this.decorators).forEach((decorator) => {
|
||||||
|
decorator.attach(this)
|
||||||
|
})
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
this.output.subscribe(() => {
|
||||||
|
this.displayActivity()
|
||||||
|
})
|
||||||
|
}, 1000)
|
||||||
|
|
||||||
|
this.frontend.bell$.subscribe(() => {
|
||||||
|
if (this.config.store.terminal.bell === 'visual') {
|
||||||
|
this.frontend.visualBell()
|
||||||
|
}
|
||||||
|
if (this.config.store.terminal.bell === 'audible') {
|
||||||
|
this.bellPlayer.play()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
this.frontend.focus()
|
||||||
|
}
|
||||||
|
|
||||||
|
async buildContextMenu (): Promise<Electron.MenuItemConstructorOptions[]> {
|
||||||
|
let items: Electron.MenuItemConstructorOptions[] = []
|
||||||
|
for (let section of await Promise.all(this.contextMenuProviders.map(x => x.getItems(this)))) {
|
||||||
|
items = items.concat(section)
|
||||||
|
items.push({ type: 'separator' })
|
||||||
|
}
|
||||||
|
items.splice(items.length - 1, 1)
|
||||||
|
return items
|
||||||
|
}
|
||||||
|
|
||||||
|
detachTermContainerHandlers () {
|
||||||
|
for (let subscription of this.termContainerSubscriptions) {
|
||||||
|
subscription.unsubscribe()
|
||||||
|
}
|
||||||
|
this.termContainerSubscriptions = []
|
||||||
|
}
|
||||||
|
|
||||||
|
attachTermContainerHandlers () {
|
||||||
|
this.detachTermContainerHandlers()
|
||||||
|
this.termContainerSubscriptions = [
|
||||||
|
this.frontend.title$.subscribe(title => this.zone.run(() => this.setTitle(title))),
|
||||||
|
|
||||||
|
this.focused$.subscribe(() => this.frontend.enableResizing = true),
|
||||||
|
this.blurred$.subscribe(() => this.frontend.enableResizing = false),
|
||||||
|
|
||||||
|
this.frontend.mouseEvent$.subscribe(async event => {
|
||||||
|
if (event.type === 'mousedown') {
|
||||||
|
if (event.which === 2) {
|
||||||
|
this.paste()
|
||||||
|
event.preventDefault()
|
||||||
|
event.stopPropagation()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (event.which === 3) {
|
||||||
|
if (this.config.store.terminal.rightClick === 'menu') {
|
||||||
|
this.hostApp.popupContextMenu(await this.buildContextMenu())
|
||||||
|
} else if (this.config.store.terminal.rightClick === 'paste') {
|
||||||
|
this.paste()
|
||||||
|
}
|
||||||
|
event.preventDefault()
|
||||||
|
event.stopPropagation()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (event.type === 'mousewheel') {
|
||||||
|
let wheelDeltaY = 0
|
||||||
|
|
||||||
|
if ('wheelDeltaY' in event) {
|
||||||
|
wheelDeltaY = (event as MouseWheelEvent)['wheelDeltaY']
|
||||||
|
} else {
|
||||||
|
wheelDeltaY = (event as MouseWheelEvent)['deltaY']
|
||||||
|
}
|
||||||
|
if (event.ctrlKey || event.metaKey) {
|
||||||
|
|
||||||
|
if (wheelDeltaY > 0) {
|
||||||
|
this.zoomIn()
|
||||||
|
} else {
|
||||||
|
this.zoomOut()
|
||||||
|
}
|
||||||
|
} else if (event.altKey) {
|
||||||
|
event.preventDefault()
|
||||||
|
let delta = Math.round(wheelDeltaY / 50)
|
||||||
|
this.sendInput(((delta > 0) ? '\u001bOA' : '\u001bOB').repeat(Math.abs(delta)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
this.frontend.input$.subscribe(data => {
|
||||||
|
this.sendInput(data)
|
||||||
|
}),
|
||||||
|
|
||||||
|
this.frontend.resize$.subscribe(({ columns, rows }) => {
|
||||||
|
this.logger.info(`Resizing to ${columns}x${rows}`)
|
||||||
|
this.size = { columns, rows }
|
||||||
|
this.zone.run(() => {
|
||||||
|
if (this.session && this.session.open) {
|
||||||
|
this.session.resize(columns, rows)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
|
||||||
|
this.hostApp.windowMoved$.subscribe(() => setTimeout(() => {
|
||||||
|
this.configure()
|
||||||
|
}, 250)),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
sendInput (data: string) {
|
||||||
|
this.session.write(data)
|
||||||
|
if (this.config.store.terminal.scrollOnInput) {
|
||||||
|
this.frontend.scrollToBottom()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
write (data: string) {
|
||||||
|
let percentageMatch = /(^|[^\d])(\d+(\.\d+)?)%([^\d]|$)/.exec(data)
|
||||||
|
if (percentageMatch) {
|
||||||
|
let percentage = percentageMatch[3] ? parseFloat(percentageMatch[2]) : parseInt(percentageMatch[2])
|
||||||
|
if (percentage > 0 && percentage <= 100) {
|
||||||
|
this.setProgress(percentage)
|
||||||
|
this.logger.debug('Detected progress:', percentage)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.setProgress(null)
|
||||||
|
}
|
||||||
|
this.frontend.write(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
paste () {
|
||||||
|
let data = this.electron.clipboard.readText()
|
||||||
|
if (this.config.store.terminal.bracketedPaste) {
|
||||||
|
data = '\x1b[200~' + data + '\x1b[201~'
|
||||||
|
}
|
||||||
|
if (this.hostApp.platform === Platform.Windows) {
|
||||||
|
data = data.replace(/\r\n/g, '\r')
|
||||||
|
} else {
|
||||||
|
data = data.replace(/\n/g, '\r')
|
||||||
|
}
|
||||||
|
this.sendInput(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
configure (): void {
|
||||||
|
this.frontend.configure()
|
||||||
|
|
||||||
|
if (this.config.store.terminal.background === 'colorScheme') {
|
||||||
|
if (this.config.store.terminal.colorScheme.background) {
|
||||||
|
this.backgroundColor = this.config.store.terminal.colorScheme.background
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.backgroundColor = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
zoomIn () {
|
||||||
|
this.zoom++
|
||||||
|
this.frontend.setZoom(this.zoom)
|
||||||
|
}
|
||||||
|
|
||||||
|
zoomOut () {
|
||||||
|
this.zoom--
|
||||||
|
this.frontend.setZoom(this.zoom)
|
||||||
|
}
|
||||||
|
|
||||||
|
resetZoom () {
|
||||||
|
this.zoom = 0
|
||||||
|
this.frontend.setZoom(this.zoom)
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy () {
|
||||||
|
this.frontend.detach(this.content.nativeElement)
|
||||||
|
this.detachTermContainerHandlers()
|
||||||
|
this.config.enabledServices(this.decorators).forEach(decorator => {
|
||||||
|
decorator.detach(this)
|
||||||
|
})
|
||||||
|
this.hotkeysSubscription.unsubscribe()
|
||||||
|
if (this.sessionCloseSubscription) {
|
||||||
|
this.sessionCloseSubscription.unsubscribe()
|
||||||
|
}
|
||||||
|
this.output.complete()
|
||||||
|
}
|
||||||
|
|
||||||
|
async destroy () {
|
||||||
|
super.destroy()
|
||||||
|
if (this.session && this.session.open) {
|
||||||
|
await this.session.destroy()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected attachSessionHandlers () {
|
||||||
|
// 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.frontend.destroy()
|
||||||
|
this.app.closeTab(this)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,58 @@
|
|||||||
|
.modal-body
|
||||||
|
.form-group
|
||||||
|
label Name
|
||||||
|
input.form-control(
|
||||||
|
type='text',
|
||||||
|
autofocus,
|
||||||
|
[(ngModel)]='profile.name',
|
||||||
|
)
|
||||||
|
|
||||||
|
.form-group
|
||||||
|
label Command
|
||||||
|
input.form-control(
|
||||||
|
type='text',
|
||||||
|
[(ngModel)]='profile.sessionOptions.command',
|
||||||
|
)
|
||||||
|
|
||||||
|
.form-group
|
||||||
|
label Arguments
|
||||||
|
.input-group(
|
||||||
|
*ngFor='let arg of profile.sessionOptions.args; index as i; trackBy: trackByIndex',
|
||||||
|
)
|
||||||
|
input.form-control(
|
||||||
|
type='text',
|
||||||
|
[(ngModel)]='profile.sessionOptions.args[i]',
|
||||||
|
)
|
||||||
|
.input-group-btn
|
||||||
|
button.btn.btn-secondary((click)='profile.sessionOptions.args.splice(i, 1)')
|
||||||
|
i.fas.fa-trash
|
||||||
|
|
||||||
|
.mt-2
|
||||||
|
button.btn.btn-secondary((click)='profile.sessionOptions.args.push("")')
|
||||||
|
i.fas.fa-plus.mr-2
|
||||||
|
| Add
|
||||||
|
|
||||||
|
.form-line(*ngIf='uac.isAvailable')
|
||||||
|
.header
|
||||||
|
.title Run as administrator
|
||||||
|
toggle(
|
||||||
|
[(ngModel)]='profile.sessionOptions.runAsAdministrator',
|
||||||
|
)
|
||||||
|
|
||||||
|
.form-group
|
||||||
|
label Working directory
|
||||||
|
input.form-control(
|
||||||
|
type='text',
|
||||||
|
[(ngModel)]='profile.sessionOptions.cwd',
|
||||||
|
)
|
||||||
|
|
||||||
|
.form-group
|
||||||
|
label Environment
|
||||||
|
environment-editor(
|
||||||
|
type='text',
|
||||||
|
[(model)]='profile.sessionOptions.env',
|
||||||
|
)
|
||||||
|
|
||||||
|
.modal-footer
|
||||||
|
button.btn.btn-outline-primary((click)='save()') Save
|
||||||
|
button.btn.btn-outline-danger((click)='cancel()') Cancel
|