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.

Fix banner and direct link to media + improve load time

+58 -53
+7 -22
src/pages/TopFlix.tsx
··· 1 1 import classNames from "classnames"; 2 2 import { ReactNode, useEffect, useState } from "react"; 3 - import { Link } from "react-router-dom"; // Import Link from react-router-dom 3 + import { useNavigate } from "react-router-dom"; // Import Link from react-router-dom 4 4 5 5 import { ThiccContainer } from "@/components/layout/ThinContainer"; 6 6 import { Divider } from "@/components/utils/Divider"; ··· 43 43 44 44 function directLinkToContent(tmdbFullId: string) { 45 45 if (isShowOrMovie(tmdbFullId) === "series") { 46 - return `/media/tmdb-tv-${tmdbFullId.split("-")[1]}#/media/tmdb-tv-${ 47 - tmdbFullId.split("-")[1] 48 - }`; 46 + return `/media/tmdb-tv-${tmdbFullId.split("-")[1]}`; 49 47 } 50 48 if (isShowOrMovie(tmdbFullId) === "movie") { 51 - return `/media/tmdb-movie-${tmdbFullId.split("-")[1]}#/media/tmdb-movie-${ 52 - tmdbFullId.split("-")[1] 53 - }`; 49 + return `/media/tmdb-movie-${tmdbFullId.split("-")[1]}`; 54 50 } 55 51 return null; 56 52 } ··· 61 57 id: string; 62 58 children?: ReactNode; 63 59 }) { 60 + const navigate = useNavigate(); 64 61 const link = directLinkToContent(props.id); 65 62 return ( 66 63 <> 67 64 <div className="flex"> 68 65 <p className="flex-1 font-bold text-white pr-5 pl-3"> 69 66 {link ? ( 70 - <Link to={link} className="hover:underline"> 67 + <p onClick={() => navigate(link)} className="hover:underline"> 71 68 {props.name} 72 - </Link> 69 + </p> 73 70 ) : ( 74 71 <p>{props.name}</p> 75 72 )} ··· 160 157 const timeDifference = 161 158 currentTime.getTime() - new Date(processStartTime).getTime(); 162 159 163 - // Convert the time difference to a human-readable format 164 160 const hours = Math.floor(timeDifference / (1000 * 60 * 60)); 165 161 const minutes = Math.floor((timeDifference % (1000 * 60 * 60)) / (1000 * 60)); 166 162 const seconds = Math.floor((timeDifference % (1000 * 60)) / 1000); 167 - const percentageOfHour = Math.ceil(minutes / 60); 168 163 169 164 if (hours > 0) { 170 - return `${hours}.${percentageOfHour} hours`; 165 + return `${hours} hours`; 171 166 } 172 167 if (minutes > 0) { 173 168 return `${minutes} minutes`; ··· 178 173 export function TopFlix() { 179 174 const [recentPlayedItems, setRecentPlayedItems] = useState<any[]>([]); 180 175 const [totalViews, setTotalViews] = useState<string | null>(null); 181 - const [loading, setLoading] = useState(true); 182 176 const [currentPage, setCurrentPage] = useState(1); 183 177 const itemsPerPage = 10; 184 178 const maxItemsToShow = 100; // Maximum items to show ··· 203 197 }) 204 198 .catch((error) => { 205 199 console.error("Error fetching recent played items:", error); 206 - }) 207 - .finally(() => { 208 - setLoading(false); 209 200 }); 210 201 getTotalViews() 211 202 .then((views) => { ··· 235 226 ...item, 236 227 rank: startIndex + index + 1, 237 228 })); 238 - } 239 - 240 - if (loading) { 241 - return ( 242 - <p className="flex items-center justify-center h-screen">Loading...</p> 243 - ); 244 229 } 245 230 246 231 return (
-8
src/setup/Layout.tsx
··· 28 28 const location = useBannerStore((s) => s.location); 29 29 const [extensionState, setExtensionState] = 30 30 useState<ExtensionStatus>("unknown"); 31 - const [loading, setLoading] = useState(true); 32 31 const [isMobile, setIsMobile] = useState(false); 33 32 34 33 useEffect(() => { ··· 37 36 getExtensionState().then((state) => { 38 37 if (isMounted) { 39 38 setExtensionState(state); 40 - setLoading(false); 41 39 } 42 40 }); 43 41 ··· 55 53 mediaQuery.removeListener(handleResize); 56 54 }; 57 55 }, []); 58 - 59 - if (loading) { 60 - return ( 61 - <p className="flex items-center justify-center h-screen">Loading...</p> 62 - ); 63 - } 64 56 65 57 return ( 66 58 <div>
+51 -23
src/stores/banner/BannerLocation.tsx
··· 1 1 import { useEffect } from "react"; 2 - import { useTranslation } from "react-i18next"; 2 + import { Trans, useTranslation } from "react-i18next"; 3 3 import { useLocation, useNavigate } from "react-router-dom"; 4 4 5 5 import { Icon, Icons } from "@/components/Icon"; ··· 22 22 info: Icons.CIRCLE_EXCLAMATION, 23 23 }; 24 24 25 + useEffect(() => { 26 + const hideBannerFlag = sessionStorage.getItem("hideBanner"); 27 + if (hideBannerFlag) { 28 + hideBanner(props.id, true); 29 + } 30 + }, [hideBanner, props.id]); 31 + 25 32 return ( 26 33 <div ref={ref}> 27 34 <div ··· 36 43 </div> 37 44 <span 38 45 className="absolute right-4 hover:cursor-pointer" 39 - onClick={() => hideBanner(props.id, true)} 46 + onClick={() => { 47 + hideBanner(props.id, true); 48 + sessionStorage.setItem("hideBanner", "true"); 49 + }} 40 50 > 41 51 <Icon icon={Icons.X} /> 42 52 </span> ··· 57 67 const { pathname } = useLocation(); 58 68 59 69 useEffect(() => { 60 - if (!loc) return; 61 - setLocation(loc); 62 - return () => { 63 - setLocation(null); 64 - }; 70 + if (loc) { 71 + setLocation(loc); 72 + return () => { 73 + setLocation(null); 74 + }; 75 + } 65 76 }, [setLocation, loc]); 66 77 78 + const hideBannerFlag = sessionStorage.getItem("hideBanner"); 79 + if (hideBannerFlag) { 80 + return null; 81 + } 82 + 67 83 if (currentLocation !== loc || pathname === "/onboarding/extension") 68 84 return null; 69 85 70 - // Show the banner with a 45% chance 71 - if (Math.random() < 0.45) { 86 + // Show the banner with a 40% chance 87 + if (Math.random() < 0.4) { 72 88 let bannerText = ""; 73 89 switch (props.extensionState) { 74 90 case "noperms": 75 - bannerText = 76 - "You don't have the necessary permissions to use the extension."; 91 + bannerText = "The extension does'nt have the necessary permissions."; 77 92 break; 78 93 case "outdated": 79 - bannerText = 80 - "Your extension is outdated. Please update it for better performance."; 94 + bannerText = "Your extension is outdated. Please update it here."; 81 95 break; 82 96 case "disallowed": 83 - bannerText = "The extension is not enabled on this page."; 97 + bannerText = 98 + "The extension is not enabled, click <bold>here</bold> to fix it."; 99 + break; 100 + case "failed": 101 + bannerText = 102 + "The extension is broken... Please click <bold>here</bold>."; 84 103 break; 85 104 default: 86 105 bannerText = 87 - "You don't have the extension! Download it here for better quality."; 106 + "You don't have the extension! Download it <bold>here</bold> for better quality."; 107 + break; 88 108 } 89 109 90 110 return ( 91 - <div 92 - onClick={() => navigate("/onboarding/extension")} 93 - style={{ cursor: "pointer" }} 94 - > 95 - <Banner id="extension" type="info"> 96 - {bannerText} 97 - </Banner> 98 - </div> 111 + <Banner id="extension" type="info"> 112 + <div 113 + onClick={() => navigate("/onboarding/extension")} 114 + style={{ cursor: "pointer" }} 115 + > 116 + <Trans 117 + i18nKey={bannerText} 118 + components={{ 119 + bold: <span className="font-bold" />, 120 + }} 121 + /> 122 + </div> 123 + </Banner> 99 124 ); 100 125 } 101 126 return null; ··· 118 143 }, [setLocation, loc]); 119 144 120 145 if (currentLocation !== loc) return null; 146 + 147 + const hideBannerFlag = sessionStorage.getItem("hideBanner"); 148 + if (hideBannerFlag) return null; 121 149 122 150 return ( 123 151 <div>