Files
blitzkit/packages/website/src/pages/[...locale]/mixer/_index.tsx
T

196 lines
5.0 KiB
TypeScript
Raw Normal View History

2025-09-18 10:41:42 -05:00
import { asset } from "@blitzkit/core";
2025-09-22 13:50:46 -05:00
import { CaretRightIcon, UpdateIcon } from "@radix-ui/react-icons";
2025-09-18 10:41:42 -05:00
import {
Box,
Button,
Dialog,
Flex,
Skeleton,
Text,
type ButtonProps,
} from "@radix-ui/themes";
import { useRef, useState } from "react";
2025-09-01 19:19:44 -05:00
import { MixerScene } from "../../../components/MixerScene";
2025-09-18 10:41:42 -05:00
import { PageWrapper } from "../../../components/PageWrapper";
2025-09-22 13:50:46 -05:00
import { ScreenshotButton } from "../../../components/ScreenshotButton";
2025-09-18 10:41:42 -05:00
import {
TankSearch,
type TankSearchProps,
} from "../../../components/TankSearch";
2025-09-22 13:50:46 -05:00
import { curateMixer } from "../../../core/blitzkit/curateMixer";
2025-08-28 16:12:40 -05:00
import {
LocaleProvider,
2025-09-18 10:41:42 -05:00
useLocale,
2025-08-28 16:12:40 -05:00
type LocaleAcceptorProps,
2025-09-01 19:19:44 -05:00
} from "../../../hooks/useLocale";
import { Mixer } from "../../../stores/mixer";
2025-09-18 10:41:42 -05:00
import type { MaybeSkeletonComponentProps } from "../../../types/maybeSkeletonComponentProps";
2025-08-28 16:12:40 -05:00
2025-09-18 10:41:42 -05:00
export function Page({
locale,
skeleton,
}: LocaleAcceptorProps & MaybeSkeletonComponentProps) {
2025-09-22 13:50:46 -05:00
const initial = useRef(curateMixer());
2025-08-28 16:12:40 -05:00
2025-09-22 13:50:46 -05:00
Mixer.useInitialization(initial.current);
2025-09-01 19:19:44 -05:00
2025-08-28 16:12:40 -05:00
return (
2025-09-18 10:41:42 -05:00
<PageWrapper color="gray" p="0" maxWidth="unset">
<LocaleProvider locale={locale}>
<Content skeleton={skeleton} />
</LocaleProvider>
</PageWrapper>
2025-08-28 16:12:40 -05:00
);
}
2025-09-18 10:41:42 -05:00
function ModuleButton({
skeleton,
tank,
onSelect,
...props
}: Omit<ButtonProps, "onSelect"> &
MaybeSkeletonComponentProps & {
tank: number;
onSelect: TankSearchProps["onSelect"];
}) {
const { strings } = useLocale();
const [open, setOpen] = useState(false);
return (
<Dialog.Root open={open} onOpenChange={setOpen}>
<Dialog.Trigger>
<Button
variant="surface"
size="4"
style={{ overflow: "clip" }}
{...props}
>
{skeleton ? (
<Skeleton width="4em" height="2em" />
) : (
<img
style={{
width: "4em",
height: "4em",
objectFit: "contain",
}}
src={asset(`icons/tanks/big/${tank}.webp`)}
/>
)}
</Button>
</Dialog.Trigger>
<Dialog.Content>
<Dialog.Title>
{strings.website.common.tank_search.select_dialog_title}
</Dialog.Title>
<TankSearch
compact
onSelect={(tank) => {
onSelect?.(tank);
setOpen(false);
}}
/>
</Dialog.Content>
</Dialog.Root>
);
}
function ModuleSeparator() {
return (
<Text size="5" color="gray" trim="end">
<CaretRightIcon width="1em" height="1em" />
</Text>
);
}
function Content({ skeleton }: MaybeSkeletonComponentProps) {
2025-09-22 13:50:46 -05:00
const hull = Mixer.use((state) => state.hull);
2025-09-18 10:41:42 -05:00
const turretId = Mixer.use((state) => state.turret.tank.id);
const gunId = Mixer.use((state) => state.gun.tank.id);
2025-09-22 13:50:46 -05:00
const canvas = useRef<HTMLCanvasElement>(null);
const { strings } = useLocale();
2025-09-18 10:41:42 -05:00
2025-08-28 16:12:40 -05:00
return (
2025-09-13 23:42:26 -05:00
<Box flexGrow="1" position="relative">
<Box position="absolute" width="100%" height="100%" top="0" left="0">
2025-09-22 13:50:46 -05:00
{!skeleton && <MixerScene ref={canvas} />}
2025-08-28 16:12:40 -05:00
</Box>
2025-09-18 10:41:42 -05:00
<Flex
align="center"
2025-09-22 13:50:46 -05:00
direction="column"
2025-09-18 10:41:42 -05:00
position="absolute"
bottom="4"
left="50%"
style={{ transform: "translateX(-50%)" }}
2025-09-22 13:50:46 -05:00
gap="4"
2025-09-18 10:41:42 -05:00
>
2025-09-22 13:50:46 -05:00
<Flex gap="2">
<ScreenshotButton
name={hull.slug}
canvas={canvas}
highContrast
variant="surface"
>
{strings.website.tools.mixer.screenshot}
</ScreenshotButton>
<Button
highContrast
variant="surface"
onClick={() => {
const mixed = curateMixer();
Mixer.mutate((draft) => {
Object.assign(draft, mixed);
});
}}
>
<UpdateIcon />
{strings.website.tools.mixer.randomize}
</Button>
</Flex>
<Flex gap="2" align="center">
<ModuleButton
skeleton={skeleton}
tank={hull.id}
onSelect={(tank) => {
Mixer.mutate((draft) => {
draft.hull = tank;
});
}}
/>
<ModuleSeparator />
<ModuleButton
skeleton={skeleton}
tank={turretId}
onSelect={(tank) => {
Mixer.mutate((draft) => {
draft.turret = {
tank,
turret: tank.turrets.at(-1)!,
};
});
}}
/>
<ModuleSeparator />
<ModuleButton
skeleton={skeleton}
tank={gunId}
onSelect={(tank) => {
Mixer.mutate((draft) => {
draft.gun = {
tank,
turret: tank.turrets.at(-1)!,
gun: tank.turrets.at(-1)!.guns.at(-1)!,
};
});
}}
/>
</Flex>
2025-09-18 10:41:42 -05:00
</Flex>
2025-09-13 23:42:26 -05:00
</Box>
2025-08-28 16:12:40 -05:00
);
}