- Created 10 SVG icon files in app/static/icons/ (Feather Icons style) - Updated base.html with SVG icons in navigation and dark mode toggle - Updated dashboard.html with icons in stats cards and quick actions - Updated content_list_new.html (playlist management) with SVG icons - Updated upload_media.html with upload-related icons - Updated manage_player.html with player management icons - Icons use currentColor for automatic theme adaptation - Removed emoji dependency for better Raspberry Pi compatibility - Added ICON_INTEGRATION.md documentation
305 lines
9.5 KiB
HTML
305 lines
9.5 KiB
HTML
{% extends "base.html" %}
|
||
|
||
{% block title %}Manage {{ playlist.name }} - DigiServer v2{% endblock %}
|
||
|
||
{% block content %}
|
||
<style>
|
||
.playlist-header {
|
||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||
color: white;
|
||
padding: 30px;
|
||
border-radius: 12px;
|
||
margin-bottom: 25px;
|
||
}
|
||
|
||
.playlist-header h1 {
|
||
margin: 0 0 10px 0;
|
||
}
|
||
|
||
.stats-row {
|
||
display: flex;
|
||
gap: 30px;
|
||
margin-top: 15px;
|
||
}
|
||
|
||
.stat-item {
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.stat-label {
|
||
font-size: 12px;
|
||
opacity: 0.9;
|
||
}
|
||
|
||
.stat-value {
|
||
font-size: 24px;
|
||
font-weight: bold;
|
||
}
|
||
|
||
.content-grid {
|
||
display: grid;
|
||
grid-template-columns: 2fr 1fr;
|
||
gap: 20px;
|
||
}
|
||
|
||
.playlist-table {
|
||
width: 100%;
|
||
border-collapse: collapse;
|
||
}
|
||
|
||
.playlist-table th {
|
||
background: #f8f9fa;
|
||
padding: 12px;
|
||
text-align: left;
|
||
border-bottom: 2px solid #dee2e6;
|
||
}
|
||
|
||
.playlist-table td {
|
||
padding: 12px;
|
||
border-bottom: 1px solid #dee2e6;
|
||
}
|
||
|
||
.draggable-row {
|
||
cursor: move;
|
||
}
|
||
|
||
.draggable-row:hover {
|
||
background: #f8f9fa;
|
||
}
|
||
|
||
.drag-handle {
|
||
cursor: grab;
|
||
font-size: 18px;
|
||
color: #999;
|
||
}
|
||
|
||
.available-content {
|
||
max-height: 500px;
|
||
overflow-y: auto;
|
||
}
|
||
|
||
.content-item {
|
||
background: #f8f9fa;
|
||
padding: 12px;
|
||
margin-bottom: 10px;
|
||
border-radius: 6px;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
}
|
||
</style>
|
||
|
||
<div class="container" style="max-width: 1400px;">
|
||
<div class="playlist-header">
|
||
<h1>🎬 {{ playlist.name }}</h1>
|
||
{% if playlist.description %}
|
||
<p style="margin: 5px 0; opacity: 0.9;">{{ playlist.description }}</p>
|
||
{% endif %}
|
||
|
||
<div class="stats-row">
|
||
<div class="stat-item">
|
||
<span class="stat-label">Content Items</span>
|
||
<span class="stat-value">{{ playlist_content|length }}</span>
|
||
</div>
|
||
<div class="stat-item">
|
||
<span class="stat-label">Total Duration</span>
|
||
<span class="stat-value">{{ playlist.total_duration }}s</span>
|
||
</div>
|
||
<div class="stat-item">
|
||
<span class="stat-label">Version</span>
|
||
<span class="stat-value">{{ playlist.version }}</span>
|
||
</div>
|
||
<div class="stat-item">
|
||
<span class="stat-label">Players Assigned</span>
|
||
<span class="stat-value">{{ playlist.player_count }}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div style="margin-bottom: 20px;">
|
||
<a href="{{ url_for('content.content_list') }}" class="btn btn-secondary">
|
||
← Back to Playlists
|
||
</a>
|
||
</div>
|
||
|
||
<div class="content-grid">
|
||
<div class="card">
|
||
<h2 style="margin-bottom: 20px;">📋 Playlist Content (Drag to Reorder)</h2>
|
||
|
||
{% 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: 100px;">Duration</th>
|
||
<th style="width: 100px;">Actions</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="playlist-tbody">
|
||
{% for content in playlist_content %}
|
||
<tr class="draggable-row" draggable="true" data-content-id="{{ content.id }}">
|
||
<td><span class="drag-handle">⋮⋮</span></td>
|
||
<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 %}📁 Other{% endif %}
|
||
</td>
|
||
<td>{{ content._playlist_duration or content.duration }}s</td>
|
||
<td>
|
||
<form method="POST"
|
||
action="{{ url_for('content.remove_content_from_playlist', playlist_id=playlist.id, content_id=content.id) }}"
|
||
style="display: inline;"
|
||
onsubmit="return confirm('Remove from playlist?');">
|
||
<button type="submit" class="btn btn-danger btn-sm">
|
||
✕
|
||
</button>
|
||
</form>
|
||
</td>
|
||
</tr>
|
||
{% endfor %}
|
||
</tbody>
|
||
</table>
|
||
{% else %}
|
||
<div style="text-align: center; padding: 40px; color: #999;">
|
||
<div style="font-size: 48px;">📭</div>
|
||
<p>No content in playlist yet. Add content from the right panel.</p>
|
||
</div>
|
||
{% endif %}
|
||
</div>
|
||
|
||
<div class="card">
|
||
<h2 style="margin-bottom: 20px;">➕ Add Content</h2>
|
||
|
||
{% if available_content %}
|
||
<div class="available-content">
|
||
{% for content in available_content %}
|
||
<div class="content-item">
|
||
<div>
|
||
<div>
|
||
{% if content.content_type == 'image' %}📷
|
||
{% elif content.content_type == 'video' %}🎥
|
||
{% elif content.content_type == 'pdf' %}📄
|
||
{% else %}📁{% endif %}
|
||
{{ content.filename }}
|
||
</div>
|
||
<div style="font-size: 12px; color: #999;">
|
||
{{ content.file_size_mb }} MB
|
||
</div>
|
||
</div>
|
||
<form method="POST"
|
||
action="{{ url_for('content.add_content_to_playlist', playlist_id=playlist.id) }}"
|
||
style="display: inline;">
|
||
<input type="hidden" name="content_id" value="{{ content.id }}">
|
||
<input type="hidden" name="duration" value="{{ content.duration }}">
|
||
<button type="submit" class="btn btn-success btn-sm">
|
||
+ Add
|
||
</button>
|
||
</form>
|
||
</div>
|
||
{% endfor %}
|
||
</div>
|
||
{% else %}
|
||
<div style="text-align: center; padding: 40px; color: #999;">
|
||
<p>All available content has been added to this playlist!</p>
|
||
</div>
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
let draggedElement = null;
|
||
|
||
document.addEventListener('DOMContentLoaded', function() {
|
||
const tbody = document.getElementById('playlist-tbody');
|
||
if (!tbody) return;
|
||
|
||
const rows = tbody.querySelectorAll('.draggable-row');
|
||
|
||
rows.forEach(row => {
|
||
row.addEventListener('dragstart', handleDragStart);
|
||
row.addEventListener('dragover', handleDragOver);
|
||
row.addEventListener('drop', handleDrop);
|
||
row.addEventListener('dragend', handleDragEnd);
|
||
});
|
||
});
|
||
|
||
function handleDragStart(e) {
|
||
draggedElement = this;
|
||
this.style.opacity = '0.5';
|
||
}
|
||
|
||
function handleDragOver(e) {
|
||
if (e.preventDefault) {
|
||
e.preventDefault();
|
||
}
|
||
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.style.opacity = '1';
|
||
}
|
||
|
||
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("content.reorder_playlist_content", playlist_id=playlist.id) }}', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
},
|
||
body: JSON.stringify({ content_ids: contentIds })
|
||
})
|
||
.then(response => response.json())
|
||
.then(data => {
|
||
if (!data.success) {
|
||
alert('Error reordering: ' + data.message);
|
||
}
|
||
})
|
||
.catch(error => {
|
||
console.error('Error:', error);
|
||
});
|
||
}
|
||
</script>
|
||
|
||
{% endblock %}
|