2024-11-16 23:11:31 -06:00
|
|
|
import type { TankDefinition } from '@blitzkit/core';
|
2024-11-26 20:24:36 -06:00
|
|
|
import { Box } from '@radix-ui/themes';
|
2024-11-16 23:11:31 -06:00
|
|
|
import {
|
|
|
|
|
useCallback,
|
2024-11-25 13:58:52 -06:00
|
|
|
useEffect,
|
2024-11-16 23:11:31 -06:00
|
|
|
useRef,
|
|
|
|
|
type PointerEvent as ReactPointerEvent,
|
|
|
|
|
} from 'react';
|
2024-11-17 16:34:18 -06:00
|
|
|
import { TierList } from '../../stores/tierList';
|
2024-11-16 23:11:31 -06:00
|
|
|
import { TankCard } from '../TankCard';
|
2024-11-25 13:58:52 -06:00
|
|
|
import { tierListCardElements, tierListRowElements } from './Table/constants';
|
2024-11-16 23:11:31 -06:00
|
|
|
|
2024-11-25 13:58:52 -06:00
|
|
|
type TierListTileProps = {
|
2024-11-16 23:11:31 -06:00
|
|
|
tank: TankDefinition;
|
2024-11-25 13:58:52 -06:00
|
|
|
} & (
|
|
|
|
|
| {
|
|
|
|
|
isPlaced?: false;
|
|
|
|
|
}
|
|
|
|
|
| {
|
|
|
|
|
isPlaced: true;
|
|
|
|
|
rowIndex: number;
|
|
|
|
|
tileIndex: number;
|
|
|
|
|
}
|
|
|
|
|
);
|
2024-11-16 23:11:31 -06:00
|
|
|
|
2024-11-25 13:58:52 -06:00
|
|
|
export function TierListTile(props: TierListTileProps) {
|
2024-11-16 23:11:31 -06:00
|
|
|
const card = useRef<HTMLDivElement>(null);
|
|
|
|
|
const lastPosition = useRef({ x: 0, y: 0 });
|
|
|
|
|
const handlePointerDown = useCallback(
|
|
|
|
|
(event: ReactPointerEvent<HTMLSpanElement>) => {
|
|
|
|
|
if (!card.current) return;
|
|
|
|
|
|
2024-11-17 16:34:18 -06:00
|
|
|
event.preventDefault();
|
|
|
|
|
|
2024-11-16 23:11:31 -06:00
|
|
|
const rect = card.current.getBoundingClientRect();
|
|
|
|
|
|
|
|
|
|
card.current.style.position = 'fixed';
|
|
|
|
|
card.current.style.cursor = 'grabbing';
|
|
|
|
|
card.current.style.zIndex = '1';
|
|
|
|
|
card.current.style.left = `${rect.left}px`;
|
|
|
|
|
card.current.style.top = `${rect.top}px`;
|
|
|
|
|
|
|
|
|
|
lastPosition.current = { x: event.clientX, y: event.clientY };
|
|
|
|
|
|
2024-11-26 20:23:20 -06:00
|
|
|
window.scrollTo({ behavior: 'smooth', top: 124 });
|
|
|
|
|
|
2024-11-16 23:11:31 -06:00
|
|
|
window.addEventListener('pointermove', handlePointerMove);
|
|
|
|
|
window.addEventListener('pointerup', handlePointerUp);
|
2024-11-17 16:34:18 -06:00
|
|
|
|
2025-09-01 18:03:07 -05:00
|
|
|
TierList.mutate((draft) => {
|
2024-11-17 16:34:18 -06:00
|
|
|
draft.dragging = true;
|
|
|
|
|
});
|
2024-11-16 23:11:31 -06:00
|
|
|
},
|
|
|
|
|
[],
|
|
|
|
|
);
|
|
|
|
|
const handlePointerMove = useCallback((event: PointerEvent) => {
|
|
|
|
|
if (!card.current) return;
|
|
|
|
|
|
2024-11-17 16:34:18 -06:00
|
|
|
event.preventDefault();
|
|
|
|
|
|
2024-11-16 23:11:31 -06:00
|
|
|
const rect = card.current.getBoundingClientRect();
|
|
|
|
|
const dx = event.clientX - lastPosition.current.x;
|
|
|
|
|
const dy = event.clientY - lastPosition.current.y;
|
|
|
|
|
|
|
|
|
|
card.current.style.left = `${rect.left + dx}px`;
|
|
|
|
|
card.current.style.top = `${rect.top + dy}px`;
|
|
|
|
|
|
|
|
|
|
lastPosition.current = { x: event.clientX, y: event.clientY };
|
|
|
|
|
}, []);
|
2024-12-15 19:30:22 -06:00
|
|
|
const handlePointerUp = useCallback(() => {
|
2024-11-16 23:11:31 -06:00
|
|
|
if (!card.current) return;
|
|
|
|
|
|
2024-11-25 13:58:52 -06:00
|
|
|
const cardRect = card.current.getBoundingClientRect();
|
|
|
|
|
const cardX = (cardRect.left + cardRect.right) / 2;
|
|
|
|
|
const cardY = (cardRect.top + cardRect.bottom) / 2;
|
2024-11-25 14:01:35 -06:00
|
|
|
let wasPlaced = false;
|
2024-11-17 16:34:18 -06:00
|
|
|
|
2024-11-25 13:58:52 -06:00
|
|
|
rowLoop: for (const row of tierListRowElements) {
|
|
|
|
|
const rowRect = row.getBoundingClientRect();
|
|
|
|
|
const isWithinRow =
|
|
|
|
|
cardX >= rowRect.left &&
|
|
|
|
|
cardX <= rowRect.right &&
|
|
|
|
|
cardY >= rowRect.top &&
|
|
|
|
|
cardY <= rowRect.bottom;
|
2024-11-17 16:34:18 -06:00
|
|
|
|
2024-11-25 13:58:52 -06:00
|
|
|
if (!isWithinRow) continue;
|
|
|
|
|
|
|
|
|
|
const rowIndex = Number(row.dataset.index);
|
|
|
|
|
|
|
|
|
|
for (const tile of tierListCardElements[rowIndex]) {
|
|
|
|
|
const tileId = Number(tile.dataset.tileId);
|
|
|
|
|
|
|
|
|
|
if (tileId === props.tank.id) continue;
|
|
|
|
|
|
|
|
|
|
const tileRect = tile.getBoundingClientRect();
|
|
|
|
|
const isWithinTile =
|
|
|
|
|
cardX >= tileRect.left &&
|
|
|
|
|
cardX <= tileRect.right &&
|
|
|
|
|
cardY >= tileRect.top &&
|
|
|
|
|
cardY <= tileRect.bottom;
|
|
|
|
|
|
|
|
|
|
if (!isWithinTile) continue;
|
|
|
|
|
|
|
|
|
|
const tileIndex = Number(tile.dataset.tileIndex);
|
|
|
|
|
|
2025-09-01 18:03:07 -05:00
|
|
|
TierList.mutate((draft) => {
|
2024-11-25 13:58:52 -06:00
|
|
|
draft.dragging = false;
|
2024-11-26 20:44:44 -06:00
|
|
|
draft.rows.forEach((row) => {
|
|
|
|
|
row.tanks = row.tanks.filter((tankId) => tankId !== props.tank.id);
|
|
|
|
|
});
|
|
|
|
|
draft.rows[rowIndex].tanks.splice(tileIndex, 0, props.tank.id);
|
2024-11-25 14:20:10 -06:00
|
|
|
draft.placedTanks.add(props.tank.id);
|
2024-11-25 13:58:52 -06:00
|
|
|
});
|
|
|
|
|
|
2024-11-25 14:01:35 -06:00
|
|
|
wasPlaced = true;
|
|
|
|
|
|
2024-11-25 13:58:52 -06:00
|
|
|
break rowLoop;
|
2024-11-17 16:34:18 -06:00
|
|
|
}
|
|
|
|
|
|
2025-09-01 18:03:07 -05:00
|
|
|
TierList.mutate((draft) => {
|
2024-11-25 13:58:52 -06:00
|
|
|
draft.dragging = false;
|
2024-11-26 20:44:44 -06:00
|
|
|
draft.rows.forEach((row) => {
|
|
|
|
|
row.tanks = row.tanks.filter((tankId) => tankId !== props.tank.id);
|
|
|
|
|
});
|
|
|
|
|
draft.rows[rowIndex].tanks.push(props.tank.id);
|
2024-11-25 14:20:10 -06:00
|
|
|
draft.placedTanks.add(props.tank.id);
|
2024-11-25 13:58:52 -06:00
|
|
|
});
|
2024-11-17 16:34:18 -06:00
|
|
|
|
2024-11-25 14:01:35 -06:00
|
|
|
wasPlaced = true;
|
|
|
|
|
|
2024-11-25 13:58:52 -06:00
|
|
|
break;
|
|
|
|
|
}
|
2024-11-17 16:34:18 -06:00
|
|
|
|
2024-11-25 14:01:35 -06:00
|
|
|
if (!wasPlaced) {
|
2025-09-01 18:03:07 -05:00
|
|
|
TierList.mutate((draft) => {
|
2024-11-25 14:01:35 -06:00
|
|
|
draft.dragging = false;
|
2024-11-26 20:44:44 -06:00
|
|
|
draft.rows.forEach((row) => {
|
|
|
|
|
row.tanks = row.tanks.filter((tankId) => tankId !== props.tank.id);
|
|
|
|
|
});
|
2024-11-25 14:20:10 -06:00
|
|
|
draft.placedTanks.delete(props.tank.id);
|
2024-11-25 14:01:35 -06:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-16 23:11:31 -06:00
|
|
|
card.current.style.position = 'static';
|
|
|
|
|
card.current.style.zIndex = 'unset';
|
|
|
|
|
card.current.style.cursor = 'grab';
|
2024-11-17 16:34:18 -06:00
|
|
|
|
|
|
|
|
window.removeEventListener('pointermove', handlePointerMove);
|
|
|
|
|
window.removeEventListener('pointerup', handlePointerUp);
|
2024-11-16 23:11:31 -06:00
|
|
|
}, []);
|
|
|
|
|
|
2024-11-25 13:58:52 -06:00
|
|
|
useEffect(() => {
|
|
|
|
|
if (!props.isPlaced || !card.current) return;
|
|
|
|
|
|
|
|
|
|
tierListCardElements[props.rowIndex].add(card.current);
|
|
|
|
|
|
|
|
|
|
return () => {
|
|
|
|
|
if (!card.current) return;
|
|
|
|
|
|
|
|
|
|
tierListCardElements[props.rowIndex].delete(card.current);
|
|
|
|
|
};
|
|
|
|
|
}, []);
|
|
|
|
|
|
2024-11-16 23:11:31 -06:00
|
|
|
return (
|
2024-11-26 20:24:36 -06:00
|
|
|
<Box
|
|
|
|
|
p="2"
|
|
|
|
|
ref={card}
|
2024-11-25 13:58:52 -06:00
|
|
|
data-tile-index={props.isPlaced ? props.tileIndex : undefined}
|
|
|
|
|
data-tile-id={props.tank.id}
|
2024-11-16 23:11:31 -06:00
|
|
|
onPointerDown={handlePointerDown}
|
2024-11-26 20:24:36 -06:00
|
|
|
style={{ cursor: 'grab', touchAction: 'none' }}
|
|
|
|
|
>
|
|
|
|
|
<TankCard noLink tank={props.tank} />
|
|
|
|
|
</Box>
|
2024-11-16 23:11:31 -06:00
|
|
|
);
|
|
|
|
|
}
|