You've already forked Atomcms-edit
Add radio embed widget, SSE real-time, song history, moderation panel, and Auto DJ
- Embed widget: standalone iframe player with dark/light/transparent themes, copy-paste embed code admin page - Real-time SSE: streaming now-playing/listeners/dj events, replaces polling in radio-player and embed - Song history: auto-records song changes to radio_song_plays table, Filament resource to view - DJ moderation: unified panel for shouts approval, song request queue, DJ applications - Auto DJ: playlist management with round-robin playback when no DJ is live - Refactored radio-player Alpine component to use EventSource API with auto-reconnect
This commit is contained in:
@@ -0,0 +1,207 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Pages\Radio;
|
||||
|
||||
use App\Models\RadioAutoDjTrack;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\ActionGroup;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\Toggle;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Pages\Page;
|
||||
use Filament\Schemas\Components\Section;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Tables\Columns\IconColumn;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Concerns\InteractsWithTable;
|
||||
use Filament\Tables\Contracts\HasTable;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
final class AutoDjPlaylist extends Page implements HasTable
|
||||
{
|
||||
use InteractsWithTable;
|
||||
|
||||
#[\Override]
|
||||
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-play-circle';
|
||||
|
||||
#[\Override]
|
||||
protected static string|\UnitEnum|null $navigationGroup = 'Radio';
|
||||
|
||||
#[\Override]
|
||||
protected static ?string $navigationLabel = 'Auto DJ Playlist';
|
||||
|
||||
#[\Override]
|
||||
protected static ?string $title = 'Auto DJ Playlist';
|
||||
|
||||
#[\Override]
|
||||
protected string $view = 'filament.pages.radio.auto-dj-playlist';
|
||||
|
||||
public function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->query(RadioAutoDjTrack::query())
|
||||
->defaultSort('sort_order')
|
||||
->columns([
|
||||
TextColumn::make('sort_order')
|
||||
->label('#')
|
||||
->sortable()
|
||||
->width(50),
|
||||
TextColumn::make('title')
|
||||
->label('Titel')
|
||||
->searchable()
|
||||
->sortable(),
|
||||
TextColumn::make('artist')
|
||||
->label('Artiest')
|
||||
->searchable()
|
||||
->sortable(),
|
||||
TextColumn::make('duration')
|
||||
->label('Duur')
|
||||
->formatStateUsing(fn ($state) => $state ? gmdate('i:s', $state) : '-'),
|
||||
TextColumn::make('play_count')
|
||||
->label('Gespeeld')
|
||||
->sortable(),
|
||||
TextColumn::make('last_played_at')
|
||||
->label('Laatst gespeeld')
|
||||
->dateTime('d-m-Y H:i')
|
||||
->placeholder('Nooit'),
|
||||
IconColumn::make('is_active')
|
||||
->label('Actief')
|
||||
->boolean()
|
||||
->trueIcon('heroicon-o-check-circle')
|
||||
->falseIcon('heroicon-o-x-circle')
|
||||
->trueColor('success')
|
||||
->falseColor('danger'),
|
||||
])
|
||||
->recordActions(function ($record) {
|
||||
return [
|
||||
ActionGroup::make([
|
||||
Action::make('toggle_active')
|
||||
->label($record->is_active ? 'Deactiveren' : 'Activeren')
|
||||
->icon($record->is_active ? 'heroicon-o-pause' : 'heroicon-o-play')
|
||||
->action(fn () => $this->toggleActive($record)),
|
||||
Action::make('move_up')
|
||||
->label('Omhoog')
|
||||
->icon('heroicon-o-chevron-up')
|
||||
->action(fn () => $this->moveUp($record)),
|
||||
Action::make('move_down')
|
||||
->label('Omlaag')
|
||||
->icon('heroicon-o-chevron-down')
|
||||
->action(fn () => $this->moveDown($record)),
|
||||
Action::make('delete')
|
||||
->label('Verwijderen')
|
||||
->icon('heroicon-o-trash')
|
||||
->color('danger')
|
||||
->requiresConfirmation()
|
||||
->action(fn () => $record->delete()),
|
||||
]),
|
||||
];
|
||||
})
|
||||
->toolbarActions([
|
||||
Action::make('create')
|
||||
->label('Track Toevoegen')
|
||||
->icon('heroicon-o-plus')
|
||||
->color('primary')
|
||||
->form([
|
||||
TextInput::make('title')
|
||||
->label('Titel')
|
||||
->required()
|
||||
->maxLength(255),
|
||||
TextInput::make('artist')
|
||||
->label('Artiest')
|
||||
->maxLength(255),
|
||||
TextInput::make('album')
|
||||
->label('Album')
|
||||
->maxLength(255),
|
||||
TextInput::make('artwork_url')
|
||||
->label('Afbeelding URL')
|
||||
->url(),
|
||||
TextInput::make('duration')
|
||||
->label('Duur (seconden)')
|
||||
->numeric()
|
||||
->minValue(0),
|
||||
Toggle::make('is_active')
|
||||
->label('Actief')
|
||||
->default(true),
|
||||
])
|
||||
->action(function (array $data) {
|
||||
$this->createTrack($data);
|
||||
}),
|
||||
])
|
||||
->poll('30s');
|
||||
}
|
||||
|
||||
public function getStatusBadge(): string
|
||||
{
|
||||
$autoDj = cache('radio_auto_dj_active');
|
||||
|
||||
if ($autoDj) {
|
||||
return 'Auto DJ is actief - speelt: ' . ($autoDj['title'] ?? 'onbekend');
|
||||
}
|
||||
|
||||
return 'Auto DJ is niet actief';
|
||||
}
|
||||
|
||||
private function createTrack(array $data): void
|
||||
{
|
||||
$maxOrder = RadioAutoDjTrack::max('sort_order') ?? 0;
|
||||
|
||||
RadioAutoDjTrack::create([
|
||||
'title' => $data['title'],
|
||||
'artist' => $data['artist'] ?? null,
|
||||
'album' => $data['album'] ?? null,
|
||||
'artwork_url' => $data['artwork_url'] ?? null,
|
||||
'duration' => $data['duration'] ? (int) $data['duration'] : null,
|
||||
'is_active' => $data['is_active'] ?? true,
|
||||
'sort_order' => $maxOrder + 1,
|
||||
]);
|
||||
|
||||
Notification::make()
|
||||
->success()
|
||||
->title('Track toegevoegd aan playlist')
|
||||
->send();
|
||||
}
|
||||
|
||||
private function toggleActive(RadioAutoDjTrack $track): void
|
||||
{
|
||||
$track->update(['is_active' => ! $track->is_active]);
|
||||
|
||||
Notification::make()
|
||||
->success()
|
||||
->title($track->is_active ? 'Track geactiveerd' : 'Track gedeactiveerd')
|
||||
->send();
|
||||
}
|
||||
|
||||
private function moveUp(RadioAutoDjTrack $track): void
|
||||
{
|
||||
$prev = RadioAutoDjTrack::where('sort_order', '<', $track->sort_order)
|
||||
->orderBy('sort_order', 'desc')
|
||||
->first();
|
||||
|
||||
if ($prev) {
|
||||
$temp = $track->sort_order;
|
||||
$track->update(['sort_order' => $prev->sort_order]);
|
||||
$prev->update(['sort_order' => $temp]);
|
||||
}
|
||||
}
|
||||
|
||||
private function moveDown(RadioAutoDjTrack $track): void
|
||||
{
|
||||
$next = RadioAutoDjTrack::where('sort_order', '>', $track->sort_order)
|
||||
->orderBy('sort_order')
|
||||
->first();
|
||||
|
||||
if ($next) {
|
||||
$temp = $track->sort_order;
|
||||
$track->update(['sort_order' => $next->sort_order]);
|
||||
$next->update(['sort_order' => $temp]);
|
||||
}
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user