858 lines
25 KiB
HTML
858 lines
25 KiB
HTML
{% extends "base.html" %}
|
||
|
||
{% block title %}Manage Playlist - {{ player.name }} - DigiServer v2{% endblock %}
|
||
|
||
{% block content %}
|
||
<style>
|
||
.playlist-container {
|
||
max-width: 1200px;
|
||
margin: 0 auto;
|
||
}
|
||
|
||
.player-info-card {
|
||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||
color: white;
|
||
padding: 25px;
|
||
border-radius: 12px;
|
||
margin-bottom: 25px;
|
||
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
|
||
}
|
||
|
||
.player-info-card h1 {
|
||
margin: 0 0 10px 0;
|
||
font-size: 28px;
|
||
}
|
||
|
||
.player-info-card p {
|
||
margin: 5px 0;
|
||
opacity: 0.9;
|
||
}
|
||
|
||
.playlist-section {
|
||
background: white;
|
||
border-radius: 12px;
|
||
padding: 25px;
|
||
margin-bottom: 25px;
|
||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||
}
|
||
|
||
.section-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 20px;
|
||
padding-bottom: 15px;
|
||
border-bottom: 2px solid #f0f0f0;
|
||
}
|
||
|
||
.section-header h2 {
|
||
margin: 0;
|
||
font-size: 24px;
|
||
color: #333;
|
||
}
|
||
|
||
.playlist-table {
|
||
width: 100%;
|
||
border-collapse: collapse;
|
||
}
|
||
|
||
.playlist-table thead {
|
||
background: #f8f9fa;
|
||
}
|
||
|
||
.playlist-table th {
|
||
padding: 12px;
|
||
text-align: left;
|
||
font-weight: 600;
|
||
color: #555;
|
||
border-bottom: 2px solid #dee2e6;
|
||
}
|
||
|
||
.playlist-table td {
|
||
padding: 12px;
|
||
border-bottom: 1px solid #dee2e6;
|
||
}
|
||
|
||
.playlist-table tr:hover {
|
||
background: #f8f9fa;
|
||
}
|
||
|
||
.draggable-row {
|
||
cursor: move;
|
||
}
|
||
|
||
.draggable-row.dragging {
|
||
opacity: 0.5;
|
||
}
|
||
|
||
.drag-handle {
|
||
cursor: grab;
|
||
font-size: 18px;
|
||
color: #999;
|
||
padding-right: 10px;
|
||
}
|
||
|
||
.drag-handle:active {
|
||
cursor: grabbing;
|
||
}
|
||
|
||
.btn {
|
||
padding: 8px 16px;
|
||
border-radius: 6px;
|
||
border: none;
|
||
cursor: pointer;
|
||
font-size: 14px;
|
||
font-weight: 500;
|
||
text-decoration: none;
|
||
display: inline-block;
|
||
transition: all 0.2s;
|
||
}
|
||
|
||
.btn-primary {
|
||
background: #667eea;
|
||
color: white;
|
||
}
|
||
|
||
.btn-primary:hover {
|
||
background: #5568d3;
|
||
}
|
||
|
||
.btn-success {
|
||
background: #28a745;
|
||
color: white;
|
||
}
|
||
|
||
.btn-success:hover {
|
||
background: #218838;
|
||
}
|
||
|
||
.btn-danger {
|
||
background: #dc3545;
|
||
color: white;
|
||
}
|
||
|
||
.btn-danger:hover {
|
||
background: #c82333;
|
||
}
|
||
|
||
.btn-secondary {
|
||
background: #6c757d;
|
||
color: white;
|
||
}
|
||
|
||
.btn-secondary:hover {
|
||
background: #5a6268;
|
||
}
|
||
|
||
.btn-sm {
|
||
padding: 5px 10px;
|
||
font-size: 12px;
|
||
}
|
||
|
||
.add-content-form {
|
||
background: #f8f9fa;
|
||
padding: 20px;
|
||
border-radius: 8px;
|
||
margin-top: 20px;
|
||
}
|
||
|
||
.form-group {
|
||
margin-bottom: 15px;
|
||
}
|
||
|
||
.form-group label {
|
||
display: block;
|
||
font-weight: 600;
|
||
margin-bottom: 5px;
|
||
color: #333;
|
||
}
|
||
|
||
.form-control {
|
||
width: 100%;
|
||
padding: 10px;
|
||
border: 1px solid #ced4da;
|
||
border-radius: 6px;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.form-control:focus {
|
||
outline: none;
|
||
border-color: #667eea;
|
||
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
|
||
}
|
||
|
||
.stats-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(3, 1fr);
|
||
gap: 15px;
|
||
margin-top: 15px;
|
||
}
|
||
|
||
.stat-item {
|
||
background: rgba(255, 255, 255, 0.2);
|
||
padding: 12px;
|
||
border-radius: 8px;
|
||
}
|
||
|
||
.stat-label {
|
||
font-size: 12px;
|
||
opacity: 0.9;
|
||
margin-bottom: 5px;
|
||
}
|
||
|
||
.stat-value {
|
||
font-size: 24px;
|
||
font-weight: bold;
|
||
}
|
||
|
||
.empty-state {
|
||
text-align: center;
|
||
padding: 40px;
|
||
color: #999;
|
||
}
|
||
|
||
.empty-state-icon {
|
||
font-size: 48px;
|
||
margin-bottom: 15px;
|
||
}
|
||
|
||
.duration-input {
|
||
width: 70px !important;
|
||
padding: 5px 8px !important;
|
||
text-align: center;
|
||
transition: all 0.3s ease;
|
||
background: white !important;
|
||
border: 2px solid #ced4da !important;
|
||
border-radius: 6px;
|
||
font-size: 14px;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.duration-input:hover {
|
||
border-color: #667eea !important;
|
||
box-shadow: 0 0 0 2px rgba(102, 126, 234, 0.1);
|
||
}
|
||
|
||
.duration-input:focus {
|
||
border-color: #667eea !important;
|
||
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.2);
|
||
background: white !important;
|
||
outline: none;
|
||
}
|
||
|
||
.save-duration-btn {
|
||
transition: all 0.2s ease;
|
||
animation: fadeIn 0.2s ease;
|
||
}
|
||
|
||
@keyframes fadeIn {
|
||
from {
|
||
opacity: 0;
|
||
transform: scale(0.8);
|
||
}
|
||
to {
|
||
opacity: 1;
|
||
transform: scale(1);
|
||
}
|
||
}
|
||
|
||
.content-type-badge {
|
||
display: inline-block;
|
||
padding: 4px 8px;
|
||
border-radius: 4px;
|
||
font-size: 12px;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.badge-image {
|
||
background: #e3f2fd;
|
||
color: #1976d2;
|
||
}
|
||
|
||
.badge-video {
|
||
background: #f3e5f5;
|
||
color: #7b1fa2;
|
||
}
|
||
|
||
.badge-pdf {
|
||
background: #ffebee;
|
||
color: #c62828;
|
||
}
|
||
|
||
/* Dark mode support */
|
||
body.dark-mode .playlist-section {
|
||
background: #2d3748;
|
||
color: #e2e8f0;
|
||
}
|
||
|
||
body.dark-mode .section-header h2 {
|
||
color: #e2e8f0;
|
||
}
|
||
|
||
body.dark-mode .section-header {
|
||
border-bottom-color: #4a5568;
|
||
}
|
||
|
||
body.dark-mode .playlist-table thead {
|
||
background: #1a202c;
|
||
}
|
||
|
||
body.dark-mode .playlist-table th {
|
||
color: #cbd5e0;
|
||
border-bottom-color: #4a5568;
|
||
}
|
||
|
||
body.dark-mode .playlist-table td {
|
||
border-bottom-color: #4a5568;
|
||
color: #e2e8f0;
|
||
}
|
||
|
||
body.dark-mode .playlist-table tr:hover {
|
||
background: #1a202c;
|
||
}
|
||
|
||
body.dark-mode .form-control {
|
||
background: #1a202c;
|
||
border-color: #4a5568;
|
||
color: #e2e8f0;
|
||
}
|
||
|
||
body.dark-mode .form-control:focus {
|
||
border-color: #667eea;
|
||
background: #2d3748;
|
||
}
|
||
|
||
body.dark-mode .add-content-form {
|
||
background: #1a202c;
|
||
}
|
||
|
||
body.dark-mode .form-group label {
|
||
color: #e2e8f0;
|
||
}
|
||
|
||
body.dark-mode .empty-state {
|
||
color: #718096;
|
||
}
|
||
|
||
body.dark-mode .drag-handle {
|
||
color: #718096;
|
||
}
|
||
|
||
body.dark-mode .duration-input {
|
||
background: #1a202c !important;
|
||
border-color: #4a5568 !important;
|
||
color: #e2e8f0 !important;
|
||
}
|
||
|
||
body.dark-mode .duration-input:hover {
|
||
border-color: #667eea !important;
|
||
box-shadow: 0 0 0 2px rgba(102, 126, 234, 0.2);
|
||
}
|
||
|
||
body.dark-mode .duration-input:focus {
|
||
background: #2d3748 !important;
|
||
border-color: #667eea !important;
|
||
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.3);
|
||
}
|
||
|
||
body.dark-mode .badge-image {
|
||
background: #1e3a5f;
|
||
color: #64b5f6;
|
||
}
|
||
|
||
body.dark-mode .badge-video {
|
||
background: #4a1e5a;
|
||
color: #ce93d8;
|
||
}
|
||
|
||
body.dark-mode .badge-pdf {
|
||
background: #5a1e1e;
|
||
color: #ef5350;
|
||
}
|
||
|
||
/* Audio toggle styles */
|
||
.audio-toggle {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
cursor: pointer;
|
||
user-select: none;
|
||
}
|
||
|
||
.audio-checkbox {
|
||
display: none;
|
||
}
|
||
|
||
.audio-label {
|
||
font-size: 20px;
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.audio-checkbox + .audio-label .audio-on {
|
||
display: none;
|
||
}
|
||
|
||
.audio-checkbox + .audio-label .audio-off {
|
||
display: inline;
|
||
}
|
||
|
||
.audio-checkbox:checked + .audio-label .audio-on {
|
||
display: inline;
|
||
}
|
||
|
||
.audio-checkbox:checked + .audio-label .audio-off {
|
||
display: none;
|
||
}
|
||
|
||
.audio-label:hover {
|
||
transform: scale(1.2);
|
||
}
|
||
</style>
|
||
|
||
<div class="playlist-container">
|
||
<!-- Player Info Card -->
|
||
<div class="player-info-card">
|
||
<h1>🎬 {{ player.name }}</h1>
|
||
<p>📍 {{ player.location or 'No location' }}</p>
|
||
<p>🖥️ Hostname: {{ player.hostname }}</p>
|
||
<p>📊 Status: {{ '🟢 Online' if player.is_online else '🔴 Offline' }}</p>
|
||
|
||
<div class="stats-grid">
|
||
<div class="stat-item">
|
||
<div class="stat-label">Playlist Items</div>
|
||
<div class="stat-value">{{ playlist_content|length }}</div>
|
||
</div>
|
||
<div class="stat-item">
|
||
<div class="stat-label">Playlist Version</div>
|
||
<div class="stat-value">{{ player.playlist_version }}</div>
|
||
</div>
|
||
<div class="stat-item">
|
||
<div class="stat-label">Total Duration</div>
|
||
<div class="stat-value">{{ playlist_content|sum(attribute='duration') }}s</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Action Buttons -->
|
||
<div style="margin-bottom: 20px; display: flex; gap: 10px;">
|
||
<a href="{{ url_for('players.player_page', player_id=player.id) }}" class="btn btn-secondary">
|
||
← Back to Player
|
||
</a>
|
||
<a href="{{ url_for('content.upload_content', player_id=player.id, return_url=url_for('playlist.manage_playlist', player_id=player.id)) }}"
|
||
class="btn btn-success">
|
||
➕ Upload New Content
|
||
</a>
|
||
{% if playlist_content %}
|
||
<form method="POST" action="{{ url_for('playlist.clear_playlist', player_id=player.id) }}"
|
||
style="display: inline;"
|
||
onsubmit="return confirm('Are you sure you want to clear the entire playlist?');">
|
||
<button type="submit" class="btn btn-danger">
|
||
🗑️ Clear Playlist
|
||
</button>
|
||
</form>
|
||
{% endif %}
|
||
</div>
|
||
|
||
<!-- Current Playlist -->
|
||
<div class="playlist-section">
|
||
<div class="section-header">
|
||
<h2>📋 Current Playlist</h2>
|
||
<span style="color: #999; font-size: 14px;">
|
||
Drag and drop to reorder
|
||
</span>
|
||
</div>
|
||
|
||
{% if playlist_content %}
|
||
<table class="playlist-table" id="playlist-table">
|
||
<thead>
|
||
<tr>
|
||
<th style="width: 40px;"></th>
|
||
<th style="width: 50px;">#</th>
|
||
<th>Filename</th>
|
||
<th style="width: 100px;">Type</th>
|
||
<th style="width: 120px;">Duration (s)</th>
|
||
<th style="width: 80px;">Audio</th>
|
||
<th style="width: 100px;">Size</th>
|
||
<th style="width: 150px;">Actions</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="playlist-tbody">
|
||
{% for content in playlist_content %}
|
||
<tr class="draggable-row" data-content-id="{{ content.id }}">
|
||
<td>
|
||
<span class="drag-handle" draggable="true">⋮⋮</span>
|
||
</td>
|
||
<td>{{ loop.index }}</td>
|
||
<td>{{ content.filename }}</td>
|
||
<td>
|
||
{% if content.content_type == 'image' %}
|
||
<span class="content-type-badge badge-image">📷 Image</span>
|
||
{% elif content.content_type == 'video' %}
|
||
<span class="content-type-badge badge-video">🎥 Video</span>
|
||
{% elif content.content_type == 'pdf' %}
|
||
<span class="content-type-badge badge-pdf">📄 PDF</span>
|
||
{% else %}
|
||
<span class="content-type-badge">📁 {{ content.content_type }}</span>
|
||
{% endif %}
|
||
</td>
|
||
<td>
|
||
<div style="display: flex; align-items: center; gap: 5px;">
|
||
<input type="number"
|
||
class="form-control duration-input"
|
||
id="duration-{{ content.id }}"
|
||
value="{{ content._playlist_duration }}"
|
||
min="1"
|
||
draggable="false"
|
||
onclick="event.stopPropagation()"
|
||
onmousedown="event.stopPropagation()"
|
||
oninput="markDurationChanged({{ content.id }})"
|
||
onkeypress="if(event.key==='Enter') saveDuration({{ content.id }})">
|
||
<button type="button"
|
||
class="btn btn-success btn-sm save-duration-btn"
|
||
id="save-btn-{{ content.id }}"
|
||
onclick="event.stopPropagation(); saveDuration({{ content.id }})"
|
||
onmousedown="event.stopPropagation()"
|
||
style="display: none;"
|
||
title="Save duration (or press Enter)">
|
||
💾
|
||
</button>
|
||
</div>
|
||
</td>
|
||
<td>
|
||
{% if content.content_type == 'video' %}
|
||
<label class="audio-toggle" onclick="event.stopPropagation()">
|
||
<input type="checkbox"
|
||
class="audio-checkbox"
|
||
data-content-id="{{ content.id }}"
|
||
{{ 'checked' if not content._playlist_muted else '' }}
|
||
onchange="toggleAudio({{ content.id }}, this.checked)"
|
||
onclick="event.stopPropagation()">
|
||
<span class="audio-label">
|
||
<span class="audio-on">🔊</span>
|
||
<span class="audio-off">🔇</span>
|
||
</span>
|
||
</label>
|
||
{% else %}
|
||
<span style="color: #999;">—</span>
|
||
{% endif %}
|
||
</td>
|
||
<td>{{ "%.2f"|format(content.file_size_mb) }} MB</td>
|
||
<td>
|
||
<form method="POST"
|
||
action="{{ url_for('playlist.remove_from_playlist', player_id=player.id, content_id=content.id) }}"
|
||
style="display: inline;"
|
||
onsubmit="return confirm('Remove {{ content.filename }} from playlist?');">
|
||
<button type="submit" class="btn btn-danger btn-sm">
|
||
✕ Remove
|
||
</button>
|
||
</form>
|
||
</td>
|
||
</tr>
|
||
{% endfor %}
|
||
</tbody>
|
||
</table>
|
||
{% else %}
|
||
<div class="empty-state">
|
||
<div class="empty-state-icon">📭</div>
|
||
<h3>No content in playlist</h3>
|
||
<p>Upload content or add existing files to get started</p>
|
||
</div>
|
||
{% endif %}
|
||
</div>
|
||
|
||
<!-- Add Content Section -->
|
||
{% if available_content %}
|
||
<div class="playlist-section">
|
||
<div class="section-header">
|
||
<h2>➕ Add Existing Content</h2>
|
||
</div>
|
||
|
||
<form method="POST" action="{{ url_for('playlist.add_to_playlist', player_id=player.id) }}"
|
||
class="add-content-form">
|
||
<div class="form-group">
|
||
<label for="content_id">Select Content:</label>
|
||
<select name="content_id" id="content_id" class="form-control" required>
|
||
<option value="" disabled selected>Choose content...</option>
|
||
{% for content in available_content %}
|
||
<option value="{{ content.id }}">{{ content.filename }} ({{ content.content_type }})</option>
|
||
{% endfor %}
|
||
</select>
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label for="duration">Display Duration (seconds):</label>
|
||
<input type="number"
|
||
name="duration"
|
||
id="duration"
|
||
class="form-control"
|
||
value="10"
|
||
min="1"
|
||
required>
|
||
</div>
|
||
|
||
<button type="submit" class="btn btn-success">
|
||
➕ Add to Playlist
|
||
</button>
|
||
</form>
|
||
</div>
|
||
{% endif %}
|
||
</div>
|
||
|
||
<script>
|
||
let draggedElement = null;
|
||
|
||
// Initialize drag and drop
|
||
document.addEventListener('DOMContentLoaded', function() {
|
||
const tbody = document.getElementById('playlist-tbody');
|
||
if (!tbody) return;
|
||
|
||
// Set up drag handles
|
||
const dragHandles = tbody.querySelectorAll('.drag-handle');
|
||
dragHandles.forEach(handle => {
|
||
handle.addEventListener('dragstart', handleDragStart);
|
||
});
|
||
|
||
// Set up drop zones on rows
|
||
const rows = tbody.querySelectorAll('.draggable-row');
|
||
rows.forEach(row => {
|
||
row.addEventListener('dragover', handleDragOver);
|
||
row.addEventListener('drop', handleDrop);
|
||
row.addEventListener('dragend', handleDragEnd);
|
||
});
|
||
|
||
// Prevent dragging from inputs and buttons
|
||
const inputs = document.querySelectorAll('.duration-input, button');
|
||
inputs.forEach(input => {
|
||
input.addEventListener('mousedown', (e) => {
|
||
e.stopPropagation();
|
||
});
|
||
input.addEventListener('click', (e) => {
|
||
e.stopPropagation();
|
||
});
|
||
});
|
||
});
|
||
|
||
function handleDragStart(e) {
|
||
// Get the parent row
|
||
const row = e.target.closest('.draggable-row');
|
||
if (!row) return;
|
||
|
||
draggedElement = row;
|
||
row.classList.add('dragging');
|
||
e.dataTransfer.effectAllowed = 'move';
|
||
e.dataTransfer.setData('text/html', row.innerHTML);
|
||
}
|
||
|
||
function handleDragOver(e) {
|
||
if (e.preventDefault) {
|
||
e.preventDefault();
|
||
}
|
||
e.dataTransfer.dropEffect = 'move';
|
||
return false;
|
||
}
|
||
|
||
function handleDrop(e) {
|
||
if (e.stopPropagation) {
|
||
e.stopPropagation();
|
||
}
|
||
|
||
if (draggedElement !== this) {
|
||
const tbody = document.getElementById('playlist-tbody');
|
||
const allRows = [...tbody.querySelectorAll('.draggable-row')];
|
||
const draggedIndex = allRows.indexOf(draggedElement);
|
||
const targetIndex = allRows.indexOf(this);
|
||
|
||
if (draggedIndex < targetIndex) {
|
||
this.parentNode.insertBefore(draggedElement, this.nextSibling);
|
||
} else {
|
||
this.parentNode.insertBefore(draggedElement, this);
|
||
}
|
||
|
||
updateRowNumbers();
|
||
saveOrder();
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
function handleDragEnd(e) {
|
||
this.classList.remove('dragging');
|
||
}
|
||
|
||
function updateRowNumbers() {
|
||
const rows = document.querySelectorAll('#playlist-tbody tr');
|
||
rows.forEach((row, index) => {
|
||
row.querySelector('td:nth-child(2)').textContent = index + 1;
|
||
});
|
||
}
|
||
|
||
function saveOrder() {
|
||
const rows = document.querySelectorAll('#playlist-tbody .draggable-row');
|
||
const contentIds = Array.from(rows).map(row => parseInt(row.dataset.contentId));
|
||
|
||
fetch('{{ url_for("playlist.reorder_playlist", player_id=player.id) }}', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
},
|
||
body: JSON.stringify({ content_ids: contentIds })
|
||
})
|
||
.then(response => response.json())
|
||
.then(data => {
|
||
if (data.success) {
|
||
console.log('Playlist reordered successfully');
|
||
} else {
|
||
alert('Error reordering playlist: ' + data.message);
|
||
}
|
||
})
|
||
.catch(error => {
|
||
console.error('Error:', error);
|
||
alert('Error reordering playlist');
|
||
});
|
||
}
|
||
|
||
function markDurationChanged(contentId) {
|
||
const saveBtn = document.getElementById(`save-btn-${contentId}`);
|
||
const input = document.getElementById(`duration-${contentId}`);
|
||
|
||
// Show save button if value changed
|
||
if (input.value !== input.defaultValue) {
|
||
saveBtn.style.display = 'inline-block';
|
||
input.style.borderColor = '#ffc107';
|
||
} else {
|
||
saveBtn.style.display = 'none';
|
||
input.style.borderColor = '';
|
||
}
|
||
}
|
||
|
||
function saveDuration(contentId) {
|
||
const inputElement = document.getElementById(`duration-${contentId}`);
|
||
const saveBtn = document.getElementById(`save-btn-${contentId}`);
|
||
const duration = parseInt(inputElement.value);
|
||
|
||
// Validate duration
|
||
if (duration < 1) {
|
||
alert('Duration must be at least 1 second');
|
||
inputElement.value = inputElement.defaultValue;
|
||
inputElement.style.borderColor = '';
|
||
saveBtn.style.display = 'none';
|
||
return;
|
||
}
|
||
|
||
const originalValue = inputElement.defaultValue;
|
||
|
||
// Visual feedback
|
||
inputElement.disabled = true;
|
||
saveBtn.disabled = true;
|
||
saveBtn.textContent = '⏳';
|
||
|
||
const formData = new FormData();
|
||
formData.append('duration', duration);
|
||
|
||
const playerId = {{ player.id }};
|
||
const url = `/playlist/${playerId}/update-duration/${contentId}`;
|
||
|
||
fetch(url, {
|
||
method: 'POST',
|
||
body: formData
|
||
})
|
||
.then(response => response.json())
|
||
.then(data => {
|
||
if (data.success) {
|
||
console.log('Duration updated successfully');
|
||
inputElement.style.borderColor = '#28a745';
|
||
inputElement.defaultValue = duration;
|
||
saveBtn.textContent = '✓';
|
||
|
||
// Update total duration display
|
||
updateTotalDuration();
|
||
|
||
setTimeout(() => {
|
||
inputElement.style.borderColor = '';
|
||
inputElement.disabled = false;
|
||
saveBtn.style.display = 'none';
|
||
saveBtn.textContent = '💾';
|
||
saveBtn.disabled = false;
|
||
}, 1500);
|
||
} else {
|
||
inputElement.style.borderColor = '#dc3545';
|
||
inputElement.value = originalValue;
|
||
saveBtn.textContent = '✖';
|
||
alert('Error updating duration: ' + data.message);
|
||
|
||
setTimeout(() => {
|
||
inputElement.disabled = false;
|
||
inputElement.style.borderColor = '';
|
||
saveBtn.style.display = 'none';
|
||
saveBtn.textContent = '💾';
|
||
saveBtn.disabled = false;
|
||
}, 1500);
|
||
}
|
||
})
|
||
.catch(error => {
|
||
console.error('Error:', error);
|
||
inputElement.style.borderColor = '#dc3545';
|
||
inputElement.value = originalValue;
|
||
saveBtn.textContent = '✖';
|
||
alert('Error updating duration');
|
||
|
||
setTimeout(() => {
|
||
inputElement.disabled = false;
|
||
inputElement.style.borderColor = '';
|
||
saveBtn.style.display = 'none';
|
||
saveBtn.textContent = '💾';
|
||
saveBtn.disabled = false;
|
||
}, 1500);
|
||
});
|
||
}
|
||
|
||
function updateTotalDuration() {
|
||
const durationInputs = document.querySelectorAll('.duration-input');
|
||
let total = 0;
|
||
durationInputs.forEach(input => {
|
||
total += parseInt(input.value) || 0;
|
||
});
|
||
|
||
const statValues = document.querySelectorAll('.stat-value');
|
||
statValues.forEach((element, index) => {
|
||
const label = element.parentElement.querySelector('.stat-label');
|
||
if (label && label.textContent.includes('Total Duration')) {
|
||
element.textContent = total + 's';
|
||
}
|
||
});
|
||
}
|
||
|
||
function toggleAudio(contentId, enabled) {
|
||
const muted = !enabled; // Checkbox is "enabled audio", but backend stores "muted"
|
||
const playerId = {{ player.id }};
|
||
const url = `/playlist/${playerId}/update-muted/${contentId}`;
|
||
|
||
const formData = new FormData();
|
||
formData.append('muted', muted ? 'true' : 'false');
|
||
|
||
fetch(url, {
|
||
method: 'POST',
|
||
body: formData
|
||
})
|
||
.then(response => response.json())
|
||
.then(data => {
|
||
if (data.success) {
|
||
console.log('Audio setting updated:', enabled ? 'Enabled' : 'Muted');
|
||
} else {
|
||
alert('Error updating audio setting: ' + data.message);
|
||
// Revert checkbox on error
|
||
const checkbox = document.querySelector(`.audio-checkbox[data-content-id="${contentId}"]`);
|
||
if (checkbox) checkbox.checked = !enabled;
|
||
}
|
||
})
|
||
.catch(error => {
|
||
console.error('Error:', error);
|
||
alert('Error updating audio setting');
|
||
// Revert checkbox on error
|
||
const checkbox = document.querySelector(`.audio-checkbox[data-content-id="${contentId}"]`);
|
||
if (checkbox) checkbox.checked = !enabled;
|
||
});
|
||
}
|
||
</script>
|
||
|
||
{% endblock %}
|