'boolean', 'last_displayed_at' => 'datetime', 'mashup' => 'json', ]; public function playlist(): BelongsTo { return $this->belongsTo(Playlist::class); } public function plugin(): BelongsTo { return $this->belongsTo(Plugin::class); } /** * Check if this playlist item is a mashup */ public function isMashup(): bool { return ! is_null($this->mashup); } /** * Get the mashup name if this is a mashup */ public function getMashupName(): ?string { return $this->mashup['mashup_name'] ?? null; } /** * Get the mashup layout type if this is a mashup */ public function getMashupLayoutType(): ?string { return $this->mashup['mashup_layout'] ?? null; } /** * Get all plugin IDs for this mashup */ public function getMashupPluginIds(): array { return $this->mashup['plugin_ids'] ?? []; } /** * Get the number of plugins required for the current layout */ public function getRequiredPluginCount(): int { if (! $this->isMashup()) { return 1; } return match ($this->getMashupLayoutType()) { '1Lx1R', '1Tx1B' => 2, // Left-Right or Top-Bottom split '1Lx2R', '2Lx1R', '2Tx1B', '1Tx2B' => 3, // Two on one side, one on other '2x2' => 4, // Quadrant default => 1, }; } /** * Get the layout type (horizontal, vertical, or grid) */ public function getLayoutType(): string { if (! $this->isMashup()) { return 'single'; } return match ($this->getMashupLayoutType()) { '1Lx1R', '1Lx2R', '2Lx1R' => 'vertical', '1Tx1B', '2Tx1B', '1Tx2B' => 'horizontal', '2x2' => 'grid', default => 'single', }; } /** * Get the layout size for a plugin based on its position */ public function getLayoutSize(int $position = 0): string { if (! $this->isMashup()) { return 'full'; } return match ($this->getMashupLayoutType()) { '1Lx1R' => 'half_vertical', // Both sides are single plugins '1Tx1B' => 'half_horizontal', // Both sides are single plugins '2Lx1R' => match ($position) { 0, 1 => 'quadrant', // Left side has 2 plugins 2 => 'half_vertical', // Right side has 1 plugin default => 'full' }, '1Lx2R' => match ($position) { 0 => 'half_vertical', // Left side has 1 plugin 1, 2 => 'quadrant', // Right side has 2 plugins default => 'full' }, '2Tx1B' => match ($position) { 0, 1 => 'quadrant', // Top side has 2 plugins 2 => 'half_horizontal', // Bottom side has 1 plugin default => 'full' }, '1Tx2B' => match ($position) { 0 => 'half_horizontal', // Top side has 1 plugin 1, 2 => 'quadrant', // Bottom side has 2 plugins default => 'full' }, '2x2' => 'quadrant', // All positions are quadrants default => 'full' }; } /** * Render all plugins with appropriate layout */ public function render(?Device $device = null): string { if (! $this->isMashup()) { return view('trmnl-layouts.single', [ 'colorDepth' => $device?->deviceModel?->color_depth, 'deviceVariant' => $device?->deviceModel?->name ?? 'og', 'scaleLevel' => $device?->deviceModel?->scale_level, 'slot' => $this->plugin instanceof Plugin ? $this->plugin->render('full', false) : throw new Exception('Invalid plugin instance'), ])->render(); } $pluginMarkups = []; $pluginIds = $this->getMashupPluginIds(); $plugins = Plugin::whereIn('id', $pluginIds)->get(); // Sort the collection to match plugin_ids order $plugins = $plugins->sortBy(function ($plugin) use ($pluginIds) { return array_search($plugin->id, $pluginIds); })->values(); foreach ($plugins as $index => $plugin) { $size = $this->getLayoutSize($index); $pluginMarkups[] = $plugin->render($size, false); } return view('trmnl-layouts.mashup', [ 'colorDepth' => $device?->deviceModel?->color_depth, 'deviceVariant' => $device?->deviceModel?->name ?? 'og', 'scaleLevel' => $device?->deviceModel?->scale_level, 'mashupLayout' => $this->getMashupLayoutType(), 'slot' => implode('', $pluginMarkups), ])->render(); } /** * Available mashup layouts with their descriptions */ public static function getAvailableLayouts(): array { return [ '1Lx1R' => '1 Left - 1 Right (2 plugins)', '1Lx2R' => '1 Left - 2 Right (3 plugins)', '2Lx1R' => '2 Left - 1 Right (3 plugins)', '1Tx1B' => '1 Top - 1 Bottom (2 plugins)', '2Tx1B' => '2 Top - 1 Bottom (3 plugins)', '1Tx2B' => '1 Top - 2 Bottom (3 plugins)', '2x2' => 'Quadrant (4 plugins)', ]; } /** * Get the required number of plugins for a given layout */ public static function getRequiredPluginCountForLayout(string $layout): int { return match ($layout) { '1Lx1R', '1Tx1B' => 2, '1Lx2R', '2Lx1R', '2Tx1B', '1Tx2B' => 3, '2x2' => 4, default => 1, }; } /** * Create a new mashup with the given layout and plugins */ public static function createMashup(Playlist $playlist, string $layout, array $pluginIds, string $name, $order): self { return static::create([ 'playlist_id' => $playlist->id, 'plugin_id' => $pluginIds[0], // First plugin is the main plugin 'mashup' => [ 'mashup_layout' => $layout, 'mashup_name' => $name, 'plugin_ids' => $pluginIds, ], 'is_active' => true, 'order' => $order, ]); } }