xbvr sync

This commit is contained in:
Lars Behrends
2025-11-14 02:42:44 +01:00
parent aed9d87c5c
commit 1b053148f0
12 changed files with 2035 additions and 1086 deletions

View File

@@ -0,0 +1,181 @@
{#
TV Show Card Component
Parameters:
- tvshow: The TV show object with all properties
- view_mode: 'grid', 'list', or 'covers' (optional, defaults to 'grid')
- show_rating: Whether to show rating (optional, defaults to true)
- show_seasons: Whether to show season count (optional, defaults to true)
- show_episodes: Whether to show episode count (optional, defaults to true)
- show_genres: Whether to show genre tags (optional, defaults to true)
#}
{% set view_mode = view_mode|default('grid') %}
{% set show_rating = show_rating|default(true) %}
{% set show_seasons = show_seasons|default(true) %}
{% set show_episodes = show_episodes|default(true) %}
{% set show_genres = show_genres|default(true) %}
{% if view_mode == 'list' %}
<!-- List View Card -->
<li class="px-4 py-3">
<div class="flex justify-between items-center">
<div class="flex items-center">
{% if tvshow.poster_url %}
<img class="rounded mr-3" style="width: 64px; height: 96px; object-fit: cover;" src="/images/{{ tvshow.poster_url }}" alt="{{ tvshow.title }}">
{% else %}
<div class="bg-gray-100 rounded mr-3 flex items-center justify-center" style="width: 64px; height: 96px;">
<svg class="text-gray-600" width="32" height="32" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 4V2a1 1 0 011-1h8a1 1 0 011 1v2h4a1 1 0 010 2h-1v14a2 2 0 01-2 2H6a2 2 0 01-2-2V6H3a1 1 0 010-2h4z"/>
</svg>
</div>
{% endif %}
<div class="flex-1">
<h3 class="text-sm font-semibold mb-1">
<a href="{{ path_for('tvshows.show', {'id': tvshow.id}) }}" class="no-underline text-gray-900 hover:text-blue-600">
{{ tvshow.title }}
</a>
</h3>
<div class="flex items-center gap-3 text-sm text-gray-600">
{% if tvshow.first_air_date %}
<span>{{ tvshow.first_air_date|date('Y') }}</span>
{% endif %}
{% if show_rating and tvshow.rating %}
<span>⭐ {{ tvshow.rating }}/10</span>
{% endif %}
{% if show_seasons and tvshow.number_of_seasons %}
<span>{{ tvshow.number_of_seasons }} season{{ tvshow.number_of_seasons > 1 ? 's' : '' }}</span>
{% endif %}
{% if show_episodes and tvshow.number_of_episodes %}
<span>{{ tvshow.number_of_episodes }} episode{{ tvshow.number_of_episodes > 1 ? 's' : '' }}</span>
{% endif %}
<span>{{ tvshow.source_name }}</span>
</div>
</div>
</div>
<div class="flex gap-2">
{% if tvshow.is_favorite %}
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-red-100 text-red-800">
Favorite
</span>
{% endif %}
</div>
</div>
</li>
{% elseif view_mode == 'covers' %}
<!-- Cover View Card -->
<div class="group relative bg-white rounded-xl shadow-lg border border-gray-200 overflow-hidden hover:shadow-2xl transition-all duration-300 transform hover:-translate-y-1">
{% if tvshow.poster_url %}
<div class="relative aspect-[2/3] overflow-hidden">
<img src="/images/{{ tvshow.poster_url }}" alt="{{ tvshow.title }}" class="w-full h-full object-cover group-hover:scale-105 transition-transform duration-300">
<!-- Overlay with TV show info -->
<div class="absolute inset-0 bg-gradient-to-t from-black/80 via-transparent to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300">
<div class="absolute bottom-0 left-0 right-0 p-3">
{% if show_rating and tvshow.rating %}
<div class="flex items-center mb-2">
<svg class="w-4 h-4 text-yellow-400 mr-1" fill="currentColor" viewBox="0 0 20 20">
<path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/>
</svg>
<span class="text-white text-sm font-medium">{{ tvshow.rating }}</span>
</div>
{% endif %}
{% if show_seasons and tvshow.number_of_seasons %}
<div class="flex items-center mb-1">
<svg class="w-3 h-3 text-blue-400 mr-1" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M3 4a1 1 0 011-1h12a1 1 0 011 1v2a1 1 0 01-1 1H4a1 1 0 01-1-1V4zM3 10a1 1 0 011-1h6a1 1 0 011 1v6a1 1 0 01-1 1H4a1 1 0 01-1-1v-6zM14 9a1 1 0 00-1 1v6a1 1 0 001 1h2a1 1 0 001-1v-6a1 1 0 00-1-1h-2z" clip-rule="evenodd"/>
</svg>
<span class="text-blue-400 text-xs">{{ tvshow.number_of_seasons }} season{{ tvshow.number_of_seasons > 1 ? 's' : '' }}</span>
</div>
{% endif %}
</div>
</div>
</div>
{% else %}
<div class="flex items-center justify-center bg-gradient-to-br from-gray-100 to-gray-200 aspect-[2/3] min-h-[200px]">
<svg class="text-gray-400 w-12 h-12" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 4V2a1 1 0 011-1h8a1 1 0 011 1v2h4a1 1 0 010 2h-1v14a2 2 0 01-2 2H6a2 2 0 01-2-2V6H3a1 1 0 010-2h4z"/>
</svg>
</div>
{% endif %}
<div class="p-4">
<h6 class="text-sm font-bold truncate mb-1" title="{{ tvshow.title }}">
<a href="{{ path_for('tvshows.show', {'id': tvshow.id}) }}" class="no-underline text-gray-900 hover:text-blue-600 transition-colors">
{{ tvshow.title }}
</a>
</h6>
{% if tvshow.first_air_date %}
<p class="text-xs text-gray-600 font-medium">{{ tvshow.first_air_date|date('Y') }}</p>
{% endif %}
{% if show_genres and tvshow.genre %}
<div class="mt-2">
{% for genre in tvshow.genre|split(',')|slice(0, 2) %}
<span class="inline-block bg-blue-100 text-blue-800 text-xs px-2 py-1 rounded-full font-medium mr-1 mb-1">{{ genre|trim }}</span>
{% endfor %}
</div>
{% endif %}
</div>
</div>
{% else %}
<!-- Grid View Card (Default) -->
<div class="bg-white rounded-lg shadow-md border border-gray-200 h-full">
<div class="p-4">
<div class="flex items-center">
<div class="flex-shrink-0">
{% if tvshow.poster_url %}
<img class="rounded" style="width: 64px; height: 96px; object-fit: cover;" src="/images/{{ tvshow.poster_url }}" alt="{{ tvshow.title }}">
{% else %}
<div class="bg-gray-100 rounded flex items-center justify-center" style="width: 64px; height: 96px;">
<svg class="text-gray-600" width="32" height="32" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 4V2a1 1 0 011-1h8a1 1 0 011 1v2h4a1 1 0 010 2h-1v14a2 2 0 01-2 2H6a2 2 0 01-2-2V6H3a1 1 0 010-2h4z"/>
</svg>
</div>
{% endif %}
</div>
<div class="ml-3 flex-1">
<h5 class="text-lg font-semibold mb-1">
<a href="{{ path_for('tvshows.show', {'id': tvshow.id}) }}" class="no-underline text-gray-900 hover:text-blue-600">
{{ tvshow.title }}
</a>
</h5>
<div class="flex items-center gap-2 text-sm text-gray-600 mb-2">
{% if tvshow.first_air_date %}
<span>{{ tvshow.first_air_date|date('Y') }}</span>
{% endif %}
{% if show_rating and tvshow.rating %}
<span>⭐ {{ tvshow.rating }}/10</span>
{% endif %}
</div>
{% if tvshow.source_name %}
<p class="text-sm text-gray-600 mb-2">
{{ tvshow.source_name }}
</p>
{% endif %}
</div>
</div>
{% if tvshow.overview %}
<div class="mt-3">
<p class="text-sm text-gray-600" style="display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden;">
{{ tvshow.overview|slice(0, 150) }}{% if tvshow.overview|length > 150 %}...{% endif %}
</p>
</div>
{% endif %}
<div class="mt-3 flex justify-between items-center text-sm text-gray-600">
{% if show_seasons and tvshow.number_of_seasons %}
<span>{{ tvshow.number_of_seasons }} season{{ tvshow.number_of_seasons > 1 ? 's' : '' }}</span>
{% endif %}
{% if show_episodes and tvshow.number_of_episodes %}
<span>{{ tvshow.number_of_episodes }} episode{{ tvshow.number_of_episodes > 1 ? 's' : '' }}</span>
{% endif %}
<div class="flex gap-1">
{% if tvshow.is_favorite %}
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-red-100 text-red-800">
Favorite
</span>
{% endif %}
</div>
</div>
</div>
</div>
{% endif %}