Display Module 13-05-2026

This commit is contained in:
Kevin Adametz 2026-05-13 14:34:08 +02:00
parent 6a65354f4c
commit 9262132325
41 changed files with 496 additions and 334 deletions

View file

@ -4,6 +4,8 @@ namespace App\Console\Commands;
use App\Models\Display;
use App\Models\DisplayFooterContent;
use App\Models\DisplayPlaylist;
use App\Models\DisplayPlaylistItem;
use App\Models\DisplayVersion;
use App\Models\DisplayVersionItem;
use App\Models\DisplayVideo;
@ -75,7 +77,16 @@ class MigrateLegacyDisplays extends Command
'is_active' => true,
]);
$display->versions()->attach($version->id, ['sort_order' => 0]);
$playlist = $display->playlists()->create([
'status' => DisplayPlaylist::STATUS_PUBLISHED,
'published_at' => now(),
]);
DisplayPlaylistItem::create([
'display_playlist_id' => $playlist->id,
'display_version_id' => $version->id,
'sort_order' => 0,
]);
$this->info("Migrated {$videos->count()} videos and {$footers->count()} footer items.");
$this->info("Created version: {$version->name} (ID: {$version->id})");

View file

@ -9,6 +9,35 @@ use Illuminate\Http\JsonResponse;
class DisplayVersionApiController extends Controller
{
public function overview(): JsonResponse
{
$displays = Display::query()
->with(['livePlaylist.modules'])
->where('is_active', true)
->whereHas('livePlaylist.modules')
->orderBy('name')
->get()
->map(function (Display $display): array {
$playlist = $display->livePlaylist;
return [
'id' => $display->id,
'name' => $display->name,
'location' => $display->location,
'is_active' => $display->is_active,
'is_live' => true,
'module_count' => $playlist?->modules->count() ?? 0,
'updated_at' => $playlist?->updated_at?->toIso8601String(),
'url' => rtrim(config('display.player_url'), '/').'/?id='.$display->id,
];
})
->values();
return response()->json([
'displays' => $displays,
]);
}
public function config(Display $display, DisplayPlaylistConfigBuilder $configBuilder): JsonResponse
{
if (! $display->is_active) {

View file

@ -44,7 +44,7 @@ class DisplayList extends Component
], true) ? $playlistStatus : DisplayPlaylist::STATUS_PUBLISHED;
if ($id) {
$display = Display::with(['livePlaylist.modules', 'draftPlaylist.modules', 'versions'])->findOrFail($id);
$display = Display::with(['livePlaylist.modules', 'draftPlaylist.modules'])->findOrFail($id);
$this->displayId = $display->id;
$this->displayName = $display->name;
$this->displayLocation = $display->location ?? '';
@ -137,7 +137,6 @@ class DisplayList extends Component
$this->previewFrameRefreshCounter++;
} else {
$this->syncPublishedPlaylist($display);
$this->syncLegacyPivot($display, $this->selectedVersionIds);
}
$this->closeModal();
@ -202,24 +201,9 @@ class DisplayList extends Component
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(
@ -297,8 +281,7 @@ class DisplayList extends Component
return $this->moduleIdsForPlaylist($display->draftPlaylist);
}
return $this->moduleIdsForPlaylist($display->livePlaylist)
?: $display->versions->pluck('id')->all();
return $this->moduleIdsForPlaylist($display->livePlaylist);
}
public function deleteDisplay(int $id): void

View file

@ -5,6 +5,7 @@ namespace App\Livewire\Admin\Cms;
use App\Enums\DisplayVersionType;
use App\Models\DisplayVersion;
use App\Models\DisplayVersionItem;
use App\Support\DisplayModuleSettings;
use Illuminate\Support\Facades\File;
use Livewire\Attributes\On;
use Livewire\Component;
@ -480,46 +481,7 @@ class DisplayVersionEditor extends Component
*/
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,
],
],
};
return DisplayModuleSettings::merge($this->version->type, $this->version->settings);
}
public function render()

View file

