mirror of
				https://github.com/advplyr/audiobookshelf.git
				synced 2025-10-27 11:18:14 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			183 lines
		
	
	
		
			6.5 KiB
		
	
	
	
		
			Vue
		
	
	
	
	
	
			
		
		
	
	
			183 lines
		
	
	
		
			6.5 KiB
		
	
	
	
		
			Vue
		
	
	
	
	
	
<template>
 | 
						|
  <div class="relative w-full py-4 px-6 border border-white border-opacity-10 shadow-lg rounded-md my-6">
 | 
						|
    <div class="absolute -top-3 -left-3 w-8 h-8 bg-bg border border-white border-opacity-10 flex items-center justify-center rounded-full">
 | 
						|
      <p class="text-base text-white text-opacity-80 font-mono">#{{ item.index }}</p>
 | 
						|
    </div>
 | 
						|
 | 
						|
    <div v-if="!processing && !uploadFailed && !uploadSuccess" class="absolute -top-3 -right-3 w-8 h-8 bg-bg border border-white border-opacity-10 flex items-center justify-center rounded-full hover:bg-error cursor-pointer" @click="$emit('remove')">
 | 
						|
      <span class="text-base text-white text-opacity-80 font-mono material-icons">close</span>
 | 
						|
    </div>
 | 
						|
 | 
						|
    <template v-if="!uploadSuccess && !uploadFailed">
 | 
						|
      <widgets-alert v-if="error" type="error">
 | 
						|
        <p class="text-base">{{ error }}</p>
 | 
						|
      </widgets-alert>
 | 
						|
 | 
						|
      <div class="flex my-2 -mx-2">
 | 
						|
        <div class="w-1/2 px-2">
 | 
						|
          <ui-text-input-with-label v-model.trim="itemData.title" :disabled="processing" :label="$strings.LabelTitle" @input="titleUpdated" />
 | 
						|
        </div>
 | 
						|
        <div class="w-1/2 px-2">
 | 
						|
          <div v-if="!isPodcast" class="flex items-end">
 | 
						|
            <ui-text-input-with-label v-model.trim="itemData.author" :disabled="processing" :label="$strings.LabelAuthor" />
 | 
						|
            <ui-tooltip :text="$strings.LabelUploaderItemFetchMetadataHelp">
 | 
						|
              <div
 | 
						|
                class="ml-2 mb-1 w-8 h-8 bg-bg border border-white border-opacity-10 flex items-center justify-center rounded-full hover:bg-primary cursor-pointer"
 | 
						|
                @click="fetchMetadata">
 | 
						|
                <span class="text-base text-white text-opacity-80 font-mono material-icons">sync</span>
 | 
						|
              </div>
 | 
						|
            </ui-tooltip>
 | 
						|
          </div>
 | 
						|
          <div v-else class="w-full">
 | 
						|
            <p class="px-1 text-sm font-semibold">{{ $strings.LabelDirectory }} <em class="font-normal text-xs pl-2">(auto)</em></p>
 | 
						|
            <ui-text-input :value="directory" disabled class="w-full font-mono text-xs" />
 | 
						|
          </div>
 | 
						|
        </div>
 | 
						|
      </div>
 | 
						|
      <div v-if="!isPodcast" class="flex my-2 -mx-2">
 | 
						|
        <div class="w-1/2 px-2">
 | 
						|
          <ui-text-input-with-label v-model.trim="itemData.series" :disabled="processing" :label="$strings.LabelSeries" note="(optional)" inputClass="h-10" />
 | 
						|
        </div>
 | 
						|
        <div class="w-1/2 px-2">
 | 
						|
          <div class="w-full">
 | 
						|
            <label class="px-1 text-sm font-semibold">{{ $strings.LabelDirectory }} <em class="font-normal text-xs pl-2">(auto)</em></label>
 | 
						|
            <ui-text-input :value="directory" disabled class="w-full font-mono text-xs h-10" />
 | 
						|
          </div>
 | 
						|
        </div>
 | 
						|
      </div>
 | 
						|
 | 
						|
      <tables-uploaded-files-table :files="item.itemFiles" :title="$strings.HeaderItemFiles" class="mt-8" />
 | 
						|
      <tables-uploaded-files-table v-if="item.otherFiles.length" :title="$strings.HeaderOtherFiles" :files="item.otherFiles" />
 | 
						|
      <tables-uploaded-files-table v-if="item.ignoredFiles.length" :title="$strings.HeaderIgnoredFiles" :files="item.ignoredFiles" />
 | 
						|
    </template>
 | 
						|
    <widgets-alert v-if="uploadSuccess" type="success">
 | 
						|
      <p class="text-base">{{ $strings.MessageUploaderItemSuccess }}</p>
 | 
						|
    </widgets-alert>
 | 
						|
    <widgets-alert v-if="uploadFailed" type="error">
 | 
						|
      <p class="text-base">{{ $strings.MessageUploaderItemFailed }}</p>
 | 
						|
    </widgets-alert>
 | 
						|
 | 
						|
    <div v-if="isNonInteractable" class="absolute top-0 left-0 w-full h-full bg-black bg-opacity-50 flex items-center justify-center z-20">
 | 
						|
      <ui-loading-indicator :text="nonInteractionLabel" />
 | 
						|
    </div>
 | 
						|
  </div>
 | 
						|
