diff --git a/src/common/data-fetcher/data-fetcher.ts b/src/common/data-fetcher/data-fetcher.ts index 3b393d8..d6b977f 100644 --- a/src/common/data-fetcher/data-fetcher.ts +++ b/src/common/data-fetcher/data-fetcher.ts @@ -16,7 +16,7 @@ export default class DataFetcher { * @param data the data to log */ public log(data: unknown) { - console.log(`[${this.name}]: ${data}`); + console.debug(`[${this.name}]: ${data}`); } /** diff --git a/src/common/data-fetcher/impl/beatsaver.ts b/src/common/data-fetcher/impl/beatsaver.ts index 14bea99..a555e63 100644 --- a/src/common/data-fetcher/impl/beatsaver.ts +++ b/src/common/data-fetcher/impl/beatsaver.ts @@ -1,6 +1,7 @@ +import BeatSaverMap from "@/common/database/types/beatsaver-map"; import { db } from "../../database/database"; 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 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 * @returns the map that match the query, or undefined if no map were found */ - async getMapBsr(query: string, useProxy = true): Promise { - this.log(`Looking up the bsr for map hash ${query}...`); + async lookupMap(query: string, useProxy = true): Promise { + 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 if (map != undefined) { - this.log(`Found cached bsr ${map.bsr} for map hash ${query}`); - return map.bsr; + this.log(`Found cached map "${query}" in ${(performance.now() - before).toFixed(2)}ms`); + return map; } - const response = await this.fetch(useProxy, LOOKUP_MAP_BY_HASH_ENDPOINT.replace(":query", query)); + const response = await this.fetch(useProxy, LOOKUP_MAP_BY_HASH_ENDPOINT.replace(":query", query)); // Map not found if (response == undefined) { return undefined; @@ -42,9 +44,11 @@ class BeatSaverFetcher extends DataFetcher { await db.beatSaverMaps.add({ hash: query, bsr: bsr, + fullData: response, }); - this.log(`Looked up bsr ${bsr} for map hash ${query}`); - return bsr; + map = await db.beatSaverMaps.get(query); + this.log(`Found map "${query}" in ${(performance.now() - before).toFixed(2)}ms`); + return map; } } diff --git a/src/common/data-fetcher/impl/scoresaber.ts b/src/common/data-fetcher/impl/scoresaber.ts index ea0fc23..3ffe0ad 100644 --- a/src/common/data-fetcher/impl/scoresaber.ts +++ b/src/common/data-fetcher/impl/scoresaber.ts @@ -22,6 +22,7 @@ class ScoreSaberFetcher extends DataFetcher { * @returns the players that match the query, or undefined if no players were found */ async searchPlayers(query: string, useProxy = true): Promise { + const before = performance.now(); this.log(`Searching for players matching "${query}"...`); const results = await this.fetch( useProxy, @@ -34,6 +35,7 @@ class ScoreSaberFetcher extends DataFetcher { return undefined; } 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; } @@ -45,8 +47,14 @@ class ScoreSaberFetcher extends DataFetcher { * @returns the player that matches the ID, or undefined */ async lookupPlayer(playerId: string, useProxy = true): Promise { + const before = performance.now(); this.log(`Looking up player "${playerId}"...`); - return await this.fetch(useProxy, LOOKUP_PLAYER_ENDPOINT.replace(":id", playerId)); + const response = await this.fetch(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, useProxy = true ): Promise { + const before = performance.now(); this.log(`Looking up scores for player "${playerId}", sort "${sort}", page "${page}"...`); - return await this.fetch( + const response = await this.fetch( useProxy, LOOKUP_PLAYER_SCORES_ENDPOINT.replace(":id", playerId) .replace(":limit", 8 + "") .replace(":sort", sort) .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; } } diff --git a/src/common/database/types/beatsaver-map.ts b/src/common/database/types/beatsaver-map.ts index df87d0d..0d97d2f 100644 --- a/src/common/database/types/beatsaver-map.ts +++ b/src/common/database/types/beatsaver-map.ts @@ -1,3 +1,4 @@ +import { BeatSaverMap as BSMap } from "@/common/data-fetcher/types/beatsaver/beatsaver-map"; import { Entity } from "dexie"; import Database from "../database"; @@ -14,4 +15,9 @@ export default class BeatSaverMap extends Entity { * The bsr code for the map. */ bsr!: string; + + /** + * The full data for the map. + */ + fullData!: BSMap; } diff --git a/src/components/fallback-link.tsx b/src/components/fallback-link.tsx new file mode 100644 index 0000000..2e6186f --- /dev/null +++ b/src/components/fallback-link.tsx @@ -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 ? ( + + {children} + + ) : ( + <>{children} + ); +} diff --git a/src/components/player/score/score.tsx b/src/components/player/score/score.tsx index ce8c326..6bc84de 100644 --- a/src/components/player/score/score.tsx +++ b/src/components/player/score/score.tsx @@ -3,15 +3,18 @@ import { copyToClipboard } from "@/common/browser-utils"; import { beatsaverFetcher } from "@/common/data-fetcher/impl/beatsaver"; 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 { getDifficultyFromScoreSaberDifficulty } from "@/common/scoresaber-utils"; import { songDifficultyToColor } from "@/common/song-utils"; import { timeAgo } from "@/common/time-utils"; import { songNameToYouTubeLink } from "@/common/youtube-utils"; +import FallbackLink from "@/components/fallback-link"; import YouTubeLogo from "@/components/logos/youtube-logo"; import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; import { useToast } from "@/hooks/use-toast"; import { GlobeAmericasIcon, StarIcon } from "@heroicons/react/24/solid"; +import clsx from "clsx"; import Image from "next/image"; import { useEffect, useState } from "react"; import BeatSaverLogo from "../../logos/beatsaver-logo"; @@ -27,16 +30,18 @@ type Props = { export default function Score({ playerScore }: Props) { const { score, leaderboard } = playerScore; const { toast } = useToast(); - const [bsr, setBsr] = useState(); + const [beatSaverMap, setBeatSaverMap] = useState(); useEffect(() => { (async () => { - const bsrFound = await beatsaverFetcher.getMapBsr(leaderboard.songHash); - setBsr(bsrFound); + const beatSaverMap = await beatsaverFetcher.lookupMap(leaderboard.songHash); + setBeatSaverMap(beatSaverMap); })(); }, [playerScore, leaderboard.songHash]); const diff = getDifficultyFromScoreSaberDifficulty(leaderboard.difficulty.difficulty); + const mappersProfile = + beatSaverMap != undefined ? `https://beatsaver.com/profile/${beatSaverMap?.fullData.uploader.id}` : undefined; return (
@@ -93,21 +98,25 @@ export default function Score({ playerScore }: Props) { {leaderboard.songName} {leaderboard.songSubName}

{leaderboard.songAuthorName}

-

{leaderboard.levelAuthorName}

+ +

+ {leaderboard.levelAuthorName} +

+
- {bsr != undefined && ( + {beatSaverMap != undefined && ( <> {/* Copy BSR */} { toast({ title: "Copied!", - description: `Copied "!bsr ${bsr}" to your clipboard!`, + description: `Copied "!bsr ${beatSaverMap}" to your clipboard!`, }); - copyToClipboard(`!bsr ${bsr}`); + copyToClipboard(`!bsr ${beatSaverMap.bsr}`); }} tooltip={

Click to copy the bsr code

} > @@ -117,7 +126,7 @@ export default function Score({ playerScore }: Props) { {/* Open map in BeatSaver */} { - window.open(`https://beatsaver.com/maps/${bsr}`, "_blank"); + window.open(`https://beatsaver.com/maps/${beatSaverMap.bsr}`, "_blank"); }} tooltip={

Click to open the map

} >