This commit is contained in:
Daan Wijns 2020-10-18 12:12:16 +02:00 committed by GitHub
parent 403c7d5aea
commit af923e6648
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
38 changed files with 915 additions and 406 deletions

View file

@ -1,6 +1,6 @@
# VueTorrent # VueTorrent
The sleekest looking WEBUI for qBittorrent made with Vuejs! The sleekest looking WEBUI for qBittorrent made with Vuejs! Forked from [WDaan/VueTorrent](https://github.com/WDaan/VueTorrent)
> Vue, qBitorrent, Vuetify > Vue, qBitorrent, Vuetify
@ -8,22 +8,25 @@ The sleekest looking WEBUI for qBittorrent made with Vuejs!
<p align="center"> <p align="center">
<a href="https://imgur.com/kEqGvem.jng"><img src="https://imgur.com/kEqGvem.jpg" title="Desktop" alt="Desktop Screenshot" ></a> <a href="https://imgur.com/hpjuVYb.png"><img src="https://imgur.com/hpjuVYb.png" title="Desktop" alt="Desktop Screenshot" ></a>
</p> </p>
| | | | | | | |
| :------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------: | | :------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------: |
| <img width="1604" alt="screen shot 2017-08-07 at 12 18 15 pm" src="https://imgur.com/eN8qAM9.png"> | <img width="1604" alt="screen shot 2017-08-07 at 12 18 15 pm" src="https://imgur.com/T1A2Bng.png"> | <img width="1604" alt="screen shot 2017-08-07 at 12 18 15 pm" src="https://imgur.com/V83NMPg.png"> | | <img width="1604" alt="screen shot 2017-08-07 at 12 18 15 pm" src="https://imgur.com/Zcm98H3.png"> | <img width="1604" alt="screen shot 2017-08-07 at 12 18 15 pm" src="https://imgur.com/OujrH0f.png"> | <img width="1604" alt="screen shot 2017-08-07 at 12 18 15 pm" src="https://imgur.com/OkukwYY.png"> |
| <img width="1604" alt="screen shot 2017-015 pm" src="https://imgur.com/QYpNCXs.png"> | <img width="1604" alt="screen shot 2017-08-07 at 12 18 15 pm" src="https://imgur.com/6j5wxhl.png"> | <img width="1604" alt="screen shot 2017-08-07 at 12 18 15 pm" src="https://imgur.com/jnzDKjW.png"> |
<p align="center"> <p align="center">
<a href="https://imgur.com/kEqGvem.jpg"><img src="https://imgur.com/c5i63Yz.jpg" title="Desktop" width="300" alt="Mobile" > <a href="https://imgur.com/U3mes8r.png"><img src="https://imgur.com/U3mes8r.png" title="Desktop" width="300" alt="Mobile" >
</a> </a>
</p> </p>
## Installation ## Installation
- ### manual
- Visit the releases page! - Visit the releases page!
- Download the latest release.zip - Download the latest release.zip
@ -32,6 +35,12 @@ The sleekest looking WEBUI for qBittorrent made with Vuejs!
- Point your Alternate WEBUI location to the 'vuetorrent' folder - Point your Alternate WEBUI location to the 'vuetorrent' folder
- ### 'automatic'
- head over to the 'latest_release' branch
- clone it
- pull every once in a while
## Development ## Development
- clone the repo - clone the repo
@ -44,11 +53,15 @@ The sleekest looking WEBUI for qBittorrent made with Vuejs!
- viewing sessions status ( down / upload speed, session uploaded / downloaded ) - viewing sessions status ( down / upload speed, session uploaded / downloaded )
- adding / removing / pausing / resuming torrents - adding / removing / pausing / resuming / renaming torrents
- selectively downloading torrents
- choosing / renaming torrent files
- filtering powered by Fuse.js! - filtering powered by Fuse.js!
- mobile friendly! (maybe not for thousands of torrents...) - mobile friendly! (can be installed as a PWA)
- torrent info / trackers / peers / content - torrent info / trackers / peers / content
@ -62,20 +75,10 @@ The sleekest looking WEBUI for qBittorrent made with Vuejs!
## Contributing ## Contributing
I'll gladly accept help/pull requests & advice! (this is my first project of this nature, pls be kind 😛 ). I'll gladly accept help/pull requests & advice!
## FAQ
- **Why build this??**
* Why not? Most WebUI's look very dated and now it's no longer necessary to search for a remote control app!
## Support ## Support
Reach out to me at one of the following places!
- <a href="https://m.me/WijnsDaan" target="_blank">`Facebook Messenger`</a>
* Open up an issue 😛 * Open up an issue 😛
[<img src="https://cdn.buymeacoffee.com/buttons/lato-blue.png" alt="drawing" width="180"/>](https://www.buymeacoffee.com/wdaan 'Buy me a coffee') [<img src="https://cdn.buymeacoffee.com/buttons/lato-blue.png" alt="drawing" width="180"/>](https://www.buymeacoffee.com/wdaan 'Buy me a coffee')
@ -84,4 +87,6 @@ Reach out to me at one of the following places!
- Dashboard design heavily inspired by: '[Net Ninja - Vuetify](https://github.com/iamshaunjp/vuetify-playlist)'. - Dashboard design heavily inspired by: '[Net Ninja - Vuetify](https://github.com/iamshaunjp/vuetify-playlist)'.
* This repo '[CzBiX qb-web ](https://github.com/CzBiX/qb-web)' - This repo '[CzBiX qb-web ](https://github.com/CzBiX/qb-web)'
- Muertocaloh's [fork](https://github.com/muertocaloh/VueTorrent)

109
package-lock.json generated
View file

@ -1,6 +1,6 @@
{ {
"name": "vuetorrent", "name": "vuetorrent",
"version": "0.3.7", "version": "0.4.0",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {
@ -845,9 +845,9 @@
} }
}, },
"@babel/polyfill": { "@babel/polyfill": {
"version": "7.11.5", "version": "7.12.1",
"resolved": "https://registry.npmjs.org/@babel/polyfill/-/polyfill-7.11.5.tgz", "resolved": "https://registry.npmjs.org/@babel/polyfill/-/polyfill-7.12.1.tgz",
"integrity": "sha512-FunXnE0Sgpd61pKSj2OSOs1D44rKTD3pGOfGilZ6LGrrIH0QEtJlTjqOqdF8Bs98JmjfGhni2BBkTfv9KcKJ9g==", "integrity": "sha512-X0pi0V6gxLi6lFZpGmeNa4zxtwEmCs42isWLNjZZDE0Y8yVfgu0T2OAHlzBbdYlqbW/YXVvoBHpATEM+goCj8g==",
"requires": { "requires": {
"core-js": "^2.6.5", "core-js": "^2.6.5",
"regenerator-runtime": "^0.13.4" "regenerator-runtime": "^0.13.4"
@ -1414,20 +1414,20 @@
} }
}, },
"@vue/cli-plugin-pwa": { "@vue/cli-plugin-pwa": {
"version": "4.5.6", "version": "4.5.7",
"resolved": "https://registry.npmjs.org/@vue/cli-plugin-pwa/-/cli-plugin-pwa-4.5.6.tgz", "resolved": "https://registry.npmjs.org/@vue/cli-plugin-pwa/-/cli-plugin-pwa-4.5.7.tgz",
"integrity": "sha512-jMTBo9oR3mkwcqFbtgbKgfuYLZoivDoH5KEwqOkzqamuapOUazAbmlrad0XSF92MKcF8XxWrdZjsEsD/TshDPw==", "integrity": "sha512-mOaEgoLCT2yE8Pdvlz8LhXKqIs3w4xJjDr2dLrOrxh0+OhSpOHJdJ3yHswlgvkxgg0/FGS6t8haj0DfInQ+fYg==",
"dev": true, "dev": true,
"requires": { "requires": {
"@vue/cli-shared-utils": "^4.5.6", "@vue/cli-shared-utils": "^4.5.7",
"webpack": "^4.0.0", "webpack": "^4.0.0",
"workbox-webpack-plugin": "^4.3.1" "workbox-webpack-plugin": "^4.3.1"
}, },
"dependencies": { "dependencies": {
"@vue/cli-shared-utils": { "@vue/cli-shared-utils": {
"version": "4.5.6", "version": "4.5.7",
"resolved": "https://registry.npmjs.org/@vue/cli-shared-utils/-/cli-shared-utils-4.5.6.tgz", "resolved": "https://registry.npmjs.org/@vue/cli-shared-utils/-/cli-shared-utils-4.5.7.tgz",
"integrity": "sha512-p6ePDlEa7Xc0GEt99KDOCwPZtR7UnoEaZLMfwPYU5LAWkdCmtAw8HPAY/WWcjtoiaAkY4k9tz7ZehQasZ9mJxg==", "integrity": "sha512-oicFfx9PvgupxN/LW0s2ktdn1U6bBu8J4lPcW2xj6TtTWUkkxwzis4Tm+XOvgvZnu44+d7216y0Y4TX90q645w==",
"dev": true, "dev": true,
"requires": { "requires": {
"@hapi/joi": "^15.0.1", "@hapi/joi": "^15.0.1",
@ -2123,9 +2123,9 @@
} }
}, },
"apexcharts": { "apexcharts": {
"version": "3.21.0", "version": "3.22.0",
"resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-3.21.0.tgz", "resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-3.22.0.tgz",
"integrity": "sha512-yeulUZCTG57swbJ5oIJIjgfRdIsvmC/2WJanrZxNGhjtZf2B9NaT95pEtbrml1BILJKtMn4VbpXVZp+8Tzmydg==", "integrity": "sha512-DDh2eXnAEA8GoKU/hdicOaS2jzGehXwv8Bj1djYYudkeQzEdglFoWsVyIxff+Ds7+aUtVAJzd/9ythZuyyIbXQ==",
"requires": { "requires": {
"svg.draggable.js": "^2.2.2", "svg.draggable.js": "^2.2.2",
"svg.easing.js": "^2.0.0", "svg.easing.js": "^2.0.0",
@ -2584,6 +2584,16 @@
"integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==", "integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==",
"dev": true "dev": true
}, },
"bindings": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
"integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
"dev": true,
"optional": true,
"requires": {
"file-uri-to-path": "1.0.0"
}
},
"block-stream": { "block-stream": {
"version": "0.0.9", "version": "0.0.9",
"resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz", "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz",
@ -3128,9 +3138,9 @@
"dev": true "dev": true
}, },
"chokidar": { "chokidar": {
"version": "3.4.0", "version": "3.4.3",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.0.tgz", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.3.tgz",
"integrity": "sha512-aXAaho2VJtisB/1fg1+3nlLJqGOuewTzQpd/Tz0yTg2R0e4IGtshYvtjowyEumcBv2z+y4+kc75Mz7j5xJskcQ==", "integrity": "sha512-DtM3g7juCXQxFVSNPNByEC2+NImtBuxQQvWlHunpJIS5Ocr0lG306cC7FCi7cEA0fzmybPUIl4txBIobk1gGOQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"anymatch": "~3.1.1", "anymatch": "~3.1.1",
@ -3140,7 +3150,7 @@
"is-binary-path": "~2.1.0", "is-binary-path": "~2.1.0",
"is-glob": "~4.0.1", "is-glob": "~4.0.1",
"normalize-path": "~3.0.0", "normalize-path": "~3.0.0",
"readdirp": "~3.4.0" "readdirp": "~3.5.0"
}, },
"dependencies": { "dependencies": {
"braces": { "braces": {
@ -3167,6 +3177,15 @@
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
"dev": true "dev": true
}, },
"readdirp": {
"version": "3.5.0",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz",
"integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==",
"dev": true,
"requires": {
"picomatch": "^2.2.1"
}
},
"to-regex-range": { "to-regex-range": {
"version": "5.0.1", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
@ -4084,9 +4103,9 @@
} }
}, },
"dayjs": { "dayjs": {
"version": "1.9.1", "version": "1.9.3",
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.9.1.tgz", "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.9.3.tgz",
"integrity": "sha512-01NCTBg8cuMJG1OQc6PR7T66+AFYiPwgDvdJmvJBn29NGzIG+DIFxPLNjHzwz3cpFIvG+NcwIjP9hSaPVoOaDg==" "integrity": "sha512-V+1SyIvkS+HmNbN1G7A9+ERbFTV9KTXu6Oor98v2xHmzzpp52OIJhQuJSTywWuBY5pyAEmlwbCi1Me87n/SLOw=="
}, },
"de-indent": { "de-indent": {
"version": "1.0.2", "version": "1.0.2",
@ -5503,6 +5522,13 @@
"schema-utils": "^2.5.0" "schema-utils": "^2.5.0"
} }
}, },
"file-uri-to-path": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
"integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",
"dev": true,
"optional": true
},
"filesize": { "filesize": {
"version": "3.6.1", "version": "3.6.1",
"resolved": "https://registry.npmjs.org/filesize/-/filesize-3.6.1.tgz", "resolved": "https://registry.npmjs.org/filesize/-/filesize-3.6.1.tgz",
@ -7867,9 +7893,9 @@
} }
}, },
"node-forge": { "node-forge": {
"version": "0.9.0", "version": "0.10.0",
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.9.0.tgz", "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz",
"integrity": "sha512-7ASaDa3pD+lJ3WvXFsxekJQelBKRpne+GOVbLbtHYdd7pFspyeuJHnWfLplGf3SwKGbfs/aYl5V/JCIaHVUKKQ==", "integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==",
"dev": true "dev": true
}, },
"node-gyp": { "node-gyp": {
@ -9655,6 +9681,7 @@
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.4.0.tgz", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.4.0.tgz",
"integrity": "sha512-0xe001vZBnJEK+uKcj8qOhyAKPzIT+gStxWr3LCB0DwcXR5NZJ3IaC+yGnHCYzB/S7ov3m3EEbZI2zeNvX+hGQ==", "integrity": "sha512-0xe001vZBnJEK+uKcj8qOhyAKPzIT+gStxWr3LCB0DwcXR5NZJ3IaC+yGnHCYzB/S7ov3m3EEbZI2zeNvX+hGQ==",
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"picomatch": "^2.2.1" "picomatch": "^2.2.1"
} }
@ -10092,9 +10119,9 @@
"dev": true "dev": true
}, },
"sass": { "sass": {
"version": "1.26.11", "version": "1.27.0",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.26.11.tgz", "resolved": "https://registry.npmjs.org/sass/-/sass-1.27.0.tgz",
"integrity": "sha512-W1l/+vjGjIamsJ6OnTe0K37U2DBO/dgsv2Z4c89XQ8ZOO6l/VwkqwLSqoYzJeJs6CLuGSTRWc91GbQFL3lvrvw==", "integrity": "sha512-0gcrER56OkzotK/GGwgg4fPrKuiFlPNitO7eUJ18Bs+/NBlofJfMxmxqpqJxjae9vu0Wq8TZzrSyxZal00WDig==",
"dev": true, "dev": true,
"requires": { "requires": {
"chokidar": ">=2.0.0 <4.0.0" "chokidar": ">=2.0.0 <4.0.0"
@ -10303,12 +10330,12 @@
"dev": true "dev": true
}, },
"selfsigned": { "selfsigned": {
"version": "1.10.7", "version": "1.10.8",
"resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.7.tgz", "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.8.tgz",
"integrity": "sha512-8M3wBCzeWIJnQfl43IKwOmC4H/RAp50S8DF60znzjW5GVqTcSe2vWclt7hmYVPkKPlHWOu5EaWOMZ2Y6W8ZXTA==", "integrity": "sha512-2P4PtieJeEwVgTU9QEcwIRDQ/mXJLX8/+I3ur+Pg16nS8oNbrGxEso9NyYWy8NAmXiNl4dlAp5MwoNeCWzON4w==",
"dev": true, "dev": true,
"requires": { "requires": {
"node-forge": "0.9.0" "node-forge": "^0.10.0"
} }
}, },
"semver": { "semver": {
@ -11911,9 +11938,9 @@
"dev": true "dev": true
}, },
"uuid": { "uuid": {
"version": "8.3.0", "version": "8.3.1",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.0.tgz", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.1.tgz",
"integrity": "sha512-fX6Z5o4m6XsXBdli9g7DtWgAx+osMsRRZFKma1mIUsLCz6vRvv+pz5VNbyu9UEDzpMWulZfvpgb/cmDXVulYFQ==" "integrity": "sha512-FOmRr+FmWEIG8uhZv6C2bTgEVXsHk08kE7mPlrBbEe+c3r9pjceVPgupIfNIhc4yx55H69OXANrUaSuu9eInKg=="
}, },
"v8-compile-cache": { "v8-compile-cache": {
"version": "2.1.1", "version": "2.1.1",
@ -12063,9 +12090,9 @@
"integrity": "sha512-xo0CEVdkjSjhJoDdLSvoZoQrw/H2BlzB5jrCBKGZNXN2zdZgMuZ9BKrxXDjNP2AxlcCoKc8OahI3F3r3JGLv2Q==" "integrity": "sha512-xo0CEVdkjSjhJoDdLSvoZoQrw/H2BlzB5jrCBKGZNXN2zdZgMuZ9BKrxXDjNP2AxlcCoKc8OahI3F3r3JGLv2Q=="
}, },
"vue-router": { "vue-router": {
"version": "3.4.5", "version": "3.4.7",
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-3.4.5.tgz", "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-3.4.7.tgz",
"integrity": "sha512-ioRY5QyDpXM9TDjOX6hX79gtaMXSVDDzSlbIlyAmbHNteIL81WIVB2e+jbzV23vzxtoV0krdS2XHm+GxFg+Nxg==" "integrity": "sha512-CbHXue5BLrDivOk5O4eZ0WT4Yj8XwdXa4kCnsEIOzYUPF/07ZukayA2jGxDCJxLc9SgVQX9QX0OuGOwGlVB4Qg=="
}, },
"vue-style-loader": { "vue-style-loader": {
"version": "4.1.2", "version": "4.1.2",
@ -12117,9 +12144,9 @@
} }
}, },
"vuetify": { "vuetify": {
"version": "2.3.12", "version": "2.3.14",
"resolved": "https://registry.npmjs.org/vuetify/-/vuetify-2.3.12.tgz", "resolved": "https://registry.npmjs.org/vuetify/-/vuetify-2.3.14.tgz",
"integrity": "sha512-FSt1pzpf0/Lh0xuctAPB7RiLbUl7bzVc7ejbXLLhfmgm7zD7yabuhVYuyVda/SzokjZMGS3j1lNu2lLfdrz0oQ==" "integrity": "sha512-1Ys1MreJQOL/Ddp3YotBi1SlC2+1A0/RVkDXX3Azspt8incPdAnNB0JyChHiJ/TM+L+KSA7T4EXF9YDrCWENmg=="
}, },
"vuex": { "vuex": {
"version": "3.5.1", "version": "3.5.1",
@ -12283,6 +12310,7 @@
"dev": true, "dev": true,
"optional": true, "optional": true,
"requires": { "requires": {
"bindings": "^1.5.0",
"nan": "^2.12.1" "nan": "^2.12.1"
} }
}, },
@ -12591,6 +12619,7 @@
"dev": true, "dev": true,
"optional": true, "optional": true,
"requires": { "requires": {
"bindings": "^1.5.0",
"nan": "^2.12.1" "nan": "^2.12.1"
} }
}, },

View file

@ -1,6 +1,6 @@
{ {
"name": "vuetorrent", "name": "vuetorrent",
"version": "0.3.7", "version": "0.4.0",
"private": true, "private": true,
"scripts": { "scripts": {
"serve": "vue-cli-service serve", "serve": "vue-cli-service serve",
@ -8,30 +8,30 @@
"lint": "vue-cli-service lint" "lint": "vue-cli-service lint"
}, },
"dependencies": { "dependencies": {
"@babel/polyfill": "^7.11.5", "@babel/polyfill": "^7.12.1",
"apexcharts": "^3.21.0", "apexcharts": "^3.22.0",
"axios": "^0.19.2", "axios": "^0.19.2",
"core-js": "^3.6.4", "core-js": "^3.6.4",
"dayjs": "^1.9.1", "dayjs": "^1.9.3",
"fuse.js": "^6.4.1", "fuse.js": "^6.4.1",
"lodash": "^4.17.20", "lodash": "^4.17.20",
"register-service-worker": "^1.7.1", "register-service-worker": "^1.7.1",
"uuid": "^8.3.0", "uuid": "^8.3.1",
"vue": "^2.6.12", "vue": "^2.6.12",
"vue-apexcharts": "^1.6.0", "vue-apexcharts": "^1.6.0",
"vue-context": "^5.2.0", "vue-context": "^5.2.0",
"vue-observe-visibility": "^0.4.6", "vue-observe-visibility": "^0.4.6",
"vue-router": "^3.4.5", "vue-router": "^3.4.7",
"vue-toastification": "^1.7.8", "vue-toastification": "^1.7.8",
"vue2-perfect-scrollbar": "^1.5.0", "vue2-perfect-scrollbar": "^1.5.0",
"vuetify": "^2.3.12", "vuetify": "^2.3.14",
"vuex": "^3.5.1", "vuex": "^3.5.1",
"vuex-persist": "^3.1.3" "vuex-persist": "^3.1.3"
}, },
"devDependencies": { "devDependencies": {
"@vue/cli-plugin-babel": "~4.3.0", "@vue/cli-plugin-babel": "~4.3.0",
"@vue/cli-plugin-eslint": "~4.3.0", "@vue/cli-plugin-eslint": "~4.3.0",
"@vue/cli-plugin-pwa": "^4.5.6", "@vue/cli-plugin-pwa": "^4.5.7",
"@vue/cli-service": "~4.3.0", "@vue/cli-service": "~4.3.0",
"@vue/eslint-config-prettier": "^6.0.0", "@vue/eslint-config-prettier": "^6.0.0",
"babel-eslint": "^10.1.0", "babel-eslint": "^10.1.0",
@ -40,7 +40,7 @@
"eslint-plugin-vue": "^6.2.2", "eslint-plugin-vue": "^6.2.2",
"fibers": "^5.0.0", "fibers": "^5.0.0",
"node-sass": "^4.14.1", "node-sass": "^4.14.1",
"sass": "^1.26.11", "sass": "^1.27.0",
"sass-loader": "^8.0.2", "sass-loader": "^8.0.2",
"vue-cli-plugin-vuetify": "^2.0.7", "vue-cli-plugin-vuetify": "^2.0.7",
"vue-template-compiler": "^2.6.12" "vue-template-compiler": "^2.6.12"

View file

@ -4,7 +4,7 @@ build_and_copy(){
npm run build npm run build
VERSION=$(jq -r .version package.json) VERSION=$(jq -r .version package.json)
mkdir ../vuetorrent-release mkdir ../vuetorrent-release
git clone git@github.com:WDaan/VueTorrent.git ../vuetorrent-release git clone https://github.com/wdaan/VueTorrent.git ../vuetorrent-release
cd ../vuetorrent-release cd ../vuetorrent-release
git checkout latest-release git checkout latest-release
git pull git pull

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 7.3 KiB

View file

@ -28,7 +28,9 @@ export default {
methods: { methods: {
async checkAuthenticated() { async checkAuthenticated() {
const res = await qbit.login() const res = await qbit.login()
this.$store.commit('LOGIN', res === 'Ok.') const authenticated = res === 'Ok.'
this.$store.commit('LOGIN', authenticated)
if (!authenticated && this.$router.currentRoute.name !== 'login') this.$router.push('login')
} }
}, },
computed: { computed: {

View file

@ -1,4 +1,5 @@
@import '~vuetify/src/styles/styles.sass'; @import '~vuetify/src/styles/styles.sass';
@import '../styles/variables.scss';
@mixin dialog-title { @mixin dialog-title {
@include theme(v-card) using($material) { @include theme(v-card) using($material) {
@ -18,58 +19,58 @@
} }
.sideborder.done { .sideborder.done {
border-left: 4px solid #3cd1c2; border-left: 4px solid #{$torrent-done};
} }
.sideborder.busy { .sideborder.downloading {
border-left: 4px solid #ffaa2c; border-left: 4px solid #{$torrent-downloading};
} }
.sideborder.fail { .sideborder.fail {
border-left: 4px solid #f83e70; border-left: 4px solid #{$torrent-fail};
} }
.sideborder.paused { .sideborder.paused {
border-left: 4px solid #cfd8dc; border-left: 4px solid #{$torrent-paused};
} }
.sideborder.queued { .sideborder.queued {
border-left: 4px solid #2e5eaa; border-left: 4px solid #{$torrent-queued};
} }
.sideborder.seeding { .sideborder.seeding {
border-left: 4px solid #26a69a; border-left: 4px solid #{$torrent-seeding};
} }
.sideborder.checking { .sideborder.checking {
border-left: 4px solid #ff7043; border-left: 4px solid #{$torrent-checking};
} }
.sideborder.stalled { .sideborder.stalled {
border-left: 4px solid #81c784; border-left: 4px solid #{$torrent-stalled};
} }
.sideborder.metadata { .sideborder.metadata {
border-left: 4px solid #7e57c2; border-left: 4px solid #{$torrent-metadata};
} }
.v-chip.done { .v-chip.done {
background: #3cd1c2 !important; background: #{$torrent-done} !important;
} }
.v-chip.busy { .v-chip.downloading {
background: #ffaa2c !important; background: #{$torrent-downloading} !important;
} }
.v-chip.fail { .v-chip.fail {
background: #f83e70 !important; background: #{$torrent-fail}!important;
} }
.v-chip.paused { .v-chip.paused {
background: #cfd8dc !important; background: #{$torrent-paused} !important;
} }
.v-chip.queued { .v-chip.queued {
background: #2e5eaa !important; background: #{$torrent-queued} !important;
} }
.v-chip.seeding { .v-chip.seeding {
background: #26a69a !important; background: #{$torrent-seeding} !important;
} }
.v-chip.checking { .v-chip.checking {
background: #ff7043 !important; background: #{$torrent-checking} !important;
} }
.v-chip.stalled { .v-chip.stalled {
background: #81c784 !important; background: #{$torrent-stalled} !important;
} }
.v-chip.metadata { .v-chip.metadata {
background: #7e57c2 !important; background: #{$torrent-metadata} !important;
} }
.noselect { .noselect {
-webkit-touch-callout: none; /* iOS Safari */ -webkit-touch-callout: none; /* iOS Safari */

View file

@ -11,6 +11,7 @@
<v-row no-gutters> <v-row no-gutters>
<v-col ref="fileZone"> <v-col ref="fileZone">
<v-file-input <v-file-input
v-if="!url"
v-model="files" v-model="files"
color="deep-purple accent-4" color="deep-purple accent-4"
counter counter
@ -44,6 +45,7 @@
</template> </template>
</v-file-input> </v-file-input>
<v-text-field <v-text-field
v-if="files.length == 0"
label="URL" label="URL"
prepend-icon="mdi-link" prepend-icon="mdi-link"
:rows=" :rows="
@ -62,19 +64,29 @@
clearable clearable
label="Category" label="Category"
prepend-icon="tag" prepend-icon="tag"
@input="categoryChanged"
></v-combobox> ></v-combobox>
<v-text-field <v-text-field
:disabled="autoTMM"
v-model="directory" v-model="directory"
:placeholder="savepath"
label="Download Directory" label="Download Directory"
prepend-icon="folder" prepend-icon="folder"
></v-text-field> ></v-text-field>
<v-row no-gutters>
<v-flex xs12 sm6>
<v-checkbox
v-model="autoTMM"
label="Automatic Torrent Management"
></v-checkbox>
</v-flex>
<v-flex xs12 sm6>
<v-checkbox <v-checkbox
v-model="skip_checking" v-model="skip_checking"
label="Skip hash check" label="Skip hash check"
></v-checkbox> ></v-checkbox>
</v-flex>
</v-row>
</v-container> </v-container>
</v-form> </v-form>
</v-card-text> </v-card-text>
@ -108,6 +120,7 @@ export default {
files: [], files: [],
category: null, category: null,
directory: '', directory: '',
autoTMM: true,
skip_checking: false, skip_checking: false,
inputRules: [ inputRules: [
v => v =>
@ -125,14 +138,11 @@ export default {
submit() { submit() {
if (this.files.length || this.url) { if (this.files.length || this.url) {
let torrents = [] let torrents = []
let params = { urls: null, autoTMM: true } let params = { urls: null, autoTMM: this.autoTMM }
if (this.files.length) torrents.push(...this.files) if (this.files.length) torrents.push(...this.files)
if (this.url) params.urls = this.url if (this.url) params.urls = this.url
if (this.category) params.category = this.category if (this.category) params.category = this.category
if (this.directory) { if (!this.autoTMM) params.savepath = this.directory
params.savepath = this.directory
params.autoTMM = false
}
if (this.skip_checking) params.skip_checking = this.skip_checking if (this.skip_checking) params.skip_checking = this.skip_checking
qbit.addTorrents(params, torrents) qbit.addTorrents(params, torrents)
@ -142,6 +152,9 @@ export default {
this.$store.commit('DELETE_MODAL', this.guid) this.$store.commit('DELETE_MODAL', this.guid)
} }
}, },
categoryChanged() {
if (this.autoTMM) this.directory = this.savepath
},
resetForm() { resetForm() {
this.url = null this.url = null
this.files = [] this.files = []
@ -162,8 +175,8 @@ export default {
let savePath = this.getSettings().save_path let savePath = this.getSettings().save_path
if (this.category) { if (this.category) {
savePath += this.category savePath += this.category
let category_path = this.getCategories()[this.category].savePath let category = this.getCategories()[this.category]
if (category_path) savePath = category_path if (category && category.savePath) savePath = category.savePath
} }
return savePath return savePath
}, },
@ -174,6 +187,7 @@ export default {
created() { created() {
this.$store.commit('FETCH_SETTINGS') this.$store.commit('FETCH_SETTINGS')
this.$store.commit('FETCH_CATEGORIES') this.$store.commit('FETCH_CATEGORIES')
this.directory = this.savepath
} }
} }
</script> </script>

View file

@ -0,0 +1,88 @@
<template>
<v-dialog
v-model="dialog"
scrollable
:width="dialogWidth"
:fullscreen="phoneLayout"
>
<v-card style="overflow: hidden !important">
<v-container :style="{ height: phoneLayout ? '100vh' : '' }">
<v-card-title class="pb-0 justify-center">
<h2>Change Torrent Location</h2>
</v-card-title>
<v-card-text>
<div>
<v-container>
<v-row>
<v-col>
<v-text-field
label="Torrent Name"
prepend-icon="insert_drive_file"
readonly
v-model="torrent.name"
/>
<v-text-field
label="Directory"
prepend-icon="folder"
v-model="newPath"
v-on:keydown.enter="setLocation"
/>
</v-col>
</v-row>
</v-container>
</div>
</v-card-text>
<div>
<v-card-actions class="justify-center">
<v-btn color="success" @click="setLocation">Save</v-btn>
</v-card-actions>
</div>
</v-container>
<v-fab-transition v-if="phoneLayout">
<v-btn @click="close" color="red" dark absolute bottom right>
<v-icon>close</v-icon>
</v-btn>
</v-fab-transition>
</v-card>
</v-dialog>
</template>
<script>
import { mapGetters } from 'vuex'
import { Modal, FullScreenModal } from '@/mixins'
import qbit from '@/services/qbit'
export default {
name: 'ChangeLocationModal',
mixins: [Modal, FullScreenModal],
props: {
hash: String
},
data() {
return {
newPath: ''
}
},
computed: {
...mapGetters(['getTorrent']),
dialogWidth() {
return this.phoneLayout ? '100%' : '750px'
},
torrent() {
return this.getTorrent(this.hash)
}
},
methods: {
setLocation() {
qbit.setTorrentLocation([this.hash], this.newPath)
this.close()
},
close() {
this.$store.commit('DELETE_MODAL', this.guid)
}
},
created() {
this.newPath = this.torrent.savePath
}
}
</script>

View file

@ -10,7 +10,7 @@
<v-card-title class="pb-0 justify-center primary"> <v-card-title class="pb-0 justify-center primary">
<h2 class="white--text">Settings</h2> <h2 class="white--text">Settings</h2>
</v-card-title> </v-card-title>
<v-tabs v-model="tab" background-color="primary" center-active> <v-tabs v-model="tab" background-color="primary" dark fixed-tabs>
<v-tab href="#downloads">Downloads</v-tab> <v-tab href="#downloads">Downloads</v-tab>
<v-tab href="#bittorrent">BitTorrent</v-tab> <v-tab href="#bittorrent">BitTorrent</v-tab>
<v-tab href="#webui">WebUI</v-tab> <v-tab href="#webui">WebUI</v-tab>

View file

@ -51,6 +51,29 @@
Global Remove/Resume/Pause Buttons</template Global Remove/Resume/Pause Buttons</template
> >
</v-switch> </v-switch>
<v-switch
class="v-input--reverse v-input--expand pa-0 ma-0"
inset
v-model="denseDashboard"
color="green_accent"
>
<template #label>
Dense version of the dasbhoard</template
>
</v-switch>
<v-row dense>
<v-col cols="10" sm="10" md="10">
<p class="subtitle-1">Pagination size:</p>
</v-col>
<v-col cols="2" sm="2" md="2">
<v-select
class="pa-0 ma-0"
color="green_accent"
:items="paginationSizes"
v-model="paginationSize"
></v-select>
</v-col>
</v-row>
<v-row dense> <v-row dense>
<v-col cols="10" sm="10" md="11"> <v-col cols="10" sm="10" md="11">
<p class="subtitle-1">Current Version:</p> <p class="subtitle-1">Current Version:</p>
@ -71,6 +94,11 @@
import { mapState, mapGetters } from 'vuex' import { mapState, mapGetters } from 'vuex'
export default { export default {
name: 'VueTorrent', name: 'VueTorrent',
data() {
return {
paginationSizes: [5, 15, 30, 50]
}
},
computed: { computed: {
...mapState(['webuiSettings']), ...mapState(['webuiSettings']),
...mapGetters(['getAppVersion']), ...mapGetters(['getAppVersion']),
@ -114,6 +142,22 @@ export default {
this.webuiSettings.showGlobalRemoveResumePause = val this.webuiSettings.showGlobalRemoveResumePause = val
} }
}, },
denseDashboard: {
get() {
return this.webuiSettings.denseDashboard
},
set(val) {
this.webuiSettings.denseDashboard = val
}
},
paginationSize: {
get() {
return this.webuiSettings.paginationSize
},
set(val) {
this.webuiSettings.paginationSize = val
}
},
version() { version() {
return this.getAppVersion() return this.getAppVersion()
} }

View file

@ -46,10 +46,10 @@ export default {
mixins: [Modal], mixins: [Modal],
data() { data() {
return { return {
sortProperty: { value: '', name: 'Default' }, sortProperty: { value: 'added_on', name: 'Default' },
reverse: false, reverse: true,
options: [ options: [
{ value: 'default', name: 'Default' }, { value: 'added_on', name: 'Default' },
{ value: 'availability', name: 'Availability' }, { value: 'availability', name: 'Availability' },
{ value: 'category', name: 'Category' }, { value: 'category', name: 'Category' },
{ value: 'completed', name: 'Completed' }, { value: 'completed', name: 'Completed' },

View file

@ -3,7 +3,7 @@
<v-card> <v-card>
<v-container style="min-height: 200px" :class="`pa-0 project done`"> <v-container style="min-height: 200px" :class="`pa-0 project done`">
<v-card-title class="justify-center"> <v-card-title class="justify-center">
<h2>Create New Category</h2> <h2>{{ hasInitialCategory ? 'Edit' : 'Create New' }} Category</h2>
</v-card-title> </v-card-title>
<v-form ref="categoryForm" class="px-6 mt-3"> <v-form ref="categoryForm" class="px-6 mt-3">
@ -69,8 +69,7 @@ export default {
hasInitialCategory() { hasInitialCategory() {
return ( return (
this.initialCategory && this.initialCategory &&
this.initialCategory.name && this.initialCategory.name
this.initialCategory.savePath
) )
} }
}, },

View file

@ -3,11 +3,13 @@
<perfect-scrollbar> <perfect-scrollbar>
<v-card-text style="max-height: 500px; min-height: 400px"> <v-card-text style="max-height: 500px; min-height: 400px">
<v-treeview <v-treeview
v-model="tree" v-model="selected"
:items="fileTree" :items="fileTree"
:open.sync="opened"
activatable activatable
item-key="name" selectable
open-on-click item-key="fullName"
open-all
> >
<template v-slot:prepend="{ item, open }"> <template v-slot:prepend="{ item, open }">
<v-icon v-if="!item.icon"> <v-icon v-if="!item.icon">
@ -15,6 +17,14 @@
</v-icon> </v-icon>
<v-icon v-else>{{ item.icon }}</v-icon> <v-icon v-else>{{ item.icon }}</v-icon>
</template> </template>
<template v-slot:label="{ item }">
<span v-if="!item.editing">{{item.name}}</span>
<v-text-field
autofocus
v-if="item.editing"
v-model="item.newName"
/>
</template>
<template v-slot:append="{ item }"> <template v-slot:append="{ item }">
<span v-if="!item.icon" <span v-if="!item.icon"
>{{ item.children.length }} Files</span >{{ item.children.length }} Files</span
@ -22,6 +32,33 @@
<div v-else> <div v-else>
<span>[{{ item.size }}]</span> <span>[{{ item.size }}]</span>
<span class="ml-4">{{ item.progress }}%</span> <span class="ml-4">{{ item.progress }}%</span>
<v-btn
v-if="!item.editing"
class="mb-2 ml-4"
x-small
fab
@click="edit(item)"
>
<v-icon>mdi-pencil</v-icon>
</v-btn>
<v-btn
v-if="item.editing"
class="mb-2 ml-4"
x-small
fab
@click="renameFile(item)"
>
<v-icon>save</v-icon>
</v-btn>
<v-btn
v-if="item.editing"
class="mb-2 ml-2"
x-small
fab
@click="togleEditing(item)"
>
<v-icon>close</v-icon>
</v-btn>
</div> </div>
</template> </template>
</v-treeview> </v-treeview>
@ -41,7 +78,8 @@ export default {
}, },
data() { data() {
return { return {
tree: [], opened: null,
selected: [],
treeData: null treeData: null
} }
}, },
@ -56,7 +94,42 @@ export default {
methods: { methods: {
async getTorrentFiles() { async getTorrentFiles() {
const { data } = await qbit.getTorrentFiles(this.hash) const { data } = await qbit.getTorrentFiles(this.hash)
data.forEach((d, i) => {
d.id = i
d.name = d.name.replace('.unwanted/', '')
})
this.treeData = data this.treeData = data
},
async changeFilePriorities(newValue, oldValue) {
if (newValue.length == oldValue.length) return
const filesToExclude = oldValue.filter(f => !newValue.includes(f))
.map(name => this.treeData.find(f => f.name === name))
.filter(f => f.priority !== 0)
.map(f => f.id)
const filesToInclude = newValue.filter(f => !oldValue.includes(f))
.map(name => this.treeData.find(f => f.name === name))
.filter(f => f.priority === 0)
.map(f => f.id)
if (filesToExclude.length)
await qbit.setTorrentFilePriority(this.hash, filesToExclude, 0)
if (filesToInclude.length)
await qbit.setTorrentFilePriority(this.hash, filesToInclude, 1)
if (filesToExclude.length || filesToInclude.length)
await this.getTorrentFiles()
},
togleEditing(item) {
item.editing = !item.editing
},
edit(item){
item.newName = item.name
this.togleEditing(item)
},
renameFile(item) {
qbit.renameFile(this.hash, item.id, item.newName)
item.name = item.newName
this.togleEditing(item)
} }
}, },
watch: { watch: {
@ -64,10 +137,21 @@ export default {
if (active) { if (active) {
this.getTorrentFiles() this.getTorrentFiles()
} }
},
selected(newValue, oldValue) {
this.changeFilePriorities(newValue, oldValue)
} }
}, },
created() { created() {
this.getTorrentFiles() this.getTorrentFiles().then(() => {
this.opened = [].concat(
...this.treeData.map(file => file.name.split('/'))
.filter(f => f.splice(-1, 1)))
.filter((f, index, self) => index === self.indexOf(f)
)
this.selected = this.treeData.filter(file => file.priority !== 0)
.map(file => file.name)
})
} }
} }
</script> </script>

View file

@ -2,8 +2,8 @@
<v-card flat> <v-card flat>
<perfect-scrollbar> <perfect-scrollbar>
<v-card-text <v-card-text
class="pa-0" style="font-size: 1.1em; min-height: 400px;"
style="font-size: 1.1em; max-height: 500px; min-height: 400px" :style="{ maxHeight: phoneLayout ? '' : '500px' }"
> >
<v-simple-table> <v-simple-table>
<tbody> <tbody>
@ -13,6 +13,12 @@
{{ torrent.name }} {{ torrent.name }}
</td> </td>
</tr> </tr>
<tr>
<td class="grey--text">Directory</td>
<td class="torrentmodaltext--text">
{{ torrent.savePath }}
</td>
</tr>
<tr style="margin-top: 10px !important"> <tr style="margin-top: 10px !important">
<td class="grey--text">hash</td> <td class="grey--text">hash</td>
<td class="torrentmodaltext--text"> <td class="torrentmodaltext--text">
@ -32,13 +38,25 @@
</td> </td>
</tr> </tr>
<tr> <tr>
<td class="grey--text">Download</td> <td class="grey--text">Uploaded:</td>
<td class="torrentmodaltext--text">
{{ torrent.uploaded }}
</td>
</tr>
<tr>
<td class="grey--text">Ratio</td>
<td class="torrentmodaltext--text">
{{ torrent.ratio }}
</td>
</tr>
<tr>
<td class="grey--text">Download Speed</td>
<td class="torrentmodaltext--text"> <td class="torrentmodaltext--text">
{{ torrent.dlspeed }} {{ torrent.dlspeed }}
</td> </td>
</tr> </tr>
<tr> <tr>
<td class="grey--text">Upload</td> <td class="grey--text">Upload Speed</td>
<td class="torrentmodaltext--text"> <td class="torrentmodaltext--text">
{{ torrent.upspeed }} {{ torrent.upspeed }}
</td> </td>
@ -68,39 +86,21 @@
</td> </td>
</tr> </tr>
<tr> <tr>
<td class="grey--text">Ratio</td> <td class="grey--text">Added on</td>
<td class="torrentmodaltext--text"> <td class="torrentmodaltext--text">
{{ torrent.ratio }}% {{ torrent.added_on }}
</td> </td>
</tr> </tr>
<tr>
<td class="grey--text">Tags</td>
<td v-if="torrent.tags">
{{ torrent.tags.join(',') }}
</td>
<td v-else>None</td>
</tr>
<tr> <tr>
<td class="grey--text">Status</td> <td class="grey--text">Status</td>
<v-chip <v-chip
small small
:class="`${torrent.state} white--text my-2 caption`" :class="`${torrent.state.toLowerCase()} white--text my-2 caption`"
>{{ torrent.state }}</v-chip >{{ torrent.state }}</v-chip
> >
</tr> </tr>
</tbody> </tbody>
</v-simple-table> </v-simple-table>
<v-flex class="pt-3 pb-4">
<v-progress-linear
height="5"
stream
rounded
color="cyan darken-1"
background-color="cyan lighten-3"
:buffer-value="torrent.progress"
></v-progress-linear>
</v-flex>
</v-card-text> </v-card-text>
</perfect-scrollbar> </perfect-scrollbar>
</v-card> </v-card>
@ -117,6 +117,9 @@ export default {
...mapGetters(['getTorrent']), ...mapGetters(['getTorrent']),
torrent() { torrent() {
return this.getTorrent(this.hash) return this.getTorrent(this.hash)
},
phoneLayout() {
return this.$vuetify.breakpoint.xsOnly
} }
} }
} }

View file

@ -16,7 +16,7 @@
<v-card-title class="pb-0 justify-center primary"> <v-card-title class="pb-0 justify-center primary">
<h2 class="white--text">Torrent Detail</h2> <h2 class="white--text">Torrent Detail</h2>
</v-card-title> </v-card-title>
<v-tabs v-model="tab" background-color="primary" center-active> <v-tabs v-model="tab" background-color="primary" dark fixed-tabs>
<v-tab href="#info">Info</v-tab> <v-tab href="#info">Info</v-tab>
<v-tab href="#trackers">Trackers</v-tab> <v-tab href="#trackers">Trackers</v-tab>
<v-tab href="#peers">Peers</v-tab> <v-tab href="#peers">Peers</v-tab>

View file

@ -92,7 +92,7 @@ export default {
}, },
data() { data() {
return { return {
drawer: false drawer: this.$vuetify.breakpoint.mdAndUp
} }
}, },

View file

@ -93,6 +93,7 @@ export default {
}, },
removeTorrents() { removeTorrents() {
qbit.deleteTorrents(this.selected_torrents, false) qbit.deleteTorrents(this.selected_torrents, false)
this.$store.commit('RESET_SELECTED')
}, },
addModal(name) { addModal(name) {
this.createModal(name) this.createModal(name)

View file

@ -7,14 +7,12 @@
@click.native="selectTorrent(torrent.hash)" @click.native="selectTorrent(torrent.hash)"
@dblclick.prevent="showInfo(torrent.hash)" @dblclick.prevent="showInfo(torrent.hash)"
> >
<v-tooltip top> <v-layout row wrap :class="style">
<template v-slot:activator="{ on }"> <v-flex xs12 class="mb-4">
<v-layout v-on="on" row wrap :class="style">
<v-flex xs12 sm2 md3>
<div class="caption grey--text">Torrent title</div> <div class="caption grey--text">Torrent title</div>
<div class="truncate">{{ torrent.name }}</div> <div class="truncate">{{ torrent.name }}</div>
</v-flex> </v-flex>
<v-flex xs6 sm1 md1 class="mr-2"> <v-flex xs6 sm1 md1>
<div class="caption grey--text">Size</div> <div class="caption grey--text">Size</div>
<div> <div>
{{ torrent.size | getDataValue }} {{ torrent.size | getDataValue }}
@ -23,16 +21,33 @@
}}</span> }}</span>
</div> </div>
</v-flex> </v-flex>
<v-flex xs5 sm1 md1 class="mr-2"> <v-flex xs12 sm1 md1 class="mr-4">
<div class="caption grey--text">Done</div> <div class="caption grey--text">Done</div>
<v-progress-linear
v-model="torrent.progress"
height="20"
:style="phoneLayout ? '' : 'width: 80%;'"
:color="`torrent-${state}-color`" >
<span
class="caption"
>
{{ torrent.progress }}%
</span>
</v-progress-linear>
</v-flex>
<v-flex xs6 sm1 md1>
<div class="caption grey--text">Ratio</div>
<div> <div>
{{ torrent.dloaded | getDataValue }} {{ torrent.ratio }}
<span class="caption grey--text">{{
torrent.dloaded | getDataUnit
}}</span>
</div> </div>
</v-flex> </v-flex>
<v-flex xs6 sm1 md1 class="mr-2"> <v-flex xs6 sm1 md1>
<div class="caption grey--text">ETA</div>
<div>
{{ torrent.eta }}
</div>
</v-flex>
<v-flex xs6 sm1 md1>
<div class="caption grey--text">Download</div> <div class="caption grey--text">Download</div>
<div> <div>
{{ torrent.dlspeed | getDataValue }} {{ torrent.dlspeed | getDataValue }}
@ -41,7 +56,7 @@
}}</span> }}</span>
</div> </div>
</v-flex> </v-flex>
<v-flex xs5 sm1 md1 class="mr-2"> <v-flex xs6 sm1 md1>
<div class="caption grey--text">Upload</div> <div class="caption grey--text">Upload</div>
<div> <div>
{{ torrent.upspeed | getDataValue }} {{ torrent.upspeed | getDataValue }}
@ -50,13 +65,7 @@
}}</span> }}</span>
</div> </div>
</v-flex> </v-flex>
<v-flex xs6 sm1 md1 class="mr-2"> <v-flex xs6 sm1 md1>
<div class="caption grey--text">ETA</div>
<div>
{{ torrent.eta | formatEta({ dayLimit: 100 }) }}
</div>
</v-flex>
<v-flex xs5 sm1 md1 class="mr-2">
<div class="caption grey--text">Peers</div> <div class="caption grey--text">Peers</div>
<div> <div>
{{ torrent.num_leechs }} {{ torrent.num_leechs }}
@ -65,7 +74,7 @@
> >
</div> </div>
</v-flex> </v-flex>
<v-flex xs5 sm1 md1 class="mr-2"> <v-flex xs6 sm1 md1>
<div class="caption grey--text">Seeds</div> <div class="caption grey--text">Seeds</div>
<div> <div>
{{ torrent.num_seeds }} {{ torrent.num_seeds }}
@ -74,165 +83,53 @@
> >
</div> </div>
</v-flex> </v-flex>
<v-flex xs4 sm12 md1> <v-flex xs6 sm1 md1 :class="phoneLayout ? '' : 'mr-4'">
<div class="right"> <div class="caption grey--text">Status</div>
<v-chip <v-chip
small small
class="my-2 caption" class="caption"
:class=" :class="
theme === 'light' theme === 'light'
? `${torrent.state} white--text ` ? `${state} white--text `
: `${torrent.state} black--text` : `${state} black--text`">
" {{ torrent.state }}
>{{ torrent.state }}</v-chip </v-chip>
>
</div>
</v-flex> </v-flex>
<!-- labels --> <!-- Category -->
<v-flex v-if="torrent.category" xs4 sm1 md1>
<v-flex v-for="tag in torrent.tags" :key="tag" xs3 sm1 md1> <div class="caption grey--text">Category</div>
<v-chip <v-chip small class="upload white--text caption">
{{ torrent.category }}
</v-chip>
</v-flex>
<!-- Tags -->
<v-flex xs12 sm1>
<div class="caption grey--text">Tags</div>
<v-row wrap class="ma-0">
<v-chip v-for="tag in torrent.tags" :key="tag"
small small
:class=" :class="
theme === 'light' theme === 'light'
? 'white--text' ? 'white--text'
: 'black--text' : 'black--text'
" "
class="download my-2 caption" class="download caption mb-1 mx-1"
> >
{{ tag }} {{ tag }}
</v-chip> </v-chip>
</v-flex> </v-row>
<v-flex v-if="torrent.category" xs3 sm1 md1>
<v-chip small class="upload white--text my-2 caption">
{{ torrent.category }}
</v-chip>
</v-flex>
<v-flex xs12 sm12 md12>
<v-progress-linear
height="3"
rounded
color="cyan darken-1"
background-color="cyan lighten-3"
:value="torrent.progress"
></v-progress-linear>
</v-flex> </v-flex>
</v-layout> </v-layout>
</template>
<span>{{ torrent.name }}</span>
</v-tooltip>
<v-divider v-if="index !== length"></v-divider> <v-divider v-if="index !== length"></v-divider>
</v-card> </v-card>
</template> </template>
<script> <script>
import { General } from '@/mixins' import { General, Torrent } from '@/mixins'
import { mapGetters } from 'vuex'
export default { export default {
name: 'Torrent', name: 'Torrent',
mixins: [General], mixins: [General, Torrent]
props: {
torrent: Object,
index: Number,
length: Number
},
computed: {
chips() {
let chips = []
if (this.torrent.category.length > 0) {
chips.push(this.torrent.category)
}
return chips
},
...mapGetters(['getTheme']),
theme() {
return this.getTheme() ? 'dark' : 'light'
},
style() {
let base = `pa-4 ml-0 sideborder ${this.torrent.state} `
if (this.index === this.length) base += ' bottomBorderRadius'
if (this.index === 0) base += ' topBorderRadius'
return base
}
},
methods: {
selectTorrent(hash) {
if (this.containsTorrent(hash)) {
this.$store.commit('SET_SELECTED', { type: 'remove', hash })
} else {
this.$store.commit('SET_SELECTED', { type: 'add', hash })
}
},
containsTorrent(hash) {
return this.$store.getters.containsTorrent(hash)
},
showInfo(hash) {
this.createModal('TorrentDetailModal', { hash })
}
},
filters: {
formatEta(value, options) {
const minute = 60
const hour = minute * 60
const day = hour * 24
const year = day * 365
const durations = [year, day, hour, minute, 1]
const units = 'ydhms'
let index = 0
let unitSize = 0
const parts = []
const defaultOptions = {
maxUnitSize: 2,
dayLimit: 0,
minUnit: 0
}
const opt = options
? Object.assign(defaultOptions, options)
: defaultOptions
if (opt.dayLimit && value >= opt.dayLimit * day) {
return '∞'
}
while (
(!opt.maxUnitSize || unitSize !== opt.maxUnitSize) &&
index !== durations.length
) {
const duration = durations[index]
if (value < duration) {
index++
continue
} else if (
opt.minUnit &&
durations.length - index <= opt.minUnit
) {
break
}
const result = Math.floor(value / duration)
parts.push(result + units[index])
value %= duration
index++
unitSize++
}
if (!parts.length) {
return '0' + units[durations.length - 1 - opt.minUnit]
}
return parts.join(' ')
}
}
} }
</script> </script>

View file

@ -20,10 +20,16 @@
> >
</v-list-item> </v-list-item>
<v-divider /> <v-divider />
<v-list-item @click="directory" link>
<v-icon>folder</v-icon>
<v-list-item-title class="ml-2" style="font-size: 15px"
>Change location</v-list-item-title
>
</v-list-item>
<v-list-item @click="reannounce" link> <v-list-item @click="reannounce" link>
<v-icon>record_voice_over</v-icon> <v-icon>record_voice_over</v-icon>
<v-list-item-title class="ml-2" style="font-size: 15px" <v-list-item-title class="ml-2" style="font-size: 15px"
>reannounce</v-list-item-title >Reannounce</v-list-item-title
> >
</v-list-item> </v-list-item>
<v-divider /> <v-divider />
@ -58,6 +64,9 @@ export default {
pause() { pause() {
qbit.pauseTorrents([this.hash]) qbit.pauseTorrents([this.hash])
}, },
directory() {
this.createModal('ChangeLocationModal', { hash: this.hash })
},
reannounce() { reannounce() {
qbit.reannounceTorrents([this.hash]) qbit.reannounceTorrents([this.hash])
}, },

View file

@ -0,0 +1,135 @@
<template>
<v-card
ripple
flat
class="pointer torrent noselect"
:class="{ torrent_selected: containsTorrent(torrent.hash) }"
@click.native="selectTorrent(torrent.hash)"
@dblclick.prevent="showInfo(torrent.hash)"
>
<v-tooltip top>
<template v-slot:activator="{ on }">
<v-layout v-on="on" row wrap :class="style">
<v-flex xs12 sm2 md3>
<div class="caption grey--text">Torrent title</div>
<div class="truncate">{{ torrent.name }}</div>
</v-flex>
<v-flex xs6 sm1 md1 class="mr-2">
<div class="caption grey--text">Size</div>
<div>
{{ torrent.size | getDataValue }}
<span class="caption grey--text">{{
torrent.size | getDataUnit
}}</span>
</div>
</v-flex>
<v-flex xs5 sm1 md1 class="mr-2">
<div class="caption grey--text">Done</div>
<div>
{{ torrent.progress }}<span class="grey--text">% </span>
</div>
</v-flex>
<v-flex xs6 sm1 md1 class="mr-2">
<div class="caption grey--text">Download</div>
<div>
{{ torrent.dlspeed | getDataValue }}
<span class="caption grey--text">{{
torrent.dlspeed | getDataUnit
}}</span>
</div>
</v-flex>
<v-flex xs5 sm1 md1 class="mr-2">
<div class="caption grey--text">Upload</div>
<div>
{{ torrent.upspeed | getDataValue }}
<span class="caption grey--text">{{
torrent.upspeed | getDataUnit
}}</span>
</div>
</v-flex>
<v-flex xs6 sm1 md1 class="mr-2">
<div class="caption grey--text">ETA</div>
<div>
{{ torrent.eta }}
</div>
</v-flex>
<v-flex xs5 sm1 md1 class="mr-2">
<div class="caption grey--text">Peers</div>
<div>
{{ torrent.num_leechs }}
<span class="grey--text caption"
>/{{ torrent.available_peers }}</span
>
</div>
</v-flex>
<v-flex xs6 sm1 md1 class="mr-2">
<div class="caption grey--text">Seeds</div>
<div>
{{ torrent.num_seeds }}
<span class="grey--text caption"
>/{{ torrent.available_seeds }}</span
>
</div>
</v-flex>
<v-flex xs5 sm1>
<div class="caption grey--text">Status</div>
<v-chip
small
class="caption"
:class="
theme === 'light'
? `${state} white--text `
: `${state} black--text`">
{{ torrent.state }}
</v-chip>
</v-flex>
<!-- Category -->
<v-flex v-if="torrent.category" class="mr-2" xs6 sm1 md1>
<div class="caption grey--text">Category</div>
<v-chip small class="upload white--text caption">
{{ torrent.category }}
</v-chip>
</v-flex>
<!-- Tags -->
<v-flex xs5 sm4>
<div class="caption grey--text">Tags</div>
<v-row wrap class="ma-0">
<v-chip v-for="tag in torrent.tags" :key="tag"
small
:class="
theme === 'light'
? 'white--text'
: 'black--text'
"
class="download caption mb-1 mx-1"
>
{{ tag }}
</v-chip>
</v-row>
</v-flex>
</v-layout>
</template>
<span>{{ torrent.name }}</span>
</v-tooltip>
<v-divider v-if="index !== length"></v-divider>
</v-card>
</template>
<script>
import { General, Torrent } from '@/mixins'
export default {
name: 'TorrentDense',
mixins: [General, Torrent]
}
</script>
<style lang="scss" scoped>
.topBorderRadius {
border-top-left-radius: 3px;
}
.bottomBorderRadius {
border-bottom-left-radius: 3px;
}
</style>

View file

@ -54,7 +54,7 @@ export function treeify(paths) {
let level = { result } let level = { result }
paths.forEach(path => { paths.forEach(path => {
path.name.split('/').reduce((r, name, i, a) => { path.name.split('/').reduce((r, name) => {
if (!r[name]) { if (!r[name]) {
r[name] = { result: [] } r[name] = { result: [] }
r.result.push(createFile(path, name, r[name].result)) r.result.push(createFile(path, name, r[name].result))
@ -81,7 +81,9 @@ export function treeify(paths) {
function createFile(data, name, children) { function createFile(data, name, children) {
return { return {
id: data.id,
name: name, name: name,
fullName: data.name,
progress: Math.round(data.progress * 100), progress: Math.round(data.progress * 100),
size: formatBytes(data.size), size: formatBytes(data.size),
icon: getIconForFileType(name.split('.').pop()), icon: getIconForFileType(name.split('.').pop()),
@ -92,6 +94,7 @@ function createFile(data, name, children) {
function createFolder(name, children) { function createFolder(name, children) {
return { return {
name: name, name: name,
fullName: name,
type: 'directory', type: 'directory',
children: children children: children
} }

42
src/mixins/Torrent.js Normal file
View file

@ -0,0 +1,42 @@
import {mapGetters} from 'vuex'
export default {
props: {
torrent: Object,
index: Number,
length: Number
},
computed: {
...mapGetters(['getTheme']),
theme() {
return this.getTheme() ? 'dark' : 'light'
},
state() {
return this.torrent.state.toLowerCase()
},
style() {
let base = `pa-4 ml-0 sideborder ${this.state} `
if (this.index === this.length) base += ' bottomBorderRadius'
if (this.index === 0) base += ' topBorderRadius'
return base
},
phoneLayout() {
return this.$vuetify.breakpoint.xsOnly
}
},
methods: {
selectTorrent(hash) {
if (this.containsTorrent(hash)) {
this.$store.commit('SET_SELECTED', { type: 'remove', hash })
} else {
this.$store.commit('SET_SELECTED', { type: 'add', hash })
}
},
containsTorrent(hash) {
return this.$store.getters.containsTorrent(hash)
},
showInfo(hash) {
this.createModal('TorrentDetailModal', { hash })
}
}
}

View file

@ -3,5 +3,6 @@ import Modal from './Modal'
import SettingsTab from './SettingsTab' import SettingsTab from './SettingsTab'
import Tab from './Tab' import Tab from './Tab'
import General from './General' import General from './General'
import Torrent from './Torrent'
export { FullScreenModal, Modal, SettingsTab, Tab, General } export { FullScreenModal, Modal, SettingsTab, Tab, General, Torrent }

View file

@ -2,68 +2,127 @@ export default class Torrent {
constructor(data) { constructor(data) {
this.name = data.name this.name = data.name
this.size = this.formatBytes(data.size) this.size = this.formatBytes(data.size)
this.birth = new Date(data.added_on * 1000).toLocaleString() this.added_on = new Date(data.added_on * 1000).toLocaleString()
this.dlspeed = this.formatBytes(data.dlspeed, 1) this.dlspeed = this.formatBytes(data.dlspeed, 1) + '/s'
this.dloaded = this.formatBytes(data.completed) this.dloaded = this.formatBytes(data.completed)
this.upspeed = this.formatBytes(data.upspeed, 1) this.upspeed = this.formatBytes(data.upspeed, 1) + '/s'
this.uploaded = this.formatBytes(data.uploaded) this.uploaded = this.formatBytes(data.uploaded)
this.eta = data.eta this.eta = this.formatEta(data.eta)
this.num_leechs = data.num_leechs this.num_leechs = data.num_leechs
this.num_seeds = data.num_seeds this.num_seeds = data.num_seeds
this.path = data.path === undefined ? '/downloads' : data.path this.path = data.path === undefined ? '/downloads' : data.path
this.state = this.formatState(data.state) this.state = this.formatState(data)
// hash is used to identify // hash is used to identify
this.hash = data.hash this.hash = data.hash
// available seeds // available seeds
this.available_seeds = data.num_complete this.available_seeds = data.num_complete
this.available_peers = data.num_incomplete this.available_peers = data.num_incomplete
this.savePath = data.save_path this.savePath = data.save_path
this.progress = data.progress * 100 this.progress = Math.round(data.progress * 10000) / 100
this.ratio = Math.round(data.ratio * 100) this.ratio = Math.round(data.ratio * 100) / 100
this.tags = data.tags.length > 0 ? data.tags.split(',') : null this.tags = data.tags.length > 0 ? data.tags.split(',') : null
this.category = data.category this.category = data.category
} }
formatState(state) { formatState(item) {
switch (state) { if (!item.tracker) return 'Fail'
switch (item.state) {
case 'forceDL': case 'forceDL':
case 'downloading': case 'downloading':
return 'busy' return 'Downloading'
case 'metaDL': case 'metaDL':
return 'metadata' return 'Metadata'
case 'forcedUP': case 'forcedUP':
case 'uploading': case 'uploading':
case 'stalledUP': case 'stalledUP':
return 'seeding' return 'Seeding'
case 'pausedDL': case 'pausedDL':
return 'paused' return 'Paused'
case 'pausedUP': case 'pausedUP':
return 'done' return 'Done'
case 'queuedDL': case 'queuedDL':
case 'queuedUP': case 'queuedUP':
return 'queued' return 'Queued'
case 'allocating': case 'allocating':
case 'checkingDL': case 'checkingDL':
case 'checkingUP': case 'checkingUP':
case 'checkingResumeData': case 'checkingResumeData':
case 'moving': case 'moving':
return 'checking' return 'Checking'
case 'unknown': case 'unknown':
case 'missingFiles': case 'missingFiles':
return 'fail' return 'Fail'
case 'stalledDL': case 'stalledDL':
return 'stalled' return 'Stalled'
default: default:
return 'fail' return 'Fail'
} }
} }
formatBytes(a, b) { formatBytes(a, b) {
if (a == 0) return '0 Bytes' if (a == 0) return '0 B'
const c = 1024 const c = 1024
const d = b || 2 const d = b || 2
const e = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'] const e = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
const f = Math.floor(Math.log(a) / Math.log(c)) const f = Math.floor(Math.log(a) / Math.log(c))
return `${parseFloat((a / Math.pow(c, f)).toFixed(d))} ${e[f]}` return `${parseFloat((a / Math.pow(c, f)).toFixed(d))} ${e[f]}`
} }
formatEta(value) {
let options = { dayLimit: 100 }
const minute = 60
const hour = minute * 60
const day = hour * 24
const year = day * 365
const durations = [year, day, hour, minute, 1]
const units = 'ydhms'
let index = 0
let unitSize = 0
const parts = []
const defaultOptions = {
maxUnitSize: 2,
dayLimit: 0,
minUnit: 0
}
const opt = options
? Object.assign(defaultOptions, options)
: defaultOptions
if (opt.dayLimit && value >= opt.dayLimit * day) {
return '∞'
}
while (
(!opt.maxUnitSize || unitSize !== opt.maxUnitSize) &&
index !== durations.length
) {
const duration = durations[index]
if (value < duration) {
index++
continue
} else if (
opt.minUnit &&
durations.length - index <= opt.minUnit
) {
break
}
const result = Math.floor(value / duration)
parts.push(result + units[index])
value %= duration
index++
unitSize++
}
if (!parts.length) {
return '0' + units[durations.length - 1 - opt.minUnit]
}
return parts.join(' ')
}
} }

View file

@ -3,6 +3,7 @@ import Vuetify from 'vuetify'
import 'vuetify/dist/vuetify.min.css' import 'vuetify/dist/vuetify.min.css'
import colors from 'vuetify/lib/util/colors' import colors from 'vuetify/lib/util/colors'
import variables from '../styles/variables.scss'
Vue.use(Vuetify) Vue.use(Vuetify)
@ -28,7 +29,8 @@ export default new Vuetify({
background: colors.grey.lighten4, background: colors.grey.lighten4,
search: colors.grey.darken1, search: colors.grey.darken1,
torrentmodaltext: colors.grey.darken4, torrentmodaltext: colors.grey.darken4,
select: colors.grey.lighten4 select: colors.grey.lighten4,
...variables
}, },
dark: { dark: {
primary: '#35495e', primary: '#35495e',
@ -45,7 +47,8 @@ export default new Vuetify({
background: colors.grey.darken4, background: colors.grey.darken4,
search: colors.grey.darken3, search: colors.grey.darken3,
torrentmodaltext: colors.grey.lighten4, torrentmodaltext: colors.grey.lighten4,
select: colors.grey.darken3 select: colors.grey.darken3,
...variables
} }
} }
} }

View file

@ -214,6 +214,16 @@ class Qbit {
}) })
} }
renameFile(hash, id, name) {
const params = {
hash,
id,
name
}
const data = new URLSearchParams(params)
return this.axios.post('/torrents/renameFile', data)
}
getAvailableTags() { getAvailableTags() {
return this.axios.get('/torrents/tags') return this.axios.get('/torrents/tags')
} }
@ -299,6 +309,7 @@ class Qbit {
// End Categories // End Categories
actionTorrents(action, hashes, extra) { actionTorrents(action, hashes, extra) {
if (action == 'delete' && !hashes.length) return
const params = { const params = {
hashes: hashes.length ? hashes.join('|') : 'all', hashes: hashes.length ? hashes.join('|') : 'all',
...extra ...extra

View file

@ -31,8 +31,8 @@ export default new Vuex.Store({
selected_torrents: [], selected_torrents: [],
authenticated: false, authenticated: false,
sort_options: { sort_options: {
sort: 'name', sort: 'default',
reverse: false, reverse: true,
hashes: [], hashes: [],
filter: null filter: null
}, },
@ -46,7 +46,9 @@ export default new Vuex.Store({
showSpeedGraph: true, showSpeedGraph: true,
showSessionStat: true, showSessionStat: true,
showCurrentSpeed: true, showCurrentSpeed: true,
showGlobalRemoveResumePause: true showGlobalRemoveResumePause: true,
denseDashboard: true,
paginationSize: 15
}, },
categories: [], categories: [],
filteredTorrentsCount: 0 filteredTorrentsCount: 0

View file

@ -61,10 +61,10 @@ export default {
state.settings = data state.settings = data
}, },
UPDATE_SORT_OPTIONS: (state, payload) => { UPDATE_SORT_OPTIONS: (state, payload) => {
state.sort_options.sort = payload.name ? payload.name : null state.sort_options.sort = payload.name ? payload.name : state.sort_options.sort
state.sort_options.reverse = payload.reverse ? payload.reverse : null state.sort_options.reverse = payload.reverse ? payload.reverse : state.sort_options.reverse
state.sort_options.hashes = payload.hashes ? payload.hashes : null state.sort_options.hashes = payload.hashes ? payload.hashes : state.sort_options.hashes
state.sort_options.filter = payload.filter ? payload.filter : null state.sort_options.filter = payload.filter ? payload.filter : state.sort_options.filter
state.sort_options.category = state.sort_options.category =
payload.category !== null ? payload.category : null payload.category !== null ? payload.category : null
}, },

21
src/styles/variables.scss Normal file
View file

@ -0,0 +1,21 @@
$torrent-done: #3cd1c2;
$torrent-downloading: #ffaa2c;
$torrent-fail: #f83e70;
$torrent-paused: #cfd8dc;
$torrent-queued: #2e5eaa;
$torrent-seeding: #26a69a;
$torrent-checking: #ff7043;
$torrent-stalled: #81c784;
$torrent-metadata: #7e57c2;
:export {
torrent-done-color: $torrent-done;
torrent-downloading-color: $torrent-downloading;
torrent-fail-color: $torrent-fail;
torrent-paused-color: $torrent-paused;
torrent-queued-color: $torrent-queued;
torrent-seeding-color: $torrent-seeding;
torrent-checking-color: $torrent-checking;
torrent-stalled-color: $torrent-stalled;
torrent-metadata-color: $torrent-metadata;
}

View file

@ -10,9 +10,9 @@
</p> </p>
</h1> </h1>
<v-container <div
color="background" color="background"
class="my-4 pt-5 pa-0" class="my-4 pt-5 px-8"
@click.self="resetSelected" @click.self="resetSelected"
> >
<v-flex xs12 sm6 md3 @click.self="resetSelected"> <v-flex xs12 sm6 md3 @click.self="resetSelected">
@ -23,6 +23,7 @@
clearable clearable
solo solo
color="search" color="search"
@click:clear="resetInput()"
v-model="input" v-model="input"
></v-text-field> ></v-text-field>
</v-flex> </v-flex>
@ -32,10 +33,21 @@
<div v-else> <div v-else>
<div <div
@contextmenu.prevent="$refs.menu.open($event, { torrent })" @contextmenu.prevent="$refs.menu.open($event, { torrent })"
v-for="(torrent, index) in torrents" v-for="(torrent, index) in paginatedData"
:key="torrent.hash" :key="torrent.hash"
> >
<Torrent <Torrent v-if="!denseDashboard"
:class="{
topBorderRadius: index === 0,
noBorderRadius:
index !== 0 && index !== torrent.length - 1,
bottomBorderRadius: index === torrents.length - 1
}"
:torrent="torrent"
:index="index"
:length="torrents.length - 1"
/>
<TorrentDense v-if="denseDashboard"
:class="{ :class="{
topBorderRadius: index === 0, topBorderRadius: index === 0,
noBorderRadius: noBorderRadius:
@ -47,8 +59,20 @@
:length="torrents.length - 1" :length="torrents.length - 1"
/> />
</div> </div>
</div> <v-row v-if="pageCount > 1" xs12 justify="center">
<v-col>
<v-container>
<v-pagination
v-model="pageNumber"
:length="pageCount"
:total-visible="7"
@input="toTop"
></v-pagination>
</v-container> </v-container>
</v-col>
</v-row>
</div>
</div>
<vue-context ref="menu" v-slot="{ data }"> <vue-context ref="menu" v-slot="{ data }">
<TorrentRightClickMenu v-if="data" :hash="data.torrent.hash" /> <TorrentRightClickMenu v-if="data" :hash="data.torrent.hash" />
</vue-context> </vue-context>
@ -58,6 +82,7 @@
<script> <script>
import { mapState, mapGetters } from 'vuex' import { mapState, mapGetters } from 'vuex'
import Torrent from '@/components/Torrent' import Torrent from '@/components/Torrent'
import TorrentDense from '@/components/TorrentDense'
import Fuse from 'fuse.js' import Fuse from 'fuse.js'
import { VueContext } from 'vue-context' import { VueContext } from 'vue-context'
import 'vue-context/src/sass/vue-context.scss' import 'vue-context/src/sass/vue-context.scss'
@ -65,17 +90,18 @@ import TorrentRightClickMenu from '@/components/Torrent/TorrentRightClickMenu.vu
export default { export default {
name: 'Dashboard', name: 'Dashboard',
components: { Torrent, VueContext, TorrentRightClickMenu }, components: { Torrent, TorrentDense, VueContext, TorrentRightClickMenu },
data() { data() {
return { return {
input: '' input: '',
pageNumber: 1
} }
}, },
computed: { computed: {
...mapState(['mainData']), ...mapState(['mainData']),
...mapGetters(['getTorrents', 'getTorrentCountString']), ...mapGetters(['getTorrents', 'getTorrentCountString', 'getWebuiSettings']),
torrents() { torrents() {
if (this.input.length === 0) return this.getTorrents() if (!this.input || !this.input.length) return this.getTorrents()
const options = { const options = {
threshold: 0.3, threshold: 0.3,
@ -92,13 +118,35 @@ export default {
const fuse = new Fuse(this.getTorrents(), options) const fuse = new Fuse(this.getTorrents(), options)
return fuse.search(this.input).map(el => el.item) return fuse.search(this.input).map(el => el.item)
}, },
paginationSize() {
return this.getWebuiSettings().paginationSize
},
pageCount(){
let l = this.torrents.length,
s = this.paginationSize
return Math.ceil(l/s)
},
paginatedData(){
const start = (this.pageNumber - 1) * this.paginationSize,
end = start + this.paginationSize
return this.torrents.slice(start, end)
},
torrentCountString() { torrentCountString() {
return this.getTorrentCountString() return this.getTorrentCountString()
},
denseDashboard(){
return this.getWebuiSettings().denseDashboard
} }
}, },
methods: { methods: {
resetSelected() { resetSelected() {
this.$store.commit('RESET_SELECTED') this.$store.commit('RESET_SELECTED')
},
resetInput(){
this.input = ''
},
toTop () {
this.$vuetify.goTo(0)
} }
}, },
created() { created() {

View file

@ -1,5 +1,13 @@
const webpack = require('webpack') const webpack = require('webpack')
module.exports = { module.exports = {
chainWebpack: config => {
config
.plugin('html')
.tap(args => {
args[0].title = 'VueTorrent'
return args
})
},
outputDir: 'vuetorrent/public', outputDir: 'vuetorrent/public',
publicPath: './', publicPath: './',
transpileDependencies: ['vuetify'], transpileDependencies: ['vuetify'],