@ -4,6 +4,8 @@ namespace App\Livewire\Admin\Cms;
use App\Enums\DisplayVersionType;
use App\Models\DisplayVersion;
use App\Support\DisplayModuleSettings;
use Illuminate\Support\Facades\DB;
use Livewire\Component;
class DisplayVersionList extends Component
@ -70,40 +72,17 @@ class DisplayVersionList extends Component
*/
private function defaultSettingsForType(string $type): array
{
return match ($type) {
'b2in' => [
'theme' => 'dark',
'header_logo_url' => '../assets/b2in-logo-positive.svg',
'header_claim' => 'Connecting Design & Property',
'footer_name' => '',
'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],
'display_active' => true,
],
'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 => [],
};
return DisplayModuleSettings::defaults($type);
}
public function render()
{
$versions = DisplayVersion::withCount(['items', 'displays'])
$versions = DisplayVersion::withCount([
'items',
'playlistItems as displays_count' => fn ($query) => $query
->join('display_playlists', 'display_playlist_items.display_playlist_id', '=', 'display_playlists.id')
->select(DB::raw('count(distinct display_playlists.display_id)')),
])
->orderBy('name')
->get();

View file

@ -17,7 +17,7 @@ class MediaLibraryUploader extends Component
{
$this->validate([
'uploads' => 'nullable|array|max:20',
'uploads.*' => 'file|mimes:jpeg,jpg,png,gif,webp,svg,pdf,doc,docx|max:10240',
'uploads.*' => 'file|mimes:jpeg,jpg,png,gif,webp,svg,pdf,doc,docx|max:204800',
]);
$service = app(MediaConversionService::class);

View file

@ -73,7 +73,7 @@ class MediaPicker extends Component
{
$this->validate([
'quickUploads' => 'nullable|array|max:5',
'quickUploads.*' => 'file|mimes:jpeg,jpg,png,gif,webp,svg,pdf,doc,docx|max:10240',
'quickUploads.*' => 'file|mimes:jpeg,jpg,png,gif,webp,svg,pdf,doc,docx|max:204800',
]);
$service = app(MediaConversionService::class);

View file

@ -4,7 +4,6 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Support\Str;
@ -30,17 +29,6 @@ class Display extends Model
];
}
/**
* @deprecated Wird in Phase 7 entfernt. Nutze stattdessen liveModules()
* oder die Playlist-Relationen (livePlaylist, draftPlaylist).
*/
public function versions(): BelongsToMany
{
return $this->belongsToMany(DisplayVersion::class, 'display_display_version')
->withPivot('sort_order')
->orderByPivot('sort_order');
}
/**
* @return HasMany<DisplayPlaylist, $this>
*/
@ -67,29 +55,6 @@ class Display extends Model
->where('status', DisplayPlaylist::STATUS_DRAFT);
}
/**
* Liefert die Module der aktuell veröffentlichten Bespielung in Reihenfolge.
*/
public function liveModules(): BelongsToMany
{
return $this->belongsToMany(
DisplayVersion::class,
'display_playlist_items',
'display_playlist_id',
'display_version_id'
)
->wherePivotIn(
'display_playlist_id',
DisplayPlaylist::query()
->where('display_id', $this->id ?? 0)
->where('status', DisplayPlaylist::STATUS_PUBLISHED)
->select('id')
)
->withPivot(['sort_order', 'id'])
->withTimestamps()
->orderByPivot('sort_order');
}
public function ensurePreviewToken(): string
{
if (! $this->preview_token) {

View file

@ -6,7 +6,6 @@ use App\Enums\DisplayVersionType;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
class DisplayVersion extends Model
@ -35,10 +34,12 @@ class DisplayVersion extends Model
return $this->hasMany(DisplayVersionItem::class)->orderBy('sort_order');
}
public function displays(): BelongsToMany
/**
* @return HasMany<DisplayPlaylistItem, $this>
*/
public function playlistItems(): HasMany
{
return $this->belongsToMany(Display::class, 'display_display_version')
->withPivot('sort_order');
return $this->hasMany(DisplayPlaylistItem::class);
}
/**

View file

@ -4,6 +4,7 @@ namespace App\Services;
use App\Models\DisplayPlaylist;
use App\Models\DisplayVersion;
use App\Support\DisplayModuleSettings;
use Illuminate\Database\Eloquent\Collection;
class DisplayPlaylistConfigBuilder
@ -95,9 +96,7 @@ class DisplayPlaylistConfigBuilder
return [
'type' => 'video-display',
'version_name' => $module->name,
'settings' => array_replace([
'qr_label' => 'Website',
], $module->settings ?? []),
'settings' => DisplayModuleSettings::merge($module->type, $module->settings),
'videoPlaylist' => $videos,
'footerContent' => $footerContent,
];
@ -133,20 +132,7 @@ class DisplayPlaylistConfigBuilder
return [
'type' => 'b2in',
'version_name' => $module->name,
'settings' => array_replace_recursive([
'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,
], $module->settings ?? []),
'settings' => DisplayModuleSettings::merge($module->type, $module->settings),
'items' => $mediaItems,
];
}
@ -180,19 +166,7 @@ class DisplayPlaylistConfigBuilder
return [
'type' => 'offers',
'version_name' => $module->name,
'settings' => array_replace_recursive([
'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,
],
], $module->settings ?? []),
'settings' => DisplayModuleSettings::merge($module->type, $module->settings),
'slides' => $slides,
];
}

View file

@ -0,0 +1,61 @@
<?php
namespace App\Support;
use App\Enums\DisplayVersionType;
class DisplayModuleSettings
{
/**
* @return array<string, mixed>
*/
public static function defaults(DisplayVersionType|string $type): array
{
$typeValue = $type instanceof DisplayVersionType ? $type->value : $type;
return match ($typeValue) {
DisplayVersionType::VideoDisplay->value => [
'qr_label' => 'Website',
],
DisplayVersionType::B2in->value => [
'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,
'rotation_weights' => ['immobilien' => 70, 'moebel' => 30],
'display_active' => true,
],
DisplayVersionType::Offers->value => [
'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,
],
],
default => [],
};
}
/**
* @param array<string, mixed>|null $settings
* @return array<string, mixed>
*/
public static function merge(DisplayVersionType|string $type, ?array $settings): array
{
return array_replace_recursive(self::defaults($type), $settings ?? []);
}
}