Skip to content

Commit

Permalink
Merge pull request #20 from cesaregarza/bugfix/weapon-leaderboards
Browse files Browse the repository at this point in the history
Bugfix/weapon leaderboards
  • Loading branch information
cesaregarza authored Sep 7, 2024
2 parents 807378d + b1770a4 commit 7486f90
Show file tree
Hide file tree
Showing 11 changed files with 282 additions and 86 deletions.
16 changes: 11 additions & 5 deletions i18n/USen.json
Original file line number Diff line number Diff line change
Expand Up @@ -92,16 +92,22 @@
"modes": "Modes",
"region": "Region",
"column_total_x_power_title": "Total X Power",
"no_data": "No data available",
"errors.503": "Service Unavailable, please try again later."
},
"weapon_leaderboard": {
"weapon_select_main": "Weapon Select",
"weapon_select_alt": "Weapon Select (Alt)",
"weapon_title": "Top Weapon Wielders",
"threshold_select": "Minimum Percent Usage",
"weapon_leaderboard.peak_x_power": "Peak X Power",
"weapon_leaderboard.final_x_power": "Season End X Power",
"peak_x_power": "Peak X Power",
"final_x_power": "Season End X Power",
"show_all": "Multiple Entries Per Player",
"dedupe_data": "Best Entry Per Player",
"select_season": "Select Season",
"all_seasons": "All Seasons",
"select_weapon": "Select Weapon",
"null_selection": "No Alt Weapon",
"no_data": "No data available",
"errors.503": "Service Unavailable, please try again later."
"null_selection": "No Alt Weapon"
},
"navigation": {
"top500": "Top 500",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import React, { useState, useEffect, useRef } from "react";
import { useTranslation } from "react-i18next";
import { calculateSeasonNow, getSeasonName } from "../utils/season_utils";
import { FaTimes } from "react-icons/fa";

const SeasonSelector = ({ selectedSeason, setSelectedSeason }) => {
const { t } = useTranslation("weapon_leaderboard");
const { t: gameT } = useTranslation("game");
const [isOpen, setIsOpen] = useState(false);
const dropdownRef = useRef(null);

const currentSeason = calculateSeasonNow();
const seasons = Array.from({ length: currentSeason }, (_, i) => i + 1).sort(
(a, b) => a - b
);

const handleSeasonSelect = (season) => {
setSelectedSeason(season);
setIsOpen(false);
};

const handleClearSeason = (e) => {
e.stopPropagation();
setSelectedSeason(null);
};

useEffect(() => {
const handleClickOutside = (event) => {
if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
setIsOpen(false);
}
};

document.addEventListener("mousedown", handleClickOutside);
return () => {
document.removeEventListener("mousedown", handleClickOutside);
};
}, []);

const toggleDropdown = () => {
setIsOpen(!isOpen);
};

return (
<div className="relative inline-block w-64" ref={dropdownRef}>
<div
className="flex items-center justify-between w-full bg-gray-800 border border-gray-700 text-white py-2 px-3 rounded leading-tight cursor-pointer"
onClick={toggleDropdown}
>
{selectedSeason !== null ? (
<div className="flex items-center flex-grow">
<span>{`${selectedSeason} ${getSeasonName(
selectedSeason,
gameT
)}`}</span>
<button
onClick={handleClearSeason}
className="ml-auto p-1 hover:bg-gray-700 rounded"
aria-label="Clear season selection"
>
<FaTimes size={14} />
</button>
</div>
) : (
<span>{t("all_seasons")}</span>
)}
<svg
className={`fill-current h-4 w-4 ml-2 ${
isOpen ? "transform rotate-180" : ""
}`}
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
>
<path d="M9.293 12.95l.707.707L15.657 8l-1.414-1.414L10 10.828 5.757 6.586 4.343 8z" />
</svg>
</div>
{isOpen && (
<div className="absolute z-10 w-full mt-1 bg-gray-800 border border-gray-700 rounded shadow-lg max-h-60 overflow-y-auto">
{seasons.map((season) => (
<div
key={season}
className="flex items-center px-3 py-2 cursor-pointer hover:bg-gray-700"
onClick={() => handleSeasonSelect(season)}
>
<span>{`${season} ${getSeasonName(season, gameT)}`}</span>
</div>
))}
</div>
)}
</div>
);
};

export default SeasonSelector;
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React, { useState, useEffect, useRef } from "react";
import { useTranslation } from "react-i18next";

