- Mediathek: Video-Vorschaubilder statt Icons (FFmpeg-Thumbnails + Backfill-Command), Kategorie "Sonstiges" - B2in Media-Picker zeigt alle Medientypen, Typ wird automatisch erkannt; Thumbnail-Preview vor allen Medien-URL-Feldern - B2in Marke/Footer: Footer ein/aus, Logo+Claim frei positionierbar (Ecken) mit Constraints, separate Anzeige-Schalter - Angebote-Modul dynamisch: kein Slide-Typ mehr, einheitliches Detail-Layout mit ein-/ausblendbaren Bloecken, Logo/Brand pro Slide, Streichpreis-Option - Player: leere Module stoppen Endlosschleife, dynamische Layout-Anpassung bei verstecktem Footer/Header - Fix: Script-Ladereihenfolge (Livewire vor Flux), entfernte stale public/flux/flux.js, Modal-Crash beim Aktualisieren behoben Co-authored-by: Cursor <cursoragent@cursor.com>
379 lines
11 KiB
PHP
379 lines
11 KiB
PHP
<?php
|
|
|
|
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
|
|
{
|
|
public $showModal = false;
|
|
|
|
public $displayId = null;
|
|
|
|
public $displayName = '';
|
|
|
|
public $displayLocation = '';
|
|
|
|
/** @var array<int> */
|
|
public $selectedVersionIds = [];
|
|
|
|
public $displayIsActive = true;
|
|
|
|
public $displayIsTest = false;
|
|
|
|
public $addVersionSelect = null;
|
|
|
|
/** @var array<int> */
|
|
public $versionsToAdd = [];
|
|
|
|
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(['livePlaylist.modules', 'draftPlaylist.modules'])->findOrFail($id);
|
|
$this->displayId = $display->id;
|
|
$this->displayName = $display->name;
|
|
$this->displayLocation = $display->location ?? '';
|
|
$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();
|
|
}
|
|
|
|
$this->showModal = true;
|
|
}
|
|
|
|
public function addVersion(?int $versionId = null): void
|
|
{
|
|
$id = $versionId ?? $this->addVersionSelect ?? $this->firstAvailableVersionId();
|
|
|
|
if ($id && ! in_array((int) $id, $this->selectedVersionIds)) {
|
|
$this->selectedVersionIds[] = (int) $id;
|
|
}
|
|
|
|
$this->addVersionSelect = null;
|
|
$this->persistDraftPreviewIfNeeded();
|
|
}
|
|
|
|
public function addSelectedVersions(): void
|
|
{
|
|
foreach ($this->versionsToAdd as $versionId) {
|
|
$id = (int) $versionId;
|
|
|
|
if ($id && ! in_array($id, $this->selectedVersionIds, true)) {
|
|
$this->selectedVersionIds[] = $id;
|
|
}
|
|
}
|
|
|
|
$this->versionsToAdd = [];
|
|
$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
|
|
{
|
|
$newIndex = $direction === 'up' ? $index - 1 : $index + 1;
|
|
|
|
if ($newIndex < 0 || $newIndex >= count($this->selectedVersionIds)) {
|
|
return;
|
|
}
|
|
|
|
$temp = $this->selectedVersionIds[$index];
|
|
$this->selectedVersionIds[$index] = $this->selectedVersionIds[$newIndex];
|
|
$this->selectedVersionIds[$newIndex] = $temp;
|
|
|
|
$this->persistDraftPreviewIfNeeded();
|
|
}
|
|
|
|
public function save(): void
|
|
{
|
|
$this->validate([
|
|
'displayName' => 'required|string|max:255',
|
|
'displayLocation' => 'nullable|string|max:255',
|
|
'selectedVersionIds' => 'array',
|
|
'selectedVersionIds.*' => 'exists:display_versions,id',
|
|
], [
|
|
'displayName.required' => 'Bitte geben Sie einen Namen ein.',
|
|
]);
|
|
|
|
$data = [
|
|
'name' => $this->displayName,
|
|
'location' => $this->displayLocation ?: null,
|
|
'is_active' => $this->displayIsActive,
|
|
'is_test' => $this->displayIsTest,
|
|
];
|
|
|
|
if ($this->displayId) {
|
|
$display = Display::findOrFail($this->displayId);
|
|
$display->update($data);
|
|
session()->flash('success', 'Display erfolgreich aktualisiert!');
|
|
} else {
|
|
$display = Display::create($data);
|
|
session()->flash('success', 'Display erfolgreich erstellt!');
|
|
}
|
|
|
|
if ($this->editingPlaylistStatus === DisplayPlaylist::STATUS_DRAFT && $this->displayId) {
|
|
$this->syncDraftPlaylist($display);
|
|
$this->draftPreviewToken = $display->ensurePreviewToken();
|
|
$this->previewFrameRefreshCounter++;
|
|
} else {
|
|
$this->syncPublishedPlaylist($display);
|
|
}
|
|
|
|
$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();
|
|
$display->clearPreviewToken();
|
|
|
|
session()->flash('success', 'Entwurf wurde verworfen.');
|
|
}
|
|
|
|
public function rotatePreviewToken(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->rotatePreviewToken();
|
|
|
|
if ($this->displayId === $display->id && $this->editingPlaylistStatus === DisplayPlaylist::STATUS_DRAFT) {
|
|
$this->draftPreviewToken = $display->preview_token;
|
|
$this->previewFrameRefreshCounter++;
|
|
}
|
|
|
|
session()->flash('success', 'Vorschau-Link wurde neu erzeugt. Der alte Link ist jetzt ungültig.');
|
|
}
|
|
|
|
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');
|
|
});
|
|
|
|
$display->clearPreviewToken();
|
|
|
|
session()->flash('success', 'Entwurf wurde veröffentlicht.');
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
public function deleteDisplay(int $id): void
|
|
{
|
|
$display = Display::findOrFail($id);
|
|
$name = $display->name;
|
|
$display->delete();
|
|
|
|
session()->flash('success', 'Display "'.$name.'" wurde gelöscht!');
|
|
}
|
|
|
|
public function toggleActive(int $id): void
|
|
{
|
|
$display = Display::findOrFail($id);
|
|
$display->update(['is_active' => ! $display->is_active]);
|
|
}
|
|
|
|
public function closeModal(): void
|
|
{
|
|
$this->showModal = false;
|
|
$this->resetForm();
|
|
}
|
|
|
|
public function resetForm(): void
|
|
{
|
|
$this->displayId = null;
|
|
$this->displayName = '';
|
|
$this->displayLocation = '';
|
|
$this->selectedVersionIds = [];
|
|
$this->displayIsActive = true;
|
|
$this->displayIsTest = false;
|
|
$this->addVersionSelect = null;
|
|
$this->versionsToAdd = [];
|
|
$this->editingPlaylistStatus = DisplayPlaylist::STATUS_PUBLISHED;
|
|
$this->draftPreviewToken = null;
|
|
$this->previewFrameRefreshCounter = 0;
|
|
}
|
|
|
|
public function render(): View
|
|
{
|
|
$displays = Display::with(['livePlaylist.modules', 'draftPlaylist.modules'])
|
|
->orderByDesc('is_test')
|
|
->orderBy('name')
|
|
->get();
|
|
|
|
$versions = DisplayVersion::active()
|
|
->orderBy('name')
|
|
->get();
|
|
|
|
return view('livewire.admin.cms.display-list', [
|
|
'displays' => $displays,
|
|
'versions' => $versions,
|
|
]);
|
|
}
|
|
}
|