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

add network images

Pas 9b6fbf8a eaf23995

+95 -7
public/platforms/appletv.png

This is a binary file and will not be displayed.

public/platforms/disney.png

This is a binary file and will not be displayed.

public/platforms/hulu.png

This is a binary file and will not be displayed.

public/platforms/max.png

This is a binary file and will not be displayed.

public/platforms/netflix.png

This is a binary file and will not be displayed.

public/platforms/paramount.png

This is a binary file and will not be displayed.

public/platforms/prime.png

This is a binary file and will not be displayed.

+21
src/backend/metadata/traktApi.ts
··· 35 35 count: number; 36 36 } 37 37 38 + export interface TraktNetworkResponse { 39 + type: string; 40 + platforms: string[]; 41 + count: number; 42 + } 43 + 38 44 // Pagination utility 39 45 export function paginateResults( 40 46 results: TraktLatestResponse, ··· 106 112 export const getDiscoverContent = () => 107 113 fetchFromTrakt<TraktDiscoverResponse>("/discover"); 108 114 115 + // Network content 116 + export const getNetworkContent = (tmdbId: string) => 117 + fetchFromTrakt<TraktNetworkResponse>(`/network/${tmdbId}`); 118 + 109 119 // Type conversion utilities 110 120 export function convertToMediaType(type: TraktContentType): MWMediaType { 111 121 return type === "movie" ? MWMediaType.MOVIE : MWMediaType.SERIES; ··· 130 140 "28": "action", // Action 131 141 "18": "drama", // Drama 132 142 } as const; 143 + 144 + // Map provider names to their image filenames 145 + export const PROVIDER_TO_IMAGE_MAP: Record<string, string> = { 146 + Max: "max", 147 + "Prime Video": "prime", 148 + Netflix: "netflix", 149 + "Disney+": "disney", 150 + Hulu: "hulu", 151 + "Apple TV+": "appletv", 152 + "Paramount+": "paramount", 153 + };
+34 -1
src/components/overlays/details/DetailsContent.tsx
··· 2 2 import { useEffect, useMemo, useRef, useState } from "react"; 3 3 import { useCopyToClipboard } from "react-use"; 4 4 5 + import { getNetworkContent } from "@/backend/metadata/traktApi"; 5 6 import { TMDBContentTypes } from "@/backend/metadata/types/tmdb"; 6 7 import { Icon, Icons } from "@/components/Icon"; 7 8 import { useLanguageStore } from "@/stores/language"; ··· 22 23 export function DetailsContent({ data, minimal = false }: DetailsContentProps) { 23 24 const [imdbData, setImdbData] = useState<any>(null); 24 25 const [rtData, setRtData] = useState<any>(null); 26 + const [providerData, setProviderData] = useState<string | undefined>( 27 + undefined, 28 + ); 25 29 const [, setIsLoadingImdb] = useState(false); 26 30 const [showTrailer, setShowTrailer] = useState(false); 27 31 const [selectedSeason, setSelectedSeason] = useState<number>(1); ··· 61 65 return () => resizeObserver.disconnect(); 62 66 } 63 67 }, []); 68 + 69 + useEffect(() => { 70 + const fetchNetworkData = async () => { 71 + if (!data.id) return; 72 + 73 + try { 74 + const networkData = await getNetworkContent(data.id.toString()); 75 + if ( 76 + networkData && 77 + networkData.platforms && 78 + networkData.platforms.length > 0 79 + ) { 80 + setProviderData(networkData.platforms[0]); 81 + } else { 82 + setProviderData(undefined); 83 + } 84 + } catch (error) { 85 + console.error("Failed to fetch network data:", error); 86 + setProviderData(undefined); 87 + } 88 + }; 89 + 90 + fetchNetworkData(); 91 + }, [data.id]); 64 92 65 93 useEffect(() => { 66 94 const fetchExternalData = async () => { ··· 273 301 274 302 {/* Right Column - Details Info (1/3) */} 275 303 <div className="md:col-span-1"> 276 - <DetailsInfo data={data} imdbData={imdbData} rtData={rtData} /> 304 + <DetailsInfo 305 + data={data} 306 + imdbData={imdbData} 307 + rtData={rtData} 308 + provider={providerData} 309 + /> 277 310 </div> 278 311 </div> 279 312
+7 -1
src/components/overlays/details/DetailsInfo.tsx
··· 5 5 import { DetailsRatings } from "./DetailsRatings"; 6 6 import { DetailsInfoProps } from "./types"; 7 7 8 - export function DetailsInfo({ data, imdbData, rtData }: DetailsInfoProps) { 8 + export function DetailsInfo({ 9 + data, 10 + imdbData, 11 + rtData, 12 + provider, 13 + }: DetailsInfoProps) { 9 14 const [isShiftPressed, setIsShiftPressed] = useState(false); 10 15 const [showCopied, setShowCopied] = useState(false); 11 16 ··· 122 127 imdbId={data.imdbId} 123 128 voteAverage={data.voteAverage} 124 129 voteCount={data.voteCount} 130 + provider={provider} 125 131 /> 126 132 </div> 127 133 </div>
+31 -5
src/components/overlays/details/DetailsRatings.tsx
··· 1 1 import { t } from "i18next"; 2 2 3 + import { PROVIDER_TO_IMAGE_MAP } from "@/backend/metadata/traktApi"; 3 4 import { Icon, Icons } from "@/components/Icon"; 4 5 import { getRTIcon } from "@/utils/rottenTomatoesScraper"; 5 6 ··· 10 11 mediaId, 11 12 mediaType, 12 13 imdbId, 14 + provider, 13 15 }: DetailsRatingsProps) { 16 + const getProviderImage = (providerName: string) => { 17 + const imageKey = 18 + PROVIDER_TO_IMAGE_MAP[providerName] || 19 + providerName.toLowerCase().replace(/\s+/g, ""); 20 + return `/platforms/${imageKey}.png`; 21 + }; 22 + 14 23 return ( 15 24 <div className="space-y-1"> 16 25 {/* External Links */} 17 26 <div className="flex gap-3 mt-2"> 27 + {provider && ( 28 + <div 29 + className="w-8 h-8 flex items-center justify-center transition-transform hover:scale-110 animate-[scaleIn_0.6s_ease-out_forwards]" 30 + style={{ 31 + animationDelay: "0ms", 32 + transform: "scale(0)", 33 + opacity: 0, 34 + }} 35 + title={provider} 36 + > 37 + <img 38 + src={getProviderImage(provider)} 39 + alt={provider} 40 + className="w-8 h-8 rounded-md" 41 + /> 42 + </div> 43 + )} 18 44 {mediaId && ( 19 45 <a 20 46 href={`https://www.themoviedb.org/${mediaType === "show" ? "tv" : "movie"}/${mediaId}`} 21 47 target="_blank" 22 48 rel="noopener noreferrer" 23 - className="w-8 h-8 rounded-full bg-[#0d253f] flex items-center justify-center transition-transform hover:scale-110 animate-[scaleIn_0.6s_ease-out_forwards]" 49 + className="w-8 h-8 rounded-md bg-[#0d253f] flex items-center justify-center transition-transform hover:scale-110 animate-[scaleIn_0.6s_ease-out_forwards]" 24 50 style={{ 25 - animationDelay: "0ms", 51 + animationDelay: "60ms", 26 52 transform: "scale(0)", 27 53 opacity: 0, 28 54 }} ··· 36 62 href={`https://www.imdb.com/title/${imdbId}`} 37 63 target="_blank" 38 64 rel="noopener noreferrer" 39 - className="w-8 h-8 rounded-full bg-yellow-500 flex items-center justify-center transition-transform hover:scale-110 animate-[scaleIn_0.6s_ease-out_forwards]" 65 + className="w-8 h-8 rounded-md bg-yellow-500 flex items-center justify-center transition-transform hover:scale-110 animate-[scaleIn_0.6s_ease-out_forwards]" 40 66 style={{ 41 - animationDelay: "60ms", 67 + animationDelay: "120ms", 42 68 transform: "scale(0)", 43 69 opacity: 0, 44 70 }} ··· 53 79 <div 54 80 className="flex items-center gap-1 animate-[scaleIn_0.6s_ease-out_forwards]" 55 81 style={{ 56 - animationDelay: "120ms", 82 + animationDelay: "180ms", 57 83 transform: "scale(0)", 58 84 opacity: 0, 59 85 }}
+2
src/components/overlays/details/types.ts
··· 120 120 data: DetailsContent; 121 121 imdbData?: any; 122 122 rtData?: any; 123 + provider?: string; 123 124 } 124 125 125 126 export interface DetailsRatingsProps { ··· 130 131 mediaId?: number; 131 132 mediaType?: "movie" | "show"; 132 133 imdbId?: string; 134 + provider?: string; 133 135 }