Skip to content

Commit d36e38d

Browse files
committed
#824 Restructured frontend using leagues from database
1 parent 1905f83 commit d36e38d

18 files changed

Lines changed: 366 additions & 232 deletions

src/frontend/.env

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
11
VITE_API_URL=http://localhost:8000 # This is the URL for the FastAPI backend
2-
VITE_APP_DEFAULT_LEAGUES="Mirage|Keepers|Mercenaries|Phrecia" # This is the default leagues, the first one is the current league to show on the frontend
3-
VITE_APP_ADDITIONAL_LEAGUES="Hardcore Mirage|Hardcore Keepers|Hardcore Mercenaries|Hardcore Phrecia" # NB: Separate by '|' for each league
42
VITE_APP_TURNSTILE_SITE_KEY=1x00000000000000000000AA # This is the site key for the Turnstile captcha
5-
VITE_APP_LEAGUE_LAUNCH_TIME=2026-03-06T19:00:00Z # ISO 8601 format. Round backwards to whole hour number
63
VITE_APP_PLOTTING_WINDOW_HOURS=336

src/frontend/src/components/Common/DateDaysHoursSinceLaunchStats.tsx

Lines changed: 42 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,42 +4,68 @@ import { useEffect, useState } from "react";
44
import {
55
getHoursSinceLaunch,
66
formatHoursSinceLaunch,
7-
LEAGUE_LAUNCH_DATETIME,
87
} from "../../hooks/graphing/utils";
9-
import { DEFAULT_LEAGUES } from "../../config";
108
import { setupHourlyUpdate } from "../../utils";
9+
import { useLeagueLaunchStats } from "../../store/LeagueLaunchStatsStore";
1110

1211
const DateDaysHoursSinceLaunchStats = (props: StatProps) => {
13-
const defaultLeague = DEFAULT_LEAGUES[0];
12+
const [currentTime, setCurrentTime] = useState(new Date());
13+
const { league, leagueLaunch, hoursSinceLaunch, setHoursSinceLaunch } =
14+
useLeagueLaunchStats();
15+
useEffect(() => {
16+
return setupHourlyUpdate(setCurrentTime);
17+
}, []);
1418

15-
const leagueLaunchDay = LEAGUE_LAUNCH_DATETIME.getDate();
16-
const leagueLaunchMonth = LEAGUE_LAUNCH_DATETIME.toLocaleString("default", {
19+
const leagueLaunchDay = leagueLaunch.getDate();
20+
const leagueLaunchMonth = leagueLaunch.toLocaleString("default", {
1721
month: "short",
1822
});
23+
const leagueLaunchYear = leagueLaunch.toLocaleString("default", {
24+
year: "numeric",
25+
});
1926

20-
const [currentTime, setCurrentTime] = useState(new Date());
2127
const currentDay = currentTime.getDate();
2228
const currentMonth = currentTime.toLocaleString("default", {
2329
month: "short",
2430
});
31+
const currentYear = currentTime.toLocaleString("default", {
32+
year: "numeric",
33+
});
2534

26-
const hoursSinceLaunch = getHoursSinceLaunch(currentTime);
27-
const daysHoursSinceLaunchFormat = formatHoursSinceLaunch(hoursSinceLaunch);
28-
const [sinceLaunchDays, sinceLaunchHours] =
29-
daysHoursSinceLaunchFormat.split("T");
30-
35+
const curHoursSinceLaunch = getHoursSinceLaunch(currentTime, leagueLaunch);
36+
const [localHoursSinceLaunch, setLocalHoursSinceLaunch] =
37+
useState(curHoursSinceLaunch);
3138
useEffect(() => {
32-
return setupHourlyUpdate(setCurrentTime);
33-
}, []);
39+
console.log(currentTime, leagueLaunch);
40+
const curHoursSinceLaunch = getHoursSinceLaunch(currentTime, leagueLaunch);
41+
setLocalHoursSinceLaunch(hoursSinceLaunch);
42+
if (curHoursSinceLaunch !== hoursSinceLaunch) {
43+
setHoursSinceLaunch(curHoursSinceLaunch);
44+
}
45+
}, [
46+
leagueLaunch,
47+
hoursSinceLaunch,
48+
setHoursSinceLaunch,
49+
currentTime,
50+
localHoursSinceLaunch,
51+
]);
52+
53+
const daysHoursSinceLaunchFormat = formatHoursSinceLaunch(
54+
localHoursSinceLaunch,
55+
);
56+
const [yearsDays, sinceLaunchHours] = daysHoursSinceLaunchFormat.split("T");
57+
const [sinceLaunchYears, sinceLaunchDays] = yearsDays.split("Y");
3458

3559
return (
3660
<Stat {...props}>
3761
<StatNumber>
38-
{sinceLaunchDays} days and {sinceLaunchHours} hours since{" "}
39-
{defaultLeague} launched
62+
{sinceLaunchYears !== "0" ? `${sinceLaunchYears} years, ` : ""}
63+
{sinceLaunchDays} days and {sinceLaunchHours} hours since {league.name}{" "}
64+
launched
4065
</StatNumber>
4166
<StatHelpText>
42-
{leagueLaunchMonth} {leagueLaunchDay} - {currentMonth} {currentDay}
67+
{leagueLaunchMonth} {leagueLaunchDay} {leagueLaunchYear} -{" "}
68+
{currentMonth} {currentDay} {currentYear}
4369
</StatHelpText>
4470
</Stat>
4571
);
Lines changed: 50 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
import {
2-
Box, Flex, FlexProps
3-
} from "@chakra-ui/layout";
1+
import { Box, Flex, FlexProps } from "@chakra-ui/layout";
42
import { Button } from "@chakra-ui/react";
53
import { capitalizeFirstLetter } from "../../hooks/utils";
64
import { TextWithUnderline } from "../Text/TextWithUnderline";
@@ -24,12 +22,18 @@ const MultiSelectButtonGrid = (props: MultiSelectProps) => {
2422

2523
let defaultSelectedOptionsBoolean: boolean[];
2624
if (defaultSelectedOptions !== undefined) {
27-
defaultSelectedOptionsBoolean = options.map((val) => defaultSelectedOptions.includes(val));
25+
defaultSelectedOptionsBoolean = options.map((val) =>
26+
defaultSelectedOptions.includes(val),
27+
);
2828
} else {
29-
defaultSelectedOptionsBoolean = new Array<boolean>(numberOfOptions).fill(false);
29+
defaultSelectedOptionsBoolean = new Array<boolean>(numberOfOptions).fill(
30+
false,
31+
);
3032
}
3133

32-
const [selectedOptions, setSelectedOptions] = useState<boolean[]>(defaultSelectedOptionsBoolean);
34+
const [selectedOptions, setSelectedOptions] = useState<boolean[]>(
35+
defaultSelectedOptionsBoolean,
36+
);
3337

3438
const clearClicked = useGraphInputStore((state) => state.clearClicked);
3539

@@ -38,47 +42,48 @@ const MultiSelectButtonGrid = (props: MultiSelectProps) => {
3842
props.onClearClick();
3943
setSelectedOptions(defaultSelectedOptionsBoolean);
4044
}
41-
}, [clearClicked])
45+
}, [clearClicked, defaultSelectedOptionsBoolean, props]);
4246

