mirror of
				https://github.com/advplyr/audiobookshelf.git
				synced 2025-10-27 11:18:14 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			205 lines
		
	
	
		
			7.1 KiB
		
	
	
	
		
			Vue
		
	
	
	
	
	
			
		
		
	
	
			205 lines
		
	
	
		
			7.1 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">
 | 
						|
      <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">
 | 
						|
            <span class="material-symbols text-xl w-5 text-gray-200">help_outline</span>
 | 
						|
          </a>
 | 
						|
        </ui-tooltip>
 | 
						|
      </div>
 | 
						|
      <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: {},
 | 
						|
  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.$store.state.globals.showShareModal
 | 
						|
      },
 | 
						|
      set(val) {
 | 
						|
        this.$store.commit('globals/setShowShareModal', val)
 | 
						|
      }
 | 
						|
    },
 | 
						|
    mediaItemShare() {
 | 
						|
      return this.$store.state.globals.selectedMediaItemShare
 | 
						|
    },
 | 
						|
    libraryItem() {
 | 
						|
      return this.$store.state.selectedLibraryItem
 | 
						|
    },
 | 
						|
    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>
 |