Skip to content

Commit

Permalink
Book cover uploader, moving streams to /metadata/streams, adding jwt …
Browse files Browse the repository at this point in the history
…auth from query string, auth check static metadata
  • Loading branch information
advplyr committed Sep 22, 2021
1 parent d234fde commit 6cb418a
Show file tree
Hide file tree
Showing 18 changed files with 376 additions and 35 deletions.
12 changes: 9 additions & 3 deletions client/components/cards/BookCover.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<div v-if="showCoverBg" class="bg-primary absolute top-0 left-0 w-full h-full">
<div class="w-full h-full z-0" ref="coverBg" />
</div>
<img ref="cover" :src="cover" @error="imageError" @load="imageLoaded" class="w-full h-full absolute top-0 left-0" :class="showCoverBg ? 'object-contain' : 'object-cover'" />
<img ref="cover" :src="fullCoverUrl" @error="imageError" @load="imageLoaded" class="w-full h-full absolute top-0 left-0" :class="showCoverBg ? 'object-contain' : 'object-cover'" />
</div>

<div v-if="imageFailed" class="absolute top-0 left-0 right-0 bottom-0 w-full h-full bg-red-100" :style="{ padding: placeholderCoverPadding + 'rem' }">
Expand Down Expand Up @@ -53,6 +53,9 @@ export default {
book() {
return this.audiobook.book || {}
},
bookLastUpdate() {
return this.book.lastUpdate || Date.now()
},
title() {
return this.book.title || 'No Title'
},
Expand All @@ -76,11 +79,11 @@ export default {
return '/book_placeholder.jpg'
},
fullCoverUrl() {
if (!this.cover || this.cover === this.placeholderUrl) return ''
if (!this.cover || this.cover === this.placeholderUrl) return 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
return url.href + `?token=${this.userToken}&ts=${this.bookLastUpdate}`
} catch (err) {
console.error(err)
return ''
Expand All @@ -106,6 +109,9 @@ export default {
},
authorBottom() {
return 0.75 * this.sizeMultiplier
},
userToken() {
return this.$store.getters['user/getToken']
}
},
methods: {
Expand Down
82 changes: 82 additions & 0 deletions client/components/cards/PreviewCover.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
<template>
<div class="relative rounded-sm overflow-hidden" :style="{ height: width * 1.6 + 'px', width: width + 'px', maxWidth: width + 'px', minWidth: width + 'px' }">
<div class="w-full h-full relative">
<div v-if="showCoverBg" class="bg-primary absolute top-0 left-0 w-full h-full">
<div class="w-full h-full z-0" ref="coverBg" />
</div>
<img ref="cover" :src="cover" @error="imageError" @load="imageLoaded" class="w-full h-full absolute top-0 left-0" :class="showCoverBg ? 'object-contain' : 'object-cover'" />
</div>

<div v-if="imageFailed" class="absolute top-0 left-0 right-0 bottom-0 w-full h-full bg-red-100" :style="{ padding: placeholderCoverPadding + 'rem' }">
<div class="w-full h-full border-2 border-error flex flex-col items-center justify-center">
<img src="/Logo.png" class="mb-2" :style="{ height: 64 * sizeMultiplier + 'px' }" />
<p class="text-center font-book text-error" :style="{ fontSize: sizeMultiplier + 'rem' }">Invalid Cover</p>
</div>
</div>
</div>
</template>

<script>
export default {
props: {
src: String,
width: {
type: Number,
default: 120
}
},
data() {
return {
imageFailed: false,
showCoverBg: false
}
},
watch: {
cover() {
this.imageFailed = false
}
},
computed: {
cover() {
return this.src
},
sizeMultiplier() {
return this.width / 120
},
placeholderCoverPadding() {
return 0.8 * this.sizeMultiplier
}
},
methods: {
setCoverBg() {
if (this.$refs.coverBg) {
this.$refs.coverBg.style.backgroundImage = `url("${this.src}")`
this.$refs.coverBg.style.backgroundSize = 'cover'
this.$refs.coverBg.style.backgroundPosition = 'center'
this.$refs.coverBg.style.opacity = 0.25
this.$refs.coverBg.style.filter = 'blur(1px)'
}
},
imageLoaded() {
if (this.$refs.cover) {
var { naturalWidth, naturalHeight } = this.$refs.cover
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) {
this.showCoverBg = true
this.$nextTick(this.setCoverBg)
} else {
this.showCoverBg = false
}
}
},
imageError(err) {
console.error('ImgError', err)
this.imageFailed = true
}
},
mounted() {}
}
</script>
66 changes: 60 additions & 6 deletions client/components/modals/edit-tabs/Cover.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<template>
<div class="w-full h-full overflow-hidden overflow-y-auto px-4 py-6">
<div class="w-full h-full overflow-hidden overflow-y-auto px-4 py-6 relative">
<div class="flex">
<div class="relative">
<cards-book-cover :audiobook="audiobook" />
Expand All @@ -12,12 +12,15 @@
</div>
</div>
<div class="flex-grow pl-6 pr-2">
<form @submit.prevent="submitForm">
<div class="flex items-center">
<div class="flex items-center">
<div v-if="userCanUpload" class="w-40 pr-2" style="min-width: 160px">
<ui-file-input ref="fileInput" @change="fileUploadSelected" />
</div>
<form @submit.prevent="submitForm" class="flex flex-grow">
<ui-text-input-with-label v-model="imageUrl" label="Cover Image URL" />
<ui-btn color="success" type="submit" :padding-x="4" class="mt-5 ml-3 w-24">Update</ui-btn>
</div>
</form>
</form>
</div>

<div v-if="localCovers.length" class="mb-4 mt-6 border-t border-b border-primary">
<div class="flex items-center justify-center py-2">
Expand Down Expand Up @@ -63,6 +66,18 @@
</div>
</div>
</div>

<div v-if="previewUpload" class="absolute top-0 left-0 w-full h-full z-10 bg-bg p-8">
<p class="text-lg">Preview Cover</p>
<span class="absolute top-4 right-4 material-icons text-2xl cursor-pointer" @click="resetCoverPreview">close</span>
<div class="flex justify-center py-4">
<cards-preview-cover :src="previewUpload" :width="240" />
</div>
<div class="absolute bottom-0 right-0 flex py-4 px-5">
<ui-btn :disabled="processingUpload" class="mx-2" @click="resetCoverPreview">Clear</ui-btn>
<ui-btn :loading="processingUpload" color="success" @click="submitCoverUpload">Upload</ui-btn>
</div>
</div>
</div>
</template>

Expand All @@ -79,12 +94,15 @@ export default {
},
data() {
return {
processingUpload: false,
searchTitle: null,
searchAuthor: null,
imageUrl: null,
coversFound: [],
hasSearched: false,
showLocalCovers: false
showLocalCovers: false,
previewUpload: null,
selectedFile: null
}
},
watch: {
Expand Down Expand Up @@ -112,6 +130,9 @@ export default {
otherFiles() {
return this.audiobook ? this.audiobook.otherFiles || [] : []
},
userCanUpload() {
return this.$store.getters['user/getUserCanUpload']
},
localCovers() {
return this.otherFiles
.filter((f) => f.filetype === 'image')
Expand All @@ -123,6 +144,39 @@ export default {
}
},
methods: {
submitCoverUpload() {
this.processingUpload = true
var form = new FormData()
form.set('cover', this.selectedFile)
this.$axios
.$post(`/api/audiobook/${this.audiobook.id}/cover`, form)
.then((data) => {
if (data.error) {
this.$toast.error(data.error)
} else {
this.$toast.success('Cover Uploaded')
this.resetCoverPreview()
}
this.processingUpload = false
})
.catch((error) => {
console.error('Failed', error)
this.$toast.error('Oops, something went wrong...')
this.processingUpload = false
})
},
resetCoverPreview() {
if (this.$refs.fileInput) {
this.$refs.fileInput.reset()
}
this.previewUpload = null
this.selectedFile = null
},
fileUploadSelected(file) {
this.previewUpload = URL.createObjectURL(file)
this.selectedFile = file
},
init() {
this.showLocalCovers = false
if (this.coversFound.length && (this.searchTitle !== this.book.title || this.searchAuthor !== this.book.author)) {
Expand Down
39 changes: 39 additions & 0 deletions client/components/ui/FileInput.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<template>
<div>
<input ref="fileInput" id="hidden-input" type="file" :accept="inputAccept" class="hidden" @change="inputChanged" />
<ui-btn @click="clickUpload" color="primary" type="text">Upload Cover</ui-btn>
</div>
</template>

<script>
export default {
data() {
return {
inputAccept: 'image/*'
}
},
computed: {},
methods: {
reset() {
if (this.$refs.fileInput) {
this.$refs.fileInput.value = ''
}
},
clickUpload() {
if (this.$refs.fileInput) {
this.$refs.fileInput.click()
}
},
inputChanged(e) {
if (!e.target || !e.target.files) return
var _files = Array.from(e.target.files)
if (_files && _files.length) {
var file = _files[0]
console.log('File', file)
this.$emit('change', file)
}
}
},
mounted() {}
}
</script>
3 changes: 2 additions & 1 deletion client/nuxt.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,8 @@ module.exports = {

proxy: {
'/dev/': { target: 'http://localhost:3333', pathRewrite: { '^/dev/': '' } },
'/local/': { target: process.env.NODE_ENV !== 'production' ? 'http://localhost:3333' : '/', pathRewrite: { '^/local/': '' } }
'/local/': { target: process.env.NODE_ENV !== 'production' ? 'http://localhost:3333' : '/', pathRewrite: { '^/local/': '' } },
'/metadata/': { target: process.env.NODE_ENV !== 'production' ? 'http://localhost:3333' : '/' }
},

io: {
Expand Down
2 changes: 1 addition & 1 deletion client/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "audiobookshelf-client",
"version": "1.1.14",
"version": "1.1.15",
"description": "Audiobook manager and player",
"main": "index.js",
"scripts": {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "audiobookshelf",
"version": "1.1.14",
"version": "1.1.15",
"description": "Self-hosted audiobook server for managing and playing audiobooks.",
"main": "index.js",
"scripts": {
Expand Down
Loading

0 comments on commit 6cb418a

Please sign in to comment.