mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-10-02 08:07:40 -05:00
KitCards for analyzer
This commit is contained in:
@@ -9,7 +9,7 @@ import type {
|
||||
} from "~/modules/in-game-lists";
|
||||
import type { BuildAbilitiesTuple } from "~/modules/in-game-lists/types";
|
||||
import { databaseTimestampToDate } from "~/utils/dates";
|
||||
import { gearImageUrl, modeImageUrl, weaponImageUrl } from "~/utils/urls";
|
||||
import { gearImageUrl, modeImageUrl, mainWeaponImageUrl } from "~/utils/urls";
|
||||
import { Ability } from "./Ability";
|
||||
import { Button, LinkButton } from "./Button";
|
||||
import { FormWithConfirm } from "./FormWithConfirm";
|
||||
@@ -128,7 +128,7 @@ export function BuildCard({
|
||||
{weapons.map((weaponSplId) => (
|
||||
<div key={weaponSplId} className="build__weapon">
|
||||
<Image
|
||||
path={weaponImageUrl(weaponSplId)}
|
||||
path={mainWeaponImageUrl(weaponSplId)}
|
||||
alt={t(`weapons:${weaponSplId}` as any)}
|
||||
title={t(`weapons:${weaponSplId}` as any)}
|
||||
height={36}
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
shoesGearIds,
|
||||
mainWeaponIds,
|
||||
} from "~/modules/in-game-lists";
|
||||
import { gearImageUrl, weaponImageUrl } from "~/utils/urls";
|
||||
import { gearImageUrl, mainWeaponImageUrl } from "~/utils/urls";
|
||||
import { Image } from "./Image";
|
||||
|
||||
const MAX_RESULTS_SHOWN = 6;
|
||||
@@ -36,6 +36,7 @@ interface ComboboxProps<T> {
|
||||
onChange?: (selectedOption?: ComboboxOption<T>) => void;
|
||||
}
|
||||
|
||||
// xxx: should clear on click
|
||||
export function Combobox<T extends Record<string, string | null | number>>({
|
||||
options,
|
||||
inputName,
|
||||
@@ -201,7 +202,7 @@ export function WeaponCombobox({
|
||||
const idToWeapon = (id: typeof mainWeaponIds[number]) => ({
|
||||
value: String(id),
|
||||
label: t(`MAIN_${id}`),
|
||||
imgPath: weaponImageUrl(id),
|
||||
imgPath: mainWeaponImageUrl(id),
|
||||
});
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { Ability } from "~/modules/in-game-lists";
|
||||
import type { Ability, MainWeaponId } from "~/modules/in-game-lists";
|
||||
import type allTags from "../routes/calendar/tags.json";
|
||||
|
||||
export interface User {
|
||||
@@ -133,7 +133,7 @@ export interface Build {
|
||||
|
||||
export interface BuildWeapon {
|
||||
buildId: number;
|
||||
weaponSplId: number;
|
||||
weaponSplId: MainWeaponId;
|
||||
}
|
||||
|
||||
export type GearType = "HEAD" | "CLOTHES" | "SHOES";
|
||||
|
||||
@@ -3,6 +3,7 @@ export type {
|
||||
MainWeaponParams,
|
||||
SubWeaponParams,
|
||||
Stat,
|
||||
AnalyzedBuild,
|
||||
} from "./types";
|
||||
|
||||
export { useAnalyzeBuild } from "./useAnalyzeBuild";
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import type {
|
||||
Ability,
|
||||
MainWeaponId,
|
||||
SpecialWeaponId,
|
||||
SubWeaponId,
|
||||
} from "~/modules/in-game-lists";
|
||||
|
||||
export interface MainWeaponParams {
|
||||
subWeaponId: SubWeaponId;
|
||||
specialWeaponId: number;
|
||||
specialWeaponId: SpecialWeaponId;
|
||||
internalName: string;
|
||||
/** Replacing default values of the ability json for this specific weapon */
|
||||
overwrites?: Record<string, Partial<Record<"High" | "Mid" | "Low", number>>>;
|
||||
@@ -110,8 +111,8 @@ type InkConsumePercentageType =
|
||||
|
||||
export interface AnalyzedBuild {
|
||||
weapon: {
|
||||
subWeaponSplId: number;
|
||||
specialWeaponSplId: number;
|
||||
subWeaponSplId: SubWeaponId;
|
||||
specialWeaponSplId: SpecialWeaponId;
|
||||
};
|
||||
stats: {
|
||||
specialPoint: Stat;
|
||||
|
||||
@@ -12,20 +12,20 @@ export function useAnalyzeBuild() {
|
||||
["UNKNOWN", "UNKNOWN", "UNKNOWN", "UNKNOWN"],
|
||||
["UNKNOWN", "UNKNOWN", "UNKNOWN", "UNKNOWN"],
|
||||
]);
|
||||
const [weaponId, setWeaponId] = React.useState<MainWeaponId>(
|
||||
const [mainWeaponId, setMainWeaponId] = React.useState<MainWeaponId>(
|
||||
mainWeaponIds[0]
|
||||
);
|
||||
|
||||
const analyzed = React.useMemo(
|
||||
() => buildStats({ build, weaponSplId: weaponId }),
|
||||
[build, weaponId]
|
||||
() => buildStats({ build, weaponSplId: mainWeaponId }),
|
||||
[build, mainWeaponId]
|
||||
);
|
||||
|
||||
return {
|
||||
build,
|
||||
setBuild,
|
||||
weaponId,
|
||||
setWeaponId,
|
||||
mainWeaponId,
|
||||
setMainWeaponId,
|
||||
analyzed,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
export { stages } from "./stages";
|
||||
export { modes, modesShort } from "./modes";
|
||||
export { mainWeaponIds, subWeaponIds } from "./weapon-ids";
|
||||
export { mainWeaponIds, subWeaponIds, specialWeaponIds } from "./weapon-ids";
|
||||
export { headGearIds, clothesGearIds, shoesGearIds } from "./gear-ids";
|
||||
export { abilitiesShort, abilities } from "./abilities";
|
||||
export type {
|
||||
@@ -12,4 +12,5 @@ export type {
|
||||
BuildAbilitiesTupleWithUnknown,
|
||||
MainWeaponId,
|
||||
SubWeaponId,
|
||||
SpecialWeaponId,
|
||||
} from "./types";
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import type { abilities } from "./abilities";
|
||||
import type { modes } from "./modes";
|
||||
import type { stages } from "./stages";
|
||||
import type { subWeaponIds, mainWeaponIds } from "./weapon-ids";
|
||||
import type {
|
||||
subWeaponIds,
|
||||
mainWeaponIds,
|
||||
specialWeaponIds,
|
||||
} from "./weapon-ids";
|
||||
|
||||
export type ModeShort = typeof modes[number]["short"];
|
||||
|
||||
@@ -13,6 +17,7 @@ export type AbilityType = typeof abilities[number]["type"];
|
||||
|
||||
export type MainWeaponId = typeof mainWeaponIds[number];
|
||||
export type SubWeaponId = typeof subWeaponIds[number];
|
||||
export type SpecialWeaponId = typeof specialWeaponIds[number];
|
||||
|
||||
export type BuildAbilitiesTuple = [
|
||||
head: [main: Ability, s1: Ability, s2: Ability, s3: Ability],
|
||||
|
||||
@@ -8,3 +8,7 @@ export const mainWeaponIds = [
|
||||
export const subWeaponIds = [
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,
|
||||
] as const;
|
||||
|
||||
export const specialWeaponIds = [
|
||||
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
|
||||
] as const;
|
||||
|
||||
@@ -6,8 +6,11 @@ import { WeaponCombobox } from "~/components/Combobox";
|
||||
import { Main } from "~/components/Main";
|
||||
import type { Stat } from "~/modules/analyzer";
|
||||
import { useAnalyzeBuild } from "~/modules/analyzer";
|
||||
import type { AnalyzedBuild } from "~/modules/analyzer";
|
||||
import type { MainWeaponId } from "~/modules/in-game-lists";
|
||||
import styles from "~/styles/analyzer.css";
|
||||
import { Image } from "~/components/Image";
|
||||
import { specialWeaponImageUrl, subWeaponImageUrl } from "~/utils/urls";
|
||||
|
||||
export const links: LinksFunction = () => {
|
||||
return [{ rel: "stylesheet", href: styles }];
|
||||
@@ -19,7 +22,7 @@ export const handle = {
|
||||
|
||||
export default function BuildAnalyzerPage() {
|
||||
const { t } = useTranslation("analyzer");
|
||||
const { build, setBuild, weaponId, setWeaponId, analyzed } =
|
||||
const { build, setBuild, mainWeaponId, setMainWeaponId, analyzed } =
|
||||
useAnalyzeBuild();
|
||||
|
||||
if (process.env.NODE_ENV === "production") return <Main>Coming soon :)</Main>;
|
||||
@@ -28,14 +31,16 @@ export default function BuildAnalyzerPage() {
|
||||
<Main>
|
||||
<div className="analyzer__container">
|
||||
<div className="stack lg items-center">
|
||||
<div>
|
||||
<div className="stack sm items-center w-full">
|
||||
<WeaponCombobox
|
||||
inputName="weapon"
|
||||
initialWeaponId={weaponId}
|
||||
initialWeaponId={mainWeaponId}
|
||||
onChange={(opt) =>
|
||||
opt && setWeaponId(Number(opt.value) as MainWeaponId)
|
||||
opt && setMainWeaponId(Number(opt.value) as MainWeaponId)
|
||||
}
|
||||
className="w-full-important"
|
||||
/>
|
||||
<KitCards analyzed={analyzed} />
|
||||
</div>
|
||||
<AbilitiesSelector selectedAbilities={build} onChange={setBuild} />
|
||||
</div>
|
||||
@@ -58,6 +63,33 @@ export default function BuildAnalyzerPage() {
|
||||
);
|
||||
}
|
||||
|
||||
function KitCards({ analyzed }: { analyzed: AnalyzedBuild }) {
|
||||
const { t } = useTranslation("weapons");
|
||||
|
||||
return (
|
||||
<div className="analyzer__kit-cards">
|
||||
<div className="analyzer__kit-card">
|
||||
<Image
|
||||
path={subWeaponImageUrl(analyzed.weapon.subWeaponSplId)}
|
||||
width={20}
|
||||
height={20}
|
||||
alt={t(`SUB_${analyzed.weapon.subWeaponSplId}`)}
|
||||
/>
|
||||
{t(`SUB_${analyzed.weapon.subWeaponSplId}`)}
|
||||
</div>
|
||||
<div className="analyzer__kit-card">
|
||||
<Image
|
||||
path={specialWeaponImageUrl(analyzed.weapon.specialWeaponSplId)}
|
||||
width={20}
|
||||
height={20}
|
||||
alt={t(`SPECIAL_${analyzed.weapon.specialWeaponSplId}`)}
|
||||
/>
|
||||
{t(`SPECIAL_${analyzed.weapon.specialWeaponSplId}`)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function StatCategory({
|
||||
title,
|
||||
children,
|
||||
|
||||
@@ -28,6 +28,7 @@ import {
|
||||
import type {
|
||||
BuildAbilitiesTuple,
|
||||
BuildAbilitiesTupleWithUnknown,
|
||||
MainWeaponId,
|
||||
} from "~/modules/in-game-lists/types";
|
||||
import { parseRequestFormData } from "~/utils/remix";
|
||||
import { modeImageUrl, userBuildsPage } from "~/utils/urls";
|
||||
@@ -141,7 +142,7 @@ export const action: ActionFunction = async ({ request }) => {
|
||||
clothesGearSplId: data["CLOTHES[value]"],
|
||||
shoesGearSplId: data["SHOES[value]"],
|
||||
modes: modesShort.filter((mode) => data[mode]),
|
||||
weaponSplIds: data["weapon[value]"],
|
||||
weaponSplIds: data["weapon[value]"] as Array<MainWeaponId>,
|
||||
ownerId: user.id,
|
||||
};
|
||||
if (data.buildToEditId) {
|
||||
|
||||
@@ -4,6 +4,26 @@
|
||||
grid-template-columns: 1fr 2fr;
|
||||
}
|
||||
|
||||
.analyzer__kit-cards {
|
||||
display: grid;
|
||||
color: var(--text-lighter);
|
||||
font-size: var(--fonts-xxs);
|
||||
font-weight: var(--semi-bold);
|
||||
gap: var(--s-2);
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
|
||||
.analyzer__kit-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: var(--theme-transparent);
|
||||
border-radius: var(--rounded);
|
||||
gap: var(--s-1-5);
|
||||
padding-block: var(--s-1);
|
||||
padding-inline: var(--s-1-5);
|
||||
}
|
||||
|
||||
.analyzer__summary {
|
||||
background-color: var(--bg-lighter);
|
||||
border-radius: var(--rounded);
|
||||
|
||||
@@ -38,6 +38,10 @@
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.w-full-important {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.w-24 {
|
||||
width: var(--s-24);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import type { Badge, GearType } from "~/db/types";
|
||||
import type { ModeShort } from "~/modules/in-game-lists";
|
||||
import type { AbilityWithUnknown } from "~/modules/in-game-lists/types";
|
||||
import type {
|
||||
AbilityWithUnknown,
|
||||
MainWeaponId,
|
||||
SpecialWeaponId,
|
||||
SubWeaponId,
|
||||
} from "~/modules/in-game-lists/types";
|
||||
|
||||
export const PLUS_SERVER_DISCORD_URL = "https://discord.gg/FW4dKrY";
|
||||
export const SENDOU_INK_DISCORD_URL = "https://discord.gg/sendou";
|
||||
@@ -51,8 +56,12 @@ export const articlePreviewUrl = (slug: string) =>
|
||||
export const navIconUrl = (navItem: string) => `/img/layout/${navItem}`;
|
||||
export const gearImageUrl = (gearType: GearType, gearSplId: number) =>
|
||||
`/img/gear/${gearType.toLowerCase()}/${gearSplId}`;
|
||||
export const weaponImageUrl = (weaponSplId: number) =>
|
||||
`/img/main-weapons/${weaponSplId}`;
|
||||
export const mainWeaponImageUrl = (mainWeaponSplId: MainWeaponId) =>
|
||||
`/img/main-weapons/${mainWeaponSplId}`;
|
||||
export const subWeaponImageUrl = (subWeaponSplId: SubWeaponId) =>
|
||||
`/img/sub-weapons/${subWeaponSplId}`;
|
||||
export const specialWeaponImageUrl = (specialWeaponSplId: SpecialWeaponId) =>
|
||||
`/img/special-weapons/${specialWeaponSplId}`;
|
||||
export const abilityImageUrl = (ability: AbilityWithUnknown) =>
|
||||
`/img/abilities/${ability}`;
|
||||
export const modeImageUrl = (mode: ModeShort) => `/img/modes/${mode}`;
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
// xxx: internal name can be deleted when to prod
|
||||
// xxx: finish merging with create-weapon-json
|
||||
|
||||
import type { SpecialWeaponId } from "~/modules/in-game-lists";
|
||||
import {
|
||||
type SubWeaponId,
|
||||
subWeaponIds,
|
||||
@@ -200,7 +201,7 @@ function resolveSpecialWeaponId(weapon: MainWeapon) {
|
||||
`Could not find special weapon for '${codeName}'`
|
||||
);
|
||||
|
||||
return specialWeaponObj.Id;
|
||||
return specialWeaponObj.Id as SpecialWeaponId;
|
||||
}
|
||||
|
||||
const overwriteSchema = z.object({
|
||||
|
||||
Reference in New Issue
Block a user