perf(stores): Rework store structure to prevent circular imports (#1325)

This commit is contained in:
Rémi Marseault 2023-11-19 20:17:59 +01:00 committed by GitHub
parent f69851cc39
commit 179af5a1d6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
88 changed files with 949 additions and 712 deletions

16
package-lock.json generated
View file

@ -8,9 +8,11 @@
"name": "vuetorrent",
"version": "2.1.1",
"dependencies": {
"@faker-js/faker": "^8.3.1",
"@fontsource/roboto": "^5.0.8",
"@fontsource/roboto-mono": "^5.0.14",
"@mdi/js": "^7.2.96",
"@mdi/font": "^7.3.67",
"@mdi/js": "^7.3.67",
"@vueuse/core": "^10.5.0",
"apexcharts": "^3.44.0",
"axios": "^1.6.1",
@ -29,8 +31,6 @@
"vuetify": "^3.4.0"
},
"devDependencies": {
"@faker-js/faker": "^8.1.0",
"@mdi/font": "^7.2.96",
"@pinia/testing": "^0.1.3",
"@types/lodash.debounce": "^4.0.7",
"@types/node": "^20.9.0",
@ -2196,10 +2196,9 @@
}
},
"node_modules/@faker-js/faker": {
"version": "8.2.0",
"resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-8.2.0.tgz",
"integrity": "sha512-VacmzZqVxdWdf9y64lDOMZNDMM/FQdtM9IsaOPKOm2suYwEatb8VkdHqOzXcDnZbk7YDE2BmsJmy/2Hmkn563g==",
"dev": true,
"version": "8.3.1",
"resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-8.3.1.tgz",
"integrity": "sha512-FdgpFxY6V6rLZE9mmIBb9hM0xpfvQOSNOLnzolzKwsE1DH+gC7lEKV1p1IbR0lAYyvYd5a4u3qWJzowUkw1bIw==",
"funding": [
{
"type": "opencollective",
@ -2371,8 +2370,7 @@
"node_modules/@mdi/font": {
"version": "7.3.67",
"resolved": "https://registry.npmjs.org/@mdi/font/-/font-7.3.67.tgz",
"integrity": "sha512-SWxvzRbUQRfewlIV+OF4/YF4DkeTjMWoT8Hh9yeU/5UBVdJZj9Uf4a9+cXjknSIhIaMxZ/4N1O/s7ojApOOGjg==",
"dev": true
"integrity": "sha512-SWxvzRbUQRfewlIV+OF4/YF4DkeTjMWoT8Hh9yeU/5UBVdJZj9Uf4a9+cXjknSIhIaMxZ/4N1O/s7ojApOOGjg=="
},
"node_modules/@mdi/js": {
"version": "7.3.67",

View file

@ -14,9 +14,11 @@
"coverage": "vitest run --coverage"
},
"dependencies": {
"@faker-js/faker": "^8.3.1",
"@fontsource/roboto": "^5.0.8",
"@fontsource/roboto-mono": "^5.0.14",
"@mdi/js": "^7.2.96",
"@mdi/font": "^7.3.67",
"@mdi/js": "^7.3.67",
"@vueuse/core": "^10.5.0",
"apexcharts": "^3.44.0",
"axios": "^1.6.1",
@ -35,8 +37,6 @@
"vuetify": "^3.4.0"
},
"devDependencies": {
"@faker-js/faker": "^8.1.0",
"@mdi/font": "^7.2.96",
"@pinia/testing": "^0.1.3",
"@types/lodash.debounce": "^4.0.7",
"@types/node": "^20.9.0",

View file

@ -2,16 +2,22 @@
import AddPanel from '@/components/AddPanel.vue'
import DnDZone from '@/components/DnDZone.vue'
import Navbar from '@/components/Navbar/Navbar.vue'
import { useAddTorrentStore } from '@/stores/addTorrents'
import { useAppStore } from '@/stores/app'
import { useAuthStore } from '@/stores/auth'
import { useDialogStore } from '@/stores/dialog'
import { useLogStore } from '@/stores/logs'
import { useMaindataStore } from '@/stores/maindata'
import { usePreferenceStore } from '@/stores/preferences'
import { useVueTorrentStore } from '@/stores/vuetorrent'
import { TitleOptions } from '@/constants/vuetorrent'
import { formatPercent, formatSpeed } from '@/helpers'
import {
useAddTorrentStore,
useAppStore,
useAuthStore,
useDialogStore,
useLogStore,
useMaindataStore,
usePreferenceStore,
useTorrentStore,
useVueTorrentStore
} from '@/stores'
import { storeToRefs } from 'pinia'
import { onBeforeMount, watch } from 'vue'
import { onBeforeMount, watch, watchEffect } from 'vue'
const addTorrentStore = useAddTorrentStore()
const appStore = useAppStore()
@ -19,8 +25,11 @@ const authStore = useAuthStore()
const dialogStore = useDialogStore()
const logStore = useLogStore()
const maindataStore = useMaindataStore()
const { serverState } = storeToRefs(maindataStore)
const { torrents } = storeToRefs(useTorrentStore())
const preferencesStore = usePreferenceStore()
const vuetorrentStore = useVueTorrentStore()
const { language, matchSystemTheme, uiTitleCustom, uiTitleType, useBitSpeed } = storeToRefs(vuetorrentStore)
const checkAuthentication = async () => {
await authStore.updateAuthStatus()
@ -43,12 +52,12 @@ const blockContextMenu = () => {
}
onBeforeMount(() => {
if (vuetorrentStore.matchSystemTheme) {
if (matchSystemTheme.value) {
vuetorrentStore.updateSystemTheme()
} else {
vuetorrentStore.updateTheme()
}
vuetorrentStore.setLanguage(vuetorrentStore.language)
vuetorrentStore.setLanguage(language.value)
checkAuthentication()
blockContextMenu()
})
@ -72,6 +81,39 @@ watch(
immediate: true
}
)
watchEffect(() => {
const mode = uiTitleType.value
switch (mode) {
case TitleOptions.GLOBAL_SPEED:
document.title =
'[' +
`D: ${ formatSpeed(serverState.value?.dl_info_speed ?? 0, useBitSpeed.value) }, ` +
`U: ${ formatSpeed(serverState.value?.up_info_speed ?? 0, useBitSpeed.value) }` +
'] VueTorrent'
break
case TitleOptions.FIRST_TORRENT_STATUS:
const torrent = torrents.value.at(0)
if (torrent) {
document.title =
'[' +
`D: ${ formatSpeed(torrent.dlspeed, useBitSpeed.value) }, ` +
`U: ${ formatSpeed(torrent.upspeed, useBitSpeed.value) }, ` +
`${ formatPercent(torrent.progress) }` +
'] VueTorrent'
} else {
document.title = '[N/A] VueTorrent'
}
break
case TitleOptions.CUSTOM:
document.title = uiTitleCustom.value
break
case TitleOptions.DEFAULT:
default:
document.title = 'VueTorrent'
break
}
})
</script>
<template>

View file

@ -1,7 +1,6 @@
<script setup lang="ts">
import AddTorrentDialog from '@/components/Dialogs/AddTorrentDialog.vue'
import { useAddTorrentStore } from '@/stores/addTorrents'
import { useDialogStore } from '@/stores/dialog'
import { useAddTorrentStore, useDialogStore } from '@/stores'
const addTorrentStore = useAddTorrentStore()
const dialogStore = useDialogStore()
@ -12,7 +11,8 @@ function openAddTorrentDialog() {
</script>
<template>
<v-bottom-navigation :active="addTorrentStore.pendingTorrentsCount > 0" class="pointer" v-touch="{ up: openAddTorrentDialog }" @click="openAddTorrentDialog">
<v-bottom-navigation :active="addTorrentStore.pendingTorrentsCount > 0" class="pointer"
v-touch="{ up: openAddTorrentDialog }" @click="openAddTorrentDialog">
<v-list-item :title="$t('navbar.addPanel.torrentsPendingCount', addTorrentStore.pendingTorrentsCount)" />
<v-spacer />
<v-list-item>

View file

@ -1,6 +1,6 @@
<script setup lang="ts">
import { formatDataUnit, formatDataValue } from '@/helpers'
import { useVueTorrentStore } from '@/stores/vuetorrent'
import { useVueTorrentStore } from '@/stores'
defineProps<{ title: string; color: string; value: number }>()
@ -12,8 +12,11 @@ const vueTorrentStore = useVueTorrentStore()
<v-row data-testid="card-wrapper" :class="[`text-${color}`]">
<v-col data-testid="card-title" cols="7" class="text-subtitle-1">{{ title }}</v-col>
<v-col cols="5" class="">
<span data-testid="card-value" class="text-subtitle-1 roboto">{{ formatDataValue(value, vueTorrentStore.useBinarySize) }}</span>
<span data-testid="card-unit" class="font-weight-light text-caption ml-1 text-subtitle-1">{{ formatDataUnit(value, vueTorrentStore.useBinarySize) }}</span>
<span data-testid="card-value"
class="text-subtitle-1 roboto">{{ formatDataValue(value, vueTorrentStore.useBinarySize) }}</span>
<span data-testid="card-unit" class="font-weight-light text-caption ml-1 text-subtitle-1">{{
formatDataUnit(value, vueTorrentStore.useBinarySize)
}}</span>
</v-col>
</v-row>
</v-sheet>
@ -21,6 +24,7 @@ const vueTorrentStore = useVueTorrentStore()
<style scoped lang="scss">
@import '@fontsource/roboto-mono';
.roboto {
font-family: 'Roboto Mono', sans-serif !important;
font-weight: 500;

View file

@ -1,6 +1,6 @@
<script setup lang="ts">
import { formatSpeedUnit, formatSpeedValue } from '@/helpers'
import { useVueTorrentStore } from '@/stores/vuetorrent'
import { useVueTorrentStore } from '@/stores'
defineProps({
icon: {
@ -27,8 +27,11 @@ const vueTorrentStore = useVueTorrentStore()
<v-icon class="" :icon="icon" :color="color" />
</v-col>
<v-col cols="8" class="d-flex flex-column align-center justify-center">
<span class="text-subtitle-1 roboto" :class="`text-${color}`">{{ formatSpeedValue(value, vueTorrentStore.useBitSpeed) }}</span>
<span class="text-caption" :class="`text-${color}`">{{ formatSpeedUnit(value, vueTorrentStore.useBitSpeed) }}</span>
<span class="text-subtitle-1 roboto"
:class="`text-${color}`">{{ formatSpeedValue(value, vueTorrentStore.useBitSpeed) }}</span>
<span class="text-caption" :class="`text-${color}`">{{
formatSpeedUnit(value, vueTorrentStore.useBitSpeed)
}}</span>
</v-col>
</v-row>
</v-sheet>
@ -36,6 +39,7 @@ const vueTorrentStore = useVueTorrentStore()
<style scoped lang="scss">
@import '@fontsource/roboto-mono';
.roboto {
font-family: 'Roboto Mono', sans-serif !important;
font-weight: 500;

View file

@ -1,6 +1,6 @@
<script setup lang="ts">
import { formatDataUnit, formatDataValue } from '@/helpers'
import { useVueTorrentStore } from '@/stores/vuetorrent'
import { useVueTorrentStore } from '@/stores'
import { Torrent } from '@/types/vuetorrent'
defineProps<{ torrent: Torrent; title: string; value: string }>()
@ -11,7 +11,7 @@ const vuetorrentStore = useVueTorrentStore()
<template>
<div class="d-flex flex-column">
<div class="text-caption text-grey">
{{ $t(`torrent.properties.${title}`) }}
{{ $t(`torrent.properties.${ title }`) }}
</div>
<div>
{{ formatDataValue(torrent[value], vuetorrentStore.useBinarySize) }}

View file

@ -1,7 +1,7 @@
<script setup lang="ts">
import { useVueTorrentStore } from '@/stores/vuetorrent'
import { Torrent } from '@/types/vuetorrent'
import dayjs from '@/plugins/dayjs'
import { useVueTorrentStore } from '@/stores'
import { Torrent } from '@/types/vuetorrent'
defineProps<{ torrent: Torrent; title: string; value: string }>()
@ -11,7 +11,7 @@ const vueTorrentStore = useVueTorrentStore()
<template>
<div class="d-flex flex-column">
<div class="text-caption text-grey">
{{ $t(`torrent.properties.${title}`) }}
{{ $t(`torrent.properties.${ title }`) }}
</div>
<div>
<span v-if="torrent[value] > 0">

View file

@ -1,6 +1,6 @@
<script setup lang="ts">
import { formatSpeedUnit, formatSpeedValue } from '@/helpers'
import { useVueTorrentStore } from '@/stores/vuetorrent'
import { useVueTorrentStore } from '@/stores'
import { Torrent } from '@/types/vuetorrent'
defineProps<{ torrent: Torrent; title: string; value: string }>()
@ -11,7 +11,7 @@ const vuetorrentStore = useVueTorrentStore()
<template>
<div class="d-flex flex-column">
<div class="text-caption text-grey">
{{ $t(`torrent.properties.${title}`) }}
{{ $t(`torrent.properties.${ title }`) }}
</div>
<div>
{{ formatSpeedValue(torrent[value], vuetorrentStore.useBitSpeed) }}

View file

@ -5,10 +5,7 @@ import MoveTorrentDialog from '@/components/Dialogs/MoveTorrentDialog.vue'
import RenameTorrentDialog from '@/components/Dialogs/RenameTorrentDialog.vue'
import ShareLimitDialog from '@/components/Dialogs/ShareLimitDialog.vue'
import SpeedLimitDialog from '@/components/Dialogs/SpeedLimitDialog.vue'
import { useDashboardStore } from '@/stores/dashboard'
import { useDialogStore } from '@/stores/dialog'
import { useMaindataStore } from '@/stores/maindata'
import { usePreferenceStore } from '@/stores/preferences'
import { useDashboardStore, useDialogStore, useMaindataStore, usePreferenceStore, useTorrentStore } from '@/stores'
import { TRCMenuEntry } from '@/types/vuetorrent'
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
@ -25,6 +22,7 @@ const dashboardStore = useDashboardStore()
const dialogStore = useDialogStore()
const maindataStore = useMaindataStore()
const preferenceStore = usePreferenceStore()
const torrentStore = useTorrentStore()
const trcVisible = computed({
get: () => props.modelValue,
@ -34,20 +32,20 @@ const trcVisible = computed({
const isMultiple = computed(() => dashboardStore.selectedTorrents.length > 1)
const hashes = computed(() => dashboardStore.selectedTorrents)
const hash = computed(() => hashes.value[0])
const torrent = computed(() => maindataStore.getTorrentByHash(hash.value))
const torrents = computed(() => dashboardStore.selectedTorrents.map(maindataStore.getTorrentByHash).filter(torrent => !!torrent))
const torrent = computed(() => torrentStore.getTorrentByHash(hash.value))
const torrents = computed(() => dashboardStore.selectedTorrents.map(torrentStore.getTorrentByHash).filter(torrent => !!torrent))
const availableCategories = computed(() => [{ name: '' }, ...maindataStore.categories])
async function resumeTorrents() {
await maindataStore.resumeTorrents(hashes)
await torrentStore.resumeTorrents(hashes)
}
async function forceResumeTorrents() {
await maindataStore.forceResumeTorrents(hashes)
await torrentStore.forceResumeTorrents(hashes)
}
async function pauseTorrents() {
await maindataStore.pauseTorrents(hashes)
await torrentStore.pauseTorrents(hashes)
}
function deleteTorrents() {
@ -63,7 +61,7 @@ function renameTorrents() {
}
async function forceRecheck() {
await maindataStore.recheckTorrents(hashes)
await torrentStore.recheckTorrents(hashes)
}
async function forceReannounce() {
@ -87,8 +85,8 @@ function hasTag(tag: string) {
}
async function toggleTag(tag: string) {
if (hasTag(tag)) await maindataStore.removeTorrentTags(hashes.value, [tag])
else await maindataStore.addTorrentTags(hashes.value, [tag])
if (hasTag(tag)) await torrentStore.removeTorrentTags(hashes.value, [tag])
else await torrentStore.addTorrentTags(hashes.value, [tag])
}
async function copyValue(valueToCopy: string) {
@ -109,12 +107,12 @@ function setShareLimit() {
async function exportTorrents() {
hashes.value.forEach(hash => {
maindataStore.exportTorrent(hash).then(blob => {
torrentStore.exportTorrent(hash).then(blob => {
const url = window.URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
link.style.opacity = '0'
link.setAttribute('download', `${hash}.torrent`)
link.setAttribute('download', `${ hash }.torrent`)
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
@ -173,22 +171,22 @@ const menuData = computed<TRCMenuEntry[]>(() => [
{
text: t('dashboard.right_click.priority.top'),
icon: 'mdi-priority-high',
action: async () => await maindataStore.setTorrentPriority(hashes.value, 'topPrio')
action: async () => await torrentStore.setTorrentPriority(hashes.value, 'topPrio')
},
{
text: t('dashboard.right_click.priority.increase'),
icon: 'mdi-arrow-up',
action: async () => await maindataStore.setTorrentPriority(hashes.value, 'increasePrio')
action: async () => await torrentStore.setTorrentPriority(hashes.value, 'increasePrio')
},
{
text: t('dashboard.right_click.priority.decrease'),
icon: 'mdi-arrow-down',
action: async () => await maindataStore.setTorrentPriority(hashes.value, 'decreasePrio')
action: async () => await torrentStore.setTorrentPriority(hashes.value, 'decreasePrio')
},
{
text: t('dashboard.right_click.priority.bottom'),
icon: 'mdi-priority-low',
action: async () => await maindataStore.setTorrentPriority(hashes.value, 'bottomPrio')
action: async () => await torrentStore.setTorrentPriority(hashes.value, 'bottomPrio')
}
]
},
@ -212,7 +210,7 @@ const menuData = computed<TRCMenuEntry[]>(() => [
disabledIcon: 'mdi-label-off',
children: availableCategories.value.map(category => ({
text: category.name === '' ? t('dashboard.right_click.category.clear') : category.name,
action: async () => await maindataStore.setTorrentCategory(hashes.value, category.name)
action: async () => await torrentStore.setTorrentCategory(hashes.value, category.name)
}))
},
{
@ -273,7 +271,8 @@ const menuData = computed<TRCMenuEntry[]>(() => [
</script>
<template>
<v-menu v-if="trcVisible" v-model="trcVisible" activator="parent" :close-on-content-click="true" transition="slide-y-transition" scroll-strategy="none">
<v-menu v-if="trcVisible" v-model="trcVisible" activator="parent" :close-on-content-click="true"
transition="slide-y-transition" scroll-strategy="none">
<v-list>
<v-list-item>
<div class="d-flex justify-space-around">
@ -286,7 +285,8 @@ const menuData = computed<TRCMenuEntry[]>(() => [
<v-tooltip location="top">
<template v-slot:activator="{ props }">
<v-btn density="compact" variant="plain" icon="mdi-fast-forward" v-bind="props" @click="forceResumeTorrents" />
<v-btn density="compact" variant="plain" icon="mdi-fast-forward" v-bind="props"
@click="forceResumeTorrents" />
</template>
<span>Force Resume</span>
</v-tooltip>
@ -300,7 +300,8 @@ const menuData = computed<TRCMenuEntry[]>(() => [
<v-tooltip location="top">
<template v-slot:activator="{ props }">
<v-btn color="red" density="compact" variant="plain" icon="mdi-delete-forever" v-bind="props" @click="deleteTorrents" />
<v-btn color="red" density="compact" variant="plain" icon="mdi-delete-forever" v-bind="props"
@click="deleteTorrents" />
</template>
<span>Delete</span>
</v-tooltip>

View file

@ -1,8 +1,7 @@
<script setup lang="ts">
import { DashboardPropertyType } from '@/constants/vuetorrent'
import { doesCommand } from '@/helpers'
import { useDashboardStore } from '@/stores/dashboard'
import { useVueTorrentStore } from '@/stores/vuetorrent'
import { useDashboardStore, useVueTorrentStore } from '@/stores'
import { Torrent } from '@/types/vuetorrent'
import { computed } from 'vue'
import ItemAmount from './DashboardItems/ItemAmount.vue'
@ -62,11 +61,13 @@ const isTorrentSelected = computed(() => dashboardStore.isTorrentInSelection(pro
</script>
<template>
<v-card :class="`sideborder ${torrent.state} pointer`" :color="isTorrentSelected ? `torrent-${torrent.state}-darken-3` : undefined" width="100%" @click="onClick">
<v-card :class="`sideborder ${torrent.state} pointer`"
:color="isTorrentSelected ? `torrent-${torrent.state}-darken-3` : undefined" width="100%" @click="onClick">
<v-card-title class="text-wrap text-subtitle-1 pt-1 pb-0">{{ torrent.name }}</v-card-title>
<v-card-text class="pa-2 pt-0">
<div class="d-flex gap flex-wrap">
<component :is="getComponent(ppt.type)" :torrent="torrent" v-bind="ppt.props" v-for="ppt in torrentProperties" />
<component :is="getComponent(ppt.type)" :torrent="torrent" v-bind="ppt.props"
v-for="ppt in torrentProperties" />
</div>
</v-card-text>
</v-card>

View file

@ -1,10 +1,7 @@
<script lang="ts" setup>
import { useDialog } from '@/composables'
import { AppPreferences } from '@/constants/qbit'
import { useAddTorrentStore } from '@/stores/addTorrents'
import { useMaindataStore } from '@/stores/maindata'
import { usePreferenceStore } from '@/stores/preferences'
import { useVueTorrentStore } from '@/stores/vuetorrent'
import { useAddTorrentStore, useMaindataStore, usePreferenceStore, useTorrentStore, useVueTorrentStore } from '@/stores'
import { storeToRefs } from 'pinia'
import { computed, ref } from 'vue'
import { useI18n } from 'vue-i18n'
@ -20,9 +17,10 @@ const props = withDefaults(defineProps<{
const { isOpened } = useDialog(props.guid)
const { t } = useI18n()
const addTorrentStore = useAddTorrentStore()
const maindataStore = useMaindataStore()
const { urls, files, form } = storeToRefs(addTorrentStore)
const maindataStore = useMaindataStore()
const preferenceStore = usePreferenceStore()
const torrentStore = useTorrentStore()
const vueTorrentStore = useVueTorrentStore()
const fileOverflowDisplayLimit = 2
@ -139,7 +137,7 @@ const inactiveSeedingTimeLimit = computed({
function submit() {
if (!isFormValid.value) return
const promise = maindataStore.addTorrents(form.value, files.value)
const promise = torrentStore.addTorrents(form.value, files.value)
.then(() => {
addTorrentStore.resetForm()
close()

View file

@ -1,6 +1,6 @@
<script setup lang="ts">
import { useDialog } from '@/composables'
import { useMaindataStore } from '@/stores/maindata'
import { useMaindataStore } from '@/stores'
import { Category } from '@/types/qbit/models'
import { onBeforeMount, reactive, ref } from 'vue'
import { useI18n } from 'vue-i18n'
@ -49,12 +49,15 @@ onBeforeMount(() => {
<template>
<v-dialog v-model="isOpened">
<v-card>
<v-card-title>{{ $t(`dialogs.category.title.${initialCategory ? 'edit' : 'create'}`) }}</v-card-title>
<v-card-title>{{ $t(`dialogs.category.title.${ initialCategory ? 'edit' : 'create' }`) }}</v-card-title>
<v-card-text>
<v-form v-model="isFormValid" ref="form" @submit.prevent @keydown.enter.prevent="submit">
<v-text-field v-if="!!initialCategory" :model-value="initialCategory.name" disabled :label="$t('dialogs.category.oldName')" />
<v-text-field v-model="formData.name" :rules="nameRules" :autofocus="!initialCategory" :label="$t('dialogs.category.name')" />
<v-text-field v-model="formData.savePath" :autofocus="!!initialCategory" :label="$t('dialogs.category.savePath')" />
<v-text-field v-if="!!initialCategory" :model-value="initialCategory.name" disabled
:label="$t('dialogs.category.oldName')" />
<v-text-field v-model="formData.name" :rules="nameRules" :autofocus="!initialCategory"
:label="$t('dialogs.category.name')" />
<v-text-field v-model="formData.savePath" :autofocus="!!initialCategory"
:label="$t('dialogs.category.savePath')" />
<v-scroll-x-transition>
<div class="text-warning" v-if="!!initialCategory && initialCategory.name !== formData.name">
<v-icon>mdi-alert</v-icon>

View file

@ -1,8 +1,6 @@
<script setup lang="ts">
import { useDialog } from '@/composables'
import { useDashboardStore } from '@/stores/dashboard'
import { useMaindataStore } from '@/stores/maindata'
import { useVueTorrentStore } from '@/stores/vuetorrent'
import { useDashboardStore, useTorrentStore, useVueTorrentStore } from '@/stores'
import { computed, onBeforeMount, onUnmounted, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { useRoute, useRouter } from 'vue-router'
@ -19,18 +17,18 @@ const route = useRoute()
const router = useRouter()
const { t } = useI18n()
const dashboardStore = useDashboardStore()
const maindataStore = useMaindataStore()
const torrentStore = useTorrentStore()
const vuetorrentStore = useVueTorrentStore()
const form = ref<VForm>()
const isFormValid = ref(false)
const selection = computed(() => maindataStore.torrents.filter(t => props.hashes?.includes(t.hash)))
const selection = computed(() => torrentStore.torrents.filter(t => props.hashes?.includes(t.hash)))
async function submit() {
if (!isFormValid.value) return
await maindataStore.deleteTorrents(
await torrentStore.deleteTorrents(
selection.value.map(t => t.hash),
vuetorrentStore.deleteWithFiles
)
@ -73,7 +71,8 @@ onUnmounted(() => {
<div class="d-flex flex-wrap gap">
<span class="pa-1 border wrap-anywhere" v-for="torrent in selection">{{ torrent.name }}</span>
</div>
<v-checkbox v-model="vuetorrentStore.deleteWithFiles" hide-details :label="$t('dialogs.delete.deleteWithFiles')" />
<v-checkbox v-model="vuetorrentStore.deleteWithFiles" hide-details
:label="$t('dialogs.delete.deleteWithFiles')" />
<v-scroll-x-transition>
<div class="text-red" v-show="vuetorrentStore.deleteWithFiles">
<v-icon>mdi-alert</v-icon>

View file

@ -1,8 +1,6 @@
<script setup lang="ts">
import { useDialog } from '@/composables'
import { useAppStore } from '@/stores/app'
import { useAuthStore } from '@/stores/auth'
import { useVueTorrentStore } from '@/stores/vuetorrent'
import { useAppStore, useAuthStore, useVueTorrentStore } from '@/stores'
import { useI18n } from 'vue-i18n'
import { toast } from 'vue3-toastify'

View file

@ -1,8 +1,7 @@
<script setup lang="ts">
import { useDialog } from '@/composables'
import { ConnectionStatus } from '@/constants/qbit'
import { useLogStore } from '@/stores/logs'
import { useMaindataStore } from '@/stores/maindata'
import { useLogStore, useMaindataStore } from '@/stores'
import { computed } from 'vue'
const props = defineProps<{
@ -38,7 +37,9 @@ const close = () => {
<v-row>
<v-col cols="12" sm="6" lg="3">
<div>{{ $t('dialogs.connectionStatus.status') }}</div>
<div :class="['ml-2', connectionStatusColor]">{{ $t('constants.connectionStatus.' + maindataStore.serverState?.connection_status) }}</div>
<div :class="['ml-2', connectionStatusColor]">
{{ $t('constants.connectionStatus.' + maindataStore.serverState?.connection_status) }}
</div>
</v-col>
<v-col cols="12" sm="6" lg="3">
<div>{{ $t('dialogs.connectionStatus.externalIp') }}</div>

View file

@ -1,6 +1,6 @@
<script setup lang="ts">
import { useDialog } from '@/composables'
import { useMaindataStore } from '@/stores/maindata'
import { useTorrentStore } from '@/stores'
import { computed, onBeforeMount, reactive, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { VForm } from 'vuetify/components'
@ -12,7 +12,7 @@ const props = defineProps<{
const { isOpened } = useDialog(props.guid)
const { t } = useI18n()
const maindataStore = useMaindataStore()
const torrentStore = useTorrentStore()
const form = ref<VForm>()
const isFormValid = ref(false)
@ -22,14 +22,14 @@ const formData = reactive({
const rules = [(v: string) => !!v || t('dialogs.moveTorrent.required'), (v: string) => v !== oldPath.value || t('dialogs.moveTorrent.samePath')]
const torrents = computed(() => props.hashes.map(maindataStore.getTorrentByHash))
const torrents = computed(() => props.hashes.map(torrentStore.getTorrentByHash))
const oldPath = computed(() => torrents.value[0]?.savePath)
async function submit() {
await form.value?.validate()
if (!isFormValid.value) return
await maindataStore.moveTorrents(props.hashes, formData.newPath)
await torrentStore.moveTorrents(props.hashes, formData.newPath)
close()
}
@ -50,7 +50,8 @@ onBeforeMount(() => {
<v-card-text>
<v-form v-model="isFormValid" ref="form" @submit.prevent>
<v-text-field v-if="oldPath" :model-value="oldPath" disabled :label="$t('dialogs.moveTorrent.oldPath')" />
<v-text-field v-model="formData.newPath" :rules="rules" autofocus :label="$t('dialogs.moveTorrent.newPath')" @keydown.enter="submit" />
<v-text-field v-model="formData.newPath" :rules="rules" autofocus :label="$t('dialogs.moveTorrent.newPath')"
@keydown.enter="submit" />
</v-form>
</v-card-text>
<v-card-actions>

View file

@ -1,6 +1,6 @@
<script setup lang="ts">
import { useDialog } from '@/composables'
import { useMaindataStore } from '@/stores/maindata'
import { useMaindataStore } from '@/stores'
import { nextTick, onBeforeMount, reactive, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { VForm } from 'vuetify/components'
@ -65,7 +65,8 @@ onBeforeMount(() => {
<v-card-text>
<v-form v-model="isFormValid" ref="form" @submit.prevent>
<v-text-field v-if="oldName" :model-value="oldName" disabled :label="$t('dialogs.moveTorrentFile.oldName')" />
<v-text-field v-model="formData.newName" ref="input" :rules="rules" autofocus :label="$t('dialogs.moveTorrent.newPath')" @keydown.enter="submit" />
<v-text-field v-model="formData.newName" ref="input" :rules="rules" autofocus
:label="$t('dialogs.moveTorrent.newPath')" @keydown.enter="submit" />
</v-form>
</v-card-text>
<v-card-actions>

View file

@ -1,6 +1,6 @@
<script setup lang="ts">
import { useDialog } from '@/composables'
import { useSearchEngineStore } from '@/stores/searchEngine'
import { useSearchEngineStore } from '@/stores'
import { SearchPlugin } from '@/types/qbit/models'
import { ref } from 'vue'
import { useI18n } from 'vue-i18n'
@ -83,7 +83,8 @@ function closeInstallDialog() {
<v-spacer />
<v-btn :text="$t('dialogs.pluginManager.update')" color="accent" class="mr-2" :loading="updateLoading" @click="updatePlugins" />
<v-btn :text="$t('dialogs.pluginManager.update')" color="accent" class="mr-2" :loading="updateLoading"
@click="updatePlugins" />
<v-dialog v-model="installisOpened">
<template v-slot:activator="{ props }">
<v-btn v-bind="props" color="primary">
@ -106,7 +107,8 @@ function closeInstallDialog() {
</v-dialog>
</v-card-title>
<v-card-text>
<v-data-table :headers="headers" items-per-page="-1" :items="searchEngineStore.searchPlugins" :sort-by="[{ key: 'fullName', order: 'asc' }]" :loading="loading">
<v-data-table :headers="headers" items-per-page="-1" :items="searchEngineStore.searchPlugins"
:sort-by="[{ key: 'fullName', order: 'asc' }]" :loading="loading">
<template v-slot:item.enabled="{ item }">
<v-checkbox-btn :model-value="item.enabled" @click="onTogglePlugin(item)" />
</template>

View file

@ -1,6 +1,6 @@
<script setup lang="ts">
import { useDialog } from '@/composables'
import { useMaindataStore } from '@/stores/maindata'
import { useTorrentStore } from '@/stores'
import { computed, onBeforeMount, onMounted, reactive, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { VForm } from 'vuetify/components'
@ -12,7 +12,7 @@ const props = defineProps<{
const { isOpened } = useDialog(props.guid)
const { t } = useI18n()
const maindataStore = useMaindataStore()
const torrentStore = useTorrentStore()
const field = ref<HTMLInputElement>()
const form = ref<VForm>()
@ -23,14 +23,14 @@ const formData = reactive({
const rules = [(v: string) => !!v || t('dialogs.renameTorrent.required'), (v: string) => v !== oldName.value || t('dialogs.renameTorrent.sameName')]
const torrent = computed(() => maindataStore.getTorrentByHash(props.hash))
const torrent = computed(() => torrentStore.getTorrentByHash(props.hash))
const oldName = computed(() => torrent.value?.name)
async function submit() {
await form.value?.validate()
if (!isFormValid.value) return
await maindataStore.renameTorrent(props.hash, formData.newName)
await torrentStore.renameTorrent(props.hash, formData.newName)
close()
}
@ -54,7 +54,8 @@ onMounted(() => {
<v-card-text>
<v-form v-model="isFormValid" ref="form" @submit.prevent>
<v-text-field v-if="oldName" :model-value="oldName" disabled :label="$t('dialogs.renameTorrent.oldName')" />
<v-text-field v-model="formData.newName" ref="field" :rules="rules" autofocus :label="$t('dialogs.renameTorrent.newName')" @keydown.enter="submit" />
<v-text-field v-model="formData.newName" ref="field" :rules="rules" autofocus
:label="$t('dialogs.renameTorrent.newName')" @keydown.enter="submit" />
</v-form>
</v-card-text>
<v-card-actions>

View file

@ -1,6 +1,6 @@
<script setup lang="ts">
import { useDialog } from '@/composables'
import { useRssStore } from '@/stores/rss'
import { useRssStore } from '@/stores'
import { Feed } from '@/types/qbit/models'
import { onBeforeMount, reactive, ref } from 'vue'
import { VForm } from 'vuetify/components'
@ -45,7 +45,7 @@ onBeforeMount(() => {
<template>
<v-dialog v-model="isOpened">
<v-card>
<v-card-title>{{ $t(`dialogs.rss.feed.title.${initialFeed ? 'edit' : 'create'}`) }}</v-card-title>
<v-card-title>{{ $t(`dialogs.rss.feed.title.${ initialFeed ? 'edit' : 'create' }`) }}</v-card-title>
<v-card-text>
<v-form v-model="isFormValid" ref="form" @submit.prevent>
<v-text-field v-model="formData.name" :label="$t('dialogs.rss.feed.name')" />

View file

@ -1,8 +1,7 @@
<script setup lang="ts">
import { useDialog } from '@/composables'
import { ContentLayout } from '@/constants/qbit/AppPreferences'
import { useMaindataStore } from '@/stores/maindata'
import { useRssStore } from '@/stores/rss'
import { useMaindataStore, useRssStore } from '@/stores'
import { FeedRule } from '@/types/qbit/models'
import { computed, onBeforeMount, reactive, ref } from 'vue'
import { useI18n } from 'vue-i18n'
@ -51,7 +50,10 @@ const contentLayoutOptions = [
{ title: t('constants.contentLayout.nosubfolder'), value: ContentLayout.NO_SUBFOLDER }
]
const categories = computed(() => {
return [{ title: t('common.none'), value: '' }, ...maindataStore.categories.map(category => ({ title: category.name, value: category.name }))]
return [{ title: t('common.none'), value: '' }, ...maindataStore.categories.map(category => ({
title: category.name,
value: category.name
}))]
})
const lastMatch = computed(() => {
@ -143,19 +145,26 @@ onBeforeMount(async () => {
<v-text-field v-model="formData.mustContain" :label="$t('dialogs.rss.rule.mustContain')" />
<v-text-field v-model="formData.mustNotContain" :label="$t('dialogs.rss.rule.mustNotContain')" />
<v-checkbox v-model="formData.smartFilter" hide-details :label="$t('dialogs.rss.rule.smartFilter')" />
<v-text-field v-model="formData.episodeFilter" :placeholder="$t('dialogs.rss.rule.episodeFilterPlaceholder')" :label="$t('dialogs.rss.rule.episodeFilter')" />
<v-text-field v-model="formData.episodeFilter"
:placeholder="$t('dialogs.rss.rule.episodeFilterPlaceholder')"
:label="$t('dialogs.rss.rule.episodeFilter')" />
<v-divider class="mb-4" />
<v-select v-model="formData.assignedCategory" :items="categories" :label="$t('dialogs.rss.rule.assignedCategory')" />
<v-text-field v-model="formData.savePath" :placeholder="$t('dialogs.rss.rule.savePathPlaceholder')" :label="$t('dialogs.rss.rule.savePath')" />
<v-text-field v-model="formData.ignoreDays" type="number" :hint="$t('dialogs.rss.rule.ignoreDaysHint')" :label="$t('dialogs.rss.rule.ignoreDays')" />
<v-select v-model="formData.assignedCategory" :items="categories"
:label="$t('dialogs.rss.rule.assignedCategory')" />
<v-text-field v-model="formData.savePath" :placeholder="$t('dialogs.rss.rule.savePathPlaceholder')"
:label="$t('dialogs.rss.rule.savePath')" />
<v-text-field v-model="formData.ignoreDays" type="number" :hint="$t('dialogs.rss.rule.ignoreDaysHint')"
:label="$t('dialogs.rss.rule.ignoreDays')" />
<v-text-field v-model="lastMatch" disabled :label="$t('dialogs.rss.rule.lastMatch.label')" />
<v-divider />
<v-select v-model="formData.addPaused" :items="addPausedOptions" :label="$t('constants.addPaused.title')" />
<v-select v-model="formData.torrentContentLayout" :items="contentLayoutOptions" :label="$t('constants.contentLayout.title')" />
<v-select v-model="formData.addPaused" :items="addPausedOptions"
:label="$t('constants.addPaused.title')" />
<v-select v-model="formData.torrentContentLayout" :items="contentLayoutOptions"
:label="$t('constants.contentLayout.title')" />
<v-list-subheader>{{ $t('dialogs.rss.rule.affectedFeedsSubheader') }}</v-list-subheader>
@ -168,7 +177,8 @@ onBeforeMount(async () => {
</v-col>
</v-row>
<v-checkbox v-for="item in rssStore.feeds" v-model="formData.affectedFeeds" multiple hide-details :label="item.name" :value="item.url" />
<v-checkbox v-for="item in rssStore.feeds" v-model="formData.affectedFeeds" multiple hide-details
:label="item.name" :value="item.url" />
</v-col>
<v-divider :vertical="!$vuetify.display.mobile" />
@ -181,7 +191,8 @@ onBeforeMount(async () => {
<v-list-subheader inset v-else-if="item.type === 'subheader'">{{ item.value }}</v-list-subheader>
<v-list-item v-else class="mb-3">{{ item.value }}</v-list-item>
</template>
<v-list-item v-if="matchingArticles.length === 0" :title="$t('dialogs.rss.rule.matchingArticles.noMatch')" />
<v-list-item v-if="matchingArticles.length === 0"
:title="$t('dialogs.rss.rule.matchingArticles.noMatch')" />
</v-list>
</v-col>
</v-row>

View file

@ -1,6 +1,6 @@
<script setup lang="ts">
import { useDialog } from '@/composables'
import { useMaindataStore } from '@/stores/maindata'
import { useMaindataStore, useTorrentStore } from '@/stores'
import { computed, onBeforeMount, ref } from 'vue'
type ShareType = 'global' | 'disabled' | 'enabled'
@ -14,6 +14,7 @@ const props = defineProps<{
const { isOpened } = useDialog(props.guid)
const maindataStore = useMaindataStore()
const torrentStore = useTorrentStore()
const isFormValid = ref(false)
@ -53,7 +54,7 @@ async function submit() {
}
onBeforeMount(async () => {
const torrent = maindataStore.getTorrentByHash(props.hash)
const torrent = torrentStore.getTorrentByHash(props.hash)
if (!torrent) {
return close()
}

View file

@ -1,6 +1,6 @@
<script setup lang="ts">
import { useDialog } from '@/composables'
import { useMaindataStore } from '@/stores/maindata.ts'
import { useMaindataStore, useTorrentStore } from '@/stores'
import { onBeforeMount, ref } from 'vue'
const props = defineProps<{
@ -11,6 +11,7 @@ const props = defineProps<{
const { isOpened } = useDialog(props.guid)
const maindataStore = useMaindataStore()
const torrentStore = useTorrentStore()
const isFormValid = ref(false)
const value = ref(0)
@ -33,7 +34,7 @@ async function submit() {
}
onBeforeMount(async () => {
const torrent = maindataStore.getTorrentByHash(props.hash)
const torrent = torrentStore.getTorrentByHash(props.hash)
if (!torrent) {
return close()
}

View file

@ -1,6 +1,6 @@
<script setup lang="ts">
import { useDialog } from '@/composables'
import { useMaindataStore } from '@/stores/maindata'
import { useMaindataStore } from '@/stores'
import { onBeforeMount, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { VForm } from 'vuetify/components'
@ -31,6 +31,7 @@ async function submit() {
close()
}
const close = () => {
isOpened.value = false
}
@ -43,11 +44,12 @@ onBeforeMount(() => {
<template>
<v-dialog v-model="isOpened">
<v-card>
<v-card-title>{{ $t(`dialogs.tag.title.${initialTag ? 'rename' : 'create'}`) }}</v-card-title>
<v-card-title>{{ $t(`dialogs.tag.title.${ initialTag ? 'rename' : 'create' }`) }}</v-card-title>
<v-card-text>
<v-form v-model="isFormValid" ref="form" @submit.prevent @keydown.enter.prevent="submit">
<v-text-field v-if="initialTag" :model-value="initialTag" disabled :label="$t('dialogs.tag.oldName')" />
<v-text-field v-model="tagName" :rules="rules" autofocus :hint="$t('dialogs.tag.hint')" :label="$t('dialogs.tag.name')" />
<v-text-field v-model="tagName" :rules="rules" autofocus :hint="$t('dialogs.tag.hint')"
:label="$t('dialogs.tag.name')" />
<v-scroll-x-transition>
<div class="text-warning" v-if="!!initialTag && initialTag !== tagName">
<v-icon>mdi-alert</v-icon>

View file

@ -1,6 +1,5 @@
<script lang="ts" setup>
import { useAddTorrentStore } from '@/stores/addTorrents'
import { useAuthStore } from '@/stores/auth'
import { useAddTorrentStore, useAuthStore } from '@/stores'
import { useDropZone } from '@vueuse/core'
import { onMounted, onUnmounted, ref } from 'vue'
import { useRoute } from 'vue-router'
@ -23,7 +22,7 @@ function onDrop(files: File[] | null, event: DragEvent) {
// Handle .torrent files
const torrentFiles = (files || [])
.filter(file => file.type === 'application/x-bittorrent' || file.name.endsWith('.torrent'))
.filter(file => file.type === 'application/x-bittorrent' || file.name.endsWith('.torrent'))
const links = event.dataTransfer.getData('text/plain').split('\n')
.filter(link => link.startsWith('magnet:') || link.startsWith('http'))

View file

@ -1,15 +1,14 @@
<script setup lang="ts">
import BottomActions from '@/components/Navbar/SideWidgets/BottomActions.vue'
import CurrentSpeed from '@/components/Navbar/SideWidgets/CurrentSpeed.vue'
import FilterSelect from '@/components/Navbar/SideWidgets/FilterSelect.vue'
import FreeSpace from '@/components/Navbar/SideWidgets/FreeSpace.vue'
import SpeedGraph from '@/components/Navbar/SideWidgets/SpeedGraph.vue'
import TransferStats from '@/components/Navbar/SideWidgets/TransferStats.vue'
import ActiveFilters from '@/components/Navbar/TopWidgets/ActiveFilters.vue'
import TopContainer from '@/components/Navbar/TopWidgets/TopContainer.vue'
import { useDashboardStore } from '@/stores/dashboard'
import { useVueTorrentStore } from '@/stores/vuetorrent'
import { useDashboardStore, useVueTorrentStore } from '@/stores'
import { ref } from 'vue'
import BottomActions from './SideWidgets/BottomActions.vue'
import CurrentSpeed from './SideWidgets/CurrentSpeed.vue'
import FilterSelect from './SideWidgets/FilterSelect.vue'
import FreeSpace from './SideWidgets/FreeSpace.vue'
import SpeedGraph from './SideWidgets/SpeedGraph.vue'
import TransferStats from './SideWidgets/TransferStats.vue'
import ActiveFilters from './TopWidgets/ActiveFilters.vue'
import TopContainer from './TopWidgets/TopContainer.vue'
const dashboardStore = useDashboardStore()
const vueTorrentStore = useVueTorrentStore()
@ -22,7 +21,8 @@ const toggleDrawer = () => {
</script>
<template>
<v-navigation-drawer v-model="isDrawerOpen" :location="vueTorrentStore.isDrawerRight ? 'right' : 'left'" color="primary" disable-route-watcher>
<v-navigation-drawer v-model="isDrawerOpen" :location="vueTorrentStore.isDrawerRight ? 'right' : 'left'"
color="primary" disable-route-watcher>
<v-list class="clean-px px-2 pt-0">
<v-list-item v-if="vueTorrentStore.showCurrentSpeed">
<CurrentSpeed />

View file

@ -2,11 +2,7 @@
import ConfirmShutdownDialog from '@/components/Dialogs/ConfirmShutdownDialog.vue'
import ConnectionStatusDialog from '@/components/Dialogs/ConnectionStatusDialog.vue'
import { ConnectionStatus } from '@/constants/qbit'
import { useAppStore } from '@/stores/app'
import { useAuthStore } from '@/stores/auth'
import { useDialogStore } from '@/stores/dialog'
import { useMaindataStore } from '@/stores/maindata'
import { useVueTorrentStore } from '@/stores/vuetorrent'
import { useAppStore, useAuthStore, useDialogStore, useMaindataStore, useVueTorrentStore } from '@/stores'
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
@ -44,7 +40,7 @@ const connectionStatusText = computed(() => {
key = 'unknown'
}
return t('navbar.side.bottom_actions.conn_status', { status: t(`constants.connectionStatus.${key}`) })
return t('navbar.side.bottom_actions.conn_status', { status: t(`constants.connectionStatus.${ key }`) })
})
const logout = async () => {
@ -58,6 +54,7 @@ const toggleAltSpeed = () => {
function openConnectionStatusDialog() {
dialogStore.createDialog(ConnectionStatusDialog)
}
function openConfirmShutdownDialog() {
dialogStore.createDialog(ConfirmShutdownDialog)
}

View file

@ -1,6 +1,6 @@
<script setup lang="ts">
import SpeedCard from '@/components/Core/SpeedCard.vue'
import { useMaindataStore } from '@/stores/maindata'
import { useMaindataStore } from '@/stores'
import { useI18n } from 'vue-i18n'
const { t } = useI18n()
@ -16,7 +16,8 @@ const maindataStore = useMaindataStore()
<v-sheet color="primary" class="mx-2">
<v-row class="pt-0">
<v-col class="px-1 pt-1">
<SpeedCard icon="mdi-chevron-down" color="download" :value="maindataStore.serverState?.dl_info_speed ?? 0" />
<SpeedCard icon="mdi-chevron-down" color="download"
:value="maindataStore.serverState?.dl_info_speed ?? 0" />
</v-col>
<v-col class="px-1 pt-1">
<SpeedCard icon="mdi-chevron-up" color="upload" :value="maindataStore.serverState?.up_info_speed ?? 0" />

View file

@ -1,7 +1,6 @@
<script lang="ts" setup>
import { TorrentState } from '@/constants/qbit'
import { useMaindataStore } from '@/stores/maindata'
import { useVueTorrentStore } from '@/stores/vuetorrent'
import { useMaindataStore, useTorrentStore, useVueTorrentStore } from '@/stores'
import { storeToRefs } from 'pinia'
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
@ -10,16 +9,18 @@ const { t } = useI18n()
const {
categories: _categories,
tags: _tags,
trackers: _trackers,
trackers: _trackers
} = storeToRefs(useMaindataStore())
const {
statusFilter,
categoryFilter,
tagFilter,
trackerFilter
} = storeToRefs(useMaindataStore())
} = storeToRefs(useTorrentStore())
const vueTorrentStore = useVueTorrentStore()
const statuses = computed(() => Object.values(TorrentState).map(state => (
{ title: t(`torrent.state.${ state }`), value: state }
{ title: t(`torrent.state.${ state }`), value: state }
)))
const categories = computed(() => [
{ title: t('navbar.side.filters.uncategorized'), value: '' },

View file

@ -1,6 +1,6 @@
<script setup lang="ts">
import DataCard from '@/components/Core/DataCard.vue'
import { useMaindataStore } from '@/stores/maindata'
import { useMaindataStore } from '@/stores'
const maindataStore = useMaindataStore()
</script>

View file

@ -1,7 +1,6 @@
<script setup lang="ts">
import { formatSpeed } from '@/helpers'
import { useNavbarStore } from '@/stores/navbar'
import { useVueTorrentStore } from '@/stores/vuetorrent'
import { useNavbarStore, useVueTorrentStore } from '@/stores'
import { ApexOptions } from 'apexcharts'
import { computed, ref } from 'vue'
import { useI18n } from 'vue-i18n'

View file

@ -1,8 +1,8 @@
<script setup lang="ts">
import { useMaindataStore } from '@/stores/maindata'
import { computed } from 'vue'
import DataCard from '@/components/Core/DataCard.vue'
import StringCard from '@/components/Core/StringCard.vue'
import { useMaindataStore } from '@/stores'
import { computed } from 'vue'
const props = defineProps<{ session: boolean }>()
const maindataStore = useMaindataStore()
@ -15,7 +15,10 @@ const ratio = computed(() => (props.session ? undefined : maindataStore.serverSt
<template>
<v-card variant="flat" color="primary">
<v-card-title class="px-0 pb-0 text-uppercase white--text ml-1 font-weight-normal text-caption">{{ title }}</v-card-title>
<v-card-title class="px-0 pb-0 text-uppercase white--text ml-1 font-weight-normal text-caption">{{
title
}}
</v-card-title>
<v-card-text class="px-0 pb-0">
<div class="d-flex flex-column gap">
<DataCard title="Downloaded" :value="download" color="download" icon="mdi-arrow-down" />

View file

@ -1,5 +1,5 @@
<script setup lang="ts">
import { useMaindataStore } from '@/stores/maindata.ts'
import { useTorrentStore } from '@/stores'
import { storeToRefs } from 'pinia'
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
@ -15,8 +15,8 @@ const {
isTagFilterActive,
tagFilter,
isTrackerFilterActive,
trackerFilter,
} = storeToRefs(useMaindataStore())
trackerFilter
} = storeToRefs(useTorrentStore())
const globalFilterActive = computed(
() =>
@ -35,7 +35,7 @@ const isTrackerFilterPresent = computed(() => trackerFilter.value.length > 0)
const globalFilterColor = computed(() => globalFilterActive.value ? 'active-global' : 'active-global-disabled')
const textFilterColor = computed(() => isTextFilterActive.value ? 'active-text' : 'active-text-disabled')
const singleStatusFilterColor = computed(() => isStatusFilterActive.value ? `torrent-${statusFilter.value[0]}` : `torrent-${statusFilter.value[0]}-darken-2`)
const singleStatusFilterColor = computed(() => isStatusFilterActive.value ? `torrent-${ statusFilter.value[0] }` : `torrent-${ statusFilter.value[0] }-darken-2`)
const statusFilterColor = computed(() => isStatusFilterActive.value ? 'active-status' : 'active-status-disabled')
const categoryFilterColor = computed(() => isCategoryFilterActive.value ? 'active-category' : 'active-category-disabled')
const tagFilterColor = computed(() => isTagFilterActive.value ? 'active-tag' : 'active-tag-disabled')
@ -131,7 +131,10 @@ function resetTrackerFilter() {
<v-chip v-if="filterPresentCount > 0" v-bind="props" class="ml-6" :color="globalFilterColor" variant="elevated"
closable @click:close="resetAllFilters()">
<template v-slot:prepend>
<v-icon class="mr-1" @click="toggleAllFilters()">{{ globalFilterActive ? 'mdi-filter' : 'mdi-filter-off' }}</v-icon>
<v-icon class="mr-1" @click="toggleAllFilters()">{{
globalFilterActive ? 'mdi-filter' : 'mdi-filter-off'
}}
</v-icon>
</template>
{{ t('navbar.top.active_filters.menu_label', filterActiveCount) }}
</v-chip>
@ -142,7 +145,10 @@ function resetTrackerFilter() {
<v-chip v-if="isTextFilterPresent" :color="textFilterColor" variant="elevated"
closable @click:close="resetTextFilter()">
<template v-slot:prepend>
<v-icon class="mr-1" @click="toggleTextFilter()">{{ isTextFilterActive ? 'mdi-filter' : 'mdi-filter-off' }}</v-icon>
<v-icon class="mr-1" @click="toggleTextFilter()">{{
isTextFilterActive ? 'mdi-filter' : 'mdi-filter-off'
}}
</v-icon>
</template>
{{ t('navbar.top.active_filters.text', { value: textFilter }) }}
</v-chip>
@ -151,14 +157,20 @@ function resetTrackerFilter() {
<v-chip v-if="statusFilter.length === 1" :color="singleStatusFilterColor" variant="elevated"
closable @click:close="resetStatusFilter()">
<template v-slot:prepend>
<v-icon class="mr-1" @click="toggleStatusFilter()">{{ isStatusFilterActive ? 'mdi-filter' : 'mdi-filter-off' }}</v-icon>
<v-icon class="mr-1" @click="toggleStatusFilter()">{{
isStatusFilterActive ? 'mdi-filter' : 'mdi-filter-off'
}}
</v-icon>
</template>
{{ t('navbar.top.active_filters.state', { value: t(`torrent.state.${ statusFilter[0] }`) }) }}
</v-chip>
<v-chip v-else :color="statusFilterColor" variant="elevated"
closable @click:close="resetStatusFilter()">
<template v-slot:prepend>
<v-icon class="mr-1" @click="toggleStatusFilter()">{{ isStatusFilterActive ? 'mdi-filter' : 'mdi-filter-off' }}</v-icon>
<v-icon class="mr-1" @click="toggleStatusFilter()">{{
isStatusFilterActive ? 'mdi-filter' : 'mdi-filter-off'
}}
</v-icon>
</template>
{{ t('navbar.top.active_filters.multiple_state', statusFilter.length) }}
</v-chip>
@ -168,7 +180,9 @@ function resetTrackerFilter() {
<v-chip v-if="categoryFilter.length === 1" :color="categoryFilterColor" variant="elevated"
closable @click:close="resetCategoryFilter()">
<template v-slot:prepend>
<v-icon class="mr-1" @click="toggleCategoryFilter()">{{ isCategoryFilterActive ? 'mdi-filter' : 'mdi-filter-off' }}</v-icon>
<v-icon class="mr-1" @click="toggleCategoryFilter()">
{{ isCategoryFilterActive ? 'mdi-filter' : 'mdi-filter-off' }}
</v-icon>
</template>
{{
t('navbar.top.active_filters.category', { value: categoryFilter[0] === '' ? t('navbar.side.filters.uncategorized') : categoryFilter[0] })
@ -177,7 +191,9 @@ function resetTrackerFilter() {
<v-chip v-else :color="categoryFilterColor" variant="elevated"
closable @click:close="resetCategoryFilter()">
<template v-slot:prepend>
<v-icon class="mr-1" @click="toggleCategoryFilter()">{{ isCategoryFilterActive ? 'mdi-filter' : 'mdi-filter-off' }}</v-icon>
<v-icon class="mr-1" @click="toggleCategoryFilter()">
{{ isCategoryFilterActive ? 'mdi-filter' : 'mdi-filter-off' }}
</v-icon>
</template>
{{ t('navbar.top.active_filters.multiple_category', categoryFilter.length) }}
</v-chip>
@ -187,7 +203,10 @@ function resetTrackerFilter() {
<v-chip v-if="tagFilter.length === 1" :color="tagFilterColor" variant="elevated"
closable @click:close="resetTagFilter()">
<template v-slot:prepend>
<v-icon class="mr-1" @click="toggleTagFilter()">{{ isTagFilterActive ? 'mdi-filter' : 'mdi-filter-off' }}</v-icon>
<v-icon class="mr-1" @click="toggleTagFilter()">{{
isTagFilterActive ? 'mdi-filter' : 'mdi-filter-off'
}}
</v-icon>
</template>
{{
t('navbar.top.active_filters.tag', { value: tagFilter[0] === null ? t('navbar.side.filters.untagged') : tagFilter[0] })
@ -196,7 +215,10 @@ function resetTrackerFilter() {
<v-chip v-else :color="tagFilterColor" variant="elevated"
closable @click:close="resetTagFilter()">
<template v-slot:prepend>
<v-icon class="mr-1" @click="toggleTagFilter()">{{ isTagFilterActive ? 'mdi-filter' : 'mdi-filter-off' }}</v-icon>
<v-icon class="mr-1" @click="toggleTagFilter()">{{
isTagFilterActive ? 'mdi-filter' : 'mdi-filter-off'
}}
</v-icon>
</template>
{{ t('navbar.top.active_filters.multiple_tag', tagFilter.length) }}
</v-chip>
@ -206,7 +228,9 @@ function resetTrackerFilter() {
<v-chip v-if="trackerFilter.length === 1" :color="trackerFilterColor" variant="elevated"
closable @click:close="resetTrackerFilter()">
<template v-slot:prepend>
<v-icon class="mr-1" @click="toggleTrackerFilter()">{{ isTrackerFilterActive ? 'mdi-filter' : 'mdi-filter-off' }}</v-icon>
<v-icon class="mr-1" @click="toggleTrackerFilter()">
{{ isTrackerFilterActive ? 'mdi-filter' : 'mdi-filter-off' }}
</v-icon>
</template>
{{
t('navbar.top.active_filters.tracker', { value: trackerFilter[0] === '' ? t('navbar.side.filters.untracked') : trackerFilter[0] })
@ -215,7 +239,9 @@ function resetTrackerFilter() {
<v-chip v-else :color="trackerFilterColor" variant="elevated"
closable @click:close="resetTrackerFilter()">
<template v-slot:prepend>
<v-icon class="mr-1" @click="toggleTrackerFilter()">{{ isTrackerFilterActive ? 'mdi-filter' : 'mdi-filter-off' }}</v-icon>
<v-icon class="mr-1" @click="toggleTrackerFilter()">
{{ isTrackerFilterActive ? 'mdi-filter' : 'mdi-filter-off' }}
</v-icon>
</template>
{{ t('navbar.top.active_filters.multiple_tracker', trackerFilter.length) }}
</v-chip>

View file

@ -1,9 +1,7 @@
<script lang="ts" setup>
import AddTorrentDialog from '@/components/Dialogs/AddTorrentDialog.vue'
import ConfirmDeleteDialog from '@/components/Dialogs/ConfirmDeleteDialog.vue'
import { useDashboardStore } from '@/stores/dashboard'
import { useDialogStore } from '@/stores/dialog'
import { useMaindataStore } from '@/stores/maindata'
import { useDashboardStore, useDialogStore, useTorrentStore } from '@/stores'
import { computed } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import TopActions from './TopActions.vue'
@ -13,7 +11,7 @@ const route = useRoute()
const router = useRouter()
const dashboardStore = useDashboardStore()
const dialogStore = useDialogStore()
const maindataStore = useMaindataStore()
const torrentStore = useTorrentStore()
const isOnTorrentDetail = computed(() => route.name === 'torrentDetail')
const hashes = computed(() => (isOnTorrentDetail.value ? [route.params.hash as string] : dashboardStore.selectedTorrents))
@ -23,11 +21,11 @@ function openAddTorrentDialog() {
}
async function resumeTorrents() {
await maindataStore.resumeTorrents(hashes.value)
await torrentStore.resumeTorrents(hashes.value)
}
async function pauseTorrents() {
await maindataStore.pauseTorrents(hashes.value)
await torrentStore.pauseTorrents(hashes.value)
}
function deleteTorrents() {

View file

@ -8,7 +8,7 @@ import {
UtpTcpMixedMode
} from '@/constants/qbit/AppPreferences'
import { qbit } from '@/services'
import { usePreferenceStore } from '@/stores/preferences'
import { usePreferenceStore } from '@/stores'
import { computed, onBeforeMount } from 'vue'
import { useI18n } from 'vue-i18n'
@ -339,7 +339,7 @@ onBeforeMount(async () => {
<v-text-field v-model="preferenceStore.preferences!.socket_send_buffer_size" type="number"
:label="t('settings.advanced.libtorrent.socketSendBufferSize')"
:hint="$t('settings.advanced.libtorrent.socketSendBufferSizeHint')"
suffix="kiB" />
suffix="kiB" />
</v-col>
<v-col cols="12" sm="4">
<v-text-field v-model="preferenceStore.preferences!.socket_receive_buffer_size" type="number"

View file

@ -1,6 +1,6 @@
<script setup lang="ts">
import { FileLogAgeType } from '@/constants/qbit/AppPreferences'
import { usePreferenceStore } from '@/stores/preferences'
import { usePreferenceStore } from '@/stores'
const preferenceStore = usePreferenceStore()

View file

@ -1,6 +1,6 @@
<script setup lang="ts">
import { Encryption, MaxRatioAction } from '@/constants/qbit/AppPreferences'
import { usePreferenceStore } from '@/stores/preferences'
import { usePreferenceStore } from '@/stores'
import { ref } from 'vue'
import { useI18n } from 'vue-i18n'
@ -25,23 +25,28 @@ const thenTypes = ref([
<v-list-subheader>{{ t('settings.bittorrent.privacy.subheader') }}</v-list-subheader>
<v-list-item>
<v-checkbox v-model="preferenceStore.preferences!.dht" hide-details :label="t('settings.bittorrent.privacy.enableDHT')" />
<v-checkbox v-model="preferenceStore.preferences!.dht" hide-details
:label="t('settings.bittorrent.privacy.enableDHT')" />
</v-list-item>
<v-list-item>
<v-checkbox v-model="preferenceStore.preferences!.pex" hide-details :label="t('settings.bittorrent.privacy.enablePeX')" />
<v-checkbox v-model="preferenceStore.preferences!.pex" hide-details
:label="t('settings.bittorrent.privacy.enablePeX')" />
</v-list-item>
<v-list-item>
<v-checkbox v-model="preferenceStore.preferences!.lsd" hide-details :label="t('settings.bittorrent.privacy.enableLPD')" />
<v-checkbox v-model="preferenceStore.preferences!.lsd" hide-details
:label="t('settings.bittorrent.privacy.enableLPD')" />
</v-list-item>
<v-list-item>
<v-select v-model="preferenceStore.preferences!.encryption" hide-details :items="encyptionModeOptions" :label="t('settings.bittorrent.privacy.encryptionMode')" />
<v-select v-model="preferenceStore.preferences!.encryption" hide-details :items="encyptionModeOptions"
:label="t('settings.bittorrent.privacy.encryptionMode')" />
</v-list-item>
<v-list-item>
<v-checkbox v-model="preferenceStore.preferences!.anonymous_mode" hide-details :label="t('settings.bittorrent.privacy.enableAnonymous')" />
<v-checkbox v-model="preferenceStore.preferences!.anonymous_mode" hide-details
:label="t('settings.bittorrent.privacy.enableAnonymous')" />
<a href="https://github.com/qbittorrent/qBittorrent/wiki/Anonymous-Mode" target="_blank">
{{ t('settings.bittorrent.privacy.moreInfo') }}
</a>
@ -50,7 +55,8 @@ const thenTypes = ref([
<v-divider />
<v-list-item class="my-3">
<v-text-field v-model="preferenceStore.preferences!.max_active_checking_torrents" type="number" hide-details :label="t('settings.bittorrent.maxActiveCheckingTorrents')" />
<v-text-field v-model="preferenceStore.preferences!.max_active_checking_torrents" type="number" hide-details
:label="t('settings.bittorrent.maxActiveCheckingTorrents')" />
</v-list-item>
<v-divider />
@ -58,7 +64,8 @@ const thenTypes = ref([
<v-list-item>
<v-row>
<v-col cols="12" class="pb-0">
<v-checkbox v-model="preferenceStore.preferences!.queueing_enabled" hide-details :label="t('settings.bittorrent.torrentQueueing.subheader')" />
<v-checkbox v-model="preferenceStore.preferences!.queueing_enabled" hide-details
:label="t('settings.bittorrent.torrentQueueing.subheader')" />
</v-col>
<v-col cols="12" sm="6" md="4">
@ -134,10 +141,12 @@ const thenTypes = ref([
<v-list-item>
<v-row>
<v-col cols="6">
<v-checkbox v-model="preferenceStore.preferences!.max_ratio_enabled" hide-details :label="t('settings.bittorrent.seedLimits.whenRatioReaches')" />
<v-checkbox v-model="preferenceStore.preferences!.max_ratio_enabled" hide-details
:label="t('settings.bittorrent.seedLimits.whenRatioReaches')" />
</v-col>
<v-col cols="6">
<v-text-field v-model="preferenceStore.preferences!.max_ratio" :disabled="!preferenceStore.preferences!.max_ratio_enabled" type="number" hide-details />
<v-text-field v-model="preferenceStore.preferences!.max_ratio"
:disabled="!preferenceStore.preferences!.max_ratio_enabled" type="number" hide-details />
</v-col>
</v-row>
</v-list-item>
@ -145,7 +154,8 @@ const thenTypes = ref([
<v-list-item>
<v-row>
<v-col cols="6">
<v-checkbox v-model="preferenceStore.preferences!.max_seeding_time_enabled" hide-details :label="t('settings.bittorrent.seedLimits.whenSeedingTimeReaches')" />
<v-checkbox v-model="preferenceStore.preferences!.max_seeding_time_enabled" hide-details
:label="t('settings.bittorrent.seedLimits.whenSeedingTimeReaches')" />
</v-col>
<v-col cols="6">
<v-text-field
@ -161,7 +171,8 @@ const thenTypes = ref([
<v-list-item>
<v-row>
<v-col cols="6">
<v-checkbox v-model="preferenceStore.preferences!.max_inactive_seeding_time_enabled" hide-details :label="t('settings.bittorrent.seedLimits.whenInactiveSeedingTimeReaches')" />
<v-checkbox v-model="preferenceStore.preferences!.max_inactive_seeding_time_enabled" hide-details
:label="t('settings.bittorrent.seedLimits.whenInactiveSeedingTimeReaches')" />
</v-col>
<v-col cols="6">
<v-text-field
@ -191,7 +202,8 @@ const thenTypes = ref([
<v-divider class="mt-3" />
<v-list-item>
<v-checkbox v-model="preferenceStore.preferences!.add_trackers_enabled" hide-details :label="t('settings.bittorrent.autoAddTrackers')" />
<v-checkbox v-model="preferenceStore.preferences!.add_trackers_enabled" hide-details
:label="t('settings.bittorrent.autoAddTrackers')" />
</v-list-item>
<v-list-item>
<v-textarea

View file

@ -1,7 +1,7 @@
<script setup lang="ts">
import PasswordField from '@/components/Core/PasswordField.vue'
import { BitTorrentProtocol, ProxyType } from '@/constants/qbit/AppPreferences'
import { usePreferenceStore } from '@/stores/preferences'
import { usePreferenceStore } from '@/stores'
import { computed, onBeforeMount, ref, watch } from 'vue'
import { useI18n } from 'vue-i18n'

View file

@ -1,7 +1,7 @@
<script setup lang="ts">
import { AppPreferences } from '@/constants/qbit'
import { ScanDirs, ScanDirsEnum } from '@/constants/qbit/AppPreferences'
import { usePreferenceStore } from '@/stores/preferences'
import { usePreferenceStore } from '@/stores'
import { nextTick, onBeforeMount, ref, watch } from 'vue'
import { useI18n } from 'vue-i18n'
@ -147,25 +147,33 @@ const closeDeleteDialog = async () => {
<v-list-subheader>{{ t('settings.downloads.whenAddTorrent.subheader') }}</v-list-subheader>
<v-list-item>
<v-select v-model="preferenceStore.preferences!.torrent_content_layout" hide-details :items="contentLayoutOptions" :label="t('constants.contentLayout.title')" />
<v-select v-model="preferenceStore.preferences!.torrent_content_layout" hide-details :items="contentLayoutOptions"
:label="t('constants.contentLayout.title')" />
<v-checkbox v-model="preferenceStore.preferences!.add_to_top_of_queue" hide-details :label="t('settings.downloads.whenAddTorrent.addToTopOfQueue')" />
<v-checkbox v-model="preferenceStore.preferences!.add_to_top_of_queue" hide-details
:label="t('settings.downloads.whenAddTorrent.addToTopOfQueue')" />
<v-checkbox v-model="preferenceStore.preferences!.merge_trackers" hide-details :label="t('settings.downloads.whenAddTorrent.mergeTrackers')" />
<v-checkbox v-model="preferenceStore.preferences!.merge_trackers" hide-details
:label="t('settings.downloads.whenAddTorrent.mergeTrackers')" />
<v-checkbox v-model="preferenceStore.preferences!.start_paused_enabled" hide-details :label="t('settings.downloads.whenAddTorrent.doNotAutoStart')" />
<v-checkbox v-model="preferenceStore.preferences!.start_paused_enabled" hide-details
:label="t('settings.downloads.whenAddTorrent.doNotAutoStart')" />
<v-select v-model="preferenceStore.preferences!.torrent_stop_condition" hide-details :items="stopConditionOptions" :label="t('constants.stopCondition.title')" />
<v-select v-model="preferenceStore.preferences!.torrent_stop_condition" hide-details :items="stopConditionOptions"
:label="t('constants.stopCondition.title')" />
<v-checkbox v-model="preferenceStore.preferences!.auto_delete_mode" hide-details :label="t('settings.downloads.whenAddTorrent.autoDeleteMode')" />
<v-checkbox v-model="preferenceStore.preferences!.auto_delete_mode" hide-details
:label="t('settings.downloads.whenAddTorrent.autoDeleteMode')" />
</v-list-item>
<v-divider />
<v-list-item>
<v-checkbox v-model="preferenceStore.preferences!.preallocate_all" hide-details :label="t('settings.downloads.publicSettings.preAllocateDisk')" />
<v-checkbox v-model="preferenceStore.preferences!.preallocate_all" hide-details
:label="t('settings.downloads.publicSettings.preAllocateDisk')" />
<v-checkbox v-model="preferenceStore.preferences!.incomplete_files_ext" hide-details :label="t('settings.downloads.publicSettings.appendQBExtension')" />
<v-checkbox v-model="preferenceStore.preferences!.incomplete_files_ext" hide-details
:label="t('settings.downloads.publicSettings.appendQBExtension')" />
</v-list-item>
<v-divider />
@ -204,7 +212,8 @@ const closeDeleteDialog = async () => {
</v-col>
<v-col cols="12">
<v-text-field v-model="preferenceStore.preferences!.save_path" hide-details :label="t('settings.downloads.saveManagement.defaultSavePath')" />
<v-text-field v-model="preferenceStore.preferences!.save_path" hide-details
:label="t('settings.downloads.saveManagement.defaultSavePath')" />
</v-col>
<v-col cols="12">
@ -265,7 +274,8 @@ const closeDeleteDialog = async () => {
<v-container>
<v-row>
<v-col cols="12">
<v-text-field v-model="monitoredFoldersEditedItem.monitoredFolderPath" :label="t('settings.downloads.monitoredFolders.monitoredFolderPath')" />
<v-text-field v-model="monitoredFoldersEditedItem.monitoredFolderPath"
:label="t('settings.downloads.monitoredFolders.monitoredFolderPath')" />
</v-col>
<v-col cols="12">
<v-select
@ -319,7 +329,8 @@ const closeDeleteDialog = async () => {
<v-divider />
<v-list-item>
<v-checkbox v-model="preferenceStore.preferences!.excluded_file_names_enabled" hide-details :label="t('settings.downloads.excludedFileNames.label')" />
<v-checkbox v-model="preferenceStore.preferences!.excluded_file_names_enabled" hide-details
:label="t('settings.downloads.excludedFileNames.label')" />
</v-list-item>
<v-list-item>
<v-textarea
@ -334,7 +345,8 @@ const closeDeleteDialog = async () => {
<v-divider />
<v-list-item>
<v-checkbox v-model="preferenceStore.preferences!.mail_notification_enabled" hide-details :label="t('settings.downloads.mailNotification.enabled')" />
<v-checkbox v-model="preferenceStore.preferences!.mail_notification_enabled" hide-details
:label="t('settings.downloads.mailNotification.enabled')" />
</v-list-item>
<v-list-item>
<v-text-field
@ -403,13 +415,15 @@ const closeDeleteDialog = async () => {
<v-list-item>
<v-row>
<v-col cols="12" md="6">
<v-checkbox v-model="preferenceStore.preferences!.autorun_on_torrent_added_enabled" hide-details :label="t('settings.downloads.runExternalProgram.onAddedEnabled')" />
<v-checkbox v-model="preferenceStore.preferences!.autorun_on_torrent_added_enabled" hide-details
:label="t('settings.downloads.runExternalProgram.onAddedEnabled')" />
<v-text-field
v-model="preferenceStore.preferences!.autorun_on_torrent_added_program"
:disabled="!preferenceStore.preferences!.autorun_on_torrent_added_enabled"
hide-details
:label="t('settings.downloads.runExternalProgram.onAddedLabel')" />
<v-checkbox v-model="preferenceStore.preferences!.autorun_enabled" hide-details :label="t('settings.downloads.runExternalProgram.onFinishedEnabled')" />
<v-checkbox v-model="preferenceStore.preferences!.autorun_enabled" hide-details
:label="t('settings.downloads.runExternalProgram.onFinishedEnabled')" />
<v-text-field
v-model="preferenceStore.preferences!.autorun_program"
:disabled="!preferenceStore.preferences!.autorun_enabled"

View file

@ -1,7 +1,6 @@
<script setup lang="ts">
import RssFeedDialog from '@/components/Dialogs/RssFeedDialog.vue'
import { useDialogStore } from '@/stores/dialog'
import { useRssStore } from '@/stores/rss'
import { useDialogStore, useRssStore } from '@/stores'
import { Feed } from '@/types/qbit/models'
import { useIntervalFn } from '@vueuse/core'
import { onBeforeMount, ref, watch } from 'vue'
@ -86,7 +85,8 @@ watch(
</v-btn>
</v-col>
<v-col cols="6" class="d-flex align-center justify-center">
<v-btn color="accent" :loading="loading" :disabled="rssStore.feeds.length === 0" :text="$t('settings.rss.feeds.refreshAll')" @click="refreshAllFeeds" />
<v-btn color="accent" :loading="loading" :disabled="rssStore.feeds.length === 0"
:text="$t('settings.rss.feeds.refreshAll')" @click="refreshAllFeeds" />
</v-col>
</v-row>
</template>

View file

@ -1,6 +1,6 @@
<script setup lang="ts">
import { usePreferenceStore } from '@/stores/preferences'
import { usePreferenceStore } from '@/stores'
const preferenceStore = usePreferenceStore()
</script>
@ -10,7 +10,8 @@ const preferenceStore = usePreferenceStore()
<v-list-subheader>{{ $t('settings.rss.general.reader.subheader') }}</v-list-subheader>
<v-list-item>
<v-checkbox v-model="preferenceStore.preferences!.rss_processing_enabled" hide-details :label="$t('settings.rss.general.reader.enableProcessing')" />
<v-checkbox v-model="preferenceStore.preferences!.rss_processing_enabled" hide-details
:label="$t('settings.rss.general.reader.enableProcessing')" />
<v-row>
<v-col cols="12" sm="6">
@ -22,7 +23,8 @@ const preferenceStore = usePreferenceStore()
:label="$t('settings.rss.general.reader.feedsRefreshInterval')" />
</v-col>
<v-col cols="12" sm="6">
<v-text-field v-model="preferenceStore.preferences!.rss_max_articles_per_feed" type="number" :label="$t('settings.rss.general.reader.maximumArticlesPerFeed')" />
<v-text-field v-model="preferenceStore.preferences!.rss_max_articles_per_feed" type="number"
:label="$t('settings.rss.general.reader.maximumArticlesPerFeed')" />
</v-col>
</v-row>
</v-list-item>
@ -31,7 +33,8 @@ const preferenceStore = usePreferenceStore()
<v-list-subheader>{{ $t('settings.rss.general.autoDownloader.subheader') }}</v-list-subheader>
<v-list-item>
<v-checkbox v-model="preferenceStore.preferences!.rss_auto_downloading_enabled" hide-details class="ma-0 pa-0" :label="$t('settings.rss.general.autoDownloader.enable')" />
<v-checkbox v-model="preferenceStore.preferences!.rss_auto_downloading_enabled" hide-details class="ma-0 pa-0"
:label="$t('settings.rss.general.autoDownloader.enable')" />
</v-list-item>
<v-divider />

View file

@ -1,7 +1,6 @@
<script setup lang="ts">
import RssRuleDialog from '@/components/Dialogs/RssRuleDialog.vue'
import { useDialogStore } from '@/stores/dialog'
import { useRssStore } from '@/stores/rss'
import { useDialogStore, useRssStore } from '@/stores'
import { FeedRule } from '@/types/qbit/models'
import { useIntervalFn } from '@vueuse/core'
import { onBeforeMount, ref, watch } from 'vue'

View file

@ -1,6 +1,6 @@
<script setup lang="ts">
import { SchedulerDays } from '@/constants/qbit/AppPreferences'
import { usePreferenceStore } from '@/stores/preferences'
import { usePreferenceStore } from '@/stores'
import { ref } from 'vue'
import { useI18n } from 'vue-i18n'
@ -30,10 +30,12 @@ const schedulerOptions = ref([
<v-row class="mx-1">
<v-col cols="12" md="6">
<v-text-field v-model="preferenceStore.preferences!.up_limit" hide-details suffix="kiB/s" :label="t('settings.speed.upload')" />
<v-text-field v-model="preferenceStore.preferences!.up_limit" hide-details suffix="kiB/s"
:label="t('settings.speed.upload')" />
</v-col>
<v-col cols="12" md="6">
<v-text-field v-model="preferenceStore.preferences!.dl_limit" hide-details suffix="kiB/s" :label="t('settings.speed.download')" />
<v-text-field v-model="preferenceStore.preferences!.dl_limit" hide-details suffix="kiB/s"
:label="t('settings.speed.download')" />
</v-col>
</v-row>
@ -49,10 +51,12 @@ const schedulerOptions = ref([
<v-row class="mx-1">
<v-col cols="12" md="6">
<v-text-field v-model="preferenceStore.preferences!.alt_up_limit" hide-details suffix="kiB/s" :label="t('settings.speed.upload')" />
<v-text-field v-model="preferenceStore.preferences!.alt_up_limit" hide-details suffix="kiB/s"
:label="t('settings.speed.upload')" />
</v-col>
<v-col cols="12" md="6">
<v-text-field v-model="preferenceStore.preferences!.alt_dl_limit" hide-details suffix="kiB/s" :label="t('settings.speed.download')" />
<v-text-field v-model="preferenceStore.preferences!.alt_dl_limit" hide-details suffix="kiB/s"
:label="t('settings.speed.download')" />
</v-col>
</v-row>
@ -66,7 +70,8 @@ const schedulerOptions = ref([
<v-divider class="mt-2" />
<v-list-item>
<v-checkbox v-model="preferenceStore.preferences!.scheduler_enabled" hide-details :label="t('settings.speed.scheduler.subheader')" />
<v-checkbox v-model="preferenceStore.preferences!.scheduler_enabled" hide-details
:label="t('settings.speed.scheduler.subheader')" />
</v-list-item>
<v-list-item>
@ -75,10 +80,12 @@ const schedulerOptions = ref([
<v-list-subheader>{{ t('settings.speed.scheduler.from') }}</v-list-subheader>
</v-col>
<v-col cols="4" md="2">
<v-text-field v-model="preferenceStore.preferences!.schedule_from_hour" :disabled="!preferenceStore.preferences!.scheduler_enabled" type="number" />
<v-text-field v-model="preferenceStore.preferences!.schedule_from_hour"
:disabled="!preferenceStore.preferences!.scheduler_enabled" type="number" />
</v-col>
<v-col cols="4" md="2">
<v-text-field v-model="preferenceStore.preferences!.schedule_from_min" :disabled="!preferenceStore.preferences!.scheduler_enabled" type="number" />
<v-text-field v-model="preferenceStore.preferences!.schedule_from_min"
:disabled="!preferenceStore.preferences!.scheduler_enabled" type="number" />
</v-col>
<v-spacer />
@ -87,10 +94,12 @@ const schedulerOptions = ref([
<v-list-subheader>{{ t('settings.speed.scheduler.to') }}</v-list-subheader>
</v-col>
<v-col cols="4" md="2">
<v-text-field v-model="preferenceStore.preferences!.schedule_to_hour" :disabled="!preferenceStore.preferences!.scheduler_enabled" type="number" />
<v-text-field v-model="preferenceStore.preferences!.schedule_to_hour"
:disabled="!preferenceStore.preferences!.scheduler_enabled" type="number" />
</v-col>
<v-col cols="4" md="2">
<v-text-field v-model="preferenceStore.preferences!.schedule_to_min" :disabled="!preferenceStore.preferences!.scheduler_enabled" type="number" />
<v-text-field v-model="preferenceStore.preferences!.schedule_to_min"
:disabled="!preferenceStore.preferences!.scheduler_enabled" type="number" />
</v-col>
</v-row>
</v-list-item>
@ -109,15 +118,18 @@ const schedulerOptions = ref([
<v-list-subheader>{{ t('settings.speed.subheader.settings') }}</v-list-subheader>
<v-list-item>
<v-checkbox v-model="preferenceStore.preferences!.limit_utp_rate" hide-details :label="t('settings.speed.settings.applyToUtp')" />
<v-checkbox v-model="preferenceStore.preferences!.limit_utp_rate" hide-details
:label="t('settings.speed.settings.applyToUtp')" />
</v-list-item>
<v-list-item>
<v-checkbox v-model="preferenceStore.preferences!.limit_tcp_overhead" hide-details :label="t('settings.speed.settings.applyToTransportOverhead')" />
<v-checkbox v-model="preferenceStore.preferences!.limit_tcp_overhead" hide-details
:label="t('settings.speed.settings.applyToTransportOverhead')" />
</v-list-item>
<v-list-item>
<v-checkbox v-model="preferenceStore.preferences!.limit_lan_peers" hide-details :label="t('settings.speed.settings.applyToPeersOnLan')" />
<v-checkbox v-model="preferenceStore.preferences!.limit_lan_peers" hide-details
:label="t('settings.speed.settings.applyToPeersOnLan')" />
</v-list-item>
</v-list>
</template>

View file

@ -1,8 +1,7 @@
<script setup lang="ts">
import CategoryFormDialog from '@/components/Dialogs/CategoryFormDialog.vue'
import TagFormDialog from '@/components/Dialogs/TagFormDialog.vue'
import { useDialogStore } from '@/stores/dialog'
import { useMaindataStore } from '@/stores/maindata'
import { useDialogStore, useMaindataStore } from '@/stores'
import { Category } from '@/types/qbit/models'
import { onBeforeMount, ref, watch } from 'vue'

View file

@ -1,14 +1,20 @@
<script setup lang="ts">
import { TorrentProperty } from '@/types/vuetorrent'
import { TorrentProperty } from '@/constants/vuetorrent'
defineProps<{ property: TorrentProperty }>()
defineEmits<{ update: [value: void] }>()
</script>
<template>
<tr class="table-row">
<td><v-icon icon="mdi-drag-vertical" class="dnd-handle" /></td>
<td><v-btn density="compact" :icon="property.active ? 'mdi-checkbox-marked' : 'mdi-checkbox-blank-outline'" variant="flat" @click="$emit('update')" /></td>
<td>{{ $t(`torrent.properties.${property.name}`) }}</td>
<td>
<v-icon icon="mdi-drag-vertical" class="dnd-handle" />
</td>
<td>
<v-btn density="compact" :icon="property.active ? 'mdi-checkbox-marked' : 'mdi-checkbox-blank-outline'"
variant="flat" @click="$emit('update')" />
</td>
<td>{{ $t(`torrent.properties.${ property.name }`) }}</td>
</tr>
</template>

View file

@ -1,8 +1,7 @@
<script setup lang="ts">
import { TitleOptions } from '@/constants/vuetorrent'
import { LOCALES } from '@/locales'
import { useAppStore } from '@/stores/app'
import { useVueTorrentStore } from '@/stores/vuetorrent'
import { useAppStore, useVueTorrentStore } from '@/stores'
import { computed, onBeforeMount, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { toast } from 'vue3-toastify'
@ -86,45 +85,57 @@ onBeforeMount(() => {
<v-list-item>
<v-row>
<v-col cols="12" sm="6">
<v-checkbox v-model="vueTorrentStore.showCurrentSpeed" hide-details density="compact" :label="t('settings.vuetorrent.general.showCurrentSpeed')" />
<v-checkbox v-model="vueTorrentStore.showCurrentSpeed" hide-details density="compact"
:label="t('settings.vuetorrent.general.showCurrentSpeed')" />
</v-col>
<v-col cols="12" sm="6">
<v-checkbox v-model="vueTorrentStore.showSpeedGraph" hide-details density="compact" :label="t('settings.vuetorrent.general.showSpeedGraph')" />
<v-checkbox v-model="vueTorrentStore.showSpeedGraph" hide-details density="compact"
:label="t('settings.vuetorrent.general.showSpeedGraph')" />
</v-col>
<v-col cols="12" sm="6">
<v-checkbox v-model="vueTorrentStore.showAlltimeStat" hide-details density="compact" :label="t('settings.vuetorrent.general.showAlltimeStat')" />
<v-checkbox v-model="vueTorrentStore.showAlltimeStat" hide-details density="compact"
:label="t('settings.vuetorrent.general.showAlltimeStat')" />
</v-col>
<v-col cols="12" sm="6">
<v-checkbox v-model="vueTorrentStore.showSessionStat" hide-details density="compact" :label="t('settings.vuetorrent.general.showSessionStat')" />
<v-checkbox v-model="vueTorrentStore.showSessionStat" hide-details density="compact"
:label="t('settings.vuetorrent.general.showSessionStat')" />
</v-col>
<v-col cols="12" sm="6">
<v-checkbox v-model="vueTorrentStore.showFreeSpace" hide-details density="compact" :label="t('settings.vuetorrent.general.showFreeSpace')" />
<v-checkbox v-model="vueTorrentStore.showFreeSpace" hide-details density="compact"
:label="t('settings.vuetorrent.general.showFreeSpace')" />
</v-col>
<v-col cols="12" sm="6">
<v-checkbox v-model="vueTorrentStore.showTrackerFilter" hide-details density="compact" :label="t('settings.vuetorrent.general.showTrackerFilter')" />
<v-checkbox v-model="vueTorrentStore.showTrackerFilter" hide-details density="compact"
:label="t('settings.vuetorrent.general.showTrackerFilter')" />
</v-col>
<v-col cols="12" sm="6">
<v-checkbox v-model="vueTorrentStore.isDrawerRight" hide-details density="compact" :label="t('settings.vuetorrent.general.isDrawerRight')" />
<v-checkbox v-model="vueTorrentStore.isDrawerRight" hide-details density="compact"
:label="t('settings.vuetorrent.general.isDrawerRight')" />
</v-col>
<v-col cols="12" sm="6">
<v-checkbox v-model="vueTorrentStore.isPaginationOnTop" hide-details density="compact" :label="t('settings.vuetorrent.general.isPaginationOnTop')" />
<v-checkbox v-model="vueTorrentStore.isPaginationOnTop" hide-details density="compact"
:label="t('settings.vuetorrent.general.isPaginationOnTop')" />
</v-col>
<v-col cols="12" sm="6">
<v-checkbox v-model="vueTorrentStore.openSideBarOnStart" hide-details density="compact" :label="t('settings.vuetorrent.general.openSideBarOnStart')" />
<v-checkbox v-model="vueTorrentStore.openSideBarOnStart" hide-details density="compact"
:label="t('settings.vuetorrent.general.openSideBarOnStart')" />
</v-col>
<v-col cols="12" sm="6">
<v-checkbox v-model="vueTorrentStore.isShutdownButtonVisible" hide-details density="compact" :label="t('settings.vuetorrent.general.isShutdownButtonVisible')" />
<v-checkbox v-model="vueTorrentStore.isShutdownButtonVisible" hide-details density="compact"
:label="t('settings.vuetorrent.general.isShutdownButtonVisible')" />
</v-col>
<v-col cols="12" sm="6">
<v-checkbox v-model="vueTorrentStore.useBinarySize" hide-details density="compact" :label="t('settings.vuetorrent.general.useBinarySize')" />
<v-checkbox v-model="vueTorrentStore.useBinarySize" hide-details density="compact"
:label="t('settings.vuetorrent.general.useBinarySize')" />
</v-col>
<v-col cols="12" sm="6">
<v-checkbox v-model="vueTorrentStore.useBitSpeed" hide-details density="compact" :label="t('settings.vuetorrent.general.useBitSpeed')" />
<v-checkbox v-model="vueTorrentStore.useBitSpeed" hide-details density="compact"
:label="t('settings.vuetorrent.general.useBitSpeed')" />
</v-col>
</v-row>
</v-list-item>
@ -132,18 +143,22 @@ onBeforeMount(() => {
<v-list-item>
<v-row>
<v-col cols="12" md="6">
<v-text-field v-model="vueTorrentStore.refreshInterval" type="number" hide-details suffix="ms" :label="t('settings.vuetorrent.general.refreshInterval')" />
<v-text-field v-model="vueTorrentStore.refreshInterval" type="number" hide-details suffix="ms"
:label="t('settings.vuetorrent.general.refreshInterval')" />
</v-col>
<v-col cols="12" md="6">
<v-text-field v-model="vueTorrentStore.fileContentInterval" type="number" hide-details suffix="ms" :label="t('settings.vuetorrent.general.fileContentInterval')" />
<v-text-field v-model="vueTorrentStore.fileContentInterval" type="number" hide-details suffix="ms"
:label="t('settings.vuetorrent.general.fileContentInterval')" />
</v-col>
</v-row>
<v-row>
<v-col cols="12" md="6">
<v-text-field v-model="vueTorrentStore.canvasRenderThreshold" type="number" :label="t('settings.vuetorrent.general.canvasRenderThreshold')" />
<v-text-field v-model="vueTorrentStore.canvasRenderThreshold" type="number"
:label="t('settings.vuetorrent.general.canvasRenderThreshold')" />
</v-col>
<v-col cols="12" md="6">
<v-text-field v-model="vueTorrentStore.canvasRefreshThreshold" type="number" :label="t('settings.vuetorrent.general.canvasRefreshThreshold')" />
<v-text-field v-model="vueTorrentStore.canvasRefreshThreshold" type="number"
:label="t('settings.vuetorrent.general.canvasRefreshThreshold')" />
</v-col>
</v-row>
</v-list-item>
@ -151,16 +166,20 @@ onBeforeMount(() => {
<v-list-item>
<v-row>
<v-col cols="12" sm="6" md="3">
<v-select v-model="vueTorrentStore.language" flat hide-details :items="LOCALES" :label="t('settings.vuetorrent.general.language')" />
<v-select v-model="vueTorrentStore.language" flat hide-details :items="LOCALES"
:label="t('settings.vuetorrent.general.language')" />
</v-col>
<v-col cols="12" sm="6" md="3">
<v-select v-model="vueTorrentStore.paginationSize" flat hide-details :items="paginationSizes" :label="t('settings.vuetorrent.general.paginationSize.label')" />
<v-select v-model="vueTorrentStore.paginationSize" flat hide-details :items="paginationSizes"
:label="t('settings.vuetorrent.general.paginationSize.label')" />
</v-col>
<v-col cols="12" sm="6" md="3">
<v-select v-model="vueTorrentStore.uiTitleType" flat hide-details :items="titleOptionsList" :label="t('settings.vuetorrent.general.vueTorrentTitle')" />
<v-select v-model="vueTorrentStore.uiTitleType" flat hide-details :items="titleOptionsList"
:label="t('settings.vuetorrent.general.vueTorrentTitle')" />
</v-col>
<v-col cols="12" sm="6" md="3">
<v-text-field :disabled="vueTorrentStore.uiTitleType !== TitleOptions.CUSTOM" v-model="vueTorrentStore.uiTitleCustom" :label="t('settings.vuetorrent.general.customTitle')" />
<v-text-field :disabled="vueTorrentStore.uiTitleType !== TitleOptions.CUSTOM"
v-model="vueTorrentStore.uiTitleCustom" :label="t('settings.vuetorrent.general.customTitle')" />
</v-col>
</v-row>
</v-list-item>
@ -171,15 +190,21 @@ onBeforeMount(() => {
<h3>
{{ t('settings.vuetorrent.general.currentVersion') }}
<span v-if="!vueTorrentVersion">undefined</span>
<a v-else-if="vueTorrentVersion === 'DEV'" target="_blank" href="https://github.com/WDaan/VueTorrent/">{{ vueTorrentVersion }}</a>
<a v-else target="_blank" :href="`https://github.com/WDaan/VueTorrent/releases/tag/v${vueTorrentVersion}`">{{ vueTorrentVersion }}</a>
<a v-else-if="vueTorrentVersion === 'DEV'" target="_blank"
href="https://github.com/WDaan/VueTorrent/">{{ vueTorrentVersion }}</a>
<a v-else target="_blank" :href="`https://github.com/WDaan/VueTorrent/releases/tag/v${vueTorrentVersion}`">{{
vueTorrentVersion
}}</a>
</h3>
</v-col>
<v-col cols="12" md="3" class="d-flex align-center justify-center">
<h3>
{{ t('settings.vuetorrent.general.qbittorrentVersion') }}
<a target="_blank" :href="`https://github.com/qbittorrent/qBittorrent/releases/tag/release-${appStore.version}`">{{ appStore.version }}</a>
<a target="_blank"
:href="`https://github.com/qbittorrent/qBittorrent/releases/tag/release-${appStore.version}`">{{
appStore.version
}}</a>
</h3>
</v-col>
@ -188,7 +213,8 @@ onBeforeMount(() => {
</v-col>
<v-col cols="12" md="3">
<v-text-field v-model="vueTorrentStore.dateFormat" placeholder="DD/MM/YYYY, HH:mm:ss" hint="using Dayjs" :label="t('settings.vuetorrent.general.dateFormat')" />
<v-text-field v-model="vueTorrentStore.dateFormat" placeholder="DD/MM/YYYY, HH:mm:ss" hint="using Dayjs"
:label="t('settings.vuetorrent.general.dateFormat')" />
</v-col>
</v-row>
</v-list-item>
@ -196,7 +222,10 @@ onBeforeMount(() => {
<v-list-item>
<v-row>
<v-col cols="12" sm="6" class="d-flex align-center justify-center">
<v-btn color="primary" @click="registerMagnetHandler">{{ t('settings.vuetorrent.general.registerMagnet') }}</v-btn>
<v-btn color="primary" @click="registerMagnetHandler">{{
t('settings.vuetorrent.general.registerMagnet')
}}
</v-btn>
</v-col>
<v-col cols="12" sm="6" class="d-flex align-center justify-center">

View file

@ -1,7 +1,7 @@
<script setup lang="ts">
import DashboardItem from '@/components/Settings/VueTorrent/DashboardItem.vue'
import { useVueTorrentStore } from '@/stores/vuetorrent'
import { TorrentProperty } from '@/types/vuetorrent'
import { TorrentProperty } from '@/constants/vuetorrent'
import { useVueTorrentStore } from '@/stores'
import { computed } from 'vue'
import Draggable from 'vuedraggable'

View file

@ -1,6 +1,6 @@
<script setup lang="ts">
import PasswordField from '@/components/Core/PasswordField.vue'
import { usePreferenceStore } from '@/stores/preferences'
import { usePreferenceStore } from '@/stores'
import { ref, watch } from 'vue'
import { useI18n } from 'vue-i18n'
@ -36,14 +36,17 @@ watch(webUiPassword, newValue => {
<v-list-item>
<v-row>
<v-col cols="9">
<v-text-field v-model="preferenceStore.preferences!.web_ui_address" hide-details :label="t('settings.webUI.interface.ipAddress')" />
<v-text-field v-model="preferenceStore.preferences!.web_ui_address" hide-details
:label="t('settings.webUI.interface.ipAddress')" />
</v-col>
<v-col cols="3">
<v-text-field v-model="preferenceStore.preferences!.web_ui_port" hide-details :label="t('settings.webUI.interface.port')" />
<v-text-field v-model="preferenceStore.preferences!.web_ui_port" hide-details
:label="t('settings.webUI.interface.port')" />
</v-col>
<v-col cols="12" class="pt-0">
<v-checkbox v-model="preferenceStore.preferences!.web_ui_upnp" hide-details :label="t('settings.webUI.interface.useUPnP')" />
<v-checkbox v-model="preferenceStore.preferences!.web_ui_upnp" hide-details
:label="t('settings.webUI.interface.useUPnP')" />
</v-col>
</v-row>
</v-list-item>
@ -69,7 +72,8 @@ watch(webUiPassword, newValue => {
<v-list-item>
<v-row>
<v-col cols="12" sm="6">
<v-text-field v-model="preferenceStore.preferences!.web_ui_username" hide-details :label="t('settings.webUI.authentication.username')" />
<v-text-field v-model="preferenceStore.preferences!.web_ui_username" hide-details
:label="t('settings.webUI.authentication.username')" />
</v-col>
<v-col cols="12" sm="6">
<PasswordField
@ -82,10 +86,12 @@ watch(webUiPassword, newValue => {
</v-col>
<v-col cols="12" class="py-0">
<v-checkbox v-model="preferenceStore.preferences!.bypass_local_auth" hide-details :label="t('settings.webUI.authentication.bypassLocalhost')" />
<v-checkbox v-model="preferenceStore.preferences!.bypass_local_auth" hide-details
:label="t('settings.webUI.authentication.bypassLocalhost')" />
</v-col>
<v-col cols="12" class="pt-0">
<v-checkbox v-model="preferenceStore.preferences!.bypass_auth_subnet_whitelist_enabled" hide-details :label="t('settings.webUI.authentication.bypassWhitelist')" />
<v-checkbox v-model="preferenceStore.preferences!.bypass_auth_subnet_whitelist_enabled" hide-details
:label="t('settings.webUI.authentication.bypassWhitelist')" />
<v-textarea
v-model="preferenceStore.preferences!.bypass_auth_subnet_whitelist"
:disabled="!preferenceStore.preferences!.bypass_auth_subnet_whitelist_enabled"
@ -99,7 +105,8 @@ watch(webUiPassword, newValue => {
<v-list-item>
<v-row>
<v-col cols="12" sm="4">
<v-text-field v-model="preferenceStore.preferences!.web_ui_max_auth_fail_count" type="number" hide-details :label="t('settings.webUI.authentication.maxAttempts')" />
<v-text-field v-model="preferenceStore.preferences!.web_ui_max_auth_fail_count" type="number" hide-details
:label="t('settings.webUI.authentication.maxAttempts')" />
</v-col>
<v-col cols="12" sm="4">
@ -127,7 +134,8 @@ watch(webUiPassword, newValue => {
<v-list-item>
<v-row>
<v-col cols="12" class="pb-0">
<v-checkbox v-model="preferenceStore.preferences!.use_https" hide-details :label="t('settings.webUI.https.subheader')" />
<v-checkbox v-model="preferenceStore.preferences!.use_https" hide-details
:label="t('settings.webUI.https.subheader')" />
</v-col>
<v-col cols="12" class="pt-0">
<v-text-field
@ -147,7 +155,8 @@ watch(webUiPassword, newValue => {
</v-list-item>
<v-list-item>
<a href="https://httpd.apache.org/docs/current/ssl/ssl_faq.html#aboutcerts" target="_blank">{{ t('settings.webUI.https.tip') }}</a>
<a href="https://httpd.apache.org/docs/current/ssl/ssl_faq.html#aboutcerts"
target="_blank">{{ t('settings.webUI.https.tip') }}</a>
</v-list-item>
<v-divider />
@ -163,7 +172,8 @@ watch(webUiPassword, newValue => {
:label="t('settings.webUI.security.clickjacking')" />
</v-col>
<v-col cols="12" class="py-0">
<v-checkbox v-model="preferenceStore.preferences!.web_ui_csrf_protection_enabled" hide-details density="compact" :label="t('settings.webUI.security.csrf')" />
<v-checkbox v-model="preferenceStore.preferences!.web_ui_csrf_protection_enabled" hide-details
density="compact" :label="t('settings.webUI.security.csrf')" />
</v-col>
<v-col cols="12" class="py-0">
<v-checkbox
@ -195,7 +205,8 @@ watch(webUiPassword, newValue => {
<v-divider />
<v-list-item>
<v-checkbox v-model="preferenceStore.preferences!.web_ui_use_custom_http_headers_enabled" hide-details :label="t('settings.webUI.customHeaders')" />
<v-checkbox v-model="preferenceStore.preferences!.web_ui_use_custom_http_headers_enabled" hide-details
:label="t('settings.webUI.customHeaders')" />
</v-list-item>
<v-list-item>
<v-textarea
@ -211,7 +222,8 @@ watch(webUiPassword, newValue => {
<v-divider />
<v-list-item>
<v-checkbox v-model="preferenceStore.preferences!.web_ui_reverse_proxy_enabled" hide-details :label="t('settings.webUI.reverseProxySupport')" />
<v-checkbox v-model="preferenceStore.preferences!.web_ui_reverse_proxy_enabled" hide-details
:label="t('settings.webUI.reverseProxySupport')" />
</v-list-item>
<v-list-item>
<v-text-field
@ -226,15 +238,19 @@ watch(webUiPassword, newValue => {
<v-divider />
<v-list-item>
<v-checkbox v-model="preferenceStore.preferences!.dyndns_enabled" hide-details :label="t('settings.webUI.dynDns.subheader')" />
<v-checkbox v-model="preferenceStore.preferences!.dyndns_enabled" hide-details
:label="t('settings.webUI.dynDns.subheader')" />
</v-list-item>
<v-list-item>
<v-row>
<v-col cols="8">
<v-select v-model="dynDnsProvider" :disabled="!preferenceStore.preferences!.dyndns_enabled" density="compact" hide-details :items="dynDnsProviderOptions" />
<v-select v-model="dynDnsProvider" :disabled="!preferenceStore.preferences!.dyndns_enabled" density="compact"
hide-details :items="dynDnsProviderOptions" />
</v-col>
<v-col cols="4">
<v-btn :disabled="!preferenceStore.preferences!.dyndns_enabled" @click="registerDynDNS">{{ $t('settings.webUI.dynDns.registerBtn') }}</v-btn>
<v-btn :disabled="!preferenceStore.preferences!.dyndns_enabled" @click="registerDynDNS">
{{ $t('settings.webUI.dynDns.registerBtn') }}
</v-btn>
</v-col>
</v-row>
</v-list-item>

View file

@ -3,9 +3,7 @@ import MoveTorrentFileDialog from '@/components/Dialogs/MoveTorrentFileDialog.vu
import RootNode from '@/components/TorrentDetail/Content/RootNode.vue'
import { useTreeBuilder } from '@/composables'
import { FilePriority } from '@/constants/qbit'
import { useDialogStore } from '@/stores/dialog'
import { useMaindataStore } from '@/stores/maindata'
import { useVueTorrentStore } from '@/stores/vuetorrent'
import { useDialogStore, useMaindataStore, useVueTorrentStore } from '@/stores'
import { TorrentFile } from '@/types/qbit/models'
import { Torrent, TreeNode } from '@/types/vuetorrent'
import { computed, nextTick, onBeforeUnmount, onMounted, reactive, ref, watch } from 'vue'
@ -35,15 +33,15 @@ const fileSelection = computed({
const oldValue = cachedFiles.value.filter(f => f.priority !== FilePriority.DO_NOT_DOWNLOAD).map(f => f.index)
const filesToExclude = oldValue
.filter(index => !newValue.includes(index))
.map(index => cachedFiles.value.find(f => f.index === index))
.filter(f => f && f.priority !== FilePriority.DO_NOT_DOWNLOAD)
.map(f => (f as TorrentFile).index)
.filter(index => !newValue.includes(index))
.map(index => cachedFiles.value.find(f => f.index === index))
.filter(f => f && f.priority !== FilePriority.DO_NOT_DOWNLOAD)
.map(f => (f as TorrentFile).index)
const filesToInclude = newValue
.filter(index => !oldValue.includes(index))
.map(index => cachedFiles.value.find(f => f.index === index))
.filter(f => f && f.priority === FilePriority.DO_NOT_DOWNLOAD)
.map(f => (f as TorrentFile).index)
.filter(index => !oldValue.includes(index))
.map(index => cachedFiles.value.find(f => f.index === index))
.filter(f => f && f.priority === FilePriority.DO_NOT_DOWNLOAD)
.map(f => (f as TorrentFile).index)
if (filesToExclude.length) {
await maindataStore.setTorrentFilePriority(props.torrent.hash, filesToExclude, FilePriority.DO_NOT_DOWNLOAD)
@ -106,7 +104,8 @@ onMounted(() => {
<template>
<v-card :loading="loading" flat>
<RootNode v-model:opened="openedItems" v-model:selected="fileSelection" :root="tree" @renameFolder="renameNode" @renameFile="renameNode" />
<RootNode v-model:opened="openedItems" v-model:selected="fileSelection" :root="tree" @renameFolder="renameNode"
@renameFile="renameNode" />
<!--
TODO: add treeview after merge
https://github.com/vuetifyjs/vuetify/issues/13518

View file

@ -1,10 +1,10 @@
<script setup lang="ts">
import { FilePriority } from '@/constants/qbit'
import { getFileIcon } from '@/constants/vuetorrent'
import { useVueTorrentStore } from '@/stores/vuetorrent'
import { formatData, formatPercent } from '@/helpers'
import { useVueTorrentStore } from '@/stores'
import { TreeFile } from '@/types/vuetorrent'
import { useI18n } from 'vue-i18n'
import { formatData, formatPercent } from '@/helpers'
defineProps<{
node: TreeFile
@ -29,7 +29,7 @@ function getNodePriority(node: TreeFile) {
</script>
<template>
<v-list-item :title="node.name" :value="node.index" :prepend-icon="getFileIcon(node)">
<v-list-item :title="node.name" :value="node.index" :prepend-icon="getFileIcon(node.name)">
<template v-slot:append>
<span class="mr-2">[ {{ formatData(node.size, vuetorrentStore.useBinarySize) }} ]</span>
<span class="mr-2">{{ formatPercent(node.progress) }}</span>

View file

@ -1,15 +1,15 @@
<script setup lang="ts">
import InfoBase from '@/components/TorrentDetail/InfoBase.vue'
import { formatData, formatSpeed } from '@/helpers'
import dayjs from '@/plugins/dayjs'
import { useMaindataStore } from '@/stores/maindata'
import { useVueTorrentStore } from '@/stores/vuetorrent'
import { useMaindataStore, useTorrentStore, useVueTorrentStore } from '@/stores'
import { Torrent } from '@/types/vuetorrent'
import { computed } from 'vue'
import { formatData, formatSpeed } from '@/helpers'
const props = defineProps<{ torrent: Torrent; isActive: boolean }>()
const maindataStore = useMaindataStore()
const torrentStore = useTorrentStore()
const vuetorrentStore = useVueTorrentStore()
const auto_tmm = computed({
@ -26,9 +26,9 @@ const forced = computed({
get: () => props.torrent.forced,
set: value => {
if (value) {
maindataStore.forceResumeTorrents([props.torrent.hash])
torrentStore.forceResumeTorrents([props.torrent.hash])
} else {
maindataStore.resumeTorrents([props.torrent.hash])
torrentStore.resumeTorrents([props.torrent.hash])
}
}
})
@ -113,9 +113,11 @@ const longTextPpts = [
<v-expansion-panel-text>
<v-row>
<InfoBase v-for="ppt in datetimePpts">
<template v-slot:title>{{ $t(`torrent.properties.${ppt.title}`) }}</template>
<template v-if="torrent[ppt.text] > 0" v-slot:text>{{ dayjs(torrent[ppt.text] * 1000).format(vuetorrentStore.dateFormat ?? 'DD/MM/YYYY, HH:mm:ss') }} </template>
<template v-else v-slot:text> {{ $t('common.NA') }} </template>
<template v-slot:title>{{ $t(`torrent.properties.${ ppt.title }`) }}</template>
<template v-if="torrent[ppt.text] > 0" v-slot:text>
{{ dayjs(torrent[ppt.text] * 1000).format(vuetorrentStore.dateFormat ?? 'DD/MM/YYYY, HH:mm:ss') }}
</template>
<template v-else v-slot:text> {{ $t('common.NA') }}</template>
</InfoBase>
</v-row>
</v-expansion-panel-text>
@ -125,7 +127,7 @@ const longTextPpts = [
<v-expansion-panel-text>
<v-row>
<InfoBase v-for="ppt in durationPpts">
<template v-slot:title>{{ $t(`torrent.properties.${ppt.title}`) }}</template>
<template v-slot:title>{{ $t(`torrent.properties.${ ppt.title }`) }}</template>
<template v-slot:text>{{ dayjs.duration(torrent[ppt.text], 's').humanize() }}</template>
</InfoBase>
</v-row>
@ -137,31 +139,36 @@ const longTextPpts = [
<v-row>
<InfoBase>
<template v-slot:title>
<v-checkbox v-model="auto_tmm" hide-details density="compact" :label="$t('torrent.properties.auto_tmm')" />
<v-checkbox v-model="auto_tmm" hide-details density="compact"
:label="$t('torrent.properties.auto_tmm')" />
</template>
</InfoBase>
<InfoBase>
<template v-slot:title>
<v-checkbox v-model="f_l_piece_prio" hide-details density="compact" :label="$t('torrent.properties.f_l_piece_prio')" />
<v-checkbox v-model="f_l_piece_prio" hide-details density="compact"
:label="$t('torrent.properties.f_l_piece_prio')" />
</template>
</InfoBase>
<InfoBase>
<template v-slot:title>
<v-checkbox v-model="forced" hide-details density="compact" :label="$t('torrent.properties.forced')" />
<v-checkbox v-model="forced" hide-details density="compact"
:label="$t('torrent.properties.forced')" />
</template>
</InfoBase>
<InfoBase>
<template v-slot:title>
<v-checkbox v-model="seq_dl" hide-details density="compact" :label="$t('torrent.properties.seq_dl')" />
<v-checkbox v-model="seq_dl" hide-details density="compact"
:label="$t('torrent.properties.seq_dl')" />
</template>
</InfoBase>
<InfoBase>
<template v-slot:title>
<v-checkbox v-model="super_seeding" hide-details density="compact" :label="$t('torrent.properties.super_seeding')" />
<v-checkbox v-model="super_seeding" hide-details density="compact"
:label="$t('torrent.properties.super_seeding')" />
</template>
</InfoBase>
</v-row>
@ -172,7 +179,7 @@ const longTextPpts = [
<v-expansion-panel-text>
<v-row>
<InfoBase v-for="ppt in dataPpts">
<template v-slot:title>{{ $t(`torrent.properties.${ppt.title}`) }}</template>
<template v-slot:title>{{ $t(`torrent.properties.${ ppt.title }`) }}</template>
<template v-slot:text>{{ formatData(torrent[ppt.text], vuetorrentStore.useBinarySize) }}</template>
</InfoBase>
</v-row>
@ -183,7 +190,7 @@ const longTextPpts = [
<v-expansion-panel-text>
<v-row>
<InfoBase v-for="ppt in speedPpts">
<template v-slot:title>{{ $t(`torrent.properties.${ppt.title}`) }}</template>
<template v-slot:title>{{ $t(`torrent.properties.${ ppt.title }`) }}</template>
<template v-slot:text>{{ formatSpeed(torrent[ppt.text], vuetorrentStore.useBitSpeed) }}</template>
</InfoBase>
</v-row>
@ -194,7 +201,7 @@ const longTextPpts = [
<v-expansion-panel-text>
<v-row>
<InfoBase v-for="ppt in textPpts">
<template v-slot:title>{{ $t(`torrent.properties.${ppt.title}`) }}</template>
<template v-slot:title>{{ $t(`torrent.properties.${ ppt.title }`) }}</template>
<template v-slot:text>{{ torrent[ppt.text] }}</template>
</InfoBase>
</v-row>

View file

@ -1,11 +1,18 @@
<script setup lang="ts">
import MoveTorrentDialog from '@/components/Dialogs/MoveTorrentDialog.vue'
import MoveTorrentFileDialog from '@/components/Dialogs/MoveTorrentFileDialog.vue'
import { PieceState, FilePriority, TorrentState } from '@/constants/qbit'
import { formatData, formatDataUnit, formatDataValue, formatPercent, formatSpeed, getDomainBody, splitByUrl, stringContainsUrl } from '@/helpers'
import { useDialogStore } from '@/stores/dialog'
import { useMaindataStore } from '@/stores/maindata'
import { useVueTorrentStore } from '@/stores/vuetorrent'
import { FilePriority, PieceState, TorrentState } from '@/constants/qbit'
import {
formatData,
formatDataUnit,
formatDataValue,
formatPercent,
formatSpeed,
getDomainBody,
splitByUrl,
stringContainsUrl
} from '@/helpers'
import { useDialogStore, useMaindataStore, useTorrentStore, useVueTorrentStore } from '@/stores'
import { TorrentFile } from '@/types/qbit/models'
import { Torrent } from '@/types/vuetorrent'
import { computed, ref, watch } from 'vue'
@ -18,6 +25,7 @@ const { t } = useI18n()
const theme = useTheme()
const dialogStore = useDialogStore()
const maindataStore = useMaindataStore()
const torrentStore = useTorrentStore()
const vuetorrentStore = useVueTorrentStore()
const canvas = ref<HTMLCanvasElement>()
@ -33,14 +41,14 @@ const torrentPieceOwned = ref(0)
const torrentPieceCount = ref(0)
const uploadSpeedAvg = ref(0)
const torrentStateColor = computed(() => `torrent-${props.torrent.state}`)
const pieceSize = computed(() => `${parseInt(formatDataValue(torrentPieceSize.value, true))} ${formatDataUnit(torrentPieceSize.value, true)}`)
const torrentStateColor = computed(() => `torrent-${ props.torrent.state }`)
const pieceSize = computed(() => `${ parseInt(formatDataValue(torrentPieceSize.value, true)) } ${ formatDataUnit(torrentPieceSize.value, true) }`)
const isFetchingMetadata = computed(() => props.torrent.state === TorrentState.META_DL)
const shouldRenderPieceState = computed(() => !isFetchingMetadata.value && torrentPieceCount.value > 0 && torrentPieceCount.value < vuetorrentStore.canvasRenderThreshold)
const shouldRefreshPieceState = computed(() => shouldRenderPieceState.value && torrentPieceCount.value < vuetorrentStore.canvasRefreshThreshold)
async function getTorrentProperties() {
const ppts = await maindataStore.getTorrentProperties(props.torrent.hash)
const ppts = await torrentStore.getTorrentProperties(props.torrent.hash)
comment.value = ppts.comment
downloadSpeedAvg.value = ppts.dl_speed_avg
torrentPieceCount.value = ppts.pieces_num
@ -122,7 +130,11 @@ function openMoveTorrentDialog() {
}
function openMoveTorrentFileDialog() {
dialogStore.createDialog(MoveTorrentFileDialog, { hash: props.torrent.hash, isFolder: false, oldName: torrentFileName.value })
dialogStore.createDialog(MoveTorrentFileDialog, {
hash: props.torrent.hash,
isFolder: false,
oldName: torrentFileName.value
})
}
watch(
@ -157,7 +169,8 @@ watch(
<v-col cols="12" md="6">
<v-row>
<v-col cols="4" md="4">
<v-progress-circular :color="torrentStateColor" :indeterminate="isFetchingMetadata" :size="100" :model-value="torrent?.progress * 100 ?? 0" :width="15">
<v-progress-circular :color="torrentStateColor" :indeterminate="isFetchingMetadata" :size="100"
:model-value="torrent?.progress * 100 ?? 0" :width="15">
<template v-slot>
<span v-if="isFetchingMetadata">{{ $t('torrentDetail.overview.fetchingMetadata') }}</span>
<v-icon v-else-if="torrent.progress === 1" icon="mdi-check" size="x-large" />
@ -212,7 +225,8 @@ watch(
<div>{{ $t('torrentDetail.overview.fileCount') }}:</div>
<div>{{ selectedFileCount }} / {{ torrentFileCount }}</div>
<div v-if="selectedFileCount === 1">{{ torrentFileName }}</div>
<v-btn v-if="selectedFileCount === 1" icon="mdi-pencil" color="accent" size="x-small" @click="openMoveTorrentFileDialog" />
<v-btn v-if="selectedFileCount === 1" icon="mdi-pencil" color="accent" size="x-small"
@click="openMoveTorrentFileDialog" />
</v-col>
</v-row>
</v-col>
@ -220,7 +234,7 @@ watch(
<v-row>
<v-col cols="6">
<div>{{ $t('torrent.properties.state') }}:</div>
<v-chip variant="flat" :color="torrentStateColor">{{ $t(`torrent.state.${torrent.state}`) }}</v-chip>
<v-chip variant="flat" :color="torrentStateColor">{{ $t(`torrent.state.${ torrent.state }`) }}</v-chip>
</v-col>
<v-col cols="6">
<div>{{ $t('torrent.properties.category') }}:</div>

View file

@ -1,7 +1,6 @@
<script lang="ts" setup>
import { codeToFlag, formatData, formatPercent, formatSpeed, isWindows } from '@/helpers'
import { useMaindataStore } from '@/stores/maindata'
import { useVueTorrentStore } from '@/stores/vuetorrent'
import { useMaindataStore, useVueTorrentStore } from '@/stores'
import { Peer } from '@/types/qbit/models'
import { Torrent } from '@/types/vuetorrent'
import { onBeforeMount, onUnmounted, ref, watch } from 'vue'
@ -73,7 +72,8 @@ watch(() => props.isActive, setupTimer)
<div>
<v-list-item-title class="overflow-visible text-select">
<span v-if="peer.country_code">
<img v-if="isWindows" :alt="codeToFlag(peer.country_code).char" :src="codeToFlag(peer.country_code).url" :title="peer.country" style="max-width: 32px" />
<img v-if="isWindows" :alt="codeToFlag(peer.country_code).char" :src="codeToFlag(peer.country_code).url"
:title="peer.country" style="max-width: 32px" />
<span v-else :title="peer.country">{{ codeToFlag(peer.country_code).char }}</span>
</span>
<span>{{ peer.ip }}</span>

View file

@ -1,28 +1,29 @@
<script setup lang="ts">
import { useMaindataStore } from '@/stores/maindata'
import { useMaindataStore, useTorrentStore } from '@/stores'
import { Torrent } from '@/types/vuetorrent'
import { computed, onBeforeMount } from 'vue'
const props = defineProps<{ torrent: Torrent; isActive: boolean }>()
const maindataStore = useMaindataStore()
const torrentStore = useTorrentStore()
const activeCategory = computed(() => maindataStore.categories.map(cat => cat.name).indexOf(props.torrent.category))
const activeTags = computed(() => maindataStore.tags.filter(tag => props.torrent.tags?.includes(tag)))
async function setCategory(category: string) {
if (props.torrent.category === category) {
await maindataStore.setTorrentCategory([props.torrent.hash], '')
await torrentStore.setTorrentCategory([props.torrent.hash], '')
} else {
await maindataStore.setTorrentCategory([props.torrent.hash], category)
await torrentStore.setTorrentCategory([props.torrent.hash], category)
}
}
async function toggleTag(tag: string) {
if (props.torrent.tags?.includes(tag)) {
await maindataStore.removeTorrentTags([props.torrent.hash], [tag])
await torrentStore.removeTorrentTags([props.torrent.hash], [tag])
} else {
await maindataStore.addTorrentTags([props.torrent.hash], [tag])
await torrentStore.addTorrentTags([props.torrent.hash], [tag])
}
}

View file

@ -1,6 +1,6 @@
<script setup lang="ts">
import { TrackerStatus } from '@/constants/qbit'
import { useMaindataStore } from '@/stores/maindata'
import { useMaindataStore } from '@/stores'
import { Tracker } from '@/types/qbit/models'
import { Torrent } from '@/types/vuetorrent'
import { nextTick, onBeforeMount, onUnmounted, reactive, ref, watch } from 'vue'
@ -162,7 +162,8 @@ watch(() => props.isActive, setupTimer)
<v-card-text>
<v-form v-model="editTrackerDialog.isFormValid" @submit.prevent>
<v-text-field :model-value="editTrackerDialog.oldUrl" disabled :label="$t('torrentDetail.trackers.editTracker.oldUrl')" />
<v-text-field :model-value="editTrackerDialog.oldUrl" disabled
:label="$t('torrentDetail.trackers.editTracker.oldUrl')" />
<v-text-field
v-model="editTrackerDialog.newUrl"
id="input"
@ -175,7 +176,9 @@ watch(() => props.isActive, setupTimer)
<v-card-actions>
<v-spacer />
<v-btn color="error" :disabled="!editTrackerDialog.isFormValid" @click="editTrackerDialog.isVisible = false">{{ t('common.cancel') }}</v-btn>
<v-btn color="error" :disabled="!editTrackerDialog.isFormValid"
@click="editTrackerDialog.isVisible = false">{{ t('common.cancel') }}
</v-btn>
<v-btn color="accent" @click="editTracker">{{ t('common.ok') }}</v-btn>
</v-card-actions>
</v-card>
@ -218,7 +221,8 @@ watch(() => props.isActive, setupTimer)
</v-card>
</v-dialog>
<v-btn variant="flat" :disabled="torrentTrackers.length === 3" :text="t('torrentDetail.trackers.reannounce')" color="primary" @click="reannounceTrackers" />
<v-btn variant="flat" :disabled="torrentTrackers.length === 3" :text="t('torrentDetail.trackers.reannounce')"
color="primary" @click="reannounceTrackers" />
</div>
</v-list-item>
</v-list>
@ -233,15 +237,19 @@ watch(() => props.isActive, setupTimer)
.tracker-disabled {
color: darken(lightgrey, 5%);
}
.tracker-not_yet_contacted {
color: orange;
}
.tracker-working {
color: lightgreen;
}
.tracker-not_working {
color: lightcoral;
}
.tracker-updating {
color: lightblue;
}
@ -251,15 +259,19 @@ watch(() => props.isActive, setupTimer)
.tracker-disabled {
color: grey;
}
.tracker-not_yet_contacted {
color: orange;
}
.tracker-working {
color: green;
}
.tracker-not_working {
color: red;
}
.tracker-updating {
color: dodgerblue;
}

View file

@ -1,19 +1,14 @@
import { FilePriority, TorrentState } from '@/constants/qbit'
import { formatEta, getDomainBody } from '@/helpers'
import { useMaindataStore } from '@/stores/maindata.ts'
import { Torrent as QbitTorrent } from '@/types/qbit/models'
import { Torrent } from '@/types/vuetorrent'
import { faker } from '@faker-js/faker/locale/en'
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
type StaticTorrent = Omit<Torrent, 'avgDownloadSpeed' | 'avgUploadSpeed' | 'globalSpeed' | 'globalVolume'>
export function useTorrentBuilder() {
const { t } = useI18n()
const maindataStore = useMaindataStore()
const categories = computed(() => maindataStore.categories.map(value => value.name) || ['ISO', 'Other', 'Movie', 'Music', 'TV'])
const computedValues = ['avgDownloadSpeed', 'avgUploadSpeed', 'globalSpeed', 'globalVolume', 'priority']
@ -86,7 +81,7 @@ export function useTorrentBuilder() {
availability: data.availability || faker.number.float({ min: 0, max: 100, precision: 0.01 }),
available_peers,
available_seeds,
category: data.category || faker.helpers.arrayElement(categories.value),
category: data.category || faker.helpers.arrayElement(['ISO', 'Other', 'Movie', 'Music', 'TV']),
completed_on: data.completed_on || faker.date.between({ from: added_on, to: Date.now() }),
content_path: data.content_path || faker.system.filePath(),
dl_limit: data.dl_limit || faker.number.float({ min: 0, max: 1, precision: 0.01 }),

View file

@ -1,11 +1,23 @@
import * as AppPreferences from './AppPreferences'
import { ConnectionStatus } from './ConnectionStatus'
import { FilePriority } from './FilePriority'
import { FilterState } from './FilterState'
import { LogType } from './LogType'
import { PieceState } from './PieceState'
import { FilePriority } from './FilePriority'
import { SortOptions } from './SortOptions'
import { TorrentOperatingMode } from './TorrentOperatingMode'
import { TorrentState } from './TorrentState'
import { TrackerStatus } from './TrackerStatus'
export { AppPreferences, ConnectionStatus, FilterState, LogType, PieceState, FilePriority, TrackerStatus, TorrentOperatingMode, TorrentState }
export {
AppPreferences,
ConnectionStatus,
FilterState,
LogType,
PieceState,
FilePriority,
SortOptions,
TrackerStatus,
TorrentOperatingMode,
TorrentState
}

View file

@ -1,6 +1,27 @@
import { DashboardProperty } from './DashboardProperty'
import { DashboardPropertyType } from './DashboardPropertyType'
import { PropertyData, PropertyMetadata } from '@/types/vuetorrent'
type pptData = { active: boolean; order: number }
type pptMetadata =
| { type: DashboardPropertyType.AMOUNT; props: { title: string; value: string; total: string } }
| { type: DashboardPropertyType.CHIP; props: { title: string; value: string; color: string } }
| { type: DashboardPropertyType.DATA; props: { title: string; value: string } }
| { type: DashboardPropertyType.DATETIME; props: { title: string; value: string } }
| { type: DashboardPropertyType.DURATION; props: { title: string; value: string } }
| { type: DashboardPropertyType.PERCENT; props: { title: string; value: string } }
| { type: DashboardPropertyType.RELATIVE; props: { title: string; value: string } }
| { type: DashboardPropertyType.SPEED; props: { title: string; value: string } }
| { type: DashboardPropertyType.TEXT; props: { title: string; value: string } }
export type TorrentProperty = { name: DashboardProperty } & pptData & pptMetadata
export interface PropertyData {
[key: DashboardProperty | string]: pptData
}
export interface PropertyMetadata {
[key: DashboardProperty | string]: pptMetadata
}
export const propsData: PropertyData = {
[DashboardProperty.ADDED_ON]: {

View file

@ -1,5 +1,3 @@
import { TreeFile } from '@/types/vuetorrent'
enum FileIcon {
PDF = 'mdi-file-pdf-box',
IMAGE = 'mdi-file-image',
@ -53,7 +51,7 @@ export const typesMap: Record<string, FileIcon> = {
jar: FileIcon.EXECUTABLE
}
export function getFileIcon(file: TreeFile) {
const type = file.name.split('.').pop()?.toLowerCase() || ''
export function getFileIcon(filename: string) {
const type = filename.split('.').pop()?.toLowerCase() || ''
return typesMap[type] || 'mdi-file'
}

View file

@ -1,7 +1,8 @@
import type { TorrentProperty, PropertyData, PropertyMetadata } from './DashboardDefaults'
import { propsData, propsMetadata } from './DashboardDefaults'
import { DashboardProperty } from './DashboardProperty'
import { DashboardPropertyType } from './DashboardPropertyType'
import { typesMap, getFileIcon } from './FileIcon'
import { TitleOptions } from './TitleOptions'
export { propsData, propsMetadata, DashboardProperty, DashboardPropertyType, typesMap, getFileIcon, TitleOptions }
export { TorrentProperty, PropertyData, PropertyMetadata, propsData, propsMetadata, DashboardProperty, DashboardPropertyType, typesMap, getFileIcon, TitleOptions }

View file

@ -4,10 +4,7 @@ import RightClickMenu from '@/components/Dashboard/TRC/RightClickMenu.vue'
import ConfirmDeleteDialog from '@/components/Dialogs/ConfirmDeleteDialog.vue'
import { useArrayPagination } from '@/composables'
import { doesCommand } from '@/helpers'
import { useDashboardStore } from '@/stores/dashboard'
import { useDialogStore } from '@/stores/dialog'
import { useMaindataStore } from '@/stores/maindata'
import { useVueTorrentStore } from '@/stores/vuetorrent'
import { useDashboardStore, useDialogStore, useMaindataStore, useTorrentStore, useVueTorrentStore } from '@/stores'
import { Torrent as TorrentType } from '@/types/vuetorrent'
import debounce from 'lodash.debounce'
import { storeToRefs } from 'pinia'
@ -23,13 +20,13 @@ const {
currentPage: dashboardPage,
isSelectionMultiple,
selectedTorrents,
sortOptions,
torrentCountString
} = storeToRefs(useDashboardStore())
const dashboardStore = useDashboardStore()
const dialogStore = useDialogStore()
const { filteredTorrents } = storeToRefs(useMaindataStore())
const maindataStore = useMaindataStore()
const torrentStore = useTorrentStore()
const { filteredTorrents, sortOptions } = storeToRefs(torrentStore)
const vuetorrentStore = useVueTorrentStore()
const torrentSortOptions = [
@ -96,9 +93,9 @@ const trcProperties = reactive({
})
const torrentTitleFilter = computed({
get: () => maindataStore.textFilter,
get: () => torrentStore.textFilter,
set: debounce((newValue: string | null) => {
maindataStore.textFilter = newValue ?? ''
torrentStore.textFilter = newValue ?? ''
}, 300)
})
@ -107,7 +104,7 @@ const {
currentPage,
pageCount
} = useArrayPagination(filteredTorrents, vuetorrentStore.paginationSize, dashboardPage)
const hasSearchFilter = computed(() => !!maindataStore.textFilter && maindataStore.textFilter.length > 0)
const hasSearchFilter = computed(() => !!torrentStore.textFilter && torrentStore.textFilter.length > 0)
const isAllTorrentsSelected = computed(() => filteredTorrents.value.length <= selectedTorrents.value.length)
@ -132,7 +129,7 @@ function goToInfo(hash: string) {
}
function resetInput() {
maindataStore.textFilter = ''
torrentStore.textFilter = ''
}
function scrollToTop() {

View file

@ -1,12 +1,12 @@
<script setup lang="ts">
import PasswordField from '@/components/Core/PasswordField.vue'
import { useAuthStore } from '@/stores/auth'
import { onMounted, reactive, ref, watchEffect } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { toast } from 'vue3-toastify'
import { useI18n } from 'vue-i18n'
import { useAuthStore } from '@/stores'
import { LoginPayload } from '@/types/qbit/payloads'
import { onMounted, reactive, ref, watchEffect } from 'vue'
import { useI18n } from 'vue-i18n'
import { useRoute, useRouter } from 'vue-router'
import { toast } from 'vue3-toastify'
const { t } = useI18n()
const router = useRouter()
@ -68,7 +68,8 @@ watchEffect(() => {
<v-card-subtitle>{{ t('login.subtitle') }}</v-card-subtitle>
<v-card-text>
<v-form v-model="rulesOk" @submit.prevent="login">
<v-text-field v-model="loginForm.username" :label="t('login.username')" autofocus :rules="rules.username" @keydown.enter.prevent="login" variant="outlined">
<v-text-field v-model="loginForm.username" :label="t('login.username')" autofocus :rules="rules.username"
@keydown.enter.prevent="login" variant="outlined">
<template v-slot:prepend>
<v-icon color="accent" icon="mdi-account" />
</template>

View file

@ -1,8 +1,7 @@
<script setup lang="ts">
import { useArrayPagination } from '@/composables'
import { LogType } from '@/constants/qbit'
import { useLogStore } from '@/stores/logs'
import { useVueTorrentStore } from '@/stores/vuetorrent'
import { useLogStore, useVueTorrentStore } from '@/stores'
import { Log } from '@/types/qbit/models'
import { useIntervalFn } from '@vueuse/core'
import dayjs from 'dayjs'
@ -42,7 +41,7 @@ const goHome = () => {
router.push({ name: 'dashboard' })
}
const getLogTypeClassName = (log: Log) => {
return `logtype-${LogType[log?.type]?.toLowerCase()}`
return `logtype-${ LogType[log?.type]?.toLowerCase() }`
}
const getLogTypeName = (log: Log) => {
return LogType[log.type]
@ -92,11 +91,13 @@ onUnmounted(() => {
<v-list-item>
<v-row>
<v-col cols="6">
<v-select v-model="logTypeFilter" :items="logTypeOptions" :label="$t('logs.filters.type')" hide-details multiple chips>
<v-select v-model="logTypeFilter" :items="logTypeOptions" :label="$t('logs.filters.type')" hide-details
multiple chips>
<template v-slot:prepend-item>
<v-list-item :title="$t('common.selectAll')" @click="toggleSelectAll">
<template v-slot:prepend>
<v-checkbox-btn :indeterminate="someTypesSelected && !allTypesSelected" :model-value="someTypesSelected" />
<v-checkbox-btn :indeterminate="someTypesSelected && !allTypesSelected"
:model-value="someTypesSelected" />
</template>
</v-list-item>
<v-divider />
@ -105,7 +106,8 @@ onUnmounted(() => {
</v-col>
<v-col cols="6">
<v-select v-model="sortBy" :items="headers" :label="$t('logs.filters.sortBy.label')" hide-details multiple chips />
<v-select v-model="sortBy" :items="headers" :label="$t('logs.filters.sortBy.label')" hide-details multiple
chips />
</v-col>
</v-row>
</v-list-item>

View file

@ -1,7 +1,6 @@
<script setup lang="ts">
import AddTorrentDialog from '@/components/Dialogs/AddTorrentDialog.vue'
import { useDialogStore } from '@/stores/dialog'
import { useAddTorrentStore } from '@/stores/addTorrents'
import { useAddTorrentStore, useDialogStore } from '@/stores'
import { onBeforeMount } from 'vue'
import { useRoute, useRouter } from 'vue-router'

View file

@ -1,8 +1,6 @@
<script lang="ts" setup>
import { useArrayPagination, useSearchQuery } from '@/composables'
import { useAddTorrentStore } from '@/stores/addTorrents'
import { useDialogStore } from '@/stores/dialog'
import { useRssStore } from '@/stores/rss'
import { useAddTorrentStore, useDialogStore, useRssStore } from '@/stores'
import { RssArticle } from '@/types/vuetorrent'
import debounce from 'lodash.debounce'
import { computed, onBeforeMount, onMounted, onUnmounted, reactive, ref } from 'vue'
@ -124,16 +122,26 @@ onUnmounted(() => {
<template v-for="(article, index) in paginatedResults">
<v-divider v-if="index > 0" color="white" />
<v-list-item :class="{ 'rss-read': article.isRead }" @click="showDescription(article)" @contextmenu="markAsRead(article)">
<v-list-item :class="{ 'rss-read': article.isRead }" @click="showDescription(article)"
@contextmenu="markAsRead(article)">
<div class="d-flex">
<div>
<v-list-item-title class="wrap-anywhere" style="white-space: unset">{{ article.title }}</v-list-item-title>
<v-list-item-title class="wrap-anywhere" style="white-space: unset">{{
article.title
}}
</v-list-item-title>
<v-list-item-subtitle class="d-block">
<div>{{ article.parsedDate.toLocaleString() }}</div>
<div>{{ t('rssArticles.item.feedName', { name: rssStore.getFeedNames(article.id).join(' | ') }) }}</div>
<div>{{
t('rssArticles.item.feedName', { name: rssStore.getFeedNames(article.id).join(' | ') })
}}
</div>
<div v-if="article.author">{{ t('rssArticles.item.author', { author: article.author }) }}</div>
<div v-if="article.category">{{ t('rssArticles.item.category', { category: article.category }) }}</div>
<div v-if="article.category">{{
t('rssArticles.item.category', { category: article.category })
}}
</div>
</v-list-item-subtitle>
</div>

View file

@ -1,14 +1,11 @@
<script setup lang="ts">
import PluginManagerDialog from '@/components/Dialogs/PluginManagerDialog.vue'
import { useSearchQuery } from '@/composables'
import { useAddTorrentStore } from '@/stores/addTorrents'
import { useDialogStore } from '@/stores/dialog'
import { useSearchEngineStore } from '@/stores/searchEngine'
import { useVueTorrentStore } from '@/stores/vuetorrent'
import { formatData } from '@/helpers'
import { useAddTorrentStore, useDialogStore, useSearchEngineStore, useVueTorrentStore } from '@/stores'
import { SearchPlugin, SearchResult } from '@/types/qbit/models'
import { SearchData } from '@/types/vuetorrent'
import { storeToRefs } from 'pinia'
import { formatData } from '@/helpers'
import { computed, onBeforeMount, onBeforeUnmount, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { useRouter } from 'vue-router'
@ -54,10 +51,10 @@ const plugins = computed(() => {
]
searchEngineStore.searchPlugins
.filter((plugin: SearchPlugin) => plugin.enabled)
.forEach((plugin: SearchPlugin) => {
plugins.push({ title: plugin.name, value: plugin.name })
})
.filter((plugin: SearchPlugin) => plugin.enabled)
.forEach((plugin: SearchPlugin) => {
plugins.push({ title: plugin.name, value: plugin.name })
})
return plugins
})
@ -173,7 +170,8 @@ onBeforeUnmount(() => {
<v-spacer />
<v-btn icon="mdi-plus-circle-outline" variant="plain" color="accent" @click="createNewTab" />
<v-btn icon="mdi-minus-circle-outline" variant="plain" color="error" :disabled="searchData.length === 1" @click="deleteTab" />
<v-btn icon="mdi-minus-circle-outline" variant="plain" color="error" :disabled="searchData.length === 1"
@click="deleteTab" />
</v-container>
</v-row>
@ -227,11 +225,14 @@ onBeforeUnmount(() => {
<v-divider class="my-3" />
<v-list-item>
<v-data-table :headers="headers" :items="filteredResults" :footer-props="{ itemsPerPageOptions: [10, 25, 50, 100, -1] }" :items-per-page.sync="selectedTab.itemsPerPage">
<v-data-table :headers="headers" :items="filteredResults"
:footer-props="{ itemsPerPageOptions: [10, 25, 50, 100, -1] }"
:items-per-page.sync="selectedTab.itemsPerPage">
<template v-slot:top>
<v-row>
<v-col cols="12">
<v-text-field v-model="selectedTab.filters.title" density="compact" hide-details :label="$t('searchEngine.filters.title.label')" />
<v-text-field v-model="selectedTab.filters.title" density="compact" hide-details
:label="$t('searchEngine.filters.title.label')" />
</v-col>
</v-row>
</template>
@ -239,7 +240,7 @@ onBeforeUnmount(() => {
{{ formatData(item.fileSize, vuetorrentStore.useBinarySize) }}
</template>
<template v-slot:item.actions="{ item }">
<v-btn icon="mdi-download" variant="flat" density="compact" @click="downloadTorrent(item)"> </v-btn>
<v-btn icon="mdi-download" variant="flat" density="compact" @click="downloadTorrent(item)"></v-btn>
</template>
</v-data-table>
</v-list-item>

View file

@ -1,24 +1,22 @@
<script setup lang="ts">
import Behavior from '@/components/Settings/Behavior.vue'
import { useDialogStore } from '@/stores/dialog'
import { usePreferenceStore } from '@/stores/preferences'
import { onBeforeUnmount, onMounted, ref } from 'vue'
import { useRouter } from 'vue-router'
import { useI18n } from 'vue-i18n'
import { toast } from 'vue3-toastify'
import Advanced from '@/components/Settings/Advanced.vue'
import Behavior from '@/components/Settings/Behavior.vue'
import BitTorrent from '@/components/Settings/BitTorrent.vue'
import Connection from '@/components/Settings/Connection.vue'
import Downloads from '@/components/Settings/Downloads.vue'
import RFeeds from '@/components/Settings/RSS/Feeds.vue'
import RGeneral from '@/components/Settings/RSS/General.vue'
import RRules from '@/components/Settings/RSS/Rules.vue'
import Speed from '@/components/Settings/Speed.vue'
import TagsAndCategories from '@/components/Settings/TagsAndCategories.vue'
import WebUI from '@/components/Settings/WebUI.vue'
import RGeneral from '@/components/Settings/RSS/General.vue'
import RFeeds from '@/components/Settings/RSS/Feeds.vue'
import RRules from '@/components/Settings/RSS/Rules.vue'
import VGeneral from '@/components/Settings/VueTorrent/General.vue'
import VTorrentCard from '@/components/Settings/VueTorrent/TorrentCard.vue'
import WebUI from '@/components/Settings/WebUI.vue'
import { useDialogStore, usePreferenceStore } from '@/stores'
import { onBeforeUnmount, onMounted, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { useRouter } from 'vue-router'
import { toast } from 'vue3-toastify'
const router = useRouter()
const { t } = useI18n()

View file

@ -6,8 +6,7 @@ import Overview from '@/components/TorrentDetail/Overview.vue'
import Peers from '@/components/TorrentDetail/Peers.vue'
import TagsAndCategories from '@/components/TorrentDetail/TagsAndCategories.vue'
import Trackers from '@/components/TorrentDetail/Trackers.vue'
import { useDialogStore } from '@/stores/dialog'
import { useMaindataStore } from '@/stores/maindata'
import { useDialogStore, useTorrentStore } from '@/stores'
import { computed, onBeforeUnmount, onMounted, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { useRoute, useRouter } from 'vue-router'
@ -16,12 +15,12 @@ const route = useRoute()
const router = useRouter()
const { t } = useI18n()
const dialogStore = useDialogStore()
const maindataStore = useMaindataStore()
const torrentStore = useTorrentStore()
const tab = ref('overview')
const hash = computed(() => route.params.hash as string)
const torrent = computed(() => maindataStore.getTorrentByHash(hash.value))
const torrent = computed(() => torrentStore.getTorrentByHash(hash.value))
const goHome = () => {
router.push({ name: 'dashboard' })

View file

@ -1,6 +1,6 @@
import { useAuthStore } from '@/stores/auth'
import { createRouter, createWebHashHistory } from 'vue-router'
import { routes } from '@/pages'
import { useAuthStore } from '@/stores'
import { createRouter, createWebHashHistory } from 'vue-router'
const router = createRouter({

View file

@ -1,3 +1,4 @@
import { NetworkInterface } from '@/types/qbit/models/AppPreferences.ts'
import type { AxiosInstance } from 'axios'
import axios from 'axios'
import type {
@ -7,7 +8,6 @@ import type {
Feed,
FeedRule,
Log,
NetworkInterface,
SearchJob,
SearchPlugin,
SearchStatus,

View file

@ -1,4 +1,4 @@
import { usePreferenceStore } from '@/stores/preferences'
import { usePreferenceStore } from './preferences'
import { AddTorrentPayload } from '@/types/qbit/payloads'
import { defineStore } from 'pinia'
import { computed, reactive, ref } from 'vue'

View file

@ -1,6 +1,6 @@
import { ref } from 'vue'
import { defineStore } from 'pinia'
import { qbit } from '@/services'
import { defineStore } from 'pinia'
import { ref } from 'vue'
export const useAppStore = defineStore('app', () => {
const intervals = ref<number[]>([])

View file

@ -1,11 +1,9 @@
import { SortOptions } from '@/constants/qbit/SortOptions'
import { formatData } from '@/helpers'
import { useMaindataStore } from '@/stores/maindata'
import { useVueTorrentStore } from '@/stores/vuetorrent'
import { GetTorrentPayload } from '@/types/qbit/payloads'
import { defineStore } from 'pinia'
import { computed, reactive, ref, watch } from 'vue'
import { computed, ref, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { useTorrentStore } from './torrents'
import { useVueTorrentStore } from './vuetorrent'
export const useDashboardStore = defineStore(
'dashboard',
@ -14,38 +12,26 @@ export const useDashboardStore = defineStore(
const isSelectionMultiple = ref(false)
const selectedTorrents = ref<string[]>([])
const latestSelectedTorrent = ref<string>()
const sortOptions = reactive({
isCustomSortEnabled: false,
sortBy: SortOptions.DEFAULT,
reverseOrder: false
})
const { t } = useI18n()
const maindataStore = useMaindataStore()
const torrentStore = useTorrentStore()
const vuetorrentStore = useVueTorrentStore()
const torrentCountString = computed(() => {
if (selectedTorrents.value.length) {
const selectedSize = selectedTorrents.value
.map(hash => maindataStore.getTorrentByHash(hash))
.map(hash => torrentStore.getTorrentByHash(hash))
.filter(torrent => torrent !== undefined)
.map(torrent => torrent!.size)
.reduce((partial, size) => partial + size, 0)
return t('dashboard.selectedTorrentsCount', {
count: selectedTorrents.value.length,
total: maindataStore.filteredTorrents.length,
total: torrentStore.filteredTorrents.length,
size: formatData(selectedSize, vuetorrentStore.useBinarySize)
})
} else {
return t('dashboard.torrentsCount', maindataStore.filteredTorrents.length)
}
})
const getTorrentsPayload = computed<GetTorrentPayload>(() => {
return {
sort: sortOptions.isCustomSortEnabled ? SortOptions.DEFAULT : sortOptions.sortBy,
reverse: sortOptions.reverseOrder
return t('dashboard.torrentsCount', torrentStore.filteredTorrents.length)
}
})
@ -84,19 +70,19 @@ export const useDashboardStore = defineStore(
function spanTorrentSelection(endHash: string) {
if (!latestSelectedTorrent.value) return
const latestIndex = maindataStore.getTorrentIndexByHash(latestSelectedTorrent.value)
const endIndex = maindataStore.getTorrentIndexByHash(endHash)
const latestIndex = torrentStore.getTorrentIndexByHash(latestSelectedTorrent.value)
const endIndex = torrentStore.getTorrentIndexByHash(endHash)
const start = Math.min(endIndex, latestIndex)
const end = Math.max(endIndex, latestIndex)
const hashes = maindataStore.filteredTorrents.slice(start, end + 1).map(t => t.hash)
const hashes = torrentStore.filteredTorrents.slice(start, end + 1).map(t => t.hash)
selectTorrents(...hashes)
}
function selectAllTorrents() {
isSelectionMultiple.value = true
selectedTorrents.value.splice(0, selectedTorrents.value.length, ...maindataStore.torrents.map(t => t.hash))
latestSelectedTorrent.value = maindataStore.torrents[0]?.hash
selectedTorrents.value.splice(0, selectedTorrents.value.length, ...torrentStore.torrents.map(t => t.hash))
latestSelectedTorrent.value = torrentStore.torrents[0]?.hash
}
function unselectAllTorrents() {
@ -109,7 +95,7 @@ export const useDashboardStore = defineStore(
}
})
watch(() => maindataStore.filteredTorrents, newValue => {
watch(() => torrentStore.filteredTorrents, newValue => {
const pageCount = Math.ceil(newValue.length / vuetorrentStore.paginationSize)
if (pageCount < currentPage.value) {
currentPage.value = Math.max(1, pageCount)
@ -121,7 +107,6 @@ export const useDashboardStore = defineStore(
isSelectionMultiple,
selectedTorrents,
latestSelectedTorrent,
sortOptions,
torrentCountString,
isTorrentInSelection,
selectTorrent,
@ -130,20 +115,8 @@ export const useDashboardStore = defineStore(
spanTorrentSelection,
selectAllTorrents,
unselectAllTorrents,
toggleSelect,
getTorrentsPayload
toggleSelect
}
},
{
persist: {
enabled: true,
strategies: [
{
storage: localStorage,
key: 'vuetorrent_dashboard',
paths: ['sortOptions']
}
]
}
}
)

29
src/stores/index.ts Normal file
View file

@ -0,0 +1,29 @@
import { useAddTorrentStore } from './addTorrents'
import { useAppStore } from './app'
import { useAuthStore } from './auth'
import { useDashboardStore } from './dashboard'
import { useDialogStore } from './dialog'
import { useLogStore } from './logs'
import { useMaindataStore } from './maindata'
import { useNavbarStore } from './navbar'
import { usePreferenceStore } from './preferences'
import { useRssStore } from './rss'
import { useSearchEngineStore } from './searchEngine'
import { useTorrentStore } from './torrents'
import { useVueTorrentStore } from './vuetorrent'
export {
useAddTorrentStore,
useAppStore,
useAuthStore,
useDashboardStore,
useDialogStore,
useLogStore,
useMaindataStore,
useNavbarStore,
usePreferenceStore,
useRssStore,
useSearchEngineStore,
useTorrentStore,
useVueTorrentStore
}

View file

@ -1,18 +1,17 @@
import { useSearchQuery, useTorrentBuilder } from '@/composables'
import { FilePriority, TorrentState } from '@/constants/qbit'
import { SortOptions } from '@/constants/qbit/SortOptions'
import { useTorrentBuilder } from '@/composables'
import { FilePriority, SortOptions } from '@/constants/qbit'
import { extractHostname } from '@/helpers'
import { uuidFromRaw } from '@/helpers/text'
import { qbit } from '@/services'
import { useAuthStore } from '@/stores/auth'
import { useDashboardStore } from '@/stores/dashboard'
import { useNavbarStore } from '@/stores/navbar'
import { useVueTorrentStore } from '@/stores/vuetorrent'
import { Category, ServerState } from '@/types/qbit/models'
import { AddTorrentPayload } from '@/types/qbit/payloads'
import { Torrent } from '@/types/vuetorrent'
import { defineStore } from 'pinia'
import { computed, MaybeRefOrGetter, ref, toValue } from 'vue'
import { defineStore, storeToRefs } from 'pinia'
import { MaybeRefOrGetter, ref, toValue } from 'vue'
import { useAuthStore } from './auth'
import { useDashboardStore } from './dashboard'
import { useNavbarStore } from './navbar'
import { useTorrentStore } from './torrents'
import { useVueTorrentStore } from './vuetorrent'
export const useMaindataStore = defineStore('maindata', () => {
const categories = ref<Category[]>([])
@ -20,64 +19,17 @@ export const useMaindataStore = defineStore('maindata', () => {
const rid = ref<number>()
const serverState = ref<ServerState>()
const tags = ref<string[]>([])
const torrents = ref<Torrent[]>([])
const trackers = ref<string[]>([])
const isTextFilterActive = ref(true)
const isStatusFilterActive = ref(true)
const isCategoryFilterActive = ref(true)
const isTagFilterActive = ref(true)
const isTrackerFilterActive = ref(true)
const textFilter = ref('')
const statusFilter = ref<TorrentState[]>([])
const categoryFilter = ref<string[]>([])
const tagFilter = ref<(string | null)[]>([])
const trackerFilter = ref<(string | null)[]>([])
const torrentsWithFilters = computed(() => {
return torrents.value.filter(torrent => {
if (statusFilter.value.length > 0 && isStatusFilterActive.value && !statusFilter.value.includes(torrent.state)) return false
if (categoryFilter.value.length > 0 && isCategoryFilterActive.value && !categoryFilter.value.includes(torrent.category)) return false
if (tagFilter.value.length > 0 && isTagFilterActive.value) {
if (torrent.tags.length === 0 && tagFilter.value.includes(null)) return true
if (!torrent.tags.some(tag => tagFilter.value.includes(tag))) return false
}
if (trackerFilter.value.length > 0 && isTrackerFilterActive.value && !trackerFilter.value.includes(extractHostname(torrent.tracker))) return false
return true
})
})
const filteredTorrents = computed(() => searchQuery.results.value)
const authStore = useAuthStore()
const dashboardStore = useDashboardStore()
const navbarStore = useNavbarStore()
const torrentStore = useTorrentStore()
const { torrents } = storeToRefs(torrentStore)
const vueTorrentStore = useVueTorrentStore()
const torrentBuilder = useTorrentBuilder()
const searchQuery = useSearchQuery(
torrentsWithFilters,
() => isTextFilterActive.value ? textFilter.value : null,
torrent => torrent.name,
results => {
if (dashboardStore.sortOptions.isCustomSortEnabled) {
if (dashboardStore.sortOptions.sortBy === 'priority') {
results.sort((a, b) => {
if (a.priority > 0 && b.priority > 0) return a.priority - b.priority
else if (a.priority <= 0 && b.priority <= 0) return a.added_on - b.added_on
else if (a.priority <= 0) return 1
else return -1
})
} else {
results.sort((a, b) => a[dashboardStore.sortOptions.sortBy] - b[dashboardStore.sortOptions.sortBy] || a.added_on - b.added_on)
}
if (dashboardStore.sortOptions.reverseOrder) results.reverse()
}
return results
}
)
async function fetchCategories() {
categories.value = await qbit.getCategories()
}
@ -90,10 +42,6 @@ export const useMaindataStore = defineStore('maindata', () => {
await qbit.createCategory(category)
}
async function setTorrentCategory(hashes: string[], category: string) {
await qbit.setCategory(hashes, category)
}
async function editCategory(category: Category, oldCategory?: string) {
if (oldCategory) {
// Create new category
@ -131,14 +79,6 @@ export const useMaindataStore = defineStore('maindata', () => {
await qbit.createTag(tags)
}
async function addTorrentTags(hashes: string[], tags: string[]) {
await qbit.addTorrentTag(hashes, tags)
}
async function removeTorrentTags(hashes: string[], tags: string[]) {
await qbit.removeTorrentTag(hashes, tags)
}
async function editTag(oldTag: string, newTag: string) {
if (oldTag === newTag) return
@ -162,22 +102,6 @@ export const useMaindataStore = defineStore('maindata', () => {
await qbit.deleteTags(tags)
}
function getTorrentByHash(hash: string) {
return torrents.value.find(t => t.hash === hash)
}
function getTorrentIndexByHash(hash: string) {
return filteredTorrents.value.findIndex(t => t.hash === hash)
}
async function deleteTorrents(hashes: string[], deleteWithFiles: boolean) {
await qbit.deleteTorrents(hashes, deleteWithFiles)
}
async function moveTorrents(hashes: string[], newPath: string) {
await qbit.setTorrentLocation(hashes, newPath)
}
async function updateMaindata() {
if (isUpdatingMaindata.value) return
isUpdatingMaindata.value = true
@ -193,8 +117,8 @@ export const useMaindataStore = defineStore('maindata', () => {
}
// fetch torrent data
dashboardStore.sortOptions.isCustomSortEnabled = torrentBuilder.computedValues.indexOf(dashboardStore.sortOptions.sortBy) !== -1
let data = await qbit.getTorrents(dashboardStore.getTorrentsPayload)
torrentStore.sortOptions.isCustomSortEnabled = torrentBuilder.computedValues.indexOf(torrentStore.sortOptions.sortBy) !== -1
let data = await qbit.getTorrents(torrentStore.getTorrentsPayload)
if (vueTorrentStore.showTrackerFilter) {
trackers.value = data
@ -219,8 +143,6 @@ export const useMaindataStore = defineStore('maindata', () => {
// filter out deleted torrents from selection
const hash_index = torrents.value.map(torrent => torrent.hash)
dashboardStore.selectedTorrents = dashboardStore.selectedTorrents.filter(hash => hash_index.includes(hash))
vueTorrentStore.updateTitle()
} catch (error: any) {
if (error?.response?.status === 403) {
console.error('No longer authenticated, logging out...')
@ -234,22 +156,10 @@ export const useMaindataStore = defineStore('maindata', () => {
}
}
async function addTorrents(payload: AddTorrentPayload, torrents: File[]) {
return await qbit.addTorrents(payload, torrents)
}
async function getTorrentProperties(hash: string) {
return await qbit.getTorrentProperties(hash)
}
async function fetchFiles(hash: string, indexes?: number[]) {
return await qbit.getTorrentFiles(hash, indexes)
}
async function renameTorrent(hash: string, newName: string) {
await qbit.setTorrentName(hash, newName)
}
async function renameTorrentFile(hash: string, oldPath: string, newPath: string) {
await qbit.renameFile(hash, oldPath, newPath)
}
@ -262,22 +172,6 @@ export const useMaindataStore = defineStore('maindata', () => {
return await qbit.getTorrentPieceStates(hash)
}
async function resumeTorrents(hashes: MaybeRefOrGetter<string[]>) {
await qbit.resumeTorrents(toValue(hashes))
}
async function forceResumeTorrents(hashes: MaybeRefOrGetter<string[]>) {
await qbit.forceStartTorrents(toValue(hashes))
}
async function pauseTorrents(hashes: MaybeRefOrGetter<string[]>) {
await qbit.pauseTorrents(toValue(hashes))
}
async function recheckTorrents(hashes: MaybeRefOrGetter<string[]>) {
await qbit.recheckTorrents(toValue(hashes))
}
async function reannounceTorrents(hashes: MaybeRefOrGetter<string[]>) {
await qbit.reannounceTorrents(toValue(hashes))
}
@ -298,10 +192,6 @@ export const useMaindataStore = defineStore('maindata', () => {
await qbit.setSuperSeeding(toValue(hashes), toValue(enable))
}
async function setTorrentPriority(hashes: string[], priority: 'increasePrio' | 'decreasePrio' | 'topPrio' | 'bottomPrio') {
await qbit.setTorrentPriority(hashes, priority)
}
async function getTorrentTrackers(hash: string) {
return await qbit.getTorrentTrackers(hash)
}
@ -334,10 +224,6 @@ export const useMaindataStore = defineStore('maindata', () => {
await qbit.setTorrentFilePriority(hash, ids, priority)
}
async function exportTorrent(hash: string) {
return await qbit.exportTorrent(hash)
}
async function setDownloadLimit(limit: number, hashes: string[]) {
return await qbit.setDownloadLimit(hashes, limit)
}
@ -356,53 +242,26 @@ export const useMaindataStore = defineStore('maindata', () => {
rid,
serverState,
tags,
torrents,
filteredTorrents,
trackers,
isTextFilterActive,
textFilter,
isStatusFilterActive,
statusFilter,
isCategoryFilterActive,
categoryFilter,
isTagFilterActive,
tagFilter,
isTrackerFilterActive,
trackerFilter,
getTorrentByHash,
getTorrentIndexByHash,
deleteTorrents,
fetchCategories,
getCategoryFromName,
createCategory,
setTorrentCategory,
editCategory,
deleteCategories,
fetchTags,
createTags,
addTorrentTags,
removeTorrentTags,
editTag,
deleteTags,
moveTorrents,
updateMaindata,
addTorrents,
getTorrentProperties,
fetchFiles,
renameTorrent,
renameTorrentFile,
renameTorrentFolder,
fetchPieceState,
resumeTorrents,
forceResumeTorrents,
pauseTorrents,
recheckTorrents,
reannounceTorrents,
toggleSeqDl,
toggleFLPiecePrio,
toggleAutoTmm,
setSuperSeeding,
setTorrentPriority,
getTorrentTrackers,
addTorrentTrackers,
editTorrentTracker,
@ -411,31 +270,8 @@ export const useMaindataStore = defineStore('maindata', () => {
addTorrentPeers,
banPeers,
setTorrentFilePriority,
exportTorrent,
setDownloadLimit,
setUploadLimit,
setShareLimit
}
}, {
persist: {
enabled: true,
strategies: [
{
storage: localStorage,
key: 'vuetorrent_maindata',
paths: [
'isTextFilterActive',
'textFilter',
'isStatusFilterActive',
'statusFilter',
'isCategoryFilterActive',
'categoryFilter',
'isTagFilterActive',
'tagFilter',
'isTrackerFilterActive',
'trackerFilter'
]
}
]
}
})

195
src/stores/torrents.ts Normal file
View file

@ -0,0 +1,195 @@
import { useSearchQuery } from '@/composables'
import { SortOptions, TorrentState } from '@/constants/qbit'
import { extractHostname } from '@/helpers'
import { qbit } from '@/services'
import { AddTorrentPayload, GetTorrentPayload } from '@/types/qbit/payloads'
import { Torrent } from '@/types/vuetorrent'
import { defineStore } from 'pinia'
import { computed, MaybeRefOrGetter, reactive, ref, toValue } from 'vue'
export const useTorrentStore = defineStore('torrents', () => {
const torrents = ref<Torrent[]>([])
const isTextFilterActive = ref(true)
const isStatusFilterActive = ref(true)
const isCategoryFilterActive = ref(true)
const isTagFilterActive = ref(true)
const isTrackerFilterActive = ref(true)
const textFilter = ref('')
const statusFilter = ref<TorrentState[]>([])
const categoryFilter = ref<string[]>([])
const tagFilter = ref<(string | null)[]>([])
const trackerFilter = ref<(string | null)[]>([])
const torrentsWithFilters = computed(() => {
return torrents.value.filter(torrent => {
if (statusFilter.value.length > 0 && isStatusFilterActive.value && !statusFilter.value.includes(torrent.state)) return false
if (categoryFilter.value.length > 0 && isCategoryFilterActive.value && !categoryFilter.value.includes(torrent.category)) return false
if (tagFilter.value.length > 0 && isTagFilterActive.value) {
if (torrent.tags.length === 0 && tagFilter.value.includes(null)) return true
if (!torrent.tags.some(tag => tagFilter.value.includes(tag))) return false
}
if (trackerFilter.value.length > 0 && isTrackerFilterActive.value && !trackerFilter.value.includes(extractHostname(torrent.tracker))) return false
return true
})
})
const filteredTorrents = computed(() => searchQuery.results.value)
const sortOptions = reactive({
isCustomSortEnabled: false,
sortBy: SortOptions.DEFAULT,
reverseOrder: false
})
const getTorrentsPayload = computed<GetTorrentPayload>(() => {
return {
sort: sortOptions.isCustomSortEnabled ? SortOptions.DEFAULT : sortOptions.sortBy,
reverse: sortOptions.reverseOrder
}
})
const searchQuery = useSearchQuery(
torrentsWithFilters,
() => isTextFilterActive.value ? textFilter.value : null,
torrent => torrent.name,
results => {
if (sortOptions.isCustomSortEnabled) {
if (sortOptions.sortBy === 'priority') {
results.sort((a, b) => {
if (a.priority > 0 && b.priority > 0) return a.priority - b.priority
else if (a.priority <= 0 && b.priority <= 0) return a.added_on - b.added_on
else if (a.priority <= 0) return 1
else return -1
})
} else {
results.sort((a, b) => a[sortOptions.sortBy] - b[sortOptions.sortBy] || a.added_on - b.added_on)
}
if (sortOptions.reverseOrder) results.reverse()
}
return results
}
)
async function setTorrentCategory(hashes: string[], category: string) {
await qbit.setCategory(hashes, category)
}
async function addTorrentTags(hashes: string[], tags: string[]) {
await qbit.addTorrentTag(hashes, tags)
}
async function removeTorrentTags(hashes: string[], tags: string[]) {
await qbit.removeTorrentTag(hashes, tags)
}
function getTorrentByHash(hash: string) {
return torrents.value.find(t => t.hash === hash)
}
function getTorrentIndexByHash(hash: string) {
return filteredTorrents.value.findIndex(t => t.hash === hash)
}
async function deleteTorrents(hashes: string[], deleteWithFiles: boolean) {
await qbit.deleteTorrents(hashes, deleteWithFiles)
}
async function moveTorrents(hashes: string[], newPath: string) {
await qbit.setTorrentLocation(hashes, newPath)
}
async function addTorrents(payload: AddTorrentPayload, torrents: File[]) {
return await qbit.addTorrents(payload, torrents)
}
async function getTorrentProperties(hash: string) {
return await qbit.getTorrentProperties(hash)
}
async function renameTorrent(hash: string, newName: string) {
await qbit.setTorrentName(hash, newName)
}
async function resumeTorrents(hashes: MaybeRefOrGetter<string[]>) {
await qbit.resumeTorrents(toValue(hashes))
}
async function forceResumeTorrents(hashes: MaybeRefOrGetter<string[]>) {
await qbit.forceStartTorrents(toValue(hashes))
}
async function pauseTorrents(hashes: MaybeRefOrGetter<string[]>) {
await qbit.pauseTorrents(toValue(hashes))
}
async function recheckTorrents(hashes: MaybeRefOrGetter<string[]>) {
await qbit.recheckTorrents(toValue(hashes))
}
async function setTorrentPriority(hashes: string[], priority: 'increasePrio' | 'decreasePrio' | 'topPrio' | 'bottomPrio') {
await qbit.setTorrentPriority(hashes, priority)
}
async function exportTorrent(hash: string) {
return await qbit.exportTorrent(hash)
}
return {
torrents,
isTextFilterActive,
isStatusFilterActive,
isCategoryFilterActive,
isTagFilterActive,
isTrackerFilterActive,
textFilter,
statusFilter,
categoryFilter,
tagFilter,
trackerFilter,
torrentsWithFilters,
filteredTorrents,
sortOptions,
getTorrentsPayload,
searchQuery,
setTorrentCategory,
addTorrentTags,
removeTorrentTags,
getTorrentByHash,
getTorrentIndexByHash,
deleteTorrents,
moveTorrents,
addTorrents,
getTorrentProperties,
renameTorrent,
resumeTorrents,
forceResumeTorrents,
pauseTorrents,
recheckTorrents,
setTorrentPriority,
exportTorrent
}
}, {
persist: {
enabled: true,
strategies: [
{
storage: localStorage,
key: 'vuetorrent_torrents',
paths: [
'isTextFilterActive',
'textFilter',
'isStatusFilterActive',
'statusFilter',
'isCategoryFilterActive',
'categoryFilter',
'isTagFilterActive',
'tagFilter',
'isTrackerFilterActive',
'trackerFilter',
'sortOptions'
]
}
]
}
})

View file

@ -1,8 +1,12 @@
import { propsData, propsMetadata, DashboardProperty, TitleOptions } from '@/constants/vuetorrent'
import { formatPercent, formatSpeed } from '@/helpers'
import {
DashboardProperty,
PropertyData,
propsData,
propsMetadata,
TitleOptions,
TorrentProperty
} from '@/constants/vuetorrent'
import { Theme } from '@/plugins/vuetify'
import { useMaindataStore } from '@/stores/maindata'
import { PropertyData, TorrentProperty } from '@/types/vuetorrent'
import { useMediaQuery } from '@vueuse/core'
import { defineStore } from 'pinia'
import { computed, ref, watch } from 'vue'
@ -73,7 +77,6 @@ export const useVueTorrentStore = defineStore(
const i18n = useI18n()
const router = useRouter()
const theme = useTheme()
const maindataStore = useMaindataStore()
watch(language, setLanguage)
watch(darkMode, updateTheme)
@ -111,45 +114,13 @@ export const useVueTorrentStore = defineStore(
await router.push({ name: 'login', query: { redirect: router.currentRoute.value.path } })
}
function updateTitle() {
const mode = uiTitleType.value
switch (mode) {
case TitleOptions.GLOBAL_SPEED:
document.title =
'[' +
`D: ${formatSpeed(maindataStore.serverState?.dl_info_speed ?? 0, useBitSpeed.value)}, ` +
`U: ${formatSpeed(maindataStore.serverState?.up_info_speed ?? 0, useBitSpeed.value)}` +
'] VueTorrent'
break
case TitleOptions.FIRST_TORRENT_STATUS:
const torrent = maindataStore.torrents.at(0)
if (torrent) {
document.title =
'[' +
`D: ${formatSpeed(torrent.dlspeed, useBitSpeed.value)}, ` +
`U: ${formatSpeed(torrent.upspeed, useBitSpeed.value)}, ` +
`${formatPercent(torrent.progress)}` +
'] VueTorrent'
} else {
document.title = '[N/A] VueTorrent'
}
break
case TitleOptions.CUSTOM:
document.title = uiTitleCustom.value
break
case TitleOptions.DEFAULT:
default:
document.title = 'VueTorrent'
break
}
}
function updateBusyProperties(values: TorrentProperty[]) {
values.forEach((ppt, index) => {
_busyProperties.value[ppt.name].active = ppt.active
_busyProperties.value[ppt.name].order = index + 1
})
}
function updateDoneProperties(values: TorrentProperty[]) {
values.forEach((ppt, index) => {
_doneProperties.value[ppt.name].active = ppt.active
@ -160,6 +131,7 @@ export const useVueTorrentStore = defineStore(
function toggleBusyProperty(name: DashboardProperty) {
_busyProperties.value[name].active = !_busyProperties.value[name].active
}
function toggleDoneProperty(name: DashboardProperty) {
_doneProperties.value[name].active = !_doneProperties.value[name].active
}
@ -201,7 +173,6 @@ export const useVueTorrentStore = defineStore(
updateSystemTheme,
toggleTheme,
redirectToLogin,
updateTitle,
updateBusyProperties,
updateDoneProperties,
toggleBusyProperty,

View file

@ -1,4 +1,4 @@
import { FeedArticle } from '@/types/qbit/models'
import FeedArticle from './FeedArticle'
export default interface Feed {
name: string

View file

@ -1,4 +1,4 @@
export interface FeedArticle {
export default interface FeedArticle {
author: string
category: string
date: string

View file

@ -12,8 +12,7 @@ import type SearchPlugin from './SearchPlugin'
import type SearchJob from './SearchJob'
import type SearchStatus from './SearchStatus'
import type SearchResult from './SearchResult'
import { FeedArticle } from './FeedArticle'
import { NetworkInterface } from './AppPreferences'
import type FeedArticle from './FeedArticle'
import type Log from './Log'
type ApplicationVersion = string
@ -21,7 +20,6 @@ type ApplicationVersion = string
export type {
ApplicationVersion,
AppPreferences,
NetworkInterface,
Category,
ServerState,
Tracker,

View file

@ -1,4 +1,4 @@
import type { BasePayload } from './index'
import type BasePayload from './BasePayload'
export default interface CreateFeedPayload extends BasePayload {
url: string

View file

@ -1,5 +1,4 @@
import { FilterState } from '@/constants/qbit'
import { SortOptions } from '@/constants/qbit/SortOptions'
import { FilterState, SortOptions } from '@/constants/qbit'
export default interface GetTorrentPayload {
filter?: FilterState

View file

@ -1,4 +1,4 @@
import type { BasePayload } from './index'
import type BasePayload from './BasePayload'
export default interface LoginPayload extends BasePayload {
/** Username used to access the WebUI */

View file

@ -1,5 +1,5 @@
import type { Optional } from '@/types/global'
import type { BasePayload } from './index'
import type BasePayload from './BasePayload'
export default interface PeerLogPayload extends BasePayload {
// Exclude messages with "message id" <= last_known_id (default: -1)

View file

@ -1,23 +0,0 @@
import { DashboardProperty, DashboardPropertyType } from '@/constants/vuetorrent'
type pptData = { active: boolean; order: number }
type pptMetadata =
| { type: DashboardPropertyType.AMOUNT; props: { title: string; value: string; total: string } }
| { type: DashboardPropertyType.CHIP; props: { title: string; value: string; color: string } }
| { type: DashboardPropertyType.DATA; props: { title: string; value: string } }
| { type: DashboardPropertyType.DATETIME; props: { title: string; value: string } }
| { type: DashboardPropertyType.DURATION; props: { title: string; value: string } }
| { type: DashboardPropertyType.PERCENT; props: { title: string; value: string } }
| { type: DashboardPropertyType.RELATIVE; props: { title: string; value: string } }
| { type: DashboardPropertyType.SPEED; props: { title: string; value: string } }
| { type: DashboardPropertyType.TEXT; props: { title: string; value: string } }
export interface PropertyData {
[key: DashboardProperty | string]: pptData
}
export interface PropertyMetadata {
[key: DashboardProperty | string]: pptMetadata
}
export type TorrentProperty = { name: DashboardProperty } & pptData & pptMetadata

View file

@ -1,8 +1,7 @@
import type { RssArticle } from './RssArticle'
import type { SearchData } from './SearchData'
import type Torrent from './Torrent'
import type { PropertyData, PropertyMetadata, TorrentProperty } from './TorrentProperty'
import type { TreeNode, TreeFile, TreeFolder, TreeRoot } from './TreeObjects'
import type { TRCMenuEntry } from './TRCMenuEntry'
export { RssArticle, SearchData, Torrent, PropertyData, PropertyMetadata, TorrentProperty, TreeNode, TreeFile, TreeFolder, TreeRoot, TRCMenuEntry }
export { RssArticle, SearchData, Torrent, TreeNode, TreeFile, TreeFolder, TreeRoot, TRCMenuEntry }

12
src/vite-env.d.ts vendored
View file

@ -1 +1,13 @@
/// <reference types="vite/client" />
interface ImportMetaEnv {
readonly VITE_QBITTORRENT_TARGET: string
readonly VITE_QBITTORRENT_PORT: number
readonly VITE_USE_FAKE_TORRENTS: string
readonly VITE_FAKE_TORRENT_COUNT: number
}
interface ImportMeta {
readonly env: ImportMetaEnv
}