mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2026-01-01 16:49:38 +00:00
Support for libraries and folder mapping, updating static cover path, detect reader.txt
This commit is contained in:
parent
a590e795e3
commit
577f3bead9
43 changed files with 2548 additions and 768 deletions
|
|
@ -1,43 +1,39 @@
|
|||
<template>
|
||||
<div id="page-wrapper" class="page p-6 overflow-y-auto" :class="streamAudiobook ? 'streaming' : ''">
|
||||
<div class="w-full max-w-4xl mx-auto">
|
||||
<div class="flex items-center mb-2">
|
||||
<h1 class="text-2xl">Users</h1>
|
||||
<div class="mx-2 w-7 h-7 flex items-center justify-center rounded-full cursor-pointer hover:bg-white hover:bg-opacity-10 text-center" @click="clickAddUser">
|
||||
<span class="material-icons" style="font-size: 1.4rem">add</span>
|
||||
<tables-users-table />
|
||||
<!-- <div class="h-0.5 bg-primary bg-opacity-50 w-full" /> -->
|
||||
|
||||
<tables-libraries-table />
|
||||
<!-- <div class="h-0.5 bg-primary bg-opacity-50 w-full" /> -->
|
||||
<div class="bg-bg rounded-md shadow-lg border border-white border-opacity-5 p-4 mb-8">
|
||||
<div class="flex items-center mb-2">
|
||||
<h1 class="text-xl">Settings</h1>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center py-2">
|
||||
<ui-toggle-switch v-model="newServerSettings.scannerParseSubtitle" small :disabled="updatingServerSettings" @input="updateScannerParseSubtitle" />
|
||||
<ui-tooltip :text="parseSubtitleTooltip">
|
||||
<p class="pl-4 text-lg">Scanner parse subtitles <span class="material-icons icon-text">info_outlined</span></p>
|
||||
</ui-tooltip>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center py-2">
|
||||
<ui-toggle-switch v-model="newServerSettings.scannerFindCovers" :disabled="updatingServerSettings" @input="updatingServerSettings" />
|
||||
<ui-tooltip :text="scannerFindCoversTooltip">
|
||||
<p class="pl-4 text-lg">Scanner find covers <span class="material-icons icon-text">info_outlined</span></p>
|
||||
</ui-tooltip>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center py-2">
|
||||
<ui-toggle-switch v-model="storeCoversInAudiobookDir" :disabled="updatingServerSettings" @input="updateCoverStorageDestination" />
|
||||
<ui-tooltip :text="coverDestinationTooltip">
|
||||
<p class="pl-4 text-lg">Store covers with audiobook <span class="material-icons icon-text">info_outlined</span></p>
|
||||
</ui-tooltip>
|
||||
</div>
|
||||
<!-- <ui-btn small :padding-x="4" class="h-8">Create User</ui-btn> -->
|
||||
</div>
|
||||
<div class="h-0.5 bg-primary bg-opacity-50 w-full" />
|
||||
<div class="p-4 text-center">
|
||||
<table id="accounts" class="mb-8">
|
||||
<tr>
|
||||
<th>Username</th>
|
||||
<th>Account Type</th>
|
||||
<th style="width: 200px">Created At</th>
|
||||
<th style="width: 100px"></th>
|
||||
</tr>
|
||||
<tr v-for="user in users" :key="user.id" :class="user.isActive ? '' : 'bg-error bg-opacity-20'">
|
||||
<td>
|
||||
{{ user.username }} <span class="text-xs text-gray-400 italic pl-4">({{ user.id }})</span>
|
||||
</td>
|
||||
<td>{{ user.type }}</td>
|
||||
<td class="text-sm font-mono">
|
||||
{{ new Date(user.createdAt).toISOString() }}
|
||||
</td>
|
||||
<td>
|
||||
<div class="w-full flex justify-center">
|
||||
<span class="material-icons hover:text-gray-400 cursor-pointer text-base pr-2" @click="editUser(user)">edit</span>
|
||||
<span v-show="user.type !== 'root'" class="material-icons text-base hover:text-error cursor-pointer" @click="deleteUserClick(user)">delete</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="h-0.5 bg-primary bg-opacity-50 w-full" />
|
||||
|
||||
<div class="py-4">
|
||||
<!-- <div class="py-4">
|
||||
<p class="text-2xl">Scanner</p>
|
||||
<div class="flex items-start py-2">
|
||||
<div class="py-2">
|
||||
|
|
@ -81,7 +77,7 @@
|
|||
</ui-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div> -->
|
||||
|
||||
<div class="h-0.5 bg-primary bg-opacity-50 w-full" />
|
||||
|
||||
|
|
@ -128,7 +124,7 @@
|
|||
|
||||
<div class="fixed bottom-0 left-0 w-10 h-10" @dblclick="setDeveloperMode"></div>
|
||||
|
||||
<modals-account-modal v-model="showAccountModal" :account="selectedAccount" />
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
@ -143,10 +139,6 @@ export default {
|
|||
return {
|
||||
storeCoversInAudiobookDir: false,
|
||||
isResettingAudiobooks: false,
|
||||
users: [],
|
||||
selectedAccount: null,
|
||||
showAccountModal: false,
|
||||
isDeletingUser: false,
|
||||
newServerSettings: {},
|
||||
updatingServerSettings: false
|
||||
}
|
||||
|
|
@ -166,6 +158,9 @@ export default {
|
|||
coverDestinationTooltip() {
|
||||
return 'By default covers are stored in /metadata/books, enabling this setting will store covers inside your audiobooks directory. Only one file named "cover" will be kept.'
|
||||
},
|
||||
scannerFindCoversTooltip() {
|
||||
return 'If your audiobook does not have an embedded cover or a cover image inside the folder, the scanner will attempt to find a cover.<br>Note: This will extend scan time'
|
||||
},
|
||||
saveMetadataTooltip() {
|
||||
return 'This will write a "metadata.nfo" file in all of your audiobook directories.'
|
||||
},
|
||||
|
|
@ -232,7 +227,7 @@ export default {
|
|||
})
|
||||
},
|
||||
scan() {
|
||||
this.$root.socket.emit('scan')
|
||||
this.$root.socket.emit('scan', this.$store.state.libraries.currentLibraryId)
|
||||
},
|
||||
scanCovers() {
|
||||
this.$root.socket.emit('scan_covers')
|
||||
|
|
@ -247,16 +242,6 @@ export default {
|
|||
this.$root.socket.once('save_metadata_complete', this.saveMetadataComplete)
|
||||
this.$root.socket.emit('save_metadata')
|
||||
},
|
||||
loadUsers() {
|
||||
this.$axios
|
||||
.$get('/api/users')
|
||||
.then((users) => {
|
||||
this.users = users
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Failed', error)
|
||||
})
|
||||
},
|
||||
resetAudiobooks() {
|
||||
if (confirm('WARNING! This action will remove all audiobooks from the database including any updates or matches you have made. This does not do anything to your actual files. Shall we continue?')) {
|
||||
this.isResettingAudiobooks = true
|
||||
|
|
@ -274,74 +259,13 @@ export default {
|
|||
})
|
||||
}
|
||||
},
|
||||
clickAddUser() {
|
||||
this.selectedAccount = null
|
||||
this.showAccountModal = true
|
||||
},
|
||||
editUser(user) {
|
||||
this.selectedAccount = user
|
||||
this.showAccountModal = true
|
||||
},
|
||||
deleteUserClick(user) {
|
||||
if (this.isDeletingUser) return
|
||||
if (confirm(`Are you sure you want to permanently delete user "${user.username}"?`)) {
|
||||
this.isDeletingUser = true
|
||||
this.$axios
|
||||
.$delete(`/api/user/${user.id}`)
|
||||
.then((data) => {
|
||||
this.isDeletingUser = false
|
||||
if (data.error) {
|
||||
this.$toast.error(data.error)
|
||||
} else {
|
||||
this.$toast.success('User deleted')
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Failed to delete user', error)
|
||||
this.$toast.error('Failed to delete user')
|
||||
this.isDeletingUser = false
|
||||
})
|
||||
}
|
||||
},
|
||||
addUpdateUser(user) {
|
||||
if (!this.users) return
|
||||
var index = this.users.findIndex((u) => u.id === user.id)
|
||||
if (index >= 0) {
|
||||
this.users.splice(index, 1, user)
|
||||
} else {
|
||||
this.users.push(user)
|
||||
}
|
||||
},
|
||||
userRemoved(user) {
|
||||
this.users = this.users.filter((u) => u.id !== user.id)
|
||||
},
|
||||
init(attempts = 0) {
|
||||
if (!this.$root.socket) {
|
||||
if (attempts > 10) {
|
||||
return console.error('Failed to setup socket listeners')
|
||||
}
|
||||
setTimeout(() => {
|
||||
this.init(++attempts)
|
||||
}, 250)
|
||||
return
|
||||
}
|
||||
this.$root.socket.on('user_added', this.addUpdateUser)
|
||||
this.$root.socket.on('user_updated', this.addUpdateUser)
|
||||
this.$root.socket.on('user_removed', this.userRemoved)
|
||||
|
||||
init() {
|
||||
this.newServerSettings = this.serverSettings ? { ...this.serverSettings } : {}
|
||||
this.storeCoversInAudiobookDir = this.newServerSettings.coverDestination === this.$constants.CoverDestination.AUDIOBOOK
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.loadUsers()
|
||||
this.init()
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (this.$root.socket) {
|
||||
this.$root.socket.off('user_added', this.newUserAdded)
|
||||
this.$root.socket.off('user_updated', this.userUpdated)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -20,9 +20,11 @@
|
|||
|
||||
<script>
|
||||
export default {
|
||||
// asyncData({ redirect }) {
|
||||
// redirect('/library')
|
||||
// },
|
||||
asyncData({ redirect, store }) {
|
||||
var currentLibraryId = store.state.libraries.currentLibraryId
|
||||
console.log('Redir', currentLibraryId)
|
||||
redirect(`/library/${currentLibraryId}`)
|
||||
},
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -12,7 +12,13 @@
|
|||
|
||||
<script>
|
||||
export default {
|
||||
async asyncData({ params, query, store, app }) {
|
||||
async asyncData({ params, query, store, app, redirect }) {
|
||||
var libraryId = params.library
|
||||
var library = await store.dispatch('libraries/fetch', libraryId)
|
||||
if (!library) {
|
||||
return redirect('/oops?message=Library not found')
|
||||
}
|
||||
|
||||
if (query.filter) {
|
||||
store.dispatch('user/updateUserSettings', { filterBy: query.filter })
|
||||
}
|
||||
|
|
@ -20,7 +26,7 @@ export default {
|
|||
var searchQuery = null
|
||||
if (params.id === 'search' && query.query) {
|
||||
searchQuery = query.query
|
||||
searchResults = await app.$axios.$get(`/api/audiobooks?q=${query.query}`).catch((error) => {
|
||||
searchResults = await app.$axios.$get(`/api/library/${libraryId}/audiobooks?q=${query.query}`).catch((error) => {
|
||||
console.error('Search error', error)
|
||||
return []
|
||||
})
|
||||
37
client/pages/library/_library/index.vue
Normal file
37
client/pages/library/_library/index.vue
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
<template>
|
||||
<div class="page" :class="streamAudiobook ? 'streaming' : ''">
|
||||
<div class="flex h-full">
|
||||
<app-side-rail />
|
||||
<div class="flex-grow">
|
||||
<app-book-shelf-toolbar is-home />
|
||||
<app-book-shelf-categorized />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
async asyncData({ store, params, redirect }) {
|
||||
var libraryId = params.library
|
||||
var library = await store.dispatch('libraries/fetch', libraryId)
|
||||
if (!library) {
|
||||
return redirect(`/oops?message=Library "${libraryId}" not found`)
|
||||
}
|
||||
return {
|
||||
library
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
computed: {
|
||||
streamAudiobook() {
|
||||
return this.$store.state.streamAudiobook
|
||||
}
|
||||
},
|
||||
methods: {},
|
||||
mounted() {},
|
||||
beforeDestroy() {}
|
||||
}
|
||||
</script>
|
||||
23
client/pages/oops.vue
Normal file
23
client/pages/oops.vue
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
<template>
|
||||
<div class="w-screen h-screen overflow-hidden page">
|
||||
<div class="flex h-1/3 items-center justify-center">
|
||||
<h1 class="text-2xl">Oops... {{ message }}</h1>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
asyncData({ query }) {
|
||||
return {
|
||||
message: query.message || ''
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
computed: {},
|
||||
methods: {},
|
||||
mounted() {}
|
||||
}
|
||||
</script>
|
||||
Loading…
Add table
Add a link
Reference in a new issue