Compare commits
81 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 12a9fa92c1 | |||
| 0fd6617eba | |||
| 7ebbf929d5 | |||
| 14223c2204 | |||
| 2cfb6b1914 | |||
| c38cf80589 | |||
| 3ee98eae1d | |||
| d2cff6356a | |||
| 3ee432d683 | |||
| 7f1822bb7e | |||
| 0223e691ff | |||
| 117dba9f37 | |||
| 66f114bf72 | |||
| 44f00483f9 | |||
| 1866e3fd4f | |||
| cfac883cbf | |||
| 1431bab366 | |||
| bb373947ff | |||
| 8f0dc65341 | |||
| 402185e1a2 | |||
| d59b5c9841 | |||
| eb442e4a7a | |||
| e82e567069 | |||
| dc2876098d | |||
| 6287942fbc | |||
| 389dc080b6 | |||
| 45a23d73e6 | |||
| 8303dd305b | |||
| a98292ce1e | |||
| 8df97b8433 | |||
| f29e95c9bc | |||
| 52e045c886 | |||
| 4632c3619a | |||
| 54c48be29b | |||
| 301f1403df | |||
| 88de047778 | |||
| c119de78ce | |||
| 9d381e16da | |||
| e4c6d6a9c0 | |||
| 5bb727cb6f | |||
| 00f3209c68 | |||
| 3f8b0e6f5f | |||
| 0543bc4e2c | |||
| 9ded42f127 | |||
| 834a1ad839 | |||
| 8f830f6a0d | |||
| ee111c92ee | |||
| 74c3a3c696 | |||
| 4d964ae16e | |||
| be2c2e8383 | |||
| 4e6164816f | |||
| fa7daf377f | |||
| afbe364525 | |||
| ece5cea512 | |||
| dc8fa5c647 | |||
| 4fd96e7cae | |||
| 1f22b5efba | |||
| 801db0ed38 | |||
| f72577de65 | |||
| dd21c76dea | |||
| 5a5a932735 | |||
| d63c1d431d | |||
| 1a12256c4c | |||
| 9394a0a79b | |||
| 5e66205298 | |||
| eb796c3c5f | |||
| 9d17b3e9b2 | |||
| 7ee8d4fa0f | |||
| dba4bfb0e7 | |||
| f9755f69cd | |||
| 7a68e1bb82 | |||
| 3b12f3960f | |||
| dbe26dfa98 | |||
| 3be8952cff | |||
| dc5a368e00 | |||
| 11aca7ea0b | |||
| c35af2b109 | |||
| b33b505ccb | |||
| dd22cc0306 | |||
| 206eda08aa | |||
| f6e29700d0 |
@@ -28,7 +28,7 @@ if (process.env.BUILD_TARGET === 'clean') {
|
||||
}
|
||||
|
||||
function clean () {
|
||||
del.sync(['build/*', '!build/icons', '!build/icons/icon.*'])
|
||||
del.sync(['release/*', '!.gitkeep'])
|
||||
console.log(`\n${doneLog}\n`)
|
||||
process.exit()
|
||||
}
|
||||
|
||||
@@ -93,10 +93,6 @@ let rendererConfig = {
|
||||
'css-loader'
|
||||
]
|
||||
},
|
||||
{
|
||||
test: /\.html$/,
|
||||
use: 'vue-html-loader'
|
||||
},
|
||||
{
|
||||
test: /\.js$/,
|
||||
use: 'babel-loader',
|
||||
@@ -237,13 +233,13 @@ if (!devMode) {
|
||||
rendererConfig.devtool = ''
|
||||
|
||||
rendererConfig.plugins.push(
|
||||
new CopyWebpackPlugin([
|
||||
{
|
||||
new CopyWebpackPlugin({
|
||||
patterns: [{
|
||||
from: path.join(__dirname, '../static'),
|
||||
to: path.join(__dirname, '../dist/electron/static'),
|
||||
ignore: ['.*']
|
||||
}
|
||||
]),
|
||||
globOptions: { ignore: [ '.*' ] }
|
||||
}]
|
||||
}),
|
||||
new webpack.DefinePlugin({
|
||||
'process.env.NODE_ENV': '"production"'
|
||||
}),
|
||||
|
||||
@@ -209,13 +209,13 @@ if (!devMode) {
|
||||
webConfig.devtool = ''
|
||||
|
||||
webConfig.plugins.push(
|
||||
new CopyWebpackPlugin([
|
||||
{
|
||||
new CopyWebpackPlugin({
|
||||
patterns: [{
|
||||
from: path.join(__dirname, '../static'),
|
||||
to: path.join(__dirname, '../dist/web/static'),
|
||||
ignore: ['.*']
|
||||
}
|
||||
]),
|
||||
to: path.join(__dirname, '../dist/electron/static'),
|
||||
globOptions: { ignore: [ '.*' ] }
|
||||
}]
|
||||
}),
|
||||
new webpack.DefinePlugin({
|
||||
'process.env.NODE_ENV': '"production"'
|
||||
}),
|
||||
|
||||
+38
-11
@@ -4,15 +4,15 @@
|
||||
<img src="https://cdn.nlark.com/yuque/0/2018/png/129147/1543735425232-a5d2c99f-d788-43e4-9781-558ff6d21027.png" width="256" alt="App Icon" />
|
||||
</a>
|
||||
|
||||
[English](./README.md) | 简体中文
|
||||
|
||||
## 一款全能的下载工具
|
||||
|
||||
[](https://github.com/agalwood/Motrix/releases)  [](https://travis-ci.com/agalwood/Motrix) [](https://ci.appveyor.com/project/agalwood/motrix/branch/master) [](https://github.com/agalwood/Motrix/releases) 
|
||||
[](https://github.com/agalwood/Motrix/releases)   
|
||||
|
||||
[English](./README.md) | 简体中文
|
||||
|
||||
我是个兴趣使然的桌面应用开发者🤓,利用搬砖之余开发了 Motrix。
|
||||
|
||||
Motrix 是一款全能的下载工具,支持下载 HTTP、FTP、BT、磁力链、某盘等资源。它的界面简洁易用,希望大家喜欢 👻。
|
||||
Motrix 是一款全能的下载工具,支持下载 HTTP、FTP、BT、磁力链等资源。它的界面简洁易用,希望大家喜欢 👻。
|
||||
|
||||
✈️ 去 [官网](https://motrix.app/zh-CN) 逛逛 | 📖 查看 [帮助手册](http://motrix.app/support/issues)
|
||||
|
||||
@@ -41,10 +41,35 @@ brew update && brew cask install motrix
|
||||
|
||||
### Linux
|
||||
|
||||
你可以下载 AppImage(适用于所有 Linux 发行版)软件包或 snap 或从源代码构建安装 Motrix。
|
||||
你可以下载 `AppImage` (适用于所有 Linux 发行版)或 `snap` 来安装 Motrix,更多 Linux 安装包格式请查看 [GitHub/release](https://github.com/agalwood/Motrix/releases) 。
|
||||
|
||||
构建请阅读 **编译打包** 部分。
|
||||
如果你想自己通过编译源码来安装,请阅读 **编译打包** 部分。
|
||||
|
||||
#### AppImage
|
||||
最新版的 Motrix AppImage 需要自己手动进执行桌面集成。请查看 [AppImageLauncher](https://github.com/TheAssassin/AppImageLauncher) 的文档进行操作。
|
||||
|
||||
> 桌面集成
|
||||
> electron-builder v21 之后,桌面集成不再是 AppImage 文件的一部分。
|
||||
> 推荐使用 [AppImageLauncher](https://github.com/TheAssassin/AppImageLauncher) 集成 AppImage。
|
||||
|
||||
Deepin 20 Beta 用户安装 Motrix 失败的问题,请按照以下方法处理:
|
||||
|
||||
打开`终端`,黏贴运行如下命令之后再次安装 Motrix。
|
||||
```bash
|
||||
sudo apt --fix-broken install
|
||||
```
|
||||
|
||||
#### Snap
|
||||
Motrix 已经上架 [Snapcraft](https://snapcraft.io/motrix) ,Ubuntu 用户推荐从 Snap 商店下载。
|
||||
|
||||
v1.5.10 提示
|
||||
|
||||
系统托盘可能无法正常显示指示器,导致退出应用程序不方便。
|
||||
请取消勾选 偏好设置——基本设置——隐藏应用程序菜单(仅限Windows和Linux),点击保存并应用。然后点击 "文件 "菜单中的 "退出",退出应用程序。
|
||||
|
||||
请更新到 v1.5.12 及以上版本,可以使用键盘组合快捷键 <kbd>Ctrl</kbd> + <kbd>q</kbd> 快速退出应用。
|
||||
|
||||
#### AUR
|
||||
对于 Arch Linux 用户,可以使用 [aur](https://aur.archlinux.org/packages/motrix/) 安装 Motrix,感谢维护者 [weearc](https://github.com/weearc)。
|
||||
|
||||
运行以下命令进行安装:
|
||||
@@ -60,7 +85,8 @@ Motrix 在 Linux 中首次启动可能需要使用 `sudo` 运行,因为可能
|
||||
- 🕹 简洁明了的图形操作界面
|
||||
- 🦄 支持BT和磁力链任务
|
||||
- ☑️ 支持选择性下载BT部分文件
|
||||
- 💾 支持下载某盘资源
|
||||
- 📡 每天自动更新 Tracker 服务器列表
|
||||
- 🔌 UPnP & NAT-PMP 端口映射
|
||||
- 🎛 最高支持 10 个任务同时下载
|
||||
- 🚀 单任务最高支持 64 线程下载
|
||||
- 🚥 设置上传/下载限速
|
||||
@@ -71,11 +97,11 @@ Motrix 在 Linux 中首次启动可能需要使用 `sudo` 运行,因为可能
|
||||
- 🌑 深色模式
|
||||
- 🗑 移除任务时可同时删除相关文件
|
||||
- 🌍 国际化,[查看已可选的语言](#-国际化)
|
||||
- 🎏 ...
|
||||
- 🛠 更多特性开发中
|
||||
|
||||
## 🖥 应用界面
|
||||
|
||||

|
||||

|
||||
|
||||
## ⌨️ 本地开发
|
||||
|
||||
@@ -104,8 +130,6 @@ export SASS_BINARY_SITE='https://npm.taobao.org/mirrors/node-sass'
|
||||
|
||||
`Electron` 下载安装失败的问题,解决方式请参考 https://github.com/electron/electron/issues/8466#issuecomment-571425574
|
||||
|
||||
如果喜欢 [Yarn](https://yarnpkg.com/),也可以使用 `yarn` 安装依赖
|
||||
|
||||
### 开发模式
|
||||
|
||||
```bash
|
||||
@@ -140,17 +164,20 @@ npm run build
|
||||
|
||||
| Key | Name | Status |
|
||||
|-------|:--------------------|:-------------|
|
||||
| bg | Българският език | ✔️ [@null-none](https://github.com/null-none) |
|
||||
| ca | Català | ✔️ [@marcizhu](https://github.com/marcizhu) |
|
||||
| de | Deutsch | ✔️ [@Schloemicher](https://github.com/Schloemicher) |
|
||||
| en-US | English | ✔️ |
|
||||
| fa | فارسی | ✔️ [@Nima-Ra](https://github.com/Nima-Ra) |
|
||||
| fr | Français | ✔️ [@gpatarin](https://github.com/gpatarin) |
|
||||
| id | Indonesia | ✔️ [@aarestu](https://github.com/aarestu) |
|
||||
| ja | 日本語 | ✔️ [@hbkrkzk](https://github.com/hbkrkzk) |
|
||||
| ko | 한국어 | ✔️ [@KOZ39](https://github.com/KOZ39) |
|
||||
| pt-BR | Portuguese (Brazil) | ✔️ [@andrenoberto](https://github.com/andrenoberto) |
|
||||
| ru | Русский | ✔️ [@bladeaweb](https://github.com/bladeaweb) |
|
||||
| tr | Türkçe | ✔️ [@abdullah](https://github.com/abdullah) |
|
||||
| uk | Українська | ✔️ [@bladeaweb](https://github.com/bladeaweb) |
|
||||
| vi | Tiếng Việt | ✔️ [@duythanhvn](https://github.com/duythanhvn) |
|
||||
| zh-CN | 简体中文 | ✔️ |
|
||||
| zh-TW | 繁體中文 | ✔️ [@Yukaii](https://github.com/Yukaii) |
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
## A full-featured download manager
|
||||
|
||||
[](https://github.com/agalwood/Motrix/releases)  [](https://travis-ci.com/agalwood/Motrix) [](https://ci.appveyor.com/project/agalwood/motrix/branch/master) [](https://github.com/agalwood/Motrix/releases) 
|
||||
[](https://github.com/agalwood/Motrix/releases)   
|
||||
|
||||
English | [简体中文](./README-CN.md)
|
||||
|
||||
@@ -41,10 +41,37 @@ brew update && brew cask install motrix
|
||||
|
||||
### Linux
|
||||
|
||||
You can download the AppImage (for all Linux distributions) package or snap or just build from source code to install Motrix.
|
||||
You can download the `AppImage` (for all Linux distributions) or `snap` to install Motrix, see [GitHub/release](https://github.com/agalwood/Motrix/releases) for more Linux installation package formats.
|
||||
|
||||
Please read the **Build** section.
|
||||
If you want to build from source code, please read the **Build** section.
|
||||
|
||||
#### AppImage
|
||||
The latest version of Motrix AppImage requires you to manually perform desktop integration. Please check the documentation of [AppImageLauncher](https://github.com/TheAssassin/AppImageLauncher) .
|
||||
|
||||
> Desktop Integration
|
||||
> Since electron-builder 21 desktop integration is not a part of produced AppImage file.
|
||||
> [AppImageLauncher](https://github.com/TheAssassin/AppImageLauncher) is the recommended way to integrate AppImages.
|
||||
|
||||
Deepin 20 Beta users failed to install Motrix, please follow the steps below:
|
||||
|
||||
Open the `Terminal`, paste and run the following command to install Motrix again.
|
||||
|
||||
```bash
|
||||
sudo apt --fix-broken install
|
||||
```
|
||||
|
||||
#### Snap
|
||||
Motrix has been listed on [Snapcraft](https://snapcraft.io/motrix) , Ubuntu users recommend downloading from the Snap Store.
|
||||
|
||||
Tips for v1.5.10
|
||||
|
||||
The tray may not display the indicator normally, which makes it inconvenient to exit the application.
|
||||
|
||||
Please unchecked Preferences--Basic Settings--Hide App Menu (Windows & Linux Only), click Save & Apply. Then click "Exit" in the File menu to exit the application.
|
||||
|
||||
Please update to v1.5.12 and above, you can use the keyboard shortcut <kbd>Ctrl</kbd> + <kbd>q</kbd> to quickly exit the application.
|
||||
|
||||
#### AUR
|
||||
For Arch Linux users, Motrix is available in [aur](https://aur.archlinux.org/packages/motrix/), thanks to the maintainer [weearc](https://github.com/weearc).
|
||||
|
||||
Run the following command to install:
|
||||
@@ -60,7 +87,8 @@ Motrix may need to run with `sudo` for the first time in Linux because there is
|
||||
- 🕹 Simple and clear user interface
|
||||
- 🦄 Supports BitTorrent & Magnet
|
||||
- ☑️ BitTorrent selective download
|
||||
- 💾 Supports downloading BD Net Disk
|
||||
- 📡 Update tracker list every day automatically
|
||||
- 🔌 UPnP & NAT-PMP Port Mapping
|
||||
- 🎛 Up to 10 concurrent download tasks
|
||||
- 🚀 Supports 64 threads in a single task
|
||||
- 🚥 Supports speed limit
|
||||
@@ -71,11 +99,11 @@ Motrix may need to run with `sudo` for the first time in Linux because there is
|
||||
- 🌑 Dark mode
|
||||
- 🗑 Delete related files when removing tasks (optional)
|
||||
- 🌍 I18n, [View supported languages](#-internationalization).
|
||||
- 🎏 ...
|
||||
- 🛠 More features in development
|
||||
|
||||
## 🖥 User Interface
|
||||
|
||||

|
||||

|
||||
|
||||
## ⌨️ Development
|
||||
|
||||
@@ -96,8 +124,6 @@ npm install
|
||||
|
||||
`Electron` failed to install correctly, please refer to https://github.com/electron/electron/issues/8466#issuecomment-571425574
|
||||
|
||||
If you like [Yarn](https://yarnpkg.com/), you can also use `yarn` to install dependencies.
|
||||
|
||||
### Dev Mode
|
||||
|
||||
```bash
|
||||
@@ -132,17 +158,20 @@ Translations into versions for other languages are welcome 🧐! Please read the
|
||||
|
||||
| Key | Name | Status |
|
||||
|-------|:--------------------|:-------------|
|
||||
| bg | Българският език | ✔️ [@null-none](https://github.com/null-none) |
|
||||
| ca | Català | ✔️ [@marcizhu](https://github.com/marcizhu) |
|
||||
| de | Deutsch | ✔️ [@Schloemicher](https://github.com/Schloemicher) |
|
||||
| en-US | English | ✔️ |
|
||||
| fa | فارسی | ✔️ [@Nima-Ra](https://github.com/Nima-Ra) |
|
||||
| fr | Français | ✔️ [@gpatarin](https://github.com/gpatarin) |
|
||||
| id | Indonesia | ✔️ [@aarestu](https://github.com/aarestu) |
|
||||
| ja | 日本語 | ✔️ [@hbkrkzk](https://github.com/hbkrkzk) |
|
||||
| ko | 한국어 | ✔️ [@KOZ39](https://github.com/KOZ39) |
|
||||
| pt-BR | Portuguese (Brazil) | ✔️ [@andrenoberto](https://github.com/andrenoberto) |
|
||||
| ru | Русский | ✔️ [@bladeaweb](https://github.com/bladeaweb) |
|
||||
| tr | Türkçe | ✔️ [@abdullah](https://github.com/abdullah) |
|
||||
| uk | Українська | ✔️ [@bladeaweb](https://github.com/bladeaweb) |
|
||||
| vi | Tiếng Việt | ✔️ [@duythanhvn](https://github.com/duythanhvn) |
|
||||
| zh-CN | 简体中文 | ✔️ |
|
||||
| zh-TW | 繁體中文 | ✔️ [@Yukaii](https://github.com/Yukaii) |
|
||||
|
||||
|
||||
@@ -46,11 +46,13 @@ user-agent=Transmission/2.94
|
||||
# Enable Local Peer Discovery.
|
||||
bt-enable-lpd=true
|
||||
# Requires BitTorrent message payload encryption with arc4.
|
||||
bt-force-encryption=true
|
||||
# bt-force-encryption=true
|
||||
# If true is given, after hash check using --check-integrity option and file is complete, continue to seed file.
|
||||
bt-hash-check-seed=true
|
||||
# Specify the maximum number of peers per torrent.
|
||||
bt-max-peers=255
|
||||
# Try to download first and last pieces of each file first. This is useful for previewing files.
|
||||
bt-prioritize-piece=head
|
||||
# Removes the unselected files when download is completed in BitTorrent.
|
||||
bt-remove-unselected-file=true
|
||||
# Seed previously downloaded files without verifying piece hashes.
|
||||
|
||||
Binary file not shown.
@@ -46,11 +46,13 @@ user-agent=Transmission/2.94
|
||||
# Enable Local Peer Discovery.
|
||||
bt-enable-lpd=true
|
||||
# Requires BitTorrent message payload encryption with arc4.
|
||||
bt-force-encryption=true
|
||||
# bt-force-encryption=true
|
||||
# If true is given, after hash check using --check-integrity option and file is complete, continue to seed file.
|
||||
bt-hash-check-seed=true
|
||||
# Specify the maximum number of peers per torrent.
|
||||
bt-max-peers=255
|
||||
# Try to download first and last pieces of each file first. This is useful for previewing files.
|
||||
bt-prioritize-piece=head
|
||||
# Removes the unselected files when download is completed in BitTorrent.
|
||||
bt-remove-unselected-file=true
|
||||
# Seed previously downloaded files without verifying piece hashes.
|
||||
|
||||
@@ -46,11 +46,13 @@ user-agent=Transmission/2.94
|
||||
# Enable Local Peer Discovery.
|
||||
bt-enable-lpd=true
|
||||
# Requires BitTorrent message payload encryption with arc4.
|
||||
bt-force-encryption=true
|
||||
# bt-force-encryption=true
|
||||
# If true is given, after hash check using --check-integrity option and file is complete, continue to seed file.
|
||||
bt-hash-check-seed=true
|
||||
# Specify the maximum number of peers per torrent.
|
||||
bt-max-peers=255
|
||||
# Try to download first and last pieces of each file first. This is useful for previewing files.
|
||||
bt-prioritize-piece=head
|
||||
# Removes the unselected files when download is completed in BitTorrent.
|
||||
bt-remove-unselected-file=true
|
||||
# Seed previously downloaded files without verifying piece hashes.
|
||||
|
||||
Generated
+5422
-2936
File diff suppressed because it is too large
Load Diff
+28
-29
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "Motrix",
|
||||
"version": "1.5.7",
|
||||
"version": "1.5.15",
|
||||
"description": "A full-featured download manager",
|
||||
"homepage": "https://motrix.app",
|
||||
"author": {
|
||||
@@ -181,58 +181,58 @@
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.9.2",
|
||||
"@babel/runtime": "^7.10.2",
|
||||
"@panter/vue-i18next": "^0.15.2",
|
||||
"aria2": "^4.1.0",
|
||||
"axios": "^0.19.2",
|
||||
"blob-util": "^2.0.2",
|
||||
"clipboard-polyfill": "^2.8.6",
|
||||
"electron-debug": "^3.0.1",
|
||||
"electron-debug": "^3.1.0",
|
||||
"electron-is": "^3.0.0",
|
||||
"electron-log": "^4.1.1",
|
||||
"electron-log": "^4.2.1",
|
||||
"electron-store": "^5.1.1",
|
||||
"electron-updater": "^4.3.1",
|
||||
"element-ui": "^2.13.1",
|
||||
"forever-monitor": "1.7.2",
|
||||
"i18next": "^19.4.4",
|
||||
"element-ui": "^2.13.2",
|
||||
"forever-monitor": "3.0.0",
|
||||
"i18next": "^19.4.5",
|
||||
"lodash": "^4.17.15",
|
||||
"nat-api": "^0.1.3",
|
||||
"@motrix/nat-api": "^0.3.1",
|
||||
"node-fetch": "^2.6.0",
|
||||
"normalize.css": "^8.0.1",
|
||||
"parse-torrent": "^7.1.2",
|
||||
"parse-torrent": "^7.1.3",
|
||||
"randomatic": "^3.1.1",
|
||||
"svg-innerhtml": "^1.1.0",
|
||||
"vue": "^2.6.11",
|
||||
"vue-electron": "^1.0.6",
|
||||
"vue-router": "^3.1.6",
|
||||
"vuex": "^3.3.0",
|
||||
"vue-router": "^3.3.2",
|
||||
"vuex": "^3.4.0",
|
||||
"vuex-router-sync": "^5.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.9.0",
|
||||
"@babel/plugin-proposal-class-properties": "^7.8.3",
|
||||
"@babel/plugin-transform-runtime": "^7.9.0",
|
||||
"@babel/preset-env": "^7.9.5",
|
||||
"@babel/register": "^7.9.0",
|
||||
"@babel/core": "^7.10.2",
|
||||
"@babel/plugin-proposal-class-properties": "^7.10.1",
|
||||
"@babel/plugin-transform-runtime": "^7.10.1",
|
||||
"@babel/preset-env": "^7.10.2",
|
||||
"@babel/register": "^7.10.1",
|
||||
"@vue/eslint-config-standard": "^5.1.2",
|
||||
"ajv": "^6.12.2",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"babel-loader": "^8.1.0",
|
||||
"babel-plugin-component": "^1.1.1",
|
||||
"cfonts": "^2.8.1",
|
||||
"cfonts": "^2.8.2",
|
||||
"chalk": "^4.0.0",
|
||||
"copy-webpack-plugin": "^5.1.1",
|
||||
"copy-webpack-plugin": "^6.0.2",
|
||||
"cross-env": "^7.0.2",
|
||||
"css-loader": "^3.5.3",
|
||||
"del": "^5.1.0",
|
||||
"devtron": "^1.4.0",
|
||||
"electron": "^8.2.3",
|
||||
"electron-builder": "^22.6.0",
|
||||
"electron": "^8.3.1",
|
||||
"electron-builder": "^22.7.0",
|
||||
"electron-builder-notarize": "^1.1.2",
|
||||
"electron-devtools-installer": "^3.0.0",
|
||||
"electron-notarize": "^0.3.0",
|
||||
"electron-osx-sign": "^0.4.15",
|
||||
"eslint": "^6.8.0",
|
||||
"electron-osx-sign": "^0.4.16",
|
||||
"eslint": "^7.1.0",
|
||||
"eslint-friendly-formatter": "^4.0.1",
|
||||
"eslint-loader": "^4.0.2",
|
||||
"eslint-plugin-import": "^2.20.2",
|
||||
@@ -241,23 +241,22 @@
|
||||
"eslint-plugin-standard": "^4.0.1",
|
||||
"eslint-plugin-vue": "^6.2.2",
|
||||
"file-loader": "^6.0.0",
|
||||
"html-webpack-plugin": "^4.2.0",
|
||||
"html-webpack-plugin": "^4.3.0",
|
||||
"mini-css-extract-plugin": "0.9.0",
|
||||
"multispinner": "^0.2.1",
|
||||
"node-loader": "^0.6.0",
|
||||
"optimize-css-assets-webpack-plugin": "^5.0.3",
|
||||
"sass": "^1.26.5",
|
||||
"sass": "^1.26.8",
|
||||
"sass-loader": "^8.0.2",
|
||||
"style-loader": "^1.2.0",
|
||||
"terser-webpack-plugin": "^2.3.6",
|
||||
"style-loader": "^1.2.1",
|
||||
"terser-webpack-plugin": "^3.0.3",
|
||||
"url-loader": "^4.1.0",
|
||||
"vue-html-loader": "^1.2.4",
|
||||
"vue-loader": "^15.9.1",
|
||||
"vue-loader": "^15.9.2",
|
||||
"vue-style-loader": "^4.1.2",
|
||||
"vue-template-compiler": "^2.6.11",
|
||||
"webpack": "^4.43.0",
|
||||
"webpack-cli": "^3.3.11",
|
||||
"webpack-dev-server": "^3.10.3",
|
||||
"webpack-dev-server": "^3.11.0",
|
||||
"webpack-hot-middleware": "^2.25.0",
|
||||
"webpack-merge": "^4.2.2"
|
||||
}
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 33 KiB |
+106
-34
@@ -21,9 +21,16 @@ import TouchBarManager from './ui/TouchBarManager'
|
||||
import TrayManager from './ui/TrayManager'
|
||||
import DockManager from './ui/DockManager'
|
||||
import ThemeManager from './ui/ThemeManager'
|
||||
import { AUTO_SYNC_TRACKER_INTERVAL, AUTO_CHECK_UPDATE_INTERVAL } from '@shared/constants'
|
||||
import {
|
||||
APP_RUN_MODE,
|
||||
AUTO_SYNC_TRACKER_INTERVAL,
|
||||
AUTO_CHECK_UPDATE_INTERVAL
|
||||
} from '@shared/constants'
|
||||
import { checkIsNeedRun } from '@shared/utils'
|
||||
import { convertTrackerDataToComma, fetchBtTrackerFromSource } from '@shared/utils/tracker'
|
||||
import {
|
||||
convertTrackerDataToComma,
|
||||
fetchBtTrackerFromSource
|
||||
} from '@shared/utils/tracker'
|
||||
|
||||
export default class Application extends EventEmitter {
|
||||
constructor () {
|
||||
@@ -39,39 +46,28 @@ export default class Application extends EventEmitter {
|
||||
this.localeManager = setupLocaleManager(this.locale)
|
||||
this.i18n = this.localeManager.getI18n()
|
||||
|
||||
this.menuManager = new MenuManager()
|
||||
this.menuManager.setup(this.locale)
|
||||
|
||||
this.initTouchBarManager()
|
||||
this.setupApplicationMenu()
|
||||
|
||||
this.initWindowManager()
|
||||
|
||||
this.engine = new Engine({
|
||||
systemConfig: this.configManager.getSystemConfig(),
|
||||
userConfig: this.configManager.getUserConfig()
|
||||
})
|
||||
this.initUPnPManager()
|
||||
|
||||
this.startEngine()
|
||||
|
||||
this.initEngineClient()
|
||||
|
||||
this.initUPnPManager()
|
||||
this.initTouchBarManager()
|
||||
|
||||
this.autoSyncTracker()
|
||||
this.initThemeManager()
|
||||
|
||||
this.trayManager = new TrayManager({
|
||||
theme: this.configManager.getUserConfig('tray-theme')
|
||||
})
|
||||
this.initTrayManager()
|
||||
|
||||
this.dockManager = new DockManager({
|
||||
runMode: this.configManager.getUserConfig('run-mode')
|
||||
})
|
||||
this.initDockManager()
|
||||
|
||||
this.autoLaunchManager = new AutoLaunchManager()
|
||||
|
||||
this.energyManager = new EnergyManager()
|
||||
|
||||
this.initThemeManager()
|
||||
|
||||
this.initUpdaterManager()
|
||||
|
||||
this.initProtocolManager()
|
||||
@@ -81,12 +77,34 @@ export default class Application extends EventEmitter {
|
||||
this.handleEvents()
|
||||
|
||||
this.handleIpcMessages()
|
||||
|
||||
this.emit('application:initialized')
|
||||
}
|
||||
|
||||
setupApplicationMenu () {
|
||||
this.menuManager = new MenuManager()
|
||||
this.menuManager.setup(this.locale)
|
||||
}
|
||||
|
||||
adjustMenu () {
|
||||
if (is.mas()) {
|
||||
const visibleStates = {
|
||||
'app.check-for-updates': false,
|
||||
'task.new-bt-task': false
|
||||
}
|
||||
this.menuManager.updateMenuStates(visibleStates, null, null)
|
||||
this.trayManager.updateMenuStates(visibleStates, null, null)
|
||||
}
|
||||
}
|
||||
|
||||
startEngine () {
|
||||
const self = this
|
||||
|
||||
try {
|
||||
this.engine = new Engine({
|
||||
systemConfig: this.configManager.getSystemConfig(),
|
||||
userConfig: this.configManager.getUserConfig()
|
||||
})
|
||||
this.engine.start()
|
||||
} catch (err) {
|
||||
const { message } = err
|
||||
@@ -105,10 +123,12 @@ export default class Application extends EventEmitter {
|
||||
async stopEngine () {
|
||||
try {
|
||||
await this.engineClient.shutdown({ force: true })
|
||||
setImmediate(() => {
|
||||
this.engine.stop()
|
||||
})
|
||||
} catch (err) {
|
||||
logger.warn('[Motrix] shutdown engine fail: ', err.message)
|
||||
} finally {
|
||||
this.engine.stop()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,6 +141,18 @@ export default class Application extends EventEmitter {
|
||||
})
|
||||
}
|
||||
|
||||
initTrayManager () {
|
||||
this.trayManager = new TrayManager({
|
||||
theme: this.configManager.getUserConfig('tray-theme')
|
||||
})
|
||||
}
|
||||
|
||||
initDockManager () {
|
||||
this.dockManager = new DockManager({
|
||||
runMode: this.configManager.getUserConfig('run-mode')
|
||||
})
|
||||
}
|
||||
|
||||
initUPnPManager () {
|
||||
this.upnp = new UPnPManager()
|
||||
|
||||
@@ -196,7 +228,8 @@ export default class Application extends EventEmitter {
|
||||
if (newValue) {
|
||||
this.startUPnPMapping()
|
||||
} else {
|
||||
this.stopUPnPMapping()
|
||||
await this.stopUPnPMapping()
|
||||
this.upnp.closeClient()
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -207,13 +240,14 @@ export default class Application extends EventEmitter {
|
||||
await this.stopUPnPMapping()
|
||||
}
|
||||
|
||||
this.upnp.destroy()
|
||||
this.upnp.closeClient()
|
||||
}
|
||||
|
||||
autoSyncTracker () {
|
||||
const enable = this.configManager.getUserConfig('auto-sync-tracker')
|
||||
const lastTime = this.configManager.getUserConfig('last-sync-tracker-time')
|
||||
const result = checkIsNeedRun(enable, lastTime, AUTO_SYNC_TRACKER_INTERVAL)
|
||||
logger.info('[Motrix] auto sync tracker checkIsNeedRun:', result)
|
||||
if (!result) {
|
||||
return
|
||||
}
|
||||
@@ -221,6 +255,7 @@ export default class Application extends EventEmitter {
|
||||
setTimeout(() => {
|
||||
const source = this.configManager.getUserConfig('tracker-source')
|
||||
fetchBtTrackerFromSource(source).then((data) => {
|
||||
logger.warn('[Motrix] auto sync tracker data:', data)
|
||||
const tracker = convertTrackerDataToComma(data)
|
||||
this.savePreference({
|
||||
system: {
|
||||
@@ -230,8 +265,19 @@ export default class Application extends EventEmitter {
|
||||
'last-sync-tracker-time': Date.now()
|
||||
}
|
||||
})
|
||||
}).catch((err) => {
|
||||
logger.warn('[Motrix] auto sync tracker failed:', err.message)
|
||||
})
|
||||
}, 3000)
|
||||
}, 500)
|
||||
}
|
||||
|
||||
autoResumeTask () {
|
||||
const enabled = this.configManager.getUserConfig('resume-all-when-app-launched')
|
||||
if (!enabled) {
|
||||
return
|
||||
}
|
||||
|
||||
this.engineClient.call('unpauseAll')
|
||||
}
|
||||
|
||||
initWindowManager () {
|
||||
@@ -242,12 +288,25 @@ export default class Application extends EventEmitter {
|
||||
this.windowManager.on('window-resized', (data) => {
|
||||
this.storeWindowState(data)
|
||||
})
|
||||
|
||||
this.windowManager.on('window-moved', (data) => {
|
||||
this.storeWindowState(data)
|
||||
})
|
||||
|
||||
this.windowManager.on('window-closed', (data) => {
|
||||
this.storeWindowState(data)
|
||||
})
|
||||
|
||||
this.windowManager.on('enter-full-screen', (window) => {
|
||||
this.dockManager.show()
|
||||
})
|
||||
|
||||
this.windowManager.on('leave-full-screen', (window) => {
|
||||
const mode = this.configManager.getUserConfig('run-mode')
|
||||
if (mode !== APP_RUN_MODE.STANDARD) {
|
||||
this.dockManager.hide()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
storeWindowState (data = {}) {
|
||||
@@ -291,7 +350,7 @@ export default class Application extends EventEmitter {
|
||||
|
||||
hide (page) {
|
||||
if (page) {
|
||||
this.windowManager.autoHideWindow(page)
|
||||
this.windowManager.hideWindow(page)
|
||||
} else {
|
||||
this.windowManager.hideAllWindow()
|
||||
}
|
||||
@@ -311,9 +370,9 @@ export default class Application extends EventEmitter {
|
||||
|
||||
this.energyManager.stopPowerSaveBlocker()
|
||||
|
||||
this.trayManager.destroy()
|
||||
|
||||
await this.stopEngine()
|
||||
|
||||
this.trayManager.destroy()
|
||||
} catch (err) {
|
||||
logger.warn('[Motrix] stop error: ', err.message)
|
||||
}
|
||||
@@ -349,9 +408,9 @@ export default class Application extends EventEmitter {
|
||||
|
||||
initThemeManager () {
|
||||
this.themeManager = new ThemeManager()
|
||||
this.themeManager.on('system-theme-changed', (theme) => {
|
||||
this.themeManager.on('system-theme-change', (theme) => {
|
||||
this.trayManager.changeIconTheme(theme)
|
||||
this.sendCommandToAll('application:system-theme', theme)
|
||||
this.sendCommandToAll('application:update-system-theme', theme)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -359,6 +418,7 @@ export default class Application extends EventEmitter {
|
||||
if (!is.macOS()) {
|
||||
return
|
||||
}
|
||||
|
||||
this.touchBarManager = new TouchBarManager()
|
||||
}
|
||||
|
||||
@@ -366,6 +426,7 @@ export default class Application extends EventEmitter {
|
||||
if (is.dev() || is.mas()) {
|
||||
return
|
||||
}
|
||||
|
||||
const protocols = this.configManager.getUserConfig('protocols', {})
|
||||
this.protocolManager = new ProtocolManager({
|
||||
protocols
|
||||
@@ -515,14 +576,14 @@ export default class Application extends EventEmitter {
|
||||
|
||||
this.on('application:change-theme', (theme) => {
|
||||
this.themeManager.updateAppAppearance(theme)
|
||||
this.sendCommandToAll('application:theme', theme)
|
||||
this.sendCommandToAll('application:update-theme', theme)
|
||||
})
|
||||
|
||||
this.on('application:change-locale', (locale) => {
|
||||
this.localeManager.changeLanguageByLocale(locale)
|
||||
.then(() => {
|
||||
this.menuManager.setup(locale)
|
||||
this.trayManager.setup(locale)
|
||||
this.menuManager.handleLocaleChange(locale)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -601,10 +662,21 @@ export default class Application extends EventEmitter {
|
||||
})
|
||||
}
|
||||
|
||||
handleConfigChange (configName) {
|
||||
this.sendCommandToAll('application:update-preference-config', configName)
|
||||
}
|
||||
|
||||
handleEvents () {
|
||||
// this.configManager.systemConfig.onDidAnyChange(() => {
|
||||
// this.engineClient.changeGlobalOption(this.configManager.getSystemConfig())
|
||||
// })
|
||||
this.once('application:initialized', () => {
|
||||
this.autoSyncTracker()
|
||||
|
||||
this.autoResumeTask()
|
||||
|
||||
this.adjustMenu()
|
||||
})
|
||||
|
||||
this.configManager.userConfig.onDidAnyChange(() => this.handleConfigChange('user'))
|
||||
this.configManager.systemConfig.onDidAnyChange(() => this.handleConfigChange('system'))
|
||||
|
||||
this.on('download-status-change', (downloading) => {
|
||||
this.trayManager.updateTrayByStatus(downloading)
|
||||
|
||||
@@ -1,19 +1,18 @@
|
||||
import { app } from 'electron'
|
||||
|
||||
import { LOGIN_SETTING_OPTIONS } from '@shared/constants'
|
||||
|
||||
export default class AutoLaunchManager {
|
||||
enable () {
|
||||
return new Promise((resolve, reject) => {
|
||||
const enabled = app.getLoginItemSettings().openAtLogin
|
||||
const enabled = app.getLoginItemSettings(LOGIN_SETTING_OPTIONS).openAtLogin
|
||||
if (enabled) {
|
||||
resolve()
|
||||
}
|
||||
|
||||
app.setLoginItemSettings({
|
||||
openAtLogin: true,
|
||||
// For Windows
|
||||
args: [
|
||||
'--opened-at-login=1'
|
||||
]
|
||||
...LOGIN_SETTING_OPTIONS,
|
||||
openAtLogin: true
|
||||
})
|
||||
resolve()
|
||||
})
|
||||
@@ -28,7 +27,7 @@ export default class AutoLaunchManager {
|
||||
|
||||
isEnabled () {
|
||||
return new Promise((resolve, reject) => {
|
||||
const enabled = app.getLoginItemSettings().openAtLogin
|
||||
const enabled = app.getLoginItemSettings(LOGIN_SETTING_OPTIONS).openAtLogin
|
||||
resolve(enabled)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -13,9 +13,10 @@ import {
|
||||
APP_RUN_MODE,
|
||||
APP_THEME,
|
||||
EMPTY_STRING,
|
||||
IP_VERSION,
|
||||
LOGIN_SETTING_OPTIONS,
|
||||
NGOSANG_TRACKERS_BEST_IP_URL,
|
||||
NGOSANG_TRACKERS_BEST_URL,
|
||||
IP_VERSION
|
||||
NGOSANG_TRACKERS_BEST_URL
|
||||
} from '@shared/constants'
|
||||
import { separateConfig } from '@shared/utils'
|
||||
|
||||
@@ -49,6 +50,7 @@ export default class ConfigManager {
|
||||
'all-proxy': EMPTY_STRING,
|
||||
'allow-overwrite': false,
|
||||
'auto-file-renaming': true,
|
||||
'bt-exclude-tracker': EMPTY_STRING,
|
||||
'bt-tracker': EMPTY_STRING,
|
||||
'continue': true,
|
||||
'dht-file-path': getDhtPath(IP_VERSION.V4),
|
||||
@@ -68,7 +70,7 @@ export default class ConfigManager {
|
||||
'rpc-secret': EMPTY_STRING,
|
||||
'seed-ratio': 1,
|
||||
'seed-time': 60,
|
||||
'split': 128,
|
||||
'split': getMaxConnectionPerServer(),
|
||||
'user-agent': 'Transmission/2.94'
|
||||
}
|
||||
/* eslint-enable quote-props */
|
||||
@@ -102,6 +104,7 @@ export default class ConfigManager {
|
||||
'locale': app.getLocale(),
|
||||
'log-path': getLogPath(),
|
||||
'new-task-show-downloading': true,
|
||||
'no-confirm-before-delete-task': false,
|
||||
'open-at-login': false,
|
||||
'protocols': { 'magnet': true, 'thunder': false },
|
||||
'resume-all-when-app-launched': false,
|
||||
@@ -138,7 +141,7 @@ export default class ConfigManager {
|
||||
fixUserConfig () {
|
||||
// Fix the value of open-at-login when the user delete
|
||||
// the Motrix self-starting item through startup management.
|
||||
const openAtLogin = app.getLoginItemSettings().openAtLogin
|
||||
const openAtLogin = app.getLoginItemSettings(LOGIN_SETTING_OPTIONS).openAtLogin
|
||||
if (this.getUserConfig('open-at-login') !== openAtLogin) {
|
||||
this.setUserConfig('open-at-login', openAtLogin)
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ export default class Engine {
|
||||
const sh = this.getStartSh()
|
||||
logger.info('[Motrix] Engine start sh:', sh)
|
||||
this.instance = forever.start(sh, {
|
||||
max: is.dev() ? 1 : 100,
|
||||
max: is.dev() ? 0 : 100,
|
||||
parser: function (command, args) {
|
||||
return {
|
||||
command: command,
|
||||
@@ -118,6 +118,9 @@ export default class Engine {
|
||||
logger.error('[Motrix] Engine stop fail:', err.message)
|
||||
this.forceStop(pid)
|
||||
} finally {
|
||||
this.instance.removeAllListeners('start')
|
||||
this.instance.removeAllListeners('error')
|
||||
this.instance.removeAllListeners('stop')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -44,10 +44,13 @@ export default class ProtocolManager extends EventEmitter {
|
||||
logger.info(`[Motrix] protocol url: ${url}`)
|
||||
|
||||
if (
|
||||
url.toLowerCase().startsWith('ftp:') ||
|
||||
url.toLowerCase().startsWith('http:') ||
|
||||
url.toLowerCase().startsWith('https:') ||
|
||||
url.toLowerCase().startsWith('magnet:') ||
|
||||
url.toLowerCase().startsWith('thunder:')
|
||||
) {
|
||||
return this.handleMagnetAndThunderProtocol(url)
|
||||
return this.handleResourceProtocol(url)
|
||||
}
|
||||
|
||||
if (
|
||||
@@ -58,7 +61,7 @@ export default class ProtocolManager extends EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
handleMagnetAndThunderProtocol (url) {
|
||||
handleResourceProtocol (url) {
|
||||
if (!url) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import NatAPI from 'nat-api'
|
||||
import NatAPI from '@motrix/nat-api'
|
||||
|
||||
import logger from './Logger'
|
||||
|
||||
@@ -17,7 +17,9 @@ export default class UPnPManager {
|
||||
return
|
||||
}
|
||||
|
||||
client = new NatAPI()
|
||||
client = new NatAPI({
|
||||
autoUpdate: true
|
||||
})
|
||||
}
|
||||
|
||||
map (port) {
|
||||
@@ -30,17 +32,21 @@ export default class UPnPManager {
|
||||
return
|
||||
}
|
||||
|
||||
client.map(port, (err) => {
|
||||
if (err) {
|
||||
logger.warn(`[Motrix] UPnPManager map ${port} failed, error: `, err)
|
||||
reject(err.message)
|
||||
return
|
||||
}
|
||||
try {
|
||||
client.map(port, (err) => {
|
||||
if (err) {
|
||||
logger.warn(`[Motrix] UPnPManager map ${port} failed, error: `, err)
|
||||
reject(err.message)
|
||||
return
|
||||
}
|
||||
|
||||
mappingStatus[port] = true
|
||||
logger.info(`[Motrix] UPnPManager port ${port} mapping succeeded`)
|
||||
resolve()
|
||||
})
|
||||
mappingStatus[port] = true
|
||||
logger.info(`[Motrix] UPnPManager port ${port} mapping succeeded`)
|
||||
resolve()
|
||||
})
|
||||
} catch (err) {
|
||||
reject(err.message)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -59,26 +65,35 @@ export default class UPnPManager {
|
||||
return
|
||||
}
|
||||
|
||||
client.unmap(port, (err) => {
|
||||
if (err) {
|
||||
logger.warn(`[Motrix] UPnPManager unmap ${port} failed, error: `, err)
|
||||
reject(err.message)
|
||||
return
|
||||
}
|
||||
try {
|
||||
client.unmap(port, (err) => {
|
||||
if (err) {
|
||||
logger.warn(`[Motrix] UPnPManager unmap ${port} failed, error: `, err)
|
||||
reject(err.message)
|
||||
return
|
||||
}
|
||||
|
||||
logger.info(`[Motrix] UPnPManager port ${port} unmapping succeeded`)
|
||||
mappingStatus[port] = false
|
||||
resolve()
|
||||
})
|
||||
logger.info(`[Motrix] UPnPManager port ${port} unmapping succeeded`)
|
||||
mappingStatus[port] = false
|
||||
resolve()
|
||||
})
|
||||
} catch (err) {
|
||||
reject(err.message)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
destroy () {
|
||||
closeClient () {
|
||||
if (!client) {
|
||||
return
|
||||
}
|
||||
|
||||
client.destroy()
|
||||
client = null
|
||||
try {
|
||||
client.destroy(() => {
|
||||
client = null
|
||||
})
|
||||
} catch (err) {
|
||||
logger.warn('[Motrix] close UPnP client fail', err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ import is from 'electron-is'
|
||||
|
||||
import Launcher from './Launcher'
|
||||
|
||||
process.env.ELECTRON_DISABLE_SECURITY_WARNINGS = 'true'
|
||||
|
||||
if (process.env.NODE_ENV !== 'development') {
|
||||
global.__static = require('path').join(__dirname, '/static').replace(/\\/g, '\\\\')
|
||||
}
|
||||
|
||||
@@ -19,10 +19,18 @@ export default class DockManager extends EventEmitter {
|
||||
}
|
||||
|
||||
show = isMac ? () => {
|
||||
if (app.dock.isVisible()) {
|
||||
return
|
||||
}
|
||||
|
||||
return app.dock.show()
|
||||
} : () => {}
|
||||
|
||||
hide = isMac ? () => {
|
||||
if (!app.dock.isVisible()) {
|
||||
return
|
||||
}
|
||||
|
||||
app.dock.hide()
|
||||
} : () => {}
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ export default class MenuManager extends EventEmitter {
|
||||
this.items = flattenMenuItems(menu)
|
||||
}
|
||||
|
||||
rebuild () {
|
||||
handleLocaleChange (locale) {
|
||||
this.setup()
|
||||
}
|
||||
|
||||
|
||||
+15
-10
@@ -1,40 +1,45 @@
|
||||
import { EventEmitter } from 'events'
|
||||
import { nativeTheme, systemPreferences } from 'electron'
|
||||
|
||||
import is from 'electron-is'
|
||||
|
||||
import { APP_THEME } from '@shared/constants'
|
||||
import { getSystemTheme } from '../utils'
|
||||
|
||||
export default class ThemeManager extends EventEmitter {
|
||||
constructor (options = {}) {
|
||||
super()
|
||||
|
||||
this.options = options
|
||||
this.init()
|
||||
}
|
||||
|
||||
init () {
|
||||
this.systemTheme = getSystemTheme()
|
||||
|
||||
this.handleEvents()
|
||||
}
|
||||
|
||||
getSystemTheme () {
|
||||
let result = APP_THEME.LIGHT
|
||||
if (!is.macOS()) {
|
||||
return result
|
||||
}
|
||||
result = nativeTheme.shouldUseDarkColors ? APP_THEME.DARK : APP_THEME.LIGHT
|
||||
return result
|
||||
return this.systemTheme
|
||||
}
|
||||
|
||||
handleEvents () {
|
||||
if (!is.macOS()) {
|
||||
return
|
||||
}
|
||||
|
||||
nativeTheme.on('updated', () => {
|
||||
const theme = this.getSystemTheme()
|
||||
this.updateAppAppearance(theme)
|
||||
this.emit('system-theme-changed', theme)
|
||||
const theme = getSystemTheme()
|
||||
this.systemTheme = theme
|
||||
console.log('nativeTheme updated===>', theme)
|
||||
this.emit('system-theme-change', theme)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* deprecated
|
||||
* @see https://www.electronjs.org/docs/all#systempreferencessetapplevelappearanceappearance-macos-deprecated
|
||||
*/
|
||||
updateAppAppearance (theme) {
|
||||
if (!is.macOS() || theme !== APP_THEME.LIGHT || theme !== APP_THEME.DARK) {
|
||||
return
|
||||
|
||||
@@ -64,13 +64,7 @@ export default class TrayManager extends EventEmitter {
|
||||
setup () {
|
||||
this.build()
|
||||
|
||||
/**
|
||||
* Linux requires setContextMenu to be called
|
||||
* in order for the context menu to populate correctly
|
||||
*/
|
||||
if (process.platform === 'linux') {
|
||||
tray.setContextMenu(this.menu)
|
||||
}
|
||||
this.updateContextMenu()
|
||||
}
|
||||
|
||||
init () {
|
||||
@@ -79,10 +73,14 @@ export default class TrayManager extends EventEmitter {
|
||||
}
|
||||
|
||||
handleEvents () {
|
||||
// All OS
|
||||
tray.on('click', this.handleTrayClick)
|
||||
|
||||
// macOS, Windows
|
||||
tray.on('double-click', this.handleTrayDbClick)
|
||||
tray.on('right-click', this.handleTrayRightClick)
|
||||
|
||||
// macOS only
|
||||
tray.on('drop-files', this.handleTrayDropFile)
|
||||
}
|
||||
|
||||
@@ -114,6 +112,8 @@ export default class TrayManager extends EventEmitter {
|
||||
updateTray () {
|
||||
const icon = this.status ? this.activeIcon : this.normalIcon
|
||||
tray.setImage(icon)
|
||||
|
||||
this.updateContextMenu()
|
||||
}
|
||||
|
||||
changeIconTheme (theme = APP_THEME.LIGHT) {
|
||||
@@ -128,6 +128,8 @@ export default class TrayManager extends EventEmitter {
|
||||
|
||||
updateMenuStates (visibleStates, enabledStates, checkedStates) {
|
||||
updateStates(this.items, visibleStates, enabledStates, checkedStates)
|
||||
|
||||
this.updateContextMenu()
|
||||
}
|
||||
|
||||
updateMenuItemVisibleState (id, flag) {
|
||||
@@ -144,7 +146,26 @@ export default class TrayManager extends EventEmitter {
|
||||
this.updateMenuStates(null, enabledStates, null)
|
||||
}
|
||||
|
||||
updateContextMenu () {
|
||||
/**
|
||||
* Linux requires setContextMenu to be called
|
||||
* in order for the context menu to populate correctly
|
||||
*/
|
||||
if (process.platform !== 'linux') {
|
||||
return
|
||||
}
|
||||
|
||||
tray.setContextMenu(this.menu)
|
||||
}
|
||||
|
||||
destroy () {
|
||||
if (tray) {
|
||||
tray.removeListener('click', this.handleTrayClick)
|
||||
tray.removeListener('double-click', this.handleTrayDbClick)
|
||||
tray.removeListener('right-click', this.handleTrayRightClick)
|
||||
tray.removeListener('drop-files', this.handleTrayDropFile)
|
||||
}
|
||||
|
||||
tray.destroy()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,6 +105,14 @@ export default class WindowManager extends EventEmitter {
|
||||
}
|
||||
})
|
||||
|
||||
window.on('enter-full-screen', () => {
|
||||
this.emit('enter-full-screen', window)
|
||||
})
|
||||
|
||||
window.on('leave-full-screen', () => {
|
||||
this.emit('leave-full-screen', window)
|
||||
})
|
||||
|
||||
this.handleWindowState(page, window)
|
||||
|
||||
this.handleWindowClose(pageOptions, page, window)
|
||||
@@ -169,7 +177,15 @@ export default class WindowManager extends EventEmitter {
|
||||
window.on('close', (event) => {
|
||||
if (pageOptions.bindCloseToHide && !this.willQuit) {
|
||||
event.preventDefault()
|
||||
window.hide()
|
||||
|
||||
// @see https://github.com/electron/electron/issues/20263
|
||||
if (window.isFullScreen()) {
|
||||
window.once('leave-full-screen', () => window.hide())
|
||||
|
||||
window.setFullScreen(false)
|
||||
} else {
|
||||
window.hide()
|
||||
}
|
||||
}
|
||||
const bounds = window.getBounds()
|
||||
this.emit('window-closed', { page, bounds })
|
||||
@@ -178,15 +194,16 @@ export default class WindowManager extends EventEmitter {
|
||||
|
||||
showWindow (page) {
|
||||
const window = this.getWindow(page)
|
||||
if (!window) {
|
||||
if (!window || window.isVisible()) {
|
||||
return
|
||||
}
|
||||
|
||||
window.show()
|
||||
}
|
||||
|
||||
autoHideWindow (page) {
|
||||
hideWindow (page) {
|
||||
const window = this.getWindow(page)
|
||||
if (!window) {
|
||||
if (!window || !window.isVisible()) {
|
||||
return
|
||||
}
|
||||
window.hide()
|
||||
@@ -203,10 +220,11 @@ export default class WindowManager extends EventEmitter {
|
||||
if (!window) {
|
||||
return
|
||||
}
|
||||
if (window.isVisible()) {
|
||||
window.hide()
|
||||
} else {
|
||||
|
||||
if (!window.isVisible() || window.isFullScreen()) {
|
||||
window.show()
|
||||
} else {
|
||||
window.hide()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+13
-5
@@ -1,12 +1,14 @@
|
||||
import { app } from 'electron'
|
||||
import { app, nativeTheme } from 'electron'
|
||||
import is from 'electron-is'
|
||||
import { resolve } from 'path'
|
||||
import { existsSync, lstatSync } from 'fs'
|
||||
|
||||
import {
|
||||
APP_THEME,
|
||||
ENGINE_MAX_CONNECTION_PER_SERVER,
|
||||
IP_VERSION
|
||||
} from '@shared/constants'
|
||||
|
||||
import logger from '../core/Logger'
|
||||
import engineBinMap from '../configs/engine'
|
||||
|
||||
@@ -100,13 +102,13 @@ export function parseArgvAsUrl (argv) {
|
||||
export function checkIsSupportedSchema (url = '') {
|
||||
const str = url.toLowerCase()
|
||||
if (
|
||||
str.startsWith('mo:') ||
|
||||
str.startsWith('motrix:') ||
|
||||
str.startsWith('ftp:') ||
|
||||
str.startsWith('http:') ||
|
||||
str.startsWith('https:') ||
|
||||
str.startsWith('ftp:') ||
|
||||
str.startsWith('magnet:') ||
|
||||
str.startsWith('thunder:')
|
||||
str.startsWith('thunder:') ||
|
||||
str.startsWith('mo:') ||
|
||||
str.startsWith('motrix:')
|
||||
) {
|
||||
return true
|
||||
} else {
|
||||
@@ -133,3 +135,9 @@ export function parseArgvAsFile (argv) {
|
||||
export const getMaxConnectionPerServer = () => {
|
||||
return ENGINE_MAX_CONNECTION_PER_SERVER
|
||||
}
|
||||
|
||||
export const getSystemTheme = () => {
|
||||
let result = APP_THEME.LIGHT
|
||||
result = nativeTheme.shouldUseDarkColors ? APP_THEME.DARK : APP_THEME.LIGHT
|
||||
return result
|
||||
}
|
||||
|
||||
+20
-21
@@ -1,6 +1,6 @@
|
||||
import { ipcRenderer, remote } from 'electron'
|
||||
import is from 'electron-is'
|
||||
import { isEmpty } from 'lodash'
|
||||
import { isEmpty, clone } from 'lodash'
|
||||
import Aria2 from 'aria2'
|
||||
import {
|
||||
separateConfig,
|
||||
@@ -18,13 +18,14 @@ export default class Api {
|
||||
constructor (options = {}) {
|
||||
this.options = options
|
||||
|
||||
this.client = null
|
||||
this.init()
|
||||
}
|
||||
|
||||
init () {
|
||||
this.loadConfig()
|
||||
this.initClient()
|
||||
this.config = this.loadConfig()
|
||||
|
||||
this.client = this.initClient()
|
||||
this.client.open()
|
||||
}
|
||||
|
||||
loadConfigFromLocalStorage () {
|
||||
@@ -47,7 +48,7 @@ export default class Api {
|
||||
: this.loadConfigFromLocalStorage()
|
||||
|
||||
result = changeKeysToCamelCase(result)
|
||||
this.config = result
|
||||
return result
|
||||
}
|
||||
|
||||
initClient () {
|
||||
@@ -56,12 +57,11 @@ export default class Api {
|
||||
rpcSecret: secret
|
||||
} = this.config
|
||||
const host = ENGINE_RPC_HOST
|
||||
this.client = new Aria2({
|
||||
return new Aria2({
|
||||
host,
|
||||
port,
|
||||
secret
|
||||
})
|
||||
this.client.open()
|
||||
}
|
||||
|
||||
closeClient () {
|
||||
@@ -76,7 +76,7 @@ export default class Api {
|
||||
|
||||
fetchPreference () {
|
||||
return new Promise((resolve) => {
|
||||
this.loadConfig()
|
||||
this.config = this.loadConfig()
|
||||
resolve(this.config)
|
||||
})
|
||||
}
|
||||
@@ -160,11 +160,10 @@ export default class Api {
|
||||
}
|
||||
|
||||
changeOption (params = {}) {
|
||||
let { gid, options = {} } = params
|
||||
options = formatOptionsForEngine(options)
|
||||
const { gid, options = {} } = params
|
||||
|
||||
const kebabOptions = changeKeysToKebabCase(options)
|
||||
const args = compactUndefined([gid, kebabOptions])
|
||||
const engineOptions = formatOptionsForEngine(options)
|
||||
const args = compactUndefined([gid, engineOptions])
|
||||
|
||||
return this.client.call('changeOption', ...args)
|
||||
}
|
||||
@@ -180,11 +179,11 @@ export default class Api {
|
||||
options
|
||||
} = params
|
||||
const tasks = uris.map((uri, index) => {
|
||||
const kebabOptions = changeKeysToKebabCase(options)
|
||||
const engineOptions = formatOptionsForEngine(options)
|
||||
if (outs && outs[index]) {
|
||||
kebabOptions.out = outs[index]
|
||||
engineOptions.out = outs[index]
|
||||
}
|
||||
const args = compactUndefined([[uri], kebabOptions])
|
||||
const args = compactUndefined([[uri], engineOptions])
|
||||
return ['aria2.addUri', ...args]
|
||||
})
|
||||
return this.client.multicall(tasks)
|
||||
@@ -195,8 +194,8 @@ export default class Api {
|
||||
torrent,
|
||||
options
|
||||
} = params
|
||||
const kebabOptions = changeKeysToKebabCase(options)
|
||||
const args = compactUndefined([torrent, [], kebabOptions])
|
||||
const engineOptions = formatOptionsForEngine(options)
|
||||
const args = compactUndefined([torrent, [], engineOptions])
|
||||
return this.client.call('addTorrent', ...args)
|
||||
}
|
||||
|
||||
@@ -205,8 +204,8 @@ export default class Api {
|
||||
metalink,
|
||||
options
|
||||
} = params
|
||||
const kebabOptions = changeKeysToKebabCase(options)
|
||||
const args = compactUndefined([metalink, kebabOptions])
|
||||
const engineOptions = formatOptionsForEngine(options)
|
||||
const args = compactUndefined([metalink, engineOptions])
|
||||
return this.client.call('addMetalink', ...args)
|
||||
}
|
||||
|
||||
@@ -328,8 +327,8 @@ export default class Api {
|
||||
options = formatOptionsForEngine(options)
|
||||
|
||||
const data = gids.map((gid, index) => {
|
||||
const kebabOptions = changeKeysToKebabCase(options)
|
||||
const args = compactUndefined([gid, kebabOptions])
|
||||
const _options = clone(options)
|
||||
const args = compactUndefined([gid, _options])
|
||||
return [method, ...args]
|
||||
})
|
||||
return this.client.multicall(data)
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
...mapState('app', {
|
||||
currentPage: state => state.currentPage
|
||||
}),
|
||||
asideDraggable: function () {
|
||||
asideDraggable () {
|
||||
return is.macOS()
|
||||
}
|
||||
},
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
import {
|
||||
openExternal
|
||||
} from '@/components/Native/utils'
|
||||
} from '@/utils/native'
|
||||
|
||||
export default {
|
||||
name: 'mo-browser',
|
||||
|
||||
+10
-2
@@ -9,11 +9,11 @@ export default class CommandManager extends EventEmitter {
|
||||
|
||||
register (id, fn) {
|
||||
if (this.commands[id]) {
|
||||
console.log('Attempting to register an already-registered command: ' + id)
|
||||
console.log('[Motrix] Attempting to register an already-registered command: ' + id)
|
||||
return null
|
||||
}
|
||||
if (!id || !fn) {
|
||||
console.error('Attempting to register a command with a missing id, or command function.')
|
||||
console.error('[Motrix] Attempting to register a command with a missing id, or command function.')
|
||||
return null
|
||||
}
|
||||
this.commands[id] = fn
|
||||
@@ -21,6 +21,14 @@ export default class CommandManager extends EventEmitter {
|
||||
this.emit('commandRegistered', id)
|
||||
}
|
||||
|
||||
unregister (id) {
|
||||
if (this.commands[id]) {
|
||||
delete this.commands[id]
|
||||
|
||||
this.emit('commandUnregistered', id)
|
||||
}
|
||||
}
|
||||
|
||||
execute (id, ...args) {
|
||||
var fn = this.commands[id]
|
||||
if (fn) {
|
||||
@@ -0,0 +1,3 @@
|
||||
import CommandManager from '.'
|
||||
|
||||
export const commands = new CommandManager()
|
||||
@@ -37,6 +37,7 @@
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
outline: none;
|
||||
text-align: center;
|
||||
font-size: 0;
|
||||
color: $--app-logo-color;
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
import {
|
||||
showItemInFolder,
|
||||
addToRecentTask
|
||||
} from '@/components/Native/utils'
|
||||
} from '@/utils/native'
|
||||
import {
|
||||
bytesToSize,
|
||||
getTaskName,
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
export default {
|
||||
name: 'mo-engine-client',
|
||||
data: function () {
|
||||
data () {
|
||||
return {
|
||||
downloading: false
|
||||
}
|
||||
@@ -39,11 +39,11 @@
|
||||
})
|
||||
},
|
||||
watch: {
|
||||
downloadSpeed (val, oldVal) {
|
||||
downloadSpeed (val) {
|
||||
const speed = val > 0 ? `${bytesToSize(val)}/s` : ''
|
||||
this.$electron.ipcRenderer.send('event', 'download-speed-change', speed)
|
||||
},
|
||||
numActive (val, oldVal) {
|
||||
numActive (val) {
|
||||
this.downloading = val > 0
|
||||
},
|
||||
downloading (val, oldVal) {
|
||||
@@ -62,6 +62,7 @@
|
||||
onDownloadStart (event) {
|
||||
this.$store.dispatch('task/fetchList')
|
||||
this.$store.dispatch('app/resetInterval')
|
||||
this.$store.dispatch('task/saveSession')
|
||||
console.log('aria2 onDownloadStart', event)
|
||||
const [{ gid }] = event
|
||||
this.fetchTaskItem({ gid })
|
||||
@@ -128,12 +129,12 @@
|
||||
})
|
||||
},
|
||||
handleDownloadComplete (task, isBT) {
|
||||
const path = getTaskFullPath(task)
|
||||
|
||||
this.showTaskCompleteNotify(task, isBT, path)
|
||||
this.$store.dispatch('task/saveSession')
|
||||
|
||||
addToRecentTask(task)
|
||||
|
||||
const path = getTaskFullPath(task)
|
||||
this.showTaskCompleteNotify(task, isBT, path)
|
||||
this.$electron.ipcRenderer.send('event', 'task-download-complete', task, path)
|
||||
},
|
||||
showTaskCompleteNotify (task, isBT, path) {
|
||||
|
||||
@@ -3,44 +3,24 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import is from 'electron-is'
|
||||
import { mapState } from 'vuex'
|
||||
import {
|
||||
commands
|
||||
} from '@/components/Command/index'
|
||||
import { commands } from '@/components/CommandManager/instance'
|
||||
|
||||
export default {
|
||||
name: 'mo-ipc',
|
||||
computed: {
|
||||
...mapState('preference', {
|
||||
enableEggFeatures: state => state.config.enableEggFeatures
|
||||
})
|
||||
},
|
||||
watch: {
|
||||
},
|
||||
methods: {
|
||||
bindIpcEvents: function () {
|
||||
bindIpcEvents () {
|
||||
this.$electron.ipcRenderer.on('command', (event, command, ...args) => {
|
||||
commands.execute(command, ...args)
|
||||
})
|
||||
},
|
||||
unbindIpcEvents: function () {
|
||||
unbindIpcEvents () {
|
||||
this.$electron.ipcRenderer.removeAllListeners('command')
|
||||
}
|
||||
},
|
||||
created: function () {
|
||||
created () {
|
||||
this.bindIpcEvents()
|
||||
// id of the menu item
|
||||
const visibleStates = {}
|
||||
if (is.mas()) {
|
||||
visibleStates['app.check-for-updates'] = false
|
||||
if (!this.enableEggFeatures) {
|
||||
visibleStates['task.new-bt-task'] = false
|
||||
}
|
||||
}
|
||||
this.$electron.ipcRenderer.send('command', 'application:change-menu-states', visibleStates, null, null)
|
||||
},
|
||||
destroyed: function () {
|
||||
destroyed () {
|
||||
this.unbindIpcEvents()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
props: {
|
||||
},
|
||||
methods: {
|
||||
onFolderClick: function () {
|
||||
onFolderClick () {
|
||||
const self = this
|
||||
this.$electron.remote.dialog.showOpenDialog({
|
||||
properties: ['openDirectory', 'createDirectory']
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
import '@/components/Icons/folder'
|
||||
import {
|
||||
showItemInFolder
|
||||
} from '@/components/Native/utils'
|
||||
} from '@/utils/native'
|
||||
|
||||
export default {
|
||||
name: 'mo-show-in-folder',
|
||||
@@ -20,7 +20,7 @@
|
||||
computed: {
|
||||
},
|
||||
methods: {
|
||||
onFolderClick: function () {
|
||||
onFolderClick () {
|
||||
if (!this.path) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -28,22 +28,22 @@
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
win: function () {
|
||||
win () {
|
||||
return this.$electron.remote.getCurrentWindow()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleMinimize: function () {
|
||||
handleMinimize () {
|
||||
this.win.minimize()
|
||||
},
|
||||
handleMaximize: function () {
|
||||
handleMaximize () {
|
||||
if (this.win.isMaximized()) {
|
||||
this.win.unmaximize()
|
||||
} else {
|
||||
this.win.maximize()
|
||||
}
|
||||
},
|
||||
handleClose: function () {
|
||||
handleClose () {
|
||||
this.win.close()
|
||||
}
|
||||
}
|
||||
@@ -73,9 +73,11 @@
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
z-index: 5100;
|
||||
font-size: 0;
|
||||
> li {
|
||||
display: inline-block;
|
||||
padding: 5px 15px;
|
||||
padding: 5px 18px;
|
||||
font-size: 16px;
|
||||
margin: 0;
|
||||
color: $--titlebar-actions-color;
|
||||
&:hover {
|
||||
|
||||
@@ -94,6 +94,8 @@
|
||||
<el-select
|
||||
class="select-track-source"
|
||||
v-model="form.trackerSource"
|
||||
allow-create
|
||||
filterable
|
||||
multiple
|
||||
>
|
||||
<el-option-group
|
||||
@@ -169,10 +171,10 @@
|
||||
:label-width="formLabelWidth"
|
||||
>
|
||||
<el-row style="margin-bottom: 8px;">
|
||||
<el-col class="form-item-sub" :span="10">
|
||||
<el-col class="form-item-sub" :span="12">
|
||||
<el-switch
|
||||
v-model="form.enableUpnp"
|
||||
active-text="UPnP"
|
||||
active-text="UPnP/NAT-PMP"
|
||||
>
|
||||
</el-switch>
|
||||
</el-col>
|
||||
@@ -407,11 +409,11 @@
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isRenderer () { return is.renderer() },
|
||||
isRenderer: () => is.renderer(),
|
||||
title () {
|
||||
return this.$t('preferences.advanced')
|
||||
},
|
||||
subnavs: function () {
|
||||
subnavs () {
|
||||
return [
|
||||
{
|
||||
key: 'basic',
|
||||
@@ -463,8 +465,9 @@
|
||||
const tracker = convertTrackerDataToLine(data)
|
||||
this.form.lastSyncTrackerTime = Date.now()
|
||||
this.form.btTracker = tracker
|
||||
this.trackerSyncing = false
|
||||
})
|
||||
.finally(() => {
|
||||
.catch((_) => {
|
||||
this.trackerSyncing = false
|
||||
})
|
||||
},
|
||||
@@ -589,6 +592,9 @@
|
||||
.select-track-source {
|
||||
width: 100%;
|
||||
}
|
||||
.el-select__tags {
|
||||
overflow-x: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
.ua-group {
|
||||
|
||||
@@ -176,6 +176,11 @@
|
||||
{{ $t('preferences.task-completed-notify') }}
|
||||
</el-checkbox>
|
||||
</el-col>
|
||||
<el-col class="form-item-sub" :span="24">
|
||||
<el-checkbox v-model="form.noConfirmBeforeDeleteTask">
|
||||
{{ $t('preferences.no-confirm-before-delete-task') }}
|
||||
</el-checkbox>
|
||||
</el-col>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div class="form-actions">
|
||||
@@ -204,7 +209,7 @@
|
||||
import ThemeSwitcher from '@/components/Preference/ThemeSwitcher'
|
||||
import { availableLanguages, getLanguage } from '@shared/locales'
|
||||
import { getLocaleManager } from '@/components/Locale'
|
||||
import { prettifyDir } from '@/components/Native/utils'
|
||||
import { prettifyDir } from '@/utils/native'
|
||||
import {
|
||||
calcFormLabelWidth,
|
||||
checkIsNeedRestart,
|
||||
@@ -225,6 +230,7 @@
|
||||
maxOverallDownloadLimit,
|
||||
maxOverallUploadLimit,
|
||||
newTaskShowDownloading,
|
||||
noConfirmBeforeDeleteTask,
|
||||
openAtLogin,
|
||||
resumeAllWhenAppLaunched,
|
||||
runMode,
|
||||
@@ -244,6 +250,7 @@
|
||||
maxOverallDownloadLimit,
|
||||
maxOverallUploadLimit,
|
||||
newTaskShowDownloading,
|
||||
noConfirmBeforeDeleteTask,
|
||||
openAtLogin,
|
||||
resumeAllWhenAppLaunched,
|
||||
runMode,
|
||||
@@ -274,8 +281,8 @@
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isRenderer () { return is.renderer() },
|
||||
isMas () { return is.mas() },
|
||||
isRenderer: () => is.renderer(),
|
||||
isMas: () => is.mas(),
|
||||
isLinux () { return is.linux() },
|
||||
title () {
|
||||
return this.$t('preferences.basic')
|
||||
@@ -324,7 +331,7 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
subnavs: function () {
|
||||
subnavs () {
|
||||
return [
|
||||
{
|
||||
key: 'basic',
|
||||
@@ -362,6 +369,7 @@
|
||||
},
|
||||
handleThemeChange (theme) {
|
||||
this.form.theme = theme
|
||||
// this.$store.dispatch('preference/changeThemeConfig', theme)
|
||||
this.$electron.ipcRenderer.send('command',
|
||||
'application:change-theme', theme)
|
||||
},
|
||||
@@ -403,11 +411,15 @@
|
||||
this.$electron.ipcRenderer.send('command',
|
||||
'application:open-at-login', openAtLogin)
|
||||
|
||||
this.$electron.ipcRenderer.send('command',
|
||||
'application:toggle-dock', runMode === APP_RUN_MODE.STANDARD)
|
||||
if ('runMode' in changed) {
|
||||
this.$electron.ipcRenderer.send('command',
|
||||
'application:toggle-dock', runMode === APP_RUN_MODE.STANDARD)
|
||||
}
|
||||
|
||||
this.$electron.ipcRenderer.send('command',
|
||||
'application:auto-hide-window', autoHideWindow)
|
||||
if ('autoHideWindow' in changed) {
|
||||
this.$electron.ipcRenderer.send('command',
|
||||
'application:auto-hide-window', autoHideWindow)
|
||||
}
|
||||
|
||||
if (checkIsNeedRestart(data)) {
|
||||
this.$electron.ipcRenderer.send('command',
|
||||
|
||||
@@ -10,11 +10,8 @@
|
||||
<script>
|
||||
export default {
|
||||
name: 'mo-content-preference',
|
||||
computed: {
|
||||
},
|
||||
components: {
|
||||
},
|
||||
methods: {
|
||||
created () {
|
||||
this.$store.dispatch('preference/fetchPreference')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
title () {
|
||||
return this.$t('preferences.lab')
|
||||
},
|
||||
subnavs: function () {
|
||||
subnavs () {
|
||||
return [
|
||||
{
|
||||
key: 'basic',
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
default: APP_THEME.AUTO
|
||||
}
|
||||
},
|
||||
data: function () {
|
||||
data () {
|
||||
return {
|
||||
currentValue: this.value
|
||||
}
|
||||
@@ -54,7 +54,7 @@
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
currentValue: function (val) {
|
||||
currentValue (val) {
|
||||
this.$emit('change', val)
|
||||
}
|
||||
},
|
||||
|
||||
@@ -47,12 +47,12 @@
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
title: function () {
|
||||
title () {
|
||||
return this.$t('subnav.preferences')
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
nav: function (category = 'basic') {
|
||||
nav (category = 'basic') {
|
||||
this.$router.push({
|
||||
path: `/preference/${category}`
|
||||
}).catch(err => {
|
||||
|
||||
@@ -47,12 +47,12 @@
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
title: function () {
|
||||
title () {
|
||||
return this.$t('subnav.task-list')
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
nav: function (status = 'active') {
|
||||
nav (status = 'active') {
|
||||
this.$router.push({
|
||||
path: `/task/${status}`
|
||||
}).catch(err => {
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
:label-width="formLabelWidth"
|
||||
>
|
||||
<el-input-number
|
||||
v-model="form.maxConnectionPerServer"
|
||||
v-model="form.split"
|
||||
controls-position="right"
|
||||
:min="1"
|
||||
:max="config.engineMaxConnectionPerServer"
|
||||
@@ -172,7 +172,7 @@
|
||||
import { isEmpty } from 'lodash'
|
||||
import SelectDirectory from '@/components/Native/SelectDirectory'
|
||||
import SelectTorrent from '@/components/Task/SelectTorrent'
|
||||
import { prettifyDir } from '@/components/Native/utils'
|
||||
import { prettifyDir } from '@/utils/native'
|
||||
import { ADD_TASK_TYPE, NONE_SELECTED_FILES, SELECTED_ALL_FILES } from '@shared/constants'
|
||||
import { detectResource, splitTaskLinks } from '@shared/utils'
|
||||
import { buildOuts } from '@shared/utils/rename'
|
||||
@@ -185,7 +185,8 @@
|
||||
dir,
|
||||
engineMaxConnectionPerServer,
|
||||
maxConnectionPerServer,
|
||||
newTaskShowDownloading
|
||||
newTaskShowDownloading,
|
||||
split
|
||||
} = state.preference.config
|
||||
const result = {
|
||||
allProxy,
|
||||
@@ -197,6 +198,7 @@
|
||||
out: '',
|
||||
referer: '',
|
||||
selectFile: NONE_SELECTED_FILES,
|
||||
split,
|
||||
torrent: '',
|
||||
uris: addTaskUrl,
|
||||
userAgent: '',
|
||||
@@ -230,23 +232,23 @@
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isRenderer () { return is.renderer() },
|
||||
isMas () { return is.mas() },
|
||||
taskType: function () {
|
||||
return this.type
|
||||
},
|
||||
downloadDir: function () {
|
||||
return prettifyDir(this.form.dir)
|
||||
},
|
||||
isRenderer: () => is.renderer(),
|
||||
isMas: () => is.mas(),
|
||||
...mapState('app', {
|
||||
taskList: state => state.taskList
|
||||
}),
|
||||
...mapState('preference', {
|
||||
config: state => state.config
|
||||
})
|
||||
}),
|
||||
taskType () {
|
||||
return this.type
|
||||
},
|
||||
downloadDir () {
|
||||
return prettifyDir(this.form.dir)
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
taskType: function (current, previous) {
|
||||
taskType (current, previous) {
|
||||
if (this.visible && previous === ADD_TASK_TYPE.URI) {
|
||||
return
|
||||
}
|
||||
@@ -256,18 +258,16 @@
|
||||
this.$refs.uri && this.$refs.uri.focus()
|
||||
}, 50)
|
||||
}
|
||||
},
|
||||
visible (current) {
|
||||
if (current === true) {
|
||||
document.addEventListener('keydown', this.handleHotkey)
|
||||
} else {
|
||||
document.removeEventListener('keydown', this.handleHotkey)
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleOpen () {
|
||||
this.form = initialForm(this.$store.state)
|
||||
if (this.taskType === ADD_TASK_TYPE.URI) {
|
||||
this.autofillResourceLink()
|
||||
setTimeout(() => {
|
||||
this.$refs.uri && this.$refs.uri.focus()
|
||||
}, 50)
|
||||
}
|
||||
},
|
||||
autofillResourceLink () {
|
||||
const content = this.$electron.clipboard.readText()
|
||||
const hasResource = detectResource(content)
|
||||
@@ -278,9 +278,21 @@
|
||||
this.form.uris = content
|
||||
}
|
||||
},
|
||||
handleOpen () {
|
||||
this.form = initialForm(this.$store.state)
|
||||
if (this.taskType === ADD_TASK_TYPE.URI) {
|
||||
this.autofillResourceLink()
|
||||
setTimeout(() => {
|
||||
this.$refs.uri && this.$refs.uri.focus()
|
||||
}, 50)
|
||||
}
|
||||
},
|
||||
handleOpened () {
|
||||
this.detectThunderResource(this.form.uris)
|
||||
},
|
||||
handleCancel (formName) {
|
||||
this.$store.dispatch('app/hideAddTaskDialog')
|
||||
},
|
||||
handleClose (done) {
|
||||
this.$store.dispatch('app/hideAddTaskDialog')
|
||||
this.$store.dispatch('app/updateAddTaskOptions', {})
|
||||
@@ -288,6 +300,13 @@
|
||||
handleClosed () {
|
||||
this.reset()
|
||||
},
|
||||
handleHotkey (event) {
|
||||
if (event.key === 'Enter' && (event.ctrlKey || event.metaKey)) {
|
||||
event.preventDefault()
|
||||
|
||||
this.submitForm('taskForm')
|
||||
}
|
||||
},
|
||||
handleTabClick (tab, event) {
|
||||
this.$store.dispatch('app/changeAddTaskType', tab.name)
|
||||
},
|
||||
@@ -317,9 +336,6 @@
|
||||
this.showAdvanced = false
|
||||
this.form = initialForm(this.$store.state)
|
||||
},
|
||||
handleCancel (formName) {
|
||||
this.$store.dispatch('app/hideAddTaskDialog')
|
||||
},
|
||||
buildHeader (form) {
|
||||
const { userAgent, referer, cookie } = form
|
||||
const result = []
|
||||
@@ -336,7 +352,13 @@
|
||||
return result
|
||||
},
|
||||
buildOption (type, form) {
|
||||
const { allProxy, dir, out, selectFile } = form
|
||||
const {
|
||||
allProxy,
|
||||
dir,
|
||||
out,
|
||||
selectFile,
|
||||
split
|
||||
} = form
|
||||
const result = {}
|
||||
|
||||
if (!isEmpty(allProxy)) {
|
||||
@@ -351,6 +373,10 @@
|
||||
result.out = out
|
||||
}
|
||||
|
||||
if (split > 0) {
|
||||
result.split = split
|
||||
}
|
||||
|
||||
if (type === ADD_TASK_TYPE.TORRENT) {
|
||||
if (
|
||||
selectFile !== SELECTED_ALL_FILES &&
|
||||
|
||||
@@ -33,10 +33,24 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
import * as clipboard from 'clipboard-polyfill'
|
||||
|
||||
import { commands } from '@/components/CommandManager/instance'
|
||||
import { ADD_TASK_TYPE } from '@shared/constants'
|
||||
import TaskSubnav from '@/components/Subnav/TaskSubnav'
|
||||
import TaskActions from '@/components/Task/TaskActions'
|
||||
import TaskList from '@/components/Task/TaskList'
|
||||
import SubnavSwitcher from '@/components/Subnav/SubnavSwitcher'
|
||||
import {
|
||||
getTaskUri,
|
||||
parseHeader
|
||||
} from '@shared/utils'
|
||||
import {
|
||||
delayDeleteTaskFiles,
|
||||
showItemInFolder,
|
||||
moveTaskFilesToTrash
|
||||
} from '@/utils/native'
|
||||
|
||||
export default {
|
||||
name: 'mo-content-task',
|
||||
@@ -53,7 +67,15 @@
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
subnavs: function () {
|
||||
...mapState('task', {
|
||||
taskList: state => state.taskList,
|
||||
selectedGidList: state => state.selectedGidList,
|
||||
selectedGidListCount: state => state.selectedGidList.length
|
||||
}),
|
||||
...mapState('preference', {
|
||||
noConfirmBeforeDelete: state => state.config.noConfirmBeforeDeleteTask
|
||||
}),
|
||||
subnavs () {
|
||||
return [
|
||||
{
|
||||
key: 'active',
|
||||
@@ -72,7 +94,7 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
title: function () {
|
||||
title () {
|
||||
const subnav = this.subnavs.find((item) => item.key === this.status)
|
||||
return subnav.title
|
||||
}
|
||||
@@ -86,10 +108,302 @@
|
||||
},
|
||||
changeCurrentList () {
|
||||
this.$store.dispatch('task/changeCurrentList', this.status)
|
||||
},
|
||||
directAddTask (uri, options = {}) {
|
||||
const uris = [uri]
|
||||
const payload = {
|
||||
uris,
|
||||
options: {
|
||||
...options
|
||||
}
|
||||
}
|
||||
this.$store.dispatch('task/addUri', payload)
|
||||
.catch((err) => {
|
||||
this.$msg.error(err.message)
|
||||
})
|
||||
},
|
||||
showAddTaskDialog (uri, options = {}) {
|
||||
const {
|
||||
header,
|
||||
...rest
|
||||
} = options
|
||||
console.log('[Motrix] show add task dialog options: ', options)
|
||||
|
||||
const headers = parseHeader(header)
|
||||
const newOptions = {
|
||||
...rest,
|
||||
...headers
|
||||
}
|
||||
|
||||
this.$store.dispatch('app/updateAddTaskUrl', uri)
|
||||
this.$store.dispatch('app/updateAddTaskOptions', newOptions)
|
||||
this.$store.dispatch('app/showAddTaskDialog', ADD_TASK_TYPE.URI)
|
||||
},
|
||||
deleteTaskFiles (task) {
|
||||
try {
|
||||
const result = moveTaskFilesToTrash(task)
|
||||
|
||||
if (!result) {
|
||||
throw new Error('task.remove-task-file-fail')
|
||||
}
|
||||
} catch (err) {
|
||||
this.$msg.error(this.$t(err.message))
|
||||
}
|
||||
},
|
||||
removeTask (task, taskName, isRemoveWithFiles = false) {
|
||||
this.$store.dispatch('task/forcePauseTask', task)
|
||||
.finally(() => {
|
||||
if (isRemoveWithFiles) {
|
||||
this.deleteTaskFiles(task)
|
||||
}
|
||||
|
||||
return this.removeTaskItem(task, taskName)
|
||||
})
|
||||
},
|
||||
removeTaskRecord (task, taskName, isRemoveWithFiles = false) {
|
||||
this.$store.dispatch('task/forcePauseTask', task)
|
||||
.finally(() => {
|
||||
if (isRemoveWithFiles) {
|
||||
this.deleteTaskFiles(task)
|
||||
}
|
||||
|
||||
return this.removeTaskRecordItem(task, taskName)
|
||||
})
|
||||
},
|
||||
removeTaskItem (task, taskName) {
|
||||
return this.$store.dispatch('task/removeTask', task)
|
||||
.then(() => {
|
||||
this.$msg.success(this.$t('task.delete-task-success', {
|
||||
taskName
|
||||
}))
|
||||
})
|
||||
.catch(({ code }) => {
|
||||
if (code === 1) {
|
||||
this.$msg.error(this.$t('task.delete-task-fail', {
|
||||
taskName
|
||||
}))
|
||||
}
|
||||
})
|
||||
},
|
||||
removeTaskRecordItem (task, taskName) {
|
||||
return this.$store.dispatch('task/removeTaskRecord', task)
|
||||
.then(() => {
|
||||
this.$msg.success(this.$t('task.remove-record-success', {
|
||||
taskName
|
||||
}))
|
||||
})
|
||||
.catch(({ code }) => {
|
||||
if (code === 1) {
|
||||
this.$msg.error(this.$t('task.remove-record-fail', {
|
||||
taskName
|
||||
}))
|
||||
}
|
||||
})
|
||||
},
|
||||
removeTasks (taskList, isRemoveWithFiles = false) {
|
||||
const gids = taskList.map((task) => task.gid)
|
||||
this.$store.dispatch('task/batchForcePauseTask', gids)
|
||||
.finally(() => {
|
||||
if (isRemoveWithFiles) {
|
||||
this.batchDeleteTaskFiles(taskList)
|
||||
}
|
||||
|
||||
this.removeTaskItems(gids)
|
||||
})
|
||||
},
|
||||
batchDeleteTaskFiles (taskList) {
|
||||
const promises = taskList.map((task, index) => delayDeleteTaskFiles(task, index * 200))
|
||||
Promise.all(promises).then(values => {
|
||||
console.log('[Motrix] batch delete task files: ', values)
|
||||
})
|
||||
},
|
||||
removeTaskItems (gids) {
|
||||
this.$store.dispatch('task/batchRemoveTask', gids)
|
||||
.then(() => {
|
||||
this.$msg.success(this.$t('task.batch-delete-task-success'))
|
||||
})
|
||||
.catch(({ code }) => {
|
||||
if (code === 1) {
|
||||
this.$msg.error(this.$t('task.batch-delete-task-fail'))
|
||||
}
|
||||
})
|
||||
},
|
||||
handlePauseTask (payload) {
|
||||
const { task, taskName } = payload
|
||||
this.$msg.info(this.$t('task.download-pause-message', { taskName }))
|
||||
this.$store.dispatch('task/pauseTask', task)
|
||||
.catch(({ code }) => {
|
||||
if (code === 1) {
|
||||
this.$msg.error(this.$t('task.pause-task-fail', { taskName }))
|
||||
}
|
||||
})
|
||||
},
|
||||
handleResumeTask (payload) {
|
||||
const { task, taskName } = payload
|
||||
this.$store.dispatch('task/resumeTask', task)
|
||||
.catch(({ code }) => {
|
||||
if (code === 1) {
|
||||
this.$msg.error(this.$t('task.resume-task-fail', {
|
||||
taskName
|
||||
}))
|
||||
}
|
||||
})
|
||||
},
|
||||
handleStopTaskSeeding (payload) {
|
||||
const { task } = payload
|
||||
this.$store.dispatch('task/stopSeeding', task)
|
||||
},
|
||||
handleRestartTask (payload) {
|
||||
const { task, taskName, showDialog } = payload
|
||||
const { gid } = task
|
||||
const uri = getTaskUri(task)
|
||||
|
||||
this.$store.dispatch('task/getTaskOption', gid)
|
||||
.then((data) => {
|
||||
console.log('[Motrix] get task option:', data)
|
||||
const { dir, header, split } = data
|
||||
const options = {
|
||||
dir,
|
||||
header,
|
||||
split,
|
||||
out: taskName
|
||||
}
|
||||
|
||||
if (showDialog) {
|
||||
this.showAddTaskDialog(uri, options)
|
||||
} else {
|
||||
this.directAddTask(uri, options)
|
||||
this.$store.dispatch('task/removeTaskRecord', task)
|
||||
}
|
||||
})
|
||||
},
|
||||
handleRevealInFolder (payload) {
|
||||
const { path } = payload
|
||||
showItemInFolder(path, {
|
||||
errorMsg: this.$t('task.file-not-exist')
|
||||
})
|
||||
},
|
||||
handleDeleteTask (payload) {
|
||||
const { task, taskName, deleteWithFiles } = payload
|
||||
const { noConfirmBeforeDelete } = this
|
||||
|
||||
if (noConfirmBeforeDelete) {
|
||||
this.removeTask(task, taskName, deleteWithFiles)
|
||||
return
|
||||
}
|
||||
|
||||
this.$electron.remote.dialog.showMessageBox({
|
||||
type: 'warning',
|
||||
title: this.$t('task.delete-task'),
|
||||
message: this.$t('task.delete-task-confirm', { taskName }),
|
||||
buttons: [this.$t('app.yes'), this.$t('app.no')],
|
||||
cancelId: 1,
|
||||
checkboxLabel: this.$t('task.delete-task-label'),
|
||||
checkboxChecked: deleteWithFiles
|
||||
}).then(({ response, checkboxChecked }) => {
|
||||
if (response === 0) {
|
||||
this.removeTask(task, taskName, checkboxChecked)
|
||||
}
|
||||
})
|
||||
},
|
||||
handleDeleteTaskRecord (payload) {
|
||||
const { task, taskName, deleteWithFiles } = payload
|
||||
const { noConfirmBeforeDelete } = this
|
||||
|
||||
if (noConfirmBeforeDelete) {
|
||||
this.removeTaskRecord(task, taskName, deleteWithFiles)
|
||||
return
|
||||
}
|
||||
|
||||
this.$electron.remote.dialog.showMessageBox({
|
||||
type: 'warning',
|
||||
title: this.$t('task.remove-record'),
|
||||
message: this.$t('task.remove-record-confirm', { taskName }),
|
||||
buttons: [this.$t('app.yes'), this.$t('app.no')],
|
||||
cancelId: 1,
|
||||
checkboxLabel: this.$t('task.remove-record-label'),
|
||||
checkboxChecked: !!deleteWithFiles
|
||||
}).then(({ response, checkboxChecked }) => {
|
||||
if (response === 0) {
|
||||
this.removeTaskRecord(task, taskName, checkboxChecked)
|
||||
}
|
||||
})
|
||||
},
|
||||
handleBatchDeleteTask (payload) {
|
||||
const { deleteWithFiles } = payload
|
||||
const {
|
||||
noConfirmBeforeDelete,
|
||||
selectedGidList,
|
||||
selectedGidListCount,
|
||||
taskList
|
||||
} = this
|
||||
if (selectedGidListCount === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
const selectedTaskList = taskList.filter((task) => {
|
||||
return selectedGidList.includes(task.gid)
|
||||
})
|
||||
|
||||
if (noConfirmBeforeDelete) {
|
||||
this.removeTasks(selectedTaskList, deleteWithFiles)
|
||||
return
|
||||
}
|
||||
|
||||
const count = `${selectedGidListCount}`
|
||||
this.$electron.remote.dialog.showMessageBox({
|
||||
type: 'warning',
|
||||
title: this.$t('task.delete-selected-task'),
|
||||
message: this.$t('task.batch-delete-task-confirm', { count }),
|
||||
buttons: [this.$t('app.yes'), this.$t('app.no')],
|
||||
cancelId: 1,
|
||||
checkboxLabel: this.$t('task.delete-task-label'),
|
||||
checkboxChecked: deleteWithFiles
|
||||
}).then(({ response, checkboxChecked }) => {
|
||||
if (response === 0) {
|
||||
this.removeTasks(selectedTaskList, checkboxChecked)
|
||||
}
|
||||
})
|
||||
},
|
||||
handleCopyTaskLink (payload) {
|
||||
const { task } = payload
|
||||
const uri = getTaskUri(task)
|
||||
clipboard.writeText(uri)
|
||||
.then(() => {
|
||||
this.$msg.success(this.$t('task.copy-link-success'))
|
||||
})
|
||||
},
|
||||
handleShowTaskInfo (payload) {
|
||||
const { task } = payload
|
||||
this.$store.dispatch('task/showTaskItemInfoDialog', task)
|
||||
}
|
||||
},
|
||||
created: function () {
|
||||
created () {
|
||||
this.changeCurrentList()
|
||||
},
|
||||
mounted () {
|
||||
commands.on('pause-task', this.handlePauseTask)
|
||||
commands.on('resume-task', this.handleResumeTask)
|
||||
commands.on('stop-task-seeding', this.handleStopTaskSeeding)
|
||||
commands.on('restart-task', this.handleRestartTask)
|
||||
commands.on('reveal-in-folder', this.handleRevealInFolder)
|
||||
commands.on('delete-task', this.handleDeleteTask)
|
||||
commands.on('delete-task-record', this.handleDeleteTaskRecord)
|
||||
commands.on('batch-delete-task', this.handleBatchDeleteTask)
|
||||
commands.on('copy-task-link', this.handleCopyTaskLink)
|
||||
commands.on('show-task-info', this.handleShowTaskInfo)
|
||||
},
|
||||
destroyed () {
|
||||
commands.off('pause-task', this.handlePauseTask)
|
||||
commands.off('resume-task', this.handleResumeTask)
|
||||
commands.off('stop-task-seeding', this.handleStopTaskSeeding)
|
||||
commands.off('restart-task', this.handleRestartTask)
|
||||
commands.off('reveal-in-folder', this.handleRevealInFolder)
|
||||
commands.off('delete-task', this.handleDeleteTask)
|
||||
commands.off('delete-task-record', this.handleDeleteTaskRecord)
|
||||
commands.off('batch-delete-task', this.handleBatchDeleteTask)
|
||||
commands.off('copy-task-link', this.handleCopyTaskLink)
|
||||
commands.off('show-task-info', this.handleShowTaskInfo)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -129,25 +129,25 @@
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState('preference', {
|
||||
config: state => state.config
|
||||
}),
|
||||
...mapState('app', {
|
||||
torrents: state => state.addTaskTorrents
|
||||
}),
|
||||
isTorrentsEmpty: function () {
|
||||
...mapState('preference', {
|
||||
config: state => state.config
|
||||
}),
|
||||
isTorrentsEmpty () {
|
||||
return this.torrents.length === 0
|
||||
},
|
||||
selectedFilesCount: function () {
|
||||
selectedFilesCount () {
|
||||
return this.selectedFiles.length
|
||||
},
|
||||
selectedFilesTotalSize: function () {
|
||||
selectedFilesTotalSize () {
|
||||
const result = this.selectedFiles.reduce((acc, cur) => {
|
||||
return acc + cur.length
|
||||
}, 0)
|
||||
return bytesToSize(result)
|
||||
},
|
||||
selectedFileIndex: function () {
|
||||
selectedFileIndex () {
|
||||
const { files, selectedFiles } = this
|
||||
if (files.length === 0 || selectedFiles.length === 0) {
|
||||
return NONE_SELECTED_FILES
|
||||
@@ -174,7 +174,7 @@
|
||||
|
||||
parseTorrent.remote(file.raw, (err, parsedTorrent) => {
|
||||
if (err) throw err
|
||||
console.log(parsedTorrent)
|
||||
console.log('[Motrix] parsed torrent: ', parsedTorrent)
|
||||
this.files = listTorrentFiles(parsedTorrent.files)
|
||||
this.$refs.torrentTable.toggleAllSelection()
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
<el-tooltip
|
||||
class="item hidden-md-and-up"
|
||||
effect="dark"
|
||||
:content="$t('task.new-task')"
|
||||
placement="bottom"
|
||||
:content="$t('task.new-task')"
|
||||
>
|
||||
<i class="task-action" @click.stop="onAddClick">
|
||||
<mo-icon name="menu-add" width="14" height="14" />
|
||||
@@ -15,6 +15,7 @@
|
||||
effect="dark"
|
||||
placement="bottom"
|
||||
:content="$t('task.delete-selected-tasks')"
|
||||
v-if="currentList !== 'stopped'"
|
||||
>
|
||||
<i
|
||||
class="task-action"
|
||||
@@ -23,17 +24,32 @@
|
||||
<mo-icon name="delete" width="14" height="14" />
|
||||
</i>
|
||||
</el-tooltip>
|
||||
<el-tooltip class="item" effect="dark" :content="$t('task.refresh-list')" placement="bottom">
|
||||
<el-tooltip
|
||||
class="item"
|
||||
effect="dark"
|
||||
placement="bottom"
|
||||
:content="$t('task.refresh-list')"
|
||||
>
|
||||
<i class="task-action" @click="onRefreshClick">
|
||||
<mo-icon name="refresh" width="14" height="14" :spin="refreshing" />
|
||||
</i>
|
||||
</el-tooltip>
|
||||
<el-tooltip class="item" effect="dark" :content="$t('task.resume-all-task')" placement="bottom">
|
||||
<el-tooltip
|
||||
class="item"
|
||||
effect="dark"
|
||||
placement="bottom"
|
||||
:content="$t('task.resume-all-task')"
|
||||
>
|
||||
<i class="task-action" @click="onResumeAllClick">
|
||||
<mo-icon name="task-start-line" width="14" height="14" />
|
||||
</i>
|
||||
</el-tooltip>
|
||||
<el-tooltip class="item" effect="dark" :content="$t('task.pause-all-task')" placement="bottom">
|
||||
<el-tooltip
|
||||
class="item"
|
||||
effect="dark"
|
||||
placement="bottom"
|
||||
:content="$t('task.pause-all-task')"
|
||||
>
|
||||
<i class="task-action" @click="onPauseAllClick">
|
||||
<mo-icon name="task-pause-line" width="14" height="14" />
|
||||
</i>
|
||||
@@ -41,8 +57,8 @@
|
||||
<el-tooltip
|
||||
class="item"
|
||||
effect="dark"
|
||||
:content="$t('task.purge-record')"
|
||||
placement="bottom"
|
||||
:content="$t('task.purge-record')"
|
||||
v-if="currentList === 'stopped'"
|
||||
>
|
||||
<i class="task-action" @click="onPurgeRecordClick">
|
||||
@@ -54,11 +70,10 @@
|
||||
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
|
||||
import { commands } from '@/components/CommandManager/instance'
|
||||
import { ADD_TASK_TYPE } from '@shared/constants'
|
||||
import { bytesToSize, timeFormat } from '@shared/utils'
|
||||
import {
|
||||
moveTaskFilesToTrash
|
||||
} from '@/components/Native/utils'
|
||||
import '@/components/Icons/menu-add'
|
||||
import '@/components/Icons/refresh'
|
||||
import '@/components/Icons/task-start-line'
|
||||
@@ -72,7 +87,7 @@
|
||||
components: {
|
||||
},
|
||||
props: ['task'],
|
||||
data: function () {
|
||||
data () {
|
||||
return {
|
||||
refreshing: false
|
||||
}
|
||||
@@ -80,8 +95,6 @@
|
||||
computed: {
|
||||
...mapState('task', {
|
||||
currentList: state => state.currentList,
|
||||
taskList: state => state.taskList,
|
||||
selectedGidList: state => state.selectedGidList,
|
||||
selectedGidListCount: state => state.selectedGidList.length
|
||||
})
|
||||
},
|
||||
@@ -90,7 +103,7 @@
|
||||
timeFormat
|
||||
},
|
||||
methods: {
|
||||
refreshSpin: function () {
|
||||
refreshSpin () {
|
||||
this.t && clearTimeout(this.t)
|
||||
|
||||
this.refreshing = true
|
||||
@@ -98,79 +111,15 @@
|
||||
this.refreshing = false
|
||||
}, 500)
|
||||
},
|
||||
delayDeleteTaskFiles (task, delay) {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
try {
|
||||
const result = moveTaskFilesToTrash(task)
|
||||
resolve(result)
|
||||
} catch (err) {
|
||||
console.log('[Motrix] batch delay delete task files fail', err)
|
||||
resolve(false)
|
||||
}
|
||||
}, delay)
|
||||
})
|
||||
onBatchDeleteClick (event) {
|
||||
const deleteWithFiles = !!event.shiftKey
|
||||
commands.emit('batch-delete-task', { deleteWithFiles })
|
||||
},
|
||||
batchDeleteTaskFiles (taskList) {
|
||||
const promises = taskList.map((task, index) => this.delayDeleteTaskFiles(task, index * 200))
|
||||
Promise.all(promises).then(values => {
|
||||
console.log(values)
|
||||
})
|
||||
},
|
||||
removeTasks (taskList, isRemoveWithFiles) {
|
||||
const gids = taskList.map((task) => task.gid)
|
||||
this.$store.dispatch('task/batchForcePauseTask', gids)
|
||||
.finally(() => {
|
||||
if (isRemoveWithFiles) {
|
||||
this.batchDeleteTaskFiles(taskList)
|
||||
}
|
||||
|
||||
this.removeTaskItems(gids)
|
||||
})
|
||||
},
|
||||
removeTaskItems (gids) {
|
||||
this.$store.dispatch('task/batchRemoveTask', gids)
|
||||
.then(() => {
|
||||
this.$msg.success(this.$t('task.batch-delete-task-success'))
|
||||
})
|
||||
.catch(({ code }) => {
|
||||
if (code === 1) {
|
||||
this.$msg.error(this.$t('task.batch-delete-task-fail'))
|
||||
}
|
||||
})
|
||||
},
|
||||
onBatchDeleteClick: function (event) {
|
||||
const self = this
|
||||
const { taskList, selectedGidList, selectedGidListCount } = this
|
||||
if (selectedGidListCount === 0) {
|
||||
event.preventDefault()
|
||||
return
|
||||
}
|
||||
|
||||
const selectedTaskList = taskList.filter((task) => {
|
||||
return selectedGidList.includes(task.gid)
|
||||
})
|
||||
const count = `${selectedGidListCount}`
|
||||
const isChecked = !!event.shiftKey
|
||||
this.$electron.remote.dialog.showMessageBox({
|
||||
type: 'warning',
|
||||
title: this.$t('task.delete-selected-task'),
|
||||
message: this.$t('task.batch-delete-task-confirm', { count }),
|
||||
buttons: [this.$t('app.yes'), this.$t('app.no')],
|
||||
cancelId: 1,
|
||||
checkboxLabel: this.$t('task.delete-task-label'),
|
||||
checkboxChecked: isChecked
|
||||
}).then(({ response, checkboxChecked }) => {
|
||||
if (response === 0) {
|
||||
self.removeTasks(selectedTaskList, checkboxChecked)
|
||||
}
|
||||
})
|
||||
},
|
||||
onRefreshClick: function () {
|
||||
onRefreshClick () {
|
||||
this.refreshSpin()
|
||||
this.$store.dispatch('task/fetchList')
|
||||
},
|
||||
onResumeAllClick: function () {
|
||||
onResumeAllClick () {
|
||||
this.$store.dispatch('task/resumeAllTask')
|
||||
.then(() => {
|
||||
this.$msg.success(this.$t('task.resume-all-task-success'))
|
||||
@@ -181,7 +130,7 @@
|
||||
}
|
||||
})
|
||||
},
|
||||
onPauseAllClick: function () {
|
||||
onPauseAllClick () {
|
||||
this.$store.dispatch('task/pauseAllTask')
|
||||
.then(() => {
|
||||
this.$msg.success(this.$t('task.pause-all-task-success'))
|
||||
@@ -192,7 +141,7 @@
|
||||
}
|
||||
})
|
||||
},
|
||||
onPurgeRecordClick: function () {
|
||||
onPurgeRecordClick () {
|
||||
this.$store.dispatch('task/purgeTaskRecord')
|
||||
.then(() => {
|
||||
this.$msg.success(this.$t('task.purge-record-success'))
|
||||
@@ -203,7 +152,7 @@
|
||||
}
|
||||
})
|
||||
},
|
||||
onAddClick: function () {
|
||||
onAddClick () {
|
||||
this.$store.dispatch('app/showAddTaskDialog', ADD_TASK_TYPE.URI)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
getTaskName
|
||||
} from '@shared/utils'
|
||||
import { TASK_STATUS } from '@shared/constants'
|
||||
import { openItem } from '@/components/Native/utils'
|
||||
import { openItem } from '@/utils/native'
|
||||
import TaskItemActions from './TaskItemActions'
|
||||
import TaskProgress from './TaskProgress'
|
||||
import TaskProgressInfo from './TaskProgressInfo'
|
||||
|
||||
@@ -36,19 +36,15 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
import is from 'electron-is'
|
||||
import * as clipboard from 'clipboard-polyfill'
|
||||
import { ADD_TASK_TYPE, TASK_STATUS } from '@shared/constants'
|
||||
import {
|
||||
showItemInFolder,
|
||||
moveTaskFilesToTrash
|
||||
} from '@/components/Native/utils'
|
||||
|
||||
import { commands } from '@/components/CommandManager/instance'
|
||||
import { TASK_STATUS } from '@shared/constants'
|
||||
import {
|
||||
checkTaskIsSeeder,
|
||||
getTaskFullPath,
|
||||
getTaskName,
|
||||
getTaskUri,
|
||||
parseHeader
|
||||
getTaskName
|
||||
} from '@shared/utils'
|
||||
import '@/components/Icons/task-start-line'
|
||||
import '@/components/Icons/task-pause-line'
|
||||
@@ -84,6 +80,9 @@
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState('preference', {
|
||||
noConfirmBeforeDelete: state => state.config.noConfirmBeforeDeleteTask
|
||||
}),
|
||||
taskName () {
|
||||
return getTaskName(this.task)
|
||||
},
|
||||
@@ -117,206 +116,67 @@
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
deleteTaskFiles (task) {
|
||||
try {
|
||||
const result = moveTaskFilesToTrash(task)
|
||||
|
||||
if (!result) {
|
||||
throw new Error('task.remove-task-file-fail')
|
||||
}
|
||||
} catch (err) {
|
||||
this.$msg.error(this.$t(err.message))
|
||||
}
|
||||
},
|
||||
removeTask (task, isRemoveWithFiles) {
|
||||
this.$store.dispatch('task/forcePauseTask', task)
|
||||
.finally(() => {
|
||||
if (isRemoveWithFiles) {
|
||||
this.deleteTaskFiles(task)
|
||||
}
|
||||
|
||||
return this.removeTaskItem(task)
|
||||
})
|
||||
},
|
||||
removeTaskItem (task) {
|
||||
return this.$store.dispatch('task/removeTask', this.task)
|
||||
.then(() => {
|
||||
this.$msg.success(this.$t('task.delete-task-success', {
|
||||
taskName: this.taskName
|
||||
}))
|
||||
})
|
||||
.catch(({ code }) => {
|
||||
if (code === 1) {
|
||||
this.$msg.error(this.$t('task.delete-task-fail', {
|
||||
taskName: this.taskName
|
||||
}))
|
||||
}
|
||||
})
|
||||
},
|
||||
removeTaskRecord (task, isRemoveWithFiles) {
|
||||
this.$store.dispatch('task/forcePauseTask', task)
|
||||
.finally(() => {
|
||||
if (isRemoveWithFiles) {
|
||||
this.deleteTaskFiles(task)
|
||||
}
|
||||
|
||||
return this.removeTaskRecordItem(task)
|
||||
})
|
||||
},
|
||||
removeTaskRecordItem (task) {
|
||||
return this.$store.dispatch('task/removeTaskRecord', this.task)
|
||||
.then(() => {
|
||||
this.$msg.success(this.$t('task.remove-record-success', {
|
||||
taskName: this.taskName
|
||||
}))
|
||||
})
|
||||
.catch(({ code }) => {
|
||||
if (code === 1) {
|
||||
this.$msg.error(this.$t('task.remove-record-fail', {
|
||||
taskName: this.taskName
|
||||
}))
|
||||
}
|
||||
})
|
||||
},
|
||||
onResumeClick () {
|
||||
this.$store.dispatch('task/resumeTask', this.task)
|
||||
.catch(({ code }) => {
|
||||
if (code === 1) {
|
||||
this.$msg.error(this.$t('task.resume-task-fail', {
|
||||
taskName: this.taskName
|
||||
}))
|
||||
}
|
||||
})
|
||||
const { task, taskName } = this
|
||||
commands.emit('resume-task', {
|
||||
task,
|
||||
taskName
|
||||
})
|
||||
},
|
||||
onRestartClick (event) {
|
||||
const { task, taskName } = this
|
||||
const { gid, status } = task
|
||||
const uri = getTaskUri(task)
|
||||
const isNeedShowDialog = status === TASK_STATUS.COMPLETE || !!event.altKey
|
||||
this.$store.dispatch('task/getTaskOption', gid)
|
||||
.then((data) => {
|
||||
console.log('[Motrix] get task option:', data)
|
||||
const { dir, header, maxConnectionPerServer } = data
|
||||
const options = {
|
||||
dir,
|
||||
header,
|
||||
maxConnectionPerServer,
|
||||
out: taskName
|
||||
}
|
||||
|
||||
if (isNeedShowDialog) {
|
||||
this.showAddTaskDialog(uri, options)
|
||||
} else {
|
||||
this.directAddTask(uri, options)
|
||||
this.$store.dispatch('task/removeTaskRecord', task)
|
||||
}
|
||||
})
|
||||
},
|
||||
directAddTask (uri, options = {}) {
|
||||
const uris = [uri]
|
||||
const payload = {
|
||||
uris,
|
||||
options: {
|
||||
...options
|
||||
}
|
||||
}
|
||||
this.$store.dispatch('task/addUri', payload)
|
||||
.catch((err) => {
|
||||
this.$msg.error(err.message)
|
||||
})
|
||||
},
|
||||
showAddTaskDialog (uri, options = {}) {
|
||||
const {
|
||||
header,
|
||||
...rest
|
||||
} = options
|
||||
|
||||
const headers = parseHeader(header)
|
||||
const newOptions = {
|
||||
...rest,
|
||||
...headers
|
||||
}
|
||||
|
||||
this.$store.dispatch('app/updateAddTaskUrl', uri)
|
||||
this.$store.dispatch('app/updateAddTaskOptions', newOptions)
|
||||
this.$store.dispatch('app/showAddTaskDialog', ADD_TASK_TYPE.URI)
|
||||
const { status } = task
|
||||
const showDialog = status === TASK_STATUS.COMPLETE || !!event.altKey
|
||||
commands.emit('restart-task', {
|
||||
task,
|
||||
taskName,
|
||||
showDialog
|
||||
})
|
||||
},
|
||||
onPauseClick () {
|
||||
this.pauseTask()
|
||||
const { task, taskName } = this
|
||||
commands.emit('pause-task', {
|
||||
task,
|
||||
taskName
|
||||
})
|
||||
},
|
||||
onStopClick () {
|
||||
this.stopSeeding()
|
||||
},
|
||||
stopSeeding () {
|
||||
if (!this.isSeeder) {
|
||||
return
|
||||
}
|
||||
this.$store.dispatch('task/stopSeeding', this.task)
|
||||
},
|
||||
pauseTask () {
|
||||
const { taskName } = this
|
||||
this.$msg.info(this.$t('task.download-pause-message', { taskName }))
|
||||
this.$store.dispatch('task/pauseTask', this.task)
|
||||
.catch(({ code }) => {
|
||||
if (code === 1) {
|
||||
this.$msg.error(this.$t('task.pause-task-fail', { taskName }))
|
||||
}
|
||||
})
|
||||
|
||||
const { task } = this
|
||||
commands.emit('stop-task-seeding', { task })
|
||||
},
|
||||
onDeleteClick (event) {
|
||||
const self = this
|
||||
const { task } = this
|
||||
const isChecked = !!event.shiftKey
|
||||
this.$electron.remote.dialog.showMessageBox({
|
||||
type: 'warning',
|
||||
title: this.$t('task.delete-task'),
|
||||
message: this.$t('task.delete-task-confirm', { taskName: this.taskName }),
|
||||
buttons: [this.$t('app.yes'), this.$t('app.no')],
|
||||
cancelId: 1,
|
||||
checkboxLabel: this.$t('task.delete-task-label'),
|
||||
checkboxChecked: isChecked
|
||||
}).then(({ response, checkboxChecked }) => {
|
||||
if (response === 0) {
|
||||
self.removeTask(task, checkboxChecked)
|
||||
}
|
||||
const { task, taskName } = this
|
||||
const deleteWithFiles = !!event.shiftKey
|
||||
commands.emit('delete-task', {
|
||||
task,
|
||||
taskName,
|
||||
deleteWithFiles
|
||||
})
|
||||
},
|
||||
onTrashClick (event) {
|
||||
const self = this
|
||||
const { task } = this
|
||||
const isChecked = !!event.shiftKey
|
||||
this.$electron.remote.dialog.showMessageBox({
|
||||
type: 'warning',
|
||||
title: this.$t('task.remove-record'),
|
||||
message: this.$t('task.remove-record-confirm', { taskName: this.taskName }),
|
||||
buttons: [this.$t('app.yes'), this.$t('app.no')],
|
||||
cancelId: 1,
|
||||
checkboxLabel: this.$t('task.remove-record-label'),
|
||||
checkboxChecked: isChecked
|
||||
}).then(({ response, checkboxChecked }) => {
|
||||
if (response === 0) {
|
||||
self.removeTaskRecord(task, checkboxChecked)
|
||||
}
|
||||
const { task, taskName } = this
|
||||
const deleteWithFiles = !!event.shiftKey
|
||||
commands.emit('delete-task-record', {
|
||||
task,
|
||||
taskName,
|
||||
deleteWithFiles
|
||||
})
|
||||
},
|
||||
onFolderClick () {
|
||||
showItemInFolder(this.path, {
|
||||
errorMsg: this.$t('task.file-not-exist')
|
||||
})
|
||||
const { path } = this
|
||||
commands.emit('reveal-in-folder', { path })
|
||||
},
|
||||
onLinkClick () {
|
||||
this.$store.dispatch('app/fetchEngineOptions')
|
||||
.then((data) => {
|
||||
const { btTracker } = data
|
||||
const uri = getTaskUri(this.task, btTracker)
|
||||
clipboard.writeText(uri)
|
||||
.then(() => {
|
||||
this.$msg.success(this.$t('task.copy-link-success'))
|
||||
})
|
||||
})
|
||||
const { task } = this
|
||||
commands.emit('copy-task-link', { task })
|
||||
},
|
||||
onInfoClick () {
|
||||
this.$store.dispatch('task/showTaskItemInfoDialog', this.task)
|
||||
const { task } = this
|
||||
commands.emit('show-task-info', { task })
|
||||
},
|
||||
onMoreClick () {
|
||||
}
|
||||
|
||||
@@ -54,19 +54,19 @@
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
taskFullName: function () {
|
||||
taskFullName () {
|
||||
return getTaskName(this.task, {
|
||||
defaultName: this.$t('task.get-task-name'),
|
||||
maxLen: -1
|
||||
})
|
||||
},
|
||||
taskName: function () {
|
||||
taskName () {
|
||||
return getTaskName(this.task, {
|
||||
defaultName: this.$t('task.get-task-name'),
|
||||
maxLen: 32
|
||||
})
|
||||
},
|
||||
dialogTitle: function () {
|
||||
dialogTitle () {
|
||||
return this.$t('task.task-info-dialog-title', { title: this.taskName })
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
<template>
|
||||
<el-row class="task-progress-info">
|
||||
<el-col :span="8" class="task-progress-info-left">
|
||||
<div v-if="task.totalLength > 0">
|
||||
{{ task.completedLength | bytesToSize }} / {{ task.totalLength | bytesToSize }}
|
||||
<div v-if="task.completedLength > 0 || task.totalLength > 0">
|
||||
<span>{{ task.completedLength | bytesToSize }}</span>
|
||||
<span v-if="task.totalLength > 0"> / {{ task.totalLength | bytesToSize }}</span>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="16" class="task-progress-info-right">
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
[Ipc.name]: Ipc
|
||||
},
|
||||
computed: {
|
||||
isRenderer () { return is.renderer() },
|
||||
isRenderer: () => is.renderer(),
|
||||
...mapState('app', {
|
||||
systemTheme: state => state.systemTheme
|
||||
}),
|
||||
@@ -42,38 +42,38 @@
|
||||
locale: state => state.config.locale,
|
||||
dir: state => getLangDirection(state.config.locale)
|
||||
}),
|
||||
themeClass: function () {
|
||||
themeClass () {
|
||||
if (this.theme === APP_THEME.AUTO) {
|
||||
return `theme-${this.systemTheme}`
|
||||
} else {
|
||||
return `theme-${this.theme}`
|
||||
}
|
||||
},
|
||||
i18nClass: function () {
|
||||
i18nClass () {
|
||||
return `i18n-${this.locale}`
|
||||
},
|
||||
dirClass: function () {
|
||||
dirClass () {
|
||||
return `dir-${this.dir}`
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
updateRootClassName: function () {
|
||||
updateRootClassName () {
|
||||
const { themeClass = '', i18nClass = '', dirClass = '' } = this
|
||||
const className = `${themeClass} ${i18nClass} ${dirClass}`
|
||||
document.documentElement.className = className
|
||||
}
|
||||
},
|
||||
beforeMount: function () {
|
||||
beforeMount () {
|
||||
this.updateRootClassName()
|
||||
},
|
||||
watch: {
|
||||
themeClass: function (val, oldVal) {
|
||||
themeClass (val, oldVal) {
|
||||
this.updateRootClassName()
|
||||
},
|
||||
i18nClass: function (val, oldVal) {
|
||||
i18nClass (val, oldVal) {
|
||||
this.updateRootClassName()
|
||||
},
|
||||
dirClass: function (val, oldVal) {
|
||||
dirClass (val, oldVal) {
|
||||
this.updateRootClassName()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,35 +6,34 @@ import store from '@/store'
|
||||
import { buildFileList } from '@shared/utils'
|
||||
import { ADD_TASK_TYPE } from '@shared/constants'
|
||||
import { getLocaleManager } from '@/components/Locale'
|
||||
import CommandManager from './CommandManager'
|
||||
import { commands } from '@/components/CommandManager/instance'
|
||||
|
||||
const commands = new CommandManager()
|
||||
const i18n = getLocaleManager().getI18n()
|
||||
|
||||
function updateSystemTheme (theme) {
|
||||
const updateSystemTheme = (theme) => {
|
||||
store.dispatch('app/updateSystemTheme', theme)
|
||||
}
|
||||
|
||||
function updateTheme (theme) {
|
||||
const updateTheme = (theme) => {
|
||||
store.dispatch('preference/changeThemeConfig', theme)
|
||||
}
|
||||
|
||||
function showAboutPanel () {
|
||||
const showAboutPanel = () => {
|
||||
store.dispatch('app/showAboutPanel')
|
||||
}
|
||||
|
||||
function showAddTask (taskType = ADD_TASK_TYPE.URI, task = '') {
|
||||
if (taskType === ADD_TASK_TYPE.URI && task) {
|
||||
store.dispatch('app/updateAddTaskUrl', task)
|
||||
const showAddTask = (taskType = ADD_TASK_TYPE.URI, uri = '') => {
|
||||
if (taskType === ADD_TASK_TYPE.URI && uri) {
|
||||
store.dispatch('app/updateAddTaskUrl', uri)
|
||||
}
|
||||
store.dispatch('app/showAddTaskDialog', taskType)
|
||||
}
|
||||
|
||||
function showAddBtTask () {
|
||||
const showAddBtTask = () => {
|
||||
store.dispatch('app/showAddTaskDialog', ADD_TASK_TYPE.TORRENT)
|
||||
}
|
||||
|
||||
function showAddBtTaskWithFile (fileName, base64Data = '') {
|
||||
const showAddBtTaskWithFile = (fileName, base64Data = '') => {
|
||||
const blob = base64StringToBlob(base64Data, 'application/x-bittorrent')
|
||||
const file = new File([blob], fileName, { type: 'application/x-bittorrent' })
|
||||
const fileList = buildFileList(file)
|
||||
@@ -44,63 +43,67 @@ function showAddBtTaskWithFile (fileName, base64Data = '') {
|
||||
}, 200)
|
||||
}
|
||||
|
||||
function navigateTaskList (status = 'active') {
|
||||
const navigateTaskList = (status = 'active') => {
|
||||
router.push({ path: `/task/${status}` }).catch(err => {
|
||||
console.log(err)
|
||||
})
|
||||
}
|
||||
|
||||
function navigatePreferences () {
|
||||
const navigatePreferences = () => {
|
||||
router.push({ path: '/preference' }).catch(err => {
|
||||
console.log(err)
|
||||
})
|
||||
}
|
||||
|
||||
function showUnderDevelopmentMessage () {
|
||||
const showUnderDevelopmentMessage = () => {
|
||||
Message.info(i18n.t('app.under-development-message'))
|
||||
}
|
||||
|
||||
function pauseTask () {
|
||||
const pauseTask = () => {
|
||||
store.dispatch('task/batchPauseSelectedTasks')
|
||||
}
|
||||
|
||||
function resumeTask () {
|
||||
const resumeTask = () => {
|
||||
store.dispatch('task/batchResumeSelectedTasks')
|
||||
}
|
||||
|
||||
function deleteTask () {
|
||||
const deleteTask = () => {
|
||||
commands.emit('batch-delete-task', {
|
||||
deleteWithFiles: false
|
||||
})
|
||||
}
|
||||
|
||||
const moveTaskUp = () => {
|
||||
showUnderDevelopmentMessage()
|
||||
}
|
||||
|
||||
function moveTaskUp () {
|
||||
const moveTaskDown = () => {
|
||||
showUnderDevelopmentMessage()
|
||||
}
|
||||
|
||||
function moveTaskDown () {
|
||||
showUnderDevelopmentMessage()
|
||||
}
|
||||
|
||||
function pauseAllTask () {
|
||||
const pauseAllTask = () => {
|
||||
store.dispatch('task/pauseAllTask')
|
||||
}
|
||||
|
||||
function resumeAllTask () {
|
||||
const resumeAllTask = () => {
|
||||
store.dispatch('task/resumeAllTask')
|
||||
}
|
||||
|
||||
function selectAllTask () {
|
||||
const selectAllTask = () => {
|
||||
store.dispatch('task/selectAllTask')
|
||||
}
|
||||
|
||||
commands.register('application:system-theme', updateSystemTheme)
|
||||
commands.register('application:theme', updateTheme)
|
||||
const fetchPreference = () => {
|
||||
store.dispatch('preference/fetchPreference')
|
||||
}
|
||||
|
||||
commands.register('application:task-list', navigateTaskList)
|
||||
commands.register('application:preferences', navigatePreferences)
|
||||
commands.register('application:about', showAboutPanel)
|
||||
|
||||
commands.register('application:new-task', showAddTask)
|
||||
commands.register('application:new-bt-task', showAddBtTask)
|
||||
commands.register('application:new-bt-task-with-file', showAddBtTaskWithFile)
|
||||
commands.register('application:task-list', navigateTaskList)
|
||||
commands.register('application:preferences', navigatePreferences)
|
||||
|
||||
commands.register('application:pause-task', pauseTask)
|
||||
commands.register('application:resume-task', resumeTask)
|
||||
commands.register('application:delete-task', deleteTask)
|
||||
@@ -110,6 +113,6 @@ commands.register('application:pause-all-task', pauseAllTask)
|
||||
commands.register('application:resume-all-task', resumeAllTask)
|
||||
commands.register('application:select-all-task', selectAllTask)
|
||||
|
||||
export {
|
||||
commands
|
||||
}
|
||||
commands.register('application:update-preference-config', fetchPreference)
|
||||
commands.register('application:update-system-theme', updateSystemTheme)
|
||||
commands.register('application:update-theme', updateTheme)
|
||||
@@ -12,6 +12,7 @@ import store from '@/store'
|
||||
import { getLocaleManager } from '@/components/Locale'
|
||||
import Icon from '@/components/Icons/Icon'
|
||||
import Msg from '@/components/Msg'
|
||||
import { commands } from '@/components/CommandManager/instance'
|
||||
|
||||
import '@/components/Theme/Index.scss'
|
||||
|
||||
@@ -36,17 +37,17 @@ function init (config) {
|
||||
Vue.use(Msg, Message, {
|
||||
showClose: true
|
||||
})
|
||||
Vue.component('mo-icon', Icon)
|
||||
|
||||
const loading = Loading.service({
|
||||
fullscreen: true,
|
||||
background: 'rgba(0, 0, 0, 0.1)'
|
||||
})
|
||||
Vue.component('mo-icon', Icon)
|
||||
|
||||
sync(store, router)
|
||||
|
||||
/* eslint-disable no-new */
|
||||
window.app = new Vue({
|
||||
global.app = new Vue({
|
||||
components: { App },
|
||||
router,
|
||||
store,
|
||||
@@ -54,6 +55,9 @@ function init (config) {
|
||||
template: '<App/>'
|
||||
}).$mount('#app')
|
||||
|
||||
global.app.commands = commands
|
||||
require('./commands')
|
||||
|
||||
setTimeout(() => {
|
||||
loading.close()
|
||||
}, 400)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ADD_TASK_TYPE } from '@shared/constants'
|
||||
import api from '@/api'
|
||||
import { getSystemTheme } from '@/components/Native/utils'
|
||||
import { getSystemTheme } from '@/utils/native'
|
||||
|
||||
const BASE_INTERVAL = 1000
|
||||
const PER_INTERVAL = 100
|
||||
@@ -151,8 +151,8 @@ const actions = {
|
||||
changeAddTaskType ({ commit }, taskType) {
|
||||
commit('CHANGE_ADD_TASK_TYPE', taskType)
|
||||
},
|
||||
updateAddTaskUrl ({ commit }, text = '') {
|
||||
commit('CHANGE_ADD_TASK_URL', text)
|
||||
updateAddTaskUrl ({ commit }, uri = '') {
|
||||
commit('CHANGE_ADD_TASK_URL', uri)
|
||||
},
|
||||
addTaskAddTorrents ({ commit }, { fileList }) {
|
||||
commit('CHANGE_ADD_TASK_TORRENTS', fileList)
|
||||
|
||||
@@ -3,9 +3,9 @@ import { access, constants } from 'fs'
|
||||
import { Message } from 'element-ui'
|
||||
|
||||
import {
|
||||
isMagnetTask,
|
||||
bytesToSize,
|
||||
getTaskFullPath,
|
||||
bytesToSize
|
||||
isMagnetTask
|
||||
} from '@shared/utils'
|
||||
import { APP_THEME, TASK_STATUS } from '@shared/constants'
|
||||
|
||||
@@ -27,7 +27,7 @@ export function showItemInFolder (fullPath, { errorMsg }) {
|
||||
}
|
||||
|
||||
access(fullPath, constants.F_OK, (err) => {
|
||||
console.log(`${fullPath} ${err ? 'does not exist' : 'exists'}`)
|
||||
console.log(`[Motrix] ${fullPath} ${err ? 'does not exist' : 'exists'}`)
|
||||
if (err) {
|
||||
Message.error(errorMsg)
|
||||
return
|
||||
@@ -67,7 +67,7 @@ export function moveTaskFilesToTrash (task) {
|
||||
|
||||
let deleteResult1 = true
|
||||
access(path, constants.F_OK, (err) => {
|
||||
console.log(`${path} ${err ? 'does not exist' : 'exists'}`)
|
||||
console.log(`[Motrix] ${path} ${err ? 'does not exist' : 'exists'}`)
|
||||
if (!err) {
|
||||
deleteResult1 = remote.shell.moveItemToTrash(path)
|
||||
}
|
||||
@@ -81,7 +81,7 @@ export function moveTaskFilesToTrash (task) {
|
||||
let deleteResult2 = true
|
||||
const extraFilePath = `${path}.aria2`
|
||||
access(extraFilePath, constants.F_OK, (err) => {
|
||||
console.log(`${extraFilePath} ${err ? 'does not exist' : 'exists'}`)
|
||||
console.log(`[Motrix] ${extraFilePath} ${err ? 'does not exist' : 'exists'}`)
|
||||
if (!err) {
|
||||
deleteResult2 = remote.shell.moveItemToTrash(extraFilePath)
|
||||
}
|
||||
@@ -150,3 +150,17 @@ export const openExternal = (url, options) => {
|
||||
|
||||
remote.shell.openExternal(url, options)
|
||||
}
|
||||
|
||||
export const delayDeleteTaskFiles = (task, delay) => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
try {
|
||||
const result = moveTaskFilesToTrash(task)
|
||||
resolve(result)
|
||||
} catch (err) {
|
||||
console.log('[Motrix] batch delay delete task files fail', err)
|
||||
resolve(false)
|
||||
}
|
||||
}, delay)
|
||||
})
|
||||
}
|
||||
@@ -14,6 +14,7 @@ const userKeys = [
|
||||
'locale',
|
||||
'log-path',
|
||||
'new-task-show-downloading',
|
||||
'no-confirm-before-delete-task',
|
||||
'open-at-login',
|
||||
'protocols',
|
||||
'resume-all-when-app-launched',
|
||||
|
||||
+14
-2
@@ -88,11 +88,16 @@ export const trackerSourceOptions = [
|
||||
}
|
||||
]
|
||||
|
||||
export const ONE_SECOND = 1000
|
||||
export const ONE_MINUTE = ONE_SECOND * 60
|
||||
export const ONE_HOUR = ONE_MINUTE * 60
|
||||
export const ONE_DAY = ONE_HOUR * 24
|
||||
|
||||
// 12 Hours
|
||||
export const AUTO_SYNC_TRACKER_INTERVAL = 12 * 60 * 60 * 1000
|
||||
export const AUTO_SYNC_TRACKER_INTERVAL = ONE_HOUR * 12
|
||||
|
||||
// One Week
|
||||
export const AUTO_CHECK_UPDATE_INTERVAL = 7 * 24 * 60 * 60 * 1000
|
||||
export const AUTO_CHECK_UPDATE_INTERVAL = ONE_DAY * 7
|
||||
|
||||
export const NONE_SELECTED_FILES = 'none'
|
||||
export const SELECTED_ALL_FILES = 'all'
|
||||
@@ -103,3 +108,10 @@ export const IP_VERSION = {
|
||||
V4: 4,
|
||||
V6: 6
|
||||
}
|
||||
|
||||
export const LOGIN_SETTING_OPTIONS = {
|
||||
// For Windows
|
||||
args: [
|
||||
'--opened-at-login=1'
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"cmd-q": "application:quit",
|
||||
"cmd-n": "application:new-task",
|
||||
"cmd-shift-n": "application:new-bt-task",
|
||||
"cmd-o": "application:open-file",
|
||||
"cmd-,": "application:preferences",
|
||||
"cmd-shift-p": "application:pause-all-task",
|
||||
"cmd-shift-r": "application:resume-all-task",
|
||||
"cmdctrl-q": "application:quit",
|
||||
"cmdctrl-n": "application:new-task",
|
||||
"cmdctrl-shift-n": "application:new-bt-task",
|
||||
"cmdctrl-o": "application:open-file",
|
||||
"cmdctrl-,": "application:preferences",
|
||||
"cmdctrl-shift-p": "application:pause-all-task",
|
||||
"cmdctrl-shift-r": "application:resume-all-task",
|
||||
"ctrl-shift-a": "application:select-all-task"
|
||||
}
|
||||
|
||||
@@ -1,28 +1,34 @@
|
||||
import eleLocaleBg from 'element-ui/lib/locale/lang/bg'
|
||||
import eleLocaleCa from 'element-ui/lib/locale/lang/ca'
|
||||
import eleLocaleDe from 'element-ui/lib/locale/lang/de'
|
||||
import eleLocaleEn from 'element-ui/lib/locale/lang/en'
|
||||
import eleLocaleEs from 'element-ui/lib/locale/lang/es'
|
||||
import eleLocaleFa from 'element-ui/lib/locale/lang/fa'
|
||||
import eleLocaleFr from 'element-ui/lib/locale/lang/fr'
|
||||
import eleLocaleId from 'element-ui/lib/locale/lang/id'
|
||||
import eleLocaleJa from 'element-ui/lib/locale/lang/ja'
|
||||
import eleLocaleKo from 'element-ui/lib/locale/lang/ko'
|
||||
import eleLocalePtBR from 'element-ui/lib/locale/lang/pt-br'
|
||||
import eleLocaleRu from 'element-ui/lib/locale/lang/ru-RU'
|
||||
import eleLocaleTr from 'element-ui/lib/locale/lang/tr-TR'
|
||||
import eleLocaleVi from 'element-ui/lib/locale/lang/vi'
|
||||
import eleLocaleZhCN from 'element-ui/lib/locale/lang/zh-CN'
|
||||
import eleLocaleZhTW from 'element-ui/lib/locale/lang/zh-TW'
|
||||
import eleLocaleUk from 'element-ui/lib/locale/lang/ua'
|
||||
import appLocaleBg from '@shared/locales/bg'
|
||||
import appLocaleCa from '@shared/locales/ca'
|
||||
import appLocaleDe from '@shared/locales/de'
|
||||
import appLocaleEnUS from '@shared/locales/en-US'
|
||||
import appLocaleEs from '@shared/locales/es'
|
||||
import appLocaleFa from '@shared/locales/fa'
|
||||
import appLocaleFr from '@shared/locales/fr'
|
||||
import appLocaleId from '@shared/locales/id'
|
||||
import appLocaleJa from '@shared/locales/ja'
|
||||
import appLocaleKo from '@shared/locales/ko'
|
||||
import appLocalePtBR from '@shared/locales/pt-BR'
|
||||
import appLocaleRu from '@shared/locales/ru'
|
||||
import appLocaleTr from '@shared/locales/tr'
|
||||
import appLocaleVi from '@shared/locales/vi'
|
||||
import appLocaleZhCN from '@shared/locales/zh-CN'
|
||||
import appLocaleZhTW from '@shared/locales/zh-TW'
|
||||
import appLocaleUk from '@shared/locales/uk'
|
||||
@@ -66,6 +72,12 @@ const resources = {
|
||||
...appLocaleFr
|
||||
}
|
||||
},
|
||||
'id': {
|
||||
translation: {
|
||||
...eleLocaleId,
|
||||
...appLocaleId
|
||||
}
|
||||
},
|
||||
'ja': {
|
||||
translation: {
|
||||
...eleLocaleJa,
|
||||
@@ -96,6 +108,12 @@ const resources = {
|
||||
...appLocaleTr
|
||||
}
|
||||
},
|
||||
'vi': {
|
||||
translation: {
|
||||
...eleLocaleVi,
|
||||
...appLocaleVi
|
||||
}
|
||||
},
|
||||
'zh-CN': {
|
||||
translation: {
|
||||
...eleLocaleZhCN,
|
||||
@@ -113,6 +131,12 @@ const resources = {
|
||||
...eleLocaleUk,
|
||||
...appLocaleUk
|
||||
}
|
||||
},
|
||||
'bg': {
|
||||
translation: {
|
||||
...eleLocaleBg,
|
||||
...appLocaleBg
|
||||
}
|
||||
}
|
||||
}
|
||||
/* eslint-enable quote-props */
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
import appLocaleBg from '@shared/locales/bg'
|
||||
import appLocaleCa from '@shared/locales/ca'
|
||||
import appLocaleDe from '@shared/locales/de'
|
||||
import appLocaleEnUS from '@shared/locales/en-US'
|
||||
import appLocaleFa from '@shared/locales/fa'
|
||||
import appLocaleFr from '@shared/locales/fr'
|
||||
import appLocaleId from '@shared/locales/id'
|
||||
import appLocaleJa from '@shared/locales/ja'
|
||||
import appLocaleKo from '@shared/locales/ko'
|
||||
import appLocalePtBR from '@shared/locales/pt-BR'
|
||||
import appLocaleRu from '@shared/locales/ru'
|
||||
import appLocaleTr from '@shared/locales/tr'
|
||||
import appLocaleVi from '@shared/locales/vi'
|
||||
import appLocaleZhCN from '@shared/locales/zh-CN'
|
||||
import appLocaleZhTW from '@shared/locales/zh-TW'
|
||||
import appLocaleUk from '@shared/locales/uk'
|
||||
@@ -40,6 +43,11 @@ const resources = {
|
||||
...appLocaleFr
|
||||
}
|
||||
},
|
||||
'id': {
|
||||
translation: {
|
||||
...appLocaleId
|
||||
}
|
||||
},
|
||||
'ja': {
|
||||
translation: {
|
||||
...appLocaleJa
|
||||
@@ -65,6 +73,11 @@ const resources = {
|
||||
...appLocaleTr
|
||||
}
|
||||
},
|
||||
'vi': {
|
||||
translation: {
|
||||
...appLocaleVi
|
||||
}
|
||||
},
|
||||
'zh-CN': {
|
||||
translation: {
|
||||
...appLocaleZhCN
|
||||
@@ -79,6 +92,11 @@ const resources = {
|
||||
translation: {
|
||||
...appLocaleUk
|
||||
}
|
||||
},
|
||||
'bg': {
|
||||
translation: {
|
||||
...appLocaleBg
|
||||
}
|
||||
}
|
||||
}
|
||||
/* eslint-enable quote-props */
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
export default {
|
||||
'engine-version': 'Версия',
|
||||
'license': 'Лиценз',
|
||||
'about': 'Информация',
|
||||
'release': 'Съобщение',
|
||||
'support': 'Подкрепа'
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
export default {
|
||||
'task-list': 'Списък със задачи',
|
||||
'add-task': 'Добавяне на задача',
|
||||
'about': 'О Motrix',
|
||||
'preferences': 'Меню...',
|
||||
'check-for-updates': 'Проверка на актуализацията...',
|
||||
'check-updates-now': 'Проверете сега',
|
||||
'checking-for-updates': 'Проверка за актуализации...',
|
||||
'check-for-updates-title': 'Проверка за актуализации',
|
||||
'update-available-message': 'Новата версия на Motrix е достъпна за изтегляне, Изтеглете сега?',
|
||||
'update-not-available-message': 'Вече използвате най-новата версия!',
|
||||
'update-downloaded-message': 'Готово за инсталиране...',
|
||||
'update-error-message': 'Грешка при обновяване',
|
||||
'engine-damaged-message': 'Програмата е повредена, моля преинсталирайте :(',
|
||||
'engine-missing-message': 'Програмата е загубена, моля преинсталирайте :(',
|
||||
'system-error-title': 'Грешка',
|
||||
'system-error-message': 'Грешка при стартиране на приложението: {{message}}',
|
||||
'hide': 'Скрия Motrix',
|
||||
'hide-others': 'Скрийте всичко останало',
|
||||
'unhide': 'Показване на всички',
|
||||
'show': 'Показване Motrix',
|
||||
'quit': 'Затваряне Motrix',
|
||||
'under-development-message': 'За съжаление тази функция все още е в процес на разработка...',
|
||||
'yes': 'Да',
|
||||
'no': 'Не',
|
||||
'cancel': 'Отказ',
|
||||
'submit': 'Потвърдя',
|
||||
'gt1d': '> 1 ден',
|
||||
'hour': 'ч',
|
||||
'minute': 'м',
|
||||
'second': 'с'
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
export default {
|
||||
'undo': 'Отказ',
|
||||
'redo': 'Повторя',
|
||||
'cut': 'Нарежа',
|
||||
'copy': 'Копирам',
|
||||
'paste': 'Поставя',
|
||||
'delete': 'Премахна',
|
||||
'select-all': 'Изберете всички'
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
export default {
|
||||
'official-website': 'Сайт Motrix',
|
||||
'manual': 'Инструкция',
|
||||
'release-notes': 'Маркировки...',
|
||||
'report-problem': 'Докладвайте за проблем',
|
||||
'toggle-dev-tools': 'Превключване на инструменти за разработчици'
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import about from './about'
|
||||
import app from './app'
|
||||
import edit from './edit'
|
||||
import help from './help'
|
||||
import menu from './menu'
|
||||
import preferences from './preferences'
|
||||
import subnav from './subnav'
|
||||
import task from './task'
|
||||
import window from './window'
|
||||
|
||||
export default {
|
||||
about,
|
||||
app,
|
||||
edit,
|
||||
help,
|
||||
menu,
|
||||
preferences,
|
||||
subnav,
|
||||
task,
|
||||
window
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
export default {
|
||||
'app': 'Motrix',
|
||||
'file': 'Файл',
|
||||
'task': 'Задача',
|
||||
'edit': 'Редктиране',
|
||||
'window': 'Прозорец',
|
||||
'help': 'Грижа'
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
export default {
|
||||
'basic':'Основа',
|
||||
'advanced':'Разширени',
|
||||
'lab':'Лаборатория',
|
||||
'save': 'Съхронване и прилагане',
|
||||
'save-success-message':'настройките са запазени успешно',
|
||||
'save-fail-message': 'грешка при запазване на настройките',
|
||||
'discard': 'Отказ',
|
||||
'startup': 'стартиране',
|
||||
'open-at-login': 'стартиране на програмата заедно със стартирането на операционната система',
|
||||
'keep-window-state':'по време на затваряне на приложението, Запишете розмера и позицията на прозореца',
|
||||
'auto-resume-all':'автоматично възобновяване на всички недовършени задачи',
|
||||
'default-dir':'пея по подразбиране',
|
||||
'mas-default-dir-tips': 'поради ограничения в App Store, препоръчително е да зададете пътя по подразбиране като ~/Downloads',
|
||||
'transfer-settings':'скоростна кутия',
|
||||
'transfer-speed-upload': 'лимит на откат',
|
||||
'transfer-speed-download': 'лимит за изтегляне',
|
||||
'transfer-speed-unlimited':'Unlimited',
|
||||
'task-manage':'мениджър на задачи',
|
||||
'max-concurrent-downloads': 'Максимум активни задачи',
|
||||
'max-connection-per-server': 'максимални връзки към сървъра',
|
||||
'new-task-show-downloading': 'автоматично показване на задача след добавяне',
|
||||
'no-confirm-before-delete-task': 'Не се изисква потвърждение преди изтриване на задача',
|
||||
'continue':'Продължи',
|
||||
'task-completed-notify': 'съобщение след изтеглянето',
|
||||
'auto-purge-record': 'автоматично почистване на записите за изтегляне след затваряне на приложението',
|
||||
'ui': 'UI',
|
||||
'appearance': 'външен вид',
|
||||
'theme-auto':'автоматично',
|
||||
'theme-light':'Светло',
|
||||
'theme-dark':'dark',
|
||||
'run-mode': 'Бягай като',
|
||||
'auto-hide-window': 'Автоматично отваряне на прозорци',
|
||||
'run-mode-standard': 'стандартно приложение',
|
||||
'run-mode-menu-bar': 'лента с менюта на приложението',
|
||||
'language':'Език',
|
||||
'change-language': 'промяна на езика',
|
||||
'hide-app-menu': 'Скриване на менюто на приложението (само за Windows и Linux)',
|
||||
'proxy': 'Proxy',
|
||||
'use-proxy': 'използване на Proxy',
|
||||
'no-proxy-input-tips': 'заобикаляне на настройките на прокси за тези хостове и домейни, един по един на ред',
|
||||
'proxy-tips': 'преглед на ръководството за прокси',
|
||||
'bt-tracker':'Tracker сървър',
|
||||
'bt-tracker-input-tips': 'Tracker сървър, един на ред',
|
||||
'bt-tracker-tips': 'препоръчително:',
|
||||
'sync-tracker-tips':'Синхронизация',
|
||||
'auto-sync-tracker':'актуализиране на списъка с тракери всеки ден автоматично',
|
||||
'port':'портове за слушане',
|
||||
'bt-port':'пристанище на слушане BT',
|
||||
'dht-port':'DHT слушане Порт',
|
||||
'security':'сигурност',
|
||||
'rpc-secret': 'RPC Secret',
|
||||
'rpc-secret-tips': 'Гледайте инструкцията RPC Secret',
|
||||
'developer':'developer',
|
||||
'Mock-user-agent':'оформление User-Agent',
|
||||
'app-log-path': 'път към дневника на приложението',
|
||||
'download-session-path': 'качване на пътя на сесията',
|
||||
'factory-reset': 'Настройки по подразбиране',
|
||||
'factory-reset-confirm': 'Сигурни ли сте, че искате да се върнете към настройките по подразбиране?',
|
||||
'lab-warning': '️ ️ включването на функциите на лабораторията може да доведе до срив на приложението и загуба на данни, решете на свой риск!',
|
||||
'download-protocol':'протоколи',
|
||||
'protocols-default-client':'Задаване като клиент по подразбиране за следните протоколи',
|
||||
'protocols-magnet': 'Magnet [ magnet:// ]',
|
||||
'protocols-thunder': 'Thunder [ thunder:// ]',
|
||||
'browser-extensions': 'Розширения',
|
||||
'baidu-exporter': 'BaiduExporter',
|
||||
'browser-extensions-tips': 'предоставени от общността,',
|
||||
'baidu-exporter-help': 'Кликнете тук, за да използвате',
|
||||
'auto-update':'автоматично обновяване',
|
||||
'auto-check-update':'автоматична проверка на актуализациите',
|
||||
'last-check-update-time': 'последната актуализация е проверена'
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export default {
|
||||
'task-list':'Задачи',
|
||||
'preferences':'Настройки'
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
export default {
|
||||
'active':'Активен',
|
||||
'waiting':'чакане',
|
||||
'stopped':'спряно',
|
||||
'new-task': 'нова задача',
|
||||
'new-bt-task': 'Нова BT задача',
|
||||
'open-file': 'отваряне на торент файл...',
|
||||
'uri-task': 'URL',
|
||||
'torrent-task': 'Torrent',
|
||||
'uri-task-tips': 'една URL задача в низ (поддръжка на magnet)',
|
||||
'thunder-link-tips': 'съвет: връзките от типа Thunder може да не се зареждат след декодиране',
|
||||
'new-task-uris-required': 'въведете валиден URL адрес на ресурс',
|
||||
'new-task-torrent-required': 'моля, изберете торент файл',
|
||||
'file-name': 'Име на файл',
|
||||
'file-extension':'тип файл',
|
||||
'file-size': 'файлов Розмер',
|
||||
'selected-files-sum': 'избрано: {{selectedFilesCount}} файлове, общ размер {{selectedFilesTotalSize}}',
|
||||
'task-name':'Име на изтегляне',
|
||||
'task-out': 'Преименуване',
|
||||
'task-out-tips':'незадължителен',
|
||||
'task-split': 'разделяне',
|
||||
'task-dir': 'Запазване в',
|
||||
'pause-task': 'пауза на задачата',
|
||||
'task-ua': 'UA',
|
||||
'task-user-agent': 'User-Agent',
|
||||
'task-referer': 'препращане',
|
||||
'task-cookie': 'Cookie',
|
||||
'task-proxy': 'Proxy',
|
||||
'navigate-to-downloading': 'напред към изтегляне',
|
||||
'show-advanced-options': 'Разширени опции',
|
||||
'copyright-warning':'предупреждение за авторски права',
|
||||
'copyright-warning-message': 'файлът, който се опитвате да изтеглите, има авторски права върху видео или аудио съдържание, моля, проверете дали имате права да изтеглите този файл.',
|
||||
'copyright-yes': 'Да, имам права',
|
||||
'copyright-no': 'Не, нямам права',
|
||||
'copyright-error-message': 'грешка при добавяне на задача поради проблеми с авторските права',
|
||||
'pause-task-success': 'успешно спряна задача" {{TaskName}}"',
|
||||
'pause-task-fail': 'грешка при спиране на задачата" {{taskName}}"',
|
||||
'resume-task': 'възобновяване на задачата',
|
||||
'resume-task-success': 'успешно възобновена задача" {{TaskName}}"',
|
||||
'resume-task-fail': 'грешка при възобновяване на задачата" {{taskName}}"',
|
||||
'delete-task': 'изтриване на задача',
|
||||
'delete-selected-tasks': 'изтриване на избраните задачи',
|
||||
'delete-task-confirm': 'сигурни ли Сте, че искате да изтриете задачата "{{taskName}}"?',
|
||||
'batch-delete-task-confirm': 'Сигурни ли сте, че искате да изтриете {{count}} задачи за зареждане в партиден режим?',
|
||||
'delete-task-label': 'изтриване заедно с файловете',
|
||||
'delete-task-success': 'успешно изтрита задача" {{TaskName}}"',
|
||||
'delete-task-fail': 'грешка при изтриване на задача" {{taskName}}"',
|
||||
'remove-task-file-fail': 'грешка при изтриване на файл (файлове) на задача, моля, изтрийте го (тях) сами',
|
||||
'remove-task-config-file-fail': 'грешка при изтриване на конфигурационния файл на заданието, моля, изтрийте го сами',
|
||||
'move-task-up': 'Преместване на задача нагоре',
|
||||
'move-task-down': 'Преместване на задача надолу',
|
||||
'pause-all-task': 'пауза на всички задачи',
|
||||
'pause-all-task-success': 'всички задачи са успешно прекратени',
|
||||
'pause-all-task-fail': 'грешка при спиране на всички задачи',
|
||||
'resume-all-task': 'възобновяване на всички задачи',
|
||||
'resume-all-task-success': 'успешно възобновени всички задачи',
|
||||
'resume-all-task-fail': 'грешка при възобновяване на всички задачи',
|
||||
'select-all-task': 'Изберете всички задачи',
|
||||
'clear-recent-tasks': 'Изчистване на последните задачи',
|
||||
'purge-record': 'Изчистване на записките за задачи',
|
||||
'purge-record-success': 'успешно изчистени записи на задачи',
|
||||
'purge-record-fail': 'грешка при изчистване на записите за задачи',
|
||||
'batch-delete-task-success': 'успешно изтриване на задачи в партиден режим',
|
||||
'batch-delete-task-fail': 'Неуспешно изтриване на задачи в партиден режим',
|
||||
'refresh-list': 'обновяване на списъка със задачи',
|
||||
'no-task': 'няма текущи задачи',
|
||||
'copy-link': 'копиране на връзка',
|
||||
'copy-link-success': 'успешно копирана връзка',
|
||||
'remove-record': 'изтриване на запис за задача',
|
||||
'remove-record-confirm': 'Сигурни ли сте, че искате да изтриете записа за задачата "{{taskName}}"?',
|
||||
'remove-record-label':'изтриване с файлове',
|
||||
'remove-record-success': 'успешно изтрит запис за задача" {{taskName}}"',
|
||||
'remove-record-fail': 'грешка при изтриване на запис за задача "{{taskName}}"',
|
||||
'show-in-folder': 'показване на файловете със задачи в папка',
|
||||
'file-not-exist': 'търсеният файл не съществува или е изтрит',
|
||||
'file-path-error': 'Грешка в пътя към файла',
|
||||
'opening-task-message': 'отваряне "{{TaskName}}" ...',
|
||||
'get-task-name': 'получаване на име на задача...',
|
||||
'remaining-prefix':'ляво',
|
||||
'select-torrent':'плъзнете торент файла тук, или натиснете Избери',
|
||||
'task-info-dialog-title':'{{Title}} детайли',
|
||||
'download-start-message': 'изтеглянето започна {{taskName}}',
|
||||
'download-pause-message': 'спиране на изтеглянето {{taskName}}',
|
||||
'download-stop-message': 'спиране на изтеглянето {{taskName}}',
|
||||
'download-error-message': 'грешка при изтегляне {{taskName}}',
|
||||
'download-complete-message': 'Завършено изтегляне {{taskName}}',
|
||||
'download-complete-notify':'изтеглянето Завършено',
|
||||
'BT-download-complete-message': 'завършено изтегляне {{TaskName}}, раздаване',
|
||||
'BT-download-complete-notify': 'BT изтеглянето приключи, раздаване...',
|
||||
'BT-download-complete-tips': 'съвет: можете да спрете задачата, за да спрете раздаването',
|
||||
'download-fail-message': 'не може да бъде изтеглено {{taskName}}',
|
||||
'download-fail-notify': 'грешка при зареждане'
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
export default {
|
||||
'reload': 'Перезагрузить',
|
||||
'close': 'Закрыть',
|
||||
'minimize': 'Свернуть',
|
||||
'zoom': 'Увеличение',
|
||||
'toggle-fullscreen': 'Перейти в полноэкранный режим',
|
||||
'front': 'Поверх всех окон'
|
||||
}
|
||||
@@ -20,6 +20,7 @@ export default {
|
||||
'max-concurrent-downloads': 'Tasques màximes actives',
|
||||
'max-connection-per-server': 'Connexions màximes per servidor',
|
||||
'new-task-show-downloading': 'Mostrar automàticament la descàrrega després d\'afegir una tasca',
|
||||
'no-confirm-before-delete-task': 'No cal confirmar abans de suprimir la tasca',
|
||||
'continue': 'Continuar',
|
||||
'task-completed-notify': 'Notificar després que la descàrrega finalitzi',
|
||||
'auto-purge-record': 'Purgar automàticament el registre de descàrregues en sortir',
|
||||
|
||||
@@ -20,6 +20,7 @@ export default {
|
||||
'max-concurrent-downloads': 'Maximal aktive Aufgaben',
|
||||
'max-connection-per-server': 'Maximale Verbindungen pro Server',
|
||||
'new-task-show-downloading': 'Nach hinzufügen einer Aufgabe zu aktiven Downloads wechseln',
|
||||
'no-confirm-before-delete-task': 'Vor dem Löschen der Aufgabe ist keine Bestätigung erforderlich',
|
||||
'continue': 'HTTPS/FTP Downloads fortsetzen wenn bereits angefangen',
|
||||
'task-completed-notify': 'Benachrichtigung nach abgeschlossenen Download anzeigen',
|
||||
'auto-purge-record': 'Download Protokoll beim Schließen der App löschen',
|
||||
|
||||
@@ -20,6 +20,7 @@ export default {
|
||||
'max-concurrent-downloads': 'Maximum active tasks',
|
||||
'max-connection-per-server': 'Maximum connection per server',
|
||||
'new-task-show-downloading': 'Automatically show downloading after adding task',
|
||||
'no-confirm-before-delete-task': 'No confirmation is required before deleting task',
|
||||
'continue': 'Continue',
|
||||
'task-completed-notify': 'Notification after download is complete',
|
||||
'auto-purge-record': 'Automatically purge download records when exiting app',
|
||||
|
||||
@@ -20,6 +20,7 @@ export default {
|
||||
'max-concurrent-downloads': 'Tareas máximas activas',
|
||||
'max-connection-per-server': 'Conexiones máximas por servidor',
|
||||
'new-task-show-downloading': 'Mostrar automáticamente la descarga después de añadir una tarea',
|
||||
'no-confirm-before-delete-task': 'No se requiere confirmación antes de eliminar la tarea',
|
||||
'continue': 'Continuar',
|
||||
'task-completed-notify': 'Notificar después de que la descarga se complete',
|
||||
'auto-purge-record': 'Eliminar automáticamente el registro de descargas al salir',
|
||||
|
||||
@@ -20,6 +20,7 @@ export default {
|
||||
'max-concurrent-downloads': 'حداکثر تسکهای فعال',
|
||||
'max-connection-per-server': 'حداکثر اتصال برای هر سرور',
|
||||
'new-task-show-downloading': 'بعد افزودن تسک به صورت اتوماتیک دانلود را نشان بده',
|
||||
'no-confirm-before-delete-task': 'قبل از حذف کار نیازی به تأیید نیست',
|
||||
'continue': 'ادامه',
|
||||
'task-completed-notify': 'اعلان پس از اتمام دانلود',
|
||||
'auto-purge-record': 'سابقهی دانلود را اتوماتیک هنگام خروج پاک کن',
|
||||
|
||||
@@ -20,6 +20,7 @@ export default {
|
||||
'max-concurrent-downloads': 'Nombre de tâches active au maximum',
|
||||
'max-connection-per-server': 'Nombre maximum de connexions par serveurs',
|
||||
'new-task-show-downloading': 'Montrer automatiquement les téléchargements après l\'ajout d\'une tâche',
|
||||
'no-confirm-before-delete-task': 'Aucune confirmation n\'est requise avant de supprimer la tâche',
|
||||
'continue': 'Continuer',
|
||||
'task-completed-notify': 'Notifier à la fin d\'un téléchargement',
|
||||
'auto-purge-record': 'Purger l\'historique de téléchargement lorsque vous quittez l\'application',
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
export default {
|
||||
'engine-version': 'Versi Mesin',
|
||||
'license': 'Lisensi',
|
||||
'about': 'Tentang',
|
||||
'release': 'Rilis',
|
||||
'support': 'Bantuan'
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
export default {
|
||||
'task-list': 'Daftar Tugas',
|
||||
'add-task': 'Tambah Tugas',
|
||||
'about': 'Tentang Motrix',
|
||||
'preferences': 'Preferensi...',
|
||||
'check-for-updates': 'Periksa Pembaruan...',
|
||||
'check-updates-now': 'Periksa Sekarang',
|
||||
'checking-for-updates': 'Memeriksa pembaruan...',
|
||||
'check-for-updates-title': 'Periksa Pembaruan',
|
||||
'update-available-message': 'Versi Motrix terbaru telah tersedia, perbarui sekarang?',
|
||||
'update-not-available-message': 'Aplikasi dalam kondisi ter-update!',
|
||||
'update-downloaded-message': 'siap meng-install...',
|
||||
'update-error-message': 'Update Gagal',
|
||||
'engine-damaged-message': 'Mesin rusak, silahkan install ulang : (',
|
||||
'engine-missing-message': 'Mesin hilang, silahkan install ulang : (',
|
||||
'system-error-title': 'System Error',
|
||||
'system-error-message': 'Gagal Menjalankan Aplikasi: {{message}}',
|
||||
'hide': 'Sembungikan Motrix',
|
||||
'hide-others': 'Sembunyikan yang lain',
|
||||
'unhide': 'Tunjukan Semua',
|
||||
'show': 'Tunjukan Motrix',
|
||||
'quit': 'Keluarkan Motrix',
|
||||
'under-development-message': 'Maaf, fitur ini dalam tahap development...',
|
||||
'yes': 'Ya',
|
||||
'no': 'Tidak',
|
||||
'cancel': 'Batal',
|
||||
'submit': 'Kirim',
|
||||
'gt1d': '> 1 hari',
|
||||
'hour': 'h',
|
||||
'minute': 'm',
|
||||
'second': 's'
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
export default {
|
||||
'undo': 'Urungkan',
|
||||
'redo': 'Ulangi',
|
||||
'cut': 'Potong',
|
||||
'copy': 'Salin',
|
||||
'paste': 'Tempel',
|
||||
'delete': 'Hapus',
|
||||
'select-all': 'Pilih Semua'
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
export default {
|
||||
'official-website': 'Motrix Website',
|
||||
'manual': 'Panduan',
|
||||
'release-notes': 'Catatan Rilis...',
|
||||
'report-problem': 'Laporkan Masalah',
|
||||
'toggle-dev-tools': 'Alihkan Alat Pengembang'
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import about from './about'
|
||||
import app from './app'
|
||||
import edit from './edit'
|
||||
import help from './help'
|
||||
import menu from './menu'
|
||||
import preferences from './preferences'
|
||||
import subnav from './subnav'
|
||||
import task from './task'
|
||||
import window from './window'
|
||||
|
||||
export default {
|
||||
about,
|
||||
app,
|
||||
edit,
|
||||
help,
|
||||
menu,
|
||||
preferences,
|
||||
subnav,
|
||||
task,
|
||||
window
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
export default {
|
||||
'app': 'Motrix',
|
||||
'file': 'Berlas',
|
||||
'task': 'Tugas',
|
||||
'edit': 'Edit',
|
||||
'window': 'Window',
|
||||
'help': 'Bantuan'
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
export default {
|
||||
'basic': 'Pengaturan Dasar',
|
||||
'advanced': 'Pengaturan Lanjut',
|
||||
'lab': 'Lab',
|
||||
'save': 'Simpan & Terapkan',
|
||||
'save-success-message': 'Berhasil menyimpan pengaturan',
|
||||
'save-fail-message': 'Gagal menyimpan pengaturan',
|
||||
'discard': 'Batal',
|
||||
'startup': 'Memulai',
|
||||
'open-at-login': 'Buka saat login',
|
||||
'keep-window-state': 'Pertahankan ukuran dan posisi jendela aplikasi saat keluar',
|
||||
'auto-resume-all': 'Otomatis lanjutkan semua tugas yang belum selesai',
|
||||
'default-dir': 'Lokasi Bawaan',
|
||||
'mas-default-dir-tips': 'Karena pembatasan dari App Store, direktori unduhan default direkomendasikan untuk disetel ke ~/Downloads',
|
||||
'transfer-settings': 'Transfer Setting',
|
||||
'transfer-speed-upload': 'Limit Unggah',
|
||||
'transfer-speed-download': 'Limit Unduh',
|
||||
'transfer-speed-unlimited': 'Tak Terbatas',
|
||||
'task-manage': 'Pengelola Tugas',
|
||||
'max-concurrent-downloads': 'Maksimal tugas aktif',
|
||||
'max-connection-per-server': 'Maksimal koneksi per server',
|
||||
'new-task-show-downloading': 'Tampilkan pengunduhan secara otomatis setelah menambahkan tugas',
|
||||
'no-confirm-before-delete-task': 'Konfirmasi tidak diperlukan sebelum menghapus tugas',
|
||||
'continue': 'Lanjutkan',
|
||||
'task-completed-notify': 'Pemberitahuan setelah pengunduhan selesai',
|
||||
'auto-purge-record': 'Otomatis bersihkan catatan unduhan saat keluar dari aplikasi',
|
||||
'ui': 'UI',
|
||||
'appearance': 'Penampilan',
|
||||
'theme-auto': 'Auto',
|
||||
'theme-light': 'Terang',
|
||||
'theme-dark': 'Gelap',
|
||||
'run-mode': 'Jalankan Sebagai',
|
||||
'auto-hide-window': 'Sembunyikan Otomatis Jendela',
|
||||
'run-mode-standard': 'Aplikasi Standar',
|
||||
'run-mode-menu-bar': 'Aplikasi Menu Bar',
|
||||
'language': 'Bahasa',
|
||||
'change-language': 'Ubah Bahasa',
|
||||
'hide-app-menu': 'Sembunyikan Menu Aplikasi (hanya: Windows & Linux)',
|
||||
'proxy': 'Proxy',
|
||||
'use-proxy': 'Aktifkan Proxy',
|
||||
'no-proxy-input-tips': 'Abaikan pengaturan proxy untuk Host dan Domain ini, satu per baris',
|
||||
'proxy-tips': 'Lihat Manual Proxy',
|
||||
'bt-tracker': 'Server Pelacak',
|
||||
'bt-tracker-input-tips': 'Server pelacak, satu per baris',
|
||||
'bt-tracker-tips': 'Direkomendasikan: ',
|
||||
'sync-tracker-tips': 'Sinkronkan',
|
||||
'auto-sync-tracker': 'Perbarui daftar pelacak setiap hari secara otomatis',
|
||||
'port': 'Dengarkan Ports',
|
||||
'bt-port': 'Dengarkan Port BT',
|
||||
'dht-port': 'Dengarkan Port DHT',
|
||||
'security': 'Keamanan',
|
||||
'rpc-secret': 'RPC Secret',
|
||||
'rpc-secret-tips': 'Lihat Petunjuk RPC Secret',
|
||||
'developer': 'Developer',
|
||||
'mock-user-agent': 'Mock User-Agent',
|
||||
'app-log-path': 'Lokasi Log Aplikasi',
|
||||
'download-session-path': 'Lokasi Session Unduhan',
|
||||
'factory-reset': 'Reset Pabrik',
|
||||
'factory-reset-confirm': 'Anda yakin ingin kembali ke pengaturan pabrik?',
|
||||
'lab-warning': 'Mengaktifkan fitur lab dapat menyebabkan aplikasi tidak berjalan semestinya atau kehilangan data, risiko ditanggung Anda sendiri!',
|
||||
'download-protocol': 'Protocols',
|
||||
'protocols-default-client': 'Tetapkan sebagai klien untuk Protocol berikut',
|
||||
'protocols-magnet': 'Magnet [ magnet:// ]',
|
||||
'protocols-thunder': 'Thunder [ thunder:// ]',
|
||||
'browser-extensions': 'Ekstensi',
|
||||
'baidu-exporter': 'BaiduExporter',
|
||||
'browser-extensions-tips': 'Disediakan oleh komunitas, ',
|
||||
'baidu-exporter-help': 'Klik di sini untuk penggunaan',
|
||||
'auto-update': 'Pembaruan Otomatis',
|
||||
'auto-check-update': 'Secara otomatis memeriksa pembaruan',
|
||||
'last-check-update-time': 'Terakhir Kali Memeriksa Pembaruan'
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export default {
|
||||
'task-list': 'Daftar Tugas',
|
||||
'preferences': 'Pengaturan'
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
export default {
|
||||
'active': 'Mengunduh',
|
||||
'waiting': 'Mengunggu',
|
||||
'stopped': 'Terhenti',
|
||||
'new-task': 'Tugas Baru',
|
||||
'new-bt-task': 'Tugas BT baru',
|
||||
'open-file': 'Buka Berkas Torrent...',
|
||||
'uri-task': 'URL',
|
||||
'torrent-task': 'Torrent',
|
||||
'uri-task-tips': 'Satu tugas per baris (mendukung magnet)',
|
||||
'thunder-link-tips': 'Tip: Thunder tautan mungkin tidak dapat diunduh setelah decoding',
|
||||
'new-task-uris-required': 'Silakan masukkan setidaknya satu url yang valid',
|
||||
'new-task-torrent-required': 'Silahkan pilih berkas torrent',
|
||||
'file-name': 'Nama Berkas',
|
||||
'file-extension': 'Tipe Berkas',
|
||||
'file-size': 'Ukuran Berkas',
|
||||
'selected-files-sum': 'Terpilih: {{selectedFilesCount}} berkas, total ukuran {{selectedFilesTotalSize}}',
|
||||
'task-name': 'Nama Tugas',
|
||||
'task-out': 'Ubah Nama',
|
||||
'task-out-tips': 'Opsional',
|
||||
'task-split': 'Pecahan',
|
||||
'task-dir': 'Simpan Ke',
|
||||
'pause-task': 'Tunda Tugas',
|
||||
'task-ua': 'UA',
|
||||
'task-user-agent': 'User-Agent',
|
||||
'task-referer': 'Referer',
|
||||
'task-cookie': 'Cookie',
|
||||
'task-proxy': 'Proxy',
|
||||
'navigate-to-downloading': 'Beralih ke Unduhan',
|
||||
'show-advanced-options': 'Setting Lanjutan',
|
||||
'copyright-warning': 'Peringatan Hak Cipta',
|
||||
'copyright-warning-message': 'File yang ingin Anda unduh mungkin berupa audio atau video yang dilindungi hak cipta, pastikan Anda memiliki izin untuk mengaksesnya.',
|
||||
'copyright-yes': 'Ya, Saya punya izin',
|
||||
'copyright-no': 'Tidak, Saya tidak punya izin',
|
||||
'copyright-error-message': 'Gagal menambahkan tugas karena masalah hak cipta',
|
||||
'pause-task-success': 'Tugas berhasil ditunda "{{taskName}}"',
|
||||
'pause-task-fail': 'Gagal menunda tugas "{{taskName}}"',
|
||||
'resume-task': 'Lanjutkan tugas',
|
||||
'resume-task-success': 'Berhasil melanjutkan tugas "{{taskName}}"',
|
||||
'resume-task-fail': 'Gagal melanjutkan tugas "{{taskName}}"',
|
||||
'delete-task': 'Hapus tugas',
|
||||
'delete-selected-tasks': 'Hapus tugas terpilih',
|
||||
'delete-task-confirm': 'Anda yakin ingin menghapus tugas unduhan "{{taskName}}"?',
|
||||
'batch-delete-task-confirm': 'Anda yakin ingin menghapus {{count}} tugas unduhan (batch)?',
|
||||
'delete-task-label': 'Hapus dengan File',
|
||||
'delete-task-success': 'Tugas "{{taskName}}" berhasil dihapus',
|
||||
'delete-task-fail': 'Tugas "{{taskName}}" gagal dihapus',
|
||||
'remove-task-file-fail': 'Gagal menghapus berkas tugas, silahkan hapus secara manual',
|
||||
'remove-task-config-file-fail': 'Gagal menghapus pengaturan berkas tugas, silahkan hapus secara manual',
|
||||
'move-task-up': 'Pidahkan Tugas ke Atas',
|
||||
'move-task-down': 'Pidahkan Tugas ke Bawah',
|
||||
'pause-all-task': 'Tunda Semua Tugas',
|
||||
'pause-all-task-success': 'Berhasil menunda semua tugas',
|
||||
'pause-all-task-fail': 'Gagal menunda semua tugas',
|
||||
'resume-all-task': 'Lanjutkan Semua Tugas',
|
||||
'resume-all-task-success': 'Berhasil melanjutkan semua tugas',
|
||||
'resume-all-task-fail': 'Gagal melanjutkan semua tugas',
|
||||
'select-all-task': 'Pilih Semua Tugas',
|
||||
'clear-recent-tasks': 'Bersihkan tugas terakhir',
|
||||
'purge-record': 'Bersihkan Catatan Tugas',
|
||||
'purge-record-success': 'Berhasil membersihkan catatan tugas',
|
||||
'purge-record-fail': 'Gagal membersihkan catatan tugas',
|
||||
'batch-delete-task-success': 'Berhasil menghapus tugas (batch)',
|
||||
'batch-delete-task-fail': 'Gagal menghapus tugas (batch)',
|
||||
'refresh-list': 'Muat Ulang Daftar',
|
||||
'no-task': 'Tidak ada tugas',
|
||||
'copy-link': 'Salin Link',
|
||||
'copy-link-success': 'Berhasil menyalin link',
|
||||
'remove-record': 'Hapus Data Tugas',
|
||||
'remove-record-confirm': 'Anda yakin ingin menghapus data unduhan "{{taskName}}"?',
|
||||
'remove-record-label': 'Hapus dengan Berkas',
|
||||
'remove-record-success': 'Catatan tugas berhasil dihapus untuk "{{taskName}}"',
|
||||
'remove-record-fail': 'Gagal menghapus catatan tugas untuk "{{taskName}}"',
|
||||
'show-in-folder': 'Tampilkan Tugas Di Folder',
|
||||
'file-not-exist': 'Berkas target tidak ada atau telah dihapus',
|
||||
'file-path-error': 'Lokasi berkas error',
|
||||
'opening-task-message': 'Membuka "{{taskName}}" ...',
|
||||
'get-task-name': 'Mendapatkan nama tugas...',
|
||||
'remaining-prefix': 'Tersisa',
|
||||
'select-torrent': 'Seret berkas torrent ke sini, atau klik untuk memilih',
|
||||
'task-info-dialog-title': '{{title}} Detail',
|
||||
'download-start-message': 'Memulai mengunduh {{taskName}}',
|
||||
'download-pause-message': 'Menunda mengunduh {{taskName}}',
|
||||
'download-stop-message': 'Berhenti mengunduh {{taskName}}',
|
||||
'download-error-message': 'Terjadi kesalahan saat mengunduh {{taskName}}',
|
||||
'download-complete-message': 'Selesai mengunduh {{taskName}}',
|
||||
'download-complete-notify': 'Unduh Selesai',
|
||||
'bt-download-complete-message': 'Selesai mengunduh {{taskName}}, penyemaian',
|
||||
'bt-download-complete-notify': 'BT Unduh Selesai, penyemaian...',
|
||||
'bt-download-complete-tips': 'Tips: Anda dapat menghentikan tugas untuk mengakhiri penyemaian',
|
||||
'download-fail-message': 'Gagal mengunduh {{taskName}}',
|
||||
'download-fail-notify': 'Unduhan Gagal'
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
export default {
|
||||
'reload': 'Muat Ulang',
|
||||
'close': 'Keluar',
|
||||
'minimize': 'Perkecil',
|
||||
'zoom': 'Zoom',
|
||||
'toggle-fullscreen': 'Layar penuh',
|
||||
'front': 'Bawa Semua ke Depan'
|
||||
}
|
||||
@@ -6,6 +6,10 @@
|
||||
* Please keep the locale key in alphabetical order.
|
||||
*/
|
||||
export const availableLanguages = [
|
||||
{
|
||||
value: 'bg',
|
||||
label: 'Българският език'
|
||||
},
|
||||
{
|
||||
value: 'ca',
|
||||
label: 'Català'
|
||||
@@ -30,6 +34,10 @@ export const availableLanguages = [
|
||||
value: 'fr',
|
||||
label: 'Français'
|
||||
},
|
||||
{
|
||||
value: 'id',
|
||||
label: 'Indonesia'
|
||||
},
|
||||
{
|
||||
value: 'ja',
|
||||
label: '日本語'
|
||||
@@ -50,6 +58,10 @@ export const availableLanguages = [
|
||||
value: 'tr',
|
||||
label: 'Türkçe'
|
||||
},
|
||||
{
|
||||
value: 'vi',
|
||||
label: 'Tiếng Việt'
|
||||
},
|
||||
{
|
||||
value: 'zh-CN',
|
||||
label: '简体中文'
|
||||
|
||||
@@ -20,6 +20,7 @@ export default {
|
||||
'max-concurrent-downloads': '最大同時タスク数',
|
||||
'max-connection-per-server': '最大サーバ接続数',
|
||||
'new-task-show-downloading': '新規タスクを作成後自動的にタスク画面に移る',
|
||||
'no-confirm-before-delete-task': 'タスクを削除する前に確認は必要ありません',
|
||||
'continue': '続ける',
|
||||
'task-completed-notify': 'タスク完了後に通知する',
|
||||
'auto-purge-record': 'アプリケーション終了後自動的にタスク履歴を削除',
|
||||
|
||||
@@ -20,6 +20,7 @@ export default {
|
||||
'max-concurrent-downloads': '최대 활성 작업',
|
||||
'max-connection-per-server': '서버당 최대 연결 수',
|
||||
'new-task-show-downloading': '작업 추가 후 다운로드 자동 표시',
|
||||
'no-confirm-before-delete-task': '작업을 삭제하기 전에 확인이 필요하지 않습니다',
|
||||
'continue': '계속',
|
||||
'task-completed-notify': '다운로드 완료 후 알림',
|
||||
'auto-purge-record': '앱 종료 시 다운로드 기록 자동 삭제',
|
||||
|
||||
@@ -20,6 +20,7 @@ export default {
|
||||
'max-concurrent-downloads': 'Máximo de tarefas ativas',
|
||||
'max-connection-per-server': 'Máximo de coneções por servidor',
|
||||
'new-task-show-downloading': 'Auto exibir progresso depois de adicionar uma tarefa',
|
||||
'no-confirm-before-delete-task': 'Nenhuma confirmação é necessária antes de excluir a tarefa',
|
||||
'continue': 'Continuar',
|
||||
'task-completed-notify': 'Notificação após o download ser completado',
|
||||
'auto-purge-record': 'Auto remover registro de download quando o app for finalizado',
|
||||
|
||||
@@ -11,8 +11,8 @@ export default {
|
||||
'update-not-available-message': 'Вы уже используете самую последнюю версию!',
|
||||
'update-downloaded-message': 'Готово к установке...',
|
||||
'update-error-message': 'Ошибка обновления',
|
||||
'engine-damaged-message': 'Движек поврежден, пожалуйста переустановите : (',
|
||||
'engine-missing-message': 'Движек потерян, пожалуйста переустановите : (',
|
||||
'engine-damaged-message': 'Движок поврежден. Пожалуйста, переустановите его : (',
|
||||
'engine-missing-message': 'Движок потерян. Пожалуйста, переустановите его : (',
|
||||
'system-error-title': 'Системная ошибка',
|
||||
'system-error-message': 'Ошибка запуска приложения: {{message}}',
|
||||
'hide': 'Спрятать Motrix',
|
||||
|
||||
@@ -2,7 +2,7 @@ export default {
|
||||
'app': 'Motrix',
|
||||
'file': 'Файл',
|
||||
'task': 'Задания',
|
||||
'edit': 'Редктировать',
|
||||
'edit': 'Редактировать',
|
||||
'window': 'Окно',
|
||||
'help': 'Помощь'
|
||||
}
|
||||
|
||||
@@ -2,15 +2,15 @@ export default {
|
||||
'basic': 'Основные',
|
||||
'advanced': 'Расширенные',
|
||||
'lab': 'Лаборатория',
|
||||
'save': 'Сохронить и Применить',
|
||||
'save': 'Сохранить и применить',
|
||||
'save-success-message': 'Настройки сохранены успешно',
|
||||
'save-fail-message': 'Ошибка при сохранении настроек',
|
||||
'discard': 'Отмена',
|
||||
'startup': 'Запускать',
|
||||
'open-at-login': 'Запускать програму вместе со стартом операционной системы',
|
||||
'keep-window-state': 'Во время закрытия приложения, сохранять розмер и положения окна',
|
||||
'keep-window-state': 'Во время закрытия приложения, сохранять размер и положение окна',
|
||||
'auto-resume-all': 'Автоматически возобновлять все незавершенные задачи',
|
||||
'default-dir': 'Петь по умолчанию',
|
||||
'default-dir': 'Папка по умолчанию',
|
||||
'mas-default-dir-tips': 'Из-за ограничения в App Store, рекомендуется устанавливать путь по умолчанию как ~/Downloads',
|
||||
'transfer-settings': 'коробка передач',
|
||||
'transfer-speed-upload': 'Лимит отдачи',
|
||||
@@ -20,28 +20,29 @@ export default {
|
||||
'max-concurrent-downloads': 'Максимум активных задач',
|
||||
'max-connection-per-server': 'Максимум соединений на сервер',
|
||||
'new-task-show-downloading': 'Автоматически отображать задачу после ее добавления',
|
||||
'no-confirm-before-delete-task': 'Перед удалением задачи подтверждение не требуется',
|
||||
'continue': 'Продолжить',
|
||||
'task-completed-notify': 'Сообщение после окончани загрузки',
|
||||
'task-completed-notify': 'Сообщение после окончания загрузки',
|
||||
'auto-purge-record': 'Автоматически чистить записи о загрузках после закрытия приложения',
|
||||
'ui': 'UI',
|
||||
'appearance': 'Внешний вид',
|
||||
'theme-auto': 'Автоматически',
|
||||
'theme-light': 'Светлый',
|
||||
'theme-dark': 'Темный',
|
||||
'run-mode': 'Беги как',
|
||||
'run-mode': 'Запускать как...',
|
||||
'auto-hide-window': 'Автоскрытие окон',
|
||||
'run-mode-standard': 'Стандартное приложение',
|
||||
'run-mode-menu-bar': 'Панель меню приложения',
|
||||
'language': 'Язык',
|
||||
'change-language': 'Сменить язык',
|
||||
'hide-app-menu': 'Скрыть меню приложения (Только для Windows и Linux)',
|
||||
'hide-app-menu': 'Скрыть меню приложения (только для Windows и Linux)',
|
||||
'proxy': 'Proxy',
|
||||
'use-proxy': 'Использовать Proxy',
|
||||
'no-proxy-input-tips': 'Обойти настройки прокси для этих хостов и доменов, по одному в строке',
|
||||
'proxy-tips': 'Посмотреть руководство по прокси',
|
||||
'bt-tracker': 'Tracker Сервер',
|
||||
'bt-tracker-input-tips': 'Tracker сервера, один в строку',
|
||||
'bt-tracker-tips': 'Рекомендованно: ',
|
||||
'bt-tracker-tips': 'Рекомендовано: ',
|
||||
'sync-tracker-tips': 'Синхронизация',
|
||||
'auto-sync-tracker': 'Обновлять список трекеров каждый день автоматически',
|
||||
'port': 'Порты прослушивания',
|
||||
@@ -56,16 +57,16 @@ export default {
|
||||
'download-session-path': 'Загрузить путь сессии',
|
||||
'factory-reset': 'Настройки по умолчанию',
|
||||
'factory-reset-confirm': 'Вы уверены, что хотите вернуться к настройкам по умолчанию?',
|
||||
'lab-warning': '⚠️ Включения функций лаборатории может привести к сбою приложения и потери данных, решайте на свой риск!',
|
||||
'lab-warning': '⚠️ Включение функций лаборатории может привести к сбоям приложения и потери данных. Вы действуете на свой страх и риск!',
|
||||
'download-protocol': 'Протоколы',
|
||||
'protocols-default-client': 'Установить как клиента по умолчанию для следующих протоколов',
|
||||
'protocols-magnet': 'Magnet [ magnet:// ]',
|
||||
'protocols-thunder': 'Thunder [ thunder:// ]',
|
||||
'browser-extensions': 'Розширения',
|
||||
'browser-extensions': 'Расширения',
|
||||
'baidu-exporter': 'BaiduExporter',
|
||||
'browser-extensions-tips': 'Предоставляются сообществом, ',
|
||||
'baidu-exporter-help': 'Нажмите здесь для использования',
|
||||
'auto-update': 'Автоматическое обновление',
|
||||
'auto-check-update': 'Автоматически проверять обновления',
|
||||
'last-check-update-time': 'В последний раз обновление проверялось'
|
||||
'last-check-update-time': 'Последняя проверка на обновления прошла в'
|
||||
}
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
export default {
|
||||
'active': 'Загрузки',
|
||||
'waiting': 'Ожидание',
|
||||
'stopped': 'Остановленно',
|
||||
'new-task': 'Нове задание',
|
||||
'new-bt-task': 'Нове BT задание',
|
||||
'stopped': 'Остановлено',
|
||||
'new-task': 'Новое задание',
|
||||
'new-bt-task': 'Новое BT задание',
|
||||
'open-file': 'Открыть Torrent файл...',
|
||||
'uri-task': 'URL',
|
||||
'torrent-task': 'Torrent',
|
||||
'uri-task-tips': 'Один URL-задания в строку (поддержка magnet)',
|
||||
'thunder-link-tips': 'Совет: Ссылки типа Thunder могут не загружаться после декодированния',
|
||||
'new-task-uris-required': 'Введите хотябы один действительный URL-адрес ресурса',
|
||||
'new-task-torrent-required': 'Пожалуйста выберите torrent файл',
|
||||
'new-task-uris-required': 'Введите хотя бы один действительный URL-адрес ресурса',
|
||||
'new-task-torrent-required': 'Пожалуйста, выберите torrent файл',
|
||||
'file-name': 'Имя файла',
|
||||
'file-extension': 'Тип файла',
|
||||
'file-size': 'Розмер файла',
|
||||
@@ -19,7 +19,7 @@ export default {
|
||||
'task-out': 'Переименовать',
|
||||
'task-out-tips': 'Необязательный',
|
||||
'task-split': 'Разбить',
|
||||
'task-dir': 'Сохранить в',
|
||||
'task-dir': 'Сохранить как',
|
||||
'pause-task': 'Приостановить задание',
|
||||
'task-ua': 'UA',
|
||||
'task-user-agent': 'User-Agent',
|
||||
@@ -33,10 +33,10 @@ export default {
|
||||
'copyright-yes': 'Да, у меня есть права',
|
||||
'copyright-no': 'Нет, у меня нет прав',
|
||||
'copyright-error-message': 'Ошибка при добавлении задачи из-за проблем с авторскими правами',
|
||||
'pause-task-success': 'Успешно приостановленно задание "{{taskName}}"',
|
||||
'pause-task-fail': 'Ошибка во время приостановленния задания "{{taskName}}"',
|
||||
'resume-task': 'Возобновить Задание',
|
||||
'resume-task-success': 'Успешно возобновленно задание "{{taskName}}"',
|
||||
'pause-task-success': 'Успешно остановлено задание "{{taskName}}"',
|
||||
'pause-task-fail': 'Ошибка во время остановки задания "{{taskName}}"',
|
||||
'resume-task': 'Возобновить задание',
|
||||
'resume-task-success': 'Успешно возобновлено задание "{{taskName}}"',
|
||||
'resume-task-fail': 'Ошибка во время возобновления задания "{{taskName}}"',
|
||||
'delete-task': 'Удалить задание',
|
||||
'delete-selected-tasks': 'Удалить выбранные задания',
|
||||
@@ -45,15 +45,15 @@ export default {
|
||||
'delete-task-label': 'Удалить вместе с файлами',
|
||||
'delete-task-success': 'Успешно удалено задание "{{taskName}}"',
|
||||
'delete-task-fail': 'Ошибка во время удаления задания "{{taskName}}"',
|
||||
'remove-task-file-fail': 'Ошибка во время удаления файла(файлов) задания, пожалуйста удалите его(их) самостоятельно',
|
||||
'remove-task-config-file-fail': 'Ошибка при удалении файла конфигурации задания, пожалуйста удалите его самостоятельно',
|
||||
'remove-task-file-fail': 'Ошибка во время удаления файла(ов) задания. Пожалуйста, удалите его (их) самостоятельно',
|
||||
'remove-task-config-file-fail': 'Ошибка при удалении файла конфигурации задания. Пожалуйста, удалите его самостоятельно',
|
||||
'move-task-up': 'Переместить задание вверх',
|
||||
'move-task-down': 'Переместить задание вниз',
|
||||
'pause-all-task': 'Приостановить все задания',
|
||||
'pause-all-task-success': 'Успешно приостановленны все задания',
|
||||
'pause-all-task-fail': 'Ошибка во время приостановления всех заданий',
|
||||
'pause-all-task-success': 'Успешно приостановлены все задания',
|
||||
'pause-all-task-fail': 'Ошибка во время остановки всех заданий',
|
||||
'resume-all-task': 'Возобновить все задания',
|
||||
'resume-all-task-success': 'Успешно возобновленны все задания',
|
||||
'resume-all-task-success': 'Успешно возобновлены все задания',
|
||||
'resume-all-task-fail': 'Ошибка во время возобновления всех заданий',
|
||||
'select-all-task': 'Выберите все задачи',
|
||||
'clear-recent-tasks': 'Очистить последние задания',
|
||||
@@ -72,7 +72,7 @@ export default {
|
||||
'remove-record-success': 'Успешно удалена запись про задание "{{taskName}}"',
|
||||
'remove-record-fail': 'Ошибка при удалении записи про задание "{{taskName}}"',
|
||||
'show-in-folder': 'Отобразить файлы заданий в папке',
|
||||
'file-not-exist': 'Розыскиваемый файл не существует или был удален',
|
||||
'file-not-exist': 'Запрошенный файл не существует или был удален',
|
||||
'file-path-error': 'Ошибка в пути к файлу',
|
||||
'opening-task-message': 'Открытие "{{taskName}}" ...',
|
||||
'get-task-name': 'Получить имя задания...',
|
||||
|
||||
@@ -20,6 +20,7 @@ export default {
|
||||
'max-concurrent-downloads': 'Maksimum aktif görev',
|
||||
'max-connection-per-server': 'Sunucu başına maksimum bağlantı',
|
||||
'new-task-show-downloading': 'Görev ekledikten sonra indirmeyi otomatik göster',
|
||||
'no-confirm-before-delete-task': 'Görevi silmeden önce onay gerekmez',
|
||||
'continue': 'Devamlı',
|
||||
'task-completed-notify': 'İndirme bittikten sonra bildirim göster',
|
||||
'auto-purge-record': 'Auto purge download record when app exit',
|
||||
|
||||
@@ -20,6 +20,7 @@ export default {
|
||||
'max-concurrent-downloads': 'Максимум активних завдань',
|
||||
'max-connection-per-server': 'Максимум з\'єднання на сервер',
|
||||
'new-task-show-downloading': 'Автоматично відображати завантаження після додавання завдання',
|
||||
'no-confirm-before-delete-task': 'Перед видаленням завдання не потрібно підтверджувати',
|
||||
'continue': 'Продовжити',
|
||||
'task-completed-notify': 'Повідомлення після завершення завантаження',
|
||||
'auto-purge-record': 'Автоматично чистити записи про завантаження перед закриттям додатка',
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
export default {
|
||||
'engine-version': 'Phiên bản Ứng dụng',
|
||||
'license': 'Giấy phép',
|
||||
'about': 'Về Motrix',
|
||||
'release': 'Phát hành',
|
||||
'support': 'Hỗ trợ'
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
export default {
|
||||
'task-list': 'Danh sách Tác vụ',
|
||||
'add-task': 'Thêm tác vụ',
|
||||
'about': 'Về Motrix',
|
||||
'preferences': 'Cài đặt...',
|
||||
'check-for-updates': 'Kiểm tra Cập nhật...',
|
||||
'check-updates-now': 'Cập nhật ngay',
|
||||
'checking-for-updates': 'Đang kiểm tra Cập nhật...',
|
||||
'check-for-updates-title': 'Kiểm tra Cập nhật',
|
||||
'update-available-message': 'Bạn ơi, đang có một bản cập nhật mới từ Motrix, bạn có muốn cập nhật không?',
|
||||
'update-not-available-message': 'Không có bản cập nhật mới.',
|
||||
'update-downloaded-message': 'Đã sẵn sàng để cài đặt...',
|
||||
'update-error-message': 'Cập nhật lỗi',
|
||||
'engine-damaged-message': 'Ứng dụng bị lỗi, vui lòng cài đặt lại : (',
|
||||
'engine-missing-message': 'Ứng dụng bị thiếu tập tin, vui lòng cài đặt lại : (',
|
||||
'system-error-title': 'Lỗi ứng dụng',
|
||||
'system-error-message': 'Khởi động ứng dụng thất bại: {{message}}',
|
||||
'hide': 'Ẩn Motrix',
|
||||
'hide-others': 'Ẩn tất cả',
|
||||
'unhide': 'Hiển thị tất cả',
|
||||
'show': 'Hiển thị Motrix',
|
||||
'quit': 'Thoát Motrix',
|
||||
'under-development-message': 'Xin lỗi, tính năng này đang được phát triển...',
|
||||
'yes': 'Có',
|
||||
'no': 'Không',
|
||||
'cancel': 'Huỷ',
|
||||
'submit': 'Tải về',
|
||||
'gt1d': '> 1 ngày',
|
||||
'hour': ' giờ',
|
||||
'minute': ' phút',
|
||||
'second': ' giây'
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user