diff --git a/src/components/Drawer.vue b/src/components/Drawer.vue index 385c23c..421ec4b 100644 --- a/src/components/Drawer.vue +++ b/src/components/Drawer.vue @@ -65,7 +65,7 @@ import Vue from 'vue'; import { mapGetters } from 'vuex'; import { tr } from '@/locale'; -import { Torrent, Category } from '@/types'; +import { Torrent, Category, Tag } from '@/types'; import FilterGroup from './drawer/FilterGroup.vue'; import api from '../Api'; import { formatSize } from '@/filters'; @@ -141,7 +141,9 @@ interface MenuChildrenItem extends MenuItem { 'isDataReady', 'allTorrents', 'allCategories', + 'allTags', 'torrentGroupByCategory', + 'torrentGroupByTag', 'torrentGroupBySite', 'torrentGroupByState', ]), @@ -165,7 +167,9 @@ export default class Drawer extends Vue { isDataReady!: boolean allTorrents!: Torrent[] allCategories!: Category[] + allTags!: Tag[] torrentGroupByCategory!: {[category: string]: Torrent[]} + torrentGroupByTag!: {[tag: string]: Torrent[]} torrentGroupBySite!: {[site: string]: Torrent[]} torrentGroupByState!: {[state: string]: Torrent[]} @@ -214,6 +218,24 @@ export default class Drawer extends Vue { }); } + buildTagGroup(): MenuChildrenItem[] { + return [{ + key: '', + name: tr('untagged'), + }].concat(this.allTags).map((tag) => { + let value = this.torrentGroupByTag[tag.key]; + if (isUndefined(value)) { + value = []; + } + const size = formatSize(sumBy(value, 'size')); + const title = `${tag.name} (${value.length})`; + const append = `[${size}]`; + return { + icon: 'mdi-folder', title, key: tag.key, append, + }; + }); + } + buildSiteGroup(): MenuChildrenItem[] { return sortBy(Object.entries(this.torrentGroupBySite).map(([key, value]) => { const size = formatSize(sumBy(value, 'size')); @@ -263,6 +285,20 @@ export default class Drawer extends Vue { ], }); + filterGroups.push({ + icon: 'mdi-menu-up', + 'icon-alt': 'mdi-menu-down', + title: tr('tag', 0), + model: null, + select: 'tag', + children: [ + { + icon: 'mdi-folder', title: `${tr('all')} (${this.allTorrents.length})`, key: null, append: `[${totalSize}]`, + }, + ...this.buildTagGroup(), + ], + }); + filterGroups.push({ icon: 'mdi-menu-up', 'icon-alt': 'mdi-menu-down', diff --git a/src/components/Torrents.vue b/src/components/Torrents.vue index ccca4a8..a1525d1 100644 --- a/src/components/Torrents.vue +++ b/src/components/Torrents.vue @@ -248,7 +248,7 @@ import api from '../Api' import { formatSize } from '@/filters' import { DialogType, TorrentFilter, ConfigPayload, DialogConfig, SnackBarConfig } from '@/store/types' import Component from 'vue-class-component' -import { Torrent, Category } from '@/types' +import { Torrent, Category, Tag } from '@/types' import { Watch } from 'vue-property-decorator' function getStateInfo(state: string) { @@ -337,8 +337,10 @@ function getStateInfo(state: string) { ...mapGetters([ 'isDataReady', 'allTorrents', + 'allTags', 'allCategories', 'torrentGroupByCategory', + 'torrentGroupByTag', 'torrentGroupBySite', 'torrentGroupByState', ]), @@ -413,7 +415,9 @@ export default class Torrents extends Vue { isDataReady!: boolean allTorrents!: Torrent[] allCategories!: Category[] + allTags!: Tag[] torrentGroupByCategory!: {[category: string]: Torrent[]} + torrentGroupByTag!: {[tag: string]: Torrent[]} torrentGroupBySite!: {[site: string]: Torrent[]} torrentGroupByState!: {[state: string]: Torrent[]} filter!: TorrentFilter @@ -444,6 +448,9 @@ export default class Torrents extends Vue { if (this.filter.category !== null) { list = intersection(list, this.torrentGroupByCategory[this.filter.category]); } + if (this.filter.tag !== null) { + list = intersection(list, this.torrentGroupByTag[this.filter.tag]); + } if (this.filter.state !== null) { list = intersection(list, this.torrentGroupByState[this.filter.state]); } diff --git a/src/locale/zh-CN.ts b/src/locale/zh-CN.ts index 755393e..37894aa 100644 --- a/src/locale/zh-CN.ts +++ b/src/locale/zh-CN.ts @@ -53,6 +53,8 @@ export default { all: '全部', category: '分类', uncategorized: '未分类', + tag: '标签', + untagged: '无标签', others: '其他', sites: '站点', files: '文件', diff --git a/src/store/index.ts b/src/store/index.ts index 979da9a..96253b6 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -53,6 +53,12 @@ const store = new Vuex.Store({ } delete payload.categories_removed; } + if (payload.tags_removed) { + for (const key of payload.tags_removed) { + Vue.delete(mainData, key); + } + delete payload.categories_removed; + } stateMerge(mainData, payload); } }, @@ -94,9 +100,38 @@ const store = new Vuex.Store({ (value, key) => merge({}, value, { key })); return sortBy(categories, 'name'); }, + allTags(state) { + if (!state.mainData) { + return []; + } + + const finalTags: any[] = [] + for (const tag of state.mainData.tags) { + finalTags.push({ + "key": tag, + "name": tag, + }); + } + return sortBy(finalTags, 'name'); + }, torrentGroupByCategory(state, getters) { return groupBy(getters.allTorrents, torrent => torrent.category); }, + torrentGroupByTag(state, getters) { + const result: any = {} + for (const torrent of getters.allTorrents) { + const tags: any[] = torrent.tags.split(","); + tags.forEach(tag => { + let list: any[] = result[tag] + if (!list) { + list = [] + result[tag] = list; + } + list.push(torrent); + }); + } + return result; + }, torrentGroupBySite(state, getters) { return groupBy(getters.allTorrents, (torrent) => { if (!torrent.tracker) { diff --git a/src/store/types.ts b/src/store/types.ts index 1f6fbc9..51b405c 100644 --- a/src/store/types.ts +++ b/src/store/types.ts @@ -25,6 +25,7 @@ export interface AddFormState { export interface TorrentFilter { state: string; category: string; + tag: string; site: string; query: string; } diff --git a/src/types.ts b/src/types.ts index 47739d9..6414fca 100644 --- a/src/types.ts +++ b/src/types.ts @@ -66,6 +66,11 @@ export interface SimpleCategory { savePath?: string; } +export interface Tag { + key: string; + name: string; +} + export interface ServerState { alltime_dl: number; alltime_ul: number; @@ -95,6 +100,7 @@ export interface ServerState { export interface MainData { categories: Record; + tags: [string]; server_state: ServerState; torrents: Record; } diff --git a/tests/unit/store/index.spec.ts b/tests/unit/store/index.spec.ts index 90bb4d0..8963c3c 100644 --- a/tests/unit/store/index.spec.ts +++ b/tests/unit/store/index.spec.ts @@ -48,6 +48,7 @@ describe('all torrents getter', () => { store.replaceState(mockState({ mainData: { categories: {}, + tags: [""], // eslint-disable-next-line @typescript-eslint/camelcase server_state: undefined as any, torrents: {