340 lines
8.3 KiB
Vue
340 lines
8.3 KiB
Vue
<template>
|
|
<default-field
|
|
:field="field"
|
|
:errors="errors"
|
|
:full-width-content="true"
|
|
:show-help-text="!isReadonly && showHelpText"
|
|
>
|
|
<template slot="field">
|
|
<div v-if="hasValue" :class="{ 'mb-6': !isReadonly }">
|
|
<template v-if="shouldShowLoader">
|
|
<ImageLoader
|
|
:src="imageUrl"
|
|
:maxWidth="maxWidth"
|
|
:rounded="field.rounded"
|
|
@missing="value => (missing = value)"
|
|
/>
|
|
</template>
|
|
|
|
<template v-if="field.value && !imageUrl">
|
|
<card
|
|
class="flex item-center relative border border-lg border-50 overflow-hidden p-4"
|
|
>
|
|
<span class="truncate mr-3"> {{ field.value }} </span>
|
|
|
|
<DeleteButton
|
|
:dusk="field.attribute + '-internal-delete-link'"
|
|
class="ml-auto"
|
|
v-if="shouldShowRemoveButton"
|
|
@click="confirmRemoval"
|
|
/>
|
|
</card>
|
|
</template>
|
|
|
|
<p
|
|
v-if="imageUrl && !isReadonly"
|
|
class="mt-3 flex items-center text-sm"
|
|
>
|
|
<DeleteButton
|
|
:dusk="field.attribute + '-delete-link'"
|
|
v-if="shouldShowRemoveButton"
|
|
@click="confirmRemoval"
|
|
>
|
|
<span class="class ml-2 mt-1"> {{ __('Delete') }} </span>
|
|
</DeleteButton>
|
|
</p>
|
|
|
|
<portal to="modals">
|
|
<confirm-upload-removal-modal
|
|
v-if="removeModalOpen"
|
|
@confirm="removeFile"
|
|
@close="closeRemoveModal"
|
|
/>
|
|
</portal>
|
|
</div>
|
|
|
|
<p v-if="!hasValue && isReadonly" class="pt-2 text-sm text-90">
|
|
{{ __('This file field is read-only.') }}
|
|
</p>
|
|
|
|
<span
|
|
v-if="shouldShowField"
|
|
class="form-file mr-4"
|
|
:class="{ 'opacity-75': isReadonly }"
|
|
>
|
|
<input
|
|
ref="fileField"
|
|
:dusk="field.attribute"
|
|
class="form-file-input select-none"
|
|
type="file"
|
|
:id="idAttr"
|
|
name="name"
|
|
@change="fileChange"
|
|
:disabled="isReadonly || uploading"
|
|
:accept="field.acceptedTypes"
|
|
/>
|
|
<label
|
|
:for="labelFor"
|
|
class="form-file-btn btn btn-default btn-primary select-none"
|
|
>
|
|
<span v-if="uploading"
|
|
>{{ __('Uploading') }} ({{ uploadProgress }}%)</span
|
|
>
|
|
<span v-else>{{ __('Choose File') }}</span>
|
|
</label>
|
|
</span>
|
|
|
|
<span v-if="shouldShowField" class="text-90 text-sm select-none">
|
|
{{ currentLabel }}
|
|
</span>
|
|
|
|
<p v-if="hasError" class="text-xs mt-2 text-danger">{{ firstError }}</p>
|
|
</template>
|
|
</default-field>
|
|
</template>
|
|
|
|
<script>
|
|
import ImageLoader from '@/components/ImageLoader'
|
|
import DeleteButton from '@/components/DeleteButton'
|
|
import { FormField, HandlesValidationErrors, Errors } from 'laravel-nova'
|
|
import Vapor from 'laravel-vapor'
|
|
|
|
export default {
|
|
props: [
|
|
'resourceId',
|
|
'relatedResourceName',
|
|
'relatedResourceId',
|
|
'viaRelationship',
|
|
],
|
|
|
|
mixins: [HandlesValidationErrors, FormField],
|
|
|
|
components: { DeleteButton, ImageLoader },
|
|
|
|
data: () => ({
|
|
file: null,
|
|
fileName: '',
|
|
removeModalOpen: false,
|
|
missing: false,
|
|
deleted: false,
|
|
uploadErrors: new Errors(),
|
|
vaporFile: {
|
|
key: '',
|
|
uuid: '',
|
|
filename: '',
|
|
extension: '',
|
|
},
|
|
uploading: false,
|
|
uploadProgress: 0,
|
|
}),
|
|
|
|
mounted() {
|
|
this.field.fill = formData => {
|
|
let attribute = this.field.attribute
|
|
|
|
if (this.file && !this.isVaporField) {
|
|
formData.append(attribute, this.file, this.fileName)
|
|
}
|
|
|
|
if (this.file && this.isVaporField) {
|
|
formData.append(attribute, this.fileName)
|
|
formData.append('vaporFile[' + attribute + '][key]', this.vaporFile.key)
|
|
formData.append(
|
|
'vaporFile[' + attribute + '][uuid]',
|
|
this.vaporFile.uuid
|
|
)
|
|
formData.append(
|
|
'vaporFile[' + attribute + '][filename]',
|
|
this.vaporFile.filename
|
|
)
|
|
formData.append(
|
|
'vaporFile[' + attribute + '][extension]',
|
|
this.vaporFile.extension
|
|
)
|
|
}
|
|
}
|
|
},
|
|
|
|
methods: {
|
|
/**
|
|
* Respond to the file change
|
|
*/
|
|
fileChange(event) {
|
|
let path = event.target.value
|
|
let fileName = path.match(/[^\\/]*$/)[0]
|
|
this.fileName = fileName
|
|
let extension = fileName.split('.').pop()
|
|
this.file = this.$refs.fileField.files[0]
|
|
|
|
if (this.isVaporField) {
|
|
this.uploading = true
|
|
this.$emit('file-upload-started')
|
|
|
|
Vapor.store(this.$refs.fileField.files[0], {
|
|
progress: progress => {
|
|
this.uploadProgress = Math.round(progress * 100)
|
|
},
|
|
}).then(response => {
|
|
this.vaporFile.key = response.key
|
|
this.vaporFile.uuid = response.uuid
|
|
this.vaporFile.filename = fileName
|
|
this.vaporFile.extension = extension
|
|
this.uploading = false
|
|
this.uploadProgress = 0
|
|
this.$emit('file-upload-finished')
|
|
})
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Confirm removal of the linked file
|
|
*/
|
|
confirmRemoval() {
|
|
this.removeModalOpen = true
|
|
},
|
|
|
|
/**
|
|
* Close the upload removal modal
|
|
*/
|
|
closeRemoveModal() {
|
|
this.removeModalOpen = false
|
|
},
|
|
|
|
/**
|
|
* Remove the linked file from storage
|
|
*/
|
|
async removeFile() {
|
|
this.uploadErrors = new Errors()
|
|
|
|
const {
|
|
resourceName,
|
|
resourceId,
|
|
relatedResourceName,
|
|
relatedResourceId,
|
|
viaRelationship,
|
|
} = this
|
|
const attribute = this.field.attribute
|
|
|
|
const uri =
|
|
this.viaRelationship &&
|
|
this.relatedResourceName &&
|
|
this.relatedResourceId
|
|
? `/nova-api/${resourceName}/${resourceId}/${relatedResourceName}/${relatedResourceId}/field/${attribute}?viaRelationship=${viaRelationship}`
|
|
: `/nova-api/${resourceName}/${resourceId}/field/${attribute}`
|
|
|
|
try {
|
|
await Nova.request().delete(uri)
|
|
this.closeRemoveModal()
|
|
this.deleted = true
|
|
this.$emit('file-deleted')
|
|
Nova.success(this.__('The file was deleted!'))
|
|
} catch (error) {
|
|
this.closeRemoveModal()
|
|
|
|
if (error.response.status == 422) {
|
|
this.uploadErrors = new Errors(error.response.data.errors)
|
|
}
|
|
}
|
|
},
|
|
},
|
|
|
|
computed: {
|
|
/**
|
|
* 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 current label of the file field.
|
|
*/
|
|
currentLabel() {
|
|
return this.fileName || this.__('no file selected')
|
|
},
|
|
|
|
/**
|
|
* 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.field.attribute}`
|
|
},
|
|
|
|
/**
|
|
* 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.isReadonly)
|
|
},
|
|
|
|
/**
|
|
* Determine whether the field should show the remove button.
|
|
*/
|
|
shouldShowRemoveButton() {
|
|
return Boolean(this.field.deletable && !this.isReadonly)
|
|
},
|
|
|
|
/**
|
|
* Return the preview or thumbnail URL for the field.
|
|
*/
|
|
imageUrl() {
|
|
return this.field.previewUrl || this.field.thumbnailUrl
|
|
},
|
|
|
|
/**
|
|
* Determine the maximum width of the field.
|
|
*/
|
|
maxWidth() {
|
|
return this.field.maxWidth || 320
|
|
},
|
|
|
|
/**
|
|
* Determing if the field is a Vapor field.
|
|
*/
|
|
isVaporField() {
|
|
return this.field.component == 'vapor-file-field'
|
|
},
|
|
},
|
|
}
|
|
</script>
|