audiobookshelf/client/pages/upload/index.vue

332 lines
12 KiB
Vue
Raw Normal View History

2021-09-14 03:18:58 +02:00
<template>
<div id="page-wrapper" class="page p-0 sm:p-6 overflow-y-auto" :class="streamLibraryItem ? 'streaming' : ''">
2022-02-26 23:19:22 +01:00
<div class="w-full max-w-6xl mx-auto">
<!-- Library & folder picker -->
<div class="flex my-6 -mx-2">
<div class="w-1/5 px-2">
<ui-dropdown v-model="selectedLibraryId" :items="libraryItems" label="Library" :disabled="!!items.length" @input="libraryChanged" />
2021-10-06 04:10:49 +02:00
</div>
<div class="w-3/5 px-2">
<ui-dropdown v-model="selectedFolderId" :items="folderItems" :disabled="!selectedLibraryId || !!items.length" label="Folder" />
</div>
<div class="w-1/5 px-2">
<ui-text-input-with-label :value="selectedLibraryMediaType" readonly label="Media Type" />
2021-09-14 03:18:58 +02:00
</div>
2022-02-26 23:19:22 +01:00
</div>
2021-09-14 03:18:58 +02:00
2022-02-26 23:19:22 +01:00
<widgets-alert v-if="error" type="error">
<p class="text-lg">{{ error }}</p>
</widgets-alert>
2021-09-14 03:18:58 +02:00
2022-02-26 23:19:22 +01:00
<!-- Picker display -->
<div v-if="!items.length && !ignoredFiles.length" class="w-full mx-auto border border-white border-opacity-20 px-12 pt-12 pb-4 my-12 relative" :class="isDragging ? 'bg-primary bg-opacity-40' : 'border-dashed'">
2022-02-26 23:19:22 +01:00
<p class="text-2xl text-center">{{ isDragging ? 'Drop files' : "Drag n' drop files or folders" }}</p>
<p class="text-center text-sm my-5">or</p>
<div class="w-full max-w-xl mx-auto">
<div class="flex">
<ui-btn class="w-full mx-1" @click="openFilePicker">Choose files</ui-btn>
<ui-btn class="w-full mx-1" @click="openFolderPicker">Choose a folder</ui-btn>
2021-09-14 03:18:58 +02:00
</div>
2022-02-26 23:19:22 +01:00
</div>
<div class="pt-8 text-center">
<p class="text-xs text-white text-opacity-50 font-mono mb-4"><strong>Supported File Types: </strong>{{ inputAccept.join(', ') }}</p>
<p class="text-sm text-white text-opacity-70">Folders with media files will be treated as separate library items. <span v-if="selectedLibraryMediaType === 'book'">If uploading only audio files then each audio file will be treated as a separate audiobook.</span></p>
2022-02-26 23:19:22 +01:00
</div>
</div>
<!-- Item list header -->
2022-02-26 23:19:22 +01:00
<div v-else class="w-full flex items-center pb-4 border-b border-white border-opacity-10">
<p class="text-lg">{{ items.length }} item{{ items.length === 1 ? '' : 's' }}</p>
2022-02-26 23:19:22 +01:00
<p v-if="ignoredFiles.length" class="text-lg">&nbsp;|&nbsp;{{ ignoredFiles.length }} file{{ ignoredFiles.length === 1 ? '' : 's' }} ignored</p>
<div class="flex-grow" />
<ui-btn :disabled="processing" small @click="reset">Reset</ui-btn>
</div>
2021-09-14 03:18:58 +02:00
2022-02-26 23:19:22 +01:00
<!-- Alerts -->
<widgets-alert v-if="!items.length && !uploadReady" type="error" class="my-4">
<p class="text-lg">No items found</p>
2022-02-26 23:19:22 +01:00
</widgets-alert>
<widgets-alert v-if="ignoredFiles.length" type="warning" class="my-4">
<div class="w-full pr-12">
<p class="text-base mb-1">Unsupported files are ignored. When choosing or dropping a folder, other files that are not in an item folder are ignored.</p>
2022-02-26 23:19:22 +01:00
<tables-uploaded-files-table :files="ignoredFiles" title="Ignored Files" class="text-white" />
<p class="text-xs text-white text-opacity-50 font-mono pt-1"><strong>Supported File Types: </strong>{{ inputAccept.join(', ') }}</p>
</div>
</widgets-alert>
2021-09-14 03:18:58 +02:00
<!-- Item Upload cards -->
<template v-for="item in items">
<cards-item-upload-card :ref="`itemCard-${item.index}`" :key="item.index" :media-type="selectedLibraryMediaType" :item="item" :processing="processing" @remove="removeItem(item)" />
2022-02-26 23:19:22 +01:00
</template>
2021-09-14 03:18:58 +02:00
2022-02-26 23:19:22 +01:00
<!-- Upload/Reset btns -->
<div v-show="items.length" class="flex justify-end pb-8 pt-4">
2022-02-26 23:19:22 +01:00
<ui-btn v-if="!uploadFinished" color="success" :loading="processing" @click="submit">Upload</ui-btn>
<ui-btn v-else @click="reset">Reset</ui-btn>
</div>
</div>
2021-09-14 03:18:58 +02:00
<input ref="fileInput" type="file" multiple :accept="inputAccept" class="hidden" @change="inputChanged" />
<input ref="fileFolderInput" type="file" webkitdirectory multiple :accept="inputAccept" class="hidden" @change="inputChanged" />
2021-09-14 03:18:58 +02:00
</div>
</template>
<script>
2022-02-26 23:19:22 +01:00
import uploadHelpers from '@/mixins/uploadHelpers'
2021-09-14 03:18:58 +02:00
export default {
2022-02-26 23:19:22 +01:00
mixins: [uploadHelpers],
2021-09-14 03:18:58 +02:00
data() {
return {
2022-02-26 23:19:22 +01:00
isDragging: false,
error: '',
items: [],
2022-02-26 23:19:22 +01:00
ignoredFiles: [],
2021-10-06 04:10:49 +02:00
selectedLibraryId: null,
2022-02-26 23:19:22 +01:00
selectedFolderId: null,
processing: false,
uploadFinished: false
2021-09-14 03:18:58 +02:00
}
},
watch: {
selectedLibrary(newVal) {
if (newVal && !this.selectedFolderId) {
this.setDefaultFolder()
}
}
},
2021-09-14 03:18:58 +02:00
computed: {
2022-02-26 23:19:22 +01:00
inputAccept() {
var extensions = []
Object.values(this.$constants.SupportedFileTypes).forEach((types) => {
extensions = extensions.concat(types.map((t) => `.${t}`))
})
return extensions
},
streamLibraryItem() {
return this.$store.state.streamLibraryItem
2021-09-14 03:18:58 +02:00
},
2021-10-06 04:10:49 +02:00
libraries() {
return this.$store.state.libraries.libraries
},
libraryItems() {
return this.libraries.map((lib) => {
return {
value: lib.id,
text: lib.name
}
})
},
selectedLibrary() {
return this.libraries.find((lib) => lib.id === this.selectedLibraryId)
},
selectedLibraryMediaType() {
return this.selectedLibrary ? this.selectedLibrary.mediaType : null
},
selectedLibraryIsPodcast() {
return this.selectedLibraryMediaType === 'podcast'
},
2021-10-06 04:10:49 +02:00
selectedFolder() {
if (!this.selectedLibrary) return null
return this.selectedLibrary.folders.find((fold) => fold.id === this.selectedFolderId)
},
folderItems() {
if (!this.selectedLibrary) return []
return this.selectedLibrary.folders.map((fold) => {
return {
value: fold.id,
text: fold.fullPath
}
})
2022-02-26 23:19:22 +01:00
},
uploadReady() {
return !this.items.length && !this.ignoredFiles.length && !this.uploadFinished
2021-09-14 03:18:58 +02:00
}
},
methods: {
2021-10-06 04:10:49 +02:00
libraryChanged() {
if (!this.selectedLibrary && this.selectedFolderId) {
this.selectedFolderId = null
} else if (this.selectedFolderId) {
if (!this.selectedLibrary.folders.find((fold) => fold.id === this.selectedFolderId)) {
this.selectedFolderId = null
}
}
this.setDefaultFolder()
},
setDefaultFolder() {
if (!this.selectedFolderId && this.selectedLibrary && this.selectedLibrary.folders.length) {
this.selectedFolderId = this.selectedLibrary.folders[0].id
}
},
removeItem(item) {
this.items = this.items.filter((b) => b.index !== item.index)
if (!this.items.length) {
2022-02-26 23:19:22 +01:00
this.reset()
}
},
2021-09-14 03:18:58 +02:00
reset() {
2022-02-26 23:19:22 +01:00
this.error = ''
this.items = []
2022-02-26 23:19:22 +01:00
this.ignoredFiles = []
this.uploadFinished = false
if (this.$refs.fileInput) this.$refs.fileInput.value = ''
if (this.$refs.fileFolderInput) this.$refs.fileFolderInput.value = ''
2021-09-14 03:18:58 +02:00
},
2022-02-26 23:19:22 +01:00
openFilePicker() {
if (this.$refs.fileInput) this.$refs.fileInput.click()
2021-09-14 03:18:58 +02:00
},
2022-02-26 23:19:22 +01:00
openFolderPicker() {
if (this.$refs.fileFolderInput) this.$refs.fileFolderInput.click()
2021-09-14 03:18:58 +02:00
},
2022-02-26 23:19:22 +01:00
isDraggingFile(e) {
// Checks dragging file or folder and not an element on the page
var dt = e.dataTransfer || {}
return dt.types && dt.types.indexOf('Files') >= 0
2021-09-14 03:18:58 +02:00
},
2022-02-26 23:19:22 +01:00
dragenter(e) {
e.preventDefault()
if (this.uploadReady && this.isDraggingFile(e) && !this.isDragging) {
this.isDragging = true
}
2021-09-14 03:18:58 +02:00
},
2022-02-26 23:19:22 +01:00
dragleave(e) {
e.preventDefault()
if (!e.fromElement && this.isDragging) {
this.isDragging = false
}
2021-09-14 03:18:58 +02:00
},
2022-02-26 23:19:22 +01:00
dragover(e) {
// This is required to catch the drop event
e.preventDefault()
2021-09-14 03:18:58 +02:00
},
2022-02-26 23:19:22 +01:00
async drop(e) {
2021-09-14 03:18:58 +02:00
e.preventDefault()
2022-02-26 23:19:22 +01:00
this.isDragging = false
var items = e.dataTransfer.items || []
2022-04-28 01:03:00 +02:00
var itemResults = await this.uploadHelpers.getItemsFromDrop(items, this.selectedLibraryMediaType)
this.setResults(itemResults)
2021-09-14 03:18:58 +02:00
},
2022-02-26 23:19:22 +01:00
inputChanged(e) {
if (!e.target || !e.target.files) return
var _files = Array.from(e.target.files)
if (_files && _files.length) {
var itemResults = this.uploadHelpers.getItemsFromPicker(_files, this.selectedLibraryMediaType)
this.setResults(itemResults)
2021-09-14 03:18:58 +02:00
}
},
setResults(itemResults) {
if (itemResults.error) {
this.error = itemResults.error
this.items = []
2022-02-26 23:19:22 +01:00
this.ignoredFiles = []
} else {
this.error = ''
this.items = itemResults.items
this.ignoredFiles = itemResults.ignoredFiles
2021-09-14 03:18:58 +02:00
}
console.log('Upload results', itemResults)
2021-09-14 03:18:58 +02:00
},
updateItemCardStatus(index, status) {
var ref = this.$refs[`itemCard-${index}`]
2022-02-26 23:19:22 +01:00
if (ref && ref.length) ref = ref[0]
if (!ref) {
console.error('Book card ref not found', index, this.$refs)
} else {
ref.setUploadStatus(status)
2021-10-06 04:10:49 +02:00
}
2022-02-26 23:19:22 +01:00
},
uploadItem(item) {
2021-09-14 03:18:58 +02:00
var form = new FormData()
form.set('title', item.title)
if (!this.selectedLibraryIsPodcast) {
form.set('author', item.author)
form.set('series', item.series)
}
2021-10-06 04:10:49 +02:00
form.set('library', this.selectedLibraryId)
form.set('folder', this.selectedFolderId)
2021-09-14 03:18:58 +02:00
var index = 0
item.files.forEach((file) => {
2021-09-14 03:18:58 +02:00
form.set(`${index++}`, file)
})
2022-02-26 23:19:22 +01:00
return this.$axios
.$post('/api/upload', form)
2022-02-26 23:19:22 +01:00
.then(() => true)
2021-09-14 03:18:58 +02:00
.catch((error) => {
console.error('Failed', error)
2021-10-06 04:10:49 +02:00
var errorMessage = error.response && error.response.data ? error.response.data : 'Oops, something went wrong...'
this.$toast.error(errorMessage)
2022-02-26 23:19:22 +01:00
return false
2021-09-14 03:18:58 +02:00
})
2022-02-26 23:19:22 +01:00
},
validateItems() {
var itemData = []
for (var item of this.items) {
var itemref = this.$refs[`itemCard-${item.index}`]
if (itemref && itemref.length) itemref = itemref[0]
2022-02-26 23:19:22 +01:00
if (!itemref) {
console.error('Invalid item index no ref', item.index, this.$refs.itemCard)
2022-02-26 23:19:22 +01:00
return false
} else {
var data = itemref.getData()
2022-02-26 23:19:22 +01:00
if (!data) {
return false
}
itemData.push(data)
2022-02-26 23:19:22 +01:00
}
}
return itemData
2022-02-26 23:19:22 +01:00
},
async submit() {
if (!this.selectedFolderId || !this.selectedLibraryId) {
this.$toast.error('Must select library and folder')
document.getElementById('page-wrapper').scroll({ top: 0, left: 0, behavior: 'smooth' })
return
}
var items = this.validateItems()
if (!items) {
this.$toast.error('Some invalid items')
2022-02-26 23:19:22 +01:00
return
}
this.processing = true
var itemsUploaded = 0
var itemsFailed = 0
for (let i = 0; i < items.length; i++) {
var item = items[i]
this.updateItemCardStatus(item.index, 'uploading')
var result = await this.uploadItem(item)
if (result) itemsUploaded++
else itemsFailed++
this.updateItemCardStatus(item.index, result ? 'success' : 'failed')
2022-02-26 23:19:22 +01:00
}
if (itemsUploaded) {
this.$toast.success(`Successfully uploaded ${itemsUploaded} item${itemsUploaded > 1 ? 's' : ''}`)
2022-02-26 23:19:22 +01:00
}
if (itemsFailed) {
this.$toast.success(`Failed to upload ${itemsFailed} item${itemsFailed > 1 ? 's' : ''}`)
2022-02-26 23:19:22 +01:00
}
this.processing = false
this.uploadFinished = true
2021-09-14 03:18:58 +02:00
}
},
2021-10-06 04:10:49 +02:00
mounted() {
this.selectedLibraryId = this.$store.state.libraries.currentLibraryId
this.setDefaultFolder()
2022-02-26 23:19:22 +01:00
window.addEventListener('dragenter', this.dragenter)
window.addEventListener('dragleave', this.dragleave)
window.addEventListener('dragover', this.dragover)
window.addEventListener('drop', this.drop)
},
beforeDestroy() {
window.removeEventListener('dragenter', this.dragenter)
window.removeEventListener('dragleave', this.dragleave)
window.removeEventListener('dragover', this.dragover)
window.removeEventListener('drop', this.drop)
2021-10-06 04:10:49 +02:00
}
2021-09-14 03:18:58 +02:00
}
</script>