mirror of
https://github.com/advplyr/audiobookshelf-app.git
synced 2025-07-13 15:34:50 +02:00
Add:Ebook files table and supplementary ereader
This commit is contained in:
parent
543ac209e4
commit
d8bc26f5f8
16 changed files with 1283 additions and 638 deletions
|
@ -143,4 +143,27 @@ Bookshelf Label
|
||||||
display: -webkit-box;
|
display: -webkit-box;
|
||||||
-webkit-box-orient: vertical;
|
-webkit-box-orient: vertical;
|
||||||
-webkit-line-clamp: 4;
|
-webkit-line-clamp: 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tracksTable {
|
||||||
|
border-collapse: collapse;
|
||||||
|
width: 100%;
|
||||||
|
border: 1px solid #474747;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tracksTable tr:nth-child(even) {
|
||||||
|
background-color: #2e2e2e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tracksTable tr {
|
||||||
|
background-color: #373838;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tracksTable td {
|
||||||
|
padding: 8px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tracksTable th {
|
||||||
|
padding: 4px 8px;
|
||||||
|
font-size: 0.75rem;
|
||||||
}
|
}
|
|
@ -7,7 +7,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<div class="w-full h-full overflow-hidden absolute top-0 left-0 flex items-center justify-center" @click="show = false">
|
<div class="w-full h-full overflow-hidden absolute top-0 left-0 flex items-center justify-center" @click="show = false">
|
||||||
<div ref="container" class="w-full overflow-x-hidden overflow-y-auto bg-primary rounded-lg border border-white border-opacity-20 p-2" style="max-height: 75%" @click.stop>
|
<div ref="container" class="w-full overflow-x-hidden overflow-y-auto bg-primary rounded-lg border border-white/20 p-2" style="max-height: 75%" @click.stop>
|
||||||
<ul class="h-full w-full" role="listbox" aria-labelledby="listbox-label">
|
<ul class="h-full w-full" role="listbox" aria-labelledby="listbox-label">
|
||||||
<template v-for="item in itemsToShow">
|
<template v-for="item in itemsToShow">
|
||||||
<slot :name="item.value" :item="item" :selected="item.value === selected">
|
<slot :name="item.value" :item="item" :selected="item.value === selected">
|
||||||
|
|
|
@ -264,16 +264,25 @@ export default {
|
||||||
flow: 'paginated'
|
flow: 'paginated'
|
||||||
})
|
})
|
||||||
|
|
||||||
// load saved progress
|
|
||||||
reader.rendition.display(this.savedEbookLocation || reader.book.locations.start)
|
|
||||||
|
|
||||||
// load style
|
// load style
|
||||||
reader.rendition.themes.default({ '*': { color: '#fff!important', 'background-color': 'rgb(35 35 35)!important' }, a: { color: '#fff!important' } })
|
reader.rendition.themes.default({ '*': { color: '#fff!important', 'background-color': 'rgb(35 35 35)!important' }, a: { color: '#fff!important' } })
|
||||||
|
|
||||||
reader.book.ready.then(() => {
|
reader.book.ready.then(() => {
|
||||||
|
// load saved progress
|
||||||
|
// when not checking spine first uncaught exception is thrown
|
||||||
|
if (this.savedEbookLocation && reader.book.spine.get(this.savedEbookLocation)) {
|
||||||
|
reader.rendition.display(this.savedEbookLocation)
|
||||||
|
} else {
|
||||||
|
reader.rendition.display(reader.book.locations.start)
|
||||||
|
}
|
||||||
|
|
||||||
// set up event listeners
|
// set up event listeners
|
||||||
reader.rendition.on('relocated', reader.relocated)
|
reader.rendition.on('relocated', reader.relocated)
|
||||||
|
|
||||||
|
reader.rendition.on('displayError', (err) => {
|
||||||
|
console.log('Display error', err)
|
||||||
|
})
|
||||||
|
|
||||||
// load ebook cfi locations
|
// load ebook cfi locations
|
||||||
const savedLocations = this.loadLocations()
|
const savedLocations = this.loadLocations()
|
||||||
if (savedLocations) {
|
if (savedLocations) {
|
||||||
|
|
|
@ -70,28 +70,3 @@ export default {
|
||||||
mounted() {}
|
mounted() {}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.tracksTable {
|
|
||||||
border-collapse: collapse;
|
|
||||||
width: 100%;
|
|
||||||
border: 1px solid #474747;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tracksTable tr:nth-child(even) {
|
|
||||||
background-color: #2e2e2e;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tracksTable tr {
|
|
||||||
background-color: #373838;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tracksTable td {
|
|
||||||
padding: 8px 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tracksTable th {
|
|
||||||
padding: 4px 8px;
|
|
||||||
font-size: 0.75rem;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -58,28 +58,3 @@ export default {
|
||||||
mounted() {}
|
mounted() {}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.tracksTable {
|
|
||||||
border-collapse: collapse;
|
|
||||||
width: 100%;
|
|
||||||
border: 1px solid #474747;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tracksTable tr:nth-child(even) {
|
|
||||||
background-color: #2e2e2e;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tracksTable tr {
|
|
||||||
background-color: #373838;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tracksTable td {
|
|
||||||
padding: 8px 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tracksTable th {
|
|
||||||
padding: 4px 8px;
|
|
||||||
font-size: 0.75rem;
|
|
||||||
}
|
|
||||||
</style>
|
|
102
components/tables/ebook/EbookFilesTable.vue
Normal file
102
components/tables/ebook/EbookFilesTable.vue
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
<template>
|
||||||
|
<div class="w-full my-2">
|
||||||
|
<div class="w-full bg-primary px-4 py-2 flex items-center" :class="showFiles ? 'rounded-t-md' : 'rounded-md'" @click.stop="clickBar">
|
||||||
|
<p class="pr-2">Ebook Files</p>
|
||||||
|
<div class="h-6 w-6 rounded-full bg-white bg-opacity-10 flex items-center justify-center">
|
||||||
|
<span class="text-xs font-mono">{{ ebookFiles.length }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex-grow" />
|
||||||
|
<div class="h-10 w-10 rounded-full flex justify-center items-center duration-500" :class="showFiles ? 'transform rotate-180' : ''">
|
||||||
|
<span class="material-icons text-3xl">expand_more</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<transition name="slide">
|
||||||
|
<div class="w-full" v-show="showFiles">
|
||||||
|
<table class="text-sm tracksTable">
|
||||||
|
<tr>
|
||||||
|
<th class="text-left px-4">Filename</th>
|
||||||
|
<th class="text-left px-4 w-16">Read</th>
|
||||||
|
<th v-if="userCanUpdate && !libraryIsAudiobooksOnly" class="text-center w-16"></th>
|
||||||
|
</tr>
|
||||||
|
<template v-for="file in ebookFiles">
|
||||||
|
<tables-ebook-files-table-row :key="file.path" :libraryItemId="libraryItemId" :file="file" @read="readEbook" @more="showMore" />
|
||||||
|
</template>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
|
|
||||||
|
<modals-dialog v-model="showMoreMenu" :items="moreMenuItems" @action="moreMenuAction" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
libraryItem: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
processing: false,
|
||||||
|
showFiles: false,
|
||||||
|
showMoreMenu: false,
|
||||||
|
moreMenuItems: [],
|
||||||
|
selectedFile: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
libraryItemId() {
|
||||||
|
return this.libraryItem.id
|
||||||
|
},
|
||||||
|
userToken() {
|
||||||
|
return this.$store.getters['user/getToken']
|
||||||
|
},
|
||||||
|
ebookFiles() {
|
||||||
|
return (this.libraryItem.libraryFiles || []).filter((lf) => lf.fileType === 'ebook')
|
||||||
|
},
|
||||||
|
userCanUpdate() {
|
||||||
|
return this.$store.getters['user/getUserCanUpdate']
|
||||||
|
},
|
||||||
|
libraryIsAudiobooksOnly() {
|
||||||
|
return this.$store.getters['libraries/getLibraryIsAudiobooksOnly']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
moreMenuAction(action) {
|
||||||
|
this.showMoreMenu = false
|
||||||
|
if (action === 'updateStatus') {
|
||||||
|
this.updateEbookStatus()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
showMore({ file, items }) {
|
||||||
|
this.showMoreMenu = true
|
||||||
|
this.selectedFile = file
|
||||||
|
this.moreMenuItems = items
|
||||||
|
},
|
||||||
|
readEbook(fileIno) {
|
||||||
|
this.$store.commit('showReader', { libraryItem: this.libraryItem, keepProgress: false, fileId: fileIno })
|
||||||
|
},
|
||||||
|
clickBar() {
|
||||||
|
this.showFiles = !this.showFiles
|
||||||
|
},
|
||||||
|
updateEbookStatus() {
|
||||||
|
this.processing = true
|
||||||
|
this.$axios
|
||||||
|
.$patch(`/api/items/${this.libraryItemId}/ebook/${this.selectedFile.ino}/status`)
|
||||||
|
.then(() => {
|
||||||
|
this.$toast.success('Ebook updated')
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('Failed to update ebook', error)
|
||||||
|
this.$toast.error('Failed to update ebook')
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.processing = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {}
|
||||||
|
}
|
||||||
|
</script>
|
63
components/tables/ebook/EbookFilesTableRow.vue
Normal file
63
components/tables/ebook/EbookFilesTableRow.vue
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
<template>
|
||||||
|
<tr>
|
||||||
|
<td class="px-4">{{ file.metadata.filename }} <span v-if="isPrimary" class="material-icons-outlined text-success align-text-bottom text-base">check_circle</span></td>
|
||||||
|
<td class="text-xs w-16">
|
||||||
|
<ui-icon-btn icon="auto_stories" outlined borderless icon-font-size="1.125rem" :size="8" @click="readEbook" />
|
||||||
|
</td>
|
||||||
|
<td v-if="contextMenuItems.length" class="text-center">
|
||||||
|
<ui-icon-btn icon="more_vert" borderless @click="clickMore" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
libraryItemId: String,
|
||||||
|
showFullPath: Boolean,
|
||||||
|
file: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
userToken() {
|
||||||
|
return this.$store.getters['user/getToken']
|
||||||
|
},
|
||||||
|
userCanUpdate() {
|
||||||
|
return this.$store.getters['user/getUserCanUpdate']
|
||||||
|
},
|
||||||
|
isPrimary() {
|
||||||
|
return !this.file.isSupplementary
|
||||||
|
},
|
||||||
|
libraryIsAudiobooksOnly() {
|
||||||
|
return this.$store.getters['libraries/getLibraryIsAudiobooksOnly']
|
||||||
|
},
|
||||||
|
contextMenuItems() {
|
||||||
|
const items = []
|
||||||
|
if (this.userCanUpdate && !this.libraryIsAudiobooksOnly) {
|
||||||
|
items.push({
|
||||||
|
text: this.isPrimary ? 'Set as supplementary' : 'Set as primary',
|
||||||
|
value: 'updateStatus'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return items
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
clickMore() {
|
||||||
|
this.$emit('more', {
|
||||||
|
file: this.file,
|
||||||
|
items: this.contextMenuItems
|
||||||
|
})
|
||||||
|
},
|
||||||
|
readEbook() {
|
||||||
|
this.$emit('read', this.file.ino)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {}
|
||||||
|
}
|
||||||
|
</script>
|
92
components/ui/Tooltip.vue
Normal file
92
components/ui/Tooltip.vue
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
<template>
|
||||||
|
<div ref="box" class="inline-block" @click.stop="click">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
text: {
|
||||||
|
type: [String, Number],
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
direction: {
|
||||||
|
type: String,
|
||||||
|
default: 'right'
|
||||||
|
},
|
||||||
|
disabled: Boolean
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
tooltip: null,
|
||||||
|
tooltipTextEl: null,
|
||||||
|
tooltipId: null,
|
||||||
|
isShowing: false,
|
||||||
|
hideTimeout: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
text() {
|
||||||
|
this.updateText()
|
||||||
|
},
|
||||||
|
disabled(newVal) {
|
||||||
|
if (newVal && this.isShowing) {
|
||||||
|
this.hideTooltip()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
updateText() {
|
||||||
|
if (this.tooltipTextEl) {
|
||||||
|
this.tooltipTextEl.innerHTML = this.text
|
||||||
|
}
|
||||||
|
},
|
||||||
|
createTooltip() {
|
||||||
|
if (!this.$refs.box) return
|
||||||
|
const tooltip = document.createElement('div')
|
||||||
|
this.tooltipId = String(Math.floor(Math.random() * 10000))
|
||||||
|
tooltip.id = this.tooltipId
|
||||||
|
tooltip.className = 'fixed inset-0 w-screen h-screen bg-black/25 text-xs flex items-center justify-center p-2'
|
||||||
|
tooltip.style.zIndex = 100
|
||||||
|
tooltip.style.backgroundColor = 'rgba(0,0,0,0.85)'
|
||||||
|
|
||||||
|
tooltip.addEventListener('click', this.hideTooltip)
|
||||||
|
|
||||||
|
const innerDiv = document.createElement('div')
|
||||||
|
innerDiv.className = 'w-full p-2 border border-white/20 pointer-events-none text-white bg-primary'
|
||||||
|
innerDiv.innerHTML = this.text
|
||||||
|
tooltip.appendChild(innerDiv)
|
||||||
|
|
||||||
|
this.tooltipTextEl = innerDiv
|
||||||
|
this.tooltip = tooltip
|
||||||
|
},
|
||||||
|
showTooltip() {
|
||||||
|
if (this.disabled) return
|
||||||
|
if (!this.tooltip) {
|
||||||
|
this.createTooltip()
|
||||||
|
if (!this.tooltip) return
|
||||||
|
}
|
||||||
|
if (!this.$refs.box) return // Ensure element is not destroyed
|
||||||
|
try {
|
||||||
|
document.body.appendChild(this.tooltip)
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isShowing = true
|
||||||
|
},
|
||||||
|
hideTooltip() {
|
||||||
|
if (!this.tooltip) return
|
||||||
|
this.tooltip.remove()
|
||||||
|
this.isShowing = false
|
||||||
|
},
|
||||||
|
click() {
|
||||||
|
if (!this.isShowing) this.showTooltip()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
this.hideTooltip()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
1513
package-lock.json
generated
1513
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -39,7 +39,8 @@
|
||||||
"@babel/core": "7.13.15",
|
"@babel/core": "7.13.15",
|
||||||
"@babel/preset-env": "7.13.15",
|
"@babel/preset-env": "7.13.15",
|
||||||
"@capacitor/cli": "^4.0.0",
|
"@capacitor/cli": "^4.0.0",
|
||||||
"@nuxtjs/tailwindcss": "^4.2.0",
|
"@nuxtjs/tailwindcss": "^4.2.1",
|
||||||
"postcss": "^8.3.5"
|
"postcss": "^8.3.5",
|
||||||
|
"tailwindcss": "^3.3.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -123,6 +123,8 @@
|
||||||
|
|
||||||
<tables-tracks-table v-if="numTracks" :tracks="tracks" :library-item-id="libraryItemId" />
|
<tables-tracks-table v-if="numTracks" :tracks="tracks" :library-item-id="libraryItemId" />
|
||||||
|
|
||||||
|
<tables-ebook-files-table v-if="ebookFiles.length" :library-item="libraryItem" />
|
||||||
|
|
||||||
<!-- modals -->
|
<!-- modals -->
|
||||||
<modals-select-local-folder-modal v-model="showSelectLocalFolder" :media-type="mediaType" @select="selectedLocalFolder" />
|
<modals-select-local-folder-modal v-model="showSelectLocalFolder" :media-type="mediaType" @select="selectedLocalFolder" />
|
||||||
|
|
||||||
|
@ -359,6 +361,12 @@ export default {
|
||||||
if (this.isPodcast || this.hasLocal) return false
|
if (this.isPodcast || this.hasLocal) return false
|
||||||
return this.user && this.userCanDownload && (this.showPlay || (this.showRead && !this.isIos))
|
return this.user && this.userCanDownload && (this.showPlay || (this.showRead && !this.isIos))
|
||||||
},
|
},
|
||||||
|
libraryFiles() {
|
||||||
|
return this.libraryItem.libraryFiles || []
|
||||||
|
},
|
||||||
|
ebookFiles() {
|
||||||
|
return this.libraryFiles.filter((lf) => lf.fileType === 'ebook')
|
||||||
|
},
|
||||||
ebookFile() {
|
ebookFile() {
|
||||||
return this.media.ebookFile
|
return this.media.ebookFile
|
||||||
},
|
},
|
||||||
|
|
|
@ -15,20 +15,20 @@ export const getters = {
|
||||||
return state.libraries.find(lib => lib.id === state.currentLibraryId)
|
return state.libraries.find(lib => lib.id === state.currentLibraryId)
|
||||||
},
|
},
|
||||||
getCurrentLibraryName: (state, getters) => {
|
getCurrentLibraryName: (state, getters) => {
|
||||||
const currLib = getters.getCurrentLibrary
|
return getters.getCurrentLibrary?.name || null
|
||||||
return currLib ? currLib.name : null
|
|
||||||
},
|
},
|
||||||
getCurrentLibraryMediaType: (state, getters) => {
|
getCurrentLibraryMediaType: (state, getters) => {
|
||||||
const currLib = getters.getCurrentLibrary
|
return getters.getCurrentLibrary?.mediaType || null
|
||||||
return currLib ? currLib.mediaType : null
|
|
||||||
},
|
},
|
||||||
getCurrentLibrarySettings: (state, getters) => {
|
getCurrentLibrarySettings: (state, getters) => {
|
||||||
if (!getters.getCurrentLibrary) return null
|
return getters.getCurrentLibrary?.settings || null
|
||||||
return getters.getCurrentLibrary.settings
|
|
||||||
},
|
},
|
||||||
getBookCoverAspectRatio: (state, getters) => {
|
getBookCoverAspectRatio: (state, getters) => {
|
||||||
if (!getters.getCurrentLibrarySettings || isNaN(getters.getCurrentLibrarySettings.coverAspectRatio)) return 1
|
if (isNaN(getters.getCurrentLibrarySettings?.coverAspectRatio)) return 1
|
||||||
return getters.getCurrentLibrarySettings.coverAspectRatio === BookCoverAspectRatio.STANDARD ? 1.6 : 1
|
return getters.getCurrentLibrarySettings.coverAspectRatio === BookCoverAspectRatio.STANDARD ? 1.6 : 1
|
||||||
|
},
|
||||||
|
getLibraryIsAudiobooksOnly: (state, getters) => {
|
||||||
|
return !!getters.getCurrentLibrarySettings?.audiobooksOnly
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,22 +15,22 @@ export const getters = {
|
||||||
getIsRoot: (state) => state.user && state.user.type === 'root',
|
getIsRoot: (state) => state.user && state.user.type === 'root',
|
||||||
getIsAdminOrUp: (state) => state.user && (state.user.type === 'admin' || state.user.type === 'root'),
|
getIsAdminOrUp: (state) => state.user && (state.user.type === 'admin' || state.user.type === 'root'),
|
||||||
getToken: (state) => {
|
getToken: (state) => {
|
||||||
return state.user ? state.user.token : null
|
return state.user?.token || null
|
||||||
},
|
},
|
||||||
getServerConnectionConfigId: (state) => {
|
getServerConnectionConfigId: (state) => {
|
||||||
return state.serverConnectionConfig ? state.serverConnectionConfig.id : null
|
return state.serverConnectionConfig?.id || null
|
||||||
},
|
},
|
||||||
getServerAddress: (state) => {
|
getServerAddress: (state) => {
|
||||||
return state.serverConnectionConfig ? state.serverConnectionConfig.address : null
|
return state.serverConnectionConfig?.address || null
|
||||||
},
|
},
|
||||||
getServerConfigName: (state) => {
|
getServerConfigName: (state) => {
|
||||||
return state.serverConnectionConfig ? state.serverConnectionConfig.name : null
|
return state.serverConnectionConfig?.name || null
|
||||||
},
|
},
|
||||||
getCustomHeaders: (state) => {
|
getCustomHeaders: (state) => {
|
||||||
return state.serverConnectionConfig ? state.serverConnectionConfig.customHeaders : null
|
return state.serverConnectionConfig?.customHeaders || null
|
||||||
},
|
},
|
||||||
getUserMediaProgress: (state) => (libraryItemId, episodeId = null) => {
|
getUserMediaProgress: (state) => (libraryItemId, episodeId = null) => {
|
||||||
if (!state.user || !state.user.mediaProgress) return null
|
if (!state.user?.mediaProgress) return null
|
||||||
return state.user.mediaProgress.find(li => {
|
return state.user.mediaProgress.find(li => {
|
||||||
if (episodeId && li.episodeId !== episodeId) return false
|
if (episodeId && li.episodeId !== episodeId) return false
|
||||||
return li.libraryItemId == libraryItemId
|
return li.libraryItemId == libraryItemId
|
||||||
|
@ -41,10 +41,16 @@ export const getters = {
|
||||||
return state.user.bookmarks.filter(bm => bm.libraryItemId === libraryItemId)
|
return state.user.bookmarks.filter(bm => bm.libraryItemId === libraryItemId)
|
||||||
},
|
},
|
||||||
getUserSetting: (state) => (key) => {
|
getUserSetting: (state) => (key) => {
|
||||||
return state.settings ? state.settings[key] || null : null
|
return state.settings?.[key] || null
|
||||||
|
},
|
||||||
|
getUserCanUpdate: (state) => {
|
||||||
|
return !!state.user?.permissions?.update
|
||||||
|
},
|
||||||
|
getUserCanDelete: (state) => {
|
||||||
|
return !!state.user?.permissions?.delete
|
||||||
},
|
},
|
||||||
getUserCanDownload: (state) => {
|
getUserCanDownload: (state) => {
|
||||||
return state.user && state.user.permissions ? !!state.user.permissions.download : false
|
return !!state.user?.permissions?.download
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,13 +2,11 @@ const defaultTheme = require('tailwindcss/defaultTheme')
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
purge: {
|
purge: {
|
||||||
options: {
|
safelist: [
|
||||||
safelist: [
|
'bg-success',
|
||||||
'bg-success',
|
'bg-info',
|
||||||
'bg-info',
|
'text-info'
|
||||||
'text-info'
|
]
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
darkMode: false,
|
darkMode: false,
|
||||||
theme: {
|
theme: {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue