From 92621323251b0579254f5e9e998eefec37d0cb6d Mon Sep 17 00:00:00 2001 From: Kevin Adametz Date: Wed, 13 May 2026 14:34:08 +0200 Subject: [PATCH] Display Module 13-05-2026 --- .devcontainer/docker-compose.dev.yml | 2 +- .devcontainer/php-upload-limits.ini | 2 + .../Commands/MigrateLegacyDisplays.php | 13 +- .../Api/DisplayVersionApiController.php | 29 ++++ app/Livewire/Admin/Cms/DisplayList.php | 21 +-- .../Admin/Cms/DisplayVersionEditor.php | 42 +----- app/Livewire/Admin/Cms/DisplayVersionList.php | 39 ++---- .../Admin/Cms/MediaLibraryUploader.php | 2 +- app/Livewire/Admin/Cms/MediaPicker.php | 2 +- app/Models/Display.php | 35 ----- app/Models/DisplayVersion.php | 9 +- app/Services/DisplayPlaylistConfigBuilder.php | 34 +---- app/Support/DisplayModuleSettings.php | 61 ++++++++ config/display.php | 3 + ...600_drop_display_display_version_table.php | 26 ++++ .../00-entwicklungskonzept.md | 37 ++--- dev/displays-11-05-2026/01-status.md | 38 ++++- docker-compose.yml | 2 +- .../cms/media-library-uploader.blade.php | 2 +- .../core/src/Helpers/MediaLibraryUploader.php | 2 +- .../flux-cms/core/src/Helpers/MediaPicker.php | 2 +- .../core/src/Helpers/MediaUploader.php | 2 +- public/_cabinet/_docs/QUICK_START.md | 4 +- .../_docs/VIDEO_OPTIMIZATION_README.md | 2 +- public/_cabinet/display/index.html | 130 +++++++++++++++++- .../components/layouts/app/sidebar.blade.php | 4 +- .../admin/cms/display-dashboard.blade.php | 32 +++-- .../livewire/admin/cms/display-list.blade.php | 26 +++- .../cms/display-version-editor.blade.php | 2 + .../cms/media-library-uploader.blade.php | 2 +- .../partials/version-editor-video.blade.php | 11 +- .../views/livewire/partner/my-data.blade.php | 24 ++-- .../livewire/products/form-standard.blade.php | 8 +- .../livewire/products/form-teaser.blade.php | 8 +- routes/admin.php | 4 - routes/domains.php | 2 + tests/Feature/Admin/Cms/MediaPickerTest.php | 7 + tests/Feature/DisplayListTest.php | 33 +++-- .../Feature/DisplayPlaylistMigrationTest.php | 61 +------- tests/Feature/DisplayVersionApiTest.php | 38 ++++- tests/Feature/DisplayVersionTest.php | 27 ++-- 41 files changed, 496 insertions(+), 334 deletions(-) create mode 100644 app/Support/DisplayModuleSettings.php create mode 100644 database/migrations/2026_05_13_103600_drop_display_display_version_table.php diff --git a/.devcontainer/docker-compose.dev.yml b/.devcontainer/docker-compose.dev.yml index df7d4f4..04f71ac 100644 --- a/.devcontainer/docker-compose.dev.yml +++ b/.devcontainer/docker-compose.dev.yml @@ -62,7 +62,7 @@ services: MYSQL_EXTRA_OPTIONS: --default-authentication-plugin=mysql_native_password volumes: - '../:/var/www/html' - - './php-upload-limits.ini:/etc/php/8.4/cli/conf.d/99-upload-limits.ini:ro' + - './php-upload-limits.ini:/etc/php/8.4/cli/conf.d/100-b2in-upload-limits.ini:ro' networks: - sail depends_on: diff --git a/.devcontainer/php-upload-limits.ini b/.devcontainer/php-upload-limits.ini index ed99be6..4a13806 100644 --- a/.devcontainer/php-upload-limits.ini +++ b/.devcontainer/php-upload-limits.ini @@ -1,2 +1,4 @@ +[PHP] +; Muss über Sail-Standard (99-sail.ini: 100M) liegen; siehe Display-Mediathek / Livewire-Uploads (~200 MB). upload_max_filesize = 210M post_max_size = 210M diff --git a/app/Console/Commands/MigrateLegacyDisplays.php b/app/Console/Commands/MigrateLegacyDisplays.php index 2ea2dc0..4ddcb64 100644 --- a/app/Console/Commands/MigrateLegacyDisplays.php +++ b/app/Console/Commands/MigrateLegacyDisplays.php @@ -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})"); diff --git a/app/Http/Controllers/Api/DisplayVersionApiController.php b/app/Http/Controllers/Api/DisplayVersionApiController.php index 37ca1ac..b101124 100644 --- a/app/Http/Controllers/Api/DisplayVersionApiController.php +++ b/app/Http/Controllers/Api/DisplayVersionApiController.php @@ -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) { diff --git a/app/Livewire/Admin/Cms/DisplayList.php b/app/Livewire/Admin/Cms/DisplayList.php index dda18eb..1a32b5c 100644 --- a/app/Livewire/Admin/Cms/DisplayList.php +++ b/app/Livewire/Admin/Cms/DisplayList.php @@ -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 $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 diff --git a/app/Livewire/Admin/Cms/DisplayVersionEditor.php b/app/Livewire/Admin/Cms/DisplayVersionEditor.php index c79d185..37d5561 100644 --- a/app/Livewire/Admin/Cms/DisplayVersionEditor.php +++ b/app/Livewire/Admin/Cms/DisplayVersionEditor.php @@ -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 - */ - 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() diff --git a/app/Livewire/Admin/Cms/DisplayVersionList.php b/app/Livewire/Admin/Cms/DisplayVersionList.php index 4a0ceb4..dad96a1 100644 --- a/app/Livewire/Admin/Cms/DisplayVersionList.php +++ b/app/Livewire/Admin/Cms/DisplayVersionList.php @@ -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(); diff --git a/app/Livewire/Admin/Cms/MediaLibraryUploader.php b/app/Livewire/Admin/Cms/MediaLibraryUploader.php index 8b495b6..90c1986 100644 --- a/app/Livewire/Admin/Cms/MediaLibraryUploader.php +++ b/app/Livewire/Admin/Cms/MediaLibraryUploader.php @@ -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); diff --git a/app/Livewire/Admin/Cms/MediaPicker.php b/app/Livewire/Admin/Cms/MediaPicker.php index 14eb146..ee4e9af 100644 --- a/app/Livewire/Admin/Cms/MediaPicker.php +++ b/app/Livewire/Admin/Cms/MediaPicker.php @@ -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); diff --git a/app/Models/Display.php b/app/Models/Display.php index 279ec51..f73082a 100644 --- a/app/Models/Display.php +++ b/app/Models/Display.php @@ -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 */ @@ -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) { diff --git a/app/Models/DisplayVersion.php b/app/Models/DisplayVersion.php index 20cf372..539a805 100644 --- a/app/Models/DisplayVersion.php +++ b/app/Models/DisplayVersion.php @@ -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 + */ + public function playlistItems(): HasMany { - return $this->belongsToMany(Display::class, 'display_display_version') - ->withPivot('sort_order'); + return $this->hasMany(DisplayPlaylistItem::class); } /** diff --git a/app/Services/DisplayPlaylistConfigBuilder.php b/app/Services/DisplayPlaylistConfigBuilder.php index ea05231..2163ed8 100644 --- a/app/Services/DisplayPlaylistConfigBuilder.php +++ b/app/Services/DisplayPlaylistConfigBuilder.php @@ -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, ]; } diff --git a/app/Support/DisplayModuleSettings.php b/app/Support/DisplayModuleSettings.php new file mode 100644 index 0000000..fc3bc1d --- /dev/null +++ b/app/Support/DisplayModuleSettings.php @@ -0,0 +1,61 @@ + + */ + 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|null $settings + * @return array + */ + public static function merge(DisplayVersionType|string $type, ?array $settings): array + { + return array_replace_recursive(self::defaults($type), $settings ?? []); + } +} diff --git a/config/display.php b/config/display.php index 9443e45..f1fff65 100644 --- a/config/display.php +++ b/config/display.php @@ -22,4 +22,7 @@ return [ // Haupt-Domain 'domain' => env('DISPLAY_DOMAIN', 'b2in.eu'), + // Öffentliche Player-URL der Display-Domain + 'player_url' => env('DISPLAY_PLAYER_URL', 'https://cabinet.b2in.eu/display'), + ]; diff --git a/database/migrations/2026_05_13_103600_drop_display_display_version_table.php b/database/migrations/2026_05_13_103600_drop_display_display_version_table.php new file mode 100644 index 0000000..cfb9b9a --- /dev/null +++ b/database/migrations/2026_05_13_103600_drop_display_display_version_table.php @@ -0,0 +1,26 @@ +id(); + $table->foreignId('display_id')->constrained()->cascadeOnDelete(); + $table->foreignId('display_version_id')->constrained()->cascadeOnDelete(); + $table->integer('sort_order')->default(0); + $table->timestamps(); + + $table->unique(['display_id', 'display_version_id']); + }); + } +}; diff --git a/dev/displays-11-05-2026/00-entwicklungskonzept.md b/dev/displays-11-05-2026/00-entwicklungskonzept.md index a6fdce2..07d7534 100644 --- a/dev/displays-11-05-2026/00-entwicklungskonzept.md +++ b/dev/displays-11-05-2026/00-entwicklungskonzept.md @@ -16,8 +16,8 @@ Im Admin-Portal unter `portal.b2in.test/admin/cms/` existiert der Bereich **Stor |---|---| | `cms/display-dashboard` | Übersicht / Einstieg | | `cms/display-media` | Mediathek (eigene Display-Mediathek, getrennt von Flux CMS) | -| `cms/display-versions` | Inhalts-„Versionen" | -| `cms/display-versions/{id}/edit` | Editor für eine Version | +| `cms/display-modules` | Inhalts-Module | +| `cms/display-modules/{id}/edit` | Editor für ein Modul | | `cms/displays` | Physische Displays + Playlist-Zuweisung | | `cms/cabinet-tablet` | Info-Tablet (Öffnungszeiten/Status) | @@ -25,13 +25,14 @@ Im Admin-Portal unter `portal.b2in.test/admin/cms/` existiert der Bereich **Stor ``` displays (5 Datensätze live) -└── m:n via display_display_version (sort_order = Playlist-Reihenfolge) - └── display_versions (5 Datensätze live) - ├── type: video-display | b2in | offers - ├── settings: JSON - └── 1:n display_version_items (17 Datensätze live) - ├── item_type: video | footer | media | slide - └── content: JSON +└── 1:n display_playlists (Live/Entwurf) + └── 1:n display_playlist_items (sort_order = Playlist-Reihenfolge) + └── display_versions (technisch), fachlich Module + ├── type: video-display | b2in | offers + ├── settings: JSON + └── 1:n display_version_items + ├── item_type: video | footer | media | slide + └── content: JSON ``` ### 1.3 Echte Live-Daten (Stand heute) @@ -83,7 +84,7 @@ displays (5 Datensätze live) | Mediathek | **Display-Mediathek** *(unverändert)* | Bilder/Videos für Displays. | | Info-Tablet | **Info-Tablet** *(unverändert)* | Eingangs-Tablet mit Öffnungszeiten. | -Routen werden entsprechend umbenannt: `display-versions` → `display-modules`. +Routen wurden entsprechend umbenannt: `display-versions` → `display-modules`. Die Übergangs-Redirects wurden in Phase 7 entfernt. ### 2.2 Neues mentales Modell @@ -181,7 +182,7 @@ für jedes Display D: erstelle display_playlists (display_id=D.id, status='published', published_at=now()) für jeden Eintrag aus display_display_version (display_id=D.id), sortiert nach sort_order: erstelle display_playlist_items (...) -display_display_version-Tabelle bleibt vorerst → wird in Phase 7 dropped. +display_display_version-Tabelle wurde in Phase 7 dropped. ``` **Ergebnis nach Migration:** Alle 5 Displays haben eine Live-Bespielung, kein Entwurf. Konsumenten-API liefert exakt das gleiche wie heute. @@ -355,15 +356,15 @@ Jede Phase liefert ein in sich getestetes, deploybares Inkrement. - [ ] Player-Templates: Single-Module-Modus ### Phase 6 – Umbenennung & Onboarding (Tag 3) -- [ ] Routen: `display-versions` → `display-modules` (mit 301-Redirect) -- [ ] Komponenten / Views umbenennen -- [ ] Dashboard-Texte / Hilfe-Bausteine aktualisieren -- [ ] Tooltips an Schlüsselstellen +- [x] Routen: `display-versions` → `display-modules` +- [x] Komponenten / Views umbenennen +- [x] Dashboard-Texte / Hilfe-Bausteine aktualisieren +- [x] Tooltips an Schlüsselstellen ### Phase 7 – Aufräumen (Tag 4) -- [ ] `display_display_version`-Tabelle dropped -- [ ] Alte Routen entfernt -- [ ] `DISPLAY_CMS_README.md` aktualisiert (in `dev/` ablegen) +- [x] `display_display_version`-Tabelle dropped +- [x] Alte Routen entfernt +- [x] Entwicklerdoku in `dev/displays-11-05-2026` aktualisiert - [ ] Vollständiger Test-Run --- diff --git a/dev/displays-11-05-2026/01-status.md b/dev/displays-11-05-2026/01-status.md index e29d514..8389a3d 100644 --- a/dev/displays-11-05-2026/01-status.md +++ b/dev/displays-11-05-2026/01-status.md @@ -245,7 +245,7 @@ Umsetzung: ## Phase 6 – Umbenennung Versionen → Module + Onboarding -**Ziel:** Die Admin-UI verwendet den fachlich korrekten Begriff „Module“. Alte URLs bleiben kompatibel und leiten weiter. +**Ziel:** Die Admin-UI verwendet den fachlich korrekten Begriff „Module“. Alte URLs wurden während der Übergangsphase per 301 weitergeleitet und in Phase 7 entfernt. ### Stand 12.05.2026 – ✅ abgeschlossen @@ -261,9 +261,9 @@ Dateien: Umsetzung: - Neue Routen: `admin/cms/display-modules` und `admin/cms/display-modules/{displayVersion}/edit` - Neue Routennamen: `admin.cms.display-modules` und `admin.cms.display-module-edit` -- Alte `display-versions`-Routen bleiben erhalten und leiten per 301 auf die Modul-Routen weiter +- Alte `display-versions`-Routen waren während der Übergangsphase als 301-Redirects aktiv und wurden in Phase 7 entfernt - Sidebar, Dashboard, Listen- und Editor-Texte verwenden „Module“ -- Technische Modell-/Klassennamen bleiben bis Phase 7 kompatibel bei `DisplayVersion` +- Technische Modell-/Klassennamen bleiben bei `DisplayVersion`, da sie fachlich weiterhin die wiederverwendbaren Module abbilden #### Tests @@ -276,3 +276,35 @@ tests/Feature/DisplayPlaylistMigrationTest.php – ok Insgesamt 64 grüne Tests für Phasen 5/6 und die angrenzenden Display-Flows. Pint clean. +--- + +## Phase 7 – Technisches Aufräumen & Optimierung + +**Ziel:** Nach Stabilisierung des neuen Playlist-Flows wird die alte Pivot-Kompatibilität entfernt und der Modul-Editor weiter vereinheitlicht. + +### Stand 13.05.2026 – ✅ umgesetzt + +Dateien: +- `app/Models/Display.php` +- `app/Models/DisplayVersion.php` +- `app/Livewire/Admin/Cms/DisplayList.php` +- `app/Console/Commands/MigrateLegacyDisplays.php` +- `app/Support/DisplayModuleSettings.php` +- `app/Services/DisplayPlaylistConfigBuilder.php` +- `app/Livewire/Admin/Cms/DisplayVersionEditor.php` +- `app/Livewire/Admin/Cms/DisplayVersionList.php` +- `routes/admin.php` +- `database/migrations/2026_05_13_103600_drop_display_display_version_table.php` +- `resources/views/livewire/admin/cms/display-list.blade.php` +- `resources/views/livewire/admin/cms/display-version-editor.blade.php` +- `resources/views/livewire/admin/cms/partials/version-editor-video.blade.php` + +Umsetzung: +- Alte Pivot-Tabelle `display_display_version` wird per Migration entfernt +- Legacy-Relationen `Display::versions()` und `DisplayVersion::displays()` wurden entfernt +- Display-Bearbeitung, Draft-Veröffentlichung und Legacy-Migrations-Command schreiben ausschließlich in `display_playlists` und `display_playlist_items` +- Alte `display-versions`-Redirect-Routen wurden entfernt; die Admin-UI nutzt nur noch `display-modules` +- Modul-Settings-Defaults liegen zentral in `App\Support\DisplayModuleSettings` und werden von Editor, Listen-Erstellung und API-Config-Builder gemeinsam genutzt +- Admin-Iframes laden per `loading="lazy"` verzögert, um die parallelen Player-Vorschauen leichter zu halten +- Video-Display-Items zeigen im Editor sichtbar an, ob die Quelle aus der Mediathek oder aus einem Legacy-Dateinamen kommt + diff --git a/docker-compose.yml b/docker-compose.yml index e3a8b9e..f77ab25 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -32,7 +32,7 @@ services: REDIS_HOST: global-redis volumes: - '.:/var/www/html' - - './.devcontainer/php-upload-limits.ini:/etc/php/8.4/cli/conf.d/99-upload-limits.ini:ro' + - './.devcontainer/php-upload-limits.ini:/etc/php/8.4/cli/conf.d/100-b2in-upload-limits.ini:ro' networks: - sail - proxy diff --git a/packages/flux-cms/core/resources/views/admin-reference/cms/media-library-uploader.blade.php b/packages/flux-cms/core/resources/views/admin-reference/cms/media-library-uploader.blade.php index f571e05..4924e6f 100644 --- a/packages/flux-cms/core/resources/views/admin-reference/cms/media-library-uploader.blade.php +++ b/packages/flux-cms/core/resources/views/admin-reference/cms/media-library-uploader.blade.php @@ -3,7 +3,7 @@ accept="image/jpeg,image/png,image/gif,image/webp,image/svg+xml,.pdf,.doc,.docx,.jpg,.jpeg,.png"> diff --git a/packages/flux-cms/core/src/Helpers/MediaLibraryUploader.php b/packages/flux-cms/core/src/Helpers/MediaLibraryUploader.php index 8b495b6..90c1986 100644 --- a/packages/flux-cms/core/src/Helpers/MediaLibraryUploader.php +++ b/packages/flux-cms/core/src/Helpers/MediaLibraryUploader.php @@ -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); diff --git a/packages/flux-cms/core/src/Helpers/MediaPicker.php b/packages/flux-cms/core/src/Helpers/MediaPicker.php index 14eb146..ee4e9af 100644 --- a/packages/flux-cms/core/src/Helpers/MediaPicker.php +++ b/packages/flux-cms/core/src/Helpers/MediaPicker.php @@ -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); diff --git a/packages/flux-cms/core/src/Helpers/MediaUploader.php b/packages/flux-cms/core/src/Helpers/MediaUploader.php index c06737d..059cdd0 100644 --- a/packages/flux-cms/core/src/Helpers/MediaUploader.php +++ b/packages/flux-cms/core/src/Helpers/MediaUploader.php @@ -18,7 +18,7 @@ class MediaUploader extends Component public string $directory = 'cms/uploads'; - #[Validate('file|max:10240')] + #[Validate('file|max:204800')] public $file; public function updatedFile(): void diff --git a/public/_cabinet/_docs/QUICK_START.md b/public/_cabinet/_docs/QUICK_START.md index 69a1f80..3d2f744 100644 --- a/public/_cabinet/_docs/QUICK_START.md +++ b/public/_cabinet/_docs/QUICK_START.md @@ -85,11 +85,11 @@ Beim Hochladen neuer Videos beachten: - [ ] Format: **MP4** (H.264 + AAC) - [ ] Auflösung: **Max 1920x1080** - [ ] Bitrate: **5-10 Mbps** -- [ ] Dateigröße: **Max 100 MB** +- [ ] Dateigröße: **Max 200 MB** - [ ] Länge: **15-60 Sekunden** (optimal) ### ⚠️ Vermeiden: -- ❌ Zu große Dateien (>100MB) +- ❌ Zu große Dateien (>200MB) - ❌ Zu hohe Bitrate (>10 Mbps) - ❌ Zu lange Videos (>3 Min) - ❌ Exotische Formate (MOV, AVI, WMV) diff --git a/public/_cabinet/_docs/VIDEO_OPTIMIZATION_README.md b/public/_cabinet/_docs/VIDEO_OPTIMIZATION_README.md index a8b86d3..7e69043 100644 --- a/public/_cabinet/_docs/VIDEO_OPTIMIZATION_README.md +++ b/public/_cabinet/_docs/VIDEO_OPTIMIZATION_README.md @@ -214,7 +214,7 @@ setTimeout(() => { ### 3. **Dateigrößen** - **Optimal:** 10-50 MB pro Video -- **Maximum:** 100 MB pro Video +- **Maximum:** 200 MB pro Video - **Warum:** Schnelleres Laden, weniger Buffering ### 4. **Playlist-Größe** diff --git a/public/_cabinet/display/index.html b/public/_cabinet/display/index.html index 4873654..96e9eff 100644 --- a/public/_cabinet/display/index.html +++ b/public/_cabinet/display/index.html @@ -411,6 +411,56 @@ .status-message { font-weight: 300; opacity: 0.7; } .status-error { color: #ef4444; font-weight: 500; } .status-sub { font-size: 1.4vh; opacity: 0.4; margin-top: 1vh; } + + .display-overview { + position: fixed; inset: 0; z-index: 10000; + overflow-y: auto; background: radial-gradient(circle at top, #12364d 0, #05070a 42%, #000 100%); + color: #fff; cursor: auto; padding: clamp(24px, 5vw, 72px); + } + .display-overview.hidden { display: none; } + .display-overview__inner { width: min(1120px, 100%); margin: 0 auto; } + .display-overview__eyebrow { + color: #38bdf8; font-size: 13px; font-weight: 700; + letter-spacing: 0.16em; text-transform: uppercase; margin-bottom: 12px; + } + .display-overview h1 { + font-size: clamp(34px, 6vw, 76px); line-height: 0.95; + letter-spacing: -0.05em; margin-bottom: 18px; + } + .display-overview__intro { + max-width: 720px; color: rgba(255,255,255,0.68); + font-size: clamp(16px, 2vw, 22px); line-height: 1.5; margin-bottom: 36px; + } + .display-overview__grid { + display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); + gap: 18px; + } + .display-card { + display: flex; flex-direction: column; gap: 16px; + min-height: 220px; padding: 24px; border-radius: 28px; + border: 1px solid rgba(255,255,255,0.14); + background: rgba(255,255,255,0.08); color: #fff; text-decoration: none; + box-shadow: 0 24px 70px rgba(0,0,0,0.24); + transition: transform 0.18s ease, border-color 0.18s ease, background 0.18s ease; + } + .display-card:hover { + transform: translateY(-2px); + border-color: rgba(56,189,248,0.55); + background: rgba(255,255,255,0.12); + } + .display-card__badges { display: flex; flex-wrap: wrap; gap: 8px; } + .display-badge { + border-radius: 999px; padding: 6px 10px; font-size: 12px; font-weight: 700; + background: rgba(34,197,94,0.18); color: #86efac; border: 1px solid rgba(134,239,172,0.28); + } + .display-badge--live { background: rgba(56,189,248,0.18); color: #7dd3fc; border-color: rgba(125,211,252,0.28); } + .display-card__title { font-size: 28px; font-weight: 700; letter-spacing: -0.03em; } + .display-card__meta { display: grid; gap: 6px; color: rgba(255,255,255,0.62); font-size: 15px; } + .display-card__action { margin-top: auto; color: #7dd3fc; font-weight: 700; } + .display-overview__empty { + border: 1px dashed rgba(255,255,255,0.24); border-radius: 28px; + padding: 32px; color: rgba(255,255,255,0.62); + } @@ -434,6 +484,18 @@
Neustart in Kürze...
+ +