Basic functionality
This commit is contained in:
49
src/App.tsx
49
src/App.tsx
@ -1,9 +1,54 @@
|
|||||||
import './App.css'
|
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() {
|
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 (
|
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
|
}else
|
||||||
throw new Error(response.statusText);
|
throw new Error(response.statusText);
|
||||||
}
|
}
|
||||||
let json = response.json();
|
return response.text().then(text => JSON.parseBigInt(text) as object);
|
||||||
return json.then((json) => json).catch(() => null);
|
|
||||||
})
|
})
|
||||||
.catch(function(err : Error){
|
.catch(function(err : Error){
|
||||||
console.error(`Error ${method}ing Data ${uri}\n${err}`);
|
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));
|
}).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{
|
export function isValidUri(uri: string) : boolean{
|
||||||
try {
|
try {
|
||||||
new URL(uri);
|
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