components([ TextInput::make('title') ->required() ->live(onBlur: true) ->afterStateUpdated(function ($state, $set): void { $set('slug', Str::slug((string) $state)); }), TextInput::make('slug') ->required() ->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('Featured Image from Gallery') ->icon('heroicon-m-photo') ->extraAttributes(['id' => 'featured-picker-button']) ->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() ->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' => "", ]); }) ), ]); } }