Compare commits
2 Commits
d81dac3622
...
773fe0740a
Author | SHA1 | Date | |
---|---|---|---|
773fe0740a | |||
76f1422bd7 |
@ -23,12 +23,14 @@
|
||||
"@trigger.dev/nextjs": "^3.0.8",
|
||||
"@trigger.dev/react": "^3.0.8",
|
||||
"@trigger.dev/sdk": "^3.0.8",
|
||||
"canvas": "3.0.0-rc2",
|
||||
"chart.js": "^4.4.4",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.1.1",
|
||||
"comlink": "^4.4.1",
|
||||
"dexie": "^4.0.8",
|
||||
"dexie-react-hooks": "^1.1.7",
|
||||
"extract-colors": "^4.0.8",
|
||||
"framer-motion": "^11.5.4",
|
||||
"js-cookie": "^3.0.5",
|
||||
"ky": "^1.7.2",
|
||||
|
@ -3,8 +3,16 @@ import { scoresaberService } from "@/common/service/impl/scoresaber";
|
||||
import { ScoreSort } from "@/common/service/score-sort";
|
||||
import PlayerData from "@/components/player/player-data";
|
||||
import { format } from "@formkit/tempo";
|
||||
import { Metadata } from "next";
|
||||
import { Metadata, Viewport } from "next";
|
||||
import { redirect } from "next/navigation";
|
||||
import { Colors } from "@/common/colors";
|
||||
import ScoreSaberPlayerScoresPageToken from "@/common/model/token/scoresaber/score-saber-player-scores-page-token";
|
||||
import { getAverageColor } from "@/common/image-utils";
|
||||
|
||||
const UNKNOWN_PLAYER = {
|
||||
title: "ScoreSaber Reloaded - Unknown Player",
|
||||
description: "The player you were looking for could not be found.",
|
||||
};
|
||||
|
||||
type Props = {
|
||||
params: Promise<{
|
||||
@ -15,19 +23,52 @@ type Props = {
|
||||
}>;
|
||||
};
|
||||
|
||||
export async function generateMetadata({ params }: Props): Promise<Metadata> {
|
||||
/**
|
||||
* Gets the player data and scores
|
||||
*
|
||||
* @param params the params
|
||||
* @param fetchScores whether to fetch the scores
|
||||
* @returns the player data and scores
|
||||
*/
|
||||
async function getPlayerData({ params }: Props, fetchScores: boolean = true) {
|
||||
const { slug } = await params;
|
||||
const id = slug[0]; // The players id
|
||||
const response = await scoresaberService.lookupPlayer(id, false);
|
||||
if (response === undefined) {
|
||||
const sort: ScoreSort = (slug[1] as ScoreSort) || "recent"; // The sorting method
|
||||
const page = parseInt(slug[2]) || 1; // The page number
|
||||
const search = (slug[3] as string) || ""; // The search query
|
||||
|
||||
const player = (await scoresaberService.lookupPlayer(id, false))?.player;
|
||||
let scores: ScoreSaberPlayerScoresPageToken | undefined;
|
||||
if (fetchScores) {
|
||||
scores = await scoresaberService.lookupPlayerScores({
|
||||
playerId: id,
|
||||
sort,
|
||||
page,
|
||||
search,
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
sort: sort,
|
||||
page: page,
|
||||
search: search,
|
||||
player: player,
|
||||
scores: scores,
|
||||
};
|
||||
}
|
||||
|
||||
export async function generateMetadata(props: Props): Promise<Metadata> {
|
||||
const { player } = await getPlayerData(props, false);
|
||||
if (player === undefined) {
|
||||
return {
|
||||
title: `Unknown Player`,
|
||||
title: UNKNOWN_PLAYER.title,
|
||||
description: UNKNOWN_PLAYER.description,
|
||||
openGraph: {
|
||||
title: `Unknown Player`,
|
||||
title: UNKNOWN_PLAYER.title,
|
||||
description: UNKNOWN_PLAYER.description,
|
||||
},
|
||||
};
|
||||
}
|
||||
const { player } = response;
|
||||
|
||||
return {
|
||||
title: `${player.name}`,
|
||||
@ -43,27 +84,32 @@ export async function generateMetadata({ params }: Props): Promise<Metadata> {
|
||||
};
|
||||
}
|
||||
|
||||
export default async function Search({ params, searchParams }: Props) {
|
||||
const { slug } = await params;
|
||||
const searchParamss = await searchParams;
|
||||
const id = slug[0]; // The players id
|
||||
const sort: ScoreSort = (slug[1] as ScoreSort) || "recent"; // The sorting method
|
||||
const page = parseInt(slug[2]) || 1; // The page number
|
||||
const search = searchParamss["search"] || ""; // The search query
|
||||
export async function generateViewport(props: Props): Promise<Viewport> {
|
||||
const { player } = await getPlayerData(props, false);
|
||||
if (player === undefined) {
|
||||
return {
|
||||
themeColor: Colors.primary,
|
||||
};
|
||||
}
|
||||
|
||||
const response = await scoresaberService.lookupPlayer(id, false);
|
||||
if (response == undefined) {
|
||||
// Invalid player id
|
||||
const color = await getAverageColor(player.avatar);
|
||||
if (color === undefined) {
|
||||
return {
|
||||
themeColor: Colors.primary,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
themeColor: color?.hex,
|
||||
};
|
||||
}
|
||||
|
||||
export default async function Search(props: Props) {
|
||||
const { player, scores, sort, page, search } = await getPlayerData(props);
|
||||
if (player == undefined) {
|
||||
return redirect("/");
|
||||
}
|
||||
|
||||
const scores = await scoresaberService.lookupPlayerScores({
|
||||
playerId: id,
|
||||
sort,
|
||||
page,
|
||||
search,
|
||||
});
|
||||
const { player } = response;
|
||||
return (
|
||||
<div className="flex flex-col h-full w-full">
|
||||
<PlayerData
|
||||
|
@ -5,13 +5,14 @@ import { ThemeProvider } from "@/components/providers/theme-provider";
|
||||
import { Toaster } from "@/components/ui/toaster";
|
||||
import { TooltipProvider } from "@/components/ui/tooltip";
|
||||
import { AnimatePresence } from "framer-motion";
|
||||
import type { Metadata } from "next";
|
||||
import type { Metadata, Viewport } from "next";
|
||||
import localFont from "next/font/local";
|
||||
import BackgroundImage from "../components/background-image";
|
||||
import DatabaseLoader from "../components/loaders/database-loader";
|
||||
import NavBar from "../components/navbar/navbar";
|
||||
|
||||
import "./globals.css";
|
||||
import { Colors } from "@/common/colors";
|
||||
|
||||
const siteFont = localFont({
|
||||
src: "./fonts/JetBrainsMono-Regular.woff2",
|
||||
@ -57,6 +58,10 @@ export const metadata: Metadata = {
|
||||
"Scoresaber Reloaded is a new way to view your scores and get more stats about your and your plays",
|
||||
};
|
||||
|
||||
export const viewport: Viewport = {
|
||||
themeColor: Colors.primary,
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
|
3
src/common/colors.ts
Normal file
3
src/common/colors.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export const Colors = {
|
||||
primary: "#0070f3",
|
||||
};
|
@ -1,4 +1,8 @@
|
||||
import { createCanvas, loadImage } from "canvas";
|
||||
import { config } from "../../config";
|
||||
import ky from "ky";
|
||||
import { extractColors } from "extract-colors";
|
||||
import { cache } from "react";
|
||||
|
||||
/**
|
||||
* Proxies all non-localhost images to make them load faster.
|
||||
@ -9,3 +13,45 @@ import { config } from "../../config";
|
||||
export function getImageUrl(originalUrl: string) {
|
||||
return `${!config.siteUrl.includes("localhost") ? "https://img.fascinated.cc/upload/q_70/" : ""}${originalUrl}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the average color of an image
|
||||
*
|
||||
* @param src the image url
|
||||
* @returns the average color
|
||||
*/
|
||||
export const getAverageColor = cache(async (src: string) => {
|
||||
const before = performance.now();
|
||||
console.log(`Getting average color of "${src}"...`);
|
||||
try {
|
||||
const response = await ky.get(src);
|
||||
if (response.status !== 200) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const imageBuffer = await response.arrayBuffer();
|
||||
|
||||
// Create an image from the buffer using canvas
|
||||
const img = await loadImage(Buffer.from(imageBuffer));
|
||||
const canvas = createCanvas(img.width, img.height);
|
||||
const ctx = canvas.getContext("2d");
|
||||
|
||||
// Draw the image onto the canvas
|
||||
ctx.drawImage(img, 0, 0);
|
||||
|
||||
// Get the pixel data from the canvas
|
||||
const imageData = ctx.getImageData(0, 0, img.width, img.height);
|
||||
const { data, width, height } = imageData;
|
||||
|
||||
// Use your extractColors function to calculate the average color
|
||||
const color = await extractColors({ data, width, height });
|
||||
|
||||
console.log(
|
||||
`Found average color of "${src}" in ${(performance.now() - before).toFixed(0)}ms`,
|
||||
);
|
||||
return color[2];
|
||||
} catch (error) {
|
||||
console.error("Error while getting average color:", error);
|
||||
return undefined;
|
||||
}
|
||||
});
|
||||
|
@ -121,8 +121,9 @@ export async function getScoreSaberPlayerFromToken(
|
||||
}
|
||||
// Sort the fallback history
|
||||
statisticHistory = Object.entries(statisticHistory)
|
||||
.sort()
|
||||
.sort((a, b) => Date.parse(b[0]) - Date.parse(a[0]))
|
||||
.reduce((obj, [key, value]) => ({ ...obj, [key]: value }), {});
|
||||
|
||||
const yesterdayDate = formatDateMinimal(
|
||||
getMidnightAlignedDate(getDaysAgoDate(1)),
|
||||
);
|
||||
|
Reference in New Issue
Block a user