4347
const buttons = options.map((val, idx) => {
44-
return <Box textAlign="center" mr={2} mb={2} key={val}>
45-
<Button
46-
variant="solid"
47-
bg={selectedOptions[idx] ? "ui.lightInput" : "ui.input"}
48-
color="ui.white"
49-
_hover={{
50-
bg: "ui.grey"
51-
}}
52-
borderWidth={1}
53-
borderColor="ui.grey"
54-
maxW="bgBoxes.mediumPPBox"
55-
fontWeight="normal"
56-
onClick={() => {
57-
setSelectedOptions((currentSelectedOptions) => [
58-
...currentSelectedOptions.slice(0, idx),
59-
!selectedOptions[idx],
60-
...currentSelectedOptions.slice(idx + 1)])
61-
if (!selectedOptions[idx]) {
62-
props.setValue(val);
63-
} else {
64-
props.removeValue(val)
65-
}
66-
}}
67-
>
68-
{capitalizeFirstLetter(val)}
69-
</Button>
70-
</Box>
71-
})
72-
return <Flex direction="column">
73-
<TextWithUnderline text={props.optionsName} />
74-
<Flex
75-
direction="row"
76-
flexWrap="wrap"
77-
mt={2}
78-
>
79-
{...buttons}
48+
return (
49+
<Box textAlign="center" mr={2} mb={2} key={val}>
50+
<Button
51+
variant="solid"
52+
bg={selectedOptions[idx] ? "ui.lightInput" : "ui.input"}
53+
color="ui.white"
54+
_hover={{
55+
bg: "ui.grey",
56+
}}
57+
borderWidth={1}
58+
borderColor="ui.grey"
59+
maxW="bgBoxes.mediumPPBox"
60+
fontWeight="normal"
61+
onClick={() => {
62+
setSelectedOptions((currentSelectedOptions) => [
63+
...currentSelectedOptions.slice(0, idx),
64+
!selectedOptions[idx],
65+
...currentSelectedOptions.slice(idx + 1),
66+
]);
67+
if (!selectedOptions[idx]) {
68+
props.setValue(val);
69+
} else {
70+
props.removeValue(val);
71+
}
72+
}}
73+
>
74+
{capitalizeFirstLetter(val)}
75+
</Button>
76+
</Box>
77+
);
78+
});
79+
return (
80+
<Flex direction="column">
81+
<TextWithUnderline text={props.optionsName} />
82+
<Flex direction="row" flexWrap="wrap" mt={2}>
83+
{...buttons}
84+
</Flex>
8085
</Flex>
81-
</Flex>
82-
}
86+
);
87+
};
8388

