Compare commits
365 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a0a1fe90f7 | |||
| 553bf80960 | |||
| 04446b582e | |||
| f10f4a2588 | |||
| 513f566a59 | |||
| f49f083044 | |||
| 830bc9336e | |||
| 3b049219e4 | |||
| bbc68b6a8a | |||
| 0afc01080d | |||
| af1cc4db8e | |||
| 8defa9ee71 | |||
| cb6d9f2b20 | |||
| 9f6c31ffa3 | |||
| 0b3161ca6f | |||
| c79bdff4b8 | |||
| b92957668b | |||
| b1b52e2640 | |||
| a39fa2979c | |||
| e0a74f7979 | |||
| a7b8333710 | |||
| 77850da0fe | |||
| 71f4eeeb25 | |||
| e5eace479d | |||
| 7e927fea10 | |||
| 38cd952b44 | |||
| b07fedeba0 | |||
| 2bbaae5d7d | |||
| 003e9ffccb | |||
| 7a51dbaf5e | |||
| 4fa4878e61 | |||
| daa04d995e | |||
| 74e4bce6bf | |||
| a0aa100a99 | |||
| 4c3b248cc0 | |||
| 96d525832f | |||
| 9255c95ecc | |||
| e117d5925a | |||
| 6a5d7637c2 | |||
| 93e8bdff00 | |||
| a82afbf97b | |||
| e1363d3fe2 | |||
| 1338861dc3 | |||
| b9068559e2 | |||
| b7ed072966 | |||
| e281b71d4e | |||
| 67d26e4b1f | |||
| 1b27980992 | |||
| 292ad15152 | |||
| df1b30d6f4 | |||
| 0d3aa86d31 | |||
| a07f81e20c | |||
| 61365d738c | |||
| 33f13b51b0 | |||
| 2bda3b40eb | |||
| e456ba660b | |||
| 342b2b2069 | |||
| 91697aee42 | |||
| f4e56ca4b7 | |||
| 363cef2959 | |||
| e938f30377 | |||
| ed622ade1f | |||
| 06f0ccf2e5 | |||
| 6885da1b86 | |||
| 3ffce0566b | |||
| a215df0360 | |||
| 6bea4322c1 | |||
| 493df2b333 | |||
| 73c5a6790a | |||
| 644966e438 | |||
| af6e0f33c0 | |||
| 81656a2cd0 | |||
| 74b9f465a1 | |||
| 92177a2c4b | |||
| bc0114a164 | |||
| ff918c916c | |||
| d81c0bbb9a | |||
| b240029a47 | |||
| ba887bcc2b | |||
| 1d90231ebd | |||
| 39d92907e9 | |||
| 4272a53f69 | |||
| 0bbe980d48 | |||
| d2017a6c25 | |||
| e3da125cc7 | |||
| 41a81e7793 | |||
| 11c08b0dc2 | |||
| dbc95965aa | |||
| 26b5662c82 | |||
| bb37d4f328 | |||
| b355bcf623 | |||
| 9076ec08f8 | |||
| 65794cabab | |||
| e3f506ec0c | |||
| 70e7c99ff9 | |||
| bc66511647 | |||
| 002ef830dd | |||
| 06ffe6bd87 | |||
| ecf59583d6 | |||
| 7201f17aed | |||
| b4df97b471 | |||
| 5ea19eb441 | |||
| 8857290a0a | |||
| 1dec09b005 | |||
| d67ba74b44 | |||
| 696d5c7f02 | |||
| 106410cb23 | |||
| 237df6011e | |||
| 20dbeb33ac | |||
| 55e2a42f3d | |||
| 637d5e9a80 | |||
| 9867c578dc | |||
| a8bac652d1 | |||
| 7d0a94ee37 | |||
| 0276c8e5b4 | |||
| e93d6a2c92 | |||
| 8743e8a025 | |||
| 680fdac38b | |||
| e06850b5a2 | |||
| 038511fed4 | |||
| 6b2deb8303 | |||
| a8846eaf92 | |||
| 6039a89441 | |||
| 2bfab8b9c8 | |||
| e6c533e139 | |||
| 1c2fd0de05 | |||
| a91af1032c | |||
| c80102c5f5 | |||
| ff312df849 | |||
| 65bf25dc1a | |||
| 6f35e64c2a | |||
| 4c5bba89ef | |||
| 51b1be7bbc | |||
| 1bf594b369 | |||
| de1db5687b | |||
| 98580fc094 | |||
| 8994312ee0 | |||
| bd9b5eca12 | |||
| 4ed0bba8ba | |||
| 7868a4870b | |||
| 8052aa047e | |||
| 559df6bb8f | |||
| c70a35e083 | |||
| 30817eac86 | |||
| 751efd6b00 | |||
| 7688115bd4 | |||
| 732327f5a3 | |||
| 38fb29382b | |||
| ee05252163 | |||
| 74ba6ec5ab | |||
| 1f5f5dc5f0 | |||
| 6be0afd5ff | |||
| 2c269e3a52 | |||
| 79dea2f4b7 | |||
| 9db5c52f13 | |||
| 81a73557e8 | |||
| 6fc02f752d | |||
| 6d893bd1ce | |||
| 68ba9207f9 | |||
| b01d43cf73 | |||
| ba97bbfa66 | |||
| b8dbc902a3 | |||
| b79ef074dd | |||
| 3ceccea9da | |||
| c591ea9655 | |||
| 3245bbdaa1 | |||
| 60161518c6 | |||
| 1de2acc505 | |||
| a77e62066a | |||
| c6722d141b | |||
| 509c79989b | |||
| 333f01afd3 | |||
| edd4053730 | |||
| 7db1e4ff5b | |||
| a9f5312a90 | |||
| 6d9a640030 | |||
| 74c241f819 | |||
| 3e0df405ee | |||
| 4dd6e948f4 | |||
| c0b7612e3a | |||
| 0486e4b417 | |||
| 160fe9725d | |||
| 577a9b32a1 | |||
| f05994d9f8 | |||
| dec938f2f0 | |||
| 084b5350fd | |||
| e971f86932 | |||
| 0be90dda31 | |||
| 2bc4a8c61e | |||
| 6baef42274 | |||
| f4cc731e63 | |||
| 480039c4cc | |||
| b0242d3d5b | |||
| 58cfd2b873 | |||
| 804f864709 | |||
| 57fb4d95ae | |||
| 8030699e15 | |||
| 1185a81c0c | |||
| aa4941f842 | |||
| 841bd8b923 | |||
| 85ae4cdbca | |||
| c257816608 | |||
| 1b44ef725b | |||
| bffe919b93 | |||
| 87635ade34 | |||
| c6a9eb226d | |||
| da2b6638d9 | |||
| 91072509d3 | |||
| 0d75370f95 | |||
| 994d351998 | |||
| 166aba7747 | |||
| bc3ea97780 | |||
| 2beb6f14a8 | |||
| 5d8566a934 | |||
| 84b002d513 | |||
| a4fb082088 | |||
| 3aa18e7f72 | |||
| e939f9e5dc | |||
| 53ec0b1dee | |||
| fcfd32a71e | |||
| 2ff56a3770 | |||
| 48768f0658 | |||
| 3e84230b33 | |||
| 5490946267 | |||
| 9461381042 | |||
| 364d7a8f66 | |||
| 7a5a1554ca | |||
| 8410be59b2 | |||
| 795db0b926 | |||
| 079cab8544 | |||
| 7109747e00 | |||
| 9b698f5a0e | |||
| 2b35fb9bc2 | |||
| 2b80127ad0 | |||
| d47fa3d705 | |||
| 555db61ec0 | |||
| 7c2fc774ca | |||
| 8eeab3d3fa | |||
| 7a5b16aecc | |||
| c5f72414e2 | |||
| 4016f5c02e | |||
| af7eb1f359 | |||
| f30ed3c8f7 | |||
| e9e86fbc83 | |||
| d7b985bc3f | |||
| e307240a60 | |||
| 8dd1d84485 | |||
| 3c7b0b26e3 | |||
| 778e092c17 | |||
| 6e462166d1 | |||
| 812b419379 | |||
| 04dcddd3e1 | |||
| 1cfec289b7 | |||
| 2dcfb8c25e | |||
| 482b9312b1 | |||
| dbb1889fc9 | |||
| a6de12c875 | |||
| d14ddef8e8 | |||
| c8698e5b80 | |||
| e8c99caf87 | |||
| f70595915d | |||
| c5962041cb | |||
| 28bbf26334 | |||
| 3d6931f8d8 | |||
| 46d686bee5 | |||
| 9ffaf1116f | |||
| 2d4a05f3b0 | |||
| e04d3a582e | |||
| 943830f2ed | |||
| 137927d44a | |||
| 819f86632e | |||
| 06f881932c | |||
| cb845ea65a | |||
| 60e4907be6 | |||
| bc38978d6a | |||
| 90c3bbff13 | |||
| 3116c53734 | |||
| 78fb6ba455 | |||
| 86304c126d | |||
| 3424424e67 | |||
| 7206f2fc3c | |||
| bd25e566c8 | |||
| f54bc32e90 | |||
| f6dde55234 | |||
| 1303713f5e | |||
| 57a5c0c932 | |||
| 22a41c332f | |||
| 76e7223e85 | |||
| 014619ed46 | |||
| aec3a25e3a | |||
| 02f6ffa3cb | |||
| 5d32fc633c | |||
| cde2832ae3 | |||
| 8656fa0862 | |||
| 0cd1ed1e34 | |||
| d58e485846 | |||
| a6aca44672 | |||
| cc28e4b19b | |||
| 599dec1d6e | |||
| d020359058 | |||
| 04d68e293b | |||
| 6b93a80888 | |||
| c3400d936e | |||
| 871be123d4 | |||
| bed2148ba0 | |||
| 8b97c90e88 | |||
| 832befbafd | |||
| ddbf27e2f9 | |||
| 64b30b6c26 | |||
| 32c8767e65 | |||
| 69391c6de0 | |||
| fbec4e7c4d | |||
| 7640ba583d | |||
| 924b397727 | |||
| 565afdde74 | |||
| 3dfd4ec4da | |||
| 0a21f590ff | |||
| 79bf90bb3c | |||
| f4602b4b68 | |||
| 93a6f1cfbd | |||
| f0d14afb7e | |||
| d9a69ae009 | |||
| 0f5398b106 | |||
| c6eb96547a | |||
| 31ab487d82 | |||
| 0b2e271663 | |||
| a4a4b2321f | |||
| ef372da87b | |||
| 10cab1a9a0 | |||
| a4a651b397 | |||
| b2770795ad | |||
| f4c8ff66d5 | |||
| 1b450c8022 | |||
| e5b8846286 | |||
| 8de8591725 | |||
| 20c30279b0 | |||
| 9000be502b | |||
| cfe66cf337 | |||
| 3557d17bb6 | |||
| 2af891aab8 | |||
| 0d276de39b | |||
| 8a6beda335 | |||
| 68f1cdc4de | |||
| 5290fcfa14 | |||
| d865b630a3 | |||
| 9d95d294cd | |||
| ea46d9b3c6 | |||
| ef2c992af9 | |||
| 64ee097c85 | |||
| 31517f93cb | |||
| 6d54e557b9 | |||
| 3e61adcea3 | |||
| 84d9ced137 | |||
| bc45ed9d5b | |||
| 3d98104dbf | |||
| fb6986ff6e | |||
| b8507570c6 | |||
| cf3ef7606c | |||
| 12a9fa92c1 | |||
| 0fd6617eba | |||
| 7ebbf929d5 | |||
| e5241d09d7 | |||
| c9ea1dece2 | |||
| 60d4108ddc | |||
| ffc8de4766 |
@@ -5,10 +5,8 @@ process.env.NODE_ENV = 'production'
|
||||
const { say } = require('cfonts')
|
||||
const chalk = require('chalk')
|
||||
const del = require('del')
|
||||
const { spawn } = require('child_process')
|
||||
const webpack = require('webpack')
|
||||
const Multispinner = require('multispinner')
|
||||
|
||||
const Webpack = require('webpack')
|
||||
const Multispinner = require('@motrix/multispinner')
|
||||
|
||||
const mainConfig = require('./webpack.main.config')
|
||||
const rendererConfig = require('./webpack.renderer.config')
|
||||
@@ -77,7 +75,7 @@ function build () {
|
||||
function pack (config) {
|
||||
return new Promise((resolve, reject) => {
|
||||
config.mode = 'production'
|
||||
webpack(config, (err, stats) => {
|
||||
Webpack(config, (err, stats) => {
|
||||
if (err) {
|
||||
reject(err.stack || err)
|
||||
} else if (stats.hasErrors()) {
|
||||
@@ -104,9 +102,9 @@ function pack (config) {
|
||||
}
|
||||
|
||||
function web () {
|
||||
del.sync(['dist/web/*', '!.gitkeep'])
|
||||
deleteSync(['dist/web/*', '!.gitkeep'])
|
||||
webConfig.mode = 'production'
|
||||
webpack(webConfig, (err, stats) => {
|
||||
Webpack(webConfig, (err, stats) => {
|
||||
if (err || stats.hasErrors()) console.log(err)
|
||||
|
||||
console.log(stats.toString({
|
||||
@@ -122,16 +120,20 @@ function greeting () {
|
||||
const cols = process.stdout.columns
|
||||
let text = ''
|
||||
|
||||
if (cols > 85) text = 'lets-build'
|
||||
else if (cols > 60) text = 'lets-|build'
|
||||
else text = false
|
||||
if (cols > 85) {
|
||||
text = 'lets-build'
|
||||
} else if (cols > 60) {
|
||||
text = 'lets-|build'
|
||||
} else {
|
||||
text = false
|
||||
}
|
||||
|
||||
if (text && !isCI) {
|
||||
say(text, {
|
||||
colors: ['yellow'],
|
||||
colors: ['magentaBright'],
|
||||
font: 'simple3d',
|
||||
space: false
|
||||
})
|
||||
} else console.log(chalk.yellow.bold('\n lets-build'))
|
||||
} else console.log(chalk.magentaBright.bold('\n lets-build'))
|
||||
console.log()
|
||||
}
|
||||
|
||||
@@ -5,17 +5,14 @@ const electron = require('electron')
|
||||
const path = require('path')
|
||||
const { say } = require('cfonts')
|
||||
const { spawn } = require('child_process')
|
||||
const webpack = require('webpack')
|
||||
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')
|
||||
|
||||
let electronProcess = null
|
||||
let manualRestart = false
|
||||
let hotMiddleware
|
||||
|
||||
function logStats (proc, data) {
|
||||
let log = ''
|
||||
@@ -40,41 +37,22 @@ function logStats (proc, data) {
|
||||
}
|
||||
|
||||
function startRenderer () {
|
||||
return new Promise((resolve, reject) => {
|
||||
rendererConfig.entry.index = [path.join(__dirname, 'dev-client')].concat(rendererConfig.entry.index)
|
||||
return new Promise(async (resolve, reject) => {
|
||||
rendererConfig.entry.index = rendererConfig.entry.index
|
||||
rendererConfig.mode = 'development'
|
||||
const compiler = webpack(rendererConfig)
|
||||
hotMiddleware = webpackHotMiddleware(compiler, {
|
||||
log: false,
|
||||
heartbeat: 2500
|
||||
})
|
||||
|
||||
compiler.hooks.compilation.tap('compilation', compilation => {
|
||||
HtmlWebpackPlugin.getHooks(compilation).afterEmit.tapAsync('html-webpack-plugin-after-emit', (data, cb) => {
|
||||
hotMiddleware.publish({ action: 'reload' })
|
||||
cb()
|
||||
})
|
||||
})
|
||||
const compiler = Webpack(rendererConfig)
|
||||
const devServerOptions = {
|
||||
...rendererConfig.devServer,
|
||||
port: 9080,
|
||||
static: {
|
||||
directory: path.resolve(__dirname, "../"),
|
||||
},
|
||||
};
|
||||
|
||||
compiler.hooks.done.tap('done', stats => {
|
||||
logStats('Renderer', stats)
|
||||
})
|
||||
|
||||
const server = new WebpackDevServer(
|
||||
compiler,
|
||||
{
|
||||
contentBase: path.join(__dirname, '../'),
|
||||
quiet: true,
|
||||
before (app, ctx) {
|
||||
app.use(hotMiddleware)
|
||||
ctx.middleware.waitUntilValid(() => {
|
||||
resolve()
|
||||
})
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
server.listen(9080)
|
||||
const server = new WebpackDevServer(devServerOptions, compiler)
|
||||
await server.start()
|
||||
resolve()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -82,11 +60,11 @@ function startMain () {
|
||||
return new Promise((resolve, reject) => {
|
||||
mainConfig.entry.main = [path.join(__dirname, '../src/main/index.dev.js')].concat(mainConfig.entry.main)
|
||||
mainConfig.mode = 'development'
|
||||
const compiler = webpack(mainConfig)
|
||||
const compiler = Webpack(mainConfig)
|
||||
|
||||
compiler.hooks.watchRun.tapAsync('watch-run', (compilation, done) => {
|
||||
logStats('Main', chalk.white.bold('compiling...'))
|
||||
hotMiddleware.publish({ action: 'compiling' })
|
||||
// hotMiddleware.publish({ action: 'compiling' })
|
||||
done()
|
||||
})
|
||||
|
||||
@@ -150,17 +128,21 @@ function greeting () {
|
||||
const cols = process.stdout.columns
|
||||
let text = ''
|
||||
|
||||
if (cols > 104) text = 'electron-vue'
|
||||
else if (cols > 76) text = 'electron-|vue'
|
||||
else text = false
|
||||
if (cols > 104) {
|
||||
text = 'motrix-dev'
|
||||
} else if (cols > 76) {
|
||||
text = 'motrix-|dev'
|
||||
} else {
|
||||
text = false
|
||||
}
|
||||
|
||||
if (text) {
|
||||
say(text, {
|
||||
colors: ['yellow'],
|
||||
colors: ['magentaBright'],
|
||||
font: 'simple3d',
|
||||
space: false
|
||||
})
|
||||
} else console.log(chalk.yellow.bold('\n electron-vue'))
|
||||
} else console.log(chalk.magentaBright.bold('\n motrix-dev'))
|
||||
console.log(chalk.blue(' getting ready...') + '\n')
|
||||
}
|
||||
|
||||
|
||||
@@ -4,9 +4,11 @@ process.env.BABEL_ENV = 'main'
|
||||
|
||||
const devMode = process.env.NODE_ENV !== 'production'
|
||||
const path = require('path')
|
||||
const { dependencies, build } = require('../package.json')
|
||||
const webpack = require('webpack')
|
||||
const { dependencies } = require('../package.json')
|
||||
const { appId } = require('../electron-builder.json')
|
||||
const Webpack = require('webpack')
|
||||
const TerserPlugin = require('terser-webpack-plugin');
|
||||
const ESLintPlugin = require('eslint-webpack-plugin');
|
||||
|
||||
let mainConfig = {
|
||||
entry: {
|
||||
@@ -17,17 +19,6 @@ let mainConfig = {
|
||||
],
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.(js)$/,
|
||||
enforce: 'pre',
|
||||
exclude: /node_modules/,
|
||||
use: {
|
||||
loader: 'eslint-loader',
|
||||
options: {
|
||||
formatter: require('eslint-friendly-formatter')
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
test: /\.js$/,
|
||||
use: 'babel-loader',
|
||||
@@ -49,7 +40,10 @@ let mainConfig = {
|
||||
path: path.join(__dirname, '../dist/electron')
|
||||
},
|
||||
plugins: [
|
||||
new webpack.NoEmitOnErrorsPlugin()
|
||||
new Webpack.NoEmitOnErrorsPlugin(),
|
||||
new ESLintPlugin({
|
||||
formatter: require('eslint-friendly-formatter')
|
||||
})
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
@@ -74,9 +68,9 @@ let mainConfig = {
|
||||
*/
|
||||
if (devMode) {
|
||||
mainConfig.plugins.push(
|
||||
new webpack.DefinePlugin({
|
||||
new Webpack.DefinePlugin({
|
||||
'__static': `"${path.join(__dirname, '../static').replace(/\\/g, '\\\\')}"`,
|
||||
'appId': `"${build.appId}"`
|
||||
'appId': `"${appId}"`
|
||||
})
|
||||
)
|
||||
}
|
||||
@@ -86,9 +80,9 @@ if (devMode) {
|
||||
*/
|
||||
if (!devMode) {
|
||||
mainConfig.plugins.push(
|
||||
new webpack.DefinePlugin({
|
||||
new Webpack.DefinePlugin({
|
||||
'process.env.NODE_ENV': '"production"',
|
||||
'appId': `"${build.appId}"`
|
||||
'appId': `"${appId}"`
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
@@ -5,13 +5,14 @@ process.env.BABEL_ENV = 'renderer'
|
||||
const devMode = process.env.NODE_ENV !== 'production'
|
||||
const path = require('path')
|
||||
const { dependencies } = require('../package.json')
|
||||
const webpack = require('webpack')
|
||||
const Webpack = require('webpack')
|
||||
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')
|
||||
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin')
|
||||
const { VueLoaderPlugin } = require('vue-loader')
|
||||
const ESLintPlugin = require('eslint-webpack-plugin');
|
||||
|
||||
/**
|
||||
* List of node_modules to include in webpack bundle
|
||||
@@ -23,7 +24,6 @@ const { VueLoaderPlugin } = require('vue-loader')
|
||||
let whiteListedModules = ['vue']
|
||||
|
||||
let rendererConfig = {
|
||||
devtool: '#cheap-module-eval-source-map',
|
||||
entry: {
|
||||
index: path.join(__dirname, '../src/renderer/pages/index/main.js')
|
||||
},
|
||||
@@ -33,14 +33,10 @@ let rendererConfig = {
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.(js|vue)$/,
|
||||
enforce: 'pre',
|
||||
exclude: /node_modules/,
|
||||
test: /\.worker\.js$/,
|
||||
use: {
|
||||
loader: 'eslint-loader',
|
||||
options: {
|
||||
formatter: require('eslint-friendly-formatter')
|
||||
}
|
||||
loader: 'worker-loader',
|
||||
options: { filename: '[name].js' }
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -52,7 +48,7 @@ let rendererConfig = {
|
||||
loader: 'sass-loader',
|
||||
options: {
|
||||
implementation: require('sass'),
|
||||
prependData: '@import "@/components/Theme/Variables.scss";',
|
||||
additionalData: '@import "@/components/Theme/Variables.scss";',
|
||||
sassOptions: {
|
||||
includePaths:[__dirname, 'src']
|
||||
}
|
||||
@@ -70,7 +66,7 @@ let rendererConfig = {
|
||||
options: {
|
||||
implementation: require('sass'),
|
||||
indentedSyntax: true,
|
||||
prependData: '@import "@/components/Theme/Variables.scss";',
|
||||
additionalData: '@import "@/components/Theme/Variables.scss";',
|
||||
sassOptions: {
|
||||
includePaths:[__dirname, 'src']
|
||||
}
|
||||
@@ -118,31 +114,15 @@ let rendererConfig = {
|
||||
},
|
||||
{
|
||||
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
|
||||
use: {
|
||||
loader: 'url-loader',
|
||||
query: {
|
||||
limit: 10000,
|
||||
name: 'imgs/[name]--[folder].[ext]'
|
||||
}
|
||||
}
|
||||
type: 'asset/inline'
|
||||
},
|
||||
{
|
||||
test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
|
||||
loader: 'url-loader',
|
||||
options: {
|
||||
limit: 10000,
|
||||
name: 'media/[name]--[folder].[ext]'
|
||||
}
|
||||
type: 'asset/resource'
|
||||
},
|
||||
{
|
||||
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
|
||||
use: {
|
||||
loader: 'url-loader',
|
||||
query: {
|
||||
limit: 10000,
|
||||
name: 'fonts/[name]--[folder].[ext]'
|
||||
}
|
||||
}
|
||||
type: 'asset/inline'
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -156,45 +136,35 @@ let rendererConfig = {
|
||||
filename: '[name].css',
|
||||
chunkFilename: '[id].css'
|
||||
}),
|
||||
new OptimizeCSSPlugin({
|
||||
cssProcessorOptions: {
|
||||
safe: true,
|
||||
discardComments: { removeAll: true }
|
||||
}
|
||||
}),
|
||||
new HtmlWebpackPlugin({
|
||||
title: 'Motrix',
|
||||
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,
|
||||
// removeComments: true
|
||||
// },
|
||||
isBrowser: false,
|
||||
isDev: process.env.NODE_ENV !== 'production',
|
||||
nodeModules: devMode
|
||||
? path.resolve(__dirname, '../node_modules')
|
||||
: false
|
||||
}),
|
||||
new webpack.HotModuleReplacementPlugin(),
|
||||
new webpack.NoEmitOnErrorsPlugin()
|
||||
new Webpack.HotModuleReplacementPlugin(),
|
||||
new Webpack.NoEmitOnErrorsPlugin(),
|
||||
new ESLintPlugin({
|
||||
extensions: ['js', 'vue'],
|
||||
formatter: require('eslint-friendly-formatter')
|
||||
})
|
||||
],
|
||||
output: {
|
||||
filename: '[name].js',
|
||||
libraryTarget: 'commonjs2',
|
||||
path: path.join(__dirname, '../dist/electron')
|
||||
path: path.join(__dirname, '../dist/electron'),
|
||||
globalObject: 'this',
|
||||
publicPath: ''
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
@@ -210,7 +180,8 @@ let rendererConfig = {
|
||||
minimizer: [
|
||||
new TerserPlugin({
|
||||
extractComments: false,
|
||||
})
|
||||
}),
|
||||
new CssMinimizerPlugin(),
|
||||
],
|
||||
},
|
||||
}
|
||||
@@ -219,8 +190,10 @@ let rendererConfig = {
|
||||
* Adjust rendererConfig for development settings
|
||||
*/
|
||||
if (devMode) {
|
||||
rendererConfig.devtool = 'eval-cheap-module-source-map'
|
||||
|
||||
rendererConfig.plugins.push(
|
||||
new webpack.DefinePlugin({
|
||||
new Webpack.DefinePlugin({
|
||||
'__static': `"${path.join(__dirname, '../static').replace(/\\/g, '\\\\')}"`
|
||||
})
|
||||
)
|
||||
@@ -230,8 +203,6 @@ if (devMode) {
|
||||
* Adjust rendererConfig for production settings
|
||||
*/
|
||||
if (!devMode) {
|
||||
rendererConfig.devtool = ''
|
||||
|
||||
rendererConfig.plugins.push(
|
||||
new CopyWebpackPlugin({
|
||||
patterns: [{
|
||||
@@ -240,10 +211,10 @@ if (!devMode) {
|
||||
globOptions: { ignore: [ '.*' ] }
|
||||
}]
|
||||
}),
|
||||
new webpack.DefinePlugin({
|
||||
new Webpack.DefinePlugin({
|
||||
'process.env.NODE_ENV': '"production"'
|
||||
}),
|
||||
new webpack.LoaderOptionsPlugin({
|
||||
new Webpack.LoaderOptionsPlugin({
|
||||
minimize: false
|
||||
})
|
||||
)
|
||||
|
||||
@@ -5,13 +5,14 @@ process.env.BABEL_ENV = 'web'
|
||||
const devMode = process.env.NODE_ENV !== 'production'
|
||||
const path = require('path')
|
||||
const { dependencies } = require('../package.json')
|
||||
const webpack = require('webpack')
|
||||
const Webpack = require('webpack')
|
||||
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')
|
||||
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin')
|
||||
const { VueLoaderPlugin } = require('vue-loader')
|
||||
const ESLintPlugin = require('eslint-webpack-plugin');
|
||||
|
||||
/**
|
||||
* List of node_modules to include in webpack bundle
|
||||
@@ -23,7 +24,6 @@ const { VueLoaderPlugin } = require('vue-loader')
|
||||
let whiteListedModules = ['vue']
|
||||
|
||||
let webConfig = {
|
||||
devtool: '#cheap-module-eval-source-map',
|
||||
entry: {
|
||||
index: path.join(__dirname, '../src/renderer/pages/index/main.js')
|
||||
},
|
||||
@@ -33,14 +33,10 @@ let webConfig = {
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.(js|vue)$/,
|
||||
enforce: 'pre',
|
||||
exclude: /node_modules/,
|
||||
test: /\.worker\.js$/,
|
||||
use: {
|
||||
loader: 'eslint-loader',
|
||||
options: {
|
||||
formatter: require('eslint-friendly-formatter')
|
||||
}
|
||||
loader: 'worker-loader',
|
||||
options: { filename: '[name].js' }
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -52,7 +48,7 @@ let webConfig = {
|
||||
loader: 'sass-loader',
|
||||
options: {
|
||||
implementation: require('sass'),
|
||||
prependData: '@import "@/components/Theme/Variables.scss";',
|
||||
additionalData: '@import "@/components/Theme/Variables.scss";',
|
||||
sassOptions: {
|
||||
includePaths:[__dirname, 'src']
|
||||
}
|
||||
@@ -70,7 +66,7 @@ let webConfig = {
|
||||
options: {
|
||||
implementation: require('sass'),
|
||||
indentedSyntax: true,
|
||||
prependData: '@import "@/components/Theme/Variables.scss";',
|
||||
additionalData: '@import "@/components/Theme/Variables.scss";',
|
||||
sassOptions: {
|
||||
includePaths:[__dirname, 'src']
|
||||
}
|
||||
@@ -115,23 +111,11 @@ let webConfig = {
|
||||
},
|
||||
{
|
||||
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
|
||||
use: {
|
||||
loader: 'url-loader',
|
||||
query: {
|
||||
limit: 10000,
|
||||
name: 'imgs/[name].[ext]'
|
||||
}
|
||||
}
|
||||
type: 'asset/inline'
|
||||
},
|
||||
{
|
||||
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
|
||||
use: {
|
||||
loader: 'url-loader',
|
||||
query: {
|
||||
limit: 10000,
|
||||
name: 'fonts/[name].[ext]'
|
||||
}
|
||||
}
|
||||
type: 'asset/inline'
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -141,47 +125,37 @@ let webConfig = {
|
||||
filename: '[name].css',
|
||||
chunkFilename: '[id].css'
|
||||
}),
|
||||
new OptimizeCSSPlugin({
|
||||
cssProcessorOptions: {
|
||||
safe: true,
|
||||
discardComments: { removeAll: true }
|
||||
}
|
||||
}),
|
||||
new HtmlWebpackPlugin({
|
||||
title: 'Motrix',
|
||||
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,
|
||||
// removeComments: true
|
||||
// },
|
||||
isBrowser: true,
|
||||
isDev: process.env.NODE_ENV !== 'production',
|
||||
nodeModules: devMode
|
||||
? path.resolve(__dirname, '../node_modules')
|
||||
: false
|
||||
}),
|
||||
new webpack.DefinePlugin({
|
||||
new Webpack.DefinePlugin({
|
||||
'process.env.IS_WEB': 'true'
|
||||
}),
|
||||
new webpack.HotModuleReplacementPlugin(),
|
||||
new webpack.NoEmitOnErrorsPlugin()
|
||||
new Webpack.HotModuleReplacementPlugin(),
|
||||
new Webpack.NoEmitOnErrorsPlugin(),
|
||||
new ESLintPlugin({
|
||||
extensions: ['js', 'vue'],
|
||||
formatter: require('eslint-friendly-formatter')
|
||||
})
|
||||
],
|
||||
output: {
|
||||
filename: '[name].js',
|
||||
path: path.join(__dirname, '../dist/web')
|
||||
path: path.join(__dirname, '../dist/web'),
|
||||
globalObject: 'this',
|
||||
publicPath: ''
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
@@ -197,17 +171,23 @@ let webConfig = {
|
||||
minimizer: [
|
||||
new TerserPlugin({
|
||||
extractComments: false,
|
||||
})
|
||||
}),
|
||||
new CssMinimizerPlugin(),
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjust webConfig for development settings
|
||||
*/
|
||||
if (devMode) {
|
||||
webConfig.devtool = 'eval-cheap-module-source-map'
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjust webConfig for production settings
|
||||
*/
|
||||
if (!devMode) {
|
||||
webConfig.devtool = ''
|
||||
|
||||
webConfig.plugins.push(
|
||||
new CopyWebpackPlugin({
|
||||
patterns: [{
|
||||
@@ -216,10 +196,10 @@ if (!devMode) {
|
||||
globOptions: { ignore: [ '.*' ] }
|
||||
}]
|
||||
}),
|
||||
new webpack.DefinePlugin({
|
||||
new Webpack.DefinePlugin({
|
||||
'process.env.NODE_ENV': '"production"'
|
||||
}),
|
||||
new webpack.LoaderOptionsPlugin({
|
||||
new Webpack.LoaderOptionsPlugin({
|
||||
minimize: true
|
||||
})
|
||||
)
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
name: 🐛 [NEW] Bug Report
|
||||
description: File a bug report here
|
||||
title: "[BUG]: "
|
||||
labels: ["bug"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for taking the time to fill out this bug report 🤗
|
||||
Make sure there aren't any open/closed issues for this topic 😃
|
||||
|
||||
- type: textarea
|
||||
id: bug-description
|
||||
attributes:
|
||||
label: Description of the bug
|
||||
description: Give us a brief description of what happened and what should have happened
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: app-version
|
||||
attributes:
|
||||
label: Motrix Version
|
||||
description: Please provide detailed version information and installation method, such as macOS Apple silicon dmg, Windows Universal installation file, etc.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Environment
|
||||
description: |
|
||||
Run this command in your project's root folder and paste the result:
|
||||
|
||||
```sh
|
||||
npx envinfo --system --binaries --browsers
|
||||
```
|
||||
add `| pbcopy` if you're in macOS for easy copy paste.
|
||||
Alternatively, you can manually gather the version information from your environment.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: steps-to-reproduce
|
||||
attributes:
|
||||
label: Steps To Reproduce
|
||||
description: Steps to reproduce the behavior.
|
||||
placeholder: |
|
||||
1. Go to '...'
|
||||
2. Click on '...'
|
||||
3. Scroll down to '...'
|
||||
4. See error
|
||||
More info: A [minimal reproduction](https://stackoverflow.com/help/minimal-reproducible-example) is **required**, otherwise the issue might be closed without further notice. [**Why & How?**](https://antfu.me/posts/why-reproductions-are-required)
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: additional-information
|
||||
attributes:
|
||||
label: Additional Information
|
||||
description: |
|
||||
Provide any additional information such as logs, screenshots, likes, scenarios in which the bug occurs so that it facilitates resolving the issue.
|
||||
|
||||
- type: checkboxes
|
||||
id: checkboxes
|
||||
attributes:
|
||||
label: Validations
|
||||
description: Before submitting the issue, please make sure you do the following
|
||||
options:
|
||||
- label: Follow our [Code of Conduct](https://github.com/agalwood/Motrix/blob/master/CODE_OF_CONDUCT.md)
|
||||
required: true
|
||||
- label: Check that there isn't already an issue that reports the same bug to avoid creating a duplicate.
|
||||
required: true
|
||||
- label: Check that this is a concrete bug. For Q&A, please open a [GitHub Discussion](https://github.com/agalwood/Motrix/discussions) instead.
|
||||
required: true
|
||||
- label: The provided reproduction is a [minimal reproducible](https://stackoverflow.com/help/minimal-reproducible-example) of the bug.
|
||||
required: true
|
||||
@@ -0,0 +1,76 @@
|
||||
name: 🐛 [新] Bug 报告
|
||||
description: 在这里提交 Bug 报告
|
||||
title: "[BUG]: "
|
||||
labels: ["bug"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
感谢您抽出时间来填写这份 Bug 报告 🤗
|
||||
请确保此问题没有已存在的开放/关闭问题 😃
|
||||
|
||||
- type: textarea
|
||||
id: bug-description
|
||||
attributes:
|
||||
label: Bug 描述
|
||||
description: 给我们一个简短的描述,说明发生了什么以及应该发生什么。
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: app-version
|
||||
attributes:
|
||||
label: Motrix 版本
|
||||
description: 请提供详细的版本信息以及安装的方式,如 macOS Apple silicon dmg、Windows Universal 安装文件等
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: 环境
|
||||
description: |
|
||||
在项目的根目录下运行以下命令,并将结果粘贴到下方:
|
||||
|
||||
```sh
|
||||
npx envinfo --system --binaries --browsers
|
||||
```
|
||||
在 macOS 中,如果您需要轻松复制粘贴,可以添加 `| pbcopy`。
|
||||
或者,您也可以手动收集您的环境版本信息。
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: steps-to-reproduce
|
||||
attributes:
|
||||
label: 复现步骤
|
||||
description: 复现该问题的步骤。
|
||||
placeholder: |
|
||||
1. 前往 '...'
|
||||
2. 点击 '...'
|
||||
3. 滚动到 '...'
|
||||
4. 查看错误
|
||||
更多信息:[最小复现例子](https://stackoverflow.com/help/minimal-reproducible-example) 是必需的,否则该问题可能会被关闭而没有进一步的通知。[**为什么 & 如何?**](https://antfu.me/posts/why-reproductions-are-required)
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: additional-information
|
||||
attributes:
|
||||
label: 额外信息
|
||||
description: |
|
||||
提供任何额外的信息,例如日志、截图、喜欢、发生该 Bug 的场景,以便有助于解决问题。
|
||||
|
||||
- type: checkboxes
|
||||
id: checkboxes
|
||||
attributes:
|
||||
label: 验证
|
||||
description: 在提交问题之前,请确保您完成了以下操作
|
||||
options:
|
||||
- label: 遵循我们的[行为准则](https://github.com/agalwood/Motrix/blob/master/CODE_OF_CONDUCT.md)
|
||||
required: true
|
||||
- label: 确认是否已经有一个报告了相同的 Bug,以避免创建重复的问题。
|
||||
required: true
|
||||
- label: 确认此问题是一个具体的 Bug。若要进行问答,请开启 [GitHub 讨论](https://github.com/agalwood/Motrix/discussions)。
|
||||
required: true
|
||||
- label: 提供的复现是该 Bug 的 [最小复现例子](https://stackoverflow.com/help/minimal-reproducible-example)。
|
||||
required: true
|
||||
@@ -1,39 +0,0 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Before feedback**
|
||||
Before you feedback, please search for the issues to see if there are similar problems that can solve your problem.
|
||||
|
||||
**Please delete the above and the contents of this line, then fill in the feedback form in the following format, Thanks.**
|
||||
<!-- Windows and Linux versions hide the application menu by default. Please use the keyboard shortcut "Ctrl+Shift+I" to open "Developer Tools" -->
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Environment (please complete the following information):**
|
||||
- OS & Version: [e.g. macOS, Windows, Linux]
|
||||
- Version: [e.g. macOS 10.14.2, Windows 10, Ubuntu 18.04]
|
||||
- Motrix Version: [e.g. v1.1.3, v1.1.0]
|
||||
- Installation package type: [e.g. dmg, AppImage]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
@@ -1,43 +0,0 @@
|
||||
---
|
||||
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,71 @@
|
||||
# For most projects, this workflow file will not need changing; you simply need
|
||||
# to commit it to your repository.
|
||||
#
|
||||
# You may wish to alter this file to override the set of languages analyzed,
|
||||
# or to provide custom queries or build logic.
|
||||
#
|
||||
# ******** NOTE ********
|
||||
# We have attempted to detect the languages in your repository. Please check
|
||||
# the `language` matrix defined below to confirm you have the correct set of
|
||||
# supported CodeQL languages.
|
||||
#
|
||||
name: "CodeQL"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
# The branches below must be a subset of the branches above
|
||||
branches: [ master ]
|
||||
schedule:
|
||||
- cron: '23 8 * * 5'
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
security-events: write
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
language: [ 'javascript' ]
|
||||
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
|
||||
# Learn more:
|
||||
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v2
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
# By default, queries listed here will override any specified in a config file.
|
||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||
# queries: ./path/to/local/query, your-org/your-repo/queries@main
|
||||
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v2
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
|
||||
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
|
||||
# and modify them (or add more) to build your code if your project
|
||||
# uses a compiled language
|
||||
|
||||
#- run: |
|
||||
# make bootstrap
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v2
|
||||
@@ -17,30 +17,27 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Check out Git repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install Node.js, NPM and Yarn
|
||||
uses: actions/setup-node@v1
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 12
|
||||
node-version: 18
|
||||
|
||||
- name: Install Snapcraft
|
||||
uses: samuelmeuli/action-snapcraft@v1
|
||||
uses: samuelmeuli/action-snapcraft@v2
|
||||
# Only install Snapcraft on Ubuntu
|
||||
if: startsWith(matrix.os, 'ubuntu')
|
||||
with:
|
||||
# Log in to Snap Store
|
||||
snapcraft_token: ${{ secrets.snapcraft_token }}
|
||||
env:
|
||||
# Snapcraft
|
||||
SNAPCRAFT_STORE_CREDENTIALS: ${{ 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: Test Snapcraft
|
||||
if: startsWith(matrix.os, 'ubuntu')
|
||||
run: snapcraft --help
|
||||
|
||||
- name: Build/release Electron app
|
||||
uses: samuelmeuli/action-electron-builder@v1
|
||||
uses: motrixapp/action-electron-builder@v2
|
||||
with:
|
||||
build_script_name: 'build:github'
|
||||
# GitHub token, automatically provided to the action
|
||||
@@ -53,8 +50,11 @@ jobs:
|
||||
|
||||
# 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') }}
|
||||
release: ${{ vars.skip_publish != 'true' }}
|
||||
env:
|
||||
# macOS notarization API key
|
||||
API_KEY_ID: ${{ secrets.api_key_id }}
|
||||
API_KEY_ISSUER_ID: ${{ secrets.api_key_issuer_id }}
|
||||
# Snapcraft
|
||||
SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.snapcraft_token }}
|
||||
# macOS notarization
|
||||
TEAM_ID: ${{ secrets.team_id }}
|
||||
APPLE_ID: ${{ secrets.apple_id }}
|
||||
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.apple_app_specific_password }}
|
||||
|
||||
@@ -1,12 +1,25 @@
|
||||
!.gitkeep
|
||||
.DS_Store
|
||||
dist/electron/*
|
||||
dist/web/*
|
||||
.env
|
||||
.idea/
|
||||
.vs/
|
||||
.vscode/
|
||||
*.log
|
||||
node_modules/
|
||||
thumbs.db
|
||||
|
||||
# npm package
|
||||
.npmrc
|
||||
npm-debug.log.*
|
||||
|
||||
# Eslint Cache
|
||||
.eslintcache*
|
||||
|
||||
# electron builder
|
||||
*.provisionprofile
|
||||
build/*.plist
|
||||
node_modules/
|
||||
npm-debug.log
|
||||
npm-debug.log.*
|
||||
thumbs.db
|
||||
!.gitkeep
|
||||
dist/electron/*
|
||||
dist/web/*
|
||||
|
||||
# release
|
||||
release/*
|
||||
.idea/
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
## 🌍 翻译指南
|
||||
|
||||
首先你要确定一个语言的英文简写作为 **locale**,如 en-US,这个 locale 值请严格参考 [Electron 的 Locales 文档](https://electronjs.org/docs/api/locales)
|
||||
首先你要确定一个语言的英文简写作为 **locale**,如 en-US,这个 locale 值请严格参考 [Electron 的 Locales 文档](https://www.electronjs.org/docs/api/app#appgetlocale) 和 [Chromium 源代码](https://source.chromium.org/chromium/chromium/src/+/main:ui/base/l10n/l10n_util.cc)。
|
||||
|
||||
Motrix 的国际化分两部分:
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ Before you start contributing, make sure you already understand [GitHub flow](ht
|
||||
|
||||
## 🌍 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).
|
||||
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://www.electronjs.org/docs/api/app#appgetlocale) and [Chromium Source Code](https://source.chromium.org/chromium/chromium/src/+/main:ui/base/l10n/l10n_util.cc).
|
||||
|
||||
The internationalization of Motrix is divided into two parts:
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Motrix
|
||||
|
||||
<a href="https://motrix.app">
|
||||
<img src="https://cdn.nlark.com/yuque/0/2018/png/129147/1543735425232-a5d2c99f-d788-43e4-9781-558ff6d21027.png" width="256" alt="App Icon" />
|
||||
<img src="./static/512x512.png" width="256" alt="App Icon" />
|
||||
</a>
|
||||
|
||||
## 一款全能的下载工具
|
||||
@@ -24,6 +24,20 @@ Motrix 是一款全能的下载工具,支持下载 HTTP、FTP、BT、磁力链
|
||||
|
||||
建议使用安装包(Motrix-Setup-x.y.z.exe)安装 Motrix 以确保完整的体验,例如关联 torrent 文件,捕获磁力链等。
|
||||
|
||||
如果你在 Windows 是用包管理工具来管理应用,如 [Chocolatey](https://chocolatey.org)、[scoop](https://github.com/lukesampson/scoop),你可以使用它们安装 Motrix。
|
||||
|
||||
#### Chocolatey
|
||||
感谢 [@Yato](https://github.com/iYato) 持续维护着 [Motrix Chocolatey](https://community.chocolatey.org/packages/motrix) 包。要安装 Motrix,请从 `命令行` 或 `PowerShell` 中运行以下命令:
|
||||
|
||||
```bash
|
||||
# 安装
|
||||
choco install motrix
|
||||
|
||||
# 升级
|
||||
choco upgrade motrix
|
||||
```
|
||||
|
||||
#### scoop
|
||||
如果你更喜欢便携版,你可以使用 [scoop](https://github.com/lukesampson/scoop)(需要 Windows 7+,天朝用户可能需要设置 Git 代理)安装最新便携版本的 Motrix。
|
||||
|
||||
```bash
|
||||
@@ -33,16 +47,18 @@ scoop install motrix
|
||||
|
||||
### macOS
|
||||
|
||||
macOS 用户可以使用 `brew cask` 安装 Motrix,感谢 [Mitscherlich](https://github.com/Mitscherlich) 的 [PR](https://github.com/Homebrew/homebrew-cask/pull/59494)。
|
||||
macOS 用户可以使用 `brew cask` 安装 Motrix,感谢 [@Mitscherlich](https://github.com/Mitscherlich) 的 [PR](https://github.com/Homebrew/homebrew-cask/pull/59494)。
|
||||
|
||||
```bash
|
||||
brew update && brew cask install motrix
|
||||
brew update && brew install --cask motrix
|
||||
```
|
||||
|
||||
### Linux
|
||||
|
||||
你可以下载 `AppImage` (适用于所有 Linux 发行版)或 `snap` 来安装 Motrix,更多 Linux 安装包格式请查看 [GitHub/release](https://github.com/agalwood/Motrix/releases) 。
|
||||
|
||||
Motrix 在 Linux 中首次启动可能需要使用 `sudo` 运行,因为可能没有创建下载会话文件的权限 (`/var/cache/aria2.session`)。
|
||||
|
||||
如果你想自己通过编译源码来安装,请阅读 **编译打包** 部分。
|
||||
|
||||
#### AppImage
|
||||
@@ -70,15 +86,24 @@ v1.5.10 提示
|
||||
请更新到 v1.5.12 及以上版本,可以使用键盘组合快捷键 <kbd>Ctrl</kbd> + <kbd>q</kbd> 快速退出应用。
|
||||
|
||||
#### AUR
|
||||
对于 Arch Linux 用户,可以使用 [aur](https://aur.archlinux.org/packages/motrix/) 安装 Motrix,感谢维护者 [weearc](https://github.com/weearc)。
|
||||
对于 Arch Linux 用户,可以使用 [aur](https://aur.archlinux.org/packages/motrix/) 安装 Motrix,感谢维护者 [@weearc](https://github.com/weearc)。
|
||||
|
||||
运行以下命令进行安装:
|
||||
|
||||
```bash
|
||||
yay motrix
|
||||
yay -S motrix
|
||||
```
|
||||
|
||||
Motrix 在 Linux 中首次启动可能需要使用 `sudo` 运行,因为可能没有创建下载会话文件的权限 (`/var/cache/aria2.session`)。
|
||||
#### Flatpak
|
||||
感谢 [@proletarius101](https://github.com/proletarius101) 的 [PR](https://github.com/flathub/flathub/pull/2334),Motrix 已经上架 [Flathub](https://flathub.org/apps/details/net.agalwood.Motrix),喜欢 Flatpak 的 Linux 用户可以尝试。
|
||||
|
||||
```bash
|
||||
# 安装
|
||||
flatpak install flathub net.agalwood.Motrix
|
||||
|
||||
# 运行
|
||||
flatpak run net.agalwood.Motrix
|
||||
```
|
||||
|
||||
## ✨ 特性
|
||||
|
||||
@@ -94,6 +119,7 @@ Motrix 在 Linux 中首次启动可能需要使用 `sudo` 运行,因为可能
|
||||
- 🔔 下载完成后通知
|
||||
- 💻 支持触控栏快捷键 (Mac 专享)
|
||||
- 🤖 常驻系统托盘,操作更加便捷
|
||||
- 📟 系统托盘速度仪表显示实时速度 (Mac 专享)
|
||||
- 🌑 深色模式
|
||||
- 🗑 移除任务时可同时删除相关文件
|
||||
- 🌍 国际化,[查看已可选的语言](#-国际化)
|
||||
@@ -115,13 +141,14 @@ git clone git@github.com:agalwood/Motrix.git
|
||||
|
||||
```bash
|
||||
cd Motrix
|
||||
npm install
|
||||
yarn
|
||||
```
|
||||
|
||||
天朝大陆用户建议使用淘宝的 npm 源
|
||||
|
||||
```bash
|
||||
npm config set registry 'https://registry.npm.taobao.org'
|
||||
yarn config set registry 'https://registry.npmmirror.com'
|
||||
npm config set registry 'https://registry.npmmirror.com'
|
||||
export ELECTRON_MIRROR='https://npm.taobao.org/mirrors/electron/'
|
||||
export SASS_BINARY_SITE='https://npm.taobao.org/mirrors/node-sass'
|
||||
```
|
||||
@@ -133,22 +160,26 @@ export SASS_BINARY_SITE='https://npm.taobao.org/mirrors/node-sass'
|
||||
### 开发模式
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
yarn run dev
|
||||
```
|
||||
|
||||
### 编译打包
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
yarn run build
|
||||
```
|
||||
#### 编译 Apple Silicon 版本
|
||||
|
||||
```bash
|
||||
yarn run build:applesilicon
|
||||
```
|
||||
完成之后可以在项目的 `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 位的)
|
||||
- [Aria2](https://aria2.github.io/)
|
||||
|
||||
## ☑️ TODO
|
||||
|
||||
@@ -164,21 +195,32 @@ npm run build
|
||||
|
||||
| Key | Name | Status |
|
||||
|-------|:--------------------|:-------------|
|
||||
| ar | Arabic | ✔️ [@hadialqattan](https://github.com/hadialqattan), [@AhmedElTabarani](https://github.com/AhmedElTabarani) |
|
||||
| bg | Българският език | ✔️ [@null-none](https://github.com/null-none) |
|
||||
| ca | Català | ✔️ [@marcizhu](https://github.com/marcizhu) |
|
||||
| de | Deutsch | ✔️ [@Schloemicher](https://github.com/Schloemicher) |
|
||||
| el | Ελληνικά | ✔️ [@Likecinema](https://github.com/Likecinema) |
|
||||
| en-US | English | ✔️ |
|
||||
| es | Español | ✔️ [@Chofito](https://github.com/Chofito)|
|
||||
| fa | فارسی | ✔️ [@Nima-Ra](https://github.com/Nima-Ra) |
|
||||
| fr | Français | ✔️ [@gpatarin](https://github.com/gpatarin) |
|
||||
| hu | Hungarian | ✔️ [@zalnaRs](https://github.com/zalnaRs) |
|
||||
| id | Indonesia | ✔️ [@aarestu](https://github.com/aarestu) |
|
||||
| it | Italiano | ✔️ [@blackcat-917](https://github.com/blackcat-917) |
|
||||
| ja | 日本語 | ✔️ [@hbkrkzk](https://github.com/hbkrkzk) |
|
||||
| ko | 한국어 | ✔️ [@KOZ39](https://github.com/KOZ39) |
|
||||
| nb | Norsk Bokmål | ✔️ [@rubjo](https://github.com/rubjo) |
|
||||
| nl | Nederlands | ✔️ [@nickbouwhuis](https://github.com/nickbouwhuis) |
|
||||
| pl | Polski | ✔️ [@KanarekLife](https://github.com/KanarekLife) |
|
||||
| pt-BR | Portuguese (Brazil) | ✔️ [@andrenoberto](https://github.com/andrenoberto) |
|
||||
| ro | Română | ✔️ [@alyn3d](https://github.com/alyn3d) |
|
||||
| ru | Русский | ✔️ [@bladeaweb](https://github.com/bladeaweb) |
|
||||
| th | แบบไทย | ✔️ [@nxanywhere](https://github.com/nxanywhere) |
|
||||
| tr | Türkçe | ✔️ [@abdullah](https://github.com/abdullah) |
|
||||
| uk | Українська | ✔️ [@bladeaweb](https://github.com/bladeaweb) |
|
||||
| vi | Tiếng Việt | ✔️ [@duythanhvn](https://github.com/duythanhvn) |
|
||||
| zh-CN | 简体中文 | ✔️ |
|
||||
| zh-TW | 繁體中文 | ✔️ [@Yukaii](https://github.com/Yukaii) |
|
||||
| zh-TW | 繁體中文 | ✔️ [@Yukaii](https://github.com/Yukaii) [@5idereal](https://github.com/5idereal) |
|
||||
|
||||
## 📜 开源许可
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Motrix
|
||||
|
||||
<a href="https://motrix.app">
|
||||
<img src="https://cdn.nlark.com/yuque/0/2018/png/129147/1543735425232-a5d2c99f-d788-43e4-9781-558ff6d21027.png" width="256" alt="App Icon" />
|
||||
<img src="./static/512x512.png" width="256" alt="App Icon" />
|
||||
</a>
|
||||
|
||||
## A full-featured download manager
|
||||
@@ -24,6 +24,20 @@ Download from [GitHub Releases](https://github.com/agalwood/Motrix/releases) and
|
||||
|
||||
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 use package management tools to manage applications on Windows, such as [Chocolatey](https://chocolatey.org), [scoop](https://github.com/lukesampson/scoop). You can use them to install Motrix.
|
||||
|
||||
#### Chocolatey
|
||||
Thanks to [@Yato](https://github.com/iYato) for continuing to maintain the [Motrix Chocolatey](https://community.chocolatey.org/packages/motrix) package. To install motrix, run the following command from the `command line` or from `PowerShell`:
|
||||
|
||||
```bash
|
||||
# Install
|
||||
choco install motrix
|
||||
|
||||
# Upgrade
|
||||
choco upgrade motrix
|
||||
```
|
||||
|
||||
#### scoop
|
||||
If you prefer the portable version, you can use [scoop](https://github.com/lukesampson/scoop) (need Windows 7+) to install Motrix.
|
||||
|
||||
```bash
|
||||
@@ -33,16 +47,18 @@ 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).
|
||||
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
|
||||
brew update && brew install --cask motrix
|
||||
```
|
||||
|
||||
### Linux
|
||||
|
||||
You can download the `AppImage` (for all Linux distributions) or `snap` to install Motrix, see [GitHub/release](https://github.com/agalwood/Motrix/releases) for more Linux installation package formats.
|
||||
|
||||
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`).
|
||||
|
||||
If you want to build from source code, please read the **Build** section.
|
||||
|
||||
#### AppImage
|
||||
@@ -72,15 +88,24 @@ Please unchecked Preferences--Basic Settings--Hide App Menu (Windows & Linux Onl
|
||||
Please update to v1.5.12 and above, you can use the keyboard shortcut <kbd>Ctrl</kbd> + <kbd>q</kbd> to quickly exit the application.
|
||||
|
||||
#### AUR
|
||||
For Arch Linux users, Motrix is available in [aur](https://aur.archlinux.org/packages/motrix/), thanks to the maintainer [weearc](https://github.com/weearc).
|
||||
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
|
||||
yay -S 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`).
|
||||
#### Flatpak
|
||||
Thanks to the [PR](https://github.com/flathub/flathub/pull/2334) of [@proletarius101](https://github.com/proletarius101), Motrix has been listed [Flathub](https://flathub.org/apps/details/net.agalwood.Motrix), Linux users who like the Flatpak can try it.
|
||||
|
||||
```bash
|
||||
# Install
|
||||
flatpak install flathub net.agalwood.Motrix
|
||||
|
||||
# Run
|
||||
flatpak run net.agalwood.Motrix
|
||||
```
|
||||
|
||||
## ✨ Features
|
||||
|
||||
@@ -96,6 +121,7 @@ Motrix may need to run with `sudo` for the first time in Linux because there is
|
||||
- 🔔 Download completed Notification
|
||||
- 💻 Ready for Touch Bar (Mac only)
|
||||
- 🤖 Resident system tray for quick operation
|
||||
- 📟 Tray speed meter displays real-time speed (Mac only)
|
||||
- 🌑 Dark mode
|
||||
- 🗑 Delete related files when removing tasks (optional)
|
||||
- 🌍 I18n, [View supported languages](#-internationalization).
|
||||
@@ -117,7 +143,7 @@ git clone git@github.com:agalwood/Motrix.git
|
||||
|
||||
```bash
|
||||
cd Motrix
|
||||
npm install
|
||||
yarn
|
||||
```
|
||||
|
||||
> Error: Electron failed to install correctly, please delete node_modules/electron and try installing again
|
||||
@@ -127,13 +153,18 @@ npm install
|
||||
### Dev Mode
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
yarn run dev
|
||||
```
|
||||
|
||||
### Build Release
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
yarn run build
|
||||
```
|
||||
#### Build for Apple Silicon
|
||||
|
||||
```bash
|
||||
yarn run build:applesilicon
|
||||
```
|
||||
|
||||
After building, the application will be found in the project's `release` directory.
|
||||
@@ -142,7 +173,7 @@ After building, the application will be found in the project's `release` directo
|
||||
|
||||
- [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)
|
||||
- [Aria2](https://aria2.github.io/)
|
||||
|
||||
## ☑️ TODO
|
||||
|
||||
@@ -158,21 +189,32 @@ Translations into versions for other languages are welcome 🧐! Please read the
|
||||
|
||||
| Key | Name | Status |
|
||||
|-------|:--------------------|:-------------|
|
||||
| ar | Arabic | ✔️ [@hadialqattan](https://github.com/hadialqattan), [@AhmedElTabarani](https://github.com/AhmedElTabarani) |
|
||||
| bg | Българският език | ✔️ [@null-none](https://github.com/null-none) |
|
||||
| ca | Català | ✔️ [@marcizhu](https://github.com/marcizhu) |
|
||||
| de | Deutsch | ✔️ [@Schloemicher](https://github.com/Schloemicher) |
|
||||
| el | Ελληνικά | ✔️ [@Likecinema](https://github.com/Likecinema) |
|
||||
| en-US | English | ✔️ |
|
||||
| es | Español | ✔️ [@Chofito](https://github.com/Chofito)|
|
||||
| fa | فارسی | ✔️ [@Nima-Ra](https://github.com/Nima-Ra) |
|
||||
| fr | Français | ✔️ [@gpatarin](https://github.com/gpatarin) |
|
||||
| hu | Hungarian | ✔️ [@zalnaRs](https://github.com/zalnaRs) |
|
||||
| id | Indonesia | ✔️ [@aarestu](https://github.com/aarestu) |
|
||||
| it | Italiano | ✔️ [@blackcat-917](https://github.com/blackcat-917) |
|
||||
| ja | 日本語 | ✔️ [@hbkrkzk](https://github.com/hbkrkzk) |
|
||||
| ko | 한국어 | ✔️ [@KOZ39](https://github.com/KOZ39) |
|
||||
| nb | Norsk Bokmål | ✔️ [@rubjo](https://github.com/rubjo) |
|
||||
| nl | Nederlands | ✔️ [@nickbouwhuis](https://github.com/nickbouwhuis) |
|
||||
| pl | Polski | ✔️ [@KanarekLife](https://github.com/KanarekLife) |
|
||||
| pt-BR | Portuguese (Brazil) | ✔️ [@andrenoberto](https://github.com/andrenoberto) |
|
||||
| ro | Română | ✔️ [@alyn3d](https://github.com/alyn3d) |
|
||||
| ru | Русский | ✔️ [@bladeaweb](https://github.com/bladeaweb) |
|
||||
| th | แบบไทย | ✔️ [@nxanywhere](https://github.com/nxanywhere) |
|
||||
| tr | Türkçe | ✔️ [@abdullah](https://github.com/abdullah) |
|
||||
| uk | Українська | ✔️ [@bladeaweb](https://github.com/bladeaweb) |
|
||||
| vi | Tiếng Việt | ✔️ [@duythanhvn](https://github.com/duythanhvn) |
|
||||
| zh-CN | 简体中文 | ✔️ |
|
||||
| zh-TW | 繁體中文 | ✔️ [@Yukaii](https://github.com/Yukaii) |
|
||||
| zh-TW | 繁體中文 | ✔️ [@Yukaii](https://github.com/Yukaii) [@5idereal](https://github.com/5idereal) |
|
||||
|
||||
## 📜 License
|
||||
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
provider: generic
|
||||
url: 'https://dl.motrix.app/release/'
|
||||
url: 'https://dl.motrix.app/releases/'
|
||||
|
||||
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 24 KiB |
@@ -1,6 +1,7 @@
|
||||
require('dotenv').config()
|
||||
const { notarize } = require('electron-notarize')
|
||||
const pkg = require('../package.json')
|
||||
const { join } = require('path')
|
||||
const { notarize } = require('@electron/notarize')
|
||||
const { appId } = require('../electron-builder.json')
|
||||
|
||||
exports.default = async function (context) {
|
||||
const { electronPlatformName, appOutDir } = context
|
||||
@@ -9,19 +10,27 @@ exports.default = async function (context) {
|
||||
}
|
||||
|
||||
const skipNotarize = process.env.SKIP_NOTARIZE
|
||||
if (skipNotarize === 'yes') {
|
||||
console.log('skipping notarize')
|
||||
if (skipNotarize === 'true') {
|
||||
console.log('Skipping notarize')
|
||||
return
|
||||
}
|
||||
|
||||
const appBundleId = pkg.build.appId
|
||||
const appBundleId = appId
|
||||
const appName = context.packager.appInfo.productFilename
|
||||
const appPath = join(appOutDir, `${appName}.app`)
|
||||
|
||||
return await notarize({
|
||||
appBundleId,
|
||||
appPath: `${appOutDir}/${appName}.app`,
|
||||
ascProvider: process.env.TEAM_ID,
|
||||
appleId: process.env.APPLE_ID,
|
||||
appleIdPassword: process.env.APPLE_ID_PWD
|
||||
})
|
||||
try {
|
||||
await notarize({
|
||||
tool: 'notarytool',
|
||||
appBundleId,
|
||||
appPath,
|
||||
teamId: process.env.TEAM_ID,
|
||||
appleId: process.env.APPLE_ID,
|
||||
appleIdPassword: process.env.APPLE_APP_SPECIFIC_PASSWORD
|
||||
})
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
|
||||
console.log(`Done notarizing ${appId}`)
|
||||
}
|
||||
|
||||
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 4.5 KiB |
|
After Width: | Height: | Size: 5.7 KiB |
|
After Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 75 KiB After Width: | Height: | Size: 62 KiB |
@@ -0,0 +1,200 @@
|
||||
{
|
||||
"productName": "Motrix",
|
||||
"appId": "app.motrix.native",
|
||||
"afterPack": "./build/afterPackHook.js",
|
||||
"afterSign": "./build/afterSignHook.js",
|
||||
"fileAssociations": [
|
||||
{
|
||||
"ext": "torrent",
|
||||
"mimeType": "application/x-bittorrent",
|
||||
"name": "Torrent",
|
||||
"role": "Viewer"
|
||||
}
|
||||
],
|
||||
"asar": true,
|
||||
"directories": {
|
||||
"output": "release"
|
||||
},
|
||||
"files": [
|
||||
"dist/electron/**/*"
|
||||
],
|
||||
"protocols": [
|
||||
{
|
||||
"name": "Motrix Protocol",
|
||||
"schemes": [
|
||||
"mo",
|
||||
"motrix"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Magnet Protocol",
|
||||
"schemes": [
|
||||
"magnet"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Thunder Protocol",
|
||||
"schemes": [
|
||||
"thunder"
|
||||
]
|
||||
}
|
||||
],
|
||||
"dmg": {
|
||||
"window": {
|
||||
"width": 540,
|
||||
"height": 380
|
||||
},
|
||||
"contents": [
|
||||
{
|
||||
"x": 410,
|
||||
"y": 230,
|
||||
"type": "link",
|
||||
"path": "/Applications"
|
||||
},
|
||||
{
|
||||
"x": 130,
|
||||
"y": 230,
|
||||
"type": "file"
|
||||
}
|
||||
]
|
||||
},
|
||||
"mac": {
|
||||
"target": [
|
||||
{
|
||||
"target": "dmg",
|
||||
"arch": [
|
||||
"x64",
|
||||
"arm64",
|
||||
"universal"
|
||||
]
|
||||
},
|
||||
{
|
||||
"target": "zip",
|
||||
"arch": [
|
||||
"x64",
|
||||
"arm64",
|
||||
"universal"
|
||||
]
|
||||
}
|
||||
],
|
||||
"type": "development",
|
||||
"darkModeSupport": true,
|
||||
"hardenedRuntime": false,
|
||||
"notarize": false,
|
||||
"extraResources": {
|
||||
"from": "./extra/darwin/${arch}/",
|
||||
"to": "./",
|
||||
"filter": [
|
||||
"**/*"
|
||||
]
|
||||
},
|
||||
"category": "public.app-category.utilities"
|
||||
},
|
||||
"win": {
|
||||
"target": [
|
||||
{
|
||||
"target": "nsis",
|
||||
"arch": [
|
||||
"x64",
|
||||
"ia32"
|
||||
]
|
||||
},
|
||||
{
|
||||
"target": "appx",
|
||||
"arch": [
|
||||
"x64",
|
||||
"ia32"
|
||||
]
|
||||
},
|
||||
{
|
||||
"target": "zip",
|
||||
"arch": [
|
||||
"x64",
|
||||
"ia32"
|
||||
]
|
||||
},
|
||||
{
|
||||
"target": "portable",
|
||||
"arch": [
|
||||
"x64",
|
||||
"ia32"
|
||||
]
|
||||
}
|
||||
],
|
||||
"extraResources": {
|
||||
"from": "./extra/win32/${arch}/",
|
||||
"to": "./",
|
||||
"filter": [
|
||||
"**/*"
|
||||
]
|
||||
}
|
||||
},
|
||||
"nsis": {
|
||||
"artifactName": "${productName}-Setup-${version}.${ext}",
|
||||
"oneClick": false,
|
||||
"allowToChangeInstallationDirectory": true
|
||||
},
|
||||
"appx": {
|
||||
"artifactName": "${productName}-${version}-${arch}.${ext}",
|
||||
"applicationId": "app.motrix.native",
|
||||
"identityName": "59744DrrOot.Motrix",
|
||||
"publisher": "CN=5BB4961D-30D8-4993-9ADF-05E1E1F5A395",
|
||||
"publisherDisplayName": "Dr_rOot"
|
||||
},
|
||||
"portable": {
|
||||
"artifactName": "${productName}-${version}-${arch}.${ext}"
|
||||
},
|
||||
"linux": {
|
||||
"category": "Network",
|
||||
"mimeTypes": [
|
||||
"application/x-bittorrent",
|
||||
"x-scheme-handler/magnet"
|
||||
],
|
||||
"target": [
|
||||
{
|
||||
"target": "AppImage",
|
||||
"arch": [
|
||||
"x64",
|
||||
"arm64",
|
||||
"armv7l"
|
||||
]
|
||||
},
|
||||
{
|
||||
"target": "deb",
|
||||
"arch": [
|
||||
"x64",
|
||||
"arm64",
|
||||
"armv7l"
|
||||
]
|
||||
},
|
||||
{
|
||||
"target": "rpm",
|
||||
"arch": [
|
||||
"x64"
|
||||
]
|
||||
},
|
||||
{
|
||||
"target": "snap",
|
||||
"arch": [
|
||||
"x64"
|
||||
]
|
||||
}
|
||||
],
|
||||
"extraResources": {
|
||||
"from": "./extra/linux/${arch}/",
|
||||
"to": "./",
|
||||
"filter": [
|
||||
"**/*"
|
||||
]
|
||||
}
|
||||
},
|
||||
"publish": [
|
||||
{
|
||||
"provider": "generic",
|
||||
"url": "https://dl.motrix.app/releases/"
|
||||
},
|
||||
{
|
||||
"provider": "github"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -19,9 +19,11 @@ rpc-listen-all=true
|
||||
# Save a control file(*.aria2) every SEC seconds.
|
||||
auto-save-interval=10
|
||||
# Enable disk cache.
|
||||
disk-cache=32M
|
||||
disk-cache=64M
|
||||
# Specify file allocation method.
|
||||
file-allocation=falloc
|
||||
file-allocation=none
|
||||
# No file allocation is made for files whose size is smaller than SIZE
|
||||
no-file-allocation-limit=64M
|
||||
# Save error/unfinished downloads to a file specified by --save-session option every SEC seconds.
|
||||
save-session-interval=10
|
||||
|
||||
@@ -33,13 +35,25 @@ bt-detach-seed-only=true
|
||||
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
|
||||
max-file-not-found=10
|
||||
# Set number of tries.
|
||||
max-tries=5
|
||||
max-tries=0
|
||||
# Set the seconds to wait between retries. When SEC > 0, aria2 will retry downloads when the HTTP server returns a 503 response.
|
||||
retry-wait=10
|
||||
# Set the connect timeout in seconds to establish connection to HTTP/FTP/proxy server. After the connection is established, this option makes no effect and --timeout option is used instead.
|
||||
connect-timeout=10
|
||||
# Set timeout in seconds.
|
||||
timeout=10
|
||||
# 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
|
||||
# Send Accept: deflate, gzip request header.
|
||||
http-accept-gzip=true
|
||||
# Retrieve timestamp of the remote file from the remote HTTP/FTP server and if it is available, apply it to the local file.
|
||||
remote-time=true
|
||||
# Set interval in seconds to output download progress summary. Setting 0 suppresses the output.
|
||||
summary-interval=0
|
||||
# Handle quoted string in Content-Disposition header as UTF-8 instead of ISO-8859-1, for example, the filename parameter, but not the extended version filename*.
|
||||
content-disposition-default-utf8=true
|
||||
|
||||
|
||||
################ BT Task ################
|
||||
@@ -50,13 +64,17 @@ bt-enable-lpd=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
|
||||
bt-max-peers=128
|
||||
# Try to download first and last pieces of each file first. This is useful for previewing files.
|
||||
bt-prioritize-piece=head
|
||||
# Removes the unselected files when download is completed in BitTorrent.
|
||||
bt-remove-unselected-file=true
|
||||
# Seed previously downloaded files without verifying piece hashes.
|
||||
bt-seed-unverified=true
|
||||
bt-seed-unverified=false
|
||||
# Set the connect timeout in seconds to establish connection to tracker. After the connection is established, this option makes no effect and --bt-tracker-timeout option is used instead.
|
||||
bt-tracker-connect-timeout=10
|
||||
# Set timeout in seconds.
|
||||
bt-tracker-timeout=10
|
||||
# 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.
|
||||
@@ -68,6 +86,6 @@ 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
|
||||
peer-agent=Transmission/3.00
|
||||
# Specify the prefix of peer ID.
|
||||
peer-id-prefix=-TR2940-
|
||||
peer-id-prefix=-TR3000-
|
||||
@@ -0,0 +1,91 @@
|
||||
###############################
|
||||
# Motrix macOS Aria2 config file
|
||||
#
|
||||
# @see https://aria2.github.io/manual/en/html/aria2c.html
|
||||
#
|
||||
###############################
|
||||
|
||||
|
||||
################ RPC ################
|
||||
# Enable JSON-RPC/XML-RPC server.
|
||||
enable-rpc=true
|
||||
# Add Access-Control-Allow-Origin header field with value * to the RPC response.
|
||||
rpc-allow-origin-all=true
|
||||
# Listen incoming JSON-RPC/XML-RPC requests on all network interfaces.
|
||||
rpc-listen-all=true
|
||||
|
||||
|
||||
################ File system ################
|
||||
# Save a control file(*.aria2) every SEC seconds.
|
||||
auto-save-interval=10
|
||||
# Enable disk cache.
|
||||
disk-cache=64M
|
||||
# Specify file allocation method.
|
||||
file-allocation=none
|
||||
# No file allocation is made for files whose size is smaller than SIZE
|
||||
no-file-allocation-limit=64M
|
||||
# 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=10
|
||||
# Set number of tries.
|
||||
max-tries=0
|
||||
# Set the seconds to wait between retries. When SEC > 0, aria2 will retry downloads when the HTTP server returns a 503 response.
|
||||
retry-wait=10
|
||||
# Set the connect timeout in seconds to establish connection to HTTP/FTP/proxy server. After the connection is established, this option makes no effect and --timeout option is used instead.
|
||||
connect-timeout=10
|
||||
# Set timeout in seconds.
|
||||
timeout=10
|
||||
# aria2 does not split less than 2*SIZE byte range.
|
||||
min-split-size=1M
|
||||
# Send Accept: deflate, gzip request header.
|
||||
http-accept-gzip=true
|
||||
# Retrieve timestamp of the remote file from the remote HTTP/FTP server and if it is available, apply it to the local file.
|
||||
remote-time=true
|
||||
# Set interval in seconds to output download progress summary. Setting 0 suppresses the output.
|
||||
summary-interval=0
|
||||
# Handle quoted string in Content-Disposition header as UTF-8 instead of ISO-8859-1, for example, the filename parameter, but not the extended version filename*.
|
||||
content-disposition-default-utf8=true
|
||||
|
||||
|
||||
################ 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=128
|
||||
# Try to download first and last pieces of each file first. This is useful for previewing files.
|
||||
bt-prioritize-piece=head
|
||||
# Removes the unselected files when download is completed in BitTorrent.
|
||||
bt-remove-unselected-file=true
|
||||
# Seed previously downloaded files without verifying piece hashes.
|
||||
bt-seed-unverified=false
|
||||
# Set the connect timeout in seconds to establish connection to tracker. After the connection is established, this option makes no effect and --bt-tracker-timeout option is used instead.
|
||||
bt-tracker-connect-timeout=10
|
||||
# Set timeout in seconds.
|
||||
bt-tracker-timeout=10
|
||||
# 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/3.00
|
||||
# Specify the prefix of peer ID.
|
||||
peer-id-prefix=-TR3000-
|
||||
@@ -19,9 +19,11 @@ rpc-listen-all=true
|
||||
# Save a control file(*.aria2) every SEC seconds.
|
||||
auto-save-interval=10
|
||||
# Enable disk cache.
|
||||
disk-cache=32M
|
||||
disk-cache=64M
|
||||
# Specify file allocation method.
|
||||
file-allocation=trunc
|
||||
# No file allocation is made for files whose size is smaller than SIZE
|
||||
no-file-allocation-limit=64M
|
||||
# Save error/unfinished downloads to a file specified by --save-session option every SEC seconds.
|
||||
save-session-interval=10
|
||||
|
||||
@@ -33,13 +35,25 @@ bt-detach-seed-only=true
|
||||
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
|
||||
max-file-not-found=10
|
||||
# Set number of tries.
|
||||
max-tries=5
|
||||
max-tries=0
|
||||
# Set the seconds to wait between retries. When SEC > 0, aria2 will retry downloads when the HTTP server returns a 503 response.
|
||||
retry-wait=10
|
||||
# Set the connect timeout in seconds to establish connection to HTTP/FTP/proxy server. After the connection is established, this option makes no effect and --timeout option is used instead.
|
||||
connect-timeout=10
|
||||
# Set timeout in seconds.
|
||||
timeout=10
|
||||
# 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
|
||||
# Send Accept: deflate, gzip request header.
|
||||
http-accept-gzip=true
|
||||
# Retrieve timestamp of the remote file from the remote HTTP/FTP server and if it is available, apply it to the local file.
|
||||
remote-time=true
|
||||
# Set interval in seconds to output download progress summary. Setting 0 suppresses the output.
|
||||
summary-interval=0
|
||||
# Handle quoted string in Content-Disposition header as UTF-8 instead of ISO-8859-1, for example, the filename parameter, but not the extended version filename*.
|
||||
content-disposition-default-utf8=true
|
||||
|
||||
|
||||
################ BT Task ################
|
||||
@@ -50,13 +64,17 @@ bt-enable-lpd=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
|
||||
bt-max-peers=128
|
||||
# Try to download first and last pieces of each file first. This is useful for previewing files.
|
||||
bt-prioritize-piece=head
|
||||
# Removes the unselected files when download is completed in BitTorrent.
|
||||
bt-remove-unselected-file=true
|
||||
# Seed previously downloaded files without verifying piece hashes.
|
||||
bt-seed-unverified=true
|
||||
bt-seed-unverified=false
|
||||
# Set the connect timeout in seconds to establish connection to tracker. After the connection is established, this option makes no effect and --bt-tracker-timeout option is used instead.
|
||||
bt-tracker-connect-timeout=10
|
||||
# Set timeout in seconds.
|
||||
bt-tracker-timeout=10
|
||||
# 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.
|
||||
@@ -68,6 +86,6 @@ 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
|
||||
peer-agent=Transmission/3.00
|
||||
# Specify the prefix of peer ID.
|
||||
peer-id-prefix=-TR2940-
|
||||
peer-id-prefix=-TR3000-
|
||||
@@ -0,0 +1,91 @@
|
||||
###############################
|
||||
# Motrix Linux Aria2 config file
|
||||
#
|
||||
# @see https://aria2.github.io/manual/en/html/aria2c.html
|
||||
#
|
||||
###############################
|
||||
|
||||
|
||||
################ RPC ################
|
||||
# Enable JSON-RPC/XML-RPC server.
|
||||
enable-rpc=true
|
||||
# Add Access-Control-Allow-Origin header field with value * to the RPC response.
|
||||
rpc-allow-origin-all=true
|
||||
# Listen incoming JSON-RPC/XML-RPC requests on all network interfaces.
|
||||
rpc-listen-all=true
|
||||
|
||||
|
||||
################ File system ################
|
||||
# Save a control file(*.aria2) every SEC seconds.
|
||||
auto-save-interval=10
|
||||
# Enable disk cache.
|
||||
disk-cache=64M
|
||||
# Specify file allocation method.
|
||||
file-allocation=trunc
|
||||
# No file allocation is made for files whose size is smaller than SIZE
|
||||
no-file-allocation-limit=64M
|
||||
# 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=10
|
||||
# Set number of tries.
|
||||
max-tries=0
|
||||
# Set the seconds to wait between retries. When SEC > 0, aria2 will retry downloads when the HTTP server returns a 503 response.
|
||||
retry-wait=10
|
||||
# Set the connect timeout in seconds to establish connection to HTTP/FTP/proxy server. After the connection is established, this option makes no effect and --timeout option is used instead.
|
||||
connect-timeout=10
|
||||
# Set timeout in seconds.
|
||||
timeout=10
|
||||
# aria2 does not split less than 2*SIZE byte range.
|
||||
min-split-size=1M
|
||||
# Send Accept: deflate, gzip request header.
|
||||
http-accept-gzip=true
|
||||
# Retrieve timestamp of the remote file from the remote HTTP/FTP server and if it is available, apply it to the local file.
|
||||
remote-time=true
|
||||
# Set interval in seconds to output download progress summary. Setting 0 suppresses the output.
|
||||
summary-interval=0
|
||||
# Handle quoted string in Content-Disposition header as UTF-8 instead of ISO-8859-1, for example, the filename parameter, but not the extended version filename*.
|
||||
content-disposition-default-utf8=true
|
||||
|
||||
|
||||
################ 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=128
|
||||
# Try to download first and last pieces of each file first. This is useful for previewing files.
|
||||
bt-prioritize-piece=head
|
||||
# Removes the unselected files when download is completed in BitTorrent.
|
||||
bt-remove-unselected-file=true
|
||||
# Seed previously downloaded files without verifying piece hashes.
|
||||
bt-seed-unverified=false
|
||||
# Set the connect timeout in seconds to establish connection to tracker. After the connection is established, this option makes no effect and --bt-tracker-timeout option is used instead.
|
||||
bt-tracker-connect-timeout=10
|
||||
# Set timeout in seconds.
|
||||
bt-tracker-timeout=10
|
||||
# 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/3.00
|
||||
# Specify the prefix of peer ID.
|
||||
peer-id-prefix=-TR3000-
|
||||
@@ -0,0 +1,91 @@
|
||||
###############################
|
||||
# Motrix Linux Aria2 config file
|
||||
#
|
||||
# @see https://aria2.github.io/manual/en/html/aria2c.html
|
||||
#
|
||||
###############################
|
||||
|
||||
|
||||
################ RPC ################
|
||||
# Enable JSON-RPC/XML-RPC server.
|
||||
enable-rpc=true
|
||||
# Add Access-Control-Allow-Origin header field with value * to the RPC response.
|
||||
rpc-allow-origin-all=true
|
||||
# Listen incoming JSON-RPC/XML-RPC requests on all network interfaces.
|
||||
rpc-listen-all=true
|
||||
|
||||
|
||||
################ File system ################
|
||||
# Save a control file(*.aria2) every SEC seconds.
|
||||
auto-save-interval=10
|
||||
# Enable disk cache.
|
||||
disk-cache=64M
|
||||
# Specify file allocation method.
|
||||
file-allocation=trunc
|
||||
# No file allocation is made for files whose size is smaller than SIZE
|
||||
no-file-allocation-limit=64M
|
||||
# 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=10
|
||||
# Set number of tries.
|
||||
max-tries=0
|
||||
# Set the seconds to wait between retries. When SEC > 0, aria2 will retry downloads when the HTTP server returns a 503 response.
|
||||
retry-wait=10
|
||||
# Set the connect timeout in seconds to establish connection to HTTP/FTP/proxy server. After the connection is established, this option makes no effect and --timeout option is used instead.
|
||||
connect-timeout=10
|
||||
# Set timeout in seconds.
|
||||
timeout=10
|
||||
# aria2 does not split less than 2*SIZE byte range.
|
||||
min-split-size=1M
|
||||
# Send Accept: deflate, gzip request header.
|
||||
http-accept-gzip=true
|
||||
# Retrieve timestamp of the remote file from the remote HTTP/FTP server and if it is available, apply it to the local file.
|
||||
remote-time=true
|
||||
# Set interval in seconds to output download progress summary. Setting 0 suppresses the output.
|
||||
summary-interval=0
|
||||
# Handle quoted string in Content-Disposition header as UTF-8 instead of ISO-8859-1, for example, the filename parameter, but not the extended version filename*.
|
||||
content-disposition-default-utf8=true
|
||||
|
||||
|
||||
################ 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=128
|
||||
# Try to download first and last pieces of each file first. This is useful for previewing files.
|
||||
bt-prioritize-piece=head
|
||||
# Removes the unselected files when download is completed in BitTorrent.
|
||||
bt-remove-unselected-file=true
|
||||
# Seed previously downloaded files without verifying piece hashes.
|
||||
bt-seed-unverified=false
|
||||
# Set the connect timeout in seconds to establish connection to tracker. After the connection is established, this option makes no effect and --bt-tracker-timeout option is used instead.
|
||||
bt-tracker-connect-timeout=10
|
||||
# Set timeout in seconds.
|
||||
bt-tracker-timeout=10
|
||||
# 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/3.00
|
||||
# Specify the prefix of peer ID.
|
||||
peer-id-prefix=-TR3000-
|
||||
@@ -0,0 +1,91 @@
|
||||
###############################
|
||||
# Motrix Windows Aria2 config file
|
||||
#
|
||||
# @see https://aria2.github.io/manual/en/html/aria2c.html
|
||||
#
|
||||
###############################
|
||||
|
||||
|
||||
################ RPC ################
|
||||
# Enable JSON-RPC/XML-RPC server.
|
||||
enable-rpc=true
|
||||
# Add Access-Control-Allow-Origin header field with value * to the RPC response.
|
||||
rpc-allow-origin-all=true
|
||||
# Listen incoming JSON-RPC/XML-RPC requests on all network interfaces.
|
||||
rpc-listen-all=true
|
||||
|
||||
|
||||
################ File system ################
|
||||
# Save a control file(*.aria2) every SEC seconds.
|
||||
auto-save-interval=10
|
||||
# Enable disk cache.
|
||||
disk-cache=64M
|
||||
# Specify file allocation method.
|
||||
file-allocation=none
|
||||
# No file allocation is made for files whose size is smaller than SIZE
|
||||
no-file-allocation-limit=64M
|
||||
# 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=10
|
||||
# Set number of tries.
|
||||
max-tries=0
|
||||
# Set the seconds to wait between retries. When SEC > 0, aria2 will retry downloads when the HTTP server returns a 503 response.
|
||||
retry-wait=10
|
||||
# Set the connect timeout in seconds to establish connection to HTTP/FTP/proxy server. After the connection is established, this option makes no effect and --timeout option is used instead.
|
||||
connect-timeout=10
|
||||
# Set timeout in seconds.
|
||||
timeout=10
|
||||
# aria2 does not split less than 2*SIZE byte range.
|
||||
min-split-size=1M
|
||||
# Send Accept: deflate, gzip request header.
|
||||
http-accept-gzip=true
|
||||
# Retrieve timestamp of the remote file from the remote HTTP/FTP server and if it is available, apply it to the local file.
|
||||
remote-time=true
|
||||
# Set interval in seconds to output download progress summary. Setting 0 suppresses the output.
|
||||
summary-interval=0
|
||||
# Handle quoted string in Content-Disposition header as UTF-8 instead of ISO-8859-1, for example, the filename parameter, but not the extended version filename*.
|
||||
content-disposition-default-utf8=true
|
||||
|
||||
|
||||
################ 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=128
|
||||
# Try to download first and last pieces of each file first. This is useful for previewing files.
|
||||
bt-prioritize-piece=head
|
||||
# Removes the unselected files when download is completed in BitTorrent.
|
||||
bt-remove-unselected-file=true
|
||||
# Seed previously downloaded files without verifying piece hashes.
|
||||
bt-seed-unverified=false
|
||||
# Set the connect timeout in seconds to establish connection to tracker. After the connection is established, this option makes no effect and --bt-tracker-timeout option is used instead.
|
||||
bt-tracker-connect-timeout=10
|
||||
# Set timeout in seconds.
|
||||
bt-tracker-timeout=10
|
||||
# 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/3.00
|
||||
# Specify the prefix of peer ID.
|
||||
peer-id-prefix=-TR3000-
|
||||
@@ -19,9 +19,11 @@ rpc-listen-all=true
|
||||
# Save a control file(*.aria2) every SEC seconds.
|
||||
auto-save-interval=10
|
||||
# Enable disk cache.
|
||||
disk-cache=32M
|
||||
disk-cache=64M
|
||||
# Specify file allocation method.
|
||||
file-allocation=falloc
|
||||
# No file allocation is made for files whose size is smaller than SIZE
|
||||
no-file-allocation-limit=64M
|
||||
# Save error/unfinished downloads to a file specified by --save-session option every SEC seconds.
|
||||
save-session-interval=10
|
||||
|
||||
@@ -33,13 +35,25 @@ bt-detach-seed-only=true
|
||||
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
|
||||
max-file-not-found=10
|
||||
# Set number of tries.
|
||||
max-tries=5
|
||||
max-tries=0
|
||||
# Set the seconds to wait between retries. When SEC > 0, aria2 will retry downloads when the HTTP server returns a 503 response.
|
||||
retry-wait=10
|
||||
# Set the connect timeout in seconds to establish connection to HTTP/FTP/proxy server. After the connection is established, this option makes no effect and --timeout option is used instead.
|
||||
connect-timeout=10
|
||||
# Set timeout in seconds.
|
||||
timeout=10
|
||||
# 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
|
||||
# Send Accept: deflate, gzip request header.
|
||||
http-accept-gzip=true
|
||||
# Retrieve timestamp of the remote file from the remote HTTP/FTP server and if it is available, apply it to the local file.
|
||||
remote-time=true
|
||||
# Set interval in seconds to output download progress summary. Setting 0 suppresses the output.
|
||||
summary-interval=0
|
||||
# Handle quoted string in Content-Disposition header as UTF-8 instead of ISO-8859-1, for example, the filename parameter, but not the extended version filename*.
|
||||
content-disposition-default-utf8=true
|
||||
|
||||
|
||||
################ BT Task ################
|
||||
@@ -50,13 +64,17 @@ bt-enable-lpd=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
|
||||
bt-max-peers=128
|
||||
# Try to download first and last pieces of each file first. This is useful for previewing files.
|
||||
bt-prioritize-piece=head
|
||||
# Removes the unselected files when download is completed in BitTorrent.
|
||||
bt-remove-unselected-file=true
|
||||
# Seed previously downloaded files without verifying piece hashes.
|
||||
bt-seed-unverified=true
|
||||
bt-seed-unverified=false
|
||||
# Set the connect timeout in seconds to establish connection to tracker. After the connection is established, this option makes no effect and --bt-tracker-timeout option is used instead.
|
||||
bt-tracker-connect-timeout=10
|
||||
# Set timeout in seconds.
|
||||
bt-tracker-timeout=10
|
||||
# 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.
|
||||
@@ -68,6 +86,6 @@ 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
|
||||
peer-agent=Transmission/3.00
|
||||
# Specify the prefix of peer ID.
|
||||
peer-id-prefix=-TR2940-
|
||||
peer-id-prefix=-TR3000-
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"./src/renderer/*"
|
||||
],
|
||||
"@shared/*": [
|
||||
"./src/shared/*"
|
||||
]
|
||||
}
|
||||
},
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
@@ -1,13 +1,13 @@
|
||||
{
|
||||
"name": "Motrix",
|
||||
"version": "1.5.14",
|
||||
"version": "1.8.19",
|
||||
"description": "A full-featured download manager",
|
||||
"homepage": "https://motrix.app",
|
||||
"author": {
|
||||
"name": "AGALWOOD",
|
||||
"name": "Dr_rOot",
|
||||
"email": "agalwood.net@gmail.com"
|
||||
},
|
||||
"copyright": "Copyright© AGALWOOD",
|
||||
"copyright": "Copyright© Dr_rOot",
|
||||
"license": "MIT",
|
||||
"main": "./dist/electron/main.js",
|
||||
"repository": {
|
||||
@@ -17,247 +17,98 @@
|
||||
"scripts": {
|
||||
"release": "npm run build --publish onTagOrDraft",
|
||||
"build": "node .electron-vue/build.js && electron-builder",
|
||||
"build:applesilicon": "node .electron-vue/build.js && electron-builder --arm64 --mac",
|
||||
"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",
|
||||
"dev": "node .electron-vue/dev-runner.js",
|
||||
"dev:renderer": "webpack-dev-server --hot --colors --config .electron-vue/webpack.renderer.config.js --port 9080 --content-base app/dist",
|
||||
"dev:renderer": "webpack serve --node-env development --hot --color --config .electron-vue/webpack.renderer.config.js --port 9080 --content-base app/dist",
|
||||
"lint": "eslint --ext .js,.vue -f ./node_modules/eslint-friendly-formatter src",
|
||||
"lint:fix": "eslint --ext .js,.vue -f ./node_modules/eslint-friendly-formatter --fix src",
|
||||
"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",
|
||||
"pack:main": "webpack --node-env production --progress --color --config .electron-vue/webpack.main.config.js",
|
||||
"pack:renderer": "webpack --node-env production --progress --color --config .electron-vue/webpack.renderer.config.js",
|
||||
"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"
|
||||
},
|
||||
"files": [
|
||||
"dist/electron/**/*"
|
||||
],
|
||||
"protocols": [
|
||||
{
|
||||
"name": "Motrix Protocol",
|
||||
"schemes": [
|
||||
"mo",
|
||||
"motrix"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Magnet Protocol",
|
||||
"schemes": [
|
||||
"magnet"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Thunder Protocol",
|
||||
"schemes": [
|
||||
"thunder"
|
||||
]
|
||||
}
|
||||
],
|
||||
"dmg": {
|
||||
"window": {
|
||||
"width": 540,
|
||||
"height": 380
|
||||
},
|
||||
"contents": [
|
||||
{
|
||||
"x": 410,
|
||||
"y": 230,
|
||||
"type": "link",
|
||||
"path": "/Applications"
|
||||
},
|
||||
{
|
||||
"x": 130,
|
||||
"y": 230,
|
||||
"type": "file"
|
||||
}
|
||||
]
|
||||
},
|
||||
"mac": {
|
||||
"target": [
|
||||
"dmg",
|
||||
"zip"
|
||||
],
|
||||
"type": "distribution",
|
||||
"darkModeSupport": true,
|
||||
"hardenedRuntime": true,
|
||||
"extraResources": {
|
||||
"from": "./extra/darwin/",
|
||||
"to": "./",
|
||||
"filter": [
|
||||
"**/*"
|
||||
]
|
||||
},
|
||||
"category": "public.app-category.utilities"
|
||||
},
|
||||
"win": {
|
||||
"target": [
|
||||
{
|
||||
"target": "nsis",
|
||||
"arch": [
|
||||
"x64",
|
||||
"ia32"
|
||||
]
|
||||
},
|
||||
{
|
||||
"target": "zip",
|
||||
"arch": [
|
||||
"x64",
|
||||
"ia32"
|
||||
]
|
||||
},
|
||||
{
|
||||
"target": "portable",
|
||||
"arch": [
|
||||
"x64",
|
||||
"ia32"
|
||||
]
|
||||
}
|
||||
],
|
||||
"extraResources": {
|
||||
"from": "./extra/win32/",
|
||||
"to": "./",
|
||||
"filter": [
|
||||
"**/*"
|
||||
]
|
||||
}
|
||||
},
|
||||
"nsis": {
|
||||
"oneClick": false,
|
||||
"allowToChangeInstallationDirectory": true
|
||||
},
|
||||
"linux": {
|
||||
"category": "Network",
|
||||
"target": [
|
||||
"AppImage",
|
||||
"deb",
|
||||
"rpm",
|
||||
"snap"
|
||||
],
|
||||
"extraResources": {
|
||||
"from": "./extra/linux/",
|
||||
"to": "./",
|
||||
"filter": [
|
||||
"**/*"
|
||||
]
|
||||
}
|
||||
},
|
||||
"snap": {
|
||||
"publish": [
|
||||
{
|
||||
"provider": "github"
|
||||
},
|
||||
{
|
||||
"provider": "snapStore",
|
||||
"channel": "edge"
|
||||
}
|
||||
]
|
||||
},
|
||||
"publish": [
|
||||
{
|
||||
"provider": "generic",
|
||||
"url": "https://dl.motrix.app/release/"
|
||||
},
|
||||
{
|
||||
"provider": "github"
|
||||
}
|
||||
]
|
||||
"engines": {
|
||||
"node" : ">=16.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.10.2",
|
||||
"@panter/vue-i18next": "^0.15.2",
|
||||
"aria2": "^4.1.0",
|
||||
"axios": "^0.19.2",
|
||||
"blob-util": "^2.0.2",
|
||||
"clipboard-polyfill": "^2.8.6",
|
||||
"electron-debug": "^3.1.0",
|
||||
"electron-is": "^3.0.0",
|
||||
"electron-log": "^4.2.1",
|
||||
"electron-store": "^5.1.1",
|
||||
"electron-updater": "^4.3.1",
|
||||
"element-ui": "^2.13.2",
|
||||
"forever-monitor": "3.0.0",
|
||||
"i18next": "^19.4.5",
|
||||
"lodash": "^4.17.15",
|
||||
"@motrix/nat-api": "^0.3.1",
|
||||
"node-fetch": "^2.6.0",
|
||||
"normalize.css": "^8.0.1",
|
||||
"parse-torrent": "^7.1.3",
|
||||
"randomatic": "^3.1.1",
|
||||
"svg-innerhtml": "^1.1.0",
|
||||
"vue": "^2.6.11",
|
||||
"vue-electron": "^1.0.6",
|
||||
"vue-router": "^3.3.2",
|
||||
"vuex": "^3.4.0",
|
||||
"vuex-router-sync": "^5.0.0"
|
||||
"node-fetch": "^2.6.1",
|
||||
"ws": "^8.13.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.10.2",
|
||||
"@babel/plugin-proposal-class-properties": "^7.10.1",
|
||||
"@babel/plugin-transform-runtime": "^7.10.1",
|
||||
"@babel/preset-env": "^7.10.2",
|
||||
"@babel/register": "^7.10.1",
|
||||
"@vue/eslint-config-standard": "^5.1.2",
|
||||
"ajv": "^6.12.2",
|
||||
"@babel/core": "^7.21.5",
|
||||
"@babel/plugin-proposal-class-properties": "^7.18.6",
|
||||
"@babel/plugin-transform-runtime": "^7.21.4",
|
||||
"@babel/preset-env": "^7.21.5",
|
||||
"@babel/register": "^7.21.0",
|
||||
"@babel/runtime": "^7.21.5",
|
||||
"@bany/curl-to-json": "^1.2.7",
|
||||
"@electron/notarize": "^1.2.3",
|
||||
"@electron/osx-sign": "^1.0.4",
|
||||
"@electron/remote": "^2.0.9",
|
||||
"@motrix/multispinner": "^0.2.4",
|
||||
"@motrix/nat-api": "^0.3.4",
|
||||
"@panter/vue-i18next": "^0.15.2",
|
||||
"@vue/eslint-config-standard": "^6.1.0",
|
||||
"ajv": "^8.12.0",
|
||||
"axios": "^1.4.0",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"babel-loader": "^8.1.0",
|
||||
"babel-loader": "^9.1.2",
|
||||
"babel-plugin-component": "^1.1.1",
|
||||
"cfonts": "^2.8.2",
|
||||
"chalk": "^4.0.0",
|
||||
"copy-webpack-plugin": "^6.0.2",
|
||||
"cross-env": "^7.0.2",
|
||||
"css-loader": "^3.5.3",
|
||||
"del": "^5.1.0",
|
||||
"devtron": "^1.4.0",
|
||||
"electron": "^8.3.1",
|
||||
"electron-builder": "^22.7.0",
|
||||
"electron-builder-notarize": "^1.1.2",
|
||||
"electron-devtools-installer": "^3.0.0",
|
||||
"electron-notarize": "^0.3.0",
|
||||
"electron-osx-sign": "^0.4.16",
|
||||
"eslint": "^7.1.0",
|
||||
"bittorrent-peerid": "^1.3.6",
|
||||
"blob-util": "^2.0.2",
|
||||
"cfonts": "^3.1.1",
|
||||
"chalk": "^4.1.2",
|
||||
"copy-webpack-plugin": "^11.0.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"css-loader": "^6.7.3",
|
||||
"css-minimizer-webpack-plugin": "^5.0.0",
|
||||
"del": "^6.1.1",
|
||||
"electron": "^22.3.7",
|
||||
"electron-builder": "^24.3.0",
|
||||
"electron-devtools-installer": "^3.2.0",
|
||||
"electron-is": "^3.0.0",
|
||||
"electron-log": "^4.4.8",
|
||||
"electron-store": "^8.1.0",
|
||||
"electron-updater": "^6.1.0",
|
||||
"element-ui": "^2.15.13",
|
||||
"eslint": "^7.32.0",
|
||||
"eslint-friendly-formatter": "^4.0.1",
|
||||
"eslint-loader": "^4.0.2",
|
||||
"eslint-plugin-import": "^2.20.2",
|
||||
"eslint-plugin-import": "^2.27.5",
|
||||
"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.3.0",
|
||||
"mini-css-extract-plugin": "0.9.0",
|
||||
"multispinner": "^0.2.1",
|
||||
"node-loader": "^0.6.0",
|
||||
"optimize-css-assets-webpack-plugin": "^5.0.3",
|
||||
"sass": "^1.26.8",
|
||||
"sass-loader": "^8.0.2",
|
||||
"style-loader": "^1.2.1",
|
||||
"terser-webpack-plugin": "^3.0.3",
|
||||
"url-loader": "^4.1.0",
|
||||
"vue-loader": "^15.9.2",
|
||||
"vue-style-loader": "^4.1.2",
|
||||
"vue-template-compiler": "^2.6.11",
|
||||
"webpack": "^4.43.0",
|
||||
"webpack-cli": "^3.3.11",
|
||||
"webpack-dev-server": "^3.11.0",
|
||||
"webpack-hot-middleware": "^2.25.0",
|
||||
"webpack-merge": "^4.2.2"
|
||||
"eslint-plugin-promise": "^6.1.1",
|
||||
"eslint-plugin-vue": "^9.11.0",
|
||||
"eslint-webpack-plugin": "^4.0.1",
|
||||
"file-loader": "^6.2.0",
|
||||
"html-webpack-plugin": "^5.5.1",
|
||||
"i18next": "^22.4.15",
|
||||
"lodash": "^4.17.21",
|
||||
"mini-css-extract-plugin": "2.7.5",
|
||||
"node-loader": "^2.0.0",
|
||||
"normalize.css": "^8.0.1",
|
||||
"parse-torrent": "^9.1.5",
|
||||
"randomatic": "^3.1.1",
|
||||
"sass": "1.62.1",
|
||||
"sass-loader": "^12.6.0",
|
||||
"style-loader": "^3.3.2",
|
||||
"terser-webpack-plugin": "^5.3.7",
|
||||
"vue": "^2.7.14",
|
||||
"vue-electron": "^1.0.6",
|
||||
"vue-loader": "^15.10.1",
|
||||
"vue-router": "^3.6.5",
|
||||
"vue-selectable": "^0.5.0",
|
||||
"vue-style-loader": "^4.1.3",
|
||||
"vue-template-compiler": "^2.7.14",
|
||||
"vuex": "^3.6.2",
|
||||
"vuex-router-sync": "^5.0.0",
|
||||
"webpack": "^5.81.0",
|
||||
"webpack-cli": "^5.0.2",
|
||||
"webpack-dev-server": "^4.13.3",
|
||||
"webpack-hot-middleware": "^2.25.3",
|
||||
"webpack-merge": "^5.8.0",
|
||||
"worker-loader": "^3.0.8"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,22 +10,49 @@
|
||||
</script>
|
||||
<% } %>
|
||||
</head>
|
||||
|
||||
<style>
|
||||
.skeleton-aside {
|
||||
background-color: rgba(0, 0, 0, 0.8);
|
||||
width: 78px;
|
||||
}
|
||||
.skeleton-subnav {
|
||||
background-color: #f4f5f7;
|
||||
width: 200px;
|
||||
}
|
||||
.skeleton-main {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.skeleton-aside {
|
||||
background-color: rgba(0, 0, 0, 0.9);
|
||||
}
|
||||
.skeleton-subnav {
|
||||
background-color: #2D2D2D;
|
||||
}
|
||||
.skeleton-main {
|
||||
background-color: #343434;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<body>
|
||||
<div id="app">
|
||||
<div class="title-bar"></div>
|
||||
<section class="el-container" id="container">
|
||||
<aside class="el-aside aside" style="width: 78px;">
|
||||
<aside class="el-aside skeleton-aside hidden-sm-and-down">
|
||||
</aside>
|
||||
<aside class="el-aside subnav" style="width: 200px;">
|
||||
<aside class="el-aside skeleton-subnav hidden-xs-only">
|
||||
</aside>
|
||||
<section class="el-container main">
|
||||
<section class="el-container skeleton-main">
|
||||
</section>
|
||||
</section>
|
||||
</div>
|
||||
<!-- Set `__static` path to static files in production -->
|
||||
<% if (!process.browser) { %>
|
||||
<% if (!htmlWebpackPlugin.options.isBrowser && !htmlWebpackPlugin.options.isDev) { %>
|
||||
<script>
|
||||
if (process.env.NODE_ENV !== 'development') window.__static = require('path').join(__dirname, '/static').replace(/\\/g, '\\\\')
|
||||
window.__static = require('path').join(__dirname, '/static').replace(/\\/g, '\\\\')
|
||||
</script>
|
||||
<% } %>
|
||||
|
||||
|
||||
@@ -1,13 +1,25 @@
|
||||
import { EventEmitter } from 'events'
|
||||
import { app, shell, dialog, ipcMain } from 'electron'
|
||||
import is from 'electron-is'
|
||||
import { readFile } from 'fs'
|
||||
import { readFile, unlink } from 'fs'
|
||||
import { extname, basename } from 'path'
|
||||
import { isEmpty } from 'lodash'
|
||||
import { isEmpty, isEqual } from 'lodash'
|
||||
|
||||
import {
|
||||
APP_RUN_MODE,
|
||||
AUTO_SYNC_TRACKER_INTERVAL,
|
||||
AUTO_CHECK_UPDATE_INTERVAL
|
||||
} from '@shared/constants'
|
||||
import { checkIsNeedRun } from '@shared/utils'
|
||||
import {
|
||||
convertTrackerDataToComma,
|
||||
fetchBtTrackerFromSource,
|
||||
reduceTrackerString
|
||||
} from '@shared/utils/tracker'
|
||||
import logger from './core/Logger'
|
||||
import Context from './core/Context'
|
||||
import ConfigManager from './core/ConfigManager'
|
||||
import { setupLocaleManager } from '@/ui/Locale'
|
||||
import { setupLocaleManager } from './ui/Locale'
|
||||
import Engine from './core/Engine'
|
||||
import EngineClient from './core/EngineClient'
|
||||
import UPnPManager from './core/UPnPManager'
|
||||
@@ -21,16 +33,6 @@ import TouchBarManager from './ui/TouchBarManager'
|
||||
import TrayManager from './ui/TrayManager'
|
||||
import DockManager from './ui/DockManager'
|
||||
import ThemeManager from './ui/ThemeManager'
|
||||
import {
|
||||
APP_RUN_MODE,
|
||||
AUTO_SYNC_TRACKER_INTERVAL,
|
||||
AUTO_CHECK_UPDATE_INTERVAL
|
||||
} from '@shared/constants'
|
||||
import { checkIsNeedRun } from '@shared/utils'
|
||||
import {
|
||||
convertTrackerDataToComma,
|
||||
fetchBtTrackerFromSource
|
||||
} from '@shared/utils/tracker'
|
||||
|
||||
export default class Application extends EventEmitter {
|
||||
constructor () {
|
||||
@@ -40,11 +42,13 @@ export default class Application extends EventEmitter {
|
||||
}
|
||||
|
||||
init () {
|
||||
this.configManager = new ConfigManager()
|
||||
this.initContext()
|
||||
|
||||
this.locale = this.configManager.getLocale()
|
||||
this.localeManager = setupLocaleManager(this.locale)
|
||||
this.i18n = this.localeManager.getI18n()
|
||||
this.initConfigManager()
|
||||
|
||||
this.setupLogger()
|
||||
|
||||
this.initLocaleManager()
|
||||
|
||||
this.setupApplicationMenu()
|
||||
|
||||
@@ -64,9 +68,9 @@ export default class Application extends EventEmitter {
|
||||
|
||||
this.initDockManager()
|
||||
|
||||
this.autoLaunchManager = new AutoLaunchManager()
|
||||
this.initAutoLaunchManager()
|
||||
|
||||
this.energyManager = new EnergyManager()
|
||||
this.initEnergyManager()
|
||||
|
||||
this.initUpdaterManager()
|
||||
|
||||
@@ -78,9 +82,49 @@ export default class Application extends EventEmitter {
|
||||
|
||||
this.handleIpcMessages()
|
||||
|
||||
this.handleIpcInvokes()
|
||||
|
||||
this.emit('application:initialized')
|
||||
}
|
||||
|
||||
initContext () {
|
||||
this.context = new Context()
|
||||
}
|
||||
|
||||
initConfigManager () {
|
||||
this.configListeners = {}
|
||||
this.configManager = new ConfigManager()
|
||||
}
|
||||
|
||||
offConfigListeners () {
|
||||
try {
|
||||
Object.keys(this.configListeners).forEach((key) => {
|
||||
this.configListeners[key]()
|
||||
})
|
||||
} catch (e) {
|
||||
logger.warn('[Motrix] offConfigListeners===>', e)
|
||||
}
|
||||
this.configListeners = {}
|
||||
}
|
||||
|
||||
setupLogger () {
|
||||
const { userConfig } = this.configManager
|
||||
const key = 'log-level'
|
||||
const logLevel = userConfig.get(key)
|
||||
logger.transports.file.level = logLevel
|
||||
|
||||
this.configListeners[key] = userConfig.onDidChange(key, async (newValue, oldValue) => {
|
||||
logger.info(`[Motrix] detected ${key} value change event:`, newValue, oldValue)
|
||||
logger.transports.file.level = newValue
|
||||
})
|
||||
}
|
||||
|
||||
initLocaleManager () {
|
||||
this.locale = this.configManager.getLocale()
|
||||
this.localeManager = setupLocaleManager(this.locale)
|
||||
this.i18n = this.localeManager.getI18n()
|
||||
}
|
||||
|
||||
setupApplicationMenu () {
|
||||
this.menuManager = new MenuManager()
|
||||
this.menuManager.setup(this.locale)
|
||||
@@ -121,14 +165,17 @@ export default class Application extends EventEmitter {
|
||||
}
|
||||
|
||||
async stopEngine () {
|
||||
logger.info('[Motrix] stopEngine===>')
|
||||
try {
|
||||
await this.engineClient.shutdown({ force: true })
|
||||
logger.info('[Motrix] stopEngine.setImmediate===>')
|
||||
setImmediate(() => {
|
||||
this.engine.stop()
|
||||
})
|
||||
} catch (err) {
|
||||
logger.warn('[Motrix] shutdown engine fail: ', err.message)
|
||||
} finally {
|
||||
// no finally
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,9 +188,47 @@ export default class Application extends EventEmitter {
|
||||
})
|
||||
}
|
||||
|
||||
initAutoLaunchManager () {
|
||||
this.autoLaunchManager = new AutoLaunchManager()
|
||||
}
|
||||
|
||||
initEnergyManager () {
|
||||
this.energyManager = new EnergyManager()
|
||||
}
|
||||
|
||||
initTrayManager () {
|
||||
this.trayManager = new TrayManager({
|
||||
theme: this.configManager.getUserConfig('tray-theme')
|
||||
theme: this.configManager.getUserConfig('tray-theme'),
|
||||
systemTheme: this.themeManager.getSystemTheme(),
|
||||
speedometer: this.configManager.getUserConfig('tray-speedometer'),
|
||||
runMode: this.configManager.getUserConfig('run-mode')
|
||||
})
|
||||
|
||||
this.watchTraySpeedometerEnabledChange()
|
||||
|
||||
this.trayManager.on('mouse-down', ({ focused }) => {
|
||||
this.sendCommandToAll('application:update-tray-focused', { focused })
|
||||
})
|
||||
|
||||
this.trayManager.on('mouse-up', ({ focused }) => {
|
||||
this.sendCommandToAll('application:update-tray-focused', { focused })
|
||||
})
|
||||
|
||||
this.trayManager.on('drop-files', (files = []) => {
|
||||
this.handleFile(files[0])
|
||||
})
|
||||
|
||||
this.trayManager.on('drop-text', (text) => {
|
||||
this.handleProtocol(text)
|
||||
})
|
||||
}
|
||||
|
||||
watchTraySpeedometerEnabledChange () {
|
||||
const { userConfig } = this.configManager
|
||||
const key = 'tray-speedometer'
|
||||
this.configListeners[key] = userConfig.onDidChange(key, async (newValue, oldValue) => {
|
||||
logger.info(`[Motrix] detected ${key} value change event:`, newValue, oldValue)
|
||||
this.trayManager.handleSpeedometerEnableChange(newValue)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -153,15 +238,78 @@ export default class Application extends EventEmitter {
|
||||
})
|
||||
}
|
||||
|
||||
watchOpenAtLoginChange () {
|
||||
const { userConfig } = this.configManager
|
||||
const key = 'open-at-login'
|
||||
this.configListeners[key] = userConfig.onDidChange(key, async (newValue, oldValue) => {
|
||||
logger.info(`[Motrix] detected ${key} value change event:`, newValue, oldValue)
|
||||
if (is.linux()) {
|
||||
return
|
||||
}
|
||||
|
||||
if (newValue) {
|
||||
this.autoLaunchManager.enable()
|
||||
} else {
|
||||
this.autoLaunchManager.disable()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
watchProtocolsChange () {
|
||||
const { userConfig } = this.configManager
|
||||
const key = 'protocols'
|
||||
this.configListeners[key] = userConfig.onDidChange(key, async (newValue, oldValue) => {
|
||||
logger.info(`[Motrix] detected ${key} value change event:`, newValue, oldValue)
|
||||
|
||||
if (!newValue || isEqual(newValue, oldValue)) {
|
||||
return
|
||||
}
|
||||
|
||||
logger.info('[Motrix] setup protocols client:', newValue)
|
||||
this.protocolManager.setup(newValue)
|
||||
})
|
||||
}
|
||||
|
||||
watchRunModeChange () {
|
||||
const { userConfig } = this.configManager
|
||||
const key = 'run-mode'
|
||||
this.configListeners[key] = userConfig.onDidChange(key, async (newValue, oldValue) => {
|
||||
logger.info(`[Motrix] detected ${key} value change event:`, newValue, oldValue)
|
||||
this.trayManager.handleRunModeChange(newValue)
|
||||
|
||||
if (newValue !== APP_RUN_MODE.TRAY) {
|
||||
this.dockManager.show()
|
||||
} else {
|
||||
this.dockManager.hide()
|
||||
// Hiding the dock icon will trigger the entire app to hide.
|
||||
this.show()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
watchShowProgressBarChange () {
|
||||
const { userConfig } = this.configManager
|
||||
const key = 'show-progress-bar'
|
||||
this.configListeners[key] = userConfig.onDidChange(key, async (newValue, oldValue) => {
|
||||
logger.info(`[Motrix] detected ${key} value change event:`, newValue, oldValue)
|
||||
|
||||
if (newValue) {
|
||||
this.bindProgressChange()
|
||||
} else {
|
||||
this.unbindProgressChange()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
initUPnPManager () {
|
||||
this.upnp = new UPnPManager()
|
||||
|
||||
this.watchEnableUPnPChange()
|
||||
this.watchUPnPEnabledChange()
|
||||
|
||||
this.watchPortsChange()
|
||||
this.watchUPnPPortsChange()
|
||||
|
||||
const enable = this.configManager.getUserConfig('enable-upnp')
|
||||
if (!enable) {
|
||||
const enabled = this.configManager.getUserConfig('enable-upnp')
|
||||
if (!enabled) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -177,9 +325,9 @@ export default class Application extends EventEmitter {
|
||||
this.upnp.map(dhtPort)
|
||||
]
|
||||
try {
|
||||
await Promise.all(promises)
|
||||
await Promise.allSettled(promises)
|
||||
} catch (e) {
|
||||
logger.warn('[Motrix] start UPnP mapping fail', e)
|
||||
logger.warn('[Motrix] start UPnP mapping fail', e.message)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -192,17 +340,18 @@ export default class Application extends EventEmitter {
|
||||
this.upnp.unmap(dhtPort)
|
||||
]
|
||||
try {
|
||||
await Promise.all(promises)
|
||||
await Promise.allSettled(promises)
|
||||
} catch (e) {
|
||||
logger.warn('[Motrix] stop UPnP mapping fail', e)
|
||||
}
|
||||
}
|
||||
|
||||
watchPortsChange () {
|
||||
watchUPnPPortsChange () {
|
||||
const { systemConfig } = this.configManager
|
||||
const watchKeys = ['listen-port', 'dht-listen-port']
|
||||
|
||||
watchKeys.map((key) => {
|
||||
this.configManager.systemConfig.onDidChange(key, async (newValue, oldValue) => {
|
||||
watchKeys.forEach((key) => {
|
||||
this.configListeners[key] = 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) {
|
||||
@@ -214,7 +363,7 @@ export default class Application extends EventEmitter {
|
||||
this.upnp.map(newValue)
|
||||
]
|
||||
try {
|
||||
await Promise.all(promises)
|
||||
await Promise.allSettled(promises)
|
||||
} catch (e) {
|
||||
logger.info('[Motrix] change UPnP port mapping failed:', e)
|
||||
}
|
||||
@@ -222,8 +371,10 @@ export default class Application extends EventEmitter {
|
||||
})
|
||||
}
|
||||
|
||||
watchEnableUPnPChange () {
|
||||
this.configManager.userConfig.onDidChange('enable-upnp', async (newValue, oldValue) => {
|
||||
watchUPnPEnabledChange () {
|
||||
const { userConfig } = this.configManager
|
||||
const key = 'enable-upnp'
|
||||
this.configListeners[key] = userConfig.onDidChange(key, async (newValue, oldValue) => {
|
||||
logger.info('[Motrix] detected enable-upnp value change event:', newValue, oldValue)
|
||||
if (newValue) {
|
||||
this.startUPnPMapping()
|
||||
@@ -252,11 +403,20 @@ export default class Application extends EventEmitter {
|
||||
return
|
||||
}
|
||||
|
||||
const source = this.configManager.getUserConfig('tracker-source')
|
||||
if (isEmpty(source)) {
|
||||
return
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
const source = this.configManager.getUserConfig('tracker-source')
|
||||
fetchBtTrackerFromSource(source).then((data) => {
|
||||
logger.warn('[Motrix] auto sync tracker data:', data)
|
||||
const tracker = convertTrackerDataToComma(data)
|
||||
if (!data || data.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
let tracker = convertTrackerDataToComma(data)
|
||||
tracker = reduceTrackerString(tracker)
|
||||
this.savePreference({
|
||||
system: {
|
||||
'bt-tracker': tracker
|
||||
@@ -303,7 +463,7 @@ export default class Application extends EventEmitter {
|
||||
|
||||
this.windowManager.on('leave-full-screen', (window) => {
|
||||
const mode = this.configManager.getUserConfig('run-mode')
|
||||
if (mode !== APP_RUN_MODE.STANDARD) {
|
||||
if (mode === APP_RUN_MODE.TRAY) {
|
||||
this.dockManager.hide()
|
||||
}
|
||||
})
|
||||
@@ -331,6 +491,7 @@ export default class Application extends EventEmitter {
|
||||
this.isReady = true
|
||||
this.emit('ready')
|
||||
})
|
||||
|
||||
if (is.macOS()) {
|
||||
this.touchBarManager.setup(page, win)
|
||||
}
|
||||
@@ -364,22 +525,27 @@ export default class Application extends EventEmitter {
|
||||
this.windowManager.destroyWindow(page)
|
||||
}
|
||||
|
||||
async stop () {
|
||||
stop () {
|
||||
try {
|
||||
await this.shutdownUPnPManager()
|
||||
const promises = [
|
||||
this.stopEngine(),
|
||||
this.shutdownUPnPManager(),
|
||||
this.energyManager.stopPowerSaveBlocker(),
|
||||
this.trayManager.destroy()
|
||||
]
|
||||
|
||||
this.energyManager.stopPowerSaveBlocker()
|
||||
|
||||
await this.stopEngine()
|
||||
|
||||
this.trayManager.destroy()
|
||||
return promises
|
||||
} catch (err) {
|
||||
logger.warn('[Motrix] stop error: ', err.message)
|
||||
}
|
||||
}
|
||||
|
||||
async stopAllSettled () {
|
||||
await Promise.allSettled(this.stop())
|
||||
}
|
||||
|
||||
async quit () {
|
||||
await this.stop()
|
||||
await this.stopAllSettled()
|
||||
app.exit()
|
||||
}
|
||||
|
||||
@@ -409,8 +575,8 @@ export default class Application extends EventEmitter {
|
||||
initThemeManager () {
|
||||
this.themeManager = new ThemeManager()
|
||||
this.themeManager.on('system-theme-change', (theme) => {
|
||||
this.trayManager.changeIconTheme(theme)
|
||||
this.sendCommandToAll('application:update-system-theme', theme)
|
||||
this.trayManager.handleSystemThemeChange(theme)
|
||||
this.sendCommandToAll('application:update-system-theme', { theme })
|
||||
})
|
||||
}
|
||||
|
||||
@@ -423,10 +589,6 @@ export default class Application extends EventEmitter {
|
||||
}
|
||||
|
||||
initProtocolManager () {
|
||||
if (is.dev() || is.mas()) {
|
||||
return
|
||||
}
|
||||
|
||||
const protocols = this.configManager.getUserConfig('protocols', {})
|
||||
this.protocolManager = new ProtocolManager({
|
||||
protocols
|
||||
@@ -434,10 +596,6 @@ export default class Application extends EventEmitter {
|
||||
}
|
||||
|
||||
handleProtocol (url) {
|
||||
if (is.dev() || is.mas()) {
|
||||
return
|
||||
}
|
||||
|
||||
this.show()
|
||||
|
||||
this.protocolManager.handle(url)
|
||||
@@ -454,15 +612,17 @@ export default class Application extends EventEmitter {
|
||||
|
||||
this.show()
|
||||
|
||||
const fileName = basename(filePath)
|
||||
const name = 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)
|
||||
const dataURL = Buffer.from(data).toString('base64')
|
||||
this.sendCommandToAll('application:new-bt-task-with-file', {
|
||||
name,
|
||||
dataURL
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -471,10 +631,10 @@ export default class Application extends EventEmitter {
|
||||
return
|
||||
}
|
||||
|
||||
const enable = this.configManager.getUserConfig('auto-check-update')
|
||||
const enabled = 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)
|
||||
autoCheck: checkIsNeedRun(enabled, lastTime, AUTO_CHECK_UPDATE_INTERVAL)
|
||||
})
|
||||
this.handleUpdaterEvents()
|
||||
}
|
||||
@@ -500,11 +660,19 @@ export default class Application extends EventEmitter {
|
||||
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)
|
||||
win.setProgressBar(1)
|
||||
})
|
||||
|
||||
this.updateManager.on('will-updated', (event) => {
|
||||
this.updateManager.on('update-cancelled', (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(-1)
|
||||
})
|
||||
|
||||
this.updateManager.on('will-updated', async (event) => {
|
||||
this.windowManager.setWillQuit(true)
|
||||
await this.stopAllSettled()
|
||||
})
|
||||
|
||||
this.updateManager.on('update-error', (event) => {
|
||||
@@ -513,12 +681,27 @@ export default class Application extends EventEmitter {
|
||||
})
|
||||
}
|
||||
|
||||
relaunch () {
|
||||
this.stop()
|
||||
async relaunch () {
|
||||
await this.stopAllSettled()
|
||||
app.relaunch()
|
||||
app.exit()
|
||||
}
|
||||
|
||||
async resetSession () {
|
||||
await this.stopEngine()
|
||||
|
||||
app.clearRecentDocuments()
|
||||
|
||||
const sessionPath = this.context.get('session-path')
|
||||
setTimeout(() => {
|
||||
unlink(sessionPath, function (err) {
|
||||
logger.info('[Motrix] Removed the download seesion file:', err)
|
||||
})
|
||||
|
||||
this.engine.start()
|
||||
}, 3000)
|
||||
}
|
||||
|
||||
savePreference (config = {}) {
|
||||
logger.info('[Motrix] save preference:', config)
|
||||
const { system, user } = config
|
||||
@@ -537,6 +720,10 @@ export default class Application extends EventEmitter {
|
||||
handleCommands () {
|
||||
this.on('application:save-preference', this.savePreference)
|
||||
|
||||
this.on('application:update-tray', (tray) => {
|
||||
this.trayManager.updateTrayByImage(tray)
|
||||
})
|
||||
|
||||
this.on('application:relaunch', () => {
|
||||
this.relaunch()
|
||||
})
|
||||
@@ -545,27 +732,18 @@ export default class Application extends EventEmitter {
|
||||
this.quit()
|
||||
})
|
||||
|
||||
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.on('application:show', ({ page }) => {
|
||||
this.show(page)
|
||||
})
|
||||
|
||||
this.on('application:hide', (page) => {
|
||||
this.on('application:hide', ({ page }) => {
|
||||
this.hide(page)
|
||||
})
|
||||
|
||||
this.on('application:reset-session', () => this.resetSession())
|
||||
|
||||
this.on('application:reset', () => {
|
||||
this.offConfigListeners()
|
||||
this.configManager.reset()
|
||||
this.relaunch()
|
||||
})
|
||||
@@ -575,15 +753,15 @@ export default class Application extends EventEmitter {
|
||||
})
|
||||
|
||||
this.on('application:change-theme', (theme) => {
|
||||
this.themeManager.updateAppAppearance(theme)
|
||||
this.sendCommandToAll('application:update-theme', theme)
|
||||
this.themeManager.updateSystemTheme(theme)
|
||||
this.sendCommandToAll('application:update-theme', { theme })
|
||||
})
|
||||
|
||||
this.on('application:change-locale', (locale) => {
|
||||
this.localeManager.changeLanguageByLocale(locale)
|
||||
.then(() => {
|
||||
this.trayManager.setup(locale)
|
||||
this.menuManager.handleLocaleChange(locale)
|
||||
this.trayManager.handleLocaleChange(locale)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -634,36 +812,48 @@ export default class Application extends EventEmitter {
|
||||
})
|
||||
|
||||
this.on('application:setup-protocols-client', (protocols) => {
|
||||
if (is.dev() || is.mas()) {
|
||||
if (is.dev() || is.mas() || !protocols) {
|
||||
return
|
||||
}
|
||||
logger.info('[Motrix] setup protocols client:', protocols)
|
||||
this.protocolManager.setup(protocols)
|
||||
})
|
||||
|
||||
this.on('application:open-external', (url) => {
|
||||
this.openExternal(url)
|
||||
})
|
||||
|
||||
this.on('help:official-website', () => {
|
||||
const url = 'https://motrix.app/'
|
||||
shell.openExternal(url)
|
||||
this.openExternal(url)
|
||||
})
|
||||
|
||||
this.on('help:manual', () => {
|
||||
const url = 'https://motrix.app/manual'
|
||||
shell.openExternal(url)
|
||||
this.openExternal(url)
|
||||
})
|
||||
|
||||
this.on('help:release-notes', () => {
|
||||
const url = 'https://motrix.app/release'
|
||||
shell.openExternal(url)
|
||||
this.openExternal(url)
|
||||
})
|
||||
|
||||
this.on('help:report-problem', () => {
|
||||
const url = 'https://motrix.app/report'
|
||||
shell.openExternal(url)
|
||||
this.openExternal(url)
|
||||
})
|
||||
}
|
||||
|
||||
openExternal (url) {
|
||||
if (!url) {
|
||||
return
|
||||
}
|
||||
|
||||
shell.openExternal(url)
|
||||
}
|
||||
|
||||
handleConfigChange (configName) {
|
||||
this.sendCommandToAll('application:update-preference-config', configName)
|
||||
this.sendCommandToAll('application:update-preference-config', { configName })
|
||||
}
|
||||
|
||||
handleEvents () {
|
||||
@@ -678,8 +868,13 @@ export default class Application extends EventEmitter {
|
||||
this.configManager.userConfig.onDidAnyChange(() => this.handleConfigChange('user'))
|
||||
this.configManager.systemConfig.onDidAnyChange(() => this.handleConfigChange('system'))
|
||||
|
||||
this.watchOpenAtLoginChange()
|
||||
this.watchProtocolsChange()
|
||||
this.watchRunModeChange()
|
||||
this.watchShowProgressBarChange()
|
||||
|
||||
this.on('download-status-change', (downloading) => {
|
||||
this.trayManager.updateTrayByStatus(downloading)
|
||||
this.trayManager.handleDownloadStatusChange(downloading)
|
||||
if (downloading) {
|
||||
this.energyManager.startPowerSaveBlocker()
|
||||
} else {
|
||||
@@ -687,13 +882,50 @@ export default class Application extends EventEmitter {
|
||||
}
|
||||
})
|
||||
|
||||
this.on('download-speed-change', (speed) => {
|
||||
this.dockManager.setBadge(speed)
|
||||
this.on('speed-change', (speed) => {
|
||||
this.dockManager.handleSpeedChange(speed)
|
||||
this.trayManager.handleSpeedChange(speed)
|
||||
})
|
||||
|
||||
this.on('task-download-complete', (task, path) => {
|
||||
this.dockManager.openDock(path)
|
||||
|
||||
if (is.linux()) {
|
||||
return
|
||||
}
|
||||
app.addRecentDocument(path)
|
||||
})
|
||||
|
||||
if (this.configManager.userConfig.get('show-progress-bar')) {
|
||||
this.bindProgressChange()
|
||||
}
|
||||
}
|
||||
|
||||
handleProgressChange (progress) {
|
||||
if (this.updateManager.isChecking) {
|
||||
return
|
||||
}
|
||||
if (!is.windows() && progress === 2) {
|
||||
progress = 0
|
||||
}
|
||||
this.windowManager.getWindow('index').setProgressBar(progress)
|
||||
}
|
||||
|
||||
bindProgressChange () {
|
||||
if (this.listeners('progress-change').length > 0) {
|
||||
return
|
||||
}
|
||||
|
||||
this.on('progress-change', this.handleProgressChange)
|
||||
}
|
||||
|
||||
unbindProgressChange () {
|
||||
if (this.listeners('progress-change').length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
this.off('progress-change', this.handleProgressChange)
|
||||
this.windowManager.getWindow('index').setProgressBar(-1)
|
||||
}
|
||||
|
||||
handleIpcMessages () {
|
||||
@@ -707,4 +939,19 @@ export default class Application extends EventEmitter {
|
||||
this.emit(eventName, ...args)
|
||||
})
|
||||
}
|
||||
|
||||
handleIpcInvokes () {
|
||||
ipcMain.handle('get-app-config', async () => {
|
||||
const systemConfig = this.configManager.getSystemConfig()
|
||||
const userConfig = this.configManager.getUserConfig()
|
||||
const context = this.context.get()
|
||||
|
||||
const result = {
|
||||
...systemConfig,
|
||||
...userConfig,
|
||||
...context
|
||||
}
|
||||
return result
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,6 +63,7 @@ export default class Launcher extends EventEmitter {
|
||||
}
|
||||
|
||||
handleAppEvents () {
|
||||
this.handleRendererRemote()
|
||||
this.handleOpenUrl()
|
||||
this.handleOpenFile()
|
||||
|
||||
@@ -70,6 +71,12 @@ export default class Launcher extends EventEmitter {
|
||||
this.handleAppWillQuit()
|
||||
}
|
||||
|
||||
handleRendererRemote () {
|
||||
app.on('browser-window-created', (_, window) => {
|
||||
require('@electron/remote/main').enable(window.webContents)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* handleOpenUrl
|
||||
* Event 'open-url' macOS only
|
||||
@@ -176,6 +183,7 @@ export default class Launcher extends EventEmitter {
|
||||
app.on('will-quit', () => {
|
||||
logger.info('[Motrix] will-quit')
|
||||
if (global.application) {
|
||||
logger.info('[Motrix] will-quit.application.stop')
|
||||
global.application.stop()
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,5 +1,22 @@
|
||||
export default {
|
||||
export const engineBinMap = {
|
||||
darwin: 'aria2c',
|
||||
win32: 'aria2c.exe',
|
||||
linux: 'aria2c'
|
||||
}
|
||||
|
||||
export const engineArchMap = {
|
||||
darwin: {
|
||||
x64: 'x64',
|
||||
arm64: 'arm64'
|
||||
},
|
||||
win32: {
|
||||
ia32: 'ia32',
|
||||
x64: 'x64',
|
||||
arm64: 'x64'
|
||||
},
|
||||
linux: {
|
||||
x64: 'x64',
|
||||
arm: 'armv7l',
|
||||
arm64: 'arm64'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,12 +6,12 @@ export default {
|
||||
title: 'Motrix',
|
||||
width: 1024,
|
||||
height: 768,
|
||||
minWidth: 400,
|
||||
minWidth: 478,
|
||||
minHeight: 420,
|
||||
// backgroundColor: '#FFFFFF',
|
||||
transparent: !is.windows()
|
||||
transparent: is.macOS()
|
||||
},
|
||||
bindCloseToHide: true,
|
||||
url: is.dev() ? 'http://localhost:9080' : `file://${__dirname}/index.html`
|
||||
openDevTools: is.dev(),
|
||||
url: is.dev() ? 'http://localhost:9080' : require('path').join('file://', __dirname, '/index.html')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,8 +4,6 @@ import Store from 'electron-store'
|
||||
|
||||
import {
|
||||
getDhtPath,
|
||||
getLogPath,
|
||||
getSessionPath,
|
||||
getUserDownloadsPath,
|
||||
getMaxConnectionPerServer
|
||||
} from '../utils/index'
|
||||
@@ -15,10 +13,12 @@ import {
|
||||
EMPTY_STRING,
|
||||
IP_VERSION,
|
||||
LOGIN_SETTING_OPTIONS,
|
||||
NGOSANG_TRACKERS_BEST_IP_URL,
|
||||
NGOSANG_TRACKERS_BEST_URL
|
||||
NGOSANG_TRACKERS_BEST_IP_URL_CDN,
|
||||
NGOSANG_TRACKERS_BEST_URL_CDN
|
||||
} from '@shared/constants'
|
||||
import { CHROME_UA } from '@shared/ua'
|
||||
import { separateConfig } from '@shared/utils'
|
||||
import { reduceTrackerString } from '@shared/utils/tracker'
|
||||
|
||||
export default class ConfigManager {
|
||||
constructor () {
|
||||
@@ -51,27 +51,33 @@ export default class ConfigManager {
|
||||
'allow-overwrite': false,
|
||||
'auto-file-renaming': true,
|
||||
'bt-exclude-tracker': EMPTY_STRING,
|
||||
'bt-force-encryption': false,
|
||||
'bt-load-saved-metadata': true,
|
||||
'bt-save-metadata': 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(),
|
||||
'enable-dht6': true,
|
||||
'follow-metalink': true,
|
||||
'follow-torrent': true,
|
||||
'listen-port': 21301,
|
||||
'max-concurrent-downloads': 5,
|
||||
'max-connection-per-server': getMaxConnectionPerServer(),
|
||||
'max-download-limit': 0,
|
||||
'max-overall-download-limit': 0,
|
||||
'max-overall-upload-limit': '256K',
|
||||
'min-split-size': '1M',
|
||||
'max-overall-upload-limit': 0,
|
||||
'no-proxy': EMPTY_STRING,
|
||||
'pause-metadata': false,
|
||||
'pause': true,
|
||||
'rpc-listen-port': 16800,
|
||||
'rpc-secret': EMPTY_STRING,
|
||||
'seed-ratio': 1,
|
||||
'seed-time': 60,
|
||||
'split': getMaxConnectionPerServer(),
|
||||
'user-agent': 'Transmission/2.94'
|
||||
'user-agent': CHROME_UA
|
||||
}
|
||||
/* eslint-enable quote-props */
|
||||
})
|
||||
@@ -97,26 +103,30 @@ export default class ConfigManager {
|
||||
'auto-sync-tracker': true,
|
||||
'enable-upnp': true,
|
||||
'engine-max-connection-per-server': getMaxConnectionPerServer(),
|
||||
'favorite-directories': [],
|
||||
'hide-app-menu': is.windows() || is.linux(),
|
||||
'history-directories': [],
|
||||
'keep-seeding': false,
|
||||
'keep-window-state': false,
|
||||
'last-check-update-time': 0,
|
||||
'last-sync-tracker-time': 0,
|
||||
'locale': app.getLocale(),
|
||||
'log-path': getLogPath(),
|
||||
'log-level': 'warn',
|
||||
'new-task-show-downloading': true,
|
||||
'no-confirm-before-delete-task': false,
|
||||
'open-at-login': false,
|
||||
'protocols': { 'magnet': true, 'thunder': false },
|
||||
'resume-all-when-app-launched': false,
|
||||
'run-mode': APP_RUN_MODE.STANDARD,
|
||||
'session-path': getSessionPath(),
|
||||
'show-progress-bar': true,
|
||||
'task-notification': true,
|
||||
'theme': APP_THEME.AUTO,
|
||||
'tracker-source': [
|
||||
NGOSANG_TRACKERS_BEST_IP_URL,
|
||||
NGOSANG_TRACKERS_BEST_URL
|
||||
NGOSANG_TRACKERS_BEST_IP_URL_CDN,
|
||||
NGOSANG_TRACKERS_BEST_URL_CDN
|
||||
],
|
||||
'tray-theme': APP_THEME.AUTO,
|
||||
'tray-speedometer': is.macOS(),
|
||||
'update-channel': 'latest',
|
||||
'use-proxy': false,
|
||||
'window-state': {}
|
||||
@@ -136,6 +146,10 @@ export default class ConfigManager {
|
||||
Object.keys(others).forEach(key => {
|
||||
this.systemConfig.delete(key)
|
||||
})
|
||||
|
||||
// Fix spawn ENAMETOOLONG on Windows
|
||||
const tracker = reduceTrackerString(this.systemConfig.get('bt-tracker'))
|
||||
this.setSystemConfig('bt-tracker', tracker)
|
||||
}
|
||||
|
||||
fixUserConfig () {
|
||||
@@ -148,8 +162,8 @@ export default class ConfigManager {
|
||||
|
||||
if (this.getUserConfig('tracker-source').length === 0) {
|
||||
this.setUserConfig('tracker-source', [
|
||||
NGOSANG_TRACKERS_BEST_IP_URL,
|
||||
NGOSANG_TRACKERS_BEST_URL
|
||||
NGOSANG_TRACKERS_BEST_IP_URL_CDN,
|
||||
NGOSANG_TRACKERS_BEST_URL_CDN
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,39 @@
|
||||
export default class Context {
|
||||
import logger from './Logger'
|
||||
import {
|
||||
getEnginePath,
|
||||
getAria2BinPath,
|
||||
getAria2ConfPath,
|
||||
getLogPath,
|
||||
getSessionPath
|
||||
} from '../utils'
|
||||
|
||||
const { platform, arch } = process
|
||||
|
||||
export default class Context {
|
||||
constructor () {
|
||||
this.init()
|
||||
}
|
||||
|
||||
init () {
|
||||
// The key of Context cannot be the same as that of userConfig and systemConfig.
|
||||
this.context = {
|
||||
platform: platform,
|
||||
arch: arch,
|
||||
'log-path': getLogPath(),
|
||||
'session-path': getSessionPath(),
|
||||
'engine-path': getEnginePath(platform, arch),
|
||||
'aria2-bin-path': getAria2BinPath(platform, arch),
|
||||
'aria2-conf-path': getAria2ConfPath(platform, arch)
|
||||
}
|
||||
|
||||
logger.info('[Motrix] Context.init===>', this.context)
|
||||
}
|
||||
|
||||
get (key) {
|
||||
if (typeof key === 'undefined') {
|
||||
return this.context
|
||||
}
|
||||
|
||||
return this.context[key]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import logger from './Logger'
|
||||
let psbId
|
||||
export default class EnergyManager {
|
||||
startPowerSaveBlocker () {
|
||||
logger.info('[Motrix] EnergyManager.startPowerSaveBlocker', psbId)
|
||||
if (psbId && powerSaveBlocker.isStarted(psbId)) {
|
||||
return
|
||||
}
|
||||
@@ -14,6 +15,7 @@ export default class EnergyManager {
|
||||
}
|
||||
|
||||
stopPowerSaveBlocker () {
|
||||
logger.info('[Motrix] EnergyManager.stopPowerSaveBlocker', psbId)
|
||||
if (typeof psbId === 'undefined' || !powerSaveBlocker.isStarted(psbId)) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1,18 +1,21 @@
|
||||
import { app } from 'electron'
|
||||
import is from 'electron-is'
|
||||
import { existsSync } from 'fs'
|
||||
import { resolve, join } from 'path'
|
||||
import forever from 'forever-monitor'
|
||||
import { existsSync, writeFile, unlink } from 'fs'
|
||||
import { spawn } from 'child_process'
|
||||
|
||||
import logger from './Logger'
|
||||
import { getI18n } from '@/ui/Locale'
|
||||
import { getI18n } from '../ui/Locale'
|
||||
import {
|
||||
getEngineBin,
|
||||
getEnginePidPath,
|
||||
getAria2BinPath,
|
||||
getAria2ConfPath,
|
||||
getSessionPath,
|
||||
transformConfig
|
||||
} from '../utils/index'
|
||||
|
||||
const { platform, arch } = process
|
||||
|
||||
export default class Engine {
|
||||
// ChildProcess | null
|
||||
static instance = null
|
||||
|
||||
constructor (options = {}) {
|
||||
@@ -23,82 +26,99 @@ export default class Engine {
|
||||
this.userConfig = options.userConfig
|
||||
}
|
||||
|
||||
getStartSh () {
|
||||
const { platform } = process
|
||||
let basePath = resolve(app.getAppPath(), '..')
|
||||
start () {
|
||||
const pidPath = getEnginePidPath()
|
||||
logger.info('[Motrix] Engie pid path:', pidPath)
|
||||
|
||||
if (this.instance) {
|
||||
return
|
||||
}
|
||||
|
||||
const binPath = this.getEngineBinPath()
|
||||
const args = this.getStartArgs()
|
||||
this.instance = spawn(binPath, args, {
|
||||
windowsHide: false,
|
||||
stdio: is.dev() ? 'pipe' : 'ignore'
|
||||
})
|
||||
const pid = this.instance.pid.toString()
|
||||
this.writePidFile(pidPath, pid)
|
||||
|
||||
this.instance.once('close', function () {
|
||||
try {
|
||||
unlink(pidPath, function (err) {
|
||||
if (err) {
|
||||
logger.warn(`[Motrix] Unlink engine process pid file failed: ${err}`)
|
||||
}
|
||||
})
|
||||
} catch (err) {
|
||||
logger.warn(`[Motrix] Unlink engine process pid file failed: ${err}`)
|
||||
}
|
||||
})
|
||||
|
||||
if (is.dev()) {
|
||||
basePath = resolve(__dirname, `../../../extra/${platform}`)
|
||||
}
|
||||
this.instance.stdout.on('data', function (data) {
|
||||
logger.log('[Motrix] engine stdout===>', data.toString())
|
||||
})
|
||||
|
||||
const binName = getEngineBin(platform)
|
||||
if (!binName) {
|
||||
throw new Error(this.i18n.t('app.engine-damaged-message'))
|
||||
this.instance.stderr.on('data', function (data) {
|
||||
logger.log('[Motrix] engine stderr===>', data.toString())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const binPath = join(basePath, `/engine/${binName}`)
|
||||
const binIsExist = existsSync(binPath)
|
||||
stop () {
|
||||
logger.info('[Motrix] engine.stop.instance')
|
||||
if (this.instance) {
|
||||
this.instance.kill()
|
||||
this.instance = null
|
||||
}
|
||||
}
|
||||
|
||||
writePidFile (pidPath, pid) {
|
||||
writeFile(pidPath, pid, (err) => {
|
||||
if (err) {
|
||||
logger.error(`[Motrix] Write engine process pid failed: ${err}`)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
getEngineBinPath () {
|
||||
const result = getAria2BinPath(platform, arch)
|
||||
const binIsExist = existsSync(result)
|
||||
if (!binIsExist) {
|
||||
logger.error('[Motrix] engine bin is not exist:', binPath)
|
||||
logger.error('[Motrix] engine bin is not exist:', result)
|
||||
throw new Error(this.i18n.t('app.engine-missing-message'))
|
||||
}
|
||||
|
||||
const confPath = join(basePath, '/engine/aria2.conf')
|
||||
|
||||
const sessionPath = this.userConfig['session-path'] || getSessionPath()
|
||||
const sessionIsExist = existsSync(sessionPath)
|
||||
|
||||
let result = [`${binPath}`, `--conf-path=${confPath}`, `--save-session=${sessionPath}`]
|
||||
if (sessionIsExist) {
|
||||
result = [...result, `--input-file=${sessionPath}`]
|
||||
}
|
||||
|
||||
const extraConfig = transformConfig(this.systemConfig)
|
||||
result = [...result, ...extraConfig]
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
start () {
|
||||
const sh = this.getStartSh()
|
||||
logger.info('[Motrix] Engine start sh:', sh)
|
||||
this.instance = forever.start(sh, {
|
||||
max: is.dev() ? 0 : 100,
|
||||
parser: function (command, args) {
|
||||
return {
|
||||
command: command,
|
||||
args: args
|
||||
}
|
||||
},
|
||||
silent: !is.dev()
|
||||
})
|
||||
getStartArgs () {
|
||||
const confPath = getAria2ConfPath(platform, arch)
|
||||
|
||||
const { child } = this.instance
|
||||
logger.info('[Motrix] Engine pid:', child.pid)
|
||||
const sessionPath = getSessionPath()
|
||||
const sessionIsExist = existsSync(sessionPath)
|
||||
|
||||
this.instance.on('error', (err) => {
|
||||
logger.info(`[Motrix] Engine error: ${err}`)
|
||||
})
|
||||
let result = [`--conf-path=${confPath}`, `--save-session=${sessionPath}`]
|
||||
if (sessionIsExist) {
|
||||
result = [...result, `--input-file=${sessionPath}`]
|
||||
}
|
||||
|
||||
this.instance.on('start', function (process, data) {
|
||||
logger.info('[Motrix] Engine started')
|
||||
})
|
||||
const extraConfig = {
|
||||
...this.systemConfig
|
||||
}
|
||||
const keepSeeding = this.userConfig['keep-seeding']
|
||||
const seedRatio = this.systemConfig['seed-ratio']
|
||||
if (keepSeeding || seedRatio === 0) {
|
||||
extraConfig['seed-ratio'] = 0
|
||||
delete extraConfig['seed-time']
|
||||
}
|
||||
console.log('extraConfig===>', extraConfig)
|
||||
|
||||
this.instance.on('stop', function (process) {
|
||||
logger.info('[Motrix] Engine stopped')
|
||||
})
|
||||
const extra = transformConfig(extraConfig)
|
||||
result = [...result, ...extra]
|
||||
|
||||
// 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}`)
|
||||
// })
|
||||
return result
|
||||
}
|
||||
|
||||
isRunning (pid) {
|
||||
@@ -109,31 +129,6 @@ export default class Engine {
|
||||
}
|
||||
}
|
||||
|
||||
stop () {
|
||||
const { pid } = this.instance.child
|
||||
try {
|
||||
logger.info('[Motrix] Engine stopping')
|
||||
this.instance.stop()
|
||||
} catch (err) {
|
||||
logger.error('[Motrix] Engine stop fail:', err.message)
|
||||
this.forceStop(pid)
|
||||
} finally {
|
||||
this.instance.removeAllListeners('start')
|
||||
this.instance.removeAllListeners('error')
|
||||
this.instance.removeAllListeners('stop')
|
||||
}
|
||||
}
|
||||
|
||||
forceStop (pid) {
|
||||
try {
|
||||
if (pid && this.isRunning(pid)) {
|
||||
process.kill(pid)
|
||||
}
|
||||
} catch (err) {
|
||||
logger.warn('[Motrix] Engine force stop fail:', err)
|
||||
}
|
||||
}
|
||||
|
||||
restart () {
|
||||
this.stop()
|
||||
this.start()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use strict'
|
||||
|
||||
import Aria2 from 'aria2'
|
||||
import { Aria2 } from '@shared/aria2'
|
||||
|
||||
import logger from './Logger'
|
||||
import {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import is from 'electron-is'
|
||||
import logger from 'electron-log'
|
||||
|
||||
logger.transports.file.level = is.production() ? 'warn' : 'silly'
|
||||
logger.transports.file.level = is.production() ? 'info' : 'silly'
|
||||
logger.info('[Motrix] Logger init')
|
||||
logger.warn('[Motrix] Logger init')
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
|
||||
import { EventEmitter } from 'events'
|
||||
import { app } from 'electron'
|
||||
import is from 'electron-is'
|
||||
import { parse } from 'querystring'
|
||||
|
||||
import logger from './Logger'
|
||||
import protocolMap from '../configs/protocol'
|
||||
@@ -27,7 +28,11 @@ export default class ProtocolManager extends EventEmitter {
|
||||
this.setup(protocols)
|
||||
}
|
||||
|
||||
setup (protocols) {
|
||||
setup (protocols = {}) {
|
||||
if (is.dev() || is.mas()) {
|
||||
return
|
||||
}
|
||||
|
||||
Object.keys(protocols).forEach((protocol) => {
|
||||
const enabled = protocols[protocol]
|
||||
if (enabled) {
|
||||
@@ -66,12 +71,15 @@ export default class ProtocolManager extends EventEmitter {
|
||||
return
|
||||
}
|
||||
|
||||
global.application.sendCommandToAll('application:new-task', ADD_TASK_TYPE.URI, url)
|
||||
global.application.sendCommandToAll('application:new-task', {
|
||||
type: ADD_TASK_TYPE.URI,
|
||||
uri: url
|
||||
})
|
||||
}
|
||||
|
||||
handleMoProtocol (url) {
|
||||
const parsed = new URL(url)
|
||||
const { host } = parsed
|
||||
const { host, search } = parsed
|
||||
logger.info('[Motrix] protocol parsed:', parsed, host)
|
||||
|
||||
const command = protocolMap[host]
|
||||
@@ -79,10 +87,8 @@ export default class ProtocolManager extends EventEmitter {
|
||||
return
|
||||
}
|
||||
|
||||
// @TODO 没想明白怎么传参数好
|
||||
// 如果按顺序传递,那 url 的 query string 就要求有序的了
|
||||
// const query = queryString.parse(parsed.query)
|
||||
const args = []
|
||||
global.application.sendCommandToAll(command, ...args)
|
||||
const query = search.startsWith('?') ? search.replace('?', '') : search
|
||||
const args = parse(query)
|
||||
global.application.sendCommandToAll(command, args)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ export default class UPnPManager {
|
||||
try {
|
||||
client.map(port, (err) => {
|
||||
if (err) {
|
||||
logger.warn(`[Motrix] UPnPManager map ${port} failed, error: `, err)
|
||||
logger.warn(`[Motrix] UPnPManager map ${port} failed, error: `, err.message)
|
||||
reject(err.message)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import is from 'electron-is'
|
||||
import { autoUpdater } from 'electron-updater'
|
||||
|
||||
import logger from './Logger'
|
||||
import { getI18n } from '@/ui/Locale'
|
||||
import { getI18n } from '../ui/Locale'
|
||||
|
||||
if (is.dev()) {
|
||||
autoUpdater.updateConfigPath = resolve(__dirname, '../../../app-update.yml')
|
||||
@@ -17,8 +17,10 @@ export default class UpdateManager extends EventEmitter {
|
||||
this.options = options
|
||||
this.i18n = getI18n()
|
||||
|
||||
this.isChecking = false
|
||||
this.updater = autoUpdater
|
||||
this.updater.autoDownload = false
|
||||
this.updater.autoInstallOnAppQuit = false
|
||||
this.updater.logger = logger
|
||||
this.autoCheckData = {
|
||||
checkEnable: this.options.autoCheck,
|
||||
@@ -40,9 +42,10 @@ export default class UpdateManager extends EventEmitter {
|
||||
this.updater.on('update-not-available', this.updateNotAvailable.bind(this))
|
||||
this.updater.on('download-progress', this.updateDownloadProgress.bind(this))
|
||||
this.updater.on('update-downloaded', this.updateDownloaded.bind(this))
|
||||
this.updater.on('update-cancelled', this.updateCancelled.bind(this))
|
||||
this.updater.on('error', this.updateError.bind(this))
|
||||
|
||||
if (this.autoCheckData.checkEnable) {
|
||||
if (this.autoCheckData.checkEnable && !this.isChecking) {
|
||||
this.autoCheckData.userCheck = false
|
||||
this.updater.checkForUpdates()
|
||||
}
|
||||
@@ -54,6 +57,7 @@ export default class UpdateManager extends EventEmitter {
|
||||
}
|
||||
|
||||
checkingForUpdate () {
|
||||
this.isChecking = true
|
||||
this.emit('checking')
|
||||
}
|
||||
|
||||
@@ -68,11 +72,14 @@ export default class UpdateManager extends EventEmitter {
|
||||
}).then(({ response }) => {
|
||||
if (response === 0) {
|
||||
this.updater.downloadUpdate()
|
||||
} else {
|
||||
this.emit('update-cancelled', info)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
updateNotAvailable (event, info) {
|
||||
this.isChecking = false
|
||||
this.emit('update-not-available', info)
|
||||
if (this.autoCheckData.userCheck) {
|
||||
dialog.showMessageBox({
|
||||
@@ -102,20 +109,26 @@ export default class UpdateManager extends EventEmitter {
|
||||
title: this.i18n.t('app.check-for-updates-title'),
|
||||
message: this.i18n.t('app.update-downloaded-message')
|
||||
}).then(_ => {
|
||||
this.isChecking = false
|
||||
this.emit('will-updated')
|
||||
setImmediate(() => {
|
||||
setTimeout(() => {
|
||||
this.updater.quitAndInstall()
|
||||
})
|
||||
}, 200)
|
||||
})
|
||||
}
|
||||
|
||||
updateCancelled () {
|
||||
this.isChecking = false
|
||||
}
|
||||
|
||||
updateError (event, error) {
|
||||
this.isChecking = false
|
||||
this.emit('update-error', error)
|
||||
const msg = (error == null)
|
||||
? this.i18n.t('update-error-message')
|
||||
? this.i18n.t('app.update-error-message')
|
||||
: (error.stack || error).toString()
|
||||
|
||||
this.updater.logger.warn(`[Motrix] update-error: ${msg}`)
|
||||
dialog.showErrorBox(msg)
|
||||
dialog.showErrorBox('Error', msg)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,12 +7,6 @@
|
||||
|
||||
/* eslint-disable */
|
||||
|
||||
// Install `electron-debug` with `devtron`
|
||||
require('electron-debug')({
|
||||
// devToolsMode: 'right',
|
||||
showDevTools: true
|
||||
})
|
||||
|
||||
// Install `vue-devtools`
|
||||
require('electron').app.whenReady().then(() => {
|
||||
let installExtension = require('electron-devtools-installer')
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
import { app } from 'electron'
|
||||
import is from 'electron-is'
|
||||
import { initialize } from '@electron/remote/main'
|
||||
|
||||
import Launcher from './Launcher'
|
||||
|
||||
/**
|
||||
* initialize the main-process side of the remote module
|
||||
*/
|
||||
initialize()
|
||||
|
||||
process.env.ELECTRON_DISABLE_SECURITY_WARNINGS = 'true'
|
||||
|
||||
if (process.env.NODE_ENV !== 'development') {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
{
|
||||
"id": "menu.app",
|
||||
"submenu": [
|
||||
{ "id": "app.about", "command": "application:about", "command-before": "application:show,index" },
|
||||
{ "id": "app.about", "command": "application:about", "command-before": "application:show?page=index" },
|
||||
{ "type": "separator" },
|
||||
{ "id": "app.preferences", "command": "application:preferences" },
|
||||
{ "id": "app.check-for-updates", "command": "application:check-for-updates" },
|
||||
@@ -11,16 +11,17 @@
|
||||
{ "id": "app.hide-others", "role": "hideothers" },
|
||||
{ "id": "app.unhide", "role": "unhide" },
|
||||
{ "type": "separator" },
|
||||
{ "id": "app.quit", "command": "application:quit" }
|
||||
{ "id": "app.quit", "role": "quit" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "menu.task",
|
||||
"submenu": [
|
||||
{ "id": "task.new-task", "command": "application:new-task", "command-after": "application:show,index" },
|
||||
{ "id": "task.new-bt-task", "command": "application:new-bt-task", "command-arg": "torrent", "command-after": "application:show,index" },
|
||||
{ "id": "task.open-file", "command": "application:open-file", "command-before": "application:show,index" },
|
||||
{ "id": "task.new-task", "command": "application:new-task", "command-after": "application:show?page=index" },
|
||||
{ "id": "task.new-bt-task", "command": "application:new-bt-task", "command-after": "application:show?page=index" },
|
||||
{ "id": "task.open-file", "command": "application:open-file", "command-before": "application:show?page=index" },
|
||||
{ "type": "separator" },
|
||||
{ "id": "app.task-list", "command": "application:task-list" },
|
||||
{ "id": "task.pause-task", "command": "application:pause-task" },
|
||||
{ "id": "task.resume-task", "command": "application:resume-task" },
|
||||
{ "id": "task.delete-task", "command": "application:delete-task" },
|
||||
|
||||
@@ -3,22 +3,23 @@
|
||||
{
|
||||
"id": "menu.file",
|
||||
"submenu": [
|
||||
{ "id": "app.about", "command": "application:about", "command-before": "application:show,index" },
|
||||
{ "id": "app.about", "command": "application:about", "command-before": "application:show?page=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" },
|
||||
{ "id": "app.show", "command": "application:show", "command-arg": { "page": "index" } },
|
||||
{ "type": "separator" },
|
||||
{ "id": "app.quit", "command": "application:quit" }
|
||||
{ "id": "app.quit", "role": "quit" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "menu.task",
|
||||
"submenu": [
|
||||
{ "id": "task.new-task", "command": "application:new-task", "command-after": "application:show,index" },
|
||||
{ "id": "task.new-bt-task", "command": "application:new-bt-task", "command-arg": "torrent", "command-after": "application:show,index" },
|
||||
{ "id": "task.open-file", "command": "application:open-file", "command-before": "application:show,index" },
|
||||
{ "id": "task.new-task", "command": "application:new-task", "command-after": "application:show?page=index" },
|
||||
{ "id": "task.new-bt-task", "command": "application:new-bt-task", "command-after": "application:show?page=index" },
|
||||
{ "id": "task.open-file", "command": "application:open-file", "command-before": "application:show?page=index" },
|
||||
{ "type": "separator" },
|
||||
{ "id": "app.task-list", "command": "application:task-list" },
|
||||
{ "id": "task.pause-task", "command": "application:pause-task" },
|
||||
{ "id": "task.resume-task", "command": "application:resume-task" },
|
||||
{ "id": "task.delete-task", "command": "application:delete-task" },
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[
|
||||
{
|
||||
"type": "button", "icon": "new-task", "id": "task.new-task", "command": "application:new-task", "command-arg": "uri", "command-after": "application:show,index"
|
||||
"type": "button", "icon": "new-task", "id": "task.new-task", "command": "application:new-task", "command-after": "application:show?page=index"
|
||||
},
|
||||
{
|
||||
"type": "spacer", "size": "small"
|
||||
@@ -10,13 +10,13 @@
|
||||
"id": "task.task-list",
|
||||
"items": [
|
||||
{
|
||||
"type": "button", "icon": "task-active", "command": "application:task-list", "command-arg": "active"
|
||||
"type": "button", "icon": "task-active", "command": "application:task-list", "command-arg": { "status": "active" }
|
||||
},
|
||||
{
|
||||
"type": "button", "icon": "task-waiting", "command": "application:task-list", "command-arg": "waiting"
|
||||
"type": "button", "icon": "task-waiting", "command": "application:task-list", "command-arg": { "status": "waiting" }
|
||||
},
|
||||
{
|
||||
"type": "button", "icon": "task-stopped", "command": "application:task-list", "command-arg": "stopped"
|
||||
"type": "button", "icon": "task-stopped", "command": "application:task-list", "command-arg": { "status": "stopped" }
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -30,6 +30,6 @@
|
||||
"type": "spacer", "size": "small"
|
||||
},
|
||||
{
|
||||
"type": "button", "icon": "about", "id": "app.about", "command": "application:about", "command-before": "application:show,index"
|
||||
"type": "button", "icon": "about", "id": "app.about", "command": "application:about", "command-before": "application:show?page=index"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
[
|
||||
{ "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" },
|
||||
{ "id": "task.new-task", "command": "application:new-task", "command-after": "application:show?page=index" },
|
||||
{ "id": "task.new-bt-task", "command": "application:new-bt-task", "command-arg": { "type": "torrent" }, "command-after": "application:show?page=index" },
|
||||
{ "id": "task.open-file", "command": "application:open-file", "command-before": "application:show?page=index" },
|
||||
{ "type": "separator" },
|
||||
{ "id": "app.show", "command": "application:show", "command-arg": "index" },
|
||||
{ "id": "app.show", "command": "application:show", "command-arg": { "page": "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.task-list", "command": "application:task-list", "command-before": "application:show?page=index" },
|
||||
{ "id": "app.preferences", "command": "application:preferences", "command-before": "application:show?page=index" },
|
||||
{ "id": "app.quit", "command": "application:quit" }
|
||||
]
|
||||
|
||||
@@ -3,22 +3,23 @@
|
||||
{
|
||||
"id": "menu.file",
|
||||
"submenu": [
|
||||
{ "id": "app.about", "command": "application:about", "command-before": "application:show,index" },
|
||||
{ "id": "app.about", "command": "application:about", "command-before": "application:show?page=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" },
|
||||
{ "id": "app.show", "command": "application:show", "command-arg": { "page": "index" } },
|
||||
{ "type": "separator" },
|
||||
{ "id": "app.quit", "command": "application:quit" }
|
||||
{ "id": "app.quit", "role": "quit" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "menu.task",
|
||||
"submenu": [
|
||||
{ "id": "task.new-task", "command": "application:new-task", "command-after": "application:show,index" },
|
||||
{ "id": "task.new-bt-task", "command": "application:new-bt-task", "command-arg": "torrent", "command-after": "application:show,index" },
|
||||
{ "id": "task.open-file", "command": "application:open-file", "command-before": "application:show,index" },
|
||||
{ "id": "task.new-task", "command": "application:new-task", "command-after": "application:show?page=index" },
|
||||
{ "id": "task.new-bt-task", "command": "application:new-bt-task", "command-after": "application:show?page=index" },
|
||||
{ "id": "task.open-file", "command": "application:open-file", "command-before": "application:show?page=index" },
|
||||
{ "type": "separator" },
|
||||
{ "id": "app.task-list", "command": "application:task-list" },
|
||||
{ "id": "task.pause-task", "command": "application:pause-task" },
|
||||
{ "id": "task.resume-task", "command": "application:resume-task" },
|
||||
{ "id": "task.delete-task", "command": "application:delete-task" },
|
||||
|
||||
@@ -2,43 +2,63 @@ import is from 'electron-is'
|
||||
import { EventEmitter } from 'events'
|
||||
import { app } from 'electron'
|
||||
|
||||
import { bytesToSize } from '@shared/utils'
|
||||
|
||||
import {
|
||||
APP_RUN_MODE
|
||||
} from '@shared/constants'
|
||||
|
||||
const isMac = is.macOS()
|
||||
const enabled = is.macOS()
|
||||
|
||||
export default class DockManager extends EventEmitter {
|
||||
constructor (options) {
|
||||
super()
|
||||
this.options = options
|
||||
const { runMode } = this.options
|
||||
if (runMode !== APP_RUN_MODE.STANDARD) {
|
||||
if (runMode === APP_RUN_MODE.TRAY) {
|
||||
this.hide()
|
||||
}
|
||||
}
|
||||
|
||||
show = isMac ? () => {
|
||||
if (app.dock.isVisible()) {
|
||||
return
|
||||
show = enabled
|
||||
? () => {
|
||||
if (app.dock.isVisible()) {
|
||||
return
|
||||
}
|
||||
|
||||
return app.dock.show()
|
||||
}
|
||||
: () => {}
|
||||
|
||||
return app.dock.show()
|
||||
} : () => {}
|
||||
hide = enabled
|
||||
? () => {
|
||||
if (!app.dock.isVisible()) {
|
||||
return
|
||||
}
|
||||
|
||||
hide = isMac ? () => {
|
||||
if (!app.dock.isVisible()) {
|
||||
return
|
||||
app.dock.hide()
|
||||
}
|
||||
: () => {}
|
||||
|
||||
app.dock.hide()
|
||||
} : () => {}
|
||||
// macOS setBadge not working
|
||||
// @see https://github.com/electron/electron/issues/25745#issuecomment-702826143
|
||||
setBadge = enabled
|
||||
? (text) => {
|
||||
app.dock.setBadge(text)
|
||||
}
|
||||
: (text) => {}
|
||||
|
||||
setBadge = isMac ? (text) => {
|
||||
app.dock.setBadge(text)
|
||||
} : (text) => {}
|
||||
handleSpeedChange = enabled
|
||||
? (speed) => {
|
||||
const { downloadSpeed } = speed
|
||||
const text = downloadSpeed > 0 ? `${bytesToSize(downloadSpeed)}/s` : ''
|
||||
this.setBadge(text)
|
||||
}
|
||||
: (text) => {}
|
||||
|
||||
openDock = isMac ? (path) => {
|
||||
app.dock.downloadFinished(path)
|
||||
} : (path) => {}
|
||||
openDock = enabled
|
||||
? (path) => {
|
||||
app.dock.downloadFinished(path)
|
||||
}
|
||||
: (path) => {}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { EventEmitter } from 'events'
|
||||
import { Menu } from 'electron'
|
||||
|
||||
import keymap from '@shared/keymap'
|
||||
import {
|
||||
translateTemplate,
|
||||
flattenMenuItems,
|
||||
updateStates
|
||||
} from '../utils/menu'
|
||||
import keymap from '@shared/keymap'
|
||||
import { getI18n } from '@/ui/Locale'
|
||||
import { getI18n } from '../ui/Locale'
|
||||
|
||||
export default class MenuManager extends EventEmitter {
|
||||
constructor (options) {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { EventEmitter } from 'events'
|
||||
import { nativeTheme, systemPreferences } from 'electron'
|
||||
import is from 'electron-is'
|
||||
import { nativeTheme } from 'electron'
|
||||
|
||||
import { APP_THEME } from '@shared/constants'
|
||||
import logger from '../core/Logger'
|
||||
import { getSystemTheme } from '../utils'
|
||||
|
||||
export default class ThemeManager extends EventEmitter {
|
||||
@@ -24,26 +24,16 @@ export default class ThemeManager extends EventEmitter {
|
||||
}
|
||||
|
||||
handleEvents () {
|
||||
if (!is.macOS()) {
|
||||
return
|
||||
}
|
||||
|
||||
nativeTheme.on('updated', () => {
|
||||
const theme = getSystemTheme()
|
||||
this.systemTheme = theme
|
||||
console.log('nativeTheme updated===>', theme)
|
||||
logger.info('[Motrix] nativeTheme updated===>', theme)
|
||||
this.emit('system-theme-change', theme)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* deprecated
|
||||
* @see https://www.electronjs.org/docs/all#systempreferencessetapplevelappearanceappearance-macos-deprecated
|
||||
*/
|
||||
updateAppAppearance (theme) {
|
||||
if (!is.macOS() || theme !== APP_THEME.LIGHT || theme !== APP_THEME.DARK) {
|
||||
return
|
||||
}
|
||||
systemPreferences.setAppLevelAppearance(theme)
|
||||
updateSystemTheme (theme) {
|
||||
theme = theme === APP_THEME.AUTO ? 'system' : theme
|
||||
nativeTheme.themeSource = theme
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,54 +1,131 @@
|
||||
import { EventEmitter } from 'events'
|
||||
import { join } from 'path'
|
||||
import { Tray, Menu, nativeTheme } from 'electron'
|
||||
import { Tray, Menu, nativeImage } from 'electron'
|
||||
import is from 'electron-is'
|
||||
|
||||
import { APP_RUN_MODE, APP_THEME } from '@shared/constants'
|
||||
import { getInverseTheme } from '@shared/utils'
|
||||
import logger from '../core/Logger'
|
||||
import { getI18n } from './Locale'
|
||||
import {
|
||||
translateTemplate,
|
||||
flattenMenuItems,
|
||||
updateStates
|
||||
} from '../utils/menu'
|
||||
import { getI18n } from '@/ui/Locale'
|
||||
import { APP_THEME } from '@shared/constants'
|
||||
import { convertArrayBufferToBuffer } from '../utils/index'
|
||||
|
||||
let tray = null
|
||||
const { platform } = process
|
||||
|
||||
export default class TrayManager extends EventEmitter {
|
||||
constructor (options = {}) {
|
||||
super()
|
||||
|
||||
this.options = options
|
||||
this.theme = options.theme || APP_THEME.AUTO
|
||||
|
||||
this.systemTheme = options.systemTheme
|
||||
this.inverseSystemTheme = getInverseTheme(this.systemTheme)
|
||||
this.macOS = platform === 'darwin'
|
||||
|
||||
this.speedometer = options.speedometer
|
||||
this.runMode = options.runMode
|
||||
|
||||
this.i18n = getI18n()
|
||||
this.menu = null
|
||||
this.cache = {}
|
||||
|
||||
this.uploadSpeed = 0
|
||||
this.downloadSpeed = 0
|
||||
this.status = false
|
||||
this.focused = false
|
||||
this.initialized = false
|
||||
|
||||
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
|
||||
init () {
|
||||
if (tray || this.initialized || this.runMode === APP_RUN_MODE.HIDE_TRAY) {
|
||||
return
|
||||
}
|
||||
|
||||
this.setIcons(theme)
|
||||
this.loadTemplate()
|
||||
this.loadImages()
|
||||
this.initTray()
|
||||
this.setupMenu()
|
||||
this.bindEvents()
|
||||
|
||||
this.initialized = true
|
||||
}
|
||||
|
||||
setIcons (theme) {
|
||||
this.normalIcon = join(__static, `./mo-tray-${theme}-normal.png`)
|
||||
this.activeIcon = join(__static, `./mo-tray-${theme}-active.png`)
|
||||
loadTemplate () {
|
||||
this.template = require('../menus/tray.json')
|
||||
}
|
||||
|
||||
build () {
|
||||
loadImages () {
|
||||
switch (platform) {
|
||||
case 'darwin':
|
||||
this.loadImagesForMacOS()
|
||||
break
|
||||
case 'win32':
|
||||
this.loadImagesForWindows()
|
||||
break
|
||||
case 'linux':
|
||||
this.loadImagesForLinux()
|
||||
break
|
||||
|
||||
default:
|
||||
this.loadImagesForDefault()
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
loadImagesForMacOS () {
|
||||
this.normalIcon = this.getFromCacheOrCreateImage('mo-tray-light-normal.png')
|
||||
}
|
||||
|
||||
loadImagesForWindows () {
|
||||
this.normalIcon = this.getFromCacheOrCreateImage('mo-tray-colorful-normal.png')
|
||||
this.activeIcon = this.getFromCacheOrCreateImage('mo-tray-colorful-active.png')
|
||||
}
|
||||
|
||||
loadImagesForLinux () {
|
||||
const { theme } = this
|
||||
if (theme === APP_THEME.AUTO) {
|
||||
this.normalIcon = this.getFromCacheOrCreateImage('mo-tray-dark-normal.png')
|
||||
this.activeIcon = this.getFromCacheOrCreateImage('mo-tray-dark-active.png')
|
||||
} else {
|
||||
this.normalIcon = this.getFromCacheOrCreateImage(`mo-tray-${theme}-normal.png`)
|
||||
this.activeIcon = this.getFromCacheOrCreateImage(`mo-tray-${theme}-active.png`)
|
||||
}
|
||||
}
|
||||
|
||||
loadImagesForDefault () {
|
||||
this.normalIcon = this.getFromCacheOrCreateImage('mo-tray-light-normal.png')
|
||||
this.activeIcon = this.getFromCacheOrCreateImage('mo-tray-light-active.png')
|
||||
}
|
||||
|
||||
getFromCacheOrCreateImage (key) {
|
||||
let file = this.getCache(key)
|
||||
if (file) {
|
||||
return file
|
||||
}
|
||||
|
||||
file = nativeImage.createFromPath(join(__static, `./${key}`))
|
||||
file.setTemplateImage(this.macOS)
|
||||
this.setCache(key, file)
|
||||
return file
|
||||
}
|
||||
|
||||
getCache (key) {
|
||||
return this.cache[key]
|
||||
}
|
||||
|
||||
setCache (key, value) {
|
||||
this.cache[key] = value
|
||||
}
|
||||
|
||||
buildMenu () {
|
||||
const keystrokesByCommand = {}
|
||||
for (const item in this.keymap) {
|
||||
keystrokesByCommand[this.keymap[item]] = item
|
||||
@@ -61,69 +138,139 @@ export default class TrayManager extends EventEmitter {
|
||||
this.items = flattenMenuItems(this.menu)
|
||||
}
|
||||
|
||||
setup () {
|
||||
this.build()
|
||||
setupMenu () {
|
||||
this.buildMenu()
|
||||
|
||||
this.updateContextMenu()
|
||||
}
|
||||
|
||||
init () {
|
||||
tray = new Tray(this.normalIcon)
|
||||
tray.setToolTip('Motrix')
|
||||
initTray () {
|
||||
const { icon } = this.getIcons()
|
||||
tray = new Tray(icon)
|
||||
// tray.setPressedImage(inverseIcon)
|
||||
|
||||
if (!this.macOS) {
|
||||
tray.setToolTip('Motrix')
|
||||
}
|
||||
}
|
||||
|
||||
handleEvents () {
|
||||
bindEvents () {
|
||||
// All OS
|
||||
tray.on('click', this.handleTrayClick)
|
||||
|
||||
// macOS, Windows
|
||||
tray.on('double-click', this.handleTrayDbClick)
|
||||
// tray.on('double-click', this.handleTrayDbClick)
|
||||
tray.on('right-click', this.handleTrayRightClick)
|
||||
tray.on('mouse-down', this.handleTrayMouseDown)
|
||||
tray.on('mouse-up', this.handleTrayMouseUp)
|
||||
|
||||
// macOS only
|
||||
tray.on('drop-files', this.handleTrayDropFile)
|
||||
tray.setIgnoreDoubleClickEvents(true)
|
||||
tray.on('drop-files', this.handleTrayDropFiles)
|
||||
tray.on('drop-text', this.handleTrayDropText)
|
||||
}
|
||||
|
||||
unbindEvents () {
|
||||
// All OS
|
||||
tray.removeListener('click', this.handleTrayClick)
|
||||
|
||||
// macOS, Windows
|
||||
tray.removeListener('right-click', this.handleTrayRightClick)
|
||||
tray.removeListener('mouse-down', this.handleTrayMouseDown)
|
||||
tray.removeListener('mouse-up', this.handleTrayMouseUp)
|
||||
|
||||
// macOS only
|
||||
tray.removeListener('drop-files', this.handleTrayDropFiles)
|
||||
tray.removeListener('drop-text', this.handleTrayDropText)
|
||||
}
|
||||
|
||||
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])
|
||||
handleTrayMouseDown = (event) => {
|
||||
this.focused = true
|
||||
this.emit('mouse-down', {
|
||||
focused: true,
|
||||
theme: this.inverseSystemTheme
|
||||
})
|
||||
this.renderTray()
|
||||
}
|
||||
|
||||
updateTrayByStatus (status) {
|
||||
this.status = status
|
||||
this.updateTray()
|
||||
handleTrayMouseUp = (event) => {
|
||||
this.focused = false
|
||||
this.emit('mouse-up', {
|
||||
focused: false,
|
||||
theme: this.theme
|
||||
})
|
||||
this.renderTray()
|
||||
}
|
||||
|
||||
updateTray () {
|
||||
const icon = this.status ? this.activeIcon : this.normalIcon
|
||||
handleTrayDropFiles = (event, files) => {
|
||||
this.emit('drop-files', files)
|
||||
}
|
||||
|
||||
handleTrayDropText = (event, text) => {
|
||||
this.emit('drop-text', text)
|
||||
}
|
||||
|
||||
toggleSpeedometer (enabled) {
|
||||
this.speedometer = enabled
|
||||
}
|
||||
|
||||
async renderTray () {
|
||||
if (!tray || this.speedometer) {
|
||||
return
|
||||
}
|
||||
|
||||
const { icon } = this.getIcons()
|
||||
|
||||
tray.setImage(icon)
|
||||
// tray.setPressedImage(inverseIcon)
|
||||
|
||||
this.updateContextMenu()
|
||||
}
|
||||
|
||||
changeIconTheme (theme = APP_THEME.LIGHT) {
|
||||
if (!is.macOS()) {
|
||||
getIcons () {
|
||||
if (this.macOS) {
|
||||
return { icon: this.normalIcon }
|
||||
}
|
||||
|
||||
const { focused, status, systemTheme } = this
|
||||
|
||||
const icon = status ? this.activeIcon : this.normalIcon
|
||||
if (systemTheme === APP_THEME.DARK) {
|
||||
return {
|
||||
icon
|
||||
}
|
||||
}
|
||||
|
||||
const inverseIcon = status ? this.inverseActiveIcon : this.inverseNormalIcon
|
||||
|
||||
return {
|
||||
icon: focused ? inverseIcon : icon
|
||||
// inverseIcon: focused ? icon : inverseIcon
|
||||
}
|
||||
}
|
||||
|
||||
updateContextMenu () {
|
||||
/**
|
||||
* Linux requires setContextMenu to be called
|
||||
* in order for the context menu to populate correctly
|
||||
*/
|
||||
if (!tray || process.platform !== 'linux') {
|
||||
return
|
||||
}
|
||||
|
||||
this.setIcons(theme)
|
||||
|
||||
this.updateTray()
|
||||
tray.setContextMenu(this.menu)
|
||||
}
|
||||
|
||||
updateMenuStates (visibleStates, enabledStates, checkedStates) {
|
||||
@@ -146,26 +293,77 @@ export default class TrayManager extends EventEmitter {
|
||||
this.updateMenuStates(null, enabledStates, null)
|
||||
}
|
||||
|
||||
updateContextMenu () {
|
||||
/**
|
||||
* Linux requires setContextMenu to be called
|
||||
* in order for the context menu to populate correctly
|
||||
*/
|
||||
if (process.platform !== 'linux') {
|
||||
handleLocaleChange (locale) {
|
||||
this.setupMenu()
|
||||
}
|
||||
|
||||
handleRunModeChange (mode) {
|
||||
this.runMode = mode
|
||||
|
||||
if (mode === APP_RUN_MODE.HIDE_TRAY) {
|
||||
this.destroy()
|
||||
} else {
|
||||
this.init()
|
||||
}
|
||||
}
|
||||
|
||||
handleSpeedometerEnableChange (enabled) {
|
||||
this.toggleSpeedometer(enabled)
|
||||
|
||||
this.renderTray()
|
||||
}
|
||||
|
||||
handleSystemThemeChange (systemTheme = APP_THEME.LIGHT) {
|
||||
if (!is.macOS()) {
|
||||
return
|
||||
}
|
||||
|
||||
tray.setContextMenu(this.menu)
|
||||
this.systemTheme = systemTheme
|
||||
this.inverseSystemTheme = getInverseTheme(systemTheme)
|
||||
|
||||
this.loadImages()
|
||||
|
||||
this.renderTray()
|
||||
}
|
||||
|
||||
handleDownloadStatusChange (status) {
|
||||
this.status = status
|
||||
|
||||
this.renderTray()
|
||||
}
|
||||
|
||||
async handleSpeedChange ({ uploadSpeed, downloadSpeed }) {
|
||||
if (!this.speedometer) {
|
||||
return
|
||||
}
|
||||
|
||||
this.uploadSpeed = uploadSpeed
|
||||
this.downloadSpeed = downloadSpeed
|
||||
|
||||
await this.renderTray()
|
||||
}
|
||||
|
||||
async updateTrayByImage (ab) {
|
||||
if (!tray) {
|
||||
return
|
||||
}
|
||||
|
||||
const buffer = convertArrayBufferToBuffer(ab)
|
||||
const image = nativeImage.createFromBuffer(buffer, {
|
||||
scaleFactor: 2
|
||||
})
|
||||
image.setTemplateImage(this.macOS)
|
||||
tray.setImage(image)
|
||||
}
|
||||
|
||||
destroy () {
|
||||
logger.info('[Motrix] TrayManager.destroy')
|
||||
if (tray) {
|
||||
tray.removeListener('click', this.handleTrayClick)
|
||||
tray.removeListener('double-click', this.handleTrayDbClick)
|
||||
tray.removeListener('right-click', this.handleTrayRightClick)
|
||||
tray.removeListener('drop-files', this.handleTrayDropFile)
|
||||
this.unbindEvents()
|
||||
}
|
||||
|
||||
tray.destroy()
|
||||
tray = null
|
||||
this.initialized = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +1,35 @@
|
||||
import { join } from 'path'
|
||||
import { EventEmitter } from 'events'
|
||||
import { debounce } from 'lodash'
|
||||
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 = {
|
||||
const baseBrowserOptions = {
|
||||
titleBarStyle: 'hiddenInset',
|
||||
show: false,
|
||||
width: 1024,
|
||||
height: 768,
|
||||
backgroundColor: '#fff',
|
||||
webPreferences: {
|
||||
nodeIntegration: true,
|
||||
webviewTag: true
|
||||
nodeIntegration: true
|
||||
}
|
||||
}
|
||||
|
||||
// fix: BrowserWindow rendering bug under linux
|
||||
const defaultBrowserOptions = is.macOS()
|
||||
? {
|
||||
...baseBrowserOptions,
|
||||
vibrancy: 'ultra-dark',
|
||||
visualEffectState: 'active',
|
||||
backgroundColor: '#00000000'
|
||||
}
|
||||
: {
|
||||
...baseBrowserOptions
|
||||
}
|
||||
|
||||
export default class WindowManager extends EventEmitter {
|
||||
constructor (options = {}) {
|
||||
super()
|
||||
@@ -82,7 +95,13 @@ export default class WindowManager extends EventEmitter {
|
||||
|
||||
window = new BrowserWindow({
|
||||
...defaultBrowserOptions,
|
||||
...pageOptions.attrs
|
||||
...pageOptions.attrs,
|
||||
webPreferences: {
|
||||
enableRemoteModule: true,
|
||||
contextIsolation: false,
|
||||
nodeIntegration: true,
|
||||
nodeIntegrationInWorker: true
|
||||
}
|
||||
})
|
||||
|
||||
const bounds = this.getPageBounds(page)
|
||||
@@ -90,9 +109,13 @@ export default class WindowManager extends EventEmitter {
|
||||
window.setBounds(bounds)
|
||||
}
|
||||
|
||||
window.webContents.on('new-window', (e, url) => {
|
||||
e.preventDefault()
|
||||
if (is.dev() && pageOptions.openDevTools) {
|
||||
window.webContents.openDevTools()
|
||||
}
|
||||
|
||||
window.webContents.setWindowOpenHandler(({ url }) => {
|
||||
shell.openExternal(url)
|
||||
return { action: 'deny' }
|
||||
})
|
||||
|
||||
if (pageOptions.url) {
|
||||
@@ -123,6 +146,7 @@ export default class WindowManager extends EventEmitter {
|
||||
if (autoHideWindow) {
|
||||
this.handleWindowBlur()
|
||||
}
|
||||
|
||||
return window
|
||||
}
|
||||
|
||||
@@ -194,7 +218,7 @@ export default class WindowManager extends EventEmitter {
|
||||
|
||||
showWindow (page) {
|
||||
const window = this.getWindow(page)
|
||||
if (!window || window.isVisible()) {
|
||||
if (!window || (window.isVisible() && !window.isMinimized())) {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -8,12 +8,10 @@ import {
|
||||
ENGINE_MAX_CONNECTION_PER_SERVER,
|
||||
IP_VERSION
|
||||
} from '@shared/constants'
|
||||
|
||||
import logger from '../core/Logger'
|
||||
import engineBinMap from '../configs/engine'
|
||||
import { engineBinMap, engineArchMap } from '../configs/engine'
|
||||
|
||||
export function getLogPath () {
|
||||
return logger.transports.file.file
|
||||
return app.getPath('logs')
|
||||
}
|
||||
|
||||
export function getDhtPath (protocol) {
|
||||
@@ -25,6 +23,10 @@ export function getSessionPath () {
|
||||
return resolve(app.getPath('userData'), './download.session')
|
||||
}
|
||||
|
||||
export function getEnginePidPath () {
|
||||
return resolve(app.getPath('userData'), './engine.pid')
|
||||
}
|
||||
|
||||
export function getUserDataPath () {
|
||||
return app.getPath('userData')
|
||||
}
|
||||
@@ -38,6 +40,42 @@ export function getEngineBin (platform) {
|
||||
return result
|
||||
}
|
||||
|
||||
export function getEngineArch (platform, arch) {
|
||||
if (!['darwin', 'win32', 'linux'].includes(platform)) {
|
||||
return ''
|
||||
}
|
||||
|
||||
const result = engineArchMap[platform][arch]
|
||||
return result
|
||||
}
|
||||
|
||||
export const getDevEnginePath = (platform, arch) => {
|
||||
const ah = getEngineArch(platform, arch)
|
||||
const base = `../../../extra/${platform}/${ah}/engine`
|
||||
const result = resolve(__dirname, base)
|
||||
return result
|
||||
}
|
||||
|
||||
export const getProdEnginePath = () => {
|
||||
return resolve(app.getAppPath(), '../engine')
|
||||
}
|
||||
|
||||
export const getEnginePath = (platform, arch) => {
|
||||
return is.dev() ? getDevEnginePath(platform, arch) : getProdEnginePath()
|
||||
}
|
||||
|
||||
export const getAria2BinPath = (platform, arch) => {
|
||||
const base = getEnginePath(platform, arch)
|
||||
const binName = getEngineBin(platform)
|
||||
const result = resolve(base, `./${binName}`)
|
||||
return result
|
||||
}
|
||||
|
||||
export const getAria2ConfPath = (platform, arch) => {
|
||||
const base = getEnginePath(platform, arch)
|
||||
return resolve(base, './aria2.conf')
|
||||
}
|
||||
|
||||
export function transformConfig (config) {
|
||||
const result = []
|
||||
for (const [k, v] of Object.entries(config)) {
|
||||
@@ -141,3 +179,12 @@ export const getSystemTheme = () => {
|
||||
result = nativeTheme.shouldUseDarkColors ? APP_THEME.DARK : APP_THEME.LIGHT
|
||||
return result
|
||||
}
|
||||
|
||||
export const convertArrayBufferToBuffer = (arrayBuffer) => {
|
||||
const buffer = Buffer.alloc(arrayBuffer.byteLength)
|
||||
const view = new Uint8Array(arrayBuffer)
|
||||
for (let i = 0; i < buffer.length; ++i) {
|
||||
buffer[i] = view[i]
|
||||
}
|
||||
return buffer
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { parse } from 'querystring'
|
||||
|
||||
export function concat (template, submenu, submenuToAdd) {
|
||||
submenuToAdd.forEach(sub => {
|
||||
let relativeItem = null
|
||||
@@ -115,16 +117,18 @@ function handleCommandBefore (item) {
|
||||
if (!item['command-before']) {
|
||||
return
|
||||
}
|
||||
const [command, ...args] = item['command-before'].split(',')
|
||||
global.application.sendCommandToAll(command, ...args)
|
||||
const [command, params] = item['command-before'].split('?')
|
||||
const args = parse(params)
|
||||
global.application.sendCommandToAll(command, args)
|
||||
}
|
||||
|
||||
function handleCommandAfter (item) {
|
||||
if (!item['command-after']) {
|
||||
return
|
||||
}
|
||||
const [command, ...args] = item['command-after'].split(',')
|
||||
global.application.sendCommandToAll(command, ...args)
|
||||
const [command, params] = item['command-after'].split('?')
|
||||
const args = parse(params)
|
||||
global.application.sendCommandToAll(command, args)
|
||||
}
|
||||
|
||||
function acceleratorForCommand (command, keystrokesByCommand) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { ipcRenderer, remote } from 'electron'
|
||||
import { ipcRenderer } from 'electron'
|
||||
import is from 'electron-is'
|
||||
import { isEmpty, clone } from 'lodash'
|
||||
import Aria2 from 'aria2'
|
||||
import { Aria2 } from '@shared/aria2'
|
||||
import {
|
||||
separateConfig,
|
||||
compactUndefined,
|
||||
@@ -12,8 +12,6 @@ import {
|
||||
} from '@shared/utils'
|
||||
import { ENGINE_RPC_HOST } from '@shared/constants'
|
||||
|
||||
const application = remote.getGlobal('application')
|
||||
|
||||
export default class Api {
|
||||
constructor (options = {}) {
|
||||
this.options = options
|
||||
@@ -21,8 +19,8 @@ export default class Api {
|
||||
this.init()
|
||||
}
|
||||
|
||||
init () {
|
||||
this.config = this.loadConfig()
|
||||
async init () {
|
||||
this.config = await this.loadConfig()
|
||||
|
||||
this.client = this.initClient()
|
||||
this.client.open()
|
||||
@@ -34,17 +32,14 @@ export default class Api {
|
||||
return result
|
||||
}
|
||||
|
||||
loadConfigFromNativeStore () {
|
||||
const systemConfig = application.configManager.getSystemConfig()
|
||||
const userConfig = application.configManager.getUserConfig()
|
||||
|
||||
const result = { ...systemConfig, ...userConfig }
|
||||
async loadConfigFromNativeStore () {
|
||||
const result = await ipcRenderer.invoke('get-app-config')
|
||||
return result
|
||||
}
|
||||
|
||||
loadConfig () {
|
||||
async loadConfig () {
|
||||
let result = is.renderer()
|
||||
? this.loadConfigFromNativeStore()
|
||||
? await this.loadConfigFromNativeStore()
|
||||
: this.loadConfigFromLocalStorage()
|
||||
|
||||
result = changeKeysToCamelCase(result)
|
||||
@@ -240,6 +235,12 @@ export default class Api {
|
||||
return this.client.call('tellStopped', ...args)
|
||||
}
|
||||
|
||||
fetchActiveTaskList (params = {}) {
|
||||
const { keys } = params
|
||||
const args = compactUndefined([keys])
|
||||
return this.client.call('tellActive', ...args)
|
||||
}
|
||||
|
||||
fetchTaskList (params = {}) {
|
||||
const { type } = params
|
||||
switch (type) {
|
||||
@@ -260,6 +261,30 @@ export default class Api {
|
||||
return this.client.call('tellStatus', ...args)
|
||||
}
|
||||
|
||||
fetchTaskItemWithPeers (params = {}) {
|
||||
const { gid, keys } = params
|
||||
const statusArgs = compactUndefined([gid, keys])
|
||||
const peersArgs = compactUndefined([gid])
|
||||
return new Promise((resolve, reject) => {
|
||||
this.client.multicall([
|
||||
['aria2.tellStatus', ...statusArgs],
|
||||
['aria2.getPeers', ...peersArgs]
|
||||
]).then((data) => {
|
||||
console.log('[Motrix] fetchTaskItemWithPeers:', data)
|
||||
const result = data[0] && data[0][0]
|
||||
const peers = data[1] && data[1][0]
|
||||
result.peers = peers || []
|
||||
console.log('[Motrix] fetchTaskItemWithPeers.result:', result)
|
||||
console.log('[Motrix] fetchTaskItemWithPeers.peers:', peers)
|
||||
|
||||
resolve(result)
|
||||
}).catch((err) => {
|
||||
console.log('[Motrix] fetch downloading task list fail:', err)
|
||||
reject(err)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fetchTaskItemPeers (params = {}) {
|
||||
const { gid, keys } = params
|
||||
const args = compactUndefined([gid, keys])
|
||||
|
||||
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 10 KiB |
@@ -0,0 +1,9 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||
<g stroke-linecap="square" stroke-linejoin="miter" stroke-width="2" fill="#000000" stroke="#000000">
|
||||
<line fill="none" stroke-miterlimit="10" x1="8.2" y1="4.5" x2="11.1" y2="7.3"></line>
|
||||
<line fill="none" stroke-miterlimit="10" x1="16.7" y1="12.9" x2="19.5" y2="15.8"></line>
|
||||
<path fill="none" stroke="#000000" stroke-miterlimit="10"
|
||||
d="M12.5,17.2 c-1.6,1.6-4.1,1.6-5.7,0c-1.6-1.6-1.6-4.1,0-5.7l7.1-7.1l-2.8-2.8L4,8.7C0.9,11.8,0.9,16.9,4,20s8.2,3.1,11.3,0l7.1-7.1l-2.8-2.8 L12.5,17.2z">
|
||||
</path>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 617 B |
@@ -2,7 +2,7 @@
|
||||
<el-dialog
|
||||
custom-class="app-about-dialog"
|
||||
width="61.8vw"
|
||||
:visible.sync="visible"
|
||||
:visible="visible"
|
||||
@open="handleOpen"
|
||||
:before-close="handleClose"
|
||||
@closed="handleClosed">
|
||||
@@ -15,6 +15,7 @@
|
||||
import { mapState } from 'vuex'
|
||||
import AppInfo from '@/components/About/AppInfo'
|
||||
import Copyright from '@/components/About/Copyright'
|
||||
import { app } from '@electron/remote'
|
||||
|
||||
export default {
|
||||
name: 'mo-about-panel',
|
||||
@@ -29,7 +30,7 @@
|
||||
}
|
||||
},
|
||||
data () {
|
||||
const version = this.$electron.remote.app.getVersion()
|
||||
const version = app.getVersion()
|
||||
return {
|
||||
version
|
||||
}
|
||||
@@ -53,12 +54,12 @@
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.app-about-dialog {
|
||||
max-width: 632px;
|
||||
min-width: 380px;
|
||||
.el-dialog__header {
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
.app-about-dialog {
|
||||
max-width: 632px;
|
||||
min-width: 380px;
|
||||
.el-dialog__header {
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -49,45 +49,45 @@
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.app-info {
|
||||
position: relative;
|
||||
margin: 8px 0;
|
||||
.app-version span {
|
||||
display: inline-block;
|
||||
vertical-align: bottom;
|
||||
font-size: $--font-size-large;
|
||||
margin-left: 20px;
|
||||
color: $--app-version-color;
|
||||
line-height: 18px;
|
||||
.app-info {
|
||||
position: relative;
|
||||
margin: 8px 0;
|
||||
.app-version span {
|
||||
display: inline-block;
|
||||
vertical-align: bottom;
|
||||
font-size: $--font-size-large;
|
||||
margin-left: 20px;
|
||||
color: $--app-version-color;
|
||||
line-height: 18px;
|
||||
}
|
||||
.app-icon {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
background: transparent url('~@/assets/app-icon.png') center center no-repeat;
|
||||
background-size: 128px 128px;
|
||||
width: 128px;
|
||||
height: 128px;
|
||||
}
|
||||
.engine-info {
|
||||
margin: 50px 35% 0 8px;
|
||||
h4 {
|
||||
font-size: $--font-size-base;
|
||||
font-weight: normal;
|
||||
color: $--app-engine-title-color;
|
||||
}
|
||||
.app-icon {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
background: transparent url('~@/assets/app-icon.png') center center no-repeat;
|
||||
background-size: 128px 128px;
|
||||
width: 128px;
|
||||
height: 128px;
|
||||
}
|
||||
.engine-info {
|
||||
margin: 50px 35% 0 8px;
|
||||
h4 {
|
||||
font-size: $--font-size-base;
|
||||
font-weight: normal;
|
||||
color: $--app-engine-title-color;
|
||||
}
|
||||
ul {
|
||||
font-size: 12px;
|
||||
color: $--app-engine-info-color;
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
line-height: 20px;
|
||||
@include clearfix();
|
||||
li {
|
||||
float: left;
|
||||
width: 50%;
|
||||
}
|
||||
ul {
|
||||
font-size: 12px;
|
||||
color: $--app-engine-info-color;
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
line-height: 20px;
|
||||
@include clearfix();
|
||||
li {
|
||||
float: left;
|
||||
width: 50%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<el-row class="copyright">
|
||||
<el-col :span="6" class="copyright-left">
|
||||
<a target="_blank" rel="noopener noreferrer" href="https://motrix.app/">
|
||||
©2019 Motrix
|
||||
©{{ year }} Motrix
|
||||
</a>
|
||||
</el-col>
|
||||
<el-col :span="18" class="copyright-right">
|
||||
@@ -24,27 +24,33 @@
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'mo-copyright'
|
||||
name: 'mo-copyright',
|
||||
data () {
|
||||
const year = new Date().getFullYear()
|
||||
return {
|
||||
year
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.copyright {
|
||||
width: 100%;
|
||||
font-size: $--font-size-small;
|
||||
a {
|
||||
color: $--app-copyright-color;
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
.copyright-left {
|
||||
text-align: left;
|
||||
.copyright {
|
||||
width: 100%;
|
||||
font-size: $--font-size-small;
|
||||
a {
|
||||
color: $--app-copyright-color;
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
.copyright-left {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.copyright-right {
|
||||
text-align: right;
|
||||
a {
|
||||
margin-left: 30px;
|
||||
}
|
||||
.copyright-right {
|
||||
text-align: right;
|
||||
a {
|
||||
margin-left: 30px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<el-aside width="78px" :class="['aside', 'hidden-sm-and-down', { 'draggable': asideDraggable }]">
|
||||
<el-aside width="78px" :class="['aside', 'hidden-sm-and-down', { 'draggable': asideDraggable }]" :style="vibrancy">
|
||||
<div class="aside-inner">
|
||||
<mo-logo-mini />
|
||||
<ul class="menu top-menu">
|
||||
@@ -43,6 +43,13 @@
|
||||
}),
|
||||
asideDraggable () {
|
||||
return is.macOS()
|
||||
},
|
||||
vibrancy () {
|
||||
return is.macOS()
|
||||
? {
|
||||
backgroundColor: 'transparent'
|
||||
}
|
||||
: {}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@@ -64,40 +71,40 @@
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.aside-inner {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
flex-flow: column;
|
||||
}
|
||||
.logo-mini {
|
||||
margin-top: 40px;
|
||||
}
|
||||
.menu {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0 auto;
|
||||
user-select: none;
|
||||
cursor: default;
|
||||
> li {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
margin-top: 24px;
|
||||
cursor: pointer;
|
||||
border-radius: 16px;
|
||||
transition: background-color 0.25s;
|
||||
&:hover {
|
||||
background-color: rgba(255, 255, 255, 0.15);
|
||||
}
|
||||
}
|
||||
svg {
|
||||
padding: 6px;
|
||||
color: #fff;
|
||||
.aside-inner {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
flex-flow: column;
|
||||
}
|
||||
.logo-mini {
|
||||
margin-top: 40px;
|
||||
}
|
||||
.menu {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0 auto;
|
||||
user-select: none;
|
||||
cursor: default;
|
||||
> li {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
margin-top: 24px;
|
||||
cursor: pointer;
|
||||
border-radius: 16px;
|
||||
transition: background-color 0.25s;
|
||||
&:hover {
|
||||
background-color: rgba(255, 255, 255, 0.15);
|
||||
}
|
||||
}
|
||||
.top-menu {
|
||||
flex: 1;
|
||||
}
|
||||
.bottom-menu {
|
||||
margin-bottom: 24px;
|
||||
svg {
|
||||
padding: 6px;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
.top-menu {
|
||||
flex: 1;
|
||||
}
|
||||
.bottom-menu {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,21 +1,18 @@
|
||||
<template>
|
||||
<div ref="webviewViewport" class="webview-viewport">
|
||||
<webview
|
||||
<iframe
|
||||
class="mo-webview"
|
||||
ref="webview"
|
||||
ref="iframe"
|
||||
:src="src"
|
||||
></webview>
|
||||
></iframe>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import is from 'electron-is'
|
||||
import { webContents } from '@electron/remote'
|
||||
import { Loading } from 'element-ui'
|
||||
|
||||
import {
|
||||
openExternal
|
||||
} from '@/utils/native'
|
||||
|
||||
export default {
|
||||
name: 'mo-browser',
|
||||
components: {
|
||||
@@ -35,11 +32,11 @@
|
||||
isRenderer: () => is.renderer()
|
||||
},
|
||||
mounted () {
|
||||
const { webview } = this.$refs
|
||||
const { iframe } = 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))
|
||||
iframe.addEventListener('did-start-loading', this.loadStart.bind(this))
|
||||
iframe.addEventListener('did-stop-loading', this.loadStop.bind(this))
|
||||
iframe.addEventListener('dom-ready', this.ready.bind(this))
|
||||
},
|
||||
methods: {
|
||||
loadStart () {
|
||||
@@ -54,12 +51,12 @@
|
||||
})
|
||||
},
|
||||
ready () {
|
||||
const { webview } = this.$refs
|
||||
const { iframe } = this.$refs
|
||||
|
||||
const webContents = this.$electron.remote.webContents.fromId(webview.getWebContentsId())
|
||||
webContents.on('new-window', (event, url) => {
|
||||
const wc = webContents.fromId(iframe.getWebContentsId())
|
||||
wc.setWindowOpenHandler((event, url) => {
|
||||
event.preventDefault()
|
||||
openExternal(url)
|
||||
this.$electron.ipcRenderer.send('command', 'application:open-external', url)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ export default class CommandManager extends EventEmitter {
|
||||
}
|
||||
|
||||
execute (id, ...args) {
|
||||
var fn = this.commands[id]
|
||||
const fn = this.commands[id]
|
||||
if (fn) {
|
||||
try {
|
||||
this.emit('beforeExecuteCommand', id)
|
||||
|
||||
@@ -8,6 +8,11 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const getCoords = (e, containerRect) => ({
|
||||
x: e.clientX - containerRect.left,
|
||||
y: e.clientY - containerRect.top
|
||||
})
|
||||
|
||||
const getDimensions = (p1, p2) => ({
|
||||
width: Math.abs(p1.x - p2.x),
|
||||
height: Math.abs(p1.y - p2.y)
|
||||
@@ -51,12 +56,6 @@
|
||||
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 }
|
||||
@@ -74,7 +73,7 @@
|
||||
function startDrag (e) {
|
||||
containerRect = container.getBoundingClientRect()
|
||||
self.children = container.childNodes
|
||||
start = getCoords(e)
|
||||
start = getCoords(e, containerRect)
|
||||
end = start
|
||||
document.addEventListener('mousemove', drag)
|
||||
document.addEventListener('touchmove', touchMove)
|
||||
@@ -87,7 +86,7 @@
|
||||
}
|
||||
|
||||
function drag (e) {
|
||||
end = getCoords(e)
|
||||
end = getCoords(e, containerRect)
|
||||
const dimensions = getDimensions(start, end)
|
||||
|
||||
if (end.x < start.x) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<svg version="1.1"
|
||||
<svg
|
||||
version="1.1"
|
||||
:class="klass"
|
||||
:role="label ? 'img' : 'presentation'"
|
||||
:aria-label="label"
|
||||
@@ -8,60 +9,25 @@
|
||||
:width="width"
|
||||
:height="height"
|
||||
:viewBox="box"
|
||||
:style="style">
|
||||
:style="style"
|
||||
>
|
||||
<slot>
|
||||
<template v-if="icon && icon.paths">
|
||||
<path v-for="(path, i) in icon.paths" :key="`path-${i}`" v-bind="path"/>
|
||||
<path v-for="(path, i) in icon.paths" :key="`path-${i}`" v-bind="path" />
|
||||
</template>
|
||||
<template v-if="icon && icon.polygons">
|
||||
<polygon v-for="(polygon, i) in icon.polygons" :key="`polygon-${i}`" v-bind="polygon"/>
|
||||
<polygon v-for="(polygon, i) in icon.polygons" :key="`polygon-${i}`" v-bind="polygon" />
|
||||
</template>
|
||||
<template v-if="icon && icon.raw"><g v-html="raw" v-bind="icon.g"></g></template>
|
||||
<template v-if="icon && icon.raw"><g v-bind="icon.g" v-html="raw" /></template>
|
||||
</slot>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.fa-icon {
|
||||
display: inline-block;
|
||||
fill: currentColor;
|
||||
}
|
||||
|
||||
.fa-flip-horizontal {
|
||||
transform: scale(-1, 1);
|
||||
}
|
||||
|
||||
.fa-flip-vertical {
|
||||
transform: scale(1, -1);
|
||||
}
|
||||
|
||||
.fa-spin {
|
||||
animation: fa-spin 0.5s 0s infinite linear;
|
||||
}
|
||||
|
||||
.fa-inverse {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.fa-pulse {
|
||||
animation: fa-spin 1s infinite steps(8);
|
||||
}
|
||||
|
||||
@keyframes fa-spin {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
const icons = {}
|
||||
|
||||
export default {
|
||||
name: 'fa-icon',
|
||||
name: 'mo-icon',
|
||||
props: {
|
||||
name: {
|
||||
type: String,
|
||||
@@ -106,12 +72,12 @@
|
||||
},
|
||||
klass () {
|
||||
return {
|
||||
'fa-icon': true,
|
||||
'fa-spin': this.spin,
|
||||
'fa-flip-horizontal': this.flip === 'horizontal',
|
||||
'fa-flip-vertical': this.flip === 'vertical',
|
||||
'fa-inverse': this.inverse,
|
||||
'fa-pulse': this.pulse,
|
||||
'mo-icon': true,
|
||||
'mo-spin': this.spin,
|
||||
'mo-flip-horizontal': this.flip === 'horizontal',
|
||||
'mo-flip-vertical': this.flip === 'vertical',
|
||||
'mo-inverse': this.inverse,
|
||||
'mo-pulse': this.pulse,
|
||||
[this.$options.name]: true
|
||||
}
|
||||
},
|
||||
@@ -184,7 +150,7 @@
|
||||
|
||||
let width = 0
|
||||
let height = 0
|
||||
this.$children.forEach(child => {
|
||||
this.$children.forEach((child) => {
|
||||
child.outerScale = this.normalizedScale
|
||||
|
||||
width = Math.max(width, child.width)
|
||||
@@ -192,7 +158,7 @@
|
||||
})
|
||||
this.childrenWidth = width
|
||||
this.childrenHeight = height
|
||||
this.$children.forEach(child => {
|
||||
this.$children.forEach((child) => {
|
||||
child.x = (width - child.width) / 2
|
||||
child.y = (height - child.height) / 2
|
||||
})
|
||||
@@ -221,8 +187,44 @@
|
||||
icons
|
||||
}
|
||||
|
||||
let cursor = 0xd4937
|
||||
let cursor = 0xD4937
|
||||
function getId () {
|
||||
return `fa-${(cursor++).toString(16)}`
|
||||
return `mo-${(cursor++).toString(16)}`
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.mo-icon {
|
||||
display: inline-block;
|
||||
fill: currentColor;
|
||||
}
|
||||
|
||||
.mo-flip-horizontal {
|
||||
transform: scale(-1, 1);
|
||||
}
|
||||
|
||||
.mo-flip-vertical {
|
||||
transform: scale(1, -1);
|
||||
}
|
||||
|
||||
.mo-spin {
|
||||
animation: mo-spin 0.5s 0s infinite linear;
|
||||
}
|
||||
|
||||
.mo-inverse {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.mo-pulse {
|
||||
animation: mo-spin 1s infinite steps(8);
|
||||
}
|
||||
|
||||
@keyframes mo-spin {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import Icon from '@/components/Icons/Icon'
|
||||
|
||||
Icon.register({
|
||||
'magnet': {
|
||||
'width': 24,
|
||||
'height': 24,
|
||||
'raw': `
|
||||
<line fill="none" stroke-miterlimit="10" x1="8.2" y1="4.5" x2="11.1" y2="7.3"></line>
|
||||
<line fill="none" stroke-miterlimit="10" x1="16.7" y1="12.9" x2="19.5" y2="15.8"></line>
|
||||
<path fill="none" stroke-miterlimit="10"
|
||||
d="M12.5,17.2 c-1.6,1.6-4.1,1.6-5.7,0c-1.6-1.6-1.6-4.1,0-5.7l7.1-7.1l-2.8-2.8L4,8.7C0.9,11.8,0.9,16.9,4,20s8.2,3.1,11.3,0l7.1-7.1l-2.8-2.8 L12.5,17.2z">
|
||||
</path>`,
|
||||
'g': {
|
||||
'stroke': 'currentColor',
|
||||
'stroke-linecap': 'round',
|
||||
'stroke-linejoin': 'round',
|
||||
'stroke-width': '2'
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -38,6 +38,7 @@
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
outline: none;
|
||||
border: none;
|
||||
text-align: center;
|
||||
font-size: 0;
|
||||
color: $--app-logo-color;
|
||||
|
||||
@@ -10,11 +10,7 @@
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'mo-logo-mini',
|
||||
components: {
|
||||
},
|
||||
methods: {
|
||||
}
|
||||
name: 'mo-logo-mini'
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -29,6 +25,7 @@
|
||||
height: 28px;
|
||||
text-align: center;
|
||||
font-size: 0;
|
||||
outline: none;
|
||||
padding: 2px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
@@ -5,7 +5,13 @@
|
||||
<mo-speedometer />
|
||||
<mo-add-task :visible="addTaskVisible" :type="addTaskType" />
|
||||
<mo-about-panel :visible="aboutPanelVisible" />
|
||||
<mo-task-item-info :visible="taskItemInfoVisible" :task="currentTaskItem" />
|
||||
<mo-task-detail
|
||||
:visible="taskDetailVisible"
|
||||
:gid="currentTaskGid"
|
||||
:task="currentTaskItem"
|
||||
:files="currentTaskFiles"
|
||||
:peers="currentTaskPeers"
|
||||
/>
|
||||
<mo-dragger />
|
||||
</el-container>
|
||||
</template>
|
||||
@@ -16,7 +22,7 @@
|
||||
import Aside from '@/components/Aside/Index'
|
||||
import Speedometer from '@/components/Speedometer/Speedometer'
|
||||
import AddTask from '@/components/Task/AddTask'
|
||||
import TaskItemInfo from '@/components/Task/TaskItemInfo'
|
||||
import TaskDetail from '@/components/TaskDetail/Index'
|
||||
import Dragger from '@/components/Dragger/Index'
|
||||
|
||||
export default {
|
||||
@@ -26,7 +32,7 @@
|
||||
[Aside.name]: Aside,
|
||||
[Speedometer.name]: Speedometer,
|
||||
[AddTask.name]: AddTask,
|
||||
[TaskItemInfo.name]: TaskItemInfo,
|
||||
[TaskDetail.name]: TaskDetail,
|
||||
[Dragger.name]: Dragger
|
||||
},
|
||||
computed: {
|
||||
@@ -36,8 +42,11 @@
|
||||
addTaskType: state => state.addTaskType
|
||||
}),
|
||||
...mapState('task', {
|
||||
taskItemInfoVisible: state => state.taskItemInfoVisible,
|
||||
currentTaskItem: state => state.currentTaskItem
|
||||
taskDetailVisible: state => state.taskDetailVisible,
|
||||
currentTaskGid: state => state.currentTaskGid,
|
||||
currentTaskItem: state => state.currentTaskItem,
|
||||
currentTaskFiles: state => state.currentTaskFiles,
|
||||
currentTaskPeers: state => state.currentTaskPeers
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
@@ -48,7 +57,7 @@
|
||||
<style lang="scss">
|
||||
.mo-speedometer {
|
||||
position: fixed;
|
||||
right: 36px;
|
||||
right: 16px;
|
||||
bottom: 24px;
|
||||
z-index: 20;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
<template>
|
||||
<div style="display: none;">
|
||||
<img
|
||||
id="tray-icon-light-normal"
|
||||
src="static/mo-tray-light-normal@2x.png"
|
||||
>
|
||||
<img
|
||||
id="tray-icon-light-active"
|
||||
src="static/mo-tray-light-active@2x.png"
|
||||
>
|
||||
<img
|
||||
id="tray-icon-dark-normal"
|
||||
src="static/mo-tray-dark-normal@2x.png"
|
||||
>
|
||||
<img
|
||||
id="tray-icon-dark-active"
|
||||
src="static/mo-tray-dark-active@2x.png"
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
|
||||
import { getInverseTheme } from '@shared/utils'
|
||||
import { APP_THEME } from '@shared/constants'
|
||||
|
||||
const cache = {}
|
||||
|
||||
export default {
|
||||
name: 'mo-dynamic-tray',
|
||||
computed: {
|
||||
...mapState('app', {
|
||||
iconStatus: state => state.stat.numActive > 0 ? 'active' : 'normal',
|
||||
theme: state => state.systemTheme,
|
||||
focused: state => state.trayFocused,
|
||||
uploadSpeed: state => state.stat.uploadSpeed,
|
||||
downloadSpeed: state => state.stat.downloadSpeed,
|
||||
speed: state => state.stat.uploadSpeed + state.stat.downloadSpeed
|
||||
}),
|
||||
scale () {
|
||||
return 2
|
||||
},
|
||||
currentTheme () {
|
||||
const { theme, focused } = this
|
||||
if (theme === APP_THEME.DARK) {
|
||||
return theme
|
||||
}
|
||||
|
||||
return focused ? getInverseTheme(theme) : theme
|
||||
},
|
||||
iconKey () {
|
||||
const { bigSur, iconStatus, currentTheme } = this
|
||||
return bigSur ? 'tray-icon-light-normal' : `tray-icon-${currentTheme}-${iconStatus}`
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
async speed (val) {
|
||||
await this.drawTray()
|
||||
},
|
||||
async iconKey (val) {
|
||||
await this.drawTray()
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
setTimeout(async () => {
|
||||
await this.drawTray()
|
||||
}, 200)
|
||||
},
|
||||
methods: {
|
||||
async getIcon (key) {
|
||||
if (cache[key]) {
|
||||
return cache[key]
|
||||
}
|
||||
|
||||
const iconImage = document.getElementById(key)
|
||||
const result = await createImageBitmap(iconImage)
|
||||
cache[key] = result
|
||||
|
||||
return result
|
||||
},
|
||||
async drawTray () {
|
||||
const {
|
||||
currentTheme: theme,
|
||||
uploadSpeed,
|
||||
downloadSpeed,
|
||||
scale,
|
||||
iconKey
|
||||
} = this
|
||||
|
||||
const icon = await this.getIcon(iconKey)
|
||||
|
||||
global.app.trayWorker.postMessage({
|
||||
type: 'tray:draw',
|
||||
payload: {
|
||||
theme,
|
||||
icon,
|
||||
uploadSpeed,
|
||||
downloadSpeed,
|
||||
scale
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -7,53 +7,57 @@
|
||||
import { mapState } from 'vuex'
|
||||
import api from '@/api'
|
||||
import {
|
||||
showItemInFolder,
|
||||
addToRecentTask
|
||||
getTaskFullPath,
|
||||
showItemInFolder
|
||||
} from '@/utils/native'
|
||||
import {
|
||||
bytesToSize,
|
||||
getTaskName,
|
||||
getTaskFullPath
|
||||
} from '@shared/utils'
|
||||
import { checkTaskIsBT, getTaskName } from '@shared/utils'
|
||||
|
||||
export default {
|
||||
name: 'mo-engine-client',
|
||||
data () {
|
||||
return {
|
||||
downloading: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isRenderer: () => is.renderer(),
|
||||
...mapState('app', {
|
||||
uploadSpeed: state => state.stat.uploadSpeed,
|
||||
downloadSpeed: state => state.stat.downloadSpeed,
|
||||
speed: state => state.stat.uploadSpeed + state.stat.downloadSpeed,
|
||||
interval: state => state.interval,
|
||||
numActive: state => state.stat.numActive
|
||||
downloading: state => state.stat.numActive > 0,
|
||||
progress: state => state.progress
|
||||
}),
|
||||
...mapState('task', {
|
||||
taskItemInfoVisible: state => state.taskItemInfoVisible,
|
||||
messages: state => state.messages,
|
||||
seedingList: state => state.seedingList,
|
||||
taskDetailVisible: state => state.taskDetailVisible,
|
||||
enabledFetchPeers: state => state.enabledFetchPeers,
|
||||
currentTaskGid: state => state.currentTaskGid,
|
||||
currentTaskItem: state => state.currentTaskItem
|
||||
}),
|
||||
...mapState('preference', {
|
||||
taskNotification: state => state.config.taskNotification
|
||||
})
|
||||
}),
|
||||
currentTaskIsBT () {
|
||||
return checkTaskIsBT(this.currentTaskItem)
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
downloadSpeed (val) {
|
||||
const speed = val > 0 ? `${bytesToSize(val)}/s` : ''
|
||||
this.$electron.ipcRenderer.send('event', 'download-speed-change', speed)
|
||||
},
|
||||
numActive (val) {
|
||||
this.downloading = val > 0
|
||||
speed (val) {
|
||||
const { uploadSpeed, downloadSpeed } = this
|
||||
this.$electron.ipcRenderer.send('event', 'speed-change', {
|
||||
uploadSpeed,
|
||||
downloadSpeed
|
||||
})
|
||||
},
|
||||
downloading (val, oldVal) {
|
||||
if (val !== oldVal && this.isRenderer) {
|
||||
this.$electron.ipcRenderer.send('event', 'download-status-change', val)
|
||||
}
|
||||
},
|
||||
progress (val) {
|
||||
this.$electron.ipcRenderer.send('event', 'progress-change', val)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
fetchTaskItem ({ gid }) {
|
||||
async fetchTaskItem ({ gid }) {
|
||||
return api.fetchTaskItem({ gid })
|
||||
.catch((e) => {
|
||||
console.warn(`fetchTaskItem fail: ${e.message}`)
|
||||
@@ -63,18 +67,28 @@
|
||||
this.$store.dispatch('task/fetchList')
|
||||
this.$store.dispatch('app/resetInterval')
|
||||
this.$store.dispatch('task/saveSession')
|
||||
console.log('aria2 onDownloadStart', event)
|
||||
const [{ gid }] = event
|
||||
const { seedingList } = this
|
||||
if (seedingList.includes(gid)) {
|
||||
return
|
||||
}
|
||||
|
||||
this.fetchTaskItem({ gid })
|
||||
.then((task) => {
|
||||
const { dir } = task
|
||||
this.$store.dispatch('preference/recordHistoryDirectory', dir)
|
||||
const taskName = getTaskName(task)
|
||||
const message = this.$t('task.download-start-message', { taskName })
|
||||
this.$msg.info(message)
|
||||
})
|
||||
},
|
||||
onDownloadPause (event) {
|
||||
console.log('aria2 onDownloadPause')
|
||||
const [{ gid }] = event
|
||||
const { seedingList } = this
|
||||
if (seedingList.includes(gid)) {
|
||||
return
|
||||
}
|
||||
|
||||
this.fetchTaskItem({ gid })
|
||||
.then((task) => {
|
||||
const taskName = getTaskName(task)
|
||||
@@ -83,7 +97,6 @@
|
||||
})
|
||||
},
|
||||
onDownloadStop (event) {
|
||||
console.log('aria2 onDownloadStop')
|
||||
const [{ gid }] = event
|
||||
this.fetchTaskItem({ gid })
|
||||
.then((task) => {
|
||||
@@ -100,7 +113,7 @@
|
||||
const { errorCode, errorMessage } = task
|
||||
console.error(`[Motrix] download error gid: ${gid}, #${errorCode}, ${errorMessage}`)
|
||||
const message = this.$t('task.download-error-message', { taskName })
|
||||
const link = `<a target="_blank" href="https://github.com/agalwood/Motrix/wiki/Error#${errorCode}" rel="noopener noreferrer">#${errorCode}</a>`
|
||||
const link = `<a target="_blank" href="https://github.com/agalwood/Motrix/wiki/Error#${errorCode}" rel="noopener noreferrer">${errorCode}</a>`
|
||||
this.$msg({
|
||||
type: 'error',
|
||||
showClose: true,
|
||||
@@ -111,18 +124,25 @@
|
||||
})
|
||||
},
|
||||
onDownloadComplete (event) {
|
||||
console.log('aria2 onDownloadComplete')
|
||||
this.$store.dispatch('task/fetchList')
|
||||
const [{ gid }] = event
|
||||
this.$store.dispatch('task/removeFromSeedingList', gid)
|
||||
|
||||
this.fetchTaskItem({ gid })
|
||||
.then((task) => {
|
||||
this.handleDownloadComplete(task, false)
|
||||
})
|
||||
},
|
||||
onBtDownloadComplete (event) {
|
||||
console.log('aria2 onBtDownloadComplete')
|
||||
this.$store.dispatch('task/fetchList')
|
||||
const [{ gid }] = event
|
||||
const { seedingList } = this
|
||||
if (seedingList.includes(gid)) {
|
||||
return
|
||||
}
|
||||
|
||||
this.$store.dispatch('task/addToSeedingList', gid)
|
||||
|
||||
this.fetchTaskItem({ gid })
|
||||
.then((task) => {
|
||||
this.handleDownloadComplete(task, true)
|
||||
@@ -131,8 +151,6 @@
|
||||
handleDownloadComplete (task, isBT) {
|
||||
this.$store.dispatch('task/saveSession')
|
||||
|
||||
addToRecentTask(task)
|
||||
|
||||
const path = getTaskFullPath(task)
|
||||
this.showTaskCompleteNotify(task, isBT, path)
|
||||
this.$electron.ipcRenderer.send('event', 'task-download-complete', task, path)
|
||||
@@ -205,10 +223,15 @@
|
||||
},
|
||||
polling () {
|
||||
this.$store.dispatch('app/fetchGlobalStat')
|
||||
this.$store.dispatch('app/fetchProgress')
|
||||
this.$store.dispatch('task/fetchList')
|
||||
|
||||
if (this.taskItemInfoVisible && this.currentTaskItem) {
|
||||
this.$store.dispatch('task/fetchItem', this.currentTaskItem.gid)
|
||||
if (this.taskDetailVisible && this.currentTaskGid) {
|
||||
if (this.currentTaskIsBT && this.enabledFetchPeers) {
|
||||
this.$store.dispatch('task/fetchItemWithPeers', this.currentTaskGid)
|
||||
} else {
|
||||
this.$store.dispatch('task/fetchItem', this.currentTaskGid)
|
||||
}
|
||||
}
|
||||
},
|
||||
stopPolling () {
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { dialog } from '@electron/remote'
|
||||
import '@/components/Icons/folder'
|
||||
|
||||
export default {
|
||||
@@ -17,7 +18,7 @@
|
||||
methods: {
|
||||
onFolderClick () {
|
||||
const self = this
|
||||
this.$electron.remote.dialog.showOpenDialog({
|
||||
dialog.showOpenDialog({
|
||||
properties: ['openDirectory', 'createDirectory']
|
||||
}).then(({ canceled, filePaths }) => {
|
||||
if (canceled || filePaths.length === 0) {
|
||||
@@ -31,6 +32,3 @@
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
</style>
|
||||
|
||||
@@ -31,6 +31,3 @@
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
</style>
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getCurrentWindow } from '@electron/remote'
|
||||
import '@/components/Icons/win-minimize'
|
||||
import '@/components/Icons/win-maximize'
|
||||
import '@/components/Icons/win-close'
|
||||
@@ -29,7 +30,7 @@
|
||||
},
|
||||
computed: {
|
||||
win () {
|
||||
return this.$electron.remote.getCurrentWindow()
|
||||
return getCurrentWindow()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@@ -61,6 +62,7 @@
|
||||
height: 36px;
|
||||
z-index: 5000;
|
||||
.title-bar-dragger {
|
||||
margin: 5px 0 0 5px;
|
||||
flex: 1;
|
||||
user-select: none;
|
||||
-webkit-app-region: drag;
|
||||
|
||||
@@ -60,14 +60,26 @@
|
||||
v-if="form.useProxy"
|
||||
style="margin-top: -16px;"
|
||||
>
|
||||
<el-col class="form-item-sub" :span="16">
|
||||
<el-col
|
||||
class="form-item-sub"
|
||||
:xs="24"
|
||||
:sm="20"
|
||||
:md="16"
|
||||
:lg="16"
|
||||
>
|
||||
<el-input
|
||||
placeholder="[http://][USER:PASSWORD@]HOST[:PORT]"
|
||||
@change="onAllProxyBackupChange"
|
||||
v-model="form.allProxyBackup">
|
||||
</el-input>
|
||||
</el-col>
|
||||
<el-col class="form-item-sub" :span="20">
|
||||
<el-col
|
||||
class="form-item-sub"
|
||||
:xs="24"
|
||||
:sm="24"
|
||||
:md="20"
|
||||
:lg="20"
|
||||
>
|
||||
<el-input
|
||||
type="textarea"
|
||||
rows="2"
|
||||
@@ -107,7 +119,18 @@
|
||||
v-for="item in group.options"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value">
|
||||
:value="item.value"
|
||||
>
|
||||
<span style="float: left">{{ item.label }}</span>
|
||||
<span style="float: right; margin-right: 24px">
|
||||
<el-tag
|
||||
type="success"
|
||||
size="mini"
|
||||
v-if="item.cdn"
|
||||
>
|
||||
CDN
|
||||
</el-tag>
|
||||
</span>
|
||||
</el-option>
|
||||
</el-option-group>
|
||||
</el-select>
|
||||
@@ -166,12 +189,71 @@
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
:label="`${$t('preferences.rpc')}: `"
|
||||
:label-width="formLabelWidth"
|
||||
>
|
||||
<el-row style="margin-bottom: 8px;">
|
||||
<el-col
|
||||
class="form-item-sub"
|
||||
:xs="24"
|
||||
:sm="18"
|
||||
:md="10"
|
||||
:lg="10"
|
||||
>
|
||||
{{ $t('preferences.rpc-listen-port') }}
|
||||
<el-input
|
||||
:placeholder="rpcDefaultPort"
|
||||
:maxlength="8"
|
||||
v-model="form.rpcListenPort"
|
||||
@change="onRpcListenPortChange"
|
||||
>
|
||||
<i slot="append" @click.prevent="onRpcPortDiceClick">
|
||||
<mo-icon name="dice" width="12" height="12" />
|
||||
</i>
|
||||
</el-input>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row style="margin-bottom: 8px;">
|
||||
<el-col
|
||||
class="form-item-sub"
|
||||
:xs="24"
|
||||
:sm="18"
|
||||
:md="18"
|
||||
:lg="18"
|
||||
>
|
||||
{{ $t('preferences.rpc-secret') }}
|
||||
<el-input
|
||||
:show-password="hideRpcSecret"
|
||||
placeholder="RPC Secret"
|
||||
:maxlength="24"
|
||||
v-model="form.rpcSecret"
|
||||
>
|
||||
<i slot="append" @click.prevent="onRpcSecretDiceClick">
|
||||
<mo-icon name="dice" width="12" height="12" />
|
||||
</i>
|
||||
</el-input>
|
||||
<div class="el-form-item__info" style="margin-top: 8px;">
|
||||
<a target="_blank" href="https://github.com/agalwood/Motrix/wiki/RPC" rel="noopener noreferrer">
|
||||
{{ $t('preferences.rpc-secret-tips') }}
|
||||
<mo-icon name="link" width="12" height="12" />
|
||||
</a>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
:label="`${$t('preferences.port')}: `"
|
||||
:label-width="formLabelWidth"
|
||||
>
|
||||
<el-row style="margin-bottom: 8px;">
|
||||
<el-col class="form-item-sub" :span="12">
|
||||
<el-col
|
||||
class="form-item-sub"
|
||||
:xs="24"
|
||||
:sm="18"
|
||||
:md="12"
|
||||
:lg="12"
|
||||
>
|
||||
<el-switch
|
||||
v-model="form.enableUpnp"
|
||||
active-text="UPnP/NAT-PMP"
|
||||
@@ -180,21 +262,32 @@
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row style="margin-bottom: 8px;">
|
||||
<el-col class="form-item-sub" :span="10">
|
||||
<el-col class="form-item-sub"
|
||||
:xs="24"
|
||||
:sm="18"
|
||||
:md="10"
|
||||
:lg="10"
|
||||
>
|
||||
{{ $t('preferences.bt-port') }}
|
||||
<el-input
|
||||
placeholder="BT Port"
|
||||
:maxlength="8"
|
||||
v-model="form.listenPort"
|
||||
>
|
||||
<i slot="append" @click.prevent="onPortDiceClick">
|
||||
<i slot="append" @click.prevent="onBtPortDiceClick">
|
||||
<mo-icon name="dice" width="12" height="12" />
|
||||
</i>
|
||||
</el-input>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col class="form-item-sub" :span="10">
|
||||
<el-col
|
||||
class="form-item-sub"
|
||||
:xs="24"
|
||||
:sm="18"
|
||||
:md="10"
|
||||
:lg="10"
|
||||
>
|
||||
{{ $t('preferences.dht-port') }}
|
||||
<el-input
|
||||
placeholder="DHT Port"
|
||||
@@ -231,7 +324,7 @@
|
||||
</el-col>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
:label="`${$t('preferences.security')}: `"
|
||||
:label="`${$t('preferences.user-agent')}: `"
|
||||
:label-width="formLabelWidth"
|
||||
>
|
||||
<el-col class="form-item-sub" :span="24">
|
||||
@@ -244,42 +337,24 @@
|
||||
v-model="form.userAgent">
|
||||
</el-input>
|
||||
<el-button-group class="ua-group">
|
||||
<el-button @click="() => changeUA('aria2')">Aria2</el-button>
|
||||
<el-button @click="() => changeUA('transmission')">Transmission</el-button>
|
||||
<el-button @click="() => changeUA('chrome')">Chrome</el-button>
|
||||
<el-button @click="() => changeUA('du')">du</el-button>
|
||||
</el-button-group>
|
||||
</el-col>
|
||||
<el-col class="form-item-sub" :span="18">
|
||||
{{ $t('preferences.rpc-secret') }}
|
||||
<el-input
|
||||
:show-password="hideRpcSecret"
|
||||
placeholder="RPC Secret"
|
||||
:maxlength="24"
|
||||
v-model="form.rpcSecret"
|
||||
>
|
||||
<i slot="append" @click.prevent="onDiceClick">
|
||||
<mo-icon name="dice" width="12" height="12" />
|
||||
</i>
|
||||
</el-input>
|
||||
<div class="el-form-item__info" style="margin-top: 8px;">
|
||||
<a target="_blank" href="https://github.com/agalwood/Motrix/wiki/RPC" rel="noopener noreferrer">
|
||||
{{ $t('preferences.rpc-secret-tips') }}
|
||||
<mo-icon name="link" width="12" height="12" />
|
||||
</a>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
:label="`${$t('preferences.developer')}: `"
|
||||
:label-width="formLabelWidth"
|
||||
>
|
||||
<el-col class="form-item-sub" :span="24">
|
||||
{{ $t('preferences.app-log-path') }}
|
||||
<el-input placeholder="" disabled v-model="logPath">
|
||||
{{ $t('preferences.aria2-conf-path') }}
|
||||
<el-input placeholder="" disabled v-model="aria2ConfPath">
|
||||
<mo-show-in-folder
|
||||
slot="append"
|
||||
v-if="isRenderer"
|
||||
:path="logPath"
|
||||
:path="aria2ConfPath"
|
||||
/>
|
||||
</el-input>
|
||||
</el-col>
|
||||
@@ -294,6 +369,33 @@
|
||||
</el-input>
|
||||
</el-col>
|
||||
<el-col class="form-item-sub" :span="24">
|
||||
{{ $t('preferences.app-log-path') }}
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="18">
|
||||
<el-input placeholder="" disabled v-model="logPath">
|
||||
<mo-show-in-folder
|
||||
slot="append"
|
||||
v-if="isRenderer"
|
||||
:path="logPath"
|
||||
/>
|
||||
</el-input>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-select v-model="form.logLevel">
|
||||
<el-option
|
||||
v-for="item in logLevels"
|
||||
:key="item"
|
||||
:label="item"
|
||||
:value="item">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-col>
|
||||
<el-col class="form-item-sub" :span="24">
|
||||
<el-button plain type="warning" @click="() => onSessionResetClick()">
|
||||
{{ $t('preferences.session-reset') }}
|
||||
</el-button>
|
||||
<el-button plain type="danger" @click="() => onFactoryResetClick()">
|
||||
{{ $t('preferences.factory-reset') }}
|
||||
</el-button>
|
||||
@@ -319,29 +421,38 @@
|
||||
|
||||
<script>
|
||||
import is from 'electron-is'
|
||||
import { dialog } from '@electron/remote'
|
||||
import { mapState } from 'vuex'
|
||||
import { cloneDeep } from 'lodash'
|
||||
import { cloneDeep, extend, isEmpty } from 'lodash'
|
||||
import randomize from 'randomatic'
|
||||
import * as clipboard from 'clipboard-polyfill'
|
||||
import ShowInFolder from '@/components/Native/ShowInFolder'
|
||||
import SubnavSwitcher from '@/components/Subnav/SubnavSwitcher'
|
||||
import userAgentMap from '@shared/ua'
|
||||
import { trackerSourceOptions } from '@shared/constants'
|
||||
import {
|
||||
EMPTY_STRING,
|
||||
ENGINE_RPC_PORT,
|
||||
LOG_LEVELS,
|
||||
trackerSourceOptions
|
||||
} from '@shared/constants'
|
||||
import {
|
||||
backupConfig,
|
||||
buildRpcUrl,
|
||||
calcFormLabelWidth,
|
||||
changedConfig,
|
||||
checkIsNeedRestart,
|
||||
convertCommaToLine,
|
||||
convertLineToComma,
|
||||
diffConfig,
|
||||
getRandomInt
|
||||
generateRandomInt
|
||||
} from '@shared/utils'
|
||||
import { convertTrackerDataToLine } from '@shared/utils/tracker'
|
||||
import { convertTrackerDataToLine, reduceTrackerString } from '@shared/utils/tracker'
|
||||
import '@/components/Icons/dice'
|
||||
import '@/components/Icons/sync'
|
||||
import '@/components/Icons/refresh'
|
||||
import { getLanguage } from '@shared/locales'
|
||||
import { getLocaleManager } from '@/components/Locale'
|
||||
|
||||
const initialForm = (config) => {
|
||||
const initForm = (config) => {
|
||||
const {
|
||||
allProxy,
|
||||
allProxyBackup,
|
||||
@@ -354,6 +465,7 @@
|
||||
lastCheckUpdateTime,
|
||||
lastSyncTrackerTime,
|
||||
listenPort,
|
||||
logLevel,
|
||||
noProxy,
|
||||
protocols,
|
||||
rpcListenPort,
|
||||
@@ -374,10 +486,9 @@
|
||||
lastCheckUpdateTime,
|
||||
lastSyncTrackerTime,
|
||||
listenPort,
|
||||
logLevel,
|
||||
noProxy: convertCommaToLine(noProxy),
|
||||
protocols: {
|
||||
...protocols
|
||||
},
|
||||
protocols: { ...protocols },
|
||||
rpcListenPort,
|
||||
rpcSecret,
|
||||
trackerSource,
|
||||
@@ -395,8 +506,9 @@
|
||||
},
|
||||
data () {
|
||||
const { locale } = this.$store.state.preference.config
|
||||
const form = initialForm(this.$store.state.preference.config)
|
||||
const formOriginal = cloneDeep(form)
|
||||
const formOriginal = initForm(this.$store.state.preference.config)
|
||||
let form = {}
|
||||
form = initForm(extend(form, formOriginal, changedConfig.advanced))
|
||||
|
||||
return {
|
||||
form,
|
||||
@@ -432,22 +544,42 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
rpcDefaultPort () {
|
||||
return ENGINE_RPC_PORT
|
||||
},
|
||||
logLevels () {
|
||||
return LOG_LEVELS
|
||||
},
|
||||
...mapState('preference', {
|
||||
config: state => state.config,
|
||||
aria2ConfPath: state => state.config.aria2ConfPath,
|
||||
logPath: state => state.config.logPath,
|
||||
sessionPath: state => state.config.sessionPath
|
||||
})
|
||||
},
|
||||
watch: {
|
||||
'form.rpcListenPort' (val) {
|
||||
const url = buildRpcUrl({
|
||||
port: this.form.rpcListenPort,
|
||||
secret: val
|
||||
})
|
||||
navigator.clipboard.writeText(url)
|
||||
},
|
||||
'form.rpcSecret' (val) {
|
||||
const url = buildRpcUrl({
|
||||
port: this.form.rpcListenPort,
|
||||
secret: val
|
||||
})
|
||||
clipboard.writeText(url)
|
||||
navigator.clipboard.writeText(url)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleLocaleChange (locale) {
|
||||
const lng = getLanguage(locale)
|
||||
getLocaleManager().changeLanguage(lng)
|
||||
this.$electron.ipcRenderer.send('command',
|
||||
'application:change-locale', lng)
|
||||
},
|
||||
onCheckUpdateClick () {
|
||||
this.$electron.ipcRenderer.send('command', 'application:check-for-updates')
|
||||
this.$msg.info(this.$t('app.checking-for-updates'))
|
||||
@@ -491,15 +623,25 @@
|
||||
}
|
||||
this.form.userAgent = ua
|
||||
},
|
||||
onPortDiceClick () {
|
||||
const port = getRandomInt(20000, 24999)
|
||||
onBtPortDiceClick () {
|
||||
const port = generateRandomInt(20000, 24999)
|
||||
this.form.listenPort = port
|
||||
},
|
||||
onDhtPortDiceClick () {
|
||||
const port = getRandomInt(25000, 29999)
|
||||
const port = generateRandomInt(25000, 29999)
|
||||
this.form.dhtListenPort = port
|
||||
},
|
||||
onDiceClick () {
|
||||
onRpcListenPortChange (value) {
|
||||
console.log('onRpcListenPortChange===>', value)
|
||||
if (EMPTY_STRING === value) {
|
||||
this.form.rpcListenPort = this.rpcDefaultPort
|
||||
}
|
||||
},
|
||||
onRpcPortDiceClick () {
|
||||
const port = generateRandomInt(ENGINE_RPC_PORT, 20000)
|
||||
this.form.rpcListenPort = port
|
||||
},
|
||||
onRpcSecretDiceClick () {
|
||||
this.hideRpcSecret = false
|
||||
const rpcSecret = randomize('Aa0', 12)
|
||||
this.form.rpcSecret = rpcSecret
|
||||
@@ -508,8 +650,25 @@
|
||||
this.hideRpcSecret = true
|
||||
}, 2000)
|
||||
},
|
||||
onSessionResetClick () {
|
||||
dialog.showMessageBox({
|
||||
type: 'warning',
|
||||
title: this.$t('preferences.session-reset'),
|
||||
message: this.$t('preferences.session-reset-confirm'),
|
||||
buttons: [this.$t('app.yes'), this.$t('app.no')],
|
||||
cancelId: 1
|
||||
}).then(({ response }) => {
|
||||
if (response === 0) {
|
||||
this.$store.dispatch('task/purgeTaskRecord')
|
||||
this.$store.dispatch('task/pauseAllTask')
|
||||
.then(() => {
|
||||
this.$electron.ipcRenderer.send('command', 'application:reset-session')
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
onFactoryResetClick () {
|
||||
this.$electron.remote.dialog.showMessageBox({
|
||||
dialog.showMessageBox({
|
||||
type: 'warning',
|
||||
title: this.$t('preferences.factory-reset'),
|
||||
message: this.$t('preferences.factory-reset-confirm'),
|
||||
@@ -524,34 +683,52 @@
|
||||
syncFormConfig () {
|
||||
this.$store.dispatch('preference/fetchPreference')
|
||||
.then((config) => {
|
||||
this.form = initialForm(config)
|
||||
this.form = initForm(config)
|
||||
this.formOriginal = cloneDeep(this.form)
|
||||
if (changedConfig.basic.theme !== undefined) {
|
||||
this.$electron.ipcRenderer.send('command',
|
||||
'application:change-theme', changedConfig.basic.theme)
|
||||
}
|
||||
})
|
||||
},
|
||||
submitForm (formName) {
|
||||
this.$refs[formName].validate((valid) => {
|
||||
if (!valid) {
|
||||
console.log('[Motrix] preference form valid:', valid)
|
||||
console.error('[Motrix] preference form valid:', valid)
|
||||
return false
|
||||
}
|
||||
|
||||
const changed = diffConfig(this.formOriginal, this.form)
|
||||
const data = {
|
||||
...changed,
|
||||
protocols: {
|
||||
...this.form.protocols
|
||||
}
|
||||
...diffConfig(this.formOriginal, this.form),
|
||||
...changedConfig.basic
|
||||
}
|
||||
|
||||
const {
|
||||
btAutoDownloadContent,
|
||||
autoHideWindow,
|
||||
btTracker,
|
||||
noProxy,
|
||||
rpcListenPort
|
||||
} = data
|
||||
|
||||
if ('btAutoDownloadContent' in data) {
|
||||
data.followTorrent = btAutoDownloadContent
|
||||
data.followMetalink = btAutoDownloadContent
|
||||
data.pauseMetadata = !btAutoDownloadContent
|
||||
}
|
||||
|
||||
const { btTracker, noProxy } = changed
|
||||
if (btTracker) {
|
||||
data.btTracker = convertLineToComma(btTracker)
|
||||
data.btTracker = reduceTrackerString(convertLineToComma(btTracker))
|
||||
}
|
||||
|
||||
if (noProxy) {
|
||||
data.noProxy = convertLineToComma(noProxy)
|
||||
}
|
||||
|
||||
if (rpcListenPort === EMPTY_STRING) {
|
||||
data.rpcListenPort = this.rpcDefaultPort
|
||||
}
|
||||
|
||||
console.log('[Motrix] preference changed data:', data)
|
||||
|
||||
this.$store.dispatch('preference/save', data)
|
||||
@@ -564,9 +741,14 @@
|
||||
this.$msg.success(this.$t('preferences.save-fail-message'))
|
||||
})
|
||||
|
||||
changedConfig.basic = {}
|
||||
changedConfig.advanced = {}
|
||||
|
||||
if (this.isRenderer) {
|
||||
this.$electron.ipcRenderer.send('command',
|
||||
'application:setup-protocols-client', data.protocols)
|
||||
if ('autoHideWindow' in data) {
|
||||
this.$electron.ipcRenderer.send('command',
|
||||
'application:auto-hide-window', autoHideWindow)
|
||||
}
|
||||
|
||||
if (checkIsNeedRestart(data)) {
|
||||
this.$electron.ipcRenderer.send('command', 'application:relaunch')
|
||||
@@ -577,6 +759,39 @@
|
||||
resetForm (formName) {
|
||||
this.syncFormConfig()
|
||||
}
|
||||
},
|
||||
beforeRouteLeave (to, from, next) {
|
||||
changedConfig.advanced = diffConfig(this.formOriginal, this.form)
|
||||
if (to.path === '/preference/basic') {
|
||||
next()
|
||||
} else {
|
||||
if (isEmpty(changedConfig.basic) && isEmpty(changedConfig.advanced)) {
|
||||
next()
|
||||
} else {
|
||||
dialog.showMessageBox({
|
||||
type: 'warning',
|
||||
title: this.$t('preferences.not-saved'),
|
||||
message: this.$t('preferences.not-saved-confirm'),
|
||||
buttons: [this.$t('app.yes'), this.$t('app.no')],
|
||||
cancelId: 1
|
||||
}).then(({ response }) => {
|
||||
if (response === 0) {
|
||||
if (changedConfig.basic.theme !== undefined) {
|
||||
this.$electron.ipcRenderer.send('command',
|
||||
'application:change-theme',
|
||||
backupConfig.theme)
|
||||
}
|
||||
if (changedConfig.basic.locale !== undefined) {
|
||||
this.handleLocaleChange(backupConfig.locale)
|
||||
}
|
||||
changedConfig.basic = {}
|
||||
changedConfig.advanced = {}
|
||||
backupConfig.theme = undefined
|
||||
next()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
<mo-theme-switcher
|
||||
v-model="form.theme"
|
||||
@change="handleThemeChange"
|
||||
ref="themeSwitcher"
|
||||
/>
|
||||
</el-col>
|
||||
<el-col v-if="showHideAppMenuOption" class="form-item-sub" :span="16">
|
||||
@@ -37,8 +38,19 @@
|
||||
{{ $t('preferences.auto-hide-window') }}
|
||||
</el-checkbox>
|
||||
</el-col>
|
||||
<el-col v-if="isMac" class="form-item-sub" :span="16">
|
||||
<el-checkbox v-model="form.traySpeedometer">
|
||||
{{ $t('preferences.tray-speedometer') }}
|
||||
</el-checkbox>
|
||||
</el-col>
|
||||
<el-col class="form-item-sub" :span="16">
|
||||
<el-checkbox v-model="form.showProgressBar">
|
||||
{{ $t('preferences.show-progress-bar') }}
|
||||
</el-checkbox>
|
||||
</el-col>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="isMac"
|
||||
:label="`${$t('preferences.run-mode')}: `"
|
||||
:label-width="formLabelWidth"
|
||||
>
|
||||
@@ -100,10 +112,14 @@
|
||||
:label-width="formLabelWidth"
|
||||
>
|
||||
<el-input placeholder="" v-model="form.dir" :readonly="isMas">
|
||||
<mo-history-directory
|
||||
slot="prepend"
|
||||
@selected="handleHistoryDirectorySelected"
|
||||
/>
|
||||
<mo-select-directory
|
||||
v-if="isRenderer"
|
||||
slot="append"
|
||||
@selected="onDirectorySelected"
|
||||
@selected="handleNativeDirectorySelected"
|
||||
/>
|
||||
</el-input>
|
||||
<div class="el-form-item__info" v-if="isMas" style="margin-top: 8px;">
|
||||
@@ -116,9 +132,22 @@
|
||||
>
|
||||
<el-col class="form-item-sub" :span="24">
|
||||
{{ $t('preferences.transfer-speed-upload') }}
|
||||
<el-select v-model="form.maxOverallUploadLimit">
|
||||
<el-input-number
|
||||
v-model="maxOverallUploadLimitParsed"
|
||||
controls-position="right"
|
||||
:min="0"
|
||||
:max="65535"
|
||||
:step="1"
|
||||
:label="$t('preferences.transfer-speed-download')"
|
||||
>
|
||||
</el-input-number>
|
||||
<el-select
|
||||
style="width: 100px;"
|
||||
v-model="uploadUnit"
|
||||
@change="handleUploadChange"
|
||||
:placeholder="$t('preferences.speed-units')">
|
||||
<el-option
|
||||
v-for="item in speedOptions"
|
||||
v-for="item in speedUnits"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value">
|
||||
@@ -127,9 +156,21 @@
|
||||
</el-col>
|
||||
<el-col class="form-item-sub" :span="24">
|
||||
{{ $t('preferences.transfer-speed-download') }}
|
||||
<el-select v-model="form.maxOverallDownloadLimit">
|
||||
<el-input-number
|
||||
v-model="maxOverallDownloadLimitParsed"
|
||||
controls-position="right"
|
||||
:min="0"
|
||||
:max="65535"
|
||||
:step="1"
|
||||
:label="$t('preferences.transfer-speed-download')">
|
||||
</el-input-number>
|
||||
<el-select
|
||||
style="width: 100px;"
|
||||
v-model="downloadUnit"
|
||||
@change="handleDownloadChange"
|
||||
:placeholder="$t('preferences.speed-units')">
|
||||
<el-option
|
||||
v-for="item in speedOptions"
|
||||
v-for="item in speedUnits"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value">
|
||||
@@ -137,6 +178,61 @@
|
||||
</el-select>
|
||||
</el-col>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
:label="`${$t('preferences.bt-settings')}: `"
|
||||
:label-width="formLabelWidth"
|
||||
>
|
||||
<el-col class="form-item-sub" :span="24">
|
||||
<el-checkbox v-model="form.btSaveMetadata">
|
||||
{{ $t('preferences.bt-save-metadata') }}
|
||||
</el-checkbox>
|
||||
</el-col>
|
||||
<el-col class="form-item-sub" :span="24">
|
||||
<el-checkbox
|
||||
v-model="form.btAutoDownloadContent"
|
||||
>
|
||||
{{ $t('preferences.bt-auto-download-content') }}
|
||||
</el-checkbox>
|
||||
</el-col>
|
||||
<el-col class="form-item-sub" :span="24">
|
||||
<el-checkbox
|
||||
v-model="form.btForceEncryption"
|
||||
>
|
||||
{{ $t('preferences.bt-force-encryption') }}
|
||||
</el-checkbox>
|
||||
</el-col>
|
||||
<el-col class="form-item-sub" :span="24">
|
||||
<el-switch
|
||||
v-model="form.keepSeeding"
|
||||
:active-text="$t('preferences.keep-seeding')"
|
||||
@change="onKeepSeedingChange"
|
||||
>
|
||||
</el-switch>
|
||||
</el-col>
|
||||
<el-col class="form-item-sub" :span="24" v-if="!form.keepSeeding">
|
||||
{{ $t('preferences.seed-ratio') }}
|
||||
<el-input-number
|
||||
v-model="form.seedRatio"
|
||||
controls-position="right"
|
||||
:min="1"
|
||||
:max="100"
|
||||
:step="0.1"
|
||||
:label="$t('preferences.seed-ratio')">
|
||||
</el-input-number>
|
||||
</el-col>
|
||||
<el-col class="form-item-sub" :span="24" v-if="!form.keepSeeding">
|
||||
{{ $t('preferences.seed-time') }}
|
||||
({{ $t('preferences.seed-time-unit') }})
|
||||
<el-input-number
|
||||
v-model="form.seedTime"
|
||||
controls-position="right"
|
||||
:min="60"
|
||||
:max="525600"
|
||||
:step="1"
|
||||
:label="$t('preferences.seed-time')">
|
||||
</el-input-number>
|
||||
</el-col>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
:label="`${$t('preferences.task-manage')}: `"
|
||||
:label-width="formLabelWidth"
|
||||
@@ -147,7 +243,7 @@
|
||||
v-model="form.maxConcurrentDownloads"
|
||||
controls-position="right"
|
||||
:min="1"
|
||||
:max="10"
|
||||
:max="maxConcurrentDownloads"
|
||||
:label="$t('preferences.max-concurrent-downloads')">
|
||||
</el-input-number>
|
||||
</el-col>
|
||||
@@ -202,27 +298,42 @@
|
||||
|
||||
<script>
|
||||
import is from 'electron-is'
|
||||
import { dialog } from '@electron/remote'
|
||||
import { mapState } from 'vuex'
|
||||
import { cloneDeep } from 'lodash'
|
||||
import { cloneDeep, extend, isEmpty } from 'lodash'
|
||||
import SubnavSwitcher from '@/components/Subnav/SubnavSwitcher'
|
||||
import HistoryDirectory from '@/components/Preference/HistoryDirectory'
|
||||
import SelectDirectory from '@/components/Native/SelectDirectory'
|
||||
import ThemeSwitcher from '@/components/Preference/ThemeSwitcher'
|
||||
import { availableLanguages, getLanguage } from '@shared/locales'
|
||||
import { getLocaleManager } from '@/components/Locale'
|
||||
import { prettifyDir } from '@/utils/native'
|
||||
import {
|
||||
backupConfig,
|
||||
calcFormLabelWidth,
|
||||
changedConfig,
|
||||
checkIsNeedRestart,
|
||||
diffConfig
|
||||
convertLineToComma,
|
||||
diffConfig,
|
||||
extractSpeedUnit
|
||||
} from '@shared/utils'
|
||||
import { APP_RUN_MODE } from '@shared/constants'
|
||||
import {
|
||||
APP_RUN_MODE,
|
||||
EMPTY_STRING,
|
||||
ENGINE_MAX_CONCURRENT_DOWNLOADS
|
||||
} from '@shared/constants'
|
||||
import { reduceTrackerString } from '@shared/utils/tracker'
|
||||
|
||||
const initialForm = (config) => {
|
||||
const initForm = (config) => {
|
||||
const {
|
||||
autoHideWindow,
|
||||
btForceEncryption,
|
||||
btSaveMetadata,
|
||||
dir,
|
||||
engineMaxConnectionPerServer,
|
||||
followMetalink,
|
||||
followTorrent,
|
||||
hideAppMenu,
|
||||
keepSeeding,
|
||||
keepWindowState,
|
||||
locale,
|
||||
maxConcurrentDownloads,
|
||||
@@ -232,17 +343,33 @@
|
||||
newTaskShowDownloading,
|
||||
noConfirmBeforeDeleteTask,
|
||||
openAtLogin,
|
||||
pauseMetadata,
|
||||
resumeAllWhenAppLaunched,
|
||||
runMode,
|
||||
seedRatio,
|
||||
seedTime,
|
||||
showProgressBar,
|
||||
taskNotification,
|
||||
theme
|
||||
theme,
|
||||
traySpeedometer
|
||||
} = config
|
||||
|
||||
const btAutoDownloadContent = followTorrent &&
|
||||
followMetalink &&
|
||||
!pauseMetadata
|
||||
|
||||
const result = {
|
||||
autoHideWindow,
|
||||
btAutoDownloadContent,
|
||||
btForceEncryption,
|
||||
btSaveMetadata,
|
||||
continue: config.continue,
|
||||
dir,
|
||||
engineMaxConnectionPerServer,
|
||||
followMetalink,
|
||||
followTorrent,
|
||||
hideAppMenu,
|
||||
keepSeeding,
|
||||
keepWindowState,
|
||||
locale,
|
||||
maxConcurrentDownloads,
|
||||
@@ -252,10 +379,15 @@
|
||||
newTaskShowDownloading,
|
||||
noConfirmBeforeDeleteTask,
|
||||
openAtLogin,
|
||||
pauseMetadata,
|
||||
resumeAllWhenAppLaunched,
|
||||
runMode,
|
||||
seedRatio,
|
||||
seedTime,
|
||||
showProgressBar,
|
||||
taskNotification,
|
||||
theme
|
||||
theme,
|
||||
traySpeedometer
|
||||
}
|
||||
return result
|
||||
}
|
||||
@@ -264,13 +396,22 @@
|
||||
name: 'mo-preference-basic',
|
||||
components: {
|
||||
[SubnavSwitcher.name]: SubnavSwitcher,
|
||||
[HistoryDirectory.name]: HistoryDirectory,
|
||||
[SelectDirectory.name]: SelectDirectory,
|
||||
[ThemeSwitcher.name]: ThemeSwitcher
|
||||
},
|
||||
data () {
|
||||
const { locale } = this.$store.state.preference.config
|
||||
const form = initialForm(this.$store.state.preference.config)
|
||||
const formOriginal = cloneDeep(form)
|
||||
const formOriginal = initForm(this.$store.state.preference.config)
|
||||
let form = {}
|
||||
form = initForm(extend(form, formOriginal, changedConfig.basic))
|
||||
|
||||
if (backupConfig.theme === undefined) {
|
||||
backupConfig.theme = formOriginal.theme
|
||||
} else {
|
||||
formOriginal.theme = backupConfig.theme
|
||||
}
|
||||
backupConfig.locale = formOriginal.locale
|
||||
|
||||
return {
|
||||
form,
|
||||
@@ -282,52 +423,84 @@
|
||||
},
|
||||
computed: {
|
||||
isRenderer: () => is.renderer(),
|
||||
isMac: () => is.macOS(),
|
||||
isMas: () => is.mas(),
|
||||
isLinux () { return is.linux() },
|
||||
title () {
|
||||
return this.$t('preferences.basic')
|
||||
},
|
||||
maxConcurrentDownloads () {
|
||||
return ENGINE_MAX_CONCURRENT_DOWNLOADS
|
||||
},
|
||||
maxOverallDownloadLimitParsed: {
|
||||
get () {
|
||||
return parseInt(this.form.maxOverallDownloadLimit)
|
||||
},
|
||||
set (value) {
|
||||
const limit = value > 0 ? `${value}${this.downloadUnit}` : 0
|
||||
this.form.maxOverallDownloadLimit = limit
|
||||
}
|
||||
},
|
||||
maxOverallUploadLimitParsed: {
|
||||
get () {
|
||||
return parseInt(this.form.maxOverallUploadLimit)
|
||||
},
|
||||
set (value) {
|
||||
const limit = value > 0 ? `${value}${this.uploadUnit}` : 0
|
||||
this.form.maxOverallUploadLimit = limit
|
||||
}
|
||||
},
|
||||
downloadUnit: {
|
||||
get () {
|
||||
const { maxOverallDownloadLimit } = this.form
|
||||
return extractSpeedUnit(maxOverallDownloadLimit)
|
||||
},
|
||||
set (value) {
|
||||
return value
|
||||
}
|
||||
},
|
||||
uploadUnit: {
|
||||
get () {
|
||||
const { maxOverallUploadLimit } = this.form
|
||||
return extractSpeedUnit(maxOverallUploadLimit)
|
||||
},
|
||||
set (value) {
|
||||
return value
|
||||
}
|
||||
},
|
||||
runModes () {
|
||||
return [
|
||||
let result = [
|
||||
{
|
||||
label: this.$t('preferences.run-mode-standard'),
|
||||
value: 1
|
||||
value: APP_RUN_MODE.STANDARD
|
||||
},
|
||||
{
|
||||
label: this.$t('preferences.run-mode-menu-bar'),
|
||||
value: 2
|
||||
label: this.$t('preferences.run-mode-tray'),
|
||||
value: APP_RUN_MODE.TRAY
|
||||
}
|
||||
]
|
||||
|
||||
if (this.isMac) {
|
||||
result = [
|
||||
...result,
|
||||
{
|
||||
label: this.$t('preferences.run-mode-hide-tray'),
|
||||
value: APP_RUN_MODE.HIDE_TRAY
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
return result
|
||||
},
|
||||
speedOptions () {
|
||||
speedUnits () {
|
||||
return [
|
||||
{
|
||||
label: this.$t('preferences.transfer-speed-unlimited'),
|
||||
value: 0
|
||||
label: 'KB/s',
|
||||
value: 'K'
|
||||
},
|
||||
{
|
||||
label: '128 KB/s',
|
||||
value: '128K'
|
||||
},
|
||||
{
|
||||
label: '256 KB/s',
|
||||
value: '256K'
|
||||
},
|
||||
{
|
||||
label: '512 KB/s',
|
||||
value: '512K'
|
||||
},
|
||||
{
|
||||
label: '1 MB/s',
|
||||
value: '1M'
|
||||
},
|
||||
{
|
||||
label: '5 MB/s',
|
||||
value: '5M'
|
||||
},
|
||||
{
|
||||
label: '10 MB/s',
|
||||
value: '10M'
|
||||
label: 'MB/s',
|
||||
value: 'M'
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -353,9 +526,6 @@
|
||||
showHideAppMenuOption () {
|
||||
return is.windows() || is.linux()
|
||||
},
|
||||
downloadDir () {
|
||||
return prettifyDir(this.form.dir)
|
||||
},
|
||||
...mapState('preference', {
|
||||
config: state => state.config
|
||||
})
|
||||
@@ -369,32 +539,80 @@
|
||||
},
|
||||
handleThemeChange (theme) {
|
||||
this.form.theme = theme
|
||||
// this.$store.dispatch('preference/changeThemeConfig', theme)
|
||||
this.$electron.ipcRenderer.send('command',
|
||||
'application:change-theme', theme)
|
||||
},
|
||||
handleDownloadChange (value) {
|
||||
const speedLimit = parseInt(this.form.maxOverallDownloadLimit)
|
||||
this.downloadUnit = value
|
||||
const limit = speedLimit > 0 ? `${speedLimit}${value}` : 0
|
||||
this.form.maxOverallDownloadLimit = limit
|
||||
},
|
||||
handleUploadChange (value) {
|
||||
const speedLimit = parseInt(this.form.maxOverallUploadLimit)
|
||||
this.uploadUnit = value
|
||||
const limit = speedLimit > 0 ? `${speedLimit}${value}` : 0
|
||||
this.form.maxOverallUploadLimit = limit
|
||||
},
|
||||
onKeepSeedingChange (enable) {
|
||||
this.form.seedRatio = enable ? 0 : 1
|
||||
this.form.seedTime = enable ? 525600 : 60
|
||||
},
|
||||
handleHistoryDirectorySelected (dir) {
|
||||
this.form.dir = dir
|
||||
},
|
||||
handleNativeDirectorySelected (dir) {
|
||||
this.form.dir = dir
|
||||
this.$store.dispatch('preference/recordHistoryDirectory', dir)
|
||||
},
|
||||
onDirectorySelected (dir) {
|
||||
this.form.dir = dir
|
||||
},
|
||||
syncFormConfig () {
|
||||
this.$store.dispatch('preference/fetchPreference')
|
||||
.then((config) => {
|
||||
this.form = initialForm(config)
|
||||
this.form = initForm(config)
|
||||
this.formOriginal = cloneDeep(this.form)
|
||||
})
|
||||
},
|
||||
submitForm (formName) {
|
||||
this.$refs[formName].validate((valid) => {
|
||||
if (!valid) {
|
||||
console.log('[Motrix] preference form valid:', valid)
|
||||
console.error('[Motrix] preference form valid:', valid)
|
||||
return false
|
||||
}
|
||||
|
||||
const { runMode, openAtLogin, autoHideWindow } = this.form
|
||||
const changed = diffConfig(this.formOriginal, this.form)
|
||||
const data = {
|
||||
...changed
|
||||
...diffConfig(this.formOriginal, this.form),
|
||||
...changedConfig.advanced
|
||||
}
|
||||
|
||||
const {
|
||||
btAutoDownloadContent,
|
||||
autoHideWindow,
|
||||
btTracker,
|
||||
noProxy,
|
||||
rpcListenPort
|
||||
} = data
|
||||
|
||||
if ('btAutoDownloadContent' in data) {
|
||||
data.followTorrent = btAutoDownloadContent
|
||||
data.followMetalink = btAutoDownloadContent
|
||||
data.pauseMetadata = !btAutoDownloadContent
|
||||
}
|
||||
|
||||
if (btTracker) {
|
||||
data.btTracker = reduceTrackerString(convertLineToComma(btTracker))
|
||||
}
|
||||
|
||||
if (noProxy) {
|
||||
data.noProxy = convertLineToComma(noProxy)
|
||||
}
|
||||
|
||||
if (rpcListenPort === EMPTY_STRING) {
|
||||
data.rpcListenPort = this.rpcDefaultPort
|
||||
}
|
||||
|
||||
console.log('[Motrix] preference changed data:', data)
|
||||
|
||||
this.$store.dispatch('preference/save', data)
|
||||
@@ -407,33 +625,59 @@
|
||||
this.$msg.success(this.$t('preferences.save-fail-message'))
|
||||
})
|
||||
|
||||
changedConfig.basic = {}
|
||||
changedConfig.advanced = {}
|
||||
|
||||
if (this.isRenderer) {
|
||||
this.$electron.ipcRenderer.send('command',
|
||||
'application:open-at-login', openAtLogin)
|
||||
|
||||
if ('runMode' in changed) {
|
||||
this.$electron.ipcRenderer.send('command',
|
||||
'application:toggle-dock', runMode === APP_RUN_MODE.STANDARD)
|
||||
}
|
||||
|
||||
if ('autoHideWindow' in changed) {
|
||||
if ('autoHideWindow' in data) {
|
||||
this.$electron.ipcRenderer.send('command',
|
||||
'application:auto-hide-window', autoHideWindow)
|
||||
}
|
||||
|
||||
if (checkIsNeedRestart(data)) {
|
||||
this.$electron.ipcRenderer.send('command',
|
||||
'application:relaunch')
|
||||
this.$electron.ipcRenderer.send('command', 'application:relaunch')
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
resetForm (formName) {
|
||||
this.$refs.themeSwitcher.currentValue = backupConfig.theme
|
||||
this.handleLocaleChange(this.formOriginal.locale)
|
||||
this.syncFormConfig()
|
||||
}
|
||||
},
|
||||
beforeRouteLeave (to, from, next) {
|
||||
changedConfig.basic = diffConfig(this.formOriginal, this.form)
|
||||
if (to.path === '/preference/advanced') {
|
||||
next()
|
||||
} else {
|
||||
if (isEmpty(changedConfig.basic) && isEmpty(changedConfig.advanced)) {
|
||||
next()
|
||||
} else {
|
||||
dialog.showMessageBox({
|
||||
type: 'warning',
|
||||
title: this.$t('preferences.not-saved'),
|
||||
message: this.$t('preferences.not-saved-confirm'),
|
||||
buttons: [this.$t('app.yes'), this.$t('app.no')],
|
||||
cancelId: 1
|
||||
}).then(({ response }) => {
|
||||
if (response === 0) {
|
||||
if (changedConfig.basic.theme !== undefined) {
|
||||
this.$electron.ipcRenderer.send('command',
|
||||
'application:change-theme',
|
||||
backupConfig.theme)
|
||||
}
|
||||
if (changedConfig.basic.locale !== undefined) {
|
||||
this.handleLocaleChange(this.formOriginal.locale)
|
||||
}
|
||||
changedConfig.basic = {}
|
||||
changedConfig.advanced = {}
|
||||
backupConfig.theme = undefined
|
||||
next()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,229 @@
|
||||
<template>
|
||||
<div class="mo-history-directory">
|
||||
<el-popover
|
||||
popper-class="mo-directory-popper"
|
||||
trigger="hover"
|
||||
:placement="placement"
|
||||
:width="width"
|
||||
>
|
||||
<el-empty class="mo-directory-empty" :image-size="48" v-if="empty" />
|
||||
<ul class="mo-directory-list" v-if="favoriteDirectories.length > 0">
|
||||
<li
|
||||
v-for="directory in favoriteDirectories"
|
||||
:key="directory"
|
||||
@click.stop="() => handleSelectItem(directory)"
|
||||
>
|
||||
<span class="mo-directory-path" :title="directory">{{directory}}</span>
|
||||
<span class="mo-directory-actions">
|
||||
<i
|
||||
class="el-icon-star-off icon-history-favorited"
|
||||
@click.stop="() => handleCancelFavoriteItem(directory)"
|
||||
/>
|
||||
<i
|
||||
class="el-icon-delete icon-history-remove"
|
||||
@click.stop="() => handleRemoveItem(directory)"
|
||||
/>
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="mo-directory-divider" v-if="showDivider" />
|
||||
<ul class="mo-directory-list" v-if="historyDirectories.length > 0">
|
||||
<li
|
||||
v-for="directory in historyDirectories"
|
||||
:key="directory"
|
||||
@click.stop="() => handleSelectItem(directory)"
|
||||
>
|
||||
<span class="mo-directory-path" :title="directory">{{directory}}</span>
|
||||
<span class="mo-directory-actions">
|
||||
<i
|
||||
v-if="showFavoriteAction"
|
||||
class="el-icon-star-off icon-history-favorite"
|
||||
@click.stop="() => handleFavoriteItem(directory)"
|
||||
/>
|
||||
<i
|
||||
class="el-icon-delete icon-history-remove"
|
||||
@click.stop="() => handleRemoveItem(directory)"
|
||||
/>
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
<el-button
|
||||
slot="reference"
|
||||
:disabled="popoverDisabled"
|
||||
>
|
||||
<i class="el-icon-time" />
|
||||
</el-button>
|
||||
</el-popover>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
import { MAX_NUM_OF_DIRECTORIES } from '@shared/constants'
|
||||
import { cloneArray } from '@shared/utils'
|
||||
|
||||
export default {
|
||||
name: 'mo-history-directory',
|
||||
components: {
|
||||
},
|
||||
props: {
|
||||
width: {
|
||||
type: Number,
|
||||
default: 360
|
||||
},
|
||||
placement: {
|
||||
type: String,
|
||||
default: 'bottom-start'
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
visible: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState('preference', {
|
||||
historyDirectories: state => {
|
||||
return cloneArray(state.config.historyDirectories, true)
|
||||
},
|
||||
favoriteDirectories: state => {
|
||||
return cloneArray(state.config.favoriteDirectories, true)
|
||||
}
|
||||
}),
|
||||
empty () {
|
||||
const { favoriteDirectories, historyDirectories } = this
|
||||
return favoriteDirectories.length + historyDirectories.length === 0
|
||||
},
|
||||
popoverDisabled () {
|
||||
const { favoriteDirectories, historyDirectories } = this
|
||||
return favoriteDirectories.length === 0 &&
|
||||
historyDirectories.length === 0
|
||||
},
|
||||
showDivider () {
|
||||
const { favoriteDirectories, historyDirectories } = this
|
||||
return favoriteDirectories.length > 0 &&
|
||||
historyDirectories.length > 0
|
||||
},
|
||||
showFavoriteAction () {
|
||||
const { favoriteDirectories } = this
|
||||
return favoriteDirectories.length < MAX_NUM_OF_DIRECTORIES
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleIconClick () {
|
||||
if (this.popoverDisabled) {
|
||||
return
|
||||
}
|
||||
|
||||
const { visible } = this
|
||||
this.visible = !visible
|
||||
},
|
||||
handleSelectItem (directory) {
|
||||
this.$emit('selected', directory.trim())
|
||||
this.visible = false
|
||||
},
|
||||
handleFavoriteItem (directory) {
|
||||
console.log('handleFavoriteItem==>', directory)
|
||||
this.$store.dispatch('preference/favoriteDirectory', directory)
|
||||
},
|
||||
handleCancelFavoriteItem (directory) {
|
||||
console.log('handleCancelFavoriteItem==>', directory)
|
||||
this.$store.dispatch('preference/cancelFavoriteDirectory', directory)
|
||||
},
|
||||
handleRemoveItem (directory) {
|
||||
console.log('handleRemoveItem==>', directory)
|
||||
this.$store.dispatch('preference/removeDirectory', directory)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.el-popover.mo-directory-popper {
|
||||
padding: $--popover-padding 0;
|
||||
}
|
||||
|
||||
.el-empty.mo-directory-empty {
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
.mo-directory-divider {
|
||||
padding: 0 $--popover-padding;
|
||||
margin: 6px 0;
|
||||
&::after {
|
||||
content: ' ';
|
||||
display: block;
|
||||
height: 1px;
|
||||
width: 100%;
|
||||
background: $--border-color-base;
|
||||
}
|
||||
}
|
||||
|
||||
.mo-directory-list {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
list-style: none;
|
||||
&> li {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
list-style: none;
|
||||
line-height: $--font-line-height-primary;
|
||||
margin: 0;
|
||||
font-size: $--font-size-small;
|
||||
color: $--color-text-regular;
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
padding: 6px 6px 6px $--popover-padding;
|
||||
&:focus, &:hover {
|
||||
background-color: $--background-color-base;
|
||||
color: $--color-primary-light-2;
|
||||
}
|
||||
}
|
||||
.mo-directory-path {
|
||||
display: inline-block;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.mo-directory-actions {
|
||||
min-width: 40px;
|
||||
text-align: right;
|
||||
&> i {
|
||||
padding: 3px;
|
||||
margin-right: 3px;
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
.icon-history-favorite {
|
||||
&:focus, &:hover {
|
||||
color: $--color-warning;
|
||||
}
|
||||
}
|
||||
.icon-history-favorited {
|
||||
color: $--color-warning;
|
||||
}
|
||||
.icon-history-remove {
|
||||
&:focus, &:hover {
|
||||
color: $--color-danger;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.theme-dark {
|
||||
.mo-directory-divider {
|
||||
&::after {
|
||||
background: $--dk-border-color-base;
|
||||
}
|
||||
}
|
||||
.mo-directory-list {
|
||||
&> li {
|
||||
color: $--dk-font-color-base;
|
||||
&:focus, &:hover {
|
||||
background-color: $--color-primary;
|
||||
color: $--color-white;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<el-container class="content panel" direction="horizontal">
|
||||
<el-container class="main panel" direction="horizontal">
|
||||
<el-aside width="200px" class="subnav hidden-xs-only">
|
||||
<router-view name="subnav" />
|
||||
</el-aside>
|
||||
@@ -17,62 +17,59 @@
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.form-preference {
|
||||
padding: 16px 7% 64px 0;
|
||||
.el-switch__label {
|
||||
font-weight: normal;
|
||||
.form-preference {
|
||||
padding: 16px 7% 64px 16px;
|
||||
.el-switch__label {
|
||||
font-weight: normal;
|
||||
color: $--color-text-regular;
|
||||
&.is-active {
|
||||
color: $--color-text-regular;
|
||||
&.is-active {
|
||||
color: $--color-text-regular;
|
||||
}
|
||||
}
|
||||
.el-checkbox__input.is-checked + .el-checkbox__label {
|
||||
color: $--color-text-regular;
|
||||
}
|
||||
.el-form-item {
|
||||
a {
|
||||
color: $--color-text-regular;
|
||||
text-decoration: none;
|
||||
&:hover {
|
||||
color: $--color-text-primary;
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
.el-checkbox__input.is-checked + .el-checkbox__label {
|
||||
color: $--color-text-regular;
|
||||
}
|
||||
.el-form-item {
|
||||
a {
|
||||
color: $--color-text-regular;
|
||||
text-decoration: none;
|
||||
&:hover {
|
||||
color: $--color-text-primary;
|
||||
text-decoration: underline;
|
||||
}
|
||||
&:active {
|
||||
color: $--color-text-primary;
|
||||
}
|
||||
}
|
||||
}
|
||||
.el-form-item.el-form-item--mini {
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
.el-form-item__content {
|
||||
color: $--color-text-regular;
|
||||
}
|
||||
.form-item-sub {
|
||||
margin-bottom: 8px;
|
||||
&:last-of-type {
|
||||
margin-bottom: 0;
|
||||
&:active {
|
||||
color: $--color-text-primary;
|
||||
}
|
||||
}
|
||||
}
|
||||
.form-actions {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: auto;
|
||||
z-index: 10;
|
||||
width: -webkit-fill-available;
|
||||
box-sizing: border-box;
|
||||
padding: 24px 36px;
|
||||
margin-left: -36px;
|
||||
// aside.width + subnav.width + padding-left + scrollbar.width
|
||||
margin-right: 322px;
|
||||
.el-form-item.el-form-item--mini {
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
.action-link {
|
||||
cursor: pointer;
|
||||
color: $--link-color;
|
||||
&:hover {
|
||||
color: $--link-hover-color;
|
||||
text-decoration: underline;
|
||||
.el-form-item__content {
|
||||
color: $--color-text-regular;
|
||||
}
|
||||
.form-item-sub {
|
||||
margin-bottom: 8px;
|
||||
&:last-of-type {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
.form-actions {
|
||||
position: sticky;
|
||||
bottom: 0;
|
||||
left: auto;
|
||||
z-index: 10;
|
||||
width: -webkit-fill-available;
|
||||
box-sizing: border-box;
|
||||
padding: 24px 16px;
|
||||
}
|
||||
.action-link {
|
||||
cursor: pointer;
|
||||
color: $--link-color;
|
||||
&:hover {
|
||||
color: $--link-hover-color;
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<mo-browser
|
||||
v-if="isRenderer"
|
||||
class="lab-webview"
|
||||
:src="src"
|
||||
:src="url"
|
||||
/>
|
||||
</el-container>
|
||||
</template>
|
||||
@@ -20,6 +20,7 @@
|
||||
import is from 'electron-is'
|
||||
import { mapState } from 'vuex'
|
||||
|
||||
import { APP_THEME } from '@shared/constants'
|
||||
import SubnavSwitcher from '@/components/Subnav/SubnavSwitcher'
|
||||
import Browser from '@/components/Browser'
|
||||
import '@/components/Icons/info-square'
|
||||
@@ -33,11 +34,30 @@
|
||||
data () {
|
||||
const { locale } = this.$store.state.preference.config
|
||||
return {
|
||||
src: `https://motrix.app/lab?lite=true&lang=${locale}`
|
||||
locale
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isRenderer: () => is.renderer(),
|
||||
...mapState('app', {
|
||||
systemTheme: state => state.systemTheme
|
||||
}),
|
||||
...mapState('preference', {
|
||||
config: state => state.config,
|
||||
theme: state => state.config.theme
|
||||
}),
|
||||
currentTheme () {
|
||||
if (this.theme === APP_THEME.AUTO) {
|
||||
return this.systemTheme
|
||||
} else {
|
||||
return this.theme
|
||||
}
|
||||
},
|
||||
url () {
|
||||
const { currentTheme, locale } = this
|
||||
const result = `https://motrix.app/lab?lite=true&theme=${currentTheme}&lang=${locale}`
|
||||
return result
|
||||
},
|
||||
title () {
|
||||
return this.$t('preferences.lab')
|
||||
},
|
||||
@@ -59,10 +79,7 @@
|
||||
route: '/preference/lab'
|
||||
}
|
||||
]
|
||||
},
|
||||
...mapState('preference', {
|
||||
config: state => state.config
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -74,6 +91,6 @@
|
||||
flex-basis: auto;
|
||||
overflow: auto;
|
||||
box-sizing: border-box;
|
||||
padding: 0 0 0 36px;
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||