mirror of
https://github.com/VueTorrent/VueTorrent.git
synced 2024-10-22 10:46:28 +03:00
fix: Sort values correctly (#1811)
Some checks failed
Build project and release / Build VueTorrent (vuetorrent-build, ./vuetorrent, build) (push) Has been cancelled
Build project and release / Run Release Please action (push) Has been cancelled
Build project and release / Build VueTorrent (vuetorrent-demo, ./vuetorrent-demo, build-demo) (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
Build project and release / Push to nightly branch (push) Has been cancelled
Build project and release / Push to demo repo (push) Has been cancelled
Build project and release / Upload release to GitHub (push) Has been cancelled
Build project and release / Push to latest branch (push) Has been cancelled
Build project and release / Push docker mod to GHCR (push) Has been cancelled
Some checks failed
Build project and release / Build VueTorrent (vuetorrent-build, ./vuetorrent, build) (push) Has been cancelled
Build project and release / Run Release Please action (push) Has been cancelled
Build project and release / Build VueTorrent (vuetorrent-demo, ./vuetorrent-demo, build-demo) (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
Build project and release / Push to nightly branch (push) Has been cancelled
Build project and release / Push to demo repo (push) Has been cancelled
Build project and release / Upload release to GitHub (push) Has been cancelled
Build project and release / Push to latest branch (push) Has been cancelled
Build project and release / Push docker mod to GHCR (push) Has been cancelled
This commit is contained in:
parent
2863b1dc85
commit
17700db598
13 changed files with 76 additions and 45 deletions
8
package-lock.json
generated
8
package-lock.json
generated
|
@ -51,6 +51,7 @@
|
|||
"jsdom": "^24.1.1",
|
||||
"prettier": "^3.3.3",
|
||||
"sass": "^1.77.8",
|
||||
"timezone-mock": "^1.3.6",
|
||||
"typescript": "^5.5.4",
|
||||
"vite": "^5.3.4",
|
||||
"vite-plugin-top-level-await": "^1.4.2",
|
||||
|
@ -4677,6 +4678,13 @@
|
|||
"integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/timezone-mock": {
|
||||
"version": "1.3.6",
|
||||
"resolved": "https://registry.npmjs.org/timezone-mock/-/timezone-mock-1.3.6.tgz",
|
||||
"integrity": "sha512-YcloWmZfLD9Li5m2VcobkCDNVaLMx8ohAb/97l/wYS3m+0TIEK5PFNMZZfRcusc6sFjIfxu8qcJT0CNnOdpqmg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tinybench": {
|
||||
"version": "2.8.0",
|
||||
"resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.8.0.tgz",
|
||||
|
|
|
@ -60,6 +60,7 @@
|
|||
"jsdom": "^24.1.1",
|
||||
"prettier": "^3.3.3",
|
||||
"sass": "^1.77.8",
|
||||
"timezone-mock": "^1.3.6",
|
||||
"typescript": "^5.5.4",
|
||||
"vite": "^5.3.4",
|
||||
"vite-plugin-top-level-await": "^1.4.2",
|
||||
|
|
|
@ -32,7 +32,7 @@ const hashes = computed(() => dashboardStore.selectedTorrents)
|
|||
const hash = computed(() => hashes.value[0])
|
||||
const torrent = computed(() => torrentStore.getTorrentByHash(hash.value))
|
||||
const torrents = computed(() => dashboardStore.selectedTorrents.map(torrentStore.getTorrentByHash).filter(torrent => !!torrent))
|
||||
const availableCategories = computed(() => ['', ...categoryStore.categories.keys()])
|
||||
const availableCategories = computed(() => ['', ...categoryStore.categories.map(cat => cat.name)])
|
||||
|
||||
async function resumeTorrents() {
|
||||
await torrentStore.resumeTorrents(hashes)
|
||||
|
@ -243,7 +243,7 @@ const menuData = computed<RightClickMenuEntryType[]>(() => [
|
|||
{
|
||||
text: t('dashboard.right_click.category.title'),
|
||||
icon: 'mdi-label',
|
||||
disabled: categoryStore.categories.size === 0,
|
||||
disabled: categoryStore.categories.length === 0,
|
||||
disabledText: t('dashboard.right_click.category.disabled_title'),
|
||||
disabledIcon: 'mdi-label-off',
|
||||
children: availableCategories.value.map(category => ({
|
||||
|
|
|
@ -33,7 +33,7 @@ const savePathField = ref<typeof HistoryField>()
|
|||
const tagSearch = ref('')
|
||||
|
||||
const categorySearch = ref('')
|
||||
const categoryNames = computed(() => Array.from(categoryStore.categories.keys()))
|
||||
const categoryNames = computed(() => categoryStore.categories.map(cat => cat.name))
|
||||
const category = computed<string | undefined>({
|
||||
get: () => form.value.category || categorySearch.value || undefined,
|
||||
set: value => (form.value.category = value || undefined)
|
||||
|
|
|
@ -17,12 +17,9 @@ const statuses = computed(() =>
|
|||
.filter(state => typeof state === 'number')
|
||||
.map(state => ({ title: t(`torrent.state.${getTorrentStateValue(state as TorrentState)}`), value: state }))
|
||||
)
|
||||
const categories = computed(() => [{ title: t('navbar.side.filters.uncategorized'), value: '' }, ...Array.from(categoryStore.categories.keys()).map(c => ({ title: c, value: c }))])
|
||||
const tags = computed(() => [{ title: t('navbar.side.filters.untagged'), value: null }, ...tagStore.tags.map(tag => ({ title: tag, value: tag }))])
|
||||
const trackers = computed(() => [
|
||||
{ title: t('navbar.side.filters.untracked'), value: null },
|
||||
...Array.from(trackerStore.trackers.keys()).map(tracker => ({ title: tracker, value: tracker }))
|
||||
])
|
||||
const categories = computed(() => [{ title: t('navbar.side.filters.uncategorized'), value: '' }, ...categoryStore.categories.map(c => c.name)])
|
||||
const tags = computed(() => [{ title: t('navbar.side.filters.untagged'), value: null }, ...tagStore.tags])
|
||||
const trackers = computed(() => [{ title: t('navbar.side.filters.untracked'), value: null }, ...trackerStore.trackers])
|
||||
|
||||
function selectAllStatuses() {
|
||||
statusFilter.value = []
|
||||
|
|
|
@ -56,7 +56,7 @@ function openCategoryFormDialog(initialCategory?: Category) {
|
|||
<v-col cols="12" sm="6">
|
||||
<v-list-subheader class="ml-2">{{ $t('settings.tagsAndCategories.categoriesSubheader') }}</v-list-subheader>
|
||||
|
||||
<v-sheet rounded="xl" class="d-flex align-center gap" v-for="category in categoryStore.categories.values()">
|
||||
<v-sheet rounded="xl" class="d-flex align-center gap" v-for="category in categoryStore.categories">
|
||||
<div class="pl-4 py-1 wrap-anywhere">{{ category.name }}</div>
|
||||
<v-spacer />
|
||||
<div class="d-flex">
|
||||
|
@ -65,7 +65,7 @@ function openCategoryFormDialog(initialCategory?: Category) {
|
|||
</div>
|
||||
</v-sheet>
|
||||
|
||||
<v-card v-if="categoryStore.categories.size === 0">
|
||||
<v-card v-if="categoryStore.categories.length === 0">
|
||||
<v-card-text>{{ $t('settings.tagsAndCategories.noCategories') }}</v-card-text>
|
||||
</v-card>
|
||||
|
||||
|
|
|
@ -42,7 +42,7 @@ async function toggleTag(tag: string) {
|
|||
<v-list-subheader>{{ $t('torrentDetail.tagsAndCategories.categories') }}</v-list-subheader>
|
||||
|
||||
<v-list-item
|
||||
v-for="category in categoryStore.categories.values()"
|
||||
v-for="category in categoryStore.categories"
|
||||
variant="text"
|
||||
color="accent"
|
||||
:title="category.name"
|
||||
|
|
|
@ -1,5 +1,14 @@
|
|||
import { formatEta, formatTimeMs, formatTimeSec } from './datetime'
|
||||
import timezoneMock from 'timezone-mock'
|
||||
import { expect, test } from 'vitest'
|
||||
import { formatEta, formatTimeMs, formatTimeSec } from './datetime'
|
||||
|
||||
beforeAll(() => {
|
||||
timezoneMock.register('UTC')
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
timezoneMock.unregister()
|
||||
})
|
||||
|
||||
test('helpers/datetime/formatEta', () => {
|
||||
// seconds
|
||||
|
|
|
@ -33,7 +33,7 @@ const headers = [
|
|||
{ title: t('searchEngine.headers.siteUrl'), key: 'siteUrl' },
|
||||
{ title: '', key: 'actions', sortable: false }
|
||||
]
|
||||
const cats = [
|
||||
const categories = [
|
||||
{ title: t('searchEngine.filters.category.movies'), value: 'movies' },
|
||||
{ title: t('searchEngine.filters.category.tv'), value: 'tv' },
|
||||
{ title: t('searchEngine.filters.category.music'), value: 'music' },
|
||||
|
@ -43,8 +43,8 @@ const cats = [
|
|||
{ title: t('searchEngine.filters.category.pictures'), value: 'pictures' },
|
||||
{ title: t('searchEngine.filters.category.books'), value: 'books' }
|
||||
]
|
||||
cats.sort((a, b) => a.title.localeCompare(b.title))
|
||||
const categories = [{ title: t('searchEngine.filters.category.all'), value: 'all' }, ...cats]
|
||||
.sort((a, b) => a.title.localeCompare(b.title))
|
||||
.splice(0, 0, { title: t('searchEngine.filters.category.all'), value: 'all' })
|
||||
|
||||
const plugins = computed(() => {
|
||||
const plugins = [
|
||||
|
|
|
@ -26,13 +26,13 @@ import IProvider from './IProvider'
|
|||
export default class MockProvider implements IProvider {
|
||||
private static instance: MockProvider
|
||||
private readonly categories: Record<string, Category> = {
|
||||
ISO: { name: 'ISO', savePath: faker.system.directoryPath() },
|
||||
Other: { name: 'Other', savePath: faker.system.directoryPath() },
|
||||
Movie: { name: 'Movie', savePath: faker.system.directoryPath() },
|
||||
Music: { name: 'Music', savePath: faker.system.directoryPath() },
|
||||
TV: { name: 'TV', savePath: faker.system.directoryPath() }
|
||||
TV: { name: 'TV', savePath: faker.system.directoryPath() },
|
||||
Other: { name: 'Other', savePath: faker.system.directoryPath() },
|
||||
ISO: { name: 'ISO', savePath: faker.system.directoryPath() },
|
||||
Music: { name: 'Music', savePath: faker.system.directoryPath() }
|
||||
}
|
||||
private readonly tags: string[] = ['sorted', 'pending_sort']
|
||||
private readonly tags: string[] = ['pending', 'sorted', 'pending_sort']
|
||||
private readonly trackers: Record<string, string[]> = faker.helpers
|
||||
.multiple(() => faker.internet.url(), { count: 5 })
|
||||
.reduce(
|
||||
|
|
|
@ -1,40 +1,46 @@
|
|||
import { comparators } from '@/helpers'
|
||||
import qbit from '@/services/qbit'
|
||||
import { Category } from '@/types/qbit/models'
|
||||
import { useSorted } from '@vueuse/core'
|
||||
import { acceptHMRUpdate, defineStore } from 'pinia'
|
||||
import { shallowRef, triggerRef } from 'vue'
|
||||
|
||||
export const useCategoryStore = defineStore('categories', () => {
|
||||
/** Key: Category name */
|
||||
const categories = shallowRef<Map<string, Category>>(new Map())
|
||||
const _categoryMap = shallowRef<Map<string, Category>>(new Map())
|
||||
const categories = useSorted(
|
||||
() => Array.from(_categoryMap.value.values()),
|
||||
(a, b) => comparators.text.asc(a.name, b.name)
|
||||
)
|
||||
|
||||
function syncFromMaindata(fullUpdate: boolean, entries: [string, Partial<Category>][], removed?: string[]) {
|
||||
if (fullUpdate) {
|
||||
categories.value = new Map(entries as [string, Category][])
|
||||
_categoryMap.value = new Map(entries as [string, Category][])
|
||||
return
|
||||
}
|
||||
|
||||
for (const [catName, qbitCat] of entries) {
|
||||
const oldCat = categories.value.get(catName)
|
||||
const oldCat = _categoryMap.value.get(catName)
|
||||
if (oldCat) {
|
||||
const newCat = {
|
||||
name: qbitCat.name ?? oldCat.name,
|
||||
savePath: qbitCat.savePath ?? oldCat.savePath
|
||||
}
|
||||
categories.value.set(catName, newCat)
|
||||
_categoryMap.value.set(catName, newCat)
|
||||
} else {
|
||||
categories.value.set(catName, {
|
||||
_categoryMap.value.set(catName, {
|
||||
name: qbitCat.name ?? catName,
|
||||
savePath: qbitCat.savePath ?? ''
|
||||
})
|
||||
}
|
||||
}
|
||||
removed?.forEach(c => categories.value.delete(c))
|
||||
triggerRef(categories)
|
||||
removed?.forEach(c => _categoryMap.value.delete(c))
|
||||
triggerRef(_categoryMap)
|
||||
}
|
||||
|
||||
function getCategoryFromName(categoryName?: string) {
|
||||
if (!categoryName) return
|
||||
return categories.value.get(categoryName)
|
||||
return _categoryMap.value.get(categoryName)
|
||||
}
|
||||
|
||||
async function createCategory(category: Category) {
|
||||
|
@ -78,8 +84,8 @@ export const useCategoryStore = defineStore('categories', () => {
|
|||
editCategory,
|
||||
deleteCategories,
|
||||
$reset: () => {
|
||||
categories.value.clear()
|
||||
triggerRef(categories)
|
||||
_categoryMap.value.clear()
|
||||
triggerRef(_categoryMap)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
@ -1,21 +1,27 @@
|
|||
import { comparators } from '@/helpers'
|
||||
import qbit from '@/services/qbit'
|
||||
import { useSorted } from '@vueuse/core'
|
||||
import { acceptHMRUpdate, defineStore } from 'pinia'
|
||||
import { shallowRef } from 'vue'
|
||||
import { shallowRef, triggerRef } from 'vue'
|
||||
|
||||
export const useTagStore = defineStore('tags', () => {
|
||||
const tags = shallowRef<string[]>([])
|
||||
const _tags = shallowRef<Set<string>>(new Set())
|
||||
const tags = useSorted(
|
||||
() => Array.from(_tags.value.values()),
|
||||
comparators.text.asc
|
||||
)
|
||||
|
||||
function syncFromMaindata(fullUpdate: boolean, values: string[], removed?: string[]) {
|
||||
if (fullUpdate) {
|
||||
tags.value = values
|
||||
_tags.value = new Set(values)
|
||||
return
|
||||
}
|
||||
|
||||
if (values) {
|
||||
tags.value = [...tags.value, ...values]
|
||||
_tags.value = _tags.value.union(new Set(values))
|
||||
}
|
||||
|
||||
tags.value = tags.value.filter(tag => !removed || !removed.includes(tag))
|
||||
_tags.value = _tags.value.difference(new Set(removed))
|
||||
}
|
||||
|
||||
async function createTags(tags: string[]) {
|
||||
|
@ -52,7 +58,8 @@ export const useTagStore = defineStore('tags', () => {
|
|||
editTag,
|
||||
deleteTags,
|
||||
$reset: () => {
|
||||
tags.value = []
|
||||
_tags.value.clear()
|
||||
triggerRef(_tags)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
@ -1,13 +1,16 @@
|
|||
import { comparators } from '@/helpers'
|
||||
import qbit from '@/services/qbit'
|
||||
import { useSorted } from '@vueuse/core'
|
||||
import { acceptHMRUpdate, defineStore } from 'pinia'
|
||||
import { computed, shallowRef, triggerRef } from 'vue'
|
||||
|
||||
export const useTrackerStore = defineStore('trackers', () => {
|
||||
/** Key: tracker domain, values: torrent hashes */
|
||||
const trackers = shallowRef<Map<string, string[]>>(new Map())
|
||||
const _trackerMap = shallowRef<Map<string, string[]>>(new Map())
|
||||
const trackers = useSorted(() => Array.from(_trackerMap.value.keys()), comparators.text.asc)
|
||||
/** Key: torrent hash, values: tracker domains */
|
||||
const torrentTrackers = computed(() =>
|
||||
Array.from(trackers.value.entries()).reduce((tot, val) => {
|
||||
Array.from(_trackerMap.value.entries()).reduce((tot, val) => {
|
||||
const [domain, hashes] = val
|
||||
hashes.forEach(hash => {
|
||||
const domains = tot.get(hash)
|
||||
|
@ -23,16 +26,16 @@ export const useTrackerStore = defineStore('trackers', () => {
|
|||
|
||||
function syncFromMaindata(fullUpdate: boolean, entries: [string, string[]][], removed?: string[]) {
|
||||
if (fullUpdate) {
|
||||
trackers.value = new Map(entries)
|
||||
_trackerMap.value = new Map(entries)
|
||||
return
|
||||
}
|
||||
|
||||
for (const [trackerUrl, linkedTorrents] of entries) {
|
||||
trackers.value.set(trackerUrl, linkedTorrents)
|
||||
_trackerMap.value.set(trackerUrl, linkedTorrents)
|
||||
}
|
||||
|
||||
removed?.forEach(t => trackers.value.delete(t))
|
||||
triggerRef(trackers)
|
||||
removed?.forEach(t => _trackerMap.value.delete(t))
|
||||
triggerRef(_trackerMap)
|
||||
}
|
||||
|
||||
async function getTorrentTrackers(hash: string) {
|
||||
|
@ -60,8 +63,8 @@ export const useTrackerStore = defineStore('trackers', () => {
|
|||
editTorrentTracker,
|
||||
removeTorrentTrackers,
|
||||
$reset: () => {
|
||||
trackers.value.clear()
|
||||
triggerRef(trackers)
|
||||
_trackerMap.value.clear()
|
||||
triggerRef(_trackerMap)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
Loading…
Reference in a new issue