feat(home): tap opens detail, long-press plays from home cards
Home carousel cards route a tap to the item's detail / Episode Focus View and a ~500ms long-press to a confirm-then-play flow. MediaCard gains an onLongPress prop with pointer-based detection (cancelled on >10px move so carousel scroll is unaffected, trailing click suppressed). Episode taps route to /library/<seriesId>?episode=<id>; the bare-episode detail page links back to its parent series/season. TRACES: UR-058 | DR-087
This commit is contained in:
+42
-8
@@ -56,18 +56,47 @@
|
||||
previousServerReachable = serverReachable;
|
||||
});
|
||||
|
||||
// Tap → detail page. Non-playable containers already routed to /library; now
|
||||
// movies and episodes go to their detail page too instead of playing straight
|
||||
// away. Channel leaves (no detail page) still go direct to the player.
|
||||
// TRACES: UR-058 | DR-087
|
||||
function handleItemClick(item: MediaItem) {
|
||||
switch (item.kind) {
|
||||
case "series":
|
||||
case "season":
|
||||
case "album":
|
||||
case "artist":
|
||||
case "folder":
|
||||
case "channel":
|
||||
goto(`/library/${item.id}`);
|
||||
case "channelItem":
|
||||
case "liveChannel":
|
||||
goto(`/player/${item.id}`);
|
||||
break;
|
||||
case "episode":
|
||||
// An episode is never browsed as a bare Episode page — it opens in its
|
||||
// series' Episode Focus View so the series context loads (ux-flows §5B.1).
|
||||
if (item.seriesId) {
|
||||
goto(`/library/${item.seriesId}?episode=${item.id}`);
|
||||
} else {
|
||||
goto(`/library/${item.id}`);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
goto(`/player/${item.id}`);
|
||||
goto(`/library/${item.id}`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Long press → play immediately, confirming first so an accidental hold on a
|
||||
// half-watched item doesn't blow away the user's spot without warning.
|
||||
// TRACES: UR-058 | DR-087
|
||||
function handleItemLongPress(item: MediaItem) {
|
||||
switch (item.kind) {
|
||||
case "movie":
|
||||
case "episode":
|
||||
case "channelItem":
|
||||
case "liveChannel":
|
||||
if (confirm(`Play "${item.name}" now?`)) {
|
||||
goto(`/player/${item.id}`);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// Containers (series/season/album/…) have no single "play now" target.
|
||||
goto(`/library/${item.id}`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -145,6 +174,7 @@
|
||||
title="Next Movie"
|
||||
items={resumeMovies}
|
||||
onItemClick={handleItemClick}
|
||||
onItemLongPress={handleItemLongPress}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
@@ -154,6 +184,7 @@
|
||||
title="Next Episode"
|
||||
items={nextUpItems}
|
||||
onItemClick={handleItemClick}
|
||||
onItemLongPress={handleItemLongPress}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
@@ -163,6 +194,7 @@
|
||||
title="Recently Listened"
|
||||
items={recentlyPlayedAudio}
|
||||
onItemClick={handleItemClick}
|
||||
onItemLongPress={handleItemLongPress}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
@@ -172,6 +204,7 @@
|
||||
title="Continue Watching"
|
||||
items={resumeItems}
|
||||
onItemClick={handleItemClick}
|
||||
onItemLongPress={handleItemLongPress}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
@@ -181,6 +214,7 @@
|
||||
title="Recently Added"
|
||||
items={latestItems}
|
||||
onItemClick={handleItemClick}
|
||||
onItemLongPress={handleItemLongPress}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
|
||||
@@ -381,12 +381,29 @@
|
||||
<div class="flex-1 space-y-4">
|
||||
<div>
|
||||
<h1 class="text-3xl font-bold text-white">{item.name}</h1>
|
||||
{#if item.kind === "episode" && (item.parentIndexNumber || item.indexNumber)}
|
||||
<p class="text-lg text-gray-400 mt-1">
|
||||
{#if item.parentIndexNumber}Season {item.parentIndexNumber}{/if}
|
||||
{#if item.parentIndexNumber && item.indexNumber}, {/if}
|
||||
{#if item.indexNumber}Episode {item.indexNumber}{/if}
|
||||
</p>
|
||||
{#if item.kind === "episode"}
|
||||
<!-- Links back to the parent series/season so the episode detail
|
||||
page is a navigable hub, not a dead end. TRACES: UR-058 | DR-087 -->
|
||||
{#if item.seriesId && item.seriesName}
|
||||
<p class="text-lg mt-1">
|
||||
<a
|
||||
href={`/library/${item.seriesId}`}
|
||||
class="text-[var(--color-jellyfin)] hover:underline"
|
||||
>{item.seriesName}</a>
|
||||
</p>
|
||||
{/if}
|
||||
{#if item.parentIndexNumber || item.indexNumber}
|
||||
<p class="text-lg text-gray-400 mt-1">
|
||||
{#if item.seasonId && item.parentIndexNumber}
|
||||
<a
|
||||
href={`/library/${item.seasonId}`}
|
||||
class="hover:underline hover:text-[var(--color-jellyfin)] transition-colors"
|
||||
>Season {item.parentIndexNumber}</a>
|
||||
{:else if item.parentIndexNumber}Season {item.parentIndexNumber}{/if}
|
||||
{#if item.parentIndexNumber && item.indexNumber}, {/if}
|
||||
{#if item.indexNumber}Episode {item.indexNumber}{/if}
|
||||
</p>
|
||||
{/if}
|
||||
{:else if item.artistItems?.length || item.artists?.length}
|
||||
<p class="text-lg text-gray-400 mt-1">
|
||||
<ArtistLinks
|
||||
|
||||
Reference in New Issue
Block a user