const ThresholdSelector = ({ threshold, setThreshold }) => {
const { t } = useTranslation();
const { t } = useTranslation("weapon_leaderboard");
const [isDragging, setIsDragging] = useState(false);
const [dragValue, setDragValue] = useState(threshold);
const containerRef = useRef(null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const ModeSelector = React.lazy(() =>
);
const WeaponSelector = React.lazy(() => import("./weapon_selector"));
const ThresholdSelector = React.lazy(() => import("./threshold_selector"));
const SeasonSelector = React.lazy(() => import("./season_selector"));

const WeaponLeaderboardControls = ({
selectedRegion,
Expand All @@ -27,8 +28,12 @@ const WeaponLeaderboardControls = ({
finalResults,
toggleFinalResults,
handleSwapWeapons,
dedupePlayers,
toggleDedupePlayers,
selectedSeason,
setSelectedSeason,
}) => {
const { t } = useTranslation("main_page");
const { t } = useTranslation("weapon_leaderboard");
const { t: pl } = useTranslation("player");
const {
weaponTranslations,
Expand Down Expand Up @@ -126,7 +131,7 @@ const WeaponLeaderboardControls = ({
!finalResults ? "highlighted-option" : ""
}`}
>
{t("weapon_leaderboard.peak_x_power")}
{t("peak_x_power")}
</span>
<div className="relative mx-2" title="Change the scale type">
<input
Expand All @@ -147,10 +152,51 @@ const WeaponLeaderboardControls = ({
finalResults ? "highlighted-option" : ""
}`}
>
{t("weapon_leaderboard.final_x_power")}
{t("final_x_power")}
</span>
</div>
</label>
<label
htmlFor="toggleDedupePlayers"
className="inline-flex items-center cursor-pointer flex-col"
>
<div className="flex items-center justify-center">
<span
className={`text-sm font-medium w-40 text-right pr-4 ${
!dedupePlayers ? "highlighted-option" : ""
}`}
>
{t("show_all")}
</span>
<div className="relative mx-2" title="Toggle data deduplication">
<input
type="checkbox"
id="toggleDedupePlayers"
className="sr-only peer"
checked={dedupePlayers}
onChange={toggleDedupePlayers}
/>
<div
className={`w-11 h-6 rounded-full peer peer-focus:ring-4 peer-focus:ring-purple-300 dark:peer-focus:ring-purple-800 after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:after:translate-x-5 ${
dedupePlayers ? "bg-purple" : "bg-gray-600"
}`}
></div>
</div>
<span
className={`text-sm font-medium w-40 text-left pl-4 ${
dedupePlayers ? "highlighted-option" : ""
}`}
>
{t("dedupe_data")}
</span>
</div>
</label>
</div>
<div className="flex flex-col items-center mb-4">
<SeasonSelector
selectedSeason={selectedSeason}
setSelectedSeason={setSelectedSeason}
/>
</div>
</Suspense>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const WeaponSelector = ({
initialWeaponId,
allowNull = false,
}) => {
const { t } = useTranslation("main_page");
const { t } = useTranslation("weapon_leaderboard");
const [selectedWeapon, setSelectedWeapon] = useState(
initialWeaponId !== undefined && initialWeaponId !== null
? initialWeaponId.toString()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { useState } from "react";
import { getSeasonName, calculateSeasonNow } from "./xchart_helper_functions";
import { getSeasonName, calculateSeasonNow } from "../utils/season_utils";
import { getImageFromId } from "./weapon_helper_functions";
import { useTranslation } from "react-i18next";
import SplatZonesIcon from "../../assets/icons/splat_zones.png";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { useState, useEffect, useRef } from "react";
import { calculateSeasonNow, getSeasonName } from "./xchart_helper_functions";
import { calculateSeasonNow, getSeasonName } from "../utils/season_utils";
import { useTranslation } from "react-i18next";

function SeasonSelector({ data, mode, onSeasonChange }) {
Expand Down
3 changes: 1 addition & 2 deletions src/react_app/src/components/player_components/xchart.jsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import React, { useState, useEffect } from "react";
import HighchartsReact from "highcharts-react-official";
import Highcharts from "highcharts/highstock";
import { getPercentageInSeason, getSeasonName } from "../utils/season_utils";
import {
getPercentageInSeason,
filterAndProcessData,
getSeasonName,
getSeasonColor,
getAccessibleColor,
getDefaultWidth,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,48 +1,9 @@
const getSeasonStartDate = (season) => {
const yearOffset = Math.floor((season - 1) / 4);
const monthIndex = (season - 1) % 4;
const monthMap = [11, 2, 5, 8]; // December, March, June, September
const year = monthIndex === 0 ? 2023 + yearOffset - 1 : 2023 + yearOffset;
return new Date(Date.UTC(year, monthMap[monthIndex], 1));
};

const getSeasonEndDate = (season) => {
const nextSeasonStart = getSeasonStartDate(season + 1);
return new Date(
Date.UTC(
nextSeasonStart.getUTCFullYear(),
nextSeasonStart.getUTCMonth(),
nextSeasonStart.getUTCDate(),
0,
0,
0,
nextSeasonStart.getUTCMilliseconds() - 1000
)
);
};

const getPercentageInSeason = (timestamp, season) => {
const seasonStart = getSeasonStartDate(season);
const seasonEnd = getSeasonEndDate(season);
const totalDuration = seasonEnd - seasonStart;
const elapsedDuration = new Date(timestamp) - seasonStart;
return (elapsedDuration / totalDuration) * 100;
};

const calculateSeasonNow = () => {
const now_utc = new Date();
return calculateSeasonByTimestamp(now_utc);
};

const calculateSeasonByTimestamp = (timestamp) => {
const timestamp_utc = new Date(timestamp);
const timestamp_utc_month = (timestamp_utc.getUTCMonth() + 1) % 12;
const timestamp_utc_year =
timestamp_utc.getUTCFullYear() + (timestamp_utc_month === 0 ? 1 : 0);
return (
4 * (timestamp_utc_year - 2022) + Math.floor(timestamp_utc_month / 3) - 3
);
};
import {
getPercentageInSeason,
calculateSeasonNow,
calculateSeasonByTimestamp,
getSeasonName,
} from "../utils/season_utils";

const dataWithNulls = (data, threshold, festsForSeason) => {
const result = [];
Expand Down Expand Up @@ -135,16 +96,6 @@ function filterAndProcessData(
};
}

const getSeasonName = (season_number, t) => {
const season_offset = season_number + 2;
const season_index = season_offset % 4;
const year = 2022 + Math.floor(season_offset / 4);
const season_names = [t("spring"), t("summer"), t("autumn"), t("winter")];
return t("format_short")
.replace("%SEASON%", season_names[season_index])
.replace("%YEAR%", year);
};

const getSeasonColor = (season_number, isCurrent) => {
const saturation = 100;
const baseLightness = 25;
Expand Down Expand Up @@ -196,11 +147,6 @@ const getAvailableModes = (data) => {
};

export {
getSeasonStartDate,
getSeasonEndDate,
getPercentageInSeason,
calculateSeasonNow,
calculateSeasonByTimestamp,
dataWithNulls,
filterAndProcessData,
getSeasonName,
Expand Down
64 changes: 64 additions & 0 deletions src/react_app/src/components/utils/season_utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
const getSeasonStartDate = (season) => {
const yearOffset = Math.floor((season - 1) / 4);
const monthIndex = (season - 1) % 4;
const monthMap = [11, 2, 5, 8]; // December, March, June, September
const year = monthIndex === 0 ? 2023 + yearOffset - 1 : 2023 + yearOffset;
return new Date(Date.UTC(year, monthMap[monthIndex], 1));
};

const getSeasonEndDate = (season) => {
const nextSeasonStart = getSeasonStartDate(season + 1);
return new Date(
Date.UTC(
nextSeasonStart.getUTCFullYear(),
nextSeasonStart.getUTCMonth(),
nextSeasonStart.getUTCDate(),
0,
0,
0,
nextSeasonStart.getUTCMilliseconds() - 1000
)
);
};

const getPercentageInSeason = (timestamp, season) => {
const seasonStart = getSeasonStartDate(season);
const seasonEnd = getSeasonEndDate(season);
const totalDuration = seasonEnd - seasonStart;
const elapsedDuration = new Date(timestamp) - seasonStart;
return (elapsedDuration / totalDuration) * 100;
};

const calculateSeasonNow = () => {
const now_utc = new Date();
return calculateSeasonByTimestamp(now_utc);
};

const calculateSeasonByTimestamp = (timestamp) => {
const timestamp_utc = new Date(timestamp);
const timestamp_utc_month = (timestamp_utc.getUTCMonth() + 1) % 12;
const timestamp_utc_year =
timestamp_utc.getUTCFullYear() + (timestamp_utc_month === 0 ? 1 : 0);
return (
4 * (timestamp_utc_year - 2022) + Math.floor(timestamp_utc_month / 3) - 3
);
};

const getSeasonName = (season_number, t) => {
const season_offset = season_number + 2;
const season_index = season_offset % 4;
const year = 2022 + Math.floor(season_offset / 4);
const season_names = [t("spring"), t("summer"), t("autumn"), t("winter")];
return t("format_short")
.replace("%SEASON%", season_names[season_index])
.replace("%YEAR%", year);
};

export {
getSeasonStartDate,
getSeasonEndDate,
getPercentageInSeason,
calculateSeasonNow,
calculateSeasonByTimestamp,
getSeasonName,
};
Loading

0 comments on commit 7486f90

Please sign in to comment.