style: Implement tutorial video upload functionality with preview and management features

This commit is contained in:
2026-05-15 21:40:27 +02:00
parent f65188d596
commit 442c7d24d2
4 changed files with 365 additions and 3 deletions
@@ -0,0 +1,276 @@
{% extends "base.html" %}
{% block title %}Admin | Tutorial-Upload{% endblock %}
{% block head %}
{{ super() }}
<style>
.upload-shell {
display: grid;
grid-template-columns: 0.9fr 1.1fr;
gap: 1.2rem;
align-items: start;
}
.panel {
background: #ffffff;
border: 1px solid #d7e1ea;
border-radius: 16px;
box-shadow: 0 14px 34px rgba(17, 43, 60, 0.06);
}
.upload-form {
padding: 1.2rem;
position: sticky;
top: 100px;
}
.upload-form h1,
.list-panel h2 {
margin: 0;
color: #0f354d;
font-family: "Merriweather", Georgia, serif;
}
.upload-form p,
.list-panel p {
color: #587286;
margin-top: 0.35rem;
}
.field {
margin-top: 0.9rem;
}
.field label {
display: block;
margin-bottom: 0.35rem;
font-size: 0.9rem;
font-weight: 700;
color: #1f4b65;
}
.field input,
.field textarea {
width: 100%;
border: 1px solid #c8d7e3;
border-radius: 10px;
padding: 0.7rem 0.8rem;
font: inherit;
color: #17384f;
background: #fff;
}
.field textarea {
min-height: 120px;
resize: vertical;
}
.field input:focus,
.field textarea:focus {
outline: none;
border-color: #2f79a7;
box-shadow: 0 0 0 3px rgba(58, 133, 180, 0.18);
}
.hint {
margin-top: 0.4rem;
font-size: 0.82rem;
color: #527085;
line-height: 1.5;
}
.submit-btn {
margin-top: 1rem;
width: 100%;
border: none;
border-radius: 999px;
padding: 0.8rem 1rem;
font-size: 0.96rem;
font-weight: 700;
color: #fff;
background: linear-gradient(120deg, #0a5f8f 0%, #08486c 100%);
cursor: pointer;
}
.preview-box {
margin-top: 1rem;
border-radius: 14px;
overflow: hidden;
border: 1px solid #d7e1ea;
background: linear-gradient(135deg, #f4f8fb 0%, #eaf1f6 100%);
}
.preview-aspect {
position: relative;
padding-bottom: 56.25%;
height: 0;
}
.preview-aspect video,
.preview-aspect img,
.preview-aspect iframe {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
object-fit: cover;
border: 0;
}
.preview-empty {
padding: 1rem;
text-align: center;
color: #5c7485;
font-size: 0.9rem;
}
.upload-list {
padding: 1.2rem;
}
.upload-list .entry {
border: 1px solid #d7e1ea;
border-radius: 14px;
padding: 1rem;
margin-top: 0.85rem;
background: #fff;
}
.upload-list .entry h3 {
margin: 0 0 0.35rem;
font-size: 1.05rem;
color: #113d59;
}
.meta {
font-size: 0.86rem;
color: #637b8c;
margin-top: 0.35rem;
}
.video-link {
display: inline-block;
margin-top: 0.55rem;
color: #0a4c74;
font-weight: 700;
}
.badge {
display: inline-flex;
align-items: center;
border-radius: 999px;
padding: 0.24rem 0.55rem;
background: #e7f1f5;
color: #1f5c73;
font-size: 0.75rem;
font-weight: 700;
margin-left: 0.4rem;
}
@media (max-width: 980px) {
.upload-shell {
grid-template-columns: 1fr;
}
.upload-form {
position: static;
}
}
</style>
{% endblock %}
{% block content %}
<section class="panel upload-form">
<h1>Tutorial-Upload</h1>
<p>Videodatei hochladen und sofort als Tutorial im Nutzerbereich bereitstellen.</p>
<form method="POST" action="{{ url_for('admin_tutorial_upload') }}" enctype="multipart/form-data">
<div class="field">
<label for="title">Titel</label>
<input id="title" name="title" type="text" placeholder="z. B. Erste Schritte mit Invario" required>
</div>
<div class="field">
<label for="video_file">Videodatei</label>
<input id="video_file" name="video_file" type="file" accept="video/mp4,video/webm,video/ogg,.mp4,.webm,.ogv,.ogg,.m4v" required>
<div class="hint">Erlaubte Formate: .mp4, .webm, .ogv, .ogg, .m4v</div>
</div>
<div class="field">
<label for="description">Beschreibung</label>
<textarea id="description" name="description" placeholder="Kurze Beschreibung des Tutorials"></textarea>
</div>
<div class="field">
<label for="thumbnail_url">Thumbnail-URL (optional)</label>
<input id="thumbnail_url" name="thumbnail_url" type="url" placeholder="https://...">
</div>
<div class="field">
<label for="duration">Dauer (optional)</label>
<input id="duration" name="duration" type="text" placeholder="z. B. 12:34">
</div>
<div class="field">
<label for="category">Kategorie (optional)</label>
<input id="category" name="category" type="text" placeholder="z. B. Anfänger">
</div>
<button type="submit" class="submit-btn">Video hochladen</button>
</form>
<div class="preview-box" aria-live="polite">
<div id="preview-empty" class="preview-empty">Vorschau erscheint nach Auswahl einer Videodatei.</div>
<div class="preview-aspect" id="preview-wrap" hidden>
<video id="preview-video" controls playsinline></video>
</div>
</div>
</section>
<section class="panel upload-list">
<h2>Aktuelle Uploads</h2>
<p>Neu hochgeladene Tutorials erscheinen hier und im Nutzerbereich.</p>
{% if tutorials %}
{% for tutorial in tutorials %}
<article class="entry">
<h3>{{ tutorial.title }} <span class="badge">Upload</span></h3>
<div class="meta">
{{ tutorial.category or 'Allgemein' }} | {{ tutorial.duration or 'ohne Dauer' }} | {{ tutorial.created_at[:10] }}
</div>
<p>{{ tutorial.description }}</p>
<a class="video-link" href="{{ tutorial.video_url }}" target="_blank" rel="noopener">Video öffnen</a>
</article>
{% endfor %}
{% else %}
<article class="entry"><p>Noch keine hochgeladenen Tutorials vorhanden.</p></article>
{% endif %}
</section>
<script>
(function () {
const input = document.getElementById('video_file');
const previewWrap = document.getElementById('preview-wrap');
const previewEmpty = document.getElementById('preview-empty');
const previewVideo = document.getElementById('preview-video');
let currentObjectUrl = null;
input.addEventListener('change', function () {
const file = input.files && input.files[0];
if (currentObjectUrl) {
URL.revokeObjectURL(currentObjectUrl);
currentObjectUrl = null;
}
if (!file) {
previewWrap.hidden = true;
previewEmpty.hidden = false;
previewVideo.removeAttribute('src');
previewVideo.load();
return;
}
currentObjectUrl = URL.createObjectURL(file);
previewVideo.src = currentObjectUrl;
previewVideo.load();
previewEmpty.hidden = true;
previewWrap.hidden = false;
});
})();
</script>
{% endblock %}