load initial scores server sided to prevent flashing
All checks were successful
Deploy SSR / deploy (push) Successful in 1m12s
All checks were successful
Deploy SSR / deploy (push) Successful in 1m12s
This commit is contained in:
parent
4d19fd3bfd
commit
aba0d4ba57
@ -43,6 +43,7 @@ export default async function Search({ params: { slug } }: Props) {
|
|||||||
const sort: ScoreSort = (slug[1] as ScoreSort) || "recent"; // The sorting method
|
const sort: ScoreSort = (slug[1] as ScoreSort) || "recent"; // The sorting method
|
||||||
const page = parseInt(slug[2]) || 1; // The page number
|
const page = parseInt(slug[2]) || 1; // The page number
|
||||||
const player = await scoresaberFetcher.lookupPlayer(id, false);
|
const player = await scoresaberFetcher.lookupPlayer(id, false);
|
||||||
|
const scores = await scoresaberFetcher.lookupPlayerScores(id, sort, page);
|
||||||
|
|
||||||
if (player == undefined) {
|
if (player == undefined) {
|
||||||
// Invalid player id
|
// Invalid player id
|
||||||
@ -51,7 +52,7 @@ export default async function Search({ params: { slug } }: Props) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col h-full w-full">
|
<div className="flex flex-col h-full w-full">
|
||||||
<PlayerData initalPlayerData={player} sort={sort} page={page} />
|
<PlayerData initalPlayerData={player} initialScoreData={scores} sort={sort} page={page} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -18,11 +18,12 @@ class BeatSaverFetcher extends DataFetcher {
|
|||||||
* @returns the map that match the query, or undefined if no map were found
|
* @returns the map that match the query, or undefined if no map were found
|
||||||
*/
|
*/
|
||||||
async getMapBsr(query: string, useProxy = true): Promise<string | undefined> {
|
async getMapBsr(query: string, useProxy = true): Promise<string | undefined> {
|
||||||
this.log(`Looking up the bsr for the map with hash ${query}...`);
|
this.log(`Looking up the bsr for map hash ${query}...`);
|
||||||
|
|
||||||
const map = await db.beatSaverMaps.get(query);
|
const map = await db.beatSaverMaps.get(query);
|
||||||
// The map is cached
|
// The map is cached
|
||||||
if (map != undefined) {
|
if (map != undefined) {
|
||||||
|
this.log(`Found cached bsr ${map.bsr} for map hash ${query}`);
|
||||||
return map.bsr;
|
return map.bsr;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -42,6 +43,7 @@ class BeatSaverFetcher extends DataFetcher {
|
|||||||
hash: query,
|
hash: query,
|
||||||
bsr: bsr,
|
bsr: bsr,
|
||||||
});
|
});
|
||||||
|
this.log(`Looked up bsr ${bsr} for map hash ${query}`);
|
||||||
return bsr;
|
return bsr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
import { scoresaberFetcher } from "@/common/data-fetcher/impl/scoresaber";
|
import { scoresaberFetcher } from "@/common/data-fetcher/impl/scoresaber";
|
||||||
import { ScoreSort } from "@/common/data-fetcher/sort";
|
import { ScoreSort } from "@/common/data-fetcher/sort";
|
||||||
import ScoreSaberPlayer from "@/common/data-fetcher/types/scoresaber/scoresaber-player";
|
import ScoreSaberPlayer from "@/common/data-fetcher/types/scoresaber/scoresaber-player";
|
||||||
|
import ScoreSaberPlayerScoresPage from "@/common/data-fetcher/types/scoresaber/scoresaber-player-scores-page";
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import PlayerHeader from "./player-header";
|
import PlayerHeader from "./player-header";
|
||||||
import PlayerRankChart from "./player-rank-chart";
|
import PlayerRankChart from "./player-rank-chart";
|
||||||
@ -12,11 +13,12 @@ const REFRESH_INTERVAL = 5 * 60 * 1000; // 5 minutes
|
|||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
initalPlayerData: ScoreSaberPlayer;
|
initalPlayerData: ScoreSaberPlayer;
|
||||||
|
initialScoreData?: ScoreSaberPlayerScoresPage;
|
||||||
sort: ScoreSort;
|
sort: ScoreSort;
|
||||||
page: number;
|
page: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function PlayerData({ initalPlayerData, sort, page }: Props) {
|
export default function PlayerData({ initalPlayerData, initialScoreData, sort, page }: Props) {
|
||||||
let player = initalPlayerData;
|
let player = initalPlayerData;
|
||||||
const { data, isLoading, isError } = useQuery({
|
const { data, isLoading, isError } = useQuery({
|
||||||
queryKey: ["player", player.id],
|
queryKey: ["player", player.id],
|
||||||
@ -36,7 +38,7 @@ export default function PlayerData({ initalPlayerData, sort, page }: Props) {
|
|||||||
<PlayerRankChart player={player} />
|
<PlayerRankChart player={player} />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<PlayerScores player={player} sort={sort} page={page} />
|
<PlayerScores initialScoreData={initialScoreData} player={player} sort={sort} page={page} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -15,70 +15,63 @@ import { Button } from "../ui/button";
|
|||||||
import Score from "./score/score";
|
import Score from "./score/score";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
initialScoreData?: ScoreSaberPlayerScoresPage;
|
||||||
player: ScoreSaberPlayer;
|
player: ScoreSaberPlayer;
|
||||||
sort: ScoreSort;
|
sort: ScoreSort;
|
||||||
page: number;
|
page: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function PlayerScores({ player, sort, page }: Props) {
|
export default function PlayerScores({ initialScoreData, player, sort, page }: Props) {
|
||||||
const { width } = useWindowDimensions();
|
const { width } = useWindowDimensions();
|
||||||
const controls = useAnimation();
|
const controls = useAnimation();
|
||||||
|
|
||||||
const [currentSort, setCurrentSort] = useState(sort);
|
const [currentSort, setCurrentSort] = useState(sort);
|
||||||
const [currentPage, setCurrentPage] = useState(page);
|
const [currentPage, setCurrentPage] = useState(page);
|
||||||
const [previousScores, setPreviousScores] = useState<ScoreSaberPlayerScoresPage | undefined>();
|
|
||||||
|
|
||||||
const { data, isError, isLoading, refetch } = useQuery({
|
const {
|
||||||
|
data: scores,
|
||||||
|
isError,
|
||||||
|
isLoading,
|
||||||
|
refetch,
|
||||||
|
} = useQuery({
|
||||||
queryKey: ["playerScores", player.id, currentSort, currentPage],
|
queryKey: ["playerScores", player.id, currentSort, currentPage],
|
||||||
queryFn: () => scoresaberFetcher.lookupPlayerScores(player.id, currentSort, currentPage),
|
queryFn: () => scoresaberFetcher.lookupPlayerScores(player.id, currentSort, currentPage),
|
||||||
staleTime: 30 * 1000, // Data will be cached for 30 seconds
|
staleTime: 30 * 1000, // Cache data for 30 seconds
|
||||||
|
initialData: initialScoreData,
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleAnimation = useCallback(() => {
|
const handleAnimation = useCallback(() => {
|
||||||
controls.set({
|
controls.set({ x: -50, opacity: 0 });
|
||||||
x: -50,
|
controls.start({ x: 0, opacity: 1, transition: { duration: 0.25 } });
|
||||||
opacity: 0,
|
|
||||||
});
|
|
||||||
controls.start({
|
|
||||||
x: 0,
|
|
||||||
opacity: 1,
|
|
||||||
transition: { duration: 0.25 },
|
|
||||||
});
|
|
||||||
}, [controls]);
|
}, [controls]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (data == undefined) {
|
if (scores) {
|
||||||
return;
|
handleAnimation();
|
||||||
}
|
}
|
||||||
setPreviousScores(data);
|
}, [scores, handleAnimation]);
|
||||||
handleAnimation();
|
|
||||||
}, [data, handleAnimation]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Update URL and refetch data when currentSort or currentPage changes
|
|
||||||
const newUrl = `/player/${player.id}/${currentSort}/${currentPage}`;
|
const newUrl = `/player/${player.id}/${currentSort}/${currentPage}`;
|
||||||
window.history.replaceState({ ...window.history.state, as: newUrl, url: newUrl }, "", newUrl);
|
window.history.replaceState({ ...window.history.state, as: newUrl, url: newUrl }, "", newUrl);
|
||||||
refetch();
|
refetch();
|
||||||
}, [currentSort, currentPage, refetch, player.id]);
|
}, [currentSort, currentPage, refetch, player.id]);
|
||||||
|
|
||||||
/**
|
const handleSortChange = (newSort: ScoreSort) => {
|
||||||
* Updates the current sort and resets the page to 1
|
|
||||||
*/
|
|
||||||
function handleSortChange(newSort: ScoreSort) {
|
|
||||||
if (newSort !== currentSort) {
|
if (newSort !== currentSort) {
|
||||||
setCurrentSort(newSort);
|
setCurrentSort(newSort);
|
||||||
setCurrentPage(1); // Reset the page
|
setCurrentPage(1); // Reset page to 1 on sort change
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
if (previousScores === undefined) {
|
if (scores === undefined) {
|
||||||
return null;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isError) {
|
if (isError) {
|
||||||
return (
|
return (
|
||||||
<Card className="gap-2">
|
<Card className="gap-2">
|
||||||
<p>Oopsies!</p>
|
<p>Oopsies! Something went wrong.</p>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -86,20 +79,20 @@ export default function PlayerScores({ player, sort, page }: Props) {
|
|||||||
return (
|
return (
|
||||||
<Card className="flex gap-4">
|
<Card className="flex gap-4">
|
||||||
<div className="flex items-center flex-row w-full gap-2 justify-center">
|
<div className="flex items-center flex-row w-full gap-2 justify-center">
|
||||||
{Object.keys(ScoreSort).map((sort, index) => (
|
{Object.values(ScoreSort).map((sortOption) => (
|
||||||
<Button
|
<Button
|
||||||
variant={sort == currentSort ? "default" : "outline"}
|
variant={sortOption === currentSort ? "default" : "outline"}
|
||||||
key={index}
|
key={sortOption}
|
||||||
onClick={() => handleSortChange(sort as ScoreSort)}
|
onClick={() => handleSortChange(sortOption)}
|
||||||
>
|
>
|
||||||
{capitalizeFirstLetter(sort)}
|
{capitalizeFirstLetter(sortOption)}
|
||||||
</Button>
|
</Button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<motion.div animate={controls}>
|
<motion.div animate={controls}>
|
||||||
<div className="grid min-w-full grid-cols-1 divide-y divide-border">
|
<div className="grid min-w-full grid-cols-1 divide-y divide-border">
|
||||||
{previousScores.playerScores.map((playerScore, index) => (
|
{scores.playerScores.map((playerScore, index) => (
|
||||||
<Score key={index} playerScore={playerScore} />
|
<Score key={index} playerScore={playerScore} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@ -108,11 +101,9 @@ export default function PlayerScores({ player, sort, page }: Props) {
|
|||||||
<Pagination
|
<Pagination
|
||||||
mobilePagination={width < 768}
|
mobilePagination={width < 768}
|
||||||
page={currentPage}
|
page={currentPage}
|
||||||
totalPages={Math.ceil(previousScores.metadata.total / previousScores.metadata.itemsPerPage)}
|
totalPages={Math.ceil(scores.metadata.total / scores.metadata.itemsPerPage)}
|
||||||
loadingPage={isLoading ? currentPage : undefined}
|
loadingPage={isLoading ? currentPage : undefined}
|
||||||
onPageChange={(newPage) => {
|
onPageChange={setCurrentPage}
|
||||||
setCurrentPage(newPage);
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
|
Reference in New Issue
Block a user