1
0
Fork 0
mirror of https://github.com/VueTorrent/VueTorrent.git synced 2025-05-02 13:31:45 +03:00
This commit is contained in:
Daan Wijns 2020-12-30 11:11:40 +01:00 committed by GitHub
parent f895738e70
commit 39e5549b80
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
81 changed files with 4718 additions and 4235 deletions

View file

@ -1,129 +1,132 @@
<template>
<v-dialog max-width="500px" v-model="dialog">
<v-card>
<v-container :class="`pa-0 project done`">
<v-card-title class="justify-center">
<h2>Add a new Torrent</h2>
</v-card-title>
<v-card-text>
<v-form v-model="valid" ref="form">
<v-container>
<v-row no-gutters>
<v-col ref="fileZone">
<div
@drop.prevent="addDropFile"
@dragover.prevent
>
<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"
>
<template
v-slot:selection="{ index, text }"
>
<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
v-if="files.length == 0"
label="URL"
prepend-icon="mdi-link"
rows="1"
required
:autofocus="!phoneLayout"
v-model="urls"
auto-grow
clearable
hint="One link per line"
/>
</v-col>
</v-row>
<v-combobox
v-model="category"
:items="availableCategories"
clearable
label="Category"
prepend-icon="tag"
@input="categoryChanged"
></v-combobox>
<v-text-field
:disabled="autoTMM"
v-model="directory"
label="Download Directory"
prepend-icon="folder"
></v-text-field>
<v-row no-gutters>
<v-flex xs12 sm6>
<v-checkbox
v-model="start"
label="Start torrent"
></v-checkbox>
</v-flex>
<v-flex xs12 sm6>
<v-checkbox
v-model="skip_checking"
label="Skip hash check"
></v-checkbox>
</v-flex>
<v-flex xs12 sm6>
<v-checkbox
v-model="root_folder"
label="Create subfolder"
></v-checkbox>
</v-flex>
<v-flex xs12 sm6>
<v-checkbox
v-model="autoTMM"
label="Automatic Torrent Management"
></v-checkbox>
</v-flex>
</v-row>
</v-container>
</v-form>
</v-card-text>
<v-spacer></v-spacer>
<v-form>
<v-card-actions class="justify-center">
<v-btn
text
@click="submit"
:disabled="!valid"
class="blue_accent white--text mx-0 mt-3"
>Add Torrent</v-btn
<v-dialog v-model="dialog" max-width="500px">
<v-card>
<v-container :class="`pa-0 project done`">
<v-card-title class="justify-center">
<h2>Add a new Torrent</h2>
</v-card-title>
<v-card-text>
<v-form ref="form" v-model="valid">
<v-container>
<v-row no-gutters>
<v-col ref="fileZone">
<div
@drop.prevent="addDropFile"
@dragover.prevent
>
<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"
>
<template
#selection="{ index, text }"
>
<v-chip
v-if="index < 2"
color="deep-purple accent-4"
dark
label
small
>
</v-card-actions>
</v-form>
{{ 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
v-if="files.length == 0"
v-model="urls"
label="URL"
prepend-icon="mdi-link"
rows="1"
required
:autofocus="!phoneLayout"
auto-grow
clearable
hint="One link per line"
/>
</v-col>
</v-row>
<v-combobox
v-model="category"
:items="availableCategories"
clearable
label="Category"
prepend-icon="tag"
@input="categoryChanged"
/>
<v-text-field
v-model="directory"
:disabled="autoTMM"
label="Download Directory"
prepend-icon="folder"
/>
<v-row no-gutters>
<v-flex xs12 sm6>
<v-checkbox
v-model="start"
label="Start torrent"
/>
</v-flex>
<v-flex xs12 sm6>
<v-checkbox
v-model="skip_checking"
label="Skip hash check"
/>
</v-flex>
<v-flex xs12 sm6>
<v-checkbox
v-model="root_folder"
label="Create subfolder"
/>
</v-flex>
<v-flex xs12 sm6>
<v-checkbox
v-model="autoTMM"
label="Automatic Torrent Management"
/>
</v-flex>
</v-row>
</v-container>
</v-card>
</v-dialog>
</v-form>
</v-card-text>
<v-spacer />
<v-form>
<v-card-actions class="justify-center">
<v-btn
text
:disabled="!valid"
class="blue_accent white--text mx-0 mt-3"
@click="submit"
>
Add Torrent
</v-btn>
</v-card-actions>
</v-form>
</v-container>
</v-card>
</v-dialog>
</template>
<script>
@ -131,102 +134,104 @@ import { mapGetters } from 'vuex'
import Modal from '@/mixins/Modal'
import qbit from '@/services/qbit'
export default {
name: 'AddModal',
props: ['initialMagnet'],
mixins: [Modal],
data() {
return {
files: [],
category: null,
directory: '',
start: true,
skip_checking: false,
root_folder: true,
autoTMM: true,
fileInputRules: [
v => {
const result = v.every(f => {
if (f.type) return f.type === 'application/x-bittorrent'
else return /^.*\.torrent$/.test(f.name)
})
return result ? result : 'One or more files is not a valid torrent'
}
],
loading: false,
urls: null,
valid: false
name: 'AddModal',
mixins: [Modal],
props: ['initialMagnet'],
data() {
return {
files: [],
category: null,
directory: '',
start: true,
skip_checking: false,
root_folder: true,
autoTMM: true,
fileInputRules: [
v => {
const result = v.every(f => {
if (f.type) return f.type === 'application/x-bittorrent'
else return /^.*\.torrent$/.test(f.name)
})
return result ? result : 'One or more files is not a valid torrent'
}
},
methods: {
addDropFile(e) {
this.files.push(...Array.from(e.dataTransfer.files))
},
submit() {
if (this.files.length || this.urls) {
const torrents = []
const params = {
urls: null,
paused: !this.start,
skip_checking: this.skip_checking,
root_folder: this.root_folder,
autoTMM: this.autoTMM
}
if (this.files.length) torrents.push(...this.files)
if (this.urls) params.urls = this.urls
if (this.category) params.category = this.category
if (!this.autoTMM) params.savepath = this.directory
qbit.addTorrents(params, torrents)
this.resetForm()
this.$store.commit('DELETE_MODAL', this.guid)
}
},
categoryChanged() {
if (this.autoTMM) this.directory = this.savepath
},
resetForm() {
this.url = null
this.files = []
this.category = null
this.directory = this.savepath
this.skip_checking = null
}
},
computed: {
...mapGetters(['getSettings', 'getCategories']),
settings() {
return this.getSettings()
},
validFile() {
return this.Files.length > 0
},
phoneLayout() {
return this.$vuetify.breakpoint.xsOnly
},
savepath() {
let savePath = this.getSettings().save_path
if (this.category) {
savePath += this.category
const category = this.getCategories()[this.category]
if (category && category.savePath) savePath = category.savePath
}
return savePath
},
availableCategories() {
return Object.keys(this.getCategories())
}
},
watch: {
settings(newvalue) {
this.directory = newvalue.save_path
}
},
created() {
this.$store.commit('FETCH_SETTINGS')
this.$store.commit('FETCH_CATEGORIES')
this.urls = this.initialMagnet
],
loading: false,
urls: null,
valid: false
}
},
computed: {
...mapGetters(['getSettings', 'getCategories']),
settings() {
return this.getSettings()
},
validFile() {
return this.Files.length > 0
},
phoneLayout() {
return this.$vuetify.breakpoint.xsOnly
},
savepath() {
let savePath = this.getSettings().save_path
if (this.category) {
savePath += this.category
const category = this.getCategories()[this.category]
if (category && category.savePath) savePath = category.savePath
}
return savePath
},
availableCategories() {
return Object.keys(this.getCategories())
}
},
watch: {
settings(newvalue) {
this.directory = newvalue.save_path
}
},
created() {
this.$store.commit('FETCH_SETTINGS')
this.$store.commit('FETCH_CATEGORIES')
this.urls = this.initialMagnet
},
methods: {
addDropFile(e) {
this.files.push(...Array.from(e.dataTransfer.files))
},
submit() {
if (this.files.length || this.urls) {
const torrents = []
const params = {
urls: null,
paused: !this.start,
skip_checking: this.skip_checking,
root_folder: this.root_folder,
autoTMM: this.autoTMM
}
if (this.files.length) torrents.push(...this.files)
if (this.urls) params.urls = this.urls
if (this.category) params.category = this.category
if (!this.autoTMM) params.savepath = this.directory
qbit.addTorrents(params, torrents)
this.resetForm()
this.$store.commit('DELETE_MODAL', this.guid)
}
},
categoryChanged() {
if (this.autoTMM) this.directory = this.savepath
},
resetForm() {
this.url = null
this.files = []
this.category = null
this.directory = this.savepath
this.skip_checking = null
}
}
}
</script>

View file

@ -1,50 +1,59 @@
<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-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
v-model="torrent.name"
label="Torrent Name"
prepend-icon="insert_drive_file"
readonly
/>
<v-text-field
v-model="newPath"
label="Directory"
prepend-icon="folder"
@keydown.enter="setLocation"
/>
</v-col>
</v-row>
</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>
</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
color="red"
dark
absolute
bottom
right
@click="close"
>
<v-icon>close</v-icon>
</v-btn>
</v-fab-transition>
</v-card>
</v-dialog>
</template>
<script>
@ -53,36 +62,36 @@ 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
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)
}
},
created() {
this.newPath = this.torrent.savePath
},
methods: {
setLocation() {
qbit.setTorrentLocation([this.hash], this.newPath)
this.close()
},
close() {
this.$store.commit('DELETE_MODAL', this.guid)
}
}
}
</script>

View file

@ -1,43 +1,48 @@
<template>
<v-dialog v-model="dialog" scrollable max-width="500px">
<v-card>
<v-container :class="`pa-0 project done`">
<v-card-title class="justify-center">
<h2>Confirm Removal</h2>
</v-card-title>
<v-card-text>
<v-list flat>
<v-list-item
v-for="t in torrents"
:key="t.hash"
>
<v-list-item-content>
<v-list-item-title class="truncate" v-text="t.name"></v-list-item-title>
</v-list-item-content>
</v-list-item>
</v-list>
</v-card-text>
<v-card-actions class="justify-center pb-5">
<v-btn text class="error white--text mt-3"
@click="close()"
>Cancel</v-btn
>
<v-btn
text
class="green_accent white--text mt-3"
@click="deleteWithoutFiles()"
>Delete</v-btn
>
<v-btn
text
class="green_accent white--text mt-3"
@click="deleteWithFiles()"
>Delete with files</v-btn
>
</v-card-actions>
</v-container>
</v-card>
</v-dialog>
<v-dialog v-model="dialog" scrollable max-width="500px">
<v-card>
<v-container :class="`pa-0 project done`">
<v-card-title class="justify-center">
<h2>Confirm Removal</h2>
</v-card-title>
<v-card-text>
<v-list flat>
<v-list-item
v-for="t in torrents"
:key="t.hash"
>
<v-list-item-content>
<v-list-item-title class="truncate" v-text="t.name" />
</v-list-item-content>
</v-list-item>
</v-list>
</v-card-text>
<v-card-actions class="justify-center pb-5">
<v-btn
text
class="error white--text mt-3"
@click="close()"
>
Cancel
</v-btn>
<v-btn
text
class="green_accent white--text mt-3"
@click="deleteWithoutFiles()"
>
Delete
</v-btn>
<v-btn
text
class="green_accent white--text mt-3"
@click="deleteWithFiles()"
>
Delete with files
</v-btn>
</v-card-actions>
</v-container>
</v-card>
</v-dialog>
</template>
<script>
@ -45,27 +50,27 @@ import { mapState, mapGetters } from 'vuex'
import { Modal } from '@/mixins'
import qbit from '@/services/qbit'
export default {
name: 'ConfirmDeleteModal',
mixins: [Modal],
methods: {
close() {
this.$store.commit('DELETE_MODAL', this.guid)
},
deleteWithoutFiles() {
qbit.deleteTorrents(this.selected_torrents, false)
this.close()
},
deleteWithFiles() {
qbit.deleteTorrents(this.selected_torrents, true)
this.close()
}
name: 'ConfirmDeleteModal',
mixins: [Modal],
methods: {
close() {
this.$store.commit('DELETE_MODAL', this.guid)
},
computed: {
...mapState(['selected_torrents']),
...mapGetters(['getTorrents']),
torrents() {
return this.getTorrents().filter(t => this.selected_torrents.includes(t.hash))
}
...mapState(['selected_torrents']),
...mapGetters(['getTorrents']),
torrents() {
return this.getTorrents().filter(t => this.selected_torrents.includes(t.hash))
}
},
deleteWithoutFiles() {
qbit.deleteTorrents(this.selected_torrents, false)
this.close()
},
deleteWithFiles() {
qbit.deleteTorrents(this.selected_torrents, true)
this.close()
}
}
}
</script>

View file

@ -1,9 +1,9 @@
<template>
<v-dialog
v-model="dialog"
scrollable
:width="dialogWidth"
:fullscreen="phoneLayout"
v-model="dialog"
scrollable
:width="dialogWidth"
:fullscreen="phoneLayout"
>
<v-card style="overflow: hidden !important">
<v-container :style="{ height: phoneLayout ? '100vh' : '' }">
@ -16,9 +16,9 @@
<v-row>
<v-col>
<v-text-field
label="Torrent Name"
prepend-icon="insert_drive_file"
v-model="name"
v-model="name"
label="Torrent Name"
prepend-icon="insert_drive_file"
/>
</v-col>
</v-row>
@ -27,12 +27,21 @@
</v-card-text>
<div>
<v-card-actions class="justify-center">
<v-btn color="success" @click="rename">Save</v-btn>
<v-btn color="success" @click="rename">
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-btn
color="red"
dark
absolute
bottom
right
@click="close"
>
<v-icon>close</v-icon>
</v-btn>
</v-fab-transition>
@ -45,36 +54,36 @@ import { mapGetters } from 'vuex'
import { Modal, FullScreenModal } from '@/mixins'
import qbit from '@/services/qbit'
export default {
name: 'RenameModal',
mixins: [Modal, FullScreenModal],
props: {
hash: String
},
data() {
return {
name: ''
}
},
computed: {
...mapGetters(['getTorrent']),
dialogWidth() {
return this.phoneLayout ? '100%' : '750px'
},
torrent() {
return this.getTorrent(this.hash)
}
},
methods: {
rename() {
qbit.setTorrentName(this.hash, this.name)
this.close()
},
close() {
this.$store.commit('DELETE_MODAL', this.guid)
}
},
created() {
this.name = this.torrent.name
name: 'RenameModal',
mixins: [Modal, FullScreenModal],
props: {
hash: String
},
data() {
return {
name: ''
}
},
computed: {
...mapGetters(['getTorrent']),
dialogWidth() {
return this.phoneLayout ? '100%' : '750px'
},
torrent() {
return this.getTorrent(this.hash)
}
},
created() {
this.name = this.torrent.name
},
methods: {
rename() {
qbit.setTorrentName(this.hash, this.name)
this.close()
},
close() {
this.$store.commit('DELETE_MODAL', this.guid)
}
}
}
</script>

View file

@ -5,36 +5,36 @@
</v-btn>
<v-bottom-sheet
scrollable
inset
v-model="opened"
v-if="this.$vuetify.breakpoint.smAndDown"
v-if="this.$vuetify.breakpoint.smAndDown"
v-model="opened"
scrollable
inset
>
<v-sheet>
<v-card>
<v-card-title> <v-icon>mdi-toy-brick</v-icon> Plugin manager </v-card-title>
<v-card-text>
<v-switch
v-for="(plugin, key) in searchPlugins"
:key="key"
:input-value="plugin.enabled"
:label="plugin.fullName"
@change="togglePlugin(plugin)"
v-for="(plugin, key) in searchPlugins"
:key="key"
:input-value="plugin.enabled"
:label="plugin.fullName"
@change="togglePlugin(plugin)"
/>
</v-card-text>
</v-card>
</v-sheet>
</v-bottom-sheet>
<v-dialog v-model="opened" width="50%" v-else>
<v-dialog v-else v-model="opened" width="50%">
<v-card>
<v-card-title> <v-icon>mdi-toy-brick</v-icon> Plugin manager </v-card-title>
<v-card-text>
<v-switch
v-for="(plugin, key) in searchPlugins"
:key="key"
v-model="plugin.enabled"
:label="plugin.fullName"
@change="togglePlugin(plugin)"
v-for="(plugin, key) in searchPlugins"
:key="key"
v-model="plugin.enabled"
:label="plugin.fullName"
@change="togglePlugin(plugin)"
/>
</v-card-text>
</v-card>
@ -46,22 +46,22 @@
import { mapState } from 'vuex'
import qbit from '@/services/qbit'
export default {
name: 'PluginsManager',
data: () =>({
opened: false
}),
computed: {
...mapState(['searchPlugins'])
},
methods: {
togglePlugin(plugin) {
qbit.enableSearchPlugin([plugin.name], plugin.enabled)
}
},
watch: {
opened() {
this.$store.commit('FETCH_SEARCH_PLUGINS')
}
name: 'PluginsManager',
data: () => ({
opened: false
}),
computed: {
...mapState(['searchPlugins'])
},
watch: {
opened() {
this.$store.commit('FETCH_SEARCH_PLUGINS')
}
},
methods: {
togglePlugin(plugin) {
qbit.enableSearchPlugin([plugin.name], plugin.enabled)
}
}
}
</script>

View file

@ -1,78 +1,86 @@
<template>
<v-dialog
v-model="dialog"
scrollable
:width="dialogWidth"
:fullscreen="phoneLayout"
:style="{ height: phoneLayout ? '100vh' : '' }"
>
<v-card :style="{ height: phoneLayout ? '100vh' : '' }">
<v-card-title class="justify-center">
<h2>Search</h2>
</v-card-title>
<v-card-text>
<v-form
ref="form"
v-model="searchForm.valid"
>
<v-container fluid>
<v-flex row class="col-12 col-sm-6 col-md-8 mx-auto">
<v-text-field
v-model="searchForm.pattern"
prepend-inner-icon="mdi-magnify"
@keypress.enter="$refs.searchButton.click"
label="Search"
:rules="[v => !!v || 'Searchterm is required']"
clearable
style="width: 70%"
/>
<v-spacer/>
<v-btn
ref="searchButton"
:disabled="!searchForm.valid"
:color="loading ? 'warning' : 'primary'"
@click="loading ? stopSearch() : startSearch()"
>
{{ loading ? "Stop" : "Search"}}
</v-btn>
</v-flex>
</v-container>
</v-form>
<perfect-scrollbar>
<v-data-table
:headers="grid.headers"
:items="search.results"
:items-per-page="10"
:loading="loading"
:style="{ maxHeight: '60vh'}"
>
<template #[`item.fileName`]="{ item }">
<a
:href="item.descrLink"
target="_blank"
v-text="item.fileName"
/>
</template>
<template v-slot:[`item.fileSize`]="{ item }">
{{ item.fileSize | formatSize }}
</template>
<template v-slot:[`item.actions`]="{ item }">
<v-icon @click="downloadTorrent(item)">mdi-download</v-icon>
</template>
</v-data-table>
</perfect-scrollbar>
</v-card-text>
<v-card-actions>
<PluginManager/>
</v-card-actions>
<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>
<v-dialog
v-model="dialog"
scrollable
:width="dialogWidth"
:fullscreen="phoneLayout"
:style="{ height: phoneLayout ? '100vh' : '' }"
>
<v-card :style="{ height: phoneLayout ? '100vh' : '' }">
<v-card-title class="justify-center">
<h2>Search</h2>
</v-card-title>
<v-card-text>
<v-form
ref="form"
v-model="searchForm.valid"
>
<v-container fluid>
<v-flex row class="col-12 col-sm-6 col-md-8 mx-auto">
<v-text-field
v-model="searchForm.pattern"
prepend-inner-icon="mdi-magnify"
label="Search"
:rules="[v => !!v || 'Searchterm is required']"
clearable
style="width: 70%"
@keypress.enter="$refs.searchButton.click"
/>
<v-spacer />
<v-btn
ref="searchButton"
:disabled="!searchForm.valid"
:color="loading ? 'warning' : 'primary'"
@click="loading ? stopSearch() : startSearch()"
>
{{ loading ? "Stop" : "Search" }}
</v-btn>
</v-flex>
</v-container>
</v-form>
<perfect-scrollbar>
<v-data-table
:headers="grid.headers"
:items="search.results"
:items-per-page="10"
:loading="loading"
:style="{ maxHeight: '60vh'}"
>
<template #[`item.fileName`]="{ item }">
<a
:href="item.descrLink"
target="_blank"
v-text="item.fileName"
/>
</template>
<template #[`item.fileSize`]="{ item }">
{{ item.fileSize | formatSize }}
</template>
<template #[`item.actions`]="{ item }">
<v-icon @click="downloadTorrent(item)">
mdi-download
</v-icon>
</template>
</v-data-table>
</perfect-scrollbar>
</v-card-text>
<v-card-actions>
<PluginManager />
</v-card-actions>
<v-fab-transition v-if="phoneLayout">
<v-btn
color="red"
dark
absolute
bottom
right
@click="close"
>
<v-icon>close</v-icon>
</v-btn>
</v-fab-transition>
</v-card>
</v-dialog>
</template>
<script>
@ -82,85 +90,86 @@ import { Modal, FullScreenModal, General } from '@/mixins'
import PluginManager from './PluginManager'
export default {
name: 'SearchModal',
components: { PluginManager },
mixins: [Modal, FullScreenModal, General],
data() {
return {
search: {
id: null,
status: null,
interval: null,
results: []
},
loading: false,
grid: {
headers: [
{ text: 'Name', value: 'fileName' },
{ text: 'Size', value: 'fileSize' },
{ text: 'Seeds', value: 'nbSeeders' },
{ text: 'Peers', value: 'nbLeechers' },
{ text: 'Search_engine', value: 'siteUrl' },
{ text: 'Action', value: 'actions', sortable: false }
]
},
searchForm: {
valid: false,
pattern: ''
}
}
},
computed: {
...mapGetters(['getSearchPlugins']),
dialogWidth() {
return this.phoneLayout ? '100%' : '60%'
},
enabledSearchPlugins() {
return this.getSearchPlugins().filter(p => p.enabled)
}
},
methods: {
async startSearch() {
if (this.searchForm.pattern.length && !this.search.interval) {
this.loading = true
this.search.status = 'Running'
this.search.results = []
const data = await qbit.startSearch(
this.searchForm.pattern,
this.enabledSearchPlugins.map(p => p.name)
)
this.search.id = data.id
await this.getStatus()
this.search.interval = setInterval(async () => {
const status = await this.getStatus()
if (status === 'Stopped') {
clearInterval(this.search.interval)
this.search.interval = null
await this.getResults()
}
}, 500)
}
},
async getStatus() {
if (this.search.id) {
const data = await qbit.getSearchStatus(this.search.id)
return (this.search.status = data[0].status)
}
},
async getResults() {
const data = await qbit.getSearchResults(this.search.id)
this.search.results = data.results
this.loading = false
},
downloadTorrent(item) {
this.createModal('addModal', { initialMagnet: item.fileUrl })
},
stopSearch() {
qbit.stopSearch(this.search.id)
},
close() {
this.$store.commit('DELETE_MODAL', this.guid)
}
name: 'SearchModal',
components: { PluginManager },
mixins: [Modal, FullScreenModal, General],
data() {
return {
search: {
id: null,
status: null,
interval: null,
results: []
},
loading: false,
grid: {
headers: [
{ text: 'Name', value: 'fileName' },
{ text: 'Size', value: 'fileSize' },
{ text: 'Seeds', value: 'nbSeeders' },
{ text: 'Peers', value: 'nbLeechers' },
{ text: 'Search_engine', value: 'siteUrl' },
{ text: 'Action', value: 'actions', sortable: false }
]
},
searchForm: {
valid: false,
pattern: ''
}
}
},
computed: {
...mapGetters(['getSearchPlugins']),
dialogWidth() {
return this.phoneLayout ? '100%' : '60%'
},
enabledSearchPlugins() {
return this.getSearchPlugins().filter(p => p.enabled)
}
},
methods: {
async startSearch() {
if (this.searchForm.pattern.length && !this.search.interval) {
this.loading = true
this.search.status = 'Running'
this.search.results = []
const data = await qbit.startSearch(
this.searchForm.pattern,
this.enabledSearchPlugins.map(p => p.name)
)
this.search.id = data.id
await this.getStatus()
this.search.interval = setInterval(async () => {
const status = await this.getStatus()
if (status === 'Stopped') {
clearInterval(this.search.interval)
this.search.interval = null
await this.getResults()
}
}, 500)
}
},
async getStatus() {
if (this.search.id) {
const data = await qbit.getSearchStatus(this.search.id)
return (this.search.status = data[0].status)
}
},
async getResults() {
const data = await qbit.getSearchResults(this.search.id)
this.search.results = data.results
this.loading = false
},
downloadTorrent(item) {
this.createModal('addModal', { initialMagnet: item.fileUrl })
},
stopSearch() {
qbit.stopSearch(this.search.id)
},
close() {
this.$store.commit('DELETE_MODAL', this.guid)
}
}
}
</script>

View file

@ -1,90 +1,109 @@
<template>
<v-dialog
v-model="dialog"
scrollable
:width="dialogWidth"
:fullscreen="phoneLayout"
>
<v-card style="min-height: 400px; overflow: hidden !important">
<div :style="{ height: phoneLayout ? '100vh' : '' }">
<v-card-title class="pb-0 justify-center primary">
<h2 class="white--text">Settings</h2>
</v-card-title>
<v-tabs v-model="tab" background-color="primary" dark fixed-tabs>
<v-tab href="#downloads">Downloads</v-tab>
<v-tab href="#bittorrent">BitTorrent</v-tab>
<v-tab href="#webui">WebUI</v-tab>
<v-tab href="#vuetorrent">VueTorrent</v-tab>
<v-tab href="#tagsAndCategories">Tags & Categories</v-tab>
</v-tabs>
<v-tabs-items v-model="tab" :touch="updateTab(tab)">
<v-tab-item value="downloads">
<Downloads :is-active="tab === 'downloads'" />
</v-tab-item>
<v-tab-item value="bittorrent">
<BitTorrent :is-active="tab === 'bittorrent'" />
</v-tab-item>
<v-tab-item value="webui">
<WebUI :is-active="tab === 'webui'" />
</v-tab-item>
<v-dialog
v-model="dialog"
scrollable
:width="dialogWidth"
:fullscreen="phoneLayout"
>
<v-card style="min-height: 400px; overflow: hidden !important">
<div :style="{ height: phoneLayout ? '100vh' : '' }">
<v-card-title class="pb-0 justify-center primary">
<h2 class="white--text">
Settings
</h2>
</v-card-title>
<v-tabs
v-model="tab"
background-color="primary"
dark
fixed-tabs
>
<v-tab href="#downloads">
Downloads
</v-tab>
<v-tab href="#bittorrent">
BitTorrent
</v-tab>
<v-tab href="#webui">
WebUI
</v-tab>
<v-tab href="#vuetorrent">
VueTorrent
</v-tab>
<v-tab href="#tagsAndCategories">
Tags & Categories
</v-tab>
</v-tabs>
<v-tabs-items v-model="tab" :touch="updateTab(tab)">
<v-tab-item value="downloads">
<Downloads :is-active="tab === 'downloads'" />
</v-tab-item>
<v-tab-item value="bittorrent">
<BitTorrent :is-active="tab === 'bittorrent'" />
</v-tab-item>
<v-tab-item value="webui">
<WebUI :is-active="tab === 'webui'" />
</v-tab-item>
<v-tab-item value="vuetorrent">
<VueTorrent :is-active="tab === 'vuetorrent'" />
</v-tab-item>
<v-tab-item value="tagsAndCategories">
<TagsAndCategories
:is-active="tab === 'tagsAndCategories'"
/>
</v-tab-item>
</v-tabs-items>
</div>
<v-card-actions class="d-flex justify-center">
<v-btn color="success" @click="saveSettings">Save</v-btn>
<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-actions>
</v-card>
</v-dialog>
<v-tab-item value="vuetorrent">
<VueTorrent :is-active="tab === 'vuetorrent'" />
</v-tab-item>
<v-tab-item value="tagsAndCategories">
<TagsAndCategories
:is-active="tab === 'tagsAndCategories'"
/>
</v-tab-item>
</v-tabs-items>
</div>
<v-card-actions class="d-flex justify-center">
<v-btn color="success" @click="saveSettings">
Save
</v-btn>
<v-fab-transition v-if="phoneLayout">
<v-btn
color="red"
dark
absolute
bottom
right
@click="close"
>
<v-icon>close</v-icon>
</v-btn>
</v-fab-transition>
</v-card-actions>
</v-card>
</v-dialog>
</template>
<script>
import { Modal, FullScreenModal, SettingsTab } from '@/mixins'
import {
WebUI,
BitTorrent,
Downloads,
VueTorrent,
TagsAndCategories
WebUI,
BitTorrent,
Downloads,
VueTorrent,
TagsAndCategories
} from './Tabs'
export default {
name: 'SettingsModal',
mixins: [Modal, FullScreenModal, SettingsTab],
components: { WebUI, BitTorrent, Downloads, VueTorrent, TagsAndCategories },
data() {
return {
tab: null,
items: [],
peers: []
}
},
methods: {
close() {
this.deleteModal()
}
},
created() {
this.$store.commit('FETCH_SETTINGS')
name: 'SettingsModal',
components: { WebUI, BitTorrent, Downloads, VueTorrent, TagsAndCategories },
mixins: [Modal, FullScreenModal, SettingsTab],
data() {
return {
tab: null,
items: [],
peers: []
}
},
created() {
this.$store.commit('FETCH_SETTINGS')
},
methods: {
close() {
this.deleteModal()
}
}
}
</script>

View file

@ -1,156 +1,156 @@
<template>
<v-container>
<v-card flat>
<perfect-scrollbar>
<v-card-text :style="{ minHeight: phoneLayout ? '' : '70vh', maxHeight: '70vh'}">
<h3>Privacy</h3>
<div class="settings_content ml-5 mr-5">
<v-checkbox
dense
:label="`Enable DHT (decentralized network) to find more peers`"
v-model="settings.dht"
/>
<v-checkbox
dense
:label="`Enable Peer Exchange (PeX) to find more peers`"
v-model="settings.pex"
/>
<v-checkbox
dense
:label="`Enable Local Peer Discovery to find more peers`"
v-model="settings.lsd"
/>
<v-checkbox
dense
:label="`Enable anonymous mode`"
v-model="settings.anonymous_mode"
/>
</div>
<v-card flat>
<perfect-scrollbar>
<v-card-text :style="{ minHeight: phoneLayout ? '' : '70vh', maxHeight: '70vh'}">
<h3>Privacy</h3>
<div class="settings_content ml-5 mr-5">
<v-checkbox
v-model="settings.dht"
dense
:label="`Enable DHT (decentralized network) to find more peers`"
/>
<v-checkbox
v-model="settings.pex"
dense
:label="`Enable Peer Exchange (PeX) to find more peers`"
/>
<v-checkbox
v-model="settings.lsd"
dense
:label="`Enable Local Peer Discovery to find more peers`"
/>
<v-checkbox
v-model="settings.anonymous_mode"
dense
:label="`Enable anonymous mode`"
/>
</div>
<v-checkbox
v-model="settings.queueing_enabled"
dense
:label="`Torrent Queueing`"
/>
<div class="settings_content ml-5 mr-5">
<v-text-field
v-model="settings.max_active_downloads"
class="mb-2"
outlined
dense
hide-details
:label="`Maximum active downloads`"
:disabled="!settings.queueing_enabled"
/>
<v-text-field
v-model="settings.max_active_uploads"
class="mb-2"
outlined
dense
hide-details
:label="`Maximum active uploads`"
:disabled="!settings.queueing_enabled"
/>
<v-text-field
v-model="settings.max_active_torrents"
class="mb-2"
outlined
dense
hide-details
:label="`Maximum active torrents`"
:disabled="!settings.queueing_enabled"
/>
<v-checkbox
v-model="settings.dont_count_slow_torrents"
dense
:label="`Do not count slow torrents in these limits`"
/>
<div class="settings_content">
<v-text-field
v-model="settings.slow_torrent_dl_rate_threshold"
class="mb-2"
outlined
dense
hide-details
:label="`Download rate threshold KiB/s`"
:disabled="!settings.dont_count_slow_torrents"
/>
<v-text-field
v-model="settings.slow_torrent_ul_rate_threshold"
class="mb-2"
outlined
dense
hide-details
:label="`Upload rate threshold KiB/s`"
:disabled="!settings.dont_count_slow_torrents"
/>
<v-text-field
v-model="settings.slow_torrent_inactive_timer"
class="mb-2"
outlined
dense
hide-details
:label="`Torrent inactivity timer`"
:disabled="!settings.dont_count_slow_torrents"
/>
</div>
</div>
<h3>Seeding Limits</h3>
<div class="settings_content ml-5 mr-5">
<v-row dense>
<v-col cols="10">
<v-checkbox
dense
:label="`Torrent Queueing`"
v-model="settings.queueing_enabled"
v-model="settings.max_ratio_enabled"
dense
:label="`When ratio reaches`"
/>
<div class="settings_content ml-5 mr-5">
<v-text-field
class="mb-2"
outlined
dense
hide-details
:label="`Maximum active downloads`"
:disabled="!settings.queueing_enabled"
v-model="settings.max_active_downloads"
/>
<v-text-field
class="mb-2"
outlined
dense
hide-details
:label="`Maximum active uploads`"
:disabled="!settings.queueing_enabled"
v-model="settings.max_active_uploads"
/>
<v-text-field
class="mb-2"
outlined
dense
hide-details
:label="`Maximum active torrents`"
:disabled="!settings.queueing_enabled"
v-model="settings.max_active_torrents"
/>
<v-checkbox
dense
:label="`Do not count slow torrents in these limits`"
v-model="settings.dont_count_slow_torrents"
/>
<div class="settings_content">
<v-text-field
class="mb-2"
outlined
dense
hide-details
:label="`Download rate threshold KiB/s`"
:disabled="!settings.dont_count_slow_torrents"
v-model="settings.slow_torrent_dl_rate_threshold"
/>
<v-text-field
class="mb-2"
outlined
dense
hide-details
:label="`Upload rate threshold KiB/s`"
:disabled="!settings.dont_count_slow_torrents"
v-model="settings.slow_torrent_ul_rate_threshold"
/>
<v-text-field
class="mb-2"
outlined
dense
hide-details
:label="`Torrent inactivity timer`"
:disabled="!settings.dont_count_slow_torrents"
v-model="settings.slow_torrent_inactive_timer"
/>
</div>
</div>
<h3>Seeding Limits</h3>
<div class="settings_content ml-5 mr-5">
<v-row dense>
<v-col cols="10">
<v-checkbox
dense
:label="`When ratio reaches`"
v-model="settings.max_ratio_enabled"
/>
</v-col>
<v-col>
<v-text-field
class="mb-2"
outlined
dense
hide-details
:disabled="!settings.max_ratio_enabled"
v-model="settings.max_ratio"
/>
</v-col>
</v-row>
<v-row dense>
<v-col cols="10">
<v-checkbox
dense
label="When seeding time reaches"
v-model="settings.max_seeding_time_enabled"
/>
</v-col>
<v-col>
<v-text-field
class="mb-2"
outlined
dense
hide-details
:disabled="!settings.max_seeding_time_enabled"
v-model="settings.max_seeding_time"
/>
</v-col>
</v-row>
</div>
</v-card-text>
</perfect-scrollbar>
</v-card>
</v-container>
</v-col>
<v-col>
<v-text-field
v-model="settings.max_ratio"
class="mb-2"
outlined
dense
hide-details
:disabled="!settings.max_ratio_enabled"
/>
</v-col>
</v-row>
<v-row dense>
<v-col cols="10">
<v-checkbox
v-model="settings.max_seeding_time_enabled"
dense
label="When seeding time reaches"
/>
</v-col>
<v-col>
<v-text-field
v-model="settings.max_seeding_time"
class="mb-2"
outlined
dense
hide-details
:disabled="!settings.max_seeding_time_enabled"
/>
</v-col>
</v-row>
</div>
</v-card-text>
</perfect-scrollbar>
</v-card>
</v-container>
</template>
<script>
import { SettingsTab, FullScreenModal } from '@/mixins'
export default {
name: 'BitTorrent',
mixins: [SettingsTab, FullScreenModal]
name: 'BitTorrent',
mixins: [SettingsTab, FullScreenModal]
}
</script>

View file

@ -1,79 +1,79 @@
<template>
<v-container>
<v-card flat>
<v-card-text :style="{ minHeight: phoneLayout ? '' : '70vh'}">
<h3>When adding a torrent</h3>
<div class="settings_content ml-5 mr-5">
<v-checkbox
dense
:label="`Create subfolder for torrents with multiple files`"
v-model="settings.create_subfolder_enabled"
/>
<v-checkbox
dense
:label="`Do not start the download automatically`"
v-model="settings.start_paused_enabled"
/>
</div>
<v-card flat>
<v-card-text :style="{ minHeight: phoneLayout ? '' : '70vh'}">
<h3>When adding a torrent</h3>
<div class="settings_content ml-5 mr-5">
<v-checkbox
v-model="settings.create_subfolder_enabled"
dense
:label="`Create subfolder for torrents with multiple files`"
/>
<v-checkbox
v-model="settings.start_paused_enabled"
dense
:label="`Do not start the download automatically`"
/>
</div>
<v-checkbox
dense
:label="`Pre-allocate disk space for all files`"
v-model="settings.preallocate_all"
/>
<v-checkbox
dense
:label="` Append .!qB extension to incomplete files`"
v-model="settings.incomplete_files_ext"
/>
<v-checkbox
v-model="settings.preallocate_all"
dense
:label="`Pre-allocate disk space for all files`"
/>
<v-checkbox
v-model="settings.incomplete_files_ext"
dense
:label="` Append .!qB extension to incomplete files`"
/>
<h3>Saving Management</h3>
<div class="settings_content ml-5 mr-5">
<v-row dense>
<v-col cols="5" class="d-flex align-center">
<h4>Default Save Path</h4>
</v-col>
<v-col>
<v-text-field
class="mb-2"
outlined
dense
hide-details
v-model="settings.save_path"
/>
</v-col>
</v-row>
<v-row dense>
<v-col cols="5">
<v-checkbox
dense
:label="`Keep incomplete torrents in:`"
v-model="settings.temp_path_enabled"
/>
</v-col>
<v-col>
<v-text-field
class="mb-2"
outlined
dense
hide-details
:disabled="!settings.temp_path_enabled"
v-model="settings.temp_path"
/>
</v-col>
</v-row>
</div>
</v-card-text>
</v-card>
</v-container>
<h3>Saving Management</h3>
<div class="settings_content ml-5 mr-5">
<v-row dense>
<v-col cols="5" class="d-flex align-center">
<h4>Default Save Path</h4>
</v-col>
<v-col>
<v-text-field
v-model="settings.save_path"
class="mb-2"
outlined
dense
hide-details
/>
</v-col>
</v-row>
<v-row dense>
<v-col cols="5">
<v-checkbox
v-model="settings.temp_path_enabled"
dense
:label="`Keep incomplete torrents in:`"
/>
</v-col>
<v-col>
<v-text-field
v-model="settings.temp_path"
class="mb-2"
outlined
dense
hide-details
:disabled="!settings.temp_path_enabled"
/>
</v-col>
</v-row>
</div>
</v-card-text>
</v-card>
</v-container>
</template>
<script>
import { FullScreenModal, SettingsTab } from '@/mixins'
export default {
name: 'Downloads',
mixins: [SettingsTab, FullScreenModal]
name: 'Downloads',
mixins: [SettingsTab, FullScreenModal]
}
</script>

View file

@ -1,73 +1,80 @@
<template>
<v-container>
<v-card flat>
<v-card-text
class="mx-auto mt-5"
style="font-size: 1.1em;"
:style="{ minHeight: phoneLayout ? '' : '64vh'}"
>
<v-layout row wrap>
<v-flex class="mx-auto text-center" xs12 md6>
<div>
<h3>Available Tags:</h3>
</div>
<div class="mt-3 d-flex flex-wrap justify-center">
<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"
>
{{ tag }}
</v-chip>
</div>
<v-card-actions class="justify-center pb-5">
<v-btn text class="error white--text mt-3" @click="deleteTag"
>Delete</v-btn
>
<v-btn
text
class="green_accent white--text mt-3"
@click="createTag"
>Create new</v-btn
>
</v-card-actions>
</v-flex>
<v-flex class="mx-auto text-center" xs12 md6>
<div>
<h3>Available Categories:</h3>
</div>
<div class="d-flex flex-wrap mt-3 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="editCategory(cat)"
@click:close="editCategory(cat)"
>
{{ cat.name }}
</v-chip>
</div>
<v-card-actions class="justify-center pb-5">
<v-btn
text
class="error white--text mt-3"
@click="deleteCategory"
>Delete</v-btn
>
<v-btn
text
class="green_accent white--text mt-3"
@click="createCategory"
>Create new</v-btn
>
</v-card-actions>
</v-flex>
</v-layout>
</v-card-text>
<v-card-text
class="mx-auto mt-5"
style="font-size: 1.1em;"
:style="{ minHeight: phoneLayout ? '' : '64vh'}"
>
<v-layout row wrap>
<v-flex class="mx-auto text-center" xs12 md6>
<div>
<h3>Available Tags:</h3>
</div>
<div class="mt-3 d-flex flex-wrap justify-center">
<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"
>
{{ tag }}
</v-chip>
</div>
<v-card-actions class="justify-center pb-5">
<v-btn
text
class="error white--text mt-3"
@click="deleteTag"
>
Delete
</v-btn>
<v-btn
text
class="green_accent white--text mt-3"
@click="createTag"
>
Create new
</v-btn>
</v-card-actions>
</v-flex>
<v-flex class="mx-auto text-center" xs12 md6>
<div>
<h3>Available Categories:</h3>
</div>
<div class="d-flex flex-wrap mt-3 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="editCategory(cat)"
@click:close="editCategory(cat)"
>
{{ cat.name }}
</v-chip>
</div>
<v-card-actions class="justify-center pb-5">
<v-btn
text
class="error white--text mt-3"
@click="deleteCategory"
>
Delete
</v-btn>
<v-btn
text
class="green_accent white--text mt-3"
@click="createCategory"
>
Create new
</v-btn>
</v-card-actions>
</v-flex>
</v-layout>
</v-card-text>
</v-card>
</v-container>
</template>
@ -80,51 +87,51 @@ import qbit from '@/services/qbit'
import { Tab, General, FullScreenModal } from '@/mixins'
export default {
name: 'TagsAndCategories',
mixins: [Tab, General, FullScreenModal],
props: {
hash: String
},
data: () => ({
selectedCategory: null
}),
computed: {
...mapGetters(['getTorrent', 'getAvailableTags', 'getCategories']),
name: 'TagsAndCategories',
mixins: [Tab, General, FullScreenModal],
props: {
hash: String
},
data: () => ({
selectedCategory: null
}),
computed: {
...mapGetters(['getTorrent', 'getAvailableTags', 'getCategories']),
availableTags() {
return this.getAvailableTags()
},
availableCategories() {
return this.getCategories()
}
availableTags() {
return this.getAvailableTags()
},
methods: {
activeMethod() {
this.fetchCategories()
},
async fetchCategories() {
const { data } = await qbit.getCategories()
this.categories = data
},
deleteTag() {
this.createModal('DeleteTagDialog')
},
createTag() {
this.createModal('CreateTagDialog')
},
createCategory() {
this.createModal('CreateCategoryDialog')
},
deleteCategory() {
this.createModal('DeleteCategoryDialog')
},
editCategory(cat) {
this.createModal('CreateCategoryDialog', { initialCategory: cat })
}
},
created() {
this.$store.commit('FETCH_CATEGORIES')
availableCategories() {
return this.getCategories()
}
},
created() {
this.$store.commit('FETCH_CATEGORIES')
},
methods: {
activeMethod() {
this.fetchCategories()
},
async fetchCategories() {
const { data } = await qbit.getCategories()
this.categories = data
},
deleteTag() {
this.createModal('DeleteTagDialog')
},
createTag() {
this.createModal('CreateTagDialog')
},
createCategory() {
this.createModal('CreateCategoryDialog')
},
deleteCategory() {
this.createModal('DeleteCategoryDialog')
},
editCategory(cat) {
this.createModal('CreateCategoryDialog', { initialCategory: cat })
}
}
}
</script>

View file

@ -1,25 +1,29 @@
<template>
<v-card flat>
<perfect-scrollbar>
<v-card-text :style="{ minHeight: phoneLayout ? '' : '70vh', maxHeight: '70vh'}">
<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 value="general">
<General :is-active="tab === 'downloads'" />
</v-tab-item>
<v-tab-item value="dashboard">
<Dashboard :is-active="tab === 'bittorrent'" />
</v-tab-item>
</v-tabs-items>
</v-card-text>
</perfect-scrollbar>
</v-card>
<v-card flat>
<perfect-scrollbar>
<v-card-text :style="{ minHeight: phoneLayout ? '' : '70vh', maxHeight: '70vh'}">
<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 value="general">
<General :is-active="tab === 'downloads'" />
</v-tab-item>
<v-tab-item value="dashboard">
<Dashboard :is-active="tab === 'bittorrent'" />
</v-tab-item>
</v-tabs-items>
</v-card-text>
</perfect-scrollbar>
</v-card>
</template>
<script>
@ -28,18 +32,18 @@ import Dashboard from './Vuetorrent/Dashboard'
import { FullScreenModal } from '@/mixins'
export default {
name: 'VueTorrent',
components: {
General, Dashboard
},
mixins: [FullScreenModal],
data: () => ({
tab: null
}),
methods: {
updateTab(tab) {
this.tab = tab
}
name: 'VueTorrent',
components: {
General, Dashboard
},
mixins: [FullScreenModal],
data: () => ({
tab: null
}),
methods: {
updateTab(tab) {
this.tab = tab
}
}
}
</script>

View file

@ -2,55 +2,65 @@
<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
<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-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-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-subheader>
Properties to display for completed torrents
</v-subheader>
<v-row dense>
<v-list flat class="ma-0 pa-0">
<draggable
<v-list flat class="ma-0 pa-0">
<draggable
:list="doneTorrentProperties"
tag="tbody"
>
<v-list-item
>
<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-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>
@ -60,18 +70,18 @@
<script>
import draggable from 'vuedraggable'
export default {
name: 'Dashboard',
components: {
draggable
name: 'Dashboard',
components: {
draggable
},
computed: {
busyTorrentProperties() {
return this.$store.state.webuiSettings.busyTorrentProperties
},
computed: {
busyTorrentProperties() {
return this.$store.state.webuiSettings.busyTorrentProperties
},
doneTorrentProperties() {
return this.$store.state.webuiSettings.doneTorrentProperties
}
doneTorrentProperties() {
return this.$store.state.webuiSettings.doneTorrentProperties
}
}
}
</script>

View file

@ -3,101 +3,129 @@
<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-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"
v-model="showCurrentSpeed"
class="v-input--reverse v-input--expand pa-0 ma-0"
inset
color="green_accent"
>
<template #label>
Global Remove/Resume/Pause Buttons</template
>
Show Current Speed
</template>
</v-switch>
<v-switch
class="v-input--reverse v-input--expand pa-0 ma-0"
inset
v-model="denseDashboard"
color="green_accent"
v-model="showSpeedGraph"
class="v-input--reverse v-input--expand pa-0 ma-0"
inset
color="green_accent"
>
<template #label>
Dense version of the dasbhoard</template
>
Show Speed Graph
</template>
</v-switch>
<v-switch
class="v-input--reverse v-input--expand pa-0 ma-0"
inset
v-model="showTrackerFilter"
color="green_accent"
v-model="showSessionStat"
class="v-input--reverse v-input--expand pa-0 ma-0"
inset
color="green_accent"
>
<template #label>
Show Tracker Filter</template
>
Show Session Stats
</template>
</v-switch>
<v-switch
v-model="freeSpace"
class="v-input--reverse v-input--expand pa-0 ma-0"
inset
color="green_accent"
>
<template #label>
Show Free Space
</template>
</v-switch>
<v-switch
v-model="showGlobalRemoveResumePause"
class="v-input--reverse v-input--expand pa-0 ma-0"
inset
color="green_accent"
>
<template #label>
Global Remove/Resume/Pause Buttons
</template>
</v-switch>
<v-switch
v-model="denseDashboard"
class="v-input--reverse v-input--expand pa-0 ma-0"
inset
color="green_accent"
>
<template #label>
Dense version of the dasbhoard
</template>
</v-switch>
<v-switch
v-model="showTrackerFilter"
class="v-input--reverse v-input--expand pa-0 ma-0"
inset
color="green_accent"
>
<template #label>
Show Tracker Filter
</template>
</v-switch>
<v-switch
v-model="showSpeedInTitle"
class="v-input--reverse v-input--expand pa-0 ma-0"
inset
color="green_accent"
>
<template #label>
Show Speed in Title
</template>
</v-switch>
<v-row dense>
<v-col cols="8" sm="8" md="10">
<p class="subtitle-1">Pagination size:</p>
<p class="subtitle-1">
Pagination size:
</p>
</v-col>
<v-col cols="4" sm="4" md="2">
<v-select
class="pa-0 ma-0"
color="green_accent"
:items="paginationSizes"
v-model="paginationSize"
></v-select>
v-model="paginationSize"
class="pa-0 ma-0"
color="green_accent"
:items="paginationSizes"
/>
</v-col>
</v-row>
<v-row dense>
<v-col cols="10" sm="10" md="11">
<p class="subtitle-1">Current Version:</p>
<p class="subtitle-1">
Current Version:
</p>
</v-col>
<v-col cols="2" sm="2" md="1">
<p class="mb-2">{{ version }}</p>
<p class="mb-2">
{{ version }}
</p>
</v-col>
</v-row>
<v-row dense>
<v-col cols="10" sm="10" md="11">
<p class="subtitle-1">QBittorrent Version:</p>
<p class="subtitle-1">
QBittorrent Version:
</p>
</v-col>
<v-col cols="2" sm="2" md="1">
<p class="mb-2">{{ Qbitversion }}</p>
<p class="mb-2">
{{ Qbitversion }}
</p>
</v-col>
</v-row>
</v-container>
@ -112,92 +140,101 @@
import { mapState, mapGetters } from 'vuex'
import qbit from '@/services/qbit'
export default {
name: 'General',
data() {
return {
paginationSizes: [5, 15, 30, 50],
Qbitversion: 0
}
},
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
}
},
showTrackerFilter: {
get() {
return this.webuiSettings.showTrackerFilter
},
set(val) {
this.webuiSettings.showTrackerFilter = val
}
},
paginationSize: {
get() {
return this.webuiSettings.paginationSize
},
set(val) {
this.webuiSettings.paginationSize = val
}
},
version() {
return this.getAppVersion()
}
},
methods: {
async fetchQbitVersion() {
this.Qbitversion = await qbit.getAppVersion()
}
},
created() {
this.fetchQbitVersion()
name: 'General',
data() {
return {
paginationSizes: [5, 15, 30, 50],
Qbitversion: 0
}
},
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
}
},
showTrackerFilter: {
get() {
return this.webuiSettings.showTrackerFilter
},
set(val) {
this.webuiSettings.showTrackerFilter = val
}
},
showSpeedInTitle: {
get() {
return this.webuiSettings.showSpeedInTitle
},
set(val) {
this.webuiSettings.showSpeedInTitle = val
document.title = 'VueTorrent'
}
},
paginationSize: {
get() {
return this.webuiSettings.paginationSize
},
set(val) {
this.webuiSettings.paginationSize = val
}
},
version() {
return this.getAppVersion()
}
},
created() {
this.fetchQbitVersion()
},
methods: {
async fetchQbitVersion() {
this.Qbitversion = await qbit.getAppVersion()
}
}
}
</script>

View file

@ -1,65 +1,67 @@
<template>
<v-container>
<v-card flat>
<v-card-text class="pa-0" style="font-size: 1.1em"
:style="{ minHeight: phoneLayout ? '' : '70vh'}"
>
<div class="box">
<v-subheader>Use Alternative WebUI</v-subheader>
<div class="ml-5 mr-5">
<v-checkbox
dense
:label="`Use Alternative WebUI`"
v-model="settings.alternative_webui_enabled"
/>
<v-text-field
outlined
dense
hide-details="true"
:label="`Files location`"
:disabled="!settings.alternative_webui_enabled"
v-model="settings.alternative_webui_path"
/>
</div>
</div>
<div class="box">
<v-subheader
>Web User Interface (Remote Control)</v-subheader
>
<v-row class="ml-5 mr-5">
<v-col cols="10">
<v-text-field
class="mr-1"
outlined
dense
hide-details="true"
:label="`IP Address:`"
v-model="settings.web_ui_address"
/>
</v-col>
<v-col>
<v-text-field
class="ml-1"
outlined
dense
hide-details="true"
:label="`Port`"
v-model="settings.web_ui_port"
/>
</v-col>
</v-row>
</div>
</v-card-text>
</v-card>
</v-container>
<v-card flat>
<v-card-text
class="pa-0"
style="font-size: 1.1em"
:style="{ minHeight: phoneLayout ? '' : '70vh'}"
>
<div class="box">
<v-subheader>Use Alternative WebUI</v-subheader>
<div class="ml-5 mr-5">
<v-checkbox
v-model="settings.alternative_webui_enabled"
dense
:label="`Use Alternative WebUI`"
/>
<v-text-field
v-model="settings.alternative_webui_path"
outlined
dense
hide-details="true"
:label="`Files location`"
:disabled="!settings.alternative_webui_enabled"
/>
</div>
</div>
<div class="box">
<v-subheader>
Web User Interface (Remote Control)
</v-subheader>
<v-row class="ml-5 mr-5">
<v-col cols="10">
<v-text-field
v-model="settings.web_ui_address"
class="mr-1"
outlined
dense
hide-details="true"
:label="`IP Address:`"
/>
</v-col>
<v-col>
<v-text-field
v-model="settings.web_ui_port"
class="ml-1"
outlined
dense
hide-details="true"
:label="`Port`"
/>
</v-col>
</v-row>
</div>
</v-card-text>
</v-card>
</v-container>
</template>
<script>
import { FullScreenModal, SettingsTab } from '@/mixins'
export default {
name: 'WebUI',
mixins: [SettingsTab, FullScreenModal]
name: 'WebUI',
mixins: [SettingsTab, FullScreenModal]
}
</script>

View file

@ -1,82 +1,84 @@
<template>
<v-dialog v-model="dialog" scrollable max-width="500px">
<v-card>
<v-container :class="`pa-0 project done`">
<v-card-title class="justify-center">
<h2>Sort Torrents</h2>
</v-card-title>
<v-card-text>
<v-form class="px-6 mt-3 justify-center mx-auto">
<v-container class="sortmodal">
<v-select
:value="sortProperty"
v-model="sort_options.sort"
flat
class="ml-2 mr-2"
:items="options"
item-text="name"
item-value="value"
dense
solo
background-color="select"
height="55"
></v-select>
<v-switch
class="v-input--reverse v-input--expand pa-0 ma-0"
inset
v-model="sort_options.reverse"
color="green_accent"
style="padding-left: 10px !important"
>
<template #label> Reverse </template>
</v-switch>
</v-container>
</v-form>
</v-card-text>
<v-dialog v-model="dialog" scrollable max-width="500px">
<v-card>
<v-container :class="`pa-0 project done`">
<v-card-title class="justify-center">
<h2>Sort Torrents</h2>
</v-card-title>
<v-card-text>
<v-form class="px-6 mt-3 justify-center mx-auto">
<v-container class="sortmodal">
<v-select
v-model="sort_options.sort"
:value="sortProperty"
flat
class="ml-2 mr-2"
:items="options"
item-text="name"
item-value="value"
dense
solo
background-color="select"
height="55"
/>
<v-switch
v-model="sort_options.reverse"
class="v-input--reverse v-input--expand pa-0 ma-0"
inset
color="green_accent"
style="padding-left: 10px !important"
>
<template #label>
Reverse
</template>
</v-switch>
</v-container>
</v-card>
</v-dialog>
</v-form>
</v-card-text>
</v-container>
</v-card>
</v-dialog>
</template>
<script>
import { mapState } from 'vuex'
import { Modal } from '@/mixins'
export default {
name: 'Sort',
mixins: [Modal],
data() {
return {
sortProperty: { value: 'added_on', name: 'Added On' },
reverse: true,
options: [
{ value: 'added_on', name: 'Added On' },
{ value: 'availability', name: 'Availability' },
{ value: 'category', name: 'Category' },
{ value: 'completed', name: 'Completed' },
{ value: 'dlspeed', name: 'Download Speed' },
{ value: 'downloaded', name: 'Downloaded' },
{ value: 'eta', name: 'ETA' },
{ value: 'name', name: 'Name' },
{ value: 'num_leechs', name: 'Peers' },
{ value: 'priority', name: 'Priority' },
{ value: 'progress', name: 'Progress' },
{ value: 'ratio', name: 'Ratio' },
{ value: 'size', name: 'Size' },
{ value: 'state', name: 'State' },
{ value: 'time_active', name: 'Time Active' },
{ value: 'uploaded', name: 'Uploaded' },
{ value: 'upspeed', name: 'Upload Speed' }
]
}
},
methods: {
close() {
this.$store.commit('DELETE_MODAL', this.guid)
}
},
computed: {
...mapState(['sort_options'])
name: 'Sort',
mixins: [Modal],
data() {
return {
sortProperty: { value: 'added_on', name: 'Added On' },
reverse: true,
options: [
{ value: 'added_on', name: 'Added On' },
{ value: 'availability', name: 'Availability' },
{ value: 'category', name: 'Category' },
{ value: 'completed', name: 'Completed' },
{ value: 'dlspeed', name: 'Download Speed' },
{ value: 'downloaded', name: 'Downloaded' },
{ value: 'eta', name: 'ETA' },
{ value: 'name', name: 'Name' },
{ value: 'num_leechs', name: 'Peers' },
{ value: 'priority', name: 'Priority' },
{ value: 'progress', name: 'Progress' },
{ value: 'ratio', name: 'Ratio' },
{ value: 'size', name: 'Size' },
{ value: 'state', name: 'State' },
{ value: 'time_active', name: 'Time Active' },
{ value: 'uploaded', name: 'Uploaded' },
{ value: 'upspeed', name: 'Upload Speed' }
]
}
},
computed: {
...mapState(['sort_options'])
},
methods: {
close() {
this.$store.commit('DELETE_MODAL', this.guid)
}
}
}
</script>

View file

@ -1,55 +1,61 @@
<template>
<v-dialog v-model="dialog" max-width="600px">
<v-card>
<v-container style="min-height: 200px" :class="`pa-0 project done`">
<v-card-title class="justify-center">
<h2>{{ hasInitialCategory ? 'Edit' : 'Create New' }} Category</h2>
</v-card-title>
<v-dialog v-model="dialog" max-width="600px">
<v-card>
<v-container style="min-height: 200px" :class="`pa-0 project done`">
<v-card-title class="justify-center">
<h2>{{ hasInitialCategory ? 'Edit' : 'Create New' }} Category</h2>
</v-card-title>
<v-form ref="categoryForm" class="px-6 mt-3">
<v-container>
<v-text-field
class="mx-auto"
style="max-width: 200px"
v-model="category.name"
:rules="nameRules"
:counter="15"
label="Category name"
required
></v-text-field>
<v-text-field
class="mx-auto"
style="max-width: 200px"
v-model="category.savePath"
:rules="PathRules"
:counter="40"
label="Path"
required
></v-text-field>
</v-container>
</v-form>
</v-container>
<v-card-actions class="justify-center pb-5 project done">
<v-btn text @click="cancel" class="error white--text mt-3"
>Cancel</v-btn
>
<v-btn
v-if="!hasInitialCategory"
text
@click="create"
class="green_accent white--text mt-3"
>Save</v-btn
>
<v-btn
v-else
text
@click="edit"
class="green_accent white--text mt-3"
>Edit</v-btn
>
</v-card-actions>
</v-card>
</v-dialog>
<v-form ref="categoryForm" class="px-6 mt-3">
<v-container>
<v-text-field
v-model="category.name"
class="mx-auto"
style="max-width: 200px"
:rules="nameRules"
:counter="15"
label="Category name"
required
/>
<v-text-field
v-model="category.savePath"
class="mx-auto"
style="max-width: 200px"
:rules="PathRules"
:counter="40"
label="Path"
required
/>
</v-container>
</v-form>
</v-container>
<v-card-actions class="justify-center pb-5 project done">
<v-btn
text
class="error white--text mt-3"
@click="cancel"
>
Cancel
</v-btn>
<v-btn
v-if="!hasInitialCategory"
text
class="green_accent white--text mt-3"
@click="create"
>
Save
</v-btn>
<v-btn
v-else
text
class="green_accent white--text mt-3"
@click="edit"
>
Edit
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</template>
<script>
@ -59,57 +65,57 @@ import { Modal } from '@/mixins'
import Vue from 'vue'
export default {
name: 'createNewCategoryDialog',
props: {
initialCategory: Object
},
mixins: [Modal],
computed: {
...mapGetters(['getSelectedCategory']),
hasInitialCategory() {
return (
this.initialCategory &&
name: 'CreateNewCategoryDialog',
mixins: [Modal],
props: {
initialCategory: Object
},
data: () => ({
nameRules: [
v => !!v || 'Category name is required',
v =>
(v && v.length <= 15) ||
'Category name must be less than 15 characters'
],
PathRules: [
v => !!v || 'Path is required',
v => (v && v.length <= 40) || 'Path must be less than 40 characters'
],
category: { name: '', savePath: '' }
}),
computed: {
...mapGetters(['getSelectedCategory']),
hasInitialCategory() {
return (
this.initialCategory &&
this.initialCategory.name
)
}
},
data: () => ({
nameRules: [
v => !!v || 'Category name is required',
v =>
(v && v.length <= 15) ||
'Category name must be less than 15 characters'
],
PathRules: [
v => !!v || 'Path is required',
v => (v && v.length <= 40) || 'Path must be less than 40 characters'
],
category: { name: '', savePath: '' }
}),
methods: {
create() {
qbit.createCategory(this.category)
this.cancel()
},
cancel() {
this.category.name = ''
this.category.savePath = ''
this.$refs.categoryForm.reset()
this.$store.commit('FETCH_CATEGORIES')
this.deleteModal()
},
edit() {
qbit.editCategory(this.category)
Vue.$toast.success('Category edited successfully!')
this.cancel()
}
},
created() {
this.$store.commit('FETCH_CATEGORIES')
if (this.hasInitialCategory) {
this.category = this.initialCategory
}
)
}
},
created() {
this.$store.commit('FETCH_CATEGORIES')
if (this.hasInitialCategory) {
this.category = this.initialCategory
}
},
methods: {
create() {
qbit.createCategory(this.category)
this.cancel()
},
cancel() {
this.category.name = ''
this.category.savePath = ''
this.$refs.categoryForm.reset()
this.$store.commit('FETCH_CATEGORIES')
this.deleteModal()
},
edit() {
qbit.editCategory(this.category)
Vue.$toast.success('Category edited successfully!')
this.cancel()
}
}
}
</script>

View file

@ -1,63 +1,68 @@
<template>
<v-dialog v-model="dialog" max-width="600px">
<v-card>
<v-container style="min-height: 200px" :class="`pa-0 project done`">
<v-card-title class="justify-center">
<h2>Create New Tag</h2>
</v-card-title>
<v-dialog v-model="dialog" max-width="600px">
<v-card>
<v-container style="min-height: 200px" :class="`pa-0 project done`">
<v-card-title class="justify-center">
<h2>Create New Tag</h2>
</v-card-title>
<v-form class="px-6 mt-3">
<v-container>
<v-text-field
class="mx-auto"
style="max-width: 200px"
v-model="tagname"
:rules="rules"
:counter="10"
label="Tag name"
required
></v-text-field>
</v-container>
</v-form>
</v-container>
<v-card-actions class="justify-center pb-5 project done">
<v-btn text @click="cancel" class="error white--text mt-3"
>Cancel</v-btn
>
<v-btn
text
@click="create"
class="green_accent white--text mt-3"
>Save</v-btn
>
</v-card-actions>
</v-card>
</v-dialog>
<v-form class="px-6 mt-3">
<v-container>
<v-text-field
v-model="tagname"
class="mx-auto"
style="max-width: 200px"
:rules="rules"
:counter="10"
label="Tag name"
required
/>
</v-container>
</v-form>
</v-container>
<v-card-actions class="justify-center pb-5 project done">
<v-btn
text
class="error white--text mt-3"
@click="cancel"
>
Cancel
</v-btn>
<v-btn
text
class="green_accent white--text mt-3"
@click="create"
>
Save
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</template>
<script>
import qbit from '@/services/qbit'
import { Modal } from '@/mixins'
export default {
name: 'createTagDialog',
mixins: [Modal],
data: () => ({
tagname: '',
rules: [
v => !!v || 'Tag is required',
v => v.length <= 10 || 'Tag must be less than 10 characters'
]
}),
methods: {
create() {
qbit.createTag(this.tagname)
this.cancel()
},
cancel() {
this.tagname = ''
this.deleteModal()
}
name: 'CreateTagDialog',
mixins: [Modal],
data: () => ({
tagname: '',
rules: [
v => !!v || 'Tag is required',
v => v.length <= 10 || 'Tag must be less than 10 characters'
]
}),
methods: {
create() {
qbit.createTag(this.tagname)
this.cancel()
},
cancel() {
this.tagname = ''
this.deleteModal()
}
}
}
</script>

View file

@ -1,44 +1,48 @@
<template>
<v-dialog :value="dialog" max-width="600px">
<v-card>
<v-container style="min-height: 200px" :class="`pa-0 project done`">
<v-card-title class="justify-center">
<h2>Delete Category</h2>
</v-card-title>
<v-dialog :value="dialog" max-width="600px">
<v-card>
<v-container style="min-height: 200px" :class="`pa-0 project done`">
<v-card-title class="justify-center">
<h2>Delete Category</h2>
</v-card-title>
<v-list
rounded
v-if="categories"
class="text-center mx-auto"
style="max-width: 200px"
>
<v-list-item
@click="deleteCategory(t)"
v-for="(t, i) in categories"
:key="i"
>
<v-list-item-content>
<v-list-item-title
v-text="t.name"
></v-list-item-title>
</v-list-item-content>
</v-list-item>
</v-list>
<v-card-subtitle
class="text-center mx-auto"
style="font-size: 1.5em; margin-top: 20px"
v-else
>
No categories found
</v-card-subtitle>
</v-container>
<v-card-actions class="justify-center pb-5 project done">
<v-btn text @click="cancel" class="error white--text mt-3"
>Close</v-btn
>
</v-card-actions>
</v-card>
</v-dialog>
<v-list
v-if="categories"
rounded
class="text-center mx-auto"
style="max-width: 200px"
>
<v-list-item
v-for="(t, i) in categories"
:key="i"
@click="deleteCategory(t)"
>
<v-list-item-content>
<v-list-item-title
v-text="t.name"
/>
</v-list-item-content>
</v-list-item>
</v-list>
<v-card-subtitle
v-else
class="text-center mx-auto"
style="font-size: 1.5em; margin-top: 20px"
>
No categories found
</v-card-subtitle>
</v-container>
<v-card-actions class="justify-center pb-5 project done">
<v-btn
text
class="error white--text mt-3"
@click="cancel"
>
Close
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</template>
<script>
@ -46,24 +50,24 @@ import qbit from '@/services/qbit'
import { Modal } from '@/mixins'
import { mapGetters } from 'vuex'
export default {
name: 'DeleteCategoryDialog',
mixins: [Modal],
computed: {
...mapGetters(['getCategories']),
categories() {
return this.getCategories()
}
},
methods: {
deleteCategory(cat) {
qbit.deleteCategory(cat.name)
this.cancel()
},
cancel() {
this.$store.commit('FETCH_CATEGORIES')
this.deleteModal()
}
name: 'DeleteCategoryDialog',
mixins: [Modal],
computed: {
...mapGetters(['getCategories']),
categories() {
return this.getCategories()
}
},
methods: {
deleteCategory(cat) {
qbit.deleteCategory(cat.name)
this.cancel()
},
cancel() {
this.$store.commit('FETCH_CATEGORIES')
this.deleteModal()
}
}
}
</script>

View file

@ -1,42 +1,46 @@
<template>
<v-dialog v-model="dialog" max-width="600px">
<v-card>
<v-container style="min-height: 200px" :class="`pa-0 project done`">
<v-card-title class="justify-center">
<h2>Delete Tag</h2>
</v-card-title>
<v-dialog v-model="dialog" max-width="600px">
<v-card>
<v-container style="min-height: 200px" :class="`pa-0 project done`">
<v-card-title class="justify-center">
<h2>Delete Tag</h2>
</v-card-title>
<v-list
rounded
v-if="availableTags && availableTags.length"
class="text-center mx-auto"
style="max-width: 200px"
>
<v-list-item
@click="deleteTag(t)"
v-for="(t, i) in availableTags"
:key="i"
>
<v-list-item-content>
<v-list-item-title v-text="t"></v-list-item-title>
</v-list-item-content>
</v-list-item>
</v-list>
<v-card-subtitle
class="text-center mx-auto"
style="font-size: 1.5em; margin-top: 20px"
v-else
>
No tags found
</v-card-subtitle>
</v-container>
<v-card-actions class="justify-center pb-5 project done">
<v-btn text @click="cancel" class="error white--text mt-3"
>Close</v-btn
>
</v-card-actions>
</v-card>
</v-dialog>
<v-list
v-if="availableTags && availableTags.length"
rounded
class="text-center mx-auto"
style="max-width: 200px"
>
<v-list-item
v-for="(t, i) in availableTags"
:key="i"
@click="deleteTag(t)"
>
<v-list-item-content>
<v-list-item-title v-text="t" />
</v-list-item-content>
</v-list-item>
</v-list>
<v-card-subtitle
v-else
class="text-center mx-auto"
style="font-size: 1.5em; margin-top: 20px"
>
No tags found
</v-card-subtitle>
</v-container>
<v-card-actions class="justify-center pb-5 project done">
<v-btn
text
class="error white--text mt-3"
@click="cancel"
>
Close
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</template>
<script>
@ -44,23 +48,23 @@ import qbit from '@/services/qbit'
import { Modal } from '@/mixins'
import { mapGetters } from 'vuex'
export default {
name: 'DeleteTagDialog',
mixins: [Modal],
computed: {
...mapGetters(['getTorrent', 'getAvailableTags']),
availableTags() {
return this.getAvailableTags()
}
},
methods: {
deleteTag(tag) {
qbit.deleteTag(tag)
this.cancel()
},
cancel() {
this.deleteModal()
}
name: 'DeleteTagDialog',
mixins: [Modal],
computed: {
...mapGetters(['getTorrent', 'getAvailableTags']),
availableTags() {
return this.getAvailableTags()
}
},
methods: {
deleteTag(tag) {
qbit.deleteTag(tag)
this.cancel()
},
cancel() {
this.deleteModal()
}
}
}
</script>

View file

@ -4,8 +4,8 @@ import CreateNewCategoryDialog from './CreateCategoryDialog.vue'
import DeleteCategoryDialog from './DeleteCategoryDialog'
export {
CreateNewTagDialog,
DeleteTagDialog,
CreateNewCategoryDialog,
DeleteCategoryDialog
CreateNewTagDialog,
DeleteTagDialog,
CreateNewCategoryDialog,
DeleteCategoryDialog
}

View file

@ -1,7 +1,7 @@
<template>
<v-card flat>
<perfect-scrollbar>
<v-card-text :style="{ minHeight: phoneLayout ? '' : '75vh', maxHeight: '75vh'}">
<v-card-text :style="{ minHeight: phoneLayout ? '' : '75vh', maxHeight: '75vh'}">
<v-treeview
v-model="selected"
:items="fileTree"
@ -11,21 +11,23 @@
item-key="fullName"
open-all
>
<template v-slot:prepend="{ item, open }">
<template #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>
<v-icon v-else>
{{ item.icon }}
</v-icon>
</template>
<template v-slot:label="{ item }">
<template #label="{ item }">
<span v-if="!item.editing">{{ item.name }}</span>
<v-text-field
autofocus
v-if="item.editing"
v-model="item.newName"
autofocus
/>
</template>
<template v-slot:append="{ item }">
<template #append="{ item }">
<span v-if="!item.icon">{{ item.children.length }} Files</span>
<div v-else>
<span>[{{ item.size }}]</span>
@ -70,96 +72,97 @@ import qbit from '@/services/qbit'
import { treeify } from '@/helpers'
import { FullScreenModal } from '@/mixins'
export default {
name: 'Content',
mixins: [FullScreenModal],
props: {
hash: String,
isActive: Boolean
},
data() {
return {
opened: null,
selected: [],
treeData: null
}
},
computed: {
fileTree() {
if (this.treeData) {
return treeify(this.treeData)
}
return []
}
},
methods: {
async getTorrentFiles() {
const { data } = await qbit.getTorrentFiles(this.hash)
data.forEach((d, i) => {
d.id = i
d.name = d.name.replace('.unwanted/', '')
})
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: {
isActive(active) {
if (active) {
this.getTorrentFiles()
}
},
selected(newValue, oldValue) {
this.changeFilePriorities(newValue, oldValue)
}
},
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)
.map(file => file.name)
})
name: 'Content',
mixins: [FullScreenModal],
props: {
hash: String,
isActive: Boolean
},
data() {
return {
opened: null,
selected: [],
treeData: null
}
},
computed: {
fileTree() {
if (this.treeData) {
return treeify(this.treeData)
}
return []
}
},
watch: {
isActive(active) {
if (active) {
this.getTorrentFiles()
}
},
selected(newValue, oldValue) {
this.changeFilePriorities(newValue, oldValue)
}
},
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)
.map(file => file.name)
})
},
methods: {
async getTorrentFiles() {
const { data } = await qbit.getTorrentFiles(this.hash)
data.forEach((d, i) => {
d.id = i
d.name = d.name.replace('.unwanted/', '')
})
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)
}
}
}
</script>

View file

@ -8,104 +8,137 @@
<v-simple-table>
<tbody>
<tr>
<td class="grey--text">Torrent title</td>
<td class="grey--text">
Torrent title
</td>
<td class="torrentmodaltext--text">
{{ torrent.name }}
</td>
</tr>
<tr>
<td class="grey--text">Directory</td>
<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="grey--text">
hash
</td>
<td class="torrentmodaltext--text">
{{ torrent.hash }}
</td>
</tr>
<tr>
<td class="grey--text">Size</td>
<td class="grey--text">
Size
</td>
<td class="torrentmodaltext--text">
{{ torrent.size }}
</td>
</tr>
<tr>
<td class="grey--text">Done:</td>
<td class="grey--text">
Done:
</td>
<td class="torrentmodaltext--text">
{{ torrent.dloaded }}
</td>
</tr>
<tr>
<td class="grey--text">Uploaded:</td>
<td class="grey--text">
Uploaded:
</td>
<td class="torrentmodaltext--text">
{{ torrent.uploaded }}
</td>
</tr>
<tr>
<td class="grey--text">Ratio</td>
<td class="grey--text">
Ratio
</td>
<td class="torrentmodaltext--text">
{{ torrent.ratio }}
</td>
</tr>
<tr>
<td class="grey--text">Download Speed</td>
<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="grey--text">
Upload Speed
</td>
<td class="torrentmodaltext--text">
{{ torrent.upspeed }}
</td>
</tr>
<tr>
<td class="grey--text">ETA</td>
<td class="grey--text">
ETA
</td>
<td class="torrentmodaltext--text">
{{ torrent.eta }}
</td>
</tr>
<tr>
<td class="grey--text">Peers</td>
<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="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="grey--text">
Added on
</td>
<td class="torrentmodaltext--text">
{{ torrent.added_on }}
</td>
</tr>
<tr v-if="torrent.tracker">
<td class="grey--text">Tracker</td>
<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="grey--text">
Comment
</td>
<td class="torrentmodaltext--text">
{{ torrent.comment }}
</td>
</tr>
<tr>
<td class="grey--text">Status</td>
<td class="grey--text">
Status
</td>
<v-chip
small
:class="`${torrent.state.toLowerCase()} white--text my-2 caption`"
>{{ torrent.state }}</v-chip
>
{{ torrent.state }}
</v-chip>
</tr>
</tbody>
</v-simple-table>
@ -118,17 +151,17 @@
import { mapGetters } from 'vuex'
import { FullScreenModal } from '@/mixins'
export default {
name: 'Info',
mixins: [FullScreenModal],
props: {
hash: String
},
computed: {
...mapGetters(['getTorrent']),
torrent() {
return this.getTorrent(this.hash)
}
name: 'Info',
mixins: [FullScreenModal],
props: {
hash: String
},
computed: {
...mapGetters(['getTorrent']),
torrent() {
return this.getTorrent(this.hash)
}
}
}
</script>

View file

@ -8,7 +8,7 @@
:hide-default-footer="true"
:style="{ minHeight: phoneLayout ? '' : '75vh', maxHeight: '75vh'}"
>
<template v-slot:item="row">
<template #item="row">
<tr>
<td class="ip">
<template v-if="row.item.country_code">
@ -18,16 +18,20 @@
: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-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 :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>
@ -48,66 +52,66 @@ 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: [
{ text: 'IP', value: 'ip' },
{ text: 'Connection', value: 'connection' },
{ text: 'Flags', value: 'flags' },
{ text: 'Client', value: 'client' },
{ text: 'Progress', value: 'progress' },
{ text: 'DL Speed', value: 'dl_speed' },
{ text: 'Downloaded', value: 'downloaded' },
{ text: 'UP Speed', value: 'up_speed' },
{ text: 'Uploaded', value: 'uploaded' },
{ text: 'Relevance', value: 'relevance' },
{ text: 'Files', value: 'files' }
],
peersObj: null
}),
methods: {
codeToFlag(val) {
return codeToFlag(val)
},
isWindows() {
return isWindows()
},
async getTorrentPeers() {
const { data } = await qbit.getTorrentPeers(
this.hash,
this.rid + 1 || undefined
)
this.rid = data.rid
this.peersObj = data.peers
}
name: 'Peers',
mixins: [FullScreenModal],
props: { hash: String, isActive: Boolean },
data: () => ({
headers: [
{ text: 'IP', value: 'ip' },
{ text: 'Connection', value: 'connection' },
{ text: 'Flags', value: 'flags' },
{ text: 'Client', value: 'client' },
{ text: 'Progress', value: 'progress' },
{ text: 'DL Speed', value: 'dl_speed' },
{ text: 'Downloaded', value: 'downloaded' },
{ text: 'UP Speed', value: 'up_speed' },
{ text: 'Uploaded', value: 'uploaded' },
{ text: 'Relevance', value: 'relevance' },
{ text: 'Files', value: 'files' }
],
peersObj: null
}),
computed: {
rid: {
get() {
return this.$store.state.rid
},
set(val) {
this.$store.state.rid = val
}
},
watch: {
isActive(active) {
if (active) {
this.getTorrentPeers()
}
}
},
computed: {
rid: {
get() {
return this.$store.state.rid
},
set(val) {
this.$store.state.rid = val
}
},
peers() {
return map(this.peersObj, (value, key) => merge({}, value, { key }))
}
},
created() {
this.getTorrentPeers()
peers() {
return map(this.peersObj, (value, key) => merge({}, value, { key }))
}
},
watch: {
isActive(active) {
if (active) {
this.getTorrentPeers()
}
}
},
created() {
this.getTorrentPeers()
},
methods: {
codeToFlag(val) {
return codeToFlag(val)
},
isWindows() {
return isWindows()
},
async getTorrentPeers() {
const { data } = await qbit.getTorrentPeers(
this.hash,
this.rid + 1 || undefined
)
this.rid = data.rid
this.peersObj = data.peers
}
}
}
</script>

View file

@ -6,7 +6,12 @@
:style="{ minHeight: phoneLayout ? '' : '75vh'}"
>
<v-row>
<v-col :cols="12" :lg="6" :md="6" :sm="12">
<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>
@ -39,14 +44,22 @@
style="font-size: 0.95em !important"
@click="deleteTag(tag)"
@click:close="deleteTag(tag)"
>{{ tag }}</v-chip
>
{{ tag }}
</v-chip>
</div>
<div v-else>
None
</div>
<div v-else>None</div>
</v-flex>
</v-layout>
</v-col>
<v-col :cols="12" :lg="6" :md="6" :sm="12">
<v-col
:cols="12"
:lg="6"
:md="6"
:sm="12"
>
<v-layout
class="mx-auto"
:class="this.$vuetify.breakpoint.smAndDown ? 'mt-12' : ''"
@ -82,9 +95,12 @@
style="font-size: 0.95em !important"
@click="deleteCategory"
@click:close="deleteCategory"
>{{ torrent.category }}</v-chip
>
<div v-else>None</div>
{{ torrent.category }}
</v-chip>
<div v-else>
None
</div>
</v-flex>
</v-layout>
</v-col>
@ -100,45 +116,46 @@ import qbit from '@/services/qbit'
import { FullScreenModal } from '@/mixins'
export default {
name: 'TorrentTagsAndCategories',
props: {
hash: String
name: 'TorrentTagsAndCategories',
mixins: [FullScreenModal],
props: {
hash: String
},
data: () => ({
categories: []
}),
computed: {
...mapGetters(['getTorrent', 'getAvailableTags', 'getCategories']),
torrent() {
return this.getTorrent(this.hash)
},
mixins: [FullScreenModal],
data: () => ({
categories: []
}),
computed: {
...mapGetters(['getTorrent', 'getAvailableTags', 'getCategories']),
torrent() {
return this.getTorrent(this.hash)
},
availableTags() {
const availableTags = this.getAvailableTags()
const currentTags = this.getTorrent(this.hash).tags
return difference(availableTags, currentTags)
},
availableCategories() {
return this.getCategories()
}
availableTags() {
const availableTags = this.getAvailableTags()
const currentTags = this.getTorrent(this.hash).tags
return difference(availableTags, currentTags)
},
methods: {
addTag(tag) {
qbit.addTorrentTag(this.hash, tag)
},
deleteTag(tag) {
qbit.removeTorrentTag(this.hash, tag)
},
setCategory(cat) {
qbit.setCategory(this.hash, cat)
},
deleteCategory() {
qbit.setCategory(this.hash, '')
}
},
created() {
this.$store.commit('FETCH_CATEGORIES')
availableCategories() {
return this.getCategories()
}
},
created() {
this.$store.commit('FETCH_CATEGORIES')
},
methods: {
addTag(tag) {
qbit.addTorrentTag(this.hash, tag)
},
deleteTag(tag) {
qbit.removeTorrentTag(this.hash, tag)
},
setCategory(cat) {
qbit.setCategory(this.hash, cat)
},
deleteCategory() {
qbit.setCategory(this.hash, '')
}
}
}
</script>

View file

@ -7,7 +7,7 @@
:hide-default-footer="true"
:style="{ minHeight: phoneLayout ? '' : '75vh', maxHeight: '75vh'}"
>
<template v-slot:item="row">
<template #item="row">
<tr>
<td>{{ row.item.tier }}</td>
<td>{{ row.item.url }}</td>
@ -27,63 +27,63 @@
import qbit from '@/services/qbit'
import { FullScreenModal } from '@/mixins'
export default {
name: 'Trackers',
mixins: [FullScreenModal],
props: { hash: String, isActive: Boolean },
data: () => ({
headers: [
{ text: '#', value: 'tier' },
{ text: 'URL', value: 'url' },
{ text: 'Status', value: 'status' },
{ text: 'Peers', value: 'num_peers' },
{ text: 'Seeds', value: 'num_seeds' },
{ text: 'Leeches', value: 'num_leeches' },
{ text: 'Downloaded', value: 'num_downloaded' },
{ text: 'Message', value: 'msg' }
],
tempTrackers: []
}),
methods: {
async getTorrentTrackers() {
const { data } = await qbit.getTorrentTrackers(this.hash)
this.tempTrackers = data
}
},
watch: {
isActive(active) {
if (active) {
this.getTorrentTrackers()
}
}
},
filters: {
formatTrackerStatus(status) {
const map = [
'Disabled',
'Not contacted',
'Working',
'Updating',
'Not working'
]
name: 'Trackers',
filters: {
formatTrackerStatus(status) {
const map = [
'Disabled',
'Not contacted',
'Working',
'Updating',
'Not working'
]
return map[status]
},
formatTrackerNum(num) {
if (num === -1) {
return 'N/A'
}
return map[status]
},
formatTrackerNum(num) {
if (num === -1) {
return 'N/A'
}
return num.toString()
}
},
computed: {
trackers() {
return this.tempTrackers
}
},
created() {
this.getTorrentTrackers()
return num.toString()
}
},
mixins: [FullScreenModal],
props: { hash: String, isActive: Boolean },
data: () => ({
headers: [
{ text: '#', value: 'tier' },
{ text: 'URL', value: 'url' },
{ text: 'Status', value: 'status' },
{ text: 'Peers', value: 'num_peers' },
{ text: 'Seeds', value: 'num_seeds' },
{ text: 'Leeches', value: 'num_leeches' },
{ text: 'Downloaded', value: 'num_downloaded' },
{ text: 'Message', value: 'msg' }
],
tempTrackers: []
}),
computed: {
trackers() {
return this.tempTrackers
}
},
watch: {
isActive(active) {
if (active) {
this.getTorrentTrackers()
}
}
},
created() {
this.getTorrentTrackers()
},
methods: {
async getTorrentTrackers() {
const { data } = await qbit.getTorrentTrackers(this.hash)
this.tempTrackers = data
}
}
}
</script>

View file

@ -1,59 +1,83 @@
<template>
<v-dialog
v-model="dialog"
scrollable
:width="dialogWidth"
:fullscreen="phoneLayout"
<v-dialog
v-model="dialog"
scrollable
:width="dialogWidth"
:fullscreen="phoneLayout"
>
<v-card
v-if="torrent"
style="min-height: 400px; overflow: hidden !important"
>
<v-card
v-if="torrent"
style="min-height: 400px; overflow: hidden !important"
<div
:class="`pa-0 project ${torrent.state}`"
:style="{ height: phoneLayout ? '100vh' : '' }"
>
<v-card-title class="pb-0 justify-center primary">
<h2 class="white--text">
Torrent Detail
</h2>
</v-card-title>
<v-tabs
v-model="tab"
background-color="primary"
dark
fixed-tabs
>
<div
:class="`pa-0 project ${torrent.state}`"
:style="{ height: phoneLayout ? '100vh' : '' }"
>
<v-card-title class="pb-0 justify-center primary">
<h2 class="white--text">Torrent Detail</h2>
</v-card-title>
<v-tabs v-model="tab" background-color="primary" dark fixed-tabs>
<v-tab href="#info">Info</v-tab>
<v-tab href="#trackers">Trackers</v-tab>
<v-tab href="#peers">Peers</v-tab>
<v-tab href="#content">Content</v-tab>
<v-tab href="#tagsAndCategories">Tags & Categories</v-tab>
</v-tabs>
<v-tabs-items v-model="tab" :touch="updateTab(tab)">
<v-tab-item value="info">
<info :is-active="tab === 'info'" :hash="hash" />
</v-tab-item>
<v-tab-item value="trackers">
<Trackers
:is-active="tab === 'trackers'"
:hash="hash"
/>
</v-tab-item>
<v-tab-item value="peers">
<Peers :is-active="tab === 'peers'" :hash="hash" />
</v-tab-item>
<v-tab-item value="content">
<Content :is-active="tab === 'content'" :hash="hash" />
</v-tab-item>
<v-tab-item value="tagsAndCategories">
<TagsAndCategories
:is-active="tab === 'tagsAndCategories'"
:hash="hash"
/>
</v-tab-item>
</v-tabs-items>
</div>
<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>
<v-tab href="#info">
Info
</v-tab>
<v-tab href="#trackers">
Trackers
</v-tab>
<v-tab href="#peers">
Peers
</v-tab>
<v-tab href="#content">
Content
</v-tab>
<v-tab href="#tagsAndCategories">
Tags & Categories
</v-tab>
</v-tabs>
<v-tabs-items v-model="tab" :touch="updateTab(tab)">
<v-tab-item value="info">
<info :is-active="tab === 'info'" :hash="hash" />
</v-tab-item>
<v-tab-item value="trackers">
<Trackers
:is-active="tab === 'trackers'"
:hash="hash"
/>
</v-tab-item>
<v-tab-item value="peers">
<Peers :is-active="tab === 'peers'" :hash="hash" />
</v-tab-item>
<v-tab-item value="content">
<Content :is-active="tab === 'content'" :hash="hash" />
</v-tab-item>
<v-tab-item value="tagsAndCategories">
<TagsAndCategories
:is-active="tab === 'tagsAndCategories'"
:hash="hash"
/>
</v-tab-item>
</v-tabs-items>
</div>
<v-fab-transition v-if="phoneLayout">
<v-btn
color="red"
dark
absolute
bottom
right
@click="close"
>
<v-icon>close</v-icon>
</v-btn>
</v-fab-transition>
</v-card>
</v-dialog>
</template>
<script>
@ -63,29 +87,29 @@ import { Modal, FullScreenModal } from '@/mixins'
import { Content, Info, Peers, Trackers, TagsAndCategories } from './Tabs'
export default {
name: 'TorrentDetailModal',
mixins: [Modal, FullScreenModal],
components: { Content, Info, Peers, Trackers, TagsAndCategories },
props: {
hash: String
},
data() {
return {
tab: null,
items: [{ tab: 'Info' }, { tab: 'Content' }],
peers: []
}
},
methods: {
close() {
this.deleteModal()
}
},
computed: {
...mapGetters(['getTorrent']),
torrent() {
return this.getTorrent(this.hash)
}
name: 'TorrentDetailModal',
components: { Content, Info, Peers, Trackers, TagsAndCategories },
mixins: [Modal, FullScreenModal],
props: {
hash: String
},
data() {
return {
tab: null,
items: [{ tab: 'Info' }, { tab: 'Content' }],
peers: []
}
},
computed: {
...mapGetters(['getTorrent']),
torrent() {
return this.getTorrent(this.hash)
}
},
methods: {
close() {
this.deleteModal()
}
}
}
</script>