From f615289569209488ff984ae093e175e1ec27129d Mon Sep 17 00:00:00 2001 From: Dasun Tharanga Date: Sun, 15 Dec 2024 22:24:44 +0530 Subject: [PATCH] feat: add support for chunked uploads --- config/livewire-dropzone.php | 15 +++ resources/views/livewire/dropzone.blade.php | 104 +++++++++++++------- src/Http/Livewire/Dropzone.php | 63 ++++++++---- src/LivewireDropzoneServiceProvider.php | 1 + tests/Feature/DropzoneTest.php | 28 +++++- workbench/resources/views/welcome.blade.php | 2 +- 6 files changed, 158 insertions(+), 55 deletions(-) create mode 100644 config/livewire-dropzone.php diff --git a/config/livewire-dropzone.php b/config/livewire-dropzone.php new file mode 100644 index 00000000..d956c8eb --- /dev/null +++ b/config/livewire-dropzone.php @@ -0,0 +1,15 @@ + env('LIVEWIRE_DROPZONE_CHUNK_SIZE', 1024 * 1024 * 5), // 5MB +]; diff --git a/resources/views/livewire/dropzone.blade.php b/resources/views/livewire/dropzone.blade.php index 8caa4275..c33da09c 100644 --- a/resources/views/livewire/dropzone.blade.php +++ b/resources/views/livewire/dropzone.blade.php @@ -3,11 +3,9 @@ x-data="dropzone({ _this: @this, uuid: @js($uuid), - multiple: @js($multiple), })" - @dragenter.prevent.document="onDragenter($event)" - @dragleave.prevent="onDragleave($event)" - @dragover.prevent="onDragover($event)" + @dragleave.prevent="isDragging = false" + @dragover.prevent="isDragging = true" @drop.prevent="onDrop" class="block antialiased" > @@ -41,13 +39,13 @@ class="block antialiased" accept)) accept="{{ $this->accept }}" @endif @if($multiple === true) multiple @endif > @@ -118,43 +116,32 @@ class="hidden" @script diff --git a/src/Http/Livewire/Dropzone.php b/src/Http/Livewire/Dropzone.php index dc6fdd2e..bc3d8ca1 100644 --- a/src/Http/Livewire/Dropzone.php +++ b/src/Http/Livewire/Dropzone.php @@ -3,6 +3,9 @@ namespace Dasundev\LivewireDropzone\Http\Livewire; use Illuminate\Contracts\View\View; +use Illuminate\Http\UploadedFile; +use Illuminate\Support\Facades\File; +use Illuminate\Support\Facades\Storage; use Illuminate\Support\Str; use Illuminate\Validation\ValidationException; use Livewire\Attributes\Computed; @@ -10,6 +13,7 @@ use Livewire\Attributes\Modelable; use Livewire\Attributes\On; use Livewire\Component; +use Livewire\Features\SupportFileUploads\FileUploadConfiguration; use Livewire\Features\SupportFileUploads\TemporaryUploadedFile; use Livewire\WithFileUploads; @@ -26,18 +30,20 @@ class Dropzone extends Component #[Locked] public string $uuid; - public $upload; - public string $error; public bool $multiple; + public $chunk; + + public $chunks = []; + + public $file; + public function rules(): array { - $field = $this->multiple ? 'upload.*' : 'upload'; - return [ - $field => [...$this->rules], + 'file' => [...$this->rules], ]; } @@ -49,9 +55,31 @@ public function mount(array $rules = [], bool $multiple = false): void $this->files = []; } - public function updatedUpload(): void + /** + * Called after updating the chunk property. + */ + public function updatedChunk($value): void + { + $this->chunks[] = $value; + } + + /** + * Merge uploaded file chunks into a single file. + * + * @throws \Livewire\Features\SupportFileUploads\FileNotPreviewableException + */ + public function mergeChunks(): void { - $this->reset('error'); + $disk = FileUploadConfiguration::disk(); + $path = null; + + foreach ($this->chunks as $chunk) { + $path = Storage::disk($disk)->putFileAs('/'.FileUploadConfiguration::path(), $chunk, TemporaryUploadedFile::generateHashNameWithOriginalNameEmbedded(UploadedFile::fake()->create(preg_replace('/\.\d+\.part/', '', $chunk->getClientOriginalName())))); + } + + $path = File::basename($path); + + $this->file = TemporaryUploadedFile::createFromLivewire($path); try { $this->validate(); @@ -62,21 +90,17 @@ public function updatedUpload(): void return; } - $this->upload = $this->multiple - ? $this->upload - : [$this->upload]; - - foreach ($this->upload as $upload) { - $this->handleUpload($upload); - } + $this->dispatchTempFileAddedEvent($this->file); - $this->reset('upload'); + $this->reset('chunks', 'error'); } /** - * Handle the uploaded file and dispatch an event with file details. + * Dispatch an event with the details of the uploaded temporary file. + * + * @throws \Livewire\Features\SupportFileUploads\FileNotPreviewableException */ - public function handleUpload(TemporaryUploadedFile $file): void + public function dispatchTempFileAddedEvent(TemporaryUploadedFile $file): void { $this->dispatch("{$this->uuid}:fileAdded", [ 'tmpFilename' => $file->getFilename(), @@ -161,6 +185,7 @@ public function maxFileSize(): ?string /** * Checks if the provided MIME type corresponds to an image. */ + #[Computed] public function isImageMime($mime): bool { return in_array($mime, ['png', 'gif', 'bmp', 'svg', 'jpeg', 'jpg']); @@ -168,6 +193,8 @@ public function isImageMime($mime): bool public function render(): View { - return view('livewire-dropzone::livewire.dropzone'); + return view('livewire-dropzone::livewire.dropzone', [ + 'chunkSize' => config('livewire-dropzone.chunk_size'), + ]); } } diff --git a/src/LivewireDropzoneServiceProvider.php b/src/LivewireDropzoneServiceProvider.php index b8eee56f..9efff51e 100644 --- a/src/LivewireDropzoneServiceProvider.php +++ b/src/LivewireDropzoneServiceProvider.php @@ -13,6 +13,7 @@ public function configurePackage(Package $package): void { $package ->name('livewire-dropzone') + ->hasConfigFile() ->hasViews(); } diff --git a/tests/Feature/DropzoneTest.php b/tests/Feature/DropzoneTest.php index f488cb49..3340fa09 100644 --- a/tests/Feature/DropzoneTest.php +++ b/tests/Feature/DropzoneTest.php @@ -18,12 +18,34 @@ ->assertSet('multiple', true); }); -it('can upload file', function () { - $dropzone = Livewire\Livewire::test(Dropzone::class); +it('can upload valid file', function () { + $dropzone = Livewire\Livewire::test(Dropzone::class, [ + 'rules' => ['mimes:pdf'], + ]); $uuid = $dropzone->get('uuid'); + // valid chunk + $chunk = 'foo.pdf.1.part'; + $dropzone - ->set('upload', UploadedFile::fake()->image('foo.png')) + ->set('chunk', UploadedFile::fake()->create($chunk)) + ->call('mergeChunks') ->assertDispatched("$uuid:fileAdded"); }); + +it('can not upload invalid file', function () { + $dropzone = Livewire\Livewire::test(Dropzone::class, [ + 'rules' => ['mimes:pdf'], + ]); + + $uuid = $dropzone->get('uuid'); + + // invalid chunk + $chunk = 'foo.png.1.part'; + + $dropzone + ->set('chunk', UploadedFile::fake()->create($chunk)) + ->call('mergeChunks') + ->assertNotDispatched("$uuid:fileAdded"); +}); diff --git a/workbench/resources/views/welcome.blade.php b/workbench/resources/views/welcome.blade.php index 62ca9ec3..41b05982 100644 --- a/workbench/resources/views/welcome.blade.php +++ b/workbench/resources/views/welcome.blade.php @@ -1,6 +1,6 @@
\ No newline at end of file