Display CMS Optimierungen 29-05-2026

- 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>
This commit is contained in:
Kevin Adametz 2026-05-29 15:57:33 +00:00
parent 9262132325
commit 6c6d683b9a
42 changed files with 2267 additions and 13905 deletions

View file

@ -115,9 +115,45 @@ test('returns playlist with b2in config', function () {
$response->assertSuccessful();
$response->assertJsonPath('playlist.0.type', 'b2in');
$response->assertJsonPath('playlist.0.settings.theme', 'dark');
$response->assertJsonPath('playlist.0.settings.show_footer', true);
$response->assertJsonPath('playlist.0.settings.logo_position', 'top-left');
$response->assertJsonPath('playlist.0.settings.claim_position', 'top-right');
$response->assertJsonPath('playlist.0.items.0.category', 'immobilien');
});
test('b2in config passes through custom brand positions and footer toggle', function () {
$version = DisplayVersion::factory()->create([
'type' => 'b2in',
'settings' => [
'show_footer' => false,
'logo_position' => 'bottom-right',
'claim_position' => 'top-left',
],
]);
DisplayVersionItem::factory()->create([
'display_version_id' => $version->id,
'item_type' => 'media',
'content' => [
'category' => 'sonstiges',
'media_type' => 'image',
'media_url' => '../assets/test.jpg',
'headline' => 'H',
'subline' => 'S',
'duration_seconds' => 10,
],
]);
$display = Display::factory()->create();
publishDisplayModules($display, [$version->id]);
$response = $this->getJson("/api/display/{$display->id}/config");
$response->assertSuccessful();
$response->assertJsonPath('playlist.0.settings.show_footer', false);
$response->assertJsonPath('playlist.0.settings.logo_position', 'bottom-right');
$response->assertJsonPath('playlist.0.settings.claim_position', 'top-left');
$response->assertJsonPath('playlist.0.items.0.category', 'sonstiges');
});
test('returns playlist with offers config', function () {
$version = DisplayVersion::factory()->create([
'type' => 'offers',
@ -320,38 +356,32 @@ test('module preview returns a single module config', function () {
$response->assertJsonPath('playlist.0.items.0.category', 'moebel');
});
test('module preview exposes configurable player chrome settings', function () {
test('offers slides carry their own logo and brand chrome', function () {
$version = DisplayVersion::factory()->create([
'type' => 'offers',
'name' => 'Custom Chrome',
'settings' => [
'logo_url' => '/storage/display-media/logo.svg',
'brand_text' => 'Musterstadt',
'footer_claim' => 'Beratung im Schauraum',
'footer_url' => 'cabinet-bielefeld.de',
'qr_default_title' => 'Mehr Infos',
'qr_subtitle' => 'Jetzt scannen',
],
]);
DisplayVersionItem::factory()->create([
'display_version_id' => $version->id,
'item_type' => 'slide',
'content' => [
'type' => 'intro',
'type' => 'detail',
'title' => 'Intro',
'image_url' => '../assets/intro.jpg',
'show_logo' => true,
'logo_url' => '/storage/display-media/logo.svg',
'brand_text' => 'Musterstadt',
'brand_tagline' => 'Beratung im Schauraum',
],
]);
$response = $this->getJson("/api/display/module/{$version->id}/preview");
$response->assertSuccessful();
$response->assertJsonPath('playlist.0.settings.logo_url', '/storage/display-media/logo.svg');
$response->assertJsonPath('playlist.0.settings.brand_text', 'Musterstadt');
$response->assertJsonPath('playlist.0.settings.footer_claim', 'Beratung im Schauraum');
$response->assertJsonPath('playlist.0.settings.footer_url', 'cabinet-bielefeld.de');
$response->assertJsonPath('playlist.0.settings.qr_default_title', 'Mehr Infos');
$response->assertJsonPath('playlist.0.settings.qr_subtitle', 'Jetzt scannen');
$response->assertJsonPath('playlist.0.slides.0.show_logo', true);
$response->assertJsonPath('playlist.0.slides.0.logo_url', '/storage/display-media/logo.svg');
$response->assertJsonPath('playlist.0.slides.0.brand_text', 'Musterstadt');
$response->assertJsonPath('playlist.0.slides.0.brand_tagline', 'Beratung im Schauraum');
});
test('module item preview returns only the selected slide', function () {
@ -416,11 +446,28 @@ test('display player keeps previews in a strict 9 by 16 viewport', function () {
->toContain('Aktive Live-Displays')
->toContain('renderOverview(data.displays || [])')
->toContain('translate(${offsetX}px, ${offsetY}px) scale(${scale})')
->toContain('this.settings.logo_url')
->toContain('this.settings.footer_claim')
->toContain('this.settings.footer_url')
->toContain('this.settings.header_logo_url')
->toContain('this.settings.qr_label');
->toContain('this.settings.qr_label')
->toContain('this.settings.show_footer !== false')
->toContain('this.settings.show_logo !== false')
->toContain('this.settings.show_claim !== false')
->toContain('this.settings.logo_position')
->toContain('this.settings.claim_position')
->toContain('b2in-brand-logo pos-')
->toContain('b2in-scrim')
->toContain('.b2in-layer.no-footer .b2in-text');
});
test('display player stops looping when the playlist has no playable content', function () {
$player = file_get_contents(public_path('_cabinet/display/index.html'));
expect($player)
->toContain('versionHasContent(version)')
->toContain('showEmptyPlaylist()')
->toContain('this.emptyVersionStreak >= this.playlist.length')
->toContain('Noch keine Inhalte vorhanden');
});
test('preview player pages are reachable for valid display and module previews', function () {
@ -438,6 +485,20 @@ test('preview player pages are reachable for valid display and module previews',
->assertSuccessful();
});
test('preview player is served with a revalidation cache header', function () {
$display = Display::factory()->create(['preview_token' => 'fresh-token']);
$version = DisplayVersion::factory()->create();
foreach ([
'/preview/fresh-token',
"/preview/module/{$version->id}",
] as $url) {
$response = $this->get($url);
$response->assertSuccessful();
expect($response->headers->get('Cache-Control'))->toContain('no-cache');
}
});
test('existing display config api still works', function () {
$response = $this->getJson('/api/display/config');