Display Module 13-05-2026
This commit is contained in:
parent
6a65354f4c
commit
9262132325
41 changed files with 496 additions and 334 deletions
|
|
@ -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})");
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
];
|
||||
}
|
||||
|
|
|
|||
61
app/Support/DisplayModuleSettings.php
Normal file
61
app/Support/DisplayModuleSettings.php
Normal 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 ?? []);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue