mirror of
https://github.com/Eugeny/tabby.git
synced 2025-09-29 03:26:05 +00:00
Compare commits
7 Commits
all-contri
...
ae3c57fc54
Author | SHA1 | Date | |
---|---|---|---|
![]() |
ae3c57fc54 | ||
![]() |
b9e335817d | ||
![]() |
56ee368b63 | ||
![]() |
b31c2a5c11 | ||
![]() |
3c17654180 | ||
![]() |
7e1905c32c | ||
![]() |
bbf3b785fc |
@@ -1355,6 +1355,15 @@
|
|||||||
"contributions": [
|
"contributions": [
|
||||||
"code"
|
"code"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "pfoundation",
|
||||||
|
"name": "P Foundation",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/80860929?v=4",
|
||||||
|
"profile": "https://p.foundation/",
|
||||||
|
"contributions": [
|
||||||
|
"financial"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"contributorsPerLine": 7,
|
"contributorsPerLine": 7,
|
||||||
|
@@ -60,7 +60,7 @@ tabby
|
|||||||
| ├─ src # Electron renderer code
|
| ├─ src # Electron renderer code
|
||||||
| └─ main.js # Electron main entry point
|
| └─ main.js # Electron main entry point
|
||||||
├─ build
|
├─ build
|
||||||
├─ clink # Clink distributive, for Windows
|
├─ clink # Clink distribution, for Windows
|
||||||
├─ scripts # Maintenance scripts
|
├─ scripts # Maintenance scripts
|
||||||
├─ tabby-community-color-schemes # Plugin that provides color schemes
|
├─ tabby-community-color-schemes # Plugin that provides color schemes
|
||||||
├─ tabby-core # Plugin that provides base UI and tab management
|
├─ tabby-core # Plugin that provides base UI and tab management
|
||||||
|
@@ -349,6 +349,7 @@ Dank geht an diese wunderbaren Menschen ([emoji key](https://allcontributors.org
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/geodic"><img src="https://avatars.githubusercontent.com/u/64704703?v=4?s=100" width="100px;" alt="geodic"/><br /><sub><b>geodic</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=geodic" title="Code">💻</a></td>
|
<td align="center" valign="top" width="14.28%"><a href="https://github.com/geodic"><img src="https://avatars.githubusercontent.com/u/64704703?v=4?s=100" width="100px;" alt="geodic"/><br /><sub><b>geodic</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=geodic" title="Code">💻</a></td>
|
||||||
|
<td align="center" valign="top" width="14.28%"><a href="https://p.foundation/"><img src="https://avatars.githubusercontent.com/u/80860929?v=4?s=100" width="100px;" alt="P Foundation"/><br /><sub><b>P Foundation</b></sub></a><br /><a href="#financial-pfoundation" title="Financial">💵</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
@@ -351,6 +351,7 @@ Gracias a estas maravillosas personas ([emoji key](https://allcontributors.org/d
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/geodic"><img src="https://avatars.githubusercontent.com/u/64704703?v=4?s=100" width="100px;" alt="geodic"/><br /><sub><b>geodic</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=geodic" title="Code">💻</a></td>
|
<td align="center" valign="top" width="14.28%"><a href="https://github.com/geodic"><img src="https://avatars.githubusercontent.com/u/64704703?v=4?s=100" width="100px;" alt="geodic"/><br /><sub><b>geodic</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=geodic" title="Code">💻</a></td>
|
||||||
|
<td align="center" valign="top" width="14.28%"><a href="https://p.foundation/"><img src="https://avatars.githubusercontent.com/u/80860929?v=4?s=100" width="100px;" alt="P Foundation"/><br /><sub><b>P Foundation</b></sub></a><br /><a href="#financial-pfoundation" title="Financial">💵</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
@@ -347,6 +347,7 @@ Terima kasih kepada mereka yang telah membantu ([emoji key](https://allcontribut
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/geodic"><img src="https://avatars.githubusercontent.com/u/64704703?v=4?s=100" width="100px;" alt="geodic"/><br /><sub><b>geodic</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=geodic" title="Code">💻</a></td>
|
<td align="center" valign="top" width="14.28%"><a href="https://github.com/geodic"><img src="https://avatars.githubusercontent.com/u/64704703?v=4?s=100" width="100px;" alt="geodic"/><br /><sub><b>geodic</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=geodic" title="Code">💻</a></td>
|
||||||
|
<td align="center" valign="top" width="14.28%"><a href="https://p.foundation/"><img src="https://avatars.githubusercontent.com/u/80860929?v=4?s=100" width="100px;" alt="P Foundation"/><br /><sub><b>P Foundation</b></sub></a><br /><a href="#financial-pfoundation" title="Financial">💵</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
@@ -343,6 +343,7 @@ Grazie a queste persone meravigliose ([emoji key](https://allcontributors.org/do
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/geodic"><img src="https://avatars.githubusercontent.com/u/64704703?v=4?s=100" width="100px;" alt="geodic"/><br /><sub><b>geodic</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=geodic" title="Code">💻</a></td>
|
<td align="center" valign="top" width="14.28%"><a href="https://github.com/geodic"><img src="https://avatars.githubusercontent.com/u/64704703?v=4?s=100" width="100px;" alt="geodic"/><br /><sub><b>geodic</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=geodic" title="Code">💻</a></td>
|
||||||
|
<td align="center" valign="top" width="14.28%"><a href="https://p.foundation/"><img src="https://avatars.githubusercontent.com/u/80860929?v=4?s=100" width="100px;" alt="P Foundation"/><br /><sub><b>P Foundation</b></sub></a><br /><a href="#financial-pfoundation" title="Financial">💵</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
@@ -358,6 +358,7 @@ Windows上では、`Tabby.exe`がある場所と同じ場所に`data`フォル
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/geodic"><img src="https://avatars.githubusercontent.com/u/64704703?v=4?s=100" width="100px;" alt="geodic"/><br /><sub><b>geodic</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=geodic" title="Code">💻</a></td>
|
<td align="center" valign="top" width="14.28%"><a href="https://github.com/geodic"><img src="https://avatars.githubusercontent.com/u/64704703?v=4?s=100" width="100px;" alt="geodic"/><br /><sub><b>geodic</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=geodic" title="Code">💻</a></td>
|
||||||
|
<td align="center" valign="top" width="14.28%"><a href="https://p.foundation/"><img src="https://avatars.githubusercontent.com/u/80860929?v=4?s=100" width="100px;" alt="P Foundation"/><br /><sub><b>P Foundation</b></sub></a><br /><a href="#financial-pfoundation" title="Financial">💵</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
@@ -107,7 +107,7 @@ This README is also available in: <a href="./README.md">:gb: English</a> · <a
|
|||||||
|
|
||||||
플러그인과 테마는 Tabby 내부의 설정에서 직접 설치할 수 있습니다.
|
플러그인과 테마는 Tabby 내부의 설정에서 직접 설치할 수 있습니다.
|
||||||
|
|
||||||
* [clickable-links](https://github.com/Eugeny/tabby-clickable-links) - m터미널의 경로 및 URL을 클릭 가능하게
|
* [clickable-links](https://github.com/Eugeny/tabby-clickable-links) - 터미널의 경로 및 URL을 클릭 가능하게
|
||||||
* [docker](https://github.com/Eugeny/tabby-docker) - Docker 컨테이너에 연결
|
* [docker](https://github.com/Eugeny/tabby-docker) - Docker 컨테이너에 연결
|
||||||
* [title-control](https://github.com/kbjr/terminus-title-control) - 접두사, 접미사 및/또는 문자열 제거를 제공하여 터미널 탭의 제목을 수정
|
* [title-control](https://github.com/kbjr/terminus-title-control) - 접두사, 접미사 및/또는 문자열 제거를 제공하여 터미널 탭의 제목을 수정
|
||||||
* [quick-cmds](https://github.com/Domain/terminus-quick-cmds) - 하나 또는 모든 터미널 탭에 신속한 명령 전송
|
* [quick-cmds](https://github.com/Domain/terminus-quick-cmds) - 하나 또는 모든 터미널 탭에 신속한 명령 전송
|
||||||
@@ -144,7 +144,7 @@ Pull requests and plugins are welcome!
|
|||||||
---
|
---
|
||||||
<a name="contributors"></a>
|
<a name="contributors"></a>
|
||||||
|
|
||||||
여기있는 멋진 사람들에게 진심으로 감사합니다. ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
|
여기 있는 멋진 사람들에게 진심으로 감사합니다. ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
|
||||||
|
|
||||||
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
|
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
|
||||||
<!-- prettier-ignore-start -->
|
<!-- prettier-ignore-start -->
|
||||||
@@ -342,6 +342,7 @@ Pull requests and plugins are welcome!
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/geodic"><img src="https://avatars.githubusercontent.com/u/64704703?v=4?s=100" width="100px;" alt="geodic"/><br /><sub><b>geodic</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=geodic" title="Code">💻</a></td>
|
<td align="center" valign="top" width="14.28%"><a href="https://github.com/geodic"><img src="https://avatars.githubusercontent.com/u/64704703?v=4?s=100" width="100px;" alt="geodic"/><br /><sub><b>geodic</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=geodic" title="Code">💻</a></td>
|
||||||
|
<td align="center" valign="top" width="14.28%"><a href="https://p.foundation/"><img src="https://avatars.githubusercontent.com/u/80860929?v=4?s=100" width="100px;" alt="P Foundation"/><br /><sub><b>P Foundation</b></sub></a><br /><a href="#financial-pfoundation" title="Financial">💵</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
@@ -366,6 +366,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/geodic"><img src="https://avatars.githubusercontent.com/u/64704703?v=4?s=100" width="100px;" alt="geodic"/><br /><sub><b>geodic</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=geodic" title="Code">💻</a></td>
|
<td align="center" valign="top" width="14.28%"><a href="https://github.com/geodic"><img src="https://avatars.githubusercontent.com/u/64704703?v=4?s=100" width="100px;" alt="geodic"/><br /><sub><b>geodic</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=geodic" title="Code">💻</a></td>
|
||||||
|
<td align="center" valign="top" width="14.28%"><a href="https://p.foundation/"><img src="https://avatars.githubusercontent.com/u/80860929?v=4?s=100" width="100px;" alt="P Foundation"/><br /><sub><b>P Foundation</b></sub></a><br /><a href="#financial-pfoundation" title="Financial">💵</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
@@ -351,6 +351,7 @@ Obrigado vai para essas pessoas maravilhosas ([emoji key](https://allcontributor
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/geodic"><img src="https://avatars.githubusercontent.com/u/64704703?v=4?s=100" width="100px;" alt="geodic"/><br /><sub><b>geodic</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=geodic" title="Code">💻</a></td>
|
<td align="center" valign="top" width="14.28%"><a href="https://github.com/geodic"><img src="https://avatars.githubusercontent.com/u/64704703?v=4?s=100" width="100px;" alt="geodic"/><br /><sub><b>geodic</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=geodic" title="Code">💻</a></td>
|
||||||
|
<td align="center" valign="top" width="14.28%"><a href="https://p.foundation/"><img src="https://avatars.githubusercontent.com/u/80860929?v=4?s=100" width="100px;" alt="P Foundation"/><br /><sub><b>P Foundation</b></sub></a><br /><a href="#financial-pfoundation" title="Financial">💵</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
@@ -343,6 +343,7 @@ Pull-запросы и плагины приветствуются!
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/geodic"><img src="https://avatars.githubusercontent.com/u/64704703?v=4?s=100" width="100px;" alt="geodic"/><br /><sub><b>geodic</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=geodic" title="Code">💻</a></td>
|
<td align="center" valign="top" width="14.28%"><a href="https://github.com/geodic"><img src="https://avatars.githubusercontent.com/u/64704703?v=4?s=100" width="100px;" alt="geodic"/><br /><sub><b>geodic</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=geodic" title="Code">💻</a></td>
|
||||||
|
<td align="center" valign="top" width="14.28%"><a href="https://p.foundation/"><img src="https://avatars.githubusercontent.com/u/80860929?v=4?s=100" width="100px;" alt="P Foundation"/><br /><sub><b>P Foundation</b></sub></a><br /><a href="#financial-pfoundation" title="Financial">💵</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
@@ -342,6 +342,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/geodic"><img src="https://avatars.githubusercontent.com/u/64704703?v=4?s=100" width="100px;" alt="geodic"/><br /><sub><b>geodic</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=geodic" title="Code">💻</a></td>
|
<td align="center" valign="top" width="14.28%"><a href="https://github.com/geodic"><img src="https://avatars.githubusercontent.com/u/64704703?v=4?s=100" width="100px;" alt="geodic"/><br /><sub><b>geodic</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=geodic" title="Code">💻</a></td>
|
||||||
|
<td align="center" valign="top" width="14.28%"><a href="https://p.foundation/"><img src="https://avatars.githubusercontent.com/u/80860929?v=4?s=100" width="100px;" alt="P Foundation"/><br /><sub><b>P Foundation</b></sub></a><br /><a href="#financial-pfoundation" title="Financial">💵</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
@@ -16,7 +16,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@electron/remote": "^2",
|
"@electron/remote": "^2",
|
||||||
"node-pty": "^1.0.0",
|
"node-pty": "^1.1.0-beta34",
|
||||||
"any-promise": "^1.3.0",
|
"any-promise": "^1.3.0",
|
||||||
"electron-config": "2.0.0",
|
"electron-config": "2.0.0",
|
||||||
"electron-debug": "^3.2.0",
|
"electron-debug": "^3.2.0",
|
||||||
|
@@ -1,53 +0,0 @@
|
|||||||
diff --git a/node_modules/node-pty/binding.gyp b/node_modules/node-pty/binding.gyp
|
|
||||||
index 79a93e7..efb0a3f 100644
|
|
||||||
--- a/node_modules/node-pty/binding.gyp
|
|
||||||
+++ b/node_modules/node-pty/binding.gyp
|
|
||||||
@@ -18,6 +18,9 @@
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
+ 'defines': [
|
|
||||||
+ 'NOMINMAX'
|
|
||||||
+ ]
|
|
||||||
}],
|
|
||||||
],
|
|
||||||
},
|
|
||||||
diff --git a/node_modules/node-pty/src/win/winpty.cc b/node_modules/node-pty/src/win/winpty.cc
|
|
||||||
index b054dee..a094b1c 100644
|
|
||||||
--- a/node_modules/node-pty/src/win/winpty.cc
|
|
||||||
+++ b/node_modules/node-pty/src/win/winpty.cc
|
|
||||||
@@ -164,7 +164,7 @@ static NAN_METHOD(PtyStartProcess) {
|
|
||||||
Nan::ThrowError(why.str().c_str());
|
|
||||||
goto cleanup;
|
|
||||||
}
|
|
||||||
-
|
|
||||||
+ {
|
|
||||||
int cols = info[4]->Int32Value(Nan::GetCurrentContext()).FromJust();
|
|
||||||
int rows = info[5]->Int32Value(Nan::GetCurrentContext()).FromJust();
|
|
||||||
bool debug = Nan::To<bool>(info[6]).FromJust();
|
|
||||||
@@ -179,6 +179,7 @@ static NAN_METHOD(PtyStartProcess) {
|
|
||||||
throw_winpty_error("Error creating WinPTY config", error_ptr);
|
|
||||||
goto cleanup;
|
|
||||||
}
|
|
||||||
+ {
|
|
||||||
winpty_error_free(error_ptr);
|
|
||||||
|
|
||||||
// Set pty size on config
|
|
||||||
@@ -215,7 +216,7 @@ static NAN_METHOD(PtyStartProcess) {
|
|
||||||
winpty_error_free(error_ptr);
|
|
||||||
|
|
||||||
// Set return values
|
|
||||||
- v8::Local<v8::Object> marshal = Nan::New<v8::Object>();
|
|
||||||
+ {v8::Local<v8::Object> marshal = Nan::New<v8::Object>();
|
|
||||||
Nan::Set(marshal, Nan::New<v8::String>("innerPid").ToLocalChecked(), Nan::New<v8::Number>((int)GetProcessId(handle)));
|
|
||||||
Nan::Set(marshal, Nan::New<v8::String>("innerPidHandle").ToLocalChecked(), Nan::New<v8::Number>((int)handle));
|
|
||||||
Nan::Set(marshal, Nan::New<v8::String>("pid").ToLocalChecked(), Nan::New<v8::Number>((int)winpty_agent_process(pc)));
|
|
||||||
@@ -232,7 +233,7 @@ static NAN_METHOD(PtyStartProcess) {
|
|
||||||
Nan::Set(marshal, Nan::New<v8::String>("conout").ToLocalChecked(), Nan::New<v8::String>(conoutPipeNameStr).ToLocalChecked());
|
|
||||||
}
|
|
||||||
info.GetReturnValue().Set(marshal);
|
|
||||||
-
|
|
||||||
+ }}}
|
|
||||||
goto cleanup;
|
|
||||||
|
|
||||||
cleanup:
|
|
@@ -2607,7 +2607,7 @@ mz@^2.7.0:
|
|||||||
object-assign "^4.0.1"
|
object-assign "^4.0.1"
|
||||||
thenify-all "^1.0.0"
|
thenify-all "^1.0.0"
|
||||||
|
|
||||||
nan@2.22.2, nan@^2.17.0:
|
nan@2.22.2:
|
||||||
version "2.22.2"
|
version "2.22.2"
|
||||||
resolved "https://registry.yarnpkg.com/nan/-/nan-2.22.2.tgz#6b504fd029fb8f38c0990e52ad5c26772fdacfbb"
|
resolved "https://registry.yarnpkg.com/nan/-/nan-2.22.2.tgz#6b504fd029fb8f38c0990e52ad5c26772fdacfbb"
|
||||||
integrity sha512-DANghxFkS1plDdRsX0X9pm0Z6SJNN6gBdtXfanwoZ8hooC5gosGFSBGRYHUVPz1asKA/kMRqDRdHrluZ61SpBQ==
|
integrity sha512-DANghxFkS1plDdRsX0X9pm0Z6SJNN6gBdtXfanwoZ8hooC5gosGFSBGRYHUVPz1asKA/kMRqDRdHrluZ61SpBQ==
|
||||||
@@ -2648,7 +2648,7 @@ node-abi@4.9.0, node-abi@^3.3.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
semver "^7.6.3"
|
semver "^7.6.3"
|
||||||
|
|
||||||
node-addon-api@3.1.0, node-addon-api@6.1.0, node-addon-api@7.1.0, node-addon-api@^3.0.2, node-addon-api@^3.1.0, node-addon-api@^4.0.0, node-addon-api@^4.3.0, node-addon-api@^8.3.0:
|
node-addon-api@3.1.0, node-addon-api@6.1.0, node-addon-api@7.1.0, node-addon-api@^3.0.2, node-addon-api@^3.1.0, node-addon-api@^4.0.0, node-addon-api@^4.3.0, node-addon-api@^7.1.0, node-addon-api@^8.3.0:
|
||||||
version "8.3.0"
|
version "8.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-8.3.0.tgz#ec3763f18befc1cdf66d11e157ce44d5eddc0603"
|
resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-8.3.0.tgz#ec3763f18befc1cdf66d11e157ce44d5eddc0603"
|
||||||
integrity sha512-8VOpLHFrOQlAH+qA0ZzuGRlALRA6/LVh8QJldbrC4DY0hXoMP0l4Acq8TzFC018HztWiRqyCEj2aTWY2UvnJUg==
|
integrity sha512-8VOpLHFrOQlAH+qA0ZzuGRlALRA6/LVh8QJldbrC4DY0hXoMP0l4Acq8TzFC018HztWiRqyCEj2aTWY2UvnJUg==
|
||||||
@@ -2683,12 +2683,12 @@ node-gyp@^10.0.0, node-gyp@^5.0.2, node-gyp@^5.1.0:
|
|||||||
tar "^6.1.2"
|
tar "^6.1.2"
|
||||||
which "^4.0.0"
|
which "^4.0.0"
|
||||||
|
|
||||||
node-pty@^1.0.0:
|
node-pty@^1.1.0-beta34:
|
||||||
version "1.0.0"
|
version "1.1.0-beta9"
|
||||||
resolved "https://registry.yarnpkg.com/node-pty/-/node-pty-1.0.0.tgz#7daafc0aca1c4ca3de15c61330373af4af5861fd"
|
resolved "https://registry.yarnpkg.com/node-pty/-/node-pty-1.1.0-beta9.tgz#ed643cb3b398d031b4e31c216e8f3b0042435f1d"
|
||||||
integrity sha512-wtBMWWS7dFZm/VgqElrTvtfMq4GzJ6+edFI0Y0zyzygUSZMgZdraDUMUhCIvkjhJjme15qWmbyJbtAx4ot4uZA==
|
integrity sha512-/Ue38pvXJdgRZ3+me1FgfglLd301GhJN0NStiotdt61tm43N5htUyR/IXOUzOKuNaFmCwIhy6nwb77Ky41LMbw==
|
||||||
dependencies:
|
dependencies:
|
||||||
nan "^2.17.0"
|
node-addon-api "^7.1.0"
|
||||||
|
|
||||||
nopt@^4.0.3:
|
nopt@^4.0.3:
|
||||||
version "4.0.3"
|
version "4.0.3"
|
||||||
@@ -3939,8 +3939,7 @@ strict-uri-encode@^2.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz#b9c7330c7042862f6b142dc274bbcc5866ce3546"
|
resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz#b9c7330c7042862f6b142dc274bbcc5866ce3546"
|
||||||
integrity sha1-ucczDHBChi9rFC3CdLvMWGbONUY=
|
integrity sha1-ucczDHBChi9rFC3CdLvMWGbONUY=
|
||||||
|
|
||||||
"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
|
"string-width-cjs@npm:string-width@^4.2.0":
|
||||||
name string-width-cjs
|
|
||||||
version "4.2.3"
|
version "4.2.3"
|
||||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
||||||
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
||||||
@@ -3975,6 +3974,15 @@ string-width@^3.0.0, string-width@^3.1.0:
|
|||||||
is-fullwidth-code-point "^2.0.0"
|
is-fullwidth-code-point "^2.0.0"
|
||||||
strip-ansi "^5.1.0"
|
strip-ansi "^5.1.0"
|
||||||
|
|
||||||
|
string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
|
||||||
|
version "4.2.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
||||||
|
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
||||||
|
dependencies:
|
||||||
|
emoji-regex "^8.0.0"
|
||||||
|
is-fullwidth-code-point "^3.0.0"
|
||||||
|
strip-ansi "^6.0.1"
|
||||||
|
|
||||||
string-width@^5.0.1, string-width@^5.1.2:
|
string-width@^5.0.1, string-width@^5.1.2:
|
||||||
version "5.1.2"
|
version "5.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794"
|
resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794"
|
||||||
@@ -4017,8 +4025,7 @@ stringify-package@^1.0.0, stringify-package@^1.0.1:
|
|||||||
resolved "https://registry.yarnpkg.com/stringify-package/-/stringify-package-1.0.1.tgz#e5aa3643e7f74d0f28628b72f3dad5cecfc3ba85"
|
resolved "https://registry.yarnpkg.com/stringify-package/-/stringify-package-1.0.1.tgz#e5aa3643e7f74d0f28628b72f3dad5cecfc3ba85"
|
||||||
integrity sha512-sa4DUQsYciMP1xhKWGuFM04fB0LG/9DlluZoSVywUMRNvzid6XucHK0/90xGxRoHrAaROrcHK1aPKaijCtSrhg==
|
integrity sha512-sa4DUQsYciMP1xhKWGuFM04fB0LG/9DlluZoSVywUMRNvzid6XucHK0/90xGxRoHrAaROrcHK1aPKaijCtSrhg==
|
||||||
|
|
||||||
"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.1:
|
"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
|
||||||
name strip-ansi-cjs
|
|
||||||
version "6.0.1"
|
version "6.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
||||||
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
||||||
@@ -4053,6 +4060,13 @@ strip-ansi@^6.0.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
ansi-regex "^5.0.0"
|
ansi-regex "^5.0.0"
|
||||||
|
|
||||||
|
strip-ansi@^6.0.1:
|
||||||
|
version "6.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
||||||
|
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
||||||
|
dependencies:
|
||||||
|
ansi-regex "^5.0.1"
|
||||||
|
|
||||||
strip-ansi@^7.0.1:
|
strip-ansi@^7.0.1:
|
||||||
version "7.1.0"
|
version "7.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45"
|
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45"
|
||||||
@@ -4457,8 +4471,7 @@ worker-farm@^1.6.0, worker-farm@^1.7.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
errno "~0.1.7"
|
errno "~0.1.7"
|
||||||
|
|
||||||
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
|
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
|
||||||
name wrap-ansi-cjs
|
|
||||||
version "7.0.0"
|
version "7.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
|
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
|
||||||
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
|
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
|
||||||
@@ -4484,6 +4497,15 @@ wrap-ansi@^5.1.0:
|
|||||||
string-width "^3.0.0"
|
string-width "^3.0.0"
|
||||||
strip-ansi "^5.0.0"
|
strip-ansi "^5.0.0"
|
||||||
|
|
||||||
|
wrap-ansi@^7.0.0:
|
||||||
|
version "7.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
|
||||||
|
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
|
||||||
|
dependencies:
|
||||||
|
ansi-styles "^4.0.0"
|
||||||
|
string-width "^4.1.0"
|
||||||
|
strip-ansi "^6.0.0"
|
||||||
|
|
||||||
wrap-ansi@^8.1.0:
|
wrap-ansi@^8.1.0:
|
||||||
version "8.1.0"
|
version "8.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"
|
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"
|
||||||
|
@@ -40,7 +40,7 @@
|
|||||||
"cross-env": "7.0.3",
|
"cross-env": "7.0.3",
|
||||||
"css-loader": "^6.7.3",
|
"css-loader": "^6.7.3",
|
||||||
"deep-equal": "2.0.5",
|
"deep-equal": "2.0.5",
|
||||||
"electron": "^36.3",
|
"electron": "^36.4",
|
||||||
"electron-builder": "^26.0",
|
"electron-builder": "^26.0",
|
||||||
"electron-download": "^4.1.1",
|
"electron-download": "^4.1.1",
|
||||||
"electron-installer-snap": "^5.1.0",
|
"electron-installer-snap": "^5.1.0",
|
||||||
|
@@ -10,7 +10,7 @@ export { Theme } from './theme'
|
|||||||
export { TabContextMenuItemProvider } from './tabContextMenuProvider'
|
export { TabContextMenuItemProvider } from './tabContextMenuProvider'
|
||||||
export { SelectorOption } from './selector'
|
export { SelectorOption } from './selector'
|
||||||
export { CLIHandler, CLIEvent } from './cli'
|
export { CLIHandler, CLIEvent } from './cli'
|
||||||
export { PlatformService, ClipboardContent, MessageBoxResult, MessageBoxOptions, FileDownload, FileUpload, FileTransfer, HTMLFileUpload, FileUploadOptions, DirectoryUpload } from './platform'
|
export { PlatformService, ClipboardContent, MessageBoxResult, MessageBoxOptions, FileDownload, FileUpload, FileTransfer, HTMLFileUpload, FileUploadOptions, DirectoryUpload, DirectoryDownload, PlatformTheme } from './platform'
|
||||||
export { MenuItemOptions } from './menu'
|
export { MenuItemOptions } from './menu'
|
||||||
export { BootstrapData, PluginInfo, BOOTSTRAP_DATA } from './mainProcess'
|
export { BootstrapData, PluginInfo, BOOTSTRAP_DATA } from './mainProcess'
|
||||||
export { HostWindowService } from './hostWindow'
|
export { HostWindowService } from './hostWindow'
|
||||||
|
@@ -22,7 +22,6 @@ 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
|
||||||
|
|
||||||
@@ -34,8 +33,16 @@ export abstract class FileTransfer {
|
|||||||
return this.completedBytes
|
return this.completedBytes
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getStatus (): string {
|
||||||
|
return this.status
|
||||||
|
}
|
||||||
|
|
||||||
|
getTotalSize (): number {
|
||||||
|
return this.totalSize
|
||||||
|
}
|
||||||
|
|
||||||
isComplete (): boolean {
|
isComplete (): boolean {
|
||||||
return this.completedBytes >= this.getSize()
|
return this.completed
|
||||||
}
|
}
|
||||||
|
|
||||||
isCancelled (): boolean {
|
isCancelled (): boolean {
|
||||||
@@ -47,6 +54,18 @@ export abstract class FileTransfer {
|
|||||||
this.close()
|
this.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setStatus (status: string): void {
|
||||||
|
this.status = status
|
||||||
|
}
|
||||||
|
|
||||||
|
setTotalSize (size: number): void {
|
||||||
|
this.totalSize = size
|
||||||
|
}
|
||||||
|
|
||||||
|
setCompleted (completed: boolean): void {
|
||||||
|
this.completed = completed
|
||||||
|
}
|
||||||
|
|
||||||
protected increaseProgress (bytes: number): void {
|
protected increaseProgress (bytes: number): void {
|
||||||
if (!bytes) {
|
if (!bytes) {
|
||||||
return
|
return
|
||||||
@@ -57,16 +76,26 @@ export abstract class FileTransfer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private completedBytes = 0
|
private completedBytes = 0
|
||||||
|
private totalSize = 0
|
||||||
private lastChunkStartTime = Date.now()
|
private lastChunkStartTime = Date.now()
|
||||||
private lastChunkSpeed = 0
|
private lastChunkSpeed = 0
|
||||||
private cancelled = false
|
private cancelled = false
|
||||||
|
private completed = false
|
||||||
|
private status = ''
|
||||||
}
|
}
|
||||||
|
|
||||||
export abstract class FileDownload extends FileTransfer {
|
export abstract class FileDownload extends FileTransfer {
|
||||||
abstract write (buffer: Uint8Array): Promise<void>
|
abstract write (buffer: Uint8Array): Promise<void>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export abstract class DirectoryDownload extends FileTransfer {
|
||||||
|
abstract createDirectory (relativePath: string): Promise<void>
|
||||||
|
abstract createFile (relativePath: string, mode: number, size: number): Promise<FileDownload>
|
||||||
|
}
|
||||||
|
|
||||||
export abstract class FileUpload extends FileTransfer {
|
export abstract class FileUpload extends FileTransfer {
|
||||||
|
abstract getMode (): number
|
||||||
|
|
||||||
abstract read (): Promise<Uint8Array>
|
abstract read (): Promise<Uint8Array>
|
||||||
|
|
||||||
async readAll (): Promise<Uint8Array> {
|
async readAll (): Promise<Uint8Array> {
|
||||||
@@ -127,6 +156,7 @@ export abstract class PlatformService {
|
|||||||
abstract saveConfig (content: string): Promise<void>
|
abstract saveConfig (content: string): Promise<void>
|
||||||
|
|
||||||
abstract startDownload (name: string, mode: number, size: number): Promise<FileDownload|null>
|
abstract startDownload (name: string, mode: number, size: number): Promise<FileDownload|null>
|
||||||
|
abstract startDownloadDirectory (name: string, estimatedSize?: number): Promise<DirectoryDownload|null>
|
||||||
abstract startUpload (options?: FileUploadOptions): Promise<FileUpload[]>
|
abstract startUpload (options?: FileUploadOptions): Promise<FileUpload[]>
|
||||||
abstract startUploadDirectory (paths?: string[]): Promise<DirectoryUpload>
|
abstract startUploadDirectory (paths?: string[]): Promise<DirectoryUpload>
|
||||||
|
|
||||||
@@ -237,7 +267,7 @@ export abstract class PlatformService {
|
|||||||
abstract setErrorHandler (handler: (_: any) => void): void
|
abstract setErrorHandler (handler: (_: any) => void): void
|
||||||
abstract popupContextMenu (menu: MenuItemOptions[], event?: MouseEvent): void
|
abstract popupContextMenu (menu: MenuItemOptions[], event?: MouseEvent): void
|
||||||
abstract showMessageBox (options: MessageBoxOptions): Promise<MessageBoxResult>
|
abstract showMessageBox (options: MessageBoxOptions): Promise<MessageBoxResult>
|
||||||
abstract pickDirectory (): Promise<string>
|
abstract pickDirectory (): Promise<string | null>
|
||||||
abstract quit (): void
|
abstract quit (): void
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -5,7 +5,9 @@
|
|||||||
.icon(*ngIf='isDownload(transfer)') !{require('../icons/download.svg')}
|
.icon(*ngIf='isDownload(transfer)') !{require('../icons/download.svg')}
|
||||||
.icon(*ngIf='!isDownload(transfer)') !{require('../icons/upload.svg')}
|
.icon(*ngIf='!isDownload(transfer)') !{require('../icons/upload.svg')}
|
||||||
.main
|
.main
|
||||||
label.no-wrap([ngbTooltip]='transfer.getName()') {{transfer.getName()}}
|
label.no-wrap([ngbTooltip]='transfer.getName()')
|
||||||
|
| {{transfer.getName()}}
|
||||||
|
span.ms-2.text-muted(*ngIf='transfer.getStatus()') ({{transfer.getStatus()}})
|
||||||
ngb-progressbar([type]='transfer.isComplete() ? "success" : transfer.isCancelled() ? "danger" : "info"', [value]='getProgress(transfer)')
|
ngb-progressbar([type]='transfer.isComplete() ? "success" : transfer.isCancelled() ? "danger" : "info"', [value]='getProgress(transfer)')
|
||||||
.metadata
|
.metadata
|
||||||
.size {{transfer.getSize()|filesize}}
|
.size {{transfer.getSize()|filesize}}
|
||||||
|
@@ -5,12 +5,11 @@ import * as os from 'os'
|
|||||||
import promiseIpc, { RendererProcessType } 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, Platform, MenuItemOptions, MessageBoxOptions, MessageBoxResult, DirectoryUpload, FileUpload, FileDownload, FileUploadOptions, wrapPromise, TranslateService } from 'tabby-core'
|
import { PlatformService, ClipboardContent, Platform, MenuItemOptions, MessageBoxOptions, MessageBoxResult, DirectoryUpload, FileUpload, FileDownload, DirectoryDownload, FileUploadOptions, wrapPromise, TranslateService, FileTransfer, PlatformTheme } from 'tabby-core'
|
||||||
import { ElectronService } from '../services/electron.service'
|
import { ElectronService } from '../services/electron.service'
|
||||||
import { ElectronHostWindow } from './hostWindow.service'
|
import { ElectronHostWindow } from './hostWindow.service'
|
||||||
import { ShellIntegrationService } from './shellIntegration.service'
|
import { ShellIntegrationService } from './shellIntegration.service'
|
||||||
import { ElectronHostAppService } from './hostApp.service'
|
import { ElectronHostAppService } from './hostApp.service'
|
||||||
import { PlatformTheme } from '../../../tabby-core/src/api/platform'
|
|
||||||
import { configPath } from '../../../app/lib/config'
|
import { configPath } from '../../../app/lib/config'
|
||||||
const fontManager = require('fontmanager-redux') // eslint-disable-line
|
const fontManager = require('fontmanager-redux') // eslint-disable-line
|
||||||
|
|
||||||
@@ -272,19 +271,48 @@ export class ElectronPlatformService extends PlatformService {
|
|||||||
return transfer
|
return transfer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async startDownloadDirectory (name: string, estimatedSize?: number): Promise<DirectoryDownload|null> {
|
||||||
|
const selectedFolder = await this.pickDirectory(this.translate.instant('Select destination folder for {name}', { name }), this.translate.instant('Download here'))
|
||||||
|
if (!selectedFolder) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
let downloadPath = path.join(selectedFolder, name)
|
||||||
|
let counter = 1
|
||||||
|
while (fsSync.existsSync(downloadPath)) {
|
||||||
|
downloadPath = path.join(selectedFolder, `${name} (${counter})`)
|
||||||
|
counter++
|
||||||
|
}
|
||||||
|
|
||||||
|
const transfer = new ElectronDirectoryDownload(downloadPath, name, estimatedSize ?? 0, this.electron, this.zone)
|
||||||
|
await wrapPromise(this.zone, transfer.open())
|
||||||
|
this.fileTransferStarted.next(transfer)
|
||||||
|
return transfer
|
||||||
|
}
|
||||||
|
|
||||||
|
_registerFileTransfer (transfer: FileTransfer): void {
|
||||||
|
this.fileTransferStarted.next(transfer)
|
||||||
|
}
|
||||||
|
|
||||||
setErrorHandler (handler: (_: any) => void): void {
|
setErrorHandler (handler: (_: any) => void): void {
|
||||||
this.electron.ipcRenderer.on('uncaughtException', (_$event, err) => {
|
this.electron.ipcRenderer.on('uncaughtException', (_$event, err) => {
|
||||||
handler(err)
|
handler(err)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async pickDirectory (): Promise<string> {
|
async pickDirectory (title?: string, buttonLabel?: string): Promise<string | null> {
|
||||||
return (await this.electron.dialog.showOpenDialog(
|
const result = await this.electron.dialog.showOpenDialog(
|
||||||
this.hostWindow.getWindow(),
|
this.hostWindow.getWindow(),
|
||||||
{
|
{
|
||||||
|
title,
|
||||||
|
buttonLabel,
|
||||||
properties: ['openDirectory', 'showHiddenFiles'],
|
properties: ['openDirectory', 'showHiddenFiles'],
|
||||||
},
|
},
|
||||||
)).filePaths[0]
|
)
|
||||||
|
if (result.canceled || !result.filePaths.length) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return result.filePaths[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
getTheme (): PlatformTheme {
|
getTheme (): PlatformTheme {
|
||||||
@@ -313,6 +341,7 @@ class ElectronFileUpload extends FileUpload {
|
|||||||
const stat = await fs.stat(this.filePath)
|
const stat = await fs.stat(this.filePath)
|
||||||
this.size = stat.size
|
this.size = stat.size
|
||||||
this.mode = stat.mode
|
this.mode = stat.mode
|
||||||
|
this.setTotalSize(this.size)
|
||||||
this.file = await fs.open(this.filePath, 'r')
|
this.file = await fs.open(this.filePath, 'r')
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -331,6 +360,9 @@ class ElectronFileUpload extends FileUpload {
|
|||||||
async read (): Promise<Uint8Array> {
|
async read (): Promise<Uint8Array> {
|
||||||
const result = await this.file.read(this.buffer, 0, this.buffer.length, null)
|
const result = await this.file.read(this.buffer, 0, this.buffer.length, null)
|
||||||
this.increaseProgress(result.bytesRead)
|
this.increaseProgress(result.bytesRead)
|
||||||
|
if (this.getCompletedBytes() >= this.getSize()) {
|
||||||
|
this.setCompleted(true)
|
||||||
|
}
|
||||||
return this.buffer.slice(0, result.bytesRead)
|
return this.buffer.slice(0, result.bytesRead)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -352,6 +384,7 @@ class ElectronFileDownload extends FileDownload {
|
|||||||
) {
|
) {
|
||||||
super()
|
super()
|
||||||
this.powerSaveBlocker = electron.powerSaveBlocker.start('prevent-app-suspension')
|
this.powerSaveBlocker = electron.powerSaveBlocker.start('prevent-app-suspension')
|
||||||
|
this.setTotalSize(size)
|
||||||
}
|
}
|
||||||
|
|
||||||
async open (): Promise<void> {
|
async open (): Promise<void> {
|
||||||
@@ -362,10 +395,6 @@ class ElectronFileDownload extends FileDownload {
|
|||||||
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
|
||||||
}
|
}
|
||||||
@@ -377,6 +406,9 @@ class ElectronFileDownload extends FileDownload {
|
|||||||
this.increaseProgress(result.bytesWritten)
|
this.increaseProgress(result.bytesWritten)
|
||||||
pos += result.bytesWritten
|
pos += result.bytesWritten
|
||||||
}
|
}
|
||||||
|
if (this.getCompletedBytes() >= this.getSize()) {
|
||||||
|
this.setCompleted(true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
close (): void {
|
close (): void {
|
||||||
@@ -384,3 +416,49 @@ class ElectronFileDownload extends FileDownload {
|
|||||||
this.file.close()
|
this.file.close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class ElectronDirectoryDownload extends DirectoryDownload {
|
||||||
|
private powerSaveBlocker = 0
|
||||||
|
|
||||||
|
constructor (
|
||||||
|
private basePath: string,
|
||||||
|
private name: string,
|
||||||
|
estimatedSize: number,
|
||||||
|
private electron: ElectronService,
|
||||||
|
private zone: NgZone,
|
||||||
|
) {
|
||||||
|
super()
|
||||||
|
this.powerSaveBlocker = electron.powerSaveBlocker.start('prevent-app-suspension')
|
||||||
|
this.setTotalSize(estimatedSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
async open (): Promise<void> {
|
||||||
|
await fs.mkdir(this.basePath, { recursive: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
getName (): string {
|
||||||
|
return this.name
|
||||||
|
}
|
||||||
|
|
||||||
|
getSize (): number {
|
||||||
|
return this.getTotalSize()
|
||||||
|
}
|
||||||
|
|
||||||
|
async createDirectory (relativePath: string): Promise<void> {
|
||||||
|
const fullPath = path.join(this.basePath, relativePath)
|
||||||
|
await fs.mkdir(fullPath, { recursive: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
async createFile (relativePath: string, mode: number, size: number): Promise<FileDownload> {
|
||||||
|
const fullPath = path.join(this.basePath, relativePath)
|
||||||
|
await fs.mkdir(path.dirname(fullPath), { recursive: true })
|
||||||
|
|
||||||
|
const fileDownload = new ElectronFileDownload(fullPath, mode, size, this.electron)
|
||||||
|
await wrapPromise(this.zone, fileDownload.open())
|
||||||
|
return fileDownload
|
||||||
|
}
|
||||||
|
|
||||||
|
close (): void {
|
||||||
|
this.electron.powerSaveBlocker.stop(this.powerSaveBlocker)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -28,6 +28,10 @@ export class LocalProfileSettingsComponent implements ProfileSettingsComponent<L
|
|||||||
// return
|
// return
|
||||||
// }
|
// }
|
||||||
|
|
||||||
this.profile.options.cwd = await this.platform.pickDirectory()
|
const cwd = await this.platform.pickDirectory()
|
||||||
|
if (!cwd) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.profile.options.cwd = cwd
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -17,6 +17,10 @@
|
|||||||
|
|
||||||
.breadcrumb-spacer.flex-grow-1.h-100((dblclick)='editPath()')
|
.breadcrumb-spacer.flex-grow-1.h-100((dblclick)='editPath()')
|
||||||
|
|
||||||
|
button.btn.btn-link.btn-sm.flex-shrink-0.d-flex(*ngIf='!showFilter', (click)='showFilter = true')
|
||||||
|
i.fas.fa-filter.me-1
|
||||||
|
div(translate) Filter
|
||||||
|
|
||||||
button.btn.btn-link.btn-sm.flex-shrink-0.d-flex((click)='openCreateDirectoryModal()')
|
button.btn.btn-link.btn-sm.flex-shrink-0.d-flex((click)='openCreateDirectoryModal()')
|
||||||
i.fas.fa-plus.me-1
|
i.fas.fa-plus.me-1
|
||||||
div(translate) Create directory
|
div(translate) Create directory
|
||||||
@@ -31,6 +35,19 @@
|
|||||||
|
|
||||||
button.btn.btn-link.text-decoration-none((click)='close()') !{require('../../../tabby-core/src/icons/times.svg')}
|
button.btn.btn-link.text-decoration-none((click)='close()') !{require('../../../tabby-core/src/icons/times.svg')}
|
||||||
|
|
||||||
|
.filter-bar.px-3.py-2.border-bottom(*ngIf='showFilter')
|
||||||
|
.input-group
|
||||||
|
input.form-control(
|
||||||
|
type='text',
|
||||||
|
placeholder='Filter...',
|
||||||
|
autofocus,
|
||||||
|
[(ngModel)]='filterText',
|
||||||
|
(input)='onFilterChange()',
|
||||||
|
(keydown.escape)='clearFilter()'
|
||||||
|
)
|
||||||
|
button.btn.btn-secondary((click)='clearFilter()')
|
||||||
|
i.fas.fa-times
|
||||||
|
|
||||||
.body(dropZone, (transfer)='uploadOneFolder($event)')
|
.body(dropZone, (transfer)='uploadOneFolder($event)')
|
||||||
a.alert.alert-info.d-flex.align-items-center(
|
a.alert.alert-info.d-flex.align-items-center(
|
||||||
*ngIf='shouldShowCWDTip && !cwdDetectionAvailable',
|
*ngIf='shouldShowCWDTip && !cwdDetectionAvailable',
|
||||||
@@ -47,13 +64,13 @@
|
|||||||
div(*ngIf='fileList === null', translate) Loading
|
div(*ngIf='fileList === null', translate) Loading
|
||||||
.list-group.list-group-light(*ngIf='fileList !== null')
|
.list-group.list-group-light(*ngIf='fileList !== null')
|
||||||
.list-group-item.list-group-item-action.d-flex.align-items-center(
|
.list-group-item.list-group-item-action.d-flex.align-items-center(
|
||||||
*ngIf='path !== "/"',
|
*ngIf='path !== "/" && (!showFilter || filterText.trim() === "")',
|
||||||
(click)='goUp()'
|
(click)='goUp()'
|
||||||
)
|
)
|
||||||
i.fas.fa-fw.fa-level-up-alt
|
i.fas.fa-fw.fa-level-up-alt
|
||||||
div(translate) Go up
|
div(translate) Go up
|
||||||
.list-group-item.list-group-item-action.d-flex.align-items-center(
|
.list-group-item.list-group-item-action.d-flex.align-items-center(
|
||||||
*ngFor='let item of fileList',
|
*ngFor='let item of filteredFileList',
|
||||||
(contextmenu)='showContextMenu(item, $event)',
|
(contextmenu)='showContextMenu(item, $event)',
|
||||||
(click)='open(item)'
|
(click)='open(item)'
|
||||||
)
|
)
|
||||||
@@ -63,3 +80,6 @@
|
|||||||
.size(*ngIf='!item.isDirectory') {{item.size|filesize}}
|
.size(*ngIf='!item.isDirectory') {{item.size|filesize}}
|
||||||
.date {{item.modified|tabbyDate}}
|
.date {{item.modified|tabbyDate}}
|
||||||
.mode {{getModeString(item)}}
|
.mode {{getModeString(item)}}
|
||||||
|
.alert.alert-info.text-center.mt-3(*ngIf='fileList !== null && filteredFileList.length === 0 && showFilter && filterText.trim() !== ""')
|
||||||
|
i.fas.fa-search.me-2
|
||||||
|
span(translate) No files match the filter "{{filterText}}"
|
||||||
|
@@ -9,6 +9,10 @@
|
|||||||
flex: none;
|
flex: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
> .filter-bar {
|
||||||
|
flex: none;
|
||||||
|
}
|
||||||
|
|
||||||
> .body {
|
> .body {
|
||||||
padding: 10px 20px;
|
padding: 10px 20px;
|
||||||
flex: 1 1 0;
|
flex: 1 1 0;
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import * as C from 'constants'
|
import * as C from 'constants'
|
||||||
import { posix as path } from 'path'
|
import { posix as path } from 'path'
|
||||||
import { Component, Input, Output, EventEmitter, Inject, Optional } from '@angular/core'
|
import { Component, Input, Output, EventEmitter, Inject, Optional } from '@angular/core'
|
||||||
import { FileUpload, DirectoryUpload, MenuItemOptions, NotificationsService, PlatformService } from 'tabby-core'
|
import { FileUpload, DirectoryUpload, DirectoryDownload, MenuItemOptions, NotificationsService, PlatformService } from 'tabby-core'
|
||||||
import { SFTPSession, SFTPFile } from '../session/sftp'
|
import { SFTPSession, SFTPFile } from '../session/sftp'
|
||||||
import { SSHSession } from '../session/ssh'
|
import { SSHSession } from '../session/ssh'
|
||||||
import { SFTPContextMenuItemProvider } from '../api'
|
import { SFTPContextMenuItemProvider } from '../api'
|
||||||
@@ -23,11 +23,14 @@ export class SFTPPanelComponent {
|
|||||||
@Output() closed = new EventEmitter<void>()
|
@Output() closed = new EventEmitter<void>()
|
||||||
sftp: SFTPSession
|
sftp: SFTPSession
|
||||||
fileList: SFTPFile[]|null = null
|
fileList: SFTPFile[]|null = null
|
||||||
|
filteredFileList: SFTPFile[] = []
|
||||||
@Input() path = '/'
|
@Input() path = '/'
|
||||||
@Output() pathChange = new EventEmitter<string>()
|
@Output() pathChange = new EventEmitter<string>()
|
||||||
pathSegments: PathSegment[] = []
|
pathSegments: PathSegment[] = []
|
||||||
@Input() cwdDetectionAvailable = false
|
@Input() cwdDetectionAvailable = false
|
||||||
editingPath: string|null = null
|
editingPath: string|null = null
|
||||||
|
showFilter = false
|
||||||
|
filterText = ''
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
private ngbModal: NgbModal,
|
private ngbModal: NgbModal,
|
||||||
@@ -54,6 +57,8 @@ export class SFTPPanelComponent {
|
|||||||
this.path = newPath
|
this.path = newPath
|
||||||
this.pathChange.next(this.path)
|
this.pathChange.next(this.path)
|
||||||
|
|
||||||
|
this.clearFilter()
|
||||||
|
|
||||||
let p = newPath
|
let p = newPath
|
||||||
this.pathSegments = []
|
this.pathSegments = []
|
||||||
while (p !== '/') {
|
while (p !== '/') {
|
||||||
@@ -65,6 +70,7 @@ export class SFTPPanelComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.fileList = null
|
this.fileList = null
|
||||||
|
this.filteredFileList = []
|
||||||
try {
|
try {
|
||||||
this.fileList = await this.sftp.readdir(this.path)
|
this.fileList = await this.sftp.readdir(this.path)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -79,6 +85,8 @@ export class SFTPPanelComponent {
|
|||||||
this.fileList.sort((a, b) =>
|
this.fileList.sort((a, b) =>
|
||||||
dirKey(b) - dirKey(a) ||
|
dirKey(b) - dirKey(a) ||
|
||||||
a.name.localeCompare(b.name))
|
a.name.localeCompare(b.name))
|
||||||
|
|
||||||
|
this.updateFilteredList()
|
||||||
}
|
}
|
||||||
|
|
||||||
getFileType (fileExtension: string): string {
|
getFileType (fileExtension: string): string {
|
||||||
@@ -220,6 +228,68 @@ export class SFTPPanelComponent {
|
|||||||
this.sftp.download(itemPath, transfer)
|
this.sftp.download(itemPath, transfer)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async downloadFolder (folder: SFTPFile): Promise<void> {
|
||||||
|
try {
|
||||||
|
const transfer = await this.platform.startDownloadDirectory(folder.name, 0)
|
||||||
|
if (!transfer) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start background size calculation and download simultaneously
|
||||||
|
const sizeCalculationPromise = this.calculateFolderSizeAndUpdate(folder, transfer)
|
||||||
|
const downloadPromise = this.downloadFolderRecursive(folder, transfer, '')
|
||||||
|
|
||||||
|
try {
|
||||||
|
await Promise.all([sizeCalculationPromise, downloadPromise])
|
||||||
|
transfer.setStatus('')
|
||||||
|
transfer.setCompleted(true)
|
||||||
|
} catch (error) {
|
||||||
|
transfer.cancel()
|
||||||
|
throw error
|
||||||
|
} finally {
|
||||||
|
transfer.close()
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
this.notifications.error(`Failed to download folder: ${error.message}`)
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async calculateFolderSizeAndUpdate (folder: SFTPFile, transfer: DirectoryDownload) {
|
||||||
|
let totalSize = 0
|
||||||
|
const items = await this.sftp.readdir(folder.fullPath)
|
||||||
|
for (const item of items) {
|
||||||
|
if (item.isDirectory) {
|
||||||
|
totalSize += await this.calculateFolderSizeAndUpdate(item, transfer)
|
||||||
|
} else {
|
||||||
|
totalSize += item.size
|
||||||
|
}
|
||||||
|
transfer.setTotalSize(totalSize)
|
||||||
|
}
|
||||||
|
return totalSize
|
||||||
|
}
|
||||||
|
|
||||||
|
private async downloadFolderRecursive (folder: SFTPFile, transfer: DirectoryDownload, relativePath: string): Promise<void> {
|
||||||
|
const items = await this.sftp.readdir(folder.fullPath)
|
||||||
|
|
||||||
|
for (const item of items) {
|
||||||
|
if (transfer.isCancelled()) {
|
||||||
|
throw new Error('Download cancelled')
|
||||||
|
}
|
||||||
|
|
||||||
|
const itemRelativePath = relativePath ? `${relativePath}/${item.name}` : item.name
|
||||||
|
|
||||||
|
transfer.setStatus(itemRelativePath)
|
||||||
|
if (item.isDirectory) {
|
||||||
|
await transfer.createDirectory(itemRelativePath)
|
||||||
|
await this.downloadFolderRecursive(item, transfer, itemRelativePath)
|
||||||
|
} else {
|
||||||
|
const fileDownload = await transfer.createFile(itemRelativePath, item.mode, item.size)
|
||||||
|
await this.sftp.download(item.fullPath, fileDownload)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
getModeString (item: SFTPFile): string {
|
getModeString (item: SFTPFile): string {
|
||||||
const s = 'SGdrwxrwxrwx'
|
const s = 'SGdrwxrwxrwx'
|
||||||
const e = ' ---------'
|
const e = ' ---------'
|
||||||
@@ -274,4 +344,29 @@ export class SFTPPanelComponent {
|
|||||||
this.closed.emit()
|
this.closed.emit()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
clearFilter (): void {
|
||||||
|
this.showFilter = false
|
||||||
|
this.filterText = ''
|
||||||
|
this.updateFilteredList()
|
||||||
|
}
|
||||||
|
|
||||||
|
onFilterChange (): void {
|
||||||
|
this.updateFilteredList()
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateFilteredList (): void {
|
||||||
|
if (!this.fileList) {
|
||||||
|
this.filteredFileList = []
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.showFilter || this.filterText.trim() === '') {
|
||||||
|
this.filteredFileList = this.fileList
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.filteredFileList = this.fileList.filter(item =>
|
||||||
|
item.name.toLowerCase().includes(this.filterText.toLowerCase()),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -20,7 +20,7 @@ export class SSHProfilesService extends QuickConnectProfileProvider<SSHProfile>
|
|||||||
auth: null,
|
auth: null,
|
||||||
password: null,
|
password: null,
|
||||||
privateKeys: [],
|
privateKeys: [],
|
||||||
keepaliveInterval: 5000,
|
keepaliveInterval: null,
|
||||||
keepaliveCountMax: 10,
|
keepaliveCountMax: 10,
|
||||||
readyTimeout: null,
|
readyTimeout: null,
|
||||||
x11: false,
|
x11: false,
|
||||||
|
@@ -317,7 +317,7 @@ export class SSHSession {
|
|||||||
key: this.profile.options.algorithms?.[SSHAlgorithmType.HOSTKEY]?.filter(x => supportedAlgorithms[SSHAlgorithmType.HOSTKEY].includes(x)),
|
key: this.profile.options.algorithms?.[SSHAlgorithmType.HOSTKEY]?.filter(x => supportedAlgorithms[SSHAlgorithmType.HOSTKEY].includes(x)),
|
||||||
compression: this.profile.options.algorithms?.[SSHAlgorithmType.COMPRESSION]?.filter(x => supportedAlgorithms[SSHAlgorithmType.COMPRESSION].includes(x)),
|
compression: this.profile.options.algorithms?.[SSHAlgorithmType.COMPRESSION]?.filter(x => supportedAlgorithms[SSHAlgorithmType.COMPRESSION].includes(x)),
|
||||||
},
|
},
|
||||||
keepaliveIntervalSeconds: Math.round((this.profile.options.keepaliveInterval ?? 15000) / 1000),
|
keepaliveIntervalSeconds: this.profile.options.keepaliveInterval ? Math.round(this.profile.options.keepaliveInterval / 1000) : undefined,
|
||||||
keepaliveCountMax: this.profile.options.keepaliveCountMax,
|
keepaliveCountMax: this.profile.options.keepaliveCountMax,
|
||||||
connectionTimeoutSeconds: this.profile.options.readyTimeout ? Math.round(this.profile.options.readyTimeout / 1000) : undefined,
|
connectionTimeoutSeconds: this.profile.options.readyTimeout ? Math.round(this.profile.options.readyTimeout / 1000) : undefined,
|
||||||
},
|
},
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import { Injectable } from '@angular/core'
|
import { Injectable } from '@angular/core'
|
||||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
||||||
import { MenuItemOptions, PlatformService, TranslateService } from 'tabby-core'
|
import { MenuItemOptions, PlatformService, TranslateService, HostAppService, Platform } from 'tabby-core'
|
||||||
import { SFTPSession, SFTPFile } from './session/sftp'
|
import { SFTPSession, SFTPFile } from './session/sftp'
|
||||||
import { SFTPContextMenuItemProvider } from './api'
|
import { SFTPContextMenuItemProvider } from './api'
|
||||||
import { SFTPDeleteModalComponent } from './components/sftpDeleteModal.component'
|
import { SFTPDeleteModalComponent } from './components/sftpDeleteModal.component'
|
||||||
@@ -16,19 +16,30 @@ export class CommonSFTPContextMenu extends SFTPContextMenuItemProvider {
|
|||||||
private platform: PlatformService,
|
private platform: PlatformService,
|
||||||
private ngbModal: NgbModal,
|
private ngbModal: NgbModal,
|
||||||
private translate: TranslateService,
|
private translate: TranslateService,
|
||||||
|
private hostApp: HostAppService,
|
||||||
) {
|
) {
|
||||||
super()
|
super()
|
||||||
}
|
}
|
||||||
|
|
||||||
async getItems (item: SFTPFile, panel: SFTPPanelComponent): Promise<MenuItemOptions[]> {
|
async getItems (item: SFTPFile, panel: SFTPPanelComponent): Promise<MenuItemOptions[]> {
|
||||||
return [
|
const items: MenuItemOptions[] = [
|
||||||
{
|
{
|
||||||
click: async () => {
|
click: async () => {
|
||||||
await panel.openCreateDirectoryModal()
|
await panel.openCreateDirectoryModal()
|
||||||
},
|
},
|
||||||
label: this.translate.instant('Create directory'),
|
label: this.translate.instant('Create directory'),
|
||||||
},
|
},
|
||||||
{
|
]
|
||||||
|
|
||||||
|
// Add download folder option for directories (only in electron)
|
||||||
|
if (item.isDirectory && this.hostApp.platform !== Platform.Web) {
|
||||||
|
items.push({
|
||||||
|
click: () => panel.downloadFolder(item),
|
||||||
|
label: this.translate.instant('Download directory'),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
items.push({
|
||||||
click: async () => {
|
click: async () => {
|
||||||
if ((await this.platform.showMessageBox({
|
if ((await this.platform.showMessageBox({
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
@@ -45,8 +56,9 @@ export class CommonSFTPContextMenu extends SFTPContextMenuItemProvider {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
label: this.translate.instant('Delete'),
|
label: this.translate.instant('Delete'),
|
||||||
},
|
})
|
||||||
]
|
|
||||||
|
return items
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteItem (item: SFTPFile, session: SFTPSession): Promise<void> {
|
async deleteItem (item: SFTPFile, session: SFTPSession): Promise<void> {
|
||||||
|
@@ -27,7 +27,7 @@ class ZModemMiddleware extends SessionMiddleware {
|
|||||||
this.logger = log.create('zmodem')
|
this.logger = log.create('zmodem')
|
||||||
this.sentry = new ZModem.Sentry({
|
this.sentry = new ZModem.Sentry({
|
||||||
to_terminal: data => {
|
to_terminal: data => {
|
||||||
if (this.isActive) {
|
if (this.isActive && this.activeSession) {
|
||||||
this.outputToTerminal.next(Buffer.from(data))
|
this.outputToTerminal.next(Buffer.from(data))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -42,25 +42,32 @@ class ZModemMiddleware extends SessionMiddleware {
|
|||||||
},
|
},
|
||||||
on_retract: () => {
|
on_retract: () => {
|
||||||
this.showMessage('transfer cancelled')
|
this.showMessage('transfer cancelled')
|
||||||
|
this.activeSession = null
|
||||||
|
this.isActive = false
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
feedFromSession (data: Buffer): void {
|
feedFromSession (data: Buffer): void {
|
||||||
const chunkSize = 1024
|
if (this.isActive || this.activeSession) {
|
||||||
for (let i = 0; i <= Math.floor(data.length / chunkSize); i++) {
|
|
||||||
try {
|
try {
|
||||||
this.sentry.consume(Buffer.from(data.slice(i * chunkSize, (i + 1) * chunkSize)))
|
this.sentry.consume(data)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.showMessage(colors.bgRed.black(' Error ') + ' ' + e)
|
this.showMessage(colors.bgRed.black(' Error ') + ' ' + e)
|
||||||
this.logger.error('protocol error', e)
|
this.logger.error('protocol error', e)
|
||||||
this.activeSession.abort()
|
this.activeSession?.abort()
|
||||||
this.activeSession = null
|
this.activeSession = null
|
||||||
this.isActive = false
|
this.isActive = false
|
||||||
|
// Don't forward the problematic data to terminal
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
this.sentry.consume(data)
|
||||||
|
} catch (e) {
|
||||||
|
this.logger.error('zmodem detection error', e)
|
||||||
}
|
}
|
||||||
if (!this.isActive) {
|
|
||||||
this.outputToTerminal.next(data)
|
this.outputToTerminal.next(data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -73,6 +80,7 @@ class ZModemMiddleware extends SessionMiddleware {
|
|||||||
this.activeSession = zsession
|
this.activeSession = zsession
|
||||||
this.logger.info('new session', zsession)
|
this.logger.info('new session', zsession)
|
||||||
|
|
||||||
|
try {
|
||||||
if (zsession.type === 'send') {
|
if (zsession.type === 'send') {
|
||||||
const transfers = await this.platform.startUpload({ multiple: true })
|
const transfers = await this.platform.startUpload({ multiple: true })
|
||||||
let filesRemaining = transfers.length
|
let filesRemaining = transfers.length
|
||||||
@@ -82,7 +90,6 @@ class ZModemMiddleware extends SessionMiddleware {
|
|||||||
filesRemaining--
|
filesRemaining--
|
||||||
sizeRemaining -= transfer.getSize()
|
sizeRemaining -= transfer.getSize()
|
||||||
}
|
}
|
||||||
this.activeSession = null
|
|
||||||
await zsession.close()
|
await zsession.close()
|
||||||
} else {
|
} else {
|
||||||
zsession.on('offer', xfer => {
|
zsession.on('offer', xfer => {
|
||||||
@@ -92,6 +99,16 @@ class ZModemMiddleware extends SessionMiddleware {
|
|||||||
zsession.start()
|
zsession.start()
|
||||||
|
|
||||||
await new Promise(resolve => zsession.on('session_end', resolve))
|
await new Promise(resolve => zsession.on('session_end', resolve))
|
||||||
|
}
|
||||||
|
|
||||||
|
this.showMessage(colors.bgBlue.black(' ZMODEM ') + ' Complete')
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error('ZMODEM session error', error)
|
||||||
|
this.showMessage(colors.bgRed.black(' ZMODEM ') + ` Session failed: ${error.message}`)
|
||||||
|
try {
|
||||||
|
zsession.abort()
|
||||||
|
} catch { }
|
||||||
|
} finally {
|
||||||
this.activeSession = null
|
this.activeSession = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -2,7 +2,7 @@ import '@vaadin/vaadin-context-menu'
|
|||||||
import copyToClipboard from 'copy-text-to-clipboard'
|
import copyToClipboard from 'copy-text-to-clipboard'
|
||||||
import { Injectable, Inject } from '@angular/core'
|
import { Injectable, Inject } from '@angular/core'
|
||||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
||||||
import { PlatformService, ClipboardContent, MenuItemOptions, MessageBoxOptions, MessageBoxResult, FileUpload, FileUploadOptions, FileDownload, HTMLFileUpload, DirectoryUpload } from 'tabby-core'
|
import { PlatformService, ClipboardContent, MenuItemOptions, MessageBoxOptions, MessageBoxResult, FileUpload, FileUploadOptions, FileDownload, DirectoryDownload, HTMLFileUpload, DirectoryUpload } from 'tabby-core'
|
||||||
|
|
||||||
// eslint-disable-next-line no-duplicate-imports
|
// eslint-disable-next-line no-duplicate-imports
|
||||||
import type { ContextMenuElement, ContextMenuItem } from '@vaadin/vaadin-context-menu'
|
import type { ContextMenuElement, ContextMenuItem } from '@vaadin/vaadin-context-menu'
|
||||||
@@ -114,6 +114,10 @@ export class WebPlatformService extends PlatformService {
|
|||||||
return transfer
|
return transfer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async startDownloadDirectory (_name: string, _estimatedSize?: number): Promise<DirectoryDownload|null> {
|
||||||
|
throw new Error('Unsupported')
|
||||||
|
}
|
||||||
|
|
||||||
startUpload (options?: FileUploadOptions): Promise<FileUpload[]> {
|
startUpload (options?: FileUploadOptions): Promise<FileUpload[]> {
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
this.fileSelector.onchange = () => {
|
this.fileSelector.onchange = () => {
|
||||||
|
@@ -3179,10 +3179,10 @@ electron-to-chromium@^1.4.284:
|
|||||||
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.286.tgz#0e039de59135f44ab9a8ec9025e53a9135eba11f"
|
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.286.tgz#0e039de59135f44ab9a8ec9025e53a9135eba11f"
|
||||||
integrity sha512-Vp3CVhmYpgf4iXNKAucoQUDcCrBQX3XLBtwgFqP9BUXuucgvAV9zWp1kYU7LL9j4++s9O+12cb3wMtN4SJy6UQ==
|
integrity sha512-Vp3CVhmYpgf4iXNKAucoQUDcCrBQX3XLBtwgFqP9BUXuucgvAV9zWp1kYU7LL9j4++s9O+12cb3wMtN4SJy6UQ==
|
||||||
|
|
||||||
electron@^36.3:
|
electron@^36.4:
|
||||||
version "36.3.1"
|
version "36.7.1"
|
||||||
resolved "https://registry.yarnpkg.com/electron/-/electron-36.3.1.tgz#12a8c1b1cd9163a4bd0cb60f89816243b26ab788"
|
resolved "https://registry.yarnpkg.com/electron/-/electron-36.7.1.tgz#73bbb460c60f529e00b9d3eff78fd135c42172ea"
|
||||||
integrity sha512-LeOZ+tVahmctHaAssLCGRRUa2SAO09GXua3pKdG+WzkbSDMh+3iOPONNVPTqGp8HlWnzGj4r6mhsIbM2RgH+eQ==
|
integrity sha512-vkih7vbmWT6O8+VWFt3a9FMLUZn0O4piR20nTX0IL/d9tz9RjpzoMvHqpI2CE1Rxew9bCzrg7FpgtcTdY6dlyw==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@electron/get" "^2.0.0"
|
"@electron/get" "^2.0.0"
|
||||||
"@types/node" "^22.7.7"
|
"@types/node" "^22.7.7"
|
||||||
|
Reference in New Issue
Block a user