Add leftover media management, PDF to PNG conversion, video delete, and playlist duration editing
Features added: - Leftover media management page in admin panel with delete functionality for images and videos - Individual file delete buttons for leftover media - PDF to PNG conversion (each page becomes a separate image at Full HD resolution) - Delete functionality for leftover video files - Enhanced playlist management with duration editing for all content types - Improved dark mode support for playlist management page - Content type badges with color coding - Better styling for duration input fields with save functionality - Fixed URL generation for duration update endpoint
This commit is contained in:
@@ -63,6 +63,7 @@
|
||||
<th style="padding: 10px; text-align: left; border-bottom: 2px solid #dee2e6;">Size</th>
|
||||
<th style="padding: 10px; text-align: left; border-bottom: 2px solid #dee2e6;">Duration</th>
|
||||
<th style="padding: 10px; text-align: left; border-bottom: 2px solid #dee2e6;">Uploaded</th>
|
||||
<th style="padding: 10px; text-align: left; border-bottom: 2px solid #dee2e6; width: 80px;">Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -72,6 +73,11 @@
|
||||
<td style="padding: 10px;">{{ "%.2f"|format(img.file_size_mb) }} MB</td>
|
||||
<td style="padding: 10px;">{{ img.duration }}s</td>
|
||||
<td style="padding: 10px;">{{ img.uploaded_at.strftime('%Y-%m-%d %H:%M') if img.uploaded_at else 'N/A' }}</td>
|
||||
<td style="padding: 10px;">
|
||||
<form method="POST" action="{{ url_for('admin.delete_single_leftover', content_id=img.id) }}" style="display: inline;" onsubmit="return confirm('Delete this image?');">
|
||||
<button type="submit" class="btn btn-danger btn-sm" style="padding: 4px 8px; font-size: 12px;">🗑️</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
@@ -107,6 +113,7 @@
|
||||
<th style="padding: 10px; text-align: left; border-bottom: 2px solid #dee2e6;">Size</th>
|
||||
<th style="padding: 10px; text-align: left; border-bottom: 2px solid #dee2e6;">Duration</th>
|
||||
<th style="padding: 10px; text-align: left; border-bottom: 2px solid #dee2e6;">Uploaded</th>
|
||||
<th style="padding: 10px; text-align: left; border-bottom: 2px solid #dee2e6; width: 80px;">Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -116,6 +123,11 @@
|
||||
<td style="padding: 10px;">{{ "%.2f"|format(video.file_size_mb) }} MB</td>
|
||||
<td style="padding: 10px;">{{ video.duration }}s</td>
|
||||
<td style="padding: 10px;">{{ video.uploaded_at.strftime('%Y-%m-%d %H:%M') if video.uploaded_at else 'N/A' }}</td>
|
||||
<td style="padding: 10px;">
|
||||
<form method="POST" action="{{ url_for('admin.delete_single_leftover', content_id=video.id) }}" style="display: inline;" onsubmit="return confirm('Delete this video?');">
|
||||
<button type="submit" class="btn btn-danger btn-sm" style="padding: 4px 8px; font-size: 12px;">🗑️</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
|
||||
@@ -217,19 +217,27 @@
|
||||
}
|
||||
|
||||
.duration-input {
|
||||
width: 80px;
|
||||
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;
|
||||
border-color: #667eea !important;
|
||||
box-shadow: 0 0 0 2px rgba(102, 126, 234, 0.1);
|
||||
}
|
||||
|
||||
.duration-input:focus {
|
||||
border-color: #667eea;
|
||||
border-color: #667eea !important;
|
||||
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.2);
|
||||
background: white !important;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.save-duration-btn {
|
||||
@@ -248,6 +256,29 @@
|
||||
}
|
||||
}
|
||||
|
||||
.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;
|
||||
@@ -306,6 +337,38 @@
|
||||
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;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="playlist-container">
|
||||
@@ -383,10 +446,14 @@
|
||||
<td>{{ loop.index }}</td>
|
||||
<td>{{ content.filename }}</td>
|
||||
<td>
|
||||
{% if content.content_type == 'image' %}📷 Image
|
||||
{% elif content.content_type == 'video' %}🎥 Video
|
||||
{% elif content.content_type == 'pdf' %}📄 PDF
|
||||
{% else %}📁 {{ content.content_type }}
|
||||
{% 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>
|
||||
@@ -400,14 +467,13 @@
|
||||
onclick="event.stopPropagation()"
|
||||
onmousedown="event.stopPropagation()"
|
||||
oninput="markDurationChanged({{ content.id }})"
|
||||
onkeypress="if(event.key==='Enter') saveDuration({{ content.id }})"
|
||||
style="width: 60px; padding: 5px 8px;">
|
||||
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; padding: 5px 10px; font-size: 12px; cursor: pointer;"
|
||||
style="display: none;"
|
||||
title="Save duration (or press Enter)">
|
||||
💾
|
||||
</button>
|
||||
@@ -626,7 +692,10 @@ function saveDuration(contentId) {
|
||||
const formData = new FormData();
|
||||
formData.append('duration', duration);
|
||||
|
||||
fetch(`{{ url_for("playlist.update_duration", player_id=player.id, content_id=0) }}`.replace('/0', `/${contentId}`), {
|
||||
const playerId = {{ player.id }};
|
||||
const url = `/playlist/${playerId}/update-duration/${contentId}`;
|
||||
|
||||
fetch(url, {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user