Basic functionality
This commit is contained in:
49
src/App.tsx
49
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<string>("http://localhost:5239");
|
||||
const [players, setPlayers] = useState<IPlayer[]>([]);
|
||||
const [games, setGames] = useState<IGame[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
GetPlayers(apiUri).then(setPlayers);
|
||||
GetGames(apiUri).then(setGames);
|
||||
}, [apiUri]);
|
||||
|
||||
return (
|
||||
<>
|
||||
</>
|
||||
<ApiUriContext value={apiUri}>
|
||||
<PlayersContext.Provider value={{players: players}}>
|
||||
<GamesContext value={{games: games}}>
|
||||
<Input type={"text"} placeholder={"Api Uri"} value={apiUri} onChange={(e) => setApiUri(e.target.value)} />
|
||||
<Stack direction={"row"} spacing={2}>
|
||||
<Box sx={{width:'50%'}}>
|
||||
<Typography level={"h2"}>Players</Typography>
|
||||
<AccordionGroup>
|
||||
{players?.map((player) => <PlayerAccordionItem key={player.steamId} player={player} />)}
|
||||
</AccordionGroup>
|
||||
</Box>
|
||||
<Divider />
|
||||
<Box sx={{width:'50%'}}>
|
||||
<Typography level={"h2"}>Games</Typography>
|
||||
<AccordionGroup>
|
||||
{games?.map((game) => <GameAccordionItem key={game.appId} game={game}/>)}
|
||||
</AccordionGroup>
|
||||
</Box>
|
||||
</Stack>
|
||||
</GamesContext>
|
||||
</PlayersContext.Provider>
|
||||
</ApiUriContext>
|
||||
)
|
||||
}
|
||||
|
8
src/api/contexts/GamesContext.tsx
Normal file
8
src/api/contexts/GamesContext.tsx
Normal file
@ -0,0 +1,8 @@
|
||||
import {createContext} from "react";
|
||||
import type IGame from "../types/IGame.ts";
|
||||
|
||||
export const GamesContext = createContext<{games: IGame[]}>(
|
||||
{
|
||||
games: []
|
||||
}
|
||||
);
|
8
src/api/contexts/PlayersContext.tsx
Normal file
8
src/api/contexts/PlayersContext.tsx
Normal file
@ -0,0 +1,8 @@
|
||||
import {createContext} from "react";
|
||||
import type IPlayer from "../types/IPlayer.ts";
|
||||
|
||||
export const PlayersContext = createContext<{players: IPlayer[]}>(
|
||||
{
|
||||
players: []
|
||||
}
|
||||
);
|
@ -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);
|
||||
|
50
src/components/GameAccordionItem.tsx
Normal file
50
src/components/GameAccordionItem.tsx
Normal file
@ -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<IPlayer[]>([]);
|
||||
const [expanded, setExpanded] = useState<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
if(!expanded)
|
||||
return;
|
||||
GetPlayersOfGame(apiUri, game.appId).then(setPlayers);
|
||||
}, [expanded]);
|
||||
|
||||
return (
|
||||
<Accordion key={game.appId} onChange={(_, expanded) => setExpanded(expanded)}>
|
||||
<AccordionSummary>{game.name}</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
<Stack flexWrap={"wrap"} direction={"row"} useFlexGap={true} spacing={1}>
|
||||
{players?.map((player) =>
|
||||
<AspectRatio sx={{width: "64px"}} ratio={1}>
|
||||
<Card key={player.steamId}>
|
||||
<CardCover>
|
||||
<img src={player.avatarUrl} />
|
||||
</CardCover>
|
||||
<CardContent>
|
||||
<Typography level={"body-lg"}>{player.name}</Typography>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</AspectRatio>
|
||||
)}
|
||||
</Stack>
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
);
|
||||
}
|
46
src/components/PlayerAccordionItem.tsx
Normal file
46
src/components/PlayerAccordionItem.tsx
Normal file
@ -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<IGame[]>([]);
|
||||
const [expanded, setExpanded] = useState<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
if(!expanded)
|
||||
return;
|
||||
GetGamesOfPlayer(apiUri, player.steamId).then(setGames);
|
||||
}, [expanded]);
|
||||
|
||||
return (
|
||||
<Accordion key={player.steamId} onChange={(_, expanded) => setExpanded(expanded)}>
|
||||
<AccordionSummary>{player.name}</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
<Stack flexWrap={"wrap"} direction={"row"} useFlexGap={true} spacing={1}>
|
||||
{games?.map((game) =>
|
||||
<AspectRatio sx={{width: "128px"}} ratio={2/3}>
|
||||
<Card key={game.appId}>
|
||||
<CardContent>
|
||||
<Typography level={"body-lg"}>{game.name}</Typography>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</AspectRatio>
|
||||
)}
|
||||
</Stack>
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
);
|
||||
}
|
Reference in New Issue
Block a user