mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2025-08-04 02:05:06 +02:00
Adding permissions per user, add volume number sort
This commit is contained in:
parent
c8d857edb9
commit
2e82370408
18 changed files with 241 additions and 43 deletions
|
@ -30,8 +30,10 @@
|
|||
<ui-btn small class="text-sm mx-2" @click="toggleSelectAll">{{ isAllSelected ? 'Select None' : 'Select All' }}</ui-btn>
|
||||
|
||||
<div class="flex-grow" />
|
||||
<ui-btn v-show="!processingBatchDelete" color="warning" small class="mx-2" @click="batchEditClick"><span class="material-icons text-gray-200 pt-1">edit</span></ui-btn>
|
||||
<ui-btn color="error" small class="mx-2" :loading="processingBatchDelete" @click="batchDeleteClick"><span class="material-icons text-gray-200 pt-1">delete</span></ui-btn>
|
||||
<template v-if="userCanUpdate">
|
||||
<ui-btn v-show="!processingBatchDelete" color="warning" small class="mx-2" @click="batchEditClick"><span class="material-icons text-gray-200 pt-1">edit</span></ui-btn>
|
||||
</template>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -69,6 +71,12 @@ export default {
|
|||
},
|
||||
audiobooksShowing() {
|
||||
return this.$store.getters['audiobooks/getFiltered']()
|
||||
},
|
||||
userCanUpdate() {
|
||||
return this.$store.getters['user/getUserCanUpdate']
|
||||
},
|
||||
userCanDelete() {
|
||||
return this.$store.getters['user/getUserCanDelete']
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
|
|
@ -19,13 +19,16 @@
|
|||
<span class="material-icons" :style="{ fontSize: playIconFontSize + 'rem' }">play_circle_filled</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-show="!isSelectionMode" class="absolute cursor-pointer hover:text-yellow-300 hover:scale-125 transform duration-50" :style="{ top: 0.375 * sizeMultiplier + 'rem', right: 0.375 * sizeMultiplier + 'rem' }" @click.stop.prevent="editClick">
|
||||
|
||||
<div v-if="userCanUpdate" v-show="!isSelectionMode" class="absolute cursor-pointer hover:text-yellow-300 hover:scale-125 transform duration-50" :style="{ top: 0.375 * sizeMultiplier + 'rem', right: 0.375 * sizeMultiplier + 'rem' }" @click.stop.prevent="editClick">
|
||||
<span class="material-icons" :style="{ fontSize: sizeMultiplier + 'rem' }">edit</span>
|
||||
</div>
|
||||
<div class="absolute cursor-pointer hover:text-yellow-300 hover:scale-125 transform duration-100" :style="{ top: 0.375 * sizeMultiplier + 'rem', left: 0.375 * sizeMultiplier + 'rem' }" @click.stop.prevent="selectBtnClick">
|
||||
|
||||
<div v-if="userCanUpdate || userCanDelete" class="absolute cursor-pointer hover:text-yellow-300 hover:scale-125 transform duration-100" :style="{ top: 0.375 * sizeMultiplier + 'rem', left: 0.375 * sizeMultiplier + 'rem' }" @click.stop.prevent="selectBtnClick">
|
||||
<span class="material-icons" :class="selected ? 'text-yellow-400' : ''" :style="{ fontSize: 1.25 * sizeMultiplier + 'rem' }">{{ selected ? 'radio_button_checked' : 'radio_button_unchecked' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-show="!isSelectionMode" class="absolute bottom-0 left-0 h-1 shadow-sm" :class="userIsRead ? 'bg-success' : 'bg-yellow-400'" :style="{ width: width * userProgressPercent + 'px' }"></div>
|
||||
|
||||
<ui-tooltip v-if="showError" :text="errorText" class="absolute bottom-4 left-0">
|
||||
|
@ -156,6 +159,12 @@ export default {
|
|||
classes.push('border-2 border-yellow-400')
|
||||
}
|
||||
return classes
|
||||
},
|
||||
userCanUpdate() {
|
||||
return this.$store.getters['user/getUserCanUpdate']
|
||||
},
|
||||
userCanDelete() {
|
||||
return this.$store.getters['user/getUserCanDelete']
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
|
|
@ -48,6 +48,10 @@ export default {
|
|||
text: 'Added At',
|
||||
value: 'addedAt'
|
||||
},
|
||||
{
|
||||
text: 'Volume #',
|
||||
value: 'book.volumeNumber'
|
||||
},
|
||||
{
|
||||
text: 'Duration',
|
||||
value: 'duration'
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
</div>
|
||||
<div class="flex py-2">
|
||||
<div class="px-2">
|
||||
<ui-input-dropdown v-model="newUser.type" label="Account Type" :disabled="isEditingRoot" :editable="false" :items="accountTypes" />
|
||||
<ui-input-dropdown v-model="newUser.type" label="Account Type" :disabled="isEditingRoot" :editable="false" :items="accountTypes" @input="userTypeUpdated" />
|
||||
</div>
|
||||
<div class="flex-grow" />
|
||||
<div v-show="!isEditingRoot" class="flex items-center pt-4 px-2">
|
||||
|
@ -26,6 +26,37 @@
|
|||
<ui-toggle-switch v-model="newUser.isActive" :disabled="isEditingRoot" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="!isEditingRoot && newUser.permissions" class="w-full border-t border-b border-black-200 py-2 mt-4">
|
||||
<p class="text-lg mb-2">Permissions</p>
|
||||
<div class="flex items-center my-2 max-w-lg">
|
||||
<div class="w-1/2">
|
||||
<p>Can Download</p>
|
||||
</div>
|
||||
<div class="w-1/2">
|
||||
<ui-toggle-switch v-model="newUser.permissions.download" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center my-2 max-w-lg">
|
||||
<div class="w-1/2">
|
||||
<p>Can Update</p>
|
||||
</div>
|
||||
<div class="w-1/2">
|
||||
<ui-toggle-switch v-model="newUser.permissions.update" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center my-2 max-w-lg">
|
||||
<div class="w-1/2">
|
||||
<p>Can Delete</p>
|
||||
</div>
|
||||
<div class="w-1/2">
|
||||
<ui-toggle-switch v-model="newUser.permissions.delete" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex pt-4">
|
||||
<div class="flex-grow" />
|
||||
<ui-btn color="success" type="submit">Submit</ui-btn>
|
||||
|
@ -144,6 +175,13 @@ export default {
|
|||
toggleActive() {
|
||||
this.newUser.isActive = !this.newUser.isActive
|
||||
},
|
||||
userTypeUpdated(type) {
|
||||
this.newUser.permissions = {
|
||||
download: type !== 'guest',
|
||||
update: type === 'admin',
|
||||
delete: type === 'admin'
|
||||
}
|
||||
},
|
||||
init() {
|
||||
this.isNew = !this.account
|
||||
if (this.account) {
|
||||
|
@ -151,14 +189,20 @@ export default {
|
|||
username: this.account.username,
|
||||
password: this.account.password,
|
||||
type: this.account.type,
|
||||
isActive: this.account.isActive
|
||||
isActive: this.account.isActive,
|
||||
permissions: { ...this.account.permissions }
|
||||
}
|
||||
} else {
|
||||
this.newUser = {
|
||||
username: null,
|
||||
password: null,
|
||||
type: 'user',
|
||||
isActive: true
|
||||
isActive: true,
|
||||
permissions: {
|
||||
download: true,
|
||||
update: false,
|
||||
delete: false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
</div>
|
||||
</template>
|
||||
<div class="absolute -top-10 left-0 w-full flex">
|
||||
<template v-for="tab in tabs">
|
||||
<template v-for="tab in availableTabs">
|
||||
<div :key="tab.id" class="w-28 rounded-t-lg flex items-center justify-center mr-1 cursor-pointer hover:bg-bg font-book border-t border-l border-r border-black-300 tab" :class="selectedTab === tab.id ? 'tab-selected bg-bg pb-px' : 'bg-primary text-gray-400'" @click="selectTab(tab.id)">{{ tab.title }}</div>
|
||||
</template>
|
||||
</div>
|
||||
|
@ -58,6 +58,15 @@ export default {
|
|||
show: {
|
||||
handler(newVal) {
|
||||
if (newVal) {
|
||||
var availableTabIds = this.availableTabs.map((tab) => tab.id)
|
||||
if (!availableTabIds.length) {
|
||||
this.show = false
|
||||
return
|
||||
}
|
||||
if (!availableTabIds.includes(this.selectedTab)) {
|
||||
this.selectedTab = availableTabIds[0]
|
||||
}
|
||||
|
||||
if (this.audiobook && this.audiobook.id === this.selectedAudiobookId) {
|
||||
if (this.fetchOnShow) this.fetchFull()
|
||||
return
|
||||
|
@ -86,6 +95,20 @@ export default {
|
|||
this.$store.commit('setEditModalTab', val)
|
||||
}
|
||||
},
|
||||
userCanUpdate() {
|
||||
return this.$store.getters['user/getUserCanUpdate']
|
||||
},
|
||||
userCanDownload() {
|
||||
return this.$store.getters['user/getUserCanDownload']
|
||||
},
|
||||
availableTabs() {
|
||||
if (!this.userCanUpdate && !this.userCanDownload) return []
|
||||
return this.tabs.filter((tab) => {
|
||||
if ((tab.id === 'download' || tab.id === 'tracks') && this.userCanDownload) return true
|
||||
if (tab.id !== 'download' && tab.id !== 'tracks' && this.userCanUpdate) return true
|
||||
return false
|
||||
})
|
||||
},
|
||||
height() {
|
||||
var maxHeightAllowed = window.innerHeight - 150
|
||||
return Math.min(maxHeightAllowed, 650)
|
||||
|
|
|
@ -54,7 +54,7 @@
|
|||
|
||||
<div class="absolute bottom-0 left-0 w-full py-4 bg-bg" :class="isScrollable ? 'box-shadow-md-up' : 'box-shadow-sm-up border-t border-primary border-opacity-50'">
|
||||
<div class="flex px-4">
|
||||
<ui-btn color="error" type="button" small @click.stop.prevent="deleteAudiobook">Remove</ui-btn>
|
||||
<ui-btn v-if="userCanDelete" color="error" type="button" small @click.stop.prevent="deleteAudiobook">Remove</ui-btn>
|
||||
<div class="flex-grow" />
|
||||
<ui-btn type="submit">Submit</ui-btn>
|
||||
</div>
|
||||
|
@ -113,12 +113,9 @@ export default {
|
|||
book() {
|
||||
return this.audiobook ? this.audiobook.book || {} : {}
|
||||
},
|
||||
// userAudiobook() {
|
||||
// return this.$store.getters['user/getUserAudiobook'](this.audiobookId)
|
||||
// },
|
||||
// userProgress() {
|
||||
// return this.userAudiobook ? this.userAudiobook.progress : 0
|
||||
// },
|
||||
userCanDelete() {
|
||||
return this.$store.getters['user/getUserCanDelete']
|
||||
},
|
||||
genres() {
|
||||
return this.$store.state.audiobooks.genres
|
||||
},
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<div class="w-full h-full overflow-y-auto overflow-x-hidden px-4 py-6">
|
||||
<div class="flex mb-4">
|
||||
<nuxt-link :to="`/audiobook/${audiobook.id}/edit`">
|
||||
<nuxt-link v-if="userCanUpdate" :to="`/audiobook/${audiobook.id}/edit`">
|
||||
<ui-btn color="primary">Edit Track Order</ui-btn>
|
||||
</nuxt-link>
|
||||
</div>
|
||||
|
@ -11,7 +11,7 @@
|
|||
<th class="text-left">Filename</th>
|
||||
<th class="text-left">Size</th>
|
||||
<th class="text-left">Duration</th>
|
||||
<th class="text-center">Download</th>
|
||||
<th v-if="userCanDownload" class="text-center">Download</th>
|
||||
</tr>
|
||||
<template v-for="track in tracks">
|
||||
<tr :key="track.index">
|
||||
|
@ -27,7 +27,7 @@
|
|||
<td class="font-mono">
|
||||
{{ $secondsToTimestamp(track.duration) }}
|
||||
</td>
|
||||
<td class="font-mono text-center">
|
||||
<td v-if="userCanDownload" class="font-mono text-center">
|
||||
<a :href="`/local/${track.path}`" download><span class="material-icons icon-text">download</span></a>
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -58,12 +58,18 @@ export default {
|
|||
}
|
||||
}
|
||||
},
|
||||
computed: {},
|
||||
computed: {
|
||||
userCanUpdate() {
|
||||
return this.$store.getters['user/getUserCanUpdate']
|
||||
},
|
||||
userCanDownload() {
|
||||
return this.$store.getters['user/getUserCanDownload']
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
init() {
|
||||
this.audioFiles = this.audiobook.audioFiles
|
||||
this.tracks = this.audiobook.tracks
|
||||
console.log('INIT', this.audiobook)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<p class="pr-4">Other Audio Files</p>
|
||||
<span class="bg-black-400 rounded-xl py-1 px-2 text-sm font-mono">{{ files.length }}</span>
|
||||
<div class="flex-grow" />
|
||||
<nuxt-link :to="`/audiobook/${audiobookId}/edit`" class="mr-4">
|
||||
<nuxt-link v-if="userCanUpdate" :to="`/audiobook/${audiobookId}/edit`" class="mr-4">
|
||||
<ui-btn small color="primary">Manage Tracks</ui-btn>
|
||||
</nuxt-link>
|
||||
<div class="cursor-pointer h-10 w-10 rounded-full hover:bg-black-400 flex justify-center items-center duration-500" :class="showTracks ? 'transform rotate-180' : ''">
|
||||
|
@ -56,7 +56,11 @@ export default {
|
|||
showTracks: false
|
||||
}
|
||||
},
|
||||
computed: {},
|
||||
computed: {
|
||||
userCanUpdate() {
|
||||
return this.$store.getters['user/getUserCanUpdate']
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
clickBar() {
|
||||
this.showTracks = !this.showTracks
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<p class="pr-4">Audio Tracks</p>
|
||||
<span class="bg-black-400 rounded-xl py-1 px-2 text-sm font-mono">{{ tracks.length }}</span>
|
||||
<div class="flex-grow" />
|
||||
<nuxt-link :to="`/audiobook/${audiobookId}/edit`" class="mr-4">
|
||||
<nuxt-link v-if="userCanUpdate" :to="`/audiobook/${audiobookId}/edit`" class="mr-4">
|
||||
<ui-btn small color="primary">Manage Tracks</ui-btn>
|
||||
</nuxt-link>
|
||||
<div class="cursor-pointer h-10 w-10 rounded-full hover:bg-black-400 flex justify-center items-center duration-500" :class="showTracks ? 'transform rotate-180' : ''">
|
||||
|
@ -19,7 +19,7 @@
|
|||
<th class="text-left">Filename</th>
|
||||
<th class="text-left">Size</th>
|
||||
<th class="text-left">Duration</th>
|
||||
<th class="text-center">Download</th>
|
||||
<th v-if="userCanDownload" class="text-center">Download</th>
|
||||
</tr>
|
||||
<template v-for="track in tracks">
|
||||
<tr :key="track.index">
|
||||
|
@ -35,7 +35,7 @@
|
|||
<td class="font-mono">
|
||||
{{ $secondsToTimestamp(track.duration) }}
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<td v-if="userCanDownload" class="text-center">
|
||||
<a :href="`/local/${track.path}`" download><span class="material-icons icon-text">download</span></a>
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -60,7 +60,14 @@ export default {
|
|||
showTracks: false
|
||||
}
|
||||
},
|
||||
computed: {},
|
||||
computed: {
|
||||
userCanDownload() {
|
||||
return this.$store.getters['user/getUserCanDownload']
|
||||
},
|
||||
userCanUpdate() {
|
||||
return this.$store.getters['user/getUserCanUpdate']
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
clickBar() {
|
||||
this.showTracks = !this.showTracks
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "audiobookshelf-client",
|
||||
"version": "1.0.7",
|
||||
"version": "1.0.8",
|
||||
"description": "Audiobook manager and player",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
|
|
|
@ -71,6 +71,9 @@ export default {
|
|||
if (!store.state.user.user) {
|
||||
return redirect(`/login?redirect=${route.path}`)
|
||||
}
|
||||
if (!store.getters['user/getUserCanUpdate']) {
|
||||
return redirect('/?error=unauthorized')
|
||||
}
|
||||
var audiobook = await app.$axios.$get(`/api/audiobook/${params.id}`).catch((error) => {
|
||||
console.error('Failed', error)
|
||||
return false
|
||||
|
@ -82,7 +85,6 @@ export default {
|
|||
return {
|
||||
audiobook,
|
||||
files: audiobook.audioFiles ? audiobook.audioFiles.map((af) => ({ ...af, include: !af.exclude })) : []
|
||||
// files: audiobook.audioFiles ? audiobook.audioFiles.map((af) => ({ ...af, index: ++index })) : []
|
||||
}
|
||||
},
|
||||
data() {
|
||||
|
|
|
@ -36,11 +36,11 @@
|
|||
{{ streaming ? 'Streaming' : 'Play' }}
|
||||
</ui-btn>
|
||||
|
||||
<ui-tooltip text="Edit" direction="top">
|
||||
<ui-tooltip v-if="userCanUpdate" text="Edit" direction="top">
|
||||
<ui-icon-btn icon="edit" class="mx-0.5" @click="editClick" />
|
||||
</ui-tooltip>
|
||||
|
||||
<ui-tooltip text="Download" direction="top">
|
||||
<ui-tooltip v-if="userCanDownload" text="Download" direction="top">
|
||||
<ui-icon-btn icon="download" class="mx-0.5" @click="downloadClick" />
|
||||
</ui-tooltip>
|
||||
|
||||
|
@ -240,6 +240,15 @@ export default {
|
|||
},
|
||||
streaming() {
|
||||
return this.streamAudiobook && this.streamAudiobook.id === this.audiobookId
|
||||
},
|
||||
userCanUpdate() {
|
||||
return this.$store.getters['user/getUserCanUpdate']
|
||||
},
|
||||
userCanDelete() {
|
||||
return this.$store.getters['user/getUserCanDelete']
|
||||
},
|
||||
userCanDownload() {
|
||||
return this.$store.getters['user/getUserCanDownload']
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import Vue from "vue";
|
||||
import Toast from "vue-toastification";
|
||||
import "vue-toastification/dist/index.css";
|
||||
import Vue from "vue"
|
||||
import Toast from "vue-toastification"
|
||||
import "vue-toastification/dist/index.css"
|
||||
|
||||
const options = {
|
||||
hideProgressBar: true
|
||||
};
|
||||
hideProgressBar: true,
|
||||
draggable: false
|
||||
}
|
||||
|
||||
|
||||
Vue.use(Toast, options);
|
||||
Vue.use(Toast, options)
|
|
@ -24,6 +24,15 @@ export const getters = {
|
|||
},
|
||||
getFilterOrderKey: (state) => {
|
||||
return Object.values(state.settings).join('-')
|
||||
},
|
||||
getUserCanUpdate: (state) => {
|
||||
return state.user && state.user.permissions ? !!state.user.permissions.update : false
|
||||
},
|
||||
getUserCanDelete: (state) => {
|
||||
return state.user && state.user.permissions ? !!state.user.permissions.delete : false
|
||||
},
|
||||
getUserCanDownload: (state) => {
|
||||
return state.user && state.user.permissions ? !!state.user.permissions.download : false
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue