From bfa937178602fa9223d9256cb45707db1c78dd6e Mon Sep 17 00:00:00 2001 From: Kalle <38327916+Sendouc@users.noreply.github.com> Date: Tue, 5 Sep 2023 21:03:36 +0300 Subject: [PATCH] SendouQ top 10 special display (#1483) * Component initial * TopTeanPlayer component * Add to seasons page * Finish --- app/components/Image.tsx | 9 +- app/components/Placement.tsx | 4 +- .../leaderboards/components/TopTenPlayer.tsx | 77 ++++++++++++ .../leaderboards/core/leaderboards.server.ts | 7 +- .../leaderboards/leaderboards-utils.ts | 37 ++++++ .../leaderboards/routes/leaderboards.tsx | 112 +++++++++++------- app/features/leaderboards/top-ten.json | 94 +++++++++++++++ app/routes/u.$identifier/seasons.tsx | 15 +++ app/styles/common.css | 25 ++++ app/styles/flags.css | 4 + app/styles/utils.css | 4 + app/utils/urls.ts | 9 ++ public/static-assets/img/winners/0/1.avif | Bin 0 -> 11143 bytes public/static-assets/img/winners/0/1.png | Bin 0 -> 108641 bytes public/static-assets/img/winners/0/10.avif | Bin 0 -> 14899 bytes public/static-assets/img/winners/0/10.png | Bin 0 -> 118514 bytes public/static-assets/img/winners/0/2.avif | Bin 0 -> 12771 bytes public/static-assets/img/winners/0/2.png | Bin 0 -> 124904 bytes public/static-assets/img/winners/0/3.avif | Bin 0 -> 13974 bytes public/static-assets/img/winners/0/3.png | Bin 0 -> 116491 bytes public/static-assets/img/winners/0/4.avif | Bin 0 -> 11826 bytes public/static-assets/img/winners/0/4.png | Bin 0 -> 106822 bytes public/static-assets/img/winners/0/5.avif | Bin 0 -> 17868 bytes public/static-assets/img/winners/0/5.png | Bin 0 -> 141842 bytes public/static-assets/img/winners/0/6.avif | Bin 0 -> 11249 bytes public/static-assets/img/winners/0/6.png | Bin 0 -> 107049 bytes public/static-assets/img/winners/0/7.avif | Bin 0 -> 15543 bytes public/static-assets/img/winners/0/7.png | Bin 0 -> 121078 bytes public/static-assets/img/winners/0/8.avif | Bin 0 -> 14689 bytes public/static-assets/img/winners/0/8.png | Bin 0 -> 136653 bytes public/static-assets/img/winners/0/9.avif | Bin 0 -> 12530 bytes public/static-assets/img/winners/0/9.png | Bin 0 -> 116057 bytes 32 files changed, 352 insertions(+), 45 deletions(-) create mode 100644 app/features/leaderboards/components/TopTenPlayer.tsx create mode 100644 app/features/leaderboards/leaderboards-utils.ts create mode 100644 app/features/leaderboards/top-ten.json create mode 100644 public/static-assets/img/winners/0/1.avif create mode 100644 public/static-assets/img/winners/0/1.png create mode 100644 public/static-assets/img/winners/0/10.avif create mode 100644 public/static-assets/img/winners/0/10.png create mode 100644 public/static-assets/img/winners/0/2.avif create mode 100644 public/static-assets/img/winners/0/2.png create mode 100644 public/static-assets/img/winners/0/3.avif create mode 100644 public/static-assets/img/winners/0/3.png create mode 100644 public/static-assets/img/winners/0/4.avif create mode 100644 public/static-assets/img/winners/0/4.png create mode 100644 public/static-assets/img/winners/0/5.avif create mode 100644 public/static-assets/img/winners/0/5.png create mode 100644 public/static-assets/img/winners/0/6.avif create mode 100644 public/static-assets/img/winners/0/6.png create mode 100644 public/static-assets/img/winners/0/7.avif create mode 100644 public/static-assets/img/winners/0/7.png create mode 100644 public/static-assets/img/winners/0/8.avif create mode 100644 public/static-assets/img/winners/0/8.png create mode 100644 public/static-assets/img/winners/0/9.avif create mode 100644 public/static-assets/img/winners/0/9.png diff --git a/app/components/Image.tsx b/app/components/Image.tsx index 2b8c49f9c..b4e30c855 100644 --- a/app/components/Image.tsx +++ b/app/components/Image.tsx @@ -21,6 +21,7 @@ interface ImageProps { height?: number; size?: number; style?: React.CSSProperties; + containerStyle?: React.CSSProperties; testId?: string; onClick?: () => void; } @@ -36,10 +37,16 @@ export function Image({ style, testId, containerClassName, + containerStyle, onClick, }: ImageProps) { return ( - + { @@ -32,6 +33,7 @@ export function Placement({ textClassName, size = 20, textOnly = false, + showAsSuperscript = true, }: PlacementProps) { const { t } = useTranslation(undefined, {}); @@ -44,7 +46,7 @@ export function Placement({ fallbackLng: [], }); - const isSuperscript = ordinalSuffix.startsWith("^"); + const isSuperscript = showAsSuperscript && ordinalSuffix.startsWith("^"); const ordinalSuffixText = ordinalSuffix.replace(/^\^/, ""); const iconPath = textOnly ? null : getSpecialPlacementIconPath(placement); diff --git a/app/features/leaderboards/components/TopTenPlayer.tsx b/app/features/leaderboards/components/TopTenPlayer.tsx new file mode 100644 index 000000000..6f1dc756a --- /dev/null +++ b/app/features/leaderboards/components/TopTenPlayer.tsx @@ -0,0 +1,77 @@ +import { Flag } from "~/components/Flag"; +import { Image } from "~/components/Image"; +import { Placement } from "~/components/Placement"; +import { winnersImageUrl } from "~/utils/urls"; +import playerData from "../top-ten.json"; +import invariant from "tiny-invariant"; +import clsx from "clsx"; + +export function TopTenPlayer({ + power, + placement, + season, + small = false, +}: { + power?: number; + placement: number; + season: number; + small?: boolean; +}) { + const data = playerData[season]?.[placement - 1]; + invariant(data, `No data for season ${season} and placement ${placement}`); + const { name, countryCode, transforms } = data; + + const transformMultiplier = small ? 1 / 3 : 1; + + return ( +
+
+ +
+
+
3 ? { marginBlockEnd: "-4px" } : undefined} + > + {placement <= 3 ? ( + + ) : null}{" "} + {" "} + place +
+ {!small ? ( + <> +
+ {name} +
+
+ {power} +
+ + ) : null} +
+
+ ); +} diff --git a/app/features/leaderboards/core/leaderboards.server.ts b/app/features/leaderboards/core/leaderboards.server.ts index aa0e26419..3f76f00bf 100644 --- a/app/features/leaderboards/core/leaderboards.server.ts +++ b/app/features/leaderboards/core/leaderboards.server.ts @@ -4,12 +4,17 @@ import type { SeasonPopularUsersWeapon } from "../queries/seasonPopularUsersWeap import type { MainWeaponId } from "~/modules/in-game-lists"; import { weaponCategories } from "~/modules/in-game-lists"; import type { TeamSPLeaderboardItem } from "../queries/teamSPLeaderboard.server"; +import { seasonHasTopTen } from "../leaderboards-utils"; export function addTiers(entries: UserSPLeaderboardItem[], season: number) { const tiers = freshUserSkills(season); const encounteredTiers = new Set(); - return entries.map((entry) => { + return entries.map((entry, i) => { + if (i < 10 && seasonHasTopTen(season)) { + return { ...entry, tier: undefined }; + } + const tier = tiers.userSkills[entry.id].tier; const tierKey = `${tier.name}${tier.isPlus ? "+" : ""}`; const tierAlreadyEncountered = encounteredTiers.has(tierKey); diff --git a/app/features/leaderboards/leaderboards-utils.ts b/app/features/leaderboards/leaderboards-utils.ts new file mode 100644 index 000000000..981bc0676 --- /dev/null +++ b/app/features/leaderboards/leaderboards-utils.ts @@ -0,0 +1,37 @@ +import playerData from "./top-ten.json"; + +export function seasonHasTopTen(season: number) { + return !!playerData[season]; +} + +export function playerTopTenData({ + season, + userId, +}: { + season: number; + userId: number; +}) { + for (const player of playerData[season] ?? []) { + if (player.id === userId) { + return player; + } + } + + return null; +} + +export function playerTopTenPlacement({ + season, + userId, +}: { + season: number; + userId: number; +}) { + for (const [i, player] of (playerData[season] ?? []).entries()) { + if (player.id === userId) { + return i + 1; + } + } + + return null; +} diff --git a/app/features/leaderboards/routes/leaderboards.tsx b/app/features/leaderboards/routes/leaderboards.tsx index 375b99a9c..d69a26803 100644 --- a/app/features/leaderboards/routes/leaderboards.tsx +++ b/app/features/leaderboards/routes/leaderboards.tsx @@ -53,6 +53,8 @@ import { seasonPopularUsersWeapon } from "../queries/seasonPopularUsersWeapon.se import { cachified } from "cachified"; import { cache, ttl } from "~/utils/cache.server"; import { HALF_HOUR_IN_MS } from "~/constants"; +import { TopTenPlayer } from "../components/TopTenPlayer"; +import { seasonHasTopTen } from "../leaderboards-utils"; export const handle: SendouRouteHandle = { i18n: ["vods"], @@ -157,6 +159,10 @@ export default function LeaderboardsPage() { const [searchParams, setSearchParams] = useSearchParams(); const data = useLoaderData(); + const isAllUserLeaderboard = + !searchParams.get(TYPE_SEARCH_PARAM_KEY) || + searchParams.get(TYPE_SEARCH_PARAM_KEY) === "USER"; + return (