diff --git a/app/Livewire/FileUploader.php b/app/Livewire/FileUploader.php index c0c259b..d45d0d4 100644 --- a/app/Livewire/FileUploader.php +++ b/app/Livewire/FileUploader.php @@ -65,8 +65,17 @@ class FileUploader extends Component ]); } + /** + * Validate a freshly uploaded batch of files. + * + * Dispatches `files-processed` so the front end can drop its "uploading" state. + * This runs for every batch, including additional files added to an existing + * selection, which a one-off `x-init` on the file list cannot cover. + */ public function updatedFiles(): void { + $this->dispatch('files-processed')->self(); + $maxFileSize = (int) Setting::get('max_file_size', 100 * 1024 * 1024); $maxFileSizeMb = $maxFileSize / (1024 * 1024); $maxFilesPerShare = (int) Setting::get('max_files_per_share', 50); diff --git a/resources/views/livewire/file-uploader.blade.php b/resources/views/livewire/file-uploader.blade.php index 94d73b8..59e1c1c 100644 --- a/resources/views/livewire/file-uploader.blade.php +++ b/resources/views/livewire/file-uploader.blade.php @@ -38,16 +38,36 @@ } setTimeout(() => { + if (! files.length) { + return; + } + const dt = new DataTransfer(); const paths = []; files.forEach(f => { dt.items.add(f.file); paths.push(f.path); }); - $wire.relativePaths = paths; - $wire.uploadMultiple('files', dt.files); + + $wire.relativePaths = [...($wire.relativePaths ?? []), ...paths]; + + this.uploading = true; + this.progress = 0; + + $wire.uploadMultiple( + 'files', + dt.files, + () => this.progress = 100, + () => this.resetUpload(), + (event) => this.progress = event.detail.progress, + () => this.resetUpload(), + ); }, 500); }, + resetUpload() { + this.uploading = false; + this.progress = 0; + }, traverseEntry(entry, path, files) { if (entry.isFile) { entry.file(file => { @@ -61,10 +81,11 @@ } } }" + x-init="$wire.$on('files-processed', () => resetUpload())" x-on:livewire-upload-start="uploading = true; progress = 0" x-on:livewire-upload-finish="progress = 100" - x-on:livewire-upload-cancel="uploading = false" - x-on:livewire-upload-error="uploading = false" + x-on:livewire-upload-cancel="resetUpload()" + x-on:livewire-upload-error="resetUpload()" x-on:livewire-upload-progress="progress = $event.detail.progress" > {{-- Drop Zone --}} @@ -118,12 +139,12 @@ {{-- Errors --}} @error('files') -
{{ $message }}
+
{{ $message }}
@enderror {{-- File List --}} @if (count($files)) -
+

{{ __('Selected Files') }} ({{ count($files) }})

@foreach ($files as $index => $file) diff --git a/tests/Feature/FileUploadTest.php b/tests/Feature/FileUploadTest.php index 600bd6a..d02c097 100644 --- a/tests/Feature/FileUploadTest.php +++ b/tests/Feature/FileUploadTest.php @@ -94,6 +94,31 @@ test('file upload with max downloads sets limit', function () { expect($share->max_downloads)->toBe(5); }); +test('every upload batch dispatches files-processed to clear the uploading state', function () { + Storage::fake('shares'); + + Livewire::test(FileUploader::class) + ->set('files', [UploadedFile::fake()->create('first.txt', 64)]) + ->assertDispatched('files-processed') + ->set('files', [UploadedFile::fake()->create('second.txt', 64)]) + ->assertDispatched('files-processed'); +}); + +test('files added in multiple batches end up in the same share', function () { + Storage::fake('shares'); + + Livewire::test(FileUploader::class) + ->set('files', [UploadedFile::fake()->create('first.txt', 64)]) + ->set('files', [UploadedFile::fake()->create('second.txt', 64)]) + ->call('createShare') + ->assertHasNoErrors() + ->assertRedirectContains('/share/'); + + $share = Share::query()->first(); + + expect($share->files->pluck('original_name')->all())->toBe(['first.txt', 'second.txt']); +}); + test('file upload requires at least one file', function () { Livewire::test(FileUploader::class) ->set('files', [])