Compare commits

...

102 Commits

Author SHA1 Message Date
Eugene Pankov
524550f6e3 made context menu extensible 2018-12-24 19:41:27 +01:00
Eugene Pankov
fe31131fc1 typo fix 2018-12-24 18:40:29 +01:00
Eugene Pankov
a7c1fe3425 Experimental UAC start-as-admin wrapper (fixes #511) 2018-12-24 18:11:26 +01:00
Eugene Pankov
d7b305bf29 fixes in profile editor 2018-12-24 17:22:27 +01:00
Eugene Pankov
0bd0c850da fixed profile duplication 2018-12-24 11:32:04 +01:00
Eugene Pankov
88bb40f94b offer shell selection in the terminal context menu 2018-12-23 21:03:09 +01:00
Eugene Pankov
120e2a2cd5 fixed --login flag for older shells 2018-12-23 21:02:18 +01:00
Eugene Pankov
cbb6821814 don't set an empty jumplist 2018-12-23 20:56:39 +01:00
Eugene Pankov
75bf374a8f build fix 2018-12-23 20:56:30 +01:00
Eugene Pankov
bf995981d3 use yarn on appveyor & autoinstall plugin deps 2018-12-23 20:03:29 +01:00
Eugene Pankov
a6fdabcd2f removed debug logging 2018-12-22 11:15:50 +01:00
Eugene Pankov
0e6886d00a fixed args field focus 2018-12-22 11:15:40 +01:00
Eugene Pankov
459d6aadd9 fixed beam cursor on xterm (fixes #582) 2018-12-22 09:39:17 +01:00
Eugene Pankov
21d533c7cf attempt to detect CWD on classic windows shells 2018-12-22 01:36:05 +01:00
Eugene Pankov
211566488d removed default ctrl-a hotkeys (fixes #578) 2018-12-21 23:49:30 +01:00
Eugene Pankov
282aab2e55 fixed alt-v passing in hterm (fixes #560) 2018-12-21 23:48:44 +01:00
Eugene Pankov
6f41865474 lint 2018-12-21 23:18:22 +01:00
Eugene Pankov
e4bcfd8f39 bumped node-pty 2018-12-21 23:11:53 +01:00
Eugene Pankov
504cfcf8ff build fix (fixes 579) 2018-12-21 23:06:24 +01:00
Eugene Pankov
6e13914712 fixed nightly builds (fixes #579) 2018-12-21 21:52:12 +01:00
Eugene Pankov
9aaf670092 fontawesome 5 2018-12-21 21:37:34 +01:00
Eugene Pankov
c204f6d5a4 use providedIn 2018-12-21 21:21:33 +01:00
Eugene Pankov
91bba042b5 made conpty optional 2018-12-21 21:05:59 +01:00
Eugene Pankov
2ca6135c06 ui tweaks 2018-12-21 20:43:11 +01:00
Eugene Pankov
9ef3cbc177 profile args editor 2018-12-21 20:06:03 +01:00
Eugene Pankov
8a3906687a Merge branch 'master' into persistence 2018-12-21 20:04:49 +01:00
Eugene
3192a14c9d Merge pull request #568 from ehwarren/feature/rename-tab-qol
Feature/rename tab qol
2018-12-19 10:17:08 +01:00
Austin Warren
b510a86f4d Change rename hotkey to ⌘-R 2018-12-18 17:20:08 -08:00
Austin Warren
fcf14eaa8b Move focus to RenameTabModalComponent onInit 2018-12-18 17:19:41 -08:00
Eugene Pankov
137dd0bbe8 profile editor, env vars editor, creating profiles from shell list 2018-12-18 15:08:23 +01:00
Eugene Pankov
4b5b75a57a ui 2018-12-17 20:41:08 +01:00
Eugene Pankov
68c497e5fc windows jumplist integration 2018-12-16 23:20:35 +01:00
Eugene Pankov
1da7c85973 cli option to launch a specific profile 2018-12-16 23:13:14 +01:00
Eugene Pankov
fe75aab724 show profiles in macos dock item menu 2018-12-16 23:02:17 +01:00
Eugene Pankov
85bcac1fb7 profile settings 2018-12-16 17:41:30 +01:00
Eugene Pankov
72287cc7cb profile settings tab 2018-12-16 17:09:35 +01:00
Eugene Pankov
1f1d212c1d build fix 2018-12-16 15:57:08 +01:00
Eugene Pankov
cded1284de simpler tab recovery system 2018-12-16 15:42:04 +01:00
Eugene Pankov
df97e7ebb5 updated wsl truecolor warning 2018-12-15 23:51:03 +01:00
Eugene Pankov
d80c9a27d3 pulled in the freshest node-pty (fixes #23) 2018-12-15 23:44:20 +01:00
Eugene Pankov
3469ec9b6b fixed blur (fixes #556) 2018-12-15 15:59:16 +01:00
Eugene Pankov
d4db8f4b18 build script fixes 2018-12-15 15:49:06 +01:00
Eugene Pankov
384744ec44 only enable agent forwarding on windows when pageant is running (fixes #496) 2018-12-15 15:19:47 +01:00
Eugene Pankov
76633db25e migrate back to stock rage-edit 2018-12-15 14:27:12 +01:00
Austin Warren
6b823d0fa0 Fixed build errors 2018-12-14 14:50:38 -08:00
Austin Warren
798dda5236 Added rename-tab hotkey 2018-12-14 14:50:16 -08:00
Austin Warren
2b90a17d5e Added hotkey rename, right click rename, and auto select text in rename modal 2018-12-14 14:49:20 -08:00
Eugene Pankov
6387539980 bumped angular 2018-12-13 18:01:44 +01:00
Eugene Pankov
cb17fd0866 fixed #564 2018-12-13 18:00:59 +01:00
Eugene Pankov
17bac5a904 build fix 2018-12-10 17:18:23 +01:00
Eugene Pankov
c34123ffe3 build fix 2018-12-10 16:31:45 +01:00
Eugene Pankov
c755885bbb build fix 2018-12-10 15:06:15 +01:00
Eugene Pankov
f49e3f0664 Revert "bumped webpack"
This reverts commit c58c629d0e.
2018-12-10 14:54:46 +01:00
Eugene Pankov
7852ac2071 potential fix for xterm double-paste (#468) 2018-12-10 14:23:08 +01:00
Eugene Pankov
60358e7ac4 xterm copy-on-select (fixes #400) 2018-12-10 13:08:57 +01:00
Eugene Pankov
f32bdbdeac make scroll-on-input behaviour configurable (fixes #543) 2018-12-10 11:57:13 +01:00
Eugene Pankov
c58c629d0e bumped webpack 2018-12-10 11:56:07 +01:00
Eugene Pankov
a091f46100 fixed settings sidebar offset (fixes #549) 2018-12-10 11:27:32 +01:00
Eugene Pankov
76e8652492 hotkey fixes 2018-12-07 15:12:37 +01:00
Eugene Pankov
2606b910f1 nicer scrollbars (fixes #440) 2018-12-07 14:54:56 +01:00
Eugene Pankov
9440d687d3 don't crash if no global spawn hotkey is assigned (#540) 2018-12-07 14:50:26 +01:00
Eugene Pankov
216f5c2213 . 2018-12-07 14:50:16 +01:00
Eugene
dbadef1c4e Merge pull request #541 from scott-kirk/patch-1
Typo in README
2018-12-06 23:55:30 +01:00
Scott Kirkpatrick
1baf80cfe6 Typo in README 2018-12-06 15:16:01 -05:00
Eugene Pankov
2e50bfccf4 Merge branch 'master' of github.com:Eugeny/terminus 2018-12-05 13:57:02 +01:00
Eugene Pankov
07a3d88397 updated icon 2018-12-05 01:34:11 +01:00
Eugene
6e5ce8e0b1 Merge pull request #536 from Drachenkaetzchen/wsl-color-warning
Inform users about 16 color limit with WSL
2018-12-05 01:33:36 +01:00
Eugene Pankov
c1d1ddd3b7 Merge branch 'master' of github.com:Eugeny/terminus 2018-12-05 01:31:03 +01:00
Eugene
bd5f274cf3 Merge pull request #537 from Drachenkaetzchen/bugfix-wheelevent-ts32
Fix for MouseWheelEvent deprecation in TypeScript 3.2
2018-12-05 01:30:19 +01:00
Eugene Pankov
38045165d4 avoid double squirrel check 2018-12-05 01:28:51 +01:00
Eugene
9e7721d2a9 Merge pull request #535 from Drachenkaetzchen/bugfix-534
Bugfix for issue #534: Catch any errors occurring during checkForUpda…
2018-12-05 01:27:29 +01:00
Felicia Hummel
1d593e0495 Inform users about 16 color limit with WSL
This patch adds a warning that when using WSL we're limited to 16 colors. It took me about 4 hours debugging why it didn't work, and this patch hopefully prevents others from wasting 4 hours.
2018-12-05 00:38:19 +01:00
Felicia Hummel
9b263c7237 Fix for MouseWheelEvent deprecation in TypeScript 3.2
MouseWheelEvent is deprecated and was removed with TypeScript 3.2, however, MouseWheelEvent is still aliased to WheelEvent. For more info see https://github.com/Microsoft/TSJS-lib-generator/pull/579

This PR fixes the build with TypeScript 3.2 by checking the object properties.
2018-12-05 00:33:10 +01:00
Felicia Hummel
ca05c1b819 Merge 2018-12-05 00:27:42 +01:00
Eugene Pankov
2107ed7ab7 force focus on new terminal tabs (fixes #533) 2018-12-05 00:06:01 +01:00
Felicia Hummel
9fd69f668a Bugfix for issue #534: Catch any errors occurring during checkForUpdates() to allow App to start up even if Squirrel is not available 2018-12-04 23:58:18 +01:00
Eugene Pankov
8800614bff tab colors 2018-12-02 16:41:17 +01:00
Eugene Pankov
867ddb4809 bumped electron 2018-12-02 15:28:25 +01:00
Eugene Pankov
5cf31d3351 fall back to github updates if squirrel is unavailable 2018-12-02 15:28:18 +01:00
Eugene Pankov
dff55558dd bump 2018-11-28 22:56:51 +01:00
Eugene Pankov
991294e61a auto-updater (fixes #348) 2018-11-28 21:26:15 +01:00
Eugene Pankov
462232a2fb xterm scrollbar styles 2018-11-28 16:45:58 +01:00
Eugene Pankov
8857ca9714 fixed #484 2018-11-28 15:44:29 +01:00
Eugene Pankov
eb81b9fd01 check tabs before closing the window (fixes #520) 2018-11-22 17:51:12 +01:00
Eugene Pankov
12d1fb9334 Merge branch 'master' of github.com:Eugeny/terminus 2018-11-15 12:39:59 +01:00
Eugene Pankov
ab6d5e851b don't crash if no cwd 2018-11-15 12:39:51 +01:00
Eugene Pankov
952e8461e6 an option to clear last ssh quick connection (fixes #435, fixes #493) 2018-11-11 13:45:47 +01:00
Eugene Pankov
702d29b5b4 added --hidden startup option (fixes #488) 2018-11-11 13:24:27 +01:00
Eugene Pankov
64fc36df51 cleanup 2018-11-11 12:38:51 +01:00
Eugene Pankov
8cfaf9082b bumped electron 2018-11-10 21:26:43 +01:00
Eugene Pankov
8269a8e01b perf: faster hotkey matching 2018-11-10 21:11:22 +01:00
Eugene Pankov
7fcf632378 perf: don't re-set tab progress every time 2018-11-10 21:11:15 +01:00
Eugene Pankov
2e6acd2fa1 perf: reduced pty bufferization window 2018-11-10 21:10:47 +01:00
Eugene Pankov
50ab4fc37e added a settings hotkey (fixes #487) 2018-10-31 17:45:42 +01:00
Eugene Pankov
4426d4827f bring window to front when called from CLI (fixes #489) 2018-10-31 17:37:34 +01:00
Eugene Pankov
215ddf0eec yarn integrity 2018-10-31 17:33:17 +01:00
Eugene Pankov
146de1a2c0 fixed #490 2018-10-31 17:33:03 +01:00
Eugene Pankov
a39eb31379 word 2018-10-31 17:30:08 +01:00
Eugene Pankov
5831e87f68 build fix 2018-10-29 17:25:02 +01:00
Eugene Pankov
f250756254 force autofocus in rename-tab modal (fixes #484) 2018-10-29 16:45:51 +01:00
Eugene Pankov
6a821fa6cf Merge branch 'master' of github.com:Eugeny/terminus 2018-10-29 16:45:50 +01:00
Eugene Pankov
9f204eddb4 fixed #485 2018-10-29 16:39:12 +01:00
135 changed files with 2356 additions and 1596 deletions

1
.gitignore vendored
View File

@@ -5,6 +5,7 @@ node_modules
build/files.wxs
dist
*/dist
*.xcworkspacedata
*.xcuserstate

View File

@@ -14,8 +14,7 @@ cache:
- app/node_modules
before_install:
- yarn install
- scripts/install-deps.js
- yarn
script:
- scripts/build-native.js

View File

@@ -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
* [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
* [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
---

View File

@@ -1,91 +1,55 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="150mm"
height="150mm"
viewBox="0 0 150 150"
version="1.1"
id="svg8"
inkscape:version="0.92.2 (5c3e80d, 2017-08-06)"
sodipodi:docname="logo.svg"
inkscape:export-filename="/home/eugene/Work/term/build/icons/512x512.png"
inkscape:export-xdpi="86.699997"
inkscape:export-ydpi="86.699997">
<defs
id="defs2" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="0.49497475"
inkscape:cx="85.897128"
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)">
<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>
<?xml version="1.0" encoding="utf-8"?>
<!-- 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"
viewBox="0 0 1024 1024" style="enable-background:new 0 0 1024 1024;" xml:space="preserve">
<style type="text/css">
.st0{fill:url(#SVGID_1_);}
.st1{opacity:0.16;fill:url(#SVGID_2_);}
.st2{fill:url(#SVGID_3_);}
.st3{opacity:0.16;fill:url(#SVGID_4_);}
.st4{fill:url(#SVGID_5_);}
.st5{opacity:0.15;fill:url(#SVGID_6_);}
.st6{fill:url(#SVGID_7_);}
</style>
<g>
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="260.9675" y1="871.1813" x2="919.1845" y2="491.1596">
<stop offset="0" style="stop-color:#669ABD"/>
<stop offset="1" style="stop-color:#77DBDB"/>
</linearGradient>
<polygon class="st0" points="297.54,934.52 882.6,596.72 882.61,427.82 297.54,765.65 "/>
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="553.5051" y1="617.8278" x2="626.647" y2="744.5132">
<stop offset="0.5588" style="stop-color:#000000;stop-opacity:0"/>
<stop offset="1" style="stop-color:#000000"/>
</linearGradient>
<polygon class="st1" points="297.54,934.52 882.6,596.72 882.61,427.82 297.54,765.65 "/>
</g>
<g>
<linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="114.6631" y1="744.5275" x2="334.0905" y2="871.2141">
<stop offset="0" style="stop-color:#6A8FAD"/>
<stop offset="1" style="stop-color:#669ABD"/>
</linearGradient>
<polygon class="st2" points="151.23,681.18 151.22,850.09 297.54,934.52 297.54,765.65 "/>
<linearGradient id="SVGID_4_" gradientUnits="userSpaceOnUse" x1="260.9478" y1="744.5281" x2="187.8059" y2="871.2135">
<stop offset="0.5588" style="stop-color:#000000;stop-opacity:0"/>
<stop offset="1" style="stop-color:#000000"/>
</linearGradient>
<polygon class="st3" points="151.23,681.18 151.22,850.09 297.54,934.52 297.54,765.65 "/>
</g>
<g>
<linearGradient id="SVGID_5_" gradientUnits="userSpaceOnUse" x1="114.663" y1="237.793" x2="553.5026" y2="491.1571">
<stop offset="0" style="stop-color:#6A8FAD"/>
<stop offset="1" style="stop-color:#669ABD"/>
</linearGradient>
<polygon class="st4" points="151.23,174.45 151.21,343.36 443.79,512.27 590.08,427.81 "/>
<linearGradient id="SVGID_6_" gradientUnits="userSpaceOnUse" x1="370.6562" y1="301.1281" x2="297.5094" y2="427.8221">
<stop offset="0.5588" style="stop-color:#000000;stop-opacity:0"/>
<stop offset="1" style="stop-color:#000000"/>
</linearGradient>
<polygon class="st5" points="151.23,174.45 151.21,343.36 443.79,512.27 590.08,427.81 "/>
</g>
<linearGradient id="SVGID_7_" gradientUnits="userSpaceOnUse" x1="78.0912" y1="554.4979" x2="736.3375" y2="174.4593">
<stop offset="0" style="stop-color:#CCECFF"/>
<stop offset="1" style="stop-color:#9FECED"/>
</linearGradient>
<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 "/>
</svg>

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

@@ -1,6 +1,6 @@
import { app, ipcMain, Menu, Tray, shell } from 'electron'
import { loadConfig } from './config'
import { Window } from './window'
import { Window, WindowOptions } from './window'
export class Application {
private tray: Tray
@@ -20,8 +20,8 @@ export class Application {
app.commandLine.appendSwitch('disable-http-cache')
}
async newWindow (): Promise<Window> {
let window = new Window()
async newWindow (options?: WindowOptions): Promise<Window> {
let window = new Window(options)
this.windows.push(window)
window.visible$.subscribe(visible => {
if (visible) {

View File

@@ -13,6 +13,9 @@ export function parseArgs (argv, cwd) {
.command('run [command...]', 'run a command in the terminal', {
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 => {
return yargs.option('escape', {
alias: 'e',
@@ -28,6 +31,10 @@ export function parseArgs (argv, cwd) {
describe: 'Show DevTools on start',
type: 'boolean'
})
.option('hidden', {
describe: 'Start minimized',
type: 'boolean'
})
.option('version', {
alias: 'v',
describe: 'Show version and exit',

View File

@@ -59,5 +59,5 @@ app.on('ready', () => {
}
]))
}
application.newWindow()
application.newWindow({ hidden: argv.hidden })
})

View File

@@ -14,23 +14,30 @@ if (process.platform === 'win32') {
DwmEnableBlurBehindWindow = require('windows-blurbehind').DwmEnableBlurBehindWindow
}
export interface WindowOptions {
hidden?: boolean
}
export class Window {
ready: Promise<void>
private visible = new Subject<boolean>()
private window: BrowserWindow
private windowConfig: ElectronConfig
private windowBounds: Rectangle
private closing = false
get visible$ (): Observable<boolean> { return this.visible }
constructor () {
constructor (options?: WindowOptions) {
let configData = loadConfig()
options = options || {}
this.windowConfig = new ElectronConfig({ name: 'window' })
this.windowBounds = this.windowConfig.get('windowBoundaries')
let maximized = this.windowConfig.get('maximized')
let options: Electron.BrowserWindowConstructorOptions = {
let bwOptions: Electron.BrowserWindowConstructorOptions = {
width: 800,
height: 600,
title: 'Terminus',
@@ -41,33 +48,36 @@ export class Window {
show: false,
backgroundColor: '#00000000'
}
Object.assign(options, this.windowBounds)
Object.assign(bwOptions, this.windowBounds)
if ((configData.appearance || {}).frame === 'native') {
options.frame = true
bwOptions.frame = true
} else {
if (process.platform === 'darwin') {
options.titleBarStyle = 'hiddenInset'
bwOptions.titleBarStyle = 'hiddenInset'
}
}
if (process.platform === 'linux') {
options.backgroundColor = '#131d27'
bwOptions.backgroundColor = '#131d27'
}
this.window = new BrowserWindow(options)
this.window = new BrowserWindow(bwOptions)
this.window.once('ready-to-show', () => {
if (process.platform === 'darwin') {
this.window.setVibrancy('dark')
} else if (process.platform === 'win32' && (configData.appearance || {}).vibrancy) {
this.setVibrancy(true)
}
if (maximized) {
this.window.maximize()
} else {
this.window.show()
if (!options.hidden) {
if (maximized) {
this.window.maximize()
} else {
this.window.show()
}
this.window.focus()
}
this.window.focus()
})
this.window.loadURL(`file://${app.getAppPath()}/dist/index.html?${this.window.id}`, { extraHeaders: 'pragma: no-cache\n' })
@@ -136,7 +146,12 @@ export class Window {
this.window.on('enter-full-screen', () => this.window.webContents.send('host:window-enter-full-screen'))
this.window.on('leave-full-screen', () => this.window.webContents.send('host:window-leave-full-screen'))
this.window.on('close', () => {
this.window.on('close', event => {
if (!this.closing) {
event.preventDefault()
this.window.webContents.send('host:window-close-request')
return
}
this.windowConfig.set('windowBoundaries', this.windowBounds)
this.windowConfig.set('maximized', this.window.isMaximized())
})
@@ -224,6 +239,22 @@ export class Window {
this.window.setTitle(title)
})
ipcMain.on('window-bring-to-front', event => {
if (event.sender !== this.window.webContents) {
return
}
if (this.window.isMinimized()) {
this.window.restore()
}
this.window.show()
this.window.moveTop()
})
ipcMain.on('window-close', () => {
this.closing = true
this.window.close()
})
this.window.webContents.on('new-window', event => event.preventDefault())
}

View File

@@ -1,6 +1,7 @@
{
"name": "terminus",
"description": "A terminal for a modern age",
"repository": "https://github.com/eugeny/terminus",
"author": {
"name": "Eugene Pankov",
"email": "e@ajenti.org"
@@ -12,13 +13,13 @@
"watch": "webpack --progress --color --watch"
},
"dependencies": {
"@angular/animations": "7.0.0",
"@angular/common": "7.0.0",
"@angular/compiler": "7.0.0",
"@angular/core": "7.0.0",
"@angular/forms": "7.0.0",
"@angular/platform-browser": "7.0.0",
"@angular/platform-browser-dynamic": "7.0.0",
"@angular/animations": "7.2.0-beta.1",
"@angular/common": "7.2.0-beta.1",
"@angular/compiler": "7.2.0-beta.1",
"@angular/core": "7.2.0-beta.1",
"@angular/forms": "7.2.0-beta.1",
"@angular/platform-browser": "7.2.0-beta.1",
"@angular/platform-browser-dynamic": "7.2.0-beta.1",
"@ng-bootstrap/ng-bootstrap": "^3.3.1",
"devtron": "1.4.0",
"electron-config": "0.2.1",

View File

@@ -1,7 +1,9 @@
import '../lib/lru'
import 'source-sans-pro'
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 './preload.scss'

View File

@@ -34,7 +34,7 @@ async function bootstrap (plugins: IPluginInfo[], safeMode = false): Promise<NgM
})
let module = getRootModule(pluginsModules)
window['rootModule'] = module
return await platformBrowserDynamic().bootstrapModule(module)
return platformBrowserDynamic().bootstrapModule(module)
}
findPlugins().then(async plugins => {

View File

@@ -118,7 +118,7 @@ export async function findPlugins (): Promise<IPluginInfo[]> {
}
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'))) {
continue
}

View File

@@ -2,52 +2,52 @@
# yarn lockfile v1
"@angular/animations@7.0.0":
version "7.0.0"
resolved "https://registry.yarnpkg.com/@angular/animations/-/animations-7.0.0.tgz#5c9e1683063c29df10253b7dc5bb9b13694ee396"
integrity sha512-IYdryQXdYfPvhJpExLSAr0o9rlUeyVS++a6h/sjqN1dkUt/yJBHLRreuHx8Udvlj2nH70raHJgevk8FwhAkTdA==
"@angular/animations@7.2.0-beta.1":
version "7.2.0-beta.1"
resolved "https://registry.yarnpkg.com/@angular/animations/-/animations-7.2.0-beta.1.tgz#c288de4a89b0197ba53a8411173ec4085fbbd9c9"
integrity sha512-82u9L2poaREjkTJlYEKdOO1B1LaVBwqJBZvIXU04+21WQBJoi050sxUl6lmjVVs5rRc0e/y2gifyrb42pUEntA==
dependencies:
tslib "^1.9.0"
"@angular/common@7.0.0":
version "7.0.0"
resolved "https://registry.yarnpkg.com/@angular/common/-/common-7.0.0.tgz#29206614d2b8dc79e5207b7dc6f9fc559e9a24f2"
integrity sha512-jp6MA6EOq/a1m+F0c1aZC345pAYYYFpN1m7GMM91JlqkjzJMhyYVk+Bod9xQOEWadcpY+RFudG+jRsPCMO8bvQ==
"@angular/common@7.2.0-beta.1":
version "7.2.0-beta.1"
resolved "https://registry.yarnpkg.com/@angular/common/-/common-7.2.0-beta.1.tgz#be9d14f239b7a390fc056e3bb540da181631fb1a"
integrity sha512-+aYfO20nrqurOLxM0w/UJkOHjGzNFOc2j52ggyj1vr62nTk+W63j4P8tcUsW6iHFCsOF5auSkclKUbNIliMf0A==
dependencies:
tslib "^1.9.0"
"@angular/compiler@7.0.0":
version "7.0.0"
resolved "https://registry.yarnpkg.com/@angular/compiler/-/compiler-7.0.0.tgz#f953a213a01e4736e94fe1a370b07e13e2393b71"
integrity sha512-4fkohfGyG1BEpeYenOartuJmduyZ/R3XQx46hDDiR/9A8/Go4qLGkgr9Bd/JL/gPIR1XAHH9D5ii2sh+28ZEmA==
"@angular/compiler@7.2.0-beta.1":
version "7.2.0-beta.1"
resolved "https://registry.yarnpkg.com/@angular/compiler/-/compiler-7.2.0-beta.1.tgz#355a10a80615afdd1d6a5ed682222e9e57e65fd7"
integrity sha512-KxI93dLm1SPNbazfG41QcmxVS7T9VmQ8wzhMHOVJo4DP77g2E5xUB5nOInMCMI43lbZEIckBxo/ci4jwiiq8uA==
dependencies:
tslib "^1.9.0"
"@angular/core@7.0.0":
version "7.0.0"
resolved "https://registry.yarnpkg.com/@angular/core/-/core-7.0.0.tgz#01e9db9074a1db1c47a32f745b787d1c86f5d61a"
integrity sha512-DjVyWNGBWKEeBvxeXy8FGBNlnr/W/tNygOZEd6/uCktcXTG4DNyNQrWuNZUKEpr7RuIT3YVMj+UNwgTq0jB/9g==
"@angular/core@7.2.0-beta.1":
version "7.2.0-beta.1"
resolved "https://registry.yarnpkg.com/@angular/core/-/core-7.2.0-beta.1.tgz#32bef8f3d424333791d0e0bd4a3f3afee9b1349d"
integrity sha512-gJzQAauezAMU8vEEMh1PrXv52fA9ceUXac/tJ8KIi08zEjyIRXLvKggWr7YXAbt5LwgKsn27JwecqeS5h4K/BQ==
dependencies:
tslib "^1.9.0"
"@angular/forms@7.0.0":
version "7.0.0"
resolved "https://registry.yarnpkg.com/@angular/forms/-/forms-7.0.0.tgz#672c306b13e94a20b72c096214642a326c43699a"
integrity sha512-rTg1UHq9gHR6zY3Kkip1KCm/YTck/rlR8CvVFIMwF0bdQxUCT51SXVn58nXts9yDaieABcGaQHNkQn1mARslgw==
"@angular/forms@7.2.0-beta.1":
version "7.2.0-beta.1"
resolved "https://registry.yarnpkg.com/@angular/forms/-/forms-7.2.0-beta.1.tgz#399ea4585502027d53396eac935f8f20bd5ad2bf"
integrity sha512-1NQ2hw8Y4hjgr5qXoOvQJRjmQno/fhQUuIRx0SC7hYySur1E9vcI8rZDVDB+LwiGexALh8H70zwJ64lNxzwpvA==
dependencies:
tslib "^1.9.0"
"@angular/platform-browser-dynamic@7.0.0":
version "7.0.0"
resolved "https://registry.yarnpkg.com/@angular/platform-browser-dynamic/-/platform-browser-dynamic-7.0.0.tgz#2b2a50b5a8176bee257f90ee47b1d873502f7182"
integrity sha512-lH2KuH+Em1y/mTOE6yTJmsOxYkMbYKzKLP9gYzc9vZu3er1df6Jx6jxefeBmAr9v+kNCLnpnHWHz2y4GzAesJA==
"@angular/platform-browser-dynamic@7.2.0-beta.1":
version "7.2.0-beta.1"
resolved "https://registry.yarnpkg.com/@angular/platform-browser-dynamic/-/platform-browser-dynamic-7.2.0-beta.1.tgz#4a95ee6d53fb02e529f1f1842ee8839a25ec4790"
integrity sha512-wnf6WSfh9AsBzI8I//eNolmQ2rwMIk/KIuGmTEOAuAxRMLgqzZUA3PjX2XAE6oUUowqy1MET1UFiqjDf/NZcNQ==
dependencies:
tslib "^1.9.0"
"@angular/platform-browser@7.0.0":
version "7.0.0"
resolved "https://registry.yarnpkg.com/@angular/platform-browser/-/platform-browser-7.0.0.tgz#8c13a6380cf465b3628e5b576a1313e9b4976093"
integrity sha512-XyvL30d6meJ+SXlOmdR+sxoLdSvkQdmVNvpdvUzAHC/EqwA/byg4V3bTe5lpZmypclgFCjkGoTsz6uOnnwlQhw==
"@angular/platform-browser@7.2.0-beta.1":
version "7.2.0-beta.1"
resolved "https://registry.yarnpkg.com/@angular/platform-browser/-/platform-browser-7.2.0-beta.1.tgz#20c272ebfcccb3151baed7ceddbafdb359f54cd8"
integrity sha512-sCMzCFCdE4Dq9foXf2PpWPegtVfirHhg+DQzoiwFDYj5i+QB9nWY7BOLlrCPAWpd8opUxCsaLrzXbfgM40FAGA==
dependencies:
tslib "^1.9.0"

View File

@@ -7,18 +7,18 @@ environment:
nodejs_version: "10"
cache:
- '%USERPROFILE%\.electron'
- "%USERPROFILE%\\.electron"
- "%LOCALAPPDATA%\\Yarn"
version: "{build}"
install:
- ps: Install-Product node $env:nodejs_version $env:platform
- npm install
- node scripts/install-deps.js
- yarn
- node scripts/build-native.js
build_script:
- npm run build
- yarn run build
- node scripts/prepackage-plugins.js
- node scripts/build-windows.js

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 644 B

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.3 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

View File

@@ -1,124 +1,57 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="150mm"
height="150mm"
viewBox="0 0 150 150"
version="1.1"
id="svg8"
inkscape:version="0.92.2 (5c3e80d, 2017-08-06)"
sodipodi:docname="icon.svg"
inkscape:export-filename="/home/eugene/Work/term/build/icons/512x512.png"
inkscape:export-xdpi="86.699997"
inkscape:export-ydpi="86.699997">
<defs
id="defs2">
<linearGradient
inkscape:collect="always"
id="linearGradient4649">
<stop
style="stop-color:#000316;stop-opacity:1"
offset="0"
id="stop4645" />
<stop
style="stop-color:#190065;stop-opacity:1"
offset="1"
id="stop4647" />
</linearGradient>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient4649"
id="linearGradient4651"
x1="89.26284"
y1="85.146751"
x2="89.26284"
y2="229.47229"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.82182032,0,0,0.82182032,15.208802,28.029361)" />
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="0.49497475"
inkscape:cx="85.897128"
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>
<?xml version="1.0" encoding="utf-8"?>
<!-- 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"
viewBox="0 0 1024 1024" style="enable-background:new 0 0 1024 1024;" xml:space="preserve">
<style type="text/css">
.st0{opacity:0.8;fill:#00232E;}
.st1{fill:url(#SVGID_1_);}
.st2{opacity:0.16;fill:url(#SVGID_2_);}
.st3{fill:url(#SVGID_3_);}
.st4{opacity:0.16;fill:url(#SVGID_4_);}
.st5{fill:url(#SVGID_5_);}
.st6{opacity:0.15;fill:url(#SVGID_6_);}
.st7{fill:url(#SVGID_7_);}
</style>
<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 "/>
<g>
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="439.0065" y1="603.0394" x2="627.9464" y2="493.9549">
<stop offset="0" style="stop-color:#669ABD"/>
<stop offset="1" style="stop-color:#77DBDB"/>
</linearGradient>
<polygon class="st1" points="449.5,621.22 617.45,524.25 617.45,475.77 449.5,572.75 "/>
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="522.9788" y1="530.3148" x2="543.9741" y2="566.6795">
<stop offset="0.5588" style="stop-color:#000000;stop-opacity:0"/>
<stop offset="1" style="stop-color:#000000"/>
</linearGradient>
<polygon class="st2" points="449.5,621.22 617.45,524.25 617.45,475.77 449.5,572.75 "/>
</g>
<g>
<linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="397.0101" y1="566.6837" x2="459.9963" y2="603.0487">
<stop offset="0" style="stop-color:#6A8FAD"/>
<stop offset="1" style="stop-color:#669ABD"/>
</linearGradient>
<polygon class="st3" points="407.51,548.5 407.5,596.99 449.5,621.22 449.5,572.75 "/>
<linearGradient id="SVGID_4_" gradientUnits="userSpaceOnUse" x1="439.0009" y1="566.6838" x2="418.0056" y2="603.0486">
<stop offset="0.5588" style="stop-color:#000000;stop-opacity:0"/>
<stop offset="1" style="stop-color:#000000"/>
</linearGradient>
<polygon class="st4" points="407.51,548.5 407.5,596.99 449.5,621.22 449.5,572.75 "/>
</g>
<g>
<linearGradient id="SVGID_5_" gradientUnits="userSpaceOnUse" x1="397.0101" y1="421.2265" x2="522.9781" y2="493.9542">
<stop offset="0" style="stop-color:#6A8FAD"/>
<stop offset="1" style="stop-color:#669ABD"/>
</linearGradient>
<polygon class="st5" points="407.51,403.04 407.5,451.53 491.49,500.01 533.48,475.77 "/>
<linearGradient id="SVGID_6_" gradientUnits="userSpaceOnUse" x1="470.4924" y1="439.4067" x2="449.4958" y2="475.774">
<stop offset="0.5588" style="stop-color:#000000;stop-opacity:0"/>
<stop offset="1" style="stop-color:#000000"/>
</linearGradient>
<polygon class="st6" points="407.51,403.04 407.5,451.53 491.49,500.01 533.48,475.77 "/>
</g>
<linearGradient id="SVGID_7_" gradientUnits="userSpaceOnUse" x1="386.5123" y1="512.136" x2="575.4605" y2="403.0467">
<stop offset="0" style="stop-color:#CCECFF"/>
<stop offset="1" style="stop-color:#9FECED"/>
</linearGradient>
<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 "/>
</svg>

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 112 KiB

After

Width:  |  Height:  |  Size: 361 KiB

View File

@@ -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
extras/UAC.exe Normal file

Binary file not shown.

View File

@@ -1,6 +1,7 @@
{
"name": "term",
"devDependencies": {
"@fortawesome/fontawesome-free": "^5.6.3",
"@types/electron-config": "^0.2.1",
"@types/electron-debug": "^1.1.0",
"@types/fs-promise": "1.0.1",
@@ -13,18 +14,15 @@
"core-js": "2.4.1",
"cross-env": "4.0.0",
"css-loader": "0.28.0",
"electron": "3.0.5",
"electron-builder": "^20.27.1",
"electron": "4.0.0-beta.7",
"electron-builder": "^20.38.2",
"electron-builder-squirrel-windows": "^20.28.3",
"electron-installer-snap": "^3.0.0",
"electron-rebuild": "^1.8.2",
"file-loader": "^1.1.11",
"font-awesome": "4.7.0",
"graceful-fs": "^4.1.11",
"html-loader": "0.4.4",
"json-loader": "0.5.4",
"less": "2.7.1",
"less-loader": "2.2.3",
"node-abi": "^2.4.4",
"node-gyp": "^3.6.2",
"node-sass": "^4.5.3",
@@ -44,13 +42,13 @@
"style-loader": "0.13.1",
"svg-inline-loader": "^0.8.0",
"to-string-loader": "1.1.5",
"tslint": "5.1.0",
"tslint-config-standard": "5.0.2",
"tslint-eslint-rules": "4.0.0",
"tslint": "^5.12.0",
"tslint-config-standard": "^8.0.1",
"tslint-eslint-rules": "^5.4.0",
"typescript": "^3.1.3",
"url-loader": "^1.1.1",
"val-loader": "0.5.0",
"webpack": "^4.22.0",
"webpack": "^4.27.1",
"webpack-cli": "^3.1.2",
"yaml-loader": "0.4.0",
"yarn": "^1.10.1"
@@ -128,7 +126,7 @@
"start": "cross-env DEV=1 electron app --debug",
"prod": "cross-env DEV=1 electron app",
"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"
}

View File

@@ -4,7 +4,7 @@ const vars = require('./vars')
builder({
dir: true,
mac: ['dmg'],
mac: ['dmg', 'zip'],
config: {
extraMetadata: {
version: vars.version,

View File

@@ -4,24 +4,18 @@ const path = require('path')
const vars = require('./vars')
lifecycles = []
lifecycles.push(rebuild({
buildPath: path.resolve(__dirname, '../app'),
electronVersion: vars.electronVersion,
force: true,
}).lifecycle)
lifecycles.push(rebuild({
buildPath: path.resolve(__dirname, '../terminus-ssh'),
electronVersion: vars.electronVersion,
force: true,
}).lifecycle)
lifecycles.push(rebuild({
buildPath: path.resolve(__dirname, '../terminus-terminal'),
electronVersion: vars.electronVersion,
force: true,
}).lifecycle)
for (let dir of ['app', 'terminus-ssh', 'terminus-terminal']) {
lifecycles.push([rebuild({
buildPath: path.resolve(__dirname, '../' + dir),
electronVersion: vars.electronVersion,
force: true,
}).lifecycle, dir])
}
for (let lc of lifecycles) {
console.info('Building against Electron', vars.electronVersion)
for (let [lc, dir] of lifecycles) {
lc.on('module-found', name => {
console.info('Rebuilding', name)
console.info('Rebuilding', dir + '/' + name)
})
}

View File

@@ -8,7 +8,6 @@ const localBinPath = path.resolve(__dirname, '../node_modules/.bin');
const npx = `${localBinPath}/npx`;
log.info('deps', 'app')
sh.exec(`${npx} yarn install`)
sh.cd('app')
sh.exec(`${npx} yarn install`)

View File

@@ -3,10 +3,11 @@ const fs = require('fs')
const childProcess = require('child_process')
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 = exports.version.substring(1, exports.version.length - 1)
exports.version = exports.version.substring(1).trim()
exports.version = exports.version.replace('-', '-c')
exports.builtinPlugins = [
'terminus-core',
@@ -20,5 +21,4 @@ exports.bundledModules = [
'@angular',
'@ng-bootstrap',
]
exports.nativeModules = ['node-pty-tmp', 'font-manager', 'xkeychain', 'electron-vibrancy']
exports.electronVersion = pkgInfo.devDependencies.electron
exports.electronVersion = electronInfo.version

View File

@@ -8,8 +8,8 @@
"main": "dist/index.js",
"typings": "dist/index.d.ts",
"scripts": {
"build": "rm -rf dist && webpack --progress --color --display-modules",
"watch": "rm -rf dist && webpack --progress --color --watch"
"build": "webpack --progress --color --display-modules",
"watch": "webpack --progress --color --watch"
},
"files": [
"dist"
@@ -21,13 +21,13 @@
"@types/node": "^7.0.37",
"@types/webpack-env": "^1.13.0",
"@types/winston": "^2.3.6",
"axios": "0.16.2",
"axios": "^0.18.0",
"bootstrap": "^4.1.3",
"core-js": "^2.4.1",
"electron-updater": "^2.8.9",
"ng2-dnd": "^5.0.2",
"ngx-perfect-scrollbar": "^6.0.0",
"rage-edit-tmp": "^1.1.0",
"rage-edit": "^1.2.0",
"shell-escape": "^0.2.0",
"universal-analytics": "^0.4.17"
},
@@ -45,6 +45,5 @@
"deepmerge": "^1.5.0",
"js-yaml": "^3.9.0",
"winston": "^2.4.0"
},
"false": {}
}
}

View File

@@ -6,5 +6,5 @@ export interface RecoveredTab {
}
export abstract class TabRecoveryProvider {
abstract async recover (recoveryToken: any): Promise<RecoveredTab|null>
abstract async recover (recoveryToken: any): Promise<RecoveredTab | null>
}

View File

@@ -50,7 +50,7 @@ title-bar(
)
button.btn.btn-secondary.btn-tab-bar.btn-update(
*ngIf='appUpdate',
*ngIf='updatesAvailable',
title='Update available',
(click)='updateApp()',
[innerHTML]='updateIcon'

View File

@@ -11,7 +11,7 @@ import { ConfigService } from '../services/config.service'
import { DockingService } from '../services/docking.service'
import { TabRecoveryService } from '../services/tabRecovery.service'
import { ThemesService } from '../services/themes.service'
import { UpdaterService, Update } from '../services/updater.service'
import { UpdaterService } from '../services/updater.service'
import { TouchbarService } from '../services/touchbar.service'
import { BaseTabComponent } from './baseTab.component'
@@ -63,8 +63,8 @@ export class AppRootComponent {
tabsDragging = false
unsortedTabs: BaseTabComponent[] = []
updateIcon: SafeHtml
updatesAvailable = false
private logger: Logger
private appUpdate: Update
constructor (
private docking: DockingService,
@@ -122,7 +122,7 @@ export class AppRootComponent {
})
this.hostApp.secondInstance$.subscribe(() => {
this.onGlobalHotkey()
this.presentWindow()
})
this.hotkeys.globalHotkey.subscribe(() => {
this.onGlobalHotkey()
@@ -132,8 +132,8 @@ export class AppRootComponent {
ngbModal.open(SafeModeModalComponent)
}
this.updater.check().then(update => {
this.appUpdate = update
this.updater.check().then(available => {
this.updatesAvailable = available
})
this.touchbar.update()
@@ -141,14 +141,19 @@ export class AppRootComponent {
config.changed$.subscribe(() => this.updateVibrancy())
this.updateVibrancy()
let lastProgress = null
this.app.tabOpened$.subscribe(tab => {
this.unsortedTabs.push(tab)
tab.progress$.subscribe(progress => {
if (lastProgress === progress) {
return
}
if (progress !== null) {
this.hostApp.getWindow().setProgressBar(progress / 100.0, { mode: 'normal' })
} else {
this.hostApp.getWindow().setProgressBar(-1, { mode: 'none' })
}
lastProgress = progress
})
this.noTabs = false
})
@@ -161,28 +166,35 @@ export class AppRootComponent {
onGlobalHotkey () {
if (this.hostApp.getWindow().isFocused()) {
// focused
this.electron.loseFocus()
this.hostApp.getWindow().blur()
if (this.hostApp.platform !== Platform.macOS) {
this.hideWindow()
} else {
this.presentWindow()
}
}
presentWindow () {
if (!this.hostApp.getWindow().isVisible()) {
// unfocused, invisible
this.hostApp.getWindow().show()
this.hostApp.getWindow().focus()
} else {
if (this.config.store.appearance.dock === 'off') {
// not docked, visible
setTimeout(() => {
this.hostApp.getWindow().focus()
})
} else {
// docked, visible
this.hostApp.getWindow().hide()
}
} else {
if (!this.hostApp.getWindow().isVisible()) {
// unfocused, invisible
this.hostApp.getWindow().show()
this.hostApp.getWindow().focus()
} else {
if (this.config.store.appearance.dock === 'off') {
// not docked, visible
setTimeout(() => {
this.hostApp.getWindow().focus()
})
} else {
// docked, visible
this.hostApp.getWindow().hide()
}
}
}
}
hideWindow () {
this.electron.loseFocus()
this.hostApp.getWindow().blur()
if (this.hostApp.platform !== Platform.macOS) {
this.hostApp.getWindow().hide()
}
}
@@ -205,7 +217,7 @@ export class AppRootComponent {
}
updateApp () {
this.electron.shell.openExternal(this.appUpdate.url)
this.updater.update()
}
onTabDragStart () {

View File

@@ -13,6 +13,7 @@ export abstract class BaseTabComponent {
hasFocus = false
hasActivity = false
hostView: ViewRef
color: string = null
protected titleChange = new Subject<string>()
protected focused = new Subject<void>()
protected blurred = new Subject<void>()
@@ -68,7 +69,7 @@ export abstract class BaseTabComponent {
this.activity.next(false)
}
getRecoveryToken (): any {
async getRecoveryToken (): Promise<any> {
return null
}

View File

@@ -1,4 +1,4 @@
.icon(tabindex='0', [class.active]='model', (keyup.space)='click()')
i.fa.fa-square-o.off
i.fa.fa-check-square.on
i.fas.fa-square.off
i.fas.fa-check-square.on
.text {{text}}

View File

@@ -1,5 +1,5 @@
.modal-body
input.form-control(type='text', [(ngModel)]='value', (keyup.enter)='save()', autofocus)
input.form-control(type='text', #input, [(ngModel)]='value', (keyup.enter)='save()', autofocus)
.modal-footer
button.btn.btn-outline-primary((click)='save()') Save

View File

@@ -1,4 +1,4 @@
import { Component, Input } from '@angular/core'
import { Component, Input, ElementRef, ViewChild } from '@angular/core'
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
@Component({
@@ -7,11 +7,19 @@ import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
})
export class RenameTabModalComponent {
@Input() value: string
@ViewChild('input') input: ElementRef
constructor (
private modalInstance: NgbActiveModal
) { }
ngOnInit () {
setTimeout(() => {
this.input.nativeElement.focus()
this.input.nativeElement.select()
}, 250)
}
save () {
this.modalInstance.close(this.value)
}

View File

@@ -14,10 +14,10 @@ div
footer.d-flex.align-items-center
.btn-group.mr-auto
button.btn.btn-secondary((click)='homeBase.openGitHub()')
i.fa.fa-github
i.fab.fa-github
span GitHub
button.btn.btn-secondary((click)='homeBase.reportBug()')
i.fa.fa-bug
i.fas.fa-bug
span Report a problem
.form-control-static.selectable.no-drag Version: {{homeBase.appVersion}}

View File

@@ -17,7 +17,7 @@ import { BaseTabComponent } from '../components/baseTab.component'
export class TabBodyComponent implements OnChanges {
@Input() @HostBinding('class.active') active: boolean
@Input() tab: BaseTabComponent
@ViewChild('placeholder', {read: ViewContainerRef}) placeholder: ViewContainerRef
@ViewChild('placeholder', { read: ViewContainerRef }) placeholder: ViewContainerRef
ngOnChanges (changes) {
if (changes.tab) {

View File

@@ -1,4 +1,7 @@
.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}}
button((click)='app.closeTab(tab, true)') &times;

View File

@@ -3,10 +3,21 @@ import { SortableComponent } from 'ng2-dnd'
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
import { BaseTabComponent } from './baseTab.component'
import { RenameTabModalComponent } from './renameTabModal.component'
import { HotkeysService } from '../services/hotkeys.service'
import { ElectronService } from '../services/electron.service'
import { AppService } from '../services/app.service'
import { HostAppService, Platform } from '../services/hostApp.service'
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' },
]
@Component({
selector: 'tab-header',
template: require('./tabHeader.component.pug'),
@@ -28,8 +39,17 @@ export class TabHeaderComponent {
private zone: NgZone,
private hostApp: HostAppService,
private ngbModal: NgbModal,
private hotkeys: HotkeysService,
private parentDraggable: SortableComponent,
) { }
) {
this.hotkeys.matchedHotkey.subscribe((hotkey) => {
if (this.app.activeTab === this.tab) {
if (hotkey === 'rename-tab') {
this.showRenameTabModal()
}
}
})
}
ngOnInit () {
if (this.hostApp.platform === Platform.macOS) {
@@ -40,7 +60,7 @@ export class TabHeaderComponent {
})
}
@HostListener('dblclick') onDoubleClick (): void {
showRenameTabModal (): void {
let modal = this.ngbModal.open(RenameTabModalComponent)
modal.componentInstance.value = this.tab.customTitle || this.tab.title
modal.result.then(result => {
@@ -49,6 +69,10 @@ export class TabHeaderComponent {
}).catch(() => null)
}
@HostListener('dblclick') onDoubleClick (): void {
this.showRenameTabModal()
}
@HostListener('auxclick', ['$event']) async onAuxClick ($event: MouseEvent) {
if ($event.which === 2) {
this.app.closeTab(this.tab, true)
@@ -87,8 +111,31 @@ export class TabHeaderComponent {
}
})
},
{
label: 'Rename',
click: () => this.zone.run(() => this.showRenameTabModal())
},
{
label: 'Color',
sublabel: COLORS.find(x => x.value === this.tab.color).name,
submenu: COLORS.map(color => ({
label: color.name,
type: 'radio',
checked: this.tab.color === color.value,
click: () => this.zone.run(() => {
this.tab.color = color.value
}),
})),
}
])
if ((this.tab as any).saveAsProfile) {
contextMenu.append(new this.electron.MenuItem({
label: 'Save as a profile',
click: () => this.zone.run(() => (this.tab as any).saveAsProfile())
}))
}
let process = await this.tab.getCurrentProcess()
if (process) {
contextMenu.append(new this.electron.MenuItem({

View File

@@ -9,7 +9,7 @@ button.btn.btn-secondary.btn-maximize(
svg(version='1.1', width='10', height='10')
path(d='M 0,0 0,10 10,10 10,0 Z M 1,1 9,1 9,9 1,9 Z')
button.btn.btn-secondary.btn-close(
(click)='hostApp.getWindow().close()',
(click)='app.closeWindow()'
)
svg(version='1.1', width='10', height='10')
path(d='M 0,0 0,0.7 4.3,5 0,9.3 0,10 0.7,10 5,5.7 9.3,10 10,10 10,9.3 5.7,5 10,0.7 10,0 9.3,0 5,4.3 0.7,0 Z')

View File

@@ -1,5 +1,6 @@
import { Component } from '@angular/core'
import { HostAppService } from '../services/hostApp.service'
import { AppService } from '../services/app.service'
@Component({
selector: 'window-controls',
@@ -7,5 +8,5 @@ import { HostAppService } from '../services/hostApp.service'
styles: [require('./windowControls.component.scss')],
})
export class WindowControlsComponent {
constructor (public hostApp: HostAppService) { }
constructor (public hostApp: HostAppService, public app: AppService) { }
}

View File

@@ -7,46 +7,33 @@ hotkeys:
- 'F11'
close-tab:
- 'Ctrl-Shift-W'
- ['Ctrl-A', 'K']
toggle-last-tab:
- ['Ctrl-A', 'A']
- ['Ctrl-A', 'Ctrl-A']
toggle-last-tab: []
rename-tab:
- 'Ctrl-Shift-R'
next-tab:
- 'Ctrl-Shift-ArrowRight'
- ['Ctrl-A', 'N']
- 'Ctrl-Tab'
previous-tab:
- 'Ctrl-Shift-ArrowLeft'
- ['Ctrl-A', 'P']
- 'Ctrl-Shift-Tab'
tab-1:
- 'Alt-1'
- ['Ctrl-A', '1']
tab-2:
- 'Alt-2'
- ['Ctrl-A', '2']
tab-3:
- 'Alt-3'
- ['Ctrl-A', '3']
tab-4:
- 'Alt-4'
- ['Ctrl-A', '4']
tab-5:
- 'Alt-5'
- ['Ctrl-A', '5']
tab-6:
- 'Alt-6'
- ['Ctrl-A', '6']
tab-7:
- 'Alt-7'
- ['Ctrl-A', '7']
tab-8:
- 'Alt-8'
- ['Ctrl-A', '8']
tab-9:
- 'Alt-9'
- ['Ctrl-A', '9']
tab-10:
- 'Alt-0'
- ['Ctrl-A', '0']
pluginBlacklist: ['ssh']

View File

@@ -8,6 +8,8 @@ hotkeys:
close-tab:
- '⌘-W'
toggle-last-tab: []
rename-tab:
- '⌘-R'
next-tab:
- 'Ctrl-Tab'
previous-tab:

View File

@@ -7,46 +7,33 @@ hotkeys:
- 'F11'
close-tab:
- 'Ctrl-Shift-W'
- ['Ctrl-A', 'K']
toggle-last-tab:
- ['Ctrl-A', 'A']
- ['Ctrl-A', 'Ctrl-A']
toggle-last-tab: []
rename-tab:
- 'Ctrl-Shift-R'
next-tab:
- 'Ctrl-Shift-ArrowRight'
- ['Ctrl-A', 'N']
- 'Ctrl-Tab'
previous-tab:
- 'Ctrl-Shift-ArrowLeft'
- ['Ctrl-A', 'P']
- 'Ctrl-Shift-Tab'
tab-1:
- 'Alt-1'
- ['Ctrl-A', '1']
tab-2:
- 'Alt-2'
- ['Ctrl-A', '2']
tab-3:
- 'Alt-3'
- ['Ctrl-A', '3']
tab-4:
- 'Alt-4'
- ['Ctrl-A', '4']
tab-5:
- 'Alt-5'
- ['Ctrl-A', '5']
tab-6:
- 'Alt-6'
- ['Ctrl-A', '6']
tab-7:
- 'Alt-7'
- ['Ctrl-A', '7']
tab-8:
- 'Alt-8'
- ['Ctrl-A', '8']
tab-9:
- 'Alt-9'
- ['Ctrl-A', '9']
tab-10:
- 'Alt-0'
- ['Ctrl-A', '0']
pluginBlacklist: []

View File

@@ -6,19 +6,7 @@ import { NgbModule } from '@ng-bootstrap/ng-bootstrap'
import { PerfectScrollbarModule, PERFECT_SCROLLBAR_CONFIG } from 'ngx-perfect-scrollbar'
import { DndModule } from 'ng2-dnd'
import { AppService } from './services/app.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 { AppHotkeyProvider } from './services/hotkeys.service'
import { AppRootComponent } from './components/appRoot.component'
import { CheckboxComponent } from './components/checkbox.component'
@@ -44,25 +32,12 @@ import 'perfect-scrollbar/css/perfect-scrollbar.css'
import 'ng2-dnd/bundles/style.css'
const PROVIDERS = [
AppService,
ConfigService,
DockingService,
ElectronService,
HomeBaseService,
HostAppService,
HotkeysService,
LogService,
ShellIntegrationService,
TabRecoveryService,
ThemesService,
TouchbarService,
UpdaterService,
{ provide: HotkeyProvider, useClass: AppHotkeyProvider, multi: true },
{ provide: Theme, useClass: StandardTheme, multi: true },
{ provide: Theme, useClass: StandardCompactTheme, multi: true },
{ provide: Theme, useClass: PaperTheme, multi: true },
{ provide: ConfigProvider, useClass: CoreConfigProvider, multi: true },
{ provide: PERFECT_SCROLLBAR_CONFIG, useValue: { suppressScrollX: true }}
{ provide: PERFECT_SCROLLBAR_CONFIG, useValue: { suppressScrollX: true } }
]
@NgModule({

View File

@@ -35,7 +35,7 @@ class CompletionObserver {
}
}
@Injectable()
@Injectable({ providedIn: 'root' })
export class AppService {
tabs: BaseTabComponent[] = []
activeTab: BaseTabComponent
@@ -64,6 +64,8 @@ export class AppService {
log: LogService,
) {
this.logger = log.create('app')
this.hostApp.windowCloseRequest$.subscribe(() => this.closeWindow())
}
openNewTab (type: TabComponentType, inputs?: any): BaseTabComponent {
@@ -158,6 +160,18 @@ export class AppService {
this.tabClosed.next(tab)
}
async closeWindow () {
for (let tab of this.tabs) {
if (!await tab.canClose()) {
return
}
}
for (let tab of this.tabs) {
tab.destroy()
}
this.hostApp.closeWindow()
}
emitReady () {
this.ready.next(null)
this.ready.complete()

View File

@@ -56,7 +56,7 @@ export class ConfigProxy {
return real[key]
} else {
if (isNonStructuralObjectMember(defaults[key])) {
real[key] = {...defaults[key]}
real[key] = { ...defaults[key] }
delete real[key].__nonStructural
return real[key]
} else {
@@ -74,7 +74,7 @@ export class ConfigProxy {
setValue (key: string, value: any) { } // tslint:disable-line
}
@Injectable()
@Injectable({ providedIn: 'root' })
export class ConfigService {
store: any
restartRequested: boolean

View File

@@ -8,7 +8,7 @@ export interface IScreen {
name: string
}
@Injectable()
@Injectable({ providedIn: 'root' })
export class DockingService {
constructor (
private electron: ElectronService,

View File

@@ -1,7 +1,7 @@
import { Injectable } from '@angular/core'
import { TouchBar, BrowserWindow, Menu, MenuItem } from 'electron'
@Injectable()
@Injectable({ providedIn: 'root' })
export class ElectronService {
app: any
ipcRenderer: any
@@ -12,6 +12,7 @@ export class ElectronService {
nativeImage: any
screen: any
remote: any
autoUpdater: any
TouchBar: typeof TouchBar
BrowserWindow: typeof BrowserWindow
Menu: typeof Menu
@@ -29,6 +30,7 @@ export class ElectronService {
this.ipcRenderer = this.electron.ipcRenderer
this.globalShortcut = this.remote.globalShortcut
this.nativeImage = this.remote.nativeImage
this.autoUpdater = this.remote.autoUpdater
this.TouchBar = this.remote.TouchBar
this.BrowserWindow = this.remote.BrowserWindow
this.Menu = this.remote.Menu

View File

@@ -5,7 +5,7 @@ import { ConfigService } from './config.service'
import ua = require('universal-analytics')
import uuidv4 = require('uuid/v4')
@Injectable()
@Injectable({ providedIn: 'root' })
export class HomeBaseService {
appVersion: string
@@ -33,7 +33,7 @@ export class HomeBaseService {
linux: 'OS: Linux',
}[os.platform()]
let plugins = (window as any).installedPlugins.filter(x => !x.isBuiltin).map(x => x.name)
body += `Plugins: ${plugins.join(', ')}\n\n`
body += `Plugins: ${plugins.join(', ') || 'none'}\n\n`
this.electron.shell.openExternal(`https://github.com/eugeny/terminus/issues/new?body=${encodeURIComponent(body)}&labels=${label}`)
}

View File

@@ -16,7 +16,7 @@ export interface Bounds {
height: number
}
@Injectable()
@Injectable({ providedIn: 'root' })
export class HostAppService {
platform: Platform
nodePlatform: string
@@ -27,7 +27,9 @@ export class HostAppService {
private cliOpenDirectory = new Subject<string>()
private cliRunCommand = new Subject<string[]>()
private cliPaste = new Subject<string>()
private cliOpenProfile = new Subject<string>()
private configChangeBroadcast = new Subject<void>()
private windowCloseRequest = new Subject<void>()
private logger: Logger
private windowId: number
@@ -36,7 +38,9 @@ export class HostAppService {
get cliOpenDirectory$ (): Observable<string> { return this.cliOpenDirectory }
get cliRunCommand$ (): Observable<string[]> { return this.cliRunCommand }
get cliPaste$ (): Observable<string> { return this.cliPaste }
get cliOpenProfile$ (): Observable<string> { return this.cliOpenProfile }
get configChangeBroadcast$ (): Observable<void> { return this.configChangeBroadcast }
get windowCloseRequest$ (): Observable<void> { return this.windowCloseRequest }
constructor (
private zone: NgZone,
@@ -72,6 +76,10 @@ export class HostAppService {
this.zone.run(() => this.shown.emit())
})
electron.ipcRenderer.on('host:window-close-request', () => {
this.zone.run(() => this.windowCloseRequest.next())
})
electron.ipcRenderer.on('host:second-instance', (_$event, argv: any, cwd: string) => this.zone.run(() => {
this.logger.info('Second instance', argv)
const op = argv._[0]
@@ -85,6 +93,10 @@ export class HostAppService {
text = shellEscape([text])
}
this.cliPaste.next(text)
} else if (op === 'profile') {
this.cliOpenProfile.next(argv.profileName)
} else {
this.secondInstance.next()
}
}))
@@ -180,6 +192,14 @@ export class HostAppService {
this.electron.ipcRenderer.send('app:ready')
}
bringToFront () {
this.electron.ipcRenderer.send('window-bring-to-front')
}
closeWindow () {
this.electron.ipcRenderer.send('window-close')
}
quit () {
this.logger.info('Quitting')
this.electron.app.quit()

View File

@@ -17,7 +17,7 @@ interface EventBufferEntry {
time: number,
}
@Injectable()
@Injectable({ providedIn: 'root' })
export class HotkeysService {
key = new EventEmitter<NativeKeyEvent>()
matchedHotkey = new EventEmitter<string>()
@@ -80,13 +80,13 @@ export class HotkeysService {
}
getCurrentKeystrokes (): string[] {
this.currentKeystrokes = this.currentKeystrokes.filter((x) => performance.now() - x.time < KEY_TIMEOUT )
return stringifyKeySequence(this.currentKeystrokes.map((x) => x.event))
this.currentKeystrokes = this.currentKeystrokes.filter(x => performance.now() - x.time < KEY_TIMEOUT)
return stringifyKeySequence(this.currentKeystrokes.map(x => x.event))
}
registerGlobalHotkey () {
this.electron.globalShortcut.unregisterAll()
let value = this.config.store.hotkeys['toggle-window']
let value = this.config.store.hotkeys['toggle-window'] || []
if (typeof value === 'string') {
value = [value]
}
@@ -120,23 +120,28 @@ export class HotkeysService {
if (typeof value === 'string') {
value = [value]
}
value = value.map((item) => (typeof item === 'string') ? [item] : item)
keys[key] = value
if (value) {
value = value.map((item) => (typeof item === 'string') ? [item] : item)
keys[key] = value
}
}
}
return keys
}
getCurrentFullyMatchedHotkey (): string {
for (let id in this.getHotkeysConfig()) {
for (let sequence of this.getHotkeysConfig()[id]) {
let currentStrokes = this.getCurrentKeystrokes()
let currentStrokes = this.getCurrentKeystrokes()
let config = this.getHotkeysConfig()
for (let id in config) {
for (let sequence of config[id]) {
if (currentStrokes.length < sequence.length) {
continue
}
if (sequence.every((x, index) => {
return x.toLowerCase() === currentStrokes[currentStrokes.length - sequence.length + index].toLowerCase()
})) {
if (sequence.every(
(x, index) =>
x.toLowerCase() ===
currentStrokes[currentStrokes.length - sequence.length + index].toLowerCase()
)) {
return id
}
}
@@ -145,15 +150,17 @@ export class HotkeysService {
}
getCurrentPartiallyMatchedHotkeys (): PartialHotkeyMatch[] {
let currentStrokes = this.getCurrentKeystrokes()
let config = this.getHotkeysConfig()
let result = []
for (let id in this.getHotkeysConfig()) {
for (let sequence of this.getHotkeysConfig()[id]) {
let currentStrokes = this.getCurrentKeystrokes()
for (let id in config) {
for (let sequence of config[id]) {
for (let matchLength = Math.min(currentStrokes.length, sequence.length); matchLength > 0; matchLength--) {
if (sequence.slice(0, matchLength).every((x, index) => {
return x.toLowerCase() === currentStrokes[currentStrokes.length - matchLength + index].toLowerCase()
})) {
if (sequence.slice(0, matchLength).every(
(x, index) =>
x.toLowerCase() ===
currentStrokes[currentStrokes.length - matchLength + index].toLowerCase()
)) {
result.push({
matchedLength: matchLength,
id,
@@ -208,6 +215,10 @@ export class AppHotkeyProvider extends HotkeyProvider {
id: 'toggle-fullscreen',
name: 'Toggle fullscreen mode',
},
{
id: 'rename-tab',
name: 'Rename Tab',
},
{
id: 'close-tab',
name: 'Close tab',

View File

@@ -53,7 +53,7 @@ export class Logger {
log (...args: any[]) { this.doLog('log', ...args) }
}
@Injectable()
@Injectable({ providedIn: 'root' })
export class LogService {
private log: any

View File

@@ -1,12 +1,12 @@
import * as path from 'path'
import * as fs from 'mz/fs'
import { Registry } from 'rage-edit-tmp'
import { Registry } from 'rage-edit'
import { exec } from 'mz/child_process'
import { Injectable } from '@angular/core'
import { ElectronService } from './electron.service'
import { HostAppService, Platform } from './hostApp.service'
@Injectable()
@Injectable({ providedIn: 'root' })
export class ShellIntegrationService {
private automatorWorkflows = ['Open Terminus here.workflow', 'Paste path into Terminus.workflow']
private automatorWorkflowsLocation: string
@@ -48,9 +48,9 @@ export class ShellIntegrationService {
async isInstalled (): Promise<boolean> {
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) {
return await Registry.has(this.registryKeys[0].path)
return Registry.has(this.registryKeys[0].path)
}
return true
}

View File

@@ -5,7 +5,7 @@ import { Logger, LogService } from '../services/log.service'
import { AppService } from '../services/app.service'
import { ConfigService } from '../services/config.service'
@Injectable()
@Injectable({ providedIn: 'root' })
export class TabRecoveryService {
logger: Logger
@@ -19,13 +19,18 @@ export class TabRecoveryService {
app.tabsChanged$.subscribe(() => {
this.saveTabs(app.tabs)
})
setInterval(() => {
this.saveTabs(app.tabs)
}, 30000)
}
saveTabs (tabs: BaseTabComponent[]) {
async saveTabs (tabs: BaseTabComponent[]) {
window.localStorage.tabsRecovery = JSON.stringify(
tabs
.map((tab) => tab.getRecoveryToken())
.filter((token) => !!token)
await Promise.all(
tabs
.map(tab => tab.getRecoveryToken())
.filter(token => !!token)
)
)
}

View File

@@ -2,7 +2,7 @@ import { Inject, Injectable } from '@angular/core'
import { ConfigService } from '../services/config.service'
import { Theme } from '../api/theme'
@Injectable()
@Injectable({ providedIn: 'root' })
export class ThemesService {
private styleElement: HTMLElement = null

View File

@@ -6,7 +6,7 @@ import { ElectronService } from './electron.service'
import { HostAppService } from './hostApp.service'
import { IToolbarButton, ToolbarButtonProvider } from '../api'
@Injectable()
@Injectable({ providedIn: 'root' })
export class TouchbarService {
private tabsSegmentedControl: TouchBarSegmentedControl
private tabSegments: SegmentedControlSegment[] = []
@@ -49,8 +49,8 @@ export class TouchbarService {
let touchBar = new this.electron.TouchBar({
items: [
this.tabsSegmentedControl,
new this.electron.TouchBar.TouchBarSpacer({size: 'flexible'}),
new this.electron.TouchBar.TouchBarSpacer({size: 'small'}),
new this.electron.TouchBar.TouchBarSpacer({ size: 'flexible' }),
new this.electron.TouchBar.TouchBarSpacer({ size: 'small' }),
...buttons.map(button => this.getButton(button))
]
})

View File

@@ -1,36 +1,77 @@
import axios from 'axios'
import * as os from 'os'
import { Injectable } from '@angular/core'
import { Logger, LogService } from './log.service'
import { ElectronService } from './electron.service'
const UPDATES_URL = 'https://api.github.com/repos/eugeny/terminus/releases/latest'
export interface Update {
version: string
url: string
}
@Injectable()
@Injectable({ providedIn: 'root' })
export class UpdaterService {
private logger: Logger
private downloaded: Promise<boolean>
private isSquirrel = true
private updateURL: string
constructor (
log: LogService,
private electron: ElectronService,
) {
this.logger = log.create('updater')
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.logger.info('Update available')
})
this.electron.autoUpdater.once('update-not-available', () => {
this.logger.info('No updates')
})
this.downloaded = new Promise<boolean>(resolve => {
this.electron.autoUpdater.once('update-downloaded', () => resolve(true))
})
this.logger.debug('Checking for updates')
if (this.isSquirrel) {
try {
this.electron.autoUpdater.checkForUpdates()
} catch (e) {
this.isSquirrel = false
this.logger.info('Squirrel updater unavailable, falling back')
}
}
}
async check (): Promise<Update> {
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:', version)
return { version, url: data.html_url }
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
}
async update () {
if (!this.isSquirrel) {
this.electron.shell.openExternal(this.updateURL)
} else {
await this.downloaded
this.electron.autoUpdater.quitAndInstall()
}
this.logger.info('No updates')
return null
}
}

View File

@@ -51,6 +51,7 @@ $input-disabled-bg: #333;
$input-color: $body-color;
$input-color-placeholder: #333;
$input-border-color: #344;
$input-border-width: 0;
//$input-box-shadow: inset 0 1px 1px rgba($black,.075);
$input-border-radius: 0;
$custom-select-border-radius: 0;
@@ -70,7 +71,7 @@ $popover-bg: $body-bg;
$dropdown-bg: $body-bg;
$dropdown-link-color: $body-color;
$dropdown-link-hover-color: #333;
$dropdown-link-hover-color: white;
$dropdown-link-hover-bg: $body-bg2;
//$dropdown-link-active-color: $component-active-color;
//$dropdown-link-active-bg: $component-active-bg;
@@ -126,7 +127,7 @@ body {
app-root {
&.no-tabs {
background: rgba(0,0,0,.5);
background: rgba(0,0,0,.5);
}
&> .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 {
-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>");
@@ -362,7 +372,31 @@ toggle.active .body .toggle {
background: $blue;
}
.modal .modal-footer {
background: rgba(0, 0, 0, .25);
.btn {
font-weight: bold;
padding: 0.375rem 1.5rem;
}
}
.list-group-item svg {
fill: white;
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;
}

View File

@@ -73,12 +73,12 @@ aws4@^1.6.0:
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f"
integrity sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==
axios@0.16.2:
version "0.16.2"
resolved "https://registry.yarnpkg.com/axios/-/axios-0.16.2.tgz#ba4f92f17167dfbab40983785454b9ac149c3c6d"
integrity sha1-uk+S8XFn37q0CYN4VFS5rBScPG0=
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.2.3"
follow-redirects "^1.3.0"
is-buffer "^1.1.5"
bcrypt-pbkdf@^1.0.0:
@@ -170,14 +170,7 @@ dashdash@^1.12.0:
dependencies:
assert-plus "^1.0.0"
debug@^2.4.5:
version "2.6.8"
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.8.tgz#e731531ca2ede27d188222427da17821d68ff4fc"
integrity sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw=
dependencies:
ms "2.0.0"
debug@^3.0.0:
debug@=3.1.0, debug@^3.0.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==
@@ -276,12 +269,12 @@ 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"
integrity sha1-1RQsDK7msRifh9OnYREGT4bIu/I=
follow-redirects@^1.2.3:
version "1.2.4"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.2.4.tgz#355e8f4d16876b43f577b0d5ce2668b9723214ea"
integrity sha512-Suw6KewLV2hReSyEOeql+UUkBVyiBm3ok1VPrVFRZnQInWpdoZbbiG5i8aJVSjTr0yQ4Ava0Sh6/joCg1Brdqw==
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 "^2.4.5"
debug "=3.1.0"
forever-agent@~0.6.1:
version "0.6.1"
@@ -364,9 +357,9 @@ http-signature@~1.2.0:
sshpk "^1.7.0"
is-buffer@^1.1.5:
version "1.1.5"
resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.5.tgz#1f3b26ef613b214b88cbca23cc6c01d87961eecc"
integrity sha1-Hzsm72E7IUuIy8ojzGwB2Hlh7sw=
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:
version "1.0.0"
@@ -493,10 +486,10 @@ qs@~6.5.1:
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"
integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==
rage-edit-tmp@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/rage-edit-tmp/-/rage-edit-tmp-1.1.0.tgz#fc5d76716d2fe2cf97dcafbf3e26753e3a08e3b2"
integrity sha512-lR97QHY5WSf9orInMJhPqUbenkdiy7QbXUoRMI+wBZGyAPkxNwgo7h6ojq634QrBf/kQo3mVXYjuD3ZYraNaZQ==
rage-edit@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/rage-edit/-/rage-edit-1.2.0.tgz#991860a60fef934d8a6d0f057e55786b02f94a2b"
integrity sha512-0RspBRc2s6We4g7hRCvT5mu7YPEnfjvQK8Tt354a2uUNJCMC7MKLvo/1mLvHUCQ/zbP6siQyp5VRZN7UCpMFZg==
request@2.86.0:
version "2.86.0"

View File

@@ -2,57 +2,61 @@
strong Error in {{erroredPlugin}}:
pre {{errorMessage}}
button.btn.btn-outline-info.btn-sm.pull-right((click)='openPluginsFolder()')
i.fa.fa-folder
span Plugins folder
.d-flex
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-outline-danger.active.mr-2(
*ngIf='config.store.pluginBlacklist.includes(plugin.name)',
(click)='enablePlugin(plugin)'
)
i.fa.fa-fw.fa-pause
button.btn.btn-outline-secondary.mr-2(
*ngIf='!config.store.pluginBlacklist.includes(plugin.name)',
(click)='disablePlugin(plugin)'
)
i.fa.fa-fw.fa-check
button.btn.btn-primary.ml-2(
*ngIf='npmInstalled && knownUpgrades[plugin.name]',
(click)='upgradePlugin(plugin)',
[disabled]='busy[plugin.name] != undefined'
)
i.fas.fa-fw.fa-arrow-up(*ngIf='busy[plugin.name] != BusyState.Installing')
i.fas.fa-fw.fa-circle-notch.fa-spin(*ngIf='busy[plugin.name] == BusyState.Installing')
span Upgrade ({{knownUpgrades[plugin.name].version}})
.mr-auto.d-flex.flex-column
div
strong {{plugin.name}}
small.text-muted.ml-1 {{plugin.version}} / {{plugin.author}}
a.text-muted.mb-0((click)='showPluginInfo(plugin)')
small {{plugin.description}}
button.btn.btn-primary.ml-2(
*ngIf='npmInstalled && knownUpgrades[plugin.name]',
(click)='upgradePlugin(plugin)',
[disabled]='busy[plugin.name] != undefined'
)
i.fa.fa-fw.fa-arrow-up(*ngIf='busy[plugin.name] != BusyState.Installing')
i.fa.fa-fw.fa-circle-o-notch.fa-spin(*ngIf='busy[plugin.name] == BusyState.Installing')
span Upgrade ({{knownUpgrades[plugin.name].version}})
button.btn.btn-outline-danger.ml-2(
(click)='uninstallPlugin(plugin)',
*ngIf='!plugin.isBuiltin && npmInstalled',
[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')
button.btn.btn-primary.ml-2(
*ngIf='config.store.pluginBlacklist.includes(plugin.name)',
(click)='enablePlugin(plugin)'
)
i.fas.fa-fw.fa-play
button.btn.btn-secondary.ml-2(
*ngIf='!config.store.pluginBlacklist.includes(plugin.name)',
(click)='disablePlugin(plugin)'
)
i.fas.fa-fw.fa-pause
button.btn.btn-danger.ml-2(
(click)='uninstallPlugin(plugin)',
*ngIf='!plugin.isBuiltin && npmInstalled',
[disabled]='busy[plugin.name] != undefined'
)
i.fas.fa-fw.fa-trash(*ngIf='busy[plugin.name] != BusyState.Uninstalling')
i.fas.fa-fw.fa-circle-notch.fa-spin(*ngIf='busy[plugin.name] == BusyState.Uninstalling')
.text-center.mt-5(*ngIf='npmMissing')
h4 npm not installed
p.mb-2 npm is required to install Terminus plugins.
.btn-group
button.btn.btn-outline-primary((click)='downloadNPM()')
i.fa.fa-download
i.fas.fa-download
span Get npm
button.btn.btn-outline-info((click)='checkNPM()')
i.fa.fa-refresh
i.fas.fa-refresh
span Try again
div(*ngIf='npmInstalled')
@@ -61,8 +65,8 @@ div(*ngIf='npmInstalled')
.input-group.mb-3
.input-group-prepend
.input-group-text
i.fa.fa-fw.fa-circle-o-notch.fa-spin(*ngIf='!availablePluginsReady')
i.fa.fa-fw.fa-search(*ngIf='availablePluginsReady')
i.fas.fa-fw.fa-circle-o-notch.fa-spin(*ngIf='!availablePluginsReady')
i.fas.fa-fw.fa-search(*ngIf='availablePluginsReady')
input.form-control(
type='text',
[(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")')
.d-flex.w-100.align-items-center.mb-3(*ngIf='!isAlreadyInstalled(plugin)')
button.btn.btn-primary.mr-2(
.list-group-item.d-flex.align-items-center(*ngIf='!isAlreadyInstalled(plugin)')
button.btn.btn-primary.mr-3(
(click)='installPlugin(plugin)',
[disabled]='busy[plugin.name] != undefined'
)
i.fa.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-download(*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
strong {{plugin.name}}
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}}

View File

@@ -22,7 +22,6 @@ import { PluginsSettingsTabProvider } from './settings'
providers: [
{ provide: SettingsTabProvider, useClass: PluginsSettingsTabProvider, multi: true },
{ provide: ConfigProvider, useClass: PluginsConfigProvider, multi: true },
PluginManagerService,
],
entryComponents: [
PluginsSettingsTabComponent,

View File

@@ -23,7 +23,7 @@ export interface IPluginInfo {
path?: string
}
@Injectable()
@Injectable({ providedIn: 'root' })
export class PluginManagerService {
logger: Logger
builtinPluginsPath: string = (window as any).builtinPluginsPath

View File

@@ -6,7 +6,7 @@
"terminus-builtin-plugin"
],
"main": "dist/index.js",
"typings": "dist/index.d.ts",
"typings": "dist/src/index.d.ts",
"scripts": {
"build": "webpack --progress --color --display-modules",
"watch": "webpack --progress --color --watch"
@@ -30,6 +30,5 @@
"@ng-bootstrap/ng-bootstrap": "1.0.0-alpha.22",
"terminus-core": "*",
"rxjs": "5.3.0"
},
"false": {}
}
}

View File

@@ -1,6 +1,6 @@
import { Injectable } from '@angular/core'
import { DomSanitizer } from '@angular/platform-browser'
import { ToolbarButtonProvider, IToolbarButton, AppService, HostAppService } from 'terminus-core'
import { ToolbarButtonProvider, IToolbarButton, AppService, HostAppService, HotkeysService } from 'terminus-core'
import { SettingsTabComponent } from './components/settingsTab.component'
@@ -8,11 +8,18 @@ import { SettingsTabComponent } from './components/settingsTab.component'
export class ButtonProvider extends ToolbarButtonProvider {
constructor (
hostApp: HostAppService,
hotkeys: HotkeysService,
private app: AppService,
private domSanitizer: DomSanitizer,
) {
super()
hostApp.preferencesMenu$.subscribe(() => this.open())
hotkeys.matchedHotkey.subscribe(async (hotkey) => {
if (hotkey === 'settings') {
this.open()
}
})
}
provide (): IToolbarButton[] {

View File

@@ -12,11 +12,11 @@ ngb-tabset.vertical(type='pills', [activeId]='activeTab')
.text-muted.mr-auto {{homeBase.appVersion}}
button.btn.btn-secondary.mr-3((click)='homeBase.openGitHub()')
i.fa.fa-github
i.fab.fa-github
span GitHub
button.btn.btn-secondary((click)='homeBase.reportBug()')
i.fa.fa-bug
i.fas.fa-bug
span Report a problem
.form-line(*ngIf='!isShellIntegrationInstalled')
@@ -24,7 +24,7 @@ ngb-tabset.vertical(type='pills', [activeId]='activeTab')
.title Shell integration
.description Allows quickly opening a terminal in the selected folder
button.btn.btn-primary((click)='installShellIntegration()')
i.fa.fa-check
i.fas.fa-check
span Install
.form-line
@@ -61,7 +61,8 @@ ngb-tabset.vertical(type='pills', [activeId]='activeTab')
.form-line(*ngIf='hostApp.platform !== Platform.Linux')
.header
.title Vibrancy
.title(*ngIf='hostApp.platform === Platform.Windows') Acrylic background
.title(*ngIf='hostApp.platform === Platform.macOS') Vibrancy
.description Gives the window a blurred transparent background
toggle(
@@ -71,7 +72,7 @@ ngb-tabset.vertical(type='pills', [activeId]='activeTab')
.form-line(*ngIf='config.store.appearance.vibrancy && isFluentVibrancySupported')
.header
.title Vibrancy type
.title Background type
.btn-group(
[(ngModel)]='config.store.appearance.vibrancyType',
(ngModelChange)='config.save()',
@@ -94,12 +95,12 @@ ngb-tabset.vertical(type='pills', [activeId]='activeTab')
.form-line
.header
.title Window opacity
.title Transparency
input(
type='range',
[(ngModel)]='config.store.appearance.opacity',
(ngModelChange)='config.save(); (hostApp.platform === Platform.Linux && config.requestRestart())',
min='0.05',
min='0.4',
max='1',
step='0.01'
)
@@ -224,7 +225,7 @@ ngb-tabset.vertical(type='pills', [activeId]='activeTab')
.title Debugging
button.btn.btn-secondary((click)='hostApp.openDevTools()')
i.fa.fa-bug
i.fas.fa-bug
span Open DevTools
.form-line
@@ -253,7 +254,7 @@ ngb-tabset.vertical(type='pills', [activeId]='activeTab')
.input-group.mb-4
.input-group-prepend
.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')
.form-group
@@ -265,7 +266,7 @@ ngb-tabset.vertical(type='pills', [activeId]='activeTab')
tr(*ngFor='let hotkey of hotkeyDescriptions|filterBy:["name"]:hotkeyFilter')
td {{hotkey.name}}
td {{hotkey.id}}
td
td.pr-5
multi-hotkey-input(
[model]='getHotkey(hotkey.id)',
(modelChange)='setHotkey(hotkey.id, $event); config.save(); docking.dock()'
@@ -297,8 +298,8 @@ ngb-tabset.vertical(type='pills', [activeId]='activeTab')
)
.mt-3
button.btn.btn-primary((click)='saveConfigFile()', *ngIf='isConfigFileValid()')
i.fa.fa-check.mr-2
i.fas.fa-check.mr-2
| Save and apply
button.btn.btn-primary(disabled, *ngIf='!isConfigFileValid()')
i.fa.fa-warning.mr-2
i.fas.fa-exclamation-triangle.mr-2
| Invalid syntax

View File

@@ -1,14 +1,13 @@
import * as yaml from 'js-yaml'
import * as os from 'os'
import { Subscription } from 'rxjs'
import { Component, Inject, Input } from '@angular/core'
import { HotkeysService } from 'terminus-core'
import { Component, Inject, Input, HostBinding } from '@angular/core'
import {
ElectronService,
DockingService,
ConfigService,
IHotkeyDescription,
HotkeyProvider,
HotkeysService,
BaseTabComponent,
Theme,
HostAppService,
@@ -37,6 +36,7 @@ export class SettingsTabComponent extends BaseTabComponent {
configFile: string
isShellIntegrationInstalled = false
isFluentVibrancySupported = false
@HostBinding('class.pad-window-controls') padWindowControls = false
private configSubscription: Subscription
constructor (
@@ -47,7 +47,6 @@ export class SettingsTabComponent extends BaseTabComponent {
public homeBase: HomeBaseService,
public shellIntegration: ShellIntegrationService,
hotkeys: HotkeysService,
@Inject(HotkeyProvider) hotkeyProviders: HotkeyProvider[],
@Inject(SettingsTabProvider) public settingsProviders: SettingsTabProvider[],
@Inject(Theme) public themes: Theme[],
) {
@@ -58,16 +57,21 @@ export class SettingsTabComponent extends BaseTabComponent {
this.themes = config.enabledServices(this.themes)
this.configDefaults = yaml.safeDump(config.getDefaults())
this.configFile = config.readRaw()
this.configSubscription = config.changed$.subscribe(() => {
const onConfigChange = () => {
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 => {
this.hotkeyDescriptions = descriptions
})
this.isFluentVibrancySupported = process.platform === 'win32'
this.isFluentVibrancySupported = hostApp.platform === Platform.Windows
&& parseFloat(os.release()) >= 10
&& parseInt(os.release().split('.')[2]) >= 17063
}
@@ -76,7 +80,7 @@ export class SettingsTabComponent extends BaseTabComponent {
this.isShellIntegrationInstalled = await this.shellIntegration.isInstalled()
}
getRecoveryToken (): any {
async getRecoveryToken (): Promise<any> {
return { type: 'app:settings' }
}

View File

@@ -21,3 +21,7 @@
:host /deep/ ngb-tabset > .tab-content > .tab-pane {
width: 100%;
}
:host.pad-window-controls /deep/ ngb-tabset > .nav {
padding-top: 40px;
}

View File

@@ -7,7 +7,7 @@ import { SettingsTabProvider } from '../api'
})
export class SettingsTabBodyComponent {
@Input() provider: SettingsTabProvider
@ViewChild('placeholder', {read: ViewContainerRef}) placeholder: ViewContainerRef
@ViewChild('placeholder', { read: ViewContainerRef }) placeholder: ViewContainerRef
component: ComponentRef<Component>
constructor (private componentFactoryResolver: ComponentFactoryResolver) { }

View File

@@ -0,0 +1,22 @@
import { ConfigProvider, Platform } from 'terminus-core'
export class SettingsConfigProvider extends ConfigProvider {
defaults = { }
platformDefaults = {
[Platform.macOS]: {
hotkeys: {
settings: ['⌘-,'],
}
},
[Platform.Windows]: {
hotkeys: {
settings: ['Ctrl-,']
}
},
[Platform.Linux]: {
hotkeys: {
settings: ['Ctrl-,']
}
},
}
}

View File

@@ -0,0 +1,16 @@
import { Injectable } from '@angular/core'
import { IHotkeyDescription, HotkeyProvider } from 'terminus-core'
@Injectable()
export class SettingsHotkeyProvider extends HotkeyProvider {
hotkeys: IHotkeyDescription[] = [
{
id: 'settings',
name: 'Open Settings',
},
]
async provide (): Promise<IHotkeyDescription[]> {
return this.hotkeys
}
}

View File

@@ -4,8 +4,7 @@ import { FormsModule } from '@angular/forms'
import { NgbModule } from '@ng-bootstrap/ng-bootstrap'
import { NgPipesModule } from 'ngx-pipes'
import { ToolbarButtonProvider, TabRecoveryProvider } from 'terminus-core'
import TerminusCorePlugin from 'terminus-core'
import TerminusCorePlugin, { ToolbarButtonProvider, TabRecoveryProvider, HotkeyProvider, ConfigProvider } from 'terminus-core'
import { HotkeyInputModalComponent } from './components/hotkeyInputModal.component'
import { MultiHotkeyInputComponent } from './components/multiHotkeyInput.component'
@@ -14,6 +13,8 @@ import { SettingsTabBodyComponent } from './components/settingsTabBody.component
import { ButtonProvider } from './buttonProvider'
import { RecoveryProvider } from './recoveryProvider'
import { SettingsHotkeyProvider } from './hotkeys'
import { SettingsConfigProvider } from './config'
@NgModule({
imports: [
@@ -25,7 +26,9 @@ import { RecoveryProvider } from './recoveryProvider'
],
providers: [
{ provide: ToolbarButtonProvider, useClass: ButtonProvider, multi: true },
{ provide: TabRecoveryProvider, useClass: RecoveryProvider, multi: true }
{ provide: TabRecoveryProvider, useClass: RecoveryProvider, multi: true },
{ provide: ConfigProvider, useClass: SettingsConfigProvider, multi: true },
{ provide: HotkeyProvider, useClass: SettingsHotkeyProvider, multi: true },
],
entryComponents: [
HotkeyInputModalComponent,
@@ -38,8 +41,7 @@ import { RecoveryProvider } from './recoveryProvider'
SettingsTabBodyComponent,
],
})
export default class SettingsModule {
}
export default class SettingsModule { }
export * from './api'
export { SettingsTabComponent }

View File

@@ -6,6 +6,7 @@ module.exports = {
entry: 'src/index.ts',
devtool: 'source-map',
context: __dirname,
mode: 'development',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'index.js',

View File

@@ -36,7 +36,8 @@
},
"optionalDependencies": {
"wincredmgr": "^2.0.0",
"xkeychain": "^0.0.6"
"xkeychain": "^0.0.6",
"windows-process-tree": "^0.2.3"
},
"dependencies": {
"ssh2": "^0.5.5"

View File

@@ -56,7 +56,7 @@
)
.input-group-btn
button.btn.btn-secondary((click)='selectPrivateKey()')
i.fa.fa-folder-open
i.fas.fa-folder-open
ngb-tab(id='advanced')
ng-template(ngbTabTitle)
@@ -119,11 +119,11 @@
td
.input-group.flex-nowrap
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)')
i.fa.fa-arrow-down
i.fas.fa-arrow-down
button.btn.btn-outline-danger.ml-0((click)='deleteScript(script)')
i.fa.fa-trash-o
i.fas.fa-trash
tr
td
input.form-control(
@@ -148,9 +148,9 @@
td
.input-group.flex-nowrap
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()')
i.fa.fa-trash-o
i.fas.fa-trash
.modal-footer
button.btn.btn-outline-primary((click)='save()') Save

View File

@@ -76,7 +76,7 @@ export class EditConnectionModalComponent {
if (!this.connection.scripts) {
this.connection.scripts = []
}
this.connection.scripts.push({...this.newScript})
this.connection.scripts.push({ ...this.newScript })
this.clearScript()
}

View File

@@ -9,11 +9,13 @@
)
.list-group.mt-3(*ngIf='lastConnection')
a.list-group-item.list-group-item-action((click)='connect(lastConnection)')
i.fa.fa-fw.fa-history
span {{lastConnection.name}}
a.list-group-item.list-group-item-action.d-flex.align-items-center((click)='connect(lastConnection)')
i.fas.fa-fw.fa-history
.mr-auto {{lastConnection.name}}
button.btn.btn-outline-danger.btn-sm((click)='clearLastConnection(); $event.stopPropagation()')
i.fas.fa-trash
.list-group.mt-3.connections-list
.list-group.mt-3.connections-list(*ngIf='childGroups.length')
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]'
@@ -25,4 +27,6 @@
.list-group-item.list-group-item-action.pl-5.d-flex.align-items-center(
*ngFor='let connection of group.connections',
(click)='connect(connection)'
) {{connection.name}}
)
.mr-2 {{connection.name}}
.text-muted {{connection.host}}

View File

@@ -54,6 +54,11 @@ export class SSHModalComponent {
this.connect(connection)
}
clearLastConnection () {
window.localStorage.lastConnection = null
this.lastConnection = null
}
connect (connection: SSHConnection) {
this.close()
this.ssh.connect(connection).catch(error => {

View File

@@ -1,25 +1,28 @@
h3 Connections
.list-group.mt-3.mb-3
.list-group.list-group-flush.mt-3.mb-3
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-down(*ngIf='!groupCollapsed[group.name]')
span.ml-3.mr-auto {{group.name || "Ungrouped"}}
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)')
i.fa.fa-trash-o
i.fas.fa-trash
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
div {{connection.name}}
.text-muted {{connection.host}}
button.btn.btn-outline-info.ml-2((click)='editConnection(connection)')
i.fa.fa-pencil
button.btn.btn-outline-danger.ml-1((click)='deleteConnection(connection)')
i.fa.fa-trash-o
button.btn.btn-outline-danger.ml-1((click)='$event.stopPropagation(); deleteConnection(connection)')
i.fas.fa-trash
button.btn.btn-outline-primary((click)='createConnection()')
div.fa.fa-fw.fa-globe
button.btn.btn-primary((click)='createConnection()')
i.fas.fa-fw.fa-plus
span.ml-2 Add connection

View File

@@ -3,16 +3,13 @@ import { CommonModule } from '@angular/common'
import { FormsModule } from '@angular/forms'
import { NgbModule } from '@ng-bootstrap/ng-bootstrap'
import { ToastrModule } from 'ngx-toastr'
import { ToolbarButtonProvider, ConfigProvider } from 'terminus-core'
import TerminusCoreModule from 'terminus-core'
import TerminusCoreModule, { ToolbarButtonProvider, ConfigProvider } from 'terminus-core'
import { SettingsTabProvider } from 'terminus-settings'
import { EditConnectionModalComponent } from './components/editConnectionModal.component'
import { SSHModalComponent } from './components/sshModal.component'
import { PromptModalComponent } from './components/promptModal.component'
import { SSHSettingsTabComponent } from './components/sshSettingsTab.component'
import { SSHService } from './services/ssh.service'
import { PasswordStorageService } from './services/passwordStorage.service'
import { ButtonProvider } from './buttonProvider'
import { SSHConfigProvider } from './config'
@@ -27,8 +24,6 @@ import { SSHSettingsTabProvider } from './settings'
TerminusCoreModule,
],
providers: [
PasswordStorageService,
SSHService,
{ provide: ToolbarButtonProvider, useClass: ButtonProvider, multi: true },
{ provide: ConfigProvider, useClass: SSHConfigProvider, multi: true },
{ provide: SettingsTabProvider, useClass: SSHSettingsTabProvider, multi: true },

View File

@@ -13,7 +13,7 @@ try {
}
}
@Injectable()
@Injectable({ providedIn: 'root' })
export class PasswordStorageService {
constructor (
private zone: NgZone,

View File

@@ -11,7 +11,13 @@ import { PromptModalComponent } from '../components/promptModal.component'
import { PasswordStorageService } from './passwordStorage.service'
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 {
private logger: Logger
@@ -67,7 +73,7 @@ export class SSHService {
let ssh = new Client()
let connected = false
let savedPassword: string = null
await new Promise((resolve, reject) => {
await new Promise(async (resolve, reject) => {
ssh.on('ready', () => {
connected = true
if (savedPassword) {
@@ -99,7 +105,14 @@ export class SSHService {
let agent: string = null
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 {
agent = process.env.SSH_AUTH_SOCK
}

View File

@@ -48,6 +48,7 @@ module.exports = {
'wincredmgr',
'path',
'ngx-toastr',
'windows-process-tree/build/Release/windows_process_tree.node',
/^rxjs/,
/^@angular/,
/^@ng-bootstrap/,

View File

@@ -1507,6 +1507,11 @@ ms@2.0.0:
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
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:
version "2.8.0"
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"
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:
version "0.0.2"
resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.2.tgz#b79669bb42ecb409f83d583cad52ca17eaa1643f"

View File

@@ -6,7 +6,7 @@
"terminus-builtin-plugin"
],
"main": "dist/index.js",
"typings": "dist/index.d.ts",
"typings": "dist/src/index.d.ts",
"scripts": {
"build": "webpack --progress --color --display-modules",
"watch": "webpack --progress --color --watch"
@@ -17,6 +17,7 @@
"author": "Eugene Pankov",
"license": "MIT",
"devDependencies": {
"@terminus-term/xterm": "3.8.4",
"@types/deep-equal": "^1.0.0",
"@types/mz": "0.0.31",
"@types/node": "7.0.12",
@@ -24,9 +25,8 @@
"dataurl": "0.1.0",
"deep-equal": "1.0.1",
"file-loader": "^0.11.2",
"rage-edit-tmp": "^1.1.0",
"rage-edit": "1.2.0",
"uuid": "^3.3.2",
"xterm": "^3.8.0",
"xterm-addon-ligatures-tmp": "^0.1.0-beta-1"
},
"peerDependencies": {
@@ -40,12 +40,10 @@
"terminus-settings": "*"
},
"dependencies": {
"@types/async-lock": "0.0.19",
"async-lock": "^1.0.0",
"@terminus-term/node-pty": "0.8.0-1",
"font-manager": "0.3.0",
"hterm-umdjs": "1.4.1",
"mz": "^2.6.0",
"node-pty-tmp": "0.7.2",
"ps-node": "^0.1.6",
"runes": "^0.4.2"
},

View File

@@ -1,4 +1,3 @@
import { Observable } from 'rxjs'
import { TerminalTabComponent } from './components/terminalTab.component'
export abstract class TerminalDecorator {
@@ -15,25 +14,19 @@ export interface ResizeEvent {
export interface SessionOptions {
name?: string
command?: string
args?: string[]
command: string
args: string[]
cwd?: string
env?: any
width?: number
height?: number
recoveryId?: string
recoveredTruePID$?: Observable<number>
pauseAfterExit?: boolean
runAsAdministrator?: boolean
}
export abstract class SessionPersistenceProvider {
abstract id: string
abstract displayName: string
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 Profile {
name: string,
sessionOptions: SessionOptions,
}
export interface ITerminalColorScheme {
@@ -48,6 +41,12 @@ export abstract class TerminalColorSchemeProvider {
abstract async getSchemes (): Promise<ITerminalColorScheme[]>
}
export abstract class TerminalContextMenuItemProvider {
weight: number
abstract async getItems (tab: TerminalTabComponent): Promise<Electron.MenuItemConstructorOptions[]>
}
export interface IShell {
id: string
name?: string

View File

@@ -11,8 +11,11 @@ module.exports = function patchPTYModule (mod) {
let lastFlush = 0
let nextTimeout = 0
const maxWindow = 250
const minWindow = 50
// Minimum prebuffering window (ms) if the input is non-stop flowing
const minWindow = 10
// Maximum buffering time (ms) until output must be flushed unconditionally
const maxWindow = 100
function flush () {
if (buffer) {
@@ -36,9 +39,11 @@ module.exports = function patchPTYModule (mod) {
terminal.on('data', data => {
buffer += data
if (Date.now() - lastFlush > maxWindow) {
// Taking too much time buffering, flush to keep things interactive
flush()
} else {
if (Date.now() > nextTimeout - (minWindow / 10)) {
if (Date.now() > nextTimeout - (maxWindow / 10)) {
// Extend the window if it's expiring
reschedule()
}
}

View File

@@ -57,7 +57,7 @@ h3.mb-3 Appearance
(click)='deleteScheme(config.store.terminal.colorScheme)',
*ngIf='isCustomScheme(config.store.terminal.colorScheme)'
)
i.fa.fa-trash-o
i.fas.fa-trash
.form-group(*ngIf='editingColorScheme')
label Editing

View File

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

View File

@@ -0,0 +1,34 @@
import { Component } from '@angular/core'
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
import { UACService } from '../services/uac.service'
import { Profile } from '../api'
@Component({
template: require('./editProfileModal.component.pug'),
})
export class EditProfileModalComponent {
profile: Profile
constructor (
public uac: UACService,
private modalInstance: NgbActiveModal,
) {
}
ngOnInit () {
this.profile.sessionOptions.env = this.profile.sessionOptions.env || {}
this.profile.sessionOptions.args = this.profile.sessionOptions.args || []
}
save () {
this.modalInstance.close(this.profile)
}
cancel () {
this.modalInstance.dismiss()
}
trackByIndex (index) {
return index
}
}

View File

@@ -0,0 +1,12 @@
.mb-2.d-flex.align-items-center(*ngFor='let pair of vars')
.input-group.w-50
input.form-control([(ngModel)]='pair.key', (blur)='emitUpdate()', placeholder='Variable name')
.input-group-append
.input-group-text =
input.form-control.w-50.mr-1([(ngModel)]='pair.value', (blur)='emitUpdate()', placeholder='Value')
button.btn.btn-secondary((click)='removeEnvironmentVar(pair.key)')
i.fas.fa-trash
button.btn.btn-secondary((click)='addEnvironmentVar()')
i.fas.fa-plus.mr-2
span Add

View File

@@ -0,0 +1,3 @@
:host {
display: block;
}

View File

@@ -0,0 +1,45 @@
import { Component, Output, Input } from '@angular/core'
import { Subject } from 'rxjs'
@Component({
selector: 'environment-editor',
template: require('./environmentEditor.component.pug'),
styles: [require('./environmentEditor.component.scss')],
})
export class EnvironmentEditorComponent {
@Output() modelChange = new Subject<any>()
vars: {key: string, value: string}[] = []
private cachedModel: any
@Input() get model (): any {
return this.cachedModel
}
set model (value) {
this.vars = Object.entries(value).map(([k, v]) => ({ key: k, value: v as string }))
this.cachedModel = this.getModel()
}
getModel () {
let model = {}
for (let pair of this.vars) {
model[pair.key] = pair.value
}
return model
}
emitUpdate () {
this.cachedModel = this.getModel()
this.modelChange.next(this.cachedModel)
}
addEnvironmentVar () {
this.vars.push({ key: '', value: '' })
}
removeEnvironmentVar (key: string) {
this.vars = this.vars.filter(x => x.key !== key)
this.emitUpdate()
}
}

View File

@@ -14,6 +14,20 @@ h3.mb-3 Shell
[ngValue]='shell.id'
) {{shell.name}}
.form-line(*ngIf='isConPTYAvailable')
.header
.title Use ConPTY
.description Enables the experimental Windows ConPTY API
toggle(
[(ngModel)]='config.store.terminal.useConPTY',
(ngModelChange)='config.save()'
)
.alert.alert-info.d-flex.align-items-center(*ngIf='config.store.terminal.shell.startsWith("wsl") && (config.store.terminal.frontend != "hterm" || !config.store.terminal.useConPTY)')
.mr-auto WSL terminal only supports TrueColor with ConPTY and the hterm frontend
.form-line(*ngIf='config.store.terminal.shell == "custom"')
.header
.title Custom shell
@@ -24,20 +38,6 @@ h3.mb-3 Shell
(ngModelChange)='config.save()',
)
.form-line(*ngIf='persistenceProviders.length > 0')
.header
.title Session persistence
.description Restores tabs when Terminus is restarted
select.form-control(
[(ngModel)]='config.store.terminal.persistence',
(ngModelChange)='config.save()',
)
option([ngValue]='null') Off
option(
*ngFor='let provider of persistenceProviders',
[ngValue]='provider.id'
) {{provider.displayName}}
.form-line
.header
.title Working directory
@@ -50,20 +50,31 @@ h3.mb-3 Shell
)
.input-group-btn
button.btn.btn-secondary((click)='pickWorkingDirectory()')
i.fa.fa-folder-open
i.fas.fa-folder-open
.form-line
.form-line.align-items-start
.header
.title Environment
.description Inject additional environment variables
div
.mb-2.d-flex.align-items-center(*ngFor='let pair of environmentVars')
input.form-control.w-50([(ngModel)]='pair.key', (blur)='saveEnvironment()', placeholder='Variable name')
input.form-control.w-50.mr-1([(ngModel)]='pair.value', (blur)='saveEnvironment()', placeholder='Value')
button.btn.btn-secondary((click)='removeEnvironmentVar(pair.key)')
i.fa.fa-trash-o
button.btn.btn-secondary((click)='addEnvironmentVar()')
i.fa.fa-plus.mr-2
span Add
environment-editor([(model)]='this.config.store.terminal.environment')
h3.mt-3 Saved Profiles
.list-group.list-group-flush.mt-3.mb-3
.list-group-item.list-group-item-action.d-flex.align-items-center(
*ngFor='let profile of profiles',
(click)='editProfile(profile)',
)
.mr-auto
div {{profile.name}}
.text-muted {{profile.sessionOptions.command}}
button.btn.btn-outline-danger.ml-1((click)='$event.stopPropagation(); deleteProfile(profile)')
i.fas.fa-trash
div(ngbDropdown, placement='top-left')
button.btn.btn-primary(ngbDropdownToggle)
i.fas.fa-fw.fa-plus
| New profile
div(ngbDropdownMenu)
button.dropdown-item(*ngFor='let shell of shells', (click)='newProfile(shell)') {{shell.name}}

View File

@@ -1,39 +1,50 @@
import { Component, Inject } from '@angular/core'
import { Component } from '@angular/core'
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
import { Subscription } from 'rxjs'
import { ConfigService, ElectronService } from 'terminus-core'
import { IShell, ShellProvider, SessionPersistenceProvider } from '../api'
import { ConfigService, ElectronService, HostAppService, Platform } from 'terminus-core'
import { EditProfileModalComponent } from './editProfileModal.component'
import { IShell, Profile } from '../api'
import { TerminalService } from '../services/terminal.service'
import { UACService } from '../services/uac.service'
@Component({
template: require('./shellSettingsTab.component.pug'),
})
export class ShellSettingsTabComponent {
shells: IShell[] = []
persistenceProviders: SessionPersistenceProvider[]
environmentVars: {key: string, value: string}[] = []
profiles: Profile[] = []
Platform = Platform
isConPTYAvailable: boolean
private configSubscription: Subscription
constructor (
uac: UACService,
public config: ConfigService,
public hostApp: HostAppService,
private electron: ElectronService,
@Inject(ShellProvider) private shellProviders: ShellProvider[],
@Inject(SessionPersistenceProvider) persistenceProviders: SessionPersistenceProvider[],
private terminalService: TerminalService,
private ngbModal: NgbModal,
) {
this.persistenceProviders = this.config.enabledServices(persistenceProviders).filter(x => x.isAvailable())
config.store.terminal.environment = config.store.terminal.environment || {}
this.reloadEnvironment()
this.configSubscription = config.changed$.subscribe(() => this.reloadEnvironment())
this.configSubscription = this.config.changed$.subscribe(() => {
this.reload()
})
this.reload()
this.isConPTYAvailable = uac.isAvailable
}
async ngOnInit () {
this.shells = (await Promise.all(this.config.enabledServices(this.shellProviders).map(x => x.provide()))).reduce((a, b) => a.concat(b))
this.shells = await this.terminalService.shells$.toPromise()
}
ngOnDestroy () {
this.configSubscription.unsubscribe()
}
reload () {
this.profiles = this.config.store.terminal.profiles
}
pickWorkingDirectory () {
let shell = this.shells.find(x => x.id === this.config.store.terminal.shell)
console.log(shell)
@@ -46,23 +57,28 @@ export class ShellSettingsTabComponent {
}
}
reloadEnvironment () {
this.environmentVars = Object.entries(this.config.store.terminal.environment).map(([k, v]) => ({ key: k, value: v as string }))
}
saveEnvironment () {
this.config.store.terminal.environment = {}
for (let pair of this.environmentVars) {
this.config.store.terminal.environment[pair.key] = pair.value
newProfile (shell: IShell) {
let profile: Profile = {
name: shell.name,
sessionOptions: this.terminalService.optionsFromShell(shell),
}
this.profiles.push(profile)
this.config.store.terminal.profiles = this.profiles
this.config.save()
}
addEnvironmentVar () {
this.environmentVars.push({ key: '', value: '' })
editProfile (profile: Profile) {
let modal = this.ngbModal.open(EditProfileModalComponent)
modal.componentInstance.profile = Object.assign({}, profile)
modal.result.then(result => {
Object.assign(profile, result)
this.config.save()
})
}
removeEnvironmentVar (key: string) {
this.environmentVars = this.environmentVars.filter(x => x.key !== key)
this.saveEnvironment()
deleteProfile (profile: Profile) {
this.profiles = this.profiles.filter(x => x !== profile)
this.config.store.terminal.profiles = this.profiles
this.config.save()
}
}

View File

@@ -82,6 +82,15 @@ h3.mb-3 Terminal
[(ngModel)]='config.store.terminal.copyOnSelect',
(ngModelChange)='config.save()',
)
.form-line
.header
.title Scroll on input
.description Scrolls the terminal to the bottom on user input
toggle(
[(ngModel)]='config.store.terminal.scrollOnInput',
(ngModelChange)='config.save()',
)
.form-line
.header

View File

@@ -4,12 +4,10 @@ import { ToastrService } from 'ngx-toastr'
import { Component, NgZone, Inject, Optional, ViewChild, HostBinding, Input } from '@angular/core'
import { AppService, ConfigService, BaseTabComponent, BaseTabProcess, ElectronService, HostAppService, HotkeysService, Platform } from 'terminus-core'
import { IShell } from '../api'
import { Session, SessionsService } from '../services/sessions.service'
import { TerminalService } from '../services/terminal.service'
import { TerminalFrontendService } from '../services/terminalFrontend.service'
import { TerminalDecorator, ResizeEvent, SessionOptions } from '../api'
import { TerminalDecorator, ResizeEvent, SessionOptions, TerminalContextMenuItemProvider } from '../api'
import { Frontend } from '../frontends/frontend'
@Component({
@@ -33,10 +31,8 @@ export class TerminalTabComponent extends BaseTabComponent {
sessionCloseSubscription: Subscription
hotkeysSubscription: Subscription
htermVisible = false
shell: IShell
private output = new Subject<string>()
private bellPlayer: HTMLAudioElement
private contextMenu: any
private termContainerSubscriptions: Subscription[] = []
get input$ (): Observable<string> { return this.frontend.input$ }
@@ -51,17 +47,17 @@ export class TerminalTabComponent extends BaseTabComponent {
private hotkeys: HotkeysService,
private sessions: SessionsService,
private electron: ElectronService,
private terminalService: TerminalService,
private terminalContainersService: TerminalFrontendService,
public config: ConfigService,
private toastr: ToastrService,
@Optional() @Inject(TerminalDecorator) private decorators: TerminalDecorator[],
@Optional() @Inject(TerminalContextMenuItemProvider) private contextMenuProviders: TerminalContextMenuItemProvider[],
) {
super()
this.decorators = this.decorators || []
this.setTitle('Terminal')
this.session = new Session()
this.session = new Session(this.config)
this.hotkeysSubscription = this.hotkeys.matchedHotkey.subscribe(hotkey => {
if (!this.hasFocus) {
@@ -118,6 +114,8 @@ export class TerminalTabComponent extends BaseTabComponent {
})
this.bellPlayer = document.createElement('audio')
this.bellPlayer.src = require<string>('../bell.ogg')
this.contextMenuProviders.sort((a, b) => a.weight - b.weight)
}
initializeSession (columns: number, rows: number) {
@@ -143,10 +141,14 @@ export class TerminalTabComponent extends BaseTabComponent {
})
}
getRecoveryToken (): any {
async getRecoveryToken (): Promise<any> {
let cwd = this.session ? await this.session.getWorkingDirectory() : null
return {
type: 'app:terminal',
recoveryId: this.sessionOptions.recoveryId,
type: 'app:terminal-tab',
sessionOptions: {
...this.sessionOptions,
cwd: cwd || this.sessionOptions.cwd,
},
}
}
@@ -162,7 +164,7 @@ export class TerminalTabComponent extends BaseTabComponent {
this.htermVisible = true
})
this.frontend.resize$.pipe(first()).subscribe(async ({columns, rows}) => {
this.frontend.resize$.pipe(first()).subscribe(async ({ columns, rows }) => {
if (!this.session.open) {
this.initializeSession(columns, rows)
}
@@ -199,35 +201,17 @@ export class TerminalTabComponent extends BaseTabComponent {
}
})
this.contextMenu = [
{
label: 'New terminal',
click: () => {
this.zone.run(() => {
this.terminalService.openTab(this.shell)
})
}
},
{
label: 'Copy',
click: () => {
this.zone.run(() => {
setTimeout(() => {
this.frontend.copySelection()
this.toastr.info('Copied')
})
})
}
},
{
label: 'Paste',
click: () => {
this.zone.run(() => {
this.paste()
})
}
},
]
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 () {
@@ -245,11 +229,11 @@ export class TerminalTabComponent extends BaseTabComponent {
this.focused$.subscribe(() => this.frontend.enableResizing = true),
this.blurred$.subscribe(() => this.frontend.enableResizing = false),
this.frontend.mouseEvent$.subscribe(event => {
this.frontend.mouseEvent$.subscribe(async event => {
if (event.type === 'mousedown') {
if (event.which === 3) {
if (this.config.store.terminal.rightClick === 'menu') {
this.hostApp.popupContextMenu(this.contextMenu)
this.hostApp.popupContextMenu(await this.buildContextMenu())
} else if (this.config.store.terminal.rightClick === 'paste') {
this.paste()
}
@@ -259,15 +243,23 @@ export class TerminalTabComponent extends BaseTabComponent {
}
}
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 ((event as MouseWheelEvent).wheelDeltaY > 0) {
if (wheelDeltaY > 0) {
this.zoomIn()
} else {
this.zoomOut()
}
} else if (event.altKey) {
event.preventDefault()
let delta = Math.round((event as MouseWheelEvent).wheelDeltaY / 50)
let delta = Math.round(wheelDeltaY / 50)
this.sendInput(((delta > 0) ? '\u001bOA' : '\u001bOB').repeat(Math.abs(delta)))
}
}
@@ -277,7 +269,7 @@ export class TerminalTabComponent extends BaseTabComponent {
this.sendInput(data)
}),
this.frontend.resize$.subscribe(({columns, rows}) => {
this.frontend.resize$.subscribe(({ columns, rows }) => {
console.log(`Resizing to ${columns}x${rows}`)
this.zone.run(() => {
if (this.session.open) {
@@ -290,7 +282,9 @@ export class TerminalTabComponent extends BaseTabComponent {
sendInput (data: string) {
this.session.write(data)
this.frontend.scrollToBottom()
if (this.config.store.terminal.scrollOnInput) {
this.frontend.scrollToBottom()
}
}
write (data: string) {
@@ -384,4 +378,20 @@ export class TerminalTabComponent extends BaseTabComponent {
}
return confirm(`"${children[0].command}" is still running. Close?`)
}
async saveAsProfile () {
let profile = {
sessionOptions: {
...this.sessionOptions,
cwd: (await this.session.getWorkingDirectory()) || this.sessionOptions.cwd,
},
name: this.sessionOptions.command,
}
this.config.store.terminal.profiles = [
...this.config.store.terminal.profiles,
profile,
]
this.config.save()
this.toastr.info('Saved')
}
}

Some files were not shown because too many files have changed in this diff Show More