Compare commits

...

228 Commits

Author SHA1 Message Date
Dr_rOot 1a2de2ad88 Merge pull request #323 from agalwood/feature/prepare_v4_201906201052
chore: prepare for release v1.4.x
2019-06-24 00:08:19 +08:00
Dr_rOot e44587035d fix: bump version to 1.4.1 2019-06-23 22:58:59 +08:00
Dr_rOot 43f1adcd84 fix: electron 5.0 performance is low revert to 424 2019-06-22 20:46:29 +08:00
Dr_rOot 8a900459b5 refactor: auto copy changed rpc secret 2019-06-22 20:39:38 +08:00
Dr_rOot d2f8a4599d feat: auto copy rpc url when dice changed secret 2019-06-21 15:46:11 +08:00
Dr_rOot 9159fb36ab chore: bump version to v1.4.0 2019-06-21 14:58:07 +08:00
Dr_rOot 5be05395f5 chore: upgrade dependencies 2019-06-21 14:57:45 +08:00
Dr_rOot fba76df945 fix: change app update url 2019-06-21 14:43:32 +08:00
Dr_rOot 5bb4f259cd fix: set bt-detach-seed-only=true 2019-06-21 14:42:57 +08:00
Dr_rOot 4b093a1a4f fix: increase the number of egine process restarts 2019-06-21 14:41:28 +08:00
Dr_rOot 134d81b205 fix: task stop seeding 2019-06-21 14:40:15 +08:00
Dr_rOot d5fc962bca fix: change task option 2019-06-21 14:40:04 +08:00
Dr_rOot 0d1b76d523 fix: checkTaskIsSeeder util 2019-06-20 15:28:28 +08:00
Dr_rOot 6499ce91b8 refactor: update element ui theme chalk 2019-06-20 11:49:30 +08:00
Dr_rOot 2d7907f218 chore: improve npm scripts postinstall 2019-06-20 11:19:54 +08:00
Dr_rOot 97c1cb0418 fix: change rpc doc link 2019-06-20 11:17:16 +08:00
Dr_rOot be585ef102 refactor: move bt-tracker config manager to file 2019-06-20 10:57:48 +08:00
Dr_rOot 03002221e7 Merge branch 'feature/task_list_201905312258'
# Conflicts:
#	src/renderer/components/Native/EngineClient.vue
2019-06-20 10:38:58 +08:00
Dr_rOot 4b8de781a4 refactor: improve download error message #293 2019-06-19 19:56:07 +08:00
Dr_rOot 8224423d8c refactor: update panel content padding 2019-06-18 21:07:14 +08:00
Dr_rOot 1e05e1e4f3 fix: element ui message link color 2019-06-17 20:27:51 +08:00
Dr_rOot bfd1e1b6fa refactor: removeTaskRecord error complete removed 2019-06-17 19:48:57 +08:00
Dr_rOot 79495cbde5 refactor: auto save session when save preferences 2019-06-16 23:44:49 +08:00
Dr_rOot a93d183988 fix: disabled certificate check 2019-06-16 21:30:24 +08:00
Dr_rOot cf082b20d8 fix: there is no localhost rule in /etc/hosts 2019-06-15 23:09:29 +08:00
Dr_rOot 8110409402 feat: empty addTaskOptions when task added 2019-06-15 20:23:11 +08:00
Dr_rOot 37b446b6f0 feat: add parseHeader util for restart task dialog 2019-06-14 23:22:36 +08:00
Dr_rOot 97801fff32 refactor: task item actions restart & stop seeding 2019-06-14 22:38:07 +08:00
Dr_rOot 4ca4f5e606 refactor: add task to support addTaskOptions 2019-06-14 22:20:34 +08:00
Dr_rOot e9caf1b0b4 refactor: transform add options keys to kebab case 2019-06-13 20:47:33 +08:00
Dr_rOot d67dca00b5 feat: add getTaskOption action 2019-06-12 20:36:03 +08:00
Dr_rOot 038536259e feat: add task restart icon 2019-06-12 20:34:22 +08:00
Dr_rOot ec21cc57bc Merge pull request #314 from theweavrs/patch-1
Add code of conduct for the project
2019-06-12 17:03:28 +08:00
thecodrr 5a42e4014b chore: add code of conduct for the project 2019-06-12 00:15:04 -07:00
Dr_rOot 02d4032283 fix: form-actions background color 2019-06-12 09:28:40 +08:00
Dr_rOot d676dbe213 Merge pull request #309 from theweavrs/master
feat: add background under form-actions in preferences
2019-06-12 09:08:31 +08:00
Dr_rOot 02d83534d1 refactor: check is exist before delete task file 2019-06-11 22:41:07 +08:00
Dr_rOot 8b531c76b8 fix: eslint no-empty-pattern error 2019-06-10 19:35:45 +08:00
Dr_rOot 22f92df299 fix: remove useless lab form.protocols key 2019-06-10 19:02:21 +08:00
Dr_rOot eaf91e15e8 Merge pull request #310 from agalwood/feature/protocol_setting_201906091107
feat: add option to set default protocols client #305
2019-06-10 18:34:05 +08:00
Dr_rOot 4535cb706a fix: setup-protocols-client command logic 2019-06-10 17:47:51 +08:00
Dr_rOot a093f3beca chore: i18n tw protocols 2019-06-10 17:47:12 +08:00
Dr_rOot 287c9038b1 Merge pull request #308 from theweavrs/feature/protocol_setting_201906091107
feat: add option to set default protocols
2019-06-10 17:12:25 +08:00
thecodrr 72aac9c0c6 feat: add background under form-actions 2019-06-10 01:48:20 -07:00
thecodrr cdac88371c refactor: remove change handlers 2019-06-10 00:52:30 -07:00
thecodrr 678bed53dc feat: add localization for protocol settings 2019-06-10 00:49:33 -07:00
thecodrr 1f805c3440 refactor: remove unused localized string 2019-06-10 00:22:31 -07:00
thecodrr c8dcb1023f feat: handle setup-protocols-client command 2019-06-10 00:17:55 -07:00
thecodrr a0e7eb84ca feat: send setup-protocols-client command 2019-06-10 00:03:10 -07:00
thecodrr fdf0bb8320 refactor: move protocol settings to advanced menu 2019-06-09 22:53:21 -07:00
thecodrr e2338757de refactor: merge magnet & thunder protocol handling 2019-06-09 22:07:09 -07:00
Dr_rOot c0cbfb5cb2 refactor: preferences lab protocol client settings 2019-06-10 11:35:48 +08:00
Dr_rOot fddaa72ac6 refactor: protocol manager init setup 2019-06-09 23:09:59 +08:00
Dr_rOot 1e531f166b feat: handle thunder protocol 2019-06-09 23:09:27 +08:00
Dr_rOot 97f54adc49 feat: add protocols to user config 2019-06-09 23:08:30 +08:00
Dr_rOot e6548d5548 Merge pull request #306 from agalwood/feature/rpc_secret_201906071137
feat: preferences add rpc-secret setting #138
2019-06-09 22:54:58 +08:00
Dr_rOot 137fdf2b26 fix: npm add randomatic deps 2019-06-09 22:35:41 +08:00
Dr_rOot 10ef4f7318 feat: i18n preferences rpc-secret 2019-06-09 22:33:52 +08:00
Dr_rOot bdd5e56c2a feat: preferences add rpc-secret setting 2019-06-08 22:53:13 +08:00
Dr_rOot a851ec5830 feat: add icon dice 2019-06-08 22:52:20 +08:00
Dr_rOot 2ae3642c62 feat: add check task is seeder util 2019-06-07 20:07:20 +08:00
Dr_rOot 7b41a9e1c6 docs: update scoop install guide 2019-06-06 12:38:29 +08:00
Dr_rOot 012e5ece00 Merge pull request #301 from agalwood/feature/preferences_layout_201905301105
refactor: improve preferences layout
2019-06-05 14:59:09 +08:00
Dr_rOot db8d3d1aec feat: i18n check for update messages 2019-06-05 12:27:18 +08:00
Dr_rOot 8e35dde082 feat: add check for update at preference #298
closed #298
2019-06-05 12:26:45 +08:00
Dr_rOot 5c2acf12ee feat: tray menu support disable item 2019-06-05 11:58:23 +08:00
Dr_rOot e710ccdb34 feat: add check for update to tray menu 2019-06-05 11:57:37 +08:00
Dr_rOot 5ecc08d66d refactor: clean up useless code 2019-06-04 22:05:51 +08:00
Dr_rOot e7b1a898fa feat: add task history icon 2019-06-03 19:57:04 +08:00
Dr_rOot 93acf1ba63 fix: el-progress status value for element ui 2.9.x 2019-06-02 02:22:13 +08:00
Dr_rOot 1164df87e3 fix: i18n add missing ja translates 2019-06-01 11:47:24 +08:00
Dr_rOot 71232603a5 refactor: rename updateStates to updateMenuStates 2019-06-01 11:44:55 +08:00
Dr_rOot 286013b2f0 refacotr: clean code 2019-05-31 19:42:53 +08:00
Dr_rOot a5f0236661 Merge pull request #296 from agalwood/feature/transfer_speed_setting_201905282346
fix: refactor transfer settings
2019-05-30 20:44:28 +08:00
Dr_rOot 87857eba94 refactor: move auto update setting to advanced 2019-05-30 20:43:45 +08:00
Dr_rOot 3059d1a0b7 refactor: preferences move mock ua to security 2019-05-30 15:47:45 +08:00
Dr_rOot 5029f98153 refactor: preferences form item style 2019-05-30 14:25:56 +08:00
Dr_rOot ce80af7ead fix: increase mo-speedometer z-index 2019-05-30 14:25:23 +08:00
Dr_rOot 6df52988b0 chore: i18n preferences transfer settings 2019-05-29 23:57:42 +08:00
Dr_rOot 6359c729ad fix: horizontal scrollbar height 2019-05-29 23:53:58 +08:00
Dr_rOot 04db94daa6 refactor: i18n calc form label width with locale 2019-05-29 23:50:33 +08:00
Dr_rOot d41d809f00 Merge pull request #295 from gee1k/master
fix: remove the button in the aside panel to drag and drop
2019-05-29 15:25:37 +08:00
Svend efdfae37b5 fix:remove the button in the aside panel to drag and drop 2019-05-29 14:01:30 +08:00
Dr_rOot 1a097c02e3 refactor: transfer speed setting to select options 2019-05-28 23:51:34 +08:00
Dr_rOot 0217b5573a fix: theme merge code error 2019-05-28 23:27:57 +08:00
Dr_rOot 86611604d5 Merge branch 'feature/theme_switch_lang_201905232132'
# Conflicts:
#	src/renderer/components/Preference/Advanced.vue
#	src/renderer/components/Preference/Basic.vue
2019-05-28 23:24:08 +08:00
Dr_rOot 0bd8ab3d11 Merge pull request #294 from agalwood/feature/change_global_options_201905241932
feat: change preferences without relaunch app #24 #172 #197
2019-05-28 23:14:05 +08:00
Dr_rOot e9eadb2eba feat: i18n save preferences message 2019-05-28 22:52:50 +08:00
Dr_rOot 72afd845ba fix: default enable check auto update on macOS 2019-05-28 22:35:19 +08:00
Gary Li 91670fac37 fix: updated en-US translation (#290)
* Updated translation: en-US

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

* Update subnav.js

* Update task.js

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

* Remove extra spaces

* Layout revision
2019-04-30 20:16:41 +08:00
Dr_rOot e07ef7dd11 fix: bump version to 1.3.8 2019-04-29 09:49:33 +08:00
Dr_rOot ebb0926d1d fix: preference basic & lab save config fail #128
v1.3.7 btTracker.trim()
2019-04-29 09:46:52 +08:00
Dr_rOot 5333438825 fix: screenshots file name add @2x suffix 2019-04-28 17:23:28 +08:00
Dr_rOot 3664341822 docs: add app screenshots 2019-04-28 17:21:32 +08:00
Dr_rOot d3cbff6dd1 fix: preference link color in dark mode 2019-04-28 17:08:51 +08:00
Dr_rOot 5668b0773d fix: sync icon stroke color 2019-04-28 12:41:25 +08:00
Dr_rOot b757112c30 fix: update readme supported languages 2019-04-28 11:49:32 +08:00
Dr_rOot dc3a01fb3c fix: bump version to 1.3.7 2019-04-27 22:02:23 +08:00
KOZ39 591de11072 fix: Improve Korean translation (#238) 2019-04-27 21:54:46 +08:00
Dr_rOot 853c361e6d fix: bump version to 1.3.6 2019-04-27 19:37:35 +08:00
Dr_rOot b0ebc737e2 fix: i18n rename and add some keys 2019-04-27 19:36:02 +08:00
Dr_rOot bc9db25dfd fix: position of the proxy input is incorrect 2019-04-27 19:34:52 +08:00
KOZ39 08c9902759 feat: Add Korean translation (#236)
* Add Korean translation

* Fix typo
2019-04-27 18:31:10 +08:00
Nima Rasooli 8d2f52710d feat: Translated to Persian/Farsi (#235)
* Create about.js

* Rename src/shared/locales/fa-IR/about.js to src/shared/locales/fa/about.js

* Translated

* Translated

* Translated

* Translated

* Create index.js

* Translated

* Translated

* Translated

* translated

* Translated
2019-04-27 18:30:46 +08:00
Dr_rOot dd48fd79db fix: bump version to 1.3.5 2019-04-27 18:03:34 +08:00
Dr_rOot 9e7ad10309 Merge branch 'feature/bt_experience_opt_201904262310' 2019-04-27 18:02:19 +08:00
Dr_rOot 6e17102210 fix: mo protocol new-bt-task 2019-04-27 17:54:27 +08:00
Dr_rOot 5ed3b68a57 fix: handle launch argv 2019-04-27 17:31:56 +08:00
Dr_rOot a705427ce2 fix: open url in win & linux 2019-04-27 15:55:34 +08:00
Dr_rOot c37f7c73ce doc: change Manual link to github wiki 2019-04-27 12:14:05 +08:00
Dr_rOot efcf01d59b feat: handle magnet protocol 2019-04-27 12:13:02 +08:00
Dr_rOot 324b61567d fix: typo tip => tips 2019-04-26 23:57:55 +08:00
Dr_rOot d1e24fced0 fix: i18n preference bt tracker 2019-04-26 23:57:11 +08:00
Dr_rOot cb50d6709b feat: sync bt tracker from github
An updated list of public BitTorrent trackers
https://github.com/ngosang/trackerslist
2019-04-26 23:27:23 +08:00
Dr_rOot 900ac4c8d1 feat: preference advanced add bt tracker input 2019-04-26 23:19:10 +08:00
Dr_rOot 6696b0a538 refactor: move bt-tracker to config manager 2019-04-26 23:15:01 +08:00
Dr_rOot 110c83bcb8 fix: dht config optimization 2019-04-26 23:13:58 +08:00
Dr_rOot 136b4969bd chore: update readme
Add dark mode
2019-04-25 15:45:58 +08:00
weearc a901fdd2b6 docs: readme added linux installation guide (#228)
* add aur helper command in readme

* add aur helper command in readme
2019-04-25 15:07:56 +08:00
Dr_rOot 215bdbc416 fix: prepare for electron 5.0.x
Set webPreferences nodeIntegration to true
2019-04-25 10:53:25 +08:00
Dr_rOot 4e8d226460 fix: optimized for small screen users #48 #224 2019-04-25 10:16:16 +08:00
Dr_rOot 23f3357d27 fix: bump version to 1.3.3 2019-04-24 21:56:38 +08:00
Dr_rOot 7adafab7fe fix: i18n ja 2019-04-24 21:55:39 +08:00
HBKRKZK e3afb14e4a feat: Add Japanese language translation (#225)
* Create darwin.json

* Add files via upload

* Create about.js

* Add files via upload

* Update all.js

* Update app.js

* Update index.js

* fix

* fix preferences.js
2019-04-24 21:48:41 +08:00
Dr_rOot 228cd1fa7d fix: bump version to 1.3.2 2019-04-24 16:29:41 +08:00
Dr_rOot 47a7464bf9 doc: update translation guide 2019-04-24 16:28:04 +08:00
Dr_rOot 8efd86af3d feat: remove similar unuse config key 2019-04-24 11:35:31 +08:00
Dr_rOot ee78ae262e fix: bump version v1.3.1 2019-04-24 10:22:52 +08:00
Dr_rOot 7562f6a7d1 fix: downgrade electron-store 2019-04-24 10:22:32 +08:00
Dr_rOot defacd50e8 fix: build script —experimental-worker 2019-04-24 00:06:01 +08:00
Dr_rOot f5ed0d89cc fix: bump version to 1.3.x 2019-04-23 23:47:14 +08:00
Dr_rOot a3bb1c7ff1 Merge branch 'feature/dark_mode_201904201437' 2019-04-23 23:43:04 +08:00
Dr_rOot d0085b295d refactor: bt complete show seeding tips #179 #134 2019-04-23 23:40:01 +08:00
Dr_rOot 6cb278fd9e chore: manual update tracker 2019-04-23 18:14:50 +08:00
Dr_rOot aeeb9813ed refactor: lint fix 2019-04-23 17:56:45 +08:00
Dr_rOot 359da895e7 fix: migration electron-log from v2 to v3 2019-04-23 17:23:52 +08:00
Dr_rOot 8a3f510c38 refactor: check pid is running before kill it 2019-04-23 17:22:57 +08:00
Dr_rOot 1345994402 feat: add config schema valid 2019-04-23 17:21:56 +08:00
Dr_rOot c770108a6c refactor: electron store sort config key 2019-04-23 17:21:27 +08:00
Dr_rOot 178f2103f1 fix: destroy tray when app exit 2019-04-23 10:41:11 +08:00
Dr_rOot d5c71b50e8 fix: change application module init order 2019-04-23 10:40:30 +08:00
Dr_rOot ce09e76ffb refactor: theme style improve 2019-04-23 10:38:44 +08:00
Dr_rOot 4688b521fa fix: trim after split task link text rows #221
closed #221
2019-04-22 20:52:33 +08:00
Dr_rOot 33960dc264 fix: decrease theme switcher theme text font size 2019-04-22 20:49:15 +08:00
Dr_rOot d29d0d3321 fix: add task dialog background color 2019-04-22 15:42:09 +08:00
Dr_rOot f4c3e7be69 feat: preference advanced add theme switcher 2019-04-22 15:17:48 +08:00
Dr_rOot 338b51975c feat: preference theme switcher i18n 2019-04-22 15:17:23 +08:00
Dr_rOot e1a287350d feat: add preference theme thumb 2019-04-22 15:16:53 +08:00
Dr_rOot afeee1fc86 fix: change theme config 2019-04-21 23:42:52 +08:00
Dr_rOot a39820f8ab refactor: app style 2019-04-21 22:29:27 +08:00
Dr_rOot aea604278c fix: copyright link color 2019-04-21 22:28:40 +08:00
Dr_rOot 0b92e6b96e fix: app info style 2019-04-21 22:28:19 +08:00
Dr_rOot 2ba0040bdd feat: title bar close btn 2019-04-21 22:28:01 +08:00
Dr_rOot 04bf2b6b9d fix: logo svg fill 2019-04-21 22:26:36 +08:00
Dr_rOot cc367f4c5c fix: change show in folder icon size 2019-04-21 22:24:40 +08:00
Dr_rOot 73809a6501 refactor: app theme & add dark theme 2019-04-21 22:23:39 +08:00
Dr_rOot 5067ad7306 feat: app ui respond to system theme changed 2019-04-21 21:36:37 +08:00
Dr_rOot d5b3ad5933 fix: tray icon adapts to the dark mode #206 #207
close #206
close #207
2019-04-21 21:30:07 +08:00
Dr_rOot 7a903e112c feat: theme manager 2019-04-21 21:26:45 +08:00
Dr_rOot 829eb83a4c fix: get task full path error when file path empty 2019-04-17 15:45:20 +08:00
Dr_rOot 886acc2486 fix: thunder link decoding error #201 #209
close #209
2019-04-17 15:12:13 +08:00
Dr_rOot 87440701ab fix: block some ui text selections when drag app 2019-04-10 21:11:10 +08:00
Dr_rOot e880430e3a fix: html tpl 2019-04-07 13:31:00 +08:00
Dr_rOot 2febff883b fix: add missing auto check update TR translation 2019-04-01 21:11:07 +08:00
Bo Yuan 7d8bb443b8 feat: preferences add auto check updates setting
* add auto check update, time out: 1 min.

* fix: add auto check update, check interval: 30 min.

* no message

* no message

* fix: modify update check interval to 7 days, add display of [last check update time] to preference.

* no message

* fix: modified the group of auto-check-update, fix the display of last-check-update-time

* no message

* fix: hide last-check-update-time when never check.
2019-04-01 20:55:30 +08:00
Dr_rOot 1489d58a63 chore: rebuild snap for snapcraft 2019-03-31 11:45:54 +08:00
Dr_rOot 00fdafe824 fix: upgrade electron-builder 2019-03-30 11:46:55 +08:00
Dr_rOot 81c91e8cb1 fix: update readme badges 2019-03-28 22:58:28 +08:00
Dr_rOot 0fd34254de chore: add latest release badge 2019-03-26 20:58:22 +08:00
Dr_rOot d9298f0d46 fix: read me typo 2019-03-25 00:15:34 +08:00
Dr_rOot 83e12a699e docs: update readme add new feature 2019-03-24 21:54:28 +08:00
170 changed files with 8099 additions and 6312 deletions
+1
View File
@@ -11,6 +11,7 @@ assignees: ''
Before you feedback, please search for the issues to see if there are similar problems that can solve your problem.
**Please delete the above and the contents of this line, then fill in the feedback form in the following format, Thanks.**
<!-- Windows and Linux versions hide the application menu by default. Please close the "Hide Menu Bar" in Preferences - Advanced Settings - Appearance. After saving and applying, the menu bar will appear. -->
**Describe the bug**
A clear and concise description of what the bug is.
+8 -5
View File
@@ -6,12 +6,14 @@ labels: ''
assignees: ''
---
**反馈之前**
<!--
反馈之前请搜索一下已有 issues 和 帮助文档,看是否有类似问题可以解决你的问题
https://github.com/agalwood/Motrix/issues
http://motrix.app/support
**请删除上面和本行的内容,然后按以下格式填写反馈信息,谢谢**
按以下格式填写反馈信息,谢谢
-->
**错误描述**
清楚简洁地描述错误,方便我们复现之后处理。
@@ -24,11 +26,12 @@ http://motrix.app/support
4. 发现报错
**预期的行为**
清楚简洁地描述期望发生的事情。
清楚简洁地描述期望发生的事情。
**截图**
请添加屏幕截图以帮助解释的问题:
请添加屏幕截图以帮助解释的问题:
打开应用菜单中的「帮助」——「开发者工具」—— 切换到 console,然后**完整**截图。
<!-- Windows 和 Linux 版本默认隐藏了应用菜单,请到偏好设置-进阶设置-外观里关闭“隐藏菜单栏”,保存并应用之后菜单栏就出现了 -->
**运行环境**
- 操作系统类型: [如 macOS, Windows, Linux]
@@ -37,4 +40,4 @@ http://motrix.app/support
- 安装包类型:[如 dmg, AppImage]
**更多信息**
添加有关问题的任何其他上下文信息。
补充有关问题的其他信息。
@@ -0,0 +1,28 @@
---
name: 新功能请求
about: 你期望 Motrix 未来添加的新功能
title: ''
labels: enhancement ✨
assignees: ''
---
<!--
反馈之前请搜索一下已有 issues 和 帮助文档,看是否已经有人提交了类似的新功能请求
https://github.com/agalwood/Motrix/issues
http://motrix.app/support
按以下格式填写反馈信息,谢谢
-->
**请描述一下你的新功能请求是否与已知问题有关?**
简明扼要地描述了问题所在。
**描述你想要的解决方案**
简明扼要地描述你想要的解决方案。
**描述你考虑过的替代方案**
简明扼要地描述你考虑过的任何替代解决方案或功能。
**更多信息**
补充有关该新功能的其他信息。
+1
View File
@@ -9,3 +9,4 @@ npm-debug.log.*
thumbs.db
!.gitkeep
release/*
.idea/
+76
View File
@@ -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
+15 -13
View File
@@ -1,30 +1,32 @@
# Motrix 贡献指南
## 🌍 翻译指南
首先你要确定一个语言的英文简写作为 **locale**,如 en-US,这个 locale 值请严格参考 [Electron 的 Locales 文档](https://electronjs.org/docs/api/locales)
Motrix 的国际化分部分:
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'
```
### 应用菜单
应用菜单的国际化文件按照语言进行目录划分,每个目录里有三大操作系统对应的 JSON 文件:
- darwin.json
- linux.json
- win32.json
### 菜单和主界面
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` 里的配置。
### 主界面
主界面和 Element UI 都是用 i18next 作为翻译支持库,所以你可能需要简单了解一下它的[使用方法](https://www.i18next.com/overview/getting-started)。
主界面的配置同样按照语言划分目录:`src/shared/locales`,如:`src/shared/locales/en-US``src/shared/locales/zh-CN`
目录里面有按业务模块划分的语言文件:
- about.js
- app.js
- edit.js
+15 -13
View File
@@ -1,30 +1,32 @@
# 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 three parts:
The internationalization of Motrix is divided into two parts:
- Element UI
- Application Menu
- Main Interface
- 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'
```
### Application Menu
The internationalization files of the application menu are divided into directories according to the **locale**. Each directory has three JSON files corresponding to the OS:
- darwin.json
- linux.json
- win32.json
### 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`.
### Main Interface
Both the main interface and the Element UI use [i18next](https://www.i18next.com/overview/getting-started) as the translation support library, so you may need to take a brief look at how to use it.
The configuration of the main interface is also divided into directories according to the **locale**: `src/shared/locales`, such as: `src/shared/locales/en-US` and `src/shared/locales/zh-CN`.
There are language files in the directory divided by business modules:
- about.js
- app.js
- edit.js
+60 -11
View File
@@ -7,24 +7,54 @@
[English](./README.md) | 简体中文
## 一款全能的下载工具
[![Build Status](https://travis-ci.org/agalwood/Motrix.svg?branch=master)](https://travis-ci.org/agalwood/Motrix) [![Build status](https://ci.appveyor.com/api/projects/status/l11d5h05xwwcvoux/branch/master?svg=true)](https://ci.appveyor.com/project/agalwood/motrix/branch/master) ![Total Downloads](https://img.shields.io/github/downloads/agalwood/Motrix/total.svg) ![Support Platforms](https://camo.githubusercontent.com/a50c47295f350646d08f2e1ccd797ceca3840e52/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f706c6174666f726d2d6d61634f5325323025374325323057696e646f77732532302537432532304c696e75782d6c69676874677265792e737667)
[![GitHub release](https://img.shields.io/github/release/agalwood/Motrix.svg)](https://github.com/agalwood/Motrix/releases) [![Build Status](https://travis-ci.org/agalwood/Motrix.svg?branch=master)](https://travis-ci.org/agalwood/Motrix) [![Build status](https://ci.appveyor.com/api/projects/status/l11d5h05xwwcvoux/branch/master?svg=true)](https://ci.appveyor.com/project/agalwood/motrix/branch/master) [![Total Downloads](https://img.shields.io/github/downloads/agalwood/Motrix/total.svg)](https://github.com/agalwood/Motrix/releases) ![Support Platforms](https://camo.githubusercontent.com/a50c47295f350646d08f2e1ccd797ceca3840e52/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f706c6174666f726d2d6d61634f5325323025374325323057696e646f77732532302537432532304c696e75782d6c69676874677265792e737667)
我是个兴趣使然的桌面应用开发者🤓,利用搬砖之余开发了 Motrix。
Motirx 是一款全能的下载工具,支持下载 HTTP、FTP、BT、磁力链、百度网盘等资源。它的界面简洁易用,希望大家喜欢 👻。
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) 提供了已经编译好的稳定版安装包,当然你也可以自己克隆代码编译打包。
更新:macOS 用户支持 `brew cask` 安装,感谢 [Mitscherlich](https://github.com/Mitscherlich) 的 [PR](https://github.com/Homebrew/homebrew-cask/pull/59494)。
### 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和磁力链任务
- 💾 支持下载百度云盘资源
@@ -32,68 +62,87 @@ brew update && brew cask install motrix
- 🚀 单任务最高支持 64 线程下载
- 🕶 模拟用户代理UA
- 🔔 下载完成后通知
- 💻 支持触控栏快捷 (Mac 专享)
- 💻 支持触控栏快捷 (Mac 专享)
- 🤖 常驻系统托盘,操作更加便捷
- 🌑 深色模式
- 🗑 移除任务时可同时删除相关文件
- 🌍 国际化,[查看已可选的语言](#-国际化)
- 🎏 ...
## 🖥 应用界面
![motrix-screenshot-task-cn.png](https://cdn.nlark.com/yuque/0/2019/png/129147/1550151234585-e513bd4f-e127-402f-accb-1ebbba9b3c41.png)
## ⌨️ 本地开发
### 克隆代码
```bash
git clone git@github.com:agalwood/Motrix.git
```
### 安装依赖
```bash
cd Motrix
npm install
```
天朝大陆用户建议使用淘宝的npm源
天朝大陆用户建议使用淘宝的 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 位的 aria2cWindows 版使用的 32 位的)
## ☑️ TODO
开发计划请移步 [Trello](https://trello.com/b/qNUzA0bv/motrix) 查看
## 🤝 参与共建 [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](http://makeapullrequest.com)
## 🤝 参与共建 [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat)](http://makeapullrequest.com)
如果你有兴趣参与共同开发,欢迎 FORK 和 PR。
## 🌍 国际化
欢迎大家将 Motrix 翻译成更多的语言版本 🧐,开工之前请先阅读一下 [翻译指南](./CONTRIBUTING-CN.md#-翻译指南)。
| Key | Name | Status |
|-------|:--------------------|:-------------|
| de | German | 下版本发布 [@Schloemicher](https://github.com/Schloemicher) |
| de | German | ✔️ [@Schloemicher](https://github.com/Schloemicher) |
| en-US | English | ✔️ |
| fr | Français | 下版本发布 [@gpatarin](https://github.com/gpatarin) |
| pt-BR | Portuguese (Brazil) | 下版本发布 [@andrenoberto](https://github.com/andrenoberto) |
| tr | Türkçe | 下版本发布 [@abdullah](https://github.com/abdullah) |
| 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) |
| zh-TW | 繁體中文 | ✔️ [@Yukaii](https://github.com/Yukaii) |
## 📜 开源许可
基于 [MIT license](https://opensource.org/licenses/MIT) 许可进行开源。
+57 -10
View File
@@ -4,8 +4,9 @@
<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.
[![Build Status](https://travis-ci.org/agalwood/Motrix.svg?branch=master)](https://travis-ci.org/agalwood/Motrix) [![Build status](https://ci.appveyor.com/api/projects/status/l11d5h05xwwcvoux/branch/master?svg=true)](https://ci.appveyor.com/project/agalwood/motrix/branch/master) ![Total Downloads](https://img.shields.io/github/downloads/agalwood/Motrix/total.svg) ![Support Platforms](https://camo.githubusercontent.com/a50c47295f350646d08f2e1ccd797ceca3840e52/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f706c6174666f726d2d6d61634f5325323025374325323057696e646f77732532302537432532304c696e75782d6c69676874677265792e737667)
## A full-featured download manager
[![GitHub release](https://img.shields.io/github/release/agalwood/Motrix.svg)](https://github.com/agalwood/Motrix/releases) [![Build Status](https://travis-ci.org/agalwood/Motrix.svg?branch=master)](https://travis-ci.org/agalwood/Motrix) [![Build status](https://ci.appveyor.com/api/projects/status/l11d5h05xwwcvoux/branch/master?svg=true)](https://ci.appveyor.com/project/agalwood/motrix/branch/master) [![Total Downloads](https://img.shields.io/github/downloads/agalwood/Motrix/total.svg)](https://github.com/agalwood/Motrix/releases) ![Support Platforms](https://camo.githubusercontent.com/a50c47295f350646d08f2e1ccd797ceca3840e52/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f706c6174666f726d2d6d61634f5325323025374325323057696e646f77732532302537432532304c696e75782d6c69676874677265792e737667)
English | [简体中文](./README-CN.md)
@@ -13,18 +14,47 @@ Motrix is a full-featured download manager that supports downloading HTTP, FTP,
Motrix has a clean and easy to use interface. I hope you will like it 👻.
✈️ [Official Website](https://motrix.app) | 📖 [Manual](http://motrix.app/support/issues) (zh-CN)
✈️ [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.
Update: macOS user support `brew cask` installation, thanks to [PR](https://github.com/Homebrew/homebrew-cask/pull/59494) of [Mitscherlich](https://github.com/Mitscherlich).
### 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
@@ -33,61 +63,78 @@ brew update && brew cask install motrix
- 🕶 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
![motrix-screenshot-task-en.png](https://cdn.nlark.com/yuque/0/2019/png/129147/1550151166169-94b4bfb0-746e-42b8-aad7-0b6890f89abb.png)
## ⌨️ Development
### Clone Code
```bash
git clone git@github.com:agalwood/Motrix.git
```
### Install Dependencies
```bash
cd Motrix
npm install
```
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
```
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
Development Roadmap see: [Trello](https://trello.com/b/qNUzA0bv/motrix)
## 🤝 Contribute [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](http://makeapullrequest.com)
## 🤝 Contribute [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat)](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 | Next Release [@Schloemicher](https://github.com/Schloemicher) |
| de | German | ✔️ [@Schloemicher](https://github.com/Schloemicher) |
| en-US | English | ✔️ |
| fr | Français | Next Release [@gpatarin](https://github.com/gpatarin) |
| pt-BR | Portuguese (Brazil) | Next Release [@andrenoberto](https://github.com/andrenoberto) |
| tr | Türkçe | Next Release [@abdullah](https://github.com/abdullah) |
| 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 | 繁體中文 | Next Release [@Yukaii](https://github.com/Yukaii) |
| zh-TW | 繁體中文 | ✔️ [@Yukaii](https://github.com/Yukaii) |
## 📜 License
[MIT](https://opensource.org/licenses/MIT) Copyright (c) 2018-present Dr_rOot
+1 -1
View File
@@ -1,2 +1,2 @@
provider: generic
url: 'https://motrix.app/release/'
url: 'https://dl.motrix.app/release/'
+23 -9
View File
@@ -73,15 +73,19 @@ 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=true
# 种子交换, PT需要禁用, 默认:true
@@ -101,8 +105,18 @@ seed-ratio=1.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
+23 -9
View File
@@ -73,15 +73,19 @@ 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=true
# 种子交换, PT需要禁用, 默认:true
@@ -101,8 +105,18 @@ seed-ratio=1.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
+23 -9
View File
@@ -73,15 +73,19 @@ 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=true
# 种子交换, PT需要禁用, 默认:true
@@ -101,8 +105,18 @@ seed-ratio=1.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
+3040 -5106
View File
File diff suppressed because it is too large Load Diff
+56 -43
View File
@@ -1,6 +1,6 @@
{
"name": "Motrix",
"version": "1.2.2",
"version": "1.4.1",
"description": "A full-featured download manager",
"homepage": "https://motrix.app",
"author": {
@@ -27,7 +27,7 @@
"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",
@@ -47,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,
@@ -83,16 +104,7 @@
"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": [
@@ -148,7 +160,7 @@
"publish": [
{
"provider": "generic",
"url": "https://motrix.app/release/"
"url": "https://dl.motrix.app/release/"
},
{
"provider": "github"
@@ -156,72 +168,73 @@
]
},
"dependencies": {
"@panter/vue-i18next": "^0.15.0",
"@panter/vue-i18next": "^0.15.1",
"aria2": "^4.0.3",
"axios": "^0.18.0",
"axios": "^0.19.0",
"blob-util": "^2.0.2",
"clipboard-polyfill": "^2.7.0",
"electron-debug": "^2.1.0",
"clipboard-polyfill": "^2.8.1",
"electron-debug": "^3.0.0",
"electron-is": "^3.0.0",
"electron-log": "^2.2.17",
"electron-updater": "^4.0.8",
"element-ui": "^2.6.2",
"electron-log": "^3.0.6",
"electron-updater": "^4.0.12",
"element-ui": "^2.9.1",
"forever-monitor": "^1.7.1",
"i18next": "^15.0.6",
"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.6.10",
"vue-electron": "^1.0.6",
"vue-router": "^3.0.2",
"vuex": "^3.1.0",
"vue-router": "^3.0.6",
"vuex": "^3.1.1",
"vuex-router-sync": "^5.0.0"
},
"devDependencies": {
"@vue/cli-plugin-babel": "^3.5.1",
"@vue/cli-plugin-eslint": "^3.5.1",
"@vue/cli-service": "^3.5.1",
"@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.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.4.2",
"cfonts": "^2.4.3",
"chalk": "^2.4.2",
"copy-webpack-plugin": "^5.0.1",
"copy-webpack-plugin": "^5.0.3",
"cross-env": "^5.1.6",
"css-loader": "^2.1.1",
"del": "^4.0.0",
"del": "^4.1.1",
"devtron": "^1.4.0",
"electron": "^4.1.1",
"electron-builder": "^20.38.5",
"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.15.3",
"eslint": "^5.16.0",
"eslint-config-standard": "^12.0.0",
"eslint-friendly-formatter": "^4.0.1",
"eslint-loader": "^2.1.2",
"eslint-plugin-html": "^4.0.6",
"eslint-plugin-import": "^2.16.0",
"eslint-plugin-import": "^2.17.3",
"eslint-plugin-node": "^8.0.1",
"eslint-plugin-promise": "^4.0.1",
"eslint-plugin-promise": "^4.1.1",
"eslint-plugin-standard": "^4.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",
@@ -230,10 +243,10 @@
"vue-loader": "^15.7.0",
"vue-style-loader": "^4.1.2",
"vue-template-compiler": "^2.6.10",
"webpack": "^4.29.6",
"webpack-cli": "^3.2.3",
"webpack-dev-server": "^3.2.1",
"webpack-hot-middleware": "^2.24.3",
"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"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

+5 -3
View File
@@ -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>
+122 -17
View File
@@ -8,6 +8,7 @@ import logger from './core/Logger'
import ConfigManager from './core/ConfigManager'
import { setupLocaleManager } from '@/ui/Locale'
import Engine from './core/Engine'
import AutoLaunchManager from './core/AutoLaunchManager'
import UpdateManager from './core/UpdateManager'
import EnergyManager from './core/EnergyManager'
import ProtocolManager from './core/ProtocolManager'
@@ -15,6 +16,8 @@ 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 () {
@@ -30,9 +33,12 @@ export default class Application extends EventEmitter {
this.localeManager = setupLocaleManager(this.locale)
this.i18n = this.localeManager.getI18n()
this.windowManager = new WindowManager({
userConfig: this.configManager.getUserConfig()
})
this.menuManager = new MenuManager()
this.menuManager.setup(this.locale)
this.initTouchBarManager()
this.initWindowManager()
this.engine = new Engine({
systemConfig: this.configManager.getSystemConfig(),
@@ -40,13 +46,12 @@ export default class Application extends EventEmitter {
})
this.startEngine()
this.menuManager = new MenuManager()
this.menuManager.setup(this.locale)
this.touchBarManager = new TouchBarManager()
this.trayManager = new TrayManager()
this.autoLaunchManager = new AutoLaunchManager()
this.initThemeManager()
this.energyManager = new EnergyManager()
this.initUpdaterManager()
@@ -54,6 +59,7 @@ export default class Application extends EventEmitter {
this.initProtocolManager()
this.handleCommands()
this.handleIpcMessages()
}
@@ -74,17 +80,53 @@ export default class Application extends EventEmitter {
}
}
start (page) {
this.showPage(page)
initWindowManager () {
this.windowManager = new WindowManager({
userConfig: this.configManager.getUserConfig()
})
this.windowManager.on('window-resized', (data) => {
this.storeWindowState(data)
})
this.windowManager.on('window-moved', (data) => {
this.storeWindowState(data)
})
this.windowManager.on('window-closed', (data) => {
this.storeWindowState(data)
})
}
showPage (page) {
const win = this.windowManager.openWindow(page)
storeWindowState (data = {}) {
const enabled = this.configManager.getUserConfig('keep-window-state')
if (!enabled) {
return
}
const state = this.configManager.getUserConfig('window-state', {})
const { page, bounds } = data
const newState = {
...state,
[page]: bounds
}
this.configManager.setUserConfig('window-state', newState)
}
start (page, options = {}) {
this.showPage(page, options)
}
showPage (page, options = {}) {
const { openedAtLogin } = options
const win = this.windowManager.openWindow(page, {
hidden: openedAtLogin
})
win.once('ready-to-show', () => {
this.isReady = true
this.emit('ready')
})
this.touchBarManager.setup(page, win)
if (is.macOS()) {
this.touchBarManager.setup(page, win)
}
}
show (page = 'index') {
@@ -110,6 +152,7 @@ export default class Application extends EventEmitter {
stop () {
this.engine.stop()
this.energyManager.stopPowerSaveBlocker()
this.trayManager.destroy()
}
sendCommand (command, ...args) {
@@ -135,11 +178,29 @@ export default class Application extends EventEmitter {
})
}
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.touchBarManager = new TouchBarManager()
}
initProtocolManager () {
if (is.dev() || is.mas()) {
return
}
this.protocolManager = new ProtocolManager()
const protocols = this.configManager.getUserConfig('protocols', {})
this.protocolManager = new ProtocolManager({
protocols
})
}
handleProtocol (url) {
@@ -179,13 +240,27 @@ export default class Application extends EventEmitter {
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) => {
@@ -195,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)
})
@@ -209,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)
})
}
@@ -231,10 +309,23 @@ export default class Application extends EventEmitter {
})
this.on('application:exit', () => {
this.engine.stop()
this.stop()
app.exit()
})
this.on('application:open-at-login', (openAtLogin) => {
console.log('application:open-at-login===>', openAtLogin)
if (is.linux()) {
return
}
if (openAtLogin) {
this.autoLaunchManager.enable()
} else {
this.autoLaunchManager.disable()
}
})
this.on('application:show', (page) => {
this.show(page)
})
@@ -252,6 +343,11 @@ export default class Application extends EventEmitter {
this.updateManager.check()
})
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)
@@ -284,6 +380,14 @@ export default class Application extends EventEmitter {
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', () => {
const url = 'https://motrix.app/'
shell.openExternal(url)
@@ -312,7 +416,8 @@ 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) => {
+73 -28
View File
@@ -5,7 +5,11 @@ import is from 'electron-is'
import ExceptionHandler from './core/ExceptionHandler'
import logger from './core/Logger'
import Application from './Application'
import { parseArgv } from './utils'
import {
splitArgv,
parseArgvAsUrl,
parseArgvAsFile
} from './utils'
const EMPTY_STRING = ''
@@ -33,10 +37,10 @@ export default class Launcher extends EventEmitter {
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) { // Windows, Linux
this.file = parseArgv(argv)
this.sendFileToApplication()
if (!is.macOS() && argv.length > 1) {
this.handleAppLaunchArgv(argv)
}
})
@@ -47,6 +51,16 @@ export default class Launcher extends EventEmitter {
init () {
this.exceptionHandler = new ExceptionHandler()
this.openedAtLogin = is.macOS()
? app.getLoginItemSettings().wasOpenedAtLogin
: false
if (process.argv.length > 1) {
this.handleAppLaunchArgv(process.argv)
}
logger.warn('openedAtLogin===>', this.openedAtLogin)
this.handleAppEvents()
}
@@ -60,40 +74,72 @@ export default class Launcher extends EventEmitter {
/**
* handleOpenUrl
* Event 'open-url' macOS only
* "name": "Motrix Protocol",
* "schemes": ["mo", "motrix"]
*/
handleOpenUrl () {
if (is.mas()) {
if (is.mas() || !is.macOS()) {
return
}
app.on('open-url', (event, url) => {
logger.info(`[Motrix] open-url path: ${url}`)
logger.info(`[Motrix] open-url: ${url}`)
event.preventDefault()
this.url = url
if (this.url && global.application && global.application.isReady) {
global.application.handleProtocol(this.url)
this.url = EMPTY_STRING
}
this.sendUrlToApplication()
})
}
/**
* handleOpenFile [WIP]
* handleOpenFile
* Event 'open-file' macOS only
* handle open torrent file
*/
handleOpenFile () {
// macOS
if (is.macOS()) {
app.on('open-file', (event, path) => {
logger.info(`[Motrix] open-file path: ${path}`)
event.preventDefault()
this.file = path
this.sendFileToApplication()
})
} else if (process.argv.length > 1) { // Windows, Linux
this.file = parseArgv(process.argv)
if (!is.macOS()) {
return
}
app.on('open-file', (event, path) => {
logger.info(`[Motrix] open-file: ${path}`)
event.preventDefault()
this.file = path
this.sendFileToApplication()
})
}
/**
* handleAppLaunchArgv
* For Windows, Linux
* @param {array} argv
*/
handleAppLaunchArgv (argv) {
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
}
}
@@ -108,16 +154,15 @@ export default class Launcher extends EventEmitter {
app.on('ready', () => {
global.application = new Application()
global.application.start('index')
const { openedAtLogin } = this
global.application.start('index', {
openedAtLogin
})
global.application.on('ready', () => {
if (this.url) {
global.application.handleProtocol(this.url)
}
this.sendUrlToApplication()
if (this.file) {
global.application.handleFile(this.file)
}
this.sendFileToApplication()
})
})
+1
View File
@@ -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',
+42
View File
@@ -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'
]
+35
View File
@@ -0,0 +1,35 @@
import { app } from 'electron'
export default class AutoLaunchManager {
enable () {
return new Promise((resolve, reject) => {
const enabled = app.getLoginItemSettings().openAtLogin
if (enabled) {
resolve()
}
app.setLoginItemSettings({
openAtLogin: true,
// For Windows
args: [
'--opened-at-login=1'
]
})
resolve()
})
}
disable () {
return new Promise((resolve, reject) => {
app.setLoginItemSettings({ openAtLogin: false })
resolve()
})
}
isEnabled () {
return new Promise((resolve, reject) => {
const enabled = app.getLoginItemSettings().openAtLogin
resolve(enabled)
})
}
}
+54 -23
View File
@@ -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
@@ -21,30 +23,35 @@ export default class ConfigManager {
}
/**
* some aria2 conf
* 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'
}
})
@@ -53,20 +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) {
+10 -2
View File
@@ -64,7 +64,7 @@ export default class Engine {
const sh = this.getStartSh()
logger.info('[Motrix] Engine start sh===>', sh)
this.instance = forever.start(sh, {
max: 10,
max: 100,
parser: function (command, args) {
return {
command: command,
@@ -102,6 +102,14 @@ export default class Engine {
// })
}
isRunning (pid) {
try {
return process.kill(pid, 0)
} catch (e) {
return e.code === 'EPERM'
}
}
stop () {
const { pid } = this.instance.child
try {
@@ -116,7 +124,7 @@ export default class Engine {
forceStop (pid) {
try {
if (pid) {
if (pid && this.isRunning(pid)) {
process.kill(pid)
}
} catch (err) {
+1 -1
View File
@@ -1,7 +1,7 @@
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')
+54 -7
View File
@@ -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)
+22 -5
View File
@@ -19,6 +19,10 @@ export default class UpdateManager extends EventEmitter {
this.updater = autoUpdater
this.updater.autoDownload = false
this.updater.logger = logger
this.autoCheckData = {
checkEnable: this.options.autoCheck,
userCheck: false
}
this.init()
}
@@ -36,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()
}
@@ -63,10 +75,12 @@ export default class UpdateManager extends EventEmitter {
updateNotAvailable (event, info) {
this.emit('update-not-available', info)
dialog.showMessageBox({
title: this.i18n.t('app.check-for-updates-title'),
message: this.i18n.t('app.update-not-available-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')
})
}
}
/**
@@ -98,7 +112,10 @@ export default class UpdateManager extends EventEmitter {
updateError (event, error) {
this.emit('update-error', error)
const msg = error == null ? this.i18n.t('update-error-message') : (error.stack || error).toString()
const msg = (error == null)
? this.i18n.t('update-error-message')
: (error.stack || error).toString()
this.updater.logger.warn(`[Motrix] update-error: ${msg}`)
dialog.showErrorBox(msg)
}
+1
View File
@@ -5,6 +5,7 @@
{ "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" }
+3 -3
View File
@@ -50,7 +50,7 @@ export default class MenuManager extends EventEmitter {
this.setup()
}
updateStates (visibleStates, enabledStates, checkedStates) {
updateMenuStates (visibleStates, enabledStates, checkedStates) {
updateStates(this.items, visibleStates, enabledStates, checkedStates)
}
@@ -58,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)
}
}
+46
View File
@@ -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)
}
}
+50 -5
View File
@@ -1,9 +1,14 @@
import { EventEmitter } from 'events'
import { join } from 'path'
import { Tray, Menu } from 'electron'
import { Tray, Menu, systemPreferences } from 'electron'
import is from 'electron-is'
import { translateTemplate } from '../utils/menu'
import {
translateTemplate,
flattenMenuItems,
updateStates
} from '../utils/menu'
import { getI18n } from '@/ui/Locale'
import { LIGHT_THEME, DARK_THEME } from '@shared/constants'
let tray = null
@@ -23,10 +28,11 @@ export default class TrayManager extends EventEmitter {
load () {
this.template = require(`../menus/tray.json`)
const theme = systemPreferences.isDarkMode() ? DARK_THEME : LIGHT_THEME
if (is.macOS()) {
this.normalIcon = join(__static, './mo-tray-normal.png')
this.activeIcon = join(__static, './mo-tray-active.png')
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')
@@ -43,6 +49,7 @@ export default class TrayManager extends EventEmitter {
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 () {
@@ -91,7 +98,45 @@ export default class TrayManager extends EventEmitter {
}
updateStatus (status) {
const icon = status ? this.activeIcon : this.normalIcon
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()
}
}
+63 -16
View File
@@ -1,16 +1,19 @@
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 {
@@ -38,6 +41,13 @@ export default class WindowManager extends EventEmitter {
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()) {
@@ -47,37 +57,57 @@ export default class WindowManager extends EventEmitter {
return result
}
openWindow (page) {
const options = this.getPageOptions(page)
getPageBounds (page) {
const enabled = this.userConfig['keep-window-state']
const windowStateMap = this.userConfig['window-state'] || {}
let result = null
if (enabled) {
result = windowStateMap[page]
}
return result
}
openWindow (page, options = {}) {
const pageOptions = this.getPageOptions(page)
const { hidden } = options
let window = this.windows[page] || null
if (window) {
window.restore()
window.show()
window.focus()
return window
}
window = new BrowserWindow({
...defaultBrowserOptions,
...options.attrs
...pageOptions.attrs
})
const bounds = this.getPageBounds(page)
console.log('bounds ====>', bounds)
if (bounds) {
window.setBounds(bounds)
}
window.webContents.on('new-window', (e, url) => {
e.preventDefault()
shell.openExternal(url)
})
if (options.url) {
window.loadURL(options.url)
if (pageOptions.url) {
window.loadURL(pageOptions.url)
}
window.once('ready-to-show', () => {
window.show()
if (!hidden) {
window.show()
}
})
if (options.bindCloseToHide) {
this.bindCloseToHide(page, window)
}
this.handleWindowState(page, window)
this.handleWindowClose(pageOptions, page, window)
this.bindAfterClosed(page, window)
@@ -104,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()
}
@@ -117,12 +150,26 @@ export default class WindowManager extends EventEmitter {
})
}
bindCloseToHide (page, window) {
handleWindowState (page, window) {
window.on('resize', debounce(() => {
const bounds = window.getBounds()
this.emit('window-resized', { page, bounds })
}, 500))
window.on('move', debounce(() => {
const bounds = window.getBounds()
this.emit('window-moved', { page, bounds })
}, 500))
}
handleWindowClose (pageOptions, page, window) {
window.on('close', (event) => {
if (!this.willQuit) {
if (pageOptions.bindCloseToHide && !this.willQuit) {
event.preventDefault()
window.hide()
}
const bounds = window.getBounds()
this.emit('window-closed', { page, bounds })
})
}
@@ -180,7 +227,7 @@ export default class WindowManager extends EventEmitter {
if (!window) {
return
}
logger.info('[Motrix] sendCommandTo===>', window, command, ...args)
logger.info('[Motrix] sendCommandTo===>', command, ...args)
window.webContents.send('command', command, ...args)
}
+50 -1
View File
@@ -9,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')
}
@@ -60,11 +65,55 @@ export function moveAppToApplicationsFolder (errorMsg = '') {
})
}
export function splitArgv (argv) {
const args = []
const extra = {}
for (const arg of argv) {
if (arg.startsWith('--')) {
const kv = arg.split('=')
const key = kv[0]
const value = kv[1] || '1'
extra[key] = value
continue
}
args.push(arg)
}
return { args, extra }
}
export function parseArgvAsUrl (argv) {
let arg = argv[1]
if (!arg) {
return
}
if (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 parseArgv (argv) {
export function parseArgvAsFile (argv) {
let arg = argv[1]
if (!arg || isDirectory(arg)) {
return
+57 -5
View File
@@ -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,12 @@ 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')
@@ -120,6 +134,28 @@ export default class Api {
})
}
getOption (params = {}) {
const { gid } = params
const args = compactUndefined([gid])
return new Promise((resolve) => {
this.client.call('getOption', ...args)
.then((data) => {
resolve(changeKeysToCamelCase(data))
})
})
}
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')
}
@@ -129,8 +165,9 @@ export default class Api {
uris,
options
} = params
const kebabOptions = changeKeysToKebabCase(options)
const tasks = uris.map((uri) => {
const args = compactUndefined([[uri], options])
const args = compactUndefined([[uri], kebabOptions])
return [ 'aria2.addUri', ...args ]
})
return this.client.multicall(tasks)
@@ -141,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)
}
@@ -150,7 +188,8 @@ export default class Api {
metalink,
options
} = params
const args = compactUndefined([metalink, options])
const kebabOptions = changeKeysToKebabCase(options)
const args = compactUndefined([metalink, kebabOptions])
return this.client.call('addMetalink', ...args)
}
@@ -268,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
})
}
}
+10
View File
@@ -0,0 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24">
<g stroke-linecap="round" stroke-linejoin="round" stroke-width="2" stroke="currentColor">
<rect x="4" y="3" width="16" height="18" fill="none" stroke="currentColor" stroke-miterlimit="10"/>
<line data-color="color-2" x1="1" y1="6" x2="1" y2="18" fill="none" stroke-miterlimit="10"/>
<line data-color="color-2" x1="23" y1="6" x2="23" y2="18" fill="none" stroke-miterlimit="10"/>
<polyline data-cap="butt" points="10 15 10 8 16 8 16 14" fill="none" stroke="currentColor" stroke-miterlimit="10"/>
<circle data-stroke="none" cx="9" cy="15" r="2" stroke="none"/>
<circle data-stroke="none" cx="15" cy="14" r="2" stroke="none"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 747 B

+11
View File
@@ -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

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

After

Width:  |  Height:  |  Size: 509 B

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

After

Width:  |  Height:  |  Size: 1.1 KiB

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

After

Width:  |  Height:  |  Size: 214 B

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

After

Width:  |  Height:  |  Size: 609 B

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

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

+4 -4
View File
@@ -49,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 {
@@ -72,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;
+4 -4
View File
@@ -6,6 +6,9 @@
</a>
</el-col>
<el-col :span="18" class="copyright-right">
<a target="_blank" href="https://motrix.app/license" rel="noopener noreferrer">
{{ $t('about.license') }}
</a>
<a target="_blank" href="https://motrix.app/about" rel="noopener noreferrer">
{{ $t('about.about') }}
</a>
@@ -30,15 +33,12 @@
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 {
+4 -7
View File
@@ -3,18 +3,18 @@
<div class="aside-inner">
<mo-logo-mini />
<ul class="menu top-menu">
<li @click="nav('/task')">
<li @click="nav('/task')" class="non-draggable">
<mo-icon name="menu-task" width="20" height="20" />
</li>
<li @click="showAddTask()">
<li @click="showAddTask()" class="non-draggable">
<mo-icon name="menu-add" width="20" height="20" />
</li>
</ul>
<ul class="menu bottom-menu">
<li @click="nav('/preference')">
<li @click="nav('/preference')" class="non-draggable">
<mo-icon name="menu-preference" width="20" height="20" />
</li>
<li @click="showAboutPanel">
<li @click="showAboutPanel" class="non-draggable">
<mo-icon name="menu-about" width="20" height="20" />
</li>
</ul>
@@ -45,9 +45,6 @@
}
},
methods: {
open (link) {
this.$electron.shell.openExternal(link)
},
showAddTask (taskType = 'uri') {
this.$store.dispatch('app/showAddTaskDialog', taskType)
},
+19 -2
View File
@@ -9,14 +9,29 @@ 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' })
@@ -67,9 +82,11 @@ 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)
+20
View File
@@ -0,0 +1,20 @@
import Icon from '@/components/Icons/Icon'
Icon.register({
'audio': {
'width': 24,
'height': 24,
'raw': `<rect x="4" y="3" width="16" height="18" fill="none" stroke="currentColor" stroke-miterlimit="10"/>
<line data-color="color-2" x1="1" y1="6" x2="1" y2="18" fill="none" stroke-miterlimit="10"/>
<line data-color="color-2" x1="23" y1="6" x2="23" y2="18" fill="none" stroke-miterlimit="10"/>
<polyline data-cap="butt" points="10 15 10 8 16 8 16 14" fill="none" stroke="currentColor" stroke-miterlimit="10"/>
<circle data-stroke="none" cx="9" cy="15" r="2" stroke="none"/>
<circle data-stroke="none" cx="15" cy="14" r="2" stroke="none"/>`,
'g': {
'stroke': 'currentColor',
'stroke-linecap': 'round',
'stroke-linejoin': 'round',
'stroke-width': '2'
}
}
})
+21
View File
@@ -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'
}
}
})
+17
View File
@@ -0,0 +1,17 @@
import Icon from '@/components/Icons/Icon'
Icon.register({
'image': {
'width': 24,
'height': 24,
'raw': `<polyline data-cap="butt" data-color="color-2" points="1 20 6 14 10 18 17 10 23 17" fill="none" stroke-miterlimit="10"/>
<rect x="1" y="3" width="22" height="18" fill="none" stroke="currentColor" stroke-miterlimit="10"/>
<circle data-color="color-2" cx="9" cy="8" r="2" fill="none" stroke-miterlimit="10"/>`,
'g': {
'stroke': 'currentColor',
'stroke-linecap': 'round',
'stroke-linejoin': 'round',
'stroke-width': '2'
}
}
})
+22
View File
@@ -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'
}
}
})
+22
View File
@@ -0,0 +1,22 @@
import Icon from '@/components/Icons/Icon'
Icon.register({
'video': {
'width': 24,
'height': 24,
'raw': `<line data-cap="butt" data-color="color-2" x1="2" y1="6" x2="22" y2="6" fill="none" stroke-miterlimit="10"/>
<line data-cap="butt" data-color="color-2" x1="12" y1="2" x2="12" y2="22" fill="none" stroke-miterlimit="10"/>
<line data-cap="butt" data-color="color-2" x1="7" y1="2" x2="7" y2="6" fill="none" stroke-miterlimit="10"/>
<line data-cap="butt" data-color="color-2" x1="17" y1="2" x2="17" y2="6" fill="none" stroke-miterlimit="10"/>
<line data-cap="butt" data-color="color-2" x1="2" y1="18" x2="22" y2="18" fill="none" stroke-miterlimit="10"/>
<line data-cap="butt" data-color="color-2" x1="7" y1="22" x2="7" y2="18" fill="none" stroke-miterlimit="10"/>
<line data-cap="butt" data-color="color-2" x1="17" y1="22" x2="17" y2="18" fill="none" stroke-miterlimit="10"/>
<rect x="2" y="2" width="20" height="20" fill="none" stroke="currentColor" stroke-miterlimit="10"/>`,
'g': {
'stroke': 'currentColor',
'stroke-linecap': 'round',
'stroke-linejoin': 'round',
'stroke-width': '2'
}
}
})
+5 -5
View File
@@ -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>
+2 -24
View File
@@ -1,7 +1,7 @@
<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" />
@@ -14,7 +14,6 @@
import { mapState } from 'vuex'
import AboutPanel from '@/components/About/AboutPanel'
import Aside from '@/components/Aside/Index'
import Subnav from '@/components/Subnav/Index'
import Speedometer from '@/components/Speedometer/Speedometer'
import AddTask from '@/components/Task/AddTask'
import TaskItemInfo from '@/components/Task/TaskItemInfo'
@@ -25,7 +24,6 @@
components: {
[AboutPanel.name]: AboutPanel,
[Aside.name]: Aside,
[Subnav.name]: Subnav,
[Speedometer.name]: Speedometer,
[AddTask.name]: AddTask,
[TaskItemInfo.name]: TaskItemInfo,
@@ -52,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>
+65 -40
View File
@@ -40,13 +40,13 @@
})
},
watch: {
downloadSpeed: function (val, oldVal) {
downloadSpeed (val, oldVal) {
showDownloadSpeedInDock(val)
},
numActive: function (val, oldVal) {
numActive (val, oldVal) {
this.downloading = val > 0
},
downloading: function (val, oldVal) {
downloading (val, oldVal) {
if (val !== oldVal && this.isRenderer) {
this.$electron.ipcRenderer.send('download-status-change', val)
}
@@ -59,7 +59,7 @@
console.warn(`fetchTaskItem fail: ${e.message}`)
})
},
onDownloadStart: function (event) {
onDownloadStart (event) {
this.$store.dispatch('task/fetchList')
this.$store.dispatch('app/resetInterval')
console.log('aria2 onDownloadStart', event)
@@ -71,7 +71,7 @@
this.$msg.info(message)
})
},
onDownloadPause: function (event) {
onDownloadPause (event) {
console.log('aria2 onDownloadPause')
const [{ gid }] = event
this.fetchTaskItem({ gid })
@@ -81,7 +81,7 @@
this.$msg.info(message)
})
},
onDownloadStop: function (event) {
onDownloadStop (event) {
console.log('aria2 onDownloadStop')
const [{ gid }] = event
this.fetchTaskItem({ gid })
@@ -91,51 +91,72 @@
this.$msg.info(message)
})
},
onDownloadError: function (event) {
console.log('aria2 onDownloadError', event)
onDownloadError (event) {
const [{ gid }] = event
this.fetchTaskItem({ gid })
.then((task) => {
const taskName = getTaskName(task)
const { errorCode, errorMessage } = task
console.error(`[Motrix] download error===> Gid: ${gid}, #${errorCode}, ${errorMessage}`)
const message = this.$t('task.download-error-message', { taskName })
this.$msg.error(message)
const link = `<a target="_blank" href="https://github.com/agalwood/Motrix/wiki/Error#${errorCode}" rel="noopener noreferrer">#${errorCode}</a>`
this.$msg({
type: 'error',
showClose: true,
duration: 5000,
dangerouslyUseHTMLString: true,
message: `${message} ${link}`
})
})
},
onDownloadComplete: function (event) {
onDownloadComplete (event) {
console.log('aria2 onDownloadComplete')
this.$store.dispatch('task/fetchList')
const [{ gid }] = event
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
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 = this.$t('task.download-complete-message', { taskName })
this.$msg.success(message)
this.showTaskCompleteNotify(task, isBT, path)
},
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 notify = new Notification(this.$t('task.download-complete-notify'), {
body: taskName
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, {
@@ -143,22 +164,22 @@
})
}
},
showTaskErrorNotify: function (task) {
if (!this.taskNotification) {
return
}
showTaskErrorNotify (task) {
const taskName = getTaskName(task)
const message = this.$t('task.download-fail-message', { taskName })
this.$msg.success(message)
if (!this.taskNotification) {
return
}
/* eslint-disable no-new */
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('onDownloadStop', this.onDownloadStop)
@@ -166,7 +187,7 @@
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('onDownloadStop', this.onDownloadStop)
@@ -174,33 +195,37 @@
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.$store.dispatch('app/fetchEngineOptions')
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>
+6 -1
View File
@@ -8,7 +8,7 @@
<li @click="handleMaximize">
<mo-icon name="win-maximize" width="12" height="12" />
</li>
<li @click="handleClose">
<li @click="handleClose" class="win-close-btn">
<mo-icon name="win-close" width="12" height="12" />
</li>
</ul>
@@ -77,9 +77,14 @@
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 {
+17 -3
View File
@@ -6,6 +6,7 @@ import {
getTaskFullPath,
bytesToSize
} from '@shared/utils'
import { LIGHT_THEME, DARK_THEME } from '@shared/constants'
const remote = is.renderer() ? require('electron').remote : {}
@@ -61,9 +62,13 @@ export function moveTaskFilesToTrash (task, messages = {}) {
return false
}
const deleteResult1 = remote.shell.moveItemToTrash(path)
if (!deleteResult1 && delFailMsg) {
Message.error(delFailMsg)
let deleteResult1 = true
const isFileExist = existsSync(path)
if (isFileExist) {
deleteResult1 = remote.shell.moveItemToTrash(path)
if (!deleteResult1 && delFailMsg) {
Message.error(delFailMsg)
}
}
let deleteResult2 = true
@@ -122,3 +127,12 @@ export function clearRecentTasks () {
}
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
}
+199 -38
View File
@@ -11,24 +11,18 @@
size="mini"
:model="form"
:rules="rules">
<el-form-item :label="`${$t('preferences.ui')}: `" :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-col v-if="showHideAppMenuOption" class="form-item-sub" :span="16">
<el-checkbox v-model="form.hideAppMenu">
{{ $t('preferences.hide-app-menu') }}
<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">
@@ -39,7 +33,7 @@
>
</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]"
@@ -48,7 +42,65 @@
</el-input>
</el-col>
</el-form-item>
<el-form-item :label="`${$t('preferences.developer')}: `" :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
@@ -64,6 +116,27 @@
<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">
@@ -102,26 +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 { availableLanguages, getLanguage } from '@shared/locales'
import { getLocaleManager } from '@/components/Locale'
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 {
locale,
hideAppMenu,
useProxy,
allProxy,
allProxyBackup,
autoCheckUpdate,
btTracker,
hideAppMenu,
lastCheckUpdateTime,
protocols,
rpcListenPort,
rpcSecret,
useProxy,
userAgent
} = config
const result = {
locale,
hideAppMenu,
useProxy,
allProxy,
allProxyBackup,
autoCheckUpdate,
btTracker: convertCommaToLine(btTracker),
hideAppMenu,
lastCheckUpdateTime,
protocols: {
...protocols
},
rpcListenPort,
rpcSecret,
useProxy,
userAgent
}
return result
@@ -133,12 +229,15 @@
[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),
form,
formLabelWidth: calcFormLabelWidth(locale),
formOriginal: cloneDeep(form),
hideRpcSecret: true,
rules: {},
color: '#c00',
locales: availableLanguages
trackerSyncing: false
}
},
computed: {
@@ -155,13 +254,42 @@
})
},
watch: {
'form.rpcSecret': function (val) {
const url = buildRpcUrl({
port: this.form.rpcListenPort,
secret: val
})
clipboard.writeText(url)
}
},
methods: {
isRenderer: is.renderer,
handleLocaleChange (locale) {
const lng = getLanguage(locale)
getLocaleManager().changeLanguage(lng)
this.$electron.ipcRenderer.send('command', 'application:change-locale', lng)
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 : ''
@@ -176,6 +304,15 @@
}
this.form.userAgent = ua
},
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',
@@ -195,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)
}
})
},
@@ -211,6 +364,14 @@
</script>
<style lang="scss">
.bt-tracker {
position: relative;
.sync-tracker {
position: absolute;
top: 8px;
right: 8px;
}
}
.ua-group {
margin-top: 8px;
}
+167 -17
View File
@@ -11,10 +11,54 @@
size="mini"
:model="form"
:rules="rules">
<el-form-item :label="`${$t('preferences.appearance')}: `" :label-width="formLabelWidth">
<el-col class="form-item-sub" :span="24">
<mo-theme-switcher
v-model="form.theme"
@change="handleThemeChange"
/>
</el-col>
<el-col v-if="showHideAppMenuOption" class="form-item-sub" :span="16">
<el-checkbox v-model="form.hideAppMenu">
{{ $t('preferences.hide-app-menu') }}
</el-checkbox>
</el-col>
</el-form-item>
<el-form-item :label="`${$t('preferences.language')}: `" :label-width="formLabelWidth">
<el-col class="form-item-sub" :span="16">
<el-select
v-model="form.locale"
@change="handleLocaleChange"
:placeholder="$t('preferences.change-language')">
<el-option
v-for="item in locales"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-col>
</el-form-item>
<el-form-item :label="`${$t('preferences.startup')}: `" :label-width="formLabelWidth">
<el-checkbox v-model="form.resumeAllWhenAppLaunched">
{{ $t('preferences.auto-resume-all') }}
</el-checkbox>
<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()">
@@ -25,9 +69,33 @@
/>
</el-input>
<div class="el-form-item__info" v-if="isMas()" style="margin-top: 8px;">
{{ $t('preferences.mas-default-dir-tip') }}
{{ $t('preferences.mas-default-dir-tips') }}
</div>
</el-form-item>
<el-form-item :label="`${$t('preferences.transfer-settings')}: `" :label-width="formLabelWidth">
<el-col class="form-item-sub" :span="24">
{{ $t('preferences.transfer-speed-upload') }}
<el-select v-model="form.maxOverallUploadLimit">
<el-option
v-for="item in speedOptions"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-col>
<el-col class="form-item-sub" :span="24">
{{ $t('preferences.transfer-speed-download') }}
<el-select v-model="form.maxOverallDownloadLimit">
<el-option
v-for="item in speedOptions"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-col>
</el-form-item>
<el-form-item :label="`${$t('preferences.task-manage')}: `" :label-width="formLabelWidth">
<el-col class="form-item-sub" :span="24">
{{ $t('preferences.max-concurrent-downloads') }}
@@ -77,28 +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
const result = {
dir,
split,
continue: config.continue,
resumeAllWhenAppLaunched,
dir,
hideAppMenu,
keepWindowState,
locale,
maxConcurrentDownloads,
maxConnectionPerServer,
maxOverallUploadLimit,
maxOverallDownloadLimit,
newTaskShowDownloading,
openAtLogin,
resumeAllWhenAppLaunched,
split,
taskNotification,
newTaskShowDownloading
theme
}
return result
}
@@ -106,19 +193,28 @@
export default {
name: 'mo-preference-basic',
components: {
[SelectDirectory.name]: SelectDirectory
[SelectDirectory.name]: SelectDirectory,
[ThemeSwitcher.name]: ThemeSwitcher
},
data: function () {
const { locale } = this.$store.state.preference.config
const form = initialForm(this.$store.state.preference.config)
return {
formLabelWidth: '23%',
form: initialForm(this.$store.state.preference.config),
rules: {}
form,
formLabelWidth: calcFormLabelWidth(locale),
formOriginal: cloneDeep(form),
locales: availableLanguages,
rules: {},
speedOptions: this.buildSpeedOptions()
}
},
computed: {
title: function () {
return this.$t('preferences.basic')
},
showHideAppMenuOption: function () {
return is.windows() || is.linux()
},
downloadDir: function () {
return prettifyDir(this.form.dir)
},
@@ -129,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
},
@@ -139,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)
}
})
},
+40 -19
View File
@@ -1,17 +1,15 @@
<template>
<el-container class="content panel" direction="horizontal">
<el-aside width="200px" class="subnav">
<router-view name="subnav"></router-view>
<router-view name="subnav" />
</el-aside>
<router-view name="form"></router-view>
<router-view name="form" />
</el-container>
</template>
<script>
export default {
name: 'mo-content-preference',
props: {
},
computed: {
},
components: {
@@ -25,20 +23,16 @@
.form-preference {
padding-right: 7%;
.el-switch__label {
font-weight: $--font-weight-secondary;
font-weight: normal;
color: $--color-text-regular;
}
.el-form-item.el-form-item--mini {
margin-bottom: 24px;
}
.el-form-item__content {
color: $--color-text-regular;
}
.form-item-sub {
margin-bottom: 12px;
&:last-of-type {
margin-bottom: 0;
&.is-active {
color: $--color-text-regular;
}
}
.el-checkbox__input.is-checked + .el-checkbox__label {
color: $--color-text-regular;
}
.el-form-item {
a {
color: $--color-text-regular;
text-decoration: none;
@@ -46,15 +40,42 @@
color: $--color-text-primary;
text-decoration: underline;
}
&:active {
color: $--color-text-primary;
}
}
}
.el-form-item.el-form-item--mini {
margin-bottom: 32px;
}
.el-form-item__content {
color: $--color-text-regular;
}
.form-item-sub {
margin-bottom: 8px;
&:last-of-type {
margin-bottom: 0;
}
}
}
.form-actions {
position: absolute;
position: fixed;
bottom: 0;
left: 0;
width: 100%;
left: auto;
z-index: 10;
width: -webkit-fill-available;
box-sizing: border-box;
padding: 24px 36px;
margin-left: -36px;
// aside.width + subnav.width + padding-left + scrollbar.width
margin-right: 322px;
}
.action-link {
cursor: pointer;
color: $--link-color;
&:hover {
color: $--link-hover-color;
text-decoration: underline;
}
}
</style>
+11 -18
View File
@@ -16,23 +16,14 @@
{{ $t('preferences.lab-warning') }}
</div>
</el-form-item>
<el-form-item :label="`${$t('preferences.download-protocol')}: `" :label-width="formLabelWidth">
<el-col class="form-item-sub" :span="24">
<el-switch
v-model="form.enableEggFeatures"
:active-text="$t('preferences.support-more-download-protocols')"
>
</el-switch>
</el-col>
</el-form-item>
<el-form-item :label="`${$t('preferences.browser-extensions')}: `" :label-width="formLabelWidth">
<el-col class="form-item-sub" :span="24">
<a target="_blank" href="https://motrix.app/release/BaiduExporter.zip" rel="noopener noreferrer">
{{ $t('preferences.baidu-exporter') }}
<mo-icon name="link" width="14" height="14" />
<mo-icon name="link" width="12" height="12" />
</a>
<div class="el-form-item__info" style="margin-top: 8px;">
{{ $t('preferences.browser-extensions-tip') }}
{{ $t('preferences.browser-extensions-tips') }}
<a target="_blank" href="https://motrix.app/extensions/baidu" rel="noopener noreferrer">
{{ $t('preferences.baidu-exporter-help') }}
</a>
@@ -52,13 +43,14 @@
import is from 'electron-is'
import { mapState } from 'vuex'
import '@/components/Icons/info-square'
import {
calcFormLabelWidth
} from '@shared/utils'
const initialForm = (config) => {
const {
enableEggFeatures
} = config
// const {
// } = config
const result = {
enableEggFeatures
}
return result
}
@@ -68,9 +60,11 @@
components: {
},
data: function () {
const { locale } = this.$store.state.preference.config
const form = initialForm(this.$store.state.preference.config)
return {
formLabelWidth: '23%',
form: initialForm(this.$store.state.preference.config),
form,
formLabelWidth: calcFormLabelWidth(locale),
rules: {}
}
},
@@ -97,7 +91,6 @@
console.log('this.form===>', this.form)
this.$store.dispatch('preference/save', this.form)
if (this.isRenderer()) {
this.$electron.ipcRenderer.send('command', 'application:relaunch')
}
})
},
@@ -0,0 +1,107 @@
<template>
<div>
<ul class="theme-switcher">
<li
class="theme-item theme-item-auto"
:class="{ active: currentValue === 'auto' }"
@click.prevent="() => handleChange('auto')"
>
<div class="theme-thumb"></div>
<span>{{ $t('preferences.theme-auto') }}</span>
</li>
<li
class="theme-item theme-item-light"
:class="{ active: currentValue === 'light' }"
@click.prevent="() => handleChange('light')"
>
<div class="theme-thumb"></div>
<span>{{ $t('preferences.theme-light') }}</span>
</li>
<li
class="theme-item theme-item-dark"
:class="{ active: currentValue === 'dark' }"
@click.prevent="() => handleChange('dark')"
>
<div class="theme-thumb"></div>
<span>{{ $t('preferences.theme-dark') }}</span>
</li>
</ul>
</div>
</template>
<script>
export default {
name: 'mo-theme-switcher',
components: {
},
props: {
value: {
type: String,
default: 'auto'
}
},
data: function () {
return {
currentValue: this.value
}
},
watch: {
currentValue: function (val) {
this.$emit('change', val)
}
},
methods: {
handleChange (theme) {
this.currentValue = theme
}
}
}
</script>
<style lang="scss">
.theme-switcher {
padding: 0;
margin: 0;
font-size: 0;
line-height: 0;
.theme-item {
text-align: center;
display: inline-block;
margin: 0 16px 0 0;
cursor: pointer;
span {
font-size: 13px;
line-height: 20px;
}
&.active {
.theme-thumb {
border-color: $--color-primary;
box-shadow: 0 0 1px $--color-primary;
}
span {
color: $--color-primary;
}
}
&.theme-item-auto .theme-thumb {
background: url('~@/assets/theme-auto@2x.png') center center no-repeat;
background-size: 68px 44px;
}
&.theme-item-light .theme-thumb {
background: url('~@/assets/theme-light@2x.png') center center no-repeat;
background-size: 68px 44px;
}
&.theme-item-dark .theme-thumb {
background: url('~@/assets/theme-dark@2x.png') center center no-repeat;
background-size: 68px 44px;
}
}
.theme-thumb {
box-sizing: border-box;
border: 1px solid #aaa;
border-radius: 5px;
width: 68px;
height: 44px;
margin-bottom: 8px;
}
}
</style>
@@ -47,9 +47,6 @@
</script>
<style lang="scss">
@import '../Theme/Variables';
@import '../Theme/Darkness/Variables';
.mo-speedometer {
font-size: 12px;
position: relative;
@@ -144,12 +141,4 @@
color: $--speedometer-primary-color;
}
}
.darkness .mo-speedometer {
border: 1px solid $--dk-speedometer-border-color;
background: $--dk-speedometer-background;
&:hover {
border-color: $--dk-speedometer-hover-border-color;
}
}
</style>
-20
View File
@@ -1,20 +0,0 @@
<template>
<el-aside width="200px" class="subnav">
<router-view name="subnav"></router-view>
</el-aside>
</template>
<script>
export default {
name: 'mo-subnav',
components: {
},
computed: {
},
methods: {
}
}
</script>
<style lang="scss">
</style>
+76 -75
View File
@@ -5,6 +5,7 @@
:visible.sync="visible"
:before-close="handleClose"
@open="handleOpen"
@opened="handleOpened"
@closed="handleClosed">
<el-form
ref="taskForm"
@@ -19,8 +20,9 @@
type="textarea"
:autosize="{ minRows: 3, maxRows: 5 }"
auto-complete="off"
:placeholder="$t('task.uri-task-tip')"
:placeholder="$t('task.uri-task-tips')"
@change="handleUriChange"
@paste.native="handleUriPaste"
v-model="form.uris">
</el-input>
</el-form-item>
@@ -28,7 +30,6 @@
<el-tab-pane
:label="$t('task.torrent-task')"
name="torrent"
v-if="iLoveEggFeatures"
>
<el-form-item>
<mo-select-torrent
@@ -40,7 +41,7 @@
<el-row :gutter="12">
<el-col :span="15">
<el-form-item :label="`${$t('task.task-out')}: `" :label-width="formLabelWidth">
<el-input :placeholder="$t('task.task-out-tip')" v-model="form.out">
<el-input :placeholder="$t('task.task-out-tips')" v-model="form.out">
</el-input>
</el-form-item>
</el-col>
@@ -115,25 +116,31 @@
import SelectDirectory from '@/components/Native/SelectDirectory'
import SelectTorrent from '@/components/Task/SelectTorrent'
import { prettifyDir } from '@/components/Native/utils'
import '@/components/Icons/inbox'
import {
NONE_SELECTED_FILES,
SELECTED_ALL_FILES
} from '@shared/constants'
import {
detectResource,
splitTaskLinks,
needCheckCopyright
splitTaskLinks
} from '@shared/utils'
import '@/components/Icons/inbox'
const initialForm = (state) => {
const { addTaskUrl, addTaskOptions } = state.app
const { dir, split, newTaskShowDownloading } = state.preference.config
const result = {
uris: '',
uris: addTaskUrl,
torrent: '',
selectFile: NONE_SELECTED_FILES,
out: '',
userAgent: '',
referer: '',
cookie: '',
dir,
split,
newTaskShowDownloading
newTaskShowDownloading,
...addTaskOptions
}
return result
}
@@ -158,7 +165,6 @@
return {
formLabelWidth: '100px',
showAdvanced: false,
torrentName: '',
form: {},
rules: {}
}
@@ -175,10 +181,7 @@
}),
...mapState('preference', {
config: state => state.config
}),
iLoveEggFeatures: function () {
return !this.isMas() || (this.isMas() && this.config.enableEggFeatures)
}
})
},
watch: {
taskType: function (current, previous) {
@@ -211,10 +214,16 @@
if (!hasResource) {
return
}
this.form.uris = content
if (isEmpty(this.form.uris)) {
this.form.uris = content
}
},
handleOpened () {
this.detectThunderResource(this.form.uris)
},
handleClose (done) {
this.$store.dispatch('app/hideAddTaskDialog')
this.$store.dispatch('app/updateAddTaskOptions', {})
},
handleClosed () {
this.reset()
@@ -222,22 +231,27 @@
handleTabClick (tab, event) {
this.$store.dispatch('app/changeAddTaskType', tab.name)
},
handleUriChange () {
// el-input does not support @paste event ?
// https://github.com/ElemeFE/element/blob/master/packages/input/src/input.vue
const { uris } = this.form
handleUriPaste () {
setImmediate(() => {
const uris = this.$refs.uri.value
this.detectThunderResource(uris)
})
},
detectThunderResource (uris = '') {
if (uris.includes('thunder://')) {
this.$msg({
type: 'warning',
message: this.$t('task.thunder-link-tip'),
message: this.$t('task.thunder-link-tips'),
duration: 6000
})
}
},
handleTorrentChange (torrent, file, fileList) {
// TODO 种子选择部分文件下载
// console.log('handleTorrentChange===>', torrent, file, fileList)
handleUriChange () {
console.log('handleUriChange===>', this.form.uris)
},
handleTorrentChange (torrent, selectedFileIndex) {
this.form.torrent = torrent
this.form.selectFile = selectedFileIndex
},
handleSplitChange (value) {
console.log('handleSplitChange===>', value)
@@ -246,6 +260,7 @@
this.form.dir = dir
},
reset () {
this.showAdvanced = false
this.form = initialForm(this.$store.state)
},
handleCancel (formName) {
@@ -268,9 +283,12 @@
}
return result
},
buildOption (form) {
buildOption (type, form) {
const {
dir, out, split
dir,
out,
selectFile,
split
} = form
const result = {}
@@ -282,6 +300,15 @@
result.out = out
}
if (type === 'torrent') {
if (
selectFile !== SELECTED_ALL_FILES &&
selectFile !== NONE_SELECTED_FILES
) {
result.selectFile = selectFile
}
}
if (split > 0) {
result.split = split
}
@@ -294,8 +321,11 @@
},
buildUriPayload (form) {
let { uris } = form
if (isEmpty(uris)) {
throw new Error(this.$t('task.new-task-uris-required'))
}
uris = splitTaskLinks(uris)
const options = this.buildOption(form)
const options = this.buildOption('uri', form)
const result = {
uris,
options
@@ -304,12 +334,14 @@
},
buildTorrentPayload (form) {
const { torrent } = form
const options = this.buildOption(form)
if (isEmpty(torrent)) {
throw new Error(this.$t('task.new-task-torrent-required'))
}
const options = this.buildOption('torrent', form)
const result = {
torrent,
options
}
console.log('buildTorrentPayload===>', result)
return result
},
addTask (type, form) {
@@ -332,58 +364,24 @@
console.error('addTask fail', form)
}
},
checkCopyright (type, form) {
const { uris } = form
return new Promise((resolve, reject) => {
if (type !== 'uri') {
resolve()
}
if (!needCheckCopyright(uris)) {
resolve()
return
}
if (this.iLoveEggFeatures) {
resolve()
return
}
this.$electron.remote.dialog.showMessageBox({
type: 'warning',
title: this.$t('task.copyright-warning'),
message: this.$t('task.copyright-warning-message'),
buttons: [this.$t('task.copyright-yes'), this.$t('task.copyright-no')],
cancelId: 1
}, (buttonIndex, checkboxChecked) => {
if (buttonIndex === 0) {
resolve()
} else {
reject(new Error(this.$t('task.copyright-error-message')))
}
})
})
},
submitForm (formName) {
this.$refs[formName].validate((valid) => {
if (!valid) {
return false
}
this.checkCopyright(this.type, this.form)
.then(() => {
this.addTask(this.type, this.form)
this.$store.dispatch('app/hideAddTaskDialog')
if (this.form.newTaskShowDownloading) {
this.$router.push({
path: '/task/active'
})
}
})
.catch((err) => {
this.$msg.error(err.message)
})
try {
this.addTask(this.type, this.form)
this.$store.dispatch('app/hideAddTaskDialog')
if (this.form.newTaskShowDownloading) {
this.$router.push({
path: '/task/active'
})
}
} catch (err) {
this.$msg.error(err.message)
}
})
}
}
@@ -391,14 +389,17 @@
</script>
<style lang="scss">
.add-task-dialog {
.el-dialog.add-task-dialog {
max-width: 632px;
.el-tabs__header {
user-select: none;
}
.el-input-number.el-input-number--mini {
width: 100%;
}
.el-dialog__footer {
padding-top: 20px;
background: #f5f5f5;
background-color: $--add-task-dialog-footer-background;
border-radius: 0 0 5px 5px;
}
.dialog-footer {
-3
View File
@@ -47,9 +47,6 @@
'status': 'onStatusChange'
},
methods: {
open (link) {
this.$electron.shell.openExternal(link)
},
onStatusChange () {
this.changeCurrentList()
},
+212 -9
View File
@@ -3,6 +3,7 @@
class="upload-torrent"
drag
action="/"
v-if="isTorrentsEmpty"
:limit="1"
:multiple="false"
accept=".torrent"
@@ -16,23 +17,115 @@
<div class="torrent-name" v-if="name">{{ name }}</div>
</div>
</el-upload>
<div
class="selective-torrent"
v-else
>
<el-row class="torrent-info" :gutter="12">
<el-col class="torrent-name" :span="20">
<el-tooltip class="item" effect="dark" :content="name" placement="top">
<span>{{ name }}</span>
</el-tooltip>
</el-col>
<el-col class="torrent-actions" :span="4">
<span
@click="handleTrashClick"
>
<mo-icon name="trash" width="14" height="14" />
</span>
</el-col>
</el-row>
<div class="torrent-file-list">
<el-table
stripe
ref="torrentTable"
height="200"
:data="files"
tooltip-effect="dark"
style="width: 100%"
@row-dblclick="handleRowDbClick"
@selection-change="handleSelectionChange">
<el-table-column
type="selection"
width="35">
</el-table-column>
<el-table-column
:label="$t('task.file-name')"
show-overflow-tooltip>
<template slot-scope="scope">{{ scope.row.name }}</template>
</el-table-column>
<el-table-column
:label="$t('task.file-extension')"
width="80">
<template slot-scope="scope">{{ scope.row.extension | removeExtensionDot }}</template>
</el-table-column>
<el-table-column
:label="$t('task.file-size')"
width="90">
<template slot-scope="scope">{{ scope.row.length | bytesToSize }}</template>
</el-table-column>
</el-table>
</div>
<el-row :gutter="12">
<el-col class="file-filters" :span="8">
<el-button-group>
<el-button @click="toggleVideoSelection()">
<mo-icon name="video" width="12" height="12" />
</el-button>
<el-button @click="toggleAudioSelection()">
<mo-icon name="audio" width="12" height="12" />
</el-button>
<el-button @click="toggleImageSelection()">
<mo-icon name="image" width="12" height="12" />
</el-button>
</el-button-group>
</el-col>
<el-col :span="16" style="text-align: right">
{{ $t('task.selected-files-sum', { selectedFilesCount, selectedFilesTotalSize }) }}
</el-col>
</el-row>
</div>
</template>
<script>
import { mapState } from 'vuex'
import { isEmpty } from 'lodash'
import parseTorrent from 'parse-torrent'
import '@/components/Icons/inbox'
import { getAsBase64, buildFileList } from '@shared/utils'
import '@/components/Icons/video'
import '@/components/Icons/audio'
import '@/components/Icons/image'
import {
NONE_SELECTED_FILES,
SELECTED_ALL_FILES
} from '@shared/constants'
import {
buildFileList,
listTorrentFiles,
bytesToSize,
filterVideoFiles,
filterAudioFiles,
filterImageFiles,
getAsBase64,
removeExtensionDot
} from '@shared/utils'
export default {
name: 'mo-select-torrent',
components: {
},
filters: {
bytesToSize,
removeExtensionDot
},
props: {
},
data () {
return {
name: ''
name: '',
currentTorrent: '',
files: [],
selectedFiles: []
}
},
computed: {
@@ -41,15 +134,40 @@
}),
...mapState('app', {
torrents: state => state.addTaskTorrents
})
}),
isTorrentsEmpty: function () {
return this.torrents.length === 0
},
selectedFilesCount: function () {
return this.selectedFiles.length
},
selectedFilesTotalSize: function () {
const result = this.selectedFiles.reduce((acc, cur) => {
return acc + cur.length
}, 0)
return bytesToSize(result)
},
selectedFileIndex: function () {
const { files, selectedFiles } = this
if (files.length === 0 || selectedFiles.length === 0) {
return NONE_SELECTED_FILES
}
if (files.length === selectedFiles.length) {
return SELECTED_ALL_FILES
}
const indexArr = this.selectedFiles.map((item) => item.idx)
const result = indexArr.join(',')
return result
}
},
watch: {
torrents (fileList) {
const file = fileList[0]
if (fileList.length === 0) {
this.name = ''
this.reset()
return
}
const file = fileList[0]
if (!file.raw) {
return
}
@@ -57,21 +175,68 @@
parseTorrent.remote(file.raw, (err, parsedTorrent) => {
if (err) throw err
console.log(parsedTorrent)
})
this.files = listTorrentFiles(parsedTorrent.files)
this.$refs.torrentTable.toggleAllSelection()
getAsBase64(file.raw, (torrent) => {
this.name = file.name
this.$emit('change', torrent, file, fileList)
getAsBase64(file.raw, (torrent) => {
this.name = file.name
this.currentTorrent = torrent
this.$emit('change', torrent, SELECTED_ALL_FILES)
})
})
},
selectedFileIndex () {
const { currentTorrent, selectedFileIndex } = this
this.$emit('change', currentTorrent, selectedFileIndex)
}
},
methods: {
reset () {
this.name = ''
this.currentTorrent = ''
this.files = []
if (this.$refs.torrentTable) {
this.$refs.torrentTable.clearSelection()
}
this.$emit('change', '', NONE_SELECTED_FILES)
},
handleChange (file, fileList) {
this.$store.dispatch('app/addTaskAddTorrents', { fileList })
},
handleExceed (files) {
const fileList = buildFileList(files[0])
this.$store.dispatch('app/addTaskAddTorrents', { fileList })
},
handleTrashClick () {
this.$store.dispatch('app/addTaskAddTorrents', { fileList: [] })
},
toggleSelection (rows) {
if (isEmpty(rows)) {
this.$refs.torrentTable.clearSelection()
} else {
this.$refs.torrentTable.clearSelection()
rows.forEach(row => {
this.$refs.torrentTable.toggleRowSelection(row)
})
}
},
toggleVideoSelection () {
const filtered = filterVideoFiles(this.files)
this.toggleSelection(filtered)
},
toggleAudioSelection () {
const filtered = filterAudioFiles(this.files)
this.toggleSelection(filtered)
},
toggleImageSelection () {
const filtered = filterImageFiles(this.files)
this.toggleSelection(filtered)
},
handleRowDbClick (row, column, event) {
this.$refs.torrentTable.toggleRowSelection(row)
},
handleSelectionChange (val) {
this.selectedFiles = val
}
}
}
@@ -99,4 +264,42 @@
line-height: 16px;
}
}
.selective-torrent {
.torrent-name {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.torrent-info {
margin-bottom: 15px;
font-size: 12px;
line-height: 16px;
}
.torrent-actions {
text-align: right;
line-height: 16px;
&> span {
cursor: pointer;
display: inline-block;
vertical-align: middle;
height: 14px;
padding: 1px;
}
}
}
.file-filters {
button {
font-size: 0;
}
}
.torrent-file-list {
border: 1px solid #ebeef5;
border-bottom: none;
overflow-x: hidden;
overflow-y: scroll;
margin-bottom: 8px;
.el-table th {
padding: 2px 0;
}
}
</style>
+2 -5
View File
@@ -119,9 +119,6 @@
</script>
<style lang="scss">
@import '../Theme/Variables';
@import '../Theme/Darkness/Variables';
.task-actions {
position: absolute;
top: 44px;
@@ -132,7 +129,7 @@
user-select: none;
cursor: default;
text-align: right;
color: #4D515A;
color: $--task-action-color;
transition: all 0.25s;
&> i {
display: inline-block;
@@ -142,7 +139,7 @@
cursor: pointer;
outline: none;
&:hover {
color: $--color-primary;
color: $--task-action-hover-color;
}
}
}
+2 -1
View File
@@ -116,7 +116,8 @@
.task-item {
position: relative;
padding: 16px 12px;
border: 1px solid #ccc;
background-color: $--task-item-background;
border: 1px solid $--task-item-border-color;
border-radius: 6px;
margin-bottom: 16px;
transition: $--border-transition-base;
+159 -53
View File
@@ -1,36 +1,38 @@
<template>
<div class="task-item-actions" v-on:dblclick.stop="() => null">
<!-- <i @click.stop="onMoreClick">
<mo-icon name="more" width="14" height="14" />
</i> -->
<i @click.stop="onInfoClick" v-if="mode === 'LIST'">
<mo-icon name="info-circle" width="14" height="14" />
</i>
<i @click.stop="onLinkClick">
<mo-icon name="link" width="14" height="14" />
</i>
<i v-if="isRenderer()" @click.stop="onFolderClick">
<mo-icon name="folder" width="14" height="14" />
</i>
<i v-if="task.status ==='complete' ||
task.status ==='removed' ||
task.status ==='error'"
@click.stop="onTrashClick">
<mo-icon name="trash" width="14" height="14" />
</i>
<i v-if="task.status ==='active' ||
task.status ==='waiting' ||
task.status ==='paused'"
@click.stop="onDeleteClick">
<mo-icon name="delete" width="14" height="14" />
</i>
<i v-if="task.status ==='active'" @click.stop="onPauseClick">
<mo-icon name="task-pause-line" width="14" height="14" />
</i>
<i v-if="task.status ==='waiting' || task.status ==='paused'" @click.stop="onResumeClick">
<mo-icon name="task-start-line" width="14" height="14" />
</i>
</div>
<ul :key="task.gid" class="task-item-actions" v-on:dblclick.stop="() => null">
<li v-for="action in taskActions" :key="action" class="task-item-action">
<i v-if="action ==='PAUSE'" @click.stop="onPauseClick">
<mo-icon name="task-pause-line" width="14" height="14" />
</i>
<i v-if="action ==='STOP'" @click.stop="onStopClick">
<mo-icon name="task-stop-line" width="14" height="14" />
</i>
<i v-if="action === 'RESUME'" @click.stop="onResumeClick">
<mo-icon name="task-start-line" width="14" height="14" />
</i>
<i v-if="action === 'RESTART'" @click="onRestartClick">
<mo-icon name="task-restart" width="14" height="14" />
</i>
<i v-if="action === 'DELETE'" @click.stop="onDeleteClick">
<mo-icon name="delete" width="14" height="14" />
</i>
<i v-if="action === 'TRASH'" @click.stop="onTrashClick">
<mo-icon name="trash" width="14" height="14" />
</i>
<i v-if="action ==='FOLDER'" @click.stop="onFolderClick">
<mo-icon name="folder" width="14" height="14" />
</i>
<i v-if="action ==='LINK'" @click.stop="onLinkClick">
<mo-icon name="link" width="14" height="14" />
</i>
<i v-if="action ==='INFO'" @click.stop="onInfoClick">
<mo-icon name="info-circle" width="14" height="14" />
</i>
<i v-if="action ==='MORE'" @click.stop="onMoreClick">
<mo-icon name="more" width="14" height="14" />
</i>
</li>
</ul>
</template>
<script>
@@ -38,6 +40,8 @@
import * as clipboard from 'clipboard-polyfill'
import '@/components/Icons/task-start-line'
import '@/components/Icons/task-pause-line'
import '@/components/Icons/task-stop-line'
import '@/components/Icons/task-restart'
import '@/components/Icons/delete'
import '@/components/Icons/folder'
import '@/components/Icons/link'
@@ -49,11 +53,23 @@
moveTaskFilesToTrash
} from '@/components/Native/utils'
import {
checkTaskIsSeeder,
getTaskFullPath,
getTaskName,
getTaskUri,
getTaskFullPath
parseHeader
} from '@shared/utils'
const taskActionsMap = {
active: ['PAUSE', 'DELETE'],
paused: ['RESUME', 'DELETE'],
waiting: ['RESUME', 'DELETE'],
error: ['RESTART', 'TRASH'],
complete: ['RESTART', 'TRASH'],
removed: ['RESTART', 'TRASH'],
seeding: ['STOP', 'DELETE']
}
export default {
name: 'mo-task-item-actions',
props: {
@@ -67,23 +83,48 @@
}
},
computed: {
taskName: function () {
taskName () {
return getTaskName(this.task)
},
path: function () {
path () {
return getTaskFullPath(this.task)
},
isSeeder () {
return checkTaskIsSeeder(this.task)
},
taskStatus () {
const { task, isSeeder } = this
if (isSeeder) {
return 'seeding'
} else {
return task.status
}
},
taskCommonActions () {
let result = is.renderer() ? ['FOLDER'] : []
result = (this.mode === 'LIST')
? [...result, 'LINK', 'INFO']
: [...result, 'LINK']
return result
},
taskActions () {
const { taskStatus, taskCommonActions } = this
const actions = taskActionsMap[taskStatus] || []
const result = [...actions, ...taskCommonActions].reverse()
return result
}
},
methods: {
isRenderer: is.renderer,
deleteTaskFiles: function (task) {
deleteTaskFiles (task) {
moveTaskFilesToTrash(task, {
pathErrorMsg: this.$t('task.file-path-error'),
delFailMsg: this.$t('task.remove-task-file-fail'),
delConfigFailMsg: this.$t('task.remove-task-config-file-fail')
})
},
removeTaskItem: function (task, isRemoveWithFiles) {
removeTaskItem (task, isRemoveWithFiles) {
this.$store.dispatch('task/removeTask', this.task)
.then(() => {
if (isRemoveWithFiles) {
@@ -101,7 +142,7 @@
}
})
},
removeTaskRecord: function (task, isRemoveWithFiles) {
removeTaskRecord (task, isRemoveWithFiles) {
this.$store.dispatch('task/removeTaskRecord', this.task)
.then(() => {
if (isRemoveWithFiles) {
@@ -119,7 +160,7 @@
}
})
},
onResumeClick: function () {
onResumeClick () {
this.$store.dispatch('task/resumeTask', this.task)
.catch(({ code }) => {
if (code === 1) {
@@ -129,7 +170,72 @@
}
})
},
onPauseClick: function () {
onRestartClick (event) {
const { task, taskName } = this
const { gid, status } = task
const uri = getTaskUri(task)
const isNeedShowDialog = status === 'complete' || !!event.altKey
this.$store.dispatch('task/getTaskOption', gid)
.then((data) => {
console.log('getTaskOption===>', data)
const { dir, header, split } = data
const options = {
dir,
header,
split,
out: taskName
}
if (isNeedShowDialog) {
this.showAddTaskDialog(uri, options)
} else {
this.directAddTask(uri, options)
this.$store.dispatch('task/removeTaskRecord', task)
}
})
},
directAddTask (uri, options = {}) {
const uris = [uri]
const payload = {
uris,
options: {
...options
}
}
this.$store.dispatch('task/addUri', payload)
.catch((err) => {
this.$msg.error(err.message)
})
},
showAddTaskDialog (uri, options = {}) {
const {
header,
...rest
} = options
const headers = parseHeader(header)
const newOptions = {
...rest,
...headers
}
this.$store.dispatch('app/updateAddTaskUrl', uri)
this.$store.dispatch('app/updateAddTaskOptions', newOptions)
this.$store.dispatch('app/showAddTaskDialog', 'uri')
},
onPauseClick () {
this.pauseTask()
},
onStopClick () {
this.stopSeeding()
},
stopSeeding () {
if (!this.isSeeder) {
return
}
this.$store.dispatch('task/stopSeeding', this.task)
},
pauseTask () {
const { taskName } = this
this.$msg.info(this.$t('task.download-pause-message', { taskName }))
this.$store.dispatch('task/pauseTask', this.task)
@@ -139,7 +245,7 @@
}
})
},
onDeleteClick: function () {
onDeleteClick () {
const self = this
const { task } = this
this.$electron.remote.dialog.showMessageBox({
@@ -155,7 +261,7 @@
}
})
},
onTrashClick: function () {
onTrashClick () {
const self = this
const { task } = this
this.$electron.remote.dialog.showMessageBox({
@@ -171,12 +277,12 @@
}
})
},
onFolderClick: function () {
onFolderClick () {
showItemInFolder(this.path, {
errorMsg: this.$t('task.file-not-exist')
})
},
onLinkClick: function () {
onLinkClick () {
this.$store.dispatch('app/fetchEngineOptions')
.then((data) => {
const { btTracker } = data
@@ -187,10 +293,10 @@
})
})
},
onInfoClick: function () {
onInfoClick () {
this.$store.dispatch('task/showTaskItemInfoDialog', this.task)
},
onMoreClick: function () {
onMoreClick () {
console.log('onMoreClick===>')
}
}
@@ -207,18 +313,18 @@
cursor: default;
text-align: right;
direction: rtl;
border: 1px solid #F5F5F5;
color: #9B9B9B;
background-color: #fff;
border: 1px solid $--task-item-action-border-color;
color: $--task-item-action-color;
background-color: $--task-item-action-background;
border-radius: 14px;
transition: $--all-transition;
&:hover {
border-color: $--task-item-hover-border-color;
color: #fff;
background-color: $--task-item-hover-background;
border-color: $--task-item-action-hover-border-color;
color: $--task-item-action-hover-color;
background-color: $--task-item-action-hover-background;
width: auto;
}
&> i {
&> .task-item-action {
display: inline-block;
padding: 5px;
margin: 0 4px;
@@ -41,6 +41,7 @@
justify-content: center;
font-size: 14px;
color: #555;
user-select: none;
}
.no-task-inner {
width: 100%;
@@ -3,7 +3,7 @@
v-if="status === 'active'"
:percentage="percent"
:show-text="false"
status="text"
status="success"
:color="color">
</el-progress>
<el-progress
-13
View File
@@ -1,13 +0,0 @@
@import '~normalize.css/normalize.css';
@import './Variables.scss';
@import "~element-ui/packages/theme-chalk/src/index";
@import './Base.scss';
/* Theme Capriccio
-------------------------- */
@import './Default.scss';
/* Theme Darkness
-------------------------- */
@import './Darkness.scss';
-98
View File
@@ -1,98 +0,0 @@
html,
body {
height: 100%;
padding: 0;
}
body {
font-family: 'Monospaced Number', 'Chinese Quote', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', 'Helvetica Neue', Helvetica, Arial, sans-serif;
font-variant: tabular-nums;
}
img {
width: auto;
height: auto;
max-width: 100%;
max-height: 100%;
}
.clearfix {
@include clearfix();
}
#app,
#container {
height: 100%;
}
.draggable {
-webkit-app-region: drag;
-webkit-user-select: none;
}
.el-progress-bar__inner {
transition: all 0.4s cubic-bezier(0.08, 0.82, 0.17, 1) 0s;
}
.el-progress--line.is-text {
.el-progress-bar__inner::before {
content: '';
opacity: 0;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: #fff;
border-radius: 10px;
animation: mo-progress-active 2.4s cubic-bezier(0.23, 1, 0.32, 1) infinite;
}
}
@keyframes mo-progress-active {
0% {
opacity: 0.1;
width: 0;
}
20% {
opacity: 0.5;
width: 0;
}
100% {
opacity: 0;
width: 100%;
}
}
.el-message-box__wrapper {
outline: none;
}
.el-message__content {
line-height: 18px;
word-break: break-all;
}
.tab-title-dialog {
.el-dialog__header {
padding: 0 20px;
position: relative;
z-index: 10;
}
.el-dialog__body {
padding-top: 10px;
padding-bottom: 10px;
}
}
.el-form-item--mini .el-form-item__info {
padding-top: 1px;
}
.el-form-item__info {
font-size: 12px;
line-height: 1;
padding-top: 4px;
color: $--color-info;
}
+393
View File
@@ -0,0 +1,393 @@
.theme-dark {
.title-bar .window-actions > li {
color: $--dk-titlebar-actions-color;
&:hover {
background-color: $--dk-titlebar-actions-active-background;
}
&.win-close-btn:hover {
background-color: $--dk-titlebar-close-active-background;
}
}
.logo > a {
color: $--dk-app-logo-color;
}
.app-info .app-version span {
color: $--dk-app-version-color;
}
.app-info .engine-info {
h4 {
color: $--dk-app-engine-title-color;
}
ul {
color: $--dk-app-engine-info-color;
}
}
.copyright a {
color: $--dk-app-copyright-color;
}
.aside {
background-color: $--dk-aside-background;
}
.subnav {
background-color: $--dk-subnav-background;
color: $--dk-subnav-text-color;
}
.subnav-inner {
h3 {
color: $--dk-subnav-title-color;
}
ul li {
&:hover,
&.active {
background-color: $--dk-subnav-active-background;
i,
span,
svg {
color: $--dk-subnav-active-text-color;
}
}
}
}
.form-actions {
background: $--dk-form-actions-background;
}
.panel {
background-color: $--dk-panel-background;
}
.panel .panel-header {
border-bottom-color: rgba(255, 255, 255, 0.1);
h4 {
color: $--dk-panel-title-color;
}
}
.task-item {
background-color: $--dk-task-item-backgroud;
border-color: $--dk-task-item-border-color;
&:hover {
border-color: $--dk-task-item-hover-border-color;
}
}
.task-name {
color: $--dk-task-item-text-color;
}
.task-actions {
color: $--dk-task-action-color;
}
.task-item-actions {
background-color: $--dk-task-item-action-background;
border-color: $--dk-task-item-action-border-color;
&:hover {
border-color: $--dk-task-item-action-hover-border-color;
color: $--dk-task-item-action-hover-color;
background-color: $--dk-task-item-action-hover-background;
}
}
.mo-speedometer {
background-color: $--dk-speedometer-background;
border-color: #5f5f5f;
}
.no-task {
color: $--dk-no-task-color;
}
/* Element UI
-------------------------- */
.el-progress-bar__outer {
background-color: #4a4a4a;
}
.el-input__inner,
.el-textarea__inner {
background-color: #373737;
border-color: #5f5f5f;
color: #eee;
&:focus {
outline: none;
border-color: $--color-primary;
}
&::placeholder {
color: #777;
}
}
.el-input.is-disabled .el-input__inner {
background-color: #373737;
border-color: #5f5f5f;
color: #aaa;
}
.el-input-group__append,
.el-input-group__prepend {
background-color: #333;
border-color: #5f5f5f;
color: #e7e7e7;
}
.el-input-number__increase,
.el-input-number__decrease {
background-color: #333;
color: #e7e7e7;
}
.el-input-number__decrease {
border-right-color: #5f5f5f;
}
.el-input-number__increase {
border-left-color: #5f5f5f;
}
.el-input-number.is-controls-right .el-input-number__increase {
border-left-color: #5f5f5f;
border-bottom-color: #5f5f5f;
}
.el-input-number.is-controls-right .el-input-number__decrease {
border-left-color: #5f5f5f;
}
.el-form-item__label,
.el-checkbox,
.el-radio {
color: $--dk-preference-form-text-color;
}
.el-switch__core,
.el-checkbox__inner {
border-color: #606060;
background-color: #5c5d5f;
}
.el-select .el-input .el-select__caret {
color: #e7e7e7;
}
.el-select-dropdown {
background-color: #3d3d3d;
border-color: #606060;
}
.el-select-dropdown__item {
color: #eee;
&.selected {
color: $--color-primary;
}
&.hover,
&:hover {
background-color: $--color-primary;
color: #fff;
}
}
.el-upload-dragger {
background-color: #2d2d2d;
border-color: #606060;
> i,
.el-upload__text {
color: #a2a3a4;
}
}
.el-popper[x-placement^="top"] .popper__arrow {
border-top-color: #606060;
&:after {
border-top-color: #3d3d3d;
}
}
.el-popper[x-placement^="right"] .popper__arrow {
border-right-color: #606060;
&:after {
border-right-color: #3d3d3d;
}
}
.el-popper[x-placement^="bottom"] .popper__arrow {
border-bottom-color: #606060;
&:after {
border-bottom-color: #3d3d3d;
}
}
.el-popper[x-placement^="left"] .popper__arrow {
border-left-color: #606060;
&:after {
border-left-color: #3d3d3d;
}
}
.el-button {
background-color: #5b5b5b;
border-color: #606060;
color: #e6e6e6;
&:hover,
&:focus {
color: $--color-primary-light-4;
border-color: #606060;
background-color: #333;
}
}
.el-button--primary {
color: #eee;
background-color: $--color-primary;
border-color: $--color-primary;
&:hover,
&:focus {
background: $--color-primary;
border-color: $--color-primary;
color: #fff;
}
}
.el-button--danger.is-plain {
color: #ff6157;
background-color: #5b5b5b;
border-color: #ffc0bc;
}
.el-button--danger.is-plain:hover,
.el-button--danger.is-plain:focus {
background-color: #ff6157;
border-color: #ff6157;
color: #fff;
}
/* Message */
.el-message {
background-color: #2d2d2d;
border-color: #606060;
}
.el-message__closeBtn {
color: rgba(255, 255, 255, 0.3);
}
.el-message--info .el-message__content {
color: #a2a3a4;
}
.el-message--success {
background-color: #2d2d2d;
border-color: #606060;
.el-message__content {
color: #67c23a;
}
}
.el-message--warning {
background-color: #2d2d2d;
border-color: #606060;
.el-message__content {
color: #e6a23c;
}
}
.el-message--error {
background-color: #2d2d2d;
border-color: #606060;
.el-message__content {
color: #f56c6c;
}
}
/* Dialog */
.el-dialog {
background-color: $--dk-dialog-background;
.el-dialog__body {
color: $--dk-dialog-text-color;
}
}
.add-task-dialog .el-dialog__footer {
background-color: $--dk-add-task-dialog-footer-background;
}
.torrent-file-list {
border-color: $--dk-table-border-color;
}
.el-table {
background-color: $--dk-table-background;
color: $--dk-table-text-color;
tr {
background-color: $--dk-table-background;
}
th {
background-color: $--dk-table-th-background;
color: $--dk-table-text-color;
}
th.is-leaf, td {
background-color: $--dk-table-background;
color: $--dk-table-text-color;
border-bottom-color: $--dk-table-border-color;
}
}
.el-table thead th.is-leaf {
background-color: $--dk-table-th-background;
}
.el-table--enable-row-hover .el-table__body tr:hover > td,
.el-table--enable-row-hover .el-table__body tr.el-table__row--striped:hover > td {
background-color: $--dk-table-hover-background;
}
.el-table--group::after, .el-table--border::after, .el-table::before {
background-color: $--dk-table-border-color;
}
.el-table--striped .el-table__body tr.el-table__row--striped td {
background-color: $--dk-table-striped-background;
}
/* Tabs */
.el-tabs__item {
color: #a2a3a4;
&.is-active {
color: $--color-primary;
}
}
.el-tabs__nav-wrap::after {
background-color: #606060;
}
.form-preference .el-form-item__content {
color: $--dk-preference-form-text-color;
}
.form-preference .el-switch__label {
color: $--dk-preference-form-text-color;
}
.form-preference .el-checkbox__input.is-checked + .el-checkbox__label {
color: $--dk-preference-form-text-color;
}
.form-preference .el-form-item {
a {
color: #dfdfdf;
&:hover {
color: #eee;
}
&:active {
color: #eee;
}
}
}
}
@@ -0,0 +1,77 @@
/* App
-------------------------- */
$--dk-app-background: transparent !default;
$--dk-titlebar-actions-color: #eee !default;
$--dk-titlebar-close-active-color: #fff !default;
$--dk-titlebar-actions-active-background: rgba(0, 0, 0, 0.9) !default;
$--dk-titlebar-close-active-background: $--color-danger !default;
$--dk-app-logo-color: #eee !default;
$--dk-app-version-color: #a2a3a4 !default;
$--dk-app-engine-title-color: #a2a3a4 !default;
$--dk-app-engine-info-color: #777 !default;
$--dk-app-copyright-color: #a2a3a4 !default;
/* Aside
-------------------------- */
$--dk-aside-background: rgba(0, 0, 0, 0.9) !default;
$--dk-aside-text-color: #fff !default;
/* SubNav
-------------------------- */
$--dk-subnav-background: #2D2D2D !default;
$--dk-subnav-title-color: #fff !default;
$--dk-subnav-text-color: #aaa !default;
$--dk-subnav-active-text-color: #fff !default;
$--dk-subnav-active-background: #444 !default;
/* Panel
-------------------------- */
$--dk-panel-background: #343434 !default;
$--dk-panel-title-color: #fff !default;
$--dk-panel-border-color: #EBECF0 !default;
/* Form Actions
-------------------------- */
$--dk-form-actions-background: #343434 !default;
/* Task
-------------------------- */
$--dk-task-action-color: #eee !default;
$--dk-task-action-hover-color: $--color-primary !default;
$--dk-task-item-backgroud: #2d2d2d !default;
$--dk-task-item-border-color: #555 !default;
$--dk-task-item-hover-border-color: $--color-primary !default;
$--dk-task-item-hover-background: $--color-primary !default;
$--dk-task-item-text-color: #eee !default;
$--dk-task-item-action-background: #4a4a4a !default;
$--dk-task-item-action-hover-background: $--color-primary !default;
$--dk-task-item-action-border-color: #5f5f5f !default;
$--dk-task-item-action-hover-border-color: $--color-primary !default;
$--dk-task-item-action-color: #eee !default;
$--dk-task-item-action-hover-color: #fff !default;
$--dk-no-task-color: #aaa !default;
$--dk-add-task-dialog-footer-background: #4a4a4a !default;
/* Preference
-------------------------- */
$--dk-preference-form-text-color: #dfdfdf !default;
/* Speedometer
-------------------------- */
$--dk-speedometer-background: #333 !default;
$--dk-speedometer-border-color: #ccc !default;
$--dk-speedometer-hover-border-color: #9b9b9b !default;
$--dk-speedometer-primary-color: $--color-primary !default;
$--dk-speedometer-stopped-color: #9b9b9b !default;
$--dk-speedometer-text-color: #9b9b9b !default;
/* Element UI
-------------------------- */
$--dk-dialog-background: #343434 !default;
$--dk-dialog-text-color: #9b9b9b !default;
$--dk-table-background: #2a2b2c !default;
$--dk-table-striped-background: #1f2122 !default;
$--dk-table-hover-background: #565656 !default;
$--dk-table-th-background: #1f2122 !default;
$--dk-table-text-color: #9b9b9b !default;
$--dk-table-border-color: #565656 !default;
@@ -1 +0,0 @@
@import './Darkness/Variables.scss';
@@ -1,35 +0,0 @@
/* App
-------------------------- */
$--dk-app-background: transparent !default;
/* Aside
-------------------------- */
$--dk-aside-background: rgba(0, 0, 0, 0.75) !default;
$--dk-aside-text-color: #fff !default;
/* SubNav
-------------------------- */
$--dk-subnav-background: #F4F5F7 !default;
$--dk-subnav-title-color: $--color-primary-dark-6 !default;
$--dk-subnav-text-color: #4D515A !default;
$--dk-subnav-active-text-color: $--color-primary !default;
/* Panel
-------------------------- */
$--panel-background: $--color-white !default;
$--dk-panel-title-color: $--color-primary-dark-6 !default;
$--dk-panel-border-color: #EBECF0 !default;
/* Task
-------------------------- */
$--dk-task-item-border-color: #ccc !default;
$--dk-task-item-hover-border-color: $--color-primary !default;
$--dk-task-item-hover-background: $--color-primary !default;
/* Speedometer
-------------------------- */
$--dk-speedometer-background: $--color-white !default;
$--dk-speedometer-border-color: #ccc !default;
$--dk-speedometer-hover-border-color: #9b9b9b !default;
$--dk-speedometer-primary-color: $--color-primary !default;
$--dk-speedometer-text-color: #9b9b9b !default;
+169 -23
View File
@@ -1,30 +1,148 @@
:root {
--app-background: '';
--aside-background: '';
--aside-text-color: '';
--subnav-background: '';
--subnav-title-color: '';
--subnav-text-color: '';
--subnav-active-text-color: '';
--panel-background: '';
--panel-title-color: '';
--panel-border-color: '';
--task-item-border-color: '';
--task-item-hover-border-color: '';
--task-item-hover-background: '';
--speedometer-background: '';
--speedometer-border-color: '';
--speedometer-hover-border-color: '';
--speedometer-primary-color: '';
--speedometer-text-color: '';
html,
body {
height: 100%;
padding: 0;
}
body {
font-family: "Monospaced Number", "Chinese Quote", -apple-system,
BlinkMacSystemFont, "Segoe UI", Roboto, "PingFang SC", "Hiragino Sans GB",
"Microsoft YaHei", "Helvetica Neue", Helvetica, Arial, sans-serif;
font-variant: tabular-nums;
}
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-thumb {
border-radius: 8px;
background-color: rgba(0, 0, 0, 0.4);
}
::-webkit-scrollbar-thumb:window-inactive {
background-color: rgba(0, 0, 0, 0.25);
}
::-webkit-scrollbar-corner {
background: transparent;
}
::-webkit-resizer {
display: none;
}
img {
width: auto;
height: auto;
max-width: 100%;
max-height: 100%;
}
.clearfix {
@include clearfix();
}
/* Element UI
-------------------------- */
.el-progress-bar__inner {
transition: all 0.4s cubic-bezier(0.08, 0.82, 0.17, 1) 0s;
}
.el-progress--line.is-text {
.el-progress-bar__inner::before {
content: "";
opacity: 0;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: #fff;
border-radius: 10px;
animation: mo-progress-active 2.4s cubic-bezier(0.23, 1, 0.32, 1) infinite;
}
}
@keyframes mo-progress-active {
0% {
opacity: 0.1;
width: 0;
}
20% {
opacity: 0.5;
width: 0;
}
100% {
opacity: 0;
width: 100%;
}
}
.el-message-box__wrapper {
outline: none;
}
.el-message__content {
line-height: 18px;
word-break: break-all;
a {
color: $--link-color;
}
}
.tab-title-dialog {
.el-dialog__header {
padding: 0 20px;
position: relative;
z-index: 10;
}
.el-dialog__body {
padding-top: 10px;
padding-bottom: 10px;
}
}
.el-form-item--mini .el-form-item__info {
padding-top: 1px;
}
.el-form-item__info {
font-size: 12px;
line-height: 1;
padding-top: 4px;
color: $--color-info;
}
.el-input-number.el-input-number--mini {
&.is-controls-left .el-input__inner {
padding-left: 34px;
padding-right: 5px;
}
&.is-controls-right .el-input__inner {
padding-left: 5px;
padding-right: 34px;
}
}
/* App Main
-------------------------- */
#app,
#container {
height: 100%;
background-color: $--app-background;
}
.draggable {
-webkit-app-region: drag;
-webkit-user-select: none;
}
.non-draggable {
-webkit-app-region: no-drag;
}
.aside {
background-color: $--aside-background;
color: $--aside-text-color;
@@ -38,6 +156,7 @@
.subnav-inner {
margin-top: 44px;
padding: 0 16px;
user-select: none;
h3 {
font-size: 16px;
@@ -51,26 +170,26 @@
list-style: none;
padding: 0;
margin: 0;
user-select: none;
cursor: default;
li {
margin-bottom: 8px;
padding: 8px 10px;
font-size: 14px;
font-size: 0;
line-height: 20px;
border-radius: 3px;
cursor: pointer;
i,
span {
vertical-align: middle;
display: inline-block;
vertical-align: middle;
font-size: 14px;
}
&:hover,
&.active {
background-color: #EAECF0;
background-color: $--subnav-active-background;
i,
span,
@@ -96,3 +215,30 @@
.content {
padding: 0;
}
.panel {
background: $--panel-background;
.panel-header {
position: relative;
padding: 44px 0 12px;
margin: 0 36px;
border-bottom: 2px solid $--panel-border-color;
user-select: none;
h4 {
margin: 0;
color: $--panel-title-color;
font-size: 16px;
font-weight: normal;
line-height: 24px;
}
}
.panel-content {
position: relative;
padding: 16px 36px 64px;
height: 100%;
}
}
.form-actions {
background: $--form-actions-background;
}
+8 -6
View File
@@ -1,13 +1,15 @@
/* Normalize.css
-------------------------- */
@import '~normalize.css/normalize.css';
@import './Variables.scss';
/* Element UI
-------------------------- */
@import "~element-ui/packages/theme-chalk/src/index";
@import './Base.scss';
/* Theme Capriccio
/* Theme Light (default)
-------------------------- */
@import './Default.scss';
/* Theme Darkness
/* Theme Dark
-------------------------- */
@import './Darkness.scss';
@import './Dark.scss';
@@ -0,0 +1,70 @@
/* App
-------------------------- */
$--app-background: transparent !default;
$--titlebar-actions-color: #1f1f1f !default;
$--titlebar-actions-active-background: #eee !default;
$--titlebar-close-active-color: #fff !default;
$--titlebar-close-active-background: $--color-danger !default;
$--app-logo-color: #4D515A !default;
$--app-version-color: $--color-text-regular !default;
$--app-engine-title-color: $--color-text-regular !default;
$--app-engine-info-color: $--color-text-secondary !default;
$--app-copyright-color: $--color-text-regular !default;
/* Aside
-------------------------- */
$--aside-background: rgba(0, 0, 0, 0.8) !default;
$--aside-text-color: #fff !default;
/* SubNav
-------------------------- */
$--subnav-background: #F4F5F7 !default;
$--subnav-title-color: $--color-text-primary !default;
$--subnav-text-color: #4D515A !default;
$--subnav-active-text-color: $--color-primary !default;
$--subnav-active-background: #EAECF0 !default;
/* Panel
-------------------------- */
$--panel-background: $--color-white !default;
$--panel-title-color: $--color-text-primary !default;
$--panel-border-color: rgba(0, 0, 0, 0.1) !default;
/* Form Actions
-------------------------- */
$--form-actions-background: $--color-white !default;
/* Task
-------------------------- */
$--task-action-color: #4d515a !default;
$--task-action-hover-color: $--color-primary !default;
$--task-item-background: #fff !default;
$--task-item-border-color: #ccc !default;
$--task-item-hover-border-color: $--color-primary !default;
$--task-item-hover-background: $--color-primary !default;
$--task-item-text-color: #505753 !default;
$--task-item-action-background: #fff !default;
$--task-item-action-hover-background: $--color-primary !default;
$--task-item-action-border-color: #F5F5F5 !default;
$--task-item-action-hover-border-color: $--color-primary !default;
$--task-item-action-color: #9B9B9B !default;
$--task-item-action-hover-color: #fff !default;
$--no-task-color: #eee !default;
$--add-task-dialog-footer-background: #f5f5f5 !default;
/* Preference
-------------------------- */
$--preference-form-text-color: #4c4c4c;
/* Speedometer
-------------------------- */
$--speedometer-background: $--color-white !default;
$--speedometer-border-color: #ccc !default;
$--speedometer-hover-border-color: #9b9b9b !default;
$--speedometer-primary-color: $--color-primary !default;
$--speedometer-stopped-color: #9b9b9b !default;
$--speedometer-text-color: #9b9b9b !default;
/* Element UI
-------------------------- */
$--dialog-background: #fff !default;
File diff suppressed because it is too large Load Diff
+43 -4
View File
@@ -4,7 +4,7 @@
v-if="isRenderer()"
:showActions="showWindowActions"
/>
<router-view></router-view>
<router-view />
<mo-engine-client
:secret="rpcSecret"
/>
@@ -18,6 +18,7 @@
import EngineClient from '@/components/Native/EngineClient'
import Ipc from '@/components/Native/Ipc'
import { mapState } from 'vuex'
import { getLangDirection } from '@shared/utils'
export default {
name: 'Motrix',
@@ -27,15 +28,53 @@
[Ipc.name]: Ipc
},
computed: {
...mapState('app', {
systemTheme: state => state.systemTheme
}),
...mapState('preference', {
showWindowActions: state => {
return (is.windows() || is.linux()) && state.config.hideAppMenu
},
rpcSecret: state => state.config.rpcSecret
})
rpcSecret: state => state.config.rpcSecret,
theme: state => state.config.theme,
locale: state => state.config.locale,
dir: state => getLangDirection(state.config.locale)
}),
themeClass: function () {
if (this.theme === 'auto') {
return `theme-${this.systemTheme}`
} else {
return `theme-${this.theme}`
}
},
i18nClass: function () {
return `i18n-${this.locale}`
},
dirClass: function () {
return `dir-${this.dir}`
}
},
methods: {
isRenderer: is.renderer
isRenderer: is.renderer,
updateRootClassName: function () {
const { themeClass = '', i18nClass = '', dirClass = '' } = this
const className = `${themeClass} ${i18nClass} ${dirClass}`
document.documentElement.className = className
}
},
beforeMount: function () {
this.updateRootClassName()
},
watch: {
themeClass: function (val, oldVal) {
this.updateRootClassName()
},
i18nClass: function (val, oldVal) {
this.updateRootClassName()
},
dirClass: function (val, oldVal) {
this.updateRootClassName()
}
}
}
</script>
+29 -4
View File
@@ -1,5 +1,6 @@
import is from 'electron-is'
import api from '@/api'
import { getSystemTheme } from '@/components/Native/utils'
const BASE_INTERVAL = 1000
const PER_INTERVAL = 100
@@ -7,6 +8,7 @@ const MIN_INTERVAL = 500
const MAX_INTERVAL = 6000
const state = {
systemTheme: getSystemTheme(),
aboutPanelVisible: false,
engineInfo: {
version: '',
@@ -18,18 +20,23 @@ const state = {
downloadSpeed: 0,
uploadSpeed: 0,
numActive: 0,
numStopped: 0,
numWaiting: 0
numWaiting: 0,
numStopped: 0
},
addTaskVisible: false,
addTaskType: 'uri',
addTaskTorrents: []
addTaskUrl: '',
addTaskTorrents: [],
addTaskOptions: {}
}
const getters = {
}
const mutations = {
CHANGE_SYSTEM_THEME (state, theme) {
state.systemTheme = theme
},
CHANGE_ABOUT_PANEL_VISIBLE (state, visible) {
state.aboutPanelVisible = visible
},
@@ -48,9 +55,17 @@ const mutations = {
CHANGE_ADD_TASK_TYPE (state, taskType) {
state.addTaskType = taskType
},
CHANGE_ADD_TASK_URL (state, text) {
state.addTaskUrl = text
},
CHANGE_ADD_TASK_TORRENTS (state, fileList) {
state.addTaskTorrents = [...fileList]
},
UPDATE_ADD_TASK_OPTIONS (state, options) {
state.addTaskOptions = {
...options
}
},
UPDATE_INTERVAL (state, millisecond) {
let interval = millisecond
if (millisecond > MAX_INTERVAL) {
@@ -77,6 +92,9 @@ const mutations = {
}
const actions = {
updateSystemTheme ({ commit }, theme) {
commit('CHANGE_SYSTEM_THEME', theme)
},
showAboutPanel ({ commit }) {
commit('CHANGE_ABOUT_PANEL_VISIBLE', true)
},
@@ -122,7 +140,7 @@ const actions = {
}
})
},
togglePowerSaveBlocker (context, numActive) {
togglePowerSaveBlocker (_, numActive) {
if (numActive > 0) {
api.startPowerSaveBlocker()
} else {
@@ -138,14 +156,21 @@ const actions = {
},
hideAddTaskDialog ({ commit }) {
commit('CHANGE_ADD_TASK_VISIBLE', false)
commit('CHANGE_ADD_TASK_URL', '')
commit('CHANGE_ADD_TASK_TORRENTS', [])
},
changeAddTaskType ({ commit }, taskType) {
commit('CHANGE_ADD_TASK_TYPE', taskType)
},
updateAddTaskUrl ({ commit }, text = '') {
commit('CHANGE_ADD_TASK_URL', text)
},
addTaskAddTorrents ({ commit }, { fileList }) {
commit('CHANGE_ADD_TASK_TORRENTS', fileList)
},
updateAddTaskOptions ({ commit }, options = {}) {
commit('UPDATE_ADD_TASK_OPTIONS', options)
},
updateInterval ({ commit }, millisecond) {
commit('UPDATE_INTERVAL', millisecond)
},
+13 -21
View File
@@ -1,36 +1,18 @@
import api from '@/api'
import { isEmpty } from 'lodash'
const state = {
currentForm: 'basic',
engineMode: 'MAX',
config: {}
}
const formTitles = {
'basic': '基础设置',
'advanced': '进阶设置',
'lab': '实验室'
}
const getters = {
currentFormTitle: (state, getters) => {
return formTitles[state.currentForm] ? formTitles[state.currentForm] : ''
}
}
const mutations = {
UPDATE_PREFERENCE_DATA (state, config) {
state.config = { ...state.config, ...config }
},
CHANGE_CURRENT_FORM (state, currentForm) {
state.currentForm = currentForm
}
}
const actions = {
changeCurrentForm ({ commit }, currentForm) {
commit('CHANGE_CURRENT_FORM', currentForm)
},
fetchPreference ({ commit }) {
return new Promise((resolve) => {
api.fetchPreference()
@@ -40,10 +22,21 @@ const actions = {
})
})
},
save ({ commit }, config) {
save ({ commit, dispatch }, config) {
dispatch('task/saveSession', null, { root: true })
if (isEmpty(config)) {
return
}
commit('UPDATE_PREFERENCE_DATA', config)
return api.savePreference(config)
},
changeThemeConfig ({ commit }, theme) {
commit('UPDATE_PREFERENCE_DATA', { theme })
},
fetchBtTracker () {
return api.fetchBtTrackerFromGitHub()
},
toggleEngineMode () {
}
@@ -52,7 +45,6 @@ const actions = {
export default {
namespaced: true,
state,
getters,
mutations,
actions
}
+26 -1
View File
@@ -57,6 +57,7 @@ const actions = {
return api.addUri({ uris, options })
.then(() => {
dispatch('fetchList')
dispatch('app/updateAddTaskOptions', {}, { root: true })
})
},
addTorrent ({ dispatch }, data) {
@@ -64,6 +65,7 @@ const actions = {
return api.addTorrent({ torrent, options })
.then(() => {
dispatch('fetchList')
dispatch('app/updateAddTaskOptions', {}, { root: true })
})
},
addMetalink ({ dispatch }, data) {
@@ -71,8 +73,21 @@ const actions = {
return api.addMetalink({ metalink, options })
.then(() => {
dispatch('fetchList')
dispatch('app/updateAddTaskOptions', {}, { root: true })
})
},
getTaskOption (_, gid) {
return new Promise((resolve) => {
api.getOption({ gid })
.then((data) => {
resolve(data)
})
})
},
changeTaskOption (_, payload) {
const { gid, options } = payload
return api.changeOption({ gid, options })
},
removeTask ({ dispatch }, task) {
const { gid } = task
return api.forcePauseTask({ gid })
@@ -123,8 +138,18 @@ const actions = {
dispatch('saveSession')
})
},
removeTaskRecord ({ dispatch }, task) {
stopSeeding ({ dispatch }, task) {
const { gid } = task
const options = {
seedTime: 0
}
return dispatch('changeTaskOption', { gid, options })
},
removeTaskRecord ({ dispatch }, task) {
const { gid, status } = task
if (['error', 'complete', 'removed'].indexOf(status) === -1) {
return
}
return api.removeTaskRecord({ gid })
.finally(() => dispatch('fetchList'))
},
+23 -15
View File
@@ -1,22 +1,27 @@
const userKeys = [
'locale',
'cookie',
'resume-all-when-app-launched',
'task-notification',
'hide-app-menu',
'new-task-show-downloading',
'use-proxy',
'all-proxy-backup',
'auto-check-update',
'cookie',
'enable-egg-features',
'hide-app-menu',
'keep-window-state',
'last-check-update-time',
'locale',
'log-path',
'new-task-show-downloading',
'open-at-login',
'protocols',
'resume-all-when-app-launched',
'session-path',
'enable-egg-features'
'task-notification',
'theme',
'use-proxy'
]
const systemKeys = [
'max-concurrent-downloads',
'all-proxy',
'all-proxy-passwd',
'all-proxy-user',
'all-proxy',
'allow-overwrite',
'allow-piece-length-change',
'always-resume',
@@ -39,10 +44,10 @@ const systemKeys = [
'bt-save-metadata',
'bt-seed-unverified',
'bt-stop-timeout',
'bt-tracker',
'bt-tracker-connect-timeout',
'bt-tracker-interval',
'bt-tracker-timeout',
'bt-tracker',
'check-integrity',
'checksum',
'conditional-get',
@@ -61,9 +66,9 @@ const systemKeys = [
'force-save',
'ftp-passwd',
'ftp-pasv',
'ftp-proxy',
'ftp-proxy-passwd',
'ftp-proxy-user',
'ftp-proxy',
'ftp-reuse-connection',
'ftp-type',
'ftp-user',
@@ -74,15 +79,16 @@ const systemKeys = [
'http-auth-challenge',
'http-no-cache',
'http-passwd',
'http-proxy',
'http-proxy-passwd',
'http-proxy-user',
'http-proxy',
'http-user',
'https-proxy',
'https-proxy-passwd',
'https-proxy-user',
'https-proxy',
'index-out',
'lowest-speed-limit',
'max-concurrent-downloads',
'max-connection-per-server',
'max-download-limit',
'max-file-not-found',
@@ -90,6 +96,8 @@ const systemKeys = [
'max-resume-failure-tries',
'max-tries',
'max-upload-limit',
'max-overall-download-limit',
'max-overall-upload-limit',
'metalink-base-uri',
'metalink-enable-unique-protocol',
'metalink-language',
@@ -103,8 +111,8 @@ const systemKeys = [
'no-proxy',
'out',
'parameterized-uri',
'pause',
'pause-metadata',
'pause',
'piece-length',
'proxy-method',
'realtime-chunk-checksum',
+11
View File
@@ -0,0 +1,11 @@
export const LIGHT_THEME = 'light'
export const DARK_THEME = 'dark'
export const BEST_TRACKERS_URL = 'https://raw.githubusercontent.com/ngosang/trackerslist/master/trackers_best.txt'
export const BEST_TRACKERS_IP_URL = 'https://raw.githubusercontent.com/ngosang/trackerslist/master/trackers_best_ip.txt'
// One Week
export const AUTO_CHECK_UPDATE_INTERVAL = 7 * 24 * 60 * 60 * 1000
export const NONE_SELECTED_FILES = 'none'
export const SELECTED_ALL_FILES = 'all'
+24
View File
@@ -1,13 +1,19 @@
import eleLocaleDe from 'element-ui/lib/locale/lang/de'
import eleLocaleEn from 'element-ui/lib/locale/lang/en'
import eleLocaleFa from 'element-ui/lib/locale/lang/fa'
import eleLocaleFr from 'element-ui/lib/locale/lang/fr'
import eleLocaleJa from 'element-ui/lib/locale/lang/ja'
import eleLocaleKo from 'element-ui/lib/locale/lang/ko'
import eleLocalePtBR from 'element-ui/lib/locale/lang/pt-br'
import eleLocaleTr from 'element-ui/lib/locale/lang/tr-TR'
import eleLocaleZhCN from 'element-ui/lib/locale/lang/zh-CN'
import eleLocaleZhTW from 'element-ui/lib/locale/lang/zh-TW'
import appLocaleDe from '@shared/locales/de'
import appLocaleEnUS from '@shared/locales/en-US'
import appLocaleFa from '@shared/locales/fa'
import appLocaleFr from '@shared/locales/fr'
import appLocaleJa from '@shared/locales/ja'
import appLocaleKo from '@shared/locales/ko'
import appLocalePtBR from '@shared/locales/pt-BR'
import appLocaleTr from '@shared/locales/tr'
import appLocaleZhCN from '@shared/locales/zh-CN'
@@ -27,12 +33,30 @@ const resources = {
...appLocaleEnUS
}
},
'fa': {
translation: {
...eleLocaleFa,
...appLocaleFa
}
},
'fr': {
translation: {
...eleLocaleFr,
...appLocaleFr
}
},
'ja': {
translation: {
...eleLocaleJa,
...appLocaleJa
}
},
'ko': {
translation: {
...eleLocaleKo,
...appLocaleKo
}
},
'pt-BR': {
translation: {
...eleLocalePtBR,
+24 -6
View File
@@ -1,8 +1,11 @@
import appLocaleDeDE from '@shared/locales/de'
import appLocaleDe from '@shared/locales/de'
import appLocaleEnUS from '@shared/locales/en-US'
import appLocaleFrFR from '@shared/locales/fr'
import appLocaleFa from '@shared/locales/fa'
import appLocaleFr from '@shared/locales/fr'
import appLocaleJa from '@shared/locales/ja'
import appLocaleKo from '@shared/locales/ko'
import appLocalePtBr from '@shared/locales/pt-BR'
import appLocaleTrTR from '@shared/locales/tr'
import appLocaleTr from '@shared/locales/tr'
import appLocaleZhCN from '@shared/locales/zh-CN'
import appLocaleZhTW from '@shared/locales/zh-TW'
@@ -10,7 +13,7 @@ import appLocaleZhTW from '@shared/locales/zh-TW'
const resources = {
'de': {
translation: {
...appLocaleDeDE
...appLocaleDe
}
},
'en-US': {
@@ -18,9 +21,24 @@ const resources = {
...appLocaleEnUS
}
},
'fa': {
translation: {
...appLocaleFa
}
},
'fr': {
translation: {
...appLocaleFrFR
...appLocaleFr
}
},
'ja': {
translation: {
...appLocaleJa
}
},
'ko': {
translation: {
...appLocaleKo
}
},
'pt-BR': {
@@ -30,7 +48,7 @@ const resources = {
},
'tr': {
translation: {
...appLocaleTrTR
...appLocaleTr
}
},
'zh-CN': {
+1
View File
@@ -1,5 +1,6 @@
export default {
'engine-version': 'Engine Version',
'license': 'Lizenz',
'about': 'Über',
'release': 'Versionen',
'support': 'Unterstützung anfordern'
+2
View File
@@ -4,6 +4,8 @@ export default {
'about': 'Über Motrix',
'preferences': 'Präferenzen...',
'check-for-updates': 'Nach Updates suchen...',
'check-updates-now': 'Jetzt prüfen',
'checking-for-updates': 'Nach Updates suchen ...',
'check-for-updates-title': 'Nach Updates suchen',
'update-available-message': 'Eine neue Version von Motrix ist verfügbar, jetzt aktualisieren?',
'update-not-available-message': 'Sie sind auf dem neuesten Stand!',
+29 -5
View File
@@ -3,11 +3,19 @@ export default {
'advanced': 'Erweitert',
'lab': 'Experimentell',
'save': 'Speichern & übernehmen',
'save-success-message': 'Einstellungen erfolgreich speichern',
'save-fail-message': 'Speichern der Einstellungen fehlgeschlagen',
'discard': 'Verwerfen',
'startup': 'Startup',
'open-at-login': 'Beim Login öffnen',
'keep-window-state': 'Stellen Sie die Größe und Position des Fensters wieder her',
'auto-resume-all': 'Alle nicht abgeschlossenen Aufgaben automatisch fortsetzen',
'default-dir': 'Standard Speicherort',
'mas-default-dir-tip': 'Aufgrund der Einschränkungen durch Sandbox-Berechtigungen im App Store wird der Download Ordner als Standard empfohlen',
'default-dir': 'Standardpfad',
'mas-default-dir-tips': 'Aufgrund der Einschränkungen durch Sandbox-Berechtigungen im App Store wird der Download Ordner als Standard empfohlen',
'transfer-settings': 'Übertragung',
'transfer-speed-upload': 'Upload-Limit',
'transfer-speed-download': 'Download-Limit',
'transfer-speed-unlimited': 'Unbegrenzt',
'task-manage': 'Aufgaben verwalten',
'max-concurrent-downloads': 'Maximal aktive Aufgaben',
'max-connection-per-server': 'Maximale Verbindungen pro Server',
@@ -16,11 +24,22 @@ export default {
'task-completed-notify': 'Benachrichtigung nach abgeschlossenen Download anzeigen',
'auto-purge-record': 'Download Protokoll beim Schließen der App löschen',
'ui': 'UI',
'appearance': 'Erscheinungsbild',
'theme-auto': 'Automatischer',
'theme-light': 'Hell',
'theme-dark': 'Dunkel',
'language': 'Sprache',
'change-language': 'Sprache ändern',
'hide-app-menu': 'App Menü ausblenden (nur auf Windows & Linux)',
'proxy': 'Proxy',
'use-proxy': 'Proxy aktivieren',
'bt-tracker': 'Tracker-Server',
'bt-tracker-input-tips': 'Tracker-Server, einer pro Zeile',
'bt-tracker-tips': 'Empfehlen:',
'sync-tracker-tips': 'Von ngosang/trackerslist synchronisieren',
'security': 'Sicherheit',
'rpc-secret': 'RPC-Geheimnis',
'rpc-secret-tips': 'Geheime RPC-Anleitung anzeigen',
'developer': 'Entwickler',
'mock-user-agent': 'User-Agent simulieren',
'app-log-path': 'Appprotokollpfad',
@@ -29,9 +48,14 @@ export default {
'factory-reset-confirm': 'Sollen die Einstellungen auf die Werkseinstellungen unwiderruflich zurückgesetzt werden?',
'lab-warning': '⚠️ Die Aktivierung von experimentellen Funktionen kann zu App-Abstürzen oder Datenverlust führen!',
'download-protocol': 'Protokoll',
'support-more-download-protocols': 'Erweiterte Protokollunterstützung aktivieren',
'protocols-default-client': 'Als Standardclient für die folgenden Protokolle festlegen',
'protocols-magnet': 'Magnet [ magnet:// ]',
'protocols-thunder': 'Donner [ thunder:// ]',
'browser-extensions': 'Erweiterungen',
'baidu-exporter': 'Baidu Exporter',
'browser-extensions-tip': 'Von der Community bereitgestellt, ',
'baidu-exporter-help': 'mehr über die Verwendung zu erfahren'
'browser-extensions-tips': 'Von der Community bereitgestellt, ',
'baidu-exporter-help': 'mehr über die Verwendung zu erfahren',
'auto-update': 'Auto-Update',
'auto-check-update': 'Automatische Updates überprüfen',
'last-check-update-time': 'letzte kontrolle update - zeit'
}

Some files were not shown because too many files have changed in this diff Show More