540 lines
18 KiB
HTML
540 lines
18 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block title %}Media Library - DigiServer v2{% endblock %}
|
|
|
|
{% block content %}
|
|
<style>
|
|
.media-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
|
|
gap: 15px;
|
|
margin-top: 20px;
|
|
}
|
|
|
|
.media-card {
|
|
background: white;
|
|
border: 1px solid #dee2e6;
|
|
border-radius: 8px;
|
|
padding: 10px;
|
|
transition: all 0.3s ease;
|
|
cursor: pointer;
|
|
position: relative;
|
|
}
|
|
|
|
.media-card:hover {
|
|
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
|
transform: translateY(-2px);
|
|
}
|
|
|
|
body.dark-mode .media-card {
|
|
background: #1a202c;
|
|
border-color: #4a5568;
|
|
}
|
|
|
|
.media-thumbnail {
|
|
width: 100%;
|
|
height: 150px;
|
|
border-radius: 6px;
|
|
overflow: hidden;
|
|
background: #f0f0f0;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
margin-bottom: 10px;
|
|
}
|
|
|
|
body.dark-mode .media-thumbnail {
|
|
background: #2d3748;
|
|
}
|
|
|
|
.media-thumbnail img {
|
|
width: 100%;
|
|
height: 100%;
|
|
object-fit: cover;
|
|
}
|
|
|
|
.media-icon {
|
|
font-size: 64px;
|
|
}
|
|
|
|
.media-info {
|
|
font-size: 12px;
|
|
color: #6c757d;
|
|
}
|
|
|
|
body.dark-mode .media-info {
|
|
color: #a0aec0;
|
|
}
|
|
|
|
.media-filename {
|
|
font-weight: 500;
|
|
margin-bottom: 5px;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
body.dark-mode .media-filename {
|
|
color: #e2e8f0;
|
|
}
|
|
|
|
.delete-btn {
|
|
position: absolute;
|
|
top: 5px;
|
|
right: 5px;
|
|
background: #dc3545;
|
|
color: white;
|
|
border: none;
|
|
border-radius: 50%;
|
|
width: 28px;
|
|
height: 28px;
|
|
cursor: pointer;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 16px;
|
|
opacity: 0;
|
|
transition: opacity 0.3s ease;
|
|
}
|
|
|
|
.media-card:hover .delete-btn {
|
|
opacity: 1;
|
|
}
|
|
|
|
.delete-btn:hover {
|
|
background: #a02834;
|
|
}
|
|
|
|
.playlist-badge {
|
|
display: inline-block;
|
|
padding: 3px 8px;
|
|
border-radius: 10px;
|
|
font-size: 10px;
|
|
font-weight: 600;
|
|
margin-top: 5px;
|
|
}
|
|
|
|
.playlist-badge.in-use {
|
|
background: #ffc107;
|
|
color: #000;
|
|
}
|
|
|
|
.playlist-badge.unused {
|
|
background: #28a745;
|
|
color: white;
|
|
}
|
|
|
|
body.dark-mode .playlist-badge.in-use {
|
|
background: #856404;
|
|
color: #ffc107;
|
|
}
|
|
|
|
body.dark-mode .upload-section {
|
|
background: #2d3748 !important;
|
|
border: 1px solid #4a5568;
|
|
}
|
|
|
|
body.dark-mode .playlist-badge.unused {
|
|
background: #1a4d2e;
|
|
color: #86efac;
|
|
}
|
|
|
|
.type-badge {
|
|
display: inline-block;
|
|
padding: 2px 8px;
|
|
border-radius: 12px;
|
|
font-size: 10px;
|
|
font-weight: 600;
|
|
text-transform: uppercase;
|
|
}
|
|
|
|
.type-badge.image { background: #d4edda; color: #155724; }
|
|
.type-badge.video { background: #cce5ff; color: #004085; }
|
|
.type-badge.pdf { background: #fff3cd; color: #856404; }
|
|
.type-badge.pptx { background: #f8d7da; color: #721c24; }
|
|
|
|
body.dark-mode .type-badge.image { background: #1a4d2e; color: #86efac; }
|
|
body.dark-mode .type-badge.video { background: #1e3a5f; color: #93c5fd; }
|
|
body.dark-mode .type-badge.pdf { background: #4a3800; color: #fbbf24; }
|
|
body.dark-mode .type-badge.pptx { background: #4a1a1a; color: #fca5a5; }
|
|
|
|
.stats-box {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
|
gap: 15px;
|
|
margin-bottom: 30px;
|
|
}
|
|
|
|
.stat-item {
|
|
background: white;
|
|
border: 1px solid #dee2e6;
|
|
border-radius: 8px;
|
|
padding: 15px;
|
|
text-align: center;
|
|
}
|
|
|
|
body.dark-mode .stat-item {
|
|
background: #1a202c;
|
|
border-color: #4a5568;
|
|
}
|
|
|
|
.stat-value {
|
|
font-size: 32px;
|
|
font-weight: bold;
|
|
color: #7c3aed;
|
|
}
|
|
|
|
body.dark-mode .stat-value {
|
|
color: #a78bfa;
|
|
}
|
|
|
|
.stat-label {
|
|
font-size: 14px;
|
|
color: #6c757d;
|
|
margin-top: 5px;
|
|
}
|
|
|
|
body.dark-mode .stat-label {
|
|
color: #a0aec0;
|
|
}
|
|
|
|
.section-header {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
margin: 30px 0 15px 0;
|
|
padding-bottom: 10px;
|
|
border-bottom: 2px solid #7c3aed;
|
|
}
|
|
|
|
body.dark-mode .section-header {
|
|
border-bottom-color: #a78bfa;
|
|
}
|
|
|
|
.section-header h2 {
|
|
margin: 0;
|
|
font-size: 24px;
|
|
}
|
|
|
|
body.dark-mode .section-header h2 {
|
|
color: #e2e8f0;
|
|
}
|
|
</style>
|
|
|
|
<div style="margin-bottom: 2rem;">
|
|
<h1 style="display: inline-block; margin-right: 1rem; display: flex; align-items: center; gap: 0.5rem;">
|
|
<img src="{{ url_for('static', filename='icons/playlist.svg') }}" alt="" style="width: 32px; height: 32px;">
|
|
📚 Media Library
|
|
</h1>
|
|
<a href="{{ url_for('content.content_list') }}" class="btn" style="float: right; display: inline-flex; align-items: center; gap: 0.5rem;">
|
|
<img src="{{ url_for('static', filename='icons/playlist.svg') }}" alt="" style="width: 18px; height: 18px; filter: brightness(0) invert(1);">
|
|
Back to Playlists
|
|
</a>
|
|
</div>
|
|
|
|
<!-- Statistics -->
|
|
<div class="stats-box">
|
|
<div class="stat-item">
|
|
<div class="stat-value">{{ media_files|length }}</div>
|
|
<div class="stat-label">Total Files</div>
|
|
</div>
|
|
<div class="stat-item">
|
|
<div class="stat-value">{{ images|length }}</div>
|
|
<div class="stat-label">Images</div>
|
|
</div>
|
|
<div class="stat-item">
|
|
<div class="stat-value">{{ videos|length }}</div>
|
|
<div class="stat-label">Videos</div>
|
|
</div>
|
|
<div class="stat-item">
|
|
<div class="stat-value">{{ pdfs|length }}</div>
|
|
<div class="stat-label">PDFs</div>
|
|
</div>
|
|
<div class="stat-item">
|
|
<div class="stat-value">{{ presentations|length }}</div>
|
|
<div class="stat-label">Presentations</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Upload Button -->
|
|
<div class="upload-section" style="text-align: center; padding: 20px; background: #f8f9fa; border-radius: 8px; margin-bottom: 30px;">
|
|
<a href="{{ url_for('content.upload_media_page') }}" class="btn btn-success" style="display: inline-flex; align-items: center; gap: 0.5rem;">
|
|
<img src="{{ url_for('static', filename='icons/upload.svg') }}" alt="" style="width: 18px; height: 18px; filter: brightness(0) invert(1);">
|
|
Upload New Media
|
|
</a>
|
|
</div>
|
|
|
|
<!-- Images Section -->
|
|
{% if images %}
|
|
<div class="section-header">
|
|
<span style="font-size: 32px;">📷</span>
|
|
<h2>Images ({{ images|length }})</h2>
|
|
</div>
|
|
<div class="media-grid">
|
|
{% for media in images %}
|
|
<div class="media-card">
|
|
<button class="delete-btn" onclick="confirmDelete({{ media.id }}, '{{ media.filename }}', {{ media.playlists.count() }})" title="Delete">🗑️</button>
|
|
<div class="media-thumbnail">
|
|
<img src="{{ url_for('static', filename='uploads/' + media.filename) }}"
|
|
alt="{{ media.filename }}"
|
|
onerror="this.style.display='none'; this.parentElement.innerHTML='<span class=\'media-icon\'>📷</span>'">
|
|
</div>
|
|
<div class="media-filename" title="{{ media.filename }}">{{ media.filename }}</div>
|
|
<div class="media-info">
|
|
<span class="type-badge image">Image</span>
|
|
<div style="margin-top: 5px;">{{ "%.1f"|format(media.file_size_mb) }} MB</div>
|
|
<div style="font-size: 10px; margin-top: 3px;">{{ media.uploaded_at | localtime('%Y-%m-%d') }}</div>
|
|
{% if media.playlists.count() > 0 %}
|
|
<div class="playlist-badge in-use">📋 In {{ media.playlists.count() }} playlist(s)</div>
|
|
{% else %}
|
|
<div class="playlist-badge unused">✓ Not in use</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- Videos Section -->
|
|
{% if videos %}
|
|
<div class="section-header">
|
|
<span style="font-size: 32px;">🎥</span>
|
|
<h2>Videos ({{ videos|length }})</h2>
|
|
</div>
|
|
<div class="media-grid">
|
|
{% for media in videos %}
|
|
<div class="media-card">
|
|
<button class="delete-btn" onclick="confirmDelete({{ media.id }}, '{{ media.filename }}', {{ media.playlists.count() }})" title="Delete">🗑️</button>
|
|
<div class="media-thumbnail">
|
|
<span class="media-icon">🎥</span>
|
|
</div>
|
|
<div class="media-filename" title="{{ media.filename }}">{{ media.filename }}</div>
|
|
<div class="media-info">
|
|
<span class="type-badge video">Video</span>
|
|
<div style="margin-top: 5px;">{{ "%.1f"|format(media.file_size_mb) }} MB</div>
|
|
<div style="font-size: 10px; margin-top: 3px;">{{ media.uploaded_at | localtime('%Y-%m-%d') }}</div>
|
|
{% if media.playlists.count() > 0 %}
|
|
<div class="playlist-badge in-use">📋 In {{ media.playlists.count() }} playlist(s)</div>
|
|
{% else %}
|
|
<div class="playlist-badge unused">✓ Not in use</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- PDFs Section -->
|
|
{% if pdfs %}
|
|
<div class="section-header">
|
|
<span style="font-size: 32px;">📄</span>
|
|
<h2>PDFs ({{ pdfs|length }})</h2>
|
|
</div>
|
|
<div class="media-grid">
|
|
{% for media in pdfs %}
|
|
<div class="media-card">
|
|
<button class="delete-btn" onclick="confirmDelete({{ media.id }}, '{{ media.filename }}', {{ media.playlists.count() }})" title="Delete">🗑️</button>
|
|
<div class="media-thumbnail">
|
|
<span class="media-icon">📄</span>
|
|
</div>
|
|
<div class="media-filename" title="{{ media.filename }}">{{ media.filename }}</div>
|
|
<div class="media-info">
|
|
<span class="type-badge pdf">PDF</span>
|
|
<div style="margin-top: 5px;">{{ "%.1f"|format(media.file_size_mb) }} MB</div>
|
|
<div style="font-size: 10px; margin-top: 3px;">{{ media.uploaded_at | localtime('%Y-%m-%d') }}</div>
|
|
{% if media.playlists.count() > 0 %}
|
|
<div class="playlist-badge in-use">📋 In {{ media.playlists.count() }} playlist(s)</div>
|
|
{% else %}
|
|
<div class="playlist-badge unused">✓ Not in use</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- Presentations Section -->
|
|
{% if presentations %}
|
|
<div class="section-header">
|
|
<span style="font-size: 32px;">📊</span>
|
|
<h2>Presentations ({{ presentations|length }})</h2>
|
|
</div>
|
|
<div class="media-grid">
|
|
{% for media in presentations %}
|
|
<div class="media-card">
|
|
<button class="delete-btn" onclick="confirmDelete({{ media.id }}, '{{ media.filename }}', {{ media.playlists.count() }})" title="Delete">🗑️</button>
|
|
<div class="media-thumbnail">
|
|
<span class="media-icon">📊</span>
|
|
</div>
|
|
<div class="media-filename" title="{{ media.filename }}">{{ media.filename }}</div>
|
|
<div class="media-info">
|
|
<span class="type-badge pptx">PPTX</span>
|
|
<div style="margin-top: 5px;">{{ "%.1f"|format(media.file_size_mb) }} MB</div>
|
|
<div style="font-size: 10px; margin-top: 3px;">{{ media.uploaded_at | localtime('%Y-%m-%d') }}</div>
|
|
{% if media.playlists.count() > 0 %}
|
|
<div class="playlist-badge in-use">📋 In {{ media.playlists.count() }} playlist(s)</div>
|
|
{% else %}
|
|
<div class="playlist-badge unused">✓ Not in use</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- Others Section -->
|
|
{% if others %}
|
|
<div class="section-header">
|
|
<span style="font-size: 32px;">📁</span>
|
|
<h2>Other Files ({{ others|length }})</h2>
|
|
</div>
|
|
<div class="media-grid">
|
|
{% for media in others %}
|
|
<div class="media-card">
|
|
<button class="delete-btn" onclick="confirmDelete({{ media.id }}, '{{ media.filename }}', {{ media.playlists.count() }})" title="Delete">🗑️</button>
|
|
<div class="media-thumbnail">
|
|
<span class="media-icon">📁</span>
|
|
</div>
|
|
<div class="media-filename" title="{{ media.filename }}">{{ media.filename }}</div>
|
|
<div class="media-info">
|
|
<span class="type-badge">{{ media.content_type }}</span>
|
|
<div style="margin-top: 5px;">{{ "%.1f"|format(media.file_size_mb) }} MB</div>
|
|
<div style="font-size: 10px; margin-top: 3px;">{{ media.uploaded_at | localtime('%Y-%m-%d') }}</div>
|
|
{% if media.playlists.count() > 0 %}
|
|
<div class="playlist-badge in-use">📋 In {{ media.playlists.count() }} playlist(s)</div>
|
|
{% else %}
|
|
<div class="playlist-badge unused">✓ Not in use</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% if not media_files %}
|
|
<div style="text-align: center; padding: 80px 20px;">
|
|
<div style="font-size: 96px; margin-bottom: 20px;">📭</div>
|
|
<h2 style="color: #6c757d;">No Media Files Yet</h2>
|
|
<p style="color: #999; margin-bottom: 30px;">Start by uploading your first media file!</p>
|
|
<a href="{{ url_for('content.upload_media_page') }}" class="btn btn-success" style="display: inline-flex; align-items: center; gap: 0.5rem;">
|
|
<img src="{{ url_for('static', filename='icons/upload.svg') }}" alt="" style="width: 18px; height: 18px; filter: brightness(0) invert(1);">
|
|
Upload Media
|
|
</a>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- Delete Confirmation Modal -->
|
|
<div id="deleteModal" style="display: none; position: fixed; z-index: 1000; left: 0; top: 0; width: 100%; height: 100%; overflow: auto; background-color: rgba(0,0,0,0.5);">
|
|
<div style="background-color: #fefefe; margin: 10% auto; padding: 30px; border-radius: 12px; width: 90%; max-width: 500px; box-shadow: 0 4px 20px rgba(0,0,0,0.3);">
|
|
<h2 style="margin-top: 0; color: #dc3545; display: flex; align-items: center; gap: 0.5rem;">
|
|
<span style="font-size: 2rem;">⚠️</span>
|
|
Confirm Delete
|
|
</h2>
|
|
<p style="font-size: 1.1rem; margin: 1.5rem 0;">
|
|
Are you sure you want to delete <strong id="deleteFilename"></strong>?
|
|
</p>
|
|
<div id="playlistWarning" style="display: none; background: #fff3cd; border-left: 4px solid #ffc107; padding: 12px; margin: 1rem 0; border-radius: 4px;">
|
|
<strong style="color: #856404;">⚠️ Warning:</strong>
|
|
<p style="margin: 0.5rem 0 0 0; color: #856404;">This file is used in <strong id="playlistCount"></strong> playlist(s). Deleting it will remove it from all playlists and increment their version numbers.</p>
|
|
</div>
|
|
<p style="color: #dc3545; margin: 1rem 0;">
|
|
<strong>This action cannot be undone!</strong>
|
|
</p>
|
|
<div style="display: flex; gap: 10px; justify-content: flex-end; margin-top: 2rem;">
|
|
<button onclick="closeDeleteModal()" class="btn" style="background: #6c757d;">
|
|
Cancel
|
|
</button>
|
|
<form id="deleteForm" method="POST" style="margin: 0;">
|
|
<button type="submit" class="btn" style="background: #dc3545;">
|
|
Yes, Delete File
|
|
</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<style>
|
|
body.dark-mode #deleteModal {
|
|
background-color: rgba(0, 0, 0, 0.8);
|
|
}
|
|
|
|
body.dark-mode #deleteModal > div {
|
|
background-color: #1a202c;
|
|
border: 1px solid #4a5568;
|
|
color: #e2e8f0;
|
|
}
|
|
|
|
body.dark-mode #deleteModal h2 {
|
|
color: #f87171;
|
|
}
|
|
|
|
body.dark-mode #deleteModal p {
|
|
color: #e2e8f0;
|
|
}
|
|
|
|
body.dark-mode #deleteModal strong {
|
|
color: #fbbf24;
|
|
}
|
|
|
|
body.dark-mode #playlistWarning {
|
|
background: #4a3800;
|
|
border-left-color: #ffc107;
|
|
}
|
|
|
|
body.dark-mode #playlistWarning strong,
|
|
body.dark-mode #playlistWarning p {
|
|
color: #fbbf24;
|
|
}
|
|
|
|
body.dark-mode #deleteForm button {
|
|
background: #dc3545 !important;
|
|
color: white !important;
|
|
}
|
|
|
|
body.dark-mode #deleteForm button:hover {
|
|
background: #a02834 !important;
|
|
}
|
|
</style>
|
|
|
|
<script>
|
|
let deleteMediaId = null;
|
|
|
|
function confirmDelete(mediaId, filename, playlistCount) {
|
|
deleteMediaId = mediaId;
|
|
document.getElementById('deleteFilename').textContent = filename;
|
|
document.getElementById('deleteForm').action = "{{ url_for('content.delete_media', media_id=0) }}".replace('/0', '/' + mediaId);
|
|
|
|
// Show playlist warning if file is in use
|
|
if (playlistCount > 0) {
|
|
document.getElementById('playlistWarning').style.display = 'block';
|
|
document.getElementById('playlistCount').textContent = playlistCount;
|
|
} else {
|
|
document.getElementById('playlistWarning').style.display = 'none';
|
|
}
|
|
|
|
document.getElementById('deleteModal').style.display = 'block';
|
|
}
|
|
|
|
function closeDeleteModal() {
|
|
document.getElementById('deleteModal').style.display = 'none';
|
|
deleteMediaId = null;
|
|
}
|
|
|
|
// Close modal when clicking outside
|
|
window.onclick = function(event) {
|
|
const modal = document.getElementById('deleteModal');
|
|
if (event.target == modal) {
|
|
closeDeleteModal();
|
|
}
|
|
}
|
|
|
|
// Close modal with ESC key
|
|
document.addEventListener('keydown', function(event) {
|
|
if (event.key === 'Escape') {
|
|
closeDeleteModal();
|
|
}
|
|
});
|
|
</script>
|
|
|
|
{% endblock %}
|