Files
2024-09-01 18:54:23 +05:00

319 lines
7.5 KiB
Vue

<template>
<DefaultField
:field="currentField"
:label-for="labelFor"
:errors="errors"
:show-help-text="!isReadonly && showHelpText"
:full-width-content="fullWidthContent"
>
<template #field>
<!-- Existing Image -->
<div class="space-y-4">
<div
v-if="hasValue && previewFile && files.length === 0"
class="grid grid-cols-4 gap-x-6 gap-y-2"
>
<FilePreviewBlock
v-if="previewFile"
:file="previewFile"
:removable="shouldShowRemoveButton"
@removed="confirmRemoval"
:rounded="field.rounded"
:dusk="`${field.attribute}-delete-link`"
/>
</div>
<!-- Upload Removal Modal -->
<ConfirmUploadRemovalModal
:show="removeModalOpen"
@confirm="removeUploadedFile"
@close="closeRemoveModal"
/>
<!-- DropZone -->
<DropZone
v-if="shouldShowField"
:files="files"
@file-changed="handleFileChange"
@file-removed="file = null"
:rounded="field.rounded"
:accepted-types="field.acceptedTypes"
:disabled="file?.processing"
:dusk="`${field.attribute}-delete-link`"
:input-dusk="field.attribute"
/>
</div>
</template>
</DefaultField>
</template>
<script>
import { DependentFormField, Errors, HandlesValidationErrors } from '@/mixins'
import InlineFormData from './InlineFormData'
import Vapor from 'laravel-vapor'
function createFile(file) {
return {
name: file.name,
extension: file.name.split('.').pop(),
type: file.type,
originalFile: file,
vapor: false,
processing: false,
progress: 0,
}
}
export default {
emits: ['file-upload-started', 'file-upload-finished', 'file-deleted'],
mixins: [HandlesValidationErrors, DependentFormField],
inject: ['removeFile'],
expose: ['beforeRemove'],
data: () => ({
previewFile: null,
file: null,
removeModalOpen: false,
missing: false,
deleted: false,
uploadErrors: new Errors(),
vaporFile: {
key: '',
uuid: '',
filename: '',
extension: '',
},
uploadProgress: 0,
startedDrag: false,
uploadModalShown: false,
}),
async mounted() {
this.preparePreviewImage()
this.field.fill = formData => {
let attribute = this.fieldAttribute
if (this.file && !this.isVaporField) {
formData.append(attribute, this.file.originalFile, this.file.name)
}
if (this.file && this.isVaporField) {
formData.append(attribute, this.file.name)
this.fillVaporFilePayload(formData, attribute)
}
}
},
methods: {
preparePreviewImage() {
if (this.hasValue && this.imageUrl) {
this.fetchPreviewImage()
}
if (this.hasValue && !this.imageUrl) {
this.previewFile = createFile({
name: this.currentField.value,
type: this.currentField.value.split('.').pop(),
})
}
},
async fetchPreviewImage() {
let response = await fetch(this.imageUrl)
let data = await response.blob()
this.previewFile = createFile(
new File([data], this.currentField.value, { type: data.type })
)
},
handleFileChange(newFiles) {
this.file = createFile(newFiles[0])
if (this.isVaporField) {
this.file.vapor = true
this.uploadVaporFiles()
}
},
uploadVaporFiles() {
this.file.processing = true
this.$emit('file-upload-started')
Vapor.store(this.file.originalFile, {
progress: progress => {
this.file.progress = Math.round(progress * 100)
},
})
.then(response => {
this.vaporFile.key = response.key
this.vaporFile.uuid = response.uuid
this.vaporFile.filename = this.file.name
this.vaporFile.extension = this.file.extension
this.file.processing = false
this.file.progress = 100
this.$emit('file-upload-finished')
})
.catch(error => {
if (error.response.status === 403) {
Nova.error(
this.__('Sorry! You are not authorized to perform this action.')
)
}
})
},
confirmRemoval() {
this.removeModalOpen = true
},
closeRemoveModal() {
this.removeModalOpen = false
},
beforeRemove() {
this.removeUploadedFile()
},
async removeUploadedFile() {
// this.uploadErrors = new Errors()
try {
await this.removeFile(this.fieldAttribute)
this.$emit('file-deleted')
this.deleted = true
this.file = null
Nova.success(this.__('The file was deleted!'))
} catch (error) {
if (error.response?.status === 422) {
this.uploadErrors = new Errors(error.response.data.errors)
}
} finally {
this.closeRemoveModal()
}
},
fillVaporFilePayload(formData, attribute) {
const vaporAttribute =
formData instanceof InlineFormData
? formData.slug(attribute)
: attribute
const vaporFormData =
formData instanceof InlineFormData ? formData.formData : formData
vaporFormData.append(
`vaporFile[${vaporAttribute}][key]`,
this.vaporFile.key
)
vaporFormData.append(
`vaporFile[${vaporAttribute}][uuid]`,
this.vaporFile.uuid
)
vaporFormData.append(
`vaporFile[${vaporAttribute}][filename]`,
this.vaporFile.filename
)
vaporFormData.append(
`vaporFile[${vaporAttribute}][extension]`,
this.vaporFile.extension
)
},
},
computed: {
files() {
return this.file ? [this.file] : []
},
/**
* Determine if the field has an upload error.
*/
hasError() {
return this.uploadErrors.has(this.fieldAttribute)
},
/**
* Return the first error for the field.
*/
firstError() {
if (this.hasError) {
return this.uploadErrors.first(this.fieldAttribute)
}
},
/**
* The ID attribute to use for the file field.
*/
idAttr() {
return this.labelFor
},
/**
* The label attribute to use for the file field.
*/
labelFor() {
let name = this.resourceName
if (this.relatedResourceName) {
name += '-' + this.relatedResourceName
}
return `file-${name}-${this.fieldAttribute}`
},
/**
* Determine whether the field has a value.
*/
hasValue() {
return (
Boolean(this.field.value || this.imageUrl) &&
!Boolean(this.deleted) &&
!Boolean(this.missing)
)
},
/**
* Determine whether the field should show the loader component.
*/
shouldShowLoader() {
return !Boolean(this.deleted) && Boolean(this.imageUrl)
},
/**
* Determine whether the file field input should be shown.
*/
shouldShowField() {
return Boolean(!this.currentlyIsReadonly)
},
/**
* Determine whether the field should show the remove button.
*/
shouldShowRemoveButton() {
return Boolean(this.currentField.deletable && !this.currentlyIsReadonly)
},
/**
* Return the preview or thumbnail URL for the field.
*/
imageUrl() {
return this.currentField.previewUrl || this.currentField.thumbnailUrl
},
/**
* Determining if the field is a Vapor field.
*/
isVaporField() {
return this.currentField.component === 'vapor-file-field'
},
},
}
</script>