12-05-2026 admin, Panel Displays
This commit is contained in:
parent
0762e3beac
commit
6a65354f4c
43 changed files with 3273 additions and 410 deletions
|
|
@ -3,7 +3,12 @@
|
|||
namespace App\Livewire\Admin\Cms;
|
||||
|
||||
use App\Models\Display;
|
||||
use App\Models\DisplayPlaylist;
|
||||
use App\Models\DisplayPlaylistItem;
|
||||
use App\Models\DisplayVersion;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Livewire\Component;
|
||||
|
||||
class DisplayList extends Component
|
||||
|
|
@ -21,17 +26,36 @@ class DisplayList extends Component
|
|||
|
||||
public $displayIsActive = true;
|
||||
|
||||
public $displayIsTest = false;
|
||||
|
||||
public $addVersionSelect = null;
|
||||
|
||||
public function openModal(?int $id = null): void
|
||||
public $editingPlaylistStatus = DisplayPlaylist::STATUS_PUBLISHED;
|
||||
|
||||
public ?string $draftPreviewToken = null;
|
||||
|
||||
public int $previewFrameRefreshCounter = 0;
|
||||
|
||||
public function openModal(?int $id = null, string $playlistStatus = DisplayPlaylist::STATUS_PUBLISHED): void
|
||||
{
|
||||
$this->editingPlaylistStatus = in_array($playlistStatus, [
|
||||
DisplayPlaylist::STATUS_PUBLISHED,
|
||||
DisplayPlaylist::STATUS_DRAFT,
|
||||
], true) ? $playlistStatus : DisplayPlaylist::STATUS_PUBLISHED;
|
||||
|
||||
if ($id) {
|
||||
$display = Display::with('versions')->findOrFail($id);
|
||||
$display = Display::with(['livePlaylist.modules', 'draftPlaylist.modules', 'versions'])->findOrFail($id);
|
||||
$this->displayId = $display->id;
|
||||
$this->displayName = $display->name;
|
||||
$this->displayLocation = $display->location ?? '';
|
||||
$this->selectedVersionIds = $display->versions->pluck('id')->toArray();
|
||||
$this->selectedVersionIds = $this->selectedVersionIdsFor($display);
|
||||
$this->displayIsActive = $display->is_active;
|
||||
$this->displayIsTest = $display->is_test;
|
||||
|
||||
if ($this->editingPlaylistStatus === DisplayPlaylist::STATUS_DRAFT) {
|
||||
$this->draftPreviewToken = $display->ensurePreviewToken();
|
||||
$this->previewFrameRefreshCounter++;
|
||||
}
|
||||
} else {
|
||||
$this->resetForm();
|
||||
}
|
||||
|
|
@ -41,18 +65,28 @@ class DisplayList extends Component
|
|||
|
||||
public function addVersion(?int $versionId = null): void
|
||||
{
|
||||
$id = $versionId ?? $this->addVersionSelect;
|
||||
$id = $versionId ?? $this->addVersionSelect ?? $this->firstAvailableVersionId();
|
||||
|
||||
if ($id && ! in_array((int) $id, $this->selectedVersionIds)) {
|
||||
$this->selectedVersionIds[] = (int) $id;
|
||||
}
|
||||
|
||||
$this->addVersionSelect = null;
|
||||
$this->persistDraftPreviewIfNeeded();
|
||||
}
|
||||
|
||||
private function firstAvailableVersionId(): ?int
|
||||
{
|
||||
return DisplayVersion::active()
|
||||
->whereNotIn('id', $this->selectedVersionIds)
|
||||
->orderBy('name')
|
||||
->value('id');
|
||||
}
|
||||
|
||||
public function removeVersion(int $index): void
|
||||
{
|
||||
array_splice($this->selectedVersionIds, $index, 1);
|
||||
$this->persistDraftPreviewIfNeeded();
|
||||
}
|
||||
|
||||
public function moveVersion(int $index, string $direction): void
|
||||
|
|
@ -66,6 +100,8 @@ class DisplayList extends Component
|
|||
$temp = $this->selectedVersionIds[$index];
|
||||
$this->selectedVersionIds[$index] = $this->selectedVersionIds[$newIndex];
|
||||
$this->selectedVersionIds[$newIndex] = $temp;
|
||||
|
||||
$this->persistDraftPreviewIfNeeded();
|
||||
}
|
||||
|
||||
public function save(): void
|
||||
|
|
@ -83,6 +119,7 @@ class DisplayList extends Component
|
|||
'name' => $this->displayName,
|
||||
'location' => $this->displayLocation ?: null,
|
||||
'is_active' => $this->displayIsActive,
|
||||
'is_test' => $this->displayIsTest,
|
||||
];
|
||||
|
||||
if ($this->displayId) {
|
||||
|
|
@ -94,16 +131,176 @@ class DisplayList extends Component
|
|||
session()->flash('success', 'Display erfolgreich erstellt!');
|
||||
}
|
||||
|
||||
// Sync versions with sort_order
|
||||
$syncData = [];
|
||||
foreach ($this->selectedVersionIds as $sortOrder => $versionId) {
|
||||
$syncData[$versionId] = ['sort_order' => $sortOrder];
|
||||
if ($this->editingPlaylistStatus === DisplayPlaylist::STATUS_DRAFT && $this->displayId) {
|
||||
$this->syncDraftPlaylist($display);
|
||||
$this->draftPreviewToken = $display->ensurePreviewToken();
|
||||
$this->previewFrameRefreshCounter++;
|
||||
} else {
|
||||
$this->syncPublishedPlaylist($display);
|
||||
$this->syncLegacyPivot($display, $this->selectedVersionIds);
|
||||
}
|
||||
$display->versions()->sync($syncData);
|
||||
|
||||
$this->closeModal();
|
||||
}
|
||||
|
||||
public function createDraft(int $displayId): void
|
||||
{
|
||||
$display = Display::with(['livePlaylist.modules', 'draftPlaylist'])->findOrFail($displayId);
|
||||
|
||||
if ($display->draftPlaylist) {
|
||||
session()->flash('success', 'Für dieses Display existiert bereits ein Entwurf.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$draft = $display->playlists()->create([
|
||||
'status' => DisplayPlaylist::STATUS_DRAFT,
|
||||
'published_at' => null,
|
||||
'published_by' => null,
|
||||
]);
|
||||
|
||||
$this->syncPlaylistItems($draft, $this->moduleIdsForPlaylist($display->livePlaylist));
|
||||
$display->ensurePreviewToken();
|
||||
|
||||
session()->flash('success', 'Entwurf wurde aus dem Live-Stand angelegt.');
|
||||
}
|
||||
|
||||
public function discardDraft(int $displayId): void
|
||||
{
|
||||
$display = Display::with('draftPlaylist')->findOrFail($displayId);
|
||||
|
||||
if (! $display->draftPlaylist) {
|
||||
session()->flash('success', 'Für dieses Display gibt es keinen Entwurf.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$display->draftPlaylist->delete();
|
||||
|
||||
session()->flash('success', 'Entwurf wurde verworfen.');
|
||||
}
|
||||
|
||||
public function publishDraft(int $displayId): void
|
||||
{
|
||||
$display = Display::with(['draftPlaylist.modules'])->findOrFail($displayId);
|
||||
|
||||
if (! $display->draftPlaylist) {
|
||||
session()->flash('success', 'Für dieses Display gibt es keinen Entwurf zum Veröffentlichen.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$publishedPlaylist = DB::transaction(function () use ($display): DisplayPlaylist {
|
||||
$display->livePlaylist()->delete();
|
||||
|
||||
$display->draftPlaylist->update([
|
||||
'status' => DisplayPlaylist::STATUS_PUBLISHED,
|
||||
'published_at' => now(),
|
||||
'published_by' => Auth::id(),
|
||||
]);
|
||||
|
||||
return $display->draftPlaylist->fresh('modules');
|
||||
});
|
||||
|
||||
$this->syncLegacyPivot($display, $this->moduleIdsForPlaylist($publishedPlaylist));
|
||||
|
||||
session()->flash('success', 'Entwurf wurde veröffentlicht.');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int> $versionIds
|
||||
*/
|
||||
private function syncLegacyPivot(Display $display, array $versionIds): void
|
||||
{
|
||||
$syncData = [];
|
||||
foreach ($versionIds as $sortOrder => $versionId) {
|
||||
$syncData[$versionId] = ['sort_order' => $sortOrder];
|
||||
}
|
||||
|
||||
$display->versions()->sync($syncData);
|
||||
}
|
||||
|
||||
private function syncPublishedPlaylist(Display $display): void
|
||||
{
|
||||
$playlist = $display->playlists()->firstOrCreate(
|
||||
['status' => DisplayPlaylist::STATUS_PUBLISHED],
|
||||
['published_at' => now(), 'published_by' => Auth::id()]
|
||||
);
|
||||
|
||||
$playlist->update([
|
||||
'published_at' => now(),
|
||||
'published_by' => Auth::id(),
|
||||
]);
|
||||
|
||||
$this->syncPlaylistItems($playlist, $this->selectedVersionIds);
|
||||
}
|
||||
|
||||
private function syncDraftPlaylist(Display $display): void
|
||||
{
|
||||
$playlist = $display->playlists()->firstOrCreate(
|
||||
['status' => DisplayPlaylist::STATUS_DRAFT],
|
||||
['published_at' => null, 'published_by' => null]
|
||||
);
|
||||
|
||||
$this->syncPlaylistItems($playlist, $this->selectedVersionIds);
|
||||
}
|
||||
|
||||
private function persistDraftPreviewIfNeeded(): void
|
||||
{
|
||||
if ($this->editingPlaylistStatus !== DisplayPlaylist::STATUS_DRAFT || ! $this->displayId) {
|
||||
return;
|
||||
}
|
||||
|
||||
$display = Display::findOrFail($this->displayId);
|
||||
|
||||
$this->syncDraftPlaylist($display);
|
||||
$this->draftPreviewToken = $display->ensurePreviewToken();
|
||||
$this->previewFrameRefreshCounter++;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int> $versionIds
|
||||
*/
|
||||
private function syncPlaylistItems(DisplayPlaylist $playlist, array $versionIds): void
|
||||
{
|
||||
$playlist->items()->delete();
|
||||
|
||||
foreach (array_values($versionIds) as $sortOrder => $versionId) {
|
||||
DisplayPlaylistItem::query()->create([
|
||||
'display_playlist_id' => $playlist->id,
|
||||
'display_version_id' => $versionId,
|
||||
'sort_order' => $sortOrder,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int>
|
||||
*/
|
||||
private function moduleIdsForPlaylist(?DisplayPlaylist $playlist): array
|
||||
{
|
||||
if (! $playlist) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$playlist->loadMissing('modules');
|
||||
|
||||
return $playlist->modules->pluck('id')->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int>
|
||||
*/
|
||||
private function selectedVersionIdsFor(Display $display): array
|
||||
{
|
||||
if ($this->editingPlaylistStatus === DisplayPlaylist::STATUS_DRAFT) {
|
||||
return $this->moduleIdsForPlaylist($display->draftPlaylist);
|
||||
}
|
||||
|
||||
return $this->moduleIdsForPlaylist($display->livePlaylist)
|
||||
?: $display->versions->pluck('id')->all();
|
||||
}
|
||||
|
||||
public function deleteDisplay(int $id): void
|
||||
{
|
||||
$display = Display::findOrFail($id);
|
||||
|
|
@ -132,12 +329,17 @@ class DisplayList extends Component
|
|||
$this->displayLocation = '';
|
||||
$this->selectedVersionIds = [];
|
||||
$this->displayIsActive = true;
|
||||
$this->displayIsTest = false;
|
||||
$this->addVersionSelect = null;
|
||||
$this->editingPlaylistStatus = DisplayPlaylist::STATUS_PUBLISHED;
|
||||
$this->draftPreviewToken = null;
|
||||
$this->previewFrameRefreshCounter = 0;
|
||||
}
|
||||
|
||||
public function render()
|
||||
public function render(): View
|
||||
{
|
||||
$displays = Display::with('versions')
|
||||
$displays = Display::with(['livePlaylist.modules', 'draftPlaylist.modules'])
|
||||
->orderByDesc('is_test')
|
||||
->orderBy('name')
|
||||
->get();
|
||||
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ class DisplayMediaPicker extends Component
|
|||
{
|
||||
$this->validate([
|
||||
'quickUploads' => 'nullable|array|max:5',
|
||||
'quickUploads.*' => 'file|mimes:jpeg,jpg,png,gif,webp,mp4,webm,mov|max:204800',
|
||||
'quickUploads.*' => 'file|mimes:jpeg,jpg,png,gif,webp,svg,mp4,webm,mov|max:204800',
|
||||
]);
|
||||
|
||||
$service = app(DisplayMediaService::class);
|
||||
|
|
|
|||
|
|
@ -101,11 +101,13 @@ class DisplayVersionEditor extends Component
|
|||
/** @var array<string> */
|
||||
public array $availableVideos = [];
|
||||
|
||||
public int $previewFrameRefreshCounter = 0;
|
||||
|
||||
public function mount(DisplayVersion $displayVersion): void
|
||||
{
|
||||
$this->version = $displayVersion;
|
||||
$this->versionName = $displayVersion->name;
|
||||
$this->settings = $displayVersion->settings ?? [];
|
||||
$this->settings = $this->settingsWithDefaults();
|
||||
|
||||
if ($this->version->type === DisplayVersionType::VideoDisplay) {
|
||||
$this->loadAvailableVideos();
|
||||
|
|
@ -127,10 +129,11 @@ class DisplayVersionEditor extends Component
|
|||
|
||||
public function toggleTheme(): void
|
||||
{
|
||||
$settings = $this->version->settings ?? [];
|
||||
$settings = $this->settingsWithDefaults();
|
||||
$settings['theme'] = ($settings['theme'] ?? 'dark') === 'dark' ? 'light' : 'dark';
|
||||
$this->version->update(['settings' => $settings]);
|
||||
$this->settings = $settings;
|
||||
$this->refreshModulePreview();
|
||||
}
|
||||
|
||||
public function saveName(): void
|
||||
|
|
@ -140,6 +143,7 @@ class DisplayVersionEditor extends Component
|
|||
]);
|
||||
|
||||
$this->version->update(['name' => $this->versionName]);
|
||||
$this->refreshModulePreview();
|
||||
session()->flash('success', 'Name aktualisiert!');
|
||||
}
|
||||
|
||||
|
|
@ -149,7 +153,7 @@ class DisplayVersionEditor extends Component
|
|||
|
||||
public function openSettingsModal(): void
|
||||
{
|
||||
$this->settings = $this->version->settings ?? [];
|
||||
$this->settings = $this->settingsWithDefaults();
|
||||
$this->showSettingsModal = true;
|
||||
}
|
||||
|
||||
|
|
@ -157,6 +161,7 @@ class DisplayVersionEditor extends Component
|
|||
{
|
||||
$this->version->update(['settings' => $this->settings]);
|
||||
$this->showSettingsModal = false;
|
||||
$this->refreshModulePreview();
|
||||
session()->flash('success', 'Einstellungen gespeichert!');
|
||||
}
|
||||
|
||||
|
|
@ -197,7 +202,7 @@ class DisplayVersionEditor extends Component
|
|||
->where('item_type', $this->itemType)
|
||||
->max('sort_order') ?? -1;
|
||||
|
||||
DisplayVersionItem::create([
|
||||
$item = DisplayVersionItem::create([
|
||||
'display_version_id' => $this->version->id,
|
||||
'item_type' => $this->itemType,
|
||||
'content' => $content,
|
||||
|
|
@ -207,12 +212,17 @@ class DisplayVersionEditor extends Component
|
|||
session()->flash('success', 'Inhalt hinzugefügt!');
|
||||
}
|
||||
|
||||
$this->closeItemModal();
|
||||
$this->itemId = $item->id;
|
||||
$this->itemType = $item->item_type;
|
||||
$this->loadItemContent($item->fresh());
|
||||
$this->showItemModal = true;
|
||||
$this->refreshModulePreview();
|
||||
}
|
||||
|
||||
public function deleteItem(int $id): void
|
||||
{
|
||||
DisplayVersionItem::findOrFail($id)->delete();
|
||||
$this->refreshModulePreview();
|
||||
session()->flash('success', 'Inhalt gelöscht!');
|
||||
}
|
||||
|
||||
|
|
@ -220,6 +230,7 @@ class DisplayVersionEditor extends Component
|
|||
{
|
||||
$item = DisplayVersionItem::findOrFail($id);
|
||||
$item->update(['is_active' => ! $item->is_active]);
|
||||
$this->refreshModulePreview();
|
||||
}
|
||||
|
||||
public function moveItem(int $id, string $direction): void
|
||||
|
|
@ -235,9 +246,24 @@ class DisplayVersionEditor extends Component
|
|||
if ($swapItem) {
|
||||
$item->update(['sort_order' => $swapItem->sort_order]);
|
||||
$swapItem->update(['sort_order' => $currentOrder]);
|
||||
$this->refreshModulePreview();
|
||||
}
|
||||
}
|
||||
|
||||
public function modulePreviewUrl(): string
|
||||
{
|
||||
return url('/preview/module/'.$this->version->id).'?refresh='.$this->previewFrameRefreshCounter;
|
||||
}
|
||||
|
||||
public function itemPreviewUrl(): string
|
||||
{
|
||||
if (! $this->itemId) {
|
||||
return $this->modulePreviewUrl();
|
||||
}
|
||||
|
||||
return url('/preview/module/'.$this->version->id.'/item/'.$this->itemId).'?refresh='.$this->previewFrameRefreshCounter;
|
||||
}
|
||||
|
||||
#[On('display-media-selected')]
|
||||
public function onDisplayMediaSelected(string $field, ?int $mediaId, ?string $url): void
|
||||
{
|
||||
|
|
@ -249,6 +275,8 @@ class DisplayVersionEditor extends Component
|
|||
'videoFilename' => $this->videoFilename = $url,
|
||||
'mediaUrl' => $this->mediaUrl = $url,
|
||||
'slideImageUrl' => $this->slideImageUrl = $url,
|
||||
'settings.header_logo_url' => $this->settings['header_logo_url'] = $url,
|
||||
'settings.logo_url' => $this->settings['logo_url'] = $url,
|
||||
default => null,
|
||||
};
|
||||
}
|
||||
|
|
@ -442,6 +470,58 @@ class DisplayVersionEditor extends Component
|
|||
$this->slideIsActive = true;
|
||||
}
|
||||
|
||||
private function refreshModulePreview(): void
|
||||
{
|
||||
$this->previewFrameRefreshCounter++;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function settingsWithDefaults(): array
|
||||
{
|
||||
return array_replace_recursive($this->defaultSettings(), $this->version->settings ?? []);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function defaultSettings(): array
|
||||
{
|
||||
return match ($this->version->type) {
|
||||
DisplayVersionType::VideoDisplay => [
|
||||
'qr_label' => 'Website',
|
||||
],
|
||||
DisplayVersionType::B2in => [
|
||||
'theme' => 'dark',
|
||||
'header_logo_url' => '../assets/b2in-logo-positive.svg',
|
||||
'header_claim' => 'Connecting Design & Property',
|
||||
'footer_url' => 'B2in.eu',
|
||||
'footer_name' => '',
|
||||
'footer_prefix' => 'by',
|
||||
'qr_url' => '',
|
||||
'transition' => [
|
||||
'type' => 'crossfade',
|
||||
'duration_ms' => 800,
|
||||
],
|
||||
'default_image_duration' => 10,
|
||||
],
|
||||
DisplayVersionType::Offers => [
|
||||
'loop' => true,
|
||||
'logo_url' => '../logo-cabinet-300.png',
|
||||
'brand_text' => 'Bielefeld',
|
||||
'footer_claim' => '',
|
||||
'footer_url' => '',
|
||||
'qr_default_title' => 'Kontakt',
|
||||
'qr_subtitle' => 'QR scannen',
|
||||
'transition' => [
|
||||
'type' => 'fade',
|
||||
'duration' => 600,
|
||||
],
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
$items = $this->version->items()->get()->groupBy('item_type');
|
||||
|
|
|
|||
|
|
@ -42,10 +42,10 @@ class DisplayVersionList extends Component
|
|||
$this->newName = '';
|
||||
$this->newType = '';
|
||||
|
||||
session()->flash('success', 'Version "'.$version->name.'" wurde erstellt!');
|
||||
session()->flash('success', 'Modul "'.$version->name.'" wurde erstellt!');
|
||||
|
||||
$this->redirect(
|
||||
route('admin.cms.display-version-edit', $version),
|
||||
route('admin.cms.display-module-edit', $version),
|
||||
navigate: true
|
||||
);
|
||||
}
|
||||
|
|
@ -56,7 +56,7 @@ class DisplayVersionList extends Component
|
|||
$name = $version->name;
|
||||
$version->delete();
|
||||
|
||||
session()->flash('success', 'Version "'.$name.'" wurde gelöscht!');
|
||||
session()->flash('success', 'Modul "'.$name.'" wurde gelöscht!');
|
||||
}
|
||||
|
||||
public function toggleActive(int $id): void
|
||||
|
|
@ -73,8 +73,12 @@ class DisplayVersionList extends Component
|
|||
return match ($type) {
|
||||
'b2in' => [
|
||||
'theme' => 'dark',
|
||||
'header_logo_url' => '../assets/b2in-logo-positive.svg',
|
||||
'header_claim' => 'Connecting Design & Property',
|
||||
'footer_name' => '',
|
||||
'footer_url' => '',
|
||||
'footer_url' => 'B2in.eu',
|
||||
'footer_prefix' => 'by',
|
||||
'qr_url' => '',
|
||||
'transition' => ['type' => 'crossfade', 'duration_ms' => 800],
|
||||
'default_image_duration' => 10,
|
||||
'rotation_weights' => ['immobilien' => 70, 'moebel' => 30],
|
||||
|
|
@ -82,8 +86,17 @@ class DisplayVersionList extends Component
|
|||
],
|
||||
'offers' => [
|
||||
'loop' => true,
|
||||
'logo_url' => '../logo-cabinet-300.png',
|
||||
'brand_text' => 'Bielefeld',
|
||||
'footer_claim' => '',
|
||||
'footer_url' => '',
|
||||
'qr_default_title' => 'Kontakt',
|
||||
'qr_subtitle' => 'QR scannen',
|
||||
'transition' => ['type' => 'fade', 'duration' => 600],
|
||||
],
|
||||
'video-display' => [
|
||||
'qr_label' => 'Website',
|
||||
],
|
||||
default => [],
|
||||
};
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue