From 990013af2b15e3f1bab53b28196c101b683018ea Mon Sep 17 00:00:00 2001 From: Katalina Okano Date: Sun, 18 Jun 2023 01:00:26 -0400 Subject: [PATCH] first pass of new server page --- app/components/alert-timer.css.ts | 23 ++++++ app/components/alert-timer.tsx | 51 +++++++++++++ app/components/faction-pie.css.ts | 19 +++++ app/components/faction-pie.tsx | 40 +++++++++++ app/components/index-world.tsx | 46 +----------- app/components/world.css.ts | 92 ++++++++++++++++++++++++ app/routes/debug.components.tsx | 72 +++++++++++++++++++ app/routes/worlds.$id.tsx | 114 ++++++++++++++++++++++++++---- app/utils/sorting.ts | 43 +++++++++++ 9 files changed, 443 insertions(+), 57 deletions(-) create mode 100644 app/components/alert-timer.css.ts create mode 100644 app/components/alert-timer.tsx create mode 100644 app/components/faction-pie.css.ts create mode 100644 app/components/faction-pie.tsx create mode 100644 app/components/world.css.ts create mode 100644 app/routes/debug.components.tsx create mode 100644 app/utils/sorting.ts diff --git a/app/components/alert-timer.css.ts b/app/components/alert-timer.css.ts new file mode 100644 index 0000000..7803a2e --- /dev/null +++ b/app/components/alert-timer.css.ts @@ -0,0 +1,23 @@ +import { keyframes, style } from "@vanilla-extract/css"; + +const alertDotBlink = keyframes({ + from: { + backgroundColor: "#ff2d2d", + }, + to: { + backgroundColor: "#662929", + }, +}); + +export const alertDot = style({ + display: "inline-block", + height: "0.5rem", + width: "0.5rem", + borderRadius: "50%", + background: "#ff2d2d", + animation: `${alertDotBlink} var(--speed) ease-in-out infinite alternate`, +}); + +export const timer = style({ + fontSize: "0.8rem", +}); diff --git a/app/components/alert-timer.tsx b/app/components/alert-timer.tsx new file mode 100644 index 0000000..da5cd5e --- /dev/null +++ b/app/components/alert-timer.tsx @@ -0,0 +1,51 @@ +import { useEffect, useState } from "react"; +import { MetagameWorld } from "~/utils/metagame"; +import { humanTimeAgo } from "~/utils/strings"; +import * as styles from "./alert-timer.css"; + +const endTime = (alert: Required["alert"]) => { + const alertDurationMins = alert.alert_type !== "sudden_death" ? 90 : 15; + return new Date(alert.start_time).getTime() + alertDurationMins * 60 * 1000; +}; + +const timeLeftString = (alert: MetagameWorld["zones"][0]["alert"]) => { + if (alert) { + const time = endTime(alert) - Date.now(); + if (time < 2000) { + return <>JUST ENDED; + } + + const speed = time < 1000 * 60 * 15 ? "1s" : "4s"; + + return ( + <> + {humanTimeAgo(time, true).toUpperCase()} LEFT{" "} +
+ + ); + } else { + return <>; + } +}; + +export const AlertTimer = ({ + alert, +}: { + alert: MetagameWorld["zones"][0]["alert"]; +}) => { + const [timeLeft, setTimeLeft] = useState(timeLeftString(alert)); + + useEffect(() => { + if (alert) { + const interval = setInterval(() => { + setTimeLeft(timeLeftString(alert)); + }, 1000); + return () => clearInterval(interval); + } + }, [alert]); + + return
{timeLeft}
; +}; diff --git a/app/components/faction-pie.css.ts b/app/components/faction-pie.css.ts new file mode 100644 index 0000000..4dc2a88 --- /dev/null +++ b/app/components/faction-pie.css.ts @@ -0,0 +1,19 @@ +import { style } from "@vanilla-extract/css"; + +export const pieRoot = style({ + width: "1em", + height: "1em", + borderRadius: "50%", + position: "relative", + + "::after": { + content: "''", + position: "absolute", + top: "var(--inner-margin)", + left: "var(--inner-margin)", + right: "var(--inner-margin)", + bottom: "var(--inner-margin)", + borderRadius: "50%", + background: "var(--inner-bg)", + }, +}); diff --git a/app/components/faction-pie.tsx b/app/components/faction-pie.tsx new file mode 100644 index 0000000..2d9ed32 --- /dev/null +++ b/app/components/faction-pie.tsx @@ -0,0 +1,40 @@ +import type { Population } from "~/utils/saerro"; +import { pieRoot } from "./faction-pie.css"; + +export const FactionPie = ({ + population, + innerMargin, + innerBackground, + size, +}: { + population: Population; + innerMargin?: number; + innerBackground?: string; + size?: string; +}) => { + const { nc, tr, vs } = population; + const total = nc + tr + vs; + + const trPct = (tr / total) * 100; + const vsPct = (vs / total) * 100; + + return ( +
+   +
+ ); +}; diff --git a/app/components/index-world.tsx b/app/components/index-world.tsx index df34c60..1c4ba4b 100644 --- a/app/components/index-world.tsx +++ b/app/components/index-world.tsx @@ -14,6 +14,7 @@ import type { MetagameWorld } from "~/utils/metagame"; import type { PopulationWorld } from "~/utils/population"; import { c } from "~/utils/classes"; import { useEffect, useState } from "react"; +import { AlertTimer } from "./alert-timer"; export type IndexWorldProps = { metagame: MetagameWorld; @@ -131,11 +132,6 @@ const JaegerContinent = ({ zone }: { zone: MetagameWorld["zones"][0] }) => { ); }; -const endTime = (alert: Required["alert"]) => { - const alertDurationMins = alert.alert_type !== "sudden_death" ? 90 : 15; - return new Date(alert.start_time).getTime() + alertDurationMins * 60 * 1000; -}; - const Continent = ({ zone }: { zone: MetagameWorld["zones"][0] }) => { const { name, @@ -171,7 +167,7 @@ const Continent = ({ zone }: { zone: MetagameWorld["zones"][0] }) => { ALERT PROGRESS {" "}
- {" "} + {" "}
@@ -182,41 +178,3 @@ const Continent = ({ zone }: { zone: MetagameWorld["zones"][0] }) => { ); }; - -const timeLeftString = (alert: MetagameWorld["zones"][0]["alert"]) => { - if (alert) { - const time = endTime(alert) - Date.now(); - if (time < 2000) { - return <>JUST ENDED; - } - - const speed = time < 1000 * 60 * 15 ? "1s" : "4s"; - - return ( - <> - {humanTimeAgo(time, true).toUpperCase()} LEFT{" "} -
- - ); - } else { - return <>; - } -}; - -const TimeLeft = ({ alert }: { alert: MetagameWorld["zones"][0]["alert"] }) => { - const [timeLeft, setTimeLeft] = useState(timeLeftString(alert)); - - useEffect(() => { - if (alert) { - const interval = setInterval(() => { - setTimeLeft(timeLeftString(alert)); - }, 1000); - return () => clearInterval(interval); - } - }, [alert]); - - return <>{timeLeft}; -}; diff --git a/app/components/world.css.ts b/app/components/world.css.ts new file mode 100644 index 0000000..edd9208 --- /dev/null +++ b/app/components/world.css.ts @@ -0,0 +1,92 @@ +import { style } from "@vanilla-extract/css"; + +export const headerFont = style({ + fontFamily: "Unbounded, Impact, monospace", + fontWeight: "bold", +}); + +export const header = style({ + backgroundColor: "#222", + padding: "2em", + display: "flex", + flexWrap: "wrap", + fontFamily: "Unbounded, Impact, monospace", +}); + +export const headerName = style({ + fontSize: "4rem", + display: "flex", + alignItems: "center", + flexBasis: "100%", +}); + +export const headerSub = style({ + fontSize: "1rem", + color: "#ccc", + marginLeft: "1em", +}); + +export const outer = style({ + display: "flex", + justifyContent: "center", + minHeight: "100vh", + flexDirection: "column", + maxWidth: "1920px", +}); + +export const population = style({ + display: "flex", + flexWrap: "wrap", + width: "100%", + flexDirection: "column", + justifyContent: "space-evenly", +}); +export const populationHead = style({ + display: "flex", + alignItems: "center", + justifyContent: "space-evenly", + flexBasis: "100%", +}); + +export const popNumbers = style({ + display: "flex", + justifyContent: "space-evenly", +}); + +export const popItem = style({ + display: "flex", + alignItems: "center", + justifyContent: "center", +}); + +export const totalPop = style({ + fontSize: "2rem", + display: "block", + width: "4em", +}); + +export const headerConts = style({ + display: "flex", + alignItems: "center", + justifyContent: "space-evenly", + flexBasis: "100%", +}); + +export const contChart = style({ + fontSize: "4rem", +}); + +export const cont = style({ + display: "flex", + alignItems: "center", + justifyContent: "center", + flexDirection: "column", +}); + +export const contSub = style({ + width: "10rem", + fontSize: "0.8rem", + textAlign: "center", + color: "#ccc", + paddingTop: "0.5rem", +}); diff --git a/app/routes/debug.components.tsx b/app/routes/debug.components.tsx new file mode 100644 index 0000000..5d7b186 --- /dev/null +++ b/app/routes/debug.components.tsx @@ -0,0 +1,72 @@ +import { useState } from "react"; +import { FactionBar } from "~/components/faction-bar"; +import { FactionPie } from "~/components/faction-pie"; +import type { Population } from "~/utils/saerro"; + +export default function DebugComponents() { + const [population, setPopulation] = useState({ + nc: 33, + tr: 33, + vs: 33, + }); + + const [innerMargin, setInnerMargin] = useState(10); + const [innerColor, setInnerColor] = useState("black"); + return ( +
+

Debug Components

+

Faction Viz

+
+ NC{" "} + + setPopulation((p) => ({ ...p, nc: Number(e.target.value) })) + } + />{" "} + || TR{" "} + + setPopulation((p) => ({ ...p, tr: Number(e.target.value) })) + } + />{" "} + || VS{" "} + + setPopulation((p) => ({ ...p, vs: Number(e.target.value) })) + } + /> +
+
+

Horizontal Stacked Bar Chart

+ +

Pie Chart

+
+ + +
+ Inner margin{" "} + setInnerMargin(Number(e.target.value))} + /> + Inner color{" "} + setInnerColor(e.target.value)} + /> +
+
+ ); +} diff --git a/app/routes/worlds.$id.tsx b/app/routes/worlds.$id.tsx index 83d1955..f2b59c4 100644 --- a/app/routes/worlds.$id.tsx +++ b/app/routes/worlds.$id.tsx @@ -17,6 +17,16 @@ import { worlds, zones, } from "~/utils/strings"; +import * as styles from "~/components/world.css"; +import { c } from "~/utils/classes"; +import { FactionBar } from "~/components/faction-bar"; +import { popImage } from "~/components/index-world.css"; +import vsLogo from "~/images/vs-100.png"; +import ncLogo from "~/images/nc-100.png"; +import trLogo from "~/images/tr-100.png"; +import { FactionPie } from "~/components/faction-pie"; +import { AlertTimer } from "~/components/alert-timer"; +import { contPrioritySort } from "~/utils/sorting"; type LoaderData = { saerro: WorldResponse; @@ -66,26 +76,104 @@ export default function World() { const { saerro: { world }, id, + metagame, } = useLoaderData(); const worldInfo = worlds[String(id || "default")]; + const nextZoneID = metagame.zones.sort( + (a, b) => + new Date(a.locked_since ?? Date.now()).getTime() - + new Date(b.locked_since ?? Date.now()).getTime() + )[0].id; return ( -
-

{worldInfo.name}

-

Total Population

-

- {totalPopulation(world.population)} players ({world.population.vs} VS,{" "} - {world.population.nc} NC, {world.population.tr} TR) -

-
-

Continents

- {world.zones.all.map((zone) => ( - - ))} + <> +
+
+
+
+
{worldInfo.name.toUpperCase()}
+
+ [{worldInfo.location}] [{worldInfo.platform}] +
+
+
+
+
+ {totalPopulation(world.population).toLocaleString()} +
+ PLAYERS +
+
+
+
+ VS{" "} + {world.population.vs} +
+
+ NC{" "} + {world.population.nc} +
+
+ TR{" "} + {world.population.tr} +
+
+ +
+
+
+
CONTINENT CONTROL
+ {metagame.zones.sort(contPrioritySort).map((zone, idx) => { + const zoneInfo = zones[String(zone.id)]; + return ( +
+
{zoneInfo.name.toUpperCase()}
+
+ +
+
+ {zone.alert ? ( + + ) : zone.locked ? ( + nextZoneID == zone.id ? ( + <>NEXT UP ยป + ) : ( + <>LOCKED + ) + ) : ( + <>UNLOCKED + )} +
+
+ ); + })} +
+
+
+

Continents

+ {world.zones.all.map((zone) => ( + + ))} +
+
+ ); } diff --git a/app/utils/sorting.ts b/app/utils/sorting.ts new file mode 100644 index 0000000..89d2318 --- /dev/null +++ b/app/utils/sorting.ts @@ -0,0 +1,43 @@ +import type { MetagameWorld } from "./metagame"; + +export const contPrioritySort = ( + a: MetagameWorld["zones"][number], + b: MetagameWorld["zones"][number] +) => { + // Sort priority: + // 1. oldest alert + // 2. unlocked by id + // 3. oldest locked since + + if (a.locked && !b.locked) { + return 1; + } else if (!a.locked && b.locked) { + return -1; + } + + if (a.alert && b.alert) { + return Date.parse(a.alert.start_time) - Date.parse(b.alert.start_time); + } + + if (a.alert) { + return -1; + } + + if (b.alert) { + return 1; + } + + if (a.locked_since && b.locked_since) { + return Date.parse(a.locked_since) - Date.parse(b.locked_since); + } + + if (a.locked_since) { + return -1; + } + + if (b.locked_since) { + return 1; + } + + return 0; +};