Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add FilePond integration #49

Merged
merged 8 commits into from
Apr 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@
},
"dependencies": {
"alpinejs": "^3.13.3",
"filepond": "^4.30.6",
"filepond-plugin-file-validate-size": "^2.2.8",
"filepond-plugin-file-validate-type": "^1.2.9",
"filepond-plugin-image-preview": "^4.6.12",
"filepond-plugin-image-validate-size": "^1.2.7",
"marked": "^4.0.10",
"underscore": "~1.13.2",
"uniqid": "^5.2.0"
Expand Down
125 changes: 82 additions & 43 deletions resources/dist/js/livewire-forms.js

Large diffs are not rendered by default.

113 changes: 113 additions & 0 deletions resources/js/alpine/filepond.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import * as FilePond from 'filepond';
import FilePondPluginFileValidateSize from 'filepond-plugin-file-validate-size';
import FilePondPluginFileValidateType from 'filepond-plugin-file-validate-type';
import FilePondPluginImagePreview from 'filepond-plugin-image-preview';
import FilePondPluginImageValidateSize from 'filepond-plugin-image-validate-size';
import ar_AR from 'filepond/locale/ar-ar';
import az_AZ from 'filepond/locale/az-az';
import cs_CZ from 'filepond/locale/cs-cz';
import da_DK from 'filepond/locale/da-dk';
import de_DE from 'filepond/locale/de-de';
import el_EL from 'filepond/locale/el-el';
import en_EN from 'filepond/locale/en-en';
import es_ES from 'filepond/locale/es-es';
import fa_IR from 'filepond/locale/fa_ir';
import fi_FI from 'filepond/locale/fi-fi';
import fr_FR from 'filepond/locale/fr-fr';
import he_HE from 'filepond/locale/he-he';
import hr_HR from 'filepond/locale/hr-hr';
import hu_HU from 'filepond/locale/hu-hu';
import id_ID from 'filepond/locale/id-id';
import it_IT from 'filepond/locale/it-it';
import ja_JA from 'filepond/locale/ja-ja';
import km_KM from 'filepond/locale/km-km';
import lt_LT from 'filepond/locale/lt-lt';
import nl_NL from 'filepond/locale/nl-nl';
import no_NB from 'filepond/locale/no_nb';
import pl_PL from 'filepond/locale/pl-pl';
import pt_BR from 'filepond/locale/pt-br';
import pt_PT from 'filepond/locale/pt-pt';
import ro_RO from 'filepond/locale/ro-ro';
import ru_RU from 'filepond/locale/ru-ru';
import sk_SK from 'filepond/locale/sk-sk';
import sv_SE from 'filepond/locale/sv_se';
import tr_TR from 'filepond/locale/tr-tr';
import uk_UA from 'filepond/locale/uk-ua';
import vi_VI from 'filepond/locale/vi-vi';
import zh_CN from 'filepond/locale/zh-cn';
import zh_TW from 'filepond/locale/zh-tw';

const locales = {
'ar-ar': ar_AR,
'az-az': az_AZ,
'cs-cz': cs_CZ,
'da-dk': da_DK,
'de-de': de_DE,
'el-el': el_EL,
'en-en': en_EN,
'es-es': es_ES,
'fa-ir': fa_IR,
'fi-fi': fi_FI,
'fr-fr': fr_FR,
'he-he': he_HE,
'hr-hr': hr_HR,
'hu-hu': hu_HU,
'id-id': id_ID,
'it-it': it_IT,
'ja-ja': ja_JA,
'km-km': km_KM,
'lt-lt': lt_LT,
'nl-nl': nl_NL,
'no-nb': no_NB,
'pl-pl': pl_PL,
'pt-br': pt_BR,
'pt-pt': pt_PT,
'ro-ro': ro_RO,
'ru-ru': ru_RU,
'sk-sk': sk_SK,
'sv-se': sv_SE,
'tr-tr': tr_TR,
'uk-ua': uk_UA,
'vi-vi': vi_VI,
'zh-cn': zh_CN,
'zh-tw': zh_TW,
};

export default (config) => ({
init() {
FilePond.registerPlugin(FilePondPluginFileValidateSize);
FilePond.registerPlugin(FilePondPluginFileValidateType);
FilePond.registerPlugin(FilePondPluginImagePreview);
FilePond.registerPlugin(FilePondPluginImageValidateSize);

const field = this.$wire.fields[config.field].properties;

FilePond.create(this.$refs.input, {
allowMultiple: field.multiple,
minFileSize: field.file_size.min ? `${field.file_size.min}KB` : null,
maxFileSize: field.file_size.max ? `${field.file_size.max}KB` : null,
acceptedFileTypes: field.file_types,
imageValidateSizeMinWidth: field.dimensions.min_width ?? 1,
imageValidateSizeMinHeight: field.dimensions.min_height ?? 1,
imageValidateSizeMaxWidth: field.dimensions.max_width ?? 65535,
imageValidateSizeMaxHeight: field.dimensions.max_height ?? 65535,
credits: false,
server: {
process: (fieldName, file, metadata, load, error, progress, abort, transfer, options) => {
this.$wire.upload(field.key, file, load, error, progress)
},
revert: (filename, load) => {
this.$wire.removeUpload(field.key, filename, load)
},
},
...locales[config.locale],
});
},

reset(livewireId) {
// Only reset the FilePond instance if the form-success event was fired by the same Livewire component
if (livewireId !== this.$wire.id) return;

FilePond.find(this.$el.querySelector('.filepond--root')).removeFiles();
}
})
2 changes: 1 addition & 1 deletion resources/js/form.js → resources/js/alpine/form.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import FieldConditions from '../../vendor/statamic/cms/resources/js/frontend/components/FieldConditions.js';
import FieldConditions from '../../../vendor/statamic/cms/resources/js/frontend/components/FieldConditions.js';

export default () => ({
fields: {},
Expand Down
4 changes: 3 additions & 1 deletion resources/js/livewire-forms.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import form from './form.js'
import filepond from './alpine/filepond.js'
import form from './alpine/form.js'

Alpine.data('filepond', filepond)
Alpine.data('form', form)
35 changes: 18 additions & 17 deletions resources/views/default/fields/assets.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,35 +6,36 @@
@endif
</div>

<input
<div
id="{{ $field->id }}"
name="{{ $field->id }}"
type="file"

@if($field->multiple)
multiple
@endif

@if($field->wire_model)
wire:model.{{ $field->wire_model }}="{{ $field->key }}"
@else
wire:model="{{ $field->key }}"
@endif

@if(! $errors->has($field->key))
class="block w-full"
@if($field->instructions)
aria-describedby="{{ $field->id }}-instructions"
@endif
@else
class="block w-full text-red-900"
aria-invalid="true"
aria-describedby="{{ $field->id }}-error"
@endif
/>
>
<div
x-data="filepond({
field: '{{ $field->handle }}',
locale: @antlers'{{ site:attributes:filepond_locale ?? 'en-en' }}'@endantlers,
})"
x-on:form-success.window="reset($event.detail.id)"
wire:ignore
>
<input type="file" x-ref="input" />
</div>
</div>

@if($errors->has($field->key))
@formView('messages.error')
@elseif($field->instructions && $field->instructions_position === 'below')
@formView('messages.instructions')
@endif

@assets
<link href="https://unpkg.com/filepond@^4/dist/filepond.css" rel="stylesheet" />
<link href="https://unpkg.com/filepond-plugin-image-preview/dist/filepond-plugin-image-preview.css" rel="stylesheet" />
@endassets
63 changes: 45 additions & 18 deletions src/Fields/Assets.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,15 @@

namespace Aerni\LivewireForms\Fields;

use Illuminate\Support\Arr;
use Illuminate\Support\Str;
use Statamic\Fieldtypes\Assets\DimensionsRule;
use Statamic\Fieldtypes\Assets\ImageRule;
use Statamic\Fieldtypes\Assets\MaxRule;
use Statamic\Fieldtypes\Assets\MimesRule;
use Statamic\Fieldtypes\Assets\MimetypesRule;
use Statamic\Fieldtypes\Assets\MinRule;
use Statamic\Forms\Uploaders\AssetsUploader;
use Symfony\Component\Mime\MimeTypes;

class Assets extends Field
{
Expand All @@ -14,32 +21,52 @@ protected function multipleProperty(?bool $multiple = null): bool
return $multiple ?? $this->field->get('max_files') !== 1;
}

protected function defaultProperty(mixed $default = null): ?array
protected function fileSizeProperty(): array
{
return $this->multiple ? [] : null;
}
$rules = collect($this->rules)->flatten();

protected function rulesProperty(string|array|null $rules = null): array
{
$rules = parent::rulesProperty($rules);
$minFileSize = $rules->whereInstanceOf(MinRule::class)
->flatMap(fn ($rule) => invade($rule)->parameters)
->first();

if ($this->multiple) {
return $rules;
}
$maxFileSize = $rules->whereInstanceOf(MaxRule::class)
->flatMap(fn ($rule) => invade($rule)->parameters)
->first();

/* Remove validation rules that would only work if multiple is enabled */
$rules = collect(array_first($rules))
->filter(fn ($rule) => $rule !== 'array')
->filter(fn ($rule) => $rule !== "min:{$this->min_files}")
->filter(fn ($rule) => $rule !== "max:{$this->max_files}")
->values()->all();
return [
'min' => $minFileSize,
'max' => $maxFileSize,
];
}

return [$this->key => $rules];
protected function fileTypesProperty(): array
{
return collect($this->rules)
->flatten()
->map(fn ($rule) => match (true) {
$rule instanceof ImageRule => ['image/*'],
$rule instanceof MimetypesRule => invade($rule)->parameters,
$rule instanceof MimesRule => collect(invade($rule)->parameters)
->flatMap(fn ($mime) => (new MimeTypes)->getMimeTypes($mime)),
default => null,
})
->filter()
->first() ?? [];
}

protected function dimensionsProperty(): array
{
return collect($this->rules)
->flatten()
->whereInstanceOf(DimensionsRule::class)
->flatMap(fn ($rule) => invade($rule)->parameters)
->mapWithKeys(fn ($dimension) => [Str::before($dimension, '=') => Str::after($dimension, '=')])
->toArray();
}

public function process(): mixed
{
$this->value = collect(Arr::wrap($this->value))
$this->value = collect($this->value)
->flatMap(fn ($file) => AssetsUploader::field($this->handle)->upload($file));

return parent::process();
Expand Down
2 changes: 1 addition & 1 deletion src/Fields/Field.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ abstract class Field implements Arrayable
use HandlesProperties;
use WithDefaultProperties;

protected mixed $value = null;
public mixed $value = null;

public function __construct(protected FormField $field)
{
Expand Down
7 changes: 5 additions & 2 deletions src/Livewire/Concerns/HandlesSuccess.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,17 @@ protected function handleSuccess(): self
{
session()->flash('success', $this->successMessage());

// Get the captcha value before we are resetting the field values.
/* Get the captcha value before we are resetting the field values. */
$captcha = $this->captcha()?->value();

$this->resetValues();

// Preserve the captcha state by setting the value to its previous state.
/* Preserve the captcha state by setting the value to its previous state. */
$this->captcha()?->value($captcha);

/* Dispatch event so we can reset the FilePond field. */
$this->dispatch('form-success', id: $this->getId());

return $this;
}
}
29 changes: 29 additions & 0 deletions src/Livewire/Synthesizers/RuleSynth.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

namespace Aerni\LivewireForms\Livewire\Synthesizers;

use Illuminate\Contracts\Validation\Rule;
use Livewire\Mechanisms\HandleComponents\Synthesizers\Synth;

class RuleSynth extends Synth
{
public static $key = 'rule';

public static function match($target)
{
return $target instanceof Rule;
}

public function dehydrate($target)
{
return [
invade($target)->parameters,
['class' => get_class($target)],
];
}

public function hydrate($value, $meta)
{
return new $meta['class']($value);
}
}
Loading
Loading