mirror of
https://github.com/VueTorrent/VueTorrent.git
synced 2025-03-14 03:59:53 +03:00
feat(filters): Allow multiple selection (#1202)
This commit is contained in:
parent
e40848d2cb
commit
41318c3d85
13 changed files with 232 additions and 175 deletions
|
@ -1,90 +1,134 @@
|
|||
<script setup lang="ts">
|
||||
import { FilterState } from '@/constants/qbit'
|
||||
import { useDashboardStore } from '@/stores/dashboard'
|
||||
<script lang="ts" setup>
|
||||
import { TorrentState } from '@/constants/qbit'
|
||||
import { useMaindataStore } from '@/stores/maindata'
|
||||
import { useVueTorrentStore } from '@/stores/vuetorrent'
|
||||
import { computed } from 'vue'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { computed, toRefs } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const { t } = useI18n()
|
||||
const dashboardStore = useDashboardStore()
|
||||
const maindataStore = useMaindataStore()
|
||||
const { categories: _categories, tags: _tags, trackers: _trackers, filters } = storeToRefs(useMaindataStore())
|
||||
const { statusFilter, categoryFilter, tagFilter, trackerFilter } = toRefs(filters.value)
|
||||
const vueTorrentStore = useVueTorrentStore()
|
||||
|
||||
const statusOptions = [
|
||||
{ title: t('navbar.side.filters.disabled'), value: FilterState.ALL },
|
||||
{ title: t('constants.filterStatus.downloading'), value: FilterState.DOWNLOADING },
|
||||
{ title: t('constants.filterStatus.seeding'), value: FilterState.SEEDING },
|
||||
{ title: t('constants.filterStatus.completed'), value: FilterState.COMPLETED },
|
||||
{ title: t('constants.filterStatus.resumed'), value: FilterState.RESUMED },
|
||||
{ title: t('constants.filterStatus.paused'), value: FilterState.PAUSED },
|
||||
{ title: t('constants.filterStatus.active'), value: FilterState.ACTIVE },
|
||||
{ title: t('constants.filterStatus.inactive'), value: FilterState.INACTIVE },
|
||||
{ title: t('constants.filterStatus.stalled'), value: FilterState.STALLED },
|
||||
{ title: t('constants.filterStatus.stalled_uploading'), value: FilterState.STALLED_UPLOADING },
|
||||
{ title: t('constants.filterStatus.stalled_downloading'), value: FilterState.STALLED_DOWNLOADING },
|
||||
{ title: t('constants.filterStatus.checking'), value: FilterState.CHECKING },
|
||||
{ title: t('constants.filterStatus.moving'), value: FilterState.MOVING },
|
||||
{ title: t('constants.filterStatus.errored'), value: FilterState.ERRORED }
|
||||
]
|
||||
const statuses = computed(() => Object.values(TorrentState).map(state => (
|
||||
{ title: t(`torrent.state.${ state }`), value: state }
|
||||
)))
|
||||
const categories = computed(() => [
|
||||
{ title: t('navbar.side.filters.uncategorized'), value: '' },
|
||||
..._categories.value.map(c => ({ title: c.name, value: c.name }))
|
||||
])
|
||||
const tags = computed(() => [
|
||||
{ title: t('navbar.side.filters.untagged'), value: null },
|
||||
..._tags.value.map(tag => ({ title: tag, value: tag }))
|
||||
])
|
||||
const trackers = computed(() => [
|
||||
{ title: t('navbar.side.filters.untracked'), value: '' },
|
||||
..._trackers.value.map(tracker => ({ title: tracker, value: tracker }))
|
||||
])
|
||||
|
||||
const categories = computed(() => {
|
||||
const categories = [
|
||||
{ title: t('navbar.side.filters.disabled'), value: null },
|
||||
{ title: t('navbar.side.filters.uncategorized'), value: '' }
|
||||
]
|
||||
categories.push(...maindataStore.categories.map(c => ({ title: c.name, value: c.name })))
|
||||
return categories
|
||||
})
|
||||
const tags = computed(() => {
|
||||
const tags = [
|
||||
{ title: t('navbar.side.filters.disabled'), value: null },
|
||||
{ title: t('navbar.side.filters.untagged'), value: '' }
|
||||
]
|
||||
tags.push(...maindataStore.tags.map(tag => ({ title: tag, value: tag })))
|
||||
return tags
|
||||
})
|
||||
const trackers = computed(() => {
|
||||
const trackers = [{ title: t('navbar.side.filters.disabled'), value: null as string | null }]
|
||||
trackers.push(...maindataStore.trackers.map(tag => ({ title: tag, value: tag })))
|
||||
return trackers
|
||||
})
|
||||
function selectAllStatuses() {
|
||||
statusFilter.value = []
|
||||
}
|
||||
|
||||
function selectAllCategories() {
|
||||
categoryFilter.value = []
|
||||
}
|
||||
|
||||
function selectAllTags() {
|
||||
tagFilter.value = []
|
||||
}
|
||||
|
||||
function selectAllTrackers() {
|
||||
trackerFilter.value = []
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-list class="pb-0">
|
||||
<v-list-item class="px-0 pb-3">
|
||||
<v-list-item-title class="px-0 text-uppercase white--text ml-1 font-weight-normal text-caption">Status</v-list-item-title>
|
||||
<v-select
|
||||
v-model="dashboardStore.sortOptions.statusFilter"
|
||||
:items="statusOptions"
|
||||
class="text-accent pt-1"
|
||||
hide-details
|
||||
density="compact"
|
||||
variant="solo"
|
||||
bg-color="secondary" />
|
||||
<v-list-item-title class="px-0 text-uppercase white--text ml-1 font-weight-normal text-caption">
|
||||
{{ t('navbar.side.filters.state') }}
|
||||
</v-list-item-title>
|
||||
<v-select v-model="statusFilter" :items="statuses" bg-color="secondary"
|
||||
:placeholder="t('navbar.side.filters.disabled')"
|
||||
class="text-accent pt-1" density="compact" hide-details multiple variant="solo">
|
||||
<template v-slot:prepend-item>
|
||||
<v-list-item :title="$t('common.disable')" @click="selectAllStatuses" />
|
||||
<v-divider />
|
||||
</template>
|
||||
<template v-slot:selection="{ item, index }">
|
||||
<span v-if="index === 0 && statusFilter.length === 1"
|
||||
class="text-accent">{{ t(`torrent.state.${ item.props.value }`) }}</span>
|
||||
<span v-else-if="index === 0" class="text-accent">{{
|
||||
t('navbar.side.filters.activeFilter', statusFilter.length)
|
||||
}}</span>
|
||||
</template>
|
||||
</v-select>
|
||||
</v-list-item>
|
||||
|
||||
<v-list-item class="px-0 pb-3">
|
||||
<v-list-item-title class="px-0 text-uppercase white--text ml-1 font-weight-light text-subtitle-2">Category</v-list-item-title>
|
||||
<v-select
|
||||
v-model="dashboardStore.sortOptions.categoryFilter"
|
||||
:items="categories"
|
||||
class="text-accent pt-1"
|
||||
hide-details
|
||||
density="compact"
|
||||
variant="solo"
|
||||
bg-color="secondary" />
|
||||
<v-list-item-title class="px-0 text-uppercase white--text ml-1 font-weight-light text-subtitle-2">
|
||||
{{ t('navbar.side.filters.category') }}
|
||||
</v-list-item-title>
|
||||
<v-select v-model="categoryFilter" :items="categories" bg-color="secondary"
|
||||
:placeholder="t('navbar.side.filters.disabled')"
|
||||
class="text-accent pt-1" density="compact" hide-details multiple variant="solo">
|
||||
<template v-slot:prepend-item>
|
||||
<v-list-item :title="$t('common.disable')" @click="selectAllCategories" />
|
||||
<v-divider />
|
||||
</template>
|
||||
<template v-slot:selection="{ item, index }">
|
||||
<span v-if="index === 0 && categoryFilter.length === 1"
|
||||
class="text-accent">{{ item.props.title }}</span>
|
||||
<span v-else-if="index === 0" class="text-accent">{{
|
||||
t('navbar.side.filters.activeFilter', categoryFilter.length)
|
||||
}}</span>
|
||||
</template>
|
||||
</v-select>
|
||||
</v-list-item>
|
||||
|
||||
<v-list-item class="px-0 pb-3">
|
||||
<v-list-item-title class="px-0 text-uppercase white--text ml-1 font-weight-light text-subtitle-2">Tags</v-list-item-title>
|
||||
<v-select v-model="dashboardStore.sortOptions.tagFilter" :items="tags" class="text-accent pt-1" hide-details density="compact" variant="solo" bg-color="secondary" />
|
||||
<v-list-item-title class="px-0 text-uppercase white--text ml-1 font-weight-light text-subtitle-2">
|
||||
{{ t('navbar.side.filters.tag') }}
|
||||
</v-list-item-title>
|
||||
<v-select v-model="tagFilter" :items="tags" bg-color="secondary"
|
||||
:placeholder="t('navbar.side.filters.disabled')"
|
||||
class="text-accent pt-1" density="compact" hide-details multiple variant="solo">
|
||||
<template v-slot:prepend-item>
|
||||
<v-list-item :title="$t('common.disable')" @click="selectAllTags" />
|
||||
<v-divider />
|
||||
</template>
|
||||
<template v-slot:selection="{ item, index }">
|
||||
<span v-if="index === 0 && tagFilter.length === 1"
|
||||
class="text-accent">{{ item.props.title }}</span>
|
||||
<span v-else-if="index === 0" class="text-accent">{{
|
||||
t('navbar.side.filters.activeFilter', tagFilter.length)
|
||||
}}</span>
|
||||
</template>
|
||||
</v-select>
|
||||
</v-list-item>
|
||||
|
||||
<v-list-item :class="{ 'px-0': true, 'pb-3': vueTorrentStore.showTrackerFilter }" v-if="vueTorrentStore.showTrackerFilter">
|
||||
<v-list-item-title class="px-0 text-uppercase white--text ml-1 font-weight-light text-subtitle-2">Tracker</v-list-item-title>
|
||||
<v-select v-model="dashboardStore.sortOptions.trackerFilter" :items="trackers" class="text-accent pt-1" hide-details density="compact" variant="solo" bg-color="secondary" />
|
||||
<v-list-item v-if="vueTorrentStore.showTrackerFilter"
|
||||
:class="{ 'px-0': true, 'pb-3': vueTorrentStore.showTrackerFilter }">
|
||||
<v-list-item-title class="px-0 text-uppercase white--text ml-1 font-weight-light text-subtitle-2">
|
||||
{{ t('navbar.side.filters.tracker') }}
|
||||
</v-list-item-title>
|
||||
<v-select v-model="trackerFilter" :items="trackers" bg-color="secondary"
|
||||
:placeholder="t('navbar.side.filters.disabled')"
|
||||
class="text-accent pt-1" density="compact" hide-details multiple variant="solo">
|
||||
<template v-slot:prepend-item>
|
||||
<v-list-item :title="$t('common.disable')" @click="selectAllTrackers" />
|
||||
<v-divider />
|
||||
</template>
|
||||
<template v-slot:selection="{ item, index }">
|
||||
<span v-if="index === 0 && trackerFilter.length === 1"
|
||||
class="text-accent">{{ item.props.title }}</span>
|
||||
<span v-else-if="index === 0" class="text-accent">{{
|
||||
t('navbar.side.filters.activeFilter', trackerFilter.length)
|
||||
}}</span>
|
||||
</template>
|
||||
</v-select>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</template>
|
||||
|
|
|
@ -1,17 +1,20 @@
|
|||
<script setup lang="ts">
|
||||
import { FilterState } from '@/constants/qbit'
|
||||
import { useDashboardStore } from '@/stores/dashboard'
|
||||
import { computed } from 'vue'
|
||||
import { useMaindataStore } from '@/stores/maindata.ts'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { computed, toRefs } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const { t } = useI18n()
|
||||
const dashboardStore = useDashboardStore()
|
||||
const { filters } = storeToRefs(useMaindataStore())
|
||||
const { statusFilter, categoryFilter, tagFilter, trackerFilter } = toRefs(filters.value)
|
||||
|
||||
const isTextFilterActive = computed(() => dashboardStore.searchFilter?.length > 0)
|
||||
const isStatusFilterActive = computed(() => dashboardStore.sortOptions.statusFilter !== FilterState.ALL)
|
||||
const isCategoryFilterActive = computed(() => dashboardStore.sortOptions.categoryFilter !== null)
|
||||
const isTagFilterActive = computed(() => dashboardStore.sortOptions.tagFilter !== null)
|
||||
const isTrackerFilterActive = computed(() => dashboardStore.sortOptions.trackerFilter !== null)
|
||||
const isStatusFilterActive = computed(() => statusFilter.value.length > 0)
|
||||
const isCategoryFilterActive = computed(() => categoryFilter.value.length > 0)
|
||||
const isTagFilterActive = computed(() => tagFilter.value.length > 0)
|
||||
const isTrackerFilterActive = computed(() => trackerFilter.value.length > 0)
|
||||
|
||||
const filterCount = computed(
|
||||
() =>
|
||||
|
@ -21,37 +24,73 @@ const filterCount = computed(
|
|||
Number(isTagFilterActive.value) +
|
||||
Number(isTrackerFilterActive.value)
|
||||
)
|
||||
|
||||
// TODO
|
||||
function resetAllFilters() {
|
||||
dashboardStore.searchFilter = ''
|
||||
statusFilter.value = []
|
||||
categoryFilter.value = []
|
||||
tagFilter.value = []
|
||||
trackerFilter.value = []
|
||||
}
|
||||
function resetTextFilter() {
|
||||
dashboardStore.searchFilter = ''
|
||||
}
|
||||
function resetStatusFilter() {
|
||||
statusFilter.value = []
|
||||
}
|
||||
function resetCategoryFilter() {
|
||||
categoryFilter.value = []
|
||||
}
|
||||
function resetTagFilter() {
|
||||
tagFilter.value = []
|
||||
}
|
||||
function resetTrackerFilter() {
|
||||
trackerFilter.value = []
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-menu open-on-click open-on-hover open-delay="0" close-delay="0">
|
||||
<v-menu close-delay="0" open-delay="0" open-on-click open-on-hover>
|
||||
<template v-slot:activator="{ props }">
|
||||
<v-slide-x-transition>
|
||||
<v-chip v-if="filterCount > 0" v-bind="props" class="ml-6" color="primary" variant="elevated">
|
||||
<v-chip v-if="filterCount > 0" v-bind="props" class="ml-6" color="primary" variant="elevated" closable @click:close="resetAllFilters()">
|
||||
{{ t('navbar.top.active_filters.menu_label', filterCount) }}
|
||||
</v-chip>
|
||||
</v-slide-x-transition>
|
||||
</template>
|
||||
|
||||
<div class="d-flex flex-column gap mt-3">
|
||||
<v-chip v-if="isTextFilterActive" variant="elevated" color="grey">
|
||||
<v-chip v-if="isTextFilterActive" variant="elevated" color="grey" closable @click:close="resetTextFilter()">
|
||||
{{ t('navbar.top.active_filters.text', { value: dashboardStore.searchFilter }) }}
|
||||
</v-chip>
|
||||
<v-chip v-if="isStatusFilterActive" variant="elevated" :color="'state-' + dashboardStore.sortOptions.statusFilter">
|
||||
{{ t('navbar.top.active_filters.status', { value: t(`constants.filterStatus.${dashboardStore.sortOptions.statusFilter}`) }) }}
|
||||
|
||||
<v-chip v-if="isStatusFilterActive && statusFilter.length === 1" :color="'torrent-' + statusFilter[0]" variant="elevated" closable @click:close="resetStatusFilter()">
|
||||
{{ t('navbar.top.active_filters.state', { value: t(`torrent.state.${statusFilter[0]}`) }) }}
|
||||
</v-chip>
|
||||
<v-chip v-if="isCategoryFilterActive" variant="elevated" color="category">
|
||||
{{
|
||||
t('navbar.top.active_filters.category', {
|
||||
value: dashboardStore.sortOptions.categoryFilter === '' ? t('navbar.side.filters.uncategorized') : dashboardStore.sortOptions.categoryFilter
|
||||
})
|
||||
}}
|
||||
<v-chip v-else-if="isStatusFilterActive" variant="elevated" closable @click:close="resetStatusFilter()">
|
||||
{{ t('navbar.top.active_filters.multiple_state', statusFilter.length) }}
|
||||
</v-chip>
|
||||
<v-chip v-if="isTagFilterActive" variant="elevated" color="tag">
|
||||
{{ t('navbar.top.active_filters.tag', { value: dashboardStore.sortOptions.tagFilter === '' ? t('navbar.side.filters.untagged') : dashboardStore.sortOptions.tagFilter }) }}
|
||||
|
||||
<v-chip v-if="isCategoryFilterActive && categoryFilter.length === 1" color="category" variant="elevated" closable @click:close="resetCategoryFilter()">
|
||||
{{ t('navbar.top.active_filters.category', { value: categoryFilter[0] === '' ? t('navbar.side.filters.uncategorized') : categoryFilter[0] }) }}
|
||||
</v-chip>
|
||||
<v-chip v-if="isTrackerFilterActive" variant="elevated" color="tracker">
|
||||
{{ t('navbar.top.active_filters.tracker', { value: dashboardStore.sortOptions.trackerFilter }) }}
|
||||
<v-chip v-else-if="isCategoryFilterActive" color="category" variant="elevated" closable @click:close="resetCategoryFilter()">
|
||||
{{ t('navbar.top.active_filters.multiple_category', categoryFilter.length) }}
|
||||
</v-chip>
|
||||
|
||||
<v-chip v-if="isTagFilterActive && tagFilter.length === 1" color="tag" variant="elevated" closable @click:close="resetTagFilter()">
|
||||
{{ t('navbar.top.active_filters.tag', { value: tagFilter[0] === null ? t('navbar.side.filters.untagged') : tagFilter[0] }) }}
|
||||
</v-chip>
|
||||
<v-chip v-else-if="isTagFilterActive" color="tag" variant="elevated" closable @click:close="resetTagFilter()">
|
||||
{{ t('navbar.top.active_filters.multiple_tag', tagFilter.length) }}
|
||||
</v-chip>
|
||||
|
||||
<v-chip v-if="isTrackerFilterActive && trackerFilter.length === 1" color="tracker" variant="elevated" closable @click:close="resetTrackerFilter()">
|
||||
{{ t('navbar.top.active_filters.tracker', { value: trackerFilter[0] === '' ? t('navbar.side.filters.untracked') : trackerFilter[0] }) }}
|
||||
</v-chip>
|
||||
<v-chip v-else-if="isTrackerFilterActive" color="tracker" variant="elevated" closable @click:close="resetTrackerFilter()">
|
||||
{{ t('navbar.top.active_filters.multiple_tracker', trackerFilter.length) }}
|
||||
</v-chip>
|
||||
</div>
|
||||
</v-menu>
|
||||
|
|
|
@ -77,12 +77,12 @@ async function renderTorrentPieceStates() {
|
|||
let rectWidth = 1
|
||||
|
||||
for (let i = 0; i < pieces.length; ++i) {
|
||||
const status = pieces[i]
|
||||
const state = pieces[i]
|
||||
let newColor = ''
|
||||
|
||||
if (status === PieceState.DOWNLOADING) newColor = theme.current.value.colors['torrent-downloading']
|
||||
else if (status === PieceState.DOWNLOADED) newColor = theme.current.value.colors['torrent-pausedUP']
|
||||
else if (status === PieceState.MISSING) {
|
||||
if (state === PieceState.DOWNLOADING) newColor = theme.current.value.colors['torrent-downloading']
|
||||
else if (state === PieceState.DOWNLOADED) newColor = theme.current.value.colors['torrent-pausedUP']
|
||||
else if (state === PieceState.MISSING) {
|
||||
const selected_piece_ranges = files.value.filter(file => file.priority !== FilePriority.DO_NOT_DOWNLOAD).map(file => file.piece_range)
|
||||
for (const [min_piece_range, max_piece_range] of selected_piece_ranges) {
|
||||
if (i > min_piece_range && i < max_piece_range) {
|
||||
|
|
|
@ -13,4 +13,4 @@ export enum FilterState {
|
|||
CHECKING = 'checking',
|
||||
MOVING = 'moving',
|
||||
ERRORED = 'errored'
|
||||
}
|
||||
}
|
|
@ -7,4 +7,4 @@ import { FilePriority } from './FilePriority'
|
|||
import { TorrentState } from './TorrentState'
|
||||
import { TrackerStatus } from './TrackerStatus'
|
||||
|
||||
export { AppPreferences, ConnectionStatus, LogType, PieceState, FilePriority, TrackerStatus, TorrentState, FilterState }
|
||||
export { AppPreferences, ConnectionStatus, FilterState, LogType, PieceState, FilePriority, TrackerStatus, TorrentState }
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
"yes": "Yes",
|
||||
"no": "No",
|
||||
"save": "Save",
|
||||
"disable": "Disable",
|
||||
"selectNone": "Select None",
|
||||
"selectAll": "Select All",
|
||||
"useGlobalSettings": "Use Global Settings",
|
||||
|
@ -374,10 +375,14 @@
|
|||
"active_filters": {
|
||||
"menu_label": "Active filter: {n} | Active filters: {n}",
|
||||
"text": "Text filter: {value}",
|
||||
"status": "Status filter: {value}",
|
||||
"state": "State filter: {value}",
|
||||
"multiple_state": "State filter: {n} states",
|
||||
"category": "Category filter: {value}",
|
||||
"multiple_category": "Category filter: {n} categories",
|
||||
"tag": "Tag filter: {value}",
|
||||
"tracker": "Tracker filter: {value}"
|
||||
"multiple_tag": "Tag filter: {n} tags",
|
||||
"tracker": "Tracker filter: {value}",
|
||||
"multiple_tracker": "Tracker filter: {n} trackers"
|
||||
}
|
||||
},
|
||||
"side": {
|
||||
|
@ -389,9 +394,14 @@
|
|||
},
|
||||
"filters": {
|
||||
"disabled": "(Disabled)",
|
||||
"state": "Torrent State Filter",
|
||||
"category": "Category Filter",
|
||||
"uncategorized": "(Uncategorized)",
|
||||
"tag": "Tag Filter",
|
||||
"untagged": "(Untagged)",
|
||||
"untracked": "(Untracked)"
|
||||
"tracker": "Tracker Filter",
|
||||
"untracked": "(Untracked)",
|
||||
"activeFilter": "{n} filters active"
|
||||
},
|
||||
"bottom_actions": {
|
||||
"logout": "Logout",
|
||||
|
@ -1026,22 +1036,6 @@
|
|||
"always": "Always",
|
||||
"never": "Never"
|
||||
},
|
||||
"filterStatus": {
|
||||
"all": "All",
|
||||
"downloading": "Downloading",
|
||||
"seeding": "Seeding",
|
||||
"completed": "Completed",
|
||||
"resumed": "Resumed",
|
||||
"paused": "Paused",
|
||||
"active": "Active",
|
||||
"inactive": "Inactive",
|
||||
"stalled": "Stalled",
|
||||
"stalled_uploading": "Stalled Uploading",
|
||||
"stalled_downloading": "Stalled Downloading",
|
||||
"checking": "Checking",
|
||||
"moving": "Moving",
|
||||
"errored": "Errored"
|
||||
},
|
||||
"connectionStatus": {
|
||||
"connected": "Connected",
|
||||
"firewalled": "Firewalled",
|
||||
|
|
|
@ -51,7 +51,7 @@ const formatLogTimestamp = (log: Log) => {
|
|||
return dayjs(log.timestamp * 1000).format(vueTorrentStore.dateFormat)
|
||||
}
|
||||
const toggleSelectAll = () => {
|
||||
if (logTypeFilter.value.length === logTypeOptions.value.length) {
|
||||
if (allTypesSelected.value) {
|
||||
logTypeFilter.value = []
|
||||
} else {
|
||||
logTypeFilter.value = logTypeOptions.value.map(option => option.value)
|
||||
|
|
|
@ -20,21 +20,6 @@ const variables = {
|
|||
tag: '#048B9A',
|
||||
tracker: '#C97D09',
|
||||
|
||||
// State filter colors
|
||||
'state-downloading': '#5BB974',
|
||||
'state-stalled_downloading': '#5BB974',
|
||||
'state-seeding': '#4ECDE6',
|
||||
'state-stalled_uploading': '#4ECDE6',
|
||||
'state-completed': '#16573E',
|
||||
'state-resumed': '#BDBDBD',
|
||||
'state-active': '#BDBDBD',
|
||||
'state-stalled': '#696969',
|
||||
'state-paused': '#696969',
|
||||
'state-inactive': '#696969',
|
||||
'state-checking': '#FF7043',
|
||||
'state-moving': '#FFAA2C',
|
||||
'state-errored': '#F83E70',
|
||||
|
||||
// Torrent state colors
|
||||
'torrent-error': '#F83E70',
|
||||
'torrent-missingFiles': '#F83E70',
|
||||
|
|
|
@ -86,7 +86,7 @@ export class QBitApi {
|
|||
return this.execute('/transfer/toggleSpeedLimitsMode')
|
||||
}
|
||||
|
||||
async getTorrents(payload: GetTorrentPayload): Promise<Torrent[]> {
|
||||
async getTorrents(payload?: GetTorrentPayload): Promise<Torrent[]> {
|
||||
return this.axios.get('/torrents/info', { params: payload }).then(r => r.data)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
import { useSearchQuery } from '@/composables'
|
||||
import { FilterState } from '@/constants/qbit'
|
||||
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 { useI18n } from 'vue-i18n'
|
||||
|
@ -21,18 +19,14 @@ export const useDashboardStore = defineStore(
|
|||
const sortOptions = reactive({
|
||||
isCustomSortEnabled: false,
|
||||
sortBy: SortOptions.DEFAULT,
|
||||
reverseOrder: false,
|
||||
statusFilter: FilterState.ALL as FilterState,
|
||||
categoryFilter: null as string | null,
|
||||
tagFilter: null as string | null,
|
||||
trackerFilter: null as string | null
|
||||
reverseOrder: false
|
||||
})
|
||||
|
||||
const { t } = useI18n()
|
||||
const maindataStore = useMaindataStore()
|
||||
const vuetorrentStore = useVueTorrentStore()
|
||||
const searchQuery = useSearchQuery(
|
||||
() => maindataStore.torrents,
|
||||
() => maindataStore.torrentsWithFilters,
|
||||
searchFilter,
|
||||
torrent => torrent.name,
|
||||
results => {
|
||||
|
@ -71,16 +65,6 @@ export const useDashboardStore = defineStore(
|
|||
}
|
||||
})
|
||||
|
||||
const getTorrentsPayload = computed<GetTorrentPayload>(() => {
|
||||
return {
|
||||
filter: sortOptions.statusFilter ?? FilterState.ALL,
|
||||
category: sortOptions.categoryFilter ?? undefined,
|
||||
tag: sortOptions.tagFilter ?? undefined,
|
||||
sort: sortOptions.isCustomSortEnabled ? SortOptions.DEFAULT : sortOptions.sortBy,
|
||||
reverse: sortOptions.reverseOrder
|
||||
}
|
||||
})
|
||||
|
||||
function isTorrentInSelection(hash: string) {
|
||||
return selectedTorrents.value.includes(hash)
|
||||
}
|
||||
|
@ -157,8 +141,7 @@ export const useDashboardStore = defineStore(
|
|||
spanTorrentSelection,
|
||||
selectAllTorrents,
|
||||
unselectAllTorrents,
|
||||
toggleSelect,
|
||||
getTorrentsPayload
|
||||
toggleSelect
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { useTorrentBuilder } from '@/composables'
|
||||
import { FilePriority } from '@/constants/qbit'
|
||||
import { FilePriority, TorrentState } from '@/constants/qbit'
|
||||
import { SortOptions } from '@/constants/qbit/SortOptions'
|
||||
import { extractHostname } from '@/helpers'
|
||||
import { qbit } from '@/services'
|
||||
|
@ -12,7 +12,7 @@ import { AddTorrentPayload } from '@/types/qbit/payloads'
|
|||
import { Torrent } from '@/types/vuetorrent'
|
||||
import { generateMultiple } from '@/utils/faker'
|
||||
import { defineStore } from 'pinia'
|
||||
import { MaybeRefOrGetter, computed, ref, toValue } from 'vue'
|
||||
import { computed, MaybeRefOrGetter, reactive, ref, toValue } from 'vue'
|
||||
|
||||
const isProduction = computed(() => process.env.NODE_ENV === 'production')
|
||||
|
||||
|
@ -25,6 +25,26 @@ export const useMaindataStore = defineStore('maindata', () => {
|
|||
const torrents = ref<Torrent[]>([])
|
||||
const trackers = ref<string[]>([])
|
||||
|
||||
const filters = reactive({
|
||||
statusFilter: [] as TorrentState[],
|
||||
categoryFilter: [] as string[],
|
||||
tagFilter: [] as (string | null)[],
|
||||
trackerFilter: [] as (string | null)[]
|
||||
})
|
||||
|
||||
const torrentsWithFilters = computed(() => {
|
||||
return torrents.value.filter(torrent => {
|
||||
if (filters.statusFilter.length > 0 && !filters.statusFilter.includes(torrent.state)) return false
|
||||
if (filters.categoryFilter.length > 0 && !filters.categoryFilter.includes(torrent.category)) return false
|
||||
if (filters.tagFilter.length > 0) {
|
||||
if (torrent.tags.length === 0 && filters.tagFilter.includes(null)) return true
|
||||
if (!torrent.tags.some(tag => filters.tagFilter.includes(tag))) return false
|
||||
}
|
||||
if (filters.trackerFilter.length > 0 && !filters.trackerFilter.includes(extractHostname(torrent.tracker))) return false
|
||||
return true
|
||||
})
|
||||
})
|
||||
|
||||
const authStore = useAuthStore()
|
||||
const dashboardStore = useDashboardStore()
|
||||
const navbarStore = useNavbarStore()
|
||||
|
@ -143,7 +163,7 @@ 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)
|
||||
let data = await qbit.getTorrents()
|
||||
|
||||
if (vueTorrentStore.showTrackerFilter) {
|
||||
trackers.value = data
|
||||
|
@ -153,16 +173,10 @@ export const useMaindataStore = defineStore('maindata', () => {
|
|||
.sort()
|
||||
}
|
||||
|
||||
if (vueTorrentStore.showTrackerFilter && dashboardStore.sortOptions.trackerFilter !== null) {
|
||||
// don't calculate trackers when disabled
|
||||
data = data.filter(d => extractHostname(d.tracker) === dashboardStore.sortOptions.trackerFilter)
|
||||
}
|
||||
|
||||
// update torrents
|
||||
torrents.value = data.map(t => torrentBuilder.buildFromQbit(t))
|
||||
|
||||
if (!isProduction.value) {
|
||||
if (import.meta.env.VITE_USE_FAKE_TORRENTS === 'false') return
|
||||
if (!isProduction.value && import.meta.env.VITE_USE_FAKE_TORRENTS === 'true') {
|
||||
const count = import.meta.env.VITE_FAKE_TORRENT_COUNT
|
||||
torrents.value.push(...generateMultiple(count).map(t => torrentBuilder.buildFromQbit(t)))
|
||||
}
|
||||
|
@ -296,7 +310,9 @@ export const useMaindataStore = defineStore('maindata', () => {
|
|||
serverState,
|
||||
tags,
|
||||
torrents,
|
||||
torrentsWithFilters,
|
||||
trackers,
|
||||
filters,
|
||||
getTorrentByHash,
|
||||
getTorrentIndexByHash,
|
||||
deleteTorrents,
|
||||
|
@ -340,4 +356,15 @@ export const useMaindataStore = defineStore('maindata', () => {
|
|||
setTorrentFilePriority,
|
||||
exportTorrent
|
||||
}
|
||||
}, {
|
||||
persist: {
|
||||
enabled: true,
|
||||
strategies: [
|
||||
{
|
||||
storage: localStorage,
|
||||
key: 'vuetorrent_maindata',
|
||||
paths: ['filters']
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
||||
|
|
|
@ -8,21 +8,6 @@ $category: #04669a;
|
|||
$tag: #048b9a;
|
||||
$tracker: #c97d09;
|
||||
|
||||
// State filter colors
|
||||
$state-downloading: #5bb974;
|
||||
$state-stalled_downloading: #5bb974;
|
||||
$state-seeding: #4ecde6;
|
||||
$state-stalled_uploading: #4ecde6;
|
||||
$state-completed: #16573e;
|
||||
$state-resumed: #bdbdbd;
|
||||
$state-active: #bdbdbd;
|
||||
$state-stalled: #696969;
|
||||
$state-paused: #696969;
|
||||
$state-inactive: #696969;
|
||||
$state-checking: #ff7043;
|
||||
$state-moving: #ffaa2c;
|
||||
$state-errored: #f83e70;
|
||||
|
||||
// Torrent state colors
|
||||
$torrent-error: #f83e70;
|
||||
$torrent-missingFiles: #f83e70;
|
||||
|
|
|
@ -38,7 +38,7 @@ export default interface Torrent {
|
|||
size: number
|
||||
state: TorrentState
|
||||
super_seeding: boolean
|
||||
tags: string[] | null
|
||||
tags: string[]
|
||||
time_active: number
|
||||
total_size: number
|
||||
tracker: string
|
||||
|
|
Loading…
Add table
Reference in a new issue