140 lines
3.9 KiB
TypeScript
140 lines
3.9 KiB
TypeScript
import { useEffect, useState } from "react";
|
|
import { useTransform } from "./TransformContext";
|
|
import { SightData } from "./types";
|
|
import { Assets, FederatedMouseEvent, Texture } from "pixi.js";
|
|
|
|
import { SIGHT_SIZE, UP_SCALE } from "./Constants";
|
|
import { coordinatesToLocal, localToCoordinates } from "./utils";
|
|
import { useMapData } from "./MapDataContext";
|
|
|
|
interface SightProps {
|
|
sight: SightData;
|
|
id: number;
|
|
}
|
|
|
|
export const Sight = ({ sight, id }: Readonly<SightProps>) => {
|
|
const { rotation, scale } = useTransform();
|
|
const { setSightCoordinates, setSelectedSight } = useMapData();
|
|
|
|
const [position, setPosition] = useState(
|
|
coordinatesToLocal(sight.latitude, sight.longitude)
|
|
);
|
|
const [isDragging, setIsDragging] = useState(false);
|
|
const [isPointerDown, setIsPointerDown] = useState(false);
|
|
const [startPosition, setStartPosition] = useState({ x: 0, y: 0 });
|
|
const [startMousePosition, setStartMousePosition] = useState({ x: 0, y: 0 });
|
|
|
|
const handlePointerDown = (e: FederatedMouseEvent) => {
|
|
setIsPointerDown(true);
|
|
setIsDragging(false);
|
|
setStartPosition({
|
|
x: position.x,
|
|
y: position.y,
|
|
});
|
|
setStartMousePosition({
|
|
x: e.globalX,
|
|
y: e.globalY,
|
|
});
|
|
|
|
e.stopPropagation();
|
|
};
|
|
const handlePointerMove = (e: FederatedMouseEvent) => {
|
|
if (!isPointerDown) return;
|
|
|
|
if (!isDragging) {
|
|
const dx = e.globalX - startMousePosition.x;
|
|
const dy = e.globalY - startMousePosition.y;
|
|
if (Math.abs(dx) > 2 || Math.abs(dy) > 2) {
|
|
setIsDragging(true);
|
|
} else {
|
|
return;
|
|
}
|
|
}
|
|
|
|
const dx = (e.globalX - startMousePosition.x) / scale / UP_SCALE;
|
|
const dy = (e.globalY - startMousePosition.y) / scale / UP_SCALE;
|
|
const cos = Math.cos(rotation);
|
|
const sin = Math.sin(rotation);
|
|
const newPosition = {
|
|
x: startPosition.x + dx * cos + dy * sin,
|
|
y: startPosition.y - dx * sin + dy * cos,
|
|
};
|
|
setPosition(newPosition);
|
|
const coordinates = localToCoordinates(newPosition.x, newPosition.y);
|
|
setSightCoordinates(sight.id, coordinates.latitude, coordinates.longitude);
|
|
e.stopPropagation();
|
|
};
|
|
|
|
const handlePointerUp = (e: FederatedMouseEvent) => {
|
|
setIsPointerDown(false);
|
|
|
|
// Если не было перетаскивания, то это клик
|
|
if (!isDragging) {
|
|
setSelectedSight(sight);
|
|
}
|
|
|
|
setIsDragging(false);
|
|
e.stopPropagation();
|
|
};
|
|
|
|
const [texture, setTexture] = useState(Texture.EMPTY);
|
|
useEffect(() => {
|
|
Assets.load("/SightIcon.png").then(setTexture);
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
console.log(
|
|
`Rendering Sight ${id + 1} at [${sight.latitude}, ${sight.longitude}]`
|
|
);
|
|
}, [id, sight.latitude, sight.longitude]);
|
|
|
|
if (!sight) {
|
|
console.error("sight is null");
|
|
return null;
|
|
}
|
|
|
|
// Компенсируем масштаб для сохранения постоянного размера
|
|
const compensatedSize = SIGHT_SIZE / scale;
|
|
const compensatedFontSize = 24 / scale;
|
|
|
|
return (
|
|
<pixiContainer
|
|
rotation={-rotation}
|
|
eventMode="static"
|
|
interactive
|
|
onPointerDown={handlePointerDown}
|
|
onGlobalPointerMove={handlePointerMove}
|
|
onPointerUp={handlePointerUp}
|
|
onPointerUpOutside={handlePointerUp}
|
|
x={position.x * UP_SCALE - SIGHT_SIZE / 2}
|
|
y={position.y * UP_SCALE - SIGHT_SIZE / 2}
|
|
>
|
|
<pixiSprite
|
|
texture={texture}
|
|
width={compensatedSize}
|
|
height={compensatedSize}
|
|
/>
|
|
<pixiGraphics
|
|
draw={(g) => {
|
|
g.clear();
|
|
g.circle(0, 0, 20 / scale);
|
|
g.fill({ color: "#000" });
|
|
}}
|
|
x={compensatedSize}
|
|
y={0}
|
|
/>
|
|
<pixiText
|
|
text={`${id + 1}`}
|
|
x={compensatedSize + 1 / scale}
|
|
y={0}
|
|
anchor={0.5}
|
|
style={{
|
|
fontSize: compensatedFontSize,
|
|
fontWeight: "bold",
|
|
fill: "#ffffff",
|
|
}}
|
|
/>
|
|
</pixiContainer>
|
|
);
|
|
};
|