2024-06-22 23:42:13 +02:00
|
|
|
<template>
|
|
|
|
<modals-modal ref="modal" v-model="show" name="share" :width="600" :height="'unset'" :processing="processing">
|
|
|
|
<template #outer>
|
|
|
|
<div class="absolute top-0 left-0 p-5 w-2/3 overflow-hidden">
|
2024-06-29 23:15:55 +02:00
|
|
|
<p class="text-3xl text-white truncate">{{ $strings.LabelShare }}</p>
|
2024-06-22 23:42:13 +02:00
|
|
|
</div>
|
|
|
|
</template>
|
|
|
|
<div class="px-6 py-8 w-full text-sm rounded-lg bg-bg shadow-lg border border-black-300 overflow-y-auto overflow-x-hidden" style="max-height: 80vh">
|
2024-07-07 23:09:32 +02:00
|
|
|
<div class="absolute top-0 right-0 p-4">
|
|
|
|
<ui-tooltip :text="$strings.LabelClickForMoreInfo" class="inline-flex ml-2">
|
|
|
|
<a href="https://www.audiobookshelf.org/guides/media-item-shares" target="_blank" class="inline-flex">
|
2024-07-09 00:29:36 +02:00
|
|
|
<span class="material-symbols text-xl w-5 text-gray-200">help_outline</span>
|
2024-07-07 23:09:32 +02:00
|
|
|
</a>
|
|
|
|
</ui-tooltip>
|
|
|
|
</div>
|
2024-06-22 23:42:13 +02:00
|
|
|
<template v-if="currentShare">
|
|
|
|
<div class="w-full py-2">
|
2024-06-29 23:15:55 +02:00
|
|
|
<label class="px-1 text-sm font-semibold block">{{ $strings.LabelShareURL }}</label>
|
|
|
|
<ui-text-input v-model="currentShareUrl" show-copy readonly class="text-base h-10" />
|
2024-06-22 23:42:13 +02:00
|
|
|
</div>
|
|
|
|
<div class="w-full py-2 px-1">
|
2024-06-29 23:15:55 +02:00
|
|
|
<p v-if="currentShare.expiresAt" class="text-base">{{ $getString('MessageShareExpiresIn', [currentShareTimeRemaining]) }}</p>
|
|
|
|
<p v-else>{{ $strings.LabelPermanent }}</p>
|
2024-06-22 23:42:13 +02:00
|
|
|
</div>
|
|
|
|
</template>
|
|
|
|
<template v-else>
|
2024-06-29 23:15:55 +02:00
|
|
|
<div class="flex flex-col sm:flex-row items-center justify-between space-y-4 sm:space-y-0 sm:space-x-4 mb-4">
|
|
|
|
<div class="w-full sm:w-48">
|
|
|
|
<label class="px-1 text-sm font-semibold block">{{ $strings.LabelSlug }}</label>
|
2024-06-22 23:42:13 +02:00
|
|
|
<ui-text-input v-model="newShareSlug" class="text-base h-10" />
|
|
|
|
</div>
|
|
|
|
<div class="flex-grow" />
|
2024-06-29 23:15:55 +02:00
|
|
|
<div class="w-full sm:w-80">
|
|
|
|
<label class="px-1 text-sm font-semibold block">{{ $strings.LabelDuration }}</label>
|
2024-06-22 23:42:13 +02:00
|
|
|
<div class="inline-flex items-center space-x-2">
|
|
|
|
<div>
|
|
|
|
<ui-icon-btn icon="remove" :size="10" @click="clickMinus" />
|
|
|
|
</div>
|
2024-06-29 23:15:55 +02:00
|
|
|
<ui-text-input v-model="newShareDuration" type="number" text-center no-spinner class="text-center max-w-12 min-w-12 h-10 text-base" />
|
2024-06-22 23:42:13 +02:00
|
|
|
<div>
|
|
|
|
<ui-icon-btn icon="add" :size="10" @click="clickPlus" />
|
|
|
|
</div>
|
2024-06-29 23:15:55 +02:00
|
|
|
<div class="w-28">
|
|
|
|
<ui-dropdown v-model="shareDurationUnit" :items="durationUnits" />
|
|
|
|
</div>
|
2024-06-22 23:42:13 +02:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
2024-06-29 23:15:55 +02:00
|
|
|
<p class="text-sm text-gray-300 py-1 px-1" v-html="$getString('MessageShareURLWillBe', [demoShareUrl])" />
|
|
|
|
<p class="text-sm text-gray-300 py-1 px-1" v-html="$getString('MessageShareExpirationWillBe', [expirationDateString])" />
|
2024-06-22 23:42:13 +02:00
|
|
|
</template>
|
|
|
|
<div class="flex items-center pt-6">
|
|
|
|
<div class="flex-grow" />
|
|
|
|
<ui-btn v-if="currentShare" color="error" small @click="deleteShare">{{ $strings.ButtonDelete }}</ui-btn>
|
|
|
|
<ui-btn v-if="!currentShare" color="success" small @click="openShare">{{ $strings.ButtonShare }}</ui-btn>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</modals-modal>
|
|
|
|
</template>
|
|
|
|
|
|
|
|
<script>
|
|
|
|
export default {
|
2024-07-07 22:51:50 +02:00
|
|
|
props: {},
|
2024-06-22 23:42:13 +02:00
|
|
|
data() {
|
|
|
|
return {
|
|
|
|
processing: false,
|
|
|
|
newShareSlug: '',
|
|
|
|
newShareDuration: 0,
|
|
|
|
currentShare: null,
|
|
|
|
shareDurationUnit: 'minutes',
|
|
|
|
durationUnits: [
|
|
|
|
{
|
2024-06-29 23:15:55 +02:00
|
|
|
text: this.$strings.LabelMinutes,
|
2024-06-22 23:42:13 +02:00
|
|
|
value: 'minutes'
|
|
|
|
},
|
|
|
|
{
|
2024-06-29 23:15:55 +02:00
|
|
|
text: this.$strings.LabelHours,
|
2024-06-22 23:42:13 +02:00
|
|
|
value: 'hours'
|
|
|
|
},
|
|
|
|
{
|
2024-06-29 23:15:55 +02:00
|
|
|
text: this.$strings.LabelDays,
|
2024-06-22 23:42:13 +02:00
|
|
|
value: 'days'
|
|
|
|
}
|
|
|
|
]
|
|
|
|
}
|
|
|
|
},
|
|
|
|
watch: {
|
|
|
|
show: {
|
|
|
|
handler(newVal) {
|
|
|
|
if (newVal) {
|
|
|
|
this.init()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
computed: {
|
|
|
|
show: {
|
|
|
|
get() {
|
2024-07-07 22:51:50 +02:00
|
|
|
return this.$store.state.globals.showShareModal
|
2024-06-22 23:42:13 +02:00
|
|
|
},
|
|
|
|
set(val) {
|
2024-07-07 22:51:50 +02:00
|
|
|
this.$store.commit('globals/setShowShareModal', val)
|
2024-06-22 23:42:13 +02:00
|
|
|
}
|
|
|
|
},
|
2024-07-07 22:51:50 +02:00
|
|
|
mediaItemShare() {
|
|
|
|
return this.$store.state.globals.selectedMediaItemShare
|
|
|
|
},
|
|
|
|
libraryItem() {
|
|
|
|
return this.$store.state.selectedLibraryItem
|
|
|
|
},
|
2024-06-22 23:42:13 +02:00
|
|
|
user() {
|
|
|
|
return this.$store.state.user.user
|
|
|
|
},
|
|
|
|
demoShareUrl() {
|
|
|
|
return `${window.origin}/share/${this.newShareSlug}`
|
|
|
|
},
|
|
|
|
currentShareUrl() {
|
|
|
|
if (!this.currentShare) return ''
|
|
|
|
return `${window.origin}/share/${this.currentShare.slug}`
|
|
|
|
},
|
|
|
|
currentShareTimeRemaining() {
|
|
|
|
if (!this.currentShare) return 'Error'
|
2024-06-29 23:15:55 +02:00
|
|
|
if (!this.currentShare.expiresAt) return this.$strings.LabelPermanent
|
2024-06-22 23:42:13 +02:00
|
|
|
const msRemaining = new Date(this.currentShare.expiresAt).valueOf() - Date.now()
|
|
|
|
if (msRemaining <= 0) return 'Expired'
|
2024-06-29 23:15:55 +02:00
|
|
|
return this.$elapsedPrettyExtended(msRemaining / 1000, true, false)
|
2024-06-22 23:42:13 +02:00
|
|
|
},
|
|
|
|
expireDurationSeconds() {
|
|
|
|
let shareDuration = Number(this.newShareDuration)
|
|
|
|
if (!shareDuration || isNaN(shareDuration)) return 0
|
|
|
|
return this.newShareDuration * (this.shareDurationUnit === 'minutes' ? 60 : this.shareDurationUnit === 'hours' ? 3600 : 86400)
|
2024-06-29 23:15:55 +02:00
|
|
|
},
|
|
|
|
expirationDateString() {
|
|
|
|
if (!this.expireDurationSeconds) return this.$strings.LabelPermanent
|
|
|
|
const dateMs = Date.now() + this.expireDurationSeconds * 1000
|
|
|
|
return this.$formatDatetime(dateMs, this.$store.state.serverSettings.dateFormat, this.$store.state.serverSettings.timeFormat)
|
2024-06-22 23:42:13 +02:00
|
|
|
}
|
|
|
|
},
|
|
|
|
methods: {
|
|
|
|
clickPlus() {
|
|
|
|
this.newShareDuration++
|
|
|
|
},
|
|
|
|
clickMinus() {
|
|
|
|
if (this.newShareDuration > 0) {
|
|
|
|
this.newShareDuration--
|
|
|
|
}
|
|
|
|
},
|
|
|
|
deleteShare() {
|
|
|
|
if (!this.currentShare) return
|
|
|
|
this.processing = true
|
|
|
|
this.$axios
|
|
|
|
.$delete(`/api/share/mediaitem/${this.currentShare.id}`)
|
|
|
|
.then(() => {
|
|
|
|
this.currentShare = null
|
|
|
|
this.$emit('removed')
|
|
|
|
})
|
|
|
|
.catch((error) => {
|
|
|
|
console.error('deleteShare', error)
|
|
|
|
let errorMsg = error.response?.data || 'Failed to delete share'
|
|
|
|
this.$toast.error(errorMsg)
|
|
|
|
})
|
|
|
|
.finally(() => {
|
|
|
|
this.processing = false
|
|
|
|
})
|
|
|
|
},
|
|
|
|
openShare() {
|
|
|
|
if (!this.newShareSlug) {
|
|
|
|
this.$toast.error('Slug is required')
|
|
|
|
return
|
|
|
|
}
|
|
|
|
const payload = {
|
|
|
|
slug: this.newShareSlug,
|
|
|
|
mediaItemType: 'book',
|
|
|
|
mediaItemId: this.libraryItem.media.id,
|
|
|
|
expiresAt: this.expireDurationSeconds ? Date.now() + this.expireDurationSeconds * 1000 : 0
|
|
|
|
}
|
|
|
|
this.processing = true
|
|
|
|
this.$axios
|
|
|
|
.$post(`/api/share/mediaitem`, payload)
|
|
|
|
.then((data) => {
|
|
|
|
this.currentShare = data
|
|
|
|
this.$emit('opened', data)
|
|
|
|
})
|
|
|
|
.catch((error) => {
|
|
|
|
console.error('openShare', error)
|
|
|
|
let errorMsg = error.response?.data || 'Failed to share item'
|
|
|
|
this.$toast.error(errorMsg)
|
|
|
|
})
|
|
|
|
.finally(() => {
|
|
|
|
this.processing = false
|
|
|
|
})
|
|
|
|
},
|
|
|
|
init() {
|
|
|
|
this.newShareSlug = this.$randomId(10)
|
|
|
|
if (this.mediaItemShare) {
|
|
|
|
this.currentShare = { ...this.mediaItemShare }
|
|
|
|
} else {
|
|
|
|
this.currentShare = null
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
mounted() {}
|
|
|
|
}
|
|
|
|
</script>
|