move logging to debug and make mappers clickable on the score
All checks were successful
Deploy SSR / deploy (push) Successful in 1m13s
All checks were successful
Deploy SSR / deploy (push) Successful in 1m13s
This commit is contained in:
parent
7db722faea
commit
574b1a6532
@ -16,7 +16,7 @@ export default class DataFetcher {
|
|||||||
* @param data the data to log
|
* @param data the data to log
|
||||||
*/
|
*/
|
||||||
public log(data: unknown) {
|
public log(data: unknown) {
|
||||||
console.log(`[${this.name}]: ${data}`);
|
console.debug(`[${this.name}]: ${data}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
|
import BeatSaverMap from "@/common/database/types/beatsaver-map";
|
||||||
import { db } from "../../database/database";
|
import { db } from "../../database/database";
|
||||||
import DataFetcher from "../data-fetcher";
|
import DataFetcher from "../data-fetcher";
|
||||||
import { BeatSaverMap } from "../types/beatsaver/beatsaver-map";
|
import { BeatSaverMap as BSMap } from "../types/beatsaver/beatsaver-map";
|
||||||
|
|
||||||
const API_BASE = "https://api.beatsaver.com";
|
const API_BASE = "https://api.beatsaver.com";
|
||||||
const LOOKUP_MAP_BY_HASH_ENDPOINT = `${API_BASE}/maps/hash/:query`;
|
const LOOKUP_MAP_BY_HASH_ENDPOINT = `${API_BASE}/maps/hash/:query`;
|
||||||
@ -17,17 +18,18 @@ class BeatSaverFetcher extends DataFetcher {
|
|||||||
* @param useProxy whether to use the proxy or not
|
* @param useProxy whether to use the proxy or not
|
||||||
* @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 lookupMap(query: string, useProxy = true): Promise<BeatSaverMap | undefined> {
|
||||||
this.log(`Looking up the bsr for map hash ${query}...`);
|
const before = performance.now();
|
||||||
|
this.log(`Looking up map "${query}"...`);
|
||||||
|
|
||||||
const map = await db.beatSaverMaps.get(query);
|
let 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}`);
|
this.log(`Found cached map "${query}" in ${(performance.now() - before).toFixed(2)}ms`);
|
||||||
return map.bsr;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await this.fetch<BeatSaverMap>(useProxy, LOOKUP_MAP_BY_HASH_ENDPOINT.replace(":query", query));
|
const response = await this.fetch<BSMap>(useProxy, LOOKUP_MAP_BY_HASH_ENDPOINT.replace(":query", query));
|
||||||
// Map not found
|
// Map not found
|
||||||
if (response == undefined) {
|
if (response == undefined) {
|
||||||
return undefined;
|
return undefined;
|
||||||
@ -42,9 +44,11 @@ class BeatSaverFetcher extends DataFetcher {
|
|||||||
await db.beatSaverMaps.add({
|
await db.beatSaverMaps.add({
|
||||||
hash: query,
|
hash: query,
|
||||||
bsr: bsr,
|
bsr: bsr,
|
||||||
|
fullData: response,
|
||||||
});
|
});
|
||||||
this.log(`Looked up bsr ${bsr} for map hash ${query}`);
|
map = await db.beatSaverMaps.get(query);
|
||||||
return bsr;
|
this.log(`Found map "${query}" in ${(performance.now() - before).toFixed(2)}ms`);
|
||||||
|
return map;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,6 +22,7 @@ class ScoreSaberFetcher extends DataFetcher {
|
|||||||
* @returns the players that match the query, or undefined if no players were found
|
* @returns the players that match the query, or undefined if no players were found
|
||||||
*/
|
*/
|
||||||
async searchPlayers(query: string, useProxy = true): Promise<ScoreSaberPlayerSearch | undefined> {
|
async searchPlayers(query: string, useProxy = true): Promise<ScoreSaberPlayerSearch | undefined> {
|
||||||
|
const before = performance.now();
|
||||||
this.log(`Searching for players matching "${query}"...`);
|
this.log(`Searching for players matching "${query}"...`);
|
||||||
const results = await this.fetch<ScoreSaberPlayerSearch>(
|
const results = await this.fetch<ScoreSaberPlayerSearch>(
|
||||||
useProxy,
|
useProxy,
|
||||||
@ -34,6 +35,7 @@ class ScoreSaberFetcher extends DataFetcher {
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
results.players.sort((a, b) => a.rank - b.rank);
|
results.players.sort((a, b) => a.rank - b.rank);
|
||||||
|
this.log(`Found ${results.players.length} players in ${(performance.now() - before).toFixed(2)}ms`);
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -45,8 +47,14 @@ class ScoreSaberFetcher extends DataFetcher {
|
|||||||
* @returns the player that matches the ID, or undefined
|
* @returns the player that matches the ID, or undefined
|
||||||
*/
|
*/
|
||||||
async lookupPlayer(playerId: string, useProxy = true): Promise<ScoreSaberPlayer | undefined> {
|
async lookupPlayer(playerId: string, useProxy = true): Promise<ScoreSaberPlayer | undefined> {
|
||||||
|
const before = performance.now();
|
||||||
this.log(`Looking up player "${playerId}"...`);
|
this.log(`Looking up player "${playerId}"...`);
|
||||||
return await this.fetch<ScoreSaberPlayer>(useProxy, LOOKUP_PLAYER_ENDPOINT.replace(":id", playerId));
|
const response = await this.fetch<ScoreSaberPlayer>(useProxy, LOOKUP_PLAYER_ENDPOINT.replace(":id", playerId));
|
||||||
|
if (response === undefined) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
this.log(`Found player "${playerId}" in ${(performance.now() - before).toFixed(2)}ms`);
|
||||||
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -64,14 +72,20 @@ class ScoreSaberFetcher extends DataFetcher {
|
|||||||
page: number,
|
page: number,
|
||||||
useProxy = true
|
useProxy = true
|
||||||
): Promise<ScoreSaberPlayerScoresPage | undefined> {
|
): Promise<ScoreSaberPlayerScoresPage | undefined> {
|
||||||
|
const before = performance.now();
|
||||||
this.log(`Looking up scores for player "${playerId}", sort "${sort}", page "${page}"...`);
|
this.log(`Looking up scores for player "${playerId}", sort "${sort}", page "${page}"...`);
|
||||||
return await this.fetch<ScoreSaberPlayerScoresPage>(
|
const response = await this.fetch<ScoreSaberPlayerScoresPage>(
|
||||||
useProxy,
|
useProxy,
|
||||||
LOOKUP_PLAYER_SCORES_ENDPOINT.replace(":id", playerId)
|
LOOKUP_PLAYER_SCORES_ENDPOINT.replace(":id", playerId)
|
||||||
.replace(":limit", 8 + "")
|
.replace(":limit", 8 + "")
|
||||||
.replace(":sort", sort)
|
.replace(":sort", sort)
|
||||||
.replace(":page", page.toString())
|
.replace(":page", page.toString())
|
||||||
);
|
);
|
||||||
|
if (response === undefined) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
this.log(`Found scores for player "${playerId}" in ${(performance.now() - before).toFixed(2)}ms`);
|
||||||
|
return response;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { BeatSaverMap as BSMap } from "@/common/data-fetcher/types/beatsaver/beatsaver-map";
|
||||||
import { Entity } from "dexie";
|
import { Entity } from "dexie";
|
||||||
import Database from "../database";
|
import Database from "../database";
|
||||||
|
|
||||||
@ -14,4 +15,9 @@ export default class BeatSaverMap extends Entity<Database> {
|
|||||||
* The bsr code for the map.
|
* The bsr code for the map.
|
||||||
*/
|
*/
|
||||||
bsr!: string;
|
bsr!: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The full data for the map.
|
||||||
|
*/
|
||||||
|
fullData!: BSMap;
|
||||||
}
|
}
|
||||||
|
23
src/components/fallback-link.tsx
Normal file
23
src/components/fallback-link.tsx
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import NextLink from "next/link";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
/**
|
||||||
|
* The link to open in a new tab.
|
||||||
|
*/
|
||||||
|
href?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The children to render.
|
||||||
|
*/
|
||||||
|
children: React.ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function FallbackLink({ href, children }: Props) {
|
||||||
|
return href ? (
|
||||||
|
<NextLink href={href} target="_blank">
|
||||||
|
{children}
|
||||||
|
</NextLink>
|
||||||
|
) : (
|
||||||
|
<>{children}</>
|
||||||
|
);
|
||||||
|
}
|
@ -3,15 +3,18 @@
|
|||||||
import { copyToClipboard } from "@/common/browser-utils";
|
import { copyToClipboard } from "@/common/browser-utils";
|
||||||
import { beatsaverFetcher } from "@/common/data-fetcher/impl/beatsaver";
|
import { beatsaverFetcher } from "@/common/data-fetcher/impl/beatsaver";
|
||||||
import ScoreSaberPlayerScore from "@/common/data-fetcher/types/scoresaber/scoresaber-player-score";
|
import ScoreSaberPlayerScore from "@/common/data-fetcher/types/scoresaber/scoresaber-player-score";
|
||||||
|
import BeatSaverMap from "@/common/database/types/beatsaver-map";
|
||||||
import { formatNumberWithCommas } from "@/common/number-utils";
|
import { formatNumberWithCommas } from "@/common/number-utils";
|
||||||
import { getDifficultyFromScoreSaberDifficulty } from "@/common/scoresaber-utils";
|
import { getDifficultyFromScoreSaberDifficulty } from "@/common/scoresaber-utils";
|
||||||
import { songDifficultyToColor } from "@/common/song-utils";
|
import { songDifficultyToColor } from "@/common/song-utils";
|
||||||
import { timeAgo } from "@/common/time-utils";
|
import { timeAgo } from "@/common/time-utils";
|
||||||
import { songNameToYouTubeLink } from "@/common/youtube-utils";
|
import { songNameToYouTubeLink } from "@/common/youtube-utils";
|
||||||
|
import FallbackLink from "@/components/fallback-link";
|
||||||
import YouTubeLogo from "@/components/logos/youtube-logo";
|
import YouTubeLogo from "@/components/logos/youtube-logo";
|
||||||
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
|
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
|
||||||
import { useToast } from "@/hooks/use-toast";
|
import { useToast } from "@/hooks/use-toast";
|
||||||
import { GlobeAmericasIcon, StarIcon } from "@heroicons/react/24/solid";
|
import { GlobeAmericasIcon, StarIcon } from "@heroicons/react/24/solid";
|
||||||
|
import clsx from "clsx";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import BeatSaverLogo from "../../logos/beatsaver-logo";
|
import BeatSaverLogo from "../../logos/beatsaver-logo";
|
||||||
@ -27,16 +30,18 @@ type Props = {
|
|||||||
export default function Score({ playerScore }: Props) {
|
export default function Score({ playerScore }: Props) {
|
||||||
const { score, leaderboard } = playerScore;
|
const { score, leaderboard } = playerScore;
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
const [bsr, setBsr] = useState<string | undefined>();
|
const [beatSaverMap, setBeatSaverMap] = useState<BeatSaverMap | undefined>();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
(async () => {
|
(async () => {
|
||||||
const bsrFound = await beatsaverFetcher.getMapBsr(leaderboard.songHash);
|
const beatSaverMap = await beatsaverFetcher.lookupMap(leaderboard.songHash);
|
||||||
setBsr(bsrFound);
|
setBeatSaverMap(beatSaverMap);
|
||||||
})();
|
})();
|
||||||
}, [playerScore, leaderboard.songHash]);
|
}, [playerScore, leaderboard.songHash]);
|
||||||
|
|
||||||
const diff = getDifficultyFromScoreSaberDifficulty(leaderboard.difficulty.difficulty);
|
const diff = getDifficultyFromScoreSaberDifficulty(leaderboard.difficulty.difficulty);
|
||||||
|
const mappersProfile =
|
||||||
|
beatSaverMap != undefined ? `https://beatsaver.com/profile/${beatSaverMap?.fullData.uploader.id}` : undefined;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="grid gap-2 md:gap-0 pb-2 pt-2 first:pt-0 last:pb-0 grid-cols-[20px 1fr_1fr] md:grid-cols-[0.85fr_5fr_1fr_1.2fr]">
|
<div className="grid gap-2 md:gap-0 pb-2 pt-2 first:pt-0 last:pb-0 grid-cols-[20px 1fr_1fr] md:grid-cols-[0.85fr_5fr_1fr_1.2fr]">
|
||||||
@ -93,21 +98,25 @@ export default function Score({ playerScore }: Props) {
|
|||||||
{leaderboard.songName} {leaderboard.songSubName}
|
{leaderboard.songName} {leaderboard.songSubName}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-sm text-gray-400">{leaderboard.songAuthorName}</p>
|
<p className="text-sm text-gray-400">{leaderboard.songAuthorName}</p>
|
||||||
<p className="text-sm">{leaderboard.levelAuthorName}</p>
|
<FallbackLink href={mappersProfile}>
|
||||||
|
<p className={clsx("text-sm", mappersProfile && "hover:brightness-75 transform-gpu transition-all")}>
|
||||||
|
{leaderboard.levelAuthorName}
|
||||||
|
</p>
|
||||||
|
</FallbackLink>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="hidden md:flex flex-row flex-wrap gap-1 justify-end">
|
<div className="hidden md:flex flex-row flex-wrap gap-1 justify-end">
|
||||||
{bsr != undefined && (
|
{beatSaverMap != undefined && (
|
||||||
<>
|
<>
|
||||||
{/* Copy BSR */}
|
{/* Copy BSR */}
|
||||||
<ScoreButton
|
<ScoreButton
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
toast({
|
toast({
|
||||||
title: "Copied!",
|
title: "Copied!",
|
||||||
description: `Copied "!bsr ${bsr}" to your clipboard!`,
|
description: `Copied "!bsr ${beatSaverMap}" to your clipboard!`,
|
||||||
});
|
});
|
||||||
copyToClipboard(`!bsr ${bsr}`);
|
copyToClipboard(`!bsr ${beatSaverMap.bsr}`);
|
||||||
}}
|
}}
|
||||||
tooltip={<p>Click to copy the bsr code</p>}
|
tooltip={<p>Click to copy the bsr code</p>}
|
||||||
>
|
>
|
||||||
@ -117,7 +126,7 @@ export default function Score({ playerScore }: Props) {
|
|||||||
{/* Open map in BeatSaver */}
|
{/* Open map in BeatSaver */}
|
||||||
<ScoreButton
|
<ScoreButton
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
window.open(`https://beatsaver.com/maps/${bsr}`, "_blank");
|
window.open(`https://beatsaver.com/maps/${beatSaverMap.bsr}`, "_blank");
|
||||||
}}
|
}}
|
||||||
tooltip={<p>Click to open the map</p>}
|
tooltip={<p>Click to open the map</p>}
|
||||||
>
|
>
|
||||||
|
Reference in New Issue
Block a user