frontend: added filter to filter manga

This commit is contained in:
wea_ondara
2023-12-02 20:49:49 +01:00
parent 0307325118
commit edae09f353
7 changed files with 91 additions and 24 deletions

View File

@@ -25,13 +25,13 @@ export default class App extends Vue {
</script>
<template>
<div class="h-100 w-100">
<div class="h-100 w-100 overflow-hidden">
<LocaleSaver/>
<DocumentLocaleSetter/>
<StoragePersist/>
<SideBar ref="sidebar" class="h-100 w-100" :toggled="sidebarToggled"
<SideBar ref="sidebar" class="h-100 w-100 overflow-hidden" :toggled="sidebarToggled"
@close="sidebarToggled=false"/>
<div class="d-flex flex-column h-100 w-100">
<div class="d-flex flex-column h-100 w-100 overflow-hidden">
<NavBar @toggleSidebar="sidebarToggled = !sidebarToggled"/>
<RouterView class="flex-grow-1"/>
</div>

View File

@@ -0,0 +1,43 @@
<script lang="ts">
import {Options, Vue} from 'vue-class-component';
import {Prop} from 'vue-property-decorator';
@Options({
name: 'FilterBar',
components: {},
emits: {
'update:modelValue': (modelValue: string) => true,
},
})
export default class FilterBar extends Vue {
@Prop({required: true})
modelValue!: string;
}
</script>
<template>
<!-- z-index needed, otherwise shadow not showing -->
<nav class="navbar border-bottom shadow flex-nowrap filter-bar" style="z-index: 1">
<div class="input-group mx-auto">
<input class="form-control" :value="modelValue" :placeholder="$t('filter')"
@input="$emit('update:modelValue', ($event.target! as HTMLInputElement).value)"/>
<button v-if="modelValue?.trim().length" class="btn btn-secondary" @click="$emit('update:modelValue', '')">
<i class="fa fa-xmark"/>
</button>
</div>
</nav>
</template>
<style lang="scss">
@import 'bootstrap/scss/functions';
@import 'bootstrap/scss/variables';
@import 'bootstrap/scss/mixins/breakpoints';
@include media-breakpoint-up(sm) {
nav.filter-bar > div {
width: 33%;
}
}
</style>

View File

@@ -7,6 +7,7 @@ import type {TableField, TableFieldObject} from 'bootstrap-vue-next/dist/src/typ
import type {ViewEntry, ViewList} from '@/components/manga/MangaList.vue';
import MangaEntryDetailsModal from '@/components/manga/MangaEntryDetailsModal.vue';
import {get, latestChaptersSorted, latestChapterString, newChapterCount} from '@/components/manga/util.manga';
import {decode} from 'html-entities';
type HeadData<I> = {
label: string,
@@ -27,6 +28,7 @@ type CellData<I, V> = {
@Options({
name: 'MangaListTable',
methods: {decode},
components: {BTable, MangaEntryDetailsModal},
})
export default class MangaListTable extends Vue {
@@ -170,7 +172,7 @@ export default class MangaListTable extends Vue {
</span>
<template v-if="cd(data).item.series">
<a :href="cd(data).item.series!.url" target="_blank">
{{ cd(data).item.series!.title }}
{{ decode(cd(data).item.series!.title) }}
</a>
</template>
<span v-else>{{ $t('mangaupdates.relation.found') }}</span>

View File

@@ -8,12 +8,15 @@ import type {MangaUpdatesRelation} from '@/data/models/mangaupdates/MangaUpdates
import type {MangaUpdatesSeries} from '@/data/models/mangaupdates/MangaUpdatesSeries';
import type {MangaUpdatesChapter} from '@/data/models/mangaupdates/MangaUpdatesChapter';
import {newChapterCount} from '@/components/manga/util.manga';
import FilterBar from '@/components/manga/FilterBar.vue';
@Options({
name: 'MangaLists',
components: {MangaList, BTable},
components: {FilterBar, MangaList, BTable},
})
export default class MangaLists extends Vue {
filter: string | undefined = '';
get mangaStore(): MangaStore {
return new MangaStore();
};
@@ -42,33 +45,50 @@ export default class MangaLists extends Vue {
const lists = this.mangaStore.aniListLists;
const manga = this.mangaStore.aniListManga;
const filterparts = this.filter?.trim().toLowerCase().split(' ') ?? [];
return lists.map(l => ({
list: l,
entries: (manga.get(l.name) ?? []).map(e => {
const media = this.mediaById.get(e.mediaId) ?? null;
const relation = this.relationsByAniListMediaId.get(e.mediaId) ?? null;
const series = this.seriesById.get(relation?.mangaUpdatesSeriesId as any) ?? null;
const chapters = this.chaptersBySeriesId.get(relation?.mangaUpdatesSeriesId as any) ?? [];
const viewEntry = {
entry: e,
media: media,
relation: relation,
series: series,
chapters: chapters,
newChapters: 0,
} as ViewEntry;
viewEntry.newChapters = newChapterCount(viewEntry);
return viewEntry;
}),
entries: (manga.get(l.name) ?? [])
.filter(e => { // apply filter if set
const media = this.mediaById.get(e.mediaId) ?? null;
return !this.filter?.trim().length
|| filterparts.some(fp => media!.title.english?.toLowerCase().includes(fp))
|| filterparts.some(fp => media!.title.native?.toLowerCase().includes(fp))
|| filterparts.some(fp => media!.title.romaji?.toLowerCase().includes(fp));
})
.map(e => {
const media = this.mediaById.get(e.mediaId) ?? null;
const relation = this.relationsByAniListMediaId.get(e.mediaId) ?? null;
const series = this.seriesById.get(relation?.mangaUpdatesSeriesId as any) ?? null;
const chapters = this.chaptersBySeriesId.get(relation?.mangaUpdatesSeriesId as any) ?? [];
const viewEntry = {
entry: e,
media: media,
relation: relation,
series: series,
chapters: chapters,
newChapters: 0,
} as ViewEntry;
viewEntry.newChapters = newChapterCount(viewEntry);
return viewEntry;
}),
}))
.filter(e => !this.filter?.trim().length || e.entries.length) // hide empty lists when filtering
.sort((l, r) => order.indexOf(l.list.name.toLowerCase()) - order.indexOf(r.list.name.toLowerCase()));
}
}
</script>
<template>
<div class="overflow-y-auto manga-lists">
<MangaList v-for="viewList in viewLists" :key="viewList.list.name" :viewList="viewList" class="mb-3"/>
<div class="d-flex flex-column overflow-hidden">
<div>
<FilterBar v-model="filter"/>
</div>
<div class="flex-grow-1 overflow-y-auto">
<div class="manga-lists">
<MangaList v-for="viewList in viewLists" :key="viewList.list.name" :viewList="viewList" class="mb-3"/>
</div>
</div>
</div>
</template>

View File

@@ -3,7 +3,7 @@ export type AniListMedia = {
title: {
userPreferred: string,
romaji: string,
english: string,
english?: string,
native: string,
}
coverImage: {

View File

@@ -19,6 +19,7 @@ export const messagesDe = {
series: 'Kapitel',
},
},
filter: 'Filter',
locale: 'Sprache',
locales: messagesEn.locales,
localStorage: {

View File

@@ -17,6 +17,7 @@ export const messagesEn = {
series: 'Series',
},
},
filter: 'Filter',
locale: 'Language',
locales: {
'de': 'Deutsch',