Compare commits
448 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1a2de2ad88 | |||
| e44587035d | |||
| 43f1adcd84 | |||
| 8a900459b5 | |||
| d2f8a4599d | |||
| 9159fb36ab | |||
| 5be05395f5 | |||
| fba76df945 | |||
| 5bb4f259cd | |||
| 4b093a1a4f | |||
| 134d81b205 | |||
| d5fc962bca | |||
| 0d1b76d523 | |||
| 6499ce91b8 | |||
| 2d7907f218 | |||
| 97c1cb0418 | |||
| be585ef102 | |||
| 03002221e7 | |||
| 4b8de781a4 | |||
| 8224423d8c | |||
| 1e05e1e4f3 | |||
| bfd1e1b6fa | |||
| 79495cbde5 | |||
| a93d183988 | |||
| cf082b20d8 | |||
| 8110409402 | |||
| 37b446b6f0 | |||
| 97801fff32 | |||
| 4ca4f5e606 | |||
| e9caf1b0b4 | |||
| d67dca00b5 | |||
| 038536259e | |||
| ec21cc57bc | |||
| 5a42e4014b | |||
| 02d4032283 | |||
| d676dbe213 | |||
| 02d83534d1 | |||
| 8b531c76b8 | |||
| 22f92df299 | |||
| eaf91e15e8 | |||
| 4535cb706a | |||
| a093f3beca | |||
| 287c9038b1 | |||
| 72aac9c0c6 | |||
| cdac88371c | |||
| 678bed53dc | |||
| 1f805c3440 | |||
| c8dcb1023f | |||
| a0e7eb84ca | |||
| fdf0bb8320 | |||
| e2338757de | |||
| c0cbfb5cb2 | |||
| fddaa72ac6 | |||
| 1e531f166b | |||
| 97f54adc49 | |||
| e6548d5548 | |||
| 137fdf2b26 | |||
| 10ef4f7318 | |||
| bdd5e56c2a | |||
| a851ec5830 | |||
| 2ae3642c62 | |||
| 7b41a9e1c6 | |||
| 012e5ece00 | |||
| db8d3d1aec | |||
| 8e35dde082 | |||
| 5c2acf12ee | |||
| e710ccdb34 | |||
| 5ecc08d66d | |||
| e7b1a898fa | |||
| 93acf1ba63 | |||
| 1164df87e3 | |||
| 71232603a5 | |||
| 286013b2f0 | |||
| a5f0236661 | |||
| 87857eba94 | |||
| 3059d1a0b7 | |||
| 5029f98153 | |||
| ce80af7ead | |||
| 6df52988b0 | |||
| 6359c729ad | |||
| 04db94daa6 | |||
| d41d809f00 | |||
| efdfae37b5 | |||
| 1a097c02e3 | |||
| 0217b5573a | |||
| 86611604d5 | |||
| 0bd8ab3d11 | |||
| e9eadb2eba | |||
| 72afd845ba | |||
| 91670fac37 | |||
| bc14e5b287 | |||
| 2d01431e7d | |||
| 81531ef9fc | |||
| 04126363ca | |||
| bcb07656b9 | |||
| b9cd991fd5 | |||
| 1b2b22f039 | |||
| 170a481d48 | |||
| 48bf7c9e2e | |||
| 03cfb013e5 | |||
| b4e23d8805 | |||
| 43965c5740 | |||
| 5c8d2e4ca4 | |||
| b51c144ace | |||
| feb839b7fa | |||
| fa36397afa | |||
| 583fe0ffe2 | |||
| 8b3a5fbaa8 | |||
| 85493c263b | |||
| 78e106abe4 | |||
| bbf90e9344 | |||
| dc30c25a83 | |||
| e1cce4f50c | |||
| 61654ff0ff | |||
| 0142aedfa5 | |||
| 323f85ca40 | |||
| f35e696c6a | |||
| d49cbcdc00 | |||
| c85ac4e895 | |||
| f831cc51ed | |||
| d47eb9ba0f | |||
| 119a374db6 | |||
| d26808e43a | |||
| 8cb3e54be2 | |||
| c55902cda7 | |||
| 426b54201b | |||
| 6c032cde2d | |||
| eb42ac6cfd | |||
| 90f0a7c5f6 | |||
| 9b61dad2c3 | |||
| 671fb82e6a | |||
| b8f444ca4d | |||
| 7509442e4e | |||
| ee2e3de782 | |||
| c6dd1e333e | |||
| fba761a29c | |||
| 4fb94ae8bb | |||
| 79721822c2 | |||
| 556f58f6b3 | |||
| e690f15335 | |||
| 1876587973 | |||
| b89c651132 | |||
| ad9d3ed15a | |||
| 6aa362332a | |||
| 18d107c062 | |||
| ebeba4663c | |||
| 9bff4257a8 | |||
| e07ef7dd11 | |||
| ebb0926d1d | |||
| 5333438825 | |||
| 3664341822 | |||
| d3cbff6dd1 | |||
| 5668b0773d | |||
| b757112c30 | |||
| dc3a01fb3c | |||
| 591de11072 | |||
| 853c361e6d | |||
| b0ebc737e2 | |||
| bc9db25dfd | |||
| 08c9902759 | |||
| 8d2f52710d | |||
| dd48fd79db | |||
| 9e7ad10309 | |||
| 6e17102210 | |||
| 5ed3b68a57 | |||
| a705427ce2 | |||
| c37f7c73ce | |||
| efcf01d59b | |||
| 324b61567d | |||
| d1e24fced0 | |||
| cb50d6709b | |||
| 900ac4c8d1 | |||
| 6696b0a538 | |||
| 110c83bcb8 | |||
| 136b4969bd | |||
| a901fdd2b6 | |||
| 215bdbc416 | |||
| 4e8d226460 | |||
| 23f3357d27 | |||
| 7adafab7fe | |||
| e3afb14e4a | |||
| 228cd1fa7d | |||
| 47a7464bf9 | |||
| 8efd86af3d | |||
| ee78ae262e | |||
| 7562f6a7d1 | |||
| defacd50e8 | |||
| f5ed0d89cc | |||
| a3bb1c7ff1 | |||
| d0085b295d | |||
| 6cb278fd9e | |||
| aeeb9813ed | |||
| 359da895e7 | |||
| 8a3f510c38 | |||
| 1345994402 | |||
| c770108a6c | |||
| 178f2103f1 | |||
| d5c71b50e8 | |||
| ce09e76ffb | |||
| 4688b521fa | |||
| 33960dc264 | |||
| d29d0d3321 | |||
| f4c3e7be69 | |||
| 338b51975c | |||
| e1a287350d | |||
| afeee1fc86 | |||
| a39820f8ab | |||
| aea604278c | |||
| 0b92e6b96e | |||
| 2ba0040bdd | |||
| 04bf2b6b9d | |||
| cc367f4c5c | |||
| 73809a6501 | |||
| 5067ad7306 | |||
| d5b3ad5933 | |||
| 7a903e112c | |||
| 829eb83a4c | |||
| 886acc2486 | |||
| 87440701ab | |||
| e880430e3a | |||
| 2febff883b | |||
| 7d8bb443b8 | |||
| 1489d58a63 | |||
| 00fdafe824 | |||
| 81c91e8cb1 | |||
| 0fd34254de | |||
| d9298f0d46 | |||
| 83e12a699e | |||
| 83b55b806d | |||
| d4dab6de9b | |||
| deae5e8e33 | |||
| b098c8c9bc | |||
| b12553cc45 | |||
| 8891b9a79c | |||
| a44f088139 | |||
| 7ea0cd3e75 | |||
| c93cc9d23b | |||
| 8d2e14486d | |||
| 3775da5e36 | |||
| 2e21285f99 | |||
| 6c2dde8e59 | |||
| e014580512 | |||
| e970a51b71 | |||
| 019f8be306 | |||
| 20f79dacb7 | |||
| 1bdd17a80a | |||
| b01e006073 | |||
| 82728a2b23 | |||
| 217d2df5be | |||
| 960f0674ef | |||
| 54e9f326d9 | |||
| 4e7267028f | |||
| efd7f97a36 | |||
| 5bb790822d | |||
| 71f3fb601b | |||
| af710aa265 | |||
| 52b6fb0849 | |||
| b8d63374b7 | |||
| ff38d4ea5c | |||
| a24a7acfaf | |||
| 16e9c8d848 | |||
| f36cdccf9f | |||
| c826a7dd77 | |||
| b96518a42c | |||
| 02f0b66500 | |||
| 377bc47d87 | |||
| f8a4ef267d | |||
| baea3f0345 | |||
| 1fcf0bb0d0 | |||
| 85e1acfe5b | |||
| 41ac6ad335 | |||
| b151c45e70 | |||
| b3686fff04 | |||
| af81107b3d | |||
| 88224998b7 | |||
| 8508ca9b3c | |||
| 6fad6a876c | |||
| 6c8a844cd9 | |||
| 4657a503f9 | |||
| 324347e591 | |||
| 16c376c108 | |||
| 79cad28cc1 | |||
| ac16bd91c2 | |||
| fafeaca47d | |||
| 3152252fb8 | |||
| a5240bfeb0 | |||
| 64cda63587 | |||
| fe071a3783 | |||
| 5d812e9c4e | |||
| f36ba605e2 | |||
| e7ed58e394 | |||
| be789e7ea8 | |||
| 780a01e404 | |||
| b45518a5b6 | |||
| 2401d68a24 | |||
| 6cb2986f39 | |||
| cb8e0dbd2e | |||
| b4faed0621 | |||
| 249aa616fe | |||
| 482ccb3293 | |||
| f1be6bc1a4 | |||
| 326e8c700b | |||
| 2a538fd7c3 | |||
| c424b981b3 | |||
| a284aba2cc | |||
| c8bfdbf087 | |||
| eb80f3698f | |||
| 23f3d85ad4 | |||
| 24019123ba | |||
| 06a471982a | |||
| 43627aba50 | |||
| 8116234b3e | |||
| e488de5c3f | |||
| 084f834a41 | |||
| 97d30ee85e | |||
| 387ccc9cf7 | |||
| fed5744607 | |||
| 3e2b448e09 | |||
| c12c13066d | |||
| 5c36454ca0 | |||
| 7b6ba13d94 | |||
| 5d93d4d7bb | |||
| 4d024eb6a6 | |||
| 182acec265 | |||
| e293b16343 | |||
| 70cf36d7b2 | |||
| f3d8cac9ba | |||
| 620b243794 | |||
| 37fe7d4dbd | |||
| 11b6d651b8 | |||
| 8a5bc11d88 | |||
| bf69146035 | |||
| 14ff0c4bbe | |||
| d9706a2c3e | |||
| c36f7b73f1 | |||
| 7e6d0542cc | |||
| 72bf4ec909 | |||
| fa87b8ff9b | |||
| 31203e72fc | |||
| 5bd9875f61 | |||
| 646ad28fbc | |||
| a66d482d3a | |||
| 5dfc8cce37 | |||
| 4c88431147 | |||
| bb06b3970d | |||
| c86767bc3f | |||
| 3c6d14ec7e | |||
| 88a9a69c27 | |||
| da7241cd6a | |||
| 397055826e | |||
| 2e6b8bb5fa | |||
| ed4f3afcbd | |||
| 53ab75fbf9 | |||
| c19cf17b07 | |||
| a994f45e8f | |||
| 1bdaeeb3a6 | |||
| 32f0a3d084 | |||
| ba4ef326f7 | |||
| 10a8f0bd16 | |||
| d3c40a8fc3 | |||
| 34a9247270 | |||
| 0b0f9b41e9 | |||
| 827eb7c32e | |||
| 0b598f6c17 | |||
| 19e7394266 | |||
| f6b345ec55 | |||
| 091201d7ff | |||
| b109764b8e | |||
| 79a488b863 | |||
| 962c8a6516 | |||
| f94cd01a3a | |||
| 31081bbbb2 | |||
| 0d1ba16525 | |||
| 5b4b00ae41 | |||
| e0ca5d8724 | |||
| fd5b21c271 | |||
| ac5e2f755c | |||
| a4415f0688 | |||
| 8a2bb03b62 | |||
| 7011160587 | |||
| 31fdff3d3b | |||
| 580bb5d622 | |||
| d5a51a8422 | |||
| 5f5b22d8d3 | |||
| 397f689be7 | |||
| 3ddffe6daf | |||
| ab1861b068 | |||
| 5bf870acdc | |||
| d85f4ea8d4 | |||
| 0a93c1954d | |||
| c4df9ff34e | |||
| 648587f52c | |||
| bc17c2d685 | |||
| 6e7327a3fe | |||
| db15cd959b | |||
| ff43994902 | |||
| 8226e14e0a | |||
| f5b30efc7d | |||
| 4c27c36e90 | |||
| 9dd950e510 | |||
| fffa7b9839 | |||
| 2d3ca45c87 | |||
| c0f032a6c6 | |||
| f8848ba0e5 | |||
| 33a3229c22 | |||
| 5a626cece9 | |||
| 16c80e75b5 | |||
| 4c604304d8 | |||
| a921695efc | |||
| 09dfe1c8ea | |||
| 1fbaeefca5 | |||
| 9a969040ef | |||
| b7615a178b | |||
| 894b9c9a54 | |||
| 6dc4b9718e | |||
| 6435ad6594 | |||
| 173e071a86 | |||
| 8812b6409e | |||
| 56bd63a418 | |||
| 915b2fcaa6 | |||
| 7eb63f105a | |||
| d6bc8a0436 | |||
| 6ea8603074 | |||
| a652e7b893 | |||
| 0e4b7dbc43 | |||
| e73e9c5b27 | |||
| 078ddab4ad | |||
| 749c77ab3e | |||
| 7c2eb7997a | |||
| 6beb1ca5a4 | |||
| b9bd866d4b | |||
| 08c96b5728 | |||
| 4f77a8ebdc | |||
| 75d7fad29f | |||
| 0adc62f04c | |||
| dadc0801c2 | |||
| 334d4e6386 | |||
| 47b8ad6e56 | |||
| c621fc457d | |||
| 6ea3112691 | |||
| 8c63f97d8e | |||
| a749740900 | |||
| cad03c1867 | |||
| c04f39318e | |||
| a7fe65f24b | |||
| 7e494ae6cf | |||
| 23c61bcb14 | |||
| c408127d6e |
@@ -0,0 +1,8 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
@@ -143,20 +143,6 @@ let webConfig = {
|
||||
? path.resolve(__dirname, '../node_modules')
|
||||
: false
|
||||
}),
|
||||
new HtmlWebpackPlugin({
|
||||
title: '关于',
|
||||
filename: 'about.html',
|
||||
chunks: ['about'],
|
||||
template: path.resolve(__dirname, '../src/about.ejs'),
|
||||
// minify: {
|
||||
// collapseWhitespace: true,
|
||||
// removeAttributeQuotes: true,
|
||||
// removeComments: true
|
||||
// },
|
||||
nodeModules: devMode
|
||||
? path.resolve(__dirname, '../node_modules')
|
||||
: false
|
||||
}),
|
||||
new webpack.DefinePlugin({
|
||||
'process.env.IS_WEB': 'true'
|
||||
}),
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Before feedback**
|
||||
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.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Environment (please complete the following information):**
|
||||
- OS & Version: [e.g. macOS, Windows, Linux]
|
||||
- Version: [e.g. macOS 10.14.2, Windows 10, Ubuntu 18.04]
|
||||
- Motrix Version: [e.g. v1.1.3, v1.1.0]
|
||||
- Installation package type: [e.g. dmg, AppImage]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
@@ -0,0 +1,43 @@
|
||||
---
|
||||
name: 错误反馈
|
||||
about: 创建一个错误报告帮助改进「请按照模板提交,提供详细的信息,方便我们复现之后处理」
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!--
|
||||
反馈之前请搜索一下已有 issues 和 帮助文档,看是否有类似问题可以解决你的问题
|
||||
https://github.com/agalwood/Motrix/issues
|
||||
http://motrix.app/support
|
||||
|
||||
按以下格式填写反馈信息,谢谢
|
||||
-->
|
||||
|
||||
**错误描述**
|
||||
清楚简洁地描述错误,方便我们复现之后处理。
|
||||
|
||||
**如何重现**
|
||||
重现步骤,如:
|
||||
1. 点击新建任务按钮
|
||||
2. 黏贴链接(如链接不涉及到隐私和版权问题,请顺便提供)
|
||||
3. 点击提交
|
||||
4. 发现报错
|
||||
|
||||
**预期的行为**
|
||||
清楚简洁地描述你期望发生的事情。
|
||||
|
||||
**截图**
|
||||
请添加屏幕截图以帮助解释你的问题:
|
||||
打开应用菜单中的「帮助」——「开发者工具」—— 切换到 console,然后**完整**截图。
|
||||
<!-- Windows 和 Linux 版本默认隐藏了应用菜单,请到偏好设置-进阶设置-外观里关闭“隐藏菜单栏”,保存并应用之后菜单栏就出现了 -->
|
||||
|
||||
**运行环境**
|
||||
- 操作系统类型: [如 macOS, Windows, Linux]
|
||||
- 具体版本: [如 macOS 10.14.2, Windows 10, Ubuntu 18.04]
|
||||
- Motrix 版本: [如 v1.1.3, v1.1.0]
|
||||
- 安装包类型:[如 dmg, AppImage]
|
||||
|
||||
**更多信息**
|
||||
补充有关该问题的其他信息。
|
||||
@@ -0,0 +1,20 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: enhancement ✨
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
@@ -0,0 +1,28 @@
|
||||
---
|
||||
name: 新功能请求
|
||||
about: 你期望 Motrix 未来添加的新功能
|
||||
title: ''
|
||||
labels: enhancement ✨
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!--
|
||||
反馈之前请搜索一下已有 issues 和 帮助文档,看是否已经有人提交了类似的新功能请求
|
||||
https://github.com/agalwood/Motrix/issues
|
||||
http://motrix.app/support
|
||||
|
||||
按以下格式填写反馈信息,谢谢
|
||||
-->
|
||||
|
||||
**请描述一下你的新功能请求是否与已知问题有关?**
|
||||
简明扼要地描述了问题所在。
|
||||
|
||||
**描述你想要的解决方案**
|
||||
简明扼要地描述你想要的解决方案。
|
||||
|
||||
**描述你考虑过的替代方案**
|
||||
简明扼要地描述你考虑过的任何替代解决方案或功能。
|
||||
|
||||
**更多信息**
|
||||
补充有关该新功能的其他信息。
|
||||
@@ -0,0 +1,38 @@
|
||||
# Configuration for Lock Threads - https://github.com/dessant/lock-threads
|
||||
|
||||
# Number of days of inactivity before a closed issue or pull request is locked
|
||||
daysUntilLock: 30
|
||||
|
||||
# Skip issues and pull requests created before a given timestamp. Timestamp must
|
||||
# follow ISO 8601 (`YYYY-MM-DD`). Set to `false` to disable
|
||||
skipCreatedBefore: false
|
||||
|
||||
# Issues and pull requests with these labels will be ignored. Set to `[]` to disable
|
||||
exemptLabels: []
|
||||
|
||||
# Label to add before locking, such as `outdated`. Set to `false` to disable
|
||||
lockLabel: false
|
||||
|
||||
# Comment to post before locking. Set to `false` to disable
|
||||
lockComment: >
|
||||
This thread has been automatically locked since there has not been
|
||||
any recent activity after it was closed. Please open a new issue for
|
||||
related bugs.
|
||||
|
||||
# Assign `resolved` as the reason for locking. Set to `false` to disable
|
||||
setLockReason: true
|
||||
|
||||
# Limit to only `issues` or `pulls`
|
||||
# only: issues
|
||||
|
||||
# Optionally, specify configuration settings just for `issues` or `pulls`
|
||||
# issues:
|
||||
# exemptLabels:
|
||||
# - help-wanted
|
||||
# lockLabel: outdated
|
||||
|
||||
# pulls:
|
||||
# daysUntilLock: 30
|
||||
|
||||
# Repository to extend settings from
|
||||
# _extends: repo
|
||||
@@ -9,3 +9,4 @@ npm-debug.log.*
|
||||
thumbs.db
|
||||
!.gitkeep
|
||||
release/*
|
||||
.idea/
|
||||
|
||||
@@ -19,9 +19,6 @@ addons:
|
||||
- libgnome-keyring-dev
|
||||
- icnsutils
|
||||
before_install:
|
||||
- mkdir -p /tmp/git-lfs && curl -L https://github.com/github/git-lfs/releases/download/v1.2.1/git-lfs-$([
|
||||
"$TRAVIS_OS_NAME" == "linux" ] && echo "linux" || echo "darwin")-amd64-1.2.1.tar.gz
|
||||
| tar -xz -C /tmp/git-lfs --strip-components 1 && /tmp/git-lfs/git-lfs pull
|
||||
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo apt-get install --no-install-recommends -y icnsutils graphicsmagick xz-utils; fi
|
||||
install:
|
||||
- nvm install 10
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as
|
||||
contributors and maintainers pledge to making participation in our project and
|
||||
our community a harassment-free experience for everyone, regardless of age, body
|
||||
size, disability, ethnicity, sex characteristics, gender identity and expression,
|
||||
level of experience, education, socio-economic status, nationality, personal
|
||||
appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment
|
||||
include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||
advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic
|
||||
address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable
|
||||
behavior and are expected to take appropriate and fair corrective action in
|
||||
response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or
|
||||
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||
permanently any contributor for other behaviors that they deem inappropriate,
|
||||
threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces
|
||||
when an individual is representing the project or its community. Examples of
|
||||
representing a project or community include using an official project e-mail
|
||||
address, posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event. Representation of a project may be
|
||||
further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting the project team at agalwood.net@gmail.com. All
|
||||
complaints will be reviewed and investigated and will result in a response that
|
||||
is deemed necessary and appropriate to the circumstances. The project team is
|
||||
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||
Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||
faith may face temporary or permanent repercussions as determined by other
|
||||
members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see
|
||||
https://www.contributor-covenant.org/faq
|
||||
@@ -0,0 +1,39 @@
|
||||
# Motrix 贡献指南
|
||||
|
||||
## 🌍 翻译指南
|
||||
|
||||
首先你要确定一个语言的英文简写作为 **locale**,如 en-US,这个 locale 值请严格参考 [Electron 的 Locales 文档](https://electronjs.org/docs/api/locales)
|
||||
|
||||
Motrix 的国际化分两部分:
|
||||
|
||||
- Element UI
|
||||
- 菜单和主界面
|
||||
|
||||
### Element UI
|
||||
|
||||
Element UI 的国际化由 [Element 社区](http://element.eleme.io/#/en-US/component/i18n)提供,找到 **locale** 对应的语言包文件「两者 locale 命名可能不一致」,在 `src/shared/locales/all.js` 中引入,如
|
||||
|
||||
```javascript
|
||||
import eleLocaleEn from 'element-ui/lib/locale/lang/en'
|
||||
import eleLocaleZhCN from 'element-ui/lib/locale/lang/zh-CN'
|
||||
```
|
||||
|
||||
### 菜单和主界面
|
||||
|
||||
Motrix 使用 i18next 作为翻译支持库,所以你可能需要简单了解一下它的[使用方法](https://www.i18next.com/overview/getting-started)。
|
||||
配置文件按照语言 (**locale**) 划分目录:`src/shared/locales`,如:`src/shared/locales/en-US` 和 `src/shared/locales/zh-CN`。
|
||||
|
||||
目录里面有按业务模块划分的语言文件
|
||||
|
||||
菜单模块经过重构之后,国际化已经打散到了以下文件里了,不再需要再复制 `src/main/menus` 里的配置。
|
||||
|
||||
- about.js
|
||||
- app.js
|
||||
- edit.js
|
||||
- help.js
|
||||
- index.js
|
||||
- menu.js
|
||||
- preferences.js
|
||||
- subnav.js
|
||||
- task.js
|
||||
- window.js
|
||||
@@ -0,0 +1,39 @@
|
||||
# Motrix Contributing Guide
|
||||
|
||||
## 🌍 Translation Guide
|
||||
|
||||
First you need to determine the English abbreviation of a language as **locale**, such as en-US, this locale value should strictly refer to the [electron's documentation](https://electronjs.org/docs/api/locales).
|
||||
|
||||
The internationalization of Motrix is divided into two parts:
|
||||
|
||||
- Element UI
|
||||
- Menu & Main Interface
|
||||
|
||||
### Element UI
|
||||
|
||||
The internationalization of Element UI is provided by the [Element community](http://element.eleme.io/#/en-US/component/i18n), then find the language pack file corresponding to **locale** (both locale naming may be inconsistent), which is import in `src/shared/locales/all.js`, such as
|
||||
|
||||
```javascript
|
||||
import eleLocaleEn from 'element-ui/lib/locale/lang/en'
|
||||
import eleLocaleZhCN from 'element-ui/lib/locale/lang/zh-CN'
|
||||
```
|
||||
|
||||
### Menu & Main Interface
|
||||
|
||||
Motrix uses the [i18next](https://www.i18next.com/overview/getting-started) library for internationalization, so you need a quick look at how to use it.
|
||||
The configuration files are divided by **locale**: `src/shared/locales`, such as `src/shared/locales/en-US` and `src/shared/locales/zh-CN`.
|
||||
|
||||
There are language files in the directory according to the business module.
|
||||
|
||||
After the menu module is refactored, the internationalization of the menu has been dispersed into the following files, and there is no need to copy the configuration in `src/main/menus`.
|
||||
|
||||
- about.js
|
||||
- app.js
|
||||
- edit.js
|
||||
- help.js
|
||||
- index.js
|
||||
- menu.js
|
||||
- preferences.js
|
||||
- subnav.js
|
||||
- task.js
|
||||
- window.js
|
||||
@@ -0,0 +1,148 @@
|
||||
# Motrix
|
||||
|
||||
<a href="https://motrix.app">
|
||||
<img src="https://cdn.nlark.com/yuque/0/2018/png/129147/1543735425232-a5d2c99f-d788-43e4-9781-558ff6d21027.png" width="256" alt="App Icon" />
|
||||
</a>
|
||||
|
||||
[English](./README.md) | 简体中文
|
||||
|
||||
## 一款全能的下载工具
|
||||
|
||||
[](https://github.com/agalwood/Motrix/releases) [](https://travis-ci.org/agalwood/Motrix) [](https://ci.appveyor.com/project/agalwood/motrix/branch/master) [](https://github.com/agalwood/Motrix/releases) 
|
||||
|
||||
我是个兴趣使然的桌面应用开发者🤓,利用搬砖之余开发了 Motrix。
|
||||
|
||||
Motrix 是一款全能的下载工具,支持下载 HTTP、FTP、BT、磁力链、百度网盘等资源。它的界面简洁易用,希望大家喜欢 👻。
|
||||
|
||||
✈️ 去 [官网](https://motrix.app/zh-CN) 逛逛 | 📖 查看 [帮助手册](http://motrix.app/support/issues)
|
||||
|
||||
## 💽 安装稳定版
|
||||
|
||||
[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+,天朝用户可能需要设置 Git 代理)安装最新便携版本的 Motrix。
|
||||
|
||||
```bash
|
||||
scoop bucket add extras
|
||||
scoop install motrix
|
||||
```
|
||||
|
||||
### macOS
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
### Linux
|
||||
|
||||
你可以下载 AppImage(适用于所有 Linux 发行版)软件包或 snap 或从源代码构建安装 Motrix。
|
||||
|
||||
构建请阅读 **编译打包** 部分。
|
||||
|
||||
对于 Arch Linux 用户,可以使用 [aur](https://aur.archlinux.org/packages/motrix/) 安装 Motrix,感谢维护者 [weearc](https://github.com/weearc)。
|
||||
|
||||
运行以下命令进行安装:
|
||||
|
||||
```bash
|
||||
yay motrix
|
||||
```
|
||||
|
||||
## ✨ 特性
|
||||
|
||||
- 🕹 简洁明了的图形操作界面
|
||||
- 🦄 支持BT和磁力链任务
|
||||
- 💾 支持下载百度云盘资源
|
||||
- 🎛 最高支持 10 个任务同时下载
|
||||
- 🚀 单任务最高支持 64 线程下载
|
||||
- 🕶 模拟用户代理UA
|
||||
- 🔔 下载完成后通知
|
||||
- 💻 支持触控栏快捷键 (Mac 专享)
|
||||
- 🤖 常驻系统托盘,操作更加便捷
|
||||
- 🌑 深色模式
|
||||
- 🗑 移除任务时可同时删除相关文件
|
||||
- 🌍 国际化,[查看已可选的语言](#-国际化)
|
||||
- 🎏 ...
|
||||
|
||||
## 🖥 应用界面
|
||||
|
||||

|
||||
|
||||
## ⌨️ 本地开发
|
||||
|
||||
### 克隆代码
|
||||
|
||||
```bash
|
||||
git clone git@github.com:agalwood/Motrix.git
|
||||
```
|
||||
|
||||
### 安装依赖
|
||||
|
||||
```bash
|
||||
cd Motrix
|
||||
npm install
|
||||
```
|
||||
|
||||
天朝大陆用户建议使用淘宝的 npm 源
|
||||
|
||||
```bash
|
||||
npm config set registry 'https://registry.npm.taobao.org'
|
||||
export ELECTRON_MIRROR='https://npm.taobao.org/mirrors/electron/'
|
||||
export SASS_BINARY_SITE='https://npm.taobao.org/mirrors/node-sass'
|
||||
```
|
||||
|
||||
如果喜欢 [Yarn](https://yarnpkg.com/),也可以使用 `yarn` 安装依赖
|
||||
|
||||
### 开发模式
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
### 编译打包
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
完成之后可以在项目的 `release` 目录看到编译打包好的应用文件
|
||||
|
||||
## 🛠 技术栈
|
||||
|
||||
- [Electron](https://electronjs.org/)
|
||||
- [Vue](https://vuejs.org/) + [VueX](https://vuex.vuejs.org/) + [Element](https://element.eleme.io)
|
||||
- [Aria2](https://aria2.github.io/) (注:macOS 和 Linux 版本使用的是 64 位的 aria2c,Windows 版使用的 32 位的)
|
||||
|
||||
## ☑️ TODO
|
||||
|
||||
开发计划请移步 [Trello](https://trello.com/b/qNUzA0bv/motrix) 查看
|
||||
|
||||
## 🤝 参与共建 [](http://makeapullrequest.com)
|
||||
|
||||
如果你有兴趣参与共同开发,欢迎 FORK 和 PR。
|
||||
|
||||
## 🌍 国际化
|
||||
|
||||
欢迎大家将 Motrix 翻译成更多的语言版本 🧐,开工之前请先阅读一下 [翻译指南](./CONTRIBUTING-CN.md#-翻译指南)。
|
||||
|
||||
| Key | Name | Status |
|
||||
|-------|:--------------------|:-------------|
|
||||
| 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 | 简体中文 | ✔️ |
|
||||
| zh-TW | 繁體中文 | ✔️ [@Yukaii](https://github.com/Yukaii) |
|
||||
|
||||
## 📜 开源许可
|
||||
|
||||
基于 [MIT license](https://opensource.org/licenses/MIT) 许可进行开源。
|
||||
@@ -1,63 +1,140 @@
|
||||
# Motrix
|
||||
|
||||
<a href="https://motrix.app">
|
||||
<img src="https://motrix.app/images/app-icon@2x.png" width="256" alt="App Icon" />
|
||||
<img src="https://cdn.nlark.com/yuque/0/2018/png/129147/1543735425232-a5d2c99f-d788-43e4-9781-558ff6d21027.png" width="256" alt="App Icon" />
|
||||
</a>
|
||||
|
||||
## 一款全能的下载工具
|
||||
## A full-featured download manager
|
||||
|
||||
支持下载 HTTP、FTP、BT、磁力链、百度网盘等资源
|
||||
[](https://github.com/agalwood/Motrix/releases) [](https://travis-ci.org/agalwood/Motrix) [](https://ci.appveyor.com/project/agalwood/motrix/branch/master) [](https://github.com/agalwood/Motrix/releases) 
|
||||
|
||||
<span style="font-size: 30px">我</span>是个兴趣使然的桌面应用开发者🤓,出于兴趣爱好,利用搬砖之余开发了 [MO 1.0](https://moapp.me) 版本,做出来有大半年了,没做过什么推广,所以大家可能都没怎么听过这个应用吧~👻~
|
||||
English | [简体中文](./README-CN.md)
|
||||
|
||||
本着自己用得舒(折)服(腾)😌的想法,🤠撸出了个全新的版本,并更名为 Motrix。新版本不仅优化了性能,还重新设计了图形操作界面,操作更简便!
|
||||
Motrix is a full-featured download manager that supports downloading HTTP, FTP, BitTorrent, Magnet, Baidu Net Disk, etc.
|
||||
|
||||
官网提供了已经编译好的应用安装包([去官网下载](https://motrix.app)),当然你也可以自己克隆代码进行编译打包。
|
||||
Motrix has a clean and easy to use interface. I hope you will like it 👻.
|
||||
|
||||
## 🛠 技术栈
|
||||
- [Electron](https://electronjs.org/)
|
||||
- [Vue](https://vuejs.org/) + [VueX](https://vuex.vuejs.org/) + [Element](https://element.eleme.io)
|
||||
- [Aria2](https://aria2.github.io/)
|
||||
✈️ [Official Website](https://motrix.app) | 📖 [Manual](https://github.com/agalwood/Motrix/wiki)
|
||||
|
||||
## 📦 自行编译
|
||||
## 💽 Installation
|
||||
|
||||
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 bucket add extras
|
||||
scoop install motrix
|
||||
```
|
||||
|
||||
### macOS
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
### Linux
|
||||
|
||||
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.
|
||||
|
||||
For Arch Linux users, Motrix is available in [aur](https://aur.archlinux.org/packages/motrix/), thanks to the maintainer [weearc](https://github.com/weearc).
|
||||
|
||||
Run the following command to install:
|
||||
|
||||
```bash
|
||||
yay motrix
|
||||
```
|
||||
|
||||
## ✨ Features
|
||||
|
||||
- 🕹 Simple and clear user interface
|
||||
- 🦄 Supports BitTorrent & Magnet
|
||||
- 💾 Supports downloading Baidu Net Disk
|
||||
- 🎛 Up to 10 concurrent download tasks
|
||||
- 🚀 Supports 64 threads in a single task
|
||||
- 🕶 Mock User-Agent
|
||||
- 🔔 Download completed Notification
|
||||
- 💻 Ready for Touch Bar (Mac only)
|
||||
- 🤖 Resident system tray for quick operation
|
||||
- 🌑 Dark mode
|
||||
- 🗑 Delete related files when removing tasks (optional)
|
||||
- 🌍 I18n, [View supported languages](#-internationalization).
|
||||
- 🎏 ...
|
||||
|
||||
## 🖥 User Interface
|
||||
|
||||

|
||||
|
||||
## ⌨️ Development
|
||||
|
||||
### Clone Code
|
||||
|
||||
### 克隆代码
|
||||
```bash
|
||||
git clone git@github.com:agalwood/Motrix.git
|
||||
```
|
||||
|
||||
### 安装依赖
|
||||
### Install Dependencies
|
||||
|
||||
```bash
|
||||
cd Motrix
|
||||
npm install
|
||||
```
|
||||
天朝大陆用户建议使用淘宝的npm源
|
||||
```bash
|
||||
npm config set registry 'https://registry.npm.taobao.org'
|
||||
export ELECTRON_MIRROR='https://npm.taobao.org/mirrors/electron/'
|
||||
export SASS_BINARY_SITE='https://npm.taobao.org/mirrors/node-sass'
|
||||
```
|
||||
如果喜欢 [Yarn](https://yarnpkg.com/),也可以使用 `yarn` 安装依赖
|
||||
|
||||
### 开发模式
|
||||
If you like [Yarn](https://yarnpkg.com/), you can also use `yarn` to install dependencies.
|
||||
|
||||
### Dev Mode
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
### 编译打包
|
||||
### Build Release
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
完成之后可以在项目的 release 目录看到编译打包好的应用文件
|
||||
|
||||
After building, the application will be found in the project's `release` directory.
|
||||
|
||||
## 🛠 Technology Stack
|
||||
|
||||
- [Electron](https://electronjs.org/)
|
||||
- [Vue](https://vuejs.org/) + [VueX](https://vuex.vuejs.org/) + [Element](https://element.eleme.io)
|
||||
- [Aria2](https://aria2.github.io/) (Note: macOS and Linux versions use 64-bit aria2c, Windows version uses 32-bit)
|
||||
|
||||
## ☑️ TODO
|
||||
- [ ] 国际化支持
|
||||
- [ ] macOS Mojave 深色模式
|
||||
- [ ] Windows 和 Linux 版本理论上会有,还未调试
|
||||
- [ ] 测试用例
|
||||
|
||||
## 🤝 参与共建 [](http://makeapullrequest.com)
|
||||
如果你有兴趣参与共同开发,欢迎 FORK 和 PR。
|
||||
Development Roadmap see: [Trello](https://trello.com/b/qNUzA0bv/motrix)
|
||||
|
||||
## 📜 开源许可
|
||||
基于 [MIT license](https://opensource.org/licenses/MIT) 许可进行开源。
|
||||
## 🤝 Contribute [](http://makeapullrequest.com)
|
||||
|
||||
If you are interested in participating in joint development, PR and Forks are welcome!
|
||||
|
||||
## 🌍 Internationalization
|
||||
|
||||
Translations into versions for other languages are welcome 🧐! Please read the [translation guide](./CONTRIBUTING.md#-translation-guide) before starting translations.
|
||||
|
||||
| Key | Name | Status |
|
||||
|-------|:--------------------|:-------------|
|
||||
| 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 | 简体中文 | ✔️ |
|
||||
| zh-TW | 繁體中文 | ✔️ [@Yukaii](https://github.com/Yukaii) |
|
||||
|
||||
## 📜 License
|
||||
|
||||
[MIT](https://opensource.org/licenses/MIT) Copyright (c) 2018-present Dr_rOot
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
provider: generic
|
||||
url: 'https://motrix.app/release/'
|
||||
url: 'https://dl.motrix.app/release/'
|
||||
|
||||
|
After Width: | Height: | Size: 59 KiB |
@@ -33,7 +33,7 @@
|
||||
# 单个任务下载速度限制, 默认:0
|
||||
#@max-download-limit=0
|
||||
# 整体上传速度限制, 运行时可修改, 默认:0
|
||||
#@max-overall-upload-limit=0
|
||||
max-overall-upload-limit=128K
|
||||
# 单个任务上传速度限制, 默认:0
|
||||
#@max-upload-limit=0
|
||||
# 禁用IPv6, 默认:false
|
||||
@@ -73,26 +73,30 @@ rpc-listen-all=true
|
||||
# 当下载的是一个种子(以.torrent结尾)时, 自动开始BT任务, 默认:true
|
||||
#follow-torrent=true
|
||||
# BT监听端口, 当端口被屏蔽时使用, 默认:6881-6999
|
||||
listen-port=51413
|
||||
listen-port=50101-50109
|
||||
# 单个种子最大连接数, 默认:55
|
||||
#bt-max-peers=55
|
||||
# 打开DHT功能, PT需要禁用, 默认:true
|
||||
enable-dht=false
|
||||
enable-dht=true
|
||||
# 打开IPv6 DHT功能, PT需要禁用
|
||||
#enable-dht6=false
|
||||
enable-dht6=true
|
||||
# DHT网络监听端口, 默认:6881-6999
|
||||
#dht-listen-port=6881-6999
|
||||
dht-listen-port=50101-50109
|
||||
# Set host and port as an entry point to IPv4 DHT network.
|
||||
dht-entry-point=dht.transmissionbt.com:6881
|
||||
# Set host and port as an entry point to IPv6 DHT network.
|
||||
dht-entry-point6=dht.transmissionbt.com:6881
|
||||
# 本地节点查找, PT需要禁用, 默认:false
|
||||
#bt-enable-lpd=false
|
||||
bt-enable-lpd=true
|
||||
# 种子交换, PT需要禁用, 默认:true
|
||||
#enable-peer-exchange=false
|
||||
enable-peer-exchange=true
|
||||
# 每个种子限速, 对少种的PT很有用, 默认:50K
|
||||
#bt-request-peer-speed-limit=50K
|
||||
# 客户端伪装, PT需要
|
||||
peer-id-prefix=-TR2770-
|
||||
user-agent=Transmission/2.94
|
||||
# 当种子的分享率达到这个数时, 自动停止做种, 0为一直做种, 默认:1.0
|
||||
seed-ratio=0
|
||||
seed-ratio=1.0
|
||||
# 强制保存会话, 即使任务已经完成, 默认:false
|
||||
# 较新的版本开启后会在任务完成后依然保留.aria2文件
|
||||
#force-save=false
|
||||
@@ -101,8 +105,18 @@ seed-ratio=0
|
||||
# 继续之前的BT任务时, 无需再次校验, 默认:false
|
||||
bt-seed-unverified=true
|
||||
# 保存磁力链接元数据为种子文件(.torrent文件), 默认:false
|
||||
bt-save-metadata=true
|
||||
|
||||
# bt-tracker数据来自https://github.com/ngosang/trackerslist/blob/master/trackers_best.txt
|
||||
|
||||
bt-tracker=udp://tracker.coppersurfer.tk:6969/announce,udp://tracker.opentrackr.org:1337/announce,udp://tracker.internetwarriors.net:1337/announce,udp://9.rarbg.to:2710/announce,udp://exodus.desync.com:6969/announce,udp://tracker2.itzmx.com:6961/announce,udp://tracker1.itzmx.com:8080/announce,udp://explodie.org:6969/announce,http://tracker.tfile.me:80/announce.php,http://tracker.tfile.me:80/announce,http://tracker.tfile.co:80/announce,http://peersteers.org:80/announce,udp://tracker.tiny-vps.com:6969/announce,udp://ipv4.tracker.harry.lu:80/announce,udp://denis.stalker.upeer.me:6969/announce,udp://tracker.torrent.eu.org:451/announce,udp://tracker.port443.xyz:6969/announce,udp://tracker.cyberia.is:6969/announce,udp://thetracker.org:80/announce,udp://retracker.lanta-net.ru:2710/announce
|
||||
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
|
||||
# Verify the peer using certificates specified
|
||||
# in --ca-certificate option. Default: true
|
||||
check-certificate=false
|
||||
# Exclude seed only downloads when counting concurrent active downloads (See -j option).
|
||||
# This means that if -j3 is given and this option is turned on and 3 downloads are active and one of those enters seed mode,
|
||||
# then it is excluded from active download count (thus it becomes 2),
|
||||
# and the next download waiting in queue gets started.
|
||||
# But be aware that seeding item is still recognized as active download in RPC method. Default: false
|
||||
bt-detach-seed-only=true
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
# 预分配所需时间: none < falloc ? trunc < prealloc
|
||||
# falloc和trunc则需要文件系统和内核支持
|
||||
# NTFS建议使用falloc, EXT3/4建议trunc, MAC 下需要注释此项
|
||||
# file-allocation=none
|
||||
file-allocation=trunc
|
||||
# 断点续传
|
||||
#@continue=true
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
# 单个任务下载速度限制, 默认:0
|
||||
#@max-download-limit=0
|
||||
# 整体上传速度限制, 运行时可修改, 默认:0
|
||||
#@max-overall-upload-limit=0
|
||||
max-overall-upload-limit=128K
|
||||
# 单个任务上传速度限制, 默认:0
|
||||
#@max-upload-limit=0
|
||||
# 禁用IPv6, 默认:false
|
||||
@@ -73,26 +73,30 @@ rpc-listen-all=true
|
||||
# 当下载的是一个种子(以.torrent结尾)时, 自动开始BT任务, 默认:true
|
||||
#follow-torrent=true
|
||||
# BT监听端口, 当端口被屏蔽时使用, 默认:6881-6999
|
||||
listen-port=51413
|
||||
listen-port=50101-50109
|
||||
# 单个种子最大连接数, 默认:55
|
||||
#bt-max-peers=55
|
||||
# 打开DHT功能, PT需要禁用, 默认:true
|
||||
enable-dht=false
|
||||
enable-dht=true
|
||||
# 打开IPv6 DHT功能, PT需要禁用
|
||||
#enable-dht6=false
|
||||
enable-dht6=true
|
||||
# DHT网络监听端口, 默认:6881-6999
|
||||
#dht-listen-port=6881-6999
|
||||
dht-listen-port=50101-50109
|
||||
# Set host and port as an entry point to IPv4 DHT network.
|
||||
dht-entry-point=dht.transmissionbt.com:6881
|
||||
# Set host and port as an entry point to IPv6 DHT network.
|
||||
dht-entry-point6=dht.transmissionbt.com:6881
|
||||
# 本地节点查找, PT需要禁用, 默认:false
|
||||
#bt-enable-lpd=false
|
||||
bt-enable-lpd=true
|
||||
# 种子交换, PT需要禁用, 默认:true
|
||||
#enable-peer-exchange=false
|
||||
enable-peer-exchange=true
|
||||
# 每个种子限速, 对少种的PT很有用, 默认:50K
|
||||
#bt-request-peer-speed-limit=50K
|
||||
# 客户端伪装, PT需要
|
||||
peer-id-prefix=-TR2770-
|
||||
user-agent=Transmission/2.94
|
||||
# 当种子的分享率达到这个数时, 自动停止做种, 0为一直做种, 默认:1.0
|
||||
seed-ratio=0
|
||||
seed-ratio=1.0
|
||||
# 强制保存会话, 即使任务已经完成, 默认:false
|
||||
# 较新的版本开启后会在任务完成后依然保留.aria2文件
|
||||
#force-save=false
|
||||
@@ -101,8 +105,18 @@ seed-ratio=0
|
||||
# 继续之前的BT任务时, 无需再次校验, 默认:false
|
||||
bt-seed-unverified=true
|
||||
# 保存磁力链接元数据为种子文件(.torrent文件), 默认:false
|
||||
bt-save-metadata=true
|
||||
|
||||
# bt-tracker数据来自https://github.com/ngosang/trackerslist/blob/master/trackers_best.txt
|
||||
|
||||
bt-tracker=udp://tracker.coppersurfer.tk:6969/announce,udp://tracker.opentrackr.org:1337/announce,udp://tracker.internetwarriors.net:1337/announce,udp://9.rarbg.to:2710/announce,udp://exodus.desync.com:6969/announce,udp://tracker2.itzmx.com:6961/announce,udp://tracker1.itzmx.com:8080/announce,udp://explodie.org:6969/announce,http://tracker.tfile.me:80/announce.php,http://tracker.tfile.me:80/announce,http://tracker.tfile.co:80/announce,http://peersteers.org:80/announce,udp://tracker.tiny-vps.com:6969/announce,udp://ipv4.tracker.harry.lu:80/announce,udp://denis.stalker.upeer.me:6969/announce,udp://tracker.torrent.eu.org:451/announce,udp://tracker.port443.xyz:6969/announce,udp://tracker.cyberia.is:6969/announce,udp://thetracker.org:80/announce,udp://retracker.lanta-net.ru:2710/announce
|
||||
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
|
||||
# Verify the peer using certificates specified
|
||||
# in --ca-certificate option. Default: true
|
||||
check-certificate=false
|
||||
# Exclude seed only downloads when counting concurrent active downloads (See -j option).
|
||||
# This means that if -j3 is given and this option is turned on and 3 downloads are active and one of those enters seed mode,
|
||||
# then it is excluded from active download count (thus it becomes 2),
|
||||
# and the next download waiting in queue gets started.
|
||||
# But be aware that seeding item is still recognized as active download in RPC method. Default: false
|
||||
bt-detach-seed-only=true
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
# 预分配所需时间: none < falloc ? trunc < prealloc
|
||||
# falloc和trunc则需要文件系统和内核支持
|
||||
# NTFS建议使用falloc, EXT3/4建议trunc, MAC 下需要注释此项
|
||||
# file-allocation=none
|
||||
file-allocation=falloc
|
||||
# 断点续传
|
||||
#@continue=true
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
# 单个任务下载速度限制, 默认:0
|
||||
#@max-download-limit=0
|
||||
# 整体上传速度限制, 运行时可修改, 默认:0
|
||||
#@max-overall-upload-limit=0
|
||||
max-overall-upload-limit=128K
|
||||
# 单个任务上传速度限制, 默认:0
|
||||
#@max-upload-limit=0
|
||||
# 禁用IPv6, 默认:false
|
||||
@@ -73,26 +73,30 @@ rpc-listen-all=true
|
||||
# 当下载的是一个种子(以.torrent结尾)时, 自动开始BT任务, 默认:true
|
||||
#follow-torrent=true
|
||||
# BT监听端口, 当端口被屏蔽时使用, 默认:6881-6999
|
||||
listen-port=51413
|
||||
listen-port=50101-50109
|
||||
# 单个种子最大连接数, 默认:55
|
||||
#bt-max-peers=55
|
||||
# 打开DHT功能, PT需要禁用, 默认:true
|
||||
enable-dht=false
|
||||
enable-dht=true
|
||||
# 打开IPv6 DHT功能, PT需要禁用
|
||||
#enable-dht6=false
|
||||
enable-dht6=true
|
||||
# DHT网络监听端口, 默认:6881-6999
|
||||
#dht-listen-port=6881-6999
|
||||
dht-listen-port=50101-50109
|
||||
# Set host and port as an entry point to IPv4 DHT network.
|
||||
dht-entry-point=dht.transmissionbt.com:6881
|
||||
# Set host and port as an entry point to IPv6 DHT network.
|
||||
dht-entry-point6=dht.transmissionbt.com:6881
|
||||
# 本地节点查找, PT需要禁用, 默认:false
|
||||
#bt-enable-lpd=false
|
||||
bt-enable-lpd=true
|
||||
# 种子交换, PT需要禁用, 默认:true
|
||||
#enable-peer-exchange=false
|
||||
enable-peer-exchange=true
|
||||
# 每个种子限速, 对少种的PT很有用, 默认:50K
|
||||
#bt-request-peer-speed-limit=50K
|
||||
# 客户端伪装, PT需要
|
||||
peer-id-prefix=-TR2770-
|
||||
user-agent=Transmission/2.94
|
||||
# 当种子的分享率达到这个数时, 自动停止做种, 0为一直做种, 默认:1.0
|
||||
seed-ratio=0
|
||||
seed-ratio=1.0
|
||||
# 强制保存会话, 即使任务已经完成, 默认:false
|
||||
# 较新的版本开启后会在任务完成后依然保留.aria2文件
|
||||
#force-save=false
|
||||
@@ -101,8 +105,18 @@ seed-ratio=0
|
||||
# 继续之前的BT任务时, 无需再次校验, 默认:false
|
||||
bt-seed-unverified=true
|
||||
# 保存磁力链接元数据为种子文件(.torrent文件), 默认:false
|
||||
bt-save-metadata=true
|
||||
|
||||
# bt-tracker数据来自https://github.com/ngosang/trackerslist/blob/master/trackers_best.txt
|
||||
|
||||
bt-tracker=udp://tracker.coppersurfer.tk:6969/announce,udp://tracker.opentrackr.org:1337/announce,udp://tracker.internetwarriors.net:1337/announce,udp://9.rarbg.to:2710/announce,udp://exodus.desync.com:6969/announce,udp://tracker2.itzmx.com:6961/announce,udp://tracker1.itzmx.com:8080/announce,udp://explodie.org:6969/announce,http://tracker.tfile.me:80/announce.php,http://tracker.tfile.me:80/announce,http://tracker.tfile.co:80/announce,http://peersteers.org:80/announce,udp://tracker.tiny-vps.com:6969/announce,udp://ipv4.tracker.harry.lu:80/announce,udp://denis.stalker.upeer.me:6969/announce,udp://tracker.torrent.eu.org:451/announce,udp://tracker.port443.xyz:6969/announce,udp://tracker.cyberia.is:6969/announce,udp://thetracker.org:80/announce,udp://retracker.lanta-net.ru:2710/announce
|
||||
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
|
||||
# Verify the peer using certificates specified
|
||||
# in --ca-certificate option. Default: true
|
||||
check-certificate=false
|
||||
# Exclude seed only downloads when counting concurrent active downloads (See -j option).
|
||||
# This means that if -j3 is given and this option is turned on and 3 downloads are active and one of those enters seed mode,
|
||||
# then it is excluded from active download count (thus it becomes 2),
|
||||
# and the next download waiting in queue gets started.
|
||||
# But be aware that seeding item is still recognized as active download in RPC method. Default: false
|
||||
bt-detach-seed-only=true
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "Motrix",
|
||||
"version": "1.0.8",
|
||||
"version": "1.4.1",
|
||||
"description": "A full-featured download manager",
|
||||
"homepage": "https://motrix.app",
|
||||
"author": {
|
||||
@@ -15,7 +15,7 @@
|
||||
"url": "git@github.com:agalwood/Motrix.git"
|
||||
},
|
||||
"scripts": {
|
||||
"release": "npm run build --publish always",
|
||||
"release": "npm run build --publish onTagOrDraft",
|
||||
"build": "node .electron-vue/build.js && electron-builder",
|
||||
"build:dir": "node .electron-vue/build.js && electron-builder --dir",
|
||||
"build:clean": "cross-env BUILD_TARGET=clean node .electron-vue/build.js",
|
||||
@@ -27,11 +27,19 @@
|
||||
"pack": "npm run pack:main && npm run pack:renderer",
|
||||
"pack:main": "cross-env NODE_ENV=production webpack --mode production --progress --colors --config .electron-vue/webpack.main.config.js",
|
||||
"pack:renderer": "cross-env NODE_ENV=production webpack --mode production --progress --colors --config .electron-vue/webpack.renderer.config.js",
|
||||
"postinstall": "npm run lint:fix"
|
||||
"postinstall": "electron-builder install-app-deps && npm run lint:fix"
|
||||
},
|
||||
"build": {
|
||||
"productName": "Motrix",
|
||||
"appId": "net.agalwood.Motrix",
|
||||
"fileAssociations": [
|
||||
{
|
||||
"ext": "torrent",
|
||||
"mimeType": "application/x-bittorrent",
|
||||
"name": "Torrent",
|
||||
"role": "Viewer"
|
||||
}
|
||||
],
|
||||
"asar": true,
|
||||
"directories": {
|
||||
"output": "release"
|
||||
@@ -39,6 +47,27 @@
|
||||
"files": [
|
||||
"dist/electron/**/*"
|
||||
],
|
||||
"protocols": [
|
||||
{
|
||||
"name": "Motrix Protocol",
|
||||
"schemes": [
|
||||
"mo",
|
||||
"motrix"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Magnet Protocol",
|
||||
"schemes": [
|
||||
"magnet"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Thunder Protocol",
|
||||
"schemes": [
|
||||
"thunder"
|
||||
]
|
||||
}
|
||||
],
|
||||
"dmg": {
|
||||
"window": {
|
||||
"width": 540,
|
||||
@@ -75,21 +104,31 @@
|
||||
"binaries": [
|
||||
"./release/mac/Motrix.app/Contents/Resources/engine/aria2c"
|
||||
],
|
||||
"category": "public.app-category.utilities",
|
||||
"protocols": [
|
||||
{
|
||||
"name": "Motrix Protocol",
|
||||
"schemes": [
|
||||
"mo",
|
||||
"motrix"
|
||||
]
|
||||
}
|
||||
]
|
||||
"category": "public.app-category.utilities"
|
||||
},
|
||||
"win": {
|
||||
"target": [
|
||||
"nsis",
|
||||
"portable"
|
||||
{
|
||||
"target": "nsis",
|
||||
"arch": [
|
||||
"x64",
|
||||
"ia32"
|
||||
]
|
||||
},
|
||||
{
|
||||
"target": "zip",
|
||||
"arch": [
|
||||
"x64",
|
||||
"ia32"
|
||||
]
|
||||
},
|
||||
{
|
||||
"target": "portable",
|
||||
"arch": [
|
||||
"x64",
|
||||
"ia32"
|
||||
]
|
||||
}
|
||||
],
|
||||
"extraResources": {
|
||||
"from": "./extra/win32/",
|
||||
@@ -104,8 +143,10 @@
|
||||
"allowToChangeInstallationDirectory": true
|
||||
},
|
||||
"linux": {
|
||||
"category": "Network",
|
||||
"target": [
|
||||
"deb",
|
||||
"snap",
|
||||
"AppImage"
|
||||
],
|
||||
"extraResources": {
|
||||
@@ -119,7 +160,7 @@
|
||||
"publish": [
|
||||
{
|
||||
"provider": "generic",
|
||||
"url": "https://motrix.app/release/"
|
||||
"url": "https://dl.motrix.app/release/"
|
||||
},
|
||||
{
|
||||
"provider": "github"
|
||||
@@ -127,80 +168,85 @@
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@panter/vue-i18next": "^0.15.1",
|
||||
"aria2": "^4.0.3",
|
||||
"axios": "^0.18.0",
|
||||
"clipboard-polyfill": "^2.7.0",
|
||||
"electron-debug": "^2.0.0",
|
||||
"axios": "^0.19.0",
|
||||
"blob-util": "^2.0.2",
|
||||
"clipboard-polyfill": "^2.8.1",
|
||||
"electron-debug": "^3.0.0",
|
||||
"electron-is": "^3.0.0",
|
||||
"electron-log": "^2.2.17",
|
||||
"electron-updater": "^4.0.7",
|
||||
"element-ui": "^2.4.11",
|
||||
"electron-log": "^3.0.6",
|
||||
"electron-updater": "^4.0.12",
|
||||
"element-ui": "^2.9.1",
|
||||
"forever-monitor": "^1.7.1",
|
||||
"i18next": "^17.0.3",
|
||||
"lodash": "^4.17.11",
|
||||
"normalize.css": "^8.0.1",
|
||||
"parse-torrent": "^6.1.2",
|
||||
"randomatic": "^3.1.1",
|
||||
"svg-innerhtml": "^1.1.0",
|
||||
"vue": "^2.5.17",
|
||||
"vue": "^2.6.10",
|
||||
"vue-electron": "^1.0.6",
|
||||
"vue-router": "^3.0.2",
|
||||
"vuex": "^3.0.1",
|
||||
"vue-router": "^3.0.6",
|
||||
"vuex": "^3.1.1",
|
||||
"vuex-router-sync": "^5.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vue/cli-plugin-babel": "^3.2.2",
|
||||
"@vue/cli-plugin-eslint": "^3.2.2",
|
||||
"@vue/cli-service": "^3.2.2",
|
||||
"@vue/cli-plugin-babel": "^3.6.0",
|
||||
"@vue/cli-plugin-eslint": "^3.6.0",
|
||||
"@vue/cli-service": "^3.6.0",
|
||||
"@vue/eslint-config-standard": "^4.0.0",
|
||||
"ajv": "^6.6.2",
|
||||
"ajv": "^6.10.0",
|
||||
"babel-core": "^6.26.3",
|
||||
"babel-eslint": "^10.0.1",
|
||||
"babel-loader": "^7.1.4",
|
||||
"babel-eslint": "^10.0.2",
|
||||
"babel-loader": "^7.1.5",
|
||||
"babel-plugin-component": "^1.1.1",
|
||||
"babel-plugin-transform-runtime": "^6.23.0",
|
||||
"babel-preset-env": "^1.7.0",
|
||||
"babel-preset-stage-0": "^6.24.1",
|
||||
"babel-register": "^6.26.0",
|
||||
"babili-webpack-plugin": "^0.1.2",
|
||||
"cfonts": "^2.2.3",
|
||||
"chalk": "^2.4.1",
|
||||
"copy-webpack-plugin": "^4.6.0",
|
||||
"cfonts": "^2.4.3",
|
||||
"chalk": "^2.4.2",
|
||||
"copy-webpack-plugin": "^5.0.3",
|
||||
"cross-env": "^5.1.6",
|
||||
"css-loader": "^1.0.1",
|
||||
"del": "^3.0.0",
|
||||
"css-loader": "^2.1.1",
|
||||
"del": "^4.1.1",
|
||||
"devtron": "^1.4.0",
|
||||
"electron": "^4.0.0",
|
||||
"electron-builder": "^20.38.4",
|
||||
"electron": "^4.2.4",
|
||||
"electron-builder": "^20.43.0",
|
||||
"electron-devtools-installer": "^2.2.4",
|
||||
"electron-notarize": "^0.0.5",
|
||||
"electron-notarize": "^0.1.1",
|
||||
"electron-osx-sign": "^0.4.11",
|
||||
"electron-store": "^2.0.0",
|
||||
"eslint": "^5.11.1",
|
||||
"eslint": "^5.16.0",
|
||||
"eslint-config-standard": "^12.0.0",
|
||||
"eslint-friendly-formatter": "^4.0.1",
|
||||
"eslint-loader": "^2.1.1",
|
||||
"eslint-loader": "^2.1.2",
|
||||
"eslint-plugin-html": "^4.0.6",
|
||||
"eslint-plugin-import": "^2.12.0",
|
||||
"eslint-plugin-node": "^7.0.1",
|
||||
"eslint-plugin-promise": "^4.0.1",
|
||||
"eslint-plugin-import": "^2.17.3",
|
||||
"eslint-plugin-node": "^8.0.1",
|
||||
"eslint-plugin-promise": "^4.1.1",
|
||||
"eslint-plugin-standard": "^4.0.0",
|
||||
"file-loader": "^2.0.0",
|
||||
"eslint-plugin-vue": "^5.2.2",
|
||||
"file-loader": "^3.0.1",
|
||||
"html-webpack-plugin": "^3.2.0",
|
||||
"mini-css-extract-plugin": "0.5.0",
|
||||
"mini-css-extract-plugin": "0.7.0",
|
||||
"multispinner": "^0.2.1",
|
||||
"node-loader": "^0.6.0",
|
||||
"node-sass": "^4.10.0",
|
||||
"node-sass": "^4.12.0",
|
||||
"optimize-css-assets-webpack-plugin": "^5.0.1",
|
||||
"sass-loader": "^7.1.0",
|
||||
"style-loader": "^0.23.1",
|
||||
"url-loader": "^1.1.2",
|
||||
"vue-html-loader": "^1.2.4",
|
||||
"vue-loader": "^15.4.2",
|
||||
"vue-loader": "^15.7.0",
|
||||
"vue-style-loader": "^4.1.2",
|
||||
"vue-template-compiler": "^2.5.17",
|
||||
"webpack": "^4.28.3",
|
||||
"webpack-cli": "^3.1.2",
|
||||
"webpack-dev-server": "^3.1.14",
|
||||
"webpack-hot-middleware": "^2.24.3",
|
||||
"webpack-merge": "^4.1.4"
|
||||
"vue-template-compiler": "^2.6.10",
|
||||
"webpack": "^4.33.0",
|
||||
"webpack-cli": "^3.3.4",
|
||||
"webpack-dev-server": "^3.7.1",
|
||||
"webpack-hot-middleware": "^2.25.0",
|
||||
"webpack-merge": "^4.2.1"
|
||||
}
|
||||
}
|
||||
|
||||
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 46 KiB |
|
After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 38 KiB |
@@ -23,9 +23,11 @@
|
||||
</section>
|
||||
</div>
|
||||
<!-- Set `__static` path to static files in production -->
|
||||
<script>
|
||||
if (process.env.NODE_ENV !== 'development') window.__static = require('path').join(__dirname, '/static').replace(/\\/g, '\\\\')
|
||||
</script>
|
||||
<% if (!process.browser) { %>
|
||||
<script>
|
||||
if (process.env.NODE_ENV !== 'development') window.__static = require('path').join(__dirname, '/static').replace(/\\/g, '\\\\')
|
||||
</script>
|
||||
<% } %>
|
||||
|
||||
<!-- webpack builds are automatically injected -->
|
||||
</body>
|
||||
|
||||
@@ -1,29 +1,44 @@
|
||||
import { EventEmitter } from 'events'
|
||||
import { app, shell, dialog, ipcMain } from 'electron'
|
||||
import is from 'electron-is'
|
||||
import { readFile } from 'fs'
|
||||
import { extname, basename } from 'path'
|
||||
|
||||
import logger from './core/Logger'
|
||||
import ExceptionHandler from './core/ExceptionHandler'
|
||||
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'
|
||||
import WindowManager from './ui/WindowManager'
|
||||
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 () {
|
||||
super()
|
||||
this.isReady = false
|
||||
this.init()
|
||||
}
|
||||
|
||||
this.exceptionHandler = new ExceptionHandler()
|
||||
|
||||
this.locale = app.getLocale()
|
||||
logger.log('[Motrix] Locale: ', this.locale)
|
||||
|
||||
init () {
|
||||
this.configManager = new ConfigManager()
|
||||
|
||||
this.windowManager = new WindowManager()
|
||||
this.locale = this.configManager.getLocale()
|
||||
this.localeManager = setupLocaleManager(this.locale)
|
||||
this.i18n = this.localeManager.getI18n()
|
||||
|
||||
this.menuManager = new MenuManager()
|
||||
this.menuManager.setup(this.locale)
|
||||
|
||||
this.initTouchBarManager()
|
||||
|
||||
this.initWindowManager()
|
||||
|
||||
this.engine = new Engine({
|
||||
systemConfig: this.configManager.getSystemConfig(),
|
||||
@@ -31,10 +46,11 @@ export default class Application extends EventEmitter {
|
||||
})
|
||||
this.startEngine()
|
||||
|
||||
this.menuManager = new MenuManager()
|
||||
this.menuManager.setup(this.locale)
|
||||
this.trayManager = new TrayManager()
|
||||
|
||||
this.touchBarManager = new TouchBarManager()
|
||||
this.autoLaunchManager = new AutoLaunchManager()
|
||||
|
||||
this.initThemeManager()
|
||||
|
||||
this.energyManager = new EnergyManager()
|
||||
|
||||
@@ -43,6 +59,7 @@ export default class Application extends EventEmitter {
|
||||
this.initProtocolManager()
|
||||
|
||||
this.handleCommands()
|
||||
|
||||
this.handleIpcMessages()
|
||||
}
|
||||
|
||||
@@ -50,12 +67,11 @@ export default class Application extends EventEmitter {
|
||||
try {
|
||||
this.engine.start()
|
||||
} catch (err) {
|
||||
const { message } = err
|
||||
dialog.showMessageBox({
|
||||
type: 'error',
|
||||
title: '系统错误',
|
||||
message: `应用启动失败:${err.message}`,
|
||||
buttons: ['知道了'],
|
||||
cancelId: 1
|
||||
title: this.i18n.t('app.system-error-title'),
|
||||
message: this.i18n.t('app.system-error-message', { message })
|
||||
}, () => {
|
||||
setTimeout(() => {
|
||||
app.quit()
|
||||
@@ -64,13 +80,69 @@ 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)
|
||||
this.touchBarManager.setup(page, win)
|
||||
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')
|
||||
})
|
||||
if (is.macOS()) {
|
||||
this.touchBarManager.setup(page, win)
|
||||
}
|
||||
}
|
||||
|
||||
show (page = 'index') {
|
||||
this.windowManager.showWindow(page)
|
||||
}
|
||||
|
||||
hide (page) {
|
||||
if (page) {
|
||||
this.windowManager.hideWindow(page)
|
||||
} else {
|
||||
this.windowManager.hideAllWindow()
|
||||
}
|
||||
}
|
||||
|
||||
toggle (page = 'index') {
|
||||
this.windowManager.toggleWindow(page)
|
||||
}
|
||||
|
||||
closePage (page) {
|
||||
@@ -80,6 +152,7 @@ export default class Application extends EventEmitter {
|
||||
stop () {
|
||||
this.engine.stop()
|
||||
this.energyManager.stopPowerSaveBlocker()
|
||||
this.trayManager.destroy()
|
||||
}
|
||||
|
||||
sendCommand (command, ...args) {
|
||||
@@ -93,44 +166,101 @@ export default class Application extends EventEmitter {
|
||||
|
||||
sendCommandToAll (command, ...args) {
|
||||
if (!this.emit(command, ...args)) {
|
||||
this.windowManager.getWindows().forEach(window => {
|
||||
this.windowManager.getWindowList().forEach(window => {
|
||||
this.windowManager.sendCommandTo(window, command, ...args)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
sendMessageToAll (channel, ...args) {
|
||||
this.windowManager.getWindows().forEach(window => {
|
||||
this.windowManager.getWindowList().forEach(window => {
|
||||
this.windowManager.sendMessageTo(window, channel, ...args)
|
||||
})
|
||||
}
|
||||
|
||||
initProtocolManager () {
|
||||
if (is.mas()) {
|
||||
initThemeManager () {
|
||||
this.themeManager = new ThemeManager()
|
||||
this.themeManager.on('system-theme-changed', (theme) => {
|
||||
this.trayManager.changeIconTheme(theme)
|
||||
this.sendCommandToAll('application:system-theme', theme)
|
||||
})
|
||||
}
|
||||
|
||||
initTouchBarManager () {
|
||||
if (!is.macOS()) {
|
||||
return
|
||||
}
|
||||
this.protocolManager = new ProtocolManager()
|
||||
this.touchBarManager = new TouchBarManager()
|
||||
}
|
||||
|
||||
initProtocolManager () {
|
||||
if (is.dev() || is.mas()) {
|
||||
return
|
||||
}
|
||||
const protocols = this.configManager.getUserConfig('protocols', {})
|
||||
this.protocolManager = new ProtocolManager({
|
||||
protocols
|
||||
})
|
||||
}
|
||||
|
||||
handleProtocol (url) {
|
||||
if (is.mas()) {
|
||||
if (is.dev() || is.mas()) {
|
||||
return
|
||||
}
|
||||
|
||||
this.show()
|
||||
|
||||
this.protocolManager.handle(url)
|
||||
this.showPage('index')
|
||||
}
|
||||
|
||||
handleFile (filePath) {
|
||||
if (!filePath) {
|
||||
return
|
||||
}
|
||||
|
||||
if (extname(filePath).toLowerCase() !== '.torrent') {
|
||||
return
|
||||
}
|
||||
|
||||
this.show()
|
||||
|
||||
const fileName = basename(filePath)
|
||||
readFile(filePath, (err, data) => {
|
||||
if (err) {
|
||||
logger.warn(`[Motrix] read file error: ${filePath}`, err.message)
|
||||
return
|
||||
}
|
||||
const file = Buffer.from(data).toString('base64')
|
||||
const args = [fileName, file]
|
||||
this.sendCommandToAll('application:new-bt-task-with-file', ...args)
|
||||
})
|
||||
}
|
||||
|
||||
initUpdaterManager () {
|
||||
if (is.mas()) {
|
||||
return
|
||||
}
|
||||
this.updateManager = new UpdateManager()
|
||||
this.updateManager = new UpdateManager({
|
||||
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)
|
||||
this.trayManager.updateMenuItemEnabledState('app.check-for-updates', false)
|
||||
})
|
||||
|
||||
this.updateManager.on('download-progress', (event) => {
|
||||
@@ -140,10 +270,12 @@ export default class Application extends EventEmitter {
|
||||
|
||||
this.updateManager.on('update-not-available', (event) => {
|
||||
this.menuManager.updateMenuItemEnabledState('app.check-for-updates', true)
|
||||
this.trayManager.updateMenuItemEnabledState('app.check-for-updates', true)
|
||||
})
|
||||
|
||||
this.updateManager.on('update-downloaded', (event) => {
|
||||
this.menuManager.updateMenuItemEnabledState('app.check-for-updates', true)
|
||||
this.trayManager.updateMenuItemEnabledState('app.check-for-updates', true)
|
||||
const win = this.windowManager.getWindow('index')
|
||||
win.setProgressBar(0)
|
||||
})
|
||||
@@ -154,6 +286,7 @@ export default class Application extends EventEmitter {
|
||||
|
||||
this.updateManager.on('update-error', (event) => {
|
||||
this.menuManager.updateMenuItemEnabledState('app.check-for-updates', true)
|
||||
this.trayManager.updateMenuItemEnabledState('app.check-for-updates', true)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -176,12 +309,29 @@ export default class Application extends EventEmitter {
|
||||
})
|
||||
|
||||
this.on('application:exit', () => {
|
||||
this.engine.stop()
|
||||
this.stop()
|
||||
app.exit()
|
||||
})
|
||||
|
||||
this.on('application:show', (page = 'index') => {
|
||||
this.showPage(page)
|
||||
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)
|
||||
})
|
||||
|
||||
this.on('application:hide', (page) => {
|
||||
this.hide(page)
|
||||
})
|
||||
|
||||
this.on('application:reset', () => {
|
||||
@@ -193,8 +343,49 @@ export default class Application extends EventEmitter {
|
||||
this.updateManager.check()
|
||||
})
|
||||
|
||||
this.on('application:set-locale', (locale) => {
|
||||
this.menuManager.setup(locale)
|
||||
this.on('application:change-theme', (theme) => {
|
||||
this.themeManager.updateAppAppearance(theme)
|
||||
this.sendCommandToAll('application:theme', theme)
|
||||
})
|
||||
|
||||
this.on('application:change-locale', (locale) => {
|
||||
logger.info('[Motrix] application:change-locale===>', locale)
|
||||
this.localeManager.changeLanguageByLocale(locale)
|
||||
.then(() => {
|
||||
this.menuManager.setup(locale)
|
||||
this.trayManager.setup(locale)
|
||||
})
|
||||
})
|
||||
|
||||
this.on('application:open-file', (event) => {
|
||||
dialog.showOpenDialog({
|
||||
properties: ['openFile'],
|
||||
filters: [
|
||||
{
|
||||
name: 'Torrent',
|
||||
extensions: ['torrent']
|
||||
}
|
||||
]
|
||||
}, (filePaths) => {
|
||||
if (!filePaths || filePaths.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
const [filePath] = filePaths
|
||||
this.handleFile(filePath)
|
||||
})
|
||||
})
|
||||
|
||||
this.on('application:clear-recent-tasks', () => {
|
||||
app.clearRecentDocuments()
|
||||
})
|
||||
|
||||
this.on('application:setup-protocols-client', (protocols) => {
|
||||
if (is.dev() || is.mas()) {
|
||||
return
|
||||
}
|
||||
console.log('this.protocolManager', protocols)
|
||||
this.protocolManager.setup(protocols)
|
||||
})
|
||||
|
||||
this.on('help:official-website', () => {
|
||||
@@ -225,7 +416,12 @@ export default class Application extends EventEmitter {
|
||||
})
|
||||
|
||||
ipcMain.on('update-menu-states', (event, visibleStates, enabledStates, checkedStates) => {
|
||||
this.menuManager.updateStates(visibleStates, enabledStates, checkedStates)
|
||||
this.menuManager.updateMenuStates(visibleStates, enabledStates, checkedStates)
|
||||
this.trayManager.updateMenuStates(visibleStates, enabledStates, checkedStates)
|
||||
})
|
||||
|
||||
ipcMain.on('download-status-change', (event, status) => {
|
||||
this.trayManager.updateStatus(status)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,185 @@
|
||||
import { EventEmitter } from 'events'
|
||||
import { app } from 'electron'
|
||||
import is from 'electron-is'
|
||||
|
||||
import ExceptionHandler from './core/ExceptionHandler'
|
||||
import logger from './core/Logger'
|
||||
import Application from './Application'
|
||||
import {
|
||||
splitArgv,
|
||||
parseArgvAsUrl,
|
||||
parseArgvAsFile
|
||||
} from './utils'
|
||||
|
||||
const EMPTY_STRING = ''
|
||||
|
||||
export default class Launcher extends EventEmitter {
|
||||
constructor () {
|
||||
super()
|
||||
this.url = EMPTY_STRING
|
||||
this.file = EMPTY_STRING
|
||||
|
||||
this.makeSingleInstance(() => {
|
||||
this.init()
|
||||
})
|
||||
}
|
||||
|
||||
makeSingleInstance (callback) {
|
||||
// Mac App Store Sandboxed App not support requestSingleInstanceLock
|
||||
if (is.mas()) {
|
||||
callback()
|
||||
return
|
||||
}
|
||||
|
||||
const gotSingleLock = app.requestSingleInstanceLock()
|
||||
|
||||
if (!gotSingleLock) {
|
||||
app.quit()
|
||||
} else {
|
||||
app.on('second-instance', (event, argv, workingDirectory) => {
|
||||
logger.warn('second-instance====>', argv, workingDirectory)
|
||||
global.application.showPage('index')
|
||||
if (!is.macOS() && argv.length > 1) {
|
||||
this.handleAppLaunchArgv(argv)
|
||||
}
|
||||
})
|
||||
|
||||
callback()
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
handleAppEvents () {
|
||||
this.handleOpenUrl()
|
||||
this.handleOpenFile()
|
||||
|
||||
this.handelAppReady()
|
||||
this.handleAppWillQuit()
|
||||
}
|
||||
|
||||
/**
|
||||
* handleOpenUrl
|
||||
* Event 'open-url' macOS only
|
||||
* "name": "Motrix Protocol",
|
||||
* "schemes": ["mo", "motrix"]
|
||||
*/
|
||||
handleOpenUrl () {
|
||||
if (is.mas() || !is.macOS()) {
|
||||
return
|
||||
}
|
||||
app.on('open-url', (event, url) => {
|
||||
logger.info(`[Motrix] open-url: ${url}`)
|
||||
event.preventDefault()
|
||||
this.url = url
|
||||
this.sendUrlToApplication()
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* handleOpenFile
|
||||
* Event 'open-file' macOS only
|
||||
* handle open torrent file
|
||||
*/
|
||||
handleOpenFile () {
|
||||
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) {
|
||||
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(args)
|
||||
if (url) {
|
||||
this.url = url
|
||||
this.sendUrlToApplication()
|
||||
}
|
||||
}
|
||||
|
||||
sendUrlToApplication () {
|
||||
if (this.url && global.application && global.application.isReady) {
|
||||
global.application.handleProtocol(this.url)
|
||||
this.url = EMPTY_STRING
|
||||
}
|
||||
}
|
||||
|
||||
sendFileToApplication () {
|
||||
if (this.file && global.application && global.application.isReady) {
|
||||
global.application.handleFile(this.file)
|
||||
this.file = EMPTY_STRING
|
||||
}
|
||||
}
|
||||
|
||||
handelAppReady () {
|
||||
app.on('ready', () => {
|
||||
global.application = new Application()
|
||||
|
||||
const { openedAtLogin } = this
|
||||
global.application.start('index', {
|
||||
openedAtLogin
|
||||
})
|
||||
|
||||
global.application.on('ready', () => {
|
||||
this.sendUrlToApplication()
|
||||
|
||||
this.sendFileToApplication()
|
||||
})
|
||||
})
|
||||
|
||||
app.on('activate', () => {
|
||||
if (global.application) {
|
||||
logger.info('[Motrix] activate')
|
||||
global.application.showPage('index')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
handleAppWillQuit () {
|
||||
app.on('will-quit', () => {
|
||||
logger.info('[Motrix] will-quit')
|
||||
if (global.application) {
|
||||
global.application.stop()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
export default {
|
||||
'darwin': 'aria2c',
|
||||
'win32': 'aria2.exe',
|
||||
'win32': 'aria2c.exe',
|
||||
'linux': 'aria2c'
|
||||
}
|
||||
|
||||
@@ -13,17 +13,5 @@ export default {
|
||||
},
|
||||
bindCloseToHide: true,
|
||||
url: is.dev() ? `http://localhost:9080` : `file://${__dirname}/index.html`
|
||||
},
|
||||
about: {
|
||||
attrs: {
|
||||
title: '关于',
|
||||
width: 580,
|
||||
height: 320,
|
||||
backgroundColor: '#FFFFFF',
|
||||
resizable: false,
|
||||
minimizable: false,
|
||||
maximizable: false
|
||||
},
|
||||
url: is.dev() ? `http://localhost:9080/about` : `file://${__dirname}/about.html`
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
export default {
|
||||
'task-list': 'application:task-list',
|
||||
'new-task': 'application:new-task',
|
||||
'new-bt-task': 'application:new-bt-task',
|
||||
'pause-all-task': 'application:pause-all-task',
|
||||
'resume-all-task': 'application:resume-all-task',
|
||||
'preferences': 'application:preferences',
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
export default [
|
||||
'udp://62.138.0.158:6969/announce',
|
||||
'udp://93.158.213.92:1337/announce',
|
||||
'udp://185.225.17.100:1337/announce',
|
||||
'udp://151.80.120.112:2710/announce',
|
||||
'udp://151.80.120.114:2710/announce',
|
||||
'udp://185.19.107.254:80/announce',
|
||||
'udp://208.83.20.20:6969/announce',
|
||||
'udp://5.206.27.172:6969/announce',
|
||||
'udp://176.31.241.153:80/announce',
|
||||
'udp://37.235.174.46:2710/announce',
|
||||
'udp://95.211.168.204:2710/announce',
|
||||
'udp://159.100.245.181:6969/announce',
|
||||
'http://51.68.122.172:80/announce',
|
||||
'udp://89.234.156.205:451/announce',
|
||||
'udp://184.105.151.164:6969/announce',
|
||||
'udp://51.15.40.114:80/announce',
|
||||
'http://82.209.230.66:80/announce',
|
||||
'udp://185.83.215.123:6969/announce',
|
||||
'udp://195.154.52.99:80/announce',
|
||||
'http://51.38.230.101:80/announce',
|
||||
'udp://tracker.coppersurfer.tk:6969/announce',
|
||||
'udp://tracker.opentrackr.org:1337/announce',
|
||||
'udp://tracker.internetwarriors.net:1337/announce',
|
||||
'udp://9.rarbg.to:2710/announce',
|
||||
'udp://9.rarbg.me:2710/announce',
|
||||
'udp://tracker.openbittorrent.com:80/announce',
|
||||
'udp://exodus.desync.com:6969/announce',
|
||||
'udp://tracker.tiny-vps.com:6969/announce',
|
||||
'udp://thetracker.org:80/announce',
|
||||
'udp://retracker.lanta-net.ru:2710/announce',
|
||||
'udp://bt.xxx-tracker.com:2710/announce',
|
||||
'udp://tracker.cyberia.is:6969/announce',
|
||||
'http://open.acgnxtracker.com:80/announce',
|
||||
'udp://tracker.torrent.eu.org:451/announce',
|
||||
'udp://explodie.org:6969/announce',
|
||||
'udp://ipv4.tracker.harry.lu:80/announce',
|
||||
'http://retracker.mgts.by:80/announce',
|
||||
'udp://tracker.uw0.xyz:6969/announce',
|
||||
'udp://open.stealth.si:80/announce',
|
||||
'http://t.nyaatracker.com:80/announce'
|
||||
]
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
import { app } from 'electron'
|
||||
import is from 'electron-is'
|
||||
import Store from 'electron-store'
|
||||
import tracker from '../configs/tracker'
|
||||
import {
|
||||
getDhtPath,
|
||||
getLogPath,
|
||||
getSessionPath,
|
||||
getUserDownloadsPath
|
||||
@@ -20,27 +22,36 @@ export default class ConfigManager {
|
||||
this.initUserConfig()
|
||||
}
|
||||
|
||||
/**
|
||||
* Some aria2 conf
|
||||
* https://aria2.github.io/manual/en/html/aria2c.html
|
||||
*
|
||||
* Best bt trackers
|
||||
* https://github.com/ngosang/trackerslist
|
||||
*/
|
||||
initSystemConfig () {
|
||||
this.systemConfig = new Store({
|
||||
name: 'system',
|
||||
defaults: {
|
||||
dir: getUserDownloadsPath(),
|
||||
// 断点续传
|
||||
continue: true,
|
||||
pause: true,
|
||||
split: 16,
|
||||
'all-proxy': '',
|
||||
'allow-overwrite': true,
|
||||
'auto-file-renaming': true,
|
||||
'bt-tracker': tracker.join(','),
|
||||
'continue': true,
|
||||
'dht-file-path': getDhtPath(4),
|
||||
'dht-file-path6': getDhtPath(6),
|
||||
'dir': getUserDownloadsPath(),
|
||||
'max-concurrent-downloads': 5,
|
||||
'max-connection-per-server': is.macOS() ? 64 : 16,
|
||||
'max-download-limit': 0,
|
||||
'max-overall-download-limit': 0,
|
||||
'max-overall-upload-limit': '128K',
|
||||
'min-split-size': '1M',
|
||||
'pause': true,
|
||||
'rpc-listen-port': 16800,
|
||||
'rpc-secret': '',
|
||||
'auto-file-renaming': true,
|
||||
'allow-overwrite': true,
|
||||
'max-concurrent-downloads': 5,
|
||||
// macOS 版本修改过源码自己编译的,Linux 和 Windows 版本 暂未处理
|
||||
'max-connection-per-server': is.macOS() ? 64 : 16,
|
||||
'min-split-size': '1M',
|
||||
'max-overall-download-limit': 0,
|
||||
'max-overall-upload-limit': 0,
|
||||
'max-download-limit': 0,
|
||||
'all-proxy': '',
|
||||
'seed-time': 60,
|
||||
'split': 16,
|
||||
'user-agent': 'Transmission/2.94'
|
||||
}
|
||||
})
|
||||
@@ -49,19 +60,44 @@ export default class ConfigManager {
|
||||
initUserConfig () {
|
||||
this.userConfig = new Store({
|
||||
name: 'user',
|
||||
// Schema need electron-store upgrade to 3.x.x,
|
||||
// but it will cause the application build to fail.
|
||||
// schema: {
|
||||
// theme: {
|
||||
// type: 'string',
|
||||
// enum: ['auto', 'light', 'dark']
|
||||
// }
|
||||
// },
|
||||
defaults: {
|
||||
'resume-all-when-app-launched': false,
|
||||
'task-notification': true,
|
||||
'all-proxy-backup': '',
|
||||
'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,
|
||||
'auto-check-for-updates': false,
|
||||
'open-at-login': false,
|
||||
'protocols': { 'magnet': true, 'thunder': 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,
|
||||
'all-proxy-backup': '',
|
||||
'log-path': getLogPath(),
|
||||
'session-path': getSessionPath(),
|
||||
'locale': app.getLocale()
|
||||
'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) {
|
||||
@@ -82,6 +118,10 @@ export default class ConfigManager {
|
||||
return this.userConfig.get(key, defaultValue)
|
||||
}
|
||||
|
||||
getLocale () {
|
||||
return this.getUserConfig('locale') || app.getLocale()
|
||||
}
|
||||
|
||||
setSystemConfig (...args) {
|
||||
this.systemConfig.set(...args)
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import { existsSync } from 'fs'
|
||||
import { resolve, join } from 'path'
|
||||
import forever from 'forever-monitor'
|
||||
import logger from './Logger'
|
||||
import { getI18n } from '@/ui/Locale'
|
||||
import {
|
||||
getEngineBin,
|
||||
getSessionPath,
|
||||
@@ -16,6 +17,9 @@ export default class Engine {
|
||||
static instance = null
|
||||
|
||||
constructor (options = {}) {
|
||||
this.options = options
|
||||
|
||||
this.i18n = getI18n()
|
||||
this.systemConfig = options.systemConfig
|
||||
this.userConfig = options.userConfig
|
||||
}
|
||||
@@ -30,14 +34,14 @@ export default class Engine {
|
||||
|
||||
const binName = getEngineBin(platform)
|
||||
if (!binName) {
|
||||
throw new Error('引擎已损坏,请重新安装: (')
|
||||
throw new Error(this.i18n.t('app.engine-damaged-message'))
|
||||
}
|
||||
|
||||
let binPath = join(basePath, `/engine/${binName}`)
|
||||
const binIsExist = existsSync(binPath)
|
||||
if (!binIsExist) {
|
||||
logger.error('[Motrix] engine bin is not exist===>', binPath)
|
||||
throw new Error('引擎文件缺失,请重新安装: (')
|
||||
throw new Error(this.i18n.t('app.engine-missing-message'))
|
||||
}
|
||||
|
||||
let confPath = join(basePath, '/engine/aria2.conf')
|
||||
@@ -60,7 +64,7 @@ export default class Engine {
|
||||
const sh = this.getStartSh()
|
||||
logger.info('[Motrix] Engine start sh===>', sh)
|
||||
this.instance = forever.start(sh, {
|
||||
max: 3,
|
||||
max: 100,
|
||||
parser: function (command, args) {
|
||||
return {
|
||||
command: command,
|
||||
@@ -73,13 +77,37 @@ export default class Engine {
|
||||
const { child } = this.instance
|
||||
logger.info('[Motrix] Engine pid===>', child.pid)
|
||||
|
||||
this.instance.on('exit:code', function (code) {
|
||||
logger.info(`[Motrix] Engine has exited after 3 restarts===> ${code}`)
|
||||
this.instance.on('error', (err) => {
|
||||
logger.info(`[Motrix] Engine error===> ${err}`)
|
||||
})
|
||||
|
||||
this.instance.on('error', (err) => {
|
||||
logger.info(`[Motrix] Engine has exited after 3 restarts===> ${err}`)
|
||||
this.instance.on('start', function (process, data) {
|
||||
logger.info(`[Motrix] Engine started===>`)
|
||||
})
|
||||
|
||||
this.instance.on('stop', function (process) {
|
||||
logger.info(`[Motrix] Engine stopped===>`)
|
||||
})
|
||||
|
||||
this.instance.on('restart', function (forever) {
|
||||
logger.info(`[Motrix] Engine exit===>`)
|
||||
})
|
||||
|
||||
this.instance.on('exit:code', function (code) {
|
||||
logger.info(`[Motrix] Engine exit===> ${code}`)
|
||||
})
|
||||
|
||||
// this.instance.on('stderr', (data) => {
|
||||
// logger.info(`[Motrix] Engine stderr===> ${data}`)
|
||||
// })
|
||||
}
|
||||
|
||||
isRunning (pid) {
|
||||
try {
|
||||
return process.kill(pid, 0)
|
||||
} catch (e) {
|
||||
return e.code === 'EPERM'
|
||||
}
|
||||
}
|
||||
|
||||
stop () {
|
||||
@@ -87,7 +115,6 @@ export default class Engine {
|
||||
try {
|
||||
logger.info('[Motrix] Engine stopping===>')
|
||||
this.instance.stop()
|
||||
logger.info('[Motrix] Engine stopped===>', pid)
|
||||
} catch (err) {
|
||||
logger.error('[Motrix] Engine stop fail===>', err.message)
|
||||
this.forceStop(pid)
|
||||
@@ -97,7 +124,7 @@ export default class Engine {
|
||||
|
||||
forceStop (pid) {
|
||||
try {
|
||||
if (pid) {
|
||||
if (pid && this.isRunning(pid)) {
|
||||
process.kill(pid)
|
||||
}
|
||||
} catch (err) {
|
||||
|
||||
@@ -26,7 +26,7 @@ export default class ExceptionHandler {
|
||||
logger.error(stack)
|
||||
|
||||
if (showDialog && app.isReady()) {
|
||||
dialog.showErrorBox('系统错误', message)
|
||||
dialog.showErrorBox('Error: ', message)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import is from 'electron-is'
|
||||
import logger from 'electron-log'
|
||||
|
||||
logger.transports.file.level = is.production() ? 'warn' : 'info'
|
||||
logger.transports.file.level = is.production() ? 'warn' : 'silly'
|
||||
logger.info('Logger init')
|
||||
logger.warn('[Motrix] Logger init')
|
||||
|
||||
export default logger
|
||||
|
||||
@@ -9,21 +9,68 @@ export default class ProtocolManager extends EventEmitter {
|
||||
super()
|
||||
this.options = options
|
||||
|
||||
// package.json:build.protocols[].schemes[]
|
||||
// options.protocols: { 'magnet': true, 'thunder': false }
|
||||
this.protocols = {
|
||||
mo: true,
|
||||
motrix: true,
|
||||
...options.protocols
|
||||
}
|
||||
|
||||
this.init()
|
||||
}
|
||||
|
||||
init () {
|
||||
// package.json:build.mac.protocols[].schemes[]
|
||||
if (!app.isDefaultProtocolClient('mo')) {
|
||||
app.setAsDefaultProtocolClient('mo')
|
||||
}
|
||||
if (!app.isDefaultProtocolClient('motrix')) {
|
||||
app.setAsDefaultProtocolClient('motrix')
|
||||
}
|
||||
const { protocols } = this
|
||||
this.setup(protocols)
|
||||
}
|
||||
|
||||
setup (protocols) {
|
||||
Object.keys(protocols).forEach((protocol) => {
|
||||
const enabled = protocols[protocol]
|
||||
if (enabled) {
|
||||
if (!app.isDefaultProtocolClient(protocol)) {
|
||||
app.setAsDefaultProtocolClient(protocol)
|
||||
}
|
||||
} else {
|
||||
app.removeAsDefaultProtocolClient(protocol)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
handle (url) {
|
||||
logger.info(`[Motrix] protocol url: ${url}`)
|
||||
|
||||
this.handleMagnetAndThunderProtocol(url)
|
||||
|
||||
if (
|
||||
url.toLowerCase().startsWith('mo:') ||
|
||||
url.toLowerCase().startsWith('motrix:')
|
||||
) {
|
||||
return this.handleMoProtocol(url)
|
||||
}
|
||||
}
|
||||
|
||||
handleMagnetAndThunderProtocol (url) {
|
||||
if (!url) {
|
||||
return
|
||||
}
|
||||
let protocolTag = ''
|
||||
|
||||
if (url.toLowerCase().startsWith('magnet:')) {
|
||||
protocolTag = 'handleMagnetProtocol'
|
||||
}
|
||||
|
||||
if (url.toLowerCase().startsWith('thunder:')) {
|
||||
protocolTag = 'handleThunderProtocol'
|
||||
}
|
||||
|
||||
logger.error(`[Motrix] ${protocolTag} url: ${url}`)
|
||||
|
||||
global.application.sendCommandToAll('application:new-task', 'uri', url)
|
||||
}
|
||||
|
||||
handleMoProtocol (url) {
|
||||
const parsed = new URL(url)
|
||||
const { host } = parsed
|
||||
logger.info('[Motrix] protocol parsed:', parsed, host)
|
||||
@@ -37,6 +84,6 @@ export default class ProtocolManager extends EventEmitter {
|
||||
// 如果按顺序传递,那 url 的 query string 就要求有序的了
|
||||
// const query = queryString.parse(parsed.query)
|
||||
const args = []
|
||||
global.application.sendCommand(command, ...args)
|
||||
global.application.sendCommandToAll(command, ...args)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import is from 'electron-is'
|
||||
import { autoUpdater } from 'electron-updater'
|
||||
import { resolve } from 'path'
|
||||
import logger from './Logger'
|
||||
import { getI18n } from '@/ui/Locale'
|
||||
|
||||
if (is.dev()) {
|
||||
autoUpdater.updateConfigPath = resolve(__dirname, '../../../app-update.yml')
|
||||
@@ -13,10 +14,15 @@ export default class UpdateManager extends EventEmitter {
|
||||
constructor (options = {}) {
|
||||
super()
|
||||
this.options = options
|
||||
this.i18n = getI18n()
|
||||
|
||||
this.updater = autoUpdater
|
||||
this.updater.autoDownload = false
|
||||
this.updater.logger = logger
|
||||
this.autoCheckData = {
|
||||
checkEnable: this.options.autoCheck,
|
||||
userCheck: false
|
||||
}
|
||||
this.init()
|
||||
}
|
||||
|
||||
@@ -34,9 +40,17 @@ export default class UpdateManager extends EventEmitter {
|
||||
this.updater.on('download-progress', this.updateDownloadProgress.bind(this))
|
||||
this.updater.on('update-downloaded', this.updateDownloaded.bind(this))
|
||||
this.updater.on('error', this.updateError.bind(this))
|
||||
|
||||
if (this.autoCheckData.checkEnable) {
|
||||
this.autoCheckData.userCheck = false
|
||||
this.options.setCheckTime.setUserConfig('last-check-update-time', Date.now())
|
||||
this.updater.checkForUpdates()
|
||||
}
|
||||
}
|
||||
|
||||
check () {
|
||||
this.options.setCheckTime.setUserConfig('last-check-update-time', Date.now())
|
||||
this.autoCheckData.userCheck = true
|
||||
this.updater.checkForUpdates()
|
||||
}
|
||||
|
||||
@@ -48,9 +62,9 @@ export default class UpdateManager extends EventEmitter {
|
||||
this.emit('update-available', info)
|
||||
dialog.showMessageBox({
|
||||
type: 'info',
|
||||
title: '发现新版本',
|
||||
message: '发现新版本,是否现在更新?',
|
||||
buttons: ['是', '否'],
|
||||
title: this.i18n.t('app.check-for-updates-title'),
|
||||
message: this.i18n.t('app.update-available-message'),
|
||||
buttons: [this.i18n.t('app.yes'), this.i18n.t('app.no')],
|
||||
cancelId: 1
|
||||
}, (buttonIndex) => {
|
||||
if (buttonIndex === 0) {
|
||||
@@ -61,10 +75,12 @@ export default class UpdateManager extends EventEmitter {
|
||||
|
||||
updateNotAvailable (event, info) {
|
||||
this.emit('update-not-available', info)
|
||||
dialog.showMessageBox({
|
||||
title: '没有更新的版本',
|
||||
message: '您目前使用的已是最新版本'
|
||||
})
|
||||
if (this.autoCheckData.userCheck) {
|
||||
dialog.showMessageBox({
|
||||
title: this.i18n.t('app.check-for-updates-title'),
|
||||
message: this.i18n.t('app.update-not-available-message')
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -84,8 +100,8 @@ export default class UpdateManager extends EventEmitter {
|
||||
this.emit('update-downloaded', info)
|
||||
this.updater.logger.log(`Update Downloaded: ${info}`)
|
||||
dialog.showMessageBox({
|
||||
title: '安装更新',
|
||||
message: '更新下载完成,应用程序将退出并开始更新...'
|
||||
title: this.i18n.t('app.check-for-updates-title'),
|
||||
message: this.i18n.t('app.update-downloaded-message')
|
||||
}, () => {
|
||||
this.emit('will-updated')
|
||||
setImmediate(() => {
|
||||
@@ -96,8 +112,11 @@ export default class UpdateManager extends EventEmitter {
|
||||
|
||||
updateError (event, error) {
|
||||
this.emit('update-error', error)
|
||||
const msg = error == null ? '未知错误' : (error.stack || error).toString()
|
||||
this.updater.logger.warn(`Update Error: ${msg}`)
|
||||
dialog.showErrorBox('错误: ', msg)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { app } from 'electron'
|
||||
import is from 'electron-is'
|
||||
|
||||
import logger from './core/Logger'
|
||||
import Application from './Application'
|
||||
import Launcher from './Launcher'
|
||||
|
||||
if (process.env.NODE_ENV !== 'development') {
|
||||
global.__static = require('path').join(__dirname, '/static').replace(/\\/g, '\\\\')
|
||||
@@ -12,67 +11,8 @@ if (process.env.NODE_ENV !== 'development') {
|
||||
* Fix Windows notification func
|
||||
* appId defined in .electron-vue/webpack.main.config.js
|
||||
*/
|
||||
if (process.platform === 'win32') {
|
||||
if (is.windows()) {
|
||||
app.setAppUserModelId(appId)
|
||||
}
|
||||
|
||||
function _init () {
|
||||
let openURL = null
|
||||
if (!is.mas()) {
|
||||
app.on('open-url', (event, url) => {
|
||||
logger.info(`You arrived from: ${url}`)
|
||||
event.preventDefault()
|
||||
openURL = url
|
||||
if (global.application) {
|
||||
global.application.handleProtocol(openURL)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
app.on('ready', () => {
|
||||
global.application = new Application()
|
||||
global.application.start('index')
|
||||
|
||||
if (openURL) {
|
||||
global.application.handleProtocol(openURL)
|
||||
}
|
||||
})
|
||||
|
||||
app.on('will-quit', () => {
|
||||
logger.warn('will-quit')
|
||||
global.application && global.application.stop()
|
||||
})
|
||||
|
||||
// Quit when all windows are closed.
|
||||
app.on('window-all-closed', function () {
|
||||
// On OS X it's common NOT to close app even if all windows are closed
|
||||
if (process.platform !== 'darwin') {
|
||||
app.quit()
|
||||
}
|
||||
})
|
||||
|
||||
app.on('activate', () => {
|
||||
global.application.showPage('index')
|
||||
})
|
||||
}
|
||||
|
||||
function init () {
|
||||
// Mac App Store Sandboxed App Not support requestSingleInstanceLock
|
||||
if (is.mas()) {
|
||||
_init()
|
||||
} else {
|
||||
const gotSingleLock = app.requestSingleInstanceLock()
|
||||
|
||||
if (!gotSingleLock) {
|
||||
app.quit()
|
||||
} else {
|
||||
app.on('second-instance', (event, commandLine, workingDirectory) => {
|
||||
global.application.showPage('index')
|
||||
})
|
||||
|
||||
_init()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init()
|
||||
global.launcher = new Launcher()
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
{
|
||||
"menu": [
|
||||
{
|
||||
"id": "menu.app",
|
||||
"submenu": [
|
||||
{ "id": "app.about", "command": "application:about", "command-before": "application:show,index" },
|
||||
{ "type": "separator" },
|
||||
{ "id": "app.preferences", "command": "application:preferences" },
|
||||
{ "id": "app.check-for-updates", "command": "application:check-for-updates" },
|
||||
{ "id": "app.hide", "role": "hide" },
|
||||
{ "id": "app.hide-others", "role": "hideothers" },
|
||||
{ "id": "app.unhide", "role": "unhide" },
|
||||
{ "type": "separator" },
|
||||
{ "id": "app.quit", "role": "quit" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "menu.task",
|
||||
"submenu": [
|
||||
{ "id": "task.new-task", "command": "application:new-task", "command-after": "application:show,index" },
|
||||
{ "id": "task.new-bt-task", "command": "application:new-bt-task", "command-arg": "torrent", "command-after": "application:show,index" },
|
||||
{ "id": "task.open-file", "command": "application:open-file", "command-before": "application:show,index" },
|
||||
{ "type": "separator" },
|
||||
{ "id": "task.pause-task", "command": "application:pause-task" },
|
||||
{ "id": "task.resume-task", "command": "application:resume-task" },
|
||||
{ "id": "task.delete-task", "command": "application:delete-task" },
|
||||
{ "id": "task.move-task-up", "command": "application:move-task-up" },
|
||||
{ "id": "task.move-task-down", "command": "application:move-task-down" },
|
||||
{ "type": "separator" },
|
||||
{ "id": "task.pause-all-task", "command": "application:pause-all-task" },
|
||||
{ "id": "task.resume-all-task", "command": "application:resume-all-task" },
|
||||
{ "type": "separator" },
|
||||
{ "id": "task.clear-recent-tasks", "command": "application:clear-recent-tasks" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "menu.edit",
|
||||
"submenu": [
|
||||
{ "id": "edit.undo", "role": "undo" },
|
||||
{ "id": "edit.redo", "role": "redo" },
|
||||
{ "type": "separator" },
|
||||
{ "id": "edit.cut", "role": "cut" },
|
||||
{ "id": "edit.copy", "role": "copy" },
|
||||
{ "id": "edit.paste", "role": "paste" },
|
||||
{ "id": "edit.delete", "role": "delete" },
|
||||
{ "id": "edit.select-all", "role": "selectall" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"role": "window",
|
||||
"id": "menu.window",
|
||||
"submenu": [
|
||||
{ "id": "window.reload", "role": "reload" },
|
||||
{ "id": "window.close", "role": "close" },
|
||||
{ "id": "window.minimize", "role": "minimize" },
|
||||
{ "id": "window.zoom", "role": "zoom" },
|
||||
{ "id": "window.toggle-fullscreen", "role": "togglefullscreen" },
|
||||
{ "type": "separator" },
|
||||
{ "id": "window.front", "role": "front" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"role": "help",
|
||||
"id": "menu.help",
|
||||
"submenu": [
|
||||
{ "id": "help.official-website", "command": "help:official-website" },
|
||||
{ "id": "help.manual", "command": "help:manual" },
|
||||
{ "id": "help.release-notes", "command": "help:release-notes" },
|
||||
{ "type": "separator" },
|
||||
{ "id": "help.report-problem", "command": "help:report-problem" },
|
||||
{ "type": "separator" },
|
||||
{ "id": "help.toggle-dev-tools", "role": "toggledevtools" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
{
|
||||
"menu": [
|
||||
{
|
||||
"label": "Motrix",
|
||||
"id": "app",
|
||||
"submenu": [
|
||||
{ "label": "About Motrix", "id": "app.about", "command": "application:about", "command-before": "application:show,index"},
|
||||
{ "type": "separator" },
|
||||
{ "label": "Preferences...", "id": "app.preferences", "command": "application:preferences" },
|
||||
{ "label": "Check for Updates...", "id": "app.check-for-updates", "command": "application:check-for-updates" },
|
||||
{ "label": "Services", "role": "services", "submenu": [] },
|
||||
{ "label": "Hide Motrix", "role": "hide" },
|
||||
{ "label": "Hide Others", "role": "hideothers" },
|
||||
{ "label": "Show All", "role": "unhide" },
|
||||
{ "type": "separator" },
|
||||
{ "label": "Quit Motrix", "id": "app.quit", "role": "quit" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Task",
|
||||
"id": "task",
|
||||
"submenu": [
|
||||
{ "label": "New Task", "id": "task.new-task", "command": "application:new-task", "command-arg": "uri", "command-after": "application:show,index"},
|
||||
{ "label": "New BT Task", "id": "task.new-bt-task", "command": "application:new-bt-task", "command-arg": "torrent", "command-after": "application:show,index"},
|
||||
{ "type": "separator" },
|
||||
{ "label": "Pause Task", "id": "task.pause-task", "command": "application:pause-task" },
|
||||
{ "label": "Resume Task", "id": "task.resume-task", "command": "application:resume-task" },
|
||||
{ "label": "Delete Task", "id": "task.delete-task", "command": "application:delete-task" },
|
||||
{ "label": "Move Task Up", "id": "task.move-task-up", "command": "application:move-task-up" },
|
||||
{ "label": "Move Task Down", "id": "task.move-task-down", "command": "application:move-task-down" },
|
||||
{ "type": "separator" },
|
||||
{ "label": "Pause All Task", "id": "task.pause-all-task", "command": "application:pause-all-task" },
|
||||
{ "label": "Resume All Task", "id": "task.resume-all-task", "command": "application:resume-all-task" },
|
||||
{ "type": "separator" },
|
||||
{ "label": "Clear Recent Tasks", "id": "task.clear-recent-tasks", "command": "application:clear-recent-tasks" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Edit",
|
||||
"id": "edit",
|
||||
"submenu": [
|
||||
{ "label": "Undo", "id": "edit.undo", "role": "undo" },
|
||||
{ "label": "Redo", "id": "edit.redo", "role": "redo" },
|
||||
{ "type": "separator" },
|
||||
{ "label": "Cut", "id": "edit.cut", "role": "cut" },
|
||||
{ "label": "Copy", "id": "edit.copy", "role": "copy" },
|
||||
{ "label": "Paste", "id": "edit.paste", "role": "paste" },
|
||||
{ "label": "Delete", "id": "edit.delete", "role": "delete" },
|
||||
{ "label": "Select All", "id": "edit.select-all", "role": "selectall" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Window",
|
||||
"role": "window",
|
||||
"id": "window",
|
||||
"submenu": [
|
||||
{ "label": "Reload", "id": "view.reload", "role": "reload" },
|
||||
{ "label": "Close", "role": "close" },
|
||||
{ "label": "Minimize", "role": "minimize" },
|
||||
{ "label": "Zoom", "role": "zoom" },
|
||||
{ "label": "Enter Full Screen", "role": "togglefullscreen" },
|
||||
{ "type": "separator" },
|
||||
{ "label": "Bring All to Front", "role": "front" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Help",
|
||||
"role": "help",
|
||||
"id": "help",
|
||||
"submenu": [
|
||||
{ "label": "Motrix Website", "id": "help.official-website", "command": "help:official-website" },
|
||||
{ "label": "Manual", "id": "help.manual", "command": "help:manual" },
|
||||
{ "label": "Release Notes...", "id": "help.release-notes", "command": "help:release-notes" },
|
||||
{ "type": "separator" },
|
||||
{ "label": "Report Problem", "id": "help.report-problem", "command": "help:report-problem" },
|
||||
{ "type": "separator" },
|
||||
{ "label": "Toggle Developer Tools", "id": "help.toggle-dev-tools", "role": "toggledevtools" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
{
|
||||
"menu": [
|
||||
{
|
||||
"label": "Motrix",
|
||||
"id": "app",
|
||||
"submenu": [
|
||||
{ "label": "About Motrix", "id": "app.about", "command": "application:about" },
|
||||
{ "type": "separator" },
|
||||
{ "label": "Preferences...", "id": "app.preferences", "command": "application:preferences" },
|
||||
{ "label": "Check for Updates...", "id": "app.check-for-updates", "command": "application:check-for-updates" },
|
||||
{ "label": "Services", "role": "services", "submenu": [] },
|
||||
{ "label": "Hide Motrix", "role": "hide" },
|
||||
{ "label": "Hide Others", "role": "hideothers" },
|
||||
{ "label": "Show All", "role": "unhide" },
|
||||
{ "type": "separator" },
|
||||
{ "label": "Quit Motrix", "id": "app.quit", "role": "quit" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Task",
|
||||
"id": "task",
|
||||
"submenu": [
|
||||
{ "label": "New Task", "id": "task.new-task", "command": "application:new-task", "command-arg": "uri" },
|
||||
{ "label": "New BT Task", "id": "task.new-bt-task", "command": "application:new-bt-task", "command-arg": "torrent" },
|
||||
{ "type": "separator" },
|
||||
{ "label": "Pause Task", "id": "task.pause-task", "command": "application:pause-task" },
|
||||
{ "label": "Resume Task", "id": "task.resume-task", "command": "application:resume-task" },
|
||||
{ "label": "Delete Task", "id": "task.delete-task", "command": "application:delete-task" },
|
||||
{ "label": "Move Task Up", "id": "task.move-task-up", "command": "application:move-task-up" },
|
||||
{ "label": "Move Task Down", "id": "task.move-task-down", "command": "application:move-task-down" },
|
||||
{ "type": "separator" },
|
||||
{ "label": "Pause All Task", "id": "task.pause-all-task", "command": "application:pause-all-task" },
|
||||
{ "label": "Resume All Task", "id": "task.resume-all-task", "command": "application:resume-all-task" },
|
||||
{ "type": "separator" },
|
||||
{ "label": "Clear Recent Tasks", "id": "task.clear-recent-tasks", "command": "application:clear-recent-tasks" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Edit",
|
||||
"id": "edit",
|
||||
"submenu": [
|
||||
{ "label": "Undo", "id": "edit.undo", "role": "undo" },
|
||||
{ "label": "Redo", "id": "edit.redo", "role": "redo" },
|
||||
{ "type": "separator" },
|
||||
{ "label": "Cut", "id": "edit.cut", "role": "cut" },
|
||||
{ "label": "Copy", "id": "edit.copy", "role": "copy" },
|
||||
{ "label": "Paste", "id": "edit.paste", "role": "paste" },
|
||||
{ "label": "Delete", "id": "edit.delete", "role": "delete" },
|
||||
{ "label": "Select All", "id": "edit.select-all", "role": "selectall" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Window",
|
||||
"role": "window",
|
||||
"id": "window",
|
||||
"submenu": [
|
||||
{ "label": "Reload", "id": "view.reload", "role": "reload" },
|
||||
{ "label": "Close", "role": "close" },
|
||||
{ "label": "Minimize", "role": "minimize" },
|
||||
{ "label": "Zoom", "role": "zoom" },
|
||||
{ "label": "Enter Full Screen", "role": "togglefullscreen" },
|
||||
{ "type": "separator" },
|
||||
{ "label": "Bring All to Front", "role": "front" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Help",
|
||||
"role": "help",
|
||||
"id": "help",
|
||||
"submenu": [
|
||||
{ "label": "Motrix Website", "id": "help.official-website", "command": "help:official-website" },
|
||||
{ "label": "Manual", "id": "help.manual", "command": "help:manual" },
|
||||
{ "label": "Release Notes...", "id": "help.release-notes", "command": "help:release-notes" },
|
||||
{ "type": "separator" },
|
||||
{ "label": "Report Problem", "id": "help.report-problem", "command": "help:report-problem" },
|
||||
{ "type": "separator" },
|
||||
{ "label": "Toggle Developer Tools", "id": "help.toggle-dev-tools", "role": "toggledevtools" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
{
|
||||
"menu": [
|
||||
{
|
||||
"label": "Motrix",
|
||||
"id": "app",
|
||||
"submenu": [
|
||||
{ "label": "About Motrix", "id": "app.about", "command": "application:about", "command-before": "application:show,index"},
|
||||
{ "type": "separator" },
|
||||
{ "label": "Preferences...", "id": "app.preferences", "command": "application:preferences" },
|
||||
{ "label": "Check for Updates...", "id": "app.check-for-updates", "command": "application:check-for-updates" },
|
||||
{ "label": "Services", "role": "services", "submenu": [] },
|
||||
{ "label": "Hide Motrix", "role": "hide" },
|
||||
{ "label": "Hide Others", "role": "hideothers" },
|
||||
{ "label": "Show All", "role": "unhide" },
|
||||
{ "type": "separator" },
|
||||
{ "label": "Quit Motrix", "id": "app.quit", "role": "quit" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Task",
|
||||
"id": "task",
|
||||
"submenu": [
|
||||
{ "label": "New Task", "id": "task.new-task", "command": "application:new-task", "command-arg": "uri", "command-after": "application:show,index"},
|
||||
{ "label": "New BT Task", "id": "task.new-bt-task", "command": "application:new-bt-task", "command-arg": "torrent", "command-after": "application:show,index"},
|
||||
{ "type": "separator" },
|
||||
{ "label": "Pause Task", "id": "task.pause-task", "command": "application:pause-task" },
|
||||
{ "label": "Resume Task", "id": "task.resume-task", "command": "application:resume-task" },
|
||||
{ "label": "Delete Task", "id": "task.delete-task", "command": "application:delete-task" },
|
||||
{ "label": "Move Task Up", "id": "task.move-task-up", "command": "application:move-task-up" },
|
||||
{ "label": "Move Task Down", "id": "task.move-task-down", "command": "application:move-task-down" },
|
||||
{ "type": "separator" },
|
||||
{ "label": "Pause All Task", "id": "task.pause-all-task", "command": "application:pause-all-task" },
|
||||
{ "label": "Resume All Task", "id": "task.resume-all-task", "command": "application:resume-all-task" },
|
||||
{ "type": "separator" },
|
||||
{ "label": "Clear Recent Tasks", "id": "task.clear-recent-tasks", "command": "application:clear-recent-tasks" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Edit",
|
||||
"id": "edit",
|
||||
"submenu": [
|
||||
{ "label": "Undo", "id": "edit.undo", "role": "undo" },
|
||||
{ "label": "Redo", "id": "edit.redo", "role": "redo" },
|
||||
{ "type": "separator" },
|
||||
{ "label": "Cut", "id": "edit.cut", "role": "cut" },
|
||||
{ "label": "Copy", "id": "edit.copy", "role": "copy" },
|
||||
{ "label": "Paste", "id": "edit.paste", "role": "paste" },
|
||||
{ "label": "Delete", "id": "edit.delete", "role": "delete" },
|
||||
{ "label": "Select All", "id": "edit.select-all", "role": "selectall" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Window",
|
||||
"role": "window",
|
||||
"id": "window",
|
||||
"submenu": [
|
||||
{ "label": "Reload", "id": "view.reload", "role": "reload" },
|
||||
{ "label": "Close", "role": "close" },
|
||||
{ "label": "Minimize", "role": "minimize" },
|
||||
{ "label": "Zoom", "role": "zoom" },
|
||||
{ "label": "Enter Full Screen", "role": "togglefullscreen" },
|
||||
{ "type": "separator" },
|
||||
{ "label": "Bring All to Front", "role": "front" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Help",
|
||||
"role": "help",
|
||||
"id": "help",
|
||||
"submenu": [
|
||||
{ "label": "Motrix Website", "id": "help.official-website", "command": "help:official-website" },
|
||||
{ "label": "Manual", "id": "help.manual", "command": "help:manual" },
|
||||
{ "label": "Release Notes...", "id": "help.release-notes", "command": "help:release-notes" },
|
||||
{ "type": "separator" },
|
||||
{ "label": "Report Problem", "id": "help.report-problem", "command": "help:report-problem" },
|
||||
{ "type": "separator" },
|
||||
{ "label": "Toggle Developer Tools", "id": "help.toggle-dev-tools", "role": "toggledevtools" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
{
|
||||
"menu": [
|
||||
{
|
||||
"id": "menu.file",
|
||||
"submenu": [
|
||||
{ "id": "app.about", "command": "application:about", "command-before": "application:show,index" },
|
||||
{ "type": "separator" },
|
||||
{ "id": "app.preferences", "command": "application:preferences" },
|
||||
{ "id": "app.check-for-updates", "command": "application:check-for-updates" },
|
||||
{ "id": "app.show", "command": "application:show", "command-arg": "index" },
|
||||
{ "type": "separator" },
|
||||
{ "id": "app.quit", "role": "quit" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "menu.task",
|
||||
"submenu": [
|
||||
{ "id": "task.new-task", "command": "application:new-task", "command-after": "application:show,index" },
|
||||
{ "id": "task.new-bt-task", "command": "application:new-bt-task", "command-arg": "torrent", "command-after": "application:show,index" },
|
||||
{ "id": "task.open-file", "command": "application:open-file", "command-before": "application:show,index" },
|
||||
{ "type": "separator" },
|
||||
{ "id": "task.pause-task", "command": "application:pause-task" },
|
||||
{ "id": "task.resume-task", "command": "application:resume-task" },
|
||||
{ "id": "task.delete-task", "command": "application:delete-task" },
|
||||
{ "id": "task.move-task-up", "command": "application:move-task-up" },
|
||||
{ "id": "task.move-task-down", "command": "application:move-task-down" },
|
||||
{ "type": "separator" },
|
||||
{ "id": "task.pause-all-task", "command": "application:pause-all-task" },
|
||||
{ "id": "task.resume-all-task", "command": "application:resume-all-task" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "menu.edit",
|
||||
"submenu": [
|
||||
{ "id": "edit.undo", "role": "undo" },
|
||||
{ "id": "edit.redo", "role": "redo" },
|
||||
{ "type": "separator" },
|
||||
{ "id": "edit.cut", "role": "cut" },
|
||||
{ "id": "edit.copy", "role": "copy" },
|
||||
{ "id": "edit.paste", "role": "paste" },
|
||||
{ "id": "edit.delete", "role": "delete" },
|
||||
{ "id": "edit.select-all", "role": "selectall" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"role": "window",
|
||||
"id": "menu.window",
|
||||
"submenu": [
|
||||
{ "id": "window.reload", "role": "reload" },
|
||||
{ "id": "window.close", "role": "close" },
|
||||
{ "id": "window.minimize", "role": "minimize" },
|
||||
{ "id": "window.zoom", "role": "zoom" },
|
||||
{ "id": "window.toggle-fullscreen", "role": "togglefullscreen" },
|
||||
{ "type": "separator" },
|
||||
{ "id": "window.front", "role": "front" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"role": "help",
|
||||
"id": "menu.help",
|
||||
"submenu": [
|
||||
{ "id": "help.official-website", "command": "help:official-website" },
|
||||
{ "id": "help.manual", "command": "help:manual" },
|
||||
{ "id": "help.release-notes", "command": "help:release-notes" },
|
||||
{ "type": "separator" },
|
||||
{ "id": "help.report-problem", "command": "help:report-problem" },
|
||||
{ "type": "separator" },
|
||||
{ "id": "help.toggle-dev-tools", "role": "toggledevtools" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
[
|
||||
{ "id": "task.new-task", "command": "application:new-task", "command-after": "application:show,index" },
|
||||
{ "id": "task.new-bt-task", "command": "application:new-bt-task", "command-arg": "torrent", "command-after": "application:show,index" },
|
||||
{ "id": "task.open-file", "command": "application:open-file", "command-before": "application:show,index" },
|
||||
{ "type": "separator" },
|
||||
{ "id": "app.show", "command": "application:show", "command-arg": "index" },
|
||||
{ "id": "help.manual", "command": "help:manual" },
|
||||
{ "id": "app.check-for-updates", "command": "application:check-for-updates" },
|
||||
{ "type": "separator" },
|
||||
{ "id": "app.preferences", "command": "application:preferences", "command-before": "application:show,index" },
|
||||
{ "id": "app.quit", "role": "quit" }
|
||||
]
|
||||
@@ -0,0 +1,74 @@
|
||||
{
|
||||
"menu": [
|
||||
{
|
||||
"id": "menu.file",
|
||||
"submenu": [
|
||||
{ "id": "app.about", "command": "application:about", "command-before": "application:show,index" },
|
||||
{ "type": "separator" },
|
||||
{ "id": "app.preferences", "command": "application:preferences" },
|
||||
{ "id": "app.check-for-updates", "command": "application:check-for-updates" },
|
||||
{ "id": "app.show", "command": "application:show", "command-arg": "index" },
|
||||
{ "type": "separator" },
|
||||
{ "id": "app.quit", "role": "quit" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "menu.task",
|
||||
"submenu": [
|
||||
{ "id": "task.new-task", "command": "application:new-task", "command-after": "application:show,index" },
|
||||
{ "id": "task.new-bt-task", "command": "application:new-bt-task", "command-arg": "torrent", "command-after": "application:show,index" },
|
||||
{ "id": "task.open-file", "command": "application:open-file", "command-before": "application:show,index" },
|
||||
{ "type": "separator" },
|
||||
{ "id": "task.pause-task", "command": "application:pause-task" },
|
||||
{ "id": "task.resume-task", "command": "application:resume-task" },
|
||||
{ "id": "task.delete-task", "command": "application:delete-task" },
|
||||
{ "id": "task.move-task-up", "command": "application:move-task-up" },
|
||||
{ "id": "task.move-task-down", "command": "application:move-task-down" },
|
||||
{ "type": "separator" },
|
||||
{ "id": "task.pause-all-task", "command": "application:pause-all-task" },
|
||||
{ "id": "task.resume-all-task", "command": "application:resume-all-task" },
|
||||
{ "type": "separator" },
|
||||
{ "id": "task.clear-recent-tasks", "command": "application:clear-recent-tasks" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "menu.edit",
|
||||
"submenu": [
|
||||
{ "id": "edit.undo", "role": "undo" },
|
||||
{ "id": "edit.redo", "role": "redo" },
|
||||
{ "type": "separator" },
|
||||
{ "id": "edit.cut", "role": "cut" },
|
||||
{ "id": "edit.copy", "role": "copy" },
|
||||
{ "id": "edit.paste", "role": "paste" },
|
||||
{ "id": "edit.delete", "role": "delete" },
|
||||
{ "id": "edit.select-all", "role": "selectall" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"role": "window",
|
||||
"id": "menu.window",
|
||||
"submenu": [
|
||||
{ "id": "window.reload", "role": "reload" },
|
||||
{ "id": "window.close", "role": "close" },
|
||||
{ "id": "window.minimize", "role": "minimize" },
|
||||
{ "id": "window.zoom", "role": "zoom" },
|
||||
{ "id": "window.toggle-fullscreen", "role": "togglefullscreen" },
|
||||
{ "type": "separator" },
|
||||
{ "id": "window.front", "role": "front" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"role": "help",
|
||||
"id": "menu.help",
|
||||
"submenu": [
|
||||
{ "id": "help.official-website", "command": "help:official-website" },
|
||||
{ "id": "help.manual", "command": "help:manual" },
|
||||
{ "id": "help.release-notes", "command": "help:release-notes" },
|
||||
{ "type": "separator" },
|
||||
{ "id": "help.report-problem", "command": "help:report-problem" },
|
||||
{ "type": "separator" },
|
||||
{ "id": "help.toggle-dev-tools", "role": "toggledevtools" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
{
|
||||
"menu": [
|
||||
{
|
||||
"label": "Motrix",
|
||||
"id": "app",
|
||||
"submenu": [
|
||||
{ "label": "关于 Motrix", "id": "app.about", "command": "application:about", "command-before": "application:show,index"},
|
||||
{ "type": "separator" },
|
||||
{ "label": "偏好设置...", "id": "app.preferences", "command": "application:preferences" },
|
||||
{ "label": "检查更新...", "id": "app.check-for-updates", "command": "application:check-for-updates" },
|
||||
{ "label": "服务", "role": "services", "submenu": [] },
|
||||
{ "label": "隐藏 Motrix", "role": "hide" },
|
||||
{ "label": "隐藏其他", "role": "hideothers" },
|
||||
{ "label": "显示全部", "role": "unhide" },
|
||||
{ "type": "separator" },
|
||||
{ "label": "退出 Motrix", "id": "app.quit", "role": "quit" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "任务",
|
||||
"id": "task",
|
||||
"submenu": [
|
||||
{ "label": "新建任务", "id": "task.new-task", "command": "application:new-task", "command-after": "application:show,index"},
|
||||
{ "label": "新建 BT 任务", "id": "task.new-bt-task", "command": "application:new-bt-task", "command-arg": "torrent", "command-after": "application:show,index"},
|
||||
{ "type": "separator" },
|
||||
{ "label": "暂停任务", "id": "task.pause-task", "command": "application:pause-task" },
|
||||
{ "label": "恢复任务", "id": "task.resume-task", "command": "application:resume-task" },
|
||||
{ "label": "删除任务", "id": "task.delete-task", "command": "application:delete-task" },
|
||||
{ "label": "上移任务", "id": "task.move-task-up", "command": "application:move-task-up" },
|
||||
{ "label": "下移任务", "id": "task.move-task-down", "command": "application:move-task-down" },
|
||||
{ "type": "separator" },
|
||||
{ "label": "暂停所有任务", "id": "task.pause-all-task", "command": "application:pause-all-task" },
|
||||
{ "label": "恢复所有任务", "id": "task.resume-all-task", "command": "application:resume-all-task" },
|
||||
{ "type": "separator" },
|
||||
{ "label": "清除最近的下载记录", "id": "task.clear-recent-tasks", "command": "application:clear-recent-tasks" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "编辑",
|
||||
"id": "edit",
|
||||
"submenu": [
|
||||
{ "label": "撤销", "id": "edit.undo", "role": "undo" },
|
||||
{ "label": "重做", "id": "edit.redo", "role": "redo" },
|
||||
{ "type": "separator" },
|
||||
{ "label": "剪切", "id": "edit.cut", "role": "cut" },
|
||||
{ "label": "复制", "id": "edit.copy", "role": "copy" },
|
||||
{ "label": "黏贴", "id": "edit.paste", "role": "paste" },
|
||||
{ "label": "删除", "id": "edit.delete", "role": "delete" },
|
||||
{ "label": "全选", "id": "edit.select-all", "role": "selectall" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "窗口",
|
||||
"role": "window",
|
||||
"id": "window",
|
||||
"submenu": [
|
||||
{ "label": "重新加载", "id": "view.reload", "role": "reload" },
|
||||
{ "label": "关闭", "role": "close" },
|
||||
{ "label": "最小化", "role": "minimize" },
|
||||
{ "label": "放大", "role": "zoom" },
|
||||
{ "label": "进入全屏幕", "role": "togglefullscreen" },
|
||||
{ "type": "separator" },
|
||||
{ "label": "前置全部窗口", "role": "front" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "帮助",
|
||||
"role": "help",
|
||||
"id": "help",
|
||||
"submenu": [
|
||||
{ "label": "Motrix 官网", "id": "help.official-website", "command": "help:official-website" },
|
||||
{ "label": "使用手册", "id": "help.manual", "command": "help:manual" },
|
||||
{ "label": "发行说明...", "id": "help.release-notes", "command": "help:release-notes" },
|
||||
{ "type": "separator" },
|
||||
{ "label": "报告问题", "id": "help.report-problem", "command": "help:report-problem" },
|
||||
{ "type": "separator" },
|
||||
{ "label": "开发者工具", "id": "help.toggle-dev-tools", "role": "toggledevtools" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
{
|
||||
"menu": [
|
||||
{
|
||||
"label": "Motrix",
|
||||
"id": "app",
|
||||
"submenu": [
|
||||
{ "label": "关于 Motrix", "id": "app.about", "command": "application:about" },
|
||||
{ "type": "separator" },
|
||||
{ "label": "偏好设置...", "id": "app.preferences", "command": "application:preferences" },
|
||||
{ "label": "检查更新...", "id": "app.check-for-updates", "command": "application:check-for-updates" },
|
||||
{ "label": "服务", "role": "services", "submenu": [] },
|
||||
{ "label": "隐藏 Motrix", "role": "hide" },
|
||||
{ "label": "隐藏其他", "role": "hideothers" },
|
||||
{ "label": "显示全部", "role": "unhide" },
|
||||
{ "type": "separator" },
|
||||
{ "label": "退出 Motrix", "id": "app.quit", "role": "quit" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "任务",
|
||||
"id": "task",
|
||||
"submenu": [
|
||||
{ "label": "新建任务", "id": "task.new-task", "command": "application:new-task" },
|
||||
{ "label": "新建 BT 任务", "id": "task.new-bt-task", "command": "application:new-bt-task", "command-arg": "torrent" },
|
||||
{ "type": "separator" },
|
||||
{ "label": "暂停任务", "id": "task.pause-task", "command": "application:pause-task" },
|
||||
{ "label": "恢复任务", "id": "task.resume-task", "command": "application:resume-task" },
|
||||
{ "label": "删除任务", "id": "task.delete-task", "command": "application:delete-task" },
|
||||
{ "label": "上移任务", "id": "task.move-task-up", "command": "application:move-task-up" },
|
||||
{ "label": "下移任务", "id": "task.move-task-down", "command": "application:move-task-down" },
|
||||
{ "type": "separator" },
|
||||
{ "label": "暂停所有任务", "id": "task.pause-all-task", "command": "application:pause-all-task" },
|
||||
{ "label": "恢复所有任务", "id": "task.resume-all-task", "command": "application:resume-all-task" },
|
||||
{ "type": "separator" },
|
||||
{ "label": "清除最近的下载记录", "id": "task.clear-recent-tasks", "command": "application:clear-recent-tasks" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "编辑",
|
||||
"id": "edit",
|
||||
"submenu": [
|
||||
{ "label": "撤销", "id": "edit.undo", "role": "undo" },
|
||||
{ "label": "重做", "id": "edit.redo", "role": "redo" },
|
||||
{ "type": "separator" },
|
||||
{ "label": "剪切", "id": "edit.cut", "role": "cut" },
|
||||
{ "label": "复制", "id": "edit.copy", "role": "copy" },
|
||||
{ "label": "黏贴", "id": "edit.paste", "role": "paste" },
|
||||
{ "label": "删除", "id": "edit.delete", "role": "delete" },
|
||||
{ "label": "全选", "id": "edit.select-all", "role": "selectall" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "窗口",
|
||||
"role": "window",
|
||||
"id": "window",
|
||||
"submenu": [
|
||||
{ "label": "重新加载", "id": "view.reload", "role": "reload" },
|
||||
{ "label": "关闭", "role": "close" },
|
||||
{ "label": "最小化", "role": "minimize" },
|
||||
{ "label": "放大", "role": "zoom" },
|
||||
{ "label": "进入全屏幕", "role": "togglefullscreen" },
|
||||
{ "type": "separator" },
|
||||
{ "label": "前置全部窗口", "role": "front" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "帮助",
|
||||
"role": "help",
|
||||
"id": "help",
|
||||
"submenu": [
|
||||
{ "label": "Motrix 官网", "id": "help.official-website", "command": "help:official-website" },
|
||||
{ "label": "使用手册", "id": "help.manual", "command": "help:manual" },
|
||||
{ "label": "发行说明...", "id": "help.release-notes", "command": "help:release-notes" },
|
||||
{ "type": "separator" },
|
||||
{ "label": "报告问题", "id": "help.report-problem", "command": "help:report-problem" },
|
||||
{ "type": "separator" },
|
||||
{ "label": "开发者工具", "id": "help.toggle-dev-tools", "role": "toggledevtools" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
{
|
||||
"menu": [
|
||||
{
|
||||
"label": "Motrix",
|
||||
"id": "app",
|
||||
"submenu": [
|
||||
{ "label": "关于 Motrix", "id": "app.about", "command": "application:about", "command-before": "application:show,index"},
|
||||
{ "type": "separator" },
|
||||
{ "label": "偏好设置...", "id": "app.preferences", "command": "application:preferences" },
|
||||
{ "label": "检查更新...", "id": "app.check-for-updates", "command": "application:check-for-updates" },
|
||||
{ "label": "服务", "role": "services", "submenu": [] },
|
||||
{ "label": "隐藏 Motrix", "role": "hide" },
|
||||
{ "label": "隐藏其他", "role": "hideothers" },
|
||||
{ "label": "显示全部", "role": "unhide" },
|
||||
{ "type": "separator" },
|
||||
{ "label": "退出 Motrix", "id": "app.quit", "role": "quit" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "任务",
|
||||
"id": "task",
|
||||
"submenu": [
|
||||
{ "label": "新建任务", "id": "task.new-task", "command": "application:new-task", "command-after": "application:show,index"},
|
||||
{ "label": "新建 BT 任务", "id": "task.new-bt-task", "command": "application:new-bt-task", "command-arg": "torrent", "command-after": "application:show,index"},
|
||||
{ "type": "separator" },
|
||||
{ "label": "暂停任务", "id": "task.pause-task", "command": "application:pause-task" },
|
||||
{ "label": "恢复任务", "id": "task.resume-task", "command": "application:resume-task" },
|
||||
{ "label": "删除任务", "id": "task.delete-task", "command": "application:delete-task" },
|
||||
{ "label": "上移任务", "id": "task.move-task-up", "command": "application:move-task-up" },
|
||||
{ "label": "下移任务", "id": "task.move-task-down", "command": "application:move-task-down" },
|
||||
{ "type": "separator" },
|
||||
{ "label": "暂停所有任务", "id": "task.pause-all-task", "command": "application:pause-all-task" },
|
||||
{ "label": "恢复所有任务", "id": "task.resume-all-task", "command": "application:resume-all-task" },
|
||||
{ "type": "separator" },
|
||||
{ "label": "清除最近的下载记录", "id": "task.clear-recent-tasks", "command": "application:clear-recent-tasks" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "编辑",
|
||||
"id": "edit",
|
||||
"submenu": [
|
||||
{ "label": "撤销", "id": "edit.undo", "role": "undo" },
|
||||
{ "label": "重做", "id": "edit.redo", "role": "redo" },
|
||||
{ "type": "separator" },
|
||||
{ "label": "剪切", "id": "edit.cut", "role": "cut" },
|
||||
{ "label": "复制", "id": "edit.copy", "role": "copy" },
|
||||
{ "label": "黏贴", "id": "edit.paste", "role": "paste" },
|
||||
{ "label": "删除", "id": "edit.delete", "role": "delete" },
|
||||
{ "label": "全选", "id": "edit.select-all", "role": "selectall" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "窗口",
|
||||
"role": "window",
|
||||
"id": "window",
|
||||
"submenu": [
|
||||
{ "label": "重新加载", "id": "view.reload", "role": "reload" },
|
||||
{ "label": "关闭", "role": "close" },
|
||||
{ "label": "最小化", "role": "minimize" },
|
||||
{ "label": "放大", "role": "zoom" },
|
||||
{ "label": "进入全屏幕", "role": "togglefullscreen" },
|
||||
{ "type": "separator" },
|
||||
{ "label": "前置全部窗口", "role": "front" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "帮助",
|
||||
"role": "help",
|
||||
"id": "help",
|
||||
"submenu": [
|
||||
{ "label": "Motrix 官网", "id": "help.official-website", "command": "help:official-website" },
|
||||
{ "label": "使用手册", "id": "help.manual", "command": "help:manual" },
|
||||
{ "label": "发行说明...", "id": "help.release-notes", "command": "help:release-notes" },
|
||||
{ "type": "separator" },
|
||||
{ "label": "报告问题", "id": "help.report-problem", "command": "help:report-problem" },
|
||||
{ "type": "separator" },
|
||||
{ "label": "开发者工具", "id": "help.toggle-dev-tools", "role": "toggledevtools" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import resources from '@shared/locales/app'
|
||||
import LocaleManager from '@shared/locales/LocaleManager'
|
||||
|
||||
const localeManager = new LocaleManager({
|
||||
resources
|
||||
})
|
||||
|
||||
export function getLocaleManager () {
|
||||
return localeManager
|
||||
}
|
||||
|
||||
export function setupLocaleManager (locale) {
|
||||
localeManager.changeLanguageByLocale(locale)
|
||||
|
||||
return localeManager
|
||||
}
|
||||
|
||||
export function getI18n () {
|
||||
return localeManager.getI18n()
|
||||
}
|
||||
|
||||
export function getI18nTranslator () {
|
||||
return localeManager.getI18n().t
|
||||
}
|
||||
@@ -6,29 +6,24 @@ import {
|
||||
updateStates
|
||||
} from '../utils/menu'
|
||||
import keymap from '@shared/keymap'
|
||||
import { getI18n } from '@/ui/Locale'
|
||||
|
||||
export default class MenuManager extends EventEmitter {
|
||||
constructor (options) {
|
||||
super()
|
||||
this.options = options
|
||||
this.i18n = getI18n()
|
||||
|
||||
this.keymap = keymap
|
||||
this.template = []
|
||||
|
||||
this.menu = null
|
||||
this.items = {}
|
||||
|
||||
this.load()
|
||||
|
||||
this.setup()
|
||||
}
|
||||
|
||||
load (locale = 'en-US') {
|
||||
let template = null
|
||||
try {
|
||||
template = require(`../menus/${locale}/${process.platform}.json`)
|
||||
if (!template) {
|
||||
template = require(`../menus/en-US/${process.platform}.json`)
|
||||
}
|
||||
} catch (err) {
|
||||
template = require(`../menus/en-US/${process.platform}.json`)
|
||||
}
|
||||
load () {
|
||||
let template = require(`../menus/${process.platform}.json`)
|
||||
this.template = template['menu']
|
||||
}
|
||||
|
||||
@@ -38,18 +33,24 @@ export default class MenuManager extends EventEmitter {
|
||||
keystrokesByCommand[this.keymap[item]] = item
|
||||
}
|
||||
|
||||
const tpl = translateTemplate(this.template, keystrokesByCommand)
|
||||
this.menu = Menu.buildFromTemplate(tpl)
|
||||
// Deepclone the menu template to refresh menu
|
||||
const template = JSON.parse(JSON.stringify(this.template))
|
||||
const tpl = translateTemplate(template, keystrokesByCommand, this.i18n)
|
||||
const menu = Menu.buildFromTemplate(tpl)
|
||||
return menu
|
||||
}
|
||||
|
||||
setup (locale) {
|
||||
this.load(locale)
|
||||
this.build()
|
||||
Menu.setApplicationMenu(this.menu)
|
||||
this.items = flattenMenuItems(this.menu)
|
||||
setup () {
|
||||
const menu = this.build()
|
||||
Menu.setApplicationMenu(menu)
|
||||
this.items = flattenMenuItems(menu)
|
||||
}
|
||||
|
||||
updateStates (visibleStates, enabledStates, checkedStates) {
|
||||
rebuild () {
|
||||
this.setup()
|
||||
}
|
||||
|
||||
updateMenuStates (visibleStates, enabledStates, checkedStates) {
|
||||
updateStates(this.items, visibleStates, enabledStates, checkedStates)
|
||||
}
|
||||
|
||||
@@ -57,13 +58,13 @@ export default class MenuManager extends EventEmitter {
|
||||
const visibleStates = {
|
||||
[id]: flag
|
||||
}
|
||||
this.updateStates(visibleStates, null, null)
|
||||
this.updateMenuStates(visibleStates, null, null)
|
||||
}
|
||||
|
||||
updateMenuItemEnabledState (id, flag) {
|
||||
const enabledStates = {
|
||||
[id]: flag
|
||||
}
|
||||
this.updateStates(null, enabledStates, null)
|
||||
this.updateMenuStates(null, enabledStates, null)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
import { EventEmitter } from 'events'
|
||||
import { systemPreferences } from 'electron'
|
||||
import is from 'electron-is'
|
||||
import { LIGHT_THEME, DARK_THEME } from '@shared/constants'
|
||||
|
||||
export default class ThemeManager extends EventEmitter {
|
||||
constructor (options = {}) {
|
||||
super()
|
||||
|
||||
this.init()
|
||||
}
|
||||
|
||||
init () {
|
||||
this.handleEvents()
|
||||
}
|
||||
|
||||
getSystemTheme () {
|
||||
let result = LIGHT_THEME
|
||||
if (!is.macOS()) {
|
||||
return result
|
||||
}
|
||||
result = systemPreferences.isDarkMode() ? DARK_THEME : LIGHT_THEME
|
||||
return result
|
||||
}
|
||||
|
||||
handleEvents () {
|
||||
if (!is.macOS()) {
|
||||
return
|
||||
}
|
||||
systemPreferences.subscribeNotification(
|
||||
'AppleInterfaceThemeChangedNotification',
|
||||
() => {
|
||||
const theme = this.getSystemTheme()
|
||||
this.updateAppAppearance(theme)
|
||||
this.emit('system-theme-changed', theme)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
updateAppAppearance (theme) {
|
||||
if (!is.macOS() || theme !== LIGHT_THEME || theme !== DARK_THEME) {
|
||||
return
|
||||
}
|
||||
systemPreferences.setAppLevelAppearance(theme)
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,142 @@
|
||||
export default class TrayManager {
|
||||
import { EventEmitter } from 'events'
|
||||
import { join } from 'path'
|
||||
import { Tray, Menu, systemPreferences } from 'electron'
|
||||
import is from 'electron-is'
|
||||
import {
|
||||
translateTemplate,
|
||||
flattenMenuItems,
|
||||
updateStates
|
||||
} from '../utils/menu'
|
||||
import { getI18n } from '@/ui/Locale'
|
||||
import { LIGHT_THEME, DARK_THEME } from '@shared/constants'
|
||||
|
||||
let tray = null
|
||||
|
||||
export default class TrayManager extends EventEmitter {
|
||||
constructor (options = {}) {
|
||||
super()
|
||||
|
||||
this.i18n = getI18n()
|
||||
|
||||
this.menu = null
|
||||
|
||||
this.load()
|
||||
this.init()
|
||||
this.setup()
|
||||
this.handleEvents()
|
||||
}
|
||||
|
||||
load () {
|
||||
this.template = require(`../menus/tray.json`)
|
||||
const theme = systemPreferences.isDarkMode() ? DARK_THEME : LIGHT_THEME
|
||||
|
||||
if (is.macOS()) {
|
||||
this.normalIcon = join(__static, `./mo-tray-${theme}-normal.png`)
|
||||
this.activeIcon = join(__static, `./mo-tray-${theme}-active.png`)
|
||||
} else {
|
||||
this.normalIcon = join(__static, './mo-tray-colorful-normal.png')
|
||||
this.activeIcon = join(__static, './mo-tray-colorful-active.png')
|
||||
}
|
||||
}
|
||||
|
||||
build () {
|
||||
const keystrokesByCommand = {}
|
||||
for (let item in this.keymap) {
|
||||
keystrokesByCommand[this.keymap[item]] = item
|
||||
}
|
||||
|
||||
// Deepclone the menu template to refresh menu
|
||||
const template = JSON.parse(JSON.stringify(this.template))
|
||||
const tpl = translateTemplate(template, keystrokesByCommand, this.i18n)
|
||||
this.menu = Menu.buildFromTemplate(tpl)
|
||||
this.items = flattenMenuItems(this.menu)
|
||||
}
|
||||
|
||||
setup () {
|
||||
this.build()
|
||||
|
||||
/**
|
||||
* Linux requires setContextMenu to be called
|
||||
* in order for the context menu to populate correctly
|
||||
*/
|
||||
if (process.platform === 'linux') {
|
||||
tray.setContextMenu(this.menu)
|
||||
}
|
||||
}
|
||||
|
||||
init () {
|
||||
tray = new Tray(this.normalIcon)
|
||||
tray.setToolTip('Motrix')
|
||||
}
|
||||
|
||||
handleEvents () {
|
||||
tray.on('click', this.handleTrayClick)
|
||||
tray.on('double-click', this.handleTrayDbClick)
|
||||
tray.on('right-click', this.handleTrayRightClick)
|
||||
|
||||
tray.on('drop-files', this.handleTrayDropFile)
|
||||
}
|
||||
|
||||
handleTrayClick = (event) => {
|
||||
event.preventDefault()
|
||||
global.application.toggle()
|
||||
}
|
||||
|
||||
handleTrayDbClick = (event) => {
|
||||
event.preventDefault()
|
||||
global.application.show()
|
||||
}
|
||||
|
||||
handleTrayRightClick = (event) => {
|
||||
event.preventDefault()
|
||||
tray.popUpContextMenu(this.menu)
|
||||
}
|
||||
|
||||
handleTrayDropFile = (event, files) => {
|
||||
global.application.show()
|
||||
global.application.handleFile(files[0])
|
||||
}
|
||||
|
||||
updateStatus (status) {
|
||||
this.status = status
|
||||
this.updateIcon()
|
||||
}
|
||||
|
||||
updateIcon () {
|
||||
const icon = this.status ? this.activeIcon : this.normalIcon
|
||||
tray.setImage(icon)
|
||||
}
|
||||
|
||||
changeIconTheme (theme = LIGHT_THEME) {
|
||||
if (!is.macOS()) {
|
||||
return
|
||||
}
|
||||
|
||||
this.normalIcon = join(__static, `./mo-tray-${theme}-normal.png`)
|
||||
this.activeIcon = join(__static, `./mo-tray-${theme}-active.png`)
|
||||
|
||||
this.updateIcon()
|
||||
}
|
||||
|
||||
updateMenuStates (visibleStates, enabledStates, checkedStates) {
|
||||
updateStates(this.items, visibleStates, enabledStates, checkedStates)
|
||||
}
|
||||
|
||||
updateMenuItemVisibleState (id, flag) {
|
||||
const visibleStates = {
|
||||
[id]: flag
|
||||
}
|
||||
this.updateMenuStates(visibleStates, null, null)
|
||||
}
|
||||
|
||||
updateMenuItemEnabledState (id, flag) {
|
||||
const enabledStates = {
|
||||
[id]: flag
|
||||
}
|
||||
this.updateMenuStates(null, enabledStates, null)
|
||||
}
|
||||
|
||||
destroy () {
|
||||
tray.destroy()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,63 +1,113 @@
|
||||
import { join } from 'path'
|
||||
import { EventEmitter } from 'events'
|
||||
import { app, shell, BrowserWindow } from 'electron'
|
||||
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
|
||||
height: 768,
|
||||
webPreferences: {
|
||||
nodeIntegration: true
|
||||
}
|
||||
}
|
||||
|
||||
export default class WindowManager extends EventEmitter {
|
||||
constructor (options = {}) {
|
||||
super()
|
||||
this.options = options
|
||||
this.userConfig = options.userConfig || {}
|
||||
|
||||
this.windows = {}
|
||||
|
||||
this.willQuit = false
|
||||
|
||||
app.on('before-quit', () => {
|
||||
this.setWillQuit(true)
|
||||
})
|
||||
this.handleBeforeQuit()
|
||||
|
||||
this.handleAllWindowClosed()
|
||||
}
|
||||
|
||||
setWillQuit (flag) {
|
||||
this.willQuit = flag
|
||||
}
|
||||
|
||||
openWindow (page) {
|
||||
const options = pageConfig[page] || {}
|
||||
getPageOptions (page) {
|
||||
const result = pageConfig[page] || {}
|
||||
const hideAppMenu = this.userConfig['hide-app-menu']
|
||||
if (hideAppMenu) {
|
||||
result.attrs.frame = false
|
||||
}
|
||||
|
||||
// Optimized for small screen users
|
||||
const { width, height } = screen.getPrimaryDisplay().workAreaSize
|
||||
const widthScale = width >= 1280 ? 1 : 0.875
|
||||
const heightScale = height >= 800 ? 1 : 0.875
|
||||
result.attrs.width *= widthScale
|
||||
result.attrs.height *= heightScale
|
||||
|
||||
// fix AppImage Dock Icon Missing
|
||||
// https://github.com/AppImage/AppImageKit/wiki/Bundling-Electron-apps
|
||||
if (is.linux()) {
|
||||
result.attrs.icon = join(__static, './512x512.png')
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
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 && process.platform === 'darwin') {
|
||||
this.bindCloseToHide(page, window)
|
||||
}
|
||||
this.handleWindowState(page, window)
|
||||
|
||||
this.handleWindowClose(pageOptions, page, window)
|
||||
|
||||
this.bindAfterClosed(page, window)
|
||||
|
||||
@@ -70,7 +120,11 @@ export default class WindowManager extends EventEmitter {
|
||||
}
|
||||
|
||||
getWindows () {
|
||||
return this.windows
|
||||
return this.windows || {}
|
||||
}
|
||||
|
||||
getWindowList () {
|
||||
return Object.values(this.getWindows())
|
||||
}
|
||||
|
||||
addWindow (page, window) {
|
||||
@@ -80,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()
|
||||
}
|
||||
|
||||
@@ -93,24 +150,84 @@ 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 })
|
||||
})
|
||||
}
|
||||
|
||||
showWindow (page) {
|
||||
const window = this.getWindow(page)
|
||||
if (!window) {
|
||||
return
|
||||
}
|
||||
window.show()
|
||||
}
|
||||
|
||||
hideWindow (page) {
|
||||
const window = this.getWindow(page)
|
||||
if (!window) {
|
||||
return
|
||||
}
|
||||
window.hide()
|
||||
}
|
||||
|
||||
hideAllWindow () {
|
||||
this.getWindowList().forEach((window) => {
|
||||
window.hide()
|
||||
})
|
||||
}
|
||||
|
||||
toggleWindow (page) {
|
||||
const window = this.getWindow(page)
|
||||
if (!window) {
|
||||
return
|
||||
}
|
||||
if (window.isVisible()) {
|
||||
window.hide()
|
||||
} else {
|
||||
window.show()
|
||||
}
|
||||
}
|
||||
|
||||
getFocusedWindow () {
|
||||
return BrowserWindow.getFocusedWindow()
|
||||
}
|
||||
|
||||
handleBeforeQuit () {
|
||||
app.on('before-quit', () => {
|
||||
this.setWillQuit(true)
|
||||
})
|
||||
}
|
||||
|
||||
handleAllWindowClosed () {
|
||||
app.on('window-all-closed', (event) => {
|
||||
event.preventDefault()
|
||||
})
|
||||
}
|
||||
|
||||
sendCommandTo (window, command, ...args) {
|
||||
if (!window) {
|
||||
return
|
||||
}
|
||||
console.log('sendCommandTo====>', window, command, ...args)
|
||||
logger.info('[Motrix] sendCommandTo===>', command, ...args)
|
||||
window.webContents.send('command', command, ...args)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { app } from 'electron'
|
||||
import is from 'electron-is'
|
||||
import { resolve } from 'path'
|
||||
import { existsSync, lstatSync } from 'fs'
|
||||
import logger from '../core/Logger'
|
||||
import engineBinMap from '../configs/engine'
|
||||
|
||||
@@ -8,6 +9,11 @@ export function getLogPath () {
|
||||
return logger.transports.file.file
|
||||
}
|
||||
|
||||
export function getDhtPath (protocol) {
|
||||
const name = protocol === 6 ? 'dht6.dat' : 'dht.dat'
|
||||
return resolve(app.getPath('userData'), `./${name}`)
|
||||
}
|
||||
|
||||
export function getSessionPath () {
|
||||
return resolve(app.getPath('userData'), './download.session')
|
||||
}
|
||||
@@ -44,17 +50,77 @@ export function isRunningInDmg () {
|
||||
return result
|
||||
}
|
||||
|
||||
export function moveAppToApplicationsFolder () {
|
||||
export function moveAppToApplicationsFolder (errorMsg = '') {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
const result = app.moveToApplicationsFolder()
|
||||
if (result) {
|
||||
resolve(result)
|
||||
} else {
|
||||
reject(new Error('应用程序移动失败'))
|
||||
reject(new Error(errorMsg))
|
||||
}
|
||||
} catch (err) {
|
||||
reject(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
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 (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()
|
||||
}
|
||||
|
||||
export function parseArgvAsFile (argv) {
|
||||
let arg = argv[1]
|
||||
if (!arg || isDirectory(arg)) {
|
||||
return
|
||||
}
|
||||
|
||||
if (is.linux()) {
|
||||
arg = arg.replace('file://', '')
|
||||
}
|
||||
return arg
|
||||
}
|
||||
|
||||
@@ -71,19 +71,29 @@ function findById (template, id) {
|
||||
return null
|
||||
}
|
||||
|
||||
export function translateTemplate (template, keystrokesByCommand) {
|
||||
export function translateTemplate (template, keystrokesByCommand, i18n) {
|
||||
for (let i in template) {
|
||||
let item = template[i]
|
||||
if (item.command) {
|
||||
item.accelerator = acceleratorForCommand(item.command, keystrokesByCommand)
|
||||
}
|
||||
|
||||
// If label is specified, label is used as the key of i18n.t(key),
|
||||
// which mainly solves the inaccurate translation of item.id.
|
||||
if (i18n) {
|
||||
if (item.label) {
|
||||
item.label = i18n.t(item.label)
|
||||
} else if (item.id) {
|
||||
item.label = i18n.t(item.id)
|
||||
}
|
||||
}
|
||||
|
||||
item.click = () => {
|
||||
console.log('click sendCommand', item)
|
||||
handleCommand(item)
|
||||
}
|
||||
|
||||
if (item.submenu) {
|
||||
translateTemplate(item.submenu, keystrokesByCommand)
|
||||
translateTemplate(item.submenu, keystrokesByCommand, i18n)
|
||||
}
|
||||
}
|
||||
return template
|
||||
@@ -96,7 +106,7 @@ export function handleCommand (item) {
|
||||
? [item.command, item['command-arg']]
|
||||
: [item.command]
|
||||
|
||||
global.application.sendCommand(...args)
|
||||
global.application.sendCommandToAll(...args)
|
||||
|
||||
handleCommandAfter(item)
|
||||
}
|
||||
@@ -108,7 +118,7 @@ function handleCommandBefore (item) {
|
||||
}
|
||||
const [ command, ...args ] = item['command-before'].split(',')
|
||||
console.log('handleCommandBefore==2=>', command, ...args)
|
||||
global.application.sendCommand(command, ...args)
|
||||
global.application.sendCommandToAll(command, ...args)
|
||||
}
|
||||
|
||||
function handleCommandAfter (item) {
|
||||
@@ -118,7 +128,7 @@ function handleCommandAfter (item) {
|
||||
}
|
||||
const [ command, ...args ] = item['command-after'].split(',')
|
||||
console.log('handleCommandAfter==2=>', command, ...args)
|
||||
global.application.sendCommand(command, ...args)
|
||||
global.application.sendCommandToAll(command, ...args)
|
||||
}
|
||||
|
||||
function acceleratorForCommand (command, keystrokesByCommand) {
|
||||
|
||||
@@ -5,10 +5,15 @@ import Aria2 from 'aria2'
|
||||
import {
|
||||
separateConfig,
|
||||
compactUndefined,
|
||||
formatOptionsForEngine,
|
||||
mergeTaskResult,
|
||||
changeKeysToCamelCase,
|
||||
changeKeysToKebabCase
|
||||
} from '@shared/utils'
|
||||
import {
|
||||
BEST_TRACKERS_URL,
|
||||
BEST_TRACKERS_IP_URL
|
||||
} from '@shared/constants'
|
||||
|
||||
const application = remote.getGlobal('application')
|
||||
|
||||
@@ -53,7 +58,9 @@ export default class Api {
|
||||
rpcListenPort: port,
|
||||
rpcSecret: secret
|
||||
} = this.config
|
||||
const host = '127.0.0.1'
|
||||
this.client = new Aria2({
|
||||
host,
|
||||
port,
|
||||
secret
|
||||
})
|
||||
@@ -80,9 +87,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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,6 +102,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)) {
|
||||
@@ -111,6 +119,43 @@ export default class Api {
|
||||
return this.client.call('getVersion')
|
||||
}
|
||||
|
||||
changeGlobalOption (options) {
|
||||
const args = formatOptionsForEngine(options)
|
||||
|
||||
return this.client.call('changeGlobalOption', args)
|
||||
}
|
||||
|
||||
getGlobalOption () {
|
||||
return new Promise((resolve) => {
|
||||
this.client.call('getGlobalOption')
|
||||
.then((data) => {
|
||||
resolve(changeKeysToCamelCase(data))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
getOption (params = {}) {
|
||||
const { gid } = params
|
||||
const args = compactUndefined([gid])
|
||||
|
||||
return new Promise((resolve) => {
|
||||
this.client.call('getOption', ...args)
|
||||
.then((data) => {
|
||||
resolve(changeKeysToCamelCase(data))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
changeOption (params = {}) {
|
||||
let { gid, options = {} } = params
|
||||
options = formatOptionsForEngine(options)
|
||||
|
||||
const kebabOptions = changeKeysToKebabCase(options)
|
||||
const args = compactUndefined([gid, kebabOptions])
|
||||
|
||||
return this.client.call('changeOption', ...args)
|
||||
}
|
||||
|
||||
getGlobalStat () {
|
||||
return this.client.call('getGlobalStat')
|
||||
}
|
||||
@@ -120,8 +165,12 @@ export default class Api {
|
||||
uris,
|
||||
options
|
||||
} = params
|
||||
const args = compactUndefined([uris, options])
|
||||
return this.client.call('addUri', ...args)
|
||||
const kebabOptions = changeKeysToKebabCase(options)
|
||||
const tasks = uris.map((uri) => {
|
||||
const args = compactUndefined([[uri], kebabOptions])
|
||||
return [ 'aria2.addUri', ...args ]
|
||||
})
|
||||
return this.client.multicall(tasks)
|
||||
}
|
||||
|
||||
addTorrent (params) {
|
||||
@@ -129,7 +178,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)
|
||||
}
|
||||
|
||||
@@ -138,12 +188,13 @@ 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)
|
||||
}
|
||||
|
||||
fetchDownloadingTaskList (params = {}) {
|
||||
const { offset = 0, num = 200, keys } = params
|
||||
const { offset = 0, num = 20, keys } = params
|
||||
const activeArgs = compactUndefined([keys])
|
||||
const waitingArgs = compactUndefined([offset, num, keys])
|
||||
return new Promise((resolve, reject) => {
|
||||
@@ -162,13 +213,13 @@ export default class Api {
|
||||
}
|
||||
|
||||
fetchWaitingTaskList (params = {}) {
|
||||
const { offset = 0, num = 200, keys } = params
|
||||
const { offset = 0, num = 20, keys } = params
|
||||
const args = compactUndefined([offset, num, keys])
|
||||
return this.client.call('tellWaiting', ...args)
|
||||
}
|
||||
|
||||
fetchStoppedTaskList (params = {}) {
|
||||
const { offset = 0, num = 200, keys } = params
|
||||
const { offset = 0, num = 20, keys } = params
|
||||
const args = compactUndefined([offset, num, keys])
|
||||
return this.client.call('tellStopped', ...args)
|
||||
}
|
||||
@@ -256,4 +307,17 @@ export default class Api {
|
||||
stopPowerSaveBlocker () {
|
||||
application.energyManager.stopPowerSaveBlocker()
|
||||
}
|
||||
|
||||
fetchBtTrackerFromGitHub () {
|
||||
const now = Date.now()
|
||||
const promises = [
|
||||
fetch(`${BEST_TRACKERS_IP_URL}?t=${now}`).then((res) => res.text()),
|
||||
fetch(`${BEST_TRACKERS_URL}?t=${now}`).then((res) => res.text())
|
||||
]
|
||||
|
||||
return Promise.all(promises).then((values) => {
|
||||
let result = values.join('\r\n').replace(/^\s*[\r\n]/gm, '')
|
||||
return result
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 |
@@ -0,0 +1,11 @@
|
||||
<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" fill="none" stroke="currentColor" stroke-miterlimit="10" points="17,23 17,7 1,7 "/>
|
||||
<line data-cap="butt" fill="none" stroke="currentColor" stroke-miterlimit="10" x1="17" y1="7" x2="23" y2="1"/>
|
||||
<polygon fill="none" stroke="currentColor" stroke-miterlimit="10" points="17,23 23,17 23,1 7,1 1,7 1,23 "/>
|
||||
<circle data-color="color-2" data-stroke="none" cx="12" cy="12" r="1" stroke-linejoin="miter" stroke-linecap="square" stroke="none"/>
|
||||
<circle data-color="color-2" data-stroke="none" cx="6" cy="12" r="1" stroke-linejoin="miter" stroke-linecap="square" stroke="none"/>
|
||||
<circle data-color="color-2" data-stroke="none" cx="12" cy="18" r="1" stroke-linejoin="miter" stroke-linecap="square" stroke="none"/>
|
||||
<circle data-color="color-2" data-stroke="none" cx="6" cy="18" r="1" stroke-linejoin="miter" stroke-linecap="square" stroke="none"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -0,0 +1,6 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 12" width="12" height="12">
|
||||
<g class="nc-icon-wrapper" stroke-width="1" fill="#111111" stroke="#111111">
|
||||
<line x1="1.5" y1="1.5" x2="10.5" y2="10.5" fill="none" stroke="#111111" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<line x1="10.5" y1="1.5" x2="1.5" y2="10.5" fill="none" stroke="#111111" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 429 B |
@@ -0,0 +1,6 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 12" width="12" height="12">
|
||||
<g class="nc-icon-wrapper" stroke-width="1" fill="#111111" stroke="#111111">
|
||||
<polyline points="5.5 1.5 10.5 1.5 10.5 6.5" fill="none" stroke="#111111" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<polyline points="1.5 5.5 1.5 10.5 6.5 10.5" fill="none" stroke-linecap="round" stroke-linejoin="round" data-color="color-2"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 435 B |
@@ -0,0 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 12" width="12" height="12">
|
||||
<g class="nc-icon-wrapper" stroke-width="1" fill="#111111" stroke="#111111">
|
||||
<line x1="1" y1="6" x2="11" y2="6" fill="none" stroke="#111111" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 294 B |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
@@ -31,7 +31,6 @@
|
||||
},
|
||||
data () {
|
||||
const version = this.$electron.remote.app.getVersion()
|
||||
console.log('version===>', version)
|
||||
return {
|
||||
version
|
||||
}
|
||||
|
||||
@@ -3,11 +3,10 @@
|
||||
<div class="app-version">
|
||||
<mo-logo :width="93" :height="21" style="vertical-align: bottom;" />
|
||||
<span>Version {{version}}</span>
|
||||
<!-- <p>一款 macOS 全能下载工具</p> -->
|
||||
</div>
|
||||
<div class="app-icon"></div>
|
||||
<div class="engine-info" v-if="!!engine">
|
||||
<h4>引擎版本 {{engine.version}}</h4>
|
||||
<h4>{{ $t('about.engine-version') }} {{engine.version}}</h4>
|
||||
<ul v-if="!isMas()">
|
||||
<li
|
||||
v-for="(feature, index) in engine.enabledFeatures"
|
||||
@@ -50,13 +49,13 @@
|
||||
<style lang="scss">
|
||||
.app-info {
|
||||
position: relative;
|
||||
padding: 8px 0;
|
||||
margin: 8px 0;
|
||||
.app-version span {
|
||||
display: inline-block;
|
||||
vertical-align: bottom;
|
||||
font-size: $--font-size-large;
|
||||
margin-left: 20px;
|
||||
color: $--color-text-regular;
|
||||
color: $--app-version-color;
|
||||
line-height: 18px;
|
||||
}
|
||||
.app-icon {
|
||||
@@ -73,11 +72,11 @@
|
||||
h4 {
|
||||
font-size: $--font-size-base;
|
||||
font-weight: $--font-weight-secondary;
|
||||
color: $--color-text-regular;
|
||||
color: $--app-engine-title-color;
|
||||
}
|
||||
ul {
|
||||
font-size: 12px;
|
||||
color: $--color-text-secondary;
|
||||
color: $--app-engine-info-color;
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
line-height: 20px;
|
||||
|
||||
@@ -1,16 +1,22 @@
|
||||
<template>
|
||||
<el-row class="copyright">
|
||||
<el-col :span="8" class="copyright-left">
|
||||
<el-col :span="6" class="copyright-left">
|
||||
<a target="_blank" href="https://motrix.app/" rel="noopener noreferrer">
|
||||
©2018 Motrix
|
||||
</a>
|
||||
</el-col>
|
||||
<el-col :span="16" class="copyright-right">
|
||||
<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>
|
||||
<a target="_blank" href="https://motrix.app/support" rel="noopener noreferrer">
|
||||
帮助支持
|
||||
{{ $t('about.support') }}
|
||||
</a>
|
||||
<a target="_blank" href="https://motrix.app/release" rel="noopener noreferrer">
|
||||
{{ $t('about.release') }}
|
||||
</a>
|
||||
</el-col>
|
||||
</el-row>
|
||||
@@ -22,21 +28,17 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<style lang="scss">
|
||||
.copyright {
|
||||
width: 100%;
|
||||
font-size: $--font-size-small;
|
||||
a {
|
||||
color: $--color-text-regular;
|
||||
color: $--app-copyright-color;
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
.copyright-left {
|
||||
text-align: left;
|
||||
a {
|
||||
color: $--color-text-regular;
|
||||
}
|
||||
}
|
||||
|
||||
.copyright-right {
|
||||
@@ -45,5 +47,4 @@
|
||||
margin-left: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
<template>
|
||||
<el-aside width="78px" class="aside" :class="{ draggable: !isWindows() }">
|
||||
<el-aside width="78px" :class="['aside', { 'draggable': asideDraggable }]">
|
||||
<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>
|
||||
@@ -24,7 +24,7 @@
|
||||
|
||||
<script>
|
||||
import is from 'electron-is'
|
||||
import { mapState, mapActions } from 'vuex'
|
||||
import { mapState } from 'vuex'
|
||||
import LogoMini from '@/components/Logo/LogoMini'
|
||||
import '@/components/Icons/menu-task'
|
||||
import '@/components/Icons/menu-add'
|
||||
@@ -39,33 +39,27 @@
|
||||
computed: {
|
||||
...mapState('app', {
|
||||
currentPage: state => state.currentPage
|
||||
})
|
||||
}),
|
||||
asideDraggable: function () {
|
||||
return is.macOS()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
isRenderer: is.renderer,
|
||||
isWindows: is.windows,
|
||||
open (link) {
|
||||
this.$electron.shell.openExternal(link)
|
||||
},
|
||||
showAddTask (taskType = 'uri') {
|
||||
this.$store.dispatch('app/showAddTaskDialog', taskType)
|
||||
},
|
||||
showAboutPanel () {
|
||||
// if (this.isRenderer()) {
|
||||
// if (is.renderer()) {
|
||||
// this.$electron.ipcRenderer.send('command', 'application:about')
|
||||
// } else {
|
||||
this.$store.dispatch('app/showAboutPanel')
|
||||
// }
|
||||
},
|
||||
nav (page) {
|
||||
console.log('nav page===>', page)
|
||||
this.$router.push({
|
||||
path: page
|
||||
})
|
||||
},
|
||||
...mapActions('app', [
|
||||
'changeCurrentPage'
|
||||
])
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,17 +1,47 @@
|
||||
import router from '@/router'
|
||||
import store from '@/store'
|
||||
import CommandManager from './CommandManager'
|
||||
import { Message } from 'element-ui'
|
||||
import { getLocaleManager } from '@/components/Locale'
|
||||
import { base64StringToBlob } from 'blob-util'
|
||||
import { buildFileList } from '@shared/utils'
|
||||
|
||||
const commands = new CommandManager()
|
||||
const i18n = getLocaleManager().getI18n()
|
||||
|
||||
function updateSystemTheme (theme) {
|
||||
store.dispatch('app/updateSystemTheme', theme)
|
||||
}
|
||||
|
||||
function updateTheme (theme) {
|
||||
store.dispatch('preference/changeThemeConfig', theme)
|
||||
}
|
||||
|
||||
function showAboutPanel () {
|
||||
store.dispatch('app/showAboutPanel')
|
||||
}
|
||||
|
||||
function showAddTask (taskType = 'uri') {
|
||||
function showAddTask (taskType = 'uri', task = '') {
|
||||
if (taskType === 'uri' && task) {
|
||||
store.dispatch('app/updateAddTaskUrl', task)
|
||||
}
|
||||
store.dispatch('app/showAddTaskDialog', taskType)
|
||||
}
|
||||
|
||||
function showAddBtTask () {
|
||||
store.dispatch('app/showAddTaskDialog', 'torrent')
|
||||
}
|
||||
|
||||
function showAddBtTaskWithFile (fileName, base64Data = '') {
|
||||
const blob = base64StringToBlob(base64Data, 'application/x-bittorrent')
|
||||
const file = new File([blob], fileName, { type: 'application/x-bittorrent' })
|
||||
const fileList = buildFileList(file)
|
||||
store.dispatch('app/showAddTaskDialog', 'torrent')
|
||||
setTimeout(() => {
|
||||
store.dispatch('app/addTaskAddTorrents', { fileList })
|
||||
}, 200)
|
||||
}
|
||||
|
||||
function navigateTaskList (status = 'active') {
|
||||
router.push({ path: `/task/${status}` })
|
||||
}
|
||||
@@ -20,24 +50,28 @@ function navigatePreferences () {
|
||||
router.push({ path: '/preference' })
|
||||
}
|
||||
|
||||
function pauseTask () {
|
||||
function showUnderDevelopmentMessage () {
|
||||
Message.info(i18n.t('app.under-development-message'))
|
||||
}
|
||||
|
||||
function pauseTask () {
|
||||
showUnderDevelopmentMessage()
|
||||
}
|
||||
|
||||
function resumeTask () {
|
||||
|
||||
showUnderDevelopmentMessage()
|
||||
}
|
||||
|
||||
function deleteTask () {
|
||||
|
||||
showUnderDevelopmentMessage()
|
||||
}
|
||||
|
||||
function moveTaskUp () {
|
||||
|
||||
showUnderDevelopmentMessage()
|
||||
}
|
||||
|
||||
function moveTaskDown () {
|
||||
|
||||
showUnderDevelopmentMessage()
|
||||
}
|
||||
|
||||
function pauseAllTask () {
|
||||
@@ -48,9 +82,12 @@ function resumeAllTask () {
|
||||
store.dispatch('task/resumeAllTask')
|
||||
}
|
||||
|
||||
commands.register('application:system-theme', updateSystemTheme)
|
||||
commands.register('application:theme', updateTheme)
|
||||
commands.register('application:about', showAboutPanel)
|
||||
commands.register('application:new-task', showAddTask)
|
||||
commands.register('application:new-bt-task', showAddTask)
|
||||
commands.register('application:new-bt-task', showAddBtTask)
|
||||
commands.register('application:new-bt-task-with-file', showAddBtTaskWithFile)
|
||||
commands.register('application:task-list', navigateTaskList)
|
||||
commands.register('application:preferences', navigatePreferences)
|
||||
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
<template>
|
||||
<div v-if="false"></div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'mo-dragger',
|
||||
mounted () {
|
||||
this.preventDefault = ev => ev.preventDefault()
|
||||
let count = 0
|
||||
this.onDragEnter = (ev) => {
|
||||
if (count === 0) {
|
||||
this.$store.dispatch('app/showAddTaskDialog', 'torrent')
|
||||
}
|
||||
count++
|
||||
}
|
||||
|
||||
this.onDragLeave = (ev) => {
|
||||
count--
|
||||
if (count === 0) {
|
||||
this.$store.dispatch('app/hideAddTaskDialog')
|
||||
}
|
||||
}
|
||||
|
||||
this.onDrop = (ev) => {
|
||||
count = 0
|
||||
|
||||
const fileList = [...ev.dataTransfer.files]
|
||||
.map(item => ({ raw: item, name: item.name }))
|
||||
.filter(item => /\.torrent$/.test(item.name))
|
||||
if (!fileList.length) {
|
||||
this.$msg.error(this.$t('task.select-torrent'))
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('dragover', this.preventDefault)
|
||||
document.body.addEventListener('dragenter', this.onDragEnter)
|
||||
document.body.addEventListener('dragleave', this.onDragLeave)
|
||||
document.body.addEventListener('drop', this.onDrop)
|
||||
},
|
||||
destroyed () {
|
||||
document.removeEventListener('dragover', this.preventDefault)
|
||||
document.body.removeEventListener('dragenter', this.onDragEnter)
|
||||
document.body.removeEventListener('dragleave', this.onDragLeave)
|
||||
document.body.removeEventListener('drop', this.onDrop)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -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'
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,21 @@
|
||||
import Icon from '@/components/Icons/Icon'
|
||||
|
||||
Icon.register({
|
||||
'dice': {
|
||||
'width': 24,
|
||||
'height': 24,
|
||||
'raw': `<polyline data-cap="butt" fill="none" stroke="currentColor" stroke-miterlimit="10" points="17,23 17,7 1,7 "/>
|
||||
<line data-cap="butt" fill="none" stroke="currentColor" stroke-miterlimit="10" x1="17" y1="7" x2="23" y2="1"/>
|
||||
<polygon fill="none" stroke="currentColor" stroke-miterlimit="10" points="17,23 23,17 23,1 7,1 1,7 1,23 "/>
|
||||
<circle data-color="color-2" data-stroke="none" cx="12" cy="12" r="1" stroke-linejoin="miter" stroke-linecap="square" stroke="none"/>
|
||||
<circle data-color="color-2" data-stroke="none" cx="6" cy="12" r="1" stroke-linejoin="miter" stroke-linecap="square" stroke="none"/>
|
||||
<circle data-color="color-2" data-stroke="none" cx="12" cy="18" r="1" stroke-linejoin="miter" stroke-linecap="square" stroke="none"/>
|
||||
<circle data-color="color-2" data-stroke="none" cx="6" cy="18" r="1" stroke-linejoin="miter" stroke-linecap="square" stroke="none"/>`,
|
||||
'g': {
|
||||
'stroke': 'currentColor',
|
||||
'stroke-linecap': 'round',
|
||||
'stroke-linejoin': 'round',
|
||||
'stroke-width': '2'
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -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'
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,22 @@
|
||||
import Icon from '@/components/Icons/Icon'
|
||||
|
||||
Icon.register({
|
||||
'sync': {
|
||||
'width': 24,
|
||||
'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-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>
|
||||
<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"></polygon>
|
||||
</g>`,
|
||||
'g': {
|
||||
'stroke': 'currentColor',
|
||||
'stroke-linecap': 'round',
|
||||
'stroke-linejoin': 'round',
|
||||
'stroke-width': '2'
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -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'
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -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'
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,16 @@
|
||||
import Icon from '@/components/Icons/Icon'
|
||||
|
||||
Icon.register({
|
||||
'win-close': {
|
||||
'width': 12,
|
||||
'height': 12,
|
||||
'raw': `<line x1="1.5" y1="1.5" x2="10.5" y2="10.5" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<line x1="10.5" y1="1.5" x2="1.5" y2="10.5" fill="none" stroke-linecap="round" stroke-linejoin="round"/>`,
|
||||
'g': {
|
||||
'stroke': 'currentColor',
|
||||
'stroke-linecap': 'round',
|
||||
'stroke-linejoin': 'round',
|
||||
'stroke-width': '1'
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,16 @@
|
||||
import Icon from '@/components/Icons/Icon'
|
||||
|
||||
Icon.register({
|
||||
'win-maximize': {
|
||||
'width': 12,
|
||||
'height': 12,
|
||||
'raw': `<polyline points="5.5 1.5 10.5 1.5 10.5 6.5" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<polyline points="1.5 5.5 1.5 10.5 6.5 10.5" fill="none" stroke-linecap="round" stroke-linejoin="round" />`,
|
||||
'g': {
|
||||
'stroke': 'currentColor',
|
||||
'stroke-linecap': 'round',
|
||||
'stroke-linejoin': 'round',
|
||||
'stroke-width': '1'
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,15 @@
|
||||
import Icon from '@/components/Icons/Icon'
|
||||
|
||||
Icon.register({
|
||||
'win-minimize': {
|
||||
'width': 12,
|
||||
'height': 12,
|
||||
'raw': `<line x1="1" y1="6" x2="11" y2="6" fill="none" stroke-linecap="round" stroke-linejoin="round"/>`,
|
||||
'g': {
|
||||
'stroke': 'currentColor',
|
||||
'stroke-linecap': 'round',
|
||||
'stroke-linejoin': 'round',
|
||||
'stroke-width': '1'
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,10 @@
|
||||
import resources from '@shared/locales/all'
|
||||
import LocaleManager from '@shared/locales/LocaleManager'
|
||||
|
||||
const localeManager = new LocaleManager({
|
||||
resources
|
||||
})
|
||||
|
||||
export function getLocaleManager () {
|
||||
return localeManager
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
<div class="logo">
|
||||
<a target="_blank" href="https://motrix.app/">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" :width="width" :height="height" viewBox="0 0 62 14">
|
||||
<g :fill="fill" fill-rule="evenodd">
|
||||
<g fill-rule="evenodd">
|
||||
<path d="M40,2 C40,1 41,1.53477231e-14 42,1.53477231e-14 C42,1.53477231e-14 60,1.27897692e-14 60,1.53477231e-14 C61,1.27897692e-14 62,1 62,2 C62,2 62,12 62,12 C62,13 61,14 60,14 C60,14 42,14 42,14 C41,14 40,13 40,12 C40,12 40,2 40,2 Z M44,3.5 C44,3.5 44,10.5 44,10.5 C44,11 44.5,11.5 45,11.5 C45,11.5 57,11.5 57,11.5 C57.5,11.5 58,11 58,10.5 C58,10.5 58,3.5 58,3.5 C58,3 57.5,2.5 57,2.5 C57,2.5 45,2.5 45,2.5 C44.5,2.5 44,3 44,3.5 Z"/>
|
||||
<rect width="4" height="2" x="32" y="6" rx=".5"/>
|
||||
<path d="M2,0 L26,0 C27,-2.04003481e-15 28,1 28,2 L28,14 L24,14 L24,3.5 C24,3 23.5,2.5 23,2.5 L16,2.5 L16,14 L12,14 L12,2.5 L5,2.5 C4.5,2.5 4,3 4,3.5 L4,14 L0,14 L0,2 C0,1 1,-2.04003481e-15 2,0 Z"/>
|
||||
@@ -23,10 +23,6 @@
|
||||
height: {
|
||||
type: Number,
|
||||
default: 14
|
||||
},
|
||||
fill: {
|
||||
type: String,
|
||||
default: '#4D515A'
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -43,6 +39,10 @@
|
||||
height: 100%;
|
||||
text-align: center;
|
||||
font-size: 0;
|
||||
color: $--app-logo-color;
|
||||
}
|
||||
svg {
|
||||
fill: currentColor;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
<template>
|
||||
<el-container id="container">
|
||||
<mo-aside />
|
||||
<router-view></router-view>
|
||||
<router-view />
|
||||
<mo-speedometer />
|
||||
<mo-add-task :visible="addTaskVisible" :type="addTaskType" />
|
||||
<mo-about-panel :visible="aboutPanelVisible" />
|
||||
<mo-task-item-info :visible="taskItemInfoVisible" :task="currentTaskItem" />
|
||||
<mo-dragger />
|
||||
</el-container>
|
||||
</template>
|
||||
|
||||
@@ -13,20 +14,20 @@
|
||||
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'
|
||||
import Dragger from '@/components/Dragger/Index'
|
||||
|
||||
export default {
|
||||
name: 'mo-main',
|
||||
components: {
|
||||
[AboutPanel.name]: AboutPanel,
|
||||
[Aside.name]: Aside,
|
||||
[Subnav.name]: Subnav,
|
||||
[Speedometer.name]: Speedometer,
|
||||
[AddTask.name]: AddTask,
|
||||
[TaskItemInfo.name]: TaskItemInfo
|
||||
[TaskItemInfo.name]: TaskItemInfo,
|
||||
[Dragger.name]: Dragger
|
||||
},
|
||||
computed: {
|
||||
...mapState('app', {
|
||||
@@ -49,26 +50,6 @@
|
||||
position: fixed;
|
||||
right: 36px;
|
||||
bottom: 24px;
|
||||
}
|
||||
.panel {
|
||||
background: $--panel-background;
|
||||
.panel-header {
|
||||
position: relative;
|
||||
padding: 44px 0 12px;
|
||||
margin: 0 36px;
|
||||
border-bottom: 2px solid $--panel-border-color;
|
||||
h4 {
|
||||
margin: 0;
|
||||
color: $--panel-title-color;
|
||||
font-size: 16px;
|
||||
font-weight: normal;
|
||||
line-height: 24px;
|
||||
}
|
||||
}
|
||||
.panel-content {
|
||||
position: relative;
|
||||
padding: 16px 36px 24px;
|
||||
height: 100%;
|
||||
}
|
||||
z-index: 20;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
const queue = []
|
||||
const maxLength = 5
|
||||
|
||||
export default {
|
||||
install: function (Vue, Message, defaultOption = {}) {
|
||||
Vue.prototype.$msg = new Proxy(Message, {
|
||||
get (obj, prop) {
|
||||
return (arg) => {
|
||||
if (!(arg instanceof Object)) {
|
||||
arg = { message: arg }
|
||||
}
|
||||
const task = {
|
||||
run () {
|
||||
obj[prop]({
|
||||
...defaultOption,
|
||||
...arg,
|
||||
onClose (...data) {
|
||||
const currentTask = queue.pop()
|
||||
if (currentTask) {
|
||||
currentTask.run()
|
||||
}
|
||||
if (arg.onClose) {
|
||||
arg.onClose(...data)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (queue.length >= maxLength) {
|
||||
queue.pop()
|
||||
}
|
||||
queue.unshift(task)
|
||||
|
||||
if (queue.length === 1) {
|
||||
queue.pop().run()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import is from 'electron-is'
|
||||
import { mapState } from 'vuex'
|
||||
import api from '@/api'
|
||||
import {
|
||||
@@ -18,10 +19,17 @@
|
||||
|
||||
export default {
|
||||
name: 'mo-engine-client',
|
||||
data: function () {
|
||||
return {
|
||||
downloading: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isRenderer: () => is.renderer(),
|
||||
...mapState('app', {
|
||||
downloadSpeed: state => state.stat.downloadSpeed,
|
||||
interval: state => state.interval
|
||||
interval: state => state.interval,
|
||||
numActive: state => state.stat.numActive
|
||||
}),
|
||||
...mapState('task', {
|
||||
taskItemInfoVisible: state => state.taskItemInfoVisible,
|
||||
@@ -32,146 +40,192 @@
|
||||
})
|
||||
},
|
||||
watch: {
|
||||
downloadSpeed: function (val, oldVal) {
|
||||
downloadSpeed (val, oldVal) {
|
||||
showDownloadSpeedInDock(val)
|
||||
},
|
||||
numActive (val, oldVal) {
|
||||
this.downloading = val > 0
|
||||
},
|
||||
downloading (val, oldVal) {
|
||||
if (val !== oldVal && this.isRenderer) {
|
||||
this.$electron.ipcRenderer.send('download-status-change', val)
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onDownloadStart: function (event) {
|
||||
fetchTaskItem ({ gid }) {
|
||||
return api.fetchTaskItem({ gid })
|
||||
.catch((e) => {
|
||||
console.warn(`fetchTaskItem fail: ${e.message}`)
|
||||
})
|
||||
},
|
||||
onDownloadStart (event) {
|
||||
this.$store.dispatch('task/fetchList')
|
||||
this.$store.dispatch('app/resetInterval')
|
||||
console.log('aria2 onDownloadStart', event)
|
||||
const [{ gid }] = event
|
||||
api.fetchTaskItem({ gid })
|
||||
this.fetchTaskItem({ gid })
|
||||
.then((task) => {
|
||||
const message = `开始下载 ${getTaskName(task)}`
|
||||
this.$message.info(message)
|
||||
const taskName = getTaskName(task)
|
||||
const message = this.$t('task.download-start-message', { taskName })
|
||||
this.$msg.info(message)
|
||||
})
|
||||
},
|
||||
onDownloadPause: function (event) {
|
||||
onDownloadPause (event) {
|
||||
console.log('aria2 onDownloadPause')
|
||||
const [{ gid }] = event
|
||||
api.fetchTaskItem({ gid })
|
||||
this.fetchTaskItem({ gid })
|
||||
.then((task) => {
|
||||
const message = `暂停下载 ${getTaskName(task)}`
|
||||
this.$message.info(message)
|
||||
const taskName = getTaskName(task)
|
||||
const message = this.$t('task.download-pause-message', { taskName })
|
||||
this.$msg.info(message)
|
||||
})
|
||||
},
|
||||
onDownloadStop: function (event) {
|
||||
onDownloadStop (event) {
|
||||
console.log('aria2 onDownloadStop')
|
||||
const [{ gid }] = event
|
||||
api.fetchTaskItem({ gid })
|
||||
this.fetchTaskItem({ gid })
|
||||
.then((task) => {
|
||||
const message = `${getTaskName(task)} 下载中止`
|
||||
this.$message.info(message)
|
||||
const taskName = getTaskName(task)
|
||||
const message = this.$t('task.download-stop-message', { taskName })
|
||||
this.$msg.info(message)
|
||||
})
|
||||
},
|
||||
onDownloadError: function (event) {
|
||||
console.log('aria2 onDownloadError', event)
|
||||
onDownloadError (event) {
|
||||
const [{ gid }] = event
|
||||
api.fetchTaskItem({ gid })
|
||||
this.fetchTaskItem({ gid })
|
||||
.then((task) => {
|
||||
const message = `${getTaskName(task)} 下载发生错误`
|
||||
this.$message.error(message)
|
||||
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 })
|
||||
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) {
|
||||
onDownloadComplete (event) {
|
||||
console.log('aria2 onDownloadComplete')
|
||||
this.$store.dispatch('task/fetchList')
|
||||
const [{ gid }] = event
|
||||
api.fetchTaskItem({ gid })
|
||||
this.fetchTaskItem({ gid })
|
||||
.then((task) => {
|
||||
this.showTaskCompleteNotify(task)
|
||||
this.handleDownloadComplete(task, false)
|
||||
})
|
||||
},
|
||||
onBtDownloadComplete: function (event) {
|
||||
onBtDownloadComplete (event) {
|
||||
console.log('aria2 onBtDownloadComplete')
|
||||
this.$store.dispatch('task/fetchList')
|
||||
const [{ gid }] = event
|
||||
api.fetchTaskItem({ gid })
|
||||
this.fetchTaskItem({ gid })
|
||||
.then((task) => {
|
||||
this.showTaskCompleteNotify(task)
|
||||
this.handleDownloadComplete(task, true)
|
||||
})
|
||||
},
|
||||
showTaskCompleteNotify: function (task) {
|
||||
if (!this.taskNotification) {
|
||||
return
|
||||
}
|
||||
|
||||
const taskName = getTaskName(task)
|
||||
handleDownloadComplete (task, isBT) {
|
||||
const path = getTaskFullPath(task)
|
||||
|
||||
addToRecentTask(task)
|
||||
openDownloadDock(path)
|
||||
|
||||
const message = `${taskName} 下载完成`
|
||||
this.$message.success(message)
|
||||
|
||||
/* eslint-disable no-new */
|
||||
const notify = new Notification('下载完成', {
|
||||
body: taskName
|
||||
})
|
||||
notify.onclick = () => {
|
||||
showItemInFolder(path)
|
||||
}
|
||||
this.showTaskCompleteNotify(task, isBT, path)
|
||||
},
|
||||
showTaskErrorNotify: function (task) {
|
||||
showTaskCompleteNotify (task, isBT, path) {
|
||||
const taskName = getTaskName(task)
|
||||
const message = isBT
|
||||
? this.$t('task.bt-download-complete-message', { taskName })
|
||||
: this.$t('task.download-complete-message', { taskName })
|
||||
const tips = isBT
|
||||
? '\n' + this.$t('task.bt-download-complete-tips')
|
||||
: ''
|
||||
|
||||
this.$msg.success(`${message}${tips}`)
|
||||
|
||||
if (!this.taskNotification) {
|
||||
return
|
||||
}
|
||||
|
||||
/* eslint-disable no-new */
|
||||
const notifyMessage = isBT
|
||||
? this.$t('task.bt-download-complete-notify')
|
||||
: this.$t('task.download-complete-notify')
|
||||
|
||||
const notify = new Notification(notifyMessage, {
|
||||
body: `${taskName}${tips}`
|
||||
})
|
||||
notify.onclick = () => {
|
||||
showItemInFolder(path, {
|
||||
errorMsg: this.$t('task.file-not-exist')
|
||||
})
|
||||
}
|
||||
},
|
||||
showTaskErrorNotify (task) {
|
||||
const taskName = getTaskName(task)
|
||||
|
||||
const message = `${taskName} 下载失败`
|
||||
this.$message.success(message)
|
||||
const message = this.$t('task.download-fail-message', { taskName })
|
||||
this.$msg.success(message)
|
||||
|
||||
if (!this.taskNotification) {
|
||||
return
|
||||
}
|
||||
|
||||
/* eslint-disable no-new */
|
||||
new Notification('下载失败', {
|
||||
new Notification(this.$t('task.download-fail-notify'), {
|
||||
body: taskName
|
||||
})
|
||||
},
|
||||
bindEngineEvents: function () {
|
||||
bindEngineEvents () {
|
||||
api.client.on('onDownloadStart', this.onDownloadStart)
|
||||
api.client.on('onDownloadPause', this.onDownloadPause)
|
||||
// api.client.on('onDownloadPause', this.onDownloadPause)
|
||||
api.client.on('onDownloadStop', this.onDownloadStop)
|
||||
api.client.on('onDownloadComplete', this.onDownloadComplete)
|
||||
api.client.on('onDownloadError', this.onDownloadError)
|
||||
api.client.on('onBtDownloadComplete', this.onBtDownloadComplete)
|
||||
},
|
||||
unbindEngineEvents: function () {
|
||||
unbindEngineEvents () {
|
||||
api.client.removeListener('onDownloadStart', this.onDownloadStart)
|
||||
api.client.removeListener('onDownloadPause', this.onDownloadPause)
|
||||
// api.client.removeListener('onDownloadPause', this.onDownloadPause)
|
||||
api.client.removeListener('onDownloadStop', this.onDownloadStop)
|
||||
api.client.removeListener('onDownloadComplete', this.onDownloadComplete)
|
||||
api.client.removeListener('onDownloadError', this.onDownloadError)
|
||||
api.client.removeListener('onBtDownloadComplete', this.onBtDownloadComplete)
|
||||
},
|
||||
startPolling: function () {
|
||||
startPolling () {
|
||||
this.timer = setTimeout(() => {
|
||||
this.polling()
|
||||
this.startPolling()
|
||||
}, this.interval)
|
||||
},
|
||||
polling: function () {
|
||||
polling () {
|
||||
this.$store.dispatch('app/fetchGlobalStat')
|
||||
this.$store.dispatch('task/fetchList')
|
||||
|
||||
if (this.taskItemInfoVisible && this.currentTaskItem) {
|
||||
this.$store.dispatch('task/fetchItem', this.currentTaskItem.gid)
|
||||
}
|
||||
},
|
||||
stopPolling: function () {
|
||||
stopPolling () {
|
||||
clearTimeout(this.timer)
|
||||
this.timer = null
|
||||
}
|
||||
},
|
||||
created: function () {
|
||||
this.$store.dispatch('app/fetchEngineInfo')
|
||||
|
||||
this.startPolling()
|
||||
|
||||
created () {
|
||||
this.bindEngineEvents()
|
||||
},
|
||||
destroyed: function () {
|
||||
mounted () {
|
||||
setTimeout(() => {
|
||||
this.$store.dispatch('app/fetchEngineInfo')
|
||||
this.$store.dispatch('app/fetchEngineOptions')
|
||||
|
||||
this.startPolling()
|
||||
}, 100)
|
||||
},
|
||||
destroyed () {
|
||||
this.$store.dispatch('task/saveSession')
|
||||
|
||||
this.unbindEngineEvents()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<i @click.stop="onFolderClick">
|
||||
<mo-icon name="folder" width="14" height="14" />
|
||||
<mo-icon name="folder" width="10" height="10" />
|
||||
</i>
|
||||
</template>
|
||||
|
||||
@@ -21,7 +21,9 @@
|
||||
if (!this.path) {
|
||||
return
|
||||
}
|
||||
this.$electron.shell.showItemInFolder(this.path)
|
||||
this.$electron.shell.showItemInFolder(this.path, {
|
||||
errorMsg: this.$t('task.file-not-exist')
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
<template>
|
||||
<div class="title-bar">
|
||||
<div class="title-bar-dragger"></div>
|
||||
<ul v-if="showActions" class="window-actions">
|
||||
<li @click="handleMinimize">
|
||||
<mo-icon name="win-minimize" width="12" height="12" />
|
||||
</li>
|
||||
<li @click="handleMaximize">
|
||||
<mo-icon name="win-maximize" width="12" height="12" />
|
||||
</li>
|
||||
<li @click="handleClose" class="win-close-btn">
|
||||
<mo-icon name="win-close" width="12" height="12" />
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import '@/components/Icons/win-minimize'
|
||||
import '@/components/Icons/win-maximize'
|
||||
import '@/components/Icons/win-close'
|
||||
|
||||
export default {
|
||||
name: 'mo-title-bar',
|
||||
props: {
|
||||
showActions: {
|
||||
type: Boolean
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
win: function () {
|
||||
return this.$electron.remote.getCurrentWindow()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleMinimize: function () {
|
||||
this.win.minimize()
|
||||
},
|
||||
handleMaximize: function () {
|
||||
if (this.win.isMaximized()) {
|
||||
this.win.unmaximize()
|
||||
} else {
|
||||
this.win.maximize()
|
||||
}
|
||||
},
|
||||
handleClose: function () {
|
||||
this.win.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.title-bar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
width: 100%;
|
||||
height: 36px;
|
||||
z-index: 5000;
|
||||
.title-bar-dragger {
|
||||
flex: 1;
|
||||
user-select: none;
|
||||
-webkit-app-region: drag;
|
||||
-webkit-user-select: none;
|
||||
}
|
||||
.window-actions {
|
||||
opacity: 0.4;
|
||||
transition: $--fade-transition;
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
z-index: 5100;
|
||||
> li {
|
||||
display: inline-block;
|
||||
padding: 5px 10px;
|
||||
margin: 0 5px;
|
||||
color: $--titlebar-actions-color;
|
||||
&:hover {
|
||||
background-color: $--titlebar-actions-active-background;
|
||||
}
|
||||
&.win-close-btn:hover {
|
||||
color: $--titlebar-close-active-color;
|
||||
background-color: $--titlebar-close-active-background;
|
||||
}
|
||||
}
|
||||
}
|
||||
&:hover {
|
||||
.window-actions {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -2,9 +2,11 @@ import is from 'electron-is'
|
||||
import { existsSync } from 'fs'
|
||||
import { Message } from 'element-ui'
|
||||
import {
|
||||
isMagnetTask,
|
||||
getTaskFullPath,
|
||||
bytesToSize
|
||||
} from '@shared/utils'
|
||||
import { LIGHT_THEME, DARK_THEME } from '@shared/constants'
|
||||
|
||||
const remote = is.renderer() ? require('electron').remote : {}
|
||||
|
||||
@@ -14,31 +16,59 @@ export function getUserDownloadsPath () {
|
||||
|
||||
export function prettifyDir (dir) {
|
||||
const downloads = getUserDownloadsPath()
|
||||
const result = dir === downloads ? '下载' : dir
|
||||
const result = dir === downloads ? 'Downloads' : dir
|
||||
return result
|
||||
}
|
||||
|
||||
export function showItemInFolder (fullPath) {
|
||||
export function showItemInFolder (fullPath, { errorMsg }) {
|
||||
if (!fullPath) {
|
||||
return
|
||||
}
|
||||
const result = remote.shell.showItemInFolder(fullPath)
|
||||
if (!result) {
|
||||
Message.error('目标文件不存在或已删除')
|
||||
if (!result && errorMsg) {
|
||||
Message.error(errorMsg)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
export function moveTaskFilesToTrash (task) {
|
||||
export function openItem (fullPath, { errorMsg }) {
|
||||
if (!fullPath) {
|
||||
return
|
||||
}
|
||||
const result = remote.shell.openItem(fullPath)
|
||||
if (!result && errorMsg) {
|
||||
Message.error(errorMsg)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
export function moveTaskFilesToTrash (task, messages = {}) {
|
||||
/**
|
||||
* 磁力链接任务,有 bittorrent,但没有 bittorrent.info ,
|
||||
* 在没下完变成BT任务之前 path 不是一个完整路径,
|
||||
* 未避免误删所在目录,所以删除时直接返回 true
|
||||
*/
|
||||
if (isMagnetTask(task)) {
|
||||
return true
|
||||
}
|
||||
|
||||
const { pathErrorMsg, delFailMsg, delConfigFailMsg } = messages
|
||||
const { dir } = task
|
||||
const path = getTaskFullPath(task)
|
||||
if (!path) {
|
||||
Message.error('文件路径异常,请手动删除')
|
||||
if (!path || dir === path) {
|
||||
if (pathErrorMsg) {
|
||||
Message.error(pathErrorMsg)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
const deleteResult1 = remote.shell.moveItemToTrash(path)
|
||||
if (!deleteResult1) {
|
||||
Message.error('删除任务文件失败,请手动删除')
|
||||
let deleteResult1 = true
|
||||
const isFileExist = existsSync(path)
|
||||
if (isFileExist) {
|
||||
deleteResult1 = remote.shell.moveItemToTrash(path)
|
||||
if (!deleteResult1 && delFailMsg) {
|
||||
Message.error(delFailMsg)
|
||||
}
|
||||
}
|
||||
|
||||
let deleteResult2 = true
|
||||
@@ -46,8 +76,8 @@ export function moveTaskFilesToTrash (task) {
|
||||
const isExtraExist = existsSync(extraFilePath)
|
||||
if (isExtraExist) {
|
||||
deleteResult2 = remote.shell.moveItemToTrash(extraFilePath)
|
||||
if (!deleteResult2) {
|
||||
Message.error('删除任务配置文件失败,请手动删除')
|
||||
if (!deleteResult2 && delConfigFailMsg) {
|
||||
Message.error(delConfigFailMsg)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,30 +85,54 @@ export function moveTaskFilesToTrash (task) {
|
||||
}
|
||||
|
||||
export function openDownloadDock (path) {
|
||||
if (!is.macOS()) {
|
||||
return
|
||||
}
|
||||
remote.app.dock.downloadFinished(path)
|
||||
}
|
||||
|
||||
export function updateDockBadge (text) {
|
||||
if (!is.macOS()) {
|
||||
return
|
||||
}
|
||||
remote.app.dock.setBadge(text)
|
||||
}
|
||||
|
||||
export function showDownloadSpeedInDock (downloadSpeed) {
|
||||
if (is.windows()) {
|
||||
if (!is.macOS()) {
|
||||
return
|
||||
}
|
||||
const text = downloadSpeed > 0 ? bytesToSize(downloadSpeed) : ''
|
||||
const text = downloadSpeed > 0 ? `${bytesToSize(downloadSpeed)}/s` : ''
|
||||
updateDockBadge(text)
|
||||
}
|
||||
|
||||
export function addToRecentTask (task) {
|
||||
if (is.linux()) {
|
||||
return
|
||||
}
|
||||
const path = getTaskFullPath(task)
|
||||
remote.app.addRecentDocument(path)
|
||||
}
|
||||
|
||||
export function addToRecentTaskByPath (path) {
|
||||
if (is.linux()) {
|
||||
return
|
||||
}
|
||||
remote.app.addRecentDocument(path)
|
||||
}
|
||||
|
||||
export function clearRecentTasks () {
|
||||
if (is.linux()) {
|
||||
return
|
||||
}
|
||||
remote.app.clearRecentDocuments()
|
||||
}
|
||||
|
||||
export function getSystemTheme () {
|
||||
let result = LIGHT_THEME
|
||||
if (!is.macOS()) {
|
||||
return result
|
||||
}
|
||||
result = remote.systemPreferences.isDarkMode() ? DARK_THEME : LIGHT_THEME
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -6,20 +6,34 @@
|
||||
<el-main class="panel-content">
|
||||
<el-form
|
||||
class="form-preference"
|
||||
ref="basicForm"
|
||||
ref="advancedForm"
|
||||
label-position="right"
|
||||
size="mini"
|
||||
:model="form"
|
||||
:rules="rules">
|
||||
<el-form-item label="代理: " :label-width="formLabelWidth">
|
||||
<el-form-item :label="`${$t('preferences.auto-update')}: `" :label-width="formLabelWidth">
|
||||
<el-col class="form-item-sub" :span="24">
|
||||
<el-checkbox v-model="form.autoCheckUpdate">
|
||||
{{ $t('preferences.auto-check-update') }}
|
||||
</el-checkbox>
|
||||
<div class="el-form-item__info" style="margin-top: 8px;" v-if="form.lastCheckUpdateTime !== 0">
|
||||
{{ $t('preferences.last-check-update-time') + ': ' + (form.lastCheckUpdateTime !== 0 ? new
|
||||
Date(form.lastCheckUpdateTime).toLocaleString() : new Date().toLocaleString()) }}
|
||||
<span class="action-link" @click.prevent="onCheckUpdateClick">
|
||||
{{ $t('app.check-updates-now') }}
|
||||
</span>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-form-item>
|
||||
<el-form-item :label="`${$t('preferences.proxy')}: `" :label-width="formLabelWidth">
|
||||
<el-switch
|
||||
v-model="form.useProxy"
|
||||
active-text="使用代理服务器"
|
||||
:active-text="$t('preferences.use-proxy')"
|
||||
@change="onUseProxyChange"
|
||||
>
|
||||
</el-switch>
|
||||
</el-form-item>
|
||||
<el-form-item label="" :label-width="formLabelWidth" v-if="form.useProxy">
|
||||
<el-form-item :label-width="formLabelWidth" v-if="form.useProxy">
|
||||
<el-col class="form-item-sub" :span="16">
|
||||
<el-input
|
||||
placeholder="[http://][USER:PASSWORD@]HOST[:PORT]"
|
||||
@@ -28,9 +42,67 @@
|
||||
</el-input>
|
||||
</el-col>
|
||||
</el-form-item>
|
||||
<el-form-item label="开发者: " :label-width="formLabelWidth">
|
||||
<el-form-item :label="`${$t('preferences.bt-tracker')}: `" :label-width="formLabelWidth">
|
||||
<div class="bt-tracker">
|
||||
<el-input
|
||||
type="textarea"
|
||||
:autosize="{ minRows: 3, maxRows: 5 }"
|
||||
auto-complete="off"
|
||||
:placeholder="`${$t('preferences.bt-tracker-input-tips')}`"
|
||||
v-model="form.btTracker">
|
||||
</el-input>
|
||||
<div class="sync-tracker">
|
||||
<el-tooltip
|
||||
class="item"
|
||||
effect="dark"
|
||||
:content="$t('preferences.sync-tracker-tips')"
|
||||
placement="bottom"
|
||||
>
|
||||
<el-button
|
||||
@click="syncTrackerFromGitHub"
|
||||
>
|
||||
<mo-icon
|
||||
name="refresh"
|
||||
width="12"
|
||||
height="12"
|
||||
:spin="true"
|
||||
v-if="trackerSyncing"
|
||||
/>
|
||||
<mo-icon name="sync" width="12" height="12" v-else />
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
<div class="el-form-item__info" style="margin-top: 8px;">
|
||||
{{ $t('preferences.bt-tracker-tips') }}
|
||||
<a target="_blank" href="https://github.com/ngosang/trackerslist" rel="noopener noreferrer">
|
||||
https://github.com/ngosang/trackerslist
|
||||
<mo-icon name="link" width="12" height="12" />
|
||||
</a>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item :label="`${$t('preferences.download-protocol')}: `" :label-width="formLabelWidth">
|
||||
{{ $t('preferences.protocols-default-client') }}
|
||||
<el-col class="form-item-sub" :span="24">
|
||||
模拟用户代理
|
||||
<el-switch
|
||||
v-model="form.protocols.magnet"
|
||||
:active-text="$t('preferences.protocols-magnet')"
|
||||
@change="(val) => onProtocolsChange('magnet', val)"
|
||||
>
|
||||
</el-switch>
|
||||
</el-col>
|
||||
<el-col class="form-item-sub" :span="24">
|
||||
<el-switch
|
||||
v-model="form.protocols.thunder"
|
||||
:active-text="$t('preferences.protocols-thunder')"
|
||||
@change="(val) => onProtocolsChange('thunder', val)"
|
||||
>
|
||||
</el-switch>
|
||||
</el-col>
|
||||
</el-form-item>
|
||||
<el-form-item :label="`${$t('preferences.security')}: `" :label-width="formLabelWidth">
|
||||
<el-col class="form-item-sub" :span="24">
|
||||
{{ $t('preferences.mock-user-agent') }}
|
||||
<el-input
|
||||
type="textarea"
|
||||
:autosize="{ minRows: 2, maxRows: 3 }"
|
||||
@@ -44,8 +116,29 @@
|
||||
<el-button @click="() => changeUA('du')">du</el-button>
|
||||
</el-button-group>
|
||||
</el-col>
|
||||
<el-col class="form-item-sub" :span="18">
|
||||
{{ $t('preferences.rpc-secret') }}
|
||||
<el-input
|
||||
:show-password="hideRpcSecret"
|
||||
placeholder="RPC Secret"
|
||||
:maxlength="24"
|
||||
v-model="form.rpcSecret"
|
||||
>
|
||||
<i slot="append" @click.prevent="onDiceClick">
|
||||
<mo-icon name="dice" width="12" height="12" />
|
||||
</i>
|
||||
</el-input>
|
||||
<div class="el-form-item__info" style="margin-top: 8px;">
|
||||
<a target="_blank" href="https://github.com/agalwood/Motrix/wiki/RPC" rel="noopener noreferrer">
|
||||
{{ $t('preferences.rpc-secret-tips') }}
|
||||
<mo-icon name="link" width="12" height="12" />
|
||||
</a>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-form-item>
|
||||
<el-form-item :label="`${$t('preferences.developer')}: `" :label-width="formLabelWidth">
|
||||
<el-col class="form-item-sub" :span="24">
|
||||
应用日志路径
|
||||
{{ $t('preferences.app-log-path') }}
|
||||
<el-input placeholder="" disabled v-model="logPath">
|
||||
<mo-show-in-folder
|
||||
slot="append"
|
||||
@@ -55,7 +148,7 @@
|
||||
</el-input>
|
||||
</el-col>
|
||||
<el-col class="form-item-sub" :span="24">
|
||||
下载会话记录
|
||||
{{ $t('preferences.download-session-path') }}
|
||||
<el-input placeholder="" disabled v-model="sessionPath">
|
||||
<mo-show-in-folder
|
||||
slot="append"
|
||||
@@ -65,13 +158,15 @@
|
||||
</el-input>
|
||||
</el-col>
|
||||
<el-col class="form-item-sub" :span="24">
|
||||
<el-button plain type="danger" @click="() => onResetClick()">恢复初始设置</el-button>
|
||||
<el-button plain type="danger" @click="() => onFactoryResetClick()">
|
||||
{{ $t('preferences.factory-reset') }}
|
||||
</el-button>
|
||||
</el-col>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div class="form-actions">
|
||||
<el-button type="primary" @click="submitForm('basicForm')">保存并应用</el-button>
|
||||
<el-button @click="resetForm('basicForm')">放弃</el-button>
|
||||
<el-button type="primary" @click="submitForm('advancedForm')">{{ $t('preferences.save') }}</el-button>
|
||||
<el-button @click="resetForm('advancedForm')">{{ $t('preferences.discard') }}</el-button>
|
||||
</div>
|
||||
</el-main>
|
||||
</el-container>
|
||||
@@ -80,20 +175,49 @@
|
||||
<script>
|
||||
import is from 'electron-is'
|
||||
import { mapState } from 'vuex'
|
||||
import { cloneDeep } from 'lodash'
|
||||
import randomize from 'randomatic'
|
||||
import * as clipboard from 'clipboard-polyfill'
|
||||
import ShowInFolder from '@/components/Native/ShowInFolder'
|
||||
import userAgentMap from '@shared/ua'
|
||||
import {
|
||||
buildRpcUrl,
|
||||
calcFormLabelWidth,
|
||||
convertCommaToLine,
|
||||
convertLineToComma,
|
||||
diffConfig
|
||||
} from '@shared/utils'
|
||||
import '@/components/Icons/dice'
|
||||
import '@/components/Icons/sync'
|
||||
import '@/components/Icons/refresh'
|
||||
|
||||
const initialForm = (config) => {
|
||||
const {
|
||||
useProxy,
|
||||
allProxy,
|
||||
allProxyBackup,
|
||||
autoCheckUpdate,
|
||||
btTracker,
|
||||
hideAppMenu,
|
||||
lastCheckUpdateTime,
|
||||
protocols,
|
||||
rpcListenPort,
|
||||
rpcSecret,
|
||||
useProxy,
|
||||
userAgent
|
||||
} = config
|
||||
const result = {
|
||||
useProxy,
|
||||
allProxy,
|
||||
allProxyBackup,
|
||||
autoCheckUpdate,
|
||||
btTracker: convertCommaToLine(btTracker),
|
||||
hideAppMenu,
|
||||
lastCheckUpdateTime,
|
||||
protocols: {
|
||||
...protocols
|
||||
},
|
||||
rpcListenPort,
|
||||
rpcSecret,
|
||||
useProxy,
|
||||
userAgent
|
||||
}
|
||||
return result
|
||||
@@ -105,15 +229,23 @@
|
||||
[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),
|
||||
rules: {}
|
||||
form,
|
||||
formLabelWidth: calcFormLabelWidth(locale),
|
||||
formOriginal: cloneDeep(form),
|
||||
hideRpcSecret: true,
|
||||
rules: {},
|
||||
trackerSyncing: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
title: function () {
|
||||
return '进阶设置'
|
||||
return this.$t('preferences.advanced')
|
||||
},
|
||||
showHideAppMenuOption: function () {
|
||||
return is.windows() || is.linux()
|
||||
},
|
||||
...mapState('preference', {
|
||||
config: state => state.config,
|
||||
@@ -122,34 +254,73 @@
|
||||
})
|
||||
},
|
||||
watch: {
|
||||
|
||||
'form.rpcSecret': function (val) {
|
||||
const url = buildRpcUrl({
|
||||
port: this.form.rpcListenPort,
|
||||
secret: val
|
||||
})
|
||||
clipboard.writeText(url)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
isRenderer: is.renderer,
|
||||
onCheckUpdateClick () {
|
||||
this.$electron.ipcRenderer.send('command', 'application:check-for-updates')
|
||||
this.$msg.info(this.$t('app.checking-for-updates'))
|
||||
this.$store.dispatch('preference/fetchPreference')
|
||||
.then((config) => {
|
||||
const { lastCheckUpdateTime } = config
|
||||
this.form.lastCheckUpdateTime = lastCheckUpdateTime
|
||||
})
|
||||
},
|
||||
syncTrackerFromGitHub () {
|
||||
this.trackerSyncing = true
|
||||
this.$store.dispatch('preference/fetchBtTracker')
|
||||
.then((data) => {
|
||||
console.log('syncTrackerFromGitHub data====>', data)
|
||||
this.form.btTracker = data
|
||||
})
|
||||
.finally(() => {
|
||||
this.trackerSyncing = false
|
||||
})
|
||||
},
|
||||
onProtocolsChange (protocol, enabled) {
|
||||
const { protocols } = this.form
|
||||
this.form.protocols = {
|
||||
...protocols,
|
||||
[protocol]: enabled
|
||||
}
|
||||
},
|
||||
onUseProxyChange (flag) {
|
||||
this.form.allProxy = flag ? this.form.allProxyBackup : ''
|
||||
console.log('this.form.allProxy===>', flag, this.form.allProxy)
|
||||
},
|
||||
onAllProxyBackupChange (value) {
|
||||
this.form.allProxy = value
|
||||
},
|
||||
changeUA (type) {
|
||||
const ua = userAgentMap[type]
|
||||
console.log('changeUA===>', ua)
|
||||
if (!ua) {
|
||||
return
|
||||
}
|
||||
this.form.userAgent = ua
|
||||
},
|
||||
onResetClick () {
|
||||
onDiceClick () {
|
||||
this.hideRpcSecret = false
|
||||
const rpcSecret = randomize('Aa0', 12)
|
||||
this.form.rpcSecret = rpcSecret
|
||||
|
||||
setTimeout(() => {
|
||||
this.hideRpcSecret = true
|
||||
}, 2000)
|
||||
},
|
||||
onFactoryResetClick () {
|
||||
this.$electron.remote.dialog.showMessageBox({
|
||||
type: 'warning',
|
||||
title: '恢复初始设置',
|
||||
message: '你确定要恢复为初始设置吗?',
|
||||
buttons: ['是', '否'],
|
||||
title: this.$t('preferences.factory-reset'),
|
||||
message: this.$t('preferences.factory-reset-confirm'),
|
||||
buttons: [this.$t('app.yes'), this.$t('app.no')],
|
||||
cancelId: 1
|
||||
}, (buttonIndex) => {
|
||||
// 点击的按钮是哪个按钮 0: 是, 1: 否
|
||||
if (buttonIndex === 0) {
|
||||
this.$electron.ipcRenderer.send('command', 'application:reset')
|
||||
}
|
||||
@@ -161,11 +332,27 @@
|
||||
console.log('error submit!!')
|
||||
return false
|
||||
}
|
||||
const changed = diffConfig(this.formOriginal, this.form)
|
||||
const data = {
|
||||
...changed,
|
||||
btTracker: convertLineToComma(this.form.btTracker),
|
||||
protocols: {
|
||||
...this.form.protocols
|
||||
}
|
||||
}
|
||||
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')
|
||||
this.$electron.ipcRenderer.send('command', 'application:setup-protocols-client', data.protocols)
|
||||
}
|
||||
})
|
||||
},
|
||||
@@ -177,7 +364,15 @@
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.ua-group {
|
||||
margin-top: 8px;
|
||||
.bt-tracker {
|
||||
position: relative;
|
||||
.sync-tracker {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 8px;
|
||||
}
|
||||
}
|
||||
.ua-group {
|
||||
margin-top: 8px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -11,12 +11,56 @@
|
||||
size="mini"
|
||||
:model="form"
|
||||
:rules="rules">
|
||||
<el-form-item label="启动: " :label-width="formLabelWidth">
|
||||
<el-checkbox v-model="form.resumeAllWhenAppLaunched">
|
||||
启动后自动开始未完成任务
|
||||
</el-checkbox>
|
||||
<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="默认下载路径: " :label-width="formLabelWidth">
|
||||
<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') }}
|
||||
</el-checkbox>
|
||||
</el-col>
|
||||
</el-form-item>
|
||||
<el-form-item :label="`${$t('preferences.default-dir')}: `" :label-width="formLabelWidth">
|
||||
<el-input placeholder="" v-model="downloadDir" :readonly="isMas()">
|
||||
<mo-select-directory
|
||||
v-if="isRenderer()"
|
||||
@@ -25,50 +69,74 @@
|
||||
/>
|
||||
</el-input>
|
||||
<div class="el-form-item__info" v-if="isMas()" style="margin-top: 8px;">
|
||||
因 App Store 的沙箱权限限制,默认下载路径建议设置为您的「下载」目录
|
||||
{{ $t('preferences.mas-default-dir-tips') }}
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="任务管理: " :label-width="formLabelWidth">
|
||||
<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') }}
|
||||
<el-input-number
|
||||
v-model="form.maxConcurrentDownloads"
|
||||
controls-position="right"
|
||||
:min="1"
|
||||
:max="10"
|
||||
label="同时下载最大任务数">
|
||||
:label="$t('preferences.max-concurrent-downloads')">
|
||||
</el-input-number>
|
||||
</el-col>
|
||||
<el-col class="form-item-sub" :span="24">
|
||||
单任务下载的线程数
|
||||
{{ $t('preferences.max-connection-per-server') }}
|
||||
<el-input-number
|
||||
v-model="form.split"
|
||||
controls-position="right"
|
||||
:min="1"
|
||||
:max="form.maxConnectionPerServer"
|
||||
label="单任务下载线程数">
|
||||
:label="$t('preferences.max-connection-per-server')">
|
||||
</el-input-number>
|
||||
</el-col>
|
||||
<el-col class="form-item-sub" :span="24">
|
||||
<el-checkbox v-model="form.continue">
|
||||
断点续传
|
||||
{{ $t('preferences.continue') }}
|
||||
</el-checkbox>
|
||||
</el-col>
|
||||
<el-col class="form-item-sub" :span="24">
|
||||
<el-checkbox v-model="form.newTaskShowDownloading">
|
||||
新建任务后自动跳转到下载页面
|
||||
{{ $t('preferences.new-task-show-downloading') }}
|
||||
</el-checkbox>
|
||||
</el-col>
|
||||
<el-col class="form-item-sub" :span="24">
|
||||
<el-checkbox v-model="form.taskNotification">
|
||||
下载完成后通知
|
||||
{{ $t('preferences.task-completed-notify') }}
|
||||
</el-checkbox>
|
||||
</el-col>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div class="form-actions">
|
||||
<el-button type="primary" @click="submitForm('basicForm')">保存并应用</el-button>
|
||||
<el-button @click="resetForm('basicForm')">放弃</el-button>
|
||||
<el-button type="primary" @click="submitForm('basicForm')">{{ $t('preferences.save') }}</el-button>
|
||||
<el-button @click="resetForm('basicForm')">{{ $t('preferences.discard') }}</el-button>
|
||||
</div>
|
||||
</el-main>
|
||||
</el-container>
|
||||
@@ -77,32 +145,47 @@
|
||||
<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 {
|
||||
dir,
|
||||
split,
|
||||
resumeAllWhenAppLaunched,
|
||||
hideAppMenu,
|
||||
keepWindowState,
|
||||
locale,
|
||||
maxConcurrentDownloads,
|
||||
maxConnectionPerServer,
|
||||
maxOverallUploadLimit,
|
||||
maxOverallDownloadLimit,
|
||||
newTaskShowDownloading,
|
||||
openAtLogin,
|
||||
resumeAllWhenAppLaunched,
|
||||
split,
|
||||
taskNotification,
|
||||
newTaskShowDownloading
|
||||
theme
|
||||
} = config
|
||||
console.log('initialForm===>', dir, split)
|
||||
const result = {
|
||||
dir,
|
||||
split,
|
||||
userAgent: '',
|
||||
referer: '',
|
||||
cookie: '',
|
||||
continue: config.continue,
|
||||
resumeAllWhenAppLaunched,
|
||||
dir,
|
||||
hideAppMenu,
|
||||
keepWindowState,
|
||||
locale,
|
||||
maxConcurrentDownloads,
|
||||
maxConnectionPerServer,
|
||||
maxOverallUploadLimit,
|
||||
maxOverallDownloadLimit,
|
||||
newTaskShowDownloading,
|
||||
openAtLogin,
|
||||
resumeAllWhenAppLaunched,
|
||||
split,
|
||||
taskNotification,
|
||||
newTaskShowDownloading
|
||||
theme
|
||||
}
|
||||
return result
|
||||
}
|
||||
@@ -110,18 +193,27 @@
|
||||
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 '基础设置'
|
||||
return this.$t('preferences.basic')
|
||||
},
|
||||
showHideAppMenuOption: function () {
|
||||
return is.windows() || is.linux()
|
||||
},
|
||||
downloadDir: function () {
|
||||
return prettifyDir(this.form.dir)
|
||||
@@ -133,6 +225,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
|
||||
},
|
||||
@@ -143,9 +274,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)
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||