mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2026-06-07 11:12:45 +02:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| fcd664c16e | |||
| 94741598af |
@@ -102,3 +102,11 @@
|
|||||||
.box-shadow-book {
|
.box-shadow-book {
|
||||||
box-shadow: 4px 1px 8px #11111166, -4px 1px 8px #11111166, 1px -4px 8px #11111166;
|
box-shadow: 4px 1px 8px #11111166, -4px 1px 8px #11111166, 1px -4px 8px #11111166;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.box-shadow-book3d {
|
||||||
|
box-shadow: 4px 1px 8px #11111166, 1px -4px 8px #11111166;
|
||||||
|
}
|
||||||
|
|
||||||
|
.box-shadow-side {
|
||||||
|
box-shadow: 4px 0px 4px #11111166;
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,15 +7,16 @@
|
|||||||
<span class="material-icons text-4xl text-white">arrow_back</span>
|
<span class="material-icons text-4xl text-white">arrow_back</span>
|
||||||
</a>
|
</a>
|
||||||
<h1 class="text-2xl font-book mr-6">AudioBookshelf</h1>
|
<h1 class="text-2xl font-book mr-6">AudioBookshelf</h1>
|
||||||
|
<!-- <div class="-mb-2">
|
||||||
|
<h1 class="text-lg font-book leading-3 mr-6 px-1">AudioBookshelf</h1>
|
||||||
|
<div class="bg-black bg-opacity-20 rounded-sm py-1.5 px-2 mt-1.5 flex items-center justify-between border border-bg">
|
||||||
|
<p class="text-sm text-gray-400 leading-3">My Library</p>
|
||||||
|
<span class="material-icons text-sm leading-3 text-gray-400">expand_more</span>
|
||||||
|
</div>
|
||||||
|
</div> -->
|
||||||
<controls-global-search />
|
<controls-global-search />
|
||||||
<div class="flex-grow" />
|
<div class="flex-grow" />
|
||||||
|
|
||||||
<!-- <a v-if="isUpdateAvailable" :href="githubTagUrl" target="_blank" class="flex items-center rounded-full bg-warning p-2 text-sm">
|
|
||||||
<span class="material-icons">notification_important</span>
|
|
||||||
<span class="pl-2">Update is available! Check release notes for v{{ latestVersion }}</span>
|
|
||||||
</a> -->
|
|
||||||
|
|
||||||
<nuxt-link v-if="userCanUpload" to="/upload" class="outline-none hover:text-gray-200 cursor-pointer w-8 h-8 flex items-center justify-center">
|
<nuxt-link v-if="userCanUpload" to="/upload" class="outline-none hover:text-gray-200 cursor-pointer w-8 h-8 flex items-center justify-center">
|
||||||
<span class="material-icons">upload</span>
|
<span class="material-icons">upload</span>
|
||||||
</nuxt-link>
|
</nuxt-link>
|
||||||
@@ -45,10 +46,8 @@
|
|||||||
</ui-tooltip>
|
</ui-tooltip>
|
||||||
<template v-if="userCanUpdate">
|
<template v-if="userCanUpdate">
|
||||||
<ui-icon-btn v-show="!processingBatchDelete" icon="edit" bg-color="warning" class="mx-1.5" @click="batchEditClick" />
|
<ui-icon-btn v-show="!processingBatchDelete" icon="edit" bg-color="warning" class="mx-1.5" @click="batchEditClick" />
|
||||||
<!-- <ui-btn v-show="!processingBatchDelete" color="warning" small class="mx-2 w-10 h-10" :padding-y="0" :padding-x="0" @click="batchEditClick"><span class="material-icons text-gray-200 text-base">edit</span></ui-btn> -->
|
|
||||||
</template>
|
</template>
|
||||||
<ui-icon-btn v-show="userCanDelete" :disabled="processingBatchDelete" icon="delete" bg-color="error" class="mx-1.5" @click="batchDeleteClick" />
|
<ui-icon-btn v-show="userCanDelete" :disabled="processingBatchDelete" icon="delete" bg-color="error" class="mx-1.5" @click="batchDeleteClick" />
|
||||||
<!-- <ui-btn v-if="userCanDelete" color="error" small class="mx-2" :loading="processingBatchDelete" @click="batchDeleteClick"><span class="material-icons text-gray-200 pt-1">delete</span></ui-btn> -->
|
|
||||||
<span class="material-icons text-4xl px-4 hover:text-gray-100 cursor-pointer" :class="processingBatchDelete ? 'text-gray-400' : ''" @click="cancelSelectionMode">close</span>
|
<span class="material-icons text-4xl px-4 hover:text-gray-100 cursor-pointer" :class="processingBatchDelete ? 'text-gray-400' : ''" @click="cancelSelectionMode">close</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -64,7 +63,7 @@ export default {
|
|||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
showBack() {
|
showBack() {
|
||||||
return this.$route.name !== 'index'
|
return this.$route.name !== 'library-id'
|
||||||
},
|
},
|
||||||
user() {
|
user() {
|
||||||
return this.$store.state.user.user
|
return this.$store.state.user.user
|
||||||
@@ -115,7 +114,7 @@ export default {
|
|||||||
if (this.$route.name === 'audiobook-id-edit') {
|
if (this.$route.name === 'audiobook-id-edit') {
|
||||||
this.$router.push(`/audiobook/${this.$route.params.id}`)
|
this.$router.push(`/audiobook/${this.$route.params.id}`)
|
||||||
} else {
|
} else {
|
||||||
this.$router.push('/')
|
this.$router.push('/library')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
cancelSelectionMode() {
|
cancelSelectionMode() {
|
||||||
|
|||||||
@@ -16,20 +16,22 @@
|
|||||||
<ui-btn color="success" class="w-52" @click="scan">Scan Audiobooks</ui-btn>
|
<ui-btn color="success" class="w-52" @click="scan">Scan Audiobooks</ui-btn>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="w-full flex flex-col items-center">
|
<div v-else id="bookshelf" class="w-full flex flex-col items-center">
|
||||||
<template v-for="(shelf, index) in groupedBooks">
|
<template v-for="(shelf, index) in shelves">
|
||||||
<div :key="index" class="w-full bookshelfRow relative">
|
<div :key="index" class="w-full bookshelfRow relative">
|
||||||
<div class="flex justify-center items-center">
|
<div class="flex justify-center items-center">
|
||||||
<template v-for="audiobook in shelf">
|
<template v-for="entity in shelf">
|
||||||
<cards-book-card :ref="`audiobookCard-${audiobook.id}`" :key="audiobook.id" :width="bookCoverWidth" :user-progress="userAudiobooks[audiobook.id]" :audiobook="audiobook" />
|
<cards-group-card v-if="showGroups" :key="entity.id" :width="bookCoverWidth" :group="entity" @click="clickGroup" />
|
||||||
|
<!-- <cards-book-3d :key="entity.id" v-else :width="100" :src="$store.getters['audiobooks/getBookCoverSrc'](entity.book)" /> -->
|
||||||
|
<cards-book-card v-else :key="entity.id" :width="bookCoverWidth" :user-progress="userAudiobooks[entity.id]" :audiobook="entity" />
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
<div class="bookshelfDivider h-4 w-full absolute bottom-0 left-0 right-0 z-10" />
|
<div class="bookshelfDivider h-4 w-full absolute bottom-0 left-0 right-0 z-10" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<div v-show="!groupedBooks.length" class="w-full py-16 text-center text-xl">
|
<div v-show="!shelves.length" class="w-full py-16 text-center text-xl">
|
||||||
<div class="py-4">No Audiobooks</div>
|
<div class="py-4">No {{ showGroups ? 'Series' : 'Audiobooks' }}</div>
|
||||||
<ui-btn v-if="filterBy !== 'all' || keywordFilter" @click="clearFilter">Clear Filter</ui-btn>
|
<ui-btn v-if="!showGroups && (filterBy !== 'all' || keywordFilter)" @click="clearFilter">Clear Filter</ui-btn>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -37,11 +39,13 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
|
props: {
|
||||||
|
page: String,
|
||||||
|
selectedSeries: String
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
width: 0,
|
shelves: [],
|
||||||
booksPerRow: 0,
|
|
||||||
groupedBooks: [],
|
|
||||||
currFilterOrderKey: null,
|
currFilterOrderKey: null,
|
||||||
availableSizes: [60, 80, 100, 120, 140, 160, 180, 200, 220],
|
availableSizes: [60, 80, 100, 120, 140, 160, 180, 200, 220],
|
||||||
selectedSizeIndex: 3,
|
selectedSizeIndex: 3,
|
||||||
@@ -53,6 +57,11 @@ export default {
|
|||||||
watch: {
|
watch: {
|
||||||
keywordFilter() {
|
keywordFilter() {
|
||||||
this.checkKeywordFilter()
|
this.checkKeywordFilter()
|
||||||
|
},
|
||||||
|
selectedSeries() {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.setBookshelfEntities()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -85,9 +94,30 @@ export default {
|
|||||||
},
|
},
|
||||||
filterBy() {
|
filterBy() {
|
||||||
return this.$store.getters['user/getUserSetting']('filterBy')
|
return this.$store.getters['user/getUserSetting']('filterBy')
|
||||||
|
},
|
||||||
|
showGroups() {
|
||||||
|
return this.page !== '' && !this.selectedSeries
|
||||||
|
},
|
||||||
|
entities() {
|
||||||
|
if (this.page === '') {
|
||||||
|
return this.$store.getters['audiobooks/getFilteredAndSorted']()
|
||||||
|
} else {
|
||||||
|
var seriesGroups = this.$store.getters['audiobooks/getSeriesGroups']()
|
||||||
|
if (this.selectedSeries) {
|
||||||
|
var group = seriesGroups.find((group) => group.name === this.selectedSeries)
|
||||||
|
return group.books
|
||||||
|
}
|
||||||
|
return seriesGroups
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
clickGroup(group) {
|
||||||
|
this.$emit('update:selectedSeries', group.name)
|
||||||
|
},
|
||||||
|
changeRotation() {
|
||||||
|
this.rotation = 'show-right'
|
||||||
|
},
|
||||||
clearFilter() {
|
clearFilter() {
|
||||||
this.$store.commit('audiobooks/setKeywordFilter', null)
|
this.$store.commit('audiobooks/setKeywordFilter', null)
|
||||||
if (this.filterBy !== 'all') {
|
if (this.filterBy !== 'all') {
|
||||||
@@ -95,13 +125,13 @@ export default {
|
|||||||
filterBy: 'all'
|
filterBy: 'all'
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
this.setGroupedBooks()
|
this.setBookshelfEntities()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
checkKeywordFilter() {
|
checkKeywordFilter() {
|
||||||
clearTimeout(this.keywordFilterTimeout)
|
clearTimeout(this.keywordFilterTimeout)
|
||||||
this.keywordFilterTimeout = setTimeout(() => {
|
this.keywordFilterTimeout = setTimeout(() => {
|
||||||
this.setGroupedBooks()
|
this.setBookshelfEntities()
|
||||||
}, 500)
|
}, 500)
|
||||||
},
|
},
|
||||||
increaseSize() {
|
increaseSize() {
|
||||||
@@ -114,59 +144,51 @@ export default {
|
|||||||
this.resize()
|
this.resize()
|
||||||
this.$store.dispatch('user/updateUserSettings', { bookshelfCoverSize: this.bookCoverWidth })
|
this.$store.dispatch('user/updateUserSettings', { bookshelfCoverSize: this.bookCoverWidth })
|
||||||
},
|
},
|
||||||
setGroupedBooks() {
|
setBookshelfEntities() {
|
||||||
|
var width = Math.max(0, this.$refs.wrapper.clientWidth - this.rowPaddingX * 2)
|
||||||
|
var booksPerRow = Math.floor(width / this.bookWidth)
|
||||||
|
|
||||||
|
var entities = this.entities
|
||||||
var groups = []
|
var groups = []
|
||||||
var currentRow = 0
|
var currentRow = 0
|
||||||
var currentGroup = []
|
var currentGroup = []
|
||||||
|
|
||||||
var audiobooksSorted = this.$store.getters['audiobooks/getFilteredAndSorted']()
|
for (let i = 0; i < entities.length; i++) {
|
||||||
this.currFilterOrderKey = this.filterOrderKey
|
var row = Math.floor(i / booksPerRow)
|
||||||
|
|
||||||
for (let i = 0; i < audiobooksSorted.length; i++) {
|
|
||||||
var row = Math.floor(i / this.booksPerRow)
|
|
||||||
if (row > currentRow) {
|
if (row > currentRow) {
|
||||||
groups.push([...currentGroup])
|
groups.push([...currentGroup])
|
||||||
currentRow = row
|
currentRow = row
|
||||||
currentGroup = []
|
currentGroup = []
|
||||||
}
|
}
|
||||||
currentGroup.push(audiobooksSorted[i])
|
currentGroup.push(entities[i])
|
||||||
}
|
}
|
||||||
if (currentGroup.length) {
|
if (currentGroup.length) {
|
||||||
groups.push([...currentGroup])
|
groups.push([...currentGroup])
|
||||||
}
|
}
|
||||||
this.groupedBooks = groups
|
this.shelves = groups
|
||||||
},
|
},
|
||||||
calculateBookshelf() {
|
async init() {
|
||||||
this.width = this.$refs.wrapper.clientWidth
|
|
||||||
this.width = Math.max(0, this.width - this.rowPaddingX * 2)
|
|
||||||
var booksPerRow = Math.floor(this.width / this.bookWidth)
|
|
||||||
this.booksPerRow = booksPerRow
|
|
||||||
},
|
|
||||||
getAudiobookCard(id) {
|
|
||||||
if (this.$refs[`audiobookCard-${id}`] && this.$refs[`audiobookCard-${id}`].length) {
|
|
||||||
return this.$refs[`audiobookCard-${id}`][0]
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
},
|
|
||||||
init() {
|
|
||||||
var bookshelfCoverSize = this.$store.getters['user/getUserSetting']('bookshelfCoverSize')
|
var bookshelfCoverSize = this.$store.getters['user/getUserSetting']('bookshelfCoverSize')
|
||||||
var sizeIndex = this.availableSizes.findIndex((s) => s === bookshelfCoverSize)
|
var sizeIndex = this.availableSizes.findIndex((s) => s === bookshelfCoverSize)
|
||||||
if (!isNaN(sizeIndex)) this.selectedSizeIndex = sizeIndex
|
if (!isNaN(sizeIndex)) this.selectedSizeIndex = sizeIndex
|
||||||
this.calculateBookshelf()
|
|
||||||
|
var isLoading = await this.$store.dispatch('audiobooks/load')
|
||||||
|
if (!isLoading) {
|
||||||
|
this.setBookshelfEntities()
|
||||||
|
}
|
||||||
},
|
},
|
||||||
resize() {
|
resize() {
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
this.calculateBookshelf()
|
this.setBookshelfEntities()
|
||||||
this.setGroupedBooks()
|
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
audiobooksUpdated() {
|
audiobooksUpdated() {
|
||||||
console.log('[AudioBookshelf] Audiobooks Updated')
|
console.log('[AudioBookshelf] Audiobooks Updated')
|
||||||
this.setGroupedBooks()
|
this.setBookshelfEntities()
|
||||||
},
|
},
|
||||||
settingsUpdated(settings) {
|
settingsUpdated(settings) {
|
||||||
if (this.currFilterOrderKey !== this.filterOrderKey) {
|
if (this.currFilterOrderKey !== this.filterOrderKey) {
|
||||||
this.setGroupedBooks()
|
this.setBookshelfEntities()
|
||||||
}
|
}
|
||||||
if (settings.bookshelfCoverSize !== this.bookCoverWidth && settings.bookshelfCoverSize !== undefined) {
|
if (settings.bookshelfCoverSize !== this.bookCoverWidth && settings.bookshelfCoverSize !== undefined) {
|
||||||
var index = this.availableSizes.indexOf(settings.bookshelfCoverSize)
|
var index = this.availableSizes.indexOf(settings.bookshelfCoverSize)
|
||||||
@@ -181,17 +203,15 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
|
window.addEventListener('resize', this.resize)
|
||||||
this.$store.commit('audiobooks/addListener', { id: 'bookshelf', meth: this.audiobooksUpdated })
|
this.$store.commit('audiobooks/addListener', { id: 'bookshelf', meth: this.audiobooksUpdated })
|
||||||
this.$store.commit('user/addSettingsListener', { id: 'bookshelf', meth: this.settingsUpdated })
|
this.$store.commit('user/addSettingsListener', { id: 'bookshelf', meth: this.settingsUpdated })
|
||||||
|
|
||||||
this.$store.dispatch('audiobooks/load')
|
|
||||||
this.init()
|
this.init()
|
||||||
window.addEventListener('resize', this.resize)
|
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
|
window.removeEventListener('resize', this.resize)
|
||||||
this.$store.commit('audiobooks/removeListener', 'bookshelf')
|
this.$store.commit('audiobooks/removeListener', 'bookshelf')
|
||||||
this.$store.commit('user/removeSettingsListener', 'bookshelf')
|
this.$store.commit('user/removeSettingsListener', 'bookshelf')
|
||||||
window.removeEventListener('resize', this.resize)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,20 +1,31 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="w-full h-10 relative">
|
<div class="w-full h-10 relative">
|
||||||
<div id="toolbar" class="absolute top-0 left-0 w-full h-full z-20 flex items-center px-8">
|
<div id="toolbar" class="absolute top-0 left-0 w-full h-full z-20 flex items-center px-8">
|
||||||
<p class="font-book">{{ numShowing }} Audiobooks</p>
|
<p v-if="!selectedSeries" class="font-book">{{ numShowing }} {{ entityName }}</p>
|
||||||
|
<div v-else class="flex items-center">
|
||||||
|
<div @click="seriesBackArrow" class="rounded-full h-10 w-10 flex items-center justify-center hover:bg-white hover:bg-opacity-10 cursor-pointer">
|
||||||
|
<span class="material-icons text-3xl text-white">west</span>
|
||||||
|
</div>
|
||||||
|
<!-- <span class="material-icons text-2xl cursor-pointer" @click="seriesBackArrow">west</span> -->
|
||||||
|
<p class="pl-4 font-book text-lg">
|
||||||
|
{{ selectedSeries }} <span class="ml-3 font-mono text-lg bg-black bg-opacity-30 rounded-lg px-1 py-0.5">{{ numShowing }}</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
<div class="flex-grow" />
|
<div class="flex-grow" />
|
||||||
|
|
||||||
<ui-text-input v-model="_keywordFilter" placeholder="Keyword Filter" :padding-y="1.5" class="text-xs w-40" />
|
<ui-text-input v-show="showSortFilters" v-model="_keywordFilter" placeholder="Keyword Filter" :padding-y="1.5" class="text-xs w-40" />
|
||||||
|
<controls-filter-select v-show="showSortFilters" v-model="settings.filterBy" class="w-48 h-7.5 ml-4" @change="updateFilter" />
|
||||||
<controls-filter-select v-model="settings.filterBy" class="w-48 h-7.5 ml-4" @change="updateFilter" />
|
<controls-order-select v-show="showSortFilters" v-model="settings.orderBy" :descending.sync="settings.orderDesc" class="w-48 h-7.5 ml-4" @change="updateOrder" />
|
||||||
|
|
||||||
<controls-order-select v-model="settings.orderBy" :descending.sync="settings.orderDesc" class="w-48 h-7.5 ml-4" @change="updateOrder" />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
|
props: {
|
||||||
|
page: String,
|
||||||
|
selectedSeries: String
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
settings: {},
|
settings: {},
|
||||||
@@ -22,8 +33,27 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
showSortFilters() {
|
||||||
|
return this.page === ''
|
||||||
|
},
|
||||||
numShowing() {
|
numShowing() {
|
||||||
return this.$store.getters['audiobooks/getFiltered']().length
|
if (this.page === '') {
|
||||||
|
return this.$store.getters['audiobooks/getFiltered']().length
|
||||||
|
} else {
|
||||||
|
var groups = this.$store.getters['audiobooks/getSeriesGroups']()
|
||||||
|
if (this.selectedSeries) {
|
||||||
|
var group = groups.find((g) => g.name === this.selectedSeries)
|
||||||
|
if (group) return group.books.length
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return groups.length
|
||||||
|
}
|
||||||
|
},
|
||||||
|
entityName() {
|
||||||
|
if (!this.page) return 'Audiobooks'
|
||||||
|
if (this.page === 'series') return 'Series'
|
||||||
|
if (this.page === 'collections') return 'Collections'
|
||||||
|
return ''
|
||||||
},
|
},
|
||||||
_keywordFilter: {
|
_keywordFilter: {
|
||||||
get() {
|
get() {
|
||||||
@@ -35,6 +65,10 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
seriesBackArrow() {
|
||||||
|
this.$router.replace('/library/series')
|
||||||
|
this.$emit('update:selectedSeries', null)
|
||||||
|
},
|
||||||
updateOrder() {
|
updateOrder() {
|
||||||
this.saveSettings()
|
this.saveSettings()
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
<template>
|
||||||
|
<div class="w-20 border-r border-primary bg-bg h-full relative box-shadow-side z-40" style="min-width: 80px">
|
||||||
|
<nuxt-link to="/library" class="w-full h-20 flex flex-col items-center justify-center text-white border-b border-primary border-opacity-70 hover:bg-primary cursor-pointer relative" :class="paramId === '' ? 'bg-primary bg-opacity-80' : 'bg-bg bg-opacity-60'">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253" />
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
<p class="font-book pt-1.5" style="font-size: 1rem">Library</p>
|
||||||
|
|
||||||
|
<div v-show="paramId === ''" class="h-full w-0.5 bg-yellow-400 absolute top-0 left-0" />
|
||||||
|
</nuxt-link>
|
||||||
|
|
||||||
|
<nuxt-link to="/library/series" class="w-full h-20 flex flex-col items-center justify-center text-white text-opacity-80 border-b border-primary border-opacity-70 hover:bg-primary cursor-pointer relative" :class="paramId === 'series' ? 'bg-primary bg-opacity-80' : 'bg-bg bg-opacity-60'">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 17V7m0 10a2 2 0 01-2 2H5a2 2 0 01-2-2V7a2 2 0 012-2h2a2 2 0 012 2m0 10a2 2 0 002 2h2a2 2 0 002-2M9 7a2 2 0 012-2h2a2 2 0 012 2m0 10V7m0 10a2 2 0 002 2h2a2 2 0 002-2V7a2 2 0 00-2-2h-2a2 2 0 00-2 2" />
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
<p class="font-book pt-1.5" style="font-size: 1rem">Series</p>
|
||||||
|
|
||||||
|
<div v-show="paramId === 'series'" class="h-full w-0.5 bg-yellow-400 absolute top-0 left-0" />
|
||||||
|
</nuxt-link>
|
||||||
|
|
||||||
|
<!-- <nuxt-link to="/library/collections" class="w-full h-20 flex flex-col items-center justify-center text-white text-opacity-80 border-b border-primary border-opacity-70 hover:bg-primary cursor-pointer relative" :class="paramId === 'collections' ? 'bg-primary bg-opacity-80' : 'bg-bg bg-opacity-60'">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10" />
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
<p class="font-book pt-1.5" style="font-size: 0.8rem">Collections</p>
|
||||||
|
|
||||||
|
<div v-show="paramId === 'collections'" class="h-full w-0.5 bg-yellow-400 absolute top-0 left-0" />
|
||||||
|
</nuxt-link> -->
|
||||||
|
|
||||||
|
<!-- <nuxt-link to="/library/tags" class="w-full h-20 flex flex-col items-center justify-center text-white text-opacity-80 border-b border-primary border-opacity-70 hover:bg-primary cursor-pointer relative" :class="paramId === 'tags' ? 'bg-primary bg-opacity-80' : 'bg-bg bg-opacity-60'">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z" />
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
<p class="font-book pt-1.5" style="font-size: 0.8rem">Tags</p>
|
||||||
|
|
||||||
|
<div v-show="paramId === 'tags'" class="h-full w-0.5 bg-yellow-400 absolute top-0 left-0" />
|
||||||
|
</nuxt-link> -->
|
||||||
|
|
||||||
|
<!-- <nuxt-link to="/library/authors" class="w-full h-20 flex flex-col items-center justify-center text-white text-opacity-80 border-b border-primary border-opacity-70 hover:bg-primary cursor-pointer relative" :class="paramId === 'authors' ? 'bg-primary bg-opacity-80' : 'bg-bg bg-opacity-60'">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z" />
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
<p class="font-book pt-1.5" style="font-size: 0.8rem">Authors</p>
|
||||||
|
|
||||||
|
<div v-show="paramId === 'authors'" class="h-full w-0.5 bg-yellow-400 absolute top-0 left-0" />
|
||||||
|
</nuxt-link> -->
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
paramId() {
|
||||||
|
return this.$route.params ? this.$route.params.id || '' : ''
|
||||||
|
},
|
||||||
|
selectedClassName() {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {},
|
||||||
|
mounted() {}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -68,7 +68,7 @@ export default {
|
|||||||
methods: {
|
methods: {
|
||||||
filterByAuthor() {
|
filterByAuthor() {
|
||||||
if (this.$route.name !== 'index') {
|
if (this.$route.name !== 'index') {
|
||||||
this.$router.push('/')
|
this.$router.push('/library')
|
||||||
}
|
}
|
||||||
var settingsUpdate = {
|
var settingsUpdate = {
|
||||||
filterBy: `authors.${this.$encode(this.author)}`
|
filterBy: `authors.${this.$encode(this.author)}`
|
||||||
|
|||||||
@@ -0,0 +1,254 @@
|
|||||||
|
<template>
|
||||||
|
<div ref="wrapper" class="relative pointer-events-none" :style="{ width: standardWidth * 0.8 * 1.1 * scale + 'px', height: standardHeight * 1.1 * scale + 'px', marginBottom: 20 + 'px', marginTop: 15 + 'px' }">
|
||||||
|
<div ref="card" class="wrap absolute origin-center transform duration-200" :style="{ transform: `scale(${scale * scaleMultiplier}) translateY(${hover2 ? '-40%' : '-50%'})` }">
|
||||||
|
<div class="perspective">
|
||||||
|
<div class="book-wrap transform duration-100 pointer-events-auto" :class="hover2 ? 'z-80' : 'rotate'" @mouseover="hover = true" @mouseout="hover = false">
|
||||||
|
<div class="book book-1 box-shadow-book3d" ref="front"></div>
|
||||||
|
<div class="title book-1 pointer-events-none" ref="left"></div>
|
||||||
|
<div class="bottom book-1 pointer-events-none" ref="bottom"></div>
|
||||||
|
<div class="book-back book-1 pointer-events-none">
|
||||||
|
<div class="text pointer-events-none">
|
||||||
|
<h3 class="mb-4">Book Back</h3>
|
||||||
|
<p>
|
||||||
|
<span>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Sunt earum doloremque aliquam culpa dolor nostrum consequatur quas dicta? Molestias repellendus minima pariatur libero vel, reiciendis optio magnam rerum, labore corporis.</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
src: String,
|
||||||
|
width: {
|
||||||
|
type: Number,
|
||||||
|
default: 200
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
hover: false,
|
||||||
|
hover2: false,
|
||||||
|
standardWidth: 200,
|
||||||
|
standardHeight: 320,
|
||||||
|
isAttached: true,
|
||||||
|
pageX: 0,
|
||||||
|
pageY: 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
src(newVal) {
|
||||||
|
this.setCover()
|
||||||
|
},
|
||||||
|
width(newVal) {
|
||||||
|
this.init()
|
||||||
|
},
|
||||||
|
hover(newVal) {
|
||||||
|
if (newVal) {
|
||||||
|
this.unattach()
|
||||||
|
} else {
|
||||||
|
this.attach()
|
||||||
|
}
|
||||||
|
setTimeout(() => {
|
||||||
|
this.hover2 = newVal
|
||||||
|
}, 100)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
scaleMultiplier() {
|
||||||
|
return this.hover2 ? 1.25 : 1
|
||||||
|
},
|
||||||
|
scale() {
|
||||||
|
var scale = this.width / this.standardWidth
|
||||||
|
return scale
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
unattach() {
|
||||||
|
if (this.$refs.card && this.isAttached) {
|
||||||
|
var bookshelf = document.getElementById('bookshelf')
|
||||||
|
if (bookshelf) {
|
||||||
|
var pos = this.$refs.wrapper.getBoundingClientRect()
|
||||||
|
|
||||||
|
this.pageX = pos.x
|
||||||
|
this.pageY = pos.y
|
||||||
|
document.body.appendChild(this.$refs.card)
|
||||||
|
this.$refs.card.style.left = this.pageX + 'px'
|
||||||
|
this.$refs.card.style.top = this.pageY + 'px'
|
||||||
|
this.$refs.card.style.zIndex = 50
|
||||||
|
this.isAttached = false
|
||||||
|
} else if (bookshelf) {
|
||||||
|
console.log(this.pageX, this.pageY)
|
||||||
|
this.isAttached = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
attach() {
|
||||||
|
if (this.$refs.card && !this.isAttached) {
|
||||||
|
if (this.$refs.wrapper) {
|
||||||
|
this.isAttached = true
|
||||||
|
|
||||||
|
this.$refs.wrapper.appendChild(this.$refs.card)
|
||||||
|
this.$refs.card.style.left = '0px'
|
||||||
|
this.$refs.card.style.top = '0px'
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log('Is attached already', this.isAttached)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
init() {
|
||||||
|
var standardWidth = this.standardWidth
|
||||||
|
document.documentElement.style.setProperty('--book-w', standardWidth + 'px')
|
||||||
|
document.documentElement.style.setProperty('--book-wx', standardWidth + 1 + 'px')
|
||||||
|
document.documentElement.style.setProperty('--book-h', standardWidth * 1.6 + 'px')
|
||||||
|
document.documentElement.style.setProperty('--book-d', 40 + 'px')
|
||||||
|
},
|
||||||
|
setElBg(el) {
|
||||||
|
el.style.backgroundImage = `url("${this.src}")`
|
||||||
|
el.style.backgroundSize = 'cover'
|
||||||
|
el.style.backgroundPosition = 'center center'
|
||||||
|
el.style.backgroundRepeat = 'no-repeat'
|
||||||
|
},
|
||||||
|
setCover() {
|
||||||
|
if (this.$refs.front) {
|
||||||
|
this.setElBg(this.$refs.front)
|
||||||
|
}
|
||||||
|
if (this.$refs.bottom) {
|
||||||
|
this.setElBg(this.$refs.bottom)
|
||||||
|
this.$refs.bottom.style.backgroundSize = '2000%'
|
||||||
|
this.$refs.bottom.style.filter = 'blur(1px)'
|
||||||
|
}
|
||||||
|
if (this.$refs.left) {
|
||||||
|
this.setElBg(this.$refs.left)
|
||||||
|
this.$refs.left.style.backgroundSize = '2000%'
|
||||||
|
this.$refs.left.style.filter = 'blur(1px)'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.setCover()
|
||||||
|
this.init()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
/* :root {
|
||||||
|
--book-w: 200px;
|
||||||
|
--book-h: 320px;
|
||||||
|
--book-d: 30px;
|
||||||
|
--book-wx: 201px;
|
||||||
|
} */
|
||||||
|
/*
|
||||||
|
.wrap {
|
||||||
|
width: calc(1.1 * var(--book-w));
|
||||||
|
height: calc(1.1 * var(--book-h));
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
.perspective {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
perspective: 600px;
|
||||||
|
transform-style: preserve-3d;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.book-wrap {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
transform-style: preserve-3d;
|
||||||
|
transition: 'all ease-out 0.6s';
|
||||||
|
}
|
||||||
|
|
||||||
|
.book {
|
||||||
|
width: var(--book-w);
|
||||||
|
height: var(--book-h);
|
||||||
|
background: url(https://covers.openlibrary.org/b/id/8303020-L.jpg) no-repeat center center;
|
||||||
|
background-size: cover;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
margin: auto;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.title {
|
||||||
|
content: '';
|
||||||
|
height: var(--book-h);
|
||||||
|
width: var(--book-d);
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
left: calc(var(--book-wx) * -1);
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
margin: auto;
|
||||||
|
background: #444;
|
||||||
|
transform: rotateY(-80deg) translateX(-14px);
|
||||||
|
|
||||||
|
background: url(https://covers.openlibrary.org/b/id/8303020-L.jpg) no-repeat center center;
|
||||||
|
background-size: 5000%;
|
||||||
|
filter: blur(1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottom {
|
||||||
|
content: '';
|
||||||
|
height: var(--book-d);
|
||||||
|
width: var(--book-w);
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
bottom: var(--book-h);
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
margin: auto;
|
||||||
|
background: #444;
|
||||||
|
transform: rotateY(0deg) rotateX(90deg) translateY(-15px) translateX(-2.5px) skewX(10deg);
|
||||||
|
|
||||||
|
background: url(https://covers.openlibrary.org/b/id/8303020-L.jpg) no-repeat center center;
|
||||||
|
background-size: 5000%;
|
||||||
|
filter: blur(1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.book-back {
|
||||||
|
width: var(--book-w);
|
||||||
|
height: var(--book-h);
|
||||||
|
background-color: #444;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
margin: auto;
|
||||||
|
cursor: pointer;
|
||||||
|
transform: rotate(180deg) translateZ(-30px) translateX(5px);
|
||||||
|
}
|
||||||
|
.book-back .text {
|
||||||
|
transform: rotateX(180deg);
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0px;
|
||||||
|
padding: 20px;
|
||||||
|
text-align: left;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
.book-back .text h3 {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.book-back .text span {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.book-wrap.rotate {
|
||||||
|
transform: rotateY(30deg) rotateX(0deg);
|
||||||
|
}
|
||||||
|
.book-wrap.flip {
|
||||||
|
transform: rotateY(180deg);
|
||||||
|
} */
|
||||||
|
</style>
|
||||||
@@ -79,15 +79,7 @@ export default {
|
|||||||
return '/book_placeholder.jpg'
|
return '/book_placeholder.jpg'
|
||||||
},
|
},
|
||||||
fullCoverUrl() {
|
fullCoverUrl() {
|
||||||
if (!this.cover || this.cover === this.placeholderUrl) return this.placeholderUrl
|
return this.$store.getters['audiobooks/getBookCoverSrc'](this.book, this.placeholderUrl)
|
||||||
if (this.cover.startsWith('http:') || this.cover.startsWith('https:')) return this.cover
|
|
||||||
try {
|
|
||||||
var url = new URL(this.cover, document.baseURI)
|
|
||||||
return url.href + `?token=${this.userToken}&ts=${this.bookLastUpdate}`
|
|
||||||
} catch (err) {
|
|
||||||
console.error(err)
|
|
||||||
return ''
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
cover() {
|
cover() {
|
||||||
return this.book.cover || this.placeholderUrl
|
return this.book.cover || this.placeholderUrl
|
||||||
|
|||||||
@@ -0,0 +1,87 @@
|
|||||||
|
<template>
|
||||||
|
<div class="relative">
|
||||||
|
<div class="rounded-sm h-full overflow-hidden relative" :style="{ padding: `16px ${paddingX}px` }" @mouseover="isHovering = true" @mouseleave="isHovering = false" @click="clickCard">
|
||||||
|
<nuxt-link :to="`/library/series?${groupType}=${groupEncode}`" class="cursor-pointer">
|
||||||
|
<div class="w-full relative" :class="isHovering ? 'bg-black-400' : 'bg-primary'" :style="{ height: height + 'px', width: height + 'px' }">
|
||||||
|
<cards-group-cover ref="groupcover" :name="groupName" :book-items="bookItems" :width="height" :height="height" />
|
||||||
|
|
||||||
|
<div v-if="hasValidCovers" class="bg-black bg-opacity-60 absolute top-0 left-0 w-full h-full flex items-center justify-center text-center transition-opacity" :class="isHovering ? '' : 'opacity-0'">
|
||||||
|
<p class="truncate font-book" :style="{ fontSize: sizeMultiplier + 'rem' }">{{ groupName }}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="absolute top-2 right-2 w-7 h-7 rounded-lg bg-black bg-opacity-90 text-gray-300 box-shadow-book flex items-center justify-center border border-white border-opacity-25 pointer-events-none">
|
||||||
|
<p class="font-book text-xl">{{ bookItems.length }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nuxt-link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
group: {
|
||||||
|
type: Object,
|
||||||
|
default: () => null
|
||||||
|
},
|
||||||
|
width: {
|
||||||
|
type: Number,
|
||||||
|
default: 120
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
isHovering: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
width(newVal) {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
if (this.$refs.groupcover) {
|
||||||
|
this.$refs.groupcover.init()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
_group() {
|
||||||
|
return this.group || {}
|
||||||
|
},
|
||||||
|
height() {
|
||||||
|
return this.width * 1.6
|
||||||
|
},
|
||||||
|
sizeMultiplier() {
|
||||||
|
return this.width / 120
|
||||||
|
},
|
||||||
|
paddingX() {
|
||||||
|
return 16 * this.sizeMultiplier
|
||||||
|
},
|
||||||
|
bookItems() {
|
||||||
|
return this._group.books || []
|
||||||
|
},
|
||||||
|
groupName() {
|
||||||
|
return this._group.name || 'No Name'
|
||||||
|
},
|
||||||
|
groupType() {
|
||||||
|
return this._group.type
|
||||||
|
},
|
||||||
|
groupEncode() {
|
||||||
|
return this.$encode(this.groupName)
|
||||||
|
},
|
||||||
|
filter() {
|
||||||
|
return `${this.groupType}.${this.$encode(this.groupName)}`
|
||||||
|
},
|
||||||
|
hasValidCovers() {
|
||||||
|
var validCovers = this.bookItems.map((bookItem) => bookItem.book.cover)
|
||||||
|
return !!validCovers.length
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
clickCard() {
|
||||||
|
this.$emit('click', this.group)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,139 @@
|
|||||||
|
<template>
|
||||||
|
<div ref="wrapper" :style="{ height: height + 'px', width: width + 'px' }" class="relative">
|
||||||
|
<div v-if="noValidCovers" class="absolute top-0 left-0 w-full h-full flex items-center justify-center box-shadow-book">
|
||||||
|
<p :style="{ fontSize: sizeMultiplier + 'rem' }">{{ name }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
name: String,
|
||||||
|
bookItems: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
width: Number,
|
||||||
|
height: Number
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
noValidCovers: false,
|
||||||
|
coverDiv: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
bookItems: {
|
||||||
|
immediate: true,
|
||||||
|
handler(newVal) {
|
||||||
|
if (newVal) {
|
||||||
|
// ensure wrapper is initialized
|
||||||
|
this.$nextTick(this.init)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
sizeMultiplier() {
|
||||||
|
return this.width / 192
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getCoverUrl(book) {
|
||||||
|
return this.$store.getters['audiobooks/getBookCoverSrc'](book, '')
|
||||||
|
},
|
||||||
|
async buildCoverImg(src, bgCoverWidth, offsetLeft, forceCoverBg = false) {
|
||||||
|
var showCoverBg =
|
||||||
|
forceCoverBg ||
|
||||||
|
(await new Promise((resolve) => {
|
||||||
|
var image = new Image()
|
||||||
|
|
||||||
|
image.onload = () => {
|
||||||
|
var { naturalWidth, naturalHeight } = image
|
||||||
|
var aspectRatio = naturalHeight / naturalWidth
|
||||||
|
var arDiff = Math.abs(aspectRatio - 1.6)
|
||||||
|
|
||||||
|
// If image aspect ratio is <= 1.45 or >= 1.75 then use cover bg, otherwise stretch to fit
|
||||||
|
if (arDiff > 0.15) {
|
||||||
|
resolve(true)
|
||||||
|
} else {
|
||||||
|
resolve(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
image.onerror = (err) => {
|
||||||
|
console.error(err)
|
||||||
|
resolve(false)
|
||||||
|
}
|
||||||
|
image.src = src
|
||||||
|
}))
|
||||||
|
|
||||||
|
var imgdiv = document.createElement('div')
|
||||||
|
imgdiv.style.height = this.height + 'px'
|
||||||
|
imgdiv.style.width = bgCoverWidth + 'px'
|
||||||
|
imgdiv.style.left = offsetLeft + 'px'
|
||||||
|
imgdiv.className = 'absolute top-0 box-shadow-book'
|
||||||
|
imgdiv.style.boxShadow = '-4px 0px 4px #11111166'
|
||||||
|
// imgdiv.style.transform = 'skew(0deg, 15deg)'
|
||||||
|
|
||||||
|
if (showCoverBg) {
|
||||||
|
var coverbgwrapper = document.createElement('div')
|
||||||
|
coverbgwrapper.className = 'absolute top-0 left-0 w-full h-full bg-primary'
|
||||||
|
|
||||||
|
var coverbg = document.createElement('div')
|
||||||
|
coverbg.className = 'w-full h-full'
|
||||||
|
coverbg.style.backgroundImage = `url("${src}")`
|
||||||
|
coverbg.style.backgroundSize = 'cover'
|
||||||
|
coverbg.style.backgroundPosition = 'center'
|
||||||
|
coverbg.style.opacity = 0.25
|
||||||
|
coverbg.style.filter = 'blur(1px)'
|
||||||
|
|
||||||
|
coverbgwrapper.appendChild(coverbg)
|
||||||
|
imgdiv.appendChild(coverbgwrapper)
|
||||||
|
}
|
||||||
|
|
||||||
|
var img = document.createElement('img')
|
||||||
|
img.src = src
|
||||||
|
img.className = 'absolute top-0 left-0 w-full h-full'
|
||||||
|
img.style.objectFit = showCoverBg ? 'contain' : 'cover'
|
||||||
|
|
||||||
|
imgdiv.appendChild(img)
|
||||||
|
return imgdiv
|
||||||
|
},
|
||||||
|
async init() {
|
||||||
|
if (this.coverDiv) {
|
||||||
|
this.coverDiv.remove()
|
||||||
|
this.coverDiv = null
|
||||||
|
}
|
||||||
|
var validCovers = this.bookItems.map((bookItem) => this.getCoverUrl(bookItem.book)).filter((b) => b !== '')
|
||||||
|
if (!validCovers.length) {
|
||||||
|
this.noValidCovers = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.noValidCovers = false
|
||||||
|
|
||||||
|
var coverWidth = this.width
|
||||||
|
var widthPer = this.width
|
||||||
|
if (validCovers.length > 1) {
|
||||||
|
coverWidth = this.height / 1.6
|
||||||
|
widthPer = (this.width - coverWidth) / (validCovers.length - 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
var outerdiv = document.createElement('div')
|
||||||
|
outerdiv.className = 'w-full h-full relative'
|
||||||
|
|
||||||
|
for (let i = 0; i < validCovers.length; i++) {
|
||||||
|
var offsetLeft = widthPer * i
|
||||||
|
var img = await this.buildCoverImg(validCovers[i], coverWidth, offsetLeft, validCovers.length === 1)
|
||||||
|
outerdiv.appendChild(img)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.$refs.wrapper) {
|
||||||
|
this.coverDiv = outerdiv
|
||||||
|
this.$refs.wrapper.appendChild(outerdiv)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -115,7 +115,10 @@ export default {
|
|||||||
clickedOption(e, item) {
|
clickedOption(e, item) {
|
||||||
this.textInput = null
|
this.textInput = null
|
||||||
this.currentSearch = null
|
this.currentSearch = null
|
||||||
this.input = this.textInput ? this.textInput.trim() : null
|
this.input = item
|
||||||
|
|
||||||
|
// this.input = this.textInput ? this.textInput.trim() : null
|
||||||
|
console.log('Clicked option', item)
|
||||||
if (this.$refs.input) this.$refs.input.blur()
|
if (this.$refs.input) this.$refs.input.blur()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ export default {
|
|||||||
var tooltip = document.createElement('div')
|
var tooltip = document.createElement('div')
|
||||||
tooltip.className = 'absolute px-2 py-1 text-white pointer-events-none text-xs rounded shadow-lg max-w-xs'
|
tooltip.className = 'absolute px-2 py-1 text-white pointer-events-none text-xs rounded shadow-lg max-w-xs'
|
||||||
tooltip.style.zIndex = 100
|
tooltip.style.zIndex = 100
|
||||||
tooltip.style.backgroundColor = 'rgba(0,0,0,0.75)'
|
tooltip.style.backgroundColor = 'rgba(0,0,0,0.85)'
|
||||||
tooltip.innerHTML = this.text
|
tooltip.innerHTML = this.text
|
||||||
|
|
||||||
this.setTooltipPosition(tooltip)
|
this.setTooltipPosition(tooltip)
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="text-white max-h-screen h-screen overflow-hidden bg-bg">
|
<div class="text-white max-h-screen h-screen overflow-hidden bg-bg">
|
||||||
<app-appbar />
|
<app-appbar />
|
||||||
|
|
||||||
<Nuxt />
|
<Nuxt />
|
||||||
|
|
||||||
<app-stream-container ref="streamContainer" />
|
<app-stream-container ref="streamContainer" />
|
||||||
<modals-edit-modal />
|
<modals-edit-modal />
|
||||||
<widgets-scan-alert />
|
<widgets-scan-alert />
|
||||||
@@ -84,7 +86,7 @@ export default {
|
|||||||
audiobookRemoved(audiobook) {
|
audiobookRemoved(audiobook) {
|
||||||
if (this.$route.name.startsWith('audiobook')) {
|
if (this.$route.name.startsWith('audiobook')) {
|
||||||
if (this.$route.params.id === audiobook.id) {
|
if (this.$route.params.id === audiobook.id) {
|
||||||
this.$router.replace('/')
|
this.$router.replace('/library')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.$store.commit('audiobooks/remove', audiobook)
|
this.$store.commit('audiobooks/remove', audiobook)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
export default function ({ store, redirect, route }) {
|
export default function ({ store, redirect, route, app }) {
|
||||||
// If the user is not authenticated
|
// If the user is not authenticated
|
||||||
if (!store.state.user.user) {
|
if (!store.state.user.user) {
|
||||||
if (route.name === 'batch') return redirect('/login')
|
if (route.name === 'batch') return redirect('/login')
|
||||||
return redirect(`/login?redirect=${route.path}`)
|
return redirect(`/login?redirect=${route.fullPath}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "audiobookshelf-client",
|
"name": "audiobookshelf-client",
|
||||||
"version": "1.1.15",
|
"version": "1.2.0",
|
||||||
"description": "Audiobook manager and player",
|
"description": "Audiobook manager and player",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -130,7 +130,7 @@ export default {
|
|||||||
this.isProcessing = false
|
this.isProcessing = false
|
||||||
if (data.updates) {
|
if (data.updates) {
|
||||||
this.$toast.success(`Successfully updated ${data.updates} audiobooks`)
|
this.$toast.success(`Successfully updated ${data.updates} audiobooks`)
|
||||||
this.$router.replace('/')
|
this.$router.replace('/library')
|
||||||
} else {
|
} else {
|
||||||
this.$toast.warning('No updates were necessary')
|
this.$toast.warning('No updates were necessary')
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-2
@@ -1,12 +1,20 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="page" :class="streamAudiobook ? 'streaming' : ''">
|
<div class="page" :class="streamAudiobook ? 'streaming' : ''">
|
||||||
<app-book-shelf-toolbar />
|
<!-- <app-book-shelf-toolbar /> -->
|
||||||
<app-book-shelf />
|
<!-- <div class="flex h-full">
|
||||||
|
<app-side-rail />
|
||||||
|
<div class="flex-grow"> -->
|
||||||
|
<!-- <app-book-shelf /> -->
|
||||||
|
<!-- </div> -->
|
||||||
|
<!-- </div> -->
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
|
asyncData({ redirect }) {
|
||||||
|
redirect('/library')
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
return {}
|
return {}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
<template>
|
||||||
|
<div class="page" :class="streamAudiobook ? 'streaming' : ''">
|
||||||
|
<div class="flex h-full">
|
||||||
|
<app-side-rail />
|
||||||
|
<div class="flex-grow">
|
||||||
|
<app-book-shelf-toolbar :page="id || ''" :selected-series.sync="selectedSeries" />
|
||||||
|
<app-book-shelf :page="id || ''" :selected-series.sync="selectedSeries" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
asyncData({ params, query, store, app }) {
|
||||||
|
if (query.filter) {
|
||||||
|
store.dispatch('user/updateUserSettings', { filterBy: query.filter })
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
id: params.id,
|
||||||
|
selectedSeries: query.series ? app.$decode(query.series) : null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
streamAudiobook() {
|
||||||
|
return this.$store.state.streamAudiobook
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {},
|
||||||
|
mounted() {}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -37,7 +37,7 @@ export default {
|
|||||||
if (this.$route.query.redirect) {
|
if (this.$route.query.redirect) {
|
||||||
this.$router.replace(this.$route.query.redirect)
|
this.$router.replace(this.$route.query.redirect)
|
||||||
} else {
|
} else {
|
||||||
this.$router.replace('/')
|
this.$router.replace('/library')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -124,4 +124,7 @@ Vue.prototype.$decode = decode
|
|||||||
export {
|
export {
|
||||||
encode,
|
encode,
|
||||||
decode
|
decode
|
||||||
|
}
|
||||||
|
export default ({ app }, inject) => {
|
||||||
|
app.$decode = decode
|
||||||
}
|
}
|
||||||
@@ -5,6 +5,7 @@ const STANDARD_GENRES = ['Adventure', 'Autobiography', 'Biography', 'Childrens',
|
|||||||
|
|
||||||
export const state = () => ({
|
export const state = () => ({
|
||||||
audiobooks: [],
|
audiobooks: [],
|
||||||
|
lastLoad: 0,
|
||||||
listeners: [],
|
listeners: [],
|
||||||
genres: [...STANDARD_GENRES],
|
genres: [...STANDARD_GENRES],
|
||||||
tags: [],
|
tags: [],
|
||||||
@@ -63,6 +64,23 @@ export const getters = {
|
|||||||
return value
|
return value
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
getSeriesGroups: (state, getters, rootState) => () => {
|
||||||
|
var series = {}
|
||||||
|
state.audiobooks.forEach((audiobook) => {
|
||||||
|
if (audiobook.book && audiobook.book.series) {
|
||||||
|
if (series[audiobook.book.series]) {
|
||||||
|
series[audiobook.book.series].books.push(audiobook)
|
||||||
|
} else {
|
||||||
|
series[audiobook.book.series] = {
|
||||||
|
type: 'series',
|
||||||
|
name: audiobook.book.series,
|
||||||
|
books: [audiobook]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return Object.values(series)
|
||||||
|
},
|
||||||
getUniqueAuthors: (state) => {
|
getUniqueAuthors: (state) => {
|
||||||
var _authors = state.audiobooks.filter(ab => !!(ab.book && ab.book.author)).map(ab => ab.book.author)
|
var _authors = state.audiobooks.filter(ab => !!(ab.book && ab.book.author)).map(ab => ab.book.author)
|
||||||
return [...new Set(_authors)].sort((a, b) => a.toLowerCase() < b.toLowerCase() ? -1 : 1)
|
return [...new Set(_authors)].sort((a, b) => a.toLowerCase() < b.toLowerCase() ? -1 : 1)
|
||||||
@@ -71,29 +89,63 @@ export const getters = {
|
|||||||
var _genres = []
|
var _genres = []
|
||||||
state.audiobooks.filter(ab => !!(ab.book && ab.book.genres)).forEach(ab => _genres = _genres.concat(ab.book.genres))
|
state.audiobooks.filter(ab => !!(ab.book && ab.book.genres)).forEach(ab => _genres = _genres.concat(ab.book.genres))
|
||||||
return [...new Set(_genres)].sort((a, b) => a.toLowerCase() < b.toLowerCase() ? -1 : 1)
|
return [...new Set(_genres)].sort((a, b) => a.toLowerCase() < b.toLowerCase() ? -1 : 1)
|
||||||
|
},
|
||||||
|
getBookCoverSrc: (state, getters, rootState, rootGetters) => (book, placeholder = '/book_placeholder.jpg') => {
|
||||||
|
if (!book || !book.cover || book.cover === placeholder) return placeholder
|
||||||
|
var cover = book.cover
|
||||||
|
|
||||||
|
// Absolute URL covers
|
||||||
|
if (cover.startsWith('http:') || cover.startsWith('https:')) return cover
|
||||||
|
|
||||||
|
// Server hosted covers
|
||||||
|
try {
|
||||||
|
// Ensure cover is refreshed if cached
|
||||||
|
var bookLastUpdate = book.lastUpdate || Date.now()
|
||||||
|
var userToken = rootGetters['user/getToken']
|
||||||
|
|
||||||
|
var url = new URL(cover, document.baseURI)
|
||||||
|
return url.href + `?token=${userToken}&ts=${bookLastUpdate}`
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err)
|
||||||
|
return placeholder
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const actions = {
|
export const actions = {
|
||||||
load({ commit, rootState }) {
|
// Return true if calling load
|
||||||
|
load({ state, commit, rootState }) {
|
||||||
if (!rootState.user || !rootState.user.user) {
|
if (!rootState.user || !rootState.user.user) {
|
||||||
console.error('audiobooks/load - User not set')
|
console.error('audiobooks/load - User not set')
|
||||||
return
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Don't load again if already loaded in the last 5 minutes
|
||||||
|
var lastLoadDiff = Date.now() - state.lastLoad
|
||||||
|
if (lastLoadDiff < 5 * 60 * 1000) {
|
||||||
|
// Already up to date
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
this.$axios
|
this.$axios
|
||||||
.$get(`/api/audiobooks`)
|
.$get(`/api/audiobooks`)
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
commit('set', data)
|
commit('set', data)
|
||||||
|
commit('setLastLoad')
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error('Failed', error)
|
console.error('Failed', error)
|
||||||
commit('set', [])
|
commit('set', [])
|
||||||
})
|
})
|
||||||
|
return true
|
||||||
},
|
},
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const mutations = {
|
export const mutations = {
|
||||||
|
setLastLoad(state) {
|
||||||
|
state.lastLoad = Date.now()
|
||||||
|
},
|
||||||
setKeywordFilter(state, val) {
|
setKeywordFilter(state, val) {
|
||||||
state.keywordFilter = val
|
state.keywordFilter = val
|
||||||
},
|
},
|
||||||
|
|||||||
+14
-2
@@ -1,13 +1,25 @@
|
|||||||
{
|
{
|
||||||
"name": "audiobookshelf",
|
"name": "audiobookshelf",
|
||||||
"version": "1.1.15",
|
"version": "1.2.0",
|
||||||
"description": "Self-hosted audiobook server for managing and playing audiobooks.",
|
"description": "Self-hosted audiobook server for managing and playing audiobooks.",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "node index.js",
|
"dev": "node index.js",
|
||||||
"start": "node index.js",
|
"start": "node index.js",
|
||||||
"client": "cd client && npm install && npm run generate",
|
"client": "cd client && npm install && npm run generate",
|
||||||
"prod": "npm run client && npm install && node prod.js"
|
"prod": "npm run client && npm install && node prod.js",
|
||||||
|
"generate": "cd client && npm run generate",
|
||||||
|
"build-win": "pkg -t node12-win-x64 -o ./dist/win/audiobookshelf .",
|
||||||
|
"build-linuxarm": "pkg -t node12-linux-arm64 -o ./dist/linuxarm/audiobookshelf .",
|
||||||
|
"build-linuxamd": "pkg -t node12-linux-amd64 -o ./dist/linuxamd/audiobookshelf ."
|
||||||
|
},
|
||||||
|
"bin": "prod.js",
|
||||||
|
"pkg": {
|
||||||
|
"assets": "client/dist/**/*",
|
||||||
|
"scripts": [
|
||||||
|
"prod.js",
|
||||||
|
"server/**/*.js"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"author": "advplyr",
|
"author": "advplyr",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ process.env.TOKEN_SECRET = '09f26e402586e2faa8da4c98a35f1b20d6b033c6097befa8be34
|
|||||||
process.env.NODE_ENV = 'production'
|
process.env.NODE_ENV = 'production'
|
||||||
|
|
||||||
const server = require('./server/Server')
|
const server = require('./server/Server')
|
||||||
|
|
||||||
global.appRoot = __dirname
|
global.appRoot = __dirname
|
||||||
|
|
||||||
var inputConfig = options.config ? Path.resolve(options.config) : null
|
var inputConfig = options.config ? Path.resolve(options.config) : null
|
||||||
@@ -24,7 +25,7 @@ const CONFIG_PATH = inputConfig || process.env.CONFIG_PATH || Path.resolve('conf
|
|||||||
const AUDIOBOOK_PATH = inputAudiobook || process.env.AUDIOBOOK_PATH || Path.resolve('audiobooks')
|
const AUDIOBOOK_PATH = inputAudiobook || process.env.AUDIOBOOK_PATH || Path.resolve('audiobooks')
|
||||||
const METADATA_PATH = inputMetadata || process.env.METADATA_PATH || Path.resolve('metadata')
|
const METADATA_PATH = inputMetadata || process.env.METADATA_PATH || Path.resolve('metadata')
|
||||||
|
|
||||||
console.log('Config', CONFIG_PATH, METADATA_PATH, AUDIOBOOK_PATH)
|
console.log(process.env.NODE_ENV, 'Config', CONFIG_PATH, METADATA_PATH, AUDIOBOOK_PATH)
|
||||||
|
|
||||||
const Server = new server(PORT, CONFIG_PATH, METADATA_PATH, AUDIOBOOK_PATH)
|
const Server = new server(PORT, CONFIG_PATH, METADATA_PATH, AUDIOBOOK_PATH)
|
||||||
Server.start()
|
Server.start()
|
||||||
|
|||||||
@@ -82,9 +82,6 @@ cd audiobookshelf
|
|||||||
# Directories will be created if they don't exist
|
# Directories will be created if they don't exist
|
||||||
# Paths are relative to the root directory, so "../Audiobooks" would be a valid path
|
# Paths are relative to the root directory, so "../Audiobooks" would be a valid path
|
||||||
npm run prod -- -p [PORT] --audiobooks [AUDIOBOOKS_PATH] --config [CONFIG_PATH] --metadata [METADATA_PATH]
|
npm run prod -- -p [PORT] --audiobooks [AUDIOBOOKS_PATH] --config [CONFIG_PATH] --metadata [METADATA_PATH]
|
||||||
|
|
||||||
# You only need to use `npm run prod` the first time, after that use `npm run start`
|
|
||||||
npm run start -- -p [PORT] --audiobooks [AUDIOBOOKS_PATH] --config [CONFIG_PATH] --metadata [METADATA_PATH]
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|||||||
+3
-1
@@ -113,6 +113,7 @@ class Server {
|
|||||||
await this.streamManager.ensureStreamsDir()
|
await this.streamManager.ensureStreamsDir()
|
||||||
await this.streamManager.removeOrphanStreams()
|
await this.streamManager.removeOrphanStreams()
|
||||||
await this.downloadManager.removeOrphanDownloads()
|
await this.downloadManager.removeOrphanDownloads()
|
||||||
|
|
||||||
await this.db.init()
|
await this.db.init()
|
||||||
this.auth.init()
|
this.auth.init()
|
||||||
|
|
||||||
@@ -171,7 +172,6 @@ class Server {
|
|||||||
|
|
||||||
async start() {
|
async start() {
|
||||||
Logger.info('=== Starting Server ===')
|
Logger.info('=== Starting Server ===')
|
||||||
|
|
||||||
await this.init()
|
await this.init()
|
||||||
|
|
||||||
const app = express()
|
const app = express()
|
||||||
@@ -199,6 +199,8 @@ class Server {
|
|||||||
|
|
||||||
// Dynamic routes are not generated on client
|
// Dynamic routes are not generated on client
|
||||||
app.get('/audiobook/:id', (req, res) => res.sendFile(Path.join(distPath, 'index.html')))
|
app.get('/audiobook/:id', (req, res) => res.sendFile(Path.join(distPath, 'index.html')))
|
||||||
|
app.get('/library/:id', (req, res) => res.sendFile(Path.join(distPath, 'index.html')))
|
||||||
|
app.get('/library', (req, res) => res.sendFile(Path.join(distPath, 'index.html')))
|
||||||
|
|
||||||
app.use('/api', this.authMiddleware.bind(this), this.apiController.router)
|
app.use('/api', this.authMiddleware.bind(this), this.apiController.router)
|
||||||
app.use('/hls', this.authMiddleware.bind(this), this.hlsController.router)
|
app.use('/hls', this.authMiddleware.bind(this), this.hlsController.router)
|
||||||
|
|||||||
Reference in New Issue
Block a user