This commit is contained in:
Daan Wijns 2020-10-28 14:44:40 +01:00 committed by GitHub
parent dde4c08f2b
commit 0b4b234b4b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
41 changed files with 1143 additions and 734 deletions

8
.github/pull_request_template.md vendored Normal file
View file

@ -0,0 +1,8 @@
# Features
- a
# Fixes
- b
# Improvements
- c

View file

@ -71,6 +71,14 @@ The sleekest looking WEBUI for qBittorrent made with Vuejs!
- tag & category support
- Keyboard shortcuts!
- select all torrents with ctrl+A
- delete selected with delete button
- select with ctrl+click or from right-click-menu (regular tap on mobile still works)
- shift + click to select from one torrent until another
- Which torrent properties are shown is configurable in the dashboard (for both busy and completed torrents)
- works on QBittorrent V4.2 and later
## Contributing

15
package-lock.json generated
View file

@ -1,6 +1,6 @@
{
"name": "vuetorrent",
"version": "0.4.0",
"version": "0.4.2",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@ -10795,6 +10795,11 @@
"is-plain-obj": "^1.0.0"
}
},
"sortablejs": {
"version": "1.12.0",
"resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.12.0.tgz",
"integrity": "sha512-bPn57rCjBRlt2sC24RBsu40wZsmLkSo2XeqG8k6DC1zru5eObQUIPPZAQG7W2SJ8FZQYq+BEJmvuw1Zxb3chqg=="
},
"source-list-map": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz",
@ -12143,6 +12148,14 @@
"postcss-import": "^12.0.0"
}
},
"vuedraggable": {
"version": "2.24.2",
"resolved": "https://registry.npmjs.org/vuedraggable/-/vuedraggable-2.24.2.tgz",
"integrity": "sha512-y1NbVhLFOVHHdJl7qsYOtExiTq4zyxF+PxiF9NC8kHEtI6sAFhUHtHYp+ONa8v4S3bAspzGHOHuOq0pNO4fFtA==",
"requires": {
"sortablejs": "^1.10.1"
}
},
"vuetify": {
"version": "2.3.14",
"resolved": "https://registry.npmjs.org/vuetify/-/vuetify-2.3.14.tgz",

View file

@ -1,8 +1,9 @@
{
"name": "vuetorrent",
"version": "0.4.1",
"version": "0.4.2",
"private": true,
"scripts": {
"start" : "npm run serve",
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
@ -24,6 +25,7 @@
"vue-router": "^3.4.7",
"vue-toastification": "^1.7.8",
"vue2-perfect-scrollbar": "^1.5.0",
"vuedraggable": "^2.24.2",
"vuetify": "^2.3.14",
"vuex": "^3.5.1",
"vuex-persist": "^3.1.3"

View file

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

View file

@ -10,39 +10,45 @@
<v-container>
<v-row no-gutters>
<v-col ref="fileZone">
<v-file-input
v-model="files"
color="deep-purple accent-4"
counter
label="File input"
multiple
placeholder="Select your files"
prepend-icon="mdi-paperclip"
outlined
:show-size="1000"
<div
@drop.prevent="addDropFile"
@dragover.prevent
>
<template
v-slot:selection="{ index, text }"
<v-file-input
v-if="!urls"
v-model="files"
color="deep-purple accent-4"
counter
label="File input"
multiple
placeholder="Select your files"
prepend-icon="mdi-paperclip"
:rules="fileInputRules"
outlined
:show-size="1000"
>
<v-chip
v-if="index < 2"
color="deep-purple accent-4"
dark
label
small
>{{ text }}</v-chip
<template
v-slot:selection="{ index, text }"
>
<span
v-else-if="index === 2"
class="overline grey--text text--darken-3 mx-2"
>+{{
files.length - 2
}}
File(s)</span
>
</template>
</v-file-input>
<v-chip
v-if="index < 2"
color="deep-purple accent-4"
dark
label
small
>{{ text }}</v-chip
>
<span
v-else-if="index === 2"
class="overline grey--text text--darken-3 mx-2"
>+{{
files.length - 2
}}
File(s)</span
>
</template>
</v-file-input>
</div>
<v-textarea
label="URL"
prepend-icon="mdi-link"
@ -123,12 +129,11 @@ export default {
directory: '',
autoTMM: true,
skip_checking: false,
inputRules: [
v =>
v.indexOf('magnet') > -1 ||
v.indexOf('http') > -1 ||
this.validFile ||
'Not a valid magnet link'
fileInputRules: [
v => {
const result = v.every(f => f.type === 'application/x-bittorrent' )
return result ? result : 'One or more files is not a valid torrent'
}
],
loading: false,
urls: null,
@ -136,6 +141,10 @@ export default {
}
},
methods: {
addDropFile(e)
{
this.files.push(...Array.from(e.dataTransfer.files))
},
submit() {
if (this.files.length || this.urls) {
let torrents = []

View file

@ -2,7 +2,7 @@
<v-dialog
v-model="dialog"
scrollable
:width="dialogWidth"
width="85%"
:fullscreen="phoneLayout"
>
<v-card style="min-height: 400px; overflow: hidden !important">
@ -102,9 +102,6 @@ export default {
},
computed: {
...mapGetters(['getSettings']),
dialogWidth() {
return this.phoneLayout ? '100%' : '750px'
},
dialogHeight() {
return this.phoneLayout ? '79vh' : '70vh'
}

View file

@ -1,7 +1,7 @@
<template>
<v-container>
<v-container class="pa-3 ma-2">
<v-card flat>
<v-card-text>
<v-card-text :style="{ minHeight: phoneLayout ? '' : '75vh'}">
<h3>Privacy</h3>
<div class="settings_content ml-5 mr-5">
<v-checkbox
@ -144,11 +144,11 @@
</template>
<script>
import SettingsTab from '@/mixins/SettingsTab'
import {SettingsTab, FullScreenModal} from '@/mixins'
export default {
name: 'BitTorrent',
mixins: [SettingsTab]
mixins: [SettingsTab, FullScreenModal]
}
</script>

View file

@ -1,7 +1,7 @@
<template>
<v-container>
<v-container class="pa-3 ma-2">
<v-card flat>
<v-card-text>
<v-card-text :style="{ minHeight: phoneLayout ? '' : '75vh'}">
<h3>When adding a torrent</h3>
<div class="settings_content ml-5 mr-5">
<v-checkbox
@ -69,11 +69,11 @@
</template>
<script>
import SettingsTab from '@/mixins/SettingsTab'
import {FullScreenModal, SettingsTab} from '@/mixins'
export default {
name: 'Downloads',
mixins: [SettingsTab]
mixins: [SettingsTab, FullScreenModal]
}
</script>

View file

@ -2,7 +2,8 @@
<v-card flat>
<v-card-text
class="mx-auto mt-5"
style="font-size: 1.1em; max-height: 500px; min-height: 300px"
style="font-size: 1.1em;"
:style="{ minHeight: phoneLayout ? '' : '75vh'}"
>
<v-layout class="mx-auto" row wrap>
<v-flex xs12 sm12>
@ -72,11 +73,11 @@ import { mapGetters } from 'vuex'
import qbit from '@/services/qbit'
import { Tab, General } from '@/mixins'
import { Tab, General,FullScreenModal } from '@/mixins'
export default {
name: 'TagsAndCategories',
mixins: [Tab, General],
mixins: [Tab, General, FullScreenModal],
props: {
hash: String
},

View file

@ -1,190 +1,45 @@
<template>
<v-container>
<v-card flat>
<v-card-text class="pa-0" style="font-size: 1.1em">
<div class="box">
<v-subheader
>These settings are for the custom WebUI
itself</v-subheader
>
<v-form class="px-6 mt-3">
<v-container>
<v-switch
class="v-input--reverse v-input--expand pa-0 ma-0"
inset
v-model="showCurrentSpeed"
color="green_accent"
>
<template #label> Show Current Speed </template>
</v-switch>
<v-switch
class="v-input--reverse v-input--expand pa-0 ma-0"
inset
v-model="showSpeedGraph"
color="green_accent"
>
<template #label> Show Speed Graph</template>
</v-switch>
<v-switch
class="v-input--reverse v-input--expand pa-0 ma-0"
inset
v-model="showSessionStat"
color="green_accent"
>
<template #label> Show Session Stats </template>
</v-switch>
<v-switch
class="v-input--reverse v-input--expand pa-0 ma-0"
inset
v-model="freeSpace"
color="green_accent"
>
<template #label> Show Free Space </template>
</v-switch>
<v-switch
class="v-input--reverse v-input--expand pa-0 ma-0"
inset
v-model="showGlobalRemoveResumePause"
color="green_accent"
>
<template #label>
Global Remove/Resume/Pause Buttons</template
>
</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-col cols="10" sm="10" md="11">
<p class="subtitle-1">Current Version:</p>
</v-col>
<v-col cols="2" sm="2" md="1">
<p class="mb-2">{{ version }}</p>
</v-col>
</v-row>
</v-container>
</v-form>
</div>
</v-card-text>
<div class="ma-1">
<v-card flat style="width: 100vh">
<v-card-text :style="{ minHeight: phoneLayout ? '' : '75vh'}">
<v-tabs v-model="tab">
<v-tab href="#general">General</v-tab>
<v-tab href="#dashboard">Dashboard</v-tab>
</v-tabs>
<v-tabs-items
v-model="tab"
:touch="updateTab(tab)"
>
<v-tab-item style="width: 100vh" value="general">
<General :is-active="tab === 'downloads'" />
</v-tab-item>
<v-tab-item style="width: 100vh" value="dashboard">
<Dashboard :is-active="tab === 'bittorrent'" />
</v-tab-item>
</v-tabs-items>
</v-card-text>
</v-card>
</v-container>
</div>
</template>
<script>
import { mapState, mapGetters } from 'vuex'
import General from './Vuetorrent/General'
import Dashboard from './Vuetorrent/Dashboard'
import {FullScreenModal} from '@/mixins'
export default {
name: 'VueTorrent',
data() {
return {
paginationSizes: [5, 15, 30, 50]
}
components: {
General, Dashboard
},
computed: {
...mapState(['webuiSettings']),
...mapGetters(['getAppVersion']),
freeSpace: {
get() {
return this.webuiSettings.showFreeSpace
},
set(val) {
this.webuiSettings.showFreeSpace = val
}
},
showCurrentSpeed: {
get() {
return this.webuiSettings.showCurrentSpeed
},
set(val) {
this.webuiSettings.showCurrentSpeed = val
}
},
showSpeedGraph: {
get() {
return this.webuiSettings.showSpeedGraph
},
set(val) {
this.webuiSettings.showSpeedGraph = val
}
},
showSessionStat: {
get() {
return this.webuiSettings.showSessionStat
},
set(val) {
this.webuiSettings.showSessionStat = val
}
},
showGlobalRemoveResumePause: {
get() {
return this.webuiSettings.showGlobalRemoveResumePause
},
set(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() {
return this.getAppVersion()
mixins: [FullScreenModal],
data : () => ({
tab: null
}),
methods: {
updateTab(tab) {
this.tab = tab
}
}
}
</script>
<style lang="scss" scoped>
@import '@/assets/styles/SettingsTab.scss';
</style>
<style lang="scss" scoped>
// Reversed input variant
::v-deep .v-input--reverse .v-input__slot {
flex-direction: row-reverse;
justify-content: flex-end;
.v-application--is-ltr & {
.v-input--selection-controls__input {
margin-right: 0;
margin-left: 8px;
}
}
.v-application--is-rtl & {
.v-input--selection-controls__input {
margin-left: 0;
margin-right: 8px;
}
}
}
</style>
</script>

View file

@ -0,0 +1,81 @@
<template>
<div class="ma-3 pa-2">
<v-card flat>
<v-card-text class="pa-0">
<v-layout row wrap align-center justify-center>
<v-flex class="box ma-1" style="width:50px !important;" xs12 md5>
<v-subheader>
Properties to display for busy torrents
</v-subheader>
<v-row dense >
<v-list flat class="ma-0 pa-0">
<draggable
:list="busyTorrentProperties"
tag="tbody"
>
<v-list-item
v-for="(item, index) in busyTorrentProperties"
:key="index"
style="width: 30vh"
>
<v-checkbox v-model="item.active" dense class="pa-0 ma-0 mt-3"/>
<v-list-item-content>
<v-list-item-title class="truncate" v-text="item.name"/>
</v-list-item-content>
</v-list-item>
</draggable>
</v-list>
</v-row>
</v-flex>
<v-flex class="box ma-1" xs12 md5>
<v-subheader>
Properties to display for completed torrents
</v-subheader>
<v-row dense>
<v-list flat class="ma-0 pa-0">
<draggable
:list="doneTorrentProperties"
tag="tbody"
>
<v-list-item
v-for="(item, index) in doneTorrentProperties"
:key="index"
style="width: 30vh"
>
<v-checkbox v-model="item.active" dense class="pa-0 ma-0 mt-3"/>
<v-list-item-content>
<v-list-item-title class="truncate" v-text="item.name"/>
</v-list-item-content>
</v-list-item>
</draggable>
</v-list>
</v-row>
</v-flex>
</v-layout>
</v-card-text>
</v-card>
</div>
</template>
<script>
import draggable from 'vuedraggable'
export default {
name: 'Dashboard',
components: {
draggable
},
computed : {
busyTorrentProperties(){
return this.$store.state.webuiSettings.busyTorrentProperties
},
doneTorrentProperties(){
return this.$store.state.webuiSettings.doneTorrentProperties
}
}
}
</script>
<style lang="scss" scoped>
@import '@/assets/styles/SettingsTab.scss';
</style>

View file

@ -0,0 +1,190 @@
<template>
<div class="ma-2">
<v-card flat style="width: 100vh">
<v-card-text class="pa-0" style="font-size: 1.1em">
<div class="box">
<v-subheader
>These settings are for the custom WebUI
itself</v-subheader
>
<v-form class="px-6 mt-3">
<v-container>
<v-switch
class="v-input--reverse v-input--expand pa-0 ma-0"
inset
v-model="showCurrentSpeed"
color="green_accent"
>
<template #label> Show Current Speed </template>
</v-switch>
<v-switch
class="v-input--reverse v-input--expand pa-0 ma-0"
inset
v-model="showSpeedGraph"
color="green_accent"
>
<template #label> Show Speed Graph</template>
</v-switch>
<v-switch
class="v-input--reverse v-input--expand pa-0 ma-0"
inset
v-model="showSessionStat"
color="green_accent"
>
<template #label> Show Session Stats </template>
</v-switch>
<v-switch
class="v-input--reverse v-input--expand pa-0 ma-0"
inset
v-model="freeSpace"
color="green_accent"
>
<template #label> Show Free Space </template>
</v-switch>
<v-switch
class="v-input--reverse v-input--expand pa-0 ma-0"
inset
v-model="showGlobalRemoveResumePause"
color="green_accent"
>
<template #label>
Global Remove/Resume/Pause Buttons</template
>
</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-col cols="10" sm="10" md="11">
<p class="subtitle-1">Current Version:</p>
</v-col>
<v-col cols="2" sm="2" md="1">
<p class="mb-2">{{ version }}</p>
</v-col>
</v-row>
</v-container>
</v-form>
</div>
</v-card-text>
</v-card>
</div>
</template>
<script>
import { mapState, mapGetters } from 'vuex'
export default {
name: 'General',
data() {
return {
paginationSizes: [5, 15, 30, 50]
}
},
computed: {
...mapState(['webuiSettings']),
...mapGetters(['getAppVersion']),
freeSpace: {
get() {
return this.webuiSettings.showFreeSpace
},
set(val) {
this.webuiSettings.showFreeSpace = val
}
},
showCurrentSpeed: {
get() {
return this.webuiSettings.showCurrentSpeed
},
set(val) {
this.webuiSettings.showCurrentSpeed = val
}
},
showSpeedGraph: {
get() {
return this.webuiSettings.showSpeedGraph
},
set(val) {
this.webuiSettings.showSpeedGraph = val
}
},
showSessionStat: {
get() {
return this.webuiSettings.showSessionStat
},
set(val) {
this.webuiSettings.showSessionStat = val
}
},
showGlobalRemoveResumePause: {
get() {
return this.webuiSettings.showGlobalRemoveResumePause
},
set(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() {
return this.getAppVersion()
}
}
}
</script>
<style lang="scss" scoped>
@import '@/assets/styles/SettingsTab.scss';
</style>
<style lang="scss" scoped>
// Reversed input variant
::v-deep .v-input--reverse .v-input__slot {
flex-direction: row-reverse;
justify-content: flex-end;
.v-application--is-ltr & {
.v-input--selection-controls__input {
margin-right: 0;
margin-left: 8px;
}
}
.v-application--is-rtl & {
.v-input--selection-controls__input {
margin-left: 0;
margin-right: 8px;
}
}
}
</style>

View file

@ -1,7 +1,9 @@
<template>
<v-container>
<div class="ma-3 pa-2">
<v-card flat>
<v-card-text class="pa-0" style="font-size: 1.1em">
<v-card-text class="pa-0" style="font-size: 1.1em"
:style="{ minHeight: phoneLayout ? '' : '75vh'}"
>
<div class="box">
<v-subheader>Use Alternative WebUI</v-subheader>
<div class="ml-5 mr-5">
@ -49,15 +51,15 @@
</div>
</v-card-text>
</v-card>
</v-container>
</div>
</template>
<script>
import SettingsTab from '@/mixins/SettingsTab'
import {FullScreenModal, SettingsTab} from '@/mixins'
export default {
name: 'WebUI',
mixins: [SettingsTab]
mixins: [SettingsTab, FullScreenModal]
}
</script>

View file

@ -1,77 +1,77 @@
<template>
<v-card flat>
<perfect-scrollbar>
<v-card-text style="max-height: 500px; min-height: 400px">
<v-treeview
v-model="selected"
:items="fileTree"
:open.sync="opened"
activatable
selectable
item-key="fullName"
open-all
>
<template v-slot:prepend="{ item, open }">
<v-icon v-if="!item.icon">
{{ open ? 'mdi-folder-open' : 'mdi-folder' }}
</v-icon>
<v-icon v-else>{{ item.icon }}</v-icon>
</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 }">
<span v-if="!item.icon"
>{{ item.children.length }} Files</span
>
<div v-else>
<span>[{{ item.size }}]</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>
</template>
</v-treeview>
</v-card-text>
</perfect-scrollbar>
</v-card>
<v-card flat>
<perfect-scrollbar>
<v-card-text :style="{ minHeight: phoneLayout ? '' : '75vh'}">
<v-treeview
v-model="selected"
:items="fileTree"
:open.sync="opened"
activatable
selectable
item-key="fullName"
open-all
>
<template v-slot:prepend="{ item, open }">
<v-icon v-if="!item.icon">
{{ open ? "mdi-folder-open" : "mdi-folder" }}
</v-icon>
<v-icon v-else>{{ item.icon }}</v-icon>
</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 }">
<span v-if="!item.icon">{{ item.children.length }} Files</span>
<div v-else>
<span>[{{ item.size }}]</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>
</template>
</v-treeview>
</v-card-text>
</perfect-scrollbar>
</v-card>
</template>
<script>
import qbit from '@/services/qbit'
import { treeify } from '@/helpers'
import { FullScreenModal } from '@/mixins'
export default {
name: 'Content',
mixins: [FullScreenModal],
props: {
hash: String,
isActive: Boolean
@ -102,16 +102,18 @@ export default {
},
async changeFilePriorities(newValue, oldValue) {
if (newValue.length == oldValue.length) return
const filesToExclude = oldValue.filter(f => !newValue.includes(f))
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))
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)
@ -122,7 +124,7 @@ export default {
togleEditing(item) {
item.editing = !item.editing
},
edit(item){
edit(item) {
item.newName = item.name
this.togleEditing(item)
},
@ -143,13 +145,16 @@ export default {
}
},
created() {
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)
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)
})
}

View file

@ -1,115 +1,113 @@
<template>
<v-card flat>
<perfect-scrollbar>
<v-card-text
style="font-size: 1.1em; min-height: 400px;"
:style="{ maxHeight: phoneLayout ? '' : '500px' }"
>
<v-simple-table>
<tbody>
<tr>
<td class="grey--text">Torrent title</td>
<td class="torrentmodaltext--text">
{{ torrent.name }}
</td>
</tr>
<tr>
<td class="grey--text">Directory</td>
<td class="torrentmodaltext--text">
{{ torrent.savePath }}
</td>
</tr>
<tr style="margin-top: 10px !important">
<td class="grey--text">hash</td>
<td class="torrentmodaltext--text">
{{ torrent.hash }}
</td>
</tr>
<tr>
<td class="grey--text">Size</td>
<td class="torrentmodaltext--text">
{{ torrent.size }}
</td>
</tr>
<tr>
<td class="grey--text">Done:</td>
<td class="torrentmodaltext--text">
{{ torrent.dloaded }}
</td>
</tr>
<tr>
<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">
{{ torrent.dlspeed }}
</td>
</tr>
<tr>
<td class="grey--text">Upload Speed</td>
<td class="torrentmodaltext--text">
{{ torrent.upspeed }}
</td>
</tr>
<tr>
<td class="grey--text">ETA</td>
<td class="torrentmodaltext--text">
{{ torrent.eta }}
</td>
</tr>
<tr>
<td class="grey--text">Peers</td>
<td class="torrentmodaltext--text">
{{ torrent.num_leechs
}}<span class="grey--text"
>/{{ torrent.available_peers }}</span
>
</td>
</tr>
<tr>
<td class="grey--text">Seeds</td>
<td class="torrentmodaltext--text">
{{ torrent.num_seeds
}}<span class="grey--text"
>/{{ torrent.available_seeds }}</span
>
</td>
</tr>
<tr>
<td class="grey--text">Added on</td>
<td class="torrentmodaltext--text">
{{ torrent.added_on }}
</td>
</tr>
<tr>
<td class="grey--text">Status</td>
<v-chip
small
:class="`${torrent.state.toLowerCase()} white--text my-2 caption`"
>{{ torrent.state }}</v-chip
>
</tr>
</tbody>
</v-simple-table>
</v-card-text>
</perfect-scrollbar>
</v-card>
<v-card flat>
<perfect-scrollbar>
<v-card-text
style="font-size: 1.1em"
:style="{ minHeight: phoneLayout ? '' : '75vh'}"
>
<v-simple-table>
<tbody>
<tr>
<td class="grey--text">Torrent title</td>
<td class="torrentmodaltext--text">
{{ torrent.name }}
</td>
</tr>
<tr>
<td class="grey--text">Directory</td>
<td class="torrentmodaltext--text">
{{ torrent.savePath }}
</td>
</tr>
<tr style="margin-top: 10px !important">
<td class="grey--text">hash</td>
<td class="torrentmodaltext--text">
{{ torrent.hash }}
</td>
</tr>
<tr>
<td class="grey--text">Size</td>
<td class="torrentmodaltext--text">
{{ torrent.size }}
</td>
</tr>
<tr>
<td class="grey--text">Done:</td>
<td class="torrentmodaltext--text">
{{ torrent.dloaded }}
</td>
</tr>
<tr>
<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">
{{ torrent.dlspeed }}
</td>
</tr>
<tr>
<td class="grey--text">Upload Speed</td>
<td class="torrentmodaltext--text">
{{ torrent.upspeed }}
</td>
</tr>
<tr>
<td class="grey--text">ETA</td>
<td class="torrentmodaltext--text">
{{ torrent.eta }}
</td>
</tr>
<tr>
<td class="grey--text">Peers</td>
<td class="torrentmodaltext--text">
{{ torrent.num_leechs
}}<span class="grey--text">/{{ torrent.available_peers }}</span>
</td>
</tr>
<tr>
<td class="grey--text">Seeds</td>
<td class="torrentmodaltext--text">
{{ torrent.num_seeds
}}<span class="grey--text">/{{ torrent.available_seeds }}</span>
</td>
</tr>
<tr>
<td class="grey--text">Added on</td>
<td class="torrentmodaltext--text">
{{ torrent.added_on }}
</td>
</tr>
<tr>
<td class="grey--text">Status</td>
<v-chip
small
:class="`${torrent.state.toLowerCase()} white--text my-2 caption`"
>{{ torrent.state }}</v-chip
>
</tr>
</tbody>
</v-simple-table>
</v-card-text>
</perfect-scrollbar>
</v-card>
</template>
<script>
import { mapGetters } from 'vuex'
import { FullScreenModal } from '@/mixins'
export default {
name: 'Info',
mixins: [FullScreenModal],
props: {
hash: String
},
@ -117,9 +115,6 @@ export default {
...mapGetters(['getTorrent']),
torrent() {
return this.getTorrent(this.hash)
},
phoneLayout() {
return this.$vuetify.breakpoint.xsOnly
}
}
}
@ -128,16 +123,16 @@ export default {
<style lang="scss" scoped>
::v-deep .v-data-table thead th,
::v-deep .v-data-table tbody td {
padding: 0 !important;
height: 3em;
padding: 0 !important;
height: 3em;
white-space: nowrap;
white-space: nowrap;
&:first-child {
padding: 0 0 0 8px !important;
}
&:last-child {
padding-right: 8px !important;
}
&:first-child {
padding: 0 0 0 8px !important;
}
&:last-child {
padding-right: 8px !important;
}
}
</style>

View file

@ -1,54 +1,55 @@
<template>
<perfect-scrollbar>
<v-data-table
v-if="peers"
:headers="headers"
:items="peers"
:items-per-page="-1"
:hide-default-footer="true"
style="max-height: 500px; min-height: 400px"
>
<template v-slot:item="row">
<tr>
<td class="ip">
<template v-if="row.item.country_code">
<img
v-if="isWindows"
class="country-flag"
:title="row.item.country"
:alt="codeToFlag(row.item.country_code).char"
:src="codeToFlag(row.item.country_code).url"
/>
<template v-else>{{
codeToFlag(row.item.country_code).char
}}</template>
</template>
{{ row.item.ip }}
<span class="grey--text">:{{ row.item.port }}</span>
</td>
<td>{{ row.item.connection }}</td>
<td :title="row.item.flags_desc">{{ row.item.flags }}</td>
<td>{{ row.item.client }}</td>
<td>{{ row.item.progress | progress }}</td>
<td>{{ row.item.dl_speed | networkSpeed }}</td>
<td>{{ row.item.downloaded | networkSize }}</td>
<td>{{ row.item.up_speed | networkSpeed }}</td>
<td>{{ row.item.uploaded | networkSize }}</td>
<td>{{ row.item.relevance | progress }}</td>
<td>{{ row.item.files }}</td>
</tr>
<perfect-scrollbar>
<v-data-table
v-if="peers"
:headers="headers"
:items="peers"
:items-per-page="-1"
:hide-default-footer="true"
:style="{ minHeight: phoneLayout ? '' : '75vh'}"
>
<template v-slot:item="row">
<tr>
<td class="ip">
<template v-if="row.item.country_code">
<img
v-if="isWindows"
class="country-flag"
:title="row.item.country"
:alt="codeToFlag(row.item.country_code).char"
:src="codeToFlag(row.item.country_code).url"
/>
<template v-else>{{
codeToFlag(row.item.country_code).char
}}</template>
</template>
</v-data-table>
</perfect-scrollbar>
{{ row.item.ip }}
<span class="grey--text">:{{ row.item.port }}</span>
</td>
<td>{{ row.item.connection }}</td>
<td :title="row.item.flags_desc">{{ row.item.flags }}</td>
<td>{{ row.item.client }}</td>
<td>{{ row.item.progress | progress }}</td>
<td>{{ row.item.dl_speed | networkSpeed }}</td>
<td>{{ row.item.downloaded | networkSize }}</td>
<td>{{ row.item.up_speed | networkSpeed }}</td>
<td>{{ row.item.uploaded | networkSize }}</td>
<td>{{ row.item.relevance | progress }}</td>
<td>{{ row.item.files }}</td>
</tr>
</template>
</v-data-table>
</perfect-scrollbar>
</template>
<script>
import { map, merge } from 'lodash'
import qbit from '@/services/qbit'
import { codeToFlag, isWindows } from '@/helpers'
import { FullScreenModal } from '@/mixins'
export default {
name: 'Peers',
mixins: [FullScreenModal],
props: { hash: String, isActive: Boolean },
data: () => ({
headers: [
@ -112,30 +113,30 @@ export default {
<style scoped>
::v-deep .ip {
display: flex;
align-items: center;
display: flex;
align-items: center;
}
::v-deep .ip .country-flag {
width: 1.5em;
margin-right: 0.5em;
width: 1.5em;
margin-right: 0.5em;
}
</style>
<style lang="scss" scoped>
@import '~@/assets/styles.scss';
@import "~@/assets/styles.scss";
::v-deep .v-data-table thead th,
::v-deep .v-data-table tbody td {
padding: 0 2px !important;
height: auto;
padding: 0 2px !important;
height: auto;
white-space: nowrap;
white-space: nowrap;
&:first-child {
padding: 0 0 0 8px !important;
}
&:last-child {
padding-right: 8px !important;
}
&:first-child {
padding: 0 0 0 8px !important;
}
&:last-child {
padding-right: 8px !important;
}
}
</style>

View file

@ -1,111 +1,110 @@
<template>
<v-card flat>
<v-card-text
class="mx-auto mt-5"
style="font-size: 1.1em; max-height: 500px; min-height: 300px"
>
<v-row>
<v-col :cols="12" :lg="6" :md="6" :sm="12">
<v-layout class="mx-auto" row wrap>
<v-flex xs12 sm12>
<h3>Available Tags:</h3>
</v-flex>
<v-flex class="mt-3 d-flex flex-wrap justify-center" xs12 sm12>
<v-chip
v-for="tag in availableTags"
:key="tag"
small
class="download white--text caption mx-2 my-1"
style="font-size: 0.95em !important"
@click="addTag(tag)"
>
{{ tag }}
</v-chip>
</v-flex>
</v-layout>
<v-layout class="mx-auto mt-12" row wrap>
<v-flex xs12 sm12>
<h3>Current Tags:</h3>
</v-flex>
<v-flex class="mt-3 d-flex flex-wrap justify-center" xs12 sm12>
<div v-if="torrent.tags">
<v-chip
v-for="tag in torrent.tags"
:key="tag"
small
close
class="download white--text caption mx-2 my-1"
style="font-size: 0.95em !important"
@click="deleteTag(tag)"
@click:close="deleteTag(tag)"
>{{ tag }}</v-chip
>
</div>
<div v-else>None</div>
</v-flex>
</v-layout>
</v-col>
<v-col :cols="12" :lg="6" :md="6" :sm="12">
<v-layout
class="mx-auto"
:class="
this.$vuetify.breakpoint.smAndDown ? 'mt-12' : ''
"
row
wrap
>
<v-flex xs12 sm12>
<h3>Available Categories:</h3>
</v-flex>
<v-flex class="mt-3 d-flex flex-wrap justify-center" xs12 sm12>
<v-chip
v-for="cat in availableCategories"
:key="cat.name"
small
class="upload white--text caption mx-2 my-1"
style="font-size: 0.95em !important"
@click="setCategory(cat.name)"
>
{{ cat.name }}
</v-chip>
</v-flex>
</v-layout>
<v-layout class="mx-auto mt-12" row wrap>
<v-flex xs12 sm12>
<h3>Current Category:</h3>
</v-flex>
<v-flex class="mt-3 d-flex justify-center" xs12 sm12>
<v-chip
v-if="torrent.category"
small
close
class="upload white--text caption mx-2"
style="font-size: 0.95em !important"
@click="deleteCategory"
@click:close="deleteCategory"
>{{ torrent.category }}</v-chip
>
<div v-else>None</div>
</v-flex>
</v-layout>
</v-col>
</v-row>
</v-card-text>
</v-card>
<v-card flat>
<v-card-text
class="mx-auto mt-4"
style="font-size: 1.1em"
:style="{ minHeight: phoneLayout ? '' : '75vh'}"
>
<v-row>
<v-col :cols="12" :lg="6" :md="6" :sm="12">
<v-layout class="mx-auto" row wrap>
<v-flex xs12 sm12>
<h3>Available Tags:</h3>
</v-flex>
<v-flex class="mt-3 d-flex flex-wrap justify-center" xs12 sm12>
<v-chip
v-for="tag in availableTags"
:key="tag"
small
class="download white--text caption mx-2 my-1"
style="font-size: 0.95em !important"
@click="addTag(tag)"
>
{{ tag }}
</v-chip>
</v-flex>
</v-layout>
<v-layout class="mx-auto mt-12" row wrap>
<v-flex xs12 sm12>
<h3>Current Tags:</h3>
</v-flex>
<v-flex class="mt-3 d-flex flex-wrap justify-center" xs12 sm12>
<div v-if="torrent.tags">
<v-chip
v-for="tag in torrent.tags"
:key="tag"
small
close
class="download white--text caption mx-2 my-1"
style="font-size: 0.95em !important"
@click="deleteTag(tag)"
@click:close="deleteTag(tag)"
>{{ tag }}</v-chip
>
</div>
<div v-else>None</div>
</v-flex>
</v-layout>
</v-col>
<v-col :cols="12" :lg="6" :md="6" :sm="12">
<v-layout
class="mx-auto"
:class="this.$vuetify.breakpoint.smAndDown ? 'mt-12' : ''"
row
wrap
>
<v-flex xs12 sm12>
<h3>Available Categories:</h3>
</v-flex>
<v-flex class="mt-3 d-flex flex-wrap justify-center" xs12 sm12>
<v-chip
v-for="cat in availableCategories"
:key="cat.name"
small
class="upload white--text caption mx-2 my-1"
style="font-size: 0.95em !important"
@click="setCategory(cat.name)"
>
{{ cat.name }}
</v-chip>
</v-flex>
</v-layout>
<v-layout class="mx-auto mt-12" row wrap>
<v-flex xs12 sm12>
<h3>Current Category:</h3>
</v-flex>
<v-flex class="mt-3 d-flex justify-center" xs12 sm12>
<v-chip
v-if="torrent.category"
small
close
class="upload white--text caption mx-2"
style="font-size: 0.95em !important"
@click="deleteCategory"
@click:close="deleteCategory"
>{{ torrent.category }}</v-chip
>
<div v-else>None</div>
</v-flex>
</v-layout>
</v-col>
</v-row>
</v-card-text>
</v-card>
</template>
<script>
import { difference } from 'lodash'
import { mapGetters } from 'vuex'
import qbit from '@/services/qbit'
import { Tab } from '@/mixins'
import { FullScreenModal } from '@/mixins'
export default {
name: 'TorrentTagsAndCategories',
props: {
hash: String
},
mixins: [Tab],
mixins: [FullScreenModal],
data: () => ({
categories: []
}),
@ -145,6 +144,6 @@ export default {
<style scoped>
h3 {
text-align: center;
text-align: center;
}
</style>

View file

@ -1,32 +1,34 @@
<template>
<perfect-scrollbar>
<v-data-table
v-if="trackers"
:headers="headers"
:items="trackers"
:hide-default-footer="true"
style="max-height: 500px; min-height: 400px"
>
<template v-slot:item="row">
<tr>
<td>{{ row.item.tier }}</td>
<td>{{ row.item.url }}</td>
<td>{{ row.item.status | formatTrackerStatus }}</td>
<td>{{ row.item.num_peers | formatTrackerNum }}</td>
<td>{{ row.item.num_seeds | formatTrackerNum }}</td>
<td>{{ row.item.num_leeches | formatTrackerNum }}</td>
<td>{{ row.item.num_downloaded | formatTrackerNum }}</td>
<td>{{ row.item.msg }}</td>
</tr>
</template>
</v-data-table>
</perfect-scrollbar>
<perfect-scrollbar>
<v-data-table
v-if="trackers"
:headers="headers"
:items="trackers"
:hide-default-footer="true"
:style="{ minHeight: phoneLayout ? '' : '75vh'}"
>
<template v-slot:item="row">
<tr>
<td>{{ row.item.tier }}</td>
<td>{{ row.item.url }}</td>
<td>{{ row.item.status | formatTrackerStatus }}</td>
<td>{{ row.item.num_peers | formatTrackerNum }}</td>
<td>{{ row.item.num_seeds | formatTrackerNum }}</td>
<td>{{ row.item.num_leeches | formatTrackerNum }}</td>
<td>{{ row.item.num_downloaded | formatTrackerNum }}</td>
<td>{{ row.item.msg }}</td>
</tr>
</template>
</v-data-table>
</perfect-scrollbar>
</template>
<script>
import qbit from '@/services/qbit'
import { FullScreenModal } from '@/mixins'
export default {
name: 'Trackers',
mixins: [FullScreenModal],
props: { hash: String, isActive: Boolean },
data: () => ({
headers: [
@ -86,20 +88,20 @@ export default {
</script>
<style lang="scss" scoped>
@import '~@/assets/styles.scss';
@import "~@/assets/styles.scss";
::v-deep .v-data-table thead th,
::v-deep .v-data-table tbody td {
padding: 0 2px !important;
height: auto;
padding: 0 2px !important;
height: auto;
white-space: nowrap;
white-space: nowrap;
&:first-child {
padding: 0 0 0 8px !important;
}
&:last-child {
padding-right: 8px !important;
}
&:first-child {
padding: 0 0 0 8px !important;
}
&:last-child {
padding-right: 8px !important;
}
}
</style>

View file

@ -1,7 +1,7 @@
<template>
<nav>
<!--title-->
<v-app-bar app flat color="background">
<v-app-bar app flat color="background" @click="resetSelected">
<v-app-bar-nav-icon
@click.stop="drawer = !drawer"
class="grey--text text--lighten-1"
@ -95,7 +95,11 @@ export default {
drawer: this.$vuetify.breakpoint.mdAndUp
}
},
methods: {
resetSelected() {
this.$store.commit('RESET_SELECTED')
}
},
created() {
this.$vuetify.theme.dark = this.getTheme()
}

View file

@ -1,5 +1,8 @@
<template>
<div>
<v-btn :ripple="false" id="no-background-hover" text>
<v-checkbox class="grey--text" v-model="$store.state.selectMode" color="grey" hide-details style="width: 5px;"/>
</v-btn>
<v-btn
text
small
@ -102,3 +105,12 @@ export default {
}
}
</script>
<style lang="scss">
#no-background-hover::before {
background-color: transparent !important;
}
#no-background-hover {
cursor: default !important;
}
</style>

View file

@ -10,6 +10,7 @@
showInfo(torrent.hash)"
@dblclick.prevent="showInfo(torrent.hash)"
@click.ctrl.exact.prevent="selectTorrent(torrent.hash)"
@click.shift.exact.prevent="selectUntil(torrent.hash, index)"
>
<v-tooltip top>
<template v-slot:activator="{ on }">
@ -18,111 +19,7 @@
<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 xs12 sm1 md1 class="mr-4">
<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 class="mr-2" v-if="torrent.progress !== 100" >
<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 xs6 sm1 md1 class="mr-2" v-else >
<div class="caption grey--text">Ratio</div>
<div>{{ torrent.ratio }}</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 sm2 v-if="torrent.tags && torrent.tags.length">
<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>
<component :key="'busy' + item.name" v-for="item in properties" :is="item.name" :torrent="torrent" />
</v-layout>
</template>
<span>{{ torrent.name }}</span>
@ -134,10 +31,35 @@
<script>
import { General, TorrentSelect } from '@/mixins'
import {mapGetters} from 'vuex'
import {
Size,
Progress,
Download,
Ratio,
Upload,
ETA,
Peers,
Seeds,
Status,
Category,
Tags
} from './Torrent/DashboardItems'
export default {
name: 'Torrent',
components: {
Size,
Progress,
Download,
Ratio,
Upload,
ETA,
Peers,
Seeds,
Status,
Category,
Tags
},
mixins: [General, TorrentSelect],
props: {
torrent: Object,
@ -163,6 +85,12 @@ export default {
},
denseDashboard(){
return this.getWebuiSettings().denseDashboard
},
properties(){
if(this.torrent.progress === 100){
return this.getWebuiSettings().doneTorrentProperties.filter(i => i.active)
}
return this.getWebuiSettings().busyTorrentProperties.filter(i => i.active)
}
},
methods: {

View file

@ -0,0 +1,14 @@
<template>
<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>
</template>
<script>
export default {
name: 'Category',
props: ['torrent']
}
</script>

View file

@ -0,0 +1,17 @@
<template>
<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>
</template>
<script>
export default {
name: 'Download',
props: ['torrent']
}
</script>

View file

@ -0,0 +1,14 @@
<template>
<v-flex xs6 sm1 md1 class="mr-2">
<div class="caption grey--text">ETA</div>
<div>
{{ torrent.eta }}
</div>
</v-flex>
</template>
<script>
export default {
name: 'ETA',
props: ['torrent']
}
</script>

View file

@ -0,0 +1,17 @@
<template>
<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>
</template>
<script>
export default {
name: 'Peers',
props: ['torrent']
}
</script>

View file

@ -0,0 +1,24 @@
<template>
<v-flex xs12 sm1 md1 class="mr-4">
<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>
</template>
<script>
import {TorrentDashboardItem} from '@/mixins'
export default {
name: 'Progress',
mixins : [TorrentDashboardItem],
props: ['torrent']
}
</script>

View file

@ -0,0 +1,12 @@
<template>
<v-flex xs6 sm1 md1 class="mr-2" >
<div class="caption grey--text">Ratio</div>
<div>{{ torrent.ratio }}</div>
</v-flex>
</template>
<script>
export default {
name: 'Ratio',
props: ['torrent']
}
</script>

View file

@ -0,0 +1,17 @@
<template>
<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>
</template>
<script>
export default {
name: 'Seeds',
props: ['torrent']
}
</script>

View file

@ -0,0 +1,17 @@
<template>
<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>
</template>
<script>
export default {
name: 'Size',
props: ['torrent']
}
</script>

View file

@ -0,0 +1,22 @@
<template>
<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>
</template>
<script>
import {TorrentDashboardItem} from '@/mixins'
export default {
name: 'Status',
mixins : [TorrentDashboardItem],
props: ['torrent']
}
</script>

View file

@ -0,0 +1,26 @@
<template>
<v-flex xs5 sm2 v-if="torrent.tags && torrent.tags.length">
<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>
</template>
<script>
import {TorrentDashboardItem} from '@/mixins'
export default {
name: 'Tags',
mixins : [TorrentDashboardItem],
props: ['torrent']
}
</script>

View file

@ -0,0 +1,17 @@
<template>
<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>
</template>
<script>
export default {
name: 'Upload',
props: ['torrent']
}
</script>

View file

@ -0,0 +1,25 @@
import Size from './Size'
import Progress from './Progress'
import Download from './Download'
import Ratio from './Ratio'
import Upload from './Upload'
import ETA from './ETA'
import Peers from './Peers'
import Seeds from './Seeds'
import Status from './Status'
import Category from './Category'
import Tags from './Tags'
export {
Size,
Progress,
Download,
Ratio,
Upload,
ETA,
Peers,
Seeds,
Status,
Category,
Tags
}

View file

@ -0,0 +1,16 @@
import {mapGetters} from 'vuex'
export default {
computed : {
...mapGetters(['getTheme']),
phoneLayout() {
return this.$vuetify.breakpoint.xsOnly
},
theme() {
return this.getTheme() ? 'dark' : 'light'
},
state() {
return this.torrent.state.toLowerCase()
}
}
}

View file

@ -9,6 +9,9 @@ export default {
} else {
this.$store.commit('SET_SELECTED', { type: 'add', hash })
}
},
selectUntil(hash, index){
this.$store.commit('SET_SELECTED', { type: 'until', hash, index })
}
}
}

View file

@ -4,5 +4,13 @@ import SettingsTab from './SettingsTab'
import Tab from './Tab'
import General from './General'
import TorrentSelect from './TorrentSelect'
import TorrentDashboardItem from './TorrentDashboardItem'
export { FullScreenModal, Modal, SettingsTab, Tab, General, TorrentSelect }
export { FullScreenModal,
Modal,
SettingsTab,
Tab,
General,
TorrentSelect,
TorrentDashboardItem
}

View file

@ -48,10 +48,36 @@ export default new Vuex.Store({
showCurrentSpeed: true,
showGlobalRemoveResumePause: true,
denseDashboard: true,
paginationSize: 15
paginationSize: 15,
busyTorrentProperties: [
{ name: 'Size', active: true},
{ name: 'Progress', active: true},
{ name: 'Download', active: true},
{ name: 'Upload', active: true},
{ name: 'ETA', active: true},
{ name: 'Peers', active: true},
{ name: 'Seeds', active: true},
{ name: 'Status', active: true},
{ name: 'Ratio', active: true},
{ name: 'Tags', active: true}
],
doneTorrentProperties: [
{ name: 'Size', active: true},
{ name: 'Progress', active: true},
{ name: 'Download', active: true},
{ name: 'Upload', active: true},
{ name: 'ETA', active: true},
{ name: 'Peers', active: true},
{ name: 'Seeds', active: true},
{ name: 'Status', active: true},
{ name: 'Ratio', active: true},
{ name: 'Tags', active: true}
]
},
categories: [],
filteredTorrentsCount: 0
filteredTorrentsCount: 0,
latestSelectedTorrent: null,
selectMode: false
},
getters: {
...getters

View file

@ -15,13 +15,26 @@ export default {
DELETE_MODAL(state, guid) {
state.modals = state.modals.filter(m => m.guid !== guid)
},
SET_SELECTED: (state, payload) => {
if (payload.type === 'add') state.selected_torrents.push(payload.hash)
if (payload.type === 'remove')
SET_SELECTED: (state, {type, hash, index}) => {
if (type === 'add') {
state.selected_torrents.push(hash)
state.latestSelectedTorrent = state.torrents.map(t => t.hash).indexOf(hash)
} else if (type === 'remove') {
state.selected_torrents.splice(
state.selected_torrents.indexOf(payload.hash),
state.selected_torrents.indexOf(hash),
1
)
} else if (type === 'until') {
let from, until
if (state.latestSelectedTorrent > index) {
from = index
until = state.latestSelectedTorrent + 1 //include latest selected
} else {
from = state.latestSelectedTorrent
until = index + 1
}
state.selected_torrents = state.torrents.map(t => t.hash).slice(from, until)
}
},
RESET_SELECTED: state => {
state.selected_torrents = []

View file

@ -34,11 +34,15 @@
<p class="grey--text">Nothing to see here!</p>
</div>
<div v-else>
<div
<v-layout
@contextmenu.prevent="$refs.menu.open($event, { torrent })"
v-for="(torrent, index) in paginatedData"
:key="torrent.hash"
>
<v-flex v-if="selectMode">
<v-checkbox color="grey" class="mt-10" xs1 :value="selected_torrents.indexOf(torrent.hash) !== -1" @click="selectTorrent(torrent.hash)" />
</v-flex>
<v-flex :class="selectMode ? 'xs11' : ''">
<Torrent
:class="{
topBorderRadius: index === 0,
@ -50,7 +54,8 @@
:index="index"
:length="torrents.length - 1"
/>
</div>
</v-flex>
</v-layout>
<v-row v-if="pageCount > 1" xs12 justify="center">
<v-col>
<v-container>
@ -98,6 +103,7 @@ export default {
const options = {
threshold: 0.3,
shouldSort: false,
keys: [
'name',
'size',
@ -126,6 +132,9 @@ export default {
},
torrentCountString() {
return this.getTorrentCountString()
},
selectMode(){
return this.$store.state.selectMode
}
},
methods: {

View file

@ -29,7 +29,7 @@ module.exports = {
port: 8000,
proxy: {
'/api': {
target: 'http://127.0.0.1:8080'
target: 'http://localhost:8080'
}
}
}