mirror of
https://github.com/advplyr/audiobookshelf-app.git
synced 2025-07-20 18:54:38 +02:00
Update search page for new data model and adding podcasts
This commit is contained in:
parent
021538820a
commit
24d124e2e8
7 changed files with 80 additions and 51 deletions
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<div @mouseover="mouseover" @mouseout="mouseout">
|
<div>
|
||||||
<div :style="{ width: width + 'px', height: height + 'px' }" class="bg-primary box-shadow-book rounded-md relative overflow-hidden">
|
<div :style="{ width: width + 'px', height: height + 'px' }" class="bg-primary box-shadow-book rounded-md relative overflow-hidden">
|
||||||
<!-- Image or placeholder -->
|
<!-- Image or placeholder -->
|
||||||
<covers-author-image :author="author" />
|
<covers-author-image :author="author" />
|
||||||
|
@ -10,14 +10,6 @@
|
||||||
<p class="text-center text-gray-200" :style="{ fontSize: sizeMultiplier * 0.65 + 'rem' }">{{ numBooks }} Book{{ numBooks === 1 ? '' : 's' }}</p>
|
<p class="text-center text-gray-200" :style="{ fontSize: sizeMultiplier * 0.65 + 'rem' }">{{ numBooks }} Book{{ numBooks === 1 ? '' : 's' }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Search icon btn -->
|
|
||||||
<div v-show="!searching && isHovering" class="absolute top-0 left-0 p-2 cursor-pointer hover:text-white text-gray-200 transform transition-transform hover:scale-125" @click.prevent.stop="searchAuthor">
|
|
||||||
<span class="material-icons text-lg">search</span>
|
|
||||||
</div>
|
|
||||||
<div v-show="isHovering && !searching" class="absolute top-0 right-0 p-2 cursor-pointer hover:text-white text-gray-200 transform transition-transform hover:scale-125" @click.prevent.stop="$emit('edit', author)">
|
|
||||||
<span class="material-icons text-lg">edit</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Loading spinner -->
|
<!-- Loading spinner -->
|
||||||
<div v-show="searching" class="absolute top-0 left-0 z-10 w-full h-full bg-black bg-opacity-50 flex items-center justify-center">
|
<div v-show="searching" class="absolute top-0 left-0 z-10 w-full h-full bg-black bg-opacity-50 flex items-center justify-center">
|
||||||
<widgets-loading-spinner size="" />
|
<widgets-loading-spinner size="" />
|
||||||
|
@ -46,8 +38,7 @@ export default {
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
searching: false,
|
searching: false
|
||||||
isHovering: false
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
@ -68,12 +59,6 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
mouseover() {
|
|
||||||
this.isHovering = true
|
|
||||||
},
|
|
||||||
mouseout() {
|
|
||||||
this.isHovering = false
|
|
||||||
},
|
|
||||||
async searchAuthor() {
|
async searchAuthor() {
|
||||||
this.searching = true
|
this.searching = true
|
||||||
var response = await this.$axios.$post(`/api/authors/${this.authorId}/match`, { q: this.name }).catch((error) => {
|
var response = await this.$axios.$post(`/api/authors/${this.authorId}/match`, { q: this.name }).catch((error) => {
|
||||||
|
|
|
@ -1,6 +1,13 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="flex h-full px-1 overflow-hidden shadow-sm">
|
<div class="flex h-full px-1 overflow-hidden">
|
||||||
<!-- <img src="/icons/NoUserPhoto.png" class="w-40 h-40 max-h-40 object-contain" style="max-height: 40px; max-width: 40px" /> -->
|
<div class="overflow-hidden bg-primary rounded" style="height: 50px; width: 40px">
|
||||||
|
<covers-author-image :author="author" />
|
||||||
|
</div>
|
||||||
|
<div class="flex-grow px-2 authorSearchCardContent h-full">
|
||||||
|
<p class="truncate text-sm">{{ name }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- <div class="flex h-full px-1 overflow-hidden shadow-sm">
|
||||||
<div style="max-height: 48px; max-width: 48px" class="w-12 h-12 bg-primary overflow-hidden rounded">
|
<div style="max-height: 48px; max-width: 48px" class="w-12 h-12 bg-primary overflow-hidden rounded">
|
||||||
<svg width="140%" height="140%" style="margin-left: -20%; margin-top: -20%; opacity: 0.6" viewBox="0 0 177 266" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="140%" height="140%" style="margin-left: -20%; margin-top: -20%; opacity: 0.6" viewBox="0 0 177 266" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path fill="white" d="M40.7156 165.47C10.2694 150.865 -31.5407 148.629 -38.0532 155.529L63.3191 204.159L76.9443 190.899C66.828 181.394 54.006 171.846 40.7156 165.47Z" stroke="white" stroke-width="4" transform="translate(-2 -1)" />
|
<path fill="white" d="M40.7156 165.47C10.2694 150.865 -31.5407 148.629 -38.0532 155.529L63.3191 204.159L76.9443 190.899C66.828 181.394 54.006 171.846 40.7156 165.47Z" stroke="white" stroke-width="4" transform="translate(-2 -1)" />
|
||||||
|
@ -19,18 +26,25 @@
|
||||||
<div class="flex-grow px-2 authorSearchCardContent h-full">
|
<div class="flex-grow px-2 authorSearchCardContent h-full">
|
||||||
<p class="truncate text-sm">{{ author }}</p>
|
<p class="truncate text-sm">{{ author }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div> -->
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
author: String
|
author: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {}
|
return {}
|
||||||
},
|
},
|
||||||
computed: {},
|
computed: {
|
||||||
|
name() {
|
||||||
|
return this.author.name
|
||||||
|
}
|
||||||
|
},
|
||||||
methods: {},
|
methods: {},
|
||||||
mounted() {}
|
mounted() {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="flex h-full px-1 overflow-hidden">
|
<div class="flex h-full px-1 overflow-hidden">
|
||||||
<covers-book-cover :audiobook="audiobook" :width="coverWidth" :book-cover-aspect-ratio="bookCoverAspectRatio" />
|
<covers-book-cover :library-item="libraryItem" :width="coverWidth" :book-cover-aspect-ratio="bookCoverAspectRatio" />
|
||||||
<div class="flex-grow px-2 h-full audiobookSearchCardContent">
|
<div class="flex-grow px-2 audiobookSearchCardContent">
|
||||||
<p v-if="matchKey !== 'title'" class="truncate text-sm">{{ title }}</p>
|
<p v-if="matchKey !== 'title'" class="truncate text-sm">{{ title }}</p>
|
||||||
<p v-else class="truncate text-sm" v-html="matchHtml" />
|
<p v-else class="truncate text-sm" v-html="matchHtml" />
|
||||||
|
|
||||||
<p v-if="matchKey === 'subtitle'" class="truncate text-xs text-gray-300">{{ matchHtml }}</p>
|
<p v-if="matchKey === 'subtitle'" class="truncate text-xs text-gray-300">{{ matchHtml }}</p>
|
||||||
|
|
||||||
<p v-if="matchKey !== 'authorFL'" class="text-xs text-gray-200 truncate">by {{ authorFL }}</p>
|
<p v-if="matchKey !== 'authors'" class="text-xs text-gray-200 truncate">by {{ authorName }}</p>
|
||||||
<p v-else class="truncate text-xs text-gray-200" v-html="matchHtml" />
|
<p v-else class="truncate text-xs text-gray-200" v-html="matchHtml" />
|
||||||
|
|
||||||
<div v-if="matchKey === 'series' || matchKey === 'tags'" class="m-0 p-0 truncate" v-html="matchHtml" />
|
<div v-if="matchKey === 'series' || matchKey === 'tags' || matchKey === 'isbn' || matchKey === 'asin'" class="m-0 p-0 truncate text-xs" v-html="matchHtml" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -18,7 +18,7 @@
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
audiobook: {
|
libraryItem: {
|
||||||
type: Object,
|
type: Object,
|
||||||
default: () => {}
|
default: () => {}
|
||||||
},
|
},
|
||||||
|
@ -37,17 +37,27 @@ export default {
|
||||||
if (this.bookCoverAspectRatio === 1) return 50 * 1.2
|
if (this.bookCoverAspectRatio === 1) return 50 * 1.2
|
||||||
return 50
|
return 50
|
||||||
},
|
},
|
||||||
book() {
|
media() {
|
||||||
return this.audiobook ? this.audiobook.book || {} : {}
|
return this.libraryItem ? this.libraryItem.media || {} : {}
|
||||||
|
},
|
||||||
|
mediaMetadata() {
|
||||||
|
return this.media.metadata || {}
|
||||||
|
},
|
||||||
|
mediaType() {
|
||||||
|
return this.libraryItem ? this.libraryItem.mediaType : null
|
||||||
|
},
|
||||||
|
isPodcast() {
|
||||||
|
return this.mediaType === 'podcast'
|
||||||
},
|
},
|
||||||
title() {
|
title() {
|
||||||
return this.book ? this.book.title : 'No Title'
|
return this.mediaMetadata.title || 'No Title'
|
||||||
},
|
},
|
||||||
subtitle() {
|
subtitle() {
|
||||||
return this.book ? this.book.subtitle : ''
|
return this.mediaMetadata.subtitle
|
||||||
},
|
},
|
||||||
authorFL() {
|
authorName() {
|
||||||
return this.book ? this.book.authorFL : 'Unknown'
|
if (this.isPodcast) return this.mediaMetadata.author
|
||||||
|
return this.mediaMetadata.authorName
|
||||||
},
|
},
|
||||||
matchHtml() {
|
matchHtml() {
|
||||||
if (!this.matchText || !this.search) return ''
|
if (!this.matchText || !this.search) return ''
|
||||||
|
@ -69,7 +79,9 @@ export default {
|
||||||
html += lastPart
|
html += lastPart
|
||||||
|
|
||||||
if (this.matchKey === 'tags') return `<p class="truncate">Tags: ${html}</p>`
|
if (this.matchKey === 'tags') return `<p class="truncate">Tags: ${html}</p>`
|
||||||
if (this.matchKey === 'authorFL') return `by ${html}`
|
if (this.matchKey === 'authors') return `by ${html}`
|
||||||
|
if (this.matchKey === 'isbn') return `<p class="truncate">ISBN: ${html}</p>`
|
||||||
|
if (this.matchKey === 'asin') return `<p class="truncate">ASIN: ${html}</p>`
|
||||||
if (this.matchKey === 'series') return `<p class="truncate">Series: ${html}</p>`
|
if (this.matchKey === 'series') return `<p class="truncate">Series: ${html}</p>`
|
||||||
return `${html}`
|
return `${html}`
|
||||||
}
|
}
|
|
@ -7,7 +7,7 @@
|
||||||
<div class="absolute cover-bg" ref="coverBg" />
|
<div class="absolute cover-bg" ref="coverBg" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="w-full h-full absolute top-0 left-0">
|
<div class="w-full h-full absolute top-0 left-0 bg-primary">
|
||||||
<img v-show="libraryItem" ref="cover" :src="bookCoverSrc" class="w-full h-full transition-opacity duration-300" :class="showCoverBg ? 'object-contain' : 'object-fill'" @load="imageLoaded" :style="{ opacity: imageReady ? 1 : 0 }" />
|
<img v-show="libraryItem" ref="cover" :src="bookCoverSrc" class="w-full h-full transition-opacity duration-300" :class="showCoverBg ? 'object-contain' : 'object-fill'" @load="imageLoaded" :style="{ opacity: imageReady ? 1 : 0 }" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="flex h-full px-1 overflow-hidden">
|
<div class="flex h-full px-1 overflow-hidden">
|
||||||
<covers-group-cover :name="series" :book-items="bookItems" :width="80" :height="60" :book-cover-aspect-ratio="bookCoverAspectRatio" />
|
<covers-group-cover :name="name" :book-items="bookItems" :width="80" :height="60" :book-cover-aspect-ratio="bookCoverAspectRatio" />
|
||||||
<div class="flex-grow px-2 seriesSearchCardContent h-full">
|
<div class="flex-grow px-2 seriesSearchCardContent h-full">
|
||||||
<p class="truncate text-sm">{{ series }}</p>
|
<p class="truncate text-sm">{{ name }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -10,7 +10,10 @@
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
series: String,
|
series: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {}
|
||||||
|
},
|
||||||
bookItems: {
|
bookItems: {
|
||||||
type: Array,
|
type: Array,
|
||||||
default: () => []
|
default: () => []
|
||||||
|
@ -22,6 +25,9 @@ export default {
|
||||||
computed: {
|
computed: {
|
||||||
bookCoverAspectRatio() {
|
bookCoverAspectRatio() {
|
||||||
return this.$store.getters['getBookCoverAspectRatio']
|
return this.$store.getters['getBookCoverAspectRatio']
|
||||||
|
},
|
||||||
|
name() {
|
||||||
|
return this.series.name
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {},
|
methods: {},
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
async asyncData({ params, app, store }) {
|
async asyncData({ params, app, store, redirect }) {
|
||||||
var series = await app.$axios.$get(`/api/series/${params.id}`).catch((error) => {
|
var series = await app.$axios.$get(`/api/series/${params.id}`).catch((error) => {
|
||||||
console.error('Failed', error)
|
console.error('Failed', error)
|
||||||
return false
|
return false
|
||||||
|
|
|
@ -11,28 +11,37 @@
|
||||||
<p class="text-lg text-gray-400">Nothing found</p>
|
<p class="text-lg text-gray-400">Nothing found</p>
|
||||||
</div>
|
</div>
|
||||||
<p v-if="bookResults.length" class="font-semibold text-sm mb-1">Books</p>
|
<p v-if="bookResults.length" class="font-semibold text-sm mb-1">Books</p>
|
||||||
<template v-for="bookResult in bookResults">
|
<template v-for="item in bookResults">
|
||||||
<div :key="bookResult.audiobook.id" class="w-full h-16 py-1">
|
<div :key="item.id" class="w-full h-16 py-1">
|
||||||
<nuxt-link :to="`/item/${bookResult.audiobook.id}`">
|
<nuxt-link :to="`/item/${item.id}`">
|
||||||
<cards-book-search-card :audiobook="bookResult.audiobook" :search="lastSearch" :match-key="bookResult.matchKey" :match-text="bookResult.matchText" />
|
<cards-item-search-card :library-item="item.libraryItem" :match-key="item.matchKey" :match-text="item.matchText" :search="lastSearch" />
|
||||||
|
</nuxt-link>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<p v-if="podcastResults.length" class="uppercase text-xs text-gray-400 my-1 px-1 font-semibold">Podcasts</p>
|
||||||
|
<template v-for="item in podcastResults">
|
||||||
|
<div :key="item.id" class="text-gray-50 select-none relative cursor-pointer hover:bg-black-400 py-1">
|
||||||
|
<nuxt-link :to="`/item/${item.id}`">
|
||||||
|
<cards-item-search-card :library-item="item.libraryItem" :match-key="item.matchKey" :match-text="item.matchText" :search="lastSearch" />
|
||||||
</nuxt-link>
|
</nuxt-link>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<p v-if="seriesResults.length" class="font-semibold text-sm mb-1 mt-2">Series</p>
|
<p v-if="seriesResults.length" class="font-semibold text-sm mb-1 mt-2">Series</p>
|
||||||
<template v-for="seriesResult in seriesResults">
|
<template v-for="seriesResult in seriesResults">
|
||||||
<div :key="seriesResult.series" class="w-full h-16 py-1">
|
<div :key="seriesResult.series.id" class="w-full h-16 py-1">
|
||||||
<nuxt-link :to="`/bookshelf/series/${$encode(seriesResult.series)}`">
|
<nuxt-link :to="`/bookshelf/series/${seriesResult.series.id}`">
|
||||||
<cards-series-search-card :series="seriesResult.series" :book-items="seriesResult.audiobooks" />
|
<cards-series-search-card :series="seriesResult.series" :book-items="seriesResult.books" />
|
||||||
</nuxt-link>
|
</nuxt-link>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<p v-if="authorResults.length" class="font-semibold text-sm mb-1 mt-2">Authors</p>
|
<p v-if="authorResults.length" class="font-semibold text-sm mb-1 mt-2">Authors</p>
|
||||||
<template v-for="authorResult in authorResults">
|
<template v-for="authorResult in authorResults">
|
||||||
<div :key="authorResult.author" class="w-full h-14 py-1">
|
<div :key="authorResult.id" class="w-full h-14 py-1">
|
||||||
<nuxt-link :to="`/bookshelf/library?filter=authors.${$encode(authorResult.author)}`">
|
<nuxt-link :to="`/bookshelf/library?filter=authors.${$encode(authorResult.id)}`">
|
||||||
<cards-author-search-card :key="authorResult.author" :author="authorResult.author" />
|
<cards-author-search-card :key="authorResult.id" :author="authorResult" />
|
||||||
</nuxt-link>
|
</nuxt-link>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -49,6 +58,7 @@ export default {
|
||||||
lastSearch: null,
|
lastSearch: null,
|
||||||
isFetching: false,
|
isFetching: false,
|
||||||
bookResults: [],
|
bookResults: [],
|
||||||
|
podcastResults: [],
|
||||||
seriesResults: [],
|
seriesResults: [],
|
||||||
authorResults: []
|
authorResults: []
|
||||||
}
|
}
|
||||||
|
@ -61,7 +71,7 @@ export default {
|
||||||
return this.$store.getters['getBookCoverAspectRatio']
|
return this.$store.getters['getBookCoverAspectRatio']
|
||||||
},
|
},
|
||||||
totalResults() {
|
totalResults() {
|
||||||
return this.bookResults.length + this.seriesResults.length + this.authorResults.length
|
return this.bookResults.length + this.seriesResults.length + this.authorResults.length + this.podcastResults.length
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
@ -69,6 +79,7 @@ export default {
|
||||||
this.lastSearch = value
|
this.lastSearch = value
|
||||||
if (!this.lastSearch) {
|
if (!this.lastSearch) {
|
||||||
this.bookResults = []
|
this.bookResults = []
|
||||||
|
this.podcastResults = []
|
||||||
this.seriesResults = []
|
this.seriesResults = []
|
||||||
this.authorResults = []
|
this.authorResults = []
|
||||||
return
|
return
|
||||||
|
@ -82,7 +93,8 @@ export default {
|
||||||
|
|
||||||
this.isFetching = false
|
this.isFetching = false
|
||||||
|
|
||||||
this.bookResults = results ? results.audiobooks || [] : []
|
this.bookResults = results ? results.book || [] : []
|
||||||
|
this.podcastResults = results ? results.podcast || [] : []
|
||||||
this.seriesResults = results ? results.series || [] : []
|
this.seriesResults = results ? results.series || [] : []
|
||||||
this.authorResults = results ? results.authors || [] : []
|
this.authorResults = results ? results.authors || [] : []
|
||||||
},
|
},
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue