mirror of
https://github.com/VueTorrent/VueTorrent.git
synced 2024-11-28 13:08:53 +03:00
0.5.4 (#119)
This commit is contained in:
parent
8a8b7e9d62
commit
c4329ce2b9
30 changed files with 6331 additions and 782 deletions
15
.eslintrc.js
15
.eslintrc.js
|
@ -69,5 +69,16 @@ module.exports = {
|
|||
'arrow-spacing': ['error', { before: true, after: true }],
|
||||
'no-multi-spaces': ['error'],
|
||||
'newline-before-return': ['error']
|
||||
}
|
||||
}
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
files: [
|
||||
'**/__tests__/*.{j,t}s?(x)',
|
||||
'**/tests/unit/**/*.spec.{j,t}s?(x)'
|
||||
],
|
||||
env: {
|
||||
jest: true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
25
.github/workflows/test.yml
vendored
Normal file
25
.github/workflows/test.yml
vendored
Normal file
|
@ -0,0 +1,25 @@
|
|||
name: Test Core Components
|
||||
on:
|
||||
push
|
||||
|
||||
jobs:
|
||||
test-core:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- name: Cache node modules
|
||||
uses: actions/cache@v2
|
||||
env:
|
||||
cache-name: cache-node-modules
|
||||
with:
|
||||
# npm cache files are stored in `~/.npm` on Linux/macOS
|
||||
path: ~/.npm
|
||||
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-build-${{ env.cache-name }}-
|
||||
${{ runner.os }}-build-
|
||||
${{ runner.os }}-
|
||||
- name: Install & test
|
||||
uses: actions/setup-node@master
|
||||
- run: npm ci
|
||||
- run: npm run test:unit
|
5
babel.config.js
Normal file
5
babel.config.js
Normal file
|
@ -0,0 +1,5 @@
|
|||
module.exports = {
|
||||
presets: [
|
||||
'@vue/app'
|
||||
]
|
||||
}
|
8
jest.config.js
Normal file
8
jest.config.js
Normal file
|
@ -0,0 +1,8 @@
|
|||
module.exports = {
|
||||
preset: '@vue/cli-plugin-unit-jest',
|
||||
moduleFileExtensions: ['js', 'json', 'vue'],
|
||||
transform: {
|
||||
'.*\\.(vue)$': 'vue-jest',
|
||||
'^.+\\.js$': '<rootDir>/node_modules/babel-jest'
|
||||
}
|
||||
}
|
6322
package-lock.json
generated
6322
package-lock.json
generated
File diff suppressed because it is too large
Load diff
121
package.json
121
package.json
|
@ -1,61 +1,64 @@
|
|||
{
|
||||
"name": "vuetorrent",
|
||||
"version": "0.5.3",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "npm run serve",
|
||||
"serve": "vue-cli-service serve",
|
||||
"build": "vue-cli-service build",
|
||||
"lint": "vue-cli-service lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/polyfill": "^7.12.1",
|
||||
"apexcharts": "^3.23.1",
|
||||
"axios": "^0.21.1",
|
||||
"core-js": "^3.8.3",
|
||||
"dayjs": "^1.10.4",
|
||||
"fuse.js": "^6.4.6",
|
||||
"lodash": "^4.17.20",
|
||||
"register-service-worker": "^1.7.2",
|
||||
"typeface-roboto": "^1.1.13",
|
||||
"typeface-roboto-mono": "^1.1.13",
|
||||
"uuid": "^8.3.2",
|
||||
"vue": "^2.6.12",
|
||||
"vue-apexcharts": "^1.6.0",
|
||||
"vue-context": "^5.2.0",
|
||||
"vue-observe-visibility": "^0.4.6",
|
||||
"vue-router": "^3.4.9",
|
||||
"vue-toastification": "^1.7.11",
|
||||
"vue2-perfect-scrollbar": "^1.5.0",
|
||||
"vuedraggable": "^2.24.3",
|
||||
"vuetify": "^2.4.3",
|
||||
"vuex": "^3.6.0",
|
||||
"vuex-persist": "^3.1.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@mdi/js": "^5.9.55",
|
||||
"@vue/cli-plugin-babel": "~4.3.0",
|
||||
"@vue/cli-plugin-eslint": "~4.3.0",
|
||||
"@vue/cli-plugin-pwa": "^4.5.11",
|
||||
"@vue/cli-service": "~4.3.0",
|
||||
"@vue/eslint-config-prettier": "^6.0.0",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"eslint": "^7.18.0",
|
||||
"eslint-config-google": "^0.14.0",
|
||||
"eslint-plugin-prettier": "^3.3.1",
|
||||
"eslint-plugin-vue": "^7.5.0",
|
||||
"fibers": "^5.0.0",
|
||||
"node-sass": "^4.14.1",
|
||||
"sass": "^1.32.5",
|
||||
"sass-loader": "^8.0.2",
|
||||
"vue-cli-plugin-vuetify": "^2.0.9",
|
||||
"vue-template-compiler": "^2.6.12",
|
||||
"vuetify-loader": "^1.6.0"
|
||||
},
|
||||
"browserslist": [
|
||||
"> 1%",
|
||||
"last 2 versions",
|
||||
"not dead",
|
||||
"not ie <= 10"
|
||||
]
|
||||
"name": "vuetorrent",
|
||||
"version": "0.5.4",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service serve",
|
||||
"build": "vue-cli-service build",
|
||||
"test:unit": "vue-cli-service test:unit --setupTestFrameworkScriptFile=./tests/setup.js",
|
||||
"lint": "vue-cli-service lint",
|
||||
"start": "npm run serve"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/polyfill": "^7.12.1",
|
||||
"apexcharts": "^3.23.1",
|
||||
"axios": "^0.21.1",
|
||||
"core-js": "^3.8.3",
|
||||
"dayjs": "^1.10.4",
|
||||
"fuse.js": "^6.4.6",
|
||||
"lodash": "^4.17.20",
|
||||
"register-service-worker": "^1.7.2",
|
||||
"typeface-roboto": "^1.1.13",
|
||||
"typeface-roboto-mono": "^1.1.13",
|
||||
"uuid": "^8.3.2",
|
||||
"vue": "^2.6.12",
|
||||
"vue-apexcharts": "^1.6.0",
|
||||
"vue-context": "^5.2.0",
|
||||
"vue-router": "^3.4.9",
|
||||
"vue-toastification": "^1.7.11",
|
||||
"vue2-perfect-scrollbar": "^1.5.0",
|
||||
"vuedraggable": "^2.24.3",
|
||||
"vuetify": "^2.4.3",
|
||||
"vuex": "^3.6.0",
|
||||
"vuex-persist": "^3.1.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@mdi/js": "^5.9.55",
|
||||
"@vue/babel-preset-app": "^4.5.11",
|
||||
"@vue/cli-plugin-babel": "~4.3.0",
|
||||
"@vue/cli-plugin-eslint": "~4.3.0",
|
||||
"@vue/cli-plugin-pwa": "^4.5.11",
|
||||
"@vue/cli-plugin-unit-jest": "^4.5.11",
|
||||
"@vue/cli-service": "~4.3.0",
|
||||
"@vue/eslint-config-prettier": "^6.0.0",
|
||||
"@vue/test-utils": "^1.0.3",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"eslint": "^7.18.0",
|
||||
"eslint-config-google": "^0.14.0",
|
||||
"eslint-plugin-prettier": "^3.3.1",
|
||||
"eslint-plugin-vue": "^7.5.0",
|
||||
"fibers": "^5.0.0",
|
||||
"node-sass": "^4.14.1",
|
||||
"sass": "^1.32.5",
|
||||
"sass-loader": "^8.0.2",
|
||||
"vue-cli-plugin-vuetify": "^2.0.9",
|
||||
"vue-template-compiler": "^2.6.12",
|
||||
"vuetify-loader": "^1.6.0"
|
||||
},
|
||||
"browserslist": [
|
||||
"> 1%",
|
||||
"last 2 versions",
|
||||
"not dead",
|
||||
"not ie <= 10"
|
||||
]
|
||||
}
|
||||
|
|
|
@ -41,6 +41,7 @@ export default {
|
|||
const res = await qbit.login()
|
||||
const authenticated = res === 'Ok.'
|
||||
this.$store.commit('LOGIN', authenticated)
|
||||
this.$store.commit('updateMainData')
|
||||
if (
|
||||
!authenticated &&
|
||||
!this.$router.currentRoute.name.includes('login')
|
||||
|
|
|
@ -3,15 +3,16 @@
|
|||
flat
|
||||
color="secondary"
|
||||
class="speedCard"
|
||||
data-testid="SpeedCard"
|
||||
>
|
||||
<v-layout row :class="color + '--text'">
|
||||
<v-flex xs2>
|
||||
<v-icon :color="color" size="16px">
|
||||
<v-flex v-if="icon" xs2>
|
||||
<v-icon data-testid="SpeedCard-icon" :color="color" size="16px">
|
||||
{{ icon }}
|
||||
</v-icon>
|
||||
</v-flex>
|
||||
<v-flex xs7 class="text-center font-weight-bold robot-mono">
|
||||
<span>
|
||||
<span data-testid="SpeedCard-value">
|
||||
{{ value | getDataValue(2) }}
|
||||
</span>
|
||||
</v-flex>
|
||||
|
@ -19,7 +20,7 @@
|
|||
xs3
|
||||
class="caption robot-mono text-right mt-1"
|
||||
>
|
||||
<span class="speedUnits">
|
||||
<span class="speedUnits" data-testid="SpeedCard-unit">
|
||||
{{ value | getDataUnit(1) }}/s
|
||||
</span>
|
||||
</v-flex>
|
||||
|
|
|
@ -5,14 +5,15 @@
|
|||
<div
|
||||
style="margin-top: 6px"
|
||||
:class="color + '--text'"
|
||||
data-testid="StorageCard-label"
|
||||
>
|
||||
{{ label }}
|
||||
</div>
|
||||
</v-flex>
|
||||
<v-flex md5 class="ml-4">
|
||||
<span :class="color + '--text title'">
|
||||
{{ value | getDataValue(2) }}
|
||||
<span class="font-weight-light caption">
|
||||
<span data-testid="StorageCard-Wrapper" :class="color + '--text title'">
|
||||
<span data-testid="StorageCard-value"> {{ value | getDataValue(2) }} </span>
|
||||
<span data-testid="StorageCard-unit" class="font-weight-light caption">
|
||||
{{ value | getDataUnit }}
|
||||
</span>
|
||||
</span>
|
||||
|
|
|
@ -129,6 +129,9 @@ export default {
|
|||
return this.getSearchPlugins().filter(p => p.enabled)
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.$store.commit('FETCH_SEARCH_PLUGINS')
|
||||
},
|
||||
methods: {
|
||||
async startSearch() {
|
||||
if (this.searchForm.pattern.length && !this.search.interval) {
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
<td class="grey--text">
|
||||
Torrent title
|
||||
</td>
|
||||
<td class="torrentmodaltext--text">
|
||||
<td>
|
||||
{{ torrent.name }}
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -19,7 +19,7 @@
|
|||
<td class="grey--text">
|
||||
Directory
|
||||
</td>
|
||||
<td class="torrentmodaltext--text">
|
||||
<td>
|
||||
{{ torrent.savePath }}
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -27,7 +27,7 @@
|
|||
<td class="grey--text">
|
||||
hash
|
||||
</td>
|
||||
<td class="torrentmodaltext--text">
|
||||
<td>
|
||||
{{ torrent.hash }}
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -35,7 +35,7 @@
|
|||
<td class="grey--text">
|
||||
Size
|
||||
</td>
|
||||
<td class="torrentmodaltext--text">
|
||||
<td>
|
||||
{{ torrent.size | getDataValue }}
|
||||
{{ torrent.size | getDataUnit(1) }}
|
||||
</td>
|
||||
|
@ -44,7 +44,7 @@
|
|||
<td class="grey--text">
|
||||
Downloaded:
|
||||
</td>
|
||||
<td class="torrentmodaltext--text">
|
||||
<td>
|
||||
{{ torrent.dloaded | getDataValue }}
|
||||
{{ torrent.dloaded | getDataUnit(1) }}
|
||||
</td>
|
||||
|
@ -53,7 +53,7 @@
|
|||
<td class="grey--text">
|
||||
Uploaded:
|
||||
</td>
|
||||
<td class="torrentmodaltext--text">
|
||||
<td>
|
||||
{{ torrent.uploaded | getDataValue }}
|
||||
{{ torrent.uploaded | getDataUnit(1) }}
|
||||
</td>
|
||||
|
@ -62,7 +62,7 @@
|
|||
<td class="grey--text">
|
||||
Ratio
|
||||
</td>
|
||||
<td class="torrentmodaltext--text">
|
||||
<td>
|
||||
{{ torrent.ratio }}
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -70,7 +70,7 @@
|
|||
<td class="grey--text">
|
||||
Download Speed
|
||||
</td>
|
||||
<td class="torrentmodaltext--text">
|
||||
<td>
|
||||
{{ torrent.dlspeed | getDataValue }}
|
||||
{{ torrent.dlspeed | getDataUnit(1) }}
|
||||
</td>
|
||||
|
@ -79,7 +79,7 @@
|
|||
<td class="grey--text">
|
||||
Upload Speed
|
||||
</td>
|
||||
<td class="torrentmodaltext--text">
|
||||
<td>
|
||||
{{ torrent.upspeed | getDataValue }}
|
||||
{{ torrent.upspeed | getDataUnit(1) }}
|
||||
</td>
|
||||
|
@ -88,7 +88,7 @@
|
|||
<td class="grey--text">
|
||||
ETA
|
||||
</td>
|
||||
<td class="torrentmodaltext--text">
|
||||
<td>
|
||||
{{ torrent.eta }}
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -96,7 +96,7 @@
|
|||
<td class="grey--text">
|
||||
Peers
|
||||
</td>
|
||||
<td class="torrentmodaltext--text">
|
||||
<td>
|
||||
{{ torrent.num_leechs
|
||||
}}<span class="grey--text">/{{ torrent.available_peers }}</span>
|
||||
</td>
|
||||
|
@ -105,7 +105,7 @@
|
|||
<td class="grey--text">
|
||||
Seeds
|
||||
</td>
|
||||
<td class="torrentmodaltext--text">
|
||||
<td>
|
||||
{{ torrent.num_seeds
|
||||
}}<span class="grey--text">/{{ torrent.available_seeds }}</span>
|
||||
</td>
|
||||
|
@ -114,26 +114,10 @@
|
|||
<td class="grey--text">
|
||||
Added on
|
||||
</td>
|
||||
<td class="torrentmodaltext--text">
|
||||
<td>
|
||||
{{ torrent.added_on }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="torrent.tracker">
|
||||
<td class="grey--text">
|
||||
Tracker
|
||||
</td>
|
||||
<td class="torrentmodaltext--text">
|
||||
{{ torrent.tracker }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="torrent.comment">
|
||||
<td class="grey--text">
|
||||
Comment
|
||||
</td>
|
||||
<td class="torrentmodaltext--text">
|
||||
{{ torrent.comment }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="grey--text">
|
||||
Status
|
||||
|
@ -145,6 +129,47 @@
|
|||
{{ torrent.state }}
|
||||
</v-chip>
|
||||
</tr>
|
||||
<tr v-if="torrent.tracker">
|
||||
<td class="grey--text">
|
||||
Tracker
|
||||
</td>
|
||||
<td>
|
||||
{{ torrent.tracker }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="torrent.comment">
|
||||
<td class="grey--text">
|
||||
Comment
|
||||
</td>
|
||||
<td>
|
||||
{{ torrent.comment }}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="grey--text">
|
||||
First/Last Piece Priority
|
||||
</td>
|
||||
<td>
|
||||
{{ torrent.f_l_piece_prio }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="grey--text">
|
||||
Sequential Download
|
||||
</td>
|
||||
<td>
|
||||
{{ torrent.seq_dl }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="grey--text">
|
||||
Auto TMM
|
||||
</td>
|
||||
<td>
|
||||
{{ torrent.auto_tmm }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</v-simple-table>
|
||||
</v-card-text>
|
||||
|
|
|
@ -152,5 +152,10 @@ export default {
|
|||
#app .v-select .v-text-field__details {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#app .v-select .v-select__selection {
|
||||
padding: 16px 0;
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
|
21
src/components/Torrent/DashboardItems/Downloaded.vue
Normal file
21
src/components/Torrent/DashboardItems/Downloaded.vue
Normal file
|
@ -0,0 +1,21 @@
|
|||
<template>
|
||||
<v-flex xs6 sm1 md1>
|
||||
<div class="caption grey--text">
|
||||
Downloaded
|
||||
</div>
|
||||
<div>
|
||||
{{ torrent.dloaded | getDataValue(2) }}
|
||||
<span class="caption grey--text">
|
||||
{{
|
||||
torrent.dloaded | getDataUnit(1)
|
||||
}}
|
||||
</span>
|
||||
</div>
|
||||
</v-flex>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: 'Downloaded',
|
||||
props: ['torrent']
|
||||
}
|
||||
</script>
|
|
@ -6,13 +6,14 @@
|
|||
class="mr-4"
|
||||
>
|
||||
<div class="caption grey--text">
|
||||
Done
|
||||
Progress
|
||||
</div>
|
||||
<v-progress-linear
|
||||
:value="torrent.progress"
|
||||
height="20"
|
||||
:style="phoneLayout ? '' : 'width: 80%;'"
|
||||
:color="`torrent-${state}-color`"
|
||||
rounded
|
||||
>
|
||||
<span
|
||||
class="caption white--text"
|
||||
|
|
|
@ -2,14 +2,15 @@
|
|||
<v-flex
|
||||
xs6
|
||||
sm1
|
||||
md2
|
||||
md1
|
||||
class="mr-4"
|
||||
>
|
||||
<div class="caption grey--text">
|
||||
Status
|
||||
</div>
|
||||
<v-chip
|
||||
small
|
||||
class="caption white--text"
|
||||
class="caption white--text px-2"
|
||||
:class="state"
|
||||
>
|
||||
{{ torrent.state }}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import Size from './Size'
|
||||
import Progress from './Progress'
|
||||
import Download from './Download'
|
||||
import Downloaded from './Downloaded'
|
||||
import Ratio from './Ratio'
|
||||
import Upload from './Upload'
|
||||
import ETA from './ETA'
|
||||
|
@ -27,5 +28,6 @@ export {
|
|||
Tags,
|
||||
AddedOn,
|
||||
Uploaded,
|
||||
UploadedSession
|
||||
UploadedSession,
|
||||
Downloaded
|
||||
}
|
||||
|
|
|
@ -1,170 +0,0 @@
|
|||
<template>
|
||||
<v-list class="py-1">
|
||||
<v-list-item link @click="resume">
|
||||
<v-icon>{{ mdiPlay }}</v-icon>
|
||||
<v-list-item-title
|
||||
class="ml-2"
|
||||
style="font-size: 1em"
|
||||
>
|
||||
Resume
|
||||
</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item link @click="forceResume">
|
||||
<v-icon>{{ mdiFastForward }}</v-icon>
|
||||
<v-list-item-title
|
||||
class="ml-2"
|
||||
style="font-size: 1em"
|
||||
>
|
||||
Force Resume
|
||||
</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item link @click="pause">
|
||||
<v-icon>{{ mdiPause }}</v-icon>
|
||||
<v-list-item-title
|
||||
class="ml-2"
|
||||
style="font-size: 1em"
|
||||
>
|
||||
Pause
|
||||
</v-list-item-title>
|
||||
</v-list-item>
|
||||
|
||||
<v-divider />
|
||||
<v-list-item link @click="deleteWithoutFiles">
|
||||
<v-icon color="red">
|
||||
{{ mdiDelete }}
|
||||
</v-icon>
|
||||
<v-list-item-title
|
||||
class="ml-2"
|
||||
style="font-size: 1em; color: red"
|
||||
>
|
||||
Delete
|
||||
</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item link @click="deleteWithFiles">
|
||||
<v-icon color="red">
|
||||
{{ mdiDeleteForever }}
|
||||
</v-icon>
|
||||
<v-list-item-title
|
||||
class="ml-2"
|
||||
style="font-size: 1em; color: red"
|
||||
>
|
||||
Delete with files
|
||||
</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-divider />
|
||||
<v-list-item link @click="recheck">
|
||||
<v-icon>{{ mdiPlaylistCheck }}</v-icon>
|
||||
<v-list-item-title
|
||||
class="ml-2"
|
||||
style="font-size: 1em"
|
||||
>
|
||||
Force recheck
|
||||
</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item link @click="reannounce">
|
||||
<v-icon>{{ mdiBullhorn }}</v-icon>
|
||||
<v-list-item-title
|
||||
class="ml-2"
|
||||
style="font-size: 1em"
|
||||
>
|
||||
Force reannounce
|
||||
</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-divider />
|
||||
<v-menu
|
||||
open-on-hover
|
||||
top
|
||||
>
|
||||
<template #activator="{ on }">
|
||||
<v-list-item link v-on="on">
|
||||
<v-icon>{{ mdiShape }}</v-icon>
|
||||
<v-list-item-title
|
||||
class="ml-2"
|
||||
style="font-size: 1em"
|
||||
>
|
||||
Set Category
|
||||
<v-icon>{{ mdiChevronRight }}</v-icon>
|
||||
</v-list-item-title>
|
||||
</v-list-item>
|
||||
</template>
|
||||
<v-list dense rounded>
|
||||
<v-list-item
|
||||
v-for="(item, index) in availableCategories"
|
||||
:key="index"
|
||||
link
|
||||
@click="setCategory(item.value)"
|
||||
>
|
||||
<v-list-item-title class="ml-2" style="font-size: 12px">
|
||||
{{ item.name }}
|
||||
</v-list-item-title>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
</v-list>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import qbit from '@/services/qbit'
|
||||
import { General, TorrentSelect } from '@/mixins'
|
||||
import {
|
||||
mdiBullhorn, mdiArrowUp, mdiArrowDown, mdiPriorityLow,
|
||||
mdiDeleteForever, mdiDelete, mdiPlaylistCheck, mdiShape,
|
||||
mdiPlay, mdiPause, mdiSelect, mdiPriorityHigh, mdiFastForward,
|
||||
mdiChevronRight
|
||||
} from '@mdi/js'
|
||||
import { mapGetters, mapState } from 'vuex'
|
||||
export default {
|
||||
name: 'TorrentMultipleRightClickMenu',
|
||||
mixins: [General, TorrentSelect],
|
||||
data: () => ({
|
||||
priority_options: [
|
||||
{ name: 'top', icon: mdiPriorityHigh, action: 'topPrio' },
|
||||
{ name: 'increase', icon: mdiArrowUp, action: 'increasePrio' },
|
||||
{ name: 'decrease', icon: mdiArrowDown, action: 'decreasePrio' },
|
||||
{ name: 'bottom', icon: mdiPriorityLow, action: 'bottomPrio' }
|
||||
],
|
||||
mdiDelete, mdiPlay, mdiPause, mdiSelect, mdiFastForward,
|
||||
mdiDeleteForever, mdiBullhorn, mdiPlaylistCheck, mdiShape,
|
||||
mdiChevronRight
|
||||
}),
|
||||
computed: {
|
||||
...mapState(['selected_torrents']),
|
||||
...mapGetters(['getCategories']),
|
||||
availableCategories() {
|
||||
const categories = [
|
||||
{ name: 'None', value: '' }]
|
||||
categories.push(...this.getCategories().map(c => {
|
||||
return { name: c.name, value: c.name }
|
||||
}))
|
||||
|
||||
return categories
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
resume() {
|
||||
qbit.resumeTorrents(this.selected_torrents)
|
||||
},
|
||||
pause() {
|
||||
qbit.pauseTorrents(this.selected_torrents)
|
||||
},
|
||||
reannounce() {
|
||||
qbit.reannounceTorrents(this.selected_torrents)
|
||||
},
|
||||
deleteWithoutFiles() {
|
||||
qbit.deleteTorrents(this.selected_torrents, false)
|
||||
},
|
||||
deleteWithFiles() {
|
||||
qbit.deleteTorrents(this.selected_torrents, true)
|
||||
},
|
||||
recheck() {
|
||||
qbit.recheckTorrents(this.selected_torrents)
|
||||
},
|
||||
forceResume() {
|
||||
qbit.forceStartTorrents(this.selected_torrents)
|
||||
},
|
||||
setCategory(cat) {
|
||||
qbit.setCategory(this.selected_torrents, cat)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -52,42 +52,88 @@
|
|||
</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-divider />
|
||||
<v-list-item link @click="location">
|
||||
<v-icon>{{ mdiFolder }}</v-icon>
|
||||
<v-list-item-title
|
||||
class="ml-2"
|
||||
style="font-size: 1em"
|
||||
>
|
||||
Change location
|
||||
</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item link @click="rename">
|
||||
<v-icon>{{ mdiRenameBox }}</v-icon>
|
||||
<v-list-item-title
|
||||
class="ml-2"
|
||||
style="font-size: 1em"
|
||||
>
|
||||
Rename
|
||||
</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item link @click="recheck">
|
||||
<v-icon>{{ mdiPlaylistCheck }}</v-icon>
|
||||
<v-list-item-title
|
||||
class="ml-2"
|
||||
style="font-size: 1em"
|
||||
>
|
||||
Force recheck
|
||||
</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item link @click="reannounce">
|
||||
<v-icon>{{ mdiBullhorn }}</v-icon>
|
||||
<v-list-item-title
|
||||
class="ml-2"
|
||||
style="font-size: 1em"
|
||||
>
|
||||
Force reannounce
|
||||
</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-menu
|
||||
open-on-hover
|
||||
top
|
||||
>
|
||||
<template #activator="{ on }">
|
||||
<v-list-item link v-on="on">
|
||||
<v-icon>{{ mdiHeadCog }}</v-icon>
|
||||
<v-list-item-title
|
||||
class="ml-2"
|
||||
style="font-size: 1em"
|
||||
>
|
||||
Advanced
|
||||
<v-icon>{{ mdiChevronRight }}</v-icon>
|
||||
</v-list-item-title>
|
||||
</v-list-item>
|
||||
</template>
|
||||
<v-list dense rounded>
|
||||
<v-list-item v-if="!multiple" link @click="location">
|
||||
<v-icon>{{ mdiFolder }}</v-icon>
|
||||
<v-list-item-title
|
||||
class="ml-2"
|
||||
style="font-size: 1em"
|
||||
>
|
||||
Change location
|
||||
</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item v-if="!multiple" link @click="rename">
|
||||
<v-icon>{{ mdiRenameBox }}</v-icon>
|
||||
<v-list-item-title
|
||||
class="ml-2"
|
||||
style="font-size: 1em"
|
||||
>
|
||||
Rename
|
||||
</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item link @click="recheck">
|
||||
<v-icon>{{ mdiPlaylistCheck }}</v-icon>
|
||||
<v-list-item-title
|
||||
class="ml-2"
|
||||
style="font-size: 1em"
|
||||
>
|
||||
Force recheck
|
||||
</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item link @click="reannounce">
|
||||
<v-icon>{{ mdiBullhorn }}</v-icon>
|
||||
<v-list-item-title
|
||||
class="ml-2"
|
||||
style="font-size: 1em"
|
||||
>
|
||||
Force reannounce
|
||||
</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item v-if="!multiple" link @click="toggleSeq">
|
||||
<v-icon> {{ torrent.seq_dl ? mdiCheckboxMarked : mdiCheckboxBlankOutline }} </v-icon>
|
||||
<v-list-item-title
|
||||
class="ml-2"
|
||||
style="font-size: 1em"
|
||||
>
|
||||
Sequential Download
|
||||
</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item v-if="!multiple" link @click="toggleFL">
|
||||
<v-icon> {{ torrent.f_l_piece_prio ? mdiCheckboxMarked : mdiCheckboxBlankOutline }} </v-icon>
|
||||
<v-list-item-title
|
||||
class="ml-2"
|
||||
style="font-size: 1em"
|
||||
>
|
||||
First/Last priority
|
||||
</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item v-if="!multiple" link @click="toggleAutoTMM">
|
||||
<v-icon> {{ torrent.auto_tmm ? mdiCheckboxMarked : mdiCheckboxBlankOutline }} </v-icon>
|
||||
<v-list-item-title
|
||||
class="ml-2"
|
||||
style="font-size: 1em"
|
||||
>
|
||||
Auto TMM
|
||||
</v-list-item-title>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
<v-menu
|
||||
open-on-hover
|
||||
top
|
||||
|
@ -147,8 +193,8 @@
|
|||
</v-list-item>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
<v-divider />
|
||||
<v-list-item link @click="showInfo">
|
||||
<v-divider v-if="!multiple" />
|
||||
<v-list-item v-if="!multiple" link @click="showInfo">
|
||||
<v-icon>{{ mdiInformation }}</v-icon>
|
||||
<v-list-item-title
|
||||
class="ml-2"
|
||||
|
@ -157,7 +203,7 @@
|
|||
Show Info
|
||||
</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item link @click="selectTorrent(hash)">
|
||||
<v-list-item v-if="!multiple" link @click="selectTorrent(hash)">
|
||||
<v-icon>{{ mdiSelect }}</v-icon>
|
||||
<v-list-item-title
|
||||
class="ml-2"
|
||||
|
@ -169,20 +215,21 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters, mapState } from 'vuex'
|
||||
import qbit from '@/services/qbit'
|
||||
import { General, TorrentSelect } from '@/mixins'
|
||||
import {
|
||||
mdiBullhorn, mdiPlaylistCheck, mdiArrowUp, mdiArrowDown, mdiPriorityLow,
|
||||
mdiInformation, mdiDeleteForever, mdiRenameBox, mdiFolder, mdiDelete,
|
||||
mdiPlay, mdiPause, mdiSelect, mdiPriorityHigh, mdiChevronRight,
|
||||
mdiFastForward, mdiShape
|
||||
mdiFastForward, mdiShape, mdiHeadCog, mdiCheckboxMarked, mdiCheckboxBlankOutline
|
||||
} from '@mdi/js'
|
||||
import { mapGetters } from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'TorrentRightClickMenu',
|
||||
mixins: [General, TorrentSelect],
|
||||
props: {
|
||||
hash: String
|
||||
torrent: Object
|
||||
},
|
||||
data: () => ({
|
||||
priority_options: [
|
||||
|
@ -194,10 +241,11 @@ export default {
|
|||
mdiDelete, mdiPlay, mdiPause, mdiSelect, mdiFastForward,
|
||||
mdiFolder, mdiRenameBox, mdiDeleteForever, mdiInformation,
|
||||
mdiPlaylistCheck, mdiPriorityHigh, mdiBullhorn, mdiChevronRight,
|
||||
mdiShape
|
||||
mdiShape, mdiHeadCog, mdiCheckboxMarked, mdiCheckboxBlankOutline
|
||||
}),
|
||||
computed: {
|
||||
...mapGetters(['getCategories']),
|
||||
...mapState(['selected_torrents']),
|
||||
availableCategories() {
|
||||
const categories = [
|
||||
{ name: 'None', value: '' }]
|
||||
|
@ -206,44 +254,61 @@ export default {
|
|||
}))
|
||||
|
||||
return categories
|
||||
},
|
||||
hashes() {
|
||||
if (this.multiple) return this.selected_torrents
|
||||
|
||||
return [this.torrent.hash]
|
||||
},
|
||||
multiple() {
|
||||
return this.selected_torrents.length
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
resume() {
|
||||
qbit.resumeTorrents([this.hash])
|
||||
qbit.resumeTorrents(this.hashes)
|
||||
},
|
||||
pause() {
|
||||
qbit.pauseTorrents([this.hash])
|
||||
qbit.pauseTorrents(this.hashes)
|
||||
},
|
||||
location() {
|
||||
this.createModal('ChangeLocationModal', { hash: this.hash })
|
||||
this.createModal('ChangeLocationModal', { hash: this.torrent.hash })
|
||||
},
|
||||
rename() {
|
||||
this.createModal('RenameModal', { hash: this.hash })
|
||||
this.createModal('RenameModal', { hash: this.torrent.hash })
|
||||
},
|
||||
reannounce() {
|
||||
qbit.reannounceTorrents([this.hash])
|
||||
qbit.reannounceTorrents(this.hashes)
|
||||
},
|
||||
deleteWithoutFiles() {
|
||||
qbit.deleteTorrents([this.hash], false)
|
||||
qbit.deleteTorrents(this.hashes, false)
|
||||
},
|
||||
deleteWithFiles() {
|
||||
qbit.deleteTorrents([this.hash], true)
|
||||
qbit.deleteTorrents(this.hashes, true)
|
||||
},
|
||||
recheck() {
|
||||
qbit.recheckTorrents([this.hash])
|
||||
qbit.recheckTorrents(this.hashes)
|
||||
},
|
||||
showInfo() {
|
||||
this.createModal('TorrentDetailModal', { hash: this.hash })
|
||||
this.createModal('TorrentDetailModal', { hash: this.torrent.hash })
|
||||
},
|
||||
setPriority(priority) {
|
||||
qbit.setTorrentPriority(this.hash, priority)
|
||||
},
|
||||
forceResume() {
|
||||
qbit.forceStartTorrents([this.hash])
|
||||
qbit.forceStartTorrents(this.hashes)
|
||||
},
|
||||
setCategory(cat) {
|
||||
qbit.setCategory([this.hash], cat)
|
||||
qbit.setCategory(this.hashes, cat)
|
||||
},
|
||||
toggleSeq() {
|
||||
qbit.toggleSequentialDownload(this.hashes)
|
||||
},
|
||||
toggleFL() {
|
||||
qbit.toggleFirstLastPiecePriority(this.hashes)
|
||||
},
|
||||
toggleAutoTMM() {
|
||||
qbit.setAutoTMM(this.hashes, !this.torrent.auto_tmm)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import '@/registerServiceWorker'
|
|||
import router from '@/router'
|
||||
import store from '@/store'
|
||||
import '@babel/polyfill'
|
||||
import vuetify from './plugins/vuetify'
|
||||
import 'typeface-roboto'
|
||||
import 'typeface-roboto-mono'
|
||||
|
||||
|
@ -12,13 +13,8 @@ import filters from '@/filters'
|
|||
import styles from '@/styles/styles.scss'
|
||||
/* eslint-enable no-unused-vars */
|
||||
|
||||
import VueObserveVisibility from 'vue-observe-visibility'
|
||||
|
||||
Vue.use(VueObserveVisibility)
|
||||
|
||||
import Toast from 'vue-toastification'
|
||||
import 'vue-toastification/dist/index.css'
|
||||
import vuetify from './plugins/vuetify'
|
||||
Vue.use(Toast, {
|
||||
maxToasts: 5,
|
||||
timeout: 2000
|
||||
|
@ -26,7 +22,6 @@ Vue.use(Toast, {
|
|||
|
||||
import PerfectScrollbar from 'vue2-perfect-scrollbar'
|
||||
import 'vue2-perfect-scrollbar/dist/vue2-perfect-scrollbar.css'
|
||||
|
||||
Vue.use(PerfectScrollbar)
|
||||
|
||||
import './registerServiceWorker'
|
||||
|
|
|
@ -23,6 +23,10 @@ export default class Torrent {
|
|||
this.category = data.category
|
||||
this.tracker = data.tracker
|
||||
this.comment = data.comment
|
||||
this.f_l_piece_prio = data.f_l_piece_prio
|
||||
this.seq_dl = data.seq_dl
|
||||
this.auto_tmm = data.auto_tmm
|
||||
|
||||
Object.freeze(this)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import Vue from 'vue'
|
||||
import Vuetify from 'vuetify'
|
||||
import 'vuetify/dist/vuetify.min.css'
|
||||
import Vuetify from 'vuetify/lib'
|
||||
|
||||
import colors from 'vuetify/lib/util/colors'
|
||||
import variables from '@/styles/colors.scss'
|
||||
|
|
|
@ -199,6 +199,18 @@ class Qbit {
|
|||
return this.torrentAction('setForceStart', hashes, { value: true })
|
||||
}
|
||||
|
||||
toggleSequentialDownload(hashes) {
|
||||
return this.torrentAction('toggleSequentialDownload', hashes)
|
||||
}
|
||||
|
||||
toggleFirstLastPiecePriority(hashes) {
|
||||
return this.torrentAction('toggleFirstLastPiecePrio', hashes)
|
||||
}
|
||||
|
||||
setAutoTMM(hashes, enable) {
|
||||
return this.torrentAction('setAutoManagement', hashes, { enable })
|
||||
}
|
||||
|
||||
reannounceTorrents(hashes) {
|
||||
return this.torrentAction('reannounce', hashes)
|
||||
}
|
||||
|
|
|
@ -8,8 +8,7 @@ const vuexPersist = new VuexPersist({
|
|||
reducer: state => ({
|
||||
sort_options: state.sort_options,
|
||||
webuiSettings: state.webuiSettings,
|
||||
authenticated: state.authenticated,
|
||||
torrents: state.torrents
|
||||
authenticated: state.authenticated
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -64,6 +63,7 @@ export default new Vuex.Store({
|
|||
busyTorrentProperties: [
|
||||
{ name: 'Size', active: true },
|
||||
{ name: 'Progress', active: true },
|
||||
{ name: 'Downloaded', active: true },
|
||||
{ name: 'Download', active: true },
|
||||
{ name: 'Upload', active: true },
|
||||
{ name: 'ETA', active: true },
|
||||
|
@ -78,6 +78,7 @@ export default new Vuex.Store({
|
|||
doneTorrentProperties: [
|
||||
{ name: 'Size', active: true },
|
||||
{ name: 'Progress', active: true },
|
||||
{ name: 'Downloaded', active: true },
|
||||
{ name: 'Download', active: true },
|
||||
{ name: 'Upload', active: true },
|
||||
{ name: 'ETA', active: true },
|
||||
|
|
|
@ -92,13 +92,17 @@ export default {
|
|||
const { data } = await qbit.getAppPreferences()
|
||||
state.settings = data
|
||||
},
|
||||
UPDATE_SORT_OPTIONS: (state, payload) => {
|
||||
state.sort_options.sort = payload.name ? payload.name : state.sort_options.sort
|
||||
state.sort_options.reverse = payload.reverse
|
||||
state.sort_options.hashes = payload.hashes ? payload.hashes : state.sort_options.hashes
|
||||
state.sort_options.filter = payload.filter
|
||||
state.sort_options.category = payload.category
|
||||
state.sort_options.tracker = payload.tracker
|
||||
UPDATE_SORT_OPTIONS: (state, {
|
||||
reverse = false,
|
||||
hashes = [],
|
||||
filter = null, category = null,
|
||||
tracker = null
|
||||
}) => {
|
||||
state.sort_options.reverse = reverse
|
||||
state.sort_options.hashes = hashes
|
||||
state.sort_options.filter = filter
|
||||
state.sort_options.category = category
|
||||
state.sort_options.tracker = tracker
|
||||
},
|
||||
FETCH_CATEGORIES: async state => state.categories = Object.values(await (qbit.getCategories())),
|
||||
FETCH_SEARCH_PLUGINS: async state => state.searchPlugins = await qbit.getSearchPlugins(),
|
||||
|
|
|
@ -78,8 +78,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<vue-context ref="menu" v-slot="{ data }">
|
||||
<TorrentRightClickMenu v-if="data && !selected_torrents.length" :hash="data.torrent.hash" />
|
||||
<TorrentMultipleRightClickMenu v-if="data && selected_torrents.length" />
|
||||
<TorrentRightClickMenu v-if="data" :torrent="data.torrent" />
|
||||
</vue-context>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -94,13 +93,12 @@ import 'vue-context/src/sass/vue-context.scss'
|
|||
|
||||
import Torrent from '@/components/Torrent/Torrent'
|
||||
import TorrentRightClickMenu from '@/components/Torrent/TorrentRightClickMenu.vue'
|
||||
import TorrentMultipleRightClickMenu from '@/components/Torrent/TorrentMultipleRightClickMenu.vue'
|
||||
|
||||
import { TorrentSelect, General } from '@/mixins'
|
||||
|
||||
export default {
|
||||
name: 'Dashboard',
|
||||
components: { Torrent, VueContext, TorrentRightClickMenu, TorrentMultipleRightClickMenu },
|
||||
components: { Torrent, VueContext, TorrentRightClickMenu },
|
||||
mixins: [TorrentSelect, General],
|
||||
data() {
|
||||
return {
|
||||
|
|
13
tests/helpers.js
Normal file
13
tests/helpers.js
Normal file
|
@ -0,0 +1,13 @@
|
|||
import { createLocalVue, mount } from '@vue/test-utils'
|
||||
import Vuetify from 'vuetify'
|
||||
|
||||
export function setup(component, propsData) {
|
||||
const localVue = createLocalVue() // because of vuetify, we should use a localVue instance
|
||||
const vuetify = new Vuetify()
|
||||
|
||||
return mount(component, {
|
||||
localVue,
|
||||
vuetify,
|
||||
propsData
|
||||
})
|
||||
}
|
7
tests/setup.js
Normal file
7
tests/setup.js
Normal file
|
@ -0,0 +1,7 @@
|
|||
import Vue from 'vue'
|
||||
import Vuetify from 'vuetify'
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
import filters from '@/filters'
|
||||
|
||||
Vue.use(Vuetify)
|
||||
Vue.config.productionTip = false
|
29
tests/unit/SpeedCard.spec.js
Normal file
29
tests/unit/SpeedCard.spec.js
Normal file
|
@ -0,0 +1,29 @@
|
|||
import { setup } from '../helpers'
|
||||
import SpeedCard from '@/components/Core/SpeedCard.vue'
|
||||
import { mdiChevronDown } from '@mdi/js'
|
||||
|
||||
describe('SpeedCard.vue', () => {
|
||||
it('should render the card', () => {
|
||||
const wrapper = setup(SpeedCard)
|
||||
expect(wrapper.find('[data-testid="SpeedCard"]').exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('shouldn\'t render the icon', () => {
|
||||
const wrapper = setup(SpeedCard)
|
||||
expect(wrapper.find('[data-testid="SpeedCard-icon"]').exists()).toBe(false)
|
||||
})
|
||||
|
||||
it('should render the icon', () => {
|
||||
const wrapper = setup(SpeedCard, { icon: mdiChevronDown })
|
||||
expect(wrapper.find('[data-testid="SpeedCard-icon"]').exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('should render value and unit & be formatted', () => {
|
||||
const wrapper = setup(SpeedCard, { value: 10000 })
|
||||
expect(wrapper.find('[data-testid="SpeedCard-value"]').exists()).toBe(true)
|
||||
expect(wrapper.find('[data-testid="SpeedCard-value"]').text()).toBe('9.77')
|
||||
|
||||
expect(wrapper.find('[data-testid="SpeedCard-unit"]').exists()).toBe(true)
|
||||
expect(wrapper.find('[data-testid="SpeedCard-unit"]').text()).toBe('KB/s')
|
||||
})
|
||||
})
|
27
tests/unit/StorageCard.spec.js
Normal file
27
tests/unit/StorageCard.spec.js
Normal file
|
@ -0,0 +1,27 @@
|
|||
import { setup } from '../helpers'
|
||||
import StorageCard from '@/components/Core/StorageCard'
|
||||
|
||||
describe('StorageCard.vue', () => {
|
||||
|
||||
it('should render the label', () => {
|
||||
const label = 'Downloaded'
|
||||
const wrapper = setup(StorageCard, { label })
|
||||
expect(wrapper.find('[data-testid="StorageCard-label"]').text()).toEqual(label)
|
||||
})
|
||||
|
||||
it('should render value and unit & be formatted', () => {
|
||||
const wrapper = setup(StorageCard, { value: 10000 })
|
||||
expect(wrapper.find('[data-testid="StorageCard-value"]').exists()).toBe(true)
|
||||
expect(wrapper.find('[data-testid="StorageCard-value"]').text()).toBe('9.77')
|
||||
|
||||
expect(wrapper.find('[data-testid="StorageCard-unit"]').exists()).toBe(true)
|
||||
expect(wrapper.find('[data-testid="StorageCard-unit"]').text()).toBe('KB')
|
||||
})
|
||||
|
||||
it('text should have the passed-in color', () => {
|
||||
const color = 'download'
|
||||
const wrapper = setup(StorageCard, { color })
|
||||
expect(wrapper.find('[data-testid="StorageCard-label"]').classes()).toContain(color + '--text')
|
||||
expect(wrapper.find('[data-testid="StorageCard-Wrapper"]').classes()).toContain(color + '--text')
|
||||
})
|
||||
})
|
|
@ -5,7 +5,7 @@ module.exports = {
|
|||
.plugin('html')
|
||||
.tap(args => {
|
||||
args[0].title = 'VueTorrent'
|
||||
|
||||
|
||||
return args
|
||||
})
|
||||
},
|
||||
|
|
Loading…
Reference in a new issue