b2in/packages/flux-cms/components/src/Livewire/Backend/PageEditor.php
2026-04-10 17:18:17 +02:00

387 lines
10 KiB
PHP

<?php
namespace FluxCms\Components\Livewire\Backend;
use FluxCms\Core\Models\Page;
use FluxCms\Core\Models\PageComponent;
use FluxCms\Core\Services\ComponentRegistry;
use Illuminate\Support\Collection;
use Livewire\Attributes\On;
use Livewire\Component;
class PageEditor extends Component
{
public Page $page;
public Collection $components;
public array $availableLanguages = [];
public string $currentLocale = 'de';
public bool $showComponentModal = false;
public array $availableComponents = [];
public string $selectedCategory = 'all';
public bool $isLoading = false;
protected ComponentRegistry $componentRegistry;
public function boot(ComponentRegistry $componentRegistry)
{
$this->componentRegistry = $componentRegistry;
}
public function mount(Page $page)
{
$this->page = $page;
$this->availableLanguages = array_keys(config('flux-cms.locales', ['de' => 'Deutsch', 'en' => 'English']));
$this->currentLocale = app()->getLocale();
$this->loadComponents();
$this->loadAvailableComponents();
}
public function render()
{
return view('flux-cms-components::livewire.backend.page-editor')
->layout('flux-cms-components::layouts.admin');
}
/**
* Load page components
*/
public function loadComponents()
{
$this->components = $this->page->allComponents()->ordered()->get();
}
/**
* Load available components from registry
*/
public function loadAvailableComponents()
{
$this->availableComponents = $this->componentRegistry->getComponentsByCategory();
}
/**
* Switch locale
*/
public function switchLocale(string $locale)
{
if (in_array($locale, $this->availableLanguages)) {
$this->currentLocale = $locale;
}
}
/**
* Show add component modal
*/
public function showAddComponentModal()
{
$this->loadAvailableComponents(); // Refresh components
$this->showComponentModal = true;
}
/**
* Close add component modal
*/
public function closeAddComponentModal()
{
$this->showComponentModal = false;
$this->selectedCategory = 'all';
}
/**
* Set category filter
*/
public function setCategory(string $category)
{
$this->selectedCategory = $category;
}
/**
* Add new component
*/
public function addComponent(string $componentClass)
{
if (! $this->componentRegistry->isValidComponent($componentClass)) {
$this->addError('component', 'Invalid component selected.');
return;
}
try {
$maxOrder = $this->page->allComponents()->max('order') ?? 0;
$component = $this->page->allComponents()->create([
'component_class' => $componentClass,
'order' => $maxOrder + 1,
'content' => $this->getDefaultContent($componentClass),
'is_active' => true,
]);
$this->loadComponents();
$this->closeAddComponentModal();
$this->dispatch('scroll-to-component', componentId: $component->id);
session()->flash('success', 'Component added successfully.');
} catch (\Exception $e) {
$this->addError('component', 'Error adding component: '.$e->getMessage());
}
}
/**
* Delete component
*/
public function deleteComponent(int $componentId)
{
try {
$component = $this->components->firstWhere('id', $componentId);
if (! $component) {
$this->addError('component', 'Component not found.');
return;
}
$component->delete();
$this->loadComponents();
$this->reorderComponents();
session()->flash('success', 'Component deleted successfully.');
} catch (\Exception $e) {
$this->addError('component', 'Error deleting component: '.$e->getMessage());
}
}
/**
* Duplicate component
*/
public function duplicateComponent(int $componentId)
{
try {
$component = $this->components->firstWhere('id', $componentId);
if (! $component) {
$this->addError('component', 'Component not found.');
return;
}
$duplicate = $component->duplicate();
$this->loadComponents();
$this->dispatch('scroll-to-component', componentId: $duplicate->id);
session()->flash('success', 'Component duplicated successfully.');
} catch (\Exception $e) {
$this->addError('component', 'Error duplicating component: '.$e->getMessage());
}
}
/**
* Toggle component active state
*/
public function toggleComponent(int $componentId)
{
try {
$component = $this->components->firstWhere('id', $componentId);
if (! $component) {
return;
}
$component->update(['is_active' => ! $component->is_active]);
$this->loadComponents();
} catch (\Exception $e) {
$this->addError('component', 'Error toggling component: '.$e->getMessage());
}
}
/**
* Update component order
*/
#[On('components-reordered')]
public function updateOrder(array $orderedIds)
{
try {
foreach ($orderedIds as $index => $id) {
PageComponent::where('id', $id)->update(['order' => $index + 1]);
}
$this->loadComponents();
session()->flash('success', 'Component order updated.');
} catch (\Exception $e) {
$this->addError('order', 'Error updating order: '.$e->getMessage());
}
}
/**
* Reorder components to close gaps
*/
protected function reorderComponents()
{
$components = $this->page->allComponents()->orderBy('order')->get();
foreach ($components as $index => $component) {
$component->update(['order' => $index + 1]);
}
}
/**
* Generate default content for component
*/
protected function getDefaultContent(string $componentClass): array
{
$config = $this->componentRegistry->getComponentConfig($componentClass);
$content = [];
if (empty($config['fields'])) {
return $content;
}
foreach ($config['fields'] as $field) {
if (! $field instanceof \FluxCms\Core\FieldTypes\BaseField) {
continue;
}
$defaultValue = $field->getDefault();
if ($field->isTranslatable()) {
foreach ($this->availableLanguages as $locale) {
$content[$field->getKey()][$locale] = $defaultValue;
}
} else {
$content[$field->getKey()] = $defaultValue;
}
}
return $content;
}
/**
* Update page data
*/
public function updatePageData()
{
try {
$this->validate([
'page.title' => 'required|array',
'page.slug' => 'required|array',
'page.meta_description' => 'nullable|array',
]);
$this->page->save();
session()->flash('success', 'Page data saved successfully.');
} catch (\Exception $e) {
$this->addError('page', 'Error saving page: '.$e->getMessage());
}
}
/**
* Toggle publish status
*/
public function togglePublish()
{
try {
if ($this->page->is_published) {
$this->page->unpublish();
$message = 'Page unpublished successfully.';
} else {
$this->page->publish();
$message = 'Page published successfully.';
}
session()->flash('success', $message);
} catch (\Exception $e) {
$this->addError('publish', 'Error updating publish status: '.$e->getMessage());
}
}
/**
* Create version
*/
public function createVersion(?string $description = null)
{
try {
$this->page->createVersion($description, auth()->id());
session()->flash('success', 'Version created successfully.');
} catch (\Exception $e) {
$this->addError('version', 'Error creating version: '.$e->getMessage());
}
}
/**
* Preview page
*/
public function preview()
{
$locale = $this->currentLocale;
$slug = $this->page->getTranslation('slug', $locale);
if (empty($slug)) {
$this->addError('preview', 'No slug available for current language.');
return;
}
$url = $this->page->getUrl($locale).'?preview=1';
$this->dispatch('open-preview', url: $url);
}
/**
* Get available categories for filter
*/
public function getAvailableCategoriesProperty(): array
{
$categories = ['all' => 'All Categories'];
foreach ($this->availableComponents as $category => $components) {
$categories[$category] = $category;
}
return $categories;
}
/**
* Get filtered components for modal
*/
public function getFilteredComponentsProperty(): array
{
if ($this->selectedCategory === 'all') {
$components = [];
foreach ($this->availableComponents as $category => $categoryComponents) {
$components = array_merge($components, $categoryComponents);
}
return $components;
}
return $this->availableComponents[$this->selectedCategory] ?? [];
}
/**
* Get page status
*/
public function getPageStatusProperty(): string
{
if (! $this->page->is_published) {
return 'draft';
}
if ($this->page->published_at && $this->page->published_at->isFuture()) {
return 'scheduled';
}
return 'published';
}
}