mirror of
https://github.com/gotson/komga.git
synced 2025-12-20 15:34:17 +01:00
parent
9fc98ed9c1
commit
5d747d2cd3
4 changed files with 165 additions and 28 deletions
63
komga-webui/src/components/AlphabeticalNavigation.vue
Normal file
63
komga-webui/src/components/AlphabeticalNavigation.vue
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
<template>
|
||||
<div>
|
||||
<v-tooltip
|
||||
v-for="symbol in symbols"
|
||||
:key="symbol"
|
||||
:disabled="groupCount === undefined"
|
||||
top
|
||||
>
|
||||
<template v-slot:activator="{ on }">
|
||||
<v-btn
|
||||
text
|
||||
small
|
||||
icon
|
||||
@click="clicked(symbol)"
|
||||
:color="selected === symbol ? 'secondary' : undefined"
|
||||
:disabled="groupCount ? getCount(symbol) === 0 : false"
|
||||
v-on="on"
|
||||
>
|
||||
<!-- <v-icon>mdi-alpha-{{ symbol.toLowerCase() }}</v-icon>-->
|
||||
{{ symbol }}
|
||||
</v-btn>
|
||||
</template>
|
||||
{{ getCount(symbol) }}
|
||||
</v-tooltip>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue, {PropType} from 'vue'
|
||||
import {GroupCountDto} from '@/types/komga-series'
|
||||
|
||||
export default Vue.extend({
|
||||
name: 'AlphabeticalNavigation',
|
||||
data: function () {
|
||||
return {}
|
||||
},
|
||||
props: {
|
||||
groupCount: {
|
||||
type: Array as PropType<GroupCountDto[]>,
|
||||
required: false,
|
||||
},
|
||||
symbols: {
|
||||
type: Array,
|
||||
default: () => ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'],
|
||||
},
|
||||
selected: {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
clicked(symbol: string) {
|
||||
this.$emit('clicked', symbol)
|
||||
},
|
||||
getCount(symbol: string): number | undefined {
|
||||
if(!this.groupCount) return undefined
|
||||
const found = this.groupCount.find(g => g.group.toLowerCase() === symbol.toLowerCase())
|
||||
return found ? found.count : 0
|
||||
},
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import {AxiosInstance} from 'axios'
|
||||
import {AuthorDto, BookDto} from '@/types/komga-books'
|
||||
import {SeriesDto, SeriesMetadataUpdateDto} from '@/types/komga-series'
|
||||
import {GroupCountDto, SeriesDto, SeriesMetadataUpdateDto} from '@/types/komga-series'
|
||||
|
||||
const qs = require('qs')
|
||||
|
||||
|
|
@ -9,17 +9,19 @@ const API_SERIES = '/api/v1/series'
|
|||
export default class KomgaSeriesService {
|
||||
private http: AxiosInstance
|
||||
|
||||
constructor (http: AxiosInstance) {
|
||||
constructor(http: AxiosInstance) {
|
||||
this.http = http
|
||||
}
|
||||
|
||||
async getSeries (libraryId?: string, pageRequest?: PageRequest, search?: string, status?: string[],
|
||||
readStatus?: string[], genre?: string[], tag?: string[], language?: string[],
|
||||
publisher?: string[], ageRating?: string[], releaseDate?: string[], authors?: AuthorDto[]): Promise<Page<SeriesDto>> {
|
||||
async getSeries(libraryId?: string, pageRequest?: PageRequest, search?: string, status?: string[],
|
||||
readStatus?: string[], genre?: string[], tag?: string[], language?: string[],
|
||||
publisher?: string[], ageRating?: string[], releaseDate?: string[], authors?: AuthorDto[],
|
||||
searchRegex?: string): Promise<Page<SeriesDto>> {
|
||||
try {
|
||||
const params = { ...pageRequest } as any
|
||||
const params = {...pageRequest} as any
|
||||
if (libraryId) params.library_id = libraryId
|
||||
if (search) params.search = search
|
||||
if (searchRegex) params.search_regex = searchRegex
|
||||
if (status) params.status = status
|
||||
if (readStatus) params.read_status = readStatus
|
||||
if (genre) params.genre = genre
|
||||
|
|
@ -32,7 +34,7 @@ export default class KomgaSeriesService {
|
|||
|
||||
return (await this.http.get(API_SERIES, {
|
||||
params: params,
|
||||
paramsSerializer: params => qs.stringify(params, { indices: false }),
|
||||
paramsSerializer: params => qs.stringify(params, {indices: false}),
|
||||
})).data
|
||||
} catch (e) {
|
||||
let msg = 'An error occurred while trying to retrieve series'
|
||||
|
|
@ -43,9 +45,39 @@ export default class KomgaSeriesService {
|
|||
}
|
||||
}
|
||||
|
||||
async getNewSeries (libraryId?: string, pageRequest?: PageRequest): Promise<Page<SeriesDto>> {
|
||||
async getAlphabeticalGroups(libraryId?: string, search?: string, status?: string[],
|
||||
readStatus?: string[], genre?: string[], tag?: string[], language?: string[],
|
||||
publisher?: string[], ageRating?: string[], releaseDate?: string[], authors?: AuthorDto[]): Promise<GroupCountDto[]> {
|
||||
try {
|
||||
const params = { ...pageRequest } as any
|
||||
const params = {} as any
|
||||
if (libraryId) params.library_id = libraryId
|
||||
if (search) params.search = search
|
||||
if (status) params.status = status
|
||||
if (readStatus) params.read_status = readStatus
|
||||
if (genre) params.genre = genre
|
||||
if (tag) params.tag = tag
|
||||
if (language) params.language = language
|
||||
if (publisher) params.publisher = publisher
|
||||
if (ageRating) params.age_rating = ageRating
|
||||
if (releaseDate) params.release_year = releaseDate
|
||||
if (authors) params.author = authors.map(a => `${a.name},${a.role}`)
|
||||
|
||||
return (await this.http.get(`${API_SERIES}/alphabetical-groups`, {
|
||||
params: params,
|
||||
paramsSerializer: params => qs.stringify(params, {indices: false}),
|
||||
})).data
|
||||
} catch (e) {
|
||||
let msg = 'An error occurred while trying to retrieve series alphabetical groups'
|
||||
if (e.response.data.message) {
|
||||
msg += `: ${e.response.data.message}`
|
||||
}
|
||||
throw new Error(msg)
|
||||
}
|
||||
}
|
||||
|
||||
async getNewSeries(libraryId?: string, pageRequest?: PageRequest): Promise<Page<SeriesDto>> {
|
||||
try {
|
||||
const params = {...pageRequest} as any
|
||||
if (libraryId) {
|
||||
params.library_id = libraryId
|
||||
}
|
||||
|
|
@ -61,9 +93,9 @@ export default class KomgaSeriesService {
|
|||
}
|
||||
}
|
||||
|
||||
async getUpdatedSeries (libraryId?: string, pageRequest?: PageRequest): Promise<Page<SeriesDto>> {
|
||||
async getUpdatedSeries(libraryId?: string, pageRequest?: PageRequest): Promise<Page<SeriesDto>> {
|
||||
try {
|
||||
const params = { ...pageRequest } as any
|
||||
const params = {...pageRequest} as any
|
||||
if (libraryId) {
|
||||
params.library_id = libraryId
|
||||
}
|
||||
|
|
@ -79,7 +111,7 @@ export default class KomgaSeriesService {
|
|||
}
|
||||
}
|
||||
|
||||
async getOneSeries (seriesId: string): Promise<SeriesDto> {
|
||||
async getOneSeries(seriesId: string): Promise<SeriesDto> {
|
||||
try {
|
||||
return (await this.http.get(`${API_SERIES}/${seriesId}`)).data
|
||||
} catch (e) {
|
||||
|
|
@ -91,16 +123,16 @@ export default class KomgaSeriesService {
|
|||
}
|
||||
}
|
||||
|
||||
async getBooks (seriesId: string, pageRequest?: PageRequest, readStatus?: string[], tag?: string[], authors?: AuthorDto[]): Promise<Page<BookDto>> {
|
||||
async getBooks(seriesId: string, pageRequest?: PageRequest, readStatus?: string[], tag?: string[], authors?: AuthorDto[]): Promise<Page<BookDto>> {
|
||||
try {
|
||||
const params = { ...pageRequest } as any
|
||||
const params = {...pageRequest} as any
|
||||
if (readStatus) params.read_status = readStatus
|
||||
if (tag) params.tag = tag
|
||||
if (authors) params.author = authors.map(a => `${a.name},${a.role}`)
|
||||
|
||||
return (await this.http.get(`${API_SERIES}/${seriesId}/books`, {
|
||||
params: params,
|
||||
paramsSerializer: params => qs.stringify(params, { indices: false }),
|
||||
paramsSerializer: params => qs.stringify(params, {indices: false}),
|
||||
})).data
|
||||
} catch (e) {
|
||||
let msg = 'An error occurred while trying to retrieve books'
|
||||
|
|
@ -111,7 +143,7 @@ export default class KomgaSeriesService {
|
|||
}
|
||||
}
|
||||
|
||||
async getCollections (seriesId: string): Promise<CollectionDto[]> {
|
||||
async getCollections(seriesId: string): Promise<CollectionDto[]> {
|
||||
try {
|
||||
return (await this.http.get(`${API_SERIES}/${seriesId}/collections`)).data
|
||||
} catch (e) {
|
||||
|
|
@ -123,7 +155,7 @@ export default class KomgaSeriesService {
|
|||
}
|
||||
}
|
||||
|
||||
async analyzeSeries (series: SeriesDto) {
|
||||
async analyzeSeries(series: SeriesDto) {
|
||||
try {
|
||||
await this.http.post(`${API_SERIES}/${series.id}/analyze`)
|
||||
} catch (e) {
|
||||
|
|
@ -135,7 +167,7 @@ export default class KomgaSeriesService {
|
|||
}
|
||||
}
|
||||
|
||||
async refreshMetadata (series: SeriesDto) {
|
||||
async refreshMetadata(series: SeriesDto) {
|
||||
try {
|
||||
await this.http.post(`${API_SERIES}/${series.id}/metadata/refresh`)
|
||||
} catch (e) {
|
||||
|
|
@ -147,7 +179,7 @@ export default class KomgaSeriesService {
|
|||
}
|
||||
}
|
||||
|
||||
async updateMetadata (seriesId: string, metadata: SeriesMetadataUpdateDto) {
|
||||
async updateMetadata(seriesId: string, metadata: SeriesMetadataUpdateDto) {
|
||||
try {
|
||||
await this.http.patch(`${API_SERIES}/${seriesId}/metadata`, metadata)
|
||||
} catch (e) {
|
||||
|
|
@ -159,7 +191,7 @@ export default class KomgaSeriesService {
|
|||
}
|
||||
}
|
||||
|
||||
async markAsRead (seriesId: string) {
|
||||
async markAsRead(seriesId: string) {
|
||||
try {
|
||||
await this.http.post(`${API_SERIES}/${seriesId}/read-progress`)
|
||||
} catch (e) {
|
||||
|
|
@ -171,7 +203,7 @@ export default class KomgaSeriesService {
|
|||
}
|
||||
}
|
||||
|
||||
async markAsUnread (seriesId: string) {
|
||||
async markAsUnread(seriesId: string) {
|
||||
try {
|
||||
await this.http.delete(`${API_SERIES}/${seriesId}/read-progress`)
|
||||
} catch (e) {
|
||||
|
|
|
|||
|
|
@ -71,3 +71,8 @@ export interface SeriesMetadataUpdateDto {
|
|||
tags?: String[],
|
||||
tagsLock?: boolean
|
||||
}
|
||||
|
||||
export interface GroupCountDto {
|
||||
group: string,
|
||||
count: number,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -86,6 +86,14 @@
|
|||
/>
|
||||
|
||||
<template v-if="totalPages > 0">
|
||||
<alphabetical-navigation
|
||||
class="text-center"
|
||||
:symbols="alphabeticalNavigation"
|
||||
:selected="selectedSymbol"
|
||||
:group-count="seriesGroups"
|
||||
@clicked="filterByStarting"
|
||||
/>
|
||||
|
||||
<v-pagination
|
||||
v-if="totalPages > 1"
|
||||
v-model="page"
|
||||
|
|
@ -139,15 +147,17 @@ import SortList from '@/components/SortList.vue'
|
|||
import FilterPanels from '@/components/FilterPanels.vue'
|
||||
import FilterList from '@/components/FilterList.vue'
|
||||
import {mergeFilterParams, sortOrFilterActive, toNameValue} from '@/functions/filter'
|
||||
import {SeriesDto} from '@/types/komga-series'
|
||||
import {GroupCountDto, SeriesDto} from '@/types/komga-series'
|
||||
import {AuthorDto} from '@/types/komga-books'
|
||||
import {authorRoles} from '@/types/author-roles'
|
||||
import {LibrarySseDto, ReadProgressSeriesSseDto, SeriesSseDto} from '@/types/komga-sse'
|
||||
import {throttle} from 'lodash'
|
||||
import AlphabeticalNavigation from '@/components/AlphabeticalNavigation.vue'
|
||||
|
||||
export default Vue.extend({
|
||||
name: 'BrowseLibraries',
|
||||
components: {
|
||||
AlphabeticalNavigation,
|
||||
LibraryActionsMenu,
|
||||
EmptyState,
|
||||
ToolbarSticky,
|
||||
|
|
@ -164,6 +174,10 @@ export default Vue.extend({
|
|||
return {
|
||||
library: undefined as LibraryDto | undefined,
|
||||
series: [] as SeriesDto[],
|
||||
seriesGroups: [] as GroupCountDto[],
|
||||
alphabeticalNavigation: ['ALL', '#', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'],
|
||||
searchRegex: undefined as any,
|
||||
selectedSymbol: 'ALL',
|
||||
selectedSeries: [] as SeriesDto[],
|
||||
page: 1,
|
||||
pageSize: 20,
|
||||
|
|
@ -234,6 +248,9 @@ export default Vue.extend({
|
|||
this.totalPages = 1
|
||||
this.totalElements = null
|
||||
this.series = []
|
||||
this.seriesGroups = []
|
||||
this.selectedSymbol = 'ALL'
|
||||
this.searchRegex = undefined
|
||||
|
||||
this.loadLibrary(to.params.libraryId)
|
||||
|
||||
|
|
@ -313,6 +330,15 @@ export default Vue.extend({
|
|||
},
|
||||
},
|
||||
methods: {
|
||||
filterByStarting(symbol: string) {
|
||||
if (symbol === 'ALL') this.searchRegex = undefined
|
||||
else if (symbol === '#') this.searchRegex = '^[^a-z],title_sort'
|
||||
else this.searchRegex = `^${symbol},title_sort`
|
||||
|
||||
this.selectedSymbol = symbol
|
||||
this.page = 1
|
||||
this.loadPage(this.libraryId, 1, this.sortActive, this.searchRegex)
|
||||
},
|
||||
resetSortAndFilters() {
|
||||
this.drawer = false
|
||||
for (const prop in this.filters) {
|
||||
|
|
@ -341,7 +367,7 @@ export default Vue.extend({
|
|||
])
|
||||
this.$set(this.filterOptions, 'genre', toNameValue(genres))
|
||||
this.$set(this.filterOptions, 'tag', toNameValue(tags))
|
||||
this.$set(this.filterOptions, 'publisher', toNameValue(publishers ))
|
||||
this.$set(this.filterOptions, 'publisher', toNameValue(publishers))
|
||||
this.$set(this.filterOptions, 'language', (languages))
|
||||
this.$set(this.filterOptions, 'ageRating', toNameValue(ageRatings))
|
||||
this.$set(this.filterOptions, 'releaseDate', toNameValue(releaseDates))
|
||||
|
|
@ -406,7 +432,7 @@ export default Vue.extend({
|
|||
|
||||
this.pageUnwatch = this.$watch('page', (val) => {
|
||||
this.updateRoute()
|
||||
this.loadPage(this.libraryId, val, this.sortActive)
|
||||
this.loadPage(this.libraryId, val, this.sortActive, this.searchRegex)
|
||||
})
|
||||
},
|
||||
unsetWatches() {
|
||||
|
|
@ -421,7 +447,7 @@ export default Vue.extend({
|
|||
this.page = 1
|
||||
|
||||
this.updateRoute()
|
||||
this.loadPage(this.libraryId, this.page, this.sortActive)
|
||||
this.loadPage(this.libraryId, this.page, this.sortActive, this.searchRegex)
|
||||
|
||||
this.setWatches()
|
||||
},
|
||||
|
|
@ -441,7 +467,7 @@ export default Vue.extend({
|
|||
async loadLibrary(libraryId: string) {
|
||||
this.library = this.getLibraryLazy(libraryId)
|
||||
|
||||
await this.loadPage(libraryId, this.page, this.sortActive)
|
||||
await this.loadPage(libraryId, this.page, this.sortActive, this.searchRegex)
|
||||
},
|
||||
updateRoute() {
|
||||
const loc = {
|
||||
|
|
@ -460,7 +486,7 @@ export default Vue.extend({
|
|||
reloadPage: throttle(function (this: any) {
|
||||
this.loadPage(this.libraryId, this.page, this.sortActive)
|
||||
}, 1000),
|
||||
async loadPage(libraryId: string, page: number, sort: SortActive) {
|
||||
async loadPage(libraryId: string, page: number, sort: SortActive, searchRegex?: string) {
|
||||
this.selectedSeries = []
|
||||
|
||||
const pageRequest = {
|
||||
|
|
@ -481,11 +507,22 @@ export default Vue.extend({
|
|||
})
|
||||
|
||||
const requestLibraryId = libraryId !== LIBRARIES_ALL ? libraryId : undefined
|
||||
const seriesPage = await this.$komgaSeries.getSeries(requestLibraryId, pageRequest, undefined, this.filters.status, replaceCompositeReadStatus(this.filters.readStatus), this.filters.genre, this.filters.tag, this.filters.language, this.filters.publisher, this.filters.ageRating, this.filters.releaseDate, authorsFilter)
|
||||
const seriesPage = await this.$komgaSeries.getSeries(requestLibraryId, pageRequest, undefined, this.filters.status, replaceCompositeReadStatus(this.filters.readStatus), this.filters.genre, this.filters.tag, this.filters.language, this.filters.publisher, this.filters.ageRating, this.filters.releaseDate, authorsFilter, searchRegex)
|
||||
|
||||
this.totalPages = seriesPage.totalPages
|
||||
this.totalElements = seriesPage.totalElements
|
||||
this.series = seriesPage.content
|
||||
|
||||
const seriesGroups = await this.$komgaSeries.getAlphabeticalGroups(requestLibraryId, undefined, this.filters.status, replaceCompositeReadStatus(this.filters.readStatus), this.filters.genre, this.filters.tag, this.filters.language, this.filters.publisher, this.filters.ageRating, this.filters.releaseDate, authorsFilter)
|
||||
const nonAlpha = seriesGroups
|
||||
.filter((g) => !(/[a-zA-Z]/).test(g.group))
|
||||
.reduce((a, b) => a + b.count, 0)
|
||||
const all = seriesGroups.reduce((a, b) => a + b.count, 0)
|
||||
this.seriesGroups = [
|
||||
...seriesGroups.filter((g) => (/[a-zA-Z]/).test(g.group)),
|
||||
{group: '#', count: nonAlpha} as GroupCountDto,
|
||||
{group: 'ALL', count: all} as GroupCountDto,
|
||||
]
|
||||
},
|
||||
getLibraryLazy(libraryId: string): LibraryDto | undefined {
|
||||
if (libraryId !== LIBRARIES_ALL) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue