frontend: make manga tables sortable

This commit is contained in:
wea_ondara
2023-11-25 20:33:43 +01:00
parent 9279002794
commit 8b58758751
3 changed files with 60 additions and 12 deletions

View File

@@ -19,6 +19,7 @@ export type ViewEntry = {
relation: MangaUpdatesRelation | null, relation: MangaUpdatesRelation | null,
series: MangaUpdatesSeries | null, series: MangaUpdatesSeries | null,
chapters: MangaUpdatesChapter[], chapters: MangaUpdatesChapter[],
newChapters: number,
}; };
@Options({ @Options({

View File

@@ -3,7 +3,7 @@ import {Options, Vue} from 'vue-class-component';
import {Prop, Watch} from 'vue-property-decorator'; import {Prop, Watch} from 'vue-property-decorator';
import {BTable, type TableItem} from 'bootstrap-vue-next'; import {BTable, type TableItem} from 'bootstrap-vue-next';
//@ts-ignore TS2307 //@ts-ignore TS2307
import type {TableFieldObject} from 'bootstrap-vue-next/dist/src/types'; import type {TableField, TableFieldObject} from 'bootstrap-vue-next/dist/src/types';
import type {ViewEntry, ViewList} from '@/components/manga/MangaList.vue'; import type {ViewEntry, ViewList} from '@/components/manga/MangaList.vue';
import MangaEntryDetailsModal from '@/components/manga/MangaEntryDetailsModal.vue'; import MangaEntryDetailsModal from '@/components/manga/MangaEntryDetailsModal.vue';
import {latestChaptersSorted, latestChapterString, newChapterCount} from '@/components/manga/util.manga'; import {latestChaptersSorted, latestChapterString, newChapterCount} from '@/components/manga/util.manga';
@@ -34,6 +34,8 @@ export default class MangaListTable extends Vue {
readonly viewList!: ViewList; readonly viewList!: ViewList;
bTableRefreshHack = true; bTableRefreshHack = true;
sortKey: string | null = null;
sortAsc: boolean = false;
//methods //methods
latestChaptersSorted = latestChaptersSorted; latestChaptersSorted = latestChaptersSorted;
@@ -44,36 +46,36 @@ export default class MangaListTable extends Vue {
return [{ return [{
key: 'media.coverImage.large', key: 'media.coverImage.large',
label: '', label: '',
sortable: true, sortable: false,
tdClass: 'c-pointer', tdClass: 'c-pointer',
}, { }, {
key: 'media.title.userPreferred', key: 'media.title.userPreferred',
label: this.$t('manga.title'), label: this.$t('manga.title'),
sortable: true, sortable: true,
tdClass: 'c-pointer', tdClass: 'c-pointer',
thClass: 'c-pointer',
}, { }, {
key: 'entry.score', key: 'entry.score',
label: this.$t('manga.score'), label: this.$t('manga.score'),
sortable: true, sortable: true,
tdClass: 'text-center manga-column-score c-pointer', tdClass: 'text-center manga-column-score c-pointer',
thClass: 'text-center manga-column-score', thClass: 'text-center manga-column-score c-pointer',
}, { }, {
key: 'entry.progress', key: 'entry.progress',
label: this.$t('manga.progress'), label: this.$t('manga.progress'),
sortable: true, sortable: true,
tdClass: 'text-end text-nowrap c-pointer', tdClass: 'text-end text-nowrap c-pointer',
thClass: 'text-end', thClass: 'text-end c-pointer',
}, { }, {
key: 'newChapters', key: 'newChapters',
formatter: (value: never, key: never, item: ViewEntry) => this.newChapterCount(item),
label: this.$t('manga.chapters.newCount'), label: this.$t('manga.chapters.newCount'),
sortable: true, sortable: true,
tdClass: 'text-end c-pointer', tdClass: 'text-end c-pointer',
thClass: 'text-end', thClass: 'text-end c-pointer',
}, { }, {
key: 'latestChapters', key: 'latestChapters',
label: this.$t('manga.chapters.latest'), label: this.$t('manga.chapters.latest'),
sortable: true, sortable: false,
tdClass: 'manga-column-latest-chapters c-pointer', tdClass: 'manga-column-latest-chapters c-pointer',
thClass: 'manga-column-latest-chapters', thClass: 'manga-column-latest-chapters',
}]; }];
@@ -83,6 +85,34 @@ export default class MangaListTable extends Vue {
return this.viewList.entries; return this.viewList.entries;
} }
get tableEntriesSorted(): ViewEntry[] {
if (!this.sortKey) {
return this.tableEntries;
}
const keyExtractor = (e: ViewEntry) => eval('e.' + this.sortKey);//TODO eval is evil
const comparer = (l: ViewEntry, r: ViewEntry) => {
const lkey = keyExtractor(l);
const rkey = keyExtractor(r);
if ([null, undefined].includes(lkey) && [null, undefined].includes(lkey)) {
return 0;
} else if ([null, undefined].includes(lkey) && ![null, undefined].includes(lkey)) {
return -1;
} else if (![null, undefined].includes(lkey) && [null, undefined].includes(lkey)) {
return 1;
} else if (typeof lkey === 'number' && typeof rkey === 'number') {
return lkey - rkey;
} else if (typeof lkey === 'string' && typeof rkey === 'string') {
return lkey.localeCompare(rkey);
} else if (lkey < rkey) {
return -1;
} else if (lkey > rkey) {
return 1;
}
return 0;
};
return [...this.tableEntries].sort((l, r) => comparer(l, r) * (this.sortAsc ? 1 : -1));
}
cd<V = any>(data: V): CellData<ViewEntry, V> { cd<V = any>(data: V): CellData<ViewEntry, V> {
return (data as CellData<ViewEntry, V>); return (data as CellData<ViewEntry, V>);
} }
@@ -91,6 +121,19 @@ export default class MangaListTable extends Vue {
return (data as HeadData<V>); return (data as HeadData<V>);
} }
onHeaderClicked(fieldKey: string, field: TableField<ViewEntry>, event: MouseEvent, isFooter: boolean): void {
if (!field.sortable) {
return;
}
if (this.sortKey === fieldKey) {
this.sortAsc = !this.sortAsc;
} else {
this.sortAsc = false;
}
this.sortKey = fieldKey;
}
onRowClicked(entry: TableItem<ViewEntry>): void { onRowClicked(entry: TableItem<ViewEntry>): void {
(this.$refs.detailsModal as MangaEntryDetailsModal).open(entry); (this.$refs.detailsModal as MangaEntryDetailsModal).open(entry);
} }
@@ -109,9 +152,10 @@ export default class MangaListTable extends Vue {
<template> <template>
<div> <div>
<BTable ref="table" v-if="bTableRefreshHack" :fields="fields" :items="tableEntries" :primary-key="'id'" <BTable ref="table" v-if="bTableRefreshHack" :fields="fields" :items="tableEntriesSorted" :primary-key="'id'"
class="manga-table" hover striped responsive no-sort-reset sort-by="newChapters" sort-desc class="manga-table" hover striped responsive no-sort-reset sort-by="newChapters" sort-desc
@row-clicked="onRowClicked as any /* TODO dumb typing issue */"> @row-clicked="onRowClicked as any /* TODO dumb typing issue */"
@head-clicked="onHeaderClicked">
<template #cell(media.coverImage.large)="data"> <template #cell(media.coverImage.large)="data">
<img :src="data.value as string" alt="cover-img" class="list-cover"/> <img :src="data.value as string" alt="cover-img" class="list-cover"/>
</template> </template>

View File

@@ -7,7 +7,7 @@ import type {AniListMedia} from '@/data/models/anilist/AniListMedia';
import type {MangaUpdatesRelation} from '@/data/models/mangaupdates/MangaUpdatesRelation'; import type {MangaUpdatesRelation} from '@/data/models/mangaupdates/MangaUpdatesRelation';
import type {MangaUpdatesSeries} from '@/data/models/mangaupdates/MangaUpdatesSeries'; import type {MangaUpdatesSeries} from '@/data/models/mangaupdates/MangaUpdatesSeries';
import type {MangaUpdatesChapter} from '@/data/models/mangaupdates/MangaUpdatesChapter'; import type {MangaUpdatesChapter} from '@/data/models/mangaupdates/MangaUpdatesChapter';
import groupBy from '@/util'; import {newChapterCount} from '@/components/manga/util.manga';
@Options({ @Options({
name: 'MangaLists', name: 'MangaLists',
@@ -49,13 +49,16 @@ export default class MangaLists extends Vue {
const relation = this.relationsByAniListMediaId.get(e.mediaId) ?? null; const relation = this.relationsByAniListMediaId.get(e.mediaId) ?? null;
const series = this.seriesById.get(relation?.mangaUpdatesSeriesId as any) ?? null; const series = this.seriesById.get(relation?.mangaUpdatesSeriesId as any) ?? null;
const chapters = this.chaptersBySeriesId.get(relation?.mangaUpdatesSeriesId as any) ?? []; const chapters = this.chaptersBySeriesId.get(relation?.mangaUpdatesSeriesId as any) ?? [];
return ({ const viewEntry = {
entry: e, entry: e,
media: media, media: media,
relation: relation, relation: relation,
series: series, series: series,
chapters: chapters, chapters: chapters,
} as ViewEntry); newChapters: 0,
} as ViewEntry;
viewEntry.newChapters = newChapterCount(viewEntry);
return viewEntry;
}), }),
})) }))
.sort((l, r) => order.indexOf(l.list.name.toLowerCase()) - order.indexOf(r.list.name.toLowerCase())); .sort((l, r) => order.indexOf(l.list.name.toLowerCase()) - order.indexOf(r.list.name.toLowerCase()));