feat(webui): make authors chips clickable

closes #431 

Co-authored-by: Gauthier Roebroeck <gauthier.roebroeck@gmail.com>
This commit is contained in:
Ben Kuskopf 2021-03-02 15:38:05 +10:00 committed by GitHub
parent 54e912e188
commit 9fed50e405
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 21157 additions and 136 deletions

File diff suppressed because it is too large Load diff

View file

@ -66,6 +66,7 @@
"typescript": "^3.9.7",
"vue-cli-plugin-i18n": "~1.0.1",
"vue-cli-plugin-vuetify": "^2.0.7",
"vue-horizontal": "^0.8.8",
"vue-i18n-extract": "^1.1.11",
"vue-template-compiler": "^2.6.11",
"vuetify-loader": "^1.6.0"

View file

@ -20,7 +20,8 @@
dense
border="right"
class="mb-0"
>{{ $t('browse_book.navigation_within_readlist') }}: {{ contextName }}</v-alert>
>{{ $t('browse_book.navigation_within_readlist') }}: {{ contextName }}
</v-alert>
<!-- Navigate to previous book -->
<v-btn
@ -115,30 +116,68 @@
</v-col>
</v-row>
<v-row class="text-body-2"
v-for="(names, key) in authorsByRole"
:key="key"
<v-divider v-if="book.metadata.authors.length > 0"/>
<v-row class="align-center text-body-2"
v-for="role in authorRoles"
:key="role"
>
<v-col cols="6" sm="4" md="3" class="py-1 text-uppercase">{{ key }}</v-col>
<v-col class="py-1 text-truncate" :title="names.join(', ')">
{{ names.join(', ') }}
<v-col cols="6" sm="4" md="3" class="py-1 text-uppercase">{{ $t(`author_roles.${role}`) }}</v-col>
<v-col cols="6" sm="8" md="9" class="py-1">
<vue-horizontal>
<template v-slot:btn-prev>
<v-btn icon small>
<v-icon>mdi-chevron-left</v-icon>
</v-btn>
</template>
<template v-slot:btn-next>
<v-btn icon small>
<v-icon>mdi-chevron-right</v-icon>
</v-btn>
</template>
<v-chip v-for="(name, i) in authorsByRole[role]"
:key="i"
:class="$vuetify.rtl ? 'ml-2' : 'mr-2'"
:title="name"
:to="{name:'browse-series', params: {seriesId: book.seriesId }, query: {[role]: name}}"
label
small
outlined
link
>{{ name }}
</v-chip>
</vue-horizontal>
</v-col>
</v-row>
<v-row v-if="book.metadata.tags.length > 0">
<v-col cols="6" sm="4" md="3" class="text-body-2 py-1">TAGS</v-col>
<v-col class="text-body-2 text-capitalize py-1">
<v-chip v-for="(t, i) in book.metadata.tags"
:key="i"
:class="$vuetify.rtl ? 'ml-2' : 'mr-2'"
:title="t"
:to="{name:'browse-series', params: {seriesId: book.seriesId}, query: {tag: t}}"
label
small
outlined
link
>{{ t }}
</v-chip>
<v-row v-if="book.metadata.tags.length > 0" class="align-center text-body-2">
<v-col cols="6" sm="4" md="3" class="py-1">TAGS</v-col>
<v-col cols="6" sm="8" md="9" class="py-1 text-capitalize">
<vue-horizontal>
<template v-slot:btn-prev>
<v-btn icon small>
<v-icon>mdi-chevron-left</v-icon>
</v-btn>
</template>
<template v-slot:btn-next>
<v-btn icon small>
<v-icon>mdi-chevron-right</v-icon>
</v-btn>
</template>
<v-chip v-for="(t, i) in book.metadata.tags"
:key="i"
:class="$vuetify.rtl ? 'ml-2' : 'mr-2'"
:title="t"
:to="{name:'browse-series', params: {seriesId: book.seriesId}, query: {tag: t}}"
label
small
outlined
link
>{{ t }}
</v-chip>
</vue-horizontal>
</v-col>
</v-row>
@ -216,7 +255,7 @@
import BookActionsMenu from '@/components/menus/BookActionsMenu.vue'
import ItemCard from '@/components/ItemCard.vue'
import ToolbarSticky from '@/components/bars/ToolbarSticky.vue'
import {groupAuthorsByRoleI18n} from '@/functions/authors'
import {groupAuthorsByRole} from '@/functions/authors'
import {getBookFormatFromMediaType} from '@/functions/book-format'
import {getReadProgress, getReadProgressPercentage} from '@/functions/book-progress'
import {getBookTitleCompact} from '@/functions/book-title'
@ -229,12 +268,15 @@ import {BookDto, BookFormat} from '@/types/komga-books'
import {Context, ContextOrigin} from '@/types/context'
import {SeriesDto} from "@/types/komga-series";
import ReadMore from "@/components/ReadMore.vue";
import VueHorizontal from "vue-horizontal";
import {authorRoles} from "@/types/author-roles";
export default Vue.extend({
name: 'BrowseBook',
components: {ReadMore, ToolbarSticky, ItemCard, BookActionsMenu, ReadListsExpansionPanels },
components: {ReadMore, ToolbarSticky, ItemCard, BookActionsMenu, ReadListsExpansionPanels, VueHorizontal},
data: () => {
return {
authorRoles,
book: {} as BookDto,
series: {} as SeriesDto,
context: {} as Context,
@ -245,12 +287,12 @@ export default Vue.extend({
readLists: [] as ReadListDto[],
}
},
async created () {
async created() {
this.loadBook(this.bookId)
this.$eventHub.$on(BOOK_CHANGED, this.reloadBook)
this.$eventHub.$on(LIBRARY_DELETED, this.libraryDeleted)
},
beforeDestroy () {
beforeDestroy() {
this.$eventHub.$off(BOOK_CHANGED, this.reloadBook)
this.$eventHub.$off(LIBRARY_DELETED, this.libraryDeleted)
},
@ -260,7 +302,7 @@ export default Vue.extend({
required: true,
},
},
async beforeRouteUpdate (to, from, next) {
async beforeRouteUpdate(to, from, next) {
if (to.params.bookId !== from.params.bookId) {
this.loadBook(to.params.bookId)
}
@ -268,59 +310,59 @@ export default Vue.extend({
next()
},
computed: {
isAdmin (): boolean {
isAdmin(): boolean {
return this.$store.getters.meAdmin
},
canReadPages (): boolean {
canReadPages(): boolean {
return this.$store.getters.mePageStreaming
},
canDownload (): boolean {
canDownload(): boolean {
return this.$store.getters.meFileDownload
},
thumbnailUrl (): string {
thumbnailUrl(): string {
return bookThumbnailUrl(this.bookId)
},
fileUrl (): string {
fileUrl(): string {
return bookFileUrl(this.bookId)
},
format (): BookFormat {
format(): BookFormat {
return getBookFormatFromMediaType(this.book.media.mediaType)
},
authorsByRole (): any {
return groupAuthorsByRoleI18n(this.book.metadata.authors)
return groupAuthorsByRole(this.book.metadata.authors)
},
isRead (): boolean {
isRead(): boolean {
return getReadProgress(this.book) === ReadStatus.READ
},
isUnread (): boolean {
isUnread(): boolean {
return getReadProgress(this.book) === ReadStatus.UNREAD
},
isInProgress (): boolean {
isInProgress(): boolean {
return getReadProgress(this.book) === ReadStatus.IN_PROGRESS
},
readProgressPercentage (): number {
readProgressPercentage(): number {
return getReadProgressPercentage(this.book)
},
previousId (): string {
previousId(): string {
return this.siblingPrevious?.id?.toString() || '0'
},
nextId (): string {
nextId(): string {
return this.siblingNext?.id?.toString() || '0'
},
contextReadList (): boolean {
contextReadList(): boolean {
return this.context.origin === ContextOrigin.READLIST
},
},
methods: {
libraryDeleted (event: EventLibraryDeleted) {
libraryDeleted(event: EventLibraryDeleted) {
if (event.id === this.book.libraryId) {
this.$router.push({ name: 'home' })
this.$router.push({name: 'home'})
}
},
reloadBook (event: EventBookChanged) {
reloadBook(event: EventBookChanged) {
if (event.id === this.bookId) this.loadBook(this.bookId)
},
async loadBook (bookId: string) {
async loadBook(bookId: string) {
this.book = await this.$komgaBooks.getBook(bookId)
this.series = await this.$komgaSeries.getOneSeries(this.book.seriesId)
@ -337,9 +379,9 @@ export default Vue.extend({
// Get siblings depending on origin
if (this?.context.origin === ContextOrigin.READLIST) {
this.siblings = (await this.$komgaReadLists.getBooks(this.context.id, { unpaged: true } as PageRequest)).content
this.siblings = (await this.$komgaReadLists.getBooks(this.context.id, {unpaged: true} as PageRequest)).content
} else {
this.siblings = (await this.$komgaSeries.getBooks(this.book.seriesId, { unpaged: true } as PageRequest)).content
this.siblings = (await this.$komgaSeries.getBooks(this.book.seriesId, {unpaged: true} as PageRequest)).content
}
this.readLists = await this.$komgaBooks.getReadLists(this.bookId)
@ -367,13 +409,13 @@ export default Vue.extend({
this.siblingPrevious = {} as BookDto
}
},
analyze () {
analyze() {
this.$komgaBooks.analyzeBook(this.book)
},
refreshMetadata () {
refreshMetadata() {
this.$komgaBooks.refreshMetadata(this.book)
},
editBook () {
editBook() {
this.$store.dispatch('dialogUpdateBooks', this.book)
},
},

View file

@ -4,7 +4,7 @@
<!-- Go back to parent library -->
<v-btn icon
:title="$t('common.go_to_library')"
:to="{name:'browse-libraries', params: {libraryId: series.libraryId ? series.libraryId : 0 }}"
:to="{name:'browse-libraries', params: {libraryId: series.libraryId }}"
>
<v-icon v-if="$vuetify.rtl">mdi-arrow-right</v-icon>
<v-icon v-else>mdi-arrow-left</v-icon>
@ -90,36 +90,38 @@
<v-row class="text-body-2">
<v-col>
<v-chip
label
small
link
:color="statusChip.color"
:text-color="statusChip.text"
:to="{name:'browse-libraries', params: {libraryId: series.libraryId ? series.libraryId : 0 }, query: {status: series.metadata.status}}"
label
small
link
:color="statusChip.color"
:text-color="statusChip.text"
:to="{name:'browse-libraries', params: {libraryId: series.libraryId }, query: {status: series.metadata.status}}"
>
{{ $t(`enums.series_status.${series.metadata.status}`) }}
</v-chip>
<v-chip
label
small
link
v-if="series.metadata.ageRating"
:to="{name:'browse-libraries', params: {libraryId: series.libraryId ? series.libraryId : 0 }, query: {ageRating: series.metadata.ageRating}}"
class="mx-1"
label
small
link
v-if="series.metadata.ageRating"
:to="{name:'browse-libraries', params: {libraryId: series.libraryId }, query: {ageRating: series.metadata.ageRating}}"
class="mx-1"
>
{{series.metadata.ageRating}}+
{{ series.metadata.ageRating }}+
</v-chip>
<v-chip
label
small
link
:to="{name:'browse-libraries', params: {libraryId: series.libraryId ? series.libraryId : 0 }, query: {language: series.metadata.language}}"
v-if="series.metadata.language"
class="mx-1"
label
small
link
:to="{name:'browse-libraries', params: {libraryId: series.libraryId }, query: {language: series.metadata.language}}"
v-if="series.metadata.language"
class="mx-1"
>
{{ languageDisplay }}
</v-chip>
<v-chip label small v-if="series.metadata.readingDirection" class="mx-1">{{ $t(`enums.reading_direction.${series.metadata.readingDirection}`) }}</v-chip>
<v-chip label small v-if="series.metadata.readingDirection" class="mx-1">
{{ $t(`enums.reading_direction.${series.metadata.readingDirection}`) }}
</v-chip>
</v-col>
</v-row>
@ -134,7 +136,7 @@
<v-tooltip right>
<template v-slot:activator="{ on }">
<span v-on="on" class="text-caption">
{{ $t('browse_series.summary_from_book',{number: series.booksMetadata.summaryNumber})}}
{{ $t('browse_series.summary_from_book', {number: series.booksMetadata.summaryNumber}) }}
</span>
</template>
{{ $t('browse_series.series_no_summary') }}
@ -155,65 +157,115 @@
</v-col>
</v-row>
<v-row v-if="series.metadata.publisher">
<v-col cols="6" sm="4" md="3" class="text-body-2 py-1 text-uppercase">{{ $t('common.publisher') }}</v-col>
<v-col class="text-body-2 text-capitalize py-1">
<v-row v-if="series.metadata.publisher" class="align-center text-body-2">
<v-col cols="6" sm="4" md="3" class="py-1 text-uppercase">{{ $t('common.publisher') }}</v-col>
<v-col class="py-1">
<v-chip
:class="$vuetify.rtl ? 'ml-2' : 'mr-2'"
:title="series.metadata.publisher"
:to="{name:'browse-libraries', params: {libraryId: series.libraryId ? series.libraryId : 0 }, query: {publisher: series.metadata.publisher}}"
label
small
outlined
link
:class="$vuetify.rtl ? 'ml-2' : 'mr-2'"
:title="series.metadata.publisher"
:to="{name:'browse-libraries', params: {libraryId: series.libraryId }, query: {publisher: series.metadata.publisher}}"
label
small
outlined
link
>{{ series.metadata.publisher }}
</v-chip>
</v-col>
</v-row>
<v-row v-if="series.metadata.genres.length > 0">
<v-col cols="6" sm="4" md="3" class="text-body-2 py-1 text-uppercase">{{ $t('common.genre') }}</v-col>
<v-col class="text-body-2 text-capitalize py-1">
<v-chip v-for="(t, i) in series.metadata.genres"
:key="i"
:class="$vuetify.rtl ? 'ml-2' : 'mr-2'"
:title="t"
:to="{name:'browse-libraries', params: {libraryId: series.libraryId ? series.libraryId : 0 }, query: {genre: t}}"
label
small
outlined
link
>{{ t }}
</v-chip>
<v-row v-if="series.metadata.genres.length > 0" class="align-center text-body-2">
<v-col cols="6" sm="4" md="3" class="py-1 text-uppercase">{{ $t('common.genre') }}</v-col>
<v-col cols="6" sm="8" md="9" class="py-1 text-capitalize">
<vue-horizontal>
<template v-slot:btn-prev>
<v-btn icon small>
<v-icon>mdi-chevron-left</v-icon>
</v-btn>
</template>
<template v-slot:btn-next>
<v-btn icon small>
<v-icon>mdi-chevron-right</v-icon>
</v-btn>
</template>
<v-chip v-for="(t, i) in series.metadata.genres"
:key="i"
:class="$vuetify.rtl ? 'ml-2' : 'mr-2'"
:title="t"
:to="{name:'browse-libraries', params: {libraryId: series.libraryId }, query: {genre: t}}"
label
small
outlined
link
>{{ t }}
</v-chip>
</vue-horizontal>
</v-col>
</v-row>
<v-row v-if="series.metadata.tags.length > 0">
<v-col cols="6" sm="4" md="3" class="text-body-2 py-1 text-uppercase">{{ $t('common.tags') }}</v-col>
<v-col class="text-body-2 text-capitalize py-1">
<v-chip v-for="(t, i) in series.metadata.tags"
:key="i"
:class="$vuetify.rtl ? 'ml-2' : 'mr-2'"
:title="t"
:to="{name:'browse-libraries', params: {libraryId: series.libraryId ? series.libraryId : 0 }, query: {tag: t}}"
label
small
outlined
link
>{{ t }}
</v-chip>
<v-row v-if="series.metadata.tags.length > 0" class="align-center text-body-2">
<v-col cols="6" sm="4" md="3" class="py-1 text-uppercase">{{ $t('common.tags') }}</v-col>
<v-col cols="6" sm="8" md="9" class="py-1 text-capitalize">
<vue-horizontal>
<template v-slot:btn-prev>
<v-btn icon small>
<v-icon>mdi-chevron-left</v-icon>
</v-btn>
</template>
<template v-slot:btn-next>
<v-btn icon small>
<v-icon>mdi-chevron-right</v-icon>
</v-btn>
</template>
<v-chip v-for="(t, i) in series.metadata.tags"
:key="i"
:class="$vuetify.rtl ? 'ml-2' : 'mr-2'"
:title="t"
:to="{name:'browse-libraries', params: {libraryId: series.libraryId }, query: {tag: t}}"
label
small
outlined
link
>{{ t }}
</v-chip>
</vue-horizontal>
</v-col>
</v-row>
<v-divider v-if="series.booksMetadata.authors.length > 0"/>
<v-row class="text-body-2"
v-for="(names, key) in authorsByRole"
:key="key"
<v-row class="align-center text-body-2"
v-for="role in authorRoles"
:key="role"
>
<v-col cols="6" sm="4" md="3" class="py-1 text-uppercase">{{ key }}</v-col>
<v-col class="py-1 text-truncate" :title="names.join(', ')">
{{ names.join(', ') }}
<v-col cols="6" sm="4" md="3" class="py-1 text-uppercase">{{ $t(`author_roles.${role}`) }}</v-col>
<v-col cols="6" sm="8" md="9" class="py-1">
<vue-horizontal>
<template v-slot:btn-prev>
<v-btn icon small>
<v-icon>mdi-chevron-left</v-icon>
</v-btn>
</template>
<template v-slot:btn-next>
<v-btn icon small>
<v-icon>mdi-chevron-right</v-icon>
</v-btn>
</template>
<v-chip v-for="(name, i) in authorsByRole[role]"
:key="i"
:class="$vuetify.rtl ? 'ml-2' : 'mr-2'"
:title="name"
:to="{name:'browse-libraries', params: {libraryId: series.libraryId }, query: {[role]: name}}"
label
small
outlined
link
>{{ name }}
</v-chip>
</vue-horizontal>
</v-col>
</v-row>
@ -291,9 +343,10 @@ import SortList from '@/components/SortList.vue'
import {mergeFilterParams, sortOrFilterActive, toNameValue} from '@/functions/filter'
import FilterPanels from '@/components/FilterPanels.vue'
import {SeriesDto} from "@/types/komga-series";
import {groupAuthorsByRole, groupAuthorsByRoleI18n} from "@/functions/authors";
import {groupAuthorsByRole} from "@/functions/authors";
import ReadMore from "@/components/ReadMore.vue";
import {authorRoles} from "@/types/author-roles";
import VueHorizontal from "vue-horizontal";
const tags = require('language-tags')
@ -315,9 +368,11 @@ export default Vue.extend({
FilterPanels,
SortList,
ReadMore,
VueHorizontal,
},
data: function () {
return {
authorRoles,
series: {} as SeriesDto,
books: [] as BookDto[],
selectedBooks: [] as BookDto[],
@ -401,7 +456,7 @@ export default Vue.extend({
return sortOrFilterActive(this.sortActive, this.sortDefault, this.filters)
},
authorsByRole(): any {
return groupAuthorsByRoleI18n(this.series.booksMetadata.authors)
return groupAuthorsByRole(this.series.booksMetadata.authors)
},
},
props: {