Skip to content
This repository has been archived by the owner on Apr 8, 2024. It is now read-only.

Commit

Permalink
Refactoring, add file field
Browse files Browse the repository at this point in the history
  • Loading branch information
jameslkingsley committed Sep 18, 2018
1 parent b41d065 commit c210752
Show file tree
Hide file tree
Showing 11 changed files with 402 additions and 18 deletions.
32 changes: 27 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,41 @@
# Laravel Nova Media Library
This package is designed to be used with the [awesome media library package from Spatie](https://github.com/spatie/laravel-medialibrary). With this package you can add an image field for uploading single files to a resource, and add an images field to resources to display all of their associated media.

This package is designed to be used with the [awesome media library package from Spatie](https://github.com/spatie/laravel-medialibrary). With this package you can add an image field for uploading single files to a resource, a generic file field for other types, and add an images field to resources to display all of their associated media.

```php
use Kingsley\NovaMediaLibrary\Fields\Image;

Image::make('Avatar')
Image::make('Avatar', 'avatar')
->usingConversion('thumb')
->preservingOriginal()
->toMediaCollection('avatar')
```

In this example we're defining a field called `Avatar` that uses the `thumb` conversion as the image to be displayed (on detail and index). The other methods called are **dynamically** applied to the upload request - **this lets you call any media-library method on the field.**.
In this example we're defining a field called `Avatar` that uses the `avatar` collection. It's also calling `usingConversion` to set the `thumb` conversion as the image to be displayed (on detail and index). The other methods called are **dynamically** applied to the upload request - **this lets you call any media-library method on the field.**.

If you want it to remove the old image before uploading the new one, be sure to make your model's media collection a [single file collection](https://docs.spatie.be/laravel-medialibrary/v7/working-with-media-collections/defining-media-collections#single-file-collections).

When you want to upload a file that isn't an image, you can use the rudimentary `File` field included with this package. It follows the same format as registering an `Image` field.

```php
use Kingsley\NovaMediaLibrary\Fields\File;

File::make('Invoice', 'invoice')
```

That's all there is to it! The rest of the configuration should be done in the media collection itself, such as:

```php
public function registerMediaCollections()
{
$this
->addMediaCollection('invoice')
->singleFile()
->acceptsFile(function (File $file) {
return $file->mimeType === 'application/pdf';
});
}
```

To show all media records for your resource simply add the `Images` field like so:

```php
Expand All @@ -23,7 +45,6 @@ public function fields(Request $request)
{
return [
...

Images::make(),
];
}
Expand All @@ -34,6 +55,7 @@ This will automatically use the `media` attribute on your model (which the `HasM
**Note: You currently cannot create media directly from Nova.**

#### Registering the media resource

If you'd like to use the media resource included with this package, you need to register it manually in your `NovaServiceProvider` in the `boot` method.

```php
Expand Down
2 changes: 1 addition & 1 deletion dist/js/app.js

Large diffs are not rendered by default.

10 changes: 7 additions & 3 deletions resources/js/app.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
Nova.booting((Vue, router) => {
Vue.component('index-nova-media-library-image-field', require('./components/IndexField'))
Vue.component('detail-nova-media-library-image-field', require('./components/DetailField'))
Vue.component('form-nova-media-library-image-field', require('./components/FormField'))
Vue.component('index-nova-media-library-image-field', require('./components/Image/IndexField'))
Vue.component('detail-nova-media-library-image-field', require('./components/Image/DetailField'))
Vue.component('form-nova-media-library-image-field', require('./components/Image/FormField'))

Vue.component('index-nova-media-library-file-field', require('./components/File/IndexField'))
Vue.component('detail-nova-media-library-file-field', require('./components/File/DetailField'))
Vue.component('form-nova-media-library-file-field', require('./components/File/FormField'))
})
14 changes: 14 additions & 0 deletions resources/js/components/File/DetailField.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<template>
<panel-item :field="field">
<div slot="value">
<span v-if="field.value">{{ field.value.name }}</span>
<span v-else>&mdash;</span>
</div>
</panel-item>
</template>

<script>
export default {
props: ['resource', 'resourceName', 'resourceId', 'field'],
}
</script>
212 changes: 212 additions & 0 deletions resources/js/components/File/FormField.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
<template>
<default-field :field="field">
<template slot="field">
<div v-if="hasValue" class="mb-6">
<template v-if="shouldShowLoader">
<ImageLoader :src="field.thumbnailUrl" class="max-w-xs" @missing="(value) => missing = value" />
</template>

<template v-if="field.value && !field.thumbnailUrl">
<div class="flex item-center relative overflow-hidden">
<span v-if="field.value">{{ field.value.name }}</span>

<DeleteButton
:dusk="field.attribute + '-internal-delete-link'"
class="ml-auto"
v-if="shouldShowRemoveButton"
@click="confirmRemoval"
/>
</div>
</template>

<p
v-if="field.thumbnailUrl"
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">
<transition name="fade">
<confirm-upload-removal-modal
v-if="removeModalOpen"
@confirm="removeFile"
@close="closeRemoveModal"
/>
</transition>
</portal>
</div>

<span class="form-file mr-4">
<input
ref="fileField"
:dusk="field.attribute"
class="form-file-input"
type="file"
:id="idAttr"
name="name"
@change="fileChange"
/>
<label :for="labelFor" class="form-file-btn btn btn-default btn-primary">
{{__('Choose File')}}
</label>
</span>

<span class="text-gray-50">
{{ currentLabel }}
</span>

<p v-if="hasError" class="mt-4 text-danger">
{{ firstError }}
</p>
</template>
</default-field>
</template>

<script>
import DeleteButton from '../../../../../../nova/resources/js/components/DeleteButton'
import { FormField, HandlesValidationErrors, Errors } from 'laravel-nova'
export default {
components: { DeleteButton },
mixins: [HandlesValidationErrors, FormField],
props: ['resourceId', 'relatedResourceName', 'relatedResourceId', 'viaRelationship', 'resourceName', 'field'],
data: () => ({
file: null,
label: 'No file selected',
fileName: '',
removeModalOpen: false,
missing: false,
deleted: false,
uploadErrors: new Errors(),
}),
mounted() {
this.field.fill = formData => {
formData.append(this.field.attribute, this.file, this.fileName)
for (let attribute in this.field) {
if (attribute.substr(0, 3) === 'ml_') {
formData.append(attribute, this.field[attribute])
}
}
}
},
methods: {
/**
* Responsd to the file change
*/
fileChange(event) {
let path = event.target.value
let fileName = path.match(/[^\\/]*$/)[0]
this.fileName = fileName
this.file = this.$refs.fileField.files[0]
},
/**
* 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 = `/nova-vendor/jameslkingsley/nova-media-library/${this.field.value.id}`
try {
await Nova.request().delete(uri)
this.closeRemoveModal()
this.deleted = true
this.$emit('file-deleted')
} catch (error) {
this.closeRemoveModal()
if (error.response.status == 422) {
this.uploadErrors = new Errors(error.response.data.errors)
}
}
},
},
computed: {
hasError() {
return this.errors.has(this.fieldAttribute) || this.uploadErrors.has(this.fieldAttribute)
},
firstError() {
if (this.hasError) {
return this.errors.first(this.fieldAttribute) || this.uploadErrors.first(this.fieldAttribute)
}
},
/**
* The current label of the file field
*/
currentLabel() {
return this.fileName || this.label
},
/**
* The ID attribute to use for the file field
*/
idAttr() {
return this.labelFor
},
/**
* The label attribute to use for the file field
* @return {[type]} [description]
*/
labelFor() {
return `file-${this.field.attribute}`
},
/**
* Determine whether the field has a value
*/
hasValue() {
return (
Boolean(this.field.value || this.field.thumbnailUrl) && !Boolean(this.deleted) && !Boolean(this.missing)
)
},
/**
* Determine whether the field should show the loader component
*/
shouldShowLoader() {
return !Boolean(this.deleted) && Boolean(this.field.thumbnailUrl)
},
/**
* Determine whether the field should show the remove button
*/
shouldShowRemoveButton() {
return Boolean(this.field.deletable)
},
},
}
</script>
13 changes: 13 additions & 0 deletions resources/js/components/File/IndexField.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<template>
<span v-if="field.value">
{{ field.value.name }}
</span>

<span v-else>&mdash;</span>
</template>

<script>
export default {
props: ['resourceName', 'field'],
}
</script>
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,8 @@
</template>

<script>
import ImageLoader from '../../../../../nova/resources/js/components/ImageLoader'
import DeleteButton from '../../../../../nova/resources/js/components/DeleteButton'
import ImageLoader from '../../../../../../nova/resources/js/components/ImageLoader'
import DeleteButton from '../../../../../../nova/resources/js/components/DeleteButton'
import { FormField, HandlesValidationErrors, Errors } from 'laravel-nova'
export default {
Expand Down
Loading

0 comments on commit c210752

Please sign in to comment.