first pass of new server page
This commit is contained in:
parent
23a1f7708b
commit
990013af2b
9 changed files with 443 additions and 57 deletions
23
app/components/alert-timer.css.ts
Normal file
23
app/components/alert-timer.css.ts
Normal file
|
@ -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",
|
||||||
|
});
|
51
app/components/alert-timer.tsx
Normal file
51
app/components/alert-timer.tsx
Normal file
|
@ -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<MetagameWorld["zones"][0]>["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{" "}
|
||||||
|
<div
|
||||||
|
className={styles.alertDot}
|
||||||
|
style={{ "--speed": speed } as any}
|
||||||
|
></div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
} 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 <div className={styles.timer}>{timeLeft}</div>;
|
||||||
|
};
|
19
app/components/faction-pie.css.ts
Normal file
19
app/components/faction-pie.css.ts
Normal file
|
@ -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)",
|
||||||
|
},
|
||||||
|
});
|
40
app/components/faction-pie.tsx
Normal file
40
app/components/faction-pie.tsx
Normal file
|
@ -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 (
|
||||||
|
<div
|
||||||
|
className={pieRoot}
|
||||||
|
style={
|
||||||
|
{
|
||||||
|
fontSize: size || "1em",
|
||||||
|
backgroundImage: `conic-gradient(
|
||||||
|
#d30101 0% ${trPct}%,
|
||||||
|
#991cba ${trPct}% ${trPct + vsPct}%,
|
||||||
|
#1564cc ${trPct + vsPct}% 100%
|
||||||
|
)`,
|
||||||
|
"--inner-margin": innerMargin ? `${innerMargin}px` : "0",
|
||||||
|
"--inner-bg": innerBackground || "none",
|
||||||
|
} as any
|
||||||
|
}
|
||||||
|
>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
|
@ -14,6 +14,7 @@ import type { MetagameWorld } from "~/utils/metagame";
|
||||||
import type { PopulationWorld } from "~/utils/population";
|
import type { PopulationWorld } from "~/utils/population";
|
||||||
import { c } from "~/utils/classes";
|
import { c } from "~/utils/classes";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
import { AlertTimer } from "./alert-timer";
|
||||||
|
|
||||||
export type IndexWorldProps = {
|
export type IndexWorldProps = {
|
||||||
metagame: MetagameWorld;
|
metagame: MetagameWorld;
|
||||||
|
@ -131,11 +132,6 @@ const JaegerContinent = ({ zone }: { zone: MetagameWorld["zones"][0] }) => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const endTime = (alert: Required<MetagameWorld["zones"][0]>["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 Continent = ({ zone }: { zone: MetagameWorld["zones"][0] }) => {
|
||||||
const {
|
const {
|
||||||
name,
|
name,
|
||||||
|
@ -171,7 +167,7 @@ const Continent = ({ zone }: { zone: MetagameWorld["zones"][0] }) => {
|
||||||
ALERT PROGRESS
|
ALERT PROGRESS
|
||||||
</div>{" "}
|
</div>{" "}
|
||||||
<div>
|
<div>
|
||||||
<TimeLeft alert={zone.alert} />{" "}
|
<AlertTimer alert={zone.alert} />{" "}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<FactionBar population={zone.alert.percentages} />
|
<FactionBar population={zone.alert.percentages} />
|
||||||
|
@ -182,41 +178,3 @@ const Continent = ({ zone }: { zone: MetagameWorld["zones"][0] }) => {
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
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{" "}
|
|
||||||
<div
|
|
||||||
className={styles.alertDot}
|
|
||||||
style={{ "--speed": speed } as any}
|
|
||||||
></div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
} 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}</>;
|
|
||||||
};
|
|
||||||
|
|
92
app/components/world.css.ts
Normal file
92
app/components/world.css.ts
Normal file
|
@ -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",
|
||||||
|
});
|
72
app/routes/debug.components.tsx
Normal file
72
app/routes/debug.components.tsx
Normal file
|
@ -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<Population>({
|
||||||
|
nc: 33,
|
||||||
|
tr: 33,
|
||||||
|
vs: 33,
|
||||||
|
});
|
||||||
|
|
||||||
|
const [innerMargin, setInnerMargin] = useState<number>(10);
|
||||||
|
const [innerColor, setInnerColor] = useState<string>("black");
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h1>Debug Components</h1>
|
||||||
|
<h2>Faction Viz</h2>
|
||||||
|
<div>
|
||||||
|
NC{" "}
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
value={population.nc}
|
||||||
|
onChange={(e) =>
|
||||||
|
setPopulation((p) => ({ ...p, nc: Number(e.target.value) }))
|
||||||
|
}
|
||||||
|
/>{" "}
|
||||||
|
|| TR{" "}
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
value={population.tr}
|
||||||
|
onChange={(e) =>
|
||||||
|
setPopulation((p) => ({ ...p, tr: Number(e.target.value) }))
|
||||||
|
}
|
||||||
|
/>{" "}
|
||||||
|
|| VS{" "}
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
value={population.vs}
|
||||||
|
onChange={(e) =>
|
||||||
|
setPopulation((p) => ({ ...p, vs: Number(e.target.value) }))
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3>Horizontal Stacked Bar Chart</h3>
|
||||||
|
<FactionBar population={population} />
|
||||||
|
<h3>Pie Chart</h3>
|
||||||
|
<div style={{ fontSize: "5rem" }}>
|
||||||
|
<FactionPie population={population} />
|
||||||
|
<FactionPie
|
||||||
|
population={population}
|
||||||
|
innerBackground={innerColor}
|
||||||
|
innerMargin={innerMargin}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
Inner margin{" "}
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
value={innerMargin}
|
||||||
|
onChange={(e) => setInnerMargin(Number(e.target.value))}
|
||||||
|
/>
|
||||||
|
Inner color{" "}
|
||||||
|
<input
|
||||||
|
type="color"
|
||||||
|
value={innerColor}
|
||||||
|
onChange={(e) => setInnerColor(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
|
@ -17,6 +17,16 @@ import {
|
||||||
worlds,
|
worlds,
|
||||||
zones,
|
zones,
|
||||||
} from "~/utils/strings";
|
} 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 = {
|
type LoaderData = {
|
||||||
saerro: WorldResponse;
|
saerro: WorldResponse;
|
||||||
|
@ -66,26 +76,104 @@ export default function World() {
|
||||||
const {
|
const {
|
||||||
saerro: { world },
|
saerro: { world },
|
||||||
id,
|
id,
|
||||||
|
metagame,
|
||||||
} = useLoaderData<typeof loader>();
|
} = useLoaderData<typeof loader>();
|
||||||
|
|
||||||
const worldInfo = worlds[String(id || "default")];
|
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 (
|
return (
|
||||||
|
<>
|
||||||
|
<div className={styles.outer}>
|
||||||
<div>
|
<div>
|
||||||
<h1>{worldInfo.name}</h1>
|
<div className={styles.header}>
|
||||||
<h2>Total Population</h2>
|
<div className={c(styles.headerName, styles.headerFont)}>
|
||||||
<p>
|
<div>{worldInfo.name.toUpperCase()}</div>
|
||||||
{totalPopulation(world.population)} players ({world.population.vs} VS,{" "}
|
<div className={styles.headerSub}>
|
||||||
{world.population.nc} NC, {world.population.tr} TR)
|
[{worldInfo.location}] [{worldInfo.platform}]
|
||||||
</p>
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={styles.populationHead}>
|
||||||
|
<div className={styles.headerFont}>
|
||||||
|
<div className={styles.totalPop}>
|
||||||
|
{totalPopulation(world.population).toLocaleString()}
|
||||||
|
</div>
|
||||||
|
PLAYERS
|
||||||
|
</div>
|
||||||
|
<div className={styles.population}>
|
||||||
|
<div className={styles.popNumbers}>
|
||||||
|
<div
|
||||||
|
className={styles.popItem}
|
||||||
|
style={{ flex: world.population.vs + 1 }}
|
||||||
|
>
|
||||||
|
<img className={popImage} src={vsLogo} alt="VS" />{" "}
|
||||||
|
{world.population.vs}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={styles.popItem}
|
||||||
|
style={{ flex: world.population.nc + 1 }}
|
||||||
|
>
|
||||||
|
<img className={popImage} src={ncLogo} alt="NC" />{" "}
|
||||||
|
{world.population.nc}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={styles.popItem}
|
||||||
|
style={{ flex: world.population.tr + 1 }}
|
||||||
|
>
|
||||||
|
<img className={popImage} src={trLogo} alt="TR" />{" "}
|
||||||
|
{world.population.tr}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<FactionBar population={world.population} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={styles.headerConts}>
|
||||||
|
<div className={styles.headerSub}>CONTINENT CONTROL</div>
|
||||||
|
{metagame.zones.sort(contPrioritySort).map((zone, idx) => {
|
||||||
|
const zoneInfo = zones[String(zone.id)];
|
||||||
|
return (
|
||||||
|
<div key={idx} className={styles.cont}>
|
||||||
|
<div style={{ flex: 0 }}>{zoneInfo.name.toUpperCase()}</div>
|
||||||
|
<div style={{ flex: 1 }}>
|
||||||
|
<FactionPie
|
||||||
|
size="4rem"
|
||||||
|
population={zone.alert?.percentages ?? zone.territory}
|
||||||
|
innerBackground={`linear-gradient(45deg, ${zoneInfo.colors[0]}, ${zoneInfo.colors[1]})`}
|
||||||
|
innerMargin={10}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className={styles.contSub}>
|
||||||
|
{zone.alert ? (
|
||||||
|
<AlertTimer alert={zone.alert} />
|
||||||
|
) : zone.locked ? (
|
||||||
|
nextZoneID == zone.id ? (
|
||||||
|
<>NEXT UP »</>
|
||||||
|
) : (
|
||||||
|
<>LOCKED</>
|
||||||
|
)
|
||||||
|
) : (
|
||||||
|
<>UNLOCKED</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h2>Continents</h2>
|
<h2>Continents</h2>
|
||||||
{world.zones.all.map((zone) => (
|
{world.zones.all.map((zone) => (
|
||||||
<ZoneInfo zone={zone} key={zone.id} />
|
<ZoneInfo zone={zone} key={zone.id} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<Footer isMainPage />
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<Footer isMainPage />
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
43
app/utils/sorting.ts
Normal file
43
app/utils/sorting.ts
Normal file
|
@ -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;
|
||||||
|
};
|
Loading…
Add table
Reference in a new issue