pstream is dead; long live pstream taciturnaxolotl.github.io/pstream-ng/
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

show total watched to details modal

Pas 310a7839 e544334b

+40 -3
+1
src/assets/locales/en.json
··· 489 489 "seasons": "Season/s", 490 490 "season": "Season", 491 491 "episode": "Episode", 492 + "watched": "Watched {{watched}} of {{total}} ({{percentage}}%)", 492 493 "airs": "Airs", 493 494 "endsAt": "Ends at {{time}}", 494 495 "trailer": "Trailer",
+37 -3
src/components/overlays/detailsModal/components/carousels/EpisodeCarousel.tsx
··· 23 23 mediaId, 24 24 mediaTitle, 25 25 mediaPosterUrl, 26 + totalEpisodes, 26 27 }: EpisodeCarouselProps) { 27 28 const [showEpisodeMenu, setShowEpisodeMenu] = useState(false); 28 29 const [customSeason, setCustomSeason] = useState(""); ··· 250 251 [mediaId, getFavoriteEpisodes], 251 252 ); 252 253 254 + // Calculate watched episodes count and percentage 255 + const watchedStats = useMemo(() => { 256 + if (!mediaId || !totalEpisodes) return { watched: 0, percentage: 0 }; 257 + 258 + let watchedCount = 0; 259 + episodes.forEach((episode) => { 260 + const episodeProgress = 261 + progress[mediaId.toString()]?.episodes?.[episode.id]; 262 + const percentage = episodeProgress 263 + ? getProgressPercentage( 264 + episodeProgress.progress.watched, 265 + episodeProgress.progress.duration, 266 + ) 267 + : 0; 268 + if (percentage > 90) { 269 + watchedCount += 1; 270 + } 271 + }); 272 + 273 + const percentage = Math.round((watchedCount / totalEpisodes) * 100); 274 + 275 + return { watched: watchedCount, percentage }; 276 + }, [episodes, progress, mediaId, totalEpisodes]); 277 + 253 278 // Load favorite episodes when favorites is selected 254 279 useEffect(() => { 255 280 if (showFavorites && mediaId && favoriteEpisodeIds.length > 0) { ··· 423 448 {showEpisodeMenu && ( 424 449 <div 425 450 ref={episodeMenuRef} 426 - className="absolute top-full left-0 mt-2 p-4 bg-background-main rounded-lg shadow-lg border border-white/10 z-50 min-w-[250px]" 451 + className="absolute top-full left-0 mt-2 p-4 bg-background-main rounded-xl shadow-lg z-50 min-w-[250px]" 427 452 > 428 453 <div className="space-y-4"> 429 454 <div> ··· 436 461 onChange={(e) => setCustomSeason(e.target.value)} 437 462 min="1" 438 463 max={seasons.length} 439 - className="w-full px-3 py-2 bg-white/5 rounded border border-white/10 text-white focus:outline-none focus:border-white/30" 464 + className="w-full px-3 py-2 bg-white/5 rounded-xl text-white focus:outline-none focus:border-white/30" 440 465 placeholder={t("details.season")} 441 466 /> 442 467 </div> ··· 449 474 value={customEpisode} 450 475 onChange={(e) => setCustomEpisode(e.target.value)} 451 476 min="1" 452 - className="w-full px-3 py-2 bg-white/5 rounded border border-white/10 text-white focus:outline-none focus:border-white/30" 477 + className="w-full px-3 py-2 bg-white/5 rounded-xl text-white focus:outline-none focus:border-white/30" 453 478 placeholder={t("details.episode")} 454 479 /> 455 480 </div> ··· 464 489 </div> 465 490 )} 466 491 </div> 492 + {totalEpisodes && ( 493 + <span className="text-xs md:text-sm text-white/70"> 494 + {t("details.watched", { 495 + watched: watchedStats.watched, 496 + total: totalEpisodes, 497 + percentage: watchedStats.percentage, 498 + })} 499 + </span> 500 + )} 467 501 </div> 468 502 469 503 {/* Season Watched Confirmation */}
+1
src/components/overlays/detailsModal/components/layout/DetailsContent.tsx
··· 411 411 mediaId={data.id} 412 412 mediaTitle={data.title} 413 413 mediaPosterUrl={data.posterUrl} 414 + totalEpisodes={data.episodes} 414 415 /> 415 416 )} 416 417
+1
src/components/overlays/detailsModal/types.ts
··· 105 105 mediaId?: number; 106 106 mediaTitle?: string; 107 107 mediaPosterUrl?: string; 108 + totalEpisodes?: number; 108 109 } 109 110 110 111 export interface DetailsBodyProps {