Skip to content

Commit e70bbb6

Browse files
committed
Add RecentStatsTable component
1 parent d090977 commit e70bbb6

File tree

14 files changed

+180
-43
lines changed

14 files changed

+180
-43
lines changed

src/components/GameView/GameView.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import Card from "../Card";
22
import GlobalTotals from "../GlobalTotals";
3+
import GlobalTPS from "../GlobalTPS";
34
import HydraHeadLiveTxs from "../HydraHeadLiveTxs";
45
import Layout from "../Layout";
56
import RestartButton from "../RestartButton";
@@ -30,7 +31,13 @@ const GameView = () => {
3031
<HydraHeadLiveTxs />
3132
</div>
3233
<Card className="h-[40rem]">dsad</Card>
33-
<div className="w-80">dsad</div>
34+
<div className="w-80 flex flex-col gap-4">
35+
<GlobalTPS size="sm" titleAlign="left" />
36+
<Card className="text-white py-3 px-4 text-sm leading-3">
37+
A comparison between the throughput from your game session (bottom)
38+
and all the Hydra heads in aggregate (top).
39+
</Card>
40+
</div>
3441
</div>
3542
</Layout>
3643
);

src/components/GlobalTPS/GlobalTPS.tsx

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,47 @@
1+
import { FC } from "react";
2+
import { GLOBAL_MAX_SPEED } from "../../constants";
13
import Card from "../Card";
24
import Speedometer from "../Speedometer";
5+
import cx from "classnames";
6+
import { useAppContext } from "../../context/useAppContext";
7+
8+
export interface GlobalTPSProps {
9+
size?: "sm" | "md" | "lg";
10+
titleAlign?: "left" | "center" | "right";
11+
}
12+
13+
const GlobalTPS: FC<GlobalTPSProps> = ({
14+
size = "md",
15+
titleAlign = "center",
16+
}) => {
17+
const { globalQuery } = useAppContext();
18+
const transactions = globalQuery?.data?.transactions ?? 0;
319

4-
const GlobalTPS = () => {
520
return (
6-
<Card glass className="py-4 px-6 flex flex-col items-center">
7-
<h1 className="mb-4 text-white text-2xl uppercase text-center">
21+
<div>
22+
<h1
23+
className={cx("text-white uppercase w-full", {
24+
"text-lg mb-1": size === "sm",
25+
"text-xl mb-2": size === "md",
26+
"text-2xl mb-3": size === "lg",
27+
"text-left": titleAlign === "left",
28+
"text-center": titleAlign === "center",
29+
"text-right": titleAlign === "right",
30+
})}
31+
>
832
Global TPS
933
</h1>
10-
<Speedometer />
11-
</Card>
34+
<Card
35+
glass
36+
className={cx("flex flex-col items-center", {
37+
"pt-3 pb-1 px-4": size === "sm",
38+
"pt-3 pb-1 px-5": size === "md",
39+
"pt-4 pb-2 px-6": size === "lg",
40+
})}
41+
>
42+
<Speedometer maxSpeed={GLOBAL_MAX_SPEED} transactions={transactions} />
43+
</Card>
44+
</div>
1245
);
1346
};
1447

src/components/InitialView/InitialView.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,9 @@ const InitialView: FC<InitialViewProps> = ({ startGame }) => {
3232
Play Doom on Hydra
3333
</Button>
3434
<div className="grid grid-cols-2 max-w-6xl w-full mt-32 gap-8 py-6">
35-
<div className="flex flex-col gap-2">
35+
<div className="flex flex-col gap-4">
3636
<GlobalTotals size="lg" />
37-
<GlobalTPS />
37+
<GlobalTPS size="lg" />
3838
</div>
3939
<div>
4040
<GlobalLeaderBoard />

src/components/Leaderboard/Leaderboard.tsx

Lines changed: 16 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { useAppContext } from "../../context/useAppContext";
22
import LeaderboardTable from "../LeaderboardTable";
3-
import Table from "../Table";
3+
import RecentStatsTable from "../RecentStatsTable";
44
import Tabs from "../Tabs";
55

66
const Leaderboard = () => {
@@ -9,36 +9,32 @@ const Leaderboard = () => {
99
const items = globalQuery?.data?.items_leaderboard;
1010
const secrets = globalQuery?.data?.secrets_leaderboard;
1111

12+
const recentKills = globalQuery?.data?.kills;
13+
const recentItems = globalQuery?.data?.items;
14+
const recentSecrets = globalQuery?.data?.secrets;
15+
1216
const allTimeData = [
1317
{ title: "Kills", data: kills },
1418
{ title: "Items", data: items },
1519
{ title: "Secrets", data: secrets },
1620
];
1721

22+
const recentData = [
23+
{ title: "Kills", data: recentKills },
24+
{ title: "Items", data: recentItems },
25+
{ title: "Secrets", data: recentSecrets },
26+
];
27+
1828
const tabs = [
1929
{
2030
id: 0,
2131
title: "Recent",
2232
content: (
2333
<div className="grid grid-cols-3 gap-6 pt-6">
24-
{[1, 2, 3].map((i) => (
25-
<div key={i}>
26-
<h2 className="text-2xl text-center mb-6">Kills</h2>
27-
<Table
28-
columns={[{ name: "player" }, { name: "score" }]}
29-
data={[
30-
{ player: "Player 1", score: "1000" },
31-
{ player: "Player 2", score: "900" },
32-
{ player: "Player 3", score: "800" },
33-
{ player: "Player 4", score: "700" },
34-
{ player: "Player 5", score: "600" },
35-
{ player: "Player 6", score: "500" },
36-
{ player: "Player 7", score: "400" },
37-
{ player: "Player 8", score: "300" },
38-
{ player: "Player 9", score: "200" },
39-
{ player: "Player 10", score: "100" },
40-
]}
41-
/>
34+
{recentData.map(({ title, data }) => (
35+
<div key={title}>
36+
<h2 className="text-2xl text-center mb-6">{title}</h2>
37+
{data && <RecentStatsTable data={data} />}
4238
</div>
4339
))}
4440
</div>
@@ -61,7 +57,7 @@ const Leaderboard = () => {
6157
];
6258

6359
return (
64-
<div className="text-center flex flex-col gap-10">
60+
<div className="text-center flex flex-col gap-10 min-w-[64rem]">
6561
<h1 className="text-5xl">Leaderboard</h1>
6662
<Tabs tabs={tabs} />
6763
</div>

src/components/Logos/Logos.tsx

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import iogLogo from "../../assets/images/iog-logo.png";
33
import onboardLogo from "../../assets/images/onboard-logo.png";
44
import popupLogo from "../../assets/images/popup-logo.png";
55
import sundaeLogo from "../../assets/images/sundae-labs-logo.svg";
6+
import { CABINET_KEY } from "../../constants";
67

78
const Logos = () => {
89
return (
@@ -20,12 +21,16 @@ const Logos = () => {
2021
>
2122
<img className="w-14" src={iogLogo} alt="IOG" />
2223
</a>
23-
<a target="_blank" href="http://onboard.ninja">
24-
<img className="w-14" src={onboardLogo} alt="Onboard" />
25-
</a>
26-
<a target="_blank" href="https://x.com/PopupVirtualEnt">
27-
<img className="w-24" src={popupLogo} alt="Popup" />
28-
</a>
24+
{CABINET_KEY && (
25+
<>
26+
<a target="_blank" href="http://onboard.ninja">
27+
<img className="w-14" src={onboardLogo} alt="Onboard" />
28+
</a>
29+
<a target="_blank" href="https://x.com/PopupVirtualEnt">
30+
<img className="w-24" src={popupLogo} alt="Popup" />
31+
</a>
32+
</>
33+
)}
2934
</div>
3035
);
3136
};
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { FC, useCallback, useEffect, useState } from "react";
2+
import Table, { TableData } from "../Table/Table";
3+
import usePlayersHandle from "../../hooks/usePlayersHandle";
4+
import { getTableData } from "../../utils/leaderboard";
5+
import { PlayerStats } from "../../types";
6+
7+
interface RecentStatsTableProps {
8+
data: PlayerStats;
9+
}
10+
11+
const RecentStatsTable: FC<RecentStatsTableProps> = ({ data }) => {
12+
const [tableData, setTableData] = useState<TableData[]>([]);
13+
const { fetchPlayerHandles } = usePlayersHandle();
14+
15+
const updateTableData = useCallback(async () => {
16+
try {
17+
const players = Object.keys(data);
18+
const handles = await fetchPlayerHandles(players);
19+
20+
setTableData(getTableData(data, handles));
21+
} catch (error) {
22+
console.error("Failed to fetch player handles:", error);
23+
}
24+
}, [data, fetchPlayerHandles]);
25+
26+
useEffect(() => {
27+
updateTableData();
28+
}, [updateTableData]);
29+
30+
if (!tableData.length) return null;
31+
32+
return (
33+
<Table columns={[{ name: "player" }, { name: "score" }]} data={tableData} />
34+
);
35+
};
36+
37+
export default RecentStatsTable;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { default } from "./RecentStatsTable";

src/components/Speedometer/Speedometer.tsx

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,62 @@
11
import speedometer from "../../assets/images/speedometer.png";
22
import speedometerTick from "../../assets/images/speedometer-tick.png";
3+
import { FC, useEffect, useMemo, useState } from "react";
4+
import { mapRange } from "../../utils/speedometer";
5+
6+
interface SpeedometerProps {
7+
maxSpeed: number;
8+
transactions: number;
9+
}
10+
11+
const Speedometer: FC<SpeedometerProps> = ({ maxSpeed, transactions }) => {
12+
const [recentQueries, setRecentQueries] = useState<
13+
{ timestamp: number; transactions: number }[]
14+
>([]);
15+
const [value, setValue] = useState(0);
16+
17+
useEffect(() => {
18+
if (
19+
transactions &&
20+
transactions !== recentQueries[recentQueries.length - 1]?.transactions
21+
) {
22+
let newRecentQueries;
23+
const item = {
24+
timestamp: performance.now(),
25+
transactions: transactions,
26+
};
27+
if (recentQueries.length === 5) {
28+
newRecentQueries = [...recentQueries.slice(1), item];
29+
const last = recentQueries[0];
30+
const difference = transactions - last.transactions;
31+
const timeDifference = (performance.now() - last.timestamp) / 1000;
32+
setValue(Math.round(difference / timeDifference));
33+
} else {
34+
newRecentQueries = [...recentQueries, item];
35+
}
36+
37+
setRecentQueries(newRecentQueries);
38+
}
39+
}, [recentQueries, transactions]);
40+
41+
const degree = useMemo(
42+
() => mapRange(value, 0, maxSpeed, 0, 180),
43+
[maxSpeed, value],
44+
);
345

4-
const Speedometer = () => {
546
return (
647
<div className="w-max text-white">
748
<div className="relative mb-3">
8-
<div className="absolute -bottom-1 left-9">0</div>
49+
<div className="absolute -bottom-1 left-9">{value}</div>
950
<img src={speedometer} alt="Speedometer" className="w-72" />
1051
<img
1152
alt="Speedometer tick"
1253
className="absolute w-28 bottom-0 left-14"
1354
src={speedometerTick}
55+
style={{ transform: `rotate(${degree}deg)` }}
1456
/>
15-
<div className="absolute -bottom-1 right-9">3000</div>
57+
<div className="absolute -bottom-1 right-9">
58+
{Math.max(maxSpeed, value)}
59+
</div>
1660
</div>
1761
<div className="text-center">0</div>
1862
</div>

src/components/StatsCard/StatsCard.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ const StatsCard: FC<StatsCardProps> = ({
2828
className={cx("text-white uppercase", {
2929
"text-lg mb-1": size === "sm",
3030
"text-xl mb-2": size === "md",
31-
"text-2xl mb-4": size === "lg",
31+
"text-2xl mb-3": size === "lg",
3232
"text-left": titleAlign === "left",
3333
"text-center": titleAlign === "center",
3434
"text-right": titleAlign === "right",

src/components/TopLinks/TopLinks.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ const TopLinks = () => {
6767

6868
return (
6969
<div>
70-
<ul className="py-16 text-xl text-white font-['Pixelify_Sans'] uppercase flex gap-20">
70+
<ul className="py-16 text-xl text-white font-['Pixelify_Sans'] uppercase flex gap-20 mb-20">
7171
{links.map(({ title, content }) => (
7272
<li key={title}>
7373
<button

0 commit comments

Comments
 (0)