mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-21 02:37:05 +02:00
144 lines
4.4 KiB
TypeScript
144 lines
4.4 KiB
TypeScript
import { tankIcon } from "@blitzkit/core";
|
|
import type { TankDefinition } from "@blitzkit/protos";
|
|
import { useEffect, useRef } from "react";
|
|
import { Vector2 } from "three";
|
|
import { useLocale } from "../../hooks/useLocale";
|
|
import { CompareEphemeral } from "../../stores/compareEphemeral";
|
|
import { InsertionMarker } from "../Compare/IntersectionMarker";
|
|
import { insertionMarkers } from "../Compare/IntersectionMarker/constants";
|
|
import { TankControl } from "../Compare/TankControl";
|
|
import { Flex } from "../Flex";
|
|
import { StickyColumnHeaderCell } from "../StickyColumnHeaderCell";
|
|
import { Text } from "../Text";
|
|
import styles from "./index.module.css";
|
|
|
|
interface TankCardProps {
|
|
index: number;
|
|
tank: TankDefinition;
|
|
}
|
|
|
|
export function CompareTankCard({ index, tank }: TankCardProps) {
|
|
const draggable = useRef<HTMLDivElement>(null);
|
|
const { unwrap } = useLocale();
|
|
|
|
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));
|
|
window.addEventListener("pointermove", handlePointerMove);
|
|
window.addEventListener("pointerup", handlePointerUp);
|
|
}
|
|
function handlePointerMove(event: PointerEvent) {
|
|
event.preventDefault();
|
|
|
|
current.set(event.clientX, event.clientY);
|
|
delta.copy(current).sub(initial);
|
|
insertionMarkers.forEach(({ element }) => {
|
|
element.style.opacity = "0";
|
|
});
|
|
|
|
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) =>
|
|
a.distance < b.distance ? a : b,
|
|
);
|
|
|
|
dropIndex = closest.index;
|
|
closest.element.style.opacity = "1";
|
|
draggable.current!.style.position = "fixed";
|
|
draggable.current!.style.cursor = "grabbing";
|
|
draggable.current!.style.zIndex = "1";
|
|
draggable.current!.style.left = `${initialRect.left + delta.x}px`;
|
|
draggable.current!.style.top = `${initialRect.top + delta.y}px`;
|
|
}
|
|
function handlePointerUp(event: PointerEvent) {
|
|
event.preventDefault();
|
|
|
|
draggable.current!.style.position = "static";
|
|
draggable.current!.style.zIndex = "unset";
|
|
draggable.current!.style.cursor = "grab";
|
|
|
|
insertionMarkers.forEach(({ element }) => {
|
|
element.style.opacity = "0";
|
|
});
|
|
|
|
window.removeEventListener("pointermove", handlePointerMove);
|
|
window.removeEventListener("pointerup", handlePointerUp);
|
|
|
|
if (dropIndex === -1) return;
|
|
|
|
CompareEphemeral.mutate((draft) => {
|
|
const fromIndex = index;
|
|
const toIndex = dropIndex > index ? dropIndex - 1 : dropIndex;
|
|
const member = draft.members[fromIndex];
|
|
|
|
draft.members.splice(fromIndex, 1);
|
|
draft.members.splice(toIndex, 0, member);
|
|
});
|
|
|
|
dropIndex = -1;
|
|
}
|
|
|
|
draggable.current?.addEventListener("pointerdown", handlePointerDown);
|
|
|
|
return () => {
|
|
draggable.current?.removeEventListener("pointerdown", handlePointerDown);
|
|
};
|
|
}, [index]);
|
|
|
|
return (
|
|
<StickyColumnHeaderCell width="0">
|
|
<Flex column align="center" justify="between" gap="2">
|
|
<TankControl index={index} key={tank.id} slug={tank.slug} />
|
|
|
|
<Flex
|
|
ref={draggable}
|
|
column
|
|
align="center"
|
|
justify="between"
|
|
gap="2"
|
|
style={{
|
|
userSelect: "none",
|
|
touchAction: "none",
|
|
cursor: "grab",
|
|
}}
|
|
>
|
|
<img
|
|
alt={unwrap(tank.name!)}
|
|
src={tankIcon(tank.id)}
|
|
width={64}
|
|
height={64}
|
|
draggable={false}
|
|
style={{
|
|
objectFit: "contain",
|
|
}}
|
|
/>
|
|
|
|
<Text className={styles.name} nowrap overflow="ellipsis">
|
|
{unwrap(tank.name!)}
|
|
</Text>
|
|
</Flex>
|
|
</Flex>
|
|
<InsertionMarker index={index + 1} />
|
|
</StickyColumnHeaderCell>
|
|
);
|
|
}
|