feat: redesign download UX — batch select, season download, toast feedback
Episode list: - Added 'Saison complète' header button to download all episodes at once - Added multi-select mode with checkboxes for batch episode download - Individual download buttons now show visual feedback (checkmark + reset) - Better grid/list toggle with selection state indicators Search results (anime + series): - Redesigned download dropdown with icons, descriptions, spinner on click - Smooth scale/opacity transitions on dropdown open/close - Consistent btn-success color for all download actions Series search JS: - Replaced basic <select> with scrollable episode list inline - Added 'Tout télécharger' button per series card - Replaced all alert() calls with toast notifications - Episode buttons show checkmark on successful download Anime details JS: - Added batch download button next to episode select - Fixed pre-existing lint error (escaped quote in translateSynopsis) - Standardized download icon to fa-arrow-down across all cards Recommendations + Tabs JS: - Unified download button color (btn-success) across all card types - Consistent icon (fa-arrow-down) for download actions Toast system: - Connected to existing Alpine.js toast infrastructure (show-toast events)
This commit is contained in:
@@ -5,14 +5,14 @@
|
||||
{% for item in items %}
|
||||
{% set _key = item.title | lower | trim %}
|
||||
{% if _key not in _groups.items %}
|
||||
{% set _ = _groups.items.update({_key: {
|
||||
{% set _ = _groups.items.update({
|
||||
"title": item.title,
|
||||
"cover": item.cover_image or (item.metadata.poster_image if item.metadata else "") or "",
|
||||
"synopsis": (item.metadata.synopsis if item.metadata and item.metadata.synopsis else ""),
|
||||
"rating": item.metadata.rating if item.metadata and item.metadata.rating else "",
|
||||
"genres": item.metadata.genres if item.metadata and item.metadata.genres else [],
|
||||
"providers": [{ "id": item.provider_id or pid, "url": item.url }]
|
||||
}}) %}
|
||||
}) %}
|
||||
{% else %}
|
||||
{% set _existing = _groups.items[_key] %}
|
||||
{% if not _existing.cover and item.cover_image %}
|
||||
@@ -35,6 +35,7 @@
|
||||
{% set first_url = group.providers[0].url %}
|
||||
<div class="card bg-base-200 border border-base-300 hover:border-primary transition-colors">
|
||||
<div class="card-body p-5 flex-row gap-5">
|
||||
<!-- Poster -->
|
||||
<figure class="w-28 shrink-0">
|
||||
<a href="{{ first_url }}" target="_blank" rel="noopener">
|
||||
<img src="{{ group.cover or 'https://placehold.co/240x360/29274c/7e52a0?text=No+Image' }}"
|
||||
@@ -43,7 +44,10 @@
|
||||
onerror="this.src='https://placehold.co/240x360/29274c/7e52a0?text=Error'; this.onerror=null;">
|
||||
</a>
|
||||
</figure>
|
||||
|
||||
<!-- Content -->
|
||||
<div class="flex-1 min-w-0 flex flex-col gap-2">
|
||||
<!-- Title + rating -->
|
||||
<div class="flex items-baseline gap-3">
|
||||
<h3 class="card-title text-base truncate">{{ group.title }}</h3>
|
||||
{% if group.rating %}
|
||||
@@ -63,6 +67,7 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Provider badges -->
|
||||
<div class="flex flex-wrap gap-1.5">
|
||||
{% for p in group.providers %}
|
||||
<a href="{{ p.url }}" target="_blank" rel="noopener"
|
||||
@@ -72,41 +77,72 @@
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<!-- Action buttons -->
|
||||
<div class="flex flex-wrap gap-2 mt-1">
|
||||
<!-- Watch -->
|
||||
<a href="{{ first_url }}" target="_blank" rel="noopener"
|
||||
class="btn btn-sm btn-primary">
|
||||
<i class="fas fa-play"></i> Regarder
|
||||
</a>
|
||||
<div class="dropdown" @click.outside="openDropdown = null">
|
||||
|
||||
<!-- Download dropdown -->
|
||||
<div class="dropdown dropdown-end" @click.outside="openDropdown = null">
|
||||
<div tabindex="0" role="button"
|
||||
@click.stop="openDropdown = (openDropdown === '{{ first_url | urlencode }}') ? null : '{{ first_url | urlencode }}'">
|
||||
<span class="btn btn-sm btn-secondary">
|
||||
<i class="fas fa-download"></i> Telecharger <i class="fas fa-chevron-down text-[0.6rem] ml-1"></i>
|
||||
@click.stop="openDropdown = (openDropdown === '{{ first_url | urlencode }}') ? null : '{{ first_url | urlencode }}'"
|
||||
x-ref="dlToggle-{{ loop.index0 }}">
|
||||
<span class="btn btn-sm btn-success">
|
||||
<i class="fas fa-arrow-down"></i> Télécharger <i class="fas fa-chevron-down text-[0.6rem] ml-1"></i>
|
||||
</span>
|
||||
</div>
|
||||
<ul tabindex="0"
|
||||
class="dropdown-content z-[1] menu p-2 shadow bg-base-200 rounded-box w-52 border border-base-300"
|
||||
class="dropdown-content z-[1] menu p-2 shadow-xl bg-base-300 rounded-xl w-64 border border-base-300 mt-2"
|
||||
x-show="openDropdown === '{{ first_url | urlencode }}'"
|
||||
x-transition>
|
||||
x-transition:enter="transition ease-out duration-100"
|
||||
x-transition:enter-start="opacity-0 scale-95"
|
||||
x-transition:enter-end="opacity-100 scale-100"
|
||||
x-transition:leave="transition ease-in duration-75"
|
||||
x-transition:leave-start="opacity-100 scale-100"
|
||||
x-transition:leave-end="opacity-0 scale-95">
|
||||
<!-- Full season -->
|
||||
<li>
|
||||
<button class="flex items-center gap-2 text-sm"
|
||||
<button class="flex items-center gap-3 px-3 py-2.5 rounded-lg hover:bg-base-200 transition-colors"
|
||||
hx-post="/api/anime/download-season?url={{ first_url | urlencode }}&lang={{ default_lang }}"
|
||||
hx-swap="none"
|
||||
hx-on::after-request="openDropdown = null">
|
||||
<i class="fas fa-layer-group"></i> Saison complete
|
||||
hx-on::before-request="this.querySelector('.dl-icon').classList.add('hidden'); this.querySelector('.dl-spinner').classList.remove('hidden')"
|
||||
hx-on::after-request="if(event.detail.successful){showToast('Saison complète mise en file'); this.querySelector('.dl-icon').classList.remove('hidden'); this.querySelector('.dl-spinner').classList.add('hidden'); openDropdown = null}">
|
||||
<span class="w-8 h-8 rounded-lg bg-success/20 text-success flex items-center justify-center shrink-0">
|
||||
<i class="fas fa-layer-group dl-icon text-sm"></i>
|
||||
<span class="loading loading-spinner loading-xs dl-spinner hidden"></span>
|
||||
</span>
|
||||
<div class="flex flex-col text-left">
|
||||
<span class="text-sm font-medium">Saison complète</span>
|
||||
<span class="text-xs text-base-content/50">Tous les épisodes d'un coup</span>
|
||||
</div>
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button class="flex items-center gap-2 text-sm"
|
||||
<div class="divider my-1 before:bg-base-content/10 after:bg-base-content/10"></div>
|
||||
</li>
|
||||
<!-- Choose episodes -->
|
||||
<li>
|
||||
<button class="flex items-center gap-3 px-3 py-2.5 rounded-lg hover:bg-base-200 transition-colors"
|
||||
hx-get="/api/anime/episodes?url={{ first_url | urlencode }}&lang={{ default_lang }}&html=1"
|
||||
hx-target="#player-container"
|
||||
hx-swap="innerHTML"
|
||||
hx-on::after-request="openDropdown = null; document.getElementById('player-container').scrollIntoView({behavior: 'smooth'})">
|
||||
<i class="fas fa-list-ol"></i> Choisir des episodes
|
||||
<span class="w-8 h-8 rounded-lg bg-primary/20 text-primary flex items-center justify-center shrink-0">
|
||||
<i class="fas fa-list-ol text-sm"></i>
|
||||
</span>
|
||||
<div class="flex flex-col text-left">
|
||||
<span class="text-sm font-medium">Choisir des épisodes</span>
|
||||
<span class="text-xs text-base-content/50">Sélectionnez ce que vous voulez</span>
|
||||
</div>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Follow -->
|
||||
<button class="btn btn-sm btn-accent btn-outline"
|
||||
hx-post="/api/watchlist"
|
||||
hx-vals='{"anime_url": "{{ first_url }}", "anime_title": "{{ group.title }}", "provider_id": "{{ group.providers[0].id }}", "lang": "{{ default_lang }}", "poster_image": "{{ group.cover }}"}'
|
||||
|
||||
@@ -1,19 +1,54 @@
|
||||
<div class="card bg-base-200 border border-primary/30 mt-8" x-data="{ view: 'grid' }">
|
||||
<div class="card bg-base-200 border border-primary/30 mt-8"
|
||||
x-data="{ view: 'grid', selectedEps: new Set(), selectMode: false, downloadingSeason: false }"
|
||||
id="episode-list-card">
|
||||
|
||||
<!-- Header -->
|
||||
<div class="card-body p-6">
|
||||
<div class="flex justify-between items-center">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-3">
|
||||
<div class="flex items-center gap-3 flex-wrap">
|
||||
<h2 class="text-xl font-bold border-none p-0 m-0">{{ anime_title }}</h2>
|
||||
<span class="badge badge-outline">{{ episodes|length }} épisodes disponibles</span>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<button class="btn btn-circle btn-sm btn-ghost" @click="view = 'grid'" :class="{ 'btn-primary': view === 'grid' }">
|
||||
<div class="flex gap-2 flex-wrap">
|
||||
<!-- View toggles -->
|
||||
<button class="btn btn-circle btn-sm btn-ghost" @click="view = 'grid'" :class="{ 'btn-primary': view === 'grid' }" title="Grille">
|
||||
<i class="fas fa-th"></i>
|
||||
</button>
|
||||
<button class="btn btn-circle btn-sm btn-ghost" @click="view = 'list'" :class="{ 'btn-primary': view === 'list' }">
|
||||
<button class="btn btn-circle btn-sm btn-ghost" @click="view = 'list'" :class="{ 'btn-primary': view === 'list' }" title="Liste">
|
||||
<i class="fas fa-list"></i>
|
||||
</button>
|
||||
<button class="btn btn-circle btn-sm btn-error" onclick="document.getElementById('player-container').innerHTML = ''">
|
||||
|
||||
<!-- Batch select toggle -->
|
||||
<button class="btn btn-circle btn-sm btn-ghost"
|
||||
@click="selectMode = !selectMode; if(!selectMode) selectedEps.clear()"
|
||||
:class="{ 'btn-accent': selectMode }"
|
||||
title="Sélection multiple">
|
||||
<i class="fas fa-check-double"></i>
|
||||
</button>
|
||||
|
||||
<!-- Download selected episodes -->
|
||||
<template x-if="selectMode && selectedEps.size > 0">
|
||||
<button class="btn btn-sm btn-success gap-1"
|
||||
@click="downloadSelected()"
|
||||
:disabled="downloadingSeason">
|
||||
<i class="fas fa-download" x-show="!downloadingSeason"></i>
|
||||
<span class="loading loading-spinner loading-xs" x-show="downloadingSeason"></span>
|
||||
<span x-text="selectedEps.size + ' épisode' + (selectedEps.size > 1 ? 's' : '')"></span>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<!-- Download full season -->
|
||||
<button class="btn btn-sm btn-secondary gap-1"
|
||||
x-show="!selectMode"
|
||||
:disabled="downloadingSeason"
|
||||
@click="downloadFullSeason()">
|
||||
<i class="fas fa-layer-group" x-show="!downloadingSeason"></i>
|
||||
<span class="loading loading-spinner loading-xs" x-show="downloadingSeason"></span>
|
||||
Saison complète
|
||||
</button>
|
||||
|
||||
<!-- Close player -->
|
||||
<button class="btn btn-circle btn-sm btn-error" onclick="document.getElementById('player-container').innerHTML = ''" title="Fermer">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
@@ -28,18 +63,38 @@
|
||||
<div x-show="view === 'grid'" x-transition class="mt-6">
|
||||
<div class="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-5 lg:grid-cols-7 gap-3">
|
||||
{% for ep in episodes %}
|
||||
<div class="bg-base-300 rounded-lg p-4 text-center hover:bg-base-100 transition-colors border border-transparent hover:border-primary flex flex-col gap-2">
|
||||
<div class="bg-base-300 rounded-lg p-4 text-center hover:bg-base-100 transition-all border border-transparent hover:border-primary flex flex-col gap-2 relative group"
|
||||
:class="{ 'ring-2 ring-accent border-accent': selectMode && selectedEps.has('{{ ep.url }}') }">
|
||||
<!-- Selection checkbox -->
|
||||
<div class="absolute top-2 right-2 z-10 transition-opacity"
|
||||
:class="selectMode ? 'opacity-100' : 'opacity-0 group-hover:opacity-50'">
|
||||
<label class="checkbox checkbox-sm checkbox-accent">
|
||||
<input type="checkbox"
|
||||
x-model="selectedEps"
|
||||
value="{{ ep.url }}"
|
||||
@change="$event.target.checked ? selectedEps.add('{{ ep.url }}') : selectedEps.delete('{{ ep.url }}')"
|
||||
:checked="selectedEps.has('{{ ep.url }}')"
|
||||
x-show="selectMode">
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="text-primary font-bold text-xl">EP {{ ep.episode_number or loop.index }}</div>
|
||||
<button class="btn btn-xs btn-primary w-full"
|
||||
hx-get="/api/player/embed?url={{ ep.url | urlencode }}"
|
||||
{% if ep.title %}
|
||||
<div class="text-[0.65rem] text-base-content/50 truncate" title="{{ ep.title }}">{{ ep.title }}</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Action buttons -->
|
||||
<button class="btn btn-xs btn-primary w-full"
|
||||
hx-get="/api/player/embed?url={{ ep.url | urlencode }}"
|
||||
hx-target="#video-player-display"
|
||||
hx-swap="innerHTML"
|
||||
onclick="document.getElementById('video-player-display').scrollIntoView({behavior: 'smooth'})">
|
||||
<i class="fas fa-play"></i> Regarder
|
||||
</button>
|
||||
<button class="btn btn-xs btn-ghost w-full"
|
||||
<button class="btn btn-xs btn-outline btn-success w-full gap-1"
|
||||
hx-post="/api/anime/download?url={{ ep.url | urlencode }}"
|
||||
hx-swap="none"
|
||||
hx-on::after-request="if(event.detail.successful){this.innerHTML='<i class=\'fas fa-check\'></i> Lancé';this.disabled=true;this.classList.remove('btn-outline','btn-success');this.classList.add('btn-ghost','pointer-events-none');setTimeout(()=>{this.innerHTML='<i class=\'fas fa-download\'></i> Télécharger';this.disabled=false;this.classList.remove('btn-ghost','pointer-events-none');this.classList.add('btn-outline','btn-success')},4000)}"
|
||||
title="Télécharger cet épisode">
|
||||
<i class="fas fa-download"></i> Télécharger
|
||||
</button>
|
||||
@@ -52,23 +107,37 @@
|
||||
<div x-show="view === 'list'" x-transition class="mt-6">
|
||||
<div class="flex flex-col gap-2">
|
||||
{% for ep in episodes %}
|
||||
<div class="flex items-center gap-4 bg-base-300 rounded-lg px-4 py-3 hover:bg-base-100 transition-colors">
|
||||
<div class="flex items-center gap-3 bg-base-300 rounded-lg px-4 py-3 hover:bg-base-100 transition-all group"
|
||||
:class="{ 'ring-2 ring-accent border-accent': selectMode && selectedEps.has('{{ ep.url }}') }">
|
||||
<!-- Selection checkbox -->
|
||||
<div class="shrink-0 transition-opacity"
|
||||
:class="selectMode ? 'opacity-100' : 'opacity-0'">
|
||||
<label class="checkbox checkbox-sm checkbox-accent">
|
||||
<input type="checkbox"
|
||||
x-model="selectedEps"
|
||||
value="{{ ep.url }}"
|
||||
@change="$event.target.checked ? selectedEps.add('{{ ep.url }}') : selectedEps.delete('{{ ep.url }}')"
|
||||
:checked="selectedEps.has('{{ ep.url }}')">
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<span class="font-bold text-primary w-12 shrink-0">EP {{ ep.episode_number or loop.index }}</span>
|
||||
<span class="flex-1 truncate text-base-content/80 font-medium"
|
||||
<span class="flex-1 truncate text-base-content/80 font-medium text-sm"
|
||||
title="{{ ep.title or 'Épisode ' ~ (ep.episode_number or loop.index) }}">
|
||||
{{ ep.title or 'Épisode ' ~ (ep.episode_number or loop.index) }}
|
||||
</span>
|
||||
<div class="flex gap-2 shrink-0">
|
||||
<button class="btn btn-xs btn-primary"
|
||||
hx-get="/api/player/embed?url={{ ep.url | urlencode }}"
|
||||
<button class="btn btn-xs btn-primary"
|
||||
hx-get="/api/player/embed?url={{ ep.url | urlencode }}"
|
||||
hx-target="#video-player-display"
|
||||
hx-swap="innerHTML"
|
||||
onclick="document.getElementById('video-player-display').scrollIntoView({behavior: 'smooth'})">
|
||||
<i class="fas fa-play"></i> Regarder
|
||||
<i class="fas fa-play"></i>
|
||||
</button>
|
||||
<button class="btn btn-xs btn-ghost"
|
||||
<button class="btn btn-xs btn-outline btn-success gap-1"
|
||||
hx-post="/api/anime/download?url={{ ep.url | urlencode }}"
|
||||
hx-swap="none"
|
||||
hx-on::after-request="if(event.detail.successful){this.innerHTML='<i class=\'fas fa-check\'></i>';this.disabled=true;this.classList.remove('btn-outline','btn-success');this.classList.add('btn-ghost','pointer-events-none');setTimeout(()=>{this.innerHTML='<i class=\'fas fa-download\'></i>';this.disabled=false;this.classList.remove('btn-ghost','pointer-events-none');this.classList.add('btn-outline','btn-success')},4000)}"
|
||||
title="Télécharger cet épisode">
|
||||
<i class="fas fa-download"></i>
|
||||
</button>
|
||||
@@ -85,3 +154,52 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.addEventListener('alpine:init', () => {
|
||||
Alpine.data('episodeListActions', () => ({
|
||||
downloadSelected() {
|
||||
if (this.selectedEps.size === 0) return;
|
||||
this.downloadingSeason = true;
|
||||
let completed = 0;
|
||||
const total = this.selectedEps.size;
|
||||
const urls = [...this.selectedEps];
|
||||
|
||||
Promise.allSettled(urls.map(url =>
|
||||
fetch(`/api/anime/download?url=${encodeURIComponent(url)}`, { method: 'POST' })
|
||||
.then(r => { completed++; return r; })
|
||||
)).then(() => {
|
||||
this.downloadingSeason = false;
|
||||
this.selectedEps.clear();
|
||||
this.selectMode = false;
|
||||
showToast(`${completed} téléchargement${completed > 1 ? 's' : ''} lancé${completed > 1 ? 's' : ''}`);
|
||||
htmx.trigger('#downloads-container-inner', 'refresh');
|
||||
});
|
||||
},
|
||||
downloadFullSeason() {
|
||||
this.downloadingSeason = true;
|
||||
const card = document.getElementById('episode-list-card');
|
||||
const downloadBtns = card.querySelectorAll('[hx-post*="/api/anime/download"]');
|
||||
let completed = 0;
|
||||
const total = downloadBtns.length;
|
||||
|
||||
Promise.allSettled([...downloadBtns].map(btn => {
|
||||
const url = new URLSearchParams(btn.getAttribute('hx-post').split('?')[1]).get('url');
|
||||
return fetch(`/api/anime/download?url=${encodeURIComponent(url)}`, { method: 'POST' })
|
||||
.then(r => { completed++; return r; });
|
||||
})).then(() => {
|
||||
this.downloadingSeason = false;
|
||||
showToast(`${total} épisodes mis en file de téléchargement`);
|
||||
htmx.trigger('#downloads-container-inner', 'refresh');
|
||||
});
|
||||
}
|
||||
}));
|
||||
});
|
||||
|
||||
// Toast notification helper — uses the Alpine.js toast system in toast_container.html
|
||||
// Already defined globally in settings.js, this is a fallback
|
||||
function showToast(message, type = 'success') {
|
||||
const ev = new CustomEvent('show-toast', { detail: { message, type } });
|
||||
(window.dispatchEvent || document.dispatchEvent).call(window, ev);
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -5,12 +5,12 @@
|
||||
{% for item in items %}
|
||||
{% set _key = item.title | lower | trim %}
|
||||
{% if _key not in _groups.items %}
|
||||
{% set _ = _groups.items.update({_key: {
|
||||
{% set _ = _groups.items.update({
|
||||
"title": item.title,
|
||||
"cover": item.cover_image or "",
|
||||
"synopsis": (item.metadata.synopsis if item.metadata and item.metadata.synopsis else ""),
|
||||
"providers": [{ "id": item.provider_id or pid, "url": item.url }]
|
||||
}}) %}
|
||||
}) %}
|
||||
{% else %}
|
||||
{% set _existing = _groups.items[_key] %}
|
||||
{% if not _existing.cover and item.cover_image %}
|
||||
@@ -27,6 +27,7 @@
|
||||
{% set first_url = group.providers[0].url %}
|
||||
<div class="card bg-base-200 border border-base-300 hover:border-primary transition-colors">
|
||||
<div class="card-body p-5 flex-row gap-5">
|
||||
<!-- Poster -->
|
||||
<figure class="w-28 shrink-0">
|
||||
<a href="{{ first_url }}" target="_blank" rel="noopener">
|
||||
<img src="{{ group.cover or 'https://placehold.co/240x360/29274c/7e52a0?text=No+Image' }}"
|
||||
@@ -35,7 +36,10 @@
|
||||
onerror="this.src='https://placehold.co/240x360/29274c/7e52a0?text=Error'; this.onerror=null;">
|
||||
</a>
|
||||
</figure>
|
||||
|
||||
<!-- Content -->
|
||||
<div class="flex-1 min-w-0 flex flex-col gap-2">
|
||||
<!-- Title -->
|
||||
<div class="flex items-baseline gap-3">
|
||||
<h3 class="card-title text-base truncate">{{ group.title }}</h3>
|
||||
</div>
|
||||
@@ -44,6 +48,7 @@
|
||||
<p class="text-sm text-base-content/60 line-clamp-3">{{ group.synopsis }}</p>
|
||||
{% endif %}
|
||||
|
||||
<!-- Provider badges -->
|
||||
<div class="flex flex-wrap gap-1.5">
|
||||
{% for p in group.providers %}
|
||||
<a href="{{ p.url }}" target="_blank" rel="noopener"
|
||||
@@ -53,41 +58,71 @@
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<!-- Action buttons -->
|
||||
<div class="flex flex-wrap gap-2 mt-1">
|
||||
<!-- Watch -->
|
||||
<a href="{{ first_url }}" target="_blank" rel="noopener"
|
||||
class="btn btn-sm btn-primary">
|
||||
<i class="fas fa-play"></i> Regarder
|
||||
</a>
|
||||
<div class="dropdown" @click.outside="openDropdown = null">
|
||||
|
||||
<!-- Download dropdown -->
|
||||
<div class="dropdown dropdown-end" @click.outside="openDropdown = null">
|
||||
<div tabindex="0" role="button"
|
||||
@click.stop="openDropdown = (openDropdown === '{{ first_url | urlencode }}') ? null : '{{ first_url | urlencode }}'">
|
||||
<span class="btn btn-sm btn-secondary">
|
||||
<i class="fas fa-download"></i> Telecharger <i class="fas fa-chevron-down text-[0.6rem] ml-1"></i>
|
||||
<span class="btn btn-sm btn-success">
|
||||
<i class="fas fa-arrow-down"></i> Télécharger <i class="fas fa-chevron-down text-[0.6rem] ml-1"></i>
|
||||
</span>
|
||||
</div>
|
||||
<ul tabindex="0"
|
||||
class="dropdown-content z-[1] menu p-2 shadow bg-base-200 rounded-box w-52 border border-base-300"
|
||||
class="dropdown-content z-[1] menu p-2 shadow-xl bg-base-300 rounded-xl w-64 border border-base-300 mt-2"
|
||||
x-show="openDropdown === '{{ first_url | urlencode }}'"
|
||||
x-transition>
|
||||
x-transition:enter="transition ease-out duration-100"
|
||||
x-transition:enter-start="opacity-0 scale-95"
|
||||
x-transition:enter-end="opacity-100 scale-100"
|
||||
x-transition:leave="transition ease-in duration-75"
|
||||
x-transition:leave-start="opacity-100 scale-100"
|
||||
x-transition:leave-end="opacity-0 scale-95">
|
||||
<!-- Full season -->
|
||||
<li>
|
||||
<button class="flex items-center gap-2 text-sm"
|
||||
<button class="flex items-center gap-3 px-3 py-2.5 rounded-lg hover:bg-base-200 transition-colors"
|
||||
hx-post="/api/anime/download-season?url={{ first_url | urlencode }}&lang={{ default_lang }}"
|
||||
hx-swap="none"
|
||||
hx-on::after-request="openDropdown = null">
|
||||
<i class="fas fa-layer-group"></i> Saison complete
|
||||
hx-on::before-request="this.querySelector('.dl-icon').classList.add('hidden'); this.querySelector('.dl-spinner').classList.remove('hidden')"
|
||||
hx-on::after-request="if(event.detail.successful){showToast('Saison complète mise en file'); this.querySelector('.dl-icon').classList.remove('hidden'); this.querySelector('.dl-spinner').classList.add('hidden'); openDropdown = null}">
|
||||
<span class="w-8 h-8 rounded-lg bg-success/20 text-success flex items-center justify-center shrink-0">
|
||||
<i class="fas fa-layer-group dl-icon text-sm"></i>
|
||||
<span class="loading loading-spinner loading-xs dl-spinner hidden"></span>
|
||||
</span>
|
||||
<div class="flex flex-col text-left">
|
||||
<span class="text-sm font-medium">Saison complète</span>
|
||||
<span class="text-xs text-base-content/50">Tous les épisodes d'un coup</span>
|
||||
</div>
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button class="flex items-center gap-2 text-sm"
|
||||
<div class="divider my-1 before:bg-base-content/10 after:bg-base-content/10"></div>
|
||||
</li>
|
||||
<!-- Choose episodes -->
|
||||
<li>
|
||||
<button class="flex items-center gap-3 px-3 py-2.5 rounded-lg hover:bg-base-200 transition-colors"
|
||||
hx-get="/api/anime/episodes?url={{ first_url | urlencode }}&lang={{ default_lang }}&html=1"
|
||||
hx-target="#player-container"
|
||||
hx-swap="innerHTML"
|
||||
hx-on::after-request="openDropdown = null; document.getElementById('player-container').scrollIntoView({behavior: 'smooth'})">
|
||||
<i class="fas fa-list-ol"></i> Choisir des episodes
|
||||
<span class="w-8 h-8 rounded-lg bg-primary/20 text-primary flex items-center justify-center shrink-0">
|
||||
<i class="fas fa-list-ol text-sm"></i>
|
||||
</span>
|
||||
<div class="flex flex-col text-left">
|
||||
<span class="text-sm font-medium">Choisir des épisodes</span>
|
||||
<span class="text-xs text-base-content/50">Sélectionnez ce que vous voulez</span>
|
||||
</div>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Follow -->
|
||||
<button class="btn btn-sm btn-accent btn-outline"
|
||||
hx-post="/api/watchlist"
|
||||
hx-vals='{"anime_url": "{{ first_url }}", "anime_title": "{{ group.title }}", "provider_id": "{{ group.providers[0].id }}", "lang": "{{ default_lang }}", "poster_image": "{{ group.cover }}"}'
|
||||
|
||||
Reference in New Issue
Block a user