Compare commits

..

195 Commits

Author SHA1 Message Date
Eugene Pankov
d42e070e6c use new style xterm events 2019-04-17 22:03:20 +02:00
Eugene Pankov
c5958bc9a0 bumped xterm for true color support (#23) 2019-04-17 21:57:52 +02:00
Eugene Pankov
9dc2337787 hotkey naming 2019-04-16 15:42:11 +02:00
Eugene
1a38cc30a8 Merge pull request #766 from Mezner/master
Create close pane hotkey.
2019-04-16 15:39:22 +02:00
Russell Myers
4949f14184 Create close pane hotkey. 2019-04-15 21:16:54 -04:00
Eugene
11902020a5 Merge pull request #743 from mischah/relaxed-theme
Add Relaxed color scheme
2019-03-30 08:30:05 -07:00
Michael Kühnel
3f96c21f33 Add Relaxed color scheme 2019-03-30 16:22:20 +01:00
Eugene
9e81f0aa0e Update README.md 2019-03-19 13:26:06 +01:00
Eugene
1cce23cef5 Update README.md 2019-03-19 13:02:33 +01:00
Eugene Pankov
b7a56adb60 smaller icons for the compact theme 2019-03-18 22:34:46 +01:00
Eugene Pankov
eb02752cbf use dynamic xterm atlas for faster startup 2019-03-18 22:23:44 +01:00
Eugene Pankov
3a6eb8cb2f bumped plugin versions 2019-03-18 22:02:23 +01:00
Eugene Pankov
a7d62b0234 fixed home/end keys with conpty (fixes #587) 2019-03-18 22:00:43 +01:00
Eugene
b9cbe4f12d Update snapcraft.yaml 2019-03-13 12:45:41 +01:00
Eugene
5f74b35ba9 Update snapcraft.yaml 2019-03-13 12:39:14 +01:00
Eugene
affb439ab2 Update snapcraft.yaml 2019-03-13 12:33:48 +01:00
Eugene Pankov
afdb06df40 fixed npm require path (fixes #714) 2019-03-11 14:01:51 +01:00
Eugene Pankov
2227f2cff3 bumped node-pty 2019-03-08 13:11:01 +01:00
Eugene Pankov
1a46f0ec3c fixed cwd detection for split tabs 2019-03-08 12:35:14 +01:00
Eugene Pankov
e246e22bfd set TERM_PROGRAM (fixes #702) 2019-03-08 12:27:57 +01:00
Eugene Pankov
89e4a80a37 fixed xterm hotkeys - fixed #696 2019-03-08 12:24:42 +01:00
Eugene
3a522f4f73 Update README.md 2019-03-08 08:32:01 +01:00
Eugene Pankov
a01238d34c travis 2019-03-07 23:17:44 +01:00
Eugene Pankov
75fea5e390 Merge branch 'master' of github.com:Eugeny/terminus 2019-03-07 22:38:26 +01:00
Eugene Pankov
af792399da travis 2019-03-07 22:38:05 +01:00
Eugene
7d86a7e4cc Update .travis.yml 2019-03-07 21:31:49 +01:00
Eugene Pankov
cf6a7d10b3 travis 2019-03-07 19:27:38 +01:00
Eugene Pankov
4bc03319e7 travis 2019-03-07 18:15:52 +01:00
Eugene Pankov
8cf7851801 autogen docs 2019-03-07 18:04:03 +01:00
Eugene Pankov
c70e6fde35 prep other plugins for typedoc 2019-03-07 02:05:26 +01:00
Eugene Pankov
2ed35cb400 prepared terminus-core for typedocs 2019-03-07 01:51:15 +01:00
Eugene Pankov
cf5e31be79 angular bump 2019-03-06 23:46:08 +01:00
Eugene Pankov
2c4c094941 splits API 2019-03-06 23:46:04 +01:00
Eugene Pankov
d25751abe7 handle split pane being closed 2019-03-04 21:26:28 +01:00
Eugene Pankov
bcc4a262e2 build fix 2019-03-04 21:03:36 +01:00
Eugene Pankov
27fb611166 splitting tabs (fixes #49) 2019-03-04 20:39:05 +01:00
Eugene Pankov
7279ba13ac split tab recovery (#49) 2019-03-03 23:51:25 +01:00
Eugene Pankov
70b463b086 splits WIP (#49) 2019-03-03 22:56:58 +01:00
Eugene Pankov
ef4e1e5a0d registry fixes 2019-03-02 17:44:02 +01:00
Eugene Pankov
04d621d62b use native registry implementation 2019-03-02 15:56:58 +01:00
Eugene Pankov
dc813b8edb windows fix 2019-03-02 15:34:18 +01:00
Eugene Pankov
426bedd7b7 restored readme images 2019-03-01 18:10:55 +01:00
Eugene Pankov
34b7c80243 website moved to gh-pages branch 2019-02-24 15:07:14 +01:00
Eugene Pankov
33281b5caf skip unnecessary ops 2019-02-24 14:39:05 +01:00
Eugene Pankov
e6e88272c2 force yarn relink on travis 2019-02-20 10:51:32 +01:00
Eugene Pankov
05bb07e37d bumped electron 2019-02-20 01:57:38 +01:00
Eugene Pankov
61726cad36 parse iTerm OSC 1337 cwd reports 2019-02-20 01:07:05 +01:00
Eugene Pankov
77c253594f don't offer separate wsl distros until Win 10 17763 (fixes #642) 2019-02-20 00:04:06 +01:00
Eugene
478d715d10 Gitter link 2019-02-19 00:26:31 +01:00
Eugene Pankov
1675312f75 added hotkeys to open specific profiles 2019-02-17 13:12:05 +01:00
Eugene Pankov
128aa618f0 avoid importing npm until required 2019-02-17 13:02:56 +01:00
Eugene Pankov
6b56155ca5 fixed #517 2019-02-17 12:32:37 +01:00
Eugene Pankov
3ee1f1b023 Update readme.png 2019-02-13 23:12:47 +01:00
Eugene Pankov
db54f4af13 a button to show config file location 2019-02-13 22:58:47 +01:00
Eugene Pankov
59d1a2fc23 bundle NPM with Terminus (fixes #584, fixes #10) 2019-02-13 21:35:37 +01:00
Eugene Pankov
808e7f4699 build fix 2019-02-12 13:02:59 +01:00
Eugene Pankov
e338bc417a only configure tabs if they're focused (fixes #659) 2019-02-12 12:44:23 +01:00
Eugene Pankov
3039a65757 fixed conpty detection (fixes #653) 2019-02-12 11:19:17 +01:00
Eugene Pankov
714f181be5 try to force terminus onto the low-power gpu 2019-02-11 15:48:16 +01:00
Eugene Pankov
329d0448d3 reconfigure terminals on DPI change (fixes #576) 2019-02-10 00:23:49 +01:00
Eugene Pankov
100436f511 set xterm as default frontend (fixes #542) 2019-02-09 22:40:31 +01:00
Eugene Pankov
22d3e35723 ignore events on destroyed windows 2019-02-09 22:38:45 +01:00
Eugene Pankov
9cdcc8d8e5 fixed #649 2019-02-09 22:10:42 +01:00
Eugene Pankov
168e6f17dc allow selecting ssh ciphers (fixes #645) 2019-02-09 18:52:09 +01:00
Eugene Pankov
a2c636fdbf console logging 2019-02-09 17:44:23 +01:00
Eugene Pankov
413ca70729 Warn when enabling ConPTY on older insider builds (fixes #609, fixes #594) 2019-02-09 17:44:17 +01:00
Eugene Pankov
6f99e6c14b Merge branch 'master' of github.com:Eugeny/terminus 2019-01-30 19:46:25 +01:00
Eugene Pankov
e65811786d bumped node-ssh2 (fixes #605) 2019-01-30 18:11:49 +01:00
Eugene
aecd381b25 Merge pull request #633 from sylveon/master
Update windows-swca dependency
2019-01-30 16:48:11 +01:00
Eugene Pankov
89465f57d5 bumped node-pty 2019-01-30 13:02:35 +01:00
Charles Milette
3bf0ac43ef Update yarn lockfile 2019-01-29 15:37:40 -05:00
Charles Milette
a66dd43e1e Update windows-swca dependency 2019-01-29 15:33:50 -05:00
Eugene Pankov
dd4566cf02 #618 fixes 2019-01-27 23:58:55 +01:00
Eugene Pankov
f2be34d137 limit max font size (fixes #618) 2019-01-27 23:40:33 +01:00
Eugene Pankov
e28c619bdc force en-us locale (#618) 2019-01-27 23:39:05 +01:00
Eugene Pankov
04bf5dbcfb fixed offset with tabs on bottom on macos (fixes #629) 2019-01-27 23:12:46 +01:00
Eugene Pankov
a2128ca1f2 use ssh connection name for the tab's title (fixes #621) 2019-01-27 22:56:50 +01:00
Eugene Pankov
bf0d02d1fc tab duplication (fixes #588) 2019-01-27 22:45:08 +01:00
Eugene Pankov
792de65696 properly recover tabs with xterm 2019-01-27 22:01:55 +01:00
Eugene Pankov
fab21f6859 mention save-output plugin 2019-01-27 22:01:46 +01:00
Eugene
b0b01b98be Update README.md 2019-01-21 15:52:55 +01:00
Eugene
24dff4b5b7 Update README.md 2019-01-18 13:56:08 +00:00
Eugene Pankov
78f8f4005e fixed #610 2019-01-16 17:13:34 +00:00
Eugene Pankov
38cfb3f036 middle click to paste (fixes #613) 2019-01-16 16:46:01 +00:00
Eugene Pankov
4e4d8a0e91 bumped node-pty 2019-01-16 16:25:43 +00:00
Eugene Pankov
21cfd14f1c use the upstream xtermjs 2019-01-16 16:16:06 +00:00
Eugene Pankov
a64bbe145c fixed automatic resizing with xterm 2019-01-16 15:23:55 +00:00
Eugene Pankov
6a5dc79c5d bumped plugin versions 2019-01-10 12:44:12 +01:00
Eugene Pankov
b799128427 fixed TerminalContextMenuProvider typing 2019-01-10 12:44:07 +01:00
Eugene Pankov
8b64a819e7 expose DOM element ref from BaseTerminalTab 2019-01-08 16:37:54 +03:00
Eugene Pankov
5b78a5c1ed made tab context menu extensible 2019-01-07 19:30:03 +03:00
Eugene Pankov
91b318853f replace the stock installer gif (fixes #606) 2019-01-07 17:31:16 +03:00
Eugene Pankov
ce3610c2da automatically recover ssh tabs (fixes #583) 2019-01-06 11:54:26 +01:00
Eugene Pankov
d03430fb2e ssh - show connection log while connecting 2019-01-06 11:14:13 +01:00
Eugene Pankov
caacc01aea split common terminal behaviour into BaseTerminalTab 2019-01-05 16:54:22 +01:00
Eugene Pankov
bcb6963c35 show ssh connection errors 2019-01-05 15:19:02 +01:00
Eugene Pankov
deb99b0865 wrap TerminalTab into SSHTab 2019-01-05 15:17:41 +01:00
Eugene Pankov
2101c18657 fixed saving ssh connections (fixes #436) 2019-01-05 15:03:31 +01:00
Eugene Pankov
1a258f32b0 fixed npm detection when fish is the default shell (#584) 2019-01-05 14:53:19 +01:00
Eugene Pankov
3aaf490f57 fixed #597 2019-01-05 14:51:36 +01:00
Eugene Pankov
9faa346699 better messageboxes 2019-01-03 17:20:02 +03:00
Eugene Pankov
d5b6a686f8 added settings tab icons 2019-01-03 17:19:50 +03:00
Eugene Pankov
492d006f64 xterm scrollback fix 2019-01-03 17:07:38 +03:00
Eugene Pankov
d999320c24 bumped plugin versions 2019-01-03 13:08:57 +03:00
Eugene Pankov
5142d12e7e fixed macos zip artifact naming 2019-01-03 13:01:15 +03:00
Eugene Pankov
453c613571 bumped xterm scrollback size (fixes #589) 2019-01-03 12:55:14 +03:00
Eugene Pankov
ccc34ae4d9 bumped angular to rc 2018-12-30 17:53:07 +01:00
Eugene Pankov
4362b5c50b bumped node-gyp 2018-12-30 17:44:52 +01:00
Eugene Pankov
2d6023446c bumped electron-builder 2018-12-30 17:35:09 +01:00
Eugene Pankov
dcd43dc019 fixed the Preferences menu item 2018-12-30 17:32:30 +01:00
Eugene Pankov
d8e70f9693 bumped nodejs on travis 2018-12-30 17:26:01 +01:00
Eugene Pankov
7a26e8bd65 ignore non-existent CWDs (fixes #586) 2018-12-30 15:59:40 +01:00
Eugene Pankov
d56287587c bumped electron to stable 2018-12-30 15:54:17 +01:00
Eugene Pankov
8793613117 potentially fixed #576 2018-12-29 13:27:45 +01:00
Eugene Pankov
92afec75e7 fixed plugin blacklisting 2018-12-29 12:50:14 +01:00
Eugene Pankov
ca71ec24f8 fixed #585 2018-12-29 12:41:32 +01:00
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
222 changed files with 8047 additions and 2713 deletions

4
.gitignore vendored
View File

@@ -5,6 +5,7 @@ node_modules
build/files.wxs
dist
*/dist
*.xcworkspacedata
*.xcuserstate
@@ -17,3 +18,6 @@ npm-debug.log
builtin-plugins
package-lock.json
yarn-error.log
docs/api
.travis.ssh.key

BIN
.travis.ssh.key.enc Normal file

Binary file not shown.

1
.travis.ssh.key.pub Normal file
View File

@@ -0,0 +1 @@
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDDFM4nHSbET5V7EYNgjA8NeVfOxV0wVMdZ2YvsDzD+qPJ4+MYbvsL7ZPaSxQSn7n6ATkLHjKje5RpF/Rl9K3kucGs0P6cqJVeE0qryEteQ3Q+fYAk+bD2J9ZQ/hv/0NtLl8T+7lJUZ3WUxFH73sgph77Sw0z+kMpPaK7U2vqMBQD/7+6iJgya31wP0qW0XKDz1BjKeXgwTg10Pm4vcGsR4c2q7YIzSzBHffcyo0vJyFvOX/ZKHlZRcq/wnQMeOl/hPgf1xCENjQZmFVReQlYSw5cNNDT9HZPKekOAZFFez7/AbPiTIo/bnBYIv0mdUjr3nw8nXF505q8LiD3z/ksaaWDqe9CCLM4W0Bh7/dhP7IGPdfX0fVHLhOnYIOsG21D8rWJjMPkVRSLyEvWNAnVuObJNHoQu8VATnOxfPNnMun72IHyyFWVoADk5JcsMbzcP7gZB+5oJO7U1qpcdndtBOA3ZlF0Uz2jVZnqavoEBWT39tl3vs69hAA3aTPGclg7HMuAJOl4HsKmaUgDxqV2wCX/S4pDqmKMbmumDLX+MM0xl0gXj/zpVJp9BzdnrArkC40ivmC6TSA4wrdN0tNBlqApkH5/jxGWrcu2AXVn9PGF3+QrjW0iu+QMZCaKWDhLIQC835uFwzhnNGlx41B7uxMLuNFxKXdQ3f/cC9QMG8ew== TravisCIDeployKey

View File

@@ -1,29 +1,48 @@
matrix:
include:
- os: linux
env: BUILD_FOR=linux
- os: osx
env: BUILD_FOR=macos
language: node_js
node_js: 8
node_js: 11
cache:
directories:
- node_modules
- app/node_modules
stages:
- Build
- name: Docs
if: branch = master
before_install:
- yarn install
- scripts/install-deps.js
jobs:
include:
- stage: 'Build'
os: linux
before_install:
- yarn
- rm app/node_modules/.yarn-integrity || true
- scripts/install-deps.js
script:
- scripts/build-native.js
- yarn run build
- scripts/prepackage-plugins.js
- scripts/build-linux.js
script:
- scripts/build-native.js
- yarn run build
- scripts/prepackage-plugins.js
- scripts/build-$BUILD_FOR.js
- stage: 'Build'
os: osx
before_install:
- rm app/node_modules/.yarn-integrity || true
- yarn
script:
- scripts/build-native.js
- yarn run build
- scripts/prepackage-plugins.js
- scripts/build-macos.js
dist: trusty
- stage: 'Docs'
os: linux
script:
- openssl aes-256-cbc -K $encrypted_4e2fb4889ef8_key -iv $encrypted_4e2fb4889ef8_iv -in .travis.ssh.key.enc -out .travis.ssh.key -d
- eval "$(ssh-agent -s)"
- chmod 600 .travis.ssh.key
- ssh-add .travis.ssh.key
- yarn
- yarn run docs
- rsync -e "ssh -o StrictHostKeyChecking=no" -arv docs/api/ root@ajenti.org:/srv/terminus-docs/
dist: xenial
sudo: false
addons:

View File

@@ -1,30 +1,36 @@
![](https://github.com/Eugeny/terminus/raw/master/docs/readme.png)
<p align="center">
<a href="https://raw.githubusercontent.com/Eugeny/terminus/master/LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue.svg"/></a> <a href="https://travis-ci.org/Eugeny/terminus"><img src="https://travis-ci.org/Eugeny/terminus.svg?branch=master"/></a>
<a href="https://ci.appveyor.com/project/Eugeny/terminus"><img src="https://ci.appveyor.com/api/projects/status/wnnq4hm5mbd9rgoy?svg=true"/></a>
<a href="https://raw.githubusercontent.com/Eugeny/terminus/master/LICENSE"><img alt="GitHub" src="https://img.shields.io/github/license/eugeny/terminus.svg?label=License&style=flat-square"></a> <a href="https://travis-ci.org/Eugeny/terminus"><img alt="Travis (.org)" src="https://img.shields.io/travis/Eugeny/terminus.svg?label=CI&logo=travis&logoColor=white&style=flat-square"></a>
<a href="https://ci.appveyor.com/project/Eugeny/terminus"><img alt="AppVeyor" src="https://img.shields.io/appveyor/ci/eugeny/terminus.svg?label=CI&logo=appveyor&logoColor=white&style=flat-square"></a>
</p>
<p align="center">
<a href="https://github.com/Eugeny/terminus/releases/latest">Downloads</a> | <a href="https://t.me/joinchat/AAAAAEZuCv2WKKYcfyQ3QA">Community</a> | <a href="https://ci.appveyor.com/project/Eugeny/terminus/build/artifacts">Latest Windows nightly</a>
<a href="https://github.com/Eugeny/terminus/releases/latest"><img alt="GitHub All Releases" src="https://img.shields.io/github/downloads/eugeny/terminus/total.svg?label=DOWNLOAD&logo=github&style=for-the-badge"></a> <a href="https://ci.appveyor.com/project/Eugeny/terminus/build/artifacts"><img src="https://img.shields.io/badge/download-nightly%20build-magenta.svg?logo=appveyor&style=for-the-badge"/></a> <a href="https://gitter.im/terminus-terminal/community"><img alt="Gitter" src="https://img.shields.io/gitter/room/terminus/community.svg?color=blue&logo=gitter&style=for-the-badge"></a>
</p>
----
**Terminus** is a terminal heavily inspired by Hyper. It is, however, designed for people who need to get things done.
**Terminus** is a highly configurable terminal emulator for Windows, macOS and Linux
* Runs on Windows, macOS and Linux
* Theming and color schemes
* Fully configurable shortcuts
* Split panes
* Remembers your tabs
* PowerShell (and PS Core), WSL, Git-Bash, Cygwin, Cmder and CMD support
* Integrated SSH client and connection manager
* Full Unicode support including double-width characters
* Doesn't choke on fast-flowing outputs
* Proper shell-like experience on Windows including tab completion (via Clink)
* PowerShell Core, WSL (Bash on Windows), PowerShell, Git-Bash, Cygwin, Cmder and CMD support
* Tab persistence on macOS and Linux
* Proper shell experience on Windows including tab completion (via Clink)
[![Buy me a coffee](https://github.com/Eugeny/terminus/raw/master/docs/kofi.png)](https://ko-fi.com/eugeny)
---
* **Terminus is** an alternative to Windows' standard terminal (conhost), PowerShell ISE, PuTTY or iTerm
* **Terminus is not** a new shell or a MinGW or Cygwin replacement. Neither is it lightweight - if RAM usage is of importance, consider [Conemu](https://conemu.github.io) or [Alacritty](https://github.com/jwilm/alacritty)
---
@@ -37,7 +43,8 @@ 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
* [save-output](https://github.com/Eugeny/terminus-save-output) - record terminal output into a file
---
@@ -45,8 +52,7 @@ Plugins can be installed directly from the Settings view inside Terminus.
Pull requests and plugins are welcome!
See [HACKING.md](https://github.com/Eugeny/terminus/blob/master/HACKING.md) for information of how the project is laid out, and a very brief plugin development tutorial.
See [HACKING.md](https://github.com/Eugeny/terminus/blob/master/HACKING.md) and [API docs](http://ajenti.org/terminus-docs/) for information of how the project is laid out, and a very brief plugin development tutorial.
## License
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bhttps%3A%2F%2Fgithub.com%2FEugeny%2Fterminus.svg?type=large)](https://app.fossa.io/projects/git%2Bhttps%3A%2F%2Fgithub.com%2FEugeny%2Fterminus?ref=badge_large)

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,4 +1,5 @@
import { app, ipcMain, Menu, Tray, shell } from 'electron'
import * as electron from 'electron'
import { loadConfig } from './config'
import { Window, WindowOptions } from './window'
@@ -18,6 +19,12 @@ export class Application {
}
app.commandLine.appendSwitch('disable-http-cache')
app.commandLine.appendSwitch('force_discrete_gpu', '0')
app.commandLine.appendSwitch('lang', 'EN')
}
init () {
electron.screen.on('display-metrics-changed', () => this.broadcast('host:display-metrics-changed'))
}
async newWindow (options?: WindowOptions): Promise<Window> {
@@ -103,7 +110,7 @@ export class Application {
{
label: 'Preferences',
accelerator: 'Cmd+,',
async click () {
click: async () => {
if (!this.hasWindows()) {
await this.newWindow()
}

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',

View File

@@ -12,7 +12,6 @@ if (!process.env.TERMINUS_PLUGINS) {
const application = new Application()
ipcMain.on('app:new-window', () => {
console.log('new-window')
application.newWindow()
})
@@ -59,5 +58,6 @@ app.on('ready', () => {
}
]))
}
application.init()
application.newWindow({ hidden: argv.hidden })
})

View File

@@ -1,4 +1,5 @@
import { Subject, Observable } from 'rxjs'
import { debounceTime } from 'rxjs/operators'
import { BrowserWindow, app, ipcMain, Rectangle } from 'electron'
import ElectronConfig = require('electron-config')
import * as os from 'os'
@@ -10,7 +11,7 @@ let AccentState: any
let DwmEnableBlurBehindWindow: any
if (process.platform === 'win32') {
SetWindowCompositionAttribute = require('windows-swca').SetWindowCompositionAttribute
AccentState = require('windows-swca').AccentState
AccentState = require('windows-swca').ACCENT_STATE
DwmEnableBlurBehindWindow = require('windows-blurbehind').DwmEnableBlurBehindWindow
}
@@ -43,7 +44,9 @@ export class Window {
title: 'Terminus',
minWidth: 400,
minHeight: 300,
webPreferences: { webSecurity: false },
webPreferences: {
nodeIntegration: true,
},
frame: false,
show: false,
backgroundColor: '#00000000'
@@ -102,16 +105,14 @@ export class Window {
if (process.platform === 'win32') {
if (parseFloat(os.release()) >= 10) {
let attribValue = AccentState.ACCENT_DISABLED
let color = 0x00000000
if (enabled) {
if (parseInt(os.release().split('.')[2]) >= 17063 && type === 'fluent') {
attribValue = AccentState.ACCENT_ENABLE_FLUENT
color = 0x01000000 // using a small alpha because acrylic bugs out at full transparency.
attribValue = AccentState.ACCENT_ENABLE_ACRYLICBLURBEHIND
} else {
attribValue = AccentState.ACCENT_ENABLE_BLURBEHIND
}
}
SetWindowCompositionAttribute(this.window, attribValue, color)
SetWindowCompositionAttribute(this.window.getNativeWindowHandle(), attribValue, 0x00000000)
} else {
DwmEnableBlurBehindWindow(this.window, enabled)
}
@@ -143,6 +144,16 @@ export class Window {
this.visible.next(false)
})
let moveSubscription = new Observable<void>(observer => {
this.window.on('move', () => observer.next())
}).pipe(debounceTime(250)).subscribe(() => {
this.window.webContents.send('host:window-moved')
})
this.window.on('closed', () => {
moveSubscription.unsubscribe()
})
this.window.on('enter-full-screen', () => this.window.webContents.send('host:window-enter-full-screen'))
this.window.on('leave-full-screen', () => this.window.webContents.send('host:window-leave-full-screen'))
@@ -173,28 +184,28 @@ export class Window {
})
ipcMain.on('window-focus', event => {
if (event.sender !== this.window.webContents) {
if (!this.window || event.sender !== this.window.webContents) {
return
}
this.window.focus()
})
ipcMain.on('window-maximize', event => {
if (event.sender !== this.window.webContents) {
if (!this.window || event.sender !== this.window.webContents) {
return
}
this.window.maximize()
})
ipcMain.on('window-unmaximize', event => {
if (event.sender !== this.window.webContents) {
if (!this.window || event.sender !== this.window.webContents) {
return
}
this.window.unmaximize()
})
ipcMain.on('window-toggle-maximize', event => {
if (event.sender !== this.window.webContents) {
if (!this.window || event.sender !== this.window.webContents) {
return
}
if (this.window.isMaximized()) {
@@ -205,42 +216,42 @@ export class Window {
})
ipcMain.on('window-minimize', event => {
if (event.sender !== this.window.webContents) {
if (!this.window || event.sender !== this.window.webContents) {
return
}
this.window.minimize()
})
ipcMain.on('window-set-bounds', (event, bounds) => {
if (event.sender !== this.window.webContents) {
if (!this.window || event.sender !== this.window.webContents) {
return
}
this.window.setBounds(bounds)
})
ipcMain.on('window-set-always-on-top', (event, flag) => {
if (event.sender !== this.window.webContents) {
if (!this.window || event.sender !== this.window.webContents) {
return
}
this.window.setAlwaysOnTop(flag)
})
ipcMain.on('window-set-vibrancy', (event, enabled, type) => {
if (event.sender !== this.window.webContents) {
if (!this.window || event.sender !== this.window.webContents) {
return
}
this.setVibrancy(enabled, type)
})
ipcMain.on('window-set-title', (event, title) => {
if (event.sender !== this.window.webContents) {
if (!this.window || event.sender !== this.window.webContents) {
return
}
this.window.setTitle(title)
})
ipcMain.on('window-bring-to-front', event => {
if (event.sender !== this.window.webContents) {
if (!this.window || event.sender !== this.window.webContents) {
return
}
if (this.window.isMinimized()) {
@@ -250,7 +261,10 @@ export class Window {
this.window.moveTop()
})
ipcMain.on('window-close', () => {
ipcMain.on('window-close', event => {
if (!this.window || event.sender !== this.window.webContents) {
return
}
this.closing = true
this.window.close()
})

View File

@@ -13,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.8",
"@angular/common": "7.2.8",
"@angular/compiler": "7.2.8",
"@angular/core": "7.2.8",
"@angular/forms": "7.2.8",
"@angular/platform-browser": "7.2.8",
"@angular/platform-browser-dynamic": "7.2.8",
"@ng-bootstrap/ng-bootstrap": "^3.3.1",
"devtron": "1.4.0",
"electron-config": "0.2.1",
@@ -36,7 +36,7 @@
},
"optionalDependencies": {
"windows-blurbehind": "^1.0.0",
"windows-swca": "^1.1.1"
"windows-swca": "^2.0.1"
},
"devDependencies": {
"@types/mz": "0.0.31"

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

@@ -15,6 +15,8 @@ import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'
import { getRootModule } from './app.module'
import { findPlugins, loadPlugins, IPluginInfo } from './plugins'
;(process as any).enablePromiseAPI = true
if (process.platform === 'win32') {
process.env.HOME = process.env.HOMEDRIVE + process.env.HOMEPATH
}
@@ -34,7 +36,7 @@ async function bootstrap (plugins: IPluginInfo[], safeMode = false): Promise<NgM
})
let module = getRootModule(pluginsModules)
window['rootModule'] = module
return await platformBrowserDynamic().bootstrapModule(module)
return platformBrowserDynamic().bootstrapModule(module)
}
findPlugins().then(async plugins => {

View File

@@ -16,11 +16,11 @@ function normalizePath (path: string): string {
nodeRequire.main.paths.map(x => nodeModule.globalPaths.push(normalizePath(x)))
if (process.env.DEV) {
if (process.env.TERMINUS_DEV) {
nodeModule.globalPaths.unshift(path.dirname(require('electron').remote.app.getAppPath()))
}
const builtinPluginsPath = process.env.DEV ? path.dirname(require('electron').remote.app.getAppPath()) : path.join((process as any).resourcesPath, 'builtin-plugins')
const builtinPluginsPath = process.env.TERMINUS_DEV ? path.dirname(require('electron').remote.app.getAppPath()) : path.join((process as any).resourcesPath, 'builtin-plugins')
const userPluginsPath = path.join(
require('electron').remote.app.getPath('appData'),
@@ -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

@@ -9,7 +9,6 @@
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"sourceMap": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"noUnusedParameters": true,

View File

@@ -9,7 +9,7 @@ module.exports = {
preload: path.resolve(__dirname, 'src/entry.preload.ts'),
bundle: path.resolve(__dirname, 'src/entry.ts'),
},
mode: process.env.DEV ? 'development' : 'production',
mode: process.env.TERMINUS_DEV ? 'development' : 'production',
optimization:{
minimize: false,
},

View File

@@ -7,7 +7,7 @@ module.exports = {
entry: {
main: path.resolve(__dirname, 'lib/index.ts'),
},
mode: process.env.DEV ? 'development' : 'production',
mode: process.env.TERMINUS_DEV ? 'development' : 'production',
context: __dirname,
devtool: 'source-map',
output: {

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.8":
version "7.2.8"
resolved "https://registry.yarnpkg.com/@angular/animations/-/animations-7.2.8.tgz#0285364c839c660a934ab0f753ec21424bfb292e"
integrity sha512-dJn9koYukyz15TouBc+z5z9fdThDk+bKgdlij25eYSu5Mpmtk04gB4eIMQA97K0UDh1d4YukgSJ5w3ZIk0m8DQ==
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.8":
version "7.2.8"
resolved "https://registry.yarnpkg.com/@angular/common/-/common-7.2.8.tgz#660c816b6f08cd2919a6efb7465397e4ff14d265"
integrity sha512-LgOhf68+LPndGZhtnUlGFd2goReXYmHzaFZW8gCEi9aC+H+Io8bjYh0gkH3xDreevEOe3f0z6coXNFLIxSmTuA==
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.8":
version "7.2.8"
resolved "https://registry.yarnpkg.com/@angular/compiler/-/compiler-7.2.8.tgz#9d9c1515e99914399e6915c1c90484b1d255560b"
integrity sha512-PrU97cTsOdofpaDkxK0rWUA/CGd0u6ESOI6XvFVm5xH9zJInsdY8ShSHklnr1JJnss70e1dGKZbZq32OChxWMw==
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.8":
version "7.2.8"
resolved "https://registry.yarnpkg.com/@angular/core/-/core-7.2.8.tgz#6586d9b6c6321c80119b3f3e2bd0edbb32d0b649"
integrity sha512-QKwug2kWJC00zm2rvmD9mCJzsOkMVhSu8vqPWf83poWTh8+F9aIVWcy29W0VoGpBkSchOnK8hf9DnKVv28j9nw==
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.8":
version "7.2.8"
resolved "https://registry.yarnpkg.com/@angular/forms/-/forms-7.2.8.tgz#adf194088495822d55dcf3e5bf69196dcf19465d"
integrity sha512-lbSX4IHFHz/c4e2RHiPpL8MJlzDkCuQEHnqsujDaV2X9o9fApS6+C1X4x7Z2XDKqonmeX+aHQwv9+SLejX6OyQ==
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.8":
version "7.2.8"
resolved "https://registry.yarnpkg.com/@angular/platform-browser-dynamic/-/platform-browser-dynamic-7.2.8.tgz#e82768900cedfa75bf453263f931a9f90f7aaab2"
integrity sha512-nOJt28A5pRn4mdL8y98V7bA6OOdMRjsQAcWCr/isGYF0l1yDC0ijUGWkHuRtj3z1/9tmERN0BLXx+xs1h4JhCQ==
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.8":
version "7.2.8"
resolved "https://registry.yarnpkg.com/@angular/platform-browser/-/platform-browser-7.2.8.tgz#11096727b99bf3d7fd82a00a3a468b933c9713bd"
integrity sha512-SizCRMc7Or27g2CugcqWnaAikRPfgLgRvb9GFFGpcgoq8CRfOVwkyR5dFZuqN39H+uwtwuTMP5OUYhZcrFNKug==
dependencies:
tslib "^1.9.0"
@@ -70,6 +70,11 @@
resolved "https://registry.yarnpkg.com/@types/node/-/node-8.0.13.tgz#530f0f9254209b0335bf5cc6387822594ef47093"
integrity sha512-Y3EAG7VA7NVNbZek/fjJtILnmTk/ZfpJuWZGDBqDZ1dVIxgJJJ82fXPW7pKnqyV9CD/9bcPOCi7eErUqGMHOrA==
"@types/node@^10.12.18":
version "10.12.18"
resolved "https://registry.yarnpkg.com/@types/node/-/node-10.12.18.tgz#1d3ca764718915584fcd9f6344621b7672665c67"
integrity sha512-fh+pAqt4xRzPfqA6eh3Z2y6fyZavRIumvjhaCL753+TVkGKGhpPeyrJG2JftD0T9q4GF00KjefsQ+PQNDdWQaQ==
accessibility-developer-tools@^2.11.0:
version "2.12.0"
resolved "https://registry.yarnpkg.com/accessibility-developer-tools/-/accessibility-developer-tools-2.12.0.tgz#3da0cce9d6ec6373964b84f35db7cfc3df7ab514"
@@ -620,10 +625,12 @@ windows-blurbehind@^1.0.0:
resolved "https://registry.yarnpkg.com/windows-blurbehind/-/windows-blurbehind-1.0.0.tgz#050efb988704c44335bdc3efefd757f6e463b8ac"
integrity sha512-lO+A7fhTHO7oy9zJM3o1AdzfSQrmtPkdwvleeuww840ghijjEA1f1Zp8bKA3mJu2DFNtVT40fwmqtgsCGat4UA==
windows-swca@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/windows-swca/-/windows-swca-1.1.1.tgz#0b3530278c67d408baac71c3a6aeb16d55318bf8"
integrity sha512-hKmHrNYJD72Kg0u35fjkiFIuMKuC+Tztmf3Obnf4aTkNjstEpbSEspEeSo3ZNixaVCETA1dLbDkVUQVF1QxtWA==
windows-swca@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/windows-swca/-/windows-swca-2.0.1.tgz#25d78ce25251292061494a0ad07c02282b28b4e3"
integrity sha512-flj+HD6RUemZUvKKguLLnMUOYkQSgDu9qrhUIO4cydvtb/x+sxU8XmpZUtugYuydcdikB9zsCOMgKnAqIQ+7nw==
dependencies:
"@types/node" "^10.12.18"
wrap-ansi@^2.0.0:
version "2.1.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
build/windows/squirrel.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

1
docs/dist/bundle.js vendored

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 MiB

View File

@@ -1,9 +0,0 @@
<!DOCTYPE html><html><head><base href="dist/"><meta name="viewport" content="initial-scale=1, minimal-ui, shrink-to-fit=no"><link href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400" rel="stylesheet"><script src="bundle.js"></script><title>Terminus</title></head><body><div class="mt-5 mb-5" id="header"><div class="text-center"><h1>Terminus</h1><div class="subtitle mb-3">A terminal for a more modern age</div><a class="btn btn-lg btn-outline-dark mt-4" href="https://github.com/Eugeny/terminus/releases/latest" target="_blank"><strong>DOWNLOAD</strong></a><a class="btn btn-lg btn-outline-secondary mt-4 ml-3" href="https://github.com/Eugeny/terminus" target="_blank"><strong>GITHUB</strong></a></div></div><div class="background-stripe"><div class="overlay overlay1"></div><div class="overlay overlay2"></div><div class="terminal"></div></div><div class="container mt-5 mb-5"><div class="d-flex flex-wrap flex-md-nowrap"><div class="w-100"><div class="feature">windows</div><div class="feature">linux</div><div class="feature">macos</div><br><div class="feature">powershell</div><div class="feature">wsl</div><div class="feature">cygwin</div><div class="feature">git-bash</div><div class="feature">cmder</div><div class="feature">clink</div></div><div class="w-100"><div class="feature">full unicode</div><div class="feature">global hotkey</div><div class="feature">plugins</div><div class="feature">tab recovery</div><div class="feature">custom css</div><div class="feature">themes</div><div class="feature">font ligatures</div><div class="feature">clickable paths</div><div class="feature">tabs on top/bottom</div><div class="feature">vibrancy</div><div class="feature">bracketed paste</div></div></div></div><div class="container mt-5 mb-5"><div class="text-center mt-5"><div class="mb-4 mt-5"><script type="text/javascript" src="https://ko-fi.com/widgets/widget_2.js"></script><script type="text/javascript">kofiwidget2.init('Buy me a coffee', '#46b798', 'J3J8KWTF')
kofiwidget2.draw()
</script></div><a class="btn btn-lg btn-outline-secondary mt-3" href="/terminus/#header"><strong>BEAM ME UP</strong></a></div></div><div class="background-stripe2"><div class="overlay overlay1"></div></div><script>(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-3278102-18', 'auto');
ga('send', 'pageview');</script></body></html>

View File

@@ -1 +0,0 @@
import './styles.scss'

View File

@@ -1,75 +0,0 @@
doctype html
html
head
base(href='dist/')
meta(name='viewport', content='initial-scale=1, minimal-ui, shrink-to-fit=no')
link(href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400", rel="stylesheet")
script(src='bundle.js')
title Terminus
body
.mt-5.mb-5#header
.text-center
h1 Terminus
.subtitle.mb-3 A terminal for a more modern age
a.btn.btn-lg.btn-outline-dark.mt-4(href='https://github.com/Eugeny/terminus/releases/latest', target='_blank')
strong DOWNLOAD
a.btn.btn-lg.btn-outline-secondary.mt-4.ml-3(href='https://github.com/Eugeny/terminus', target='_blank')
strong GITHUB
.background-stripe
.overlay.overlay1
.overlay.overlay2
.terminal
.container.mt-5.mb-5
.d-flex.flex-wrap.flex-md-nowrap
.w-100
.feature windows
.feature linux
.feature macos
br
.feature powershell
.feature wsl
.feature cygwin
.feature git-bash
.feature cmder
.feature clink
.w-100
.feature full unicode
.feature global hotkey
.feature plugins
.feature tab recovery
.feature custom css
.feature themes
.feature font ligatures
.feature clickable paths
.feature tabs on top/bottom
.feature vibrancy
.feature bracketed paste
.container.mt-5.mb-5
.text-center.mt-5
.mb-4.mt-5
script(type='text/javascript', src='https://ko-fi.com/widgets/widget_2.js')
script(type='text/javascript').
kofiwidget2.init('Buy me a coffee', '#46b798', 'J3J8KWTF')
kofiwidget2.draw()
a.btn.btn-lg.btn-outline-secondary.mt-3(href='/terminus/#header')
strong BEAM ME UP
.background-stripe2
.overlay.overlay1
script.
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-3278102-18', 'auto');
ga('send', 'pageview');

View File

@@ -1,24 +0,0 @@
{
"name": "docs",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"build": "webpack --progress",
"watch": "webpack --progress --watch"
},
"private": true,
"devDependencies": {
"bootstrap": "^4.1.3",
"css-loader": "^1.0.0",
"file-loader": "^1.1.11",
"node-sass": "^4.9.3",
"pug": "^2.0.3",
"pug-cli": "^1.0.0-alpha6",
"pug-html-loader": "^1.1.5",
"sass-loader": "^7.1.0",
"style-loader": "^0.22.1",
"val-loader": "^1.1.1",
"webpack": "^4.16.5",
"webpack-cli": "^3.1.0"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

After

Width:  |  Height:  |  Size: 1.6 MiB

View File

@@ -1,141 +0,0 @@
$font-family-sans-serif: "Source Sans Pro";
$border-radius-lg: 0;
$btn-border-width: 3px;
@import "node_modules/bootstrap/scss/bootstrap";
h1 {
font-size: 10vw;
font-weight: 200;
margin: 0;
}
body {
overflow-x: hidden;
}
.subtitle {
font-style: italic;
color: #999;
font-size: 5vw;
font-weight: 300;
}
.background-stripe {
width: 100vw;
background-image: url('./background.jpeg');
background-size: cover;
height: 30vw;
margin: 200px 0 150px;
min-height: 1000px;
position: relative;
.overlay {
position: absolute;
width: 100vw;
width: 1px;
height: 1px;
&.overlay1 {
top: -1px;
left: 0;
border-top: 10vw solid white;
border-right: 100vw solid transparent;
}
&.overlay2 {
bottom: -1px;
right: 0;
border-bottom: 10vw solid white;
border-left: 100vw solid transparent;
}
}
.terminal {
position: absolute;
left: 50%;
top: 5vw;
width: 1304px;
margin-left: -652px;
height: 972px;
border-radius: 9px;
box-shadow: 0 0 100px black;
background: url('./terminal.png');
background-size: cover;
animation: slideIn ease-out 1s;
opacity: .95;
}
@media(max-width: 1500px) {
min-height: 500px;
margin: 200px 0 100px;
.terminal {
width: 652px;
top: -100px;
margin-left: -326px;
height: 486px;
border-radius: 5px;
}
}
@media(max-width: 750px) {
min-height: 250px;
margin: 100px 0 50px;
.terminal {
width: 326px;
top: -50px;
margin-left: -163px;
height: 243px;
border-radius: 3px;
}
}
}
.feature {
font-size: 45px;
line-height: 40px;
opacity: .5;
font-style: italic;
}
@keyframes slideIn {
from {
opacity: 0;
margin-top: 200px;
}
to {
opacity: .95;
margin-top: 0px;
}
}
.background-stripe2 {
width: 100vw;
background-image: url('./background.jpeg');
background-size: cover;
height: 30vw;
margin: 100px 0 0;
position: relative;
.overlay {
position: absolute;
width: 100vw;
width: 1px;
height: 1px;
&.overlay1 {
top: -1px;
right: 0;
border-top: 10vw solid white;
border-left: 100vw solid transparent;
}
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

View File

@@ -1,27 +0,0 @@
const path = require('path')
module.exports = {
entry: {
'index.ignore': 'file-loader?name=../index.html!pug-html-loader!' + path.resolve(__dirname, './index.pug'),
'bundle': path.resolve(__dirname, 'index.js'),
},
context: __dirname,
output: {
path: path.join(__dirname, 'dist'),
filename: '[name].js'
},
module: {
rules: [
{ test: /\.scss$/, use: ['style-loader', 'css-loader', 'sass-loader'] },
{
test: /\.(jpeg|png)?$/,
use: {
loader: 'file-loader',
options: {
name: 'assets/[name].[ext]'
}
}
}
]
},
}

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,20 +14,17 @@
"core-js": "2.4.1",
"cross-env": "4.0.0",
"css-loader": "0.28.0",
"electron": "3.0.8",
"electron-builder": "^20.27.1",
"electron": "4.0.5",
"electron-builder": "^20.38.4",
"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-gyp": "^3.8.0",
"node-sass": "^4.5.3",
"npmlog": "4.1.0",
"npx": "^10.2.0",
@@ -41,20 +39,24 @@
"shelljs": "0.7.7",
"source-code-pro": "^2.30.1",
"source-sans-pro": "2.0.10",
"style-loader": "0.13.1",
"style-loader": "^0.23.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",
"typedoc": "^0.14.2",
"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"
},
"resolutions": {
"*/node-abi": "^2.5.0"
},
"build": {
"appId": "org.terminus",
"productName": "Terminus",
@@ -76,6 +78,7 @@
},
"squirrelWindows": {
"iconUrl": "https://github.com/Eugeny/terminus/raw/master/build/windows/icon.ico",
"loadingGif": "./build/windows/squirrel.gif",
"artifactName": "terminus-${version}-setup.exe"
},
"portable": {
@@ -84,6 +87,7 @@
"mac": {
"category": "public.app-category.video",
"icon": "./build/mac/icon.icns",
"artifactName": "terminus-${version}-macos.${ext}",
"publish": [
"github"
],
@@ -124,11 +128,12 @@
},
"scripts": {
"build": "webpack --color --config app/webpack.main.config.js && webpack --color --config app/webpack.config.js && webpack --color --config terminus-core/webpack.config.js && webpack --color --config terminus-settings/webpack.config.js && webpack --color --config terminus-terminal/webpack.config.js && webpack --color --config terminus-settings/webpack.config.js && webpack --color --config terminus-plugin-manager/webpack.config.js && webpack --color --config terminus-community-color-schemes/webpack.config.js && webpack --color --config terminus-ssh/webpack.config.js",
"watch": "cross-env DEV=1 webpack --progress --color --watch",
"start": "cross-env DEV=1 electron app --debug",
"prod": "cross-env DEV=1 electron app",
"watch": "cross-env TERMINUS_DEV=1 webpack --progress --color --watch",
"start": "cross-env TERMINUS_DEV=1 electron app --debug",
"prod": "cross-env TERMINUS_DEV=1 electron app",
"docs": "typedoc --out docs/api terminus-core/src && typedoc --out docs/api/terminal terminus-terminal/src && typedoc --out docs/api/settings terminus-settings/src",
"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,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,4 +21,4 @@ exports.bundledModules = [
'@angular',
'@ng-bootstrap',
]
exports.electronVersion = pkgInfo.devDependencies.electron
exports.electronVersion = electronInfo.version

26
snap/snapcraft.yaml Normal file
View File

@@ -0,0 +1,26 @@
name: terminus
version: '1.0.0'
summary: A terminal for a modern age
description: |
Terminus is a terminal heavily inspired by Hyper. It is, however, designed for people who need to get things done.
grade: devel
confinement: devmode
apps:
terminus:
command: opt/terminus/terminus
parts:
app:
plugin: nodejs
source: .
build-packages:
- libfontconfig-dev
override-build: |
yarn
./scripts/build-native.js
yarn run build
./scripts/build-linux.js
mkdir -p $SNAPCRAFT_PART_INSTALL/opt/terminus || true
cp -ar dist/linux-unpacked/* $SNAPCRAFT_PART_INSTALL/opt/terminus/

View File

@@ -1,6 +1,6 @@
{
"name": "terminus-community-color-schemes",
"version": "1.0.0-alpha.55",
"version": "1.0.73-c4-ga7d62b0",
"description": "Community color schemes for Terminus",
"keywords": [
"terminus-builtin-plugin"

View File

@@ -0,0 +1,36 @@
! special
*.foreground: #d8d8d8
*.background: #343a43
*.cursorColor: #d8d8d8
! black
*.color0: #2c3037
*.color8: #626262
! red
*.color1: #bb5653
*.color9: #c35956
! green
*.color2: #909d62
*.color10: #9fab76
! yellow
*.color3: #eac179
*.color11: #ecc179
! blue
*.color4: #698698
*.color12: #7da9c7
! magenta
*.color5: #b06597
*.color13: #ba6ca0
! cyan
*.color6: #c9dfff
*.color14: #abbacf
! white
*.color7: #d8d8d8
*.color15: #f7f7f7

View File

@@ -13,7 +13,7 @@ module.exports = {
libraryTarget: 'umd',
devtoolModuleFilenameTemplate: 'webpack-terminus-community-color-schemes:///[resource-path]',
},
mode: process.env.DEV ? 'development' : 'production',
mode: process.env.TERMINUS_DEV ? 'development' : 'production',
optimization:{
minimize: false,
},

31
terminus-core/README.md Normal file
View File

@@ -0,0 +1,31 @@
Terminus Core Plugin
--------------------
See also: [Settings plugin API](./settings/), [Terminal plugin API](./settings/)
* tabbed interface services
* toolbar UI
* config file management
* hotkeys
* tab recovery
* logging
* theming
Using the API:
```ts
import { AppService, TabContextMenuItemProvider } from 'terminus-core'
```
Exporting your subclasses:
```ts
@NgModule({
...
providers: [
...
{ provide: TabContextMenuItemProvider, useClass: MyContextMenu, multi: true },
...
]
})
```

View File

@@ -1,6 +1,6 @@
{
"name": "terminus-core",
"version": "1.0.0-alpha.55",
"version": "1.0.73-c4-ga7d62b0",
"description": "Terminus core",
"keywords": [
"terminus-builtin-plugin"
@@ -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,12 +21,13 @@
"@types/node": "^7.0.37",
"@types/webpack-env": "^1.13.0",
"@types/winston": "^2.3.6",
"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"
},
@@ -44,6 +45,5 @@
"deepmerge": "^1.5.0",
"js-yaml": "^3.9.0",
"winston": "^2.4.0"
},
"false": {}
}
}

View File

@@ -1,4 +1,37 @@
/**
* Extend to add your own config options
*/
export abstract class ConfigProvider {
/**
* Default values, e.g.
*
* ```ts
* defaults = {
* myPlugin: {
* foo: 1
* }
* }
* ```
*/
defaults: any = {}
platformDefaults: any = {}
/**
* [[Platform]] specific defaults, e.g.
*
* ```ts
* platformDefaults = {
* [Platform.Windows]: {
* myPlugin: {
* bar: true
* }
* },
* [Platform.macOS]: {
* myPlugin: {
* bar: false
* }
* },
* }
* ```
*/
platformDefaults: {[platform: string]: any} = {}
}

View File

@@ -1,8 +1,12 @@
export interface IHotkeyDescription {
id: string,
name: string,
id: string
name: string
}
/**
* Extend to provide your own hotkeys. A corresponding [[ConfigProvider]]
* must also provide the `hotkeys.foo` config options with the default values
*/
export abstract class HotkeyProvider {
hotkeys: IHotkeyDescription[] = []

View File

@@ -1,9 +1,11 @@
export { BaseTabComponent, BaseTabProcess } from '../components/baseTab.component'
export { SplitTabComponent, SplitContainer } from '../components/splitTab.component'
export { TabRecoveryProvider, RecoveredTab } from './tabRecovery'
export { ToolbarButtonProvider, IToolbarButton } from './toolbarButtonProvider'
export { ConfigProvider } from './configProvider'
export { HotkeyProvider, IHotkeyDescription } from './hotkeyProvider'
export { Theme } from './theme'
export { TabContextMenuItemProvider } from './tabContextMenuProvider'
export { AppService } from '../services/app.service'
export { ConfigService } from '../services/config.service'
@@ -15,3 +17,4 @@ export { HotkeysService } from '../services/hotkeys.service'
export { HostAppService, Platform } from '../services/hostApp.service'
export { ShellIntegrationService } from '../services/shellIntegration.service'
export { ThemesService } from '../services/themes.service'
export { TabsService } from '../services/tabs.service'

View File

@@ -0,0 +1,11 @@
import { BaseTabComponent } from '../components/baseTab.component'
import { TabHeaderComponent } from '../components/tabHeader.component'
/**
* Extend to add items to the tab header's context menu
*/
export abstract class TabContextMenuItemProvider {
weight = 0
abstract async getItems (tab: BaseTabComponent, tabHeader?: TabHeaderComponent): Promise<Electron.MenuItemConstructorOptions[]>
}

View File

@@ -1,10 +1,38 @@
import { TabComponentType } from '../services/app.service'
import { TabComponentType } from '../services/tabs.service'
export interface RecoveredTab {
type: TabComponentType,
options?: any,
/**
* Component type to be instantiated
*/
type: TabComponentType
/**
* Component instance inputs
*/
options?: any
}
/**
* Extend to enable recovery for your custom tab.
* This works in conjunction with [[getRecoveryToken()]]
*
* Terminus will try to find any [[TabRecoveryProvider]] that is able to process
* the recovery token previously returned by [[getRecoveryToken]].
*
* Recommended token format:
*
* ```json
* {
* type: 'my-tab-type',
* foo: 'bar',
* }
* ```
*/
export abstract class TabRecoveryProvider {
abstract async recover (recoveryToken: any): Promise<RecoveredTab|null>
/**
* @param recoveryToken a recovery token found in the saved tabs list
* @returns [[RecoveredTab]] descriptor containing tab type and component inputs
* or `null` if this token is from a different tab type or is not supported
*/
abstract async recover (recoveryToken: any): Promise<RecoveredTab | null>
}

View File

@@ -1,5 +1,13 @@
/**
* Extend to add a custom CSS theme
*/
export abstract class Theme {
name: string
/**
* Complete CSS stylesheet
*/
css: string
terminalBackground: string
}

View File

@@ -1,14 +1,34 @@
import { SafeHtml } from '@angular/platform-browser'
/**
* See [[ToolbarButtonProvider]]
*/
export interface IToolbarButton {
/**
* Raw SVG icon code
*/
icon: SafeHtml
touchBarNSImage?: string
title: string
/**
* Optional Touch Bar icon ID
*/
touchBarNSImage?: string
/**
* Optional Touch Bar button label
*/
touchBarTitle?: string
weight?: number
click: () => void
}
/**
* Extend to add buttons to the toolbar
*/
export abstract class ToolbarButtonProvider {
abstract provide (): IToolbarButton[]
}

View File

@@ -9,7 +9,6 @@ import { HotkeysService } from '../services/hotkeys.service'
import { Logger, LogService } from '../services/log.service'
import { ConfigService } from '../services/config.service'
import { DockingService } from '../services/docking.service'
import { TabRecoveryService } from '../services/tabRecovery.service'
import { ThemesService } from '../services/themes.service'
import { UpdaterService } from '../services/updater.service'
import { TouchbarService } from '../services/touchbar.service'
@@ -18,6 +17,7 @@ import { BaseTabComponent } from './baseTab.component'
import { SafeModeModalComponent } from './safeModeModal.component'
import { AppService, IToolbarButton, ToolbarButtonProvider } from '../api'
/** @hidden */
@Component({
selector: 'app-root',
template: require('./appRoot.component.pug'),
@@ -69,7 +69,6 @@ export class AppRootComponent {
constructor (
private docking: DockingService,
private electron: ElectronService,
private tabRecovery: TabRecoveryService,
private hotkeys: HotkeysService,
private updater: UpdaterService,
private touchbar: TouchbarService,
@@ -128,12 +127,17 @@ export class AppRootComponent {
this.onGlobalHotkey()
})
this.hostApp.windowCloseRequest$.subscribe(async () => {
await this.app.closeAllTabs()
this.hostApp.closeWindow()
})
if (window['safeModeReason']) {
ngbModal.open(SafeModeModalComponent)
}
this.updater.check().then(() => {
this.updatesAvailable = true
this.updater.check().then(available => {
this.updatesAvailable = available
})
this.touchbar.update()
@@ -199,9 +203,7 @@ export class AppRootComponent {
}
async ngOnInit () {
await this.tabRecovery.recoverTabs()
this.ready = true
this.tabRecovery.saveTabs(this.app.tabs)
this.app.emitReady()
}

View File

@@ -1,26 +1,58 @@
import { Observable, Subject } from 'rxjs'
import { ViewRef } from '@angular/core'
/**
* Represents an active "process" inside a tab,
* for example, a user process running inside a terminal tab
*/
export interface BaseTabProcess {
name: string
}
/**
* Abstract base class for custom tab components
*/
export abstract class BaseTabComponent {
private static lastTabID = 0
id: number
/**
* Current tab title
*/
title: string
/**
* User-defined title override
*/
customTitle: string
hasFocus = false
/**
* Last tab activity state
*/
hasActivity = false
/**
* ViewRef to the tab DOM element
*/
hostView: ViewRef
protected titleChange = new Subject<string>()
protected focused = new Subject<void>()
protected blurred = new Subject<void>()
protected progress = new Subject<number>()
protected activity = new Subject<boolean>()
protected destroyed = new Subject<void>()
/**
* CSS color override for the tab's header
*/
color: string = null
protected hasFocus = false
/**
* Ping this if your recovery state has been changed and you want
* your tab state to be saved sooner
*/
protected recoveryStateChangedHint = new Subject<void>()
private progressClearTimeout: number
private titleChange = new Subject<string>()
private focused = new Subject<void>()
private blurred = new Subject<void>()
private progress = new Subject<number>()
private activity = new Subject<boolean>()
private destroyed = new Subject<void>()
get focused$ (): Observable<void> { return this.focused }
get blurred$ (): Observable<void> { return this.blurred }
@@ -28,9 +60,9 @@ export abstract class BaseTabComponent {
get progress$ (): Observable<number> { return this.progress }
get activity$ (): Observable<boolean> { return this.activity }
get destroyed$ (): Observable<void> { return this.destroyed }
get recoveryStateChangedHint$ (): Observable<void> { return this.recoveryStateChangedHint }
constructor () {
this.id = BaseTabComponent.lastTabID++
this.focused$.subscribe(() => {
this.hasFocus = true
})
@@ -46,6 +78,11 @@ export abstract class BaseTabComponent {
}
}
/**
* Sets visual progressbar on the tab
*
* @param {type} progress: value between 0 and 1, or `null` to remove
*/
setProgress (progress: number) {
this.progress.next(progress)
if (progress) {
@@ -58,24 +95,43 @@ export abstract class BaseTabComponent {
}
}
/**
* Shows the acticity marker on the tab header
*/
displayActivity (): void {
this.hasActivity = true
this.activity.next(true)
}
/**
* Removes the acticity marker from the tab header
*/
clearActivity (): void {
this.hasActivity = false
this.activity.next(false)
}
getRecoveryToken (): any {
/**
* Override this and implement a [[TabRecoveryProvider]] to enable recovery
* for your custom tab
*
* @return JSON serializable tab state representation
* for your [[TabRecoveryProvider]] to parse
*/
async getRecoveryToken (): Promise<any> {
return null
}
/**
* Override this to enable task completion notifications for the tab
*/
async getCurrentProcess (): Promise<BaseTabProcess> {
return null
}
/**
* Return false to prevent the tab from being closed
*/
async canClose (): Promise<boolean> {
return true
}
@@ -88,11 +144,15 @@ export abstract class BaseTabComponent {
this.blurred.next()
}
/**
* Called before the tab is closed
*/
destroy (): void {
this.focused.complete()
this.blurred.complete()
this.titleChange.complete()
this.progress.complete()
this.recoveryStateChangedHint.complete()
this.destroyed.next()
this.destroyed.complete()
}

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

@@ -20,6 +20,10 @@
flex-direction: row;
align-items: center;
.off {
color: rgba(0, 0, 0, .5);
}
.icon {
position: relative;
flex: none;

View File

@@ -1,6 +1,7 @@
import { NgZone, Component, Input, HostBinding, HostListener } from '@angular/core'
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'
/** @hidden */
@Component({
selector: 'checkbox',
template: require('./checkbox.component.pug'),

View File

@@ -1,6 +1,7 @@
import { Component, Input, ElementRef, ViewChild } from '@angular/core'
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
/** @hidden */
@Component({
selector: 'rename-tab-modal',
template: require('./renameTabModal.component.pug'),
@@ -16,6 +17,7 @@ export class RenameTabModalComponent {
ngOnInit () {
setTimeout(() => {
this.input.nativeElement.focus()
this.input.nativeElement.select()
}, 250)
}

View File

@@ -1,6 +1,7 @@
import { Component, Input } from '@angular/core'
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
/** @hidden */
@Component({
template: require('./safeModeModal.component.pug'),
})

View File

@@ -0,0 +1,5 @@
:host {
display: block;
position: relative;
flex: auto;
}

View File

@@ -0,0 +1,524 @@
import { Observable, Subject, Subscription } from 'rxjs'
import { Component, Injectable, ViewChild, ViewContainerRef, EmbeddedViewRef, OnInit, OnDestroy } from '@angular/core'
import { BaseTabComponent, BaseTabProcess } from './baseTab.component'
import { TabRecoveryProvider, RecoveredTab } from '../api/tabRecovery'
import { TabsService } from '../services/tabs.service'
import { HotkeysService } from '../services/hotkeys.service'
import { TabRecoveryService } from '../services/tabRecovery.service'
export declare type SplitOrientation = 'v' | 'h'
export declare type SplitDirection = 'r' | 't' | 'b' | 'l'
/**
* Describes a horizontal or vertical split row or column
*/
export class SplitContainer {
orientation: SplitOrientation = 'h'
/**
* Children could be tabs or other containers
*/
children: (BaseTabComponent | SplitContainer)[] = []
/**
* Relative sizes of children, between 0 and 1. Total sum is 1
*/
ratios: number[] = []
x: number
y: number
w: number
h: number
/**
* @return Flat list of all tabs inside this container
*/
getAllTabs () {
let r = []
for (let child of this.children) {
if (child instanceof SplitContainer) {
r = r.concat(child.getAllTabs())
} else {
r.push(child)
}
}
return r
}
/**
* Remove unnecessarily nested child containers and renormalizes [[ratios]]
*/
normalize () {
for (let i = 0; i < this.children.length; i++) {
let child = this.children[i]
if (child instanceof SplitContainer) {
child.normalize()
if (child.children.length === 0) {
this.children.splice(i, 1)
this.ratios.splice(i, 1)
i--
continue
} else if (child.children.length === 1) {
this.children[i] = child.children[0]
} else if (child.orientation === this.orientation) {
let ratio = this.ratios[i]
this.children.splice(i, 1)
this.ratios.splice(i, 1)
for (let j = 0; j < child.children.length; j++) {
this.children.splice(i, 0, child.children[j])
this.ratios.splice(i, 0, child.ratios[j] * ratio)
i++
}
}
}
}
let s = 0
for (let x of this.ratios) {
s += x
}
this.ratios = this.ratios.map(x => x / s)
}
/**
* Gets the left/top side offset for the given element index (between 0 and 1)
*/
getOffsetRatio (index: number): number {
let s = 0
for (let i = 0; i < index; i++) {
s += this.ratios[i]
}
return s
}
async serialize () {
let children = []
for (let child of this.children) {
if (child instanceof SplitContainer) {
children.push(await child.serialize())
} else {
children.push(await child.getRecoveryToken())
}
}
return {
type: 'app:split-tab',
ratios: this.ratios,
orientation: this.orientation,
children,
}
}
}
/**
* Represents a spanner (draggable border between two split areas)
*/
export interface SplitSpannerInfo {
container: SplitContainer
/**
* Number of the right/bottom split in the container
*/
index: number
}
/**
* Split tab is a tab that contains other tabs and allows further splitting them
* You'll mainly encounter it inside [[AppService]].tabs
*/
@Component({
selector: 'split-tab',
template: `
<ng-container #vc></ng-container>
<split-tab-spanner
*ngFor='let spanner of _spanners'
[container]='spanner.container'
[index]='spanner.index'
(change)='onSpannerAdjusted(spanner)'
></split-tab-spanner>
`,
styles: [require('./splitTab.component.scss')],
})
export class SplitTabComponent extends BaseTabComponent implements OnInit, OnDestroy {
/** @hidden */
@ViewChild('vc', { read: ViewContainerRef }) viewContainer: ViewContainerRef
/**
* Top-level split container
*/
root: SplitContainer
/** @hidden */
_recoveredState: any
/** @hidden */
_spanners: SplitSpannerInfo[] = []
private focusedTab: BaseTabComponent
private hotkeysSubscription: Subscription
private viewRefs: Map<BaseTabComponent, EmbeddedViewRef<any>> = new Map()
private tabAdded = new Subject<BaseTabComponent>()
private tabRemoved = new Subject<BaseTabComponent>()
private splitAdjusted = new Subject<SplitSpannerInfo>()
private focusChanged = new Subject<BaseTabComponent>()
get tabAdded$ (): Observable<BaseTabComponent> { return this.tabAdded }
get tabRemoved$ (): Observable<BaseTabComponent> { return this.tabRemoved }
/**
* Fired when split ratio is changed for a given spanner
*/
get splitAdjusted$ (): Observable<SplitSpannerInfo> { return this.splitAdjusted }
/**
* Fired when a different sub-tab gains focus
*/
get focusChanged$ (): Observable<BaseTabComponent> { return this.focusChanged }
/** @hidden */
constructor (
private hotkeys: HotkeysService,
private tabsService: TabsService,
private tabRecovery: TabRecoveryService,
) {
super()
this.root = new SplitContainer()
this.setTitle('')
this.focused$.subscribe(() => {
this.getAllTabs().forEach(x => x.emitFocused())
this.focus(this.focusedTab)
})
this.blurred$.subscribe(() => this.getAllTabs().forEach(x => x.emitBlurred()))
this.hotkeysSubscription = this.hotkeys.matchedHotkey.subscribe(hotkey => {
if (!this.hasFocus) {
return
}
switch (hotkey) {
case 'split-right':
this.splitTab(this.focusedTab, 'r')
break
case 'split-bottom':
this.splitTab(this.focusedTab, 'b')
break
case 'split-top':
this.splitTab(this.focusedTab, 't')
break
case 'split-left':
this.splitTab(this.focusedTab, 'l')
break
case 'pane-nav-left':
this.navigate('l')
break
case 'pane-nav-right':
this.navigate('r')
break
case 'pane-nav-up':
this.navigate('t')
break
case 'pane-nav-down':
this.navigate('b')
break
case 'close-pane':
this.removeTab(this.focusedTab)
break
}
})
}
/** @hidden */
async ngOnInit () {
if (this._recoveredState) {
await this.recoverContainer(this.root, this._recoveredState)
this.layout()
setImmediate(() => {
this.getAllTabs().forEach(x => x.emitFocused())
this.focusAnyIn(this.root)
})
}
}
/** @hidden */
ngOnDestroy () {
this.hotkeysSubscription.unsubscribe()
}
/** @returns Flat list of all sub-tabs */
getAllTabs () {
return this.root.getAllTabs()
}
getFocusedTab (): BaseTabComponent {
return this.focusedTab
}
focus (tab: BaseTabComponent) {
this.focusedTab = tab
for (let x of this.getAllTabs()) {
if (x !== tab) {
x.emitBlurred()
}
}
if (tab) {
tab.emitFocused()
this.focusChanged.next(tab)
}
this.layout()
}
/**
* Focuses the first available tab inside the given [[SplitContainer]]
*/
focusAnyIn (parent: BaseTabComponent | SplitContainer) {
if (!parent) {
return
}
if (parent instanceof SplitContainer) {
this.focusAnyIn(parent.children[0])
} else {
this.focus(parent)
}
}
/**
* Inserts a new `tab` to the `side` of the `relative` tab
*/
addTab (tab: BaseTabComponent, relative: BaseTabComponent, side: SplitDirection) {
let target = this.getParentOf(relative) || this.root
let insertIndex = target.children.indexOf(relative)
if (
(target.orientation === 'v' && ['l', 'r'].includes(side)) ||
(target.orientation === 'h' && ['t', 'b'].includes(side))
) {
let newContainer = new SplitContainer()
newContainer.orientation = (target.orientation === 'v') ? 'h' : 'v'
newContainer.children = [relative]
newContainer.ratios = [1]
target.children[insertIndex] = newContainer
target = newContainer
insertIndex = 0
}
if (insertIndex === -1) {
insertIndex = 0
} else {
insertIndex += (side === 'l' || side === 't') ? 0 : 1
}
for (let i = 0; i < target.children.length; i++) {
target.ratios[i] *= target.children.length / (target.children.length + 1)
}
target.ratios.splice(insertIndex, 0, 1 / (target.children.length + 1))
target.children.splice(insertIndex, 0, tab)
this.recoveryStateChangedHint.next()
this.attachTabView(tab)
setImmediate(() => {
this.layout()
this.tabAdded.next(tab)
this.focus(tab)
})
}
removeTab (tab: BaseTabComponent) {
let parent = this.getParentOf(tab)
let index = parent.children.indexOf(tab)
parent.ratios.splice(index, 1)
parent.children.splice(index, 1)
this.detachTabView(tab)
this.layout()
this.tabRemoved.next(tab)
if (this.root.children.length === 0) {
this.destroy()
} else {
this.focusAnyIn(parent)
}
}
/**
* Moves focus in the given direction
*/
navigate (dir: SplitDirection) {
let rel: BaseTabComponent | SplitContainer = this.focusedTab
let parent = this.getParentOf(rel)
let orientation = ['l', 'r'].includes(dir) ? 'h' : 'v'
while (parent !== this.root && parent.orientation !== orientation) {
rel = parent
parent = this.getParentOf(rel)
}
if (parent.orientation !== orientation) {
return
}
let index = parent.children.indexOf(rel)
if (['l', 't'].includes(dir)) {
if (index > 0) {
this.focusAnyIn(parent.children[index - 1])
}
} else {
if (index < parent.children.length - 1) {
this.focusAnyIn(parent.children[index + 1])
}
}
}
async splitTab (tab: BaseTabComponent, dir: SplitDirection) {
let newTab = await this.tabsService.duplicate(tab)
this.addTab(newTab, tab, dir)
}
/**
* @returns the immediate parent of `tab`
*/
getParentOf (tab: BaseTabComponent | SplitContainer, root?: SplitContainer): SplitContainer {
root = root || this.root
for (let child of root.children) {
if (child instanceof SplitContainer) {
let r = this.getParentOf(tab, child)
if (r) {
return r
}
}
if (child === tab) {
return root
}
}
return null
}
/** @hidden */
async canClose (): Promise<boolean> {
return !(await Promise.all(this.getAllTabs().map(x => x.canClose()))).some(x => !x)
}
/** @hidden */
async getRecoveryToken (): Promise<any> {
return this.root.serialize()
}
/** @hidden */
async getCurrentProcess (): Promise<BaseTabProcess> {
return (await Promise.all(this.getAllTabs().map(x => x.getCurrentProcess()))).find(x => !!x)
}
/** @hidden */
onSpannerAdjusted (spanner: SplitSpannerInfo) {
this.layout()
this.splitAdjusted.next(spanner)
}
private attachTabView (tab: BaseTabComponent) {
let ref = this.viewContainer.insert(tab.hostView) as EmbeddedViewRef<any>
this.viewRefs.set(tab, ref)
ref.rootNodes[0].addEventListener('click', () => this.focus(tab))
tab.titleChange$.subscribe(t => this.setTitle(t))
tab.activity$.subscribe(a => a ? this.displayActivity() : this.clearActivity())
tab.progress$.subscribe(p => this.setProgress(p))
if (tab.title) {
this.setTitle(tab.title)
}
tab.destroyed$.subscribe(() => {
this.removeTab(tab)
})
}
private detachTabView (tab: BaseTabComponent) {
let ref = this.viewRefs.get(tab)
this.viewRefs.delete(tab)
this.viewContainer.remove(this.viewContainer.indexOf(ref))
}
private layout () {
this.root.normalize()
this._spanners = []
this.layoutInternal(this.root, 0, 0, 100, 100)
}
private layoutInternal (root: SplitContainer, x: number, y: number, w: number, h: number) {
let size = (root.orientation === 'v') ? h : w
let sizes = root.ratios.map(x => x * size)
root.x = x
root.y = y
root.w = w
root.h = h
let offset = 0
root.children.forEach((child, i) => {
let childX = (root.orientation === 'v') ? x : (x + offset)
let childY = (root.orientation === 'v') ? (y + offset) : y
let childW = (root.orientation === 'v') ? w : sizes[i]
let childH = (root.orientation === 'v') ? sizes[i] : h
if (child instanceof SplitContainer) {
this.layoutInternal(child, childX, childY, childW, childH)
} else {
let element = this.viewRefs.get(child).rootNodes[0]
element.style.position = 'absolute'
element.style.left = `${childX}%`
element.style.top = `${childY}%`
element.style.width = `${childW}%`
element.style.height = `${childH}%`
element.style.opacity = (child === this.focusedTab) ? 1 : 0.75
}
offset += sizes[i]
if (i !== 0) {
this._spanners.push({
container: root,
index: i,
})
}
})
}
private async recoverContainer (root: SplitContainer, state: any) {
let children: (SplitContainer | BaseTabComponent)[] = []
root.orientation = state.orientation
root.ratios = state.ratios
root.children = children
for (let childState of state.children) {
if (childState.type === 'app:split-tab') {
let child = new SplitContainer()
await this.recoverContainer(child, childState)
children.push(child)
} else {
let recovered = await this.tabRecovery.recoverTab(childState)
if (recovered) {
let tab = this.tabsService.create(recovered.type, recovered.options)
children.push(tab)
this.attachTabView(tab)
} else {
state.ratios.splice(state.children.indexOf(childState), 0)
}
}
}
}
}
/** @hidden */
@Injectable()
export class SplitTabRecoveryProvider extends TabRecoveryProvider {
async recover (recoveryToken: any): Promise<RecoveredTab> {
if (recoveryToken && recoveryToken.type === 'app:split-tab') {
return {
type: SplitTabComponent,
options: { _recoveredState: recoveryToken },
}
}
return null
}
}

View File

@@ -0,0 +1,22 @@
:host {
display: block;
position: absolute;
z-index: 5;
transition: 0.125s background;
&.v {
cursor: ns-resize;
height: 10px;
margin-top: -5px;
}
&.h {
cursor: ew-resize;
width: 10px;
margin-left: -5px;
}
&:hover, &.active {
background: rgba(255, 255, 255, .125);
}
}

View File

@@ -0,0 +1,88 @@
import { Component, Input, HostBinding, ElementRef, Output, EventEmitter } from '@angular/core'
import { SplitContainer } from './splitTab.component'
/** @hidden */
@Component({
selector: 'split-tab-spanner',
template: '',
styles: [require('./splitTabSpanner.component.scss')],
})
export class SplitTabSpannerComponent {
@Input() container: SplitContainer
@Input() index: number
@Output() change = new EventEmitter<void>()
@HostBinding('class.active') isActive = false
@HostBinding('class.h') isHorizontal = false
@HostBinding('class.v') isVertical = true
@HostBinding('style.left') cssLeft: string
@HostBinding('style.top') cssTop: string
@HostBinding('style.width') cssWidth: string
@HostBinding('style.height') cssHeight: string
private marginOffset = -5
constructor (private element: ElementRef) { }
ngAfterViewInit () {
this.element.nativeElement.addEventListener('mousedown', e => {
this.isActive = true
let start = this.isVertical ? e.pageY : e.pageX
let current = start
let oldPosition = this.isVertical ? this.element.nativeElement.offsetTop : this.element.nativeElement.offsetLeft
const dragHandler = e => {
current = this.isVertical ? e.pageY : e.pageX
let newPosition = oldPosition + (current - start)
if (this.isVertical) {
this.element.nativeElement.style.top = `${newPosition - this.marginOffset}px`
} else {
this.element.nativeElement.style.left = `${newPosition - this.marginOffset}px`
}
}
const offHandler = () => {
this.isActive = false
document.removeEventListener('mouseup', offHandler)
this.element.nativeElement.parentElement.removeEventListener('mousemove', dragHandler)
let diff = (current - start) / (this.isVertical ? this.element.nativeElement.parentElement.clientHeight : this.element.nativeElement.parentElement.clientWidth)
diff = Math.max(diff, -this.container.ratios[this.index - 1] + 0.1)
diff = Math.min(diff, this.container.ratios[this.index] - 0.1)
this.container.ratios[this.index - 1] += diff
this.container.ratios[this.index] -= diff
this.change.emit()
}
document.addEventListener('mouseup', offHandler)
this.element.nativeElement.parentElement.addEventListener('mousemove', dragHandler)
})
}
ngOnChanges () {
this.isHorizontal = this.container.orientation === 'h'
this.isVertical = this.container.orientation === 'v'
if (this.isVertical) {
this.setDimensions(
this.container.x,
this.container.y + this.container.h * this.container.getOffsetRatio(this.index),
this.container.w,
null
)
} else {
this.setDimensions(
this.container.x + this.container.w * this.container.getOffsetRatio(this.index),
this.container.y,
null,
this.container.h
)
}
}
private setDimensions (x: number, y: number, w: number, h: number) {
this.cssLeft = `${x}%`
this.cssTop = `${y}%`
this.cssWidth = w ? `${w}%` : null
this.cssHeight = h ? `${h}%` : null
}
}

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

@@ -3,6 +3,7 @@ import { ConfigService } from '../services/config.service'
import { HomeBaseService } from '../services/homeBase.service'
import { IToolbarButton, ToolbarButtonProvider } from '../api'
/** @hidden */
@Component({
selector: 'start-page',
template: require('./startPage.component.pug'),

View File

@@ -1,6 +1,7 @@
import { Component, Input, ViewChild, HostBinding, ViewContainerRef, OnChanges } from '@angular/core'
import { BaseTabComponent } from '../components/baseTab.component'
/** @hidden */
@Component({
selector: 'tab-body',
template: `
@@ -17,7 +18,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

@@ -1,12 +1,15 @@
import { Component, Input, HostBinding, HostListener, NgZone, ViewChild, ElementRef } from '@angular/core'
import { Component, Input, Optional, Inject, HostBinding, HostListener, ViewChild, ElementRef } from '@angular/core'
import { SortableComponent } from 'ng2-dnd'
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
import { TabContextMenuItemProvider } from '../api/tabContextMenuProvider'
import { BaseTabComponent } from './baseTab.component'
import { RenameTabModalComponent } from './renameTabModal.component'
import { HotkeysService } from '../services/hotkeys.service'
import { ElectronService } from '../services/electron.service'
import { AppService } from '../services/app.service'
import { HostAppService, Platform } from '../services/hostApp.service'
/** @hidden */
@Component({
selector: 'tab-header',
template: require('./tabHeader.component.pug'),
@@ -20,16 +23,24 @@ export class TabHeaderComponent {
@Input() progress: number
@ViewChild('handle') handle: ElementRef
private completionNotificationEnabled = false
constructor (
public app: AppService,
private electron: ElectronService,
private zone: NgZone,
private hostApp: HostAppService,
private ngbModal: NgbModal,
private hotkeys: HotkeysService,
private parentDraggable: SortableComponent,
) { }
@Optional() @Inject(TabContextMenuItemProvider) protected contextMenuProviders: TabContextMenuItemProvider[],
) {
this.hotkeys.matchedHotkey.subscribe((hotkey) => {
if (this.app.activeTab === this.tab) {
if (hotkey === 'rename-tab') {
this.showRenameTabModal()
}
}
})
this.contextMenuProviders.sort((a, b) => a.weight - b.weight)
}
ngOnInit () {
if (this.hostApp.platform === Platform.macOS) {
@@ -40,7 +51,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 +60,19 @@ export class TabHeaderComponent {
}).catch(() => null)
}
async buildContextMenu (): Promise<Electron.MenuItemConstructorOptions[]> {
let items: Electron.MenuItemConstructorOptions[] = []
for (let section of await Promise.all(this.contextMenuProviders.map(x => x.getItems(this.tab, this)))) {
items.push({ type: 'separator' })
items = items.concat(section)
}
return items.slice(1)
}
@HostListener('dblclick') onDoubleClick (): void {
this.showRenameTabModal()
}
@HostListener('auxclick', ['$event']) async onAuxClick ($event: MouseEvent) {
if ($event.which === 2) {
this.app.closeTab(this.tab, true)
@@ -56,73 +80,7 @@ export class TabHeaderComponent {
if ($event.which === 3) {
event.preventDefault()
let contextMenu = this.electron.remote.Menu.buildFromTemplate([
{
label: 'Close',
click: () => this.zone.run(() => {
this.app.closeTab(this.tab, true)
})
},
{
label: 'Close other tabs',
click: () => this.zone.run(() => {
for (let tab of this.app.tabs.filter(x => x !== this.tab)) {
this.app.closeTab(tab, true)
}
})
},
{
label: 'Close tabs to the right',
click: () => this.zone.run(() => {
for (let tab of this.app.tabs.slice(this.app.tabs.indexOf(this.tab) + 1)) {
this.app.closeTab(tab, true)
}
})
},
{
label: 'Close tabs to the left',
click: () => this.zone.run(() => {
for (let tab of this.app.tabs.slice(0, this.app.tabs.indexOf(this.tab))) {
this.app.closeTab(tab, true)
}
})
},
])
let process = await this.tab.getCurrentProcess()
if (process) {
contextMenu.append(new this.electron.MenuItem({
id: 'sep',
type: 'separator',
}))
contextMenu.append(new this.electron.MenuItem({
id: 'process-name',
enabled: false,
label: 'Current process: ' + process.name,
}))
contextMenu.append(new this.electron.MenuItem({
id: 'completion',
label: 'Notify when done',
type: 'checkbox',
checked: this.completionNotificationEnabled,
click: () => this.zone.run(() => {
this.completionNotificationEnabled = !this.completionNotificationEnabled
if (this.completionNotificationEnabled) {
this.app.observeTabCompletion(this.tab).subscribe(() => {
new Notification('Process completed', {
body: process.name,
}).addEventListener('click', () => {
this.app.selectTab(this.tab)
})
this.completionNotificationEnabled = false
})
} else {
this.app.stopObservingTabCompletion(this.tab)
}
})
}))
}
const contextMenu = this.electron.remote.Menu.buildFromTemplate(await this.buildContextMenu())
contextMenu.popup({
x: $event.pageX,

View File

@@ -1,5 +1,6 @@
import { Component } from '@angular/core'
/** @hidden */
@Component({
selector: 'title-bar',
template: require('./titleBar.component.pug'),

View File

@@ -2,6 +2,7 @@ import { Component } from '@angular/core'
import { NG_VALUE_ACCESSOR } from '@angular/forms'
import { CheckboxComponent } from './checkbox.component'
/** @hidden */
@Component({
selector: 'toggle',
template: `

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)='app.closeWindow()'
(click)='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

@@ -2,6 +2,7 @@ import { Component } from '@angular/core'
import { HostAppService } from '../services/hostApp.service'
import { AppService } from '../services/app.service'
/** @hidden */
@Component({
selector: 'window-controls',
template: require('./windowControls.component.pug'),
@@ -9,4 +10,9 @@ import { AppService } from '../services/app.service'
})
export class WindowControlsComponent {
constructor (public hostApp: HostAppService, public app: AppService) { }
async closeWindow () {
await this.app.closeAllTabs()
this.hostApp.closeWindow()
}
}

View File

@@ -1,6 +1,7 @@
import { ConfigProvider } from './api/configProvider'
import { Platform } from './services/hostApp.service'
/** @hidden */
export class CoreConfigProvider extends ConfigProvider {
platformDefaults = {
[Platform.macOS]: require('./configDefaults.macos.yaml'),

View File

@@ -7,46 +7,48 @@ 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']
split-right:
- 'Ctrl-Shift-E'
split-bottom:
- 'Ctrl-Shift-D'
split-left: []
split-top: []
pane-nav-right:
- 'Ctrl-Alt-ArrowRight'
pane-nav-down:
- 'Ctrl-Alt-ArrowDown'
pane-nav-up:
- 'Ctrl-Alt-ArrowUp'
pane-nav-left:
- 'Ctrl-Alt-ArrowLeft'
close-pane: []
pluginBlacklist: ['ssh']

View File

@@ -8,6 +8,8 @@ hotkeys:
close-tab:
- '⌘-W'
toggle-last-tab: []
rename-tab:
- '⌘-R'
next-tab:
- 'Ctrl-Tab'
previous-tab:
@@ -32,4 +34,20 @@ hotkeys:
- '⌘-9'
tab-10:
- '⌘-0'
split-right:
- '⌘-Shift-D'
split-bottom:
- '⌘-D'
split-left: []
split-top: []
pane-nav-right:
- '⌘-⌥-ArrowRight'
pane-nav-down:
- '⌘-⌥-ArrowDown'
pane-nav-up:
- '⌘-⌥-ArrowUp'
pane-nav-left:
- '⌘-⌥-ArrowLeft'
close-pane:
- '⌘-Shift-W'
pluginBlacklist: ['ssh']

View File

@@ -7,46 +7,48 @@ 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']
split-right:
- 'Ctrl-Shift-E'
split-bottom:
- 'Ctrl-Shift-D'
split-left: []
split-top: []
pane-nav-right:
- 'Ctrl-Alt-ArrowRight'
pane-nav-down:
- 'Ctrl-Alt-ArrowDown'
pane-nav-up:
- 'Ctrl-Alt-ArrowUp'
pane-nav-left:
- 'Ctrl-Alt-ArrowLeft'
close-pane: []
pluginBlacklist: []

View File

@@ -1,5 +1,6 @@
import { Directive, AfterViewInit, ElementRef } from '@angular/core'
/** @hidden */
@Directive({
selector: '[autofocus]'
})

View File

@@ -0,0 +1,121 @@
import { Injectable } from '@angular/core'
import { IHotkeyDescription, HotkeyProvider } from './api/hotkeyProvider'
/** @hidden */
@Injectable()
export class AppHotkeyProvider extends HotkeyProvider {
hotkeys: IHotkeyDescription[] = [
{
id: 'new-window',
name: 'New window',
},
{
id: 'toggle-window',
name: 'Toggle terminal window',
},
{
id: 'toggle-fullscreen',
name: 'Toggle fullscreen mode',
},
{
id: 'rename-tab',
name: 'Rename Tab',
},
{
id: 'close-tab',
name: 'Close tab',
},
{
id: 'toggle-last-tab',
name: 'Toggle last tab',
},
{
id: 'next-tab',
name: 'Next tab',
},
{
id: 'previous-tab',
name: 'Previous tab',
},
{
id: 'tab-1',
name: 'Tab 1',
},
{
id: 'tab-2',
name: 'Tab 2',
},
{
id: 'tab-3',
name: 'Tab 3',
},
{
id: 'tab-4',
name: 'Tab 4',
},
{
id: 'tab-5',
name: 'Tab 5',
},
{
id: 'tab-6',
name: 'Tab 6',
},
{
id: 'tab-7',
name: 'Tab 7',
},
{
id: 'tab-8',
name: 'Tab 8',
},
{
id: 'tab-9',
name: 'Tab 9',
},
{
id: 'tab-10',
name: 'Tab 10',
},
{
id: 'split-right',
name: 'Split to the right',
},
{
id: 'split-bottom',
name: 'Split to the bottom',
},
{
id: 'split-left',
name: 'Split to the left',
},
{
id: 'split-top',
name: 'Split to the top',
},
{
id: 'pane-nav-up',
name: 'Focus the pane above',
},
{
id: 'pane-nav-down',
name: 'Focus the pane below',
},
{
id: 'pane-nav-left',
name: 'Focus the pane on the left',
},
{
id: 'pane-nav-right',
name: 'Focus the pane on the right',
},
{
id: 'close-pane',
name: 'Close focused pane',
},
]
async provide (): Promise<IHotkeyDescription[]> {
return this.hotkeys
}
}

View File

@@ -6,20 +6,6 @@ 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 { AppRootComponent } from './components/appRoot.component'
import { CheckboxComponent } from './components/checkbox.component'
import { TabBodyComponent } from './components/tabBody.component'
@@ -30,41 +16,39 @@ import { TitleBarComponent } from './components/titleBar.component'
import { ToggleComponent } from './components/toggle.component'
import { WindowControlsComponent } from './components/windowControls.component'
import { RenameTabModalComponent } from './components/renameTabModal.component'
import { SplitTabComponent, SplitTabRecoveryProvider } from './components/splitTab.component'
import { SplitTabSpannerComponent } from './components/splitTabSpanner.component'
import { AutofocusDirective } from './directives/autofocus.directive'
import { HotkeyProvider } from './api/hotkeyProvider'
import { ConfigProvider } from './api/configProvider'
import { Theme } from './api/theme'
import { TabContextMenuItemProvider } from './api/tabContextMenuProvider'
import { TabRecoveryProvider } from './api/tabRecovery'
import { StandardTheme, StandardCompactTheme, PaperTheme } from './theme'
import { CoreConfigProvider } from './config'
import { AppHotkeyProvider } from './hotkeys'
import { TaskCompletionContextMenu, CommonOptionsContextMenu, CloseContextMenu } from './tabContextMenu'
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: TabContextMenuItemProvider, useClass: CommonOptionsContextMenu, multi: true },
{ provide: TabContextMenuItemProvider, useClass: CloseContextMenu, multi: true },
{ provide: TabContextMenuItemProvider, useClass: TaskCompletionContextMenu, multi: true },
{ provide: TabRecoveryProvider, useClass: SplitTabRecoveryProvider, multi: true },
{ provide: PERFECT_SCROLLBAR_CONFIG, useValue: { suppressScrollX: true } }
]
/** @hidden */
@NgModule({
imports: [
BrowserModule,
@@ -86,10 +70,13 @@ const PROVIDERS = [
RenameTabModalComponent,
SafeModeModalComponent,
AutofocusDirective,
SplitTabComponent,
SplitTabSpannerComponent,
],
entryComponents: [
RenameTabModalComponent,
SafeModeModalComponent,
SplitTabComponent,
],
exports: [
CheckboxComponent,

View File

@@ -1,12 +1,12 @@
import { Observable, Subject, AsyncSubject } from 'rxjs'
import { takeUntil } from 'rxjs/operators'
import { Injectable, ComponentFactoryResolver, Injector } from '@angular/core'
import { Injectable } from '@angular/core'
import { BaseTabComponent } from '../components/baseTab.component'
import { Logger, LogService } from './log.service'
import { SplitTabComponent } from '../components/splitTab.component'
import { ConfigService } from './config.service'
import { HostAppService } from './hostApp.service'
export declare type TabComponentType = new (...args: any[]) => BaseTabComponent
import { TabRecoveryService } from './tabRecovery.service'
import { TabsService, TabComponentType } from './tabs.service'
class CompletionObserver {
get done$ (): Observable<void> { return this.done }
@@ -35,12 +35,14 @@ class CompletionObserver {
}
}
@Injectable()
@Injectable({ providedIn: 'root' })
export class AppService {
tabs: BaseTabComponent[] = []
activeTab: BaseTabComponent
lastTabIndex = 0
logger: Logger
get activeTab (): BaseTabComponent { return this._activeTab }
private lastTabIndex = 0
private _activeTab: BaseTabComponent
private activeTabChange = new Subject<BaseTabComponent>()
private tabsChanged = new Subject<void>()
@@ -54,62 +56,105 @@ export class AppService {
get tabOpened$ (): Observable<BaseTabComponent> { return this.tabOpened }
get tabsChanged$ (): Observable<void> { return this.tabsChanged }
get tabClosed$ (): Observable<BaseTabComponent> { return this.tabClosed }
/** Fires once when the app is ready */
get ready$ (): Observable<void> { return this.ready }
/** @hidden */
constructor (
private componentFactoryResolver: ComponentFactoryResolver,
private config: ConfigService,
private hostApp: HostAppService,
private injector: Injector,
log: LogService,
private tabRecovery: TabRecoveryService,
private tabsService: TabsService,
) {
this.logger = log.create('app')
this.tabRecovery.recoverTabs().then(tabs => {
for (let tab of tabs) {
this.openNewTabRaw(tab.type, tab.options)
}
this.hostApp.windowCloseRequest$.subscribe(() => this.closeWindow())
this.tabsChanged$.subscribe(() => {
tabRecovery.saveTabs(this.tabs)
})
setInterval(() => {
tabRecovery.saveTabs(this.tabs)
}, 30000)
})
}
openNewTab (type: TabComponentType, inputs?: any): BaseTabComponent {
let componentFactory = this.componentFactoryResolver.resolveComponentFactory(type)
let componentRef = componentFactory.create(this.injector)
let tab = componentRef.instance
tab.hostView = componentRef.hostView
Object.assign(tab, inputs || {})
private addTabRaw (tab: BaseTabComponent) {
this.tabs.push(tab)
this.selectTab(tab)
this.tabsChanged.next()
this.tabOpened.next(tab)
tab.recoveryStateChangedHint$.subscribe(() => {
this.tabRecovery.saveTabs(this.tabs)
})
tab.titleChange$.subscribe(title => {
if (tab === this.activeTab) {
if (tab === this._activeTab) {
this.hostApp.setTitle(title)
}
})
tab.destroyed$.subscribe(() => {
let newIndex = Math.max(0, this.tabs.indexOf(tab) - 1)
this.tabs = this.tabs.filter((x) => x !== tab)
if (tab === this._activeTab) {
this.selectTab(this.tabs[newIndex])
}
this.tabsChanged.next()
this.tabClosed.next(tab)
})
}
/**
* Adds a new tab **without** wrapping it in a SplitTabComponent
* @param inputs Properties to be assigned on the new tab component instance
*/
openNewTabRaw (type: TabComponentType, inputs?: any): BaseTabComponent {
let tab = this.tabsService.create(type, inputs)
this.addTabRaw(tab)
return tab
}
/**
* Adds a new tab while wrapping it in a SplitTabComponent
* @param inputs Properties to be assigned on the new tab component instance
*/
openNewTab (type: TabComponentType, inputs?: any): BaseTabComponent {
let splitTab = this.tabsService.create(SplitTabComponent) as SplitTabComponent
let tab = this.tabsService.create(type, inputs)
splitTab.addTab(tab, null, 'r')
this.addTabRaw(splitTab)
return tab
}
selectTab (tab: BaseTabComponent) {
if (this.activeTab === tab) {
this.activeTab.emitFocused()
if (this._activeTab === tab) {
this._activeTab.emitFocused()
return
}
if (this.tabs.includes(this.activeTab)) {
this.lastTabIndex = this.tabs.indexOf(this.activeTab)
if (this.tabs.includes(this._activeTab)) {
this.lastTabIndex = this.tabs.indexOf(this._activeTab)
} else {
this.lastTabIndex = null
}
if (this.activeTab) {
this.activeTab.clearActivity()
this.activeTab.emitBlurred()
if (this._activeTab) {
this._activeTab.clearActivity()
this._activeTab.emitBlurred()
}
this.activeTab = tab
this._activeTab = tab
this.activeTabChange.next(tab)
if (this.activeTab) {
this.activeTab.emitFocused()
this.hostApp.setTitle(this.activeTab.title)
if (this._activeTab) {
setImmediate(() => {
this._activeTab.emitFocused()
})
this.hostApp.setTitle(this._activeTab.title)
}
}
/** Switches between the current tab and the previously active one */
toggleLastTab () {
if (!this.lastTabIndex || this.lastTabIndex >= this.tabs.length) {
this.lastTabIndex = 0
@@ -119,7 +164,7 @@ export class AppService {
nextTab () {
if (this.tabs.length > 1) {
let tabIndex = this.tabs.indexOf(this.activeTab)
let tabIndex = this.tabs.indexOf(this._activeTab)
if (tabIndex < this.tabs.length - 1) {
this.selectTab(this.tabs[tabIndex + 1])
} else if (this.config.store.appearance.cycleTabs) {
@@ -130,7 +175,7 @@ export class AppService {
previousTab () {
if (this.tabs.length > 1) {
let tabIndex = this.tabs.indexOf(this.activeTab)
let tabIndex = this.tabs.indexOf(this._activeTab)
if (tabIndex > 0) {
this.selectTab(this.tabs[tabIndex - 1])
} else if (this.config.store.appearance.cycleTabs) {
@@ -139,6 +184,7 @@ export class AppService {
}
}
/** @hidden */
emitTabsChanged () {
this.tabsChanged.next()
}
@@ -150,17 +196,17 @@ export class AppService {
if (checkCanClose && !await tab.canClose()) {
return
}
let newIndex = Math.max(0, this.tabs.indexOf(tab) - 1)
this.tabs = this.tabs.filter((x) => x !== tab)
tab.destroy()
if (tab === this.activeTab) {
this.selectTab(this.tabs[newIndex])
}
this.tabsChanged.next()
this.tabClosed.next(tab)
}
async closeWindow () {
async duplicateTab (tab: BaseTabComponent) {
let dup = await this.tabsService.duplicate(tab)
if (dup) {
this.addTabRaw(dup)
}
}
async closeAllTabs () {
for (let tab of this.tabs) {
if (!await tab.canClose()) {
return
@@ -169,15 +215,19 @@ export class AppService {
for (let tab of this.tabs) {
tab.destroy()
}
this.hostApp.closeWindow()
}
/** @hidden */
emitReady () {
this.ready.next(null)
this.ready.complete()
this.hostApp.emitReady()
}
/**
* Returns an observable that fires once
* the tab's internal "process" (see [[BaseTabProcess]]) completes
*/
observeTabCompletion (tab: BaseTabComponent): Observable<void> {
if (!this.completionObservers.has(tab)) {
let observer = new CompletionObserver(tab)

View File

@@ -18,6 +18,7 @@ function isNonStructuralObjectMember (v) {
return v instanceof Object && !(v instanceof Array) && v.__nonStructural
}
/** @hidden */
export class ConfigProxy {
constructor (real: any, defaults: any) {
for (let key in defaults) {
@@ -56,7 +57,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,18 +75,31 @@ export class ConfigProxy {
setValue (key: string, value: any) { } // tslint:disable-line
}
@Injectable()
@Injectable({ providedIn: 'root' })
export class ConfigService {
/**
* Contains the actual config values
*/
store: any
/**
* Whether an app restart is required due to recent changes
*/
restartRequested: boolean
/**
* Full config file path
*/
path: string
private changed = new Subject<void>()
private _store: any
private path: string
private defaults: any
private servicesCache: { [id: string]: Function[] } = null
get changed$ (): Observable<void> { return this.changed }
/** @hidden */
constructor (
electron: ElectronService,
private hostApp: HostAppService,
@@ -129,10 +143,16 @@ export class ConfigService {
this.hostApp.broadcastConfigChange()
}
/**
* Reads config YAML as string
*/
readRaw (): string {
return yaml.safeDump(this._store)
}
/**
* Writes config YAML as string
*/
writeRaw (data: string): void {
this._store = yaml.safeLoad(data)
this.save()
@@ -140,7 +160,7 @@ export class ConfigService {
this.emitChange()
}
emitChange (): void {
private emitChange (): void {
this.changed.next()
}
@@ -148,6 +168,12 @@ export class ConfigService {
this.restartRequested = true
}
/**
* Filters a list of Angular services to only include those provided
* by plugins that are enabled
*
* @typeparam T Base provider type
*/
enabledServices<T> (services: T[]): T[] {
if (!this.servicesCache) {
this.servicesCache = {}

View File

@@ -8,8 +8,9 @@ export interface IScreen {
name: string
}
@Injectable()
@Injectable({ providedIn: 'root' })
export class DockingService {
/** @hidden */
constructor (
private electron: ElectronService,
private config: ConfigService,
@@ -20,21 +21,23 @@ export class DockingService {
}
dock () {
let dockSide = this.config.store.appearance.dock
if (dockSide === 'off') {
this.hostApp.setAlwaysOnTop(false)
return
}
let display = this.electron.screen.getAllDisplays()
.filter((x) => x.id === this.config.store.appearance.dockScreen)[0]
if (!display) {
display = this.getCurrentScreen()
}
let dockSide = this.config.store.appearance.dock
let newBounds: Bounds = { x: 0, y: 0, width: 0, height: 0 }
let fill = this.config.store.appearance.dockFill
let [minWidth, minHeight] = this.hostApp.getWindow().getMinimumSize()
if (dockSide === 'off') {
this.hostApp.setAlwaysOnTop(false)
return
}
if (dockSide === 'left' || dockSide === 'right') {
newBounds.width = Math.max(minWidth, Math.round(fill * display.bounds.width))
newBounds.height = display.bounds.height
@@ -76,7 +79,7 @@ export class DockingService {
})
}
repositionWindow () {
private repositionWindow () {
let [x, y] = this.hostApp.getWindow().getPosition()
for (let screen of this.electron.screen.getAllDisplays()) {
let bounds = screen.bounds

View File

@@ -1,7 +1,12 @@
import { Injectable } from '@angular/core'
import { TouchBar, BrowserWindow, Menu, MenuItem } from 'electron'
@Injectable()
export interface MessageBoxResponse {
response: number
checkboxChecked?: boolean
}
@Injectable({ providedIn: 'root' })
export class ElectronService {
app: any
ipcRenderer: any
@@ -19,6 +24,7 @@ export class ElectronService {
MenuItem: typeof MenuItem
private electron: any
/** @hidden */
constructor () {
this.electron = require('electron')
this.remote = this.electron.remote
@@ -37,21 +43,23 @@ export class ElectronService {
this.MenuItem = this.remote.MenuItem
}
remoteRequire (name: string): any {
return this.remote.require(name)
}
remoteRequirePluginModule (plugin: string, module: string, globals: any): any {
return this.remoteRequire(this.remoteResolvePluginModule(plugin, module, globals))
}
remoteResolvePluginModule (plugin: string, module: string, globals: any): any {
return globals.require.resolve(`${plugin}/node_modules/${module}`)
}
/**
* Removes OS focus from Terminus' window
*/
loseFocus () {
if (process.platform === 'darwin') {
this.remote.Menu.sendActionToFirstResponder('hide:')
}
}
showMessageBox (
browserWindow: Electron.BrowserWindow,
options: Electron.MessageBoxOptions
): Promise<MessageBoxResponse> {
return new Promise(resolve => {
this.dialog.showMessageBox(browserWindow, options, (response, checkboxChecked) => {
resolve({ response, checkboxChecked })
})
})
}
}

View File

@@ -5,10 +5,11 @@ import { ConfigService } from './config.service'
import ua = require('universal-analytics')
import uuidv4 = require('uuid/v4')
@Injectable()
@Injectable({ providedIn: 'root' })
export class HomeBaseService {
appVersion: string
/** @hidden */
constructor (
private electron: ElectronService,
private config: ConfigService,
@@ -33,7 +34,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,42 +16,88 @@ export interface Bounds {
height: number
}
@Injectable()
/**
* Provides interaction with the main process
*/
@Injectable({ providedIn: 'root' })
export class HostAppService {
platform: Platform
nodePlatform: string
/**
* Fired once the window is visible
*/
shown = new EventEmitter<any>()
isFullScreen = false
private preferencesMenu = new Subject<void>()
private secondInstance = new Subject<void>()
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 windowMoved = new Subject<void>()
private displayMetricsChanged = new Subject<void>()
private logger: Logger
private windowId: number
/**
* Fired when Preferences is selected in the macOS menu
*/
get preferencesMenu$ (): Observable<void> { return this.preferencesMenu }
/**
* Fired when a second instance of Terminus is launched
*/
get secondInstance$ (): Observable<void> { return this.secondInstance }
/**
* Fired for the `terminus open` CLI command
*/
get cliOpenDirectory$ (): Observable<string> { return this.cliOpenDirectory }
/**
* Fired for the `terminus run` CLI command
*/
get cliRunCommand$ (): Observable<string[]> { return this.cliRunCommand }
/**
* Fired for the `terminus paste` CLI command
*/
get cliPaste$ (): Observable<string> { return this.cliPaste }
/**
* Fired for the `terminus profile` CLI command
*/
get cliOpenProfile$ (): Observable<string> { return this.cliOpenProfile }
/**
* Fired when another window modified the config file
*/
get configChangeBroadcast$ (): Observable<void> { return this.configChangeBroadcast }
/**
* Fired when the window close button is pressed
*/
get windowCloseRequest$ (): Observable<void> { return this.windowCloseRequest }
get windowMoved$ (): Observable<void> { return this.windowMoved }
get displayMetricsChanged$ (): Observable<void> { return this.displayMetricsChanged }
/** @hidden */
constructor (
private zone: NgZone,
private electron: ElectronService,
log: LogService,
) {
this.logger = log.create('hostApp')
this.nodePlatform = require('os').platform()
this.platform = {
win32: Platform.Windows,
darwin: Platform.macOS,
linux: Platform.Linux
}[this.nodePlatform]
}[process.platform]
this.windowId = parseInt(location.search.substring(1))
this.logger.info('Window ID:', this.windowId)
@@ -78,6 +124,14 @@ export class HostAppService {
this.zone.run(() => this.windowCloseRequest.next())
})
electron.ipcRenderer.on('host:window-moved', () => {
this.zone.run(() => this.windowMoved.next())
})
electron.ipcRenderer.on('host:display-metrics-changed', () => {
this.zone.run(() => this.displayMetricsChanged.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]
@@ -91,6 +145,8 @@ export class HostAppService {
text = shellEscape([text])
}
this.cliPaste.next(text)
} else if (op === 'profile') {
this.cliOpenProfile.next(argv.profileName)
} else {
this.secondInstance.next()
}
@@ -101,6 +157,9 @@ export class HostAppService {
}))
}
/**
* Returns the current remote [[BrowserWindow]]
*/
getWindow () {
return this.electron.BrowserWindow.fromId(this.windowId)
}
@@ -109,18 +168,6 @@ export class HostAppService {
this.electron.ipcRenderer.send('app:new-window')
}
getShell () {
return this.electron.shell
}
getAppPath () {
return this.electron.app.getAppPath()
}
getPath (type: string) {
return this.electron.app.getPath(type)
}
toggleFullscreen () {
let window = this.getWindow()
window.setFullScreen(!this.isFullScreen)
@@ -158,6 +205,11 @@ export class HostAppService {
this.electron.ipcRenderer.send('window-set-always-on-top', flag)
}
/**
* Sets window vibrancy mode (Windows, macOS)
*
* @param type `null`, or `fluent` when supported (Windowd only)
*/
setVibrancy (enable: boolean, type: string) {
document.body.classList.toggle('vibrant', enable)
if (this.platform === Platform.macOS) {
@@ -180,6 +232,9 @@ export class HostAppService {
this.electron.Menu.buildFromTemplate(menuDefinition).popup({})
}
/**
* Notifies other windows of config file changes
*/
broadcastConfigChange () {
this.electron.ipcRenderer.send('app:config-change')
}

View File

@@ -5,19 +5,19 @@ import { ConfigService } from '../services/config.service'
import { ElectronService } from '../services/electron.service'
export interface PartialHotkeyMatch {
id: string,
strokes: string[],
matchedLength: number,
id: string
strokes: string[]
matchedLength: number
}
const KEY_TIMEOUT = 2000
interface EventBufferEntry {
event: NativeKeyEvent,
time: number,
event: NativeKeyEvent
time: number
}
@Injectable()
@Injectable({ providedIn: 'root' })
export class HotkeysService {
key = new EventEmitter<NativeKeyEvent>()
matchedHotkey = new EventEmitter<string>()
@@ -26,6 +26,7 @@ export class HotkeysService {
private disabledLevel = 0
private hotkeyDescriptions: IHotkeyDescription[] = []
/** @hidden */
constructor (
private zone: NgZone,
private electron: ElectronService,
@@ -51,11 +52,20 @@ export class HotkeysService {
})
}
/**
* Adds a new key event to the buffer
*
* @param name DOM event name
* @param nativeEvent event object
*/
pushKeystroke (name, nativeEvent) {
nativeEvent.event = name
this.currentKeystrokes.push({ event: nativeEvent, time: performance.now() })
}
/**
* Check the buffer for new complete keystrokes
*/
processKeystrokes () {
if (this.isEnabled()) {
this.zone.run(() => {
@@ -80,13 +90,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 () {
private 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]
}
@@ -103,11 +113,11 @@ export class HotkeysService {
})
}
getHotkeysConfig () {
private getHotkeysConfig () {
return this.getHotkeysConfigRecursive(this.config.store.hotkeys)
}
getHotkeysConfigRecursive (branch) {
private getHotkeysConfigRecursive (branch) {
let keys = {}
for (let key in branch) {
let value = branch[key]
@@ -120,14 +130,16 @@ 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 {
private getCurrentFullyMatchedHotkey (): string {
let currentStrokes = this.getCurrentKeystrokes()
let config = this.getHotkeysConfig()
for (let id in config) {
@@ -197,81 +209,3 @@ export class HotkeysService {
).reduce((a, b) => a.concat(b))
}
}
@Injectable()
export class AppHotkeyProvider extends HotkeyProvider {
hotkeys: IHotkeyDescription[] = [
{
id: 'new-window',
name: 'New window',
},
{
id: 'toggle-window',
name: 'Toggle terminal window',
},
{
id: 'toggle-fullscreen',
name: 'Toggle fullscreen mode',
},
{
id: 'close-tab',
name: 'Close tab',
},
{
id: 'toggle-last-tab',
name: 'Toggle last tab',
},
{
id: 'next-tab',
name: 'Next tab',
},
{
id: 'previous-tab',
name: 'Previous tab',
},
{
id: 'tab-1',
name: 'Tab 1',
},
{
id: 'tab-2',
name: 'Tab 2',
},
{
id: 'tab-3',
name: 'Tab 3',
},
{
id: 'tab-4',
name: 'Tab 4',
},
{
id: 'tab-5',
name: 'Tab 5',
},
{
id: 'tab-6',
name: 'Tab 6',
},
{
id: 'tab-7',
name: 'Tab 7',
},
{
id: 'tab-8',
name: 'Tab 8',
},
{
id: 'tab-9',
name: 'Tab 9',
},
{
id: 'tab-10',
name: 'Tab 10',
},
]
async provide (): Promise<IHotkeyDescription[]> {
return this.hotkeys
}
}

View File

@@ -11,13 +11,13 @@ export const altKeyName = {
}[process.platform]
export interface NativeKeyEvent {
event?: string,
altKey: boolean,
ctrlKey: boolean,
metaKey: boolean,
shiftKey: boolean,
key: string,
keyCode: string,
event?: string
altKey: boolean
ctrlKey: boolean
metaKey: boolean
shiftKey: boolean
key: string
keyCode: string
}
export function stringifyKeySequence (events: NativeKeyEvent[]): string[] {

View File

@@ -23,7 +23,7 @@ const initializeWinston = (electron: ElectronService) => {
colorize: false
}),
new winston.transports.Console({
level: 'info',
level: 'debug',
handleExceptions: false,
json: false,
colorize: true
@@ -39,7 +39,7 @@ export class Logger {
private name: string,
) {}
doLog (level: string, ...args: any[]) {
private doLog (level: string, ...args: any[]) {
console[level](`%c[${this.name}]`, 'color: #aaa', ...args)
if (this.winstonLogger) {
this.winstonLogger[level](...args)
@@ -53,10 +53,11 @@ export class Logger {
log (...args: any[]) { this.doLog('log', ...args) }
}
@Injectable()
@Injectable({ providedIn: 'root' })
export class LogService {
private log: any
/** @hidden */
constructor (electron: ElectronService) {
this.log = initializeWinston(electron)
}

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
@@ -37,7 +37,7 @@ export class ShellIntegrationService {
this.updatePaths()
}
async updatePaths (): Promise<void> {
private async updatePaths (): Promise<void> {
// Update paths in case of an update
if (this.hostApp.platform === Platform.Windows) {
if (await this.isInstalled()) {
@@ -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

@@ -2,52 +2,57 @@ import { Injectable, Inject } from '@angular/core'
import { TabRecoveryProvider, RecoveredTab } from '../api/tabRecovery'
import { BaseTabComponent } from '../components/baseTab.component'
import { Logger, LogService } from '../services/log.service'
import { AppService } from '../services/app.service'
import { ConfigService } from '../services/config.service'
@Injectable()
/** @hidden */
@Injectable({ providedIn: 'root' })
export class TabRecoveryService {
logger: Logger
constructor (
@Inject(TabRecoveryProvider) private tabRecoveryProviders: TabRecoveryProvider[],
private app: AppService,
private config: ConfigService,
log: LogService
) {
this.logger = log.create('tabRecovery')
app.tabsChanged$.subscribe(() => {
this.saveTabs(app.tabs)
})
}
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)
)
)
}
async recoverTabs (): Promise<void> {
async recoverTab (token: any): Promise<RecoveredTab> {
for (let provider of this.config.enabledServices(this.tabRecoveryProviders)) {
try {
let tab = await provider.recover(token)
if (tab) {
return tab
}
} catch (error) {
this.logger.warn('Tab recovery crashed:', token, provider, error)
}
}
return null
}
async recoverTabs (): Promise<RecoveredTab[]> {
if (window.localStorage.tabsRecovery) {
let tabs: RecoveredTab[] = []
for (let token of JSON.parse(window.localStorage.tabsRecovery)) {
for (let provider of this.config.enabledServices(this.tabRecoveryProviders)) {
try {
let tab = await provider.recover(token)
if (tab) {
tabs.push(tab)
}
} catch (error) {
this.logger.warn('Tab recovery crashed:', token, provider, error)
}
let tab = await this.recoverTab(token)
if (tab) {
tabs.push(tab)
}
}
tabs.forEach(tab => {
this.app.openNewTab(tab.type, tab.options)
})
return tabs
}
return []
}
}

View File

@@ -0,0 +1,42 @@
import { Injectable, ComponentFactoryResolver, Injector } from '@angular/core'
import { BaseTabComponent } from '../components/baseTab.component'
import { TabRecoveryService } from './tabRecovery.service'
export declare type TabComponentType = new (...args: any[]) => BaseTabComponent
@Injectable({ providedIn: 'root' })
export class TabsService {
/** @hidden */
constructor (
private componentFactoryResolver: ComponentFactoryResolver,
private injector: Injector,
private tabRecovery: TabRecoveryService,
) { }
/**
* Instantiates a tab component and assigns given inputs
*/
create (type: TabComponentType, inputs?: any): BaseTabComponent {
let componentFactory = this.componentFactoryResolver.resolveComponentFactory(type)
let componentRef = componentFactory.create(this.injector)
let tab = componentRef.instance
tab.hostView = componentRef.hostView
Object.assign(tab, inputs || {})
return tab
}
/**
* Duplicates an existing tab instance (using the tab recovery system)
*/
async duplicate (tab: BaseTabComponent): Promise<BaseTabComponent> {
let token = await tab.getRecoveryToken()
if (!token) {
return null
}
let dup = await this.tabRecovery.recoverTab(token)
if (dup) {
return this.create(dup.type, dup.options)
}
return null
}
}

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