user()->plugins->contains($this->plugin), 403); $this->blade_code = $this->plugin->render_markup; if ($this->plugin->render_markup_view) { try { $basePath = resource_path('views/' . str_replace('.', '/', $this->plugin->render_markup_view)); $paths = [ $basePath . '.blade.php', $basePath . '.liquid', ]; $this->view_content = null; foreach ($paths as $path) { if (file_exists($path)) { $this->view_content = file_get_contents($path); break; } } } catch (\Exception $e) { $this->view_content = null; } } $this->fillformFields(); $this->data_payload_updated_at = $this->plugin->data_payload_updated_at; } public function fillFormFields(): void { $this->name = $this->plugin->name; $this->data_stale_minutes = $this->plugin->data_stale_minutes; $this->data_strategy = $this->plugin->data_strategy; $this->polling_url = $this->plugin->polling_url; $this->polling_verb = $this->plugin->polling_verb; $this->polling_header = $this->plugin->polling_header; $this->data_payload = json_encode($this->plugin->data_payload, JSON_PRETTY_PRINT); } public function saveMarkup(): void { abort_unless(auth()->user()->plugins->contains($this->plugin), 403); $this->validate(); $this->plugin->update(['render_markup' => $this->blade_code]); } protected array $rules = [ 'name' => 'required|string|max:255', 'data_stale_minutes' => 'required|integer|min:1', 'data_strategy' => 'required|string|in:polling,webhook', 'polling_url' => 'required_if:data_strategy,polling|nullable|url', 'polling_verb' => 'required|string|in:get,post', 'polling_header' => 'nullable|string|max:255', 'blade_code' => 'nullable|string', 'checked_devices' => 'array', 'playlist_name' => 'required_if:selected_playlist,new|string|max:255', 'selected_weekdays' => 'nullable|array', 'active_from' => 'nullable|date_format:H:i', 'active_until' => 'nullable|date_format:H:i', 'selected_playlist' => 'nullable|string', ]; public function editSettings() { abort_unless(auth()->user()->plugins->contains($this->plugin), 403); $validated = $this->validate(); $this->plugin->update($validated); } public function updateData() { if ($this->plugin->data_strategy === 'polling') { // Parse headers from polling_header string $headers = ['User-Agent' => 'usetrmnl/byos_laravel', 'Accept' => 'application/json']; if ($this->plugin->polling_header) { $headerLines = explode("\n", trim($this->plugin->polling_header)); foreach ($headerLines as $line) { $parts = explode(':', $line, 2); if (count($parts) === 2) { $headers[trim($parts[0])] = trim($parts[1]); } } } $response = Http::withHeaders($headers) ->get($this->plugin->polling_url) ->json(); $this->plugin->update([ 'data_payload' => $response, 'data_payload_updated_at' => now() ]); $this->data_payload = json_encode($response, JSON_PRETTY_PRINT); $this->data_payload_updated_at = now(); } } public function getAvailablePlugins() { return auth()->user()->plugins()->where('id', '!=', $this->plugin->id)->get(); } public function getRequiredPluginCount(): int { if ($this->mashup_layout === 'full') { return 1; } return match ($this->mashup_layout) { '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, }; } public function addToPlaylist() { $this->validate([ 'checked_devices' => 'required|array|min:1', 'selected_playlist' => 'required|string', 'mashup_layout' => 'required|string', 'mashup_plugins' => 'required_if:mashup_layout,1Lx1R,1Lx2R,2Lx1R,1Tx1B,2Tx1B,1Tx2B,2x2|array', ]); foreach ($this->checked_devices as $deviceId) { $playlist = null; if ($this->selected_playlist === 'new') { // Create new playlist $this->validate([ 'playlist_name' => 'required|string|max:255', ]); $playlist = \App\Models\Playlist::create([ 'device_id' => $deviceId, 'name' => $this->playlist_name, 'weekdays' => !empty($this->selected_weekdays) ? $this->selected_weekdays : null, 'active_from' => $this->active_from ?: null, 'active_until' => $this->active_until ?: null, ]); } else { $playlist = \App\Models\Playlist::findOrFail($this->selected_playlist); } // Add plugin to playlist $maxOrder = $playlist->items()->max('order') ?? 0; if ($this->mashup_layout === 'full') { $playlist->items()->create([ 'plugin_id' => $this->plugin->id, 'order' => $maxOrder + 1, ]); } else { // Create mashup $pluginIds = array_merge([$this->plugin->id], array_map('intval', $this->mashup_plugins)); \App\Models\PlaylistItem::createMashup( $playlist, $this->mashup_layout, $pluginIds, $this->plugin->name . ' Mashup', $maxOrder + 1 ); } } $this->reset(['checked_devices', 'playlist_name', 'selected_weekdays', 'active_from', 'active_until', 'selected_playlist', 'mashup_layout', 'mashup_plugins']); Flux::modal('add-to-playlist')->close(); } public function getDevicePlaylists($deviceId) { return \App\Models\Playlist::where('device_id', $deviceId)->get(); } public function renderExample(string $example) { switch ($example) { case 'layoutTitle': $markup = $this->renderLayoutWithTitleBar(); break; case 'layout': $markup = $this->renderLayoutBlank(); break; default: $markup = '

Hello World!

'; break; } $this->blade_code = $markup; } public function renderLayoutWithTitleBar(): string { return << 'full']) HTML; } public function renderLayoutBlank(): string { return << 'full']) HTML; } public function renderPreview($size = 'full'): void { abort_unless(auth()->user()->plugins->contains($this->plugin), 403); try { $previewMarkup = $this->plugin->render($size); $this->dispatch('preview-updated', preview: $previewMarkup); } catch (\Exception $e) { $this->dispatch('preview-error', message: $e->getMessage()); } } public function deletePlugin(): void { abort_unless(auth()->user()->plugins->contains($this->plugin), 403); $this->plugin->delete(); $this->redirect(route('plugins.index')); } } ?>

{{$plugin->name}} Recipe

Preview Half-Horizontal Half-Vertical Quadrant Add to Playlist Delete Plugin
Add to Playlist
@foreach(auth()->user()->devices as $device) @endforeach
@if(count($checked_devices) === 1)
@foreach($this->getDevicePlaylists($checked_devices[0]) as $playlist) @endforeach
@endif @if($selected_playlist) @if($selected_playlist === 'new')
@endif
@if($mashup_layout !== 'full')
Mashup Slots
Main Plugin
@for($i = 0; $i < $this->getRequiredPluginCount() - 1; $i++)
Plugin {{ $i + 2 }}:
@foreach($this->getAvailablePlugins() as $availablePlugin) @endforeach
@endfor
@endif @endif
Add to Playlist
Delete {{ $plugin->name }}?

This will remove this plugin from your account.

Cancel Delete plugin
Preview {{ $plugin->name }}

Settings

@if($data_strategy === 'polling')
@else

Send JSON payload with key merge_variables to the webhook URL. The payload will be merged with the plugin data.

@endif
Save
Data Payload {{ $this->data_payload_updated_at?->diffForHumans() ?? 'Never' }}

Markup

@if($plugin->render_markup_view)
Edit view {{ $plugin->render_markup_view }} to update.
@else
Getting started:Responsive Layout with Title Bar Responsive Layout
@endif
@if(!$plugin->render_markup_view)
Save
@endif
@script @endscript