Add hover effects and dynamic anchor computation to Station component

- Integrated `onTextHover` callback for handling text hover states.
- Implemented dynamic anchor calculation using `useMemo` for improved positioning.
- Updated visual feedback by highlighting labels on hover.
- Adjusted offset calculations and added interactive pointer movement refinements.
- Added `packageManager` field to `package.json`.
This commit is contained in:
2025-07-09 21:12:54 +03:00
parent e2547cb571
commit 2d4a1e169b
2 changed files with 56 additions and 17 deletions

View File

@ -56,5 +56,6 @@
"typescript": "~5.8.3",
"typescript-eslint": "^8.30.1",
"vite": "^6.3.5"
}
},
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
}

View File

@ -1,5 +1,5 @@
import { FederatedMouseEvent, Graphics } from "pixi.js";
import { useCallback, useState, useEffect, useRef, FC } from "react";
import {useCallback, useState, useEffect, useRef, FC, useMemo} from "react";
import { observer } from "mobx-react-lite";
// --- Заглушки для зависимостей (замените на ваши реальные импорты) ---
@ -67,6 +67,8 @@ interface StationProps {
labelAlign?: LabelAlign;
/** Callback для изменения внутреннего выравнивания */
onLabelAlignChange?: (align: LabelAlign) => void;
/** Callback для отслеживания наведения на текст */
onTextHover?: (isHovered: boolean) => void;
}
interface LabelAlignmentControlProps {
@ -82,6 +84,18 @@ interface LabelAlignmentControlProps {
interface StationLabelProps
extends Omit<StationProps, "ruLabelAnchor" | "nameLabelAnchor"> {}
const getAnchorFromOffset = (offsetX: number, offsetY: number): { x: number; y: number } => {
if (offsetX === 0 && offsetY === 0) {
return { x: 0.5, y: 0.5 };
}
const length = Math.hypot(offsetX, offsetY);
const nx = offsetX / length;
const ny = offsetY / length;
return { x: (1 - nx) / 2, y: (1 - ny) / 2 };
};
// =========================================================================
// Компонент: Панель управления выравниванием в стиле УрФУ
// =========================================================================
@ -240,6 +254,7 @@ const StationLabel = observer(
labelBlockAnchor: labelBlockAnchorProp,
labelAlign: labelAlignProp = "center",
onLabelAlignChange,
onTextHover,
}: Readonly<StationLabelProps>) => {
const { language } = languageStore;
const { rotation, scale } = useTransform();
@ -272,6 +287,7 @@ const StationLabel = observer(
hideTimer.current = null;
}
setIsHovered(true);
onTextHover?.(true); // Call the callback to indicate text is hovered
};
const handleControlPointerEnter = () => {
@ -282,6 +298,7 @@ const StationLabel = observer(
}
setIsControlHovered(true);
setIsHovered(true);
onTextHover?.(true); // Call the callback to indicate text/control is hovered
};
const handleControlPointerLeave = () => {
@ -290,6 +307,7 @@ const StationLabel = observer(
if (!isHovered) {
hideTimer.current = setTimeout(() => {
setIsHovered(false);
onTextHover?.(false); // Call the callback to indicate neither text nor control is hovered
}, 0);
}
};
@ -302,6 +320,7 @@ const StationLabel = observer(
if (!isControlHovered) {
setIsControlHovered(false);
}
onTextHover?.(false); // Call the callback to indicate text is no longer hovered
}, 100); // Увеличиваем время до скрытия панели
};
@ -347,7 +366,7 @@ const StationLabel = observer(
const compensatedRuFontSize = (26 * 0.75) / scale;
const compensatedNameFontSize = (16 * 0.75) / scale;
const minDistance = 30;
const compensatedOffset = Math.max(minDistance / scale, 24 / scale);
const compensatedOffset = minDistance / scale;
const textBlockPosition = anchorPoint || position;
const finalLabelBlockAnchor = labelBlockAnchorProp || "right center";
const labelBlockAnchor =
@ -372,17 +391,20 @@ const StationLabel = observer(
const handlePointerMove = (e: FederatedMouseEvent) => {
if (!isPointerDown) return;
if (!isDragging) {
const dx = e.global.x - mouseStartPos.current.x;
const dy = e.global.y - mouseStartPos.current.y;
if (Math.hypot(dx, dy) > 3) setIsDragging(true);
else return;
}
const dx = (e.global.x - mouseStartPos.current.x) / scale;
const dy = (e.global.y - mouseStartPos.current.y) / scale;
const dx_screen = e.global.x - mouseStartPos.current.x;
const dy_screen = e.global.y - mouseStartPos.current.y;
const newPosition = {
x: dragStartPos.current.x + dx,
y: dragStartPos.current.y + dy,
x: dragStartPos.current.x + dx_screen,
y: dragStartPos.current.y + dy_screen,
};
// Проверяем, изменилась ли позиция
@ -410,6 +432,11 @@ const StationLabel = observer(
setStationAlign(station.id, numericAlign);
};
const dynamicAnchor = useMemo(
() => getAnchorFromOffset(position.x, position.y),
[position.x, position.y]
);
// Функция для расчета позиции нижнего лейбла относительно ширины верхнего
const getSecondLabelPosition = (): number => {
if (!ruLabelWidth) return 0;
@ -459,13 +486,11 @@ const StationLabel = observer(
onGlobalPointerMove={handlePointerMove}
>
<pixiContainer
position={{
x:
textBlockPosition.x +
compensatedOffset * (labelBlockAnchor.x === 1 ? -1 : 1),
y: textBlockPosition.y,
}}
anchor={labelBlockAnchor}
position={{
x: (position.x + Math.cos(Math.atan2(position.y, position.x))) / scale,
y: (position.y + Math.sin(Math.atan2(position.y, position.x))) / scale,
}}
anchor={dynamicAnchor}
>
{ruLabel && (
<pixiText
@ -524,6 +549,8 @@ export const Station = ({
labelAlign,
onLabelAlignChange,
}: Readonly<StationProps>) => {
const [isTextHovered, setIsTextHovered] = useState(false);
const draw = useCallback(
(g: Graphics) => {
g.clear();
@ -531,12 +558,22 @@ export const Station = ({
station.latitude,
station.longitude
);
const radius = STATION_RADIUS;
const strokeWidth = STATION_OUTLINE_WIDTH;
g.circle(coordinates.x * UP_SCALE, coordinates.y * UP_SCALE, radius);
g.fill({ color: PATH_COLOR });
g.stroke({ color: BACKGROUND_COLOR, width: STATION_OUTLINE_WIDTH });
// Change fill color when text is hovered
if (isTextHovered) {
g.fill({ color: 0x00AAFF }); // Highlight color when hovered
g.stroke({ color: 0xFFFFFF, width: strokeWidth + 1 }); // Brighter outline when hovered
} else {
g.fill({ color: PATH_COLOR });
g.stroke({ color: BACKGROUND_COLOR, width: strokeWidth });
}
},
[station.latitude, station.longitude]
[station.latitude, station.longitude, isTextHovered]
);
return (
@ -549,6 +586,7 @@ export const Station = ({
labelBlockAnchor={labelBlockAnchor}
labelAlign={labelAlign}
onLabelAlignChange={onLabelAlignChange}
onTextHover={setIsTextHovered}
/>
</pixiContainer>
);