feat(filters): Allow multiple selection (#1202)

This commit is contained in:
Rémi Marseault 2023-10-26 15:08:24 +02:00 committed by GitHub
parent e40848d2cb
commit 41318c3d85
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 232 additions and 175 deletions

View file

@ -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>

View file

@ -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>

View file

@ -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) {

View file

@ -13,4 +13,4 @@ export enum FilterState {
CHECKING = 'checking',
MOVING = 'moving',
ERRORED = 'errored'
}
}

View file

@ -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 }

View file

@ -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",

View file

@ -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)

View file

@ -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',

View file

@ -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)
}

View file

@ -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
}
},
{

View file

@ -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']
}
]
}
})

View file

@ -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;

View file

@ -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