diff --git a/src/Providers/BladeServiceProvider.php b/src/Providers/BladeServiceProvider.php index 4446b9c..d054379 100644 --- a/src/Providers/BladeServiceProvider.php +++ b/src/Providers/BladeServiceProvider.php @@ -85,6 +85,7 @@ public function bootComponents(): void Blade::component($prefix . 'form.date', Components\Forms\Inputs\Date::class); Blade::component($prefix . 'form.datetime', Components\Forms\Inputs\Datetime::class); Blade::component($prefix . 'form.input', Components\Forms\Inputs\Input::class); + Blade::component($prefix . 'form.image-library', Components\Forms\Inputs\ImageLibrary::class); Blade::component($prefix . 'form.password', Components\Forms\Inputs\Password::class); Blade::component($prefix . 'form.plaintext', Components\Forms\Inputs\Plaintext::class); Blade::component($prefix . 'form.radio', Components\Forms\Inputs\Radio::class); diff --git a/src/Traits/WithMediaSync.php b/src/Traits/WithMediaSync.php new file mode 100644 index 0000000..29a8b4b --- /dev/null +++ b/src/Traits/WithMediaSync.php @@ -0,0 +1,100 @@ +{$library} = $this->{$library}->filter(fn($image) => $image['uuid'] != $uuid); + + // Remove file + $name = str($url)->after('preview-file/')->before('?expires')->toString(); + $this->{$filesModelName} = collect($this->{$filesModelName})->filter(fn($file) => $file->getFilename() != $name)->all(); + } + + // Set order + public function refreshMediaOrder(array $order, string $library): void + { + $this->{$library} = $this->{$library}->sortBy(function ($item) use ($order) { + return array_search($item['uuid'], $order); + }); + } + + // Bind temporary files with respective previews and replace existing ones, if necessary + public function refreshMediaSources(string $filesModelName, string $library) + { + // New files area + foreach ($this->{$filesModelName}['*'] ?? [] as $key => $file) { + $this->{$library} = $this->{$library}->add(['uuid' => Str::uuid()->toString(), 'url' => $file->temporaryUrl()]); + + $key = $this->{$library}->keys()->last(); + $this->{$filesModelName}[$key] = $file; + } + + // Reset new files area + unset($this->{$filesModelName}['*']); + + //Replace existing files + foreach ($this->{$filesModelName} as $key => $file) { + $media = $this->{$library}->get($key); + $media['url'] = $file->temporaryUrl(); + + $this->{$library} = $this->{$library}->replace([$key => $media]); + } + + $this->validateOnly($filesModelName . '.*'); + } + + // Storage files into permanent area and updates the model with fresh sources + public function syncMedia( + Model $model, + string $library = 'library', + string $files = 'files', + string $storage_subpath = '', + $model_field = 'library', + string $visibility = 'public', + string $disk = 'public' + ): void { + // Store files + foreach ($this->{$files} as $index => $file) { + $media = $this->{$library}->get($index); + $name = $this->getFileName($media); + + $file = Storage::disk($disk)->putFileAs($storage_subpath, $file, $name, $visibility); + $url = Storage::disk($disk)->url($file); + + // Update library + $media['url'] = $url . "?updated_at=" . time(); + $media['path'] = str($storage_subpath)->finish('/')->append($name)->toString(); + $this->{$library} = $this->{$library}->replace([$index => $media]); + } + + // Delete removed files from library + $diffs = $model->{$model_field}?->filter(fn($item) => $this->{$library}->doesntContain('uuid', $item['uuid'])) ?? []; + + foreach ($diffs as $diff) { + Storage::disk($disk)->delete($diff['path']); + } + + // Updates model + $model->update([$model_field => $this->{$library}]); + + // Resets files + $this->{$files} = []; + } + + private function getFileName(?array $media): ?string + { + $name = $media['uuid'] ?? null; + $extension = str($media['url'] ?? null)->afterLast('.')->before('?expires')->toString(); + + return "$name.$extension"; + } +} diff --git a/src/View/Components/Forms/Inputs/ImageLibrary.php b/src/View/Components/Forms/Inputs/ImageLibrary.php new file mode 100644 index 0000000..7970c43 --- /dev/null +++ b/src/View/Components/Forms/Inputs/ImageLibrary.php @@ -0,0 +1,279 @@ +uuid = "mary" . md5(serialize($this)); + } + + public function modelName(): ?string + { + return $this->attributes->wire('model'); + } + + public function libraryName(): ?string + { + return $this->attributes->wire('library'); + } + + public function validationMessage(string $message): string + { + return str($message)->after('field'); + } + + public function cropSetup(): string + { + return json_encode(array_merge([ + 'autoCropArea' => 1, + 'viewMode' => 2, + 'dragMode' => 'move', + 'checkCrossOrigin' => false, + 'aspectRatio' => 16/9, + ], $this->cropConfig), JSON_THROW_ON_ERROR); + } + + public function render(): View|Closure|string + { + return <<<'HTML' +