mirror of
				https://github.com/advplyr/audiobookshelf.git
				synced 2025-10-31 10:27:01 -04:00 
			
		
		
		
	
		
			
				
	
	
		
			202 lines
		
	
	
		
			6.6 KiB
		
	
	
	
		
			Vue
		
	
	
	
	
	
			
		
		
	
	
			202 lines
		
	
	
		
			6.6 KiB
		
	
	
	
		
			Vue
		
	
	
	
	
	
| <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">
 | |
|         <p class="text-3xl text-white truncate">{{ $strings.LabelShare }}</p>
 | |
|       </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">
 | |
|       <template v-if="currentShare">
 | |
|         <div class="w-full py-2">
 | |
|           <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" />
 | |
|         </div>
 | |
|         <div class="w-full py-2 px-1">
 | |
|           <p v-if="currentShare.expiresAt" class="text-base">{{ $getString('MessageShareExpiresIn', [currentShareTimeRemaining]) }}</p>
 | |
|           <p v-else>{{ $strings.LabelPermanent }}</p>
 | |
|         </div>
 | |
|       </template>
 | |
|       <template v-else>
 | |
|         <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>
 | |
|             <ui-text-input v-model="newShareSlug" class="text-base h-10" />
 | |
|           </div>
 | |
|           <div class="flex-grow" />
 | |
|           <div class="w-full sm:w-80">
 | |
|             <label class="px-1 text-sm font-semibold block">{{ $strings.LabelDuration }}</label>
 | |
|             <div class="inline-flex items-center space-x-2">
 | |
|               <div>
 | |
|                 <ui-icon-btn icon="remove" :size="10" @click="clickMinus" />
 | |
|               </div>
 | |
|               <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" />
 | |
|               <div>
 | |
|                 <ui-icon-btn icon="add" :size="10" @click="clickPlus" />
 | |
|               </div>
 | |
|               <div class="w-28">
 | |
|                 <ui-dropdown v-model="shareDurationUnit" :items="durationUnits" />
 | |
|               </div>
 | |
|             </div>
 | |
|           </div>
 | |
|         </div>
 | |
|         <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])" />
 | |
|       </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 {
 | |
|   props: {
 | |
|     value: Boolean,
 | |
|     libraryItem: {
 | |
|       type: Object,
 | |
|       default: () => null
 | |
|     },
 | |
|     mediaItemShare: {
 | |
|       type: Object,
 | |
|       default: () => null
 | |
|     }
 | |
|   },
 | |
|   data() {
 | |
|     return {
 | |
|       processing: false,
 | |
|       newShareSlug: '',
 | |
|       newShareDuration: 0,
 | |
|       currentShare: null,
 | |
|       shareDurationUnit: 'minutes',
 | |
|       durationUnits: [
 | |
|         {
 | |
|           text: this.$strings.LabelMinutes,
 | |
|           value: 'minutes'
 | |
|         },
 | |
|         {
 | |
|           text: this.$strings.LabelHours,
 | |
|           value: 'hours'
 | |
|         },
 | |
|         {
 | |
|           text: this.$strings.LabelDays,
 | |
|           value: 'days'
 | |
|         }
 | |
|       ]
 | |
|     }
 | |
|   },
 | |
|   watch: {
 | |
|     show: {
 | |
|       handler(newVal) {
 | |
|         if (newVal) {
 | |
|           this.init()
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   },
 | |
|   computed: {
 | |
|     show: {
 | |
|       get() {
 | |
|         return this.value
 | |
|       },
 | |
|       set(val) {
 | |
|         this.$emit('input', val)
 | |
|       }
 | |
|     },
 | |
|     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'
 | |
|       if (!this.currentShare.expiresAt) return this.$strings.LabelPermanent
 | |
|       const msRemaining = new Date(this.currentShare.expiresAt).valueOf() - Date.now()
 | |
|       if (msRemaining <= 0) return 'Expired'
 | |
|       return this.$elapsedPrettyExtended(msRemaining / 1000, true, false)
 | |
|     },
 | |
|     expireDurationSeconds() {
 | |
|       let shareDuration = Number(this.newShareDuration)
 | |
|       if (!shareDuration || isNaN(shareDuration)) return 0
 | |
|       return this.newShareDuration * (this.shareDurationUnit === 'minutes' ? 60 : this.shareDurationUnit === 'hours' ? 3600 : 86400)
 | |
|     },
 | |
|     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)
 | |
|     }
 | |
|   },
 | |
|   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>
 |