sendou.ink/app/components/BuildCard.tsx
Kalle c2e43fb535
X Rank placements (#1325)
* Read in x rank placements script

* Xsearch initial

* XSearch initial

* XSearch with select

* Add badges

* XSearch player page initial

* Consider only one build by user for popular builds Closes #1312

* Revert "Side nav labels on hover (#1290)"

This reverts commit 6e839c6c2d.

* Added and fixed DAnish translations (#1315)

* Added Danish translations

Added Danish translations for the builds.json file.

Translations for the following variables have been added
stats.count.title # used the Danish word for "average" instead of "stats", as it makes more sense in Danish.
stats.ap.title
stats.percentage.title
stats.all
linkButton.abilityStats
linkButton.popularBuilds
noPopularBuilds

* Added Danish translations

Added Danish translations in the calander.json file

tag.desc.SZ
tag.desc.TW
tag.desc.S1
tag.desc.S2
tag.desc.SR
tag.desc.CARDS

* Added Danish translations

Added Danish translations in the common.json file

The following translations have been added
pages.vods
tag.name.SZ
tag.name.TW
tag.name.S1
tag.name.S2
tag.name.SR
tag.name.CARDS

* Added Danish translations in the faq.json file

Added the following Danish translations in the faq.json file

q7
a7

* Updated the DA/game-misc.json file

Added the names for the 3.0 maps.

* Added Danish translation for team.json

Added the following Danish translations for team.json file
- roles.MIDLINE

Fixed the following translation for the team.json file
- "roles.FRONTLINE": # fixed a typo

* Created a vods.json file for the Danish trans

* Fixed typo

Fixed the following translation in the Da\contributions.json file

"yaga" # fixed a Typo of "våben"

* Add Chinese Translation (#1314)

* Update contributions.json

* Update contributions.json

* Update faq.json

* Update user.json

* Create team.json

* Update common.json

* Update analyzer.json

* Prettier

* Prettier

* Prettier

* Update builds.json

* Update calendar.json

* Update common.json

* Update faq.json

* Update team.json

* Create vods.json

* Admin link player action

* Make PlacementTable rounded if only child

* Fix arrow disappeared

* Placements on user page initial

* Remove S2 site link

* Add badge

* Add feature to README

* Fix type error

* Different badge text if XP badge

* Add badge winners script

* Better user avatar + name wrapping for mobile

* Allow script to skip users

* Fix bad align when no weapons in weapon pool

* Add aliases to player name

* Add division icon to placements table

* Link to user page

* Link to xsearch on player details page

* Top 500 icons to user build page

* Working query but slow for weapons page

* Fix lint complaints

* Add cache to builds

* Remove useless SWR value

* Group months in xsearch

* Add xsearch to nav

* Add meta

* Remove TW for now

* Add splatoon3.ink as contributor

* Remove unneeded TODO

* Fix TODOs related to fetching monthYears

* Add FAQ question

* Leaderboards to nav

* Fix sploosh build stat pages returning 404

* Add badge

* Add article

* Patch 3.1

* Fix Prettier issue

* Add badge

* Add April SR gear

* Add badges

* Rename badge

* Add badge

* Add zh badge translation (#1322)

* Splatoon Competitive Guide article (#1316)

* Create splatoon-competitive-guide.md

* Update splatoon-competitive-guide.md

* Fix ToC Issues

* Update splatoon-competitive-guide.md

* Update splatoon-competitive-guide.md

* Update splatoon-competitive-guide.md

* Corrections

* Formatting

---------

Co-authored-by: Kalle <38327916+Sendouc@users.noreply.github.com>

* Fix typos

* Rename table and adjust columns

* Make cache ttl 0 in dev

* Make placements container a bit nicer for mobile

* Rename leftover xxx

* Placements script allow passing number as arg

* Skip leaderboards for now

* Add translations

* Rename placements folder to top-search

* Add placements to seed

* Add E2E tests

* Read in x rank placements script

* Xsearch initial

* XSearch initial

* XSearch with select

* XSearch player page initial

* Admin link player action

* Make PlacementTable rounded if only child

* Fix arrow disappeared

* Placements on user page initial

* Fix bad align when no weapons in weapon pool

* Add aliases to player name

* Add division icon to placements table

* Link to user page

* Link to xsearch on player details page

* Top 500 icons to user build page

* Working query but slow for weapons page

* Fix lint complaints

* Add cache to builds

* Remove useless SWR value

* Group months in xsearch

* Add xsearch to nav

* Add meta

* Remove TW for now

* Add splatoon3.ink as contributor

* Remove unneeded TODO

* Fix TODOs related to fetching monthYears

* Add FAQ question

* Leaderboards to nav

* Fix sploosh build stat pages returning 404

* Rename table and adjust columns

* Make cache ttl 0 in dev

* Make placements container a bit nicer for mobile

* Rename leftover xxx

* Placements script allow passing number as arg

* Skip leaderboards for now

* Add translations

* Rename placements folder to top-search

* Add placements to seed

* Add E2E tests

* Rename url variable and unify

* Tweaks

---------

Co-authored-by: Frederik <112972665+FrederikFraJylland@users.noreply.github.com>
Co-authored-by: Gell <61431488+gellneko@users.noreply.github.com>
Co-authored-by: Zen <99558412+zenpk@users.noreply.github.com>
Co-authored-by: Teddi <83455454+teddinotteddy@users.noreply.github.com>
2023-04-07 21:02:18 +03:00

273 lines
7.3 KiB
TypeScript

import clsx from "clsx";
import { useTranslation } from "~/hooks/useTranslation";
import { Link } from "react-router-dom";
import type {
Build,
BuildWeapon,
GearType,
UserWithPlusTier,
} from "~/db/types";
import { useIsMounted } from "~/hooks/useIsMounted";
import { useUser } from "~/modules/auth";
import type {
Ability as AbilityType,
ModeShort,
} from "~/modules/in-game-lists";
import type { BuildAbilitiesTuple } from "~/modules/in-game-lists/types";
import { databaseTimestampToDate } from "~/utils/dates";
import { discordFullName, gearTypeToInitial } from "~/utils/strings";
import {
analyzerPage,
gearImageUrl,
mainWeaponImageUrl,
modeImageUrl,
mySlugify,
navIconUrl,
userBuildsPage,
weaponBuildPage,
} from "~/utils/urls";
import { Ability } from "./Ability";
import { Button, LinkButton } from "./Button";
import { FormWithConfirm } from "./FormWithConfirm";
import { TrashIcon } from "./icons/Trash";
import { EditIcon } from "./icons/Edit";
import { Image } from "./Image";
import { Popover } from "./Popover";
import { InfoIcon } from "./icons/Info";
import type { BuildWeaponWithTop500Info } from "~/db/models/builds/queries.server";
interface BuildProps {
build: Pick<
Build,
| "id"
| "title"
| "description"
| "clothesGearSplId"
| "headGearSplId"
| "shoesGearSplId"
| "updatedAt"
> & {
abilities: BuildAbilitiesTuple;
modes: ModeShort[] | null;
weapons: Array<{
weaponSplId: BuildWeapon["weaponSplId"];
minRank: number | null;
maxPower: number | null;
}>;
};
owner?: Pick<
UserWithPlusTier,
"discordId" | "discordName" | "discordDiscriminator" | "plusTier"
>;
canEdit?: boolean;
}
export function BuildCard({ build, owner, canEdit = false }: BuildProps) {
const user = useUser();
const { t } = useTranslation(["weapons", "builds", "common", "game-misc"]);
const { i18n } = useTranslation();
const isMounted = useIsMounted();
const {
id,
title,
description,
clothesGearSplId,
headGearSplId,
shoesGearSplId,
updatedAt,
abilities,
modes,
weapons,
} = build;
return (
<div className="build">
<div>
<div className="build__top-row">
{modes && modes.length > 0 && (
<div className="build__modes">
{modes.map((mode) => (
<Image
key={mode}
alt={t(`game-misc:MODE_LONG_${mode}` as any)}
title={t(`game-misc:MODE_LONG_${mode}` as any)}
path={modeImageUrl(mode)}
width={18}
height={18}
/>
))}
</div>
)}
<h2 className="build__title">{title}</h2>
</div>
<div className="build__date-author-row">
{owner && (
<>
<Link to={userBuildsPage(owner)}>{discordFullName(owner)}</Link>
<div></div>
</>
)}
{owner?.plusTier ? (
<>
<span>+{owner.plusTier}</span>
<div></div>
</>
) : null}
<time className={clsx({ invisible: !isMounted })}>
{isMounted
? databaseTimestampToDate(updatedAt).toLocaleDateString(
i18n.language,
{
day: "numeric",
month: "numeric",
year: "numeric",
}
)
: "t"}
</time>
</div>
</div>
<div className="build__weapons">
{weapons.map((weapon) => (
<RoundWeaponImage key={weapon.weaponSplId} weapon={weapon} />
))}
{weapons.length === 1 && (
<div className="build__weapon-text">
{t(`weapons:MAIN_${weapons[0]!.weaponSplId}` as any)}
</div>
)}
</div>
<div className="build__gear-abilities">
<AbilitiesRowWithGear
gearType="HEAD"
abilities={abilities[0]}
gearId={headGearSplId}
/>
<AbilitiesRowWithGear
gearType="CLOTHES"
abilities={abilities[1]}
gearId={clothesGearSplId}
/>
<AbilitiesRowWithGear
gearType="SHOES"
abilities={abilities[2]}
gearId={shoesGearSplId}
/>
</div>
<div className="build__bottom-row">
<Link
to={analyzerPage({
weaponId: weapons[0]!.weaponSplId,
abilities: abilities.flat(),
})}
>
<Image
alt={t("common:pages.analyzer")}
className="build__icon"
path={navIconUrl("analyzer")}
/>
</Link>
{description ? (
<Popover
buttonChildren={<InfoIcon className="build__icon" />}
triggerClassName="minimal tiny build__small-text"
>
{description}
</Popover>
) : null}
{canEdit && (
<>
<LinkButton
className="build__small-text"
variant="minimal"
size="tiny"
to={`new?buildId=${id}&userId=${user!.id}`}
>
<EditIcon className="build__icon" />
</LinkButton>
<FormWithConfirm
dialogHeading={t("builds:deleteConfirm", { title })}
fields={[["buildToDeleteId", id]]}
>
<Button
className="build__small-text"
variant="minimal-destructive"
size="tiny"
type="submit"
>
<TrashIcon className="build__icon" />
</Button>
</FormWithConfirm>
</>
)}
</div>
</div>
);
}
function RoundWeaponImage({ weapon }: { weapon: BuildWeaponWithTop500Info }) {
const { weaponSplId, maxPower, minRank } = weapon;
const { t } = useTranslation(["weapons"]);
const slug = mySlugify(t(`weapons:MAIN_${weaponSplId}`, { lng: "en" }));
const isTop500 = typeof maxPower === "number" && typeof minRank === "number";
return (
<div key={weaponSplId} className="build__weapon">
{isTop500 ? (
<Image
className="build__top500"
path={navIconUrl("xsearch")}
alt=""
title={`Max X Power: ${maxPower} | Best Rank: ${minRank}`}
height={24}
width={24}
testId="top500-crown"
/>
) : null}
<Link to={weaponBuildPage(slug)}>
<Image
path={mainWeaponImageUrl(weaponSplId)}
alt={t(`weapons:MAIN_${weaponSplId}` as any)}
title={t(`weapons:MAIN_${weaponSplId}` as any)}
height={36}
width={36}
/>
</Link>
</div>
);
}
function AbilitiesRowWithGear({
gearType,
abilities,
gearId,
}: {
gearType: GearType;
abilities: AbilityType[];
gearId: number;
}) {
const { t } = useTranslation(["gear"]);
const translatedGearName = t(
`gear:${gearTypeToInitial(gearType)}_${gearId}` as any
);
return (
<>
<Image
height={64}
width={64}
alt={translatedGearName}
title={translatedGearName}
path={gearImageUrl(gearType, gearId)}
className="build__gear"
/>
{abilities.map((ability, i) => (
<Ability key={i} ability={ability} size={i === 0 ? "MAIN" : "SUB"} />
))}
</>
);
}