243 lines
14 KiB
Plaintext
243 lines
14 KiB
Plaintext
{{define "title"}}Watch {{.Anime.Title}} - MyAnimeList{{end}}
|
|
{{define "content"}}
|
|
{{if .WatchlistIDs}}<script>initWatchlist({{.WatchlistIDs}})</script>{{end}}
|
|
{{$anime := .Anime}}
|
|
{{$episodes := .Episodes}}
|
|
{{$currentEpID := .CurrentEpID}}
|
|
{{if or (not $currentEpID) (eq (printf "%v" $currentEpID) "0") (eq (printf "%v" $currentEpID) "")}}{{$currentEpID = "1"}}{{end}}
|
|
{{$totalEpisodes := len $episodes}}
|
|
|
|
<div id="watch-layout" class="flex flex-col gap-6 pb-12 lg:flex-row lg:gap-6">
|
|
<div class="flex-1 min-w-0">
|
|
<div class="flex items-center justify-between mb-4">
|
|
<a href="/anime/{{$anime.MalID}}" class="inline-flex items-center gap-2 text-sm text-neutral-400 hover:text-white transition-colors">
|
|
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18" /></svg>
|
|
Back
|
|
</a>
|
|
<ui-dropdown class="relative block" data-align="right" data-width="min-w-[160px]">
|
|
<div data-trigger class="cursor-pointer">
|
|
<button type="button" class="bg-accent/20 hover:bg-accent/30 flex items-center justify-between gap-2 px-4 py-2 text-sm text-accent font-medium transition-colors">
|
|
<span id="watchlist-status-display-{{$anime.MalID}}">
|
|
{{if .WatchlistStatus}}{{if eq .WatchlistStatus "watching"}}Watching{{else if eq .WatchlistStatus "completed"}}Completed{{else if eq .WatchlistStatus "plan_to_watch"}}Plan to Watch{{else if eq .WatchlistStatus "dropped"}}Dropped{{end}}{{else}}Add to Watchlist{{end}}
|
|
</span>
|
|
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m6 9 6 6 6-6" /></svg>
|
|
</button>
|
|
</div>
|
|
<div data-content class="hidden absolute z-50 min-w-40 bg-background-button shadow-2xl right-0 top-full mt-2">
|
|
{{if .WatchlistStatus}}
|
|
<div class="flex flex-col py-1">
|
|
<button class="flex w-full items-center px-5 py-2.5 transition-colors focus:outline-none hover:bg-white/10" onclick="updateWatchlist({{$anime.MalID}}, 'watching', 'Watching', this)">
|
|
<span class="text-sm text-white">Watching</span>
|
|
</button>
|
|
<button class="flex w-full items-center px-5 py-2.5 transition-colors focus:outline-none hover:bg-white/10" onclick="updateWatchlist({{$anime.MalID}}, 'completed', 'Completed', this)">
|
|
<span class="text-sm text-white">Completed</span>
|
|
</button>
|
|
<button class="flex w-full items-center px-5 py-2.5 transition-colors focus:outline-none hover:bg-white/10" onclick="updateWatchlist({{$anime.MalID}}, 'plan_to_watch', 'Plan to Watch', this)">
|
|
<span class="text-sm text-white">Plan to Watch</span>
|
|
</button>
|
|
<button class="flex w-full items-center px-5 py-2.5 transition-colors focus:outline-none hover:bg-white/10" onclick="updateWatchlist({{$anime.MalID}}, 'dropped', 'Dropped', this)">
|
|
<span class="text-sm text-white">Dropped</span>
|
|
</button>
|
|
<div class="border-t border-white/10 my-1"></div>
|
|
<button class="flex w-full items-center px-5 py-2.5 transition-colors focus:outline-none hover:bg-red-500/10" onclick="removeWatchlist({{$anime.MalID}}, this)">
|
|
<span class="text-sm text-red-400">Remove from Watchlist</span>
|
|
</button>
|
|
</div>
|
|
{{else}}
|
|
<div class="flex flex-col py-1">
|
|
<button class="flex w-full items-center px-5 py-2.5 transition-colors focus:outline-none hover:bg-white/10" onclick="updateWatchlist({{$anime.MalID}}, 'watching', 'Watching', this)">
|
|
<span class="text-sm text-white">Watching</span>
|
|
</button>
|
|
<button class="flex w-full items-center px-5 py-2.5 transition-colors focus:outline-none hover:bg-white/10" onclick="updateWatchlist({{$anime.MalID}}, 'completed', 'Completed', this)">
|
|
<span class="text-sm text-white">Completed</span>
|
|
</button>
|
|
<button class="flex w-full items-center px-5 py-2.5 transition-colors focus:outline-none hover:bg-white/10" onclick="updateWatchlist({{$anime.MalID}}, 'plan_to_watch', 'Plan to Watch', this)">
|
|
<span class="text-sm text-white">Plan to Watch</span>
|
|
</button>
|
|
<button class="flex w-full items-center px-5 py-2.5 transition-colors focus:outline-none hover:bg-white/10" onclick="updateWatchlist({{$anime.MalID}}, 'dropped', 'Dropped', this)">
|
|
<span class="text-sm text-white">Dropped</span>
|
|
</button>
|
|
</div>
|
|
{{end}}
|
|
</div>
|
|
</ui-dropdown>
|
|
</div>
|
|
<div id="video-player-container">
|
|
{{template "video_player" dict "WatchData" .WatchData "TotalEpisodes" $anime.Episodes}}
|
|
</div>
|
|
|
|
<div class="flex items-center mt-4">
|
|
<div class="flex gap-2">
|
|
{{$prevEp := sub (int $currentEpID) 1}}
|
|
{{if ge $prevEp 1}}
|
|
<a href="/anime/{{$anime.MalID}}/watch?ep={{$prevEp}}" class="flex items-center gap-2 px-4 py-2 bg-white/5 hover:bg-white/10 text-sm text-neutral-300 transition-colors">
|
|
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" /></svg>
|
|
Prev
|
|
</a>
|
|
{{end}}
|
|
{{$nextEp := add (int $currentEpID) 1}}
|
|
{{if le $nextEp $anime.Episodes}}
|
|
<a href="/anime/{{$anime.MalID}}/watch?ep={{$nextEp}}" class="flex items-center gap-2 px-4 py-2 bg-white/5 hover:bg-white/10 text-sm text-neutral-300 transition-colors">
|
|
Next
|
|
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" /></svg>
|
|
</a>
|
|
{{end}}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="w-full lg:w-80 xl:w-96 shrink-0">
|
|
{{if .Seasons}}
|
|
{{$currentSeason := dict "Prefix" "" "Title" $anime.Title}}
|
|
{{range .Seasons}}
|
|
{{if .IsCurrent}}{{$currentSeason = .}}{{end}}
|
|
{{end}}
|
|
<ui-dropdown class="relative block mb-4" data-align="left" data-width="w-full">
|
|
<div data-trigger>
|
|
<button class="w-full flex items-center justify-between px-3 py-2 bg-white/5 border border-white/10 rounded text-sm text-neutral-300 hover:bg-white/10 transition-colors">
|
|
<span class="truncate">
|
|
{{if $currentSeason.Prefix}}<span class="font-bold text-white mr-1">{{$currentSeason.Prefix}}:</span>{{end}}
|
|
{{$currentSeason.Title}}
|
|
</span>
|
|
<svg class="w-4 h-4 text-neutral-500 shrink-0 ml-2" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" /></svg>
|
|
</button>
|
|
</div>
|
|
<div data-content class="hidden absolute z-50 top-full mt-1 left-0 w-full bg-background-button rounded shadow-2xl border border-white/10 max-h-64 overflow-y-auto scrollbar-hide">
|
|
<div class="flex flex-col py-1">
|
|
{{range .Seasons}}
|
|
<a href="/anime/{{.MalID}}/watch" class="px-4 py-2 text-left text-sm {{if .IsCurrent}}text-accent bg-accent/10{{else}}text-neutral-300 hover:bg-white/10{{end}} transition-colors">
|
|
<span class="font-bold text-white mr-1">{{.Prefix}}:</span>
|
|
{{.Title}}
|
|
</a>
|
|
{{end}}
|
|
</div>
|
|
</div>
|
|
</ui-dropdown>
|
|
{{end}}
|
|
|
|
{{if eq $totalEpisodes 0}}
|
|
<div class="flex flex-col items-center justify-center gap-2 py-12 text-neutral-400">
|
|
<svg class="h-10 w-10 opacity-30" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path stroke-linecap="round" stroke-linejoin="round" d="M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z" /></svg>
|
|
<p class="text-sm">No episodes found</p>
|
|
</div>
|
|
{{else}}
|
|
<div class="flex flex-col gap-2">
|
|
<div class="flex items-center justify-between">
|
|
<span class="text-sm font-medium text-neutral-400">Episodes</span>
|
|
{{if gt $totalEpisodes 100}}
|
|
<input type="text" placeholder="Find" data-episode-search class="w-24 bg-white/5 text-sm px-3 py-1.5 rounded border border-white/10 text-neutral-200 placeholder-neutral-500 focus:outline-none focus:border-accent" />
|
|
{{end}}
|
|
</div>
|
|
|
|
{{if gt $totalEpisodes 100}}
|
|
<ui-dropdown class="relative block" data-align="left" data-width="min-w-[200px]" data-episode-dropdown>
|
|
<div data-trigger>
|
|
<button class="w-full flex items-center justify-between px-3 py-2 bg-white/5 border border-white/10 rounded text-sm text-neutral-300 hover:bg-white/10">
|
|
<span data-dropdown-label>01-100</span>
|
|
<svg class="w-4 h-4 text-neutral-500" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" /></svg>
|
|
</button>
|
|
</div>
|
|
<div data-content class="hidden absolute z-50 top-full mt-1 left-0 min-w-50 bg-background-button rounded shadow-2xl">
|
|
<div class="flex flex-col py-1">
|
|
{{$ranges := ceilDiv $totalEpisodes 100}}
|
|
{{range $i := seq $ranges}}
|
|
{{$start := imul $i 100}}
|
|
{{$end := min (add $start 100) $totalEpisodes}}
|
|
<button class="episode-range-btn px-4 py-2 text-left text-sm text-neutral-300 hover:bg-white/10" data-range-index="{{$i}}" data-range-start="{{add $start 1}}" data-range-end="{{$end}}">
|
|
{{printf "%02d" (add $start 1)}}-{{printf "%02d" $end}}
|
|
</button>
|
|
{{end}}
|
|
</div>
|
|
</div>
|
|
</ui-dropdown>
|
|
{{end}}
|
|
</div>
|
|
|
|
{{if gt $totalEpisodes 100}}
|
|
<div class="grid grid-cols-5 gap-1 mt-2" data-episode-grid>
|
|
{{range $episodes}}
|
|
{{$isCurrent := eq (printf "%v" .MalID) $currentEpID}}
|
|
{{$isFiller := .Filler}}
|
|
{{$isRecap := .Recap}}
|
|
<a href="/anime/{{$anime.MalID}}/watch?ep={{.MalID}}" class="flex items-center justify-center py-2 text-xs transition-colors {{if $isFiller}}bg-yellow-500/20 text-yellow-400{{else if $isRecap}}bg-blue-500/20 text-blue-400{{else}}text-neutral-400 hover:bg-white/5{{end}} {{if $isCurrent}}bg-accent/20 text-accent ring-1 ring-accent{{end}}" data-episode-id="{{.MalID}}" data-episode-index="{{.MalID}}" data-episode-title="{{.Title}}">
|
|
{{.MalID}}
|
|
</a>
|
|
{{end}}
|
|
</div>
|
|
{{else}}
|
|
<div class="flex flex-col gap-1 overflow-y-auto max-h-[70vh] lg:max-h-[calc(100vh-8rem)] pr-2 scrollbar-hide mt-2" data-episode-list>
|
|
{{range $episodes}}
|
|
{{$isCurrent := eq (printf "%v" .MalID) $currentEpID}}
|
|
{{$isFiller := .Filler}}
|
|
{{$isRecap := .Recap}}
|
|
<a href="/anime/{{$anime.MalID}}/watch?ep={{.MalID}}" class="flex items-center gap-3 px-3 py-2 transition-colors hover:bg-white/5 text-left {{if $isFiller}}border-l-2 border-l-yellow-500{{else if $isRecap}}border-l-2 border-l-blue-500{{end}} {{if $isCurrent}}bg-accent/20{{end}}" data-episode-id="{{.MalID}}" data-episode-title="{{.Title}}">
|
|
<span class="w-10 shrink-0 text-xs font-medium text-neutral-500 tabular-nums">EP{{.MalID}}</span>
|
|
<span class="truncate text-sm {{if $isFiller}}text-yellow-400{{else if $isRecap}}text-blue-400{{else}}text-neutral-300{{end}}" data-episode-title>{{.Title}}</span>
|
|
</a>
|
|
{{end}}
|
|
</div>
|
|
{{end}}
|
|
{{end}}
|
|
|
|
<script>
|
|
function updateWatchlist(id, status, display, btn) {
|
|
fetch('/api/watchlist', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ animeId: id, status: status })
|
|
}).then(res => {
|
|
if (res.ok) {
|
|
watchlistIds.add(id);
|
|
document.getElementById('watchlist-status-display-' + id).textContent = display;
|
|
|
|
// Update all watchlist icons on the page
|
|
document.querySelectorAll('.watchlist-icon').forEach(function(icon) {
|
|
const button = icon.closest('button')
|
|
if (button) {
|
|
const malId = button.dataset.malId
|
|
if (malId && parseInt(malId) === id) {
|
|
button.classList.add('in-watchlist')
|
|
}
|
|
}
|
|
});
|
|
|
|
// Close dropdown after a small delay to let click event finish
|
|
requestAnimationFrame(() => {
|
|
const dropdown = btn.closest('ui-dropdown');
|
|
if (dropdown) dropdown.close();
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
function removeWatchlist(id, btn) {
|
|
fetch('/api/watchlist/' + id, { method: 'DELETE' }).then(res => {
|
|
if (res.ok) {
|
|
watchlistIds.delete(id);
|
|
document.getElementById('watchlist-status-display-' + id).textContent = 'Add to Watchlist';
|
|
if (window.showToast) showToast({ message: 'Removed from watchlist' });
|
|
|
|
// Update all watchlist icons on the page
|
|
document.querySelectorAll('.watchlist-icon').forEach(function(icon) {
|
|
const button = icon.closest('button')
|
|
if (button) {
|
|
const malId = button.dataset.malId
|
|
if (malId && parseInt(malId) === id) {
|
|
button.classList.remove('in-watchlist')
|
|
}
|
|
}
|
|
});
|
|
|
|
// Close dropdown
|
|
if (btn) {
|
|
const dropdown = btn.closest('ui-dropdown');
|
|
if (dropdown) dropdown.close();
|
|
}
|
|
}
|
|
});
|
|
}
|
|
</script>
|
|
</div>
|
|
</div>
|
|
{{end}}
|