diff --git a/client/components/cards/BookUploadCard.vue b/client/components/cards/ItemUploadCard.vue similarity index 61% rename from client/components/cards/BookUploadCard.vue rename to client/components/cards/ItemUploadCard.vue index 08d66e2f..16f789b3 100644 --- a/client/components/cards/BookUploadCard.vue +++ b/client/components/cards/ItemUploadCard.vue @@ -1,7 +1,7 @@

Successfully Uploaded!

@@ -55,15 +59,16 @@ import Path from 'path' export default { props: { - book: { + item: { type: Object, default: () => {} }, + mediaType: String, processing: Boolean }, data() { return { - bookData: { + itemData: { title: '', author: '', series: '' @@ -75,14 +80,19 @@ export default { } }, computed: { + isPodcast() { + return this.mediaType === 'podcast' + }, directory() { - if (!this.bookData.title) return '' - if (this.bookData.series && this.bookData.author) { - return Path.join(this.bookData.author, this.bookData.series, this.bookData.title) - } else if (this.bookData.author) { - return Path.join(this.bookData.author, this.bookData.title) + if (!this.itemData.title) return '' + if (this.isPodcast) return this.itemData.title + + if (this.itemData.series && this.itemData.author) { + return Path.join(this.itemData.author, this.itemData.series, this.itemData.title) + } else if (this.itemData.author) { + return Path.join(this.itemData.author, this.itemData.title) } else { - return this.bookData.title + return this.itemData.title } } }, @@ -96,24 +106,24 @@ export default { this.error = '' }, getData() { - if (!this.bookData.title) { + if (!this.itemData.title) { this.error = 'Must have a title' return null } this.error = '' - var files = this.book.bookFiles.concat(this.book.otherFiles) + var files = this.item.itemFiles.concat(this.item.otherFiles) return { - index: this.book.index, - ...this.bookData, + index: this.item.index, + ...this.itemData, files } } }, mounted() { - if (this.book) { - this.bookData.title = this.book.title - this.bookData.author = this.book.author - this.bookData.series = this.book.series + if (this.item) { + this.itemData.title = this.item.title + this.itemData.author = this.item.author + this.itemData.series = this.item.series } } } diff --git a/client/components/modals/item/EditModal.vue b/client/components/modals/item/EditModal.vue index 2840898e..094ef9bd 100644 --- a/client/components/modals/item/EditModal.vue +++ b/client/components/modals/item/EditModal.vue @@ -5,7 +5,7 @@

{{ title }}

-
+
@@ -252,7 +252,7 @@ export default { } - \ No newline at end of file diff --git a/client/components/modals/libraries/FolderChooser.vue b/client/components/modals/libraries/FolderChooser.vue index d41d16e9..a54cdfe6 100644 --- a/client/components/modals/libraries/FolderChooser.vue +++ b/client/components/modals/libraries/FolderChooser.vue @@ -1,10 +1,14 @@ @@ -161,4 +161,9 @@ export default { .dir-item.dir-used { background-color: rgba(255, 25, 0, 0.1); } +.folder-container { + max-height: calc(100% - 130px); + height: calc(100% - 130px); + min-height: calc(100% - 130px); +} \ No newline at end of file diff --git a/client/components/modals/libraries/LibrarySettings.vue b/client/components/modals/libraries/LibrarySettings.vue new file mode 100644 index 00000000..c27f4838 --- /dev/null +++ b/client/components/modals/libraries/LibrarySettings.vue @@ -0,0 +1,63 @@ + + + \ No newline at end of file diff --git a/client/components/ui/MediaIconPicker.vue b/client/components/ui/MediaIconPicker.vue index 50280904..efb0b9a0 100644 --- a/client/components/ui/MediaIconPicker.vue +++ b/client/components/ui/MediaIconPicker.vue @@ -40,8 +40,8 @@ export default { showMenu: false, types: [ { - id: 'default', - name: 'Default' + id: 'database', + name: 'Database' }, { id: 'audiobook', @@ -65,7 +65,7 @@ export default { computed: { selected: { get() { - return this.value || 'default' + return this.value || 'database' }, set(val) { this.$emit('input', val) @@ -75,7 +75,7 @@ export default { return this.types.find((t) => t.id === this.selected) }, selectedName() { - return this.selectedItem ? this.selectedItem.name : 'Default' + return this.selectedItem ? this.selectedItem.name : 'Database' } }, methods: { diff --git a/client/components/ui/TextInputWithLabel.vue b/client/components/ui/TextInputWithLabel.vue index f5cbd1e4..0dab6109 100644 --- a/client/components/ui/TextInputWithLabel.vue +++ b/client/components/ui/TextInputWithLabel.vue @@ -3,7 +3,7 @@

{{ label }}{{ note }}

- +
@@ -38,6 +38,9 @@ export default { if (this.$refs.input && this.$refs.input.blur) { this.$refs.input.blur() } + }, + inputBlurred() { + this.$emit('blur') } }, mounted() {} diff --git a/client/mixins/uploadHelpers.js b/client/mixins/uploadHelpers.js index caa06edc..a54453e5 100644 --- a/client/mixins/uploadHelpers.js +++ b/client/mixins/uploadHelpers.js @@ -4,8 +4,8 @@ export default { data() { return { uploadHelpers: { - getBooksFromDrop: this.getBooksFromDataTransferItems, - getBooksFromPicker: this.getBooksFromFileList + getItemsFromDrop: this.getItemsFromDataTransferItems, + getItemsFromPicker: this.getItemsFromFilelist } } }, @@ -23,8 +23,8 @@ export default { } return false }, - filterAudiobookFiles(files) { - var validBookFiles = [] + filterItemFiles(files, mediaType) { + var validItemFiles = [] var validOtherFiles = [] var ignoredFiles = [] files.forEach((file) => { @@ -32,60 +32,60 @@ export default { if (!filetype) ignoredFiles.push(file) else { file.filetype = filetype - if (filetype === 'audio' || filetype === 'ebook') validBookFiles.push(file) + if (filetype === 'audio' || (filetype === 'ebook' && mediaType === 'book')) validItemFiles.push(file) else validOtherFiles.push(file) } }) return { - bookFiles: validBookFiles, + itemFiles: validItemFiles, otherFiles: validOtherFiles, ignoredFiles } }, - audiobookFromItems(items) { - var { bookFiles, otherFiles, ignoredFiles } = this.filterAudiobookFiles(items) - if (!bookFiles.length) { + itemFromTreeItems(items, mediaType) { + var { itemFiles, otherFiles, ignoredFiles } = this.filterItemFiles(items, mediaType) + if (!itemFiles.length) { ignoredFiles = ignoredFiles.concat(otherFiles) otherFiles = [] } return [ { - bookFiles, + itemFiles, otherFiles, ignoredFiles } ] }, - traverseForAudiobook(folder, depth = 1) { + traverseForItem(folder, mediaType, depth = 1) { if (folder.items.some((f) => f.isDirectory)) { - var audiobooks = [] + var items = [] folder.items.forEach((file) => { if (file.isDirectory) { - var audiobookResults = this.traverseForAudiobook(file, ++depth) - audiobooks = audiobooks.concat(audiobookResults) + var itemResults = this.traverseForItem(file, mediaType, ++depth) + items = items.concat(itemResults) } }) - return audiobooks + return items } else { - return this.audiobookFromItems(folder.items) + return this.itemFromTreeItems(folder.items, mediaType) } }, - fileTreeToAudiobooks(filetree) { + fileTreeToItems(filetree, mediaType) { // Has directores - Is Multi Book Drop if (filetree.some((f) => f.isDirectory)) { var ignoredFilesInRoot = filetree.filter((f) => !f.isDirectory) if (ignoredFilesInRoot.length) filetree = filetree.filter((f) => f.isDirectory) - var audiobookResults = this.traverseForAudiobook({ items: filetree }) + var itemResults = this.traverseForItem({ items: filetree }, mediaType) return { - audiobooks: audiobookResults, + items: itemResults, ignoredFiles: ignoredFilesInRoot } } else { // Single Book drop return { - audiobooks: this.audiobookFromItems(filetree), + items: this.itemFromTreeItems(filetree, mediaType), ignoredFiles: [] } } @@ -140,7 +140,7 @@ export default { series: '', ...book } - var firstBookFile = book.bookFiles[0] + var firstBookFile = book.itemFiles[0] if (!firstBookFile.filepath) return audiobook // No path var firstBookPath = Path.dirname(firstBookFile.filepath) @@ -157,32 +157,49 @@ export default { } return audiobook }, - async getBooksFromDataTransferItems(items) { + cleanPodcast(item, index) { + var podcast = { + index, + title: '', + ...item + } + var firstAudioFile = podcast.itemFiles[0] + if (!firstAudioFile.filepath) return podcast // No path + var firstPath = Path.dirname(firstAudioFile.filepath) + var dirs = firstPath.split('/').filter(d => !!d && d !== '.') + podcast.title = dirs.length > 1 ? dirs[1] : dirs[0] + return podcast + }, + cleanItem(item, mediaType, index) { + if (mediaType === 'podcast') return this.cleanPodcast(item, index) + return this.cleanBook(item, index) + }, + async getItemsFromDataTransferItems(items, mediaType) { var files = await this.getFilesDropped(items) if (!files || !files.length) return { error: 'No files found ' } - var audiobooksData = this.fileTreeToAudiobooks(files) - if (!audiobooksData.audiobooks.length && !audiobooksData.ignoredFiles.length) { + var itemData = this.fileTreeToItems(files, mediaType) + if (!itemData.items.length && !itemData.ignoredFiles.length) { return { error: 'Invalid file drop' } } - var ignoredFiles = audiobooksData.ignoredFiles + var ignoredFiles = itemData.ignoredFiles var index = 1 - var books = audiobooksData.audiobooks.filter((ab) => { - if (!ab.bookFiles.length) { + var items = itemData.items.filter((ab) => { + if (!ab.itemFiles.length) { if (ab.otherFiles.length) ignoredFiles = ignoredFiles.concat(ab.otherFiles) if (ab.ignoredFiles.length) ignoredFiles = ignoredFiles.concat(ab.ignoredFiles) } - return ab.bookFiles.length - }).map(ab => this.cleanBook(ab, index++)) + return ab.itemFiles.length + }).map(ab => this.cleanItem(ab, index++)) return { - books, + items, ignoredFiles } }, - getBooksFromFileList(filelist) { + getItemsFromFilelist(filelist, mediaType) { var ignoredFiles = [] var otherFiles = [] - var bookMap = {} + var itemMap = {} filelist.forEach((file) => { var filetype = this.checkFileType(file.name) @@ -191,17 +208,17 @@ export default { file.filetype = filetype if (file.webkitRelativePath) file.filepath = file.webkitRelativePath - if (filetype === 'audio' || filetype === 'ebook') { + if (filetype === 'audio' || (filetype === 'ebook' && mediaType === 'book')) { var dir = file.filepath ? Path.dirname(file.filepath) : '' - if (!bookMap[dir]) { - bookMap[dir] = { + if (!itemMap[dir]) { + itemMap[dir] = { path: dir, ignoredFiles: [], - bookFiles: [], + itemFiles: [], otherFiles: [] } } - bookMap[dir].bookFiles.push(file) + itemMap[dir].itemFiles.push(file) } else { otherFiles.push(file) } @@ -210,18 +227,18 @@ export default { otherFiles.forEach((file) => { var dir = Path.dirname(file.filepath) - var findBook = Object.values(bookMap).find(b => dir.startsWith(b.path)) - if (findBook) { - bookMap[dir].otherFiles.push(file) + var findItem = Object.values(itemMap).find(b => dir.startsWith(b.path)) + if (findItem) { + findItem.otherFiles.push(file) } else { ignoredFiles.push(file) } }) var index = 1 - var books = Object.values(bookMap).map(ab => this.cleanBook(ab, index++)) + var items = Object.values(itemMap).map(i => this.cleanItem(i, mediaType, index++)) return { - books, + items, ignoredFiles: ignoredFiles } }, diff --git a/client/pages/collection/_id.vue b/client/pages/collection/_id.vue index 37c9f609..02be207c 100644 --- a/client/pages/collection/_id.vue +++ b/client/pages/collection/_id.vue @@ -51,6 +51,7 @@ export default { if (!collection) { return redirect('/') } + store.commit('user/addUpdateCollection', collection) return { collectionId: collection.id diff --git a/client/pages/library/_library/authors/index.vue b/client/pages/library/_library/authors/index.vue index 686702e1..34e819c1 100644 --- a/client/pages/library/_library/authors/index.vue +++ b/client/pages/library/_library/authors/index.vue @@ -23,11 +23,16 @@ export default { async asyncData({ store, params, redirect, query, app }) { var libraryId = params.library - var library = await store.dispatch('libraries/fetch', libraryId) - if (!library) { + var libraryData = await store.dispatch('libraries/fetch', libraryId) + if (!libraryData) { return redirect('/oops?message=Library not found') } + const library = libraryData.library + if (library.mediaType === 'podcast') { + return redirect(`/library/${libraryId}`) + } + return { libraryId } diff --git a/client/pages/library/_library/bookshelf/_id.vue b/client/pages/library/_library/bookshelf/_id.vue index 30f8b551..8b94747a 100644 --- a/client/pages/library/_library/bookshelf/_id.vue +++ b/client/pages/library/_library/bookshelf/_id.vue @@ -14,8 +14,8 @@ export default { async asyncData({ params, query, store, app, redirect }) { var libraryId = params.library - var library = await store.dispatch('libraries/fetch', libraryId) - if (!library) { + var libraryData = await store.dispatch('libraries/fetch', libraryId) + if (!libraryData) { return redirect('/oops?message=Library not found') } @@ -23,6 +23,13 @@ export default { if (query.filter) { store.dispatch('user/updateUserSettings', { filterBy: query.filter }) } + + // Redirect podcast libraries + const library = libraryData.library + if (library.mediaType === 'podcast' && (params.id === 'collections' || params.id === 'series')) { + return redirect(`/library/${libraryId}`) + } + return { id: params.id || '', libraryId diff --git a/client/pages/library/_library/podcast/search.vue b/client/pages/library/_library/podcast/search.vue index 03b25497..5433b81f 100644 --- a/client/pages/library/_library/podcast/search.vue +++ b/client/pages/library/_library/podcast/search.vue @@ -44,10 +44,17 @@ export default { async asyncData({ params, query, store, app, redirect }) { var libraryId = params.library - var library = await store.dispatch('libraries/fetch', libraryId) - if (!library) { + var libraryData = await store.dispatch('libraries/fetch', libraryId) + if (!libraryData) { return redirect('/oops?message=Library not found') } + + // Redirect book libraries + const library = libraryData.library + if (library.mediaType === 'book') { + return redirect(`/library/${libraryId}`) + } + return { libraryId } diff --git a/client/pages/library/_library/series/_id.vue b/client/pages/library/_library/series/_id.vue index 054f19cf..754b332e 100644 --- a/client/pages/library/_library/series/_id.vue +++ b/client/pages/library/_library/series/_id.vue @@ -14,10 +14,16 @@ export default { async asyncData({ store, params, redirect, query, app }) { var libraryId = params.library - var library = await store.dispatch('libraries/fetch', libraryId) - if (!library) { + var libraryData = await store.dispatch('libraries/fetch', libraryId) + if (!libraryData) { return redirect('/oops?message=Library not found') } + + const library = libraryData.library + if (library.mediaType === 'podcast') { + return redirect(`/library/${libraryId}`) + } + var series = await app.$axios.$get(`/api/series/${params.id}`).catch((error) => { console.error('Failed', error) return false diff --git a/client/pages/upload/index.vue b/client/pages/upload/index.vue index cc9a0b09..b225ad0d 100644 --- a/client/pages/upload/index.vue +++ b/client/pages/upload/index.vue @@ -3,11 +3,14 @@
-
- +
+
-
- +
+ +
+
+
@@ -16,7 +19,7 @@ -
+

{{ isDragging ? 'Drop files' : "Drag n' drop files or folders" }}

or

@@ -29,33 +32,33 @@

Supported File Types: {{ inputAccept.join(', ') }}

- +
-

{{ books.length }} book{{ books.length === 1 ? '' : 's' }}

+

{{ items.length }} item{{ items.length === 1 ? '' : 's' }}

 | {{ ignoredFiles.length }} file{{ ignoredFiles.length === 1 ? '' : 's' }} ignored

Reset
- -

No books found

+ +

No items found

-

Unsupported files are ignored. When choosing or dropping a folder, other files that are not in a book folder are ignored.

+

Unsupported files are ignored. When choosing or dropping a folder, other files that are not in an item folder are ignored.

Supported File Types: {{ inputAccept.join(', ') }}

- -