diff --git a/src/components/Navbar/SideWidgets/FilterSelect.vue b/src/components/Navbar/SideWidgets/FilterSelect.vue index e6d29260..bab31873 100644 --- a/src/components/Navbar/SideWidgets/FilterSelect.vue +++ b/src/components/Navbar/SideWidgets/FilterSelect.vue @@ -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> diff --git a/src/components/Navbar/TopWidgets/ActiveFilters.vue b/src/components/Navbar/TopWidgets/ActiveFilters.vue index 97b6ccbb..186d7a6f 100644 --- a/src/components/Navbar/TopWidgets/ActiveFilters.vue +++ b/src/components/Navbar/TopWidgets/ActiveFilters.vue @@ -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> diff --git a/src/components/TorrentDetail/Overview.vue b/src/components/TorrentDetail/Overview.vue index ecf3dd5d..722bdf36 100644 --- a/src/components/TorrentDetail/Overview.vue +++ b/src/components/TorrentDetail/Overview.vue @@ -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) { diff --git a/src/constants/qbit/FilterState.ts b/src/constants/qbit/FilterState.ts index fc18d34e..d8248c4a 100644 --- a/src/constants/qbit/FilterState.ts +++ b/src/constants/qbit/FilterState.ts @@ -13,4 +13,4 @@ export enum FilterState { CHECKING = 'checking', MOVING = 'moving', ERRORED = 'errored' -} +} \ No newline at end of file diff --git a/src/constants/qbit/index.ts b/src/constants/qbit/index.ts index ade2082a..e22b1c97 100644 --- a/src/constants/qbit/index.ts +++ b/src/constants/qbit/index.ts @@ -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 } diff --git a/src/locales/en.json b/src/locales/en.json index 16d0ca7d..f7b4ded9 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -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", diff --git a/src/pages/Logs.vue b/src/pages/Logs.vue index c6e01cbb..942d7a53 100644 --- a/src/pages/Logs.vue +++ b/src/pages/Logs.vue @@ -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) diff --git a/src/plugins/vuetify.ts b/src/plugins/vuetify.ts index 343d31ef..59b83194 100644 --- a/src/plugins/vuetify.ts +++ b/src/plugins/vuetify.ts @@ -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', diff --git a/src/services/qbit.ts b/src/services/qbit.ts index 5fa975d2..8fdab8e6 100644 --- a/src/services/qbit.ts +++ b/src/services/qbit.ts @@ -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) } diff --git a/src/stores/dashboard.ts b/src/stores/dashboard.ts index f223bf91..dda028dc 100644 --- a/src/stores/dashboard.ts +++ b/src/stores/dashboard.ts @@ -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 } }, { diff --git a/src/stores/maindata.ts b/src/stores/maindata.ts index 19b07f9c..c3cc1eb4 100644 --- a/src/stores/maindata.ts +++ b/src/stores/maindata.ts @@ -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'] + } + ] + } }) diff --git a/src/styles/colors.scss b/src/styles/colors.scss index eb4db4ab..52fa967c 100644 --- a/src/styles/colors.scss +++ b/src/styles/colors.scss @@ -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; diff --git a/src/types/vuetorrent/Torrent.ts b/src/types/vuetorrent/Torrent.ts index ff1877e5..028cb0ee 100644 --- a/src/types/vuetorrent/Torrent.ts +++ b/src/types/vuetorrent/Torrent.ts @@ -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