Files
blitzkit/packages/website/src/components/CompareTankCard/index.tsx
T

143 lines
4.4 KiB
TypeScript
Raw Normal View History

2026-09-27 17:19:39 -05:00
import { tankIcon } from "@blitzkit/core";
import type { TankDefinition } from "@blitzkit/protos";
2025-09-01 23:05:22 -05:00
import { useEffect, useRef } from "react";
import { Vector2 } from "three";
import { useLocale } from "../../hooks/useLocale";
import { CompareEphemeral } from "../../stores/compareEphemeral";
2026-08-27 11:46:55 -05:00
import { InsertionMarker } from "../Compare/IntersectionMarker";
import { insertionMarkers } from "../Compare/IntersectionMarker/constants";
import { TankControl } from "../Compare/TankControl";
import { Flex } from "../Flex";
2025-09-01 23:05:22 -05:00
import { StickyColumnHeaderCell } from "../StickyColumnHeaderCell";
2026-08-27 11:46:55 -05:00
import { Text } from "../Text";
import styles from "./index.module.css";
2024-07-12 20:38:19 -05:00
interface TankCardProps {
index: number;
tank: TankDefinition;
}
2026-08-27 11:46:55 -05:00
export function CompareTankCard({ index, tank }: TankCardProps) {
2024-07-12 20:38:19 -05:00
const draggable = useRef<HTMLDivElement>(null);
2025-01-14 19:22:41 -06:00
const { unwrap } = useLocale();
2024-07-12 20:38:19 -05:00
useEffect(() => {
const initial = new Vector2();
const current = new Vector2();
const delta = new Vector2();
const marker = new Vector2();
let initialRect = draggable.current!.getBoundingClientRect();
let dropIndex = -1;
function handlePointerDown(event: PointerEvent) {
event.preventDefault();
initialRect = draggable.current!.getBoundingClientRect();
delta.set(0, 0);
current.copy(initial.set(event.clientX, event.clientY));
2025-09-01 23:05:22 -05:00
window.addEventListener("pointermove", handlePointerMove);
window.addEventListener("pointerup", handlePointerUp);
2024-07-12 20:38:19 -05:00
}
function handlePointerMove(event: PointerEvent) {
event.preventDefault();
current.set(event.clientX, event.clientY);
delta.copy(current).sub(initial);
insertionMarkers.forEach(({ element }) => {
2025-09-01 23:05:22 -05:00
element.style.opacity = "0";
2024-07-12 20:38:19 -05:00
});
const distances = insertionMarkers.map((insertionMarker) => {
const markerBounds = insertionMarker.element.getBoundingClientRect();
marker.set(markerBounds.left, markerBounds.top);
return {
...insertionMarker,
distance: marker.distanceTo(current),
};
});
const closest = distances.reduce((a, b) =>
2026-03-23 22:07:20 -05:00
a.distance < b.distance ? a : b,
2024-07-12 20:38:19 -05:00
);
dropIndex = closest.index;
2025-09-01 23:05:22 -05:00
closest.element.style.opacity = "1";
draggable.current!.style.position = "fixed";
draggable.current!.style.cursor = "grabbing";
draggable.current!.style.zIndex = "1";
2024-07-12 20:38:19 -05:00
draggable.current!.style.left = `${initialRect.left + delta.x}px`;
draggable.current!.style.top = `${initialRect.top + delta.y}px`;
}
function handlePointerUp(event: PointerEvent) {
event.preventDefault();
2025-09-01 23:05:22 -05:00
draggable.current!.style.position = "static";
draggable.current!.style.zIndex = "unset";
draggable.current!.style.cursor = "grab";
2024-07-12 20:38:19 -05:00
insertionMarkers.forEach(({ element }) => {
2025-09-01 23:05:22 -05:00
element.style.opacity = "0";
2024-07-12 20:38:19 -05:00
});
2025-09-01 23:05:22 -05:00
window.removeEventListener("pointermove", handlePointerMove);
window.removeEventListener("pointerup", handlePointerUp);
2024-07-12 20:38:19 -05:00
if (dropIndex === -1) return;
2025-09-01 23:05:22 -05:00
CompareEphemeral.mutate((draft) => {
2024-07-12 20:38:19 -05:00
const fromIndex = index;
const toIndex = dropIndex > index ? dropIndex - 1 : dropIndex;
const member = draft.members[fromIndex];
2026-03-23 22:07:20 -05:00
2024-07-12 20:38:19 -05:00
draft.members.splice(fromIndex, 1);
draft.members.splice(toIndex, 0, member);
});
dropIndex = -1;
}
2025-09-01 23:05:22 -05:00
draggable.current?.addEventListener("pointerdown", handlePointerDown);
2024-07-12 20:38:19 -05:00
return () => {
2025-09-01 23:05:22 -05:00
draggable.current?.removeEventListener("pointerdown", handlePointerDown);
2024-07-12 20:38:19 -05:00
};
2026-03-23 22:07:20 -05:00
}, [index]);
2024-07-12 20:38:19 -05:00
return (
2025-11-27 17:31:28 -06:00
<StickyColumnHeaderCell width="0">
2026-08-27 11:46:55 -05:00
<Flex column align="center" justify="between" gap="2">
2025-05-15 23:44:20 -05:00
<TankControl index={index} key={tank.id} slug={tank.slug} />
2024-07-12 20:38:19 -05:00
<Flex
ref={draggable}
2026-08-27 11:46:55 -05:00
column
2024-07-12 20:38:19 -05:00
align="center"
justify="between"
gap="2"
style={{
2025-09-01 23:05:22 -05:00
userSelect: "none",
touchAction: "none",
cursor: "grab",
2024-07-12 20:38:19 -05:00
}}
>
<img
2026-03-31 14:11:45 -05:00
alt={unwrap(tank.name!)}
2024-07-12 20:38:19 -05:00
src={tankIcon(tank.id)}
width={64}
height={64}
draggable={false}
style={{
2025-09-01 23:05:22 -05:00
objectFit: "contain",
2024-07-12 20:38:19 -05:00
}}
/>
2026-08-27 11:46:55 -05:00
<Text className={styles.name} nowrap overflow="ellipsis">
2026-03-31 14:11:45 -05:00
{unwrap(tank.name!)}
2024-07-12 20:38:19 -05:00
</Text>
</Flex>
</Flex>
<InsertionMarker index={index + 1} />
</StickyColumnHeaderCell>
);
}