Compare commits
206 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 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 |
+13
-10
@@ -5,8 +5,7 @@ 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 Webpack = require('webpack')
|
||||
const Multispinner = require('@motrix/multispinner')
|
||||
|
||||
const mainConfig = require('./webpack.main.config')
|
||||
@@ -76,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()) {
|
||||
@@ -103,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({
|
||||
@@ -121,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()
|
||||
}
|
||||
|
||||
+25
-43
@@ -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,8 +4,9 @@ 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');
|
||||
|
||||
@@ -39,7 +40,7 @@ let mainConfig = {
|
||||
path: path.join(__dirname, '../dist/electron')
|
||||
},
|
||||
plugins: [
|
||||
new webpack.NoEmitOnErrorsPlugin(),
|
||||
new Webpack.NoEmitOnErrorsPlugin(),
|
||||
new ESLintPlugin({
|
||||
formatter: require('eslint-friendly-formatter')
|
||||
})
|
||||
@@ -67,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}"`
|
||||
})
|
||||
)
|
||||
}
|
||||
@@ -79,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,7 +5,7 @@ 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')
|
||||
@@ -114,31 +114,15 @@ let rendererConfig = {
|
||||
},
|
||||
{
|
||||
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
|
||||
use: {
|
||||
loader: 'url-loader',
|
||||
options: {
|
||||
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',
|
||||
options: {
|
||||
limit: 10000,
|
||||
name: 'fonts/[name]--[folder].[ext]'
|
||||
}
|
||||
}
|
||||
type: 'asset/inline'
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -168,8 +152,8 @@ let rendererConfig = {
|
||||
? 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')
|
||||
@@ -209,7 +193,7 @@ 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, '\\\\')}"`
|
||||
})
|
||||
)
|
||||
@@ -227,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,7 +5,7 @@ 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')
|
||||
@@ -111,23 +111,11 @@ let webConfig = {
|
||||
},
|
||||
{
|
||||
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
|
||||
use: {
|
||||
loader: 'url-loader',
|
||||
options: {
|
||||
limit: 10000,
|
||||
name: 'imgs/[name].[ext]'
|
||||
}
|
||||
}
|
||||
type: 'asset/inline'
|
||||
},
|
||||
{
|
||||
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
|
||||
use: {
|
||||
loader: 'url-loader',
|
||||
options: {
|
||||
limit: 10000,
|
||||
name: 'fonts/[name].[ext]'
|
||||
}
|
||||
}
|
||||
type: 'asset/inline'
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -153,11 +141,11 @@ let webConfig = {
|
||||
? 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')
|
||||
@@ -208,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@v2
|
||||
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,13 @@ jobs:
|
||||
|
||||
# If the commit is tagged with a version (e.g. "v1.0.0"),
|
||||
# release the app after building
|
||||
release: ${{ secrets.force_release == 'true' || startsWith(github.ref, 'refs/tags/v') }}
|
||||
release: ${{ vars.skip_publish != 'true' }}
|
||||
env:
|
||||
# Snapcraft
|
||||
SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.snapcraft_token }}
|
||||
# macOS notarization API key
|
||||
API_KEY_ID: ${{ secrets.api_key_id }}
|
||||
API_KEY_ISSUER_ID: ${{ secrets.api_key_issuer_id }}
|
||||
TEAM_ID: ${{ secrets.team_id }}
|
||||
APPLE_ID: ${{ secrets.apple_id }}
|
||||
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.apple_app_specific_password }}
|
||||
|
||||
+21
-8
@@ -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/
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
|
||||
## 🌍 翻译指南
|
||||
|
||||
首先你要确定一个语言的英文简写作为 **locale**,如 en-US,这个 locale 值请严格参考 [Electron 的 Locales 文档](https://www.electronjs.org/docs/api/app#appgetlocale)
|
||||
首先你要确定一个语言的英文简写作为 **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 的国际化分两部分:
|
||||
|
||||
|
||||
+1
-1
@@ -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://www.electronjs.org/docs/api/app#appgetlocale).
|
||||
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:
|
||||
|
||||
|
||||
+39
-7
@@ -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,7 +47,7 @@ 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 install --cask motrix
|
||||
@@ -43,6 +57,8 @@ brew update && brew install --cask motrix
|
||||
|
||||
你可以下载 `AppImage` (适用于所有 Linux 发行版)或 `snap` 来安装 Motrix,更多 Linux 安装包格式请查看 [GitHub/release](https://github.com/agalwood/Motrix/releases) 。
|
||||
|
||||
Motrix 在 Linux 中首次启动可能需要使用 `sudo` 运行,因为可能没有创建下载会话文件的权限 (`/var/cache/aria2.session`)。
|
||||
|
||||
如果你想自己通过编译源码来安装,请阅读 **编译打包** 部分。
|
||||
|
||||
#### AppImage
|
||||
@@ -70,7 +86,7 @@ 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)。
|
||||
|
||||
运行以下命令进行安装:
|
||||
|
||||
@@ -78,7 +94,16 @@ v1.5.10 提示
|
||||
yay 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
|
||||
```
|
||||
|
||||
## ✨ 特性
|
||||
|
||||
@@ -122,8 +147,8 @@ yarn
|
||||
天朝大陆用户建议使用淘宝的 npm 源
|
||||
|
||||
```bash
|
||||
yarn config set registry 'https://registry.npm.taobao.org'
|
||||
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'
|
||||
```
|
||||
@@ -143,14 +168,18 @@ yarn run dev
|
||||
```bash
|
||||
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
|
||||
|
||||
@@ -180,15 +209,18 @@ yarn run build
|
||||
| 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) |
|
||||
|
||||
## 📜 开源许可
|
||||
|
||||
|
||||
@@ -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,7 +47,7 @@ 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 install --cask motrix
|
||||
@@ -43,6 +57,8 @@ brew update && brew install --cask motrix
|
||||
|
||||
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,7 +88,7 @@ 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:
|
||||
|
||||
@@ -80,7 +96,16 @@ Run the following command to install:
|
||||
yay motrix
|
||||
```
|
||||
|
||||
Motrix may need to run with `sudo` for the first time in Linux because there is no permission to create the download session file (`/var/cache/aria2.session`).
|
||||
#### 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
|
||||
|
||||
@@ -136,6 +161,11 @@ yarn run dev
|
||||
```bash
|
||||
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.
|
||||
|
||||
@@ -143,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
|
||||
|
||||
@@ -173,15 +203,18 @@ Translations into versions for other languages are welcome 🧐! Please read the
|
||||
| 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
-1
@@ -1,2 +1,2 @@
|
||||
provider: generic
|
||||
url: 'https://dl.motrix.app/release/'
|
||||
url: 'https://dl.motrix.app/releases/'
|
||||
|
||||
+21
-12
@@ -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}`)
|
||||
}
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 33 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 4.5 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 5.7 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 35 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,15 +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
|
||||
# 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 ################
|
||||
@@ -52,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.
|
||||
@@ -70,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-
|
||||
Executable
BIN
Binary file not shown.
Binary file not shown.
@@ -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-
|
||||
Executable
BIN
Binary file not shown.
@@ -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,15 +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
|
||||
# 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 ################
|
||||
@@ -52,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.
|
||||
@@ -70,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-
|
||||
Executable
BIN
Binary file not shown.
@@ -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-
|
||||
Executable
BIN
Binary file not shown.
@@ -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-
|
||||
Binary file not shown.
Binary file not shown.
@@ -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-
|
||||
Executable
BIN
Binary file not shown.
@@ -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,15 +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
|
||||
# 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 ################
|
||||
@@ -52,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.
|
||||
@@ -70,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-
|
||||
Executable
BIN
Binary file not shown.
+71
-232
@@ -1,13 +1,13 @@
|
||||
{
|
||||
"name": "Motrix",
|
||||
"version": "1.6.8",
|
||||
"version": "1.8.14",
|
||||
"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,259 +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": [
|
||||
{
|
||||
"target": "dmg",
|
||||
"arch": [
|
||||
"x64",
|
||||
"arm64"
|
||||
]
|
||||
},
|
||||
{
|
||||
"target": "zip",
|
||||
"arch": [
|
||||
"x64",
|
||||
"arm64"
|
||||
]
|
||||
}
|
||||
],
|
||||
"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" : ">=18.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.14.0",
|
||||
"@motrix/nat-api": "^0.3.1",
|
||||
"@panter/vue-i18next": "^0.15.2",
|
||||
"axios": "^0.21.1",
|
||||
"bittorrent-peerid": "^1.3.3",
|
||||
"blob-util": "^2.0.2",
|
||||
"clipboard-polyfill": "^3.0.3",
|
||||
"electron-debug": "^3.2.0",
|
||||
"electron-is": "^3.0.0",
|
||||
"electron-log": "^4.3.5",
|
||||
"electron-store": "^8.0.0",
|
||||
"electron-updater": "^4.3.8",
|
||||
"element-ui": "^2.15.1",
|
||||
"i18next": "^20.2.2",
|
||||
"lodash": "^4.17.21",
|
||||
"node-fetch": "^2.6.1",
|
||||
"normalize.css": "^8.0.1",
|
||||
"parse-torrent": "^9.1.3",
|
||||
"randomatic": "^3.1.1",
|
||||
"svg-innerhtml": "^1.1.0",
|
||||
"vue": "^2.6.12",
|
||||
"vue-electron": "^1.0.6",
|
||||
"vue-router": "^3.5.1",
|
||||
"vuex": "^3.6.2",
|
||||
"vuex-router-sync": "^5.0.0",
|
||||
"ws": "^7.4.5"
|
||||
"ws": "^8.13.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.14.0",
|
||||
"@babel/plugin-proposal-class-properties": "^7.13.0",
|
||||
"@babel/plugin-transform-runtime": "^7.13.15",
|
||||
"@babel/preset-env": "^7.14.1",
|
||||
"@babel/register": "^7.13.16",
|
||||
"@electron/remote": "^1.1.0",
|
||||
"@motrix/multispinner": "^0.2.2",
|
||||
"@vue/eslint-config-standard": "^6.0.0",
|
||||
"ajv": "^8.2.0",
|
||||
"@babel/core": "^7.21.4",
|
||||
"@babel/plugin-proposal-class-properties": "^7.18.6",
|
||||
"@babel/plugin-transform-runtime": "^7.21.4",
|
||||
"@babel/preset-env": "^7.21.4",
|
||||
"@babel/register": "^7.21.0",
|
||||
"@babel/runtime": "^7.21.0",
|
||||
"@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.3.6",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"babel-loader": "^8.2.2",
|
||||
"babel-loader": "^9.1.2",
|
||||
"babel-plugin-component": "^1.1.1",
|
||||
"cfonts": "^2.9.1",
|
||||
"chalk": "^4.1.1",
|
||||
"copy-webpack-plugin": "^8.1.1",
|
||||
"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": "^5.2.4",
|
||||
"css-minimizer-webpack-plugin": "^2.0.0",
|
||||
"del": "^6.0.0",
|
||||
"electron": "^11.4.4",
|
||||
"electron-builder": "22.10.5",
|
||||
"electron-builder-notarize": "^1.2.0",
|
||||
"css-loader": "^6.7.3",
|
||||
"css-minimizer-webpack-plugin": "^5.0.0",
|
||||
"del": "^6.1.1",
|
||||
"electron": "^22.3.5",
|
||||
"electron-builder": "^24.2.0",
|
||||
"electron-devtools-installer": "^3.2.0",
|
||||
"electron-notarize": "^1.0.0",
|
||||
"electron-osx-sign": "^0.5.0",
|
||||
"eslint": "^7.25.0",
|
||||
"electron-is": "^3.0.0",
|
||||
"electron-log": "^4.4.8",
|
||||
"electron-store": "^8.1.0",
|
||||
"electron-updater": "^5.3.0",
|
||||
"element-ui": "^2.15.13",
|
||||
"eslint": "^7.32.0",
|
||||
"eslint-friendly-formatter": "^4.0.1",
|
||||
"eslint-plugin-import": "^2.22.1",
|
||||
"eslint-plugin-import": "^2.27.5",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-promise": "^5.1.0",
|
||||
"eslint-plugin-vue": "^7.9.0",
|
||||
"eslint-webpack-plugin": "^2.5.4",
|
||||
"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.3.1",
|
||||
"mini-css-extract-plugin": "1.6.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",
|
||||
"sass": "^1.32.12",
|
||||
"sass-loader": "^11.0.1",
|
||||
"style-loader": "^2.0.0",
|
||||
"terser-webpack-plugin": "^5.1.1",
|
||||
"url-loader": "^4.1.1",
|
||||
"vue-loader": "^15.9.6",
|
||||
"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.6.12",
|
||||
"webpack": "^5.36.2",
|
||||
"webpack-cli": "^4.6.0",
|
||||
"webpack-dev-server": "^3.11.2",
|
||||
"webpack-hot-middleware": "^2.25.0",
|
||||
"webpack-merge": "^5.7.3",
|
||||
"vue-template-compiler": "^2.7.14",
|
||||
"vuex": "^3.6.2",
|
||||
"vuex-router-sync": "^5.0.0",
|
||||
"webpack": "^5.80.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"
|
||||
}
|
||||
}
|
||||
|
||||
+30
-3
@@ -10,15 +10,42 @@
|
||||
</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>
|
||||
|
||||
+221
-57
@@ -3,7 +3,7 @@ import { app, shell, dialog, ipcMain } from 'electron'
|
||||
import is from 'electron-is'
|
||||
import { readFile, unlink } from 'fs'
|
||||
import { extname, basename } from 'path'
|
||||
import { isEmpty } from 'lodash'
|
||||
import { isEmpty, isEqual } from 'lodash'
|
||||
|
||||
import {
|
||||
APP_RUN_MODE,
|
||||
@@ -13,9 +13,11 @@ import {
|
||||
import { checkIsNeedRun } from '@shared/utils'
|
||||
import {
|
||||
convertTrackerDataToComma,
|
||||
fetchBtTrackerFromSource
|
||||
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 Engine from './core/Engine'
|
||||
@@ -31,7 +33,6 @@ import TouchBarManager from './ui/TouchBarManager'
|
||||
import TrayManager from './ui/TrayManager'
|
||||
import DockManager from './ui/DockManager'
|
||||
import ThemeManager from './ui/ThemeManager'
|
||||
import { getSessionPath } from './utils'
|
||||
|
||||
export default class Application extends EventEmitter {
|
||||
constructor () {
|
||||
@@ -41,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()
|
||||
|
||||
@@ -65,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()
|
||||
|
||||
@@ -84,6 +87,44 @@ export default class Application extends EventEmitter {
|
||||
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)
|
||||
@@ -124,8 +165,10 @@ 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()
|
||||
})
|
||||
@@ -145,11 +188,20 @@ 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'),
|
||||
systemTheme: this.themeManager.getSystemTheme(),
|
||||
speedometer: this.configManager.getUserConfig('tray-speedometer')
|
||||
speedometer: this.configManager.getUserConfig('tray-speedometer'),
|
||||
runMode: this.configManager.getUserConfig('run-mode')
|
||||
})
|
||||
|
||||
this.watchTraySpeedometerEnabledChange()
|
||||
@@ -172,8 +224,10 @@ export default class Application extends EventEmitter {
|
||||
}
|
||||
|
||||
watchTraySpeedometerEnabledChange () {
|
||||
this.configManager.userConfig.onDidChange('tray-speedometer', async (newValue, oldValue) => {
|
||||
logger.info('[Motrix] detected tray speedometer value change event:', newValue, oldValue)
|
||||
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)
|
||||
})
|
||||
}
|
||||
@@ -184,6 +238,69 @@ 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()
|
||||
|
||||
@@ -210,7 +327,7 @@ export default class Application extends EventEmitter {
|
||||
try {
|
||||
await Promise.allSettled(promises)
|
||||
} catch (e) {
|
||||
logger.warn('[Motrix] start UPnP mapping fail', e)
|
||||
logger.warn('[Motrix] start UPnP mapping fail', e.message)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -230,10 +347,11 @@ export default class Application extends EventEmitter {
|
||||
}
|
||||
|
||||
watchUPnPPortsChange () {
|
||||
const { systemConfig } = this.configManager
|
||||
const watchKeys = ['listen-port', 'dht-listen-port']
|
||||
|
||||
watchKeys.forEach((key) => {
|
||||
this.configManager.systemConfig.onDidChange(key, async (newValue, oldValue) => {
|
||||
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) {
|
||||
@@ -254,7 +372,9 @@ export default class Application extends EventEmitter {
|
||||
}
|
||||
|
||||
watchUPnPEnabledChange () {
|
||||
this.configManager.userConfig.onDidChange('enable-upnp', async (newValue, oldValue) => {
|
||||
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()
|
||||
@@ -295,7 +415,8 @@ export default class Application extends EventEmitter {
|
||||
return
|
||||
}
|
||||
|
||||
const tracker = convertTrackerDataToComma(data)
|
||||
let tracker = convertTrackerDataToComma(data)
|
||||
tracker = reduceTrackerString(tracker)
|
||||
this.savePreference({
|
||||
system: {
|
||||
'bt-tracker': tracker
|
||||
@@ -342,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()
|
||||
}
|
||||
})
|
||||
@@ -370,6 +491,7 @@ export default class Application extends EventEmitter {
|
||||
this.isReady = true
|
||||
this.emit('ready')
|
||||
})
|
||||
|
||||
if (is.macOS()) {
|
||||
this.touchBarManager.setup(page, win)
|
||||
}
|
||||
@@ -403,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()
|
||||
}
|
||||
|
||||
@@ -536,8 +663,16 @@ export default class Application extends EventEmitter {
|
||||
win.setProgressBar(0)
|
||||
})
|
||||
|
||||
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) => {
|
||||
@@ -546,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
|
||||
@@ -582,18 +732,6 @@ 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.show(page)
|
||||
})
|
||||
@@ -602,22 +740,10 @@ export default class Application extends EventEmitter {
|
||||
this.hide(page)
|
||||
})
|
||||
|
||||
this.on('application:reset-session', () => {
|
||||
this.engine.stop()
|
||||
|
||||
app.clearRecentDocuments()
|
||||
|
||||
const sessionPath = this.configManager.getUserConfig('session-path') || getSessionPath()
|
||||
setTimeout(() => {
|
||||
unlink(sessionPath, function (err) {
|
||||
logger.info('[Motrix] Removed the download seesion file:', err)
|
||||
})
|
||||
|
||||
this.engine.start()
|
||||
}, 3000)
|
||||
})
|
||||
this.on('application:reset-session', () => this.resetSession())
|
||||
|
||||
this.on('application:reset', () => {
|
||||
this.offConfigListeners()
|
||||
this.configManager.reset()
|
||||
this.relaunch()
|
||||
})
|
||||
@@ -627,7 +753,7 @@ export default class Application extends EventEmitter {
|
||||
})
|
||||
|
||||
this.on('application:change-theme', (theme) => {
|
||||
this.themeManager.updateAppAppearance(theme)
|
||||
this.themeManager.updateSystemTheme(theme)
|
||||
this.sendCommandToAll('application:update-theme', { theme })
|
||||
})
|
||||
|
||||
@@ -686,7 +812,7 @@ 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)
|
||||
@@ -742,6 +868,11 @@ 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.handleDownloadStatusChange(downloading)
|
||||
if (downloading) {
|
||||
@@ -764,6 +895,37 @@ export default class Application extends EventEmitter {
|
||||
}
|
||||
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 () {
|
||||
@@ -782,10 +944,12 @@ export default class Application extends EventEmitter {
|
||||
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
|
||||
...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,
|
||||
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'
|
||||
@@ -18,7 +16,9 @@ import {
|
||||
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,6 +51,7 @@ 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,
|
||||
@@ -59,21 +60,24 @@ export default class ConfigManager {
|
||||
'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 */
|
||||
})
|
||||
@@ -99,20 +103,22 @@ 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': [
|
||||
@@ -140,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 () {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
+13
-26
@@ -1,19 +1,18 @@
|
||||
import { app } from 'electron'
|
||||
import is from 'electron-is'
|
||||
import { existsSync, writeFile, unlink } from 'fs'
|
||||
import { resolve, join } from 'path'
|
||||
import { spawn } from 'child_process'
|
||||
|
||||
import logger from './Logger'
|
||||
import { getI18n } from '../ui/Locale'
|
||||
import {
|
||||
getEngineBin,
|
||||
getEnginePidPath,
|
||||
getAria2BinPath,
|
||||
getAria2ConfPath,
|
||||
getSessionPath,
|
||||
transformConfig
|
||||
} from '../utils/index'
|
||||
|
||||
const { platform } = process
|
||||
const { platform, arch } = process
|
||||
|
||||
export default class Engine {
|
||||
// ChildProcess | null
|
||||
@@ -25,7 +24,6 @@ export default class Engine {
|
||||
this.i18n = getI18n()
|
||||
this.systemConfig = options.systemConfig
|
||||
this.userConfig = options.userConfig
|
||||
this.basePath = this.getBasePath()
|
||||
}
|
||||
|
||||
start () {
|
||||
@@ -36,9 +34,12 @@ export default class Engine {
|
||||
return
|
||||
}
|
||||
|
||||
const binPath = this.getBinPath()
|
||||
const binPath = this.getEngineBinPath()
|
||||
const args = this.getStartArgs()
|
||||
this.instance = spawn(binPath, args)
|
||||
this.instance = spawn(binPath, args, {
|
||||
windowsHide: false,
|
||||
stdio: is.dev() ? 'pipe' : 'ignore'
|
||||
})
|
||||
const pid = this.instance.pid.toString()
|
||||
this.writePidFile(pidPath, pid)
|
||||
|
||||
@@ -66,6 +67,7 @@ export default class Engine {
|
||||
}
|
||||
|
||||
stop () {
|
||||
logger.info('[Motrix] engine.stop.instance')
|
||||
if (this.instance) {
|
||||
this.instance.kill()
|
||||
this.instance = null
|
||||
@@ -80,13 +82,8 @@ export default class Engine {
|
||||
})
|
||||
}
|
||||
|
||||
getBinPath () {
|
||||
const binName = getEngineBin(platform)
|
||||
if (!binName) {
|
||||
throw new Error(this.i18n.t('app.engine-damaged-message'))
|
||||
}
|
||||
|
||||
const result = join(this.basePath, `/engine/${binName}`)
|
||||
getEngineBinPath () {
|
||||
const result = getAria2BinPath(platform, arch)
|
||||
const binIsExist = existsSync(result)
|
||||
if (!binIsExist) {
|
||||
logger.error('[Motrix] engine bin is not exist:', result)
|
||||
@@ -96,20 +93,10 @@ export default class Engine {
|
||||
return result
|
||||
}
|
||||
|
||||
getBasePath () {
|
||||
let result = resolve(app.getAppPath(), '..')
|
||||
|
||||
if (is.dev()) {
|
||||
result = resolve(__dirname, `../../../extra/${platform}`)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
getStartArgs () {
|
||||
const confPath = join(this.basePath, '/engine/aria2.conf')
|
||||
const confPath = getAria2ConfPath(platform, arch)
|
||||
|
||||
const sessionPath = this.userConfig['session-path'] || getSessionPath()
|
||||
const sessionPath = getSessionPath()
|
||||
const sessionIsExist = existsSync(sessionPath)
|
||||
|
||||
let result = [`--conf-path=${confPath}`, `--save-session=${sessionPath}`]
|
||||
|
||||
@@ -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')
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ export default class ProtocolManager extends EventEmitter {
|
||||
this.setup(protocols)
|
||||
}
|
||||
|
||||
setup (protocols) {
|
||||
setup (protocols = {}) {
|
||||
if (is.dev() || is.mas()) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
{ "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" },
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
{ "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" },
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
{ "id": "help.manual", "command": "help:manual" },
|
||||
{ "id": "app.check-for-updates", "command": "application:check-for-updates" },
|
||||
{ "type": "separator" },
|
||||
{ "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" }
|
||||
]
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
{ "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" },
|
||||
|
||||
@@ -8,19 +8,19 @@ 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
|
||||
show = enabled
|
||||
? () => {
|
||||
if (app.dock.isVisible()) {
|
||||
return
|
||||
@@ -30,7 +30,7 @@ export default class DockManager extends EventEmitter {
|
||||
}
|
||||
: () => {}
|
||||
|
||||
hide = isMac
|
||||
hide = enabled
|
||||
? () => {
|
||||
if (!app.dock.isVisible()) {
|
||||
return
|
||||
@@ -40,13 +40,15 @@ export default class DockManager extends EventEmitter {
|
||||
}
|
||||
: () => {}
|
||||
|
||||
setBadge = isMac
|
||||
// macOS setBadge not working
|
||||
// @see https://github.com/electron/electron/issues/25745#issuecomment-702826143
|
||||
setBadge = enabled
|
||||
? (text) => {
|
||||
app.dock.setBadge(text)
|
||||
}
|
||||
: (text) => {}
|
||||
|
||||
handleSpeedChange = isMac
|
||||
handleSpeedChange = enabled
|
||||
? (speed) => {
|
||||
const { downloadSpeed } = speed
|
||||
const text = downloadSpeed > 0 ? `${bytesToSize(downloadSpeed)}/s` : ''
|
||||
@@ -54,7 +56,7 @@ export default class DockManager extends EventEmitter {
|
||||
}
|
||||
: (text) => {}
|
||||
|
||||
openDock = isMac
|
||||
openDock = enabled
|
||||
? (path) => {
|
||||
app.dock.downloadFinished(path)
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
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'
|
||||
|
||||
export default class MenuManager extends EventEmitter {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
+54
-42
@@ -3,8 +3,9 @@ import { join } from 'path'
|
||||
import { Tray, Menu, nativeImage } from 'electron'
|
||||
import is from 'electron-is'
|
||||
|
||||
import { APP_THEME } from '@shared/constants'
|
||||
import { getInverseTheme, getSystemMajorVersion } from '@shared/utils'
|
||||
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,
|
||||
@@ -12,7 +13,6 @@ import {
|
||||
updateStates
|
||||
} from '../utils/menu'
|
||||
import { convertArrayBufferToBuffer } from '../utils/index'
|
||||
// import logger from '../core/Logger'
|
||||
|
||||
let tray = null
|
||||
const { platform } = process
|
||||
@@ -26,9 +26,10 @@ export default class TrayManager extends EventEmitter {
|
||||
|
||||
this.systemTheme = options.systemTheme
|
||||
this.inverseSystemTheme = getInverseTheme(this.systemTheme)
|
||||
this.bigSur = platform === 'darwin' && getSystemMajorVersion() >= 20
|
||||
this.macOS = platform === 'darwin'
|
||||
|
||||
this.speedometer = options.speedometer
|
||||
this.runMode = options.runMode
|
||||
|
||||
this.i18n = getI18n()
|
||||
this.menu = null
|
||||
@@ -38,20 +39,23 @@ export default class TrayManager extends EventEmitter {
|
||||
this.downloadSpeed = 0
|
||||
this.status = false
|
||||
this.focused = false
|
||||
this.initialized = false
|
||||
|
||||
this.init()
|
||||
}
|
||||
|
||||
init () {
|
||||
if (tray || this.initialized || this.runMode === APP_RUN_MODE.HIDE_TRAY) {
|
||||
return
|
||||
}
|
||||
|
||||
this.loadTemplate()
|
||||
|
||||
this.loadImages()
|
||||
|
||||
this.initTray()
|
||||
|
||||
this.setupMenu()
|
||||
this.bindEvents()
|
||||
|
||||
this.handleEvents()
|
||||
this.initialized = true
|
||||
}
|
||||
|
||||
loadTemplate () {
|
||||
@@ -77,25 +81,7 @@ export default class TrayManager extends EventEmitter {
|
||||
}
|
||||
|
||||
loadImagesForMacOS () {
|
||||
if (this.bigSur) {
|
||||
const {
|
||||
systemTheme,
|
||||
inverseSystemTheme
|
||||
} = this
|
||||
|
||||
this.normalIcon = this.getFromCacheOrCreateImage(`mo-tray-${systemTheme}-normal.png`)
|
||||
this.activeIcon = this.getFromCacheOrCreateImage(`mo-tray-${systemTheme}-active.png`)
|
||||
|
||||
// if (systemTheme === APP_THEME.DARK) {
|
||||
// this.inverseNormalIcon = this.normalIcon
|
||||
// this.inverseActiveIcon = this.activeIcon
|
||||
// } else {
|
||||
this.inverseNormalIcon = this.getFromCacheOrCreateImage(`mo-tray-${inverseSystemTheme}-normal.png`)
|
||||
this.inverseActiveIcon = this.getFromCacheOrCreateImage(`mo-tray-${inverseSystemTheme}-active.png`)
|
||||
// }
|
||||
} else {
|
||||
this.normalIcon = this.getFromCacheOrCreateImage('mo-tray-light-normal.png')
|
||||
}
|
||||
this.normalIcon = this.getFromCacheOrCreateImage('mo-tray-light-normal.png')
|
||||
}
|
||||
|
||||
loadImagesForWindows () {
|
||||
@@ -126,7 +112,7 @@ export default class TrayManager extends EventEmitter {
|
||||
}
|
||||
|
||||
file = nativeImage.createFromPath(join(__static, `./${key}`))
|
||||
file.setTemplateImage(this.bigSur)
|
||||
file.setTemplateImage(this.macOS)
|
||||
this.setCache(key, file)
|
||||
return file
|
||||
}
|
||||
@@ -163,10 +149,12 @@ export default class TrayManager extends EventEmitter {
|
||||
tray = new Tray(icon)
|
||||
// tray.setPressedImage(inverseIcon)
|
||||
|
||||
tray.setToolTip('Motrix')
|
||||
if (!this.macOS) {
|
||||
tray.setToolTip('Motrix')
|
||||
}
|
||||
}
|
||||
|
||||
handleEvents () {
|
||||
bindEvents () {
|
||||
// All OS
|
||||
tray.on('click', this.handleTrayClick)
|
||||
|
||||
@@ -182,6 +170,20 @@ export default class TrayManager extends EventEmitter {
|
||||
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) => {
|
||||
global.application.toggle()
|
||||
}
|
||||
@@ -225,7 +227,7 @@ export default class TrayManager extends EventEmitter {
|
||||
}
|
||||
|
||||
async renderTray () {
|
||||
if (this.speedometer) {
|
||||
if (!tray || this.speedometer) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -238,7 +240,7 @@ export default class TrayManager extends EventEmitter {
|
||||
}
|
||||
|
||||
getIcons () {
|
||||
if (this.bigSur) {
|
||||
if (this.macOS) {
|
||||
return { icon: this.normalIcon }
|
||||
}
|
||||
|
||||
@@ -264,7 +266,7 @@ export default class TrayManager extends EventEmitter {
|
||||
* Linux requires setContextMenu to be called
|
||||
* in order for the context menu to populate correctly
|
||||
*/
|
||||
if (process.platform !== 'linux') {
|
||||
if (!tray || process.platform !== 'linux') {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -295,6 +297,16 @@ export default class TrayManager extends EventEmitter {
|
||||
this.setupMenu()
|
||||
}
|
||||
|
||||
handleRunModeChange (mode) {
|
||||
this.runMode = mode
|
||||
|
||||
if (mode === APP_RUN_MODE.HIDE_TRAY) {
|
||||
this.destroy()
|
||||
} else {
|
||||
this.init()
|
||||
}
|
||||
}
|
||||
|
||||
handleSpeedometerEnableChange (enabled) {
|
||||
this.toggleSpeedometer(enabled)
|
||||
|
||||
@@ -332,26 +344,26 @@ export default class TrayManager extends EventEmitter {
|
||||
}
|
||||
|
||||
async updateTrayByImage (ab) {
|
||||
if (!tray) {
|
||||
return
|
||||
}
|
||||
|
||||
const buffer = convertArrayBufferToBuffer(ab)
|
||||
const image = nativeImage.createFromBuffer(buffer, {
|
||||
scaleFactor: 2
|
||||
})
|
||||
image.setTemplateImage(this.bigSur)
|
||||
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('mouse-down', this.handleTrayMouseDown)
|
||||
tray.removeListener('mouse-up', this.handleTrayMouseUp)
|
||||
|
||||
tray.removeListener('drop-files', this.handleTrayDropFiles)
|
||||
tray.removeListener('drop-text', this.handleTrayDropText)
|
||||
this.unbindEvents()
|
||||
}
|
||||
|
||||
tray.destroy()
|
||||
tray = null
|
||||
this.initialized = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +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
|
||||
}
|
||||
}
|
||||
|
||||
// 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()
|
||||
@@ -87,8 +101,7 @@ export default class WindowManager extends EventEmitter {
|
||||
contextIsolation: false,
|
||||
nodeIntegration: true,
|
||||
nodeIntegrationInWorker: true
|
||||
},
|
||||
hasShadow: !is.macOS()
|
||||
}
|
||||
})
|
||||
|
||||
const bounds = this.getPageBounds(page)
|
||||
@@ -96,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) {
|
||||
@@ -129,6 +146,7 @@ export default class WindowManager extends EventEmitter {
|
||||
if (autoHideWindow) {
|
||||
this.handleWindowBlur()
|
||||
}
|
||||
|
||||
return window
|
||||
}
|
||||
|
||||
@@ -200,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
|
||||
}
|
||||
|
||||
|
||||
+37
-2
@@ -8,8 +8,7 @@ import {
|
||||
ENGINE_MAX_CONNECTION_PER_SERVER,
|
||||
IP_VERSION
|
||||
} from '@shared/constants'
|
||||
|
||||
import engineBinMap from '../configs/engine'
|
||||
import { engineBinMap, engineArchMap } from '../configs/engine'
|
||||
|
||||
export function getLogPath () {
|
||||
return app.getPath('logs')
|
||||
@@ -41,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)) {
|
||||
|
||||
@@ -235,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) {
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 10 KiB |
@@ -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">
|
||||
@@ -54,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/">
|
||||
©2020 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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
<style lang="scss">
|
||||
.mo-speedometer {
|
||||
position: fixed;
|
||||
right: 36px;
|
||||
right: 16px;
|
||||
bottom: 24px;
|
||||
z-index: 20;
|
||||
}
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
<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>
|
||||
<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>
|
||||
@@ -31,7 +31,6 @@
|
||||
name: 'mo-dynamic-tray',
|
||||
computed: {
|
||||
...mapState('app', {
|
||||
bigSur: state => state.bigSur,
|
||||
iconStatus: state => state.stat.numActive > 0 ? 'active' : 'normal',
|
||||
theme: state => state.systemTheme,
|
||||
focused: state => state.trayFocused,
|
||||
|
||||
@@ -21,7 +21,8 @@
|
||||
downloadSpeed: state => state.stat.downloadSpeed,
|
||||
speed: state => state.stat.uploadSpeed + state.stat.downloadSpeed,
|
||||
interval: state => state.interval,
|
||||
downloading: state => state.stat.numActive > 0
|
||||
downloading: state => state.stat.numActive > 0,
|
||||
progress: state => state.progress
|
||||
}),
|
||||
...mapState('task', {
|
||||
messages: state => state.messages,
|
||||
@@ -50,10 +51,13 @@
|
||||
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}`)
|
||||
@@ -71,6 +75,8 @@
|
||||
|
||||
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)
|
||||
@@ -217,6 +223,7 @@
|
||||
},
|
||||
polling () {
|
||||
this.$store.dispatch('app/fetchGlobalStat')
|
||||
this.$store.dispatch('app/fetchProgress')
|
||||
this.$store.dispatch('task/fetchList')
|
||||
|
||||
if (this.taskDetailVisible && this.currentTaskGid) {
|
||||
|
||||
@@ -32,6 +32,3 @@
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
</style>
|
||||
|
||||
@@ -31,6 +31,3 @@
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
</style>
|
||||
|
||||
@@ -62,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"
|
||||
@@ -177,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"
|
||||
@@ -191,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"
|
||||
@@ -242,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">
|
||||
@@ -255,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>
|
||||
@@ -304,6 +368,30 @@
|
||||
/>
|
||||
</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') }}
|
||||
@@ -335,26 +423,29 @@
|
||||
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 { trackerSourceOptions, ENGINE_RPC_PORT, EMPTY_STRING, LOG_LEVELS } 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 initForm = (config) => {
|
||||
const {
|
||||
@@ -369,6 +460,7 @@
|
||||
lastCheckUpdateTime,
|
||||
lastSyncTrackerTime,
|
||||
listenPort,
|
||||
logLevel,
|
||||
noProxy,
|
||||
protocols,
|
||||
rpcListenPort,
|
||||
@@ -389,6 +481,7 @@
|
||||
lastCheckUpdateTime,
|
||||
lastSyncTrackerTime,
|
||||
listenPort,
|
||||
logLevel,
|
||||
noProxy: convertCommaToLine(noProxy),
|
||||
protocols: {
|
||||
...protocols
|
||||
@@ -410,8 +503,9 @@
|
||||
},
|
||||
data () {
|
||||
const { locale } = this.$store.state.preference.config
|
||||
const form = initForm(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,
|
||||
@@ -447,19 +541,33 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
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: {
|
||||
@@ -506,15 +614,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
|
||||
@@ -558,6 +676,10 @@
|
||||
.then((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) {
|
||||
@@ -567,23 +689,37 @@
|
||||
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.pauseMetadata = !btAutoDownloadContent
|
||||
data.followMetalink = btAutoDownloadContent
|
||||
data.followTorrent = 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)
|
||||
@@ -596,9 +732,19 @@
|
||||
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')
|
||||
}
|
||||
|
||||
if (checkIsNeedRestart(data)) {
|
||||
this.$electron.ipcRenderer.send('command', 'application:relaunch')
|
||||
@@ -609,6 +755,41 @@
|
||||
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) {
|
||||
const lng = getLanguage(backupConfig.locale)
|
||||
getLocaleManager().changeLanguage(lng)
|
||||
this.$electron.ipcRenderer.send('command',
|
||||
'application:change-locale', lng)
|
||||
}
|
||||
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">
|
||||
@@ -42,6 +43,11 @@
|
||||
{{ $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"
|
||||
@@ -106,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;">
|
||||
@@ -122,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">
|
||||
@@ -133,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">
|
||||
@@ -152,6 +187,20 @@
|
||||
{{ $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"
|
||||
@@ -183,8 +232,6 @@
|
||||
:label="$t('preferences.seed-time')">
|
||||
</el-input-number>
|
||||
</el-col>
|
||||
<div class="el-form-item__info" style="margin-top: 8px;">
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
:label="`${$t('preferences.task-manage')}: `"
|
||||
@@ -196,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>
|
||||
@@ -251,26 +298,36 @@
|
||||
|
||||
<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 {
|
||||
backupConfig,
|
||||
calcFormLabelWidth,
|
||||
changedConfig,
|
||||
checkIsNeedRestart,
|
||||
diffConfig
|
||||
convertLineToComma,
|
||||
diffConfig,
|
||||
extractSpeedUnit
|
||||
} from '@shared/utils'
|
||||
import { APP_RUN_MODE } from '@shared/constants'
|
||||
import { APP_RUN_MODE, ENGINE_MAX_CONCURRENT_DOWNLOADS } from '@shared/constants'
|
||||
import { reduceTrackerString } from '@shared/utils/tracker'
|
||||
|
||||
const initForm = (config) => {
|
||||
const {
|
||||
autoHideWindow,
|
||||
btSaveMetadata,
|
||||
btForceEncryption,
|
||||
dir,
|
||||
engineMaxConnectionPerServer,
|
||||
followMetalink,
|
||||
followTorrent,
|
||||
hideAppMenu,
|
||||
keepSeeding,
|
||||
keepWindowState,
|
||||
@@ -282,20 +339,26 @@
|
||||
newTaskShowDownloading,
|
||||
noConfirmBeforeDeleteTask,
|
||||
openAtLogin,
|
||||
pauseMetadata,
|
||||
resumeAllWhenAppLaunched,
|
||||
runMode,
|
||||
seedRatio,
|
||||
seedTime,
|
||||
showProgressBar,
|
||||
taskNotification,
|
||||
theme,
|
||||
traySpeedometer
|
||||
} = config
|
||||
const result = {
|
||||
autoHideWindow,
|
||||
btAutoDownloadContent: !pauseMetadata,
|
||||
btSaveMetadata,
|
||||
btForceEncryption,
|
||||
continue: config.continue,
|
||||
dir,
|
||||
engineMaxConnectionPerServer,
|
||||
followMetalink,
|
||||
followTorrent,
|
||||
hideAppMenu,
|
||||
keepSeeding,
|
||||
keepWindowState,
|
||||
@@ -311,6 +374,7 @@
|
||||
runMode,
|
||||
seedRatio,
|
||||
seedTime,
|
||||
showProgressBar,
|
||||
taskNotification,
|
||||
theme,
|
||||
traySpeedometer
|
||||
@@ -322,13 +386,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 = initForm(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,
|
||||
@@ -346,59 +419,78 @@
|
||||
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: '2 MB/s',
|
||||
value: '2M'
|
||||
},
|
||||
{
|
||||
label: '3 MB/s',
|
||||
value: '3M'
|
||||
},
|
||||
{
|
||||
label: '5 MB/s',
|
||||
value: '5M'
|
||||
},
|
||||
{
|
||||
label: '10 MB/s',
|
||||
value: '10M'
|
||||
},
|
||||
{
|
||||
label: '20 MB/s',
|
||||
value: '20M'
|
||||
label: 'MB/s',
|
||||
value: 'M'
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -440,10 +532,29 @@
|
||||
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
|
||||
},
|
||||
@@ -461,11 +572,27 @@
|
||||
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 } = data
|
||||
|
||||
if ('btAutoDownloadContent' in data) {
|
||||
data.pauseMetadata = !btAutoDownloadContent
|
||||
data.followMetalink = btAutoDownloadContent
|
||||
data.followTorrent = btAutoDownloadContent
|
||||
}
|
||||
|
||||
if (btTracker) {
|
||||
data.btTracker = reduceTrackerString(convertLineToComma(btTracker))
|
||||
}
|
||||
|
||||
if (noProxy) {
|
||||
data.noProxy = convertLineToComma(noProxy)
|
||||
}
|
||||
|
||||
console.log('[Motrix] preference changed data:', data)
|
||||
|
||||
this.$store.dispatch('preference/save', data)
|
||||
@@ -478,16 +605,11 @@
|
||||
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)
|
||||
}
|
||||
@@ -496,15 +618,50 @@
|
||||
this.$electron.ipcRenderer.send('command',
|
||||
'application:relaunch')
|
||||
}
|
||||
|
||||
if (checkIsNeedRestart(data)) {
|
||||
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,59 +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: sticky;
|
||||
bottom: 0;
|
||||
left: auto;
|
||||
z-index: 10;
|
||||
width: -webkit-fill-available;
|
||||
box-sizing: border-box;
|
||||
padding: 24px 36px 24px 0;
|
||||
.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>
|
||||
|
||||
@@ -19,8 +19,6 @@
|
||||
|
||||
export default {
|
||||
name: 'mo-theme-switcher',
|
||||
components: {
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
type: String,
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<em>{{ engineMode }}</em>
|
||||
</div>
|
||||
<div class="value" v-if="stat.numActive > 0">
|
||||
<em >{{ stat.uploadSpeed | bytesToSize }}/s</em>
|
||||
<em>{{ stat.uploadSpeed | bytesToSize }}/s</em>
|
||||
<span>{{ stat.downloadSpeed | bytesToSize }}/s</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -18,15 +18,11 @@
|
||||
|
||||
<script>
|
||||
import { mapState, mapActions } from 'vuex'
|
||||
import { bytesToSize } from '@shared/utils'
|
||||
import '@/components/Icons/speedometer'
|
||||
import {
|
||||
bytesToSize
|
||||
} from '@shared/utils'
|
||||
|
||||
export default {
|
||||
name: 'mo-speedometer',
|
||||
components: {
|
||||
},
|
||||
computed: {
|
||||
...mapState('app', [
|
||||
'stat'
|
||||
|
||||
@@ -62,6 +62,3 @@
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
</style>
|
||||
|
||||
@@ -62,6 +62,3 @@
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
</style>
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
custom-class="tab-title-dialog add-task-dialog"
|
||||
width="64vw"
|
||||
:visible.sync="visible"
|
||||
:before-close="handleClose"
|
||||
width="67vw"
|
||||
:visible="visible"
|
||||
:show-close="false"
|
||||
:before-close="beforeClose"
|
||||
@open="handleOpen"
|
||||
@opened="handleOpened"
|
||||
@closed="handleClosed"
|
||||
@@ -15,8 +16,8 @@
|
||||
<el-input
|
||||
ref="uri"
|
||||
type="textarea"
|
||||
:autosize="{ minRows: 3, maxRows: 5 }"
|
||||
auto-complete="off"
|
||||
:autosize="{ minRows: 3, maxRows: 5 }"
|
||||
:placeholder="$t('task.uri-task-tips')"
|
||||
@paste.native="handleUriPaste"
|
||||
v-model="form.uris"
|
||||
@@ -68,10 +69,14 @@
|
||||
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>
|
||||
</el-form-item>
|
||||
@@ -82,21 +87,34 @@
|
||||
>
|
||||
<el-input
|
||||
type="textarea"
|
||||
:autosize="{ minRows: 2, maxRows: 3 }"
|
||||
auto-complete="off"
|
||||
:autosize="{ minRows: 2, maxRows: 3 }"
|
||||
:placeholder="$t('task.task-user-agent')"
|
||||
v-model="form.userAgent"
|
||||
>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
:label="`${$t('task.task-authorization')}: `"
|
||||
:label-width="formLabelWidth"
|
||||
>
|
||||
<el-input
|
||||
type="textarea"
|
||||
auto-complete="off"
|
||||
:autosize="{ minRows: 2, maxRows: 3 }"
|
||||
:placeholder="$t('task.task-authorization')"
|
||||
v-model="form.authorization"
|
||||
>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
:label="`${$t('task.task-referer')}: `"
|
||||
:label-width="formLabelWidth"
|
||||
>
|
||||
<el-input
|
||||
type="textarea"
|
||||
:autosize="{ minRows: 2, maxRows: 3 }"
|
||||
auto-complete="off"
|
||||
:autosize="{ minRows: 2, maxRows: 3 }"
|
||||
:placeholder="$t('task.task-referer')"
|
||||
v-model="form.referer"
|
||||
>
|
||||
@@ -108,15 +126,15 @@
|
||||
>
|
||||
<el-input
|
||||
type="textarea"
|
||||
:autosize="{ minRows: 2, maxRows: 3 }"
|
||||
auto-complete="off"
|
||||
:autosize="{ minRows: 2, maxRows: 3 }"
|
||||
:placeholder="$t('task.task-cookie')"
|
||||
v-model="form.cookie"
|
||||
>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-row :gutter="12">
|
||||
<el-col :span="15" :xs="24">
|
||||
<el-col :span="16" :xs="24">
|
||||
<el-form-item
|
||||
:label="`${$t('task.task-proxy')}: `"
|
||||
:label-width="formLabelWidth"
|
||||
@@ -127,7 +145,7 @@
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="9" :xs="24">
|
||||
<el-col :span="8" :xs="24">
|
||||
<div class="help-link">
|
||||
<a target="_blank" href="https://github.com/agalwood/Motrix/wiki/Proxy" rel="noopener noreferrer">
|
||||
{{ $t('preferences.proxy-tips') }}
|
||||
@@ -143,6 +161,14 @@
|
||||
</el-form-item>
|
||||
</div>
|
||||
</el-form>
|
||||
<button
|
||||
slot="title"
|
||||
type="button"
|
||||
class="el-dialog__headerbtn"
|
||||
aria-label="Close"
|
||||
@click="handleClose">
|
||||
<i class="el-dialog__close el-icon el-icon-close"></i>
|
||||
</button>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-row>
|
||||
<el-col :span="9" :xs="9">
|
||||
@@ -170,6 +196,7 @@
|
||||
import is from 'electron-is'
|
||||
import { mapState } from 'vuex'
|
||||
import { isEmpty } from 'lodash'
|
||||
import HistoryDirectory from '@/components/Preference/HistoryDirectory'
|
||||
import SelectDirectory from '@/components/Native/SelectDirectory'
|
||||
import SelectTorrent from '@/components/Task/SelectTorrent'
|
||||
import {
|
||||
@@ -184,6 +211,7 @@
|
||||
export default {
|
||||
name: 'mo-add-task',
|
||||
components: {
|
||||
[HistoryDirectory.name]: HistoryDirectory,
|
||||
[SelectDirectory.name]: SelectDirectory,
|
||||
[SelectTorrent.name]: SelectTorrent
|
||||
},
|
||||
@@ -199,7 +227,7 @@
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
formLabelWidth: '100px',
|
||||
formLabelWidth: '110px',
|
||||
showAdvanced: false,
|
||||
form: {},
|
||||
rules: {}
|
||||
@@ -239,16 +267,22 @@
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
autofillResourceLink () {
|
||||
const content = this.$electron.clipboard.readText()
|
||||
async autofillResourceLink () {
|
||||
const content = await navigator.clipboard.readText()
|
||||
const hasResource = detectResource(content)
|
||||
if (!hasResource) {
|
||||
return
|
||||
}
|
||||
|
||||
if (isEmpty(this.form.uris)) {
|
||||
this.form.uris = content
|
||||
}
|
||||
},
|
||||
beforeClose () {
|
||||
if (isEmpty(this.form.uris) && isEmpty(this.form.torrent)) {
|
||||
this.handleClose()
|
||||
}
|
||||
},
|
||||
handleOpen () {
|
||||
this.form = initTaskForm(this.$store.state)
|
||||
if (this.taskType === ADD_TASK_TYPE.URI) {
|
||||
@@ -261,10 +295,10 @@
|
||||
handleOpened () {
|
||||
this.detectThunderResource(this.form.uris)
|
||||
},
|
||||
handleCancel (formName) {
|
||||
handleCancel () {
|
||||
this.$store.dispatch('app/hideAddTaskDialog')
|
||||
},
|
||||
handleClose (done) {
|
||||
handleClose () {
|
||||
this.$store.dispatch('app/hideAddTaskDialog')
|
||||
this.$store.dispatch('app/updateAddTaskOptions', {})
|
||||
},
|
||||
@@ -278,7 +312,7 @@
|
||||
this.submitForm('taskForm')
|
||||
}
|
||||
},
|
||||
handleTabClick (tab, event) {
|
||||
handleTabClick (tab) {
|
||||
this.$store.dispatch('app/changeAddTaskType', tab.name)
|
||||
},
|
||||
handleUriPaste () {
|
||||
@@ -300,9 +334,13 @@
|
||||
this.form.torrent = torrent
|
||||
this.form.selectFile = selectedFileIndex
|
||||
},
|
||||
onDirectorySelected (dir) {
|
||||
handleHistoryDirectorySelected (dir) {
|
||||
this.form.dir = dir
|
||||
},
|
||||
handleNativeDirectorySelected (dir) {
|
||||
this.form.dir = dir
|
||||
this.$store.dispatch('preference/recordHistoryDirectory', dir)
|
||||
},
|
||||
reset () {
|
||||
this.showAdvanced = false
|
||||
this.form = initTaskForm(this.$store.state)
|
||||
@@ -322,7 +360,7 @@
|
||||
} else if (type === 'metalink') {
|
||||
// @TODO addMetalink
|
||||
} else {
|
||||
console.error('addTask fail', form)
|
||||
console.error('[Motrix] Add task fail', form)
|
||||
}
|
||||
},
|
||||
submitForm (formName) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<el-container
|
||||
class="content panel"
|
||||
class="main panel"
|
||||
direction="horizontal"
|
||||
>
|
||||
<el-aside
|
||||
@@ -35,7 +35,6 @@
|
||||
<script>
|
||||
import { dialog } from '@electron/remote'
|
||||
import { mapState } from 'vuex'
|
||||
import * as clipboard from 'clipboard-polyfill'
|
||||
|
||||
import { commands } from '@/components/CommandManager/instance'
|
||||
import { ADD_TASK_TYPE } from '@shared/constants'
|
||||
@@ -371,7 +370,7 @@
|
||||
handleCopyTaskLink (payload) {
|
||||
const { task } = payload
|
||||
const uri = getTaskUri(task)
|
||||
clipboard.writeText(uri)
|
||||
navigator.clipboard.writeText(uri)
|
||||
.then(() => {
|
||||
this.$msg.success(this.$t('task.copy-link-success'))
|
||||
})
|
||||
@@ -410,6 +409,3 @@
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
</style>
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
import parseTorrent from 'parse-torrent'
|
||||
import { remote } from 'parse-torrent'
|
||||
import TaskFiles from '@/components/TaskDetail/TaskFiles'
|
||||
import '@/components/Icons/inbox'
|
||||
import {
|
||||
@@ -103,7 +103,7 @@
|
||||
return
|
||||
}
|
||||
|
||||
parseTorrent.remote(file.raw, (err, parsedTorrent) => {
|
||||
remote(file.raw, { timeout: 60 * 1000 }, (err, parsedTorrent) => {
|
||||
if (err) throw err
|
||||
console.log('[Motrix] parsed torrent: ', parsedTorrent)
|
||||
this.files = listTorrentFiles(parsedTorrent.files)
|
||||
|
||||
@@ -160,31 +160,31 @@
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.task-actions {
|
||||
position: absolute;
|
||||
top: 44px;
|
||||
right: 0;
|
||||
height: 24px;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
user-select: none;
|
||||
cursor: default;
|
||||
text-align: right;
|
||||
color: $--task-action-color;
|
||||
transition: all 0.25s;
|
||||
.task-action {
|
||||
display: inline-block;
|
||||
padding: 5px;
|
||||
margin: 0 4px;
|
||||
font-size: 0;
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
&:hover {
|
||||
color: $--task-action-hover-color;
|
||||
}
|
||||
&.disabled {
|
||||
color: $--task-action-disabled-color;
|
||||
}
|
||||
.task-actions {
|
||||
position: absolute;
|
||||
top: 44px;
|
||||
right: 0;
|
||||
height: 24px;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
user-select: none;
|
||||
cursor: default;
|
||||
text-align: right;
|
||||
color: $--task-action-color;
|
||||
transition: all 0.25s;
|
||||
.task-action {
|
||||
display: inline-block;
|
||||
padding: 5px;
|
||||
margin: 0 4px;
|
||||
font-size: 0;
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
&:hover {
|
||||
color: $--task-action-hover-color;
|
||||
}
|
||||
&.disabled {
|
||||
color: $--task-action-disabled-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<mo-task-progress
|
||||
:completed="Number(task.completedLength)"
|
||||
:total="Number(task.totalLength)"
|
||||
:status="task.status"
|
||||
:status="taskStatus"
|
||||
/>
|
||||
<mo-task-progress-info :task="task" />
|
||||
</div>
|
||||
@@ -16,7 +16,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getTaskName } from '@shared/utils'
|
||||
import { checkTaskIsSeeder, getTaskName } from '@shared/utils'
|
||||
import { TASK_STATUS } from '@shared/constants'
|
||||
import { openItem, getTaskFullPath } from '@/utils/native'
|
||||
import TaskItemActions from './TaskItemActions'
|
||||
@@ -46,6 +46,17 @@
|
||||
return getTaskName(this.task, {
|
||||
defaultName: this.$t('task.get-task-name')
|
||||
})
|
||||
},
|
||||
isSeeder () {
|
||||
return checkTaskIsSeeder(this.task)
|
||||
},
|
||||
taskStatus () {
|
||||
const { task, isSeeder } = this
|
||||
if (isSeeder) {
|
||||
return TASK_STATUS.SEEDING
|
||||
} else {
|
||||
return task.status
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@@ -75,41 +86,41 @@
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.task-item {
|
||||
position: relative;
|
||||
min-height: 88px;
|
||||
padding: 16px 12px;
|
||||
background-color: $--task-item-background;
|
||||
border: 1px solid $--task-item-border-color;
|
||||
border-radius: 6px;
|
||||
margin-bottom: 16px;
|
||||
transition: $--border-transition-base;
|
||||
&:hover {
|
||||
border-color: $--task-item-hover-border-color;
|
||||
}
|
||||
.task-item-actions {
|
||||
position: absolute;
|
||||
top: 16px;
|
||||
right: 12px;
|
||||
}
|
||||
}
|
||||
.selected .task-item {
|
||||
.task-item {
|
||||
position: relative;
|
||||
min-height: 78px;
|
||||
padding: 16px 12px;
|
||||
background-color: $--task-item-background;
|
||||
border: 1px solid $--task-item-border-color;
|
||||
border-radius: 6px;
|
||||
margin-bottom: 16px;
|
||||
transition: $--border-transition-base;
|
||||
&:hover {
|
||||
border-color: $--task-item-hover-border-color;
|
||||
}
|
||||
.task-name {
|
||||
color: #505753;
|
||||
margin-bottom: 32px;
|
||||
margin-right: 240px;
|
||||
word-break: break-all;
|
||||
min-height: 26px;
|
||||
&> span {
|
||||
font-size: 14px;
|
||||
line-height: 26px;
|
||||
overflow : hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
.task-item-actions {
|
||||
position: absolute;
|
||||
top: 16px;
|
||||
right: 12px;
|
||||
}
|
||||
}
|
||||
.selected .task-item {
|
||||
border-color: $--task-item-hover-border-color;
|
||||
}
|
||||
.task-name {
|
||||
color: #505753;
|
||||
margin-bottom: 1.5rem;
|
||||
margin-right: 200px;
|
||||
word-break: break-all;
|
||||
min-height: 26px;
|
||||
&> span {
|
||||
font-size: 14px;
|
||||
line-height: 26px;
|
||||
overflow : hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -28,9 +28,6 @@
|
||||
<i v-if="action ==='INFO'" @click.stop="onInfoClick">
|
||||
<mo-icon name="info-circle" width="14" height="14" />
|
||||
</i>
|
||||
<i v-if="action ==='MORE'" @click.stop="onMoreClick">
|
||||
<mo-icon name="more" width="14" height="14" />
|
||||
</i>
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
@@ -54,7 +51,6 @@
|
||||
import '@/components/Icons/folder'
|
||||
import '@/components/Icons/link'
|
||||
import '@/components/Icons/info-circle'
|
||||
import '@/components/Icons/more'
|
||||
import '@/components/Icons/trash'
|
||||
|
||||
const taskActionsMap = {
|
||||
@@ -187,8 +183,6 @@
|
||||
onInfoClick () {
|
||||
const { task } = this
|
||||
commands.emit('show-task-info', { task })
|
||||
},
|
||||
onMoreClick () {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,24 +68,24 @@
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.task-list {
|
||||
padding: 16px 0 64px 0;
|
||||
min-height: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.no-task {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
text-align: center;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 14px;
|
||||
color: #555;
|
||||
user-select: none;
|
||||
}
|
||||
.no-task-inner {
|
||||
width: 100%;
|
||||
padding-top: 360px;
|
||||
background: transparent url('~@/assets/no-task.svg') top center no-repeat;
|
||||
}
|
||||
.task-list {
|
||||
padding: 16px 16px 64px;
|
||||
min-height: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.no-task {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
text-align: center;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 14px;
|
||||
color: #555;
|
||||
user-select: none;
|
||||
}
|
||||
.no-task-inner {
|
||||
width: 100%;
|
||||
padding-top: 360px;
|
||||
background: transparent url('~@/assets/no-task.svg') top center no-repeat;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,42 +1,57 @@
|
||||
<template>
|
||||
<el-row class="task-progress-info">
|
||||
<el-col :span="8" class="task-progress-info-left">
|
||||
<el-col
|
||||
class="task-progress-info-left"
|
||||
:xs="12"
|
||||
:sm="7"
|
||||
:md="6"
|
||||
:lg="6"
|
||||
>
|
||||
<div v-if="task.completedLength > 0 || task.totalLength > 0">
|
||||
<span>{{ task.completedLength | bytesToSize }}</span>
|
||||
<span v-if="task.totalLength > 0"> / {{ task.totalLength | bytesToSize }}</span>
|
||||
<span>{{ task.completedLength | bytesToSize(2) }}</span>
|
||||
<span v-if="task.totalLength > 0"> / {{ task.totalLength | bytesToSize(2) }}</span>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="16" class="task-progress-info-right">
|
||||
<div class="task-speed-info" v-if="isActive">
|
||||
<span class="task-speed-text" v-if="isBT">
|
||||
<i><mo-icon name="arrow-up" width="10" height="10" /></i>
|
||||
<i>{{ task.uploadSpeed | bytesToSize }}/s</i>
|
||||
</span>
|
||||
<span class="task-speed-text">
|
||||
<i><mo-icon name="arrow-down" width="10" height="10" /></i>
|
||||
<i>{{ task.downloadSpeed | bytesToSize }}/s</i>
|
||||
</span>
|
||||
<span class="task-speed-text" v-if="remaining > 0">
|
||||
{{
|
||||
remaining | timeFormat({
|
||||
prefix: $t('task.remaining-prefix'),
|
||||
i18n: {
|
||||
'gt1d': $t('app.gt1d'),
|
||||
'hour': $t('app.hour'),
|
||||
'minute': $t('app.minute'),
|
||||
'second': $t('app.second')
|
||||
}
|
||||
})
|
||||
}}
|
||||
</span>
|
||||
<span class="task-speed-text" v-if="isBT">
|
||||
<i><mo-icon name="magnet" width="10" height="10" /></i>
|
||||
<i>{{ task.numSeeders }}</i>
|
||||
</span>
|
||||
<span class="task-speed-text">
|
||||
<i><mo-icon name="node" width="10" height="10" /></i>
|
||||
<i>{{ task.connections }}</i>
|
||||
</span>
|
||||
<el-col
|
||||
class="task-progress-info-right"
|
||||
v-if="isActive"
|
||||
:xs="12"
|
||||
:sm="17"
|
||||
:md="18"
|
||||
:lg="18"
|
||||
>
|
||||
<div class="task-speed-info">
|
||||
<div class="task-speed-text" v-if="isBT">
|
||||
<i><mo-icon name="arrow-up" width="10" height="14" /></i>
|
||||
<span>{{ task.uploadSpeed | bytesToSize }}/s</span>
|
||||
</div>
|
||||
<div class="task-speed-text">
|
||||
<i><mo-icon name="arrow-down" width="10" height="14" /></i>
|
||||
<span>{{ task.downloadSpeed | bytesToSize }}/s</span>
|
||||
</div>
|
||||
<div class="task-speed-text hidden-sm-and-down" v-if="remaining > 0">
|
||||
<span>
|
||||
{{
|
||||
remaining | timeFormat({
|
||||
prefix: $t('task.remaining-prefix'),
|
||||
i18n: {
|
||||
'gt1d': $t('app.gt1d'),
|
||||
'hour': $t('app.hour'),
|
||||
'minute': $t('app.minute'),
|
||||
'second': $t('app.second')
|
||||
}
|
||||
})
|
||||
}}
|
||||
</span>
|
||||
</div>
|
||||
<div class="task-speed-text hidden-sm-and-down" v-if="isBT">
|
||||
<i><mo-icon name="magnet" width="10" height="14" /></i>
|
||||
<span>{{ task.numSeeders }}</span>
|
||||
</div>
|
||||
<div class="task-speed-text hidden-sm-and-down">
|
||||
<i><mo-icon name="node" width="10" height="14" /></i>
|
||||
<span>{{ task.connections }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
@@ -87,29 +102,46 @@
|
||||
|
||||
<style lang="scss">
|
||||
.task-progress-info {
|
||||
font-size: 12px;
|
||||
line-height: 14px;
|
||||
min-height: 14px;
|
||||
font-size: 0.75rem;
|
||||
line-height: 0.875rem;
|
||||
min-height: 0.875rem;
|
||||
color: #9B9B9B;
|
||||
margin-top: 8px;
|
||||
margin-top: 0.5rem;
|
||||
i {
|
||||
font-style: normal;
|
||||
}
|
||||
}
|
||||
.task-progress-info-left {
|
||||
min-height: 14px;
|
||||
min-height: 0.875rem;
|
||||
text-align: left;
|
||||
}
|
||||
.task-progress-info-right {
|
||||
min-height: 14px;
|
||||
min-height: 0.875rem;
|
||||
text-align: right;
|
||||
}
|
||||
.task-speed-info {
|
||||
font-size: 0;
|
||||
& > .task-speed-text {
|
||||
margin-left: 8px;
|
||||
& > i {
|
||||
margin-left: 0.375rem;
|
||||
font-size: 0;
|
||||
line-height: 0.875rem;
|
||||
vertical-align: middle;
|
||||
display: inline-block;
|
||||
&:first-of-type {
|
||||
margin-left: 0;
|
||||
}
|
||||
& > i, & > span {
|
||||
height: 0.875rem;
|
||||
line-height: 0.875rem;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
& > i {
|
||||
margin-right: 0.125rem;
|
||||
}
|
||||
& > span {
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -6,7 +6,8 @@
|
||||
:title="$t('task.task-detail-title')"
|
||||
:with-header="true"
|
||||
:show-close="true"
|
||||
:visible.sync="visible"
|
||||
:destroy-on-close="true"
|
||||
:visible="visible"
|
||||
:before-close="handleClose"
|
||||
@closed="handleClosed"
|
||||
>
|
||||
@@ -44,7 +45,19 @@
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
<div class="task-detail-actions">
|
||||
<mo-task-item-actions mode="DETAIL" :task="task" />
|
||||
<div class="action-wrapper action-wrapper-left" v-if="optionsChanged">
|
||||
<el-button @click="resetChanged">
|
||||
{{$t('app.reset')}}
|
||||
</el-button>
|
||||
</div>
|
||||
<div class="action-wrapper action-wrapper-center">
|
||||
<mo-task-item-actions mode="DETAIL" :task="task" />
|
||||
</div>
|
||||
<div class="action-wrapper action-wrapper-right" v-if="optionsChanged">
|
||||
<el-button type="primary" @click="saveChanged">
|
||||
{{$t('app.save')}}
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-drawer>
|
||||
</template>
|
||||
@@ -59,7 +72,12 @@
|
||||
getFileName,
|
||||
getFileExtension
|
||||
} from '@shared/utils'
|
||||
import { EMPTY_STRING, TASK_STATUS } from '@shared/constants'
|
||||
import {
|
||||
EMPTY_STRING,
|
||||
NONE_SELECTED_FILES,
|
||||
SELECTED_ALL_FILES,
|
||||
TASK_STATUS
|
||||
} from '@shared/constants'
|
||||
import TaskItemActions from '@/components/Task/TaskItemActions'
|
||||
import TaskGeneral from './TaskGeneral'
|
||||
import TaskActivity from './TaskActivity'
|
||||
@@ -112,7 +130,10 @@
|
||||
formLabelWidth: calcFormLabelWidth(locale),
|
||||
locale,
|
||||
activeTab: 'general',
|
||||
graphicWidth: 0
|
||||
graphicWidth: 0,
|
||||
optionsChanged: false,
|
||||
filesSelection: EMPTY_STRING,
|
||||
selectionChangedCount: 0
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -141,13 +162,12 @@
|
||||
selected: item.selected === 'true',
|
||||
path: item.path,
|
||||
name,
|
||||
extension,
|
||||
length: item.length,
|
||||
extension: `.${extension}`,
|
||||
length: parseInt(item.length, 10),
|
||||
completedLength: item.completedLength
|
||||
}
|
||||
})
|
||||
merge(cached.files, result)
|
||||
|
||||
return cached.files
|
||||
},
|
||||
selectedFileList () {
|
||||
@@ -158,35 +178,42 @@
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
window.addEventListener('resize', debounce(() => {
|
||||
console.log('resize===>', this.activeTab, this.$refs.taskGraphic)
|
||||
if (this.activeTab === 'activity' && this.$refs.taskGraphic) {
|
||||
this.$refs.taskGraphic.updateGraphicWidth()
|
||||
}
|
||||
}, 300))
|
||||
window.addEventListener('resize', this.handleAppResize)
|
||||
},
|
||||
destroyed () {
|
||||
window.removeEventListener('resize', this.handleAppResize)
|
||||
cached.files = []
|
||||
window.removeEventListener('resize')
|
||||
},
|
||||
watch: {
|
||||
gid () {
|
||||
cached.files = []
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleClose (done) {
|
||||
window.removeEventListener('resize', this.handleAppResize)
|
||||
this.$store.dispatch('task/hideTaskDetail')
|
||||
},
|
||||
handleClosed (done) {
|
||||
this.$store.dispatch('task/updateCurrentTaskGid', EMPTY_STRING)
|
||||
this.$store.dispatch('task/updateCurrentTaskItem', null)
|
||||
this.optionsChanged = false
|
||||
this.resetFaskFilesSelection()
|
||||
},
|
||||
handleTabBeforeLeave (activeName, oldActiveName) {
|
||||
this.activeTab = activeName
|
||||
if (oldActiveName !== 'peers') {
|
||||
return
|
||||
this.optionsChanged = false
|
||||
switch (oldActiveName) {
|
||||
case 'peers':
|
||||
this.$store.dispatch('task/toggleEnabledFetchPeers', false)
|
||||
break
|
||||
case 'files':
|
||||
this.resetFaskFilesSelection()
|
||||
break
|
||||
}
|
||||
this.$store.dispatch('task/toggleEnabledFetchPeers', false)
|
||||
},
|
||||
handleTabClick (tab) {
|
||||
const { name } = tab
|
||||
|
||||
switch (name) {
|
||||
case 'peers':
|
||||
this.$store.dispatch('task/toggleEnabledFetchPeers', true)
|
||||
@@ -198,6 +225,33 @@
|
||||
break
|
||||
}
|
||||
},
|
||||
resetChanged () {
|
||||
const { activeTab } = this
|
||||
switch (activeTab) {
|
||||
case 'files':
|
||||
this.resetFaskFilesSelection()
|
||||
this.updateFilesListSelection()
|
||||
break
|
||||
}
|
||||
this.optionsChanged = false
|
||||
},
|
||||
saveChanged () {
|
||||
const { activeTab } = this
|
||||
switch (activeTab) {
|
||||
case 'files':
|
||||
this.saveFaskFilesSelection()
|
||||
break
|
||||
}
|
||||
this.optionsChanged = false
|
||||
},
|
||||
handleAppResize () {
|
||||
debounce(() => {
|
||||
console.log('resize===>', this.activeTab, this.$refs.taskGraphic)
|
||||
if (this.activeTab === 'activity' && this.$refs.taskGraphic) {
|
||||
this.$refs.taskGraphic.updateGraphicWidth()
|
||||
}
|
||||
}, 250)
|
||||
},
|
||||
updateFilesListSelection () {
|
||||
if (!this.$refs.detailFileList) {
|
||||
return
|
||||
@@ -207,7 +261,27 @@
|
||||
this.$refs.detailFileList.toggleSelection(selectedFileList)
|
||||
},
|
||||
handleSelectionChange (val) {
|
||||
console.log('task detail handleSelectionChange==>', val)
|
||||
this.filesSelection = val
|
||||
this.selectionChangedCount += 1
|
||||
if (this.selectionChangedCount > 1) {
|
||||
this.optionsChanged = true
|
||||
}
|
||||
},
|
||||
resetFaskFilesSelection () {
|
||||
this.filesSelection = EMPTY_STRING
|
||||
this.selectionChangedCount = 0
|
||||
},
|
||||
saveFaskFilesSelection () {
|
||||
const { gid, filesSelection } = this
|
||||
if (filesSelection === NONE_SELECTED_FILES) {
|
||||
this.$msg.warning(this.$t('task.select-at-least-one'))
|
||||
return
|
||||
}
|
||||
|
||||
const options = {
|
||||
selectFile: filesSelection !== SELECTED_ALL_FILES ? filesSelection : EMPTY_STRING
|
||||
}
|
||||
this.$store.dispatch('task/changeTaskOption', { gid, options })
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -215,11 +289,14 @@
|
||||
|
||||
<style lang="scss">
|
||||
.task-detail-drawer {
|
||||
min-width: 478px;
|
||||
.el-drawer__header {
|
||||
padding-top: 2rem;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.el-drawer__body {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
.task-detail-actions {
|
||||
position: sticky;
|
||||
@@ -229,6 +306,10 @@
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
font-size: 0;
|
||||
padding: 0 1.25rem;
|
||||
display: flex;
|
||||
align-content: space-between;
|
||||
justify-content: space-between;
|
||||
.task-item-actions {
|
||||
display: inline-block;
|
||||
&> .task-item-action {
|
||||
@@ -241,6 +322,21 @@
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
.action-wrapper {
|
||||
flex: 1;
|
||||
}
|
||||
.action-wrapper-left {
|
||||
text-align: left;
|
||||
}
|
||||
.action-wrapper-center {
|
||||
padding: 1px 0;
|
||||
&> .task-item-actions {
|
||||
margin: 0 auto;
|
||||
}
|
||||
}
|
||||
.action-wrapper-right {
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
|
||||
.task-detail-tab {
|
||||
@@ -265,4 +361,13 @@
|
||||
bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.tab-panel-actions {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
position: absolute;
|
||||
bottom: -28px;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user