- 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>
188 lines
7.1 KiB
PHP
188 lines
7.1 KiB
PHP
<?php
|
|
|
|
namespace App\Services;
|
|
|
|
use App\Models\DisplayPlaylist;
|
|
use App\Models\DisplayVersion;
|
|
use App\Support\DisplayModuleSettings;
|
|
use Illuminate\Database\Eloquent\Collection;
|
|
|
|
class DisplayPlaylistConfigBuilder
|
|
{
|
|
/**
|
|
* @return array{playlist: array<int, array<string, mixed>>, updated_at: string|null}
|
|
*/
|
|
public function fromPlaylist(DisplayPlaylist $playlist): array
|
|
{
|
|
$playlist->loadMissing('modules.items');
|
|
|
|
return $this->fromModules($playlist->modules, $playlist->updated_at);
|
|
}
|
|
|
|
/**
|
|
* @param Collection<int, DisplayVersion> $modules
|
|
* @return array{playlist: array<int, array<string, mixed>>, updated_at: string|null}
|
|
*/
|
|
public function fromModules(Collection $modules, mixed $fallbackUpdatedAt = null): array
|
|
{
|
|
$playlist = $modules
|
|
->loadMissing('items')
|
|
->map(fn (DisplayVersion $module) => $this->moduleData($module))
|
|
->filter()
|
|
->values()
|
|
->all();
|
|
|
|
return [
|
|
'playlist' => $playlist,
|
|
'updated_at' => $this->updatedAt($modules, $fallbackUpdatedAt),
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @return array<string, mixed>|null
|
|
*/
|
|
private function moduleData(DisplayVersion $module): ?array
|
|
{
|
|
$items = $module->items
|
|
->where('is_active', true)
|
|
->sortBy('sort_order')
|
|
->values();
|
|
|
|
return match ($module->type->value) {
|
|
'video-display' => $this->videoDisplayData($module, $items),
|
|
'b2in' => $this->b2inData($module, $items),
|
|
'offers' => $this->offersData($module, $items),
|
|
default => null,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* @param Collection<int, DisplayVersion> $modules
|
|
*/
|
|
private function updatedAt(Collection $modules, mixed $fallbackUpdatedAt = null): ?string
|
|
{
|
|
$timestamps = collect([$fallbackUpdatedAt])
|
|
->merge($modules->pluck('updated_at'))
|
|
->merge($modules->flatMap(fn (DisplayVersion $module) => $module->items->pluck('updated_at')))
|
|
->filter();
|
|
|
|
return $timestamps->max()?->toIso8601String();
|
|
}
|
|
|
|
/**
|
|
* @param Collection<int, \App\Models\DisplayVersionItem> $items
|
|
* @return array<string, mixed>
|
|
*/
|
|
private function videoDisplayData(DisplayVersion $module, Collection $items): array
|
|
{
|
|
$videos = $items->where('item_type', 'video')->values()->map(fn ($item) => [
|
|
'src' => $this->mediaSource($item->content['filename'] ?? ''),
|
|
'position' => $item->content['position'] ?? 25,
|
|
]);
|
|
|
|
$footerContent = $items->where('item_type', 'footer')->values()->map(function ($item) {
|
|
$data = [
|
|
'headline' => $item->content['headline'] ?? '',
|
|
'subline' => $item->content['subline'] ?? '',
|
|
];
|
|
|
|
if (! empty($item->content['url'])) {
|
|
$data['url'] = $item->content['url'];
|
|
}
|
|
|
|
return $data;
|
|
});
|
|
|
|
return [
|
|
'type' => 'video-display',
|
|
'version_name' => $module->name,
|
|
'settings' => DisplayModuleSettings::merge($module->type, $module->settings),
|
|
'videoPlaylist' => $videos,
|
|
'footerContent' => $footerContent,
|
|
];
|
|
}
|
|
|
|
private function mediaSource(string $source): string
|
|
{
|
|
if ($source === '' || str_starts_with($source, 'http') || str_starts_with($source, '/') || str_starts_with($source, '../')) {
|
|
return $source;
|
|
}
|
|
|
|
return 'assets/'.$source;
|
|
}
|
|
|
|
/**
|
|
* @param Collection<int, \App\Models\DisplayVersionItem> $items
|
|
* @return array<string, mixed>
|
|
*/
|
|
private function b2inData(DisplayVersion $module, Collection $items): array
|
|
{
|
|
$mediaItems = $items->where('item_type', 'media')->values()->map(fn ($item) => [
|
|
'id' => $item->id,
|
|
'category' => $item->content['category'] ?? 'immobilien',
|
|
'media_type' => $item->content['media_type'] ?? 'image',
|
|
'media_url' => $item->content['media_url'] ?? '',
|
|
'headline' => $item->content['headline'] ?? '',
|
|
'subline' => $item->content['subline'] ?? '',
|
|
'duration_seconds' => $item->content['duration_seconds'] ?? 10,
|
|
'sort_order' => $item->sort_order,
|
|
'is_active' => true,
|
|
]);
|
|
|
|
return [
|
|
'type' => 'b2in',
|
|
'version_name' => $module->name,
|
|
'settings' => DisplayModuleSettings::merge($module->type, $module->settings),
|
|
'items' => $mediaItems,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @param Collection<int, \App\Models\DisplayVersionItem> $items
|
|
* @return array<string, mixed>
|
|
*/
|
|
private function offersData(DisplayVersion $module, Collection $items): array
|
|
{
|
|
$slides = $items->where('item_type', 'slide')->values()->map(function ($item) {
|
|
$content = $item->content;
|
|
|
|
return [
|
|
'type' => $content['type'] ?? 'detail',
|
|
'show_logo' => $content['show_logo'] ?? true,
|
|
'logo_url' => $content['logo_url'] ?? '',
|
|
'brand_text' => $content['brand_text'] ?? '',
|
|
'duration' => $content['duration'] ?? 8000,
|
|
'image_url' => $content['image_url'] ?? '',
|
|
'badge_text' => $content['badge_text'] ?? '',
|
|
'show_badge' => $content['show_badge'] ?? ($content['badge_text'] ?? '') !== '',
|
|
'eyebrow' => $content['eyebrow'] ?? '',
|
|
'show_eyebrow' => $content['show_eyebrow'] ?? ($content['eyebrow'] ?? '') !== '',
|
|
'title' => $content['title'] ?? '',
|
|
'subline' => $content['subline'] ?? '',
|
|
'show_subline' => $content['show_subline'] ?? ($content['subline'] ?? '') !== '',
|
|
'price' => $content['price'] ?? '',
|
|
'original_price' => $content['original_price'] ?? '',
|
|
'strike_original_price' => $content['strike_original_price'] ?? false,
|
|
'tag_text' => $content['tag_text'] ?? '',
|
|
'show_price' => $content['show_price'] ?? ($content['price'] ?? '') !== '',
|
|
'bullets' => $content['bullets'] ?? [],
|
|
'show_bullets' => $content['show_bullets'] ?? ! empty($content['bullets']),
|
|
'disclaimer' => $content['disclaimer'] ?? '',
|
|
'show_disclaimer' => $content['show_disclaimer'] ?? ($content['disclaimer'] ?? '') !== '',
|
|
'qr_url' => $content['qr_url'] ?? '',
|
|
'qr_title' => $content['qr_title'] ?? '',
|
|
'show_qr' => $content['show_qr'] ?? ($content['qr_url'] ?? '') !== '',
|
|
'contact' => $content['contact'] ?? '',
|
|
'show_contact' => $content['show_contact'] ?? ($content['contact'] ?? '') !== '',
|
|
'brand_tagline' => $content['brand_tagline'] ?? '',
|
|
];
|
|
});
|
|
|
|
return [
|
|
'type' => 'offers',
|
|
'version_name' => $module->name,
|
|
'settings' => DisplayModuleSettings::merge($module->type, $module->settings),
|
|
'slides' => $slides,
|
|
];
|
|
}
|
|
}
|