feat(AddTorrentDialog): Rework dialog to add missing parameters (#1323)

This commit is contained in:
Rémi Marseault 2023-11-19 12:32:32 +01:00 committed by GitHub
parent 153268f30d
commit f69851cc39
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 506 additions and 226 deletions

View file

@ -2,23 +2,23 @@
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 { useNavbarStore } from '@/stores/navbar.ts'
import { usePreferenceStore } from '@/stores/preferences'
import { useVueTorrentStore } from '@/stores/vuetorrent'
import { onBeforeMount, watch } from 'vue'
const authStore = useAuthStore()
const addTorrentStore = useAddTorrentStore()
const appStore = useAppStore()
const authStore = useAuthStore()
const dialogStore = useDialogStore()
const logStore = useLogStore()
const maindataStore = useMaindataStore()
const navbarStore = useNavbarStore()
const preferencesStore = usePreferenceStore()
const vuetorrentStore = useVueTorrentStore()
@ -63,7 +63,7 @@ watch(
await logStore.fetchLogs()
await maindataStore.fetchCategories()
await maindataStore.fetchTags()
navbarStore.initAddTorrentDialogForm()
addTorrentStore.initForm()
} else {
appStore.clearIntervals()
}

View file

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

View file

@ -22,7 +22,7 @@ defineProps<{
<span v-if="disabled && disabledText">{{ disabledText }}</span>
<span v-else>{{ text }}</span>
<v-spacer />
<v-icon v-if="children">mdi-chevron-right</v-icon>
<v-icon v-if="!disabled && children">mdi-chevron-right</v-icon>
</div>
<v-menu v-if="children" activator="parent" :open-on-hover="true" :open-on-click="true" close-delay="0" open-delay="0" location="right">
<v-list>

View file

@ -1,13 +1,14 @@
<script lang="ts" setup>
import { useDialog } from '@/composables'
import { AppPreferences } from '@/constants/qbit'
import { useAddTorrentStore } from '@/stores/addTorrents'
import { useMaindataStore } from '@/stores/maindata'
import { useNavbarStore } from '@/stores/navbar'
import { usePreferenceStore } from '@/stores/preferences'
import { useVueTorrentStore } from '@/stores/vuetorrent'
import { AddTorrentPayload } from '@/types/qbit/payloads'
import { storeToRefs } from 'pinia'
import { computed, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { toast } from 'vue3-toastify'
const props = withDefaults(defineProps<{
guid: string
@ -18,65 +19,152 @@ const props = withDefaults(defineProps<{
const { isOpened } = useDialog(props.guid)
const { t } = useI18n()
const addTorrentStore = useAddTorrentStore()
const maindataStore = useMaindataStore()
const navbarStore = useNavbarStore()
const { urls, files, form } = storeToRefs(addTorrentStore)
const preferenceStore = usePreferenceStore()
const vueTorrentStore = useVueTorrentStore()
const fileOverflowDisplayLimit = 2
const isFormValid = computed(() => navbarStore.addTorrentDialogUrls.length > 0 || navbarStore.addTorrentDialogFiles.length > 0)
const tagSearch = ref('')
const categorySearch = ref('')
const isLoading = ref(false)
const contentLayoutOptions = ref([
const contentLayoutOptions = [
{ title: t('constants.contentLayout.original'), value: AppPreferences.ContentLayout.ORIGINAL },
{ title: t('constants.contentLayout.subfolder'), value: AppPreferences.ContentLayout.SUBFOLDER },
{ title: t('constants.contentLayout.nosubfolder'), value: AppPreferences.ContentLayout.NO_SUBFOLDER }
])
const stopConditionOptions = ref([
]
const stopConditionOptions = [
{ title: t('constants.stopCondition.none'), value: AppPreferences.StopCondition.NONE },
{ title: t('constants.stopCondition.metadataReceived'), value: AppPreferences.StopCondition.METADATA_RECEIVED },
{ title: t('constants.stopCondition.filesChecked'), value: AppPreferences.StopCondition.FILES_CHECKED }
])
]
const submit = async () => {
const isFormValid = computed(() => urls.value.length > 0 || files.value.length > 0)
const cookie = computed({
get: () => form.value.cookie,
set: value => form.value.cookie = value || undefined
})
const rename = computed({
get: () => form.value.rename,
set: value => form.value.rename = value || undefined
})
const tagSearch = ref('')
const tags = computed({
get: () => {
if (form.value.tags) {
return form.value.tags.split(',').map(tag => tag.trim())
} else {
return []
}
},
set: (value: string[]) => form.value.tags = value.join(',')
})
const categorySearch = ref('')
const categories = computed(() => maindataStore.categories.map(c => c.name))
const category = computed<string | undefined>({
get: () => form.value.category || categorySearch.value || undefined,
set: value => form.value.category = value || undefined
})
const downloadPath = computed({
get: () => form.value.downloadPath,
set: value => {
form.value.useDownloadPath = !!value || undefined
form.value.downloadPath = value || undefined
}
})
const startNow = computed({
get: () => !form.value.paused,
set: value => form.value.paused = !value
})
const dlLimit = computed({
get: () => {
if (!form.value.dlLimit || form.value.dlLimit === -1) {
return ''
} else {
return (form.value.dlLimit / 1024).toString()
}
},
set: value => {
if (!value) {
form.value.dlLimit = undefined
} else {
const parsedValue = parseInt(value)
if (parsedValue > 0) {
form.value.dlLimit = parsedValue * 1024
}
}
}
})
const upLimit = computed({
get: () => {
if (!form.value.upLimit || form.value.upLimit === -1) {
return ''
} else {
return (form.value.upLimit / 1024).toString()
}
},
set: value => {
if (!value) {
form.value.upLimit = undefined
} else {
const parsedValue = parseInt(value)
if (parsedValue > 0) {
form.value.upLimit = parsedValue * 1024
}
}
}
})
const ratioLimit = computed({
get: () => form.value.ratioLimit,
set: val => form.value.ratioLimit = val || undefined
})
const seedingTimeLimit = computed({
get: () => form.value.seedingTimeLimit,
set: val => form.value.seedingTimeLimit = val || undefined
})
const inactiveSeedingTimeLimit = computed({
get: () => form.value.inactiveSeedingTimeLimit,
set: val => form.value.inactiveSeedingTimeLimit = val || undefined
})
function submit() {
if (!isFormValid.value) return
const params: AddTorrentPayload = {
savepath: navbarStore.addTorrentDialogForm.savepath,
paused: !navbarStore.addTorrentDialogForm.startNow,
skip_checking: navbarStore.addTorrentDialogForm.skipChecking,
autoTMM: navbarStore.addTorrentDialogForm.autoTMM,
sequentialDownload: navbarStore.addTorrentDialogForm.sequentialDownload,
firstLastPiecePrio: navbarStore.addTorrentDialogForm.firstLastPiecePrio,
contentLayout: navbarStore.addTorrentDialogForm.contentLayout,
stopCondition: navbarStore.addTorrentDialogForm.stopCondition
}
const promise = maindataStore.addTorrents(form.value, files.value)
.then(() => {
addTorrentStore.resetForm()
close()
})
if (navbarStore.addTorrentDialogUrls.length > 0) params.urls = navbarStore.addTorrentDialogUrls
if (navbarStore.addTorrentDialogForm.category && navbarStore.addTorrentDialogForm.category.name) params.category = navbarStore.addTorrentDialogForm.category.name
if (navbarStore.addTorrentDialogForm.tags.length > 0) params.tags = navbarStore.addTorrentDialogForm.tags.join(',')
isLoading.value = true
await maindataStore.addTorrents(params, navbarStore.addTorrentDialogFiles)
isLoading.value = false
navbarStore.resetAddTorrentDialogForm()
close()
toast.promise(promise, {
pending: t('dialogs.add.pending'),
error: t('dialogs.add.error', addTorrentStore.pendingTorrentsCount),
success: t('dialogs.add.success', addTorrentStore.pendingTorrentsCount)
}, {
autoClose: 1500
})
}
const close = () => {
function close() {
isOpened.value = false
}
const onCategoryChanged = () => {
navbarStore.addTorrentDialogForm.savepath = navbarStore.addTorrentDialogForm.category && navbarStore.addTorrentDialogForm.category.savePath ? navbarStore.addTorrentDialogForm.category.savePath : preferenceStore.preferences!.save_path
form.value.savepath = maindataStore.getCategoryFromName(form.value.category)?.savePath ?? preferenceStore.preferences!.save_path
}
</script>
<template>
<v-dialog v-model="isOpened" :class="$vuetify.display.mobile ? '' : 'w-50'"
<v-dialog v-model="isOpened" :class="$vuetify.display.mobile ? '' : 'w-75'"
:fullscreen="$vuetify.display.mobile" :transition="openSuddenly ? 'none' : 'dialog-bottom-transition'">
<v-card>
<v-card-title>
@ -88,8 +176,8 @@ const onCategoryChanged = () => {
<v-card-text>
<v-row>
<v-col>
<v-file-input v-model="navbarStore.addTorrentDialogFiles" :label="t('dialogs.add.files')"
<v-col cols="12">
<v-file-input v-model="files" :label="t('dialogs.add.files')"
:show-size="vueTorrentStore.useBinarySize ? 1024 : 1000"
accept=".torrent" counter multiple
persistent-clear persistent-hint prepend-icon="" variant="outlined">
@ -102,24 +190,33 @@ const onCategoryChanged = () => {
{{ filename }}
</v-chip>
</template>
<span v-if="fileNames.length == fileOverflowDisplayLimit" class="text-overline text-grey-darken-2 ml-2">
<span v-if="fileNames.length === fileOverflowDisplayLimit + 1"
class="text-overline text-grey-darken-2 ml-2">
{{ t('dialogs.add.fileOverflow', fileNames.length - fileOverflowDisplayLimit) }}
</span>
</template>
</v-file-input>
<v-textarea v-model="navbarStore.addTorrentDialogUrls" :label="t('dialogs.add.links')" clearable>
<v-textarea v-model="urls" :label="t('dialogs.add.links')" clearable>
<template v-slot:prepend>
<v-icon color="accent">mdi-link</v-icon>
</template>
</v-textarea>
<v-text-field v-if="!!urls" v-model="cookie" :label="$t('dialogs.add.cookie')" :placeholder="$t('dialogs.add.cookiePlaceholder')"
clearable>
<template v-slot:prepend>
<v-icon color="accent">mdi-cookie</v-icon>
</template>
</v-text-field>
<v-text-field v-model="rename" :label="$t('dialogs.add.rename')" clearable hide-details>
<template v-slot:prepend>
<v-icon color="accent">mdi-rename</v-icon>
</template>
</v-text-field>
</v-col>
</v-row>
<v-row>
<v-col>
<v-combobox v-model="navbarStore.addTorrentDialogForm.tags" v-model:search="tagSearch"
:hide-no-data="false" :items="maindataStore.tags" :label="t('dialogs.add.tags')" chips clearable
multiple>
<v-col cols="12" md="6">
<v-combobox v-model="tags" v-model:search="tagSearch" :hide-no-data="false" :items="maindataStore.tags" :label="t('dialogs.add.tags')" chips
clearable hide-details multiple>
<template v-slot:prepend>
<v-icon color="accent">mdi-tag</v-icon>
</template>
@ -134,9 +231,12 @@ const onCategoryChanged = () => {
</v-list-item>
</template>
</v-combobox>
<v-combobox v-model="navbarStore.addTorrentDialogForm.category" v-model:search="categorySearch"
:hide-no-data="false" :items="maindataStore.categories" chips clearable
item-title="name" label="Category" return-object @update:modelValue="onCategoryChanged">
</v-col>
<v-col cols="12" md="6">
<v-combobox v-model="category" v-model:search="categorySearch" :hide-no-data="false" :items="categories"
:label="$t('dialogs.add.category')" clearable hide-details
@update:modelValue="onCategoryChanged">
<template v-slot:prepend>
<v-icon color="accent">mdi-label</v-icon>
</template>
@ -152,72 +252,115 @@ const onCategoryChanged = () => {
</template>
</v-combobox>
</v-col>
</v-row>
<v-row>
<v-col>
<v-text-field v-model="navbarStore.addTorrentDialogForm.savepath"
:disabled="navbarStore.addTorrentDialogForm.autoTMM" :label="t('dialogs.add.savePath')">
<v-col cols="12">
<v-text-field v-model="downloadPath" :disabled="form.autoTMM"
:label="t('dialogs.add.downloadPath')" hide-details>
<template v-slot:prepend>
<v-icon color="accent">mdi-folder</v-icon>
<v-icon color="accent">mdi-tray-arrow-down</v-icon>
</template>
</v-text-field>
</v-col>
</v-row>
<v-row>
<v-col>
<v-select v-model="navbarStore.addTorrentDialogForm.contentLayout" :items="contentLayoutOptions"
<v-col cols="12">
<v-text-field v-model="form.savepath" :disabled="form.autoTMM"
:label="t('dialogs.add.savePath')" hide-details>
<template v-slot:prepend>
<v-icon color="accent">mdi-content-save</v-icon>
</template>
</v-text-field>
</v-col>
<v-col cols="12" md="6">
<v-select v-model="form.contentLayout" :items="contentLayoutOptions"
:label="t('constants.contentLayout.title')" color="accent" hide-details rounded="xl"
variant="solo-filled" />
</v-col>
</v-row>
<v-row>
<v-col>
<v-select v-model="navbarStore.addTorrentDialogForm.stopCondition" :items="stopConditionOptions"
<v-col cols="12" md="6">
<v-select v-model="form.stopCondition" :items="stopConditionOptions"
:label="t('constants.stopCondition.title')" color="accent" hide-details rounded="xl"
variant="solo-filled" />
</v-col>
</v-row>
<v-row class="mx-3">
<v-col cols="12" md="6">
<v-checkbox v-model="startNow" :label="t('dialogs.add.startNow')" color="accent"
density="compact" hide-details />
</v-col>
<v-col cols="12" md="6">
<v-checkbox v-model="form.addToTopOfQueue" :label="t('dialogs.add.addToTopOfQueue')" color="accent"
density="compact" hide-details />
</v-col>
<v-col cols="12" md="6">
<v-checkbox v-model="form.skip_checking" :label="t('dialogs.add.skipChecking')" color="accent"
density="compact" hide-details />
</v-col>
<v-col cols="12" md="6">
<v-checkbox v-model="form.autoTMM" :label="t('dialogs.add.autoTMM')" color="accent"
density="compact" hide-details />
</v-col>
<v-col cols="12" md="6">
<v-checkbox v-model="form.sequentialDownload" :label="t('dialogs.add.sequentialDownload')" color="accent"
density="compact" hide-details />
</v-col>
<v-col cols="12" md="6">
<v-checkbox v-model="form.firstLastPiecePrio" :label="t('dialogs.add.firstLastPiecePrio')" color="accent"
density="compact" hide-details />
</v-col>
</v-row>
<v-row>
<v-col>
<v-list>
<v-list-item>
<v-checkbox v-model="navbarStore.addTorrentDialogForm.startNow" :label="t('dialogs.add.startNow')"
density="compact"
hide-details />
</v-list-item>
<v-list-item>
<v-checkbox v-model="navbarStore.addTorrentDialogForm.skipChecking"
:label="t('dialogs.add.skipChecking')" density="compact"
hide-details />
</v-list-item>
<v-list-item>
<v-checkbox v-model="navbarStore.addTorrentDialogForm.autoTMM" :label="t('dialogs.add.autoTMM')"
density="compact"
hide-details />
</v-list-item>
<v-list-item>
<v-checkbox v-model="navbarStore.addTorrentDialogForm.sequentialDownload"
:label="t('dialogs.add.sequentialDownload')" density="compact"
hide-details />
</v-list-item>
<v-list-item>
<v-checkbox v-model="navbarStore.addTorrentDialogForm.firstLastPiecePrio"
:label="t('dialogs.add.firstLastPiecePrio')" density="compact"
hide-details />
</v-list-item>
</v-list>
<v-col cols="12">
<v-expansion-panels>
<v-expansion-panel color="primary" :title="$t('dialogs.add.limitCollapse')">
<v-expansion-panel-text>
<v-row>
<v-col cols="12" md="6">
<v-text-field v-model="dlLimit" :label="$t('dialogs.add.dlLimit')"
hide-details suffix="KiB/s">
<template v-slot:prepend>
<v-icon color="accent">mdi-download</v-icon>
</template>
</v-text-field>
</v-col>
<v-col cols="12" md="6">
<v-text-field v-model="upLimit" :label="$t('dialogs.add.upLimit')"
hide-details suffix="KiB/s">
<template v-slot:prepend>
<v-icon color="accent">mdi-upload</v-icon>
</template>
</v-text-field>
</v-col>
<v-col cols="12" md="4">
<v-text-field v-model="ratioLimit" :hint="$t('dialogs.add.limitHint')"
:label="$t('dialogs.add.ratioLimit')" type="number" />
</v-col>
<v-col cols="12" md="4">
<v-text-field v-model="seedingTimeLimit" :hint="$t('dialogs.add.limitHint')" :label="$t('dialogs.add.seedingTimeLimit')"
:suffix="$t('units.minutes')" type="number" />
</v-col>
<v-col cols="12" md="4">
<v-text-field v-model="inactiveSeedingTimeLimit" :hint="$t('dialogs.add.limitHint')"
:label="$t('dialogs.add.inactiveSeedingTimeLimit')"
:suffix="$t('units.minutes')" type="number" />
</v-col>
</v-row>
</v-expansion-panel-text>
</v-expansion-panel>
</v-expansion-panels>
</v-col>
</v-row>
</v-card-text>
<v-card-actions class="justify-center">
<v-btn :disabled="!isFormValid" :loading="isLoading" color="accent" variant="elevated"
:text="$t('dialogs.add.submit')" @click="submit" />
<v-btn color="error" variant="flat" :text="$t('common.close')" @click="close" />
<v-btn :text="$t('dialogs.add.resetForm')" color="error" variant="flat" @click="addTorrentStore.resetForm()" />
<v-spacer />
<v-btn :disabled="!isFormValid" :text="$t('dialogs.add.submit')" color="accent"
variant="elevated" @click="submit" />
<v-btn :text="$t('common.close')" color="" variant="flat" @click="close" />
</v-card-actions>
</v-card>
</v-dialog>

View file

@ -1,13 +1,14 @@
<script lang="ts" setup>
import { useAddTorrentStore } from '@/stores/addTorrents'
import { useAuthStore } from '@/stores/auth'
import { useNavbarStore } from '@/stores/navbar'
import { useDropZone } from '@vueuse/core'
import { onMounted, onUnmounted, ref } from 'vue'
import { useRoute } from 'vue-router'
const route = useRoute()
const addTorrentStore = useAddTorrentStore()
const authStore = useAuthStore()
const navbarStore = useNavbarStore()
const dndZoneRef = ref<HTMLDivElement>()
function onDragEnter() {
@ -26,8 +27,8 @@ function onDrop(files: File[] | null, event: DragEvent) {
const links = event.dataTransfer.getData('text/plain').split('\n')
.filter(link => link.startsWith('magnet:') || link.startsWith('http'))
torrentFiles.forEach(navbarStore.pushTorrentToQueue)
links.forEach(navbarStore.pushTorrentToQueue)
torrentFiles.forEach(addTorrentStore.pushTorrentToQueue)
links.forEach(addTorrentStore.pushTorrentToQueue)
}
const { isOverDropZone } = useDropZone(dndZoneRef, { onDrop })

View file

@ -19,7 +19,7 @@ const isOnTorrentDetail = computed(() => route.name === 'torrentDetail')
const hashes = computed(() => (isOnTorrentDetail.value ? [route.params.hash as string] : dashboardStore.selectedTorrents))
function openAddTorrentDialog() {
dialogStore.createDialog(AddTorrentDialog, { openSuddenly: true })
dialogStore.createDialog(AddTorrentDialog)
}
async function resumeTorrents() {

View file

@ -0,0 +1,4 @@
export enum TorrentOperatingMode {
AUTO_MANAGED = 'AutoManaged',
FORCED = 'Forced'
}

View file

@ -4,7 +4,8 @@ import { FilterState } from './FilterState'
import { LogType } from './LogType'
import { PieceState } from './PieceState'
import { FilePriority } from './FilePriority'
import { TorrentOperatingMode } from './TorrentOperatingMode'
import { TorrentState } from './TorrentState'
import { TrackerStatus } from './TrackerStatus'
export { AppPreferences, ConnectionStatus, FilterState, LogType, PieceState, FilePriority, TrackerStatus, TorrentState }
export { AppPreferences, ConnectionStatus, FilterState, LogType, PieceState, FilePriority, TrackerStatus, TorrentOperatingMode, TorrentState }

View file

@ -1,7 +1,7 @@
<script setup lang="ts">
import AddTorrentDialog from '@/components/Dialogs/AddTorrentDialog.vue'
import { useDialogStore } from '@/stores/dialog'
import { useNavbarStore } from '@/stores/navbar'
import { useAddTorrentStore } from '@/stores/addTorrents'
import { onBeforeMount } from 'vue'
import { useRoute, useRouter } from 'vue-router'
@ -11,9 +11,9 @@ const router = useRouter()
onBeforeMount(async () => {
const magnetLink = decodeURIComponent(route.params.url as string)
if (magnetLink.startsWith('magnet:')) {
const navbarStore = useNavbarStore()
navbarStore.isAddTorrentDialogFirstInit = false
navbarStore.pushTorrentToQueue(magnetLink)
const addTorrentStore = useAddTorrentStore()
addTorrentStore.isFirstInit = false
addTorrentStore.pushTorrentToQueue(magnetLink)
useDialogStore().createDialog(AddTorrentDialog, {})
}
await router.push({ name: 'dashboard' })

View file

@ -1,7 +1,7 @@
<script lang="ts" setup>
import { useArrayPagination, useSearchQuery } from '@/composables'
import { useAddTorrentStore } from '@/stores/addTorrents'
import { useDialogStore } from '@/stores/dialog'
import { useNavbarStore } from '@/stores/navbar'
import { useRssStore } from '@/stores/rss'
import { RssArticle } from '@/types/vuetorrent'
import debounce from 'lodash.debounce'
@ -11,8 +11,8 @@ import { useRouter } from 'vue-router'
const router = useRouter()
const { t } = useI18n()
const addTorrentStore = useAddTorrentStore()
const dialogStore = useDialogStore()
const navbarStore = useNavbarStore()
const rssStore = useRssStore()
const descriptionDialogVisible = ref(false)
@ -48,7 +48,7 @@ function showDescription(article: RssArticle) {
}
function downloadArticle(item: RssArticle) {
navbarStore.pushTorrentToQueue(item.torrentURL)
addTorrentStore.pushTorrentToQueue(item.torrentURL)
}
async function markAsRead(item: RssArticle) {

View file

@ -1,8 +1,8 @@
<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 { useNavbarStore } from '@/stores/navbar'
import { useSearchEngineStore } from '@/stores/searchEngine'
import { useVueTorrentStore } from '@/stores/vuetorrent'
import { SearchPlugin, SearchResult } from '@/types/qbit/models'
@ -15,8 +15,8 @@ import { useRouter } from 'vue-router'
const router = useRouter()
const { t } = useI18n()
const addTorrentStore = useAddTorrentStore()
const dialogStore = useDialogStore()
const navbarStore = useNavbarStore()
const searchEngineStore = useSearchEngineStore()
const vuetorrentStore = useVueTorrentStore()
@ -82,7 +82,7 @@ function deleteTab() {
}
function downloadTorrent(result: SearchResult) {
navbarStore.pushTorrentToQueue(result.fileUrl)
addTorrentStore.pushTorrentToQueue(result.fileUrl)
}
async function runNewSearch() {

83
src/stores/addTorrents.ts Normal file
View file

@ -0,0 +1,83 @@
import { usePreferenceStore } from '@/stores/preferences'
import { AddTorrentPayload } from '@/types/qbit/payloads'
import { defineStore } from 'pinia'
import { computed, reactive, ref } from 'vue'
export const useAddTorrentStore = defineStore('addTorrents', () => {
const preferenceStore = usePreferenceStore()
const isFirstInit = ref(true)
const files = ref<File[]>([])
const urls = ref<string>('')
const form = reactive<AddTorrentPayload>({})
const pendingTorrentsCount = computed(() => files.value.length + urls.value.split('\n').filter(url => url.trim() !== '').length)
function pushTorrentToQueue(torrentDescriptor: File | string) {
if (torrentDescriptor instanceof File) {
files.value.push(torrentDescriptor)
} else {
if (urls.value !== '') {
urls.value += '\n'
}
urls.value += torrentDescriptor
}
}
function initForm() {
if (isFirstInit.value) {
isFirstInit.value = false
resetForm()
}
}
function resetForm() {
urls.value = ''
files.value = []
form.addToTopOfQueue = preferenceStore.preferences!.add_to_top_of_queue
form.autoTMM = preferenceStore.preferences!.auto_tmm_enabled
form.category = undefined
form.contentLayout = preferenceStore.preferences!.torrent_content_layout
form.cookie = undefined
form.dlLimit = preferenceStore.preferences!.dl_limit
form.downloadPath = preferenceStore.preferences!.temp_path
form.firstLastPiecePrio = false
form.inactiveSeedingTimeLimit = undefined
form.paused = preferenceStore.preferences!.start_paused_enabled
form.ratioLimit = undefined
form.rename = undefined
form.savepath = preferenceStore.preferences!.save_path
form.seedingTimeLimit = undefined
form.sequentialDownload = false
form.skip_checking = false
form.stopCondition = preferenceStore.preferences!.torrent_stop_condition
form.tags = undefined
form.upLimit = preferenceStore.preferences!.up_limit
form.useDownloadPath = preferenceStore.preferences!.temp_path_enabled
}
return {
isFirstInit,
files,
urls,
form,
pendingTorrentsCount,
pushTorrentToQueue,
initForm,
resetForm
}
},
{
persist: {
enabled: true,
strategies: [
{
storage: sessionStorage,
key: 'vuetorrent_addTorrents'
}
]
}
})

View file

@ -82,6 +82,10 @@ export const useMaindataStore = defineStore('maindata', () => {
categories.value = await qbit.getCategories()
}
function getCategoryFromName(categoryName?: string) {
return categories.value.find(c => c.name === categoryName)
}
async function createCategory(category: Category) {
await qbit.createCategory(category)
}
@ -369,6 +373,7 @@ export const useMaindataStore = defineStore('maindata', () => {
getTorrentIndexByHash,
deleteTorrents,
fetchCategories,
getCategoryFromName,
createCategory,
setTorrentCategory,
editCategory,

View file

@ -1,37 +1,12 @@
import { ContentLayout, StopCondition } from '@/constants/qbit/AppPreferences.ts'
import { usePreferenceStore } from '@/stores/preferences.ts'
import { Category } from '@/types/qbit/models'
import { defineStore } from 'pinia'
import { computed, reactive, ref } from 'vue'
import { ref } from 'vue'
export const useNavbarStore = defineStore(
'navbar',
() => {
const preferenceStore = usePreferenceStore()
const downloadData = ref<number[]>(new Array(15).fill(0))
const uploadData = ref<number[]>(new Array(15).fill(0))
const isAddTorrentDialogFirstInit = ref(true)
const addTorrentDialogFiles = ref<File[]>([])
const addTorrentDialogUrls = ref('')
const addTorrentDialogForm = reactive({
autoTMM: false,
skipChecking: false,
sequentialDownload: false,
firstLastPiecePrio: false,
startNow: true,
contentLayout: ContentLayout.ORIGINAL,
stopCondition: StopCondition.NONE,
savepath: '',
category: null as Category | null,
tags: [] as string[]
})
const pendingTorrentsCount = computed(() => addTorrentDialogFiles.value.length + addTorrentDialogUrls.value.split('\n').filter(url => url.trim() !== '').length)
function pushDownloadData(data: number) {
downloadData.value.shift()
downloadData.value.push(data)
@ -42,53 +17,11 @@ export const useNavbarStore = defineStore(
uploadData.value.push(data)
}
function pushTorrentToQueue(torrentDescriptor: File | string) {
if (torrentDescriptor instanceof File) {
addTorrentDialogFiles.value.push(torrentDescriptor)
} else {
if (addTorrentDialogUrls.value !== '') {
addTorrentDialogUrls.value += '\n'
}
addTorrentDialogUrls.value += torrentDescriptor
}
}
function initAddTorrentDialogForm() {
if (isAddTorrentDialogFirstInit.value) {
isAddTorrentDialogFirstInit.value = false
resetAddTorrentDialogForm()
}
}
function resetAddTorrentDialogForm() {
addTorrentDialogUrls.value = ''
addTorrentDialogFiles.value = []
addTorrentDialogForm.autoTMM = preferenceStore.preferences!.auto_tmm_enabled
addTorrentDialogForm.category = null
addTorrentDialogForm.contentLayout = preferenceStore.preferences!.torrent_content_layout
addTorrentDialogForm.firstLastPiecePrio = false
addTorrentDialogForm.savepath = preferenceStore.preferences!.save_path
addTorrentDialogForm.sequentialDownload = false
addTorrentDialogForm.skipChecking = false
addTorrentDialogForm.startNow = !preferenceStore.preferences!.start_paused_enabled
addTorrentDialogForm.stopCondition = preferenceStore.preferences!.torrent_stop_condition
addTorrentDialogForm.tags = []
}
return {
downloadData,
uploadData,
isAddTorrentDialogFirstInit,
addTorrentDialogFiles,
addTorrentDialogUrls,
addTorrentDialogForm,
pendingTorrentsCount,
pushDownloadData,
pushUploadData,
pushTorrentToQueue,
initAddTorrentDialogForm,
resetAddTorrentDialogForm
pushUploadData
}
},
{

View file

@ -22,6 +22,7 @@ export interface NetworkInterface {
}
export default interface AppPreferences {
/** Whether torrents should be placed at the top of the queue when added */
add_to_top_of_queue: boolean
/** List of trackers to add to new torrent */
add_trackers: string
@ -35,6 +36,7 @@ export default interface AppPreferences {
alternative_webui_enabled: boolean
/** File path to the alternative WebUI */
alternative_webui_path: string
/** IP address reported to trackers */
announce_ip: string
/** True always announce to all tiers */
announce_to_all_tiers: boolean
@ -58,10 +60,13 @@ export default interface AppPreferences {
autorun_program: string
/** List of banned IPs */
banned_IPs: string
/** depth limit used when decoding bencoded torrent files */
bdecode_depth_limit: number
/** token limit used when decoding bencoded torrent files */
bdecode_token_limit: number
/** Bittorrent Protocol to use (see list of possible values below) */
/** Bittorrent Protocol to use */
bittorrent_protocol: BitTorrentProtocol
/** Whether peers connection should be blocked on privileged ports (< 1024) */
block_peers_on_privileged_ports: boolean
/** (White)list of ipv4/ipv6 subnets for which webui authentication should be bypassed; list entries are separated by commas */
bypass_auth_subnet_whitelist: string
@ -73,6 +78,7 @@ export default interface AppPreferences {
category_changed_tmm_enabled: boolean
/** Outstanding memory when checking torrents in MiB */
checking_memory_use: number
/** Outgoing connections per second */
connection_speed: number
/** IP Address to bind to. Empty String means All addresses */
current_interface_address: string
@ -86,9 +92,13 @@ export default interface AppPreferences {
disk_cache: number
/** Disk cache expiry interval in seconds */
disk_cache_ttl: number
/** Disk IO read mode */
disk_io_read_mode: DiskIOMode
/** Disk IO type */
disk_io_type: DiskIOType
/** Disk IO write mode */
disk_io_write_mode: DiskIOMode
/** Disk queue size */
disk_queue_size: number
/** Global download speed limit in KiB/s; -1 means no limit is applied */
dl_limit: number
@ -106,6 +116,7 @@ export default interface AppPreferences {
dyndns_username: string
/** Port used for embedded tracker */
embedded_tracker_port: number
/** Enable port forwarding for embedded tracker */
embedded_tracker_port_forwarding: boolean
/** True enables coalesce reads & writes */
enable_coalesce_read_write: boolean
@ -119,22 +130,39 @@ export default interface AppPreferences {
enable_upload_suggestions: boolean
/** See list of possible values here below */
encryption: Encryption
/** File name patterns to automatically exclude on added torrents */
excluded_file_names: string
/** Whether to use the `excluded_file_names` patterns */
excluded_file_names_enabled: boolean
/** Path to directory to copy .torrent files to. Slashes are used as path separators */
export_dir: string
/** Path to directory to copy .torrent files of completed downloads to. Slashes are used as path separators */
export_dir_fin: string
/** Max backup log files retention time */
file_log_age: number
/** File log age unit */
file_log_age_type: FileLogAgeType
/** Enable log file backup */
file_log_backup_enabled: boolean
/** Enable log file auto-delete after a certain period of time */
file_log_delete_old: boolean
/** Enable log files */
file_log_enabled: boolean
/** Log file max size before backing up */
file_log_max_size: number
/** Log files save path */
file_log_path: string
/** File pool size */
file_pool_size: number
/**
* Number of disk I/O threads to use for piece hash verification
* These threads are in addition to the regular disk I/O threads.
*
* These are only used when force rechecking torrent.
* The hash checking done while downloading are done by the regular disk I/O threads
*/
hashing_threads: number
/** Support internationalized domain name (IDN) */
idn_support_enabled: boolean
/** True if ".!qB" should be appended to incomplete files */
incomplete_files_ext: boolean
@ -172,6 +200,7 @@ export default interface AppPreferences {
mail_notification_ssl_enabled: boolean
/** Username for smtp authentication */
mail_notification_username: string
/** Max active checking torrents */
max_active_checking_torrents: number
/** Maximum number of active simultaneous downloads */
max_active_downloads: number
@ -179,12 +208,15 @@ export default interface AppPreferences {
max_active_torrents: number
/** Maximum number of active simultaneous uploads */
max_active_uploads: number
/** Limits the number of concurrent HTTP tracker announces */
max_concurrent_http_announces: number
/** Maximum global number of simultaneous connections */
max_connec: number
/** Maximum number of simultaneous connections per torrent */
max_connec_per_torrent: number
/** Number of minutes to keep seeding a torrent while inactive */
max_inactive_seeding_time: number
/** True enables max inactive seeding time */
max_inactive_seeding_time_enabled: boolean
/** Get the global share ratio limit */
max_ratio: number
@ -200,16 +232,23 @@ export default interface AppPreferences {
max_uploads: number
/** Maximum number of upload slots per torrent */
max_uploads_per_torrent: number
/** Physical memory (RAM) usage limit */
memory_working_set_limit: number
/** Whether trackers should be merged when adding a duplicate torrent */
merge_trackers: boolean
/** Maximal outgoing port (0: Disabled) */
outgoing_ports_max: number
/** Minimal outgoing port (0: Disabled) */
outgoing_ports_min: number
/** Type of service (ToS) for connections to peers */
peer_tos: number
/** Peer turnover disconnect percentage */
peer_turnover: number
/** Peer turnover threshold percentage */
peer_turnover_cutoff: number
/** Peer turnover disconnect interval */
peer_turnover_interval: number
/** Whether to log performance warnings */
performance_warning: boolean
/** True if PeX is enabled */
pex: boolean
@ -217,17 +256,21 @@ export default interface AppPreferences {
preallocate_all: boolean
/** True proxy requires authentication; doesn't apply to SOCKS4 proxies */
proxy_auth_enabled: boolean
/** Whether to use proxy for BitTorrent purposes */
proxy_bittorrent: boolean
/** Perform hostname lookup via proxy */
proxy_hostname_lookup: boolean
/** Proxy IP address or domain name */
proxy_ip: string
/** Whether to use proxy for general purposes */
proxy_misc: boolean
/** Password for proxy authentication */
proxy_password: string
/** True if peer and web seed connections should be proxified; this option will have any effect only in qBittorent built against libtorrent version 0.16.X and higher */
/** Whether to use proxy for peer connections */
proxy_peer_connections: boolean
/** Proxy port */
proxy_port: number
/** Whether to use proxy for RSS purposes */
proxy_rss: boolean
/** See list of possible values here below */
proxy_type: ProxyType
@ -237,13 +280,17 @@ export default interface AppPreferences {
queueing_enabled: boolean
/** True if the port is randomly selected */
random_port: boolean
/** Reannounce to all trackers when IP or port changed */
reannounce_when_address_changed: boolean
/** True rechecks torrents on completion */
recheck_completed_torrents: boolean
/** Refresh interval (in ms) */
refresh_interval: number
/** Maximum outstanding requests to a single peer */
request_queue_size: number
/** True resolves peer countries */
resolve_peer_countries: boolean
/** Resume data storage type */
resume_data_storage_type: ResumeDataStorageType
/** Enable auto-downloading of torrents from the RSS feeds */
rss_auto_downloading_enabled: boolean
@ -291,8 +338,11 @@ export default interface AppPreferences {
slow_torrent_ul_rate_threshold: number
/** Socket backlog size */
socket_backlog_size: number
/** Socket receive buffer size */
socket_receive_buffer_size: number
/** Socket send buffer size */
socket_send_buffer_size: number
/** Server-side request forgery (SSRF) mitigation **/
ssrf_mitigation: boolean
/** True if torrents should be added in a Paused state */
start_paused_enabled: boolean
@ -306,6 +356,7 @@ export default interface AppPreferences {
torrent_changed_tmm_enabled: boolean
/** Default content layout to select when adding a new torrent */
torrent_content_layout: ContentLayout
/** .torrent file size limit */
torrent_file_size_limit: number
/** Default stop condition to select when adding a new torrent */
torrent_stop_condition: StopCondition
@ -319,12 +370,15 @@ export default interface AppPreferences {
upnp: boolean
/** UPnP lease duration (0: Permanent lease) */
upnp_lease_duration: number
/** Whether to use category save path when autoTMM isn't enabled */
use_category_paths_in_manual_mode: boolean
/** True if WebUI HTTPS access is enabled */
use_https: boolean
/** Whether to use subcategories */
use_subcategories: boolean
/** μTP-TCP mixed mode algorithm (see list of possible values below) */
utp_tcp_mixed_mode: UtpTcpMixedMode
/** Validate HTTPS tracker certificates */
validate_https_tracker_certificate: boolean
/** IP address to use for the WebUI */
web_ui_address: string
@ -350,7 +404,9 @@ export default interface AppPreferences {
web_ui_password?: string
/** WebUI port */
web_ui_port: number
/** Trusted proxies list */
web_ui_reverse_proxies_list: string
/** Enable reverse proxy support */
web_ui_reverse_proxy_enabled: boolean
/** True if WebUI cookie Secure flag is enabled */
web_ui_secure_cookie_enabled: boolean

View file

@ -1,34 +1,72 @@
import { AppPreferences } from '@/constants/qbit'
export default interface FeedRule {
/** Add matched torrent in paused mode */
addPaused: boolean | null
/** The feed URLs the rule applied to */
affectedFeeds: string[]
/** Assign category to the torrent */
assignedCategory: string
/** The feed URLs the rule applies to */
affectedFeeds?: string[]
/** Whether the rule is enabled */
enabled: boolean
/** Episode filter definition */
episodeFilter: string
/** Ignore subsequent rule matches */
ignoreDays: number
/** The rule last match time */
lastMatch: string
/** The substring that the torrent name must contain */
mustContain: string
/** The substring that the torrent name must not contain */
mustNotContain: string
/** The rule name, !! injected by VueTorrent !! */
enabled?: boolean
/**
* Matches articles based on episode filter.
*
* Example: 1x2;8-15;5;30-; will match 2, 5, 8 through 15, 30 and onward episodes of season one
*
* Episode filter rules:
*
* - Season number is a mandatory non-zero value
* - Episode number is a mandatory positive value
* - Filter must end with semicolon
* - Three range types for episodes are supported:
* - * Single number: 1x25; matches episode 25 of season one
* - * Normal range: 1x25-40; matches episodes 25 through 40 of season one
* - * Infinite range: 1x25-; matches episodes 25 and upward of season one, and all episodes of later seasons
*/
episodeFilter?: string
/**
* Ignore articles where its date is within n days
* Values less than 1 will be ignored
*/
ignoreDays?: number
/**
* The rule last match time
* Must match RFC-2822 or ISO-8601 date format
* source: https://www.rfc-editor.org/rfc/rfc2822#page-14
*/
lastMatch?: string
/**
* Wildcard mode: you can use
*
* - ? to match any single character
* - \* to match zero or more of any characters
* - Whitespaces count as AND operators (all words, any order)
* - | is used as OR operator
*
* If word order is important use * instead of whitespace.
*
* An expression with an empty | clause (e.g. expr|) will match all articles.
*/
mustContain?: string
/**
* Wildcard mode: you can use
*
* - ? to match any single character
* - \* to match zero or more of any characters
* - Whitespaces count as AND operators (all words, any order)
* - | is used as OR operator
*
* If word order is important use * instead of whitespace.
*
* An expression with an empty | clause (e.g. expr|) will exclude all articles.
*/
mustNotContain?: string
/** The rule name */
name?: string
/** The list of episode IDs already matched by smart filter */
previouslyMatchedEpisodes?: unknown[]
/** Save torrent to the given directory */
savePath: string
/** Enable smart episode filter */
smartFilter: boolean
/** Torrent content layout to use with matched torrent */
torrentContentLayout: AppPreferences.ContentLayout | null
/** The list of episodes already matched by smart filter */
previouslyMatchedEpisodes?: string[]
/** The rule priority */
priority?: number
/**
* Smart Episode Filter will check the episode number to prevent downloading of duplicates.
* Supports the formats: S01E01, 1x1, 2017.12.31 and 31.12.2017 (Date formats also support - as a separator)
*/
smartFilter?: boolean
/** Enable regex mode in "mustContain" and "mustNotContain" */
useRegex: boolean
useRegex?: boolean
}

View file

@ -1,34 +1,49 @@
import { ContentLayout, StopCondition } from '@/constants/qbit/AppPreferences'
export default interface AddTorrentPayload {
/** Whether to add the torrent at the top of the queue */
addToTopOfQueue?: boolean
/** Whether Automatic Torrent Management should be used */
autoTMM?: boolean
/** Category for the torrent */
category?: string
/** Content layout used when creating the torrent */
contentLayout?: ContentLayout
/** Cookie sent to download the .torrent file */
/** Cookie sent to download the files using HTTP(S) */
cookie?: string
/** Set torrent download speed limit. Unit in bytes/second */
dlLimit?: number
/**
* If enabled and set, will use this location to save torrent content when downloading
* Otherwise, use `savepath` or default save path
*/
downloadPath?: string
/** Prioritize download first last piece. Possible values are true, false (default) */
/** Prioritize download first last piece */
firstLastPiecePrio?: boolean
/**
* Set inactive torrent seeding time limit. Unit in minutes
* - -1 to disable
* - -2 to use global value
*/
inactiveSeedingTimeLimit?: number
/** Add torrents in the paused state. Possible values are true, false (default) */
/** Add torrents in the paused state */
paused?: boolean
/** Set torrent share ratio limit */
ratioLimit?: number
/** Rename torrent */
rename?: string
/** Download folder */
/**
* Will use this location to save torrent content when download is complete
* It will also be used when `downloadPath` is disabled or not set
*/
savepath?: string
/** Set torrent seeding time limit. Unit in minutes */
seedingTimeLimit?: number
/** Enable sequential download. Possible values are true, false (default) */
/** Enable sequential download */
sequentialDownload?: boolean
/** Skip hash checking. Possible values are true, false (default) */
/** Skip hash checking */
skip_checking?: boolean
/** Torrent stop condition */
stopCondition?: StopCondition
/** Tags for the torrent, split by ',' */
tags?: string
@ -36,5 +51,6 @@ export default interface AddTorrentPayload {
upLimit?: number
/** URLs separated with newlines */
urls?: string
/** Whether to enable use of `downloadPath` */
useDownloadPath?: boolean
}