initial index
This commit is contained in:
parent
62cc828d6a
commit
88015a98cd
21 changed files with 343 additions and 56 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -7,4 +7,4 @@ node_modules
|
||||||
.env
|
.env
|
||||||
|
|
||||||
.DS_Store
|
.DS_Store
|
||||||
*/.DS_Store
|
**/.DS_Store
|
||||||
|
|
30
app/components/faction-bar.css.ts
Normal file
30
app/components/faction-bar.css.ts
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
import type { ComplexStyleRule } from "@vanilla-extract/css";
|
||||||
|
import { style } from "@vanilla-extract/css";
|
||||||
|
|
||||||
|
export const bar = style({
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
flexDirection: "row",
|
||||||
|
overflow: "hidden",
|
||||||
|
borderRadius: "0.4rem",
|
||||||
|
border: "2px solid #2d2d2d",
|
||||||
|
});
|
||||||
|
|
||||||
|
const shared: ComplexStyleRule = {
|
||||||
|
textAlign: "center",
|
||||||
|
};
|
||||||
|
|
||||||
|
export const left = style({
|
||||||
|
...shared,
|
||||||
|
backgroundColor: "#991cba",
|
||||||
|
});
|
||||||
|
export const center = style({
|
||||||
|
...shared,
|
||||||
|
backgroundColor: "#1564cc",
|
||||||
|
borderBottom: "1px solid #2d2d2d",
|
||||||
|
});
|
||||||
|
export const right = style({
|
||||||
|
...shared,
|
||||||
|
backgroundColor: "#d30101",
|
||||||
|
});
|
32
app/components/faction-bar.tsx
Normal file
32
app/components/faction-bar.tsx
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
import { useMemo } from "react";
|
||||||
|
import type { Population } from "~/utils/saerro";
|
||||||
|
import { totalPopulation } from "~/utils/saerro";
|
||||||
|
import * as styles from "./faction-bar.css";
|
||||||
|
|
||||||
|
export const FactionBar = ({
|
||||||
|
population: { vs, nc, tr },
|
||||||
|
}: {
|
||||||
|
population: Population;
|
||||||
|
}) => {
|
||||||
|
const { vsPercent, ncPercent, trPercent } = useMemo(() => {
|
||||||
|
const total = totalPopulation({ vs, nc, tr, total: 0 });
|
||||||
|
return {
|
||||||
|
vsPercent: Math.floor((vs / total) * 100) || 0,
|
||||||
|
ncPercent: Math.floor((nc / total) * 100) || 0,
|
||||||
|
trPercent: Math.floor((tr / total) * 100) || 0,
|
||||||
|
};
|
||||||
|
}, [vs, nc, tr]);
|
||||||
|
return (
|
||||||
|
<div className={styles.bar}>
|
||||||
|
<div className={styles.left} style={{ flexGrow: vs + 1 }}>
|
||||||
|
{vsPercent}%
|
||||||
|
</div>
|
||||||
|
<div className={styles.center} style={{ flexGrow: nc + 1 }}>
|
||||||
|
{ncPercent}%
|
||||||
|
</div>
|
||||||
|
<div className={styles.right} style={{ flexGrow: tr + 1 }}>
|
||||||
|
{trPercent}%
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
8
app/components/index-world-container.css.ts
Normal file
8
app/components/index-world-container.css.ts
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
import { style } from "@vanilla-extract/css";
|
||||||
|
|
||||||
|
export const container = style({
|
||||||
|
display: "flex",
|
||||||
|
flexBasis: "100%",
|
||||||
|
flexWrap: "wrap",
|
||||||
|
justifyContent: "center",
|
||||||
|
});
|
22
app/components/index-world-container.tsx
Normal file
22
app/components/index-world-container.tsx
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import { useMemo } from "react";
|
||||||
|
import type { Health, World } from "~/utils/saerro";
|
||||||
|
import { IndexWorld } from "./index-world";
|
||||||
|
import * as styles from "./index-world-container.css";
|
||||||
|
|
||||||
|
export const WorldContainer = ({
|
||||||
|
worlds,
|
||||||
|
health,
|
||||||
|
}: {
|
||||||
|
worlds: World[];
|
||||||
|
health: Health;
|
||||||
|
}) => (
|
||||||
|
<div className={styles.container}>
|
||||||
|
{worlds.map((world) => (
|
||||||
|
<IndexWorld
|
||||||
|
key={world.id}
|
||||||
|
world={world}
|
||||||
|
health={health.worlds.find((w) => world.name.toLowerCase() === w.name)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
70
app/components/index-world.css.ts
Normal file
70
app/components/index-world.css.ts
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
import { style } from "@vanilla-extract/css";
|
||||||
|
|
||||||
|
export const container = style({
|
||||||
|
background: "#333",
|
||||||
|
flexBasis: "30%",
|
||||||
|
margin: "0.5rem",
|
||||||
|
});
|
||||||
|
|
||||||
|
export const header = style({
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
color: "inherit",
|
||||||
|
textDecoration: "none",
|
||||||
|
transition: "background-color 0.2s ease-in-out",
|
||||||
|
backgroundColor: "#222",
|
||||||
|
":hover": {
|
||||||
|
backgroundColor: "#383838",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
export const headerName = style({
|
||||||
|
padding: "0.5rem",
|
||||||
|
fontSize: "1.5rem",
|
||||||
|
});
|
||||||
|
export const headerDetailsLink = style({
|
||||||
|
fontVariant: "small-caps",
|
||||||
|
fontSize: "0.8rem",
|
||||||
|
color: "#aaa",
|
||||||
|
paddingRight: "0.5rem",
|
||||||
|
});
|
||||||
|
export const headerMarkers = style({
|
||||||
|
fontSize: "0.8rem",
|
||||||
|
flex: 1,
|
||||||
|
fontWeight: "bold",
|
||||||
|
color: "#aaa",
|
||||||
|
});
|
||||||
|
|
||||||
|
export const circle = style({
|
||||||
|
display: "inline-block",
|
||||||
|
width: "0.4rem",
|
||||||
|
height: "0.4rem",
|
||||||
|
borderRadius: "50%",
|
||||||
|
marginLeft: "0.2rem",
|
||||||
|
});
|
||||||
|
|
||||||
|
export const details = style({
|
||||||
|
padding: "0.5rem",
|
||||||
|
});
|
||||||
|
|
||||||
|
export const population = style({
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "space-evenly",
|
||||||
|
});
|
||||||
|
|
||||||
|
export const popFaction = style({
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "row",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
});
|
||||||
|
|
||||||
|
export const popImage = style({
|
||||||
|
height: "1.5rem",
|
||||||
|
marginRight: "0.5rem",
|
||||||
|
});
|
||||||
|
|
||||||
|
export const totalPop = style({
|
||||||
|
fontWeight: "bold",
|
||||||
|
fontSize: "1.2rem",
|
||||||
|
});
|
|
@ -1,18 +1,61 @@
|
||||||
import { World } from "~/utils/saerro";
|
import { Link } from "@remix-run/react";
|
||||||
|
import { Health, totalPopulation, World } from "~/utils/saerro";
|
||||||
|
import { humanTimeAgo } from "~/utils/strings";
|
||||||
|
import { worlds } from "~/utils/worlds";
|
||||||
|
import * as styles from "./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 { FactionBar } from "./faction-bar";
|
||||||
|
|
||||||
export type IndexWorldProps = {
|
export type IndexWorldProps = {
|
||||||
world: World;
|
world: World;
|
||||||
|
health?: Health["worlds"][number];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const IndexWorld = ({ world }: IndexWorldProps) => {
|
export const IndexWorld = ({ world, health }: IndexWorldProps) => {
|
||||||
|
const { platform, location } = worlds[String(world.id || "default")];
|
||||||
|
|
||||||
|
const timeSinceLastEvent = humanTimeAgo(
|
||||||
|
new Date().getTime() - new Date(health?.lastEvent || 0).getTime()
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className={styles.container}>
|
||||||
<h1>
|
<Link to={`/worlds/${world.id}`} className={styles.header}>
|
||||||
{world.name} (total: {world.population.total})
|
<div className={styles.headerName}>{world.name}</div>
|
||||||
</h1>
|
<div className={styles.headerMarkers}>
|
||||||
<p>VS: {world.population.vs}</p>
|
[{location}] [{platform}]{" "}
|
||||||
<p>NC: {world.population.nc}</p>
|
<div
|
||||||
<p>TR: {world.population.tr}</p>
|
className={styles.circle}
|
||||||
|
style={{
|
||||||
|
backgroundColor: health?.status === "UP" ? "limegreen" : "red",
|
||||||
|
}}
|
||||||
|
title={`Status: ${health?.status} || Last event: ${timeSinceLastEvent}`}
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
<div className={styles.headerDetailsLink}>DETAILS ⇨</div>
|
||||||
|
</Link>
|
||||||
|
<div className={styles.details}>
|
||||||
|
<div className={styles.population}>
|
||||||
|
<div className={styles.totalPop}>
|
||||||
|
{totalPopulation(world.population)}
|
||||||
|
</div>
|
||||||
|
<div className={styles.popFaction}>
|
||||||
|
<img className={styles.popImage} src={vsLogo} alt="VS" />{" "}
|
||||||
|
{world.population.vs}
|
||||||
|
</div>
|
||||||
|
<div className={styles.popFaction}>
|
||||||
|
<img className={styles.popImage} src={ncLogo} alt="NC" />{" "}
|
||||||
|
{world.population.nc}
|
||||||
|
</div>
|
||||||
|
<div className={styles.popFaction}>
|
||||||
|
<img className={styles.popImage} src={trLogo} alt="TR" />{" "}
|
||||||
|
{world.population.tr}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<FactionBar population={world.population} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
BIN
app/images/nc-100.png
Normal file
BIN
app/images/nc-100.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
BIN
app/images/nc.png
Normal file
BIN
app/images/nc.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 796 KiB |
BIN
app/images/tr-100.png
Normal file
BIN
app/images/tr-100.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.3 KiB |
BIN
app/images/tr.png
Normal file
BIN
app/images/tr.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 457 KiB |
BIN
app/images/vs-100.png
Normal file
BIN
app/images/vs-100.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
BIN
app/images/vs.png
Normal file
BIN
app/images/vs.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.5 MiB |
7
app/root.css.ts
Normal file
7
app/root.css.ts
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
import { style } from "@vanilla-extract/css";
|
||||||
|
|
||||||
|
export const root = style({
|
||||||
|
fontFamily: "Arial, Helvetica, sans-serif",
|
||||||
|
background: "#101010",
|
||||||
|
color: "#efefef",
|
||||||
|
});
|
|
@ -8,8 +8,13 @@ import {
|
||||||
Scripts,
|
Scripts,
|
||||||
ScrollRestoration,
|
ScrollRestoration,
|
||||||
} from "@remix-run/react";
|
} from "@remix-run/react";
|
||||||
|
import * as styles from "./root.css";
|
||||||
|
|
||||||
export const links: LinksFunction = () => [
|
export const links: LinksFunction = () => [
|
||||||
|
{
|
||||||
|
rel: "stylesheet",
|
||||||
|
href: "https://unpkg.com/modern-css-reset@1.4.0/dist/reset.min.css",
|
||||||
|
},
|
||||||
...(cssBundleHref ? [{ rel: "stylesheet", href: cssBundleHref }] : []),
|
...(cssBundleHref ? [{ rel: "stylesheet", href: cssBundleHref }] : []),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -22,7 +27,7 @@ export default function App() {
|
||||||
<Meta />
|
<Meta />
|
||||||
<Links />
|
<Links />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body className={styles.root}>
|
||||||
<Outlet />
|
<Outlet />
|
||||||
<ScrollRestoration />
|
<ScrollRestoration />
|
||||||
<Scripts />
|
<Scripts />
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { json, type V2_MetaFunction } from "@remix-run/cloudflare";
|
import { json, type V2_MetaFunction } from "@remix-run/cloudflare";
|
||||||
import { useLoaderData } from "@remix-run/react";
|
import { useLoaderData } from "@remix-run/react";
|
||||||
import { IndexWorld } from "~/components/index-world";
|
import { IndexWorld } from "~/components/index-world";
|
||||||
import type { IndexResponse } from "~/utils/saerro";
|
import { WorldContainer } from "~/components/index-world-container";
|
||||||
import { indexQuery } from "~/utils/saerro";
|
import { indexQuery } from "~/utils/saerro";
|
||||||
|
|
||||||
export const loader = async () => {
|
export const loader = async () => {
|
||||||
|
@ -9,18 +9,22 @@ export const loader = async () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const meta: V2_MetaFunction = () => {
|
export const meta: V2_MetaFunction = () => {
|
||||||
return [{ title: "PS2.LIVE" }];
|
return [
|
||||||
|
{ title: "PS2.LIVE" },
|
||||||
|
{
|
||||||
|
name: "description",
|
||||||
|
content: "PlanetSide 2 Live Population Stats",
|
||||||
|
},
|
||||||
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function Index() {
|
export default function Index() {
|
||||||
const data = useLoaderData<typeof loader>();
|
const data = useLoaderData<typeof loader>();
|
||||||
return (
|
return (
|
||||||
<div style={{ fontFamily: "system-ui, sans-serif", lineHeight: "1.4" }}>
|
<div>
|
||||||
<h1>PS2.LIVE</h1>
|
<h1>PS2.LIVE</h1>
|
||||||
<h2>Worlds</h2>
|
<h2>Worlds</h2>
|
||||||
{data.allWorlds.map((world) => (
|
<WorldContainer worlds={data.allWorlds} health={data.health} />
|
||||||
<IndexWorld key={world.id} world={world} />
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
import type { LoaderArgs, V2_MetaFunction } from "@remix-run/cloudflare";
|
import type { LoaderArgs, V2_MetaFunction } from "@remix-run/cloudflare";
|
||||||
import { json } from "@remix-run/cloudflare";
|
import { json } from "@remix-run/cloudflare";
|
||||||
import { useLoaderData } from "@remix-run/react";
|
import { useLoaderData } from "@remix-run/react";
|
||||||
import type { WorldResponse, Zone } from "~/utils/saerro";
|
import type { Zone } from "~/utils/saerro";
|
||||||
|
import { totalPopulation } from "~/utils/saerro";
|
||||||
import { allClasses, allVehicles, worldQuery } from "~/utils/saerro";
|
import { allClasses, allVehicles, worldQuery } from "~/utils/saerro";
|
||||||
import { pascalCaseToTitleCase, toTitleCase } from "~/utils/strings";
|
import { pascalCaseToTitleCase, toTitleCase } from "~/utils/strings";
|
||||||
|
import { worlds } from "~/utils/worlds";
|
||||||
|
|
||||||
export const loader = async ({ params }: LoaderArgs) => {
|
export const loader = async ({ params }: LoaderArgs) => {
|
||||||
return json(await worldQuery(params.id as string));
|
return json(await worldQuery(params.id as string));
|
||||||
|
@ -12,50 +14,34 @@ export const loader = async ({ params }: LoaderArgs) => {
|
||||||
export const meta: V2_MetaFunction<typeof loader> = ({ data }) => {
|
export const meta: V2_MetaFunction<typeof loader> = ({ data }) => {
|
||||||
const date = new Date();
|
const date = new Date();
|
||||||
const id = data?.world.id;
|
const id = data?.world.id;
|
||||||
const timeZone =
|
const worldInfo = worlds[String(id || "default")];
|
||||||
id === 1
|
const datetimeHumanFriendly = date.toLocaleString(worldInfo.locale, {
|
||||||
? "America/Los_Angeles"
|
timeZone: worldInfo.timeZone,
|
||||||
: id === 17 || id === 19 || id === 1000
|
|
||||||
? "America/New_York"
|
|
||||||
: id === 40
|
|
||||||
? "Asia/Tokyo"
|
|
||||||
: "UTC";
|
|
||||||
const datetimeHumanFriendly = date.toLocaleString("en-GB", {
|
|
||||||
timeZone,
|
|
||||||
hour12: true,
|
|
||||||
dateStyle: "medium",
|
dateStyle: "medium",
|
||||||
timeStyle: "short",
|
timeStyle: "short",
|
||||||
});
|
});
|
||||||
return [
|
return [
|
||||||
{ title: `${data?.world.name || "Unknown world"} | PS2.LIVE` },
|
|
||||||
{
|
{
|
||||||
name: "description",
|
title: `${
|
||||||
content: `${data?.world.name} currently has ${
|
data?.world.name || "Unknown world"
|
||||||
data?.world.population.total
|
} | PlanetSide 2 Live Population Stats`,
|
||||||
} players online as of ${datetimeHumanFriendly} local server time (<t:${Math.round(
|
|
||||||
date.getTime() / 1000
|
|
||||||
)}:R>). VS: ${data?.world.population.vs}, NC: ${
|
|
||||||
data?.world.population.nc
|
|
||||||
}, TR: ${
|
|
||||||
data?.world.population.tr
|
|
||||||
} -- See more detailed stats on ps2.live.`,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "timestamp",
|
name: "description",
|
||||||
content: date.toISOString(),
|
content: `${data?.world.name} currently has ${data?.world.population.total} players online as of ${datetimeHumanFriendly} ${data?.world.name} time. VS: ${data?.world.population.vs}, NC: ${data?.world.population.nc}, TR: ${data?.world.population.tr} -- See more detailed stats on ps2.live.`,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function World() {
|
export default function World() {
|
||||||
const { world } = useLoaderData<WorldResponse>();
|
const { world } = useLoaderData<typeof loader>();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ fontFamily: "system-ui, sans-serif", lineHeight: "1.6" }}>
|
<div style={{ fontFamily: "system-ui, sans-serif", lineHeight: "1.6" }}>
|
||||||
<h1>{world.name}</h1>
|
<h1>{world.name}</h1>
|
||||||
<h2>Total Population</h2>
|
<h2>Total Population</h2>
|
||||||
<p>
|
<p>
|
||||||
{world.population.total} players ({world.population.vs} VS,{" "}
|
{totalPopulation(world.population)} players ({world.population.vs} VS,{" "}
|
||||||
{world.population.nc} NC, {world.population.tr} TR)
|
{world.population.nc} NC, {world.population.tr} TR)
|
||||||
</p>
|
</p>
|
||||||
<div>
|
<div>
|
||||||
|
@ -73,7 +59,7 @@ const ZoneInfo = ({ zone }: { zone: Zone }) => {
|
||||||
<section>
|
<section>
|
||||||
<h3>{zone.name}</h3>
|
<h3>{zone.name}</h3>
|
||||||
<p>
|
<p>
|
||||||
{zone.population.total} players ({zone.population.vs} VS,{" "}
|
{totalPopulation(zone.population)} players ({zone.population.vs} VS,{" "}
|
||||||
{zone.population.nc} NC, {zone.population.tr} TR)
|
{zone.population.nc} NC, {zone.population.tr} TR)
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
|
@ -88,13 +74,14 @@ const ZoneInfo = ({ zone }: { zone: Zone }) => {
|
||||||
</ul>
|
</ul>
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
{zone.vehicles?.total} vehicles...
|
{totalPopulation(zone.vehicles as any)} vehicles...
|
||||||
<ul>
|
<ul>
|
||||||
{allVehicles.map((vehicle, idx) => (
|
{allVehicles.map((vehicle, idx) => (
|
||||||
<li key={idx}>
|
<li key={idx}>
|
||||||
<b>{toTitleCase(vehicle)}</b>: {zone.vehicles?.[vehicle].total}{" "}
|
<b>{toTitleCase(vehicle)}</b>:{" "}
|
||||||
total, {zone.vehicles?.[vehicle].vs} VS,{" "}
|
{totalPopulation(zone.vehicles?.[vehicle] as any)} total,{" "}
|
||||||
{zone.vehicles?.[vehicle].nc} NC, {zone.vehicles?.[vehicle].tr} TR
|
{zone.vehicles?.[vehicle].vs} VS, {zone.vehicles?.[vehicle].nc}{" "}
|
||||||
|
NC, {zone.vehicles?.[vehicle].tr} TR
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
|
@ -44,6 +44,7 @@ export type Health = {
|
||||||
worlds: {
|
worlds: {
|
||||||
name: string;
|
name: string;
|
||||||
status: string;
|
status: string;
|
||||||
|
lastEvent: string;
|
||||||
}[];
|
}[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -55,19 +56,16 @@ export type IndexResponse = {
|
||||||
export const indexQuery = async (): Promise<IndexResponse> => {
|
export const indexQuery = async (): Promise<IndexResponse> => {
|
||||||
const query = `{
|
const query = `{
|
||||||
health {
|
health {
|
||||||
ingestReachable
|
|
||||||
ingest
|
|
||||||
database
|
|
||||||
worlds {
|
worlds {
|
||||||
name
|
name
|
||||||
status
|
status
|
||||||
|
lastEvent
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
allWorlds {
|
allWorlds {
|
||||||
id
|
id
|
||||||
name
|
name
|
||||||
population {
|
population {
|
||||||
total
|
|
||||||
nc
|
nc
|
||||||
tr
|
tr
|
||||||
vs
|
vs
|
||||||
|
@ -77,7 +75,6 @@ export const indexQuery = async (): Promise<IndexResponse> => {
|
||||||
id
|
id
|
||||||
name
|
name
|
||||||
population {
|
population {
|
||||||
total
|
|
||||||
nc
|
nc
|
||||||
tr
|
tr
|
||||||
vs
|
vs
|
||||||
|
@ -91,8 +88,6 @@ export const indexQuery = async (): Promise<IndexResponse> => {
|
||||||
|
|
||||||
indexData.allWorlds.sort((a, b) => a.id - b.id);
|
indexData.allWorlds.sort((a, b) => a.id - b.id);
|
||||||
|
|
||||||
console.log(indexData);
|
|
||||||
|
|
||||||
return indexData;
|
return indexData;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -169,3 +164,6 @@ export const worldQuery = async (worldID: string): Promise<WorldResponse> => {
|
||||||
|
|
||||||
return worldData;
|
return worldData;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const totalPopulation = ({ nc, vs, tr }: Population): number =>
|
||||||
|
nc + vs + tr;
|
||||||
|
|
|
@ -7,3 +7,24 @@ export const toTitleCase = (str: string) => {
|
||||||
export const pascalCaseToTitleCase = (str: string) => {
|
export const pascalCaseToTitleCase = (str: string) => {
|
||||||
return toTitleCase(str.replace(/([A-Z])/g, " $1"));
|
return toTitleCase(str.replace(/([A-Z])/g, " $1"));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const humanTimeAgo = (ms: number) => {
|
||||||
|
const millis = Math.floor(ms % 1000);
|
||||||
|
const seconds = Math.floor(ms / 1000);
|
||||||
|
const minutes = Math.floor(seconds / 60);
|
||||||
|
const hours = Math.floor(minutes / 60);
|
||||||
|
|
||||||
|
if (hours > 0) {
|
||||||
|
return `${hours}h`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (minutes > 0) {
|
||||||
|
return `${minutes}m`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (seconds > 0) {
|
||||||
|
return `${seconds}s`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${millis}ms`;
|
||||||
|
};
|
||||||
|
|
59
app/utils/worlds.ts
Normal file
59
app/utils/worlds.ts
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
export const worlds: Record<
|
||||||
|
string,
|
||||||
|
{ timeZone: string; locale: string; location: string; platform: string }
|
||||||
|
> = {
|
||||||
|
"1": {
|
||||||
|
timeZone: "America/Los_Angeles",
|
||||||
|
locale: "en-US",
|
||||||
|
location: "US-W",
|
||||||
|
platform: "PC",
|
||||||
|
},
|
||||||
|
"10": {
|
||||||
|
timeZone: "UTC",
|
||||||
|
locale: "en-GB",
|
||||||
|
location: "EU",
|
||||||
|
platform: "PC",
|
||||||
|
},
|
||||||
|
"13": {
|
||||||
|
timeZone: "UTC",
|
||||||
|
locale: "en-GB",
|
||||||
|
location: "EU",
|
||||||
|
platform: "PC",
|
||||||
|
},
|
||||||
|
"17": {
|
||||||
|
timeZone: "America/New_York",
|
||||||
|
locale: "en-US",
|
||||||
|
location: "US-E",
|
||||||
|
platform: "PC",
|
||||||
|
},
|
||||||
|
"19": {
|
||||||
|
timeZone: "America/New_York",
|
||||||
|
locale: "en-US",
|
||||||
|
location: "US-E",
|
||||||
|
platform: "PC",
|
||||||
|
},
|
||||||
|
"40": {
|
||||||
|
timeZone: "Asia/Tokyo",
|
||||||
|
locale: "en-GB",
|
||||||
|
location: "JP",
|
||||||
|
platform: "PC",
|
||||||
|
},
|
||||||
|
"1000": {
|
||||||
|
timeZone: "America/New_York",
|
||||||
|
locale: "en-US",
|
||||||
|
location: "US-E",
|
||||||
|
platform: "PS4",
|
||||||
|
},
|
||||||
|
"2000": {
|
||||||
|
timeZone: "UTC",
|
||||||
|
locale: "en-GB",
|
||||||
|
location: "EU",
|
||||||
|
platform: "PS4",
|
||||||
|
},
|
||||||
|
default: {
|
||||||
|
timeZone: "UTC",
|
||||||
|
locale: "en-US",
|
||||||
|
location: "???",
|
||||||
|
platform: "???",
|
||||||
|
},
|
||||||
|
};
|
|
@ -1,4 +1,5 @@
|
||||||
{
|
{
|
||||||
|
"name": "ps2.live",
|
||||||
"private": true,
|
"private": true,
|
||||||
"sideEffects": false,
|
"sideEffects": false,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|
Loading…
Add table
Reference in a new issue