From 521e53fadd9234995da7b89332a5ba2c9a41c4a8 Mon Sep 17 00:00:00 2001 From: glax Date: Mon, 26 May 2025 04:22:15 +0200 Subject: [PATCH] Basic functionality --- src/App.tsx | 49 +++++++++++++++++++++++-- src/api/contexts/GamesContext.tsx | 8 +++++ src/api/contexts/PlayersContext.tsx | 8 +++++ src/api/fetchApi.tsx | 36 +++++++++++++++++-- src/components/GameAccordionItem.tsx | 50 ++++++++++++++++++++++++++ src/components/PlayerAccordionItem.tsx | 46 ++++++++++++++++++++++++ 6 files changed, 193 insertions(+), 4 deletions(-) create mode 100644 src/api/contexts/GamesContext.tsx create mode 100644 src/api/contexts/PlayersContext.tsx create mode 100644 src/components/GameAccordionItem.tsx create mode 100644 src/components/PlayerAccordionItem.tsx diff --git a/src/App.tsx b/src/App.tsx index 452ddae..3468105 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,9 +1,54 @@ import './App.css' +import {PlayersContext} from "./api/contexts/PlayersContext.tsx"; +import type IPlayer from "./api/types/IPlayer.ts"; +import {useEffect, useState} from "react"; +import type IGame from "./api/types/IGame.ts"; +import {GamesContext} from "./api/contexts/GamesContext.tsx"; +import {ApiUriContext} from "./api/fetchApi.tsx"; +import {GetGames, GetPlayers} from "./api/endpoints/Data.tsx"; +import { + AccordionGroup, + Box, + Divider, + Input, + Stack, + Typography +} from "@mui/joy"; +import GameAccordionItem from "./components/GameAccordionItem.tsx"; +import PlayerAccordionItem from "./components/PlayerAccordionItem.tsx"; export default function App() { + const [apiUri, setApiUri] = useState("http://localhost:5239"); + const [players, setPlayers] = useState([]); + const [games, setGames] = useState([]); + + useEffect(() => { + GetPlayers(apiUri).then(setPlayers); + GetGames(apiUri).then(setGames); + }, [apiUri]); return ( - <> - + + + + setApiUri(e.target.value)} /> + + + Players + + {players?.map((player) => )} + + + + + Games + + {games?.map((game) => )} + + + + + + ) } diff --git a/src/api/contexts/GamesContext.tsx b/src/api/contexts/GamesContext.tsx new file mode 100644 index 0000000..0aa6c77 --- /dev/null +++ b/src/api/contexts/GamesContext.tsx @@ -0,0 +1,8 @@ +import {createContext} from "react"; +import type IGame from "../types/IGame.ts"; + +export const GamesContext = createContext<{games: IGame[]}>( + { + games: [] + } +); \ No newline at end of file diff --git a/src/api/contexts/PlayersContext.tsx b/src/api/contexts/PlayersContext.tsx new file mode 100644 index 0000000..ae3a767 --- /dev/null +++ b/src/api/contexts/PlayersContext.tsx @@ -0,0 +1,8 @@ +import {createContext} from "react"; +import type IPlayer from "../types/IPlayer.ts"; + +export const PlayersContext = createContext<{players: IPlayer[]}>( + { + players: [] + } +); \ No newline at end of file diff --git a/src/api/fetchApi.tsx b/src/api/fetchApi.tsx index a0a5ec4..601cef7 100644 --- a/src/api/fetchApi.tsx +++ b/src/api/fetchApi.tsx @@ -70,8 +70,7 @@ function makeRequest(method: string, uri: string, content?: object | string | nu }else throw new Error(response.statusText); } - let json = response.json(); - return json.then((json) => json).catch(() => null); + return response.text().then(text => JSON.parseBigInt(text) as object); }) .catch(function(err : Error){ console.error(`Error ${method}ing Data ${uri}\n${err}`); @@ -79,6 +78,39 @@ function makeRequest(method: string, uri: string, content?: object | string | nu }).finally(() => currentlyRequestedEndpoints.splice(currentlyRequestedEndpoints.indexOf(id), 1)); } +declare global { + interface JSON { + parseBigInt: (jsonStr: string, options?: { minDigits?: number; fallbackToString?: boolean; }) => unknown; + } +} + +function quoteLargeNumbers(jsonStr: string, minDigits: number): string { + const regex = new RegExp(`(-?\\d{${minDigits},})(?=\\s*[,}\\]])`, 'g'); + return jsonStr.replace(regex, '"$1"'); +} + +JSON.parseBigInt = function (jsonStr: string, options?: { minDigits?: number; fallbackToString?: boolean;}): unknown { + const minDigits = options?.minDigits ?? 15; + const fallbackToString = options?.fallbackToString ?? true; + + const safeStr = quoteLargeNumbers(jsonStr, minDigits); + + return JSON.parse(safeStr, (_key, value) => { + if (typeof value === 'string') { + const bigintRegex = new RegExp(`^-?\\d{${minDigits},}$`); + if (bigintRegex.test(value)) { + try { + if (typeof BigInt !== 'undefined') return BigInt(value); + else if (fallbackToString) return value; + } catch { + return value; + } + } + } + return value; + }); +}; + export function isValidUri(uri: string) : boolean{ try { new URL(uri); diff --git a/src/components/GameAccordionItem.tsx b/src/components/GameAccordionItem.tsx new file mode 100644 index 0000000..33d5b5c --- /dev/null +++ b/src/components/GameAccordionItem.tsx @@ -0,0 +1,50 @@ +import type IGame from "../api/types/IGame.ts"; +import { + Accordion, + AccordionDetails, + AccordionSummary, + AspectRatio, + Card, + CardContent, + CardCover, + Stack, Typography +} from "@mui/joy"; +import type IPlayer from "../api/types/IPlayer.ts"; +import {useContext, useEffect, useState} from "react"; +import {GetPlayersOfGame} from "../api/endpoints/Data.tsx"; +import {ApiUriContext} from "../api/fetchApi.tsx"; + +export default function GameAccordionItem({game} : {game: IGame}) { + const apiUri = useContext(ApiUriContext); + + const [players, setPlayers] = useState([]); + const [expanded, setExpanded] = useState(false); + + useEffect(() => { + if(!expanded) + return; + GetPlayersOfGame(apiUri, game.appId).then(setPlayers); + }, [expanded]); + + return ( + setExpanded(expanded)}> + {game.name} + + + {players?.map((player) => + + + + + + + {player.name} + + + + )} + + + + ); +} \ No newline at end of file diff --git a/src/components/PlayerAccordionItem.tsx b/src/components/PlayerAccordionItem.tsx new file mode 100644 index 0000000..66acc3c --- /dev/null +++ b/src/components/PlayerAccordionItem.tsx @@ -0,0 +1,46 @@ +import type IGame from "../api/types/IGame.ts"; +import { + Accordion, + AccordionDetails, + AccordionSummary, + AspectRatio, + Card, + CardContent, + Stack, Typography +} from "@mui/joy"; +import type IPlayer from "../api/types/IPlayer.ts"; +import {useContext, useEffect, useState} from "react"; +import {ApiUriContext} from "../api/fetchApi.tsx"; +import {GetGamesOfPlayer} from "../api/endpoints/Data.tsx"; + +export default function PlayerAccordionItem({player} : {player: IPlayer}) { + const apiUri = useContext(ApiUriContext); + + const [games, setGames] = useState([]); + const [expanded, setExpanded] = useState(false); + + useEffect(() => { + if(!expanded) + return; + GetGamesOfPlayer(apiUri, player.steamId).then(setGames); + }, [expanded]); + + return ( + setExpanded(expanded)}> + {player.name} + + + {games?.map((game) => + + + + {game.name} + + + + )} + + + + ); +} \ No newline at end of file