</template>
 | 
						|
 | 
						|
<script>
 | 
						|
import Path from 'path'
 | 
						|
 | 
						|
export default {
 | 
						|
  props: {
 | 
						|
    item: {
 | 
						|
      type: Object,
 | 
						|
      default: () => { }
 | 
						|
    },
 | 
						|
    mediaType: String,
 | 
						|
    processing: Boolean,
 | 
						|
    provider: String
 | 
						|
  },
 | 
						|
  data() {
 | 
						|
    return {
 | 
						|
      itemData: {
 | 
						|
        title: '',
 | 
						|
        author: '',
 | 
						|
        series: ''
 | 
						|
      },
 | 
						|
      error: '',
 | 
						|
      isUploading: false,
 | 
						|
      uploadFailed: false,
 | 
						|
      uploadSuccess: false,
 | 
						|
      isFetchingMetadata: false
 | 
						|
    }
 | 
						|
  },
 | 
						|
  computed: {
 | 
						|
    isPodcast() {
 | 
						|
      return this.mediaType === 'podcast'
 | 
						|
    },
 | 
						|
    directory() {
 | 
						|
      if (!this.itemData.title) return ''
 | 
						|
      if (this.isPodcast) return this.itemData.title
 | 
						|
 | 
						|
      const outputPathParts = [this.itemData.author, this.itemData.series, this.itemData.title]
 | 
						|
      const cleanedOutputPathParts = outputPathParts.filter(Boolean).map(part => this.$sanitizeFilename(part))
 | 
						|
 | 
						|
      return Path.join(...cleanedOutputPathParts)
 | 
						|
    },
 | 
						|
    isNonInteractable() {
 | 
						|
      return this.isUploading || this.isFetchingMetadata
 | 
						|
    },
 | 
						|
    nonInteractionLabel() {
 | 
						|
      if (this.isUploading) {
 | 
						|
        return this.$strings.MessageUploading
 | 
						|
      } else if (this.isFetchingMetadata) {
 | 
						|
        return this.$strings.LabelFetchingMetadata
 | 
						|
      }
 | 
						|
    }
 | 
						|
  },
 | 
						|
  methods: {
 | 
						|
    setUploadStatus(status) {
 | 
						|
      this.isUploading = status === 'uploading'
 | 
						|
      this.uploadFailed = status === 'failed'
 | 
						|
      this.uploadSuccess = status === 'success'
 | 
						|
    },
 | 
						|
    titleUpdated() {
 | 
						|
      this.error = ''
 | 
						|
    },
 | 
						|
    async fetchMetadata() {
 | 
						|
      if (!this.itemData.title.trim().length) {
 | 
						|
        return
 | 
						|
      }
 | 
						|
 | 
						|
      this.isFetchingMetadata = true
 | 
						|
      this.error = ''
 | 
						|
 | 
						|
      try {
 | 
						|
        const searchQueryString = new URLSearchParams({
 | 
						|
          title: this.itemData.title,
 | 
						|
          author: this.itemData.author,
 | 
						|
          provider: this.provider
 | 
						|
        })
 | 
						|
        const [bestCandidate, ..._rest] = await this.$axios.$get(`/api/search/books?${searchQueryString}`)
 | 
						|
 | 
						|
        if (bestCandidate) {
 | 
						|
          this.itemData = {
 | 
						|
            ...this.itemData,
 | 
						|
            title: bestCandidate.title,
 | 
						|
            author: bestCandidate.author,
 | 
						|
            series: (bestCandidate.series || [])[0]?.series
 | 
						|
          }
 | 
						|
        } else {
 | 
						|
          this.error = this.$strings.ErrorUploadFetchMetadataNoResults
 | 
						|
        }
 | 
						|
      } catch (e) {
 | 
						|
        console.error('Failed', e)
 | 
						|
        this.error = this.$strings.ErrorUploadFetchMetadataAPI
 | 
						|
      } finally {
 | 
						|
        this.isFetchingMetadata = false
 | 
						|
      }
 | 
						|
    },
 | 
						|
    getData() {
 | 
						|
      if (!this.itemData.title) {
 | 
						|
        this.error = this.$strings.ErrorUploadLacksTitle
 | 
						|
        return null
 | 
						|
      }
 | 
						|
      this.error = ''
 | 
						|
      var files = this.item.itemFiles.concat(this.item.otherFiles)
 | 
						|
      return {
 | 
						|
        index: this.item.index,
 | 
						|
        directory: this.directory,
 | 
						|
        ...this.itemData,
 | 
						|
        files
 | 
						|
      }
 | 
						|
    }
 | 
						|
  },
 | 
						|
  mounted() {
 | 
						|
    if (this.item) {
 | 
						|
      this.itemData.title = this.item.title
 | 
						|
      this.itemData.author = this.item.author
 | 
						|
      this.itemData.series = this.item.series
 | 
						|
    }
 | 
						|
  }
 | 
						|
}
 | 
						|
</script>
 |