Compare commits

..

93 Commits

Author SHA1 Message Date
Dr_rOot 4b8de781a4 refactor: improve download error message #293 2019-06-19 19:56:07 +08:00
Dr_rOot 8224423d8c refactor: update panel content padding 2019-06-18 21:07:14 +08:00
Dr_rOot 1e05e1e4f3 fix: element ui message link color 2019-06-17 20:27:51 +08:00
Dr_rOot bfd1e1b6fa refactor: removeTaskRecord error complete removed 2019-06-17 19:48:57 +08:00
Dr_rOot 8110409402 feat: empty addTaskOptions when task added 2019-06-15 20:23:11 +08:00
Dr_rOot 37b446b6f0 feat: add parseHeader util for restart task dialog 2019-06-14 23:22:36 +08:00
Dr_rOot 97801fff32 refactor: task item actions restart & stop seeding 2019-06-14 22:38:07 +08:00
Dr_rOot 4ca4f5e606 refactor: add task to support addTaskOptions 2019-06-14 22:20:34 +08:00
Dr_rOot e9caf1b0b4 refactor: transform add options keys to kebab case 2019-06-13 20:47:33 +08:00
Dr_rOot d67dca00b5 feat: add getTaskOption action 2019-06-12 20:36:03 +08:00
Dr_rOot 038536259e feat: add task restart icon 2019-06-12 20:34:22 +08:00
Dr_rOot 02d83534d1 refactor: check is exist before delete task file 2019-06-11 22:41:07 +08:00
Dr_rOot 2ae3642c62 feat: add check task is seeder util 2019-06-07 20:07:20 +08:00
Dr_rOot 5ecc08d66d refactor: clean up useless code 2019-06-04 22:05:51 +08:00
Dr_rOot e7b1a898fa feat: add task history icon 2019-06-03 19:57:04 +08:00
Dr_rOot 93acf1ba63 fix: el-progress status value for element ui 2.9.x 2019-06-02 02:22:13 +08:00
Dr_rOot a5f0236661 Merge pull request #296 from agalwood/feature/transfer_speed_setting_201905282346
fix: refactor transfer settings
2019-05-30 20:44:28 +08:00
Dr_rOot 6df52988b0 chore: i18n preferences transfer settings 2019-05-29 23:57:42 +08:00
Dr_rOot 6359c729ad fix: horizontal scrollbar height 2019-05-29 23:53:58 +08:00
Dr_rOot 04db94daa6 refactor: i18n calc form label width with locale 2019-05-29 23:50:33 +08:00
Dr_rOot d41d809f00 Merge pull request #295 from gee1k/master
fix: remove the button in the aside panel to drag and drop
2019-05-29 15:25:37 +08:00
Svend efdfae37b5 fix:remove the button in the aside panel to drag and drop 2019-05-29 14:01:30 +08:00
Dr_rOot 1a097c02e3 refactor: transfer speed setting to select options 2019-05-28 23:51:34 +08:00
Dr_rOot 0217b5573a fix: theme merge code error 2019-05-28 23:27:57 +08:00
Dr_rOot 86611604d5 Merge branch 'feature/theme_switch_lang_201905232132'
# Conflicts:
#	src/renderer/components/Preference/Advanced.vue
#	src/renderer/components/Preference/Basic.vue
2019-05-28 23:24:08 +08:00
Dr_rOot 0bd8ab3d11 Merge pull request #294 from agalwood/feature/change_global_options_201905241932
feat: change preferences without relaunch app #24 #172 #197
2019-05-28 23:14:05 +08:00
Dr_rOot e9eadb2eba feat: i18n save preferences message 2019-05-28 22:52:50 +08:00
Dr_rOot 72afd845ba fix: default enable check auto update on macOS 2019-05-28 22:35:19 +08:00
Gary Li 91670fac37 fix: updated en-US translation (#290)
* Updated translation: en-US

* Update: en-US translation
2019-05-27 22:38:58 +08:00
Dr_rOot bc14e5b287 Merge pull request #292 from jjandxa/master
feat: add transfer speed setting
2019-05-27 22:33:11 +08:00
jjandxa 2d01431e7d feat: add transfer speed setting 2019-05-26 22:13:38 +08:00
Dr_rOot 81531ef9fc feat: change preference show message 2019-05-26 19:40:13 +08:00
Dr_rOot 04126363ca refactor: change preference without relaunch app 2019-05-25 17:17:00 +08:00
Dr_rOot bcb07656b9 refactor: rename isSyncTracker to trackerSyncing 2019-05-24 19:49:34 +08:00
Dr_rOot b9cd991fd5 refactor: delayed engine client call 2019-05-24 19:45:56 +08:00
Dr_rOot 1b2b22f039 feat: api add changeGlobalOption 2019-05-24 19:45:01 +08:00
Dr_rOot 170a481d48 feat: add diffConfig util 2019-05-24 19:43:53 +08:00
Dr_rOot 48bf7c9e2e refactor: move theme switcher & lang to basic 2019-05-23 21:46:58 +08:00
Dr_rOot 03cfb013e5 docs: fix cn readme typo 2019-05-22 10:31:43 +08:00
Dr_rOot b4e23d8805 chore: add feature request cn issue template 2019-05-22 10:31:25 +08:00
Dr_rOot 43965c5740 chore: update bug report issue template 2019-05-21 10:56:50 +08:00
Dr_rOot 5c8d2e4ca4 fix: unified scrollbar style 2019-05-20 15:01:44 +08:00
Dr_rOot b51c144ace fix: i18n task code format 2019-05-20 14:58:55 +08:00
Dr_rOot feb839b7fa Merge pull request #273 from agalwood/feature/selective_torrent_201905142048
feat: torrent selective download #253 #229 #211
2019-05-19 16:09:09 +08:00
Dr_rOot fa36397afa fix: i18n add task style 2019-05-19 15:48:53 +08:00
Dr_rOot 583fe0ffe2 feat: i18n select torrent 2019-05-19 15:48:03 +08:00
Dr_rOot 8b3a5fbaa8 fix: select torrent reset 2019-05-19 15:45:39 +08:00
Dr_rOot 85493c263b fix: select torrent layout 2019-05-19 15:45:20 +08:00
Dr_rOot 78e106abe4 fix: add selective torrent list utils 2019-05-19 11:44:49 +08:00
Dr_rOot bbf90e9344 fix: torrent file list dark theme style 2019-05-19 11:44:21 +08:00
Dr_rOot dc30c25a83 refactor: select torrent to support selective 2019-05-19 11:42:47 +08:00
Dr_rOot e1cce4f50c refactor: add task to support selective torrent 2019-05-18 21:11:03 +08:00
Dr_rOot 61654ff0ff refactor: remove add task iLoveEggFeatures 2019-05-17 22:39:27 +08:00
Dr_rOot 0142aedfa5 feat: element dialog & table dark theme style 2019-05-17 23:11:24 +08:00
Dr_rOot 323f85ca40 feat: add video audio image files filter utils 2019-05-17 23:08:47 +08:00
Dr_rOot f35e696c6a feat: add file type icon 2019-05-16 20:05:13 +08:00
Dr_rOot d49cbcdc00 feat: add seed-time to system config 2019-05-16 19:54:35 +08:00
Dr_rOot c85ac4e895 feat: set bt-remove-unselected-file to true 2019-05-15 22:06:05 +08:00
Dr_rOot f831cc51ed fix: add task number input text overflow 2019-05-14 20:51:57 +08:00
Dr_rOot d47eb9ba0f refactor: subnav li style 2019-05-14 20:51:04 +08:00
Dr_rOot 119a374db6 fix: clean subnav code 2019-05-13 19:34:47 +08:00
Dr_rOot d26808e43a Merge pull request #262 from agalwood/feature/open_source_license_201905102203
feat: add open source license link to about panel
2019-05-12 11:36:34 +08:00
Dr_rOot 8cb3e54be2 fix: i18n add missing fa about license translate 2019-05-11 10:38:40 +08:00
Dr_rOot c55902cda7 fix: about pannel add license link 2019-05-10 22:33:16 +08:00
Dr_rOot 426b54201b Merge pull request #258 from agalwood/feature/keep_window_size_201905072013
feat: keep window size & position #232 #224 #48
2019-05-09 16:58:40 +08:00
Dr_rOot 6c032cde2d feat: i18n preference keep window state 2019-05-09 12:23:43 +08:00
Dr_rOot eb42ac6cfd feat: preference basic add keep window state chk 2019-05-08 11:40:49 +08:00
Dr_rOot 90f0a7c5f6 feat: restore window state at init 2019-05-08 00:04:53 +08:00
Dr_rOot 9b61dad2c3 feat: store window state 2019-05-08 00:03:25 +08:00
Dr_rOot 671fb82e6a feat: window manager emit resized, moved, closed 2019-05-07 23:56:38 +08:00
Dr_rOot b8f444ca4d fix: detect thunder resource uris add default val 2019-05-06 23:05:56 +08:00
Dr_rOot 7509442e4e refactor: thunder link tip improve 2019-05-05 12:01:11 +08:00
Dr_rOot ee2e3de782 Merge pull request #246 from agalwood/feature/auto_launch_201905031132
feat: auto open at login #164 #210 #237
2019-05-04 20:18:08 +08:00
Dr_rOot c6dd1e333e docs: add fixUserConfig comment 2019-05-04 20:02:16 +08:00
Dr_rOot fba761a29c feat: auto fix user config open-at-login value
Auto fix user config `open-at-login` value when user delete Motrix in OS startup manage
2019-05-04 11:50:51 +08:00
Dr_rOot 4fb94ae8bb fix: second instance could not restore window #233 2019-05-03 22:21:08 +08:00
Dr_rOot 79721822c2 feat: hide window when app launch at login 2019-05-03 15:40:00 +08:00
Dr_rOot 556f58f6b3 chore: i18n open at login 2019-05-03 15:39:18 +08:00
Dr_rOot e690f15335 feat: preference open at login 2019-05-03 15:20:34 +08:00
Dr_rOot 1876587973 feat: auto launch manager 2019-05-03 15:18:57 +08:00
Dr_rOot b89c651132 refactor: auto check update 2019-05-02 15:37:46 +08:00
zhtw ad9d3ed15a fix: some zh-TW typos (#242)
* Update preferences.js

* Update subnav.js

* Update task.js

* Update task.js
2019-05-01 17:54:17 +08:00
Dr_rOot 6aa362332a feat: add dir class watch 2019-05-01 17:09:49 +08:00
Dr_rOot 18d107c062 feat: detect text direction 2019-05-01 17:03:52 +08:00
Dr_rOot ebeba4663c docs: update readme 2019-04-30 21:01:05 +08:00
FloatingShuYin 9bff4257a8 docs: Add tips on how to install package management tools on Windows (#241)
* Add tips on how to install package management tools on Windows

* Remove extra spaces

* Layout revision
2019-04-30 20:16:41 +08:00
Dr_rOot e07ef7dd11 fix: bump version to 1.3.8 2019-04-29 09:49:33 +08:00
Dr_rOot ebb0926d1d fix: preference basic & lab save config fail #128
v1.3.7 btTracker.trim()
2019-04-29 09:46:52 +08:00
Dr_rOot 5333438825 fix: screenshots file name add @2x suffix 2019-04-28 17:23:28 +08:00
Dr_rOot 3664341822 docs: add app screenshots 2019-04-28 17:21:32 +08:00
Dr_rOot d3cbff6dd1 fix: preference link color in dark mode 2019-04-28 17:08:51 +08:00
Dr_rOot 5668b0773d fix: sync icon stroke color 2019-04-28 12:41:25 +08:00
Dr_rOot b757112c30 fix: update readme supported languages 2019-04-28 11:49:32 +08:00
90 changed files with 1773 additions and 482 deletions
+1
View File
@@ -11,6 +11,7 @@ assignees: ''
Before you feedback, please search for the issues to see if there are similar problems that can solve your problem.
**Please delete the above and the contents of this line, then fill in the feedback form in the following format, Thanks.**
<!-- Windows and Linux versions hide the application menu by default. Please close the "Hide Menu Bar" in Preferences - Advanced Settings - Appearance. After saving and applying, the menu bar will appear. -->
**Describe the bug**
A clear and concise description of what the bug is.
+8 -5
View File
@@ -6,12 +6,14 @@ labels: ''
assignees: ''
---
**反馈之前**
<!--
反馈之前请搜索一下已有 issues 和 帮助文档,看是否有类似问题可以解决你的问题
https://github.com/agalwood/Motrix/issues
http://motrix.app/support
**请删除上面和本行的内容,然后按以下格式填写反馈信息,谢谢**
按以下格式填写反馈信息,谢谢
-->
**错误描述**
清楚简洁地描述错误,方便我们复现之后处理。
@@ -24,11 +26,12 @@ http://motrix.app/support
4. 发现报错
**预期的行为**
清楚简洁地描述期望发生的事情。
清楚简洁地描述期望发生的事情。
**截图**
请添加屏幕截图以帮助解释的问题:
请添加屏幕截图以帮助解释的问题:
打开应用菜单中的「帮助」——「开发者工具」—— 切换到 console,然后**完整**截图。
<!-- Windows 和 Linux 版本默认隐藏了应用菜单,请到偏好设置-进阶设置-外观里关闭“隐藏菜单栏”,保存并应用之后菜单栏就出现了 -->
**运行环境**
- 操作系统类型: [如 macOS, Windows, Linux]
@@ -37,4 +40,4 @@ http://motrix.app/support
- 安装包类型:[如 dmg, AppImage]
**更多信息**
添加有关问题的任何其他上下文信息。
补充有关问题的其他信息。
@@ -0,0 +1,28 @@
---
name: 新功能请求
about: 你期望 Motrix 未来添加的新功能
title: ''
labels: enhancement ✨
assignees: ''
---
<!--
反馈之前请搜索一下已有 issues 和 帮助文档,看是否已经有人提交了类似的新功能请求
https://github.com/agalwood/Motrix/issues
http://motrix.app/support
按以下格式填写反馈信息,谢谢
-->
**请描述一下你的新功能请求是否与已知问题有关?**
简明扼要地描述了问题所在。
**描述你想要的解决方案**
简明扼要地描述你想要的解决方案。
**描述你考虑过的替代方案**
简明扼要地描述你考虑过的任何替代解决方案或功能。
**更多信息**
补充有关该新功能的其他信息。
+15 -3
View File
@@ -20,9 +20,19 @@ Motrix 是一款全能的下载工具,支持下载 HTTP、FTP、BT、磁力链
[GitHub](https://github.com/agalwood/Motrix/releases) 和 [官网](https://motrix.app/zh-CN) 提供了已经编译好的稳定版安装包,当然你也可以自己克隆代码编译打包。
### Windows
建议使用安装包(Motrix-Setup-x.y.z.exe)安装 Motrix 以确保完整的体验,例如关联 torrent 文件,捕获磁力链等。
如果你更喜欢便携版,你可以使用 [scoop](https://github.com/lukesampson/scoop)(需要 Windows 7+)安装最新便携版本的 Motrix。
```bash
scoop install motrix
```
### macOS
更新:macOS 用户支持 `brew cask` 安装,感谢 [Mitscherlich](https://github.com/Mitscherlich) 的 [PR](https://github.com/Homebrew/homebrew-cask/pull/59494)。
macOS 用户可以使用 `brew cask` 安装 Motrix,感谢 [Mitscherlich](https://github.com/Mitscherlich) 的 [PR](https://github.com/Homebrew/homebrew-cask/pull/59494)。
```bash
brew update && brew cask install motrix
@@ -30,7 +40,7 @@ brew update && brew cask install motrix
### Linux
可以下载 AppImage(适用于所有Linux发行版)软件包或 snap 或从源代码构建。
可以下载 AppImage(适用于所有 Linux 发行版)软件包或 snap 或从源代码构建安装 Motrix
构建请阅读 **编译打包** 部分。
@@ -51,7 +61,7 @@ yay motrix
- 🚀 单任务最高支持 64 线程下载
- 🕶 模拟用户代理UA
- 🔔 下载完成后通知
- 💻 支持触控栏快捷 (Mac 专享)
- 💻 支持触控栏快捷 (Mac 专享)
- 🤖 常驻系统托盘,操作更加便捷
- 🌑 深色模式
- 🗑 移除任务时可同时删除相关文件
@@ -123,8 +133,10 @@ npm run build
|-------|:--------------------|:-------------|
| de | German | ✔️ [@Schloemicher](https://github.com/Schloemicher) |
| en-US | English | ✔️ |
| fa | فارسی | ✔️ [@Nima-Ra](https://github.com/Nima-Ra) |
| fr | Français | ✔️ [@gpatarin](https://github.com/gpatarin) |
| ja | 日本語 | ✔️ [@hbkrkzk](https://github.com/hbkrkzk) |
| ko | 한국어 | ✔️ [@KOZ39](https://github.com/KOZ39) |
| pt-BR | Portuguese (Brazil) | ✔️ [@andrenoberto](https://github.com/andrenoberto) |
| tr | Türkçe | ✔️ [@abdullah](https://github.com/abdullah) |
| zh-CN | 简体中文 | ✔️ |
+14 -2
View File
@@ -20,9 +20,19 @@ Motrix has a clean and easy to use interface. I hope you will like it 👻.
Download from [GitHub Releases](https://github.com/agalwood/Motrix/releases) and install it.
### Windows
It is recommended to install Motrix using the installation package (Motrix-Setup-x.y.z.exe) to ensure a complete experience, such as associating torrent files, capturing magnet links, etc.
If you prefer the portable version, you can use [scoop](https://github.com/lukesampson/scoop) (need Windows 7+) to install Motrix.
```bash
scoop install motrix
```
### macOS
Update: macOS user support `brew cask` installation, thanks to [PR](https://github.com/Homebrew/homebrew-cask/pull/59494) of [Mitscherlich](https://github.com/Mitscherlich).
The macOS users can install Motrix using `brew cask`, thanks to [PR](https://github.com/Homebrew/homebrew-cask/pull/59494) of [Mitscherlich](https://github.com/Mitscherlich).
```bash
brew update && brew cask install motrix
@@ -30,7 +40,7 @@ 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.
You can download the AppImage (for all Linux distributions) package or snap or just build from source code to install Motrix.
Please read the **Build** section.
@@ -115,8 +125,10 @@ Translations into versions for other languages are welcome 🧐! Please read the
|-------|:--------------------|:-------------|
| de | German | ✔️ [@Schloemicher](https://github.com/Schloemicher) |
| en-US | English | ✔️ |
| fa | فارسی | ✔️ [@Nima-Ra](https://github.com/Nima-Ra) |
| fr | Français | ✔️ [@gpatarin](https://github.com/gpatarin) |
| ja | 日本語 | ✔️ [@hbkrkzk](https://github.com/hbkrkzk) |
| ko | 한국어 | ✔️ [@KOZ39](https://github.com/KOZ39) |
| pt-BR | Portuguese (Brazil) | ✔️ [@andrenoberto](https://github.com/andrenoberto) |
| tr | Türkçe | ✔️ [@abdullah](https://github.com/abdullah) |
| zh-CN | 简体中文 | ✔️ |
+5
View File
@@ -106,3 +106,8 @@ seed-ratio=1.0
bt-seed-unverified=true
# 保存磁力链接元数据为种子文件(.torrent文件), 默认:false
bt-save-metadata=false
# Removes the unselected files when download is completed in BitTorrent.
# To select files, use --select-file option. If it is not used,
# all files are assumed to be selected. Please use this option with care
# because it will actually remove files from your disk. Default: false
bt-remove-unselected-file=true
+5
View File
@@ -106,3 +106,8 @@ seed-ratio=1.0
bt-seed-unverified=true
# 保存磁力链接元数据为种子文件(.torrent文件), 默认:false
bt-save-metadata=false
# Removes the unselected files when download is completed in BitTorrent.
# To select files, use --select-file option. If it is not used,
# all files are assumed to be selected. Please use this option with care
# because it will actually remove files from your disk. Default: false
bt-remove-unselected-file=true
+5
View File
@@ -106,3 +106,8 @@ seed-ratio=1.0
bt-seed-unverified=true
# 保存磁力链接元数据为种子文件(.torrent文件), 默认:false
bt-save-metadata=false
# Removes the unselected files when download is completed in BitTorrent.
# To select files, use --select-file option. If it is not used,
# all files are assumed to be selected. Please use this option with care
# because it will actually remove files from your disk. Default: false
bt-remove-unselected-file=true
+14 -35
View File
@@ -1,6 +1,6 @@
{
"name": "Motrix",
"version": "1.3.4",
"version": "1.3.8",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -5829,8 +5829,7 @@
"version": "4.6.0",
"resolved": "http://registry.npm.taobao.org/co/download/co-4.6.0.tgz",
"integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=",
"dev": true,
"optional": true
"dev": true
},
"coa": {
"version": "2.0.2",
@@ -9699,8 +9698,7 @@
},
"ansi-regex": {
"version": "2.1.1",
"bundled": true,
"optional": true
"bundled": true
},
"aproba": {
"version": "1.2.0",
@@ -9718,13 +9716,11 @@
},
"balanced-match": {
"version": "1.0.0",
"bundled": true,
"optional": true
"bundled": true
},
"brace-expansion": {
"version": "1.1.11",
"bundled": true,
"optional": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@@ -9737,18 +9733,15 @@
},
"code-point-at": {
"version": "1.1.0",
"bundled": true,
"optional": true
"bundled": true
},
"concat-map": {
"version": "0.0.1",
"bundled": true,
"optional": true
"bundled": true
},
"console-control-strings": {
"version": "1.1.0",
"bundled": true,
"optional": true
"bundled": true
},
"core-util-is": {
"version": "1.0.2",
@@ -9851,8 +9844,7 @@
},
"inherits": {
"version": "2.0.3",
"bundled": true,
"optional": true
"bundled": true
},
"ini": {
"version": "1.3.5",
@@ -9862,7 +9854,6 @@
"is-fullwidth-code-point": {
"version": "1.0.0",
"bundled": true,
"optional": true,
"requires": {
"number-is-nan": "^1.0.0"
}
@@ -9875,20 +9866,17 @@
"minimatch": {
"version": "3.0.4",
"bundled": true,
"optional": true,
"requires": {
"brace-expansion": "^1.1.7"
}
},
"minimist": {
"version": "0.0.8",
"bundled": true,
"optional": true
"bundled": true
},
"minipass": {
"version": "2.3.5",
"bundled": true,
"optional": true,
"requires": {
"safe-buffer": "^5.1.2",
"yallist": "^3.0.0"
@@ -9905,7 +9893,6 @@
"mkdirp": {
"version": "0.5.1",
"bundled": true,
"optional": true,
"requires": {
"minimist": "0.0.8"
}
@@ -9978,8 +9965,7 @@
},
"number-is-nan": {
"version": "1.0.1",
"bundled": true,
"optional": true
"bundled": true
},
"object-assign": {
"version": "4.1.1",
@@ -9989,7 +9975,6 @@
"once": {
"version": "1.4.0",
"bundled": true,
"optional": true,
"requires": {
"wrappy": "1"
}
@@ -10065,8 +10050,7 @@
},
"safe-buffer": {
"version": "5.1.2",
"bundled": true,
"optional": true
"bundled": true
},
"safer-buffer": {
"version": "2.1.2",
@@ -10096,7 +10080,6 @@
"string-width": {
"version": "1.0.2",
"bundled": true,
"optional": true,
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
@@ -10114,7 +10097,6 @@
"strip-ansi": {
"version": "3.0.1",
"bundled": true,
"optional": true,
"requires": {
"ansi-regex": "^2.0.0"
}
@@ -10153,13 +10135,11 @@
},
"wrappy": {
"version": "1.0.2",
"bundled": true,
"optional": true
"bundled": true
},
"yallist": {
"version": "3.0.3",
"bundled": true,
"optional": true
"bundled": true
}
}
},
@@ -16024,8 +16004,7 @@
"version": "4.0.8",
"resolved": "http://registry.npm.taobao.org/rx-lite/download/rx-lite-4.0.8.tgz",
"integrity": "sha1-Cx4Rr4vESDbwSmQH6S2kJGe3lEQ=",
"dev": true,
"optional": true
"dev": true
},
"rx-lite-aggregates": {
"version": "4.0.8",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "Motrix",
"version": "1.3.7",
"version": "1.3.8",
"description": "A full-featured download manager",
"homepage": "https://motrix.app",
"author": {
Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

+69 -11
View File
@@ -8,6 +8,7 @@ import logger from './core/Logger'
import ConfigManager from './core/ConfigManager'
import { setupLocaleManager } from '@/ui/Locale'
import Engine from './core/Engine'
import AutoLaunchManager from './core/AutoLaunchManager'
import UpdateManager from './core/UpdateManager'
import EnergyManager from './core/EnergyManager'
import ProtocolManager from './core/ProtocolManager'
@@ -16,6 +17,7 @@ import MenuManager from './ui/MenuManager'
import TouchBarManager from './ui/TouchBarManager'
import TrayManager from './ui/TrayManager'
import ThemeManager from './ui/ThemeManager'
import { AUTO_CHECK_UPDATE_INTERVAL } from '@shared/constants'
export default class Application extends EventEmitter {
constructor () {
@@ -36,9 +38,7 @@ export default class Application extends EventEmitter {
this.initTouchBarManager()
this.windowManager = new WindowManager({
userConfig: this.configManager.getUserConfig()
})
this.initWindowManager()
this.engine = new Engine({
systemConfig: this.configManager.getSystemConfig(),
@@ -48,6 +48,8 @@ export default class Application extends EventEmitter {
this.trayManager = new TrayManager()
this.autoLaunchManager = new AutoLaunchManager()
this.initThemeManager()
this.energyManager = new EnergyManager()
@@ -57,6 +59,7 @@ export default class Application extends EventEmitter {
this.initProtocolManager()
this.handleCommands()
this.handleIpcMessages()
}
@@ -77,12 +80,46 @@ export default class Application extends EventEmitter {
}
}
start (page) {
this.showPage(page)
initWindowManager () {
this.windowManager = new WindowManager({
userConfig: this.configManager.getUserConfig()
})
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)
})
}
showPage (page) {
const win = this.windowManager.openWindow(page)
storeWindowState (data = {}) {
const enabled = this.configManager.getUserConfig('keep-window-state')
if (!enabled) {
return
}
const state = this.configManager.getUserConfig('window-state', {})
const { page, bounds } = data
const newState = {
...state,
[page]: bounds
}
this.configManager.setUserConfig('window-state', newState)
}
start (page, options = {}) {
this.showPage(page, options)
}
showPage (page, options = {}) {
const { openedAtLogin } = options
const win = this.windowManager.openWindow(page, {
hidden: openedAtLogin
})
win.once('ready-to-show', () => {
this.isReady = true
this.emit('ready')
@@ -201,14 +238,22 @@ export default class Application extends EventEmitter {
return
}
this.updateManager = new UpdateManager({
autoCheck: this.configManager.getUserConfig('auto-check-update')
? (new Date().getTime() - this.configManager.getUserConfig('last-check-update-time') > 7 * 24 * 60 * 60 * 1000)
: false,
autoCheck: this.isNeedAutoCheck(),
setCheckTime: this.configManager
})
this.handleUpdaterEvents()
}
isNeedAutoCheck () {
const enable = this.configManager.getUserConfig('auto-check-update')
if (!enable) {
return false
}
const lastCheck = this.configManager.getUserConfig('last-check-update-time')
return (Date.now() - lastCheck > AUTO_CHECK_UPDATE_INTERVAL)
}
handleUpdaterEvents () {
this.updateManager.on('checking', (event) => {
this.menuManager.updateMenuItemEnabledState('app.check-for-updates', false)
@@ -257,10 +302,23 @@ export default class Application extends EventEmitter {
})
this.on('application:exit', () => {
this.engine.stop()
this.stop()
app.exit()
})
this.on('application:open-at-login', (openAtLogin) => {
console.log('application:open-at-login===>', openAtLogin)
if (is.linux()) {
return
}
if (openAtLogin) {
this.autoLaunchManager.enable()
} else {
this.autoLaunchManager.disable()
}
})
this.on('application:show', (page) => {
this.show(page)
})
+49 -17
View File
@@ -5,7 +5,11 @@ import is from 'electron-is'
import ExceptionHandler from './core/ExceptionHandler'
import logger from './core/Logger'
import Application from './Application'
import { parseArgvAsUrl, parseArgvAsFile } from './utils'
import {
splitArgv,
parseArgvAsUrl,
parseArgvAsFile
} from './utils'
const EMPTY_STRING = ''
@@ -33,9 +37,9 @@ export default class Launcher extends EventEmitter {
app.quit()
} else {
app.on('second-instance', (event, argv, workingDirectory) => {
logger.warn('second-instance====>', event, argv, workingDirectory)
logger.warn('second-instance====>', argv, workingDirectory)
global.application.showPage('index')
if (!is.macOS() && argv.length > 1) { // Windows, Linux
if (!is.macOS() && argv.length > 1) {
this.handleAppLaunchArgv(argv)
}
})
@@ -47,6 +51,16 @@ export default class Launcher extends EventEmitter {
init () {
this.exceptionHandler = new ExceptionHandler()
this.openedAtLogin = is.macOS()
? app.getLoginItemSettings().wasOpenedAtLogin
: false
if (process.argv.length > 1) {
this.handleAppLaunchArgv(process.argv)
}
logger.warn('openedAtLogin===>', this.openedAtLogin)
this.handleAppEvents()
}
@@ -60,11 +74,12 @@ export default class Launcher extends EventEmitter {
/**
* handleOpenUrl
* Event 'open-url' macOS only
* "name": "Motrix Protocol",
* "schemes": ["mo", "motrix"]
*/
handleOpenUrl () {
if (is.mas()) {
if (is.mas() || !is.macOS()) {
return
}
app.on('open-url', (event, url) => {
@@ -77,30 +92,44 @@ export default class Launcher extends EventEmitter {
/**
* handleOpenFile
* Event 'open-file' macOS only
* handle open torrent file
*/
handleOpenFile () {
// macOS
if (is.macOS()) {
app.on('open-file', (event, path) => {
logger.info(`[Motrix] open-file: ${path}`)
event.preventDefault()
this.file = path
this.sendFileToApplication()
})
} else if (process.argv.length > 1) { // Windows, Linux
this.handleAppLaunchArgv(process.argv)
if (!is.macOS()) {
return
}
app.on('open-file', (event, path) => {
logger.info(`[Motrix] open-file: ${path}`)
event.preventDefault()
this.file = path
this.sendFileToApplication()
})
}
/**
* handleAppLaunchArgv
* For Windows, Linux
* @param {array} argv
*/
handleAppLaunchArgv (argv) {
const file = parseArgvAsFile(argv)
logger.info('handleAppLaunchArgv===>', argv)
// args: array, extra: map
const { args, extra } = splitArgv(argv)
logger.info('splitArgv.args===>', args)
logger.info('splitArgv.extra===>', extra)
if (extra['--opened-at-login'] === '1') {
this.openedAtLogin = true
}
const file = parseArgvAsFile(args)
if (file) {
this.file = file
this.sendFileToApplication()
}
const url = parseArgvAsUrl(argv)
const url = parseArgvAsUrl(args)
if (url) {
this.url = url
this.sendUrlToApplication()
@@ -125,7 +154,10 @@ export default class Launcher extends EventEmitter {
app.on('ready', () => {
global.application = new Application()
global.application.start('index')
const { openedAtLogin } = this
global.application.start('index', {
openedAtLogin
})
global.application.on('ready', () => {
this.sendUrlToApplication()
+35
View File
@@ -0,0 +1,35 @@
import { app } from 'electron'
export default class AutoLaunchManager {
enable () {
return new Promise((resolve, reject) => {
const enabled = app.getLoginItemSettings().openAtLogin
if (enabled) {
resolve()
}
app.setLoginItemSettings({
openAtLogin: true,
// For Windows
args: [
'--opened-at-login=1'
]
})
resolve()
})
}
disable () {
return new Promise((resolve, reject) => {
app.setLoginItemSettings({ openAtLogin: false })
resolve()
})
}
isEnabled () {
return new Promise((resolve, reject) => {
const enabled = app.getLoginItemSettings().openAtLogin
resolve(enabled)
})
}
}
+16 -2
View File
@@ -90,6 +90,7 @@ export default class ConfigManager {
'pause': true,
'rpc-listen-port': 16800,
'rpc-secret': '',
'seed-time': 60,
'split': 16,
'user-agent': 'Transmission/2.94'
}
@@ -109,20 +110,33 @@ export default class ConfigManager {
// },
defaults: {
'all-proxy-backup': '',
'auto-check-update': false,
'auto-check-update': is.macOS(),
'hide-app-menu': is.windows() || is.linux(),
'last-check-update-time': 0,
'locale': app.getLocale(),
'log-path': getLogPath(),
'new-task-show-downloading': true,
'open-at-login': false,
'resume-all-when-app-launched': false,
'keep-window-state': false,
'session-path': getSessionPath(),
'task-notification': true,
'theme': 'auto',
'update-channel': 'latest',
'use-proxy': false
'use-proxy': false,
'window-state': {}
}
})
this.fixUserConfig()
}
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
if (this.getUserConfig('open-at-login') !== openAtLogin) {
this.setUserConfig('open-at-login', openAtLogin)
}
}
getSystemConfig (key, defaultValue) {
+6 -3
View File
@@ -43,13 +43,13 @@ export default class UpdateManager extends EventEmitter {
if (this.autoCheckData.checkEnable) {
this.autoCheckData.userCheck = false
this.options.setCheckTime.setUserConfig('last-check-update-time', new Date().getTime())
this.options.setCheckTime.setUserConfig('last-check-update-time', Date.now())
this.updater.checkForUpdates()
}
}
check () {
this.options.setCheckTime.setUserConfig('last-check-update-time', new Date().getTime())
this.options.setCheckTime.setUserConfig('last-check-update-time', Date.now())
this.autoCheckData.userCheck = true
this.updater.checkForUpdates()
}
@@ -112,7 +112,10 @@ export default class UpdateManager extends EventEmitter {
updateError (event, error) {
this.emit('update-error', error)
const msg = error == null ? this.i18n.t('update-error-message') : (error.stack || error).toString()
const msg = (error == null)
? this.i18n.t('update-error-message')
: (error.stack || error).toString()
this.updater.logger.warn(`[Motrix] update-error: ${msg}`)
dialog.showErrorBox(msg)
}
+50 -13
View File
@@ -4,10 +4,10 @@ import { app, shell, screen, BrowserWindow } from 'electron'
import is from 'electron-is'
import pageConfig from '../configs/page'
import logger from '../core/Logger'
import { debounce } from 'lodash'
const defaultBrowserOptions = {
titleBarStyle: 'hiddenInset',
useContentSize: true,
show: false,
width: 1024,
height: 768,
@@ -57,37 +57,57 @@ export default class WindowManager extends EventEmitter {
return result
}
openWindow (page) {
const options = this.getPageOptions(page)
getPageBounds (page) {
const enabled = this.userConfig['keep-window-state']
const windowStateMap = this.userConfig['window-state'] || {}
let result = null
if (enabled) {
result = windowStateMap[page]
}
return result
}
openWindow (page, options = {}) {
const pageOptions = this.getPageOptions(page)
const { hidden } = options
let window = this.windows[page] || null
if (window) {
window.restore()
window.show()
window.focus()
return window
}
window = new BrowserWindow({
...defaultBrowserOptions,
...options.attrs
...pageOptions.attrs
})
const bounds = this.getPageBounds(page)
console.log('bounds ====>', bounds)
if (bounds) {
window.setBounds(bounds)
}
window.webContents.on('new-window', (e, url) => {
e.preventDefault()
shell.openExternal(url)
})
if (options.url) {
window.loadURL(options.url)
if (pageOptions.url) {
window.loadURL(pageOptions.url)
}
window.once('ready-to-show', () => {
window.show()
if (!hidden) {
window.show()
}
})
if (options.bindCloseToHide) {
this.bindCloseToHide(page, window)
}
this.handleWindowState(page, window)
this.handleWindowClose(pageOptions, page, window)
this.bindAfterClosed(page, window)
@@ -114,6 +134,9 @@ export default class WindowManager extends EventEmitter {
destroyWindow (page) {
const win = this.getWindow(page)
this.removeWindow(page)
win.removeListener('closed')
win.removeListener('move')
win.removeListener('resize')
win.destroy()
}
@@ -127,12 +150,26 @@ export default class WindowManager extends EventEmitter {
})
}
bindCloseToHide (page, window) {
handleWindowState (page, window) {
window.on('resize', debounce(() => {
const bounds = window.getBounds()
this.emit('window-resized', { page, bounds })
}, 500))
window.on('move', debounce(() => {
const bounds = window.getBounds()
this.emit('window-moved', { page, bounds })
}, 500))
}
handleWindowClose (pageOptions, page, window) {
window.on('close', (event) => {
if (!this.willQuit) {
if (pageOptions.bindCloseToHide && !this.willQuit) {
event.preventDefault()
window.hide()
}
const bounds = window.getBounds()
this.emit('window-closed', { page, bounds })
})
}
+34 -9
View File
@@ -65,25 +65,50 @@ export function moveAppToApplicationsFolder (errorMsg = '') {
})
}
export function splitArgv (argv) {
const args = []
const extra = {}
for (const arg of argv) {
if (arg.startsWith('--')) {
const kv = arg.split('=')
const key = kv[0]
const value = kv[1] || '1'
extra[key] = value
continue
}
args.push(arg)
}
return { args, extra }
}
export function parseArgvAsUrl (argv) {
let arg = argv[1]
if (!arg) {
return
}
if (
arg.toLowerCase().startsWith('mo:') ||
arg.toLowerCase().startsWith('motrix:') ||
arg.toLowerCase().startsWith('http:') ||
arg.toLowerCase().startsWith('https:') ||
arg.toLowerCase().startsWith('ftp:') ||
arg.toLowerCase().startsWith('magnet:') ||
arg.toLowerCase().startsWith('thunder:')
) {
if (checkIsSupportedSchema(arg)) {
return arg
}
}
export function checkIsSupportedSchema (url = '') {
const str = url.toLowerCase()
if (
str.startsWith('mo:') ||
str.startsWith('motrix:') ||
str.startsWith('http:') ||
str.startsWith('https:') ||
str.startsWith('ftp:') ||
str.startsWith('magnet:') ||
str.startsWith('thunder:')
) {
return true
} else {
return false
}
}
export function isDirectory (path) {
return existsSync(path) && lstatSync(path).isDirectory()
}
+30 -5
View File
@@ -84,9 +84,9 @@ export default class Api {
savePreference (params = {}) {
const kebabParams = changeKeysToKebabCase(params)
if (is.renderer()) {
this.savePreferenceToNativeStore(kebabParams)
return this.savePreferenceToNativeStore(kebabParams)
} else {
this.savePreferenceToLocalStorage(kebabParams)
return this.savePreferenceToLocalStorage(kebabParams)
}
}
@@ -99,6 +99,7 @@ export default class Api {
if (!isEmpty(system)) {
console.info('[Motrix] save system config: ', system)
application.configManager.setSystemConfig(system)
this.changeGlobalOption(system)
}
if (!isEmpty(user)) {
@@ -115,6 +116,15 @@ export default class Api {
return this.client.call('getVersion')
}
changeGlobalOption (options) {
const args = {}
Object.keys(options).forEach((key) => {
args[key] = `${options[key]}`
})
return this.client.call('changeGlobalOption', args)
}
getGlobalOption () {
return new Promise((resolve) => {
this.client.call('getGlobalOption')
@@ -124,6 +134,18 @@ export default class Api {
})
}
getOption (params = {}) {
const { gid } = params
const args = compactUndefined([gid])
return new Promise((resolve) => {
this.client.call('getOption', ...args)
.then((data) => {
resolve(changeKeysToCamelCase(data))
})
})
}
getGlobalStat () {
return this.client.call('getGlobalStat')
}
@@ -133,8 +155,9 @@ export default class Api {
uris,
options
} = params
const kebabOptions = changeKeysToKebabCase(options)
const tasks = uris.map((uri) => {
const args = compactUndefined([[uri], options])
const args = compactUndefined([[uri], kebabOptions])
return [ 'aria2.addUri', ...args ]
})
return this.client.multicall(tasks)
@@ -145,7 +168,8 @@ export default class Api {
torrent,
options
} = params
const args = compactUndefined([torrent, [], options])
const kebabOptions = changeKeysToKebabCase(options)
const args = compactUndefined([torrent, [], kebabOptions])
return this.client.call('addTorrent', ...args)
}
@@ -154,7 +178,8 @@ export default class Api {
metalink,
options
} = params
const args = compactUndefined([metalink, options])
const kebabOptions = changeKeysToKebabCase(options)
const args = compactUndefined([metalink, kebabOptions])
return this.client.call('addMetalink', ...args)
}
+10
View File
@@ -0,0 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24">
<g stroke-linecap="round" stroke-linejoin="round" stroke-width="2" stroke="currentColor">
<rect x="4" y="3" width="16" height="18" fill="none" stroke="currentColor" stroke-miterlimit="10"/>
<line data-color="color-2" x1="1" y1="6" x2="1" y2="18" fill="none" stroke-miterlimit="10"/>
<line data-color="color-2" x1="23" y1="6" x2="23" y2="18" fill="none" stroke-miterlimit="10"/>
<polyline data-cap="butt" points="10 15 10 8 16 8 16 14" fill="none" stroke="currentColor" stroke-miterlimit="10"/>
<circle data-stroke="none" cx="9" cy="15" r="2" stroke="none"/>
<circle data-stroke="none" cx="15" cy="14" r="2" stroke="none"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 747 B

+7
View File
@@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24">
<g stroke-linecap="round" stroke-linejoin="round" stroke-width="2" stroke="currentColor">
<polyline data-cap="butt" data-color="color-2" points="1 20 6 14 10 18 17 10 23 17" fill="none" stroke-miterlimit="10"/>
<rect x="1" y="3" width="22" height="18" fill="none" stroke="currentColor" stroke-miterlimit="10"/>
<circle data-color="color-2" cx="9" cy="8" r="2" fill="none" stroke-miterlimit="10"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 509 B

+10
View File
@@ -0,0 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24">
<g class="nc-icon-wrapper" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" stroke="currentColor">
<path fill="none" stroke="currentColor" stroke-miterlimit="10" d="M3,14V4 c0-0.552,0.448-1,1-1h16c0.552,0,1,0.448,1,1v6"/>
<path fill="none" stroke="currentColor" stroke-miterlimit="10" d="M10,18H1v0 c0,1.657,1.343,3,3,3h6"/>
<path data-cap="butt" data-color="color-2" fill="none" stroke-miterlimit="10" d="M14.126,17 c0.444-1.725,2.01-3,3.874-3c1.48,0,2.772,0.804,3.464,1.999"/>
<polygon data-color="color-2" data-stroke="none" points="23.22,13.649 22.792,18 18.522,17.061 " stroke-linejoin="miter" stroke-linecap="square" stroke="none"/>
<path data-cap="butt" data-color="color-2" fill="none" stroke-miterlimit="10" d="M21.874,20 c-0.444,1.725-2.01,3-3.874,3c-1.48,0-2.772-0.804-3.464-1.999"/>
<polygon data-color="color-2" data-stroke="none" points="12.78,23.351 13.208,19 17.478,19.939 " stroke-linejoin="miter" stroke-linecap="square" stroke="none"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24">
<g>
<path d="M12,0C5.383,0,0,5.383,0,12s5.383,12,12,12s12-5.383,12-12S18.617,0,12,0z M19,13h-8V5h2v6h6V13z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 214 B

@@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24">
<g stroke-linecap="round" stroke-linejoin="round" stroke-width="2" stroke="currentColor">
<path data-cap="butt" fill="none" stroke="currentColor" stroke-miterlimit="10" d="M6.121,20.121 C7.727,21.22,9.907,22,12,22c5.523,0,10-4.477,10-10c0-5.523-4.477-10-10-10C8.101,2,4.728,4.233,3.078,7.488"/>
<polyline fill="none" stroke="currentColor" stroke-miterlimit="10" points="2.278,1.588 3.078,7.488 9.078,6.688 "/>
<circle data-color="color-2" fill="none" stroke-miterlimit="10" cx="4" cy="18" r="3"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 609 B

+12
View File
@@ -0,0 +1,12 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24">
<g stroke-linecap="round" stroke-linejoin="round" stroke-width="2" stroke="currentColor">
<line data-cap="butt" data-color="color-2" x1="2" y1="6" x2="22" y2="6" fill="none" stroke-miterlimit="10"/>
<line data-cap="butt" data-color="color-2" x1="12" y1="2" x2="12" y2="22" fill="none" stroke-miterlimit="10"/>
<line data-cap="butt" data-color="color-2" x1="7" y1="2" x2="7" y2="6" fill="none" stroke-miterlimit="10"/>
<line data-cap="butt" data-color="color-2" x1="17" y1="2" x2="17" y2="6" fill="none" stroke-miterlimit="10"/>
<line data-cap="butt" data-color="color-2" x1="2" y1="18" x2="22" y2="18" fill="none" stroke-miterlimit="10"/>
<line data-cap="butt" data-color="color-2" x1="7" y1="22" x2="7" y2="18" fill="none" stroke-miterlimit="10"/>
<line data-cap="butt" data-color="color-2" x1="17" y1="22" x2="17" y2="18" fill="none" stroke-miterlimit="10"/>
<rect x="2" y="2" width="20" height="20" fill="none" stroke="currentColor" stroke-miterlimit="10"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

@@ -6,6 +6,9 @@
</a>
</el-col>
<el-col :span="18" class="copyright-right">
<a target="_blank" href="https://motrix.app/license" rel="noopener noreferrer">
{{ $t('about.license') }}
</a>
<a target="_blank" href="https://motrix.app/about" rel="noopener noreferrer">
{{ $t('about.about') }}
</a>
+4 -7
View File
@@ -3,18 +3,18 @@
<div class="aside-inner">
<mo-logo-mini />
<ul class="menu top-menu">
<li @click="nav('/task')">
<li @click="nav('/task')" class="non-draggable">
<mo-icon name="menu-task" width="20" height="20" />
</li>
<li @click="showAddTask()">
<li @click="showAddTask()" class="non-draggable">
<mo-icon name="menu-add" width="20" height="20" />
</li>
</ul>
<ul class="menu bottom-menu">
<li @click="nav('/preference')">
<li @click="nav('/preference')" class="non-draggable">
<mo-icon name="menu-preference" width="20" height="20" />
</li>
<li @click="showAboutPanel">
<li @click="showAboutPanel" class="non-draggable">
<mo-icon name="menu-about" width="20" height="20" />
</li>
</ul>
@@ -45,9 +45,6 @@
}
},
methods: {
open (link) {
this.$electron.shell.openExternal(link)
},
showAddTask (taskType = 'uri') {
this.$store.dispatch('app/showAddTaskDialog', taskType)
},
+20
View File
@@ -0,0 +1,20 @@
import Icon from '@/components/Icons/Icon'
Icon.register({
'audio': {
'width': 24,
'height': 24,
'raw': `<rect x="4" y="3" width="16" height="18" fill="none" stroke="currentColor" stroke-miterlimit="10"/>
<line data-color="color-2" x1="1" y1="6" x2="1" y2="18" fill="none" stroke-miterlimit="10"/>
<line data-color="color-2" x1="23" y1="6" x2="23" y2="18" fill="none" stroke-miterlimit="10"/>
<polyline data-cap="butt" points="10 15 10 8 16 8 16 14" fill="none" stroke="currentColor" stroke-miterlimit="10"/>
<circle data-stroke="none" cx="9" cy="15" r="2" stroke="none"/>
<circle data-stroke="none" cx="15" cy="14" r="2" stroke="none"/>`,
'g': {
'stroke': 'currentColor',
'stroke-linecap': 'round',
'stroke-linejoin': 'round',
'stroke-width': '2'
}
}
})
+17
View File
@@ -0,0 +1,17 @@
import Icon from '@/components/Icons/Icon'
Icon.register({
'image': {
'width': 24,
'height': 24,
'raw': `<polyline data-cap="butt" data-color="color-2" points="1 20 6 14 10 18 17 10 23 17" fill="none" stroke-miterlimit="10"/>
<rect x="1" y="3" width="22" height="18" fill="none" stroke="currentColor" stroke-miterlimit="10"/>
<circle data-color="color-2" cx="9" cy="8" r="2" fill="none" stroke-miterlimit="10"/>`,
'g': {
'stroke': 'currentColor',
'stroke-linecap': 'round',
'stroke-linejoin': 'round',
'stroke-width': '2'
}
}
})
+1 -1
View File
@@ -6,7 +6,7 @@ Icon.register({
'height': 24,
'raw': `<g stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
<path fill="none" stroke-miterlimit="10" d="M3,14V4 c0-0.552,0.448-1,1-1h16c0.552,0,1,0.448,1,1v6"></path>
<path fill="none" stroke="#111111" stroke-miterlimit="10" d="M10,18H1v0 c0,1.657,1.343,3,3,3h6"></path>
<path fill="none" stroke-miterlimit="10" d="M10,18H1v0 c0,1.657,1.343,3,3,3h6"></path>
<path data-cap="butt" data-color="color-2" fill="none" stroke-miterlimit="10" d="M14.126,17 c0.444-1.725,2.01-3,3.874-3c1.48,0,2.772,0.804,3.464,1.999"></path>
<polygon data-color="color-2" data-stroke="none" points="23.22,13.649 22.792,18 18.522,17.061 " stroke-linejoin="miter" stroke-linecap="square" stroke="none"></polygon>
<path data-cap="butt" data-color="color-2" fill="none" stroke-miterlimit="10" d="M21.874,20 c-0.444,1.725-2.01,3-3.874,3c-1.48,0-2.772-0.804-3.464-1.999"></path>
@@ -0,0 +1,11 @@
import Icon from '@/components/Icons/Icon'
Icon.register({
'task-history': {
'width': 24,
'height': 24,
'paths': [{
'd': 'M12,0C5.383,0,0,5.383,0,12s5.383,12,12,12s12-5.383,12-12S18.617,0,12,0z M19,13h-8V5h2v6h6V13z'
}]
}
})
@@ -0,0 +1,17 @@
import Icon from '@/components/Icons/Icon'
Icon.register({
'task-restart': {
'width': 24,
'height': 24,
'raw': `<path data-cap="butt" fill="none" stroke="currentColor" stroke-miterlimit="10" d="M6.121,20.121 C7.727,21.22,9.907,22,12,22c5.523,0,10-4.477,10-10c0-5.523-4.477-10-10-10C8.101,2,4.728,4.233,3.078,7.488"/>
<polyline fill="none" stroke="currentColor" stroke-miterlimit="10" points="2.278,1.588 3.078,7.488 9.078,6.688 "/>
<circle data-color="color-2" fill="none" stroke-miterlimit="10" cx="4" cy="18" r="3"/>`,
'g': {
'stroke': 'currentColor',
'stroke-linecap': 'round',
'stroke-linejoin': 'round',
'stroke-width': '2'
}
}
})
+22
View File
@@ -0,0 +1,22 @@
import Icon from '@/components/Icons/Icon'
Icon.register({
'video': {
'width': 24,
'height': 24,
'raw': `<line data-cap="butt" data-color="color-2" x1="2" y1="6" x2="22" y2="6" fill="none" stroke-miterlimit="10"/>
<line data-cap="butt" data-color="color-2" x1="12" y1="2" x2="12" y2="22" fill="none" stroke-miterlimit="10"/>
<line data-cap="butt" data-color="color-2" x1="7" y1="2" x2="7" y2="6" fill="none" stroke-miterlimit="10"/>
<line data-cap="butt" data-color="color-2" x1="17" y1="2" x2="17" y2="6" fill="none" stroke-miterlimit="10"/>
<line data-cap="butt" data-color="color-2" x1="2" y1="18" x2="22" y2="18" fill="none" stroke-miterlimit="10"/>
<line data-cap="butt" data-color="color-2" x1="7" y1="22" x2="7" y2="18" fill="none" stroke-miterlimit="10"/>
<line data-cap="butt" data-color="color-2" x1="17" y1="22" x2="17" y2="18" fill="none" stroke-miterlimit="10"/>
<rect x="2" y="2" width="20" height="20" fill="none" stroke="currentColor" stroke-miterlimit="10"/>`,
'g': {
'stroke': 'currentColor',
'stroke-linecap': 'round',
'stroke-linejoin': 'round',
'stroke-width': '2'
}
}
})
-2
View File
@@ -14,7 +14,6 @@
import { mapState } from 'vuex'
import AboutPanel from '@/components/About/AboutPanel'
import Aside from '@/components/Aside/Index'
import Subnav from '@/components/Subnav/Index'
import Speedometer from '@/components/Speedometer/Speedometer'
import AddTask from '@/components/Task/AddTask'
import TaskItemInfo from '@/components/Task/TaskItemInfo'
@@ -25,7 +24,6 @@
components: {
[AboutPanel.name]: AboutPanel,
[Aside.name]: Aside,
[Subnav.name]: Subnav,
[Speedometer.name]: Speedometer,
[AddTask.name]: AddTask,
[TaskItemInfo.name]: TaskItemInfo,
@@ -92,13 +92,21 @@
})
},
onDownloadError: function (event) {
console.log('aria2 onDownloadError', event)
const [{ gid }] = event
this.fetchTaskItem({ gid })
.then((task) => {
const taskName = getTaskName(task)
const { errorCode, errorMessage } = task
console.error(`[Motrix] download error===> Gid: ${gid}, #${errorCode}, ${errorMessage}`)
const message = this.$t('task.download-error-message', { taskName })
this.$msg.error(message)
const link = `<a target="_blank" href="https://github.com/agalwood/Motrix/wiki/Error#${errorCode}" rel="noopener noreferrer">#${errorCode}</a>`
this.$msg({
type: 'error',
showClose: true,
duration: 5000,
dangerouslyUseHTMLString: true,
message: `${message} ${link}`
})
})
},
onDownloadComplete: function (event) {
@@ -196,6 +204,7 @@
polling: function () {
this.$store.dispatch('app/fetchGlobalStat')
this.$store.dispatch('task/fetchList')
if (this.taskItemInfoVisible && this.currentTaskItem) {
this.$store.dispatch('task/fetchItem', this.currentTaskItem.gid)
}
@@ -206,13 +215,16 @@
}
},
created: function () {
this.$store.dispatch('app/fetchEngineInfo')
this.$store.dispatch('app/fetchEngineOptions')
this.startPolling()
this.bindEngineEvents()
},
mounted: function () {
setTimeout(() => {
this.$store.dispatch('app/fetchEngineInfo')
this.$store.dispatch('app/fetchEngineOptions')
this.startPolling()
}, 100)
},
destroyed: function () {
this.$store.dispatch('task/saveSession')
+7 -3
View File
@@ -62,9 +62,13 @@ export function moveTaskFilesToTrash (task, messages = {}) {
return false
}
const deleteResult1 = remote.shell.moveItemToTrash(path)
if (!deleteResult1 && delFailMsg) {
Message.error(delFailMsg)
let deleteResult1 = true
const isFileExist = existsSync(path)
if (isFileExist) {
deleteResult1 = remote.shell.moveItemToTrash(path)
if (!deleteResult1 && delFailMsg) {
Message.error(delFailMsg)
}
}
let deleteResult2 = true
+30 -58
View File
@@ -11,34 +11,6 @@
size="mini"
:model="form"
:rules="rules">
<el-form-item :label="`${$t('preferences.appearance')}: `" :label-width="formLabelWidth">
<el-col class="form-item-sub" :span="24">
<mo-theme-switcher
v-model="form.theme"
@change="handleThemeChange"
/>
</el-col>
<el-col v-if="showHideAppMenuOption" class="form-item-sub" :span="16">
<el-checkbox v-model="form.hideAppMenu">
{{ $t('preferences.hide-app-menu') }}
</el-checkbox>
</el-col>
</el-form-item>
<el-form-item :label="`${$t('preferences.language')}: `" :label-width="formLabelWidth">
<el-col class="form-item-sub" :span="16">
<el-select
v-model="form.locale"
@change="handleLocaleChange"
:placeholder="$t('preferences.change-language')">
<el-option
v-for="item in locales"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-col>
</el-form-item>
<el-form-item :label="`${$t('preferences.proxy')}: `" :label-width="formLabelWidth">
<el-switch
v-model="form.useProxy"
@@ -80,7 +52,7 @@
width="12"
height="12"
:spin="true"
v-if="isSyncTracker"
v-if="trackerSyncing"
/>
<mo-icon name="sync" width="12" height="12" v-else />
</el-button>
@@ -149,13 +121,14 @@
<script>
import is from 'electron-is'
import { mapState } from 'vuex'
import ThemeSwitcher from '@/components/Preference/ThemeSwitcher'
import { cloneDeep } from 'lodash'
import ShowInFolder from '@/components/Native/ShowInFolder'
import userAgentMap from '@shared/ua'
import { availableLanguages, getLanguage } from '@shared/locales'
import { getLocaleManager } from '@/components/Locale'
import {
convertToTextRows
calcFormLabelWidth,
convertCommaToLine,
convertLineToComma,
diffConfig
} from '@shared/utils'
import '@/components/Icons/sync'
import '@/components/Icons/refresh'
@@ -166,18 +139,14 @@
allProxyBackup,
btTracker,
hideAppMenu,
locale,
theme,
useProxy,
userAgent
} = config
const result = {
allProxy,
allProxyBackup,
btTracker: convertToTextRows(btTracker),
btTracker: convertCommaToLine(btTracker),
hideAppMenu,
locale,
theme,
useProxy,
userAgent
}
@@ -187,17 +156,17 @@
export default {
name: 'mo-preference-advanced',
components: {
[ThemeSwitcher.name]: ThemeSwitcher,
[ShowInFolder.name]: ShowInFolder
},
data: function () {
const { locale } = this.$store.state.preference.config
const form = initialForm(this.$store.state.preference.config)
return {
formLabelWidth: '23%',
form: initialForm(this.$store.state.preference.config),
isSyncTracker: false,
form,
formLabelWidth: calcFormLabelWidth(locale),
formOriginal: cloneDeep(form),
rules: {},
color: '#c00',
locales: availableLanguages
trackerSyncing: false
}
},
computed: {
@@ -217,24 +186,15 @@
},
methods: {
isRenderer: is.renderer,
handleLocaleChange (locale) {
const lng = getLanguage(locale)
getLocaleManager().changeLanguage(lng)
this.$electron.ipcRenderer.send('command', 'application:change-locale', lng)
},
handleThemeChange (theme) {
this.form.theme = theme
this.$electron.ipcRenderer.send('command', 'application:change-theme', theme)
},
syncTrackerFromGitHub () {
this.isSyncTracker = true
this.trackerSyncing = true
this.$store.dispatch('preference/fetchBtTracker')
.then((data) => {
console.log('syncTrackerFromGitHub data====>', data)
this.form.btTracker = data
})
.finally(() => {
this.isSyncTracker = false
this.trackerSyncing = false
})
},
onUseProxyChange (flag) {
@@ -269,11 +229,23 @@
console.log('error submit!!')
return false
}
const changed = diffConfig(this.formOriginal, this.form)
const data = {
...changed,
btTracker: convertLineToComma(this.form.btTracker)
}
console.log('changed====》', data)
this.$store.dispatch('preference/save', data)
.then(() => {
this.$store.dispatch('app/fetchEngineOptions')
this.$msg.success(this.$t('preferences.save-success-message'))
})
.catch(() => {
this.$msg.success(this.$t('preferences.save-fail-message'))
})
console.log('this.form===>', this.form)
this.$store.dispatch('preference/save', this.form)
if (this.isRenderer()) {
this.$electron.ipcRenderer.send('command', 'application:relaunch')
}
})
},
+165 -17
View File
@@ -11,7 +11,49 @@
size="mini"
:model="form"
:rules="rules">
<el-form-item :label="`${$t('preferences.appearance')}: `" :label-width="formLabelWidth">
<el-col class="form-item-sub" :span="24">
<mo-theme-switcher
v-model="form.theme"
@change="handleThemeChange"
/>
</el-col>
<el-col v-if="showHideAppMenuOption" class="form-item-sub" :span="16">
<el-checkbox v-model="form.hideAppMenu">
{{ $t('preferences.hide-app-menu') }}
</el-checkbox>
</el-col>
</el-form-item>
<el-form-item :label="`${$t('preferences.language')}: `" :label-width="formLabelWidth">
<el-col class="form-item-sub" :span="16">
<el-select
v-model="form.locale"
@change="handleLocaleChange"
:placeholder="$t('preferences.change-language')">
<el-option
v-for="item in locales"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-col>
</el-form-item>
<el-form-item :label="`${$t('preferences.startup')}: `" :label-width="formLabelWidth">
<el-col
class="form-item-sub"
:span="24"
v-if="!isLinux()"
>
<el-checkbox v-model="form.openAtLogin">
{{ $t('preferences.open-at-login') }}
</el-checkbox>
</el-col>
<el-col class="form-item-sub" :span="24">
<el-checkbox v-model="form.keepWindowState">
{{ $t('preferences.keep-window-state') }}
</el-checkbox>
</el-col>
<el-col class="form-item-sub" :span="24">
<el-checkbox v-model="form.resumeAllWhenAppLaunched">
{{ $t('preferences.auto-resume-all') }}
@@ -39,6 +81,30 @@
{{ $t('preferences.mas-default-dir-tips') }}
</div>
</el-form-item>
<el-form-item :label="`${$t('preferences.transfer-settings')}: `" :label-width="formLabelWidth">
<el-col class="form-item-sub" :span="24">
{{ $t('preferences.transfer-speed-upload') }}
<el-select v-model="form.maxOverallUploadLimit">
<el-option
v-for="item in speedOptions"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-col>
<el-col class="form-item-sub" :span="24">
{{ $t('preferences.transfer-speed-download') }}
<el-select v-model="form.maxOverallDownloadLimit">
<el-option
v-for="item in speedOptions"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-col>
</el-form-item>
<el-form-item :label="`${$t('preferences.task-manage')}: `" :label-width="formLabelWidth">
<el-col class="form-item-sub" :span="24">
{{ $t('preferences.max-concurrent-downloads') }}
@@ -88,32 +154,51 @@
<script>
import is from 'electron-is'
import { mapState } from 'vuex'
import { cloneDeep } from 'lodash'
import SelectDirectory from '@/components/Native/SelectDirectory'
import ThemeSwitcher from '@/components/Preference/ThemeSwitcher'
import { availableLanguages, getLanguage } from '@shared/locales'
import { getLocaleManager } from '@/components/Locale'
import { prettifyDir } from '@/components/Native/utils'
import { calcFormLabelWidth, diffConfig } from '@shared/utils'
const initialForm = (config) => {
const {
autoCheckUpdate,
dir,
split,
resumeAllWhenAppLaunched,
hideAppMenu,
keepWindowState,
lastCheckUpdateTime,
locale,
maxConcurrentDownloads,
maxConnectionPerServer,
taskNotification,
autoCheckUpdate,
maxOverallUploadLimit,
maxOverallDownloadLimit,
newTaskShowDownloading,
lastCheckUpdateTime
openAtLogin,
resumeAllWhenAppLaunched,
split,
taskNotification,
theme
} = config
const result = {
dir,
split,
autoCheckUpdate,
continue: config.continue,
resumeAllWhenAppLaunched,
dir,
hideAppMenu,
keepWindowState,
lastCheckUpdateTime,
locale,
maxConcurrentDownloads,
maxConnectionPerServer,
taskNotification,
autoCheckUpdate,
maxOverallUploadLimit,
maxOverallDownloadLimit,
newTaskShowDownloading,
lastCheckUpdateTime
openAtLogin,
resumeAllWhenAppLaunched,
split,
taskNotification,
theme
}
return result
}
@@ -121,19 +206,28 @@
export default {
name: 'mo-preference-basic',
components: {
[SelectDirectory.name]: SelectDirectory
[SelectDirectory.name]: SelectDirectory,
[ThemeSwitcher.name]: ThemeSwitcher
},
data: function () {
const { locale } = this.$store.state.preference.config
const form = initialForm(this.$store.state.preference.config)
return {
formLabelWidth: '23%',
form: initialForm(this.$store.state.preference.config),
rules: {}
form,
formLabelWidth: calcFormLabelWidth(locale),
formOriginal: cloneDeep(form),
locales: availableLanguages,
rules: {},
speedOptions: this.buildSpeedOptions()
}
},
computed: {
title: function () {
return this.$t('preferences.basic')
},
showHideAppMenuOption: function () {
return is.windows() || is.linux()
},
downloadDir: function () {
return prettifyDir(this.form.dir)
},
@@ -144,6 +238,45 @@
methods: {
isRenderer: is.renderer,
isMas: is.mas,
isLinux: is.linux,
handleLocaleChange (locale) {
const lng = getLanguage(locale)
getLocaleManager().changeLanguage(lng)
this.speedOptions = this.buildSpeedOptions()
this.$electron.ipcRenderer.send('command', 'application:change-locale', lng)
},
handleThemeChange (theme) {
this.form.theme = theme
this.$electron.ipcRenderer.send('command', 'application:change-theme', theme)
},
buildSpeedOptions () {
return [
{
label: this.$t('preferences.transfer-speed-unlimited'),
value: 0
},
{
label: '128 KB/s',
value: '128K'
},
{
label: '512 KB/s',
value: '512K'
},
{
label: '1 MB/s',
value: '1M'
},
{
label: '5 MB/s',
value: '5M'
},
{
label: '10 MB/s',
value: '10M'
}
]
},
onDirectorySelected (dir) {
this.form.dir = dir
},
@@ -154,9 +287,24 @@
return false
}
this.$store.dispatch('preference/save', this.form)
const { openAtLogin } = this.form
const changed = diffConfig(this.formOriginal, this.form)
const data = {
...changed
}
console.log('changed====》', data)
this.$store.dispatch('preference/save', data)
.then(() => {
this.$store.dispatch('app/fetchEngineOptions')
this.$msg.success(this.$t('preferences.save-success-message'))
})
.catch(() => {
this.$msg.success(this.$t('preferences.save-fail-message'))
})
if (this.isRenderer()) {
this.$electron.ipcRenderer.send('command', 'application:relaunch')
this.$electron.ipcRenderer.send('command', 'application:open-at-login', openAtLogin)
}
})
},
+7 -3
View File
@@ -52,6 +52,9 @@
import is from 'electron-is'
import { mapState } from 'vuex'
import '@/components/Icons/info-square'
import {
calcFormLabelWidth
} from '@shared/utils'
const initialForm = (config) => {
const {
@@ -68,9 +71,11 @@
components: {
},
data: function () {
const { locale } = this.$store.state.preference.config
const form = initialForm(this.$store.state.preference.config)
return {
formLabelWidth: '23%',
form: initialForm(this.$store.state.preference.config),
form,
formLabelWidth: calcFormLabelWidth(locale),
rules: {}
}
},
@@ -97,7 +102,6 @@
console.log('this.form===>', this.form)
this.$store.dispatch('preference/save', this.form)
if (this.isRenderer()) {
this.$electron.ipcRenderer.send('command', 'application:relaunch')
}
})
},
-20
View File
@@ -1,20 +0,0 @@
<template>
<el-aside width="200px" class="subnav">
<router-view name="subnav" />
</el-aside>
</template>
<script>
export default {
name: 'mo-subnav',
components: {
},
computed: {
},
methods: {
}
}
</script>
<style lang="scss">
</style>
+64 -69
View File
@@ -5,6 +5,7 @@
:visible.sync="visible"
:before-close="handleClose"
@open="handleOpen"
@opened="handleOpened"
@closed="handleClosed">
<el-form
ref="taskForm"
@@ -21,6 +22,7 @@
auto-complete="off"
:placeholder="$t('task.uri-task-tips')"
@change="handleUriChange"
@paste.native="handleUriPaste"
v-model="form.uris">
</el-input>
</el-form-item>
@@ -28,7 +30,6 @@
<el-tab-pane
:label="$t('task.torrent-task')"
name="torrent"
v-if="iLoveEggFeatures"
>
<el-form-item>
<mo-select-torrent
@@ -115,26 +116,31 @@
import SelectDirectory from '@/components/Native/SelectDirectory'
import SelectTorrent from '@/components/Task/SelectTorrent'
import { prettifyDir } from '@/components/Native/utils'
import '@/components/Icons/inbox'
import {
NONE_SELECTED_FILES,
SELECTED_ALL_FILES
} from '@shared/constants'
import {
detectResource,
splitTaskLinks,
needCheckCopyright
splitTaskLinks
} from '@shared/utils'
import '@/components/Icons/inbox'
const initialForm = (state) => {
const { addTaskUrl } = state.app
const { addTaskUrl, addTaskOptions } = state.app
const { dir, split, newTaskShowDownloading } = state.preference.config
const result = {
uris: addTaskUrl,
torrent: '',
selectFile: NONE_SELECTED_FILES,
out: '',
userAgent: '',
referer: '',
cookie: '',
dir,
split,
newTaskShowDownloading
newTaskShowDownloading,
...addTaskOptions
}
return result
}
@@ -159,7 +165,6 @@
return {
formLabelWidth: '100px',
showAdvanced: false,
torrentName: '',
form: {},
rules: {}
}
@@ -176,10 +181,7 @@
}),
...mapState('preference', {
config: state => state.config
}),
iLoveEggFeatures: function () {
return !this.isMas() || (this.isMas() && this.config.enableEggFeatures)
}
})
},
watch: {
taskType: function (current, previous) {
@@ -216,8 +218,12 @@
this.form.uris = content
}
},
handleOpened () {
this.detectThunderResource(this.form.uris)
},
handleClose (done) {
this.$store.dispatch('app/hideAddTaskDialog')
this.$store.dispatch('app/updateAddTaskOptions', {})
},
handleClosed () {
this.reset()
@@ -225,10 +231,13 @@
handleTabClick (tab, event) {
this.$store.dispatch('app/changeAddTaskType', tab.name)
},
handleUriChange () {
// el-input does not support @paste event ?
// https://github.com/ElemeFE/element/blob/master/packages/input/src/input.vue
const { uris } = this.form
handleUriPaste () {
setImmediate(() => {
const uris = this.$refs.uri.value
this.detectThunderResource(uris)
})
},
detectThunderResource (uris = '') {
if (uris.includes('thunder://')) {
this.$msg({
type: 'warning',
@@ -237,10 +246,12 @@
})
}
},
handleTorrentChange (torrent, file, fileList) {
// TODO 种子选择部分文件下载
// console.log('handleTorrentChange===>', torrent, file, fileList)
handleUriChange () {
console.log('handleUriChange===>', this.form.uris)
},
handleTorrentChange (torrent, selectedFileIndex) {
this.form.torrent = torrent
this.form.selectFile = selectedFileIndex
},
handleSplitChange (value) {
console.log('handleSplitChange===>', value)
@@ -249,6 +260,7 @@
this.form.dir = dir
},
reset () {
this.showAdvanced = false
this.form = initialForm(this.$store.state)
},
handleCancel (formName) {
@@ -271,9 +283,12 @@
}
return result
},
buildOption (form) {
buildOption (type, form) {
const {
dir, out, split
dir,
out,
selectFile,
split
} = form
const result = {}
@@ -285,6 +300,15 @@
result.out = out
}
if (type === 'torrent') {
if (
selectFile !== SELECTED_ALL_FILES &&
selectFile !== NONE_SELECTED_FILES
) {
result.selectFile = selectFile
}
}
if (split > 0) {
result.split = split
}
@@ -297,8 +321,11 @@
},
buildUriPayload (form) {
let { uris } = form
if (isEmpty(uris)) {
throw new Error(this.$t('task.new-task-uris-required'))
}
uris = splitTaskLinks(uris)
const options = this.buildOption(form)
const options = this.buildOption('uri', form)
const result = {
uris,
options
@@ -307,12 +334,14 @@
},
buildTorrentPayload (form) {
const { torrent } = form
const options = this.buildOption(form)
if (isEmpty(torrent)) {
throw new Error(this.$t('task.new-task-torrent-required'))
}
const options = this.buildOption('torrent', form)
const result = {
torrent,
options
}
console.log('buildTorrentPayload===>', result)
return result
},
addTask (type, form) {
@@ -335,58 +364,24 @@
console.error('addTask fail', form)
}
},
checkCopyright (type, form) {
const { uris } = form
return new Promise((resolve, reject) => {
if (type !== 'uri') {
resolve()
}
if (!needCheckCopyright(uris)) {
resolve()
return
}
if (this.iLoveEggFeatures) {
resolve()
return
}
this.$electron.remote.dialog.showMessageBox({
type: 'warning',
title: this.$t('task.copyright-warning'),
message: this.$t('task.copyright-warning-message'),
buttons: [this.$t('task.copyright-yes'), this.$t('task.copyright-no')],
cancelId: 1
}, (buttonIndex, checkboxChecked) => {
if (buttonIndex === 0) {
resolve()
} else {
reject(new Error(this.$t('task.copyright-error-message')))
}
})
})
},
submitForm (formName) {
this.$refs[formName].validate((valid) => {
if (!valid) {
return false
}
this.checkCopyright(this.type, this.form)
.then(() => {
this.addTask(this.type, this.form)
this.$store.dispatch('app/hideAddTaskDialog')
if (this.form.newTaskShowDownloading) {
this.$router.push({
path: '/task/active'
})
}
})
.catch((err) => {
this.$msg.error(err.message)
})
try {
this.addTask(this.type, this.form)
this.$store.dispatch('app/hideAddTaskDialog')
if (this.form.newTaskShowDownloading) {
this.$router.push({
path: '/task/active'
})
}
} catch (err) {
this.$msg.error(err.message)
}
})
}
}
-3
View File
@@ -47,9 +47,6 @@
'status': 'onStatusChange'
},
methods: {
open (link) {
this.$electron.shell.openExternal(link)
},
onStatusChange () {
this.changeCurrentList()
},
+212 -9
View File
@@ -3,6 +3,7 @@
class="upload-torrent"
drag
action="/"
v-if="isTorrentsEmpty"
:limit="1"
:multiple="false"
accept=".torrent"
@@ -16,23 +17,115 @@
<div class="torrent-name" v-if="name">{{ name }}</div>
</div>
</el-upload>
<div
class="selective-torrent"
v-else
>
<el-row class="torrent-info" :gutter="12">
<el-col class="torrent-name" :span="20">
<el-tooltip class="item" effect="dark" :content="name" placement="top">
<span>{{ name }}</span>
</el-tooltip>
</el-col>
<el-col class="torrent-actions" :span="4">
<span
@click="handleTrashClick"
>
<mo-icon name="trash" width="14" height="14" />
</span>
</el-col>
</el-row>
<div class="torrent-file-list">
<el-table
stripe
ref="torrentTable"
height="200"
:data="files"
tooltip-effect="dark"
style="width: 100%"
@row-dblclick="handleRowDbClick"
@selection-change="handleSelectionChange">
<el-table-column
type="selection"
width="35">
</el-table-column>
<el-table-column
:label="$t('task.file-name')"
show-overflow-tooltip>
<template slot-scope="scope">{{ scope.row.name }}</template>
</el-table-column>
<el-table-column
:label="$t('task.file-extension')"
width="80">
<template slot-scope="scope">{{ scope.row.extension | removeExtensionDot }}</template>
</el-table-column>
<el-table-column
:label="$t('task.file-size')"
width="90">
<template slot-scope="scope">{{ scope.row.length | bytesToSize }}</template>
</el-table-column>
</el-table>
</div>
<el-row :gutter="12">
<el-col class="file-filters" :span="8">
<el-button-group>
<el-button @click="toggleVideoSelection()">
<mo-icon name="video" width="12" height="12" />
</el-button>
<el-button @click="toggleAudioSelection()">
<mo-icon name="audio" width="12" height="12" />
</el-button>
<el-button @click="toggleImageSelection()">
<mo-icon name="image" width="12" height="12" />
</el-button>
</el-button-group>
</el-col>
<el-col :span="16" style="text-align: right">
{{ $t('task.selected-files-sum', { selectedFilesCount, selectedFilesTotalSize }) }}
</el-col>
</el-row>
</div>
</template>
<script>
import { mapState } from 'vuex'
import { isEmpty } from 'lodash'
import parseTorrent from 'parse-torrent'
import '@/components/Icons/inbox'
import { getAsBase64, buildFileList } from '@shared/utils'
import '@/components/Icons/video'
import '@/components/Icons/audio'
import '@/components/Icons/image'
import {
NONE_SELECTED_FILES,
SELECTED_ALL_FILES
} from '@shared/constants'
import {
buildFileList,
listTorrentFiles,
bytesToSize,
filterVideoFiles,
filterAudioFiles,
filterImageFiles,
getAsBase64,
removeExtensionDot
} from '@shared/utils'
export default {
name: 'mo-select-torrent',
components: {
},
filters: {
bytesToSize,
removeExtensionDot
},
props: {
},
data () {
return {
name: ''
name: '',
currentTorrent: '',
files: [],
selectedFiles: []
}
},
computed: {
@@ -41,15 +134,40 @@
}),
...mapState('app', {
torrents: state => state.addTaskTorrents
})
}),
isTorrentsEmpty: function () {
return this.torrents.length === 0
},
selectedFilesCount: function () {
return this.selectedFiles.length
},
selectedFilesTotalSize: function () {
const result = this.selectedFiles.reduce((acc, cur) => {
return acc + cur.length
}, 0)
return bytesToSize(result)
},
selectedFileIndex: function () {
const { files, selectedFiles } = this
if (files.length === 0 || selectedFiles.length === 0) {
return NONE_SELECTED_FILES
}
if (files.length === selectedFiles.length) {
return SELECTED_ALL_FILES
}
const indexArr = this.selectedFiles.map((item) => item.idx)
const result = indexArr.join(',')
return result
}
},
watch: {
torrents (fileList) {
const file = fileList[0]
if (fileList.length === 0) {
this.name = ''
this.reset()
return
}
const file = fileList[0]
if (!file.raw) {
return
}
@@ -57,21 +175,68 @@
parseTorrent.remote(file.raw, (err, parsedTorrent) => {
if (err) throw err
console.log(parsedTorrent)
})
this.files = listTorrentFiles(parsedTorrent.files)
this.$refs.torrentTable.toggleAllSelection()
getAsBase64(file.raw, (torrent) => {
this.name = file.name
this.$emit('change', torrent, file, fileList)
getAsBase64(file.raw, (torrent) => {
this.name = file.name
this.currentTorrent = torrent
this.$emit('change', torrent, SELECTED_ALL_FILES)
})
})
},
selectedFileIndex () {
const { currentTorrent, selectedFileIndex } = this
this.$emit('change', currentTorrent, selectedFileIndex)
}
},
methods: {
reset () {
this.name = ''
this.currentTorrent = ''
this.files = []
if (this.$refs.torrentTable) {
this.$refs.torrentTable.clearSelection()
}
this.$emit('change', '', NONE_SELECTED_FILES)
},
handleChange (file, fileList) {
this.$store.dispatch('app/addTaskAddTorrents', { fileList })
},
handleExceed (files) {
const fileList = buildFileList(files[0])
this.$store.dispatch('app/addTaskAddTorrents', { fileList })
},
handleTrashClick () {
this.$store.dispatch('app/addTaskAddTorrents', { fileList: [] })
},
toggleSelection (rows) {
if (isEmpty(rows)) {
this.$refs.torrentTable.clearSelection()
} else {
this.$refs.torrentTable.clearSelection()
rows.forEach(row => {
this.$refs.torrentTable.toggleRowSelection(row)
})
}
},
toggleVideoSelection () {
const filtered = filterVideoFiles(this.files)
this.toggleSelection(filtered)
},
toggleAudioSelection () {
const filtered = filterAudioFiles(this.files)
this.toggleSelection(filtered)
},
toggleImageSelection () {
const filtered = filterImageFiles(this.files)
this.toggleSelection(filtered)
},
handleRowDbClick (row, column, event) {
this.$refs.torrentTable.toggleRowSelection(row)
},
handleSelectionChange (val) {
this.selectedFiles = val
}
}
}
@@ -99,4 +264,42 @@
line-height: 16px;
}
}
.selective-torrent {
.torrent-name {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.torrent-info {
margin-bottom: 15px;
font-size: 12px;
line-height: 16px;
}
.torrent-actions {
text-align: right;
line-height: 16px;
&> span {
cursor: pointer;
display: inline-block;
vertical-align: middle;
height: 14px;
padding: 1px;
}
}
}
.file-filters {
button {
font-size: 0;
}
}
.torrent-file-list {
border: 1px solid #ebeef5;
border-bottom: none;
overflow-x: hidden;
overflow-y: scroll;
margin-bottom: 8px;
.el-table th {
padding: 2px 0;
}
}
</style>
+156 -47
View File
@@ -1,36 +1,38 @@
<template>
<div class="task-item-actions" v-on:dblclick.stop="() => null">
<!-- <i @click.stop="onMoreClick">
<mo-icon name="more" width="14" height="14" />
</i> -->
<i @click.stop="onInfoClick" v-if="mode === 'LIST'">
<mo-icon name="info-circle" width="14" height="14" />
</i>
<i @click.stop="onLinkClick">
<mo-icon name="link" width="14" height="14" />
</i>
<i v-if="isRenderer()" @click.stop="onFolderClick">
<mo-icon name="folder" width="14" height="14" />
</i>
<i v-if="task.status ==='complete' ||
task.status ==='removed' ||
task.status ==='error'"
@click.stop="onTrashClick">
<mo-icon name="trash" width="14" height="14" />
</i>
<i v-if="task.status ==='active' ||
task.status ==='waiting' ||
task.status ==='paused'"
@click.stop="onDeleteClick">
<mo-icon name="delete" width="14" height="14" />
</i>
<i v-if="task.status ==='active'" @click.stop="onPauseClick">
<mo-icon name="task-pause-line" width="14" height="14" />
</i>
<i v-if="task.status ==='waiting' || task.status ==='paused'" @click.stop="onResumeClick">
<mo-icon name="task-start-line" width="14" height="14" />
</i>
</div>
<ul :key="task.gid" class="task-item-actions" v-on:dblclick.stop="() => null">
<li v-for="action in taskActions" :key="action" class="task-item-action">
<i v-if="action ==='PAUSE'" @click.stop="onPauseClick">
<mo-icon name="task-pause-line" width="14" height="14" />
</i>
<i v-if="action ==='STOP'" @click.stop="onStopClick">
<mo-icon name="task-stop-line" width="14" height="14" />
</i>
<i v-if="action === 'RESUME'" @click.stop="onResumeClick">
<mo-icon name="task-start-line" width="14" height="14" />
</i>
<i v-if="action === 'RESTART'" @click="onRestartClick">
<mo-icon name="task-restart" width="14" height="14" />
</i>
<i v-if="action === 'DELETE'" @click.stop="onDeleteClick">
<mo-icon name="delete" width="14" height="14" />
</i>
<i v-if="action === 'TRASH'" @click.stop="onTrashClick">
<mo-icon name="trash" width="14" height="14" />
</i>
<i v-if="action ==='FOLDER'" @click.stop="onFolderClick">
<mo-icon name="folder" width="14" height="14" />
</i>
<i v-if="action ==='LINK'" @click.stop="onLinkClick">
<mo-icon name="link" width="14" height="14" />
</i>
<i v-if="action ==='INFO'" @click.stop="onInfoClick">
<mo-icon name="info-circle" width="14" height="14" />
</i>
<i v-if="action ==='MORE'" @click.stop="onMoreClick">
<mo-icon name="more" width="14" height="14" />
</i>
</li>
</ul>
</template>
<script>
@@ -38,6 +40,8 @@
import * as clipboard from 'clipboard-polyfill'
import '@/components/Icons/task-start-line'
import '@/components/Icons/task-pause-line'
import '@/components/Icons/task-stop-line'
import '@/components/Icons/task-restart'
import '@/components/Icons/delete'
import '@/components/Icons/folder'
import '@/components/Icons/link'
@@ -49,11 +53,23 @@
moveTaskFilesToTrash
} from '@/components/Native/utils'
import {
checkTaskIsSeeder,
getTaskFullPath,
getTaskName,
getTaskUri,
getTaskFullPath
parseHeader
} from '@shared/utils'
const taskActionsMap = {
active: ['PAUSE', 'DELETE'],
paused: ['RESUME', 'DELETE'],
waiting: ['RESUME', 'DELETE'],
error: ['RESTART', 'TRASH'],
complete: ['RESTART', 'TRASH'],
removed: ['RESTART', 'TRASH'],
seeding: ['STOP', 'DELETE']
}
export default {
name: 'mo-task-item-actions',
props: {
@@ -67,23 +83,48 @@
}
},
computed: {
taskName: function () {
taskName () {
return getTaskName(this.task)
},
path: function () {
path () {
return getTaskFullPath(this.task)
},
isSeeder () {
return checkTaskIsSeeder(this.task)
},
taskStatus () {
const { task, isSeeder } = this
if (isSeeder) {
return 'seeding'
} else {
return task.status
}
},
taskCommonActions () {
let result = is.renderer() ? ['FOLDER'] : []
result = (this.mode === 'LIST')
? [...result, 'LINK', 'INFO']
: [...result, 'LINK']
return result
},
taskActions () {
const { taskStatus, taskCommonActions } = this
const actions = taskActionsMap[taskStatus] || []
const result = [...actions, ...taskCommonActions].reverse()
return result
}
},
methods: {
isRenderer: is.renderer,
deleteTaskFiles: function (task) {
deleteTaskFiles (task) {
moveTaskFilesToTrash(task, {
pathErrorMsg: this.$t('task.file-path-error'),
delFailMsg: this.$t('task.remove-task-file-fail'),
delConfigFailMsg: this.$t('task.remove-task-config-file-fail')
})
},
removeTaskItem: function (task, isRemoveWithFiles) {
removeTaskItem (task, isRemoveWithFiles) {
this.$store.dispatch('task/removeTask', this.task)
.then(() => {
if (isRemoveWithFiles) {
@@ -101,7 +142,7 @@
}
})
},
removeTaskRecord: function (task, isRemoveWithFiles) {
removeTaskRecord (task, isRemoveWithFiles) {
this.$store.dispatch('task/removeTaskRecord', this.task)
.then(() => {
if (isRemoveWithFiles) {
@@ -119,7 +160,7 @@
}
})
},
onResumeClick: function () {
onResumeClick () {
this.$store.dispatch('task/resumeTask', this.task)
.catch(({ code }) => {
if (code === 1) {
@@ -129,7 +170,75 @@
}
})
},
onPauseClick: function () {
onRestartClick (event) {
const { task, taskName } = this
const { gid, status } = task
const uri = getTaskUri(task)
const isNeedShowDialog = status === 'complete' || !!event.altKey
this.$store.dispatch('task/getTaskOption', gid)
.then((data) => {
console.log('getTaskOption===>', data)
const { dir, header, split } = data
const options = {
dir,
header,
split,
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', 'uri')
},
onPauseClick () {
this.pauseTask()
},
onStopClick () {
this.stopSeeding()
},
stopSeeding () {
if (!this.isSeeder) {
return
}
this.$store.dispatch('task/pauseTask', this.task)
.then(() => {
this.$store.dispatch('task/resumeTask', this.task)
})
},
pauseTask () {
const { taskName } = this
this.$msg.info(this.$t('task.download-pause-message', { taskName }))
this.$store.dispatch('task/pauseTask', this.task)
@@ -139,7 +248,7 @@
}
})
},
onDeleteClick: function () {
onDeleteClick () {
const self = this
const { task } = this
this.$electron.remote.dialog.showMessageBox({
@@ -155,7 +264,7 @@
}
})
},
onTrashClick: function () {
onTrashClick () {
const self = this
const { task } = this
this.$electron.remote.dialog.showMessageBox({
@@ -171,12 +280,12 @@
}
})
},
onFolderClick: function () {
onFolderClick () {
showItemInFolder(this.path, {
errorMsg: this.$t('task.file-not-exist')
})
},
onLinkClick: function () {
onLinkClick () {
this.$store.dispatch('app/fetchEngineOptions')
.then((data) => {
const { btTracker } = data
@@ -187,10 +296,10 @@
})
})
},
onInfoClick: function () {
onInfoClick () {
this.$store.dispatch('task/showTaskItemInfoDialog', this.task)
},
onMoreClick: function () {
onMoreClick () {
console.log('onMoreClick===>')
}
}
@@ -218,7 +327,7 @@
background-color: $--task-item-action-hover-background;
width: auto;
}
&> i {
&> .task-item-action {
display: inline-block;
padding: 5px;
margin: 0 4px;
@@ -3,7 +3,7 @@
v-if="status === 'active'"
:percentage="percent"
:show-text="false"
status="text"
status="success"
:color="color">
</el-progress>
<el-progress
+52
View File
@@ -307,12 +307,52 @@
/* Dialog */
.el-dialog {
background-color: $--dk-dialog-background;
.el-dialog__body {
color: $--dk-dialog-text-color;
}
}
.add-task-dialog .el-dialog__footer {
background-color: $--dk-add-task-dialog-footer-background;
}
.torrent-file-list {
border-color: $--dk-table-border-color;
}
.el-table {
background-color: $--dk-table-background;
color: $--dk-table-text-color;
tr {
background-color: $--dk-table-background;
}
th {
background-color: $--dk-table-th-background;
color: $--dk-table-text-color;
}
th.is-leaf, td {
background-color: $--dk-table-background;
color: $--dk-table-text-color;
border-bottom-color: $--dk-table-border-color;
}
}
.el-table thead th.is-leaf {
background-color: $--dk-table-th-background;
}
.el-table--enable-row-hover .el-table__body tr:hover > td,
.el-table--enable-row-hover .el-table__body tr.el-table__row--striped:hover > td {
background-color: $--dk-table-hover-background;
}
.el-table--group::after, .el-table--border::after, .el-table::before {
background-color: $--dk-table-border-color;
}
.el-table--striped .el-table__body tr.el-table__row--striped td {
background-color: $--dk-table-striped-background;
}
/* Tabs */
.el-tabs__item {
color: #a2a3a4;
@@ -332,4 +372,16 @@
.form-preference .el-switch__label {
color: $--dk-preference-form-text-color;
}
.form-preference .el-form-item {
a {
color: #dfdfdf;
&:hover {
color: #eee;
}
&:active {
color: #eee;
}
}
}
}
@@ -34,7 +34,7 @@ $--dk-panel-border-color: #EBECF0 !default;
-------------------------- */
$--dk-task-action-color: #eee !default;
$--dk-task-action-hover-color: $--color-primary !default;
$--dk-task-item-backgroud: #2D2D2D !default;
$--dk-task-item-backgroud: #2d2d2d !default;
$--dk-task-item-border-color: #555 !default;
$--dk-task-item-hover-border-color: $--color-primary !default;
$--dk-task-item-hover-background: $--color-primary !default;
@@ -50,7 +50,7 @@ $--dk-add-task-dialog-footer-background: #4a4a4a !default;
/* Preference
-------------------------- */
$--dk-preference-form-text-color: #dfdfdf;
$--dk-preference-form-text-color: #dfdfdf !default;
/* Speedometer
-------------------------- */
@@ -64,3 +64,10 @@ $--dk-speedometer-text-color: #9b9b9b !default;
/* Element UI
-------------------------- */
$--dk-dialog-background: #343434 !default;
$--dk-dialog-text-color: #9b9b9b !default;
$--dk-table-background: #2a2b2c !default;
$--dk-table-striped-background: #1f2122 !default;
$--dk-table-hover-background: #565656 !default;
$--dk-table-th-background: #1f2122 !default;
$--dk-table-text-color: #9b9b9b !default;
$--dk-table-border-color: #565656 !default;
+48 -5
View File
@@ -5,10 +5,34 @@ body {
}
body {
font-family: 'Monospaced Number', 'Chinese Quote', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', 'Helvetica Neue', Helvetica, Arial, sans-serif;
font-family: "Monospaced Number", "Chinese Quote", -apple-system,
BlinkMacSystemFont, "Segoe UI", Roboto, "PingFang SC", "Hiragino Sans GB",
"Microsoft YaHei", "Helvetica Neue", Helvetica, Arial, sans-serif;
font-variant: tabular-nums;
}
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-thumb {
border-radius: 8px;
background-color: rgba(0, 0, 0, 0.4);
}
::-webkit-scrollbar-thumb:window-inactive {
background-color: rgba(0, 0, 0, 0.25);
}
::-webkit-scrollbar-corner {
background: transparent;
}
::-webkit-resizer {
display: none;
}
img {
width: auto;
height: auto;
@@ -28,7 +52,7 @@ img {
.el-progress--line.is-text {
.el-progress-bar__inner::before {
content: '';
content: "";
opacity: 0;
position: absolute;
top: 0;
@@ -63,6 +87,9 @@ img {
.el-message__content {
line-height: 18px;
word-break: break-all;
a {
color: $--link-color;
}
}
.tab-title-dialog {
@@ -88,6 +115,17 @@ img {
color: $--color-info;
}
.el-input-number.el-input-number--mini {
&.is-controls-left .el-input__inner {
padding-left: 34px;
padding-right: 5px;
}
&.is-controls-right .el-input__inner {
padding-left: 5px;
padding-right: 34px;
}
}
/* App Main
-------------------------- */
#app,
@@ -101,6 +139,10 @@ img {
-webkit-user-select: none;
}
.non-draggable {
-webkit-app-region: no-drag;
}
.aside {
background-color: $--aside-background;
color: $--aside-text-color;
@@ -133,15 +175,16 @@ img {
li {
margin-bottom: 8px;
padding: 8px 10px;
font-size: 14px;
font-size: 0;
line-height: 20px;
border-radius: 3px;
cursor: pointer;
i,
span {
vertical-align: middle;
display: inline-block;
vertical-align: middle;
font-size: 14px;
}
&:hover,
@@ -191,7 +234,7 @@ img {
}
.panel-content {
position: relative;
padding: 16px 36px 52px;
padding: 16px 36px 64px;
height: 100%;
}
}
+12 -3
View File
@@ -18,6 +18,7 @@
import EngineClient from '@/components/Native/EngineClient'
import Ipc from '@/components/Native/Ipc'
import { mapState } from 'vuex'
import { getLangDirection } from '@shared/utils'
export default {
name: 'Motrix',
@@ -36,7 +37,8 @@
},
rpcSecret: state => state.config.rpcSecret,
theme: state => state.config.theme,
locale: state => state.config.locale
locale: state => state.config.locale,
dir: state => getLangDirection(state.config.locale)
}),
themeClass: function () {
if (this.theme === 'auto') {
@@ -47,13 +49,17 @@
},
i18nClass: function () {
return `i18n-${this.locale}`
},
dirClass: function () {
return `dir-${this.dir}`
}
},
methods: {
isRenderer: is.renderer,
updateRootClassName: function () {
const { themeClass = '', i18nClass = '' } = this
document.documentElement.className = `${themeClass} ${i18nClass}`
const { themeClass = '', i18nClass = '', dirClass = '' } = this
const className = `${themeClass} ${i18nClass} ${dirClass}`
document.documentElement.className = className
}
},
beforeMount: function () {
@@ -65,6 +71,9 @@
},
i18nClass: function (val, oldVal) {
this.updateRootClassName()
},
dirClass: function (val, oldVal) {
this.updateRootClassName()
}
}
}
+12 -3
View File
@@ -20,13 +20,14 @@ const state = {
downloadSpeed: 0,
uploadSpeed: 0,
numActive: 0,
numStopped: 0,
numWaiting: 0
numWaiting: 0,
numStopped: 0
},
addTaskVisible: false,
addTaskType: 'uri',
addTaskUrl: '',
addTaskTorrents: []
addTaskTorrents: [],
addTaskOptions: {}
}
const getters = {
@@ -60,6 +61,11 @@ const mutations = {
CHANGE_ADD_TASK_TORRENTS (state, fileList) {
state.addTaskTorrents = [...fileList]
},
UPDATE_ADD_TASK_OPTIONS (state, options) {
state.addTaskOptions = {
...options
}
},
UPDATE_INTERVAL (state, millisecond) {
let interval = millisecond
if (millisecond > MAX_INTERVAL) {
@@ -162,6 +168,9 @@ const actions = {
addTaskAddTorrents ({ commit }, { fileList }) {
commit('CHANGE_ADD_TASK_TORRENTS', fileList)
},
updateAddTaskOptions ({ commit }, options = {}) {
commit('UPDATE_ADD_TASK_OPTIONS', options)
},
updateInterval ({ commit }, millisecond) {
commit('UPDATE_INTERVAL', millisecond)
},
+5 -6
View File
@@ -1,4 +1,5 @@
import api from '@/api'
import { isEmpty } from 'lodash'
const state = {
engineMode: 'MAX',
@@ -21,13 +22,11 @@ const actions = {
})
})
},
save ({ commit }, data) {
let { btTracker } = data
btTracker = btTracker.trim().replace(/(?:\r\n|\r|\n)/g, ',')
const config = {
...data,
btTracker
save ({ commit }, config) {
if (isEmpty(config)) {
return
}
commit('UPDATE_PREFERENCE_DATA', config)
return api.savePreference(config)
},
+15 -1
View File
@@ -57,6 +57,7 @@ const actions = {
return api.addUri({ uris, options })
.then(() => {
dispatch('fetchList')
dispatch('app/updateAddTaskOptions', {}, { root: true })
})
},
addTorrent ({ dispatch }, data) {
@@ -64,6 +65,7 @@ const actions = {
return api.addTorrent({ torrent, options })
.then(() => {
dispatch('fetchList')
dispatch('app/updateAddTaskOptions', {}, { root: true })
})
},
addMetalink ({ dispatch }, data) {
@@ -71,8 +73,17 @@ const actions = {
return api.addMetalink({ metalink, options })
.then(() => {
dispatch('fetchList')
dispatch('app/updateAddTaskOptions', {}, { root: true })
})
},
getTaskOption (_, gid) {
return new Promise((resolve) => {
api.getOption({ gid })
.then((data) => {
resolve(data)
})
})
},
removeTask ({ dispatch }, task) {
const { gid } = task
return api.forcePauseTask({ gid })
@@ -124,7 +135,10 @@ const actions = {
})
},
removeTaskRecord ({ dispatch }, task) {
const { gid } = task
const { gid, status } = task
if (['error', 'complete', 'removed'].indexOf(status) === -1) {
return
}
return api.removeTaskRecord({ gid })
.finally(() => dispatch('fetchList'))
},
+23 -19
View File
@@ -1,25 +1,26 @@
const userKeys = [
'locale',
'cookie',
'resume-all-when-app-launched',
'task-notification',
'hide-app-menu',
'new-task-show-downloading',
'use-proxy',
'all-proxy-backup',
'log-path',
'session-path',
'enable-egg-features',
'theme',
'auto-check-update',
'last-check-update-time'
'cookie',
'enable-egg-features',
'hide-app-menu',
'keep-window-state',
'last-check-update-time',
'locale',
'log-path',
'new-task-show-downloading',
'open-at-login',
'resume-all-when-app-launched',
'session-path',
'task-notification',
'theme',
'use-proxy'
]
const systemKeys = [
'max-concurrent-downloads',
'all-proxy',
'all-proxy-passwd',
'all-proxy-user',
'all-proxy',
'allow-overwrite',
'allow-piece-length-change',
'always-resume',
@@ -42,10 +43,10 @@ const systemKeys = [
'bt-save-metadata',
'bt-seed-unverified',
'bt-stop-timeout',
'bt-tracker',
'bt-tracker-connect-timeout',
'bt-tracker-interval',
'bt-tracker-timeout',
'bt-tracker',
'check-integrity',
'checksum',
'conditional-get',
@@ -64,9 +65,9 @@ const systemKeys = [
'force-save',
'ftp-passwd',
'ftp-pasv',
'ftp-proxy',
'ftp-proxy-passwd',
'ftp-proxy-user',
'ftp-proxy',
'ftp-reuse-connection',
'ftp-type',
'ftp-user',
@@ -77,15 +78,16 @@ const systemKeys = [
'http-auth-challenge',
'http-no-cache',
'http-passwd',
'http-proxy',
'http-proxy-passwd',
'http-proxy-user',
'http-proxy',
'http-user',
'https-proxy',
'https-proxy-passwd',
'https-proxy-user',
'https-proxy',
'index-out',
'lowest-speed-limit',
'max-concurrent-downloads',
'max-connection-per-server',
'max-download-limit',
'max-file-not-found',
@@ -93,6 +95,8 @@ const systemKeys = [
'max-resume-failure-tries',
'max-tries',
'max-upload-limit',
'max-overall-download-limit',
'max-overall-upload-limit',
'metalink-base-uri',
'metalink-enable-unique-protocol',
'metalink-language',
@@ -106,8 +110,8 @@ const systemKeys = [
'no-proxy',
'out',
'parameterized-uri',
'pause',
'pause-metadata',
'pause',
'piece-length',
'proxy-method',
'realtime-chunk-checksum',
+6
View File
@@ -3,3 +3,9 @@ export const DARK_THEME = 'dark'
export const BEST_TRACKERS_URL = 'https://raw.githubusercontent.com/ngosang/trackerslist/master/trackers_best.txt'
export const BEST_TRACKERS_IP_URL = 'https://raw.githubusercontent.com/ngosang/trackerslist/master/trackers_best_ip.txt'
// One Week
export const AUTO_CHECK_UPDATE_INTERVAL = 7 * 24 * 60 * 60 * 1000
export const NONE_SELECTED_FILES = 'none'
export const SELECTED_ALL_FILES = 'all'
+1
View File
@@ -1,5 +1,6 @@
export default {
'engine-version': 'Engine Version',
'license': 'Lizenz',
'about': 'Über',
'release': 'Versionen',
'support': 'Unterstützung anfordern'
+9 -1
View File
@@ -3,11 +3,19 @@ export default {
'advanced': 'Erweitert',
'lab': 'Experimentell',
'save': 'Speichern & übernehmen',
'save-success-message': 'Einstellungen erfolgreich speichern',
'save-fail-message': 'Speichern der Einstellungen fehlgeschlagen',
'discard': 'Verwerfen',
'startup': 'Startup',
'open-at-login': 'Beim Login öffnen',
'keep-window-state': 'Stellen Sie die Größe und Position des Fensters wieder her',
'auto-resume-all': 'Alle nicht abgeschlossenen Aufgaben automatisch fortsetzen',
'default-dir': 'Standard Speicherort',
'default-dir': 'Standardpfad',
'mas-default-dir-tips': 'Aufgrund der Einschränkungen durch Sandbox-Berechtigungen im App Store wird der Download Ordner als Standard empfohlen',
'transfer-settings': 'Übertragung',
'transfer-speed-upload': 'Upload-Limit',
'transfer-speed-download': 'Download-Limit',
'transfer-speed-unlimited': 'Unbegrenzt',
'task-manage': 'Aufgaben verwalten',
'max-concurrent-downloads': 'Maximal aktive Aufgaben',
'max-connection-per-server': 'Maximale Verbindungen pro Server',
+7 -1
View File
@@ -9,8 +9,14 @@ export default {
'torrent-task': 'Torrent',
'uri-task-tips': 'Eine Download URL pro Zeile (magnet wird unterstützt)',
'thunder-link-tips': 'Tipp: Thunder Links werden möglicherweise nach dem dekodieren nicht heruntergeladen',
'new-task-uris-required': 'Bitte geben Sie mindestens eine gültige URL ein',
'new-task-torrent-required': 'Bitte wählen Sie eine Torrent-Datei',
'file-name': 'Dateiname',
'file-extension': 'Dateityp',
'file-size': 'Dateigröße',
'selected-files-sum': 'Ausgewählt: {{selectedFilesCount}} Dateien, insgesamt {{selectedFilesTotalSize}}',
'task-name': 'Aufgaben Name',
'task-out': 'Umbenennen',
'task-out': 'Dateiname',
'task-out-tips': 'Optional, unterstützt nur Einzelaufgaben',
'task-split': 'Splits',
'task-dir': 'Ordner',
+2 -1
View File
@@ -1,6 +1,7 @@
export default {
'engine-version': 'Engine Version',
'license': 'License',
'about': 'About',
'release': 'Release',
'release': 'Releases',
'support': 'Support'
}
+1 -1
View File
@@ -5,7 +5,7 @@ export default {
'preferences': 'Preferences...',
'check-for-updates': 'Check for Updates...',
'check-for-updates-title': 'Check for Updates',
'update-available-message': 'A new version of Motrix is available, update now?',
'update-available-message': 'A newer version of Motrix is available, update now?',
'update-not-available-message': 'You are up-to-date!',
'update-downloaded-message': 'Ready to install...',
'update-error-message': 'Update Error',
+25 -14
View File
@@ -3,18 +3,26 @@ export default {
'advanced': 'Advanced',
'lab': 'Lab',
'save': 'Save & Apply',
'save-success-message': 'Save preferences successfully',
'save-fail-message': 'Save preferences failed',
'discard': 'Discard',
'startup': 'Startup',
'auto-resume-all': 'Auto resume all unfinished tasks',
'default-dir': 'Default Dir',
'mas-default-dir-tips': 'Due to the sandbox permissions restrictions of the App Store, the default download dir is recommended to be set to Downloads directory',
'task-manage': 'Task Manage',
'open-at-login': 'Open at login',
'keep-window-state': 'Keep size and position of the window when exit',
'auto-resume-all': 'Automatically resume all unfinished tasks',
'default-dir': 'Default Path',
'mas-default-dir-tips': 'Due to sandbox permission restrictions of the App Store, the default download directory is recommended to be set to ~/Downloads',
'transfer-settings': 'Transmission',
'transfer-speed-upload': 'Upload limit',
'transfer-speed-download': 'Download limit',
'transfer-speed-unlimited': 'Unlimited',
'task-manage': 'Task Management',
'max-concurrent-downloads': 'Maximum active tasks',
'max-connection-per-server': 'Max connection per server',
'new-task-show-downloading': 'Auto show downloading after add task',
'max-connection-per-server': 'Maximum connection per server',
'new-task-show-downloading': 'Automatically show downloading after adding task',
'continue': 'Continue',
'task-completed-notify': 'Notification after download is complete',
'auto-purge-record': 'Auto purge download record when app exit',
'auto-purge-record': 'Automatically purge download records when exiting app',
'ui': 'UI',
'appearance': 'Appearance',
'theme-auto': 'Auto',
@@ -26,22 +34,25 @@ export default {
'proxy': 'Proxy',
'use-proxy': 'Enable Proxy',
'bt-tracker': 'Tracker Servers',
'bt-tracker-input-tips': 'Tracker server, one per line',
'bt-tracker-tips': 'Recommend: ',
'bt-tracker-input-tips': 'Tracker servers, one per line',
'bt-tracker-tips': 'Recommended: ',
'sync-tracker-tips': 'Sync from ngosang/trackerslist',
'security': 'Security',
'rpc-secret': 'RPC Secret',
'rpc-secret-tips': 'RPC Secret Manual',
'developer': 'Developer',
'mock-user-agent': 'Mock User-Agent',
'app-log-path': 'App log path',
'download-session-path': 'Download session path',
'factory-reset': 'Factory Reset',
'factory-reset-confirm': 'Are you sure you want to revert to factory settings?',
'lab-warning': '⚠️ Enabling lab features may cause app crashes or data loss, decide for yourself!',
'download-protocol': 'Protocol',
'support-more-download-protocols': 'Enable more download protocols support',
'lab-warning': '⚠️ Enabling lab features may result in app crash or data loss, decide at you own risk!',
'download-protocol': 'Protocols',
'support-more-download-protocols': 'Enable support for more download protocols',
'browser-extensions': 'Extensions',
'baidu-exporter': 'BaiduExporter',
'browser-extensions-tips': 'Provided by the community, ',
'baidu-exporter-help': 'Click here for usage',
'auto-check-update': 'Auto check update',
'last-check-update-time': 'Last Check Update Time'
'auto-check-update': 'Automatically check for update',
'last-check-update-time': 'Last Time Checking for Update'
}
+48 -42
View File
@@ -7,13 +7,19 @@ export default {
'open-file': 'Open Torrent File...',
'uri-task': 'URL',
'torrent-task': 'Torrent',
'uri-task-tips': 'One task url per line (support magnet)',
'thunder-link-tips': 'Tip: Thunder links may not be downloaded after decoding',
'uri-task-tips': 'One task url per line (supports magnet)',
'thunder-link-tips': 'Tip: Thunder links may not be downloadable after decoding',
'new-task-uris-required': 'Please enter at least one valid resource url',
'new-task-torrent-required': 'Please select a torrent file',
'file-name': 'File Name',
'file-extension': 'File Type',
'file-size': 'File Size',
'selected-files-sum': 'Selected: {{selectedFilesCount}} files, total size {{selectedFilesTotalSize}}',
'task-name': 'Task Name',
'task-out': 'Rename',
'task-out-tips': 'Optional, support single task',
'task-out-tips': 'Optional (only supports single task)',
'task-split': 'Splits',
'task-dir': 'Dir',
'task-dir': 'Save to',
'pause-task': 'Pause Task',
'task-ua': 'UA',
'task-user-agent': 'User-Agent',
@@ -22,61 +28,61 @@ export default {
'navigate-to-downloading': 'Navigate to Downloading',
'show-advanced-options': 'Advanced Options',
'copyright-warning': 'Copyright Warning',
'copyright-warning-message': 'The file you want to download may be copyrighted audio or video, please make sure you have the copyright license.',
'copyright-yes': 'Yes, I have',
'copyright-no': 'No',
'copyright-error-message': 'Adding task failed due to copyright issues',
'pause-task-success': 'Pause task "{{taskName}}" success',
'pause-task-fail': 'Pause task "{{taskName}}" fail',
'copyright-warning-message': 'The file you want to download may be copyrighted audio or video, please ensure that you have permission to access to it.',
'copyright-yes': 'Yes, I have permission',
'copyright-no': 'No, I don\'t have permission',
'copyright-error-message': 'Failed to add task due to copyright issue',
'pause-task-success': 'Successfully paused task "{{taskName}}"',
'pause-task-fail': 'Failed to pause task "{{taskName}}"',
'resume-task': 'Resume Task',
'resume-task-success': 'Resume task "{{taskName}}" success',
'resume-task-fail': 'Resume task "{{taskName}}" fail',
'resume-task-success': 'Successfully resumed task "{{taskName}}"',
'resume-task-fail': 'Failed to resume task "{{taskName}}"',
'delete-task': 'Delete Task',
'delete-selected-tasks': 'Delete Selected Tasks',
'delete-task-confirm': 'Are you sure to remove the "{{taskName}}" download task?',
'delete-task-confirm': 'Are you sure you want to remove download task "{{taskName}}"?',
'delete-task-label': 'Delete with Files',
'delete-task-success': 'Delete task "{{taskName}}" success',
'delete-task-fail': 'Delete task "{{taskName}}" fail',
'remove-task-file-fail': 'Failed to delete task file, please delete it manually',
'delete-task-success': 'Successfully deleted task "{{taskName}}"',
'delete-task-fail': 'Failed to delete task "{{taskName}}"',
'remove-task-file-fail': 'Failed to delete task file(s), please delete them manually',
'remove-task-config-file-fail': 'Failed to delete task config file, please delete it manually',
'move-task-up': 'Move Task Up',
'move-task-down': 'Move Task Down',
'pause-all-task': 'Pause All Task',
'pause-all-task-success': 'Pause all task success',
'pause-all-task-fail': 'Pause all task fail',
'resume-all-task': 'Resume All Task',
'resume-all-task-success': 'Resume all task success',
'resume-all-task-fail': 'Resume all task fail',
'pause-all-task': 'Pause All Tasks',
'pause-all-task-success': 'Successfully paused all tasks',
'pause-all-task-fail': 'Failed to pause all tasks',
'resume-all-task': 'Resume All Tasks',
'resume-all-task-success': 'Successfully resumed all tasks',
'resume-all-task-fail': 'Failed to resume all tasks',
'clear-recent-tasks': 'Clear Recent Tasks',
'purge-record': 'Purge Task Record',
'purge-record-success': 'Purge task record success',
'purge-record-fail': 'Purge task record fail',
'purge-record-success': 'Successfully purged task records',
'purge-record-fail': 'Failed to purge task records',
'refresh-list': 'Refresh Task List',
'no-task': 'There are no current downloads',
'no-task': 'There are no current tasks',
'copy-link': 'Copy Link',
'copy-link-success': 'Copy link success',
'copy-link-success': 'Successfully copied link',
'remove-record': 'Remove Task Record',
'remove-record-confirm': 'Are you sure to remove the "{{taskName}}" download record?',
'remove-record-confirm': 'Are you sure you want to remove download record for "{{taskName}}"?',
'remove-record-label': 'Delete with Files',
'remove-record-success': 'Remove task "{{taskName}}" record success',
'remove-record-fail': 'Remove task "{{taskName}}" record fail',
'remove-record-success': 'Successfully removed task record for "{{taskName}}"',
'remove-record-fail': 'Failed to remove task record for "{{taskName}}"',
'show-in-folder': 'Show Task In Folder',
'file-not-exist': 'File does not exist or has been deleted',
'file-not-exist': 'Target file does not exist or has been deleted',
'file-path-error': 'File path error',
'opening-task-message': 'Opening "{{taskName}}" ...',
'get-task-name': 'Get the task name...',
'get-task-name': 'Getting task name...',
'remaining-prefix': 'Remaining',
'select-torrent': 'Drag the torrent here, or click to select',
'task-info-dialog-title': '{{title}} Detail',
'download-start-message': 'Start download {{taskName}}',
'download-pause-message': 'Pause download {{taskName}}',
'download-stop-message': '{{taskName}} download stopped',
'download-error-message': '{{taskName}} download error occurred',
'download-complete-message': '{{taskName}} download completed',
'select-torrent': 'Drag torrent file here, or click to select',
'task-info-dialog-title': '{{title}} Details',
'download-start-message': 'Started downloading {{taskName}}',
'download-pause-message': 'Paused downloading {{taskName}}',
'download-stop-message': 'Stopped downloading {{taskName}}',
'download-error-message': 'Error occurred when downloading {{taskName}}',
'download-complete-message': 'Completed downloading {{taskName}}',
'download-complete-notify': 'Download Completed',
'bt-download-complete-message': '{{taskName}} download completed, seeding',
'bt-download-complete-notify': 'BT Download Completed, Seeding...',
'bt-download-complete-tips': 'Tips: You can stop the task to end the seeding',
'download-fail-message': '{{taskName}} download failed',
'bt-download-complete-message': 'Completed downloading {{taskName}}, seeding',
'bt-download-complete-notify': 'BT Download Completed, seeding...',
'bt-download-complete-tips': 'Tips: You can stop a task to end its seeding',
'download-fail-message': 'Failed to download {{taskName}}',
'download-fail-notify': 'Download Failed'
}
+1
View File
@@ -1,5 +1,6 @@
export default {
'engine-version': 'نسخه‌ی موتور',
'license': 'مجوز منبع باز',
'about': 'درباره ما',
'release': 'نسخه‌های منتشر شده',
'support': 'حمایت'
+8
View File
@@ -3,11 +3,19 @@ export default {
'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': 'بخاطر محدودیت‌های دسترسی سندباکس از اپ استور، پیشنهاد می‌شود فولدر دانلود پیشفرض ست شده باشد.',
'transfer-settings': 'انتقال',
'transfer-speed-upload': 'محدودیت آپلود',
'transfer-speed-download': 'محدودیت دانلود',
'transfer-speed-unlimited': 'نامحدود',
'task-manage': 'مدیریت تسک',
'max-concurrent-downloads': 'حداکثر تسک‌های فعال',
'max-connection-per-server': 'حداکثر اتصال‌ برای هر سرور',
+6
View File
@@ -9,6 +9,12 @@ export default {
'torrent-task': 'تورنت',
'uri-task-tips': 'یک آدرس دانلود در هر خط',
'thunder-link-tips': 'نکته: لینک‌های تاندر ممکن هست بعد دیکد کردن دانلود نشوند',
'new-task-uris-required': 'لطفا حداقل یک آدرس دانلود معتبر وارد کنید',
'new-task-torrent-required': 'لطفا فایل تورنت را انتخاب کنید',
'file-name': 'نام فایل',
'file-extension': 'امتداد',
'file-size': 'اندازه',
'selected-files-sum': 'انتخاب شده: {{selectedFilesCount}} فایلها، مجموع {{selectedFilesTotalSize}}',
'task-name': 'اسم تسک',
'task-out': 'تغییرنام',
'task-out-tips': 'اختیاری، حمایت تک تسک',
+1
View File
@@ -1,5 +1,6 @@
export default {
'engine-version': 'Version du moteur',
'license': 'Licence',
'about': 'À Propos',
'release': 'Release',
'support': 'Support'
+8
View File
@@ -3,11 +3,19 @@ export default {
'advanced': 'Avancé',
'lab': 'Labo',
'save': 'Sauver et appliquer',
'save-success-message': 'Enregistrer les préférences avec succès',
'save-fail-message': 'La sauvegarde des préférences a échoué',
'discard': 'Annuler les changement',
'startup': 'Démarrage',
'open-at-login': 'Ouvrir à la connexion',
'keep-window-state': 'Restaurez la taille et la position de la fenêtre',
'auto-resume-all': 'Reprendre les tâches non terminées',
'default-dir': 'Répertoire par défaut',
'mas-default-dir-tips': 'En raison des restrictions d\'autorisations du bac à sable de l\'App Store, il est recommandé de définir le répertoire de téléchargement par défaut sur le répertoire Téléchargements.',
'transfer-settings': 'Transmission',
'transfer-speed-upload': 'Limite de téléchargement',
'transfer-speed-download': 'Limite de téléchargement',
'transfer-speed-unlimited': 'Unlimited',
'task-manage': 'Tâches',
'max-concurrent-downloads': 'Nombre de tâches active au maximum',
'max-connection-per-server': 'Nombre maximum de connexions par serveurs',
+6
View File
@@ -9,6 +9,12 @@ export default {
'torrent-task': 'Torrent',
'uri-task-tips': 'Un lien par ligne (supporte les magnets)',
'thunder-link-tips': 'Astuce: Les liens Thunder ne doivent pas être téléchargés après décodage',
'new-task-uris-required': 'Veuillez entrer au moins une URL de ressource valide',
'new-task-torrent-required': 'Veuillez sélectionner un fichier torrent',
'file-name': 'Nom de fichier',
'file-extension': 'Ext',
'file-size': 'Taille',
'selected-files-sum': 'Sélectionné: {{selectedFilesCount}} fichiers, total {{selectedFilesTotalSize}}',
'task-name': 'Nom de la tâche',
'task-out': 'Renommer',
'task-out-tips': 'Optionel, supporte une seule tâche',
+1
View File
@@ -1,5 +1,6 @@
export default {
'engine-version': 'バージョンを確認',
'license': 'ライセンス',
'about': '私たちについて',
'release': 'リリースノート',
'support': 'サポート'
+8
View File
@@ -3,11 +3,19 @@ export default {
'advanced': '詳細設定',
'lab': '実験室',
'save': '保存して適用',
'save-success-message': '設定を保存します',
'save-fail-message': '設定を保存できませんでした',
'discard': '放棄',
'startup': '起動',
'open-at-login': '自動的に起動します',
'auto-resume-all': '起動後自動的に未完了タスクを再開',
'keep-window-state': 'ウィンドウのサイズと位置を元に戻します',
'default-dir': '既定の保存先',
'mas-default-dir-tips': 'App Store の Sandbox 制限のため,デフォルトディレクトリ設定は「Download」を推奨します。',
'transfer-settings': '転送設定',
'transfer-speed-upload': 'アップロード制限',
'transfer-speed-download': 'ダウンロード制限',
'transfer-speed-unlimited': '無制限',
'task-manage': 'タスク管理',
'max-concurrent-downloads': '最大同時タスク数',
'max-connection-per-server': '最大サーバ接続数',
+6
View File
@@ -9,6 +9,12 @@ export default {
'torrent-task': 'torrentタスク',
'uri-task-tips': 'URLを複数追加したとき、一行につき一つのリンクとなります(マグネットリンクをサポート)',
'thunder-link-tips': '注意:Thunder(Xunlei)リンク解析後、ダウンロードできるかは保証できません',
'new-task-uris-required': '少なくとも1つの有効なリソースURLを入力してください',
'new-task-torrent-required': 'トレントファイルを選択してください',
'file-name': 'ファイル名',
'file-extension': '拡張子',
'file-size': 'サイズ',
'selected-files-sum': '選択済み:{{selectedFilesCount}}ファイル、合計{{selectedFilesTotalSize}}',
'task-name': 'タスク名',
'task-out': '名前を変更',
'task-out-tips': 'オプション(単独タスクのみサポート)',
+1
View File
@@ -1,5 +1,6 @@
export default {
'engine-version': '엔진 버전',
'license': '라이선스',
'about': '정보',
'release': '배포',
'support': '지원'
+8
View File
@@ -3,11 +3,19 @@ export default {
'advanced': '고급',
'lab': '실험실',
'save': '저장 & 적용',
'save-success-message': '환경 설정을 성공적으로 저장 합니다',
'save-fail-message': '환경 설정 저장 실패',
'discard': '포기',
'startup': '시작',
'open-at-login': '로그인 시 엽니다',
'auto-resume-all': '완료되지 않은 모든 작업 자동 재개',
'keep-window-state': '창의 크기와 위치를 복원합니다',
'default-dir': '기본 폴더',
'mas-default-dir-tips': '앱스토어의 샌드박스 사용 권한 제한으로 인해 기본 다운로드 폴더를 다운로드 폴더로 설정하는 것이 권장됩니다.',
'transfer-settings': '전송 설정',
'transfer-speed-upload': '업로드 제한',
'transfer-speed-download': '다운로드 제한',
'transfer-speed-unlimited': '무제한',
'task-manage': '작업 관리',
'max-concurrent-downloads': '최대 활성 작업',
'max-connection-per-server': '서버당 최대 연결 수',
+6
View File
@@ -9,6 +9,12 @@ export default {
'torrent-task': '토렌트',
'uri-task-tips': '한 줄의 하나의 작업 URL (마그넷 지원)',
'thunder-link-tips': '팁 : 디코딩 후 썬더 링크가 다운로드 되지 않을 수 있습니다.',
'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': '선택적, 단일 작업 지원',
+1
View File
@@ -1,5 +1,6 @@
export default {
'engine-version': 'Versão da Engine',
'license': 'Licença',
'about': 'Sobre',
'release': 'Release',
'support': 'Suporte'
+8
View File
@@ -3,11 +3,19 @@ export default {
'advanced': 'Avançado',
'lab': 'Lab',
'save': 'Salvar & Aplicar',
'save-success-message': 'Salvar preferências com sucesso',
'save-fail-message': 'Salvar preferências falhadas',
'discard': 'Descartar',
'startup': 'Inicialização',
'open-at-login': 'Abra no login',
'keep-window-state': 'Restaurar tamanho e posição da janela',
'auto-resume-all': 'Auto resumir todas as tarefas não finalizadas',
'default-dir': 'Diretório Padrão',
'mas-default-dir-tips': 'Devido às restrições de permissões do sandbox da App Store, recomenda-se que o diretório de download padrão seja definido como o diretório de downloads',
'transfer-settings': 'Transmissão',
'transfer-speed-upload': 'limite de envio',
'transfer-speed-download': 'limite de transferência',
'transfer-speed-unlimited': 'Ilimitado',
'task-manage': 'Gerenciador de Tarefas',
'max-concurrent-downloads': 'Máximo de tarefas ativas',
'max-connection-per-server': 'Máximo de coneções por servidor',
+6
View File
@@ -9,6 +9,12 @@ export default {
'torrent-task': 'Torrent',
'uri-task-tips': 'Uma url por linha (magnet links são aceitos)',
'thunder-link-tips': 'Dica: Thunder links não podem ser baixados após decodificados',
'new-task-uris-required': 'Por favor, insira pelo menos um URL de recurso válido',
'new-task-torrent-required': 'Por favor, selecione um arquivo torrent',
'file-name': 'Nome do arquivo',
'file-extension': 'Ext',
'file-size': 'Tamanho',
'selected-files-sum': 'Selecionado: {{selectedFilesCount}} arquivos, total {{selectedFilesTotalSize}}',
'task-name': 'Nome da Tarefa',
'task-out': 'Renomear',
'task-out-tips': 'Opcional, suporte únicc tarefa',
+1
View File
@@ -1,5 +1,6 @@
export default {
'engine-version': 'Engine Versiyonu',
'license': 'Lisans',
'about': 'Hakkında',
'release': 'Sürüm',
'support': 'Destek'
+8
View File
@@ -3,11 +3,19 @@ export default {
'advanced': 'Gelişmiş',
'lab': 'Deneysel',
'save': 'Kaydet & Uygula',
'save-success-message': 'Tercihleri başarıyla kaydedin',
'save-fail-message': 'Tercihleri kaydetme başarısız oldu',
'discard': 'İptal Et',
'startup': 'Başlangıçta',
'open-at-login': 'Giriş sırasında aç',
'keep-window-state': 'Pencerenin boyutunu ve konumunu geri yükleyin',
'auto-resume-all': 'Tüm bitmemiş görevleri otomatik olarak devam ettir',
'default-dir': 'Varsayılan Klasör',
'mas-default-dir-tips': 'App Store\'un sanal alan izinleri kısıtlamaları nedeniyle, varsayılan indirme dizininin İndirilenler dizinine ayarlanması önerilir.',
'transfer-settings': 'İletim',
'transfer-speed-upload': 'Yükleme limiti',
'transfer-speed-download': 'İndirme limiti',
'transfer-speed-unlimited': 'Sınırsız',
'task-manage': 'Görev Yöneticisi',
'max-concurrent-downloads': 'Maksimum aktif görev',
'max-connection-per-server': 'Sunucu başına maksimum bağlantı',
+7 -1
View File
@@ -9,8 +9,14 @@ export default {
'torrent-task': 'Torrent',
'uri-task-tips': 'Her bir satır için bir görev (magnet destekli)',
'thunder-link-tips': 'İpucu: Thunder bağlantıları kod çözme işleminden sonra indirilemeyebilir',
'new-task-uris-required': 'Lütfen en az bir geçerli kaynak URL adresi girin',
'new-task-torrent-required': 'Lütfen bir torrent dosyası seçin',
'file-name': 'Dosya Adı',
'file-extension': 'uzantı',
'file-size': 'Boyut',
'selected-files-sum': 'Seçildi: {{selectedFilesCount}} dosya sayısı, total {{selectedFilesTotalSize}}',
'task-name': 'Görev Adı',
'task-out': 'Yeniden Adlandır',
'task-out': 'Dosya Adı',
'task-out-tips': 'Opsiyonel, tek görev destekle',
'task-split': 'Parça',
'task-dir': 'Yol',
+1
View File
@@ -1,5 +1,6 @@
export default {
'engine-version': '引擎版本',
'license': '开源许可',
'about': '关于我们',
'release': '更新日志',
'support': '帮助支持'
+9 -1
View File
@@ -3,11 +3,19 @@ export default {
'advanced': '进阶设置',
'lab': '实验室',
'save': '保存并应用',
'save-success-message': '偏好设置保存成功',
'save-fail-message': '偏好设置保存失败',
'discard': '放弃',
'startup': '启动',
'auto-resume-all': '启动后自动开始未完成任务',
'open-at-login': '开机自动启动',
'keep-window-state': '恢复上次退出时窗口的大小和位置',
'auto-resume-all': '自动开始未完成的任务',
'default-dir': '默认下载路径',
'mas-default-dir-tips': '因 App Store 的沙箱权限限制,默认下载路径建议设置为您的「下载」目录',
'transfer-settings': '传输设置',
'transfer-speed-upload': '上传限速',
'transfer-speed-download': '下载限速',
'transfer-speed-unlimited': '不限速',
'task-manage': '任务管理',
'max-concurrent-downloads': '同时下载的最大任务数',
'max-connection-per-server': '每个服务器最大连接数',
+6
View File
@@ -9,6 +9,12 @@ export default {
'torrent-task': '种子任务',
'uri-task-tips': '添加多个下载链接时,请确保每行只有一个链接(支持磁力链)',
'thunder-link-tips': '友情提示:迅雷链接解码之后的资源不一定存在',
'new-task-uris-required': '请至少输入一个有效的下载地址',
'new-task-torrent-required': '请先选择种子文件',
'file-name': '文件名',
'file-extension': '类型',
'file-size': '大小',
'selected-files-sum': '已选:{{selectedFilesCount}}个文件,共 {{selectedFilesTotalSize}}',
'task-name': '任务名',
'task-out': '重命名',
'task-out-tips': '选填(仅支持单任务)',
+1
View File
@@ -1,5 +1,6 @@
export default {
'engine-version': '引擎版本',
'license': '開源許可',
'about': '關於',
'release': '版本資訊',
'support': '支援'
+17 -9
View File
@@ -3,15 +3,23 @@ export default {
'advanced': '進階設定',
'lab': '實驗性功能',
'save': '儲存並套用',
'discard': '拋棄',
'save-success-message': '偏好設定保存成功',
'save-fail-message': '偏好設定保存失敗',
'discard': '捨棄',
'startup': '啟動',
'auto-resume-all': '啟動後自動繼續未完成任務',
'open-at-login': '開機自動啟動',
'keep-window-state': '恢復上次退出時視窗的大小和位置',
'auto-resume-all': '自動繼續未完成的任務',
'default-dir': '預設下載路徑',
'mas-default-dir-tips': '因 App Store 的沙盒權限限制,建議將預設下載路徑設定為您的「下載」資料夾',
'transfer-settings': '傳輸設置',
'transfer-speed-upload': '上傳限制',
'transfer-speed-download': '下載限制',
'transfer-speed-unlimited': '無限制',
'task-manage': '任務管理',
'max-concurrent-downloads': '最多可同時下載的任務數',
'max-connection-per-server': '每台伺服器的最大連線數',
'new-task-show-downloading': '新增任務後自動顯示下載頁',
'new-task-show-downloading': '新增任務後自動顯示下載頁',
'continue': '中斷續傳',
'task-completed-notify': '下載完成後通知',
'auto-purge-record': '當結束程式時自動清除下載紀錄',
@@ -32,16 +40,16 @@ export default {
'developer': '開發者',
'mock-user-agent': '模擬使用者代理(UA',
'app-log-path': '應用程式記錄檔位置',
'download-session-path': '下載會談路徑',
'factory-reset': '回復出廠預設值',
'factory-reset-confirm': '您確定要回復成出廠預設值嗎?',
'lab-warning': '⚠️開啟實驗性功能可能會造成程式崩潰或資料失,請自己決定!',
'download-session-path': '下載工作階段路徑',
'factory-reset': '還原出廠預設值',
'factory-reset-confirm': '您確定要還原為出廠預設值嗎?',
'lab-warning': '⚠️開啟實驗性功能可能會造成程式當機或資料失,請自己決定!',
'download-protocol': '下載協定',
'support-more-download-protocols': '支援更多下載協定',
'browser-extensions': '瀏覽器擴充功能',
'baidu-exporter': '百度网盘助手',
'baidu-exporter': '百度網盤助手',
'browser-extensions-tips': '社群提供的瀏覽器擴充功能「不保證可用性」,',
'baidu-exporter-help': '点此查看使用明',
'baidu-exporter-help': '點此檢視使用明',
'auto-check-update': '自動檢查更新',
'last-check-update-time': '上次檢查更新時間'
}
+1 -1
View File
@@ -1,4 +1,4 @@
export default {
'task-list': '任務列表',
'task-list': '任務清單',
'preferences': '偏好設定'
}
+16 -10
View File
@@ -4,11 +4,17 @@ export default {
'stopped': '已停止',
'new-task': '新增任務',
'new-bt-task': '新增 BT 任務',
'open-file': '打開種子文件...',
'open-file': '開啟種子檔案...',
'uri-task': '連結任務',
'torrent-task': '種子任務',
'uri-task-tips': '新增多個下載連結時,請確保每行只有一個連結(支援磁力連結)',
'thunder-link-tips': '友情提示:迅雷連結解碼之後的資源不一定存在',
'new-task-uris-required': '請至少輸入一個有效的資源網址',
'new-task-torrent-required': '請先選擇種子文件',
'file-name': '文件名',
'file-extension': '擴展名',
'file-size': '大小',
'selected-files-sum': '選中:{{selectedFilesCount}}個文件,總計 {{selectedFilesTotalSize}}',
'task-name': '任務名稱',
'task-out': '重新命名',
'task-out-tips': '選填(僅支援單一任務)',
@@ -18,13 +24,13 @@ export default {
'task-user-agent': 'User-Agent',
'task-referer': 'Referer',
'task-cookie': 'Cookie',
'navigate-to-downloading': '跳轉到下載頁面',
'navigate-to-downloading': '前往下載頁面',
'show-advanced-options': '進階選項',
'copyright-warning': '版權警告',
'copyright-warning-message': '您要下載的檔案可能是有版權的音訊視訊,請確保您有相應的版權方授權。',
'copyright-yes': '是,我有版權方授權',
'copyright-no': '否',
'copyright-error-message': '因版權问题,新增任務失敗',
'copyright-error-message': '因版權問題,新增任務失敗',
'pause-task': '暫停任務',
'pause-task-success': '暫停任務 "{{taskName}}" 成功',
'pause-task-fail': '暫停任務 "{{taskName}}" 失敗',
@@ -34,11 +40,11 @@ export default {
'delete-task': '移除任務',
'delete-selected-tasks': '移除選取的任務',
'delete-task-confirm': '你確定要移除 "{{taskName}}" 下載任務嗎?',
'delete-task-label': '同時除檔案',
'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': '暫停所有任務',
@@ -57,16 +63,16 @@ export default {
'copy-link-success': '複製連結成功',
'remove-record': '移除下載紀錄',
'remove-record-confirm': '你確定要移除 "{{taskName}}" 下載紀錄吗?',
'remove-record-label': '同时除檔案',
'remove-record-label': '同时除檔案',
'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': '取任務名稱中...',
'get-task-name': '取任務名稱中...',
'remaining-prefix': '剩下',
'select-torrent': '將種子拖曳此,或點選來選取',
'select-torrent': '將種子拖曳此,或點選來選取',
'task-info-dialog-title': '{{title}} 詳細資訊',
'download-start-message': '開始下載 {{taskName}}',
'download-pause-message': '暫停下載 {{taskName}}',
+164 -8
View File
@@ -1,12 +1,15 @@
import {
isEmpty,
isNaN,
camelCase,
compact,
difference,
parseInt,
isArray,
isEmpty,
isFunction,
camelCase,
kebabCase
isNaN,
kebabCase,
omitBy,
parseInt,
pick
} from 'lodash'
import { resolve } from 'path'
import { userKeys, systemKeys } from './configKeys'
@@ -176,6 +179,11 @@ export function isMagnetTask (task) {
return bittorrent && !bittorrent.info
}
export function checkTaskIsSeeder (task) {
const { bittorrent, seeder } = task
return !!bittorrent && seeder
}
export function getTaskUri (task, btTracker = []) {
const { files } = task
let result = ''
@@ -301,6 +309,7 @@ export function separateConfig (options) {
const system = {}
// others
const others = {}
for (const [k, v] of Object.entries(options)) {
if (userKeys.indexOf(k) !== -1) {
user[k] = v
@@ -327,15 +336,77 @@ export function splitTextRows (text = '') {
return result
}
export function convertToTextRows (text = '') {
export function convertCommaToLine (text = '') {
let arr = text.split(',')
arr = arr.map((row) => row.trim())
const result = arr.join('\n')
return result
}
const audioSuffix = ['.aac', '.mp3', '.ogg', '.ape', '.flac', '.m4a', '.wav', '.wma', '.flav']
const videoSuffix = ['.avi', '.mkv', '.rmvb', '.wmv', '.mp4', '.m4a', '.vob', '.mov', '.mpg']
export function convertLineToComma (text = '') {
const result = text.trim().replace(/(?:\r\n|\r|\n)/g, ',')
return result
}
export const imageSuffix = [
'.ai',
'.bmp',
'.eps',
'.gif',
'.icn',
'.ico',
'.jpeg',
'.jpg',
'.png',
'.psd',
'.raw',
'.sketch',
'.svg',
'.tif',
'.webp',
'.xd'
]
export const audioSuffix = [
'.aac',
'.ape',
'.flac',
'.flav',
'.m4a',
'.mp3',
'.ogg',
'.wav',
'.wma'
]
export const videoSuffix = [
'.avi',
'.m4a',
'.mkv',
'.mov',
'.mp4',
'.mpg',
'.rmvb',
'.vob',
'.wmv'
]
export function filterVideoFiles (files = []) {
return files.filter((item) => {
return videoSuffix.includes(item.extension)
})
}
export function filterAudioFiles (files = []) {
return files.filter((item) => {
return audioSuffix.includes(item.extension)
})
}
export function filterImageFiles (files = []) {
return files.filter((item) => {
return imageSuffix.includes(item.extension)
})
}
export function isAudioOrVideo (uri = '') {
const suffixs = [...audioSuffix, ...videoSuffix]
const result = suffixs.some((suffix) => {
@@ -394,3 +465,88 @@ export function buildFileList (rawFile) {
const fileList = [file]
return fileList
}
const supportRtlLocales = [
/* 'العربية', Arabic */
'ar',
/* 'فارسی', Persian */
'fa',
/* 'עברית', Hebrew */
'he',
/* 'Kurdî / كوردی', Kurdish */
'ku',
/* 'پنجابی', Western Punjabi */
'pa',
/* 'پښتو', Pashto, */
'ps',
/* 'سنڌي', Sindhi */
'sd',
/* 'اردو', Urdu */
'ur',
/* 'ייִדיש', Yiddish */
'yi'
]
export function isRTL (locale = 'en-US') {
return supportRtlLocales.includes(locale)
}
export function getLangDirection (locale = 'en-US') {
return isRTL(locale) ? 'rtl' : 'ltr'
}
export function listTorrentFiles (files) {
const result = files.map((file, index) => {
const extension = getFileExtension(file.path)
const item = {
// aria2 select-file start index at 1
// possible Values: 1-1048576
idx: index + 1,
extension: `.${extension}`,
...file
}
return item
})
return result
}
export function getFileExtension (filename) {
return filename.slice((filename.lastIndexOf('.') - 1 >>> 0) + 2)
}
export function removeExtensionDot (extension = '') {
return extension.replace('.', '')
}
export function diffConfig (current = {}, next = {}) {
const curr = pick(current, Object.keys(next))
const result = omitBy(next, (val, key) => {
if (isArray(val)) {
return false
}
return curr[key] === val
})
return result
}
export function calcFormLabelWidth (locale) {
return locale.startsWith('de') ? '28%' : '23%'
}
export function parseHeader (header = '') {
header = header.trim()
let result = {}
if (!header) {
return result
}
const headers = splitTextRows(header)
headers.forEach((line) => {
const index = line.indexOf(':')
const name = line.substr(0, index)
const value = line.substr(index + 1).trim()
result[name] = value
})
result = changeKeysToCamelCase(result)
return result
}