Files
digiserver-v2/app/templates/playlist/manage_playlist.html
ske087 498c03ef00 Replace emoji icons with local SVG files for consistent rendering
- 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
2025-11-13 21:00:07 +02:00

493 lines
14 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{% 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: 80px;
text-align: center;
}
</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: 100px;">Size</th>
<th style="width: 150px;">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 %}📁 {{ content.content_type }}
{% endif %}
</td>
<td>
<input type="number"
class="form-control duration-input"
value="{{ content.duration }}"
min="1"
onchange="updateDuration({{ content.id }}, this.value)">
</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_files %}
<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="filename">Select File:</label>
<select name="filename" id="filename" class="form-control" required>
<option value="" disabled selected>Choose a file...</option>
{% for filename in available_files %}
<option value="{{ filename }}">{{ filename }}</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;
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.classList.add('dragging');
e.dataTransfer.effectAllowed = 'move';
}
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 updateDuration(contentId, duration) {
const formData = new FormData();
formData.append('duration', duration);
fetch(`{{ url_for("playlist.update_duration", player_id=player.id, content_id=0) }}`.replace('/0', `/${contentId}`), {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
if (data.success) {
console.log('Duration updated successfully');
// Update total duration
location.reload();
} else {
alert('Error updating duration: ' + data.message);
}
})
.catch(error => {
console.error('Error:', error);
alert('Error updating duration');
});
}
</script>
{% endblock %}