8489
export default MultiSelectButtonGrid;

src/frontend/src/components/Common/QueryButtons.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
import { useErrorStore } from "../../store/ErrorStore";
1010
import { ErrorMessage } from "../../components/Input/StandardLayoutInput/ErrorMessage";
1111
import { getOptimizedPlotQuery } from "../../hooks/graphing/utils";
12+
import { useLeagueLaunchStats } from "../../store/LeagueLaunchStatsStore";
1213

1314
const QueryButtons = (props: FlexProps) => {
1415
const { setExpandedGraphInputFilters } = useExpandedComponentStore();
@@ -20,6 +21,7 @@ const QueryButtons = (props: FlexProps) => {
2021
modifiersUnidentifiedError,
2122
baseSpecDoesNotMatchError,
2223
} = useErrorStore();
24+
const { hoursSinceLaunch } = useLeagueLaunchStats();
2325
const { stateHash, fetchStatus, setHashFromStore, setPlotQuery } =
2426
useGraphInputStore();
2527
const isFetching = fetchStatus === "fetching";
@@ -42,13 +44,13 @@ const QueryButtons = (props: FlexProps) => {
4244
}, 10);
4345
};
4446

45-
const handlePlotQuery = () => {
47+
const handlePlotQuery = (hoursSinceLaunch: number) => {
4648
if (isFetching) return;
4749
if (stateHash) return;
4850
const leagueValid = checkGraphQueryLeagueInput();
4951
const modifierValid = checkGraphQueryModifierInput();
5052
if (!leagueValid || !modifierValid) return;
51-
const plotQuery = getOptimizedPlotQuery();
53+
const plotQuery = getOptimizedPlotQuery(hoursSinceLaunch);
5254
if (plotQuery === undefined) return;
5355
setPlotQuery(plotQuery);
5456
useGraphInputStore.getState().setQueryClicked();
@@ -89,7 +91,7 @@ const QueryButtons = (props: FlexProps) => {
8991
borderColor="ui.grey"
9092
width={["inputSizes.defaultBox", "inputSizes.lgBox"]}
9193
maxW="98vw"
92-
onClick={handlePlotQuery}
94+
onClick={() => handlePlotQuery(hoursSinceLaunch)}
9395
disabled={isFetching}
9496
opacity={isFetching ? 0.5 : 1}
9597
cursor={isFetching ? "not-allowed" : "pointer"}

src/frontend/src/components/Graph/CustomTooltip.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { capitalizeFirstLetter } from "../../hooks/utils";
1212
import { BiError } from "react-icons/bi";
1313
import { getHoursSinceLaunch } from "../../hooks/graphing/utils";
1414
import { setupHourlyUpdate } from "../../utils";
15+
import { useLeagueLaunchStats } from "../../store/LeagueLaunchStatsStore";
1516

1617
interface CustomTooltipProps extends TooltipContentProps<ValueType, NameType> {
1718
upperBoundry: number;
@@ -21,10 +22,11 @@ interface CustomTooltipProps extends TooltipContentProps<ValueType, NameType> {
2122

2223
export const CustomTooltip = (props: CustomTooltipProps) => {
2324
const [currentTime, setCurrentTime] = useState(new Date());
25+
const { leagueLaunch } = useLeagueLaunchStats();
2426

2527
let hoursAgo = -1;
2628
if (typeof props.label == "number") {
27-
hoursAgo = getHoursSinceLaunch(currentTime) - props.label;
29+
hoursAgo = getHoursSinceLaunch(currentTime, leagueLaunch) - props.label;
2830
}
2931

3032
const daysToDisplay = Math.floor(hoursAgo / 24);

src/frontend/src/components/Graph/GraphComponent.tsx

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ import PlotCustomizationButtons from "../Common/PlotCustomizationButtons";
3333
* and a graph if there has been
3434
*/
3535
function GraphComponent(props: BoxProps) {
36-
const { plotQuery, leagues } = useGraphInputStore();
36+
const { plotQuery, choosableLeagues } = useGraphInputStore();
3737
const {
3838
result: plotData,
3939
mostCommonCurrencyUsed,
@@ -51,7 +51,10 @@ function GraphComponent(props: BoxProps) {
5151
const showSecondary = usePlotSettingsStore((state) => state.showSecondary);
5252

5353
if (error || plotData == undefined) return;
54-
const fetchedLeagues = plotData.data.map((val) => val.name);
54+
const fetchedLeagueIds = plotData.data.map((val) => val.name);
55+
const fetchedLeagues = choosableLeagues
56+
.filter((league) => fetchedLeagueIds.includes(league.leagueId))
57+
.map((league) => league.name);
5558

5659
const isLowConfidence = confidenceRating === "low";
5760
const isMediumConfidence = confidenceRating === "medium";
@@ -171,13 +174,13 @@ function GraphComponent(props: BoxProps) {
171174
<Legend verticalAlign="top" height={36} />
172175
{plotData.data.map(
173176
(series, idx) =>
174-
leagues.includes(series.name) && (
177+
fetchedLeagueIds.includes(series.name) && (
175178
<Line
176179
type="monotone"
177180
data={series.data}
178181
dataKey={"valueInChaos"}
179182
key={series.name}
180-
name={series.name + " - " + "Chaos Value"}
183+
name={fetchedLeagues[idx] + " - " + "Chaos Value"}
181184
stroke={colors[idx]}
182185
yAxisId={0}
183186
hide={!showChaos}
@@ -204,13 +207,15 @@ function GraphComponent(props: BoxProps) {
204207
{mostCommonCurrencyUsed !== "chaos" &&
205208
plotData.data.map(
206209
(series, idx) =>
207-
leagues.includes(series.name) && (
210+
fetchedLeagueIds.includes(series.name) && (
208211
<Line
209212
type="monotone"
210213
data={series.data}
211214
dataKey={"valueInMostCommonCurrencyUsed"}
212215
key={series.name}
213-
name={series.name + " - " + mostCommonCurrencyUsedName}
216+
name={
217+
fetchedLeagues[idx] + " - " + mostCommonCurrencyUsedName
218+
}
214219
stroke={colors[idx]}
215220
yAxisId={1}
216221
hide={!showSecondary}
Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,33 @@
11
import { useGraphInputStore } from "../../store/GraphInputStore";
2-
import { DEFAULT_LEAGUES, ADDITIONAL_LEAGUES } from "../../config";
2+
import { useLeagueLaunchStats } from "../../store/LeagueLaunchStatsStore";
33
import MultiSelectButtonGrid from "../Common/MultiSelectButtonGrid";
44

55
// Set the default league in the environment variables file.
66
// League Input Component - This component is used to select the league of the game.
77
export const LeagueInput = () => {
8-
const { leagues, addLeague, removeLeague } = useGraphInputStore();
9-
10-
11-
const selectLeagueOptions: string[] = [...DEFAULT_LEAGUES, ...ADDITIONAL_LEAGUES]
8+
const { league } = useLeagueLaunchStats();
9+
const { leagues, choosableLeagues, addLeague, removeLeague } =
10+
useGraphInputStore();
1211

12+
const selectLeagueOptions: string[] = choosableLeagues.map(
13+
(league) => league.name,
14+
);
15+
let defaultLeagues;
16+
if (leagues.length > 0) {
17+
defaultLeagues = leagues;
18+
} else {
19+
defaultLeagues = league ? [league.name] : undefined;
20+
}
1321
return (
1422
<MultiSelectButtonGrid
1523
optionsName="Select one or more Leagues"
1624
options={selectLeagueOptions}
17-
defaultSelectedOptions={leagues}
25+
defaultSelectedOptions={defaultLeagues}
1826
setValue={addLeague}
1927
removeValue={removeLeague}
20-
onClearClick={() => useGraphInputStore.setState({ leagues: DEFAULT_LEAGUES })}
28+
onClearClick={() =>
29+
useGraphInputStore.setState({ leagues: defaultLeagues })
30+
}
2131
/>
22-
)
32+
);
2333
};

src/frontend/src/config.tsx

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,5 @@
22

33
export const VITE_API_URL = import.meta.env.VITE_API_URL;
44
export const TURNSTILE_SITE_KEY = import.meta.env.VITE_APP_TURNSTILE_SITE_KEY;
5-
export const LEAGUE_LAUNCH_TIME = import.meta.env.VITE_APP_LEAGUE_LAUNCH_TIME;
6-
export const PLOTTING_WINDOW_HOURS = import.meta.env.VITE_APP_PLOTTING_WINDOW_HOURS;
7-
8-
export const DEFAULT_LEAGUES = (import.meta.env.VITE_APP_DEFAULT_LEAGUES).split("|") as Array<string>;
9-
export const ADDITIONAL_LEAGUES = (import.meta.env.VITE_APP_ADDITIONAL_LEAGUES).split("|") as Array<string>;
5+
export const PLOTTING_WINDOW_HOURS = import.meta.env
6+
.VITE_APP_PLOTTING_WINDOW_HOURS;

0 commit comments

Comments
 (0)