mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2025-08-02 09:14:40 +02:00
Add:Audiobooks only library settings, supplementary ebooks #1664
This commit is contained in:
parent
4b4fb33d8f
commit
014fc45c15
39 changed files with 624 additions and 122 deletions
|
@ -921,7 +921,7 @@ export default {
|
|||
return null
|
||||
})
|
||||
if (!libraryItem) return
|
||||
this.store.commit('showEReader', libraryItem)
|
||||
this.store.commit('showEReader', { libraryItem, keepProgress: true })
|
||||
},
|
||||
selectBtnClick(evt) {
|
||||
if (this.processingBatch) return
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<div class="w-full h-full px-1 md:px-4 py-1 mb-4">
|
||||
<div class="flex items-center py-2">
|
||||
<div class="flex items-center py-3">
|
||||
<ui-toggle-switch v-model="useSquareBookCovers" @input="formUpdated" />
|
||||
<ui-tooltip :text="$strings.LabelSettingsSquareBookCoversHelp">
|
||||
<p class="pl-4 text-base">
|
||||
|
@ -17,13 +17,22 @@
|
|||
</div>
|
||||
<p v-if="globalWatcherDisabled" class="text-xs text-warning">*{{ $strings.MessageWatcherIsDisabledGlobally }}</p>
|
||||
</div>
|
||||
<div v-if="mediaType == 'book'" class="py-3">
|
||||
<div v-if="isBookLibrary" class="flex items-center py-3">
|
||||
<ui-toggle-switch v-model="audiobooksOnly" @input="formUpdated" />
|
||||
<ui-tooltip :text="$strings.LabelSettingsAudiobooksOnlyHelp">
|
||||
<p class="pl-4 text-base">
|
||||
{{ $strings.LabelSettingsAudiobooksOnly }}
|
||||
<span class="material-icons icon-text text-sm">info_outlined</span>
|
||||
</p>
|
||||
</ui-tooltip>
|
||||
</div>
|
||||
<div v-if="isBookLibrary" class="py-3">
|
||||
<div class="flex items-center">
|
||||
<ui-toggle-switch v-model="skipMatchingMediaWithAsin" @input="formUpdated" />
|
||||
<p class="pl-4 text-base">{{ $strings.LabelSettingsSkipMatchingBooksWithASIN }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="mediaType == 'book'" class="py-3">
|
||||
<div v-if="isBookLibrary" class="py-3">
|
||||
<div class="flex items-center">
|
||||
<ui-toggle-switch v-model="skipMatchingMediaWithIsbn" @input="formUpdated" />
|
||||
<p class="pl-4 text-base">{{ $strings.LabelSettingsSkipMatchingBooksWithISBN }}</p>
|
||||
|
@ -47,7 +56,8 @@ export default {
|
|||
useSquareBookCovers: false,
|
||||
disableWatcher: false,
|
||||
skipMatchingMediaWithAsin: false,
|
||||
skipMatchingMediaWithIsbn: false
|
||||
skipMatchingMediaWithIsbn: false,
|
||||
audiobooksOnly: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
@ -60,6 +70,9 @@ export default {
|
|||
mediaType() {
|
||||
return this.library.mediaType
|
||||
},
|
||||
isBookLibrary() {
|
||||
return this.mediaType === 'book'
|
||||
},
|
||||
providers() {
|
||||
if (this.mediaType === 'podcast') return this.$store.state.scanners.podcastProviders
|
||||
return this.$store.state.scanners.providers
|
||||
|
@ -72,7 +85,8 @@ export default {
|
|||
coverAspectRatio: this.useSquareBookCovers ? this.$constants.BookCoverAspectRatio.SQUARE : this.$constants.BookCoverAspectRatio.STANDARD,
|
||||
disableWatcher: !!this.disableWatcher,
|
||||
skipMatchingMediaWithAsin: !!this.skipMatchingMediaWithAsin,
|
||||
skipMatchingMediaWithIsbn: !!this.skipMatchingMediaWithIsbn
|
||||
skipMatchingMediaWithIsbn: !!this.skipMatchingMediaWithIsbn,
|
||||
audiobooksOnly: !!this.audiobooksOnly
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -84,6 +98,7 @@ export default {
|
|||
this.disableWatcher = !!this.librarySettings.disableWatcher
|
||||
this.skipMatchingMediaWithAsin = !!this.librarySettings.skipMatchingMediaWithAsin
|
||||
this.skipMatchingMediaWithIsbn = !!this.librarySettings.skipMatchingMediaWithIsbn
|
||||
this.audiobooksOnly = !!this.librarySettings.audiobooksOnly
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
|
|
|
@ -64,7 +64,9 @@ export default {
|
|||
type: Object,
|
||||
default: () => {}
|
||||
},
|
||||
playerOpen: Boolean
|
||||
playerOpen: Boolean,
|
||||
keepProgress: Boolean,
|
||||
fileId: String
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
@ -98,6 +100,9 @@ export default {
|
|||
return this.libraryItem?.id
|
||||
},
|
||||
ebookUrl() {
|
||||
if (this.fileId) {
|
||||
return `/api/items/${this.libraryItemId}/ebook/${this.fileId}`
|
||||
}
|
||||
return `/api/items/${this.libraryItemId}/ebook`
|
||||
},
|
||||
comicMetadataKeys() {
|
||||
|
@ -114,6 +119,8 @@ export default {
|
|||
return this.$store.getters['user/getUserMediaProgress'](this.libraryItemId)
|
||||
},
|
||||
savedPage() {
|
||||
if (!this.keepProgress) return 0
|
||||
|
||||
// Validate ebookLocation is a number
|
||||
if (!this.userMediaProgress?.ebookLocation || isNaN(this.userMediaProgress.ebookLocation)) return 0
|
||||
return Number(this.userMediaProgress.ebookLocation)
|
||||
|
@ -141,6 +148,8 @@ export default {
|
|||
this.showInfoMenu = !this.showInfoMenu
|
||||
},
|
||||
updateProgress() {
|
||||
if (!this.keepProgress) return
|
||||
|
||||
if (!this.numPages) {
|
||||
console.error('Num pages not loaded')
|
||||
return
|
||||
|
|
|
@ -28,7 +28,9 @@ export default {
|
|||
type: Object,
|
||||
default: () => {}
|
||||
},
|
||||
playerOpen: Boolean
|
||||
playerOpen: Boolean,
|
||||
keepProgress: Boolean,
|
||||
fileId: String
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
@ -68,6 +70,7 @@ export default {
|
|||
return this.$store.getters['user/getUserMediaProgress'](this.libraryItemId)
|
||||
},
|
||||
savedEbookLocation() {
|
||||
if (!this.keepProgress) return null
|
||||
if (!this.userMediaProgress?.ebookLocation) return null
|
||||
// Validate ebookLocation is an epubcfi
|
||||
if (!String(this.userMediaProgress.ebookLocation).startsWith('epubcfi')) return null
|
||||
|
@ -84,7 +87,10 @@ export default {
|
|||
if (this.windowHeight < 400 || !this.playerOpen) return this.windowHeight
|
||||
return this.windowHeight - 164
|
||||
},
|
||||
epubUrl() {
|
||||
ebookUrl() {
|
||||
if (this.fileId) {
|
||||
return `/api/items/${this.libraryItemId}/ebook/${this.fileId}`
|
||||
}
|
||||
return `/api/items/${this.libraryItemId}/ebook`
|
||||
}
|
||||
},
|
||||
|
@ -112,6 +118,7 @@ export default {
|
|||
* @param {string} payload.ebookProgress - eBook Progress Percentage
|
||||
*/
|
||||
updateProgress(payload) {
|
||||
if (!this.keepProgress) return
|
||||
this.$axios.$patch(`/api/me/progress/${this.libraryItemId}`, payload).catch((error) => {
|
||||
console.error('EpubReader.updateProgress failed:', error)
|
||||
})
|
||||
|
@ -223,7 +230,7 @@ export default {
|
|||
const reader = this
|
||||
|
||||
/** @type {ePub.Book} */
|
||||
reader.book = new ePub(reader.epubUrl, {
|
||||
reader.book = new ePub(reader.ebookUrl, {
|
||||
width: this.readerWidth,
|
||||
height: this.readerHeight - 50,
|
||||
openAs: 'epub',
|
||||
|
@ -242,7 +249,7 @@ export default {
|
|||
reader.rendition.display(this.savedEbookLocation || reader.book.locations.start)
|
||||
|
||||
// load style
|
||||
reader.rendition.themes.default({ '*': { color: '#fff!important', 'background-color': 'rgb(35 35 35)!important' } })
|
||||
reader.rendition.themes.default({ '*': { color: '#fff!important', 'background-color': 'rgb(35 35 35)!important' }, a: { color: '#fff!important' } })
|
||||
|
||||
reader.book.ready.then(() => {
|
||||
// set up event listeners
|
||||
|
|
|
@ -19,7 +19,8 @@ export default {
|
|||
type: Object,
|
||||
default: () => {}
|
||||
},
|
||||
playerOpen: Boolean
|
||||
playerOpen: Boolean,
|
||||
fileId: String
|
||||
},
|
||||
data() {
|
||||
return {}
|
||||
|
@ -32,6 +33,9 @@ export default {
|
|||
return this.libraryItem?.id
|
||||
},
|
||||
ebookUrl() {
|
||||
if (this.fileId) {
|
||||
return `/api/items/${this.libraryItemId}/ebook/${this.fileId}`
|
||||
}
|
||||
return `/api/items/${this.libraryItemId}/ebook`
|
||||
}
|
||||
},
|
||||
|
|
|
@ -45,7 +45,9 @@ export default {
|
|||
type: Object,
|
||||
default: () => {}
|
||||
},
|
||||
playerOpen: Boolean
|
||||
playerOpen: Boolean,
|
||||
keepProgress: Boolean,
|
||||
fileId: String
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
@ -95,13 +97,21 @@ export default {
|
|||
return this.$store.getters['user/getUserMediaProgress'](this.libraryItemId)
|
||||
},
|
||||
savedPage() {
|
||||
if (!this.keepProgress) return 0
|
||||
|
||||
// Validate ebookLocation is a number
|
||||
if (!this.userMediaProgress?.ebookLocation || isNaN(this.userMediaProgress.ebookLocation)) return 0
|
||||
return Number(this.userMediaProgress.ebookLocation)
|
||||
},
|
||||
ebookUrl() {
|
||||
if (this.fileId) {
|
||||
return `/api/items/${this.libraryItemId}/ebook/${this.fileId}`
|
||||
}
|
||||
return `/api/items/${this.libraryItemId}/ebook`
|
||||
},
|
||||
pdfDocInitParams() {
|
||||
return {
|
||||
url: `/api/items/${this.libraryItemId}/ebook`,
|
||||
url: this.ebookUrl,
|
||||
httpHeaders: {
|
||||
Authorization: `Bearer ${this.userToken}`
|
||||
}
|
||||
|
@ -116,6 +126,7 @@ export default {
|
|||
this.scale -= 0.1
|
||||
},
|
||||
updateProgress() {
|
||||
if (!this.keepProgress) return
|
||||
if (!this.numPages) {
|
||||
console.error('Num pages not loaded')
|
||||
return
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
<span class="material-icons cursor-pointer text-2xl" @click="close">close</span>
|
||||
</div>
|
||||
|
||||
<component v-if="componentName" ref="readerComponent" :is="componentName" :library-item="selectedLibraryItem" :player-open="!!streamLibraryItem" />
|
||||
<component v-if="componentName" ref="readerComponent" :is="componentName" :library-item="selectedLibraryItem" :player-open="!!streamLibraryItem" :keep-progress="keepProgress" :file-id="ebookFileId" />
|
||||
|
||||
<!-- TOC side nav -->
|
||||
<div v-if="tocOpen" class="w-full h-full fixed inset-0 bg-black/20 z-20" @click.stop.prevent="toggleToC"></div>
|
||||
|
@ -103,10 +103,18 @@ export default {
|
|||
return this.selectedLibraryItem.folderId
|
||||
},
|
||||
ebookFile() {
|
||||
// ebook file id is passed when reading a supplementary ebook
|
||||
if (this.ebookFileId) {
|
||||
return this.selectedLibraryItem.libraryFiles.find((lf) => lf.ino === this.ebookFileId)
|
||||
}
|
||||
return this.media.ebookFile
|
||||
},
|
||||
ebookFormat() {
|
||||
if (!this.ebookFile) return null
|
||||
// Use file extension for supplementary ebook
|
||||
if (!this.ebookFile.ebookFormat) {
|
||||
return this.ebookFile.metadata.ext.toLowerCase().slice(1)
|
||||
}
|
||||
return this.ebookFile.ebookFormat
|
||||
},
|
||||
ebookType() {
|
||||
|
@ -130,6 +138,12 @@ export default {
|
|||
},
|
||||
userToken() {
|
||||
return this.$store.getters['user/getToken']
|
||||
},
|
||||
keepProgress() {
|
||||
return this.$store.state.ereaderKeepProgress
|
||||
},
|
||||
ebookFileId() {
|
||||
return this.$store.state.ereaderFileId
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
|
|
@ -88,7 +88,7 @@ export default {
|
|||
},
|
||||
deleteLibraryFile() {
|
||||
const payload = {
|
||||
message: 'This will delete the file from your file system. Are you sure?',
|
||||
message: this.$strings.MessageConfirmDeleteFile,
|
||||
callback: (confirmed) => {
|
||||
if (confirmed) {
|
||||
this.$axios
|
||||
|
|
87
client/components/tables/EbookFilesTable.vue
Normal file
87
client/components/tables/EbookFilesTable.vue
Normal file
|
@ -0,0 +1,87 @@
|
|||
<template>
|
||||
<div class="w-full my-2">
|
||||
<div class="w-full bg-primary px-4 md:px-6 py-2 flex items-center cursor-pointer" @click.stop="clickBar">
|
||||
<p class="pr-2 md:pr-4">{{ $strings.HeaderEbookFiles }}</p>
|
||||
<div class="h-5 md:h-7 w-5 md:w-7 rounded-full bg-white bg-opacity-10 flex items-center justify-center">
|
||||
<span class="text-sm font-mono">{{ ebookFiles.length }}</span>
|
||||
</div>
|
||||
<div class="flex-grow" />
|
||||
<ui-btn v-if="userIsAdmin" small :color="showFullPath ? 'gray-600' : 'primary'" class="mr-2 hidden md:block" @click.stop="showFullPath = !showFullPath">{{ $strings.ButtonFullPath }}</ui-btn>
|
||||
<div class="cursor-pointer h-10 w-10 rounded-full hover:bg-black-400 flex justify-center items-center duration-500" :class="showFiles ? 'transform rotate-180' : ''">
|
||||
<span class="material-icons text-4xl">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">{{ $strings.LabelPath }}</th>
|
||||
<th class="text-left w-24 min-w-24">{{ $strings.LabelSize }}</th>
|
||||
<th class="text-left px-4 w-24">
|
||||
{{ $strings.LabelRead }} <ui-tooltip :text="$strings.LabelReadEbookWithoutProgress" direction="top" class="inline-block"><span class="material-icons-outlined text-sm align-middle">info</span></ui-tooltip>
|
||||
</th>
|
||||
<th v-if="userCanDelete || userCanDownload || userIsAdmin" class="text-center w-16"></th>
|
||||
</tr>
|
||||
<template v-for="file in ebookFiles">
|
||||
<tables-ebook-files-table-row :key="file.path" :libraryItemId="libraryItemId" :showFullPath="showFullPath" :file="file" @read="readEbook" />
|
||||
</template>
|
||||
</table>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
libraryItem: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showFiles: false,
|
||||
showFullPath: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
libraryItemId() {
|
||||
return this.libraryItem.id
|
||||
},
|
||||
userToken() {
|
||||
return this.$store.getters['user/getToken']
|
||||
},
|
||||
userCanDownload() {
|
||||
return this.$store.getters['user/getUserCanDownload']
|
||||
},
|
||||
userCanDelete() {
|
||||
return this.$store.getters['user/getUserCanDelete']
|
||||
},
|
||||
userIsAdmin() {
|
||||
return this.$store.getters['user/getIsAdminOrUp']
|
||||
},
|
||||
ebookFiles() {
|
||||
return (this.libraryItem.libraryFiles || []).filter((lf) => lf.fileType === 'ebook')
|
||||
},
|
||||
ebookFileIno() {
|
||||
return this.libraryItem.media.ebookFile?.ino
|
||||
},
|
||||
audioFiles() {
|
||||
if (this.libraryItem.mediaType === 'podcast') {
|
||||
return this.libraryItem.media?.episodes.map((ep) => ep.audioFile) || []
|
||||
}
|
||||
return this.libraryItem.media?.audioFiles || []
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
readEbook(fileIno) {
|
||||
this.$store.commit('showEReader', { libraryItem: this.libraryItem, keepProgress: false, fileId: fileIno })
|
||||
},
|
||||
clickBar() {
|
||||
this.showFiles = !this.showFiles
|
||||
}
|
||||
},
|
||||
mounted() {}
|
||||
}
|
||||
</script>
|
139
client/components/tables/EbookFilesTableRow.vue
Normal file
139
client/components/tables/EbookFilesTableRow.vue
Normal file
|
@ -0,0 +1,139 @@
|
|||
<template>
|
||||
<tr>
|
||||
<td class="px-4">
|
||||
{{ showFullPath ? file.metadata.path : file.metadata.relPath }} <ui-tooltip :text="$strings.LabelPrimaryEbook" class="inline-block"><span v-if="isPrimary" class="material-icons-outlined text-success align-text-bottom">check_circle</span></ui-tooltip>
|
||||
</td>
|
||||
<td>
|
||||
{{ $bytesPretty(file.metadata.size) }}
|
||||
</td>
|
||||
<td class="text-xs">
|
||||
<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-context-menu-dropdown :items="contextMenuItems" :menu-width="130" :processing="processing" @action="contextMenuAction" />
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
libraryItemId: String,
|
||||
showFullPath: Boolean,
|
||||
file: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
processing: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
userToken() {
|
||||
return this.$store.getters['user/getToken']
|
||||
},
|
||||
userCanDownload() {
|
||||
return this.$store.getters['user/getUserCanDownload']
|
||||
},
|
||||
userCanDelete() {
|
||||
return this.$store.getters['user/getUserCanDelete']
|
||||
},
|
||||
userCanUpdate() {
|
||||
return this.$store.getters['user/getUserCanUpdate']
|
||||
},
|
||||
userIsAdmin() {
|
||||
return this.$store.getters['user/getIsAdminOrUp']
|
||||
},
|
||||
downloadUrl() {
|
||||
return `${process.env.serverUrl}/api/items/${this.libraryItemId}/file/${this.file.ino}/download?token=${this.userToken}`
|
||||
},
|
||||
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 ? this.$strings.LabelSetEbookAsSupplementary : this.$strings.LabelSetEbookAsPrimary,
|
||||
action: 'updateStatus'
|
||||
})
|
||||
}
|
||||
if (this.userCanDownload) {
|
||||
items.push({
|
||||
text: this.$strings.LabelDownload,
|
||||
action: 'download'
|
||||
})
|
||||
}
|
||||
if (this.userCanDelete) {
|
||||
items.push({
|
||||
text: this.$strings.ButtonDelete,
|
||||
action: 'delete'
|
||||
})
|
||||
}
|
||||
return items
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
readEbook() {
|
||||
this.$emit('read', this.file.ino)
|
||||
},
|
||||
contextMenuAction({ action }) {
|
||||
if (action === 'delete') {
|
||||
this.deleteLibraryFile()
|
||||
} else if (action === 'download') {
|
||||
this.downloadLibraryFile()
|
||||
} else if (action === 'updateStatus') {
|
||||
this.updateEbookStatus()
|
||||
}
|
||||
},
|
||||
updateEbookStatus() {
|
||||
this.processing = true
|
||||
this.$axios
|
||||
.$patch(`/api/items/${this.libraryItemId}/ebook/${this.file.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
|
||||
})
|
||||
},
|
||||
deleteLibraryFile() {
|
||||
const payload = {
|
||||
message: this.$strings.MessageConfirmDeleteFile,
|
||||
callback: (confirmed) => {
|
||||
if (confirmed) {
|
||||
this.processing = true
|
||||
this.$axios
|
||||
.$delete(`/api/items/${this.libraryItemId}/file/${this.file.ino}`)
|
||||
.then(() => {
|
||||
this.$toast.success('File deleted')
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Failed to delete file', error)
|
||||
this.$toast.error('Failed to delete file')
|
||||
})
|
||||
.finally(() => {
|
||||
this.processing = false
|
||||
})
|
||||
}
|
||||
},
|
||||
type: 'yesNo'
|
||||
}
|
||||
this.$store.commit('globals/setConfirmPrompt', payload)
|
||||
},
|
||||
downloadLibraryFile() {
|
||||
this.$downloadFile(this.downloadUrl, this.file.metadata.filename)
|
||||
}
|
||||
},
|
||||
mounted() {}
|
||||
}
|
||||
</script>
|
|
@ -38,7 +38,6 @@ export default {
|
|||
type: Object,
|
||||
default: () => {}
|
||||
},
|
||||
isMissing: Boolean,
|
||||
expanded: Boolean, // start expanded
|
||||
inModal: Boolean
|
||||
},
|
||||
|
|
|
@ -83,7 +83,7 @@ export default {
|
|||
},
|
||||
deleteLibraryFile() {
|
||||
const payload = {
|
||||
message: 'This will delete the file from your file system. Are you sure?',
|
||||
message: this.$strings.MessageConfirmDeleteFile,
|
||||
callback: (confirmed) => {
|
||||
if (confirmed) {
|
||||
this.$axios
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
<template>
|
||||
<div class="relative h-9 w-9" v-click-outside="clickOutsideObj">
|
||||
<slot :disabled="disabled" :showMenu="showMenu" :clickShowMenu="clickShowMenu">
|
||||
<button type="button" :disabled="disabled" class="relative h-full w-full flex items-center justify-center shadow-sm pl-3 pr-3 text-left focus:outline-none cursor-pointer text-gray-100 hover:text-gray-200 rounded-full hover:bg-white/5" aria-haspopup="listbox" :aria-expanded="showMenu" @click.stop.prevent="clickShowMenu">
|
||||
<slot :disabled="disabled" :showMenu="showMenu" :clickShowMenu="clickShowMenu" :processing="processing">
|
||||
<button v-if="!processing" type="button" :disabled="disabled" class="relative h-full w-full flex items-center justify-center shadow-sm pl-3 pr-3 text-left focus:outline-none cursor-pointer text-gray-100 hover:text-gray-200 rounded-full hover:bg-white/5" aria-haspopup="listbox" :aria-expanded="showMenu" @click.stop.prevent="clickShowMenu">
|
||||
<span class="material-icons" :class="iconClass">more_vert</span>
|
||||
</button>
|
||||
<div v-else class="h-full w-full flex items-center justify-center">
|
||||
<widgets-loading-spinner />
|
||||
</div>
|
||||
</slot>
|
||||
|
||||
<transition name="menu">
|
||||
|
@ -28,7 +31,7 @@
|
|||
</div>
|
||||
</template>
|
||||
<div v-else :key="index" class="flex items-center px-2 py-1.5 hover:bg-white hover:bg-opacity-5 text-white text-xs cursor-pointer" @click.stop="clickAction(item.action)">
|
||||
<p>{{ item.text }}</p>
|
||||
<p class="text-left">{{ item.text }}</p>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
@ -51,7 +54,8 @@ export default {
|
|||
menuWidth: {
|
||||
type: Number,
|
||||
default: 192
|
||||
}
|
||||
},
|
||||
processing: Boolean
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue