mirror of
https://github.com/Eugeny/tabby.git
synced 2025-08-10 19:31:52 +00:00
Compare commits
97 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
ff49b9e38a | ||
![]() |
439e407595 | ||
![]() |
1eed32f8d8 | ||
![]() |
66098b5c6d | ||
![]() |
a725d25e46 | ||
![]() |
4e42dfd46b | ||
![]() |
c2657568a6 | ||
![]() |
dbe7b8cf56 | ||
![]() |
a82a65ed46 | ||
![]() |
893d9a9887 | ||
![]() |
1facd46901 | ||
![]() |
7af89e1d07 | ||
![]() |
50b2040d16 | ||
![]() |
a65505c498 | ||
![]() |
7e8c19e97b | ||
![]() |
8ac101cf9c | ||
![]() |
6297987e4f | ||
![]() |
cbd7c7c02f | ||
![]() |
57a198b082 | ||
![]() |
e245629c5a | ||
![]() |
760311ffa0 | ||
![]() |
2f13f3a401 | ||
![]() |
5ddf36d4c1 | ||
![]() |
a632a599d3 | ||
![]() |
ca9f11484c | ||
![]() |
9d224cbce2 | ||
![]() |
7df36b89c3 | ||
![]() |
8b62aa24ea | ||
![]() |
9502240480 | ||
![]() |
31efa2f9c1 | ||
![]() |
b40192f2ad | ||
![]() |
489ea5f891 | ||
![]() |
05eb24cd99 | ||
![]() |
5053743b1b | ||
![]() |
6d7f25870e | ||
![]() |
6cdee22164 | ||
![]() |
c043d5bc83 | ||
![]() |
d7741f07a1 | ||
![]() |
14c0b8891d | ||
![]() |
ea1d8e95f3 | ||
![]() |
50c20f08f8 | ||
![]() |
8f55333d23 | ||
![]() |
3be98e6244 | ||
![]() |
5e771534a8 | ||
![]() |
ba33f18af7 | ||
![]() |
82f3b61b5e | ||
![]() |
f587fd279c | ||
![]() |
8d13cb0fe8 | ||
![]() |
d1a6baf858 | ||
![]() |
25034342c3 | ||
![]() |
379775bcd3 | ||
![]() |
ff18926bf9 | ||
![]() |
37e564130e | ||
![]() |
d8a8d41614 | ||
![]() |
7db3335938 | ||
![]() |
c1051379c1 | ||
![]() |
f7a0fb488b | ||
![]() |
2706045cc2 | ||
![]() |
908f90cd52 | ||
![]() |
67bbbd7f65 | ||
![]() |
0008b2f022 | ||
![]() |
3e61630c6a | ||
![]() |
6f912dc12b | ||
![]() |
e1f2e176ce | ||
![]() |
f39b4c6dbe | ||
![]() |
c49ff68ed6 | ||
![]() |
891cf42338 | ||
![]() |
b9763044ee | ||
![]() |
46e0035327 | ||
![]() |
6df8707b6d | ||
![]() |
24b7922539 | ||
![]() |
485665d449 | ||
![]() |
e09a011c23 | ||
![]() |
833a348fdb | ||
![]() |
26ff6f17e7 | ||
![]() |
d026e634e5 | ||
![]() |
356a2f38b6 | ||
![]() |
bdb37a9a18 | ||
![]() |
22d89041f8 | ||
![]() |
d5285cf268 | ||
![]() |
3db98aa421 | ||
![]() |
47dba5b52c | ||
![]() |
72874a1e84 | ||
![]() |
fc1deb67e8 | ||
![]() |
d0bb3c731c | ||
![]() |
13e54a46d7 | ||
![]() |
55a975bc8b | ||
![]() |
a62752efec | ||
![]() |
4e97ce5117 | ||
![]() |
19a5f2dc2d | ||
![]() |
52433afd13 | ||
![]() |
e1d9f50426 | ||
![]() |
5837c61ac4 | ||
![]() |
8c03e5b1aa | ||
![]() |
66074e3eb6 | ||
![]() |
221746f3e7 | ||
![]() |
2c59e30c39 |
@@ -415,6 +415,24 @@
|
|||||||
"contributions": [
|
"contributions": [
|
||||||
"doc"
|
"doc"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "cypherbits",
|
||||||
|
"name": "cypherbits",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/10424900?v=4",
|
||||||
|
"profile": "https://github.com/cypherbits",
|
||||||
|
"contributions": [
|
||||||
|
"doc"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "KingMob",
|
||||||
|
"name": "Matthew Davidson",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/946421?v=4",
|
||||||
|
"profile": "https://modulolotus.net",
|
||||||
|
"contributions": [
|
||||||
|
"code"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"contributorsPerLine": 7,
|
"contributorsPerLine": 7,
|
||||||
|
@@ -15,8 +15,8 @@ yarn
|
|||||||
```
|
```
|
||||||
|
|
||||||
```
|
```
|
||||||
# Linux (Debian here as an example)
|
# Linux (Debian/Ubuntu here as an example)
|
||||||
sudo apt install libfontconfig-dev libsecret-1-dev bsdtar libnss3 libatk1.0-0 libatk-bridge2.0-0 libgdk-pixbuf2.0-0 libgtk-3-0 libgbm1
|
sudo apt install libfontconfig-dev libsecret-1-dev libarchive-tools libnss3 libatk1.0-0 libatk-bridge2.0-0 libgdk-pixbuf2.0-0 libgtk-3-0 libgbm1 cmake
|
||||||
yarn
|
yarn
|
||||||
./scripts/build-native.js
|
./scripts/build-native.js
|
||||||
```
|
```
|
||||||
|
@@ -185,6 +185,8 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
|
|||||||
<td align="center"><a href="https://github.com/ydcool"><img src="https://avatars.githubusercontent.com/u/5668295?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Dominic Yin</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=ydcool" title="Code">💻</a></td>
|
<td align="center"><a href="https://github.com/ydcool"><img src="https://avatars.githubusercontent.com/u/5668295?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Dominic Yin</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=ydcool" title="Code">💻</a></td>
|
||||||
<td align="center"><a href="https://github.com/bdr99"><img src="https://avatars.githubusercontent.com/u/2292715?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Brandon Rothweiler</b></sub></a><br /><a href="#design-bdr99" title="Design">🎨</a></td>
|
<td align="center"><a href="https://github.com/bdr99"><img src="https://avatars.githubusercontent.com/u/2292715?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Brandon Rothweiler</b></sub></a><br /><a href="#design-bdr99" title="Design">🎨</a></td>
|
||||||
<td align="center"><a href="https://git.io/JnP49"><img src="https://avatars.githubusercontent.com/u/63876444?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Logic Machine</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=logicmachine123" title="Documentation">📖</a></td>
|
<td align="center"><a href="https://git.io/JnP49"><img src="https://avatars.githubusercontent.com/u/63876444?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Logic Machine</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=logicmachine123" title="Documentation">📖</a></td>
|
||||||
|
<td align="center"><a href="https://github.com/cypherbits"><img src="https://avatars.githubusercontent.com/u/10424900?v=4?s=100" width="100px;" alt=""/><br /><sub><b>cypherbits</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=cypherbits" title="Documentation">📖</a></td>
|
||||||
|
<td align="center"><a href="https://modulolotus.net"><img src="https://avatars.githubusercontent.com/u/946421?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Matthew Davidson</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=KingMob" title="Code">💻</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
@@ -117,7 +117,7 @@ export class Window {
|
|||||||
})
|
})
|
||||||
|
|
||||||
this.window.on('blur', () => {
|
this.window.on('blur', () => {
|
||||||
if (this.configStore.appearance?.dock !== 'off' && this.configStore.appearance?.dockHideOnBlur) {
|
if ((this.configStore.appearance?.dock ?? 'off') !== 'off' && this.configStore.appearance?.dockHideOnBlur) {
|
||||||
this.hide()
|
this.hide()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@@ -35,12 +35,12 @@
|
|||||||
"macos-native-processlist": "^2.0.0",
|
"macos-native-processlist": "^2.0.0",
|
||||||
"serialport": "^9.2.0",
|
"serialport": "^9.2.0",
|
||||||
"windows-blurbehind": "^1.0.1",
|
"windows-blurbehind": "^1.0.1",
|
||||||
"windows-native-registry": "^3.0.0",
|
"windows-native-registry": "^3.1.0",
|
||||||
"windows-process-tree": "^0.3.0"
|
"windows-process-tree": "^0.3.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/mz": "2.7.4",
|
"@types/mz": "2.7.4",
|
||||||
"@types/node": "16.0.0",
|
"@types/node": "16.0.1",
|
||||||
"ngx-filesize": "^2.0.16",
|
"ngx-filesize": "^2.0.16",
|
||||||
"node-abi": "^2.30.0"
|
"node-abi": "^2.30.0"
|
||||||
},
|
},
|
||||||
|
@@ -115,7 +115,8 @@ ngb-typeahead-window {
|
|||||||
|
|
||||||
.hover-reveal-parent:hover &,
|
.hover-reveal-parent:hover &,
|
||||||
*:hover > &,
|
*:hover > &,
|
||||||
&:hover {
|
&:hover,
|
||||||
|
&.show {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -162,3 +163,27 @@ ngb-typeahead-window {
|
|||||||
.list-group-item > button {
|
.list-group-item > button {
|
||||||
margin: -7px 0;
|
margin: -7px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Windows high contrast mode
|
||||||
|
@media screen and (forced-colors: active) {
|
||||||
|
.custom-switch .custom-control-label::before {
|
||||||
|
background: buttonface;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-switch .custom-control-label::after {
|
||||||
|
background: buttontext;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-switch .custom-control-input:checked ~ .custom-control-label::before {
|
||||||
|
background: activetext;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-switch .custom-control-input:checked ~ .custom-control-label::after {
|
||||||
|
background: canvas;
|
||||||
|
}
|
||||||
|
|
||||||
|
color-scheme-preview, terminaltab > .content {
|
||||||
|
forced-color-adjust: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -94,10 +94,10 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@types/node" "*"
|
"@types/node" "*"
|
||||||
|
|
||||||
"@types/node@*", "@types/node@16.0.0":
|
"@types/node@*", "@types/node@16.0.1":
|
||||||
version "16.0.0"
|
version "16.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-16.0.0.tgz#067a6c49dc7a5c2412a505628e26902ae967bf6f"
|
resolved "https://registry.yarnpkg.com/@types/node/-/node-16.0.1.tgz#70cedfda26af7a2ca073fdcc9beb2fff4aa693f8"
|
||||||
integrity sha512-TmCW5HoZ2o2/z2EYi109jLqIaPIi9y/lc2LmDCWzuCi35bcaQ+OtUh6nwBiFK7SOu25FAU5+YKdqFZUwtqGSdg==
|
integrity sha512-hBOx4SUlEPKwRi6PrXuTGw1z6lz0fjsibcWCM378YxsSu/6+C30L6CR49zIBKHiwNWCYIcOLjg4OHKZaFeLAug==
|
||||||
|
|
||||||
JSONStream@^1.3.4, JSONStream@^1.3.5:
|
JSONStream@^1.3.4, JSONStream@^1.3.5:
|
||||||
version "1.3.5"
|
version "1.3.5"
|
||||||
@@ -3586,12 +3586,12 @@ windows-blurbehind@^1.0.1:
|
|||||||
resolved "https://registry.yarnpkg.com/windows-blurbehind/-/windows-blurbehind-1.0.1.tgz#ff098713873304e38330b2c54cc41bb369b587b9"
|
resolved "https://registry.yarnpkg.com/windows-blurbehind/-/windows-blurbehind-1.0.1.tgz#ff098713873304e38330b2c54cc41bb369b587b9"
|
||||||
integrity sha512-1HzHfCiM1ayrbACJu5qE9zELV24uX/tINT6kxaZwLY3rtQAoeav6x9z7LFHWoLaGDN/sYbnK+9Vk0cz7fsk5HQ==
|
integrity sha512-1HzHfCiM1ayrbACJu5qE9zELV24uX/tINT6kxaZwLY3rtQAoeav6x9z7LFHWoLaGDN/sYbnK+9Vk0cz7fsk5HQ==
|
||||||
|
|
||||||
windows-native-registry@^3.0.0:
|
windows-native-registry@^3.1.0:
|
||||||
version "3.0.0"
|
version "3.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/windows-native-registry/-/windows-native-registry-3.0.0.tgz#82e715df7a59d5054c768547d81e0bfc81a59d2e"
|
resolved "https://registry.yarnpkg.com/windows-native-registry/-/windows-native-registry-3.1.0.tgz#909ef3254519fdec57d2f149ac59a2c9dc84419a"
|
||||||
integrity sha512-Mz/9a23UivwPc23DsTOL/ZCp/XXogT+6h/khk1psOfDDusXqpomBdxNdsBBE/BvIgOExjGom0XPOfEPiDnHy7A==
|
integrity sha512-WrDysn2V7dH+EYE6cS2RF+7r2P+M0pOYWtU8iBrjV2HaGkCLlUdGUWzOdzT0JPdWwz0BkVu3IOae2xmBajQqBA==
|
||||||
dependencies:
|
dependencies:
|
||||||
node-addon-api "^3.0.0"
|
node-addon-api "^3.1.0"
|
||||||
|
|
||||||
windows-process-tree@^0.3.0:
|
windows-process-tree@^0.3.0:
|
||||||
version "0.3.0"
|
version "0.3.0"
|
||||||
|
File diff suppressed because it is too large
Load Diff
22
package.json
22
package.json
@@ -9,24 +9,26 @@
|
|||||||
"@angular/platform-browser-dynamic": "^12.0.0",
|
"@angular/platform-browser-dynamic": "^12.0.0",
|
||||||
"@fortawesome/fontawesome-free": "^5.15.3",
|
"@fortawesome/fontawesome-free": "^5.15.3",
|
||||||
"@ng-bootstrap/ng-bootstrap": "^10.0.0",
|
"@ng-bootstrap/ng-bootstrap": "^10.0.0",
|
||||||
"@sentry/cli": "^1.64.2",
|
"@sentry/cli": "^1.67.1",
|
||||||
"@sentry/electron": "^2.5.0",
|
"@sentry/electron": "^2.5.1",
|
||||||
"@tabby-gang/to-string-loader": "^1.1.7-beta.2",
|
"@tabby-gang/to-string-loader": "^1.1.7-beta.2",
|
||||||
"@types/electron-config": "^3.2.2",
|
"@types/electron-config": "^3.2.2",
|
||||||
"@types/electron-debug": "^2.1.0",
|
"@types/electron-debug": "^2.1.0",
|
||||||
"@types/fs-extra": "^9.0.12",
|
"@types/fs-extra": "^9.0.12",
|
||||||
"@types/js-yaml": "^4.0.2",
|
"@types/js-yaml": "^4.0.2",
|
||||||
"@types/node": "16.0.0",
|
"@types/node": "16.0.1",
|
||||||
|
"@types/sortablejs": "^1.10.7",
|
||||||
"@types/webpack-env": "^1.16.2",
|
"@types/webpack-env": "^1.16.2",
|
||||||
"@typescript-eslint/eslint-plugin": "^4.28.2",
|
"@typescript-eslint/eslint-plugin": "^4.28.3",
|
||||||
"@typescript-eslint/parser": "^4.28.2",
|
"@typescript-eslint/parser": "^4.28.3",
|
||||||
"apply-loader": "2.0.0",
|
"apply-loader": "2.0.0",
|
||||||
|
"axios": "^0.21.1",
|
||||||
"clone-deep": "^4.0.1",
|
"clone-deep": "^4.0.1",
|
||||||
"compare-versions": "^3.6.0",
|
"compare-versions": "^3.6.0",
|
||||||
"core-js": "^3.15.2",
|
"core-js": "^3.15.2",
|
||||||
"cross-env": "7.0.3",
|
"cross-env": "7.0.3",
|
||||||
"css-loader": "5.2.6",
|
"css-loader": "5.2.6",
|
||||||
"electron": "13.1.6",
|
"electron": "13.1.7",
|
||||||
"electron-builder": "22.10.5",
|
"electron-builder": "22.10.5",
|
||||||
"electron-download": "^4.1.1",
|
"electron-download": "^4.1.1",
|
||||||
"electron-installer-snap": "^5.1.0",
|
"electron-installer-snap": "^5.1.0",
|
||||||
@@ -40,6 +42,7 @@
|
|||||||
"json-loader": "0.5.7",
|
"json-loader": "0.5.7",
|
||||||
"lru-cache": "^6.0.0",
|
"lru-cache": "^6.0.0",
|
||||||
"macos-release": "^2.5.0",
|
"macos-release": "^2.5.0",
|
||||||
|
"ngx-sortablejs": "^11.1.0",
|
||||||
"ngx-toastr": "^14.0.0",
|
"ngx-toastr": "^14.0.0",
|
||||||
"node-abi": "^2.30.0",
|
"node-abi": "^2.30.0",
|
||||||
"node-sass": "^6.0.1",
|
"node-sass": "^6.0.1",
|
||||||
@@ -53,16 +56,19 @@
|
|||||||
"pug-static-loader": "2.0.0",
|
"pug-static-loader": "2.0.0",
|
||||||
"raw-loader": "4.0.2",
|
"raw-loader": "4.0.2",
|
||||||
"sass-loader": "^12.1.0",
|
"sass-loader": "^12.1.0",
|
||||||
|
"shell-quote": "^1.7.2",
|
||||||
"shelljs": "0.8.4",
|
"shelljs": "0.8.4",
|
||||||
"slugify": "^1.5.3",
|
"slugify": "^1.5.3",
|
||||||
|
"sortablejs": "^1.14.0",
|
||||||
"source-code-pro": "^2.38.0",
|
"source-code-pro": "^2.38.0",
|
||||||
"source-map-loader": "^3.0.0",
|
"source-map-loader": "^3.0.0",
|
||||||
"source-sans-pro": "3.6.0",
|
"source-sans-pro": "3.6.0",
|
||||||
"style-loader": "^3.0.0",
|
"ssh2": "^1.1.0",
|
||||||
|
"style-loader": "^3.1.0",
|
||||||
"svg-inline-loader": "^0.8.2",
|
"svg-inline-loader": "^0.8.2",
|
||||||
"ts-loader": "^9.2.3",
|
"ts-loader": "^9.2.3",
|
||||||
"tslib": "^2.3.0",
|
"tslib": "^2.3.0",
|
||||||
"typedoc": "^0.21.2",
|
"typedoc": "^0.21.4",
|
||||||
"typescript": "^4.3.5",
|
"typescript": "^4.3.5",
|
||||||
"url-loader": "^4.1.1",
|
"url-loader": "^4.1.1",
|
||||||
"val-loader": "4.0.0",
|
"val-loader": "4.0.0",
|
||||||
|
@@ -1,13 +1,13 @@
|
|||||||
diff --git a/node_modules/app-builder-lib/out/appInfo.js b/node_modules/app-builder-lib/out/appInfo.js
|
diff --git a/node_modules/app-builder-lib/out/appInfo.js b/node_modules/app-builder-lib/out/appInfo.js
|
||||||
index 25a159e..d8a0262 100644
|
index 25a159e..bfe0590 100644
|
||||||
--- a/node_modules/app-builder-lib/out/appInfo.js
|
--- a/node_modules/app-builder-lib/out/appInfo.js
|
||||||
+++ b/node_modules/app-builder-lib/out/appInfo.js
|
+++ b/node_modules/app-builder-lib/out/appInfo.js
|
||||||
@@ -165,7 +165,7 @@ class AppInfo {
|
@@ -165,7 +165,7 @@ class AppInfo {
|
||||||
get linuxPackageName() {
|
get linuxPackageName() {
|
||||||
const name = this.name; // https://github.com/electron-userland/electron-builder/issues/2963
|
const name = this.name; // https://github.com/electron-userland/electron-builder/issues/2963
|
||||||
|
|
||||||
- return name.startsWith("@") ? this.sanitizedProductName : name;
|
- return name.startsWith("@") ? this.sanitizedProductName : name;
|
||||||
+ return 'tabby-terminal'
|
+ return 'tabby-terminal';
|
||||||
}
|
}
|
||||||
|
|
||||||
get sanitizedName() {
|
get sanitizedName() {
|
||||||
|
15
patches/ssh2+1.1.0.patch
Normal file
15
patches/ssh2+1.1.0.patch
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
diff --git a/node_modules/ssh2/lib/protocol/Protocol.js b/node_modules/ssh2/lib/protocol/Protocol.js
|
||||||
|
index b4d1ee0..1e3ac66 100644
|
||||||
|
--- a/node_modules/ssh2/lib/protocol/Protocol.js
|
||||||
|
+++ b/node_modules/ssh2/lib/protocol/Protocol.js
|
||||||
|
@@ -254,8 +254,8 @@ class Protocol {
|
||||||
|
);
|
||||||
|
if (greeting)
|
||||||
|
this._onWrite(greeting);
|
||||||
|
- this._onWrite(this._identRaw);
|
||||||
|
- this._onWrite(CRLF);
|
||||||
|
+ this._onWrite(Buffer.concat([this._identRaw, CRLF]));
|
||||||
|
+ // this._onWrite(CRLF);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
_destruct(reason) {
|
@@ -1,5 +1,4 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
const sh = require('shelljs')
|
|
||||||
const vars = require('./vars')
|
const vars = require('./vars')
|
||||||
const log = require('npmlog')
|
const log = require('npmlog')
|
||||||
const webpack = require('webpack')
|
const webpack = require('webpack')
|
||||||
@@ -8,8 +7,7 @@ const { promisify } = require('util')
|
|||||||
const configs = [
|
const configs = [
|
||||||
'../app/webpack.main.config.js',
|
'../app/webpack.main.config.js',
|
||||||
'../app/webpack.config.js',
|
'../app/webpack.config.js',
|
||||||
'../web/webpack.config.js',
|
...vars.allPackages.map(x => `../${x}/webpack.config.js`),
|
||||||
...vars.builtinPlugins.map(x => `../${x}/webpack.config.js`),
|
|
||||||
]
|
]
|
||||||
|
|
||||||
;(async () => {
|
;(async () => {
|
||||||
|
@@ -4,8 +4,11 @@ const path = require('path')
|
|||||||
const vars = require('./vars')
|
const vars = require('./vars')
|
||||||
const log = require('npmlog')
|
const log = require('npmlog')
|
||||||
|
|
||||||
const localBinPath = path.resolve(__dirname, '../node_modules/.bin');
|
const localBinPath = path.resolve(__dirname, '../node_modules/.bin')
|
||||||
const npx = `${localBinPath}/npx`;
|
const npx = `${localBinPath}/npx`
|
||||||
|
|
||||||
|
log.info('patch')
|
||||||
|
sh.exec(`${npx} patch-package`)
|
||||||
|
|
||||||
log.info('deps', 'app')
|
log.info('deps', 'app')
|
||||||
|
|
||||||
@@ -18,16 +21,16 @@ sh.exec(`${npx} yarn install --force`)
|
|||||||
sh.cd('..')
|
sh.cd('..')
|
||||||
|
|
||||||
vars.builtinPlugins.forEach(plugin => {
|
vars.builtinPlugins.forEach(plugin => {
|
||||||
log.info('deps', plugin)
|
log.info('deps', plugin)
|
||||||
sh.cd(plugin)
|
sh.cd(plugin)
|
||||||
sh.exec(`${npx} yarn install --force`)
|
sh.exec(`${npx} yarn install --force`)
|
||||||
sh.cd('..')
|
sh.cd('..')
|
||||||
})
|
})
|
||||||
|
|
||||||
if (['darwin', 'linux'].includes(process.platform)) {
|
if (['darwin', 'linux'].includes(process.platform)) {
|
||||||
sh.cd('node_modules')
|
sh.cd('node_modules')
|
||||||
for (let x of vars.builtinPlugins) {
|
for (let x of vars.builtinPlugins) {
|
||||||
sh.ln('-fs', '../' + x, x)
|
sh.ln('-fs', '../' + x, x)
|
||||||
}
|
}
|
||||||
sh.cd('..')
|
sh.cd('..')
|
||||||
}
|
}
|
||||||
|
@@ -3,7 +3,7 @@ const sh = require('shelljs')
|
|||||||
const vars = require('./vars')
|
const vars = require('./vars')
|
||||||
const log = require('npmlog')
|
const log = require('npmlog')
|
||||||
|
|
||||||
;[...vars.builtinPlugins, 'web'].forEach(plugin => {
|
vars.allPackages.forEach(plugin => {
|
||||||
log.info('bump', plugin)
|
log.info('bump', plugin)
|
||||||
sh.cd(plugin)
|
sh.cd(plugin)
|
||||||
sh.exec('npm --no-git-tag-version version ' + vars.version)
|
sh.exec('npm --no-git-tag-version version ' + vars.version)
|
||||||
|
@@ -10,7 +10,7 @@ exports.version = exports.version.substring(1).trim()
|
|||||||
exports.version = exports.version.replace('-', '-c')
|
exports.version = exports.version.replace('-', '-c')
|
||||||
|
|
||||||
if (exports.version.includes('-c')) {
|
if (exports.version.includes('-c')) {
|
||||||
exports.version = semver.inc(exports.version, 'prepatch').replace('-0', '-nightly.0')
|
exports.version = semver.inc(exports.version, 'prepatch').replace('-0', `-nightly.${process.env.REV ?? 0}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.builtinPlugins = [
|
exports.builtinPlugins = [
|
||||||
@@ -26,6 +26,13 @@ exports.builtinPlugins = [
|
|||||||
'tabby-serial',
|
'tabby-serial',
|
||||||
'tabby-telnet',
|
'tabby-telnet',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
exports.allPackages = [
|
||||||
|
...exports.builtinPlugins,
|
||||||
|
'web',
|
||||||
|
'tabby-web-demo',
|
||||||
|
]
|
||||||
|
|
||||||
exports.bundledModules = [
|
exports.bundledModules = [
|
||||||
'@angular',
|
'@angular',
|
||||||
'@ng-bootstrap',
|
'@ng-bootstrap',
|
||||||
|
1
tabby-community-color-schemes/.gitignore
vendored
1
tabby-community-color-schemes/.gitignore
vendored
@@ -1 +0,0 @@
|
|||||||
dist
|
|
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "tabby-community-color-schemes",
|
"name": "tabby-community-color-schemes",
|
||||||
"version": "1.0.145-nightly.0",
|
"version": "1.0.148-nightly.2",
|
||||||
"description": "Community color schemes for Tabby",
|
"description": "Community color schemes for Tabby",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"tabby-builtin-plugin"
|
"tabby-builtin-plugin"
|
||||||
|
1
tabby-core/.gitignore
vendored
1
tabby-core/.gitignore
vendored
@@ -1 +0,0 @@
|
|||||||
dist
|
|
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "tabby-core",
|
"name": "tabby-core",
|
||||||
"version": "1.0.145-nightly.0",
|
"version": "1.0.148-nightly.2",
|
||||||
"description": "Tabby core",
|
"description": "Tabby core",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"tabby-builtin-plugin"
|
"tabby-builtin-plugin"
|
||||||
|
@@ -16,11 +16,11 @@ export { BootstrapData, PluginInfo, BOOTSTRAP_DATA } from './mainProcess'
|
|||||||
export { HostWindowService } from './hostWindow'
|
export { HostWindowService } from './hostWindow'
|
||||||
export { HostAppService, Platform } from './hostApp'
|
export { HostAppService, Platform } from './hostApp'
|
||||||
export { FileProvider } from './fileProvider'
|
export { FileProvider } from './fileProvider'
|
||||||
export { ProfileProvider, Profile, ProfileSettingsComponent } from './profileProvider'
|
export { ProfileProvider, Profile, PartialProfile, ProfileSettingsComponent } from './profileProvider'
|
||||||
export { PromptModalComponent } from '../components/promptModal.component'
|
export { PromptModalComponent } from '../components/promptModal.component'
|
||||||
|
|
||||||
export { AppService } from '../services/app.service'
|
export { AppService } from '../services/app.service'
|
||||||
export { ConfigService } from '../services/config.service'
|
export { ConfigService, configMerge, ConfigProxy } from '../services/config.service'
|
||||||
export { DockingService, Screen } from '../services/docking.service'
|
export { DockingService, Screen } from '../services/docking.service'
|
||||||
export { Logger, ConsoleLogger, LogService } from '../services/log.service'
|
export { Logger, ConsoleLogger, LogService } from '../services/log.service'
|
||||||
export { HomeBaseService } from '../services/homeBase.service'
|
export { HomeBaseService } from '../services/homeBase.service'
|
||||||
@@ -31,6 +31,6 @@ export { ProfilesService } from '../services/profiles.service'
|
|||||||
export { SelectorService } from '../services/selector.service'
|
export { SelectorService } from '../services/selector.service'
|
||||||
export { TabsService, NewTabParameters, TabComponentType } from '../services/tabs.service'
|
export { TabsService, NewTabParameters, TabComponentType } from '../services/tabs.service'
|
||||||
export { UpdaterService } from '../services/updater.service'
|
export { UpdaterService } from '../services/updater.service'
|
||||||
export { VaultService, Vault, VaultSecret, VAULT_SECRET_TYPE_FILE } from '../services/vault.service'
|
export { VaultService, Vault, VaultSecret, VaultFileSecret, VAULT_SECRET_TYPE_FILE } from '../services/vault.service'
|
||||||
export { FileProvidersService } from '../services/fileProviders.service'
|
export { FileProvidersService } from '../services/fileProviders.service'
|
||||||
export * from '../utils'
|
export * from '../utils'
|
||||||
|
@@ -21,6 +21,7 @@ export interface MessageBoxResult {
|
|||||||
|
|
||||||
export abstract class FileTransfer {
|
export abstract class FileTransfer {
|
||||||
abstract getName (): string
|
abstract getName (): string
|
||||||
|
abstract getMode (): number
|
||||||
abstract getSize (): number
|
abstract getSize (): number
|
||||||
abstract close (): void
|
abstract close (): void
|
||||||
|
|
||||||
@@ -95,7 +96,7 @@ export abstract class PlatformService {
|
|||||||
abstract loadConfig (): Promise<string>
|
abstract loadConfig (): Promise<string>
|
||||||
abstract saveConfig (content: string): Promise<void>
|
abstract saveConfig (content: string): Promise<void>
|
||||||
|
|
||||||
abstract startDownload (name: string, size: number): Promise<FileDownload|null>
|
abstract startDownload (name: string, mode: number, size: number): Promise<FileDownload|null>
|
||||||
abstract startUpload (options?: FileUploadOptions): Promise<FileUpload[]>
|
abstract startUpload (options?: FileUploadOptions): Promise<FileUpload[]>
|
||||||
|
|
||||||
startUploadFromDragEvent (event: DragEvent, multiple = false): FileUpload[] {
|
startUploadFromDragEvent (event: DragEvent, multiple = false): FileUpload[] {
|
||||||
@@ -188,6 +189,10 @@ export class HTMLFileUpload extends FileUpload {
|
|||||||
return this.file.name
|
return this.file.name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getMode (): number {
|
||||||
|
return 0o644
|
||||||
|
}
|
||||||
|
|
||||||
getSize (): number {
|
getSize (): number {
|
||||||
return this.file.size
|
return this.file.size
|
||||||
}
|
}
|
||||||
|
@@ -1,44 +1,56 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-type-alias */
|
||||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||||
/* eslint-disable @typescript-eslint/no-empty-function */
|
/* eslint-disable @typescript-eslint/no-empty-function */
|
||||||
import { BaseTabComponent } from '../components/baseTab.component'
|
import { BaseTabComponent } from '../components/baseTab.component'
|
||||||
import { NewTabParameters } from '../services/tabs.service'
|
import { NewTabParameters } from '../services/tabs.service'
|
||||||
|
|
||||||
export interface Profile {
|
export interface Profile {
|
||||||
id?: string
|
id: string
|
||||||
type: string
|
type: string
|
||||||
name: string
|
name: string
|
||||||
group?: string
|
group?: string
|
||||||
options?: Record<string, any>
|
options: any
|
||||||
|
|
||||||
icon?: string
|
icon?: string
|
||||||
color?: string
|
color?: string
|
||||||
disableDynamicTitle?: boolean
|
disableDynamicTitle: boolean
|
||||||
|
|
||||||
weight?: number
|
weight: number
|
||||||
isBuiltin?: boolean
|
isBuiltin: boolean
|
||||||
isTemplate?: boolean
|
isTemplate: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ProfileSettingsComponent {
|
export type PartialProfile<T extends Profile> = Omit<Omit<Omit<{
|
||||||
profile: Profile
|
[K in keyof T]?: T[K]
|
||||||
|
}, 'options'>, 'type'>, 'name'> & {
|
||||||
|
type: string
|
||||||
|
name: string
|
||||||
|
options?: {
|
||||||
|
[K in keyof T['options']]?: T['options'][K]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ProfileSettingsComponent<P extends Profile> {
|
||||||
|
profile: P
|
||||||
save?: () => void
|
save?: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export abstract class ProfileProvider {
|
export abstract class ProfileProvider<P extends Profile> {
|
||||||
id: string
|
id: string
|
||||||
name: string
|
name: string
|
||||||
supportsQuickConnect = false
|
supportsQuickConnect = false
|
||||||
settingsComponent: new (...args: any[]) => ProfileSettingsComponent
|
settingsComponent?: new (...args: any[]) => ProfileSettingsComponent<P>
|
||||||
|
configDefaults = {}
|
||||||
|
|
||||||
abstract getBuiltinProfiles (): Promise<Profile[]>
|
abstract getBuiltinProfiles (): Promise<PartialProfile<P>[]>
|
||||||
|
|
||||||
abstract getNewTabParameters (profile: Profile): Promise<NewTabParameters<BaseTabComponent>>
|
abstract getNewTabParameters (profile: PartialProfile<P>): Promise<NewTabParameters<BaseTabComponent>>
|
||||||
|
|
||||||
abstract getDescription (profile: Profile): string
|
abstract getDescription (profile: PartialProfile<P>): string
|
||||||
|
|
||||||
quickConnect (query: string): Profile|null {
|
quickConnect (query: string): PartialProfile<P>|null {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteProfile (profile: Profile): void { }
|
deleteProfile (profile: P): void { }
|
||||||
}
|
}
|
||||||
|
@@ -2,26 +2,19 @@
|
|||||||
import { Injectable } from '@angular/core'
|
import { Injectable } from '@angular/core'
|
||||||
|
|
||||||
import { ToolbarButton, ToolbarButtonProvider } from './api/toolbarButtonProvider'
|
import { ToolbarButton, ToolbarButtonProvider } from './api/toolbarButtonProvider'
|
||||||
import { SelectorService } from './services/selector.service'
|
|
||||||
import { HostAppService, Platform } from './api/hostApp'
|
import { HostAppService, Platform } from './api/hostApp'
|
||||||
import { Profile } from './api/profileProvider'
|
import { PartialProfile, Profile } from './api/profileProvider'
|
||||||
import { ConfigService } from './services/config.service'
|
import { ConfigService } from './services/config.service'
|
||||||
import { SelectorOption } from './api/selector'
|
|
||||||
import { HotkeysService } from './services/hotkeys.service'
|
import { HotkeysService } from './services/hotkeys.service'
|
||||||
import { ProfilesService } from './services/profiles.service'
|
import { ProfilesService } from './services/profiles.service'
|
||||||
import { AppService } from './services/app.service'
|
|
||||||
import { NotificationsService } from './services/notifications.service'
|
|
||||||
|
|
||||||
/** @hidden */
|
/** @hidden */
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ButtonProvider extends ToolbarButtonProvider {
|
export class ButtonProvider extends ToolbarButtonProvider {
|
||||||
constructor (
|
constructor (
|
||||||
private selector: SelectorService,
|
|
||||||
private app: AppService,
|
|
||||||
private hostApp: HostAppService,
|
private hostApp: HostAppService,
|
||||||
private profilesServices: ProfilesService,
|
private profilesService: ProfilesService,
|
||||||
private config: ConfigService,
|
private config: ConfigService,
|
||||||
private notifications: NotificationsService,
|
|
||||||
hotkeys: HotkeysService,
|
hotkeys: HotkeysService,
|
||||||
) {
|
) {
|
||||||
super()
|
super()
|
||||||
@@ -33,77 +26,17 @@ export class ButtonProvider extends ToolbarButtonProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async activate () {
|
async activate () {
|
||||||
const recentProfiles: Profile[] = this.config.store.recentProfiles
|
const profile = await this.profilesService.showProfileSelector()
|
||||||
|
if (profile) {
|
||||||
const getProfileOptions = (profile): SelectorOption<void> => {
|
this.launchProfile(profile)
|
||||||
const result: SelectorOption<void> = this.profilesServices.selectorOptionForProfile(profile)
|
|
||||||
if (recentProfiles.includes(profile)) {
|
|
||||||
result.icon = 'fas fa-history'
|
|
||||||
}
|
|
||||||
result.callback = () => this.launchProfile(profile)
|
|
||||||
return result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let options = recentProfiles.map(getProfileOptions)
|
|
||||||
if (recentProfiles.length) {
|
|
||||||
options.push({
|
|
||||||
name: 'Clear recent connections',
|
|
||||||
icon: 'fas fa-eraser',
|
|
||||||
callback: () => {
|
|
||||||
this.config.store.recentProfiles = []
|
|
||||||
this.config.save()
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
let profiles = await this.profilesServices.getProfiles()
|
|
||||||
|
|
||||||
if (!this.config.store.terminal.showBuiltinProfiles) {
|
|
||||||
profiles = profiles.filter(x => !x.isBuiltin)
|
|
||||||
}
|
|
||||||
|
|
||||||
profiles = profiles.filter(x => !x.isTemplate)
|
|
||||||
|
|
||||||
options = [...options, ...profiles.map(getProfileOptions)]
|
|
||||||
|
|
||||||
try {
|
|
||||||
const { SettingsTabComponent } = window['nodeRequire']('tabby-settings')
|
|
||||||
options.push({
|
|
||||||
name: 'Manage profiles',
|
|
||||||
icon: 'fas fa-window-restore',
|
|
||||||
callback: () => this.app.openNewTabRaw({
|
|
||||||
type: SettingsTabComponent,
|
|
||||||
inputs: { activeTab: 'profiles' },
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
} catch { }
|
|
||||||
|
|
||||||
if (this.profilesServices.getProviders().some(x => x.supportsQuickConnect)) {
|
|
||||||
options.push({
|
|
||||||
name: 'Quick connect',
|
|
||||||
freeInputPattern: 'Connect to "%s"...',
|
|
||||||
icon: 'fas fa-arrow-right',
|
|
||||||
callback: query => this.quickConnect(query),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
await this.selector.show('Select profile', options)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
quickConnect (query: string) {
|
async launchProfile (profile: PartialProfile<Profile>) {
|
||||||
for (const provider of this.profilesServices.getProviders()) {
|
await this.profilesService.openNewTabForProfile(profile)
|
||||||
const profile = provider.quickConnect(query)
|
|
||||||
if (profile) {
|
|
||||||
this.launchProfile(profile)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.notifications.error(`Could not parse "${query}"`)
|
|
||||||
}
|
|
||||||
|
|
||||||
async launchProfile (profile: Profile) {
|
let recentProfiles = this.config.store.recentProfiles
|
||||||
await this.profilesServices.openNewTabForProfile(profile)
|
recentProfiles = recentProfiles.filter(x => x.group !== profile.group || x.name !== profile.name)
|
||||||
|
|
||||||
const recentProfiles = this.config.store.recentProfiles
|
|
||||||
recentProfiles.unshift(profile)
|
recentProfiles.unshift(profile)
|
||||||
if (recentProfiles.length > 5) {
|
if (recentProfiles.length > 5) {
|
||||||
recentProfiles.pop()
|
recentProfiles.pop()
|
||||||
|
@@ -64,7 +64,7 @@ export class AppRootComponent {
|
|||||||
activeTransfersDropdownOpen = false
|
activeTransfersDropdownOpen = false
|
||||||
private logger: Logger
|
private logger: Logger
|
||||||
|
|
||||||
private constructor (
|
constructor (
|
||||||
private hotkeys: HotkeysService,
|
private hotkeys: HotkeysService,
|
||||||
private updater: UpdaterService,
|
private updater: UpdaterService,
|
||||||
public hostWindow: HostWindowService,
|
public hostWindow: HostWindowService,
|
||||||
|
@@ -1,19 +1,19 @@
|
|||||||
.modal-body
|
.modal-body
|
||||||
input.form-control(
|
input.form-control(
|
||||||
[type]='password ? "password" : "text"',
|
[type]='password ? "password" : "text"',
|
||||||
autofocus,
|
autofocus,
|
||||||
[(ngModel)]='value',
|
[(ngModel)]='value',
|
||||||
#input,
|
#input,
|
||||||
[placeholder]='prompt',
|
[placeholder]='prompt',
|
||||||
(keyup.enter)='ok()',
|
(keyup.enter)='ok()',
|
||||||
(keyup.esc)='cancel()',
|
(keyup.esc)='cancel()',
|
||||||
)
|
)
|
||||||
.d-flex.align-items-start.mt-2
|
.d-flex.align-items-start.mt-2
|
||||||
checkbox(
|
checkbox(
|
||||||
*ngIf='showRememberCheckbox',
|
*ngIf='showRememberCheckbox',
|
||||||
[(ngModel)]='remember',
|
[(ngModel)]='remember',
|
||||||
text='Remember'
|
text='Remember'
|
||||||
)
|
)
|
||||||
button.btn.btn-primary.ml-auto(
|
button.btn.btn-primary.ml-auto(
|
||||||
(click)='ok()',
|
(click)='ok()',
|
||||||
) Enter
|
) OK
|
||||||
|
@@ -2,5 +2,5 @@
|
|||||||
input.form-control(type='text', #input, [(ngModel)]='value', (keyup.enter)='save()', autofocus)
|
input.form-control(type='text', #input, [(ngModel)]='value', (keyup.enter)='save()', autofocus)
|
||||||
|
|
||||||
.modal-footer
|
.modal-footer
|
||||||
button.btn.btn-outline-primary((click)='save()') Save
|
button.btn.btn-primary((click)='save()') Save
|
||||||
button.btn.btn-outline-secondary((click)='close()') Cancel
|
button.btn.btn-secondary((click)='close()') Cancel
|
||||||
|
@@ -4,4 +4,4 @@
|
|||||||
pre {{error}}
|
pre {{error}}
|
||||||
|
|
||||||
.modal-footer
|
.modal-footer
|
||||||
button.btn.btn-outline-primary((click)='close()') Close
|
button.btn.btn-primary((click)='close()') Close
|
||||||
|
@@ -369,12 +369,7 @@ export class SplitTabComponent extends BaseTabComponent implements AfterViewInit
|
|||||||
await this.initialized$.toPromise()
|
await this.initialized$.toPromise()
|
||||||
|
|
||||||
this.attachTabView(tab)
|
this.attachTabView(tab)
|
||||||
|
this.onAfterTabAdded(tab)
|
||||||
setImmediate(() => {
|
|
||||||
this.layout()
|
|
||||||
this.tabAdded.next(tab)
|
|
||||||
this.focus(tab)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
removeTab (tab: BaseTabComponent): void {
|
removeTab (tab: BaseTabComponent): void {
|
||||||
@@ -399,6 +394,21 @@ export class SplitTabComponent extends BaseTabComponent implements AfterViewInit
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
replaceTab (tab: BaseTabComponent, newTab: BaseTabComponent): void {
|
||||||
|
const parent = this.getParentOf(tab)
|
||||||
|
if (!parent) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const position = parent.children.indexOf(tab)
|
||||||
|
parent.children[position] = newTab
|
||||||
|
this.detachTabView(tab)
|
||||||
|
this.attachTabView(newTab)
|
||||||
|
tab.parent = null
|
||||||
|
newTab.parent = this
|
||||||
|
this.recoveryStateChangedHint.next()
|
||||||
|
this.onAfterTabAdded(newTab)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Moves focus in the given direction
|
* Moves focus in the given direction
|
||||||
*/
|
*/
|
||||||
@@ -539,6 +549,14 @@ export class SplitTabComponent extends BaseTabComponent implements AfterViewInit
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private onAfterTabAdded (tab: BaseTabComponent) {
|
||||||
|
setImmediate(() => {
|
||||||
|
this.layout()
|
||||||
|
this.tabAdded.next(tab)
|
||||||
|
this.focus(tab)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
private layoutInternal (root: SplitContainer, x: number, y: number, w: number, h: number) {
|
private layoutInternal (root: SplitContainer, x: number, y: number, w: number, h: number) {
|
||||||
const size = root.orientation === 'v' ? h : w
|
const size = root.orientation === 'v' ? h : w
|
||||||
const sizes = root.ratios.map(ratio => ratio * size)
|
const sizes = root.ratios.map(ratio => ratio * size)
|
||||||
@@ -618,7 +636,7 @@ export class SplitTabComponent extends BaseTabComponent implements AfterViewInit
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** @hidden */
|
/** @hidden */
|
||||||
@Injectable()
|
@Injectable({ providedIn: 'root' })
|
||||||
export class SplitTabRecoveryProvider extends TabRecoveryProvider<SplitTabComponent> {
|
export class SplitTabRecoveryProvider extends TabRecoveryProvider<SplitTabComponent> {
|
||||||
async applicableTo (recoveryToken: RecoveryToken): Promise<boolean> {
|
async applicableTo (recoveryToken: RecoveryToken): Promise<boolean> {
|
||||||
return recoveryToken.type === 'app:split-tab'
|
return recoveryToken.type === 'app:split-tab'
|
||||||
|
@@ -13,7 +13,7 @@ import { ToolbarButton, ToolbarButtonProvider } from '../api'
|
|||||||
export class StartPageComponent {
|
export class StartPageComponent {
|
||||||
version: string
|
version: string
|
||||||
|
|
||||||
private constructor (
|
constructor (
|
||||||
private config: ConfigService,
|
private config: ConfigService,
|
||||||
private domSanitizer: DomSanitizer,
|
private domSanitizer: DomSanitizer,
|
||||||
public homeBase: HomeBaseService,
|
public homeBase: HomeBaseService,
|
||||||
|
@@ -6,9 +6,6 @@ import { BaseTabComponent } from '../components/baseTab.component'
|
|||||||
@Component({
|
@Component({
|
||||||
selector: 'tab-body',
|
selector: 'tab-body',
|
||||||
template: `
|
template: `
|
||||||
<!--perfect-scrollbar [config]="{ suppressScrollX: true }" *ngIf="scrollable">
|
|
||||||
<ng-template #scrollablePlaceholder></ng-template>
|
|
||||||
</perfect-scrollbar-->
|
|
||||||
<ng-template #placeholder></ng-template>
|
<ng-template #placeholder></ng-template>
|
||||||
`,
|
`,
|
||||||
styles: [
|
styles: [
|
||||||
|
@@ -31,7 +31,7 @@ export class TabHeaderComponent extends BaseComponent {
|
|||||||
@Input() progress: number|null
|
@Input() progress: number|null
|
||||||
@ViewChild('handle') handle?: ElementRef
|
@ViewChild('handle') handle?: ElementRef
|
||||||
|
|
||||||
private constructor (
|
constructor (
|
||||||
public app: AppService,
|
public app: AppService,
|
||||||
public config: ConfigService,
|
public config: ConfigService,
|
||||||
private hostApp: HostAppService,
|
private hostApp: HostAppService,
|
||||||
|
@@ -10,7 +10,7 @@ import { AppService } from '../services/app.service'
|
|||||||
styles: [require('./windowControls.component.scss')],
|
styles: [require('./windowControls.component.scss')],
|
||||||
})
|
})
|
||||||
export class WindowControlsComponent {
|
export class WindowControlsComponent {
|
||||||
private constructor (public hostWindow: HostWindowService, public app: AppService) { }
|
constructor (public hostWindow: HostWindowService, public app: AppService) { }
|
||||||
|
|
||||||
async closeWindow () {
|
async closeWindow () {
|
||||||
this.app.closeWindow()
|
this.app.closeWindow()
|
||||||
|
@@ -69,6 +69,8 @@ hotkeys:
|
|||||||
pane-maximize:
|
pane-maximize:
|
||||||
- 'Ctrl-Alt-Enter'
|
- 'Ctrl-Alt-Enter'
|
||||||
close-pane: []
|
close-pane: []
|
||||||
|
switch-profile:
|
||||||
|
- 'Ctrl-Alt-T'
|
||||||
profile-selector:
|
profile-selector:
|
||||||
- 'Ctrl-Shift-T'
|
- 'Ctrl-Shift-T'
|
||||||
pluginBlacklist: ['ssh']
|
pluginBlacklist: ['ssh']
|
||||||
|
@@ -70,4 +70,6 @@ hotkeys:
|
|||||||
- '⌘-Shift-W'
|
- '⌘-Shift-W'
|
||||||
profile-selector:
|
profile-selector:
|
||||||
- '⌘-E'
|
- '⌘-E'
|
||||||
|
switch-profile:
|
||||||
|
- '⌘-Shift-E'
|
||||||
pluginBlacklist: ['ssh']
|
pluginBlacklist: ['ssh']
|
||||||
|
@@ -70,6 +70,8 @@ hotkeys:
|
|||||||
pane-maximize:
|
pane-maximize:
|
||||||
- 'Ctrl-Alt-Enter'
|
- 'Ctrl-Alt-Enter'
|
||||||
close-pane: []
|
close-pane: []
|
||||||
|
switch-profile:
|
||||||
|
- 'Ctrl-Alt-T'
|
||||||
profile-selector:
|
profile-selector:
|
||||||
- 'Ctrl-Shift-T'
|
- 'Ctrl-Shift-T'
|
||||||
pluginBlacklist: []
|
pluginBlacklist: []
|
||||||
|
@@ -170,6 +170,10 @@ export class AppHotkeyProvider extends HotkeyProvider {
|
|||||||
id: 'pane-nav-next',
|
id: 'pane-nav-next',
|
||||||
name: 'Focus next pane',
|
name: 'Focus next pane',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'switch-profile',
|
||||||
|
name: 'Switch profile in the active pane',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: 'close-pane',
|
id: 'close-pane',
|
||||||
name: 'Close focused pane',
|
name: 'Close focused pane',
|
||||||
|
@@ -6,6 +6,7 @@ import { NgbModule } from '@ng-bootstrap/ng-bootstrap'
|
|||||||
import { PerfectScrollbarModule, PERFECT_SCROLLBAR_CONFIG } from 'ngx-perfect-scrollbar'
|
import { PerfectScrollbarModule, PERFECT_SCROLLBAR_CONFIG } from 'ngx-perfect-scrollbar'
|
||||||
import { NgxFilesizeModule } from 'ngx-filesize'
|
import { NgxFilesizeModule } from 'ngx-filesize'
|
||||||
import { DndModule } from 'ng2-dnd'
|
import { DndModule } from 'ng2-dnd'
|
||||||
|
import { SortablejsModule } from 'ngx-sortablejs'
|
||||||
|
|
||||||
import { AppRootComponent } from './components/appRoot.component'
|
import { AppRootComponent } from './components/appRoot.component'
|
||||||
import { CheckboxComponent } from './components/checkbox.component'
|
import { CheckboxComponent } from './components/checkbox.component'
|
||||||
@@ -30,7 +31,7 @@ import { AlwaysVisibleTypeaheadDirective } from './directives/alwaysVisibleTypea
|
|||||||
import { FastHtmlBindDirective } from './directives/fastHtmlBind.directive'
|
import { FastHtmlBindDirective } from './directives/fastHtmlBind.directive'
|
||||||
import { DropZoneDirective } from './directives/dropZone.directive'
|
import { DropZoneDirective } from './directives/dropZone.directive'
|
||||||
|
|
||||||
import { Theme, CLIHandler, TabContextMenuItemProvider, TabRecoveryProvider, HotkeyProvider, ConfigProvider, PlatformService, FileProvider, ToolbarButtonProvider, ProfilesService } from './api'
|
import { Theme, CLIHandler, TabContextMenuItemProvider, TabRecoveryProvider, HotkeyProvider, ConfigProvider, PlatformService, FileProvider, ToolbarButtonProvider, ProfilesService, ProfileProvider } from './api'
|
||||||
|
|
||||||
import { AppService } from './services/app.service'
|
import { AppService } from './services/app.service'
|
||||||
import { ConfigService } from './services/config.service'
|
import { ConfigService } from './services/config.service'
|
||||||
@@ -40,9 +41,10 @@ import { HotkeysService } from './services/hotkeys.service'
|
|||||||
import { StandardTheme, StandardCompactTheme, PaperTheme } from './theme'
|
import { StandardTheme, StandardCompactTheme, PaperTheme } from './theme'
|
||||||
import { CoreConfigProvider } from './config'
|
import { CoreConfigProvider } from './config'
|
||||||
import { AppHotkeyProvider } from './hotkeys'
|
import { AppHotkeyProvider } from './hotkeys'
|
||||||
import { TaskCompletionContextMenu, CommonOptionsContextMenu, TabManagementContextMenu } from './tabContextMenu'
|
import { TaskCompletionContextMenu, CommonOptionsContextMenu, TabManagementContextMenu, ProfilesContextMenu } from './tabContextMenu'
|
||||||
import { LastCLIHandler, ProfileCLIHandler } from './cli'
|
import { LastCLIHandler, ProfileCLIHandler } from './cli'
|
||||||
import { ButtonProvider } from './buttonProvider'
|
import { ButtonProvider } from './buttonProvider'
|
||||||
|
import { SplitLayoutProfilesService } from './profiles'
|
||||||
|
|
||||||
import 'perfect-scrollbar/css/perfect-scrollbar.css'
|
import 'perfect-scrollbar/css/perfect-scrollbar.css'
|
||||||
import 'ng2-dnd/bundles/style.css'
|
import 'ng2-dnd/bundles/style.css'
|
||||||
@@ -56,12 +58,14 @@ const PROVIDERS = [
|
|||||||
{ provide: TabContextMenuItemProvider, useClass: CommonOptionsContextMenu, multi: true },
|
{ provide: TabContextMenuItemProvider, useClass: CommonOptionsContextMenu, multi: true },
|
||||||
{ provide: TabContextMenuItemProvider, useClass: TabManagementContextMenu, multi: true },
|
{ provide: TabContextMenuItemProvider, useClass: TabManagementContextMenu, multi: true },
|
||||||
{ provide: TabContextMenuItemProvider, useClass: TaskCompletionContextMenu, multi: true },
|
{ provide: TabContextMenuItemProvider, useClass: TaskCompletionContextMenu, multi: true },
|
||||||
{ provide: TabRecoveryProvider, useClass: SplitTabRecoveryProvider, multi: true },
|
{ provide: TabContextMenuItemProvider, useClass: ProfilesContextMenu, multi: true },
|
||||||
|
{ provide: TabRecoveryProvider, useExisting: SplitTabRecoveryProvider, multi: true },
|
||||||
{ provide: CLIHandler, useClass: ProfileCLIHandler, multi: true },
|
{ provide: CLIHandler, useClass: ProfileCLIHandler, multi: true },
|
||||||
{ provide: CLIHandler, useClass: LastCLIHandler, multi: true },
|
{ provide: CLIHandler, useClass: LastCLIHandler, multi: true },
|
||||||
{ provide: PERFECT_SCROLLBAR_CONFIG, useValue: { suppressScrollX: true } },
|
{ provide: PERFECT_SCROLLBAR_CONFIG, useValue: { suppressScrollX: true } },
|
||||||
{ provide: FileProvider, useClass: VaultFileProvider, multi: true },
|
{ provide: FileProvider, useClass: VaultFileProvider, multi: true },
|
||||||
{ provide: ToolbarButtonProvider, useClass: ButtonProvider, multi: true },
|
{ provide: ToolbarButtonProvider, useClass: ButtonProvider, multi: true },
|
||||||
|
{ provide: ProfileProvider, useExisting: SplitLayoutProfilesService, multi: true },
|
||||||
]
|
]
|
||||||
|
|
||||||
/** @hidden */
|
/** @hidden */
|
||||||
@@ -74,9 +78,10 @@ const PROVIDERS = [
|
|||||||
NgxFilesizeModule,
|
NgxFilesizeModule,
|
||||||
PerfectScrollbarModule,
|
PerfectScrollbarModule,
|
||||||
DndModule.forRoot(),
|
DndModule.forRoot(),
|
||||||
|
SortablejsModule.forRoot({ animation: 150 }),
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
AppRootComponent as any,
|
AppRootComponent,
|
||||||
CheckboxComponent,
|
CheckboxComponent,
|
||||||
PromptModalComponent,
|
PromptModalComponent,
|
||||||
StartPageComponent,
|
StartPageComponent,
|
||||||
@@ -115,6 +120,7 @@ const PROVIDERS = [
|
|||||||
DropZoneDirective,
|
DropZoneDirective,
|
||||||
FastHtmlBindDirective,
|
FastHtmlBindDirective,
|
||||||
AlwaysVisibleTypeaheadDirective,
|
AlwaysVisibleTypeaheadDirective,
|
||||||
|
SortablejsModule,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export default class AppModule { // eslint-disable-line @typescript-eslint/no-extraneous-class
|
export default class AppModule { // eslint-disable-line @typescript-eslint/no-extraneous-class
|
||||||
|
57
tabby-core/src/profiles.ts
Normal file
57
tabby-core/src/profiles.ts
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
import slugify from 'slugify'
|
||||||
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
|
import { Injectable } from '@angular/core'
|
||||||
|
import { ConfigService, NewTabParameters, PartialProfile, Profile, ProfileProvider } from './api'
|
||||||
|
import { SplitTabComponent, SplitTabRecoveryProvider } from './components/splitTab.component'
|
||||||
|
|
||||||
|
export interface SplitLayoutProfileOptions {
|
||||||
|
recoveryToken: any
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SplitLayoutProfile extends Profile {
|
||||||
|
options: SplitLayoutProfileOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
@Injectable({ providedIn: 'root' })
|
||||||
|
export class SplitLayoutProfilesService extends ProfileProvider<SplitLayoutProfile> {
|
||||||
|
id = 'split-layout'
|
||||||
|
name = 'Saved layout'
|
||||||
|
configDefaults = {
|
||||||
|
options: {
|
||||||
|
recoveryToken: null,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor (
|
||||||
|
private splitTabRecoveryProvider: SplitTabRecoveryProvider,
|
||||||
|
private config: ConfigService,
|
||||||
|
) {
|
||||||
|
super()
|
||||||
|
}
|
||||||
|
|
||||||
|
async getBuiltinProfiles (): Promise<PartialProfile<SplitLayoutProfile>[]> {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
async getNewTabParameters (profile: SplitLayoutProfile): Promise<NewTabParameters<SplitTabComponent>> {
|
||||||
|
return this.splitTabRecoveryProvider.recover(profile.options.recoveryToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
getDescription (_: SplitLayoutProfile): string {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
async createProfile (tab: SplitTabComponent, name: string): Promise<void> {
|
||||||
|
const token = await tab.getRecoveryToken()
|
||||||
|
const profile: PartialProfile<SplitLayoutProfile> = {
|
||||||
|
id: `${this.id}:custom:${slugify(name)}:${uuidv4()}`,
|
||||||
|
type: this.id,
|
||||||
|
name,
|
||||||
|
options: {
|
||||||
|
recoveryToken: token,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
this.config.store.profiles.push(profile)
|
||||||
|
await this.config.save()
|
||||||
|
}
|
||||||
|
}
|
@@ -1,3 +1,5 @@
|
|||||||
|
import deepClone from 'clone-deep'
|
||||||
|
import deepEqual from 'deep-equal'
|
||||||
import { v4 as uuidv4 } from 'uuid'
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
import * as yaml from 'js-yaml'
|
import * as yaml from 'js-yaml'
|
||||||
import { Observable, Subject, AsyncSubject } from 'rxjs'
|
import { Observable, Subject, AsyncSubject } from 'rxjs'
|
||||||
@@ -8,7 +10,8 @@ import { HostAppService } from '../api/hostApp'
|
|||||||
import { Vault, VaultService } from './vault.service'
|
import { Vault, VaultService } from './vault.service'
|
||||||
const deepmerge = require('deepmerge')
|
const deepmerge = require('deepmerge')
|
||||||
|
|
||||||
const configMerge = (a, b) => deepmerge(a, b, { arrayMerge: (_d, s) => s }) // eslint-disable-line @typescript-eslint/no-var-requires
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||||
|
export const configMerge = (a, b) => deepmerge(a, b, { arrayMerge: (_d, s) => s }) // eslint-disable-line @typescript-eslint/no-var-requires
|
||||||
|
|
||||||
const LATEST_VERSION = 1
|
const LATEST_VERSION = 1
|
||||||
|
|
||||||
@@ -18,7 +21,7 @@ function isStructuralMember (v) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function isNonStructuralObjectMember (v): boolean {
|
function isNonStructuralObjectMember (v): boolean {
|
||||||
return v instanceof Object && !(v instanceof Array) && v.__nonStructural
|
return v instanceof Object && (v instanceof Array || v.__nonStructural)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @hidden */
|
/** @hidden */
|
||||||
@@ -46,49 +49,63 @@ export class ConfigProxy {
|
|||||||
{
|
{
|
||||||
enumerable: true,
|
enumerable: true,
|
||||||
configurable: false,
|
configurable: false,
|
||||||
get: () => this.getValue(key),
|
get: () => this.__getValue(key),
|
||||||
set: (value) => {
|
set: (value) => {
|
||||||
this.setValue(key, value)
|
this.__setValue(key, value)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.getValue = (key: string) => { // eslint-disable-line @typescript-eslint/unbound-method
|
this.__getValue = (key: string) => { // eslint-disable-line @typescript-eslint/unbound-method
|
||||||
if (real[key] !== undefined) {
|
if (real[key] !== undefined) {
|
||||||
return real[key]
|
return real[key]
|
||||||
} else {
|
} else {
|
||||||
return this.getDefault(key)
|
if (isNonStructuralObjectMember(defaults[key])) {
|
||||||
|
// The object might be modified outside
|
||||||
|
real[key] = this.__getDefault(key)
|
||||||
|
delete real[key].__nonStructural
|
||||||
|
return real[key]
|
||||||
|
}
|
||||||
|
return this.__getDefault(key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.getDefault = (key: string) => { // eslint-disable-line @typescript-eslint/unbound-method
|
this.__getDefault = (key: string) => { // eslint-disable-line @typescript-eslint/unbound-method
|
||||||
if (isNonStructuralObjectMember(defaults[key])) {
|
return deepClone(defaults[key])
|
||||||
real[key] = { ...defaults[key] }
|
|
||||||
delete real[key].__nonStructural
|
|
||||||
return real[key]
|
|
||||||
} else {
|
|
||||||
return defaults[key]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setValue = (key: string, value: any) => { // eslint-disable-line @typescript-eslint/unbound-method
|
this.__setValue = (key: string, value: any) => { // eslint-disable-line @typescript-eslint/unbound-method
|
||||||
if (value === this.getDefault(key)) {
|
if (deepEqual(value, this.__getDefault(key))) {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
||||||
delete real[key]
|
delete real[key]
|
||||||
} else {
|
} else {
|
||||||
real[key] = value
|
real[key] = value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.__cleanup = () => { // eslint-disable-line @typescript-eslint/unbound-method
|
||||||
|
// Trigger removal of default values
|
||||||
|
for (const key in defaults) {
|
||||||
|
if (isStructuralMember(defaults[key])) {
|
||||||
|
this[key].__cleanup()
|
||||||
|
} else {
|
||||||
|
const v = this.__getValue(key)
|
||||||
|
this.__setValue(key, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-empty-function
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-empty-function
|
||||||
getValue (_key: string): any { }
|
__getValue (_key: string): any { }
|
||||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-empty-function
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-empty-function
|
||||||
setValue (_key: string, _value: any) { }
|
__setValue (_key: string, _value: any) { }
|
||||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-empty-function
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-empty-function
|
||||||
getDefault (_key: string): any { }
|
__getDefault (_key: string): any { }
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-empty-function
|
||||||
|
__cleanup () { }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Injectable({ providedIn: 'root' })
|
@Injectable({ providedIn: 'root' })
|
||||||
@@ -177,6 +194,7 @@ export class ConfigService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async save (): Promise<void> {
|
async save (): Promise<void> {
|
||||||
|
this.store.__cleanup()
|
||||||
// Scrub undefined values
|
// Scrub undefined values
|
||||||
let cleanStore = JSON.parse(JSON.stringify(this._store))
|
let cleanStore = JSON.parse(JSON.stringify(this._store))
|
||||||
cleanStore = await this.maybeEncryptConfig(cleanStore)
|
cleanStore = await this.maybeEncryptConfig(cleanStore)
|
||||||
|
@@ -3,6 +3,7 @@ import { Observable, Subject } from 'rxjs'
|
|||||||
import { HotkeyDescription, HotkeyProvider } from '../api/hotkeyProvider'
|
import { HotkeyDescription, HotkeyProvider } from '../api/hotkeyProvider'
|
||||||
import { stringifyKeySequence, EventData } from './hotkeys.util'
|
import { stringifyKeySequence, EventData } from './hotkeys.util'
|
||||||
import { ConfigService } from './config.service'
|
import { ConfigService } from './config.service'
|
||||||
|
import { HostAppService, Platform } from '../api/hostApp'
|
||||||
import { deprecate } from 'util'
|
import { deprecate } from 'util'
|
||||||
|
|
||||||
export interface PartialHotkeyMatch {
|
export interface PartialHotkeyMatch {
|
||||||
@@ -35,6 +36,7 @@ export class HotkeysService {
|
|||||||
private zone: NgZone,
|
private zone: NgZone,
|
||||||
private config: ConfigService,
|
private config: ConfigService,
|
||||||
@Inject(HotkeyProvider) private hotkeyProviders: HotkeyProvider[],
|
@Inject(HotkeyProvider) private hotkeyProviders: HotkeyProvider[],
|
||||||
|
hostApp: HostAppService,
|
||||||
) {
|
) {
|
||||||
const events = ['keydown', 'keyup']
|
const events = ['keydown', 'keyup']
|
||||||
events.forEach(event => {
|
events.forEach(event => {
|
||||||
@@ -43,6 +45,10 @@ export class HotkeysService {
|
|||||||
this.pushKeystroke(event, nativeEvent)
|
this.pushKeystroke(event, nativeEvent)
|
||||||
this.processKeystrokes()
|
this.processKeystrokes()
|
||||||
this.emitKeyEvent(nativeEvent)
|
this.emitKeyEvent(nativeEvent)
|
||||||
|
if (hostApp.platform === Platform.Web) {
|
||||||
|
nativeEvent.preventDefault()
|
||||||
|
nativeEvent.stopPropagation()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -53,7 +59,7 @@ export class HotkeysService {
|
|||||||
|
|
||||||
// deprecated
|
// deprecated
|
||||||
this.hotkey$.subscribe(h => this.matchedHotkey.emit(h))
|
this.hotkey$.subscribe(h => this.matchedHotkey.emit(h))
|
||||||
this.matchedHotkey.subscribe = deprecate(s => this.matchedHotkey.subscribe(s), 'matchedHotkey is deprecated, use hotkey$')
|
this.matchedHotkey.subscribe = deprecate(s => this.hotkey$.subscribe(s), 'matchedHotkey is deprecated, use hotkey$')
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -63,7 +69,7 @@ export class HotkeysService {
|
|||||||
* @param nativeEvent event object
|
* @param nativeEvent event object
|
||||||
*/
|
*/
|
||||||
pushKeystroke (name: string, nativeEvent: KeyboardEvent): void {
|
pushKeystroke (name: string, nativeEvent: KeyboardEvent): void {
|
||||||
(nativeEvent as any).event = name
|
nativeEvent['event'] = name
|
||||||
this.currentKeystrokes.push({
|
this.currentKeystrokes.push({
|
||||||
ctrlKey: nativeEvent.ctrlKey,
|
ctrlKey: nativeEvent.ctrlKey,
|
||||||
metaKey: nativeEvent.metaKey,
|
metaKey: nativeEvent.metaKey,
|
||||||
|
@@ -1,25 +1,46 @@
|
|||||||
import { Injectable, Inject } from '@angular/core'
|
import { Injectable, Inject } from '@angular/core'
|
||||||
import { NewTabParameters } from './tabs.service'
|
import { NewTabParameters } from './tabs.service'
|
||||||
import { BaseTabComponent } from '../components/baseTab.component'
|
import { BaseTabComponent } from '../components/baseTab.component'
|
||||||
import { Profile, ProfileProvider } from '../api/profileProvider'
|
import { PartialProfile, Profile, ProfileProvider } from '../api/profileProvider'
|
||||||
import { SelectorOption } from '../api/selector'
|
import { SelectorOption } from '../api/selector'
|
||||||
import { AppService } from './app.service'
|
import { AppService } from './app.service'
|
||||||
import { ConfigService } from './config.service'
|
import { configMerge, ConfigProxy, ConfigService } from './config.service'
|
||||||
|
import { NotificationsService } from './notifications.service'
|
||||||
|
import { SelectorService } from './selector.service'
|
||||||
|
|
||||||
@Injectable({ providedIn: 'root' })
|
@Injectable({ providedIn: 'root' })
|
||||||
export class ProfilesService {
|
export class ProfilesService {
|
||||||
|
private profileDefaults = {
|
||||||
|
id: '',
|
||||||
|
type: '',
|
||||||
|
name: '',
|
||||||
|
group: '',
|
||||||
|
options: {},
|
||||||
|
icon: '',
|
||||||
|
color: '',
|
||||||
|
disableDynamicTitle: false,
|
||||||
|
weight: 0,
|
||||||
|
isBuiltin: false,
|
||||||
|
isTemplate: false,
|
||||||
|
}
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
private app: AppService,
|
private app: AppService,
|
||||||
private config: ConfigService,
|
private config: ConfigService,
|
||||||
@Inject(ProfileProvider) private profileProviders: ProfileProvider[],
|
private notifications: NotificationsService,
|
||||||
|
private selector: SelectorService,
|
||||||
|
@Inject(ProfileProvider) private profileProviders: ProfileProvider<Profile>[],
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
async openNewTabForProfile (profile: Profile): Promise<BaseTabComponent|null> {
|
async openNewTabForProfile <P extends Profile> (profile: PartialProfile<P>): Promise<BaseTabComponent|null> {
|
||||||
const params = await this.newTabParametersForProfile(profile)
|
const params = await this.newTabParametersForProfile(profile)
|
||||||
if (params) {
|
if (params) {
|
||||||
const tab = this.app.openNewTab(params)
|
const tab = this.app.openNewTab(params)
|
||||||
;(this.app.getParentTab(tab) ?? tab).color = profile.color ?? null
|
;(this.app.getParentTab(tab) ?? tab).color = profile.color ?? null
|
||||||
tab.setTitle(profile.name)
|
|
||||||
|
if (profile.name) {
|
||||||
|
tab.setTitle(profile.name)
|
||||||
|
}
|
||||||
if (profile.disableDynamicTitle) {
|
if (profile.disableDynamicTitle) {
|
||||||
tab['enableDynamicTitle'] = false
|
tab['enableDynamicTitle'] = false
|
||||||
}
|
}
|
||||||
@@ -28,36 +49,136 @@ export class ProfilesService {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
async newTabParametersForProfile (profile: Profile): Promise<NewTabParameters<BaseTabComponent>|null> {
|
async newTabParametersForProfile <P extends Profile> (profile: PartialProfile<P>): Promise<NewTabParameters<BaseTabComponent>|null> {
|
||||||
return this.providerForProfile(profile)?.getNewTabParameters(profile) ?? null
|
const fullProfile = this.getConfigProxyForProfile(profile)
|
||||||
|
return this.providerForProfile(fullProfile)?.getNewTabParameters(fullProfile) ?? null
|
||||||
}
|
}
|
||||||
|
|
||||||
getProviders (): ProfileProvider[] {
|
getProviders (): ProfileProvider<Profile>[] {
|
||||||
return [...this.profileProviders]
|
return [...this.profileProviders]
|
||||||
}
|
}
|
||||||
|
|
||||||
async getProfiles (): Promise<Profile[]> {
|
async getProfiles (): Promise<PartialProfile<Profile>[]> {
|
||||||
const lists = await Promise.all(this.config.enabledServices(this.profileProviders).map(x => x.getBuiltinProfiles()))
|
const lists = await Promise.all(this.config.enabledServices(this.profileProviders).map(x => x.getBuiltinProfiles()))
|
||||||
let list = lists.reduce((a, b) => a.concat(b), [])
|
let list = lists.reduce((a, b) => a.concat(b), [])
|
||||||
list = [
|
list = [
|
||||||
...this.config.store.profiles ?? [],
|
...this.config.store.profiles ?? [],
|
||||||
...list,
|
...list,
|
||||||
]
|
]
|
||||||
list.sort((a, b) => a.group?.localeCompare(b.group ?? '') ?? -1)
|
const sortKey = p => `${p.group ?? ''} / ${p.name}`
|
||||||
list.sort((a, b) => a.name.localeCompare(b.name))
|
list.sort((a, b) => sortKey(a).localeCompare(sortKey(b)))
|
||||||
list.sort((a, b) => (a.isBuiltin ? 1 : 0) - (b.isBuiltin ? 1 : 0))
|
list.sort((a, b) => (a.isBuiltin ? 1 : 0) - (b.isBuiltin ? 1 : 0))
|
||||||
return list
|
return list
|
||||||
}
|
}
|
||||||
|
|
||||||
providerForProfile (profile: Profile): ProfileProvider|null {
|
providerForProfile <T extends Profile> (profile: PartialProfile<T>): ProfileProvider<T>|null {
|
||||||
return this.profileProviders.find(x => x.id === profile.type) ?? null
|
const provider = this.profileProviders.find(x => x.id === profile.type) ?? null
|
||||||
|
return provider as unknown as ProfileProvider<T>|null
|
||||||
}
|
}
|
||||||
|
|
||||||
selectorOptionForProfile <T> (profile: Profile): SelectorOption<T> {
|
getDescription <P extends Profile> (profile: PartialProfile<P>): string|null {
|
||||||
|
profile = this.getConfigProxyForProfile(profile)
|
||||||
|
return this.providerForProfile(profile)?.getDescription(profile) ?? null
|
||||||
|
}
|
||||||
|
|
||||||
|
selectorOptionForProfile <P extends Profile, T> (profile: PartialProfile<P>): SelectorOption<T> {
|
||||||
|
const fullProfile = this.getConfigProxyForProfile(profile)
|
||||||
return {
|
return {
|
||||||
icon: profile.icon,
|
icon: profile.icon,
|
||||||
name: profile.group ? `${profile.group} / ${profile.name}` : profile.name,
|
name: profile.group ? `${fullProfile.group} / ${fullProfile.name}` : fullProfile.name,
|
||||||
description: this.providerForProfile(profile)?.getDescription(profile),
|
description: this.providerForProfile(fullProfile)?.getDescription(fullProfile),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
showProfileSelector (): Promise<PartialProfile<Profile>|null> {
|
||||||
|
return new Promise<PartialProfile<Profile>|null>(async (resolve, reject) => {
|
||||||
|
try {
|
||||||
|
const recentProfiles: PartialProfile<Profile>[] = this.config.store.recentProfiles
|
||||||
|
|
||||||
|
let options: SelectorOption<void>[] = recentProfiles.map(p => ({
|
||||||
|
...this.selectorOptionForProfile(p),
|
||||||
|
icon: 'fas fa-history',
|
||||||
|
callback: async () => {
|
||||||
|
if (p.id) {
|
||||||
|
p = (await this.getProfiles()).find(x => x.id === p.id) ?? p
|
||||||
|
}
|
||||||
|
resolve(p)
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
if (recentProfiles.length) {
|
||||||
|
options.push({
|
||||||
|
name: 'Clear recent connections',
|
||||||
|
icon: 'fas fa-eraser',
|
||||||
|
callback: async () => {
|
||||||
|
this.config.store.recentProfiles = []
|
||||||
|
this.config.save()
|
||||||
|
resolve(null)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
let profiles = await this.getProfiles()
|
||||||
|
|
||||||
|
if (!this.config.store.terminal.showBuiltinProfiles) {
|
||||||
|
profiles = profiles.filter(x => !x.isBuiltin)
|
||||||
|
}
|
||||||
|
|
||||||
|
profiles = profiles.filter(x => !x.isTemplate)
|
||||||
|
|
||||||
|
options = [...options, ...profiles.map((p): SelectorOption<void> => ({
|
||||||
|
...this.selectorOptionForProfile(p),
|
||||||
|
callback: () => resolve(p),
|
||||||
|
}))]
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { SettingsTabComponent } = window['nodeRequire']('tabby-settings')
|
||||||
|
options.push({
|
||||||
|
name: 'Manage profiles',
|
||||||
|
icon: 'fas fa-window-restore',
|
||||||
|
callback: () => {
|
||||||
|
this.app.openNewTabRaw({
|
||||||
|
type: SettingsTabComponent,
|
||||||
|
inputs: { activeTab: 'profiles' },
|
||||||
|
})
|
||||||
|
resolve(null)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
} catch { }
|
||||||
|
|
||||||
|
if (this.getProviders().some(x => x.supportsQuickConnect)) {
|
||||||
|
options.push({
|
||||||
|
name: 'Quick connect',
|
||||||
|
freeInputPattern: 'Connect to "%s"...',
|
||||||
|
icon: 'fas fa-arrow-right',
|
||||||
|
callback: query => {
|
||||||
|
const profile = this.quickConnect(query)
|
||||||
|
resolve(profile)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
await this.selector.show('Select profile or enter an address', options)
|
||||||
|
} catch (err) {
|
||||||
|
reject(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async quickConnect (query: string): Promise<PartialProfile<Profile>|null> {
|
||||||
|
for (const provider of this.getProviders()) {
|
||||||
|
if (provider.supportsQuickConnect) {
|
||||||
|
const profile = provider.quickConnect(query)
|
||||||
|
if (profile) {
|
||||||
|
return profile
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.notifications.error(`Could not parse "${query}"`)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
getConfigProxyForProfile <T extends Profile> (profile: PartialProfile<T>): T {
|
||||||
|
const provider = this.providerForProfile(profile)
|
||||||
|
const defaults = configMerge(this.profileDefaults, provider?.configDefaults ?? {})
|
||||||
|
return new ConfigProxy(profile, defaults) as unknown as T
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -30,6 +30,13 @@ export interface VaultSecret {
|
|||||||
value: string
|
value: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface VaultFileSecret extends VaultSecret {
|
||||||
|
key: {
|
||||||
|
id: string
|
||||||
|
description: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export interface Vault {
|
export interface Vault {
|
||||||
config: any
|
config: any
|
||||||
secrets: VaultSecret[]
|
secrets: VaultSecret[]
|
||||||
@@ -121,6 +128,10 @@ export class VaultService {
|
|||||||
return !!_rememberedPassphrase
|
return !!_rememberedPassphrase
|
||||||
}
|
}
|
||||||
|
|
||||||
|
forgetPassphrase (): void {
|
||||||
|
_rememberedPassphrase = null
|
||||||
|
}
|
||||||
|
|
||||||
async decrypt (storage: StoredVault, passphrase?: string): Promise<Vault> {
|
async decrypt (storage: StoredVault, passphrase?: string): Promise<Vault> {
|
||||||
if (!passphrase) {
|
if (!passphrase) {
|
||||||
passphrase = await this.getPassphrase()
|
passphrase = await this.getPassphrase()
|
||||||
@@ -128,7 +139,7 @@ export class VaultService {
|
|||||||
try {
|
try {
|
||||||
return await wrapPromise(this.zone, decryptVault(storage, passphrase))
|
return await wrapPromise(this.zone, decryptVault(storage, passphrase))
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
_rememberedPassphrase = null
|
this.forgetPassphrase()
|
||||||
if (e.toString().includes('BAD_DECRYPT')) {
|
if (e.toString().includes('BAD_DECRYPT')) {
|
||||||
this.notifications.error('Incorrect passphrase')
|
this.notifications.error('Incorrect passphrase')
|
||||||
}
|
}
|
||||||
@@ -193,6 +204,20 @@ export class VaultService {
|
|||||||
await this.save(vault)
|
await this.save(vault)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async updateSecret (secret: VaultSecret, update: VaultSecret): Promise<void> {
|
||||||
|
await this.ready$.toPromise()
|
||||||
|
const vault = await this.load()
|
||||||
|
if (!vault) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const target = vault.secrets.find(s => s.type === secret.type && this.keyMatches(secret.key, s))
|
||||||
|
if (!target) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
Object.assign(target, update)
|
||||||
|
await this.save(vault)
|
||||||
|
}
|
||||||
|
|
||||||
async removeSecret (type: string, key: Record<string, any>): Promise<void> {
|
async removeSecret (type: string, key: Record<string, any>): Promise<void> {
|
||||||
await this.ready$.toPromise()
|
await this.ready$.toPromise()
|
||||||
const vault = await this.load()
|
const vault = await this.load()
|
||||||
@@ -274,7 +299,7 @@ export class VaultFileProvider extends FileProvider {
|
|||||||
type: VAULT_SECRET_TYPE_FILE,
|
type: VAULT_SECRET_TYPE_FILE,
|
||||||
key: {
|
key: {
|
||||||
id,
|
id,
|
||||||
description,
|
description: `${description} (${transfer.getName()})`,
|
||||||
},
|
},
|
||||||
value: (await transfer.readAll()).toString('base64'),
|
value: (await transfer.readAll()).toString('base64'),
|
||||||
})
|
})
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||||
import { Injectable } from '@angular/core'
|
import { Injectable } from '@angular/core'
|
||||||
|
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
||||||
import { Subscription } from 'rxjs'
|
import { Subscription } from 'rxjs'
|
||||||
import { AppService } from './services/app.service'
|
import { AppService } from './services/app.service'
|
||||||
import { BaseTabComponent } from './components/baseTab.component'
|
import { BaseTabComponent } from './components/baseTab.component'
|
||||||
@@ -7,6 +8,11 @@ import { TabHeaderComponent } from './components/tabHeader.component'
|
|||||||
import { SplitTabComponent, SplitDirection } from './components/splitTab.component'
|
import { SplitTabComponent, SplitDirection } from './components/splitTab.component'
|
||||||
import { TabContextMenuItemProvider } from './api/tabContextMenuProvider'
|
import { TabContextMenuItemProvider } from './api/tabContextMenuProvider'
|
||||||
import { MenuItemOptions } from './api/menu'
|
import { MenuItemOptions } from './api/menu'
|
||||||
|
import { ProfilesService } from './services/profiles.service'
|
||||||
|
import { TabsService } from './services/tabs.service'
|
||||||
|
import { HotkeysService } from './services/hotkeys.service'
|
||||||
|
import { PromptModalComponent } from './components/promptModal.component'
|
||||||
|
import { SplitLayoutProfilesService } from './profiles'
|
||||||
|
|
||||||
/** @hidden */
|
/** @hidden */
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@@ -100,6 +106,8 @@ export class CommonOptionsContextMenu extends TabContextMenuItemProvider {
|
|||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
private app: AppService,
|
private app: AppService,
|
||||||
|
private ngbModal: NgbModal,
|
||||||
|
private splitLayoutProfilesService: SplitLayoutProfilesService,
|
||||||
) {
|
) {
|
||||||
super()
|
super()
|
||||||
}
|
}
|
||||||
@@ -130,6 +138,21 @@ export class CommonOptionsContextMenu extends TabContextMenuItemProvider {
|
|||||||
})) as MenuItemOptions[],
|
})) as MenuItemOptions[],
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
if (tab instanceof SplitTabComponent && tab.getAllTabs().length > 1) {
|
||||||
|
items.push({
|
||||||
|
label: 'Save layout as profile',
|
||||||
|
click: async () => {
|
||||||
|
const modal = this.ngbModal.open(PromptModalComponent)
|
||||||
|
modal.componentInstance.prompt = 'Profile name'
|
||||||
|
const name = (await modal.result)?.value
|
||||||
|
if (!name) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.splitLayoutProfilesService.createProfile(tab, name)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return items
|
return items
|
||||||
}
|
}
|
||||||
@@ -203,3 +226,65 @@ export class TaskCompletionContextMenu extends TabContextMenuItemProvider {
|
|||||||
return items
|
return items
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/** @hidden */
|
||||||
|
@Injectable()
|
||||||
|
export class ProfilesContextMenu extends TabContextMenuItemProvider {
|
||||||
|
weight = 10
|
||||||
|
|
||||||
|
constructor (
|
||||||
|
private profilesService: ProfilesService,
|
||||||
|
private tabsService: TabsService,
|
||||||
|
private app: AppService,
|
||||||
|
hotkeys: HotkeysService,
|
||||||
|
) {
|
||||||
|
super()
|
||||||
|
hotkeys.hotkey$.subscribe(hotkey => {
|
||||||
|
if (hotkey === 'switch-profile') {
|
||||||
|
let tab = this.app.activeTab
|
||||||
|
if (tab instanceof SplitTabComponent) {
|
||||||
|
tab = tab.getFocusedTab()
|
||||||
|
if (tab) {
|
||||||
|
this.switchTabProfile(tab)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async switchTabProfile (tab: BaseTabComponent) {
|
||||||
|
const profile = await this.profilesService.showProfileSelector()
|
||||||
|
if (!profile) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const params = await this.profilesService.newTabParametersForProfile(profile)
|
||||||
|
if (!params) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!await tab.canClose()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const newTab = this.tabsService.create(params)
|
||||||
|
;(tab.parent as SplitTabComponent).replaceTab(tab, newTab)
|
||||||
|
|
||||||
|
tab.destroy()
|
||||||
|
}
|
||||||
|
|
||||||
|
async getItems (tab: BaseTabComponent, tabHeader?: TabHeaderComponent): Promise<MenuItemOptions[]> {
|
||||||
|
|
||||||
|
if (!tabHeader && tab.parent instanceof SplitTabComponent && tab.parent.getAllTabs().length > 1) {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
label: 'Switch profile',
|
||||||
|
click: () => this.switchTabProfile(tab),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -306,21 +306,6 @@ search-panel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn.btn-outline-secondary {
|
|
||||||
@include button-outline-variant(#9badb9, #fff);
|
|
||||||
&:hover:not([disabled]), &:active:not([disabled]), &.active:not([disabled]) {
|
|
||||||
background-color: #3f484e;
|
|
||||||
border-color: darken(#9badb9, 25%);
|
|
||||||
}
|
|
||||||
|
|
||||||
border-color: darken(#9badb9, 25%);
|
|
||||||
|
|
||||||
&.disabled,
|
|
||||||
&:disabled {
|
|
||||||
color: #9badb9;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-warning:not(:disabled):not(.disabled) {
|
.btn-warning:not(:disabled):not(.disabled) {
|
||||||
&.active, &:active {
|
&.active, &:active {
|
||||||
color: $gray-900;
|
color: $gray-900;
|
||||||
|
1
tabby-electron/.gitignore
vendored
1
tabby-electron/.gitignore
vendored
@@ -1 +0,0 @@
|
|||||||
dist
|
|
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "tabby-electron",
|
"name": "tabby-electron",
|
||||||
"version": "1.0.145-nightly.0",
|
"version": "1.0.148-nightly.2",
|
||||||
"description": "Electron-specific bindings",
|
"description": "Electron-specific bindings",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"tabby-builtin-plugin"
|
"tabby-builtin-plugin"
|
||||||
@@ -20,7 +20,6 @@
|
|||||||
"@angular/core": "^9.1.9"
|
"@angular/core": "^9.1.9"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"axios": "^0.21.1",
|
|
||||||
"winston": "^3.3.3",
|
"winston": "^3.3.3",
|
||||||
"electron-promise-ipc": "^2.2.4"
|
"electron-promise-ipc": "^2.2.4"
|
||||||
}
|
}
|
||||||
|
@@ -2,7 +2,7 @@ import * as path from 'path'
|
|||||||
import * as fs from 'fs/promises'
|
import * as fs from 'fs/promises'
|
||||||
import * as fsSync from 'fs'
|
import * as fsSync from 'fs'
|
||||||
import * as os from 'os'
|
import * as os from 'os'
|
||||||
import promiseIpc from 'electron-promise-ipc'
|
import promiseIpc, { RendererProcessType } from 'electron-promise-ipc'
|
||||||
import { execFile } from 'mz/child_process'
|
import { execFile } from 'mz/child_process'
|
||||||
import { Injectable, NgZone } from '@angular/core'
|
import { Injectable, NgZone } from '@angular/core'
|
||||||
import { PlatformService, ClipboardContent, HostAppService, Platform, MenuItemOptions, MessageBoxOptions, MessageBoxResult, FileUpload, FileDownload, FileUploadOptions, wrapPromise } from 'tabby-core'
|
import { PlatformService, ClipboardContent, HostAppService, Platform, MenuItemOptions, MessageBoxOptions, MessageBoxResult, FileUpload, FileDownload, FileUploadOptions, wrapPromise } from 'tabby-core'
|
||||||
@@ -49,11 +49,11 @@ export class ElectronPlatformService extends PlatformService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async installPlugin (name: string, version: string): Promise<void> {
|
async installPlugin (name: string, version: string): Promise<void> {
|
||||||
await (promiseIpc as any).send('plugin-manager:install', name, version)
|
await (promiseIpc as RendererProcessType).send('plugin-manager:install', name, version)
|
||||||
}
|
}
|
||||||
|
|
||||||
async uninstallPlugin (name: string): Promise<void> {
|
async uninstallPlugin (name: string): Promise<void> {
|
||||||
await (promiseIpc as any).send('plugin-manager:uninstall', name)
|
await (promiseIpc as RendererProcessType).send('plugin-manager:uninstall', name)
|
||||||
}
|
}
|
||||||
|
|
||||||
async isProcessRunning (name: string): Promise<boolean> {
|
async isProcessRunning (name: string): Promise<boolean> {
|
||||||
@@ -205,7 +205,7 @@ export class ElectronPlatformService extends PlatformService {
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
async startDownload (name: string, size: number): Promise<FileDownload|null> {
|
async startDownload (name: string, mode: number, size: number): Promise<FileDownload|null> {
|
||||||
const result = await this.electron.dialog.showSaveDialog(
|
const result = await this.electron.dialog.showSaveDialog(
|
||||||
this.hostWindow.getWindow(),
|
this.hostWindow.getWindow(),
|
||||||
{
|
{
|
||||||
@@ -215,7 +215,7 @@ export class ElectronPlatformService extends PlatformService {
|
|||||||
if (!result.filePath) {
|
if (!result.filePath) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
const transfer = new ElectronFileDownload(result.filePath, size)
|
const transfer = new ElectronFileDownload(result.filePath, mode, size)
|
||||||
await wrapPromise(this.zone, transfer.open())
|
await wrapPromise(this.zone, transfer.open())
|
||||||
this.fileTransferStarted.next(transfer)
|
this.fileTransferStarted.next(transfer)
|
||||||
return transfer
|
return transfer
|
||||||
@@ -230,6 +230,7 @@ export class ElectronPlatformService extends PlatformService {
|
|||||||
|
|
||||||
class ElectronFileUpload extends FileUpload {
|
class ElectronFileUpload extends FileUpload {
|
||||||
private size: number
|
private size: number
|
||||||
|
private mode: number
|
||||||
private file: fs.FileHandle
|
private file: fs.FileHandle
|
||||||
private buffer: Buffer
|
private buffer: Buffer
|
||||||
|
|
||||||
@@ -239,7 +240,9 @@ class ElectronFileUpload extends FileUpload {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async open (): Promise<void> {
|
async open (): Promise<void> {
|
||||||
this.size = (await fs.stat(this.filePath)).size
|
const stat = await fs.stat(this.filePath)
|
||||||
|
this.size = stat.size
|
||||||
|
this.mode = stat.mode
|
||||||
this.file = await fs.open(this.filePath, 'r')
|
this.file = await fs.open(this.filePath, 'r')
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -247,6 +250,10 @@ class ElectronFileUpload extends FileUpload {
|
|||||||
return path.basename(this.filePath)
|
return path.basename(this.filePath)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getMode (): number {
|
||||||
|
return this.mode
|
||||||
|
}
|
||||||
|
|
||||||
getSize (): number {
|
getSize (): number {
|
||||||
return this.size
|
return this.size
|
||||||
}
|
}
|
||||||
@@ -267,19 +274,24 @@ class ElectronFileDownload extends FileDownload {
|
|||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
private filePath: string,
|
private filePath: string,
|
||||||
|
private mode: number,
|
||||||
private size: number,
|
private size: number,
|
||||||
) {
|
) {
|
||||||
super()
|
super()
|
||||||
}
|
}
|
||||||
|
|
||||||
async open (): Promise<void> {
|
async open (): Promise<void> {
|
||||||
this.file = await fs.open(this.filePath, 'w')
|
this.file = await fs.open(this.filePath, 'w', this.mode)
|
||||||
}
|
}
|
||||||
|
|
||||||
getName (): string {
|
getName (): string {
|
||||||
return path.basename(this.filePath)
|
return path.basename(this.filePath)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getMode (): number {
|
||||||
|
return this.mode
|
||||||
|
}
|
||||||
|
|
||||||
getSize (): number {
|
getSize (): number {
|
||||||
return this.size
|
return this.size
|
||||||
}
|
}
|
||||||
|
@@ -16,13 +16,6 @@ async@^3.1.0:
|
|||||||
resolved "https://registry.yarnpkg.com/async/-/async-3.2.0.tgz#b3a2685c5ebb641d3de02d161002c60fc9f85720"
|
resolved "https://registry.yarnpkg.com/async/-/async-3.2.0.tgz#b3a2685c5ebb641d3de02d161002c60fc9f85720"
|
||||||
integrity sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw==
|
integrity sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw==
|
||||||
|
|
||||||
axios@^0.21.1:
|
|
||||||
version "0.21.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.1.tgz#22563481962f4d6bde9a76d516ef0e5d3c09b2b8"
|
|
||||||
integrity sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==
|
|
||||||
dependencies:
|
|
||||||
follow-redirects "^1.10.0"
|
|
||||||
|
|
||||||
call-bind@^1.0.0, call-bind@^1.0.2:
|
call-bind@^1.0.0, call-bind@^1.0.2:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c"
|
resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c"
|
||||||
@@ -150,11 +143,6 @@ fn.name@1.x.x:
|
|||||||
resolved "https://registry.yarnpkg.com/fn.name/-/fn.name-1.1.0.tgz#26cad8017967aea8731bc42961d04a3d5988accc"
|
resolved "https://registry.yarnpkg.com/fn.name/-/fn.name-1.1.0.tgz#26cad8017967aea8731bc42961d04a3d5988accc"
|
||||||
integrity sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==
|
integrity sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==
|
||||||
|
|
||||||
follow-redirects@^1.10.0:
|
|
||||||
version "1.14.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.1.tgz#d9114ded0a1cfdd334e164e6662ad02bfd91ff43"
|
|
||||||
integrity sha512-HWqDgT7ZEkqRzBvc2s64vSZ/hfOceEol3ac/7tKwzuvEyWx3/4UegXh5oBOIotkGsObyk3xznnSRVADBgWSQVg==
|
|
||||||
|
|
||||||
function-bind@^1.1.1:
|
function-bind@^1.1.1:
|
||||||
version "1.1.1"
|
version "1.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
|
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
|
||||||
|
1
tabby-local/.gitignore
vendored
1
tabby-local/.gitignore
vendored
@@ -1 +0,0 @@
|
|||||||
dist
|
|
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "tabby-local",
|
"name": "tabby-local",
|
||||||
"version": "1.0.145-nightly.0",
|
"version": "1.0.148-nightly.2",
|
||||||
"description": "Tabby's local shell plugin",
|
"description": "Tabby's local shell plugin",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"tabby-builtin-plugin"
|
"tabby-builtin-plugin"
|
||||||
@@ -22,13 +22,11 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/deep-equal": "^1.0.0",
|
"@types/deep-equal": "^1.0.0",
|
||||||
"@types/shell-escape": "^0.2.0",
|
|
||||||
"ansi-colors": "^4.1.1",
|
"ansi-colors": "^4.1.1",
|
||||||
"dataurl": "0.1.0",
|
"dataurl": "0.1.0",
|
||||||
"deep-equal": "2.0.5",
|
"deep-equal": "2.0.5",
|
||||||
"ps-node": "^0.1.6",
|
"ps-node": "^0.1.6",
|
||||||
"runes": "^0.4.2",
|
"runes": "^0.4.2",
|
||||||
"shell-escape": "^0.2.0",
|
|
||||||
"utils-decorators": "^1.8.3"
|
"utils-decorators": "^1.8.3"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
|
41
tabby-local/src/components/commandLineEditor.component.pug
Normal file
41
tabby-local/src/components/commandLineEditor.component.pug
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
ng-container(*ngIf='!argvMode')
|
||||||
|
.form-group
|
||||||
|
label Command line
|
||||||
|
.input-group
|
||||||
|
.input-group-prepend
|
||||||
|
button.btn.btn-secondary((click)='switchToArgv()', title='Switch to split arguments')
|
||||||
|
i.fas.fa-fw.fa-caret-right
|
||||||
|
input.form-control.text-monospace(
|
||||||
|
[(ngModel)]='command',
|
||||||
|
(ngModelChange)='parseCommand()'
|
||||||
|
)
|
||||||
|
|
||||||
|
ng-container(*ngIf='argvMode')
|
||||||
|
.form-group
|
||||||
|
label Program
|
||||||
|
.input-group
|
||||||
|
.input-group-prepend
|
||||||
|
button.btn.btn-secondary((click)='switchToCommand()', title='Switch to a single-line command')
|
||||||
|
i.fas.fa-fw.fa-caret-down
|
||||||
|
input.form-control.text-monospace(
|
||||||
|
type='text',
|
||||||
|
[(ngModel)]='_model.command',
|
||||||
|
)
|
||||||
|
|
||||||
|
.form-group
|
||||||
|
label Arguments
|
||||||
|
.input-group(
|
||||||
|
*ngFor='let arg of _model.args; index as i; trackBy: trackByIndex',
|
||||||
|
)
|
||||||
|
input.form-control.text-monospace(
|
||||||
|
type='text',
|
||||||
|
[(ngModel)]='_model.args[i]',
|
||||||
|
)
|
||||||
|
.input-group-append
|
||||||
|
button.btn.btn-secondary((click)='_model.args.splice(i, 1)')
|
||||||
|
i.fas.fa-fw.fa-trash
|
||||||
|
|
||||||
|
.mt-2
|
||||||
|
button.btn.btn-secondary((click)='_model.args.push("")')
|
||||||
|
i.fas.fa-plus.mr-2
|
||||||
|
| Add
|
50
tabby-local/src/components/commandLineEditor.component.ts
Normal file
50
tabby-local/src/components/commandLineEditor.component.ts
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||||
|
import * as shellQuote from 'shell-quote'
|
||||||
|
import { Component, Input } from '@angular/core'
|
||||||
|
import { SessionOptions } from '../api'
|
||||||
|
|
||||||
|
/** @hidden */
|
||||||
|
@Component({
|
||||||
|
selector: 'command-line-editor',
|
||||||
|
template: require('./commandLineEditor.component.pug'),
|
||||||
|
})
|
||||||
|
export class CommandLineEditorComponent {
|
||||||
|
@Input() argvMode = false
|
||||||
|
@Input() _model: SessionOptions
|
||||||
|
command = ''
|
||||||
|
|
||||||
|
@Input() get model (): SessionOptions {
|
||||||
|
return this._model
|
||||||
|
}
|
||||||
|
|
||||||
|
set model (value: SessionOptions) {
|
||||||
|
this._model = value
|
||||||
|
this.updateCommand()
|
||||||
|
}
|
||||||
|
|
||||||
|
switchToCommand () {
|
||||||
|
this.updateCommand()
|
||||||
|
this.argvMode = false
|
||||||
|
}
|
||||||
|
|
||||||
|
switchToArgv () {
|
||||||
|
this.argvMode = true
|
||||||
|
}
|
||||||
|
|
||||||
|
parseCommand () {
|
||||||
|
const args = shellQuote.parse(this.command)
|
||||||
|
this.model.command = args[0] ?? ''
|
||||||
|
this.model.args = args.slice(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
updateCommand () {
|
||||||
|
this.command = shellQuote.quote([
|
||||||
|
this.model.command,
|
||||||
|
...this.model.args ?? [],
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
trackByIndex (index) {
|
||||||
|
return index
|
||||||
|
}
|
||||||
|
}
|
@@ -1,12 +1,12 @@
|
|||||||
.mb-2.d-flex.align-items-center(*ngFor='let pair of vars')
|
.mb-2.d-flex.align-items-center(*ngFor='let pair of vars')
|
||||||
.input-group
|
.input-group
|
||||||
input.form-control.w-25([(ngModel)]='pair.key', (blur)='emitUpdate()', placeholder='Variable name')
|
input.form-control.w-25.text-monospace([(ngModel)]='pair.key', (blur)='emitUpdate()', placeholder='Variable name')
|
||||||
.input-group-append
|
.input-group-append
|
||||||
.input-group-text =
|
.input-group-text =
|
||||||
input.form-control.w-50([(ngModel)]='pair.value', (blur)='emitUpdate()', placeholder='Value')
|
input.form-control.w-50.text-monospace([(ngModel)]='pair.value', (blur)='emitUpdate()', placeholder='Value')
|
||||||
.input-group-append
|
.input-group-append
|
||||||
button.btn.btn-secondary((click)='removeEnvironmentVar(pair.key)')
|
button.btn.btn-secondary((click)='removeEnvironmentVar(pair.key)')
|
||||||
i.fas.fa-trash
|
i.fas.fa-fw.fa-trash
|
||||||
|
|
||||||
button.btn.btn-secondary((click)='addEnvironmentVar()')
|
button.btn.btn-secondary((click)='addEnvironmentVar()')
|
||||||
i.fas.fa-plus.mr-2
|
i.fas.fa-plus.mr-2
|
||||||
|
@@ -1,27 +1,4 @@
|
|||||||
.form-group
|
command-line-editor([model]='profile.options')
|
||||||
label Command
|
|
||||||
input.form-control(
|
|
||||||
type='text',
|
|
||||||
[(ngModel)]='profile.options.command',
|
|
||||||
)
|
|
||||||
|
|
||||||
.form-group
|
|
||||||
label Arguments
|
|
||||||
.input-group(
|
|
||||||
*ngFor='let arg of profile.options.args; index as i; trackBy: trackByIndex',
|
|
||||||
)
|
|
||||||
input.form-control(
|
|
||||||
type='text',
|
|
||||||
[(ngModel)]='profile.options.args[i]',
|
|
||||||
)
|
|
||||||
.input-group-append
|
|
||||||
button.btn.btn-secondary((click)='profile.options.args.splice(i, 1)')
|
|
||||||
i.fas.fa-trash
|
|
||||||
|
|
||||||
.mt-2
|
|
||||||
button.btn.btn-secondary((click)='profile.options.args.push("")')
|
|
||||||
i.fas.fa-plus.mr-2
|
|
||||||
| Add
|
|
||||||
|
|
||||||
.form-line(*ngIf='uac.isAvailable')
|
.form-line(*ngIf='uac.isAvailable')
|
||||||
.header
|
.header
|
||||||
|
@@ -3,14 +3,14 @@ import { Component } from '@angular/core'
|
|||||||
import { UACService } from '../services/uac.service'
|
import { UACService } from '../services/uac.service'
|
||||||
import { LocalProfile } from '../api'
|
import { LocalProfile } from '../api'
|
||||||
import { ElectronHostWindow, ElectronService } from 'tabby-electron'
|
import { ElectronHostWindow, ElectronService } from 'tabby-electron'
|
||||||
import { ProfileSettingsComponent } from '../../../tabby-core/src/api/profileProvider'
|
import { ProfileSettingsComponent } from 'tabby-core'
|
||||||
|
|
||||||
|
|
||||||
/** @hidden */
|
/** @hidden */
|
||||||
@Component({
|
@Component({
|
||||||
template: require('./localProfileSettings.component.pug'),
|
template: require('./localProfileSettings.component.pug'),
|
||||||
})
|
})
|
||||||
export class LocalProfileSettingsComponent implements ProfileSettingsComponent {
|
export class LocalProfileSettingsComponent implements ProfileSettingsComponent<LocalProfile> {
|
||||||
profile: LocalProfile
|
profile: LocalProfile
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
@@ -40,8 +40,4 @@ export class LocalProfileSettingsComponent implements ProfileSettingsComponent {
|
|||||||
)).filePaths
|
)).filePaths
|
||||||
this.profile.options.cwd = paths[0]
|
this.profile.options.cwd = paths[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
trackByIndex (index) {
|
|
||||||
return index
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import { Component, Input, Injector } from '@angular/core'
|
import { Component, Input, Injector } from '@angular/core'
|
||||||
import { BaseTabProcess, WIN_BUILD_CONPTY_SUPPORTED, isWindowsBuild } from 'tabby-core'
|
import { BaseTabProcess, WIN_BUILD_CONPTY_SUPPORTED, isWindowsBuild } from 'tabby-core'
|
||||||
import { BaseTerminalTabComponent } from 'tabby-terminal'
|
import { BaseTerminalTabComponent } from 'tabby-terminal'
|
||||||
import { SessionOptions } from '../api'
|
import { LocalProfile, SessionOptions } from '../api'
|
||||||
import { Session } from '../session'
|
import { Session } from '../session'
|
||||||
import { UACService } from '../services/uac.service'
|
import { UACService } from '../services/uac.service'
|
||||||
|
|
||||||
@@ -13,7 +13,8 @@ import { UACService } from '../services/uac.service'
|
|||||||
animations: BaseTerminalTabComponent.animations,
|
animations: BaseTerminalTabComponent.animations,
|
||||||
})
|
})
|
||||||
export class TerminalTabComponent extends BaseTerminalTabComponent {
|
export class TerminalTabComponent extends BaseTerminalTabComponent {
|
||||||
@Input() sessionOptions: SessionOptions
|
@Input() sessionOptions: SessionOptions // Deprecated
|
||||||
|
@Input() profile: LocalProfile
|
||||||
session: Session|null = null
|
session: Session|null = null
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-useless-constructor
|
// eslint-disable-next-line @typescript-eslint/no-useless-constructor
|
||||||
@@ -25,6 +26,8 @@ export class TerminalTabComponent extends BaseTerminalTabComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit (): void {
|
ngOnInit (): void {
|
||||||
|
this.sessionOptions = this.profile.options
|
||||||
|
|
||||||
this.logger = this.log.create('terminalTab')
|
this.logger = this.log.create('terminalTab')
|
||||||
this.session = new Session(this.injector)
|
this.session = new Session(this.injector)
|
||||||
|
|
||||||
@@ -49,17 +52,17 @@ export class TerminalTabComponent extends BaseTerminalTabComponent {
|
|||||||
|
|
||||||
protected onFrontendReady (): void {
|
protected onFrontendReady (): void {
|
||||||
this.initializeSession(this.size.columns, this.size.rows)
|
this.initializeSession(this.size.columns, this.size.rows)
|
||||||
this.savedStateIsLive = this.sessionOptions.restoreFromPTYID === this.session?.getPTYID()
|
this.savedStateIsLive = this.profile.options.restoreFromPTYID === this.session?.getPTYID()
|
||||||
super.onFrontendReady()
|
super.onFrontendReady()
|
||||||
}
|
}
|
||||||
|
|
||||||
initializeSession (columns: number, rows: number): void {
|
initializeSession (columns: number, rows: number): void {
|
||||||
if (this.sessionOptions.runAsAdministrator && this.uac.isAvailable) {
|
if (this.profile.options.runAsAdministrator && this.uac.isAvailable) {
|
||||||
this.sessionOptions = this.uac.patchSessionOptionsForUAC(this.sessionOptions)
|
this.profile.options = this.uac.patchSessionOptionsForUAC(this.profile.options)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.session!.start({
|
this.session!.start({
|
||||||
...this.sessionOptions,
|
...this.profile.options,
|
||||||
width: columns,
|
width: columns,
|
||||||
height: rows,
|
height: rows,
|
||||||
})
|
})
|
||||||
@@ -71,11 +74,14 @@ export class TerminalTabComponent extends BaseTerminalTabComponent {
|
|||||||
async getRecoveryToken (): Promise<any> {
|
async getRecoveryToken (): Promise<any> {
|
||||||
const cwd = this.session ? await this.session.getWorkingDirectory() : null
|
const cwd = this.session ? await this.session.getWorkingDirectory() : null
|
||||||
return {
|
return {
|
||||||
type: 'app:terminal-tab',
|
type: 'app:local-tab',
|
||||||
sessionOptions: {
|
profile: {
|
||||||
...this.sessionOptions,
|
...this.profile,
|
||||||
cwd: cwd ?? this.sessionOptions.cwd,
|
options: {
|
||||||
restoreFromPTYID: this.session?.getPTYID(),
|
...this.profile.options,
|
||||||
|
cwd: cwd ?? this.profile.options.cwd,
|
||||||
|
restoreFromPTYID: this.session?.getPTYID(),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
savedState: this.frontend?.saveState(),
|
savedState: this.frontend?.saveState(),
|
||||||
}
|
}
|
||||||
|
@@ -13,6 +13,7 @@ import { TerminalTabComponent } from './components/terminalTab.component'
|
|||||||
import { ShellSettingsTabComponent } from './components/shellSettingsTab.component'
|
import { ShellSettingsTabComponent } from './components/shellSettingsTab.component'
|
||||||
import { EnvironmentEditorComponent } from './components/environmentEditor.component'
|
import { EnvironmentEditorComponent } from './components/environmentEditor.component'
|
||||||
import { LocalProfileSettingsComponent } from './components/localProfileSettings.component'
|
import { LocalProfileSettingsComponent } from './components/localProfileSettings.component'
|
||||||
|
import { CommandLineEditorComponent } from './components/commandLineEditor.component'
|
||||||
|
|
||||||
import { TerminalService } from './services/terminal.service'
|
import { TerminalService } from './services/terminal.service'
|
||||||
import { DockMenuService } from './services/dockMenu.service'
|
import { DockMenuService } from './services/dockMenu.service'
|
||||||
@@ -91,16 +92,18 @@ import { LocalProfilesService } from './profiles'
|
|||||||
TerminalTabComponent,
|
TerminalTabComponent,
|
||||||
ShellSettingsTabComponent,
|
ShellSettingsTabComponent,
|
||||||
LocalProfileSettingsComponent,
|
LocalProfileSettingsComponent,
|
||||||
] as any[],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
TerminalTabComponent,
|
TerminalTabComponent,
|
||||||
ShellSettingsTabComponent,
|
ShellSettingsTabComponent,
|
||||||
EnvironmentEditorComponent,
|
EnvironmentEditorComponent,
|
||||||
|
CommandLineEditorComponent,
|
||||||
LocalProfileSettingsComponent,
|
LocalProfileSettingsComponent,
|
||||||
] as any[],
|
],
|
||||||
exports: [
|
exports: [
|
||||||
TerminalTabComponent,
|
TerminalTabComponent,
|
||||||
EnvironmentEditorComponent,
|
EnvironmentEditorComponent,
|
||||||
|
CommandLineEditorComponent,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export default class LocalTerminalModule { // eslint-disable-line @typescript-eslint/no-extraneous-class
|
export default class LocalTerminalModule { // eslint-disable-line @typescript-eslint/no-extraneous-class
|
||||||
|
@@ -1,14 +1,30 @@
|
|||||||
|
import deepClone from 'clone-deep'
|
||||||
import { Injectable, Inject } from '@angular/core'
|
import { Injectable, Inject } from '@angular/core'
|
||||||
import { ProfileProvider, Profile, NewTabParameters, ConfigService, SplitTabComponent, AppService } from 'tabby-core'
|
import { ProfileProvider, NewTabParameters, ConfigService, SplitTabComponent, AppService, PartialProfile } from 'tabby-core'
|
||||||
import { TerminalTabComponent } from './components/terminalTab.component'
|
import { TerminalTabComponent } from './components/terminalTab.component'
|
||||||
import { LocalProfileSettingsComponent } from './components/localProfileSettings.component'
|
import { LocalProfileSettingsComponent } from './components/localProfileSettings.component'
|
||||||
import { ShellProvider, Shell, SessionOptions } from './api'
|
import { ShellProvider, Shell, SessionOptions, LocalProfile } from './api'
|
||||||
|
|
||||||
@Injectable({ providedIn: 'root' })
|
@Injectable({ providedIn: 'root' })
|
||||||
export class LocalProfilesService extends ProfileProvider {
|
export class LocalProfilesService extends ProfileProvider<LocalProfile> {
|
||||||
id = 'local'
|
id = 'local'
|
||||||
name = 'Local'
|
name = 'Local'
|
||||||
settingsComponent = LocalProfileSettingsComponent
|
settingsComponent = LocalProfileSettingsComponent
|
||||||
|
configDefaults = {
|
||||||
|
options: {
|
||||||
|
restoreFromPTYID: null,
|
||||||
|
command: '',
|
||||||
|
args: [],
|
||||||
|
cwd: null,
|
||||||
|
env: {
|
||||||
|
__nonStructural: true,
|
||||||
|
},
|
||||||
|
width: null,
|
||||||
|
height: null,
|
||||||
|
pauseAfterExit: false,
|
||||||
|
runAsAdministrator: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
private app: AppService,
|
private app: AppService,
|
||||||
@@ -18,7 +34,7 @@ export class LocalProfilesService extends ProfileProvider {
|
|||||||
super()
|
super()
|
||||||
}
|
}
|
||||||
|
|
||||||
async getBuiltinProfiles (): Promise<Profile[]> {
|
async getBuiltinProfiles (): Promise<PartialProfile<LocalProfile>[]> {
|
||||||
return (await this.getShells()).map(shell => ({
|
return (await this.getShells()).map(shell => ({
|
||||||
id: `local:${shell.id}`,
|
id: `local:${shell.id}`,
|
||||||
type: 'local',
|
type: 'local',
|
||||||
@@ -29,18 +45,20 @@ export class LocalProfilesService extends ProfileProvider {
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
async getNewTabParameters (profile: Profile): Promise<NewTabParameters<TerminalTabComponent>> {
|
async getNewTabParameters (profile: PartialProfile<LocalProfile>): Promise<NewTabParameters<TerminalTabComponent>> {
|
||||||
const options = { ...profile.options }
|
profile = deepClone(profile)
|
||||||
|
|
||||||
if (!options.cwd) {
|
if (!profile.options?.cwd) {
|
||||||
if (this.app.activeTab instanceof TerminalTabComponent && this.app.activeTab.session) {
|
if (this.app.activeTab instanceof TerminalTabComponent && this.app.activeTab.session) {
|
||||||
options.cwd = await this.app.activeTab.session.getWorkingDirectory()
|
profile.options ??= {}
|
||||||
|
profile.options.cwd = await this.app.activeTab.session.getWorkingDirectory() ?? undefined
|
||||||
}
|
}
|
||||||
if (this.app.activeTab instanceof SplitTabComponent) {
|
if (this.app.activeTab instanceof SplitTabComponent) {
|
||||||
const focusedTab = this.app.activeTab.getFocusedTab()
|
const focusedTab = this.app.activeTab.getFocusedTab()
|
||||||
|
|
||||||
if (focusedTab instanceof TerminalTabComponent && focusedTab.session) {
|
if (focusedTab instanceof TerminalTabComponent && focusedTab.session) {
|
||||||
options.cwd = await focusedTab.session.getWorkingDirectory()
|
profile.options ??= {}
|
||||||
|
profile.options.cwd = await focusedTab.session.getWorkingDirectory() ?? undefined
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -48,7 +66,7 @@ export class LocalProfilesService extends ProfileProvider {
|
|||||||
return {
|
return {
|
||||||
type: TerminalTabComponent,
|
type: TerminalTabComponent,
|
||||||
inputs: {
|
inputs: {
|
||||||
sessionOptions: options,
|
profile,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -66,7 +84,7 @@ export class LocalProfilesService extends ProfileProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getDescription (profile: Profile): string {
|
getDescription (profile: PartialProfile<LocalProfile>): string {
|
||||||
return profile.options?.command
|
return profile.options?.command ?? ''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -7,14 +7,14 @@ import { TerminalTabComponent } from './components/terminalTab.component'
|
|||||||
@Injectable()
|
@Injectable()
|
||||||
export class RecoveryProvider extends TabRecoveryProvider<TerminalTabComponent> {
|
export class RecoveryProvider extends TabRecoveryProvider<TerminalTabComponent> {
|
||||||
async applicableTo (recoveryToken: RecoveryToken): Promise<boolean> {
|
async applicableTo (recoveryToken: RecoveryToken): Promise<boolean> {
|
||||||
return recoveryToken.type === 'app:terminal-tab'
|
return recoveryToken.type === 'app:local-tab'
|
||||||
}
|
}
|
||||||
|
|
||||||
async recover (recoveryToken: RecoveryToken): Promise<NewTabParameters<TerminalTabComponent>> {
|
async recover (recoveryToken: RecoveryToken): Promise<NewTabParameters<TerminalTabComponent>> {
|
||||||
return {
|
return {
|
||||||
type: TerminalTabComponent,
|
type: TerminalTabComponent,
|
||||||
inputs: {
|
inputs: {
|
||||||
sessionOptions: recoveryToken.sessionOptions,
|
profile: recoveryToken.profile,
|
||||||
savedState: recoveryToken.savedState,
|
savedState: recoveryToken.savedState,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -23,9 +23,12 @@ export class RecoveryProvider extends TabRecoveryProvider<TerminalTabComponent>
|
|||||||
duplicate (recoveryToken: RecoveryToken): RecoveryToken {
|
duplicate (recoveryToken: RecoveryToken): RecoveryToken {
|
||||||
return {
|
return {
|
||||||
...recoveryToken,
|
...recoveryToken,
|
||||||
sessionOptions: {
|
profile: {
|
||||||
...recoveryToken.sessionOptions,
|
...recoveryToken.profile,
|
||||||
restoreFromPTYID: null,
|
options: {
|
||||||
|
...recoveryToken.profile.options,
|
||||||
|
restoreFromPTYID: null,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
savedState: null,
|
savedState: null,
|
||||||
}
|
}
|
||||||
|
@@ -30,7 +30,7 @@ export class DockMenuService {
|
|||||||
iconPath: process.execPath,
|
iconPath: process.execPath,
|
||||||
iconIndex: 0,
|
iconIndex: 0,
|
||||||
})),
|
})),
|
||||||
}] : null as any)
|
}] : null)
|
||||||
}
|
}
|
||||||
if (this.hostApp.platform === Platform.macOS) {
|
if (this.hostApp.platform === Platform.macOS) {
|
||||||
this.electron.app.dock.setMenu(this.electron.Menu.buildFromTemplate(
|
this.electron.app.dock.setMenu(this.electron.Menu.buildFromTemplate(
|
||||||
|
@@ -1,8 +1,8 @@
|
|||||||
import * as fs from 'mz/fs'
|
import * as fs from 'mz/fs'
|
||||||
import { Injectable } from '@angular/core'
|
import { Injectable } from '@angular/core'
|
||||||
import { Logger, LogService, ConfigService, AppService, ProfilesService } from 'tabby-core'
|
import { Logger, LogService, ConfigService, ProfilesService, PartialProfile } from 'tabby-core'
|
||||||
import { TerminalTabComponent } from '../components/terminalTab.component'
|
import { TerminalTabComponent } from '../components/terminalTab.component'
|
||||||
import { SessionOptions, LocalProfile } from '../api'
|
import { LocalProfile } from '../api'
|
||||||
|
|
||||||
@Injectable({ providedIn: 'root' })
|
@Injectable({ providedIn: 'root' })
|
||||||
export class TerminalService {
|
export class TerminalService {
|
||||||
@@ -10,7 +10,6 @@ export class TerminalService {
|
|||||||
|
|
||||||
/** @hidden */
|
/** @hidden */
|
||||||
private constructor (
|
private constructor (
|
||||||
private app: AppService,
|
|
||||||
private profilesService: ProfilesService,
|
private profilesService: ProfilesService,
|
||||||
private config: ConfigService,
|
private config: ConfigService,
|
||||||
log: LogService,
|
log: LogService,
|
||||||
@@ -18,52 +17,43 @@ export class TerminalService {
|
|||||||
this.logger = log.create('terminal')
|
this.logger = log.create('terminal')
|
||||||
}
|
}
|
||||||
|
|
||||||
async getDefaultProfile (): Promise<LocalProfile> {
|
async getDefaultProfile (): Promise<PartialProfile<LocalProfile>> {
|
||||||
const profiles = await this.profilesService.getProfiles()
|
const profiles = await this.profilesService.getProfiles()
|
||||||
let profile = profiles.find(x => x.id === this.config.store.terminal.profile)
|
let profile = profiles.find(x => x.id === this.config.store.terminal.profile)
|
||||||
if (!profile) {
|
if (!profile) {
|
||||||
profile = profiles.filter(x => x.type === 'local' && x.isBuiltin)[0]
|
profile = profiles.filter(x => x.type === 'local' && x.isBuiltin)[0]
|
||||||
}
|
}
|
||||||
return profile as LocalProfile
|
return profile as PartialProfile<LocalProfile>
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Launches a new terminal with a specific shell and CWD
|
* Launches a new terminal with a specific shell and CWD
|
||||||
* @param pause Wait for a keypress when the shell exits
|
* @param pause Wait for a keypress when the shell exits
|
||||||
*/
|
*/
|
||||||
async openTab (profile?: LocalProfile|null, cwd?: string|null, pause?: boolean): Promise<TerminalTabComponent> {
|
async openTab (profile?: PartialProfile<LocalProfile>|null, cwd?: string|null, pause?: boolean): Promise<TerminalTabComponent> {
|
||||||
if (!profile) {
|
if (!profile) {
|
||||||
profile = await this.getDefaultProfile()
|
profile = await this.getDefaultProfile()
|
||||||
}
|
}
|
||||||
|
|
||||||
cwd = cwd ?? profile.options.cwd
|
const fullProfile = this.profilesService.getConfigProxyForProfile(profile)
|
||||||
|
|
||||||
|
cwd = cwd ?? fullProfile.options.cwd
|
||||||
|
|
||||||
if (cwd && !fs.existsSync(cwd)) {
|
if (cwd && !fs.existsSync(cwd)) {
|
||||||
console.warn('Ignoring non-existent CWD:', cwd)
|
console.warn('Ignoring non-existent CWD:', cwd)
|
||||||
cwd = null
|
cwd = null
|
||||||
}
|
}
|
||||||
|
|
||||||
this.logger.info(`Starting profile ${profile.name}`, profile)
|
this.logger.info(`Starting profile ${fullProfile.name}`, fullProfile)
|
||||||
const options = {
|
const options = {
|
||||||
...profile.options,
|
...fullProfile.options,
|
||||||
pauseAfterExit: pause,
|
pauseAfterExit: pause,
|
||||||
cwd: cwd ?? undefined,
|
cwd: cwd ?? undefined,
|
||||||
}
|
}
|
||||||
|
|
||||||
return (await this.profilesService.openNewTabForProfile({
|
return (await this.profilesService.openNewTabForProfile({
|
||||||
...profile,
|
...fullProfile,
|
||||||
options,
|
options,
|
||||||
})) as TerminalTabComponent
|
})) as TerminalTabComponent
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Open a terminal with custom session options
|
|
||||||
*/
|
|
||||||
openTabWithOptions (sessionOptions: SessionOptions): TerminalTabComponent {
|
|
||||||
this.logger.info('Using session options:', sessionOptions)
|
|
||||||
return this.app.openNewTab({
|
|
||||||
type: TerminalTabComponent,
|
|
||||||
inputs: { sessionOptions },
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -104,8 +104,6 @@ export class Session extends BaseSession {
|
|||||||
}
|
}
|
||||||
|
|
||||||
start (options: SessionOptions): void {
|
start (options: SessionOptions): void {
|
||||||
this.name = options.name ?? ''
|
|
||||||
|
|
||||||
let pty: PTYProxy|null = null
|
let pty: PTYProxy|null = null
|
||||||
|
|
||||||
if (options.restoreFromPTYID) {
|
if (options.restoreFromPTYID) {
|
||||||
@@ -162,7 +160,7 @@ export class Session extends BaseSession {
|
|||||||
cwd,
|
cwd,
|
||||||
env: env,
|
env: env,
|
||||||
// `1` instead of `true` forces ConPTY even if unstable
|
// `1` instead of `true` forces ConPTY even if unstable
|
||||||
useConpty: (isWindowsBuild(WIN_BUILD_CONPTY_SUPPORTED) && this.config.store.terminal.useConPTY ? 1 : false) as any,
|
useConpty: isWindowsBuild(WIN_BUILD_CONPTY_SUPPORTED) && this.config.store.terminal.useConPTY ? 1 : false,
|
||||||
})
|
})
|
||||||
|
|
||||||
this.guessedCWD = cwd ?? null
|
this.guessedCWD = cwd ?? null
|
||||||
|
@@ -24,7 +24,7 @@ export class POSIXShellsProvider extends ShellProvider {
|
|||||||
.filter(x => x && !x.startsWith('#'))
|
.filter(x => x && !x.startsWith('#'))
|
||||||
.map(x => ({
|
.map(x => ({
|
||||||
id: slugify(x),
|
id: slugify(x),
|
||||||
name: x.split('/')[2],
|
name: x.split('/').pop(),
|
||||||
icon: 'fas fa-terminal',
|
icon: 'fas fa-terminal',
|
||||||
command: x,
|
command: x,
|
||||||
args: ['-l'],
|
args: ['-l'],
|
||||||
|
@@ -27,14 +27,14 @@ export class SaveAsProfileContextMenu extends TabContextMenuItemProvider {
|
|||||||
click: async () => {
|
click: async () => {
|
||||||
const modal = this.ngbModal.open(PromptModalComponent)
|
const modal = this.ngbModal.open(PromptModalComponent)
|
||||||
modal.componentInstance.prompt = 'New profile name'
|
modal.componentInstance.prompt = 'New profile name'
|
||||||
const name = (await modal.result)?.name
|
const name = (await modal.result)?.value
|
||||||
if (!name) {
|
if (!name) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const profile = {
|
const profile = {
|
||||||
options: {
|
options: {
|
||||||
...tab.sessionOptions,
|
...tab.profile.options,
|
||||||
cwd: await tab.session?.getWorkingDirectory() ?? tab.sessionOptions.cwd,
|
cwd: await tab.session?.getWorkingDirectory() ?? tab.profile.options.cwd,
|
||||||
},
|
},
|
||||||
name,
|
name,
|
||||||
type: 'local',
|
type: 'local',
|
||||||
@@ -74,7 +74,11 @@ export class NewTabContextMenu extends TabContextMenuItemProvider {
|
|||||||
{
|
{
|
||||||
label: 'New terminal',
|
label: 'New terminal',
|
||||||
click: () => {
|
click: () => {
|
||||||
this.terminalService.openTabWithOptions((tab as any).sessionOptions)
|
if (tab instanceof TerminalTabComponent) {
|
||||||
|
this.profilesService.openNewTabForProfile(tab.profile)
|
||||||
|
} else {
|
||||||
|
this.terminalService.openTab()
|
||||||
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -98,9 +102,12 @@ export class NewTabContextMenu extends TabContextMenuItemProvider {
|
|||||||
submenu: profiles.map(profile => ({
|
submenu: profiles.map(profile => ({
|
||||||
label: profile.name,
|
label: profile.name,
|
||||||
click: () => {
|
click: () => {
|
||||||
this.terminalService.openTabWithOptions({
|
this.profilesService.openNewTabForProfile({
|
||||||
...profile.options,
|
...profile,
|
||||||
runAsAdministrator: true,
|
options: {
|
||||||
|
...profile.options,
|
||||||
|
runAsAdministrator: true,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
})),
|
})),
|
||||||
@@ -111,9 +118,12 @@ export class NewTabContextMenu extends TabContextMenuItemProvider {
|
|||||||
items.push({
|
items.push({
|
||||||
label: 'Duplicate as administrator',
|
label: 'Duplicate as administrator',
|
||||||
click: () => {
|
click: () => {
|
||||||
this.terminalService.openTabWithOptions({
|
this.profilesService.openNewTabForProfile({
|
||||||
...tab.sessionOptions,
|
...tab.profile,
|
||||||
runAsAdministrator: true,
|
options: {
|
||||||
|
...tab.profile.options,
|
||||||
|
runAsAdministrator: true,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
@@ -7,11 +7,6 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@types/deep-equal/-/deep-equal-1.0.1.tgz#71cfabb247c22bcc16d536111f50c0ed12476b03"
|
resolved "https://registry.yarnpkg.com/@types/deep-equal/-/deep-equal-1.0.1.tgz#71cfabb247c22bcc16d536111f50c0ed12476b03"
|
||||||
integrity sha512-mMUu4nWHLBlHtxXY17Fg6+ucS/MnndyOWyOe7MmwkoMYxvfQU2ajtRaEvqSUv+aVkMqH/C0NCI8UoVfRNQ10yg==
|
integrity sha512-mMUu4nWHLBlHtxXY17Fg6+ucS/MnndyOWyOe7MmwkoMYxvfQU2ajtRaEvqSUv+aVkMqH/C0NCI8UoVfRNQ10yg==
|
||||||
|
|
||||||
"@types/shell-escape@^0.2.0":
|
|
||||||
version "0.2.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/@types/shell-escape/-/shell-escape-0.2.0.tgz#cd2f0df814388599dd07196dcc510de2669d1ed2"
|
|
||||||
integrity sha512-7kUdtJtUylvyISJbe9FMcvMTjRdP0EvNDO1WbT0lT22k/IPBiPRTpmWaKu5HTWLCGLQRWVHrzVHZktTDvvR23g==
|
|
||||||
|
|
||||||
ansi-colors@^4.1.1:
|
ansi-colors@^4.1.1:
|
||||||
version "4.1.1"
|
version "4.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348"
|
resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348"
|
||||||
@@ -357,11 +352,6 @@ runes@^0.4.2:
|
|||||||
resolved "https://registry.yarnpkg.com/runes/-/runes-0.4.3.tgz#32f7738844bc767b65cc68171528e3373c7bb355"
|
resolved "https://registry.yarnpkg.com/runes/-/runes-0.4.3.tgz#32f7738844bc767b65cc68171528e3373c7bb355"
|
||||||
integrity sha512-K6p9y4ZyL9wPzA+PMDloNQPfoDGTiFYDvdlXznyGKgD10BJpcAosvATKrExRKOrNLgD8E7Um7WGW0lxsnOuNLg==
|
integrity sha512-K6p9y4ZyL9wPzA+PMDloNQPfoDGTiFYDvdlXznyGKgD10BJpcAosvATKrExRKOrNLgD8E7Um7WGW0lxsnOuNLg==
|
||||||
|
|
||||||
shell-escape@^0.2.0:
|
|
||||||
version "0.2.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/shell-escape/-/shell-escape-0.2.0.tgz#68fd025eb0490b4f567a027f0bf22480b5f84133"
|
|
||||||
integrity sha1-aP0CXrBJC09WegJ/C/IkgLX4QTM=
|
|
||||||
|
|
||||||
side-channel@^1.0.3:
|
side-channel@^1.0.3:
|
||||||
version "1.0.4"
|
version "1.0.4"
|
||||||
resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf"
|
resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf"
|
||||||
|
1
tabby-plugin-manager/.gitignore
vendored
1
tabby-plugin-manager/.gitignore
vendored
@@ -1 +0,0 @@
|
|||||||
dist
|
|
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "tabby-plugin-manager",
|
"name": "tabby-plugin-manager",
|
||||||
"version": "1.0.145-nightly.0",
|
"version": "1.0.148-nightly.2",
|
||||||
"description": "Tabby's plugin manager",
|
"description": "Tabby's plugin manager",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"tabby-builtin-plugin"
|
"tabby-builtin-plugin"
|
||||||
@@ -18,7 +18,6 @@
|
|||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/semver": "^7.1.0",
|
"@types/semver": "^7.1.0",
|
||||||
"axios": "^0.21.1",
|
|
||||||
"semver": "^7.1.1"
|
"semver": "^7.1.1"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
|
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
.d-flex
|
.d-flex
|
||||||
h3.mb-1 Installed
|
h3.mb-1 Installed
|
||||||
button.btn.btn-outline-secondary.btn-sm.ml-auto((click)='openPluginsFolder()')
|
button.btn.btn-secondary.btn-sm.ml-auto((click)='openPluginsFolder()')
|
||||||
i.fas.fa-folder
|
i.fas.fa-folder
|
||||||
span Plugins folder
|
span Plugins folder
|
||||||
|
|
||||||
|
@@ -7,18 +7,6 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.3.7.tgz#b9eb89d7dfa70d5d1ce525bc1411a35347f533a3"
|
resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.3.7.tgz#b9eb89d7dfa70d5d1ce525bc1411a35347f533a3"
|
||||||
integrity sha512-4g1jrL98mdOIwSOUh6LTlB0Cs9I0dQPwINUhBg7C6pN4HLr8GS8xsksJxilW6S6dQHVi2K/o+lQuQcg7LroCnw==
|
integrity sha512-4g1jrL98mdOIwSOUh6LTlB0Cs9I0dQPwINUhBg7C6pN4HLr8GS8xsksJxilW6S6dQHVi2K/o+lQuQcg7LroCnw==
|
||||||
|
|
||||||
axios@^0.21.1:
|
|
||||||
version "0.21.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.1.tgz#22563481962f4d6bde9a76d516ef0e5d3c09b2b8"
|
|
||||||
integrity sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==
|
|
||||||
dependencies:
|
|
||||||
follow-redirects "^1.10.0"
|
|
||||||
|
|
||||||
follow-redirects@^1.10.0:
|
|
||||||
version "1.13.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.1.tgz#5f69b813376cee4fd0474a3aba835df04ab763b7"
|
|
||||||
integrity sha512-SSG5xmZh1mkPGyKzjZP8zLjltIfpW32Y5QpdNJyjcfGxK3qo3NDDkZOZSFiGn1A6SclQxY9GzEwAHQ3dmYRWpg==
|
|
||||||
|
|
||||||
lru-cache@^6.0.0:
|
lru-cache@^6.0.0:
|
||||||
version "6.0.0"
|
version "6.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94"
|
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94"
|
||||||
|
0
tabby-serial/.gitignore
vendored
0
tabby-serial/.gitignore
vendored
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "tabby-serial",
|
"name": "tabby-serial",
|
||||||
"version": "1.0.145-nightly.0",
|
"version": "1.0.148-nightly.2",
|
||||||
"description": "Serial connections for Tabby",
|
"description": "Serial connections for Tabby",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"tabby-builtin-plugin"
|
"tabby-builtin-plugin"
|
||||||
@@ -18,7 +18,8 @@
|
|||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "14.14.14",
|
"@types/node": "14.14.14",
|
||||||
"ansi-colors": "^4.1.1"
|
"ansi-colors": "^4.1.1",
|
||||||
|
"serialport-binding-webserialapi": "^1.0.3"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@angular/animations": "^9.1.9",
|
"@angular/animations": "^9.1.9",
|
||||||
|
@@ -4,6 +4,7 @@ import { LogService, NotificationsService, Profile } from 'tabby-core'
|
|||||||
import { Subject, Observable } from 'rxjs'
|
import { Subject, Observable } from 'rxjs'
|
||||||
import { Injector, NgZone } from '@angular/core'
|
import { Injector, NgZone } from '@angular/core'
|
||||||
import { BaseSession, LoginScriptsOptions, StreamProcessingOptions, TerminalStreamProcessor } from 'tabby-terminal'
|
import { BaseSession, LoginScriptsOptions, StreamProcessingOptions, TerminalStreamProcessor } from 'tabby-terminal'
|
||||||
|
import { SerialService } from './services/serial.service'
|
||||||
|
|
||||||
export interface SerialProfile extends Profile {
|
export interface SerialProfile extends Profile {
|
||||||
options: SerialProfileOptions
|
options: SerialProfileOptions
|
||||||
@@ -19,7 +20,6 @@ export interface SerialProfileOptions extends StreamProcessingOptions, LoginScri
|
|||||||
xon?: boolean
|
xon?: boolean
|
||||||
xoff?: boolean
|
xoff?: boolean
|
||||||
xany?: boolean
|
xany?: boolean
|
||||||
color?: string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const BAUD_RATES = [
|
export const BAUD_RATES = [
|
||||||
@@ -39,9 +39,12 @@ export class SerialSession extends BaseSession {
|
|||||||
private streamProcessor: TerminalStreamProcessor
|
private streamProcessor: TerminalStreamProcessor
|
||||||
private zone: NgZone
|
private zone: NgZone
|
||||||
private notifications: NotificationsService
|
private notifications: NotificationsService
|
||||||
|
private serialService: SerialService
|
||||||
|
|
||||||
constructor (injector: Injector, public profile: SerialProfile) {
|
constructor (injector: Injector, public profile: SerialProfile) {
|
||||||
super(injector.get(LogService).create(`serial-${profile.options.port}`))
|
super(injector.get(LogService).create(`serial-${profile.options.port}`))
|
||||||
|
this.serialService = injector.get(SerialService)
|
||||||
|
|
||||||
this.zone = injector.get(NgZone)
|
this.zone = injector.get(NgZone)
|
||||||
this.notifications = injector.get(NotificationsService)
|
this.notifications = injector.get(NotificationsService)
|
||||||
|
|
||||||
@@ -58,6 +61,10 @@ export class SerialSession extends BaseSession {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async start (): Promise<void> {
|
async start (): Promise<void> {
|
||||||
|
if (!this.profile.options.port) {
|
||||||
|
this.profile.options.port = (await this.serialService.listPorts())[0].name
|
||||||
|
}
|
||||||
|
|
||||||
this.serial = new SerialPort(this.profile.options.port, {
|
this.serial = new SerialPort(this.profile.options.port, {
|
||||||
autoOpen: false,
|
autoOpen: false,
|
||||||
baudRate: parseInt(this.profile.options.baudrate as any),
|
baudRate: parseInt(this.profile.options.baudrate as any),
|
||||||
|
@@ -3,7 +3,7 @@ ul.nav-tabs(ngbNav, #nav='ngbNav')
|
|||||||
a(ngbNavLink) General
|
a(ngbNavLink) General
|
||||||
ng-template(ngbNavContent)
|
ng-template(ngbNavContent)
|
||||||
.row
|
.row
|
||||||
.col-6
|
.col-6(ng:if='hostApp.platform !== Platform.Web')
|
||||||
.form-group
|
.form-group
|
||||||
label Device
|
label Device
|
||||||
input.form-control(
|
input.form-control(
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||||
import { Component } from '@angular/core'
|
import { Component } from '@angular/core'
|
||||||
import { debounceTime, distinctUntilChanged, map } from 'rxjs'
|
import { debounceTime, distinctUntilChanged, map } from 'rxjs'
|
||||||
import { ProfileSettingsComponent } from 'tabby-core'
|
import { HostAppService, Platform, ProfileSettingsComponent } from 'tabby-core'
|
||||||
import { SerialPortInfo, BAUD_RATES, SerialProfile } from '../api'
|
import { SerialPortInfo, BAUD_RATES, SerialProfile } from '../api'
|
||||||
import { SerialService } from '../services/serial.service'
|
import { SerialService } from '../services/serial.service'
|
||||||
|
|
||||||
@@ -9,12 +9,14 @@ import { SerialService } from '../services/serial.service'
|
|||||||
@Component({
|
@Component({
|
||||||
template: require('./serialProfileSettings.component.pug'),
|
template: require('./serialProfileSettings.component.pug'),
|
||||||
})
|
})
|
||||||
export class SerialProfileSettingsComponent implements ProfileSettingsComponent {
|
export class SerialProfileSettingsComponent implements ProfileSettingsComponent<SerialProfile> {
|
||||||
profile: SerialProfile
|
profile: SerialProfile
|
||||||
foundPorts: SerialPortInfo[]
|
foundPorts: SerialPortInfo[]
|
||||||
|
Platform = Platform
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
private serial: SerialService,
|
private serial: SerialService,
|
||||||
|
public hostApp: HostAppService,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
portsAutocomplete = text$ => text$.pipe(map(() => {
|
portsAutocomplete = text$ => text$.pipe(map(() => {
|
||||||
|
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
.mr-auto
|
.mr-auto
|
||||||
|
|
||||||
button.btn.btn-sm.btn-link.mr-3((click)='changeBaudRate()', *ngIf='session && session.open')
|
button.btn.btn-sm.btn-link.mr-3((click)='changeBaudRate()', *ngIf='session && session.open && hostApp.platform !== Platform.Web')
|
||||||
span Change baud rate
|
span Change baud rate
|
||||||
|
|
||||||
button.btn.btn-sm.btn-link((click)='reconnect()', *ngIf='!session || !session.open')
|
button.btn.btn-sm.btn-link((click)='reconnect()', *ngIf='!session || !session.open')
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
import colors from 'ansi-colors'
|
import colors from 'ansi-colors'
|
||||||
import { Component, Injector } from '@angular/core'
|
import { Component, Injector } from '@angular/core'
|
||||||
import { first } from 'rxjs'
|
import { first } from 'rxjs'
|
||||||
import { SelectorService } from 'tabby-core'
|
import { Platform, SelectorService } from 'tabby-core'
|
||||||
import { BaseTerminalTabComponent } from 'tabby-terminal'
|
import { BaseTerminalTabComponent } from 'tabby-terminal'
|
||||||
import { SerialSession, BAUD_RATES, SerialProfile } from '../api'
|
import { SerialSession, BAUD_RATES, SerialProfile } from '../api'
|
||||||
|
|
||||||
@@ -14,10 +14,10 @@ import { SerialSession, BAUD_RATES, SerialProfile } from '../api'
|
|||||||
animations: BaseTerminalTabComponent.animations,
|
animations: BaseTerminalTabComponent.animations,
|
||||||
})
|
})
|
||||||
export class SerialTabComponent extends BaseTerminalTabComponent {
|
export class SerialTabComponent extends BaseTerminalTabComponent {
|
||||||
enableToolbar = true
|
|
||||||
profile?: SerialProfile
|
profile?: SerialProfile
|
||||||
session: SerialSession|null = null
|
session: SerialSession|null = null
|
||||||
serialPort: any
|
serialPort: any
|
||||||
|
Platform = Platform
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-useless-constructor
|
// eslint-disable-next-line @typescript-eslint/no-useless-constructor
|
||||||
constructor (
|
constructor (
|
||||||
@@ -25,6 +25,7 @@ export class SerialTabComponent extends BaseTerminalTabComponent {
|
|||||||
private selector: SelectorService,
|
private selector: SelectorService,
|
||||||
) {
|
) {
|
||||||
super(injector)
|
super(injector)
|
||||||
|
this.enableToolbar = true
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit () {
|
ngOnInit () {
|
||||||
|
@@ -1,48 +1,71 @@
|
|||||||
import slugify from 'slugify'
|
import slugify from 'slugify'
|
||||||
|
import SerialPort from 'serialport'
|
||||||
|
import WSABinding from 'serialport-binding-webserialapi'
|
||||||
import deepClone from 'clone-deep'
|
import deepClone from 'clone-deep'
|
||||||
import { Injectable } from '@angular/core'
|
import { Injectable } from '@angular/core'
|
||||||
import { ProfileProvider, NewTabParameters, SelectorService } from 'tabby-core'
|
import { ProfileProvider, NewTabParameters, SelectorService, HostAppService, Platform } from 'tabby-core'
|
||||||
import { InputMode, NewlineMode } from 'tabby-terminal'
|
|
||||||
import { SerialProfileSettingsComponent } from './components/serialProfileSettings.component'
|
import { SerialProfileSettingsComponent } from './components/serialProfileSettings.component'
|
||||||
import { SerialTabComponent } from './components/serialTab.component'
|
import { SerialTabComponent } from './components/serialTab.component'
|
||||||
import { SerialService } from './services/serial.service'
|
import { SerialService } from './services/serial.service'
|
||||||
import { BAUD_RATES, SerialProfile } from './api'
|
import { BAUD_RATES, SerialProfile } from './api'
|
||||||
|
|
||||||
@Injectable({ providedIn: 'root' })
|
@Injectable({ providedIn: 'root' })
|
||||||
export class SerialProfilesService extends ProfileProvider {
|
export class SerialProfilesService extends ProfileProvider<SerialProfile> {
|
||||||
id = 'serial'
|
id = 'serial'
|
||||||
name = 'Serial'
|
name = 'Serial'
|
||||||
settingsComponent = SerialProfileSettingsComponent
|
settingsComponent = SerialProfileSettingsComponent
|
||||||
|
configDefaults = {
|
||||||
|
options: {
|
||||||
|
port: null,
|
||||||
|
baudrate: null,
|
||||||
|
databits: 8,
|
||||||
|
stopbits: 1,
|
||||||
|
parity: 'none',
|
||||||
|
rtscts: false,
|
||||||
|
xon: false,
|
||||||
|
xoff: false,
|
||||||
|
xany: false,
|
||||||
|
inputMode: 'local-echo',
|
||||||
|
outputMode: null,
|
||||||
|
inputNewlines: null,
|
||||||
|
outputNewlines: 'crlf',
|
||||||
|
scripts: [],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
private selector: SelectorService,
|
private selector: SelectorService,
|
||||||
private serial: SerialService,
|
private serial: SerialService,
|
||||||
) { super() }
|
private hostApp: HostAppService,
|
||||||
|
) {
|
||||||
|
super()
|
||||||
|
if (hostApp.platform === Platform.Web) {
|
||||||
|
SerialPort.Binding = WSABinding
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async getBuiltinProfiles (): Promise<SerialProfile[]> {
|
async getBuiltinProfiles (): Promise<SerialProfile[]> {
|
||||||
|
if (this.hostApp.platform === Platform.Web) {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
id: `serial:web`,
|
||||||
|
type: 'serial',
|
||||||
|
name: 'Serial connection',
|
||||||
|
icon: 'fas fa-microchip',
|
||||||
|
isBuiltin: true,
|
||||||
|
} as SerialProfile,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
id: `serial:template`,
|
id: `serial:template`,
|
||||||
type: 'serial',
|
type: 'serial',
|
||||||
name: 'Serial connection',
|
name: 'Serial connection',
|
||||||
icon: 'fas fa-microchip',
|
icon: 'fas fa-microchip',
|
||||||
options: {
|
|
||||||
port: '',
|
|
||||||
databits: 8,
|
|
||||||
parity: 'none',
|
|
||||||
rtscts: false,
|
|
||||||
stopbits: 1,
|
|
||||||
xany: false,
|
|
||||||
xoff: false,
|
|
||||||
xon: false,
|
|
||||||
inputMode: 'local-echo' as InputMode,
|
|
||||||
outputMode: null,
|
|
||||||
inputNewlines: null,
|
|
||||||
outputNewlines: 'crlf' as NewlineMode,
|
|
||||||
},
|
|
||||||
isBuiltin: true,
|
isBuiltin: true,
|
||||||
isTemplate: true,
|
isTemplate: true,
|
||||||
},
|
} as SerialProfile,
|
||||||
...(await this.serial.listPorts()).map(p => ({
|
...(await this.serial.listPorts()).map(p => ({
|
||||||
id: `serial:port-${slugify(p.name).replace('.', '-')}`,
|
id: `serial:port-${slugify(p.name).replace('.', '-')}`,
|
||||||
type: 'serial',
|
type: 'serial',
|
||||||
@@ -51,10 +74,8 @@ export class SerialProfilesService extends ProfileProvider {
|
|||||||
isBuiltin: true,
|
isBuiltin: true,
|
||||||
options: {
|
options: {
|
||||||
port: p.name,
|
port: p.name,
|
||||||
inputMode: 'local-echo' as InputMode,
|
|
||||||
outputNewlines: 'crlf' as NewlineMode,
|
|
||||||
},
|
},
|
||||||
})),
|
} as SerialProfile)),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import { Injectable } from '@angular/core'
|
import { Injectable } from '@angular/core'
|
||||||
import SerialPort from 'serialport'
|
import SerialPort from 'serialport'
|
||||||
import { ProfilesService } from 'tabby-core'
|
import { PartialProfile, ProfilesService } from 'tabby-core'
|
||||||
import { SerialPortInfo, SerialProfile } from '../api'
|
import { SerialPortInfo, SerialProfile } from '../api'
|
||||||
import { SerialTabComponent } from '../components/serialTab.component'
|
import { SerialTabComponent } from '../components/serialTab.component'
|
||||||
|
|
||||||
@@ -24,19 +24,12 @@ export class SerialService {
|
|||||||
baudrate = parseInt(path.split('@')[1])
|
baudrate = parseInt(path.split('@')[1])
|
||||||
path = path.split('@')[0]
|
path = path.split('@')[0]
|
||||||
}
|
}
|
||||||
const profile: SerialProfile = {
|
const profile: PartialProfile<SerialProfile> = {
|
||||||
name: query,
|
name: query,
|
||||||
type: 'serial',
|
type: 'serial',
|
||||||
options: {
|
options: {
|
||||||
port: path,
|
port: path,
|
||||||
baudrate: baudrate,
|
baudrate: baudrate,
|
||||||
databits: 8,
|
|
||||||
parity: 'none',
|
|
||||||
rtscts: false,
|
|
||||||
stopbits: 1,
|
|
||||||
xany: false,
|
|
||||||
xoff: false,
|
|
||||||
xon: false,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
window.localStorage.lastSerialConnection = JSON.stringify(profile)
|
window.localStorage.lastSerialConnection = JSON.stringify(profile)
|
||||||
|
@@ -2,6 +2,20 @@
|
|||||||
# yarn lockfile v1
|
# yarn lockfile v1
|
||||||
|
|
||||||
|
|
||||||
|
"@serialport/binding-abstract@^9.0.2":
|
||||||
|
version "9.0.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/@serialport/binding-abstract/-/binding-abstract-9.0.7.tgz#d2c7ecea0f100bdf20187bfc0d34ba90f5504e1e"
|
||||||
|
integrity sha512-g1ncCMIG9rMsxo/28ObYmXZcHThlvtZygsCANmyMUuFS7SwXY4+PhcEnt2+ZcMkEDNRiOklT+ngtIVx5GGpt/A==
|
||||||
|
dependencies:
|
||||||
|
debug "^4.3.1"
|
||||||
|
|
||||||
|
"@serialport/stream@^9.0.2":
|
||||||
|
version "9.0.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/@serialport/stream/-/stream-9.0.7.tgz#0bf023eb0233a714fcc5a86de09e381e466d9882"
|
||||||
|
integrity sha512-c/h7HPAeFiryD9iTGlaSvPqHFHSZ0NMQHxC4rcmKS2Vu3qJuEtkBdTLABwsMp7iWEiSnI4KC3s7bHapaXP06FQ==
|
||||||
|
dependencies:
|
||||||
|
debug "^4.3.1"
|
||||||
|
|
||||||
"@types/node@14.14.14":
|
"@types/node@14.14.14":
|
||||||
version "14.14.14"
|
version "14.14.14"
|
||||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.14.tgz#f7fd5f3cc8521301119f63910f0fb965c7d761ae"
|
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.14.tgz#f7fd5f3cc8521301119f63910f0fb965c7d761ae"
|
||||||
@@ -11,3 +25,23 @@ ansi-colors@^4.1.1:
|
|||||||
version "4.1.1"
|
version "4.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348"
|
resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348"
|
||||||
integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==
|
integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==
|
||||||
|
|
||||||
|
debug@^4.3.1:
|
||||||
|
version "4.3.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b"
|
||||||
|
integrity sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==
|
||||||
|
dependencies:
|
||||||
|
ms "2.1.2"
|
||||||
|
|
||||||
|
ms@2.1.2:
|
||||||
|
version "2.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
|
||||||
|
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
|
||||||
|
|
||||||
|
serialport-binding-webserialapi@^1.0.3:
|
||||||
|
version "1.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/serialport-binding-webserialapi/-/serialport-binding-webserialapi-1.0.3.tgz#cf4348c075da2de8f6cf9936c0b95645f3ae657b"
|
||||||
|
integrity sha512-TS7dsvetVoTeiWlzpsT/akjtljiYPO56FoJWSFyJSoO/E8icYJ2neQ7CW5NW/sHZDnMqAxULyAny47UFhWz9oQ==
|
||||||
|
dependencies:
|
||||||
|
"@serialport/binding-abstract" "^9.0.2"
|
||||||
|
"@serialport/stream" "^9.0.2"
|
||||||
|
1
tabby-settings/.gitignore
vendored
1
tabby-settings/.gitignore
vendored
@@ -1 +0,0 @@
|
|||||||
dist
|
|
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "tabby-settings",
|
"name": "tabby-settings",
|
||||||
"version": "1.0.145-nightly.0",
|
"version": "1.0.148-nightly.2",
|
||||||
"description": "Tabby terminal settings page",
|
"description": "Tabby terminal settings page",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"tabby-builtin-plugin"
|
"tabby-builtin-plugin"
|
||||||
@@ -18,6 +18,8 @@
|
|||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/deep-equal": "1.0.1",
|
"@types/deep-equal": "1.0.1",
|
||||||
|
"marked": "^2.1.3",
|
||||||
|
"ngx-infinite-scroll": "^10.0.1",
|
||||||
"utils-decorators": "^1.8.0"
|
"utils-decorators": "^1.8.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
|
@@ -57,9 +57,9 @@
|
|||||||
|
|
||||||
.mb-4
|
.mb-4
|
||||||
|
|
||||||
.col-12.col-lg-8
|
.col-12.col-lg-8(*ngIf='this.profileProvider.settingsComponent')
|
||||||
ng-template(#placeholder)
|
ng-template(#placeholder)
|
||||||
|
|
||||||
.modal-footer
|
.modal-footer
|
||||||
button.btn.btn-outline-primary((click)='save()') Save
|
button.btn.btn-primary((click)='save()') Save
|
||||||
button.btn.btn-outline-danger((click)='cancel()') Cancel
|
button.btn.btn-danger((click)='cancel()') Cancel
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
import { Observable, OperatorFunction, debounceTime, map, distinctUntilChanged } from 'rxjs'
|
import { Observable, OperatorFunction, debounceTime, map, distinctUntilChanged } from 'rxjs'
|
||||||
import { Component, Input, ViewChild, ViewContainerRef, ComponentFactoryResolver, Injector } from '@angular/core'
|
import { Component, Input, ViewChild, ViewContainerRef, ComponentFactoryResolver, Injector } from '@angular/core'
|
||||||
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
|
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
|
||||||
import { ConfigService, Profile, ProfileProvider, ProfileSettingsComponent } from 'tabby-core'
|
import { ConfigProxy, ConfigService, Profile, ProfileProvider, ProfileSettingsComponent, ProfilesService } from 'tabby-core'
|
||||||
|
|
||||||
const iconsData = require('../../../tabby-core/src/icons.json')
|
const iconsData = require('../../../tabby-core/src/icons.json')
|
||||||
const iconsClassList = Object.keys(iconsData).map(
|
const iconsClassList = Object.keys(iconsData).map(
|
||||||
@@ -15,18 +15,20 @@ const iconsClassList = Object.keys(iconsData).map(
|
|||||||
@Component({
|
@Component({
|
||||||
template: require('./editProfileModal.component.pug'),
|
template: require('./editProfileModal.component.pug'),
|
||||||
})
|
})
|
||||||
export class EditProfileModalComponent {
|
export class EditProfileModalComponent<P extends Profile> {
|
||||||
@Input() profile: Profile
|
@Input() profile: P & ConfigProxy
|
||||||
@Input() profileProvider: ProfileProvider
|
@Input() profileProvider: ProfileProvider<P>
|
||||||
@Input() settingsComponent: new () => ProfileSettingsComponent
|
@Input() settingsComponent: new () => ProfileSettingsComponent<P>
|
||||||
groupNames: string[]
|
groupNames: string[]
|
||||||
@ViewChild('placeholder', { read: ViewContainerRef }) placeholder: ViewContainerRef
|
@ViewChild('placeholder', { read: ViewContainerRef }) placeholder: ViewContainerRef
|
||||||
|
|
||||||
private settingsComponentInstance: ProfileSettingsComponent
|
private _profile: Profile
|
||||||
|
private settingsComponentInstance: ProfileSettingsComponent<P>
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
private injector: Injector,
|
private injector: Injector,
|
||||||
private componentFactoryResolver: ComponentFactoryResolver,
|
private componentFactoryResolver: ComponentFactoryResolver,
|
||||||
|
private profilesService: ProfilesService,
|
||||||
config: ConfigService,
|
config: ConfigService,
|
||||||
private modalInstance: NgbActiveModal,
|
private modalInstance: NgbActiveModal,
|
||||||
) {
|
) {
|
||||||
@@ -37,14 +39,22 @@ export class EditProfileModalComponent {
|
|||||||
)].sort() as string[]
|
)].sort() as string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ngOnInit () {
|
||||||
|
this._profile = this.profile
|
||||||
|
this.profile = this.profilesService.getConfigProxyForProfile(this.profile)
|
||||||
|
}
|
||||||
|
|
||||||
ngAfterViewInit () {
|
ngAfterViewInit () {
|
||||||
setTimeout(() => {
|
const componentType = this.profileProvider.settingsComponent
|
||||||
const componentFactory = this.componentFactoryResolver.resolveComponentFactory(this.profileProvider.settingsComponent)
|
if (componentType) {
|
||||||
const componentRef = componentFactory.create(this.injector)
|
setTimeout(() => {
|
||||||
this.settingsComponentInstance = componentRef.instance
|
const componentFactory = this.componentFactoryResolver.resolveComponentFactory(componentType)
|
||||||
this.settingsComponentInstance.profile = this.profile
|
const componentRef = componentFactory.create(this.injector)
|
||||||
this.placeholder.insert(componentRef.hostView)
|
this.settingsComponentInstance = componentRef.instance
|
||||||
})
|
this.settingsComponentInstance.profile = this.profile
|
||||||
|
this.placeholder.insert(componentRef.hostView)
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
groupTypeahead = (text$: Observable<string>) =>
|
groupTypeahead = (text$: Observable<string>) =>
|
||||||
@@ -63,7 +73,8 @@ export class EditProfileModalComponent {
|
|||||||
save () {
|
save () {
|
||||||
this.profile.group ||= undefined
|
this.profile.group ||= undefined
|
||||||
this.settingsComponentInstance.save?.()
|
this.settingsComponentInstance.save?.()
|
||||||
this.modalInstance.close(this.profile)
|
this.profile.__cleanup()
|
||||||
|
this.modalInstance.close(this._profile)
|
||||||
}
|
}
|
||||||
|
|
||||||
cancel () {
|
cancel () {
|
||||||
|
@@ -9,4 +9,4 @@
|
|||||||
div([style.width]='timeoutProgress + "%"')
|
div([style.width]='timeoutProgress + "%"')
|
||||||
|
|
||||||
.modal-footer
|
.modal-footer
|
||||||
button.btn.btn-outline-primary((click)='close()') Cancel
|
button.btn.btn-primary((click)='close()') Cancel
|
||||||
|
@@ -56,7 +56,7 @@ h3.mb-3 Profiles
|
|||||||
*ngIf='group.editable && group.name',
|
*ngIf='group.editable && group.name',
|
||||||
(click)='$event.stopPropagation(); deleteGroup(group)'
|
(click)='$event.stopPropagation(); deleteGroup(group)'
|
||||||
)
|
)
|
||||||
i.fas.fa-trash
|
i.fas.fa-trash-alt
|
||||||
ng-container(*ngIf='!group.collapsed')
|
ng-container(*ngIf='!group.collapsed')
|
||||||
ng-container(*ngFor='let profile of group.profiles')
|
ng-container(*ngFor='let profile of group.profiles')
|
||||||
.list-group-item.pl-5.d-flex.align-items-center(
|
.list-group-item.pl-5.d-flex.align-items-center(
|
||||||
@@ -85,10 +85,10 @@ h3.mb-3 Profiles
|
|||||||
button.btn.btn-link.hover-reveal.ml-1((click)='$event.stopPropagation(); newProfile(profile)')
|
button.btn.btn-link.hover-reveal.ml-1((click)='$event.stopPropagation(); newProfile(profile)')
|
||||||
i.fas.fa-copy
|
i.fas.fa-copy
|
||||||
|
|
||||||
button.btn.btn-link.text-danger.hover-reveal.ml-1(
|
button.btn.btn-link.hover-reveal.ml-1(
|
||||||
*ngIf='!profile.isBuiltin',
|
*ngIf='!profile.isBuiltin',
|
||||||
(click)='$event.stopPropagation(); deleteProfile(profile)'
|
(click)='$event.stopPropagation(); deleteProfile(profile)'
|
||||||
)
|
)
|
||||||
i.fas.fa-trash
|
i.fas.fa-trash-alt
|
||||||
|
|
||||||
.ml-1(class='badge badge-{{getTypeColorClass(profile)}}') {{getTypeLabel(profile)}}
|
.ml-1(class='badge badge-{{getTypeColorClass(profile)}}') {{getTypeLabel(profile)}}
|
||||||
|
@@ -3,12 +3,12 @@ import slugify from 'slugify'
|
|||||||
import deepClone from 'clone-deep'
|
import deepClone from 'clone-deep'
|
||||||
import { Component } from '@angular/core'
|
import { Component } from '@angular/core'
|
||||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
||||||
import { ConfigService, HostAppService, Profile, SelectorService, ProfilesService, PromptModalComponent, PlatformService, BaseComponent } from 'tabby-core'
|
import { ConfigService, HostAppService, Profile, SelectorService, ProfilesService, PromptModalComponent, PlatformService, BaseComponent, PartialProfile } from 'tabby-core'
|
||||||
import { EditProfileModalComponent } from './editProfileModal.component'
|
import { EditProfileModalComponent } from './editProfileModal.component'
|
||||||
|
|
||||||
interface ProfileGroup {
|
interface ProfileGroup {
|
||||||
name?: string
|
name?: string
|
||||||
profiles: Profile[]
|
profiles: PartialProfile<Profile>[]
|
||||||
editable: boolean
|
editable: boolean
|
||||||
collapsed: boolean
|
collapsed: boolean
|
||||||
}
|
}
|
||||||
@@ -19,9 +19,9 @@ interface ProfileGroup {
|
|||||||
styles: [require('./profilesSettingsTab.component.scss')],
|
styles: [require('./profilesSettingsTab.component.scss')],
|
||||||
})
|
})
|
||||||
export class ProfilesSettingsTabComponent extends BaseComponent {
|
export class ProfilesSettingsTabComponent extends BaseComponent {
|
||||||
profiles: Profile[] = []
|
profiles: PartialProfile<Profile>[] = []
|
||||||
builtinProfiles: Profile[] = []
|
builtinProfiles: PartialProfile<Profile>[] = []
|
||||||
templateProfiles: Profile[] = []
|
templateProfiles: PartialProfile<Profile>[] = []
|
||||||
profileGroups: ProfileGroup[]
|
profileGroups: ProfileGroup[]
|
||||||
filter = ''
|
filter = ''
|
||||||
|
|
||||||
@@ -45,11 +45,11 @@ export class ProfilesSettingsTabComponent extends BaseComponent {
|
|||||||
this.subscribeUntilDestroyed(this.config.changed$, () => this.refresh())
|
this.subscribeUntilDestroyed(this.config.changed$, () => this.refresh())
|
||||||
}
|
}
|
||||||
|
|
||||||
launchProfile (profile: Profile): void {
|
launchProfile (profile: PartialProfile<Profile>): void {
|
||||||
this.profilesService.openNewTabForProfile(profile)
|
this.profilesService.openNewTabForProfile(profile)
|
||||||
}
|
}
|
||||||
|
|
||||||
async newProfile (base?: Profile): Promise<void> {
|
async newProfile (base?: PartialProfile<Profile>): Promise<void> {
|
||||||
if (!base) {
|
if (!base) {
|
||||||
const profiles = [...this.templateProfiles, ...this.builtinProfiles, ...this.profiles]
|
const profiles = [...this.templateProfiles, ...this.builtinProfiles, ...this.profiles]
|
||||||
profiles.sort((a, b) => (a.weight ?? 0) - (b.weight ?? 0))
|
profiles.sort((a, b) => (a.weight ?? 0) - (b.weight ?? 0))
|
||||||
@@ -57,7 +57,7 @@ export class ProfilesSettingsTabComponent extends BaseComponent {
|
|||||||
'Select a base profile to use as a template',
|
'Select a base profile to use as a template',
|
||||||
profiles.map(p => ({
|
profiles.map(p => ({
|
||||||
icon: p.icon,
|
icon: p.icon,
|
||||||
description: this.profilesService.providerForProfile(p)?.getDescription(p),
|
description: this.profilesService.getDescription(p) ?? undefined,
|
||||||
name: p.group ? `${p.group} / ${p.name}` : p.name,
|
name: p.group ? `${p.group} / ${p.name}` : p.name,
|
||||||
result: p,
|
result: p,
|
||||||
})),
|
})),
|
||||||
@@ -74,7 +74,7 @@ export class ProfilesSettingsTabComponent extends BaseComponent {
|
|||||||
await this.config.save()
|
await this.config.save()
|
||||||
}
|
}
|
||||||
|
|
||||||
async editProfile (profile: Profile): Promise<void> {
|
async editProfile (profile: PartialProfile<Profile>): Promise<void> {
|
||||||
const modal = this.ngbModal.open(
|
const modal = this.ngbModal.open(
|
||||||
EditProfileModalComponent,
|
EditProfileModalComponent,
|
||||||
{ size: 'lg' },
|
{ size: 'lg' },
|
||||||
@@ -82,11 +82,18 @@ export class ProfilesSettingsTabComponent extends BaseComponent {
|
|||||||
modal.componentInstance.profile = Object.assign({}, profile)
|
modal.componentInstance.profile = Object.assign({}, profile)
|
||||||
modal.componentInstance.profileProvider = this.profilesService.providerForProfile(profile)
|
modal.componentInstance.profileProvider = this.profilesService.providerForProfile(profile)
|
||||||
const result = await modal.result
|
const result = await modal.result
|
||||||
|
|
||||||
|
// Fully replace the config
|
||||||
|
for (const k in profile) {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
||||||
|
delete profile[k]
|
||||||
|
}
|
||||||
Object.assign(profile, result)
|
Object.assign(profile, result)
|
||||||
|
|
||||||
await this.config.save()
|
await this.config.save()
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteProfile (profile: Profile): Promise<void> {
|
async deleteProfile (profile: PartialProfile<Profile>): Promise<void> {
|
||||||
if ((await this.platform.showMessageBox(
|
if ((await this.platform.showMessageBox(
|
||||||
{
|
{
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
@@ -95,7 +102,8 @@ export class ProfilesSettingsTabComponent extends BaseComponent {
|
|||||||
defaultId: 0,
|
defaultId: 0,
|
||||||
}
|
}
|
||||||
)).response === 1) {
|
)).response === 1) {
|
||||||
this.profilesService.providerForProfile(profile)?.deleteProfile(profile)
|
this.profilesService.providerForProfile(profile)?.deleteProfile(
|
||||||
|
this.profilesService.getConfigProxyForProfile(profile))
|
||||||
this.config.store.profiles = this.config.store.profiles.filter(x => x !== profile)
|
this.config.store.profiles = this.config.store.profiles.filter(x => x !== profile)
|
||||||
await this.config.save()
|
await this.config.save()
|
||||||
}
|
}
|
||||||
@@ -174,7 +182,7 @@ export class ProfilesSettingsTabComponent extends BaseComponent {
|
|||||||
return !this.filter || group.profiles.some(x => this.isProfileVisible(x))
|
return !this.filter || group.profiles.some(x => this.isProfileVisible(x))
|
||||||
}
|
}
|
||||||
|
|
||||||
isProfileVisible (profile: Profile): boolean {
|
isProfileVisible (profile: PartialProfile<Profile>): boolean {
|
||||||
return !this.filter || profile.name.toLowerCase().includes(this.filter.toLowerCase())
|
return !this.filter || profile.name.toLowerCase().includes(this.filter.toLowerCase())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -182,11 +190,11 @@ export class ProfilesSettingsTabComponent extends BaseComponent {
|
|||||||
return icon?.startsWith('<') ?? false
|
return icon?.startsWith('<') ?? false
|
||||||
}
|
}
|
||||||
|
|
||||||
getDescription (profile: Profile): string|null {
|
getDescription (profile: PartialProfile<Profile>): string|null {
|
||||||
return this.profilesService.providerForProfile(profile)?.getDescription(profile) ?? null
|
return this.profilesService.getDescription(profile)
|
||||||
}
|
}
|
||||||
|
|
||||||
getTypeLabel (profile: Profile): string {
|
getTypeLabel (profile: PartialProfile<Profile>): string {
|
||||||
const name = this.profilesService.providerForProfile(profile)?.name
|
const name = this.profilesService.providerForProfile(profile)?.name
|
||||||
if (name === 'Local') {
|
if (name === 'Local') {
|
||||||
return ''
|
return ''
|
||||||
@@ -194,11 +202,12 @@ export class ProfilesSettingsTabComponent extends BaseComponent {
|
|||||||
return name ?? 'Unknown'
|
return name ?? 'Unknown'
|
||||||
}
|
}
|
||||||
|
|
||||||
getTypeColorClass (profile: Profile): string {
|
getTypeColorClass (profile: PartialProfile<Profile>): string {
|
||||||
return {
|
return {
|
||||||
ssh: 'secondary',
|
ssh: 'secondary',
|
||||||
serial: 'success',
|
serial: 'success',
|
||||||
telnet: 'info',
|
telnet: 'info',
|
||||||
|
'split-layout': 'primary',
|
||||||
}[this.profilesService.providerForProfile(profile)?.id ?? ''] ?? 'warning'
|
}[this.profilesService.providerForProfile(profile)?.id ?? ''] ?? 'warning'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
12
tabby-settings/src/components/releaseNotesTab.component.pug
Normal file
12
tabby-settings/src/components/releaseNotesTab.component.pug
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
.container(
|
||||||
|
infiniteScroll,
|
||||||
|
[infiniteScrollDistance]='2',
|
||||||
|
[infiniteScrollThrottle]='50',
|
||||||
|
infiniteScrollContainer='release-notes-tab',
|
||||||
|
[fromRoot]='true',
|
||||||
|
(scrolled)='onScrolled()'
|
||||||
|
)
|
||||||
|
div(*ngFor='let release of releases')
|
||||||
|
h1 {{release.name}}
|
||||||
|
.text-muted {{release.version}} / {{release.date|date:'mediumDate'}}
|
||||||
|
section([fastHtmlBind]='release.content')
|
17
tabby-settings/src/components/releaseNotesTab.component.scss
Normal file
17
tabby-settings/src/components/releaseNotesTab.component.scss
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
:host {
|
||||||
|
overflow-y: scroll;
|
||||||
|
width: 100%;
|
||||||
|
padding: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::ng-deep img {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
section {
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
47
tabby-settings/src/components/releaseNotesTab.component.ts
Normal file
47
tabby-settings/src/components/releaseNotesTab.component.ts
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||||
|
import axios from 'axios'
|
||||||
|
import marked from 'marked'
|
||||||
|
import { Component } from '@angular/core'
|
||||||
|
import { BaseTabComponent } from 'tabby-core'
|
||||||
|
|
||||||
|
export interface Release {
|
||||||
|
name: string
|
||||||
|
version: string
|
||||||
|
content: string
|
||||||
|
date: Date
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @hidden */
|
||||||
|
@Component({
|
||||||
|
selector: 'release-notes-tab',
|
||||||
|
template: require('./releaseNotesTab.component.pug'),
|
||||||
|
styles: [require('./releaseNotesTab.component.scss')],
|
||||||
|
})
|
||||||
|
export class ReleaseNotesComponent extends BaseTabComponent {
|
||||||
|
releases: Release[] = []
|
||||||
|
lastPage = 1
|
||||||
|
|
||||||
|
constructor () {
|
||||||
|
super()
|
||||||
|
this.setTitle('Release notes')
|
||||||
|
this.loadReleases(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
async loadReleases (page) {
|
||||||
|
console.log('Loading releases page', page)
|
||||||
|
const response = await axios.get(`https://api.github.com/repos/eugeny/tabby/releases?page=${page}`, {
|
||||||
|
headers: { Accept: 'application/vnd.github.v3+json' },
|
||||||
|
})
|
||||||
|
this.releases = this.releases.concat(response.data.map(r => ({
|
||||||
|
name: r.name,
|
||||||
|
version: r.tag_name,
|
||||||
|
content: marked(r.body),
|
||||||
|
date: new Date(r.created_at),
|
||||||
|
})))
|
||||||
|
this.lastPage = page
|
||||||
|
}
|
||||||
|
|
||||||
|
onScrolled () {
|
||||||
|
this.loadReleases(this.lastPage + 1)
|
||||||
|
}
|
||||||
|
}
|
@@ -16,5 +16,5 @@ h3.modal-header.m-0.pb-0 Set master passphrase
|
|||||||
i.fas.fa-eye
|
i.fas.fa-eye
|
||||||
|
|
||||||
.modal-footer
|
.modal-footer
|
||||||
button.btn.btn-outline-primary((click)='ok()') Set passphrase
|
button.btn.btn-primary((click)='ok()') Set passphrase
|
||||||
button.btn.btn-outline-danger((click)='cancel()') Cancel
|
button.btn.btn-danger((click)='cancel()') Cancel
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
button.btn.btn-outline-warning.btn-block(*ngIf='config.restartRequested', '(click)'='restartApp()') Restart the app to apply changes
|
button.btn.btn-warning.btn-block(*ngIf='config.restartRequested', '(click)'='restartApp()') Restart the app to apply changes
|
||||||
|
|
||||||
.content
|
.content
|
||||||
ul.nav-pills(ngbNav, #nav='ngbNav', [activeId]='activeTab', orientation='vertical')
|
ul.nav-pills(ngbNav, #nav='ngbNav', [activeId]='activeTab', orientation='vertical')
|
||||||
@@ -23,6 +23,12 @@ button.btn.btn-outline-warning.btn-block(*ngIf='config.restartRequested', '(clic
|
|||||||
i.fas.fa-bug
|
i.fas.fa-bug
|
||||||
span Report a problem
|
span Report a problem
|
||||||
|
|
||||||
|
button.btn.btn-secondary.mr-3(
|
||||||
|
(click)='showReleaseNotes()',
|
||||||
|
)
|
||||||
|
i.fas.fa-book
|
||||||
|
span What's new
|
||||||
|
|
||||||
button.btn.btn-secondary(
|
button.btn.btn-secondary(
|
||||||
*ngIf='!updateAvailable && hostApp.platform !== Platform.Web',
|
*ngIf='!updateAvailable && hostApp.platform !== Platform.Web',
|
||||||
(click)='checkForUpdates()',
|
(click)='checkForUpdates()',
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||||
import * as yaml from 'js-yaml'
|
import * as yaml from 'js-yaml'
|
||||||
import { debounce } from 'utils-decorators/dist/cjs'
|
import { debounce } from 'utils-decorators/dist/esm/debounce/debounce'
|
||||||
import { Component, Inject, Input, HostBinding, NgZone } from '@angular/core'
|
import { Component, Inject, Input, HostBinding, NgZone } from '@angular/core'
|
||||||
import {
|
import {
|
||||||
ConfigService,
|
ConfigService,
|
||||||
@@ -11,9 +11,11 @@ import {
|
|||||||
UpdaterService,
|
UpdaterService,
|
||||||
PlatformService,
|
PlatformService,
|
||||||
HostWindowService,
|
HostWindowService,
|
||||||
|
AppService,
|
||||||
} from 'tabby-core'
|
} from 'tabby-core'
|
||||||
|
|
||||||
import { SettingsTabProvider } from '../api'
|
import { SettingsTabProvider } from '../api'
|
||||||
|
import { ReleaseNotesComponent } from './releaseNotesTab.component'
|
||||||
|
|
||||||
/** @hidden */
|
/** @hidden */
|
||||||
@Component({
|
@Component({
|
||||||
@@ -42,6 +44,7 @@ export class SettingsTabComponent extends BaseTabComponent {
|
|||||||
public platform: PlatformService,
|
public platform: PlatformService,
|
||||||
public zone: NgZone,
|
public zone: NgZone,
|
||||||
private updater: UpdaterService,
|
private updater: UpdaterService,
|
||||||
|
private app: AppService,
|
||||||
@Inject(SettingsTabProvider) public settingsProviders: SettingsTabProvider[],
|
@Inject(SettingsTabProvider) public settingsProviders: SettingsTabProvider[],
|
||||||
) {
|
) {
|
||||||
super()
|
super()
|
||||||
@@ -115,4 +118,10 @@ export class SettingsTabComponent extends BaseTabComponent {
|
|||||||
this.updateAvailable = await this.updater.check()
|
this.updateAvailable = await this.updater.check()
|
||||||
this.checkingForUpdate = false
|
this.checkingForUpdate = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
showReleaseNotes () {
|
||||||
|
this.app.openNewTabRaw({
|
||||||
|
type: ReleaseNotesComponent,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -27,8 +27,35 @@ div(*ngIf='vault.isEnabled()')
|
|||||||
.list-group-item.d-flex.align-items-center.p-1.pl-3(*ngFor='let secret of vaultContents.secrets')
|
.list-group-item.d-flex.align-items-center.p-1.pl-3(*ngFor='let secret of vaultContents.secrets')
|
||||||
i.fas.fa-key
|
i.fas.fa-key
|
||||||
.mr-auto {{getSecretLabel(secret)}}
|
.mr-auto {{getSecretLabel(secret)}}
|
||||||
button.btn.btn-link((click)='removeSecret(secret)')
|
|
||||||
i.fas.fa-trash
|
.hover-reveal(ngbDropdown)
|
||||||
|
button.btn.btn-link(ngbDropdownToggle)
|
||||||
|
i.fas.fa-ellipsis-v
|
||||||
|
div(ngbDropdownMenu)
|
||||||
|
button(
|
||||||
|
ngbDropdownItem,
|
||||||
|
*ngIf='secret.type === VAULT_SECRET_TYPE_FILE',
|
||||||
|
(click)='renameFile(secret)'
|
||||||
|
)
|
||||||
|
i.fas.fa-fw.fa-pencil-alt
|
||||||
|
span Rename
|
||||||
|
button(
|
||||||
|
ngbDropdownItem,
|
||||||
|
*ngIf='secret.type === VAULT_SECRET_TYPE_FILE',
|
||||||
|
(click)='replaceFileContent(secret)'
|
||||||
|
)
|
||||||
|
i.fas.fa-fw.fa-file-import
|
||||||
|
span Replace
|
||||||
|
button(
|
||||||
|
ngbDropdownItem,
|
||||||
|
*ngIf='secret.type === VAULT_SECRET_TYPE_FILE',
|
||||||
|
(click)='exportFile(secret)'
|
||||||
|
)
|
||||||
|
i.fas.fa-fw.fa-file-export
|
||||||
|
span Export
|
||||||
|
button(ngbDropdownItem, (click)='removeSecret(secret)')
|
||||||
|
i.fas.fa-fw.fa-trash
|
||||||
|
span Delete
|
||||||
|
|
||||||
h3.mt-5 Options
|
h3.mt-5 Options
|
||||||
.form-line
|
.form-line
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||||
import { Component } from '@angular/core'
|
import { Component } from '@angular/core'
|
||||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
||||||
import { BaseComponent, VaultService, VaultSecret, Vault, PlatformService, ConfigService, VAULT_SECRET_TYPE_FILE } from 'tabby-core'
|
import { BaseComponent, VaultService, VaultSecret, Vault, PlatformService, ConfigService, VAULT_SECRET_TYPE_FILE, PromptModalComponent, VaultFileSecret } from 'tabby-core'
|
||||||
import { SetVaultPassphraseModalComponent } from './setVaultPassphraseModal.component'
|
import { SetVaultPassphraseModalComponent } from './setVaultPassphraseModal.component'
|
||||||
|
|
||||||
|
|
||||||
@@ -12,6 +12,7 @@ import { SetVaultPassphraseModalComponent } from './setVaultPassphraseModal.comp
|
|||||||
})
|
})
|
||||||
export class VaultSettingsTabComponent extends BaseComponent {
|
export class VaultSettingsTabComponent extends BaseComponent {
|
||||||
vaultContents: Vault|null = null
|
vaultContents: Vault|null = null
|
||||||
|
VAULT_SECRET_TYPE_FILE = VAULT_SECRET_TYPE_FILE
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
public vault: VaultService,
|
public vault: VaultService,
|
||||||
@@ -91,4 +92,51 @@ export class VaultSettingsTabComponent extends BaseComponent {
|
|||||||
this.vaultContents.secrets = this.vaultContents.secrets.filter(x => x !== secret)
|
this.vaultContents.secrets = this.vaultContents.secrets.filter(x => x !== secret)
|
||||||
this.vault.removeSecret(secret.type, secret.key)
|
this.vault.removeSecret(secret.type, secret.key)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async replaceFileContent (secret: VaultFileSecret) {
|
||||||
|
const transfers = await this.platform.startUpload()
|
||||||
|
if (!transfers.length) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
await this.vault.updateSecret(secret, {
|
||||||
|
...secret,
|
||||||
|
value: (await transfers[0].readAll()).toString('base64'),
|
||||||
|
})
|
||||||
|
this.loadVault()
|
||||||
|
}
|
||||||
|
|
||||||
|
async renameFile (secret: VaultFileSecret) {
|
||||||
|
const modal = this.ngbModal.open(PromptModalComponent)
|
||||||
|
modal.componentInstance.prompt = 'New name'
|
||||||
|
modal.componentInstance.value = secret.key.description
|
||||||
|
|
||||||
|
const description = (await modal.result)?.value
|
||||||
|
if (!description) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.vault.updateSecret(secret, {
|
||||||
|
...secret,
|
||||||
|
key: {
|
||||||
|
...secret.key,
|
||||||
|
description,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
this.loadVault()
|
||||||
|
}
|
||||||
|
|
||||||
|
async exportFile (secret: VaultFileSecret) {
|
||||||
|
this.vault.forgetPassphrase()
|
||||||
|
|
||||||
|
secret = (await this.vault.getSecret(secret.type, secret.key)) as VaultFileSecret
|
||||||
|
|
||||||
|
const content = Buffer.from(secret.value, 'base64')
|
||||||
|
const download = await this.platform.startDownload(secret.key.description, 0o600, content.length)
|
||||||
|
|
||||||
|
if (download) {
|
||||||
|
await download.write(content)
|
||||||
|
download.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -9,6 +9,17 @@ h3.mb-3 Window
|
|||||||
)
|
)
|
||||||
option(*ngFor='let theme of themes', [ngValue]='theme.name') {{theme.name}}
|
option(*ngFor='let theme of themes', [ngValue]='theme.name') {{theme.name}}
|
||||||
|
|
||||||
|
|
||||||
|
.form-line(*ngIf='hostApp.platform === Platform.Web')
|
||||||
|
.header
|
||||||
|
.title Ask before closing the browser tab
|
||||||
|
.description Prevents accidental closing
|
||||||
|
toggle(
|
||||||
|
[(ngModel)]='config.store.web.preventAccidentalTabClosure',
|
||||||
|
(ngModelChange)='saveConfiguration()',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
.form-line(*ngIf='platform.supportsWindowControls')
|
.form-line(*ngIf='platform.supportsWindowControls')
|
||||||
.header
|
.header
|
||||||
.title(*ngIf='hostApp.platform !== Platform.macOS') Acrylic background
|
.title(*ngIf='hostApp.platform !== Platform.macOS') Acrylic background
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||||
import { debounce } from 'utils-decorators/dist/cjs'
|
import { debounce } from 'utils-decorators/dist/esm/debounce/debounce'
|
||||||
import { Component, Inject, NgZone, Optional } from '@angular/core'
|
import { Component, Inject, NgZone, Optional } from '@angular/core'
|
||||||
import {
|
import {
|
||||||
DockingService,
|
DockingService,
|
||||||
|
@@ -2,6 +2,7 @@ import { NgModule } from '@angular/core'
|
|||||||
import { BrowserModule } from '@angular/platform-browser'
|
import { BrowserModule } from '@angular/platform-browser'
|
||||||
import { FormsModule } from '@angular/forms'
|
import { FormsModule } from '@angular/forms'
|
||||||
import { NgbModule } from '@ng-bootstrap/ng-bootstrap'
|
import { NgbModule } from '@ng-bootstrap/ng-bootstrap'
|
||||||
|
import { InfiniteScrollModule } from 'ngx-infinite-scroll'
|
||||||
|
|
||||||
import TabbyCorePlugin, { ToolbarButtonProvider, HotkeyProvider, ConfigProvider } from 'tabby-core'
|
import TabbyCorePlugin, { ToolbarButtonProvider, HotkeyProvider, ConfigProvider } from 'tabby-core'
|
||||||
|
|
||||||
@@ -15,6 +16,7 @@ import { WindowSettingsTabComponent } from './components/windowSettingsTab.compo
|
|||||||
import { VaultSettingsTabComponent } from './components/vaultSettingsTab.component'
|
import { VaultSettingsTabComponent } from './components/vaultSettingsTab.component'
|
||||||
import { SetVaultPassphraseModalComponent } from './components/setVaultPassphraseModal.component'
|
import { SetVaultPassphraseModalComponent } from './components/setVaultPassphraseModal.component'
|
||||||
import { ProfilesSettingsTabComponent } from './components/profilesSettingsTab.component'
|
import { ProfilesSettingsTabComponent } from './components/profilesSettingsTab.component'
|
||||||
|
import { ReleaseNotesComponent } from './components/releaseNotesTab.component'
|
||||||
|
|
||||||
import { SettingsTabProvider } from './api'
|
import { SettingsTabProvider } from './api'
|
||||||
import { ButtonProvider } from './buttonProvider'
|
import { ButtonProvider } from './buttonProvider'
|
||||||
@@ -29,6 +31,7 @@ import { HotkeySettingsTabProvider, WindowSettingsTabProvider, VaultSettingsTabP
|
|||||||
FormsModule,
|
FormsModule,
|
||||||
NgbModule,
|
NgbModule,
|
||||||
TabbyCorePlugin,
|
TabbyCorePlugin,
|
||||||
|
InfiniteScrollModule,
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
{ provide: ToolbarButtonProvider, useClass: ButtonProvider, multi: true },
|
{ provide: ToolbarButtonProvider, useClass: ButtonProvider, multi: true },
|
||||||
@@ -48,6 +51,7 @@ import { HotkeySettingsTabProvider, WindowSettingsTabProvider, VaultSettingsTabP
|
|||||||
SetVaultPassphraseModalComponent,
|
SetVaultPassphraseModalComponent,
|
||||||
VaultSettingsTabComponent,
|
VaultSettingsTabComponent,
|
||||||
WindowSettingsTabComponent,
|
WindowSettingsTabComponent,
|
||||||
|
ReleaseNotesComponent,
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
EditProfileModalComponent,
|
EditProfileModalComponent,
|
||||||
@@ -60,6 +64,7 @@ import { HotkeySettingsTabProvider, WindowSettingsTabProvider, VaultSettingsTabP
|
|||||||
SetVaultPassphraseModalComponent,
|
SetVaultPassphraseModalComponent,
|
||||||
VaultSettingsTabComponent,
|
VaultSettingsTabComponent,
|
||||||
WindowSettingsTabComponent,
|
WindowSettingsTabComponent,
|
||||||
|
ReleaseNotesComponent,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export default class SettingsModule { } // eslint-disable-line @typescript-eslint/no-extraneous-class
|
export default class SettingsModule { } // eslint-disable-line @typescript-eslint/no-extraneous-class
|
||||||
|
@@ -2,11 +2,34 @@
|
|||||||
# yarn lockfile v1
|
# yarn lockfile v1
|
||||||
|
|
||||||
|
|
||||||
|
"@scarf/scarf@^1.1.0":
|
||||||
|
version "1.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@scarf/scarf/-/scarf-1.1.1.tgz#d8b9f20037b3a37dbf8dcdc4b3b72f9285bfce35"
|
||||||
|
integrity sha512-VGbKDbk1RFIaSmdVb0cNjjWJoRWRI/Weo23AjRCC2nryO0iAS8pzsToJfPVPtVs74WHw4L1UTADNdIYRLkirZQ==
|
||||||
|
|
||||||
"@types/deep-equal@1.0.1":
|
"@types/deep-equal@1.0.1":
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/@types/deep-equal/-/deep-equal-1.0.1.tgz#71cfabb247c22bcc16d536111f50c0ed12476b03"
|
resolved "https://registry.yarnpkg.com/@types/deep-equal/-/deep-equal-1.0.1.tgz#71cfabb247c22bcc16d536111f50c0ed12476b03"
|
||||||
integrity sha512-mMUu4nWHLBlHtxXY17Fg6+ucS/MnndyOWyOe7MmwkoMYxvfQU2ajtRaEvqSUv+aVkMqH/C0NCI8UoVfRNQ10yg==
|
integrity sha512-mMUu4nWHLBlHtxXY17Fg6+ucS/MnndyOWyOe7MmwkoMYxvfQU2ajtRaEvqSUv+aVkMqH/C0NCI8UoVfRNQ10yg==
|
||||||
|
|
||||||
|
marked@^2.1.3:
|
||||||
|
version "2.1.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/marked/-/marked-2.1.3.tgz#bd017cef6431724fd4b27e0657f5ceb14bff3753"
|
||||||
|
integrity sha512-/Q+7MGzaETqifOMWYEA7HVMaZb4XbcRfaOzcSsHZEith83KGlvaSG33u0SKu89Mj5h+T8V2hM+8O45Qc5XTgwA==
|
||||||
|
|
||||||
|
ngx-infinite-scroll@^10.0.1:
|
||||||
|
version "10.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/ngx-infinite-scroll/-/ngx-infinite-scroll-10.0.1.tgz#6f51f2f8775a7c50d1dd8bad125d4e748abbe880"
|
||||||
|
integrity sha512-7is0eJZ9kJPsaHohRmMhJ/QFHAW9jp9twO5HcHRvFM/Yl/R8QCiokgjwmH0/CR3MuxUanxfHZMfO3PbYTwlBEg==
|
||||||
|
dependencies:
|
||||||
|
"@scarf/scarf" "^1.1.0"
|
||||||
|
opencollective-postinstall "^2.0.2"
|
||||||
|
|
||||||
|
opencollective-postinstall@^2.0.2:
|
||||||
|
version "2.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz#7a0fff978f6dbfa4d006238fbac98ed4198c3259"
|
||||||
|
integrity sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q==
|
||||||
|
|
||||||
tinyqueue@^2.0.3:
|
tinyqueue@^2.0.3:
|
||||||
version "2.0.3"
|
version "2.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/tinyqueue/-/tinyqueue-2.0.3.tgz#64d8492ebf39e7801d7bd34062e29b45b2035f08"
|
resolved "https://registry.yarnpkg.com/tinyqueue/-/tinyqueue-2.0.3.tgz#64d8492ebf39e7801d7bd34062e29b45b2035f08"
|
||||||
|
0
tabby-ssh/.gitignore
vendored
0
tabby-ssh/.gitignore
vendored
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "tabby-ssh",
|
"name": "tabby-ssh",
|
||||||
"version": "1.0.145-nightly.0",
|
"version": "1.0.148-nightly.2",
|
||||||
"description": "SSH connections for Tabby",
|
"description": "SSH connections for Tabby",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"tabby-builtin-plugin"
|
"tabby-builtin-plugin"
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
"watch": "webpack --progress --color --watch",
|
"watch": "webpack --progress --color --watch",
|
||||||
"postinstall": "run-script-os",
|
"postinstall": "run-script-os",
|
||||||
"postinstall:darwin:linux": "exit",
|
"postinstall:darwin:linux": "exit",
|
||||||
"postinstall:win32": "xcopy /i /y node_modules\\ssh2\\util\\pagent.exe util\\"
|
"postinstall:win32": "xcopy /i /y ..\\node_modules\\ssh2\\util\\pagent.exe util\\"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"dist",
|
"dist",
|
||||||
@@ -21,10 +21,9 @@
|
|||||||
"author": "Eugene Pankov",
|
"author": "Eugene Pankov",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "16.0.0",
|
"@types/node": "16.0.1",
|
||||||
"@types/ssh2": "^0.5.46",
|
"@types/ssh2": "^0.5.46",
|
||||||
"ansi-colors": "^4.1.1",
|
"ansi-colors": "^4.1.1",
|
||||||
"ssh2": "^1.1.0",
|
|
||||||
"sshpk": "Eugeny/node-sshpk#89ed17dfae425a8b629873c8337e77d26838c04f",
|
"sshpk": "Eugeny/node-sshpk#89ed17dfae425a8b629873c8337e77d26838c04f",
|
||||||
"strip-ansi": "^7.0.0"
|
"strip-ansi": "^7.0.0"
|
||||||
},
|
},
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user