import ScoreSaberPlayerToken from "@/common/model/token/scoresaber/score-saber-player-token"; import { ScoreSaberPlayersPageToken } from "@/common/model/token/scoresaber/score-saber-players-page-token"; import { formatNumberWithCommas, 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"; import ScoreSaberPlayer from "@/common/model/player/impl/scoresaber-player"; import { scoresaberService } from "@/common/service/impl/scoresaber"; const REFRESH_INTERVAL = 5 * 60 * 1000; // 5 minutes const PLAYER_NAME_MAX_LENGTH = 18; type MiniProps = { type: "Global" | "Country"; player: ScoreSaberPlayer; }; type Variants = { [key: string]: { itemsPerPage: number; icon: (player: ScoreSaberPlayer) => ReactElement; getPage: (player: ScoreSaberPlayer, itemsPerPage: number) => number; query: ( page: number, country: string, ) => Promise; }; }; const miniVariants: Variants = { Global: { itemsPerPage: 50, icon: () => , getPage: (player: ScoreSaberPlayer, itemsPerPage: number) => { return Math.floor((player.rank - 1) / itemsPerPage) + 1; }, query: (page: number) => { return scoresaberService.lookupPlayers(page); }, }, Country: { itemsPerPage: 50, icon: (player: ScoreSaberPlayer) => { return ; }, getPage: (player: ScoreSaberPlayer, itemsPerPage: number) => { return Math.floor((player.countryRank - 1) / itemsPerPage) + 1; }, query: (page: number, country: string) => { return scoresaberService.lookupPlayersByCountry(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((playerRanking, index) => { const rank = type == "Global" ? playerRanking.rank : playerRanking.countryRank; const playerName = playerRanking.name.length > PLAYER_NAME_MAX_LENGTH ? playerRanking.name.substring(0, PLAYER_NAME_MAX_LENGTH) + "..." : playerRanking.name; return (

#{formatNumberWithCommas(rank)}

{playerName}

{formatPp(playerRanking.pp)}pp

); })}
); }