diff --git a/src/app/tools/ratings/page.css.ts b/src/app/tools/ratings/page.css.ts new file mode 100644 index 000000000..057061637 --- /dev/null +++ b/src/app/tools/ratings/page.css.ts @@ -0,0 +1,9 @@ +import { style } from '@vanilla-extract/css'; + +export const noArrows = style({ + '::-webkit-progress-inner-value': { + appearance: 'none', + display: 'none', + margin: 0, + }, +}); diff --git a/src/app/tools/ratings/page.tsx b/src/app/tools/ratings/page.tsx index b8e74a80e..78cda33a6 100644 --- a/src/app/tools/ratings/page.tsx +++ b/src/app/tools/ratings/page.tsx @@ -9,53 +9,104 @@ import { Text, TextField, } from '@radix-ui/themes'; +import { produce } from 'immer'; import { range } from 'lodash'; -import { useState } from 'react'; +import { useEffect, useRef, useState } from 'react'; import useSWR from 'swr'; +import { create } from 'zustand'; import * as Leaderboard from '../../../components/Leaderboard'; import PageWrapper from '../../../components/PageWrapper'; import { REGIONS, REGION_NAMES, Region } from '../../../constants/regions'; +import { getAccountInfo } from '../../../core/blitz/getAccountInfo'; import getRatingsInfo from '../../../core/blitz/getRatingsInfo'; import getArchivedRatingsInfoAPI from '../../../core/blitzkrieg/getArchivedRatingsInfoAPI'; import { getArchivedRatingsLeaderboardAPI } from '../../../core/blitzkrieg/getArchivedRatingsLeaderboardAPI'; import { numberFetcher } from '../../../core/blitzkrieg/numberFetcher'; -import { useRatings } from '../../../stores/ratings'; +import { noArrows } from './page.css'; export const FIRST_ARCHIVED_RATINGS_SEASON = 49; +const ROWS_PER_PAGE = 64; + +const useUsernameCache = create>>(() => ({ + asia: {}, + com: {}, + eu: {}, +})); +const clanCache: Record> = { + com: {}, + eu: {}, + asia: {}, +}; export default function Page() { - const ratings = useRatings(); + const usernameCache = useUsernameCache(); + const [region, setRegion] = useState('com'); // null being the latest season const [season, setSeason] = useState(null); const { data: ratingsInfo } = useSWR( - `ratings-info-${ratings.region}-${season}`, + `ratings-info-${region}-${season}`, () => season === null - ? getRatingsInfo(ratings.region) - : getArchivedRatingsInfoAPI(ratings.region, season), + ? getRatingsInfo(region) + : getArchivedRatingsInfoAPI(region, season), ); const [jumpToLeague, setJumpToLeague] = useState(0); const { data: latestArchivedSeasonNumber } = useSWR( '/api/ratings/latest-archived-season-number', numberFetcher, ); - const players = useSWR(`ratings-players-${ratings.region}-${season}`, () => { + const players = useSWR(`ratings-players-${region}-${season}`, () => { if (season === null) { // i'll deal with this later return null; } else { - return getArchivedRatingsLeaderboardAPI(ratings.region, season); + return getArchivedRatingsLeaderboardAPI(region, season); } }); + const [page, setPage] = useState(0); + const pageInput = useRef(null); + const playerSlice = players.data?.slice( + page * ROWS_PER_PAGE, + page * ROWS_PER_PAGE + ROWS_PER_PAGE, + ); + + cachePage(page - 1); + cachePage(page); + cachePage(page + 1); + + function cachePage(page: number) { + const ids = players.data + ?.slice(page * ROWS_PER_PAGE, page * ROWS_PER_PAGE + ROWS_PER_PAGE) + .map(({ id }) => id) + .filter((id) => !(id in usernameCache[region])); + + if (ids && ids.length > 0) { + getAccountInfo(region, ids).then((data) => { + data.map((player) => { + useUsernameCache.setState( + produce((draft) => { + draft[region][player.account_id] = player.nickname; + }), + ); + }); + }); + } + } + + useEffect(() => { + if (pageInput.current) pageInput.current.value = `${page + 1}`; + }, [page]); + + useEffect(() => { + setPage(0); + }, [season, region]); return ( - + - useRatings.setState({ region: value as Region }) - } + defaultValue={region} + onValueChange={(value) => setRegion(region)} > @@ -98,84 +149,139 @@ export default function Page() { - - - - - - - - - setJumpToLeague(parseInt(value))} - > - - - - {ratingsInfo?.detail === undefined && - ratingsInfo?.leagues.map(({ index, title }) => ( - - {title} - - ))} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {players.data && ( - - {players.data.map((player, index) => ( - + + + + + + + + + setJumpToLeague(parseInt(value))} + > + + + + {ratingsInfo?.detail === undefined && + ratingsInfo?.leagues.map(({ index, title }) => ( + + {title} + + ))} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Page + { + setPage( + Math.max( + 0, + Math.min( + Math.floor((players.data?.length ?? 0) / ROWS_PER_PAGE), + event.target.valueAsNumber - 1, + ), + ), + ); + }} + onKeyDown={(event) => { + if (event.key === 'Enter') { + (event.target as HTMLInputElement).blur(); + } + }} /> - ))} - - )} + + out of {Math.ceil((players.data?.length ?? 0) / ROWS_PER_PAGE)} + + + + + + + + + {playerSlice?.map((player, index) => ( + + ))} + {players.isLoading && Loading...} diff --git a/src/core/blitz/fetchBlitz.ts b/src/core/blitz/fetchBlitz.ts index 64f1ae26d..5ac7a256d 100644 --- a/src/core/blitz/fetchBlitz.ts +++ b/src/core/blitz/fetchBlitz.ts @@ -32,7 +32,7 @@ async function manageQueue() { if (data.status === 'ok') { resolve(data.data); } else { - throw new Error(`Wargaming response error status:"${data.status}"`, { + throw new Error(`Wargaming response error status: "${data.status}"`, { cause: `Message: "${data.error.message}"\nURL: "${url}"`, }); } diff --git a/src/stores/ratings.ts b/src/stores/ratings.ts deleted file mode 100644 index 4fb2f3f35..000000000 --- a/src/stores/ratings.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { create } from 'zustand'; -import { devtools, persist } from 'zustand/middleware'; -import { Region } from '../constants/regions'; - -interface Ratings { - /** - * When null, the current season is used unless there ain't one in which case - * we do whatever lol - */ - region: Region; -} - -export const useRatings = create()( - devtools( - persist( - (set) => ({ - region: 'com', - }), - { - name: 'ratings', - }, - ), - ), -);