Allow multiple file imports into vaults

This commit is contained in:
brufdev
2025-09-09 19:47:51 +01:00
parent 78d2bca252
commit 2125c64db1
3 changed files with 42 additions and 29 deletions
+25 -16
View File
@@ -26,8 +26,11 @@ final class ImportFile extends Component
public VaultNode $parent;
#[Validate('required|file')]
public ?TemporaryUploadedFile $file = null;
/**
* @var array<int, TemporaryUploadedFile> $files
*/
#[Validate(['files.*' => 'required|file'])]
public array $files = [];
public string $fileMimes;
@@ -55,29 +58,35 @@ final class ImportFile extends Component
$this->openModal();
}
public function updatedFile(): void
public function updatedFiles(): void
{
$this->validate();
/** @var TemporaryUploadedFile $file */
$file = $this->file;
$fileExtension = $file->getClientOriginalExtension();
$fileMimeType = $file->getMimeType();
$fileName = $file->getClientOriginalName();
$filePath = $file->getRealPath();
$filesImported = 0;
if (!VaultFile::validate($fileExtension, $fileMimeType)) {
$this->closeModal();
foreach ($this->files as $file) {
$fileExtension = $file->getClientOriginalExtension();
$fileMimeType = $file->getMimeType();
$fileName = $file->getClientOriginalName();
$filePath = $file->getRealPath();
$this->dispatch('toast', message: __('File not supported'), type: 'error');
if (!VaultFile::validate($fileExtension, $fileMimeType)) {
continue;
}
new ProcessImportedFile()->handle($this->vault, $this->parent, $fileName, $filePath);
$filesImported++;
}
$this->closeModal();
if ($filesImported === 0) {
$this->dispatch('toast', message: __('No files imported'), type: 'error');
return;
}
new ProcessImportedFile()->handle($this->vault, $this->parent, $fileName, $filePath);
$this->closeModal();
$this->dispatch('toast', message: __('File imported'), type: 'success');
$this->dispatch('toast', message: __('Files imported'), type: 'success');
broadcast(new VaultFileSystemUpdatedEvent($this->vault));
}
@@ -11,14 +11,14 @@
x-on:livewire-upload-progress="progress = $event.detail.progress"
>
<label
for="file-upload"
for="files-upload"
class="flex flex-col items-center justify-center w-full h-full gap-2 text-base font-medium cursor-pointer"
>
<h6 class="font-semibold">{{ __('Browse file to import') }}</h6>
<h6 class="font-semibold">{{ __('Browse files to import') }}</h6>
<span class="text-sm">{{ __('Image, video, audio, note or pdf file') }}</span>
<span class="text-sm">{{ __('Max size ') . ini_get('upload_max_filesize') }}</span>
@error('file')
@error('files.*')
<p class="text-sm text-center text-error-500" aria-live="assertive">{{ $message }}</p>
@enderror
@@ -28,7 +28,7 @@
</div>
</label>
<input type="file" id="file-upload" class="hidden" wire:model="file" accept="{{ $fileMimes }}" />
<input type="file" id="files-upload" class="hidden" wire:model="files" accept="{{ $fileMimes }}" multiple />
</div>
</x-form>
</x-modal.panel>
+13 -9
View File
@@ -42,7 +42,7 @@ it('does not accept passing a file as parent node', function (): void {
->assertStatus(400);
});
it('saves an imported file to both the database and the disk', function (): void {
it('saves imported files to both the database and the disk', function (): void {
$user = User::factory()->create()->first();
$vault = new CreateVault()->handle($user, [
'name' => fake()->words(3, true),
@@ -52,16 +52,20 @@ it('saves an imported file to both the database and the disk', function (): void
'name' => fake()->words(3, true),
]);
$content = fake()->paragraph();
$file = UploadedFile::fake()->createWithContent('note.md', $content);
$file1 = UploadedFile::fake()->createWithContent('note1.md', $content);
$file2 = UploadedFile::fake()->createWithContent('note2.md', $content);
Livewire::actingAs($user)
->test(ImportFile::class, ['vault' => $vault])
->assertSet('show', false)
->call('open', $node)
->set('file', $file)
->set('files', [$file1, $file2])
->assertSet('show', false);
$path = new GetPathFromVaultNode()->handle($node) . '/' . $file->name;
$path = new GetPathFromVaultNode()->handle($node) . '/' . $file1->name;
expect(Storage::disk('local')->exists($path))->toBeTrue();
expect(Storage::disk('local')->get($path))->toBe($content);
$path = new GetPathFromVaultNode()->handle($node) . '/' . $file2->name;
expect(Storage::disk('local')->exists($path))->toBeTrue();
expect(Storage::disk('local')->get($path))->toBe($content);
});
@@ -87,7 +91,7 @@ it('handles name collisions when importing a file with an existing name', functi
Livewire::actingAs($user)
->test(ImportFile::class, ['vault' => $vault])
->call('open', $folderNode)
->set('file', $file);
->set('files', $file);
$nodes = $vault->nodes()->get();
expect($nodes->count())->toBe(3);
@@ -122,7 +126,7 @@ it('handles name collisions when importing a file with a name existing in multip
Livewire::actingAs($user)
->test(ImportFile::class, ['vault' => $vault])
->call('open')
->set('file', $file);
->set('files', $file);
$nodes = $vault->nodes()->get();
expect($nodes->count())->toBe(3);
@@ -148,7 +152,7 @@ it('does not import a file with a non-allowed extension', function (): void {
->test(ImportFile::class, ['vault' => $vault])
->assertSet('show', false)
->call('open')
->set('file', $file)
->set('files', $file)
->assertDispatched('toast', type: 'error');
});
@@ -169,7 +173,7 @@ it('creates links when importing a file', function (): void {
Livewire::actingAs($user)
->test(ImportFile::class, ['vault' => $vault])
->call('open')
->set('file', $file);
->set('files', $file);
expect($vault->nodes()->get()->get(1)->links()->count())->toBe(1);
});
@@ -185,7 +189,7 @@ it('creates tags when importing a file', function (): void {
Livewire::actingAs($user)
->test(ImportFile::class, ['vault' => $vault])
->call('open')
->set('file', $file);
->set('files', $file);
expect($vault->nodes()->first()->tags()->count())->toBe(2);
});