Files
ScratchingPost/Views/Devices/Playlist.cshtml
T

82 lines
6.4 KiB
Plaintext

@model Device
@{
ViewData["Title"] = $"PURR — {Model.Name}";
var availableSlides = ViewBag.AvailableSlides as List<Slide>;
}
@section HeaderActions {
<a href="/@Model.Slug" target="_blank" class="btn btn-success"><i class="bi bi-box-arrow-up-right me-1"></i>View Display</a>
}
<div class="row g-4">
<div class="col-lg-8">
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center">
<h5 class="mb-0"><i class="bi bi-music-note-list me-2"></i>Playlist <small class="text-muted ms-2">Drag to reorder</small></h5>
<span class="badge bg-secondary" id="totalDuration"></span>
</div>
<div class="card-body p-0">
@if (Model.DeviceSlides.Any())
{
<div id="playlistItems" class="list-group list-group-flush">
@foreach (var ds in Model.DeviceSlides)
{
<div class="list-group-item playlist-item d-flex align-items-center gap-3" data-id="@ds.Id" data-duration="@ds.DurationSeconds">
<div class="drag-handle" title="Drag to reorder"><i class="bi bi-grip-vertical"></i></div>
<div class="flex-grow-1">
<div class="d-flex align-items-center gap-2">
<strong>@ds.Slide.Name</strong>
@switch (ds.Slide.SlideType) {
case SlideType.Content: <span class="badge bg-info badge-sm">Content</span> break;
case SlideType.Embed: <span class="badge bg-warning text-dark badge-sm">Embed</span> break;
case SlideType.IcsCalendar: <span class="badge bg-success badge-sm">Calendar</span> break;
}
</div>
</div>
<div class="d-flex align-items-center gap-2">
<div class="form-check form-switch"><input class="form-check-input toggle-enabled" type="checkbox" data-id="@ds.Id" @(ds.Enabled ? "checked" : "") /></div>
<div class="input-group input-group-sm duration-input" style="width:120px;"><input type="number" class="form-control text-center duration-value" value="@ds.DurationSeconds" min="5" max="3600" data-id="@ds.Id" /><span class="input-group-text">sec</span></div>
<form asp-action="RemoveFromPlaylist" method="post" class="d-inline" onsubmit="return confirm('Remove this meow?');">
<input type="hidden" name="id" value="@Model.Id" /><input type="hidden" name="deviceSlideId" value="@ds.Id" />
<button type="submit" class="btn btn-sm btn-outline-danger"><i class="bi bi-x-lg"></i></button>
</form>
</div>
</div>
}
</div>
}
else
{
<div class="p-4 text-center text-muted"><i class="bi bi-music-note-list" style="font-size:2em;"></i><p class="mt-2">No meows in this PURR yet.</p></div>
}
</div>
</div>
</div>
<div class="col-lg-4">
<div class="card mb-4"><div class="card-header"><h6 class="mb-0">Post Info</h6></div><div class="card-body"><p class="mb-1"><strong>@Model.Name</strong></p><p class="mb-1 text-muted"><code>/@Model.Slug</code></p><p class="mb-0 text-muted">@(Model.ResolutionWidth)x@(Model.ResolutionHeight) · @Model.Transition</p></div></div>
<div class="card"><div class="card-header"><h6 class="mb-0"><i class="bi bi-plus-circle me-1"></i>Add Meow</h6></div><div class="card-body">
@if (availableSlides != null && availableSlides.Any())
{
<form asp-action="AddToPlaylist" method="post"><input type="hidden" name="id" value="@Model.Id" />
<div class="mb-3"><label class="form-label">Select Slide</label><select name="slideId" class="form-select" required><option value="">— Choose a meow —</option>@foreach (var slide in availableSlides){<option value="@slide.Id">@slide.Name (@slide.SlideType)</option>}</select></div>
<div class="mb-3"><label class="form-label">Duration (seconds)</label><input type="number" name="durationSeconds" class="form-control" value="30" min="5" max="3600" /></div>
<button type="submit" class="btn btn-primary w-100"><i class="bi bi-plus-lg me-1"></i>Add to PURR</button>
</form>
}
else { <p class="text-muted mb-2">All meows assigned.</p><a href="/admin/slides/create" class="btn btn-outline-primary btn-sm"><i class="bi bi-plus-lg me-1"></i>Create New Meow</a> }
</div></div>
</div>
</div>
@section Scripts {
<script src="https://cdn.jsdelivr.net/npm/sortablejs@1.15.3/Sortable.min.js"></script>
<script>
function updateTotalDuration(){var t=0;document.querySelectorAll('.duration-value').forEach(function(e){t+=parseInt(e.value)||0;});var m=Math.floor(t/60),s=t%60;document.getElementById('totalDuration').textContent='Total: '+(m>0?m+'m ':'')+s+'s loop';}
updateTotalDuration();
var list=document.getElementById('playlistItems');
if(list){Sortable.create(list,{handle:'.drag-handle',animation:200,onEnd:function(){var ids=[];list.querySelectorAll('.playlist-item').forEach(function(e){ids.push(parseInt(e.dataset.id));});fetch('/admin/devices/reorderplaylist',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({itemIds:ids})});}});}
document.querySelectorAll('.duration-value').forEach(function(e){e.addEventListener('change',function(){fetch('/admin/devices/updateduration',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({deviceSlideId:parseInt(this.dataset.id),durationSeconds:parseInt(this.value)})});updateTotalDuration();});});
document.querySelectorAll('.toggle-enabled').forEach(function(e){e.addEventListener('change',function(){fetch('/admin/devices/toggleenabled',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({deviceSlideId:parseInt(this.dataset.id),enabled:this.checked})});});});
</script>
}