feat: add Settings tab with provider management and language preferences
- Implemented AppSettings model and table using SQLModel. - Created Settings router with endpoints for preferences and provider toggling. - Added Settings tab to the UI with real-time health status of providers. - Integrated language and provider filtering into anime and series search logic. - Updated templates to respect user-defined settings.
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
{% macro anime_card(anime, in_watchlist=False) %}
|
||||
{% macro anime_card(anime, in_watchlist=False, lang='vostfr') %}
|
||||
<div class="anime-card" id="anime-{{ anime.url | hash }}">
|
||||
<div class="anime-poster">
|
||||
{% set poster = anime.cover_image or (anime.metadata.poster_image if anime.metadata else None) or 'https://placehold.co/400x600/161625/00d9ff?text=No+Image' %}
|
||||
@@ -17,7 +17,7 @@
|
||||
<div class="anime-overlay">
|
||||
<div class="overlay-buttons">
|
||||
<button class="btn btn-primary btn-circle"
|
||||
hx-get="/api/anime/episodes?url={{ anime.url | urlencode }}"
|
||||
hx-get="/api/anime/episodes?url={{ anime.url | urlencode }}&lang={{ lang }}"
|
||||
hx-target="#player-container"
|
||||
hx-swap="innerHTML"
|
||||
title="Play">
|
||||
@@ -32,20 +32,21 @@
|
||||
|
||||
<div class="anime-meta-tags">
|
||||
<span class="badge">{{ anime.provider_id or 'Anime' }}</span>
|
||||
<span class="badge" style="color: var(--primary)">{{ lang | upper }}</span>
|
||||
{% if anime.metadata and anime.metadata.status %}
|
||||
<span class="badge" style="color: var(--primary)">{{ anime.metadata.status }}</span>
|
||||
<span class="badge" style="color: #aaa">{{ anime.metadata.status }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="anime-card-buttons">
|
||||
<button class="btn btn-primary btn-small"
|
||||
hx-get="/api/anime/episodes?url={{ anime.url | urlencode }}"
|
||||
hx-get="/api/anime/episodes?url={{ anime.url | urlencode }}&lang={{ lang }}"
|
||||
hx-target="#player-container"
|
||||
hx-swap="innerHTML">
|
||||
<i class="fas fa-eye"></i> <span>Regarder</span>
|
||||
</button>
|
||||
<button class="btn btn-secondary btn-small"
|
||||
hx-get="/api/anime/episodes?url={{ anime.url | urlencode }}"
|
||||
hx-get="/api/anime/episodes?url={{ anime.url | urlencode }}&lang={{ lang }}"
|
||||
hx-target="#player-container"
|
||||
hx-swap="innerHTML">
|
||||
<i class="fas fa-download"></i> <span>Télécharger</span>
|
||||
@@ -55,7 +56,7 @@
|
||||
{% if not in_watchlist %}
|
||||
<button class="btn btn-secondary btn-small btn-block"
|
||||
hx-post="/api/watchlist"
|
||||
hx-vals='{"anime_url": "{{ anime.url }}", "anime_title": "{{ anime.title }}", "provider_id": "{{ anime.provider_id }}"}'
|
||||
hx-vals='{"anime_url": "{{ anime.url }}", "anime_title": "{{ anime.title }}", "provider_id": "{{ anime.provider_id }}", "lang": "{{ lang }}"}'
|
||||
hx-swap="none"
|
||||
hx-on::after-request="this.innerHTML='<i class=\'fas fa-check\'></i> Suivi'; this.disabled=true; this.classList.add('followed')">
|
||||
<i class="fas fa-plus"></i> Watchlist
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<h3 class="provider-title">{{ provider_id | upper }}</h3>
|
||||
<div class="anime-grid">
|
||||
{% for anime in items %}
|
||||
{{ anime_card(anime) }}
|
||||
{{ anime_card(anime, lang=settings.default_lang if settings else 'vostfr') }}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -65,5 +65,14 @@
|
||||
</svg>
|
||||
Téléchargements
|
||||
</button>
|
||||
<button class="tab"
|
||||
:class="{ 'active': activeTab === 'settings' }"
|
||||
@click="activeTab = 'settings'; window.dispatchEvent(new CustomEvent('set-tab', { detail: { tab: 'settings' } }))">
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"></path>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path>
|
||||
</svg>
|
||||
Paramètres
|
||||
</button>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{% macro series_card(series, in_watchlist=False) %}
|
||||
{% macro series_card(series, in_watchlist=False, lang='vf') %}
|
||||
<div class="anime-card" id="series-{{ series.url | hash }}">
|
||||
<div class="anime-poster">
|
||||
<img src="{{ series.cover_image or 'https://placehold.co/400x600/161625/ff6b6b?text=No+Image' }}"
|
||||
@@ -9,7 +9,7 @@
|
||||
<div class="anime-overlay">
|
||||
<div class="overlay-buttons">
|
||||
<button class="btn btn-primary btn-circle"
|
||||
hx-get="/api/anime/episodes?url={{ series.url | urlencode }}&lang=vf"
|
||||
hx-get="/api/anime/episodes?url={{ series.url | urlencode }}&lang={{ lang }}"
|
||||
hx-target="#player-container"
|
||||
hx-swap="innerHTML"
|
||||
title="Play">
|
||||
@@ -21,18 +21,19 @@
|
||||
<div class="anime-info">
|
||||
<h3 class="anime-title" title="{{ series.title }}">{{ series.title }}</h3>
|
||||
<div class="anime-meta-tags">
|
||||
<span class="badge">FS7</span>
|
||||
<span class="badge">{{ series.provider_id | upper if series.provider_id else 'FS7' }}</span>
|
||||
<span class="badge" style="color: var(--primary)">{{ lang | upper }}</span>
|
||||
</div>
|
||||
|
||||
<div class="anime-card-buttons">
|
||||
<button class="btn btn-primary btn-small"
|
||||
hx-get="/api/anime/episodes?url={{ series.url | urlencode }}&lang=vf"
|
||||
hx-get="/api/anime/episodes?url={{ series.url | urlencode }}&lang={{ lang }}"
|
||||
hx-target="#player-container"
|
||||
hx-swap="innerHTML">
|
||||
<i class="fas fa-eye"></i> <span>Regarder</span>
|
||||
</button>
|
||||
<button class="btn btn-secondary btn-small"
|
||||
hx-get="/api/anime/episodes?url={{ series.url | urlencode }}&lang=vf"
|
||||
hx-get="/api/anime/episodes?url={{ series.url | urlencode }}&lang={{ lang }}"
|
||||
hx-target="#player-container"
|
||||
hx-swap="innerHTML">
|
||||
<i class="fas fa-download"></i> <span>Télécharger</span>
|
||||
@@ -42,7 +43,7 @@
|
||||
{% if not in_watchlist %}
|
||||
<button class="btn btn-secondary btn-small btn-block"
|
||||
hx-post="/api/watchlist"
|
||||
hx-vals='{"anime_url": "{{ series.url }}", "anime_title": "{{ series.title }}", "provider_id": "fs7", "lang": "vf"}'
|
||||
hx-vals='{"anime_url": "{{ series.url }}", "anime_title": "{{ series.title }}", "provider_id": "{{ series.provider_id or 'fs7' }}", "lang": "{{ lang }}"}'
|
||||
hx-swap="none"
|
||||
hx-on::after-request="this.innerHTML='<i class=\'fas fa-check\'></i> Suivi'; this.disabled=true; this.classList.add('followed')">
|
||||
<i class="fas fa-plus"></i> Watchlist
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<h3 class="provider-title">{{ provider_id | upper }}</h3>
|
||||
<div class="anime-grid">
|
||||
{% for series in items %}
|
||||
{{ series_card(series) }}
|
||||
{{ series_card(series, lang=settings.default_lang if settings else 'vf') }}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
<div class="settings-container section-container">
|
||||
<div class="section-header">
|
||||
<h2>⚙️ Paramètres</h2>
|
||||
</div>
|
||||
|
||||
<!-- General Preferences -->
|
||||
<div class="settings-card card" style="margin-bottom: 30px; padding: 25px; background: var(--bg-card); border-radius: var(--card-radius); border: 1px solid rgba(255,255,255,0.05);">
|
||||
<h3 style="margin-bottom: 20px; color: var(--primary);">Général</h3>
|
||||
|
||||
<form hx-patch="/api/settings" hx-swap="none" hx-ext="json-enc" class="settings-form">
|
||||
<div class="form-group">
|
||||
<label for="default_lang">Langue par défaut</label>
|
||||
<select name="default_lang" id="default_lang" class="btn btn-secondary btn-block" style="text-align: left; padding: 12px 15px;">
|
||||
<option value="vostfr" {% if settings.default_lang == 'vostfr' %}selected{% endif %}>VOSTFR (Version Originale Sous-Titrée Français)</option>
|
||||
<option value="vf" {% if settings.default_lang == 'vf' %}selected{% endif %}>VF (Version Française)</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group" style="margin-top: 20px;">
|
||||
<label for="theme">Thème</label>
|
||||
<select name="theme" id="theme" class="btn btn-secondary btn-block" style="text-align: left; padding: 12px 15px;">
|
||||
<option value="dark" {% if settings.theme == 'dark' %}selected{% endif %}>Sombre (Premium Dark)</option>
|
||||
<option value="light" {% if settings.theme == 'light' %}selected{% endif %}>Clair (Soon)</option>
|
||||
<option value="oled" {% if settings.theme == 'oled' %}selected{% endif %}>OLED (Noir absolu)</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary" style="margin-top: 20px; width: 100%;">
|
||||
<i class="fas fa-save"></i> Enregistrer les préférences
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Providers Management -->
|
||||
<div class="settings-card card" style="padding: 25px; background: var(--bg-card); border-radius: var(--card-radius); border: 1px solid rgba(255,255,255,0.05);">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
|
||||
<h3 style="margin: 0; color: var(--primary);">Disponibilité des Fournisseurs</h3>
|
||||
<button class="btn btn-secondary btn-small" hx-post="/api/providers/health/check" hx-swap="none">
|
||||
<i class="fas fa-sync-alt"></i> Forcer vérification
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="providers-settings-list" style="display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 15px;">
|
||||
{% for provider in providers %}
|
||||
<div class="provider-status-card" style="padding: 15px; background: rgba(255,255,255,0.02); border-radius: 10px; border: 1px solid rgba(255,255,255,0.05); display: flex; align-items: center; justify-content: space-between;">
|
||||
<div style="display: flex; align-items: center; gap: 12px;">
|
||||
<span style="font-size: 1.5rem;">{{ provider.icon }}</span>
|
||||
<div>
|
||||
<div style="font-weight: 600;">{{ provider.name }}</div>
|
||||
<div style="font-size: 0.75rem; display: flex; align-items: center; gap: 5px;">
|
||||
<span class="status-dot" style="width: 8px; height: 8px; border-radius: 50%; background: {% if provider.status == 'up' %}var(--accent){% elif provider.status == 'down' %}var(--danger){% else %}#aaa{% endif %};"></span>
|
||||
<span style="color: {% if provider.status == 'up' %}var(--accent){% elif provider.status == 'down' %}var(--danger){% else %}var(--text-dim){% endif %}; text-transform: uppercase; font-weight: 800;">
|
||||
{{ provider.status | upper }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="btn {% if provider.enabled %}btn-secondary{% else %}btn-accent{% endif %} btn-sm"
|
||||
hx-post="/api/settings/providers/{{ provider.id }}/toggle"
|
||||
hx-target="closest .settings-container"
|
||||
hx-get="/api/settings/ui"
|
||||
hx-trigger="click delay:200ms"
|
||||
style="min-width: 100px;">
|
||||
{% if provider.enabled %}Désactiver{% else %}Activer{% endif %}
|
||||
</button>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.settings-form label {
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
font-weight: 600;
|
||||
color: var(--text-dim);
|
||||
}
|
||||
.status-dot {
|
||||
display: inline-block;
|
||||
box-shadow: 0 0 5px currentColor;
|
||||
}
|
||||
</style>
|
||||
@@ -142,6 +142,14 @@
|
||||
{% include "components/downloads_section.html" %}
|
||||
</div>
|
||||
|
||||
<div id="tab-settings" class="tab-content" x-show="activeTab === 'settings'">
|
||||
<div hx-get="/api/settings/ui" hx-trigger="load, set-tab[detail.tab === 'settings'] from:window" hx-swap="innerHTML">
|
||||
<div class="loading-placeholder">
|
||||
<div class="spinner"></div> Chargement des paramètres...
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<!-- End of main-content -->
|
||||
|
||||
|
||||
Reference in New Issue
Block a user