import { leaderboards } from "@/common/leaderboards"; import ScoreSaberPlayerToken from "@/common/model/token/scoresaber/score-saber-player-token"; import { ScoreSaberPlayersPageToken } from "@/common/model/token/scoresaber/score-saber-players-page-token"; import { formatPp } from "@/common/number-utils"; import { GlobeAmericasIcon } from "@heroicons/react/24/solid"; import { useQuery } from "@tanstack/react-query"; import Link from "next/link"; import { ReactElement } from "react"; import Card from "../card"; import CountryFlag from "../country-flag"; import { Avatar, AvatarImage } from "../ui/avatar"; const REFRESH_INTERVAL = 5 * 60 * 1000; // 5 minutes type MiniProps = { type: "Global" | "Country"; player: ScoreSaberPlayerToken; }; type Variants = { [key: string]: { itemsPerPage: number; icon: (player: ScoreSaberPlayerToken) => ReactElement; getPage: (player: ScoreSaberPlayerToken, itemsPerPage: number) => number; query: (page: number, country: string) => Promise; }; }; const miniVariants: Variants = { Global: { itemsPerPage: 50, icon: () => , getPage: (player: ScoreSaberPlayerToken, itemsPerPage: number) => { return Math.floor((player.rank - 1) / itemsPerPage) + 1; }, query: (page: number) => { return leaderboards.ScoreSaber.queries.lookupGlobalPlayers(page); }, }, Country: { itemsPerPage: 50, icon: (player: ScoreSaberPlayerToken) => { return ; }, getPage: (player: ScoreSaberPlayerToken, itemsPerPage: number) => { return Math.floor((player.countryRank - 1) / itemsPerPage) + 1; }, query: (page: number, country: string) => { return leaderboards.ScoreSaber.queries.lookupGlobalPlayersByCountry(page, country); }, }, }; export default function Mini({ type, player }: MiniProps) { const variant = miniVariants[type]; const icon = variant.icon(player); const itemsPerPage = variant.itemsPerPage; const page = variant.getPage(player, itemsPerPage); const rankWithinPage = player.rank % itemsPerPage; const { data, isLoading, isError } = useQuery({ queryKey: ["player-" + type, player.id, type, page], queryFn: async () => { // Determine pages to search based on player's rank within the page const pagesToSearch = [page]; if (rankWithinPage < 5 && page > 0) { // Player is near the start of the page, so search the previous page too pagesToSearch.push(page - 1); } if (rankWithinPage > itemsPerPage - 5) { // Player is near the end of the page, so search the next page too pagesToSearch.push(page + 1); } // Fetch players from the determined pages const players: ScoreSaberPlayerToken[] = []; for (const p of pagesToSearch) { const response = await variant.query(p, player.country); if (response === undefined) { return undefined; } players.push(...response.players); } return players; }, refetchInterval: REFRESH_INTERVAL, }); let players = data; // So we can update it later if (players && (!isLoading || !isError)) { // Find the player's position and show 3 players above and 1 below const playerPosition = players.findIndex((p) => p.id === player.id); players = players.slice(playerPosition - 3, playerPosition + 2); } return (
{icon}

{type} Ranking

{isLoading &&

Loading...

} {isError &&

Error

} {players?.map((player, index) => { const rank = type == "Global" ? player.rank : player.countryRank; return (

#{rank}

{player.name}

{formatPp(player.pp)}pp

); })}
); }