387 lines
10 KiB
PHP
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';
|
|
}
|
|
}
|