Compare commits
568 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c17ceb365d | |||
| a1851c6b31 | |||
| 071b0a21c2 | |||
| e25dfb143c | |||
| 652f04bada | |||
| 5962334f3d | |||
| f984b175dc | |||
| ad1979417a | |||
| 16822b9b55 | |||
| 36551da19e | |||
| f3426fdc90 | |||
| 94ead296bf | |||
| 22a6f73e51 | |||
| 94b4c08da9 | |||
| 041b7701c2 | |||
| c20d9b65ee | |||
| 47c5daea81 | |||
| 9676e2a060 | |||
| bb75fa6dcc | |||
| b547ef8f2e | |||
| cc44e21d0e | |||
| 20ac050514 | |||
| 7eeafb6c93 | |||
| 6fd63c872f | |||
| b8b4f04423 | |||
| e3e2abac6f | |||
| b34f3ceb89 | |||
| feb07a43e1 | |||
| 4652672ef4 | |||
| 8b375c019d | |||
| e232e4af70 | |||
| f57d71cebe | |||
| 90daf4cb19 | |||
| d1f759ec44 | |||
| 45ad6dfbda | |||
| 65d45f69fe | |||
| 7e626704ac | |||
| e0f9dce952 | |||
| edf627923e | |||
| 40f7abcc22 | |||
| d837e97d02 | |||
| 13034b4e90 | |||
| b3826032ea | |||
| d5e9ada2c9 | |||
| 724a2a7f76 | |||
| 64326a11af | |||
| b00536a0c7 | |||
| 8885e63474 | |||
| 28936fd13e | |||
| 08a419f209 | |||
| 8a3391c9f4 | |||
| 3777a1f753 | |||
| b81298d6eb | |||
| f67b9b8dd6 | |||
| a303afb9a5 | |||
| eee07b1b7d | |||
| a27c49a376 | |||
| fd81dc5194 | |||
| 394dfdfffc | |||
| 99e9aa8046 | |||
| ec295b3d72 | |||
| 8208440987 | |||
| 47426f2d66 | |||
| ac75a05511 | |||
| c3ff1f3b23 | |||
| 0f3157980c | |||
| 36929f9dc5 | |||
| 11c507008b | |||
| 3d66549849 | |||
| 47ca535268 | |||
| 7d102f9fcb | |||
| edf350adf3 | |||
| 651f28e7e8 | |||
| 4d7acb7c0a | |||
| b75e969a01 | |||
| 0b6f5e7d43 | |||
| a82000a13b | |||
| edee889a1a | |||
| afd2fdeabf | |||
| 34011d7bc8 | |||
| 6cf6b69f8e | |||
| a386c704e4 | |||
| 0994244b0e | |||
| 777977e001 | |||
| af5ac44dc8 | |||
| 23382a9415 | |||
| 7b2da0f35b | |||
| 4037dad582 | |||
| 353ee23cfc | |||
| 29b0d90bc7 | |||
| 073e514f95 | |||
| a35fba4820 | |||
| 828d854eb5 | |||
| 5dbe157170 | |||
| cdc196c2b0 | |||
| 3d26445953 | |||
| f887878b9e | |||
| 908f60df1a | |||
| 56c9b58d87 | |||
| d56983d45d | |||
| 8775e12587 | |||
| df2e01813e | |||
| d77a6faf36 | |||
| 5e735b513d | |||
| 64f0a3db80 | |||
| e470ed00d6 | |||
| 36a27bae46 | |||
| 5d900806e8 | |||
| ace14c85dc | |||
| 3ec4bd26e6 | |||
| b9bc5e6c07 | |||
| 021fbc243a | |||
| 8b66f2ea6e | |||
| 7703c28c6e | |||
| 26db88ec51 | |||
| 51b7ea4030 | |||
| 1e2368dd3b | |||
| f28c3ae5c5 | |||
| ebdd125d91 | |||
| 9bcc386269 | |||
| 27c48af278 | |||
| 4b4df884dc | |||
| 64082ddd70 | |||
| 188a39d0d2 | |||
| 44f8f23a7c | |||
| 32340218e2 | |||
| 85a8430c3f | |||
| e47816e273 | |||
| b7356702fc | |||
| a61c857e11 | |||
| d3ef30d943 | |||
| b8d5eb5ac1 | |||
| aeaf0fe9ca | |||
| 8bebb942ca | |||
| f0e795f00c | |||
| 7d4eec6b02 | |||
| fe2dc7a541 | |||
| 848b9e78e1 | |||
| 2fa33f75e5 | |||
| 1f6c2a6ff0 | |||
| a8e3c7843c | |||
| 8b1ba2425c | |||
| 47acf4fad1 | |||
| 9beb48be92 | |||
| bbe52a2a18 | |||
| 0b73028412 | |||
| 2311a7ed86 | |||
| 95ed300d1b | |||
| dcd2d50c83 | |||
| 57ed6c7941 | |||
| 870c4089fb | |||
| 1082f9330e | |||
| a7f33869a0 | |||
| 89336dda37 | |||
| 4240dee426 | |||
| 83c1c1340e | |||
| 408e33adb1 | |||
| d2dde274d3 | |||
| 7b27f82f1e | |||
| 2d32a2848c | |||
| c128689b2e | |||
| 7702aa6ca1 | |||
| 2fb32fa347 | |||
| 2744db2d75 | |||
| adb060b076 | |||
| 561d7608d8 | |||
| 06d9a95827 | |||
| 9c386c4bdc | |||
| a1eb48926a | |||
| c1ed827244 | |||
| 663e1a2db1 | |||
| c0a2b50b9f | |||
| a843ae4553 | |||
| d195b23512 | |||
| f6c84b7e57 | |||
| 98df9c3f2a | |||
| 7e703eb552 | |||
| 87bcac6122 | |||
| 6935a12289 | |||
| 39688b667e | |||
| f174887f9f | |||
| caba086651 | |||
| b627b38985 | |||
| 1c35d72aa5 | |||
| 8ecbfdd66d | |||
| 3be568f6ab | |||
| fa29c11c73 | |||
| 6b153d68b4 | |||
| 852266dc16 | |||
| 4170cd3419 | |||
| a15870871f | |||
| 571499caad | |||
| 766c2c26b8 | |||
| 1f27f88aa7 | |||
| e06888e998 | |||
| 91a2d0a270 | |||
| 8e3fe4c980 | |||
| f3724107f0 | |||
| 580c32a2b9 | |||
| 575712ba9f | |||
| 84006f348c | |||
| a9759441d3 | |||
| 728f8e9019 | |||
| e814210896 | |||
| a0c6877524 | |||
| 29b0dd627d | |||
| dc48cad7e8 | |||
| 35df1c799b | |||
| 912e661c02 | |||
| 1a2de2ad88 | |||
| e44587035d | |||
| 43f1adcd84 | |||
| 8a900459b5 | |||
| d2f8a4599d | |||
| 9159fb36ab | |||
| 5be05395f5 | |||
| fba76df945 | |||
| 5bb4f259cd | |||
| 4b093a1a4f | |||
| 134d81b205 | |||
| d5fc962bca | |||
| 0d1b76d523 | |||
| 6499ce91b8 | |||
| 2d7907f218 | |||
| 97c1cb0418 | |||
| be585ef102 | |||
| 03002221e7 | |||
| 4b8de781a4 | |||
| 8224423d8c | |||
| 1e05e1e4f3 | |||
| bfd1e1b6fa | |||
| 79495cbde5 | |||
| a93d183988 | |||
| cf082b20d8 | |||
| 8110409402 | |||
| 37b446b6f0 | |||
| 97801fff32 | |||
| 4ca4f5e606 | |||
| e9caf1b0b4 | |||
| d67dca00b5 | |||
| 038536259e | |||
| ec21cc57bc | |||
| 5a42e4014b | |||
| 02d4032283 | |||
| d676dbe213 | |||
| 02d83534d1 | |||
| 8b531c76b8 | |||
| 22f92df299 | |||
| eaf91e15e8 | |||
| 4535cb706a | |||
| a093f3beca | |||
| 287c9038b1 | |||
| 72aac9c0c6 | |||
| cdac88371c | |||
| 678bed53dc | |||
| 1f805c3440 | |||
| c8dcb1023f | |||
| a0e7eb84ca | |||
| fdf0bb8320 | |||
| e2338757de | |||
| c0cbfb5cb2 | |||
| fddaa72ac6 | |||
| 1e531f166b | |||
| 97f54adc49 | |||
| e6548d5548 | |||
| 137fdf2b26 | |||
| 10ef4f7318 | |||
| bdd5e56c2a | |||
| a851ec5830 | |||
| 2ae3642c62 | |||
| 7b41a9e1c6 | |||
| 012e5ece00 | |||
| db8d3d1aec | |||
| 8e35dde082 | |||
| 5c2acf12ee | |||
| e710ccdb34 | |||
| 5ecc08d66d | |||
| e7b1a898fa | |||
| 93acf1ba63 | |||
| 1164df87e3 | |||
| 71232603a5 | |||
| 286013b2f0 | |||
| a5f0236661 | |||
| 87857eba94 | |||
| 3059d1a0b7 | |||
| 5029f98153 | |||
| ce80af7ead | |||
| 6df52988b0 | |||
| 6359c729ad | |||
| 04db94daa6 | |||
| d41d809f00 | |||
| efdfae37b5 | |||
| 1a097c02e3 | |||
| 0217b5573a | |||
| 86611604d5 | |||
| 0bd8ab3d11 | |||
| e9eadb2eba | |||
| 72afd845ba | |||
| 91670fac37 | |||
| bc14e5b287 | |||
| 2d01431e7d | |||
| 81531ef9fc | |||
| 04126363ca | |||
| bcb07656b9 | |||
| b9cd991fd5 | |||
| 1b2b22f039 | |||
| 170a481d48 | |||
| 48bf7c9e2e | |||
| 03cfb013e5 | |||
| b4e23d8805 | |||
| 43965c5740 | |||
| 5c8d2e4ca4 | |||
| b51c144ace | |||
| feb839b7fa | |||
| fa36397afa | |||
| 583fe0ffe2 | |||
| 8b3a5fbaa8 | |||
| 85493c263b | |||
| 78e106abe4 | |||
| bbf90e9344 | |||
| dc30c25a83 | |||
| e1cce4f50c | |||
| 61654ff0ff | |||
| 0142aedfa5 | |||
| 323f85ca40 | |||
| f35e696c6a | |||
| d49cbcdc00 | |||
| c85ac4e895 | |||
| f831cc51ed | |||
| d47eb9ba0f | |||
| 119a374db6 | |||
| d26808e43a | |||
| 8cb3e54be2 | |||
| c55902cda7 | |||
| 426b54201b | |||
| 6c032cde2d | |||
| eb42ac6cfd | |||
| 90f0a7c5f6 | |||
| 9b61dad2c3 | |||
| 671fb82e6a | |||
| b8f444ca4d | |||
| 7509442e4e | |||
| ee2e3de782 | |||
| c6dd1e333e | |||
| fba761a29c | |||
| 4fb94ae8bb | |||
| 79721822c2 | |||
| 556f58f6b3 | |||
| e690f15335 | |||
| 1876587973 | |||
| b89c651132 | |||
| ad9d3ed15a | |||
| 6aa362332a | |||
| 18d107c062 | |||
| ebeba4663c | |||
| 9bff4257a8 | |||
| e07ef7dd11 | |||
| ebb0926d1d | |||
| 5333438825 | |||
| 3664341822 | |||
| d3cbff6dd1 | |||
| 5668b0773d | |||
| b757112c30 | |||
| dc3a01fb3c | |||
| 591de11072 | |||
| 853c361e6d | |||
| b0ebc737e2 | |||
| bc9db25dfd | |||
| 08c9902759 | |||
| 8d2f52710d | |||
| dd48fd79db | |||
| 9e7ad10309 | |||
| 6e17102210 | |||
| 5ed3b68a57 | |||
| a705427ce2 | |||
| c37f7c73ce | |||
| efcf01d59b | |||
| 324b61567d | |||
| d1e24fced0 | |||
| cb50d6709b | |||
| 900ac4c8d1 | |||
| 6696b0a538 | |||
| 110c83bcb8 | |||
| 136b4969bd | |||
| a901fdd2b6 | |||
| 215bdbc416 | |||
| 4e8d226460 | |||
| 23f3357d27 | |||
| 7adafab7fe | |||
| e3afb14e4a | |||
| 228cd1fa7d | |||
| 47a7464bf9 | |||
| 8efd86af3d | |||
| ee78ae262e | |||
| 7562f6a7d1 | |||
| defacd50e8 | |||
| f5ed0d89cc | |||
| a3bb1c7ff1 | |||
| d0085b295d | |||
| 6cb278fd9e | |||
| aeeb9813ed | |||
| 359da895e7 | |||
| 8a3f510c38 | |||
| 1345994402 | |||
| c770108a6c | |||
| 178f2103f1 | |||
| d5c71b50e8 | |||
| ce09e76ffb | |||
| 4688b521fa | |||
| 33960dc264 | |||
| d29d0d3321 | |||
| f4c3e7be69 | |||
| 338b51975c | |||
| e1a287350d | |||
| afeee1fc86 | |||
| a39820f8ab | |||
| aea604278c | |||
| 0b92e6b96e | |||
| 2ba0040bdd | |||
| 04bf2b6b9d | |||
| cc367f4c5c | |||
| 73809a6501 | |||
| 5067ad7306 | |||
| d5b3ad5933 | |||
| 7a903e112c | |||
| 829eb83a4c | |||
| 886acc2486 | |||
| 87440701ab | |||
| e880430e3a | |||
| 2febff883b | |||
| 7d8bb443b8 | |||
| 1489d58a63 | |||
| 00fdafe824 | |||
| 81c91e8cb1 | |||
| 0fd34254de | |||
| d9298f0d46 | |||
| 83e12a699e | |||
| 83b55b806d | |||
| d4dab6de9b | |||
| deae5e8e33 | |||
| b098c8c9bc | |||
| b12553cc45 | |||
| 8891b9a79c | |||
| a44f088139 | |||
| 7ea0cd3e75 | |||
| c93cc9d23b | |||
| 8d2e14486d | |||
| 3775da5e36 | |||
| 2e21285f99 | |||
| 6c2dde8e59 | |||
| e014580512 | |||
| e970a51b71 | |||
| 019f8be306 | |||
| 20f79dacb7 | |||
| 1bdd17a80a | |||
| b01e006073 | |||
| 82728a2b23 | |||
| 217d2df5be | |||
| 960f0674ef | |||
| 54e9f326d9 | |||
| 4e7267028f | |||
| efd7f97a36 | |||
| 5bb790822d | |||
| 71f3fb601b | |||
| af710aa265 | |||
| 52b6fb0849 | |||
| b8d63374b7 | |||
| ff38d4ea5c | |||
| a24a7acfaf | |||
| 16e9c8d848 | |||
| f36cdccf9f | |||
| c826a7dd77 | |||
| b96518a42c | |||
| 02f0b66500 | |||
| 377bc47d87 | |||
| f8a4ef267d | |||
| baea3f0345 | |||
| 1fcf0bb0d0 | |||
| 85e1acfe5b | |||
| 41ac6ad335 | |||
| b151c45e70 | |||
| b3686fff04 | |||
| af81107b3d | |||
| 88224998b7 | |||
| 8508ca9b3c | |||
| 6fad6a876c | |||
| 6c8a844cd9 | |||
| 4657a503f9 | |||
| 324347e591 | |||
| 16c376c108 | |||
| 79cad28cc1 | |||
| ac16bd91c2 | |||
| fafeaca47d | |||
| 3152252fb8 | |||
| a5240bfeb0 | |||
| 64cda63587 | |||
| fe071a3783 | |||
| 5d812e9c4e | |||
| f36ba605e2 | |||
| e7ed58e394 | |||
| be789e7ea8 | |||
| 780a01e404 | |||
| b45518a5b6 | |||
| 2401d68a24 | |||
| 6cb2986f39 | |||
| cb8e0dbd2e | |||
| b4faed0621 | |||
| 249aa616fe | |||
| 482ccb3293 | |||
| f1be6bc1a4 | |||
| 326e8c700b | |||
| 2a538fd7c3 | |||
| c424b981b3 | |||
| a284aba2cc | |||
| c8bfdbf087 | |||
| eb80f3698f | |||
| 23f3d85ad4 | |||
| 24019123ba | |||
| 06a471982a | |||
| 43627aba50 | |||
| 8116234b3e | |||
| e488de5c3f | |||
| 084f834a41 | |||
| 97d30ee85e | |||
| 387ccc9cf7 | |||
| fed5744607 | |||
| 3e2b448e09 | |||
| c12c13066d | |||
| 5c36454ca0 | |||
| 7b6ba13d94 | |||
| 5d93d4d7bb | |||
| 4d024eb6a6 | |||
| 182acec265 | |||
| e293b16343 | |||
| 70cf36d7b2 | |||
| f3d8cac9ba | |||
| 620b243794 | |||
| 37fe7d4dbd | |||
| 11b6d651b8 | |||
| 8a5bc11d88 | |||
| bf69146035 | |||
| 14ff0c4bbe | |||
| d9706a2c3e | |||
| c36f7b73f1 | |||
| 7e6d0542cc | |||
| 72bf4ec909 | |||
| fa87b8ff9b | |||
| 31203e72fc | |||
| 5bd9875f61 | |||
| 646ad28fbc | |||
| a66d482d3a | |||
| 5dfc8cce37 | |||
| 4c88431147 | |||
| bb06b3970d | |||
| c86767bc3f | |||
| 3c6d14ec7e | |||
| 88a9a69c27 | |||
| da7241cd6a | |||
| 397055826e | |||
| 2e6b8bb5fa | |||
| ed4f3afcbd | |||
| 53ab75fbf9 | |||
| c19cf17b07 | |||
| a994f45e8f | |||
| 1bdaeeb3a6 | |||
| 32f0a3d084 | |||
| ba4ef326f7 | |||
| 10a8f0bd16 |
@@ -2,19 +2,11 @@
|
||||
"comments": false,
|
||||
"env": {
|
||||
"main": {
|
||||
"presets": [
|
||||
["env", {
|
||||
"targets": { "node": 7 }
|
||||
}],
|
||||
"stage-0"
|
||||
]
|
||||
"presets": ["@babel/preset-env"]
|
||||
},
|
||||
"renderer": {
|
||||
"presets": [
|
||||
["env", {
|
||||
"modules": false
|
||||
}],
|
||||
"stage-0"
|
||||
"@babel/preset-env"
|
||||
],
|
||||
"plugins": [
|
||||
[
|
||||
@@ -27,12 +19,7 @@
|
||||
]
|
||||
},
|
||||
"web": {
|
||||
"presets": [
|
||||
["env", {
|
||||
"modules": false
|
||||
}],
|
||||
"stage-0"
|
||||
],
|
||||
"presets": ["@babel/preset-env"],
|
||||
"plugins": [
|
||||
[
|
||||
"component",
|
||||
@@ -44,5 +31,8 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"plugins": ["transform-runtime"]
|
||||
"plugins": [
|
||||
"@babel/plugin-proposal-class-properties",
|
||||
"@babel/plugin-transform-runtime"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
@@ -19,9 +19,13 @@ const errorLog = chalk.bgRed.white(' ERROR ') + ' '
|
||||
const okayLog = chalk.bgBlue.white(' OKAY ') + ' '
|
||||
const isCI = process.env.CI || false
|
||||
|
||||
if (process.env.BUILD_TARGET === 'clean') clean()
|
||||
else if (process.env.BUILD_TARGET === 'web') web()
|
||||
else build()
|
||||
if (process.env.BUILD_TARGET === 'clean') {
|
||||
clean()
|
||||
} else if (process.env.BUILD_TARGET === 'web') {
|
||||
web()
|
||||
} else {
|
||||
build()
|
||||
}
|
||||
|
||||
function clean () {
|
||||
del.sync(['build/*', '!build/icons', '!build/icons/icon.*'])
|
||||
@@ -74,8 +78,9 @@ function pack (config) {
|
||||
return new Promise((resolve, reject) => {
|
||||
config.mode = 'production'
|
||||
webpack(config, (err, stats) => {
|
||||
if (err) reject(err.stack || err)
|
||||
else if (stats.hasErrors()) {
|
||||
if (err) {
|
||||
reject(err.stack || err)
|
||||
} else if (stats.hasErrors()) {
|
||||
let err = ''
|
||||
|
||||
stats.toString({
|
||||
@@ -129,4 +134,4 @@ function greeting () {
|
||||
})
|
||||
} else console.log(chalk.yellow.bold('\n lets-build'))
|
||||
console.log()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ const { spawn } = require('child_process')
|
||||
const webpack = require('webpack')
|
||||
const WebpackDevServer = require('webpack-dev-server')
|
||||
const webpackHotMiddleware = require('webpack-hot-middleware')
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
|
||||
const mainConfig = require('./webpack.main.config')
|
||||
const rendererConfig = require('./webpack.renderer.config')
|
||||
@@ -49,7 +50,7 @@ function startRenderer () {
|
||||
})
|
||||
|
||||
compiler.hooks.compilation.tap('compilation', compilation => {
|
||||
compilation.hooks.htmlWebpackPluginAfterEmit.tapAsync('html-webpack-plugin-after-emit', (data, cb) => {
|
||||
HtmlWebpackPlugin.getHooks(compilation).afterEmit.tapAsync('html-webpack-plugin-after-emit', (data, cb) => {
|
||||
hotMiddleware.publish({ action: 'reload' })
|
||||
cb()
|
||||
})
|
||||
|
||||
@@ -6,8 +6,7 @@ const devMode = process.env.NODE_ENV !== 'production'
|
||||
const path = require('path')
|
||||
const { dependencies, build } = require('../package.json')
|
||||
const webpack = require('webpack')
|
||||
|
||||
const BabiliWebpackPlugin = require('babili-webpack-plugin')
|
||||
const TerserPlugin = require('terser-webpack-plugin');
|
||||
|
||||
let mainConfig = {
|
||||
entry: {
|
||||
@@ -59,7 +58,15 @@ let mainConfig = {
|
||||
},
|
||||
extensions: ['.js', '.json', '.node']
|
||||
},
|
||||
target: 'electron-main'
|
||||
target: 'electron-main',
|
||||
optimization: {
|
||||
minimize: !devMode,
|
||||
minimizer: [
|
||||
new TerserPlugin({
|
||||
extractComments: false,
|
||||
})
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -79,7 +86,6 @@ if (devMode) {
|
||||
*/
|
||||
if (!devMode) {
|
||||
mainConfig.plugins.push(
|
||||
new BabiliWebpackPlugin(),
|
||||
new webpack.DefinePlugin({
|
||||
'process.env.NODE_ENV': '"production"',
|
||||
'appId': `"${build.appId}"`
|
||||
|
||||
@@ -6,8 +6,7 @@ const devMode = process.env.NODE_ENV !== 'production'
|
||||
const path = require('path')
|
||||
const { dependencies } = require('../package.json')
|
||||
const webpack = require('webpack')
|
||||
|
||||
const BabiliWebpackPlugin = require('babili-webpack-plugin')
|
||||
const TerserPlugin = require('terser-webpack-plugin');
|
||||
const CopyWebpackPlugin = require('copy-webpack-plugin')
|
||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
|
||||
const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
|
||||
@@ -52,8 +51,11 @@ let rendererConfig = {
|
||||
{
|
||||
loader: 'sass-loader',
|
||||
options: {
|
||||
data: '@import "@/components/Theme/Variables.scss";',
|
||||
includePaths:[__dirname, 'src']
|
||||
implementation: require('sass'),
|
||||
prependData: '@import "@/components/Theme/Variables.scss";',
|
||||
sassOptions: {
|
||||
includePaths:[__dirname, 'src']
|
||||
}
|
||||
},
|
||||
}
|
||||
]
|
||||
@@ -66,9 +68,12 @@ let rendererConfig = {
|
||||
{
|
||||
loader: 'sass-loader',
|
||||
options: {
|
||||
implementation: require('sass'),
|
||||
indentedSyntax: true,
|
||||
data: '@import "@/components/Theme/Variables.scss";',
|
||||
includePaths:[__dirname, 'src']
|
||||
prependData: '@import "@/components/Theme/Variables.scss";',
|
||||
sassOptions: {
|
||||
includePaths:[__dirname, 'src']
|
||||
}
|
||||
},
|
||||
}
|
||||
]
|
||||
@@ -166,6 +171,18 @@ let rendererConfig = {
|
||||
filename: 'index.html',
|
||||
chunks: ['index'],
|
||||
template: path.resolve(__dirname, '../src/index.ejs'),
|
||||
templateParameters(compilation, assets, options) {
|
||||
return {
|
||||
compilation: compilation,
|
||||
webpack: compilation.getStats().toJson(),
|
||||
webpackConfig: compilation.options,
|
||||
htmlWebpackPlugin: {
|
||||
files: assets,
|
||||
options: options
|
||||
},
|
||||
process
|
||||
}
|
||||
},
|
||||
// minify: {
|
||||
// collapseWhitespace: true,
|
||||
// removeAttributeQuotes: true,
|
||||
@@ -191,7 +208,15 @@ let rendererConfig = {
|
||||
},
|
||||
extensions: ['.js', '.vue', '.json', '.css', '.node']
|
||||
},
|
||||
target: 'electron-renderer'
|
||||
target: 'electron-renderer',
|
||||
optimization: {
|
||||
minimize: !devMode,
|
||||
minimizer: [
|
||||
new TerserPlugin({
|
||||
extractComments: false,
|
||||
})
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -212,7 +237,6 @@ if (!devMode) {
|
||||
rendererConfig.devtool = ''
|
||||
|
||||
rendererConfig.plugins.push(
|
||||
new BabiliWebpackPlugin(),
|
||||
new CopyWebpackPlugin([
|
||||
{
|
||||
from: path.join(__dirname, '../static'),
|
||||
|
||||
@@ -6,8 +6,7 @@ const devMode = process.env.NODE_ENV !== 'production'
|
||||
const path = require('path')
|
||||
const { dependencies } = require('../package.json')
|
||||
const webpack = require('webpack')
|
||||
|
||||
const BabiliWebpackPlugin = require('babili-webpack-plugin')
|
||||
const TerserPlugin = require('terser-webpack-plugin');
|
||||
const CopyWebpackPlugin = require('copy-webpack-plugin')
|
||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
|
||||
const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
|
||||
@@ -49,7 +48,16 @@ let webConfig = {
|
||||
use: [
|
||||
devMode ? 'vue-style-loader' : MiniCssExtractPlugin.loader,
|
||||
'css-loader',
|
||||
'sass-loader'
|
||||
{
|
||||
loader: 'sass-loader',
|
||||
options: {
|
||||
implementation: require('sass'),
|
||||
prependData: '@import "@/components/Theme/Variables.scss";',
|
||||
sassOptions: {
|
||||
includePaths:[__dirname, 'src']
|
||||
}
|
||||
},
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -57,7 +65,17 @@ let webConfig = {
|
||||
use: [
|
||||
devMode ? 'vue-style-loader' : MiniCssExtractPlugin.loader,
|
||||
'css-loader',
|
||||
'sass-loader?indentedSyntax'
|
||||
{
|
||||
loader: 'sass-loader',
|
||||
options: {
|
||||
implementation: require('sass'),
|
||||
indentedSyntax: true,
|
||||
prependData: '@import "@/components/Theme/Variables.scss";',
|
||||
sassOptions: {
|
||||
includePaths:[__dirname, 'src']
|
||||
}
|
||||
},
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -134,20 +152,18 @@ let webConfig = {
|
||||
filename: 'index.html',
|
||||
chunks: ['index'],
|
||||
template: path.resolve(__dirname, '../src/index.ejs'),
|
||||
// minify: {
|
||||
// collapseWhitespace: true,
|
||||
// removeAttributeQuotes: true,
|
||||
// removeComments: true
|
||||
// },
|
||||
nodeModules: devMode
|
||||
? path.resolve(__dirname, '../node_modules')
|
||||
: false
|
||||
}),
|
||||
new HtmlWebpackPlugin({
|
||||
title: '关于',
|
||||
filename: 'about.html',
|
||||
chunks: ['about'],
|
||||
template: path.resolve(__dirname, '../src/about.ejs'),
|
||||
templateParameters(compilation, assets, options) {
|
||||
return {
|
||||
compilation: compilation,
|
||||
webpack: compilation.getStats().toJson(),
|
||||
webpackConfig: compilation.options,
|
||||
htmlWebpackPlugin: {
|
||||
files: assets,
|
||||
options: options
|
||||
},
|
||||
process
|
||||
}
|
||||
},
|
||||
// minify: {
|
||||
// collapseWhitespace: true,
|
||||
// removeAttributeQuotes: true,
|
||||
@@ -175,7 +191,15 @@ let webConfig = {
|
||||
},
|
||||
extensions: ['.js', '.vue', '.json', '.css']
|
||||
},
|
||||
target: 'web'
|
||||
target: 'web',
|
||||
optimization: {
|
||||
minimize: !devMode,
|
||||
minimizer: [
|
||||
new TerserPlugin({
|
||||
extractComments: false,
|
||||
})
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -185,7 +209,6 @@ if (!devMode) {
|
||||
webConfig.devtool = ''
|
||||
|
||||
webConfig.plugins.push(
|
||||
new BabiliWebpackPlugin(),
|
||||
new CopyWebpackPlugin([
|
||||
{
|
||||
from: path.join(__dirname, '../static'),
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
src/renderer/components/Icons/*.js
|
||||
|
||||
src/shared/locales/*
|
||||
!src/shared/locales/all.js
|
||||
!src/shared/locales/app.js
|
||||
!src/shared/locales/index.js
|
||||
!src/shared/locales/LocalManager.js
|
||||
|
||||
@@ -1,27 +1,34 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
parser: 'babel-eslint',
|
||||
parserOptions: {
|
||||
sourceType: 'module'
|
||||
},
|
||||
env: {
|
||||
browser: true,
|
||||
node: true
|
||||
},
|
||||
extends: 'standard',
|
||||
extends: [
|
||||
'plugin:vue/essential',
|
||||
'@vue/standard'
|
||||
],
|
||||
parserOptions: {
|
||||
parser: 'babel-eslint'
|
||||
},
|
||||
globals: {
|
||||
appId: true,
|
||||
__static: true
|
||||
},
|
||||
plugins: [
|
||||
'html'
|
||||
],
|
||||
'rules': {
|
||||
// allow paren-less arrow functions
|
||||
'arrow-parens': 0,
|
||||
// allow async-await
|
||||
'generator-star-spacing': 0,
|
||||
// allow debugger during development
|
||||
'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0
|
||||
}
|
||||
}
|
||||
rules: {
|
||||
'no-console': 'off',
|
||||
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
|
||||
'indent': ['error', 2],
|
||||
'vue/script-indent': ['error', 2, {
|
||||
'baseIndent': 1
|
||||
}],
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
'files': ['*.vue'],
|
||||
'rules': {
|
||||
'indent': 'off'
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
@@ -2,11 +2,17 @@
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: "bug \U0001F41E"
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Before feedback**
|
||||
Before you feedback, please search for the issues to see if there are similar problems that can solve your problem.
|
||||
|
||||
**Please delete the above and the contents of this line, then fill in the feedback form in the following format, Thanks.**
|
||||
<!-- Windows and Linux versions hide the application menu by default. Please use the keyboard shortcut "Ctrl+Shift+I" to open "Developer Tools" -->
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
@@ -23,12 +29,11 @@ A clear and concise description of what you expected to happen.
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Desktop (please complete the following information):**
|
||||
- OS: [e.g. macOS, Windows, Linux]
|
||||
- Version [e.g. 10.14.2, Win 10]
|
||||
|
||||
**App (please complete the following information):**
|
||||
- Version [e.g. 1.1.0]
|
||||
**Environment (please complete the following information):**
|
||||
- OS & Version: [e.g. macOS, Windows, Linux]
|
||||
- Version: [e.g. macOS 10.14.2, Windows 10, Ubuntu 18.04]
|
||||
- Motrix Version: [e.g. v1.1.3, v1.1.0]
|
||||
- Installation package type: [e.g. dmg, AppImage]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
---
|
||||
name: 错误反馈
|
||||
about: 创建一个错误报告帮助改进「请按照模板提交,提供详细的信息,方便我们复现之后处理」
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!--
|
||||
反馈之前请搜索一下已有 issues 和 帮助文档,看是否有类似问题可以解决你的问题
|
||||
https://github.com/agalwood/Motrix/issues
|
||||
http://motrix.app/support
|
||||
|
||||
按以下格式填写反馈信息,谢谢
|
||||
-->
|
||||
|
||||
**错误描述**
|
||||
清楚简洁地描述错误,方便我们复现之后处理。
|
||||
|
||||
**如何重现**
|
||||
重现步骤,如:
|
||||
1. 点击新建任务按钮
|
||||
2. 黏贴链接(如链接不涉及到隐私和版权问题,请顺便提供)
|
||||
3. 点击提交
|
||||
4. 发现报错
|
||||
|
||||
**预期的行为**
|
||||
清楚简洁地描述你期望发生的事情。
|
||||
|
||||
**截图**
|
||||
请添加屏幕截图以帮助解释你的问题:
|
||||
打开应用菜单中的「帮助」——「开发者工具」—— 切换到 console,然后**完整**截图。
|
||||
<!-- Windows 和 Linux 版本默认隐藏了应用菜单,请使用键盘快捷键 Ctrl+Shift+I 打开「开发者工具」 -->
|
||||
|
||||
**运行环境**
|
||||
- 操作系统类型: [如 macOS, Windows, Linux]
|
||||
- 具体版本: [如 macOS 10.14.2, Windows 10, Ubuntu 18.04]
|
||||
- Motrix 版本: [如 v1.1.3, v1.1.0]
|
||||
- 安装包类型:[如 dmg, AppImage]
|
||||
|
||||
**更多信息**
|
||||
补充有关该问题的其他信息。
|
||||
@@ -0,0 +1,28 @@
|
||||
---
|
||||
name: 新功能请求
|
||||
about: 你期望 Motrix 未来添加的新功能
|
||||
title: ''
|
||||
labels: enhancement ✨
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!--
|
||||
反馈之前请搜索一下已有 issues 和 帮助文档,看是否已经有人提交了类似的新功能请求
|
||||
https://github.com/agalwood/Motrix/issues
|
||||
http://motrix.app/support
|
||||
|
||||
按以下格式填写反馈信息,谢谢
|
||||
-->
|
||||
|
||||
**请描述一下你的新功能请求是否与已知问题有关?**
|
||||
简明扼要地描述了问题所在。
|
||||
|
||||
**描述你想要的解决方案**
|
||||
简明扼要地描述你想要的解决方案。
|
||||
|
||||
**描述你考虑过的替代方案**
|
||||
简明扼要地描述你考虑过的任何替代解决方案或功能。
|
||||
|
||||
**更多信息**
|
||||
补充有关该新功能的其他信息。
|
||||
@@ -0,0 +1,16 @@
|
||||
<!-- You can erase any parts of this template not applicable to your Pull Request. -->
|
||||
|
||||
## Description
|
||||
<!-- Write a brief description of the changes introduced by this PR -->
|
||||
|
||||
## Related Issues
|
||||
<!--
|
||||
Link to the issue that is fixed by this PR (if there is one)
|
||||
e.g. Fixes #1234, Addresses #1234, Related to #1234, etc.
|
||||
-->
|
||||
|
||||
### Checklist:
|
||||
|
||||
* [ ] Have you checked to ensure there aren't other open [Pull Requests](../../../pulls) for the same update/change?
|
||||
* [ ] Have you linted your code locally prior to submission?
|
||||
* [ ] Have you successfully ran app with your changes locally?
|
||||
@@ -0,0 +1,38 @@
|
||||
# Configuration for Lock Threads - https://github.com/dessant/lock-threads
|
||||
|
||||
# Number of days of inactivity before a closed issue or pull request is locked
|
||||
daysUntilLock: 60
|
||||
|
||||
# Skip issues and pull requests created before a given timestamp. Timestamp must
|
||||
# follow ISO 8601 (`YYYY-MM-DD`). Set to `false` to disable
|
||||
skipCreatedBefore: false
|
||||
|
||||
# Issues and pull requests with these labels will be ignored. Set to `[]` to disable
|
||||
exemptLabels: []
|
||||
|
||||
# Label to add before locking, such as `outdated`. Set to `false` to disable
|
||||
lockLabel: false
|
||||
|
||||
# Comment to post before locking. Set to `false` to disable
|
||||
lockComment: >
|
||||
This thread has been automatically locked since there has not been
|
||||
any recent activity after it was closed. Please open a new issue for
|
||||
related bugs.
|
||||
|
||||
# Assign `resolved` as the reason for locking. Set to `false` to disable
|
||||
setLockReason: true
|
||||
|
||||
# Limit to only `issues` or `pulls`
|
||||
# only: issues
|
||||
|
||||
# Optionally, specify configuration settings just for `issues` or `pulls`
|
||||
# issues:
|
||||
# exemptLabels:
|
||||
# - help-wanted
|
||||
# lockLabel: outdated
|
||||
|
||||
# pulls:
|
||||
# daysUntilLock: 30
|
||||
|
||||
# Repository to extend settings from
|
||||
# _extends: repo
|
||||
@@ -0,0 +1,60 @@
|
||||
name: Build/release
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
# Platforms to build on/for
|
||||
strategy:
|
||||
matrix:
|
||||
os: [macos-latest, ubuntu-latest, windows-latest]
|
||||
|
||||
steps:
|
||||
- name: Check out Git repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install Node.js, NPM and Yarn
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 12
|
||||
|
||||
- name: Install Snapcraft
|
||||
uses: samuelmeuli/action-snapcraft@v1
|
||||
# Only install Snapcraft on Ubuntu
|
||||
if: startsWith(matrix.os, 'ubuntu')
|
||||
with:
|
||||
# Log in to Snap Store
|
||||
snapcraft_token: ${{ secrets.snapcraft_token }}
|
||||
|
||||
- name: Prepare for app notarization (macOS)
|
||||
if: startsWith(matrix.os, 'macos')
|
||||
# Import Apple API key for app notarization on macOS
|
||||
run: |
|
||||
mkdir -p ~/private_keys/
|
||||
echo '${{ secrets.api_key }}' > ~/private_keys/AuthKey_${{ secrets.api_key_id }}.p8
|
||||
|
||||
- name: Build/release Electron app
|
||||
uses: samuelmeuli/action-electron-builder@v1
|
||||
with:
|
||||
build_script_name: 'build:github'
|
||||
# GitHub token, automatically provided to the action
|
||||
# (No need to define this secret in the repo settings)
|
||||
github_token: ${{ secrets.github_token }}
|
||||
|
||||
# macOS code signing certificate
|
||||
mac_certs: ${{ secrets.mac_certs }}
|
||||
mac_certs_password: ${{ secrets.mac_certs_password }}
|
||||
|
||||
# If the commit is tagged with a version (e.g. "v1.0.0"),
|
||||
# release the app after building
|
||||
release: ${{ startsWith(github.ref, 'refs/tags/v') }}
|
||||
env:
|
||||
# macOS notarization API key
|
||||
API_KEY_ID: ${{ secrets.api_key_id }}
|
||||
API_KEY_ISSUER_ID: ${{ secrets.api_key_issuer_id }}
|
||||
@@ -9,3 +9,4 @@ npm-debug.log.*
|
||||
thumbs.db
|
||||
!.gitkeep
|
||||
release/*
|
||||
.idea/
|
||||
|
||||
@@ -1,32 +1,44 @@
|
||||
osx_image: xcode10.1
|
||||
sudo: required
|
||||
dist: trusty
|
||||
|
||||
language: c
|
||||
matrix:
|
||||
|
||||
jobs:
|
||||
include:
|
||||
- os: osx
|
||||
osx_image: xcode11.3
|
||||
- os: linux
|
||||
env: CC=clang CXX=clang++ npm_config_clang=1
|
||||
compiler: clang
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- node_modules
|
||||
- "$HOME/.electron"
|
||||
- "$HOME/.cache"
|
||||
- $HOME/.cache/electron
|
||||
- $HOME/.cache/electron-builder
|
||||
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- libgnome-keyring-dev
|
||||
- icnsutils
|
||||
- rpm
|
||||
|
||||
before_install:
|
||||
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo apt-get install --no-install-recommends -y icnsutils graphicsmagick xz-utils; fi
|
||||
|
||||
install:
|
||||
- nvm install 10
|
||||
- nvm install 12.14.1
|
||||
- source ~/.bashrc
|
||||
- npm install -g xvfb-maybe
|
||||
- npm install
|
||||
|
||||
script:
|
||||
- npm run release
|
||||
|
||||
before_cache:
|
||||
- rm -rf $HOME/.cache/electron-builder/wine
|
||||
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as
|
||||
contributors and maintainers pledge to making participation in our project and
|
||||
our community a harassment-free experience for everyone, regardless of age, body
|
||||
size, disability, ethnicity, sex characteristics, gender identity and expression,
|
||||
level of experience, education, socio-economic status, nationality, personal
|
||||
appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment
|
||||
include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||
advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic
|
||||
address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable
|
||||
behavior and are expected to take appropriate and fair corrective action in
|
||||
response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or
|
||||
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||
permanently any contributor for other behaviors that they deem inappropriate,
|
||||
threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces
|
||||
when an individual is representing the project or its community. Examples of
|
||||
representing a project or community include using an official project e-mail
|
||||
address, posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event. Representation of a project may be
|
||||
further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting the project team at agalwood.net@gmail.com. All
|
||||
complaints will be reviewed and investigated and will result in a response that
|
||||
is deemed necessary and appropriate to the circumstances. The project team is
|
||||
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||
Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||
faith may face temporary or permanent repercussions as determined by other
|
||||
members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see
|
||||
https://www.contributor-covenant.org/faq
|
||||
@@ -0,0 +1,41 @@
|
||||
# Motrix 贡献指南
|
||||
|
||||
开始贡献之前,确保你已经理解了 [GitHub 的协作流程](https://guides.github.com/introduction/flow/)。
|
||||
|
||||
## 🌍 翻译指南
|
||||
|
||||
首先你要确定一个语言的英文简写作为 **locale**,如 en-US,这个 locale 值请严格参考 [Electron 的 Locales 文档](https://electronjs.org/docs/api/locales)
|
||||
|
||||
Motrix 的国际化分两部分:
|
||||
|
||||
- Element UI
|
||||
- 菜单和主界面
|
||||
|
||||
### Element UI
|
||||
|
||||
Element UI 的国际化由 [Element 社区](http://element.eleme.io/#/en-US/component/i18n)提供,找到 **locale** 对应的语言包文件「两者 locale 命名可能不一致」,在 `src/shared/locales/all.js` 中引入,如
|
||||
|
||||
```javascript
|
||||
import eleLocaleEn from 'element-ui/lib/locale/lang/en'
|
||||
import eleLocaleZhCN from 'element-ui/lib/locale/lang/zh-CN'
|
||||
```
|
||||
|
||||
### 菜单和主界面
|
||||
|
||||
Motrix 使用 i18next 作为翻译支持库,所以你可能需要简单了解一下它的[使用方法](https://www.i18next.com/overview/getting-started)。
|
||||
配置文件按照语言 (**locale**) 划分目录:`src/shared/locales`,如:`src/shared/locales/en-US` 和 `src/shared/locales/zh-CN`。
|
||||
|
||||
目录里面有按业务模块划分的语言文件
|
||||
|
||||
菜单模块经过重构之后,国际化已经打散到了以下文件里了,不再需要再复制 `src/main/menus` 里的配置。
|
||||
|
||||
- about.js
|
||||
- app.js
|
||||
- edit.js
|
||||
- help.js
|
||||
- index.js
|
||||
- menu.js
|
||||
- preferences.js
|
||||
- subnav.js
|
||||
- task.js
|
||||
- window.js
|
||||
@@ -0,0 +1,41 @@
|
||||
# Motrix Contributing Guide
|
||||
|
||||
Before you start contributing, make sure you already understand [GitHub flow](https://guides.github.com/introduction/flow/).
|
||||
|
||||
## 🌍 Translation Guide
|
||||
|
||||
First you need to determine the English abbreviation of a language as **locale**, such as en-US, this locale value should strictly refer to the [electron's documentation](https://electronjs.org/docs/api/locales).
|
||||
|
||||
The internationalization of Motrix is divided into two parts:
|
||||
|
||||
- Element UI
|
||||
- Menu & Main Interface
|
||||
|
||||
### Element UI
|
||||
|
||||
The internationalization of Element UI is provided by the [Element community](http://element.eleme.io/#/en-US/component/i18n), then find the language pack file corresponding to **locale** (both locale naming may be inconsistent), which is import in `src/shared/locales/all.js`, such as
|
||||
|
||||
```javascript
|
||||
import eleLocaleEn from 'element-ui/lib/locale/lang/en'
|
||||
import eleLocaleZhCN from 'element-ui/lib/locale/lang/zh-CN'
|
||||
```
|
||||
|
||||
### Menu & Main Interface
|
||||
|
||||
Motrix uses the [i18next](https://www.i18next.com/overview/getting-started) library for internationalization, so you need a quick look at how to use it.
|
||||
The configuration files are divided by **locale**: `src/shared/locales`, such as `src/shared/locales/en-US` and `src/shared/locales/zh-CN`.
|
||||
|
||||
There are language files in the directory according to the business module.
|
||||
|
||||
After the menu module is refactored, the internationalization of the menu has been dispersed into the following files, and there is no need to copy the configuration in `src/main/menus`.
|
||||
|
||||
- about.js
|
||||
- app.js
|
||||
- edit.js
|
||||
- help.js
|
||||
- index.js
|
||||
- menu.js
|
||||
- preferences.js
|
||||
- subnav.js
|
||||
- task.js
|
||||
- window.js
|
||||
@@ -1,4 +1,6 @@
|
||||
Copyright 2018 Dr_rOot
|
||||
The MIT License
|
||||
|
||||
Copyright 2018-present Dr_rOot
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
|
||||
@@ -7,76 +7,153 @@
|
||||
[English](./README.md) | 简体中文
|
||||
|
||||
## 一款全能的下载工具
|
||||
[](https://travis-ci.org/agalwood/Motrix) [](https://ci.appveyor.com/project/agalwood/motrix/branch/master)  
|
||||
|
||||
[](https://github.com/agalwood/Motrix/releases)  [](https://travis-ci.com/agalwood/Motrix) [](https://ci.appveyor.com/project/agalwood/motrix/branch/master) [](https://github.com/agalwood/Motrix/releases) 
|
||||
|
||||
我是个兴趣使然的桌面应用开发者🤓,利用搬砖之余开发了 Motrix。
|
||||
|
||||
Motirx 是一款全能的下载工具,支持下载 HTTP、FTP、BT、磁力链、百度网盘等资源。它的界面简洁易用,希望大家喜欢 👻。
|
||||
Motrix 是一款全能的下载工具,支持下载 HTTP、FTP、BT、磁力链、某盘等资源。它的界面简洁易用,希望大家喜欢 👻。
|
||||
|
||||
✈️ 去 [官网](https://motrix.app) 逛逛
|
||||
✈️ 去 [官网](https://motrix.app/zh-CN) 逛逛 | 📖 查看 [帮助手册](http://motrix.app/support/issues)
|
||||
|
||||
## 💽 安装稳定版
|
||||
[GitHub](https://github.com/agalwood/Motrix/releases) 和 [官网](https://motrix.app) 提供了已经编译好的稳定版安装包,当然你也可以自己克隆代码编译打包。
|
||||
|
||||
> 七牛CDN流量🔥烧不起,高峰时一天烧了110G的流量💸💸💸
|
||||
[GitHub](https://github.com/agalwood/Motrix/releases) 和 [官网](https://motrix.app/zh-CN) 提供了已经编译好的稳定版安装包,当然你也可以自己克隆代码编译打包。
|
||||
|
||||
### Windows
|
||||
|
||||
建议使用安装包(Motrix-Setup-x.y.z.exe)安装 Motrix 以确保完整的体验,例如关联 torrent 文件,捕获磁力链等。
|
||||
|
||||
如果你更喜欢便携版,你可以使用 [scoop](https://github.com/lukesampson/scoop)(需要 Windows 7+,天朝用户可能需要设置 Git 代理)安装最新便携版本的 Motrix。
|
||||
|
||||
```bash
|
||||
scoop bucket add extras
|
||||
scoop install motrix
|
||||
```
|
||||
|
||||
### macOS
|
||||
|
||||
macOS 用户可以使用 `brew cask` 安装 Motrix,感谢 [Mitscherlich](https://github.com/Mitscherlich) 的 [PR](https://github.com/Homebrew/homebrew-cask/pull/59494)。
|
||||
|
||||
```bash
|
||||
brew update && brew cask install motrix
|
||||
```
|
||||
|
||||
### Linux
|
||||
|
||||
你可以下载 AppImage(适用于所有 Linux 发行版)软件包或 snap 或从源代码构建安装 Motrix。
|
||||
|
||||
构建请阅读 **编译打包** 部分。
|
||||
|
||||
对于 Arch Linux 用户,可以使用 [aur](https://aur.archlinux.org/packages/motrix/) 安装 Motrix,感谢维护者 [weearc](https://github.com/weearc)。
|
||||
|
||||
运行以下命令进行安装:
|
||||
|
||||
```bash
|
||||
yay motrix
|
||||
```
|
||||
|
||||
Motrix 在 Linux 中首次启动可能需要使用 `sudo` 运行,因为可能没有创建下载会话文件的权限 (`/var/cache/aria2.session`)。
|
||||
|
||||
## ✨ 特性
|
||||
|
||||
- 🕹 简洁明了的图形操作界面
|
||||
- 🧲 支持BT和磁力链任务
|
||||
- 🤫 支持下载百度云盘资源
|
||||
- 🦄 支持BT和磁力链任务
|
||||
- ☑️ 支持选择性下载BT部分文件
|
||||
- 💾 支持下载某盘资源
|
||||
- 🎛 最高支持 10 个任务同时下载
|
||||
- 🚀 单任务最高支持 64 线程下载
|
||||
- 🚥 设置上传/下载限速
|
||||
- 🕶 模拟用户代理UA
|
||||
- 🔔 下载完成后通知
|
||||
- 💻 支持触控栏快捷健 (Mac 专享)
|
||||
- 💻 支持触控栏快捷键 (Mac 专享)
|
||||
- 🤖 常驻系统托盘,操作更加便捷
|
||||
- 🌑 深色模式
|
||||
- 🗑 移除任务时可同时删除相关文件
|
||||
- 🌍 国际化(可选择简体中文或英文界面)
|
||||
- 🌍 国际化,[查看已可选的语言](#-国际化)
|
||||
- 🎏 ...
|
||||
|
||||
## 🖥 应用界面
|
||||

|
||||
|
||||

|
||||
|
||||
## ⌨️ 本地开发
|
||||
|
||||
### 克隆代码
|
||||
|
||||
```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'
|
||||
```
|
||||
|
||||
> Error: Electron failed to install correctly, please delete node_modules/electron and try installing again
|
||||
|
||||
`Electron` 下载安装失败的问题,解决方式请参考 https://github.com/electron/electron/issues/8466#issuecomment-571425574
|
||||
|
||||
如果喜欢 [Yarn](https://yarnpkg.com/),也可以使用 `yarn` 安装依赖
|
||||
|
||||
### 开发模式
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
### 编译打包
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
完成之后可以在项目的 `release` 目录看到编译打包好的应用文件
|
||||
|
||||
## 🛠 技术栈
|
||||
|
||||
- [Electron](https://electronjs.org/)
|
||||
- [Vue](https://vuejs.org/) + [VueX](https://vuex.vuejs.org/) + [Element](https://element.eleme.io)
|
||||
- [Aria2](https://aria2.github.io/) (注:macOS 和 Linux 版本使用的是 64 位的 aria2c,Windows 版使用的 32 位的)
|
||||
|
||||
## ☑️ TODO
|
||||
|
||||
开发计划请移步 [Trello](https://trello.com/b/qNUzA0bv/motrix) 查看
|
||||
|
||||
## 🤝 参与共建 [](http://makeapullrequest.com)
|
||||
## 🤝 参与共建 [](http://makeapullrequest.com)
|
||||
|
||||
如果你有兴趣参与共同开发,欢迎 FORK 和 PR。
|
||||
|
||||
## 🌍 国际化
|
||||
|
||||
欢迎大家将 Motrix 翻译成更多的语言版本 🧐,开工之前请先阅读一下 [翻译指南](./CONTRIBUTING-CN.md#-翻译指南)。
|
||||
|
||||
| Key | Name | Status |
|
||||
|-------|:--------------------|:-------------|
|
||||
| ca | Català | ✔️ [@marcizhu](https://github.com/marcizhu) |
|
||||
| de | Deutsch | ✔️ [@Schloemicher](https://github.com/Schloemicher) |
|
||||
| en-US | English | ✔️ |
|
||||
| fa | فارسی | ✔️ [@Nima-Ra](https://github.com/Nima-Ra) |
|
||||
| fr | Français | ✔️ [@gpatarin](https://github.com/gpatarin) |
|
||||
| ja | 日本語 | ✔️ [@hbkrkzk](https://github.com/hbkrkzk) |
|
||||
| ko | 한국어 | ✔️ [@KOZ39](https://github.com/KOZ39) |
|
||||
| pt-BR | Portuguese (Brazil) | ✔️ [@andrenoberto](https://github.com/andrenoberto) |
|
||||
| ru | Русский | ✔️ [@bladeaweb](https://github.com/bladeaweb) |
|
||||
| tr | Türkçe | ✔️ [@abdullah](https://github.com/abdullah) |
|
||||
| uk | Українська | ✔️ [@bladeaweb](https://github.com/bladeaweb) |
|
||||
| zh-CN | 简体中文 | ✔️ |
|
||||
| zh-TW | 繁體中文 | ✔️ [@Yukaii](https://github.com/Yukaii) |
|
||||
|
||||
## 📜 开源许可
|
||||
|
||||
基于 [MIT license](https://opensource.org/licenses/MIT) 许可进行开源。
|
||||
|
||||
@@ -4,71 +4,148 @@
|
||||
<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.
|
||||
[](https://travis-ci.org/agalwood/Motrix) [](https://ci.appveyor.com/project/agalwood/motrix/branch/master)  
|
||||
## A full-featured download manager
|
||||
|
||||
[](https://github.com/agalwood/Motrix/releases)  [](https://travis-ci.com/agalwood/Motrix) [](https://ci.appveyor.com/project/agalwood/motrix/branch/master) [](https://github.com/agalwood/Motrix/releases) 
|
||||
|
||||
English | [简体中文](./README-CN.md)
|
||||
|
||||
Motrix is a full-featured download manager that support downloading HTTP, FTP, BitTorrent, Magnet, Baidu Net Disk etc.
|
||||
Motrix is a full-featured download manager that supports downloading HTTP, FTP, BitTorrent, Magnet, etc.
|
||||
|
||||
It has a clean and simple interface and is easy to use. I hope you will like it 👻.
|
||||
Motrix has a clean and easy to use interface. I hope you will like it 👻.
|
||||
|
||||
✈️ [Official Website](https://motrix.app)
|
||||
✈️ [Official Website](https://motrix.app) | 📖 [Manual](https://github.com/agalwood/Motrix/wiki)
|
||||
|
||||
## 💽 Installation
|
||||
|
||||
Download from [GitHub Releases](https://github.com/agalwood/Motrix/releases) and install it.
|
||||
|
||||
### Windows
|
||||
|
||||
It is recommended to install Motrix using the installation package (Motrix-Setup-x.y.z.exe) to ensure a complete experience, such as associating torrent files, capturing magnet links, etc.
|
||||
|
||||
If you prefer the portable version, you can use [scoop](https://github.com/lukesampson/scoop) (need Windows 7+) to install Motrix.
|
||||
|
||||
```bash
|
||||
scoop bucket add extras
|
||||
scoop install motrix
|
||||
```
|
||||
|
||||
### macOS
|
||||
|
||||
The macOS users can install Motrix using `brew cask`, thanks to [PR](https://github.com/Homebrew/homebrew-cask/pull/59494) of [Mitscherlich](https://github.com/Mitscherlich).
|
||||
|
||||
```bash
|
||||
brew update && brew cask install motrix
|
||||
```
|
||||
|
||||
### Linux
|
||||
|
||||
You can download the AppImage (for all Linux distributions) package or snap or just build from source code to install Motrix.
|
||||
|
||||
Please read the **Build** section.
|
||||
|
||||
For Arch Linux users, Motrix is available in [aur](https://aur.archlinux.org/packages/motrix/), thanks to the maintainer [weearc](https://github.com/weearc).
|
||||
|
||||
Run the following command to install:
|
||||
|
||||
```bash
|
||||
yay motrix
|
||||
```
|
||||
|
||||
Motrix may need to run with `sudo` for the first time in Linux because there is no permission to create the download session file (`/var/cache/aria2.session`).
|
||||
|
||||
## ✨ Features
|
||||
|
||||
- 🕹 Simple and clear user interface
|
||||
- 🧲 Support BitTorrent & Magnet
|
||||
- 🤫 Support downloading Baidu Net Disk
|
||||
- 🎛 Up to 10 tasks concurrently download
|
||||
- 🚀 Single task maximum support 64 thread download
|
||||
- 🦄 Supports BitTorrent & Magnet
|
||||
- ☑️ BitTorrent selective download
|
||||
- 💾 Supports downloading BD Net Disk
|
||||
- 🎛 Up to 10 concurrent download tasks
|
||||
- 🚀 Supports 64 threads in a single task
|
||||
- 🚥 Supports speed limit
|
||||
- 🕶 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, currently available Simplified Chinese & English.
|
||||
- 🌍 I18n, [View supported languages](#-internationalization).
|
||||
- 🎏 ...
|
||||
|
||||
## 🖥 User Interface
|
||||

|
||||
|
||||

|
||||
|
||||
## ⌨️ Development
|
||||
|
||||
### Clone Code
|
||||
|
||||
```bash
|
||||
git clone git@github.com:agalwood/Motrix.git
|
||||
```
|
||||
|
||||
### Install Dependencies
|
||||
|
||||
```bash
|
||||
cd Motrix
|
||||
npm install
|
||||
```
|
||||
|
||||
> Error: Electron failed to install correctly, please delete node_modules/electron and try installing again
|
||||
|
||||
`Electron` failed to install correctly, please refer to https://github.com/electron/electron/issues/8466#issuecomment-571425574
|
||||
|
||||
If you like [Yarn](https://yarnpkg.com/), you can also use `yarn` to install dependencies.
|
||||
|
||||
### Dev Mode
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
### Build Release
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
After build finish, you can see the compiled packaged application file in the `release` directory of the project.
|
||||
|
||||
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 [](http://makeapullrequest.com)
|
||||
If you are interested in participating in joint development, Fork and PR are welcome.
|
||||
## 🤝 Contribute [](http://makeapullrequest.com)
|
||||
|
||||
If you are interested in participating in joint development, PR and Forks are welcome!
|
||||
|
||||
## 🌍 Internationalization
|
||||
|
||||
Translations into versions for other languages are welcome 🧐! Please read the [translation guide](./CONTRIBUTING.md#-translation-guide) before starting translations.
|
||||
|
||||
| Key | Name | Status |
|
||||
|-------|:--------------------|:-------------|
|
||||
| ca | Català | ✔️ [@marcizhu](https://github.com/marcizhu) |
|
||||
| de | Deutsch | ✔️ [@Schloemicher](https://github.com/Schloemicher) |
|
||||
| en-US | English | ✔️ |
|
||||
| fa | فارسی | ✔️ [@Nima-Ra](https://github.com/Nima-Ra) |
|
||||
| fr | Français | ✔️ [@gpatarin](https://github.com/gpatarin) |
|
||||
| ja | 日本語 | ✔️ [@hbkrkzk](https://github.com/hbkrkzk) |
|
||||
| ko | 한국어 | ✔️ [@KOZ39](https://github.com/KOZ39) |
|
||||
| pt-BR | Portuguese (Brazil) | ✔️ [@andrenoberto](https://github.com/andrenoberto) |
|
||||
| ru | Русский | ✔️ [@bladeaweb](https://github.com/bladeaweb) |
|
||||
| tr | Türkçe | ✔️ [@abdullah](https://github.com/abdullah) |
|
||||
| uk | Українська | ✔️ [@bladeaweb](https://github.com/bladeaweb) |
|
||||
| zh-CN | 简体中文 | ✔️ |
|
||||
| zh-TW | 繁體中文 | ✔️ [@Yukaii](https://github.com/Yukaii) |
|
||||
|
||||
## 📜 License
|
||||
|
||||
[MIT](https://opensource.org/licenses/MIT) Copyright (c) 2018-present Dr_rOot
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
provider: generic
|
||||
url: 'https://motrix.app/release/'
|
||||
url: 'https://dl.motrix.app/release/'
|
||||
|
||||
@@ -1,24 +1,17 @@
|
||||
version: 1.0.{build}
|
||||
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
|
||||
image: Visual Studio 2017
|
||||
|
||||
platform:
|
||||
- x64
|
||||
|
||||
cache:
|
||||
- node_modules
|
||||
- '%APPDATA%\npm-cache'
|
||||
- '%USERPROFILE%\.electron'
|
||||
- '%USERPROFILE%\AppData\Local\Yarn\cache'
|
||||
|
||||
init:
|
||||
- git config --global core.autocrlf input
|
||||
|
||||
install:
|
||||
- ps: Install-Product node 10 x64
|
||||
- ps: Install-Product node 12.14.1 x64
|
||||
- git reset --hard HEAD
|
||||
- npm install
|
||||
- node --version
|
||||
@@ -27,3 +20,7 @@ build_script:
|
||||
- npm run release
|
||||
|
||||
test: off
|
||||
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
// Forked from https://github.com/samuelmeuli/mini-diary/blob/master/scripts/after-pack.js
|
||||
|
||||
/**
|
||||
* Source: https://github.com/patrikx3/redis-ui/blob/master/src/build/after-pack.js
|
||||
*
|
||||
* Copyright (c) 2019 Patrik Laszlo / P3X / Corifeus and contributors.
|
||||
*
|
||||
* MIT License
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
// TODO: Remove script once https://github.com/electron/electron/issues/17972 is solved by
|
||||
// `electron-builder`
|
||||
|
||||
const fs = require('fs')
|
||||
const { spawn } = require('child_process')
|
||||
const { chdir } = require('process')
|
||||
|
||||
const pkg = require('../package.json')
|
||||
const binName = `${pkg.name}`.toLowerCase()
|
||||
|
||||
const exec = async function exec (cmd, args = []) {
|
||||
const child = spawn(cmd, args, { shell: true })
|
||||
redirectOutputFor(child)
|
||||
await waitFor(child)
|
||||
}
|
||||
|
||||
const redirectOutputFor = child => {
|
||||
const printStdout = data => {
|
||||
process.stdout.write(data.toString())
|
||||
}
|
||||
const printStderr = data => {
|
||||
process.stderr.write(data.toString())
|
||||
}
|
||||
child.stdout.on('data', printStdout)
|
||||
child.stderr.on('data', printStderr)
|
||||
|
||||
child.once('close', () => {
|
||||
child.stdout.off('data', printStdout)
|
||||
child.stderr.off('data', printStderr)
|
||||
})
|
||||
}
|
||||
|
||||
const waitFor = async function (child) {
|
||||
return new Promise(resolve => {
|
||||
child.once('close', () => resolve())
|
||||
})
|
||||
}
|
||||
|
||||
const linuxTargets = [
|
||||
'AppImage',
|
||||
'deb',
|
||||
'rpm',
|
||||
'snap'
|
||||
]
|
||||
|
||||
module.exports = async function (context) {
|
||||
console.warn('after build; disable sandbox')
|
||||
const isLinux = context.targets.find(
|
||||
target => linuxTargets.includes(target)
|
||||
)
|
||||
if (!isLinux) {
|
||||
return
|
||||
}
|
||||
const originalDir = process.cwd()
|
||||
const dirname = context.appOutDir
|
||||
chdir(dirname)
|
||||
|
||||
await exec('mv', [binName, binName + '.bin'])
|
||||
const wrapperScript = `#!/bin/bash
|
||||
"\${BASH_SOURCE%/*}"/${binName}.bin "$@" --no-sandbox
|
||||
`
|
||||
fs.writeFileSync(binName, wrapperScript)
|
||||
await exec('chmod', ['+x', binName])
|
||||
|
||||
chdir(originalDir)
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
require('dotenv').config()
|
||||
const { notarize } = require('electron-notarize')
|
||||
const pkg = require('../package.json')
|
||||
|
||||
exports.default = async function (context) {
|
||||
const { electronPlatformName, appOutDir } = context
|
||||
if (electronPlatformName !== 'darwin') {
|
||||
return
|
||||
}
|
||||
|
||||
const skipNotarize = process.env.SKIP_NOTARIZE
|
||||
if (skipNotarize === 'yes') {
|
||||
console.log('skipping notarize')
|
||||
return
|
||||
}
|
||||
|
||||
const appBundleId = pkg.build.appId
|
||||
const appName = context.packager.appInfo.productFilename
|
||||
|
||||
return await notarize({
|
||||
appBundleId,
|
||||
appPath: `${appOutDir}/${appName}.app`,
|
||||
ascProvider: process.env.TEAM_ID,
|
||||
appleId: process.env.APPLE_ID,
|
||||
appleIdPassword: process.env.APPLE_ID_PWD
|
||||
})
|
||||
}
|
||||
|
After Width: | Height: | Size: 59 KiB |
@@ -0,0 +1,3 @@
|
||||
# aria2
|
||||
|
||||
Source code: https://github.com/agalwood/aria2
|
||||
@@ -1,108 +1,71 @@
|
||||
## '#'开头为注释内容, 选项都有相应的注释说明, 根据需要修改 ##
|
||||
## 被注释的选项填写的是默认值, 建议在需要修改时再取消注释 ##
|
||||
## 添加了@和默认启用的选项都是系统需要调用的,请不要随意改动否则可能无法正常运行
|
||||
###############################
|
||||
# Motrix macOS Aria2 config file
|
||||
#
|
||||
# @see https://aria2.github.io/manual/en/html/aria2c.html
|
||||
#
|
||||
###############################
|
||||
|
||||
## 文件保存相关 ##
|
||||
|
||||
# 文件的保存路径(可使用绝对路径或相对路径), 默认: 当前启动位置
|
||||
# 此项 OS X 无法使用 $HOME 及 ~/ 设置路径 建议使用 /users/用户名/downloads
|
||||
#@dir=$HOME/downloads
|
||||
# 启用磁盘缓存, 0为禁用缓存, 需1.16以上版本, 默认:16M
|
||||
#@disk-cache=32M
|
||||
# 文件预分配方式, 能有效降低磁盘碎片, 默认:prealloc
|
||||
# 预分配所需时间: none < falloc ? trunc < prealloc
|
||||
# falloc和trunc则需要文件系统和内核支持
|
||||
# NTFS建议使用falloc, EXT3/4建议trunc, MAC 下需要注释此项
|
||||
# file-allocation=none
|
||||
# 断点续传
|
||||
#@continue=true
|
||||
|
||||
## 下载连接相关 ##
|
||||
|
||||
# 最大同时下载任务数, 运行时可修改, 默认:5
|
||||
#@max-concurrent-downloads=10
|
||||
# 同一服务器连接数, 添加时可指定, 默认:1
|
||||
#@max-connection-per-server=15
|
||||
# 最小文件分片大小, 添加时可指定, 取值范围1M -1024M, 默认:20M
|
||||
# 假定size=10M, 文件为20MiB 则使用两个来源下载; 文件为15MiB 则使用一个来源下载
|
||||
#@min-split-size=10M
|
||||
# 单个任务最大线程数, 添加时可指定, 默认:5
|
||||
#@split=15
|
||||
# 整体下载速度限制, 运行时可修改, 默认:0
|
||||
#@max-overall-download-limit=0
|
||||
# 单个任务下载速度限制, 默认:0
|
||||
#@max-download-limit=0
|
||||
# 整体上传速度限制, 运行时可修改, 默认:0
|
||||
#@max-overall-upload-limit=0
|
||||
# 单个任务上传速度限制, 默认:0
|
||||
#@max-upload-limit=0
|
||||
# 禁用IPv6, 默认:false
|
||||
disable-ipv6=false
|
||||
#运行覆盖已存在文件
|
||||
#@allow-overwrite=true
|
||||
#自动重命名
|
||||
#@auto-file-renaming=true
|
||||
|
||||
## 进度保存相关 ##
|
||||
|
||||
# 从会话文件中读取下载任务
|
||||
#@input-file=/Users/Shared/aria2.session
|
||||
# 在Aria2退出时保存`错误/未完成`的下载任务到会话文件
|
||||
#@save-session=/Users/Shared/aria2.session
|
||||
# 定时保存会话, 0为退出时才保存, 需1.16.1以上版本, 默认:0
|
||||
save-session-interval=30
|
||||
|
||||
## RPC相关设置 ##
|
||||
|
||||
# 启用RPC, 默认:false
|
||||
################ RPC ################
|
||||
# Enable JSON-RPC/XML-RPC server.
|
||||
enable-rpc=true
|
||||
# 允许所有来源, 默认:false
|
||||
# Add Access-Control-Allow-Origin header field with value * to the RPC response.
|
||||
rpc-allow-origin-all=true
|
||||
# 允许非外部访问, 默认:false
|
||||
# Listen incoming JSON-RPC/XML-RPC requests on all network interfaces.
|
||||
rpc-listen-all=true
|
||||
# 事件轮询方式, 取值:[epoll, kqueue, port, poll, select], 不同系统默认值不同
|
||||
#event-poll=select
|
||||
# RPC监听端口, 端口被占用时可以修改, 默认:6800
|
||||
# 使用本客户端请勿修改此项
|
||||
# rpc-listen-port=6800
|
||||
# 设置的RPC授权令牌, v1.18.4新增功能, 取代 --rpc-user 和 --rpc-passwd 选项
|
||||
#rpc-secret=token
|
||||
|
||||
## BT/PT下载相关 ##
|
||||
|
||||
# 当下载的是一个种子(以.torrent结尾)时, 自动开始BT任务, 默认:true
|
||||
#follow-torrent=true
|
||||
# BT监听端口, 当端口被屏蔽时使用, 默认:6881-6999
|
||||
listen-port=51413
|
||||
# 单个种子最大连接数, 默认:55
|
||||
#bt-max-peers=55
|
||||
# 打开DHT功能, PT需要禁用, 默认:true
|
||||
# enable-dht=false
|
||||
# 打开IPv6 DHT功能, PT需要禁用
|
||||
#enable-dht6=false
|
||||
# DHT网络监听端口, 默认:6881-6999
|
||||
#dht-listen-port=6881-6999
|
||||
# 本地节点查找, PT需要禁用, 默认:false
|
||||
bt-enable-lpd=true
|
||||
# 种子交换, PT需要禁用, 默认:true
|
||||
enable-peer-exchange=true
|
||||
# 每个种子限速, 对少种的PT很有用, 默认:50K
|
||||
#bt-request-peer-speed-limit=50K
|
||||
# 客户端伪装, PT需要
|
||||
peer-id-prefix=-TR2770-
|
||||
################ File system ################
|
||||
# Save a control file(*.aria2) every SEC seconds.
|
||||
auto-save-interval=10
|
||||
# Enable disk cache.
|
||||
disk-cache=32M
|
||||
# Specify file allocation method.
|
||||
file-allocation=falloc
|
||||
# Save error/unfinished downloads to a file specified by --save-session option every SEC seconds.
|
||||
save-session-interval=10
|
||||
|
||||
|
||||
################ Task ################
|
||||
# Exclude seed only downloads when counting concurrent active downloads
|
||||
bt-detach-seed-only=true
|
||||
# Verify the peer using certificates specified in --ca-certificate option.
|
||||
check-certificate=false
|
||||
# If aria2 receives "file not found" status from the remote HTTP/FTP servers NUM times
|
||||
# without getting a single byte, then force the download to fail.
|
||||
max-file-not-found=5
|
||||
# Set number of tries.
|
||||
max-tries=5
|
||||
# aria2 does not split less than 2*SIZE byte range.
|
||||
min-split-size=1M
|
||||
# Set user agent for HTTP(S) downloads.
|
||||
user-agent=Transmission/2.94
|
||||
# 当种子的分享率达到这个数时, 自动停止做种, 0为一直做种, 默认:1.0
|
||||
seed-ratio=1.0
|
||||
# 强制保存会话, 即使任务已经完成, 默认:false
|
||||
# 较新的版本开启后会在任务完成后依然保留.aria2文件
|
||||
#force-save=false
|
||||
# BT校验相关, 默认:true
|
||||
#bt-hash-check-seed=true
|
||||
# 继续之前的BT任务时, 无需再次校验, 默认:false
|
||||
|
||||
|
||||
################ BT Task ################
|
||||
# Enable Local Peer Discovery.
|
||||
bt-enable-lpd=true
|
||||
# Requires BitTorrent message payload encryption with arc4.
|
||||
bt-force-encryption=true
|
||||
# If true is given, after hash check using --check-integrity option and file is complete, continue to seed file.
|
||||
bt-hash-check-seed=true
|
||||
# Specify the maximum number of peers per torrent.
|
||||
bt-max-peers=255
|
||||
# Removes the unselected files when download is completed in BitTorrent.
|
||||
bt-remove-unselected-file=true
|
||||
# Seed previously downloaded files without verifying piece hashes.
|
||||
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
|
||||
# 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
|
||||
# Enable IPv4 DHT functionality. It also enables UDP tracker support.
|
||||
enable-dht=true
|
||||
# Enable IPv6 DHT functionality.
|
||||
enable-dht6=true
|
||||
# Enable Peer Exchange extension.
|
||||
enable-peer-exchange=true
|
||||
# Specify the string used during the bitorrent extended handshake for the peer's client version.
|
||||
peer-agent=Transmission/2.94
|
||||
# Specify the prefix of peer ID.
|
||||
peer-id-prefix=-TR2940-
|
||||
|
||||
@@ -1,108 +1,71 @@
|
||||
## '#'开头为注释内容, 选项都有相应的注释说明, 根据需要修改 ##
|
||||
## 被注释的选项填写的是默认值, 建议在需要修改时再取消注释 ##
|
||||
## 添加了@和默认启用的选项都是系统需要调用的,请不要随意改动否则可能无法正常运行
|
||||
###############################
|
||||
# Motrix Linux Aria2 config file
|
||||
#
|
||||
# @see https://aria2.github.io/manual/en/html/aria2c.html
|
||||
#
|
||||
###############################
|
||||
|
||||
## 文件保存相关 ##
|
||||
|
||||
# 文件的保存路径(可使用绝对路径或相对路径), 默认: 当前启动位置
|
||||
# 此项 OS X 无法使用 $HOME 及 ~/ 设置路径 建议使用 /users/用户名/downloads
|
||||
#@dir=$HOME/downloads
|
||||
# 启用磁盘缓存, 0为禁用缓存, 需1.16以上版本, 默认:16M
|
||||
#@disk-cache=32M
|
||||
# 文件预分配方式, 能有效降低磁盘碎片, 默认:prealloc
|
||||
# 预分配所需时间: none < falloc ? trunc < prealloc
|
||||
# falloc和trunc则需要文件系统和内核支持
|
||||
# NTFS建议使用falloc, EXT3/4建议trunc, MAC 下需要注释此项
|
||||
file-allocation=trunc
|
||||
# 断点续传
|
||||
#@continue=true
|
||||
|
||||
## 下载连接相关 ##
|
||||
|
||||
# 最大同时下载任务数, 运行时可修改, 默认:5
|
||||
#@max-concurrent-downloads=10
|
||||
# 同一服务器连接数, 添加时可指定, 默认:1
|
||||
#@max-connection-per-server=15
|
||||
# 最小文件分片大小, 添加时可指定, 取值范围1M -1024M, 默认:20M
|
||||
# 假定size=10M, 文件为20MiB 则使用两个来源下载; 文件为15MiB 则使用一个来源下载
|
||||
#@min-split-size=10M
|
||||
# 单个任务最大线程数, 添加时可指定, 默认:5
|
||||
#@split=15
|
||||
# 整体下载速度限制, 运行时可修改, 默认:0
|
||||
#@max-overall-download-limit=0
|
||||
# 单个任务下载速度限制, 默认:0
|
||||
#@max-download-limit=0
|
||||
# 整体上传速度限制, 运行时可修改, 默认:0
|
||||
#@max-overall-upload-limit=0
|
||||
# 单个任务上传速度限制, 默认:0
|
||||
#@max-upload-limit=0
|
||||
# 禁用IPv6, 默认:false
|
||||
disable-ipv6=false
|
||||
#运行覆盖已存在文件
|
||||
#@allow-overwrite=true
|
||||
#自动重命名
|
||||
#@auto-file-renaming=true
|
||||
|
||||
## 进度保存相关 ##
|
||||
|
||||
# 从会话文件中读取下载任务
|
||||
#@input-file=/Users/Shared/aria2.session
|
||||
# 在Aria2退出时保存`错误/未完成`的下载任务到会话文件
|
||||
#@save-session=/Users/Shared/aria2.session
|
||||
# 定时保存会话, 0为退出时才保存, 需1.16.1以上版本, 默认:0
|
||||
save-session-interval=30
|
||||
|
||||
## RPC相关设置 ##
|
||||
|
||||
# 启用RPC, 默认:false
|
||||
################ RPC ################
|
||||
# Enable JSON-RPC/XML-RPC server.
|
||||
enable-rpc=true
|
||||
# 允许所有来源, 默认:false
|
||||
# Add Access-Control-Allow-Origin header field with value * to the RPC response.
|
||||
rpc-allow-origin-all=true
|
||||
# 允许非外部访问, 默认:false
|
||||
# Listen incoming JSON-RPC/XML-RPC requests on all network interfaces.
|
||||
rpc-listen-all=true
|
||||
# 事件轮询方式, 取值:[epoll, kqueue, port, poll, select], 不同系统默认值不同
|
||||
#event-poll=select
|
||||
# RPC监听端口, 端口被占用时可以修改, 默认:6800
|
||||
# 使用本客户端请勿修改此项
|
||||
# rpc-listen-port=6800
|
||||
# 设置的RPC授权令牌, v1.18.4新增功能, 取代 --rpc-user 和 --rpc-passwd 选项
|
||||
#rpc-secret=token
|
||||
|
||||
## BT/PT下载相关 ##
|
||||
|
||||
# 当下载的是一个种子(以.torrent结尾)时, 自动开始BT任务, 默认:true
|
||||
#follow-torrent=true
|
||||
# BT监听端口, 当端口被屏蔽时使用, 默认:6881-6999
|
||||
listen-port=51413
|
||||
# 单个种子最大连接数, 默认:55
|
||||
#bt-max-peers=55
|
||||
# 打开DHT功能, PT需要禁用, 默认:true
|
||||
# enable-dht=false
|
||||
# 打开IPv6 DHT功能, PT需要禁用
|
||||
#enable-dht6=false
|
||||
# DHT网络监听端口, 默认:6881-6999
|
||||
#dht-listen-port=6881-6999
|
||||
# 本地节点查找, PT需要禁用, 默认:false
|
||||
bt-enable-lpd=true
|
||||
# 种子交换, PT需要禁用, 默认:true
|
||||
enable-peer-exchange=true
|
||||
# 每个种子限速, 对少种的PT很有用, 默认:50K
|
||||
#bt-request-peer-speed-limit=50K
|
||||
# 客户端伪装, PT需要
|
||||
peer-id-prefix=-TR2770-
|
||||
################ File system ################
|
||||
# Save a control file(*.aria2) every SEC seconds.
|
||||
auto-save-interval=10
|
||||
# Enable disk cache.
|
||||
disk-cache=32M
|
||||
# Specify file allocation method.
|
||||
file-allocation=trunc
|
||||
# Save error/unfinished downloads to a file specified by --save-session option every SEC seconds.
|
||||
save-session-interval=10
|
||||
|
||||
|
||||
################ Task ################
|
||||
# Exclude seed only downloads when counting concurrent active downloads
|
||||
bt-detach-seed-only=true
|
||||
# Verify the peer using certificates specified in --ca-certificate option.
|
||||
check-certificate=false
|
||||
# If aria2 receives "file not found" status from the remote HTTP/FTP servers NUM times
|
||||
# without getting a single byte, then force the download to fail.
|
||||
max-file-not-found=5
|
||||
# Set number of tries.
|
||||
max-tries=5
|
||||
# aria2 does not split less than 2*SIZE byte range.
|
||||
min-split-size=1M
|
||||
# Set user agent for HTTP(S) downloads.
|
||||
user-agent=Transmission/2.94
|
||||
# 当种子的分享率达到这个数时, 自动停止做种, 0为一直做种, 默认:1.0
|
||||
seed-ratio=1.0
|
||||
# 强制保存会话, 即使任务已经完成, 默认:false
|
||||
# 较新的版本开启后会在任务完成后依然保留.aria2文件
|
||||
#force-save=false
|
||||
# BT校验相关, 默认:true
|
||||
#bt-hash-check-seed=true
|
||||
# 继续之前的BT任务时, 无需再次校验, 默认:false
|
||||
|
||||
|
||||
################ BT Task ################
|
||||
# Enable Local Peer Discovery.
|
||||
bt-enable-lpd=true
|
||||
# Requires BitTorrent message payload encryption with arc4.
|
||||
bt-force-encryption=true
|
||||
# If true is given, after hash check using --check-integrity option and file is complete, continue to seed file.
|
||||
bt-hash-check-seed=true
|
||||
# Specify the maximum number of peers per torrent.
|
||||
bt-max-peers=255
|
||||
# Removes the unselected files when download is completed in BitTorrent.
|
||||
bt-remove-unselected-file=true
|
||||
# Seed previously downloaded files without verifying piece hashes.
|
||||
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
|
||||
# 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
|
||||
# Enable IPv4 DHT functionality. It also enables UDP tracker support.
|
||||
enable-dht=true
|
||||
# Enable IPv6 DHT functionality.
|
||||
enable-dht6=true
|
||||
# Enable Peer Exchange extension.
|
||||
enable-peer-exchange=true
|
||||
# Specify the string used during the bitorrent extended handshake for the peer's client version.
|
||||
peer-agent=Transmission/2.94
|
||||
# Specify the prefix of peer ID.
|
||||
peer-id-prefix=-TR2940-
|
||||
|
||||
@@ -1,108 +1,71 @@
|
||||
## '#'开头为注释内容, 选项都有相应的注释说明, 根据需要修改 ##
|
||||
## 被注释的选项填写的是默认值, 建议在需要修改时再取消注释 ##
|
||||
## 添加了@和默认启用的选项都是系统需要调用的,请不要随意改动否则可能无法正常运行
|
||||
###############################
|
||||
# Motrix Windows Aria2 config file
|
||||
#
|
||||
# @see https://aria2.github.io/manual/en/html/aria2c.html
|
||||
#
|
||||
###############################
|
||||
|
||||
## 文件保存相关 ##
|
||||
|
||||
# 文件的保存路径(可使用绝对路径或相对路径), 默认: 当前启动位置
|
||||
# 此项 OS X 无法使用 $HOME 及 ~/ 设置路径 建议使用 /users/用户名/downloads
|
||||
#@dir=$HOME/downloads
|
||||
# 启用磁盘缓存, 0为禁用缓存, 需1.16以上版本, 默认:16M
|
||||
#@disk-cache=32M
|
||||
# 文件预分配方式, 能有效降低磁盘碎片, 默认:prealloc
|
||||
# 预分配所需时间: none < falloc ? trunc < prealloc
|
||||
# falloc和trunc则需要文件系统和内核支持
|
||||
# NTFS建议使用falloc, EXT3/4建议trunc, MAC 下需要注释此项
|
||||
file-allocation=falloc
|
||||
# 断点续传
|
||||
#@continue=true
|
||||
|
||||
## 下载连接相关 ##
|
||||
|
||||
# 最大同时下载任务数, 运行时可修改, 默认:5
|
||||
#@max-concurrent-downloads=10
|
||||
# 同一服务器连接数, 添加时可指定, 默认:1
|
||||
#@max-connection-per-server=15
|
||||
# 最小文件分片大小, 添加时可指定, 取值范围1M -1024M, 默认:20M
|
||||
# 假定size=10M, 文件为20MiB 则使用两个来源下载; 文件为15MiB 则使用一个来源下载
|
||||
#@min-split-size=10M
|
||||
# 单个任务最大线程数, 添加时可指定, 默认:5
|
||||
#@split=15
|
||||
# 整体下载速度限制, 运行时可修改, 默认:0
|
||||
#@max-overall-download-limit=0
|
||||
# 单个任务下载速度限制, 默认:0
|
||||
#@max-download-limit=0
|
||||
# 整体上传速度限制, 运行时可修改, 默认:0
|
||||
#@max-overall-upload-limit=0
|
||||
# 单个任务上传速度限制, 默认:0
|
||||
#@max-upload-limit=0
|
||||
# 禁用IPv6, 默认:false
|
||||
disable-ipv6=false
|
||||
#运行覆盖已存在文件
|
||||
#@allow-overwrite=true
|
||||
#自动重命名
|
||||
#@auto-file-renaming=true
|
||||
|
||||
## 进度保存相关 ##
|
||||
|
||||
# 从会话文件中读取下载任务
|
||||
#@input-file=/Users/Shared/aria2.session
|
||||
# 在Aria2退出时保存`错误/未完成`的下载任务到会话文件
|
||||
#@save-session=/Users/Shared/aria2.session
|
||||
# 定时保存会话, 0为退出时才保存, 需1.16.1以上版本, 默认:0
|
||||
save-session-interval=30
|
||||
|
||||
## RPC相关设置 ##
|
||||
|
||||
# 启用RPC, 默认:false
|
||||
################ RPC ################
|
||||
# Enable JSON-RPC/XML-RPC server.
|
||||
enable-rpc=true
|
||||
# 允许所有来源, 默认:false
|
||||
# Add Access-Control-Allow-Origin header field with value * to the RPC response.
|
||||
rpc-allow-origin-all=true
|
||||
# 允许非外部访问, 默认:false
|
||||
# Listen incoming JSON-RPC/XML-RPC requests on all network interfaces.
|
||||
rpc-listen-all=true
|
||||
# 事件轮询方式, 取值:[epoll, kqueue, port, poll, select], 不同系统默认值不同
|
||||
#event-poll=select
|
||||
# RPC监听端口, 端口被占用时可以修改, 默认:6800
|
||||
# 使用本客户端请勿修改此项
|
||||
# rpc-listen-port=6800
|
||||
# 设置的RPC授权令牌, v1.18.4新增功能, 取代 --rpc-user 和 --rpc-passwd 选项
|
||||
#rpc-secret=token
|
||||
|
||||
## BT/PT下载相关 ##
|
||||
|
||||
# 当下载的是一个种子(以.torrent结尾)时, 自动开始BT任务, 默认:true
|
||||
#follow-torrent=true
|
||||
# BT监听端口, 当端口被屏蔽时使用, 默认:6881-6999
|
||||
listen-port=51413
|
||||
# 单个种子最大连接数, 默认:55
|
||||
#bt-max-peers=55
|
||||
# 打开DHT功能, PT需要禁用, 默认:true
|
||||
# enable-dht=false
|
||||
# 打开IPv6 DHT功能, PT需要禁用
|
||||
#enable-dht6=false
|
||||
# DHT网络监听端口, 默认:6881-6999
|
||||
#dht-listen-port=6881-6999
|
||||
# 本地节点查找, PT需要禁用, 默认:false
|
||||
bt-enable-lpd=true
|
||||
# 种子交换, PT需要禁用, 默认:true
|
||||
enable-peer-exchange=true
|
||||
# 每个种子限速, 对少种的PT很有用, 默认:50K
|
||||
#bt-request-peer-speed-limit=50K
|
||||
# 客户端伪装, PT需要
|
||||
peer-id-prefix=-TR2770-
|
||||
################ File system ################
|
||||
# Save a control file(*.aria2) every SEC seconds.
|
||||
auto-save-interval=10
|
||||
# Enable disk cache.
|
||||
disk-cache=32M
|
||||
# Specify file allocation method.
|
||||
file-allocation=falloc
|
||||
# Save error/unfinished downloads to a file specified by --save-session option every SEC seconds.
|
||||
save-session-interval=10
|
||||
|
||||
|
||||
################ Task ################
|
||||
# Exclude seed only downloads when counting concurrent active downloads
|
||||
bt-detach-seed-only=true
|
||||
# Verify the peer using certificates specified in --ca-certificate option.
|
||||
check-certificate=false
|
||||
# If aria2 receives "file not found" status from the remote HTTP/FTP servers NUM times
|
||||
# without getting a single byte, then force the download to fail.
|
||||
max-file-not-found=5
|
||||
# Set number of tries.
|
||||
max-tries=5
|
||||
# aria2 does not split less than 2*SIZE byte range.
|
||||
min-split-size=1M
|
||||
# Set user agent for HTTP(S) downloads.
|
||||
user-agent=Transmission/2.94
|
||||
# 当种子的分享率达到这个数时, 自动停止做种, 0为一直做种, 默认:1.0
|
||||
seed-ratio=1.0
|
||||
# 强制保存会话, 即使任务已经完成, 默认:false
|
||||
# 较新的版本开启后会在任务完成后依然保留.aria2文件
|
||||
#force-save=false
|
||||
# BT校验相关, 默认:true
|
||||
#bt-hash-check-seed=true
|
||||
# 继续之前的BT任务时, 无需再次校验, 默认:false
|
||||
|
||||
|
||||
################ BT Task ################
|
||||
# Enable Local Peer Discovery.
|
||||
bt-enable-lpd=true
|
||||
# Requires BitTorrent message payload encryption with arc4.
|
||||
bt-force-encryption=true
|
||||
# If true is given, after hash check using --check-integrity option and file is complete, continue to seed file.
|
||||
bt-hash-check-seed=true
|
||||
# Specify the maximum number of peers per torrent.
|
||||
bt-max-peers=255
|
||||
# Removes the unselected files when download is completed in BitTorrent.
|
||||
bt-remove-unselected-file=true
|
||||
# Seed previously downloaded files without verifying piece hashes.
|
||||
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
|
||||
# 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
|
||||
# Enable IPv4 DHT functionality. It also enables UDP tracker support.
|
||||
enable-dht=true
|
||||
# Enable IPv6 DHT functionality.
|
||||
enable-dht6=true
|
||||
# Enable Peer Exchange extension.
|
||||
enable-peer-exchange=true
|
||||
# Specify the string used during the bitorrent extended handshake for the peer's client version.
|
||||
peer-agent=Transmission/2.94
|
||||
# Specify the prefix of peer ID.
|
||||
peer-id-prefix=-TR2940-
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "Motrix",
|
||||
"version": "1.1.2",
|
||||
"version": "1.5.7",
|
||||
"description": "A full-featured download manager",
|
||||
"homepage": "https://motrix.app",
|
||||
"author": {
|
||||
@@ -17,6 +17,7 @@
|
||||
"scripts": {
|
||||
"release": "npm run build --publish onTagOrDraft",
|
||||
"build": "node .electron-vue/build.js && electron-builder",
|
||||
"build:github": "node .electron-vue/build.js",
|
||||
"build:dir": "node .electron-vue/build.js && electron-builder --dir",
|
||||
"build:clean": "cross-env BUILD_TARGET=clean node .electron-vue/build.js",
|
||||
"build:web": "cross-env BUILD_TARGET=web node .electron-vue/build.js",
|
||||
@@ -27,11 +28,21 @@
|
||||
"pack": "npm run pack:main && npm run pack:renderer",
|
||||
"pack:main": "cross-env NODE_ENV=production webpack --mode production --progress --colors --config .electron-vue/webpack.main.config.js",
|
||||
"pack:renderer": "cross-env NODE_ENV=production webpack --mode production --progress --colors --config .electron-vue/webpack.renderer.config.js",
|
||||
"postinstall": "npm run lint:fix"
|
||||
"postinstall": "electron-builder install-app-deps && npm run lint:fix"
|
||||
},
|
||||
"build": {
|
||||
"productName": "Motrix",
|
||||
"appId": "net.agalwood.Motrix",
|
||||
"afterPack": "./build/afterPackHook.js",
|
||||
"afterSign": "electron-builder-notarize",
|
||||
"fileAssociations": [
|
||||
{
|
||||
"ext": "torrent",
|
||||
"mimeType": "application/x-bittorrent",
|
||||
"name": "Torrent",
|
||||
"role": "Viewer"
|
||||
}
|
||||
],
|
||||
"asar": true,
|
||||
"directories": {
|
||||
"output": "release"
|
||||
@@ -39,6 +50,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,
|
||||
@@ -65,6 +97,7 @@
|
||||
],
|
||||
"type": "distribution",
|
||||
"darkModeSupport": true,
|
||||
"hardenedRuntime": true,
|
||||
"extraResources": {
|
||||
"from": "./extra/darwin/",
|
||||
"to": "./",
|
||||
@@ -72,25 +105,31 @@
|
||||
"**/*"
|
||||
]
|
||||
},
|
||||
"binaries": [
|
||||
"./release/mac/Motrix.app/Contents/Resources/engine/aria2c"
|
||||
],
|
||||
"category": "public.app-category.utilities",
|
||||
"protocols": [
|
||||
{
|
||||
"name": "Motrix Protocol",
|
||||
"schemes": [
|
||||
"mo",
|
||||
"motrix"
|
||||
]
|
||||
}
|
||||
]
|
||||
"category": "public.app-category.utilities"
|
||||
},
|
||||
"win": {
|
||||
"target": [
|
||||
"nsis",
|
||||
"zip",
|
||||
"portable"
|
||||
{
|
||||
"target": "nsis",
|
||||
"arch": [
|
||||
"x64",
|
||||
"ia32"
|
||||
]
|
||||
},
|
||||
{
|
||||
"target": "zip",
|
||||
"arch": [
|
||||
"x64",
|
||||
"ia32"
|
||||
]
|
||||
},
|
||||
{
|
||||
"target": "portable",
|
||||
"arch": [
|
||||
"x64",
|
||||
"ia32"
|
||||
]
|
||||
}
|
||||
],
|
||||
"extraResources": {
|
||||
"from": "./extra/win32/",
|
||||
@@ -105,11 +144,12 @@
|
||||
"allowToChangeInstallationDirectory": true
|
||||
},
|
||||
"linux": {
|
||||
"category": "Network",
|
||||
"target": [
|
||||
"AppImage",
|
||||
"deb",
|
||||
"snap",
|
||||
"pacman",
|
||||
"AppImage"
|
||||
"rpm",
|
||||
"snap"
|
||||
],
|
||||
"extraResources": {
|
||||
"from": "./extra/linux/",
|
||||
@@ -119,10 +159,21 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"snap": {
|
||||
"publish": [
|
||||
{
|
||||
"provider": "github"
|
||||
},
|
||||
{
|
||||
"provider": "snapStore",
|
||||
"channel": "edge"
|
||||
}
|
||||
]
|
||||
},
|
||||
"publish": [
|
||||
{
|
||||
"provider": "generic",
|
||||
"url": "https://motrix.app/release/"
|
||||
"url": "https://dl.motrix.app/release/"
|
||||
},
|
||||
{
|
||||
"provider": "github"
|
||||
@@ -130,83 +181,84 @@
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@panter/vue-i18next": "^0.15.0",
|
||||
"aria2": "^4.0.3",
|
||||
"axios": "^0.18.0",
|
||||
"clipboard-polyfill": "^2.7.0",
|
||||
"electron-debug": "^2.1.0",
|
||||
"@babel/runtime": "^7.9.2",
|
||||
"@panter/vue-i18next": "^0.15.2",
|
||||
"aria2": "^4.1.0",
|
||||
"axios": "^0.19.2",
|
||||
"blob-util": "^2.0.2",
|
||||
"clipboard-polyfill": "^2.8.6",
|
||||
"electron-debug": "^3.0.1",
|
||||
"electron-is": "^3.0.0",
|
||||
"electron-log": "^2.2.17",
|
||||
"electron-updater": "^4.0.7",
|
||||
"element-ui": "^2.5.4",
|
||||
"forever-monitor": "^1.7.1",
|
||||
"i18next": "^15.0.0",
|
||||
"lodash": "^4.17.11",
|
||||
"electron-log": "^4.1.1",
|
||||
"electron-store": "^5.1.1",
|
||||
"electron-updater": "^4.3.1",
|
||||
"element-ui": "^2.13.1",
|
||||
"forever-monitor": "1.7.2",
|
||||
"i18next": "^19.4.4",
|
||||
"lodash": "^4.17.15",
|
||||
"nat-api": "^0.1.3",
|
||||
"node-fetch": "^2.6.0",
|
||||
"normalize.css": "^8.0.1",
|
||||
"parse-torrent": "^6.1.2",
|
||||
"parse-torrent": "^7.1.2",
|
||||
"randomatic": "^3.1.1",
|
||||
"svg-innerhtml": "^1.1.0",
|
||||
"vue": "^2.6.4",
|
||||
"vue": "^2.6.11",
|
||||
"vue-electron": "^1.0.6",
|
||||
"vue-router": "^3.0.2",
|
||||
"vuex": "^3.1.0",
|
||||
"vue-router": "^3.1.6",
|
||||
"vuex": "^3.3.0",
|
||||
"vuex-router-sync": "^5.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vue/cli-plugin-babel": "^3.4.0",
|
||||
"@vue/cli-plugin-eslint": "^3.4.0",
|
||||
"@vue/cli-service": "^3.4.0",
|
||||
"@vue/eslint-config-standard": "^4.0.0",
|
||||
"ajv": "^6.9.1",
|
||||
"babel-core": "^6.26.3",
|
||||
"babel-eslint": "^10.0.1",
|
||||
"babel-loader": "^7.1.4",
|
||||
"@babel/core": "^7.9.0",
|
||||
"@babel/plugin-proposal-class-properties": "^7.8.3",
|
||||
"@babel/plugin-transform-runtime": "^7.9.0",
|
||||
"@babel/preset-env": "^7.9.5",
|
||||
"@babel/register": "^7.9.0",
|
||||
"@vue/eslint-config-standard": "^5.1.2",
|
||||
"ajv": "^6.12.2",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"babel-loader": "^8.1.0",
|
||||
"babel-plugin-component": "^1.1.1",
|
||||
"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",
|
||||
"chalk": "^2.4.1",
|
||||
"copy-webpack-plugin": "^4.6.0",
|
||||
"cross-env": "^5.1.6",
|
||||
"css-loader": "^1.0.1",
|
||||
"del": "^3.0.0",
|
||||
"cfonts": "^2.8.1",
|
||||
"chalk": "^4.0.0",
|
||||
"copy-webpack-plugin": "^5.1.1",
|
||||
"cross-env": "^7.0.2",
|
||||
"css-loader": "^3.5.3",
|
||||
"del": "^5.1.0",
|
||||
"devtron": "^1.4.0",
|
||||
"electron": "^4.0.4",
|
||||
"electron-builder": "^20.38.5",
|
||||
"electron-devtools-installer": "^2.2.4",
|
||||
"electron-notarize": "^0.0.5",
|
||||
"electron-osx-sign": "^0.4.11",
|
||||
"electron-store": "^2.0.0",
|
||||
"eslint": "^5.13.0",
|
||||
"eslint-config-standard": "^12.0.0",
|
||||
"electron": "^8.2.3",
|
||||
"electron-builder": "^22.6.0",
|
||||
"electron-builder-notarize": "^1.1.2",
|
||||
"electron-devtools-installer": "^3.0.0",
|
||||
"electron-notarize": "^0.3.0",
|
||||
"electron-osx-sign": "^0.4.15",
|
||||
"eslint": "^6.8.0",
|
||||
"eslint-friendly-formatter": "^4.0.1",
|
||||
"eslint-loader": "^2.1.2",
|
||||
"eslint-plugin-import": "^2.16.0",
|
||||
"eslint-plugin-node": "^7.0.1",
|
||||
"eslint-plugin-promise": "^4.0.1",
|
||||
"eslint-plugin-standard": "^4.0.0",
|
||||
"eslint-plugin-vue": "^5.1.0",
|
||||
"file-loader": "^2.0.0",
|
||||
"html-webpack-plugin": "^3.2.0",
|
||||
"mini-css-extract-plugin": "0.5.0",
|
||||
"eslint-loader": "^4.0.2",
|
||||
"eslint-plugin-import": "^2.20.2",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-promise": "^4.2.1",
|
||||
"eslint-plugin-standard": "^4.0.1",
|
||||
"eslint-plugin-vue": "^6.2.2",
|
||||
"file-loader": "^6.0.0",
|
||||
"html-webpack-plugin": "^4.2.0",
|
||||
"mini-css-extract-plugin": "0.9.0",
|
||||
"multispinner": "^0.2.1",
|
||||
"node-loader": "^0.6.0",
|
||||
"node-sass": "^4.10.0",
|
||||
"optimize-css-assets-webpack-plugin": "^5.0.1",
|
||||
"sass-loader": "^7.1.0",
|
||||
"style-loader": "^0.23.1",
|
||||
"url-loader": "^1.1.2",
|
||||
"eslint-plugin-html": "^4.0.6",
|
||||
"optimize-css-assets-webpack-plugin": "^5.0.3",
|
||||
"sass": "^1.26.5",
|
||||
"sass-loader": "^8.0.2",
|
||||
"style-loader": "^1.2.0",
|
||||
"terser-webpack-plugin": "^2.3.6",
|
||||
"url-loader": "^4.1.0",
|
||||
"vue-html-loader": "^1.2.4",
|
||||
"vue-loader": "^15.6.2",
|
||||
"vue-loader": "^15.9.1",
|
||||
"vue-style-loader": "^4.1.2",
|
||||
"vue-template-compiler": "^2.6.4",
|
||||
"webpack": "^4.29.3",
|
||||
"webpack-cli": "^3.2.3",
|
||||
"webpack-dev-server": "^3.1.14",
|
||||
"webpack-hot-middleware": "^2.24.3",
|
||||
"webpack-merge": "^4.1.4"
|
||||
"vue-template-compiler": "^2.6.11",
|
||||
"webpack": "^4.43.0",
|
||||
"webpack-cli": "^3.3.11",
|
||||
"webpack-dev-server": "^3.10.3",
|
||||
"webpack-hot-middleware": "^2.25.0",
|
||||
"webpack-merge": "^4.2.2"
|
||||
}
|
||||
}
|
||||
|
||||
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 46 KiB |
|
After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 38 KiB |
@@ -23,9 +23,11 @@
|
||||
</section>
|
||||
</div>
|
||||
<!-- Set `__static` path to static files in production -->
|
||||
<script>
|
||||
if (process.env.NODE_ENV !== 'development') window.__static = require('path').join(__dirname, '/static').replace(/\\/g, '\\\\')
|
||||
</script>
|
||||
<% if (!process.browser) { %>
|
||||
<script>
|
||||
if (process.env.NODE_ENV !== 'development') window.__static = require('path').join(__dirname, '/static').replace(/\\/g, '\\\\')
|
||||
</script>
|
||||
<% } %>
|
||||
|
||||
<!-- webpack builds are automatically injected -->
|
||||
</body>
|
||||
|
||||
@@ -1,33 +1,50 @@
|
||||
import { EventEmitter } from 'events'
|
||||
import { app, shell, dialog, ipcMain } from 'electron'
|
||||
import is from 'electron-is'
|
||||
import { readFile } from 'fs'
|
||||
import { extname, basename } from 'path'
|
||||
import { isEmpty } from 'lodash'
|
||||
|
||||
import logger from './core/Logger'
|
||||
import ExceptionHandler from './core/ExceptionHandler'
|
||||
import ConfigManager from './core/ConfigManager'
|
||||
import { setupLocaleManager } from '@/ui/Locale'
|
||||
import Engine from './core/Engine'
|
||||
import EngineClient from './core/EngineClient'
|
||||
import UPnPManager from './core/UPnPManager'
|
||||
import AutoLaunchManager from './core/AutoLaunchManager'
|
||||
import UpdateManager from './core/UpdateManager'
|
||||
import EnergyManager from './core/EnergyManager'
|
||||
import ProtocolManager from './core/ProtocolManager'
|
||||
import WindowManager from './ui/WindowManager'
|
||||
import MenuManager from './ui/MenuManager'
|
||||
import TouchBarManager from './ui/TouchBarManager'
|
||||
import TrayManager from './ui/TrayManager'
|
||||
import DockManager from './ui/DockManager'
|
||||
import ThemeManager from './ui/ThemeManager'
|
||||
import { AUTO_SYNC_TRACKER_INTERVAL, AUTO_CHECK_UPDATE_INTERVAL } from '@shared/constants'
|
||||
import { checkIsNeedRun } from '@shared/utils'
|
||||
import { convertTrackerDataToComma, fetchBtTrackerFromSource } from '@shared/utils/tracker'
|
||||
|
||||
export default class Application extends EventEmitter {
|
||||
constructor () {
|
||||
super()
|
||||
|
||||
this.exceptionHandler = new ExceptionHandler()
|
||||
|
||||
this.isReady = false
|
||||
this.init()
|
||||
}
|
||||
|
||||
init () {
|
||||
this.configManager = new ConfigManager()
|
||||
this.locale = this.configManager.getLocale()
|
||||
|
||||
this.windowManager = new WindowManager({
|
||||
userConfig: this.configManager.getUserConfig()
|
||||
})
|
||||
this.locale = this.configManager.getLocale()
|
||||
this.localeManager = setupLocaleManager(this.locale)
|
||||
this.i18n = this.localeManager.getI18n()
|
||||
|
||||
this.menuManager = new MenuManager()
|
||||
this.menuManager.setup(this.locale)
|
||||
|
||||
this.initTouchBarManager()
|
||||
|
||||
this.initWindowManager()
|
||||
|
||||
this.engine = new Engine({
|
||||
systemConfig: this.configManager.getSystemConfig(),
|
||||
@@ -35,55 +52,276 @@ export default class Application extends EventEmitter {
|
||||
})
|
||||
this.startEngine()
|
||||
|
||||
this.menuManager = new MenuManager()
|
||||
this.menuManager.setup(this.locale)
|
||||
this.initEngineClient()
|
||||
|
||||
this.touchBarManager = new TouchBarManager()
|
||||
this.initUPnPManager()
|
||||
|
||||
this.autoSyncTracker()
|
||||
|
||||
this.trayManager = new TrayManager({
|
||||
theme: this.configManager.getUserConfig('tray-theme')
|
||||
})
|
||||
|
||||
this.dockManager = new DockManager({
|
||||
runMode: this.configManager.getUserConfig('run-mode')
|
||||
})
|
||||
|
||||
this.autoLaunchManager = new AutoLaunchManager()
|
||||
|
||||
this.energyManager = new EnergyManager()
|
||||
|
||||
this.initThemeManager()
|
||||
|
||||
this.initUpdaterManager()
|
||||
|
||||
this.initProtocolManager()
|
||||
|
||||
this.handleCommands()
|
||||
|
||||
this.handleEvents()
|
||||
|
||||
this.handleIpcMessages()
|
||||
}
|
||||
|
||||
startEngine () {
|
||||
const self = this
|
||||
|
||||
try {
|
||||
this.engine.start()
|
||||
} catch (err) {
|
||||
const { message } = err
|
||||
dialog.showMessageBox({
|
||||
type: 'error',
|
||||
title: '系统错误',
|
||||
message: `应用启动失败:${err.message}`,
|
||||
buttons: ['知道了'],
|
||||
cancelId: 1
|
||||
}, () => {
|
||||
title: this.i18n.t('app.system-error-title'),
|
||||
message: this.i18n.t('app.system-error-message', { message })
|
||||
}).then(_ => {
|
||||
setTimeout(() => {
|
||||
app.quit()
|
||||
self.quit()
|
||||
}, 100)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
start (page) {
|
||||
this.showPage(page)
|
||||
async stopEngine () {
|
||||
try {
|
||||
await this.engineClient.shutdown({ force: true })
|
||||
} catch (err) {
|
||||
logger.warn('[Motrix] shutdown engine fail: ', err.message)
|
||||
} finally {
|
||||
this.engine.stop()
|
||||
}
|
||||
}
|
||||
|
||||
showPage (page) {
|
||||
const win = this.windowManager.openWindow(page)
|
||||
this.touchBarManager.setup(page, win)
|
||||
initEngineClient () {
|
||||
const port = this.configManager.getSystemConfig('rpc-listen-port')
|
||||
const secret = this.configManager.getSystemConfig('rpc-secret')
|
||||
this.engineClient = new EngineClient({
|
||||
port,
|
||||
secret
|
||||
})
|
||||
}
|
||||
|
||||
initUPnPManager () {
|
||||
this.upnp = new UPnPManager()
|
||||
|
||||
this.watchEnableUPnPChange()
|
||||
|
||||
this.watchPortsChange()
|
||||
|
||||
const enable = this.configManager.getUserConfig('enable-upnp')
|
||||
if (!enable) {
|
||||
return
|
||||
}
|
||||
|
||||
this.startUPnPMapping()
|
||||
}
|
||||
|
||||
async startUPnPMapping () {
|
||||
const btPort = this.configManager.getSystemConfig('listen-port')
|
||||
const dhtPort = this.configManager.getSystemConfig('dht-listen-port')
|
||||
|
||||
const promises = [
|
||||
this.upnp.map(btPort),
|
||||
this.upnp.map(dhtPort)
|
||||
]
|
||||
try {
|
||||
await Promise.all(promises)
|
||||
} catch (e) {
|
||||
logger.warn('[Motrix] start UPnP mapping fail', e)
|
||||
}
|
||||
}
|
||||
|
||||
async stopUPnPMapping () {
|
||||
const btPort = this.configManager.getSystemConfig('listen-port')
|
||||
const dhtPort = this.configManager.getSystemConfig('dht-listen-port')
|
||||
|
||||
const promises = [
|
||||
this.upnp.unmap(btPort),
|
||||
this.upnp.unmap(dhtPort)
|
||||
]
|
||||
try {
|
||||
await Promise.all(promises)
|
||||
} catch (e) {
|
||||
logger.warn('[Motrix] stop UPnP mapping fail', e)
|
||||
}
|
||||
}
|
||||
|
||||
watchPortsChange () {
|
||||
const watchKeys = ['listen-port', 'dht-listen-port']
|
||||
|
||||
watchKeys.map((key) => {
|
||||
this.configManager.systemConfig.onDidChange(key, async (newValue, oldValue) => {
|
||||
logger.info('[Motrix] detected port change event:', key, newValue, oldValue)
|
||||
const enable = this.configManager.getUserConfig('enable-upnp')
|
||||
if (!enable) {
|
||||
return
|
||||
}
|
||||
|
||||
const promises = [
|
||||
this.upnp.unmap(oldValue),
|
||||
this.upnp.map(newValue)
|
||||
]
|
||||
try {
|
||||
await Promise.all(promises)
|
||||
} catch (e) {
|
||||
logger.info('[Motrix] change UPnP port mapping failed:', e)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
watchEnableUPnPChange () {
|
||||
this.configManager.userConfig.onDidChange('enable-upnp', async (newValue, oldValue) => {
|
||||
logger.info('[Motrix] detected enable-upnp value change event:', newValue, oldValue)
|
||||
if (newValue) {
|
||||
this.startUPnPMapping()
|
||||
} else {
|
||||
this.stopUPnPMapping()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async shutdownUPnPManager () {
|
||||
const enable = this.configManager.getUserConfig('enable-upnp')
|
||||
if (enable) {
|
||||
await this.stopUPnPMapping()
|
||||
}
|
||||
|
||||
this.upnp.destroy()
|
||||
}
|
||||
|
||||
autoSyncTracker () {
|
||||
const enable = this.configManager.getUserConfig('auto-sync-tracker')
|
||||
const lastTime = this.configManager.getUserConfig('last-sync-tracker-time')
|
||||
const result = checkIsNeedRun(enable, lastTime, AUTO_SYNC_TRACKER_INTERVAL)
|
||||
if (!result) {
|
||||
return
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
const source = this.configManager.getUserConfig('tracker-source')
|
||||
fetchBtTrackerFromSource(source).then((data) => {
|
||||
const tracker = convertTrackerDataToComma(data)
|
||||
this.savePreference({
|
||||
system: {
|
||||
'bt-tracker': tracker
|
||||
},
|
||||
user: {
|
||||
'last-sync-tracker-time': Date.now()
|
||||
}
|
||||
})
|
||||
})
|
||||
}, 3000)
|
||||
}
|
||||
|
||||
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)
|
||||
})
|
||||
}
|
||||
|
||||
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 = {}) {
|
||||
const win = this.showPage(page, options)
|
||||
|
||||
win.once('ready-to-show', () => {
|
||||
this.isReady = true
|
||||
this.emit('ready')
|
||||
})
|
||||
if (is.macOS()) {
|
||||
this.touchBarManager.setup(page, win)
|
||||
}
|
||||
}
|
||||
|
||||
showPage (page, options = {}) {
|
||||
const { openedAtLogin } = options
|
||||
const autoHideWindow = this.configManager.getUserConfig('auto-hide-window')
|
||||
return this.windowManager.openWindow(page, {
|
||||
hidden: openedAtLogin || autoHideWindow
|
||||
})
|
||||
}
|
||||
|
||||
show (page = 'index') {
|
||||
this.windowManager.showWindow(page)
|
||||
}
|
||||
|
||||
hide (page) {
|
||||
if (page) {
|
||||
this.windowManager.autoHideWindow(page)
|
||||
} else {
|
||||
this.windowManager.hideAllWindow()
|
||||
}
|
||||
}
|
||||
|
||||
toggle (page = 'index') {
|
||||
this.windowManager.toggleWindow(page)
|
||||
}
|
||||
|
||||
closePage (page) {
|
||||
this.windowManager.destroyWindow(page)
|
||||
}
|
||||
|
||||
stop () {
|
||||
this.engine.stop()
|
||||
this.energyManager.stopPowerSaveBlocker()
|
||||
async stop () {
|
||||
try {
|
||||
await this.shutdownUPnPManager()
|
||||
|
||||
this.energyManager.stopPowerSaveBlocker()
|
||||
|
||||
this.trayManager.destroy()
|
||||
|
||||
await this.stopEngine()
|
||||
} catch (err) {
|
||||
logger.warn('[Motrix] stop error: ', err.message)
|
||||
}
|
||||
}
|
||||
|
||||
async quit () {
|
||||
await this.stop()
|
||||
app.exit()
|
||||
}
|
||||
|
||||
sendCommand (command, ...args) {
|
||||
@@ -109,32 +347,82 @@ 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) {
|
||||
if (is.dev() || is.mas()) {
|
||||
return
|
||||
}
|
||||
|
||||
this.show()
|
||||
|
||||
this.protocolManager.handle(url)
|
||||
this.showPage('index')
|
||||
}
|
||||
|
||||
handleFile (filePath) {
|
||||
if (!filePath) {
|
||||
return
|
||||
}
|
||||
|
||||
if (extname(filePath).toLowerCase() !== '.torrent') {
|
||||
return
|
||||
}
|
||||
|
||||
this.show()
|
||||
|
||||
const fileName = basename(filePath)
|
||||
readFile(filePath, (err, data) => {
|
||||
if (err) {
|
||||
logger.warn(`[Motrix] read file error: ${filePath}`, err.message)
|
||||
return
|
||||
}
|
||||
const file = Buffer.from(data).toString('base64')
|
||||
const args = [fileName, file]
|
||||
this.sendCommandToAll('application:new-bt-task-with-file', ...args)
|
||||
})
|
||||
}
|
||||
|
||||
initUpdaterManager () {
|
||||
if (is.mas()) {
|
||||
return
|
||||
}
|
||||
this.updateManager = new UpdateManager()
|
||||
|
||||
const enable = this.configManager.getUserConfig('auto-check-update')
|
||||
const lastTime = this.configManager.getUserConfig('last-check-update-time')
|
||||
this.updateManager = new UpdateManager({
|
||||
autoCheck: checkIsNeedRun(enable, lastTime, AUTO_CHECK_UPDATE_INTERVAL)
|
||||
})
|
||||
this.handleUpdaterEvents()
|
||||
}
|
||||
|
||||
handleUpdaterEvents () {
|
||||
this.updateManager.on('checking', (event) => {
|
||||
this.menuManager.updateMenuItemEnabledState('app.check-for-updates', false)
|
||||
this.trayManager.updateMenuItemEnabledState('app.check-for-updates', false)
|
||||
this.configManager.setUserConfig('last-check-update-time', Date.now())
|
||||
})
|
||||
|
||||
this.updateManager.on('download-progress', (event) => {
|
||||
@@ -144,10 +432,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)
|
||||
})
|
||||
@@ -158,34 +448,60 @@ 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)
|
||||
})
|
||||
}
|
||||
|
||||
relaunch (page = 'index') {
|
||||
relaunch () {
|
||||
this.stop()
|
||||
app.relaunch()
|
||||
app.exit()
|
||||
// this.closePage(page)
|
||||
// if (page === 'index') {
|
||||
// this.engine.restart()
|
||||
// }
|
||||
// setTimeout(() => {
|
||||
// this.showPage(page)
|
||||
// }, 500)
|
||||
}
|
||||
|
||||
savePreference (config = {}) {
|
||||
logger.info('[Motrix] save preference:', config)
|
||||
const { system, user } = config
|
||||
if (!isEmpty(system)) {
|
||||
console.info('[Motrix] main save system config: ', system)
|
||||
this.configManager.setSystemConfig(system)
|
||||
this.engineClient.changeGlobalOption(system)
|
||||
}
|
||||
|
||||
if (!isEmpty(user)) {
|
||||
console.info('[Motrix] main save user config: ', user)
|
||||
this.configManager.setUserConfig(user)
|
||||
}
|
||||
}
|
||||
|
||||
handleCommands () {
|
||||
this.on('application:save-preference', this.savePreference)
|
||||
|
||||
this.on('application:relaunch', () => {
|
||||
this.relaunch()
|
||||
})
|
||||
|
||||
this.on('application:exit', () => {
|
||||
this.engine.stop()
|
||||
app.exit()
|
||||
this.on('application:quit', () => {
|
||||
this.quit()
|
||||
})
|
||||
|
||||
this.on('application:show', (page = 'index') => {
|
||||
this.showPage(page)
|
||||
this.on('application:open-at-login', (openAtLogin) => {
|
||||
if (is.linux()) {
|
||||
return
|
||||
}
|
||||
|
||||
if (openAtLogin) {
|
||||
this.autoLaunchManager.enable()
|
||||
} else {
|
||||
this.autoLaunchManager.disable()
|
||||
}
|
||||
})
|
||||
|
||||
this.on('application:show', (page) => {
|
||||
this.show(page)
|
||||
})
|
||||
|
||||
this.on('application:hide', (page) => {
|
||||
this.hide(page)
|
||||
})
|
||||
|
||||
this.on('application:reset', () => {
|
||||
@@ -197,8 +513,71 @@ 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) => {
|
||||
this.menuManager.setup(locale)
|
||||
this.localeManager.changeLanguageByLocale(locale)
|
||||
.then(() => {
|
||||
this.menuManager.setup(locale)
|
||||
this.trayManager.setup(locale)
|
||||
})
|
||||
})
|
||||
|
||||
this.on('application:toggle-dock', (visible) => {
|
||||
if (visible) {
|
||||
this.dockManager.show()
|
||||
} else {
|
||||
this.dockManager.hide()
|
||||
// Hiding the dock icon will trigger the entire app to hide.
|
||||
this.show()
|
||||
}
|
||||
})
|
||||
|
||||
this.on('application:auto-hide-window', (hide) => {
|
||||
if (hide) {
|
||||
this.windowManager.handleWindowBlur()
|
||||
} else {
|
||||
this.windowManager.unbindWindowBlur()
|
||||
}
|
||||
})
|
||||
|
||||
this.on('application:change-menu-states', (visibleStates, enabledStates, checkedStates) => {
|
||||
this.menuManager.updateMenuStates(visibleStates, enabledStates, checkedStates)
|
||||
this.trayManager.updateMenuStates(visibleStates, enabledStates, checkedStates)
|
||||
})
|
||||
|
||||
this.on('application:open-file', (event) => {
|
||||
dialog.showOpenDialog({
|
||||
properties: ['openFile'],
|
||||
filters: [
|
||||
{
|
||||
name: 'Torrent',
|
||||
extensions: ['torrent']
|
||||
}
|
||||
]
|
||||
}).then(({ canceled, filePaths }) => {
|
||||
if (canceled || filePaths.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
const [filePath] = filePaths
|
||||
this.handleFile(filePath)
|
||||
})
|
||||
})
|
||||
|
||||
this.on('application:clear-recent-tasks', () => {
|
||||
app.clearRecentDocuments()
|
||||
})
|
||||
|
||||
this.on('application:setup-protocols-client', (protocols) => {
|
||||
if (is.dev() || is.mas()) {
|
||||
return
|
||||
}
|
||||
logger.info('[Motrix] setup protocols client:', protocols)
|
||||
this.protocolManager.setup(protocols)
|
||||
})
|
||||
|
||||
this.on('help:official-website', () => {
|
||||
@@ -222,14 +601,38 @@ export default class Application extends EventEmitter {
|
||||
})
|
||||
}
|
||||
|
||||
handleEvents () {
|
||||
// this.configManager.systemConfig.onDidAnyChange(() => {
|
||||
// this.engineClient.changeGlobalOption(this.configManager.getSystemConfig())
|
||||
// })
|
||||
|
||||
this.on('download-status-change', (downloading) => {
|
||||
this.trayManager.updateTrayByStatus(downloading)
|
||||
if (downloading) {
|
||||
this.energyManager.startPowerSaveBlocker()
|
||||
} else {
|
||||
this.energyManager.stopPowerSaveBlocker()
|
||||
}
|
||||
})
|
||||
|
||||
this.on('download-speed-change', (speed) => {
|
||||
this.dockManager.setBadge(speed)
|
||||
})
|
||||
|
||||
this.on('task-download-complete', (task, path) => {
|
||||
this.dockManager.openDock(path)
|
||||
})
|
||||
}
|
||||
|
||||
handleIpcMessages () {
|
||||
ipcMain.on('command', (event, command, ...args) => {
|
||||
logger.log('receive command', command, ...args)
|
||||
logger.log('[Motrix] ipc receive command', command, ...args)
|
||||
this.emit(command, ...args)
|
||||
})
|
||||
|
||||
ipcMain.on('update-menu-states', (event, visibleStates, enabledStates, checkedStates) => {
|
||||
this.menuManager.updateStates(visibleStates, enabledStates, checkedStates)
|
||||
ipcMain.on('event', (event, eventName, ...args) => {
|
||||
logger.log('[Motrix] ipc receive event', eventName, ...args)
|
||||
this.emit(eventName, ...args)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,183 @@
|
||||
import { EventEmitter } from 'events'
|
||||
import { app } from 'electron'
|
||||
import is from 'electron-is'
|
||||
|
||||
import ExceptionHandler from './core/ExceptionHandler'
|
||||
import logger from './core/Logger'
|
||||
import Application from './Application'
|
||||
import {
|
||||
splitArgv,
|
||||
parseArgvAsUrl,
|
||||
parseArgvAsFile
|
||||
} from './utils'
|
||||
import { EMPTY_STRING } from '@shared/constants'
|
||||
|
||||
export default class Launcher extends EventEmitter {
|
||||
constructor () {
|
||||
super()
|
||||
this.url = EMPTY_STRING
|
||||
this.file = EMPTY_STRING
|
||||
|
||||
this.makeSingleInstance(() => {
|
||||
this.init()
|
||||
})
|
||||
}
|
||||
|
||||
makeSingleInstance (callback) {
|
||||
// Mac App Store Sandboxed App not support requestSingleInstanceLock
|
||||
if (is.mas()) {
|
||||
callback && callback()
|
||||
return
|
||||
}
|
||||
|
||||
const gotSingleLock = app.requestSingleInstanceLock()
|
||||
|
||||
if (!gotSingleLock) {
|
||||
app.quit()
|
||||
} else {
|
||||
app.on('second-instance', (event, argv, workingDirectory) => {
|
||||
global.application.showPage('index')
|
||||
if (!is.macOS() && argv.length > 1) {
|
||||
this.handleAppLaunchArgv(argv)
|
||||
}
|
||||
})
|
||||
|
||||
callback && callback()
|
||||
}
|
||||
}
|
||||
|
||||
init () {
|
||||
this.exceptionHandler = new ExceptionHandler()
|
||||
|
||||
this.openedAtLogin = is.macOS()
|
||||
? app.getLoginItemSettings().wasOpenedAtLogin
|
||||
: false
|
||||
|
||||
if (process.argv.length > 1) {
|
||||
this.handleAppLaunchArgv(process.argv)
|
||||
}
|
||||
|
||||
logger.info('[Motrix] openedAtLogin:', this.openedAtLogin)
|
||||
|
||||
this.handleAppEvents()
|
||||
}
|
||||
|
||||
handleAppEvents () {
|
||||
this.handleOpenUrl()
|
||||
this.handleOpenFile()
|
||||
|
||||
this.handelAppReady()
|
||||
this.handleAppWillQuit()
|
||||
}
|
||||
|
||||
/**
|
||||
* handleOpenUrl
|
||||
* Event 'open-url' macOS only
|
||||
* "name": "Motrix Protocol",
|
||||
* "schemes": ["mo", "motrix"]
|
||||
*/
|
||||
handleOpenUrl () {
|
||||
if (is.mas() || !is.macOS()) {
|
||||
return
|
||||
}
|
||||
app.on('open-url', (event, url) => {
|
||||
logger.info(`[Motrix] open-url: ${url}`)
|
||||
event.preventDefault()
|
||||
this.url = url
|
||||
this.sendUrlToApplication()
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* handleOpenFile
|
||||
* Event 'open-file' macOS only
|
||||
* handle open torrent file
|
||||
*/
|
||||
handleOpenFile () {
|
||||
if (!is.macOS()) {
|
||||
return
|
||||
}
|
||||
app.on('open-file', (event, path) => {
|
||||
logger.info(`[Motrix] open-file: ${path}`)
|
||||
event.preventDefault()
|
||||
this.file = path
|
||||
this.sendFileToApplication()
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* handleAppLaunchArgv
|
||||
* For Windows, Linux
|
||||
* @param {array} argv
|
||||
*/
|
||||
handleAppLaunchArgv (argv) {
|
||||
logger.info('[Motrix] handleAppLaunchArgv:', argv)
|
||||
|
||||
// args: array, extra: map
|
||||
const { args, extra } = splitArgv(argv)
|
||||
logger.info('[Motrix] split argv args:', args)
|
||||
logger.info('[Motrix] split argv extra:', extra)
|
||||
if (extra['--opened-at-login'] === '1') {
|
||||
this.openedAtLogin = true
|
||||
}
|
||||
|
||||
const file = parseArgvAsFile(args)
|
||||
if (file) {
|
||||
this.file = file
|
||||
this.sendFileToApplication()
|
||||
}
|
||||
|
||||
const url = parseArgvAsUrl(args)
|
||||
if (url) {
|
||||
this.url = url
|
||||
this.sendUrlToApplication()
|
||||
}
|
||||
}
|
||||
|
||||
sendUrlToApplication () {
|
||||
if (this.url && global.application && global.application.isReady) {
|
||||
global.application.handleProtocol(this.url)
|
||||
this.url = EMPTY_STRING
|
||||
}
|
||||
}
|
||||
|
||||
sendFileToApplication () {
|
||||
if (this.file && global.application && global.application.isReady) {
|
||||
global.application.handleFile(this.file)
|
||||
this.file = EMPTY_STRING
|
||||
}
|
||||
}
|
||||
|
||||
handelAppReady () {
|
||||
app.on('ready', () => {
|
||||
global.application = new Application()
|
||||
|
||||
const { openedAtLogin } = this
|
||||
global.application.start('index', {
|
||||
openedAtLogin
|
||||
})
|
||||
|
||||
global.application.on('ready', () => {
|
||||
this.sendUrlToApplication()
|
||||
|
||||
this.sendFileToApplication()
|
||||
})
|
||||
})
|
||||
|
||||
app.on('activate', () => {
|
||||
if (global.application) {
|
||||
logger.info('[Motrix] activate')
|
||||
global.application.showPage('index')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
handleAppWillQuit () {
|
||||
app.on('will-quit', () => {
|
||||
logger.info('[Motrix] will-quit')
|
||||
if (global.application) {
|
||||
global.application.stop()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
export default {
|
||||
'darwin': 'aria2c',
|
||||
'win32': 'aria2c.exe',
|
||||
'linux': 'aria2c'
|
||||
darwin: 'aria2c',
|
||||
win32: 'aria2c.exe',
|
||||
linux: 'aria2c'
|
||||
}
|
||||
|
||||
@@ -6,24 +6,12 @@ export default {
|
||||
title: 'Motrix',
|
||||
width: 1024,
|
||||
height: 768,
|
||||
minWidth: 840,
|
||||
minWidth: 400,
|
||||
minHeight: 420,
|
||||
// backgroundColor: '#FFFFFF',
|
||||
transparent: !is.windows()
|
||||
},
|
||||
bindCloseToHide: true,
|
||||
url: is.dev() ? `http://localhost:9080` : `file://${__dirname}/index.html`
|
||||
},
|
||||
about: {
|
||||
attrs: {
|
||||
title: '关于',
|
||||
width: 580,
|
||||
height: 320,
|
||||
backgroundColor: '#FFFFFF',
|
||||
resizable: false,
|
||||
minimizable: false,
|
||||
maximizable: false
|
||||
},
|
||||
url: is.dev() ? `http://localhost:9080/about` : `file://${__dirname}/about.html`
|
||||
url: is.dev() ? 'http://localhost:9080' : `file://${__dirname}/index.html`
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
/* eslint quote-props: ["error", "always"] */
|
||||
export default {
|
||||
'task-list': 'application:task-list',
|
||||
'new-task': 'application:new-task',
|
||||
'new-bt-task': 'application:new-bt-task',
|
||||
'pause-all-task': 'application:pause-all-task',
|
||||
'resume-all-task': 'application:resume-all-task',
|
||||
'preferences': 'application:preferences',
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import { app } from 'electron'
|
||||
|
||||
export default class AutoLaunchManager {
|
||||
enable () {
|
||||
return new Promise((resolve, reject) => {
|
||||
const enabled = app.getLoginItemSettings().openAtLogin
|
||||
if (enabled) {
|
||||
resolve()
|
||||
}
|
||||
|
||||
app.setLoginItemSettings({
|
||||
openAtLogin: true,
|
||||
// For Windows
|
||||
args: [
|
||||
'--opened-at-login=1'
|
||||
]
|
||||
})
|
||||
resolve()
|
||||
})
|
||||
}
|
||||
|
||||
disable () {
|
||||
return new Promise((resolve, reject) => {
|
||||
app.setLoginItemSettings({ openAtLogin: false })
|
||||
resolve()
|
||||
})
|
||||
}
|
||||
|
||||
isEnabled () {
|
||||
return new Promise((resolve, reject) => {
|
||||
const enabled = app.getLoginItemSettings().openAtLogin
|
||||
resolve(enabled)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,23 @@
|
||||
import { app } from 'electron'
|
||||
import is from 'electron-is'
|
||||
import Store from 'electron-store'
|
||||
|
||||
import {
|
||||
getDhtPath,
|
||||
getLogPath,
|
||||
getSessionPath,
|
||||
getUserDownloadsPath
|
||||
getUserDownloadsPath,
|
||||
getMaxConnectionPerServer
|
||||
} from '../utils/index'
|
||||
import {
|
||||
APP_RUN_MODE,
|
||||
APP_THEME,
|
||||
EMPTY_STRING,
|
||||
NGOSANG_TRACKERS_BEST_IP_URL,
|
||||
NGOSANG_TRACKERS_BEST_URL,
|
||||
IP_VERSION
|
||||
} from '@shared/constants'
|
||||
import { separateConfig } from '@shared/utils'
|
||||
|
||||
export default class ConfigManager {
|
||||
constructor () {
|
||||
@@ -20,49 +32,123 @@ export default class ConfigManager {
|
||||
this.initUserConfig()
|
||||
}
|
||||
|
||||
/**
|
||||
* Some aria2 conf
|
||||
* https://aria2.github.io/manual/en/html/aria2c.html
|
||||
*
|
||||
* Best bt trackers
|
||||
* @see https://github.com/ngosang/trackerslist
|
||||
*
|
||||
* @see https://github.com/XIU2/TrackersListCollection
|
||||
*/
|
||||
initSystemConfig () {
|
||||
this.systemConfig = new Store({
|
||||
name: 'system',
|
||||
/* eslint-disable quote-props */
|
||||
defaults: {
|
||||
dir: getUserDownloadsPath(),
|
||||
// 断点续传
|
||||
continue: true,
|
||||
pause: true,
|
||||
split: 16,
|
||||
'rpc-listen-port': 16800,
|
||||
'rpc-secret': '',
|
||||
'all-proxy': EMPTY_STRING,
|
||||
'allow-overwrite': false,
|
||||
'auto-file-renaming': true,
|
||||
'allow-overwrite': true,
|
||||
'bt-tracker': EMPTY_STRING,
|
||||
'continue': true,
|
||||
'dht-file-path': getDhtPath(IP_VERSION.V4),
|
||||
'dht-file-path6': getDhtPath(IP_VERSION.V6),
|
||||
'dht-listen-port': 26701,
|
||||
'dir': getUserDownloadsPath(),
|
||||
'listen-port': 21301,
|
||||
'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-connection-per-server': getMaxConnectionPerServer(),
|
||||
'max-download-limit': 0,
|
||||
'all-proxy': '',
|
||||
'max-overall-download-limit': 0,
|
||||
'max-overall-upload-limit': '256K',
|
||||
'min-split-size': '1M',
|
||||
'no-proxy': EMPTY_STRING,
|
||||
'pause': true,
|
||||
'rpc-listen-port': 16800,
|
||||
'rpc-secret': EMPTY_STRING,
|
||||
'seed-ratio': 1,
|
||||
'seed-time': 60,
|
||||
'split': 128,
|
||||
'user-agent': 'Transmission/2.94'
|
||||
}
|
||||
/* eslint-enable quote-props */
|
||||
})
|
||||
this.fixSystemConfig()
|
||||
}
|
||||
|
||||
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']
|
||||
// }
|
||||
// },
|
||||
/* eslint-disable quote-props */
|
||||
defaults: {
|
||||
'resume-all-when-app-launched': false,
|
||||
'task-notification': true,
|
||||
'hide-app-menu': false,
|
||||
'all-proxy-backup': EMPTY_STRING,
|
||||
'auto-check-update': is.macOS(),
|
||||
'auto-hide-window': false,
|
||||
'auto-sync-tracker': true,
|
||||
'enable-upnp': true,
|
||||
'engine-max-connection-per-server': getMaxConnectionPerServer(),
|
||||
'hide-app-menu': is.windows() || is.linux(),
|
||||
'keep-window-state': false,
|
||||
'last-check-update-time': 0,
|
||||
'last-sync-tracker-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,
|
||||
'run-mode': APP_RUN_MODE.STANDARD,
|
||||
'session-path': getSessionPath(),
|
||||
'task-notification': true,
|
||||
'theme': APP_THEME.AUTO,
|
||||
'tracker-source': [
|
||||
NGOSANG_TRACKERS_BEST_IP_URL,
|
||||
NGOSANG_TRACKERS_BEST_URL
|
||||
],
|
||||
'tray-theme': APP_THEME.AUTO,
|
||||
'update-channel': 'latest',
|
||||
'use-proxy': false,
|
||||
'all-proxy-backup': '',
|
||||
'log-path': getLogPath(),
|
||||
'session-path': getSessionPath(),
|
||||
'locale': app.getLocale()
|
||||
'window-state': {}
|
||||
}
|
||||
/* eslint-enable quote-props */
|
||||
})
|
||||
this.fixUserConfig()
|
||||
}
|
||||
|
||||
fixSystemConfig () {
|
||||
// Remove aria2c unrecognized options
|
||||
const { others } = separateConfig(this.systemConfig.store)
|
||||
if (!others) {
|
||||
return
|
||||
}
|
||||
|
||||
Object.keys(others).forEach(key => {
|
||||
this.systemConfig.delete(key)
|
||||
})
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
if (this.getUserConfig('tracker-source').length === 0) {
|
||||
this.setUserConfig('tracker-source', [
|
||||
NGOSANG_TRACKERS_BEST_IP_URL,
|
||||
NGOSANG_TRACKERS_BEST_URL
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
getSystemConfig (key, defaultValue) {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { powerSaveBlocker } from 'electron'
|
||||
|
||||
let psbId = null
|
||||
import logger from './Logger'
|
||||
|
||||
let psbId
|
||||
export default class EnergyManager {
|
||||
startPowerSaveBlocker () {
|
||||
if (psbId && powerSaveBlocker.isStarted(psbId)) {
|
||||
@@ -8,16 +10,16 @@ export default class EnergyManager {
|
||||
}
|
||||
|
||||
psbId = powerSaveBlocker.start('prevent-app-suspension')
|
||||
console.log('startPowerSaveBlocker===>', psbId)
|
||||
logger.info('[Motrix] start power save blocker:', psbId)
|
||||
}
|
||||
|
||||
stopPowerSaveBlocker () {
|
||||
if (!psbId || !powerSaveBlocker.isStarted(psbId)) {
|
||||
if (typeof psbId === 'undefined' || !powerSaveBlocker.isStarted(psbId)) {
|
||||
return
|
||||
}
|
||||
|
||||
powerSaveBlocker.stop(psbId)
|
||||
console.log('stopPowerSaveBlocker===>', psbId)
|
||||
psbId = null
|
||||
logger.info('[Motrix] stop power save blocker:', psbId)
|
||||
psbId = undefined
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
'use strict'
|
||||
|
||||
import { app } from 'electron'
|
||||
import is from 'electron-is'
|
||||
import { existsSync } from 'fs'
|
||||
import { resolve, join } from 'path'
|
||||
import forever from 'forever-monitor'
|
||||
|
||||
import logger from './Logger'
|
||||
import { getI18n } from '@/ui/Locale'
|
||||
import {
|
||||
getEngineBin,
|
||||
getSessionPath,
|
||||
@@ -16,6 +16,9 @@ export default class Engine {
|
||||
static instance = null
|
||||
|
||||
constructor (options = {}) {
|
||||
this.options = options
|
||||
|
||||
this.i18n = getI18n()
|
||||
this.systemConfig = options.systemConfig
|
||||
this.userConfig = options.userConfig
|
||||
}
|
||||
@@ -30,19 +33,19 @@ export default class Engine {
|
||||
|
||||
const binName = getEngineBin(platform)
|
||||
if (!binName) {
|
||||
throw new Error('引擎已损坏,请重新安装: (')
|
||||
throw new Error(this.i18n.t('app.engine-damaged-message'))
|
||||
}
|
||||
|
||||
let binPath = join(basePath, `/engine/${binName}`)
|
||||
const binPath = join(basePath, `/engine/${binName}`)
|
||||
const binIsExist = existsSync(binPath)
|
||||
if (!binIsExist) {
|
||||
logger.error('[Motrix] engine bin is not exist===>', binPath)
|
||||
throw new Error('引擎文件缺失,请重新安装: (')
|
||||
logger.error('[Motrix] engine bin is not exist:', binPath)
|
||||
throw new Error(this.i18n.t('app.engine-missing-message'))
|
||||
}
|
||||
|
||||
let confPath = join(basePath, '/engine/aria2.conf')
|
||||
const confPath = join(basePath, '/engine/aria2.conf')
|
||||
|
||||
let sessionPath = this.userConfig['session-path'] || getSessionPath()
|
||||
const sessionPath = this.userConfig['session-path'] || getSessionPath()
|
||||
const sessionIsExist = existsSync(sessionPath)
|
||||
|
||||
let result = [`${binPath}`, `--conf-path=${confPath}`, `--save-session=${sessionPath}`]
|
||||
@@ -58,9 +61,9 @@ export default class Engine {
|
||||
|
||||
start () {
|
||||
const sh = this.getStartSh()
|
||||
logger.info('[Motrix] Engine start sh===>', sh)
|
||||
logger.info('[Motrix] Engine start sh:', sh)
|
||||
this.instance = forever.start(sh, {
|
||||
max: 3,
|
||||
max: is.dev() ? 1 : 100,
|
||||
parser: function (command, args) {
|
||||
return {
|
||||
command: command,
|
||||
@@ -71,25 +74,48 @@ export default class Engine {
|
||||
})
|
||||
|
||||
const { child } = this.instance
|
||||
logger.info('[Motrix] Engine pid===>', child.pid)
|
||||
|
||||
this.instance.on('exit:code', function (code) {
|
||||
logger.info(`[Motrix] Engine has exited after 3 restarts===> ${code}`)
|
||||
})
|
||||
logger.info('[Motrix] Engine pid:', child.pid)
|
||||
|
||||
this.instance.on('error', (err) => {
|
||||
logger.info(`[Motrix] Engine has exited after 3 restarts===> ${err}`)
|
||||
logger.info(`[Motrix] Engine error: ${err}`)
|
||||
})
|
||||
|
||||
this.instance.on('start', function (process, data) {
|
||||
logger.info('[Motrix] Engine started')
|
||||
})
|
||||
|
||||
this.instance.on('stop', function (process) {
|
||||
logger.info('[Motrix] Engine stopped')
|
||||
})
|
||||
|
||||
// this.instance.on('restart', function (forever) {
|
||||
// logger.info(`[Motrix] Engine exit:`)
|
||||
// })
|
||||
|
||||
// this.instance.on('exit:code', function (code) {
|
||||
// logger.info(`[Motrix] Engine exit: ${code}`)
|
||||
// })
|
||||
|
||||
// this.instance.on('stderr', (data) => {
|
||||
// logger.info(`[Motrix] Engine stderr: ${data}`)
|
||||
// })
|
||||
}
|
||||
|
||||
isRunning (pid) {
|
||||
try {
|
||||
return process.kill(pid, 0)
|
||||
} catch (e) {
|
||||
return e.code === 'EPERM'
|
||||
}
|
||||
}
|
||||
|
||||
stop () {
|
||||
const { pid } = this.instance.child
|
||||
try {
|
||||
logger.info('[Motrix] Engine stopping===>')
|
||||
logger.info('[Motrix] Engine stopping')
|
||||
this.instance.stop()
|
||||
logger.info('[Motrix] Engine stopped===>', pid)
|
||||
} catch (err) {
|
||||
logger.error('[Motrix] Engine stop fail===>', err.message)
|
||||
logger.error('[Motrix] Engine stop fail:', err.message)
|
||||
this.forceStop(pid)
|
||||
} finally {
|
||||
}
|
||||
@@ -97,11 +123,11 @@ export default class Engine {
|
||||
|
||||
forceStop (pid) {
|
||||
try {
|
||||
if (pid) {
|
||||
if (pid && this.isRunning(pid)) {
|
||||
process.kill(pid)
|
||||
}
|
||||
} catch (err) {
|
||||
logger.warn('[Motrix] Engine forceStop fail===>', err)
|
||||
logger.warn('[Motrix] Engine force stop fail:', err)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
'use strict'
|
||||
|
||||
import Aria2 from 'aria2'
|
||||
|
||||
import logger from './Logger'
|
||||
import {
|
||||
compactUndefined,
|
||||
formatOptionsForEngine
|
||||
} from '@shared/utils'
|
||||
import {
|
||||
ENGINE_RPC_HOST,
|
||||
ENGINE_RPC_PORT,
|
||||
EMPTY_STRING
|
||||
} from '@shared/constants'
|
||||
|
||||
const defaults = {
|
||||
host: ENGINE_RPC_HOST,
|
||||
port: ENGINE_RPC_PORT,
|
||||
secret: EMPTY_STRING
|
||||
}
|
||||
|
||||
export default class EngineClient {
|
||||
static instance = null
|
||||
static client = null
|
||||
|
||||
constructor (options = {}) {
|
||||
this.options = {
|
||||
...defaults,
|
||||
...options
|
||||
}
|
||||
|
||||
this.init()
|
||||
}
|
||||
|
||||
init () {
|
||||
this.connect()
|
||||
}
|
||||
|
||||
connect () {
|
||||
logger.info('[Motrix] main engine client connect', this.options)
|
||||
const { host, port, secret } = this.options
|
||||
this.client = new Aria2({
|
||||
host,
|
||||
port,
|
||||
secret
|
||||
})
|
||||
}
|
||||
|
||||
async call (method, ...args) {
|
||||
return this.client.call(method, ...args).catch((err) => {
|
||||
logger.warn('[Motrix] call client fail:', err.message)
|
||||
})
|
||||
}
|
||||
|
||||
async changeGlobalOption (options) {
|
||||
logger.info('[Motrix] change engine global option:', options)
|
||||
const args = formatOptionsForEngine(options)
|
||||
|
||||
return this.call('changeGlobalOption', args)
|
||||
}
|
||||
|
||||
async shutdown (options = {}) {
|
||||
const { force = false } = options
|
||||
const { secret } = this.options
|
||||
|
||||
const method = force ? 'forceShutdown' : 'shutdown'
|
||||
const args = compactUndefined([secret])
|
||||
return this.call(method, ...args)
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import { app, dialog } from 'electron'
|
||||
import is from 'electron-is'
|
||||
|
||||
import logger from './Logger'
|
||||
|
||||
const defaults = {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import is from 'electron-is'
|
||||
import logger from 'electron-log'
|
||||
|
||||
logger.transports.file.level = is.production() ? 'warn' : 'info'
|
||||
logger.info('Logger init')
|
||||
logger.transports.file.level = is.production() ? 'warn' : 'silly'
|
||||
logger.info('[Motrix] Logger init')
|
||||
logger.warn('[Motrix] Logger init')
|
||||
|
||||
export default logger
|
||||
|
||||
@@ -1,29 +1,72 @@
|
||||
|
||||
import { EventEmitter } from 'events'
|
||||
import { app } from 'electron'
|
||||
|
||||
import logger from './Logger'
|
||||
import protocolMap from '../configs/protocol'
|
||||
import { ADD_TASK_TYPE } from '@shared/constants'
|
||||
|
||||
export default class ProtocolManager extends EventEmitter {
|
||||
constructor (options = {}) {
|
||||
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}`)
|
||||
|
||||
if (
|
||||
url.toLowerCase().startsWith('magnet:') ||
|
||||
url.toLowerCase().startsWith('thunder:')
|
||||
) {
|
||||
return this.handleMagnetAndThunderProtocol(url)
|
||||
}
|
||||
|
||||
if (
|
||||
url.toLowerCase().startsWith('mo:') ||
|
||||
url.toLowerCase().startsWith('motrix:')
|
||||
) {
|
||||
return this.handleMoProtocol(url)
|
||||
}
|
||||
}
|
||||
|
||||
handleMagnetAndThunderProtocol (url) {
|
||||
if (!url) {
|
||||
return
|
||||
}
|
||||
|
||||
global.application.sendCommandToAll('application:new-task', ADD_TASK_TYPE.URI, url)
|
||||
}
|
||||
|
||||
handleMoProtocol (url) {
|
||||
const parsed = new URL(url)
|
||||
const { host } = parsed
|
||||
logger.info('[Motrix] protocol parsed:', parsed, host)
|
||||
@@ -37,6 +80,6 @@ export default class ProtocolManager extends EventEmitter {
|
||||
// 如果按顺序传递,那 url 的 query string 就要求有序的了
|
||||
// const query = queryString.parse(parsed.query)
|
||||
const args = []
|
||||
global.application.sendCommand(command, ...args)
|
||||
global.application.sendCommandToAll(command, ...args)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
import NatAPI from 'nat-api'
|
||||
|
||||
import logger from './Logger'
|
||||
|
||||
let client = null
|
||||
const mappingStatus = {}
|
||||
|
||||
export default class UPnPManager {
|
||||
constructor (options = {}) {
|
||||
this.options = {
|
||||
...options
|
||||
}
|
||||
}
|
||||
|
||||
init () {
|
||||
if (client) {
|
||||
return
|
||||
}
|
||||
|
||||
client = new NatAPI()
|
||||
}
|
||||
|
||||
map (port) {
|
||||
this.init()
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
logger.info('[Motrix] UPnPManager port mapping: ', port)
|
||||
if (!port) {
|
||||
reject(new Error('[Motrix] port was not specified'))
|
||||
return
|
||||
}
|
||||
|
||||
client.map(port, (err) => {
|
||||
if (err) {
|
||||
logger.warn(`[Motrix] UPnPManager map ${port} failed, error: `, err)
|
||||
reject(err.message)
|
||||
return
|
||||
}
|
||||
|
||||
mappingStatus[port] = true
|
||||
logger.info(`[Motrix] UPnPManager port ${port} mapping succeeded`)
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
unmap (port) {
|
||||
this.init()
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
logger.info('[Motrix] UPnPManager port unmapping: ', port)
|
||||
if (!port) {
|
||||
reject(new Error('[Motrix] port was not specified'))
|
||||
return
|
||||
}
|
||||
|
||||
if (!mappingStatus[port]) {
|
||||
resolve()
|
||||
return
|
||||
}
|
||||
|
||||
client.unmap(port, (err) => {
|
||||
if (err) {
|
||||
logger.warn(`[Motrix] UPnPManager unmap ${port} failed, error: `, err)
|
||||
reject(err.message)
|
||||
return
|
||||
}
|
||||
|
||||
logger.info(`[Motrix] UPnPManager port ${port} unmapping succeeded`)
|
||||
mappingStatus[port] = false
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
destroy () {
|
||||
if (!client) {
|
||||
return
|
||||
}
|
||||
|
||||
client.destroy()
|
||||
client = null
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
import { EventEmitter } from 'events'
|
||||
import { resolve } from 'path'
|
||||
import { dialog } from 'electron'
|
||||
import is from 'electron-is'
|
||||
import { autoUpdater } from 'electron-updater'
|
||||
import { resolve } from 'path'
|
||||
|
||||
import logger from './Logger'
|
||||
import { getI18n } from '@/ui/Locale'
|
||||
|
||||
if (is.dev()) {
|
||||
autoUpdater.updateConfigPath = resolve(__dirname, '../../../app-update.yml')
|
||||
@@ -13,10 +15,15 @@ export default class UpdateManager extends EventEmitter {
|
||||
constructor (options = {}) {
|
||||
super()
|
||||
this.options = options
|
||||
this.i18n = getI18n()
|
||||
|
||||
this.updater = autoUpdater
|
||||
this.updater.autoDownload = false
|
||||
this.updater.logger = logger
|
||||
this.autoCheckData = {
|
||||
checkEnable: this.options.autoCheck,
|
||||
userCheck: false
|
||||
}
|
||||
this.init()
|
||||
}
|
||||
|
||||
@@ -34,9 +41,15 @@ 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.updater.checkForUpdates()
|
||||
}
|
||||
}
|
||||
|
||||
check () {
|
||||
this.autoCheckData.userCheck = true
|
||||
this.updater.checkForUpdates()
|
||||
}
|
||||
|
||||
@@ -48,12 +61,12 @@ export default class UpdateManager extends EventEmitter {
|
||||
this.emit('update-available', info)
|
||||
dialog.showMessageBox({
|
||||
type: 'info',
|
||||
title: '发现新版本',
|
||||
message: '发现新版本,是否现在更新?',
|
||||
buttons: ['是', '否'],
|
||||
title: this.i18n.t('app.check-for-updates-title'),
|
||||
message: this.i18n.t('app.update-available-message'),
|
||||
buttons: [this.i18n.t('app.yes'), this.i18n.t('app.no')],
|
||||
cancelId: 1
|
||||
}, (buttonIndex) => {
|
||||
if (buttonIndex === 0) {
|
||||
}).then(({ response }) => {
|
||||
if (response === 0) {
|
||||
this.updater.downloadUpdate()
|
||||
}
|
||||
})
|
||||
@@ -61,10 +74,12 @@ export default class UpdateManager extends EventEmitter {
|
||||
|
||||
updateNotAvailable (event, info) {
|
||||
this.emit('update-not-available', info)
|
||||
dialog.showMessageBox({
|
||||
title: '没有更新的版本',
|
||||
message: '您目前使用的已是最新版本'
|
||||
})
|
||||
if (this.autoCheckData.userCheck) {
|
||||
dialog.showMessageBox({
|
||||
title: this.i18n.t('app.check-for-updates-title'),
|
||||
message: this.i18n.t('app.update-not-available-message')
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -84,9 +99,9 @@ export default class UpdateManager extends EventEmitter {
|
||||
this.emit('update-downloaded', info)
|
||||
this.updater.logger.log(`Update Downloaded: ${info}`)
|
||||
dialog.showMessageBox({
|
||||
title: '安装更新',
|
||||
message: '更新下载完成,应用程序将退出并开始更新...'
|
||||
}, () => {
|
||||
title: this.i18n.t('app.check-for-updates-title'),
|
||||
message: this.i18n.t('app.update-downloaded-message')
|
||||
}).then(_ => {
|
||||
this.emit('will-updated')
|
||||
setImmediate(() => {
|
||||
this.updater.quitAndInstall()
|
||||
@@ -96,8 +111,11 @@ export default class UpdateManager extends EventEmitter {
|
||||
|
||||
updateError (event, error) {
|
||||
this.emit('update-error', error)
|
||||
const msg = error == null ? '未知错误' : (error.stack || error).toString()
|
||||
this.updater.logger.warn(`Update Error: ${msg}`)
|
||||
dialog.showErrorBox('错误: ', msg)
|
||||
const msg = (error == null)
|
||||
? this.i18n.t('update-error-message')
|
||||
: (error.stack || error).toString()
|
||||
|
||||
this.updater.logger.warn(`[Motrix] update-error: ${msg}`)
|
||||
dialog.showErrorBox(msg)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ require('electron-debug')({
|
||||
})
|
||||
|
||||
// Install `vue-devtools`
|
||||
require('electron').app.on('ready', () => {
|
||||
require('electron').app.whenReady().then(() => {
|
||||
let installExtension = require('electron-devtools-installer')
|
||||
installExtension.default(installExtension.VUEJS_DEVTOOLS)
|
||||
.then(() => {})
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { app } from 'electron'
|
||||
import is from 'electron-is'
|
||||
|
||||
import logger from './core/Logger'
|
||||
import Application from './Application'
|
||||
import Launcher from './Launcher'
|
||||
|
||||
if (process.env.NODE_ENV !== 'development') {
|
||||
global.__static = require('path').join(__dirname, '/static').replace(/\\/g, '\\\\')
|
||||
@@ -12,67 +11,8 @@ if (process.env.NODE_ENV !== 'development') {
|
||||
* Fix Windows notification func
|
||||
* appId defined in .electron-vue/webpack.main.config.js
|
||||
*/
|
||||
if (process.platform === 'win32') {
|
||||
if (is.windows()) {
|
||||
app.setAppUserModelId(appId)
|
||||
}
|
||||
|
||||
function _init () {
|
||||
let openURL = null
|
||||
if (!is.mas()) {
|
||||
app.on('open-url', (event, url) => {
|
||||
logger.info(`You arrived from: ${url}`)
|
||||
event.preventDefault()
|
||||
openURL = url
|
||||
if (global.application) {
|
||||
global.application.handleProtocol(openURL)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
app.on('ready', () => {
|
||||
global.application = new Application()
|
||||
global.application.start('index')
|
||||
|
||||
if (openURL) {
|
||||
global.application.handleProtocol(openURL)
|
||||
}
|
||||
})
|
||||
|
||||
app.on('will-quit', () => {
|
||||
logger.warn('will-quit')
|
||||
global.application && global.application.stop()
|
||||
})
|
||||
|
||||
// Quit when all windows are closed.
|
||||
app.on('window-all-closed', function () {
|
||||
// On OS X it's common NOT to close app even if all windows are closed
|
||||
if (process.platform !== 'darwin') {
|
||||
app.quit()
|
||||
}
|
||||
})
|
||||
|
||||
app.on('activate', () => {
|
||||
global.application.showPage('index')
|
||||
})
|
||||
}
|
||||
|
||||
function init () {
|
||||
// Mac App Store Sandboxed App Not support requestSingleInstanceLock
|
||||
if (is.mas()) {
|
||||
_init()
|
||||
} else {
|
||||
const gotSingleLock = app.requestSingleInstanceLock()
|
||||
|
||||
if (!gotSingleLock) {
|
||||
app.quit()
|
||||
} else {
|
||||
app.on('second-instance', (event, commandLine, workingDirectory) => {
|
||||
global.application.showPage('index')
|
||||
})
|
||||
|
||||
_init()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init()
|
||||
global.launcher = new Launcher()
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
{
|
||||
"menu": [
|
||||
{
|
||||
"id": "menu.app",
|
||||
"submenu": [
|
||||
{ "id": "app.about", "command": "application:about", "command-before": "application:show,index" },
|
||||
{ "type": "separator" },
|
||||
{ "id": "app.preferences", "command": "application:preferences" },
|
||||
{ "id": "app.check-for-updates", "command": "application:check-for-updates" },
|
||||
{ "id": "app.hide", "role": "hide" },
|
||||
{ "id": "app.hide-others", "role": "hideothers" },
|
||||
{ "id": "app.unhide", "role": "unhide" },
|
||||
{ "type": "separator" },
|
||||
{ "id": "app.quit", "command": "application:quit" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "menu.task",
|
||||
"submenu": [
|
||||
{ "id": "task.new-task", "command": "application:new-task", "command-after": "application:show,index" },
|
||||
{ "id": "task.new-bt-task", "command": "application:new-bt-task", "command-arg": "torrent", "command-after": "application:show,index" },
|
||||
{ "id": "task.open-file", "command": "application:open-file", "command-before": "application:show,index" },
|
||||
{ "type": "separator" },
|
||||
{ "id": "task.pause-task", "command": "application:pause-task" },
|
||||
{ "id": "task.resume-task", "command": "application:resume-task" },
|
||||
{ "id": "task.delete-task", "command": "application:delete-task" },
|
||||
{ "id": "task.move-task-up", "command": "application:move-task-up" },
|
||||
{ "id": "task.move-task-down", "command": "application:move-task-down" },
|
||||
{ "type": "separator" },
|
||||
{ "id": "task.pause-all-task", "command": "application:pause-all-task" },
|
||||
{ "id": "task.resume-all-task", "command": "application:resume-all-task" },
|
||||
{ "id": "task.select-all-task", "command": "application:select-all-task" },
|
||||
{ "type": "separator" },
|
||||
{ "id": "task.clear-recent-tasks", "command": "application:clear-recent-tasks" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "menu.edit",
|
||||
"submenu": [
|
||||
{ "id": "edit.undo", "role": "undo" },
|
||||
{ "id": "edit.redo", "role": "redo" },
|
||||
{ "type": "separator" },
|
||||
{ "id": "edit.cut", "role": "cut" },
|
||||
{ "id": "edit.copy", "role": "copy" },
|
||||
{ "id": "edit.paste", "role": "paste" },
|
||||
{ "id": "edit.delete", "role": "delete" },
|
||||
{ "id": "edit.select-all", "role": "selectall" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"role": "window",
|
||||
"id": "menu.window",
|
||||
"submenu": [
|
||||
{ "id": "window.reload", "role": "reload" },
|
||||
{ "id": "window.close", "role": "close" },
|
||||
{ "id": "window.minimize", "role": "minimize" },
|
||||
{ "id": "window.zoom", "role": "zoom" },
|
||||
{ "id": "window.toggle-fullscreen", "role": "togglefullscreen" },
|
||||
{ "type": "separator" },
|
||||
{ "id": "window.front", "role": "front" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"role": "help",
|
||||
"id": "menu.help",
|
||||
"submenu": [
|
||||
{ "id": "help.official-website", "command": "help:official-website" },
|
||||
{ "id": "help.manual", "command": "help:manual" },
|
||||
{ "id": "help.release-notes", "command": "help:release-notes" },
|
||||
{ "type": "separator" },
|
||||
{ "id": "help.report-problem", "command": "help:report-problem" },
|
||||
{ "type": "separator" },
|
||||
{ "id": "help.toggle-dev-tools", "role": "toggledevtools" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
{
|
||||
"menu": [
|
||||
{
|
||||
"label": "Motrix",
|
||||
"id": "menu.app",
|
||||
"submenu": [
|
||||
{ "label": "About Motrix", "id": "app.about", "command": "application:about", "command-before": "application:show,index"},
|
||||
{ "type": "separator" },
|
||||
{ "label": "Preferences...", "id": "app.preferences", "command": "application:preferences", "command-before": "application:show,index"},
|
||||
{ "label": "Check for Updates...", "id": "app.check-for-updates", "command": "application:check-for-updates" },
|
||||
{ "label": "Hide Motrix", "id": "app.hide", "role": "hide" },
|
||||
{ "label": "Hide Others", "id": "app.hide-others", "role": "hideothers" },
|
||||
{ "label": "Show All", "id": "app.unhide", "role": "unhide" },
|
||||
{ "type": "separator" },
|
||||
{ "label": "Quit Motrix", "id": "app.quit", "role": "quit" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Task",
|
||||
"id": "menu.task",
|
||||
"submenu": [
|
||||
{ "label": "New Task", "id": "task.new-task", "command": "application:new-task", "command-arg": "uri", "command-after": "application:show,index"},
|
||||
{ "label": "New BT Task", "id": "task.new-bt-task", "command": "application:new-bt-task", "command-arg": "torrent", "command-after": "application:show,index"},
|
||||
{ "type": "separator" },
|
||||
{ "label": "Pause Task", "id": "task.pause-task", "command": "application:pause-task" },
|
||||
{ "label": "Resume Task", "id": "task.resume-task", "command": "application:resume-task" },
|
||||
{ "label": "Delete Task", "id": "task.delete-task", "command": "application:delete-task" },
|
||||
{ "label": "Move Task Up", "id": "task.move-task-up", "command": "application:move-task-up" },
|
||||
{ "label": "Move Task Down", "id": "task.move-task-down", "command": "application:move-task-down" },
|
||||
{ "type": "separator" },
|
||||
{ "label": "Pause All Task", "id": "task.pause-all-task", "command": "application:pause-all-task" },
|
||||
{ "label": "Resume All Task", "id": "task.resume-all-task", "command": "application:resume-all-task" },
|
||||
{ "type": "separator" },
|
||||
{ "label": "Clear Recent Tasks", "id": "task.clear-recent-tasks", "command": "application:clear-recent-tasks" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Edit",
|
||||
"id": "menu.edit",
|
||||
"submenu": [
|
||||
{ "label": "Undo", "id": "edit.undo", "role": "undo" },
|
||||
{ "label": "Redo", "id": "edit.redo", "role": "redo" },
|
||||
{ "type": "separator" },
|
||||
{ "label": "Cut", "id": "edit.cut", "role": "cut" },
|
||||
{ "label": "Copy", "id": "edit.copy", "role": "copy" },
|
||||
{ "label": "Paste", "id": "edit.paste", "role": "paste" },
|
||||
{ "label": "Delete", "id": "edit.delete", "role": "delete" },
|
||||
{ "label": "Select All", "id": "edit.select-all", "role": "selectall" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Window",
|
||||
"role": "window",
|
||||
"id": "menu.window",
|
||||
"submenu": [
|
||||
{ "label": "Reload", "id": "window.reload", "role": "reload" },
|
||||
{ "label": "Close", "id": "window.close", "role": "close" },
|
||||
{ "label": "Minimize", "id": "window.minimize", "role": "minimize" },
|
||||
{ "label": "Zoom", "id": "window.zoom", "role": "zoom" },
|
||||
{ "label": "Enter Full Screen", "id": "window.toggle-fullscreen", "role": "togglefullscreen" },
|
||||
{ "type": "separator" },
|
||||
{ "label": "Bring All to Front", "id": "window.front", "role": "front" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Help",
|
||||
"role": "help",
|
||||
"id": "menu.help",
|
||||
"submenu": [
|
||||
{ "label": "Motrix Website", "id": "help.official-website", "command": "help:official-website" },
|
||||
{ "label": "Manual", "id": "help.manual", "command": "help:manual" },
|
||||
{ "label": "Release Notes...", "id": "help.release-notes", "command": "help:release-notes" },
|
||||
{ "type": "separator" },
|
||||
{ "label": "Report Problem", "id": "help.report-problem", "command": "help:report-problem" },
|
||||
{ "type": "separator" },
|
||||
{ "label": "Toggle Developer Tools", "id": "help.toggle-dev-tools", "role": "toggledevtools" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
{
|
||||
"menu": [
|
||||
{
|
||||
"label": "File",
|
||||
"id": "menu.file",
|
||||
"submenu": [
|
||||
{ "label": "About Motrix", "id": "app.about", "command": "application:about", "command-before": "application:show,index"},
|
||||
{ "type": "separator" },
|
||||
{ "label": "Preferences...", "id": "app.preferences", "command": "application:preferences", "command-before": "application:show,index"},
|
||||
{ "label": "Check for Updates...", "id": "app.check-for-updates", "command": "application:check-for-updates" },
|
||||
{ "label": "Hide Motrix", "id": "app.hide", "role": "hide" },
|
||||
{ "label": "Hide Others", "id": "app.hide-others", "role": "hideothers" },
|
||||
{ "label": "Show All", "id": "app.unhide", "role": "unhide" },
|
||||
{ "type": "separator" },
|
||||
{ "label": "Quit Motrix", "id": "app.quit", "role": "quit" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Task",
|
||||
"id": "menu.task",
|
||||
"submenu": [
|
||||
{ "label": "New Task", "id": "task.new-task", "command": "application:new-task", "command-arg": "uri", "command-after": "application:show,index"},
|
||||
{ "label": "New BT Task", "id": "task.new-bt-task", "command": "application:new-bt-task", "command-arg": "torrent", "command-after": "application:show,index"},
|
||||
{ "type": "separator" },
|
||||
{ "label": "Pause Task", "id": "task.pause-task", "command": "application:pause-task" },
|
||||
{ "label": "Resume Task", "id": "task.resume-task", "command": "application:resume-task" },
|
||||
{ "label": "Delete Task", "id": "task.delete-task", "command": "application:delete-task" },
|
||||
{ "label": "Move Task Up", "id": "task.move-task-up", "command": "application:move-task-up" },
|
||||
{ "label": "Move Task Down", "id": "task.move-task-down", "command": "application:move-task-down" },
|
||||
{ "type": "separator" },
|
||||
{ "label": "Pause All Task", "id": "task.pause-all-task", "command": "application:pause-all-task" },
|
||||
{ "label": "Resume All Task", "id": "task.resume-all-task", "command": "application:resume-all-task" },
|
||||
{ "type": "separator" },
|
||||
{ "label": "Clear Recent Tasks", "id": "task.clear-recent-tasks", "command": "application:clear-recent-tasks" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Edit",
|
||||
"id": "menu.edit",
|
||||
"submenu": [
|
||||
{ "label": "Undo", "id": "edit.undo", "role": "undo" },
|
||||
{ "label": "Redo", "id": "edit.redo", "role": "redo" },
|
||||
{ "type": "separator" },
|
||||
{ "label": "Cut", "id": "edit.cut", "role": "cut" },
|
||||
{ "label": "Copy", "id": "edit.copy", "role": "copy" },
|
||||
{ "label": "Paste", "id": "edit.paste", "role": "paste" },
|
||||
{ "label": "Delete", "id": "edit.delete", "role": "delete" },
|
||||
{ "label": "Select All", "id": "edit.select-all", "role": "selectall" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Window",
|
||||
"role": "window",
|
||||
"id": "menu.window",
|
||||
"submenu": [
|
||||
{ "label": "Reload", "id": "window.reload", "role": "reload" },
|
||||
{ "label": "Close", "id": "window.close", "role": "close" },
|
||||
{ "label": "Minimize", "id": "window.minimize", "role": "minimize" },
|
||||
{ "label": "Zoom", "id": "window.zoom", "role": "zoom" },
|
||||
{ "label": "Enter Full Screen", "id": "window.toggle-fullscreen", "role": "togglefullscreen" },
|
||||
{ "type": "separator" },
|
||||
{ "label": "Bring All to Front", "id": "window.front", "role": "front" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Help",
|
||||
"role": "help",
|
||||
"id": "menu.help",
|
||||
"submenu": [
|
||||
{ "label": "Motrix Website", "id": "help.official-website", "command": "help:official-website" },
|
||||
{ "label": "Manual", "id": "help.manual", "command": "help:manual" },
|
||||
{ "label": "Release Notes...", "id": "help.release-notes", "command": "help:release-notes" },
|
||||
{ "type": "separator" },
|
||||
{ "label": "Report Problem", "id": "help.report-problem", "command": "help:report-problem" },
|
||||
{ "type": "separator" },
|
||||
{ "label": "Toggle Developer Tools", "id": "help.toggle-dev-tools", "role": "toggledevtools" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
{
|
||||
"menu": [
|
||||
{
|
||||
"label": "File",
|
||||
"id": "menu.file",
|
||||
"submenu": [
|
||||
{ "label": "About Motrix", "id": "app.about", "command": "application:about", "command-before": "application:show,index"},
|
||||
{ "type": "separator" },
|
||||
{ "label": "Preferences...", "id": "app.preferences", "command": "application:preferences", "command-before": "application:show,index"},
|
||||
{ "label": "Check for Updates...", "id": "app.check-for-updates", "command": "application:check-for-updates" },
|
||||
{ "label": "Hide Motrix", "id": "app.hide", "role": "hide" },
|
||||
{ "label": "Hide Others", "id": "app.hide-others", "role": "hideothers" },
|
||||
{ "label": "Show All", "id": "app.unhide", "role": "unhide" },
|
||||
{ "type": "separator" },
|
||||
{ "label": "Quit Motrix", "id": "app.quit", "role": "quit" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Task",
|
||||
"id": "menu.task",
|
||||
"submenu": [
|
||||
{ "label": "New Task", "id": "task.new-task", "command": "application:new-task", "command-arg": "uri", "command-after": "application:show,index"},
|
||||
{ "label": "New BT Task", "id": "task.new-bt-task", "command": "application:new-bt-task", "command-arg": "torrent", "command-after": "application:show,index"},
|
||||
{ "type": "separator" },
|
||||
{ "label": "Pause Task", "id": "task.pause-task", "command": "application:pause-task" },
|
||||
{ "label": "Resume Task", "id": "task.resume-task", "command": "application:resume-task" },
|
||||
{ "label": "Delete Task", "id": "task.delete-task", "command": "application:delete-task" },
|
||||
{ "label": "Move Task Up", "id": "task.move-task-up", "command": "application:move-task-up" },
|
||||
{ "label": "Move Task Down", "id": "task.move-task-down", "command": "application:move-task-down" },
|
||||
{ "type": "separator" },
|
||||
{ "label": "Pause All Task", "id": "task.pause-all-task", "command": "application:pause-all-task" },
|
||||
{ "label": "Resume All Task", "id": "task.resume-all-task", "command": "application:resume-all-task" },
|
||||
{ "type": "separator" },
|
||||
{ "label": "Clear Recent Tasks", "id": "task.clear-recent-tasks", "command": "application:clear-recent-tasks" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Edit",
|
||||
"id": "menu.edit",
|
||||
"submenu": [
|
||||
{ "label": "Undo", "id": "edit.undo", "role": "undo" },
|
||||
{ "label": "Redo", "id": "edit.redo", "role": "redo" },
|
||||
{ "type": "separator" },
|
||||
{ "label": "Cut", "id": "edit.cut", "role": "cut" },
|
||||
{ "label": "Copy", "id": "edit.copy", "role": "copy" },
|
||||
{ "label": "Paste", "id": "edit.paste", "role": "paste" },
|
||||
{ "label": "Delete", "id": "edit.delete", "role": "delete" },
|
||||
{ "label": "Select All", "id": "edit.select-all", "role": "selectall" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Window",
|
||||
"role": "window",
|
||||
"id": "menu.window",
|
||||
"submenu": [
|
||||
{ "label": "Reload", "id": "window.reload", "role": "reload" },
|
||||
{ "label": "Close", "id": "window.close", "role": "close" },
|
||||
{ "label": "Minimize", "id": "window.minimize", "role": "minimize" },
|
||||
{ "label": "Zoom", "id": "window.zoom", "role": "zoom" },
|
||||
{ "label": "Enter Full Screen", "id": "window.toggle-fullscreen", "role": "togglefullscreen" },
|
||||
{ "type": "separator" },
|
||||
{ "label": "Bring All to Front", "id": "window.front", "role": "front" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Help",
|
||||
"role": "help",
|
||||
"id": "menu.help",
|
||||
"submenu": [
|
||||
{ "label": "Motrix Website", "id": "help.official-website", "command": "help:official-website" },
|
||||
{ "label": "Manual", "id": "help.manual", "command": "help:manual" },
|
||||
{ "label": "Release Notes...", "id": "help.release-notes", "command": "help:release-notes" },
|
||||
{ "type": "separator" },
|
||||
{ "label": "Report Problem", "id": "help.report-problem", "command": "help:report-problem" },
|
||||
{ "type": "separator" },
|
||||
{ "label": "Toggle Developer Tools", "id": "help.toggle-dev-tools", "role": "toggledevtools" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
{
|
||||
"menu": [
|
||||
{
|
||||
"id": "menu.file",
|
||||
"submenu": [
|
||||
{ "id": "app.about", "command": "application:about", "command-before": "application:show,index" },
|
||||
{ "type": "separator" },
|
||||
{ "id": "app.preferences", "command": "application:preferences" },
|
||||
{ "id": "app.check-for-updates", "command": "application:check-for-updates" },
|
||||
{ "id": "app.show", "command": "application:show", "command-arg": "index" },
|
||||
{ "type": "separator" },
|
||||
{ "id": "app.quit", "command": "application:quit" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "menu.task",
|
||||
"submenu": [
|
||||
{ "id": "task.new-task", "command": "application:new-task", "command-after": "application:show,index" },
|
||||
{ "id": "task.new-bt-task", "command": "application:new-bt-task", "command-arg": "torrent", "command-after": "application:show,index" },
|
||||
{ "id": "task.open-file", "command": "application:open-file", "command-before": "application:show,index" },
|
||||
{ "type": "separator" },
|
||||
{ "id": "task.pause-task", "command": "application:pause-task" },
|
||||
{ "id": "task.resume-task", "command": "application:resume-task" },
|
||||
{ "id": "task.delete-task", "command": "application:delete-task" },
|
||||
{ "id": "task.move-task-up", "command": "application:move-task-up" },
|
||||
{ "id": "task.move-task-down", "command": "application:move-task-down" },
|
||||
{ "type": "separator" },
|
||||
{ "id": "task.pause-all-task", "command": "application:pause-all-task" },
|
||||
{ "id": "task.resume-all-task", "command": "application:resume-all-task" },
|
||||
{ "id": "task.select-all-task", "command": "application:select-all-task" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "menu.edit",
|
||||
"submenu": [
|
||||
{ "id": "edit.undo", "role": "undo" },
|
||||
{ "id": "edit.redo", "role": "redo" },
|
||||
{ "type": "separator" },
|
||||
{ "id": "edit.cut", "role": "cut" },
|
||||
{ "id": "edit.copy", "role": "copy" },
|
||||
{ "id": "edit.paste", "role": "paste" },
|
||||
{ "id": "edit.delete", "role": "delete" },
|
||||
{ "id": "edit.select-all", "role": "selectall" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"role": "window",
|
||||
"id": "menu.window",
|
||||
"submenu": [
|
||||
{ "id": "window.reload", "role": "reload" },
|
||||
{ "id": "window.close", "role": "close" },
|
||||
{ "id": "window.minimize", "role": "minimize" },
|
||||
{ "id": "window.zoom", "role": "zoom" },
|
||||
{ "id": "window.toggle-fullscreen", "role": "togglefullscreen" },
|
||||
{ "type": "separator" },
|
||||
{ "id": "window.front", "role": "front" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"role": "help",
|
||||
"id": "menu.help",
|
||||
"submenu": [
|
||||
{ "id": "help.official-website", "command": "help:official-website" },
|
||||
{ "id": "help.manual", "command": "help:manual" },
|
||||
{ "id": "help.release-notes", "command": "help:release-notes" },
|
||||
{ "type": "separator" },
|
||||
{ "id": "help.report-problem", "command": "help:report-problem" },
|
||||
{ "type": "separator" },
|
||||
{ "id": "help.toggle-dev-tools", "role": "toggledevtools" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
[
|
||||
{ "id": "task.new-task", "command": "application:new-task", "command-after": "application:show,index" },
|
||||
{ "id": "task.new-bt-task", "command": "application:new-bt-task", "command-arg": "torrent", "command-after": "application:show,index" },
|
||||
{ "id": "task.open-file", "command": "application:open-file", "command-before": "application:show,index" },
|
||||
{ "type": "separator" },
|
||||
{ "id": "app.show", "command": "application:show", "command-arg": "index" },
|
||||
{ "id": "help.manual", "command": "help:manual" },
|
||||
{ "id": "app.check-for-updates", "command": "application:check-for-updates" },
|
||||
{ "type": "separator" },
|
||||
{ "id": "app.preferences", "command": "application:preferences", "command-before": "application:show,index" },
|
||||
{ "id": "app.quit", "command": "application:quit" }
|
||||
]
|
||||
@@ -0,0 +1,75 @@
|
||||
{
|
||||
"menu": [
|
||||
{
|
||||
"id": "menu.file",
|
||||
"submenu": [
|
||||
{ "id": "app.about", "command": "application:about", "command-before": "application:show,index" },
|
||||
{ "type": "separator" },
|
||||
{ "id": "app.preferences", "command": "application:preferences" },
|
||||
{ "id": "app.check-for-updates", "command": "application:check-for-updates" },
|
||||
{ "id": "app.show", "command": "application:show", "command-arg": "index" },
|
||||
{ "type": "separator" },
|
||||
{ "id": "app.quit", "command": "application:quit" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "menu.task",
|
||||
"submenu": [
|
||||
{ "id": "task.new-task", "command": "application:new-task", "command-after": "application:show,index" },
|
||||
{ "id": "task.new-bt-task", "command": "application:new-bt-task", "command-arg": "torrent", "command-after": "application:show,index" },
|
||||
{ "id": "task.open-file", "command": "application:open-file", "command-before": "application:show,index" },
|
||||
{ "type": "separator" },
|
||||
{ "id": "task.pause-task", "command": "application:pause-task" },
|
||||
{ "id": "task.resume-task", "command": "application:resume-task" },
|
||||
{ "id": "task.delete-task", "command": "application:delete-task" },
|
||||
{ "id": "task.move-task-up", "command": "application:move-task-up" },
|
||||
{ "id": "task.move-task-down", "command": "application:move-task-down" },
|
||||
{ "type": "separator" },
|
||||
{ "id": "task.pause-all-task", "command": "application:pause-all-task" },
|
||||
{ "id": "task.resume-all-task", "command": "application:resume-all-task" },
|
||||
{ "id": "task.select-all-task", "command": "application:select-all-task" },
|
||||
{ "type": "separator" },
|
||||
{ "id": "task.clear-recent-tasks", "command": "application:clear-recent-tasks" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "menu.edit",
|
||||
"submenu": [
|
||||
{ "id": "edit.undo", "role": "undo" },
|
||||
{ "id": "edit.redo", "role": "redo" },
|
||||
{ "type": "separator" },
|
||||
{ "id": "edit.cut", "role": "cut" },
|
||||
{ "id": "edit.copy", "role": "copy" },
|
||||
{ "id": "edit.paste", "role": "paste" },
|
||||
{ "id": "edit.delete", "role": "delete" },
|
||||
{ "id": "edit.select-all", "role": "selectall" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"role": "window",
|
||||
"id": "menu.window",
|
||||
"submenu": [
|
||||
{ "id": "window.reload", "role": "reload" },
|
||||
{ "id": "window.close", "role": "close" },
|
||||
{ "id": "window.minimize", "role": "minimize" },
|
||||
{ "id": "window.zoom", "role": "zoom" },
|
||||
{ "id": "window.toggle-fullscreen", "role": "togglefullscreen" },
|
||||
{ "type": "separator" },
|
||||
{ "id": "window.front", "role": "front" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"role": "help",
|
||||
"id": "menu.help",
|
||||
"submenu": [
|
||||
{ "id": "help.official-website", "command": "help:official-website" },
|
||||
{ "id": "help.manual", "command": "help:manual" },
|
||||
{ "id": "help.release-notes", "command": "help:release-notes" },
|
||||
{ "type": "separator" },
|
||||
{ "id": "help.report-problem", "command": "help:report-problem" },
|
||||
{ "type": "separator" },
|
||||
{ "id": "help.toggle-dev-tools", "role": "toggledevtools" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
{
|
||||
"menu": [
|
||||
{
|
||||
"label": "Motrix",
|
||||
"id": "menu.app",
|
||||
"submenu": [
|
||||
{ "label": "关于 Motrix", "id": "app.about", "command": "application:about", "command-before": "application:show,index"},
|
||||
{ "type": "separator" },
|
||||
{ "label": "偏好设置...", "id": "app.preferences", "command": "application:preferences" },
|
||||
{ "label": "检查更新...", "id": "app.check-for-updates", "command": "application:check-for-updates" },
|
||||
{ "label": "隐藏 Motrix", "id": "app.hide", "role": "hide" },
|
||||
{ "label": "隐藏其他", "id": "app.hide-others", "role": "hideothers" },
|
||||
{ "label": "显示全部", "id": "app.unhide", "role": "unhide" },
|
||||
{ "type": "separator" },
|
||||
{ "label": "退出 Motrix", "id": "app.quit", "role": "quit" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "任务",
|
||||
"id": "menu.task",
|
||||
"submenu": [
|
||||
{ "label": "新建任务", "id": "task.new-task", "command": "application:new-task", "command-after": "application:show,index"},
|
||||
{ "label": "新建 BT 任务", "id": "task.new-bt-task", "command": "application:new-bt-task", "command-arg": "torrent", "command-after": "application:show,index"},
|
||||
{ "type": "separator" },
|
||||
{ "label": "暂停任务", "id": "task.pause-task", "command": "application:pause-task" },
|
||||
{ "label": "恢复任务", "id": "task.resume-task", "command": "application:resume-task" },
|
||||
{ "label": "删除任务", "id": "task.delete-task", "command": "application:delete-task" },
|
||||
{ "label": "上移任务", "id": "task.move-task-up", "command": "application:move-task-up" },
|
||||
{ "label": "下移任务", "id": "task.move-task-down", "command": "application:move-task-down" },
|
||||
{ "type": "separator" },
|
||||
{ "label": "暂停所有任务", "id": "task.pause-all-task", "command": "application:pause-all-task" },
|
||||
{ "label": "恢复所有任务", "id": "task.resume-all-task", "command": "application:resume-all-task" },
|
||||
{ "type": "separator" },
|
||||
{ "label": "清除最近的下载记录", "id": "task.clear-recent-tasks", "command": "application:clear-recent-tasks" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "编辑",
|
||||
"id": "menu.edit",
|
||||
"submenu": [
|
||||
{ "label": "撤销", "id": "edit.undo", "role": "undo" },
|
||||
{ "label": "重做", "id": "edit.redo", "role": "redo" },
|
||||
{ "type": "separator" },
|
||||
{ "label": "剪切", "id": "edit.cut", "role": "cut" },
|
||||
{ "label": "复制", "id": "edit.copy", "role": "copy" },
|
||||
{ "label": "黏贴", "id": "edit.paste", "role": "paste" },
|
||||
{ "label": "删除", "id": "edit.delete", "role": "delete" },
|
||||
{ "label": "全选", "id": "edit.select-all", "role": "selectall" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "窗口",
|
||||
"role": "window",
|
||||
"id": "menu.window",
|
||||
"submenu": [
|
||||
{ "label": "重新加载", "id": "window.reload", "role": "reload" },
|
||||
{ "label": "关闭", "id": "window.close", "role": "close" },
|
||||
{ "label": "最小化", "id": "window.minimize", "role": "minimize" },
|
||||
{ "label": "放大", "id": "window.zoom", "role": "zoom" },
|
||||
{ "label": "进入全屏幕", "id": "window.toggle-fullscreen", "role": "togglefullscreen" },
|
||||
{ "type": "separator" },
|
||||
{ "label": "前置全部窗口", "id": "window.front", "role": "front" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "帮助",
|
||||
"role": "help",
|
||||
"id": "menu.help",
|
||||
"submenu": [
|
||||
{ "label": "Motrix 官网", "id": "help.official-website", "command": "help:official-website" },
|
||||
{ "label": "使用手册", "id": "help.manual", "command": "help:manual" },
|
||||
{ "label": "发行说明...", "id": "help.release-notes", "command": "help:release-notes" },
|
||||
{ "type": "separator" },
|
||||
{ "label": "报告问题", "id": "help.report-problem", "command": "help:report-problem" },
|
||||
{ "type": "separator" },
|
||||
{ "label": "开发者工具", "id": "help.toggle-dev-tools", "role": "toggledevtools" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
{
|
||||
"menu": [
|
||||
{
|
||||
"label": "文件",
|
||||
"id": "menu.file",
|
||||
"submenu": [
|
||||
{ "label": "关于 Motrix", "id": "app.about", "command": "application:about", "command-before": "application:show,index"},
|
||||
{ "type": "separator" },
|
||||
{ "label": "偏好设置...", "id": "app.preferences", "command": "application:preferences" },
|
||||
{ "label": "检查更新...", "id": "app.check-for-updates", "command": "application:check-for-updates" },
|
||||
{ "label": "隐藏 Motrix", "id": "app.hide", "role": "hide" },
|
||||
{ "label": "隐藏其他", "id": "app.hide-others", "role": "hideothers" },
|
||||
{ "label": "显示全部", "id": "app.unhide", "role": "unhide" },
|
||||
{ "type": "separator" },
|
||||
{ "label": "退出 Motrix", "id": "app.quit", "role": "quit" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "任务",
|
||||
"id": "menu.task",
|
||||
"submenu": [
|
||||
{ "label": "新建任务", "id": "task.new-task", "command": "application:new-task", "command-after": "application:show,index"},
|
||||
{ "label": "新建 BT 任务", "id": "task.new-bt-task", "command": "application:new-bt-task", "command-arg": "torrent", "command-after": "application:show,index"},
|
||||
{ "type": "separator" },
|
||||
{ "label": "暂停任务", "id": "task.pause-task", "command": "application:pause-task" },
|
||||
{ "label": "恢复任务", "id": "task.resume-task", "command": "application:resume-task" },
|
||||
{ "label": "删除任务", "id": "task.delete-task", "command": "application:delete-task" },
|
||||
{ "label": "上移任务", "id": "task.move-task-up", "command": "application:move-task-up" },
|
||||
{ "label": "下移任务", "id": "task.move-task-down", "command": "application:move-task-down" },
|
||||
{ "type": "separator" },
|
||||
{ "label": "暂停所有任务", "id": "task.pause-all-task", "command": "application:pause-all-task" },
|
||||
{ "label": "恢复所有任务", "id": "task.resume-all-task", "command": "application:resume-all-task" },
|
||||
{ "type": "separator" },
|
||||
{ "label": "清除最近的下载记录", "id": "task.clear-recent-tasks", "command": "application:clear-recent-tasks" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "编辑",
|
||||
"id": "menu.edit",
|
||||
"submenu": [
|
||||
{ "label": "撤销", "id": "edit.undo", "role": "undo" },
|
||||
{ "label": "重做", "id": "edit.redo", "role": "redo" },
|
||||
{ "type": "separator" },
|
||||
{ "label": "剪切", "id": "edit.cut", "role": "cut" },
|
||||
{ "label": "复制", "id": "edit.copy", "role": "copy" },
|
||||
{ "label": "黏贴", "id": "edit.paste", "role": "paste" },
|
||||
{ "label": "删除", "id": "edit.delete", "role": "delete" },
|
||||
{ "label": "全选", "id": "edit.select-all", "role": "selectall" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "窗口",
|
||||
"role": "window",
|
||||
"id": "menu.window",
|
||||
"submenu": [
|
||||
{ "label": "重新加载", "id": "window.reload", "role": "reload" },
|
||||
{ "label": "关闭", "id": "window.close", "role": "close" },
|
||||
{ "label": "最小化", "id": "window.minimize", "role": "minimize" },
|
||||
{ "label": "放大", "id": "window.zoom", "role": "zoom" },
|
||||
{ "label": "进入全屏幕", "id": "window.toggle-fullscreen", "role": "togglefullscreen" },
|
||||
{ "type": "separator" },
|
||||
{ "label": "前置全部窗口", "id": "window.front", "role": "front" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "帮助",
|
||||
"role": "help",
|
||||
"id": "menu.help",
|
||||
"submenu": [
|
||||
{ "label": "Motrix 官网", "id": "help.official-website", "command": "help:official-website" },
|
||||
{ "label": "使用手册", "id": "help.manual", "command": "help:manual" },
|
||||
{ "label": "发行说明...", "id": "help.release-notes", "command": "help:release-notes" },
|
||||
{ "type": "separator" },
|
||||
{ "label": "报告问题", "id": "help.report-problem", "command": "help:report-problem" },
|
||||
{ "type": "separator" },
|
||||
{ "label": "开发者工具", "id": "help.toggle-dev-tools", "role": "toggledevtools" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
{
|
||||
"menu": [
|
||||
{
|
||||
"label": "文件",
|
||||
"id": "menu.file",
|
||||
"submenu": [
|
||||
{ "label": "关于 Motrix", "id": "app.about", "command": "application:about", "command-before": "application:show,index"},
|
||||
{ "type": "separator" },
|
||||
{ "label": "偏好设置...", "id": "app.preferences", "command": "application:preferences" },
|
||||
{ "label": "检查更新...", "id": "app.check-for-updates", "command": "application:check-for-updates" },
|
||||
{ "label": "隐藏 Motrix", "id": "app.hide", "role": "hide" },
|
||||
{ "label": "隐藏其他", "id": "app.hide-others", "role": "hideothers" },
|
||||
{ "label": "显示全部", "id": "app.unhide", "role": "unhide" },
|
||||
{ "type": "separator" },
|
||||
{ "label": "退出 Motrix", "id": "app.quit", "role": "quit" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "任务",
|
||||
"id": "menu.task",
|
||||
"submenu": [
|
||||
{ "label": "新建任务", "id": "task.new-task", "command": "application:new-task", "command-after": "application:show,index"},
|
||||
{ "label": "新建 BT 任务", "id": "task.new-bt-task", "command": "application:new-bt-task", "command-arg": "torrent", "command-after": "application:show,index"},
|
||||
{ "type": "separator" },
|
||||
{ "label": "暂停任务", "id": "task.pause-task", "command": "application:pause-task" },
|
||||
{ "label": "恢复任务", "id": "task.resume-task", "command": "application:resume-task" },
|
||||
{ "label": "删除任务", "id": "task.delete-task", "command": "application:delete-task" },
|
||||
{ "label": "上移任务", "id": "task.move-task-up", "command": "application:move-task-up" },
|
||||
{ "label": "下移任务", "id": "task.move-task-down", "command": "application:move-task-down" },
|
||||
{ "type": "separator" },
|
||||
{ "label": "暂停所有任务", "id": "task.pause-all-task", "command": "application:pause-all-task" },
|
||||
{ "label": "恢复所有任务", "id": "task.resume-all-task", "command": "application:resume-all-task" },
|
||||
{ "type": "separator" },
|
||||
{ "label": "清除最近的下载记录", "id": "task.clear-recent-tasks", "command": "application:clear-recent-tasks" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "编辑",
|
||||
"id": "menu.edit",
|
||||
"submenu": [
|
||||
{ "label": "撤销", "id": "edit.undo", "role": "undo" },
|
||||
{ "label": "重做", "id": "edit.redo", "role": "redo" },
|
||||
{ "type": "separator" },
|
||||
{ "label": "剪切", "id": "edit.cut", "role": "cut" },
|
||||
{ "label": "复制", "id": "edit.copy", "role": "copy" },
|
||||
{ "label": "黏贴", "id": "edit.paste", "role": "paste" },
|
||||
{ "label": "删除", "id": "edit.delete", "role": "delete" },
|
||||
{ "label": "全选", "id": "edit.select-all", "role": "selectall" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "窗口",
|
||||
"role": "window",
|
||||
"id": "menu.window",
|
||||
"submenu": [
|
||||
{ "label": "重新加载", "id": "window.reload", "role": "reload" },
|
||||
{ "label": "关闭", "id": "window.close", "role": "close" },
|
||||
{ "label": "最小化", "id": "window.minimize", "role": "minimize" },
|
||||
{ "label": "放大", "id": "window.zoom", "role": "zoom" },
|
||||
{ "label": "进入全屏幕", "id": "window.toggle-fullscreen", "role": "togglefullscreen" },
|
||||
{ "type": "separator" },
|
||||
{ "label": "前置全部窗口", "id": "window.front", "role": "front" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "帮助",
|
||||
"role": "help",
|
||||
"id": "menu.help",
|
||||
"submenu": [
|
||||
{ "label": "Motrix 官网", "id": "help.official-website", "command": "help:official-website" },
|
||||
{ "label": "使用手册", "id": "help.manual", "command": "help:manual" },
|
||||
{ "label": "发行说明...", "id": "help.release-notes", "command": "help:release-notes" },
|
||||
{ "type": "separator" },
|
||||
{ "label": "报告问题", "id": "help.report-problem", "command": "help:report-problem" },
|
||||
{ "type": "separator" },
|
||||
{ "label": "开发者工具", "id": "help.toggle-dev-tools", "role": "toggledevtools" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import is from 'electron-is'
|
||||
import { EventEmitter } from 'events'
|
||||
import { app } from 'electron'
|
||||
|
||||
import {
|
||||
APP_RUN_MODE
|
||||
} from '@shared/constants'
|
||||
|
||||
const isMac = is.macOS()
|
||||
|
||||
export default class DockManager extends EventEmitter {
|
||||
constructor (options) {
|
||||
super()
|
||||
this.options = options
|
||||
const { runMode } = this.options
|
||||
if (runMode !== APP_RUN_MODE.STANDARD) {
|
||||
this.hide()
|
||||
}
|
||||
}
|
||||
|
||||
show = isMac ? () => {
|
||||
return app.dock.show()
|
||||
} : () => {}
|
||||
|
||||
hide = isMac ? () => {
|
||||
app.dock.hide()
|
||||
} : () => {}
|
||||
|
||||
setBadge = isMac ? (text) => {
|
||||
app.dock.setBadge(text)
|
||||
} : (text) => {}
|
||||
|
||||
openDock = isMac ? (path) => {
|
||||
app.dock.downloadFinished(path)
|
||||
} : (path) => {}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import resources from '@shared/locales/app'
|
||||
import LocaleManager from '@shared/locales/LocaleManager'
|
||||
|
||||
const localeManager = new LocaleManager({
|
||||
resources
|
||||
})
|
||||
|
||||
export function getLocaleManager () {
|
||||
return localeManager
|
||||
}
|
||||
|
||||
export function setupLocaleManager (locale) {
|
||||
localeManager.changeLanguageByLocale(locale)
|
||||
|
||||
return localeManager
|
||||
}
|
||||
|
||||
export function getI18n () {
|
||||
return localeManager.getI18n()
|
||||
}
|
||||
|
||||
export function getI18nTranslator () {
|
||||
return localeManager.getI18n().t
|
||||
}
|
||||
@@ -1,55 +1,57 @@
|
||||
import { EventEmitter } from 'events'
|
||||
import { Menu } from 'electron'
|
||||
|
||||
import {
|
||||
translateTemplate,
|
||||
flattenMenuItems,
|
||||
updateStates
|
||||
} from '../utils/menu'
|
||||
import keymap from '@shared/keymap'
|
||||
import { getI18n } from '@/ui/Locale'
|
||||
|
||||
export default class MenuManager extends EventEmitter {
|
||||
constructor (options) {
|
||||
super()
|
||||
this.options = options
|
||||
this.i18n = getI18n()
|
||||
|
||||
this.keymap = keymap
|
||||
this.template = []
|
||||
|
||||
this.menu = null
|
||||
this.items = {}
|
||||
|
||||
this.load()
|
||||
|
||||
this.setup()
|
||||
}
|
||||
|
||||
load (locale = 'en-US') {
|
||||
let template = null
|
||||
try {
|
||||
template = require(`../menus/${locale}/${process.platform}.json`)
|
||||
if (!template) {
|
||||
template = require(`../menus/en-US/${process.platform}.json`)
|
||||
}
|
||||
} catch (err) {
|
||||
template = require(`../menus/en-US/${process.platform}.json`)
|
||||
}
|
||||
this.template = template['menu']
|
||||
load () {
|
||||
const template = require(`../menus/${process.platform}.json`)
|
||||
this.template = template.menu
|
||||
}
|
||||
|
||||
build () {
|
||||
const keystrokesByCommand = {}
|
||||
for (let item in this.keymap) {
|
||||
for (const item in this.keymap) {
|
||||
keystrokesByCommand[this.keymap[item]] = item
|
||||
}
|
||||
|
||||
const tpl = translateTemplate(this.template, keystrokesByCommand)
|
||||
this.menu = Menu.buildFromTemplate(tpl)
|
||||
// Deepclone the menu template to refresh menu
|
||||
const template = JSON.parse(JSON.stringify(this.template))
|
||||
const tpl = translateTemplate(template, keystrokesByCommand, this.i18n)
|
||||
const menu = Menu.buildFromTemplate(tpl)
|
||||
return menu
|
||||
}
|
||||
|
||||
setup (locale) {
|
||||
this.load(locale)
|
||||
this.build()
|
||||
Menu.setApplicationMenu(this.menu)
|
||||
this.items = flattenMenuItems(this.menu)
|
||||
setup () {
|
||||
const menu = this.build()
|
||||
Menu.setApplicationMenu(menu)
|
||||
this.items = flattenMenuItems(menu)
|
||||
}
|
||||
|
||||
updateStates (visibleStates, enabledStates, checkedStates) {
|
||||
rebuild () {
|
||||
this.setup()
|
||||
}
|
||||
|
||||
updateMenuStates (visibleStates, enabledStates, checkedStates) {
|
||||
updateStates(this.items, visibleStates, enabledStates, checkedStates)
|
||||
}
|
||||
|
||||
@@ -57,13 +59,13 @@ export default class MenuManager extends EventEmitter {
|
||||
const visibleStates = {
|
||||
[id]: flag
|
||||
}
|
||||
this.updateStates(visibleStates, null, null)
|
||||
this.updateMenuStates(visibleStates, null, null)
|
||||
}
|
||||
|
||||
updateMenuItemEnabledState (id, flag) {
|
||||
const enabledStates = {
|
||||
[id]: flag
|
||||
}
|
||||
this.updateStates(null, enabledStates, null)
|
||||
this.updateMenuStates(null, enabledStates, null)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
import { EventEmitter } from 'events'
|
||||
import { nativeTheme, systemPreferences } from 'electron'
|
||||
|
||||
import is from 'electron-is'
|
||||
import { APP_THEME } from '@shared/constants'
|
||||
|
||||
export default class ThemeManager extends EventEmitter {
|
||||
constructor (options = {}) {
|
||||
super()
|
||||
|
||||
this.init()
|
||||
}
|
||||
|
||||
init () {
|
||||
this.handleEvents()
|
||||
}
|
||||
|
||||
getSystemTheme () {
|
||||
let result = APP_THEME.LIGHT
|
||||
if (!is.macOS()) {
|
||||
return result
|
||||
}
|
||||
result = nativeTheme.shouldUseDarkColors ? APP_THEME.DARK : APP_THEME.LIGHT
|
||||
return result
|
||||
}
|
||||
|
||||
handleEvents () {
|
||||
if (!is.macOS()) {
|
||||
return
|
||||
}
|
||||
nativeTheme.on('updated', () => {
|
||||
const theme = this.getSystemTheme()
|
||||
this.updateAppAppearance(theme)
|
||||
this.emit('system-theme-changed', theme)
|
||||
})
|
||||
}
|
||||
|
||||
updateAppAppearance (theme) {
|
||||
if (!is.macOS() || theme !== APP_THEME.LIGHT || theme !== APP_THEME.DARK) {
|
||||
return
|
||||
}
|
||||
systemPreferences.setAppLevelAppearance(theme)
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import { EventEmitter } from 'events'
|
||||
import { join } from 'path'
|
||||
import { TouchBar, nativeImage } from 'electron'
|
||||
|
||||
import { handleCommand } from '../utils/menu'
|
||||
import logger from '../core/Logger'
|
||||
|
||||
@@ -15,7 +16,7 @@ export default class TouchBarManager extends EventEmitter {
|
||||
}
|
||||
|
||||
load () {
|
||||
this.template = require(`../menus/touchBar.json`)
|
||||
this.template = require('../menus/touchBar.json')
|
||||
}
|
||||
|
||||
getClickFn (item) {
|
||||
@@ -41,30 +42,32 @@ export default class TouchBarManager extends EventEmitter {
|
||||
const { label, backgroundColor, textColor, size } = options
|
||||
|
||||
switch (type) {
|
||||
case 'button':
|
||||
const icon = this.getIconImage(options.icon)
|
||||
const click = this.getClickFn(options)
|
||||
result = new TouchBarButton({
|
||||
label,
|
||||
backgroundColor,
|
||||
icon,
|
||||
click
|
||||
case 'button':
|
||||
result = new TouchBarButton({
|
||||
label,
|
||||
backgroundColor,
|
||||
icon: this.getIconImage(options.icon),
|
||||
click: this.getClickFn(options)
|
||||
})
|
||||
break
|
||||
case 'label':
|
||||
result = new TouchBarLabel({
|
||||
label,
|
||||
textColor
|
||||
})
|
||||
break
|
||||
case 'spacer':
|
||||
result = new TouchBarSpacer({ size })
|
||||
break
|
||||
case 'group':
|
||||
result = new TouchBarGroup({
|
||||
items: new TouchBar({
|
||||
items: options.items
|
||||
})
|
||||
break
|
||||
case 'label':
|
||||
result = new TouchBarLabel({
|
||||
label,
|
||||
textColor
|
||||
})
|
||||
break
|
||||
case 'spacer':
|
||||
result = new TouchBarSpacer({ size })
|
||||
break
|
||||
case 'group':
|
||||
result = new TouchBarGroup({ items: options.items })
|
||||
break
|
||||
default:
|
||||
result = null
|
||||
})
|
||||
break
|
||||
default:
|
||||
result = null
|
||||
}
|
||||
|
||||
return result
|
||||
@@ -90,7 +93,7 @@ export default class TouchBarManager extends EventEmitter {
|
||||
if (!bar) {
|
||||
try {
|
||||
const items = this.build(this.template)
|
||||
bar = new TouchBar(items)
|
||||
bar = new TouchBar({ items })
|
||||
this.bars[page] = bar
|
||||
} catch (e) {
|
||||
logger.info('getTouchBarByPage fail', e)
|
||||
|
||||
@@ -1,3 +1,150 @@
|
||||
export default class TrayManager {
|
||||
import { EventEmitter } from 'events'
|
||||
import { join } from 'path'
|
||||
import { Tray, Menu, nativeTheme } from 'electron'
|
||||
import is from 'electron-is'
|
||||
|
||||
import {
|
||||
translateTemplate,
|
||||
flattenMenuItems,
|
||||
updateStates
|
||||
} from '../utils/menu'
|
||||
import { getI18n } from '@/ui/Locale'
|
||||
import { APP_THEME } from '@shared/constants'
|
||||
|
||||
let tray = null
|
||||
|
||||
export default class TrayManager extends EventEmitter {
|
||||
constructor (options = {}) {
|
||||
super()
|
||||
|
||||
this.theme = options.theme || APP_THEME.AUTO
|
||||
this.i18n = getI18n()
|
||||
this.menu = null
|
||||
|
||||
this.load()
|
||||
this.init()
|
||||
this.setup()
|
||||
this.handleEvents()
|
||||
}
|
||||
|
||||
load () {
|
||||
this.template = require('../menus/tray.json')
|
||||
|
||||
let theme = APP_THEME.LIGHT
|
||||
|
||||
if (is.windows()) {
|
||||
theme = 'colorful'
|
||||
} else if (is.macOS()) {
|
||||
theme = nativeTheme.shouldUseDarkColors ? APP_THEME.DARK : APP_THEME.LIGHT
|
||||
} else if (is.linux()) {
|
||||
theme = (this.theme === APP_THEME.AUTO) ? APP_THEME.DARK : this.theme
|
||||
}
|
||||
|
||||
this.setIcons(theme)
|
||||
}
|
||||
|
||||
setIcons (theme) {
|
||||
this.normalIcon = join(__static, `./mo-tray-${theme}-normal.png`)
|
||||
this.activeIcon = join(__static, `./mo-tray-${theme}-active.png`)
|
||||
}
|
||||
|
||||
build () {
|
||||
const keystrokesByCommand = {}
|
||||
for (const item in this.keymap) {
|
||||
keystrokesByCommand[this.keymap[item]] = item
|
||||
}
|
||||
|
||||
// Deepclone the menu template to refresh menu
|
||||
const template = JSON.parse(JSON.stringify(this.template))
|
||||
const tpl = translateTemplate(template, keystrokesByCommand, this.i18n)
|
||||
this.menu = Menu.buildFromTemplate(tpl)
|
||||
this.items = flattenMenuItems(this.menu)
|
||||
}
|
||||
|
||||
setup () {
|
||||
this.build()
|
||||
|
||||
/**
|
||||
* Linux requires setContextMenu to be called
|
||||
* in order for the context menu to populate correctly
|
||||
*/
|
||||
if (process.platform === 'linux') {
|
||||
tray.setContextMenu(this.menu)
|
||||
}
|
||||
}
|
||||
|
||||
init () {
|
||||
tray = new Tray(this.normalIcon)
|
||||
tray.setToolTip('Motrix')
|
||||
}
|
||||
|
||||
handleEvents () {
|
||||
tray.on('click', this.handleTrayClick)
|
||||
tray.on('double-click', this.handleTrayDbClick)
|
||||
tray.on('right-click', this.handleTrayRightClick)
|
||||
|
||||
tray.on('drop-files', this.handleTrayDropFile)
|
||||
}
|
||||
|
||||
handleTrayClick = (event) => {
|
||||
event.preventDefault()
|
||||
global.application.toggle()
|
||||
}
|
||||
|
||||
handleTrayDbClick = (event) => {
|
||||
event.preventDefault()
|
||||
global.application.show()
|
||||
}
|
||||
|
||||
handleTrayRightClick = (event) => {
|
||||
event.preventDefault()
|
||||
tray.popUpContextMenu(this.menu)
|
||||
}
|
||||
|
||||
handleTrayDropFile = (event, files) => {
|
||||
global.application.show()
|
||||
global.application.handleFile(files[0])
|
||||
}
|
||||
|
||||
updateTrayByStatus (status) {
|
||||
this.status = status
|
||||
this.updateTray()
|
||||
}
|
||||
|
||||
updateTray () {
|
||||
const icon = this.status ? this.activeIcon : this.normalIcon
|
||||
tray.setImage(icon)
|
||||
}
|
||||
|
||||
changeIconTheme (theme = APP_THEME.LIGHT) {
|
||||
if (!is.macOS()) {
|
||||
return
|
||||
}
|
||||
|
||||
this.setIcons(theme)
|
||||
|
||||
this.updateTray()
|
||||
}
|
||||
|
||||
updateMenuStates (visibleStates, enabledStates, checkedStates) {
|
||||
updateStates(this.items, visibleStates, enabledStates, checkedStates)
|
||||
}
|
||||
|
||||
updateMenuItemVisibleState (id, flag) {
|
||||
const visibleStates = {
|
||||
[id]: flag
|
||||
}
|
||||
this.updateMenuStates(visibleStates, null, null)
|
||||
}
|
||||
|
||||
updateMenuItemEnabledState (id, flag) {
|
||||
const enabledStates = {
|
||||
[id]: flag
|
||||
}
|
||||
this.updateMenuStates(null, enabledStates, null)
|
||||
}
|
||||
|
||||
destroy () {
|
||||
tray.destroy()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,20 @@
|
||||
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,
|
||||
webviewTag: true
|
||||
}
|
||||
}
|
||||
|
||||
export default class WindowManager extends EventEmitter {
|
||||
@@ -19,9 +26,9 @@ export default class WindowManager extends EventEmitter {
|
||||
|
||||
this.willQuit = false
|
||||
|
||||
app.on('before-quit', () => {
|
||||
this.setWillQuit(true)
|
||||
})
|
||||
this.handleBeforeQuit()
|
||||
|
||||
this.handleAllWindowClosed()
|
||||
}
|
||||
|
||||
setWillQuit (flag) {
|
||||
@@ -32,46 +39,82 @@ export default class WindowManager extends EventEmitter {
|
||||
const result = pageConfig[page] || {}
|
||||
const hideAppMenu = this.userConfig['hide-app-menu']
|
||||
if (hideAppMenu) {
|
||||
result.frame = false
|
||||
result.attrs.frame = false
|
||||
}
|
||||
|
||||
// Optimized for small screen users
|
||||
const { width, height } = screen.getPrimaryDisplay().workAreaSize
|
||||
const widthScale = width >= 1280 ? 1 : 0.875
|
||||
const heightScale = height >= 800 ? 1 : 0.875
|
||||
result.attrs.width *= widthScale
|
||||
result.attrs.height *= heightScale
|
||||
|
||||
// fix AppImage Dock Icon Missing
|
||||
// https://github.com/AppImage/AppImageKit/wiki/Bundling-Electron-apps
|
||||
if (is.linux()) {
|
||||
result.attrs.icon = join(__static, './512x512.png')
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
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
|
||||
const autoHideWindow = this.userConfig['auto-hide-window']
|
||||
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)
|
||||
if (bounds) {
|
||||
window.setBounds(bounds)
|
||||
}
|
||||
|
||||
window.webContents.on('new-window', (e, url) => {
|
||||
e.preventDefault()
|
||||
shell.openExternal(url)
|
||||
})
|
||||
|
||||
if (options.url) {
|
||||
window.loadURL(options.url)
|
||||
if (pageOptions.url) {
|
||||
window.loadURL(pageOptions.url)
|
||||
}
|
||||
|
||||
window.once('ready-to-show', () => {
|
||||
window.show()
|
||||
if (!hidden) {
|
||||
window.show()
|
||||
}
|
||||
})
|
||||
|
||||
if (options.bindCloseToHide && process.platform === 'darwin') {
|
||||
this.bindCloseToHide(page, window)
|
||||
}
|
||||
this.handleWindowState(page, window)
|
||||
|
||||
this.handleWindowClose(pageOptions, page, window)
|
||||
|
||||
this.bindAfterClosed(page, window)
|
||||
|
||||
this.addWindow(page, window)
|
||||
if (autoHideWindow) {
|
||||
this.handleWindowBlur()
|
||||
}
|
||||
return window
|
||||
}
|
||||
|
||||
@@ -94,6 +137,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()
|
||||
}
|
||||
|
||||
@@ -107,24 +153,96 @@ export default class WindowManager extends EventEmitter {
|
||||
})
|
||||
}
|
||||
|
||||
bindCloseToHide (page, window) {
|
||||
handleWindowState (page, window) {
|
||||
window.on('resize', debounce(() => {
|
||||
const bounds = window.getBounds()
|
||||
this.emit('window-resized', { page, bounds })
|
||||
}, 500))
|
||||
|
||||
window.on('move', debounce(() => {
|
||||
const bounds = window.getBounds()
|
||||
this.emit('window-moved', { page, bounds })
|
||||
}, 500))
|
||||
}
|
||||
|
||||
handleWindowClose (pageOptions, page, window) {
|
||||
window.on('close', (event) => {
|
||||
if (!this.willQuit) {
|
||||
if (pageOptions.bindCloseToHide && !this.willQuit) {
|
||||
event.preventDefault()
|
||||
window.hide()
|
||||
}
|
||||
const bounds = window.getBounds()
|
||||
this.emit('window-closed', { page, bounds })
|
||||
})
|
||||
}
|
||||
|
||||
showWindow (page) {
|
||||
const window = this.getWindow(page)
|
||||
if (!window) {
|
||||
return
|
||||
}
|
||||
window.show()
|
||||
}
|
||||
|
||||
autoHideWindow (page) {
|
||||
const window = this.getWindow(page)
|
||||
if (!window) {
|
||||
return
|
||||
}
|
||||
window.hide()
|
||||
}
|
||||
|
||||
hideAllWindow () {
|
||||
this.getWindowList().forEach((window) => {
|
||||
window.hide()
|
||||
})
|
||||
}
|
||||
|
||||
toggleWindow (page) {
|
||||
const window = this.getWindow(page)
|
||||
if (!window) {
|
||||
return
|
||||
}
|
||||
if (window.isVisible()) {
|
||||
window.hide()
|
||||
} else {
|
||||
window.show()
|
||||
}
|
||||
}
|
||||
|
||||
getFocusedWindow () {
|
||||
return BrowserWindow.getFocusedWindow()
|
||||
}
|
||||
|
||||
handleBeforeQuit () {
|
||||
app.on('before-quit', () => {
|
||||
this.setWillQuit(true)
|
||||
})
|
||||
}
|
||||
|
||||
onWindowBlur (event, window) {
|
||||
window.hide()
|
||||
}
|
||||
|
||||
handleWindowBlur () {
|
||||
app.on('browser-window-blur', this.onWindowBlur)
|
||||
}
|
||||
|
||||
unbindWindowBlur () {
|
||||
app.removeListener('browser-window-blur', this.onWindowBlur)
|
||||
}
|
||||
|
||||
handleAllWindowClosed () {
|
||||
app.on('window-all-closed', (event) => {
|
||||
event.preventDefault()
|
||||
})
|
||||
}
|
||||
|
||||
sendCommandTo (window, command, ...args) {
|
||||
if (!window) {
|
||||
return
|
||||
}
|
||||
console.log('sendCommandTo====>', window, command, ...args)
|
||||
logger.info('[Motrix] send command to:', command, ...args)
|
||||
window.webContents.send('command', command, ...args)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
import { app } from 'electron'
|
||||
import is from 'electron-is'
|
||||
import { resolve } from 'path'
|
||||
import { existsSync, lstatSync } from 'fs'
|
||||
|
||||
import {
|
||||
ENGINE_MAX_CONNECTION_PER_SERVER,
|
||||
IP_VERSION
|
||||
} from '@shared/constants'
|
||||
import logger from '../core/Logger'
|
||||
import engineBinMap from '../configs/engine'
|
||||
|
||||
@@ -8,6 +14,11 @@ export function getLogPath () {
|
||||
return logger.transports.file.file
|
||||
}
|
||||
|
||||
export function getDhtPath (protocol) {
|
||||
const name = protocol === IP_VERSION.V6 ? 'dht6.dat' : 'dht.dat'
|
||||
return resolve(app.getPath('userData'), `./${name}`)
|
||||
}
|
||||
|
||||
export function getSessionPath () {
|
||||
return resolve(app.getPath('userData'), './download.session')
|
||||
}
|
||||
@@ -21,12 +32,12 @@ export function getUserDownloadsPath () {
|
||||
}
|
||||
|
||||
export function getEngineBin (platform) {
|
||||
let result = engineBinMap.hasOwnProperty(platform) ? engineBinMap[platform] : ''
|
||||
const result = engineBinMap[platform] || ''
|
||||
return result
|
||||
}
|
||||
|
||||
export function transformConfig (config) {
|
||||
let result = []
|
||||
const result = []
|
||||
for (const [k, v] of Object.entries(config)) {
|
||||
if (v !== '') {
|
||||
result.push(`--${k}=${v}`)
|
||||
@@ -44,17 +55,81 @@ export function isRunningInDmg () {
|
||||
return result
|
||||
}
|
||||
|
||||
export function moveAppToApplicationsFolder () {
|
||||
export function moveAppToApplicationsFolder (errorMsg = '') {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
const result = app.moveToApplicationsFolder()
|
||||
if (result) {
|
||||
resolve(result)
|
||||
} else {
|
||||
reject(new Error('应用程序移动失败'))
|
||||
reject(new Error(errorMsg))
|
||||
}
|
||||
} catch (err) {
|
||||
reject(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export function splitArgv (argv) {
|
||||
const args = []
|
||||
const extra = {}
|
||||
for (const arg of argv) {
|
||||
if (arg.startsWith('--')) {
|
||||
const kv = arg.split('=')
|
||||
const key = kv[0]
|
||||
const value = kv[1] || '1'
|
||||
extra[key] = value
|
||||
continue
|
||||
}
|
||||
args.push(arg)
|
||||
}
|
||||
return { args, extra }
|
||||
}
|
||||
|
||||
export function parseArgvAsUrl (argv) {
|
||||
const arg = argv[1]
|
||||
if (!arg) {
|
||||
return
|
||||
}
|
||||
|
||||
if (checkIsSupportedSchema(arg)) {
|
||||
return arg
|
||||
}
|
||||
}
|
||||
|
||||
export function checkIsSupportedSchema (url = '') {
|
||||
const str = url.toLowerCase()
|
||||
if (
|
||||
str.startsWith('mo:') ||
|
||||
str.startsWith('motrix:') ||
|
||||
str.startsWith('http:') ||
|
||||
str.startsWith('https:') ||
|
||||
str.startsWith('ftp:') ||
|
||||
str.startsWith('magnet:') ||
|
||||
str.startsWith('thunder:')
|
||||
) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
export function isDirectory (path) {
|
||||
return existsSync(path) && lstatSync(path).isDirectory()
|
||||
}
|
||||
|
||||
export function parseArgvAsFile (argv) {
|
||||
let arg = argv[1]
|
||||
if (!arg || isDirectory(arg)) {
|
||||
return
|
||||
}
|
||||
|
||||
if (is.linux()) {
|
||||
arg = arg.replace('file://', '')
|
||||
}
|
||||
return arg
|
||||
}
|
||||
|
||||
export const getMaxConnectionPerServer = () => {
|
||||
return ENGINE_MAX_CONNECTION_PER_SERVER
|
||||
}
|
||||
|
||||
@@ -3,31 +3,31 @@ export function concat (template, submenu, submenuToAdd) {
|
||||
let relativeItem = null
|
||||
if (sub.position) {
|
||||
switch (sub.position) {
|
||||
case 'first':
|
||||
submenu.unshift(sub)
|
||||
break
|
||||
case 'last':
|
||||
submenu.push(sub)
|
||||
break
|
||||
case 'before':
|
||||
relativeItem = findById(template, sub['relative-id'])
|
||||
if (relativeItem) {
|
||||
let array = relativeItem.__parent
|
||||
let index = array.indexOf(relativeItem)
|
||||
array.splice(index, 0, sub)
|
||||
}
|
||||
break
|
||||
case 'after':
|
||||
relativeItem = findById(template, sub['relative-id'])
|
||||
if (relativeItem) {
|
||||
let array = relativeItem.__parent
|
||||
let index = array.indexOf(relativeItem)
|
||||
array.splice(index + 1, 0, sub)
|
||||
}
|
||||
break
|
||||
default:
|
||||
submenu.push(sub)
|
||||
break
|
||||
case 'first':
|
||||
submenu.unshift(sub)
|
||||
break
|
||||
case 'last':
|
||||
submenu.push(sub)
|
||||
break
|
||||
case 'before':
|
||||
relativeItem = findById(template, sub['relative-id'])
|
||||
if (relativeItem) {
|
||||
const array = relativeItem.__parent
|
||||
const index = array.indexOf(relativeItem)
|
||||
array.splice(index, 0, sub)
|
||||
}
|
||||
break
|
||||
case 'after':
|
||||
relativeItem = findById(template, sub['relative-id'])
|
||||
if (relativeItem) {
|
||||
const array = relativeItem.__parent
|
||||
const index = array.indexOf(relativeItem)
|
||||
array.splice(index + 1, 0, sub)
|
||||
}
|
||||
break
|
||||
default:
|
||||
submenu.push(sub)
|
||||
break
|
||||
}
|
||||
} else {
|
||||
submenu.push(sub)
|
||||
@@ -37,7 +37,7 @@ export function concat (template, submenu, submenuToAdd) {
|
||||
|
||||
export function merge (template, item) {
|
||||
if (item.id) {
|
||||
let matched = findById(template, item.id)
|
||||
const matched = findById(template, item.id)
|
||||
if (matched) {
|
||||
if (item.submenu && Array.isArray(item.submenu)) {
|
||||
if (!Array.isArray(matched.submenu)) {
|
||||
@@ -54,15 +54,15 @@ export function merge (template, item) {
|
||||
}
|
||||
|
||||
function findById (template, id) {
|
||||
for (let i in template) {
|
||||
let item = template[i]
|
||||
for (const i in template) {
|
||||
const item = template[i]
|
||||
if (item.id === id) {
|
||||
// Returned item need to have a reference to parent Array (.__parent).
|
||||
// This is required to handle `position` and `relative-id`
|
||||
item.__parent = template
|
||||
return item
|
||||
} else if (Array.isArray(item.submenu)) {
|
||||
let result = findById(item.submenu, id)
|
||||
const result = findById(item.submenu, id)
|
||||
if (result) {
|
||||
return result
|
||||
}
|
||||
@@ -71,19 +71,29 @@ function findById (template, id) {
|
||||
return null
|
||||
}
|
||||
|
||||
export function translateTemplate (template, keystrokesByCommand) {
|
||||
for (let i in template) {
|
||||
let item = template[i]
|
||||
export function translateTemplate (template, keystrokesByCommand, i18n) {
|
||||
for (const i in template) {
|
||||
const item = template[i]
|
||||
if (item.command) {
|
||||
item.accelerator = acceleratorForCommand(item.command, keystrokesByCommand)
|
||||
}
|
||||
|
||||
// If label is specified, label is used as the key of i18n.t(key),
|
||||
// which mainly solves the inaccurate translation of item.id.
|
||||
if (i18n) {
|
||||
if (item.label) {
|
||||
item.label = i18n.t(item.label)
|
||||
} else if (item.id) {
|
||||
item.label = i18n.t(item.id)
|
||||
}
|
||||
}
|
||||
|
||||
item.click = () => {
|
||||
console.log('click sendCommand', item)
|
||||
handleCommand(item)
|
||||
}
|
||||
|
||||
if (item.submenu) {
|
||||
translateTemplate(item.submenu, keystrokesByCommand)
|
||||
translateTemplate(item.submenu, keystrokesByCommand, i18n)
|
||||
}
|
||||
}
|
||||
return template
|
||||
@@ -96,36 +106,32 @@ export function handleCommand (item) {
|
||||
? [item.command, item['command-arg']]
|
||||
: [item.command]
|
||||
|
||||
global.application.sendCommand(...args)
|
||||
global.application.sendCommandToAll(...args)
|
||||
|
||||
handleCommandAfter(item)
|
||||
}
|
||||
|
||||
function handleCommandBefore (item) {
|
||||
console.log('handleCommandBefore==1=>', item)
|
||||
if (!item['command-before']) {
|
||||
return
|
||||
}
|
||||
const [ command, ...args ] = item['command-before'].split(',')
|
||||
console.log('handleCommandBefore==2=>', command, ...args)
|
||||
global.application.sendCommand(command, ...args)
|
||||
const [command, ...args] = item['command-before'].split(',')
|
||||
global.application.sendCommandToAll(command, ...args)
|
||||
}
|
||||
|
||||
function handleCommandAfter (item) {
|
||||
console.log('handleCommandAfter==1=>', item)
|
||||
if (!item['command-after']) {
|
||||
return
|
||||
}
|
||||
const [ command, ...args ] = item['command-after'].split(',')
|
||||
console.log('handleCommandAfter==2=>', command, ...args)
|
||||
global.application.sendCommand(command, ...args)
|
||||
const [command, ...args] = item['command-after'].split(',')
|
||||
global.application.sendCommandToAll(command, ...args)
|
||||
}
|
||||
|
||||
function acceleratorForCommand (command, keystrokesByCommand) {
|
||||
const keystroke = keystrokesByCommand[command]
|
||||
if (keystroke) {
|
||||
let modifiers = keystroke.split(/-(?=.)/)
|
||||
let key = modifiers.pop().toUpperCase()
|
||||
const key = modifiers.pop().toUpperCase()
|
||||
.replace('+', 'Plus')
|
||||
.replace('MINUS', '-')
|
||||
modifiers = modifiers.map((modifier) => {
|
||||
@@ -142,14 +148,14 @@ function acceleratorForCommand (command, keystrokesByCommand) {
|
||||
.replace(/alt/ig, 'Alt')
|
||||
}
|
||||
})
|
||||
let keys = modifiers.concat([key])
|
||||
const keys = modifiers.concat([key])
|
||||
return keys.join('+')
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
export function flattenMenuItems (menu) {
|
||||
let flattenItems = {}
|
||||
const flattenItems = {}
|
||||
menu.items.forEach(item => {
|
||||
if (item.id) {
|
||||
flattenItems[item.id] = item
|
||||
@@ -163,24 +169,24 @@ export function flattenMenuItems (menu) {
|
||||
|
||||
export function updateStates (itemsById, visibleStates, enabledStates, checkedStates) {
|
||||
if (visibleStates) {
|
||||
for (let command in visibleStates) {
|
||||
let item = itemsById[command]
|
||||
for (const command in visibleStates) {
|
||||
const item = itemsById[command]
|
||||
if (item) {
|
||||
item.visible = visibleStates[command]
|
||||
}
|
||||
}
|
||||
}
|
||||
if (enabledStates) {
|
||||
for (let command in enabledStates) {
|
||||
let item = itemsById[command]
|
||||
for (const command in enabledStates) {
|
||||
const item = itemsById[command]
|
||||
if (item) {
|
||||
item.enabled = enabledStates[command]
|
||||
}
|
||||
}
|
||||
}
|
||||
if (checkedStates) {
|
||||
for (let id in checkedStates) {
|
||||
let item = itemsById[id]
|
||||
for (const id in checkedStates) {
|
||||
const item = itemsById[id]
|
||||
if (item) {
|
||||
item.checked = checkedStates[id]
|
||||
}
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
import { remote } from 'electron'
|
||||
import { ipcRenderer, remote } from 'electron'
|
||||
import is from 'electron-is'
|
||||
import { isEmpty } from 'lodash'
|
||||
import Aria2 from 'aria2'
|
||||
import {
|
||||
separateConfig,
|
||||
compactUndefined,
|
||||
formatOptionsForEngine,
|
||||
mergeTaskResult,
|
||||
changeKeysToCamelCase,
|
||||
changeKeysToKebabCase
|
||||
} from '@shared/utils'
|
||||
import { ENGINE_RPC_HOST } from '@shared/constants'
|
||||
|
||||
const application = remote.getGlobal('application')
|
||||
|
||||
@@ -53,7 +55,9 @@ export default class Api {
|
||||
rpcListenPort: port,
|
||||
rpcSecret: secret
|
||||
} = this.config
|
||||
const host = ENGINE_RPC_HOST
|
||||
this.client = new Aria2({
|
||||
host,
|
||||
port,
|
||||
secret
|
||||
})
|
||||
@@ -80,9 +84,9 @@ export default class Api {
|
||||
savePreference (params = {}) {
|
||||
const kebabParams = changeKeysToKebabCase(params)
|
||||
if (is.renderer()) {
|
||||
this.savePreferenceToNativeStore(kebabParams)
|
||||
return this.savePreferenceToNativeStore(kebabParams)
|
||||
} else {
|
||||
this.savePreferenceToLocalStorage(kebabParams)
|
||||
return this.savePreferenceToLocalStorage(kebabParams)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,25 +96,79 @@ export default class Api {
|
||||
|
||||
savePreferenceToNativeStore (params = {}) {
|
||||
const { user, system, others } = separateConfig(params)
|
||||
if (!isEmpty(system)) {
|
||||
console.info('[Motrix] save system config: ', system)
|
||||
application.configManager.setSystemConfig(system)
|
||||
}
|
||||
const config = {}
|
||||
|
||||
if (!isEmpty(user)) {
|
||||
console.info('[Motrix] save user config: ', user)
|
||||
application.configManager.setUserConfig(user)
|
||||
config.user = user
|
||||
}
|
||||
|
||||
if (!isEmpty(system)) {
|
||||
console.info('[Motrix] save system config: ', system)
|
||||
config.system = system
|
||||
this.updateActiveTaskOption(system)
|
||||
}
|
||||
|
||||
if (!isEmpty(others)) {
|
||||
console.info('[Motrix] save config found iillegal key: ', others)
|
||||
console.info('[Motrix] save config found illegal key: ', others)
|
||||
}
|
||||
|
||||
ipcRenderer.send('command', 'application:save-preference', config)
|
||||
}
|
||||
|
||||
getVersion () {
|
||||
return this.client.call('getVersion')
|
||||
}
|
||||
|
||||
changeGlobalOption (options) {
|
||||
const args = formatOptionsForEngine(options)
|
||||
|
||||
return this.client.call('changeGlobalOption', args)
|
||||
}
|
||||
|
||||
getGlobalOption () {
|
||||
return new Promise((resolve) => {
|
||||
this.client.call('getGlobalOption')
|
||||
.then((data) => {
|
||||
resolve(changeKeysToCamelCase(data))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
getOption (params = {}) {
|
||||
const { gid } = params
|
||||
const args = compactUndefined([gid])
|
||||
|
||||
return new Promise((resolve) => {
|
||||
this.client.call('getOption', ...args)
|
||||
.then((data) => {
|
||||
resolve(changeKeysToCamelCase(data))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
updateActiveTaskOption (options) {
|
||||
this.fetchTaskList({ type: 'active' })
|
||||
.then((data) => {
|
||||
if (isEmpty(data)) {
|
||||
return
|
||||
}
|
||||
|
||||
const gids = data.map((task) => task.gid)
|
||||
this.batchChangeOption({ gids, options })
|
||||
})
|
||||
}
|
||||
|
||||
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')
|
||||
}
|
||||
@@ -118,11 +176,16 @@ export default class Api {
|
||||
addUri (params) {
|
||||
const {
|
||||
uris,
|
||||
outs,
|
||||
options
|
||||
} = params
|
||||
const tasks = uris.map((uri) => {
|
||||
const args = compactUndefined([[uri], options])
|
||||
return [ 'aria2.addUri', ...args ]
|
||||
const tasks = uris.map((uri, index) => {
|
||||
const kebabOptions = changeKeysToKebabCase(options)
|
||||
if (outs && outs[index]) {
|
||||
kebabOptions.out = outs[index]
|
||||
}
|
||||
const args = compactUndefined([[uri], kebabOptions])
|
||||
return ['aria2.addUri', ...args]
|
||||
})
|
||||
return this.client.multicall(tasks)
|
||||
}
|
||||
@@ -132,7 +195,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)
|
||||
}
|
||||
|
||||
@@ -141,37 +205,38 @@ export default class Api {
|
||||
metalink,
|
||||
options
|
||||
} = params
|
||||
const args = compactUndefined([metalink, options])
|
||||
const kebabOptions = changeKeysToKebabCase(options)
|
||||
const args = compactUndefined([metalink, kebabOptions])
|
||||
return this.client.call('addMetalink', ...args)
|
||||
}
|
||||
|
||||
fetchDownloadingTaskList (params = {}) {
|
||||
const { offset = 0, num = 200, keys } = params
|
||||
const { offset = 0, num = 20, keys } = params
|
||||
const activeArgs = compactUndefined([keys])
|
||||
const waitingArgs = compactUndefined([offset, num, keys])
|
||||
return new Promise((resolve, reject) => {
|
||||
this.client.multicall([
|
||||
[ 'aria2.tellActive', ...activeArgs ],
|
||||
[ 'aria2.tellWaiting', ...waitingArgs ]
|
||||
['aria2.tellActive', ...activeArgs],
|
||||
['aria2.tellWaiting', ...waitingArgs]
|
||||
]).then((data) => {
|
||||
console.log('fetchDownloadingTaskList data', data)
|
||||
console.log('[Motrix] fetch downloading task list data:', data)
|
||||
const result = mergeTaskResult(data)
|
||||
resolve(result)
|
||||
}).catch((err) => {
|
||||
console.log('fetchDownloadingTaskList fail===>', err)
|
||||
console.log('[Motrix] fetch downloading task list fail:', err)
|
||||
reject(err)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fetchWaitingTaskList (params = {}) {
|
||||
const { offset = 0, num = 200, keys } = params
|
||||
const { offset = 0, num = 20, keys } = params
|
||||
const args = compactUndefined([offset, num, keys])
|
||||
return this.client.call('tellWaiting', ...args)
|
||||
}
|
||||
|
||||
fetchStoppedTaskList (params = {}) {
|
||||
const { offset = 0, num = 200, keys } = params
|
||||
const { offset = 0, num = 20, keys } = params
|
||||
const args = compactUndefined([offset, num, keys])
|
||||
return this.client.call('tellStopped', ...args)
|
||||
}
|
||||
@@ -179,14 +244,14 @@ export default class Api {
|
||||
fetchTaskList (params = {}) {
|
||||
const { type } = params
|
||||
switch (type) {
|
||||
case 'active':
|
||||
return this.fetchDownloadingTaskList(params)
|
||||
case 'waiting':
|
||||
return this.fetchWaitingTaskList(params)
|
||||
case 'stopped':
|
||||
return this.fetchStoppedTaskList(params)
|
||||
default:
|
||||
return this.fetchDownloadingTaskList(params)
|
||||
case 'active':
|
||||
return this.fetchDownloadingTaskList(params)
|
||||
case 'waiting':
|
||||
return this.fetchWaitingTaskList(params)
|
||||
case 'stopped':
|
||||
return this.fetchStoppedTaskList(params)
|
||||
default:
|
||||
return this.fetchDownloadingTaskList(params)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -196,6 +261,12 @@ export default class Api {
|
||||
return this.client.call('tellStatus', ...args)
|
||||
}
|
||||
|
||||
fetchTaskItemPeers (params = {}) {
|
||||
const { gid, keys } = params
|
||||
const args = compactUndefined([gid, keys])
|
||||
return this.client.call('getPeers', ...args)
|
||||
}
|
||||
|
||||
pauseTask (params = {}) {
|
||||
const { gid } = params
|
||||
const args = compactUndefined([gid])
|
||||
@@ -252,11 +323,35 @@ export default class Api {
|
||||
return this.client.call('removeDownloadResult', ...args)
|
||||
}
|
||||
|
||||
startPowerSaveBlocker () {
|
||||
application.energyManager.startPowerSaveBlocker()
|
||||
multicall (method, params = {}) {
|
||||
let { gids, options = {} } = params
|
||||
options = formatOptionsForEngine(options)
|
||||
|
||||
const data = gids.map((gid, index) => {
|
||||
const kebabOptions = changeKeysToKebabCase(options)
|
||||
const args = compactUndefined([gid, kebabOptions])
|
||||
return [method, ...args]
|
||||
})
|
||||
return this.client.multicall(data)
|
||||
}
|
||||
|
||||
stopPowerSaveBlocker () {
|
||||
application.energyManager.stopPowerSaveBlocker()
|
||||
batchChangeOption (params = {}) {
|
||||
return this.multicall('aria2.changeOption', params)
|
||||
}
|
||||
|
||||
batchRemoveTask (params = {}) {
|
||||
return this.multicall('aria2.remove', params)
|
||||
}
|
||||
|
||||
batchResumeTask (params = {}) {
|
||||
return this.multicall('aria2.unpause', params)
|
||||
}
|
||||
|
||||
batchPauseTask (params = {}) {
|
||||
return this.multicall('aria2.pause', params)
|
||||
}
|
||||
|
||||
batchForcePauseTask (params = {}) {
|
||||
return this.multicall('aria2.forcePause', params)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
<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">
|
||||
<line data-cap="butt" data-color="color-2" fill="none" stroke-miterlimit="10" x1="12" y1="2" x2="12" y2="22"/>
|
||||
<polyline fill="none" stroke="currentColor" stroke-miterlimit="10" points="19,15 12,22 5,15 "/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 429 B |
@@ -0,0 +1,6 @@
|
||||
<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">
|
||||
<line data-cap="butt" data-color="color-2" fill="none" stroke-miterlimit="10" x1="12" y1="22" x2="12" y2="2"/>
|
||||
<polyline fill="none" stroke="currentColor" stroke-miterlimit="10" points="5,9 12,2 19,9 "/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 426 B |
@@ -0,0 +1,10 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24">
|
||||
<g stroke-linecap="round" stroke-linejoin="round" stroke-width="2" stroke="currentColor">
|
||||
<rect x="4" y="3" width="16" height="18" fill="none" stroke="currentColor" stroke-miterlimit="10"/>
|
||||
<line data-color="color-2" x1="1" y1="6" x2="1" y2="18" fill="none" stroke-miterlimit="10"/>
|
||||
<line data-color="color-2" x1="23" y1="6" x2="23" y2="18" fill="none" stroke-miterlimit="10"/>
|
||||
<polyline data-cap="butt" points="10 15 10 8 16 8 16 14" fill="none" stroke="currentColor" stroke-miterlimit="10"/>
|
||||
<circle data-stroke="none" cx="9" cy="15" r="2" stroke="none"/>
|
||||
<circle data-stroke="none" cx="15" cy="14" r="2" stroke="none"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 747 B |
@@ -0,0 +1,11 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24">
|
||||
<g stroke-linecap="round" stroke-linejoin="round" stroke-width="2" stroke="currentColor">
|
||||
<polyline data-cap="butt" fill="none" stroke="currentColor" stroke-miterlimit="10" points="17,23 17,7 1,7 "/>
|
||||
<line data-cap="butt" fill="none" stroke="currentColor" stroke-miterlimit="10" x1="17" y1="7" x2="23" y2="1"/>
|
||||
<polygon fill="none" stroke="currentColor" stroke-miterlimit="10" points="17,23 23,17 23,1 7,1 1,7 1,23 "/>
|
||||
<circle data-color="color-2" data-stroke="none" cx="12" cy="12" r="1" stroke-linejoin="miter" stroke-linecap="square" stroke="none"/>
|
||||
<circle data-color="color-2" data-stroke="none" cx="6" cy="12" r="1" stroke-linejoin="miter" stroke-linecap="square" stroke="none"/>
|
||||
<circle data-color="color-2" data-stroke="none" cx="12" cy="18" r="1" stroke-linejoin="miter" stroke-linecap="square" stroke="none"/>
|
||||
<circle data-color="color-2" data-stroke="none" cx="6" cy="18" r="1" stroke-linejoin="miter" stroke-linecap="square" stroke="none"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24">
|
||||
<g stroke-linecap="round" stroke-linejoin="round" stroke-width="2" stroke="currentColor">
|
||||
<polyline data-cap="butt" data-color="color-2" points="1 20 6 14 10 18 17 10 23 17" fill="none" stroke-miterlimit="10"/>
|
||||
<rect x="1" y="3" width="22" height="18" fill="none" stroke="currentColor" stroke-miterlimit="10"/>
|
||||
<circle data-color="color-2" cx="9" cy="8" r="2" fill="none" stroke-miterlimit="10"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 509 B |
@@ -0,0 +1,9 @@
|
||||
<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">
|
||||
<polyline data-cap="butt" fill="none" stroke-miterlimit="10" points="12,9 12,13 7.8,16.1 "/>
|
||||
<line data-cap="butt" fill="none" stroke-miterlimit="10" x1="12" y1="13" x2="16.2" y2="16.1"/>
|
||||
<circle fill="none" stroke="currentColor" stroke-miterlimit="10" cx="12" cy="5" r="4"/>
|
||||
<circle fill="none" stroke="currentColor" stroke-miterlimit="10" cx="5" cy="19" r="4"/>
|
||||
<circle fill="none" stroke="currentColor" stroke-miterlimit="10" cx="19" cy="19" r="4"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 687 B |
@@ -0,0 +1,10 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24">
|
||||
<g class="nc-icon-wrapper" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" stroke="currentColor">
|
||||
<path fill="none" stroke="currentColor" stroke-miterlimit="10" d="M3,14V4 c0-0.552,0.448-1,1-1h16c0.552,0,1,0.448,1,1v6"/>
|
||||
<path fill="none" stroke="currentColor" stroke-miterlimit="10" d="M10,18H1v0 c0,1.657,1.343,3,3,3h6"/>
|
||||
<path data-cap="butt" data-color="color-2" fill="none" stroke-miterlimit="10" d="M14.126,17 c0.444-1.725,2.01-3,3.874-3c1.48,0,2.772,0.804,3.464,1.999"/>
|
||||
<polygon data-color="color-2" data-stroke="none" points="23.22,13.649 22.792,18 18.522,17.061 " stroke-linejoin="miter" stroke-linecap="square" stroke="none"/>
|
||||
<path data-cap="butt" data-color="color-2" fill="none" stroke-miterlimit="10" d="M21.874,20 c-0.444,1.725-2.01,3-3.874,3c-1.48,0-2.772-0.804-3.464-1.999"/>
|
||||
<polygon data-color="color-2" data-stroke="none" points="12.78,23.351 13.208,19 17.478,19.939 " stroke-linejoin="miter" stroke-linecap="square" stroke="none"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24">
|
||||
<g>
|
||||
<path d="M12,0C5.383,0,0,5.383,0,12s5.383,12,12,12s12-5.383,12-12S18.617,0,12,0z M19,13h-8V5h2v6h6V13z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 214 B |
@@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24">
|
||||
<g stroke-linecap="round" stroke-linejoin="round" stroke-width="2" stroke="currentColor">
|
||||
<path data-cap="butt" fill="none" stroke="currentColor" stroke-miterlimit="10" d="M6.121,20.121 C7.727,21.22,9.907,22,12,22c5.523,0,10-4.477,10-10c0-5.523-4.477-10-10-10C8.101,2,4.728,4.233,3.078,7.488"/>
|
||||
<polyline fill="none" stroke="currentColor" stroke-miterlimit="10" points="2.278,1.588 3.078,7.488 9.078,6.688 "/>
|
||||
<circle data-color="color-2" fill="none" stroke-miterlimit="10" cx="4" cy="18" r="3"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 609 B |
@@ -0,0 +1,12 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24">
|
||||
<g stroke-linecap="round" stroke-linejoin="round" stroke-width="2" stroke="currentColor">
|
||||
<line data-cap="butt" data-color="color-2" x1="2" y1="6" x2="22" y2="6" fill="none" stroke-miterlimit="10"/>
|
||||
<line data-cap="butt" data-color="color-2" x1="12" y1="2" x2="12" y2="22" fill="none" stroke-miterlimit="10"/>
|
||||
<line data-cap="butt" data-color="color-2" x1="7" y1="2" x2="7" y2="6" fill="none" stroke-miterlimit="10"/>
|
||||
<line data-cap="butt" data-color="color-2" x1="17" y1="2" x2="17" y2="6" fill="none" stroke-miterlimit="10"/>
|
||||
<line data-cap="butt" data-color="color-2" x1="2" y1="18" x2="22" y2="18" fill="none" stroke-miterlimit="10"/>
|
||||
<line data-cap="butt" data-color="color-2" x1="7" y1="22" x2="7" y2="18" fill="none" stroke-miterlimit="10"/>
|
||||
<line data-cap="butt" data-color="color-2" x1="17" y1="22" x2="17" y2="18" fill="none" stroke-miterlimit="10"/>
|
||||
<rect x="2" y="2" width="20" height="20" fill="none" stroke="currentColor" stroke-miterlimit="10"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
@@ -12,7 +12,6 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import is from 'electron-is'
|
||||
import { mapState } from 'vuex'
|
||||
import AppInfo from '@/components/About/AppInfo'
|
||||
import Copyright from '@/components/About/Copyright'
|
||||
@@ -41,8 +40,6 @@
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
isRenderer: is.renderer,
|
||||
isMas: is.mas,
|
||||
handleOpen () {
|
||||
this.$store.dispatch('app/fetchEngineInfo')
|
||||
},
|
||||
@@ -58,6 +55,7 @@
|
||||
<style lang="scss">
|
||||
.app-about-dialog {
|
||||
max-width: 632px;
|
||||
min-width: 380px;
|
||||
.el-dialog__header {
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
|
||||
@@ -34,9 +34,11 @@
|
||||
},
|
||||
engine: {
|
||||
type: Object,
|
||||
default: {
|
||||
version: '',
|
||||
enabledFeatures: []
|
||||
default () {
|
||||
return {
|
||||
version: '',
|
||||
enabledFeatures: []
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -49,13 +51,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 {
|
||||
@@ -71,12 +73,12 @@
|
||||
margin: 50px 35% 0 8px;
|
||||
h4 {
|
||||
font-size: $--font-size-base;
|
||||
font-weight: $--font-weight-secondary;
|
||||
color: $--color-text-regular;
|
||||
font-weight: normal;
|
||||
color: $--app-engine-title-color;
|
||||
}
|
||||
ul {
|
||||
font-size: 12px;
|
||||
color: $--color-text-secondary;
|
||||
color: $--app-engine-info-color;
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
line-height: 20px;
|
||||
|
||||
@@ -1,18 +1,21 @@
|
||||
<template>
|
||||
<el-row class="copyright">
|
||||
<el-col :span="8" class="copyright-left">
|
||||
<a target="_blank" href="https://motrix.app/" rel="noopener noreferrer">
|
||||
©2018 Motrix
|
||||
<el-col :span="6" class="copyright-left">
|
||||
<a target="_blank" rel="noopener noreferrer" href="https://motrix.app/">
|
||||
©2019 Motrix
|
||||
</a>
|
||||
</el-col>
|
||||
<el-col :span="16" class="copyright-right">
|
||||
<a target="_blank" href="https://motrix.app/about" rel="noopener noreferrer">
|
||||
<el-col :span="18" class="copyright-right">
|
||||
<a target="_blank" rel="noopener noreferrer" href="https://motrix.app/license">
|
||||
{{ $t('about.license') }}
|
||||
</a>
|
||||
<a target="_blank" rel="noopener noreferrer" href="https://motrix.app/about">
|
||||
{{ $t('about.about') }}
|
||||
</a>
|
||||
<a target="_blank" href="https://motrix.app/support" rel="noopener noreferrer">
|
||||
<a target="_blank" rel="noopener noreferrer" href="https://motrix.app/support">
|
||||
{{ $t('about.support') }}
|
||||
</a>
|
||||
<a target="_blank" href="https://motrix.app/release" rel="noopener noreferrer">
|
||||
<a target="_blank" rel="noopener noreferrer" href="https://motrix.app/release">
|
||||
{{ $t('about.release') }}
|
||||
</a>
|
||||
</el-col>
|
||||
@@ -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 {
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
<template>
|
||||
<el-aside width="78px" class="aside" :class="{ draggable: !isWindows() }">
|
||||
<el-aside width="78px" :class="['aside', 'hidden-sm-and-down', { 'draggable': asideDraggable }]">
|
||||
<div class="aside-inner">
|
||||
<mo-logo-mini />
|
||||
<ul class="menu top-menu">
|
||||
<li @click="nav('/task')">
|
||||
<li @click="nav('/task')" class="non-draggable">
|
||||
<mo-icon name="menu-task" width="20" height="20" />
|
||||
</li>
|
||||
<li @click="showAddTask()">
|
||||
<li @click="showAddTask()" class="non-draggable">
|
||||
<mo-icon name="menu-add" width="20" height="20" />
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="menu bottom-menu">
|
||||
<li @click="nav('/preference')">
|
||||
<li @click="nav('/preference')" class="non-draggable">
|
||||
<mo-icon name="menu-preference" width="20" height="20" />
|
||||
</li>
|
||||
<li @click="showAboutPanel">
|
||||
<li @click="showAboutPanel" class="non-draggable">
|
||||
<mo-icon name="menu-about" width="20" height="20" />
|
||||
</li>
|
||||
</ul>
|
||||
@@ -25,6 +25,7 @@
|
||||
<script>
|
||||
import is from 'electron-is'
|
||||
import { mapState } from 'vuex'
|
||||
import { ADD_TASK_TYPE } from '@shared/constants'
|
||||
import LogoMini from '@/components/Logo/LogoMini'
|
||||
import '@/components/Icons/menu-task'
|
||||
import '@/components/Icons/menu-add'
|
||||
@@ -39,27 +40,23 @@
|
||||
computed: {
|
||||
...mapState('app', {
|
||||
currentPage: state => state.currentPage
|
||||
})
|
||||
}),
|
||||
asideDraggable: function () {
|
||||
return is.macOS()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
isRenderer: is.renderer,
|
||||
isWindows: is.windows,
|
||||
open (link) {
|
||||
this.$electron.shell.openExternal(link)
|
||||
},
|
||||
showAddTask (taskType = 'uri') {
|
||||
showAddTask (taskType = ADD_TASK_TYPE.URI) {
|
||||
this.$store.dispatch('app/showAddTaskDialog', taskType)
|
||||
},
|
||||
showAboutPanel () {
|
||||
// if (this.isRenderer()) {
|
||||
// this.$electron.ipcRenderer.send('command', 'application:about')
|
||||
// } else {
|
||||
this.$store.dispatch('app/showAboutPanel')
|
||||
// }
|
||||
},
|
||||
nav (page) {
|
||||
this.$router.push({
|
||||
path: page
|
||||
}).catch(err => {
|
||||
console.log(err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
<template>
|
||||
<div ref="webviewViewport" class="webview-viewport">
|
||||
<webview
|
||||
class="mo-webview"
|
||||
ref="webview"
|
||||
:src="src"
|
||||
></webview>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import is from 'electron-is'
|
||||
import { Loading } from 'element-ui'
|
||||
|
||||
import {
|
||||
openExternal
|
||||
} from '@/components/Native/utils'
|
||||
|
||||
export default {
|
||||
name: 'mo-browser',
|
||||
components: {
|
||||
},
|
||||
props: {
|
||||
src: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
loading: null
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isRenderer: () => is.renderer()
|
||||
},
|
||||
mounted () {
|
||||
const { webview } = this.$refs
|
||||
|
||||
webview.addEventListener('did-start-loading', this.loadStart.bind(this))
|
||||
webview.addEventListener('did-stop-loading', this.loadStop.bind(this))
|
||||
webview.addEventListener('dom-ready', this.ready.bind(this))
|
||||
},
|
||||
methods: {
|
||||
loadStart () {
|
||||
const { webviewViewport } = this.$refs
|
||||
this.loading = Loading.service({
|
||||
target: webviewViewport
|
||||
})
|
||||
},
|
||||
loadStop () {
|
||||
this.$nextTick(() => {
|
||||
this.loading.close()
|
||||
})
|
||||
},
|
||||
ready () {
|
||||
const { webview } = this.$refs
|
||||
|
||||
const webContents = this.$electron.remote.webContents.fromId(webview.getWebContentsId())
|
||||
webContents.on('new-window', (event, url) => {
|
||||
event.preventDefault()
|
||||
openExternal(url)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.webview-viewport {
|
||||
position: relative;
|
||||
}
|
||||
.mo-webview {
|
||||
display: inline-flex;;
|
||||
flex: 1;
|
||||
flex-basis: auto;
|
||||
}
|
||||
</style>
|
||||
@@ -1,26 +1,59 @@
|
||||
import { Message } from 'element-ui'
|
||||
import { base64StringToBlob } from 'blob-util'
|
||||
|
||||
import router from '@/router'
|
||||
import store from '@/store'
|
||||
import CommandManager from './CommandManager'
|
||||
import { Message } from 'element-ui'
|
||||
import { buildFileList } from '@shared/utils'
|
||||
import { ADD_TASK_TYPE } from '@shared/constants'
|
||||
import { getLocaleManager } from '@/components/Locale'
|
||||
import CommandManager from './CommandManager'
|
||||
|
||||
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 = ADD_TASK_TYPE.URI, task = '') {
|
||||
if (taskType === ADD_TASK_TYPE.URI && task) {
|
||||
store.dispatch('app/updateAddTaskUrl', task)
|
||||
}
|
||||
store.dispatch('app/showAddTaskDialog', taskType)
|
||||
}
|
||||
|
||||
function showAddBtTask () {
|
||||
store.dispatch('app/showAddTaskDialog', ADD_TASK_TYPE.TORRENT)
|
||||
}
|
||||
|
||||
function showAddBtTaskWithFile (fileName, base64Data = '') {
|
||||
const blob = base64StringToBlob(base64Data, 'application/x-bittorrent')
|
||||
const file = new File([blob], fileName, { type: 'application/x-bittorrent' })
|
||||
const fileList = buildFileList(file)
|
||||
store.dispatch('app/showAddTaskDialog', ADD_TASK_TYPE.TORRENT)
|
||||
setTimeout(() => {
|
||||
store.dispatch('app/addTaskAddTorrents', { fileList })
|
||||
}, 200)
|
||||
}
|
||||
|
||||
function navigateTaskList (status = 'active') {
|
||||
router.push({ path: `/task/${status}` })
|
||||
router.push({ path: `/task/${status}` }).catch(err => {
|
||||
console.log(err)
|
||||
})
|
||||
}
|
||||
|
||||
function navigatePreferences () {
|
||||
router.push({ path: '/preference' })
|
||||
router.push({ path: '/preference' }).catch(err => {
|
||||
console.log(err)
|
||||
})
|
||||
}
|
||||
|
||||
function showUnderDevelopmentMessage () {
|
||||
@@ -28,11 +61,11 @@ function showUnderDevelopmentMessage () {
|
||||
}
|
||||
|
||||
function pauseTask () {
|
||||
showUnderDevelopmentMessage()
|
||||
store.dispatch('task/batchPauseSelectedTasks')
|
||||
}
|
||||
|
||||
function resumeTask () {
|
||||
showUnderDevelopmentMessage()
|
||||
store.dispatch('task/batchResumeSelectedTasks')
|
||||
}
|
||||
|
||||
function deleteTask () {
|
||||
@@ -55,9 +88,16 @@ function resumeAllTask () {
|
||||
store.dispatch('task/resumeAllTask')
|
||||
}
|
||||
|
||||
function selectAllTask () {
|
||||
store.dispatch('task/selectAllTask')
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
@@ -68,6 +108,7 @@ commands.register('application:move-task-up', moveTaskUp)
|
||||
commands.register('application:move-task-down', moveTaskDown)
|
||||
commands.register('application:pause-all-task', pauseAllTask)
|
||||
commands.register('application:resume-all-task', resumeAllTask)
|
||||
commands.register('application:select-all-task', selectAllTask)
|
||||
|
||||
export {
|
||||
commands
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
<template>
|
||||
<div
|
||||
ref="container"
|
||||
style="position: relative; user-select: none; overflow-x: hidden; touch-action: none;"
|
||||
>
|
||||
<slot v-bind="{ selected: intersected }" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const getDimensions = (p1, p2) => ({
|
||||
width: Math.abs(p1.x - p2.x),
|
||||
height: Math.abs(p1.y - p2.y)
|
||||
})
|
||||
|
||||
const collisionCheck = (node1, node2) =>
|
||||
node1.left < node2.left + node2.width &&
|
||||
node1.left + node1.width > node2.left &&
|
||||
node1.top < node2.top + node2.height &&
|
||||
node1.top + node1.height > node2.top
|
||||
|
||||
export default {
|
||||
name: 'mo-drag-select',
|
||||
props: {
|
||||
attribute: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
default: '#bad7fb'
|
||||
},
|
||||
opacity: {
|
||||
type: Number,
|
||||
default: 0.7
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
intersected: [],
|
||||
children: []
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
intersected (i) {
|
||||
this.$emit('change', i)
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
const { container } = this.$refs
|
||||
const self = this
|
||||
|
||||
let containerRect = container.getBoundingClientRect()
|
||||
|
||||
const getCoords = e => ({
|
||||
x: e.clientX - containerRect.left,
|
||||
y: e.clientY - containerRect.top
|
||||
})
|
||||
|
||||
const box = this.createBox()
|
||||
let start = { x: 0, y: 0 }
|
||||
let end = { x: 0, y: 0 }
|
||||
|
||||
function touchStart (e) {
|
||||
e.preventDefault()
|
||||
startDrag(e.touches[0])
|
||||
}
|
||||
|
||||
function touchMove (e) {
|
||||
e.preventDefault()
|
||||
drag(e.touches[0])
|
||||
}
|
||||
|
||||
function startDrag (e) {
|
||||
containerRect = container.getBoundingClientRect()
|
||||
self.children = container.childNodes
|
||||
start = getCoords(e)
|
||||
end = start
|
||||
document.addEventListener('mousemove', drag)
|
||||
document.addEventListener('touchmove', touchMove)
|
||||
|
||||
box.style.top = start.y + 'px'
|
||||
box.style.left = start.x + 'px'
|
||||
|
||||
container.prepend(box)
|
||||
self.intersection(box)
|
||||
}
|
||||
|
||||
function drag (e) {
|
||||
end = getCoords(e)
|
||||
const dimensions = getDimensions(start, end)
|
||||
|
||||
if (end.x < start.x) {
|
||||
box.style.left = end.x + 'px'
|
||||
}
|
||||
if (end.y < start.y) {
|
||||
box.style.top = end.y + 'px'
|
||||
}
|
||||
box.style.width = dimensions.width + 'px'
|
||||
box.style.height = dimensions.height + 'px'
|
||||
|
||||
self.intersection(box)
|
||||
}
|
||||
|
||||
function endDrag () {
|
||||
start = { x: 0, y: 0 }
|
||||
end = { x: 0, y: 0 }
|
||||
|
||||
box.style.width = 0
|
||||
box.style.height = 0
|
||||
|
||||
document.removeEventListener('mousemove', drag)
|
||||
document.removeEventListener('touchmove', touchMove)
|
||||
box.remove()
|
||||
}
|
||||
|
||||
container.addEventListener('mousedown', startDrag)
|
||||
container.addEventListener('touchstart', touchStart)
|
||||
|
||||
document.addEventListener('mouseup', endDrag)
|
||||
document.addEventListener('touchend', endDrag)
|
||||
|
||||
this.$once('on:destroy', () => {
|
||||
container.removeEventListener('mousedown', startDrag)
|
||||
container.removeEventListener('touchstart', touchStart)
|
||||
document.removeEventListener('mouseup', endDrag)
|
||||
document.removeEventListener('touchend', endDrag)
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
createBox () {
|
||||
const box = document.createElement('div')
|
||||
box.setAttribute('data-drag-box-component', '')
|
||||
box.style.position = 'absolute'
|
||||
box.style.backgroundColor = this.color
|
||||
box.style.opacity = this.opacity
|
||||
box.style.zIndex = 1000
|
||||
|
||||
return box
|
||||
},
|
||||
intersection (box) {
|
||||
const { children } = this
|
||||
const rect = box.getBoundingClientRect()
|
||||
const intersected = []
|
||||
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
if (collisionCheck(rect, children[i].getBoundingClientRect())) {
|
||||
const attr = children[i].getAttribute(this.attribute)
|
||||
if (children[i].hasAttribute(this.attribute)) {
|
||||
intersected.push(attr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
JSON.stringify([...intersected]) !==
|
||||
JSON.stringify([...this.intersected])
|
||||
) { this.intersected = intersected }
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||