Add complete frontend interface with Jinja2 templates

- Created base.html template with navigation and flash messages
- Created dashboard.html for video listing sorted by date
- Created channels.html for channel management
- Created add_channel.html with subscription form
- Created watch.html with HTML5 video player
- Created static/style.css with YouTube-inspired dark theme
- Updated main.py with frontend routes:
  - / (index): Dashboard with all videos
  - /channels: Channel management page
  - /add-channel: Add new channel form (GET/POST)
  - /watch/<video_id>: Video player page
- Added new API endpoints:
  - /api/videos/refresh/<channel_id>: Refresh channel videos
  - /api/video/stream/<video_id>: Stream/download video files
- Enhanced /api/download/<video_id> with status checks
- Updated CLAUDE.md with comprehensive frontend documentation

Features:
- Video grid with thumbnails and download status badges
- Inline download buttons for pending videos
- Channel subscription and refresh functionality
- HTML5 video player for downloaded videos
- Auto-refresh during video downloads
- Responsive design for mobile/desktop
- Flash message system for user feedback
- Dark theme with hover effects and animations

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-26 14:18:33 -05:00
parent cf692d2299
commit 9bcd439024
8 changed files with 1198 additions and 24 deletions

77
templates/dashboard.html Normal file
View File

@@ -0,0 +1,77 @@
{% extends "base.html" %}
{% block title %}Videos - YottoB{% endblock %}
{% block content %}
<div class="dashboard">
<div class="dashboard-header">
<h2>All Videos</h2>
<p class="video-count">{{ videos|length }} videos</p>
</div>
{% if videos %}
<div class="video-grid">
{% for video in videos %}
<div class="video-card">
<a href="/watch/{{ video.id }}" class="video-thumbnail">
{% if video.thumbnail_url %}
<img src="{{ video.thumbnail_url }}" alt="{{ video.title }}">
{% else %}
<div class="thumbnail-placeholder">No Thumbnail</div>
{% endif %}
<div class="video-duration">
{% if video.download_status == 'completed' %}
<span class="badge badge-success">Downloaded</span>
{% elif video.download_status == 'downloading' %}
<span class="badge badge-info">Downloading...</span>
{% elif video.download_status == 'failed' %}
<span class="badge badge-error">Failed</span>
{% endif %}
</div>
</a>
<div class="video-info">
<h3 class="video-title">
<a href="/watch/{{ video.id }}">{{ video.title }}</a>
</h3>
<p class="video-channel">{{ video.channel.title }}</p>
<div class="video-meta">
<span class="video-date">{{ video.published_at.strftime('%b %d, %Y') }}</span>
{% if video.download_status != 'completed' and video.download_status != 'downloading' %}
<button class="btn-download" onclick="downloadVideo({{ video.id }})">Download</button>
{% endif %}
</div>
</div>
</div>
{% endfor %}
</div>
{% else %}
<div class="empty-state">
<h3>No videos yet</h3>
<p>Add a channel to start downloading videos</p>
<a href="/add-channel" class="btn btn-primary">Add Channel</a>
</div>
{% endif %}
</div>
{% endblock %}
{% block scripts %}
<script>
function downloadVideo(videoId) {
fetch(`/api/download/${videoId}`, {
method: 'POST'
})
.then(response => response.json())
.then(data => {
if (data.status === 'success') {
alert('Download started!');
location.reload();
} else {
alert('Failed to start download: ' + data.message);
}
})
.catch(error => {
alert('Error: ' + error);
});
}
</script>
{% endblock %}