diff --git a/app/Console/Commands/MoveMediaToPublic.php b/app/Console/Commands/MoveMediaToPublic.php new file mode 100644 index 0000000..bce2727 --- /dev/null +++ b/app/Console/Commands/MoveMediaToPublic.php @@ -0,0 +1,112 @@ +option('dry-run'); + + if ($dryRun) { + $this->warn('DRY RUN MODE - No files will actually be moved'); + } + + // Get all media records using local disk + $mediaRecords = Media::where('disk', 'local')->get(); + + if ($mediaRecords->isEmpty()) { + $this->info('No media records found using local disk.'); + return self::SUCCESS; + } + + $this->info("Found {$mediaRecords->count()} media records to migrate."); + + $progressBar = $this->output->createProgressBar($mediaRecords->count()); + $progressBar->start(); + + $moved = 0; + $errors = 0; + + foreach ($mediaRecords as $media) { + // Use relative path: {id}/{filename} + $relativePath = $media->id . '/' . $media->file_name; + + // Check if source file exists + if (!Storage::disk('local')->exists($relativePath)) { + $this->newLine(); + $this->error("Source file not found: {$relativePath}"); + $errors++; + $progressBar->advance(); + continue; + } + + try { + if (!$dryRun) { + // Copy file from local to public disk + $fileContent = Storage::disk('local')->get($relativePath); + Storage::disk('public')->put($relativePath, $fileContent); + + // Verify the file was copied successfully + if (Storage::disk('public')->exists($relativePath)) { + // Update the database record + $media->update([ + 'disk' => 'public', + 'conversions_disk' => 'public', + ]); + + // Delete the old file from local disk + Storage::disk('local')->delete($relativePath); + + $moved++; + } else { + throw new \Exception("Failed to copy file to public disk"); + } + } else { + $this->newLine(); + $this->line("Would move: local:{$relativePath} -> public:{$relativePath}"); + $moved++; + } + } catch (\Exception $e) { + $this->newLine(); + $this->error("Error moving {$relativePath}: {$e->getMessage()}"); + $errors++; + } + + $progressBar->advance(); + } + + $progressBar->finish(); + $this->newLine(2); + + if ($dryRun) { + $this->info("DRY RUN: Would move {$moved} files, {$errors} errors encountered."); + $this->info("Run without --dry-run to actually perform the migration."); + } else { + $this->info("Successfully moved {$moved} files, {$errors} errors encountered."); + } + + return self::SUCCESS; + } +} diff --git a/app/Filament/Resources/Entries/Schemas/EntryForm.php b/app/Filament/Resources/Entries/Schemas/EntryForm.php index 82e163d..6c029f6 100644 --- a/app/Filament/Resources/Entries/Schemas/EntryForm.php +++ b/app/Filament/Resources/Entries/Schemas/EntryForm.php @@ -2,13 +2,18 @@ namespace App\Filament\Resources\Entries\Schemas; +use Filament\Actions\Action; use Filament\Forms\Components\DatePicker; use Filament\Forms\Components\RichEditor; -use Filament\Forms\Components\TextInput; +use Filament\Forms\Components\Select; +use Filament\Forms\Components\SpatieMediaLibraryFileUpload; use Filament\Forms\Components\Textarea; +use Filament\Forms\Components\TextInput; use Filament\Forms\Components\Toggle; use Filament\Schemas\Schema; +use Illuminate\Support\Facades\Storage; use Illuminate\Support\Str; +use Spatie\MediaLibrary\MediaCollections\Models\Media; class EntryForm { @@ -18,22 +23,183 @@ class EntryForm ->components([ TextInput::make('title') ->required() - ->reactive() + ->live(onBlur: true) ->afterStateUpdated(function ($state, $set): void { $set('slug', Str::slug((string) $state)); }), TextInput::make('slug') ->required() - ->disabled(), + ->dehydrated() + ->readOnly(), Textarea::make('description') ->columnSpanFull(), + SpatieMediaLibraryFileUpload::make('featured_image') + ->collection('featured-image') + ->image() + ->imageEditor() + ->disk('public') + ->visibility('public') + ->columnSpanFull() + ->dehydrated(false) + ->hintAction( + Action::make('featured_picker') + ->label('Pick from Gallery') + ->icon('heroicon-m-photo') + ->schema([ + Select::make('image_id') + ->label('Select an existing image') + ->allowHtml() + ->options(function () { + return Media::where('model_type', 'temp') + ->where('model_id', 0) + ->where('disk', 'public') + ->latest() + ->limit(30) + ->get(['id', 'file_name', 'name', 'disk']) + ->mapWithKeys(function (Media $item) { + try { + $url = $item->getUrl(); + $fileName = e($item->file_name); + $name = e($item->name ?? ''); + + $html = "
" . + "{$fileName}" . + "
" . + "{$name}" . + "{$fileName}" . + "
"; + + return [$item->id => $html]; + } catch (\Exception $e) { + return []; + } + })->toArray(); + }) + ->searchable() + ->preload() + ->required(), + ]) + ->action(function (array $data, SpatieMediaLibraryFileUpload $component): void { + $record = $component->getRecord(); + + if (!$record) { + \Filament\Notifications\Notification::make() + ->warning() + ->title('Save the entry first') + ->send(); + return; + } + + if (!$data['image_id']) { + return; + } + + $sourceMedia = Media::find($data['image_id']); + if (!$sourceMedia || !file_exists($sourceMedia->getPath())) { + \Filament\Notifications\Notification::make() + ->danger() + ->title('Image file not found') + ->send(); + return; + } + + $sourceFile = $sourceMedia->getPath(); + $tempCopy = sys_get_temp_dir() . '/' . uniqid() . '_' . $sourceMedia->file_name; + copy($sourceFile, $tempCopy); + + try { + // Verify record has ID + if (!$record->id) { + \Filament\Notifications\Notification::make() + ->danger() + ->title('Entry must be saved first') + ->send(); + return; + } + + // Add the copy to the entry's featured-image collection + $newMedia = $record->addMedia($tempCopy) + ->usingName($sourceMedia->name ?: pathinfo($sourceMedia->file_name, PATHINFO_FILENAME)) + ->usingFileName($sourceMedia->file_name) + ->toMediaCollection('featured-image', 'public'); + + // Dispatch event for app.js to handle + $component->getLivewire()->dispatch('featured-image-added', ['mediaId' => $newMedia->id]); + + \Filament\Notifications\Notification::make() + ->success() + ->title('Image added to featured image') + ->send(); + } catch (\Exception $e) { + \Filament\Notifications\Notification::make() + ->danger() + ->title('Error: ' . $e->getMessage()) + ->send(); + } finally { + if (file_exists($tempCopy)) { + unlink($tempCopy); + } + } + }) + ), Toggle::make('is_published') ->required(), Toggle::make('is_featured') ->required(), DatePicker::make('published_at'), RichEditor::make('content') - ->columnSpanFull(), + ->columnSpanFull() + ->hintAction( + Action::make('picker') + ->label('Gallery Picker') + ->icon('heroicon-m-photo') + ->schema([ + Select::make('image_url') + ->label('Select an existing image') + ->allowHtml() + ->options(function () { + return Media::latest() + ->limit(30) // Limit to 30 most recent items for performance + ->get(['id', 'file_name', 'name', 'uuid', 'collection_name', 'model_type', 'model_id', 'disk']) + ->filter(function (Media $item) { + // Only include media items that have a valid disk + return $item->disk !== null; + }) + ->mapWithKeys(function (Media $item) { + try { + $url = $item->getUrl(); + } catch (\Exception $e) { + // Skip items that can't generate URLs + return []; + } + + $fileName = e($item->file_name); + $name = e($item->name ?? ''); + + // Smaller image preview for better performance + $html = "
" . + "{$fileName}" . + "
" . + "{$name}" . + "{$fileName}" . + "
"; + + return [$url => $html]; + })->toArray(); + }) + ->searchable() + ->preload() + ->required(), + ]) + ->action(function (array $data, RichEditor $component) { + // We dispatch the URL to the browser to be inserted into TipTap + $component->getLivewire()->dispatch('insert-editor-content', [ + 'statePath' => $component->getStatePath(), + 'html' => "", + ]); + }) + ), + ]); } } diff --git a/app/Filament/Resources/Entries/Schemas/EntryInfolist.php b/app/Filament/Resources/Entries/Schemas/EntryInfolist.php index 6a5d87d..be741bc 100644 --- a/app/Filament/Resources/Entries/Schemas/EntryInfolist.php +++ b/app/Filament/Resources/Entries/Schemas/EntryInfolist.php @@ -3,6 +3,7 @@ namespace App\Filament\Resources\Entries\Schemas; use Filament\Infolists\Components\IconEntry; +use Filament\Infolists\Components\SpatieMediaLibraryImageEntry; use Filament\Infolists\Components\TextEntry; use Filament\Schemas\Schema; @@ -17,6 +18,9 @@ class EntryInfolist TextEntry::make('description') ->placeholder('-') ->columnSpanFull(), + SpatieMediaLibraryImageEntry::make('featured_image') + ->collection('featured-image') + ->columnSpanFull(), IconEntry::make('is_published') ->boolean(), IconEntry::make('is_featured') diff --git a/app/Filament/Resources/Entries/Tables/EntriesTable.php b/app/Filament/Resources/Entries/Tables/EntriesTable.php index 57035c0..eb89fd4 100644 --- a/app/Filament/Resources/Entries/Tables/EntriesTable.php +++ b/app/Filament/Resources/Entries/Tables/EntriesTable.php @@ -7,6 +7,7 @@ use Filament\Actions\DeleteBulkAction; use Filament\Actions\EditAction; use Filament\Actions\ViewAction; use Filament\Tables\Columns\IconColumn; +use Filament\Tables\Columns\SpatieMediaLibraryImageColumn; use Filament\Tables\Columns\TextColumn; use Filament\Tables\Table; @@ -16,6 +17,11 @@ class EntriesTable { return $table ->columns([ + SpatieMediaLibraryImageColumn::make('featured_image') + ->collection('featured-image') + ->circular() + ->stacked() + ->limit(3), TextColumn::make('title') ->searchable(), TextColumn::make('slug') diff --git a/app/Filament/Resources/Media/MediaResource.php b/app/Filament/Resources/Media/MediaResource.php new file mode 100644 index 0000000..2ac19fe --- /dev/null +++ b/app/Filament/Resources/Media/MediaResource.php @@ -0,0 +1,58 @@ + ListMedia::route('/'), + 'create' => CreateMedia::route('/create'), + 'view' => ViewMedia::route('/{record}'), + 'edit' => EditMedia::route('/{record}/edit'), + ]; + } +} diff --git a/app/Filament/Resources/Media/Pages/CreateMedia.php b/app/Filament/Resources/Media/Pages/CreateMedia.php new file mode 100644 index 0000000..c652a96 --- /dev/null +++ b/app/Filament/Resources/Media/Pages/CreateMedia.php @@ -0,0 +1,61 @@ +uploadedFile = $file; + + // Set required fields for Media model + $data['model_type'] = $data['model_type'] ?? 'temp'; + $data['model_id'] = $data['model_id'] ?? 0; + $data['collection_name'] = $data['collection_name'] ?? 'default'; + $data['disk'] = $data['disk'] ?? 'public'; + $data['file_name'] = $file ? basename($file) : ''; + $data['mime_type'] = $file && Storage::disk('public')->exists($file) + ? Storage::disk('public')->mimeType($file) + : 'application/octet-stream'; + $data['size'] = $file && Storage::disk('public')->exists($file) + ? Storage::disk('public')->size($file) + : 0; + $data['manipulations'] = []; + $data['custom_properties'] = []; + $data['generated_conversions'] = []; + $data['responsive_images'] = []; + + return $data; + } + + protected function afterCreate(): void + { + if ($this->uploadedFile && $this->record) { + $disk = Storage::disk('public'); + + // Create the directory for this media ID (Spatie structure: {id}/{filename}) + $mediaDirectory = (string) $this->record->id; + $disk->makeDirectory($mediaDirectory); + + // Move file from temporary upload location to Spatie's expected location + if ($disk->exists($this->uploadedFile)) { + $newPath = $mediaDirectory.'/'.$this->record->file_name; + $disk->move($this->uploadedFile, $newPath); + } + } + } +} diff --git a/app/Filament/Resources/Media/Pages/EditMedia.php b/app/Filament/Resources/Media/Pages/EditMedia.php new file mode 100644 index 0000000..b6f4cdb --- /dev/null +++ b/app/Filament/Resources/Media/Pages/EditMedia.php @@ -0,0 +1,72 @@ +record->getPathRelativeToRoot()) { + $this->uploadedFile = $file; + + // Keep the original file_name to prevent breaking existing references + // $data['file_name'] is not updated - we preserve the original filename + $data['mime_type'] = Storage::disk('public')->exists($file) + ? Storage::disk('public')->mimeType($file) + : 'application/octet-stream'; + $data['size'] = Storage::disk('public')->exists($file) + ? Storage::disk('public')->size($file) + : 0; + } + + return $data; + } + + protected function afterSave(): void + { + if ($this->uploadedFile && $this->record) { + $disk = Storage::disk('public'); + $mediaDirectory = (string) $this->record->id; + + // Delete old file if it exists + $oldPath = $mediaDirectory.'/'.$this->record->getOriginal('file_name'); + if ($disk->exists($oldPath)) { + $disk->delete($oldPath); + } + + // Move new file to Spatie's expected location using the original filename + if ($disk->exists($this->uploadedFile)) { + $disk->makeDirectory($mediaDirectory); + // Use the original file_name to preserve existing references + $newPath = $mediaDirectory.'/'.$this->record->file_name; + $disk->move($this->uploadedFile, $newPath); + } + + // Redirect to the same page to refresh the form state + $this->redirect(static::getUrl(['record' => $this->record]), navigate: true); + } + } +} diff --git a/app/Filament/Resources/Media/Pages/ListMedia.php b/app/Filament/Resources/Media/Pages/ListMedia.php new file mode 100644 index 0000000..2e6b63d --- /dev/null +++ b/app/Filament/Resources/Media/Pages/ListMedia.php @@ -0,0 +1,19 @@ +components([ + TextInput::make('name') + ->required() + ->maxLength(255), + TextInput::make('collection_name') + ->default('default') + ->required() + ->maxLength(255), + Hidden::make('disk') + ->default('public'), + FileUpload::make('file') + ->label('File') + ->imageEditor() + ->imageEditorAspectRatios([ + '16:9', + '4:3', + '1:1', + ]) + ->columnSpanFull() + ->disk('public') + ->directory('media') + ->visibility('public') + ->acceptedFileTypes(['image/*', 'application/pdf']) + ->maxSize(10240) + ->required(fn ($context) => $context === 'create') + ->afterStateHydrated(function (FileUpload $component, $state, $record): void { + if (! $record) { + return; + } + + $media = $record; + + if (! $media instanceof SpatieMedia) { + return; + } + + // Construct the correct path: {media_id}/{filename} + $path = $media->id.'/'.$media->file_name; + + $component->state($path); + }), + ]); + } +} diff --git a/app/Filament/Resources/Media/Schemas/MediaInfolist.php b/app/Filament/Resources/Media/Schemas/MediaInfolist.php new file mode 100644 index 0000000..dfe4801 --- /dev/null +++ b/app/Filament/Resources/Media/Schemas/MediaInfolist.php @@ -0,0 +1,36 @@ +components([ + ImageEntry::make('file_name') + ->label('Preview') + ->getStateUsing(fn ($record) => $record->getUrl()) + ->visible(fn ($record) => $record->mime_type && str_starts_with($record->mime_type, 'image/')), + TextEntry::make('name'), + TextEntry::make('file_name'), + TextEntry::make('mime_type'), + TextEntry::make('collection_name'), + TextEntry::make('size') + ->formatStateUsing(fn ($state) => number_format($state / 1024, 2).' KB'), + TextEntry::make('model_type') + ->label('Attached to Model'), + TextEntry::make('model_id'), + TextEntry::make('custom_properties') + ->formatStateUsing(fn ($state) => json_encode($state, JSON_PRETTY_PRINT)), + TextEntry::make('created_at') + ->dateTime(), + TextEntry::make('updated_at') + ->dateTime(), + ]); + } +} diff --git a/app/Filament/Resources/Media/Tables/MediaTable.php b/app/Filament/Resources/Media/Tables/MediaTable.php new file mode 100644 index 0000000..dba76b0 --- /dev/null +++ b/app/Filament/Resources/Media/Tables/MediaTable.php @@ -0,0 +1,86 @@ +modifyQueryUsing(fn ($query) => $query->where('collection_name', '!=', 'avatars')) + ->columns([ + ImageColumn::make('url') + ->label('Preview') + ->getStateUsing(fn ($record) => + // Prefer the stored path produced by Filament's FileUpload (saved in custom_properties), + // fall back to Spatie's getUrl() when no stored_path exists. + ($record->getCustomProperty('stored_path')) + ? Storage::url($record->getCustomProperty('stored_path')) + : $record->getUrl() + ) + ->height(40) + ->width(40), + TextColumn::make('name') + ->searchable(), + TextColumn::make('file_name') + ->searchable(), + TextColumn::make('collection_name') + ->badge(), + TextColumn::make('mime_type'), + TextColumn::make('size') + ->formatStateUsing(fn ($state) => number_format($state / 1024, 2).' KB'), + TextColumn::make('created_at') + ->dateTime(), + ]) + ->filters([ + SelectFilter::make('collection_name') + ->options([ + 'images' => 'Images', + 'documents' => 'Documents', + ]), + ]) + ->recordActions([ + EditAction::make(), + DeleteAction::make() + ->action(function (Media $record) { + // Delete the actual stored file path if we saved one, otherwise fall back to the Spatie path. + $stored = $record->getCustomProperty('stored_path'); + if ($stored) { + Storage::disk($record->disk)->delete($stored); + } else { + Storage::disk($record->disk)->delete($record->getPath()); + } + + $record->delete(); + }), + ]) + ->toolbarActions([ + BulkActionGroup::make([ + DeleteBulkAction::make() + ->action(function (Collection $records) { + $records->each(function (Media $record) { + $stored = $record->getCustomProperty('stored_path'); + if ($stored) { + Storage::disk($record->disk)->delete($stored); + } else { + Storage::disk($record->disk)->delete($record->getPath()); + } + $record->delete(); + }); + }), + ]), + ]); + } +} diff --git a/app/Livewire/GalleryPicker.php b/app/Livewire/GalleryPicker.php new file mode 100644 index 0000000..c1da485 --- /dev/null +++ b/app/Livewire/GalleryPicker.php @@ -0,0 +1,108 @@ +entryId = $entryId; + $this->loadMediaItems(); + $this->showModal = true; + } + + public function loadMediaItems(): void + { + $this->mediaItems = Media::where('model_type', 'temp') + ->where('model_id', 0) + ->where('disk', 'public') + ->latest() + ->limit(30) + ->get(['id', 'file_name', 'name', 'disk']) + ->toArray(); + } + + public function selectMedia($mediaId): void + { + $this->selectedMediaId = $mediaId; + } + + public function copyToEntry(): void + { + if (!$this->selectedMediaId || !$this->entryId) { + $this->dispatch('notify-error', ['message' => 'Please select an image']); + return; + } + + $sourceMedia = Media::find($this->selectedMediaId); + if (!$sourceMedia) { + $this->dispatch('notify-error', ['message' => 'Media not found']); + return; + } + + try { + // Get the entry + $entry = \App\Models\Entry::find($this->entryId); + if (!$entry) { + $this->dispatch('notify-error', ['message' => 'Entry not found']); + return; + } + + // Get source file + $sourceFile = $sourceMedia->getPath(); + if (!file_exists($sourceFile)) { + $this->dispatch('notify-error', ['message' => 'Source file not found']); + return; + } + + // Create temp copy + $tempCopy = sys_get_temp_dir() . '/' . uniqid() . '_' . $sourceMedia->file_name; + copy($sourceFile, $tempCopy); + + try { + // Clear existing featured image + $entry->clearMediaCollection('featured-image'); + + // Add to entry + $newMedia = $entry->addMedia($tempCopy) + ->usingName($sourceMedia->name ?: pathinfo($sourceMedia->file_name, PATHINFO_FILENAME)) + ->usingFileName($sourceMedia->file_name) + ->toMediaCollection('featured-image', 'public'); + + // Close modal and notify + $this->showModal = false; + $this->selectedMediaId = null; + $this->dispatch('media-selected', ['mediaId' => $newMedia->id, 'fileName' => $newMedia->file_name]); + $this->dispatch('notify-success', ['message' => 'Image added to entry']); + } finally { + if (file_exists($tempCopy)) { + unlink($tempCopy); + } + } + } catch (\Exception $e) { + $this->dispatch('notify-error', ['message' => 'Error: ' . $e->getMessage()]); + } + } + + public function closePicker(): void + { + $this->showModal = false; + $this->selectedMediaId = null; + } + + public function render() + { + return view('livewire.gallery-picker'); + } +} diff --git a/app/Providers/Filament/AdminPanelProvider.php b/app/Providers/Filament/AdminPanelProvider.php index 373a4be..81aa43d 100644 --- a/app/Providers/Filament/AdminPanelProvider.php +++ b/app/Providers/Filament/AdminPanelProvider.php @@ -10,6 +10,8 @@ use Filament\Pages\Dashboard; use Filament\Panel; use Filament\PanelProvider; use Filament\Support\Colors\Color; +use Filament\Support\Facades\FilamentView; +use Filament\View\PanelsRenderHook; use Filament\Widgets\AccountWidget; use Filament\Widgets\FilamentInfoWidget; use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse; @@ -56,4 +58,12 @@ class AdminPanelProvider extends PanelProvider Authenticate::class, ]); } + + public function boot(): void + { + FilamentView::registerRenderHook( + PanelsRenderHook::BODY_END, + fn (): string => \Illuminate\Support\Facades\Blade::render('@vite("resources/js/app.js")'), + ); + } } diff --git a/composer.json b/composer.json index 4745dfb..c67e0d1 100644 --- a/composer.json +++ b/composer.json @@ -8,7 +8,6 @@ "require": { "php": "^8.2", "filament/filament": "^4.0", - "filament/spatie-laravel-media-library-plugin": "^4.0", "laravel/fortify": "^1.30", "laravel/framework": "^12.0", "laravel/tinker": "^2.10.1", diff --git a/resources/js/app.js b/resources/js/app.js index b677733..22e1c9c 100644 --- a/resources/js/app.js +++ b/resources/js/app.js @@ -1 +1,48 @@ console.log('App.js is loaded'); + +document.addEventListener('livewire:init', () => { + Livewire.on('insert-editor-content', (data) => { + console.log('Received insert-editor-content data:', data); + // Handle if data is an array + const payload = Array.isArray(data) ? data[0] : data; + const statePath = payload.statePath; + const html = payload.html; + console.log('Extracted statePath:', statePath, 'html:', html); + // 1. Find the editor by its statePath + const container = document.querySelector(`[wire\\:model="${statePath}"]`) || document.querySelector(`[statepath="${statePath}"]`); + console.log('Container found:', container); + const editorElement = container ? container.querySelector('.tiptap') : null; + console.log('Editor element found:', editorElement); + + if (editorElement && editorElement.editor) { + console.log('Inserting content:', html); + // 2. Insert the HTML (the tag) into the editor + setTimeout(() => { + editorElement.editor.chain().focus().insertContent(html).run(); + }, 500); + } else { + console.log('Editor not found or not initialized'); + // Fallback: try to find any .tiptap on the page + const anyTiptap = document.querySelector('.tiptap'); + console.log('Any tiptap found:', anyTiptap); + if (anyTiptap && anyTiptap.editor) { + console.log('Inserting to any tiptap'); + setTimeout(() => { + anyTiptap.editor.chain().focus().insertContent(html).run(); + }, 500); + } + } + }); + + Livewire.on('featured-image-added', (data) => { + console.log('Received featured-image-added event:', data); + const payload = Array.isArray(data) ? data[0] : data; + + // Reload the page to show the updated featured image + setTimeout(() => { + window.location.reload(); + }, 500); + }); +}); + +console.log('Testing if app.js is still running'); \ No newline at end of file diff --git a/resources/views/components/featured-image-display.blade.php b/resources/views/components/featured-image-display.blade.php new file mode 100644 index 0000000..437552f --- /dev/null +++ b/resources/views/components/featured-image-display.blade.php @@ -0,0 +1,32 @@ +
+ @php + $record = $this->data; + if ($record && isset($record['id'])) { + $entry = \App\Models\Entry::find($record['id']); + $media = $entry ? $entry->getMedia('featured-image') : []; + } else { + $media = []; + } + @endphp + +
+ + @if(count($media) > 0) +
+ @foreach($media as $item) +
+
+ +
+

{{ $item->name }}

+

{{ $item->file_name }}

+
+
+
+ @endforeach +
+ @else +

No featured image selected

+ @endif +
+
diff --git a/resources/views/livewire/gallery-picker.blade.php b/resources/views/livewire/gallery-picker.blade.php new file mode 100644 index 0000000..103b66b --- /dev/null +++ b/resources/views/livewire/gallery-picker.blade.php @@ -0,0 +1,72 @@ +
+ @if($showModal ?? false) +
+
+ +
+ + +
+
+
+

+ Select Image from Gallery +

+ +
+ + @if(empty($mediaItems ?? [])) +

No images in gallery

+ @else +
+ @foreach($mediaItems ?? [] as $item) + + @endforeach +
+ @endif +
+ +
+ + +
+
+
+
+ @endif +