171 lines
4.7 KiB
Svelte
171 lines
4.7 KiB
Svelte
<script lang="ts">
|
|
import {
|
|
nextEpisode,
|
|
isNextEpisodePopupVisible,
|
|
nextEpisodeItem,
|
|
countdownSeconds,
|
|
initialCountdownSeconds,
|
|
isCountdownActive,
|
|
} from "$lib/stores/nextEpisode";
|
|
import {
|
|
cancelAutoPlay,
|
|
watchNextManually,
|
|
} from "$lib/services/nextEpisodeService";
|
|
import { auth } from "$lib/stores/auth";
|
|
import CachedImage from "../common/CachedImage.svelte";
|
|
|
|
// Use series primary image for better visual consistency
|
|
const imageId = $derived($nextEpisodeItem ? ($nextEpisodeItem.seriesId || $nextEpisodeItem.id) : null);
|
|
|
|
// Format episode info (S1:E5)
|
|
const episodeInfo = $derived.by(() => {
|
|
const episode = $nextEpisodeItem;
|
|
if (!episode) return "";
|
|
|
|
const season = episode.parentIndexNumber;
|
|
const epNum = episode.indexNumber;
|
|
|
|
if (season !== undefined && epNum !== undefined) {
|
|
return `S${season}:E${epNum}`;
|
|
}
|
|
return "";
|
|
});
|
|
|
|
// Calculate progress for the countdown bar (1 to 0)
|
|
const countdownProgress = $derived.by(() => {
|
|
const initial = $initialCountdownSeconds;
|
|
const current = $countdownSeconds;
|
|
if (initial <= 0) return 0;
|
|
return current / initial;
|
|
});
|
|
|
|
function handlePlayNow() {
|
|
if ($nextEpisodeItem) {
|
|
watchNextManually($nextEpisodeItem);
|
|
}
|
|
}
|
|
|
|
function handleCancel() {
|
|
cancelAutoPlay();
|
|
}
|
|
|
|
// Note: Countdown pause/resume on hover is not implemented
|
|
// Backend controls countdown timing via CountdownTick events
|
|
function handleMouseEnter() {
|
|
// TODO: Could add visual feedback on hover
|
|
}
|
|
|
|
function handleMouseLeave() {
|
|
// TODO: Could remove visual feedback
|
|
}
|
|
</script>
|
|
|
|
{#if $isNextEpisodePopupVisible && $nextEpisodeItem}
|
|
<div
|
|
class="fixed bottom-24 right-6 z-50 max-w-sm animate-slide-up"
|
|
onmouseenter={handleMouseEnter}
|
|
onmouseleave={handleMouseLeave}
|
|
role="dialog"
|
|
aria-label="Next episode"
|
|
tabindex="-1"
|
|
>
|
|
<div
|
|
class="bg-[var(--color-surface)] rounded-xl shadow-2xl overflow-hidden border border-gray-700"
|
|
>
|
|
<!-- Episode Card -->
|
|
<div class="flex gap-4 p-4">
|
|
<!-- Thumbnail -->
|
|
<div
|
|
class="relative flex-shrink-0 w-28 h-16 rounded-lg overflow-hidden bg-gray-800"
|
|
>
|
|
{#if imageId && $nextEpisodeItem.primaryImageTag}
|
|
<CachedImage
|
|
itemId={imageId}
|
|
imageType="Primary"
|
|
tag={$nextEpisodeItem.primaryImageTag}
|
|
maxHeight={200}
|
|
alt={$nextEpisodeItem.name}
|
|
class="w-full h-full object-cover"
|
|
/>
|
|
{/if}
|
|
|
|
<!-- Play icon overlay -->
|
|
<div
|
|
class="absolute inset-0 flex items-center justify-center bg-black/30"
|
|
>
|
|
<svg
|
|
class="w-8 h-8 text-white"
|
|
fill="currentColor"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<path d="M8 5v14l11-7z" />
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Episode Info -->
|
|
<div class="flex-1 min-w-0">
|
|
<p class="text-xs text-gray-400 mb-1">Up Next</p>
|
|
<h3 class="text-sm font-semibold text-white truncate">
|
|
{$nextEpisodeItem.name}
|
|
</h3>
|
|
<p class="text-xs text-gray-400">
|
|
{$nextEpisodeItem.seriesName}
|
|
{episodeInfo}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Actions -->
|
|
<div class="px-4 pb-4 flex gap-3">
|
|
<!-- Cancel button -->
|
|
<button
|
|
onclick={handleCancel}
|
|
class="flex-1 px-4 py-2 bg-gray-700 hover:bg-gray-600 rounded-lg text-sm font-medium text-white transition-colors"
|
|
>
|
|
Cancel
|
|
</button>
|
|
|
|
<!-- Play now button with countdown -->
|
|
<button
|
|
onclick={handlePlayNow}
|
|
class="flex-1 px-4 py-2 bg-[var(--color-jellyfin)] hover:bg-[var(--color-jellyfin-dark)] rounded-lg text-sm font-medium text-white transition-colors relative overflow-hidden"
|
|
>
|
|
{#if $isCountdownActive}
|
|
<!-- Countdown progress bar -->
|
|
<div
|
|
class="absolute inset-0 bg-white/20 origin-left transition-transform duration-1000 ease-linear"
|
|
style="transform: scaleX({countdownProgress})"
|
|
></div>
|
|
{/if}
|
|
|
|
<span class="relative">
|
|
{#if $isCountdownActive}
|
|
Play in {$countdownSeconds}s
|
|
{:else}
|
|
Play Now
|
|
{/if}
|
|
</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{/if}
|
|
|
|
<style>
|
|
@keyframes slide-up {
|
|
from {
|
|
opacity: 0;
|
|
transform: translateY(20px);
|
|
}
|
|
to {
|
|
opacity: 1;
|
|
transform: translateY(0);
|
|
}
|
|
}
|
|
|
|
.animate-slide-up {
|
|
animation: slide-up 0.3s ease-out forwards;
|
|
}
|
|
</style>
|